source: mainline/uspace/lib/ui/src/filelist.c@ 03fc3a9

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

Correct handling of partially visible file list entry

  • Property mode set to 100644
File size: 29.1 KB
Line 
1/*
2 * Copyright (c) 2022 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 File list.
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/filelist.h>
45#include <ui/paint.h>
46#include <ui/resource.h>
47#include <vfs/vfs.h>
48#include <qsort.h>
49#include "../private/filelist.h"
50#include "../private/resource.h"
51
52static void ui_file_list_ctl_destroy(void *);
53static errno_t ui_file_list_ctl_paint(void *);
54static ui_evclaim_t ui_file_list_ctl_kbd_event(void *, kbd_event_t *);
55static ui_evclaim_t ui_file_list_ctl_pos_event(void *, pos_event_t *);
56
57/** File list control ops */
58ui_control_ops_t ui_file_list_ctl_ops = {
59 .destroy = ui_file_list_ctl_destroy,
60 .paint = ui_file_list_ctl_paint,
61 .kbd_event = ui_file_list_ctl_kbd_event,
62 .pos_event = ui_file_list_ctl_pos_event
63};
64
65enum {
66 file_list_entry_hpad = 2,
67 file_list_entry_vpad = 2,
68 file_list_entry_hpad_text = 1,
69 file_list_entry_vpad_text = 0,
70};
71
72/** Create file list.
73 *
74 * @param window Containing window
75 * @param active @c true iff file list should be active
76 * @param rflist Place to store pointer to new file list
77 * @return EOK on success or an error code
78 */
79errno_t ui_file_list_create(ui_window_t *window, bool active,
80 ui_file_list_t **rflist)
81{
82 ui_file_list_t *flist;
83 errno_t rc;
84
85 flist = calloc(1, sizeof(ui_file_list_t));
86 if (flist == NULL)
87 return ENOMEM;
88
89 rc = ui_control_new(&ui_file_list_ctl_ops, (void *)flist,
90 &flist->control);
91 if (rc != EOK) {
92 free(flist);
93 return rc;
94 }
95
96 rc = gfx_color_new_ega(0x0f, &flist->dir_color);
97 if (rc != EOK)
98 goto error;
99
100 rc = gfx_color_new_ega(0x0a, &flist->svc_color);
101 if (rc != EOK)
102 goto error;
103
104 flist->window = window;
105 list_initialize(&flist->entries);
106 flist->entries_cnt = 0;
107 flist->active = active;
108
109 *rflist = flist;
110 return EOK;
111error:
112 if (flist->dir_color != NULL)
113 gfx_color_delete(flist->dir_color);
114 if (flist->svc_color != NULL)
115 gfx_color_delete(flist->svc_color);
116 ui_control_delete(flist->control);
117 free(flist);
118 return rc;
119}
120
121/** Destroy file list.
122 *
123 * @param flist File list
124 */
125void ui_file_list_destroy(ui_file_list_t *flist)
126{
127 ui_file_list_clear_entries(flist);
128 ui_control_delete(flist->control);
129 free(flist);
130}
131
132/** Set file list callbacks.
133 *
134 * @param flist File list
135 * @param cb Callbacks
136 * @param arg Argument to callback functions
137 */
138void ui_file_list_set_cb(ui_file_list_t *flist, ui_file_list_cb_t *cb, void *arg)
139{
140 flist->cb = cb;
141 flist->cb_arg = arg;
142}
143
144/** Get height of file list entry.
145 *
146 * @param flist File list
147 * @return Entry height in pixels
148 */
149gfx_coord_t ui_file_list_entry_height(ui_file_list_t *flist)
150{
151 ui_resource_t *res;
152 gfx_font_metrics_t metrics;
153 gfx_coord_t height;
154 gfx_coord_t vpad;
155
156 res = ui_window_get_res(flist->window);
157
158 if (res->textmode) {
159 vpad = file_list_entry_vpad_text;
160 } else {
161 vpad = file_list_entry_vpad;
162 }
163
164 /* Normal menu entry */
165 gfx_font_get_metrics(res->font, &metrics);
166 height = metrics.ascent + metrics.descent + 1;
167
168 return height + 2 * vpad;
169}
170
171/** Paint file list entry.
172 *
173 * @param entry File list entry
174 * @param entry_idx Entry index (within list of entries)
175 * @return EOK on success or an error code
176 */
177errno_t ui_file_list_entry_paint(ui_file_list_entry_t *entry, size_t entry_idx)
178{
179 ui_file_list_t *flist = entry->flist;
180 gfx_context_t *gc = ui_window_get_gc(flist->window);
181 ui_resource_t *res = ui_window_get_res(flist->window);
182 gfx_font_t *font = ui_resource_get_font(res);
183 gfx_text_fmt_t fmt;
184 gfx_coord2_t pos;
185 gfx_rect_t rect;
186 gfx_rect_t lrect;
187 gfx_rect_t crect;
188 gfx_color_t *bgcolor;
189 char *caption;
190 gfx_coord_t hpad, vpad;
191 gfx_coord_t line_height;
192 size_t rows;
193 errno_t rc;
194 int rv;
195
196 line_height = ui_file_list_entry_height(flist);
197 ui_file_list_inside_rect(entry->flist, &lrect);
198
199 gfx_text_fmt_init(&fmt);
200 fmt.font = font;
201 rows = ui_file_list_page_size(flist) + 1;
202
203 /* Do not display entry outside of current page */
204 if (entry_idx < flist->page_idx ||
205 entry_idx >= flist->page_idx + rows)
206 return EOK;
207
208 if (res->textmode) {
209 hpad = file_list_entry_hpad_text;
210 vpad = file_list_entry_vpad_text;
211 } else {
212 hpad = file_list_entry_hpad;
213 vpad = file_list_entry_vpad;
214 }
215
216 pos.x = lrect.p0.x;
217 pos.y = lrect.p0.y + line_height * (entry_idx - flist->page_idx);
218
219 if (entry == flist->cursor && flist->active) {
220 fmt.color = res->entry_sel_text_fg_color;
221 bgcolor = res->entry_sel_text_bg_color;
222 } else if (entry->isdir) {
223 if (res->textmode) {
224 fmt.color = flist->dir_color;
225 bgcolor = flist->dir_color;
226 } else {
227 fmt.color = res->entry_fg_color;
228 bgcolor = res->entry_bg_color;
229 }
230 } else if (entry->svc != 0) {
231 if (res->textmode) {
232 fmt.color = flist->svc_color;
233 bgcolor = flist->svc_color;
234 } else {
235 fmt.color = res->entry_fg_color;
236 bgcolor = res->entry_bg_color;
237 }
238 } else {
239 fmt.color = res->entry_fg_color;
240 bgcolor = res->entry_bg_color;
241 }
242
243 /* Draw entry background */
244 rect.p0 = pos;
245 rect.p1.x = lrect.p1.x;
246 rect.p1.y = rect.p0.y + line_height;
247
248 /* Clip to file list interior */
249 gfx_rect_clip(&rect, &lrect, &crect);
250
251 rc = gfx_set_color(gc, bgcolor);
252 if (rc != EOK)
253 return rc;
254
255 rc = gfx_fill_rect(gc, &crect);
256 if (rc != EOK)
257 return rc;
258
259 /*
260 * Make sure name does not overflow the entry rectangle.
261 *
262 * XXX We probably want to measure the text width, and,
263 * if it's too long, use gfx_text_find_pos() to find where
264 * it should be cut off (and append some sort of overflow
265 * marker.
266 */
267 rc = gfx_set_clip_rect(gc, &crect);
268 if (rc != EOK)
269 return rc;
270
271 pos.x += hpad;
272 pos.y += vpad;
273
274 if (res->textmode || !entry->isdir) {
275 rc = gfx_puttext(&pos, &fmt, entry->name);
276 if (rc != EOK) {
277 (void) gfx_set_clip_rect(gc, NULL);
278 return rc;
279 }
280 } else {
281 /*
282 * XXX This is mostly a hack to distinguish directories
283 * util a better solution is available. (E.g. a Size
284 * column where we can put <dir>, an icon.)
285 */
286 rv = asprintf(&caption, "[%s]", entry->name);
287 if (rv < 0) {
288 (void) gfx_set_clip_rect(gc, NULL);
289 return rc;
290 }
291
292 rc = gfx_puttext(&pos, &fmt, caption);
293 if (rc != EOK) {
294 free(caption);
295 (void) gfx_set_clip_rect(gc, NULL);
296 return rc;
297 }
298
299 free(caption);
300 }
301
302 return gfx_set_clip_rect(gc, NULL);
303}
304
305/** Paint file list.
306 *
307 * @param flist File list
308 */
309errno_t ui_file_list_paint(ui_file_list_t *flist)
310{
311 gfx_context_t *gc = ui_window_get_gc(flist->window);
312 ui_resource_t *res = ui_window_get_res(flist->window);
313 ui_file_list_entry_t *entry;
314 int i, lines;
315 errno_t rc;
316
317 rc = gfx_set_color(gc, res->entry_bg_color);
318 if (rc != EOK)
319 return rc;
320
321 rc = gfx_fill_rect(gc, &flist->rect);
322 if (rc != EOK)
323 return rc;
324
325 if (!res->textmode) {
326 rc = ui_paint_inset_frame(res, &flist->rect, NULL);
327 if (rc != EOK)
328 return rc;
329 }
330
331 lines = ui_file_list_page_size(flist) + 1;
332 i = 0;
333
334 entry = flist->page;
335 while (entry != NULL && i < lines) {
336 rc = ui_file_list_entry_paint(entry, flist->page_idx + i);
337 if (rc != EOK)
338 return rc;
339
340 ++i;
341 entry = ui_file_list_next(entry);
342 }
343
344 rc = gfx_update(gc);
345 if (rc != EOK)
346 return rc;
347
348 return EOK;
349}
350
351/** Handle file list keyboard event.
352 *
353 * @param flist File list
354 * @param event Keyboard event
355 * @return ui_claimed iff event was claimed
356 */
357ui_evclaim_t ui_file_list_kbd_event(ui_file_list_t *flist, kbd_event_t *event)
358{
359 if (!flist->active)
360 return ui_unclaimed;
361
362 if (event->type == KEY_PRESS) {
363 if ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
364 switch (event->key) {
365 case KC_UP:
366 ui_file_list_cursor_up(flist);
367 break;
368 case KC_DOWN:
369 ui_file_list_cursor_down(flist);
370 break;
371 case KC_HOME:
372 ui_file_list_cursor_top(flist);
373 break;
374 case KC_END:
375 ui_file_list_cursor_bottom(flist);
376 break;
377 case KC_PAGE_UP:
378 ui_file_list_page_up(flist);
379 break;
380 case KC_PAGE_DOWN:
381 ui_file_list_page_down(flist);
382 break;
383 case KC_ENTER:
384 ui_file_list_open(flist, flist->cursor);
385 break;
386 default:
387 break;
388 }
389 }
390 }
391
392 return ui_claimed;
393}
394
395/** Handle file list position event.
396 *
397 * @param flist File list
398 * @param event Position event
399 * @return ui_claimed iff event was claimed
400 */
401ui_evclaim_t ui_file_list_pos_event(ui_file_list_t *flist, pos_event_t *event)
402{
403 gfx_coord2_t pos;
404 gfx_rect_t irect;
405 ui_file_list_entry_t *entry;
406 gfx_coord_t line_height;
407 size_t entry_idx;
408 int n;
409
410 line_height = ui_file_list_entry_height(flist);
411
412 pos.x = event->hpos;
413 pos.y = event->vpos;
414 if (!gfx_pix_inside_rect(&pos, &flist->rect))
415 return ui_unclaimed;
416
417 if (!flist->active && event->type == POS_PRESS)
418 ui_file_list_activate_req(flist);
419
420 if (event->type == POS_PRESS || event->type == POS_DCLICK) {
421 ui_file_list_inside_rect(flist, &irect);
422
423 /* Did we click on one of the entries? */
424 if (gfx_pix_inside_rect(&pos, &irect)) {
425 /* Index within page */
426 n = (pos.y - irect.p0.y) / line_height;
427
428 /* Entry and its index within entire listing */
429 entry = ui_file_list_page_nth_entry(flist, n, &entry_idx);
430 if (entry == NULL)
431 return ui_claimed;
432
433 if (event->type == POS_PRESS) {
434 /* Move to the entry found */
435 ui_file_list_cursor_move(flist, entry, entry_idx);
436 } else {
437 /* event->type == POS_DCLICK */
438 ui_file_list_open(flist, entry);
439 }
440 } else {
441 /* It's in the border. */
442 if (event->type == POS_PRESS) {
443 /* Top or bottom half? */
444 if (pos.y >= (irect.p0.y + irect.p1.y) / 2)
445 ui_file_list_page_down(flist);
446 else
447 ui_file_list_page_up(flist);
448 }
449 }
450 }
451
452 return ui_claimed;
453}
454
455/** Get base control for file list.
456 *
457 * @param flist File list
458 * @return Base UI control
459 */
460ui_control_t *ui_file_list_ctl(ui_file_list_t *flist)
461{
462 return flist->control;
463}
464
465/** Set file list rectangle.
466 *
467 * @param flist File list
468 * @param rect Rectangle
469 */
470void ui_file_list_set_rect(ui_file_list_t *flist, gfx_rect_t *rect)
471{
472 flist->rect = *rect;
473}
474
475/** Get file list page size.
476 *
477 * @param flist File list
478 * @return Number of entries that fit in flist at the same time.
479 */
480unsigned ui_file_list_page_size(ui_file_list_t *flist)
481{
482 gfx_coord_t line_height;
483 gfx_rect_t irect;
484
485 line_height = ui_file_list_entry_height(flist);
486 ui_file_list_inside_rect(flist, &irect);
487 return (irect.p1.y - irect.p0.y) / line_height;
488}
489
490void ui_file_list_inside_rect(ui_file_list_t *flist, gfx_rect_t *irect)
491{
492 ui_resource_t *res = ui_window_get_res(flist->window);
493
494 if (res->textmode) {
495 *irect = flist->rect;
496 } else {
497 ui_paint_get_inset_frame_inside(res, &flist->rect, irect);
498 }
499}
500
501/** Determine if file list is active.
502 *
503 * @param flist File list
504 * @return @c true iff file list is active
505 */
506bool ui_file_list_is_active(ui_file_list_t *flist)
507{
508 return flist->active;
509}
510
511/** Activate file list.
512 *
513 * @param flist File list
514 *
515 * @return EOK on success or an error code
516 */
517errno_t ui_file_list_activate(ui_file_list_t *flist)
518{
519 errno_t rc;
520
521 if (flist->dir != NULL) {
522 rc = vfs_cwd_set(flist->dir);
523 if (rc != EOK)
524 return rc;
525 }
526
527 flist->active = true;
528 (void) ui_file_list_paint(flist);
529 return EOK;
530}
531
532/** Deactivate file list.
533 *
534 * @param flist File list
535 */
536void ui_file_list_deactivate(ui_file_list_t *flist)
537{
538 flist->active = false;
539 (void) ui_file_list_paint(flist);
540}
541
542/** Initialize file list entry attributes.
543 *
544 * @param attr Attributes
545 */
546void ui_file_list_entry_attr_init(ui_file_list_entry_attr_t *attr)
547{
548 memset(attr, 0, sizeof(*attr));
549}
550
551/** Destroy file list control.
552 *
553 * @param arg Argument (ui_file_list_t *)
554 */
555void ui_file_list_ctl_destroy(void *arg)
556{
557 ui_file_list_t *flist = (ui_file_list_t *) arg;
558
559 ui_file_list_destroy(flist);
560}
561
562/** Paint file list control.
563 *
564 * @param arg Argument (ui_file_list_t *)
565 * @return EOK on success or an error code
566 */
567errno_t ui_file_list_ctl_paint(void *arg)
568{
569 ui_file_list_t *flist = (ui_file_list_t *) arg;
570
571 return ui_file_list_paint(flist);
572}
573
574/** Handle file list control keyboard event.
575 *
576 * @param arg Argument (ui_file_list_t *)
577 * @param kbd_event Keyboard event
578 * @return @c ui_claimed iff the event is claimed
579 */
580ui_evclaim_t ui_file_list_ctl_kbd_event(void *arg, kbd_event_t *event)
581{
582 ui_file_list_t *flist = (ui_file_list_t *) arg;
583
584 return ui_file_list_kbd_event(flist, event);
585}
586
587/** Handle file list control position event.
588 *
589 * @param arg Argument (ui_file_list_t *)
590 * @param pos_event Position event
591 * @return @c ui_claimed iff the event is claimed
592 */
593ui_evclaim_t ui_file_list_ctl_pos_event(void *arg, pos_event_t *event)
594{
595 ui_file_list_t *flist = (ui_file_list_t *) arg;
596
597 return ui_file_list_pos_event(flist, event);
598}
599
600/** Append new file list entry.
601 *
602 * @param flist File list
603 * @param attr Entry attributes
604 * @return EOK on success or an error code
605 */
606errno_t ui_file_list_entry_append(ui_file_list_t *flist, ui_file_list_entry_attr_t *attr)
607{
608 ui_file_list_entry_t *entry;
609
610 entry = calloc(1, sizeof(ui_file_list_entry_t));
611 if (entry == NULL)
612 return ENOMEM;
613
614 entry->flist = flist;
615 entry->name = str_dup(attr->name);
616 if (entry->name == NULL) {
617 free(entry);
618 return ENOMEM;
619 }
620
621 entry->size = attr->size;
622 entry->isdir = attr->isdir;
623 entry->svc = attr->svc;
624 link_initialize(&entry->lentries);
625 list_append(&entry->lentries, &flist->entries);
626 ++flist->entries_cnt;
627 return EOK;
628}
629
630/** Delete file list entry.
631 *
632 * @param entry File list entry
633 */
634void ui_file_list_entry_delete(ui_file_list_entry_t *entry)
635{
636 if (entry->flist->cursor == entry)
637 entry->flist->cursor = NULL;
638 if (entry->flist->page == entry)
639 entry->flist->page = NULL;
640
641 list_remove(&entry->lentries);
642 --entry->flist->entries_cnt;
643 free((char *) entry->name);
644 free(entry);
645}
646
647/** Clear file list entry list.
648 *
649 * @param flist File list
650 */
651void ui_file_list_clear_entries(ui_file_list_t *flist)
652{
653 ui_file_list_entry_t *entry;
654
655 entry = ui_file_list_first(flist);
656 while (entry != NULL) {
657 ui_file_list_entry_delete(entry);
658 entry = ui_file_list_first(flist);
659 }
660}
661
662/** Read directory into file list entry list.
663 *
664 * @param flist File list
665 * @param dirname Directory path
666 * @return EOK on success or an error code
667 */
668errno_t ui_file_list_read_dir(ui_file_list_t *flist, const char *dirname)
669{
670 DIR *dir;
671 struct dirent *dirent;
672 vfs_stat_t finfo;
673 char newdir[256];
674 char *ndir = NULL;
675 ui_file_list_entry_attr_t attr;
676 ui_file_list_entry_t *next;
677 ui_file_list_entry_t *prev;
678 char *olddn;
679 size_t pg_size;
680 size_t max_idx;
681 size_t i;
682 errno_t rc;
683
684 rc = vfs_cwd_set(dirname);
685 if (rc != EOK)
686 return rc;
687
688 rc = vfs_cwd_get(newdir, sizeof(newdir));
689 if (rc != EOK)
690 return rc;
691
692 ndir = str_dup(newdir);
693 if (ndir == NULL)
694 return ENOMEM;
695
696 dir = opendir(".");
697 if (dir == NULL) {
698 rc = errno;
699 goto error;
700 }
701
702 if (str_cmp(ndir, "/") != 0) {
703 /* Need to add a synthetic up-dir entry */
704 ui_file_list_entry_attr_init(&attr);
705 attr.name = "..";
706 attr.isdir = true;
707
708 rc = ui_file_list_entry_append(flist, &attr);
709 if (rc != EOK)
710 goto error;
711 }
712
713 dirent = readdir(dir);
714 while (dirent != NULL) {
715 rc = vfs_stat_path(dirent->d_name, &finfo);
716 if (rc != EOK) {
717 /* Possibly a stale entry */
718 dirent = readdir(dir);
719 continue;
720 }
721
722 ui_file_list_entry_attr_init(&attr);
723 attr.name = dirent->d_name;
724 attr.size = finfo.size;
725 attr.isdir = finfo.is_directory;
726 attr.svc = finfo.service;
727
728 rc = ui_file_list_entry_append(flist, &attr);
729 if (rc != EOK)
730 goto error;
731
732 dirent = readdir(dir);
733 }
734
735 closedir(dir);
736
737 rc = ui_file_list_sort(flist);
738 if (rc != EOK)
739 goto error;
740
741 flist->cursor = ui_file_list_first(flist);
742 flist->cursor_idx = 0;
743 flist->page = ui_file_list_first(flist);
744 flist->page_idx = 0;
745
746 /* Moving up? */
747 if (str_cmp(dirname, "..") == 0) {
748 /* Get the last component of old path */
749 olddn = str_rchr(flist->dir, '/');
750 if (olddn != NULL && *olddn != '\0') {
751 /* Find corresponding entry */
752 ++olddn;
753 next = ui_file_list_next(flist->cursor);
754 while (next != NULL && str_cmp(next->name, olddn) <= 0 &&
755 next->isdir) {
756 flist->cursor = next;
757 ++flist->cursor_idx;
758 next = ui_file_list_next(flist->cursor);
759 }
760
761 /* Move page so that cursor is in the center */
762 flist->page = flist->cursor;
763 flist->page_idx = flist->cursor_idx;
764
765 pg_size = ui_file_list_page_size(flist);
766
767 for (i = 0; i < pg_size / 2; i++) {
768 prev = ui_file_list_prev(flist->page);
769 if (prev == NULL)
770 break;
771
772 flist->page = prev;
773 --flist->page_idx;
774 }
775
776 /* Make sure page is not beyond the end if possible */
777 if (flist->entries_cnt > pg_size)
778 max_idx = flist->entries_cnt - pg_size;
779 else
780 max_idx = 0;
781
782 while (flist->page_idx > 0 && flist->page_idx > max_idx) {
783 prev = ui_file_list_prev(flist->page);
784 if (prev == NULL)
785 break;
786
787 flist->page = prev;
788 --flist->page_idx;
789 }
790 }
791 }
792
793 free(flist->dir);
794 flist->dir = ndir;
795
796 return EOK;
797error:
798 (void) vfs_cwd_set(flist->dir);
799 if (ndir != NULL)
800 free(ndir);
801 if (dir != NULL)
802 closedir(dir);
803 return rc;
804}
805
806/** Sort file list entries.
807 *
808 * @param flist File list
809 * @return EOK on success, ENOMEM if out of memory
810 */
811errno_t ui_file_list_sort(ui_file_list_t *flist)
812{
813 ui_file_list_entry_t **emap;
814 ui_file_list_entry_t *entry;
815 size_t i;
816
817 /* Create an array to hold pointer to each entry */
818 emap = calloc(flist->entries_cnt, sizeof(ui_file_list_entry_t *));
819 if (emap == NULL)
820 return ENOMEM;
821
822 /* Write entry pointers to array */
823 entry = ui_file_list_first(flist);
824 i = 0;
825 while (entry != NULL) {
826 assert(i < flist->entries_cnt);
827 emap[i++] = entry;
828 entry = ui_file_list_next(entry);
829 }
830
831 /* Sort the array of pointers */
832 qsort(emap, flist->entries_cnt, sizeof(ui_file_list_entry_t *),
833 ui_file_list_entry_ptr_cmp);
834
835 /* Unlink entries from entry list */
836 entry = ui_file_list_first(flist);
837 while (entry != NULL) {
838 list_remove(&entry->lentries);
839 entry = ui_file_list_first(flist);
840 }
841
842 /* Add entries back to entry list sorted */
843 for (i = 0; i < flist->entries_cnt; i++)
844 list_append(&emap[i]->lentries, &flist->entries);
845
846 free(emap);
847 return EOK;
848}
849
850/** Compare two file list entries indirectly referenced by pointers.
851 *
852 * @param pa Pointer to pointer to first entry
853 * @param pb Pointer to pointer to second entry
854 * @return <0, =0, >=0 if pa < b, pa == pb, pa > pb, resp.
855 */
856int ui_file_list_entry_ptr_cmp(const void *pa, const void *pb)
857{
858 ui_file_list_entry_t *a = *(ui_file_list_entry_t **)pa;
859 ui_file_list_entry_t *b = *(ui_file_list_entry_t **)pb;
860 int dcmp;
861
862 /* Sort directories first */
863 dcmp = b->isdir - a->isdir;
864 if (dcmp != 0)
865 return dcmp;
866
867 return str_cmp(a->name, b->name);
868}
869
870/** Return first file list entry.
871 *
872 * @param flist File list
873 * @return First file list entry or @c NULL if there are no entries
874 */
875ui_file_list_entry_t *ui_file_list_first(ui_file_list_t *flist)
876{
877 link_t *link;
878
879 link = list_first(&flist->entries);
880 if (link == NULL)
881 return NULL;
882
883 return list_get_instance(link, ui_file_list_entry_t, lentries);
884}
885
886/** Return last file list entry.
887 *
888 * @param flist File list
889 * @return Last file list entry or @c NULL if there are no entries
890 */
891ui_file_list_entry_t *ui_file_list_last(ui_file_list_t *flist)
892{
893 link_t *link;
894
895 link = list_last(&flist->entries);
896 if (link == NULL)
897 return NULL;
898
899 return list_get_instance(link, ui_file_list_entry_t, lentries);
900}
901
902/** Return next file list entry.
903 *
904 * @param cur Current entry
905 * @return Next entry or @c NULL if @a cur is the last entry
906 */
907ui_file_list_entry_t *ui_file_list_next(ui_file_list_entry_t *cur)
908{
909 link_t *link;
910
911 link = list_next(&cur->lentries, &cur->flist->entries);
912 if (link == NULL)
913 return NULL;
914
915 return list_get_instance(link, ui_file_list_entry_t, lentries);
916}
917
918/** Return previous file list entry.
919 *
920 * @param cur Current entry
921 * @return Previous entry or @c NULL if @a cur is the first entry
922 */
923ui_file_list_entry_t *ui_file_list_prev(ui_file_list_entry_t *cur)
924{
925 link_t *link;
926
927 link = list_prev(&cur->lentries, &cur->flist->entries);
928 if (link == NULL)
929 return NULL;
930
931 return list_get_instance(link, ui_file_list_entry_t, lentries);
932}
933
934/** Find the n-th entry of the current file list page.
935 *
936 * If the page is short and has less than n+1 entries, return the last entry.
937 *
938 * @param flist File list
939 * @param n Which entry to get (starting from 0)
940 * @param ridx Place to store index (within listing) of the entry
941 * @return n-th entry of the page
942 */
943ui_file_list_entry_t *ui_file_list_page_nth_entry(ui_file_list_t *flist,
944 size_t n, size_t *ridx)
945{
946 ui_file_list_entry_t *entry;
947 ui_file_list_entry_t *next;
948 size_t i;
949 size_t idx;
950
951 assert(n <= ui_file_list_page_size(flist));
952
953 entry = flist->page;
954 if (entry == NULL)
955 return NULL;
956
957 idx = flist->page_idx;
958 for (i = 0; i < n; i++) {
959 next = ui_file_list_next(entry);
960 if (next == NULL)
961 break;
962
963 entry = next;
964 ++idx;
965 }
966
967 *ridx = idx;
968 return entry;
969}
970
971/** Move cursor to a new position, possibly scrolling.
972 *
973 * @param flist File list
974 * @param entry New entry under cursor
975 * @param entry_idx Index of new entry under cursor
976 */
977void ui_file_list_cursor_move(ui_file_list_t *flist,
978 ui_file_list_entry_t *entry, size_t entry_idx)
979{
980 gfx_context_t *gc = ui_window_get_gc(flist->window);
981 ui_file_list_entry_t *old_cursor;
982 size_t old_idx;
983 size_t rows;
984 ui_file_list_entry_t *e;
985 size_t i;
986
987 rows = ui_file_list_page_size(flist);
988
989 old_cursor = flist->cursor;
990 old_idx = flist->cursor_idx;
991
992 flist->cursor = entry;
993 flist->cursor_idx = entry_idx;
994
995 if (entry_idx >= flist->page_idx &&
996 entry_idx < flist->page_idx + rows) {
997 /*
998 * If cursor is still on the current page, we're not
999 * scrolling. Just unpaint old cursor and paint new
1000 * cursor.
1001 */
1002 ui_file_list_entry_paint(old_cursor, old_idx);
1003 ui_file_list_entry_paint(flist->cursor, flist->cursor_idx);
1004
1005 (void) gfx_update(gc);
1006 } else {
1007 /*
1008 * Need to scroll and update all rows.
1009 */
1010
1011 /* Scrolling up */
1012 if (entry_idx < flist->page_idx) {
1013 flist->page = entry;
1014 flist->page_idx = entry_idx;
1015 }
1016
1017 /* Scrolling down */
1018 if (entry_idx >= flist->page_idx + rows) {
1019 if (entry_idx >= rows) {
1020 flist->page_idx = entry_idx - rows + 1;
1021 /* Find first page entry (go back rows - 1) */
1022 e = entry;
1023 for (i = 0; i < rows - 1; i++) {
1024 e = ui_file_list_prev(e);
1025 }
1026
1027 /* Should be valid */
1028 assert(e != NULL);
1029 flist->page = e;
1030 } else {
1031 flist->page = ui_file_list_first(flist);
1032 flist->page_idx = 0;
1033 }
1034 }
1035
1036 (void) ui_file_list_paint(flist);
1037 }
1038}
1039
1040/** Move cursor one entry up.
1041 *
1042 * @param flist File list
1043 */
1044void ui_file_list_cursor_up(ui_file_list_t *flist)
1045{
1046 ui_file_list_entry_t *prev;
1047 size_t prev_idx;
1048
1049 prev = ui_file_list_prev(flist->cursor);
1050 prev_idx = flist->cursor_idx - 1;
1051 if (prev != NULL)
1052 ui_file_list_cursor_move(flist, prev, prev_idx);
1053}
1054
1055/** Move cursor one entry down.
1056 *
1057 * @param flist File list
1058 */
1059void ui_file_list_cursor_down(ui_file_list_t *flist)
1060{
1061 ui_file_list_entry_t *next;
1062 size_t next_idx;
1063
1064 next = ui_file_list_next(flist->cursor);
1065 next_idx = flist->cursor_idx + 1;
1066 if (next != NULL)
1067 ui_file_list_cursor_move(flist, next, next_idx);
1068}
1069
1070/** Move cursor to top.
1071 *
1072 * @param flist File list
1073 */
1074void ui_file_list_cursor_top(ui_file_list_t *flist)
1075{
1076 ui_file_list_cursor_move(flist, ui_file_list_first(flist), 0);
1077}
1078
1079/** Move cursor to bottom.
1080 *
1081 * @param flist File list
1082 */
1083void ui_file_list_cursor_bottom(ui_file_list_t *flist)
1084{
1085 ui_file_list_cursor_move(flist, ui_file_list_last(flist),
1086 flist->entries_cnt - 1);
1087}
1088
1089/** Move one page up.
1090 *
1091 * @param flist File list
1092 */
1093void ui_file_list_page_up(ui_file_list_t *flist)
1094{
1095 gfx_context_t *gc = ui_window_get_gc(flist->window);
1096 ui_file_list_entry_t *old_page;
1097 ui_file_list_entry_t *old_cursor;
1098 size_t old_idx;
1099 size_t rows;
1100 ui_file_list_entry_t *entry;
1101 size_t i;
1102
1103 rows = ui_file_list_page_size(flist);
1104
1105 old_page = flist->page;
1106 old_cursor = flist->cursor;
1107 old_idx = flist->cursor_idx;
1108
1109 /* Move page by rows entries up (if possible) */
1110 for (i = 0; i < rows; i++) {
1111 entry = ui_file_list_prev(flist->page);
1112 if (entry != NULL) {
1113 flist->page = entry;
1114 --flist->page_idx;
1115 }
1116 }
1117
1118 /* Move cursor by rows entries up (if possible) */
1119
1120 for (i = 0; i < rows; i++) {
1121 entry = ui_file_list_prev(flist->cursor);
1122 if (entry != NULL) {
1123 flist->cursor = entry;
1124 --flist->cursor_idx;
1125 }
1126 }
1127
1128 if (flist->page != old_page) {
1129 /* We have scrolled. Need to repaint all entries */
1130 (void) ui_file_list_paint(flist);
1131 } else if (flist->cursor != old_cursor) {
1132 /* No scrolling, but cursor has moved */
1133 ui_file_list_entry_paint(old_cursor, old_idx);
1134 ui_file_list_entry_paint(flist->cursor, flist->cursor_idx);
1135
1136 (void) gfx_update(gc);
1137 }
1138}
1139
1140/** Move one page down.
1141 *
1142 * @param flist File list
1143 */
1144void ui_file_list_page_down(ui_file_list_t *flist)
1145{
1146 gfx_context_t *gc = ui_window_get_gc(flist->window);
1147 ui_file_list_entry_t *old_page;
1148 ui_file_list_entry_t *old_cursor;
1149 size_t old_idx;
1150 size_t max_idx;
1151 size_t rows;
1152 ui_file_list_entry_t *entry;
1153 size_t i;
1154
1155 rows = ui_file_list_page_size(flist);
1156
1157 old_page = flist->page;
1158 old_cursor = flist->cursor;
1159 old_idx = flist->cursor_idx;
1160
1161 if (flist->entries_cnt > rows)
1162 max_idx = flist->entries_cnt - rows;
1163 else
1164 max_idx = 0;
1165
1166 /* Move page by rows entries down (if possible) */
1167 for (i = 0; i < rows; i++) {
1168 entry = ui_file_list_next(flist->page);
1169 /* Do not scroll that results in a short page */
1170 if (entry != NULL && flist->page_idx < max_idx) {
1171 flist->page = entry;
1172 ++flist->page_idx;
1173 }
1174 }
1175
1176 /* Move cursor by rows entries down (if possible) */
1177
1178 for (i = 0; i < rows; i++) {
1179 entry = ui_file_list_next(flist->cursor);
1180 if (entry != NULL) {
1181 flist->cursor = entry;
1182 ++flist->cursor_idx;
1183 }
1184 }
1185
1186 if (flist->page != old_page) {
1187 /* We have scrolled. Need to repaint all entries */
1188 (void) ui_file_list_paint(flist);
1189 } else if (flist->cursor != old_cursor) {
1190 /* No scrolling, but cursor has moved */
1191 ui_file_list_entry_paint(old_cursor, old_idx);
1192 ui_file_list_entry_paint(flist->cursor, flist->cursor_idx);
1193
1194 (void) gfx_update(gc);
1195 }
1196}
1197
1198/** Open file list entry.
1199 *
1200 * Perform Open action on a file list entry (e.g. switch to a subdirectory).
1201 *
1202 * @param flist File list
1203 * @param entry File list entry
1204 *
1205 * @return EOK on success or an error code
1206 */
1207errno_t ui_file_list_open(ui_file_list_t *flist, ui_file_list_entry_t *entry)
1208{
1209 if (entry->isdir)
1210 return ui_file_list_open_dir(flist, entry);
1211 else if (entry->svc == 0)
1212 return ui_file_list_open_file(flist, entry);
1213 else
1214 return EOK;
1215}
1216
1217/** Open file list directory entry.
1218 *
1219 * Perform Open action on a directory entry (i.e. switch to the directory).
1220 *
1221 * @param flist File list
1222 * @param entry File list entry (which is a directory)
1223 *
1224 * @return EOK on success or an error code
1225 */
1226errno_t ui_file_list_open_dir(ui_file_list_t *flist,
1227 ui_file_list_entry_t *entry)
1228{
1229 gfx_context_t *gc = ui_window_get_gc(flist->window);
1230 char *dirname;
1231 errno_t rc;
1232
1233 assert(entry->isdir);
1234
1235 /*
1236 * Need to copy out name before we free the entry below
1237 * via ui_file_list_clear_entries().
1238 */
1239 dirname = str_dup(entry->name);
1240 if (dirname == NULL)
1241 return ENOMEM;
1242
1243 ui_file_list_clear_entries(flist);
1244
1245 rc = ui_file_list_read_dir(flist, dirname);
1246 if (rc != EOK) {
1247 free(dirname);
1248 return rc;
1249 }
1250
1251 free(dirname);
1252
1253 rc = ui_file_list_paint(flist);
1254 if (rc != EOK)
1255 return rc;
1256
1257 return gfx_update(gc);
1258}
1259
1260/** Open file list file entry.
1261 *
1262 * Perform Open action on a file entry (i.e. try running it).
1263 *
1264 * @param flist File list
1265 * @param entry File list entry (which is a file)
1266 *
1267 * @return EOK on success or an error code
1268 */
1269errno_t ui_file_list_open_file(ui_file_list_t *flist,
1270 ui_file_list_entry_t *entry)
1271{
1272 ui_file_list_selected(flist, entry->name);
1273 return EOK;
1274}
1275
1276/** Request file list activation.
1277 *
1278 * Call back to request file list activation.
1279 *
1280 * @param flist File list
1281 */
1282void ui_file_list_activate_req(ui_file_list_t *flist)
1283{
1284 if (flist->cb != NULL && flist->cb->activate_req != NULL)
1285 flist->cb->activate_req(flist, flist->cb_arg);
1286}
1287
1288/** Call back when a file is selected.
1289 *
1290 * @param flist File list
1291 * @param fname File name
1292 */
1293void ui_file_list_selected(ui_file_list_t *flist, const char *fname)
1294{
1295 if (flist->cb != NULL && flist->cb->selected != NULL)
1296 flist->cb->selected(flist, flist->cb_arg, fname);
1297}
1298
1299/** @}
1300 */
Note: See TracBrowser for help on using the repository browser.