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

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

Custom colors for selected text in text entry

In text mode, swapping 'background' and 'text' color does not work,
they are the same. Looks better in graphics mode, too.

  • Property mode set to 100644
File size: 24.3 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.color = res->entry_fg_color;
317 fmt.halign = gfx_halign_left;
318 fmt.valign = gfx_valign_top;
319
320 rc = gfx_set_clip_rect(res->gc, &inside);
321 if (rc != EOK)
322 goto error;
323
324 off1 = min(entry->pos, entry->sel_start);
325 off2 = max(entry->pos, entry->sel_start);
326
327 /* Render initial segment before start of selection */
328 c = entry->text[off1];
329 entry->text[off1] = '\0';
330
331 rc = gfx_puttext(res->font, &pos, &fmt, entry->text);
332 if (rc != EOK) {
333 (void) gfx_set_clip_rect(res->gc, NULL);
334 goto error;
335 }
336
337 gfx_text_cont(res->font, &pos, &fmt, entry->text, &cpos, &cfmt);
338 entry->text[off1] = c;
339
340 /* Render selected text */
341
342 if (off1 != off2) {
343 c = entry->text[off2];
344 entry->text[off2] = '\0';
345 cfmt.color = res->entry_sel_text_fg_color;
346
347 gfx_text_rect(res->font, &cpos, &cfmt, entry->text + off1, &sel);
348 sel.p0.x -= ui_entry_sel_hpad;
349 sel.p0.y -= ui_entry_sel_vpad;
350 sel.p1.x += ui_entry_sel_hpad;
351 sel.p1.y += ui_entry_sel_vpad;
352
353 rc = gfx_set_color(res->gc, res->entry_sel_text_bg_color);
354 if (rc != EOK)
355 goto error;
356
357 rc = gfx_fill_rect(res->gc, &sel);
358 if (rc != EOK)
359 goto error;
360
361 rc = gfx_puttext(res->font, &cpos, &cfmt, entry->text + off1);
362 if (rc != EOK) {
363 (void) gfx_set_clip_rect(res->gc, NULL);
364 goto error;
365 }
366
367 gfx_text_cont(res->font, &cpos, &cfmt, entry->text + off1,
368 &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(res->font, &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.halign = gfx_halign_left;
424 fmt.valign = gfx_valign_top;
425
426 return gfx_text_find_pos(res->font, &geom.text_pos, &fmt,
427 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.halign = entry->halign;
1114 gfx_text_start_pos(res->font, &anchor, &fmt, entry->text,
1115 &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.