/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 . */ #include "config.h" #include #include #include #include "libpikabase/pikabase.h" #include "libpikawidgets/pikawidgets.h" #include "actions-types.h" #include "config/pikaguiconfig.h" #include "core/pika.h" #include "core/pikacontainer.h" #include "core/pikaimage.h" #include "core/pikaimagefile.h" #include "core/pikaprogress.h" #include "core/pikatemplate.h" #include "plug-in/pikapluginmanager-file.h" #include "file/file-open.h" #include "file/file-save.h" #include "file/pika-file.h" #include "widgets/pikaactiongroup.h" #include "widgets/pikaclipboard.h" #include "widgets/pikadialogfactory.h" #include "widgets/pikaexportdialog.h" #include "widgets/pikafiledialog.h" #include "widgets/pikahelp-ids.h" #include "widgets/pikamessagebox.h" #include "widgets/pikamessagedialog.h" #include "widgets/pikaopendialog.h" #include "widgets/pikasavedialog.h" #include "widgets/pikawidgets-utils.h" #include "display/pikadisplay.h" #include "display/pikadisplay-foreach.h" #include "dialogs/dialogs.h" #include "dialogs/file-save-dialog.h" #include "actions.h" #include "file-commands.h" #include "pika-intl.h" /* local function prototypes */ static void file_open_dialog_show (Pika *pika, GtkWidget *parent, const gchar *title, PikaImage *image, GFile *file, gboolean open_as_layers); static GtkWidget * file_save_dialog_show (Pika *pika, PikaImage *image, GtkWidget *parent, const gchar *title, gboolean save_a_copy, gboolean close_after_saving, PikaDisplay *display); static GtkWidget * file_export_dialog_show (Pika *pika, PikaImage *image, GtkWidget *parent, PikaDisplay *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, PikaDisplay *display); /* public functions */ void file_open_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { Pika *pika; GtkWidget *widget; PikaImage *image; return_if_no_pika (pika, data); return_if_no_widget (widget, data); image = action_data_get_image (data); file_open_dialog_show (pika, widget, _("Open Image"), image, NULL, FALSE); } void file_open_as_layers_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { Pika *pika; GtkWidget *widget; PikaDisplay *display; PikaImage *image = NULL; return_if_no_pika (pika, data); return_if_no_widget (widget, data); display = action_data_get_display (data); if (display) image = pika_display_get_image (display); file_open_dialog_show (pika, widget, _("Open Image as Layers"), image, NULL, TRUE); } void file_open_location_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { GtkWidget *widget; return_if_no_widget (widget, data); pika_dialog_factory_dialog_new (pika_dialog_factory_get_singleton (), pika_widget_get_monitor (widget), NULL /*ui_manager*/, widget, "pika-file-open-location-dialog", -1, TRUE); } void file_open_recent_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { Pika *pika; PikaImagefile *imagefile; gint index; gint num_entries; return_if_no_pika (pika, data); index = g_variant_get_int32 (value); num_entries = pika_container_get_n_children (pika->documents); if (index >= num_entries) return; imagefile = (PikaImagefile *) pika_container_get_child_by_index (pika->documents, index); if (imagefile) { GFile *file; PikaDisplay *display; GtkWidget *widget; PikaProgress *progress; PikaImage *image; PikaPDBStatusType 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 = pika_imagefile_get_file (imagefile); progress = pika_display_get_image (display) ? NULL : PIKA_PROGRESS (display); image = file_open_with_display (pika, action_data_get_context (data), progress, file, FALSE, G_OBJECT (pika_widget_get_monitor (widget)), &status, &error); if (! image && status != PIKA_PDB_CANCEL) { pika_message (pika, G_OBJECT (display), PIKA_MESSAGE_ERROR, _("Opening '%s' failed:\n\n%s"), pika_file_get_utf8_name (file), error->message); g_clear_error (&error); } g_object_unref (imagefile); g_object_unref (display); } } void file_save_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { Pika *pika; PikaDisplay *display; PikaImage *image; GList *drawables; GtkWidget *widget; PikaSaveMode save_mode; GFile *file = NULL; gboolean saved = FALSE; return_if_no_pika (pika, data); return_if_no_display (display, data); return_if_no_widget (widget, data); image = pika_display_get_image (display); save_mode = (PikaSaveMode) g_variant_get_int32 (value); drawables = pika_image_get_selected_drawables (image); if (! drawables) { g_list_free (drawables); return; } g_list_free (drawables); file = pika_image_get_file (image); switch (save_mode) { case PIKA_SAVE_MODE_SAVE: case PIKA_SAVE_MODE_SAVE_AND_CLOSE: /* Only save if the image has been modified, or if it is new. */ if ((pika_image_is_dirty (image) || ! PIKA_GUI_CONFIG (image->pika->config)->trust_dirty_flag) || file == NULL) { PikaPlugInProcedure *save_proc = pika_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 = pika_plug_in_manager_file_procedure_find (image->pika->plug_in_manager, PIKA_FILE_PROCEDURE_GROUP_SAVE, file, NULL); } if (valid_file && save_proc) { saved = file_save_dialog_save_image (PIKA_PROGRESS (display), pika, image, file, save_proc, PIKA_RUN_WITH_LAST_VALS, TRUE, FALSE, FALSE, pika_image_get_xcf_compression (image), TRUE); break; } /* fall thru */ } else { pika_message_literal (image->pika, G_OBJECT (display), PIKA_MESSAGE_INFO, _("No changes need to be saved")); saved = TRUE; break; } case PIKA_SAVE_MODE_SAVE_AS: file_save_dialog_show (pika, image, widget, _("Save Image"), FALSE, save_mode == PIKA_SAVE_MODE_SAVE_AND_CLOSE, display); break; case PIKA_SAVE_MODE_SAVE_A_COPY: file_save_dialog_show (pika, image, widget, _("Save a Copy of the Image"), TRUE, FALSE, display); break; case PIKA_SAVE_MODE_EXPORT_AS: file_export_dialog_show (pika, image, widget, display); break; case PIKA_SAVE_MODE_EXPORT: case PIKA_SAVE_MODE_OVERWRITE: { GFile *file = NULL; PikaPlugInProcedure *export_proc = NULL; gboolean overwrite = FALSE; if (save_mode == PIKA_SAVE_MODE_EXPORT) { file = pika_image_get_exported_file (image); export_proc = pika_image_get_export_proc (image); if (! file) { /* Behave as if Export As... was invoked */ file_export_dialog_show (pika, image, widget, display); break; } overwrite = FALSE; } else if (save_mode == PIKA_SAVE_MODE_OVERWRITE) { file = pika_image_get_imported_file (image); overwrite = TRUE; } if (file && ! export_proc) { export_proc = pika_plug_in_manager_file_procedure_find (image->pika->plug_in_manager, PIKA_FILE_PROCEDURE_GROUP_EXPORT, file, NULL); } if (file && export_proc) { saved = file_save_dialog_save_image (PIKA_PROGRESS (display), pika, image, file, export_proc, PIKA_RUN_WITH_LAST_VALS, FALSE, overwrite, ! overwrite, FALSE, TRUE); } } break; } if (save_mode == PIKA_SAVE_MODE_SAVE_AND_CLOSE && saved && ! pika_image_is_dirty (image)) { pika_display_close (display); } } void file_create_template_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaDisplay *display; PikaImage *image; GtkWidget *dialog; return_if_no_display (display, data); image = pika_display_get_image (display); dialog = pika_query_string_box (_("Create New Template"), GTK_WIDGET (pika_display_get_shell (display)), pika_standard_help_func, PIKA_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 (PikaAction *action, GVariant *value, gpointer data) { PikaDisplay *display; PikaImage *image; GtkWidget *dialog; GFile *file; return_if_no_display (display, data); image = pika_display_get_image (display); file = pika_image_get_file (image); if (! file) file = pika_image_get_imported_file (image); if (! file) { pika_message_literal (image->pika, G_OBJECT (display), PIKA_MESSAGE_ERROR, _("Revert failed. " "No file name associated with this image.")); return; } #define REVERT_DIALOG_KEY "pika-revert-confirm-dialog" dialog = dialogs_get_dialog (G_OBJECT (image), REVERT_DIALOG_KEY); if (! dialog) { dialog = pika_message_dialog_new (_("Revert Image"), PIKA_ICON_DOCUMENT_REVERT, GTK_WIDGET (pika_display_get_shell (display)), 0, pika_standard_help_func, PIKA_HELP_FILE_REVERT, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Revert"), GTK_RESPONSE_OK, NULL); pika_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); pika_message_box_set_primary_text (PIKA_MESSAGE_DIALOG (dialog)->box, _("Revert '%s' to '%s'?"), pika_image_get_display_name (image), pika_file_get_utf8_name (file)); pika_message_box_set_text (PIKA_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 (PikaAction *action, GVariant *value, gpointer data) { Pika *pika; return_if_no_pika (pika, data); if (! pika_displays_dirty (pika)) { pika_displays_close (pika); } else { GtkWidget *widget; return_if_no_widget (widget, data); pika_dialog_factory_dialog_raise (pika_dialog_factory_get_singleton (), pika_widget_get_monitor (widget), widget, "pika-close-all-dialog", -1); } } void file_copy_location_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { Pika *pika; PikaDisplay *display; PikaImage *image; GFile *file; return_if_no_pika (pika, data); return_if_no_display (display, data); image = pika_display_get_image (display); file = pika_image_get_any_file (image); if (file) { gchar *uri = g_file_get_uri (file); pika_clipboard_set_text (pika, uri); g_free (uri); } } void file_show_in_file_manager_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { Pika *pika; PikaDisplay *display; PikaImage *image; GFile *file; return_if_no_pika (pika, data); return_if_no_display (display, data); image = pika_display_get_image (display); file = pika_image_get_any_file (image); if (file) { GError *error = NULL; if (! pika_file_show_in_file_manager (file, &error)) { pika_message (pika, G_OBJECT (display), PIKA_MESSAGE_ERROR, _("Can't show file in file manager: %s"), error->message); g_clear_error (&error); } } } void file_quit_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { Pika *pika; return_if_no_pika (pika, data); pika_exit (pika, FALSE); } void file_file_open_dialog (Pika *pika, GFile *file, GtkWidget *parent) { file_open_dialog_show (pika, parent, _("Open Image"), NULL, file, FALSE); } /* private functions */ static void file_open_dialog_show (Pika *pika, GtkWidget *parent, const gchar *title, PikaImage *image, GFile *file, gboolean open_as_layers) { GtkWidget *dialog; dialog = pika_dialog_factory_dialog_new (pika_dialog_factory_get_singleton (), pika_widget_get_monitor (parent), NULL /*ui_manager*/, parent, "pika-file-open-dialog", -1, FALSE); if (dialog) { if (! file && image) file = pika_image_get_file (image); if (! file) file = g_object_get_data (G_OBJECT (pika), PIKA_FILE_OPEN_LAST_FILE_KEY); if (file) { gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), file, NULL); } else if (pika->default_folder) { gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), pika->default_folder, NULL); } gtk_window_set_title (GTK_WINDOW (dialog), title); pika_open_dialog_set_image (PIKA_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 (Pika *pika, PikaImage *image, GtkWidget *parent, const gchar *title, gboolean save_a_copy, gboolean close_after_saving, PikaDisplay *display) { GtkWidget *dialog; #define SAVE_DIALOG_KEY "pika-file-save-dialog" dialog = dialogs_get_dialog (G_OBJECT (image), SAVE_DIALOG_KEY); if (! dialog) { dialog = pika_dialog_factory_dialog_new (pika_dialog_factory_get_singleton (), pika_widget_get_monitor (parent), NULL /*ui_manager*/, parent, "pika-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); pika_save_dialog_set_image (PIKA_SAVE_DIALOG (dialog), image, save_a_copy, close_after_saving, PIKA_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) { PikaFileDialog *file_dialog = PIKA_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 (pika_file_get_utf8_name (file)); basename = g_path_get_basename (pika_file_get_utf8_name (file)); g_object_unref (file); other = file_export_dialog_show (PIKA_FILE_DIALOG (file_dialog)->image->pika, PIKA_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 (Pika *pika, PikaImage *image, GtkWidget *parent, PikaDisplay *display) { GtkWidget *dialog; #define EXPORT_DIALOG_KEY "pika-file-export-dialog" dialog = dialogs_get_dialog (G_OBJECT (image), EXPORT_DIALOG_KEY); if (! dialog) { dialog = pika_dialog_factory_dialog_new (pika_dialog_factory_get_singleton (), pika_widget_get_monitor (parent), NULL /*ui_manager*/, parent, "pika-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) { pika_export_dialog_set_image (PIKA_EXPORT_DIALOG (dialog), image, PIKA_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) { PikaFileDialog *file_dialog = PIKA_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 (pika_file_get_utf8_name (file)); basename = g_path_get_basename (pika_file_get_utf8_name (file)); g_object_unref (file); other = file_save_dialog_show (PIKA_FILE_DIALOG (file_dialog)->image->pika, PIKA_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) { PikaTemplate *template; PikaImage *image; image = (PikaImage *) data; if (! (name && strlen (name))) name = _("(Unnamed Template)"); template = pika_template_new (name); pika_template_set_from_image (template, image); pika_container_add (image->pika->templates, PIKA_OBJECT (template)); g_object_unref (template); } static void file_revert_confirm_response (GtkWidget *dialog, gint response_id, PikaDisplay *display) { PikaImage *old_image = pika_display_get_image (display); gtk_widget_destroy (dialog); if (response_id == GTK_RESPONSE_OK) { Pika *pika = old_image->pika; PikaImage *new_image; GFile *file; PikaPDBStatusType status; GError *error = NULL; file = pika_image_get_file (old_image); if (! file) file = pika_image_get_imported_file (old_image); new_image = file_open_image (pika, pika_get_user_context (pika), PIKA_PROGRESS (display), file, FALSE, NULL, PIKA_RUN_INTERACTIVE, &status, NULL, &error); if (new_image) { pika_displays_reconnect (pika, old_image, new_image); pika_image_flush (new_image); /* the displays own the image now */ g_object_unref (new_image); } else if (status != PIKA_PDB_CANCEL) { pika_message (pika, G_OBJECT (display), PIKA_MESSAGE_ERROR, _("Reverting to '%s' failed:\n\n%s"), pika_file_get_utf8_name (file), error->message); g_clear_error (&error); } } }