1
1
mirror of https://gitlab.gnome.org/GNOME/gimp.git synced 2025-10-06 01:12:40 +02:00
Files
gimp/app/dialogs/quit-dialog.c
Michael Natterer b416994ed0 app: make GimpContainerView behave like a normal widget
The old API to select stuff and its signals was doing unexpected
stuff and was very confusing. Change things to be "normal":

The selection API is now set_selected() and get_selected(), with no
internal data exposed, and set_selected() emitting the expected
signal.

The signals are now "selection-changed" and "item-activated".
"selection-changed" is always emitted in response to changing the
selection, be it via the API, or by changes in the model (the internal
callbacks in from e.g. GimpContext or GimpImage simply call set_selected()
and don't do any unxpected magic).
2025-08-01 10:02:37 +02:00

643 lines
23 KiB
C

/* 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/gimpaction.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/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_images_selected (GimpContainerView *view,
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;
GdkRectangle geometry;
GdkMonitor *monitor;
gint max_rows;
gint scale_factor;
const gfloat rows_per_height = 32 / 1440.0f;
const gint greatest_max_rows = 36;
const gint least_max_rows = 6;
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);
gimp_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;
monitor = gimp_widget_get_monitor (private->dialog);
scale_factor = gdk_monitor_get_scale_factor (monitor);
gdk_monitor_get_geometry (monitor, &geometry);
if (scale_factor > 1)
{
#ifdef GDK_WINDOWING_WIN32
max_rows = (geometry.height * scale_factor * rows_per_height)
/ (scale_factor + 1);
#else
max_rows = (geometry.height * rows_per_height) / (scale_factor + 1);
#endif
}
else
{
max_rows = geometry.height * rows_per_height;
}
max_rows = CLAMP (max_rows, least_max_rows, greatest_max_rows);
view_size = gimp->config->layer_preview_size;
rows = CLAMP (gimp_container_get_n_children (private->images), 3, max_rows);
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, "selection-changed",
G_CALLBACK (quit_close_all_dialog_images_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_viewable,
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_signal_connect_swapped (private->dialog, "destroy", G_CALLBACK (g_closure_invalidate), 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 exit 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 (GIMP_ICON_EDIT_DELETE,
GTK_ICON_SIZE_BUTTON);
g_object_set (private->ok_button,
"label", _("_Discard Changes"),
"use-stock", FALSE,
"image", icon,
NULL);
gtk_style_context_add_class (gtk_widget_get_style_context (private->ok_button),
"text-button");
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_images_selected (GimpContainerView *view,
QuitDialog *private)
{
GimpViewable *image = gimp_container_view_get_1_selected (view);
if (image)
{
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) == GIMP_IMAGE (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)
{
GAction *action;
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)
action = g_action_map_lookup_action (G_ACTION_MAP (private->gimp->app),
"file-save-as");
else
action = g_action_map_lookup_action (G_ACTION_MAP (private->gimp->app),
"file-save");
g_return_if_fail (GIMP_IS_ACTION (action));
gimp_action_activate (GIMP_ACTION (action));
}
break;
}
}
}
gtk_tree_path_free (path);
}
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;
}