/* Copyright (c) 2013-2015 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "file-select.h" #include "util/gui/font.h" #include "util/vfs.h" #include DEFINE_VECTOR(FileList, char*); #define ITERATION_SIZE 5 #define SCANNING_THRESHOLD 15 static void _cleanFiles(struct FileList* currentFiles) { size_t size = FileListSize(currentFiles); size_t i; for (i = 1; i < size; ++i) { free(*FileListGetPointer(currentFiles, i)); } FileListClear(currentFiles); } static void _upDirectory(char* currentPath) { char* end = strrchr(currentPath, '/'); if (!end) { return; } if (end == currentPath) { end[1] = '\0'; return; } end[0] = '\0'; if (end[1]) { return; } // TODO: What if there was a trailing slash? } static int _strpcmp(const void* a, const void* b) { return strcmp(*(const char**) a, *(const char**) b); } static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct FileList* currentFiles, bool (*filter)(struct VFile*)) { _cleanFiles(currentFiles); struct VDir* dir = VDirOpen(currentPath); if (!dir) { return false; } *FileListAppend(currentFiles) = "(Up)"; size_t i = 0; struct VDirEntry* de; while ((de = dir->listNext(dir))) { ++i; if (i % SCANNING_THRESHOLD == SCANNING_THRESHOLD - 1) { int input = 0; GUIPollInput(params, &input, 0); if (input & (1 << GUI_INPUT_CANCEL)) { return false; } params->drawStart(); GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning)"); params->drawEnd(); } const char* name = de->name(de); if (name[0] == '.') { continue; } if (de->type(de) == VFS_FILE) { struct VFile* vf = dir->openFile(dir, name, O_RDONLY); if (!vf) { continue; } if (!filter || filter(vf)) { *FileListAppend(currentFiles) = strdup(name); } vf->close(vf); } else { *FileListAppend(currentFiles) = strdup(name); } } dir->close(dir); qsort(FileListGetPointer(currentFiles, 1), FileListSize(currentFiles) - 1, sizeof(char*), _strpcmp); return true; } bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filter)(struct VFile*)) { size_t start = 0; size_t pageSize = params->height / GUIFontHeight(params->font); if (pageSize > 4) { pageSize -= 4; } else { pageSize = 1; } GUIInvalidateKeys(params); struct FileList currentFiles; FileListInit(¤tFiles, 0); _refreshDirectory(params, params->currentPath, ¤tFiles, filter); while (true) { int newInput = 0; GUIPollInput(params, &newInput, 0); if (newInput & (1 << GUI_INPUT_UP) && params->fileIndex > 0) { --params->fileIndex; } if (newInput & (1 << GUI_INPUT_DOWN) && params->fileIndex < FileListSize(¤tFiles) - 1) { ++params->fileIndex; } if (newInput & (1 << GUI_INPUT_LEFT)) { if (params->fileIndex >= pageSize) { params->fileIndex -= pageSize; } else { params->fileIndex = 0; } } if (newInput & (1 << GUI_INPUT_RIGHT)) { if (params->fileIndex + pageSize < FileListSize(¤tFiles)) { params->fileIndex += pageSize; } else { params->fileIndex = FileListSize(¤tFiles) - 1; } } if (params->fileIndex < start) { start = params->fileIndex; } while ((params->fileIndex - start + 4) * GUIFontHeight(params->font) > params->height) { ++start; } if (newInput & (1 << GUI_INPUT_CANCEL)) { break; } if (newInput & (1 << GUI_INPUT_SELECT)) { if (params->fileIndex == 0) { _upDirectory(params->currentPath); if (!_refreshDirectory(params, params->currentPath, ¤tFiles, filter)) { break; } } else { size_t len = strlen(params->currentPath); const char* sep = PATH_SEP; if (params->currentPath[len - 1] == *sep) { sep = ""; } snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, *FileListGetPointer(¤tFiles, params->fileIndex)); struct FileList newFiles; FileListInit(&newFiles, 0); if (!_refreshDirectory(params, outPath, &newFiles, filter)) { _cleanFiles(&newFiles); FileListDeinit(&newFiles); struct VFile* vf = VFileOpen(outPath, O_RDONLY); if (!vf) { continue; } if (!filter || filter(vf)) { vf->close(vf); _cleanFiles(¤tFiles); FileListDeinit(¤tFiles); return true; } vf->close(vf); break; } else { _cleanFiles(¤tFiles); FileListDeinit(¤tFiles); currentFiles = newFiles; strncpy(params->currentPath, outPath, PATH_MAX); } } params->fileIndex = 0; } if (newInput & (1 << GUI_INPUT_BACK)) { if (strncmp(params->currentPath, params->basePath, PATH_MAX) == 0) { break; } _upDirectory(params->currentPath); if (!_refreshDirectory(params, params->currentPath, ¤tFiles, filter)) { break; } params->fileIndex = 0; } params->drawStart(); unsigned y = GUIFontHeight(params->font); GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", params->currentPath); y += 2 * GUIFontHeight(params->font); size_t i; for (i = start; i < FileListSize(¤tFiles); ++i) { int color = 0xE0A0A0A0; char bullet = ' '; if (i == params->fileIndex) { color = 0xFFFFFFFF; bullet = '>'; } GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, color, "%c %s", bullet, *FileListGetPointer(¤tFiles, i)); y += GUIFontHeight(params->font); if (y + GUIFontHeight(params->font) > params->height) { break; } } y += GUIFontHeight(params->font) * 2; params->drawEnd(); } _cleanFiles(¤tFiles); FileListDeinit(¤tFiles); return false; }