summaryrefslogtreecommitdiffstats
path: root/app/dialogs/quit-dialog.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/dialogs/quit-dialog.c')
-rw-r--r--app/dialogs/quit-dialog.c614
1 files changed, 614 insertions, 0 deletions
diff --git a/app/dialogs/quit-dialog.c b/app/dialogs/quit-dialog.c
new file mode 100644
index 0000000..11737f9
--- /dev/null
+++ b/app/dialogs/quit-dialog.c
@@ -0,0 +1,614 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * Copyright (C) 2004 Sven Neumann <sven@gimp.org>
+ *
+ * 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 <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "dialogs-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplay-foreach.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpimagewindow.h"
+
+#include "widgets/gimpcellrendererbutton.h"
+#include "widgets/gimpcontainertreestore.h"
+#include "widgets/gimpcontainertreeview.h"
+#include "widgets/gimpcontainerview.h"
+#include "widgets/gimpdnd.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpmessagebox.h"
+#include "widgets/gimpmessagedialog.h"
+#include "widgets/gimpuimanager.h"
+#include "widgets/gimpviewrenderer.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "quit-dialog.h"
+
+#include "gimp-intl.h"
+
+
+typedef struct _QuitDialog QuitDialog;
+
+struct _QuitDialog
+{
+ Gimp *gimp;
+ GimpContainer *images;
+ GimpContext *context;
+
+ gboolean do_quit;
+
+ GtkWidget *dialog;
+ GimpContainerTreeView *tree_view;
+ GtkTreeViewColumn *save_column;
+ GtkWidget *ok_button;
+ GimpMessageBox *box;
+ GtkWidget *lost_label;
+ GtkWidget *hint_label;
+
+ guint accel_key;
+ GdkModifierType accel_mods;
+};
+
+
+static GtkWidget * quit_close_all_dialog_new (Gimp *gimp,
+ gboolean do_quit);
+static void quit_close_all_dialog_free (QuitDialog *private);
+static void quit_close_all_dialog_response (GtkWidget *dialog,
+ gint response_id,
+ QuitDialog *private);
+static void quit_close_all_dialog_accel_marshal (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+static void quit_close_all_dialog_container_changed (GimpContainer *images,
+ GimpObject *image,
+ QuitDialog *private);
+static void quit_close_all_dialog_image_selected (GimpContainerView *view,
+ GimpImage *image,
+ gpointer insert_data,
+ QuitDialog *private);
+static void quit_close_all_dialog_name_cell_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data);
+static void quit_close_all_dialog_save_clicked (GtkCellRenderer *cell,
+ const gchar *path,
+ GdkModifierType state,
+ QuitDialog *private);
+static gboolean quit_close_all_dialog_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip,
+ QuitDialog *private);
+static gboolean quit_close_all_idle (QuitDialog *private);
+
+
+/* public functions */
+
+GtkWidget *
+quit_dialog_new (Gimp *gimp)
+{
+ return quit_close_all_dialog_new (gimp, TRUE);
+}
+
+GtkWidget *
+close_all_dialog_new (Gimp *gimp)
+{
+ return quit_close_all_dialog_new (gimp, FALSE);
+}
+
+
+/* private functions */
+
+static GtkWidget *
+quit_close_all_dialog_new (Gimp *gimp,
+ gboolean do_quit)
+{
+ QuitDialog *private;
+ GtkWidget *view;
+ GimpContainerTreeView *tree_view;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ GtkWidget *dnd_widget;
+ GtkAccelGroup *accel_group;
+ GClosure *closure;
+ gint rows;
+ gint view_size;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ private = g_slice_new0 (QuitDialog);
+
+ private->gimp = gimp;
+ private->do_quit = do_quit;
+ private->images = gimp_displays_get_dirty_images (gimp);
+ private->context = gimp_context_new (gimp, "close-all-dialog",
+ gimp_get_user_context (gimp));
+
+ g_return_val_if_fail (private->images != NULL, NULL);
+
+ private->dialog =
+ gimp_message_dialog_new (do_quit ? _("Quit GIMP") : _("Close All Images"),
+ GIMP_ICON_DIALOG_WARNING,
+ NULL, 0,
+ gimp_standard_help_func,
+ do_quit ?
+ GIMP_HELP_FILE_QUIT : GIMP_HELP_FILE_CLOSE_ALL,
+
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+
+ NULL);
+
+ private->ok_button = gtk_dialog_add_button (GTK_DIALOG (private->dialog),
+ "", GTK_RESPONSE_OK);
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (private->dialog),
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ g_object_weak_ref (G_OBJECT (private->dialog),
+ (GWeakNotify) quit_close_all_dialog_free, private);
+
+ g_signal_connect (private->dialog, "response",
+ G_CALLBACK (quit_close_all_dialog_response),
+ private);
+
+ /* connect <Primary>D to the quit/close button */
+ accel_group = gtk_accel_group_new ();
+ gtk_window_add_accel_group (GTK_WINDOW (private->dialog), accel_group);
+ g_object_unref (accel_group);
+
+ closure = g_closure_new_object (sizeof (GClosure), G_OBJECT (private->dialog));
+ g_closure_set_marshal (closure, quit_close_all_dialog_accel_marshal);
+ gtk_accelerator_parse ("<Primary>D",
+ &private->accel_key, &private->accel_mods);
+ gtk_accel_group_connect (accel_group,
+ private->accel_key, private->accel_mods,
+ 0, closure);
+
+ private->box = GIMP_MESSAGE_DIALOG (private->dialog)->box;
+
+ view_size = gimp->config->layer_preview_size;
+ rows = CLAMP (gimp_container_get_n_children (private->images), 3, 6);
+
+ view = gimp_container_tree_view_new (private->images, private->context,
+ view_size, 1);
+ gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (view),
+ -1,
+ rows * (view_size + 2));
+
+ private->tree_view = tree_view = GIMP_CONTAINER_TREE_VIEW (view);
+
+ gtk_tree_view_column_set_expand (tree_view->main_column, TRUE);
+
+ renderer = gimp_container_tree_view_get_name_cell (tree_view);
+ gtk_tree_view_column_set_cell_data_func (tree_view->main_column,
+ renderer,
+ quit_close_all_dialog_name_cell_func,
+ NULL, NULL);
+
+ private->save_column = column = gtk_tree_view_column_new ();
+ renderer = gimp_cell_renderer_button_new ();
+ g_object_set (renderer,
+ "icon-name", "document-save",
+ NULL);
+ gtk_tree_view_column_pack_end (column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes (column, renderer, NULL);
+
+ gtk_tree_view_append_column (tree_view->view, column);
+ gimp_container_tree_view_add_toggle_cell (tree_view, renderer);
+
+ g_signal_connect (renderer, "clicked",
+ G_CALLBACK (quit_close_all_dialog_save_clicked),
+ private);
+
+ gtk_box_pack_start (GTK_BOX (private->box), view, TRUE, TRUE, 0);
+ gtk_widget_show (view);
+
+ g_signal_connect (view, "select-item",
+ G_CALLBACK (quit_close_all_dialog_image_selected),
+ private);
+
+ dnd_widget = gimp_container_view_get_dnd_widget (GIMP_CONTAINER_VIEW (view));
+ gimp_dnd_xds_source_add (dnd_widget,
+ (GimpDndDragViewableFunc) gimp_dnd_get_drag_data,
+ NULL);
+
+ g_signal_connect (tree_view->view, "query-tooltip",
+ G_CALLBACK (quit_close_all_dialog_query_tooltip),
+ private);
+
+ if (do_quit)
+ private->lost_label = gtk_label_new (_("If you quit GIMP now, "
+ "these changes will be lost."));
+ else
+ private->lost_label = gtk_label_new (_("If you close these images now, "
+ "changes will be lost."));
+ gtk_label_set_xalign (GTK_LABEL (private->lost_label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (private->lost_label), TRUE);
+ gtk_box_pack_start (GTK_BOX (private->box), private->lost_label,
+ FALSE, FALSE, 0);
+ gtk_widget_show (private->lost_label);
+
+ private->hint_label = gtk_label_new (NULL);
+ gtk_label_set_xalign (GTK_LABEL (private->hint_label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (private->hint_label), TRUE);
+ gtk_box_pack_start (GTK_BOX (private->box), private->hint_label,
+ FALSE, FALSE, 0);
+ gtk_widget_show (private->hint_label);
+
+ closure = g_cclosure_new (G_CALLBACK (quit_close_all_dialog_container_changed),
+ private, NULL);
+ g_object_watch_closure (G_OBJECT (private->dialog), closure);
+ g_signal_connect_closure (private->images, "add", closure, FALSE);
+ g_signal_connect_closure (private->images, "remove", closure, FALSE);
+
+ quit_close_all_dialog_container_changed (private->images, NULL,
+ private);
+
+ return private->dialog;
+}
+
+static void
+quit_close_all_dialog_free (QuitDialog *private)
+{
+ g_idle_remove_by_data (private);
+ g_object_unref (private->images);
+ g_object_unref (private->context);
+
+ g_slice_free (QuitDialog, private);
+}
+
+static void
+quit_close_all_dialog_response (GtkWidget *dialog,
+ gint response_id,
+ QuitDialog *private)
+{
+ Gimp *gimp = private->gimp;
+ gboolean do_quit = private->do_quit;
+
+ gtk_widget_destroy (dialog);
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ if (do_quit)
+ gimp_exit (gimp, TRUE);
+ else
+ gimp_displays_close (gimp);
+ }
+}
+
+static void
+quit_close_all_dialog_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_OK);
+
+ /* we handled the accelerator */
+ g_value_set_boolean (return_value, TRUE);
+}
+
+static void
+quit_close_all_dialog_container_changed (GimpContainer *images,
+ GimpObject *image,
+ QuitDialog *private)
+{
+ gint num_images = gimp_container_get_n_children (images);
+ gchar *accel_string;
+ gchar *hint;
+ gchar *markup;
+
+ accel_string = gtk_accelerator_get_label (private->accel_key,
+ private->accel_mods);
+
+ gimp_message_box_set_primary_text (private->box,
+ /* TRANSLATORS: unless your language
+ msgstr[0] applies to 1 only (as
+ in English), replace "one" with %d. */
+ ngettext ("There is one image with "
+ "unsaved changes:",
+ "There are %d images with "
+ "unsaved changes:",
+ num_images), num_images);
+
+ if (num_images == 0)
+ {
+ gtk_widget_hide (private->lost_label);
+
+ if (private->do_quit)
+ hint = g_strdup_printf (_("Press %s to quit."),
+ accel_string);
+ else
+ hint = g_strdup_printf (_("Press %s to close all images."),
+ accel_string);
+
+ g_object_set (private->ok_button,
+ "label", private->do_quit ? _("_Quit") : _("Cl_ose"),
+ "use-stock", TRUE,
+ "image", NULL,
+ NULL);
+
+ gtk_widget_grab_default (private->ok_button);
+
+ /* When no image requires saving anymore, there is no harm in
+ * assuming completing the original quit or close-all action is
+ * the expected end-result.
+ * I don't immediately exit though because of some unfinished
+ * actions provoking warnings. Let's just close as soon as
+ * possible with an idle source.
+ * Also the idle source has another benefit: allowing to change
+ * one's mind and not exist after the last save, for instance by
+ * hitting Esc quickly while the last save is in progress.
+ */
+ g_idle_add ((GSourceFunc) quit_close_all_idle, private);
+ }
+ else
+ {
+ GtkWidget *icon;
+
+ if (private->do_quit)
+ hint = g_strdup_printf (_("Press %s to discard all changes and quit."),
+ accel_string);
+ else
+ hint = g_strdup_printf (_("Press %s to discard all changes and close all images."),
+ accel_string);
+
+ gtk_widget_show (private->lost_label);
+
+ icon = gtk_image_new_from_icon_name ("edit-delete",
+ GTK_ICON_SIZE_BUTTON);
+ g_object_set (private->ok_button,
+ "label", _("_Discard Changes"),
+ "use-stock", FALSE,
+ "image", icon,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (private->dialog),
+ GTK_RESPONSE_CANCEL);
+ }
+
+ markup = g_strdup_printf ("<i><small>%s</small></i>", hint);
+
+ gtk_label_set_markup (GTK_LABEL (private->hint_label), markup);
+
+ g_free (markup);
+ g_free (hint);
+ g_free (accel_string);
+}
+
+static void
+quit_close_all_dialog_image_selected (GimpContainerView *view,
+ GimpImage *image,
+ gpointer insert_data,
+ QuitDialog *private)
+{
+ GList *list;
+
+ for (list = gimp_get_display_iter (private->gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpDisplay *display = list->data;
+
+ if (gimp_display_get_image (display) == image)
+ {
+ gimp_display_shell_present (gimp_display_get_shell (display));
+
+ /* We only want to update the active shell. Give back keyboard
+ * focus to the quit dialog after this.
+ */
+ gtk_window_present (GTK_WINDOW (private->dialog));
+ }
+ }
+}
+
+static void
+quit_close_all_dialog_name_cell_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GimpViewRenderer *renderer;
+ GimpImage *image;
+ gchar *name;
+
+ gtk_tree_model_get (tree_model, iter,
+ GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer,
+ GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, &name,
+ -1);
+
+ image = GIMP_IMAGE (renderer->viewable);
+
+ if (gimp_image_is_export_dirty (image))
+ {
+ g_object_set (cell,
+ "markup", NULL,
+ "text", name,
+ NULL);
+ }
+ else
+ {
+ GFile *file;
+ const gchar *filename;
+ gchar *escaped_name;
+ gchar *escaped_filename;
+ gchar *exported;
+ gchar *markup;
+
+ file = gimp_image_get_exported_file (image);
+ if (! file)
+ file = gimp_image_get_imported_file (image);
+
+ filename = gimp_file_get_utf8_name (file);
+
+ escaped_name = g_markup_escape_text (name, -1);
+ escaped_filename = g_markup_escape_text (filename, -1);
+
+ exported = g_strdup_printf (_("Exported to %s"), escaped_filename);
+ markup = g_strdup_printf ("%s\n<i>%s</i>", escaped_name, exported);
+ g_free (exported);
+
+ g_free (escaped_name);
+ g_free (escaped_filename);
+
+ g_object_set (cell,
+ "text", NULL,
+ "markup", markup,
+ NULL);
+
+ g_free (markup);
+ }
+
+ g_object_unref (renderer);
+ g_free (name);
+}
+
+static void
+quit_close_all_dialog_save_clicked (GtkCellRenderer *cell,
+ const gchar *path_str,
+ GdkModifierType state,
+ QuitDialog *private)
+{
+ GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter (private->tree_view->model, &iter, path))
+ {
+ GimpViewRenderer *renderer;
+ GimpImage *image;
+ GList *list;
+
+ gtk_tree_model_get (private->tree_view->model, &iter,
+ GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer,
+ -1);
+
+ image = GIMP_IMAGE (renderer->viewable);
+ g_object_unref (renderer);
+
+ for (list = gimp_get_display_iter (private->gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpDisplay *display = list->data;
+
+ if (gimp_display_get_image (display) == image)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+
+ if (window)
+ {
+ GimpUIManager *manager;
+
+ manager = gimp_image_window_get_ui_manager (window);
+
+ gimp_display_shell_present (shell);
+ /* Make sure the quit dialog kept keyboard focus when
+ * the save dialog will exit. */
+ gtk_window_present (GTK_WINDOW (private->dialog));
+
+ if (state & GDK_SHIFT_MASK)
+ {
+ gimp_ui_manager_activate_action (manager, "file",
+ "file-save-as");
+ }
+ else
+ {
+ gimp_ui_manager_activate_action (manager, "file",
+ "file-save");
+ }
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+static gboolean
+quit_close_all_dialog_query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip,
+ QuitDialog *private)
+{
+ GtkTreePath *path;
+ gboolean show_tip = FALSE;
+
+ if (gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (widget), &x, &y,
+ keyboard_tip,
+ NULL, &path, NULL))
+ {
+ GtkTreeViewColumn *column = NULL;
+
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), x, y,
+ NULL, &column, NULL, NULL);
+
+ if (column == private->save_column)
+ {
+ gchar *tip = g_strconcat (_("Save this image"), "\n<b>",
+ gimp_get_mod_string (GDK_SHIFT_MASK),
+ "</b> ", _("Save as"),
+ NULL);
+
+ gtk_tooltip_set_markup (tooltip, tip);
+ gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (widget), tooltip, path);
+
+ g_free (tip);
+
+ show_tip = TRUE;
+ }
+
+ gtk_tree_path_free (path);
+ }
+
+ return show_tip;
+}
+
+static gboolean
+quit_close_all_idle (QuitDialog *private)
+{
+ gtk_dialog_response (GTK_DIALOG (private->dialog), GTK_RESPONSE_OK);
+
+ return FALSE;
+}