TDME2  1.9.200
GUIStyledTextNodeController.cpp
Go to the documentation of this file.
2 
3 #include <algorithm>
4 #include <string>
5 #include <vector>
6 
7 #include <tdme/tdme.h>
12 #include <tdme/gui/nodes/GUINode.h>
15 #include <tdme/gui/GUI.h>
16 #include <tdme/math/Math.h>
18 #include <tdme/utilities/Console.h>
20 #include <tdme/utilities/Time.h>
21 
22 using std::remove;
23 using std::string;
24 using std::to_string;
25 using std::vector;
26 
34 using tdme::gui::GUI;
39 
40 GUIStyledTextNodeController::GUIStyledTextNodeController(GUINode* node)
41  : GUINodeController(node)
42 {
43 }
44 
46 {
47  cursorModeStarted = Time::getCurrentMillis();
49 }
50 
52 {
53  if (cursorModeStarted == -1) {
55  return cursorMode;
56  }
57  if (Time::getCurrentMillis() - cursorModeStarted > CURSOR_MODE_DURATION) {
59  cursorModeStarted = Time::getCurrentMillis();
60  }
61  return cursorMode;
62 }
63 
65 {
66  return false;
67 }
68 
70 {
71 }
72 
74 {
75  auto inputControllerNodeCandidate = node->getParentControllerNode();
76  while (inputControllerNodeCandidate != nullptr) {
77  if (dynamic_cast<GUIStyledInputController*>(inputControllerNodeCandidate->getController()) != nullptr) {
78  input = required_dynamic_cast<GUIStyledTextNode*>(this->node)->isEditable();
79  //
80  break;
81  }
82  inputControllerNodeCandidate = inputControllerNodeCandidate->getParentControllerNode();
83  }
84 }
85 
87 {
88 }
89 
91 {
92  //
93  if (input == false) return;
94 
95  // extend styledTextNode auto width to parent width if this is larger
96  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
97  if (styledTextNode->getRequestsConstraints().widthType == GUINode_RequestedConstraints_RequestedConstraintsType::AUTO) {
98  const auto& styledTextNodeBorder = styledTextNode->getBorder();
99  const auto& styledTextNodePadding = styledTextNode->getPadding();
100  auto styledTextNodeAutoWidth = styledTextNode->getContentWidth();
101  auto parentNode = styledTextNode->getParentNode();
102  auto parentNodeWidth = parentNode->getComputedConstraints().width;
103  const auto& parentNodeBorder = parentNode->getBorder();
104  const auto& parentNodePadding = parentNode->getPadding();
105  if (parentNodeWidth > styledTextNodeAutoWidth) {
106  styledTextNode->getComputedConstraints().width =
107  parentNodeWidth
108  - (parentNodeBorder.left + parentNodeBorder.right + parentNodePadding.left + parentNodePadding.right)
109  - (styledTextNodeBorder.left + styledTextNodeBorder.right + styledTextNodePadding.left + styledTextNodePadding.right);
110  }
111  }
112  if (styledTextNode->getRequestsConstraints().heightType == GUINode_RequestedConstraints_RequestedConstraintsType::AUTO) {
113  const auto& styledTextNodeBorder = styledTextNode->getBorder();
114  const auto& styledTextNodePadding = styledTextNode->getPadding();
115  auto styledTextNodeAutoHeight = styledTextNode->getContentHeight();
116  auto parentNode = styledTextNode->getParentNode();
117  auto parentNodeHeight = parentNode->getComputedConstraints().height;
118  const auto& parentNodeBorder = parentNode->getBorder();
119  const auto& parentNodePadding = parentNode->getPadding();
120  if (parentNodeHeight > styledTextNodeAutoHeight) {
121  styledTextNode->getComputedConstraints().height =
122  parentNodeHeight
123  - (parentNodeBorder.top + parentNodeBorder.bottom + parentNodePadding.top + parentNodePadding.bottom)
124  - (styledTextNodeBorder.top + styledTextNodeBorder.bottom + styledTextNodePadding.top + styledTextNodePadding.bottom);
125  }
126  }
127 }
128 
130 {
131  //
132  auto released = false;
133  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
134  if (node == styledTextNode) {
135  Vector2 nodeMouseCoordinate;
136  Vector2 nodeMouseCoordinateNoOffsets;
137  if (styledTextNode->isEventBelongingToNode(event, nodeMouseCoordinate) == true) {
138  nodeMouseCoordinateNoOffsets = nodeMouseCoordinate.clone().sub(Vector2(styledTextNode->getParentNode()->getChildrenRenderOffsetX(), styledTextNode->getParentNode()->getChildrenRenderOffsetY()));
139  switch(event->getType()) {
140  case GUIMouseEvent::MOUSEEVENT_PRESSED:
141  {
142  //
143  if (input == true) storeTypingHistoryEntry();
144  //
145  if (timeLastClick != -1LL &&
146  Time::getCurrentMillis() - timeLastClick < TIME_DOUBLECLICK) {
147  doubleClick = true;
148  timeLastClick = -1LL;
149  } else {
150  timeLastClick = Time::getCurrentMillis();
151  doubleClick = false;
152  }
153 
154  // submit to styled text node
155  selectionIndex = -1;
156  styledTextNode->setIndexMousePosition(nodeMouseCoordinateNoOffsets.getX(), nodeMouseCoordinateNoOffsets.getY());
157  //
158  resetCursorMode();
159  //
160  event->setProcessed(true);
161 
162  //
163  break;
164  }
165  case GUIMouseEvent::MOUSEEVENT_MOVED:
166  {
167  // find URL area that had a hit and setup corresponding cursor
168  const auto& urlAreas = styledTextNode->getURLAreas();
169  const GUIStyledTextNode::URLArea* urlAreaHit = nullptr;
170  for (auto& urlArea: urlAreas) {
171  if (nodeMouseCoordinate.getX() < urlArea.left ||
172  nodeMouseCoordinate.getY() < urlArea.top ||
173  nodeMouseCoordinate.getX() > urlArea.left + urlArea.width ||
174  nodeMouseCoordinate.getY() > urlArea.top + urlArea.height) {
175  continue;
176  }
177  urlAreaHit = &urlArea;
178  if (Application::getMouseCursor() != MOUSE_CURSOR_HAND) {
179  Application::setMouseCursor(MOUSE_CURSOR_HAND);
180  Console::println("hand: " + node->getId() + "(" + urlAreaHit->url + ")");
181  }
182  break;
183  }
184  if (urlAreaHit == nullptr) {
185  if (Application::getMouseCursor() != MOUSE_CURSOR_ENABLED) {
186  Application::setMouseCursor(MOUSE_CURSOR_ENABLED);
187  Console::println("normal: " + node->getId());
188  }
189  }
190 
191  //
192  event->setProcessed(true);
193 
194  //
195  break;
196  }
197  case GUIMouseEvent::MOUSEEVENT_RELEASED:
198  {
199  //
200  if (input == true) storeTypingHistoryEntry();
201  //
202  if (doubleClick == true) {
203  //
204  const auto& text = styledTextNode->getText();
205  auto textLength = text.length();
206  if (textLength > 0) {
207  auto wordLeftIdx = 0;
208  for (auto i = 0; i < index && i < textLength; i++) {
209  auto c = text.getUTF8CharAt(i);
210  if (Character::isAlphaNumeric(c) == false) {
211  wordLeftIdx = i + 1;
212  }
213  }
214  auto wordRightIdx = textLength;
215  for (auto i = index; i < textLength; i++) {
216  auto c = text.getUTF8CharAt(i);
217  if (Character::isAlphaNumeric(c) == false) {
218  wordRightIdx = i;
219  break;
220  }
221  }
222  if (wordLeftIdx != wordRightIdx) {
223  index = wordLeftIdx;
224  selectionIndex = wordRightIdx;
225  }
226  //
227  resetCursorMode();
228  }
229  //
230  doubleClick = false;
231  } else {
232  //
233  styledTextNode->unsetIndexMousePosition();
234  styledTextNode->unsetSelectionIndexMousePosition();
235 
236  //
237  styledTextNode->getScreenNode()->removeTickNode(styledTextNode);
238 
239  //
240  if (dragging == true && selectionIndex != -1) {
241  auto _index = index;
243  selectionIndex = _index;
244  }
245 
246  //
247  released = true;
248 
249  //
251  //
252  styledTextNode->scrollToIndex();
253  }
254 
255  //
256  if (input == true) unsetTypingHistoryEntryIdx();
257 
258  // find URL area that had a hit and setup corresponding cursor
259  const auto& urlAreas = styledTextNode->getURLAreas();
260  const GUIStyledTextNode::URLArea* urlAreaHit = nullptr;
261  for (auto& urlArea: urlAreas) {
262  if (nodeMouseCoordinate.getX() < urlArea.left ||
263  nodeMouseCoordinate.getY() < urlArea.top ||
264  nodeMouseCoordinate.getX() > urlArea.left + urlArea.width ||
265  nodeMouseCoordinate.getY() > urlArea.top + urlArea.height) {
266  continue;
267  }
268  urlAreaHit = &urlArea;
269  break;
270  }
271  // if release open browser if URL is valid
272  if (urlAreaHit != nullptr) {
273  node->getScreenNode()->getGUI()->addMouseOutCandidateNode(styledTextNode);
274  if (StringTools::startsWith(urlAreaHit->url, "http://") == true || StringTools::startsWith(urlAreaHit->url, "https://") == true) {
275  Application::openBrowser(urlAreaHit->url);
276  return;
277  }
278  }
279  //
280  event->setProcessed(true);
281  }
282  break;
283  default:
284  break;
285  }
286  }
287 
288  //
289  nodeMouseCoordinateNoOffsets = nodeMouseCoordinate.clone().sub(Vector2(styledTextNode->getParentNode()->getChildrenRenderOffsetX(), styledTextNode->getParentNode()->getChildrenRenderOffsetY()));
290 
291  // dragging, releasing
292  switch(event->getType()) {
293  case GUIMouseEvent::MOUSEEVENT_DRAGGED:
294  {
295  //
296  if (input == true) storeTypingHistoryEntry();
297  //
298  dragging = true;
299  //
300  if (nodeMouseCoordinateNoOffsets.getY() < 50) {
302  // unset
303  styledTextNode->unsetIndexMousePosition();
304  styledTextNode->unsetSelectionIndexMousePosition();
305  //
306  styledTextNode->getScreenNode()->addTickNode(styledTextNode);
307  } else
308  if (nodeMouseCoordinateNoOffsets.getY() > styledTextNode->getParentNode()->getComputedConstraints().height - 50) {
310  // unset
311  styledTextNode->unsetIndexMousePosition();
312  styledTextNode->unsetSelectionIndexMousePosition();
313  //
314  styledTextNode->getScreenNode()->addTickNode(styledTextNode);
315  } else {
316  styledTextNode->getScreenNode()->removeTickNode(styledTextNode);
317  //
319  // submit to styled text node
320  styledTextNode->setSelectionIndexMousePosition(nodeMouseCoordinateNoOffsets.getX(), nodeMouseCoordinateNoOffsets.getY());
321  }
322  //
323  resetCursorMode();
324  //
325  event->setProcessed(true);
326  break;
327  }
328  case GUIMouseEvent::MOUSEEVENT_RELEASED:
329  {
330  if (released == false) {
331  //
332  if (input == true) storeTypingHistoryEntry();
333 
334  //
335  styledTextNode->unsetIndexMousePosition();
336  styledTextNode->unsetSelectionIndexMousePosition();
337 
338  //
339  styledTextNode->getScreenNode()->removeTickNode(styledTextNode);
340 
341  //
342  if (dragging == true && selectionIndex != -1) {
343  auto _index = index;
345  selectionIndex = _index;
346  }
347 
348  //
350  //
351  styledTextNode->scrollToIndex();
352  //
353  event->setProcessed(true);
354  //
355  dragging = false;
356  //
358  }
359  break;
360  }
361  default:
362  break;
363  }
364  }
365 }
366 
368 {
369  if (input == false) return;
370 
371  //
372  auto maxLength = 0;
373  auto disabled = false;
374  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
375  if (disabled == false &&
376  event->isControlDown() == false &&
377  event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_TYPED) {
378  event->setProcessed(true);
379  //
380  auto selection = selectionIndex != -1 && index != selectionIndex;
381  if (event->getKeyChar() == ' ' || selection == true) {
383  }
384  //
385  if (selection == true) {
386  //
388  //
389  styledTextNode->removeText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
390  styledTextNode->scrollToIndex();
391  forwardRemoveText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
392  //
393  index = Math::min(index, selectionIndex);
394  selectionIndex = -1;
395  }
396  if (maxLength == 0 || styledTextNode->getTextLength() < maxLength) {
397  //
399  //
400  styledTextNode->insertText(index, event->getKeyChar());
401  styledTextNode->scrollToIndex();
403  index++;
404  resetCursorMode();
405  }
406  //
407  typedChars = true;
408  //
409  if (selection == true) {
412  }
413  //
414  typedChars = true;
415  } else {
416  if (Character::toLowerCase(event->getKeyChar()) == 'z' && event->isControlDown() == true && event->isShiftDown() == true) {
417  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) redo();
418  event->setProcessed(true);
419  } else
420  if (Character::toLowerCase(event->getKeyChar()) == 'z' && event->isControlDown() == true) {
421  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) undo();
422  event->setProcessed(true);
423  } else
424  // determine select all, copy, paste, cut
425  if (Character::toLowerCase(event->getKeyChar()) == 'a' && event->isControlDown() == true) {
426  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) selectAll();
427  event->setProcessed(true);
428  } else
429  if (Character::toLowerCase(event->getKeyChar()) == 'x' && event->isControlDown() == true) {
430  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) cut();
431  event->setProcessed(true);
432  } else
433  if (Character::toLowerCase(event->getKeyChar()) == 'c' && event->isControlDown() == true) {
434  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) copy();
435  event->setProcessed(true);
436  } else
437  if (Character::toLowerCase(event->getKeyChar()) == 'v' && event->isControlDown() == true) {
438  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) paste();
439  event->setProcessed(true);
440  } else
441  if (event->getKeyChar() == ' ' && event->isControlDown() == true) {
442  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) forwardCodeCompletion(index);
443  event->setProcessed(true);
444  } else {
445  // navigation, delete, return
446  switch (event->getKeyCode()) {
447  case GUIKeyboardEvent::KEYCODE_LEFT: {
448  event->setProcessed(true);
449  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
450  //
452  //
453  auto wordLeftIdx = -1;
454  auto lineStartIdx = styledTextNode->getPreviousNewLineUtf8(index - 1);
455  if (lineStartIdx != 0) lineStartIdx++;
456  if (event->isControlDown() == true) {
457  string delimiter = "^´!\"§$%&/()=?`+#<,.-*'>;:_";
458  const auto& text = styledTextNode->getText();
459  auto textLength = text.length();
460  if (textLength > 0) {
461  wordLeftIdx = lineStartIdx;
462  auto i = index - 1;
463  for (; i >= lineStartIdx; i--) {
464  auto c = text.getUTF8CharAt(i);
465  if (Character::isAlphaNumeric(c) == true || delimiter.find(c) != string::npos) break;
466  }
467  if (delimiter.find(text.getUTF8CharAt(i)) != string::npos) {
468  for (; i >= lineStartIdx && delimiter.find(text.getUTF8CharAt(i)) != string::npos; i--);
469  wordLeftIdx = i + 1;
470  } else {
471  for (; i >= lineStartIdx; i--) {
472  auto c = text.getUTF8CharAt(i);
473  if (Character::isAlphaNumeric(c) == false || delimiter.find(c) != string::npos) {
474  wordLeftIdx = i + 1;
475  break;
476  }
477  }
478  }
479  }
480  }
481  if (event->isShiftDown() == false) {
482  selectionIndex = -1;
483  } else {
484  if (selectionIndex == -1) selectionIndex = index;
485  }
486  if (index > 0) {
487  if (wordLeftIdx == -1) {
488  index--;
489  } else {
490  index = wordLeftIdx;
491  }
492  styledTextNode->scrollToIndex();
493  resetCursorMode();
494  }
495  //
497  }
498  }
499  break;
500  case GUIKeyboardEvent::KEYCODE_RIGHT: {
501  event->setProcessed(true);
502  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
503  //
505  //
506  auto wordRightIdx = -1;
507  if (event->isControlDown() == true) {
508  string delimiter = "^´!\"§$%&/()=?`+#<,.-*'>;:_";
509  const auto& text = styledTextNode->getText();
510  auto lineEndIdx = styledTextNode->getNextNewLineUtf8(index);
511  if (lineEndIdx > 0) {
512  wordRightIdx = lineEndIdx;
513  auto i = index;
514  for (; i < lineEndIdx; i++) {
515  auto c = text.getUTF8CharAt(i);
516  if (Character::isAlphaNumeric(c) == true || delimiter.find(c) != string::npos) break;
517  }
518  if (delimiter.find(text.getUTF8CharAt(i)) != string::npos) {
519  for (; i < lineEndIdx && delimiter.find(text.getUTF8CharAt(i)) != string::npos; i++);
520  wordRightIdx = i;
521  } else {
522  for (; i < lineEndIdx; i++) {
523  auto c = text.getUTF8CharAt(i);
524  if (Character::isAlphaNumeric(c) == false || delimiter.find(c) != string::npos) {
525  wordRightIdx = i;
526  break;
527  }
528  }
529  }
530  if (Character::isSpace(text.getUTF8CharAt(i)) == true) {
531  for (; i < lineEndIdx && Character::isSpace(text.getUTF8CharAt(i)) == true; i++);
532  wordRightIdx = i;
533  }
534  }
535  }
536  if (event->isShiftDown() == false) {
537  selectionIndex = -1;
538  } else {
539  if (selectionIndex == -1) selectionIndex = index;
540  }
541  if (index < styledTextNode->getTextLength()) {
542  if (wordRightIdx == -1) {
543  index++;
544  } else {
545  index = wordRightIdx;
546  }
547  styledTextNode->scrollToIndex();
548  resetCursorMode();
549  }
550  //
552  }
553  }
554  break;
555  case GUIKeyboardEvent::KEYCODE_UP: {
556  event->setProcessed(true);
557  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
558  //
560  //
561  if (event->isShiftDown() == false) {
562  selectionIndex = -1;
563  } else {
564  if (selectionIndex == -1) selectionIndex = index;
565  }
566  //
567  const auto& text = styledTextNode->getText();
568  // find index of current line newline and store difference
569  auto lineNewLineIndex = styledTextNode->getPreviousNewLineUtf8(index);
570  if (lineNewLineIndex == index) lineNewLineIndex = styledTextNode->getPreviousNewLineUtf8(index - 1);
571  lineNewLineIndex++;
572  // current line index
573  auto lineIndex = Math::max(index - lineNewLineIndex, 0);
574  // take tabs into account
575  for (auto i = lineNewLineIndex; i < index; i++) {
576  if (text.getUTF8CharAt(i) == '\t') lineIndex+= 3;
577  }
578  // store or reuse line index
579  if (lineIndex != 0) {
580  this->lineIndex = lineIndex;
581  } else {
582  lineIndex = this->lineIndex;
583  }
584  // find index of previous newline and iterate to difference if possible
585  auto previousNewLineIndex = styledTextNode->getPreviousNewLineUtf8(styledTextNode->getPreviousNewLineUtf8(index - 1) - 1);
586  if (previousNewLineIndex != 0) previousNewLineIndex++;
587  // find next index of previous 2 newline as upper bound
588  auto nextNewLineIndex = styledTextNode->getNextNewLineUtf8(previousNewLineIndex);
589  // take tabs into account
590  index = previousNewLineIndex;
591  for (auto i = 0; i < lineIndex && index < nextNewLineIndex; ) {
592  if (text.getUTF8CharAt(index) == '\t') i+= 4; else i+= 1;
593  index++;
594  }
595  //
596  styledTextNode->scrollToIndex();
597  resetCursorMode();
598  //
600  }
601  }
602  break;
603  case GUIKeyboardEvent::KEYCODE_DOWN: {
604  event->setProcessed(true);
605  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
606  //
608  //
609  if (event->isShiftDown() == false) {
610  selectionIndex = -1;
611  } else {
612  if (selectionIndex == -1) selectionIndex = index;
613  }
614  //
615  const auto& text = styledTextNode->getText();
616  // find index of current line newline and store difference
617  auto lineNewLineIndex = styledTextNode->getPreviousNewLineUtf8(index);
618  if (lineNewLineIndex == index) lineNewLineIndex = styledTextNode->getPreviousNewLineUtf8(index - 1);
619  lineNewLineIndex++;
620  // current line index
621  auto lineIndex = Math::max(index - lineNewLineIndex, 0);
622  // take tabs into account
623  for (auto i = lineNewLineIndex; i < index; i++) {
624  if (text.getUTF8CharAt(i) == '\t') lineIndex+= 3;
625  }
626  // store or reuse line index
627  if (lineIndex != 0) {
628  this->lineIndex = lineIndex;
629  } else {
630  lineIndex = this->lineIndex;
631  }
632  // find index of next newline
633  auto nextNewLineIndex = styledTextNode->getNextNewLineUtf8(index);
634  // find index of next * 2 newline as upper bound
635  auto next2NewLineIndex = styledTextNode->getNextNewLineUtf8(nextNewLineIndex + 1);
636  // take tabs into account
637  index = nextNewLineIndex + 1;
638  for (auto i = 0; i < lineIndex && index < next2NewLineIndex; ) {
639  if (text.getUTF8CharAt(index) == '\t') i+= 4; else i+= 1;
640  index++;
641  }
642  //
643  styledTextNode->scrollToIndex();
644  resetCursorMode();
645  //
647  }
648  }
649  break;
650  case GUIKeyboardEvent::KEYCODE_PAGE_UP: {
651  event->setProcessed(true);
652  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
653  //
655  //
656  if (event->isShiftDown() == false) {
657  selectionIndex = -1;
658  } else {
659  if (selectionIndex == -1) selectionIndex = index;
660  }
661  index = styledTextNode->doPageUp();
662  resetCursorMode();
663  //
665  }
666  }
667  break;
668  case GUIKeyboardEvent::KEYCODE_PAGE_DOWN: {
669  event->setProcessed(true);
670  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
671  //
673  //
674  if (event->isShiftDown() == false) {
675  selectionIndex = -1;
676  } else {
677  if (selectionIndex == -1) selectionIndex = index;
678  }
679  index = styledTextNode->doPageDown();
680  resetCursorMode();
681  //
683  }
684  }
685  break;
686  case GUIKeyboardEvent::KEYCODE_BACKSPACE: {
687  if (disabled == false) {
688  event->setProcessed(true);
689  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
690  //
692  //
693  if (index != -1 && selectionIndex != -1 && index != selectionIndex) {
695  styledTextNode->removeText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
696  styledTextNode->scrollToIndex();
697  forwardRemoveText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
698  index = Math::min(index, selectionIndex);
699  selectionIndex = -1;
700  } else
701  if (index > 0) {
703  styledTextNode->removeText(index - 1, 1);
704  styledTextNode->scrollToIndex();
705  forwardRemoveText(index - 1, 1);
706  index--;
707  resetCursorMode();
708  }
709  //
711  }
712  }
713  }
714  break;
715  case GUIKeyboardEvent::KEYCODE_DELETE: {
716  if (disabled == false) {
717  event->setProcessed(true);
718  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
719  delete_();
720  }
721  }
722  }
723  break;
724  case GUIKeyboardEvent::KEYCODE_RETURN: {
725  if (disabled == false) {
726  event->setProcessed(true);
727  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
728  //
730  // find out current line indenting
731  string newLinePrefix;
732  {
733  const auto& text = styledTextNode->getText();
734  auto lineNewLineIndex = styledTextNode->getPreviousNewLineUtf8(index);
735  if (lineNewLineIndex == index) lineNewLineIndex = styledTextNode->getPreviousNewLineUtf8(index - 1);
736  lineNewLineIndex++;
737  //
738  for (auto i = lineNewLineIndex; i < index; i++) {
739  auto c = text.getUTF8CharAt(i);
740  if (c == ' ' || c == '\t') {
741  newLinePrefix+= (char)c;
742  } else {
743  break;
744  }
745  }
746  }
747  //
748  if (index != -1 && selectionIndex != -1 && index != selectionIndex) {
749  styledTextNode->removeText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
750  styledTextNode->scrollToIndex();
751  forwardRemoveText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
752  index = Math::min(index, selectionIndex);
753  selectionIndex = -1;
754  }
755  if (maxLength == 0 || styledTextNode->getTextLength() < maxLength) {
756  // insert new line and new line prefix
757  styledTextNode->insertText(index, '\n' + newLinePrefix);
758  styledTextNode->scrollToIndex();
760  index+= 1 + newLinePrefix.size();
761  resetCursorMode();
762  }
763  //
764  typedChars = true;
765  //
768  }
769  }
770  }
771  break;
772  case GUIKeyboardEvent::KEYCODE_TAB: {
773  if (disabled == false) {
774  event->setProcessed(true);
775  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
776  //
778  //
779  if ((maxLength == 0 || styledTextNode->getTextLength() + 1 < maxLength) &&
780  index != -1 && selectionIndex != -1 && index != selectionIndex) {
781  //
782  auto lineNewLineIndex = styledTextNode->getPreviousNewLineUtf8(Math::min(index, selectionIndex)) + (Math::min(index, selectionIndex) == 0?0:1);
783  styledTextNode->insertText(lineNewLineIndex, '\t');
784  forwardInsertText(lineNewLineIndex, 1);
785  storeTypingHistoryEntry2(lineNewLineIndex, "\t");
786  if (index > selectionIndex) index++; else selectionIndex++;
787  //
788  auto nextNewLineIndex = lineNewLineIndex;
789  for (;;) {
790  //
791  nextNewLineIndex = styledTextNode->getNextNewLineUtf8(nextNewLineIndex) + 1;
792  styledTextNode->insertText(nextNewLineIndex, '\t');
793  forwardInsertText(nextNewLineIndex, 1);
794  storeTypingHistoryEntry2(nextNewLineIndex, "\t");
795  if (index > selectionIndex) index++; else selectionIndex++;
796  if (nextNewLineIndex > Math::max(index, selectionIndex)) break;
797  if (styledTextNode->getTextLength() >= maxLength) break;
798  }
799  } else
800  if ((maxLength == 0 || styledTextNode->getTextLength() < maxLength) &&
801  index != -1 && selectionIndex == -1) {
802  //
804  //
805  styledTextNode->insertText(index, '\t');
806  styledTextNode->scrollToIndex();
808  index++;
809  resetCursorMode();
810  //
811  typedChars = true;
812  }
813  }
814  }
815  }
816  break;
817  case GUIKeyboardEvent::KEYCODE_POS1: {
818  if (disabled == false) {
819  event->setProcessed(true);
820  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
821  //
823  //
824  resetCursorMode();
825  if (event->isShiftDown() == false) {
826  selectionIndex = -1;
827  } else {
828  if (selectionIndex == -1) selectionIndex = index;
829  }
830  if (event->isControlDown() == true) {
831  index = 0;
832  } else {
833  // find index of previous newline
834  index = styledTextNode->getPreviousNewLineUtf8(index - 1);
835  if (index != 0) index++;
836 
837  }
838  styledTextNode->scrollToIndex();
839  //
841  }
842  }
843  }
844  break;
845  case GUIKeyboardEvent::KEYCODE_END: {
846  if (disabled == false) {
847  if (event->getType() == GUIKeyboardEvent::KEYBOARDEVENT_KEY_PRESSED) {
848  //
850  //
851  resetCursorMode();
852  if (event->isShiftDown() == false) {
853  selectionIndex = -1;
854  } else {
855  if (selectionIndex == -1) selectionIndex = index;
856  }
857  if (event->isControlDown() == true) {
858  index = styledTextNode->getTextLength() - 1;
859  } else {
860  // find index of next newline
861  index = styledTextNode->getNextNewLineUtf8(index);;
862  }
863  styledTextNode->scrollToIndex();
864  //
866  }
867  }
868  }
869  break;
870  }
871  }
872  }
873 }
874 
876 {
877  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
878  if (scrollMode == SCROLLMODE_UP) {
879  // find index of current line newline and store difference
880  auto lineNewLineIndex = styledTextNode->getPreviousNewLineUtf8(selectionIndex) + (selectionIndex == 0?0:1);
881  // current line index
882  auto lineIndex = Math::max(selectionIndex - lineNewLineIndex, 0);
883  // find index of previous newline and iterate to difference if possible
884  auto previousNewLineIndex = styledTextNode->getPreviousNewLineUtf8(styledTextNode->getPreviousNewLineUtf8(selectionIndex - 1) - 1);
885  if (previousNewLineIndex != 0) previousNewLineIndex++;
886  // find next index of previous 2 newline as upper bound
887  auto nextNewLineIndex = styledTextNode->getNextNewLineUtf8(previousNewLineIndex);
888  //
889  selectionIndex = Math::min(previousNewLineIndex + lineIndex, nextNewLineIndex);
890  //
891  styledTextNode->scrollToSelectionIndex();
892  } else
893  if (scrollMode == SCROLLMODE_DOWN) {
894  // find index of current line newline and store difference
895  auto lineNewLineIndex = styledTextNode->getPreviousNewLineUtf8(selectionIndex) + (selectionIndex == 0?0:1);
896  // current line index
897  auto lineIndex = Math::max(selectionIndex - lineNewLineIndex, 0);
898  // find index of next newline
899  auto nextNewLineIndex = styledTextNode->getNextNewLineUtf8(selectionIndex);
900  // find index of next * 2 newline as upper bound
901  auto next2NewLineIndex = styledTextNode->getNextNewLineUtf8(nextNewLineIndex + 1);
902  // iterate to difference if possible
903  selectionIndex = Math::min(nextNewLineIndex + 1 + lineIndex, next2NewLineIndex);
904  //
905  styledTextNode->scrollToSelectionIndex();
906  }
907 }
908 
910 {
911 }
912 
914 {
915 }
916 
918 {
919  return false;
920 }
921 
923 {
924  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
925  return styledTextNode->getText();
926 }
927 
929 {
930  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
931  styledTextNode->setText(value);
932 }
933 
935 {
936 }
937 
939 {
940  removeChangeListener(listener);
941  changeListeners.push_back(listener);
942 }
943 
945 {
946  changeListeners.erase(std::remove(changeListeners.begin(), changeListeners.end(), listener), changeListeners.end());
947 }
948 
951  codeCompletionListeners.push_back(listener);
952 }
953 
955  codeCompletionListeners.erase(std::remove(codeCompletionListeners.begin(), codeCompletionListeners.end(), listener), codeCompletionListeners.end());
956 }
957 
959  // determine binary start and end positions
960  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
961  const auto& text = styledTextNode->getText();
962  auto u8It = text.getUTF8CharacterIterator();
963  u8It.seekCharacterPosition(idx);
964  auto binaryStartIdx = u8It.getBinaryPosition();
965  for (auto i = 0; u8It.hasNext() == true && i < count; i++) u8It.next();
966  auto binaryEndIdx = u8It.getBinaryPosition();
967  // forward remove text
968  for (auto i = 0; i < changeListeners.size(); i++) {
969  changeListeners[i]->onRemoveText(binaryStartIdx, binaryEndIdx - binaryStartIdx);
970  }
971 }
972 
974  // determine binary start and end positions
975  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
976  const auto& text = styledTextNode->getText();
977  auto u8It = text.getUTF8CharacterIterator();
978  u8It.seekCharacterPosition(idx);
979  auto binaryStartIdx = u8It.getBinaryPosition();
980  for (auto i = 0; u8It.hasNext() == true && i < count; i++) u8It.next();
981  auto binaryEndIdx = u8It.getBinaryPosition();
982 
983  //
984  for (auto i = 0; i < changeListeners.size(); i++) {
985  changeListeners[i]->onInsertText(binaryStartIdx, binaryEndIdx - binaryStartIdx);
986  }
987 }
988 
990  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
991  const auto& text = styledTextNode->getText();
992  auto binaryIdx = text.getUtf8BinaryIndex(idx);
993  for (auto i = 0; i < changeListeners.size(); i++) {
994  codeCompletionListeners[i]->onCodeCompletion(binaryIdx);
995  }
996 }
997 
999  Console::println("GUIStyledTextNodeController::storeTypingHistoryEntry()");
1000 
1001  // if no char has been typed we have nothing to do
1002  if (typedChars == false) {
1003  return;
1004  }
1005 
1006  // unset
1007  typedChars = false;
1008 
1009  // no position to start storing from?
1010  if (historyEntryIdx == -1) {
1011  return;
1012  }
1013 
1014  // get upper modification char bound
1015  auto index = this->index;
1016  if (selectionIndex != -1) index = Math::max(index, selectionIndex);
1017 
1018  // no change?
1019  if (historyEntryIdx == index) {
1020  historyEntryIdx = -1;
1021  return;
1022  }
1023 
1024  // we need to remove history from now on
1025  if (historyIdx != -1 && historyIdx < history.size()) {
1026  history.erase(history.begin() + historyIdx, history.end());
1027  historyIdx = history.size();
1028  }
1029 
1030  //
1031  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
1032  const auto& text = styledTextNode->getText();
1033  auto u8It = text.getUTF8CharacterIterator();
1034  u8It.seekCharacterPosition(historyEntryIdx);
1035  string data;
1036  for (; u8It.hasNext() == true && u8It.getCharacterPosition() < index;) Character::appendToString(data, u8It.next());
1037  history.emplace_back(
1040  data,
1041  false
1042  );
1043  //
1044  historyEntryIdx = -1;
1045 
1046  // just point to the latest history entry
1047  historyIdx = history.size();
1048 
1049  //
1050  for (auto i = history.size() - 1; i < history.size(); i++) {
1051  const auto& historyEntry = history[i];
1052  string historyEntryTypeString;
1053  switch (historyEntry.type) {
1055  historyEntryTypeString = "NONE";
1056  break;
1058  historyEntryTypeString = "INSERT";
1059  break;
1061  historyEntryTypeString = "DELETE";
1062  break;
1063 
1064  }
1065  Console::println("GUIStyledTextNodeController::storeTypingHistoryEntry(): " + to_string(i) + ": history entry @ " + to_string(historyEntry.idx) + ": '" + historyEntry.data + "'" + ": " + historyEntryTypeString);
1066  }
1067 }
1068 
1069 void GUIStyledTextNodeController::storeTypingHistoryEntry2(int index, const string& data) {
1070  Console::println("GUIStyledTextNodeController::storeTypingHistoryEntry2()");
1071 
1072  // we need to remove history from now on
1073  if (historyIdx != -1 && historyIdx < history.size()) {
1074  history.erase(history.begin() + historyIdx, history.end());
1075  historyIdx = history.size();
1076  }
1077 
1078  //
1079  history.emplace_back(
1081  index,
1082  data,
1083  false
1084  );
1085  //
1086  historyEntryIdx = -1;
1087 
1088  // just point to the latest history entry
1089  historyIdx = history.size();
1090 
1091  //
1092  for (auto i = history.size() - 1; i < history.size(); i++) {
1093  const auto& historyEntry = history[i];
1094  string historyEntryTypeString;
1095  switch (historyEntry.type) {
1097  historyEntryTypeString = "NONE";
1098  break;
1100  historyEntryTypeString = "INSERT";
1101  break;
1103  historyEntryTypeString = "DELETE";
1104  break;
1105 
1106  }
1107  Console::println("GUIStyledTextNodeController::storeTypingHistoryEntry2(): " + to_string(i) + ": history entry @ " + to_string(historyEntry.idx) + ": '" + historyEntry.data + "'" + ": " + historyEntryTypeString);
1108  }
1109 }
1110 
1112  Console::println("GUIStyledTextNodeController::storeDeletionHistoryInternal(): " + to_string(index) + " / " + to_string(count));
1113  //
1114  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
1115  const auto& text = styledTextNode->getText();
1116  auto u8It = text.getUTF8CharacterIterator();
1117  u8It.seekCharacterPosition(index);
1118  string data;
1119  for (auto i = 0; u8It.hasNext() == true && i < count; i++) Character::appendToString(data, u8It.next());
1120  history.emplace_back(
1122  index,
1123  data,
1124  count == 1
1125  );
1126  // just point to the latest history entry
1127  historyIdx = history.size();
1128 
1129  //
1130  for (auto i = history.size() - 1; i < history.size(); i++) {
1131  const auto& historyEntry = history[i];
1132  string historyEntryTypeString;
1133  switch (historyEntry.type) {
1135  historyEntryTypeString = "NONE";
1136  break;
1138  historyEntryTypeString = "INSERT";
1139  break;
1141  historyEntryTypeString = "DELETE";
1142  break;
1143 
1144  }
1145  Console::println("GUIStyledTextNodeController::storeDeletionHistoryInternal(): " + to_string(i) + ": history entry @ " + to_string(historyEntry.idx) + ": '" + historyEntry.data + "'" + ": " + historyEntryTypeString);
1146  }
1147  //
1148 }
1149 
1151  //
1153 
1154  //
1155  Console::println("GUIStyledTextNodeController::redo(): " + to_string(historyIdx) + " / " + to_string(history.size()));
1156 
1157  // exit if no history
1158  if (history.empty() == true) return;
1159 
1160  // after undoing everything we can point to history index of -1
1161  if (historyIdx == -1) historyIdx = 0;
1162 
1163  // do not go further than the history entries we have
1164  if (historyIdx >= history.size()) return;
1165 
1166  //
1167  const auto& historyEntry = history[historyIdx];
1168 
1169  //
1170  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
1171  switch (historyEntry.type) {
1173  {
1174  auto dataUtf8Length = StringTools::getUtf8Length(historyEntry.data);
1175  index = historyEntry.idx;
1176  selectionIndex = -1;
1177  styledTextNode->insertText(index, historyEntry.data);
1178  forwardInsertText(index, dataUtf8Length);
1179  index+= dataUtf8Length;
1180  }
1181  break;
1183  {
1184  index = historyEntry.idx;
1185  selectionIndex = -1;
1186  auto dataUtf8Length = StringTools::getUtf8Length(historyEntry.data);
1187  styledTextNode->removeText(index, dataUtf8Length);
1188  forwardRemoveText(index, dataUtf8Length);
1189  }
1190  break;
1191  default: break;
1192  }
1193 
1194  //
1195  historyIdx++;
1196 
1197  //
1198  styledTextNode->scrollToIndex();
1199 }
1200 
1202  //
1204 
1205  //
1206  // Console::println("GUIStyledTextNodeController::undo(): " + to_string(historyIdx) + " / " + to_string(history.size()));
1207 
1208  // skip if empty history
1209  if (history.empty() == true) return;
1210 
1211  // we should not go beyond first history entry
1212  if (historyIdx == 0) return;
1213 
1214  //
1215  historyIdx--;
1216 
1217  //
1218  const auto& historyEntry = history[historyIdx];
1219 
1220  //
1221  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
1222 
1223  //
1224  switch (historyEntry.type) {
1226  {
1227  index = historyEntry.idx;
1228  selectionIndex = -1;
1229  auto dataUtf8Length = StringTools::getUtf8Length(historyEntry.data);
1230  styledTextNode->removeText(index, dataUtf8Length);
1231  forwardRemoveText(index, dataUtf8Length);
1232  }
1233  break;
1235  {
1236  auto dataUtf8Length = StringTools::getUtf8Length(historyEntry.data);
1237  index = historyEntry.idx;
1238  selectionIndex = -1;
1239  styledTextNode->insertText(index, historyEntry.data);
1240  forwardInsertText(index, dataUtf8Length);
1241  index+= dataUtf8Length;
1242  }
1243  break;
1244  default: break;
1245  }
1246 
1247  //
1248  styledTextNode->scrollToIndex();
1249 }
1250 
1252  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
1253  const auto& text = styledTextNode->getText();
1254  index = 0;
1255  selectionIndex = text.length() - 1;
1256 }
1257 
1259  //
1261  //
1262  if (index != -1 && selectionIndex != -1 && index != selectionIndex) {
1263  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
1264  const auto& text = styledTextNode->getText();
1265  Application::getApplication()->setClipboardContent(StringTools::substring(text.getString(), Math::min(text.getUtf8BinaryIndex(index), text.getUtf8BinaryIndex(selectionIndex)), Math::max(text.getUtf8BinaryIndex(index), text.getUtf8BinaryIndex(selectionIndex))));
1267  styledTextNode->removeText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
1268  styledTextNode->scrollToIndex();
1269  forwardRemoveText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
1270  index = Math::min(index, selectionIndex);
1271  selectionIndex = -1;
1272  }
1273  //
1275 }
1276 
1278  if (index != -1 && selectionIndex != -1 && index != selectionIndex) {
1279  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
1280  const auto& text = styledTextNode->getText();
1281  Application::getApplication()->setClipboardContent(StringTools::substring(text.getString(), Math::min(text.getUtf8BinaryIndex(index), text.getUtf8BinaryIndex(selectionIndex)), Math::max(text.getUtf8BinaryIndex(index), text.getUtf8BinaryIndex(selectionIndex))));
1282  }
1283 }
1284 
1286  //
1288  //
1289  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
1290  auto maxLength = 0;
1291  auto clipboardContent = Application::getApplication()->getClipboardContent();
1292  auto clipboardContentLength = StringTools::getUtf8Length(clipboardContent);
1293  if (index != -1 && selectionIndex != -1 && index != selectionIndex) {
1294  if (maxLength == 0 || styledTextNode->getTextLength() - Math::abs(index - selectionIndex) + clipboardContentLength < maxLength) {
1296  styledTextNode->removeText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
1297  styledTextNode->scrollToIndex();
1298  forwardRemoveText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
1299  index = Math::min(index, selectionIndex);
1300  selectionIndex = -1;
1301  }
1302  }
1303  //
1304  if (maxLength == 0 || styledTextNode->getTextLength() + clipboardContentLength < maxLength) {
1305  //
1307  typedChars = true;
1308  //
1309  styledTextNode->insertText(index, clipboardContent);
1310  styledTextNode->scrollToIndex();
1311  forwardInsertText(index, clipboardContentLength);
1312  index+= clipboardContentLength;
1313  //
1315  }
1316  //
1318 }
1319 
1321  //
1323  //
1324  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
1325  if (index != -1 && selectionIndex != -1 && index != selectionIndex) {
1327  styledTextNode->removeText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
1328  styledTextNode->scrollToIndex();
1329  forwardRemoveText(Math::min(index, selectionIndex), Math::abs(index - selectionIndex));
1330  index = Math::min(index, selectionIndex);
1331  selectionIndex = -1;
1332  } else
1333  if (index < styledTextNode->getTextLength()) {
1335  styledTextNode->removeText(index, 1);
1336  styledTextNode->scrollToIndex();
1338  resetCursorMode();
1339  }
1340  //
1342 }
1343 
1344 void GUIStyledTextNodeController::replace(const string& by, int index, int count) {
1345  auto styledTextNode = required_dynamic_cast<GUIStyledTextNode*>(this->node);
1346  // unselect
1347  setSelectionIndex(-1);
1348  // remove
1350  styledTextNode->removeText(index, count);
1351  // insert
1352  setIndex(index);
1354  styledTextNode->insertText(index, by);
1355  typedChars = true;
1356  setIndex(index + StringTools::getUtf8Length(by));
1358  // unset history typing index
1360 
1361 }
#define MOUSE_CURSOR_HAND
Definition: Application.h:14
#define MOUSE_CURSOR_ENABLED
Definition: Application.h:12
Application base class, please make sure to allocate application on heap to have correct application ...
Definition: Application.h:41
GUI module class.
Definition: GUI.h:64
void addMouseOutCandidateNode(GUINode *node)
Add node that is a possible mouse out candidate as it received a mouse over.
Definition: GUI.h:317
GUIKeyboardEventType getType() const
GUIMouseEventType getType() const
Definition: GUIMouseEvent.h:78
GUI node controller base class.
static STATIC_DLL_IMPEXT GUINode_RequestedConstraints_RequestedConstraintsType * AUTO
GUI node base class.
Definition: GUINode.h:64
GUIParentNode * getParentControllerNode()
Definition: GUINode.cpp:1011
GUIScreenNode * getScreenNode()
Definition: GUINode.h:325
const string & getId()
Definition: GUINode.h:339
void replace(const string &by, int index, int count)
Replace text from given index with given count by string by.
void storeDeletionHistoryEntry(int index, int count)
Store deletion history entry.
void initialize() override
Initialize controller after element has been created.
void handleKeyboardEvent(GUIKeyboardEvent *event) override
Handle keyboard event.
void storeDeletionHistoryEntryStoreTypingEntry(int index, int count)
Store deletion history entry and store prior typing if we have any.
void setValue(const MutableString &value) override
Set value.
void handleMouseEvent(GUINode *node, GUIMouseEvent *event) override
Handle mouse event.
void addCodeCompletionListener(CodeCompletionListener *listener)
Add code completion listener.
void tick() override
Tick method will be executed once per frame.
void forwardInsertText(int idx, int count)
Forward insert text.
void unsetTypingHistoryEntryIdx()
Unset typing history entry index.
void setTypingHistoryEntryIdx()
Set typing history entry index.
void setDisabled(bool disabled) override
Set disabled.
void addChangeListener(ChangeListener *listener)
Add change listener.
void storeDeletionHistoryInternal(int index, int count)
Store typing history entry.
void removeCodeCompletionListener(CodeCompletionListener *listener)
Remove code completion listener.
void storeTypingHistoryEntry2(int index, const string &data)
Store typing history entry.
void removeChangeListener(ChangeListener *listener)
Remove change listener.
void forwardRemoveText(int idx, int count)
Forward remove text.
vector< CodeCompletionListener * > codeCompletionListeners
void setSelectionIndex(int selectionIndex)
Set selection index.
void forwardCodeCompletion(int idx)
Forward code completion.
Vector2 class representing vector2 mathematical structure and operations with x, y components.
Definition: Vector2.h:20
float getY() const
Definition: Vector2.h:111
Vector2 & sub(float scalar)
Subtracts a scalar.
Definition: Vector2.h:152
float getX() const
Definition: Vector2.h:94
Vector2 clone() const
Clones this vector2.
Definition: Vector2.h:265
Character class.
Definition: Character.h:17
Console class.
Definition: Console.h:29
Mutable utf8 aware string class.
Definition: MutableString.h:23
String tools class.
Definition: StringTools.h:22
Time utility class.
Definition: Time.h:20