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(¤tFiles, 0);
104 _refreshDirectory(params, params->currentPath, ¤tFiles, 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(¤tFiles) - 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(¤tFiles)) {
125 params->fileIndex += pageSize;
126 } else {
127 params->fileIndex = FileListSize(¤tFiles) - 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, ¤tFiles, 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(¤tFiles, 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(¤tFiles);
166 FileListDeinit(¤tFiles);
167 return true;
168 }
169 vf->close(vf);
170 break;
171 } else {
172 _cleanFiles(¤tFiles);
173 FileListDeinit(¤tFiles);
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, ¤tFiles, 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(¤tFiles); ++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(¤tFiles, 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(¤tFiles);
215 FileListDeinit(¤tFiles);
216 return false;
217}