summaryrefslogtreecommitdiffstats
path: root/app/display/gimpdisplayshell-close.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/display/gimpdisplayshell-close.c')
-rw-r--r--app/display/gimpdisplayshell-close.c447
1 files changed, 447 insertions, 0 deletions
diff --git a/app/display/gimpdisplayshell-close.c b/app/display/gimpdisplayshell-close.c
new file mode 100644
index 0000000..b7d7c16
--- /dev/null
+++ b/app/display/gimpdisplayshell-close.c
@@ -0,0 +1,447 @@
+/* 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 <time.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpmessagebox.h"
+#include "widgets/gimpmessagedialog.h"
+#include "widgets/gimpuimanager.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-close.h"
+#include "gimpimagewindow.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_close_dialog (GimpDisplayShell *shell,
+ GimpImage *image);
+static void gimp_display_shell_close_name_changed (GimpImage *image,
+ GimpMessageBox *box);
+static void gimp_display_shell_close_exported (GimpImage *image,
+ GFile *file,
+ GimpMessageBox *box);
+static gboolean gimp_display_shell_close_time_changed (GimpMessageBox *box);
+static void gimp_display_shell_close_response (GtkWidget *widget,
+ gboolean close,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_close_accel_marshal(GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+static void gimp_time_since (gint64 then,
+ gint *hours,
+ gint *minutes);
+
+
+/* public functions */
+
+void
+gimp_display_shell_close (GimpDisplayShell *shell,
+ gboolean kill_it)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ image = gimp_display_get_image (shell->display);
+
+ /* FIXME: gimp_busy HACK not really appropriate here because we only
+ * want to prevent the busy image and display to be closed. --Mitch
+ */
+ if (shell->display->gimp->busy)
+ return;
+
+ /* If the image has been modified, give the user a chance to save
+ * it before nuking it--this only applies if its the last view
+ * to an image canvas. (a image with disp_count = 1)
+ */
+ if (! kill_it &&
+ image &&
+ gimp_image_get_display_count (image) == 1 &&
+ gimp_image_is_dirty (image))
+ {
+ /* If there's a save dialog active for this image, then raise it.
+ * (see bug #511965)
+ */
+ GtkWidget *dialog = g_object_get_data (G_OBJECT (image),
+ "gimp-file-save-dialog");
+ if (dialog)
+ {
+ gtk_window_present (GTK_WINDOW (dialog));
+ }
+ else
+ {
+ gimp_display_shell_close_dialog (shell, image);
+ }
+ }
+ else if (image)
+ {
+ gimp_display_close (shell->display);
+ }
+ else
+ {
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+
+ if (window)
+ {
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+
+ /* Activate the action instead of simply calling gimp_exit(), so
+ * the quit action's sensitivity is taken into account.
+ */
+ gimp_ui_manager_activate_action (manager, "file", "file-quit");
+ }
+ }
+}
+
+
+/* private functions */
+
+#define RESPONSE_SAVE 1
+
+
+static void
+gimp_display_shell_close_dialog (GimpDisplayShell *shell,
+ GimpImage *image)
+{
+ GtkWidget *dialog;
+ GimpMessageBox *box;
+ GtkWidget *label;
+ GtkAccelGroup *accel_group;
+ GClosure *closure;
+ GSource *source;
+ guint accel_key;
+ GdkModifierType accel_mods;
+ gchar *title;
+ gchar *accel_string;
+ gchar *hint;
+ gchar *markup;
+ GFile *file;
+
+ if (shell->close_dialog)
+ {
+ gtk_window_present (GTK_WINDOW (shell->close_dialog));
+ return;
+ }
+
+ file = gimp_image_get_file (image);
+
+ title = g_strdup_printf (_("Close %s"), gimp_image_get_display_name (image));
+
+ shell->close_dialog =
+ dialog = gimp_message_dialog_new (title, GIMP_ICON_DOCUMENT_SAVE,
+ GTK_WIDGET (shell),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ gimp_standard_help_func, NULL,
+
+ file ?
+ _("_Save") :
+ _("Save _As"), RESPONSE_SAVE,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Discard Changes"), GTK_RESPONSE_CLOSE,
+ NULL);
+
+ g_free (title);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+ RESPONSE_SAVE,
+ GTK_RESPONSE_CLOSE,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ g_signal_connect (dialog, "destroy",
+ G_CALLBACK (gtk_widget_destroyed),
+ &shell->close_dialog);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gimp_display_shell_close_response),
+ shell);
+
+ /* connect <Primary>D to the quit/close button */
+ accel_group = gtk_accel_group_new ();
+ gtk_window_add_accel_group (GTK_WINDOW (shell->close_dialog), accel_group);
+ g_object_unref (accel_group);
+
+ closure = g_closure_new_object (sizeof (GClosure),
+ G_OBJECT (shell->close_dialog));
+ g_closure_set_marshal (closure, gimp_display_shell_close_accel_marshal);
+ gtk_accelerator_parse ("<Primary>D", &accel_key, &accel_mods);
+ gtk_accel_group_connect (accel_group, accel_key, accel_mods, 0, closure);
+
+ box = GIMP_MESSAGE_DIALOG (dialog)->box;
+
+ accel_string = gtk_accelerator_get_label (accel_key, accel_mods);
+ hint = g_strdup_printf (_("Press %s to discard all changes and close the image."),
+ accel_string);
+ markup = g_strdup_printf ("<i><small>%s</small></i>", hint);
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ g_free (markup);
+ g_free (hint);
+ g_free (accel_string);
+
+ g_signal_connect_object (image, "name-changed",
+ G_CALLBACK (gimp_display_shell_close_name_changed),
+ box, 0);
+ g_signal_connect_object (image, "exported",
+ G_CALLBACK (gimp_display_shell_close_exported),
+ box, 0);
+
+ gimp_display_shell_close_name_changed (image, box);
+
+ closure =
+ g_cclosure_new_object (G_CALLBACK (gimp_display_shell_close_time_changed),
+ G_OBJECT (box));
+
+ /* update every 10 seconds */
+ source = g_timeout_source_new_seconds (10);
+ g_source_set_closure (source, closure);
+ g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ /* The dialog is destroyed with the shell, so it should be safe
+ * to hold an image pointer for the lifetime of the dialog.
+ */
+ g_object_set_data (G_OBJECT (box), "gimp-image", image);
+
+ gimp_display_shell_close_time_changed (box);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+gimp_display_shell_close_name_changed (GimpImage *image,
+ GimpMessageBox *box)
+{
+ GtkWidget *window = gtk_widget_get_toplevel (GTK_WIDGET (box));
+
+ if (GTK_IS_WINDOW (window))
+ {
+ gchar *title = g_strdup_printf (_("Close %s"),
+ gimp_image_get_display_name (image));
+
+ gtk_window_set_title (GTK_WINDOW (window), title);
+ g_free (title);
+ }
+
+ gimp_message_box_set_primary_text (box,
+ _("Save the changes to image '%s' "
+ "before closing?"),
+ gimp_image_get_display_name (image));
+}
+
+static void
+gimp_display_shell_close_exported (GimpImage *image,
+ GFile *file,
+ GimpMessageBox *box)
+{
+ gimp_display_shell_close_time_changed (box);
+}
+
+static gboolean
+gimp_display_shell_close_time_changed (GimpMessageBox *box)
+{
+ GimpImage *image = g_object_get_data (G_OBJECT (box), "gimp-image");
+ gint64 dirty_time = gimp_image_get_dirty_time (image);
+ gchar *time_text = NULL;
+ gchar *export_text = NULL;
+
+ if (dirty_time)
+ {
+ gint hours = 0;
+ gint minutes = 0;
+
+ gimp_time_since (dirty_time, &hours, &minutes);
+
+ if (hours > 0)
+ {
+ if (hours > 1 || minutes == 0)
+ {
+ time_text =
+ g_strdup_printf (ngettext ("If you don't save the image, "
+ "changes from the last hour "
+ "will be lost.",
+ "If you don't save the image, "
+ "changes from the last %d "
+ "hours will be lost.",
+ hours), hours);
+ }
+ else
+ {
+ time_text =
+ g_strdup_printf (ngettext ("If you don't save the image, "
+ "changes from the last hour "
+ "and %d minute will be lost.",
+ "If you don't save the image, "
+ "changes from the last hour "
+ "and %d minutes will be lost.",
+ minutes), minutes);
+ }
+ }
+ else
+ {
+ time_text =
+ g_strdup_printf (ngettext ("If you don't save the image, "
+ "changes from the last minute "
+ "will be lost.",
+ "If you don't save the image, "
+ "changes from the last %d "
+ "minutes will be lost.",
+ minutes), minutes);
+ }
+ }
+
+ if (! gimp_image_is_export_dirty (image))
+ {
+ GFile *file;
+
+ file = gimp_image_get_exported_file (image);
+ if (! file)
+ file = gimp_image_get_imported_file (image);
+
+ export_text = g_strdup_printf (_("The image has been exported to '%s'."),
+ gimp_file_get_utf8_name (file));
+ }
+
+ if (time_text && export_text)
+ gimp_message_box_set_text (box, "%s\n\n%s", time_text, export_text);
+ else if (time_text || export_text)
+ gimp_message_box_set_text (box, "%s", time_text ? time_text : export_text);
+ else
+ gimp_message_box_set_text (box, "%s", time_text);
+
+ g_free (time_text);
+ g_free (export_text);
+
+ return TRUE;
+}
+
+static void
+gimp_display_shell_close_response (GtkWidget *widget,
+ gint response_id,
+ GimpDisplayShell *shell)
+{
+ gtk_widget_destroy (widget);
+
+ switch (response_id)
+ {
+ case GTK_RESPONSE_CLOSE:
+ gimp_display_close (shell->display);
+ break;
+
+ case RESPONSE_SAVE:
+ {
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+
+ if (window)
+ {
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+
+ gimp_image_window_set_active_shell (window, shell);
+
+ gimp_ui_manager_activate_action (manager,
+ "file", "file-save-and-close");
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+gimp_display_shell_close_accel_marshal (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data)
+{
+ gtk_dialog_response (GTK_DIALOG (closure->data), GTK_RESPONSE_CLOSE);
+
+ /* we handled the accelerator */
+ g_value_set_boolean (return_value, TRUE);
+}
+
+static void
+gimp_time_since (gint64 then,
+ gint *hours,
+ gint *minutes)
+{
+ gint64 now = time (NULL);
+ gint64 diff = 1 + now - then;
+
+ g_return_if_fail (now >= then);
+
+ /* first round up to the nearest minute */
+ diff = (diff + 59) / 60;
+
+ /* then optionally round minutes to multiples of 5 or 10 */
+ if (diff > 50)
+ diff = ((diff + 8) / 10) * 10;
+ else if (diff > 20)
+ diff = ((diff + 3) / 5) * 5;
+
+ /* determine full hours */
+ if (diff >= 60)
+ {
+ *hours = diff / 60;
+ diff = (diff % 60);
+ }
+
+ /* round up to full hours for 2 and more */
+ if (*hours > 1 && diff > 0)
+ {
+ *hours += 1;
+ diff = 0;
+ }
+
+ *minutes = diff;
+}