diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/platform/gl/gl-file.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,576 @@
+// 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;
+}