1 #include "gui/interface/Textbox.h"
2
3 #include "Config.h"
4 #include "Platform.h"
5 #include "Format.h"
6 #include "PowderToy.h"
7
8 #include "graphics/Graphics.h"
9
10 #include "gui/interface/Point.h"
11 #include "gui/interface/Keys.h"
12 #include "gui/interface/Mouse.h"
13
14 #include "ContextMenu.h"
15
16 using namespace ui;
17
Textbox(Point position,Point size,String textboxText,String textboxPlaceholder)18 Textbox::Textbox(Point position, Point size, String textboxText, String textboxPlaceholder):
19 Label(position, size, ""),
20 ReadOnly(false),
21 inputType(All),
22 limit(String::npos),
23 keyDown(0),
24 characterDown(0),
25 mouseDown(false),
26 masked(false),
27 border(true)
28 {
29 placeHolder = textboxPlaceholder;
30
31 SetText(textboxText);
32 cursor = text.length();
33
34 menu->RemoveItem(0);
35 menu->AddItem(ContextMenuItem("Cut", 1, true));
36 menu->AddItem(ContextMenuItem("Copy", 0, true));
37 menu->AddItem(ContextMenuItem("Paste", 2, true));
38 }
39
SetHidden(bool hidden)40 void Textbox::SetHidden(bool hidden)
41 {
42 menu->RemoveItem(0);
43 menu->RemoveItem(1);
44 menu->RemoveItem(2);
45 menu->AddItem(ContextMenuItem("Cut", 1, !hidden));
46 menu->AddItem(ContextMenuItem("Copy", 0, !hidden));
47 menu->AddItem(ContextMenuItem("Paste", 2, true));
48
49 masked = hidden;
50 }
51
SetPlaceholder(String text)52 void Textbox::SetPlaceholder(String text)
53 {
54 placeHolder = text;
55 }
56
SetText(String newText)57 void Textbox::SetText(String newText)
58 {
59 backingText = newText;
60
61 if(masked)
62 {
63 String maskedText = newText;
64 std::fill(maskedText.begin(), maskedText.end(), 0xE00D);
65 Label::SetText(maskedText);
66 }
67 else
68 Label::SetText(newText);
69
70 cursor = newText.length();
71
72 if(cursor)
73 {
74 textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
75 }
76 else
77 {
78 cursorPositionY = cursorPositionX = 0;
79 }
80 }
81
GetInputType()82 Textbox::ValidInput Textbox::GetInputType()
83 {
84 return inputType;
85 }
86
SetInputType(ValidInput input)87 void Textbox::SetInputType(ValidInput input)
88 {
89 inputType = input;
90 }
91
SetLimit(size_t limit)92 void Textbox::SetLimit(size_t limit)
93 {
94 this->limit = limit;
95 }
96
GetLimit()97 size_t Textbox::GetLimit()
98 {
99 return limit;
100 }
101
GetText()102 String Textbox::GetText()
103 {
104 return backingText;
105 }
106
OnContextMenuAction(int item)107 void Textbox::OnContextMenuAction(int item)
108 {
109 switch(item)
110 {
111 case 0:
112 copySelection();
113 break;
114 case 1:
115 cutSelection();
116 break;
117 case 2:
118 pasteIntoSelection();
119 break;
120 }
121 }
122
resetCursorPosition()123 void Textbox::resetCursorPosition()
124 {
125 textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
126 }
127
TabFocus()128 void Textbox::TabFocus()
129 {
130 GetParentWindow()->FocusComponent(this);
131 selectAll();
132 }
133
cutSelection()134 void Textbox::cutSelection()
135 {
136 if (HasSelection())
137 {
138 if (getLowerSelectionBound() < 0 || getHigherSelectionBound() > (int)backingText.length())
139 return;
140 String toCopy = backingText.Between(getLowerSelectionBound(), getHigherSelectionBound());
141 ClipboardPush(format::CleanString(toCopy, false, true, false).ToUtf8());
142 backingText.erase(backingText.begin()+getLowerSelectionBound(), backingText.begin()+getHigherSelectionBound());
143 cursor = getLowerSelectionBound();
144 }
145 else
146 {
147 if (!backingText.length())
148 return;
149 ClipboardPush(format::CleanString(backingText, false, true, false).ToUtf8());
150 backingText.clear();
151 cursor = 0;
152 }
153 ClearSelection();
154
155 if(masked)
156 {
157 String maskedText = backingText;
158 std::fill(maskedText.begin(), maskedText.end(), 0xE00D);
159 Label::SetText(maskedText);
160 }
161 else
162 {
163 text = backingText;
164 }
165
166 updateTextWrapper();
167 updateSelection();
168 TextPosition(displayTextWrapper.WrappedText());
169
170 if(cursor)
171 {
172 textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
173 }
174 else
175 {
176 cursorPositionY = cursorPositionX = 0;
177 }
178 if (actionCallback.change)
179 actionCallback.change();
180 }
181
pasteIntoSelection()182 void Textbox::pasteIntoSelection()
183 {
184 String newText = format::CleanString(ClipboardPull().FromUtf8(), true, true, inputType != Multiline, inputType == Number || inputType == Numeric);
185 if (HasSelection())
186 {
187 if (getLowerSelectionBound() < 0 || getHigherSelectionBound() > (int)backingText.length())
188 return;
189 backingText.EraseBetween(getLowerSelectionBound(), getHigherSelectionBound());
190 cursor = getLowerSelectionBound();
191 }
192
193 int regionWidth = Size.X;
194 if (Appearance.icon)
195 regionWidth -= 13;
196 regionWidth -= Appearance.Margin.Left;
197 regionWidth -= Appearance.Margin.Right;
198
199 if (limit != String::npos)
200 {
201 newText = newText.Substr(0, limit-backingText.length());
202 }
203 if (!multiline && Graphics::textwidth(backingText + newText) > regionWidth)
204 {
205 int pLimit = regionWidth - Graphics::textwidth(backingText);
206 int pWidth = 0;
207 auto it = newText.begin();
208 while (it != newText.end())
209 {
210 auto w = Graphics::CharWidth(*it);
211 if (pWidth + w > pLimit)
212 {
213 break;
214 }
215 pWidth += w;
216 ++it;
217 }
218 newText = String(newText.begin(), it);
219 }
220
221 backingText.Insert(cursor, newText);
222 cursor = cursor+newText.length();
223 ClearSelection();
224
225 if(masked)
226 {
227 String maskedText = backingText;
228 std::fill(maskedText.begin(), maskedText.end(), 0xE00D);
229 Label::SetText(maskedText);
230 }
231 else
232 {
233 text = backingText;
234 }
235
236 updateTextWrapper();
237 updateSelection();
238 TextPosition(displayTextWrapper.WrappedText());
239
240 if(cursor)
241 {
242 textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
243 }
244 else
245 {
246 cursorPositionY = cursorPositionX = 0;
247 }
248 if (actionCallback.change)
249 actionCallback.change();
250 }
251
CharacterValid(int character)252 bool Textbox::CharacterValid(int character)
253 {
254 switch(inputType)
255 {
256 case Numeric:
257 if (character == '-' && cursor == 0 && backingText[0] != '-')
258 return true;
259 case Number:
260 return (character >= '0' && character <= '9');
261 case Multiline:
262 if (character == '\n')
263 return true;
264 case All:
265 default:
266 return (character >= ' ' && character < 127);
267 }
268 return false;
269 }
270
271 // TODO: proper unicode validation
StringValid(String text)272 bool Textbox::StringValid(String text)
273 {
274 for (String::value_type c : text)
275 if (!CharacterValid(c))
276 return false;
277 return true;
278 }
279
Tick(float dt)280 void Textbox::Tick(float dt)
281 {
282 Label::Tick(dt);
283 if (!IsFocused())
284 {
285 keyDown = 0;
286 characterDown = 0;
287 }
288 unsigned long time_pls = Platform::GetTime();
289 if ((keyDown || characterDown) && repeatTime <= time_pls)
290 {
291 //OnVKeyPress(keyDown, characterDown, false, false, false);
292 repeatTime = Platform::GetTime()+30;
293 }
294 }
295
OnKeyRelease(int key,int scan,bool repeat,bool shift,bool ctrl,bool alt)296 void Textbox::OnKeyRelease(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt)
297 {
298 keyDown = 0;
299 characterDown = 0;
300 }
301
OnKeyPress(int key,int scan,bool repeat,bool shift,bool ctrl,bool alt)302 void Textbox::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt)
303 {
304 characterDown = scan;
305 keyDown = key;
306 repeatTime = Platform::GetTime()+300;
307 OnVKeyPress(key, scan, repeat, shift, ctrl, alt);
308 }
309
OnVKeyPress(int key,int scan,bool repeat,bool shift,bool ctrl,bool alt)310 void Textbox::OnVKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt)
311 {
312 bool changed = false;
313 if (ctrl && scan == SDL_SCANCODE_C && !masked && !repeat)
314 {
315 copySelection();
316 return;
317 }
318 if (ctrl && scan == SDL_SCANCODE_V && !ReadOnly)
319 {
320 pasteIntoSelection();
321 return;
322 }
323 if (ctrl && scan == SDL_SCANCODE_X && !masked && !repeat && !ReadOnly)
324 {
325 cutSelection();
326 return;
327 }
328 if (ctrl && scan == SDL_SCANCODE_A)
329 {
330 selectAll();
331 return;
332 }
333
334 try
335 {
336 switch(key)
337 {
338 case SDLK_HOME:
339 cursor = 0;
340 ClearSelection();
341 break;
342 case SDLK_END:
343 cursor = backingText.length();
344 ClearSelection();
345 break;
346 case SDLK_LEFT:
347 if(cursor > 0)
348 cursor--;
349 ClearSelection();
350 break;
351 case SDLK_RIGHT:
352 if (cursor < (int)backingText.length())
353 cursor++;
354 ClearSelection();
355 break;
356 case SDLK_DELETE:
357 if(ReadOnly)
358 break;
359 if (HasSelection())
360 {
361 if (getLowerSelectionBound() < 0 || getHigherSelectionBound() > (int)backingText.length())
362 return;
363 backingText.Erase(getLowerSelectionBound(), getHigherSelectionBound());
364 cursor = getLowerSelectionBound();
365 changed = true;
366 }
367 else if (backingText.length() && cursor < (int)backingText.length())
368 {
369 if (ctrl)
370 {
371 size_t stopChar;
372 stopChar = backingText.SplitByNot(" .,!?\n", cursor).PositionBefore();
373 stopChar = backingText.SplitByAny(" .,!?\n", stopChar).PositionBefore();
374 backingText.EraseBetween(cursor, stopChar);
375 }
376 else
377 backingText.erase(cursor, 1);
378 changed = true;
379 }
380 ClearSelection();
381 break;
382 case SDLK_BACKSPACE:
383 if (ReadOnly)
384 break;
385 if (HasSelection())
386 {
387 if (getLowerSelectionBound() < 0 || getHigherSelectionBound() > (int)backingText.length())
388 return;
389 backingText.erase(backingText.begin()+getLowerSelectionBound(), backingText.begin()+getHigherSelectionBound());
390 cursor = getLowerSelectionBound();
391 changed = true;
392 }
393 else if (backingText.length() && cursor > 0)
394 {
395 if (ctrl)
396 {
397 size_t stopChar;
398 stopChar = backingText.SplitFromEndByNot(" .,!?\n", cursor).PositionBefore();
399 if (stopChar == backingText.npos)
400 stopChar = -1;
401 else
402 stopChar = backingText.SplitFromEndByAny(" .,!?\n", stopChar).PositionBefore();
403 backingText.EraseBetween(stopChar+1, cursor);
404 cursor = stopChar+1;
405 }
406 else
407 {
408 backingText.erase(cursor-1, 1);
409 cursor--;
410 }
411 changed = true;
412 }
413 ClearSelection();
414 break;
415 case SDLK_RETURN:
416 OnTextInput("\n");
417 break;
418 }
419 }
420 catch (std::out_of_range &e)
421 {
422 cursor = 0;
423 backingText = "";
424 }
425 AfterTextChange(changed);
426 }
427
AfterTextChange(bool changed)428 void Textbox::AfterTextChange(bool changed)
429 {
430 if (cursor > (int)backingText.length())
431 cursor = backingText.length();
432
433 if (changed)
434 {
435 if (inputType == Number)
436 {
437 //Remove extra preceding 0's
438 while(backingText[0] == '0' && backingText.length()>1)
439 backingText.erase(backingText.begin());
440 }
441
442 if (masked)
443 {
444 String maskedText = backingText;
445 std::fill(maskedText.begin(), maskedText.end(), 0xE00D);
446 Label::SetText(maskedText);
447 }
448 else
449 {
450 text = backingText;
451 }
452 }
453
454 updateTextWrapper();
455 updateSelection();
456 TextPosition(displayTextWrapper.WrappedText());
457
458 if(cursor)
459 {
460 textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
461 }
462 else
463 {
464 cursorPositionY = cursorPositionX = 0;
465 }
466 if (changed && actionCallback.change)
467 actionCallback.change();
468 }
469
OnTextInput(String text)470 void Textbox::OnTextInput(String text)
471 {
472 if (StringValid(text) && !ReadOnly)
473 {
474 if (HasSelection())
475 {
476 if (getLowerSelectionBound() < 0 || getHigherSelectionBound() > (int)backingText.length())
477 return;
478 backingText.erase(backingText.begin()+getLowerSelectionBound(), backingText.begin()+getHigherSelectionBound());
479 cursor = getLowerSelectionBound();
480 }
481
482 int regionWidth = Size.X;
483 if (Appearance.icon)
484 regionWidth -= 13;
485 regionWidth -= Appearance.Margin.Left;
486 regionWidth -= Appearance.Margin.Right;
487 if ((limit==String::npos || backingText.length() < limit) && (Graphics::textwidth(backingText + text) <= regionWidth || multiline))
488 {
489 if (cursor == (int)backingText.length())
490 {
491 backingText += text;
492 }
493 else
494 {
495 backingText.Insert(cursor, text);
496 }
497 cursor++;
498 }
499 ClearSelection();
500 AfterTextChange(true);
501 }
502 }
503
OnMouseClick(int x,int y,unsigned button)504 void Textbox::OnMouseClick(int x, int y, unsigned button)
505 {
506
507 if (button != SDL_BUTTON_RIGHT)
508 {
509 mouseDown = true;
510 auto index = textWrapper.Point2Index(x-textPosition.X, y-textPosition.Y);
511 cursor = index.raw_index;
512 if(cursor)
513 {
514 textWrapper.Index2Point(index, cursorPositionX, cursorPositionY);
515 }
516 else
517 {
518 cursorPositionY = cursorPositionX = 0;
519 }
520 }
521 Label::OnMouseClick(x, y, button);
522 }
523
OnMouseUp(int x,int y,unsigned button)524 void Textbox::OnMouseUp(int x, int y, unsigned button)
525 {
526 mouseDown = false;
527 Label::OnMouseUp(x, y, button);
528 }
529
OnMouseMoved(int localx,int localy,int dx,int dy)530 void Textbox::OnMouseMoved(int localx, int localy, int dx, int dy)
531 {
532 if(mouseDown)
533 {
534 auto index = textWrapper.Point2Index(localx-textPosition.X, localy-textPosition.Y);
535 cursor = index.raw_index;
536 if(cursor)
537 {
538 textWrapper.Index2Point(index, cursorPositionX, cursorPositionY);
539 }
540 else
541 {
542 cursorPositionY = cursorPositionX = 0;
543 }
544 }
545 Label::OnMouseMoved(localx, localy, dx, dy);
546 }
547
Draw(const Point & screenPos)548 void Textbox::Draw(const Point& screenPos)
549 {
550 Label::Draw(screenPos);
551
552 Graphics * g = GetGraphics();
553 if(IsFocused())
554 {
555 if(border) g->drawrect(screenPos.X, screenPos.Y, Size.X, Size.Y, 255, 255, 255, 255);
556 g->draw_line(screenPos.X+textPosition.X+cursorPositionX, screenPos.Y-2+textPosition.Y+cursorPositionY, screenPos.X+textPosition.X+cursorPositionX, screenPos.Y+9+textPosition.Y+cursorPositionY, 255, 255, 255, 255);
557 }
558 else
559 {
560 if(!text.length())
561 {
562 g->drawtext(screenPos.X+textPosition.X, screenPos.Y+textPosition.Y, placeHolder, textColour.Red, textColour.Green, textColour.Blue, 170);
563 }
564 if(border) g->drawrect(screenPos.X, screenPos.Y, Size.X, Size.Y, 160, 160, 160, 255);
565 }
566 if(Appearance.icon)
567 g->draw_icon(screenPos.X+iconPosition.X, screenPos.Y+iconPosition.Y, Appearance.icon);
568 }
569