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/system.h"
15#include "ui/textedit.h"
16
17extern System *g_system;
18extern FormInput *focusInput;
19var_p_t form = NULL;
20
21enum Mode {
22 m_init,
23 m_active,
24 m_selected
25} mode = m_init;
26
27const char *FormInput::getValue() {
28 const char *result = NULL;
29 var_p_t field = getField(form);
30 if (field != NULL) {
31 var_p_t value = map_get(field, FORM_INPUT_VALUE);
32 if (value != NULL && value->type == V_STR) {
33 result = value->v.p.ptr;
34 }
35 }
36 return result;
37}
38
39void FormInput::clicked(int x, int y, bool pressed) {
40 setFocus(true);
41 if (!pressed && g_system->isRunning()) {
42 if (_onclick) {
43 bcip_t ip = prog_ip;
44 prog_ip = _onclick;
45 cmd_push_args(kwPROC, prog_ip, INVALID_ADDR);
46 bc_loop(2);
47 prog_ip = ip;
48 } else if (form != NULL) {
49 if (_exit) {
50 const char *value = getValue();
51 g_system->setLoadBreak(value != NULL ? value : getText());
52 }
53 else {
54 selected();
55 }
56 }
57 }
58}
59
60void FormInput::selected() {
61 if (form != NULL && g_system->isRunning()) {
62 setFocus(true);
63 updateForm(form);
64 if (focusInput != NULL) {
65 focusInput->updateField(form);
66 }
67 mode = m_selected;
68 g_system->getOutput()->setDirty();
69 }
70}
71
72bool FormLineInput::selected(MAPoint2d pt, int scrollX, int scrollY, bool &redraw) {
73 bool result = FormInput::overlaps(pt, scrollX, scrollY);
74 if (result) {
75 int x = pt.x - (_x + scrollX);
76 int charWidth = g_system->getOutput()->getCharWidth();
77 int selected = x / charWidth;
78 int len = strlen(_buffer);
79 if (selected <= len && selected > -1 && selected != _mark) {
80 _mark = selected;
81 redraw = true;
82 }
83 }
84 return result;
85}
86
87void FormDropList::clicked(int x, int y, bool pressed) {
88 if (form != NULL && !pressed && g_system->isRunning()) {
89 setFocus(true);
90 updateForm(form);
91 _listActive = !_listActive;
92 if (!_listActive) {
93 mode = m_selected;
94 }
95 g_system->getOutput()->setDirty();
96 }
97}
98
99void FormListBox::clicked(int x, int y, bool pressed) {
100 if (form != NULL && !pressed && g_system->isRunning()) {
101 setFocus(true);
102 if (_activeIndex != -1) {
103 optionSelected(_activeIndex + _topIndex);
104 }
105 }
106 FormInput::clicked(x, y, pressed);
107}
108
109void FormLink::clicked(int x, int y, bool pressed) {
110 setFocus(true);
111 if (!pressed && _external && form != NULL && g_system->isRunning()) {
112 const char *value = getValue();
113 g_system->browseFile(value != NULL ? value : _link.c_str());
114 } else {
115 FormInput::clicked(x, y, pressed);
116 }
117}
118
119void cmd_form_close(var_s *self, var_s *) {
120 g_system->getOutput()->removeInputs();
121 g_system->getOutput()->resetScroll();
122
123 var_t arg;
124 v_init(&arg);
125 eval(&arg);
126 if (arg.type == V_STR) {
127 g_system->setLoadBreak(arg.v.p.ptr);
128 }
129 v_free(&arg);
130}
131
132void cmd_form_refresh(var_s *self, var_s *) {
133 var_t arg;
134 v_init(&arg);
135 eval(&arg);
136 bool setVars = v_getint(&arg) != 0;
137 v_free(&arg);
138 g_system->getOutput()->updateInputs(self, setVars);
139}
140
141void cmd_form_do_events(var_s *self, var_s *) {
142 // apply any variable changes onto attached widgets
143 if (g_system->isRunning()) {
144 AnsiWidget *out = g_system->getOutput();
145
146 form = self;
147
148 // pump system messages until there is a widget callback
149 mode = m_active;
150
151 // clear the keyboard
152 dev_clrkb();
153
154 int charWidth = out->getCharWidth();
155 int sw = out->getScreenWidth();
156
157 // process events
158 while (g_system->isRunning() && mode == m_active) {
159 MAEvent event = g_system->processEvents(true);
160 if (event.type == EVENT_TYPE_KEY_PRESSED) {
161 if (event.key == SB_KEY_TAB) {
162 dev_clrkb();
163 focusInput = out->getNextField(focusInput);
164 out->setDirty();
165 } else if (focusInput != NULL &&
166 event.key != SB_KEY_MENU &&
167 focusInput->edit(event.key, sw, charWidth)) {
168 dev_clrkb();
169 out->setDirty();
170 } else if (event.key == SB_KEY_MK_PUSH ||
171 event.key == SB_KEY_MK_RELEASE ||
172 event.key == (int)SB_KEY_CTRL(SB_KEY_UP) ||
173 event.key == (int)SB_KEY_CTRL(SB_KEY_DOWN)) {
174 // no exit on mouse events
175 dev_clrkb();
176 } else {
177 break;
178 }
179 }
180 }
181 form = NULL;
182 }
183}
184
185int get_selected_index(var_p_t v_field) {
186 var_p_t value = map_get(v_field, FORM_INPUT_INDEX);
187 int result;
188 if (value == NULL) {
189 result = 0;
190 map_add_var(v_field, FORM_INPUT_INDEX, result);
191 } else {
192 result = v_getint(value);
193 }
194 return result;
195}
196
197FormInput *create_input(var_p_t v_field) {
198 int x = map_get_int(v_field, FORM_INPUT_X, -1);
199 int y = map_get_int(v_field, FORM_INPUT_Y, -1);
200 int w = map_get_int(v_field, FORM_INPUT_W, -1);
201 int h = map_get_int(v_field, FORM_INPUT_H, -1);
202
203 const char *label = map_get_str(v_field, FORM_INPUT_LABEL);
204 const char *type = map_get_str(v_field, FORM_INPUT_TYPE);
205 const char *help = map_get_str(v_field, FORM_INPUT_HELP);
206 var_p_t value = map_get(v_field, FORM_INPUT_VALUE);
207
208 if (label == NULL) {
209 label = "";
210 }
211
212 if (value == NULL) {
213 value = map_add_var(v_field, FORM_INPUT_VALUE, 0);
214 v_setstr(value, label);
215 }
216
217 FormInput *widget = NULL;
218 if (type == NULL || strcasecmp("button", type) == 0) {
219 widget = new FormButton(label, x, y, w, h);
220 } else if (strcasecmp("label", type) == 0) {
221 widget = new FormLabel(label, x, y, w, h);
222 } else if (strcasecmp("link", type) == 0) {
223 bool external = map_get_int(v_field, FORM_INPUT_IS_EXTERNAL, 0);
224 widget = new FormLink(label, external, x, y, w, h);
225 } else if (strcasecmp("listbox", type) == 0 ||
226 strcasecmp("list", type) == 0) {
227 ListModel *model = new ListModel(get_selected_index(v_field), value);
228 widget = new FormListBox(model, help, x, y, w, h);
229 } else if (strcasecmp("choice", type) == 0 ||
230 strcasecmp("dropdown", type) == 0) {
231 ListModel *model = new ListModel(get_selected_index(v_field), value);
232 widget = new FormDropList(model, x, y, w, h);
233 } else if (strcasecmp("text", type) == 0) {
234 int maxSize = map_get_int(v_field, FORM_INPUT_LENGTH, -1);
235 int charHeight = g_system->getOutput()->getCharHeight();
236 int charWidth = g_system->getOutput()->getCharWidth();
237 if (maxSize < 1 || maxSize > 1024) {
238 maxSize = 100;
239 }
240 const char *text = NULL;
241 if (value->type == V_STR) {
242 text = value->v.p.ptr;
243 }
244 if (h * 2 >= charHeight) {
245 widget = new TextEditInput(text, charWidth, charHeight, x, y, w, h);
246 } else {
247 widget = new FormLineInput(text, help, maxSize, false, x, y, w, h);
248 }
249 } else if (strcasecmp("image", type) == 0) {
250 const char *name = map_get_str(v_field, FORM_INPUT_NAME);
251 ImageDisplay *image = create_display_image(v_field, name);
252 if (image != NULL) {
253 widget = new FormImage(image, x, y);
254 }
255 } else if (strcasecmp("print", type) == 0) {
256 const char *text = map_get_str(v_field, FORM_INPUT_VALUE);
257 if (text) {
258 g_system->getOutput()->print(text);
259 }
260 }
261 return widget;
262}
263
264// creates a new form using the given map
265extern "C" void v_create_form(var_p_t var) {
266 bool hasInputs = false;
267 var_p_t arg;
268 AnsiWidget *out = g_system->getOutput();
269 if (code_isvar()) {
270 arg = code_getvarptr();
271 if (arg->type == V_MAP) {
272 var_p_t inputs = map_get(arg, FORM_INPUTS);
273 if (inputs != NULL && inputs->type == V_ARRAY) {
274 for (unsigned i = 0; i < v_asize(inputs); i++) {
275 var_p_t elem = v_elem(inputs, i);
276 if (elem->type == V_MAP) {
277 hasInputs = true;
278 }
279 }
280 }
281 }
282 }
283
284 if (hasInputs) {
285 set_input_defaults(out->getColor(), out->getBackgroundColor());
286 map_set(var, arg);
287 var_p_t v_focus = map_get(var, FORM_FOCUS);
288 unsigned i_focus = v_focus != NULL ? v_getint(v_focus) : -1;
289 var_p_t inputs = map_get(var, FORM_INPUTS);
290 for (unsigned i = 0; inputs != NULL && i < v_asize(inputs); i++) {
291 var_p_t elem = v_elem(inputs, i);
292 if (elem->type == V_MAP) {
293 FormInput *widget = create_input(elem);
294 if (widget != NULL) {
295 widget->construct(var, elem, i);
296 out->addInput(widget);
297 if (i_focus == i || v_asize(inputs) == 1) {
298 widget->setFocus(true);
299 }
300 }
301 }
302 }
303 out->setDirty();
304 v_zerostr(map_add_var(var, FORM_VALUE, 0));
305 v_create_func(var, "doEvents", cmd_form_do_events);
306 v_create_func(var, "close", cmd_form_close);
307 v_create_func(var, "refresh", cmd_form_refresh);
308 } else {
309 err_form_input();
310 }
311}
312
313