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 <stdint.h> |
11 | #include <unistd.h> |
12 | #include <errno.h> |
13 | #include <sys/stat.h> |
14 | #include <FL/fl_ask.H> |
15 | #include <FL/Fl_Color_Chooser.H> |
16 | #include <FL/Fl_Tile.H> |
17 | #include "platform/fltk/MainWindow.h" |
18 | #include "platform/fltk/EditorWidget.h" |
19 | #include "platform/fltk/FileWidget.h" |
20 | #include "common/smbas.h" |
21 | |
22 | // in MainWindow.cxx |
23 | extern String recentPath[]; |
24 | extern String recentLabel[]; |
25 | extern int []; |
26 | extern const char *historyFile; |
27 | extern const char *untitledFile; |
28 | |
29 | // in BasicEditor.cxx |
30 | extern Fl_Text_Display::Style_Table_Entry styletable[]; |
31 | extern Fl_Color defaultColor[]; |
32 | |
33 | int completionIndex = 0; |
34 | |
35 | static bool rename_active = false; |
36 | const char scanLabel[] = "(Refresh)" ; |
37 | |
38 | EditorWidget *get_editor() { |
39 | EditorWidget *result = wnd->getEditor(); |
40 | if (!result) { |
41 | result = wnd->getEditor(true); |
42 | } |
43 | return result; |
44 | } |
45 | |
46 | struct LineInput : public Fl_Input { |
47 | LineInput(int x, int y, int w, int h); |
48 | void resize(int x, int y, int w, int h); |
49 | int handle(int event); |
50 | |
51 | private: |
52 | int orig_x, orig_y, orig_w, orig_h; |
53 | }; |
54 | |
55 | //--EditorWidget---------------------------------------------------------------- |
56 | |
57 | EditorWidget::(Fl_Widget *rect, Fl_Menu_Bar *) : |
58 | Fl_Group(rect->x(), rect->y(), rect->w(), rect->h()), |
59 | _editor(NULL), |
60 | _tty(NULL), |
61 | _dirty(false), |
62 | _loading(false), |
63 | _modifiedTime(0), |
64 | _commandText(NULL), |
65 | _rowStatus(NULL), |
66 | _colStatus(NULL), |
67 | _runStatus(NULL), |
68 | _modStatus(NULL), |
69 | _funcList(NULL), |
70 | _funcListEvent(false), |
71 | _logPrintBn(NULL), |
72 | _lockBn(NULL), |
73 | _hideIdeBn(NULL), |
74 | _gotoLineBn(NULL), |
75 | _commandOpt(cmd_find), |
76 | _commandChoice(NULL), |
77 | _menuBar(menuBar) { |
78 | _filename[0] = 0; |
79 | box(FL_NO_BOX); |
80 | begin(); |
81 | |
82 | const int st_w = 40; |
83 | const int bn_w = 28; |
84 | const int st_h = STATUS_HEIGHT; |
85 | const int choice_w = 80; |
86 | const int tileHeight = rect->h() - st_h; |
87 | const int ttyHeight = tileHeight / 8; |
88 | const int browserWidth = rect->w() / 5; |
89 | const int editHeight = tileHeight - ttyHeight; |
90 | const int editWidth = rect->w() - browserWidth; |
91 | const int st_y = rect->y() + editHeight + ttyHeight; |
92 | |
93 | Fl_Group *tile = new Fl_Tile(rect->x(), rect->y(), rect->w(), tileHeight); |
94 | _editor = new BasicEditor(rect->x(), rect->y(), editWidth, editHeight, this); |
95 | _editor->linenumber_width(LINE_NUMBER_WIDTH); |
96 | _editor->wrap_mode(true, 0); |
97 | _editor->selection_color(fl_rgb_color(190, 189, 188)); |
98 | _editor->_textbuf->add_modify_callback(changed_cb, this); |
99 | _editor->box(FL_FLAT_BOX); |
100 | _editor->take_focus(); |
101 | |
102 | // sub-func jump droplist |
103 | _funcList = new Fl_Tree(_editor->w(), rect->y(), browserWidth, editHeight); |
104 | _funcList->labelfont(FL_HELVETICA); |
105 | _funcList->when(FL_WHEN_RELEASE); |
106 | _funcList->box(FL_FLAT_BOX); |
107 | |
108 | Fl_Tree_Item *scan = new Fl_Tree_Item(_funcList); |
109 | scan->label(scanLabel); |
110 | _funcList->showroot(0); |
111 | _funcList->add(scanLabel, scan); |
112 | |
113 | _tty = new TtyWidget(rect->x(), rect->y() + editHeight, rect->w(), ttyHeight, TTY_ROWS); |
114 | tile->end(); |
115 | |
116 | // editor status bar |
117 | _statusBar = new Fl_Group(rect->x(), st_y, rect->w(), st_h); |
118 | _logPrintBn = new Fl_Toggle_Button(rect->w() - bn_w, st_y + 1, bn_w, st_h - 2); |
119 | _lockBn = new Fl_Toggle_Button(_logPrintBn->x() - (bn_w + 2), st_y + 1, bn_w, st_h - 2); |
120 | _hideIdeBn = new Fl_Toggle_Button(_lockBn->x() - (bn_w + 2), st_y + 1, bn_w, st_h - 2); |
121 | _gotoLineBn = new Fl_Toggle_Button(_hideIdeBn->x() - (bn_w + 2), st_y + 1, bn_w, st_h - 2); |
122 | _colStatus = new Fl_Button(_gotoLineBn->x() - (st_w + 2), st_y, st_w, st_h); |
123 | _rowStatus = new Fl_Button(_colStatus->x() - (st_w + 2), st_y, st_w, st_h); |
124 | _runStatus = new Fl_Button(_rowStatus->x() - (st_w + 2), st_y, st_w, st_h); |
125 | _modStatus = new Fl_Button(_runStatus->x() - (st_w + 2), st_y, st_w, st_h); |
126 | _commandChoice = new Fl_Button(rect->x(), st_y, choice_w, st_h); |
127 | _commandText = new Fl_Input(rect->x() + choice_w + 2, st_y + 1, _modStatus->x() - choice_w - 4, st_h - 2); |
128 | _commandText->align(FL_ALIGN_LEFT | FL_ALIGN_CLIP); |
129 | _commandText->when(FL_WHEN_ENTER_KEY_ALWAYS); |
130 | _commandText->labelfont(FL_HELVETICA); |
131 | |
132 | for (int n = 0; n < _statusBar->children(); n++) { |
133 | Fl_Widget *w = _statusBar->child(n); |
134 | w->labelfont(FL_HELVETICA); |
135 | w->box(FL_NO_BOX); |
136 | } |
137 | _commandText->box(FL_THIN_DOWN_BOX); |
138 | _logPrintBn->box(FL_THIN_UP_BOX); |
139 | _lockBn->box(FL_THIN_UP_BOX); |
140 | _hideIdeBn->box(FL_THIN_UP_BOX); |
141 | _gotoLineBn->box(FL_THIN_UP_BOX); |
142 | |
143 | _statusBar->resizable(_commandText); |
144 | _statusBar->end(); |
145 | resizable(tile); |
146 | end(); |
147 | |
148 | // command selection |
149 | setCommand(cmd_find); |
150 | runState(rs_ready); |
151 | setModified(false); |
152 | |
153 | // button callbacks |
154 | _lockBn->callback(scroll_lock_cb); |
155 | _modStatus->callback(save_file_cb); |
156 | _runStatus->callback(MainWindow::run_cb); |
157 | _commandChoice->callback(command_cb, (void *)1); |
158 | _commandText->callback(command_cb, (void *)1); |
159 | _funcList->callback(func_list_cb, 0); |
160 | _logPrintBn->callback(un_select_cb, (void *)_hideIdeBn); |
161 | _hideIdeBn->callback(un_select_cb, (void *)_logPrintBn); |
162 | _colStatus->callback(goto_line_cb, 0); |
163 | _rowStatus->callback(goto_line_cb, 0); |
164 | |
165 | // setup icons |
166 | _gotoLineBn->label("B" ); // right arrow (goto) |
167 | _hideIdeBn->label("W" ); // large dot |
168 | _lockBn->label("J" ); // vertical bars |
169 | _logPrintBn->label("T" ); // italic bold T |
170 | |
171 | // setup tooltips |
172 | _commandText->tooltip("Press Ctrl+f or Ctrl+Shift+f to find again" ); |
173 | _rowStatus->tooltip("Cursor row position" ); |
174 | _colStatus->tooltip("Cursor column position" ); |
175 | _runStatus->tooltip("Run or BREAK" ); |
176 | _modStatus->tooltip("Save file" ); |
177 | _logPrintBn->tooltip("Display PRINT statements in the log window" ); |
178 | _lockBn->tooltip("Prevent log window auto-scrolling" ); |
179 | _hideIdeBn->tooltip("Hide the editor while program is running" ); |
180 | _gotoLineBn->tooltip("Position the cursor to the last program line after BREAK" ); |
181 | statusMsg(SB_STR_VER); |
182 | |
183 | // setup defaults or restore settings |
184 | if (wnd && wnd->_profile) { |
185 | wnd->_profile->loadConfig(this); |
186 | } |
187 | take_focus(); |
188 | } |
189 | |
190 | EditorWidget::~EditorWidget() { |
191 | delete _editor; |
192 | } |
193 | |
194 | //--Event handler methods------------------------------------------------------- |
195 | |
196 | /** |
197 | * change the selected text to upper/lower/camel case |
198 | */ |
199 | void EditorWidget::change_case(Fl_Widget *w, void *eventData) { |
200 | Fl_Text_Buffer *tb = _editor->_textbuf; |
201 | int start, end; |
202 | char *selection = getSelection(&start, &end); |
203 | int len = strlen(selection); |
204 | enum { up, down, mixed } curcase = isupper(selection[0]) ? up : down; |
205 | |
206 | for (int i = 1; i < len; i++) { |
207 | if (isalpha(selection[i])) { |
208 | bool isup = isupper(selection[i]); |
209 | if ((curcase == up && isup == false) || (curcase == down && isup)) { |
210 | curcase = mixed; |
211 | break; |
212 | } |
213 | } |
214 | } |
215 | |
216 | // transform pattern: Foo -> FOO, FOO -> foo, foo -> Foo |
217 | for (int i = 0; i < len; i++) { |
218 | selection[i] = curcase == mixed ? toupper(selection[i]) : tolower(selection[i]); |
219 | } |
220 | if (curcase == down) { |
221 | selection[0] = toupper(selection[0]); |
222 | // upcase chars following non-alpha chars |
223 | for (int i = 1; i < len; i++) { |
224 | if (isalpha(selection[i]) == false && i + 1 < len) { |
225 | selection[i + 1] = toupper(selection[i + 1]); |
226 | } |
227 | } |
228 | } |
229 | |
230 | if (selection[0]) { |
231 | tb->replace_selection(selection); |
232 | tb->select(start, end); |
233 | } |
234 | free((void *)selection); |
235 | } |
236 | |
237 | /** |
238 | * command handler |
239 | */ |
240 | void EditorWidget::command_opt(Fl_Widget *w, void *eventData) { |
241 | setCommand((CommandOpt) (intptr_t) eventData); |
242 | } |
243 | |
244 | /** |
245 | * cut selected text to the clipboard |
246 | */ |
247 | void EditorWidget::cut_text(Fl_Widget *w, void *eventData) { |
248 | Fl_Text_Editor::kf_cut(0, _editor); |
249 | } |
250 | |
251 | /** |
252 | * delete selected text |
253 | */ |
254 | void EditorWidget::do_delete(Fl_Widget *w, void *eventData) { |
255 | _editor->_textbuf->remove_selection(); |
256 | } |
257 | |
258 | /** |
259 | * perform keyword completion |
260 | */ |
261 | void EditorWidget::expand_word(Fl_Widget *w, void *eventData) { |
262 | int start, end; |
263 | const char *fullWord = 0; |
264 | unsigned fullWordLen = 0; |
265 | |
266 | Fl_Text_Buffer *textbuf = _editor->_textbuf; |
267 | char *text = textbuf->text(); |
268 | |
269 | if (textbuf->selected()) { |
270 | // get word before selection |
271 | int pos1, pos2; |
272 | textbuf->selection_position(&pos1, &pos2); |
273 | start = textbuf->word_start(pos1 - 1); |
274 | end = pos1; |
275 | // get word from before selection to end of selection |
276 | fullWord = text + start; |
277 | fullWordLen = pos2 - start - 1; |
278 | } else { |
279 | // nothing selected - get word to left of cursor position |
280 | int pos = _editor->insert_position(); |
281 | end = textbuf->word_end(pos); |
282 | start = textbuf->word_start(end - 1); |
283 | completionIndex = 0; |
284 | } |
285 | |
286 | if (start >= end) { |
287 | free(text); |
288 | return; |
289 | } |
290 | |
291 | const char *expandWord = text + start; |
292 | unsigned expandWordLen = end - start; |
293 | int wordPos = 0; |
294 | |
295 | // scan for expandWord from within the current text buffer |
296 | if (completionIndex != -1 && searchBackward(text, start - 1, |
297 | expandWord, expandWordLen, |
298 | &wordPos)) { |
299 | int matchPos = -1; |
300 | if (textbuf->selected() == 0) { |
301 | matchPos = wordPos; |
302 | completionIndex = 1; // find next word on next call |
303 | } else { |
304 | // find the next word prior to the currently selected word |
305 | int index = 1; |
306 | while (wordPos > 0) { |
307 | if (strncasecmp(text + wordPos, fullWord, fullWordLen) != 0 || |
308 | isalpha(text[wordPos + fullWordLen + 1])) { |
309 | // isalpha - matches fullWord but word has more chars |
310 | matchPos = wordPos; |
311 | if (completionIndex == index) { |
312 | completionIndex++; |
313 | break; |
314 | } |
315 | // count index for non-matching fullWords only |
316 | index++; |
317 | } |
318 | |
319 | if (searchBackward(text, wordPos - 1, expandWord, |
320 | expandWordLen, &wordPos) == 0) { |
321 | matchPos = -1; |
322 | break; // no more partial matches |
323 | } |
324 | } |
325 | if (index == completionIndex) { |
326 | // end of expansion sequence |
327 | matchPos = -1; |
328 | } |
329 | } |
330 | if (matchPos != -1) { |
331 | char *word = textbuf->text_range(matchPos, textbuf->word_end(matchPos)); |
332 | if (textbuf->selected()) { |
333 | textbuf->replace_selection(word + expandWordLen); |
334 | } else { |
335 | textbuf->insert(end, word + expandWordLen); |
336 | } |
337 | textbuf->select(end, end + strlen(word + expandWordLen)); |
338 | _editor->insert_position(end + strlen(word + expandWordLen)); |
339 | free((void *)word); |
340 | free(text); |
341 | return; |
342 | } |
343 | } |
344 | |
345 | completionIndex = -1; // no more buffer expansions |
346 | |
347 | strlib::List<String *> keywords; |
348 | _editor->getKeywords(keywords); |
349 | |
350 | // find the next replacement |
351 | int firstIndex = -1; |
352 | int lastIndex = -1; |
353 | int curIndex = -1; |
354 | int numWords = keywords.size(); |
355 | for (int i = 0; i < numWords; i++) { |
356 | const char *keyword = ((String *)keywords.get(i))->c_str(); |
357 | if (strncasecmp(expandWord, keyword, expandWordLen) == 0) { |
358 | if (firstIndex == -1) { |
359 | firstIndex = i; |
360 | } |
361 | if (fullWordLen == 0) { |
362 | if (expandWordLen == strlen(keyword)) { |
363 | // nothing selected and word to left of cursor matches |
364 | curIndex = i; |
365 | } |
366 | } else if (strncasecmp(fullWord, keyword, fullWordLen) == 0) { |
367 | // selection+word to left of selection matches |
368 | curIndex = i; |
369 | } |
370 | lastIndex = i; |
371 | } else if (lastIndex != -1) { |
372 | // moved beyond matching words |
373 | break; |
374 | } |
375 | } |
376 | |
377 | if (lastIndex != -1) { |
378 | if (lastIndex == curIndex || curIndex == -1) { |
379 | lastIndex = firstIndex; // wrap to first in subset |
380 | } else { |
381 | lastIndex = curIndex + 1; |
382 | } |
383 | |
384 | const char *keyword = ((String *)keywords.get(lastIndex))->c_str(); |
385 | // updated the segment of the replacement text |
386 | // that completes the current selection |
387 | if (textbuf->selected()) { |
388 | textbuf->replace_selection(keyword + expandWordLen); |
389 | } else { |
390 | textbuf->insert(end, keyword + expandWordLen); |
391 | } |
392 | textbuf->select(end, end + strlen(keyword + expandWordLen)); |
393 | } |
394 | free(text); |
395 | } |
396 | |
397 | /** |
398 | * handler for find text command |
399 | */ |
400 | void EditorWidget::find(Fl_Widget *w, void *eventData) { |
401 | setCommand(cmd_find); |
402 | } |
403 | |
404 | /** |
405 | * clears the console |
406 | */ |
407 | void EditorWidget::clear_console(Fl_Widget *w, void *eventData) { |
408 | _tty->clearScreen(); |
409 | } |
410 | |
411 | /** |
412 | * performs the current command |
413 | */ |
414 | void EditorWidget::command(Fl_Widget *w, void *eventData) { |
415 | bool found = false; |
416 | bool forward = (intptr_t) eventData; |
417 | bool updatePos = (_commandOpt != cmd_find_inc); |
418 | |
419 | if (Fl::event_button() == FL_RIGHT_MOUSE) { |
420 | // right click |
421 | forward = 0; |
422 | } |
423 | |
424 | switch (_commandOpt) { |
425 | case cmd_find_inc: |
426 | case cmd_find: |
427 | found = _editor->findText(_commandText->value(), forward, updatePos); |
428 | _commandText->textcolor(found ? _commandChoice->color() : FL_RED); |
429 | _commandText->redraw(); |
430 | break; |
431 | |
432 | case cmd_replace: |
433 | _commandBuffer.clear(); |
434 | _commandBuffer.append(_commandText->value()); |
435 | setCommand(cmd_replace_with); |
436 | break; |
437 | |
438 | case cmd_replace_with: |
439 | replace_next(); |
440 | break; |
441 | |
442 | case cmd_goto: |
443 | gotoLine(atoi(_commandText->value())); |
444 | take_focus(); |
445 | break; |
446 | |
447 | case cmd_input_text: |
448 | wnd->setModal(false); |
449 | break; |
450 | } |
451 | } |
452 | |
453 | /** |
454 | * sub/func selection list handler |
455 | */ |
456 | void EditorWidget::func_list(Fl_Widget *w, void *eventData) { |
457 | if (_funcList && _funcList->callback_item()) { |
458 | Fl_Tree_Item *item = (Fl_Tree_Item*)_funcList->callback_item(); |
459 | const char *label = item->label(); |
460 | if (label) { |
461 | _funcListEvent = true; |
462 | if (strcmp(label, scanLabel) == 0) { |
463 | resetList(); |
464 | } else { |
465 | gotoLine((int)(intptr_t)item->user_data()); |
466 | take_focus(); |
467 | } |
468 | } |
469 | } |
470 | } |
471 | |
472 | /** |
473 | * goto-line command handler |
474 | */ |
475 | void EditorWidget::goto_line(Fl_Widget *w, void *eventData) { |
476 | setCommand(cmd_goto); |
477 | } |
478 | |
479 | /** |
480 | * paste clipboard text onto the buffer |
481 | */ |
482 | void EditorWidget::paste_text(Fl_Widget *w, void *eventData) { |
483 | Fl_Text_Editor::kf_paste(0, _editor); |
484 | } |
485 | |
486 | /** |
487 | * rename the currently selected variable |
488 | */ |
489 | void EditorWidget::rename_word(Fl_Widget *w, void *eventData) { |
490 | if (rename_active) { |
491 | rename_active = false; |
492 | } else { |
493 | Fl_Rect rc; |
494 | char *selection = getSelection(&rc); |
495 | if (selection) { |
496 | showFindText(selection); |
497 | begin(); |
498 | LineInput *in = new LineInput(rc.x(), rc.y(), rc.w() + 10, rc.h()); |
499 | end(); |
500 | |
501 | in->value(selection); |
502 | in->callback(rename_word_cb); |
503 | in->textfont(FL_COURIER); |
504 | in->textsize(getFontSize()); |
505 | |
506 | rename_active = true; |
507 | while (rename_active && in == Fl::focus()) { |
508 | Fl::wait(); |
509 | } |
510 | |
511 | showFindText("" ); |
512 | replaceAll(selection, in->value(), true, true); |
513 | remove(in); |
514 | take_focus(); |
515 | delete in; |
516 | free((void *)selection); |
517 | } |
518 | } |
519 | } |
520 | |
521 | /** |
522 | * replace the next find occurance |
523 | */ |
524 | void EditorWidget::replace_next(Fl_Widget *w, void *eventData) { |
525 | if (readonly()) { |
526 | return; |
527 | } |
528 | |
529 | const char *find = _commandBuffer; |
530 | const char *replace = _commandText->value(); |
531 | |
532 | Fl_Text_Buffer *textbuf = _editor->_textbuf; |
533 | int pos = _editor->insert_position(); |
534 | int found = textbuf->search_forward(pos, find, &pos); |
535 | |
536 | if (found) { |
537 | // found a match; update the position and replace text |
538 | textbuf->select(pos, pos + strlen(find)); |
539 | textbuf->remove_selection(); |
540 | textbuf->insert(pos, replace); |
541 | textbuf->select(pos, pos + strlen(replace)); |
542 | _editor->insert_position(pos + strlen(replace)); |
543 | _editor->show_insert_position(); |
544 | } else { |
545 | setCommand(cmd_find); |
546 | _editor->take_focus(); |
547 | } |
548 | } |
549 | |
550 | /** |
551 | * save file menu command handler |
552 | */ |
553 | void EditorWidget::save_file(Fl_Widget *w, void *eventData) { |
554 | if (_filename[0] == '\0') { |
555 | // no filename - get one! |
556 | wnd->save_file_as(); |
557 | return; |
558 | } else { |
559 | doSaveFile(_filename); |
560 | } |
561 | } |
562 | |
563 | /** |
564 | * prevent the tty window from scrolling with new data |
565 | */ |
566 | void EditorWidget::scroll_lock(Fl_Widget *w, void *eventData) { |
567 | _tty->setScrollLock(_lockBn->value()); |
568 | } |
569 | |
570 | /** |
571 | * select all text |
572 | */ |
573 | void EditorWidget::select_all(Fl_Widget *w, void *eventData) { |
574 | Fl_Text_Editor::kf_select_all(0, _editor); |
575 | } |
576 | |
577 | /** |
578 | * set colour menu command handler |
579 | */ |
580 | void EditorWidget::set_color(Fl_Widget *w, void *eventData) { |
581 | StyleField field = (StyleField)(intptr_t)eventData; |
582 | uint8_t r, g, b; |
583 | Fl::get_color(styletable[field].color, r, g, b); |
584 | if (fl_color_chooser(w->label(), r, g, b)) { |
585 | styletable[field].color = fl_rgb_color(r, g, b); |
586 | _editor->styleChanged(); |
587 | wnd->_profile->updateTheme(); |
588 | wnd->_profile->setEditTheme(this); |
589 | wnd->updateConfig(this); |
590 | wnd->show(); |
591 | } |
592 | } |
593 | |
594 | /** |
595 | * replace text menu command handler |
596 | */ |
597 | void EditorWidget::show_replace(Fl_Widget *w, void *eventData) { |
598 | const char *prime = _editor->_search; |
599 | if (!prime || !prime[0]) { |
600 | // use selected text when search not available |
601 | prime = _editor->_textbuf->selection_text(); |
602 | } |
603 | _commandText->value(prime); |
604 | setCommand(cmd_replace); |
605 | } |
606 | |
607 | /** |
608 | * undo any edit changes |
609 | */ |
610 | void EditorWidget::undo(Fl_Widget *w, void *eventData) { |
611 | Fl_Text_Editor::kf_undo(0, _editor); |
612 | } |
613 | |
614 | /** |
615 | * de-select the button specified in the eventData |
616 | */ |
617 | void EditorWidget::un_select(Fl_Widget *w, void *eventData) { |
618 | ((Fl_Button *)eventData)->value(false); |
619 | } |
620 | |
621 | //--Public methods-------------------------------------------------------------- |
622 | |
623 | /** |
624 | * handles saving the current buffer |
625 | */ |
626 | bool EditorWidget::checkSave(bool discard) { |
627 | if (!_dirty) { |
628 | // continue next operation |
629 | return true; |
630 | } |
631 | |
632 | const char *msg = "The current file has not been saved\n\nWould you like to save it now?" ; |
633 | int r; |
634 | if (discard) { |
635 | r = fl_choice(msg, "Save" , "Discard" , "Cancel" , NULL); |
636 | } else { |
637 | r = fl_choice(msg, "Save" , "Cancel" , NULL, NULL); |
638 | } |
639 | if (r == 0) { |
640 | // save selected |
641 | save_file(); |
642 | return !_dirty; |
643 | } |
644 | |
645 | // continue operation when discard selected |
646 | return (discard && r == 1); |
647 | } |
648 | |
649 | /** |
650 | * copy selection text to the clipboard |
651 | */ |
652 | void EditorWidget::copyText() { |
653 | if (!_tty->copySelection()) { |
654 | Fl_Text_Editor::kf_copy(0, _editor); |
655 | } |
656 | } |
657 | |
658 | /** |
659 | * saves the editor buffer to the given file name |
660 | */ |
661 | void EditorWidget::doSaveFile(const char *newfile, bool force) { |
662 | if (!force && !_dirty && strcmp(newfile, _filename) == 0) { |
663 | // neither buffer or filename have changed |
664 | return; |
665 | } |
666 | |
667 | char basfile[PATH_MAX]; |
668 | Fl_Text_Buffer *textbuf = _editor->_textbuf; |
669 | |
670 | if (wnd->_profile->createBackups() && access(newfile, 0) == 0) { |
671 | // rename any existing file as a backup |
672 | strcpy(basfile, newfile); |
673 | strcat(basfile, "~" ); |
674 | rename(newfile, basfile); |
675 | } |
676 | |
677 | strcpy(basfile, newfile); |
678 | if (strchr(basfile, '.') == 0) { |
679 | strcat(basfile, ".bas" ); |
680 | } |
681 | |
682 | if (textbuf->savefile(basfile)) { |
683 | fl_alert("Error writing to file \'%s\':\n%s." , basfile, strerror(errno)); |
684 | return; |
685 | } |
686 | |
687 | _dirty = 0; |
688 | strcpy(_filename, basfile); |
689 | _modifiedTime = getModifiedTime(); |
690 | |
691 | wnd->updateEditTabName(this); |
692 | wnd->showEditTab(this); |
693 | |
694 | // store a copy in lastedit.bas |
695 | if (wnd->_profile->createBackups()) { |
696 | getHomeDir(basfile, sizeof(basfile)); |
697 | strlcat(basfile, "lastedit.bas" , sizeof(basfile)); |
698 | textbuf->savefile(basfile); |
699 | } |
700 | |
701 | textbuf->call_modify_callbacks(); |
702 | showPath(); |
703 | fileChanged(true); |
704 | addHistory(_filename); |
705 | _editor->take_focus(); |
706 | } |
707 | |
708 | /** |
709 | * called when the buffer has changed |
710 | */ |
711 | void EditorWidget::fileChanged(bool loadfile) { |
712 | _funcList->clear(); |
713 | if (loadfile) { |
714 | // update the func/sub navigator |
715 | createFuncList(); |
716 | _funcList->redraw(); |
717 | |
718 | const char *filename = getFilename(); |
719 | if (filename && filename[0]) { |
720 | // update the last used file menu |
721 | bool found = false; |
722 | |
723 | for (int i = 0; i < NUM_RECENT_ITEMS; i++) { |
724 | if (recentPath[i].c_str() != NULL && |
725 | strcmp(filename, recentPath[i].c_str()) == 0) { |
726 | found = true; |
727 | break; |
728 | } |
729 | } |
730 | |
731 | if (found == false) { |
732 | const char *label = FileWidget::splitPath(filename, NULL); |
733 | |
734 | // shift items downwards |
735 | for (int i = NUM_RECENT_ITEMS - 1; i > 0; i--) { |
736 | _menuBar->replace(recentMenu[i], recentLabel[i - 1]); |
737 | recentLabel[i].clear(); |
738 | recentLabel[i].append(recentLabel[i - 1]); |
739 | recentPath[i].clear(); |
740 | recentPath[i].append(recentPath[i - 1]); |
741 | } |
742 | // create new item in first position |
743 | recentLabel[0].clear(); |
744 | recentLabel[0].append(label); |
745 | recentPath[0].clear(); |
746 | recentPath[0].append(filename); |
747 | _menuBar->replace(recentMenu[0], recentLabel[0]); |
748 | } |
749 | } |
750 | } |
751 | |
752 | _funcList->add(scanLabel); |
753 | } |
754 | |
755 | /** |
756 | * keyboard shortcut handler |
757 | */ |
758 | bool EditorWidget::focusWidget() { |
759 | switch (Fl::event_key()) { |
760 | case 'b': |
761 | setBreakToLine(!isBreakToLine()); |
762 | return true; |
763 | |
764 | case 'e': |
765 | setCommand(cmd_find); |
766 | take_focus(); |
767 | return true; |
768 | |
769 | case 'f': |
770 | if (strlen(_commandText->value()) > 0 && _commandOpt == cmd_find) { |
771 | // continue search - shift -> backward else forward |
772 | command(0, (void *)(intptr_t)(Fl::event_state(FL_SHIFT) ? 0 : 1)); |
773 | } |
774 | setCommand(cmd_find); |
775 | return true; |
776 | |
777 | case 'i': |
778 | setCommand(cmd_find_inc); |
779 | return true; |
780 | |
781 | case 't': |
782 | setLogPrint(!isLogPrint()); |
783 | return true; |
784 | |
785 | case 'w': |
786 | setHideIde(!isHideIDE()); |
787 | return true; |
788 | |
789 | case 'j': |
790 | setScrollLock(!isScrollLock()); |
791 | return true; |
792 | } |
793 | return false; |
794 | } |
795 | |
796 | /** |
797 | * returns the current font size |
798 | */ |
799 | int EditorWidget::getFontSize() { |
800 | return _editor->getFontSize(); |
801 | } |
802 | |
803 | /** |
804 | * use the input control as the INPUT basic command handler |
805 | */ |
806 | void EditorWidget::getInput(char *result, int size) { |
807 | if (result && result[0]) { |
808 | _commandText->value(result); |
809 | } |
810 | setCommand(cmd_input_text); |
811 | wnd->setModal(true); |
812 | while (wnd->isModal()) { |
813 | Fl::wait(); |
814 | } |
815 | if (wnd->isBreakExec()) { |
816 | brun_break(); |
817 | } else { |
818 | strlcpy(result, _commandText->value(), size); |
819 | } |
820 | setCommand(cmd_find); |
821 | } |
822 | |
823 | /** |
824 | * returns the row and col position for the current cursor position |
825 | */ |
826 | void EditorWidget::getRowCol(int *row, int *col) { |
827 | return ((BasicEditor *)_editor)->getRowCol(row, col); |
828 | } |
829 | |
830 | /** |
831 | * returns the selected text or the word around the cursor if there |
832 | * is no current selection. caller must free the returned value |
833 | */ |
834 | char *EditorWidget::getSelection(int *start, int *end) { |
835 | char *result = 0; |
836 | |
837 | Fl_Text_Buffer *tb = _editor->_textbuf; |
838 | if (tb->selected()) { |
839 | result = tb->selection_text(); |
840 | tb->selection_position(start, end); |
841 | } else { |
842 | int pos = _editor->insert_position(); |
843 | *start = tb->word_start(pos); |
844 | *end = tb->word_end(pos); |
845 | result = tb->text_range(*start, *end); |
846 | } |
847 | |
848 | return result; |
849 | } |
850 | |
851 | /** |
852 | * returns where text selection ends |
853 | */ |
854 | void EditorWidget::getSelEndRowCol(int *row, int *col) { |
855 | return ((BasicEditor *)_editor)->getSelEndRowCol(row, col); |
856 | } |
857 | |
858 | /** |
859 | * returns where text selection starts |
860 | */ |
861 | void EditorWidget::getSelStartRowCol(int *row, int *col) { |
862 | return ((BasicEditor *)_editor)->getSelStartRowCol(row, col); |
863 | } |
864 | |
865 | /** |
866 | * sets the cursor to the given line number |
867 | */ |
868 | void EditorWidget::gotoLine(int line) { |
869 | ((BasicEditor *)_editor)->gotoLine(line); |
870 | } |
871 | |
872 | /** |
873 | * FLTK event handler |
874 | */ |
875 | int EditorWidget::handle(int e) { |
876 | switch (e) { |
877 | case FL_SHOW: |
878 | case FL_FOCUS: |
879 | Fl::focus(_editor); |
880 | handleFileChange(); |
881 | return 1; |
882 | case FL_KEYBOARD: |
883 | if (Fl::event_key() == FL_Escape) { |
884 | take_focus(); |
885 | return 1; |
886 | } |
887 | break; |
888 | case FL_ENTER: |
889 | if (rename_active) { |
890 | // prevent drawing over the inplace editor child control |
891 | return 0; |
892 | } |
893 | } |
894 | |
895 | return Fl_Group::handle(e); |
896 | } |
897 | |
898 | /** |
899 | * load the given filename into the buffer |
900 | */ |
901 | void EditorWidget::loadFile(const char *newfile) { |
902 | // save the current filename |
903 | char oldpath[PATH_MAX]; |
904 | strcpy(oldpath, _filename); |
905 | |
906 | // convert relative path to full path |
907 | getcwd(_filename, sizeof(_filename)); |
908 | strcat(_filename, "/" ); |
909 | strcat(_filename, newfile); |
910 | |
911 | if (access(_filename, R_OK) != 0) { |
912 | // filename unreadable, try newfile |
913 | strcpy(_filename, newfile); |
914 | } |
915 | |
916 | FileWidget::forwardSlash(_filename); |
917 | |
918 | _loading = true; |
919 | if (_editor->_textbuf->loadfile(_filename)) { |
920 | // read failed |
921 | fl_alert("Error reading from file \'%s\':\n%s." , _filename, strerror(errno)); |
922 | |
923 | // restore previous file |
924 | strcpy(_filename, oldpath); |
925 | _editor->_textbuf->loadfile(_filename); |
926 | } |
927 | |
928 | _dirty = false; |
929 | _loading = false; |
930 | |
931 | _editor->_textbuf->call_modify_callbacks(); |
932 | _editor->show_insert_position(); |
933 | _modifiedTime = getModifiedTime(); |
934 | readonly(false); |
935 | |
936 | wnd->updateEditTabName(this); |
937 | wnd->showEditTab(this); |
938 | |
939 | showPath(); |
940 | fileChanged(true); |
941 | setRowCol(1, 1); |
942 | } |
943 | |
944 | /** |
945 | * returns the buffer readonly flag |
946 | */ |
947 | bool EditorWidget::readonly() { |
948 | return ((BasicEditor *)_editor)->_readonly; |
949 | } |
950 | |
951 | /** |
952 | * sets the buffer readonly flag |
953 | */ |
954 | void EditorWidget::readonly(bool is_readonly) { |
955 | if (!is_readonly && access(_filename, W_OK) != 0) { |
956 | // cannot set writable since file is readonly |
957 | is_readonly = true; |
958 | } |
959 | _modStatus->label(is_readonly ? "RO" : "@line" ); |
960 | _modStatus->redraw(); |
961 | _editor->cursor_style(is_readonly ? Fl_Text_Display::DIM_CURSOR : Fl_Text_Display::NORMAL_CURSOR); |
962 | ((BasicEditor *)_editor)->_readonly = is_readonly; |
963 | } |
964 | |
965 | /** |
966 | * displays the current run-mode flag |
967 | */ |
968 | void EditorWidget::runState(RunMessage runMessage) { |
969 | _runStatus->callback(MainWindow::run_cb); |
970 | const char *msg = 0; |
971 | switch (runMessage) { |
972 | case rs_err: |
973 | msg = "ERR" ; |
974 | break; |
975 | case rs_run: |
976 | msg = "BRK" ; |
977 | _runStatus->callback(MainWindow::run_break_cb); |
978 | break; |
979 | default: |
980 | msg = "RUN" ; |
981 | } |
982 | _runStatus->copy_label(msg); |
983 | _runStatus->redraw(); |
984 | } |
985 | |
986 | /** |
987 | * Saves the selected text to the given file path |
988 | */ |
989 | void EditorWidget::saveSelection(const char *path) { |
990 | FILE *fp = fopen(path, "w" ); |
991 | if (fp) { |
992 | Fl_Rect rc; |
993 | char *selection = getSelection(&rc); |
994 | if (selection) { |
995 | fwrite(selection, strlen(selection), 1, fp); |
996 | free((void *)selection); |
997 | } else { |
998 | // save as an empty file |
999 | fputc(0, fp); |
1000 | } |
1001 | fclose(fp); |
1002 | } |
1003 | } |
1004 | |
1005 | /** |
1006 | * Sets the editor and editor toolbar color from the selected theme |
1007 | */ |
1008 | void EditorWidget::setTheme(EditTheme *theme) { |
1009 | _editor->color(get_color(theme->_background)); |
1010 | _editor->linenumber_bgcolor(get_color(theme->_background)); |
1011 | _editor->linenumber_fgcolor(get_color(theme->_number_color)); |
1012 | _editor->cursor_color(get_color(theme->_cursor_color)); |
1013 | _editor->selection_color(get_color(theme->_selection_background)); |
1014 | _funcList->color(fl_color_average(get_color(theme->_background), get_color(theme->_color), .92f)); |
1015 | _funcList->item_labelfgcolor(get_color(theme->_color)); |
1016 | _tty->color(_editor->color()); |
1017 | _tty->labelcolor(fl_contrast(_tty->color(), get_color(theme->_background))); |
1018 | _tty->selection_color(_editor->selection_color()); |
1019 | resetList(); |
1020 | } |
1021 | |
1022 | /** |
1023 | * sets the current display font |
1024 | */ |
1025 | void EditorWidget::setFont(Fl_Font font) { |
1026 | if (font) { |
1027 | _editor->setFont(font); |
1028 | _tty->setFont(font); |
1029 | wnd->_profile->setFont(font); |
1030 | } |
1031 | } |
1032 | |
1033 | /** |
1034 | * sets the current font size |
1035 | */ |
1036 | void EditorWidget::setFontSize(int size) { |
1037 | _editor->setFontSize(size); |
1038 | _tty->setFontSize(size); |
1039 | wnd->_profile->setFontSize(size); |
1040 | } |
1041 | |
1042 | /** |
1043 | * sets the indent level to the given amount |
1044 | */ |
1045 | void EditorWidget::setIndentLevel(int level) { |
1046 | ((BasicEditor *)_editor)->_indentLevel = level; |
1047 | } |
1048 | |
1049 | /** |
1050 | * displays the row/col in the editor toolbar |
1051 | */ |
1052 | void EditorWidget::setRowCol(int row, int col) { |
1053 | char rowcol[20]; |
1054 | sprintf(rowcol, "%d" , row); |
1055 | _rowStatus->copy_label(rowcol); |
1056 | _rowStatus->redraw(); |
1057 | sprintf(rowcol, "%d" , col); |
1058 | _colStatus->copy_label(rowcol); |
1059 | _colStatus->redraw(); |
1060 | if (!_funcListEvent) { |
1061 | selectRowInBrowser(row); |
1062 | } else { |
1063 | _funcListEvent = false; |
1064 | } |
1065 | } |
1066 | |
1067 | /** |
1068 | * display the full pathname |
1069 | */ |
1070 | void EditorWidget::showPath() { |
1071 | _commandChoice->tooltip(_filename); |
1072 | } |
1073 | |
1074 | /** |
1075 | * prints a status message on the tty-widget |
1076 | */ |
1077 | void EditorWidget::statusMsg(const char *msg) { |
1078 | if (msg) { |
1079 | _tty->print(msg); |
1080 | _tty->print("\n" ); |
1081 | } |
1082 | } |
1083 | |
1084 | /** |
1085 | * sets the font face, size and colour |
1086 | */ |
1087 | void EditorWidget::updateConfig(EditorWidget *current) { |
1088 | setFont(current->_editor->getFont()); |
1089 | setFontSize(current->_editor->getFontSize()); |
1090 | } |
1091 | |
1092 | //--Protected methods----------------------------------------------------------- |
1093 | |
1094 | /** |
1095 | * add filename to the hiistory file |
1096 | */ |
1097 | void EditorWidget::addHistory(const char *filename) { |
1098 | FILE *fp; |
1099 | char buffer[PATH_MAX]; |
1100 | char updatedfile[PATH_MAX]; |
1101 | char path[PATH_MAX]; |
1102 | |
1103 | int len = strlen(filename); |
1104 | if (strcasecmp(filename + len - 4, ".sbx" ) == 0 || access(filename, R_OK) != 0) { |
1105 | // don't remember bas exe or invalid files |
1106 | return; |
1107 | } |
1108 | |
1109 | len -= strlen(untitledFile); |
1110 | if (len > 0 && strcmp(filename + len, untitledFile) == 0) { |
1111 | // don't remember the untitled file |
1112 | return; |
1113 | } |
1114 | // save paths with unix path separators |
1115 | strcpy(updatedfile, filename); |
1116 | FileWidget::forwardSlash(updatedfile); |
1117 | filename = updatedfile; |
1118 | |
1119 | // save into the history file |
1120 | getHomeDir(path, sizeof(path)); |
1121 | strlcat(path, historyFile, sizeof(path)); |
1122 | |
1123 | fp = fopen(path, "r" ); |
1124 | if (fp) { |
1125 | // don't add the item if it already exists |
1126 | while (feof(fp) == 0) { |
1127 | if (fgets(buffer, sizeof(buffer), fp) && strncmp(filename, buffer, strlen(filename) - 1) == 0) { |
1128 | fclose(fp); |
1129 | return; |
1130 | } |
1131 | } |
1132 | fclose(fp); |
1133 | } |
1134 | |
1135 | fp = fopen(path, "a" ); |
1136 | if (fp) { |
1137 | fwrite(filename, strlen(filename), 1, fp); |
1138 | fwrite("\n" , 1, 1, fp); |
1139 | fclose(fp); |
1140 | } |
1141 | } |
1142 | |
1143 | /** |
1144 | * creates the sub/func selection list |
1145 | */ |
1146 | void EditorWidget::createFuncList() { |
1147 | Fl_Text_Buffer *textbuf = _editor->_textbuf; |
1148 | char *text = textbuf->text(); |
1149 | int len = textbuf->length(); |
1150 | int curLine = 1; |
1151 | const char *keywords[] = { |
1152 | "sub " , "func " , "def " , "label " , "const " , "local " , "dim " |
1153 | }; |
1154 | int keywords_length = sizeof(keywords) / sizeof(keywords[0]); |
1155 | int keywords_len[keywords_length]; |
1156 | for (int j = 0; j < keywords_length; j++) { |
1157 | keywords_len[j] = strlen(keywords[j]); |
1158 | } |
1159 | Fl_Tree_Item * = NULL; |
1160 | |
1161 | for (int i = 0; i < len; i++) { |
1162 | // skip to the newline start |
1163 | while (i < len && i != 0 && text[i] != '\n') { |
1164 | i++; |
1165 | } |
1166 | |
1167 | // skip any successive newlines |
1168 | while (i < len && text[i] == '\n') { |
1169 | curLine++; |
1170 | i++; |
1171 | } |
1172 | |
1173 | // skip any leading whitespace |
1174 | while (i < len && (text[i] == ' ' || text[i] == '\t')) { |
1175 | i++; |
1176 | } |
1177 | |
1178 | for (int j = 0; j < keywords_length; j++) { |
1179 | if (!strncasecmp(text + i, keywords[j], keywords_len[j])) { |
1180 | i += keywords_len[j]; |
1181 | int i_begin = i; |
1182 | while (i < len && text[i] != '=' && text[i] != '\r' && text[i] != '\n') { |
1183 | i++; |
1184 | } |
1185 | if (i > i_begin) { |
1186 | String s(text + i_begin, i - i_begin); |
1187 | if (j < 2) { |
1188 | menuGroup = new Fl_Tree_Item(_funcList); |
1189 | menuGroup->label(s.c_str()); |
1190 | menuGroup->user_data((void *)(intptr_t)curLine); |
1191 | _funcList->add(s.c_str(), menuGroup); |
1192 | } else if (menuGroup != NULL) { |
1193 | Fl_Tree_Item *leaf = new Fl_Tree_Item(_funcList); |
1194 | leaf->label(s.c_str()); |
1195 | leaf->user_data((void *)(intptr_t)curLine); |
1196 | menuGroup->add(_funcList->prefs(), s.c_str(), leaf); |
1197 | } |
1198 | } |
1199 | break; |
1200 | } |
1201 | } |
1202 | if (text[i] == '\n') { |
1203 | i--; // avoid eating the entire next line |
1204 | } |
1205 | } |
1206 | free(text); |
1207 | } |
1208 | |
1209 | /** |
1210 | * called when the buffer has change - sets the modified flag |
1211 | */ |
1212 | void EditorWidget::doChange(int inserted, int deleted) { |
1213 | if (!_loading) { |
1214 | // do nothing while file load in progress |
1215 | if (inserted || deleted) { |
1216 | _dirty = 1; |
1217 | } |
1218 | |
1219 | if (!readonly()) { |
1220 | setModified(_dirty); |
1221 | } |
1222 | } |
1223 | } |
1224 | |
1225 | /** |
1226 | * handler for the sub/func list selection event |
1227 | */ |
1228 | void EditorWidget::findFunc(const char *find) { |
1229 | char *text = _editor->_textbuf->text(); |
1230 | int findLen = strlen(find); |
1231 | int len = _editor->_textbuf->length(); |
1232 | int lineNo = 1; |
1233 | for (int i = 0; i < len; i++) { |
1234 | if (strncasecmp(text + i, find, findLen) == 0) { |
1235 | gotoLine(lineNo); |
1236 | break; |
1237 | } else if (text[i] == '\n') { |
1238 | lineNo++; |
1239 | } |
1240 | } |
1241 | free(text); |
1242 | } |
1243 | |
1244 | /** |
1245 | * returns the current selection text |
1246 | */ |
1247 | char *EditorWidget::getSelection(Fl_Rect *rc) { |
1248 | return ((BasicEditor *)_editor)->getSelection(rc); |
1249 | } |
1250 | |
1251 | /** |
1252 | * returns the current file modified time |
1253 | */ |
1254 | uint32_t EditorWidget::getModifiedTime() { |
1255 | struct stat st_file; |
1256 | uint32_t modified = 0; |
1257 | if (_filename[0] && !stat(_filename, &st_file)) { |
1258 | modified = st_file.st_mtime; |
1259 | } |
1260 | return modified; |
1261 | } |
1262 | |
1263 | /** |
1264 | * handler for the external file change event |
1265 | */ |
1266 | void EditorWidget::handleFileChange() { |
1267 | // handle outside changes to the file |
1268 | if (_filename[0] && _modifiedTime != 0 && _modifiedTime != getModifiedTime()) { |
1269 | String st; |
1270 | st.append("File" ) |
1271 | .append(_filename) |
1272 | .append("has changed on disk.\n\n" ) |
1273 | .append("Do you want to reload the file?" ); |
1274 | if (fl_choice(st.c_str(), "Yes" , "No" , NULL, NULL) == 0) { |
1275 | reloadFile(); |
1276 | } else { |
1277 | _modifiedTime = 0; |
1278 | } |
1279 | } |
1280 | } |
1281 | |
1282 | /** |
1283 | * reset the function list |
1284 | */ |
1285 | void EditorWidget::resetList() { |
1286 | _funcList->clear(); |
1287 | createFuncList(); |
1288 | _funcList->add(scanLabel); |
1289 | } |
1290 | |
1291 | /** |
1292 | * resize the heights |
1293 | */ |
1294 | void EditorWidget::resize(int x, int y, int w, int h) { |
1295 | Fl_Group *tile = _editor->parent(); |
1296 | int edit_scale_w = 1 + (_editor->w() * 100 / tile->w()); |
1297 | int edit_scale_h = 1 + (_editor->h() * 100 / tile->h()); |
1298 | edit_scale_w = MIN(80, edit_scale_w); |
1299 | edit_scale_h = MIN(80, edit_scale_h); |
1300 | |
1301 | tile->resizable(_editor); |
1302 | Fl_Group::resize(x, y, w, h); |
1303 | tile->resize(tile->x(), y, w, h - STATUS_HEIGHT); |
1304 | tile->resizable(NULL); |
1305 | |
1306 | int status_y = y + h - STATUS_HEIGHT; |
1307 | int edit_w = tile->w() * edit_scale_w / 100; |
1308 | int edit_h = tile->h() * edit_scale_h / 100; |
1309 | int tty_y = tile->y() + edit_h; |
1310 | int tty_h = status_y - tty_y; |
1311 | int func_x = tile->x() + edit_w; |
1312 | int func_w = tile->w() - edit_w; |
1313 | |
1314 | _editor->resize(_editor->x(), _editor->y(), edit_w, edit_h); |
1315 | _funcList->resize(func_x, _funcList->y(), func_w, _editor->h()); |
1316 | _tty->resize(_tty->x(), tty_y, _tty->w(), tty_h); |
1317 | _statusBar->resize(_statusBar->x(), status_y, _statusBar->w(), STATUS_HEIGHT); |
1318 | } |
1319 | |
1320 | /** |
1321 | * create a new editor buffer |
1322 | */ |
1323 | void EditorWidget::newFile() { |
1324 | if (!readonly() && checkSave(true)) { |
1325 | Fl_Text_Buffer *textbuf = _editor->_textbuf; |
1326 | _filename[0] = '\0'; |
1327 | textbuf->select(0, textbuf->length()); |
1328 | textbuf->remove_selection(); |
1329 | _dirty = 0; |
1330 | textbuf->call_modify_callbacks(); |
1331 | fileChanged(false); |
1332 | _modifiedTime = 0; |
1333 | } |
1334 | } |
1335 | |
1336 | /** |
1337 | * reload the editor buffer |
1338 | */ |
1339 | void EditorWidget::reloadFile() { |
1340 | char buffer[PATH_MAX]; |
1341 | strcpy(buffer, _filename); |
1342 | loadFile(buffer); |
1343 | } |
1344 | |
1345 | /** |
1346 | * replace all occurances of the given text |
1347 | */ |
1348 | int EditorWidget::replaceAll(const char *find, const char *replace, bool restorePos, bool matchWord) { |
1349 | int times = 0; |
1350 | |
1351 | if (strcmp(find, replace) != 0) { |
1352 | Fl_Text_Buffer *textbuf = _editor->_textbuf; |
1353 | int prevPos = _editor->insert_position(); |
1354 | |
1355 | // loop through the whole string |
1356 | int pos = 0; |
1357 | _editor->insert_position(pos); |
1358 | |
1359 | while (textbuf->search_forward(pos, find, &pos)) { |
1360 | // found a match; update the position and replace text |
1361 | if (!matchWord || |
1362 | ((pos == 0 || !isvar(textbuf->char_at(pos - 1))) && |
1363 | !isvar(textbuf->char_at(pos + strlen(find))))) { |
1364 | textbuf->select(pos, pos + strlen(find)); |
1365 | textbuf->remove_selection(); |
1366 | textbuf->insert(pos, replace); |
1367 | } |
1368 | // advance beyond replace string |
1369 | pos += strlen(replace); |
1370 | _editor->insert_position(pos); |
1371 | times++; |
1372 | } |
1373 | |
1374 | if (restorePos) { |
1375 | _editor->insert_position(prevPos); |
1376 | } |
1377 | _editor->show_insert_position(); |
1378 | } |
1379 | |
1380 | return times; |
1381 | } |
1382 | |
1383 | /** |
1384 | * handler for searching backwards |
1385 | */ |
1386 | bool EditorWidget::searchBackward(const char *text, int startPos, |
1387 | const char *find, int findLen, int *foundPos) { |
1388 | int matchIndex = findLen - 1; |
1389 | for (int i = startPos; i >= 0; i--) { |
1390 | bool equals = toupper(text[i]) == toupper(find[matchIndex]); |
1391 | if (equals == false && matchIndex < findLen - 1) { |
1392 | // partial match now fails - reset search at current index |
1393 | matchIndex = findLen - 1; |
1394 | equals = toupper(text[i]) == toupper(find[matchIndex]); |
1395 | } |
1396 | matchIndex = (equals ? matchIndex - 1 : findLen - 1); |
1397 | if (matchIndex == -1 && (i == 0 || isalpha(text[i - 1]) == 0)) { |
1398 | // char prior to word is non-alpha |
1399 | *foundPos = i; |
1400 | return true; |
1401 | } |
1402 | } |
1403 | return false; |
1404 | } |
1405 | |
1406 | /** |
1407 | * sync the browser widget selection |
1408 | */ |
1409 | void EditorWidget::selectRowInBrowser(int row) { |
1410 | Fl_Tree_Item *root = _funcList->root(); |
1411 | int len = root->children() - 1; |
1412 | bool found = false; |
1413 | for (int i = 0; i < len; i++) { |
1414 | int line = (int)(intptr_t)root->child(i)->user_data(); |
1415 | int nextLine = (int)(intptr_t)root->child(i + 1)->user_data(); |
1416 | if (row >= line && (i == len - 1 || row < nextLine)) { |
1417 | int y = _funcList->vposition() + root->child(i)->y(); |
1418 | int bottom = _funcList->y() + _funcList->h(); |
1419 | int pos = bottom - y - (_funcList->h() / 2); |
1420 | _funcList->select_only(root->child(i), 0); |
1421 | _funcList->vposition(-pos); |
1422 | found = true; |
1423 | break; |
1424 | } |
1425 | } |
1426 | if (!found) { |
1427 | _funcList->select_only(root->child(0), 0); |
1428 | _funcList->vposition(0); |
1429 | } |
1430 | } |
1431 | |
1432 | /** |
1433 | * sets the current command |
1434 | */ |
1435 | void EditorWidget::setCommand(CommandOpt command) { |
1436 | if (_commandOpt == cmd_input_text) { |
1437 | wnd->setModal(false); |
1438 | } |
1439 | |
1440 | _commandOpt = command; |
1441 | switch (command) { |
1442 | case cmd_find: |
1443 | _commandChoice->label("@search Find:" ); |
1444 | break; |
1445 | case cmd_find_inc: |
1446 | _commandChoice->label("Inc Find:" ); |
1447 | break; |
1448 | case cmd_replace: |
1449 | _commandChoice->label("Replace:" ); |
1450 | break; |
1451 | case cmd_replace_with: |
1452 | _commandChoice->label("With:" ); |
1453 | break; |
1454 | case cmd_goto: |
1455 | _commandChoice->label("Goto:" ); |
1456 | break; |
1457 | case cmd_input_text: |
1458 | _commandChoice->label("INPUT:" ); |
1459 | break; |
1460 | } |
1461 | _commandChoice->redraw(); |
1462 | |
1463 | _commandText->color(_commandChoice->color()); |
1464 | _commandText->redraw(); |
1465 | _commandText->take_focus(); |
1466 | _commandText->when(_commandOpt == cmd_find_inc ? FL_WHEN_CHANGED : FL_WHEN_ENTER_KEY_ALWAYS); |
1467 | } |
1468 | |
1469 | /** |
1470 | * display the toolbar modified flag |
1471 | */ |
1472 | void EditorWidget::setModified(bool dirty) { |
1473 | _dirty = dirty; |
1474 | _modStatus->when(dirty ? FL_WHEN_CHANGED : FL_WHEN_NEVER); |
1475 | _modStatus->label(dirty ? "MOD" : "@line" ); |
1476 | _modStatus->redraw(); |
1477 | } |
1478 | |
1479 | /** |
1480 | * highlight the given search text |
1481 | */ |
1482 | void EditorWidget::showFindText(const char *text) { |
1483 | _editor->showFindText(text); |
1484 | } |
1485 | |
1486 | LineInput::LineInput(int x, int y, int w, int h) : |
1487 | Fl_Input(x, y, w, h), |
1488 | orig_x(x), |
1489 | orig_y(y), |
1490 | orig_w(w), |
1491 | orig_h(h) { |
1492 | when(FL_WHEN_ENTER_KEY); |
1493 | box(FL_BORDER_BOX); |
1494 | fl_color(fl_rgb_color(220, 220, 220)); |
1495 | take_focus(); |
1496 | } |
1497 | |
1498 | /** |
1499 | * veto the layout changes |
1500 | */ |
1501 | void LineInput::resize(int x, int y, int w, int h) { |
1502 | Fl_Input::resize(orig_x, orig_y, orig_w, orig_h); |
1503 | } |
1504 | |
1505 | int LineInput::handle(int event) { |
1506 | int result; |
1507 | if (event == FL_KEYBOARD) { |
1508 | if (Fl::event_state(FL_CTRL) && Fl::event_key() == 'b') { |
1509 | if (!wnd->isEdit()) { |
1510 | wnd->setBreak(); |
1511 | } |
1512 | } else if (Fl::event_key(FL_Escape)) { |
1513 | do_callback(); |
1514 | } else { |
1515 | // grow the input box width as text is entered |
1516 | const char *text = value(); |
1517 | int strw = fl_width(text) + fl_width(value()) + 4; |
1518 | if (strw > w()) { |
1519 | w(strw); |
1520 | orig_w = strw; |
1521 | redraw(); |
1522 | } |
1523 | } |
1524 | result = Fl_Input::handle(event); |
1525 | } else { |
1526 | result = Fl_Input::handle(event); |
1527 | } |
1528 | return result; |
1529 | } |
1530 | |