source: mainline/uspace/lib/ui/src/list.c@ ef4d684

topic/simplify-dev-export
Last change on this file since ef4d684 was 5e758e4, checked in by Jiri Svoboda <jiri@…>, 2 years ago

When start menu entry is edited, editor list needs updating

We need to update the entry caption in the UI list to reflect
any changes made while the entry was being edited.

  • Property mode set to 100644
File size: 33.0 KB
Line 
1/*
2 * Copyright (c) 2023 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/** @file List.
33 *
34 * Simple list control.
35 */
36
37#include <errno.h>
38#include <gfx/render.h>
39#include <gfx/text.h>
40#include <stdlib.h>
41#include <str.h>
42#include <ui/control.h>
43#include <ui/list.h>
44#include <ui/paint.h>
45#include <ui/resource.h>
46#include <ui/scrollbar.h>
47#include "../private/list.h"
48#include "../private/resource.h"
49
50static void ui_list_ctl_destroy(void *);
51static errno_t ui_list_ctl_paint(void *);
52static ui_evclaim_t ui_list_ctl_kbd_event(void *, kbd_event_t *);
53static ui_evclaim_t ui_list_ctl_pos_event(void *, pos_event_t *);
54
55/** List control ops */
56ui_control_ops_t ui_list_ctl_ops = {
57 .destroy = ui_list_ctl_destroy,
58 .paint = ui_list_ctl_paint,
59 .kbd_event = ui_list_ctl_kbd_event,
60 .pos_event = ui_list_ctl_pos_event
61};
62
63enum {
64 list_entry_hpad = 2,
65 list_entry_vpad = 2,
66 list_entry_hpad_text = 1,
67 list_entry_vpad_text = 0,
68};
69
70static void ui_list_scrollbar_up(ui_scrollbar_t *, void *);
71static void ui_list_scrollbar_down(ui_scrollbar_t *, void *);
72static void ui_list_scrollbar_page_up(ui_scrollbar_t *, void *);
73static void ui_list_scrollbar_page_down(ui_scrollbar_t *, void *);
74static void ui_list_scrollbar_moved(ui_scrollbar_t *, void *, gfx_coord_t);
75
76/** List scrollbar callbacks */
77static ui_scrollbar_cb_t ui_list_scrollbar_cb = {
78 .up = ui_list_scrollbar_up,
79 .down = ui_list_scrollbar_down,
80 .page_up = ui_list_scrollbar_page_up,
81 .page_down = ui_list_scrollbar_page_down,
82 .moved = ui_list_scrollbar_moved
83};
84
85/** Create UI list.
86 *
87 * @param window Containing window
88 * @param active @c true iff list should be active
89 * @param rlist Place to store pointer to new list
90 * @return EOK on success or an error code
91 */
92errno_t ui_list_create(ui_window_t *window, bool active,
93 ui_list_t **rlist)
94{
95 ui_list_t *list;
96 errno_t rc;
97
98 list = calloc(1, sizeof(ui_list_t));
99 if (list == NULL)
100 return ENOMEM;
101
102 rc = ui_control_new(&ui_list_ctl_ops, (void *)list,
103 &list->control);
104 if (rc != EOK) {
105 free(list);
106 return rc;
107 }
108
109 rc = ui_scrollbar_create(ui_window_get_ui(window), window,
110 ui_sbd_vert, &list->scrollbar);
111 if (rc != EOK)
112 goto error;
113
114 ui_scrollbar_set_cb(list->scrollbar, &ui_list_scrollbar_cb,
115 (void *) list);
116
117 list->window = window;
118 list_initialize(&list->entries);
119 list->entries_cnt = 0;
120 list->active = active;
121
122 *rlist = list;
123 return EOK;
124error:
125 ui_control_delete(list->control);
126 free(list);
127 return rc;
128}
129
130/** Destroy UI list.
131 *
132 * @param list UI list
133 */
134void ui_list_destroy(ui_list_t *list)
135{
136 ui_list_clear_entries(list);
137 ui_control_delete(list->control);
138 free(list);
139}
140
141/** Set UI list callbacks.
142 *
143 * @param list UI list
144 * @param cb Callbacks
145 * @param arg Argument to callback functions
146 */
147void ui_list_set_cb(ui_list_t *list, ui_list_cb_t *cb, void *arg)
148{
149 list->cb = cb;
150 list->cb_arg = arg;
151}
152
153/** Get UI list callback argument.
154 *
155 * @param list UI list
156 * @return Callback argument
157 */
158void *ui_list_get_cb_arg(ui_list_t *list)
159{
160 return list->cb_arg;
161}
162
163/** Get height of list entry.
164 *
165 * @param list UI list
166 * @return Entry height in pixels
167 */
168gfx_coord_t ui_list_entry_height(ui_list_t *list)
169{
170 ui_resource_t *res;
171 gfx_font_metrics_t metrics;
172 gfx_coord_t height;
173 gfx_coord_t vpad;
174
175 res = ui_window_get_res(list->window);
176
177 if (res->textmode) {
178 vpad = list_entry_vpad_text;
179 } else {
180 vpad = list_entry_vpad;
181 }
182
183 /* Normal menu entry */
184 gfx_font_get_metrics(res->font, &metrics);
185 height = metrics.ascent + metrics.descent + 1;
186
187 return height + 2 * vpad;
188}
189
190/** Paint list entry.
191 *
192 * @param entry List entry
193 * @param entry_idx Entry index (within list of entries)
194 * @return EOK on success or an error code
195 */
196errno_t ui_list_entry_paint(ui_list_entry_t *entry, size_t entry_idx)
197{
198 ui_list_t *list = entry->list;
199 gfx_context_t *gc = ui_window_get_gc(list->window);
200 ui_resource_t *res = ui_window_get_res(list->window);
201 gfx_font_t *font = ui_resource_get_font(res);
202 gfx_text_fmt_t fmt;
203 gfx_coord2_t pos;
204 gfx_rect_t rect;
205 gfx_rect_t lrect;
206 gfx_rect_t crect;
207 gfx_color_t *bgcolor;
208 gfx_coord_t hpad, vpad;
209 gfx_coord_t line_height;
210 size_t rows;
211 errno_t rc;
212
213 line_height = ui_list_entry_height(list);
214 ui_list_inside_rect(entry->list, &lrect);
215
216 gfx_text_fmt_init(&fmt);
217 fmt.font = font;
218 rows = ui_list_page_size(list) + 1;
219
220 /* Do not display entry outside of current page */
221 if (entry_idx < list->page_idx ||
222 entry_idx >= list->page_idx + rows)
223 return EOK;
224
225 if (res->textmode) {
226 hpad = list_entry_hpad_text;
227 vpad = list_entry_vpad_text;
228 } else {
229 hpad = list_entry_hpad;
230 vpad = list_entry_vpad;
231 }
232
233 pos.x = lrect.p0.x;
234 pos.y = lrect.p0.y + line_height * (entry_idx - list->page_idx);
235
236 if (entry == list->cursor && list->active) {
237 fmt.color = res->entry_sel_text_fg_color;
238 bgcolor = res->entry_sel_text_bg_color;
239 } else {
240 if (entry->color != NULL)
241 fmt.color = entry->color;
242 else
243 fmt.color = res->entry_fg_color;
244
245 if (entry->bgcolor != NULL)
246 bgcolor = entry->bgcolor;
247 else
248 bgcolor = res->entry_bg_color;
249 }
250
251 /* Draw entry background */
252 rect.p0 = pos;
253 rect.p1.x = lrect.p1.x;
254 rect.p1.y = rect.p0.y + line_height;
255
256 /* Clip to list interior */
257 gfx_rect_clip(&rect, &lrect, &crect);
258
259 rc = gfx_set_color(gc, bgcolor);
260 if (rc != EOK)
261 return rc;
262
263 rc = gfx_fill_rect(gc, &crect);
264 if (rc != EOK)
265 return rc;
266
267 /*
268 * Make sure caption does not overflow the entry rectangle.
269 *
270 * XXX We probably want to measure the text width, and,
271 * if it's too long, use gfx_text_find_pos() to find where
272 * it should be cut off (and append some sort of overflow
273 * marker.
274 */
275 rc = gfx_set_clip_rect(gc, &crect);
276 if (rc != EOK)
277 return rc;
278
279 pos.x += hpad;
280 pos.y += vpad;
281
282 rc = gfx_puttext(&pos, &fmt, entry->caption);
283 if (rc != EOK) {
284 (void) gfx_set_clip_rect(gc, NULL);
285 return rc;
286 }
287
288 return gfx_set_clip_rect(gc, NULL);
289}
290
291/** Paint UI list.
292 *
293 * @param list UI list
294 */
295errno_t ui_list_paint(ui_list_t *list)
296{
297 gfx_context_t *gc = ui_window_get_gc(list->window);
298 ui_resource_t *res = ui_window_get_res(list->window);
299 ui_list_entry_t *entry;
300 int i, lines;
301 errno_t rc;
302
303 rc = gfx_set_color(gc, res->entry_bg_color);
304 if (rc != EOK)
305 return rc;
306
307 rc = gfx_fill_rect(gc, &list->rect);
308 if (rc != EOK)
309 return rc;
310
311 if (!res->textmode) {
312 rc = ui_paint_inset_frame(res, &list->rect, NULL);
313 if (rc != EOK)
314 return rc;
315 }
316
317 lines = ui_list_page_size(list) + 1;
318 i = 0;
319
320 entry = list->page;
321 while (entry != NULL && i < lines) {
322 rc = ui_list_entry_paint(entry, list->page_idx + i);
323 if (rc != EOK)
324 return rc;
325
326 ++i;
327 entry = ui_list_next(entry);
328 }
329
330 rc = ui_scrollbar_paint(list->scrollbar);
331 if (rc != EOK)
332 return rc;
333
334 rc = gfx_update(gc);
335 if (rc != EOK)
336 return rc;
337
338 return EOK;
339}
340
341/** Handle list keyboard event.
342 *
343 * @param list UI list
344 * @param event Keyboard event
345 * @return ui_claimed iff event was claimed
346 */
347ui_evclaim_t ui_list_kbd_event(ui_list_t *list, kbd_event_t *event)
348{
349 if (!list->active)
350 return ui_unclaimed;
351
352 if (event->type == KEY_PRESS) {
353 if ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
354 switch (event->key) {
355 case KC_UP:
356 ui_list_cursor_up(list);
357 break;
358 case KC_DOWN:
359 ui_list_cursor_down(list);
360 break;
361 case KC_HOME:
362 ui_list_cursor_top(list);
363 break;
364 case KC_END:
365 ui_list_cursor_bottom(list);
366 break;
367 case KC_PAGE_UP:
368 ui_list_page_up(list);
369 break;
370 case KC_PAGE_DOWN:
371 ui_list_page_down(list);
372 break;
373 case KC_ENTER:
374 ui_list_selected(list->cursor);
375 break;
376 default:
377 break;
378 }
379 }
380 }
381
382 return ui_claimed;
383}
384
385/** Handle UI list position event.
386 *
387 * @param list UI list
388 * @param event Position event
389 * @return ui_claimed iff event was claimed
390 */
391ui_evclaim_t ui_list_pos_event(ui_list_t *list, pos_event_t *event)
392{
393 gfx_coord2_t pos;
394 gfx_rect_t irect;
395 ui_list_entry_t *entry;
396 gfx_coord_t line_height;
397 size_t entry_idx;
398 ui_evclaim_t claim;
399 int n;
400
401 claim = ui_scrollbar_pos_event(list->scrollbar, event);
402 if (claim == ui_claimed)
403 return ui_claimed;
404
405 line_height = ui_list_entry_height(list);
406
407 pos.x = event->hpos;
408 pos.y = event->vpos;
409 if (!gfx_pix_inside_rect(&pos, &list->rect))
410 return ui_unclaimed;
411
412 if (event->type == POS_PRESS || event->type == POS_DCLICK) {
413 ui_list_inside_rect(list, &irect);
414
415 /* Did we click on one of the entries? */
416 if (gfx_pix_inside_rect(&pos, &irect)) {
417 /* Index within page */
418 n = (pos.y - irect.p0.y) / line_height;
419
420 /* Entry and its index within entire listing */
421 entry = ui_list_page_nth_entry(list, n, &entry_idx);
422 if (entry == NULL)
423 return ui_claimed;
424
425 if (event->type == POS_PRESS) {
426 /* Move to the entry found */
427 ui_list_cursor_move(list, entry, entry_idx);
428 } else {
429 /* event->type == POS_DCLICK */
430 ui_list_selected(entry);
431 }
432 } else {
433 /* It's in the border. */
434 if (event->type == POS_PRESS) {
435 /* Top or bottom half? */
436 if (pos.y >= (irect.p0.y + irect.p1.y) / 2)
437 ui_list_page_down(list);
438 else
439 ui_list_page_up(list);
440 }
441 }
442 }
443
444 if (!list->active && event->type == POS_PRESS)
445 ui_list_activate_req(list);
446
447 return ui_claimed;
448}
449
450/** Get base control for UI list.
451 *
452 * @param list UI list
453 * @return Base UI control
454 */
455ui_control_t *ui_list_ctl(ui_list_t *list)
456{
457 return list->control;
458}
459
460/** Set UI list rectangle.
461 *
462 * @param list UI list
463 * @param rect Rectangle
464 */
465void ui_list_set_rect(ui_list_t *list, gfx_rect_t *rect)
466{
467 gfx_rect_t srect;
468
469 list->rect = *rect;
470
471 ui_list_scrollbar_rect(list, &srect);
472 ui_scrollbar_set_rect(list->scrollbar, &srect);
473}
474
475/** Get UI list page size.
476 *
477 * @param list UI list
478 * @return Number of entries that fit in list at the same time.
479 */
480unsigned ui_list_page_size(ui_list_t *list)
481{
482 gfx_coord_t line_height;
483 gfx_rect_t irect;
484
485 line_height = ui_list_entry_height(list);
486 ui_list_inside_rect(list, &irect);
487 return (irect.p1.y - irect.p0.y) / line_height;
488}
489
490/** Get UI list interior rectangle.
491 *
492 * @param list UI list
493 * @param irect Place to store interior rectangle
494 */
495void ui_list_inside_rect(ui_list_t *list, gfx_rect_t *irect)
496{
497 ui_resource_t *res = ui_window_get_res(list->window);
498 gfx_rect_t rect;
499 gfx_coord_t width;
500
501 if (res->textmode) {
502 rect = list->rect;
503 } else {
504 ui_paint_get_inset_frame_inside(res, &list->rect, &rect);
505 }
506
507 if (res->textmode) {
508 width = 1;
509 } else {
510 width = 23;
511 }
512
513 irect->p0 = rect.p0;
514 irect->p1.x = rect.p1.x - width;
515 irect->p1.y = rect.p1.y;
516}
517
518/** Get UI list scrollbar rectangle.
519 *
520 * @param list UI list
521 * @param irect Place to store interior rectangle
522 */
523void ui_list_scrollbar_rect(ui_list_t *list, gfx_rect_t *srect)
524{
525 ui_resource_t *res = ui_window_get_res(list->window);
526 gfx_rect_t rect;
527 gfx_coord_t width;
528
529 if (res->textmode) {
530 rect = list->rect;
531 } else {
532 ui_paint_get_inset_frame_inside(res, &list->rect, &rect);
533 }
534
535 if (res->textmode) {
536 width = 1;
537 } else {
538 width = 23;
539 }
540
541 srect->p0.x = rect.p1.x - width;
542 srect->p0.y = rect.p0.y;
543 srect->p1 = rect.p1;
544}
545
546/** Compute new position for UI list scrollbar thumb.
547 *
548 * @param list UI list
549 * @return New position
550 */
551gfx_coord_t ui_list_scrollbar_pos(ui_list_t *list)
552{
553 size_t entries;
554 size_t pglen;
555 size_t sbar_len;
556
557 entries = list_count(&list->entries);
558 pglen = ui_list_page_size(list);
559 sbar_len = ui_scrollbar_move_length(list->scrollbar);
560
561 if (entries > pglen)
562 return sbar_len * list->page_idx / (entries - pglen);
563 else
564 return 0;
565}
566
567/** Update UI list scrollbar position.
568 *
569 * @param list UI list
570 */
571void ui_list_scrollbar_update(ui_list_t *list)
572{
573 ui_scrollbar_set_pos(list->scrollbar,
574 ui_list_scrollbar_pos(list));
575}
576
577/** Determine if UI list is active.
578 *
579 * @param list UI list
580 * @return @c true iff UI list is active
581 */
582bool ui_list_is_active(ui_list_t *list)
583{
584 return list->active;
585}
586
587/** Activate UI list.
588 *
589 * @param list UI list
590 *
591 * @return EOK on success or an error code
592 */
593errno_t ui_list_activate(ui_list_t *list)
594{
595 list->active = true;
596 (void) ui_list_paint(list);
597 return EOK;
598}
599
600/** Deactivate UI list.
601 *
602 * @param list UI list
603 */
604void ui_list_deactivate(ui_list_t *list)
605{
606 list->active = false;
607 (void) ui_list_paint(list);
608}
609
610/** Initialize UI list entry attributes.
611 *
612 * @param attr Attributes
613 */
614void ui_list_entry_attr_init(ui_list_entry_attr_t *attr)
615{
616 memset(attr, 0, sizeof(*attr));
617}
618
619/** Destroy UI list control.
620 *
621 * @param arg Argument (ui_list_t *)
622 */
623void ui_list_ctl_destroy(void *arg)
624{
625 ui_list_t *list = (ui_list_t *) arg;
626
627 ui_list_destroy(list);
628}
629
630/** Paint UI list control.
631 *
632 * @param arg Argument (ui_list_t *)
633 * @return EOK on success or an error code
634 */
635errno_t ui_list_ctl_paint(void *arg)
636{
637 ui_list_t *list = (ui_list_t *) arg;
638
639 return ui_list_paint(list);
640}
641
642/** Handle UI list control keyboard event.
643 *
644 * @param arg Argument (ui_list_t *)
645 * @param kbd_event Keyboard event
646 * @return @c ui_claimed iff the event is claimed
647 */
648ui_evclaim_t ui_list_ctl_kbd_event(void *arg, kbd_event_t *event)
649{
650 ui_list_t *list = (ui_list_t *) arg;
651
652 return ui_list_kbd_event(list, event);
653}
654
655/** Handle UI list control position event.
656 *
657 * @param arg Argument (ui_list_t *)
658 * @param pos_event Position event
659 * @return @c ui_claimed iff the event is claimed
660 */
661ui_evclaim_t ui_list_ctl_pos_event(void *arg, pos_event_t *event)
662{
663 ui_list_t *list = (ui_list_t *) arg;
664
665 return ui_list_pos_event(list, event);
666}
667
668/** Append new UI list entry.
669 *
670 * @param list UI list
671 * @param attr Entry attributes
672 * @param rentry Place to store pointer to new entry or @c NULL if not
673 * interested
674 * @return EOK on success or an error code
675 */
676errno_t ui_list_entry_append(ui_list_t *list, ui_list_entry_attr_t *attr,
677 ui_list_entry_t **rentry)
678{
679 ui_list_entry_t *entry;
680
681 entry = calloc(1, sizeof(ui_list_entry_t));
682 if (entry == NULL)
683 return ENOMEM;
684
685 entry->list = list;
686 entry->caption = str_dup(attr->caption);
687 if (entry->caption == NULL) {
688 free(entry);
689 return ENOMEM;
690 }
691
692 entry->arg = attr->arg;
693 entry->color = attr->color;
694 entry->bgcolor = attr->bgcolor;
695 link_initialize(&entry->lentries);
696 list_append(&entry->lentries, &list->entries);
697
698 if (list->entries_cnt == 0) {
699 /* Adding first entry - need to set the cursor */
700 list->cursor = entry;
701 list->cursor_idx = 0;
702 list->page = entry;
703 list->page_idx = 0;
704 }
705
706 ++list->entries_cnt;
707
708 if (rentry != NULL)
709 *rentry = entry;
710 return EOK;
711}
712
713/** Destroy UI list entry.
714 *
715 * This is the quick way, but does not update cursor or page position.
716 *
717 * @param entry UI list entry
718 */
719void ui_list_entry_destroy(ui_list_entry_t *entry)
720{
721 if (entry->list->cursor == entry)
722 entry->list->cursor = NULL;
723 if (entry->list->page == entry)
724 entry->list->page = NULL;
725
726 list_remove(&entry->lentries);
727 --entry->list->entries_cnt;
728 free((char *) entry->caption);
729 free(entry);
730}
731
732/** Delete UI list entry.
733 *
734 * If required, update cursor and page position and repaint.
735 *
736 * @param entry UI list entry
737 */
738void ui_list_entry_delete(ui_list_entry_t *entry)
739{
740 ui_list_t *list = entry->list;
741
742 /* Try to make sure entry does not disappear under cursor or page */
743 if (entry->list->cursor == entry)
744 ui_list_cursor_up(entry->list);
745 if (entry->list->cursor == entry)
746 ui_list_cursor_down(entry->list);
747 if (entry->list->page == entry)
748 ui_list_scroll_up(entry->list);
749 if (entry->list->page == entry)
750 ui_list_scroll_down(entry->list);
751
752 ui_list_entry_destroy(entry);
753
754 /*
755 * But it could still happen if there are not enough entries.
756 * In that case just move page and/or cursor to the first
757 * entry.
758 */
759 if (list->page == NULL) {
760 list->page = ui_list_first(list);
761 list->page_idx = 0;
762 } else {
763 /*
764 * Entry index might have changed if earlier entry
765 * was deleted.
766 */
767 list->page_idx = ui_list_entry_get_idx(list->page);
768 }
769
770 if (list->cursor == NULL) {
771 list->cursor = ui_list_first(list);
772 list->cursor_idx = 0;
773 } else {
774 /*
775 * Entry index might have changed if earlier entry
776 * was deleted.
777 */
778 list->cursor_idx = ui_list_entry_get_idx(list->cursor);
779 }
780}
781
782/** Get entry argument.
783 *
784 * @param entry UI list entry
785 * @return User argument
786 */
787void *ui_list_entry_get_arg(ui_list_entry_t *entry)
788{
789 return entry->arg;
790}
791
792/** Get containing list.
793 *
794 * @param entry UI list entry
795 * @return Containing list
796 */
797ui_list_t *ui_list_entry_get_list(ui_list_entry_t *entry)
798{
799 return entry->list;
800}
801
802/** Change list entry caption.
803 *
804 * @param entry UI list entry
805 * @param caption New caption
806 *
807 * @return EOK on success, ENOMEM if out of memory
808 */
809errno_t ui_list_entry_set_caption(ui_list_entry_t *entry, const char *caption)
810{
811 char *dcaption;
812
813 dcaption = str_dup(caption);
814 if (dcaption == NULL)
815 return ENOMEM;
816
817 free(entry->caption);
818 entry->caption = dcaption;
819
820 (void)ui_list_entry_paint(entry, ui_list_entry_get_idx(entry));
821 return EOK;
822}
823
824/** Clear UI list entry list.
825 *
826 * @param list UI list
827 */
828void ui_list_clear_entries(ui_list_t *list)
829{
830 ui_list_entry_t *entry;
831
832 entry = ui_list_first(list);
833 while (entry != NULL) {
834 ui_list_entry_destroy(entry);
835 entry = ui_list_first(list);
836 }
837}
838
839/** Get number of UI list entries.
840 *
841 * @param list UI list
842 * @return Number of entries
843 */
844size_t ui_list_entries_cnt(ui_list_t *list)
845{
846 return list->entries_cnt;
847}
848
849/** Return first UI list entry.
850 *
851 * @param list UI list
852 * @return First UI list entry or @c NULL if there are no entries
853 */
854ui_list_entry_t *ui_list_first(ui_list_t *list)
855{
856 link_t *link;
857
858 link = list_first(&list->entries);
859 if (link == NULL)
860 return NULL;
861
862 return list_get_instance(link, ui_list_entry_t, lentries);
863}
864
865/** Return last UI list entry.
866 *
867 * @param list UI list
868 * @return Last UI list entry or @c NULL if there are no entries
869 */
870ui_list_entry_t *ui_list_last(ui_list_t *list)
871{
872 link_t *link;
873
874 link = list_last(&list->entries);
875 if (link == NULL)
876 return NULL;
877
878 return list_get_instance(link, ui_list_entry_t, lentries);
879}
880
881/** Return next UI list entry.
882 *
883 * @param cur Current entry
884 * @return Next entry or @c NULL if @a cur is the last entry
885 */
886ui_list_entry_t *ui_list_next(ui_list_entry_t *cur)
887{
888 link_t *link;
889
890 link = list_next(&cur->lentries, &cur->list->entries);
891 if (link == NULL)
892 return NULL;
893
894 return list_get_instance(link, ui_list_entry_t, lentries);
895}
896
897/** Return previous UI list entry.
898 *
899 * @param cur Current entry
900 * @return Previous entry or @c NULL if @a cur is the first entry
901 */
902ui_list_entry_t *ui_list_prev(ui_list_entry_t *cur)
903{
904 link_t *link;
905
906 link = list_prev(&cur->lentries, &cur->list->entries);
907 if (link == NULL)
908 return NULL;
909
910 return list_get_instance(link, ui_list_entry_t, lentries);
911}
912
913/** Find the n-th entry of the current UI list page.
914 *
915 * @param list UI list
916 * @param n Which entry to get (starting from 0)
917 * @param ridx Place to store index (within listing) of the entry
918 * @return n-th entry of the page
919 */
920ui_list_entry_t *ui_list_page_nth_entry(ui_list_t *list,
921 size_t n, size_t *ridx)
922{
923 ui_list_entry_t *entry;
924 size_t i;
925 size_t idx;
926
927 assert(n <= ui_list_page_size(list));
928
929 entry = list->page;
930 if (entry == NULL)
931 return NULL;
932
933 idx = list->page_idx;
934 for (i = 0; i < n; i++) {
935 entry = ui_list_next(entry);
936 if (entry == NULL)
937 return NULL;
938
939 ++idx;
940 }
941
942 *ridx = idx;
943 return entry;
944}
945
946/** Get entry under cursor.
947 *
948 * @param list UI list
949 * @return Current cursor
950 */
951ui_list_entry_t *ui_list_get_cursor(ui_list_t *list)
952{
953 return list->cursor;
954}
955
956/** Set new cursor position.
957 *
958 * O(N) in list size, use with caution.
959 *
960 * @param list UI list
961 * @param entry New cursor position
962 */
963void ui_list_set_cursor(ui_list_t *list, ui_list_entry_t *entry)
964{
965 size_t idx;
966
967 idx = ui_list_entry_get_idx(entry);
968 ui_list_cursor_move(list, entry, idx);
969}
970
971/** Move cursor to a new position, possibly scrolling.
972 *
973 * @param list UI list
974 * @param entry New entry under cursor
975 * @param entry_idx Index of new entry under cursor
976 */
977void ui_list_cursor_move(ui_list_t *list,
978 ui_list_entry_t *entry, size_t entry_idx)
979{
980 gfx_context_t *gc = ui_window_get_gc(list->window);
981 ui_list_entry_t *old_cursor;
982 size_t old_idx;
983 size_t rows;
984 ui_list_entry_t *e;
985 size_t i;
986
987 rows = ui_list_page_size(list);
988
989 old_cursor = list->cursor;
990 old_idx = list->cursor_idx;
991
992 list->cursor = entry;
993 list->cursor_idx = entry_idx;
994
995 if (entry_idx >= list->page_idx &&
996 entry_idx < list->page_idx + rows) {
997 /*
998 * If cursor is still on the current page, we're not
999 * scrolling. Just unpaint old cursor and paint new
1000 * cursor.
1001 */
1002 ui_list_entry_paint(old_cursor, old_idx);
1003 ui_list_entry_paint(list->cursor, list->cursor_idx);
1004
1005 (void) gfx_update(gc);
1006 } else {
1007 /*
1008 * Need to scroll and update all rows.
1009 */
1010
1011 /* Scrolling up */
1012 if (entry_idx < list->page_idx) {
1013 list->page = entry;
1014 list->page_idx = entry_idx;
1015 }
1016
1017 /* Scrolling down */
1018 if (entry_idx >= list->page_idx + rows) {
1019 if (entry_idx >= rows) {
1020 list->page_idx = entry_idx - rows + 1;
1021 /* Find first page entry (go back rows - 1) */
1022 e = entry;
1023 for (i = 0; i + 1 < rows; i++) {
1024 e = ui_list_prev(e);
1025 }
1026
1027 /* Should be valid */
1028 assert(e != NULL);
1029 list->page = e;
1030 } else {
1031 list->page = ui_list_first(list);
1032 list->page_idx = 0;
1033 }
1034 }
1035
1036 ui_list_scrollbar_update(list);
1037 (void) ui_list_paint(list);
1038 }
1039}
1040
1041/** Move cursor one entry up.
1042 *
1043 * @param list UI list
1044 */
1045void ui_list_cursor_up(ui_list_t *list)
1046{
1047 ui_list_entry_t *prev;
1048 size_t prev_idx;
1049
1050 prev = ui_list_prev(list->cursor);
1051 prev_idx = list->cursor_idx - 1;
1052 if (prev != NULL)
1053 ui_list_cursor_move(list, prev, prev_idx);
1054}
1055
1056/** Move cursor one entry down.
1057 *
1058 * @param list UI list
1059 */
1060void ui_list_cursor_down(ui_list_t *list)
1061{
1062 ui_list_entry_t *next;
1063 size_t next_idx;
1064
1065 next = ui_list_next(list->cursor);
1066 next_idx = list->cursor_idx + 1;
1067 if (next != NULL)
1068 ui_list_cursor_move(list, next, next_idx);
1069}
1070
1071/** Move cursor to top.
1072 *
1073 * @param list UI list
1074 */
1075void ui_list_cursor_top(ui_list_t *list)
1076{
1077 ui_list_cursor_move(list, ui_list_first(list), 0);
1078}
1079
1080/** Move cursor to bottom.
1081 *
1082 * @param list UI list
1083 */
1084void ui_list_cursor_bottom(ui_list_t *list)
1085{
1086 ui_list_cursor_move(list, ui_list_last(list),
1087 list->entries_cnt - 1);
1088}
1089
1090/** Move cursor one page up.
1091 *
1092 * @param list UI list
1093 */
1094void ui_list_page_up(ui_list_t *list)
1095{
1096 gfx_context_t *gc = ui_window_get_gc(list->window);
1097 ui_list_entry_t *old_page;
1098 ui_list_entry_t *old_cursor;
1099 size_t old_idx;
1100 size_t rows;
1101 ui_list_entry_t *entry;
1102 size_t i;
1103
1104 rows = ui_list_page_size(list);
1105
1106 old_page = list->page;
1107 old_cursor = list->cursor;
1108 old_idx = list->cursor_idx;
1109
1110 /* Move page by rows entries up (if possible) */
1111 for (i = 0; i < rows; i++) {
1112 entry = ui_list_prev(list->page);
1113 if (entry != NULL) {
1114 list->page = entry;
1115 --list->page_idx;
1116 }
1117 }
1118
1119 /* Move cursor by rows entries up (if possible) */
1120
1121 for (i = 0; i < rows; i++) {
1122 entry = ui_list_prev(list->cursor);
1123 if (entry != NULL) {
1124 list->cursor = entry;
1125 --list->cursor_idx;
1126 }
1127 }
1128
1129 if (list->page != old_page) {
1130 /* We have scrolled. Need to repaint all entries */
1131 ui_list_scrollbar_update(list);
1132 (void) ui_list_paint(list);
1133 } else if (list->cursor != old_cursor) {
1134 /* No scrolling, but cursor has moved */
1135 ui_list_entry_paint(old_cursor, old_idx);
1136 ui_list_entry_paint(list->cursor, list->cursor_idx);
1137
1138 (void) gfx_update(gc);
1139 }
1140}
1141
1142/** Move cursor one page down.
1143 *
1144 * @param list UI list
1145 */
1146void ui_list_page_down(ui_list_t *list)
1147{
1148 gfx_context_t *gc = ui_window_get_gc(list->window);
1149 ui_list_entry_t *old_page;
1150 ui_list_entry_t *old_cursor;
1151 size_t old_idx;
1152 size_t max_idx;
1153 size_t rows;
1154 ui_list_entry_t *entry;
1155 size_t i;
1156
1157 rows = ui_list_page_size(list);
1158
1159 old_page = list->page;
1160 old_cursor = list->cursor;
1161 old_idx = list->cursor_idx;
1162
1163 if (list->entries_cnt > rows)
1164 max_idx = list->entries_cnt - rows;
1165 else
1166 max_idx = 0;
1167
1168 /* Move page by rows entries down (if possible) */
1169 for (i = 0; i < rows; i++) {
1170 entry = ui_list_next(list->page);
1171 /* Do not scroll that results in a short page */
1172 if (entry != NULL && list->page_idx < max_idx) {
1173 list->page = entry;
1174 ++list->page_idx;
1175 }
1176 }
1177
1178 /* Move cursor by rows entries down (if possible) */
1179
1180 for (i = 0; i < rows; i++) {
1181 entry = ui_list_next(list->cursor);
1182 if (entry != NULL) {
1183 list->cursor = entry;
1184 ++list->cursor_idx;
1185 }
1186 }
1187
1188 if (list->page != old_page) {
1189 /* We have scrolled. Need to repaint all entries */
1190 ui_list_scrollbar_update(list);
1191 (void) ui_list_paint(list);
1192 } else if (list->cursor != old_cursor) {
1193 /* No scrolling, but cursor has moved */
1194 ui_list_entry_paint(old_cursor, old_idx);
1195 ui_list_entry_paint(list->cursor, list->cursor_idx);
1196
1197 (void) gfx_update(gc);
1198 }
1199}
1200
1201/** Scroll one entry up.
1202 *
1203 * @param list UI list
1204 */
1205void ui_list_scroll_up(ui_list_t *list)
1206{
1207 ui_list_entry_t *prev;
1208
1209 if (list->page == NULL)
1210 return;
1211
1212 prev = ui_list_prev(list->page);
1213 if (prev == NULL)
1214 return;
1215
1216 list->page = prev;
1217 assert(list->page_idx > 0);
1218 --list->page_idx;
1219
1220 ui_list_scrollbar_update(list);
1221 (void) ui_list_paint(list);
1222}
1223
1224/** Scroll one entry down.
1225 *
1226 * @param list UI list
1227 */
1228void ui_list_scroll_down(ui_list_t *list)
1229{
1230 ui_list_entry_t *next;
1231 ui_list_entry_t *pgend;
1232 size_t i;
1233 size_t rows;
1234
1235 if (list->page == NULL)
1236 return;
1237
1238 next = ui_list_next(list->page);
1239 if (next == NULL)
1240 return;
1241
1242 rows = ui_list_page_size(list);
1243
1244 /* Find last page entry */
1245 pgend = list->page;
1246 for (i = 0; i < rows && pgend != NULL; i++) {
1247 pgend = ui_list_next(pgend);
1248 }
1249
1250 /* Scroll down by one entry, if the page remains full */
1251 if (pgend != NULL) {
1252 list->page = next;
1253 ++list->page_idx;
1254 }
1255
1256 ui_list_scrollbar_update(list);
1257 (void) ui_list_paint(list);
1258}
1259
1260/** Scroll one page up.
1261 *
1262 * @param list UI list
1263 */
1264void ui_list_scroll_page_up(ui_list_t *list)
1265{
1266 ui_list_entry_t *prev;
1267 size_t i;
1268 size_t rows;
1269
1270 prev = ui_list_prev(list->page);
1271 if (prev == NULL)
1272 return;
1273
1274 rows = ui_list_page_size(list);
1275
1276 for (i = 0; i < rows && prev != NULL; i++) {
1277 list->page = prev;
1278 assert(list->page_idx > 0);
1279 --list->page_idx;
1280 prev = ui_list_prev(prev);
1281 }
1282
1283 ui_list_scrollbar_update(list);
1284 (void) ui_list_paint(list);
1285}
1286
1287/** Scroll one page down.
1288 *
1289 * @param list UI list
1290 */
1291void ui_list_scroll_page_down(ui_list_t *list)
1292{
1293 ui_list_entry_t *next;
1294 ui_list_entry_t *pgend;
1295 size_t i;
1296 size_t rows;
1297
1298 next = ui_list_next(list->page);
1299 if (next == NULL)
1300 return;
1301
1302 rows = ui_list_page_size(list);
1303
1304 /* Find last page entry */
1305 pgend = list->page;
1306 for (i = 0; i < rows && pgend != NULL; i++) {
1307 pgend = ui_list_next(pgend);
1308 }
1309
1310 /* Scroll by up to 'rows' entries, keeping the page full */
1311 for (i = 0; i < rows && pgend != NULL; i++) {
1312 list->page = next;
1313 ++list->page_idx;
1314 next = ui_list_next(next);
1315 pgend = ui_list_next(pgend);
1316 }
1317
1318 ui_list_scrollbar_update(list);
1319 (void) ui_list_paint(list);
1320}
1321
1322/** Scroll to a specific entry
1323 *
1324 * @param list UI list
1325 * @param page_idx New index of first entry on the page
1326 */
1327void ui_list_scroll_pos(ui_list_t *list, size_t page_idx)
1328{
1329 ui_list_entry_t *entry;
1330 size_t i;
1331
1332 entry = ui_list_first(list);
1333 for (i = 0; i < page_idx; i++) {
1334 entry = ui_list_next(entry);
1335 assert(entry != NULL);
1336 }
1337
1338 list->page = entry;
1339 list->page_idx = page_idx;
1340
1341 (void) ui_list_paint(list);
1342}
1343
1344/** Request UI list activation.
1345 *
1346 * Call back to request UI list activation.
1347 *
1348 * @param list UI list
1349 */
1350void ui_list_activate_req(ui_list_t *list)
1351{
1352 if (list->cb != NULL && list->cb->activate_req != NULL) {
1353 list->cb->activate_req(list, list->cb_arg);
1354 } else {
1355 /*
1356 * If there is no callback for activation request,
1357 * just activate the list.
1358 */
1359 ui_list_activate(list);
1360 }
1361}
1362
1363/** Sort list entries.
1364 *
1365 * @param list UI list
1366 * @return EOK on success, ENOMEM if out of memory
1367 */
1368errno_t ui_list_sort(ui_list_t *list)
1369{
1370 ui_list_entry_t **emap;
1371 ui_list_entry_t *entry;
1372 size_t i;
1373
1374 /* Create an array to hold pointer to each entry */
1375 emap = calloc(list->entries_cnt, sizeof(ui_list_entry_t *));
1376 if (emap == NULL)
1377 return ENOMEM;
1378
1379 /* Write entry pointers to array */
1380 entry = ui_list_first(list);
1381 i = 0;
1382 while (entry != NULL) {
1383 assert(i < list->entries_cnt);
1384 emap[i++] = entry;
1385 entry = ui_list_next(entry);
1386 }
1387
1388 /* Sort the array of pointers */
1389 qsort(emap, list->entries_cnt, sizeof(ui_list_entry_t *),
1390 ui_list_entry_ptr_cmp);
1391
1392 /* Unlink entries from entry list */
1393 entry = ui_list_first(list);
1394 while (entry != NULL) {
1395 list_remove(&entry->lentries);
1396 entry = ui_list_first(list);
1397 }
1398
1399 /* Add entries back to entry list sorted */
1400 for (i = 0; i < list->entries_cnt; i++)
1401 list_append(&emap[i]->lentries, &list->entries);
1402
1403 free(emap);
1404
1405 list->page = ui_list_first(list);
1406 list->page_idx = 0;
1407 list->cursor = ui_list_first(list);
1408 list->cursor_idx = 0;
1409 return EOK;
1410}
1411
1412/** Determine list entry index.
1413 *
1414 * @param entry List entry
1415 * @return List entry index
1416 */
1417size_t ui_list_entry_get_idx(ui_list_entry_t *entry)
1418{
1419 ui_list_entry_t *ep;
1420 size_t idx;
1421
1422 idx = 0;
1423 ep = ui_list_prev(entry);
1424 while (ep != NULL) {
1425 ++idx;
1426 ep = ui_list_prev(ep);
1427 }
1428
1429 return idx;
1430}
1431
1432/** Center list cursor on entry.
1433 *
1434 * @param list UI list
1435 * @param entry Entry
1436 */
1437void ui_list_cursor_center(ui_list_t *list, ui_list_entry_t *entry)
1438{
1439 ui_list_entry_t *prev;
1440 size_t idx;
1441 size_t max_idx;
1442 size_t pg_size;
1443 size_t i;
1444
1445 idx = ui_list_entry_get_idx(entry);
1446 list->cursor = entry;
1447 list->cursor_idx = idx;
1448
1449 /* Move page so that cursor is in the center */
1450 list->page = list->cursor;
1451 list->page_idx = list->cursor_idx;
1452
1453 pg_size = ui_list_page_size(list);
1454
1455 for (i = 0; i < pg_size / 2; i++) {
1456 prev = ui_list_prev(list->page);
1457 if (prev == NULL)
1458 break;
1459
1460 list->page = prev;
1461 --list->page_idx;
1462 }
1463
1464 /* Make sure page is not beyond the end if possible */
1465 if (list->entries_cnt > pg_size)
1466 max_idx = list->entries_cnt - pg_size;
1467 else
1468 max_idx = 0;
1469
1470 while (list->page_idx > 0 && list->page_idx > max_idx) {
1471 prev = ui_list_prev(list->page);
1472 if (prev == NULL)
1473 break;
1474
1475 list->page = prev;
1476 --list->page_idx;
1477 }
1478}
1479
1480/** Call back when an entry is selected.
1481 *
1482 * @param entry UI list entry
1483 * @param fname File name
1484 */
1485void ui_list_selected(ui_list_entry_t *entry)
1486{
1487 if (entry->list->cb != NULL && entry->list->cb->selected != NULL)
1488 entry->list->cb->selected(entry, entry->arg);
1489}
1490
1491/** UI list scrollbar up button pressed.
1492 *
1493 * @param scrollbar Scrollbar
1494 * @param arg Argument (ui_list_t *)
1495 */
1496static void ui_list_scrollbar_up(ui_scrollbar_t *scrollbar, void *arg)
1497{
1498 ui_list_t *list = (ui_list_t *)arg;
1499 ui_list_scroll_up(list);
1500}
1501
1502/** UI list scrollbar down button pressed.
1503 *
1504 * @param scrollbar Scrollbar
1505 * @param arg Argument (ui_list_t *)
1506 */
1507static void ui_list_scrollbar_down(ui_scrollbar_t *scrollbar, void *arg)
1508{
1509 ui_list_t *list = (ui_list_t *)arg;
1510 ui_list_scroll_down(list);
1511}
1512
1513/** UI list scrollbar page up pressed.
1514 *
1515 * @param scrollbar Scrollbar
1516 * @param arg Argument (ui_list_t *)
1517 */
1518static void ui_list_scrollbar_page_up(ui_scrollbar_t *scrollbar, void *arg)
1519{
1520 ui_list_t *list = (ui_list_t *)arg;
1521 ui_list_scroll_page_up(list);
1522}
1523
1524/** UI list scrollbar page down pressed.
1525 *
1526 * @param scrollbar Scrollbar
1527 * @param arg Argument (ui_list_t *)
1528 */
1529static void ui_list_scrollbar_page_down(ui_scrollbar_t *scrollbar,
1530 void *arg)
1531{
1532 ui_list_t *list = (ui_list_t *)arg;
1533 ui_list_scroll_page_down(list);
1534}
1535
1536/** UI list scrollbar moved.
1537 *
1538 * @param scrollbar Scrollbar
1539 * @param arg Argument (ui_list_t *)
1540 */
1541static void ui_list_scrollbar_moved(ui_scrollbar_t *scrollbar, void *arg,
1542 gfx_coord_t pos)
1543{
1544 ui_list_t *list = (ui_list_t *)arg;
1545 size_t entries;
1546 size_t pglen;
1547 size_t sbar_len;
1548 size_t pgstart;
1549
1550 entries = list_count(&list->entries);
1551 pglen = ui_list_page_size(list);
1552 sbar_len = ui_scrollbar_move_length(list->scrollbar);
1553
1554 if (entries > pglen)
1555 pgstart = (entries - pglen) * pos / (sbar_len - 1);
1556 else
1557 pgstart = 0;
1558
1559 ui_list_scroll_pos(list, pgstart);
1560}
1561
1562/** Compare two list entries indirectly referenced by pointers.
1563 *
1564 * @param pa Pointer to pointer to first entry
1565 * @param pb Pointer to pointer to second entry
1566 * @return <0, =0, >=0 if pa < b, pa == pb, pa > pb, resp.
1567 */
1568int ui_list_entry_ptr_cmp(const void *pa, const void *pb)
1569{
1570 ui_list_entry_t *a = *(ui_list_entry_t **)pa;
1571 ui_list_entry_t *b = *(ui_list_entry_t **)pb;
1572
1573 return a->list->cb->compare(a, b);
1574}
1575
1576/** @}
1577 */
Note: See TracBrowser for help on using the repository browser.