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 if (filterName && !filterName(name)) {
93 continue;
94 } else {
95 name = strdup(name);
96 }
97 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = name, .data = (void*) de->type(de) };
98 ++items;
99 }
100 qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp);
101 if (preselect || filterContents) {
102 i = 0;
103 size_t item = 0;
104 while (item < GUIMenuItemListSize(currentFiles)) {
105 ++i;
106 // If we're not filtering the contents, this loop is fast, so there's no need to show updates
107 if (filterContents && !(i % SCANNING_THRESHOLD_2)) {
108 uint32_t input = 0;
109 GUIPollInput(params, &input, 0);
110 if (input & (1 << GUI_INPUT_CANCEL)) {
111 dir->close(dir);
112 return false;
113 }
114
115 params->drawStart();
116 if (params->guiPrepare) {
117 params->guiPrepare();
118 }
119 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_ALIGN_LEFT, 0xFFFFFFFF, "(scanning item %"PRIz"u of %"PRIz"u)", i, items);
120 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_ALIGN_LEFT, 0xFFFFFFFF, "%s", currentPath);
121 if (params->guiFinish) {
122 params->guiFinish();
123 }
124 params->drawEnd();
125 }
126 struct GUIMenuItem* testItem = GUIMenuItemListGetPointer(currentFiles, item);
127 if (testItem->data != (void*) VFS_FILE) {
128 ++item;
129 continue;
130 }
131 bool failed = false;
132 if (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 }
155 dir->close(dir);
156
157 return true;
158}
159
160bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) {
161 struct GUIMenu menu = {
162 .title = "Select file",
163 .subtitle = params->currentPath,
164 };
165 GUIMenuItemListInit(&menu.items, 0);
166 _refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, preselect);
167 menu.index = params->fileIndex;
168
169 while (true) {
170 struct GUIMenuItem* item;
171 enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item);
172 params->fileIndex = menu.index;
173 if (reason == GUI_MENU_EXIT_CANCEL) {
174 break;
175 }
176 if (reason == GUI_MENU_EXIT_ACCEPT) {
177 if (params->fileIndex == 0) {
178 if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
179 continue;
180 }
181 _upDirectory(params->currentPath);
182 if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
183 break;
184 }
185 } else {
186 size_t len = strlen(params->currentPath);
187 const char* sep = PATH_SEP;
188 if (!len || params->currentPath[len - 1] == *sep) {
189 sep = "";
190 }
191 snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item->title);
192
193 struct GUIMenuItemList newFiles;
194 GUIMenuItemListInit(&newFiles, 0);
195 if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents, NULL)) {
196 _cleanFiles(&newFiles);
197 GUIMenuItemListDeinit(&newFiles);
198 _cleanFiles(&menu.items);
199 GUIMenuItemListDeinit(&menu.items);
200 return true;
201 } else {
202 _cleanFiles(&menu.items);
203 GUIMenuItemListDeinit(&menu.items);
204 menu.items = newFiles;
205 strlcpy(params->currentPath, outPath, PATH_MAX);
206 }
207 }
208 params->fileIndex = 0;
209 menu.index = 0;
210 }
211 if (reason == GUI_MENU_EXIT_BACK) {
212 if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
213 break;
214 }
215 _upDirectory(params->currentPath);
216 if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
217 break;
218 }
219 params->fileIndex = 0;
220 menu.index = 0;
221 }
222 }
223
224 _cleanFiles(&menu.items);
225 GUIMenuItemListDeinit(&menu.items);
226 return false;
227}