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

Last change on this file was 28ca31ed, checked in by Jiri Svoboda <jiri@…>, 17 months ago

Moving start menu entry up and down

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