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

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

Translate keys to characters even if modifiers are pressed.

So that we can match Alt-key to menu accelerators. Of course
this means clients must check Ctrl and Alt state to determine
if they should insert characters, not just just kbd_event_t.c.

  • Property mode set to 100644
File size: 24.3 KB
RevLine 
[03145ee]1/*
[2ab8ab3]2 * Copyright (c) 2021 Jiri Svoboda
[03145ee]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
[c9722c1]39#include <clipboard.h>
[03145ee]40#include <errno.h>
41#include <gfx/context.h>
[bb14312]42#include <gfx/cursor.h>
[03145ee]43#include <gfx/render.h>
44#include <gfx/text.h>
[9eb8d12]45#include <macros.h>
[03145ee]46#include <stdlib.h>
47#include <str.h>
48#include <ui/control.h>
49#include <ui/paint.h>
50#include <ui/entry.h>
[9c7dc8e]51#include <ui/ui.h>
[db3895d]52#include <ui/window.h>
[03145ee]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 *);
[7481ee19]58static ui_evclaim_t ui_entry_ctl_kbd_event(void *, kbd_event_t *);
[03145ee]59static ui_evclaim_t ui_entry_ctl_pos_event(void *, pos_event_t *);
60
61enum {
62 ui_entry_hpad = 4,
[9c7dc8e]63 ui_entry_vpad = 4,
64 ui_entry_hpad_text = 1,
[65ec18d]65 ui_entry_vpad_text = 0,
66 ui_entry_cursor_overshoot = 1,
[9eb8d12]67 ui_entry_cursor_width = 2,
68 ui_entry_sel_hpad = 0,
[dbb42c9]69 ui_entry_sel_vpad = 2,
70 /** Additional amount to scroll to the left after revealing cursor */
71 ui_entry_left_scroll_margin = 30
[03145ee]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,
[7481ee19]78 .kbd_event = ui_entry_ctl_kbd_event,
[03145ee]79 .pos_event = ui_entry_ctl_pos_event
80};
81
82/** Create new text entry.
83 *
[db3895d]84 * @param window UI window
[03145ee]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 */
[db3895d]89errno_t ui_entry_create(ui_window_t *window, const char *text,
[03145ee]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
[db3895d]112 entry->window = window;
[03145ee]113 entry->halign = gfx_halign_left;
114 *rentry = entry;
[bb14312]115
[03145ee]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;
[dbb42c9]160 ui_entry_scroll_update(entry, true);
161 ui_entry_paint(entry);
[03145ee]162}
163
[7481ee19]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
[03145ee]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;
[61bf9dd9]190 entry->pos = str_size(text);
[9eb8d12]191 entry->sel_start = entry->pos;
[5e109e1]192
[dbb42c9]193 ui_entry_scroll_update(entry, false);
194 ui_entry_paint(entry);
[03145ee]195
196 return EOK;
197}
198
[5e109e1]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
[f5819ca1]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 */
[65ec18d]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
[bb14312]223 if (res->textmode) {
224 rc = gfx_cursor_set_pos(res->gc, pos);
225 return rc;
226 }
[65ec18d]227
[bb14312]228 gfx_font_get_metrics(res->font, &metrics);
[65ec18d]229
230 rect.p0.x = pos->x;
231 rect.p0.y = pos->y - ui_entry_cursor_overshoot;
[bb14312]232 rect.p1.x = pos->x + ui_entry_cursor_width;
[65ec18d]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
[61bf9dd9]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
[03145ee]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{
[db3895d]278 ui_resource_t *res;
[d63623f]279 ui_entry_geom_t geom;
[03145ee]280 gfx_text_fmt_t fmt;
281 gfx_coord2_t pos;
[9eb8d12]282 gfx_text_fmt_t cfmt;
283 gfx_coord2_t cpos;
[03145ee]284 gfx_rect_t inside;
[9eb8d12]285 unsigned off1, off2;
286 gfx_rect_t sel;
287 char c;
[03145ee]288 errno_t rc;
289
[db3895d]290 res = ui_window_get_res(entry->window);
291
[d63623f]292 ui_entry_get_geom(entry, &geom);
[9c7dc8e]293
[db3895d]294 if (res->textmode == false) {
[cd74fa8]295 /* Paint inset frame */
[db3895d]296 rc = ui_paint_inset_frame(res, &entry->rect, &inside);
[cd74fa8]297 if (rc != EOK)
298 goto error;
299 } else {
300 inside = entry->rect;
301 }
[03145ee]302
303 /* Paint entry background */
304
[db3895d]305 rc = gfx_set_color(res->gc, res->entry_bg_color);
[03145ee]306 if (rc != EOK)
307 goto error;
308
[db3895d]309 rc = gfx_fill_rect(res->gc, &inside);
[03145ee]310 if (rc != EOK)
311 goto error;
312
[d63623f]313 pos = geom.text_pos;
[03145ee]314
315 gfx_text_fmt_init(&fmt);
[4583015]316 fmt.font = res->font;
[db3895d]317 fmt.color = res->entry_fg_color;
[7481ee19]318 fmt.halign = gfx_halign_left;
[03145ee]319 fmt.valign = gfx_valign_top;
320
[7481ee19]321 rc = gfx_set_clip_rect(res->gc, &inside);
322 if (rc != EOK)
323 goto error;
324
[9eb8d12]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
[4583015]332 rc = gfx_puttext(&pos, &fmt, entry->text);
[7481ee19]333 if (rc != EOK) {
334 (void) gfx_set_clip_rect(res->gc, NULL);
335 goto error;
336 }
337
[4583015]338 gfx_text_cont(&pos, &fmt, entry->text, &cpos, &cfmt);
[9eb8d12]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';
[6df564c]346 cfmt.color = res->entry_sel_text_fg_color;
[9eb8d12]347
[4583015]348 gfx_text_rect(&cpos, &cfmt, entry->text + off1, &sel);
[9eb8d12]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
[6df564c]354 rc = gfx_set_color(res->gc, res->entry_sel_text_bg_color);
[9eb8d12]355 if (rc != EOK)
356 goto error;
357
358 rc = gfx_fill_rect(res->gc, &sel);
359 if (rc != EOK)
360 goto error;
361
[4583015]362 rc = gfx_puttext(&cpos, &cfmt, entry->text + off1);
[9eb8d12]363 if (rc != EOK) {
364 (void) gfx_set_clip_rect(res->gc, NULL);
365 goto error;
366 }
367
[4583015]368 gfx_text_cont(&cpos, &cfmt, entry->text + off1, &cpos, &cfmt);
[9eb8d12]369
370 entry->text[off2] = c;
371 }
372
373 /* Render trailing, non-selected text */
374 cfmt.color = res->entry_fg_color;
375
[4583015]376 rc = gfx_puttext(&cpos, &cfmt, entry->text + off2);
[9eb8d12]377 if (rc != EOK) {
378 (void) gfx_set_clip_rect(res->gc, NULL);
379 goto error;
380 }
381
[7481ee19]382 if (entry->active) {
383 /* Cursor */
[61bf9dd9]384 pos.x += ui_entry_lwidth(entry);
[7481ee19]385
[65ec18d]386 rc = ui_entry_paint_cursor(entry, &pos);
[7481ee19]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);
[03145ee]394 if (rc != EOK)
395 goto error;
396
[db3895d]397 rc = gfx_update(res->gc);
[2ab8ab3]398 if (rc != EOK)
399 goto error;
400
[03145ee]401 return EOK;
402error:
403 return rc;
404}
405
[d63623f]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);
[4583015]423 fmt.font = res->font;
[d63623f]424 fmt.halign = gfx_halign_left;
425 fmt.valign = gfx_valign_top;
426
[4583015]427 return gfx_text_find_pos(&geom.text_pos, &fmt, entry->text, fpos);
[d63623f]428}
429
[03145ee]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
[9eb8d12]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;
[dbb42c9]470 ui_entry_scroll_update(entry, false);
[9eb8d12]471 ui_entry_paint(entry);
472}
473
[7481ee19]474/** Insert string at cursor position.
[03145ee]475 *
[7481ee19]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{
[61bf9dd9]482 uint8_t tmp;
483 char *ltext = NULL;
[7481ee19]484 char *newtext;
485 char *oldtext;
486 int rc;
487
[9eb8d12]488 /* Do we have a selection? */
489 if (entry->sel_start != entry->pos)
490 ui_entry_delete_sel(entry);
491
[61bf9dd9]492 tmp = entry->text[entry->pos];
493 entry->text[entry->pos] = '\0';
494 ltext = str_dup(entry->text);
495 if (ltext == NULL)
[7481ee19]496 return ENOMEM;
497
[61bf9dd9]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
[7481ee19]506 oldtext = entry->text;
507 entry->text = newtext;
[61bf9dd9]508 entry->pos += str_size(str);
[7481ee19]509 free(oldtext);
[61bf9dd9]510 free(ltext);
511
[9eb8d12]512 entry->sel_start = entry->pos;
[dbb42c9]513 ui_entry_scroll_update(entry, false);
[7481ee19]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
[9eb8d12]527 /* Do we have a selection? */
528 if (entry->sel_start != entry->pos) {
529 ui_entry_delete_sel(entry);
530 return;
531 }
532
[61bf9dd9]533 if (entry->pos == 0)
534 return;
535
536 /* Find offset where character before cursor starts */
537 off = entry->pos;
[7481ee19]538 (void) str_decode_reverse(entry->text, &off,
539 str_size(entry->text));
[61bf9dd9]540
541 memmove(entry->text + off, entry->text + entry->pos,
542 str_size(entry->text + entry->pos) + 1);
543 entry->pos = off;
[9eb8d12]544 entry->sel_start = off;
[61bf9dd9]545
[dbb42c9]546 ui_entry_scroll_update(entry, false);
[61bf9dd9]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
[9eb8d12]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 */
[61bf9dd9]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
[dbb42c9]572 ui_entry_scroll_update(entry, false);
[7481ee19]573 ui_entry_paint(entry);
574}
575
[c9722c1]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
[7481ee19]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
[61bf9dd9]634 switch (event->key) {
635 case KC_BACKSPACE:
[7481ee19]636 ui_entry_backspace(entry);
[61bf9dd9]637 break;
638
639 case KC_DELETE:
640 ui_entry_delete(entry);
641 break;
[7481ee19]642
[61bf9dd9]643 case KC_ESCAPE:
[bb14312]644 ui_entry_deactivate(entry);
[61bf9dd9]645 break;
646
647 case KC_HOME:
[9eb8d12]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 return ui_claimed;
667}
668
669/** Handle text entry key press with shift modifier.
670 *
671 * @param entry Text entry
672 * @param kbd_event Keyboard event
673 * @return @c ui_claimed iff the event is claimed
674 */
675ui_evclaim_t ui_entry_key_press_shift(ui_entry_t *entry, kbd_event_t *event)
676{
677 assert(event->type == KEY_PRESS);
678
679 switch (event->key) {
680 case KC_HOME:
681 ui_entry_seek_start(entry, true);
[61bf9dd9]682 break;
683
684 case KC_END:
[9eb8d12]685 ui_entry_seek_end(entry, true);
[61bf9dd9]686 break;
687
688 case KC_LEFT:
[9eb8d12]689 ui_entry_seek_prev_char(entry, true);
[61bf9dd9]690 break;
691
692 case KC_RIGHT:
[9eb8d12]693 ui_entry_seek_next_char(entry, true);
[61bf9dd9]694 break;
695
696 default:
697 break;
698 }
[7481ee19]699
700 return ui_claimed;
701}
702
[c9722c1]703/** Handle text entry key press with control modifier.
704 *
705 * @param entry Text entry
706 * @param kbd_event Keyboard event
707 * @return @c ui_claimed iff the event is claimed
708 */
709ui_evclaim_t ui_entry_key_press_ctrl(ui_entry_t *entry, kbd_event_t *event)
710{
711 assert(event->type == KEY_PRESS);
712
713 switch (event->key) {
714 case KC_C:
715 ui_entry_copy(entry);
716 break;
717 case KC_V:
718 ui_entry_paste(entry);
719 break;
720 case KC_X:
721 ui_entry_cut(entry);
722 break;
723 default:
724 break;
725 }
726
727 return ui_claimed;
728}
729
[7481ee19]730/** Handle text entry keyboard event.
731 *
732 * @param entry Text entry
733 * @param kbd_event Keyboard event
734 * @return @c ui_claimed iff the event is claimed
735 */
736ui_evclaim_t ui_entry_kbd_event(ui_entry_t *entry, kbd_event_t *event)
737{
738 char buf[STR_BOUNDS(1) + 1];
739 size_t off;
740 errno_t rc;
741
742 if (!entry->active)
743 return ui_unclaimed;
744
[a106037]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
[b987eb4]761 if (event->type == KEY_PRESS &&
762 (event->mods & (KM_CTRL | KM_ALT)) == 0 && event->c >= ' ') {
763 off = 0;
764 rc = chr_encode(event->c, buf, &off, sizeof(buf));
765 if (rc == EOK) {
766 buf[off] = '\0';
767 (void) ui_entry_insert_str(entry, buf);
768 }
769 }
770
[7481ee19]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
[9eb8d12]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
[c9722c1]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
[7481ee19]785 return ui_claimed;
786}
787
788/** Handle text entry position event.
789 *
790 * @param entry Text entry
[03145ee]791 * @param pos_event Position event
792 * @return @c ui_claimed iff the event is claimed
793 */
[7481ee19]794ui_evclaim_t ui_entry_pos_event(ui_entry_t *entry, pos_event_t *event)
[03145ee]795{
[db3895d]796 gfx_coord2_t pos;
797
[7481ee19]798 if (entry->read_only)
799 return ui_unclaimed;
800
[db3895d]801 if (event->type == POS_UPDATE) {
802 pos.x = event->hpos;
803 pos.y = event->vpos;
804
[282c86d]805 /* Change cursor shape when pointer is entering/leaving */
[db3895d]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 }
[282c86d]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 }
[db3895d]828 }
[03145ee]829
[7481ee19]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)) {
[282c86d]835 /* Clicked inside - activate, set position */
836 entry->held = true;
[d63623f]837 entry->pos = ui_entry_find_pos(entry, &pos);
[a106037]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
[d63623f]843 if (entry->active)
844 ui_entry_paint(entry);
845 else
846 ui_entry_activate(entry);
[7481ee19]847
848 return ui_claimed;
849 } else {
[282c86d]850 /* Clicked outside - deactivate */
[bb14312]851 ui_entry_deactivate(entry);
[7481ee19]852 }
853 }
854
[282c86d]855 if (event->type == POS_RELEASE) {
856 entry->held = false;
857 }
858
[03145ee]859 return ui_unclaimed;
860}
861
[7481ee19]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
[d63623f]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
[dbb42c9]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;
[d63623f]924
925 switch (entry->halign) {
926 case gfx_halign_left:
927 case gfx_halign_justify:
[dbb42c9]928 geom->anchor_x = geom->text_rect.p0.x;
[d63623f]929 break;
930 case gfx_halign_center:
[dbb42c9]931 geom->anchor_x = (geom->text_rect.p0.x +
932 geom->text_rect.p1.x) / 2;
[d63623f]933 break;
934 case gfx_halign_right:
[dbb42c9]935 geom->anchor_x = geom->text_rect.p1.x;
[d63623f]936 break;
937 }
938}
939
[bb14312]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
[61bf9dd9]960/** Move text cursor to the beginning of text.
961 *
962 * @param entry Text entry
[9eb8d12]963 * @param shift @c true iff shift key is pressed
[61bf9dd9]964 */
[9eb8d12]965void ui_entry_seek_start(ui_entry_t *entry, bool shift)
[61bf9dd9]966{
967 entry->pos = 0;
[9eb8d12]968
969 if (!shift)
970 entry->sel_start = entry->pos;
[dbb42c9]971
972 ui_entry_scroll_update(entry, false);
[61bf9dd9]973 (void) ui_entry_paint(entry);
974}
975
976/** Move text cursor to the end of text.
977 *
978 * @param entry Text entry
[9eb8d12]979 * @param shift @c true iff shift key is pressed
[61bf9dd9]980 */
[9eb8d12]981void ui_entry_seek_end(ui_entry_t *entry, bool shift)
[61bf9dd9]982{
983 entry->pos = str_size(entry->text);
[9eb8d12]984
985 if (!shift)
986 entry->sel_start = entry->pos;
[dbb42c9]987
988 ui_entry_scroll_update(entry, false);
[61bf9dd9]989 (void) ui_entry_paint(entry);
990}
991
992/** Move text cursor one character backward.
993 *
994 * @param entry Text entry
[9eb8d12]995 * @param shift @c true iff shift key is pressed
[61bf9dd9]996 */
[9eb8d12]997void ui_entry_seek_prev_char(ui_entry_t *entry, bool shift)
[61bf9dd9]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;
[9eb8d12]1005
1006 if (!shift)
1007 entry->sel_start = entry->pos;
[dbb42c9]1008
1009 ui_entry_scroll_update(entry, false);
[61bf9dd9]1010 (void) ui_entry_paint(entry);
1011}
1012
1013/** Move text cursor one character forward.
1014 *
1015 * @param entry Text entry
[9eb8d12]1016 * @param shift @c true iff shift key is pressed
[61bf9dd9]1017 */
[9eb8d12]1018void ui_entry_seek_next_char(ui_entry_t *entry, bool shift)
[61bf9dd9]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;
[9eb8d12]1026
1027 if (!shift)
1028 entry->sel_start = entry->pos;
[dbb42c9]1029
1030 ui_entry_scroll_update(entry, false);
[61bf9dd9]1031 (void) ui_entry_paint(entry);
1032}
1033
[bb14312]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;
[9eb8d12]1048 entry->sel_start = entry->pos;
[bb14312]1049 (void) ui_entry_paint(entry);
1050
1051 if (res->textmode)
1052 gfx_cursor_set_visible(res->gc, false);
1053}
1054
[dbb42c9]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)
[ba74416]1103 entry->scroll_pos -= x - geom.text_rect.p1.x;
[dbb42c9]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);
[4583015]1113 fmt.font = res->font;
[dbb42c9]1114 fmt.halign = entry->halign;
[4583015]1115 gfx_text_start_pos(&anchor, &fmt, entry->text, &tpos);
[dbb42c9]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
[03145ee]1125/** @}
1126 */
Note: See TracBrowser for help on using the repository browser.