source: mainline/uspace/app/edit/edit.c

Last change on this file was 994f87b, checked in by Jiri Svoboda <jiri@…>, 11 months ago

Add File/New and File/Open to text editor

Change repeat search shortcut to Ctrl-R as Ctrl-N is now used for
New File.

  • Property mode set to 100644
File size: 59.9 KB
Line 
1/*
2 * Copyright (c) 2024 Jiri Svoboda
3 * Copyright (c) 2012 Martin Sucha
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup edit
31 * @brief Text editor.
32 * @{
33 */
34/**
35 * @file
36 */
37
38#include <align.h>
39#include <clipboard.h>
40#include <errno.h>
41#include <gfx/color.h>
42#include <gfx/cursor.h>
43#include <gfx/font.h>
44#include <gfx/render.h>
45#include <gfx/text.h>
46#include <io/kbd_event.h>
47#include <io/keycode.h>
48#include <io/pos_event.h>
49#include <io/style.h>
50#include <macros.h>
51#include <stdio.h>
52#include <stdlib.h>
53#include <stddef.h>
54#include <stdbool.h>
55#include <types/common.h>
56#include <ui/control.h>
57#include <ui/filedialog.h>
58#include <ui/fixed.h>
59#include <ui/label.h>
60#include <ui/menu.h>
61#include <ui/menubar.h>
62#include <ui/menudd.h>
63#include <ui/menuentry.h>
64#include <ui/promptdialog.h>
65#include <ui/resource.h>
66#include <ui/ui.h>
67#include <ui/window.h>
68#include <vfs/vfs.h>
69
70#include "sheet.h"
71#include "search.h"
72
73enum redraw_flags {
74 REDRAW_TEXT = (1 << 0),
75 REDRAW_ROW = (1 << 1),
76 REDRAW_STATUS = (1 << 2),
77 REDRAW_CARET = (1 << 3)
78};
79
80/** Pane
81 *
82 * A rectangular area of the screen used to edit a document. Different
83 * panes can be possibly used to edit the same document. This is a custom
84 * UI control.
85 */
86typedef struct {
87 /** Base control object */
88 struct ui_control *control;
89
90 /** Containing window */
91 ui_window_t *window;
92
93 /** UI resource */
94 struct ui_resource *res;
95
96 /** Pane rectangle */
97 gfx_rect_t rect;
98
99 /** Pane color */
100 gfx_color_t *color;
101
102 /** Selection color */
103 gfx_color_t *sel_color;
104
105 /* Pane dimensions */
106 int rows, columns;
107
108 /* Position of the visible area */
109 int sh_row, sh_column;
110
111 /** Bitmask of components that need redrawing */
112 enum redraw_flags rflags;
113
114 /** Current position of the caret */
115 tag_t caret_pos;
116
117 /** Start of selection */
118 tag_t sel_start;
119
120 /** Active keyboard modifiers */
121 keymod_t keymod;
122
123 /**
124 * Ideal column where the caret should try to get. This is used
125 * for maintaining the same column during vertical movement.
126 */
127 int ideal_column;
128
129 bool search_reverse;
130 char *previous_search;
131 bool previous_search_reverse;
132} pane_t;
133
134/** Text editor */
135typedef struct {
136 /** User interface */
137 ui_t *ui;
138 /** Editor window */
139 ui_window_t *window;
140 /** UI resource */
141 ui_resource_t *ui_res;
142 /** Menu bar */
143 ui_menu_bar_t *menubar;
144 /** Status bar */
145 ui_label_t *status;
146} edit_t;
147
148/** Document
149 *
150 * Associates a sheet with a file where it can be saved to.
151 */
152typedef struct {
153 char *file_name;
154 sheet_t *sh;
155} doc_t;
156
157static edit_t edit;
158static doc_t doc;
159static pane_t pane;
160
161#define ROW_BUF_SIZE 4096
162#define BUF_SIZE 64
163#define TAB_WIDTH 8
164
165/** Maximum filename length that can be entered. */
166#define INFNAME_MAX_LEN 128
167
168static void cursor_setvis(bool visible);
169
170static void key_handle_press(kbd_event_t *ev);
171static void key_handle_unmod(kbd_event_t const *ev);
172static void key_handle_ctrl(kbd_event_t const *ev);
173static void key_handle_shift(kbd_event_t const *ev);
174static void key_handle_shift_ctrl(kbd_event_t const *ev);
175static void key_handle_movement(unsigned int key, bool shift);
176
177static void pos_handle(pos_event_t *ev);
178
179static errno_t file_new(void);
180static void file_open(void);
181static errno_t file_open_file(const char *fname);
182static errno_t file_save(char const *fname);
183static void file_save_as(void);
184static errno_t file_insert(const char *fname);
185static errno_t file_save_range(char const *fname, spt_t const *spos,
186 spt_t const *epos);
187static char *range_get_str(spt_t const *spos, spt_t const *epos);
188
189static errno_t pane_init(ui_window_t *, pane_t *);
190static void pane_fini(pane_t *);
191static ui_control_t *pane_ctl(pane_t *);
192static errno_t pane_update(pane_t *);
193static errno_t pane_text_display(pane_t *);
194static void pane_row_display(void);
195static errno_t pane_row_range_display(pane_t *, int r0, int r1);
196static void pane_status_display(pane_t *);
197static void pane_caret_display(pane_t *);
198
199static void insert_char(char32_t c);
200static void delete_char_before(void);
201static void delete_char_after(void);
202static void caret_update(void);
203static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir, bool select);
204static void caret_move_absolute(int row, int column, enum dir_spec align_dir, bool select);
205static void caret_move(spt_t spt, bool select, bool update_ideal_column);
206static void caret_move_word_left(bool select);
207static void caret_move_word_right(bool select);
208static void caret_go_to_line_ask(void);
209
210static bool selection_active(void);
211static void selection_sel_all(void);
212static void selection_sel_range(spt_t pa, spt_t pb);
213static void selection_get_points(spt_t *pa, spt_t *pb);
214static void selection_delete(void);
215static void selection_copy(void);
216static void edit_cut(void);
217static void edit_paste(void);
218static void insert_clipboard_data(void);
219
220static void search(char *pattern, bool reverse);
221static void search_prompt(bool reverse);
222static void search_repeat(void);
223
224static void pt_get_sof(spt_t *pt);
225static void pt_get_eof(spt_t *pt);
226static void pt_get_sol(spt_t *cpt, spt_t *spt);
227static void pt_get_eol(spt_t *cpt, spt_t *ept);
228static bool pt_is_word_beginning(spt_t *pt);
229static bool pt_is_delimiter(spt_t *pt);
230static bool pt_is_punctuation(spt_t *pt);
231static spt_t pt_find_word_left(spt_t spt);
232static spt_t pt_find_word_left(spt_t spt);
233
234static int tag_cmp(tag_t const *a, tag_t const *b);
235static int spt_cmp(spt_t const *a, spt_t const *b);
236static int coord_cmp(coord_t const *a, coord_t const *b);
237
238static void status_display(char const *str);
239static errno_t edit_ui_create(edit_t *);
240static void edit_ui_destroy(edit_t *);
241
242static void edit_wnd_close(ui_window_t *, void *);
243static void edit_wnd_focus(ui_window_t *, void *, unsigned);
244static void edit_wnd_kbd_event(ui_window_t *, void *, kbd_event_t *);
245static void edit_wnd_unfocus(ui_window_t *, void *, unsigned);
246
247static ui_window_cb_t edit_window_cb = {
248 .close = edit_wnd_close,
249 .focus = edit_wnd_focus,
250 .kbd = edit_wnd_kbd_event,
251 .unfocus = edit_wnd_unfocus
252};
253
254static void edit_menubar_activate(ui_menu_bar_t *, void *);
255static void edit_menubar_deactivate(ui_menu_bar_t *, void *);
256
257static ui_menu_bar_cb_t edit_menubar_cb = {
258 .activate = edit_menubar_activate,
259 .deactivate = edit_menubar_deactivate
260};
261
262static void edit_file_new(ui_menu_entry_t *, void *);
263static void edit_file_open(ui_menu_entry_t *, void *);
264static void edit_file_save(ui_menu_entry_t *, void *);
265static void edit_file_save_as(ui_menu_entry_t *, void *);
266static void edit_file_exit(ui_menu_entry_t *, void *);
267static void edit_edit_cut(ui_menu_entry_t *, void *);
268static void edit_edit_copy(ui_menu_entry_t *, void *);
269static void edit_edit_paste(ui_menu_entry_t *, void *);
270static void edit_edit_delete(ui_menu_entry_t *, void *);
271static void edit_edit_select_all(ui_menu_entry_t *, void *);
272static void edit_search_find(ui_menu_entry_t *, void *);
273static void edit_search_reverse_find(ui_menu_entry_t *, void *);
274static void edit_search_find_next(ui_menu_entry_t *, void *);
275static void edit_search_go_to_line(ui_menu_entry_t *, void *);
276
277static void pane_ctl_destroy(void *);
278static errno_t pane_ctl_paint(void *);
279static ui_evclaim_t pane_ctl_pos_event(void *, pos_event_t *);
280
281/** Pabe control ops */
282ui_control_ops_t pane_ctl_ops = {
283 .destroy = pane_ctl_destroy,
284 .paint = pane_ctl_paint,
285 .pos_event = pane_ctl_pos_event
286};
287
288static void open_dialog_bok(ui_file_dialog_t *, void *, const char *);
289static void open_dialog_bcancel(ui_file_dialog_t *, void *);
290static void open_dialog_close(ui_file_dialog_t *, void *);
291
292static ui_file_dialog_cb_t open_dialog_cb = {
293 .bok = open_dialog_bok,
294 .bcancel = open_dialog_bcancel,
295 .close = open_dialog_close
296};
297
298static void save_as_dialog_bok(ui_file_dialog_t *, void *, const char *);
299static void save_as_dialog_bcancel(ui_file_dialog_t *, void *);
300static void save_as_dialog_close(ui_file_dialog_t *, void *);
301
302static ui_file_dialog_cb_t save_as_dialog_cb = {
303 .bok = save_as_dialog_bok,
304 .bcancel = save_as_dialog_bcancel,
305 .close = save_as_dialog_close
306};
307
308static void go_to_line_dialog_bok(ui_prompt_dialog_t *, void *, const char *);
309static void go_to_line_dialog_bcancel(ui_prompt_dialog_t *, void *);
310static void go_to_line_dialog_close(ui_prompt_dialog_t *, void *);
311
312static ui_prompt_dialog_cb_t go_to_line_dialog_cb = {
313 .bok = go_to_line_dialog_bok,
314 .bcancel = go_to_line_dialog_bcancel,
315 .close = go_to_line_dialog_close
316};
317
318static void search_dialog_bok(ui_prompt_dialog_t *, void *, const char *);
319static void search_dialog_bcancel(ui_prompt_dialog_t *, void *);
320static void search_dialog_close(ui_prompt_dialog_t *, void *);
321
322static ui_prompt_dialog_cb_t search_dialog_cb = {
323 .bok = search_dialog_bok,
324 .bcancel = search_dialog_bcancel,
325 .close = search_dialog_close
326};
327
328int main(int argc, char *argv[])
329{
330 errno_t rc;
331
332 pane.sh_row = 1;
333 pane.sh_column = 1;
334
335 /* Create UI */
336 rc = edit_ui_create(&edit);
337 if (rc != EOK)
338 return 1;
339
340 if (argc == 2) {
341 doc.file_name = str_dup(argv[1]);
342 rc = file_open_file(argv[1]);
343 if (rc != EOK) {
344 status_display("File not found. Starting empty file.");
345 rc = file_new();
346 }
347 } else if (argc > 1) {
348 printf("Invalid arguments.\n");
349 return -2;
350 } else {
351 rc = file_new();
352 }
353
354 /* Initial display */
355 rc = ui_window_paint(edit.window);
356 if (rc != EOK) {
357 printf("Error painting window.\n");
358 return rc;
359 }
360
361 ui_run(edit.ui);
362
363 edit_ui_destroy(&edit);
364 return 0;
365}
366
367/** Create text editor UI.
368 *
369 * @param edit Editor
370 * @return EOK on success or an error code
371 */
372static errno_t edit_ui_create(edit_t *edit)
373{
374 errno_t rc;
375 ui_wnd_params_t params;
376 ui_fixed_t *fixed = NULL;
377 ui_menu_t *mfile = NULL;
378 ui_menu_t *medit = NULL;
379 ui_menu_entry_t *mnew = NULL;
380 ui_menu_entry_t *mopen = NULL;
381 ui_menu_entry_t *msave = NULL;
382 ui_menu_entry_t *msaveas = NULL;
383 ui_menu_entry_t *mfsep = NULL;
384 ui_menu_entry_t *mexit = NULL;
385 ui_menu_entry_t *mcut = NULL;
386 ui_menu_entry_t *mcopy = NULL;
387 ui_menu_entry_t *mpaste = NULL;
388 ui_menu_entry_t *mdelete = NULL;
389 ui_menu_entry_t *mesep = NULL;
390 ui_menu_entry_t *mselall = NULL;
391 ui_menu_t *msearch = NULL;
392 ui_menu_entry_t *mfind = NULL;
393 ui_menu_entry_t *mfindr = NULL;
394 ui_menu_entry_t *mfindn = NULL;
395 ui_menu_entry_t *mssep = NULL;
396 ui_menu_entry_t *mgoto = NULL;
397 gfx_rect_t arect;
398 gfx_rect_t rect;
399
400 rc = ui_create(UI_CONSOLE_DEFAULT, &edit->ui);
401 if (rc != EOK) {
402 printf("Error creating UI on display %s.\n",
403 UI_CONSOLE_DEFAULT);
404 goto error;
405 }
406
407 ui_wnd_params_init(&params);
408 params.caption = "Text Editor";
409 params.style &= ~ui_wds_decorated;
410 params.placement = ui_wnd_place_full_screen;
411
412 rc = ui_window_create(edit->ui, &params, &edit->window);
413 if (rc != EOK) {
414 printf("Error creating window.\n");
415 goto error;
416 }
417
418 ui_window_set_cb(edit->window, &edit_window_cb, (void *) edit);
419
420 edit->ui_res = ui_window_get_res(edit->window);
421
422 rc = ui_fixed_create(&fixed);
423 if (rc != EOK) {
424 printf("Error creating fixed layout.\n");
425 return rc;
426 }
427
428 rc = ui_menu_bar_create(edit->ui, edit->window, &edit->menubar);
429 if (rc != EOK) {
430 printf("Error creating menu bar.\n");
431 return rc;
432 }
433
434 ui_menu_bar_set_cb(edit->menubar, &edit_menubar_cb, (void *) edit);
435
436 rc = ui_menu_dd_create(edit->menubar, "~F~ile", NULL, &mfile);
437 if (rc != EOK) {
438 printf("Error creating menu.\n");
439 return rc;
440 }
441
442 rc = ui_menu_entry_create(mfile, "~N~ew", "Ctrl-N", &mnew);
443 if (rc != EOK) {
444 printf("Error creating menu.\n");
445 return rc;
446 }
447
448 ui_menu_entry_set_cb(mnew, edit_file_new, (void *) edit);
449
450 rc = ui_menu_entry_create(mfile, "~O~pen", "Ctrl-O", &mopen);
451 if (rc != EOK) {
452 printf("Error creating menu.\n");
453 return rc;
454 }
455
456 ui_menu_entry_set_cb(mopen, edit_file_open, (void *) edit);
457
458 rc = ui_menu_entry_create(mfile, "~S~ave", "Ctrl-S", &msave);
459 if (rc != EOK) {
460 printf("Error creating menu.\n");
461 return rc;
462 }
463
464 ui_menu_entry_set_cb(msave, edit_file_save, (void *) edit);
465
466 rc = ui_menu_entry_create(mfile, "Save ~A~s", "Ctrl-E", &msaveas);
467 if (rc != EOK) {
468 printf("Error creating menu.\n");
469 return rc;
470 }
471
472 ui_menu_entry_set_cb(msaveas, edit_file_save_as, (void *) edit);
473
474 rc = ui_menu_entry_sep_create(mfile, &mfsep);
475 if (rc != EOK) {
476 printf("Error creating menu.\n");
477 return rc;
478 }
479
480 rc = ui_menu_entry_create(mfile, "E~x~it", "Ctrl-Q", &mexit);
481 if (rc != EOK) {
482 printf("Error creating menu.\n");
483 return rc;
484 }
485
486 ui_menu_entry_set_cb(mexit, edit_file_exit, (void *) edit);
487
488 rc = ui_menu_dd_create(edit->menubar, "~E~dit", NULL, &medit);
489 if (rc != EOK) {
490 printf("Error creating menu.\n");
491 return rc;
492 }
493
494 rc = ui_menu_entry_create(medit, "Cu~t~", "Ctrl-X", &mcut);
495 if (rc != EOK) {
496 printf("Error creating menu.\n");
497 return rc;
498 }
499
500 ui_menu_entry_set_cb(mcut, edit_edit_cut, (void *) edit);
501
502 rc = ui_menu_entry_create(medit, "~C~opy", "Ctrl-C", &mcopy);
503 if (rc != EOK) {
504 printf("Error creating menu.\n");
505 return rc;
506 }
507
508 ui_menu_entry_set_cb(mcopy, edit_edit_copy, (void *) edit);
509
510 rc = ui_menu_entry_create(medit, "~P~aste", "Ctrl-V", &mpaste);
511 if (rc != EOK) {
512 printf("Error creating menu.\n");
513 return rc;
514 }
515
516 ui_menu_entry_set_cb(mpaste, edit_edit_paste, (void *) edit);
517
518 rc = ui_menu_entry_create(medit, "~D~elete", "Del", &mdelete);
519 if (rc != EOK) {
520 printf("Error creating menu.\n");
521 return rc;
522 }
523
524 ui_menu_entry_set_cb(mdelete, edit_edit_delete, (void *) edit);
525
526 rc = ui_menu_entry_sep_create(medit, &mesep);
527 if (rc != EOK) {
528 printf("Error creating menu.\n");
529 return rc;
530 }
531
532 rc = ui_menu_entry_create(medit, "Select ~A~ll", "Ctrl-A", &mselall);
533 if (rc != EOK) {
534 printf("Error creating menu.\n");
535 return rc;
536 }
537
538 ui_menu_entry_set_cb(mselall, edit_edit_select_all, (void *) edit);
539
540 rc = ui_menu_dd_create(edit->menubar, "~S~earch", NULL, &msearch);
541 if (rc != EOK) {
542 printf("Error creating menu.\n");
543 return rc;
544 }
545
546 rc = ui_menu_entry_create(msearch, "~F~ind", "Ctrl-F", &mfind);
547 if (rc != EOK) {
548 printf("Error creating menu.\n");
549 return rc;
550 }
551
552 ui_menu_entry_set_cb(mfind, edit_search_find, (void *) edit);
553
554 rc = ui_menu_entry_create(msearch, "~R~everse Find", "Ctrl-Shift-F", &mfindr);
555 if (rc != EOK) {
556 printf("Error creating menu.\n");
557 return rc;
558 }
559
560 ui_menu_entry_set_cb(mfindr, edit_search_reverse_find, (void *) edit);
561
562 rc = ui_menu_entry_create(msearch, "Find ~N~ext", "Ctrl-R", &mfindn);
563 if (rc != EOK) {
564 printf("Error creating menu.\n");
565 return rc;
566 }
567
568 ui_menu_entry_set_cb(mfindn, edit_search_find_next, (void *) edit);
569
570 rc = ui_menu_entry_sep_create(msearch, &mssep);
571 if (rc != EOK) {
572 printf("Error creating menu.\n");
573 return rc;
574 }
575
576 rc = ui_menu_entry_create(msearch, "Go To ~L~ine", "Ctrl-L", &mgoto);
577 if (rc != EOK) {
578 printf("Error creating menu.\n");
579 return rc;
580 }
581
582 ui_menu_entry_set_cb(mgoto, edit_search_go_to_line, (void *) edit);
583
584 ui_window_get_app_rect(edit->window, &arect);
585
586 rect.p0 = arect.p0;
587 rect.p1.x = arect.p1.x;
588 rect.p1.y = arect.p0.y + 1;
589 ui_menu_bar_set_rect(edit->menubar, &rect);
590
591 rc = ui_fixed_add(fixed, ui_menu_bar_ctl(edit->menubar));
592 if (rc != EOK) {
593 printf("Error adding control to layout.\n");
594 return rc;
595 }
596
597 rc = pane_init(edit->window, &pane);
598 if (rc != EOK) {
599 printf("Error initializing pane.\n");
600 return rc;
601 }
602
603 rc = ui_fixed_add(fixed, pane_ctl(&pane));
604 if (rc != EOK) {
605 printf("Error adding control to layout.\n");
606 return rc;
607 }
608
609 rc = ui_label_create(edit->ui_res, "", &edit->status);
610 if (rc != EOK) {
611 printf("Error creating menu bar.\n");
612 return rc;
613 }
614
615 rect.p0.x = arect.p0.x;
616 rect.p0.y = arect.p1.y - 1;
617 rect.p1 = arect.p1;
618 ui_label_set_rect(edit->status, &rect);
619
620 rc = ui_fixed_add(fixed, ui_label_ctl(edit->status));
621 if (rc != EOK) {
622 printf("Error adding control to layout.\n");
623 return rc;
624 }
625
626 ui_window_add(edit->window, ui_fixed_ctl(fixed));
627 return EOK;
628error:
629 if (edit->window != NULL)
630 ui_window_destroy(edit->window);
631 if (edit->ui != NULL)
632 ui_destroy(edit->ui);
633 return rc;
634}
635
636/** Destroy text editor UI.
637 *
638 * @param edit Editor
639 */
640static void edit_ui_destroy(edit_t *edit)
641{
642 ui_window_destroy(edit->window);
643 ui_destroy(edit->ui);
644}
645
646/* Handle key press. */
647static void key_handle_press(kbd_event_t *ev)
648{
649 if (((ev->mods & KM_ALT) == 0) &&
650 ((ev->mods & KM_SHIFT) == 0) &&
651 (ev->mods & KM_CTRL) != 0) {
652 key_handle_ctrl(ev);
653 } else if (((ev->mods & KM_ALT) == 0) &&
654 ((ev->mods & KM_CTRL) == 0) &&
655 (ev->mods & KM_SHIFT) != 0) {
656 key_handle_shift(ev);
657 } else if (((ev->mods & KM_ALT) == 0) &&
658 ((ev->mods & KM_CTRL) != 0) &&
659 (ev->mods & KM_SHIFT) != 0) {
660 key_handle_shift_ctrl(ev);
661 } else if ((ev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
662 key_handle_unmod(ev);
663 }
664}
665
666static void cursor_setvis(bool visible)
667{
668 gfx_context_t *gc = ui_window_get_gc(edit.window);
669
670 (void) gfx_cursor_set_visible(gc, visible);
671}
672
673/** Handle key without modifier. */
674static void key_handle_unmod(kbd_event_t const *ev)
675{
676 switch (ev->key) {
677 case KC_ENTER:
678 selection_delete();
679 insert_char('\n');
680 caret_update();
681 break;
682 case KC_LEFT:
683 case KC_RIGHT:
684 case KC_UP:
685 case KC_DOWN:
686 case KC_HOME:
687 case KC_END:
688 case KC_PAGE_UP:
689 case KC_PAGE_DOWN:
690 key_handle_movement(ev->key, false);
691 break;
692 case KC_BACKSPACE:
693 if (selection_active())
694 selection_delete();
695 else
696 delete_char_before();
697 caret_update();
698 break;
699 case KC_DELETE:
700 if (selection_active())
701 selection_delete();
702 else
703 delete_char_after();
704 caret_update();
705 break;
706 default:
707 if (ev->c >= 32 || ev->c == '\t') {
708 selection_delete();
709 insert_char(ev->c);
710 caret_update();
711 }
712 break;
713 }
714}
715
716/** Handle Shift-key combination. */
717static void key_handle_shift(kbd_event_t const *ev)
718{
719 switch (ev->key) {
720 case KC_LEFT:
721 case KC_RIGHT:
722 case KC_UP:
723 case KC_DOWN:
724 case KC_HOME:
725 case KC_END:
726 case KC_PAGE_UP:
727 case KC_PAGE_DOWN:
728 key_handle_movement(ev->key, true);
729 break;
730 default:
731 if (ev->c >= 32 || ev->c == '\t') {
732 selection_delete();
733 insert_char(ev->c);
734 caret_update();
735 }
736 break;
737 }
738}
739
740/** Handle Ctrl-key combination. */
741static void key_handle_ctrl(kbd_event_t const *ev)
742{
743 spt_t pt;
744 switch (ev->key) {
745 case KC_Q:
746 ui_quit(edit.ui);
747 break;
748 case KC_N:
749 file_new();
750 break;
751 case KC_O:
752 file_open();
753 break;
754 case KC_S:
755 if (doc.file_name != NULL)
756 file_save(doc.file_name);
757 else
758 file_save_as();
759 break;
760 case KC_E:
761 file_save_as();
762 break;
763 case KC_C:
764 selection_copy();
765 break;
766 case KC_V:
767 edit_paste();
768 break;
769 case KC_X:
770 edit_cut();
771 break;
772 case KC_A:
773 selection_sel_all();
774 break;
775 case KC_RIGHT:
776 caret_move_word_right(false);
777 break;
778 case KC_LEFT:
779 caret_move_word_left(false);
780 break;
781 case KC_L:
782 caret_go_to_line_ask();
783 break;
784 case KC_F:
785 search_prompt(false);
786 break;
787 case KC_R:
788 search_repeat();
789 break;
790 case KC_HOME:
791 pt_get_sof(&pt);
792 caret_move(pt, false, true);
793 break;
794 case KC_END:
795 pt_get_eof(&pt);
796 caret_move(pt, false, true);
797 break;
798 default:
799 break;
800 }
801}
802
803static void key_handle_shift_ctrl(kbd_event_t const *ev)
804{
805 spt_t pt;
806 switch (ev->key) {
807 case KC_LEFT:
808 caret_move_word_left(true);
809 break;
810 case KC_RIGHT:
811 caret_move_word_right(true);
812 break;
813 case KC_F:
814 search_prompt(true);
815 break;
816 case KC_HOME:
817 pt_get_sof(&pt);
818 caret_move(pt, true, true);
819 break;
820 case KC_END:
821 pt_get_eof(&pt);
822 caret_move(pt, true, true);
823 break;
824 default:
825 break;
826 }
827}
828
829static void pos_handle(pos_event_t *ev)
830{
831 coord_t bc;
832 spt_t pt;
833 bool select;
834
835 if (ev->type == POS_PRESS && ev->vpos < (unsigned)pane.rows) {
836 bc.row = pane.sh_row + ev->vpos - pane.rect.p0.y;
837 bc.column = pane.sh_column + ev->hpos - pane.rect.p0.x;
838 sheet_get_cell_pt(doc.sh, &bc, dir_before, &pt);
839
840 select = (pane.keymod & KM_SHIFT) != 0;
841
842 caret_move(pt, select, true);
843 pane_update(&pane);
844 }
845}
846
847/** Move caret while preserving or resetting selection. */
848static void caret_move(spt_t new_caret_pt, bool select, bool update_ideal_column)
849{
850 spt_t old_caret_pt, old_sel_pt;
851 coord_t c_old, c_new;
852 bool had_sel;
853
854 /* Check if we had selection before. */
855 tag_get_pt(&pane.caret_pos, &old_caret_pt);
856 tag_get_pt(&pane.sel_start, &old_sel_pt);
857 had_sel = !spt_equal(&old_caret_pt, &old_sel_pt);
858
859 /* Place tag of the caret */
860 sheet_remove_tag(doc.sh, &pane.caret_pos);
861 sheet_place_tag(doc.sh, &new_caret_pt, &pane.caret_pos);
862
863 if (select == false) {
864 /* Move sel_start to the same point as caret. */
865 sheet_remove_tag(doc.sh, &pane.sel_start);
866 sheet_place_tag(doc.sh, &new_caret_pt, &pane.sel_start);
867 }
868
869 spt_get_coord(&new_caret_pt, &c_new);
870 if (select) {
871 spt_get_coord(&old_caret_pt, &c_old);
872
873 if (c_old.row == c_new.row)
874 pane.rflags |= REDRAW_ROW;
875 else
876 pane.rflags |= REDRAW_TEXT;
877
878 } else if (had_sel == true) {
879 /* Redraw because text was unselected. */
880 pane.rflags |= REDRAW_TEXT;
881 }
882
883 if (update_ideal_column)
884 pane.ideal_column = c_new.column;
885
886 caret_update();
887}
888
889static void key_handle_movement(unsigned int key, bool select)
890{
891 spt_t pt;
892 switch (key) {
893 case KC_LEFT:
894 caret_move_relative(0, -1, dir_before, select);
895 break;
896 case KC_RIGHT:
897 caret_move_relative(0, 0, dir_after, select);
898 break;
899 case KC_UP:
900 caret_move_relative(-1, 0, dir_before, select);
901 break;
902 case KC_DOWN:
903 caret_move_relative(+1, 0, dir_before, select);
904 break;
905 case KC_HOME:
906 tag_get_pt(&pane.caret_pos, &pt);
907 pt_get_sol(&pt, &pt);
908 caret_move(pt, select, true);
909 break;
910 case KC_END:
911 tag_get_pt(&pane.caret_pos, &pt);
912 pt_get_eol(&pt, &pt);
913 caret_move(pt, select, true);
914 break;
915 case KC_PAGE_UP:
916 caret_move_relative(-pane.rows, 0, dir_before, select);
917 break;
918 case KC_PAGE_DOWN:
919 caret_move_relative(+pane.rows, 0, dir_before, select);
920 break;
921 default:
922 break;
923 }
924}
925
926/** Create new document. */
927static errno_t file_new(void)
928{
929 errno_t rc;
930 sheet_t *sh;
931
932 /* Create empty sheet. */
933 rc = sheet_create(&sh);
934 if (rc != EOK) {
935 printf("Out of memory.\n");
936 return ENOMEM;
937 }
938
939 if (doc.sh != NULL)
940 sheet_destroy(doc.sh);
941
942 doc.sh = sh;
943
944 /* Place caret at the beginning of file. */
945 spt_t sof;
946 pt_get_sof(&sof);
947 sheet_place_tag(doc.sh, &sof, &pane.caret_pos);
948 pane.ideal_column = 1;
949
950 doc.file_name = NULL;
951
952 /* Place selection start tag. */
953 sheet_place_tag(doc.sh, &sof, &pane.sel_start);
954
955 /* Move to beginning of file. */
956 pt_get_sof(&sof);
957
958 caret_move(sof, true, true);
959
960 pane_status_display(&pane);
961 pane_caret_display(&pane);
962 pane_text_display(&pane);
963 cursor_setvis(true);
964
965 return EOK;
966}
967
968/** Open Open File dialog. */
969static void file_open(void)
970{
971 const char *old_fname = (doc.file_name != NULL) ? doc.file_name : "";
972 ui_file_dialog_params_t fdparams;
973 ui_file_dialog_t *dialog;
974 errno_t rc;
975
976 ui_file_dialog_params_init(&fdparams);
977 fdparams.caption = "Open File";
978 fdparams.ifname = old_fname;
979
980 rc = ui_file_dialog_create(edit.ui, &fdparams, &dialog);
981 if (rc != EOK) {
982 printf("Error creating message dialog.\n");
983 return;
984 }
985
986 ui_file_dialog_set_cb(dialog, &open_dialog_cb, &edit);
987}
988
989/** Open exising document. */
990static errno_t file_open_file(const char *fname)
991{
992 errno_t rc;
993 sheet_t *sh;
994 char *fn;
995
996 /* Create empty sheet. */
997 rc = sheet_create(&sh);
998 if (rc != EOK) {
999 printf("Out of memory.\n");
1000 return ENOMEM;
1001 }
1002
1003 fn = str_dup(fname);
1004 if (fn == NULL) {
1005 sheet_destroy(sh);
1006 return ENOMEM;
1007 }
1008
1009 if (doc.sh != NULL)
1010 sheet_destroy(doc.sh);
1011
1012 doc.sh = sh;
1013
1014 /* Place caret at the beginning of file. */
1015 spt_t sof;
1016 pt_get_sof(&sof);
1017 sheet_place_tag(doc.sh, &sof, &pane.caret_pos);
1018 pane.ideal_column = 1;
1019
1020 rc = file_insert(fname);
1021 if (rc != EOK)
1022 return rc;
1023
1024 doc.file_name = fn;
1025
1026 /* Place selection start tag. */
1027 sheet_place_tag(doc.sh, &sof, &pane.sel_start);
1028
1029 /* Move to beginning of file. */
1030 pt_get_sof(&sof);
1031
1032 caret_move(sof, true, true);
1033
1034 pane_status_display(&pane);
1035 pane_caret_display(&pane);
1036 pane_text_display(&pane);
1037 cursor_setvis(true);
1038
1039 return EOK;
1040}
1041
1042/** Save the document. */
1043static errno_t file_save(char const *fname)
1044{
1045 spt_t sp, ep;
1046 errno_t rc;
1047
1048 status_display("Saving...");
1049 pt_get_sof(&sp);
1050 pt_get_eof(&ep);
1051
1052 rc = file_save_range(fname, &sp, &ep);
1053
1054 switch (rc) {
1055 case EINVAL:
1056 status_display("Error opening file!");
1057 break;
1058 case EIO:
1059 status_display("Error writing data!");
1060 break;
1061 default:
1062 status_display("File saved.");
1063 break;
1064 }
1065
1066 return rc;
1067}
1068
1069/** Open Save As dialog. */
1070static void file_save_as(void)
1071{
1072 const char *old_fname = (doc.file_name != NULL) ? doc.file_name : "";
1073 ui_file_dialog_params_t fdparams;
1074 ui_file_dialog_t *dialog;
1075 errno_t rc;
1076
1077 ui_file_dialog_params_init(&fdparams);
1078 fdparams.caption = "Save As";
1079 fdparams.ifname = old_fname;
1080
1081 rc = ui_file_dialog_create(edit.ui, &fdparams, &dialog);
1082 if (rc != EOK) {
1083 printf("Error creating message dialog.\n");
1084 return;
1085 }
1086
1087 ui_file_dialog_set_cb(dialog, &save_as_dialog_cb, &edit);
1088}
1089
1090/** Insert file at caret position.
1091 *
1092 * Reads in the contents of a file and inserts them at the current position
1093 * of the caret.
1094 */
1095static errno_t file_insert(const char *fname)
1096{
1097 FILE *f;
1098 char32_t c;
1099 char buf[BUF_SIZE];
1100 int bcnt;
1101 int n_read;
1102 size_t off;
1103
1104 f = fopen(fname, "rt");
1105 if (f == NULL)
1106 return EINVAL;
1107
1108 bcnt = 0;
1109
1110 while (true) {
1111 if (bcnt < STR_BOUNDS(1)) {
1112 n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
1113 bcnt += n_read;
1114 }
1115
1116 off = 0;
1117 c = str_decode(buf, &off, bcnt);
1118 if (c == '\0')
1119 break;
1120
1121 bcnt -= off;
1122 memmove(buf, buf + off, bcnt);
1123
1124 insert_char(c);
1125 }
1126
1127 fclose(f);
1128
1129 return EOK;
1130}
1131
1132/** Save a range of text into a file. */
1133static errno_t file_save_range(char const *fname, spt_t const *spos,
1134 spt_t const *epos)
1135{
1136 FILE *f;
1137 char buf[BUF_SIZE];
1138 spt_t sp, bep;
1139 size_t bytes, n_written;
1140
1141 f = fopen(fname, "wt");
1142 if (f == NULL)
1143 return EINVAL;
1144
1145 sp = *spos;
1146
1147 do {
1148 sheet_copy_out(doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
1149 bytes = str_size(buf);
1150
1151 n_written = fwrite(buf, 1, bytes, f);
1152 if (n_written != bytes) {
1153 return EIO;
1154 }
1155
1156 sp = bep;
1157 } while (!spt_equal(&bep, epos));
1158
1159 if (fclose(f) < 0)
1160 return EIO;
1161
1162 return EOK;
1163}
1164
1165/** Return contents of range as a new string. */
1166static char *range_get_str(spt_t const *spos, spt_t const *epos)
1167{
1168 char *buf;
1169 spt_t sp, bep;
1170 size_t bytes;
1171 size_t buf_size, bpos;
1172
1173 buf_size = 1;
1174
1175 buf = malloc(buf_size);
1176 if (buf == NULL)
1177 return NULL;
1178
1179 bpos = 0;
1180 sp = *spos;
1181
1182 while (true) {
1183 sheet_copy_out(doc.sh, &sp, epos, &buf[bpos], buf_size - bpos,
1184 &bep);
1185 bytes = str_size(&buf[bpos]);
1186 bpos += bytes;
1187 sp = bep;
1188
1189 if (spt_equal(&bep, epos))
1190 break;
1191
1192 buf_size *= 2;
1193 char *tmp = realloc(buf, buf_size);
1194 if (tmp == NULL) {
1195 free(buf);
1196 return NULL;
1197 }
1198 buf = tmp;
1199 }
1200
1201 return buf;
1202}
1203
1204/** Initialize pane.
1205 *
1206 * TODO: Replace with pane_create() that allocates the pane.
1207 *
1208 * @param window Editor window
1209 * @param pane Pane
1210 * @return EOK on success or an error code
1211 */
1212static errno_t pane_init(ui_window_t *window, pane_t *pane)
1213{
1214 errno_t rc;
1215 gfx_rect_t arect;
1216
1217 pane->control = NULL;
1218 pane->color = NULL;
1219 pane->sel_color = NULL;
1220
1221 rc = ui_control_new(&pane_ctl_ops, (void *) pane, &pane->control);
1222 if (rc != EOK)
1223 goto error;
1224
1225 rc = gfx_color_new_ega(0x07, &pane->color);
1226 if (rc != EOK)
1227 goto error;
1228
1229 rc = gfx_color_new_ega(0x1e, &pane->sel_color);
1230 if (rc != EOK)
1231 goto error;
1232
1233 pane->res = ui_window_get_res(window);
1234 pane->window = window;
1235
1236 ui_window_get_app_rect(window, &arect);
1237 pane->rect.p0.x = arect.p0.x;
1238 pane->rect.p0.y = arect.p0.y + 1;
1239 pane->rect.p1.x = arect.p1.x;
1240 pane->rect.p1.y = arect.p1.y - 1;
1241
1242 pane->columns = pane->rect.p1.x - pane->rect.p0.x;
1243 pane->rows = pane->rect.p1.y - pane->rect.p0.y;
1244
1245 return EOK;
1246error:
1247 if (pane->control != NULL) {
1248 ui_control_delete(pane->control);
1249 pane->control = NULL;
1250 }
1251
1252 if (pane->color != NULL) {
1253 gfx_color_delete(pane->color);
1254 pane->color = NULL;
1255 }
1256
1257 return rc;
1258}
1259
1260/** Finalize pane.
1261 *
1262 * TODO: Replace with pane_destroy() that deallocates the pane.
1263 *
1264 * @param pane Pane
1265 */
1266static void pane_fini(pane_t *pane)
1267{
1268 gfx_color_delete(pane->color);
1269 pane->color = NULL;
1270 gfx_color_delete(pane->sel_color);
1271 pane->sel_color = NULL;
1272 ui_control_delete(pane->control);
1273 pane->control = NULL;
1274}
1275
1276/** Return base control object for a pane.
1277 *
1278 * @param pane Pane
1279 * @return Base UI cntrol
1280 */
1281static ui_control_t *pane_ctl(pane_t *pane)
1282{
1283 return pane->control;
1284}
1285
1286/** Repaint parts of pane that need updating.
1287 *
1288 * @param pane Pane
1289 * @return EOK on succes or an error code
1290 */
1291static errno_t pane_update(pane_t *pane)
1292{
1293 errno_t rc;
1294
1295 if (pane->rflags & REDRAW_TEXT) {
1296 rc = pane_text_display(pane);
1297 if (rc != EOK)
1298 return rc;
1299 }
1300
1301 if (pane->rflags & REDRAW_ROW)
1302 pane_row_display();
1303
1304 if (pane->rflags & REDRAW_STATUS)
1305 pane_status_display(pane);
1306
1307 if (pane->rflags & REDRAW_CARET)
1308 pane_caret_display(pane);
1309
1310 pane->rflags &= ~(REDRAW_TEXT | REDRAW_ROW | REDRAW_STATUS |
1311 REDRAW_CARET);
1312 return EOK;
1313}
1314
1315/** Display pane text.
1316 *
1317 * @param pane Pane
1318 * @return EOK on success or an error code
1319 */
1320static errno_t pane_text_display(pane_t *pane)
1321{
1322 gfx_rect_t rect;
1323 gfx_context_t *gc;
1324 errno_t rc;
1325 int sh_rows, rows;
1326
1327 sheet_get_num_rows(doc.sh, &sh_rows);
1328 rows = min(sh_rows - pane->sh_row + 1, pane->rows);
1329
1330 /* Draw rows from the sheet. */
1331
1332 rc = pane_row_range_display(pane, 0, rows);
1333 if (rc != EOK)
1334 return rc;
1335
1336 /* Clear the remaining rows if file is short. */
1337
1338 gc = ui_window_get_gc(pane->window);
1339
1340 rc = gfx_set_color(gc, pane->color);
1341 if (rc != EOK)
1342 goto error;
1343
1344 rect.p0.x = pane->rect.p0.x;
1345 rect.p0.y = pane->rect.p0.y + rows;
1346 rect.p1.x = pane->rect.p1.x;
1347 rect.p1.y = pane->rect.p1.y;
1348
1349 rc = gfx_fill_rect(gc, &rect);
1350 if (rc != EOK)
1351 goto error;
1352
1353 pane->rflags &= ~REDRAW_ROW;
1354 return EOK;
1355error:
1356 return rc;
1357}
1358
1359/** Display just the row where the caret is. */
1360static void pane_row_display(void)
1361{
1362 spt_t caret_pt;
1363 coord_t coord;
1364 int ridx;
1365
1366 tag_get_pt(&pane.caret_pos, &caret_pt);
1367 spt_get_coord(&caret_pt, &coord);
1368
1369 ridx = coord.row - pane.sh_row;
1370 (void) pane_row_range_display(&pane, ridx, ridx + 1);
1371 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
1372}
1373
1374/** Display a range of rows of text.
1375 *
1376 * @param r0 Start row (inclusive)
1377 * @param r1 End row (exclusive)
1378 * @return EOk on success or an error code
1379 */
1380static errno_t pane_row_range_display(pane_t *pane, int r0, int r1)
1381{
1382 int i, fill;
1383 spt_t rb, re, dep, pt;
1384 coord_t rbc, rec;
1385 char row_buf[ROW_BUF_SIZE];
1386 char cbuf[STR_BOUNDS(1) + 1];
1387 char32_t c;
1388 size_t pos, size;
1389 size_t cpos;
1390 int s_column;
1391 coord_t csel_start, csel_end, ctmp;
1392 gfx_font_t *font;
1393 gfx_context_t *gc;
1394 gfx_text_fmt_t fmt;
1395 gfx_coord2_t tpos;
1396 gfx_rect_t rect;
1397 errno_t rc;
1398
1399 font = ui_resource_get_font(edit.ui_res);
1400 gc = ui_window_get_gc(edit.window);
1401
1402 gfx_text_fmt_init(&fmt);
1403 fmt.font = font;
1404 fmt.color = pane->color;
1405
1406 /* Determine selection start and end. */
1407
1408 tag_get_pt(&pane->sel_start, &pt);
1409 spt_get_coord(&pt, &csel_start);
1410
1411 tag_get_pt(&pane->caret_pos, &pt);
1412 spt_get_coord(&pt, &csel_end);
1413
1414 if (coord_cmp(&csel_start, &csel_end) > 0) {
1415 ctmp = csel_start;
1416 csel_start = csel_end;
1417 csel_end = ctmp;
1418 }
1419
1420 /* Draw rows from the sheet. */
1421
1422 for (i = r0; i < r1; ++i) {
1423 tpos.x = pane->rect.p0.x;
1424 tpos.y = pane->rect.p0.y + i;
1425
1426 /* Starting point for row display */
1427 rbc.row = pane->sh_row + i;
1428 rbc.column = pane->sh_column;
1429 sheet_get_cell_pt(doc.sh, &rbc, dir_before, &rb);
1430
1431 /* Ending point for row display */
1432 rec.row = pane->sh_row + i;
1433 rec.column = pane->sh_column + pane->columns;
1434 sheet_get_cell_pt(doc.sh, &rec, dir_before, &re);
1435
1436 /* Copy the text of the row to the buffer. */
1437 sheet_copy_out(doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
1438
1439 /* Display text from the buffer. */
1440
1441 if (coord_cmp(&csel_start, &rbc) <= 0 &&
1442 coord_cmp(&rbc, &csel_end) < 0) {
1443 fmt.color = pane->sel_color;
1444 }
1445
1446 size = str_size(row_buf);
1447 pos = 0;
1448 s_column = pane->sh_column;
1449 while (pos < size) {
1450 if ((csel_start.row == rbc.row) && (csel_start.column == s_column))
1451 fmt.color = pane->sel_color;
1452
1453 if ((csel_end.row == rbc.row) && (csel_end.column == s_column))
1454 fmt.color = pane->color;
1455
1456 c = str_decode(row_buf, &pos, size);
1457 if (c != '\t') {
1458 cpos = 0;
1459 rc = chr_encode(c, cbuf, &cpos, sizeof(cbuf));
1460 if (rc != EOK)
1461 return rc;
1462
1463 rc = gfx_puttext(&tpos, &fmt, cbuf);
1464 if (rc != EOK)
1465 return rc;
1466
1467 s_column += 1;
1468 tpos.x++;
1469 } else {
1470 fill = 1 + ALIGN_UP(s_column, TAB_WIDTH) -
1471 s_column;
1472
1473 rc = gfx_set_color(gc, fmt.color);
1474 if (rc != EOK)
1475 return rc;
1476
1477 rect.p0.x = tpos.x;
1478 rect.p0.y = tpos.y;
1479 rect.p1.x = tpos.x + fill;
1480 rect.p1.y = tpos.y + 1;
1481
1482 rc = gfx_fill_rect(gc, &rect);
1483 if (rc != EOK)
1484 return rc;
1485
1486 s_column += fill;
1487 tpos.x += fill;
1488 }
1489 }
1490
1491 if ((csel_end.row == rbc.row) && (csel_end.column == s_column))
1492 fmt.color = pane->color;
1493
1494 /* Fill until the end of display area. */
1495
1496 rc = gfx_set_color(gc, fmt.color);
1497 if (rc != EOK)
1498 return rc;
1499
1500 rect.p0.x = tpos.x;
1501 rect.p0.y = tpos.y;
1502 rect.p1.x = pane->rect.p1.x;
1503 rect.p1.y = tpos.y + 1;
1504
1505 rc = gfx_fill_rect(gc, &rect);
1506 if (rc != EOK)
1507 return rc;
1508 }
1509
1510 return EOK;
1511}
1512
1513/** Display pane status in the status line.
1514 *
1515 * @param pane Pane
1516 */
1517static void pane_status_display(pane_t *pane)
1518{
1519 spt_t caret_pt;
1520 coord_t coord;
1521 int last_row;
1522 char *fname;
1523 char *p;
1524 char *text;
1525 size_t n;
1526 size_t nextra;
1527 size_t fnw;
1528
1529 tag_get_pt(&pane->caret_pos, &caret_pt);
1530 spt_get_coord(&caret_pt, &coord);
1531
1532 sheet_get_num_rows(doc.sh, &last_row);
1533
1534 if (doc.file_name != NULL) {
1535 /* Remove directory component */
1536 p = str_rchr(doc.file_name, '/');
1537 if (p != NULL)
1538 fname = str_dup(p + 1);
1539 else
1540 fname = str_dup(doc.file_name);
1541 } else {
1542 fname = str_dup("<unnamed>");
1543 }
1544
1545 if (fname == NULL)
1546 return;
1547
1548 /*
1549 * Make sure the status fits on the screen. This loop should
1550 * be executed at most twice.
1551 */
1552 while (true) {
1553 int rc = asprintf(&text, "%d, %d (%d): File '%s'. Ctrl-Q Quit "
1554 "F10 Menu", coord.row, coord.column, last_row, fname);
1555 if (rc < 0) {
1556 n = 0;
1557 goto finish;
1558 }
1559
1560 /* If it already fits, we're done */
1561 n = str_width(text);
1562 if ((int)n <= pane->columns - 2)
1563 break;
1564
1565 /* Compute number of excess characters */
1566 nextra = n - (pane->columns - 2);
1567 /** With of the file name part */
1568 fnw = str_width(fname);
1569
1570 /*
1571 * If reducing file name to two characters '..' won't help,
1572 * just give up and print a blank status.
1573 */
1574 if (nextra > fnw - 2) {
1575 text[0] = '\0';
1576 goto finish;
1577 }
1578
1579 /* Compute position where we overwrite with '..\0' */
1580 if (fnw >= nextra + 2) {
1581 p = fname + str_lsize(fname, fnw - nextra - 2);
1582 } else {
1583 p = fname;
1584 }
1585
1586 /* Shorten the string */
1587 p[0] = p[1] = '.';
1588 p[2] = '\0';
1589
1590 /* Need to format the string once more. */
1591 free(text);
1592 }
1593
1594finish:
1595 (void) ui_label_set_text(edit.status, text);
1596 (void) ui_label_paint(edit.status);
1597 free(text);
1598 free(fname);
1599}
1600
1601/** Set cursor to reflect position of the caret.
1602 *
1603 * @param pane Pane
1604 */
1605static void pane_caret_display(pane_t *pane)
1606{
1607 spt_t caret_pt;
1608 coord_t coord;
1609 gfx_coord2_t pos;
1610 gfx_context_t *gc;
1611
1612 tag_get_pt(&pane->caret_pos, &caret_pt);
1613
1614 spt_get_coord(&caret_pt, &coord);
1615
1616 gc = ui_window_get_gc(edit.window);
1617 pos.x = pane->rect.p0.x + coord.column - pane->sh_column;
1618 pos.y = pane->rect.p0.y + coord.row - pane->sh_row;
1619
1620 (void) gfx_cursor_set_pos(gc, &pos);
1621}
1622
1623/** Destroy pane control.
1624 *
1625 * @param arg Argument (pane_t *)
1626 */
1627static void pane_ctl_destroy(void *arg)
1628{
1629 pane_t *pane = (pane_t *)arg;
1630
1631 pane_fini(pane);
1632}
1633
1634/** Paint pane control.
1635 *
1636 * @param arg Argument (pane_t *)
1637 */
1638static errno_t pane_ctl_paint(void *arg)
1639{
1640 pane_t *pane = (pane_t *)arg;
1641 gfx_context_t *gc;
1642 errno_t rc;
1643
1644 gc = ui_window_get_gc(pane->window);
1645
1646 rc = pane_text_display(pane);
1647 if (rc != EOK)
1648 goto error;
1649
1650 rc = gfx_update(gc);
1651 if (rc != EOK)
1652 goto error;
1653
1654error:
1655 return rc;
1656}
1657
1658/** Handle pane control position event.
1659 *
1660 * @param arg Argument (pane_t *)
1661 * @param event Position event
1662 */
1663static ui_evclaim_t pane_ctl_pos_event(void *arg, pos_event_t *event)
1664{
1665 gfx_coord2_t pos;
1666
1667 pos.x = event->hpos;
1668 pos.y = event->vpos;
1669
1670 if (!gfx_pix_inside_rect(&pos, &pane.rect))
1671 return ui_unclaimed;
1672
1673 pos_handle(event);
1674 (void) gfx_update(ui_window_get_gc(edit.window));
1675 return ui_claimed;
1676}
1677
1678/** Insert a character at caret position. */
1679static void insert_char(char32_t c)
1680{
1681 spt_t pt;
1682 char cbuf[STR_BOUNDS(1) + 1];
1683 size_t offs;
1684
1685 tag_get_pt(&pane.caret_pos, &pt);
1686
1687 offs = 0;
1688 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
1689 cbuf[offs] = '\0';
1690
1691 (void) sheet_insert(doc.sh, &pt, dir_before, cbuf);
1692
1693 pane.rflags |= REDRAW_ROW;
1694 if (c == '\n')
1695 pane.rflags |= REDRAW_TEXT;
1696}
1697
1698/** Delete the character before the caret. */
1699static void delete_char_before(void)
1700{
1701 spt_t sp, ep;
1702 coord_t coord;
1703
1704 tag_get_pt(&pane.caret_pos, &ep);
1705 spt_get_coord(&ep, &coord);
1706
1707 coord.column -= 1;
1708 sheet_get_cell_pt(doc.sh, &coord, dir_before, &sp);
1709
1710 (void) sheet_delete(doc.sh, &sp, &ep);
1711
1712 pane.rflags |= REDRAW_ROW;
1713 if (coord.column < 1)
1714 pane.rflags |= REDRAW_TEXT;
1715}
1716
1717/** Delete the character after the caret. */
1718static void delete_char_after(void)
1719{
1720 spt_t sp, ep;
1721 coord_t sc, ec;
1722
1723 tag_get_pt(&pane.caret_pos, &sp);
1724 spt_get_coord(&sp, &sc);
1725
1726 sheet_get_cell_pt(doc.sh, &sc, dir_after, &ep);
1727 spt_get_coord(&ep, &ec);
1728
1729 (void) sheet_delete(doc.sh, &sp, &ep);
1730
1731 pane.rflags |= REDRAW_ROW;
1732 if (ec.row != sc.row)
1733 pane.rflags |= REDRAW_TEXT;
1734}
1735
1736/** Scroll pane after caret has moved.
1737 *
1738 * After modifying the position of the caret, this is called to scroll
1739 * the pane to ensure that the caret is in the visible area.
1740 */
1741static void caret_update(void)
1742{
1743 spt_t pt;
1744 coord_t coord;
1745
1746 tag_get_pt(&pane.caret_pos, &pt);
1747 spt_get_coord(&pt, &coord);
1748
1749 /* Scroll pane vertically. */
1750
1751 if (coord.row < pane.sh_row) {
1752 pane.sh_row = coord.row;
1753 pane.rflags |= REDRAW_TEXT;
1754 }
1755
1756 if (coord.row > pane.sh_row + pane.rows - 1) {
1757 pane.sh_row = coord.row - pane.rows + 1;
1758 pane.rflags |= REDRAW_TEXT;
1759 }
1760
1761 /* Scroll pane horizontally. */
1762
1763 if (coord.column < pane.sh_column) {
1764 pane.sh_column = coord.column;
1765 pane.rflags |= REDRAW_TEXT;
1766 }
1767
1768 if (coord.column > pane.sh_column + pane.columns - 1) {
1769 pane.sh_column = coord.column - pane.columns + 1;
1770 pane.rflags |= REDRAW_TEXT;
1771 }
1772
1773 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
1774}
1775
1776/** Relatively move caret position.
1777 *
1778 * Moves caret relatively to the current position. Looking at the first
1779 * character cell after the caret and moving by @a drow and @a dcolumn, we get
1780 * to a new character cell, and thus a new character. Then we either go to the
1781 * point before the the character or after it, depending on @a align_dir.
1782 *
1783 * @param select true if the selection tag should stay where it is
1784 */
1785static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir,
1786 bool select)
1787{
1788 spt_t pt;
1789 coord_t coord;
1790 int num_rows;
1791 bool pure_vertical;
1792
1793 tag_get_pt(&pane.caret_pos, &pt);
1794 spt_get_coord(&pt, &coord);
1795 coord.row += drow;
1796 coord.column += dcolumn;
1797
1798 /* Clamp coordinates. */
1799 if (drow < 0 && coord.row < 1)
1800 coord.row = 1;
1801 if (dcolumn < 0 && coord.column < 1) {
1802 if (coord.row < 2)
1803 coord.column = 1;
1804 else {
1805 coord.row--;
1806 sheet_get_row_width(doc.sh, coord.row, &coord.column);
1807 }
1808 }
1809 if (drow > 0) {
1810 sheet_get_num_rows(doc.sh, &num_rows);
1811 if (coord.row > num_rows)
1812 coord.row = num_rows;
1813 }
1814
1815 /* For purely vertical movement try attaining @c ideal_column. */
1816 pure_vertical = (dcolumn == 0 && align_dir == dir_before);
1817 if (pure_vertical)
1818 coord.column = pane.ideal_column;
1819
1820 /*
1821 * Select the point before or after the character at the designated
1822 * coordinates. The character can be wider than one cell (e.g. tab).
1823 */
1824 sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
1825
1826 /* For non-vertical movement set the new value for @c ideal_column. */
1827 caret_move(pt, select, !pure_vertical);
1828}
1829
1830/** Absolutely move caret position.
1831 *
1832 * Moves caret to a specified position. We get to a new character cell, and
1833 * thus a new character. Then we either go to the point before the the character
1834 * or after it, depending on @a align_dir.
1835 *
1836 * @param select true if the selection tag should stay where it is
1837 */
1838static void caret_move_absolute(int row, int column, enum dir_spec align_dir,
1839 bool select)
1840{
1841 coord_t coord;
1842 coord.row = row;
1843 coord.column = column;
1844
1845 spt_t pt;
1846 sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
1847
1848 caret_move(pt, select, true);
1849}
1850
1851/** Find beginning of a word to the left of spt */
1852static spt_t pt_find_word_left(spt_t spt)
1853{
1854 do {
1855 spt_prev_char(spt, &spt);
1856 } while (!pt_is_word_beginning(&spt));
1857 return spt;
1858}
1859
1860/** Find beginning of a word to the right of spt */
1861static spt_t pt_find_word_right(spt_t spt)
1862{
1863 do {
1864 spt_next_char(spt, &spt);
1865 } while (!pt_is_word_beginning(&spt));
1866 return spt;
1867}
1868
1869static void caret_move_word_left(bool select)
1870{
1871 spt_t pt;
1872 tag_get_pt(&pane.caret_pos, &pt);
1873 spt_t word_left = pt_find_word_left(pt);
1874 caret_move(word_left, select, true);
1875}
1876
1877static void caret_move_word_right(bool select)
1878{
1879 spt_t pt;
1880 tag_get_pt(&pane.caret_pos, &pt);
1881 spt_t word_right = pt_find_word_right(pt);
1882 caret_move(word_right, select, true);
1883}
1884
1885/** Ask for line and go to it. */
1886static void caret_go_to_line_ask(void)
1887{
1888 ui_prompt_dialog_params_t pdparams;
1889 ui_prompt_dialog_t *dialog;
1890 errno_t rc;
1891
1892 ui_prompt_dialog_params_init(&pdparams);
1893 pdparams.caption = "Go To Line";
1894 pdparams.prompt = "Line Number";
1895
1896 rc = ui_prompt_dialog_create(edit.ui, &pdparams, &dialog);
1897 if (rc != EOK) {
1898 printf("Error creating prompt dialog.\n");
1899 return;
1900 }
1901
1902 ui_prompt_dialog_set_cb(dialog, &go_to_line_dialog_cb, &edit);
1903}
1904
1905/* Search operations */
1906static errno_t search_spt_producer(void *data, char32_t *ret)
1907{
1908 assert(data != NULL);
1909 assert(ret != NULL);
1910 spt_t *spt = data;
1911 *ret = spt_next_char(*spt, spt);
1912 return EOK;
1913}
1914
1915static errno_t search_spt_reverse_producer(void *data, char32_t *ret)
1916{
1917 assert(data != NULL);
1918 assert(ret != NULL);
1919 spt_t *spt = data;
1920 *ret = spt_prev_char(*spt, spt);
1921 return EOK;
1922}
1923
1924static errno_t search_spt_mark(void *data, void **mark)
1925{
1926 assert(data != NULL);
1927 assert(mark != NULL);
1928 spt_t *spt = data;
1929 spt_t *new = calloc(1, sizeof(spt_t));
1930 *mark = new;
1931 if (new == NULL)
1932 return ENOMEM;
1933 *new = *spt;
1934 return EOK;
1935}
1936
1937static void search_spt_mark_free(void *data)
1938{
1939 free(data);
1940}
1941
1942static search_ops_t search_spt_ops = {
1943 .equals = char_exact_equals,
1944 .producer = search_spt_producer,
1945 .mark = search_spt_mark,
1946 .mark_free = search_spt_mark_free,
1947};
1948
1949static search_ops_t search_spt_reverse_ops = {
1950 .equals = char_exact_equals,
1951 .producer = search_spt_reverse_producer,
1952 .mark = search_spt_mark,
1953 .mark_free = search_spt_mark_free,
1954};
1955
1956/** Ask for line and go to it. */
1957static void search_prompt(bool reverse)
1958{
1959 ui_prompt_dialog_params_t pdparams;
1960 ui_prompt_dialog_t *dialog;
1961 errno_t rc;
1962
1963 ui_prompt_dialog_params_init(&pdparams);
1964 pdparams.caption = reverse ? "Reverse Search" : "Search";
1965 pdparams.prompt = "Search text";
1966 pdparams.itext = "";
1967
1968 if (pane.previous_search)
1969 pdparams.itext = pane.previous_search;
1970
1971 rc = ui_prompt_dialog_create(edit.ui, &pdparams, &dialog);
1972 if (rc != EOK) {
1973 printf("Error creating prompt dialog.\n");
1974 return;
1975 }
1976
1977 ui_prompt_dialog_set_cb(dialog, &search_dialog_cb, &edit);
1978 pane.search_reverse = reverse;
1979}
1980
1981static void search_repeat(void)
1982{
1983 if (pane.previous_search == NULL) {
1984 status_display("No previous search to repeat.");
1985 return;
1986 }
1987
1988 search(pane.previous_search, pane.previous_search_reverse);
1989}
1990
1991static void search(char *pattern, bool reverse)
1992{
1993 status_display("Searching...");
1994
1995 spt_t sp, producer_pos;
1996 tag_get_pt(&pane.caret_pos, &sp);
1997
1998 /* Start searching on the position before/after caret */
1999 if (!reverse) {
2000 spt_next_char(sp, &sp);
2001 } else {
2002 spt_prev_char(sp, &sp);
2003 }
2004 producer_pos = sp;
2005
2006 search_ops_t ops = search_spt_ops;
2007 if (reverse)
2008 ops = search_spt_reverse_ops;
2009
2010 search_t *search = search_init(pattern, &producer_pos, ops, reverse);
2011 if (search == NULL) {
2012 status_display("Failed initializing search.");
2013 return;
2014 }
2015
2016 match_t match;
2017 errno_t rc = search_next_match(search, &match);
2018 if (rc != EOK) {
2019 status_display("Failed searching.");
2020 search_fini(search);
2021 }
2022
2023 if (match.end) {
2024 status_display("Match found.");
2025 assert(match.end != NULL);
2026 spt_t *end = match.end;
2027 caret_move(*end, false, true);
2028 while (match.length > 0) {
2029 match.length--;
2030 if (reverse) {
2031 spt_next_char(*end, end);
2032 } else {
2033 spt_prev_char(*end, end);
2034 }
2035 }
2036 caret_move(*end, true, true);
2037 free(end);
2038 } else {
2039 status_display("Not found.");
2040 }
2041
2042 search_fini(search);
2043}
2044
2045/** Check for non-empty selection. */
2046static bool selection_active(void)
2047{
2048 return (tag_cmp(&pane.caret_pos, &pane.sel_start) != 0);
2049}
2050
2051static void selection_get_points(spt_t *pa, spt_t *pb)
2052{
2053 spt_t pt;
2054
2055 tag_get_pt(&pane.sel_start, pa);
2056 tag_get_pt(&pane.caret_pos, pb);
2057
2058 if (spt_cmp(pa, pb) > 0) {
2059 pt = *pa;
2060 *pa = *pb;
2061 *pb = pt;
2062 }
2063}
2064
2065/** Delete selected text. */
2066static void selection_delete(void)
2067{
2068 spt_t pa, pb;
2069 coord_t ca, cb;
2070 int rel;
2071
2072 tag_get_pt(&pane.sel_start, &pa);
2073 tag_get_pt(&pane.caret_pos, &pb);
2074 spt_get_coord(&pa, &ca);
2075 spt_get_coord(&pb, &cb);
2076 rel = coord_cmp(&ca, &cb);
2077
2078 if (rel == 0)
2079 return;
2080
2081 if (rel < 0)
2082 sheet_delete(doc.sh, &pa, &pb);
2083 else
2084 sheet_delete(doc.sh, &pb, &pa);
2085
2086 if (ca.row == cb.row)
2087 pane.rflags |= REDRAW_ROW;
2088 else
2089 pane.rflags |= REDRAW_TEXT;
2090}
2091
2092/** Select all text in the editor */
2093static void selection_sel_all(void)
2094{
2095 spt_t spt, ept;
2096
2097 pt_get_sof(&spt);
2098 pt_get_eof(&ept);
2099
2100 selection_sel_range(spt, ept);
2101}
2102
2103/** Select select all text in a given range with the given direction */
2104static void selection_sel_range(spt_t pa, spt_t pb)
2105{
2106 sheet_remove_tag(doc.sh, &pane.sel_start);
2107 sheet_place_tag(doc.sh, &pa, &pane.sel_start);
2108 sheet_remove_tag(doc.sh, &pane.caret_pos);
2109 sheet_place_tag(doc.sh, &pb, &pane.caret_pos);
2110
2111 pane.rflags |= REDRAW_TEXT;
2112 caret_update();
2113}
2114
2115static void selection_copy(void)
2116{
2117 spt_t pa, pb;
2118 char *str;
2119
2120 selection_get_points(&pa, &pb);
2121 str = range_get_str(&pa, &pb);
2122 if (str == NULL || clipboard_put_str(str) != EOK) {
2123 status_display("Copying to clipboard failed!");
2124 }
2125 free(str);
2126}
2127
2128static void edit_paste(void)
2129{
2130 selection_delete();
2131 insert_clipboard_data();
2132 pane.rflags |= (REDRAW_TEXT | REDRAW_CARET);
2133 pane_update(&pane);
2134}
2135
2136static void edit_cut(void)
2137{
2138 selection_copy();
2139 selection_delete();
2140 pane.rflags |= (REDRAW_TEXT | REDRAW_CARET);
2141 pane_update(&pane);
2142}
2143
2144static void insert_clipboard_data(void)
2145{
2146 char *str;
2147 size_t off;
2148 char32_t c;
2149 errno_t rc;
2150
2151 rc = clipboard_get_str(&str);
2152 if (rc != EOK || str == NULL)
2153 return;
2154
2155 off = 0;
2156
2157 while (true) {
2158 c = str_decode(str, &off, STR_NO_LIMIT);
2159 if (c == '\0')
2160 break;
2161
2162 insert_char(c);
2163 }
2164
2165 free(str);
2166}
2167
2168/** Get start-of-file s-point. */
2169static void pt_get_sof(spt_t *pt)
2170{
2171 coord_t coord;
2172
2173 coord.row = coord.column = 1;
2174 sheet_get_cell_pt(doc.sh, &coord, dir_before, pt);
2175}
2176
2177/** Get end-of-file s-point. */
2178static void pt_get_eof(spt_t *pt)
2179{
2180 coord_t coord;
2181 int num_rows;
2182
2183 sheet_get_num_rows(doc.sh, &num_rows);
2184 coord.row = num_rows + 1;
2185 coord.column = 1;
2186
2187 sheet_get_cell_pt(doc.sh, &coord, dir_after, pt);
2188}
2189
2190/** Get start-of-line s-point for given s-point cpt */
2191static void pt_get_sol(spt_t *cpt, spt_t *spt)
2192{
2193 coord_t coord;
2194
2195 spt_get_coord(cpt, &coord);
2196 coord.column = 1;
2197
2198 sheet_get_cell_pt(doc.sh, &coord, dir_before, spt);
2199}
2200
2201/** Get end-of-line s-point for given s-point cpt */
2202static void pt_get_eol(spt_t *cpt, spt_t *ept)
2203{
2204 coord_t coord;
2205 int row_width;
2206
2207 spt_get_coord(cpt, &coord);
2208 sheet_get_row_width(doc.sh, coord.row, &row_width);
2209 coord.column = row_width - 1;
2210
2211 sheet_get_cell_pt(doc.sh, &coord, dir_after, ept);
2212}
2213
2214/** Check whether the spt is at a beginning of a word */
2215static bool pt_is_word_beginning(spt_t *pt)
2216{
2217 spt_t lp, sfp, efp, slp, elp;
2218 coord_t coord;
2219
2220 pt_get_sof(&sfp);
2221 pt_get_eof(&efp);
2222 pt_get_sol(pt, &slp);
2223 pt_get_eol(pt, &elp);
2224
2225 /* the spt is at the beginning or end of the file or line */
2226 if ((spt_cmp(&sfp, pt) == 0) || (spt_cmp(&efp, pt) == 0) ||
2227 (spt_cmp(&slp, pt) == 0) || (spt_cmp(&elp, pt) == 0))
2228 return true;
2229
2230 /* the spt is a delimiter */
2231 if (pt_is_delimiter(pt))
2232 return false;
2233
2234 spt_get_coord(pt, &coord);
2235
2236 coord.column -= 1;
2237 sheet_get_cell_pt(doc.sh, &coord, dir_before, &lp);
2238
2239 return pt_is_delimiter(&lp) ||
2240 (pt_is_punctuation(pt) && !pt_is_punctuation(&lp)) ||
2241 (pt_is_punctuation(&lp) && !pt_is_punctuation(pt));
2242}
2243
2244static char32_t get_first_wchar(const char *str)
2245{
2246 size_t offset = 0;
2247 return str_decode(str, &offset, str_size(str));
2248}
2249
2250static bool pt_is_delimiter(spt_t *pt)
2251{
2252 spt_t rp;
2253 coord_t coord;
2254 char *ch = NULL;
2255
2256 spt_get_coord(pt, &coord);
2257
2258 coord.column += 1;
2259 sheet_get_cell_pt(doc.sh, &coord, dir_after, &rp);
2260
2261 ch = range_get_str(pt, &rp);
2262 if (ch == NULL)
2263 return false;
2264
2265 char32_t first_char = get_first_wchar(ch);
2266 switch (first_char) {
2267 case ' ':
2268 case '\t':
2269 case '\n':
2270 return true;
2271 default:
2272 return false;
2273 }
2274}
2275
2276static bool pt_is_punctuation(spt_t *pt)
2277{
2278 spt_t rp;
2279 coord_t coord;
2280 char *ch = NULL;
2281
2282 spt_get_coord(pt, &coord);
2283
2284 coord.column += 1;
2285 sheet_get_cell_pt(doc.sh, &coord, dir_after, &rp);
2286
2287 ch = range_get_str(pt, &rp);
2288 if (ch == NULL)
2289 return false;
2290
2291 char32_t first_char = get_first_wchar(ch);
2292 switch (first_char) {
2293 case ',':
2294 case '.':
2295 case ';':
2296 case ':':
2297 case '/':
2298 case '?':
2299 case '\\':
2300 case '|':
2301 case '_':
2302 case '+':
2303 case '-':
2304 case '*':
2305 case '=':
2306 case '<':
2307 case '>':
2308 return true;
2309 default:
2310 return false;
2311 }
2312}
2313
2314/** Compare tags. */
2315static int tag_cmp(tag_t const *a, tag_t const *b)
2316{
2317 spt_t pa, pb;
2318
2319 tag_get_pt(a, &pa);
2320 tag_get_pt(b, &pb);
2321
2322 return spt_cmp(&pa, &pb);
2323}
2324
2325/** Compare s-points. */
2326static int spt_cmp(spt_t const *a, spt_t const *b)
2327{
2328 coord_t ca, cb;
2329
2330 spt_get_coord(a, &ca);
2331 spt_get_coord(b, &cb);
2332
2333 return coord_cmp(&ca, &cb);
2334}
2335
2336/** Compare coordinats. */
2337static int coord_cmp(coord_t const *a, coord_t const *b)
2338{
2339 if (a->row - b->row != 0)
2340 return a->row - b->row;
2341
2342 return a->column - b->column;
2343}
2344
2345/** Display text in the status line. */
2346static void status_display(char const *str)
2347{
2348 (void) ui_label_set_text(edit.status, str);
2349 (void) ui_label_paint(edit.status);
2350}
2351
2352/** Window close request
2353 *
2354 * @param window Window
2355 * @param arg Argument (edit_t *)
2356 */
2357static void edit_wnd_close(ui_window_t *window, void *arg)
2358{
2359 edit_t *edit = (edit_t *) arg;
2360
2361 ui_quit(edit->ui);
2362}
2363
2364/** Window focus event
2365 *
2366 * @param window Window
2367 * @param arg Argument (edit_t *)
2368 * @param focus Focus number
2369 */
2370static void edit_wnd_focus(ui_window_t *window, void *arg, unsigned focus)
2371{
2372 edit_t *edit = (edit_t *)arg;
2373
2374 (void)edit;
2375 pane_caret_display(&pane);
2376 cursor_setvis(true);
2377}
2378
2379/** Window keyboard event
2380 *
2381 * @param window Window
2382 * @param arg Argument (edit_t *)
2383 * @param event Keyboard event
2384 */
2385static void edit_wnd_kbd_event(ui_window_t *window, void *arg,
2386 kbd_event_t *event)
2387{
2388 pane.keymod = event->mods;
2389
2390 if (ui_window_def_kbd(window, event) == ui_claimed)
2391 return;
2392
2393 if (event->type == KEY_PRESS) {
2394 key_handle_press(event);
2395 (void) pane_update(&pane);
2396 (void) gfx_update(ui_window_get_gc(window));
2397 }
2398}
2399
2400/** Window unfocus event
2401 *
2402 * @param window Window
2403 * @param arg Argument (edit_t *)
2404 * @param focus Focus number
2405 */
2406static void edit_wnd_unfocus(ui_window_t *window, void *arg, unsigned focus)
2407{
2408 edit_t *edit = (edit_t *) arg;
2409
2410 (void)edit;
2411 cursor_setvis(false);
2412}
2413
2414/** Menu bar activate event
2415 *
2416 * @param mbar Menu bar
2417 * @param arg Argument (edit_t *)
2418 */
2419static void edit_menubar_activate(ui_menu_bar_t *mbar, void *arg)
2420{
2421 edit_t *edit = (edit_t *)arg;
2422
2423 (void)edit;
2424 cursor_setvis(false);
2425}
2426
2427/** Menu bar deactivate event
2428 *
2429 * @param mbar Menu bar
2430 * @param arg Argument (edit_t *)
2431 */
2432static void edit_menubar_deactivate(ui_menu_bar_t *mbar, void *arg)
2433{
2434 edit_t *edit = (edit_t *)arg;
2435
2436 (void)edit;
2437 pane_caret_display(&pane);
2438 cursor_setvis(true);
2439}
2440
2441/** File / New menu entry selected.
2442 *
2443 * @param mentry Menu entry
2444 * @param arg Argument (edit_t *)
2445 */
2446static void edit_file_new(ui_menu_entry_t *mentry, void *arg)
2447{
2448 edit_t *edit = (edit_t *) arg;
2449
2450 (void)edit;
2451 file_new();
2452 (void) gfx_update(ui_window_get_gc(edit->window));
2453}
2454
2455/** File / Open menu entry selected.
2456 *
2457 * @param mentry Menu entry
2458 * @param arg Argument (edit_t *)
2459 */
2460static void edit_file_open(ui_menu_entry_t *mentry, void *arg)
2461{
2462 edit_t *edit = (edit_t *) arg;
2463
2464 (void)edit;
2465 file_open();
2466}
2467
2468/** File / Save menu entry selected.
2469 *
2470 * @param mentry Menu entry
2471 * @param arg Argument (edit_t *)
2472 */
2473static void edit_file_save(ui_menu_entry_t *mentry, void *arg)
2474{
2475 edit_t *edit = (edit_t *) arg;
2476
2477 (void)edit;
2478
2479 if (doc.file_name != NULL)
2480 file_save(doc.file_name);
2481 else
2482 file_save_as();
2483}
2484
2485/** File / Save As menu entry selected.
2486 *
2487 * @param mentry Menu entry
2488 * @param arg Argument (edit_t *)
2489 */
2490static void edit_file_save_as(ui_menu_entry_t *mentry, void *arg)
2491{
2492 edit_t *edit = (edit_t *) arg;
2493
2494 (void)edit;
2495 file_save_as();
2496}
2497
2498/** File / Exit menu entry selected.
2499 *
2500 * @param mentry Menu entry
2501 * @param arg Argument (edit_t *)
2502 */
2503static void edit_file_exit(ui_menu_entry_t *mentry, void *arg)
2504{
2505 edit_t *edit = (edit_t *) arg;
2506
2507 ui_quit(edit->ui);
2508}
2509
2510/** Edit / Cut menu entry selected.
2511 *
2512 * @param mentry Menu entry
2513 * @param arg Argument (edit_t *)
2514 */
2515static void edit_edit_cut(ui_menu_entry_t *mentry, void *arg)
2516{
2517 (void) arg;
2518 edit_cut();
2519 (void) gfx_update(ui_window_get_gc(edit.window));
2520}
2521
2522/** Edit / Copy menu entry selected.
2523 *
2524 * @param mentry Menu entry
2525 * @param arg Argument (edit_t *)
2526 */
2527static void edit_edit_copy(ui_menu_entry_t *mentry, void *arg)
2528{
2529 (void) arg;
2530 selection_copy();
2531}
2532
2533/** Edit / Paste menu entry selected.
2534 *
2535 * @param mentry Menu entry
2536 * @param arg Argument (edit_t *)
2537 */
2538static void edit_edit_paste(ui_menu_entry_t *mentry, void *arg)
2539{
2540 (void) arg;
2541 edit_paste();
2542 (void) gfx_update(ui_window_get_gc(edit.window));
2543}
2544
2545/** Edit / Delete menu entry selected.
2546 *
2547 * @param mentry Menu entry
2548 * @param arg Argument (edit_t *)
2549 */
2550static void edit_edit_delete(ui_menu_entry_t *mentry, void *arg)
2551{
2552 (void) arg;
2553
2554 if (selection_active())
2555 selection_delete();
2556
2557 pane.rflags |= REDRAW_CARET;
2558 (void) pane_update(&pane);
2559 (void) gfx_update(ui_window_get_gc(edit.window));
2560}
2561
2562/** Edit / Select All menu entry selected.
2563 *
2564 * @param mentry Menu entry
2565 * @param arg Argument (edit_t *)
2566 */
2567static void edit_edit_select_all(ui_menu_entry_t *mentry, void *arg)
2568{
2569 (void) arg;
2570
2571 selection_sel_all();
2572 pane.rflags |= (REDRAW_CARET | REDRAW_TEXT | REDRAW_STATUS);
2573 pane_update(&pane);
2574 (void) gfx_update(ui_window_get_gc(edit.window));
2575}
2576
2577/** Search / Find menu entry selected.
2578 *
2579 * @param mentry Menu entry
2580 * @param arg Argument (edit_t *)
2581 */
2582static void edit_search_find(ui_menu_entry_t *mentry, void *arg)
2583{
2584 (void) arg;
2585 search_prompt(false);
2586}
2587
2588/** Search / Reverse Find menu entry selected.
2589 *
2590 * @param mentry Menu entry
2591 * @param arg Argument (edit_t *)
2592 */
2593static void edit_search_reverse_find(ui_menu_entry_t *mentry, void *arg)
2594{
2595 (void) arg;
2596 search_prompt(true);
2597}
2598
2599/** Search / Find Next menu entry selected.
2600 *
2601 * @param mentry Menu entry
2602 * @param arg Argument (edit_t *)
2603 */
2604static void edit_search_find_next(ui_menu_entry_t *mentry, void *arg)
2605{
2606 (void) arg;
2607 search_repeat();
2608 (void) pane_update(&pane);
2609 (void) gfx_update(ui_window_get_gc(edit.window));
2610}
2611
2612/** Search / Go To Line menu entry selected.
2613 *
2614 * @param mentry Menu entry
2615 * @param arg Argument (edit_t *)
2616 */
2617static void edit_search_go_to_line(ui_menu_entry_t *mentry, void *arg)
2618{
2619 (void) arg;
2620 caret_go_to_line_ask();
2621}
2622
2623/** Open File dialog OK button press.
2624 *
2625 * @param dialog Open File dialog
2626 * @param arg Argument (ui_demo_t *)
2627 * @param fname File name
2628 */
2629static void open_dialog_bok(ui_file_dialog_t *dialog, void *arg,
2630 const char *fname)
2631{
2632 edit_t *edit = (edit_t *)arg;
2633 char *cname;
2634 errno_t rc;
2635
2636 (void)edit;
2637 ui_file_dialog_destroy(dialog);
2638
2639 cname = str_dup(fname);
2640 if (cname == NULL) {
2641 printf("Out of memory.\n");
2642 return;
2643 }
2644
2645 rc = file_open_file(fname);
2646 if (rc != EOK)
2647 return;
2648
2649 if (doc.file_name != NULL)
2650 free(doc.file_name);
2651 doc.file_name = cname;
2652
2653 (void) gfx_update(ui_window_get_gc(edit->window));
2654}
2655
2656/** Open File dialog cancel button press.
2657 *
2658 * @param dialog File dialog
2659 * @param arg Argument (ui_demo_t *)
2660 */
2661static void open_dialog_bcancel(ui_file_dialog_t *dialog, void *arg)
2662{
2663 edit_t *edit = (edit_t *)arg;
2664
2665 (void)edit;
2666 ui_file_dialog_destroy(dialog);
2667}
2668
2669/** Open File dialog close request.
2670 *
2671 * @param dialog File dialog
2672 * @param arg Argument (ui_demo_t *)
2673 */
2674static void open_dialog_close(ui_file_dialog_t *dialog, void *arg)
2675{
2676 edit_t *edit = (edit_t *)arg;
2677
2678 (void)edit;
2679 ui_file_dialog_destroy(dialog);
2680}
2681
2682/** Save As dialog OK button press.
2683 *
2684 * @param dialog Save As dialog
2685 * @param arg Argument (ui_demo_t *)
2686 * @param fname File name
2687 */
2688static void save_as_dialog_bok(ui_file_dialog_t *dialog, void *arg,
2689 const char *fname)
2690{
2691 edit_t *edit = (edit_t *)arg;
2692 char *cname;
2693 errno_t rc;
2694
2695 (void)edit;
2696 ui_file_dialog_destroy(dialog);
2697
2698 cname = str_dup(fname);
2699 if (cname == NULL) {
2700 printf("Out of memory.\n");
2701 return;
2702 }
2703
2704 rc = file_save(fname);
2705 if (rc != EOK)
2706 return;
2707
2708 if (doc.file_name != NULL)
2709 free(doc.file_name);
2710 doc.file_name = cname;
2711
2712}
2713
2714/** Save As dialog cancel button press.
2715 *
2716 * @param dialog File dialog
2717 * @param arg Argument (ui_demo_t *)
2718 */
2719static void save_as_dialog_bcancel(ui_file_dialog_t *dialog, void *arg)
2720{
2721 edit_t *edit = (edit_t *)arg;
2722
2723 (void)edit;
2724 ui_file_dialog_destroy(dialog);
2725}
2726
2727/** Save As dialog close request.
2728 *
2729 * @param dialog File dialog
2730 * @param arg Argument (ui_demo_t *)
2731 */
2732static void save_as_dialog_close(ui_file_dialog_t *dialog, void *arg)
2733{
2734 edit_t *edit = (edit_t *)arg;
2735
2736 (void)edit;
2737 ui_file_dialog_destroy(dialog);
2738}
2739
2740/** Go To Line dialog OK button press.
2741 *
2742 * @param dialog Go To Line dialog
2743 * @param arg Argument (ui_demo_t *)
2744 * @param text Submitted text
2745 */
2746static void go_to_line_dialog_bok(ui_prompt_dialog_t *dialog, void *arg,
2747 const char *text)
2748{
2749 edit_t *edit = (edit_t *) arg;
2750 char *endptr;
2751 int line;
2752
2753 ui_prompt_dialog_destroy(dialog);
2754 line = strtol(text, &endptr, 10);
2755 if (*endptr != '\0') {
2756 status_display("Invalid number entered.");
2757 return;
2758 }
2759
2760 caret_move_absolute(line, pane.ideal_column, dir_before, false);
2761 (void)edit;
2762 (void) pane_update(&pane);
2763}
2764
2765/** Go To Line dialog cancel button press.
2766 *
2767 * @param dialog File dialog
2768 * @param arg Argument (ui_demo_t *)
2769 */
2770static void go_to_line_dialog_bcancel(ui_prompt_dialog_t *dialog, void *arg)
2771{
2772 edit_t *edit = (edit_t *) arg;
2773
2774 (void)edit;
2775 ui_prompt_dialog_destroy(dialog);
2776}
2777
2778/** Go To Line dialog close request.
2779 *
2780 * @param dialog File dialog
2781 * @param arg Argument (ui_demo_t *)
2782 */
2783static void go_to_line_dialog_close(ui_prompt_dialog_t *dialog, void *arg)
2784{
2785 edit_t *edit = (edit_t *) arg;
2786
2787 (void)edit;
2788 ui_prompt_dialog_destroy(dialog);
2789}
2790
2791/** Search dialog OK button press.
2792 *
2793 * @param dialog Search dialog
2794 * @param arg Argument (ui_demo_t *)
2795 * @param text Submitted text
2796 */
2797static void search_dialog_bok(ui_prompt_dialog_t *dialog, void *arg,
2798 const char *text)
2799{
2800 edit_t *edit = (edit_t *) arg;
2801 char *pattern;
2802 bool reverse;
2803
2804 (void)edit;
2805 ui_prompt_dialog_destroy(dialog);
2806
2807 /* Abort if search phrase is empty */
2808 if (text[0] == '\0')
2809 return;
2810
2811 pattern = str_dup(text);
2812 reverse = pane.search_reverse;
2813
2814 if (pane.previous_search)
2815 free(pane.previous_search);
2816 pane.previous_search = pattern;
2817 pane.previous_search_reverse = reverse;
2818
2819 search(pattern, reverse);
2820
2821 (void) pane_update(&pane);
2822}
2823
2824/** Search dialog cancel button press.
2825 *
2826 * @param dialog File dialog
2827 * @param arg Argument (ui_demo_t *)
2828 */
2829static void search_dialog_bcancel(ui_prompt_dialog_t *dialog, void *arg)
2830{
2831 edit_t *edit = (edit_t *) arg;
2832
2833 (void)edit;
2834 ui_prompt_dialog_destroy(dialog);
2835}
2836
2837/** Search dialog close request.
2838 *
2839 * @param dialog File dialog
2840 * @param arg Argument (ui_demo_t *)
2841 */
2842static void search_dialog_close(ui_prompt_dialog_t *dialog, void *arg)
2843{
2844 edit_t *edit = (edit_t *) arg;
2845
2846 (void)edit;
2847 ui_prompt_dialog_destroy(dialog);
2848}
2849
2850/** @}
2851 */
Note: See TracBrowser for help on using the repository browser.