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 | |
17 | extern System *g_system; |
18 | extern FormInput *focusInput; |
19 | var_p_t form = NULL; |
20 | |
21 | enum Mode { |
22 | m_init, |
23 | m_active, |
24 | m_selected |
25 | } mode = m_init; |
26 | |
27 | const 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 | |
39 | void 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 | |
60 | void 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 | |
72 | bool 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 | |
87 | void 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 | |
99 | void 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 | |
109 | void 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 | |
119 | void 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 | |
132 | void 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 | |
141 | void 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 | |
185 | int 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 | |
197 | FormInput *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 |
265 | extern "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 | |