all repos — mgba @ fadd0391d6f2b143a470c97d6ae129346a789239

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 {
 93			name = strdup(name);
 94		}
 95		*GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = name, .data = (void*) de->type(de) };
 96		++items;
 97	}
 98	qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp);
 99	i = 0;
100	size_t item = 0;
101	while (item < GUIMenuItemListSize(currentFiles)) {
102		++i;
103		if (!(i % SCANNING_THRESHOLD_2)) {
104			uint32_t input = 0;
105			GUIPollInput(params, &input, 0);
106			if (input & (1 << GUI_INPUT_CANCEL)) {
107				dir->close(dir);
108				return false;
109			}
110
111			params->drawStart();
112			if (params->guiPrepare) {
113				params->guiPrepare();
114			}
115			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_ALIGN_LEFT, 0xFFFFFFFF, "(scanning item %"PRIz"u of %"PRIz"u)", i, items);
116			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_ALIGN_LEFT, 0xFFFFFFFF, "%s", currentPath);
117			if (params->guiFinish) {
118				params->guiFinish();
119			}
120			params->drawEnd();
121		}
122		struct GUIMenuItem* testItem = GUIMenuItemListGetPointer(currentFiles, item);
123		if (testItem->data != (void*) VFS_FILE) {
124			++item;
125			continue;
126		}
127		bool failed = false;
128		if (filterName && !filterName(testItem->title)) {
129			failed = true;
130		}
131
132		if (!failed && 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	dir->close(dir);
155
156	return true;
157}
158
159bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) {
160	struct GUIMenu menu = {
161		.title = "Select file",
162		.subtitle = params->currentPath,
163	};
164	GUIMenuItemListInit(&menu.items, 0);
165	_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, preselect);
166	menu.index = params->fileIndex;
167
168	while (true) {
169		struct GUIMenuItem* item;
170		enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item);
171		params->fileIndex = menu.index;
172		if (reason == GUI_MENU_EXIT_CANCEL) {
173			break;
174		}
175		if (reason == GUI_MENU_EXIT_ACCEPT) {
176			if (params->fileIndex == 0) {
177				if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
178					continue;
179				}
180				_upDirectory(params->currentPath);
181				if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
182					break;
183				}
184			} else {
185				size_t len = strlen(params->currentPath);
186				const char* sep = PATH_SEP;
187				if (!len || params->currentPath[len - 1] == *sep) {
188					sep = "";
189				}
190				snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item->title);
191
192				struct GUIMenuItemList newFiles;
193				GUIMenuItemListInit(&newFiles, 0);
194				if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents, NULL)) {
195					_cleanFiles(&newFiles);
196					GUIMenuItemListDeinit(&newFiles);
197					_cleanFiles(&menu.items);
198					GUIMenuItemListDeinit(&menu.items);
199					return true;
200				} else {
201					_cleanFiles(&menu.items);
202					GUIMenuItemListDeinit(&menu.items);
203					menu.items = newFiles;
204					strlcpy(params->currentPath, outPath, PATH_MAX);
205				}
206			}
207			params->fileIndex = 0;
208			menu.index = 0;
209		}
210		if (reason == GUI_MENU_EXIT_BACK) {
211			if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
212				break;
213			}
214			_upDirectory(params->currentPath);
215			if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
216				break;
217			}
218			params->fileIndex = 0;
219			menu.index = 0;
220		}
221	}
222
223	_cleanFiles(&menu.items);
224	GUIMenuItemListDeinit(&menu.items);
225	return false;
226}