all repos — mgba @ dd3b56eb7a3d6fc30f59267bb3b444b611380f8b

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 15
 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 == SCANNING_THRESHOLD - 1) {
 62			int input = params->pollInput();
 63			if (input & (1 << GUI_INPUT_CANCEL)) {
 64				return false;
 65			}
 66			params->drawStart();
 67			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
 68			GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning)");
 69			params->drawEnd();
 70		}
 71		const char* name = de->name(de);
 72		if (name[0] == '.') {
 73			continue;
 74		}
 75		if (de->type(de) == VFS_FILE) {
 76			struct VFile* vf = dir->openFile(dir, name, O_RDONLY);
 77			if (!vf) {
 78				continue;
 79			}
 80			if (!filter || filter(vf)) {
 81				*FileListAppend(currentFiles) = strdup(name);
 82			}
 83			vf->close(vf);
 84		} else {
 85			*FileListAppend(currentFiles) = strdup(name);
 86		}
 87	}
 88	dir->close(dir);
 89	qsort(FileListGetPointer(currentFiles, 1), FileListSize(currentFiles) - 1, sizeof(char*), _strpcmp);
 90	return true;
 91}
 92
 93bool selectFile(const struct GUIParams* params, const char* basePath, char* outPath, char* currentPath, size_t outLen, bool (*filter)(struct VFile*)) {
 94	if (!currentPath[0]) {
 95		strncpy(currentPath, basePath, outLen);
 96	}
 97	size_t fileIndex = 0;
 98	size_t start = 0;
 99	size_t pageSize = params->height / GUIFontHeight(params->font);
100	if (pageSize > 4) {
101		pageSize -= 4;
102	} else {
103		pageSize = 1;
104	}
105
106	struct FileList currentFiles;
107	FileListInit(&currentFiles, 0);
108	_refreshDirectory(params, currentPath, &currentFiles, filter);
109
110	int inputHistory[GUI_INPUT_MAX] = { 0 };
111
112	while (true) {
113		int input = params->pollInput();
114		int newInput = 0;
115		for (int i = 0; i < GUI_INPUT_MAX; ++i) {
116			if (input & (1 << i)) {
117				++inputHistory[i];
118			} else {
119				inputHistory[i] = -1;
120			}
121			if (!inputHistory[i] || (inputHistory[i] >= 30 && !(inputHistory[i] % 6))) {
122				newInput |= (1 << i);
123			}
124		}
125
126		if (newInput & (1 << GUI_INPUT_UP) && fileIndex > 0) {
127			--fileIndex;
128		}
129		if (newInput & (1 << GUI_INPUT_DOWN) && fileIndex < FileListSize(&currentFiles) - 1) {
130			++fileIndex;
131		}
132		if (newInput & (1 << GUI_INPUT_LEFT)) {
133			if (fileIndex >= pageSize) {
134				fileIndex -= pageSize;
135			} else {
136				fileIndex = 0;
137			}
138		}
139		if (newInput & (1 << GUI_INPUT_RIGHT)) {
140			if (fileIndex + pageSize < FileListSize(&currentFiles)) {
141				fileIndex += pageSize;
142			} else {
143				fileIndex = FileListSize(&currentFiles) - 1;
144			}
145		}
146
147		if (fileIndex < start) {
148			start = fileIndex;
149		}
150		while ((fileIndex - start + 4) * GUIFontHeight(params->font) > params->height) {
151			++start;
152		}
153		if (newInput & (1 << GUI_INPUT_CANCEL)) {
154			break;
155		}
156		if (newInput & (1 << GUI_INPUT_SELECT)) {
157			if (fileIndex == 0) {
158				_upDirectory(currentPath);
159				if (!_refreshDirectory(params, currentPath, &currentFiles, filter)) {
160					break;
161				}
162			} else {
163				size_t len = strlen(currentPath);
164				const char* sep = PATH_SEP;
165				if (currentPath[len - 1] == *sep) {
166					sep = "";
167				}
168				snprintf(outPath, outLen, "%s%s%s", currentPath, sep, *FileListGetPointer(&currentFiles, fileIndex));
169				if (!_refreshDirectory(params, outPath, &currentFiles, filter)) {
170					struct VFile* vf = VFileOpen(currentPath, O_RDONLY);
171					if (!vf) {
172						break;
173					}
174					if (!filter || filter(vf)) {
175						vf->close(vf);
176						return true;
177					}
178					vf->close(vf);
179					_upDirectory(currentPath);
180					if (!_refreshDirectory(params, currentPath, &currentFiles, filter)) {
181						break;
182					}
183				}
184				strncpy(currentPath, outPath, outLen);
185			}
186			fileIndex = 0;
187		}
188		if (newInput & (1 << GUI_INPUT_BACK)) {
189			if (strncmp(currentPath, basePath, outLen) == 0) {
190				break;
191			}
192			_upDirectory(currentPath);
193			if (!_refreshDirectory(params, currentPath, &currentFiles, filter)) {
194				break;
195			}
196			fileIndex = 0;
197		}
198
199		params->drawStart();
200		unsigned y = GUIFontHeight(params->font);
201		GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
202		y += 2 * GUIFontHeight(params->font);
203		size_t i;
204		for (i = start; i < FileListSize(&currentFiles); ++i) {
205			int color = 0xE0A0A0A0;
206			char bullet = ' ';
207			if (i == fileIndex) {
208				color = 0xFFFFFFFF;
209				bullet = '>';
210			}
211			GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, color, "%c %s", bullet, *FileListGetPointer(&currentFiles, i));
212			y += GUIFontHeight(params->font);
213			if (y + GUIFontHeight(params->font) > params->height) {
214				break;
215			}
216		}
217		y += GUIFontHeight(params->font) * 2;
218
219		params->drawEnd();
220	}
221
222	_cleanFiles(&currentFiles);
223	FileListDeinit(&currentFiles);
224	return false;
225}