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_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(GUIMenuItemListGetPointer(currentFiles, i)->title);
28 }
29 GUIMenuItemListClear(currentFiles);
30}
31
32static void _upDirectory(char* currentPath) {
33 char* end = strrchr(currentPath, '/');
34 if (!end) {
35 return;
36 }
37 if (end == currentPath) {
38 end[1] = '\0';
39 return;
40 }
41 end[0] = '\0';
42 if (end[1]) {
43 return;
44 }
45 // TODO: What if there was a trailing slash?
46}
47
48static int _strpcmp(const void* a, const void* b) {
49 return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title);
50}
51
52static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filter)(struct VFile*)) {
53 _cleanFiles(currentFiles);
54
55 struct VDir* dir = VDirOpen(currentPath);
56 if (!dir) {
57 return false;
58 }
59 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = "(Up)" };
60 size_t i = 0;
61 size_t items = 0;
62 struct VDirEntry* de;
63 while ((de = dir->listNext(dir))) {
64 ++i;
65 if (!(i % SCANNING_THRESHOLD_1)) {
66 uint32_t input = 0;
67 GUIPollInput(params, &input, 0);
68 if (input & (1 << GUI_INPUT_CANCEL)) {
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_TEXT_LEFT, 0xFFFFFFFF, "(scanning for items: %zu)", i);
77 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_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 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
88 ++items;
89 }
90 qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp);
91 i = 0;
92 size_t item = 0;
93 while (item < GUIMenuItemListSize(currentFiles)) {
94 ++i;
95 if (!(i % SCANNING_THRESHOLD_2)) {
96 uint32_t input = 0;
97 GUIPollInput(params, &input, 0);
98 if (input & (1 << GUI_INPUT_CANCEL)) {
99 return false;
100 }
101
102 params->drawStart();
103 if (params->guiPrepare) {
104 params->guiPrepare();
105 }
106 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %zu of %zu)", i, items);
107 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
108 if (params->guiFinish) {
109 params->guiFinish();
110 }
111 params->drawEnd();
112 }
113 if (!filter) {
114 ++item;
115 continue;
116 }
117 struct VDir* vd = dir->openDir(dir, GUIMenuItemListGetPointer(currentFiles, item)->title);
118 if (vd) {
119 bool success = false;
120 struct VDirEntry* de;
121 while ((de = vd->listNext(vd)) && !success) {
122 struct VFile* vf2 = vd->openFile(vd, de->name(de), O_RDONLY);
123 if (!vf2) {
124 continue;
125 }
126 if (filter(vf2)) {
127 success = true;
128 }
129 vf2->close(vf2);
130 }
131 vd->close(vd);
132 if (success) {
133 ++item;
134 continue;
135 }
136 }
137 struct VFile* vf = dir->openFile(dir, GUIMenuItemListGetPointer(currentFiles, item)->title, O_RDONLY);
138 if (vf) {
139 if (filter(vf)) {
140 ++item;
141 } else {
142 free(GUIMenuItemListGetPointer(currentFiles, item)->title);
143 GUIMenuItemListShift(currentFiles, item, 1);
144 }
145 vf->close(vf);
146 continue;
147 }
148 ++item;
149 }
150 dir->close(dir);
151
152 return true;
153}
154
155bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filter)(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, filter);
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 _upDirectory(params->currentPath);
174 if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
175 break;
176 }
177 } else {
178 size_t len = strlen(params->currentPath);
179 const char* sep = PATH_SEP;
180 if (params->currentPath[len - 1] == *sep) {
181 sep = "";
182 }
183 snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item->title);
184
185 struct GUIMenuItemList newFiles;
186 GUIMenuItemListInit(&newFiles, 0);
187 if (!_refreshDirectory(params, outPath, &newFiles, filter)) {
188 _cleanFiles(&newFiles);
189 GUIMenuItemListDeinit(&newFiles);
190 _cleanFiles(&menu.items);
191 GUIMenuItemListDeinit(&menu.items);
192 return true;
193 } else {
194 _cleanFiles(&menu.items);
195 GUIMenuItemListDeinit(&menu.items);
196 menu.items = newFiles;
197 strncpy(params->currentPath, outPath, PATH_MAX);
198 }
199 }
200 params->fileIndex = 0;
201 menu.index = 0;
202 }
203 if (reason == GUI_MENU_EXIT_BACK) {
204 if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
205 break;
206 }
207 _upDirectory(params->currentPath);
208 if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
209 break;
210 }
211 params->fileIndex = 0;
212 menu.index = 0;
213 }
214 }
215
216 _cleanFiles(&menu.items);
217 GUIMenuItemListDeinit(&menu.items);
218 return false;
219}