1 /***************************************************************************
2 * Mechanized Assault and Exploration Reloaded Projectfile *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
18 ***************************************************************************/
19
20 #include <algorithm>
21
22 #include "ui/graphical/menu/widgets/lineedit.h"
23 #include "ui/graphical/menu/widgets/tools/validator.h"
24 #include "ui/graphical/application.h"
25 #include "ui/graphical/window.h"
26 #include "settings.h"
27 #include "video.h"
28 #include "input/mouse/mouse.h"
29 #include "input/keyboard/keyboard.h"
30 #include "utility/log.h"
31 #include "keys.h"
32
33 //------------------------------------------------------------------------------
cLineEdit(const cBox<cPosition> & area,eLineEditFrameType frameType_,eUnicodeFontType fontType_)34 cLineEdit::cLineEdit (const cBox<cPosition>& area, eLineEditFrameType frameType_, eUnicodeFontType fontType_) :
35 cClickableWidget (area),
36 cursorVisibleTime (800),
37 cursorInvisibleTime (500),
38 text (""),
39 fontType (fontType_),
40 frameType (frameType_),
41 cursorPos (0),
42 startOffset (0),
43 endOffset (0),
44 readOnly (false),
45 hasKeyFocus (false),
46 showCursor (false)
47 {
48 createBackground();
49 }
50
51 //------------------------------------------------------------------------------
~cLineEdit()52 cLineEdit::~cLineEdit()
53 {}
54
55 //------------------------------------------------------------------------------
getText()56 const std::string& cLineEdit::getText()
57 {
58 return text;
59 }
60
61 //------------------------------------------------------------------------------
setText(std::string text_)62 void cLineEdit::setText (std::string text_)
63 {
64 std::swap (text, text_);
65 if (validator)
66 {
67 const auto state = validator->validate (text);
68 if (state != eValidatorState::Valid)
69 {
70 validator->fixup (text);
71 }
72 }
73 resetTextPosition();
74 textSet();
75 }
76
77 //------------------------------------------------------------------------------
setReadOnly(bool readOnly_)78 void cLineEdit::setReadOnly (bool readOnly_)
79 {
80 readOnly = readOnly_;
81 setConsumeClick (!readOnly);
82 }
83
84 //------------------------------------------------------------------------------
setValidator(std::unique_ptr<cValidator> validator_)85 void cLineEdit::setValidator (std::unique_ptr<cValidator> validator_)
86 {
87 validator = std::move (validator_);
88 }
89
90 //------------------------------------------------------------------------------
finishEditing()91 void cLineEdit::finishEditing()
92 {
93 auto application = getActiveApplication();
94 if (application)
95 {
96 application->releaseKeyFocus (*this);
97 }
98 else
99 {
100 finishEditingInternal();
101 }
102 }
103
104 //------------------------------------------------------------------------------
finishEditingInternal()105 void cLineEdit::finishEditingInternal()
106 {
107 SDL_StopTextInput();
108
109 if (validator)
110 {
111 const auto state = validator->validate (text);
112 if (state != eValidatorState::Valid)
113 {
114 validator->fixup (text);
115 resetTextPosition();
116 }
117 editingFinished (state);
118 }
119 else
120 {
121 editingFinished (eValidatorState::Valid);
122 }
123 }
124 //------------------------------------------------------------------------------
draw(SDL_Surface & destination,const cBox<cPosition> & clipRect)125 void cLineEdit::draw (SDL_Surface& destination, const cBox<cPosition>& clipRect)
126 {
127 if (surface != nullptr)
128 {
129 SDL_Rect position = getArea().toSdlRect();
130 SDL_BlitSurface (surface.get(), nullptr, &destination, &position);
131 }
132
133 const auto offsetRect = getTextDrawOffset();
134 const auto cursorXOffset = font->getFontSize (fontType) == FONT_SIZE_SMALL ? -1 : 0;
135
136 const cPosition textPosition = getPosition() + offsetRect;
137
138 font->showText (textPosition.x(), textPosition.y(), text.substr (startOffset, endOffset - startOffset), fontType);
139 if (hasKeyFocus && !readOnly)
140 {
141 const auto now = std::chrono::steady_clock::now();
142
143 if (now - lastCursorBlinkTime > cursorVisibleTime)
144 {
145 showCursor = !showCursor;
146 lastCursorBlinkTime = now;
147 }
148
149 if (showCursor) font->showText (textPosition.x() + cursorXOffset + font->getTextWide (text.substr (startOffset, cursorPos - startOffset), fontType), textPosition.y(), "|", fontType);
150 }
151
152 cClickableWidget::draw (destination, clipRect);
153 }
154
155 //------------------------------------------------------------------------------
handleGetKeyFocus(cApplication & application)156 bool cLineEdit::handleGetKeyFocus (cApplication& application)
157 {
158 if (readOnly) return false;
159
160 const auto hadKeyFocus = hasKeyFocus;
161
162 hasKeyFocus = true;
163
164 showCursor = true;
165 lastCursorBlinkTime = std::chrono::steady_clock::now();
166
167 if (!hadKeyFocus)
168 {
169 cursorPos = (int)text.length();
170 while (cursorPos > endOffset) doPosIncrease (endOffset, endOffset);
171 while (font->getTextWide (text.substr (startOffset, endOffset - startOffset), fontType) > getSize().x() - getBorderSize()) doPosIncrease (startOffset, startOffset);
172 }
173
174 SDL_StartTextInput();
175
176 return true;
177 }
178
179 //------------------------------------------------------------------------------
handleLooseKeyFocus(cApplication & application)180 void cLineEdit::handleLooseKeyFocus (cApplication& application)
181 {
182 if (hasKeyFocus)
183 {
184 hasKeyFocus = false;
185 finishEditingInternal();
186 }
187 }
188
189 //------------------------------------------------------------------------------
handleClicked(cApplication & application,cMouse & mouse,eMouseButtonType button)190 bool cLineEdit::handleClicked (cApplication& application, cMouse& mouse, eMouseButtonType button)
191 {
192 if (readOnly) return false;
193
194 int x = mouse.getPosition().x() - (getPosition().x() + getTextDrawOffset().x());
195 int cursor = startOffset;
196 while (font->getTextWide (text.substr (startOffset, cursor - startOffset), fontType) < x)
197 {
198 doPosIncrease (cursor, cursor);
199 if (cursor >= endOffset)
200 {
201 cursor = endOffset;
202 break;
203 }
204 }
205 cursorPos = cursor;
206
207 return true;
208 }
209
210 //------------------------------------------------------------------------------
createBackground()211 void cLineEdit::createBackground()
212 {
213 if (frameType == eLineEditFrameType::Box)
214 {
215 //surface = SDL_CreateRGBSurface (0, getSize ().x (), getSize ().y (), Video.getColDepth (), 0, 0, 0, 0);
216
217 //SDL_FillRect (surface, nullptr, 0xFF00FF);
218 //SDL_SetColorKey (surface, SDL_TRUE, 0xFF00FF);
219 }
220 else
221 {
222 surface = nullptr;
223 }
224 }
225
226 //------------------------------------------------------------------------------
getTextDrawOffset() const227 cPosition cLineEdit::getTextDrawOffset() const
228 {
229 switch (frameType)
230 {
231 default:
232 case eLineEditFrameType::None:
233 return cPosition (0, 0);
234 case eLineEditFrameType::Box:
235 return cPosition (6, 3);
236 }
237 }
238
239 //------------------------------------------------------------------------------
getBorderSize() const240 int cLineEdit::getBorderSize() const
241 {
242 switch (frameType)
243 {
244 default:
245 case eLineEditFrameType::None:
246 return 0;
247 case eLineEditFrameType::Box:
248 return 12;
249 }
250 }
251
252 //------------------------------------------------------------------------------
resetTextPosition()253 void cLineEdit::resetTextPosition()
254 {
255 startOffset = 0;
256 endOffset = (int)text.length();
257 cursorPos = endOffset;
258 while (font->getTextWide (text.substr (startOffset, endOffset - startOffset), fontType) > getSize().x() - getBorderSize()) doPosDecrease (endOffset);
259 }
260
261 //------------------------------------------------------------------------------
doPosIncrease(int & value,int pos)262 void cLineEdit::doPosIncrease (int& value, int pos)
263 {
264 if (pos < (int)text.length())
265 {
266 unsigned char c = text[pos];
267 if ((c & 0xE0) == 0xE0) value += 3;
268 else if ((c & 0xC0) == 0xC0) value += 2;
269 else value += 1;
270 }
271
272 if (value > (int)text.length())
273 {
274 value = (int)text.length();
275 Log.write ("Invalid UTF-8 string in line edit: '" + text + "'", cLog::eLOG_TYPE_WARNING);
276 }
277 }
278
279 //------------------------------------------------------------------------------
doPosDecrease(int & pos)280 void cLineEdit::doPosDecrease (int& pos)
281 {
282 if (pos > 0)
283 {
284 unsigned char c = text[pos - 1];
285 while (((c & 0xE0) != 0xE0) && ((c & 0xC0) != 0xC0) && ((c & 0x80) == 0x80))
286 {
287 if (pos <= 1)
288 {
289 Log.write ("Invalid UTF-8 string in line edit: '" + text + "'", cLog::eLOG_TYPE_WARNING);
290 break;
291 }
292
293 pos--;
294 c = text[pos - 1];
295 }
296 pos--;
297 }
298 }
299
300 //------------------------------------------------------------------------------
scrollLeft(bool changeCursor)301 void cLineEdit::scrollLeft (bool changeCursor)
302 {
303 // makes the cursor go left
304 if (changeCursor && cursorPos > 0) doPosDecrease (cursorPos);
305
306 if (cursorPos > 0) while (cursorPos - 1 < startOffset) doPosDecrease (startOffset);
307 else while (cursorPos < startOffset) doPosDecrease (startOffset);
308
309 if (font->getTextWide (text.substr (startOffset, text.length() - startOffset), fontType) > getSize().x() - getBorderSize())
310 {
311 endOffset = (int)text.length();
312 while (font->getTextWide (text.substr (startOffset, endOffset - startOffset), fontType) > getSize().x() - getBorderSize()) doPosDecrease (endOffset);
313 }
314 }
315
316 //------------------------------------------------------------------------------
scrollRight()317 void cLineEdit::scrollRight()
318 {
319 // makes the cursor go right
320 if (cursorPos < (int)text.length()) doPosIncrease (cursorPos, cursorPos);
321 assert (cursorPos <= (int)text.length());
322 while (cursorPos > endOffset) doPosIncrease (endOffset, endOffset);
323 while (font->getTextWide (text.substr (startOffset, endOffset - startOffset), fontType) > getSize().x() - getBorderSize()) doPosIncrease (startOffset, startOffset);
324 }
325
326 //------------------------------------------------------------------------------
deleteLeft()327 void cLineEdit::deleteLeft()
328 {
329 // deletes the first character left from the cursor
330 if (cursorPos > 0)
331 {
332 unsigned char c = text[cursorPos - 1];
333 while (((c & 0xE0) != 0xE0) && ((c & 0xC0) != 0xC0) && ((c & 0x80) == 0x80))
334 {
335 if (cursorPos <= 1)
336 {
337 Log.write ("Invalid UTF-8 string in line edit: '" + text + "'", cLog::eLOG_TYPE_WARNING);
338 break;
339 }
340
341 text.erase (cursorPos - 1, 1);
342 cursorPos--;
343 c = text[cursorPos - 1];
344 }
345 text.erase (cursorPos - 1, 1);
346 cursorPos--;
347 endOffset = std::min<int> (text.length(), endOffset);
348 scrollLeft (false);
349 }
350 }
351
352 //------------------------------------------------------------------------------
deleteRight()353 void cLineEdit::deleteRight()
354 {
355 // deletes the first character right from the cursor
356 if (cursorPos < (int)text.length())
357 {
358 unsigned char c = text[cursorPos];
359 if ((c & 0xE0) == 0xE0) text.erase (cursorPos, 3);
360 else if ((c & 0xC0) == 0xC0) text.erase (cursorPos, 2);
361 else text.erase (cursorPos, 1);
362 endOffset = std::min<int> (text.length(), endOffset);
363 }
364 }
365
366 //------------------------------------------------------------------------------
handleKeyPressed(cApplication & application,cKeyboard & keyboard,SDL_Keycode key)367 bool cLineEdit::handleKeyPressed (cApplication& application, cKeyboard& keyboard, SDL_Keycode key)
368 {
369 if (readOnly || !hasKeyFocus) return false;
370
371 switch (key)
372 {
373 case SDLK_ESCAPE:
374 escapePressed();
375 application.releaseKeyFocus (*this);
376 break;
377 case SDLK_KP_ENTER: // fall through
378 case SDLK_RETURN:
379 returnPressed();
380 break;
381 case SDLK_LEFT:
382 scrollLeft();
383 break;
384 case SDLK_RIGHT:
385 scrollRight();
386 break;
387 case SDLK_HOME:
388 cursorPos = 0;
389 startOffset = 0;
390 endOffset = (int)text.length();
391 while (font->getTextWide (text.substr (startOffset, endOffset - startOffset), fontType) > getSize().x() - getBorderSize()) doPosDecrease (endOffset);
392 break;
393 case SDLK_END:
394 cursorPos = (int)text.length();
395 startOffset = 0;
396 endOffset = (int)text.length();
397 while (font->getTextWide (text.substr (startOffset, endOffset - startOffset), fontType) > getSize().x() - getBorderSize()) doPosIncrease (startOffset, startOffset);
398 break;
399 case SDLK_BACKSPACE:
400 deleteLeft();
401 break;
402 case SDLK_DELETE:
403 deleteRight();
404 break;
405 case SDLK_c:
406 if (keyboard.getCurrentModifiers() & (toEnumFlag (eKeyModifierType::CtrlLeft) | eKeyModifierType::CtrlRight))
407 {
408 SDL_SetClipboardText (text.c_str());
409 }
410 break;
411 case SDLK_x:
412 if (keyboard.getCurrentModifiers() & (toEnumFlag (eKeyModifierType::CtrlLeft) | eKeyModifierType::CtrlRight))
413 {
414 SDL_SetClipboardText (text.c_str());
415 text.clear();
416 resetTextPosition();
417 }
418 break;
419 case SDLK_v:
420 if (keyboard.getCurrentModifiers() & (toEnumFlag (eKeyModifierType::CtrlLeft) | eKeyModifierType::CtrlRight) &&
421 SDL_HasClipboardText())
422 {
423 const auto clipboardText = SDL_GetClipboardText();
424
425 if (clipboardText == nullptr) break;
426
427 const auto clipboardFree = makeScopedOperation ([clipboardText]() { SDL_free (clipboardText); });
428
429 std::string insertText (clipboardText);
430
431 if (validator)
432 {
433 const auto state = validator->validate (insertText);
434 if (state == eValidatorState::Invalid) break;
435 }
436
437 text.insert (cursorPos, insertText);
438
439 cursorPos += insertText.size();
440
441 endOffset = cursorPos;
442
443 while (font->getTextWide (text.substr (startOffset, endOffset - startOffset), fontType) > getSize().x() - getBorderSize()) doPosIncrease (startOffset, startOffset);
444 }
445 break;
446 default: // normal characters are handled as textInput:
447 break;
448 }
449 return true;
450 }
451
452 //------------------------------------------------------------------------------
handleTextEntered(cApplication & application,cKeyboard & keyboard,const char * inputText)453 void cLineEdit::handleTextEntered (cApplication& application, cKeyboard& keyboard, const char* inputText)
454 {
455 if (readOnly || !hasKeyFocus) return;
456
457 text.insert (cursorPos, inputText);
458
459 if (validator)
460 {
461 const auto state = validator->validate (text);
462 if (state == eValidatorState::Invalid)
463 {
464 validator->fixup (text);
465 resetTextPosition();
466 }
467 }
468 if (cursorPos < (int)text.length()) doPosIncrease (cursorPos, cursorPos);
469 if (cursorPos >= endOffset)
470 {
471 doPosIncrease (endOffset, endOffset);
472 while (font->getTextWide (text.substr (startOffset, endOffset - startOffset), fontType) > getSize().x() - getBorderSize()) doPosIncrease (startOffset, startOffset);
473 }
474 else
475 {
476 if (font->getTextWide (text.substr (startOffset, endOffset - startOffset), fontType) > getSize().x() - getBorderSize()) doPosDecrease (endOffset);
477 else doPosIncrease (endOffset, cursorPos);
478 }
479 }
480