Gimp/app/actions/file-commands.c
Jehan 473f0b49ef app: better handle bogus file names.
When a plug-in enters a bogus file name such as "Untitled.xcd" as URI,
g_file_is_native() returns FALSE. So when saving such file, core code tries to
mount a remote volume and fails (of course), without even opening the file
dialog.

This change is an attempt to detect such cases beforehand and not even try to
save it (just open the file dialog directly; the file name is still useful as
default proposed file name, as this part is actually valid).
Actually remote files will have a valid URI anyway (something with a valid
scheme, such as https:// or whatever other scheme).

As side fix, I am adding a missing space to the error which I got.
2023-02-13 22:57:09 +01:00

864 lines
28 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <gegl.h>
#include <gtk/gtk.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "actions-types.h"
#include "config/gimpguiconfig.h"
#include "core/gimp.h"
#include "core/gimpcontainer.h"
#include "core/gimpimage.h"
#include "core/gimpimagefile.h"
#include "core/gimpprogress.h"
#include "core/gimptemplate.h"
#include "plug-in/gimppluginmanager-file.h"
#include "file/file-open.h"
#include "file/file-save.h"
#include "file/gimp-file.h"
#include "widgets/gimpactiongroup.h"
#include "widgets/gimpclipboard.h"
#include "widgets/gimpdialogfactory.h"
#include "widgets/gimpexportdialog.h"
#include "widgets/gimpfiledialog.h"
#include "widgets/gimphelp-ids.h"
#include "widgets/gimpmessagebox.h"
#include "widgets/gimpmessagedialog.h"
#include "widgets/gimpopendialog.h"
#include "widgets/gimpsavedialog.h"
#include "widgets/gimpwidgets-utils.h"
#include "display/gimpdisplay.h"
#include "display/gimpdisplay-foreach.h"
#include "dialogs/dialogs.h"
#include "dialogs/file-save-dialog.h"
#include "actions.h"
#include "file-commands.h"
#include "gimp-intl.h"
/* local function prototypes */
static void file_open_dialog_show (Gimp *gimp,
GtkWidget *parent,
const gchar *title,
GimpImage *image,
GFile *file,
gboolean open_as_layers);
static GtkWidget * file_save_dialog_show (Gimp *gimp,
GimpImage *image,
GtkWidget *parent,
const gchar *title,
gboolean save_a_copy,
gboolean close_after_saving,
GimpDisplay *display);
static GtkWidget * file_export_dialog_show (Gimp *gimp,
GimpImage *image,
GtkWidget *parent,
GimpDisplay *display);
static void file_save_dialog_response (GtkWidget *dialog,
gint response_id,
gpointer data);
static void file_export_dialog_response (GtkWidget *dialog,
gint response_id,
gpointer data);
static void file_new_template_callback (GtkWidget *widget,
const gchar *name,
gpointer data);
static void file_revert_confirm_response (GtkWidget *dialog,
gint response_id,
GimpDisplay *display);
/* public functions */
void
file_open_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
Gimp *gimp;
GtkWidget *widget;
GimpImage *image;
return_if_no_gimp (gimp, data);
return_if_no_widget (widget, data);
image = action_data_get_image (data);
file_open_dialog_show (gimp, widget,
_("Open Image"),
image, NULL, FALSE);
}
void
file_open_as_layers_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
Gimp *gimp;
GtkWidget *widget;
GimpDisplay *display;
GimpImage *image = NULL;
return_if_no_gimp (gimp, data);
return_if_no_widget (widget, data);
display = action_data_get_display (data);
if (display)
image = gimp_display_get_image (display);
file_open_dialog_show (gimp, widget,
_("Open Image as Layers"),
image, NULL, TRUE);
}
void
file_open_location_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
GtkWidget *widget;
return_if_no_widget (widget, data);
gimp_dialog_factory_dialog_new (gimp_dialog_factory_get_singleton (),
gimp_widget_get_monitor (widget),
NULL /*ui_manager*/,
widget,
"gimp-file-open-location-dialog", -1, TRUE);
}
void
file_open_recent_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
Gimp *gimp;
GimpImagefile *imagefile;
gint index;
gint num_entries;
return_if_no_gimp (gimp, data);
index = g_variant_get_int32 (value);
num_entries = gimp_container_get_n_children (gimp->documents);
if (index >= num_entries)
return;
imagefile = (GimpImagefile *)
gimp_container_get_child_by_index (gimp->documents, index);
if (imagefile)
{
GFile *file;
GimpDisplay *display;
GtkWidget *widget;
GimpProgress *progress;
GimpImage *image;
GimpPDBStatusType status;
GError *error = NULL;
return_if_no_display (display, data);
return_if_no_widget (widget, data);
g_object_ref (display);
g_object_ref (imagefile);
file = gimp_imagefile_get_file (imagefile);
progress = gimp_display_get_image (display) ?
NULL : GIMP_PROGRESS (display);
image = file_open_with_display (gimp, action_data_get_context (data),
progress,
file, FALSE,
G_OBJECT (gimp_widget_get_monitor (widget)),
&status, &error);
if (! image && status != GIMP_PDB_CANCEL)
{
gimp_message (gimp, G_OBJECT (display), GIMP_MESSAGE_ERROR,
_("Opening '%s' failed:\n\n%s"),
gimp_file_get_utf8_name (file), error->message);
g_clear_error (&error);
}
g_object_unref (imagefile);
g_object_unref (display);
}
}
void
file_save_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
Gimp *gimp;
GimpDisplay *display;
GimpImage *image;
GList *drawables;
GtkWidget *widget;
GimpSaveMode save_mode;
GFile *file = NULL;
gboolean saved = FALSE;
return_if_no_gimp (gimp, data);
return_if_no_display (display, data);
return_if_no_widget (widget, data);
image = gimp_display_get_image (display);
save_mode = (GimpSaveMode) g_variant_get_int32 (value);
drawables = gimp_image_get_selected_drawables (image);
if (! drawables)
{
g_list_free (drawables);
return;
}
g_list_free (drawables);
file = gimp_image_get_file (image);
switch (save_mode)
{
case GIMP_SAVE_MODE_SAVE:
case GIMP_SAVE_MODE_SAVE_AND_CLOSE:
/* Only save if the image has been modified, or if it is new. */
if ((gimp_image_is_dirty (image) ||
! GIMP_GUI_CONFIG (image->gimp->config)->trust_dirty_flag) ||
file == NULL)
{
GimpPlugInProcedure *save_proc = gimp_image_get_save_proc (image);
gboolean valid_file = FALSE;
if (file)
{
gchar *uri = g_file_get_uri (file);
/* Non-valid URI (such as "Untitled.xcd" without a scheme) are
* considered non-native by GLib and will trigger remote file code
* path in file_save_dialog_save_image(), eventually failing with
* a weird error. When we encounter such non-valid URI, we just
* consider that the file was entered manually with a bogus name
* (possibly by some script or plug-in) and we fall through
* directly to showing the file dialog. The file name will still
* be useful as default file name.
*/
valid_file = g_uri_is_valid (uri, G_URI_FLAGS_NONE, NULL);
g_free (uri);
}
if (valid_file && ! save_proc)
{
save_proc =
gimp_plug_in_manager_file_procedure_find (image->gimp->plug_in_manager,
GIMP_FILE_PROCEDURE_GROUP_SAVE,
file, NULL);
}
if (valid_file && save_proc)
{
saved = file_save_dialog_save_image (GIMP_PROGRESS (display),
gimp, image, file,
save_proc,
GIMP_RUN_WITH_LAST_VALS,
TRUE, FALSE, FALSE,
gimp_image_get_xcf_compression (image),
TRUE);
break;
}
/* fall thru */
}
else
{
gimp_message_literal (image->gimp,
G_OBJECT (display), GIMP_MESSAGE_INFO,
_("No changes need to be saved"));
saved = TRUE;
break;
}
case GIMP_SAVE_MODE_SAVE_AS:
file_save_dialog_show (gimp, image, widget,
_("Save Image"), FALSE,
save_mode == GIMP_SAVE_MODE_SAVE_AND_CLOSE, display);
break;
case GIMP_SAVE_MODE_SAVE_A_COPY:
file_save_dialog_show (gimp, image, widget,
_("Save a Copy of the Image"), TRUE,
FALSE, display);
break;
case GIMP_SAVE_MODE_EXPORT_AS:
file_export_dialog_show (gimp, image, widget, display);
break;
case GIMP_SAVE_MODE_EXPORT:
case GIMP_SAVE_MODE_OVERWRITE:
{
GFile *file = NULL;
GimpPlugInProcedure *export_proc = NULL;
gboolean overwrite = FALSE;
if (save_mode == GIMP_SAVE_MODE_EXPORT)
{
file = gimp_image_get_exported_file (image);
export_proc = gimp_image_get_export_proc (image);
if (! file)
{
/* Behave as if Export As... was invoked */
file_export_dialog_show (gimp, image, widget, display);
break;
}
overwrite = FALSE;
}
else if (save_mode == GIMP_SAVE_MODE_OVERWRITE)
{
file = gimp_image_get_imported_file (image);
overwrite = TRUE;
}
if (file && ! export_proc)
{
export_proc =
gimp_plug_in_manager_file_procedure_find (image->gimp->plug_in_manager,
GIMP_FILE_PROCEDURE_GROUP_EXPORT,
file, NULL);
}
if (file && export_proc)
{
saved = file_save_dialog_save_image (GIMP_PROGRESS (display),
gimp, image, file,
export_proc,
GIMP_RUN_WITH_LAST_VALS,
FALSE,
overwrite, ! overwrite,
FALSE, TRUE);
}
}
break;
}
if (save_mode == GIMP_SAVE_MODE_SAVE_AND_CLOSE &&
saved &&
! gimp_image_is_dirty (image))
{
gimp_display_close (display);
}
}
void
file_create_template_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
GimpDisplay *display;
GimpImage *image;
GtkWidget *dialog;
return_if_no_display (display, data);
image = gimp_display_get_image (display);
dialog = gimp_query_string_box (_("Create New Template"),
GTK_WIDGET (gimp_display_get_shell (display)),
gimp_standard_help_func,
GIMP_HELP_FILE_CREATE_TEMPLATE,
_("Enter a name for this template"),
NULL,
G_OBJECT (image), "disconnect",
file_new_template_callback,
image, NULL);
gtk_widget_show (dialog);
}
void
file_revert_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
GimpDisplay *display;
GimpImage *image;
GtkWidget *dialog;
GFile *file;
return_if_no_display (display, data);
image = gimp_display_get_image (display);
file = gimp_image_get_file (image);
if (! file)
file = gimp_image_get_imported_file (image);
if (! file)
{
gimp_message_literal (image->gimp,
G_OBJECT (display), GIMP_MESSAGE_ERROR,
_("Revert failed. "
"No file name associated with this image."));
return;
}
#define REVERT_DIALOG_KEY "gimp-revert-confirm-dialog"
dialog = dialogs_get_dialog (G_OBJECT (image), REVERT_DIALOG_KEY);
if (! dialog)
{
dialog =
gimp_message_dialog_new (_("Revert Image"), GIMP_ICON_DOCUMENT_REVERT,
GTK_WIDGET (gimp_display_get_shell (display)),
0,
gimp_standard_help_func, GIMP_HELP_FILE_REVERT,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Revert"), GTK_RESPONSE_OK,
NULL);
gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
g_signal_connect_object (display, "disconnect",
G_CALLBACK (gtk_widget_destroy),
dialog, G_CONNECT_SWAPPED);
g_signal_connect (dialog, "response",
G_CALLBACK (file_revert_confirm_response),
display);
gimp_message_box_set_primary_text (GIMP_MESSAGE_DIALOG (dialog)->box,
_("Revert '%s' to '%s'?"),
gimp_image_get_display_name (image),
gimp_file_get_utf8_name (file));
gimp_message_box_set_text (GIMP_MESSAGE_DIALOG (dialog)->box,
_("By reverting the image to the state saved "
"on disk, you will lose all changes, "
"including all undo information."));
dialogs_attach_dialog (G_OBJECT (image), REVERT_DIALOG_KEY, dialog);
}
gtk_window_present (GTK_WINDOW (dialog));
}
void
file_close_all_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
Gimp *gimp;
return_if_no_gimp (gimp, data);
if (! gimp_displays_dirty (gimp))
{
gimp_displays_close (gimp);
}
else
{
GtkWidget *widget;
return_if_no_widget (widget, data);
gimp_dialog_factory_dialog_raise (gimp_dialog_factory_get_singleton (),
gimp_widget_get_monitor (widget),
widget,
"gimp-close-all-dialog", -1);
}
}
void
file_copy_location_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
Gimp *gimp;
GimpDisplay *display;
GimpImage *image;
GFile *file;
return_if_no_gimp (gimp, data);
return_if_no_display (display, data);
image = gimp_display_get_image (display);
file = gimp_image_get_any_file (image);
if (file)
{
gchar *uri = g_file_get_uri (file);
gimp_clipboard_set_text (gimp, uri);
g_free (uri);
}
}
void
file_show_in_file_manager_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
Gimp *gimp;
GimpDisplay *display;
GimpImage *image;
GFile *file;
return_if_no_gimp (gimp, data);
return_if_no_display (display, data);
image = gimp_display_get_image (display);
file = gimp_image_get_any_file (image);
if (file)
{
GError *error = NULL;
if (! gimp_file_show_in_file_manager (file, &error))
{
gimp_message (gimp, G_OBJECT (display), GIMP_MESSAGE_ERROR,
_("Can't show file in file manager: %s"),
error->message);
g_clear_error (&error);
}
}
}
void
file_quit_cmd_callback (GimpAction *action,
GVariant *value,
gpointer data)
{
Gimp *gimp;
return_if_no_gimp (gimp, data);
gimp_exit (gimp, FALSE);
}
void
file_file_open_dialog (Gimp *gimp,
GFile *file,
GtkWidget *parent)
{
file_open_dialog_show (gimp, parent,
_("Open Image"),
NULL, file, FALSE);
}
/* private functions */
static void
file_open_dialog_show (Gimp *gimp,
GtkWidget *parent,
const gchar *title,
GimpImage *image,
GFile *file,
gboolean open_as_layers)
{
GtkWidget *dialog;
dialog = gimp_dialog_factory_dialog_new (gimp_dialog_factory_get_singleton (),
gimp_widget_get_monitor (parent),
NULL /*ui_manager*/,
parent,
"gimp-file-open-dialog", -1, FALSE);
if (dialog)
{
if (! file && image)
file = gimp_image_get_file (image);
if (! file)
file = g_object_get_data (G_OBJECT (gimp),
GIMP_FILE_OPEN_LAST_FILE_KEY);
if (file)
{
gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), file, NULL);
}
else if (gimp->default_folder)
{
gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog),
gimp->default_folder, NULL);
}
gtk_window_set_title (GTK_WINDOW (dialog), title);
gimp_open_dialog_set_image (GIMP_OPEN_DIALOG (dialog),
image, open_as_layers);
gtk_window_set_transient_for (GTK_WINDOW (dialog),
GTK_WINDOW (gtk_widget_get_toplevel (parent)));
gtk_window_present (GTK_WINDOW (dialog));
}
}
static GtkWidget *
file_save_dialog_show (Gimp *gimp,
GimpImage *image,
GtkWidget *parent,
const gchar *title,
gboolean save_a_copy,
gboolean close_after_saving,
GimpDisplay *display)
{
GtkWidget *dialog;
#define SAVE_DIALOG_KEY "gimp-file-save-dialog"
dialog = dialogs_get_dialog (G_OBJECT (image), SAVE_DIALOG_KEY);
if (! dialog)
{
dialog = gimp_dialog_factory_dialog_new (gimp_dialog_factory_get_singleton (),
gimp_widget_get_monitor (parent),
NULL /*ui_manager*/,
parent,
"gimp-file-save-dialog",
-1, FALSE);
if (dialog)
{
gtk_window_set_transient_for (GTK_WINDOW (dialog),
GTK_WINDOW (gtk_widget_get_toplevel (parent)));
dialogs_attach_dialog (G_OBJECT (image), SAVE_DIALOG_KEY, dialog);
g_signal_connect_object (image, "disconnect",
G_CALLBACK (gtk_widget_destroy),
dialog, G_CONNECT_SWAPPED);
g_signal_connect (dialog, "response",
G_CALLBACK (file_save_dialog_response),
image);
}
}
if (dialog)
{
gtk_window_set_title (GTK_WINDOW (dialog), title);
gimp_save_dialog_set_image (GIMP_SAVE_DIALOG (dialog),
image, save_a_copy,
close_after_saving, GIMP_OBJECT (display));
gtk_window_present (GTK_WINDOW (dialog));
}
return dialog;
}
static void
file_save_dialog_response (GtkWidget *dialog,
gint response_id,
gpointer data)
{
if (response_id == FILE_SAVE_RESPONSE_OTHER_DIALOG)
{
GimpFileDialog *file_dialog = GIMP_FILE_DIALOG (dialog);
GtkWindow *parent;
GtkWidget *other;
GFile *file;
gchar *folder;
gchar *basename;
parent = gtk_window_get_transient_for (GTK_WINDOW (dialog));
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
folder = g_path_get_dirname (gimp_file_get_utf8_name (file));
basename = g_path_get_basename (gimp_file_get_utf8_name (file));
g_object_unref (file);
other = file_export_dialog_show (GIMP_FILE_DIALOG (file_dialog)->image->gimp,
GIMP_FILE_DIALOG (file_dialog)->image,
GTK_WIDGET (parent), NULL);
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (other), folder);
gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (other), basename);
g_free (folder);
g_free (basename);
}
}
static GtkWidget *
file_export_dialog_show (Gimp *gimp,
GimpImage *image,
GtkWidget *parent,
GimpDisplay *display)
{
GtkWidget *dialog;
#define EXPORT_DIALOG_KEY "gimp-file-export-dialog"
dialog = dialogs_get_dialog (G_OBJECT (image), EXPORT_DIALOG_KEY);
if (! dialog)
{
dialog = gimp_dialog_factory_dialog_new (gimp_dialog_factory_get_singleton (),
gimp_widget_get_monitor (parent),
NULL /*ui_manager*/,
parent,
"gimp-file-export-dialog",
-1, FALSE);
if (dialog)
{
gtk_window_set_transient_for (GTK_WINDOW (dialog),
GTK_WINDOW (gtk_widget_get_toplevel (parent)));
dialogs_attach_dialog (G_OBJECT (image), EXPORT_DIALOG_KEY, dialog);
g_signal_connect_object (image, "disconnect",
G_CALLBACK (gtk_widget_destroy),
dialog, G_CONNECT_SWAPPED);
g_signal_connect (dialog, "response",
G_CALLBACK (file_export_dialog_response),
image);
}
}
if (dialog)
{
gimp_export_dialog_set_image (GIMP_EXPORT_DIALOG (dialog), image,
GIMP_OBJECT (display));
gtk_window_present (GTK_WINDOW (dialog));
}
return dialog;
}
static void
file_export_dialog_response (GtkWidget *dialog,
gint response_id,
gpointer data)
{
if (response_id == FILE_SAVE_RESPONSE_OTHER_DIALOG)
{
GimpFileDialog *file_dialog = GIMP_FILE_DIALOG (dialog);
GtkWindow *parent;
GtkWidget *other;
GFile *file;
gchar *folder;
gchar *basename;
parent = gtk_window_get_transient_for (GTK_WINDOW (dialog));
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
folder = g_path_get_dirname (gimp_file_get_utf8_name (file));
basename = g_path_get_basename (gimp_file_get_utf8_name (file));
g_object_unref (file);
other = file_save_dialog_show (GIMP_FILE_DIALOG (file_dialog)->image->gimp,
GIMP_FILE_DIALOG (file_dialog)->image,
GTK_WIDGET (parent),
_("Save Image"),
FALSE, FALSE, NULL);
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (other), folder);
gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (other), basename);
g_free (folder);
g_free (basename);
}
}
static void
file_new_template_callback (GtkWidget *widget,
const gchar *name,
gpointer data)
{
GimpTemplate *template;
GimpImage *image;
image = (GimpImage *) data;
if (! (name && strlen (name)))
name = _("(Unnamed Template)");
template = gimp_template_new (name);
gimp_template_set_from_image (template, image);
gimp_container_add (image->gimp->templates, GIMP_OBJECT (template));
g_object_unref (template);
}
static void
file_revert_confirm_response (GtkWidget *dialog,
gint response_id,
GimpDisplay *display)
{
GimpImage *old_image = gimp_display_get_image (display);
gtk_widget_destroy (dialog);
if (response_id == GTK_RESPONSE_OK)
{
Gimp *gimp = old_image->gimp;
GimpImage *new_image;
GFile *file;
GimpPDBStatusType status;
GError *error = NULL;
file = gimp_image_get_file (old_image);
if (! file)
file = gimp_image_get_imported_file (old_image);
new_image = file_open_image (gimp, gimp_get_user_context (gimp),
GIMP_PROGRESS (display),
file, FALSE, NULL,
GIMP_RUN_INTERACTIVE,
&status, NULL, &error);
if (new_image)
{
gimp_displays_reconnect (gimp, old_image, new_image);
gimp_image_flush (new_image);
/* the displays own the image now */
g_object_unref (new_image);
}
else if (status != GIMP_PDB_CANCEL)
{
gimp_message (gimp, G_OBJECT (display), GIMP_MESSAGE_ERROR,
_("Reverting to '%s' failed:\n\n%s"),
gimp_file_get_utf8_name (file), error->message);
g_clear_error (&error);
}
}
}