/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimpwidgets-utils.c * Copyright (C) 1999-2003 Michael Natterer * * 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 . */ #include "config.h" #include #include #include #ifdef GDK_WINDOWING_WIN32 #include #endif #ifdef GDK_WINDOWING_X11 #include #endif #ifdef PLATFORM_OSX #include #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 %s", 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) : ""); 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) : ""); 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); }