all repos — mgba @ 20b0c0d2fb7429190d94743379f009b7e5b4f5cb

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 20
 16
 17static void _cleanFiles(struct GUIMenuItemList* currentFiles) {
 18	size_t size = GUIMenuItemListSize(currentFiles);
 19	size_t i;
 20	for (i = 1; i < size; ++i) {
 21		free(GUIMenuItemListGetPointer(currentFiles, i)->title);
 22	}
 23	GUIMenuItemListClear(currentFiles);
 24}
 25
 26static void _upDirectory(char* currentPath) {
 27	char* end = strrchr(currentPath, '/');
 28	if (!end) {
 29		return;
 30	}
 31	if (end == currentPath) {
 32		end[1] = '\0';
 33		return;
 34	}
 35	end[0] = '\0';
 36	if (end[1]) {
 37		return;
 38	}
 39	// TODO: What if there was a trailing slash?
 40}
 41
 42static int _strpcmp(const void* a, const void* b) {
 43	return strcmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title);
 44}
 45
 46static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filter)(struct VFile*)) {
 47	_cleanFiles(currentFiles);
 48
 49	struct VDir* dir = VDirOpen(currentPath);
 50	if (!dir) {
 51		return false;
 52	}
 53	*GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = "(Up)" };
 54	size_t i = 0;
 55	struct VDirEntry* de;
 56	while ((de = dir->listNext(dir))) {
 57		++i;
 58		if (!(i % SCANNING_THRESHOLD)) {
 59			int input = 0;
 60			GUIPollInput(params, &input, 0);
 61			if (input & (1 << GUI_INPUT_CANCEL)) {
 62				return false;
 63			}
 64			params->drawStart();
 65			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
 66			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %lu)", i);
 67			params->drawEnd();
 68		}
 69		const char* name = de->name(de);
 70		if (name[0] == '.') {
 71			continue;
 72		}
 73		if (de->type(de) == VFS_FILE) {
 74			struct VFile* vf = dir->openFile(dir, name, O_RDONLY);
 75			if (!vf) {
 76				continue;
 77			}
 78			if (!filter || filter(vf)) {
 79				*GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
 80			}
 81			vf->close(vf);
 82		} else {
 83			*GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
 84		}
 85	}
 86	dir->close(dir);
 87	qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp);
 88
 89	return true;
 90}
 91
 92bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filter)(struct VFile*)) {
 93	struct GUIMenu menu = {
 94		.title = params->currentPath,
 95		.index = params->fileIndex,
 96	};
 97	GUIMenuItemListInit(&menu.items, 0);
 98	_refreshDirectory(params, params->currentPath, &menu.items, filter);
 99
100	while (true) {
101		struct GUIMenuItem item;
102		enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item);
103		params->fileIndex = menu.index;
104		if (reason == GUI_MENU_EXIT_CANCEL) {
105			break;
106		}
107		if (reason == GUI_MENU_EXIT_ACCEPT) {
108			if (params->fileIndex == 0) {
109				_upDirectory(params->currentPath);
110				if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
111					break;
112				}
113			} else {
114				size_t len = strlen(params->currentPath);
115				const char* sep = PATH_SEP;
116				if (params->currentPath[len - 1] == *sep) {
117					sep = "";
118				}
119				snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item.title);
120
121				struct GUIMenuItemList newFiles;
122				GUIMenuItemListInit(&newFiles, 0);
123				if (!_refreshDirectory(params, outPath, &newFiles, filter)) {
124					_cleanFiles(&newFiles);
125					GUIMenuItemListDeinit(&newFiles);
126					struct VFile* vf = VFileOpen(outPath, O_RDONLY);
127					if (!vf) {
128						continue;
129					}
130					if (!filter || filter(vf)) {
131						vf->close(vf);
132						_cleanFiles(&menu.items);
133						GUIMenuItemListDeinit(&menu.items);
134						return true;
135					}
136					vf->close(vf);
137					break;
138				} else {
139					_cleanFiles(&menu.items);
140					GUIMenuItemListDeinit(&menu.items);
141					menu.items = newFiles;
142					strncpy(params->currentPath, outPath, PATH_MAX);
143				}
144			}
145			params->fileIndex = 0;
146		}
147		if (reason == GUI_MENU_EXIT_BACK) {
148			if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
149				break;
150			}
151			_upDirectory(params->currentPath);
152			if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
153				break;
154			}
155			params->fileIndex = 0;
156		}
157	}
158
159	_cleanFiles(&menu.items);
160	GUIMenuItemListDeinit(&menu.items);
161	return false;
162}