| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576 |
- // Copyright (C) 2004-2024 Artifex Software, Inc.
- //
- // This file is part of MuPDF.
- //
- // MuPDF is free software: you can redistribute it and/or modify it under the
- // terms of the GNU Affero General Public License as published by the Free
- // Software Foundation, either version 3 of the License, or (at your option)
- // any later version.
- //
- // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
- // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
- // details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
- //
- // Alternative licensing terms are available from the licensor.
- // For commercial licensing, see <https://www.artifex.com/> or contact
- // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
- // CA 94129, USA, for further information.
- #include "gl-app.h"
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <limits.h>
- #define ICON_PC 0x1f4bb
- #define ICON_HOME 0x1f3e0
- #define ICON_FOLDER 0x1f4c1
- #define ICON_DOCUMENT 0x1f4c4
- #define ICON_DISK 0x1f4be
- #define ICON_PIN 0x1f4cc
- struct entry
- {
- int is_dir;
- char name[FILENAME_MAX];
- };
- static struct
- {
- int (*filter)(const char *fn);
- struct input input_dir;
- struct input input_file;
- struct list list_dir;
- char original_file_name[PATH_MAX];
- char curdir[PATH_MAX];
- int count;
- int max;
- struct entry *files;
- int selected;
- int confirm;
- } fc;
- static int cmp_entry(const void *av, const void *bv)
- {
- const struct entry *a = av;
- const struct entry *b = bv;
- /* "." first */
- if (a->name[0] == '.' && a->name[1] == 0) return -1;
- if (b->name[0] == '.' && b->name[1] == 0) return 1;
- /* ".." second */
- if (a->name[0] == '.' && a->name[1] == '.' && a->name[2] == 0) return -1;
- if (b->name[0] == '.' && b->name[1] == '.' && b->name[2] == 0) return 1;
- /* directories before files */
- if (a->is_dir && !b->is_dir) return -1;
- if (b->is_dir && !a->is_dir) return 1;
- /* then alphabetically */
- return strcmp(a->name, b->name);
- }
- static void
- ensure_one_more_file(void)
- {
- if (fc.count == fc.max)
- {
- int new_max = fc.max == 0 ? 512 : fc.max*2;
- fc.files = fz_realloc_array(ctx, fc.files, new_max, struct entry);
- fc.max = new_max;
- }
- }
- #ifdef _WIN32
- #include <strsafe.h>
- #include <shlobj.h>
- static void load_dir(const char *path)
- {
- WIN32_FIND_DATAW ffd;
- HANDLE dir;
- wchar_t wpath[PATH_MAX];
- char buf[PATH_MAX];
- int i;
- fz_realpath(path, fc.curdir);
- if (!fz_is_directory(ctx, path))
- return;
- ui_input_init(&fc.input_dir, fc.curdir);
- fc.selected = -1;
- fc.count = 0;
- MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX);
- for (i=0; wpath[i]; ++i)
- if (wpath[i] == '/')
- wpath[i] = '\\';
- StringCchCatW(wpath, PATH_MAX, L"/*");
- dir = FindFirstFileW(wpath, &ffd);
- if (dir)
- {
- do
- {
- WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, buf, PATH_MAX, NULL, NULL);
- if (ffd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
- continue;
- ensure_one_more_file();
- fc.files[fc.count].is_dir = ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
- if (fc.files[fc.count].is_dir || !fc.filter || fc.filter(buf))
- {
- fz_strlcpy(fc.files[fc.count].name, buf, FILENAME_MAX);
- ++fc.count;
- }
- }
- while (FindNextFileW(dir, &ffd));
- FindClose(dir);
- }
- qsort(fc.files, fc.count, sizeof fc.files[0], cmp_entry);
- }
- static void list_drives(void)
- {
- static struct list drive_list;
- DWORD drives;
- char dir[PATH_MAX], vis[PATH_MAX], buf[100];
- const char *user, *home;
- char personal[MAX_PATH], desktop[MAX_PATH];
- int i, n;
- drives = GetLogicalDrives();
- n = 5; /* curdir + home + desktop + documents + downloads */
- for (i=0; i < 26; ++i)
- if (drives & (1<<i))
- ++n;
- ui_list_begin(&drive_list, n, 0, 10 * ui.lineheight + 4);
- user = getenv("USERNAME");
- home = getenv("USERPROFILE");
- if (user && home)
- {
- fz_snprintf(vis, sizeof vis, "%C %s", ICON_HOME, user);
- if (ui_list_item(&drive_list, "~", vis, 0))
- load_dir(home);
- }
- if (SHGetFolderPathA(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, desktop) == S_OK)
- {
- fz_snprintf(vis, sizeof vis, "%C Desktop", ICON_PC);
- if (ui_list_item(&drive_list, "~/Desktop", vis, 0))
- load_dir(desktop);
- }
- if (SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, 0, personal) == S_OK)
- {
- fz_snprintf(vis, sizeof vis, "%C Documents", ICON_FOLDER);
- if (ui_list_item(&drive_list, "~/Documents", vis, 0))
- load_dir(personal);
- }
- if (home)
- {
- fz_snprintf(vis, sizeof vis, "%C Downloads", ICON_FOLDER);
- fz_snprintf(dir, sizeof dir, "%s/Downloads", home);
- if (ui_list_item(&drive_list, "~/Downloads", vis, 0))
- load_dir(dir);
- }
- for (i = 0; i < 26; ++i)
- {
- if (drives & (1<<i))
- {
- fz_snprintf(dir, sizeof dir, "%c:\\", i+'A');
- if (!GetVolumeInformationA(dir, buf, sizeof buf, NULL, NULL, NULL, NULL, 0))
- buf[0] = 0;
- fz_snprintf(vis, sizeof vis, "%C %c: %s", ICON_DISK, i+'A', buf);
- if (ui_list_item(&drive_list, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+i, vis, 0))
- {
- load_dir(dir);
- }
- }
- }
- fz_snprintf(vis, sizeof vis, "%C .", ICON_PIN);
- if (ui_list_item(&drive_list, ".", vis, 0))
- load_dir(".");
- ui_list_end(&drive_list);
- }
- #else
- #include <dirent.h>
- static void load_dir(const char *path)
- {
- char buf[PATH_MAX];
- DIR *dir;
- struct dirent *dp;
- fz_realpath(path, fc.curdir);
- if (!fz_is_directory(ctx, fc.curdir))
- return;
- ui_input_init(&fc.input_dir, fc.curdir);
- fc.selected = -1;
- fc.count = 0;
- dir = opendir(fc.curdir);
- if (!dir)
- {
- ensure_one_more_file();
- fc.files[fc.count].is_dir = 1;
- fz_strlcpy(fc.files[fc.count].name, "..", FILENAME_MAX);
- ++fc.count;
- }
- else
- {
- while ((dp = readdir(dir)))
- {
- /* skip hidden files */
- if (dp->d_name[0] == '.' && strcmp(dp->d_name, ".") && strcmp(dp->d_name, ".."))
- continue;
- fz_snprintf(buf, sizeof buf, "%s/%s", fc.curdir, dp->d_name);
- ensure_one_more_file();
- fc.files[fc.count].is_dir = fz_is_directory(ctx, buf);
- if (fc.files[fc.count].is_dir || !fc.filter || fc.filter(buf))
- {
- fz_strlcpy(fc.files[fc.count].name, dp->d_name, FILENAME_MAX);
- ++fc.count;
- }
- }
- closedir(dir);
- }
- qsort(fc.files, fc.count, sizeof fc.files[0], cmp_entry);
- }
- static const struct {
- int icon;
- const char *name;
- } common_dirs[] = {
- { ICON_HOME, "~" },
- { ICON_PC, "~/Desktop" },
- { ICON_FOLDER, "~/Documents" },
- { ICON_FOLDER, "~/Downloads" },
- { ICON_FOLDER, "/" },
- { ICON_DISK, "/Volumes" },
- { ICON_DISK, "/media" },
- { ICON_DISK, "/mnt" },
- { ICON_PIN, "." },
- };
- static int has_dir(const char *home, const char *user, int i, char dir[PATH_MAX], char vis[PATH_MAX])
- {
- const char *subdir = common_dirs[i].name;
- int icon = common_dirs[i].icon;
- if (subdir[0] == '~')
- {
- if (!home)
- return 0;
- if (subdir[1] == '/')
- {
- fz_snprintf(dir, PATH_MAX, "%s/%s", home, subdir+2);
- fz_snprintf(vis, PATH_MAX, "%C %s", icon, subdir+2);
- }
- else
- {
- fz_snprintf(dir, PATH_MAX, "%s", home);
- fz_snprintf(vis, PATH_MAX, "%C %s", icon, user ? user : "~");
- }
- }
- else
- {
- fz_strlcpy(dir, subdir, PATH_MAX);
- fz_snprintf(vis, PATH_MAX, "%C %s", icon, subdir);
- }
- return fz_is_directory(ctx, dir);
- }
- static void list_drives(void)
- {
- static struct list drive_list;
- char dir[PATH_MAX], vis[PATH_MAX];
- const char *home = getenv("HOME");
- const char *user = getenv("USER");
- int i;
- ui_list_begin(&drive_list, nelem(common_dirs), 0, nelem(common_dirs) * ui.lineheight + 4);
- for (i = 0; i < (int)nelem(common_dirs); ++i)
- if (has_dir(home, user, i, dir, vis))
- if (ui_list_item(&drive_list, common_dirs[i].name, vis, 0))
- load_dir(dir);
- ui_list_end(&drive_list);
- }
- #endif
- void ui_init_open_file(const char *dir, int (*filter)(const char *fn))
- {
- fc.filter = filter;
- load_dir(dir);
- }
- int ui_open_file(char *filename, const char *label)
- {
- static int last_click_time = 0;
- static int last_click_sel = -1;
- int i, rv = 0;
- ui_panel_begin(0, 0, ui.padsize*2, ui.padsize*2, 1);
- {
- if (label)
- {
- ui_layout(T, X, NW, ui.padsize*2, ui.padsize);
- ui_label(label);
- }
- ui_layout(L, Y, NW, 0, 0);
- ui_panel_begin(ui.gridsize*6, 0, 0, 0, 0);
- {
- ui_layout(T, X, NW, ui.padsize, ui.padsize);
- list_drives();
- ui_layout(B, X, NW, ui.padsize, ui.padsize);
- if (ui_button("Cancel") || (!ui.focus && ui.key == KEY_ESCAPE))
- {
- filename[0] = 0;
- rv = 1;
- }
- }
- ui_panel_end();
- ui_layout(T, X, NW, ui.padsize, ui.padsize);
- ui_panel_begin(0, ui.gridsize, 0, 0, 0);
- {
- int disabled = (fc.selected < 0);
- ui_layout(R, NONE, CENTER, 0, 0);
- if (ui_button_aux("Open", disabled) || (!disabled && !ui.focus && ui.key == KEY_ENTER))
- {
- fz_snprintf(filename, PATH_MAX, "%s/%s", fc.curdir, fc.files[fc.selected].name);
- rv = 1;
- }
- ui_spacer();
- ui_layout(ALL, X, CENTER, 0, 0);
- if (ui_input(&fc.input_dir, 0, 1) == UI_INPUT_ACCEPT)
- load_dir(fc.input_dir.text);
- }
- ui_panel_end();
- ui_layout(ALL, BOTH, NW, ui.padsize, ui.padsize);
- ui_list_begin(&fc.list_dir, fc.count, 0, 0);
- for (i = 0; i < fc.count; ++i)
- {
- const char *name = fc.files[i].name;
- char buf[PATH_MAX];
- if (fc.files[i].is_dir)
- fz_snprintf(buf, sizeof buf, "%C %s", ICON_FOLDER, name);
- else
- fz_snprintf(buf, sizeof buf, "%C %s", ICON_DOCUMENT, name);
- if (ui_list_item(&fc.list_dir, &fc.files[i], buf, i==fc.selected))
- {
- fc.selected = i;
- if (fc.files[i].is_dir)
- {
- fz_snprintf(buf, sizeof buf, "%s/%s", fc.curdir, name);
- load_dir(buf);
- ui.active = NULL;
- last_click_sel = -1;
- }
- else
- {
- int click_time = glutGet(GLUT_ELAPSED_TIME);
- if (i == last_click_sel && click_time < last_click_time + 250)
- {
- fz_snprintf(filename, PATH_MAX, "%s/%s", fc.curdir, name);
- rv = 1;
- }
- last_click_time = click_time;
- last_click_sel = i;
- }
- }
- }
- ui_list_end(&fc.list_dir);
- }
- ui_panel_end();
- return rv;
- }
- void ui_init_save_file(const char *path, int (*filter)(const char *fn))
- {
- char dir[PATH_MAX], *p;
- fc.filter = filter;
- fz_strlcpy(dir, path, sizeof dir);
- for (p=dir; *p; ++p)
- if (*p == '\\') *p = '/';
- fz_cleanname(dir);
- p = strrchr(dir, '/');
- if (p)
- {
- *p = 0;
- load_dir(dir);
- ui_input_init(&fc.input_file, p+1);
- }
- else
- {
- load_dir(".");
- ui_input_init(&fc.input_file, dir);
- }
- fz_snprintf(fc.original_file_name, PATH_MAX, "%s/%s", fc.curdir, fc.input_file.text);
- fc.confirm = 0;
- }
- static void bump_file_version(int dir)
- {
- char buf[PATH_MAX], *p, *n;
- char base[PATH_MAX], out[PATH_MAX];
- int x;
- fz_strlcpy(buf, fc.input_file.text, sizeof buf);
- p = strrchr(buf, '.');
- if (p)
- {
- n = p;
- while (n > buf && n[-1] >= '0' && n[-1] <= '9')
- --n;
- if (n != p)
- x = atoi(n) + dir;
- else
- x = dir;
- memcpy(base, buf, n-buf);
- base[n-buf] = 0;
- fz_snprintf(out, sizeof out, "%s%d%s", base, x, p);
- ui_input_init(&fc.input_file, out);
- }
- }
- static int ui_save_file_confirm(char *filename)
- {
- int rv = 0;
- ui_dialog_begin(ui.gridsize*20, (ui.gridsize+7)*3);
- ui_layout(T, NONE, NW, ui.padsize, ui.padsize);
- ui_label("%C File %s already exists!", 0x26a0, filename); /* WARNING SIGN */
- ui_label("Do you want to replace it?");
- ui_layout(B, X, S, ui.padsize, ui.padsize);
- ui_panel_begin(0, ui.gridsize, 0, 0, 0);
- {
- ui_layout(R, NONE, S, 0, 0);
- if (ui_button("Replace"))
- rv = 1;
- ui_spacer();
- ui_layout(L, NONE, S, 0, 0);
- if (ui_button("Cancel") || ui.key == KEY_ESCAPE)
- fc.confirm = 0;
- }
- ui_panel_end();
- ui_dialog_end();
- return rv;
- }
- int ui_save_file(char *filename, void (*extra_panel)(void), const char *label)
- {
- int i, rv = 0;
- if (fc.confirm)
- {
- return ui_save_file_confirm(filename);
- }
- ui_panel_begin(0, 0, ui.padsize*2, ui.padsize*2, 1);
- {
- if (label)
- {
- ui_layout(T, X, NW, ui.padsize*2, ui.padsize);
- ui_label(label);
- }
- ui_layout(L, Y, NW, 0, 0);
- ui_panel_begin(ui.gridsize*6, 0, 0, 0, 0);
- {
- ui_layout(T, X, NW, ui.padsize, ui.padsize);
- list_drives();
- if (extra_panel)
- {
- ui_spacer();
- extra_panel();
- }
- ui_layout(B, X, NW, ui.padsize, ui.padsize);
- if (ui_button("Cancel") || (!ui.focus && ui.key == KEY_ESCAPE))
- {
- filename[0] = 0;
- rv = 1;
- }
- }
- ui_panel_end();
- ui_layout(T, X, NW, ui.padsize, ui.padsize);
- if (ui_input(&fc.input_dir, 0, 1) == UI_INPUT_ACCEPT)
- load_dir(fc.input_dir.text);
- ui_layout(T, X, NW, ui.padsize, ui.padsize);
- ui_panel_begin(0, ui.gridsize, 0, 0, 0);
- {
- ui_layout(R, NONE, CENTER, 0, 0);
- if (ui_button("Save"))
- {
- fz_snprintf(filename, PATH_MAX, "%s/%s", fc.curdir, fc.input_file.text);
- rv = 1;
- /* Show confirmation dialog if we would overwrite another file. */
- if (strcmp(filename, fc.original_file_name))
- {
- if (fz_file_exists(ctx, filename))
- {
- fc.confirm = 1;
- rv = 0;
- }
- }
- }
- ui_spacer();
- if (ui_button("\xe2\x9e\x95")) /* U+2795 HEAVY PLUS */
- bump_file_version(1);
- if (ui_button("\xe2\x9e\x96")) /* U+2796 HEAVY MINUS */
- bump_file_version(-1);
- ui_spacer();
- ui_layout(ALL, X, CENTER, 0, 0);
- ui_input(&fc.input_file, 0, 1);
- }
- ui_panel_end();
- ui_layout(ALL, BOTH, NW, ui.padsize, ui.padsize);
- ui_list_begin(&fc.list_dir, fc.count, 0, 0);
- for (i = 0; i < fc.count; ++i)
- {
- const char *name = fc.files[i].name;
- char buf[PATH_MAX];
- if (fc.files[i].is_dir)
- fz_snprintf(buf, sizeof buf, "%C %s", ICON_FOLDER, name);
- else
- fz_snprintf(buf, sizeof buf, "%C %s", ICON_DOCUMENT, name);
- if (ui_list_item(&fc.list_dir, &fc.files[i], buf, i==fc.selected))
- {
- fc.selected = i;
- if (fc.files[i].is_dir)
- {
- fz_snprintf(buf, sizeof buf, "%s/%s", fc.curdir, name);
- load_dir(buf);
- ui.active = NULL;
- }
- else
- {
- ui_input_init(&fc.input_file, name);
- }
- }
- }
- ui_list_end(&fc.list_dir);
- }
- ui_panel_end();
- return rv;
- }
|