PIKApp/app/actions/file-commands.c

869 lines
28 KiB
C

/* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <gegl.h>
#include <gtk/gtk.h>
#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);
}
}
}