all repos — mgba @ e8b7d180bfa016a487fb79329c23545a06e5c4b5

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