src/util/gui/file-select.c (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "file-select.h"
7
8#include "util/gui/font.h"
9#include "util/gui/menu.h"
10#include "util/vfs.h"
11
12#include <stdlib.h>
13
14#define ITERATION_SIZE 5
15#define SCANNING_THRESHOLD 20
16
17static void _cleanFiles(struct GUIMenuItemList* currentFiles) {
18 size_t size = GUIMenuItemListSize(currentFiles);
19 size_t i;
20 for (i = 1; i < size; ++i) {
21 free(GUIMenuItemListGetPointer(currentFiles, i)->title);
22 }
23 GUIMenuItemListClear(currentFiles);
24}
25
26static void _upDirectory(char* currentPath) {
27 char* end = strrchr(currentPath, '/');
28 if (!end) {
29 return;
30 }
31 if (end == currentPath) {
32 end[1] = '\0';
33 return;
34 }
35 end[0] = '\0';
36 if (end[1]) {
37 return;
38 }
39 // TODO: What if there was a trailing slash?
40}
41
42static int _strpcmp(const void* a, const void* b) {
43 return strcmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title);
44}
45
46static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filter)(struct VFile*)) {
47 _cleanFiles(currentFiles);
48
49 struct VDir* dir = VDirOpen(currentPath);
50 if (!dir) {
51 return false;
52 }
53 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = "(Up)" };
54 size_t i = 0;
55 struct VDirEntry* de;
56 while ((de = dir->listNext(dir))) {
57 ++i;
58 if (!(i % SCANNING_THRESHOLD)) {
59 int input = 0;
60 GUIPollInput(params, &input, 0);
61 if (input & (1 << GUI_INPUT_CANCEL)) {
62 return false;
63 }
64 params->drawStart();
65 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
66 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %lu)", i);
67 params->drawEnd();
68 }
69 const char* name = de->name(de);
70 if (name[0] == '.') {
71 continue;
72 }
73 if (de->type(de) == VFS_FILE) {
74 struct VFile* vf = dir->openFile(dir, name, O_RDONLY);
75 if (!vf) {
76 continue;
77 }
78 if (!filter || filter(vf)) {
79 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
80 }
81 vf->close(vf);
82 } else {
83 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
84 }
85 }
86 dir->close(dir);
87 qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp);
88
89 return true;
90}
91
92bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filter)(struct VFile*)) {
93 struct GUIMenu menu = {
94 .title = params->currentPath,
95 .index = params->fileIndex,
96 };
97 GUIMenuItemListInit(&menu.items, 0);
98 _refreshDirectory(params, params->currentPath, &menu.items, filter);
99
100 while (true) {
101 struct GUIMenuItem item;
102 enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item);
103 params->fileIndex = menu.index;
104 if (reason == GUI_MENU_EXIT_CANCEL) {
105 break;
106 }
107 if (reason == GUI_MENU_EXIT_ACCEPT) {
108 if (params->fileIndex == 0) {
109 _upDirectory(params->currentPath);
110 if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
111 break;
112 }
113 } else {
114 size_t len = strlen(params->currentPath);
115 const char* sep = PATH_SEP;
116 if (params->currentPath[len - 1] == *sep) {
117 sep = "";
118 }
119 snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item.title);
120
121 struct GUIMenuItemList newFiles;
122 GUIMenuItemListInit(&newFiles, 0);
123 if (!_refreshDirectory(params, outPath, &newFiles, filter)) {
124 _cleanFiles(&newFiles);
125 GUIMenuItemListDeinit(&newFiles);
126 struct VFile* vf = VFileOpen(outPath, O_RDONLY);
127 if (!vf) {
128 continue;
129 }
130 if (!filter || filter(vf)) {
131 vf->close(vf);
132 _cleanFiles(&menu.items);
133 GUIMenuItemListDeinit(&menu.items);
134 return true;
135 }
136 vf->close(vf);
137 break;
138 } else {
139 _cleanFiles(&menu.items);
140 GUIMenuItemListDeinit(&menu.items);
141 menu.items = newFiles;
142 strncpy(params->currentPath, outPath, PATH_MAX);
143 }
144 }
145 params->fileIndex = 0;
146 }
147 if (reason == GUI_MENU_EXIT_BACK) {
148 if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
149 break;
150 }
151 _upDirectory(params->currentPath);
152 if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
153 break;
154 }
155 params->fileIndex = 0;
156 }
157 }
158
159 _cleanFiles(&menu.items);
160 GUIMenuItemListDeinit(&menu.items);
161 return false;
162}