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

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

I said cursor width should not be taken into account

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