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

ticket/834-toolchain-update
Last change on this file since 1c6c3e1d was a77c722, checked in by Jiri Svoboda <jiri@…>, 23 months ago

Update page_idx and cursor_idx after deleting UI list entry

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