Updated with upstream update
This commit is contained in:
@ -161,7 +161,9 @@ libappwidgets_sources = [
|
||||
'pikapatternselect.c',
|
||||
'pikapdbdialog.c',
|
||||
'pikapickablebutton.c',
|
||||
'pikapickablechooser.c',
|
||||
'pikapickablepopup.c',
|
||||
'pikapickableselect.c',
|
||||
'pikapivotselector.c',
|
||||
'pikapixbuf.c',
|
||||
'pikapluginview.c',
|
||||
|
@ -366,6 +366,8 @@ pika_action_history_action_activated (PikaAction *action)
|
||||
GList *link;
|
||||
PikaActionHistoryItem *item;
|
||||
|
||||
g_return_if_fail (PIKA_IS_ACTION (action));
|
||||
|
||||
/* Silently return when called at the wrong time, like when the
|
||||
* activated action was "quit" and the history is already gone.
|
||||
*/
|
||||
|
@ -693,6 +693,39 @@ pika_action_use_default_accels (PikaAction *action)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
pika_action_is_default_accel (PikaAction *action,
|
||||
const gchar *accel)
|
||||
{
|
||||
gchar **default_accels;
|
||||
guint accelerator_key = 0;
|
||||
GdkModifierType accelerator_mods = 0;
|
||||
|
||||
g_return_val_if_fail (PIKA_IS_ACTION (action), TRUE);
|
||||
g_return_val_if_fail (accel != NULL, TRUE);
|
||||
|
||||
gtk_accelerator_parse (accel, &accelerator_key, &accelerator_mods);
|
||||
g_return_val_if_fail (accelerator_key != 0 || accelerator_mods == 0, FALSE);
|
||||
|
||||
default_accels = GET_PRIVATE (action)->default_accels;
|
||||
|
||||
if (default_accels == NULL)
|
||||
return FALSE;
|
||||
|
||||
for (gint i = 0; default_accels[i] != NULL; i++)
|
||||
{
|
||||
guint default_key;
|
||||
GdkModifierType default_mods;
|
||||
|
||||
gtk_accelerator_parse (default_accels[i], &default_key, &default_mods);
|
||||
|
||||
if (default_key == accelerator_key && default_mods == accelerator_mods)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
pika_action_get_menu_path (PikaAction *action)
|
||||
{
|
||||
|
@ -119,6 +119,8 @@ const gchar ** pika_action_get_default_accels (PikaAction *action);
|
||||
const gchar ** pika_action_get_accels (PikaAction *action);
|
||||
gchar ** pika_action_get_display_accels (PikaAction *action);
|
||||
gboolean pika_action_use_default_accels (PikaAction *action);
|
||||
gboolean pika_action_is_default_accel (PikaAction *action,
|
||||
const gchar *accel);
|
||||
|
||||
const gchar * pika_action_get_menu_path (PikaAction *action);
|
||||
|
||||
|
@ -394,7 +394,87 @@ pika_action_group_add_action_with_accel (PikaActionGroup *group,
|
||||
|
||||
if ((accelerators != NULL && accelerators[0] != NULL &&
|
||||
g_strcmp0 (accelerators[0], "") != 0))
|
||||
pika_action_set_default_accels (action, (const gchar **) accelerators);
|
||||
{
|
||||
guint i = 0;
|
||||
GdkDisplay *display;
|
||||
GdkKeymap *keymap;
|
||||
gchar *accel_strs[] = { NULL, NULL, NULL, NULL};
|
||||
|
||||
display = gdk_display_get_default ();
|
||||
keymap = gdk_keymap_get_for_display (display);
|
||||
|
||||
while (accelerators[i] != NULL)
|
||||
{
|
||||
/**
|
||||
* Shifted numeric key accelerators ("<shift>0" .. "<shift>9") do
|
||||
* not work with GTK3 so the following code converts these
|
||||
* accelerators to the shifted character (or the un-shifted
|
||||
* character in the case of an azerty keyboard for example) that
|
||||
* relates to the selected numeric character. This takes into
|
||||
* account the keyboard layout that is being used when PIKA starts.
|
||||
* This means that the appropriate characters will be shown for the
|
||||
* shortcuts in the menus and keyboard preferences.
|
||||
**/
|
||||
guint accelerator_key;
|
||||
GdkModifierType accelerator_modifier;
|
||||
gboolean accelerator_string_set;
|
||||
|
||||
accelerator_string_set = FALSE;
|
||||
|
||||
gtk_accelerator_parse (accelerators[i],
|
||||
&accelerator_key,
|
||||
&accelerator_modifier);
|
||||
|
||||
if ((accelerator_key >= '0') &&
|
||||
(accelerator_key <= '9') &&
|
||||
(accelerator_modifier == GDK_SHIFT_MASK))
|
||||
{
|
||||
gboolean result;
|
||||
gint count;
|
||||
GdkKeymapKey key;
|
||||
GdkKeymapKey *keys = NULL;
|
||||
guint non_number_keyval;
|
||||
|
||||
result = gdk_keymap_get_entries_for_keyval (keymap,
|
||||
accelerator_key,
|
||||
&keys,
|
||||
&count);
|
||||
if (result && (count > 0))
|
||||
{
|
||||
key.keycode = keys[0].keycode;
|
||||
key.group = 0;
|
||||
key.level = 1;
|
||||
|
||||
non_number_keyval = gdk_keymap_lookup_key (keymap, &key);
|
||||
|
||||
if (non_number_keyval == accelerator_key)
|
||||
{
|
||||
/**
|
||||
* the number shifted is the number - assume keyboard
|
||||
* such as azerty where the numbers are on the shifted
|
||||
* key and the other characters are obtained without
|
||||
* the shift
|
||||
**/
|
||||
key.level = 0;
|
||||
non_number_keyval = gdk_keymap_lookup_key (keymap, &key);
|
||||
}
|
||||
accel_strs[i] = g_strdup (gdk_keyval_name (non_number_keyval));
|
||||
accelerator_string_set = TRUE;
|
||||
}
|
||||
g_free (keys);
|
||||
}
|
||||
|
||||
if (! accelerator_string_set)
|
||||
accel_strs[i] = g_strdup (accelerators[i]);
|
||||
|
||||
i++;
|
||||
}
|
||||
pika_action_set_default_accels (action, (const gchar **) accel_strs);
|
||||
|
||||
/* free up to 3 accelerator strings (4th entry is always NULL) */
|
||||
for (guint i = 0; i < 3; i++)
|
||||
g_free (accel_strs[i]);
|
||||
}
|
||||
|
||||
pika_action_set_group (action, group);
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ pika_brush_select_run_callback (PikaPdbDialog *dialog,
|
||||
dialog->caller_context,
|
||||
NULL, error,
|
||||
dialog->callback_name,
|
||||
G_TYPE_STRING, pika_object_get_name (object),
|
||||
PIKA_TYPE_RESOURCE, object,
|
||||
G_TYPE_DOUBLE, pika_context_get_opacity (dialog->context) * 100.0,
|
||||
G_TYPE_INT, PIKA_BRUSH_SELECT (dialog)->spacing,
|
||||
PIKA_TYPE_LAYER_MODE, pika_context_get_paint_mode (dialog->context),
|
||||
|
@ -538,7 +538,7 @@ pika_color_history_color_changed (GtkWidget *widget,
|
||||
|
||||
pika_color_area_get_color (PIKA_COLOR_AREA (widget), &color);
|
||||
|
||||
pika_palette_set_entry_color (palette, GPOINTER_TO_INT (data), &color);
|
||||
pika_palette_set_entry_color (palette, GPOINTER_TO_INT (data), &color, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -47,29 +47,33 @@
|
||||
#include "pika-intl.h"
|
||||
|
||||
|
||||
static void pika_colormap_editor_docked_iface_init (PikaDockedInterface *face);
|
||||
static void pika_colormap_editor_docked_iface_init (PikaDockedInterface *face);
|
||||
|
||||
static void pika_colormap_editor_constructed (GObject *object);
|
||||
static void pika_colormap_editor_dispose (GObject *object);
|
||||
static void pika_colormap_editor_constructed (GObject *object);
|
||||
static void pika_colormap_editor_dispose (GObject *object);
|
||||
|
||||
static void pika_colormap_editor_unmap (GtkWidget *widget);
|
||||
static void pika_colormap_editor_unmap (GtkWidget *widget);
|
||||
|
||||
static void pika_colormap_editor_set_context (PikaDocked *docked,
|
||||
PikaContext *context);
|
||||
static void pika_colormap_editor_set_context (PikaDocked *docked,
|
||||
PikaContext *context);
|
||||
|
||||
static void pika_colormap_editor_color_update (PikaColorDialog *dialog,
|
||||
const PikaRGB *color,
|
||||
PikaColorDialogState state,
|
||||
PikaColormapEditor *editor);
|
||||
static void pika_colormap_editor_color_update (PikaColorDialog *dialog,
|
||||
const PikaRGB *color,
|
||||
PikaColorDialogState state,
|
||||
PikaColormapEditor *editor);
|
||||
|
||||
static gboolean pika_colormap_editor_entry_button_press (GtkWidget *widget,
|
||||
GdkEvent *event,
|
||||
gpointer user_data);
|
||||
static gboolean pika_colormap_editor_entry_popup (GtkWidget *widget,
|
||||
gpointer user_data);
|
||||
static void pika_colormap_editor_color_clicked (PikaColormapEditor *editor,
|
||||
PikaPaletteEntry *entry,
|
||||
GdkModifierType state);
|
||||
static void pika_colormap_editor_notify_index (PikaColormapSelection *selection,
|
||||
const GParamSpec *pspec,
|
||||
PikaColormapEditor *editor);
|
||||
|
||||
static gboolean pika_colormap_editor_entry_button_press (GtkWidget *widget,
|
||||
GdkEvent *event,
|
||||
gpointer user_data);
|
||||
static gboolean pika_colormap_editor_entry_popup (GtkWidget *widget,
|
||||
gpointer user_data);
|
||||
static void pika_colormap_editor_color_clicked (PikaColormapEditor *editor,
|
||||
PikaPaletteEntry *entry,
|
||||
GdkModifierType state);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (PikaColormapEditor, pika_colormap_editor,
|
||||
PIKA_TYPE_IMAGE_EDITOR,
|
||||
@ -127,6 +131,9 @@ pika_colormap_editor_constructed (GObject *object)
|
||||
pika_editor_add_action_button (PIKA_EDITOR (editor), "colormap",
|
||||
"colormap-edit-color",
|
||||
NULL);
|
||||
pika_editor_add_action_button (PIKA_EDITOR (editor), "colormap",
|
||||
"colormap-delete-color",
|
||||
NULL);
|
||||
|
||||
pika_editor_add_action_button (PIKA_EDITOR (editor), "colormap",
|
||||
"colormap-add-color-from-fg",
|
||||
@ -197,6 +204,9 @@ pika_colormap_editor_set_context (PikaDocked *docked,
|
||||
g_signal_connect (editor->selection, "popup-menu",
|
||||
G_CALLBACK (pika_colormap_editor_entry_popup),
|
||||
editor);
|
||||
g_signal_connect (editor->selection, "notify::index",
|
||||
G_CALLBACK (pika_colormap_editor_notify_index),
|
||||
editor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,6 +291,43 @@ pika_colormap_editor_edit_color (PikaColormapEditor *editor)
|
||||
gtk_window_present (GTK_WINDOW (editor->color_dialog));
|
||||
}
|
||||
|
||||
void
|
||||
pika_colormap_editor_delete_color (PikaColormapEditor *editor)
|
||||
{
|
||||
PikaColormapSelection *selection;
|
||||
PikaImage *image;
|
||||
gint index;
|
||||
|
||||
g_return_if_fail (PIKA_IS_COLORMAP_EDITOR (editor));
|
||||
g_return_if_fail (pika_colormap_editor_is_color_deletable (editor));
|
||||
|
||||
image = PIKA_IMAGE_EDITOR (editor)->image;
|
||||
selection = PIKA_COLORMAP_SELECTION (editor->selection);
|
||||
index = pika_colormap_selection_get_index (selection, NULL);
|
||||
|
||||
pika_image_delete_colormap_entry (image, index, TRUE);
|
||||
}
|
||||
|
||||
gboolean
|
||||
pika_colormap_editor_is_color_deletable (PikaColormapEditor *editor)
|
||||
{
|
||||
PikaColormapSelection *selection;
|
||||
PikaImage *image;
|
||||
gint index;
|
||||
|
||||
g_return_val_if_fail (PIKA_IS_COLORMAP_EDITOR (editor), FALSE);
|
||||
|
||||
image = PIKA_IMAGE_EDITOR (editor)->image;
|
||||
selection = PIKA_COLORMAP_SELECTION (editor->selection);
|
||||
index = pika_colormap_selection_get_index (selection, NULL);
|
||||
|
||||
if (index == -1)
|
||||
/* No colormap. */
|
||||
return FALSE;
|
||||
else
|
||||
return ! pika_image_colormap_is_index_used (image, index);
|
||||
}
|
||||
|
||||
gint
|
||||
pika_colormap_editor_get_index (PikaColormapEditor *editor,
|
||||
const PikaRGB *search)
|
||||
@ -411,3 +458,16 @@ pika_colormap_editor_color_clicked (PikaColormapEditor *editor,
|
||||
else
|
||||
pika_context_set_foreground (image_editor->context, &entry->color);
|
||||
}
|
||||
|
||||
static void
|
||||
pika_colormap_editor_notify_index (PikaColormapSelection *selection,
|
||||
const GParamSpec *pspec,
|
||||
PikaColormapEditor *editor)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_COLORMAP_EDITOR (editor));
|
||||
|
||||
pika_editor_set_action_sensitive (PIKA_EDITOR (editor), "colormap",
|
||||
"colormap-delete-color",
|
||||
pika_colormap_editor_is_color_deletable (editor),
|
||||
_("The color is used in this indexed image"));
|
||||
}
|
||||
|
@ -50,19 +50,21 @@ struct _PikaColormapEditorClass
|
||||
};
|
||||
|
||||
|
||||
GType pika_colormap_editor_get_type (void) G_GNUC_CONST;
|
||||
GType pika_colormap_editor_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GtkWidget * pika_colormap_editor_new (PikaMenuFactory *menu_factory);
|
||||
GtkWidget * pika_colormap_editor_new (PikaMenuFactory *menu_factory);
|
||||
|
||||
void pika_colormap_editor_edit_color (PikaColormapEditor *editor);
|
||||
void pika_colormap_editor_edit_color (PikaColormapEditor *editor);
|
||||
void pika_colormap_editor_delete_color (PikaColormapEditor *editor);
|
||||
gboolean pika_colormap_editor_is_color_deletable (PikaColormapEditor *editor);
|
||||
|
||||
gint pika_colormap_editor_get_index (PikaColormapEditor *editor,
|
||||
const PikaRGB *search);
|
||||
gboolean pika_colormap_editor_set_index (PikaColormapEditor *editor,
|
||||
gint index,
|
||||
PikaRGB *color);
|
||||
gint pika_colormap_editor_get_index (PikaColormapEditor *editor,
|
||||
const PikaRGB *search);
|
||||
gboolean pika_colormap_editor_set_index (PikaColormapEditor *editor,
|
||||
gint index,
|
||||
PikaRGB *color);
|
||||
|
||||
gint pika_colormap_editor_max_index (PikaColormapEditor *editor);
|
||||
gint pika_colormap_editor_max_index (PikaColormapEditor *editor);
|
||||
|
||||
|
||||
#endif /* __PIKA_COLORMAP_EDITOR_H__ */
|
||||
|
@ -57,7 +57,8 @@
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_CONTEXT
|
||||
PROP_CONTEXT,
|
||||
PROP_INDEX
|
||||
};
|
||||
|
||||
enum
|
||||
@ -169,6 +170,10 @@ pika_colormap_selection_class_init (PikaColormapSelectionClass* klass)
|
||||
PIKA_TYPE_CONTEXT,
|
||||
PIKA_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT));
|
||||
g_object_class_install_property (object_class, PROP_INDEX,
|
||||
g_param_spec_int ("index", NULL, NULL,
|
||||
0, G_MAXINT, 0,
|
||||
PIKA_PARAM_READABLE));
|
||||
}
|
||||
|
||||
static void
|
||||
@ -285,6 +290,9 @@ pika_colormap_selection_get_property (GObject *object,
|
||||
case PROP_CONTEXT:
|
||||
g_value_set_object (value, selection->context);
|
||||
break;
|
||||
case PROP_INDEX:
|
||||
g_value_set_int (value, selection->col_index);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
@ -433,6 +441,7 @@ pika_colormap_selection_set_index (PikaColormapSelection *selection,
|
||||
|
||||
selection->col_index = index;
|
||||
|
||||
g_object_notify (G_OBJECT (selection), "index");
|
||||
pika_palette_view_select_entry (PIKA_PALETTE_VIEW (selection->view),
|
||||
pika_palette_get_entry (palette, index));
|
||||
|
||||
@ -789,6 +798,8 @@ pika_colormap_selection_set_palette (PikaColormapSelection *selection)
|
||||
{
|
||||
if (selection->active_palette)
|
||||
{
|
||||
pika_colormap_selection_set_index (selection, 0, NULL);
|
||||
|
||||
g_signal_handlers_disconnect_by_func (selection->active_palette,
|
||||
G_CALLBACK (gtk_widget_queue_draw),
|
||||
selection);
|
||||
|
@ -899,6 +899,7 @@ pika_container_tree_view_select_items (PikaContainerView *view,
|
||||
GList *path;
|
||||
gboolean free_paths = FALSE;
|
||||
gboolean scroll_to_first = TRUE;
|
||||
GtkTreePath *focused_path = NULL;
|
||||
|
||||
/* If @paths is not set, compute it ourselves. */
|
||||
if (g_list_length (items) != g_list_length (paths))
|
||||
@ -926,6 +927,95 @@ pika_container_tree_view_select_items (PikaContainerView *view,
|
||||
pika_container_tree_view_selection_changed,
|
||||
tree_view);
|
||||
gtk_tree_selection_unselect_all (tree_view->priv->selection);
|
||||
gtk_tree_view_get_cursor (tree_view->view, &focused_path, NULL);
|
||||
if (focused_path != NULL)
|
||||
{
|
||||
GtkTreePath *closer_up = NULL;
|
||||
GtkTreePath *closer_down = NULL;
|
||||
|
||||
for (path = paths; path; path = path->next)
|
||||
{
|
||||
if (gtk_tree_path_compare (path->data, focused_path) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (gtk_tree_path_compare (path->data, focused_path) == -1)
|
||||
{
|
||||
if (closer_up == NULL || gtk_tree_path_compare (path->data, closer_up) == 1)
|
||||
closer_up = path->data;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (closer_down == NULL || gtk_tree_path_compare (path->data, closer_down) == -1)
|
||||
closer_down = path->data;
|
||||
}
|
||||
}
|
||||
|
||||
if (path == NULL)
|
||||
{
|
||||
/* The current cursor is not part of the selection. This may happen in
|
||||
* particular with a ctrl-click interaction which would deselect the
|
||||
* item the cursor is now on.
|
||||
*/
|
||||
g_clear_pointer (&focused_path, gtk_tree_path_free);
|
||||
|
||||
if (closer_up != NULL || closer_down != NULL)
|
||||
{
|
||||
GtkTreePath *first = NULL;
|
||||
GtkTreePath *last = NULL;
|
||||
|
||||
if (gtk_tree_view_get_visible_range (tree_view->view, &first, &last))
|
||||
{
|
||||
if (closer_up != NULL &&
|
||||
gtk_tree_path_compare (closer_up, first) >= 0 &&
|
||||
gtk_tree_path_compare (closer_up, last) <= 0)
|
||||
focused_path = gtk_tree_path_copy (closer_up);
|
||||
else if (closer_down != NULL &&
|
||||
gtk_tree_path_compare (closer_down, first) >= 0 &&
|
||||
gtk_tree_path_compare (closer_down, last) <= 0)
|
||||
focused_path = gtk_tree_path_copy (closer_down);
|
||||
}
|
||||
|
||||
if (focused_path == NULL)
|
||||
{
|
||||
if (closer_up != NULL)
|
||||
focused_path = gtk_tree_path_copy (closer_up);
|
||||
else
|
||||
focused_path = gtk_tree_path_copy (closer_down);
|
||||
}
|
||||
|
||||
gtk_tree_path_free (first);
|
||||
gtk_tree_path_free (last);
|
||||
}
|
||||
else if (paths != NULL)
|
||||
{
|
||||
focused_path = gtk_tree_path_copy (paths->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (paths != NULL)
|
||||
{
|
||||
focused_path = gtk_tree_path_copy (paths->data);
|
||||
}
|
||||
/* Setting a cursor will reset the selection, so we must do it first. We don't
|
||||
* want to change the cursor (which is likely the last clicked item), yet we
|
||||
* also want to make sure that the cursor cannot end up out of the selected
|
||||
* items, leading to discrepancy between pointer and keyboard navigation. This
|
||||
* is why we set the cursor with this priority:
|
||||
* 1. unchanged if it stays within selection;
|
||||
* 2. closer item above the current cursor, if visible;
|
||||
* 3. closer item below the current cursor, if visible;
|
||||
* 4. closer item above the current cursor (even though invisible, which will
|
||||
* make the view scroll up);
|
||||
* 5. closer item below the current cursor if there are no items above (view
|
||||
* will scroll down);
|
||||
* 6. top selected item if there was no current cursor;
|
||||
* 7. nothing if no selected items.
|
||||
*/
|
||||
if (focused_path != NULL)
|
||||
gtk_tree_view_set_cursor (tree_view->view, focused_path, NULL, FALSE);
|
||||
gtk_tree_path_free (focused_path);
|
||||
|
||||
for (item = items, path = paths; item && path; item = item->next, path = path->next)
|
||||
{
|
||||
GtkTreePath *parent_path;
|
||||
@ -1267,12 +1357,10 @@ pika_container_tree_view_button (GtkWidget *widget,
|
||||
PikaContainerView *container_view = PIKA_CONTAINER_VIEW (tree_view);
|
||||
GtkTreeViewColumn *column;
|
||||
GtkTreePath *path;
|
||||
gboolean handled = TRUE;
|
||||
|
||||
tree_view->priv->dnd_renderer = NULL;
|
||||
|
||||
if (bevent->type != GDK_BUTTON_RELEASE && ! gtk_widget_has_focus (widget))
|
||||
gtk_widget_grab_focus (widget);
|
||||
|
||||
if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
|
||||
bevent->x, bevent->y,
|
||||
&path, &column, NULL, NULL))
|
||||
@ -1283,10 +1371,10 @@ pika_container_tree_view_button (GtkWidget *widget,
|
||||
GtkCellRenderer *edit_cell = NULL;
|
||||
GdkRectangle column_area;
|
||||
GtkTreeIter iter;
|
||||
gboolean handled = TRUE;
|
||||
gboolean multisel_mode;
|
||||
GdkModifierType modifiers = (bevent->state & pika_get_all_modifiers_mask ());
|
||||
|
||||
handled = TRUE;
|
||||
multisel_mode = (gtk_tree_selection_get_mode (tree_view->priv->selection)
|
||||
== GTK_SELECTION_MULTIPLE);
|
||||
|
||||
@ -1301,6 +1389,19 @@ pika_container_tree_view_button (GtkWidget *widget,
|
||||
multisel_mode = FALSE;
|
||||
}
|
||||
|
||||
/* We need to grab focus after a button click, in order to make keyboard
|
||||
* navigation possible. For multi-selection though, actual selection will
|
||||
* happen in pika_container_tree_view_selection_changed() but the widget
|
||||
* must already be focused or the handler won't run.
|
||||
* Whereas for single selection, grab must happen after we changed the
|
||||
* selection (which will happen in this function) otherwise we end up
|
||||
* first scrolling to the current selection. So we have a separate
|
||||
* gtk_widget_grab_focus() at the end of the function.
|
||||
* See also commit 3e101922 and MR !1128.
|
||||
*/
|
||||
if (multisel_mode && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget))
|
||||
gtk_widget_grab_focus (widget);
|
||||
|
||||
gtk_tree_model_get_iter (tree_view->model, &iter, path);
|
||||
|
||||
renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (tree_view->model), &iter);
|
||||
@ -1607,7 +1708,7 @@ pika_container_tree_view_button (GtkWidget *widget,
|
||||
gtk_tree_path_free (path);
|
||||
g_object_unref (renderer);
|
||||
|
||||
return multisel_mode ? handled : (bevent->type == GDK_BUTTON_RELEASE ? FALSE : TRUE);
|
||||
handled = (multisel_mode ? handled : (bevent->type == GDK_BUTTON_RELEASE ? FALSE : TRUE));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1616,8 +1717,13 @@ pika_container_tree_view_button (GtkWidget *widget,
|
||||
pika_editor_popup_menu_at_pointer (PIKA_EDITOR (tree_view), (GdkEvent *) bevent);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
handled = TRUE;
|
||||
}
|
||||
|
||||
if (handled && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget))
|
||||
gtk_widget_grab_focus (widget);
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
/* We want to zoom on each 1/4 scroll events to roughly match zooming
|
||||
|
@ -1195,9 +1195,7 @@ pika_container_view_remove (PikaContainerView *view,
|
||||
|
||||
if (insert_data)
|
||||
{
|
||||
PIKA_CONTAINER_VIEW_GET_IFACE (view)->remove_item (view,
|
||||
viewable,
|
||||
insert_data);
|
||||
PIKA_CONTAINER_VIEW_GET_IFACE (view)->remove_item (view, viewable, insert_data);
|
||||
|
||||
g_hash_table_remove (private->item_hash, viewable);
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ pika_data_factory_view_constructed (GObject *object)
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
|
||||
pika_container_editor_set_selection_mode (editor, GTK_SELECTION_MULTIPLE);
|
||||
pika_container_editor_set_selection_mode (editor, GTK_SELECTION_SINGLE);
|
||||
|
||||
if (PIKA_IS_CONTAINER_TREE_VIEW (editor->view))
|
||||
{
|
||||
|
@ -778,6 +778,31 @@ pika_editor_add_action_button (PikaEditor *editor,
|
||||
return button;
|
||||
}
|
||||
|
||||
void
|
||||
pika_editor_set_action_sensitive (PikaEditor *editor,
|
||||
const gchar *group_name,
|
||||
const gchar *action_name,
|
||||
gboolean sensitive,
|
||||
const gchar *reason)
|
||||
{
|
||||
PikaActionGroup *group;
|
||||
PikaAction *action;
|
||||
|
||||
g_return_if_fail (PIKA_IS_EDITOR (editor));
|
||||
g_return_if_fail (action_name != NULL);
|
||||
g_return_if_fail (editor->priv->ui_manager != NULL);
|
||||
|
||||
group = pika_ui_manager_get_action_group (editor->priv->ui_manager,
|
||||
group_name);
|
||||
|
||||
g_return_if_fail (group != NULL);
|
||||
|
||||
action = pika_action_group_get_action (group, action_name);
|
||||
g_return_if_fail (action != NULL);
|
||||
|
||||
pika_action_set_sensitive (action, sensitive, reason);
|
||||
}
|
||||
|
||||
void
|
||||
pika_editor_set_show_name (PikaEditor *editor,
|
||||
gboolean show)
|
||||
|
@ -89,6 +89,11 @@ GtkWidget * pika_editor_add_action_button (PikaEditor *editor,
|
||||
const gchar *group_name,
|
||||
const gchar *action_name,
|
||||
...) G_GNUC_NULL_TERMINATED;
|
||||
void pika_editor_set_action_sensitive (PikaEditor *editor,
|
||||
const gchar *group_name,
|
||||
const gchar *action_name,
|
||||
gboolean sensitive,
|
||||
const gchar *reason);
|
||||
|
||||
void pika_editor_set_show_name (PikaEditor *editor,
|
||||
gboolean show);
|
||||
|
@ -90,6 +90,8 @@ static gboolean pika_file_dialog_delete_event (GtkWidget *w
|
||||
GdkEventAny *event);
|
||||
static void pika_file_dialog_response (GtkDialog *dialog,
|
||||
gint response_id);
|
||||
static void pika_file_dialog_map (PikaFileDialog *dialog,
|
||||
gpointer data);
|
||||
static GFile * pika_file_dialog_real_get_default_folder (PikaFileDialog *dialog);
|
||||
static void pika_file_dialog_real_save_state (PikaFileDialog *dialog,
|
||||
const gchar *state_name);
|
||||
@ -108,7 +110,7 @@ static void pika_file_dialog_progress_set_value (PikaProgress *p
|
||||
gdouble percentage);
|
||||
static gdouble pika_file_dialog_progress_get_value (PikaProgress *progress);
|
||||
static void pika_file_dialog_progress_pulse (PikaProgress *progress);
|
||||
static guint32 pika_file_dialog_progress_get_window_id (PikaProgress *progress);
|
||||
static GBytes * pika_file_dialog_progress_get_window_id (PikaProgress *progress);
|
||||
|
||||
static void pika_file_dialog_add_user_dir (PikaFileDialog *dialog,
|
||||
GUserDirectory directory);
|
||||
@ -229,6 +231,11 @@ pika_file_dialog_class_init (PikaFileDialogClass *klass)
|
||||
static void
|
||||
pika_file_dialog_init (PikaFileDialog *dialog)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
g_signal_connect (dialog, "map",
|
||||
G_CALLBACK (pika_file_dialog_map),
|
||||
NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
@ -372,6 +379,8 @@ pika_file_dialog_constructed (GObject *object)
|
||||
dialog->progress = pika_progress_box_new ();
|
||||
gtk_box_pack_end (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
|
||||
dialog->progress, FALSE, FALSE, 0);
|
||||
|
||||
pika_widget_set_native_handle (GTK_WIDGET (dialog), &dialog->window_handle);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -422,6 +431,15 @@ pika_file_dialog_response (GtkDialog *dialog,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pika_file_dialog_map (PikaFileDialog *dialog,
|
||||
gpointer data)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
pika_window_set_title_bar_theme (dialog->pika, GTK_WIDGET (dialog), FALSE);
|
||||
#endif
|
||||
}
|
||||
|
||||
static GFile *
|
||||
pika_file_dialog_real_get_default_folder (PikaFileDialog *dialog)
|
||||
{
|
||||
@ -579,12 +597,12 @@ pika_file_dialog_progress_pulse (PikaProgress *progress)
|
||||
pika_progress_pulse (PIKA_PROGRESS (dialog->progress));
|
||||
}
|
||||
|
||||
static guint32
|
||||
static GBytes *
|
||||
pika_file_dialog_progress_get_window_id (PikaProgress *progress)
|
||||
{
|
||||
PikaFileDialog *dialog = PIKA_FILE_DIALOG (progress);
|
||||
|
||||
return pika_window_get_native_id (GTK_WINDOW (dialog));
|
||||
return dialog->window_handle;
|
||||
}
|
||||
|
||||
|
||||
|
@ -42,6 +42,8 @@ struct _PikaFileDialog
|
||||
{
|
||||
GtkFileChooserDialog parent_instance;
|
||||
|
||||
GBytes *window_handle;
|
||||
|
||||
Pika *pika;
|
||||
PikaImage *image;
|
||||
|
||||
|
@ -110,7 +110,7 @@ pika_font_select_run_callback (PikaPdbDialog *dialog,
|
||||
dialog->caller_context,
|
||||
NULL, error,
|
||||
dialog->callback_name,
|
||||
G_TYPE_STRING, pika_object_get_name (object),
|
||||
G_TYPE_BOOLEAN, closing,
|
||||
PIKA_TYPE_RESOURCE, object,
|
||||
G_TYPE_BOOLEAN, closing,
|
||||
G_TYPE_NONE);
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pika_gradient_select_run_callback (PikaPdbDialog *dialog,
|
||||
dialog->caller_context,
|
||||
NULL, error,
|
||||
dialog->callback_name,
|
||||
G_TYPE_STRING, pika_object_get_name (object),
|
||||
PIKA_TYPE_RESOURCE, object,
|
||||
G_TYPE_INT, array->length / sizeof (gdouble),
|
||||
PIKA_TYPE_FLOAT_ARRAY, array->data,
|
||||
G_TYPE_BOOLEAN, closing,
|
||||
|
@ -451,12 +451,7 @@ pika_help_browser_error (Pika *pika,
|
||||
-1);
|
||||
|
||||
if (progress)
|
||||
{
|
||||
guint32 window_id = pika_progress_get_window_id (progress);
|
||||
|
||||
if (window_id)
|
||||
pika_window_set_transient_for (GTK_WINDOW (dialog), window_id);
|
||||
}
|
||||
pika_window_set_transient_for (GTK_WINDOW (dialog), progress);
|
||||
|
||||
pika_message_box_set_primary_text (PIKA_MESSAGE_DIALOG (dialog)->box,
|
||||
"%s", primary);
|
||||
@ -785,12 +780,7 @@ pika_help_query_alt_user_manual (PikaIdleHelp *idle_help)
|
||||
idle_help->query_dialog = GTK_DIALOG (dialog);
|
||||
|
||||
if (idle_help->progress)
|
||||
{
|
||||
guint32 window_id = pika_progress_get_window_id (idle_help->progress);
|
||||
|
||||
if (window_id)
|
||||
pika_window_set_transient_for (GTK_WINDOW (dialog), window_id);
|
||||
}
|
||||
pika_window_set_transient_for (GTK_WINDOW (dialog), idle_help->progress);
|
||||
|
||||
pika_message_box_set_primary_text (PIKA_MESSAGE_DIALOG (dialog)->box,
|
||||
_("The PIKA user manual is not installed "
|
||||
|
@ -823,14 +823,29 @@ pika_layer_tree_view_select_items (PikaContainerView *view,
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
for (layers = items, path = paths;
|
||||
layers && path;
|
||||
layers = layers->next, path = path->next)
|
||||
if (g_list_length (items) == 1)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
GtkTreeIter *iter = NULL;
|
||||
|
||||
gtk_tree_model_get_iter (tree_view->model, &iter, path->data);
|
||||
pika_layer_tree_view_update_borders (layer_view, &iter);
|
||||
iter =
|
||||
pika_container_view_lookup (PIKA_CONTAINER_VIEW (layer_view),
|
||||
PIKA_VIEWABLE (layers->data));
|
||||
|
||||
if (iter)
|
||||
pika_layer_tree_view_update_borders (layer_view, iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (layers = items, path = paths;
|
||||
layers && path;
|
||||
layers = layers->next, path = path->next)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
|
||||
gtk_tree_model_get_iter (tree_view->model, &iter,
|
||||
path->data);
|
||||
pika_layer_tree_view_update_borders (layer_view, &iter);
|
||||
}
|
||||
}
|
||||
|
||||
pika_layer_tree_view_update_options (layer_view, items);
|
||||
@ -2156,13 +2171,14 @@ pika_layer_tree_view_layer_clicked (PikaCellRendererViewable *cell,
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (! modifiers && mask && pika_layer_get_edit_mask (layer))
|
||||
if (! modifiers)
|
||||
{
|
||||
/* Simple clicks (without modifiers) activate the layer */
|
||||
|
||||
if (mask)
|
||||
if (mask && pika_layer_get_edit_mask (layer))
|
||||
pika_action_group_set_action_active (group,
|
||||
"layers-mask-edit", FALSE);
|
||||
else
|
||||
pika_layer_tree_view_update_borders (layer_view, &iter);
|
||||
}
|
||||
|
||||
g_object_unref (renderer);
|
||||
|
@ -560,7 +560,7 @@ pika_palette_editor_pick_color (PikaPaletteEditor *editor,
|
||||
case PIKA_COLOR_PICK_STATE_UPDATE:
|
||||
case PIKA_COLOR_PICK_STATE_END:
|
||||
pika_palette_set_entry_color (PIKA_PALETTE (data),
|
||||
index, color);
|
||||
index, color, FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -113,8 +113,8 @@ pika_palette_select_run_callback (PikaPdbDialog *dialog,
|
||||
dialog->caller_context,
|
||||
NULL, error,
|
||||
dialog->callback_name,
|
||||
G_TYPE_STRING, pika_object_get_name (object),
|
||||
G_TYPE_INT, pika_palette_get_n_colors (palette),
|
||||
G_TYPE_BOOLEAN, closing,
|
||||
PIKA_TYPE_RESOURCE, object,
|
||||
G_TYPE_INT, pika_palette_get_n_colors (palette),
|
||||
G_TYPE_BOOLEAN, closing,
|
||||
G_TYPE_NONE);
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ pika_pattern_select_run_callback (PikaPdbDialog *dialog,
|
||||
dialog->caller_context,
|
||||
NULL, error,
|
||||
dialog->callback_name,
|
||||
G_TYPE_STRING, pika_object_get_name (object),
|
||||
PIKA_TYPE_RESOURCE, object,
|
||||
G_TYPE_INT, pika_temp_buf_get_width (pattern->mask),
|
||||
G_TYPE_INT, pika_temp_buf_get_height (pattern->mask),
|
||||
G_TYPE_INT, babl_format_get_bytes_per_pixel (pika_temp_buf_get_format (pattern->mask)),
|
||||
|
@ -36,6 +36,7 @@
|
||||
|
||||
#include "core/pika.h"
|
||||
#include "core/pikacontext.h"
|
||||
#include "core/pikaresource.h"
|
||||
|
||||
#include "pdb/pikapdb.h"
|
||||
|
||||
@ -58,22 +59,24 @@ enum
|
||||
};
|
||||
|
||||
|
||||
static void pika_pdb_dialog_constructed (GObject *object);
|
||||
static void pika_pdb_dialog_dispose (GObject *object);
|
||||
static void pika_pdb_dialog_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void pika_pdb_dialog_constructed (GObject *object);
|
||||
static void pika_pdb_dialog_dispose (GObject *object);
|
||||
static void pika_pdb_dialog_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
|
||||
static void pika_pdb_dialog_response (GtkDialog *dialog,
|
||||
gint response_id);
|
||||
static void pika_pdb_dialog_response (GtkDialog *dialog,
|
||||
gint response_id);
|
||||
|
||||
static void pika_pdb_dialog_context_changed (PikaContext *context,
|
||||
PikaObject *object,
|
||||
PikaPdbDialog *dialog);
|
||||
static void pika_pdb_dialog_plug_in_closed (PikaPlugInManager *manager,
|
||||
PikaPlugIn *plug_in,
|
||||
PikaPdbDialog *dialog);
|
||||
static void pika_pdb_dialog_context_changed (PikaContext *context,
|
||||
PikaObject *object,
|
||||
PikaPdbDialog *dialog);
|
||||
static void pika_pdb_dialog_plug_in_closed (PikaPlugInManager *manager,
|
||||
PikaPlugIn *plug_in,
|
||||
PikaPdbDialog *dialog);
|
||||
|
||||
static gboolean pika_pdb_dialog_run_callback_idle (PikaPdbDialog *dialog);
|
||||
|
||||
|
||||
G_DEFINE_ABSTRACT_TYPE (PikaPdbDialog, pika_pdb_dialog, PIKA_TYPE_DIALOG)
|
||||
@ -96,6 +99,8 @@ pika_pdb_dialog_class_init (PikaPdbDialogClass *klass)
|
||||
dialog_class->response = pika_pdb_dialog_response;
|
||||
|
||||
klass->run_callback = NULL;
|
||||
klass->get_object = NULL;
|
||||
klass->set_object = NULL;
|
||||
|
||||
g_object_class_install_property (object_class, PROP_CONTEXT,
|
||||
g_param_spec_object ("context", NULL, NULL,
|
||||
@ -142,7 +147,9 @@ static void
|
||||
pika_pdb_dialog_init (PikaPdbDialog *dialog)
|
||||
{
|
||||
gtk_dialog_add_button (GTK_DIALOG (dialog),
|
||||
_("_Close"), GTK_RESPONSE_CLOSE);
|
||||
_("_OK"), GTK_RESPONSE_OK);
|
||||
gtk_dialog_add_button (GTK_DIALOG (dialog),
|
||||
_("_Cancel"), GTK_RESPONSE_CANCEL);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -164,17 +171,19 @@ pika_pdb_dialog_constructed (GObject *object)
|
||||
G_OBJECT_TYPE_NAME (object),
|
||||
NULL);
|
||||
|
||||
pika_context_set_by_type (dialog->context, dialog->select_type,
|
||||
dialog->initial_object);
|
||||
if (g_type_is_a (dialog->select_type, PIKA_TYPE_RESOURCE))
|
||||
{
|
||||
pika_context_set_by_type (dialog->context, dialog->select_type,
|
||||
dialog->initial_object);
|
||||
|
||||
dialog->initial_object = NULL;
|
||||
signal_name = pika_context_type_to_signal_name (dialog->select_type);
|
||||
|
||||
signal_name = pika_context_type_to_signal_name (dialog->select_type);
|
||||
g_signal_connect_object (dialog->context, signal_name,
|
||||
G_CALLBACK (pika_pdb_dialog_context_changed),
|
||||
dialog, 0);
|
||||
}
|
||||
|
||||
g_signal_connect_object (dialog->context, signal_name,
|
||||
G_CALLBACK (pika_pdb_dialog_context_changed),
|
||||
dialog, 0);
|
||||
g_signal_connect_object (dialog->context->pika->plug_in_manager,
|
||||
g_signal_connect_object (dialog->caller_context->pika->plug_in_manager,
|
||||
"plug-in-closed",
|
||||
G_CALLBACK (pika_pdb_dialog_plug_in_closed),
|
||||
dialog, 0);
|
||||
@ -191,6 +200,7 @@ pika_pdb_dialog_dispose (GObject *object)
|
||||
g_clear_object (&dialog->pdb);
|
||||
g_clear_object (&dialog->caller_context);
|
||||
g_clear_object (&dialog->context);
|
||||
g_clear_object (&dialog->initial_object);
|
||||
|
||||
g_clear_pointer (&dialog->callback_name, g_free);
|
||||
|
||||
@ -222,8 +232,7 @@ pika_pdb_dialog_set_property (GObject *object,
|
||||
break;
|
||||
|
||||
case PROP_INITIAL_OBJECT:
|
||||
/* don't ref, see constructor */
|
||||
dialog->initial_object = g_value_get_object (value);
|
||||
dialog->initial_object = g_value_dup_object (value);
|
||||
break;
|
||||
|
||||
case PROP_CALLBACK_NAME:
|
||||
@ -248,9 +257,24 @@ pika_pdb_dialog_response (GtkDialog *gtk_dialog,
|
||||
{
|
||||
PikaPdbDialog *dialog = PIKA_PDB_DIALOG (gtk_dialog);
|
||||
|
||||
if (response_id != GTK_RESPONSE_OK)
|
||||
{
|
||||
if (g_type_is_a (dialog->select_type, PIKA_TYPE_RESOURCE))
|
||||
{
|
||||
pika_context_set_by_type (dialog->context, dialog->select_type,
|
||||
dialog->initial_object);
|
||||
}
|
||||
else
|
||||
{
|
||||
PikaPdbDialogClass *klass = PIKA_PDB_DIALOG_GET_CLASS (dialog);
|
||||
|
||||
g_return_if_fail (klass->set_object != NULL);
|
||||
klass->set_object (dialog, dialog->initial_object);
|
||||
}
|
||||
}
|
||||
|
||||
pika_pdb_dialog_run_callback (&dialog, TRUE);
|
||||
if (dialog)
|
||||
gtk_widget_destroy (GTK_WIDGET (dialog));
|
||||
gtk_widget_destroy (GTK_WIDGET (dialog));
|
||||
}
|
||||
|
||||
void
|
||||
@ -261,16 +285,26 @@ pika_pdb_dialog_run_callback (PikaPdbDialog **dialog,
|
||||
PikaObject *object;
|
||||
|
||||
g_object_add_weak_pointer (G_OBJECT (*dialog), (gpointer) dialog);
|
||||
object = pika_context_get_by_type ((*dialog)->context, (*dialog)->select_type);
|
||||
|
||||
if (*dialog && object &&
|
||||
if (g_type_is_a ((*dialog)->select_type, PIKA_TYPE_RESOURCE))
|
||||
{
|
||||
object = pika_context_get_by_type ((*dialog)->context, (*dialog)->select_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_return_if_fail (klass->get_object != NULL);
|
||||
object = klass->get_object (*dialog);
|
||||
}
|
||||
|
||||
if (*dialog &&
|
||||
klass->run_callback &&
|
||||
(*dialog)->callback_name &&
|
||||
! (*dialog)->callback_busy)
|
||||
{
|
||||
(*dialog)->callback_busy = TRUE;
|
||||
|
||||
if (pika_pdb_lookup_procedure ((*dialog)->pdb, (*dialog)->callback_name))
|
||||
if (pika_pdb_lookup_procedure ((*dialog)->pdb, (*dialog)->callback_name) &&
|
||||
(object == NULL || g_type_is_a (G_TYPE_FROM_INSTANCE (object), (*dialog)->select_type)))
|
||||
{
|
||||
PikaValueArray *return_vals;
|
||||
GError *error = NULL;
|
||||
@ -288,7 +322,7 @@ pika_pdb_dialog_run_callback (PikaPdbDialog **dialog,
|
||||
else
|
||||
message = _("The corresponding plug-in may have crashed.");
|
||||
|
||||
pika_message ((*dialog)->context->pika, G_OBJECT (*dialog),
|
||||
pika_message ((*dialog)->caller_context->pika, G_OBJECT (*dialog),
|
||||
PIKA_MESSAGE_ERROR,
|
||||
_("Unable to run %s callback.\n%s"),
|
||||
g_type_name (G_TYPE_FROM_INSTANCE (*dialog)),
|
||||
@ -296,7 +330,7 @@ pika_pdb_dialog_run_callback (PikaPdbDialog **dialog,
|
||||
}
|
||||
else if (*dialog && error)
|
||||
{
|
||||
pika_message_literal ((*dialog)->context->pika, G_OBJECT (*dialog),
|
||||
pika_message_literal ((*dialog)->caller_context->pika, G_OBJECT (*dialog),
|
||||
PIKA_MESSAGE_ERROR,
|
||||
error->message);
|
||||
g_error_free (error);
|
||||
@ -342,8 +376,47 @@ pika_pdb_dialog_context_changed (PikaContext *context,
|
||||
PikaObject *object,
|
||||
PikaPdbDialog *dialog)
|
||||
{
|
||||
/* XXX Long explanation below because this was annoying to debug!
|
||||
*
|
||||
* The context might change for 2 reasons: either from code coming from the
|
||||
* plug-in or because of direct GUI interaction with the dialog. This second
|
||||
* case may happen while the plug-in is doing something else completely and
|
||||
* making PDB calls too. If the callback itself triggers the plug-in to run
|
||||
* other PDB calls, we may end up in a case where PDB requests made by the
|
||||
* plug-in receive the wrong result. Here is an actual case I encountered:
|
||||
*
|
||||
* 1. The plug-in calls pika-gradient-get-uniform-samples to draw a button.
|
||||
* 2. Because you click inside the dialog (using any of the Gimp*Select
|
||||
* subclasses, e.g. PikaBrushSelect), the core dialog run its callback
|
||||
* indicating that a resource changed. This is unrelated to step 1. It just
|
||||
* happens nearly in the same time by a race condition.
|
||||
* 3. The plug-in receives the callback as a temp procedure request, processes
|
||||
* it first. In particular, as part of the code in libpika/plug-in, it
|
||||
* verifies the resource is valid calling pika-resource-id-is-valid.
|
||||
* 4. Meanwhile, the core returns pika-gradient-get-uniform-samples result.
|
||||
* 5. The plug-in takes it as a result for pika-resource-id-is-valid with
|
||||
* invalid return values (size and types).
|
||||
* 6. Then again, it receives result of pika-resource-id-is-valid as being the
|
||||
* result of pika-gradient-get-uniform-samples, again invalid.
|
||||
*
|
||||
* This scenario mostly happens because the callback is not the consequence of
|
||||
* the plug-in's requests (if the callback from core were the consequence of a
|
||||
* call from the plug-in, the PDB protocol would ensure that every message in
|
||||
* both direction get its return values in the proper order). The problem here
|
||||
* is really that the callback interrupts unexpectedly PDB requests coming
|
||||
* from the plug-in while the plug-in process can't really know that this
|
||||
* callback is not a consequence of its own PDB call.
|
||||
*
|
||||
* Running as idle will make the callback run when the core is not already
|
||||
* processing anything, which should also mean that the plug-in is not waiting
|
||||
* for an unrelated procedure result.
|
||||
* To be fair, I am not 100% sure if a complex race condition may not still
|
||||
* happen, but extensive testing were successful so far.
|
||||
*/
|
||||
if (object)
|
||||
pika_pdb_dialog_run_callback (&dialog, FALSE);
|
||||
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
|
||||
(GSourceFunc) pika_pdb_dialog_run_callback_idle,
|
||||
g_object_ref (dialog), g_object_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -359,3 +432,11 @@ pika_pdb_dialog_plug_in_closed (PikaPlugInManager *manager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pika_pdb_dialog_run_callback_idle (PikaPdbDialog *dialog)
|
||||
{
|
||||
pika_pdb_dialog_run_callback (&dialog, FALSE);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
@ -73,6 +73,9 @@ struct _PikaPdbDialogClass
|
||||
PikaObject *object,
|
||||
gboolean closing,
|
||||
GError **error);
|
||||
PikaObject * (* get_object) (PikaPdbDialog *dialog);
|
||||
void (* set_object) (PikaPdbDialog *dialog,
|
||||
PikaObject *object);
|
||||
};
|
||||
|
||||
|
||||
|
@ -61,6 +61,10 @@ struct _PikaPickableButtonPrivate
|
||||
PikaPickable *pickable;
|
||||
|
||||
GtkWidget *view;
|
||||
|
||||
GBinding *popup_binding;
|
||||
GtkWidget *popup;
|
||||
PikaPickable *initial_pickable;
|
||||
};
|
||||
|
||||
|
||||
@ -78,8 +82,8 @@ static void pika_pickable_button_get_property (GObject *object,
|
||||
|
||||
static void pika_pickable_button_clicked (GtkButton *button);
|
||||
|
||||
static void pika_pickable_button_popup_confirm (PikaPickablePopup *popup,
|
||||
PikaPickableButton *button);
|
||||
static void pika_pickable_button_popup_confirm (PikaPickableButton *button);
|
||||
static void pika_pickable_button_popup_cancel (PikaPickableButton *button);
|
||||
static void pika_pickable_button_drop_pickable (GtkWidget *widget,
|
||||
gint x,
|
||||
gint y,
|
||||
@ -131,6 +135,7 @@ pika_pickable_button_init (PikaPickableButton *button)
|
||||
|
||||
button->private->view_size = PIKA_VIEW_SIZE_LARGE;
|
||||
button->private->view_border_width = 1;
|
||||
button->private->popup_binding = NULL;
|
||||
|
||||
pika_dnd_viewable_dest_add (GTK_WIDGET (button), PIKA_TYPE_LAYER,
|
||||
pika_pickable_button_drop_pickable,
|
||||
@ -182,6 +187,9 @@ pika_pickable_button_finalize (GObject *object)
|
||||
PikaPickableButton *button = PIKA_PICKABLE_BUTTON (object);
|
||||
|
||||
g_clear_object (&button->private->context);
|
||||
g_clear_object (&button->private->initial_pickable);
|
||||
if (button->private->popup)
|
||||
gtk_widget_destroy (button->private->popup);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
@ -244,23 +252,39 @@ pika_pickable_button_clicked (GtkButton *button)
|
||||
pickable_button->private->view_size,
|
||||
pickable_button->private->view_border_width);
|
||||
|
||||
g_signal_connect (popup, "confirm",
|
||||
G_CALLBACK (pika_pickable_button_popup_confirm),
|
||||
button);
|
||||
g_signal_connect_swapped (popup, "confirm",
|
||||
G_CALLBACK (pika_pickable_button_popup_confirm),
|
||||
button);
|
||||
g_signal_connect_swapped (popup, "cancel",
|
||||
G_CALLBACK (pika_pickable_button_popup_cancel),
|
||||
button);
|
||||
pickable_button->private->popup_binding = g_object_bind_property (G_OBJECT (button), "pickable",
|
||||
G_OBJECT (popup), "pickable",
|
||||
G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
pickable_button->private->initial_pickable = pickable_button->private->pickable;
|
||||
if (pickable_button->private->initial_pickable)
|
||||
g_object_ref (pickable_button->private->initial_pickable);
|
||||
|
||||
pickable_button->private->popup = popup;
|
||||
g_signal_connect (popup, "destroy",
|
||||
G_CALLBACK (gtk_widget_destroyed),
|
||||
&pickable_button->private->popup);
|
||||
pika_popup_show (PIKA_POPUP (popup), GTK_WIDGET (button));
|
||||
}
|
||||
|
||||
static void
|
||||
pika_pickable_button_popup_confirm (PikaPickablePopup *popup,
|
||||
PikaPickableButton *button)
|
||||
pika_pickable_button_popup_confirm (PikaPickableButton *button)
|
||||
{
|
||||
PikaPickable *pickable = pika_pickable_popup_get_pickable (popup);
|
||||
|
||||
if (pickable)
|
||||
pika_pickable_button_set_pickable (button, pickable);
|
||||
g_clear_pointer (&button->private->popup_binding, g_binding_unbind);
|
||||
g_clear_object (&button->private->initial_pickable);
|
||||
}
|
||||
|
||||
static void
|
||||
pika_pickable_button_popup_cancel (PikaPickableButton *button)
|
||||
{
|
||||
pika_pickable_button_set_pickable (button, button->private->initial_pickable);
|
||||
pika_pickable_button_popup_confirm (button);
|
||||
}
|
||||
static void
|
||||
pika_pickable_button_drop_pickable (GtkWidget *widget,
|
||||
gint x,
|
||||
|
@ -41,6 +41,7 @@
|
||||
|
||||
#include "pikacontainertreeview.h"
|
||||
#include "pikacontainerview.h"
|
||||
#include "pikapickablechooser.h"
|
||||
#include "pikapickablepopup.h"
|
||||
#include "pikaviewrenderer.h"
|
||||
|
||||
@ -58,12 +59,13 @@ enum
|
||||
|
||||
struct _PikaPickablePopupPrivate
|
||||
{
|
||||
PikaPickable *pickable;
|
||||
PikaContext *context;
|
||||
|
||||
gint view_size;
|
||||
gint view_border_width;
|
||||
|
||||
GtkWidget *chooser;
|
||||
|
||||
GtkWidget *image_view;
|
||||
GtkWidget *layer_view;
|
||||
GtkWidget *channel_view;
|
||||
@ -71,24 +73,19 @@ struct _PikaPickablePopupPrivate
|
||||
};
|
||||
|
||||
|
||||
static void pika_pickable_popup_constructed (GObject *object);
|
||||
static void pika_pickable_popup_finalize (GObject *object);
|
||||
static void pika_pickable_popup_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void pika_pickable_popup_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void pika_pickable_popup_constructed (GObject *object);
|
||||
static void pika_pickable_popup_finalize (GObject *object);
|
||||
static void pika_pickable_popup_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void pika_pickable_popup_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
|
||||
static void pika_pickable_popup_image_changed (PikaContext *context,
|
||||
PikaImage *image,
|
||||
PikaPickablePopup *popup);
|
||||
static void pika_pickable_popup_item_activate (PikaContainerView *view,
|
||||
PikaPickable *pickable,
|
||||
gpointer unused,
|
||||
PikaPickablePopup *popup);
|
||||
static void pika_pickable_popup_activate (PikaPickablePopup *popup);
|
||||
static void pika_pickable_popup_notify_pickable (PikaPickablePopup *popup);
|
||||
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (PikaPickablePopup, pika_pickable_popup,
|
||||
@ -118,7 +115,7 @@ pika_pickable_popup_class_init (PikaPickablePopupClass *klass)
|
||||
g_param_spec_object ("pickable",
|
||||
NULL, NULL,
|
||||
PIKA_TYPE_PICKABLE,
|
||||
PIKA_PARAM_READABLE));
|
||||
PIKA_PARAM_READWRITE));
|
||||
|
||||
g_object_class_install_property (object_class, PROP_VIEW_SIZE,
|
||||
g_param_spec_int ("view-size",
|
||||
@ -153,114 +150,22 @@ static void
|
||||
pika_pickable_popup_constructed (GObject *object)
|
||||
{
|
||||
PikaPickablePopup *popup = PIKA_PICKABLE_POPUP (object);
|
||||
GtkWidget *frame;
|
||||
GtkWidget *hbox;
|
||||
GtkWidget *vbox;
|
||||
GtkWidget *label;
|
||||
GtkWidget *notebook;
|
||||
PikaImage *image;
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
|
||||
pika_assert (PIKA_IS_CONTEXT (popup->priv->context));
|
||||
|
||||
frame = gtk_frame_new (NULL);
|
||||
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
|
||||
gtk_container_add (GTK_CONTAINER (popup), frame);
|
||||
gtk_widget_show (frame);
|
||||
|
||||
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
|
||||
gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
|
||||
gtk_container_add (GTK_CONTAINER (frame), hbox);
|
||||
gtk_widget_show (hbox);
|
||||
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
|
||||
gtk_widget_show (vbox);
|
||||
|
||||
label = gtk_label_new (_("Images"));
|
||||
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
|
||||
gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
|
||||
gtk_widget_show (label);
|
||||
|
||||
popup->priv->image_view =
|
||||
pika_container_tree_view_new (popup->priv->context->pika->images,
|
||||
popup->priv->context,
|
||||
popup->priv->view_size,
|
||||
popup->priv->view_border_width);
|
||||
pika_container_box_set_size_request (PIKA_CONTAINER_BOX (popup->priv->image_view),
|
||||
4 * (popup->priv->view_size +
|
||||
2 * popup->priv->view_border_width),
|
||||
4 * (popup->priv->view_size +
|
||||
2 * popup->priv->view_border_width));
|
||||
gtk_box_pack_start (GTK_BOX (vbox), popup->priv->image_view, TRUE, TRUE, 0);
|
||||
gtk_widget_show (popup->priv->image_view);
|
||||
|
||||
g_signal_connect_object (popup->priv->image_view, "activate-item",
|
||||
G_CALLBACK (pika_pickable_popup_item_activate),
|
||||
G_OBJECT (popup), 0);
|
||||
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
|
||||
gtk_widget_show (vbox);
|
||||
|
||||
popup->priv->layer_label = label =
|
||||
gtk_label_new (_("Select an image in the left pane"));
|
||||
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
|
||||
gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
|
||||
gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
|
||||
gtk_widget_show (label);
|
||||
|
||||
notebook = gtk_notebook_new ();
|
||||
gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0);
|
||||
gtk_widget_show (notebook);
|
||||
|
||||
popup->priv->layer_view =
|
||||
pika_container_tree_view_new (NULL,
|
||||
popup->priv->context,
|
||||
popup->priv->view_size,
|
||||
popup->priv->view_border_width);
|
||||
gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (PIKA_CONTAINER_TREE_VIEW (popup->priv->layer_view)->view),
|
||||
TRUE);
|
||||
pika_container_box_set_size_request (PIKA_CONTAINER_BOX (popup->priv->layer_view),
|
||||
4 * (popup->priv->view_size +
|
||||
2 * popup->priv->view_border_width),
|
||||
4 * (popup->priv->view_size +
|
||||
2 * popup->priv->view_border_width));
|
||||
gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
|
||||
popup->priv->layer_view,
|
||||
gtk_label_new (_("Layers")));
|
||||
gtk_widget_show (popup->priv->layer_view);
|
||||
|
||||
g_signal_connect_object (popup->priv->layer_view, "activate-item",
|
||||
G_CALLBACK (pika_pickable_popup_item_activate),
|
||||
G_OBJECT (popup), 0);
|
||||
|
||||
popup->priv->channel_view =
|
||||
pika_container_tree_view_new (NULL,
|
||||
popup->priv->context,
|
||||
popup->priv->view_size,
|
||||
popup->priv->view_border_width);
|
||||
pika_container_box_set_size_request (PIKA_CONTAINER_BOX (popup->priv->channel_view),
|
||||
4 * (popup->priv->view_size +
|
||||
2 * popup->priv->view_border_width),
|
||||
4 * (popup->priv->view_size +
|
||||
2 * popup->priv->view_border_width));
|
||||
gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
|
||||
popup->priv->channel_view,
|
||||
gtk_label_new (_("Channels")));
|
||||
gtk_widget_show (popup->priv->channel_view);
|
||||
|
||||
g_signal_connect_object (popup->priv->channel_view, "activate-item",
|
||||
G_CALLBACK (pika_pickable_popup_item_activate),
|
||||
G_OBJECT (popup), 0);
|
||||
|
||||
g_signal_connect_object (popup->priv->context, "image-changed",
|
||||
G_CALLBACK (pika_pickable_popup_image_changed),
|
||||
G_OBJECT (popup), 0);
|
||||
|
||||
image = pika_context_get_image (popup->priv->context);
|
||||
pika_pickable_popup_image_changed (popup->priv->context, image, popup);
|
||||
popup->priv->chooser = pika_pickable_chooser_new (popup->priv->context, PIKA_TYPE_PICKABLE,
|
||||
popup->priv->view_size,
|
||||
popup->priv->view_border_width);
|
||||
gtk_container_add (GTK_CONTAINER (popup), popup->priv->chooser);
|
||||
g_signal_connect_swapped (popup->priv->chooser, "notify::pickable",
|
||||
G_CALLBACK (pika_pickable_popup_notify_pickable),
|
||||
popup);
|
||||
g_signal_connect_swapped (popup->priv->chooser, "activate",
|
||||
G_CALLBACK (pika_pickable_popup_activate),
|
||||
popup);
|
||||
gtk_widget_show (popup->priv->chooser);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -268,7 +173,6 @@ pika_pickable_popup_finalize (GObject *object)
|
||||
{
|
||||
PikaPickablePopup *popup = PIKA_PICKABLE_POPUP (object);
|
||||
|
||||
g_clear_object (&popup->priv->pickable);
|
||||
g_clear_object (&popup->priv->context);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
@ -290,6 +194,11 @@ pika_pickable_popup_set_property (GObject *object,
|
||||
popup->priv->context = g_value_dup_object (value);
|
||||
break;
|
||||
|
||||
case PROP_PICKABLE:
|
||||
pika_pickable_chooser_set_pickable (PIKA_PICKABLE_CHOOSER (popup->priv->chooser),
|
||||
g_value_get_object (value));
|
||||
break;
|
||||
|
||||
case PROP_VIEW_SIZE:
|
||||
popup->priv->view_size = g_value_get_int (value);
|
||||
break;
|
||||
@ -298,7 +207,6 @@ pika_pickable_popup_set_property (GObject *object,
|
||||
popup->priv->view_border_width = g_value_get_int (value);
|
||||
break;
|
||||
|
||||
case PROP_PICKABLE:
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
@ -320,7 +228,7 @@ pika_pickable_popup_get_property (GObject *object,
|
||||
break;
|
||||
|
||||
case PROP_PICKABLE:
|
||||
g_value_set_object (value, popup->priv->pickable);
|
||||
g_value_set_object (value, pika_pickable_popup_get_pickable (popup));
|
||||
break;
|
||||
|
||||
case PROP_VIEW_SIZE:
|
||||
@ -337,6 +245,9 @@ pika_pickable_popup_get_property (GObject *object,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Public functions */
|
||||
|
||||
GtkWidget *
|
||||
pika_pickable_popup_new (PikaContext *context,
|
||||
gint view_size,
|
||||
@ -360,82 +271,22 @@ pika_pickable_popup_new (PikaContext *context,
|
||||
PikaPickable *
|
||||
pika_pickable_popup_get_pickable (PikaPickablePopup *popup)
|
||||
{
|
||||
GtkWidget *focus;
|
||||
PikaPickable *pickable = NULL;
|
||||
|
||||
g_return_val_if_fail (PIKA_IS_PICKABLE_POPUP (popup), NULL);
|
||||
|
||||
focus = gtk_window_get_focus (GTK_WINDOW (popup));
|
||||
|
||||
if (focus && gtk_widget_is_ancestor (focus, popup->priv->image_view))
|
||||
{
|
||||
pickable = PIKA_PICKABLE (pika_context_get_image (popup->priv->context));
|
||||
}
|
||||
else if (focus && gtk_widget_is_ancestor (focus, popup->priv->layer_view))
|
||||
{
|
||||
GList *selected;
|
||||
|
||||
if (pika_container_view_get_selected (PIKA_CONTAINER_VIEW (popup->priv->layer_view),
|
||||
&selected, NULL))
|
||||
{
|
||||
pickable = selected->data;
|
||||
g_list_free (selected);
|
||||
}
|
||||
}
|
||||
else if (focus && gtk_widget_is_ancestor (focus, popup->priv->channel_view))
|
||||
{
|
||||
GList *selected;
|
||||
|
||||
if (pika_container_view_get_selected (PIKA_CONTAINER_VIEW (popup->priv->channel_view),
|
||||
&selected, NULL))
|
||||
{
|
||||
pickable = selected->data;
|
||||
g_list_free (selected);
|
||||
}
|
||||
}
|
||||
|
||||
return pickable;
|
||||
return pika_pickable_chooser_get_pickable (PIKA_PICKABLE_CHOOSER (popup->priv->chooser));
|
||||
}
|
||||
|
||||
|
||||
/* private functions */
|
||||
/* Private functions */
|
||||
|
||||
static void
|
||||
pika_pickable_popup_image_changed (PikaContext *context,
|
||||
PikaImage *image,
|
||||
PikaPickablePopup *popup)
|
||||
{
|
||||
PikaContainer *layers = NULL;
|
||||
PikaContainer *channels = NULL;
|
||||
|
||||
if (image)
|
||||
{
|
||||
gchar *desc;
|
||||
|
||||
layers = pika_image_get_layers (image);
|
||||
channels = pika_image_get_channels (image);
|
||||
|
||||
desc = pika_viewable_get_description (PIKA_VIEWABLE (image), NULL);
|
||||
gtk_label_set_text (GTK_LABEL (popup->priv->layer_label), desc);
|
||||
g_free (desc);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_label_set_text (GTK_LABEL (popup->priv->layer_label),
|
||||
_("Select an image in the left pane"));
|
||||
}
|
||||
|
||||
pika_container_view_set_container (PIKA_CONTAINER_VIEW (popup->priv->layer_view),
|
||||
layers);
|
||||
pika_container_view_set_container (PIKA_CONTAINER_VIEW (popup->priv->channel_view),
|
||||
channels);
|
||||
}
|
||||
|
||||
static void
|
||||
pika_pickable_popup_item_activate (PikaContainerView *view,
|
||||
PikaPickable *pickable,
|
||||
gpointer unused,
|
||||
PikaPickablePopup *popup)
|
||||
pika_pickable_popup_activate (PikaPickablePopup *popup)
|
||||
{
|
||||
g_signal_emit_by_name (popup, "confirm");
|
||||
}
|
||||
|
||||
static void
|
||||
pika_pickable_popup_notify_pickable (PikaPickablePopup *popup)
|
||||
{
|
||||
g_object_notify (G_OBJECT (popup), "pickable");
|
||||
}
|
||||
|
@ -42,6 +42,11 @@ enum
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
typedef struct _PikaPopupPrivate
|
||||
{
|
||||
GtkWidget *parent;
|
||||
} PikaPopupPrivate;
|
||||
|
||||
|
||||
static gboolean pika_popup_map_event (GtkWidget *widget,
|
||||
GdkEventAny *event);
|
||||
@ -54,7 +59,7 @@ static void pika_popup_real_cancel (PikaPopup *popup);
|
||||
static void pika_popup_real_confirm (PikaPopup *popup);
|
||||
|
||||
|
||||
G_DEFINE_TYPE (PikaPopup, pika_popup, GTK_TYPE_WINDOW)
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (PikaPopup, pika_popup, GTK_TYPE_WINDOW)
|
||||
|
||||
#define parent_class pika_popup_parent_class
|
||||
|
||||
@ -179,7 +184,8 @@ pika_popup_button_press (GtkWidget *widget,
|
||||
GdkEventButton *bevent)
|
||||
{
|
||||
GtkWidget *event_widget;
|
||||
gboolean cancel = FALSE;
|
||||
gboolean cancel = FALSE;
|
||||
gboolean confirm = FALSE;
|
||||
|
||||
event_widget = gtk_get_event_widget ((GdkEvent *) bevent);
|
||||
|
||||
@ -204,15 +210,22 @@ pika_popup_button_press (GtkWidget *widget,
|
||||
}
|
||||
else if (gtk_widget_get_toplevel (event_widget) != widget)
|
||||
{
|
||||
PikaPopupPrivate *priv;
|
||||
/* the event was on a pika widget, but not inside the popup */
|
||||
|
||||
cancel = TRUE;
|
||||
priv = pika_popup_get_instance_private (PIKA_POPUP (widget));
|
||||
if (event_widget == priv->parent || gtk_widget_is_ancestor (event_widget, priv->parent))
|
||||
confirm = TRUE;
|
||||
else
|
||||
cancel = TRUE;
|
||||
}
|
||||
|
||||
if (cancel)
|
||||
g_signal_emit (widget, popup_signals[CANCEL], 0);
|
||||
else if (confirm)
|
||||
g_signal_emit (widget, popup_signals[CONFIRM], 0);
|
||||
|
||||
return cancel;
|
||||
return (cancel || confirm);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -279,23 +292,39 @@ pika_popup_real_confirm (PikaPopup *popup)
|
||||
gtk_widget_destroy (widget);
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_popup_show:
|
||||
* @popup:
|
||||
* @widget: the parent widget which will determine @popup location.
|
||||
*
|
||||
* Shows @popup above @widget. Its placement will be determined relatively to
|
||||
* @widget.
|
||||
* Moreover clicking outside @popup but within @widget will be considered a
|
||||
* "confirm" signal. Typically @widget would be a button. A typical UX could be:
|
||||
* clicking it once would show the popup; clicking it again would confirm the
|
||||
* choice within the popup.
|
||||
*/
|
||||
void
|
||||
pika_popup_show (PikaPopup *popup,
|
||||
GtkWidget *widget)
|
||||
{
|
||||
GdkDisplay *display;
|
||||
GdkMonitor *monitor;
|
||||
GtkRequisition requisition;
|
||||
GtkAllocation allocation;
|
||||
GdkRectangle rect;
|
||||
gint orig_x;
|
||||
gint orig_y;
|
||||
gint x;
|
||||
gint y;
|
||||
PikaPopupPrivate *priv;
|
||||
GdkDisplay *display;
|
||||
GdkMonitor *monitor;
|
||||
GtkRequisition requisition;
|
||||
GtkAllocation allocation;
|
||||
GdkRectangle rect;
|
||||
gint orig_x;
|
||||
gint orig_y;
|
||||
gint x;
|
||||
gint y;
|
||||
|
||||
g_return_if_fail (PIKA_IS_POPUP (popup));
|
||||
g_return_if_fail (GTK_IS_WIDGET (widget));
|
||||
|
||||
priv = pika_popup_get_instance_private (popup);
|
||||
priv->parent = widget;
|
||||
|
||||
gtk_widget_get_preferred_size (GTK_WIDGET (popup), &requisition, NULL);
|
||||
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
|
@ -161,10 +161,20 @@ pika_procedure_action_activate (GAction *action,
|
||||
{
|
||||
gsize hack = GPOINTER_TO_SIZE (procedure_action->procedure);
|
||||
|
||||
g_object_add_weak_pointer (G_OBJECT (action), (gpointer) &action);
|
||||
pika_action_emit_activate (PIKA_ACTION (action),
|
||||
g_variant_new_uint64 (hack));
|
||||
|
||||
pika_action_history_action_activated (PIKA_ACTION (action));
|
||||
if (action != NULL)
|
||||
{
|
||||
g_object_remove_weak_pointer (G_OBJECT (action), (gpointer) &action);
|
||||
/* An action might disappear between the moment it's run and the
|
||||
* moment we log it. I experienced this when script-fu crashed, though
|
||||
* we could imagine that it may be soon possible in normal process too
|
||||
* for procedures to get un-registered (e.g. in extensions).
|
||||
*/
|
||||
pika_action_history_action_activated (PIKA_ACTION (action));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,12 @@
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#ifdef GDK_WINDOWING_WIN32
|
||||
#include <dwmapi.h>
|
||||
#include <gdk/gdkwin32.h>
|
||||
|
||||
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
@ -55,7 +60,10 @@
|
||||
|
||||
#include "gegl/pika-babl.h"
|
||||
|
||||
#include "config/pikaguiconfig.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
#include "core/pikaprogress.h"
|
||||
#include "core/pikatoolinfo.h"
|
||||
|
||||
#include "pikaaccellabel.h"
|
||||
@ -91,11 +99,18 @@ typedef struct
|
||||
gchar *settings_value;
|
||||
} BlinkStep;
|
||||
|
||||
static void pika_widget_blink_after (GtkWidget *widget,
|
||||
gint ms_timeout);
|
||||
static void pika_search_widget_rec (GtkWidget *widget,
|
||||
BlinkSearch *data);
|
||||
static void pika_blink_free_script (GList *blink_scenario);
|
||||
static void pika_widget_blink_after (GtkWidget *widget,
|
||||
gint ms_timeout);
|
||||
static void pika_search_widget_rec (GtkWidget *widget,
|
||||
BlinkSearch *data);
|
||||
static void pika_blink_free_script (GList *blink_scenario);
|
||||
|
||||
static gboolean pika_window_transient_on_mapped (GtkWidget *widget,
|
||||
GdkEventAny *event,
|
||||
PikaProgress *progress);
|
||||
static void pika_window_set_transient_cb (GtkWidget *window,
|
||||
GdkEventAny *event,
|
||||
GBytes *handle);
|
||||
|
||||
|
||||
GtkWidget *
|
||||
@ -909,105 +924,51 @@ pika_window_set_hint (GtkWindow *window,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_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.
|
||||
*
|
||||
* Returns: a native window ID of the window's #GdkWindow or 0
|
||||
* if the window isn't realized yet
|
||||
*/
|
||||
guint32
|
||||
pika_window_get_native_id (GtkWindow *window)
|
||||
{
|
||||
GdkWindow *surface;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_WINDOW (window), 0);
|
||||
|
||||
surface = gtk_widget_get_window (GTK_WIDGET (window));
|
||||
if (!surface) /* aka window is not yet realized */
|
||||
return 0;
|
||||
|
||||
#ifdef GDK_WINDOWING_WIN32
|
||||
if (GDK_IS_WIN32_WINDOW (surface))
|
||||
return GPOINTER_TO_INT (GDK_WINDOW_HWND (gtk_widget_get_window (GTK_WIDGET (window))));
|
||||
#endif
|
||||
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
if (GDK_IS_X11_WINDOW (surface))
|
||||
return GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (window)));
|
||||
#endif
|
||||
|
||||
#ifdef GDK_WINDOWING_WAYLAND
|
||||
if (GDK_IS_WAYLAND_WINDOW (surface))
|
||||
g_debug ("Getting window ID for progress not supported on Wayland yet");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef GDK_WINDOWING_WIN32
|
||||
static void
|
||||
pika_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 libpika/pikaui.c */
|
||||
static GdkWindow *
|
||||
pika_get_foreign_window (guint32 window)
|
||||
pika_get_foreign_window (gpointer window)
|
||||
{
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
|
||||
return gdk_x11_window_foreign_new_for_display (gdk_display_get_default (),
|
||||
window);
|
||||
(Window) window);
|
||||
#endif
|
||||
|
||||
#ifdef GDK_WINDOWING_WIN32
|
||||
return gdk_win32_window_foreign_new_for_display (gdk_display_get_default (),
|
||||
window);
|
||||
(HWND) window);
|
||||
#endif
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
pika_window_set_transient_for (GtkWindow *window,
|
||||
guint32 parent_ID)
|
||||
pika_window_set_transient_for (GtkWindow *window,
|
||||
PikaProgress *parent)
|
||||
{
|
||||
/* Cross-process transient-for is broken in gdk/win32 <= 2.10.6. It
|
||||
* causes hangs, at least when used as by the pika 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 libpika/pikaui.c
|
||||
*
|
||||
* Note: this hanging bug is still happening with GTK+3 as of 2019-10,
|
||||
* with steps described in comment 4 in:
|
||||
* https://bugzilla.gnome.org/show_bug.cgi?id=359538
|
||||
*/
|
||||
#ifndef GDK_WINDOWING_WIN32
|
||||
GdkWindow *parent;
|
||||
g_signal_connect_after (window, "map-event",
|
||||
G_CALLBACK (pika_window_transient_on_mapped),
|
||||
parent);
|
||||
|
||||
parent = pika_get_foreign_window (parent_ID);
|
||||
if (! parent)
|
||||
return;
|
||||
if (gtk_widget_get_mapped (GTK_WIDGET (window)))
|
||||
pika_window_transient_on_mapped (GTK_WIDGET (window), NULL, parent);
|
||||
}
|
||||
|
||||
if (gtk_widget_get_realized (GTK_WIDGET (window)))
|
||||
gdk_window_set_transient_for (gtk_widget_get_window (GTK_WIDGET (window)),
|
||||
parent);
|
||||
void
|
||||
pika_window_set_transient_for_handle (GtkWindow *window,
|
||||
GBytes *handle)
|
||||
{
|
||||
g_return_if_fail (GTK_IS_WINDOW (window));
|
||||
g_return_if_fail (handle != NULL);
|
||||
|
||||
g_signal_connect_object (window, "realize",
|
||||
G_CALLBACK (pika_window_transient_realized),
|
||||
parent, 0);
|
||||
g_signal_connect_data (window, "map-event",
|
||||
G_CALLBACK (pika_window_set_transient_cb),
|
||||
g_bytes_ref (handle),
|
||||
(GClosureNotify) g_bytes_unref,
|
||||
G_CONNECT_AFTER);
|
||||
|
||||
g_object_unref (parent);
|
||||
#endif
|
||||
if (gtk_widget_get_mapped (GTK_WIDGET (window)))
|
||||
pika_window_set_transient_cb (GTK_WIDGET (window), NULL, handle);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -2574,3 +2535,151 @@ pika_utils_are_menu_path_identical (const gchar *path1,
|
||||
|
||||
return identical;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pika_window_transient_on_mapped (GtkWidget *window,
|
||||
GdkEventAny *event G_GNUC_UNUSED,
|
||||
PikaProgress *progress)
|
||||
{
|
||||
GBytes *handle;
|
||||
|
||||
handle = pika_progress_get_window_id (progress);
|
||||
|
||||
if (handle == NULL)
|
||||
return FALSE;
|
||||
|
||||
pika_window_set_transient_cb (window, NULL, handle);
|
||||
|
||||
g_bytes_unref (handle);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
pika_window_set_transient_cb (GtkWidget *window,
|
||||
GdkEventAny *event G_GNUC_UNUSED,
|
||||
GBytes *handle)
|
||||
{
|
||||
gboolean transient_set = FALSE;
|
||||
|
||||
g_return_if_fail (handle != NULL);
|
||||
|
||||
#ifdef GDK_WINDOWING_WAYLAND
|
||||
if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()))
|
||||
{
|
||||
char *wayland_handle;
|
||||
|
||||
wayland_handle = (char *) g_bytes_get_data (handle, NULL);
|
||||
gdk_wayland_window_set_transient_for_exported (gtk_widget_get_window (window),
|
||||
wayland_handle);
|
||||
transient_set = TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
if (! transient_set && GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
|
||||
{
|
||||
GdkWindow *parent;
|
||||
Window *handle_data;
|
||||
Window parent_ID;
|
||||
gsize handle_size;
|
||||
|
||||
handle_data = (Window *) g_bytes_get_data (handle, &handle_size);
|
||||
g_return_if_fail (handle_size == sizeof (Window));
|
||||
parent_ID = *handle_data;
|
||||
|
||||
parent = pika_get_foreign_window ((gpointer) parent_ID);
|
||||
|
||||
if (parent)
|
||||
gdk_window_set_transient_for (gtk_widget_get_window (window), parent);
|
||||
|
||||
transient_set = TRUE;
|
||||
}
|
||||
#endif
|
||||
/* Cross-process transient-for is broken in gdk/win32. It causes hangs of the
|
||||
* main process and we still don't know why.
|
||||
* If it eventually is fixed to actually work, change this to a run-time check
|
||||
* of GTK+ version. Remember to change also pika_window_transient_on_mapped()
|
||||
* in libpika/pikaui.c
|
||||
*
|
||||
* Note: this hanging bug is still happening with GTK+3 as of mid-2023 with
|
||||
* steps described in comment 4 in:
|
||||
* https://bugzilla.gnome.org/show_bug.cgi?id=359538
|
||||
*/
|
||||
#if 0 && defined (GDK_WINDOWING_WIN32)
|
||||
if (! transient_set)
|
||||
{
|
||||
GdkWindow *parent;
|
||||
HANDLE *handle_data;
|
||||
HANDLE parent_ID;
|
||||
gsize handle_size;
|
||||
|
||||
handle_data = (HANDLE *) g_bytes_get_data (handle, &handle_size);
|
||||
g_return_if_fail (handle_size == sizeof (HANDLE));
|
||||
parent_ID = *handle_data;
|
||||
|
||||
parent = pika_get_foreign_window ((gpointer) parent_ID);
|
||||
|
||||
if (parent)
|
||||
gdk_window_set_transient_for (gtk_widget_get_window (window), parent);
|
||||
|
||||
transient_set = TRUE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
pika_window_set_title_bar_theme (Pika *pika,
|
||||
GtkWidget *dialog,
|
||||
gboolean is_main_window)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
HWND hwnd;
|
||||
GdkWindow *window = NULL;
|
||||
gboolean use_dark_mode = FALSE;
|
||||
|
||||
window = gtk_widget_get_window (GTK_WIDGET (dialog));
|
||||
if (window)
|
||||
{
|
||||
if (pika)
|
||||
{
|
||||
PikaGuiConfig *config;
|
||||
|
||||
config = PIKA_GUI_CONFIG (pika->config);
|
||||
use_dark_mode = config->prefer_dark_theme;
|
||||
}
|
||||
else
|
||||
{
|
||||
GtkStyleContext *style;
|
||||
GdkRGBA *color = NULL;
|
||||
|
||||
/* Workaround if we don't have access to PikaGuiConfig.
|
||||
* If the background color is below the threshold, then we're
|
||||
* likely in dark mode.
|
||||
*/
|
||||
style = gtk_widget_get_style_context (dialog);
|
||||
gtk_style_context_get (style, gtk_style_context_get_state (style),
|
||||
GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &color,
|
||||
NULL);
|
||||
if (color)
|
||||
{
|
||||
if (color->red < 0.5 && color->green < 0.5 && color->blue < 0.5)
|
||||
use_dark_mode = TRUE;
|
||||
|
||||
gdk_rgba_free (color);
|
||||
}
|
||||
}
|
||||
|
||||
hwnd = (HWND) gdk_win32_window_get_handle (window);
|
||||
DwmSetWindowAttribute (hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||
&use_dark_mode, sizeof (use_dark_mode));
|
||||
|
||||
if (! is_main_window)
|
||||
{
|
||||
/* Toggle the window's visibility so the title bar change appears */
|
||||
gdk_window_hide (window);
|
||||
gdk_window_show (window);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -75,9 +75,10 @@ gboolean pika_get_style_color (GtkWidget *widget
|
||||
GdkRGBA *color);
|
||||
void pika_window_set_hint (GtkWindow *window,
|
||||
PikaWindowHint hint);
|
||||
guint32 pika_window_get_native_id (GtkWindow *window);
|
||||
void pika_window_set_transient_for (GtkWindow *window,
|
||||
guint32 parent_ID);
|
||||
PikaProgress *progress);
|
||||
void pika_window_set_transient_for_handle (GtkWindow *window,
|
||||
GBytes *handle);
|
||||
void pika_widget_set_accel_help (GtkWidget *widget,
|
||||
PikaAction *action);
|
||||
|
||||
@ -168,5 +169,9 @@ gboolean pika_utils_are_menu_path_identical (const gchar *path1
|
||||
gchar **mnemonic_path1,
|
||||
gchar **path1_section_name);
|
||||
|
||||
void pika_window_set_title_bar_theme (Pika *pika,
|
||||
GtkWidget *dialog,
|
||||
gboolean is_main_window);
|
||||
|
||||
|
||||
#endif /* __APP_PIKA_WIDGETS_UTILS_H__ */
|
||||
|
@ -168,6 +168,7 @@ typedef struct _PikaFontSelect PikaFontSelect;
|
||||
typedef struct _PikaGradientSelect PikaGradientSelect;
|
||||
typedef struct _PikaPaletteSelect PikaPaletteSelect;
|
||||
typedef struct _PikaPatternSelect PikaPatternSelect;
|
||||
typedef struct _PikaPickableSelect PikaPickableSelect;
|
||||
typedef struct _PikaPdbDialog PikaPdbDialog;
|
||||
|
||||
|
||||
@ -225,6 +226,7 @@ typedef struct _PikaMeter PikaMeter;
|
||||
typedef struct _PikaModifiersEditor PikaModifiersEditor;
|
||||
typedef struct _PikaOverlayBox PikaOverlayBox;
|
||||
typedef struct _PikaPickableButton PikaPickableButton;
|
||||
typedef struct _PikaPickableChooser PikaPickableChooser;
|
||||
typedef struct _PikaPickablePopup PikaPickablePopup;
|
||||
typedef struct _PikaPivotSelector PikaPivotSelector;
|
||||
typedef struct _PikaPlugInView PikaPlugInView;
|
||||
|
Reference in New Issue
Block a user