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
11#include <string.h>
12#include <unistd.h>
13#include <stdio.h>
14#include <errno.h>
15
16#include "include/osd.h"
17#include "common/sbapp.h"
18#include "common/device.h"
19#include "common/fs_socket_client.h"
20#include "common/keymap.h"
21#include "ui/system.h"
22#include "ui/inputs.h"
23#include "ui/theme.h"
24
25#define MENU_CONSOLE 0
26#define MENU_SOURCE 1
27#define MENU_BACK 2
28#define MENU_RESTART 3
29#define MENU_KEYPAD 4
30#define MENU_ZOOM_UP 5
31#define MENU_ZOOM_DN 6
32#define MENU_CUT 7
33#define MENU_COPY 8
34#define MENU_PASTE 9
35#define MENU_SELECT_ALL 10
36#define MENU_CTRL_MODE 11
37#define MENU_EDITMODE 12
38#define MENU_AUDIO 13
39#define MENU_SCREENSHOT 14
40#define MENU_UNDO 15
41#define MENU_REDO 16
42#define MENU_SAVE 17
43#define MENU_RUN 18
44#define MENU_DEBUG 19
45#define MENU_OUTPUT 20
46#define MENU_HELP 21
47#define MENU_SHORTCUT 22
48#define MENU_SHARE 23
49#define MENU_THEME 24
50#define MENU_SIZE 25
51#define MENU_COMPLETION_0 (MENU_SIZE + 1)
52#define MENU_COMPLETION_1 (MENU_SIZE + 2)
53#define MENU_COMPLETION_2 (MENU_SIZE + 3)
54#define MENU_COMPLETION_3 (MENU_SIZE + 4)
55#define MAX_COMPLETIONS 4
56#define MAX_CACHE 8
57#define CHANGE_WAIT_SLEEP 1000
58
59#define FONT_SCALE_INTERVAL 10
60#define FONT_MIN 20
61#define FONT_MAX 200
62
63#if defined(_SDL)
64#define MK_MENU(l, a) " " l " " a " "
65#else
66#define MK_MENU(l, a) " " l
67#endif
68
69#define MENU_STR_BACK MK_MENU("Back", "^b")
70#define MENU_STR_COPY MK_MENU("Copy", "^c")
71#define MENU_STR_CUT MK_MENU("Cut", "^x")
72#define MENU_STR_PASTE MK_MENU("Paste", "^v")
73#define MENU_STR_REDO MK_MENU("Redo", "^y")
74#define MENU_STR_RUN MK_MENU("Run", "^r")
75#define MENU_STR_SAVE MK_MENU("Save", "^s")
76#define MENU_STR_UNDO MK_MENU("Undo", "^z")
77#define MENU_STR_SCREEN MK_MENU("Screenshot", "^p")
78#define MENU_STR_SELECT MK_MENU("Select All", "^a")
79#define MENU_STR_OFF "OFF"
80#define MENU_STR_ON "ON"
81#define MENU_STR_AUDIO " Audio [%s] "
82#define MENU_STR_EDITOR " Editor [%s] "
83#define MENU_STR_THEME " Theme [%s] "
84#define MENU_STR_CONSOLE " Console "
85#define MENU_STR_CONTROL " Control Mode [%s] "
86#define MENU_STR_DEBUG " Debug "
87#define MENU_STR_FONT " Font Size %d%% "
88#define MENU_STR_HELP " Help "
89#define MENU_STR_KEYPAD " Show Keypad "
90#define MENU_STR_OUTPUT " Show Output "
91#define MENU_STR_RESTART " Restart "
92#define MENU_STR_SHARE " Share "
93#define MENU_STR_SHORT " Desktop Shortcut "
94#define MENU_STR_SOURCE " View Source "
95
96System *g_system;
97
98void Cache::add(const char *key, const char *value) {
99 if (_size == _count) {
100 // overwrite at next index position
101 _head[_index]->clear();
102 _head[_index]->append(key);
103 _head[_index + 1]->clear();
104 _head[_index + 1]->append(value);
105 _index = (_index + 2) % _size;
106 } else {
107 Properties::put(key, value);
108 }
109}
110
111System::System() :
112 _cache(MAX_CACHE),
113 _output(nullptr),
114 _editor(nullptr),
115 _systemMenu(nullptr),
116 _programSrc(nullptr),
117 _state(kInitState),
118 _touchX(-1),
119 _touchY(-1),
120 _touchCurX(-1),
121 _touchCurY(-1),
122 _initialFontSize(0),
123 _fontScale(100),
124 _userScreenId(-1),
125 _modifiedTime(0),
126 _mainBas(false),
127 _buttonPressed(false),
128 _srcRendered(false),
129 _menuActive(false) {
130 g_system = this;
131}
132
133System::~System() {
134 delete [] _systemMenu;
135 delete [] _programSrc;
136 delete _editor;
137
138 _systemMenu = nullptr;
139 _programSrc = nullptr;
140 _editor = nullptr;
141}
142
143bool System::execute(const char *bas) {
144 _stackTrace.removeAll();
145 _output->reset();
146 reset_image_cache();
147
148 // reset program controlled options
149 opt_antialias = 1;
150 opt_show_page = 0;
151
152 opt_pref_width = _output->getWidth();
153 opt_pref_height = _output->getHeight();
154 opt_base = 0;
155 opt_usepcre = 0;
156 opt_autolocal = 0;
157
158 _state = kRunState;
159 setWindowTitle(bas);
160 showCursor(kArrow);
161 saveWindowRect();
162
163 int result = ::sbasic_main(bas);
164 if (isRunning()) {
165 _state = kActiveState;
166 }
167
168 if (_editor == nullptr) {
169 opt_command[0] = '\0';
170 }
171
172 if (!_mainBas) {
173 onRunCompleted();
174 }
175
176 enableCursor(true);
177 opt_file_permitted = 1;
178 opt_loadmod = 0;
179 _output->selectScreen(USER_SCREEN1);
180 _output->resetFont();
181 _output->flush(true);
182 _userScreenId = -1;
183 return result != 0;
184}
185
186void System::formatOptions(StringList *items) {
187 int maxLength = 0;
188 bool hasControl = false;
189 List_each(String *, it, *items) {
190 String *str = * it;
191 if (str->indexOf('^', 0) != -1) {
192 hasControl = true;
193 }
194 int len = str->length();
195 if (len > maxLength) {
196 maxLength = len;
197 }
198 }
199 if (hasControl) {
200 List_each(String *, it, *items) {
201 String *str = * it;
202 if (str->indexOf('^', 0) != -1) {
203 String command = str->leftOf('^');
204 String control = str->rightOf('^');
205 int len = maxLength - str->length();
206 for (int i = 0; i < len; i++) {
207 command.append(' ');
208 }
209 command.append("C-");
210 command.append(control);
211 str->clear();
212 str->append(command);
213 }
214 }
215 }
216}
217
218bool System::fileExists(strlib::String &path) {
219 bool result = false;
220 if (path.indexOf("://", 1) != -1) {
221 result = true;
222 } else if (!path.empty()) {
223 struct stat st_file;
224 result = stat(path.c_str(), &st_file) == 0;
225 }
226 return result;
227}
228
229int System::getPen(int code) {
230 _output->flush(true);
231 int result = 0;
232 if (!isClosing()) {
233 switch (code) {
234 case 0:
235 // UNTIL PEN(0) - wait until click or move
236 processEvents(1);
237 // fallthru
238
239 case 3: // returns true if the pen is down (and save curpos)
240 result = getPen3();
241 break;
242
243 case 1: // last pen-down x
244 result = _touchX;
245 break;
246
247 case 2: // last pen-down y
248 result = _touchY;
249 break;
250
251 case 4: // cur pen-down x
252 case 10:
253 result = _touchCurX;
254 break;
255
256 case 5: // cur pen-down y
257 case 11:
258 result = _touchCurY;
259 break;
260
261 case 12: // true if left button pressed
262 return _buttonPressed;
263 }
264 }
265 return result;
266}
267
268char *System::getText(char *dest, int maxSize) {
269 int x = _output->getX();
270 int y = _output->getY();
271 int w = EXTENT_X(maGetTextSize("YNM"));
272 int h = _output->textHeight();
273 int charWidth = _output->getCharWidth();
274
275 FormInput *widget = new FormLineInput(nullptr, nullptr, maxSize, true, x, y, w, h);
276 widget->setFocus(true);
277
278 int bg = _output->getBackgroundColor();
279 int fg = _output->getColor();
280 if (bg != DEFAULT_BACKGROUND || fg != DEFAULT_FOREGROUND) {
281 widget->setColor(bg, fg);
282 } else {
283 widget->setColor(FOCUS_COLOR, DEFAULT_FOREGROUND);
284 }
285 _output->addInput(widget);
286 _output->redraw();
287 _state = kModalState;
288 maShowVirtualKeyboard();
289 showCursor(kIBeam);
290
291 while (isModal()) {
292 MAEvent event = getNextEvent();
293 if (event.type == EVENT_TYPE_KEY_PRESSED) {
294 dev_clrkb();
295 if (isModal()) {
296 int sw = _output->getScreenWidth();
297 switch (event.key) {
298 case SB_KEY_ENTER:
299 _state = kRunState;
300 break;
301 case SB_KEY_MENU:
302 break;
303 default:
304 if (widget->edit(event.key, sw, charWidth)) {
305 _output->redraw();
306 }
307 }
308 }
309 }
310 }
311
312 const char *result = widget->getText();
313 if (result) {
314 strcpy(dest, result);
315 } else {
316 dest[0] = '\0';
317 }
318
319 // paint the widget result onto the backing screen
320 if (dest[0]) {
321 _output->setXY(x, y);
322 _output->print(dest);
323 }
324
325 maHideVirtualKeyboard();
326 showCursor(kArrow);
327 _output->removeInput(widget);
328 delete widget;
329 return dest;
330}
331
332uint32_t System::getModifiedTime() {
333 uint32_t result = 0;
334 if (!_activeFile.empty()) {
335 struct stat st_file;
336 if (!stat(_activeFile.c_str(), &st_file)) {
337 result = st_file.st_mtime;
338 }
339 }
340 return result;
341}
342
343void System::handleMenu(MAEvent &event) {
344 int menuId = event.optionsBoxButtonIndex;
345 int fontSize = _output->getFontSize();
346 int menuItem = _systemMenu[menuId];
347 delete [] _systemMenu;
348 _systemMenu = nullptr;
349
350 switch (menuItem) {
351 case MENU_SOURCE:
352 showSystemScreen(true);
353 break;
354 case MENU_CONSOLE:
355 showSystemScreen(false);
356 break;
357 case MENU_KEYPAD:
358 maShowVirtualKeyboard();
359 break;
360 case MENU_RESTART:
361 setRestart();
362 break;
363 case MENU_BACK:
364 setBack();
365 break;
366 case MENU_ZOOM_UP:
367 if (_fontScale > FONT_MIN) {
368 _fontScale -= FONT_SCALE_INTERVAL;
369 fontSize = (_initialFontSize * _fontScale / 100);
370 }
371 break;
372 case MENU_ZOOM_DN:
373 if (_fontScale < FONT_MAX) {
374 _fontScale += FONT_SCALE_INTERVAL;
375 fontSize = (_initialFontSize * _fontScale / 100);
376 }
377 break;
378 case MENU_COPY:
379 case MENU_CUT:
380 if (get_focus_edit() != nullptr) {
381 char *text = get_focus_edit()->copy(menuItem == MENU_CUT);
382 if (text) {
383 setClipboardText(text);
384 free(text);
385 _output->redraw();
386 }
387 }
388 break;
389 case MENU_PASTE:
390 if (get_focus_edit() != nullptr) {
391 char *text = getClipboardText();
392 get_focus_edit()->paste(text);
393 _output->redraw();
394 free(text);
395 }
396 break;
397 case MENU_SELECT_ALL:
398 if (get_focus_edit() != nullptr) {
399 get_focus_edit()->selectAll();
400 _output->redraw();
401 }
402 break;
403 case MENU_CTRL_MODE:
404 if (get_focus_edit() != nullptr) {
405 bool controlMode = get_focus_edit()->getControlMode();
406 get_focus_edit()->setControlMode(!controlMode);
407 }
408 break;
409 case MENU_EDITMODE:
410 opt_ide = (opt_ide == IDE_NONE ? IDE_INTERNAL : IDE_NONE);
411 break;
412 case MENU_THEME:
413 g_themeId = (g_themeId + 1) % NUM_THEMES;
414 setRestart();
415 break;
416 case MENU_AUDIO:
417 opt_mute_audio = !opt_mute_audio;
418 break;
419 case MENU_SCREENSHOT:
420 ::screen_dump();
421 break;
422 case MENU_UNDO:
423 event.type = EVENT_TYPE_KEY_PRESSED;
424 event.key = SB_KEY_CTRL('z');
425 break;
426 case MENU_REDO:
427 event.type = EVENT_TYPE_KEY_PRESSED;
428 event.key = SB_KEY_CTRL('y');
429 break;
430 case MENU_SAVE:
431 event.type = EVENT_TYPE_KEY_PRESSED;
432 event.key = SB_KEY_CTRL('s');
433 break;
434 case MENU_RUN:
435 event.type = EVENT_TYPE_KEY_PRESSED;
436 event.key = SB_KEY_F(9);
437 break;
438 case MENU_DEBUG:
439 event.type = EVENT_TYPE_KEY_PRESSED;
440 event.key = SB_KEY_F(5);
441 break;
442 case MENU_OUTPUT:
443 event.type = EVENT_TYPE_KEY_PRESSED;
444 event.key = SB_KEY_CTRL('o');
445 break;
446 case MENU_HELP:
447 event.type = EVENT_TYPE_KEY_PRESSED;
448 event.key = SB_KEY_F(1);
449 break;
450 case MENU_SHORTCUT:
451 if (!_activeFile.empty()) {
452 addShortcut(_activeFile.c_str());
453 }
454 break;
455 case MENU_SHARE:
456 if (!_activeFile.empty()) {
457 share(_activeFile.c_str());
458 }
459 break;
460 case MENU_COMPLETION_0:
461 completeKeyword(0);
462 break;
463 case MENU_COMPLETION_1:
464 completeKeyword(1);
465 break;
466 case MENU_COMPLETION_2:
467 completeKeyword(2);
468 break;
469 case MENU_COMPLETION_3:
470 completeKeyword(3);
471 break;
472 }
473
474 if (fontSize != _output->getFontSize()) {
475 // restart the shell
476 _output->setFontSize(fontSize);
477 setRestart();
478 }
479
480 if (!isRunning()) {
481 _output->flush(true);
482 }
483}
484
485void System::handleEvent(MAEvent &event) {
486 switch (event.type) {
487 case EVENT_TYPE_OPTIONS_BOX_BUTTON_CLICKED:
488 if (_systemMenu != nullptr) {
489 handleMenu(event);
490 } else if (isRunning()) {
491 if (!form_ui::optionSelected(event.optionsBoxButtonIndex)) {
492 dev_clrkb();
493 dev_pushkey(event.optionsBoxButtonIndex);
494 }
495 }
496 break;
497 case EVENT_TYPE_SCREEN_CHANGED:
498 resize();
499 break;
500 case EVENT_TYPE_POINTER_PRESSED:
501 _touchX = _touchCurX = event.point.x;
502 _touchY = _touchCurY = event.point.y;
503 if (_output->overMenu(_touchX, _touchY)) {
504 showMenu();
505 } else {
506 dev_pushkey(SB_KEY_MK_PUSH);
507 _buttonPressed = _output->pointerTouchEvent(event);
508 showCursor(get_focus_edit() != nullptr ? kIBeam : kHand);
509 }
510 break;
511 case EVENT_TYPE_POINTER_DRAGGED:
512 _touchCurX = event.point.x;
513 _touchCurY = event.point.y;
514 _output->pointerMoveEvent(event);
515 if (_output->hasHover() ||
516 _output->overMenu(_touchCurX, _touchCurY) ||
517 (_touchX != -1 && _touchY != -1)) {
518 showCursor(kHand);
519 } else if (_output->hasMenu()) {
520 showCursor(kArrow);
521 } else if (get_focus_edit()) {
522 showCursor(kIBeam);
523 } else {
524 showCursor(kArrow);
525 }
526 break;
527 case EVENT_TYPE_POINTER_RELEASED:
528 _buttonPressed = false;
529 _touchX = _touchY = _touchCurX = _touchCurY = -1;
530 _output->pointerReleaseEvent(event);
531 showCursor(get_focus_edit() != nullptr ? kIBeam : kArrow);
532 break;
533 default:
534 // no event
535 _output->flush(false);
536 break;
537 }
538}
539
540char *System::loadResource(const char *fileName) {
541 char *buffer = nullptr;
542 if (strstr(fileName, "://") != nullptr) {
543 String *cached = _cache.get(fileName);
544 if (cached != nullptr) {
545 int len = cached->length();
546 buffer = (char *)malloc(len + 1);
547 memcpy(buffer, cached->c_str(), len);
548 buffer[len] = '\0';
549 } else {
550 int handle = 1;
551 var_t *var_p = v_new();
552 dev_file_t *f = dev_getfileptr(handle);
553 _output->setStatus("Loading...");
554 _output->redraw();
555 if (dev_fopen(handle, fileName, 0)) {
556 if (http_read(f, var_p) == 0) {
557 systemPrint("\nfailed to read %s\n", fileName);
558 } else {
559 int len = var_p->v.p.length;
560 buffer = (char *)malloc(len + 1);
561 memcpy(buffer, var_p->v.p.ptr, len);
562 buffer[len] = '\0';
563 _cache.add(fileName, buffer);
564 }
565 } else {
566 systemPrint("\nfailed to open %s\n", fileName);
567 }
568 _output->setStatus(nullptr);
569 dev_fclose(handle);
570 v_free(var_p);
571 v_detach(var_p);
572 opt_file_permitted = 0;
573 }
574 }
575 if (buffer == nullptr) {
576 // remove failed item from history
577 strlib::String *old = _history.peek();
578 if (old && old->equals(fileName)) {
579 delete _history.pop();
580 }
581 }
582 return buffer;
583}
584
585bool System::loadSource(const char *fileName) {
586 // loads _programSrc
587 char *source = readSource(fileName);
588 if (source != nullptr) {
589 free(source);
590 return true;
591 }
592 return false;
593}
594
595void System::logStack(const char *keyword, int type, int line) {
596#if defined(_SDL)
597 if (_editor != nullptr) {
598 if (type == kwPROC || type == kwFUNC) {
599 _stackTrace.add(new StackTraceNode(keyword, type, line));
600 }
601 }
602#endif
603}
604
605char *System::readSource(const char *fileName) {
606 _activeFile.clear();
607 char *buffer;
608 if (!_mainBas && _editor != nullptr && _loadPath.equals(fileName)) {
609 buffer = _editor->getTextSelection(true);
610 } else {
611 buffer = loadResource(fileName);
612 if (!buffer) {
613 int h = open(fileName, O_BINARY | O_RDONLY);
614 if (h != -1) {
615 struct stat st;
616 if (fstat(h, &st) == 0) {
617 int len = st.st_size;
618 buffer = (char *)malloc(len + 1);
619 len = read(h, buffer, len);
620 buffer[len] = '\0';
621 _modifiedTime = st.st_mtime;
622 char fullPath[PATH_MAX + 1];
623 char *path = realpath(fileName, fullPath);
624 if (path != nullptr) {
625 // set full path for getModifiedTime()
626 _activeFile = fullPath;
627 } else {
628 _activeFile = fileName;
629 }
630 }
631 close(h);
632 }
633 }
634 }
635 if (buffer != nullptr) {
636 delete [] _programSrc;
637 int len = strlen(buffer) + 1;
638 _programSrc = new char[len];
639 memcpy(_programSrc, buffer, len);
640 _programSrc[len - 1] = '\0';
641 _srcRendered = false;
642 systemPrint("Opened: %s %d bytes\n", fileName, len);
643 }
644 return buffer;
645}
646
647void System::resize() {
648 MAExtent screenSize = maGetScrSize();
649 logEntered();
650 _output->resize(EXTENT_X(screenSize), EXTENT_Y(screenSize));
651 if (isRunning()) {
652 setDimensions();
653 dev_pushkey(SB_PKEY_SIZE_CHG);
654 }
655}
656
657void System::runEdit(const char *startupBas) {
658 logEntered();
659 _mainBas = false;
660 _loadPath = startupBas;
661
662 while (true) {
663 if (loadSource(_loadPath)) {
664 setupPath(_loadPath);
665 editSource(_loadPath, false);
666 if (isBack() || isClosing()) {
667 break;
668 } else {
669 do {
670 execute(_loadPath);
671 } while (isRestart());
672 }
673 } else {
674 FILE *fp = fopen(_loadPath, "w");
675 if (fp) {
676 fprintf(fp, "rem Welcome to SmallBASIC\n");
677 fclose(fp);
678 } else {
679 alert("Failed to load file", strerror(errno));
680 break;
681 }
682 }
683 }
684}
685
686void System::runLive(const char *startupBas) {
687 logEntered();
688 _mainBas = false;
689
690 while (!isBack() && !isClosing()) {
691 bool success = execute(startupBas);
692 if (isClosing()) {
693 break;
694 } else if (!success) {
695 showSystemScreen(true);
696 _output->selectBackScreen(CONSOLE_SCREEN);
697 _output->flush(true);
698 waitForChange(true);
699 } else {
700 _state = kActiveState;
701 waitForChange(false);
702 }
703 }
704}
705
706void System::runMain(const char *mainBasPath) {
707 logEntered();
708
709 // activePath provides the program name after termination
710 String activePath = mainBasPath;
711 _loadPath = mainBasPath;
712 _mainBas = true;
713 strcpy(opt_command, "welcome");
714
715 bool started = execute(_loadPath);
716 if (!started) {
717 alert("Error", gsb_last_errmsg);
718 _state = kClosingState;
719 }
720
721 while (!isClosing() && started) {
722 if (isRestart()) {
723 _loadPath = activePath;
724 _state = kActiveState;
725 } else {
726 if (fileExists(_loadPath)) {
727 _mainBas = false;
728 activePath = _loadPath;
729 if (!isEditReady()) {
730 setupPath(_loadPath);
731 }
732 } else {
733 _mainBas = true;
734 _loadPath = mainBasPath;
735 activePath = mainBasPath;
736 }
737 }
738
739 if (!_mainBas && isEditReady() && loadSource(_loadPath)) {
740 editSource(_loadPath, true);
741 if (isBack()) {
742 _loadPath.clear();
743 _state = kActiveState;
744 continue;
745 } else if (isClosing()) {
746 break;
747 }
748 }
749
750 bool success = execute(_loadPath);
751 bool networkFile = isNetworkLoad();
752 if (!isBack() && !isClosing() &&
753 (success || networkFile || !isEditEnabled())) {
754 // when editing, only pause here when successful, otherwise the editor shows
755 // the error. load the next network file without displaying the previous result
756 if (!_mainBas && !networkFile) {
757 // display an indication the program has completed
758 showCompletion(success);
759 }
760 if (!success) {
761 if (_mainBas) {
762 // unexpected error in main.bas
763 alert("", gsb_last_errmsg);
764 _state = kClosingState;
765 } else {
766 // don't reload
767 _loadPath.clear();
768 _state = kActiveState;
769 }
770 }
771 if (!_mainBas && !networkFile) {
772 waitForBack();
773 }
774 }
775 }
776}
777
778void System::runOnce(const char *startupBas, bool runWait) {
779 // startupBas must not be _loadPath.c_str()
780 logEntered();
781 _mainBas = false;
782
783 bool restart = true;
784 while (restart) {
785 bool success = execute(startupBas);
786 if (_state == kActiveState) {
787 showCompletion(success);
788 }
789 if (runWait) {
790 waitForBack();
791 }
792 restart = isRestart();
793 }
794}
795
796void System::saveFile(TextEditInput *edit, strlib::String &path) {
797 if (!edit->save(path)) {
798 systemPrint("\nfailed to save: %s. error: %s\n", path.c_str(), strerror(errno));
799 alert(strerror(errno), "Failed to save file");
800 } else {
801 _modifiedTime = getModifiedTime();
802 }
803}
804
805void System::setBack() {
806 if (_userScreenId != -1) {
807 // return (back) from user screen, (view source)
808 _output->selectBackScreen(_userScreenId);
809 _output->selectFrontScreen(_userScreenId);
810 _userScreenId = -1;
811 } else {
812 // quit app when shell is active
813 setExit(_mainBas);
814
815 // follow history when available and not exiting
816 if (!_mainBas) {
817 // remove the current item
818 strlib::String *old = _history.pop();
819 if (old) {
820 delete old;
821 }
822 if (_history.peek() != nullptr) {
823 _loadPath.clear();
824 _loadPath.append(_history.peek());
825 }
826 }
827 }
828}
829
830void System::setLoadBreak(const char *path) {
831 _loadPath = path;
832 _history.push(new strlib::String(path));
833 _state = kBreakState;
834 brun_break();
835}
836
837void System::setLoadPath(const char *path) {
838 _loadPath = path;
839}
840
841bool System::setParentPath() {
842 bool result = true;
843 char path[FILENAME_MAX + 1];
844 getcwd(path, FILENAME_MAX);
845 if (!path[0] || strcmp(path, "/") == 0) {
846 result = false;
847 } else {
848 int len = strlen(path);
849 if (path[len - 1] == '/') {
850 // eg /sdcard/bas/
851 path[len - 1] = '\0';
852 }
853 const char *slash = strrchr(path, '/');
854 len = slash - path;
855 if (!len) {
856 strcpy(path, "/");
857 } else {
858 path[len] = 0;
859 }
860 chdir(path);
861 }
862 return result;
863}
864
865void System::setupPath(String &loadPath) {
866 const char *filename = loadPath;
867 if (strstr(filename, "://") == nullptr) {
868 const char *slash = strrchr(filename, '/');
869 if (!slash) {
870 slash = strrchr(filename, '\\');
871 }
872 if (slash) {
873 int len = slash - filename;
874 if (len > 0) {
875 // change to the loadPath directory
876 char path[FILENAME_MAX + 1];
877 strncpy(path, filename, len);
878 path[len] = 0;
879 chdir(path);
880 struct stat st_file;
881 if (stat(loadPath.c_str(), &st_file) < 0) {
882 // reset relative path back to full path
883 getcwd(path, FILENAME_MAX);
884 strlcat(path, filename + len, sizeof(path));
885 loadPath = path;
886 }
887 }
888 }
889 }
890}
891
892void System::setDimensions() {
893 dev_resize(_output->getWidth(), _output->getHeight());
894}
895
896void System::setRunning(bool running) {
897 if (running) {
898 dev_fgcolor = -DEFAULT_FOREGROUND;
899 dev_bgcolor = -DEFAULT_BACKGROUND;
900 setDimensions();
901 dev_clrkb();
902 _output->setAutoflush(!opt_show_page);
903 if (_mainBas || isNetworkLoad() || !isEditEnabled()) {
904 _loadPath.clear();
905 }
906 _userScreenId = -1;
907 } else {
908 osd_clear_sound_queue();
909 if (!isClosing() && !isRestart() && !isBack()) {
910 _state = kActiveState;
911 _output->setAutoflush(true);
912 }
913 }
914}
915
916void System::showCompletion(bool success) {
917 if (success) {
918 _output->setStatus("Done - press back [<-]");
919 } else {
920 printErrorLine();
921 _output->setStatus("Error - see console");
922 showSystemScreen(true);
923 }
924 _output->flush(true);
925}
926
927void System::showMenu() {
928 logEntered();
929
930 if (!_menuActive) {
931 _menuActive = true;
932 char buffer[64];
933 delete [] _systemMenu;
934 auto *items = new StringList();
935 int completions = 0;
936
937 if (get_focus_edit() && isEditing()) {
938 completions = get_focus_edit()->getCompletions(items, MAX_COMPLETIONS);
939 }
940
941 _systemMenu = new int[MENU_SIZE + completions];
942
943 int index = 0;
944 if (get_focus_edit() != nullptr) {
945 if (isEditing()) {
946 items->add(new String(MENU_STR_UNDO));
947 items->add(new String(MENU_STR_REDO));
948 items->add(new String(MENU_STR_CUT));
949 items->add(new String(MENU_STR_COPY));
950 items->add(new String(MENU_STR_PASTE));
951 items->add(new String(MENU_STR_SELECT));
952 items->add(new String(MENU_STR_SAVE));
953 items->add(new String(MENU_STR_RUN));
954#if defined(_SDL)
955 items->add(new String(MENU_STR_DEBUG));
956 items->add(new String(MENU_STR_OUTPUT));
957#endif
958 items->add(new String(MENU_STR_HELP));
959 for (int i = 0; i < completions; i++) {
960 _systemMenu[index++] = MENU_COMPLETION_0 + i;
961 }
962 _systemMenu[index++] = MENU_UNDO;
963 _systemMenu[index++] = MENU_REDO;
964 _systemMenu[index++] = MENU_CUT;
965 _systemMenu[index++] = MENU_COPY;
966 _systemMenu[index++] = MENU_PASTE;
967 _systemMenu[index++] = MENU_SELECT_ALL;
968 _systemMenu[index++] = MENU_SAVE;
969 _systemMenu[index++] = MENU_RUN;
970#if defined(_SDL)
971 _systemMenu[index++] = MENU_DEBUG;
972 _systemMenu[index++] = MENU_OUTPUT;
973#endif
974 _systemMenu[index++] = MENU_HELP;
975 } else if (isRunning()) {
976 items->add(new String(MENU_STR_CUT));
977 items->add(new String(MENU_STR_COPY));
978 items->add(new String(MENU_STR_PASTE));
979 items->add(new String(MENU_STR_SELECT));
980 _systemMenu[index++] = MENU_CUT;
981 _systemMenu[index++] = MENU_COPY;
982 _systemMenu[index++] = MENU_PASTE;
983 _systemMenu[index++] = MENU_SELECT_ALL;
984 }
985#if defined(_SDL) || defined(_FLTK)
986 items->add(new String(MENU_STR_BACK));
987 _systemMenu[index++] = MENU_BACK;
988#else
989 items->add(new String(MENU_STR_KEYPAD));
990 _systemMenu[index++] = MENU_KEYPAD;
991 if (!isEditing()) {
992 bool controlMode = get_focus_edit()->getControlMode();
993 sprintf(buffer, MENU_STR_CONTROL, (controlMode ? MENU_STR_ON : MENU_STR_OFF));
994 items->add(new String(buffer));
995 _systemMenu[index++] = MENU_CTRL_MODE;
996 }
997#endif
998 } else {
999 items->add(new String(MENU_STR_CONSOLE));
1000 items->add(new String(MENU_STR_SOURCE));
1001 _systemMenu[index++] = MENU_CONSOLE;
1002 _systemMenu[index++] = MENU_SOURCE;
1003#if !defined(_FLTK)
1004 if (!isEditing()) {
1005 items->add(new String(MENU_STR_RESTART));
1006 _systemMenu[index++] = MENU_RESTART;
1007 }
1008#endif
1009#if !defined(_SDL) && !defined(_FLTK)
1010 items->add(new String(MENU_STR_KEYPAD));
1011 _systemMenu[index++] = MENU_KEYPAD;
1012#endif
1013 if (_mainBas) {
1014 sprintf(buffer, MENU_STR_FONT, _fontScale - FONT_SCALE_INTERVAL);
1015 items->add(new String(buffer));
1016 sprintf(buffer, MENU_STR_FONT, _fontScale + FONT_SCALE_INTERVAL);
1017 items->add(new String(buffer));
1018 _systemMenu[index++] = MENU_ZOOM_UP;
1019 _systemMenu[index++] = MENU_ZOOM_DN;
1020 sprintf(buffer, MENU_STR_EDITOR, opt_ide == IDE_NONE ? MENU_STR_OFF : MENU_STR_ON);
1021 items->add(new String(buffer));
1022 _systemMenu[index++] = MENU_EDITMODE;
1023 sprintf(buffer, MENU_STR_THEME, themeName());
1024 items->add(new String(buffer));
1025 _systemMenu[index++] = MENU_THEME;
1026 }
1027 sprintf(buffer, MENU_STR_AUDIO, (opt_mute_audio ? MENU_STR_OFF : MENU_STR_ON));
1028 items->add(new String(buffer));
1029 _systemMenu[index++] = MENU_AUDIO;
1030#if !defined(_SDL) && !defined(_FLTK)
1031 if (!_mainBas && !_activeFile.empty()) {
1032 items->add(new String(MENU_STR_SHORT));
1033 items->add(new String(MENU_STR_SHARE));
1034 _systemMenu[index++] = MENU_SHORTCUT;
1035 _systemMenu[index++] = MENU_SHARE;
1036 }
1037#endif
1038 items->add(new String(MENU_STR_SCREEN));
1039 _systemMenu[index++] = MENU_SCREENSHOT;
1040#if defined(_SDL) || defined(_FLTK)
1041 items->add(new String(MENU_STR_BACK));
1042 _systemMenu[index++] = MENU_BACK;
1043#endif
1044 }
1045
1046 formatOptions(items);
1047 optionsBox(items);
1048 delete items;
1049 _menuActive = false;
1050 }
1051}
1052
1053void System::showSystemScreen(bool showSrc) {
1054 int prevScreenId;
1055 if (showSrc) {
1056 prevScreenId = _output->getScreenId(true);
1057 _output->selectBackScreen(SOURCE_SCREEN);
1058 printSource();
1059 _output->selectBackScreen(prevScreenId);
1060 _output->selectFrontScreen(SOURCE_SCREEN);
1061 } else {
1062 prevScreenId = _output->getScreenId(false);
1063 _output->selectFrontScreen(CONSOLE_SCREEN);
1064 }
1065 if (_userScreenId == -1) {
1066 _userScreenId = prevScreenId;
1067 }
1068}
1069
1070void System::waitForBack() {
1071 while (!isBack() && !isClosing() && !isRestart()) {
1072 MAEvent event = getNextEvent();
1073 if (event.type == EVENT_TYPE_KEY_PRESSED &&
1074 event.key == SB_KEY_BACKSPACE) {
1075 break;
1076 }
1077 }
1078}
1079
1080void System::waitForChange(bool error) {
1081 while (!isBack() && !isClosing()) {
1082 processEvents(0);
1083 if (error && _userScreenId == -1) {
1084 // back button presses while error displayed
1085 setExit(true);
1086 break;
1087 }
1088 if (_modifiedTime != getModifiedTime()) {
1089 break;
1090 }
1091 dev_delay(CHANGE_WAIT_SLEEP);
1092 }
1093}
1094
1095void System::printErrorLine() {
1096 if (_programSrc) {
1097 int line = 1;
1098 char *errLine = _programSrc;
1099 char *ch = _programSrc;
1100 while (line < gsb_last_line) {
1101 while (*ch && *ch != '\n') {
1102 ch++;
1103 }
1104 if (*ch) {
1105 errLine = ++ch;
1106 }
1107 line++;
1108 }
1109 while (*ch && *ch != '\n') {
1110 ch++;
1111 }
1112 char end;
1113 if (*ch == '\n') {
1114 ch++;
1115 end = *ch;
1116 *ch = '\0';
1117 } else {
1118 end = *ch;
1119 }
1120 while (*errLine && (IS_WHITE(*errLine))) {
1121 errLine++;
1122 }
1123
1124 int prevScreenId = _output->getScreenId(true);
1125 _output->selectBackScreen(CONSOLE_SCREEN);
1126 _output->print("\033[4mError line:\033[0m\n");
1127 _output->print(errLine);
1128 *ch = end;
1129 _output->selectBackScreen(prevScreenId);
1130 }
1131}
1132
1133void System::printSourceLine(char *text, int line, bool last) {
1134 char lineMargin[32];
1135 sprintf(lineMargin, "\033[7m%03d\033[0m ", line);
1136 _output->print(lineMargin);
1137 if (line == gsb_last_line && gsb_last_error) {
1138 _output->print("\033[7m");
1139 _output->print(text);
1140 if (last) {
1141 _output->print("\n");
1142 }
1143 _output->print("\033[27;31m --^\n");
1144 _output->print(gsb_last_errmsg);
1145 _output->print("\033[0m\n");
1146 } else {
1147 _output->print(text);
1148 }
1149}
1150
1151void System::printSource() {
1152 if (_programSrc && !_srcRendered) {
1153 _srcRendered = true;
1154 _output->clearScreen();
1155 int line = 1;
1156 char *ch = _programSrc;
1157 char *nextLine = _programSrc;
1158 int errorLine = gsb_last_error ? gsb_last_line : -1;
1159 int charHeight = _output->getCharHeight();
1160 int height = _output->getHeight();
1161 int pageLines = height / charHeight;
1162
1163 while (*ch) {
1164 while (*ch && *ch != '\n') {
1165 ch++;
1166 }
1167 if (*ch == '\n') {
1168 ch++;
1169 char end = *ch;
1170 *ch = '\0';
1171 printSourceLine(nextLine, line, false);
1172 *ch = end;
1173 nextLine = ch;
1174 } else {
1175 printSourceLine(nextLine, line, true);
1176 }
1177 line++;
1178
1179 if (errorLine != -1 && line == errorLine + pageLines) {
1180 // avoid scrolling past the error line
1181 if (*ch) {
1182 _output->print("... \n");
1183 }
1184 break;
1185 }
1186 }
1187
1188 // scroll to the error line
1189 if (errorLine != -1) {
1190 int displayLines = _output->getY() / charHeight;
1191 if (line > displayLines) {
1192 // printed more than displayed due to scrolling
1193 errorLine -= (line - displayLines);
1194 }
1195 int yScroll = charHeight * (errorLine - (pageLines / 2));
1196 int maxScroll = ((displayLines * charHeight) - height);
1197 if (yScroll < 0) {
1198 yScroll = 0;
1199 }
1200 if (yScroll < maxScroll) {
1201 _output->setScroll(0, yScroll);
1202 }
1203 } else {
1204 _output->setScroll(0, 0);
1205 }
1206 }
1207}
1208
1209void System::setExit(bool quit) {
1210 if (!isClosing()) {
1211 bool running = isRunning();
1212 _state = (quit && _editor == nullptr) ? kClosingState : kBackState;
1213 if (running) {
1214 brun_break();
1215 }
1216 }
1217}
1218
1219void System::setRestart() {
1220 if (isRunning()) {
1221 brun_break();
1222 }
1223 _state = kRestartState;
1224}
1225
1226void System::systemLog(const char *buf) {
1227 deviceLog("%s", buf);
1228 int prevScreenId = _output->getScreenId(true);
1229 _output->selectBackScreen(CONSOLE_SCREEN);
1230 _output->print(buf);
1231 _output->selectBackScreen(prevScreenId);
1232}
1233
1234void System::systemPrint(const char *format, ...) {
1235 va_list args;
1236
1237 va_start(args, format);
1238 unsigned size = vsnprintf(nullptr, 0, format, args);
1239 va_end(args);
1240
1241 if (size) {
1242 char *buf = (char *)malloc(size + 1);
1243 buf[0] = '\0';
1244 va_start(args, format);
1245 vsnprintf(buf, size + 1, format, args);
1246 va_end(args);
1247 buf[size] = '\0';
1248 systemLog(buf);
1249 free(buf);
1250 }
1251}
1252
1253//
1254// common device implementation
1255//
1256void osd_cls(void) {
1257 logEntered();
1258 g_system->getOutput()->clearScreen();
1259}
1260
1261int osd_events(int wait_flag) {
1262 int result;
1263 if (g_system->isBreak()) {
1264 result = -2;
1265 } else {
1266 g_system->processEvents(wait_flag);
1267 result = g_system->isBreak() ? -2 : 0;
1268 }
1269 return result;
1270}
1271
1272int osd_getpen(int mode) {
1273 return g_system->getPen(mode);
1274}
1275
1276long osd_getpixel(int x, int y) {
1277 return g_system->getOutput()->getPixel(x, y);
1278}
1279
1280int osd_getx(void) {
1281 return g_system->getOutput()->getX();
1282}
1283
1284int osd_gety(void) {
1285 return g_system->getOutput()->getY();
1286}
1287
1288void osd_line(int x1, int y1, int x2, int y2) {
1289 g_system->getOutput()->drawLine(x1, y1, x2, y2);
1290}
1291
1292void osd_arc(int xc, int yc, double r, double start, double end, double aspect) {
1293 g_system->getOutput()->drawArc(xc, yc, r, start, end, aspect);
1294}
1295
1296void osd_ellipse(int xc, int yc, int xr, int yr, int fill) {
1297 g_system->getOutput()->drawEllipse(xc, yc, xr, yr, fill);
1298}
1299
1300void osd_rect(int x1, int y1, int x2, int y2, int fill) {
1301 if (fill) {
1302 g_system->getOutput()->drawRectFilled(x1, y1, x2, y2);
1303 } else {
1304 g_system->getOutput()->drawRect(x1, y1, x2, y2);
1305 }
1306}
1307
1308void osd_refresh(void) {
1309 if (!g_system->isClosing()) {
1310 g_system->getOutput()->flush(true);
1311 }
1312}
1313
1314void osd_setcolor(long color) {
1315 if (!g_system->isClosing()) {
1316 g_system->getOutput()->setColor(color);
1317 }
1318}
1319
1320void osd_setpenmode(int enable) {
1321 g_system->enableCursor(enable);
1322}
1323
1324void osd_setpixel(int x, int y) {
1325 g_system->getOutput()->setPixel(x, y, dev_fgcolor);
1326}
1327
1328void osd_settextcolor(long fg, long bg) {
1329 g_system->getOutput()->setTextColor(fg, bg);
1330}
1331
1332void osd_setxy(int x, int y) {
1333 g_system->getOutput()->setXY(x, y);
1334}
1335
1336int osd_textheight(const char *str) {
1337 return g_system->getOutput()->textHeight();
1338}
1339
1340int osd_textwidth(const char *str) {
1341 MAExtent textSize = maGetTextSize(str);
1342 return EXTENT_X(textSize);
1343}
1344
1345#if !defined(_FLTK)
1346void osd_write(const char *str) {
1347 if (!g_system->isClosing()) {
1348 g_system->getOutput()->print(str);
1349 }
1350}
1351#endif
1352
1353void lwrite(const char *str) {
1354 if (!(str[0] == '\n' && str[1] == '\0') && !g_system->isClosing()) {
1355 g_system->systemLog(str);
1356 }
1357}
1358
1359void dev_delay(uint32_t ms) {
1360 g_system->getOutput()->flush(true);
1361 maWait(ms);
1362}
1363
1364char *dev_gets(char *dest, int maxSize) {
1365 return g_system->getText(dest, maxSize);
1366}
1367
1368char *dev_read(const char *fileName) {
1369 return g_system->readSource(fileName);
1370}
1371
1372int maGetMilliSecondCount(void) {
1373 return dev_get_millisecond_count();
1374}
1375
1376void dev_log_stack(const char *keyword, int type, int line) {
1377 return g_system->logStack(keyword, type, line);
1378}
1379