source: mainline/uspace/lib/ui/src/filelist.c@ 11662bd

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

File list control

File dialogs now allow browsing files/directories using the new
file list control. This is essentialy a copy of the Panel class
from Navigator, modified and extended to work in graphics mode as well.
Later Panel should be re-implemented using file list to prevent
duplication.

Note that this is not 100% done, it needs, for example, a scrollbar
(instead of scrolling by clicking the edges).

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