all repos — mgba @ 0ab9190b1051f8f6c8b3449ca9a0ca066851a7a3

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