all repos — mgba @ e89837909b3bfc8f1bd4d6740f5f25603611e654

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