source: mainline/uspace/app/nav/panel.c@ 03c4b23

Last change on this file since 03c4b23 was 03c4b23, checked in by Jiri Svoboda <jiri@…>, 4 years ago

Sort panel entries

  • Property mode set to 100644
File size: 17.8 KB
Line 
1/*
2 * Copyright (c) 2021 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 nav
30 * @{
31 */
32/** @file Navigator panel.
33 *
34 * Displays a file listing.
35 */
36
37#include <dirent.h>
38#include <errno.h>
39#include <gfx/render.h>
40#include <gfx/text.h>
41#include <stdlib.h>
42#include <str.h>
43#include <ui/control.h>
44#include <ui/paint.h>
45#include <ui/resource.h>
46#include <qsort.h>
47#include "panel.h"
48#include "nav.h"
49
50static void panel_ctl_destroy(void *);
51static errno_t panel_ctl_paint(void *);
52static ui_evclaim_t panel_ctl_kbd_event(void *, kbd_event_t *);
53static ui_evclaim_t panel_ctl_pos_event(void *, pos_event_t *);
54
55/** Panel control ops */
56ui_control_ops_t panel_ctl_ops = {
57 .destroy = panel_ctl_destroy,
58 .paint = panel_ctl_paint,
59 .kbd_event = panel_ctl_kbd_event,
60 .pos_event = panel_ctl_pos_event
61};
62
63/** Create panel.
64 *
65 * @param window Containing window
66 * @param active @c true iff panel should be active
67 * @param rpanel Place to store pointer to new panel
68 * @return EOK on success or an error code
69 */
70errno_t panel_create(ui_window_t *window, bool active, panel_t **rpanel)
71{
72 panel_t *panel;
73 errno_t rc;
74
75 panel = calloc(1, sizeof(panel_t));
76 if (panel == NULL)
77 return ENOMEM;
78
79 rc = ui_control_new(&panel_ctl_ops, (void *)panel,
80 &panel->control);
81 if (rc != EOK) {
82 free(panel);
83 return rc;
84 }
85
86 rc = gfx_color_new_ega(0x07, &panel->color);
87 if (rc != EOK)
88 goto error;
89
90 rc = gfx_color_new_ega(0x30, &panel->curs_color);
91 if (rc != EOK)
92 goto error;
93
94 rc = gfx_color_new_ega(0x0f, &panel->act_border_color);
95 if (rc != EOK)
96 goto error;
97
98 panel->window = window;
99 list_initialize(&panel->entries);
100 panel->entries_cnt = 0;
101 panel->active = active;
102 *rpanel = panel;
103 return EOK;
104error:
105 if (panel->color != NULL)
106 gfx_color_delete(panel->color);
107 if (panel->curs_color != NULL)
108 gfx_color_delete(panel->curs_color);
109 ui_control_delete(panel->control);
110 free(panel);
111 return rc;
112}
113
114/** Destroy panel.
115 *
116 * @param panel Panel
117 */
118void panel_destroy(panel_t *panel)
119{
120 gfx_color_delete(panel->color);
121 gfx_color_delete(panel->curs_color);
122 gfx_color_delete(panel->act_border_color);
123 panel_clear_entries(panel);
124 ui_control_delete(panel->control);
125 free(panel);
126}
127
128/** Paint panel entry.
129 *
130 * @param entry Panel entry
131 * @param entry_idx Entry index (within list of entries)
132 * @return EOK on success or an error code
133 */
134errno_t panel_entry_paint(panel_entry_t *entry, size_t entry_idx)
135{
136 panel_t *panel = entry->panel;
137 gfx_context_t *gc = ui_window_get_gc(panel->window);
138 ui_resource_t *res = ui_window_get_res(panel->window);
139 gfx_font_t *font = ui_resource_get_font(res);
140 gfx_text_fmt_t fmt;
141 gfx_coord2_t pos;
142 gfx_rect_t rect;
143 size_t rows;
144 errno_t rc;
145
146 gfx_text_fmt_init(&fmt);
147 rows = panel_page_size(panel);
148
149 /* Do not display entry outside of current page */
150 if (entry_idx < panel->page_idx ||
151 entry_idx >= panel->page_idx + rows)
152 return EOK;
153
154 pos.x = panel->rect.p0.x + 1;
155 pos.y = panel->rect.p0.y + 1 + entry_idx - panel->page_idx;
156
157 if (entry == panel->cursor && panel->active)
158 fmt.color = panel->curs_color;
159 else
160 fmt.color = panel->color;
161
162 /* Draw entry background */
163 rect.p0 = pos;
164 rect.p1.x = panel->rect.p1.x - 1;
165 rect.p1.y = rect.p0.y + 1;
166
167 rc = gfx_set_color(gc, fmt.color);
168 if (rc != EOK)
169 return rc;
170
171 rc = gfx_fill_rect(gc, &rect);
172 if (rc != EOK)
173 return rc;
174
175 rc = gfx_puttext(font, &pos, &fmt, entry->name);
176 if (rc != EOK)
177 return rc;
178
179 return EOK;
180}
181
182/** Paint panel.
183 *
184 * @param panel Panel
185 */
186errno_t panel_paint(panel_t *panel)
187{
188 gfx_context_t *gc = ui_window_get_gc(panel->window);
189 ui_resource_t *res = ui_window_get_res(panel->window);
190 gfx_text_fmt_t fmt;
191 panel_entry_t *entry;
192 ui_box_style_t bstyle;
193 gfx_color_t *bcolor;
194 int i, lines;
195 errno_t rc;
196
197 gfx_text_fmt_init(&fmt);
198
199 rc = gfx_set_color(gc, panel->color);
200 if (rc != EOK)
201 return rc;
202
203 rc = gfx_fill_rect(gc, &panel->rect);
204 if (rc != EOK)
205 return rc;
206
207 if (panel->active) {
208 bstyle = ui_box_double;
209 bcolor = panel->act_border_color;
210 } else {
211 bstyle = ui_box_single;
212 bcolor = panel->color;
213 }
214
215 rc = ui_paint_text_box(res, &panel->rect, bstyle, bcolor);
216 if (rc != EOK)
217 return rc;
218
219 lines = panel_page_size(panel);
220 i = 0;
221
222 entry = panel->page;
223 while (entry != NULL && i < lines) {
224 rc = panel_entry_paint(entry, panel->page_idx + i);
225 if (rc != EOK)
226 return rc;
227
228 ++i;
229 entry = panel_next(entry);
230 }
231
232 rc = gfx_update(gc);
233 if (rc != EOK)
234 return rc;
235
236 return EOK;
237}
238
239/** Handle panel keyboard event.
240 *
241 * @param panel Panel
242 * @param event Keyboard event
243 * @return ui_claimed iff event was claimed
244 */
245ui_evclaim_t panel_kbd_event(panel_t *panel, kbd_event_t *event)
246{
247 if (!panel->active)
248 return ui_unclaimed;
249
250 if (event->type == KEY_PRESS) {
251 if ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
252 switch (event->key) {
253 case KC_UP:
254 panel_cursor_up(panel);
255 break;
256 case KC_DOWN:
257 panel_cursor_down(panel);
258 break;
259 case KC_HOME:
260 panel_cursor_top(panel);
261 break;
262 case KC_END:
263 panel_cursor_bottom(panel);
264 break;
265 case KC_PAGE_UP:
266 panel_page_up(panel);
267 break;
268 case KC_PAGE_DOWN:
269 panel_page_down(panel);
270 break;
271 default:
272 break;
273 }
274 }
275 }
276
277 return ui_claimed;
278}
279
280/** Handle panel position event.
281 *
282 * @param panel Panel
283 * @param event Position event
284 * @return ui_claimed iff event was claimed
285 */
286ui_evclaim_t panel_pos_event(panel_t *panel, pos_event_t *event)
287{
288 return ui_unclaimed;
289}
290
291/** Get base control for panel.
292 *
293 * @param panel Panel
294 * @return Base UI control
295 */
296ui_control_t *panel_ctl(panel_t *panel)
297{
298 return panel->control;
299}
300
301/** Set panel rectangle.
302 *
303 * @param panel Panel
304 * @param rect Rectangle
305 */
306void panel_set_rect(panel_t *panel, gfx_rect_t *rect)
307{
308 panel->rect = *rect;
309}
310
311/** Get panel page size.
312 *
313 * @param panel Panel
314 * @return Number of entries that fit in panel at the same time.
315 */
316unsigned panel_page_size(panel_t *panel)
317{
318 return panel->rect.p1.y - panel->rect.p0.y - 2;
319}
320
321/** Determine if panel is active.
322 *
323 * @param panel Panel
324 * @return @c true iff panel is active
325 */
326bool panel_is_active(panel_t *panel)
327{
328 return panel->active;
329}
330
331/** Activate panel.
332 *
333 * @param panel Panel
334 */
335void panel_activate(panel_t *panel)
336{
337 panel->active = true;
338 (void) panel_paint(panel);
339}
340
341/** Deactivate panel.
342 *
343 * @param panel Panel
344 */
345void panel_deactivate(panel_t *panel)
346{
347 panel->active = false;
348 (void) panel_paint(panel);
349}
350
351/** Destroy panel control.
352 *
353 * @param arg Argument (panel_t *)
354 */
355void panel_ctl_destroy(void *arg)
356{
357 panel_t *panel = (panel_t *) arg;
358
359 panel_destroy(panel);
360}
361
362/** Paint panel control.
363 *
364 * @param arg Argument (panel_t *)
365 * @return EOK on success or an error code
366 */
367errno_t panel_ctl_paint(void *arg)
368{
369 panel_t *panel = (panel_t *) arg;
370
371 return panel_paint(panel);
372}
373
374/** Handle panel control keyboard event.
375 *
376 * @param arg Argument (panel_t *)
377 * @param kbd_event Keyboard event
378 * @return @c ui_claimed iff the event is claimed
379 */
380ui_evclaim_t panel_ctl_kbd_event(void *arg, kbd_event_t *event)
381{
382 panel_t *panel = (panel_t *) arg;
383
384 return panel_kbd_event(panel, event);
385}
386
387/** Handle panel control position event.
388 *
389 * @param arg Argument (panel_t *)
390 * @param pos_event Position event
391 * @return @c ui_claimed iff the event is claimed
392 */
393ui_evclaim_t panel_ctl_pos_event(void *arg, pos_event_t *event)
394{
395 panel_t *panel = (panel_t *) arg;
396
397 return panel_pos_event(panel, event);
398}
399
400/** Append new panel entry.
401 *
402 * @param panel Panel
403 * @param name File name
404 * @param size File size;
405 * @return EOK on success or an error code
406 */
407errno_t panel_entry_append(panel_t *panel, const char *name, uint64_t size)
408{
409 panel_entry_t *entry;
410
411 entry = calloc(1, sizeof(panel_entry_t));
412 if (entry == NULL)
413 return ENOMEM;
414
415 entry->panel = panel;
416 entry->name = str_dup(name);
417 if (entry->name == NULL) {
418 free(entry);
419 return ENOMEM;
420 }
421
422 entry->size = size;
423 link_initialize(&entry->lentries);
424 list_append(&entry->lentries, &panel->entries);
425 ++panel->entries_cnt;
426 return EOK;
427}
428
429/** Delete panel entry.
430 *
431 * @param entry Panel entry
432 */
433void panel_entry_delete(panel_entry_t *entry)
434{
435 if (entry->panel->cursor == entry)
436 entry->panel->cursor = NULL;
437 if (entry->panel->page == entry)
438 entry->panel->page = NULL;
439
440 list_remove(&entry->lentries);
441 --entry->panel->entries_cnt;
442 free(entry->name);
443 free(entry);
444}
445
446/** Clear panel entry list.
447 *
448 * @param panel Panel
449 */
450void panel_clear_entries(panel_t *panel)
451{
452 panel_entry_t *entry;
453
454 entry = panel_first(panel);
455 while (entry != NULL) {
456 panel_entry_delete(entry);
457 entry = panel_first(panel);
458 }
459}
460
461/** Read directory into panel entry list.
462 *
463 * @param panel Panel
464 * @param dirname Directory path
465 * @return EOK on success or an error code
466 */
467errno_t panel_read_dir(panel_t *panel, const char *dirname)
468{
469 DIR *dir;
470 struct dirent *dirent;
471 errno_t rc;
472
473 dir = opendir(dirname);
474 if (dir == NULL)
475 return errno;
476
477 dirent = readdir(dir);
478 while (dirent != NULL) {
479 rc = panel_entry_append(panel, dirent->d_name, 1);
480 if (rc != EOK)
481 goto error;
482 dirent = readdir(dir);
483 }
484
485 closedir(dir);
486
487 rc = panel_sort(panel);
488 if (rc != EOK)
489 goto error;
490
491 panel->cursor = panel_first(panel);
492 panel->cursor_idx = 0;
493 panel->page = panel_first(panel);
494 panel->page_idx = 0;
495 return EOK;
496error:
497 closedir(dir);
498 return rc;
499}
500
501/** Sort panel entries.
502 *
503 * @param panel Panel
504 * @return EOK on success, ENOMEM if out of memory
505 */
506errno_t panel_sort(panel_t *panel)
507{
508 panel_entry_t **emap;
509 panel_entry_t *entry;
510 size_t i;
511
512 /* Create an array to hold pointer to each entry */
513 emap = calloc(panel->entries_cnt, sizeof(panel_entry_t *));
514 if (emap == NULL)
515 return ENOMEM;
516
517 /* Write entry pointers to array */
518 entry = panel_first(panel);
519 i = 0;
520 while (entry != NULL) {
521 assert(i < panel->entries_cnt);
522 emap[i++] = entry;
523 entry = panel_next(entry);
524 }
525
526 /* Sort the array of pointers */
527 qsort(emap, panel->entries_cnt, sizeof(panel_entry_t *),
528 panel_entry_ptr_cmp);
529
530 /* Unlink entries from entry list */
531 entry = panel_first(panel);
532 while (entry != NULL) {
533 list_remove(&entry->lentries);
534 entry = panel_first(panel);
535 }
536
537 /* Add entries back to entry list sorted */
538 for (i = 0; i < panel->entries_cnt; i++)
539 list_append(&emap[i]->lentries, &panel->entries);
540
541 free(emap);
542 return EOK;
543}
544
545/** Compare two panel entries indirectly referenced by pointers.
546 *
547 * @param pa Pointer to pointer to first entry
548 * @param pb Pointer to pointer to second entry
549 * @return <0, =0, >=0 if pa < b, pa == pb, pa > pb, resp.
550 */
551int panel_entry_ptr_cmp(const void *pa, const void *pb)
552{
553 panel_entry_t *a = *(panel_entry_t **)pa;
554 panel_entry_t *b = *(panel_entry_t **)pb;
555
556 return str_cmp(a->name, b->name);
557}
558
559/** Return first panel entry.
560 *
561 * @panel Panel
562 * @return First panel entry or @c NULL if there are no entries
563 */
564panel_entry_t *panel_first(panel_t *panel)
565{
566 link_t *link;
567
568 link = list_first(&panel->entries);
569 if (link == NULL)
570 return NULL;
571
572 return list_get_instance(link, panel_entry_t, lentries);
573}
574
575/** Return last panel entry.
576 *
577 * @panel Panel
578 * @return Last panel entry or @c NULL if there are no entries
579 */
580panel_entry_t *panel_last(panel_t *panel)
581{
582 link_t *link;
583
584 link = list_last(&panel->entries);
585 if (link == NULL)
586 return NULL;
587
588 return list_get_instance(link, panel_entry_t, lentries);
589}
590
591/** Return next panel entry.
592 *
593 * @param cur Current entry
594 * @return Next entry or @c NULL if @a cur is the last entry
595 */
596panel_entry_t *panel_next(panel_entry_t *cur)
597{
598 link_t *link;
599
600 link = list_next(&cur->lentries, &cur->panel->entries);
601 if (link == NULL)
602 return NULL;
603
604 return list_get_instance(link, panel_entry_t, lentries);
605}
606
607/** Return previous panel entry.
608 *
609 * @param cur Current entry
610 * @return Previous entry or @c NULL if @a cur is the first entry
611 */
612panel_entry_t *panel_prev(panel_entry_t *cur)
613{
614 link_t *link;
615
616 link = list_prev(&cur->lentries, &cur->panel->entries);
617 if (link == NULL)
618 return NULL;
619
620 return list_get_instance(link, panel_entry_t, lentries);
621}
622
623/** Move cursor to a new position, possibly scrolling.
624 *
625 * @param panel Panel
626 * @param entry New entry under cursor
627 * @param entry_idx Index of new entry under cursor
628 */
629void panel_cursor_move(panel_t *panel, panel_entry_t *entry, size_t entry_idx)
630{
631 gfx_context_t *gc = ui_window_get_gc(panel->window);
632 panel_entry_t *old_cursor;
633 size_t old_idx;
634 size_t rows;
635 panel_entry_t *e;
636 size_t i;
637
638 rows = panel_page_size(panel);
639
640 old_cursor = panel->cursor;
641 old_idx = panel->cursor_idx;
642
643 panel->cursor = entry;
644 panel->cursor_idx = entry_idx;
645
646 if (entry_idx >= panel->page_idx &&
647 entry_idx < panel->page_idx + rows) {
648 /*
649 * If cursor is still on the current page, we're not
650 * scrolling. Just unpaint old cursor and paint new
651 * cursor.
652 */
653 panel_entry_paint(old_cursor, old_idx);
654 panel_entry_paint(panel->cursor, panel->cursor_idx);
655
656 (void) gfx_update(gc);
657 } else {
658 /*
659 * Need to scroll and update all rows.
660 */
661
662 /* Scrolling up */
663 if (entry_idx < panel->page_idx) {
664 panel->page = entry;
665 panel->page_idx = entry_idx;
666 }
667
668 /* Scrolling down */
669 if (entry_idx >= panel->page_idx + rows) {
670 if (entry_idx >= rows) {
671 panel->page_idx = entry_idx - rows + 1;
672 /* Find first page entry (go back rows - 1) */
673 e = entry;
674 for (i = 0; i < rows - 1; i++) {
675 e = panel_prev(e);
676 }
677
678 /* Should be valid */
679 assert(e != NULL);
680 panel->page = e;
681 } else {
682 panel->page = panel_first(panel);
683 panel->page_idx = 0;
684 }
685 }
686
687 (void) panel_paint(panel);
688 }
689}
690
691/** Move cursor one entry up.
692 *
693 * @param panel Panel
694 */
695void panel_cursor_up(panel_t *panel)
696{
697 panel_entry_t *prev;
698 size_t prev_idx;
699
700 prev = panel_prev(panel->cursor);
701 prev_idx = panel->cursor_idx - 1;
702 if (prev != NULL)
703 panel_cursor_move(panel, prev, prev_idx);
704}
705
706/** Move cursor one entry down.
707 *
708 * @param panel Panel
709 */
710void panel_cursor_down(panel_t *panel)
711{
712 panel_entry_t *next;
713 size_t next_idx;
714
715 next = panel_next(panel->cursor);
716 next_idx = panel->cursor_idx + 1;
717 if (next != NULL)
718 panel_cursor_move(panel, next, next_idx);
719}
720
721/** Move cursor to top.
722 *
723 * @param panel Panel
724 */
725void panel_cursor_top(panel_t *panel)
726{
727 panel_cursor_move(panel, panel_first(panel), 0);
728}
729
730/** Move cursor to bottom.
731 *
732 * @param panel Panel
733 */
734void panel_cursor_bottom(panel_t *panel)
735{
736 panel_cursor_move(panel, panel_last(panel), panel->entries_cnt - 1);
737}
738
739/** Move one page up.
740 *
741 * @param panel Panel
742 */
743void panel_page_up(panel_t *panel)
744{
745 gfx_context_t *gc = ui_window_get_gc(panel->window);
746 panel_entry_t *old_page;
747 panel_entry_t *old_cursor;
748 size_t old_idx;
749 size_t rows;
750 panel_entry_t *entry;
751 size_t i;
752
753 rows = panel_page_size(panel);
754
755 old_page = panel->page;
756 old_cursor = panel->cursor;
757 old_idx = panel->cursor_idx;
758
759 /* Move page by rows entries up (if possible) */
760 for (i = 0; i < rows; i++) {
761 entry = panel_prev(panel->page);
762 if (entry != NULL) {
763 panel->page = entry;
764 --panel->page_idx;
765 }
766 }
767
768 /* Move cursor by rows entries up (if possible) */
769
770 for (i = 0; i < rows; i++) {
771 entry = panel_prev(panel->cursor);
772 if (entry != NULL) {
773 panel->cursor = entry;
774 --panel->cursor_idx;
775 }
776 }
777
778 if (panel->page != old_page) {
779 /* We have scrolled. Need to repaint all entries */
780 (void) panel_paint(panel);
781 } else if (panel->cursor != old_cursor) {
782 /* No scrolling, but cursor has moved */
783 panel_entry_paint(old_cursor, old_idx);
784 panel_entry_paint(panel->cursor, panel->cursor_idx);
785
786 (void) gfx_update(gc);
787 }
788}
789
790/** Move one page down.
791 *
792 * @param panel Panel
793 */
794void panel_page_down(panel_t *panel)
795{
796 gfx_context_t *gc = ui_window_get_gc(panel->window);
797 panel_entry_t *old_page;
798 panel_entry_t *old_cursor;
799 size_t old_idx;
800 size_t max_idx;
801 size_t rows;
802 panel_entry_t *entry;
803 size_t i;
804
805 rows = panel_page_size(panel);
806
807 old_page = panel->page;
808 old_cursor = panel->cursor;
809 old_idx = panel->cursor_idx;
810 max_idx = panel->entries_cnt - rows;
811
812 /* Move page by rows entries down (if possible) */
813 for (i = 0; i < rows; i++) {
814 entry = panel_next(panel->page);
815 /* Do not scroll that results in a short page */
816 if (entry != NULL && panel->page_idx < max_idx) {
817 panel->page = entry;
818 ++panel->page_idx;
819 }
820 }
821
822 /* Move cursor by rows entries down (if possible) */
823
824 for (i = 0; i < rows; i++) {
825 entry = panel_next(panel->cursor);
826 if (entry != NULL) {
827 panel->cursor = entry;
828 ++panel->cursor_idx;
829 }
830 }
831
832 if (panel->page != old_page) {
833 /* We have scrolled. Need to repaint all entries */
834 (void) panel_paint(panel);
835 } else if (panel->cursor != old_cursor) {
836 /* No scrolling, but cursor has moved */
837 panel_entry_paint(old_cursor, old_idx);
838 panel_entry_paint(panel->cursor, panel->cursor_idx);
839
840 (void) gfx_update(gc);
841 }
842}
843
844/** @}
845 */
Note: See TracBrowser for help on using the repository browser.