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
18extern MainWindow *wnd;
19extern System *g_system;
20static auto *onlineUrl = "http://smallbasic.github.io/samples/index.bas";
21
22#define MIN_WINDOW_SIZE 10
23#define OPTIONS_BOX_WIDTH_EXTRA 4
24#define WAIT_INTERVAL_MILLIS 5
25#define WAIT_INTERVAL (WAIT_INTERVAL_MILLIS/1000)
26
27int event_x() {
28 return Fl::event_x();
29}
30
31int event_y() {
32 return Fl::event_y() - wnd->_out->y() - 2;
33}
34
35void 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//
44Runtime::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
52Runtime::~Runtime() {
53 delete _output;
54 audio_close();
55}
56
57void Runtime::alert(const char *title, const char *message) {
58 fl_alert("%s", message);
59}
60
61int 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
71void Runtime::browseFile(const char *url) {
72 ::browseFile(url);
73}
74
75void Runtime::enableCursor(bool enabled) {
76 fl_cursor(enabled ? FL_CURSOR_DEFAULT : FL_CURSOR_NONE);
77}
78
79char *Runtime::getClipboardText() {
80 return nullptr;
81}
82
83int 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
111void Runtime::optionsBox(StringList *items) {
112 Fl_Menu_Item menu[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 menuX = event_x();
145 int menuY = 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 popup(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
171MAEvent 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
199void Runtime::resize(int w, int h) {
200 if (w != _output->getWidth() || h != _output->getHeight()) {
201 _output->resize(w, h);
202 }
203}
204
205void 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
229void Runtime::setClipboardText(const char *text) {
230 Fl::copy(text, strlen(text), 1);
231}
232
233void Runtime::setFontSize(int size) {
234 _output->setFontSize(size);
235}
236
237void Runtime::setWindowSize(int width, int height) {
238 wnd->size(width, height);
239}
240
241void Runtime::setWindowTitle(const char *title) {
242 wnd->label(title);
243}
244
245void 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//
262void System::editSource(strlib::String loadPath, bool restoreOnExit) {
263 // empty
264}
265
266bool 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
276void System::completeKeyword(int index) {
277 // empty
278}
279
280//
281// ma event handling
282//
283int 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
304void 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//
326int 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
357void 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
364int osd_devrestore() {
365 g_system->setRunning(false);
366 return 1;
367}
368
369//
370// utils
371//
372void 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