1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include "Dialog.hxx"
19#include "StellaKeys.hxx"
20#include "FBSurface.hxx"
21#include "Font.hxx"
22#include "EditableWidget.hxx"
23
24// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
25EditableWidget::EditableWidget(GuiObject* boss, const GUI::Font& font,
26 int x, int y, int w, int h, const string& str)
27 : Widget(boss, font, x, y, w, h),
28 CommandSender(boss),
29 _editable(true),
30 _editString(str)
31{
32 _caretVisible = false;
33 _caretTime = 0;
34 _caretPos = 0;
35
36 _caretInverse = false;
37
38 _editScrollOffset = 0;
39
40 _bgcolor = kWidColor;
41 _bgcolorhi = kWidColor;
42 _bgcolorlo = kDlgColor;
43 _textcolor = kTextColor;
44 _textcolorhi = kTextColor;
45
46 // By default, include all printable chars except quotes
47 _filter = [](char c) { return isprint(c) && c != '\"'; };
48}
49
50// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
51void EditableWidget::setText(const string& str, bool)
52{
53 // Filter input string
54 _editString = "";
55 for(char c: str)
56 if(_filter(tolower(c)))
57 _editString.push_back(c);
58
59 _caretPos = int(_editString.size());
60
61 _editScrollOffset = (_font.getStringWidth(_editString) - (getEditRect().w()));
62 if (_editScrollOffset < 0)
63 _editScrollOffset = 0;
64
65 setDirty();
66}
67// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
68void EditableWidget::setEditable(bool editable, bool hiliteBG)
69{
70 _editable = editable;
71 if(_editable)
72 {
73 setFlags(Widget::FLAG_WANTS_RAWDATA | Widget::FLAG_RETAIN_FOCUS);
74 _bgcolor = kWidColor;
75 }
76 else
77 {
78 clearFlags(Widget::FLAG_WANTS_RAWDATA | Widget::FLAG_RETAIN_FOCUS);
79 _bgcolor = hiliteBG ? kBGColorHi : kWidColor;
80 }
81}
82
83// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
84bool EditableWidget::tryInsertChar(char c, int pos)
85{
86 if(_filter(tolower(c)))
87 {
88 _editString.insert(pos, 1, c);
89 return true;
90 }
91 return false;
92}
93
94// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
95bool EditableWidget::handleText(char text)
96{
97 if(!_editable)
98 return true;
99
100 if(tryInsertChar(text, _caretPos))
101 {
102 _caretPos++;
103 sendCommand(EditableWidget::kChangedCmd, 0, _id);
104 setDirty();
105 return true;
106 }
107 return false;
108}
109
110// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
111bool EditableWidget::handleKeyDown(StellaKey key, StellaMod mod)
112{
113 if(!_editable)
114 return true;
115
116 // Ignore all alt-mod keys
117 if(StellaModTest::isAlt(mod))
118 return true;
119
120 bool handled = true;
121 bool dirty = false;
122
123 switch(key)
124 {
125 case KBDK_RETURN:
126 case KBDK_KP_ENTER:
127 // confirm edit and exit editmode
128 endEditMode();
129 sendCommand(EditableWidget::kAcceptCmd, 0, _id);
130 dirty = true;
131 break;
132
133 case KBDK_ESCAPE:
134 abortEditMode();
135 sendCommand(EditableWidget::kCancelCmd, 0, _id);
136 dirty = true;
137 break;
138
139 case KBDK_BACKSPACE:
140 dirty = killChar(-1);
141 if(dirty) sendCommand(EditableWidget::kChangedCmd, key, _id);
142 break;
143
144 case KBDK_DELETE:
145 case KBDK_KP_PERIOD:
146 dirty = killChar(+1);
147 if(dirty) sendCommand(EditableWidget::kChangedCmd, key, _id);
148 break;
149
150 case KBDK_LEFT:
151 if(StellaModTest::isControl(mod))
152 dirty = specialKeys(key);
153 else if(_caretPos > 0)
154 dirty = setCaretPos(_caretPos - 1);
155 break;
156
157 case KBDK_RIGHT:
158 if(StellaModTest::isControl(mod))
159 dirty = specialKeys(key);
160 else if(_caretPos < int(_editString.size()))
161 dirty = setCaretPos(_caretPos + 1);
162 break;
163
164 case KBDK_HOME:
165 dirty = setCaretPos(0);
166 break;
167
168 case KBDK_END:
169 dirty = setCaretPos(int(_editString.size()));
170 break;
171
172 default:
173 if (StellaModTest::isControl(mod))
174 {
175 dirty = specialKeys(key);
176 }
177 else
178 handled = false;
179 }
180
181 if (dirty)
182 setDirty();
183
184 return handled;
185}
186
187// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
188int EditableWidget::getCaretOffset() const
189{
190 int caretpos = 0;
191 for (int i = 0; i < _caretPos; i++)
192 caretpos += _font.getCharWidth(_editString[i]);
193
194 caretpos -= _editScrollOffset;
195
196 return caretpos;
197}
198
199// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
200void EditableWidget::drawCaret()
201{
202 // Only draw if item is visible
203 if (!_editable || !isVisible() || !_boss->isVisible() || !_hasFocus)
204 return;
205
206 const Common::Rect& editRect = getEditRect();
207 int x = editRect.x();
208 int y = editRect.y();
209
210 x += getCaretOffset();
211
212 x += _x;
213 y += _y;
214
215 FBSurface& s = _boss->dialog().surface();
216 s.vLine(x, y+2, y + editRect.h() - 2, kTextColorHi);
217}
218
219// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
220bool EditableWidget::setCaretPos(int newPos)
221{
222 assert(newPos >= 0 && newPos <= int(_editString.size()));
223 _caretPos = newPos;
224
225 return adjustOffset();
226}
227
228// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
229bool EditableWidget::adjustOffset()
230{
231 // check if the caret is still within the textbox; if it isn't,
232 // adjust _editScrollOffset
233
234 // For some reason (differences in ScummVM event handling??),
235 // this method should always return true.
236
237 int caretpos = getCaretOffset();
238 const int editWidth = getEditRect().w();
239
240 if (caretpos < 0)
241 {
242 // scroll left
243 _editScrollOffset += caretpos;
244 }
245 else if (caretpos >= editWidth)
246 {
247 // scroll right
248 _editScrollOffset -= (editWidth - caretpos);
249 }
250 else if (_editScrollOffset > 0)
251 {
252 const int strWidth = _font.getStringWidth(_editString);
253 if (strWidth - _editScrollOffset < editWidth)
254 {
255 // scroll right
256 _editScrollOffset = (strWidth - editWidth);
257 if (_editScrollOffset < 0)
258 _editScrollOffset = 0;
259 }
260 }
261
262 return true;
263}
264
265// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
266bool EditableWidget::specialKeys(StellaKey key)
267{
268 bool handled = true;
269
270 switch (key)
271 {
272 case KBDK_A:
273 setCaretPos(0);
274 break;
275
276 case KBDK_C:
277 copySelectedText();
278 if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
279 break;
280
281 case KBDK_E:
282 setCaretPos(int(_editString.size()));
283 break;
284
285 case KBDK_D:
286 handled = killChar(+1);
287 if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
288 break;
289
290 case KBDK_K:
291 handled = killLine(+1);
292 if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
293 break;
294
295 case KBDK_U:
296 handled = killLine(-1);
297 if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
298 break;
299
300 case KBDK_V:
301 pasteSelectedText();
302 if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
303 break;
304
305 case KBDK_W:
306 handled = killLastWord();
307 if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
308 break;
309
310 case KBDK_LEFT:
311 handled = moveWord(-1);
312 break;
313
314 case KBDK_RIGHT:
315 handled = moveWord(+1);
316 break;
317
318 default:
319 handled = false;
320 }
321
322 return handled;
323}
324
325// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
326bool EditableWidget::killChar(int direction)
327{
328 bool handled = false;
329
330 if(direction == -1) // Delete previous character (backspace)
331 {
332 if(_caretPos > 0)
333 {
334 _caretPos--;
335 _editString.erase(_caretPos, 1);
336 handled = true;
337 }
338 }
339 else if(direction == 1) // Delete next character (delete)
340 {
341 _editString.erase(_caretPos, 1);
342 handled = true;
343 }
344
345 return handled;
346}
347
348// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
349bool EditableWidget::killLine(int direction)
350{
351 bool handled = false;
352
353 if(direction == -1) // erase from current position to beginning of line
354 {
355 int count = _caretPos;
356 if(count > 0)
357 {
358 for (int i = 0; i < count; i++)
359 killChar(-1);
360
361 handled = true;
362 }
363 }
364 else if(direction == 1) // erase from current position to end of line
365 {
366 int count = int(_editString.size()) - _caretPos;
367 if(count > 0)
368 {
369 for (int i = 0; i < count; i++)
370 killChar(+1);
371
372 handled = true;
373 }
374 }
375
376 return handled;
377}
378
379// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
380bool EditableWidget::killLastWord()
381{
382 bool handled = false;
383 int count = 0, currentPos = _caretPos;
384 bool space = true;
385 while (currentPos > 0)
386 {
387 if (_editString[currentPos - 1] == ' ')
388 {
389 if (!space)
390 break;
391 }
392 else
393 space = false;
394
395 currentPos--;
396 count++;
397 }
398
399 if(count > 0)
400 {
401 for (int i = 0; i < count; i++)
402 killChar(-1);
403
404 handled = true;
405 }
406
407 return handled;
408}
409
410// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
411bool EditableWidget::moveWord(int direction)
412{
413 bool handled = false;
414 bool space = true;
415 int currentPos = _caretPos;
416
417 if(direction == -1) // move to first character of previous word
418 {
419 while (currentPos > 0)
420 {
421 if (_editString[currentPos - 1] == ' ')
422 {
423 if (!space)
424 break;
425 }
426 else
427 space = false;
428
429 currentPos--;
430 }
431 _caretPos = currentPos;
432 handled = true;
433 }
434 else if(direction == +1) // move to first character of next word
435 {
436 while (currentPos < int(_editString.size()))
437 {
438 if (_editString[currentPos - 1] == ' ')
439 {
440 if (!space)
441 break;
442 }
443 else
444 space = false;
445
446 currentPos++;
447 }
448 _caretPos = currentPos;
449 handled = true;
450 }
451
452 return handled;
453}
454
455// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
456void EditableWidget::copySelectedText()
457{
458// _clippedText = _editString;
459}
460
461// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
462void EditableWidget::pasteSelectedText()
463{
464// _editString = _clippedText;
465}
466
467// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
468string EditableWidget::_clippedText = "";
469