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