all repos — mgba @ 1a50718c26c6336b77cb119d0b1a4b399a6434e5

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(GUIMenuItemListGetPointer(currentFiles, i)->title);
 28	}
 29	GUIMenuItemListClear(currentFiles);
 30}
 31
 32static void _upDirectory(char* currentPath) {
 33	char* end = strrchr(currentPath, '/');
 34	if (!end) {
 35		return;
 36	}
 37	if (end == currentPath) {
 38		end[1] = '\0';
 39		return;
 40	}
 41	end[0] = '\0';
 42	if (end[1]) {
 43		return;
 44	}
 45	// TODO: What if there was a trailing slash?
 46}
 47
 48static int _strpcmp(const void* a, const void* b) {
 49	return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title);
 50}
 51
 52static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filter)(struct VFile*)) {
 53	_cleanFiles(currentFiles);
 54
 55	struct VDir* dir = VDirOpen(currentPath);
 56	if (!dir) {
 57		return false;
 58	}
 59	*GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = "(Up)" };
 60	size_t i = 0;
 61	size_t items = 0;
 62	struct VDirEntry* de;
 63	while ((de = dir->listNext(dir))) {
 64		++i;
 65		if (!(i % SCANNING_THRESHOLD_1)) {
 66			uint32_t input = 0;
 67			GUIPollInput(params, &input, 0);
 68			if (input & (1 << GUI_INPUT_CANCEL)) {
 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_TEXT_LEFT, 0xFFFFFFFF, "(scanning for items: %zu)", i);
 77			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_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		*GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
 88		++items;
 89	}
 90	qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp);
 91	i = 0;
 92	size_t item = 0;
 93	while (item < GUIMenuItemListSize(currentFiles)) {
 94		++i;
 95		if (!(i % SCANNING_THRESHOLD_2)) {
 96			uint32_t input = 0;
 97			GUIPollInput(params, &input, 0);
 98			if (input & (1 << GUI_INPUT_CANCEL)) {
 99				return false;
100			}
101
102			params->drawStart();
103			if (params->guiPrepare) {
104				params->guiPrepare();
105			}
106			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %zu of %zu)", i, items);
107			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
108			if (params->guiFinish) {
109				params->guiFinish();
110			}
111			params->drawEnd();
112		}
113		if (!filter) {
114			++item;
115			continue;
116		}
117		struct VDir* vd = dir->openDir(dir, GUIMenuItemListGetPointer(currentFiles, item)->title);
118		if (vd) {
119			bool success = false;
120			struct VDirEntry* de;
121			while ((de = vd->listNext(vd)) && !success) {
122				struct VFile* vf2 = vd->openFile(vd, de->name(de), O_RDONLY);
123				if (!vf2) {
124					continue;
125				}
126				if (filter(vf2)) {
127					success = true;
128				}
129				vf2->close(vf2);
130			}
131			vd->close(vd);
132			if (success) {
133				++item;
134				continue;
135			}
136		}
137		struct VFile* vf = dir->openFile(dir, GUIMenuItemListGetPointer(currentFiles, item)->title, O_RDONLY);
138		if (vf) {
139			if (filter(vf)) {
140				++item;
141			} else {
142				free(GUIMenuItemListGetPointer(currentFiles, item)->title);
143				GUIMenuItemListShift(currentFiles, item, 1);
144			}
145			vf->close(vf);
146			continue;
147		}
148		++item;
149	}
150	dir->close(dir);
151
152	return true;
153}
154
155bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filter)(struct VFile*)) {
156	struct GUIMenu menu = {
157		.title = "Select file",
158		.subtitle = params->currentPath,
159		.index = params->fileIndex,
160	};
161	GUIMenuItemListInit(&menu.items, 0);
162	_refreshDirectory(params, params->currentPath, &menu.items, filter);
163
164	while (true) {
165		struct GUIMenuItem* item;
166		enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item);
167		params->fileIndex = menu.index;
168		if (reason == GUI_MENU_EXIT_CANCEL) {
169			break;
170		}
171		if (reason == GUI_MENU_EXIT_ACCEPT) {
172			if (params->fileIndex == 0) {
173				_upDirectory(params->currentPath);
174				if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
175					break;
176				}
177			} else {
178				size_t len = strlen(params->currentPath);
179				const char* sep = PATH_SEP;
180				if (params->currentPath[len - 1] == *sep) {
181					sep = "";
182				}
183				snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item->title);
184
185				struct GUIMenuItemList newFiles;
186				GUIMenuItemListInit(&newFiles, 0);
187				if (!_refreshDirectory(params, outPath, &newFiles, filter)) {
188					_cleanFiles(&newFiles);
189					GUIMenuItemListDeinit(&newFiles);
190					_cleanFiles(&menu.items);
191					GUIMenuItemListDeinit(&menu.items);
192					return true;
193				} else {
194					_cleanFiles(&menu.items);
195					GUIMenuItemListDeinit(&menu.items);
196					menu.items = newFiles;
197					strncpy(params->currentPath, outPath, PATH_MAX);
198				}
199			}
200			params->fileIndex = 0;
201			menu.index = 0;
202		}
203		if (reason == GUI_MENU_EXIT_BACK) {
204			if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
205				break;
206			}
207			_upDirectory(params->currentPath);
208			if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
209				break;
210			}
211			params->fileIndex = 0;
212			menu.index = 0;
213		}
214	}
215
216	_cleanFiles(&menu.items);
217	GUIMenuItemListDeinit(&menu.items);
218	return false;
219}