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

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

Generic UI list control

Derived from file list, now file list is based on UI list.
Whew!

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