all repos — mgba @ 15037950f0346f49e06d4a2643c16a69388d1611

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/vector.h"
 10#include "util/vfs.h"
 11
 12#include <stdlib.h>
 13
 14DECLARE_VECTOR(FileList, char*);
 15DEFINE_VECTOR(FileList, char*);
 16
 17#define ITERATION_SIZE 5
 18#define SCANNING_THRESHOLD 20
 19
 20static void _cleanFiles(struct FileList* currentFiles) {
 21	size_t size = FileListSize(currentFiles);
 22	size_t i;
 23	for (i = 1; i < size; ++i) {
 24		free(*FileListGetPointer(currentFiles, i));
 25	}
 26	FileListClear(currentFiles);
 27}
 28
 29static void _upDirectory(char* currentPath) {
 30	char* end = strrchr(currentPath, '/');
 31	if (!end) {
 32		return;
 33	}
 34	if (end == currentPath) {
 35		end[1] = '\0';
 36		return;
 37	}
 38	end[0] = '\0';
 39	if (end[1]) {
 40		return;
 41	}
 42	// TODO: What if there was a trailing slash?
 43}
 44
 45static int _strpcmp(const void* a, const void* b) {
 46	return strcmp(*(const char**) a, *(const char**) b);
 47}
 48
 49static bool _refreshDirectory(const struct GUIParams* params, const char* currentPath, struct FileList* currentFiles, bool (*filter)(struct VFile*)) {
 50	_cleanFiles(currentFiles);
 51
 52	struct VDir* dir = VDirOpen(currentPath);
 53	if (!dir) {
 54		return false;
 55	}
 56	*FileListAppend(currentFiles) = "(Up)";
 57	size_t i = 0;
 58	struct VDirEntry* de;
 59	while ((de = dir->listNext(dir))) {
 60		++i;
 61		if (i == SCANNING_THRESHOLD) {
 62			params->drawStart();
 63			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
 64			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning)");
 65			params->drawEnd();
 66		}
 67		const char* name = de->name(de);
 68		if (name[0] == '.') {
 69			continue;
 70		}
 71		if (de->type(de) == VFS_FILE) {
 72			struct VFile* vf = dir->openFile(dir, name, O_RDONLY);
 73			if (!vf) {
 74				continue;
 75			}
 76			if (!filter || filter(vf)) {
 77				*FileListAppend(currentFiles) = strdup(name);
 78			}
 79			vf->close(vf);
 80		} else {
 81			*FileListAppend(currentFiles) = strdup(name);
 82		}
 83	}
 84	dir->close(dir);
 85	qsort(FileListGetPointer(currentFiles, 1), FileListSize(currentFiles) - 1, sizeof(char*), _strpcmp);
 86	return true;
 87}
 88
 89bool selectFile(const struct GUIParams* params, const char* basePath, char* outPath, char* currentPath, size_t outLen, bool (*filter)(struct VFile*)) {
 90	if (!currentPath[0]) {
 91		strncpy(currentPath, basePath, outLen);
 92	}
 93	size_t fileIndex = 0;
 94	size_t start = 0;
 95	size_t pageSize = params->height / GUIFontHeight(params->font);
 96	if (pageSize > 4) {
 97		pageSize -= 4;
 98	} else {
 99		pageSize = 1;
100	}
101
102	struct FileList currentFiles;
103	FileListInit(&currentFiles, 0);
104	_refreshDirectory(params, currentPath, &currentFiles, filter);
105
106	int inputHistory[GUI_INPUT_MAX] = { 0 };
107
108	while (true) {
109		int input = params->pollInput();
110		int newInput = 0;
111		for (int i = 0; i < GUI_INPUT_MAX; ++i) {
112			if (input & (1 << i)) {
113				++inputHistory[i];
114			} else {
115				inputHistory[i] = -1;
116			}
117			if (!inputHistory[i] || (inputHistory[i] >= 30 && !(inputHistory[i] % 6))) {
118				newInput |= (1 << i);
119			}
120		}
121
122		if (newInput & (1 << GUI_INPUT_UP) && fileIndex > 0) {
123			--fileIndex;
124		}
125		if (newInput & (1 << GUI_INPUT_DOWN) && fileIndex < FileListSize(&currentFiles) - 1) {
126			++fileIndex;
127		}
128		if (newInput & (1 << GUI_INPUT_LEFT)) {
129			if (fileIndex >= pageSize) {
130				fileIndex -= pageSize;
131			} else {
132				fileIndex = 0;
133			}
134		}
135		if (newInput & (1 << GUI_INPUT_RIGHT)) {
136			if (fileIndex + pageSize < FileListSize(&currentFiles)) {
137				fileIndex += pageSize;
138			} else {
139				fileIndex = FileListSize(&currentFiles) - 1;
140			}
141		}
142
143		if (fileIndex < start) {
144			start = fileIndex;
145		}
146		while ((fileIndex - start + 4) * GUIFontHeight(params->font) > params->height) {
147			++start;
148		}
149		if (newInput & (1 << GUI_INPUT_CANCEL)) {
150			_cleanFiles(&currentFiles);
151			FileListDeinit(&currentFiles);
152			return false;
153		}
154		if (newInput & (1 << GUI_INPUT_SELECT)) {
155			if (fileIndex == 0) {
156				_upDirectory(currentPath);
157				_refreshDirectory(params, currentPath, &currentFiles, filter);
158			} else {
159				size_t len = strlen(currentPath);
160				const char* sep = PATH_SEP;
161				if (currentPath[len - 1] == *sep) {
162					sep = "";
163				}
164				snprintf(outPath, outLen, "%s%s%s", currentPath, sep, *FileListGetPointer(&currentFiles, fileIndex));
165				if (!_refreshDirectory(params, outPath, &currentFiles, filter)) {
166					return true;
167				}
168				strncpy(currentPath, outPath, outLen);
169			}
170			fileIndex = 0;
171		}
172		if (newInput & (1 << GUI_INPUT_BACK)) {
173			if (strncmp(currentPath, basePath, outLen) == 0) {
174				_cleanFiles(&currentFiles);
175				FileListDeinit(&currentFiles);
176				return false;
177			}
178			_upDirectory(currentPath);
179			_refreshDirectory(params, currentPath, &currentFiles, filter);
180			fileIndex = 0;
181		}
182
183		params->drawStart();
184		unsigned y = GUIFontHeight(params->font);
185		GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
186		y += 2 * GUIFontHeight(params->font);
187		size_t i;
188		for (i = start; i < FileListSize(&currentFiles); ++i) {
189			int color = 0xE0A0A0A0;
190			char bullet = ' ';
191			if (i == fileIndex) {
192				color = 0xFFFFFFFF;
193				bullet = '>';
194			}
195			GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, color, "%c %s", bullet, *FileListGetPointer(&currentFiles, i));
196			y += GUIFontHeight(params->font);
197			if (y + GUIFontHeight(params->font) > params->height) {
198				break;
199			}
200		}
201		y += GUIFontHeight(params->font) * 2;
202
203		params->drawEnd();
204	}
205}