all repos — mgba @ d044c05f30cd6c7591c5ec03b310253fdc06d7d1

mGBA Game Boy Advance Emulator

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*), const char* preselect) {
 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			if (preselect && strncmp(testItem->title, preselect, PATH_MAX) == 0) {
148				params->fileIndex = item;
149			}
150			++item;
151		}
152	}
153	dir->close(dir);
154
155	return true;
156}
157
158bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) {
159	struct GUIMenu menu = {
160		.title = "Select file",
161		.subtitle = params->currentPath,
162	};
163	GUIMenuItemListInit(&menu.items, 0);
164	_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, preselect);
165	menu.index = params->fileIndex;
166
167	while (true) {
168		struct GUIMenuItem* item;
169		enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item);
170		params->fileIndex = menu.index;
171		if (reason == GUI_MENU_EXIT_CANCEL) {
172			break;
173		}
174		if (reason == GUI_MENU_EXIT_ACCEPT) {
175			if (params->fileIndex == 0) {
176				if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
177					continue;
178				}
179				_upDirectory(params->currentPath);
180				if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
181					break;
182				}
183			} else {
184				size_t len = strlen(params->currentPath);
185				const char* sep = PATH_SEP;
186				if (!len || params->currentPath[len - 1] == *sep) {
187					sep = "";
188				}
189				snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item->title);
190
191				struct GUIMenuItemList newFiles;
192				GUIMenuItemListInit(&newFiles, 0);
193				if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents, NULL)) {
194					_cleanFiles(&newFiles);
195					GUIMenuItemListDeinit(&newFiles);
196					_cleanFiles(&menu.items);
197					GUIMenuItemListDeinit(&menu.items);
198					return true;
199				} else {
200					_cleanFiles(&menu.items);
201					GUIMenuItemListDeinit(&menu.items);
202					menu.items = newFiles;
203					strncpy(params->currentPath, outPath, PATH_MAX);
204				}
205			}
206			params->fileIndex = 0;
207			menu.index = 0;
208		}
209		if (reason == GUI_MENU_EXIT_BACK) {
210			if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
211				break;
212			}
213			_upDirectory(params->currentPath);
214			if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
215				break;
216			}
217			params->fileIndex = 0;
218			menu.index = 0;
219		}
220	}
221
222	_cleanFiles(&menu.items);
223	GUIMenuItemListDeinit(&menu.items);
224	return false;
225}