summaryrefslogtreecommitdiffstats
path: root/app/widgets/gimpwidgets-utils.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:23:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:23:22 +0000
commite42129241681dde7adae7d20697e7b421682fbb4 (patch)
treeaf1fe815a5e639e68e59fabd8395ec69458b3e5e /app/widgets/gimpwidgets-utils.c
parentInitial commit. (diff)
downloadgimp-e42129241681dde7adae7d20697e7b421682fbb4.tar.xz
gimp-e42129241681dde7adae7d20697e7b421682fbb4.zip
Adding upstream version 2.10.22.upstream/2.10.22upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/widgets/gimpwidgets-utils.c')
-rw-r--r--app/widgets/gimpwidgets-utils.c1964
1 files changed, 1964 insertions, 0 deletions
diff --git a/app/widgets/gimpwidgets-utils.c b/app/widgets/gimpwidgets-utils.c
new file mode 100644
index 0000000..b6750bf
--- /dev/null
+++ b/app/widgets/gimpwidgets-utils.c
@@ -0,0 +1,1964 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpwidgets-utils.c
+ * Copyright (C) 1999-2003 Michael Natterer <mitch@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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#ifdef GDK_WINDOWING_WIN32
+#include <gdk/gdkwin32.h>
+#endif
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#ifdef PLATFORM_OSX
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "widgets-types.h"
+
+#include "gegl/gimp-babl.h"
+
+#include "gimpaction.h"
+#include "gimpdialogfactory.h"
+#include "gimpdock.h"
+#include "gimpdockcontainer.h"
+#include "gimpdockwindow.h"
+#include "gimperrordialog.h"
+#include "gimpsessioninfo.h"
+#include "gimpwidgets-utils.h"
+
+#include "gimp-intl.h"
+
+
+#define GIMP_TOOL_OPTIONS_GUI_KEY "gimp-tool-options-gui"
+#define GIMP_TOOL_OPTIONS_GUI_FUNC_KEY "gimp-tool-options-gui-func"
+
+
+/**
+ * gimp_menu_position:
+ * @menu: a #GtkMenu widget
+ * @x: pointer to horizontal position
+ * @y: pointer to vertical position
+ *
+ * Positions a #GtkMenu so that it pops up on screen. This function
+ * takes care of the preferred popup direction (taken from the widget
+ * render direction) and it handles multiple monitors representing a
+ * single #GdkScreen (Xinerama).
+ *
+ * You should call this function with @x and @y initialized to the
+ * origin of the menu. This is typically the center of the widget the
+ * menu is popped up from. gimp_menu_position() will then decide if
+ * and how these initial values need to be changed.
+ **/
+void
+gimp_menu_position (GtkMenu *menu,
+ gint *x,
+ gint *y)
+{
+ GtkWidget *widget;
+ GdkScreen *screen;
+ GtkRequisition requisition;
+ GdkRectangle rect;
+ gint monitor;
+
+ g_return_if_fail (GTK_IS_MENU (menu));
+ g_return_if_fail (x != NULL);
+ g_return_if_fail (y != NULL);
+
+ widget = GTK_WIDGET (menu);
+
+ screen = gtk_widget_get_screen (widget);
+
+ monitor = gdk_screen_get_monitor_at_point (screen, *x, *y);
+ gdk_screen_get_monitor_workarea (screen, monitor, &rect);
+
+ gtk_menu_set_screen (menu, screen);
+
+ gtk_widget_size_request (widget, &requisition);
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ {
+ *x -= requisition.width;
+ if (*x < rect.x)
+ *x += requisition.width;
+ }
+ else
+ {
+ if (*x + requisition.width > rect.x + rect.width)
+ *x -= requisition.width;
+ }
+
+ if (*x < rect.x)
+ *x = rect.x;
+
+ if (*y + requisition.height > rect.y + rect.height)
+ *y -= requisition.height;
+
+ if (*y < rect.y)
+ *y = rect.y;
+}
+
+/**
+ * gimp_button_menu_position:
+ * @button: a button widget to popup the menu from
+ * @menu: the menu to position
+ * @position: the preferred popup direction for the menu (left or right)
+ * @x: return location for x coordinate
+ * @y: return location for y coordinate
+ *
+ * Utility function to position a menu that pops up from a button.
+ **/
+void
+gimp_button_menu_position (GtkWidget *button,
+ GtkMenu *menu,
+ GtkPositionType position,
+ gint *x,
+ gint *y)
+{
+ GdkScreen *screen;
+ GtkAllocation button_allocation;
+ GtkRequisition menu_requisition;
+ GdkRectangle rect;
+ gint monitor;
+
+ g_return_if_fail (GTK_IS_WIDGET (button));
+ g_return_if_fail (gtk_widget_get_realized (button));
+ g_return_if_fail (GTK_IS_MENU (menu));
+ g_return_if_fail (x != NULL);
+ g_return_if_fail (y != NULL);
+
+ gtk_widget_get_allocation (button, &button_allocation);
+
+ if (gtk_widget_get_direction (button) == GTK_TEXT_DIR_RTL)
+ {
+ switch (position)
+ {
+ case GTK_POS_LEFT: position = GTK_POS_RIGHT; break;
+ case GTK_POS_RIGHT: position = GTK_POS_LEFT; break;
+ default:
+ break;
+ }
+ }
+
+ *x = 0;
+ *y = 0;
+
+ if (! gtk_widget_get_has_window (button))
+ {
+ *x += button_allocation.x;
+ *y += button_allocation.y;
+ }
+
+ gdk_window_get_root_coords (gtk_widget_get_window (button), *x, *y, x, y);
+
+ gtk_widget_size_request (GTK_WIDGET (menu), &menu_requisition);
+
+ screen = gtk_widget_get_screen (button);
+
+ monitor = gdk_screen_get_monitor_at_point (screen, *x, *y);
+ gdk_screen_get_monitor_workarea (screen, monitor, &rect);
+
+ gtk_menu_set_screen (menu, screen);
+
+ switch (position)
+ {
+ case GTK_POS_LEFT:
+ *x -= menu_requisition.width;
+ if (*x < rect.x)
+ *x += menu_requisition.width + button_allocation.width;
+ break;
+
+ case GTK_POS_RIGHT:
+ *x += button_allocation.width;
+ if (*x + menu_requisition.width > rect.x + rect.width)
+ *x -= button_allocation.width + menu_requisition.width;
+ break;
+
+ default:
+ g_warning ("%s: unhandled position (%d)", G_STRFUNC, position);
+ break;
+ }
+
+ if (*y + menu_requisition.height > rect.y + rect.height)
+ *y -= menu_requisition.height - button_allocation.height;
+
+ if (*y < rect.y)
+ *y = rect.y;
+}
+
+void
+gimp_table_attach_icon (GtkTable *table,
+ gint row,
+ const gchar *icon_name,
+ GtkWidget *widget,
+ gint colspan,
+ gboolean left_align)
+{
+ GtkWidget *image;
+
+ g_return_if_fail (GTK_IS_TABLE (table));
+ g_return_if_fail (icon_name != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON);
+ gtk_misc_set_alignment (GTK_MISC (image), 1.0, 0.5);
+ gtk_table_attach (table, image, 0, 1, row, row + 1,
+ GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_show (image);
+
+ if (left_align)
+ {
+ GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = hbox;
+ }
+
+ gtk_table_attach (table, widget, 1, 1 + colspan, row, row + 1,
+ GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+ gtk_widget_show (widget);
+}
+
+void
+gimp_enum_radio_box_add (GtkBox *box,
+ GtkWidget *widget,
+ gint enum_value,
+ gboolean below)
+{
+ GList *children;
+ GList *list;
+ gint pos;
+
+ g_return_if_fail (GTK_IS_BOX (box));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ children = gtk_container_get_children (GTK_CONTAINER (box));
+
+ for (list = children, pos = 1;
+ list;
+ list = g_list_next (list), pos++)
+ {
+ if (GTK_IS_RADIO_BUTTON (list->data) &&
+ GPOINTER_TO_INT (g_object_get_data (list->data, "gimp-item-data")) ==
+ enum_value)
+ {
+ GtkWidget *radio = list->data;
+ GtkWidget *hbox;
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0);
+ gtk_box_reorder_child (GTK_BOX (box), hbox, pos);
+
+ if (below)
+ {
+ GtkWidget *spacer;
+ gint indicator_size;
+ gint indicator_spacing;
+ gint focus_width;
+ gint focus_padding;
+ gint border_width;
+
+ gtk_widget_style_get (radio,
+ "indicator-size", &indicator_size,
+ "indicator-spacing", &indicator_spacing,
+ "focus-line-width", &focus_width,
+ "focus-padding", &focus_padding,
+ NULL);
+
+ border_width = gtk_container_get_border_width (GTK_CONTAINER (radio));
+
+ spacer = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_set_size_request (spacer,
+ indicator_size +
+ 3 * indicator_spacing +
+ focus_width +
+ focus_padding +
+ border_width,
+ -1);
+ gtk_box_pack_start (GTK_BOX (hbox), spacer, FALSE, FALSE, 0);
+ gtk_widget_show (spacer);
+ }
+ else
+ {
+ GtkSizeGroup *size_group;
+
+ size_group = g_object_get_data (G_OBJECT (box), "size-group");
+
+ if (! size_group)
+ {
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ g_object_set_data (G_OBJECT (box), "size-group", size_group);
+
+ gtk_size_group_add_widget (size_group, radio);
+ g_object_unref (size_group);
+ }
+ else
+ {
+ gtk_size_group_add_widget (size_group, radio);
+ }
+
+ gtk_box_set_spacing (GTK_BOX (hbox), 4);
+
+ g_object_ref (radio);
+ gtk_container_remove (GTK_CONTAINER (box), radio);
+ gtk_box_pack_start (GTK_BOX (hbox), radio, FALSE, FALSE, 0);
+ g_object_unref (radio);
+ }
+
+ gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (radio, "active",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ gtk_widget_show (hbox);
+
+ break;
+ }
+ }
+
+ g_list_free (children);
+}
+
+void
+gimp_enum_radio_frame_add (GtkFrame *frame,
+ GtkWidget *widget,
+ gint enum_value,
+ gboolean below)
+{
+ GtkWidget *box;
+
+ g_return_if_fail (GTK_IS_FRAME (frame));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ box = gtk_bin_get_child (GTK_BIN (frame));
+
+ g_return_if_fail (GTK_IS_BOX (box));
+
+ gimp_enum_radio_box_add (GTK_BOX (box), widget, enum_value, below);
+}
+
+/**
+ * gimp_widget_load_icon:
+ * @widget: parent widget (to determine icon theme and
+ * style)
+ * @icon_name: icon name
+ * @size: requested pixel size
+ *
+ * Loads an icon into a pixbuf with size as close as possible to @size.
+ * If icon does not exist or fail to load, the function will fallback to
+ * "gimp-wilber-eek" instead to prevent NULL pixbuf. As a last resort,
+ * if even the fallback failed to load, a magenta @size square will be
+ * returned, so this function is guaranteed to always return a
+ * #GdkPixbuf.
+ *
+ * Return value: a newly allocated #GdkPixbuf containing @icon_name at
+ * size @size or a fallback icon/size.
+ **/
+GdkPixbuf *
+gimp_widget_load_icon (GtkWidget *widget,
+ const gchar *icon_name,
+ gint size)
+{
+ GdkPixbuf *pixbuf;
+ GtkIconTheme *icon_theme;
+ gint *icon_sizes;
+ gint closest_size = -1;
+ gint min_diff = G_MAXINT;
+ gint i;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+ g_return_val_if_fail (icon_name != NULL, NULL);
+
+ icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
+
+ if (! gtk_icon_theme_has_icon (icon_theme, icon_name))
+ {
+ g_printerr ("WARNING: icon theme has no icon '%s'.\n",
+ icon_name);
+
+ return gtk_icon_theme_load_icon (icon_theme, GIMP_ICON_WILBER_EEK,
+ size, 0, NULL);
+ }
+
+ icon_sizes = gtk_icon_theme_get_icon_sizes (icon_theme, icon_name);
+
+ for (i = 0; icon_sizes[i]; i++)
+ {
+ if (icon_sizes[i] > 0 &&
+ icon_sizes[i] <= size)
+ {
+ if (size - icon_sizes[i] < min_diff)
+ {
+ min_diff = size - icon_sizes[i];
+ closest_size = icon_sizes[i];
+ }
+ }
+ }
+
+ g_free (icon_sizes);
+
+ if (closest_size != -1)
+ size = closest_size;
+
+ pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name, size,
+ GTK_ICON_LOOKUP_USE_BUILTIN, NULL);
+
+ if (! pixbuf)
+ {
+ /* The icon was seemingly present in the current icon theme, yet
+ * it failed to load. Maybe the file is broken?
+ * As last resort, try to load "gimp-wilber-eek" as fallback.
+ * Note that we are not making more checks, so if the fallback
+ * icon fails to load as well, the function may still return NULL.
+ */
+ g_printerr ("WARNING: icon '%s' failed to load. Check the files "
+ "in your icon theme.\n", icon_name);
+
+ pixbuf = gtk_icon_theme_load_icon (icon_theme,
+ GIMP_ICON_WILBER_EEK,
+ size, 0, NULL);
+ if (! pixbuf)
+ {
+ /* As last resort, just draw an ugly magenta square. */
+ guchar *data;
+ gint rowstride = 3 * size;
+ gint i, j;
+
+ g_printerr ("WARNING: icon '%s' failed to load. Check the files "
+ "in your icon theme.\n", GIMP_ICON_WILBER_EEK);
+
+ data = g_new (guchar, rowstride * size);
+ for (i = 0; i < size; i++)
+ {
+ for (j = 0; j < size; j++)
+ {
+ data[i * rowstride + j * 3] = 255;
+ data[i * rowstride + j * 3 + 1] = 0;
+ data[i * rowstride + j * 3 + 2] = 255;
+ }
+ }
+ pixbuf = gdk_pixbuf_new_from_data (data, GDK_COLORSPACE_RGB, FALSE,
+ 8, size, size, rowstride,
+ (GdkPixbufDestroyNotify) g_free,
+ NULL);
+ }
+ }
+
+ return pixbuf;
+}
+
+GtkIconSize
+gimp_get_icon_size (GtkWidget *widget,
+ const gchar *icon_name,
+ GtkIconSize max_size,
+ gint width,
+ gint height)
+{
+ GtkIconSet *icon_set;
+ GtkIconSize *sizes;
+ gint n_sizes;
+ gint i;
+ gint width_diff = 1024;
+ gint height_diff = 1024;
+ gint max_width;
+ gint max_height;
+ GtkIconSize icon_size = GTK_ICON_SIZE_MENU;
+ GtkSettings *settings;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), icon_size);
+ g_return_val_if_fail (icon_name != NULL, icon_size);
+ g_return_val_if_fail (width > 0, icon_size);
+ g_return_val_if_fail (height > 0, icon_size);
+
+ icon_set = gtk_style_lookup_icon_set (gtk_widget_get_style (widget),
+ icon_name);
+
+ if (! icon_set)
+ return GTK_ICON_SIZE_INVALID;
+
+ settings = gtk_widget_get_settings (widget);
+
+ if (! gtk_icon_size_lookup_for_settings (settings, max_size,
+ &max_width, &max_height))
+ {
+ max_width = 1024;
+ max_height = 1024;
+ }
+
+ gtk_icon_set_get_sizes (icon_set, &sizes, &n_sizes);
+
+ for (i = 0; i < n_sizes; i++)
+ {
+ gint icon_width;
+ gint icon_height;
+
+ if (gtk_icon_size_lookup_for_settings (settings, sizes[i],
+ &icon_width, &icon_height))
+ {
+ if (icon_width <= width &&
+ icon_height <= height &&
+ icon_width <= max_width &&
+ icon_height <= max_height &&
+ ((width - icon_width) < width_diff ||
+ (height - icon_height) < height_diff))
+ {
+ width_diff = width - icon_width;
+ height_diff = height - icon_height;
+
+ icon_size = sizes[i];
+ }
+ }
+ }
+
+ g_free (sizes);
+
+ return icon_size;
+}
+
+GimpTabStyle
+gimp_preview_tab_style_to_icon (GimpTabStyle tab_style)
+{
+ switch (tab_style)
+ {
+ case GIMP_TAB_STYLE_PREVIEW:
+ tab_style = GIMP_TAB_STYLE_ICON;
+ break;
+
+ case GIMP_TAB_STYLE_PREVIEW_NAME:
+ tab_style = GIMP_TAB_STYLE_ICON_NAME;
+ break;
+
+ case GIMP_TAB_STYLE_PREVIEW_BLURB:
+ tab_style = GIMP_TAB_STYLE_ICON_BLURB;
+ break;
+
+ default:
+ break;
+ }
+
+ return tab_style;
+}
+
+const gchar *
+gimp_get_mod_string (GdkModifierType modifiers)
+{
+ static GHashTable *mod_labels;
+ gchar *label;
+
+ if (! modifiers)
+ return NULL;
+
+ if (G_UNLIKELY (! mod_labels))
+ mod_labels = g_hash_table_new (g_int_hash, g_int_equal);
+
+ modifiers = gimp_replace_virtual_modifiers (modifiers);
+
+ label = g_hash_table_lookup (mod_labels, &modifiers);
+
+ if (! label)
+ {
+ GtkAccelLabelClass *accel_label_class;
+
+ label = gtk_accelerator_get_label (0, modifiers);
+
+ accel_label_class = g_type_class_ref (GTK_TYPE_ACCEL_LABEL);
+
+ if (accel_label_class->mod_separator &&
+ *accel_label_class->mod_separator)
+ {
+ gchar *sep = g_strrstr (label, accel_label_class->mod_separator);
+
+ if (sep - label ==
+ strlen (label) - strlen (accel_label_class->mod_separator))
+ *sep = '\0';
+ }
+
+ g_type_class_unref (accel_label_class);
+
+ g_hash_table_insert (mod_labels,
+ g_memdup (&modifiers, sizeof (GdkModifierType)),
+ label);
+ }
+
+ return label;
+}
+
+#define BUF_SIZE 100
+/**
+ * gimp_suggest_modifiers:
+ * @message: initial text for the message
+ * @modifiers: bit mask of modifiers that should be suggested
+ * @extend_selection_format: optional format string for the
+ * "Extend selection" modifier
+ * @toggle_behavior_format: optional format string for the
+ * "Toggle behavior" modifier
+ * @alt_format: optional format string for the Alt modifier
+ *
+ * Utility function to build a message suggesting to use some
+ * modifiers for performing different actions (only Shift, Ctrl and
+ * Alt are currently supported). If some of these modifiers are
+ * already active, they will not be suggested. The optional format
+ * strings #extend_selection_format, #toggle_behavior_format and
+ * #alt_format may be used to describe what the modifier will do.
+ * They must contain a single '%%s' which will be replaced by the name
+ * of the modifier. They can also be %NULL if the modifier name
+ * should be left alone.
+ *
+ * Return value: a newly allocated string containing the message.
+ **/
+gchar *
+gimp_suggest_modifiers (const gchar *message,
+ GdkModifierType modifiers,
+ const gchar *extend_selection_format,
+ const gchar *toggle_behavior_format,
+ const gchar *alt_format)
+{
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+ gchar msg_buf[3][BUF_SIZE];
+ gint num_msgs = 0;
+ gboolean try = FALSE;
+
+ if (modifiers & extend_mask)
+ {
+ if (extend_selection_format && *extend_selection_format)
+ {
+ g_snprintf (msg_buf[num_msgs], BUF_SIZE, extend_selection_format,
+ gimp_get_mod_string (extend_mask));
+ }
+ else
+ {
+ g_strlcpy (msg_buf[num_msgs],
+ gimp_get_mod_string (extend_mask), BUF_SIZE);
+ try = TRUE;
+ }
+
+ num_msgs++;
+ }
+
+ if (modifiers & toggle_mask)
+ {
+ if (toggle_behavior_format && *toggle_behavior_format)
+ {
+ g_snprintf (msg_buf[num_msgs], BUF_SIZE, toggle_behavior_format,
+ gimp_get_mod_string (toggle_mask));
+ }
+ else
+ {
+ g_strlcpy (msg_buf[num_msgs],
+ gimp_get_mod_string (toggle_mask), BUF_SIZE);
+ try = TRUE;
+ }
+
+ num_msgs++;
+ }
+
+ if (modifiers & GDK_MOD1_MASK)
+ {
+ if (alt_format && *alt_format)
+ {
+ g_snprintf (msg_buf[num_msgs], BUF_SIZE, alt_format,
+ gimp_get_mod_string (GDK_MOD1_MASK));
+ }
+ else
+ {
+ g_strlcpy (msg_buf[num_msgs],
+ gimp_get_mod_string (GDK_MOD1_MASK), BUF_SIZE);
+ try = TRUE;
+ }
+
+ num_msgs++;
+ }
+
+ /* This convoluted way to build the message using multiple format strings
+ * tries to make the messages easier to translate to other languages.
+ */
+
+ switch (num_msgs)
+ {
+ case 1:
+ return g_strdup_printf (try ? _("%s (try %s)") : _("%s (%s)"),
+ message, msg_buf[0]);
+
+ case 2:
+ return g_strdup_printf (_("%s (try %s, %s)"),
+ message, msg_buf[0], msg_buf[1]);
+
+ case 3:
+ return g_strdup_printf (_("%s (try %s, %s, %s)"),
+ message, msg_buf[0], msg_buf[1], msg_buf[2]);
+ }
+
+ return g_strdup (message);
+}
+#undef BUF_SIZE
+
+GimpChannelOps
+gimp_modifiers_to_channel_op (GdkModifierType modifiers)
+{
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType modify_mask = gimp_get_modify_selection_mask ();
+
+ if (modifiers & extend_mask)
+ {
+ if (modifiers & modify_mask)
+ {
+ return GIMP_CHANNEL_OP_INTERSECT;
+ }
+ else
+ {
+ return GIMP_CHANNEL_OP_ADD;
+ }
+ }
+ else if (modifiers & modify_mask)
+ {
+ return GIMP_CHANNEL_OP_SUBTRACT;
+ }
+
+ return GIMP_CHANNEL_OP_REPLACE;
+}
+
+GdkModifierType
+gimp_replace_virtual_modifiers (GdkModifierType modifiers)
+{
+ GdkDisplay *display = gdk_display_get_default ();
+ GdkModifierType result = 0;
+ gint i;
+
+ for (i = 0; i < 8; i++)
+ {
+ GdkModifierType real = 1 << i;
+
+ if (modifiers & real)
+ {
+ GdkModifierType virtual = real;
+
+ gdk_keymap_add_virtual_modifiers (gdk_keymap_get_for_display (display),
+ &virtual);
+
+ if (virtual == real)
+ result |= virtual;
+ else
+ result |= virtual & ~real;
+ }
+ }
+
+ return result;
+}
+
+GdkModifierType
+gimp_get_primary_accelerator_mask (void)
+{
+ GdkDisplay *display = gdk_display_get_default ();
+
+ return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
+ GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR);
+}
+
+GdkModifierType
+gimp_get_extend_selection_mask (void)
+{
+ GdkDisplay *display = gdk_display_get_default ();
+
+ return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
+ GDK_MODIFIER_INTENT_EXTEND_SELECTION);
+}
+
+GdkModifierType
+gimp_get_modify_selection_mask (void)
+{
+ GdkDisplay *display = gdk_display_get_default ();
+
+ return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
+ GDK_MODIFIER_INTENT_MODIFY_SELECTION);
+}
+
+GdkModifierType
+gimp_get_toggle_behavior_mask (void)
+{
+ GdkDisplay *display = gdk_display_get_default ();
+
+ /* use the modify selection modifier */
+ return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
+ GDK_MODIFIER_INTENT_MODIFY_SELECTION);
+}
+
+GdkModifierType
+gimp_get_constrain_behavior_mask (void)
+{
+ GdkDisplay *display = gdk_display_get_default ();
+
+ /* use the modify selection modifier */
+ return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
+ GDK_MODIFIER_INTENT_MODIFY_SELECTION);
+}
+
+GdkModifierType
+gimp_get_all_modifiers_mask (void)
+{
+ GdkDisplay *display = gdk_display_get_default ();
+
+ return (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK |
+ gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
+ GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR) |
+ gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
+ GDK_MODIFIER_INTENT_EXTEND_SELECTION) |
+ gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
+ GDK_MODIFIER_INTENT_MODIFY_SELECTION));
+}
+
+/**
+ * gimp_get_monitor_resolution:
+ * @screen: a #GdkScreen
+ * @monitor: a monitor number
+ * @xres: returns the horizontal monitor resolution (in dpi)
+ * @yres: returns the vertical monitor resolution (in dpi)
+ *
+ * Retrieves the monitor's resolution from GDK.
+ **/
+void
+gimp_get_monitor_resolution (GdkScreen *screen,
+ gint monitor,
+ gdouble *xres,
+ gdouble *yres)
+{
+ GdkRectangle size_pixels;
+ gint width_mm, height_mm;
+ gdouble x = 0.0;
+ gdouble y = 0.0;
+#ifdef PLATFORM_OSX
+ CGSize size;
+#endif
+
+ g_return_if_fail (GDK_IS_SCREEN (screen));
+ g_return_if_fail (xres != NULL);
+ g_return_if_fail (yres != NULL);
+
+#ifndef PLATFORM_OSX
+ gdk_screen_get_monitor_geometry (screen, monitor, &size_pixels);
+
+ width_mm = gdk_screen_get_monitor_width_mm (screen, monitor);
+ height_mm = gdk_screen_get_monitor_height_mm (screen, monitor);
+#else
+ width_mm = 0;
+ height_mm = 0;
+ size = CGDisplayScreenSize (kCGDirectMainDisplay);
+ if (!CGSizeEqualToSize (size, CGSizeZero))
+ {
+ width_mm = size.width;
+ height_mm = size.height;
+ }
+ size_pixels.width = CGDisplayPixelsWide (kCGDirectMainDisplay);
+ size_pixels.height = CGDisplayPixelsHigh (kCGDirectMainDisplay);
+#endif
+ /*
+ * From xdpyinfo.c:
+ *
+ * there are 2.54 centimeters to an inch; so there are 25.4 millimeters.
+ *
+ * dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch))
+ * = N pixels / (M inch / 25.4)
+ * = N * 25.4 pixels / M inch
+ */
+
+ if (width_mm > 0 && height_mm > 0)
+ {
+ x = (size_pixels.width * 25.4) / (gdouble) width_mm;
+ y = (size_pixels.height * 25.4) / (gdouble) height_mm;
+ }
+
+ if (x < GIMP_MIN_RESOLUTION || x > GIMP_MAX_RESOLUTION ||
+ y < GIMP_MIN_RESOLUTION || y > GIMP_MAX_RESOLUTION)
+ {
+ g_printerr ("gimp_get_monitor_resolution(): GDK returned bogus "
+ "values for the monitor resolution, using 96 dpi instead.\n");
+
+ x = 96.0;
+ y = 96.0;
+ }
+
+ /* round the value to full integers to give more pleasant results */
+ *xres = ROUND (x);
+ *yres = ROUND (y);
+}
+
+
+/**
+ * gimp_rgb_get_gdk_color:
+ * @rgb: the source color as #GimpRGB
+ * @gdk_color: pointer to a #GdkColor
+ *
+ * Initializes @gdk_color from a #GimpRGB. This function does not
+ * allocate the color for you. Depending on how you want to use it,
+ * you may have to call gdk_colormap_alloc_color().
+ **/
+void
+gimp_rgb_get_gdk_color (const GimpRGB *rgb,
+ GdkColor *gdk_color)
+{
+ guchar r, g, b;
+
+ g_return_if_fail (rgb != NULL);
+ g_return_if_fail (gdk_color != NULL);
+
+ gimp_rgb_get_uchar (rgb, &r, &g, &b);
+
+ gdk_color->red = (r << 8) | r;
+ gdk_color->green = (g << 8) | g;
+ gdk_color->blue = (b << 8) | b;
+}
+
+/**
+ * gimp_rgb_set_gdk_color:
+ * @rgb: a #GimpRGB that is to be set
+ * @gdk_color: pointer to the source #GdkColor
+ *
+ * Initializes @rgb from a #GdkColor. This function does not touch
+ * the alpha value of @rgb.
+ **/
+void
+gimp_rgb_set_gdk_color (GimpRGB *rgb,
+ const GdkColor *gdk_color)
+{
+ guchar r, g, b;
+
+ g_return_if_fail (rgb != NULL);
+ g_return_if_fail (gdk_color != NULL);
+
+ r = gdk_color->red >> 8;
+ g = gdk_color->green >> 8;
+ b = gdk_color->blue >> 8;
+
+ gimp_rgb_set_uchar (rgb, r, g, b);
+}
+
+void
+gimp_window_set_hint (GtkWindow *window,
+ GimpWindowHint hint)
+{
+ g_return_if_fail (GTK_IS_WINDOW (window));
+
+ switch (hint)
+ {
+ case GIMP_WINDOW_HINT_NORMAL:
+ gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_NORMAL);
+ break;
+
+ case GIMP_WINDOW_HINT_UTILITY:
+ gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_UTILITY);
+ break;
+
+ case GIMP_WINDOW_HINT_KEEP_ABOVE:
+ gtk_window_set_keep_above (window, TRUE);
+ break;
+ }
+}
+
+/**
+ * gimp_window_get_native_id:
+ * @window: a #GtkWindow
+ *
+ * This function is used to pass a window handle to plug-ins so that
+ * they can set their dialog windows transient to the parent window.
+ *
+ * Return value: a native window ID of the window's #GdkWindow or 0
+ * if the window isn't realized yet
+ */
+guint32
+gimp_window_get_native_id (GtkWindow *window)
+{
+ g_return_val_if_fail (GTK_IS_WINDOW (window), 0);
+
+#ifdef GDK_NATIVE_WINDOW_POINTER
+#ifdef __GNUC__
+#warning gimp_window_get_native() unimplementable for the target windowing system
+#endif
+ return 0;
+#endif
+
+#ifdef GDK_WINDOWING_WIN32
+ if (window && gtk_widget_get_realized (GTK_WIDGET (window)))
+ return GDK_WINDOW_HWND (gtk_widget_get_window (GTK_WIDGET (window)));
+#endif
+
+#ifdef GDK_WINDOWING_X11
+ if (window && gtk_widget_get_realized (GTK_WIDGET (window)))
+ return GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (window)));
+#endif
+
+ return 0;
+}
+
+static void
+gimp_window_transient_realized (GtkWidget *window,
+ GdkWindow *parent)
+{
+ if (gtk_widget_get_realized (window))
+ gdk_window_set_transient_for (gtk_widget_get_window (window), parent);
+}
+
+/* similar to what we have in libgimp/gimpui.c */
+static GdkWindow *
+gimp_get_foreign_window (guint32 window)
+{
+#ifdef GDK_WINDOWING_X11
+ return gdk_x11_window_foreign_new_for_display (gdk_display_get_default (),
+ window);
+#endif
+
+#ifdef GDK_WINDOWING_WIN32
+ return gdk_win32_window_foreign_new_for_display (gdk_display_get_default (),
+ window);
+#endif
+
+ return NULL;
+}
+
+void
+gimp_window_set_transient_for (GtkWindow *window,
+ guint32 parent_ID)
+{
+ /* Cross-process transient-for is broken in gdk/win32 <= 2.10.6. It
+ * causes hangs, at least when used as by the gimp and script-fu
+ * processes. In some newer GTK+ version it will be fixed to be a
+ * no-op. If it eventually is fixed to actually work, change this to
+ * a run-time check of GTK+ version. Remember to change also the
+ * function with the same name in libgimp/gimpui.c
+ */
+#ifndef GDK_WINDOWING_WIN32
+ GdkWindow *parent;
+
+ parent = gimp_get_foreign_window (parent_ID);
+ if (! parent)
+ return;
+
+ if (gtk_widget_get_realized (GTK_WIDGET (window)))
+ gdk_window_set_transient_for (gtk_widget_get_window (GTK_WIDGET (window)),
+ parent);
+
+ g_signal_connect_object (window, "realize",
+ G_CALLBACK (gimp_window_transient_realized),
+ parent, 0);
+
+ g_object_unref (parent);
+#endif
+}
+
+static gboolean
+gimp_widget_accel_find_func (GtkAccelKey *key,
+ GClosure *closure,
+ gpointer data)
+{
+ return (GClosure *) data == closure;
+}
+
+static void
+gimp_widget_accel_changed (GtkAccelGroup *accel_group,
+ guint unused1,
+ GdkModifierType unused2,
+ GClosure *accel_closure,
+ GtkWidget *widget)
+{
+ GClosure *widget_closure;
+
+ widget_closure = g_object_get_data (G_OBJECT (widget), "gimp-accel-closure");
+
+ if (accel_closure == widget_closure)
+ {
+ GimpAction *action;
+ GtkAccelKey *accel_key;
+ const gchar *tooltip;
+ const gchar *help_id;
+
+ action = g_object_get_data (G_OBJECT (widget), "gimp-accel-action");
+
+ tooltip = gimp_action_get_tooltip (action);
+ help_id = gimp_action_get_help_id (action);
+
+ accel_key = gtk_accel_group_find (accel_group,
+ gimp_widget_accel_find_func,
+ accel_closure);
+
+ if (accel_key &&
+ accel_key->accel_key &&
+ (accel_key->accel_flags & GTK_ACCEL_VISIBLE))
+ {
+ gchar *escaped = g_markup_escape_text (tooltip, -1);
+ gchar *accel = gtk_accelerator_get_label (accel_key->accel_key,
+ accel_key->accel_mods);
+ gchar *tmp = g_strdup_printf ("%s <b>%s</b>", escaped, accel);
+
+ g_free (accel);
+ g_free (escaped);
+
+ gimp_help_set_help_data_with_markup (widget, tmp, help_id);
+ g_free (tmp);
+ }
+ else
+ {
+ gimp_help_set_help_data (widget, tooltip, help_id);
+ }
+ }
+}
+
+static void gimp_accel_help_widget_weak_notify (gpointer accel_group,
+ GObject *where_widget_was);
+
+static void
+gimp_accel_help_accel_group_weak_notify (gpointer widget,
+ GObject *where_accel_group_was)
+{
+ g_object_weak_unref (widget,
+ gimp_accel_help_widget_weak_notify,
+ where_accel_group_was);
+
+ g_object_set_data (widget, "gimp-accel-group", NULL);
+}
+
+static void
+gimp_accel_help_widget_weak_notify (gpointer accel_group,
+ GObject *where_widget_was)
+{
+ g_object_weak_unref (accel_group,
+ gimp_accel_help_accel_group_weak_notify,
+ where_widget_was);
+}
+
+void
+gimp_widget_set_accel_help (GtkWidget *widget,
+ GimpAction *action)
+{
+ GtkAccelGroup *accel_group;
+ GClosure *accel_closure;
+
+ accel_group = g_object_get_data (G_OBJECT (widget), "gimp-accel-group");
+
+ if (accel_group)
+ {
+ g_signal_handlers_disconnect_by_func (accel_group,
+ gimp_widget_accel_changed,
+ widget);
+ g_object_weak_unref (G_OBJECT (accel_group),
+ gimp_accel_help_accel_group_weak_notify,
+ widget);
+ g_object_weak_unref (G_OBJECT (widget),
+ gimp_accel_help_widget_weak_notify,
+ accel_group);
+ g_object_set_data (G_OBJECT (widget), "gimp-accel-group", NULL);
+ }
+
+ accel_closure = gimp_action_get_accel_closure (action);
+
+ if (accel_closure)
+ {
+ accel_group = gtk_accel_group_from_accel_closure (accel_closure);
+
+ g_object_set_data (G_OBJECT (widget), "gimp-accel-group",
+ accel_group);
+ g_object_weak_ref (G_OBJECT (accel_group),
+ gimp_accel_help_accel_group_weak_notify,
+ widget);
+ g_object_weak_ref (G_OBJECT (widget),
+ gimp_accel_help_widget_weak_notify,
+ accel_group);
+
+ g_object_set_data (G_OBJECT (widget), "gimp-accel-closure",
+ accel_closure);
+ g_object_set_data (G_OBJECT (widget), "gimp-accel-action",
+ action);
+
+ g_signal_connect_object (accel_group, "accel-changed",
+ G_CALLBACK (gimp_widget_accel_changed),
+ widget, 0);
+
+ gimp_widget_accel_changed (accel_group,
+ 0, 0,
+ accel_closure,
+ widget);
+ }
+ else
+ {
+ gimp_help_set_help_data (widget,
+ gimp_action_get_tooltip (action),
+ gimp_action_get_help_id (action));
+
+ }
+}
+
+const gchar *
+gimp_get_message_icon_name (GimpMessageSeverity severity)
+{
+ switch (severity)
+ {
+ case GIMP_MESSAGE_INFO:
+ return GIMP_ICON_DIALOG_INFORMATION;
+
+ case GIMP_MESSAGE_WARNING:
+ return GIMP_ICON_DIALOG_WARNING;
+
+ case GIMP_MESSAGE_ERROR:
+ return GIMP_ICON_DIALOG_ERROR;
+
+ case GIMP_MESSAGE_BUG_WARNING:
+ case GIMP_MESSAGE_BUG_CRITICAL:
+ return GIMP_ICON_WILBER_EEK;
+ }
+
+ g_return_val_if_reached (GIMP_ICON_DIALOG_WARNING);
+}
+
+gboolean
+gimp_get_color_tag_color (GimpColorTag color_tag,
+ GimpRGB *color,
+ gboolean inherited)
+{
+ static const struct
+ {
+ guchar r;
+ guchar g;
+ guchar b;
+ }
+ colors[] =
+ {
+ { 0, 0, 0 }, /* none */
+ { 84, 102, 159 }, /* blue */
+ { 111, 143, 48 }, /* green */
+ { 210, 182, 45 }, /* yellow */
+ { 217, 122, 38 }, /* orange */
+ { 87, 53, 25 }, /* brown */
+ { 170, 42, 47 }, /* red */
+ { 99, 66, 174 }, /* violet */
+ { 87, 87, 87 } /* gray */
+ };
+
+ g_return_val_if_fail (color != NULL, FALSE);
+ g_return_val_if_fail (color_tag < G_N_ELEMENTS (colors), FALSE);
+
+ if (color_tag > GIMP_COLOR_TAG_NONE)
+ {
+ gimp_rgba_set_uchar (color,
+ colors[color_tag].r,
+ colors[color_tag].g,
+ colors[color_tag].b,
+ 255);
+
+ if (inherited)
+ {
+ gimp_rgb_composite (color, &(GimpRGB) {1.0, 1.0, 1.0, 0.2},
+ GIMP_RGB_COMPOSITE_NORMAL);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_pango_layout_set_scale (PangoLayout *layout,
+ gdouble scale)
+{
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+
+ g_return_if_fail (PANGO_IS_LAYOUT (layout));
+
+ attrs = pango_attr_list_new ();
+
+ attr = pango_attr_scale_new (scale);
+ attr->start_index = 0;
+ attr->end_index = -1;
+ pango_attr_list_insert (attrs, attr);
+
+ pango_layout_set_attributes (layout, attrs);
+ pango_attr_list_unref (attrs);
+}
+
+void
+gimp_pango_layout_set_weight (PangoLayout *layout,
+ PangoWeight weight)
+{
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+
+ g_return_if_fail (PANGO_IS_LAYOUT (layout));
+
+ attrs = pango_attr_list_new ();
+
+ attr = pango_attr_weight_new (weight);
+ attr->start_index = 0;
+ attr->end_index = -1;
+ pango_attr_list_insert (attrs, attr);
+
+ pango_layout_set_attributes (layout, attrs);
+ pango_attr_list_unref (attrs);
+}
+
+static gboolean
+gimp_highlight_widget_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ gpointer data)
+{
+ /* this code is a modified version of gtk+'s gtk_drag_highlight_expose(),
+ * changing the highlight color from black to the widget's text color, which
+ * improves its visibility when using a dark theme.
+ */
+
+ gint x, y, width, height;
+
+ if (gtk_widget_is_drawable (widget))
+ {
+ GdkWindow *window;
+ GtkStyle *style;
+ const GdkColor *color;
+ cairo_t *cr;
+
+ window = gtk_widget_get_window (widget);
+ style = gtk_widget_get_style (widget);
+
+ if (!gtk_widget_get_has_window (widget))
+ {
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ x = allocation.x;
+ y = allocation.y;
+ width = allocation.width;
+ height = allocation.height;
+ }
+ else
+ {
+ x = 0;
+ y = 0;
+ width = gdk_window_get_width (window);
+ height = gdk_window_get_height (window);
+ }
+
+ gtk_paint_shadow (style, window,
+ GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ &event->area, widget, "dnd",
+ x, y, width, height);
+
+ color = &style->text[GTK_STATE_NORMAL];
+
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+ cairo_set_source_rgb (cr,
+ (gdouble) color->red / 0xffff,
+ (gdouble) color->green / 0xffff,
+ (gdouble) color->blue / 0xffff);
+ cairo_set_line_width (cr, 1.0);
+ cairo_rectangle (cr,
+ x + 0.5, y + 0.5,
+ width - 1, height - 1);
+ cairo_stroke (cr);
+ cairo_destroy (cr);
+ }
+
+ return FALSE;
+}
+
+/**
+ * gimp_highlight_widget:
+ * @widget:
+ * @highlight:
+ *
+ * Turns highlighting for @widget on or off according to
+ * @highlight, in a similar fashion to gtk_drag_highlight()
+ * and gtk_drag_unhighlight().
+ **/
+void
+gimp_highlight_widget (GtkWidget *widget,
+ gboolean highlight)
+{
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ if (highlight)
+ {
+ g_signal_connect_after (widget, "expose-event",
+ G_CALLBACK (gimp_highlight_widget_expose),
+ NULL);
+ }
+ else
+ {
+ g_signal_handlers_disconnect_by_func (widget,
+ gimp_highlight_widget_expose,
+ NULL);
+ }
+
+ gtk_widget_queue_draw (widget);
+}
+
+typedef struct
+{
+ gint timeout_id;
+ gint counter;
+} WidgetBlink;
+
+static WidgetBlink *
+widget_blink_new (void)
+{
+ WidgetBlink *blink;
+
+ blink = g_slice_new (WidgetBlink);
+
+ blink->timeout_id = 0;
+ blink->counter = 0;
+
+ return blink;
+}
+
+static void
+widget_blink_free (WidgetBlink *blink)
+{
+ if (blink->timeout_id)
+ {
+ g_source_remove (blink->timeout_id);
+ blink->timeout_id = 0;
+ }
+
+ g_slice_free (WidgetBlink, blink);
+}
+
+static gboolean
+gimp_widget_blink_timeout (GtkWidget *widget)
+{
+ WidgetBlink *blink;
+
+ blink = g_object_get_data (G_OBJECT (widget), "gimp-widget-blink");
+
+ gimp_highlight_widget (widget, blink->counter % 2 == 1);
+ blink->counter++;
+
+ if (blink->counter == 3)
+ {
+ blink->timeout_id = 0;
+
+ g_object_set_data (G_OBJECT (widget), "gimp-widget-blink", NULL);
+
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+void
+gimp_widget_blink (GtkWidget *widget)
+{
+ WidgetBlink *blink;
+
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ blink = widget_blink_new ();
+
+ g_object_set_data_full (G_OBJECT (widget), "gimp-widget-blink", blink,
+ (GDestroyNotify) widget_blink_free);
+
+ blink->timeout_id = g_timeout_add (150,
+ (GSourceFunc) gimp_widget_blink_timeout,
+ widget);
+
+ gimp_highlight_widget (widget, TRUE);
+
+ while ((widget = gtk_widget_get_parent (widget)))
+ gimp_widget_blink_cancel (widget);
+}
+
+void gimp_widget_blink_cancel (GtkWidget *widget)
+{
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ if (g_object_get_data (G_OBJECT (widget), "gimp-widget-blink"))
+ {
+ gimp_highlight_widget (widget, FALSE);
+
+ g_object_set_data (G_OBJECT (widget), "gimp-widget-blink", NULL);
+ }
+}
+
+/**
+ * gimp_dock_with_window_new:
+ * @factory: a #GimpDialogFacotry
+ * @screen: the #GdkScreen the dock window should appear on
+ * @toolbox: if %TRUE; gives a "gimp-toolbox-window" with a
+ * "gimp-toolbox", "gimp-dock-window"+"gimp-dock"
+ * otherwise
+ *
+ * Returns: the newly created #GimpDock with the #GimpDockWindow
+ **/
+GtkWidget *
+gimp_dock_with_window_new (GimpDialogFactory *factory,
+ GdkScreen *screen,
+ gint monitor,
+ gboolean toolbox)
+{
+ GtkWidget *dock_window;
+ GimpDockContainer *dock_container;
+ GtkWidget *dock;
+ GimpUIManager *ui_manager;
+
+ g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL);
+ g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+ /* Create a dock window to put the dock in. We need to create the
+ * dock window before the dock because the dock has a dependency to
+ * the ui manager in the dock window
+ */
+ dock_window = gimp_dialog_factory_dialog_new (factory, screen, monitor,
+ NULL /*ui_manager*/,
+ (toolbox ?
+ "gimp-toolbox-window" :
+ "gimp-dock-window"),
+ -1 /*view_size*/,
+ FALSE /*present*/);
+
+ dock_container = GIMP_DOCK_CONTAINER (dock_window);
+ ui_manager = gimp_dock_container_get_ui_manager (dock_container);
+ dock = gimp_dialog_factory_dialog_new (factory,
+ screen,
+ monitor,
+ ui_manager,
+ (toolbox ?
+ "gimp-toolbox" :
+ "gimp-dock"),
+ -1 /*view_size*/,
+ FALSE /*present*/);
+
+ if (dock)
+ gimp_dock_window_add_dock (GIMP_DOCK_WINDOW (dock_window),
+ GIMP_DOCK (dock),
+ -1);
+
+ return dock;
+}
+
+GtkWidget *
+gimp_tools_get_tool_options_gui (GimpToolOptions *tool_options)
+{
+ GtkWidget *widget;
+
+ widget = g_object_get_data (G_OBJECT (tool_options),
+ GIMP_TOOL_OPTIONS_GUI_KEY);
+
+ if (! widget)
+ {
+ GimpToolOptionsGUIFunc func;
+
+ func = g_object_get_data (G_OBJECT (tool_options),
+ GIMP_TOOL_OPTIONS_GUI_FUNC_KEY);
+
+ if (func)
+ {
+ widget = func (tool_options);
+
+ gimp_tools_set_tool_options_gui (tool_options, widget);
+ }
+ }
+
+ return widget;
+}
+
+void
+gimp_tools_set_tool_options_gui (GimpToolOptions *tool_options,
+ GtkWidget *widget)
+{
+ GtkWidget *prev_widget;
+
+ prev_widget = g_object_get_data (G_OBJECT (tool_options),
+ GIMP_TOOL_OPTIONS_GUI_KEY);
+
+ if (widget == prev_widget)
+ return;
+
+ if (prev_widget)
+ gtk_widget_destroy (prev_widget);
+
+ g_object_set_data_full (G_OBJECT (tool_options),
+ GIMP_TOOL_OPTIONS_GUI_KEY,
+ widget ? g_object_ref_sink (widget) : NULL,
+ widget ? (GDestroyNotify) g_object_unref : NULL);
+}
+
+void
+gimp_tools_set_tool_options_gui_func (GimpToolOptions *tool_options,
+ GimpToolOptionsGUIFunc func)
+{
+ g_object_set_data (G_OBJECT (tool_options),
+ GIMP_TOOL_OPTIONS_GUI_FUNC_KEY,
+ func);
+}
+
+void
+gimp_widget_flush_expose (GtkWidget *widget)
+{
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ if (! gtk_widget_is_drawable (widget))
+ return;
+
+ gdk_window_process_updates (gtk_widget_get_window (widget), FALSE);
+ gdk_flush ();
+}
+
+gboolean
+gimp_widget_get_fully_opaque (GtkWidget *widget)
+{
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+
+ return g_object_get_data (G_OBJECT (widget),
+ "gimp-widget-fully-opaque") != NULL;
+}
+
+void
+gimp_widget_set_fully_opaque (GtkWidget *widget,
+ gboolean fully_opaque)
+{
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ return g_object_set_data (G_OBJECT (widget),
+ "gimp-widget-fully-opaque",
+ GINT_TO_POINTER (fully_opaque));
+}
+
+static void
+gimp_gtk_container_clear_callback (GtkWidget *widget,
+ GtkContainer *container)
+{
+ gtk_container_remove (container, widget);
+}
+
+void
+gimp_gtk_container_clear (GtkContainer *container)
+{
+ gtk_container_foreach (container,
+ (GtkCallback) gimp_gtk_container_clear_callback,
+ container);
+}
+
+void
+gimp_gtk_adjustment_chain (GtkAdjustment *adjustment1,
+ GtkAdjustment *adjustment2)
+{
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment1));
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment2));
+
+ g_object_bind_property (adjustment1, "value",
+ adjustment2, "lower",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (adjustment2, "value",
+ adjustment1, "upper",
+ G_BINDING_SYNC_CREATE);
+}
+
+static gboolean
+gimp_print_event_free (gpointer data)
+{
+ g_free (data);
+
+ return FALSE;
+}
+
+const gchar *
+gimp_print_event (const GdkEvent *event)
+{
+ gchar *str;
+
+ switch (event->type)
+ {
+ case GDK_ENTER_NOTIFY:
+ str = g_strdup ("ENTER_NOTIFY");
+ break;
+
+ case GDK_LEAVE_NOTIFY:
+ str = g_strdup ("LEAVE_NOTIFY");
+ break;
+
+ case GDK_PROXIMITY_IN:
+ str = g_strdup ("PROXIMITY_IN");
+ break;
+
+ case GDK_PROXIMITY_OUT:
+ str = g_strdup ("PROXIMITY_OUT");
+ break;
+
+ case GDK_FOCUS_CHANGE:
+ if (event->focus_change.in)
+ str = g_strdup ("FOCUS_IN");
+ else
+ str = g_strdup ("FOCUS_OUT");
+ break;
+
+ case GDK_BUTTON_PRESS:
+ str = g_strdup_printf ("BUTTON_PRESS (%d @ %0.0f:%0.0f)",
+ event->button.button,
+ event->button.x,
+ event->button.y);
+ break;
+
+ case GDK_2BUTTON_PRESS:
+ str = g_strdup_printf ("2BUTTON_PRESS (%d @ %0.0f:%0.0f)",
+ event->button.button,
+ event->button.x,
+ event->button.y);
+ break;
+
+ case GDK_3BUTTON_PRESS:
+ str = g_strdup_printf ("3BUTTON_PRESS (%d @ %0.0f:%0.0f)",
+ event->button.button,
+ event->button.x,
+ event->button.y);
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ str = g_strdup_printf ("BUTTON_RELEASE (%d @ %0.0f:%0.0f)",
+ event->button.button,
+ event->button.x,
+ event->button.y);
+ break;
+
+ case GDK_SCROLL:
+ str = g_strdup_printf ("SCROLL (%d)",
+ event->scroll.direction);
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ str = g_strdup_printf ("MOTION_NOTIFY (%0.0f:%0.0f %d)",
+ event->motion.x,
+ event->motion.y,
+ event->motion.time);
+ break;
+
+ case GDK_KEY_PRESS:
+ str = g_strdup_printf ("KEY_PRESS (%d, %s)",
+ event->key.keyval,
+ gdk_keyval_name (event->key.keyval) ?
+ gdk_keyval_name (event->key.keyval) : "<none>");
+ break;
+
+ case GDK_KEY_RELEASE:
+ str = g_strdup_printf ("KEY_RELEASE (%d, %s)",
+ event->key.keyval,
+ gdk_keyval_name (event->key.keyval) ?
+ gdk_keyval_name (event->key.keyval) : "<none>");
+ break;
+
+ default:
+ str = g_strdup_printf ("UNHANDLED (type %d)",
+ event->type);
+ break;
+ }
+
+ g_idle_add (gimp_print_event_free, str);
+
+ return str;
+}
+
+gboolean
+gimp_color_profile_store_add_defaults (GimpColorProfileStore *store,
+ GimpColorConfig *config,
+ GimpImageBaseType base_type,
+ GimpPrecision precision,
+ GError **error)
+{
+ GimpColorProfile *profile;
+ const Babl *format;
+ gchar *label;
+ GError *my_error = NULL;
+
+ g_return_val_if_fail (GIMP_IS_COLOR_PROFILE_STORE (store), FALSE);
+ g_return_val_if_fail (GIMP_IS_COLOR_CONFIG (config), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ format = gimp_babl_format (base_type, precision, TRUE);
+ profile = gimp_babl_format_get_color_profile (format);
+
+ if (base_type == GIMP_GRAY)
+ {
+ label = g_strdup_printf (_("Built-in grayscale (%s)"),
+ gimp_color_profile_get_label (profile));
+
+ profile = gimp_color_config_get_gray_color_profile (config, &my_error);
+ }
+ else
+ {
+ label = g_strdup_printf (_("Built-in RGB (%s)"),
+ gimp_color_profile_get_label (profile));
+
+ profile = gimp_color_config_get_rgb_color_profile (config, &my_error);
+ }
+
+ gimp_color_profile_store_add_file (store, NULL, label);
+ g_free (label);
+
+ if (profile)
+ {
+ GFile *file;
+
+ if (base_type == GIMP_GRAY)
+ {
+ file = gimp_file_new_for_config_path (config->gray_profile, NULL);
+
+ label = g_strdup_printf (_("Preferred grayscale (%s)"),
+ gimp_color_profile_get_label (profile));
+ }
+ else
+ {
+ file = gimp_file_new_for_config_path (config->rgb_profile, NULL);
+
+ label = g_strdup_printf (_("Preferred RGB (%s)"),
+ gimp_color_profile_get_label (profile));
+ }
+
+ g_object_unref (profile);
+
+ gimp_color_profile_store_add_file (store, file, label);
+
+ g_object_unref (file);
+ g_free (label);
+
+ return TRUE;
+ }
+ else if (my_error)
+ {
+ g_propagate_error (error, my_error);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+connect_path_show (GimpColorProfileChooserDialog *dialog)
+{
+ GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
+ GFile *file = gtk_file_chooser_get_file (chooser);
+
+ if (file)
+ {
+ /* if something is already selected in this dialog,
+ * leave it alone
+ */
+ g_object_unref (file);
+ }
+ else
+ {
+ GObject *config;
+ const gchar *property;
+ gchar *path = NULL;
+
+ config = g_object_get_data (G_OBJECT (dialog), "profile-path-config");
+ property = g_object_get_data (G_OBJECT (dialog), "profile-path-property");
+
+ g_object_get (config, property, &path, NULL);
+
+ if (path)
+ {
+ GFile *folder = gimp_file_new_for_config_path (path, NULL);
+
+ if (folder)
+ {
+ gtk_file_chooser_set_current_folder_file (chooser, folder, NULL);
+ g_object_unref (folder);
+ }
+
+ g_free (path);
+ }
+ }
+}
+
+static void
+connect_path_response (GimpColorProfileChooserDialog *dialog,
+ gint response)
+{
+ if (response == GTK_RESPONSE_ACCEPT)
+ {
+ GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
+ GFile *file = gtk_file_chooser_get_file (chooser);
+
+ if (file)
+ {
+ GFile *folder = gtk_file_chooser_get_current_folder_file (chooser);
+
+ if (folder)
+ {
+ GObject *config;
+ const gchar *property;
+ gchar *path = NULL;
+
+ config = g_object_get_data (G_OBJECT (dialog),
+ "profile-path-config");
+ property = g_object_get_data (G_OBJECT (dialog),
+ "profile-path-property");
+
+ path = gimp_file_get_config_path (folder, NULL);
+
+ g_object_set (config, property, path, NULL);
+
+ if (path)
+ g_free (path);
+
+ g_object_unref (folder);
+ }
+
+ g_object_unref (file);
+ }
+ }
+}
+
+void
+gimp_color_profile_chooser_dialog_connect_path (GtkWidget *dialog,
+ GObject *config,
+ const gchar *property_name)
+{
+ g_return_if_fail (GIMP_IS_COLOR_PROFILE_CHOOSER_DIALOG (dialog));
+ g_return_if_fail (G_IS_OBJECT (config));
+ g_return_if_fail (property_name != NULL);
+
+ g_object_set_data_full (G_OBJECT (dialog), "profile-path-config",
+ g_object_ref (config),
+ (GDestroyNotify) g_object_unref);
+ g_object_set_data_full (G_OBJECT (dialog), "profile-path-property",
+ g_strdup (property_name),
+ (GDestroyNotify) g_free);
+
+ g_signal_connect (dialog, "show",
+ G_CALLBACK (connect_path_show),
+ NULL);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (connect_path_response),
+ NULL);
+}