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

Last change on this file since f9c4c433 was f9c4c433, checked in by Jiri Svoboda <jiri@…>, 3 weeks ago

Create new file in Navigator (WIP)

  • Property mode set to 100644
File size: 16.7 KB
Line 
1/*
2 * Copyright (c) 2025 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 control.
33 *
34 * Displays a file listing.
35 */
36
37#include <dirent.h>
38#include <errno.h>
39#include <stdlib.h>
40#include <str.h>
41#include <ui/control.h>
42#include <ui/filelist.h>
43#include <ui/list.h>
44#include <ui/resource.h>
45#include <vfs/vfs.h>
46#include <qsort.h>
47#include "../private/filelist.h"
48#include "../private/list.h"
49#include "../private/resource.h"
50
51static void ui_file_list_ctl_destroy(void *);
52static errno_t ui_file_list_ctl_paint(void *);
53static ui_evclaim_t ui_file_list_ctl_kbd_event(void *, kbd_event_t *);
54static ui_evclaim_t ui_file_list_ctl_pos_event(void *, pos_event_t *);
55
56/** List control ops */
57ui_control_ops_t ui_file_list_ctl_ops = {
58 .destroy = ui_file_list_ctl_destroy,
59 .paint = ui_file_list_ctl_paint,
60 .kbd_event = ui_file_list_ctl_kbd_event,
61 .pos_event = ui_file_list_ctl_pos_event
62};
63
64static void ui_file_list_list_activate_req(ui_list_t *, void *);
65static void ui_file_list_list_selected(ui_list_entry_t *, void *);
66
67/** List callbacks */
68ui_list_cb_t ui_file_list_list_cb = {
69 .activate_req = ui_file_list_list_activate_req,
70 .selected = ui_file_list_list_selected,
71 .compare = ui_file_list_list_compare,
72};
73
74/** Create file list.
75 *
76 * @param window Containing window
77 * @param active @c true iff file list should be active
78 * @param rflist Place to store pointer to new file list
79 * @return EOK on success or an error code
80 */
81errno_t ui_file_list_create(ui_window_t *window, bool active,
82 ui_file_list_t **rflist)
83{
84 ui_file_list_t *flist;
85 errno_t rc;
86
87 flist = calloc(1, sizeof(ui_file_list_t));
88 if (flist == NULL)
89 return ENOMEM;
90
91 rc = ui_control_new(&ui_file_list_ctl_ops, (void *)flist,
92 &flist->control);
93 if (rc != EOK)
94 goto error;
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 rc = ui_list_create(window, active, &flist->list);
105 if (rc != EOK)
106 goto error;
107
108 ui_list_set_cb(flist->list, &ui_file_list_list_cb, (void *)flist);
109
110 flist->window = window;
111 *rflist = flist;
112 return EOK;
113error:
114 ui_control_delete(flist->control);
115 if (flist->dir_color != NULL)
116 gfx_color_delete(flist->dir_color);
117 if (flist->svc_color != NULL)
118 gfx_color_delete(flist->svc_color);
119 free(flist);
120 return rc;
121}
122
123/** Destroy file list.
124 *
125 * @param flist File list
126 */
127void ui_file_list_destroy(ui_file_list_t *flist)
128{
129 ui_file_list_clear_entries(flist);
130 ui_list_destroy(flist->list);
131 ui_control_delete(flist->control);
132 free(flist);
133}
134
135/** Set file list callbacks.
136 *
137 * @param flist File list
138 * @param cb Callbacks
139 * @param arg Argument to callback functions
140 */
141void ui_file_list_set_cb(ui_file_list_t *flist, ui_file_list_cb_t *cb, void *arg)
142{
143 flist->cb = cb;
144 flist->cb_arg = arg;
145}
146
147/** Get base control for file list.
148 *
149 * @param flist File list
150 * @return Base UI control
151 */
152ui_control_t *ui_file_list_ctl(ui_file_list_t *flist)
153{
154 return flist->control;
155}
156
157/** Set file list rectangle.
158 *
159 * @param flist File list
160 * @param rect Rectangle
161 */
162void ui_file_list_set_rect(ui_file_list_t *flist, gfx_rect_t *rect)
163{
164 ui_list_set_rect(flist->list, rect);
165}
166
167/** Determine if file list is active.
168 *
169 * @param flist File list
170 * @return @c true iff file list is active
171 */
172bool ui_file_list_is_active(ui_file_list_t *flist)
173{
174 return ui_list_is_active(flist->list);
175}
176
177/** Activate file list.
178 *
179 * @param flist File list
180 *
181 * @return EOK on success or an error code
182 */
183errno_t ui_file_list_activate(ui_file_list_t *flist)
184{
185 errno_t rc;
186
187 if (flist->dir != NULL) {
188 rc = vfs_cwd_set(flist->dir);
189 if (rc != EOK)
190 return rc;
191 }
192
193 return ui_list_activate(flist->list);
194}
195
196/** Deactivate file list.
197 *
198 * @param flist File list
199 */
200void ui_file_list_deactivate(ui_file_list_t *flist)
201{
202 ui_list_deactivate(flist->list);
203}
204
205/** Initialize file list entry attributes.
206 *
207 * @param attr Attributes
208 */
209void ui_file_list_entry_attr_init(ui_file_list_entry_attr_t *attr)
210{
211 memset(attr, 0, sizeof(*attr));
212}
213
214/** Append new file list entry.
215 *
216 * @param flist File list
217 * @param attr Entry attributes
218 * @return EOK on success or an error code
219 */
220errno_t ui_file_list_entry_append(ui_file_list_t *flist, ui_file_list_entry_attr_t *attr)
221{
222 ui_file_list_entry_t *entry;
223 ui_list_entry_attr_t lattr;
224 ui_list_entry_t *lentry;
225 ui_resource_t *res;
226 char *caption;
227 errno_t rc;
228 int rv;
229
230 res = ui_window_get_res(flist->window);
231
232 entry = calloc(1, sizeof(ui_file_list_entry_t));
233 if (entry == NULL)
234 return ENOMEM;
235
236 entry->flist = flist;
237 entry->name = str_dup(attr->name);
238 if (entry->name == NULL) {
239 free(entry);
240 return ENOMEM;
241 }
242
243 entry->size = attr->size;
244 entry->isdir = attr->isdir;
245 entry->svc = attr->svc;
246
247 if (attr->isdir && !res->textmode) {
248 rv = asprintf(&caption, "[%s]", attr->name);
249 if (rv < 0)
250 caption = NULL;
251 } else {
252 caption = str_dup(attr->name);
253 }
254
255 if (caption == NULL) {
256 free(entry->name);
257 free(entry);
258 return ENOMEM;
259 }
260
261 lattr.caption = caption;
262 lattr.arg = (void *)entry;
263 lattr.color = NULL;
264 lattr.bgcolor = NULL;
265
266 if (res->textmode) {
267 if (attr->isdir) {
268 lattr.color = flist->dir_color;
269 lattr.bgcolor = flist->dir_color;
270 } else if (attr->svc != 0) {
271 lattr.color = flist->svc_color;
272 lattr.bgcolor = flist->svc_color;
273 }
274 }
275
276 rc = ui_list_entry_append(flist->list, &lattr, &lentry);
277 if (rc != EOK) {
278 free(caption);
279 free(entry->name);
280 free(entry);
281 return rc;
282 }
283
284 free(caption);
285 entry->entry = lentry;
286 return EOK;
287}
288
289/** Delete file list entry.
290 *
291 * @param entry File list entry
292 */
293void ui_file_list_entry_destroy(ui_file_list_entry_t *entry)
294{
295 ui_list_entry_destroy(entry->entry);
296 free(entry->name);
297 free(entry);
298}
299
300/** Clear file list entry list.
301 *
302 * @param flist File list
303 */
304void ui_file_list_clear_entries(ui_file_list_t *flist)
305{
306 ui_file_list_entry_t *entry;
307
308 entry = ui_file_list_first(flist);
309 while (entry != NULL) {
310 ui_file_list_entry_destroy(entry);
311 entry = ui_file_list_first(flist);
312 }
313}
314
315/** Read directory into file list entry list.
316 *
317 * @param flist File list
318 * @param dirname Directory path
319 * @return EOK on success or an error code
320 */
321errno_t ui_file_list_read_dir(ui_file_list_t *flist, const char *dirname)
322{
323 DIR *dir;
324 struct dirent *dirent;
325 vfs_stat_t finfo;
326 char newdir[256];
327 char *ndir = NULL;
328 ui_file_list_entry_attr_t attr;
329 ui_file_list_entry_t *cur;
330 ui_file_list_entry_t *next;
331 char *olddn;
332 errno_t rc;
333
334 rc = vfs_cwd_set(dirname);
335 if (rc != EOK)
336 return rc;
337
338 rc = vfs_cwd_get(newdir, sizeof(newdir));
339 if (rc != EOK)
340 return rc;
341
342 ndir = str_dup(newdir);
343 if (ndir == NULL)
344 return ENOMEM;
345
346 dir = opendir(".");
347 if (dir == NULL) {
348 rc = errno;
349 goto error;
350 }
351
352 ui_file_list_clear_entries(flist);
353
354 if (str_cmp(ndir, "/") != 0) {
355 /* Need to add a synthetic up-dir entry */
356 ui_file_list_entry_attr_init(&attr);
357 attr.name = "..";
358 attr.isdir = true;
359
360 rc = ui_file_list_entry_append(flist, &attr);
361 if (rc != EOK)
362 goto error;
363 }
364
365 dirent = readdir(dir);
366 while (dirent != NULL) {
367 rc = vfs_stat_path(dirent->d_name, &finfo);
368 if (rc != EOK) {
369 /* Possibly a stale entry */
370 dirent = readdir(dir);
371 continue;
372 }
373
374 ui_file_list_entry_attr_init(&attr);
375 attr.name = dirent->d_name;
376 attr.size = finfo.size;
377 attr.isdir = finfo.is_directory;
378 attr.svc = finfo.service;
379
380 rc = ui_file_list_entry_append(flist, &attr);
381 if (rc != EOK)
382 goto error;
383
384 dirent = readdir(dir);
385 }
386
387 closedir(dir);
388
389 rc = ui_file_list_sort(flist);
390 if (rc != EOK)
391 goto error;
392
393 /* Moving up? */
394 if (str_cmp(dirname, "..") == 0) {
395 /* Get the last component of old path */
396 olddn = str_rchr(flist->dir, '/');
397 if (olddn != NULL && *olddn != '\0') {
398 /* Find corresponding entry */
399 ++olddn;
400 cur = ui_file_list_first(flist);
401 next = ui_file_list_next(cur);
402 while (next != NULL && str_cmp(next->name, olddn) <= 0 &&
403 next->isdir) {
404 cur = next;
405 next = ui_file_list_next(cur);
406 }
407
408 /* Center on the entry */
409 ui_list_cursor_center(flist->list, cur->entry);
410 }
411 }
412
413 free(flist->dir);
414 flist->dir = ndir;
415
416 return EOK;
417error:
418 (void) vfs_cwd_set(flist->dir);
419 if (ndir != NULL)
420 free(ndir);
421 if (dir != NULL)
422 closedir(dir);
423 return rc;
424}
425
426/** Re-read file list from directory.
427 *
428 * @param flist File list
429 * @return EOK on success or an error code
430 */
431errno_t ui_file_list_refresh(ui_file_list_t *flist)
432{
433 errno_t rc;
434 ui_list_pos_t pos;
435
436 ui_list_save_pos(flist->list, &pos);
437 rc = ui_file_list_read_dir(flist, flist->dir);
438 if (rc != EOK)
439 return rc;
440 ui_list_restore_pos(flist->list, &pos);
441 return EOK;
442}
443
444/** Sort file list entries.
445 *
446 * @param flist File list
447 * @return EOK on success, ENOMEM if out of memory
448 */
449errno_t ui_file_list_sort(ui_file_list_t *flist)
450{
451 return ui_list_sort(flist->list);
452}
453
454/** Compare two list entries within file list entries.
455 *
456 * @param ea First UI list entry
457 * @param eb Second UI list entry
458 * @return <0, =0, >=0 if a < b, a == b, a > b, resp.
459 */
460int ui_file_list_list_compare(ui_list_entry_t *ea, ui_list_entry_t *eb)
461{
462 ui_file_list_entry_t *a;
463 ui_file_list_entry_t *b;
464 int dcmp;
465
466 a = (ui_file_list_entry_t *)ui_list_entry_get_arg(ea);
467 b = (ui_file_list_entry_t *)ui_list_entry_get_arg(eb);
468
469 /* Sort directories first */
470 dcmp = b->isdir - a->isdir;
471 if (dcmp != 0)
472 return dcmp;
473
474 return str_cmp(a->name, b->name);
475}
476
477/** Return first file list entry.
478 *
479 * @param flist File list
480 * @return First file list entry or @c NULL if there are no entries
481 */
482ui_file_list_entry_t *ui_file_list_first(ui_file_list_t *flist)
483{
484 ui_list_entry_t *lentry;
485
486 lentry = ui_list_first(flist->list);
487 if (lentry == NULL)
488 return NULL;
489
490 return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
491}
492
493/** Return last file list entry.
494 *
495 * @param flist File list
496 * @return Last file list entry or @c NULL if there are no entries
497 */
498ui_file_list_entry_t *ui_file_list_last(ui_file_list_t *flist)
499{
500 ui_list_entry_t *lentry;
501
502 lentry = ui_list_last(flist->list);
503 if (lentry == NULL)
504 return NULL;
505
506 return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
507}
508
509/** Return next file list entry.
510 *
511 * @param cur Current entry
512 * @return Next entry or @c NULL if @a cur is the last entry
513 */
514ui_file_list_entry_t *ui_file_list_next(ui_file_list_entry_t *cur)
515{
516 ui_list_entry_t *lentry;
517
518 lentry = ui_list_next(cur->entry);
519 if (lentry == NULL)
520 return NULL;
521
522 return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
523}
524
525/** Return previous file list entry.
526 *
527 * @param cur Current entry
528 * @return Previous entry or @c NULL if @a cur is the first entry
529 */
530ui_file_list_entry_t *ui_file_list_prev(ui_file_list_entry_t *cur)
531{
532 ui_list_entry_t *lentry;
533
534 lentry = ui_list_prev(cur->entry);
535 if (lentry == NULL)
536 return NULL;
537
538 return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
539}
540
541/** Get entry under cursor.
542 *
543 * @param flist File list
544 * @return Current cursor
545 */
546ui_file_list_entry_t *ui_file_list_get_cursor(ui_file_list_t *flist)
547{
548 ui_list_entry_t *entry;
549
550 entry = ui_list_get_cursor(flist->list);
551 if (entry == NULL)
552 return NULL;
553
554 return (ui_file_list_entry_t *)ui_list_entry_get_arg(entry);
555}
556
557/** Get file list entry attributes.
558 *
559 * @param entry File list entry
560 * @return Current cursor
561 */
562void ui_file_list_entry_get_attr(ui_file_list_entry_t *entry,
563 ui_file_list_entry_attr_t *attr)
564{
565 attr->name = entry->name;
566 attr->size = entry->size;
567 attr->isdir = entry->isdir;
568 attr->svc = entry->svc;
569}
570
571/** Open file list entry.
572 *
573 * Perform Open action on a file list entry (e.g. switch to a subdirectory).
574 *
575 * @param flist File list
576 * @param entry File list entry
577 *
578 * @return EOK on success or an error code
579 */
580errno_t ui_file_list_open(ui_file_list_t *flist, ui_file_list_entry_t *entry)
581{
582 if (entry->isdir)
583 return ui_file_list_open_dir(flist, entry);
584 else if (entry->svc == 0)
585 return ui_file_list_open_file(flist, entry);
586 else
587 return EOK;
588}
589
590/** Open file list directory entry.
591 *
592 * Perform Open action on a directory entry (i.e. switch to the directory).
593 *
594 * @param flist File list
595 * @param entry File list entry (which is a directory)
596 *
597 * @return EOK on success or an error code
598 */
599errno_t ui_file_list_open_dir(ui_file_list_t *flist,
600 ui_file_list_entry_t *entry)
601{
602 char *dirname;
603 errno_t rc;
604
605 assert(entry->isdir);
606
607 /*
608 * Need to copy out name before we free the entry below
609 * via ui_file_list_clear_entries().
610 */
611 dirname = str_dup(entry->name);
612 if (dirname == NULL)
613 return ENOMEM;
614
615 rc = ui_file_list_read_dir(flist, dirname);
616 if (rc != EOK) {
617 free(dirname);
618 return rc;
619 }
620
621 free(dirname);
622
623 rc = ui_file_list_paint(flist);
624 if (rc != EOK)
625 return rc;
626
627 return EOK;
628}
629
630/** Open file list file entry.
631 *
632 * Perform Open action on a file entry (i.e. try running it).
633 *
634 * @param flist File list
635 * @param entry File list entry (which is a file)
636 *
637 * @return EOK on success or an error code
638 */
639errno_t ui_file_list_open_file(ui_file_list_t *flist,
640 ui_file_list_entry_t *entry)
641{
642 ui_file_list_selected(flist, entry->name);
643 return EOK;
644}
645
646/** Request file list activation.
647 *
648 * Call back to request file list activation.
649 *
650 * @param flist File list
651 */
652void ui_file_list_activate_req(ui_file_list_t *flist)
653{
654 if (flist->cb != NULL && flist->cb->activate_req != NULL)
655 flist->cb->activate_req(flist, flist->cb_arg);
656}
657
658/** Call back when a file is selected.
659 *
660 * @param flist File list
661 * @param fname File name
662 */
663void ui_file_list_selected(ui_file_list_t *flist, const char *fname)
664{
665 if (flist->cb != NULL && flist->cb->selected != NULL)
666 flist->cb->selected(flist, flist->cb_arg, fname);
667}
668
669/** Paint file list.
670 *
671 * @param flist File list
672 * @return EOK on success or an error code.
673 */
674errno_t ui_file_list_paint(ui_file_list_t *flist)
675{
676 return ui_control_paint(ui_list_ctl(flist->list));
677}
678
679/** Destroy file list control.
680 *
681 * @param arg Argument (ui_list_t *)
682 */
683void ui_file_list_ctl_destroy(void *arg)
684{
685 ui_file_list_t *flist = (ui_file_list_t *) arg;
686
687 ui_file_list_destroy(flist);
688}
689
690/** Paint file list control.
691 *
692 * @param arg Argument (ui_file_list_t *)
693 * @return EOK on success or an error code
694 */
695errno_t ui_file_list_ctl_paint(void *arg)
696{
697 ui_file_list_t *flist = (ui_file_list_t *) arg;
698
699 return ui_file_list_paint(flist);
700}
701
702/** Handle file list control keyboard event.
703 *
704 * @param arg Argument (ui_file_list_t *)
705 * @param kbd_event Keyboard event
706 * @return @c ui_claimed iff the event is claimed
707 */
708ui_evclaim_t ui_file_list_ctl_kbd_event(void *arg, kbd_event_t *event)
709{
710 ui_file_list_t *flist = (ui_file_list_t *) arg;
711
712 return ui_control_kbd_event(ui_list_ctl(flist->list), event);
713}
714
715/** Handle file list control position event.
716 *
717 * @param arg Argument (ui_file_list_t *)
718 * @param pos_event Position event
719 * @return @c ui_claimed iff the event is claimed
720 */
721ui_evclaim_t ui_file_list_ctl_pos_event(void *arg, pos_event_t *event)
722{
723 ui_file_list_t *flist = (ui_file_list_t *) arg;
724
725 return ui_control_pos_event(ui_list_ctl(flist->list), event);
726}
727
728/** Activate request callback handler for UI list within file list.
729 *
730 * @param list UI list
731 * @param arg Argument (File list)
732 */
733static void ui_file_list_list_activate_req(ui_list_t *list, void *arg)
734{
735 ui_file_list_t *flist = (ui_file_list_t *)arg;
736
737 ui_file_list_activate_req(flist);
738}
739
740/** Entry selected callback handler for UI list within file list.
741 *
742 * @param entr Activated UI list entry
743 * @param arg Argument (File list entry)
744 */
745static void ui_file_list_list_selected(ui_list_entry_t *entry, void *arg)
746{
747 ui_file_list_entry_t *fentry = (void *)arg;
748
749 (void) ui_file_list_open(fentry->flist, fentry);
750}
751
752/** @}
753 */
Note: See TracBrowser for help on using the repository browser.