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

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

Selecting entry text by shift-click

You can never have enough ways of selecting text. Besides, mouse drag
does not work in console, because console does not deliver position
update events (we might have to rethink that).

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