source: mainline/uspace/lib/ui/src/entry.c@ d7f7a4a

Last change on this file since d7f7a4a was d7f7a4a, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 4 years ago

Replace some license headers with SPDX identifier

Headers are replaced using tools/transorm-copyright.sh only
when it can be matched verbatim with the license header used
throughout most of the codebase.

  • Property mode set to 100644
File size: 22.9 KB
Line 
1/*
2 * SPDX-FileCopyrightText: 2021 Jiri Svoboda
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7/** @addtogroup libui
8 * @{
9 */
10/**
11 * @file Text entry.
12 *
13 * Currentry text entry is always read-only. It differs from label mostly
14 * by its looks.
15 */
16
17#include <clipboard.h>
18#include <errno.h>
19#include <gfx/context.h>
20#include <gfx/cursor.h>
21#include <gfx/render.h>
22#include <gfx/text.h>
23#include <macros.h>
24#include <stdlib.h>
25#include <str.h>
26#include <ui/control.h>
27#include <ui/paint.h>
28#include <ui/entry.h>
29#include <ui/ui.h>
30#include <ui/window.h>
31#include "../private/entry.h"
32#include "../private/resource.h"
33
34static void ui_entry_ctl_destroy(void *);
35static errno_t ui_entry_ctl_paint(void *);
36static ui_evclaim_t ui_entry_ctl_kbd_event(void *, kbd_event_t *);
37static ui_evclaim_t ui_entry_ctl_pos_event(void *, pos_event_t *);
38
39enum {
40 ui_entry_hpad = 4,
41 ui_entry_vpad = 4,
42 ui_entry_hpad_text = 1,
43 ui_entry_vpad_text = 0,
44 ui_entry_cursor_overshoot = 1,
45 ui_entry_cursor_width = 2,
46 ui_entry_sel_hpad = 0,
47 ui_entry_sel_vpad = 2,
48 /** Additional amount to scroll to the left after revealing cursor */
49 ui_entry_left_scroll_margin = 30
50};
51
52/** Text entry control ops */
53ui_control_ops_t ui_entry_ops = {
54 .destroy = ui_entry_ctl_destroy,
55 .paint = ui_entry_ctl_paint,
56 .kbd_event = ui_entry_ctl_kbd_event,
57 .pos_event = ui_entry_ctl_pos_event
58};
59
60/** Create new text entry.
61 *
62 * @param window UI window
63 * @param text Text
64 * @param rentry Place to store pointer to new text entry
65 * @return EOK on success, ENOMEM if out of memory
66 */
67errno_t ui_entry_create(ui_window_t *window, const char *text,
68 ui_entry_t **rentry)
69{
70 ui_entry_t *entry;
71 errno_t rc;
72
73 entry = calloc(1, sizeof(ui_entry_t));
74 if (entry == NULL)
75 return ENOMEM;
76
77 rc = ui_control_new(&ui_entry_ops, (void *) entry, &entry->control);
78 if (rc != EOK) {
79 free(entry);
80 return rc;
81 }
82
83 entry->text = str_dup(text);
84 if (entry->text == NULL) {
85 ui_control_delete(entry->control);
86 free(entry);
87 return ENOMEM;
88 }
89
90 entry->window = window;
91 entry->halign = gfx_halign_left;
92 *rentry = entry;
93
94 return EOK;
95}
96
97/** Destroy text entry.
98 *
99 * @param entry Text entry or @c NULL
100 */
101void ui_entry_destroy(ui_entry_t *entry)
102{
103 if (entry == NULL)
104 return;
105
106 ui_control_delete(entry->control);
107 free(entry);
108}
109
110/** Get base control from text entry.
111 *
112 * @param entry Text entry
113 * @return Control
114 */
115ui_control_t *ui_entry_ctl(ui_entry_t *entry)
116{
117 return entry->control;
118}
119
120/** Set text entry rectangle.
121 *
122 * @param entry Text entry
123 * @param rect New text entry rectangle
124 */
125void ui_entry_set_rect(ui_entry_t *entry, gfx_rect_t *rect)
126{
127 entry->rect = *rect;
128}
129
130/** Set text entry horizontal text alignment.
131 *
132 * @param entry Text entry
133 * @param halign Horizontal alignment
134 */
135void ui_entry_set_halign(ui_entry_t *entry, gfx_halign_t halign)
136{
137 entry->halign = halign;
138 ui_entry_scroll_update(entry, true);
139 ui_entry_paint(entry);
140}
141
142/** Set text entry read-only flag.
143 *
144 * @param entry Text entry
145 * @param read_only True iff entry is to be read-only.
146 */
147void ui_entry_set_read_only(ui_entry_t *entry, bool read_only)
148{
149 entry->read_only = read_only;
150}
151
152/** Set entry text.
153 *
154 * @param entry Text entry
155 * @param text New entry text
156 * @return EOK on success, ENOMEM if out of memory
157 */
158errno_t ui_entry_set_text(ui_entry_t *entry, const char *text)
159{
160 char *tcopy;
161
162 tcopy = str_dup(text);
163 if (tcopy == NULL)
164 return ENOMEM;
165
166 free(entry->text);
167 entry->text = tcopy;
168 entry->pos = str_size(text);
169 entry->sel_start = entry->pos;
170
171 ui_entry_scroll_update(entry, false);
172 ui_entry_paint(entry);
173
174 return EOK;
175}
176
177/** Get entry text.
178 *
179 * @return Pointer to entry text.
180 */
181const char *ui_entry_get_text(ui_entry_t *entry)
182{
183 return entry->text;
184}
185
186/** Paint cursor.
187 *
188 * @param entry Text entry
189 * @param pos Cursor position (top-left corner of next character)
190 * @return EOK on success or an error code
191 */
192static errno_t ui_entry_paint_cursor(ui_entry_t *entry, gfx_coord2_t *pos)
193{
194 ui_resource_t *res;
195 gfx_rect_t rect;
196 gfx_font_metrics_t metrics;
197 errno_t rc;
198
199 res = ui_window_get_res(entry->window);
200
201 if (res->textmode) {
202 rc = gfx_cursor_set_pos(res->gc, pos);
203 return rc;
204 }
205
206 gfx_font_get_metrics(res->font, &metrics);
207
208 rect.p0.x = pos->x;
209 rect.p0.y = pos->y - ui_entry_cursor_overshoot;
210 rect.p1.x = pos->x + ui_entry_cursor_width;
211 rect.p1.y = pos->y + metrics.ascent + metrics.descent + 1 +
212 ui_entry_cursor_overshoot;
213
214 rc = gfx_set_color(res->gc, res->entry_fg_color);
215 if (rc != EOK)
216 goto error;
217
218 rc = gfx_fill_rect(res->gc, &rect);
219 if (rc != EOK)
220 goto error;
221
222 return EOK;
223error:
224 return rc;
225}
226
227/** Return width of text before cursor.
228 *
229 * @param entry Text entry
230 * @return Widht of text before cursor
231 */
232static gfx_coord_t ui_entry_lwidth(ui_entry_t *entry)
233{
234 ui_resource_t *res;
235 uint8_t tmp;
236 gfx_coord_t width;
237
238 res = ui_window_get_res(entry->window);
239
240 tmp = entry->text[entry->pos];
241
242 entry->text[entry->pos] = '\0';
243 width = gfx_text_width(res->font, entry->text);
244 entry->text[entry->pos] = tmp;
245
246 return width;
247}
248
249/** Paint text entry.
250 *
251 * @param entry Text entry
252 * @return EOK on success or an error code
253 */
254errno_t ui_entry_paint(ui_entry_t *entry)
255{
256 ui_resource_t *res;
257 ui_entry_geom_t geom;
258 gfx_text_fmt_t fmt;
259 gfx_coord2_t pos;
260 gfx_text_fmt_t cfmt;
261 gfx_coord2_t cpos;
262 gfx_rect_t inside;
263 unsigned off1, off2;
264 gfx_rect_t sel;
265 char c;
266 errno_t rc;
267
268 res = ui_window_get_res(entry->window);
269
270 ui_entry_get_geom(entry, &geom);
271
272 if (res->textmode == false) {
273 /* Paint inset frame */
274 rc = ui_paint_inset_frame(res, &entry->rect, &inside);
275 if (rc != EOK)
276 goto error;
277 } else {
278 inside = entry->rect;
279 }
280
281 /* Paint entry background */
282
283 rc = gfx_set_color(res->gc, res->entry_bg_color);
284 if (rc != EOK)
285 goto error;
286
287 rc = gfx_fill_rect(res->gc, &inside);
288 if (rc != EOK)
289 goto error;
290
291 pos = geom.text_pos;
292
293 gfx_text_fmt_init(&fmt);
294 fmt.font = res->font;
295 fmt.color = res->entry_fg_color;
296 fmt.halign = gfx_halign_left;
297 fmt.valign = gfx_valign_top;
298
299 rc = gfx_set_clip_rect(res->gc, &inside);
300 if (rc != EOK)
301 goto error;
302
303 off1 = min(entry->pos, entry->sel_start);
304 off2 = max(entry->pos, entry->sel_start);
305
306 /* Render initial segment before start of selection */
307 c = entry->text[off1];
308 entry->text[off1] = '\0';
309
310 rc = gfx_puttext(&pos, &fmt, entry->text);
311 if (rc != EOK) {
312 (void) gfx_set_clip_rect(res->gc, NULL);
313 goto error;
314 }
315
316 gfx_text_cont(&pos, &fmt, entry->text, &cpos, &cfmt);
317 entry->text[off1] = c;
318
319 /* Render selected text */
320
321 if (off1 != off2) {
322 c = entry->text[off2];
323 entry->text[off2] = '\0';
324 cfmt.color = res->entry_sel_text_fg_color;
325
326 gfx_text_rect(&cpos, &cfmt, entry->text + off1, &sel);
327 sel.p0.x -= ui_entry_sel_hpad;
328 sel.p0.y -= ui_entry_sel_vpad;
329 sel.p1.x += ui_entry_sel_hpad;
330 sel.p1.y += ui_entry_sel_vpad;
331
332 rc = gfx_set_color(res->gc, res->entry_sel_text_bg_color);
333 if (rc != EOK)
334 goto error;
335
336 rc = gfx_fill_rect(res->gc, &sel);
337 if (rc != EOK)
338 goto error;
339
340 rc = gfx_puttext(&cpos, &cfmt, entry->text + off1);
341 if (rc != EOK) {
342 (void) gfx_set_clip_rect(res->gc, NULL);
343 goto error;
344 }
345
346 gfx_text_cont(&cpos, &cfmt, entry->text + off1, &cpos, &cfmt);
347
348 entry->text[off2] = c;
349 }
350
351 /* Render trailing, non-selected text */
352 cfmt.color = res->entry_fg_color;
353
354 rc = gfx_puttext(&cpos, &cfmt, entry->text + off2);
355 if (rc != EOK) {
356 (void) gfx_set_clip_rect(res->gc, NULL);
357 goto error;
358 }
359
360 if (entry->active) {
361 /* Cursor */
362 pos.x += ui_entry_lwidth(entry);
363
364 rc = ui_entry_paint_cursor(entry, &pos);
365 if (rc != EOK) {
366 (void) gfx_set_clip_rect(res->gc, NULL);
367 goto error;
368 }
369 }
370
371 rc = gfx_set_clip_rect(res->gc, NULL);
372 if (rc != EOK)
373 goto error;
374
375 rc = gfx_update(res->gc);
376 if (rc != EOK)
377 goto error;
378
379 return EOK;
380error:
381 return rc;
382}
383
384/** Find position in text entry.
385 *
386 * @param entry Text entry
387 * @param fpos Position for which we need to find text offset
388 * @return Corresponding byte offset in entry text
389 */
390size_t ui_entry_find_pos(ui_entry_t *entry, gfx_coord2_t *fpos)
391{
392 ui_resource_t *res;
393 ui_entry_geom_t geom;
394 gfx_text_fmt_t fmt;
395
396 res = ui_window_get_res(entry->window);
397
398 ui_entry_get_geom(entry, &geom);
399
400 gfx_text_fmt_init(&fmt);
401 fmt.font = res->font;
402 fmt.halign = gfx_halign_left;
403 fmt.valign = gfx_valign_top;
404
405 return gfx_text_find_pos(&geom.text_pos, &fmt, entry->text, fpos);
406}
407
408/** Destroy text entry control.
409 *
410 * @param arg Argument (ui_entry_t *)
411 */
412void ui_entry_ctl_destroy(void *arg)
413{
414 ui_entry_t *entry = (ui_entry_t *) arg;
415
416 ui_entry_destroy(entry);
417}
418
419/** Paint text entry control.
420 *
421 * @param arg Argument (ui_entry_t *)
422 * @return EOK on success or an error code
423 */
424errno_t ui_entry_ctl_paint(void *arg)
425{
426 ui_entry_t *entry = (ui_entry_t *) arg;
427
428 return ui_entry_paint(entry);
429}
430
431/** Delete selected text.
432 *
433 * @param entry Text entry
434 */
435void ui_entry_delete_sel(ui_entry_t *entry)
436{
437 size_t off1;
438 size_t off2;
439
440 off1 = min(entry->sel_start, entry->pos);
441 off2 = max(entry->sel_start, entry->pos);
442
443 memmove(entry->text + off1, entry->text + off2,
444 str_size(entry->text + off2) + 1);
445
446 entry->pos = off1;
447 entry->sel_start = off1;
448 ui_entry_scroll_update(entry, false);
449 ui_entry_paint(entry);
450}
451
452/** Insert string at cursor position.
453 *
454 * @param entry Text entry
455 * @param str String
456 * @return EOK on success, ENOMEM if out of memory
457 */
458errno_t ui_entry_insert_str(ui_entry_t *entry, const char *str)
459{
460 uint8_t tmp;
461 char *ltext = NULL;
462 char *newtext;
463 char *oldtext;
464 int rc;
465
466 /* Do we have a selection? */
467 if (entry->sel_start != entry->pos)
468 ui_entry_delete_sel(entry);
469
470 tmp = entry->text[entry->pos];
471 entry->text[entry->pos] = '\0';
472 ltext = str_dup(entry->text);
473 if (ltext == NULL)
474 return ENOMEM;
475
476 entry->text[entry->pos] = tmp;
477
478 rc = asprintf(&newtext, "%s%s%s", ltext, str, entry->text + entry->pos);
479 if (rc < 0) {
480 free(ltext);
481 return ENOMEM;
482 }
483
484 oldtext = entry->text;
485 entry->text = newtext;
486 entry->pos += str_size(str);
487 free(oldtext);
488 free(ltext);
489
490 entry->sel_start = entry->pos;
491 ui_entry_scroll_update(entry, false);
492 ui_entry_paint(entry);
493
494 return EOK;
495}
496
497/** Delete character before cursor.
498 *
499 * @param entry Text entry
500 */
501void ui_entry_backspace(ui_entry_t *entry)
502{
503 size_t off;
504
505 /* Do we have a selection? */
506 if (entry->sel_start != entry->pos) {
507 ui_entry_delete_sel(entry);
508 return;
509 }
510
511 if (entry->pos == 0)
512 return;
513
514 /* Find offset where character before cursor starts */
515 off = entry->pos;
516 (void) str_decode_reverse(entry->text, &off,
517 str_size(entry->text));
518
519 memmove(entry->text + off, entry->text + entry->pos,
520 str_size(entry->text + entry->pos) + 1);
521 entry->pos = off;
522 entry->sel_start = off;
523
524 ui_entry_scroll_update(entry, false);
525 ui_entry_paint(entry);
526}
527
528/** Delete character after cursor.
529 *
530 * @param entry Text entry
531 */
532void ui_entry_delete(ui_entry_t *entry)
533{
534 size_t off;
535
536 /* Do we have a selection? */
537 if (entry->sel_start != entry->pos) {
538 ui_entry_delete_sel(entry);
539 return;
540 }
541
542 /* Find offset where character after cursor ends */
543 off = entry->pos;
544 (void) str_decode(entry->text, &off,
545 str_size(entry->text));
546
547 memmove(entry->text + entry->pos, entry->text + off,
548 str_size(entry->text + off) + 1);
549
550 ui_entry_scroll_update(entry, false);
551 ui_entry_paint(entry);
552}
553
554/** Copy selected text to clipboard.
555 *
556 * @param entry Text entry
557 */
558void ui_entry_copy(ui_entry_t *entry)
559{
560 unsigned off1;
561 unsigned off2;
562 char c;
563
564 off1 = min(entry->pos, entry->sel_start);
565 off2 = max(entry->pos, entry->sel_start);
566
567 c = entry->text[off2];
568 entry->text[off2] = '\0';
569
570 (void) clipboard_put_str(entry->text + off1);
571
572 entry->text[off2] = c;
573}
574
575/** Cut selected text to clipboard.
576 *
577 * @param entry Text entry
578 */
579void ui_entry_cut(ui_entry_t *entry)
580{
581 ui_entry_copy(entry);
582 ui_entry_delete_sel(entry);
583}
584
585/** Paste text from clipboard.
586 *
587 * @param entry Text entry
588 */
589void ui_entry_paste(ui_entry_t *entry)
590{
591 char *str;
592 errno_t rc;
593
594 rc = clipboard_get_str(&str);
595 if (rc != EOK)
596 return;
597
598 ui_entry_insert_str(entry, str);
599 free(str);
600}
601
602/** Handle text entry key press without modifiers.
603 *
604 * @param entry Text entry
605 * @param kbd_event Keyboard event
606 * @return @c ui_claimed iff the event is claimed
607 */
608ui_evclaim_t ui_entry_key_press_unmod(ui_entry_t *entry, kbd_event_t *event)
609{
610 assert(event->type == KEY_PRESS);
611
612 switch (event->key) {
613 case KC_BACKSPACE:
614 ui_entry_backspace(entry);
615 break;
616
617 case KC_DELETE:
618 ui_entry_delete(entry);
619 break;
620
621 case KC_ESCAPE:
622 ui_entry_deactivate(entry);
623 break;
624
625 case KC_HOME:
626 ui_entry_seek_start(entry, false);
627 break;
628
629 case KC_END:
630 ui_entry_seek_end(entry, false);
631 break;
632
633 case KC_LEFT:
634 ui_entry_seek_prev_char(entry, false);
635 break;
636
637 case KC_RIGHT:
638 ui_entry_seek_next_char(entry, false);
639 break;
640
641 default:
642 break;
643 }
644 return ui_claimed;
645}
646
647/** Handle text entry key press with shift modifier.
648 *
649 * @param entry Text entry
650 * @param kbd_event Keyboard event
651 * @return @c ui_claimed iff the event is claimed
652 */
653ui_evclaim_t ui_entry_key_press_shift(ui_entry_t *entry, kbd_event_t *event)
654{
655 assert(event->type == KEY_PRESS);
656
657 switch (event->key) {
658 case KC_HOME:
659 ui_entry_seek_start(entry, true);
660 break;
661
662 case KC_END:
663 ui_entry_seek_end(entry, true);
664 break;
665
666 case KC_LEFT:
667 ui_entry_seek_prev_char(entry, true);
668 break;
669
670 case KC_RIGHT:
671 ui_entry_seek_next_char(entry, true);
672 break;
673
674 default:
675 break;
676 }
677
678 return ui_claimed;
679}
680
681/** Handle text entry key press with control modifier.
682 *
683 * @param entry Text entry
684 * @param kbd_event Keyboard event
685 * @return @c ui_claimed iff the event is claimed
686 */
687ui_evclaim_t ui_entry_key_press_ctrl(ui_entry_t *entry, kbd_event_t *event)
688{
689 assert(event->type == KEY_PRESS);
690
691 switch (event->key) {
692 case KC_C:
693 ui_entry_copy(entry);
694 break;
695 case KC_V:
696 ui_entry_paste(entry);
697 break;
698 case KC_X:
699 ui_entry_cut(entry);
700 break;
701 default:
702 break;
703 }
704
705 return ui_claimed;
706}
707
708/** Handle text entry keyboard event.
709 *
710 * @param entry Text entry
711 * @param kbd_event Keyboard event
712 * @return @c ui_claimed iff the event is claimed
713 */
714ui_evclaim_t ui_entry_kbd_event(ui_entry_t *entry, kbd_event_t *event)
715{
716 char buf[STR_BOUNDS(1) + 1];
717 size_t off;
718 errno_t rc;
719
720 if (!entry->active)
721 return ui_unclaimed;
722
723 /*
724 * Need to keep track if any shift is held for the case
725 * of selecting by shift-click. This could be simplified
726 * if position events were decorated with modifier
727 * state.
728 */
729
730 if (event->type == KEY_PRESS && event->key == KC_LSHIFT)
731 entry->lshift_held = true;
732 if (event->type == KEY_RELEASE && event->key == KC_LSHIFT)
733 entry->lshift_held = false;
734 if (event->type == KEY_PRESS && event->key == KC_RSHIFT)
735 entry->rshift_held = true;
736 if (event->type == KEY_RELEASE && event->key == KC_RSHIFT)
737 entry->rshift_held = false;
738
739 if (event->type == KEY_PRESS &&
740 (event->mods & (KM_CTRL | KM_ALT)) == 0 && event->c >= ' ') {
741 off = 0;
742 rc = chr_encode(event->c, buf, &off, sizeof(buf));
743 if (rc == EOK) {
744 buf[off] = '\0';
745 (void) ui_entry_insert_str(entry, buf);
746 }
747 }
748
749 if (event->type == KEY_PRESS &&
750 (event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
751 return ui_entry_key_press_unmod(entry, event);
752
753 if (event->type == KEY_PRESS &&
754 (event->mods & KM_SHIFT) != 0 &&
755 (event->mods & (KM_CTRL | KM_ALT)) == 0)
756 return ui_entry_key_press_shift(entry, event);
757
758 if (event->type == KEY_PRESS &&
759 (event->mods & KM_CTRL) != 0 &&
760 (event->mods & (KM_ALT | KM_SHIFT)) == 0)
761 return ui_entry_key_press_ctrl(entry, event);
762
763 return ui_claimed;
764}
765
766/** Handle text entry position event.
767 *
768 * @param entry Text entry
769 * @param pos_event Position event
770 * @return @c ui_claimed iff the event is claimed
771 */
772ui_evclaim_t ui_entry_pos_event(ui_entry_t *entry, pos_event_t *event)
773{
774 gfx_coord2_t pos;
775
776 if (entry->read_only)
777 return ui_unclaimed;
778
779 if (event->type == POS_UPDATE) {
780 pos.x = event->hpos;
781 pos.y = event->vpos;
782
783 /* Change cursor shape when pointer is entering/leaving */
784 if (gfx_pix_inside_rect(&pos, &entry->rect)) {
785 if (!entry->pointer_inside) {
786 ui_window_set_ctl_cursor(entry->window,
787 ui_curs_ibeam);
788 entry->pointer_inside = true;
789 }
790 } else {
791 if (entry->pointer_inside) {
792 ui_window_set_ctl_cursor(entry->window,
793 ui_curs_arrow);
794 entry->pointer_inside = false;
795 }
796 }
797
798 if (entry->held) {
799 /*
800 * Selecting using mouse drag: Change pos,
801 * keep sel_start
802 */
803 entry->pos = ui_entry_find_pos(entry, &pos);
804 ui_entry_paint(entry);
805 }
806 }
807
808 if (event->type == POS_PRESS) {
809 pos.x = event->hpos;
810 pos.y = event->vpos;
811
812 if (gfx_pix_inside_rect(&pos, &entry->rect)) {
813 /* Clicked inside - activate, set position */
814 entry->held = true;
815 entry->pos = ui_entry_find_pos(entry, &pos);
816
817 /* Clear selection if no shift key is held */
818 if (!entry->lshift_held && !entry->rshift_held)
819 entry->sel_start = entry->pos;
820
821 if (entry->active)
822 ui_entry_paint(entry);
823 else
824 ui_entry_activate(entry);
825
826 return ui_claimed;
827 } else {
828 /* Clicked outside - deactivate */
829 ui_entry_deactivate(entry);
830 }
831 }
832
833 if (event->type == POS_RELEASE) {
834 entry->held = false;
835 }
836
837 return ui_unclaimed;
838}
839
840/** Handle text entry control keyboard event.
841 *
842 * @param arg Argument (ui_entry_t *)
843 * @param kbd_event Keyboard event
844 * @return @c ui_claimed iff the event is claimed
845 */
846static ui_evclaim_t ui_entry_ctl_kbd_event(void *arg, kbd_event_t *event)
847{
848 ui_entry_t *entry = (ui_entry_t *) arg;
849
850 return ui_entry_kbd_event(entry, event);
851}
852
853/** Handle text entry control position event.
854 *
855 * @param arg Argument (ui_entry_t *)
856 * @param pos_event Position event
857 * @return @c ui_claimed iff the event is claimed
858 */
859static ui_evclaim_t ui_entry_ctl_pos_event(void *arg, pos_event_t *event)
860{
861 ui_entry_t *entry = (ui_entry_t *) arg;
862
863 return ui_entry_pos_event(entry, event);
864}
865
866/** Get text entry geometry.
867 *
868 * @param entry Text entry
869 * @param geom Structure to fill in with computed geometry
870 */
871void ui_entry_get_geom(ui_entry_t *entry, ui_entry_geom_t *geom)
872{
873 gfx_coord_t hpad;
874 gfx_coord_t vpad;
875 ui_resource_t *res;
876
877 res = ui_window_get_res(entry->window);
878
879 if (res->textmode) {
880 hpad = ui_entry_hpad_text;
881 vpad = ui_entry_vpad_text;
882 } else {
883 hpad = ui_entry_hpad;
884 vpad = ui_entry_vpad;
885 }
886
887 if (res->textmode == false) {
888 ui_paint_get_inset_frame_inside(res, &entry->rect,
889 &geom->interior_rect);
890 } else {
891 geom->interior_rect = entry->rect;
892 }
893
894 geom->text_rect.p0.x = geom->interior_rect.p0.x + hpad;
895 geom->text_rect.p0.y = geom->interior_rect.p0.y + vpad;
896 geom->text_rect.p1.x = geom->interior_rect.p1.x - hpad;
897 geom->text_rect.p1.y = geom->interior_rect.p1.y - vpad;
898
899 geom->text_pos.x = geom->interior_rect.p0.x + hpad +
900 entry->scroll_pos;
901 geom->text_pos.y = geom->interior_rect.p0.y + vpad;
902
903 switch (entry->halign) {
904 case gfx_halign_left:
905 case gfx_halign_justify:
906 geom->anchor_x = geom->text_rect.p0.x;
907 break;
908 case gfx_halign_center:
909 geom->anchor_x = (geom->text_rect.p0.x +
910 geom->text_rect.p1.x) / 2;
911 break;
912 case gfx_halign_right:
913 geom->anchor_x = geom->text_rect.p1.x;
914 break;
915 }
916}
917
918/** Activate text entry.
919 *
920 * @param entry Text entry
921 */
922void ui_entry_activate(ui_entry_t *entry)
923{
924 ui_resource_t *res;
925
926 res = ui_window_get_res(entry->window);
927
928 if (entry->active)
929 return;
930
931 entry->active = true;
932 (void) ui_entry_paint(entry);
933
934 if (res->textmode)
935 gfx_cursor_set_visible(res->gc, true);
936}
937
938/** Move text cursor to the beginning of text.
939 *
940 * @param entry Text entry
941 * @param shift @c true iff shift key is pressed
942 */
943void ui_entry_seek_start(ui_entry_t *entry, bool shift)
944{
945 entry->pos = 0;
946
947 if (!shift)
948 entry->sel_start = entry->pos;
949
950 ui_entry_scroll_update(entry, false);
951 (void) ui_entry_paint(entry);
952}
953
954/** Move text cursor to the end of text.
955 *
956 * @param entry Text entry
957 * @param shift @c true iff shift key is pressed
958 */
959void ui_entry_seek_end(ui_entry_t *entry, bool shift)
960{
961 entry->pos = str_size(entry->text);
962
963 if (!shift)
964 entry->sel_start = entry->pos;
965
966 ui_entry_scroll_update(entry, false);
967 (void) ui_entry_paint(entry);
968}
969
970/** Move text cursor one character backward.
971 *
972 * @param entry Text entry
973 * @param shift @c true iff shift key is pressed
974 */
975void ui_entry_seek_prev_char(ui_entry_t *entry, bool shift)
976{
977 size_t off;
978
979 off = entry->pos;
980 (void) str_decode_reverse(entry->text, &off,
981 str_size(entry->text));
982 entry->pos = off;
983
984 if (!shift)
985 entry->sel_start = entry->pos;
986
987 ui_entry_scroll_update(entry, false);
988 (void) ui_entry_paint(entry);
989}
990
991/** Move text cursor one character forward.
992 *
993 * @param entry Text entry
994 * @param shift @c true iff shift key is pressed
995 */
996void ui_entry_seek_next_char(ui_entry_t *entry, bool shift)
997{
998 size_t off;
999
1000 off = entry->pos;
1001 (void) str_decode(entry->text, &off,
1002 str_size(entry->text));
1003 entry->pos = off;
1004
1005 if (!shift)
1006 entry->sel_start = entry->pos;
1007
1008 ui_entry_scroll_update(entry, false);
1009 (void) ui_entry_paint(entry);
1010}
1011
1012/** Deactivate text entry.
1013 *
1014 * @param entry Text entry
1015 */
1016void ui_entry_deactivate(ui_entry_t *entry)
1017{
1018 ui_resource_t *res;
1019
1020 res = ui_window_get_res(entry->window);
1021
1022 if (!entry->active)
1023 return;
1024
1025 entry->active = false;
1026 entry->sel_start = entry->pos;
1027 (void) ui_entry_paint(entry);
1028
1029 if (res->textmode)
1030 gfx_cursor_set_visible(res->gc, false);
1031}
1032
1033/** Update text entry scroll position.
1034 *
1035 * @param entry Text entry
1036 * @param realign @c true iff we should left-align short text.
1037 * This should be only used when changing text alignment,
1038 * because left-aligned text entries should not realign
1039 * the text to the left side under normal circumstances.
1040 */
1041void ui_entry_scroll_update(ui_entry_t *entry, bool realign)
1042{
1043 ui_entry_geom_t geom;
1044 gfx_coord_t x;
1045 gfx_coord_t width;
1046 gfx_coord2_t tpos;
1047 gfx_coord2_t anchor;
1048 gfx_text_fmt_t fmt;
1049 ui_resource_t *res;
1050
1051 res = ui_window_get_res(entry->window);
1052
1053 ui_entry_get_geom(entry, &geom);
1054
1055 /* Compute position where cursor is currently displayed at */
1056 x = geom.text_pos.x + ui_entry_lwidth(entry);
1057
1058 /* Is cursor off to the left? */
1059 if (x < geom.text_rect.p0.x) {
1060 /*
1061 * Scroll to make cursor visible and put some space between it
1062 * and the left edge of the text rectangle.
1063 */
1064 entry->scroll_pos += geom.text_rect.p0.x - x +
1065 ui_entry_left_scroll_margin;
1066
1067 /*
1068 * We don't want to scroll further than what's needed
1069 * to reveal the beginning of the text.
1070 */
1071 if (entry->scroll_pos > 0)
1072 entry->scroll_pos = 0;
1073 }
1074
1075 /*
1076 * Is cursor off to the right? Note that the width of the cursor
1077 * is deliberately not taken into account (i.e. we only care
1078 * about the left edge of the cursor).
1079 */
1080 if (x > geom.text_rect.p1.x)
1081 entry->scroll_pos -= x - geom.text_rect.p1.x;
1082
1083 width = gfx_text_width(res->font, entry->text);
1084
1085 if (width < geom.text_rect.p1.x - geom.text_rect.p0.x &&
1086 (realign || entry->halign != gfx_halign_left)) {
1087 /* Text fits inside entry, so we need to align it */
1088 anchor.x = geom.anchor_x;
1089 anchor.y = 0;
1090 gfx_text_fmt_init(&fmt);
1091 fmt.font = res->font;
1092 fmt.halign = entry->halign;
1093 gfx_text_start_pos(&anchor, &fmt, entry->text, &tpos);
1094 entry->scroll_pos = tpos.x - geom.text_rect.p0.x;
1095 } else if (geom.text_pos.x + width < geom.text_rect.p1.x &&
1096 entry->halign != gfx_halign_left) {
1097 /* Text is long, unused space on the right */
1098 entry->scroll_pos += geom.text_rect.p1.x -
1099 geom.text_pos.x - width;
1100 }
1101}
1102
1103/** @}
1104 */
Note: See TracBrowser for help on using the repository browser.