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

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

UI display configuration utility

In addition to the command-line utility 'disp', we introduce its UI
equivalent 'display-cfg'. Currently this allows the user to configure
seats in a very comfortable way.

  • Property mode set to 100644
File size: 31.9 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 /* Try to make sure entry does not disappear between cursor and page */
741 if (entry->list->cursor == entry)
742 ui_list_cursor_up(entry->list);
743 if (entry->list->cursor == entry)
744 ui_list_cursor_down(entry->list);
745 if (entry->list->page == entry)
746 ui_list_scroll_up(entry->list);
747 if (entry->list->page == entry)
748 ui_list_scroll_down(entry->list);
749
750 ui_list_entry_destroy(entry);
751}
752
753/** Get entry argument.
754 *
755 * @param entry UI list entry
756 * @return User argument
757 */
758void *ui_list_entry_get_arg(ui_list_entry_t *entry)
759{
760 return entry->arg;
761}
762
763/** Get containing list.
764 *
765 * @param entry UI list entry
766 * @return Containing list
767 */
768ui_list_t *ui_list_entry_get_list(ui_list_entry_t *entry)
769{
770 return entry->list;
771}
772
773/** Clear UI list entry list.
774 *
775 * @param list UI list
776 */
777void ui_list_clear_entries(ui_list_t *list)
778{
779 ui_list_entry_t *entry;
780
781 entry = ui_list_first(list);
782 while (entry != NULL) {
783 ui_list_entry_destroy(entry);
784 entry = ui_list_first(list);
785 }
786}
787
788/** Get number of UI list entries.
789 *
790 * @param list UI list
791 * @return Number of entries
792 */
793size_t ui_list_entries_cnt(ui_list_t *list)
794{
795 return list->entries_cnt;
796}
797
798/** Return first UI list entry.
799 *
800 * @param list UI list
801 * @return First UI list entry or @c NULL if there are no entries
802 */
803ui_list_entry_t *ui_list_first(ui_list_t *list)
804{
805 link_t *link;
806
807 link = list_first(&list->entries);
808 if (link == NULL)
809 return NULL;
810
811 return list_get_instance(link, ui_list_entry_t, lentries);
812}
813
814/** Return last UI list entry.
815 *
816 * @param list UI list
817 * @return Last UI list entry or @c NULL if there are no entries
818 */
819ui_list_entry_t *ui_list_last(ui_list_t *list)
820{
821 link_t *link;
822
823 link = list_last(&list->entries);
824 if (link == NULL)
825 return NULL;
826
827 return list_get_instance(link, ui_list_entry_t, lentries);
828}
829
830/** Return next UI list entry.
831 *
832 * @param cur Current entry
833 * @return Next entry or @c NULL if @a cur is the last entry
834 */
835ui_list_entry_t *ui_list_next(ui_list_entry_t *cur)
836{
837 link_t *link;
838
839 link = list_next(&cur->lentries, &cur->list->entries);
840 if (link == NULL)
841 return NULL;
842
843 return list_get_instance(link, ui_list_entry_t, lentries);
844}
845
846/** Return previous UI list entry.
847 *
848 * @param cur Current entry
849 * @return Previous entry or @c NULL if @a cur is the first entry
850 */
851ui_list_entry_t *ui_list_prev(ui_list_entry_t *cur)
852{
853 link_t *link;
854
855 link = list_prev(&cur->lentries, &cur->list->entries);
856 if (link == NULL)
857 return NULL;
858
859 return list_get_instance(link, ui_list_entry_t, lentries);
860}
861
862/** Find the n-th entry of the current UI list page.
863 *
864 * @param list UI list
865 * @param n Which entry to get (starting from 0)
866 * @param ridx Place to store index (within listing) of the entry
867 * @return n-th entry of the page
868 */
869ui_list_entry_t *ui_list_page_nth_entry(ui_list_t *list,
870 size_t n, size_t *ridx)
871{
872 ui_list_entry_t *entry;
873 size_t i;
874 size_t idx;
875
876 assert(n <= ui_list_page_size(list));
877
878 entry = list->page;
879 if (entry == NULL)
880 return NULL;
881
882 idx = list->page_idx;
883 for (i = 0; i < n; i++) {
884 entry = ui_list_next(entry);
885 if (entry == NULL)
886 return NULL;
887
888 ++idx;
889 }
890
891 *ridx = idx;
892 return entry;
893}
894
895/** Get entry under cursor.
896 *
897 * @param list UI list
898 * @return Current cursor
899 */
900ui_list_entry_t *ui_list_get_cursor(ui_list_t *list)
901{
902 return list->cursor;
903}
904
905/** Set new cursor position.
906 *
907 * O(N) in list size, use with caution.
908 *
909 * @param list UI list
910 * @param entry New cursor position
911 */
912void ui_list_set_cursor(ui_list_t *list, ui_list_entry_t *entry)
913{
914 size_t idx;
915
916 idx = ui_list_entry_get_idx(entry);
917 ui_list_cursor_move(list, entry, idx);
918}
919
920/** Move cursor to a new position, possibly scrolling.
921 *
922 * @param list UI list
923 * @param entry New entry under cursor
924 * @param entry_idx Index of new entry under cursor
925 */
926void ui_list_cursor_move(ui_list_t *list,
927 ui_list_entry_t *entry, size_t entry_idx)
928{
929 gfx_context_t *gc = ui_window_get_gc(list->window);
930 ui_list_entry_t *old_cursor;
931 size_t old_idx;
932 size_t rows;
933 ui_list_entry_t *e;
934 size_t i;
935
936 rows = ui_list_page_size(list);
937
938 old_cursor = list->cursor;
939 old_idx = list->cursor_idx;
940
941 list->cursor = entry;
942 list->cursor_idx = entry_idx;
943
944 if (entry_idx >= list->page_idx &&
945 entry_idx < list->page_idx + rows) {
946 /*
947 * If cursor is still on the current page, we're not
948 * scrolling. Just unpaint old cursor and paint new
949 * cursor.
950 */
951 ui_list_entry_paint(old_cursor, old_idx);
952 ui_list_entry_paint(list->cursor, list->cursor_idx);
953
954 (void) gfx_update(gc);
955 } else {
956 /*
957 * Need to scroll and update all rows.
958 */
959
960 /* Scrolling up */
961 if (entry_idx < list->page_idx) {
962 list->page = entry;
963 list->page_idx = entry_idx;
964 }
965
966 /* Scrolling down */
967 if (entry_idx >= list->page_idx + rows) {
968 if (entry_idx >= rows) {
969 list->page_idx = entry_idx - rows + 1;
970 /* Find first page entry (go back rows - 1) */
971 e = entry;
972 for (i = 0; i + 1 < rows; i++) {
973 e = ui_list_prev(e);
974 }
975
976 /* Should be valid */
977 assert(e != NULL);
978 list->page = e;
979 } else {
980 list->page = ui_list_first(list);
981 list->page_idx = 0;
982 }
983 }
984
985 ui_list_scrollbar_update(list);
986 (void) ui_list_paint(list);
987 }
988}
989
990/** Move cursor one entry up.
991 *
992 * @param list UI list
993 */
994void ui_list_cursor_up(ui_list_t *list)
995{
996 ui_list_entry_t *prev;
997 size_t prev_idx;
998
999 prev = ui_list_prev(list->cursor);
1000 prev_idx = list->cursor_idx - 1;
1001 if (prev != NULL)
1002 ui_list_cursor_move(list, prev, prev_idx);
1003}
1004
1005/** Move cursor one entry down.
1006 *
1007 * @param list UI list
1008 */
1009void ui_list_cursor_down(ui_list_t *list)
1010{
1011 ui_list_entry_t *next;
1012 size_t next_idx;
1013
1014 next = ui_list_next(list->cursor);
1015 next_idx = list->cursor_idx + 1;
1016 if (next != NULL)
1017 ui_list_cursor_move(list, next, next_idx);
1018}
1019
1020/** Move cursor to top.
1021 *
1022 * @param list UI list
1023 */
1024void ui_list_cursor_top(ui_list_t *list)
1025{
1026 ui_list_cursor_move(list, ui_list_first(list), 0);
1027}
1028
1029/** Move cursor to bottom.
1030 *
1031 * @param list UI list
1032 */
1033void ui_list_cursor_bottom(ui_list_t *list)
1034{
1035 ui_list_cursor_move(list, ui_list_last(list),
1036 list->entries_cnt - 1);
1037}
1038
1039/** Move cursor one page up.
1040 *
1041 * @param list UI list
1042 */
1043void ui_list_page_up(ui_list_t *list)
1044{
1045 gfx_context_t *gc = ui_window_get_gc(list->window);
1046 ui_list_entry_t *old_page;
1047 ui_list_entry_t *old_cursor;
1048 size_t old_idx;
1049 size_t rows;
1050 ui_list_entry_t *entry;
1051 size_t i;
1052
1053 rows = ui_list_page_size(list);
1054
1055 old_page = list->page;
1056 old_cursor = list->cursor;
1057 old_idx = list->cursor_idx;
1058
1059 /* Move page by rows entries up (if possible) */
1060 for (i = 0; i < rows; i++) {
1061 entry = ui_list_prev(list->page);
1062 if (entry != NULL) {
1063 list->page = entry;
1064 --list->page_idx;
1065 }
1066 }
1067
1068 /* Move cursor by rows entries up (if possible) */
1069
1070 for (i = 0; i < rows; i++) {
1071 entry = ui_list_prev(list->cursor);
1072 if (entry != NULL) {
1073 list->cursor = entry;
1074 --list->cursor_idx;
1075 }
1076 }
1077
1078 if (list->page != old_page) {
1079 /* We have scrolled. Need to repaint all entries */
1080 ui_list_scrollbar_update(list);
1081 (void) ui_list_paint(list);
1082 } else if (list->cursor != old_cursor) {
1083 /* No scrolling, but cursor has moved */
1084 ui_list_entry_paint(old_cursor, old_idx);
1085 ui_list_entry_paint(list->cursor, list->cursor_idx);
1086
1087 (void) gfx_update(gc);
1088 }
1089}
1090
1091/** Move cursor one page down.
1092 *
1093 * @param list UI list
1094 */
1095void ui_list_page_down(ui_list_t *list)
1096{
1097 gfx_context_t *gc = ui_window_get_gc(list->window);
1098 ui_list_entry_t *old_page;
1099 ui_list_entry_t *old_cursor;
1100 size_t old_idx;
1101 size_t max_idx;
1102 size_t rows;
1103 ui_list_entry_t *entry;
1104 size_t i;
1105
1106 rows = ui_list_page_size(list);
1107
1108 old_page = list->page;
1109 old_cursor = list->cursor;
1110 old_idx = list->cursor_idx;
1111
1112 if (list->entries_cnt > rows)
1113 max_idx = list->entries_cnt - rows;
1114 else
1115 max_idx = 0;
1116
1117 /* Move page by rows entries down (if possible) */
1118 for (i = 0; i < rows; i++) {
1119 entry = ui_list_next(list->page);
1120 /* Do not scroll that results in a short page */
1121 if (entry != NULL && list->page_idx < max_idx) {
1122 list->page = entry;
1123 ++list->page_idx;
1124 }
1125 }
1126
1127 /* Move cursor by rows entries down (if possible) */
1128
1129 for (i = 0; i < rows; i++) {
1130 entry = ui_list_next(list->cursor);
1131 if (entry != NULL) {
1132 list->cursor = entry;
1133 ++list->cursor_idx;
1134 }
1135 }
1136
1137 if (list->page != old_page) {
1138 /* We have scrolled. Need to repaint all entries */
1139 ui_list_scrollbar_update(list);
1140 (void) ui_list_paint(list);
1141 } else if (list->cursor != old_cursor) {
1142 /* No scrolling, but cursor has moved */
1143 ui_list_entry_paint(old_cursor, old_idx);
1144 ui_list_entry_paint(list->cursor, list->cursor_idx);
1145
1146 (void) gfx_update(gc);
1147 }
1148}
1149
1150/** Scroll one entry up.
1151 *
1152 * @param list UI list
1153 */
1154void ui_list_scroll_up(ui_list_t *list)
1155{
1156 ui_list_entry_t *prev;
1157
1158 if (list->page == NULL)
1159 return;
1160
1161 prev = ui_list_prev(list->page);
1162 if (prev == NULL)
1163 return;
1164
1165 list->page = prev;
1166 assert(list->page_idx > 0);
1167 --list->page_idx;
1168
1169 ui_list_scrollbar_update(list);
1170 (void) ui_list_paint(list);
1171}
1172
1173/** Scroll one entry down.
1174 *
1175 * @param list UI list
1176 */
1177void ui_list_scroll_down(ui_list_t *list)
1178{
1179 ui_list_entry_t *next;
1180 ui_list_entry_t *pgend;
1181 size_t i;
1182 size_t rows;
1183
1184 if (list->page == NULL)
1185 return;
1186
1187 next = ui_list_next(list->page);
1188 if (next == NULL)
1189 return;
1190
1191 rows = ui_list_page_size(list);
1192
1193 /* Find last page entry */
1194 pgend = list->page;
1195 for (i = 0; i < rows && pgend != NULL; i++) {
1196 pgend = ui_list_next(pgend);
1197 }
1198
1199 /* Scroll down by one entry, if the page remains full */
1200 if (pgend != NULL) {
1201 list->page = next;
1202 ++list->page_idx;
1203 }
1204
1205 ui_list_scrollbar_update(list);
1206 (void) ui_list_paint(list);
1207}
1208
1209/** Scroll one page up.
1210 *
1211 * @param list UI list
1212 */
1213void ui_list_scroll_page_up(ui_list_t *list)
1214{
1215 ui_list_entry_t *prev;
1216 size_t i;
1217 size_t rows;
1218
1219 prev = ui_list_prev(list->page);
1220 if (prev == NULL)
1221 return;
1222
1223 rows = ui_list_page_size(list);
1224
1225 for (i = 0; i < rows && prev != NULL; i++) {
1226 list->page = prev;
1227 assert(list->page_idx > 0);
1228 --list->page_idx;
1229 prev = ui_list_prev(prev);
1230 }
1231
1232 ui_list_scrollbar_update(list);
1233 (void) ui_list_paint(list);
1234}
1235
1236/** Scroll one page down.
1237 *
1238 * @param list UI list
1239 */
1240void ui_list_scroll_page_down(ui_list_t *list)
1241{
1242 ui_list_entry_t *next;
1243 ui_list_entry_t *pgend;
1244 size_t i;
1245 size_t rows;
1246
1247 next = ui_list_next(list->page);
1248 if (next == NULL)
1249 return;
1250
1251 rows = ui_list_page_size(list);
1252
1253 /* Find last page entry */
1254 pgend = list->page;
1255 for (i = 0; i < rows && pgend != NULL; i++) {
1256 pgend = ui_list_next(pgend);
1257 }
1258
1259 /* Scroll by up to 'rows' entries, keeping the page full */
1260 for (i = 0; i < rows && pgend != NULL; i++) {
1261 list->page = next;
1262 ++list->page_idx;
1263 next = ui_list_next(next);
1264 pgend = ui_list_next(pgend);
1265 }
1266
1267 ui_list_scrollbar_update(list);
1268 (void) ui_list_paint(list);
1269}
1270
1271/** Scroll to a specific entry
1272 *
1273 * @param list UI list
1274 * @param page_idx New index of first entry on the page
1275 */
1276void ui_list_scroll_pos(ui_list_t *list, size_t page_idx)
1277{
1278 ui_list_entry_t *entry;
1279 size_t i;
1280
1281 entry = ui_list_first(list);
1282 for (i = 0; i < page_idx; i++) {
1283 entry = ui_list_next(entry);
1284 assert(entry != NULL);
1285 }
1286
1287 list->page = entry;
1288 list->page_idx = page_idx;
1289
1290 (void) ui_list_paint(list);
1291}
1292
1293/** Request UI list activation.
1294 *
1295 * Call back to request UI list activation.
1296 *
1297 * @param list UI list
1298 */
1299void ui_list_activate_req(ui_list_t *list)
1300{
1301 if (list->cb != NULL && list->cb->activate_req != NULL) {
1302 list->cb->activate_req(list, list->cb_arg);
1303 } else {
1304 /*
1305 * If there is no callback for activation request,
1306 * just activate the list.
1307 */
1308 ui_list_activate(list);
1309 }
1310}
1311
1312/** Sort list entries.
1313 *
1314 * @param list UI list
1315 * @return EOK on success, ENOMEM if out of memory
1316 */
1317errno_t ui_list_sort(ui_list_t *list)
1318{
1319 ui_list_entry_t **emap;
1320 ui_list_entry_t *entry;
1321 size_t i;
1322
1323 /* Create an array to hold pointer to each entry */
1324 emap = calloc(list->entries_cnt, sizeof(ui_list_entry_t *));
1325 if (emap == NULL)
1326 return ENOMEM;
1327
1328 /* Write entry pointers to array */
1329 entry = ui_list_first(list);
1330 i = 0;
1331 while (entry != NULL) {
1332 assert(i < list->entries_cnt);
1333 emap[i++] = entry;
1334 entry = ui_list_next(entry);
1335 }
1336
1337 /* Sort the array of pointers */
1338 qsort(emap, list->entries_cnt, sizeof(ui_list_entry_t *),
1339 ui_list_entry_ptr_cmp);
1340
1341 /* Unlink entries from entry list */
1342 entry = ui_list_first(list);
1343 while (entry != NULL) {
1344 list_remove(&entry->lentries);
1345 entry = ui_list_first(list);
1346 }
1347
1348 /* Add entries back to entry list sorted */
1349 for (i = 0; i < list->entries_cnt; i++)
1350 list_append(&emap[i]->lentries, &list->entries);
1351
1352 free(emap);
1353
1354 list->page = ui_list_first(list);
1355 list->page_idx = 0;
1356 list->cursor = ui_list_first(list);
1357 list->cursor_idx = 0;
1358 return EOK;
1359}
1360
1361/** Determine list entry index.
1362 *
1363 * @param entry List entry
1364 * @return List entry index
1365 */
1366size_t ui_list_entry_get_idx(ui_list_entry_t *entry)
1367{
1368 ui_list_entry_t *ep;
1369 size_t idx;
1370
1371 idx = 0;
1372 ep = ui_list_prev(entry);
1373 while (ep != NULL) {
1374 ++idx;
1375 ep = ui_list_prev(ep);
1376 }
1377
1378 return idx;
1379}
1380
1381/** Center list cursor on entry.
1382 *
1383 * @param list UI list
1384 * @param entry Entry
1385 */
1386void ui_list_cursor_center(ui_list_t *list, ui_list_entry_t *entry)
1387{
1388 ui_list_entry_t *prev;
1389 size_t idx;
1390 size_t max_idx;
1391 size_t pg_size;
1392 size_t i;
1393
1394 idx = ui_list_entry_get_idx(entry);
1395 list->cursor = entry;
1396 list->cursor_idx = idx;
1397
1398 /* Move page so that cursor is in the center */
1399 list->page = list->cursor;
1400 list->page_idx = list->cursor_idx;
1401
1402 pg_size = ui_list_page_size(list);
1403
1404 for (i = 0; i < pg_size / 2; i++) {
1405 prev = ui_list_prev(list->page);
1406 if (prev == NULL)
1407 break;
1408
1409 list->page = prev;
1410 --list->page_idx;
1411 }
1412
1413 /* Make sure page is not beyond the end if possible */
1414 if (list->entries_cnt > pg_size)
1415 max_idx = list->entries_cnt - pg_size;
1416 else
1417 max_idx = 0;
1418
1419 while (list->page_idx > 0 && list->page_idx > max_idx) {
1420 prev = ui_list_prev(list->page);
1421 if (prev == NULL)
1422 break;
1423
1424 list->page = prev;
1425 --list->page_idx;
1426 }
1427}
1428
1429/** Call back when an entry is selected.
1430 *
1431 * @param entry UI list entry
1432 * @param fname File name
1433 */
1434void ui_list_selected(ui_list_entry_t *entry)
1435{
1436 if (entry->list->cb != NULL && entry->list->cb->selected != NULL)
1437 entry->list->cb->selected(entry, entry->arg);
1438}
1439
1440/** UI list scrollbar up button pressed.
1441 *
1442 * @param scrollbar Scrollbar
1443 * @param arg Argument (ui_list_t *)
1444 */
1445static void ui_list_scrollbar_up(ui_scrollbar_t *scrollbar, void *arg)
1446{
1447 ui_list_t *list = (ui_list_t *)arg;
1448 ui_list_scroll_up(list);
1449}
1450
1451/** UI list scrollbar down button pressed.
1452 *
1453 * @param scrollbar Scrollbar
1454 * @param arg Argument (ui_list_t *)
1455 */
1456static void ui_list_scrollbar_down(ui_scrollbar_t *scrollbar, void *arg)
1457{
1458 ui_list_t *list = (ui_list_t *)arg;
1459 ui_list_scroll_down(list);
1460}
1461
1462/** UI list scrollbar page up pressed.
1463 *
1464 * @param scrollbar Scrollbar
1465 * @param arg Argument (ui_list_t *)
1466 */
1467static void ui_list_scrollbar_page_up(ui_scrollbar_t *scrollbar, void *arg)
1468{
1469 ui_list_t *list = (ui_list_t *)arg;
1470 ui_list_scroll_page_up(list);
1471}
1472
1473/** UI list scrollbar page down pressed.
1474 *
1475 * @param scrollbar Scrollbar
1476 * @param arg Argument (ui_list_t *)
1477 */
1478static void ui_list_scrollbar_page_down(ui_scrollbar_t *scrollbar,
1479 void *arg)
1480{
1481 ui_list_t *list = (ui_list_t *)arg;
1482 ui_list_scroll_page_down(list);
1483}
1484
1485/** UI list scrollbar moved.
1486 *
1487 * @param scrollbar Scrollbar
1488 * @param arg Argument (ui_list_t *)
1489 */
1490static void ui_list_scrollbar_moved(ui_scrollbar_t *scrollbar, void *arg,
1491 gfx_coord_t pos)
1492{
1493 ui_list_t *list = (ui_list_t *)arg;
1494 size_t entries;
1495 size_t pglen;
1496 size_t sbar_len;
1497 size_t pgstart;
1498
1499 entries = list_count(&list->entries);
1500 pglen = ui_list_page_size(list);
1501 sbar_len = ui_scrollbar_move_length(list->scrollbar);
1502
1503 if (entries > pglen)
1504 pgstart = (entries - pglen) * pos / (sbar_len - 1);
1505 else
1506 pgstart = 0;
1507
1508 ui_list_scroll_pos(list, pgstart);
1509}
1510
1511/** Compare two list entries indirectly referenced by pointers.
1512 *
1513 * @param pa Pointer to pointer to first entry
1514 * @param pb Pointer to pointer to second entry
1515 * @return <0, =0, >=0 if pa < b, pa == pb, pa > pb, resp.
1516 */
1517int ui_list_entry_ptr_cmp(const void *pa, const void *pb)
1518{
1519 ui_list_entry_t *a = *(ui_list_entry_t **)pa;
1520 ui_list_entry_t *b = *(ui_list_entry_t **)pb;
1521
1522 return a->list->cb->compare(a, b);
1523}
1524
1525/** @}
1526 */
Note: See TracBrowser for help on using the repository browser.