all repos — mgba @ bda43168394b3edf785ef916186d131f871e4029

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/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}