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

serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since e116461 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
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
39#include <errno.h>
40#include <gfx/context.h>
[bb14312]41#include <gfx/cursor.h>
[03145ee]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>
[9c7dc8e]49#include <ui/ui.h>
[db3895d]50#include <ui/window.h>
[03145ee]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 *);
[7481ee19]56static ui_evclaim_t ui_entry_ctl_kbd_event(void *, kbd_event_t *);
[03145ee]57static ui_evclaim_t ui_entry_ctl_pos_event(void *, pos_event_t *);
58
59enum {
60 ui_entry_hpad = 4,
[9c7dc8e]61 ui_entry_vpad = 4,
62 ui_entry_hpad_text = 1,
[65ec18d]63 ui_entry_vpad_text = 0,
64 ui_entry_cursor_overshoot = 1,
[bb14312]65 ui_entry_cursor_width = 2
[03145ee]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,
[7481ee19]72 .kbd_event = ui_entry_ctl_kbd_event,
[03145ee]73 .pos_event = ui_entry_ctl_pos_event
74};
75
76/** Create new text entry.
77 *
[db3895d]78 * @param window UI window
[03145ee]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 */
[db3895d]83errno_t ui_entry_create(ui_window_t *window, const char *text,
[03145ee]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
[db3895d]106 entry->window = window;
[03145ee]107 entry->halign = gfx_halign_left;
108 *rentry = entry;
[bb14312]109
[03145ee]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
[7481ee19]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
[03145ee]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;
[61bf9dd9]182 entry->pos = str_size(text);
[03145ee]183
184 return EOK;
185}
186
[f5819ca1]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 */
[65ec18d]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
[bb14312]202 if (res->textmode) {
203 rc = gfx_cursor_set_pos(res->gc, pos);
204 return rc;
205 }
[65ec18d]206
[bb14312]207 gfx_font_get_metrics(res->font, &metrics);
[65ec18d]208
209 rect.p0.x = pos->x;
210 rect.p0.y = pos->y - ui_entry_cursor_overshoot;
[bb14312]211 rect.p1.x = pos->x + ui_entry_cursor_width;
[65ec18d]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
[61bf9dd9]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
[03145ee]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{
[db3895d]257 ui_resource_t *res;
[d63623f]258 ui_entry_geom_t geom;
[03145ee]259 gfx_text_fmt_t fmt;
260 gfx_coord2_t pos;
261 gfx_rect_t inside;
262 errno_t rc;
263
[db3895d]264 res = ui_window_get_res(entry->window);
265
[d63623f]266 ui_entry_get_geom(entry, &geom);
[9c7dc8e]267
[db3895d]268 if (res->textmode == false) {
[cd74fa8]269 /* Paint inset frame */
[db3895d]270 rc = ui_paint_inset_frame(res, &entry->rect, &inside);
[cd74fa8]271 if (rc != EOK)
272 goto error;
273 } else {
274 inside = entry->rect;
275 }
[03145ee]276
277 /* Paint entry background */
278
[db3895d]279 rc = gfx_set_color(res->gc, res->entry_bg_color);
[03145ee]280 if (rc != EOK)
281 goto error;
282
[db3895d]283 rc = gfx_fill_rect(res->gc, &inside);
[03145ee]284 if (rc != EOK)
285 goto error;
286
[d63623f]287 pos = geom.text_pos;
[03145ee]288
289 gfx_text_fmt_init(&fmt);
[db3895d]290 fmt.color = res->entry_fg_color;
[7481ee19]291 fmt.halign = gfx_halign_left;
[03145ee]292 fmt.valign = gfx_valign_top;
293
[7481ee19]294 rc = gfx_set_clip_rect(res->gc, &inside);
295 if (rc != EOK)
296 goto error;
297
[db3895d]298 rc = gfx_puttext(res->font, &pos, &fmt, entry->text);
[7481ee19]299 if (rc != EOK) {
300 (void) gfx_set_clip_rect(res->gc, NULL);
301 goto error;
302 }
303
304 if (entry->active) {
305 /* Cursor */
[61bf9dd9]306 pos.x += ui_entry_lwidth(entry);
[7481ee19]307
[65ec18d]308 rc = ui_entry_paint_cursor(entry, &pos);
[7481ee19]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);
[03145ee]316 if (rc != EOK)
317 goto error;
318
[db3895d]319 rc = gfx_update(res->gc);
[2ab8ab3]320 if (rc != EOK)
321 goto error;
322
[03145ee]323 return EOK;
324error:
325 return rc;
326}
327
[d63623f]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
[03145ee]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
[7481ee19]375/** Insert string at cursor position.
[03145ee]376 *
[7481ee19]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{
[61bf9dd9]383 uint8_t tmp;
384 char *ltext = NULL;
[7481ee19]385 char *newtext;
386 char *oldtext;
387 int rc;
388
[61bf9dd9]389 tmp = entry->text[entry->pos];
390 entry->text[entry->pos] = '\0';
391 ltext = str_dup(entry->text);
392 if (ltext == NULL)
[7481ee19]393 return ENOMEM;
394
[61bf9dd9]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
[7481ee19]403 oldtext = entry->text;
404 entry->text = newtext;
[61bf9dd9]405 entry->pos += str_size(str);
[7481ee19]406 free(oldtext);
[61bf9dd9]407 free(ltext);
408
[7481ee19]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
[61bf9dd9]422 if (entry->pos == 0)
423 return;
424
425 /* Find offset where character before cursor starts */
426 off = entry->pos;
[7481ee19]427 (void) str_decode_reverse(entry->text, &off,
428 str_size(entry->text));
[61bf9dd9]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
[7481ee19]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
[61bf9dd9]466 switch (event->key) {
467 case KC_BACKSPACE:
[7481ee19]468 ui_entry_backspace(entry);
[61bf9dd9]469 break;
470
471 case KC_DELETE:
472 ui_entry_delete(entry);
473 break;
[7481ee19]474
[61bf9dd9]475 case KC_ESCAPE:
[bb14312]476 ui_entry_deactivate(entry);
[61bf9dd9]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 }
[7481ee19]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
[03145ee]536 * @param pos_event Position event
537 * @return @c ui_claimed iff the event is claimed
538 */
[7481ee19]539ui_evclaim_t ui_entry_pos_event(ui_entry_t *entry, pos_event_t *event)
[03145ee]540{
[db3895d]541 gfx_coord2_t pos;
542
[7481ee19]543 if (entry->read_only)
544 return ui_unclaimed;
545
[db3895d]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 }
[03145ee]564
[7481ee19]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)) {
[d63623f]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);
[7481ee19]575
576 return ui_claimed;
577 } else {
[bb14312]578 ui_entry_deactivate(entry);
[7481ee19]579 }
580 }
581
[03145ee]582 return ui_unclaimed;
583}
584
[7481ee19]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
[d63623f]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
[bb14312]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
[61bf9dd9]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
[bb14312]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
[03145ee]749/** @}
750 */
Note: See TracBrowser for help on using the repository browser.