| 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 | |