1// This file is part of SmallBASIC
2//
3// Copyright(C) 2001-2020 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 <FL/fl_ask.H>
11#include <FL/Fl_PNG_Image.H>
12#include <FL/fl_utf8.h>
13#include "platform/fltk/MainWindow.h"
14#include "platform/fltk/EditorWidget.h"
15#include "platform/fltk/HelpView.h"
16#include "platform/fltk/FileWidget.h"
17#include "platform/fltk/utils.h"
18#include "ui/strlib.h"
19#include "common/sbapp.h"
20#include "common/sys.h"
21#include "common/fs_socket_client.h"
22#include "common/keymap.h"
23
24char *packageHome;
25char *runfile = 0;
26int recentIndex = 0;
27int restart = 0;
28bool opt_interactive;
29int recentMenu[NUM_RECENT_ITEMS];
30strlib::String recentPath[NUM_RECENT_ITEMS];
31strlib::String recentLabel[NUM_RECENT_ITEMS];
32int recentPosition[NUM_RECENT_ITEMS];
33MainWindow *wnd;
34ExecState runMode = init_state;
35
36const char *fontCache = "fonts.txt";
37const char *endFont = ".";
38const char *untitledFile = "untitled.bas";
39const char *fileTabName = "File";
40const char *helpTabName = "Help";
41const char *pluginHome = "plugins";
42const char *historyFile = "history.txt";
43const char *keywordsFile = "keywords.txt";
44const char *recentFile0 = "&File/_Open Recent File/%s %d";
45const char *recentFile = "&File/Open Recent File/%s %d";
46
47//--EditWindow functions--------------------------------------------------------
48
49void MainWindow::statusMsg(RunMessage runMessage, const char *statusMessage) {
50 EditorWidget *editWidget = getEditor();
51 if (editWidget) {
52 editWidget->statusMsg(statusMessage);
53 editWidget->runState(runMessage);
54 }
55}
56
57void MainWindow::busyMessage() {
58 EditorWidget *editWidget = getEditor();
59 if (editWidget) {
60 editWidget->statusMsg("Selection unavailable while program is running.");
61 }
62}
63
64void MainWindow::pathMessage(const char *file) {
65 char message[PATH_MAX];
66 snprintf(message, sizeof(message), "File not found: %s", file);
67 statusMsg(rs_err, message);
68}
69
70void MainWindow::showEditTab(EditorWidget *editWidget) {
71 if (editWidget) {
72 _tabGroup->value(editWidget->parent());
73 editWidget->take_focus();
74 }
75}
76
77/**
78 * run the give file. returns whether break was hit
79 */
80bool MainWindow::basicMain(EditorWidget *editWidget,
81 const char *fullpath, bool toolExec) {
82 int len = strlen(fullpath);
83 char path[PATH_MAX];
84 bool breakToLine = false; // whether to restore the editor cursor
85
86 if (strcasecmp(fullpath + len - 4, ".htm") == 0 ||
87 strcasecmp(fullpath + len - 5, ".html") == 0) {
88 // render html edit buffer
89 snprintf(path, sizeof(path), "file:%s", fullpath);
90 if (editWidget) {
91 editWidget->take_focus();
92 }
93 return false;
94 }
95 if (access(fullpath, 0) != 0) {
96 pathMessage(fullpath);
97 runMode = edit_state;
98 return false;
99 }
100 // start in the directory of the bas program
101 const char *filename = FileWidget::splitPath(fullpath, path);
102
103 if (editWidget) {
104 _runEditWidget = editWidget;
105 if (!toolExec) {
106 editWidget->readonly(true);
107 editWidget->runState(rs_run);
108 breakToLine = editWidget->isBreakToLine();
109 opt_ide = editWidget->isHideIDE()? IDE_NONE : IDE_INTERNAL;
110 }
111 }
112
113 Fl_Window *fullScreen = NULL;
114 Fl_Group *oldOutputGroup = _outputGroup;
115 int old_w = _out->w();
116 int old_h = _out->h();
117 int old_x = _out->x();
118 int old_y = _out->y();
119 int interactive = opt_interactive;
120
121 if (!toolExec) {
122 if (opt_ide == IDE_NONE) {
123 // run in a separate window with the ide hidden
124 fullScreen = new BaseWindow(w(), h(), _runtime);
125 fullScreen->box(FL_NO_BOX);
126 fullScreen->callback(quit_cb);
127 fullScreen->add(_out);
128 fullScreen->end();
129 fullScreen->take_focus();
130 setTitle(fullScreen, filename);
131 _profile->restoreAppPosition(fullScreen);
132 _outputGroup = fullScreen;
133 resizeDisplay(0, 0, w(), h());
134 hide();
135 } else {
136 setTitle(this, filename);
137 }
138 } else {
139 opt_interactive = false;
140 }
141
142 int success;
143 do {
144 restart = false;
145 runMode = run_state;
146 chdir(path);
147 success = _runtime->run(filename);
148 }
149 while (restart);
150
151 opt_interactive = interactive;
152 bool was_break = (runMode == break_state);
153
154 if (fullScreen != NULL) {
155 _profile->setAppPosition(*fullScreen);
156 fullScreen->remove(_out);
157 delete fullScreen;
158
159 _outputGroup = oldOutputGroup;
160 _outputGroup->add(_out);
161 resizeDisplay(old_x, old_y, old_w, old_h);
162 take_focus();
163 show();
164 } else {
165 copy_label("SmallBASIC");
166 }
167
168 if (runMode == quit_state) {
169 exit(0);
170 }
171
172 if (!success || was_break) {
173 if (!toolExec && editWidget && (!was_break || breakToLine)) {
174 editWidget->gotoLine(gsb_last_line);
175 }
176 if (editWidget) {
177 showEditTab(editWidget);
178 editWidget->runState(was_break ? rs_ready : rs_err);
179 }
180 } else if (!toolExec && editWidget) {
181 // normal termination
182 editWidget->runState(rs_ready);
183 }
184
185 if (!toolExec && editWidget) {
186 editWidget->readonly(false);
187 }
188
189 runMode = edit_state;
190 _runEditWidget = 0;
191 return was_break;
192}
193
194//--Menu callbacks--------------------------------------------------------------
195
196void MainWindow::close_tab(Fl_Widget *w, void *eventData) {
197 if (_tabGroup->children() > 1) {
198 Fl_Group *group = getSelectedTab();
199 if (group && group != _outputGroup) {
200 if (gw_editor == getGroupWidget(group)) {
201 EditorWidget *editWidget = (EditorWidget *)group->child(0);
202 if (!editWidget->checkSave(true)) {
203 return;
204 }
205 // check whether the editor is a running program
206 if (editWidget == _runEditWidget) {
207 setBreak();
208 return;
209 }
210 }
211 _tabGroup->remove(group);
212 delete group;
213 redraw();
214 }
215 }
216}
217
218void MainWindow::close_other_tabs(Fl_Widget *w, void *eventData) {
219 Fl_Group *selected = getSelectedTab();
220 int n = _tabGroup->children();
221 Fl_Group *items[n];
222 for (int c = 0; c < n; c++) {
223 items[c] = NULL;
224 Fl_Group *child = (Fl_Group *)_tabGroup->child(c);
225 if (child != selected && gw_editor == getGroupWidget(child)) {
226 EditorWidget *editWidget = (EditorWidget *)child->child(0);
227 if (editWidget != _runEditWidget && editWidget->checkSave(true)) {
228 items[c] = child;
229 }
230 }
231 }
232 for (int c = 0; c < n; c++) {
233 if (items[c] != NULL) {
234 _tabGroup->remove(items[c]);
235 delete items[c];
236 }
237 }
238 redraw();
239}
240
241void MainWindow::restart_run(Fl_Widget *w, void *eventData) {
242 if (runMode == run_state) {
243 setBreak();
244 restart = true;
245 }
246}
247
248void MainWindow::quit(Fl_Widget *w, void *eventData) {
249 if (runMode == edit_state || runMode == quit_state) {
250 // auto-save scratchpad
251 int n = _tabGroup->children();
252 for (int c = 0; c < n; c++) {
253 Fl_Group *group = (Fl_Group *)_tabGroup->child(c);
254 char path[PATH_MAX];
255 if (gw_editor == getGroupWidget(group)) {
256 EditorWidget *editWidget = (EditorWidget *)group->child(0);
257 const char *filename = editWidget->getFilename();
258 int offs = strlen(filename) - strlen(untitledFile);
259 if (filename[0] == 0 || (offs > 0 && strcasecmp(filename + offs, untitledFile) == 0)) {
260 getHomeDir(path, sizeof(path));
261 strcat(path, untitledFile);
262 editWidget->doSaveFile(path);
263 } else if (!editWidget->checkSave(true)) {
264 return;
265 }
266 }
267 }
268 exit(0);
269 } else {
270 switch (fl_choice("Terminate running program?", "*Exit", "Break", "Cancel")) {
271 case 0:
272 exit(0);
273 case 1:
274 setBreak();
275 default:
276 break;
277 }
278 }
279}
280
281/**
282 * opens the smallbasic home page in a browser window
283 */
284void MainWindow::help_home(Fl_Widget *w, void *eventData) {
285 browseFile("https://smallbasic.github.io");
286}
287
288/**
289 * displays the program help page in a browser window
290 */
291void MainWindow::help_app(Fl_Widget *w, void *eventData) {
292 browseFile("https://smallbasic.github.io/pages/fltk.html");
293}
294
295/**
296 * handle click from within help window
297 */
298void MainWindow::help_contents_anchor(Fl_Widget *w, void *eventData) {
299 String eventName = wnd->getHelp()->getEventName();
300 if (eventName.indexOf("http", 0) != -1) {
301 browseFile(eventName.c_str());
302 } else {
303 char path[PATH_MAX];
304 sprintf(path, "https://smallbasic.github.io%s", eventName.c_str());
305 browseFile(path);
306 }
307}
308
309/**
310 * handle f1 context help
311 */
312void MainWindow::help_contents(Fl_Widget *w, void *eventData) {
313 EditorWidget *editWidget = getEditor();
314 if (editWidget) {
315 int start, end;
316 char *selection = editWidget->getSelection(&start, &end);
317 getHelp()->showContextHelp(selection);
318 free((void *)selection);
319 } else {
320 getHelp()->helpIndex();
321 }
322}
323
324void MainWindow::help_contents_brief(Fl_Widget *w, void *eventData) {
325 EditorWidget *editWidget = getEditor();
326 if (editWidget) {
327 int start, end;
328 char *selection = editWidget->getSelection(&start, &end);
329 const char *help = getBriefHelp(selection);
330 if (help != NULL) {
331 editWidget->getTty()->print(help);
332 editWidget->getTty()->print("\n");
333 }
334 free((void *)selection);
335 }
336}
337
338void MainWindow::help_about(Fl_Widget *w, void *eventData) {
339 getHelp()->about();
340}
341
342void MainWindow::export_file(Fl_Widget *w, void *eventData) {
343 EditorWidget *editWidget = getEditor();
344 static char token[PATH_MAX];
345 if (editWidget) {
346 if (runMode == edit_state) {
347 int handle = 1;
348 char buffer[PATH_MAX];
349 if (_exportFile.length()) {
350 strcpy(buffer, _exportFile.c_str());
351 } else {
352 buffer[0] = 0;
353 }
354 editWidget->statusMsg("Enter file/address: For mobile SmallBASIC use SOCL:<IP>:<Port>");
355 editWidget->getInput(buffer, PATH_MAX);
356 if (buffer[0]) {
357 if (strncasecmp(buffer, "SOCL:", 5) == 0) {
358 editWidget->statusMsg("Enter token:");
359 editWidget->getInput(token, PATH_MAX);
360 } else {
361 token[0] = 0;
362 }
363 _exportFile = buffer;
364 if (dev_fopen(handle, _exportFile, DEV_FILE_OUTPUT)) {
365 const char *data = editWidget->data();
366 if (token[0]) {
367 vsncat(buffer, sizeof(buffer), "# ", token, "\n", NULL);
368 dev_fwrite(handle, (byte *)buffer, strlen(buffer));
369 }
370 if (!dev_fwrite(handle, (byte *)data, editWidget->dataLength())) {
371 vsncat(buffer, sizeof(buffer), "Failed to write: ", _exportFile.c_str(), NULL);
372 statusMsg(rs_err, buffer);
373 } else {
374 vsncat(buffer, sizeof(buffer), "Exported", editWidget->getFilename(), " to ",
375 _exportFile.c_str(), NULL);
376 statusMsg(rs_ready, buffer);
377 }
378 } else {
379 vsncat(buffer, sizeof(buffer), "Failed to open: ", _exportFile.c_str(), NULL);
380 statusMsg(rs_err, buffer);
381 }
382 dev_fclose(handle);
383 }
384 // cancel setModal() from editWidget->getInput()
385 runMode = edit_state;
386 } else {
387 busyMessage();
388 }
389 }
390}
391
392void MainWindow::set_options(Fl_Widget *w, void *eventData) {
393 const char *args = fl_input("Enter program command line", opt_command);
394 if (args) {
395 strcpy(opt_command, args);
396 }
397}
398
399void MainWindow::set_theme(Fl_Widget *w, void *eventData) {
400 Fl_Group *group = getSelectedTab();
401 if (group && group != _outputGroup) {
402 GroupWidgetEnum gw = getGroupWidget(group);
403 switch (gw) {
404 case gw_editor:
405 // change all editors
406 _profile->loadEditTheme(((intptr_t) eventData));
407 for (int c = 0; c < _tabGroup->children(); c++) {
408 Fl_Group *child = (Fl_Group *)_tabGroup->child(c);
409 if (getGroupWidget(child) == gw_editor) {
410 _profile->setEditTheme((EditorWidget *)child->child(0));
411 }
412 }
413 break;
414 case gw_help:
415 _profile->setHelpTheme((HelpView *)group->child(0), ((intptr_t) eventData));
416 break;
417 default:
418 break;
419 }
420 }
421 damage(FL_DAMAGE_ALL);
422}
423
424void MainWindow::next_tab(Fl_Widget *w, void *eventData) {
425 Fl_Group *group = getNextTab(getSelectedTab());
426 _tabGroup->value(group);
427 EditorWidget *editWidget = getEditor(group);
428 if (editWidget) {
429 editWidget->take_focus();
430 }
431}
432
433void MainWindow::prev_tab(Fl_Widget *w, void *eventData) {
434 Fl_Group *group = getPrevTab(getSelectedTab());
435 _tabGroup->value(group);
436 EditorWidget *editWidget = getEditor(group);
437 if (editWidget) {
438 editWidget->take_focus();
439 }
440}
441
442void MainWindow::copy_text(Fl_Widget *w, void *eventData) {
443 EditorWidget *editWidget = getEditor();
444 if (editWidget) {
445 editWidget->copyText();
446 } else {
447 handle(EVENT_COPY_TEXT);
448 }
449}
450
451void MainWindow::font_size_incr(Fl_Widget *w, void *eventData) {
452 int size = _runtime->getFontSize();
453 if (size < MAX_FONT_SIZE) {
454 _runtime->setFontSize(size + 1);
455 resizeTabs(size + 1);
456 }
457}
458
459void MainWindow::font_size_decr(Fl_Widget *w, void *eventData) {
460 int size = _runtime->getFontSize();
461 if (size > MIN_FONT_SIZE) {
462 _runtime->setFontSize(size - 1);
463 resizeTabs(size - 1);
464 }
465}
466
467void MainWindow::run(Fl_Widget *w, void *eventData) {
468 EditorWidget *editWidget = getEditor();
469 if (editWidget) {
470 const char *filename = editWidget->getFilename();
471 if (runMode == edit_state) {
472 // inhibit autosave on run function with environment var
473 const char *noSave = dev_getenv("NO_RUN_SAVE");
474 char path[PATH_MAX];
475 if (noSave == 0 || noSave[0] != '1') {
476 if (filename == 0 || filename[0] == 0) {
477 getHomeDir(path, sizeof(path));
478 strcat(path, untitledFile);
479 filename = path;
480 editWidget->doSaveFile(filename);
481 } else if (access(filename, W_OK) == 0) {
482 editWidget->doSaveFile(filename);
483 }
484 }
485 basicMain(editWidget, filename, false);
486 } else {
487 busyMessage();
488 }
489 }
490}
491
492void MainWindow::run_break(Fl_Widget *w, void *eventData) {
493 if (runMode == modal_state && !count_tasks()) {
494 // break from modal edit mode loop
495 runMode = edit_state;
496 } else if (runMode == run_state || runMode == modal_state) {
497 setBreak();
498 }
499}
500
501/**
502 * run the online samples program
503 */
504void MainWindow::run_samples(Fl_Widget *w, void *eventData) {
505 if (runMode == edit_state) {
506 runMode = run_state;
507 _runtime->runSamples();
508 runMode = edit_state;
509 } else {
510 busyMessage();
511 }
512}
513
514/**
515 * run the selected text as the main program
516 */
517void MainWindow::run_selection(Fl_Widget *w, void *eventData) {
518 EditorWidget *editWidget = getEditor();
519 if (editWidget) {
520 char path[PATH_MAX];
521 getHomeDir(path, sizeof(path));
522 strcat(path, "selection.bas");
523 editWidget->saveSelection(path);
524 basicMain(editWidget, path, false);
525 }
526}
527
528/**
529 * run the active program in sbasicg
530 */
531void MainWindow::run_live(Fl_Widget *w, void *eventData) {
532 EditorWidget *editWidget = getEditor();
533 if (editWidget) {
534 launchExec(editWidget->getFilename());
535 }
536}
537
538/**
539 * callback for editor-plug-in plug-ins. we assume the target
540 * program will be changing the contents of the editor buffer
541 */
542void MainWindow::editor_plugin(Fl_Widget *w, void *eventData) {
543 EditorWidget *editWidget = getEditor();
544 if (editWidget) {
545 Fl_Text_Editor *editor = editWidget->getEditor();
546 char filename[PATH_MAX];
547 strlcpy(filename, editWidget->getFilename(), PATH_MAX);
548
549 if (runMode == edit_state) {
550 if (editWidget->checkSave(false) && filename[0]) {
551 char path[PATH_MAX];
552 int pos = editor->insert_position();
553 int row, col, s1r, s1c, s2r, s2c;
554 editWidget->getRowCol(&row, &col);
555 editWidget->getSelStartRowCol(&s1r, &s1c);
556 editWidget->getSelEndRowCol(&s2r, &s2c);
557 strlcpy(opt_command, filename, PATH_MAX);
558 snprintf(path, sizeof(path), "|%d|%d|%d|%d|%d|%d", row - 1, col, s1r - 1, s1c, s2r - 1, s2c);
559 strlcat(opt_command, path, PATH_MAX);
560 runMode = run_state;
561 editWidget->runState(rs_run);
562 snprintf(path, sizeof(path), "%s/%s", packageHome, (const char *)eventData);
563 int interactive = opt_interactive;
564 opt_interactive = false;
565 int success = sbasic_main(path);
566 opt_interactive = interactive;
567 editWidget->runState(success ? rs_ready : rs_err);
568 editWidget->loadFile(filename);
569 editor->insert_position(pos);
570 editor->show_insert_position();
571 editWidget->setRowCol(row, col + 1);
572 showEditTab(editWidget);
573 runMode = edit_state;
574 opt_command[0] = 0;
575 }
576 } else {
577 busyMessage();
578 }
579 }
580}
581
582/**
583 * callback for tool-plug-in plug-ins.
584 */
585void MainWindow::tool_plugin(Fl_Widget *w, void *eventData) {
586 if (runMode == edit_state) {
587 char path[PATH_MAX];
588 snprintf(opt_command, sizeof(opt_command), "%s/%s", packageHome, pluginHome);
589 statusMsg(rs_ready, (const char *)eventData);
590 snprintf(path, sizeof(path), "%s/%s", packageHome, (const char *)eventData);
591 _tabGroup->value(_outputGroup);
592 basicMain(0, path, true);
593 statusMsg(rs_ready, 0);
594 opt_command[0] = 0;
595 } else {
596 busyMessage();
597 }
598}
599
600void MainWindow::load_file(Fl_Widget *w, void *eventData) {
601 int pathIndex = ((intptr_t) eventData) - 1;
602 const char *path = recentPath[pathIndex].c_str();
603 EditorWidget *editWidget = getEditor(path);
604 if (!editWidget) {
605 editWidget = getEditor(createEditor(path));
606 }
607
608 if (editWidget->checkSave(true)) {
609 Fl_Text_Editor *editor = editWidget->getEditor();
610 // save current position
611 recentPosition[recentIndex] = editor->insert_position();
612 recentIndex = pathIndex;
613 // load selected file
614 if (access(path, 0) == 0) {
615 editWidget->loadFile(path);
616 // restore previous position
617 editor->insert_position(recentPosition[recentIndex]);
618 editWidget->fileChanged(true);
619 const char *slash = strrchr(path, '/');
620 editWidget->parent()->copy_label(slash ? slash + 1 : path);
621 showEditTab(editWidget);
622 } else {
623 pathMessage(path);
624 }
625 }
626}
627
628//--Startup functions-----------------------------------------------------------
629
630/**
631 * scan for recent files
632 */
633void MainWindow::scanRecentFiles(Fl_Menu_Bar *menu) {
634 FILE *fp;
635 char buffer[PATH_MAX];
636 char path[PATH_MAX];
637 char label[1024];
638 int i = 0;
639
640 getHomeDir(path, sizeof(path));
641 strcat(path, historyFile);
642 fp = fopen(path, "r");
643 if (fp) {
644 while (feof(fp) == 0 && fgets(buffer, sizeof(buffer), fp)) {
645 buffer[strlen(buffer) - 1] = 0; // trim new-line
646 if (access(buffer, 0) == 0) {
647 char *fileLabel = strrchr(buffer, '/');
648 if (fileLabel == 0) {
649 fileLabel = strrchr(buffer, '\\');
650 }
651 fileLabel = fileLabel ? fileLabel + 1 : buffer;
652 if (fileLabel != 0 && *fileLabel == '_') {
653 fileLabel++;
654 }
655 void *data = (void *)(intptr_t)(i + 1);
656 snprintf(label, sizeof(label), i == 0 ? recentFile0 : recentFile, fileLabel, i);
657 recentLabel[i].append(fileLabel);
658 recentPath[i].append(buffer);
659 recentMenu[i] = menu->add(label, FL_CTRL + '1' + i, load_file_cb, data);
660 if (++i == NUM_RECENT_ITEMS) {
661 break;
662 }
663 }
664 }
665 fclose(fp);
666 }
667 while (i < NUM_RECENT_ITEMS) {
668 void *data = (void *)(intptr_t)(i + 1);
669 snprintf(label, sizeof(label), i == 0 ? recentFile0 : recentFile, untitledFile, i);
670 recentLabel[i].append(untitledFile);
671 recentPath[i].append(untitledFile);
672 recentMenu[i] = menu->add(label, FL_CTRL + '1' + i, load_file_cb, data);
673 i++;
674 }
675}
676
677/**
678 * scan for optional plugins
679 */
680void MainWindow::scanPlugIns(Fl_Menu_Bar *menu) {
681 char buffer[PATH_MAX];
682 char path[PATH_MAX];
683 char label[1024];
684
685 snprintf(path, sizeof(path), "%s/%s", packageHome, pluginHome);
686 DIR *dp = opendir(path);
687 while (dp != NULL) {
688 struct dirent *e = readdir(dp);
689 if (e == NULL) {
690 break;
691 }
692 const char *filename = e->d_name;
693 int len = strlen(filename);
694
695 if (strcasecmp(filename + len - 4, ".bas") == 0) {
696 snprintf(path, sizeof(path), "%s/%s/%s", packageHome, pluginHome, filename);
697 FILE *file = fopen(path, "r");
698 if (!file) {
699 continue;
700 }
701
702 if (!fgets(buffer, PATH_MAX, file)) {
703 fclose(file);
704 continue;
705 }
706 bool editorTool = false;
707 FileWidget::trimEOL(buffer);
708 if (strcmp("'tool-plug-in", buffer) == 0) {
709 editorTool = true;
710 } else if (strcmp("'app-plug-in", buffer) != 0) {
711 fclose(file);
712 continue;
713 }
714
715 if (fgets(buffer, PATH_MAX, file) && strncmp("'menu", buffer, 5) == 0) {
716 FileWidget::trimEOL(buffer);
717 int offs = 6;
718 while (buffer[offs] && (buffer[offs] == '\t' || buffer[offs] == ' ')) {
719 offs++;
720 }
721 snprintf(label, sizeof(label), (editorTool ? "&Edit/Basic/%s" : "&Basic/%s"), buffer + offs);
722 // use an absolute path
723 snprintf(path, sizeof(path), "%s/%s", pluginHome, filename);
724 menu->add(label, 0, (Fl_Callback *)(editorTool ? editor_plugin_cb : tool_plugin_cb), strdup(path));
725 }
726 fclose(file);
727 }
728 }
729 // cleanup
730 if (dp) {
731 closedir(dp);
732 }
733}
734
735/**
736 * process the program command line arguments
737 */
738int arg_cb(int argc, char **argv, int &i) {
739 const char *s = argv[i];
740 int len = strlen(s);
741 int c;
742
743 if (strcasecmp(s + len - 4, ".bas") == 0 && access(s, 0) == 0) {
744 runfile = strdup(s);
745 runMode = run_state;
746 i += 1;
747 return 1;
748 }
749
750 if (argv[i][0] == '-') {
751 if (!argv[i][2] && argv[i + 1]) {
752 // commands that take an additional file name argument
753 switch (argv[i][1]) {
754 case 'e':
755 runfile = strdup(argv[i + 1]);
756 runMode = edit_state;
757 i += 2;
758 return 1;
759
760 case 'r':
761 runfile = strdup(argv[i + 1]);
762 runMode = run_state;
763 i += 2;
764 return 1;
765
766 case 'm':
767 opt_loadmod = 1;
768 strcpy(opt_modpath, argv[i + 1]);
769 i += 2;
770 return 1;
771 }
772 }
773
774 switch (argv[i][1]) {
775 case 'n':
776 i += 1;
777 opt_interactive = true;
778 return 1;
779
780 case 'v':
781 i += 1;
782 opt_verbose = 1;
783 opt_quiet = 0;
784 return 1;
785
786 case '-':
787 // echo foo | sbasic foo.bas --
788 while ((c = fgetc(stdin)) != EOF) {
789 int len = strlen(opt_command);
790 opt_command[len] = c;
791 opt_command[len + 1] = 0;
792 }
793 i++;
794 return 1;
795 }
796 }
797
798 if (runMode == run_state) {
799 // remaining text is .bas program argument
800 if (opt_command[0]) {
801 strcat(opt_command, " ");
802 }
803 strcat(opt_command, s);
804 i++;
805 return 1;
806 }
807
808 return 0;
809}
810
811/**
812 * start the application in run mode
813 */
814void run_mode_startup(void *data) {
815 if (data) {
816 Fl_Window *w = (Fl_Window *)data;
817 delete w;
818 }
819
820 EditorWidget *editWidget = wnd->getEditor(true);
821 if (editWidget) {
822 editWidget->setHideIde(true);
823 editWidget->loadFile(runfile);
824
825 opt_ide = IDE_NONE;
826 if (!wnd->basicMain(0, runfile, false)) {
827 exit(0);
828 }
829 editWidget->getEditor()->take_focus();
830 opt_ide = IDE_INTERNAL;
831 }
832}
833
834/**
835 * prepare to start the application
836 */
837bool initialise(int argc, char **argv) {
838 opt_graphics = 1;
839 opt_quiet = 1;
840 opt_verbose = 0;
841 opt_nosave = 1;
842 opt_ide = IDE_INTERNAL;
843 opt_interactive = true;
844 opt_file_permitted = 1;
845 os_graphics = 1;
846 opt_mute_audio = 0;
847
848 int i = 0;
849 if (Fl::args(argc, argv, i, arg_cb) < argc) {
850 fl_message("Options are:\n"
851 " -e[dit] file.bas\n"
852 " -r[un] file.bas\n"
853 " -v[erbose]\n"
854 " -n[on]-interactive\n"
855 " -m[odule]-home");
856 return false;
857 }
858
859 // package home contains installed components
860#if defined(WIN32)
861 packageHome = strdup(argv[0]);
862 char *slash = (char *)FileWidget::forwardSlash(packageHome);
863 if (slash) {
864 *slash = 0;
865 }
866#else
867 packageHome = (char *)PACKAGE_DATA_DIR;
868#endif
869 dev_setenv("PKG_HOME", packageHome);
870
871 // bas_home contains user editable files along with generated help
872 char path[PATH_MAX];
873 getHomeDir(path, sizeof(path), false);
874 dev_setenv("BAS_HOME", path);
875 setAppName(argv[0]);
876
877 wnd = new MainWindow(800, 650);
878
879 // load startup editors
880 wnd->new_file(0, 0);
881 wnd->_profile->restore(wnd);
882
883 Fl::scheme("gtk+");
884 Fl_Window::default_xclass("smallbasic");
885 fl_message_title("SmallBASIC");
886 wnd->loadIcon();
887 Fl::wait(0);
888
889 Fl_Window *run_wnd;
890
891 switch (runMode) {
892 case run_state:
893 run_wnd = new Fl_Window(0, 0);
894 run_wnd->show();
895 Fl::add_timeout(0.5f, run_mode_startup, run_wnd);
896 break;
897
898 case edit_state:
899 wnd->getEditor(true)->loadFile(runfile);
900 break;
901
902 default:
903 runMode = edit_state;
904 }
905
906 if (runMode != run_state) {
907 wnd->show(argc, argv);
908 }
909
910 return true;
911}
912
913/**
914 * application entry point
915 */
916int main(int argc, char **argv) {
917 int result;
918 if (initialise(argc, argv)) {
919 Fl::run();
920 result = 0;
921 } else {
922 result = 1;
923 }
924 return result;
925}
926
927//--MainWindow methods----------------------------------------------------------
928
929MainWindow::MainWindow(int w, int h) :
930 BaseWindow(w, h) {
931 _runEditWidget = 0;
932 _profile = new Profile();
933 size_range(250, 250);
934
935 FileWidget::forwardSlash(runfile);
936 begin();
937 Fl_Menu_Bar *m = _menuBar = new Fl_Menu_Bar(0, 0, w, MENU_HEIGHT);
938 m->add("&File/&New File", FL_CTRL + 'n', new_file_cb);
939 m->add("&File/&Open File", FL_CTRL + 'o', open_file_cb);
940 scanRecentFiles(m);
941 m->add("&File/&Close", FL_CTRL + FL_F+4, close_tab_cb);
942 m->add("&File/_Close Others", 0, close_other_tabs_cb);
943 m->add("&File/&Save File", FL_CTRL + 's', EditorWidget::save_file_cb);
944 m->add("&File/_Save File &As", FL_CTRL + FL_SHIFT + 'S', save_file_as_cb);
945 m->add("&File/_Export", FL_CTRL + FL_F+9, export_file_cb);
946 m->add("&File/E&xit", FL_CTRL + 'q', quit_cb);
947 m->add("&Edit/_&Undo", FL_CTRL + 'z', EditorWidget::undo_cb);
948 m->add("&Edit/Cu&t", FL_CTRL + 'x', EditorWidget::cut_text_cb);
949 m->add("&Edit/&Copy", FL_CTRL + 'c', copy_text_cb);
950 m->add("&Edit/_&Paste", FL_CTRL + 'v', EditorWidget::paste_text_cb);
951 m->add("&Edit/_&Select All", FL_CTRL + 'a', EditorWidget::select_all_cb);
952 m->add("&Edit/Ch&ange Case", FL_ALT + 'c', EditorWidget::change_case_cb);
953 m->add("&Edit/&Expand Word", FL_ALT + '/', EditorWidget::expand_word_cb);
954 m->add("&Edit/_&Rename Word", FL_CTRL + FL_SHIFT + 'r', EditorWidget::rename_word_cb);
955 m->add("&Edit/&Find", FL_CTRL + 'f', EditorWidget::find_cb);
956 m->add("&Edit/Replace", FL_CTRL + 'r', EditorWidget::show_replace_cb);
957 m->add("&Edit/_&Goto Line", FL_CTRL + 'g', EditorWidget::goto_line_cb);
958 m->add("&Edit/Clear Console", FL_CTRL + '-', EditorWidget::clear_console_cb);
959 m->add("&View/&Next Tab", FL_F+6, next_tab_cb);
960 m->add("&View/_&Prev Tab", FL_CTRL + FL_F+6, prev_tab_cb);
961 m->add("&View/Theme/&Solarized Dark", 0, set_theme_cb, (void *)(intptr_t)0);
962 m->add("&View/Theme/&Solarized Light", 0, set_theme_cb, (void *)(intptr_t)1);
963 m->add("&View/Theme/&Shian", 0, set_theme_cb, (void *)(intptr_t)2);
964 m->add("&View/Theme/&Atom 1", 0, set_theme_cb, (void *)(intptr_t)3);
965 m->add("&View/Theme/&Atom 2", 0, set_theme_cb, (void *)(intptr_t)4);
966 m->add("&View/Theme/&R157", 0, set_theme_cb, (void *)(intptr_t)5);
967 m->add("&View/Text Color/_Background", 0, EditorWidget::set_color_cb, (void *)st_background);
968 m->add("&View/Text Color/Text", 0, EditorWidget::set_color_cb, (void *)st_text);
969 m->add("&View/Text Color/Comments", 0, EditorWidget::set_color_cb, (void *)st_comments);
970 m->add("&View/Text Color/_Strings", 0, EditorWidget::set_color_cb, (void *)st_strings);
971 m->add("&View/Text Color/Keywords", 0, EditorWidget::set_color_cb, (void *)st_keywords);
972 m->add("&View/Text Color/Funcs", 0, EditorWidget::set_color_cb, (void *)st_funcs);
973 m->add("&View/Text Color/_Subs", 0, EditorWidget::set_color_cb, (void *)st_subs);
974 m->add("&View/Text Color/Numbers", 0, EditorWidget::set_color_cb, (void *)st_numbers);
975 m->add("&View/Text Color/Operators", 0, EditorWidget::set_color_cb, (void *)st_operators);
976 m->add("&View/Text Color/_Find Matches", 0, EditorWidget::set_color_cb, (void *)st_findMatches);
977 m->add("&View/Text Color/Line Numbers", 0, EditorWidget::set_color_cb, (void *)st_lineNumbers);
978 m->add("&View/Text Size/&Increase", FL_CTRL + ']', font_size_incr_cb);
979 m->add("&View/Text Size/&Decrease", FL_CTRL + '[', font_size_decr_cb);
980
981 scanPlugIns(m);
982
983 m->add("&Run/&Run", FL_F+9, run_cb);
984 m->add("&Run/&Live Editing", FL_F+8, run_live_cb);
985 m->add("&Run/_&Selection", FL_F+7, run_selection_cb);
986 m->add("&Run/&Break", FL_CTRL + 'b', run_break_cb);
987 m->add("&Run/_&Restart", FL_CTRL + 'r', restart_run_cb);
988 m->add("&Run/&Command", FL_F+10, set_options_cb);
989 m->add("&Run/Online Samples", 0, run_samples_cb);
990 m->add("&Help/&Help Contents", FL_F+1, help_contents_cb);
991 m->add("&Help/_&Context Help", FL_F+2, help_contents_brief_cb);
992 m->add("&Help/&Program Help", FL_F+11, help_app_cb);
993 m->add("&Help/_&Home Page", 0, help_home_cb);
994 m->add("&Help/&About SmallBASIC", FL_F+12, help_about_cb);
995
996 callback(quit_cb);
997
998 int x1 = 0;
999 int y1 = MENU_HEIGHT + TAB_BORDER;
1000 int x2 = w;
1001 int y2 = h - MENU_HEIGHT - TAB_BORDER;
1002
1003 // group for all tabs
1004 _tabGroup = new Fl_Tabs(x1, y1, x2, y2);
1005
1006 // create the output tab
1007 y1 += MENU_HEIGHT;
1008 y2 -= MENU_HEIGHT;
1009 _outputGroup = new Fl_Group(x1, y1, x2, y2, "Output");
1010 _outputGroup->labelfont(FL_HELVETICA);
1011 _outputGroup->user_data((void *)gw_output);
1012 _out = new GraphicsWidget(x1, y1 + 1, x2, y2 -1);
1013 _runtime = new Runtime(x2, y2 - 1, DEF_FONT_SIZE);
1014 _outputGroup->resizable(_out);
1015 _outputGroup->end();
1016 _tabGroup->resizable(_outputGroup);
1017 _tabGroup->end();
1018
1019 end();
1020 resizable(_tabGroup);
1021}
1022
1023MainWindow::~MainWindow() {
1024 _profile->save(this);
1025 delete _profile;
1026 delete _runtime;
1027}
1028
1029Fl_Group *MainWindow::createTab(GroupWidgetEnum groupWidgetEnum, const char *label) {
1030 _tabGroup->begin();
1031 Fl_Group * result = new Fl_Group(_out->x(), _out->y(), _out->w(), _out->h(), label);
1032 result->box(FL_NO_BOX);
1033 result->labelfont(FL_HELVETICA);
1034 result->user_data((void *)groupWidgetEnum);
1035 result->begin();
1036 return result;
1037}
1038
1039/**
1040 * create a new help widget and add it to the tab group
1041 */
1042Fl_Group *MainWindow::createEditor(const char *title) {
1043 const char *slash = strrchr(title, '/');
1044 Fl_Group *editGroup = createTab(gw_editor, slash ? slash + 1 : title);
1045 editGroup->resizable(new EditorWidget(_out, _menuBar));
1046 editGroup->end();
1047 _tabGroup->add(editGroup);
1048 _tabGroup->value(editGroup);
1049 _tabGroup->end();
1050 return editGroup;
1051}
1052
1053void MainWindow::new_file(Fl_Widget *w, void *eventData) {
1054 EditorWidget *editWidget = 0;
1055 Fl_Group *untitledEditor = findTab(untitledFile);
1056 char path[PATH_MAX];
1057
1058 if (untitledEditor) {
1059 _tabGroup->value(untitledEditor);
1060 editWidget = getEditor(untitledEditor);
1061 }
1062 if (!editWidget) {
1063 editWidget = getEditor(createEditor(untitledFile));
1064
1065 // preserve the contents of any existing untitled.bas
1066 getHomeDir(path, sizeof(path));
1067 strcat(path, untitledFile);
1068 if (access(path, 0) == 0) {
1069 editWidget->loadFile(path);
1070 }
1071 }
1072
1073 editWidget->selectAll();
1074}
1075
1076void MainWindow::open_file(Fl_Widget *w, void *eventData) {
1077 FileWidget *fileWidget = NULL;
1078 Fl_Group *openFileGroup = findTab(gw_file);
1079 if (!openFileGroup) {
1080 openFileGroup = createTab(gw_file, fileTabName);
1081 fileWidget = new FileWidget(_out, _runtime->getFontSize());
1082 openFileGroup->resizable(fileWidget);
1083 openFileGroup->end();
1084 _tabGroup->end();
1085 } else {
1086 fileWidget = (FileWidget *)openFileGroup->resizable();
1087 }
1088
1089 // change to the directory of the current editor widget
1090 EditorWidget *editWidget = getEditor(false);
1091 char path[PATH_MAX];
1092
1093 if (editWidget) {
1094 FileWidget::splitPath(editWidget->getFilename(), path);
1095 } else {
1096 Fl_Group *group = (Fl_Group *)_tabGroup->value();
1097 GroupWidgetEnum gw = getGroupWidget(group);
1098 switch (gw) {
1099 case gw_output:
1100 strcpy(path, packageHome);
1101 break;
1102 case gw_help:
1103 getHomeDir(path, sizeof(path));
1104 break;
1105 default:
1106 path[0] = 0;
1107 }
1108 }
1109
1110 StringList *paths = new StringList();
1111 for (int i = 0; i < NUM_RECENT_ITEMS; i++) {
1112 char nextPath[PATH_MAX];
1113 FileWidget::splitPath(recentPath[i].c_str(), nextPath);
1114 if (nextPath[0] && !paths->contains(nextPath)) {
1115 paths->add(nextPath);
1116 }
1117 }
1118
1119 fileWidget->openPath(path, paths);
1120 _tabGroup->value(openFileGroup);
1121}
1122
1123void MainWindow::save_file_as(Fl_Widget *w, void *eventData) {
1124 EditorWidget *editWidget = getEditor();
1125 if (editWidget) {
1126 open_file(w, eventData);
1127 FileWidget *fileWidget = (FileWidget *)getSelectedTab()->resizable();
1128 fileWidget->fileOpen(editWidget);
1129 }
1130}
1131
1132HelpView *MainWindow::getHelp() {
1133 HelpView *help = 0;
1134 Fl_Group *helpGroup = findTab(gw_help);
1135 if (!helpGroup) {
1136 helpGroup = createTab(gw_help, helpTabName);
1137 help = new HelpView(_out, _runtime->getFontSize());
1138 help->callback(help_contents_anchor_cb);
1139 helpGroup->resizable(help);
1140 _profile->setHelpTheme(help);
1141 _tabGroup->end();
1142 } else {
1143 help = (HelpView *)helpGroup->resizable();
1144 }
1145 _tabGroup->value(helpGroup);
1146 return help;
1147}
1148
1149EditorWidget *MainWindow::getEditor(bool select) {
1150 EditorWidget *result = 0;
1151 if (select) {
1152 int n = _tabGroup->children();
1153 for (int c = 0; c < n; c++) {
1154 Fl_Group *group = (Fl_Group *)_tabGroup->child(c);
1155 if (gw_editor == getGroupWidget(group)) {
1156 result = (EditorWidget *)group->child(0);
1157 _tabGroup->value(group);
1158 break;
1159 }
1160 }
1161 } else {
1162 result = getEditor(getSelectedTab());
1163 }
1164 return result;
1165}
1166
1167EditorWidget *MainWindow::getEditor(Fl_Group *group) {
1168 EditorWidget *editWidget = 0;
1169 if (group != 0 && gw_editor == getGroupWidget(group)) {
1170 editWidget = (EditorWidget *)group->resizable();
1171 }
1172 return editWidget;
1173}
1174
1175EditorWidget *MainWindow::getEditor(const char *fullpath) {
1176 if (fullpath != 0 && fullpath[0] != 0) {
1177 int n = _tabGroup->children();
1178 for (int c = 0; c < n; c++) {
1179 Fl_Group *group = (Fl_Group *)_tabGroup->child(c);
1180 if (gw_editor == getGroupWidget(group)) {
1181 EditorWidget *editWidget = (EditorWidget *)group->child(0);
1182 const char *fileName = editWidget->getFilename();
1183 if (fileName && strcmp(fullpath, fileName) == 0) {
1184 return editWidget;
1185 }
1186 }
1187 }
1188 }
1189 return NULL;
1190}
1191
1192/**
1193 * called by FileWidget to open the selected file
1194 */
1195void MainWindow::editFile(const char *filePath) {
1196 EditorWidget *editWidget = getEditor(filePath);
1197 if (!editWidget) {
1198 editWidget = getEditor(createEditor(filePath));
1199 editWidget->loadFile(filePath);
1200 }
1201 showEditTab(editWidget);
1202}
1203
1204Fl_Group *MainWindow::getSelectedTab() {
1205 return (Fl_Group *)_tabGroup->value();
1206}
1207
1208/**
1209 * returns the tab with the given name
1210 */
1211Fl_Group *MainWindow::findTab(const char *label) {
1212 int n = _tabGroup->children();
1213 for (int c = 0; c < n; c++) {
1214 Fl_Group *child = (Fl_Group *)_tabGroup->child(c);
1215 if (strcmp(child->label(), label) == 0) {
1216 return child;
1217 }
1218 }
1219 return 0;
1220}
1221
1222Fl_Group *MainWindow::findTab(GroupWidgetEnum groupWidget) {
1223 int n = _tabGroup->children();
1224 for (int c = 0; c < n; c++) {
1225 Fl_Group *child = (Fl_Group *)_tabGroup->child(c);
1226 if (groupWidget == getGroupWidget(child)) {
1227 return child;
1228 }
1229 }
1230 return 0;
1231}
1232
1233/**
1234 * find and select the tab with the given tab label
1235 */
1236Fl_Group *MainWindow::selectTab(const char *label) {
1237 Fl_Group *tab = findTab(label);
1238 if (tab) {
1239 _tabGroup->value(tab);
1240 }
1241 return tab;
1242}
1243
1244/**
1245 * copies the configuration from the current editor to any remaining editors
1246 */
1247void MainWindow::updateConfig(EditorWidget *current) {
1248 int n = _tabGroup->children();
1249 for (int c = 0; c < n; c++) {
1250 Fl_Group *child = (Fl_Group *)_tabGroup->child(c);
1251 if (getGroupWidget(child) == gw_editor) {
1252 EditorWidget *editWidget = (EditorWidget *)child->child(0);
1253 if (editWidget != current) {
1254 editWidget->updateConfig(current);
1255 _profile->setEditTheme(editWidget);
1256 }
1257 }
1258 }
1259}
1260
1261/**
1262 * updates the names of the editor tabs based on the enclosed editing file
1263 */
1264void MainWindow::updateEditTabName(EditorWidget *editWidget) {
1265 int n = _tabGroup->children();
1266 for (int c = 0; c < n; c++) {
1267 Fl_Group *group = (Fl_Group *)_tabGroup->child(c);
1268 if (gw_editor == getGroupWidget(group) && editWidget == (EditorWidget *)group->child(0)) {
1269 const char *editFileName = editWidget->getFilename();
1270 if (editFileName && editFileName[0]) {
1271 const char *slash = strrchr(editFileName, '/');
1272 group->copy_label(slash ? slash + 1 : editFileName);
1273 }
1274 }
1275 }
1276}
1277
1278/**
1279 * returns the tab following the given tab
1280 */
1281Fl_Group *MainWindow::getNextTab(Fl_Group *current) {
1282 int n = _tabGroup->children();
1283 for (int c = 0; c < n - 1; c++) {
1284 Fl_Group *child = (Fl_Group *)_tabGroup->child(c);
1285 if (child == current) {
1286 return (Fl_Group *)_tabGroup->child(c + 1);
1287 }
1288 }
1289 return (Fl_Group *)_tabGroup->child(0);
1290}
1291
1292/**
1293 * returns the tab prior the given tab or null if not found
1294 */
1295Fl_Group *MainWindow::getPrevTab(Fl_Group *current) {
1296 int n = _tabGroup->children();
1297 for (int c = n - 1; c > 0; c--) {
1298 Fl_Group *child = (Fl_Group *)_tabGroup->child(c);
1299 if (child == current) {
1300 return (Fl_Group *)_tabGroup->child(c - 1);
1301 }
1302 }
1303 return (Fl_Group *)_tabGroup->child(n - 1);
1304}
1305
1306/**
1307 * Opens the config file ready for writing
1308 */
1309FILE *MainWindow::openConfig(const char *fileName, const char *flags) {
1310 char path[PATH_MAX];
1311 getHomeDir(path, sizeof(path));
1312 strcat(path, fileName);
1313 return fopen(path, flags);
1314}
1315
1316bool MainWindow::isBreakExec(void) {
1317 return (runMode == break_state || runMode == quit_state);
1318}
1319
1320bool MainWindow::isRunning(void) {
1321 return (runMode == run_state || runMode == modal_state);
1322}
1323
1324void MainWindow::setModal(bool modal) {
1325 runMode = modal ? modal_state : run_state;
1326}
1327
1328/**
1329 * sets the window title based on the filename
1330 */
1331void MainWindow::setTitle(Fl_Window *widget, const char *filename) {
1332 char title[PATH_MAX];
1333 const char *dot = strrchr(filename, '.');
1334 int len = (dot ? dot - filename : strlen(filename));
1335
1336 strncpy(title, filename, len);
1337 title[len] = 0;
1338 title[0] = toupper(title[0]);
1339 strcat(title, " - SmallBASIC");
1340 widget->copy_label(title);
1341}
1342
1343void MainWindow::setBreak() {
1344 _runtime->setExit(false);
1345 runMode = break_state;
1346}
1347
1348bool MainWindow::isModal() {
1349 return (runMode == modal_state);
1350}
1351
1352bool MainWindow::isEdit() {
1353 return (runMode == edit_state);
1354}
1355
1356bool MainWindow::isInteractive() {
1357 return opt_interactive;
1358}
1359
1360bool MainWindow::isIdeHidden() {
1361 return (opt_ide == IDE_NONE);
1362}
1363
1364void MainWindow::resize(int x, int y, int w, int h) {
1365 BaseWindow::resize(x, y, w, h);
1366 _runtime->resize(_out->w(), _out->h());
1367}
1368
1369void MainWindow::resizeDisplay(int x, int y, int w, int h) {
1370 // resize the graphics widget (output tab child)
1371 _out->resize(x, y, w, h);
1372
1373 // resize the runtime platforn
1374 _runtime->resize(w, h);
1375}
1376
1377void MainWindow::resizeTabs(int fontSize) {
1378 int n = _tabGroup->children();
1379 for (int c = 0; c < n; c++) {
1380 Fl_Group *group = (Fl_Group *)_tabGroup->child(c);
1381 GroupWidgetEnum gw = getGroupWidget(group);
1382 switch (gw) {
1383 case gw_editor:
1384 ((EditorWidget *)group->child(0))->setFontSize(fontSize);
1385 break;
1386 case gw_help:
1387 ((HelpWidget *)group->child(0))->setFontSize(fontSize);
1388 break;
1389 case gw_file:
1390 ((FileWidget *)group->child(0))->setFontSize(fontSize);
1391 break;
1392 default:
1393 break;
1394 }
1395 }
1396}
1397
1398/**
1399 * returns any active tty widget
1400 */
1401TtyWidget *MainWindow::tty() {
1402 TtyWidget *result = 0;
1403 EditorWidget *editor = _runEditWidget;
1404 if (!editor) {
1405 editor = getEditor(false);
1406 }
1407 if (editor) {
1408 result = editor->getTty();
1409 }
1410 return result;
1411}
1412
1413/**
1414 * returns whether printing to the tty widget is active
1415 */
1416bool MainWindow::logPrint() {
1417 return (_runEditWidget && _runEditWidget->getTty() && _runEditWidget->isLogPrint());
1418}
1419
1420int MainWindow::handle(int e) {
1421 int result;
1422 if (getSelectedTab() == _outputGroup && _runtime->handle(e)) {
1423 result = 1;
1424 } else {
1425 result = BaseWindow::handle(e);
1426 }
1427 return result;
1428}
1429
1430void MainWindow::loadHelp(const char *path) {
1431 if (!getHelp()->loadHelp(path) && getEditor() != NULL) {
1432 getEditor()->statusMsg("Failed to open help file");
1433 }
1434}
1435
1436/**
1437 * loads the desktop icon
1438 */
1439void MainWindow::loadIcon() {
1440 if (!icon()) {
1441#if defined(WIN32)
1442 HICON ico = (HICON) icon();
1443 if (!ico) {
1444 ico = (HICON) LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(101),
1445 IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR | LR_SHARED);
1446 if (!ico) {
1447 ico = LoadIcon(NULL, IDI_APPLICATION);
1448 }
1449 }
1450 icon((char *)ico);
1451#else
1452#include "icon.h"
1453 Fl_RGB_Image *image = new Fl_PNG_Image(NULL, sb_desktop_128x128_png, sb_desktop_128x128_png_len);
1454 icon(image);
1455#endif
1456 }
1457}
1458
1459int BaseWindow::handle(int e) {
1460 switch (runMode) {
1461 case run_state:
1462 case modal_state:
1463 switch (e) {
1464 case FL_FOCUS:
1465 // accept key events into handleKeyEvent
1466 return 1;
1467 case FL_PUSH:
1468 if (keymap_invoke(SB_KEY_MK_PUSH)) {
1469 return 1;
1470 }
1471 break;
1472 case FL_DRAG:
1473 if (keymap_invoke(SB_KEY_MK_DRAG)) {
1474 return 1;
1475 }
1476 break;
1477 case FL_MOVE:
1478 if (keymap_invoke(SB_KEY_MK_MOVE)) {
1479 return 1;
1480 }
1481 break;
1482 case FL_RELEASE:
1483 if (keymap_invoke(SB_KEY_MK_RELEASE)) {
1484 return 1;
1485 }
1486 break;
1487 case FL_MOUSEWHEEL:
1488 if (keymap_invoke(SB_KEY_MK_WHEEL)) {
1489 return 1;
1490 }
1491 break;
1492 case FL_SHORTCUT:
1493 case FL_KEYBOARD:
1494 if (handleKeyEvent()) {
1495 // no default processing by Window
1496 return 1;
1497 }
1498 break;
1499 }
1500 if (_mainSystem != NULL) {
1501 _mainSystem->handle(e);
1502 }
1503 break;
1504
1505 case edit_state:
1506 switch (e) {
1507 case FL_SHORTCUT:
1508 case FL_KEYBOARD:
1509 if (Fl::event_state(FL_CTRL)) {
1510 EditorWidget *editWidget = wnd->getEditor();
1511 if (editWidget) {
1512 if (Fl::event_key() == FL_F+1) {
1513 // FL_CTRL + F1 key for brief log mode help
1514 wnd->help_contents(0, (void *)true);
1515 return 1;
1516 }
1517 if (editWidget->focusWidget()) {
1518 return 1;
1519 }
1520 }
1521 }
1522 }
1523 break;
1524
1525 default:
1526 break;
1527 }
1528
1529 return Fl_Window::handle(e);
1530}
1531
1532bool BaseWindow::handleKeyEvent() {
1533 int k = Fl::event_key();
1534 bool key_pushed = true;
1535
1536 switch (k) {
1537 case FL_Tab:
1538 dev_pushkey(SB_KEY_TAB);
1539 break;
1540 case FL_Home:
1541 dev_pushkey(SB_KEY_KP_HOME);
1542 break;
1543 case FL_End:
1544 dev_pushkey(SB_KEY_END);
1545 break;
1546 case FL_Insert:
1547 dev_pushkey(SB_KEY_INSERT);
1548 break;
1549 case FL_Menu:
1550 dev_pushkey(SB_KEY_MENU);
1551 break;
1552 case FL_Multiply:
1553 dev_pushkey(SB_KEY_KP_MUL);
1554 break;
1555 case FL_AddKey:
1556 dev_pushkey(SB_KEY_KP_PLUS);
1557 break;
1558 case FL_SubtractKey:
1559 dev_pushkey(SB_KEY_KP_MINUS);
1560 break;
1561 case FL_DivideKey:
1562 dev_pushkey(SB_KEY_KP_DIV);
1563 break;
1564 case FL_F:
1565 dev_pushkey(SB_KEY_F(0));
1566 break;
1567 case FL_F+1:
1568 dev_pushkey(SB_KEY_F(1));
1569 break;
1570 case FL_F+2:
1571 dev_pushkey(SB_KEY_F(2));
1572 break;
1573 case FL_F+3:
1574 dev_pushkey(SB_KEY_F(3));
1575 break;
1576 case FL_F+4:
1577 dev_pushkey(SB_KEY_F(4));
1578 break;
1579 case FL_F+5:
1580 dev_pushkey(SB_KEY_F(5));
1581 break;
1582 case FL_F+6:
1583 dev_pushkey(SB_KEY_F(6));
1584 break;
1585 case FL_F+7:
1586 dev_pushkey(SB_KEY_F(7));
1587 break;
1588 case FL_F+8:
1589 dev_pushkey(SB_KEY_F(8));
1590 break;
1591 case FL_F+9:
1592 dev_pushkey(SB_KEY_F(9));
1593 break;
1594 case FL_F+10:
1595 dev_pushkey(SB_KEY_F(10));
1596 break;
1597 case FL_F+11:
1598 dev_pushkey(SB_KEY_F(11));
1599 break;
1600 case FL_F+12:
1601 dev_pushkey(SB_KEY_F(12));
1602 break;
1603 case FL_Page_Up:
1604 dev_pushkey(SB_KEY_PGUP);
1605 break;
1606 case FL_Page_Down:
1607 dev_pushkey(SB_KEY_PGDN);
1608 break;
1609 case FL_Up:
1610 dev_pushkey(SB_KEY_UP);
1611 break;
1612 case FL_Down:
1613 dev_pushkey(SB_KEY_DN);
1614 break;
1615 case FL_Left:
1616 dev_pushkey(SB_KEY_LEFT);
1617 break;
1618 case FL_Right:
1619 dev_pushkey(SB_KEY_RIGHT);
1620 break;
1621 case FL_BackSpace:
1622 dev_pushkey(SB_KEY_BACKSPACE);
1623 break;
1624 case FL_Delete:
1625 dev_pushkey(SB_KEY_DELETE);
1626 break;
1627 case FL_Enter:
1628 case FL_KP_Enter:
1629 dev_pushkey(13);
1630 break;
1631 case 'b':
1632 if (Fl::event_state(FL_CTRL)) {
1633 wnd->run_break();
1634 key_pushed = false;
1635 break;
1636 }
1637 dev_pushkey(Fl::event_text()[0]);
1638 break;
1639 case 'q':
1640 if (Fl::event_state(FL_CTRL)) {
1641 wnd->quit();
1642 key_pushed = false;
1643 break;
1644 }
1645 dev_pushkey(Fl::event_text()[0]);
1646 break;
1647
1648 default:
1649 if (k >= FL_Shift_L && k <= FL_Alt_R) {
1650 // avoid pushing meta-keys
1651 key_pushed = false;
1652 } else if (Fl::event_state(FL_CTRL & FL_ALT)) {
1653 dev_pushkey(SB_KEY_CTRL_ALT(k));
1654 } else if (Fl::event_state(FL_CTRL)) {
1655 dev_pushkey(SB_KEY_CTRL(k));
1656 } else if (Fl::event_state(FL_ALT)) {
1657 dev_pushkey(SB_KEY_ALT(k));
1658 } else {
1659 dev_pushkey(Fl::event_text()[0]);
1660 }
1661 break;
1662 }
1663 return key_pushed;
1664}
1665
1666