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(¤tFiles, 0);
108 _refreshDirectory(params, currentPath, ¤tFiles, 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(¤tFiles) - 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(¤tFiles)) {
141 fileIndex += pageSize;
142 } else {
143 fileIndex = FileListSize(¤tFiles) - 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, ¤tFiles, 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(¤tFiles, fileIndex));
169 if (!_refreshDirectory(params, outPath, ¤tFiles, 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, ¤tFiles, 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, ¤tFiles, 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(¤tFiles); ++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(¤tFiles, 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(¤tFiles);
223 FileListDeinit(¤tFiles);
224 return false;
225}