1 | // This file is part of SmallBASIC |
2 | // |
3 | // Copyright(C) 2001-2019 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 | #include "include/osd.h" |
11 | #include "lib/maapi.h" |
12 | #include "platform/fltk/MainWindow.h" |
13 | #include "platform/fltk/runtime.h" |
14 | #include "ui/audio.h" |
15 | #include <FL/fl_ask.H> |
16 | #include <FL/Fl_Menu_Button.H> |
17 | |
18 | extern MainWindow *wnd; |
19 | extern System *g_system; |
20 | static auto *onlineUrl = "http://smallbasic.github.io/samples/index.bas" ; |
21 | |
22 | #define MIN_WINDOW_SIZE 10 |
23 | #define 4 |
24 | #define WAIT_INTERVAL_MILLIS 5 |
25 | #define WAIT_INTERVAL (WAIT_INTERVAL_MILLIS/1000) |
26 | |
27 | int event_x() { |
28 | return Fl::event_x(); |
29 | } |
30 | |
31 | int event_y() { |
32 | return Fl::event_y() - wnd->_out->y() - 2; |
33 | } |
34 | |
35 | void setMotionEvent(MAEvent &event, int type) { |
36 | event.type = type; |
37 | event.point.x = event_x(); |
38 | event.point.y = event_y(); |
39 | } |
40 | |
41 | // |
42 | // Runtime implementation |
43 | // |
44 | Runtime::Runtime(int w, int h, int defsize) : System() { |
45 | _output = new AnsiWidget(w, h); |
46 | _output->construct(); |
47 | _output->setTextColor(DEFAULT_FOREGROUND, DEFAULT_BACKGROUND); |
48 | _output->setFontSize(defsize); |
49 | audio_open(); |
50 | } |
51 | |
52 | Runtime::~Runtime() { |
53 | delete _output; |
54 | audio_close(); |
55 | } |
56 | |
57 | void Runtime::alert(const char *title, const char *message) { |
58 | fl_alert("%s" , message); |
59 | } |
60 | |
61 | int Runtime::ask(const char *title, const char *prompt, bool cancel) { |
62 | int result; |
63 | if (cancel) { |
64 | result = fl_choice(prompt, "Yes" , "No" , "Cancel" , nullptr); |
65 | } else { |
66 | result = fl_choice(prompt, "Yes" , "No" , 0, nullptr); |
67 | } |
68 | return result; |
69 | } |
70 | |
71 | void Runtime::browseFile(const char *url) { |
72 | ::browseFile(url); |
73 | } |
74 | |
75 | void Runtime::enableCursor(bool enabled) { |
76 | fl_cursor(enabled ? FL_CURSOR_DEFAULT : FL_CURSOR_NONE); |
77 | } |
78 | |
79 | char *Runtime::getClipboardText() { |
80 | return nullptr; |
81 | } |
82 | |
83 | int Runtime::handle(int e) { |
84 | MAEvent event; |
85 | |
86 | switch (e) { |
87 | case FL_PUSH: |
88 | setMotionEvent(event, EVENT_TYPE_POINTER_PRESSED); |
89 | if (event.point.y >= 0) { |
90 | handleEvent(event); |
91 | } |
92 | break; |
93 | |
94 | case FL_DRAG: |
95 | setMotionEvent(event, EVENT_TYPE_POINTER_DRAGGED); |
96 | handleEvent(event); |
97 | break; |
98 | |
99 | case FL_RELEASE: |
100 | setMotionEvent(event, EVENT_TYPE_POINTER_RELEASED); |
101 | handleEvent(event); |
102 | break; |
103 | |
104 | default: |
105 | break; |
106 | } |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | void Runtime::optionsBox(StringList *items) { |
112 | Fl_Menu_Item [items->size() + 1]; |
113 | int index = 0; |
114 | int width = 0; |
115 | int height = 0; |
116 | int charWidth = _output->getCharWidth(); |
117 | |
118 | List_each(String *, it, *items) { |
119 | int w, h; |
120 | char *str = (char *)(* it)->c_str(); |
121 | menu[index].text = str; |
122 | menu[index].shortcut_ = 0; |
123 | menu[index].callback_ = 0; |
124 | menu[index].user_data_ = (void *)(intptr_t)index; |
125 | menu[index].flags = 0; |
126 | menu[index].labeltype_ = 0; |
127 | menu[index].labelfont_ = FL_HELVETICA; |
128 | menu[index].labelsize_ = FL_NORMAL_SIZE; |
129 | menu[index].labelcolor_ = FL_FOREGROUND_COLOR; |
130 | menu[index].measure(&h, nullptr); |
131 | |
132 | height += h + 1; |
133 | w = (strlen(str) * charWidth); |
134 | if (w > width) { |
135 | width = w; |
136 | } |
137 | index++; |
138 | } |
139 | |
140 | menu[index].flags = 0; |
141 | menu[index].text = nullptr; |
142 | width += (charWidth * OPTIONS_BOX_WIDTH_EXTRA); |
143 | |
144 | int = event_x(); |
145 | int = event_y(); |
146 | int maxWidth = wnd->_out->w() - wnd->_out->x(); |
147 | int maxHeight = wnd->h() - height - MENU_HEIGHT; |
148 | |
149 | if (menuX + width >= maxWidth) { |
150 | menuX = maxWidth - width; |
151 | } |
152 | |
153 | if (menuY + height >= maxHeight) { |
154 | menuY = maxHeight - height; |
155 | } else { |
156 | menuY -= wnd->y(); |
157 | } |
158 | |
159 | Fl_Menu_Button (menuX, menuY, width, height); |
160 | popup.menu(menu); |
161 | |
162 | const Fl_Menu_Item *result = popup.popup(); |
163 | if (result && result->text) { |
164 | MAEvent event; |
165 | event.type = EVENT_TYPE_OPTIONS_BOX_BUTTON_CLICKED; |
166 | event.optionsBoxButtonIndex = (intptr_t)(void *)result->user_data_; |
167 | handleEvent(event); |
168 | } |
169 | } |
170 | |
171 | MAEvent Runtime::processEvents(int waitFlag) { |
172 | switch (waitFlag) { |
173 | case 1: |
174 | // wait for an event |
175 | _output->flush(true); |
176 | Fl::wait(); |
177 | break; |
178 | case 2: |
179 | _output->flush(false); |
180 | Fl::wait(WAIT_INTERVAL); |
181 | break; |
182 | default: |
183 | Fl::check(); |
184 | break; |
185 | } |
186 | |
187 | MAEvent event; |
188 | event.type = 0; |
189 | event.key = 0; |
190 | event.nativeKey = 0; |
191 | |
192 | if (keymap_kbhit()) { |
193 | event.type = EVENT_TYPE_KEY_PRESSED; |
194 | event.key = keymap_kbpeek(); |
195 | } |
196 | return event; |
197 | } |
198 | |
199 | void Runtime::resize(int w, int h) { |
200 | if (w != _output->getWidth() || h != _output->getHeight()) { |
201 | _output->resize(w, h); |
202 | } |
203 | } |
204 | |
205 | void Runtime::runSamples() { |
206 | logEntered(); |
207 | _loadPath = onlineUrl; |
208 | |
209 | String activePath = _loadPath; |
210 | bool started = execute(onlineUrl); |
211 | |
212 | while (started && !wnd->isBreakExec()) { |
213 | if (isBreak() && (activePath.equals(onlineUrl) || activePath.equals(_loadPath))) { |
214 | // break from index page OR break with same _loadPath |
215 | break; |
216 | } |
217 | if (_loadPath.empty()) { |
218 | // return to index page |
219 | _loadPath = onlineUrl; |
220 | } |
221 | activePath = _loadPath; |
222 | started = execute(_loadPath); |
223 | } |
224 | |
225 | showCompletion(started); |
226 | _output->redraw(); |
227 | } |
228 | |
229 | void Runtime::setClipboardText(const char *text) { |
230 | Fl::copy(text, strlen(text), 1); |
231 | } |
232 | |
233 | void Runtime::setFontSize(int size) { |
234 | _output->setFontSize(size); |
235 | } |
236 | |
237 | void Runtime::setWindowSize(int width, int height) { |
238 | wnd->size(width, height); |
239 | } |
240 | |
241 | void Runtime::setWindowTitle(const char *title) { |
242 | wnd->label(title); |
243 | } |
244 | |
245 | void Runtime::showCursor(CursorType cursorType) { |
246 | switch (cursorType) { |
247 | case kHand: |
248 | fl_cursor(FL_CURSOR_HAND); |
249 | break; |
250 | case kArrow: |
251 | fl_cursor(FL_CURSOR_ARROW); |
252 | break; |
253 | case kIBeam: |
254 | fl_cursor(FL_CURSOR_INSERT); |
255 | break; |
256 | } |
257 | } |
258 | |
259 | // |
260 | // System platform methods |
261 | // |
262 | void System::editSource(strlib::String loadPath, bool restoreOnExit) { |
263 | // empty |
264 | } |
265 | |
266 | bool System::getPen3() { |
267 | Fl::check(); |
268 | bool result = Fl::event_state(FL_BUTTON1); |
269 | if (result) { |
270 | _touchCurX = event_x(); |
271 | _touchCurY = event_y(); |
272 | } |
273 | return result; |
274 | } |
275 | |
276 | void System::completeKeyword(int index) { |
277 | // empty |
278 | } |
279 | |
280 | // |
281 | // ma event handling |
282 | // |
283 | int maGetEvent(MAEvent *event) { |
284 | int result = 0; |
285 | if (Fl::check()) { |
286 | switch (Fl::event()) { |
287 | case FL_PUSH: |
288 | event->type = EVENT_TYPE_POINTER_PRESSED; |
289 | result = 1; |
290 | break; |
291 | case FL_DRAG: |
292 | event->type = EVENT_TYPE_POINTER_DRAGGED; |
293 | result = 1; |
294 | break; |
295 | case FL_RELEASE: |
296 | event->type = EVENT_TYPE_POINTER_RELEASED; |
297 | result = 1; |
298 | break; |
299 | } |
300 | } |
301 | return result; |
302 | } |
303 | |
304 | void maWait(int timeout) { |
305 | if (timeout == -1) { |
306 | Fl::wait(); |
307 | } else { |
308 | int slept = 0; |
309 | while (1) { |
310 | Fl::check(); |
311 | if (wnd->isBreakExec()) { |
312 | break; |
313 | } |
314 | usleep(WAIT_INTERVAL_MILLIS * 1000); |
315 | slept += WAIT_INTERVAL_MILLIS; |
316 | if (timeout > 0 && slept > timeout) { |
317 | break; |
318 | } |
319 | } |
320 | } |
321 | } |
322 | |
323 | // |
324 | // sbasic implementation |
325 | // |
326 | int osd_devinit() { |
327 | // allow the application to set the preferred width and height |
328 | if ((opt_pref_width || opt_pref_height) && wnd->isIdeHidden()) { |
329 | int delta_x = wnd->w() - g_system->getOutput()->getWidth(); |
330 | int delta_y = wnd->h() - g_system->getOutput()->getHeight(); |
331 | if (opt_pref_width < MIN_WINDOW_SIZE) { |
332 | opt_pref_width = MIN_WINDOW_SIZE; |
333 | } |
334 | if (opt_pref_height < MIN_WINDOW_SIZE) { |
335 | opt_pref_height = MIN_WINDOW_SIZE; |
336 | } |
337 | |
338 | int x = wnd->_outputGroup->x(); |
339 | int y = wnd->_outputGroup->y(); |
340 | int w = opt_pref_width + delta_x; |
341 | int h = opt_pref_height + delta_y; |
342 | |
343 | wnd->resizeDisplay(0, 0, w, h); |
344 | wnd->_outputGroup->resize(x, y, w, h); |
345 | wnd->_outputGroup->show(); |
346 | } |
347 | |
348 | // show the output-group in case it's the full-screen container. |
349 | if (wnd->isInteractive() && !wnd->logPrint()) { |
350 | wnd->_outputGroup->show(); |
351 | } |
352 | |
353 | g_system->setRunning(true); |
354 | return 1; |
355 | } |
356 | |
357 | void osd_write(const char *str) { |
358 | if (wnd->tty() && wnd->logPrint()) { |
359 | wnd->tty()->print(str); |
360 | } |
361 | g_system->getOutput()->print(str); |
362 | } |
363 | |
364 | int osd_devrestore() { |
365 | g_system->setRunning(false); |
366 | return 1; |
367 | } |
368 | |
369 | // |
370 | // utils |
371 | // |
372 | void appLog(const char *format, ...) { |
373 | va_list args; |
374 | va_start(args, format); |
375 | unsigned size = vsnprintf(nullptr, 0, format, args); |
376 | va_end(args); |
377 | |
378 | if (size) { |
379 | char *buf = (char *)malloc(size + 3); |
380 | buf[0] = '\0'; |
381 | va_start(args, format); |
382 | vsnprintf(buf, size + 1, format, args); |
383 | va_end(args); |
384 | buf[size] = '\0'; |
385 | |
386 | int i = size - 1; |
387 | while (i >= 0 && isspace(buf[i])) { |
388 | buf[i--] = '\0'; |
389 | } |
390 | strcat(buf, "\r\n" ); |
391 | if (wnd && wnd->tty()) { |
392 | wnd->tty()->print(buf); |
393 | } else { |
394 | fprintf(stderr, "%s" , buf); |
395 | } |
396 | free(buf); |
397 | } |
398 | } |
399 | |