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 <mgba-util/gui/file-select.h>
7
8#include <mgba-util/gui/font.h>
9#include <mgba-util/gui/menu.h>
10#include <mgba-util/string.h>
11#include <mgba-util/vfs.h>
12
13#include <stdlib.h>
14
15#define ITERATION_SIZE 5
16#define SCANNING_THRESHOLD_1 50
17#ifdef _3DS
18// 3DS is slooooow at opening files
19#define SCANNING_THRESHOLD_2 10
20#else
21#define SCANNING_THRESHOLD_2 50
22#endif
23
24static void _cleanFiles(struct GUIMenuItemList* currentFiles) {
25 size_t size = GUIMenuItemListSize(currentFiles);
26 size_t i;
27 for (i = 1; i < size; ++i) {
28 free((char*) GUIMenuItemListGetPointer(currentFiles, i)->title);
29 }
30 GUIMenuItemListClear(currentFiles);
31}
32
33static void _upDirectory(char* currentPath) {
34 char* end = strrchr(currentPath, '/');
35 if (!end) {
36 currentPath[0] = '\0';
37 return;
38 }
39 if (!end[1]) {
40 // Trailing slash
41 end[0] = '\0';
42 return _upDirectory(currentPath);
43 }
44 end[1] = '\0';
45}
46
47static int _strpcmp(const void* a, const void* b) {
48 return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title);
49}
50
51static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) {
52 _cleanFiles(currentFiles);
53
54 struct VDir* dir = VDirOpen(currentPath);
55 if (!dir) {
56 return false;
57 }
58 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = "(Up)" };
59 size_t i = 0;
60 size_t items = 0;
61 struct VDirEntry* de;
62 while ((de = dir->listNext(dir))) {
63 ++i;
64 if (!(i % SCANNING_THRESHOLD_1)) {
65 uint32_t input = 0;
66 GUIPollInput(params, &input, 0);
67 if (input & (1 << GUI_INPUT_CANCEL)) {
68 dir->close(dir);
69 return false;
70 }
71
72 params->drawStart();
73 if (params->guiPrepare) {
74 params->guiPrepare();
75 }
76 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_ALIGN_LEFT, 0xFFFFFFFF, "(scanning for items: %"PRIz"u)", i);
77 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_ALIGN_LEFT, 0xFFFFFFFF, "%s", currentPath);
78 if (params->guiFinish) {
79 params->guiFinish();
80 }
81 params->drawEnd();
82 }
83 const char* name = de->name(de);
84 if (name[0] == '.') {
85 continue;
86 }
87 if (de->type(de) == VFS_DIRECTORY) {
88 size_t len = strlen(name) + 2;
89 char* n2 = malloc(len);
90 snprintf(n2, len, "%s/", name);
91 name = n2;
92 } else {
93 name = strdup(name);
94 }
95 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = name, .data = (void*) de->type(de) };
96 ++items;
97 }
98 qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp);
99 i = 0;
100 size_t item = 0;
101 while (item < GUIMenuItemListSize(currentFiles)) {
102 ++i;
103 if (!(i % SCANNING_THRESHOLD_2)) {
104 uint32_t input = 0;
105 GUIPollInput(params, &input, 0);
106 if (input & (1 << GUI_INPUT_CANCEL)) {
107 dir->close(dir);
108 return false;
109 }
110
111 params->drawStart();
112 if (params->guiPrepare) {
113 params->guiPrepare();
114 }
115 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_ALIGN_LEFT, 0xFFFFFFFF, "(scanning item %"PRIz"u of %"PRIz"u)", i, items);
116 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_ALIGN_LEFT, 0xFFFFFFFF, "%s", currentPath);
117 if (params->guiFinish) {
118 params->guiFinish();
119 }
120 params->drawEnd();
121 }
122 struct GUIMenuItem* testItem = GUIMenuItemListGetPointer(currentFiles, item);
123 if (testItem->data != (void*) VFS_FILE) {
124 ++item;
125 continue;
126 }
127 bool failed = false;
128 if (filterName && !filterName(testItem->title)) {
129 failed = true;
130 }
131
132 if (!failed && filterContents) {
133 struct VFile* vf = dir->openFile(dir, testItem->title, O_RDONLY);
134 if (!vf) {
135 failed = true;
136 } else {
137 if (!filterContents(vf)) {
138 failed = true;
139 }
140 vf->close(vf);
141 }
142 }
143
144 if (failed) {
145 free((char*) testItem->title);
146 GUIMenuItemListShift(currentFiles, item, 1);
147 } else {
148 if (preselect && strncmp(testItem->title, preselect, PATH_MAX) == 0) {
149 params->fileIndex = item;
150 }
151 ++item;
152 }
153 }
154 dir->close(dir);
155
156 return true;
157}
158
159bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) {
160 struct GUIMenu menu = {
161 .title = "Select file",
162 .subtitle = params->currentPath,
163 };
164 GUIMenuItemListInit(&menu.items, 0);
165 _refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, preselect);
166 menu.index = params->fileIndex;
167
168 while (true) {
169 struct GUIMenuItem* item;
170 enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item);
171 params->fileIndex = menu.index;
172 if (reason == GUI_MENU_EXIT_CANCEL) {
173 break;
174 }
175 if (reason == GUI_MENU_EXIT_ACCEPT) {
176 if (params->fileIndex == 0) {
177 if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
178 continue;
179 }
180 _upDirectory(params->currentPath);
181 if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
182 break;
183 }
184 } else {
185 size_t len = strlen(params->currentPath);
186 const char* sep = PATH_SEP;
187 if (!len || params->currentPath[len - 1] == *sep) {
188 sep = "";
189 }
190 snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item->title);
191
192 struct GUIMenuItemList newFiles;
193 GUIMenuItemListInit(&newFiles, 0);
194 if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents, NULL)) {
195 _cleanFiles(&newFiles);
196 GUIMenuItemListDeinit(&newFiles);
197 _cleanFiles(&menu.items);
198 GUIMenuItemListDeinit(&menu.items);
199 return true;
200 } else {
201 _cleanFiles(&menu.items);
202 GUIMenuItemListDeinit(&menu.items);
203 menu.items = newFiles;
204 strlcpy(params->currentPath, outPath, PATH_MAX);
205 }
206 }
207 params->fileIndex = 0;
208 menu.index = 0;
209 }
210 if (reason == GUI_MENU_EXIT_BACK) {
211 if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
212 break;
213 }
214 _upDirectory(params->currentPath);
215 if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
216 break;
217 }
218 params->fileIndex = 0;
219 menu.index = 0;
220 }
221 }
222
223 _cleanFiles(&menu.items);
224 GUIMenuItemListDeinit(&menu.items);
225 return false;
226}