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((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 == currentPath) {
39 end[1] = '\0';
40 return;
41 }
42 end[0] = '\0';
43 if (end[1]) {
44 return;
45 }
46 // TODO: What if there was a trailing slash?
47}
48
49static int _strpcmp(const void* a, const void* b) {
50 return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title);
51}
52
53static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filter)(struct VFile*)) {
54 _cleanFiles(currentFiles);
55
56 struct VDir* dir = VDirOpen(currentPath);
57 if (!dir) {
58 return false;
59 }
60 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = "(Up)" };
61 size_t i = 0;
62 size_t items = 0;
63 struct VDirEntry* de;
64 while ((de = dir->listNext(dir))) {
65 ++i;
66 if (!(i % SCANNING_THRESHOLD_1)) {
67 uint32_t input = 0;
68 GUIPollInput(params, &input, 0);
69 if (input & (1 << GUI_INPUT_CANCEL)) {
70 return false;
71 }
72
73 params->drawStart();
74 if (params->guiPrepare) {
75 params->guiPrepare();
76 }
77 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning for items: %zu)", i);
78 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
79 if (params->guiFinish) {
80 params->guiFinish();
81 }
82 params->drawEnd();
83 }
84 const char* name = de->name(de);
85 if (name[0] == '.') {
86 continue;
87 }
88 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
89 ++items;
90 }
91 qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp);
92 i = 0;
93 size_t item = 0;
94 while (item < GUIMenuItemListSize(currentFiles)) {
95 ++i;
96 if (!(i % SCANNING_THRESHOLD_2)) {
97 uint32_t input = 0;
98 GUIPollInput(params, &input, 0);
99 if (input & (1 << GUI_INPUT_CANCEL)) {
100 return false;
101 }
102
103 params->drawStart();
104 if (params->guiPrepare) {
105 params->guiPrepare();
106 }
107 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %zu of %zu)", i, items);
108 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
109 if (params->guiFinish) {
110 params->guiFinish();
111 }
112 params->drawEnd();
113 }
114 if (!filter) {
115 ++item;
116 continue;
117 }
118 struct VDir* vd = dir->openDir(dir, GUIMenuItemListGetPointer(currentFiles, item)->title);
119 if (vd) {
120 vd->close(vd);
121 ++item;
122 continue;
123 }
124 struct VFile* vf = dir->openFile(dir, GUIMenuItemListGetPointer(currentFiles, item)->title, O_RDONLY);
125 if (vf) {
126 if (filter(vf)) {
127 ++item;
128 } else {
129 free((char*) GUIMenuItemListGetPointer(currentFiles, item)->title);
130 GUIMenuItemListShift(currentFiles, item, 1);
131 }
132 vf->close(vf);
133 continue;
134 }
135 ++item;
136 }
137 dir->close(dir);
138
139 return true;
140}
141
142bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filter)(struct VFile*)) {
143 struct GUIMenu menu = {
144 .title = "Select file",
145 .subtitle = params->currentPath,
146 .index = params->fileIndex,
147 };
148 GUIMenuItemListInit(&menu.items, 0);
149 _refreshDirectory(params, params->currentPath, &menu.items, filter);
150
151 while (true) {
152 struct GUIMenuItem* item;
153 enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item);
154 params->fileIndex = menu.index;
155 if (reason == GUI_MENU_EXIT_CANCEL) {
156 break;
157 }
158 if (reason == GUI_MENU_EXIT_ACCEPT) {
159 if (params->fileIndex == 0) {
160 _upDirectory(params->currentPath);
161 if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
162 break;
163 }
164 } else {
165 size_t len = strlen(params->currentPath);
166 const char* sep = PATH_SEP;
167 if (!len || params->currentPath[len - 1] == *sep) {
168 sep = "";
169 }
170 snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item->title);
171
172 struct GUIMenuItemList newFiles;
173 GUIMenuItemListInit(&newFiles, 0);
174 if (!_refreshDirectory(params, outPath, &newFiles, filter)) {
175 _cleanFiles(&newFiles);
176 GUIMenuItemListDeinit(&newFiles);
177 _cleanFiles(&menu.items);
178 GUIMenuItemListDeinit(&menu.items);
179 return true;
180 } else {
181 _cleanFiles(&menu.items);
182 GUIMenuItemListDeinit(&menu.items);
183 menu.items = newFiles;
184 strncpy(params->currentPath, outPath, PATH_MAX);
185 }
186 }
187 params->fileIndex = 0;
188 menu.index = 0;
189 }
190 if (reason == GUI_MENU_EXIT_BACK) {
191 if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
192 break;
193 }
194 _upDirectory(params->currentPath);
195 if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
196 break;
197 }
198 params->fileIndex = 0;
199 menu.index = 0;
200 }
201 }
202
203 _cleanFiles(&menu.items);
204 GUIMenuItemListDeinit(&menu.items);
205 return false;
206}