comparison mupdf-source/platform/gl/gl-file.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2024 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 #include "gl-app.h"
24
25 #include <string.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <limits.h>
29
30 #define ICON_PC 0x1f4bb
31 #define ICON_HOME 0x1f3e0
32 #define ICON_FOLDER 0x1f4c1
33 #define ICON_DOCUMENT 0x1f4c4
34 #define ICON_DISK 0x1f4be
35 #define ICON_PIN 0x1f4cc
36
37 struct entry
38 {
39 int is_dir;
40 char name[FILENAME_MAX];
41 };
42
43 static struct
44 {
45 int (*filter)(const char *fn);
46 struct input input_dir;
47 struct input input_file;
48 struct list list_dir;
49 char original_file_name[PATH_MAX];
50 char curdir[PATH_MAX];
51 int count;
52 int max;
53 struct entry *files;
54 int selected;
55 int confirm;
56 } fc;
57
58 static int cmp_entry(const void *av, const void *bv)
59 {
60 const struct entry *a = av;
61 const struct entry *b = bv;
62 /* "." first */
63 if (a->name[0] == '.' && a->name[1] == 0) return -1;
64 if (b->name[0] == '.' && b->name[1] == 0) return 1;
65 /* ".." second */
66 if (a->name[0] == '.' && a->name[1] == '.' && a->name[2] == 0) return -1;
67 if (b->name[0] == '.' && b->name[1] == '.' && b->name[2] == 0) return 1;
68 /* directories before files */
69 if (a->is_dir && !b->is_dir) return -1;
70 if (b->is_dir && !a->is_dir) return 1;
71 /* then alphabetically */
72 return strcmp(a->name, b->name);
73 }
74
75 static void
76 ensure_one_more_file(void)
77 {
78 if (fc.count == fc.max)
79 {
80 int new_max = fc.max == 0 ? 512 : fc.max*2;
81 fc.files = fz_realloc_array(ctx, fc.files, new_max, struct entry);
82 fc.max = new_max;
83 }
84 }
85
86 #ifdef _WIN32
87
88 #include <strsafe.h>
89 #include <shlobj.h>
90
91 static void load_dir(const char *path)
92 {
93 WIN32_FIND_DATAW ffd;
94 HANDLE dir;
95 wchar_t wpath[PATH_MAX];
96 char buf[PATH_MAX];
97 int i;
98
99 fz_realpath(path, fc.curdir);
100 if (!fz_is_directory(ctx, path))
101 return;
102
103 ui_input_init(&fc.input_dir, fc.curdir);
104
105 fc.selected = -1;
106 fc.count = 0;
107
108 MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX);
109 for (i=0; wpath[i]; ++i)
110 if (wpath[i] == '/')
111 wpath[i] = '\\';
112 StringCchCatW(wpath, PATH_MAX, L"/*");
113 dir = FindFirstFileW(wpath, &ffd);
114 if (dir)
115 {
116 do
117 {
118 WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, buf, PATH_MAX, NULL, NULL);
119 if (ffd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
120 continue;
121 ensure_one_more_file();
122 fc.files[fc.count].is_dir = ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
123 if (fc.files[fc.count].is_dir || !fc.filter || fc.filter(buf))
124 {
125 fz_strlcpy(fc.files[fc.count].name, buf, FILENAME_MAX);
126 ++fc.count;
127 }
128 }
129 while (FindNextFileW(dir, &ffd));
130 FindClose(dir);
131 }
132
133 qsort(fc.files, fc.count, sizeof fc.files[0], cmp_entry);
134 }
135
136 static void list_drives(void)
137 {
138 static struct list drive_list;
139 DWORD drives;
140 char dir[PATH_MAX], vis[PATH_MAX], buf[100];
141 const char *user, *home;
142 char personal[MAX_PATH], desktop[MAX_PATH];
143 int i, n;
144
145 drives = GetLogicalDrives();
146 n = 5; /* curdir + home + desktop + documents + downloads */
147 for (i=0; i < 26; ++i)
148 if (drives & (1<<i))
149 ++n;
150
151 ui_list_begin(&drive_list, n, 0, 10 * ui.lineheight + 4);
152
153 user = getenv("USERNAME");
154 home = getenv("USERPROFILE");
155 if (user && home)
156 {
157 fz_snprintf(vis, sizeof vis, "%C %s", ICON_HOME, user);
158 if (ui_list_item(&drive_list, "~", vis, 0))
159 load_dir(home);
160 }
161
162 if (SHGetFolderPathA(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, desktop) == S_OK)
163 {
164 fz_snprintf(vis, sizeof vis, "%C Desktop", ICON_PC);
165 if (ui_list_item(&drive_list, "~/Desktop", vis, 0))
166 load_dir(desktop);
167 }
168
169 if (SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, 0, personal) == S_OK)
170 {
171 fz_snprintf(vis, sizeof vis, "%C Documents", ICON_FOLDER);
172 if (ui_list_item(&drive_list, "~/Documents", vis, 0))
173 load_dir(personal);
174 }
175
176 if (home)
177 {
178 fz_snprintf(vis, sizeof vis, "%C Downloads", ICON_FOLDER);
179 fz_snprintf(dir, sizeof dir, "%s/Downloads", home);
180 if (ui_list_item(&drive_list, "~/Downloads", vis, 0))
181 load_dir(dir);
182 }
183
184 for (i = 0; i < 26; ++i)
185 {
186 if (drives & (1<<i))
187 {
188 fz_snprintf(dir, sizeof dir, "%c:\\", i+'A');
189 if (!GetVolumeInformationA(dir, buf, sizeof buf, NULL, NULL, NULL, NULL, 0))
190 buf[0] = 0;
191 fz_snprintf(vis, sizeof vis, "%C %c: %s", ICON_DISK, i+'A', buf);
192 if (ui_list_item(&drive_list, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+i, vis, 0))
193 {
194 load_dir(dir);
195 }
196 }
197 }
198
199 fz_snprintf(vis, sizeof vis, "%C .", ICON_PIN);
200 if (ui_list_item(&drive_list, ".", vis, 0))
201 load_dir(".");
202
203 ui_list_end(&drive_list);
204 }
205
206 #else
207
208 #include <dirent.h>
209
210 static void load_dir(const char *path)
211 {
212 char buf[PATH_MAX];
213 DIR *dir;
214 struct dirent *dp;
215
216 fz_realpath(path, fc.curdir);
217 if (!fz_is_directory(ctx, fc.curdir))
218 return;
219
220 ui_input_init(&fc.input_dir, fc.curdir);
221
222 fc.selected = -1;
223 fc.count = 0;
224
225 dir = opendir(fc.curdir);
226 if (!dir)
227 {
228 ensure_one_more_file();
229 fc.files[fc.count].is_dir = 1;
230 fz_strlcpy(fc.files[fc.count].name, "..", FILENAME_MAX);
231 ++fc.count;
232 }
233 else
234 {
235 while ((dp = readdir(dir)))
236 {
237 /* skip hidden files */
238 if (dp->d_name[0] == '.' && strcmp(dp->d_name, ".") && strcmp(dp->d_name, ".."))
239 continue;
240 fz_snprintf(buf, sizeof buf, "%s/%s", fc.curdir, dp->d_name);
241 ensure_one_more_file();
242 fc.files[fc.count].is_dir = fz_is_directory(ctx, buf);
243 if (fc.files[fc.count].is_dir || !fc.filter || fc.filter(buf))
244 {
245 fz_strlcpy(fc.files[fc.count].name, dp->d_name, FILENAME_MAX);
246 ++fc.count;
247 }
248 }
249 closedir(dir);
250 }
251
252 qsort(fc.files, fc.count, sizeof fc.files[0], cmp_entry);
253 }
254
255 static const struct {
256 int icon;
257 const char *name;
258 } common_dirs[] = {
259 { ICON_HOME, "~" },
260 { ICON_PC, "~/Desktop" },
261 { ICON_FOLDER, "~/Documents" },
262 { ICON_FOLDER, "~/Downloads" },
263 { ICON_FOLDER, "/" },
264 { ICON_DISK, "/Volumes" },
265 { ICON_DISK, "/media" },
266 { ICON_DISK, "/mnt" },
267 { ICON_PIN, "." },
268 };
269
270 static int has_dir(const char *home, const char *user, int i, char dir[PATH_MAX], char vis[PATH_MAX])
271 {
272 const char *subdir = common_dirs[i].name;
273 int icon = common_dirs[i].icon;
274 if (subdir[0] == '~')
275 {
276 if (!home)
277 return 0;
278 if (subdir[1] == '/')
279 {
280 fz_snprintf(dir, PATH_MAX, "%s/%s", home, subdir+2);
281 fz_snprintf(vis, PATH_MAX, "%C %s", icon, subdir+2);
282 }
283 else
284 {
285 fz_snprintf(dir, PATH_MAX, "%s", home);
286 fz_snprintf(vis, PATH_MAX, "%C %s", icon, user ? user : "~");
287 }
288 }
289 else
290 {
291 fz_strlcpy(dir, subdir, PATH_MAX);
292 fz_snprintf(vis, PATH_MAX, "%C %s", icon, subdir);
293 }
294 return fz_is_directory(ctx, dir);
295 }
296
297 static void list_drives(void)
298 {
299 static struct list drive_list;
300 char dir[PATH_MAX], vis[PATH_MAX];
301 const char *home = getenv("HOME");
302 const char *user = getenv("USER");
303 int i;
304
305 ui_list_begin(&drive_list, nelem(common_dirs), 0, nelem(common_dirs) * ui.lineheight + 4);
306
307 for (i = 0; i < (int)nelem(common_dirs); ++i)
308 if (has_dir(home, user, i, dir, vis))
309 if (ui_list_item(&drive_list, common_dirs[i].name, vis, 0))
310 load_dir(dir);
311
312 ui_list_end(&drive_list);
313 }
314
315 #endif
316
317 void ui_init_open_file(const char *dir, int (*filter)(const char *fn))
318 {
319 fc.filter = filter;
320 load_dir(dir);
321 }
322
323 int ui_open_file(char *filename, const char *label)
324 {
325 static int last_click_time = 0;
326 static int last_click_sel = -1;
327 int i, rv = 0;
328
329 ui_panel_begin(0, 0, ui.padsize*2, ui.padsize*2, 1);
330 {
331 if (label)
332 {
333 ui_layout(T, X, NW, ui.padsize*2, ui.padsize);
334 ui_label(label);
335 }
336 ui_layout(L, Y, NW, 0, 0);
337 ui_panel_begin(ui.gridsize*6, 0, 0, 0, 0);
338 {
339 ui_layout(T, X, NW, ui.padsize, ui.padsize);
340 list_drives();
341 ui_layout(B, X, NW, ui.padsize, ui.padsize);
342 if (ui_button("Cancel") || (!ui.focus && ui.key == KEY_ESCAPE))
343 {
344 filename[0] = 0;
345 rv = 1;
346 }
347 }
348 ui_panel_end();
349
350 ui_layout(T, X, NW, ui.padsize, ui.padsize);
351 ui_panel_begin(0, ui.gridsize, 0, 0, 0);
352 {
353 int disabled = (fc.selected < 0);
354 ui_layout(R, NONE, CENTER, 0, 0);
355 if (ui_button_aux("Open", disabled) || (!disabled && !ui.focus && ui.key == KEY_ENTER))
356 {
357 fz_snprintf(filename, PATH_MAX, "%s/%s", fc.curdir, fc.files[fc.selected].name);
358 rv = 1;
359 }
360 ui_spacer();
361 ui_layout(ALL, X, CENTER, 0, 0);
362 if (ui_input(&fc.input_dir, 0, 1) == UI_INPUT_ACCEPT)
363 load_dir(fc.input_dir.text);
364 }
365 ui_panel_end();
366
367 ui_layout(ALL, BOTH, NW, ui.padsize, ui.padsize);
368 ui_list_begin(&fc.list_dir, fc.count, 0, 0);
369 for (i = 0; i < fc.count; ++i)
370 {
371 const char *name = fc.files[i].name;
372 char buf[PATH_MAX];
373 if (fc.files[i].is_dir)
374 fz_snprintf(buf, sizeof buf, "%C %s", ICON_FOLDER, name);
375 else
376 fz_snprintf(buf, sizeof buf, "%C %s", ICON_DOCUMENT, name);
377 if (ui_list_item(&fc.list_dir, &fc.files[i], buf, i==fc.selected))
378 {
379 fc.selected = i;
380 if (fc.files[i].is_dir)
381 {
382 fz_snprintf(buf, sizeof buf, "%s/%s", fc.curdir, name);
383 load_dir(buf);
384 ui.active = NULL;
385 last_click_sel = -1;
386 }
387 else
388 {
389 int click_time = glutGet(GLUT_ELAPSED_TIME);
390 if (i == last_click_sel && click_time < last_click_time + 250)
391 {
392 fz_snprintf(filename, PATH_MAX, "%s/%s", fc.curdir, name);
393 rv = 1;
394 }
395 last_click_time = click_time;
396 last_click_sel = i;
397 }
398 }
399 }
400 ui_list_end(&fc.list_dir);
401 }
402 ui_panel_end();
403
404 return rv;
405 }
406
407 void ui_init_save_file(const char *path, int (*filter)(const char *fn))
408 {
409 char dir[PATH_MAX], *p;
410 fc.filter = filter;
411 fz_strlcpy(dir, path, sizeof dir);
412 for (p=dir; *p; ++p)
413 if (*p == '\\') *p = '/';
414 fz_cleanname(dir);
415 p = strrchr(dir, '/');
416 if (p)
417 {
418 *p = 0;
419 load_dir(dir);
420 ui_input_init(&fc.input_file, p+1);
421 }
422 else
423 {
424 load_dir(".");
425 ui_input_init(&fc.input_file, dir);
426 }
427 fz_snprintf(fc.original_file_name, PATH_MAX, "%s/%s", fc.curdir, fc.input_file.text);
428 fc.confirm = 0;
429 }
430
431 static void bump_file_version(int dir)
432 {
433 char buf[PATH_MAX], *p, *n;
434 char base[PATH_MAX], out[PATH_MAX];
435 int x;
436 fz_strlcpy(buf, fc.input_file.text, sizeof buf);
437 p = strrchr(buf, '.');
438 if (p)
439 {
440 n = p;
441 while (n > buf && n[-1] >= '0' && n[-1] <= '9')
442 --n;
443 if (n != p)
444 x = atoi(n) + dir;
445 else
446 x = dir;
447 memcpy(base, buf, n-buf);
448 base[n-buf] = 0;
449 fz_snprintf(out, sizeof out, "%s%d%s", base, x, p);
450 ui_input_init(&fc.input_file, out);
451 }
452 }
453
454 static int ui_save_file_confirm(char *filename)
455 {
456 int rv = 0;
457 ui_dialog_begin(ui.gridsize*20, (ui.gridsize+7)*3);
458 ui_layout(T, NONE, NW, ui.padsize, ui.padsize);
459 ui_label("%C File %s already exists!", 0x26a0, filename); /* WARNING SIGN */
460 ui_label("Do you want to replace it?");
461 ui_layout(B, X, S, ui.padsize, ui.padsize);
462 ui_panel_begin(0, ui.gridsize, 0, 0, 0);
463 {
464 ui_layout(R, NONE, S, 0, 0);
465 if (ui_button("Replace"))
466 rv = 1;
467 ui_spacer();
468 ui_layout(L, NONE, S, 0, 0);
469 if (ui_button("Cancel") || ui.key == KEY_ESCAPE)
470 fc.confirm = 0;
471 }
472 ui_panel_end();
473 ui_dialog_end();
474 return rv;
475 }
476
477 int ui_save_file(char *filename, void (*extra_panel)(void), const char *label)
478 {
479 int i, rv = 0;
480
481 if (fc.confirm)
482 {
483 return ui_save_file_confirm(filename);
484 }
485
486 ui_panel_begin(0, 0, ui.padsize*2, ui.padsize*2, 1);
487 {
488 if (label)
489 {
490 ui_layout(T, X, NW, ui.padsize*2, ui.padsize);
491 ui_label(label);
492 }
493 ui_layout(L, Y, NW, 0, 0);
494 ui_panel_begin(ui.gridsize*6, 0, 0, 0, 0);
495 {
496 ui_layout(T, X, NW, ui.padsize, ui.padsize);
497 list_drives();
498 if (extra_panel)
499 {
500 ui_spacer();
501 extra_panel();
502 }
503 ui_layout(B, X, NW, ui.padsize, ui.padsize);
504 if (ui_button("Cancel") || (!ui.focus && ui.key == KEY_ESCAPE))
505 {
506 filename[0] = 0;
507 rv = 1;
508 }
509 }
510 ui_panel_end();
511
512 ui_layout(T, X, NW, ui.padsize, ui.padsize);
513 if (ui_input(&fc.input_dir, 0, 1) == UI_INPUT_ACCEPT)
514 load_dir(fc.input_dir.text);
515
516 ui_layout(T, X, NW, ui.padsize, ui.padsize);
517 ui_panel_begin(0, ui.gridsize, 0, 0, 0);
518 {
519 ui_layout(R, NONE, CENTER, 0, 0);
520 if (ui_button("Save"))
521 {
522 fz_snprintf(filename, PATH_MAX, "%s/%s", fc.curdir, fc.input_file.text);
523 rv = 1;
524
525 /* Show confirmation dialog if we would overwrite another file. */
526 if (strcmp(filename, fc.original_file_name))
527 {
528 if (fz_file_exists(ctx, filename))
529 {
530 fc.confirm = 1;
531 rv = 0;
532 }
533 }
534 }
535 ui_spacer();
536 if (ui_button("\xe2\x9e\x95")) /* U+2795 HEAVY PLUS */
537 bump_file_version(1);
538 if (ui_button("\xe2\x9e\x96")) /* U+2796 HEAVY MINUS */
539 bump_file_version(-1);
540 ui_spacer();
541 ui_layout(ALL, X, CENTER, 0, 0);
542 ui_input(&fc.input_file, 0, 1);
543 }
544 ui_panel_end();
545
546 ui_layout(ALL, BOTH, NW, ui.padsize, ui.padsize);
547 ui_list_begin(&fc.list_dir, fc.count, 0, 0);
548 for (i = 0; i < fc.count; ++i)
549 {
550 const char *name = fc.files[i].name;
551 char buf[PATH_MAX];
552 if (fc.files[i].is_dir)
553 fz_snprintf(buf, sizeof buf, "%C %s", ICON_FOLDER, name);
554 else
555 fz_snprintf(buf, sizeof buf, "%C %s", ICON_DOCUMENT, name);
556 if (ui_list_item(&fc.list_dir, &fc.files[i], buf, i==fc.selected))
557 {
558 fc.selected = i;
559 if (fc.files[i].is_dir)
560 {
561 fz_snprintf(buf, sizeof buf, "%s/%s", fc.curdir, name);
562 load_dir(buf);
563 ui.active = NULL;
564 }
565 else
566 {
567 ui_input_init(&fc.input_file, name);
568 }
569 }
570 }
571 ui_list_end(&fc.list_dir);
572 }
573 ui_panel_end();
574
575 return rv;
576 }