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