ManaPlus
textbox.cpp
Go to the documentation of this file.
1 /*
2  * The ManaPlus Client
3  * Copyright (C) 2004-2009 The Mana World Development Team
4  * Copyright (C) 2009-2010 The Mana Developers
5  * Copyright (C) 2011-2019 The ManaPlus Developers
6  * Copyright (C) 2019-2021 Andrei Karas
7  *
8  * This file is part of The ManaPlus Client.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program. If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /* _______ __ __ __ ______ __ __ _______ __ __
25  * / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___ /\ / |\/ /\
26  * / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / /
27  * / / /__ / / // / // / // / / / ___ / // ___ / // /| ' / /
28  * / /_// /\ / /_// / // / // /_/_ / / // / // /\_/ / // / | / /
29  * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ /
30  * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/
31  *
32  * Copyright (c) 2004 - 2008 Olof Naessén and Per Larsson
33  *
34  *
35  * Per Larsson a.k.a finalman
36  * Olof Naessén a.k.a jansem/yakslem
37  *
38  * Visit: http://guichan.sourceforge.net
39  *
40  * License: (BSD)
41  * Redistribution and use in source and binary forms, with or without
42  * modification, are permitted provided that the following conditions
43  * are met:
44  * 1. Redistributions of source code must retain the above copyright
45  * notice, this list of conditions and the following disclaimer.
46  * 2. Redistributions in binary form must reproduce the above copyright
47  * notice, this list of conditions and the following disclaimer in
48  * the documentation and/or other materials provided with the
49  * distribution.
50  * 3. Neither the name of Guichan nor the names of its contributors may
51  * be used to endorse or promote products derived from this software
52  * without specific prior written permission.
53  *
54  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
55  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
56  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
57  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
58  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
59  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
60  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
61  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
62  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
63  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65  */
66 
67 #include "gui/widgets/textbox.h"
68 
69 #include "gui/gui.h"
70 
71 #include "gui/fonts/font.h"
72 
73 #include "render/graphics.h"
74 
75 #include <sstream>
76 
77 #include "debug.h"
78 
79 TextBox::TextBox(const Widget2 *const widget) :
80  Widget(widget),
81  MouseListener(),
82  KeyListener(),
83  mTextRows(),
84  mCaretColumn(0),
85  mCaretRow(0),
86  mMinWidth(getWidth()),
87  mEditable(true),
88  mOpaque(Opaque_true)
89 {
90  mAllowLogic = false;
91  setText("");
92  setFocusable(true);
93 
94  addMouseListener(this);
95  addKeyListener(this);
96  adjustSize();
97 
98  mForegroundColor = getThemeColor(ThemeColorId::TEXTBOX, 255U);
100  setFrameSize(0);
101 }
102 
104 {
105  if (gui != nullptr)
106  gui->removeDragged(this);
107 }
108 
109 void TextBox::setTextWrapped(const std::string &text, const int minDimension)
110 {
111  // Make sure parent scroll area sets width of this widget
112  if (getParent() != nullptr)
113  getParent()->logic();
114 
115  // Take the supplied minimum dimension as a starting
116  // point and try to beat it
117  mMinWidth = minDimension;
118 
119  const size_t textSize = text.size();
120  size_t spacePos = text.rfind(' ', textSize);
121 
122  if (spacePos != std::string::npos)
123  {
124  const std::string word = text.substr(spacePos + 1);
125  const int length = getFont()->getWidth(word);
126 
127  if (length > mMinWidth)
128  mMinWidth = length;
129  }
130 
131  std::stringstream wrappedStream;
132  size_t newlinePos;
133  size_t lastNewlinePos = 0;
134  int minWidth = 0;
135  int xpos;
136 
137  do
138  {
139  // Determine next piece of string to wrap
140  newlinePos = text.find('\n', lastNewlinePos);
141 
142  if (newlinePos == std::string::npos)
143  newlinePos = textSize;
144 
145  std::string line =
146  text.substr(lastNewlinePos, newlinePos - lastNewlinePos);
147  size_t lastSpacePos = 0;
148  xpos = 0;
149  const Font *const font = getFont();
150  const int spaceWidth = font->getWidth(" ");
151  size_t sz = line.size();
152 
153  do
154  {
155  spacePos = line.find(' ', lastSpacePos);
156 
157  if (spacePos == std::string::npos)
158  spacePos = sz;
159 
160  const std::string word =
161  line.substr(lastSpacePos, spacePos - lastSpacePos);
162 
163  const int width = font->getWidth(word);
164 
165  if (xpos == 0 && width > mMinWidth)
166  {
167  mMinWidth = width;
168  xpos = width;
169  wrappedStream << word;
170  }
171  else if (xpos != 0 && xpos + spaceWidth + width <=
172  mMinWidth)
173  {
174  xpos += spaceWidth + width;
175  wrappedStream << " " << word;
176  }
177  else if (lastSpacePos == 0)
178  {
179  xpos += width;
180  wrappedStream << word;
181  }
182  else
183  {
184  if (xpos > minWidth)
185  minWidth = xpos;
186 
187  // The window wasn't big enough. Resize it and try again.
188  if (minWidth > mMinWidth)
189  {
190  mMinWidth = minWidth;
191  wrappedStream.clear();
192  wrappedStream.str("");
193  lastNewlinePos = 0;
194  newlinePos = text.find('\n', lastNewlinePos);
195  if (newlinePos == std::string::npos)
196  newlinePos = textSize;
197  line = text.substr(lastNewlinePos, newlinePos -
198  lastNewlinePos);
199  sz = line.size();
200  break;
201  }
202  else
203  {
204  wrappedStream << "\n" << word;
205  }
206  xpos = width;
207  }
208  lastSpacePos = spacePos + 1;
209  }
210  while (spacePos != sz);
211 
212  if (text.find('\n', lastNewlinePos) != std::string::npos)
213  wrappedStream << "\n";
214 
215  lastNewlinePos = newlinePos + 1;
216  }
217  while (newlinePos != textSize);
218 
219  if (xpos > minWidth)
220  minWidth = xpos;
221 
222  mMinWidth = minWidth;
223 
224  setText(wrappedStream.str());
225 }
226 
227 void TextBox::setText(const std::string& text)
228 {
229  mCaretColumn = 0;
230  mCaretRow = 0;
231 
232  mTextRows.clear();
233  if (text.empty())
234  {
235  adjustSize();
236  return;
237  }
238 
239  size_t pos;
240  size_t lastPos = 0;
241  int length;
242  do
243  {
244  pos = text.find('\n', lastPos);
245 
246  if (pos != std::string::npos)
247  length = CAST_S32(pos - lastPos);
248  else
249  length = CAST_S32(text.size() - lastPos);
250  std::string sub = text.substr(lastPos, length);
251  mTextRows.push_back(sub);
252  lastPos = pos + 1;
253  } while (pos != std::string::npos);
254 
255  adjustSize();
256 }
257 
259 {
260  const Key &key = event.getKey();
261  const InputActionT action = event.getActionId();
262 
263  PRAGMA45(GCC diagnostic push)
264  PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
265  switch (action)
266  {
268  {
269  --mCaretColumn;
270  if (mCaretColumn < 0)
271  {
272  --mCaretRow;
273 
274  if (mCaretRow < 0)
275  {
276  mCaretRow = 0;
277  mCaretColumn = 0;
278  }
279  else
280  {
283  }
284  }
285  break;
286  }
287 
289  {
290  ++mCaretColumn;
292  {
293  ++ mCaretRow;
294 
295  const int sz = CAST_S32(mTextRows.size());
296  if (mCaretRow >= sz)
297  {
298  mCaretRow = sz - 1;
299  if (mCaretRow < 0)
300  mCaretRow = 0;
301 
304  }
305  else
306  {
307  mCaretColumn = 0;
308  }
309  }
310  break;
311  }
312 
314  {
315  setCaretRow(mCaretRow + 1);
316  break;
317  }
318  case InputAction::GUI_UP:
319  {
320  setCaretRow(mCaretRow - 1);
321  break;
322  }
324  {
325  mCaretColumn = 0;
326  break;
327  }
329  {
331  break;
332  }
333 
335  {
336  if (mEditable)
337  {
338  mTextRows.insert(mTextRows.begin() + mCaretRow + 1,
340  mTextRows[mCaretRow].size() - mCaretColumn));
342  ++mCaretRow;
343  mCaretColumn = 0;
344  }
345  break;
346  }
347 
349  {
350  if (mCaretColumn != 0 && mEditable)
351  {
352  mTextRows[mCaretRow].erase(mCaretColumn - 1, 1);
353  --mCaretColumn;
354  }
355  else if (mCaretColumn == 0 && mCaretRow != 0 && mEditable)
356  {
358  mTextRows[mCaretRow - 1].size());
360  mTextRows.erase(mTextRows.begin() + mCaretRow);
361  --mCaretRow;
362  }
363  break;
364  }
365 
367  {
368  if (mCaretColumn < CAST_S32(
370  {
371  mTextRows[mCaretRow].erase(mCaretColumn, 1);
372  }
373  else if (mCaretColumn == CAST_S32(
374  mTextRows[mCaretRow].size()) &&
375  mCaretRow < (CAST_S32(mTextRows.size()) - 1) &&
376  mEditable)
377  {
379  mTextRows.erase(mTextRows.begin() + mCaretRow + 1);
380  }
381  break;
382  }
383 
385  {
386  Widget *const par = getParent();
387 
388  if (par != nullptr)
389  {
390  const int rowsPerPage = par->getChildrenArea().height
391  / getFont()->getHeight();
392  mCaretRow -= rowsPerPage;
393 
394  if (mCaretRow < 0)
395  mCaretRow = 0;
396  }
397  break;
398  }
399 
401  {
402  Widget *const par = getParent();
403 
404  if (par != nullptr)
405  {
406  const int rowsPerPage = par->getChildrenArea().height
407  / getFont()->getHeight();
408  mCaretRow += rowsPerPage;
409 
410  const int sz = CAST_S32(mTextRows.size());
411  if (mCaretRow >= sz)
412  mCaretRow = sz - 1;
413  }
414  break;
415  }
416 
418  {
419  if (mEditable)
420  {
421  mTextRows[mCaretRow].insert(mCaretColumn, std::string(" "));
422  mCaretColumn += 4;
423  }
424  break;
425  }
426 
427  default:
428  {
429  if (key.isCharacter() && mEditable)
430  {
432  std::string(1, CAST_S8(key.getValue())));
433  ++ mCaretColumn;
434  }
435  break;
436  }
437  }
438  PRAGMA45(GCC diagnostic pop)
439 
440  adjustSize();
441  scrollToCaret();
442 
443  event.consume();
444 }
445 
446 void TextBox::draw(Graphics *const graphics)
447 {
448  BLOCK_START("TextBox::draw")
449  if (mOpaque == Opaque_true)
450  {
451  graphics->setColor(mBackgroundColor);
452  graphics->fillRectangle(Rect(0, 0, getWidth(), getHeight()));
453  }
454 
455  Font *const font = getFont();
456  if (isFocused() && isEditable())
457  {
458  drawCaret(graphics, font->getWidth(
459  mTextRows[mCaretRow].substr(0, mCaretColumn)),
460  mCaretRow * font->getHeight());
461  }
462 
463  const int fontHeight = font->getHeight();
464 
465  for (size_t i = 0, sz = mTextRows.size(); i < sz; i++)
466  {
467  // Move the text one pixel so we can have a caret before a letter.
468  font->drawString(graphics,
471  mTextRows[i], 1,
472  CAST_S32(i * CAST_SIZE(fontHeight)));
473  }
474  BLOCK_END("TextBox::draw")
475 }
476 
477 void TextBox::safeDraw(Graphics *const graphics)
478 {
479  TextBox::draw(graphics);
480 }
481 
483 {
484  mForegroundColor = color;
485  mForegroundColor2 = color;
486 }
487 
489  const Color &color2)
490 {
491  mForegroundColor = color1;
492  mForegroundColor2 = color2;
493 }
494 
495 std::string TextBox::getText() const
496 {
497  if (mTextRows.empty())
498  return std::string();
499 
500  int i;
501  std::string text;
502 
503  const int sz = CAST_S32(mTextRows.size());
504  for (i = 0; i < sz - 1; ++ i)
505  text.append(mTextRows[i]).append("\n");
506  text.append(mTextRows[i]);
507 
508  return text;
509 }
510 
511 void TextBox::setTextRow(const int row, const std::string& text)
512 {
513  mTextRows[row] = text;
514 
515  if (mCaretRow == row)
517 
518  adjustSize();
519 }
520 
521 void TextBox::setCaretPosition(unsigned int position)
522 {
523  for (int row = 0, fsz = CAST_S32(mTextRows.size());
524  row < fsz;
525  row ++)
526  {
527  if (position <= mTextRows[row].size())
528  {
529  mCaretRow = row;
530  mCaretColumn = position;
531  return; // we are done
532  }
533 
534  position--;
535  }
536 
537  // position beyond end of text
538  mCaretRow = CAST_S32(mTextRows.size() - 1);
540 }
541 
542 void TextBox::setCaretRow(const int row)
543 {
544  mCaretRow = row;
545 
546  const int sz = CAST_S32(mTextRows.size());
547  if (mCaretRow >= sz)
548  mCaretRow = sz - 1;
549 
550  if (mCaretRow < 0)
551  mCaretRow = 0;
552 
554 }
555 
556 unsigned int TextBox::getCaretPosition() const
557 {
558  int pos = 0;
559 
560  for (int row = 0; row < mCaretRow; row++)
561  pos += CAST_S32(mTextRows[row].size());
562 
563  return pos + mCaretColumn;
564 }
565 
566 void TextBox::setCaretColumn(const int column)
567 {
568  mCaretColumn = column;
569 
570  const int sz = CAST_S32(mTextRows[mCaretRow].size());
571  if (mCaretColumn > sz)
572  mCaretColumn = sz;
573 
574  if (mCaretColumn < 0)
575  mCaretColumn = 0;
576 }
577 
578 void TextBox::setCaretRowColumn(const int row, const int column)
579 {
580  setCaretRow(row);
581  setCaretColumn(column);
582 }
583 
585 {
586  const Font *const font = getFont();
587  Rect scroll;
588  scroll.x = font->getWidth(mTextRows[mCaretRow].substr(0, mCaretColumn));
589  scroll.y = font->getHeight() * mCaretRow;
590  scroll.width = font->getWidth(" ");
591  // add 2 for some extra space
592  scroll.height = font->getHeight() + 2;
593  showPart(scroll);
594 }
595 
596 void TextBox::addRow(const std::string &row)
597 {
598  mTextRows.push_back(row);
599  adjustSize();
600 }
601 
603 {
604  if (event.getButton() == MouseButton::LEFT)
605  {
606  const int height = getFont()->getHeight();
607  if (height == 0)
608  return;
609 
610  event.consume();
611  mCaretRow = event.getY() / height;
612 
613  const int sz = CAST_S32(mTextRows.size());
614  if (mCaretRow >= sz)
615  mCaretRow = sz - 1;
616 
618  mTextRows[mCaretRow], event.getX());
619  }
620 }
621 
623 {
624  event.consume();
625 }
626 
627 void TextBox::drawCaret(Graphics *const graphics,
628  const int x,
629  const int y) const
630 {
631  graphics->setColor(mForegroundColor);
632  graphics->drawLine(x, getFont()->getHeight() + y, x, y);
633 }
634 
636 {
637  int width = 0;
638  const Font *const font = getFont();
639  for (size_t i = 0, sz = mTextRows.size(); i < sz; ++i)
640  {
641  const int w = font->getWidth(mTextRows[i]);
642  if (width < w)
643  width = w;
644  }
645 
646  setWidth(width + 1);
647  setHeight(font->getHeight() * CAST_S32(mTextRows.size()));
648 }
649 
650 void TextBox::setEditable(const bool editable)
651 {
652  mEditable = editable;
653  mSelectable = editable;
654 }
#define CAST_S8
Definition: cast.h:26
#define CAST_S32
Definition: cast.h:30
#define CAST_SIZE
Definition: cast.h:34
Definition: color.h:76
Definition: font.h:90
int getHeight() const
Definition: font.cpp:362
int getStringIndexAt(const std::string &text, const int x) const
Definition: font.cpp:413
int getWidth(const std::string &text) const
Definition: font.cpp:334
void drawString(Graphics *const graphics, Color col, const Color &col2, const std::string &text, const int x, const int y)
Definition: font.cpp:254
virtual void fillRectangle(const Rect &rectangle)=0
virtual void setColor(const Color &color)
Definition: graphics.h:320
virtual void drawLine(int x1, int y1, int x2, int y2)=0
void removeDragged(const Widget *const widget)
Definition: gui.cpp:1162
Definition: key.h:81
bool isCharacter() const
Definition: key.cpp:78
int getValue() const
Definition: key.cpp:99
MouseButtonT getButton() const
Definition: mouseevent.h:116
int getX() const
Definition: mouseevent.h:127
Definition: rect.h:74
int y
Definition: rect.h:214
int width
Definition: rect.h:219
int x
Definition: rect.h:209
int height
Definition: rect.h:224
int mMinWidth
Definition: textbox.h:316
void safeDraw(Graphics *const graphics)
Definition: textbox.cpp:477
void mouseDragged(MouseEvent &event)
Definition: textbox.cpp:622
std::vector< std::string > mTextRows
Definition: textbox.h:304
void setCaretPosition(unsigned int position)
Definition: textbox.cpp:521
void setEditable(const bool editable)
Definition: textbox.cpp:650
std::string getText() const
Definition: textbox.cpp:495
void setCaretColumn(const int column)
Definition: textbox.cpp:566
void setCaretRowColumn(const int row, const int column)
Definition: textbox.cpp:578
void setOpaque(const Opaque opaque)
Definition: textbox.h:273
void setTextRow(const int row, const std::string &text)
Definition: textbox.cpp:511
void addRow(const std::string &row)
Definition: textbox.cpp:596
void adjustSize()
Definition: textbox.cpp:635
void setCaretRow(const int row)
Definition: textbox.cpp:542
void keyPressed(KeyEvent &event)
Definition: textbox.cpp:258
void setForegroundColor(const Color &color)
Definition: textbox.cpp:482
void mousePressed(MouseEvent &event)
Definition: textbox.cpp:602
void scrollToCaret()
Definition: textbox.cpp:584
void drawCaret(Graphics *const graphics, const int x, const int y) const
Definition: textbox.cpp:627
void setForegroundColorAll(const Color &color1, const Color &color2)
Definition: textbox.cpp:488
void draw(Graphics *const graphics)
Definition: textbox.cpp:446
int mCaretRow
Definition: textbox.h:314
unsigned int getCaretPosition() const
Definition: textbox.cpp:556
void setText(const std::string &text)
Definition: textbox.cpp:227
Opaque mOpaque
Definition: textbox.h:326
~TextBox()
Definition: textbox.cpp:103
void setTextWrapped(const std::string &text, const int minDimension)
Definition: textbox.cpp:109
bool mEditable
Definition: textbox.h:321
bool isEditable() const
Definition: textbox.h:237
TextBox(const Widget2 *const widget)
Definition: textbox.cpp:79
int mCaretColumn
Definition: textbox.h:309
Color mForegroundColor2
Definition: widget2.h:113
const Color & getThemeColor(const ThemeColorIdT type, const unsigned int alpha) const A_INLINE
Definition: widget2.h:45
Definition: widget.h:99
Color mForegroundColor
Definition: widget.h:1086
virtual Rect getChildrenArea()
Definition: widget.cpp:451
void setFrameSize(const unsigned int frameSize)
Definition: widget.h:168
void setFocusable(const bool focusable)
Definition: widget.cpp:192
void setWidth(const int width)
Definition: widget.cpp:133
virtual void logic()
Definition: widget.h:193
virtual void showPart(const Rect &rectangle)
Definition: widget.cpp:511
Color mBackgroundColor
Definition: widget.h:1091
bool mAllowLogic
Definition: widget.h:1160
void addMouseListener(MouseListener *const mouseListener)
Definition: widget.cpp:292
bool mSelectable
Definition: widget.h:1166
void setHeight(const int height)
Definition: widget.cpp:140
void addKeyListener(KeyListener *const keyListener)
Definition: widget.cpp:272
Font * getFont() const
Definition: widget.cpp:331
Widget * getParent() const
Definition: widget.h:202
int getHeight() const
Definition: widget.h:240
int getWidth() const
Definition: widget.h:221
virtual bool isFocused() const
Definition: widget.cpp:184
PRAGMA45(GCC diagnostic push) PRAGMA45(GCC diagnostic ignored "-Wredundant-decls") PRAGMA45(GCC diagnostic pop) class TestMain
Gui * gui
Definition: gui.cpp:111
InputAction ::T InputActionT
Definition: inputaction.h:717
int size()
Definition: emotedb.cpp:306
const bool Opaque_false
Definition: opaque.h:30
const bool Opaque_true
Definition: opaque.h:30
#define BLOCK_END(name)
Definition: perfomance.h:80
#define BLOCK_START(name)
Definition: perfomance.h:79