source: mainline/uspace/app/edit/edit.c@ 5de852c

serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 5de852c was 5de852c, checked in by Jiri Svoboda <jiri@…>, 4 years ago

Coordinate keyboard event delivery between application and UI framework

If an application sets its own keyboard event handler, it needs to call
ui_window_def_kbd() to deliver events to the window's UI control tree.
Menubar should be able to capture these events so that they are not
accidentally acted upon by the application keyboard handler.

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