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

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

Seeking in entry text using mouse

  • Property mode set to 100644
File size: 15.5 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 <errno.h>
40#include <gfx/context.h>
41#include <gfx/cursor.h>
42#include <gfx/render.h>
43#include <gfx/text.h>
44#include <stdlib.h>
45#include <str.h>
46#include <ui/control.h>
47#include <ui/paint.h>
48#include <ui/entry.h>
49#include <ui/ui.h>
50#include <ui/window.h>
51#include "../private/entry.h"
52#include "../private/resource.h"
53
54static void ui_entry_ctl_destroy(void *);
55static errno_t ui_entry_ctl_paint(void *);
56static ui_evclaim_t ui_entry_ctl_kbd_event(void *, kbd_event_t *);
57static ui_evclaim_t ui_entry_ctl_pos_event(void *, pos_event_t *);
58
59enum {
60 ui_entry_hpad = 4,
61 ui_entry_vpad = 4,
62 ui_entry_hpad_text = 1,
63 ui_entry_vpad_text = 0,
64 ui_entry_cursor_overshoot = 1,
65 ui_entry_cursor_width = 2
66};
67
68/** Text entry control ops */
69ui_control_ops_t ui_entry_ops = {
70 .destroy = ui_entry_ctl_destroy,
71 .paint = ui_entry_ctl_paint,
72 .kbd_event = ui_entry_ctl_kbd_event,
73 .pos_event = ui_entry_ctl_pos_event
74};
75
76/** Create new text entry.
77 *
78 * @param window UI window
79 * @param text Text
80 * @param rentry Place to store pointer to new text entry
81 * @return EOK on success, ENOMEM if out of memory
82 */
83errno_t ui_entry_create(ui_window_t *window, const char *text,
84 ui_entry_t **rentry)
85{
86 ui_entry_t *entry;
87 errno_t rc;
88
89 entry = calloc(1, sizeof(ui_entry_t));
90 if (entry == NULL)
91 return ENOMEM;
92
93 rc = ui_control_new(&ui_entry_ops, (void *) entry, &entry->control);
94 if (rc != EOK) {
95 free(entry);
96 return rc;
97 }
98
99 entry->text = str_dup(text);
100 if (entry->text == NULL) {
101 ui_control_delete(entry->control);
102 free(entry);
103 return ENOMEM;
104 }
105
106 entry->window = window;
107 entry->halign = gfx_halign_left;
108 *rentry = entry;
109
110 return EOK;
111}
112
113/** Destroy text entry.
114 *
115 * @param entry Text entry or @c NULL
116 */
117void ui_entry_destroy(ui_entry_t *entry)
118{
119 if (entry == NULL)
120 return;
121
122 ui_control_delete(entry->control);
123 free(entry);
124}
125
126/** Get base control from text entry.
127 *
128 * @param entry Text entry
129 * @return Control
130 */
131ui_control_t *ui_entry_ctl(ui_entry_t *entry)
132{
133 return entry->control;
134}
135
136/** Set text entry rectangle.
137 *
138 * @param entry Text entry
139 * @param rect New text entry rectangle
140 */
141void ui_entry_set_rect(ui_entry_t *entry, gfx_rect_t *rect)
142{
143 entry->rect = *rect;
144}
145
146/** Set text entry horizontal text alignment.
147 *
148 * @param entry Text entry
149 * @param halign Horizontal alignment
150 */
151void ui_entry_set_halign(ui_entry_t *entry, gfx_halign_t halign)
152{
153 entry->halign = halign;
154}
155
156/** Set text entry read-only flag.
157 *
158 * @param entry Text entry
159 * @param read_only True iff entry is to be read-only.
160 */
161void ui_entry_set_read_only(ui_entry_t *entry, bool read_only)
162{
163 entry->read_only = read_only;
164}
165
166/** Set entry text.
167 *
168 * @param entry Text entry
169 * @param text New entry text
170 * @return EOK on success, ENOMEM if out of memory
171 */
172errno_t ui_entry_set_text(ui_entry_t *entry, const char *text)
173{
174 char *tcopy;
175
176 tcopy = str_dup(text);
177 if (tcopy == NULL)
178 return ENOMEM;
179
180 free(entry->text);
181 entry->text = tcopy;
182 entry->pos = str_size(text);
183
184 return EOK;
185}
186
187/** Paint cursor.
188 *
189 * @param entry Text entry
190 * @param pos Cursor position (top-left corner of next character)
191 * @return EOK on success or an error code
192 */
193static errno_t ui_entry_paint_cursor(ui_entry_t *entry, gfx_coord2_t *pos)
194{
195 ui_resource_t *res;
196 gfx_rect_t rect;
197 gfx_font_metrics_t metrics;
198 errno_t rc;
199
200 res = ui_window_get_res(entry->window);
201
202 if (res->textmode) {
203 rc = gfx_cursor_set_pos(res->gc, pos);
204 return rc;
205 }
206
207 gfx_font_get_metrics(res->font, &metrics);
208
209 rect.p0.x = pos->x;
210 rect.p0.y = pos->y - ui_entry_cursor_overshoot;
211 rect.p1.x = pos->x + ui_entry_cursor_width;
212 rect.p1.y = pos->y + metrics.ascent + metrics.descent + 1 +
213 ui_entry_cursor_overshoot;
214
215 rc = gfx_set_color(res->gc, res->entry_fg_color);
216 if (rc != EOK)
217 goto error;
218
219 rc = gfx_fill_rect(res->gc, &rect);
220 if (rc != EOK)
221 goto error;
222
223 return EOK;
224error:
225 return rc;
226}
227
228/** Return width of text before cursor.
229 *
230 * @param entry Text entry
231 * @return Widht of text before cursor
232 */
233static gfx_coord_t ui_entry_lwidth(ui_entry_t *entry)
234{
235 ui_resource_t *res;
236 uint8_t tmp;
237 gfx_coord_t width;
238
239 res = ui_window_get_res(entry->window);
240
241 tmp = entry->text[entry->pos];
242
243 entry->text[entry->pos] = '\0';
244 width = gfx_text_width(res->font, entry->text);
245 entry->text[entry->pos] = tmp;
246
247 return width;
248}
249
250/** Paint text entry.
251 *
252 * @param entry Text entry
253 * @return EOK on success or an error code
254 */
255errno_t ui_entry_paint(ui_entry_t *entry)
256{
257 ui_resource_t *res;
258 ui_entry_geom_t geom;
259 gfx_text_fmt_t fmt;
260 gfx_coord2_t pos;
261 gfx_rect_t inside;
262 errno_t rc;
263
264 res = ui_window_get_res(entry->window);
265
266 ui_entry_get_geom(entry, &geom);
267
268 if (res->textmode == false) {
269 /* Paint inset frame */
270 rc = ui_paint_inset_frame(res, &entry->rect, &inside);
271 if (rc != EOK)
272 goto error;
273 } else {
274 inside = entry->rect;
275 }
276
277 /* Paint entry background */
278
279 rc = gfx_set_color(res->gc, res->entry_bg_color);
280 if (rc != EOK)
281 goto error;
282
283 rc = gfx_fill_rect(res->gc, &inside);
284 if (rc != EOK)
285 goto error;
286
287 pos = geom.text_pos;
288
289 gfx_text_fmt_init(&fmt);
290 fmt.color = res->entry_fg_color;
291 fmt.halign = gfx_halign_left;
292 fmt.valign = gfx_valign_top;
293
294 rc = gfx_set_clip_rect(res->gc, &inside);
295 if (rc != EOK)
296 goto error;
297
298 rc = gfx_puttext(res->font, &pos, &fmt, entry->text);
299 if (rc != EOK) {
300 (void) gfx_set_clip_rect(res->gc, NULL);
301 goto error;
302 }
303
304 if (entry->active) {
305 /* Cursor */
306 pos.x += ui_entry_lwidth(entry);
307
308 rc = ui_entry_paint_cursor(entry, &pos);
309 if (rc != EOK) {
310 (void) gfx_set_clip_rect(res->gc, NULL);
311 goto error;
312 }
313 }
314
315 rc = gfx_set_clip_rect(res->gc, NULL);
316 if (rc != EOK)
317 goto error;
318
319 rc = gfx_update(res->gc);
320 if (rc != EOK)
321 goto error;
322
323 return EOK;
324error:
325 return rc;
326}
327
328/** Find position in text entry.
329 *
330 * @param entry Text entry
331 * @param fpos Position for which we need to find text offset
332 * @return Corresponding byte offset in entry text
333 */
334size_t ui_entry_find_pos(ui_entry_t *entry, gfx_coord2_t *fpos)
335{
336 ui_resource_t *res;
337 ui_entry_geom_t geom;
338 gfx_text_fmt_t fmt;
339
340 res = ui_window_get_res(entry->window);
341
342 ui_entry_get_geom(entry, &geom);
343
344 gfx_text_fmt_init(&fmt);
345 fmt.halign = gfx_halign_left;
346 fmt.valign = gfx_valign_top;
347
348 return gfx_text_find_pos(res->font, &geom.text_pos, &fmt,
349 entry->text, fpos);
350}
351
352/** Destroy text entry control.
353 *
354 * @param arg Argument (ui_entry_t *)
355 */
356void ui_entry_ctl_destroy(void *arg)
357{
358 ui_entry_t *entry = (ui_entry_t *) arg;
359
360 ui_entry_destroy(entry);
361}
362
363/** Paint text entry control.
364 *
365 * @param arg Argument (ui_entry_t *)
366 * @return EOK on success or an error code
367 */
368errno_t ui_entry_ctl_paint(void *arg)
369{
370 ui_entry_t *entry = (ui_entry_t *) arg;
371
372 return ui_entry_paint(entry);
373}
374
375/** Insert string at cursor position.
376 *
377 * @param entry Text entry
378 * @param str String
379 * @return EOK on success, ENOMEM if out of memory
380 */
381errno_t ui_entry_insert_str(ui_entry_t *entry, const char *str)
382{
383 uint8_t tmp;
384 char *ltext = NULL;
385 char *newtext;
386 char *oldtext;
387 int rc;
388
389 tmp = entry->text[entry->pos];
390 entry->text[entry->pos] = '\0';
391 ltext = str_dup(entry->text);
392 if (ltext == NULL)
393 return ENOMEM;
394
395 entry->text[entry->pos] = tmp;
396
397 rc = asprintf(&newtext, "%s%s%s", ltext, str, entry->text + entry->pos);
398 if (rc < 0) {
399 free(ltext);
400 return ENOMEM;
401 }
402
403 oldtext = entry->text;
404 entry->text = newtext;
405 entry->pos += str_size(str);
406 free(oldtext);
407 free(ltext);
408
409 ui_entry_paint(entry);
410
411 return EOK;
412}
413
414/** Delete character before cursor.
415 *
416 * @param entry Text entry
417 */
418void ui_entry_backspace(ui_entry_t *entry)
419{
420 size_t off;
421
422 if (entry->pos == 0)
423 return;
424
425 /* Find offset where character before cursor starts */
426 off = entry->pos;
427 (void) str_decode_reverse(entry->text, &off,
428 str_size(entry->text));
429
430 memmove(entry->text + off, entry->text + entry->pos,
431 str_size(entry->text + entry->pos) + 1);
432 entry->pos = off;
433
434 ui_entry_paint(entry);
435}
436
437/** Delete character after cursor.
438 *
439 * @param entry Text entry
440 */
441void ui_entry_delete(ui_entry_t *entry)
442{
443 size_t off;
444
445 /* Find offset where character after cursor end */
446 off = entry->pos;
447 (void) str_decode(entry->text, &off,
448 str_size(entry->text));
449
450 memmove(entry->text + entry->pos, entry->text + off,
451 str_size(entry->text + off) + 1);
452
453 ui_entry_paint(entry);
454}
455
456/** Handle text entry key press without modifiers.
457 *
458 * @param entry Text entry
459 * @param kbd_event Keyboard event
460 * @return @c ui_claimed iff the event is claimed
461 */
462ui_evclaim_t ui_entry_key_press_unmod(ui_entry_t *entry, kbd_event_t *event)
463{
464 assert(event->type == KEY_PRESS);
465
466 switch (event->key) {
467 case KC_BACKSPACE:
468 ui_entry_backspace(entry);
469 break;
470
471 case KC_DELETE:
472 ui_entry_delete(entry);
473 break;
474
475 case KC_ESCAPE:
476 ui_entry_deactivate(entry);
477 break;
478
479 case KC_HOME:
480 ui_entry_seek_start(entry);
481 break;
482
483 case KC_END:
484 ui_entry_seek_end(entry);
485 break;
486
487 case KC_LEFT:
488 ui_entry_seek_prev_char(entry);
489 break;
490
491 case KC_RIGHT:
492 ui_entry_seek_next_char(entry);
493 break;
494
495 default:
496 break;
497 }
498
499 return ui_claimed;
500}
501
502/** Handle text entry keyboard event.
503 *
504 * @param entry Text entry
505 * @param kbd_event Keyboard event
506 * @return @c ui_claimed iff the event is claimed
507 */
508ui_evclaim_t ui_entry_kbd_event(ui_entry_t *entry, kbd_event_t *event)
509{
510 char buf[STR_BOUNDS(1) + 1];
511 size_t off;
512 errno_t rc;
513
514 if (!entry->active)
515 return ui_unclaimed;
516
517 if (event->type == KEY_PRESS && event->c >= ' ') {
518 off = 0;
519 rc = chr_encode(event->c, buf, &off, sizeof(buf));
520 if (rc == EOK) {
521 buf[off] = '\0';
522 (void) ui_entry_insert_str(entry, buf);
523 }
524 }
525
526 if (event->type == KEY_PRESS &&
527 (event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
528 return ui_entry_key_press_unmod(entry, event);
529
530 return ui_claimed;
531}
532
533/** Handle text entry position event.
534 *
535 * @param entry Text entry
536 * @param pos_event Position event
537 * @return @c ui_claimed iff the event is claimed
538 */
539ui_evclaim_t ui_entry_pos_event(ui_entry_t *entry, pos_event_t *event)
540{
541 gfx_coord2_t pos;
542
543 if (entry->read_only)
544 return ui_unclaimed;
545
546 if (event->type == POS_UPDATE) {
547 pos.x = event->hpos;
548 pos.y = event->vpos;
549
550 if (gfx_pix_inside_rect(&pos, &entry->rect)) {
551 if (!entry->pointer_inside) {
552 ui_window_set_ctl_cursor(entry->window,
553 ui_curs_ibeam);
554 entry->pointer_inside = true;
555 }
556 } else {
557 if (entry->pointer_inside) {
558 ui_window_set_ctl_cursor(entry->window,
559 ui_curs_arrow);
560 entry->pointer_inside = false;
561 }
562 }
563 }
564
565 if (event->type == POS_PRESS) {
566 pos.x = event->hpos;
567 pos.y = event->vpos;
568
569 if (gfx_pix_inside_rect(&pos, &entry->rect)) {
570 entry->pos = ui_entry_find_pos(entry, &pos);
571 if (entry->active)
572 ui_entry_paint(entry);
573 else
574 ui_entry_activate(entry);
575
576 return ui_claimed;
577 } else {
578 ui_entry_deactivate(entry);
579 }
580 }
581
582 return ui_unclaimed;
583}
584
585/** Handle text entry control keyboard event.
586 *
587 * @param arg Argument (ui_entry_t *)
588 * @param kbd_event Keyboard event
589 * @return @c ui_claimed iff the event is claimed
590 */
591static ui_evclaim_t ui_entry_ctl_kbd_event(void *arg, kbd_event_t *event)
592{
593 ui_entry_t *entry = (ui_entry_t *) arg;
594
595 return ui_entry_kbd_event(entry, event);
596}
597
598/** Handle text entry control position event.
599 *
600 * @param arg Argument (ui_entry_t *)
601 * @param pos_event Position event
602 * @return @c ui_claimed iff the event is claimed
603 */
604static ui_evclaim_t ui_entry_ctl_pos_event(void *arg, pos_event_t *event)
605{
606 ui_entry_t *entry = (ui_entry_t *) arg;
607
608 return ui_entry_pos_event(entry, event);
609}
610
611/** Get text entry geometry.
612 *
613 * @param entry Text entry
614 * @param geom Structure to fill in with computed geometry
615 */
616void ui_entry_get_geom(ui_entry_t *entry, ui_entry_geom_t *geom)
617{
618 gfx_coord_t hpad;
619 gfx_coord_t vpad;
620 gfx_coord_t width;
621 ui_resource_t *res;
622
623 res = ui_window_get_res(entry->window);
624
625 if (res->textmode) {
626 hpad = ui_entry_hpad_text;
627 vpad = ui_entry_vpad_text;
628 } else {
629 hpad = ui_entry_hpad;
630 vpad = ui_entry_vpad;
631 }
632
633 if (res->textmode == false) {
634 ui_paint_get_inset_frame_inside(res, &entry->rect,
635 &geom->interior_rect);
636 } else {
637 geom->interior_rect = entry->rect;
638 }
639
640 width = gfx_text_width(res->font, entry->text);
641
642 switch (entry->halign) {
643 case gfx_halign_left:
644 case gfx_halign_justify:
645 geom->text_pos.x = geom->interior_rect.p0.x + hpad;
646 break;
647 case gfx_halign_center:
648 geom->text_pos.x = (geom->interior_rect.p0.x +
649 geom->interior_rect.p1.x) / 2 - width / 2;
650 break;
651 case gfx_halign_right:
652 geom->text_pos.x = geom->interior_rect.p1.x - hpad - 1 - width;
653 break;
654 }
655
656 geom->text_pos.y = geom->interior_rect.p0.y + vpad;
657}
658
659/** Activate text entry.
660 *
661 * @param entry Text entry
662 */
663void ui_entry_activate(ui_entry_t *entry)
664{
665 ui_resource_t *res;
666
667 res = ui_window_get_res(entry->window);
668
669 if (entry->active)
670 return;
671
672 entry->active = true;
673 (void) ui_entry_paint(entry);
674
675 if (res->textmode)
676 gfx_cursor_set_visible(res->gc, true);
677}
678
679/** Move text cursor to the beginning of text.
680 *
681 * @param entry Text entry
682 */
683void ui_entry_seek_start(ui_entry_t *entry)
684{
685 entry->pos = 0;
686 (void) ui_entry_paint(entry);
687}
688
689/** Move text cursor to the end of text.
690 *
691 * @param entry Text entry
692 */
693void ui_entry_seek_end(ui_entry_t *entry)
694{
695 entry->pos = str_size(entry->text);
696 (void) ui_entry_paint(entry);
697}
698
699/** Move text cursor one character backward.
700 *
701 * @param entry Text entry
702 */
703void ui_entry_seek_prev_char(ui_entry_t *entry)
704{
705 size_t off;
706
707 off = entry->pos;
708 (void) str_decode_reverse(entry->text, &off,
709 str_size(entry->text));
710 entry->pos = off;
711 (void) ui_entry_paint(entry);
712}
713
714/** Move text cursor one character forward.
715 *
716 * @param entry Text entry
717 */
718void ui_entry_seek_next_char(ui_entry_t *entry)
719{
720 size_t off;
721
722 off = entry->pos;
723 (void) str_decode(entry->text, &off,
724 str_size(entry->text));
725 entry->pos = off;
726 (void) ui_entry_paint(entry);
727}
728
729/** Deactivate text entry.
730 *
731 * @param entry Text entry
732 */
733void ui_entry_deactivate(ui_entry_t *entry)
734{
735 ui_resource_t *res;
736
737 res = ui_window_get_res(entry->window);
738
739 if (!entry->active)
740 return;
741
742 entry->active = false;
743 (void) ui_entry_paint(entry);
744
745 if (res->textmode)
746 gfx_cursor_set_visible(res->gc, false);
747}
748
749/** @}
750 */
Note: See TracBrowser for help on using the repository browser.