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

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

Add font to gfx_text_fmt_t

This is quite logical and saves us one argument that we need to pass to
all text formatting functions.

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