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(¤tFiles, 0);
104 _refreshDirectory(params, currentPath, ¤tFiles, 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(¤tFiles) - 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(¤tFiles)) {
137 fileIndex += pageSize;
138 } else {
139 fileIndex = FileListSize(¤tFiles) - 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(¤tFiles);
151 FileListDeinit(¤tFiles);
152 return false;
153 }
154 if (newInput & (1 << GUI_INPUT_SELECT)) {
155 if (fileIndex == 0) {
156 _upDirectory(currentPath);
157 _refreshDirectory(params, currentPath, ¤tFiles, 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(¤tFiles, fileIndex));
165 if (!_refreshDirectory(params, outPath, ¤tFiles, 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(¤tFiles);
175 FileListDeinit(¤tFiles);
176 return false;
177 }
178 _upDirectory(currentPath);
179 _refreshDirectory(params, currentPath, ¤tFiles, 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(¤tFiles); ++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(¤tFiles, 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}