1// This file is part of SmallBASIC
2//
3// Copyright(C) 2001-2014 Chris Warren-Smith.
4//
5// This program is distributed under the terms of the GPL v2.0 or later
6// Download the GNU Public License (GPL) from www.gnu.org
7//
8
9#include "config.h"
10
11#include "common/sys.h"
12#include "common/sbapp.h"
13#include "common/keymap.h"
14#include "ui/utils.h"
15#include "ui/inputs.h"
16#include "ui/image.h"
17#include "ui/system.h"
18
19extern System *g_system;
20
21FormList *activeList = nullptr;
22FormInput *focusInput = nullptr;
23FormEditInput *focusEdit = nullptr;
24int background = DEFAULT_BACKGROUND;
25int foreground = DEFAULT_FOREGROUND;
26
27#define LINE_Y (_height - 2)
28#define LINE_W (_width - 2)
29
30FormEditInput *get_focus_edit() {
31 return focusEdit;
32}
33
34bool form_ui::optionSelected(int index) {
35 bool result = false;
36 if (activeList != nullptr) {
37 activeList->optionSelected(index);
38 activeList = nullptr;
39 result = false;
40 }
41 return result;
42}
43
44void set_input_defaults(int fg, int bg) {
45 foreground = fg;
46 background = bg;
47}
48
49int get_color(var_p_t value, int def) {
50 int result = def;
51 if (value != nullptr && value->type == V_INT) {
52 result = value->v.i;
53 if (result < 0) {
54 result = -result;
55 } else if (result < 16) {
56 result = colors[result];
57 }
58 } else if (value != nullptr && value->type == V_STR &&
59 value->v.p.length) {
60 const char *n = value->v.p.ptr;
61 if (n[0] == '0' && n[1] == 'x' && n[2]) {
62 result = strtol(n + 2, nullptr, 16);
63 } else if (n[0] == '#' && n[1]) {
64 result = strtol(n + 1, nullptr, 16);
65 } else if (strcasecmp(n, "black") == 0) {
66 result = DEFAULT_BACKGROUND;
67 } else if (strcasecmp(n, "red") == 0) {
68 result = 0x800000;
69 } else if (strcasecmp(n, "green") == 0) {
70 result = 0x008000;
71 } else if (strcasecmp(n, "yellow") == 0) {
72 result = 0x808000;
73 } else if (strcasecmp(n, "blue") == 0) {
74 result = 0x000080;
75 } else if (strcasecmp(n, "magenta") == 0 ||
76 strcasecmp(n, "fuchsia") == 0) {
77 result = 0x800080;
78 } else if (strcasecmp(n, "cyan") == 0 ||
79 strcasecmp(n, "aqua") == 0) {
80 result = 0x008080;
81 } else if (strcasecmp(n, "white") == 0) {
82 result = 0xFFFFFF;
83 } else if (strcasecmp(n, "gray") == 0 ||
84 strcasecmp(n, "grey") == 0) {
85 result = 0x808080;
86 }
87 }
88 return result;
89}
90
91int lerp(int c0, int c1, float weight) {
92 int result;
93 if (weight <= 0) {
94 result = c0;
95 } else if (weight >= 1) {
96 result = c1;
97 } else {
98 uint8_t r = ((uint8_t)(c1 >> 24)) * weight + ((uint8_t)(c0 >> 24)) * (1 - weight);
99 uint8_t g = ((uint8_t)(c1 >> 16)) * weight + ((uint8_t)(c0 >> 16)) * (1 - weight);
100 uint8_t b = ((uint8_t)(c1 >> 8)) * weight + ((uint8_t)(c0 >> 8)) * (1 - weight);
101 result = (r << 24) + (g << 16) + (b << 8);
102 }
103 return result;
104}
105
106//
107// FormInput
108//
109FormInput::FormInput(int x, int y, int w, int h) :
110 Shape(x, y, w, h),
111 _pressed(false),
112 _id(0),
113 _exit(false),
114 _visible(true),
115 _noFocus(false),
116 _resizable(false),
117 _bg(background),
118 _fg(foreground),
119 _onclick(0) {
120}
121
122FormInput::~FormInput() {
123 if (focusInput == this) {
124 focusInput = nullptr;
125 }
126}
127
128void FormInput::construct(var_p_t form, var_p_t field, int id) {
129 _exit = (map_get_bool(field, FORM_INPUT_IS_EXIT));
130 if (!_noFocus) {
131 _noFocus = (map_get_bool(field, FORM_INPUT_NO_FOCUS));
132 }
133 _id = id;
134
135 var_p_t v_id = map_get(field, FORM_INPUT_ID);
136 if (v_id == nullptr) {
137 map_add_var(field, FORM_INPUT_ID, _id);
138 } else {
139 v_setint(v_id, _id);
140 }
141
142 var_p_t v_onclick = map_get(field, FORM_INPUT_ONCLICK);
143 if (v_onclick != nullptr && v_onclick->type == V_PTR) {
144 _onclick = v_onclick->v.ap.p;
145 }
146
147 const char *caption = getText();
148 MAExtent extent = maGetTextSize(caption != nullptr && caption[0] ? caption : "Z");
149 int textW = EXTENT_X(extent);
150 int textH = EXTENT_Y(extent);
151
152 if (_width <= 0 && caption != nullptr) {
153 _width = textW + padding(false);
154 }
155
156 if (_height <= 0 || _height < (textH + padding(true))) {
157 _height = textH + padding(true);
158 }
159
160 AnsiWidget *out = g_system->getOutput();
161 int formX = out->getX();
162 int formY = out->getY();
163
164 if (_x < 0) {
165 _x = (formX - _x) - 1;
166 }
167
168 if (_y < 0) {
169 _y = (formY - _y) - 1;
170 }
171
172 formX = _x + _width;
173 formY = _y;
174
175 if (formX > out->getWidth()) {
176 formX = 0;
177 }
178
179 out->setXY(formX, formY);
180 updateUI(form, field);
181}
182
183void FormInput::drawButton(const char *caption, int dx, int dy,
184 int w, int h, bool pressed) {
185 int r = dx + w;
186 int b = dy + h - 2;
187 MAExtent textSize = maGetTextSize(caption);
188 int textW = EXTENT_X(textSize);
189 int textH = EXTENT_Y(textSize);
190 int textX = dx + (w - textW - 1) / 2;
191 int textY = dy + (h - textH - 1) / 2;
192
193 maSetColor(_bg);
194 maFillRect(dx, dy, r-dx, b-dy);
195
196 if (pressed) {
197 maSetColor(0x909090);
198 maLine(dx, dy, r, dy); // top
199 maLine(dx, dy, dx, b); // left
200 maSetColor(0xd0d0d0);
201 maLine(dx+1, b, r, b); // bottom
202 maLine(r, dy+1, r, b); // right
203 maSetColor(0x606060);
204 maLine(dx+1, dy+1, r-1, dy+1); // top
205 maLine(dx+1, dy+1, dx+1, b-1); // left
206 textX += 2;
207 textY += 2;
208 } else {
209 maSetColor(0xd0d0d0);
210 maLine(dx, dy, r, dy); // top
211 maLine(dx, dy, dx, b); // left
212 maSetColor(0x606060);
213 maLine(dx, b, r, b); // bottom
214 maLine(r, dy, r, b); // right
215 maSetColor(0x909090);
216 maLine(dx+1, b-1, r-1, b-1); // bottom
217 maLine(r-1, dy+1, r-1, b-1); // right
218 }
219
220 if (caption && caption[0]) {
221 setTextColor();
222 maDrawText(textX, textY, caption, strlen(caption));
223 }
224}
225
226void FormInput::drawHover(int dx, int dy, bool selected) {
227 MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN);
228 maSetColor(selected ? _fg : _bg);
229 int y = _y + dy + LINE_Y;
230 maLine(dx + _x + 2, y, dx + _x + LINE_W, y);
231 maUpdateScreen();
232 maSetDrawTarget(currentHandle);
233}
234
235void FormInput::drawLink(const char *caption, int dx, int dy, int sw, int chw) {
236 maSetColor(_fg);
237 drawText(caption, dx, dy, sw, chw);
238}
239
240void FormInput::drawText(const char *caption, int dx, int dy, int sw, int chw) {
241 if (caption != nullptr) {
242 int strWidth = chw * strlen(caption);
243 int width = sw - dx;
244 if (width > _width) {
245 width = _width;
246 }
247 if (strWidth > width) {
248 int len = width / chw;
249 if (len > 0) {
250 maDrawText(dx, dy, caption, len - 1);
251 if (caption[len - 1] != ' ') {
252 maDrawText(dx + ((len - 1) * chw), dy, "~", 1);
253 }
254 }
255 } else if (strWidth) {
256 maDrawText(dx, dy, caption, strlen(caption));
257 }
258 }
259}
260
261void FormInput::draw(int x, int y, int w, int h, int chw) {
262 drawButton(getText(), x, y, _width, _height, _pressed);
263}
264
265bool FormInput::overlaps(MAPoint2d pt, int offsX, int offsY) {
266 return !(OUTSIDE_RECT(pt.x, pt.y, _x + offsX, _y + offsY, _width, _height));
267}
268
269bool FormInput::selected(MAPoint2d pt, int scrollX, int scrollY, bool &redraw) {
270 return FormInput::overlaps(pt, scrollX, scrollY);
271}
272
273void FormInput::setTextColor() {
274 maSetColor(hasFocus() || _noFocus ? _fg : lerp(_bg, _fg, 0.7));
275}
276
277void FormInput::setHelpTextColor() {
278 maSetColor(lerp(_bg, _fg, 0.4));
279}
280
281// returns the field var attached to the field
282var_p_t FormInput::getField(var_p_t form) {
283 var_p_t result = nullptr;
284 if (form->type == V_MAP) {
285 var_p_t inputs = map_get(form, FORM_INPUTS);
286 if (inputs != nullptr && inputs->type == V_ARRAY) {
287 for (unsigned i = 0; i < v_asize(inputs) && !result; i++) {
288 var_p_t elem = v_elem(inputs, i);
289 if (elem->type == V_MAP && (_id == map_get_int(elem, FORM_INPUT_ID, -1))) {
290 result = elem;
291 }
292 }
293 }
294 }
295 return result;
296}
297
298// copy basic variable to widget state when the variable has changed
299bool FormInput::updateUI(var_p_t form, var_p_t field) {
300 bool updated = false;
301
302 var_p_t var = map_get(field, FORM_INPUT_LABEL);
303 if (var == nullptr) {
304 var = map_get(form, FORM_INPUT_LABEL);
305 }
306 if (var != nullptr) {
307 setText(v_getstr(var));
308 updated = true;
309 }
310
311 var_p_t v_bg = map_get(field, FORM_INPUT_BG);
312 if (v_bg == nullptr) {
313 v_bg = map_get(form, FORM_INPUT_BG);
314 }
315 if (v_bg != nullptr) {
316 int bg = get_color(v_bg, _bg);
317 if (bg != _bg) {
318 updated = true;
319 _bg = bg;
320 }
321 }
322
323 var_p_t v_fg = map_get(field, FORM_INPUT_FG);
324 if (v_fg == nullptr) {
325 v_fg = map_get(form, FORM_INPUT_FG);
326 }
327 if (v_fg != nullptr) {
328 int fg = get_color(v_fg, _fg);
329 if (fg != _fg) {
330 updated = true;
331 _fg = fg;
332 }
333 }
334
335 bool visible = map_get_int(field, FORM_INPUT_VISIBLE, 1) != 0;
336 if (visible != _visible) {
337 updated = true;
338 _visible = visible;
339 }
340
341 bool resizable = map_get_bool(field, FORM_INPUT_RESIZABLE);
342 if (resizable != _resizable) {
343 updated = true;
344 _resizable = resizable;
345 }
346
347 return updated;
348}
349
350bool FormInput::edit(int key, int screenWidth, int charWidth) {
351 bool result = false;
352 if (key == SB_KEY_ENTER) {
353 clicked(-1, -1, false);
354 result = true;
355 }
356 return result;
357}
358
359// set the widget value onto the form value
360void FormInput::updateForm(var_p_t form) {
361 var_p_t field = getField(form);
362 if (field != nullptr) {
363 var_p_t inputValue = map_get(field, FORM_INPUT_VALUE);
364 var_p_t value = map_get(form, FORM_VALUE);
365 if (value != nullptr && inputValue != nullptr) {
366 v_set(value, inputValue);
367 }
368 }
369}
370
371bool FormInput::hasFocus() const {
372 return (focusInput == this);
373}
374
375void FormInput::setFocus(bool focus) {
376 focusEdit = nullptr;
377 if (!_noFocus && focus == (focusInput != this)) {
378 focusInput = focus ? this : nullptr;
379 g_system->getOutput()->setDirty();
380 }
381}
382
383//
384// FormButton
385//
386FormButton::FormButton(const char *caption, int x, int y, int w, int h) :
387 FormInput(x, y, w, h),
388 _label(caption) {
389}
390
391//
392// FormLabel
393//
394FormLabel::FormLabel(const char *caption, int x, int y, int w, int h) :
395 FormInput(x, y, w, h),
396 _label(caption) {
397}
398
399bool FormLabel::updateUI(var_p_t form, var_p_t field) {
400 bool updated = FormInput::updateUI(form, field);
401 var_p_t var = map_get(field, FORM_INPUT_LABEL);
402 if (var != nullptr && var->type == V_STR && !_label.equals(var->v.p.ptr)) {
403 _label = var->v.p.ptr;
404 updated = true;
405 }
406 return updated;
407}
408
409void FormLabel::draw(int x, int y, int w, int h, int chw) {
410 maSetColor(_fg);
411 drawText(_label.c_str(), x, y, w, chw);
412}
413
414//
415// FormLink
416//
417FormLink::FormLink(const char *link, bool external, int x, int y, int w, int h) :
418 FormInput(x, y, w, h),
419 _link(link),
420 _external(external) {
421}
422
423void FormLink::draw(int x, int y, int w, int h, int chw) {
424 drawLink(_link.c_str(), x, y, w, chw);
425 if (_pressed) {
426 maSetColor(_pressed ? _fg : _bg);
427 maLine(x, y + LINE_Y, x + LINE_W, y + LINE_Y);
428 }
429}
430
431//
432// FormEditInput
433//
434FormEditInput::FormEditInput(int x, int y, int w, int h) :
435 FormInput(x, y, w, h),
436 _controlMode(false) {
437}
438
439FormEditInput::~FormEditInput() {
440 if (focusEdit == this) {
441 focusEdit = nullptr;
442 }
443}
444
445int FormEditInput::getControlKey(int key) {
446 int result = key;
447 if (_controlMode) {
448 switch (key) {
449 case 'x':
450 g_system->setClipboardText(copy(true));
451 result = -1;
452 break;
453 case 'c':
454 g_system->setClipboardText(copy(false));
455 result = -1;
456 break;
457 case 'v':
458 paste(g_system->getClipboardText());
459 result = -1;
460 break;
461 case 'h':
462 result = SB_KEY_LEFT;
463 break;
464 case 'l':
465 result = SB_KEY_RIGHT;
466 break;
467 case 'j':
468 result = SB_KEY_HOME;
469 break;
470 case 'k':
471 result = SB_KEY_END;
472 break;
473 case 'a':
474 selectAll();
475 break;
476 }
477 }
478 return result;
479}
480
481void FormEditInput::setFocus(bool focus) {
482 if (!_noFocus && focus == (focusInput != this)) {
483 focusInput = focus ? this : nullptr;
484 focusEdit = focus ? this : nullptr;
485 g_system->getOutput()->setDirty();
486 }
487}
488
489void FormEditInput::clicked(int x, int y, bool pressed) {
490 if (pressed && g_system->isRunning()) {
491 dev_clrkb();
492 setFocus(true);
493 focusEdit = this;
494 }
495}
496
497//
498// FormLineInput
499//
500FormLineInput::FormLineInput(const char *value, const char *help, int size, bool grow,
501 int x, int y, int w, int h) :
502 FormEditInput(x, y, w, h),
503 _help(help),
504 _buffer(nullptr),
505 _size(size),
506 _scroll(0),
507 _mark(-1),
508 _point(0),
509 _grow(grow) {
510 _buffer = new char[_size + 1];
511 _buffer[0] = '\0';
512 if (value != nullptr && value[0]) {
513 int len = MIN(strlen(value), (unsigned)_size);
514 memcpy(_buffer, value, len);
515 _buffer[len] = '\0';
516 _mark = _point = len;
517 }
518}
519
520FormLineInput::~FormLineInput() {
521 delete [] _buffer;
522 _buffer = nullptr;
523}
524
525void FormLineInput::clicked(int x, int y, bool pressed) {
526 FormEditInput::clicked(x, y, pressed);
527 if (pressed && g_system->isRunning()) {
528 AnsiWidget *out = g_system->getOutput();
529 int charWidth = out->getCharWidth();
530 int selected = (x - _x) / charWidth;
531 int len = strlen(_buffer);
532 if (selected > len) {
533 selected = len;
534 _mark = selected;
535 }
536 if (selected > -1) {
537 _point = selected;
538 out->setDirty();
539 }
540 }
541}
542
543void FormLineInput::draw(int x, int y, int w, int h, int chw) {
544 maSetColor(_bg);
545 maFillRect(x, y, _width, _height);
546 setTextColor();
547
548 int len = strlen(_buffer + _scroll);
549 if (len * chw >= _width) {
550 len = _width / chw;
551 }
552
553 if (!len && !_help.empty()) {
554 if (hasFocus()) {
555 setTextColor();
556 maFillRect(x, y, chw, _height);
557 }
558 setHelpTextColor();
559 drawText(_help.c_str(), x + (chw * 0), y, w, chw);
560 } else {
561 maDrawText(x, y, _buffer + _scroll, len);
562 if (_mark != _point && _mark != -1) {
563 int chars = abs(_mark - _point);
564 int start = MIN(_mark, _point);
565 int width = chars * chw;
566 int px = x + (start * chw);
567 setTextColor();
568 maFillRect(px, y, width, _height);
569 maSetColor(_bg);
570 maDrawText(px, y, _buffer + _scroll + start, chars);
571 } else if (hasFocus()) {
572 int px = x + (_point * chw);
573 maFillRect(px, y, chw, _height);
574 if (_point < len) {
575 maSetColor(_bg);
576 maDrawText(px, y, _buffer + _scroll + _point, 1);
577 }
578 }
579 }
580}
581
582bool FormLineInput::edit(int key, int screenWidth, int charWidth) {
583 int wChars;
584 int len = _buffer == nullptr ? 0 : strlen(_buffer);
585
586 key = getControlKey(key);
587 switch (key) {
588 case SB_KEY_BACKSPACE:
589 // backspace
590 if (len > 0) {
591 if (_mark != _point) {
592 cut();
593 } else {
594 if (_scroll) {
595 _scroll--;
596 } else if (_point > 0) {
597 _point--;
598 }
599 int j = _scroll + _point;
600 while (j < len - 1) {
601 _buffer[j] = _buffer[j + 1];
602 j++;
603 }
604 _buffer[j] = '\0';
605 }
606 }
607 break;
608 case SB_KEY_DELETE:
609 if (_mark != _point) {
610 cut();
611 } else {
612 int j = _point + _scroll;
613 while (j < len - 1) {
614 _buffer[j] = _buffer[j + 1];
615 j++;
616 }
617 _buffer[j] = '\0';
618 }
619 break;
620 case SB_KEY_LEFT:
621 if (_point > 0) {
622 _point--;
623 } else if (_scroll > 0) {
624 _scroll--;
625 }
626 break;
627 case SB_KEY_RIGHT:
628 if ((_scroll + _point) < len) {
629 int maxPoint = (_width / charWidth) - 1;
630 if (_point < maxPoint) {
631 _point++;
632 } else {
633 _scroll++;
634 }
635 }
636 break;
637 case SB_KEY_HOME:
638 _scroll = 0;
639 _mark = _point = 0;
640 break;
641 case SB_KEY_END:
642 wChars = (_width / charWidth) - 1;
643 if (len > wChars) {
644 _mark = _point = wChars;
645 _scroll = len - wChars;
646 } else {
647 _mark = _point = len;
648 _scroll = 0;
649 }
650 break;
651 default:
652 if (key >= SB_KEY_SPACE && !_controlMode) {
653 // insert
654 if (_mark != _point) {
655 cut();
656 }
657 if (len < _size - 1) {
658 int j = len;
659 int point = _scroll + _point;
660 if (point < len) {
661 // insert
662 while (j >= point) {
663 _buffer[j + 1] = _buffer[j];
664 j--;
665 }
666 }
667
668 _buffer[point] = key;
669 _buffer[++len] = '\0';
670
671 if (_grow && (_x + _width + charWidth < screenWidth)
672 && (len * charWidth) >= _width) {
673 _width += charWidth;
674 }
675 int maxPoint = (MIN(_width, screenWidth) / charWidth) - 1;
676 if (_point < maxPoint) {
677 _point++;
678 } else {
679 _scroll++;
680 }
681 }
682 } else if (key > 0) {
683 maShowVirtualKeyboard();
684 }
685 break;
686 }
687 if (key != -1) {
688 _mark = _point;
689 }
690 return true;
691}
692
693void FormLineInput::selectAll() {
694 _point = 0;
695 _mark = _buffer == nullptr ? -0 : strlen(_buffer);
696}
697
698void FormLineInput::updateField(var_p_t form) {
699 var_p_t field = getField(form);
700 if (field != nullptr) {
701 var_p_t value = map_get(field, FORM_INPUT_VALUE);
702 if (value != nullptr) {
703 v_setstr(value, _buffer);
704 }
705 }
706}
707
708bool FormLineInput::updateUI(var_p_t form, var_p_t field) {
709 bool updated = FormInput::updateUI(form, field);
710 var_p_t var = map_get(field, FORM_INPUT_VALUE);
711 if (var != nullptr) {
712 const char *value = v_getstr(var);
713 if (value && strcmp(value, _buffer) != 0) {
714 int len = MIN(strlen(value), (unsigned)_size);
715 memcpy(_buffer, value, len);
716 _buffer[len] = '\0';
717 _mark = _point = len;
718 updated = true;
719 }
720 }
721 return updated;
722}
723
724char *FormLineInput::copy(bool cut) {
725 char *result = nullptr;
726 int len = strlen(_buffer);
727 int start = MIN(_mark, _point);
728 if (_mark != -1 && start < len) {
729 int chars = _mark == _point ? 1 : abs(_mark - _point);
730 char *offset = _buffer + _scroll + start;
731 result = (char *)malloc(chars + 1);
732 memcpy(result, offset, chars);
733 result[chars] = '\0';
734 if (cut) {
735 _mark = _point = start;
736 memcpy(offset, offset + chars, chars);
737 len -= chars;
738 _buffer[len] = '\0';
739 }
740 }
741 return result;
742}
743
744void FormLineInput::cut() {
745 int len = strlen(_buffer);
746 int start = MIN(_mark, _point);
747 if (_mark != -1 && start < len) {
748 int chars = _mark == _point ? 1 : abs(_mark - _point);
749 char *offset = _buffer + _scroll + start;
750 _mark = _point = start;
751 memcpy(offset, offset + chars, chars);
752 len -= chars;
753 _buffer[len] = '\0';
754 }
755}
756
757void FormLineInput::paste(const char *text) {
758 int len = strlen(_buffer);
759 int avail = _size - (len + 1);
760 if (text != nullptr && avail > 0) {
761 int index = _scroll + _point;
762 int count = strlen(text);
763 if (count > avail) {
764 count = avail;
765 }
766
767 // move existing text to the right
768 int size = len - index;
769 int end = len + count - 1;
770 for (int i = 0; i < size; i++) {
771 _buffer[end - i] = _buffer[len - i - 1];
772 }
773
774 // terminate buffer
775 _buffer[len + count] = '\0';
776
777 // insert new text
778 for (int i = 0; i < count; i++) {
779 _buffer[index + i] = text[i];
780 }
781
782 // remove selection
783 _mark = _point;
784
785 if (_grow) {
786 // enlarge the edit box for the number of inserted characters
787 int charWidth = g_system->getOutput()->getCharWidth();
788 int maxSize = g_system->getOutput()->getScreenWidth() - _x;
789 int textSize = (len + count + 1) * charWidth;
790 if (textSize > maxSize) {
791 textSize = maxSize;
792 }
793 if (textSize > _width) {
794 _width = textSize;
795 }
796 }
797 }
798}
799
800//
801// ListModel
802//
803ListModel::ListModel(int index, var_t *v) :
804 _selectedIndex(index) {
805 create(v);
806}
807
808void ListModel::clear() {
809 _list.removeAll();
810}
811
812void ListModel::create(var_t *v) {
813 if (v->type == V_ARRAY) {
814 fromArray(v);
815 } else if (v->type == V_STR) {
816 // construct from a string like "Easy|Medium|Hard"
817 const char *items = v->v.p.ptr;
818 int len = items ? strlen(items) : 0;
819 for (int i = 0; i < len; i++) {
820 const char *c = strchr(items + i, '|');
821 int end_index = c ? c - items : len;
822 if (end_index > 0) {
823 strlib::String *s = new strlib::String(items + i, end_index - i);
824 _list.add(s);
825 i = end_index;
826 }
827 }
828 }
829 if (_selectedIndex == -1) {
830 _selectedIndex = 0;
831 }
832}
833
834// construct from an array of values
835void ListModel::fromArray(var_t *v) {
836 for (unsigned i = 0; i < v_asize(v); i++) {
837 var_t *el_p = v_elem(v, i);
838 if (el_p->type == V_STR) {
839 _list.add(new strlib::String((const char *)el_p->v.p.ptr));
840 } else if (el_p->type == V_INT) {
841 char buff[40];
842 sprintf(buff, VAR_INT_FMT, el_p->v.i);
843 _list.add(new strlib::String(buff));
844 } else if (el_p->type == V_ARRAY) {
845 fromArray(el_p);
846 }
847 }
848}
849
850// return the text at the given index
851const char *ListModel::getTextAt(int index) {
852 const char *s = 0;
853 if (index > -1 && index < _list.size()) {
854 s = _list[index]->c_str();
855 }
856 return s;
857}
858
859// returns the model index corresponding to the given string
860int ListModel::getIndex(const char *t) {
861 int size = _list.size();
862 for (int i = 0; i < size; i++) {
863 if (!strcasecmp(_list[i]->c_str(), t)) {
864 return i;
865 }
866 }
867 return -1;
868}
869
870//
871// FormList
872//
873FormList::FormList(ListModel *model, int x, int y, int w, int h) :
874 FormInput(x, y, w, h),
875 _model(model),
876 _topIndex(0),
877 _activeIndex(-1) {
878}
879
880FormList::~FormList() {
881 if (activeList == this) {
882 activeList = nullptr;
883 }
884 delete _model;
885 _model = nullptr;
886}
887
888void FormList::optionSelected(int index) {
889 if (index > -1 && index < _model->rows()) {
890 _model->selected(index);
891 }
892}
893
894void FormList::selectIndex(int index) {
895 if (index > -1 && index < _model->rows()) {
896 MAExtent textSize = maGetTextSize(_model->getTextAt(0));
897 int rowHeight = EXTENT_Y(textSize) + 1;
898 int visibleRows = getListHeight() / rowHeight;
899 if (index < visibleRows) {
900 _activeIndex = index;
901 _topIndex = 0;
902 } else {
903 _topIndex = index - visibleRows + 1;
904 _activeIndex = index - _topIndex;
905 }
906 _model->selected(index);
907 }
908}
909
910bool FormList::updateUI(var_p_t form, var_p_t field) {
911 bool updated = FormInput::updateUI(form, field);
912 var_p_t var = map_get(field, FORM_INPUT_VALUE);
913 if (var != nullptr && (var->type == V_ARRAY || var->type == V_STR)) {
914 // update list control with new array or string variable
915 _model->clear();
916 _model->create(var);
917 updated = true;
918 }
919
920 // set the selectedIndex
921 var = map_get(field, FORM_INPUT_INDEX);
922 if (var != nullptr) {
923 selectIndex(v_getint(var));
924 updated = true;
925 }
926 return updated;
927}
928
929// transfer the widget state onto the associated variable
930void FormList::updateForm(var_p_t form) {
931 const char *selected = getText();
932
933 // set the form value
934 var_p_t value = map_get(form, FORM_VALUE);
935 if (value == nullptr) {
936 value = map_add_var(form, FORM_VALUE, 0);
937 }
938 if (selected) {
939 v_setstr(value, selected);
940 } else {
941 v_zerostr(value);
942 }
943
944 // set the selectedIndex
945 var_p_t field = getField(form);
946 if (field != nullptr) {
947 value = map_get(field, FORM_INPUT_INDEX);
948 if (value != nullptr) {
949 v_setint(value, _model->selected());
950 }
951 }
952}
953
954bool FormList::edit(int key, int screenWidth, int charWidth) {
955 if (key == SB_KEY_UP || key == SB_KEY_DOWN) {
956 MAExtent textSize = maGetTextSize(_model->getTextAt(0));
957 int rowHeight = EXTENT_Y(textSize) + 1;
958 int visibleRows = getListHeight() / rowHeight;
959 int activeIndex = _activeIndex;
960 int topIndex = _topIndex;
961
962 if (key == SB_KEY_UP) {
963 if (_activeIndex > 0) {
964 _activeIndex--;
965 } else if (_topIndex > 0) {
966 _topIndex--;
967 }
968 } else if (key == SB_KEY_DOWN &&
969 _activeIndex + _topIndex < _model->rows() - 1) {
970 if (_activeIndex == visibleRows - 1) {
971 _topIndex++;
972 } else {
973 _activeIndex++;
974 }
975 }
976 if (activeIndex != _activeIndex || topIndex != _topIndex) {
977 _model->selected(_topIndex + _activeIndex);
978 selected();
979 }
980 } else if (key == SB_KEY_ENTER) {
981 clicked(-1, -1, false);
982 } else {
983 // match by keystroke
984 int firstIndex = -1;
985 int lastIndex = -1;
986 for (int index = 0; index < _model->rows(); index++) {
987 const char *text = _model->getTextAt(index);
988 if (text && tolower(text[0]) == tolower(key)) {
989 if (firstIndex == -1) {
990 firstIndex = index;
991 }
992 if (index > _activeIndex + _topIndex) {
993 lastIndex = index;
994 break;
995 }
996 }
997 }
998 int index = lastIndex != -1 ? lastIndex : firstIndex;
999 if (index != -1) {
1000 selectIndex(index);
1001 selected();
1002 }
1003 }
1004 return true;
1005}
1006
1007//
1008// FormListBox
1009//
1010FormListBox::FormListBox(ListModel *model, const char *help, int x, int y, int w, int h) :
1011 FormList(model, x, y, w, h), _help(help) {
1012}
1013
1014void FormListBox::draw(int x, int y, int w, int h, int chw) {
1015 maSetColor(_bg);
1016 maFillRect(x, y, _width, _height);
1017 MAExtent textSize = maGetTextSize(_model->getTextAt(0));
1018 int rowHeight = EXTENT_Y(textSize) + 1;
1019 int textY = y;
1020 if (!_model->rows() && !_help.empty()) {
1021 setHelpTextColor();
1022 drawText(_help.c_str(), x, textY, w, chw);
1023 } else {
1024 for (int i = 0; i < _model->rows(); i++) {
1025 const char *str = _model->getTextAt(i + _topIndex);
1026 if (textY + rowHeight >= y + _height) {
1027 break;
1028 }
1029 if (i == _activeIndex) {
1030 setTextColor();
1031 maFillRect(x, textY, _width, rowHeight);
1032 maSetColor(_bg);
1033 drawText(str, x, textY, w, chw);
1034 } else {
1035 setTextColor();
1036 drawText(str, x, textY, w, chw);
1037 }
1038 textY += rowHeight;
1039 }
1040 }
1041}
1042
1043bool FormListBox::selected(MAPoint2d pt, int offsX, int offsY, bool &redraw) {
1044 bool result = FormInput::overlaps(pt, offsX, offsY);
1045 MAExtent textSize = maGetTextSize(_model->getTextAt(0));
1046 int rowHeight = EXTENT_Y(textSize) + 1;
1047 int visibleRows = _height / rowHeight;
1048 if (result) {
1049 int y = pt.y - (_y + offsY);
1050 _activeIndex = y / rowHeight;
1051 activeList = this;
1052 redraw = true;
1053 } else if (activeList == this &&
1054 pt.y < _y + offsY + _height && _topIndex > 0) {
1055 _topIndex--;
1056 redraw = true;
1057 } else if (activeList == this &&
1058 pt.y > _y + offsY + _height &&
1059 _topIndex + visibleRows < _model->rows()) {
1060 _topIndex++;
1061 redraw = true;
1062 } else {
1063 activeList = nullptr;
1064 }
1065 return result;
1066}
1067
1068//
1069// FormDropList
1070//
1071FormDropList::FormDropList(ListModel *model, int x, int y, int w, int h) :
1072 FormList(model, x, y, w, h),
1073 _listWidth(w),
1074 _listHeight(h),
1075 _visibleRows(0),
1076 _listActive(false) {
1077}
1078
1079void FormDropList::draw(int x, int y, int w, int h, int chw) {
1080 if (!hasFocus()) {
1081 _listActive = false;
1082 }
1083 bool pressed = _listActive ? false : _pressed;
1084 drawButton(getText(), x, y, _width - CHOICE_BN_W, _height, pressed);
1085 drawButton("", x + _width - CHOICE_BN_W, y, CHOICE_BN_W, _height, false);
1086 if (_listActive) {
1087 drawList(x, y, h);
1088 }
1089}
1090
1091void FormDropList::drawList(int dx, int dy, int sh) {
1092 int availHeight = sh - (dy + _y + _height + _height);
1093 int textHeight = 0;
1094 int textY = dy + _height;
1095
1096 // determine the available boundary
1097 _listHeight = 0;
1098 _visibleRows = 0;
1099 for (int i = _topIndex; i < _model->rows(); i++) {
1100 MAExtent textSize = maGetTextSize(_model->getTextAt(i));
1101 int textWidth = EXTENT_X(textSize);
1102 textHeight = EXTENT_Y(textSize) + 1;
1103 if (textWidth > _listWidth) {
1104 _listWidth = textWidth;
1105 }
1106 if (_listHeight + textHeight >= availHeight) {
1107 break;
1108 }
1109 _listHeight += textHeight;
1110 _visibleRows++;
1111 }
1112 maSetColor(_bg);
1113 maFillRect(dx, dy + _height, _listWidth, _listHeight);
1114 for (int i = 0; i < _visibleRows; i++) {
1115 const char *str = _model->getTextAt(i + _topIndex);
1116 if (i == _activeIndex) {
1117 setTextColor();
1118 maFillRect(dx, textY, _listWidth, textHeight);
1119 maSetColor(_bg);
1120 maDrawText(dx, textY, str, strlen(str));
1121 } else {
1122 setTextColor();
1123 maDrawText(dx, textY, str, strlen(str));
1124 }
1125 textY += textHeight;
1126 }
1127}
1128
1129bool FormDropList::selected(MAPoint2d pt, int offsX, int offsY, bool &redraw) {
1130 bool result;
1131 if (_listActive) {
1132 result = true;
1133 _activeIndex = -1;
1134 if (!(OUTSIDE_RECT(pt.x, pt.y, _x + offsX, _y + offsY + _height,
1135 _listWidth, _listHeight))) {
1136 int y = pt.y - (_y + offsY + _height);
1137 int rowHeight = _listHeight / _visibleRows;
1138 _activeIndex = y / rowHeight;
1139 activeList = this;
1140 redraw = true;
1141 } else if (activeList == this &&
1142 pt.y < _y + offsY + _height && _topIndex > 0) {
1143 _topIndex--;
1144 redraw = true;
1145 } else if (activeList == this &&
1146 pt.y > _y + offsY + _height + _listHeight &&
1147 _topIndex + _visibleRows < _model->rows()) {
1148 _topIndex++;
1149 redraw = true;
1150 }
1151 } else {
1152 result = FormInput::overlaps(pt, offsX, offsY);
1153 }
1154 return result;
1155}
1156
1157void FormDropList::updateForm(var_p_t form) {
1158 if (!_listActive) {
1159 _activeIndex = -1;
1160 } else if (_activeIndex != -1) {
1161 optionSelected(_activeIndex + _topIndex);
1162 }
1163 FormList::updateForm(form);
1164}
1165
1166//
1167// FormImage
1168//
1169FormImage::FormImage(ImageDisplay *image, int x, int y) :
1170 FormInput(x, y, image->_width + 2, image->_height + 2),
1171 _image(image) {
1172 _image->_x = x;
1173 _image->_y = y;
1174}
1175
1176FormImage::~FormImage() {
1177 delete _image;
1178}
1179
1180void FormImage::draw(int x, int y, int w, int h, int chw) {
1181 int dx = _pressed ? x + 1 : x;
1182 int dy = _pressed ? y + 1 : y;
1183 maSetColor(_bg);
1184 maFillRect(x, y, _width, _height);
1185 _image->draw(dx + 1, dy + 1, w, h, chw);
1186}
1187
1188//
1189// MenuButton
1190//
1191MenuButton::MenuButton(int index, int &selectedIndex,
1192 const char *caption, int x, int y, int w, int h) :
1193 FormButton(caption, x, y, w, h),
1194 _selectedIndex(selectedIndex),
1195 _index(index) {
1196}
1197
1198void MenuButton::clicked(int x, int y, bool pressed) {
1199 if (!pressed) {
1200 _selectedIndex = _index;
1201 }
1202}
1203
1204void MenuButton::draw(int x, int y, int w, int h, int chw) {
1205 maSetColor(_pressed ? _bg : _fg);
1206 maFillRect(x, y, _width, _height);
1207 int len = _label.length();
1208 if (len > 0) {
1209 if (len * chw >= _width) {
1210 len = _width / chw;
1211 }
1212 int charHeight = g_system->getOutput()->getCharHeight();
1213 int textY = y + (_height - charHeight) / 2;
1214
1215 maSetColor(_pressed ? _fg : _bg);
1216 maDrawText(x + 4, textY, _label.c_str(), len);
1217 if (!_pressed && _index > 0 && _index % 2 == 0) {
1218 maSetColor(0x3b3a36);
1219 maLine(x + 2, y, x + LINE_W, y);
1220 maSetColor(0x46453f);
1221 maLine(x + 2, y - 1, x + LINE_W, y - 1);
1222 }
1223 }
1224}
1225