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/gui/menu.h"
10#include "util/vfs.h"
11
12#include <stdlib.h>
13
14#define ITERATION_SIZE 5
15#define SCANNING_THRESHOLD 20
16
17static void _cleanFiles(struct GUIMenuItemList* currentFiles) {
18 size_t size = GUIMenuItemListSize(currentFiles);
19 size_t i;
20 for (i = 1; i < size; ++i) {
21 free(GUIMenuItemListGetPointer(currentFiles, i)->title);
22 }
23 GUIMenuItemListClear(currentFiles);
24}
25
26static void _upDirectory(char* currentPath) {
27 char* end = strrchr(currentPath, '/');
28 if (!end) {
29 return;
30 }
31 if (end == currentPath) {
32 end[1] = '\0';
33 return;
34 }
35 end[0] = '\0';
36 if (end[1]) {
37 return;
38 }
39 // TODO: What if there was a trailing slash?
40}
41
42static int _strpcmp(const void* a, const void* b) {
43 return strcmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title);
44}
45
46static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filter)(struct VFile*)) {
47 _cleanFiles(currentFiles);
48
49 struct VDir* dir = VDirOpen(currentPath);
50 if (!dir) {
51 return false;
52 }
53 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = "(Up)" };
54 size_t i = 0;
55 struct VDirEntry* de;
56 while ((de = dir->listNext(dir))) {
57 ++i;
58 if (!(i % SCANNING_THRESHOLD)) {
59 uint32_t input = 0;
60 GUIPollInput(params, &input, 0);
61 if (input & (1 << GUI_INPUT_CANCEL)) {
62 return false;
63 }
64
65 params->drawStart();
66 if (params->guiPrepare) {
67 params->guiPrepare();
68 }
69 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath);
70 GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %lu)", i);
71 if (params->guiFinish) {
72 params->guiFinish();
73 }
74 params->drawEnd();
75 }
76 const char* name = de->name(de);
77 if (name[0] == '.') {
78 continue;
79 }
80 if (de->type(de) == VFS_FILE) {
81 struct VFile* vf = dir->openFile(dir, name, O_RDONLY);
82 if (!vf) {
83 continue;
84 }
85 if (!filter || filter(vf)) {
86 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
87 }
88 vf->close(vf);
89 } else {
90 *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
91 }
92 }
93 dir->close(dir);
94 qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp);
95
96 return true;
97}
98
99bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filter)(struct VFile*)) {
100 struct GUIMenu menu = {
101 .title = params->currentPath,
102 .index = params->fileIndex,
103 };
104 GUIMenuItemListInit(&menu.items, 0);
105 _refreshDirectory(params, params->currentPath, &menu.items, filter);
106
107 while (true) {
108 struct GUIMenuItem item;
109 enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item);
110 params->fileIndex = menu.index;
111 if (reason == GUI_MENU_EXIT_CANCEL) {
112 break;
113 }
114 if (reason == GUI_MENU_EXIT_ACCEPT) {
115 if (params->fileIndex == 0) {
116 _upDirectory(params->currentPath);
117 if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
118 break;
119 }
120 } else {
121 size_t len = strlen(params->currentPath);
122 const char* sep = PATH_SEP;
123 if (params->currentPath[len - 1] == *sep) {
124 sep = "";
125 }
126 snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item.title);
127
128 struct GUIMenuItemList newFiles;
129 GUIMenuItemListInit(&newFiles, 0);
130 if (!_refreshDirectory(params, outPath, &newFiles, filter)) {
131 _cleanFiles(&newFiles);
132 GUIMenuItemListDeinit(&newFiles);
133 struct VFile* vf = VFileOpen(outPath, O_RDONLY);
134 if (!vf) {
135 continue;
136 }
137 if (!filter || filter(vf)) {
138 vf->close(vf);
139 _cleanFiles(&menu.items);
140 GUIMenuItemListDeinit(&menu.items);
141 return true;
142 }
143 vf->close(vf);
144 break;
145 } else {
146 _cleanFiles(&menu.items);
147 GUIMenuItemListDeinit(&menu.items);
148 menu.items = newFiles;
149 strncpy(params->currentPath, outPath, PATH_MAX);
150 }
151 }
152 params->fileIndex = 0;
153 menu.index = 0;
154 }
155 if (reason == GUI_MENU_EXIT_BACK) {
156 if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) {
157 break;
158 }
159 _upDirectory(params->currentPath);
160 if (!_refreshDirectory(params, params->currentPath, &menu.items, filter)) {
161 break;
162 }
163 params->fileIndex = 0;
164 menu.index = 0;
165 }
166 }
167
168 _cleanFiles(&menu.items);
169 GUIMenuItemListDeinit(&menu.items);
170 return false;
171}