all repos — mgba @ c14da05d8dca225010677643c32fea5c0ac8517a

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			uint32_t input = 0;
 60			GUIPollInput(params, &input, 0);
 61			if (input & (1 << GUI_INPUT_CANCEL)) {
 62				return false;
 63			}
 64
 65			params->drawStart();
 66			if (params->guiPrepare) {
 67				params->guiPrepare();
 68			}
 69			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
 70			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %lu)", i);
 71			if (params->guiFinish) {
 72				params->guiFinish();
 73			}
 74			params->drawEnd();
 75		}
 76		const char* name = de->name(de);
 77		if (name[0] == '.') {
 78			continue;
 79		}
 80		if (de->type(de) == VFS_FILE) {
 81			struct VFile* vf = dir->openFile(dir, name, O_RDONLY);
 82			if (!vf) {
 83				continue;
 84			}
 85			if (!filter || filter(vf)) {
 86				*GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
 87			}
 88			vf->close(vf);
 89		} else {
 90			*GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
 91		}
 92	}
 93	dir->close(dir);
 94	qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp);
 95
 96	return true;
 97}
 98
 99bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filter)(struct VFile*)) {
100	struct GUIMenu menu = {
101		.title = params->currentPath,
102		.index = params->fileIndex,
103	};
104	GUIMenuItemListInit(&menu.items, 0);
105	_refreshDirectory(params, params->currentPath, &menu.items, filter);
106
107	while (true) {
108		struct GUIMenuItem item;
109		enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item);
110		params->fileIndex = menu.index;
111		if (reason == GUI_MENU_EXIT_CANCEL) {
112			break;
113		}
114		if (reason == GUI_MENU_EXIT_ACCEPT) {
115			if (params->fileIndex == 0) {
116				_upDirectory(params->currentPath);
117				if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
118					break;
119				}
120			} else {
121				size_t len = strlen(params->currentPath);
122				const char* sep = PATH_SEP;
123				if (params->currentPath[len - 1] == *sep) {
124					sep = "";
125				}
126				snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item.title);
127
128				struct GUIMenuItemList newFiles;
129				GUIMenuItemListInit(&newFiles, 0);
130				if (!_refreshDirectory(params, outPath, &newFiles, filter)) {
131					_cleanFiles(&newFiles);
132					GUIMenuItemListDeinit(&newFiles);
133					struct VFile* vf = VFileOpen(outPath, O_RDONLY);
134					if (!vf) {
135						continue;
136					}
137					if (!filter || filter(vf)) {
138						vf->close(vf);
139						_cleanFiles(&menu.items);
140						GUIMenuItemListDeinit(&menu.items);
141						return true;
142					}
143					vf->close(vf);
144					break;
145				} else {
146					_cleanFiles(&menu.items);
147					GUIMenuItemListDeinit(&menu.items);
148					menu.items = newFiles;
149					strncpy(params->currentPath, outPath, PATH_MAX);
150				}
151			}
152			params->fileIndex = 0;
153			menu.index = 0;
154		}
155		if (reason == GUI_MENU_EXIT_BACK) {
156			if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
157				break;
158			}
159			_upDirectory(params->currentPath);
160			if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
161				break;
162			}
163			params->fileIndex = 0;
164			menu.index = 0;
165		}
166	}
167
168	_cleanFiles(&menu.items);
169	GUIMenuItemListDeinit(&menu.items);
170	return false;
171}