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

Last change on this file since 2309891 was 2309891, checked in by Jiri Svoboda <jiri@…>, 2 days ago

Copy files (Navigator and command line).

TODO Overwrite query, new I/O error types.

  • Property mode set to 100644
File size: 16.9 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/** Return path to the current directory.
427 *
428 * @return Path to current directory or @c NULL if out of memory
429 */
430char *ui_file_list_get_dir(ui_file_list_t *flist)
431{
432 return str_dup(flist->dir);
433}
434
435/** Re-read file list from directory.
436 *
437 * @param flist File list
438 * @return EOK on success or an error code
439 */
440errno_t ui_file_list_refresh(ui_file_list_t *flist)
441{
442 errno_t rc;
443 ui_list_pos_t pos;
444
445 ui_list_save_pos(flist->list, &pos);
446 rc = ui_file_list_read_dir(flist, flist->dir);
447 if (rc != EOK)
448 return rc;
449 ui_list_restore_pos(flist->list, &pos);
450 return EOK;
451}
452
453/** Sort file list entries.
454 *
455 * @param flist File list
456 * @return EOK on success, ENOMEM if out of memory
457 */
458errno_t ui_file_list_sort(ui_file_list_t *flist)
459{
460 return ui_list_sort(flist->list);
461}
462
463/** Compare two list entries within file list entries.
464 *
465 * @param ea First UI list entry
466 * @param eb Second UI list entry
467 * @return <0, =0, >=0 if a < b, a == b, a > b, resp.
468 */
469int ui_file_list_list_compare(ui_list_entry_t *ea, ui_list_entry_t *eb)
470{
471 ui_file_list_entry_t *a;
472 ui_file_list_entry_t *b;
473 int dcmp;
474
475 a = (ui_file_list_entry_t *)ui_list_entry_get_arg(ea);
476 b = (ui_file_list_entry_t *)ui_list_entry_get_arg(eb);
477
478 /* Sort directories first */
479 dcmp = b->isdir - a->isdir;
480 if (dcmp != 0)
481 return dcmp;
482
483 return str_cmp(a->name, b->name);
484}
485
486/** Return first file list entry.
487 *
488 * @param flist File list
489 * @return First file list entry or @c NULL if there are no entries
490 */
491ui_file_list_entry_t *ui_file_list_first(ui_file_list_t *flist)
492{
493 ui_list_entry_t *lentry;
494
495 lentry = ui_list_first(flist->list);
496 if (lentry == NULL)
497 return NULL;
498
499 return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
500}
501
502/** Return last file list entry.
503 *
504 * @param flist File list
505 * @return Last file list entry or @c NULL if there are no entries
506 */
507ui_file_list_entry_t *ui_file_list_last(ui_file_list_t *flist)
508{
509 ui_list_entry_t *lentry;
510
511 lentry = ui_list_last(flist->list);
512 if (lentry == NULL)
513 return NULL;
514
515 return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
516}
517
518/** Return next file list entry.
519 *
520 * @param cur Current entry
521 * @return Next entry or @c NULL if @a cur is the last entry
522 */
523ui_file_list_entry_t *ui_file_list_next(ui_file_list_entry_t *cur)
524{
525 ui_list_entry_t *lentry;
526
527 lentry = ui_list_next(cur->entry);
528 if (lentry == NULL)
529 return NULL;
530
531 return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
532}
533
534/** Return previous file list entry.
535 *
536 * @param cur Current entry
537 * @return Previous entry or @c NULL if @a cur is the first entry
538 */
539ui_file_list_entry_t *ui_file_list_prev(ui_file_list_entry_t *cur)
540{
541 ui_list_entry_t *lentry;
542
543 lentry = ui_list_prev(cur->entry);
544 if (lentry == NULL)
545 return NULL;
546
547 return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
548}
549
550/** Get entry under cursor.
551 *
552 * @param flist File list
553 * @return Current cursor
554 */
555ui_file_list_entry_t *ui_file_list_get_cursor(ui_file_list_t *flist)
556{
557 ui_list_entry_t *entry;
558
559 entry = ui_list_get_cursor(flist->list);
560 if (entry == NULL)
561 return NULL;
562
563 return (ui_file_list_entry_t *)ui_list_entry_get_arg(entry);
564}
565
566/** Get file list entry attributes.
567 *
568 * @param entry File list entry
569 * @return Current cursor
570 */
571void ui_file_list_entry_get_attr(ui_file_list_entry_t *entry,
572 ui_file_list_entry_attr_t *attr)
573{
574 attr->name = entry->name;
575 attr->size = entry->size;
576 attr->isdir = entry->isdir;
577 attr->svc = entry->svc;
578}
579
580/** Open file list entry.
581 *
582 * Perform Open action on a file list entry (e.g. switch to a subdirectory).
583 *
584 * @param flist File list
585 * @param entry File list entry
586 *
587 * @return EOK on success or an error code
588 */
589errno_t ui_file_list_open(ui_file_list_t *flist, ui_file_list_entry_t *entry)
590{
591 if (entry->isdir)
592 return ui_file_list_open_dir(flist, entry);
593 else if (entry->svc == 0)
594 return ui_file_list_open_file(flist, entry);
595 else
596 return EOK;
597}
598
599/** Open file list directory entry.
600 *
601 * Perform Open action on a directory entry (i.e. switch to the directory).
602 *
603 * @param flist File list
604 * @param entry File list entry (which is a directory)
605 *
606 * @return EOK on success or an error code
607 */
608errno_t ui_file_list_open_dir(ui_file_list_t *flist,
609 ui_file_list_entry_t *entry)
610{
611 char *dirname;
612 errno_t rc;
613
614 assert(entry->isdir);
615
616 /*
617 * Need to copy out name before we free the entry below
618 * via ui_file_list_clear_entries().
619 */
620 dirname = str_dup(entry->name);
621 if (dirname == NULL)
622 return ENOMEM;
623
624 rc = ui_file_list_read_dir(flist, dirname);
625 if (rc != EOK) {
626 free(dirname);
627 return rc;
628 }
629
630 free(dirname);
631
632 rc = ui_file_list_paint(flist);
633 if (rc != EOK)
634 return rc;
635
636 return EOK;
637}
638
639/** Open file list file entry.
640 *
641 * Perform Open action on a file entry (i.e. try running it).
642 *
643 * @param flist File list
644 * @param entry File list entry (which is a file)
645 *
646 * @return EOK on success or an error code
647 */
648errno_t ui_file_list_open_file(ui_file_list_t *flist,
649 ui_file_list_entry_t *entry)
650{
651 ui_file_list_selected(flist, entry->name);
652 return EOK;
653}
654
655/** Request file list activation.
656 *
657 * Call back to request file list activation.
658 *
659 * @param flist File list
660 */
661void ui_file_list_activate_req(ui_file_list_t *flist)
662{
663 if (flist->cb != NULL && flist->cb->activate_req != NULL)
664 flist->cb->activate_req(flist, flist->cb_arg);
665}
666
667/** Call back when a file is selected.
668 *
669 * @param flist File list
670 * @param fname File name
671 */
672void ui_file_list_selected(ui_file_list_t *flist, const char *fname)
673{
674 if (flist->cb != NULL && flist->cb->selected != NULL)
675 flist->cb->selected(flist, flist->cb_arg, fname);
676}
677
678/** Paint file list.
679 *
680 * @param flist File list
681 * @return EOK on success or an error code.
682 */
683errno_t ui_file_list_paint(ui_file_list_t *flist)
684{
685 return ui_control_paint(ui_list_ctl(flist->list));
686}
687
688/** Destroy file list control.
689 *
690 * @param arg Argument (ui_list_t *)
691 */
692void ui_file_list_ctl_destroy(void *arg)
693{
694 ui_file_list_t *flist = (ui_file_list_t *) arg;
695
696 ui_file_list_destroy(flist);
697}
698
699/** Paint file list control.
700 *
701 * @param arg Argument (ui_file_list_t *)
702 * @return EOK on success or an error code
703 */
704errno_t ui_file_list_ctl_paint(void *arg)
705{
706 ui_file_list_t *flist = (ui_file_list_t *) arg;
707
708 return ui_file_list_paint(flist);
709}
710
711/** Handle file list control keyboard event.
712 *
713 * @param arg Argument (ui_file_list_t *)
714 * @param kbd_event Keyboard event
715 * @return @c ui_claimed iff the event is claimed
716 */
717ui_evclaim_t ui_file_list_ctl_kbd_event(void *arg, kbd_event_t *event)
718{
719 ui_file_list_t *flist = (ui_file_list_t *) arg;
720
721 return ui_control_kbd_event(ui_list_ctl(flist->list), event);
722}
723
724/** Handle file list control position event.
725 *
726 * @param arg Argument (ui_file_list_t *)
727 * @param pos_event Position event
728 * @return @c ui_claimed iff the event is claimed
729 */
730ui_evclaim_t ui_file_list_ctl_pos_event(void *arg, pos_event_t *event)
731{
732 ui_file_list_t *flist = (ui_file_list_t *) arg;
733
734 return ui_control_pos_event(ui_list_ctl(flist->list), event);
735}
736
737/** Activate request callback handler for UI list within file list.
738 *
739 * @param list UI list
740 * @param arg Argument (File list)
741 */
742static void ui_file_list_list_activate_req(ui_list_t *list, void *arg)
743{
744 ui_file_list_t *flist = (ui_file_list_t *)arg;
745
746 ui_file_list_activate_req(flist);
747}
748
749/** Entry selected callback handler for UI list within file list.
750 *
751 * @param entr Activated UI list entry
752 * @param arg Argument (File list entry)
753 */
754static void ui_file_list_list_selected(ui_list_entry_t *entry, void *arg)
755{
756 ui_file_list_entry_t *fentry = (void *)arg;
757
758 (void) ui_file_list_open(fentry->flist, fentry);
759}
760
761/** @}
762 */
Note: See TracBrowser for help on using the repository browser.