1467 lines
45 KiB
C
1467 lines
45 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* pikaaction.c
|
|
* Copyright (C) 2004-2019 Michael Natterer <mitch@gimp.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libpikabase/pikabase.h"
|
|
#include "libpikacolor/pikacolor.h"
|
|
#include "libpikawidgets/pikawidgets.h"
|
|
|
|
#include "widgets-types.h"
|
|
|
|
#include "config/pikacoreconfig.h"
|
|
|
|
#include "core/pika.h"
|
|
#include "core/pikacontext.h"
|
|
#include "core/pikaimagefile.h" /* eek */
|
|
|
|
#include "pikaaction.h"
|
|
#include "pikaprocedureaction.h"
|
|
#include "pikaview.h"
|
|
#include "pikaviewrenderer.h"
|
|
#include "pikawidgets-utils.h"
|
|
|
|
|
|
enum
|
|
{
|
|
ACTIVATE,
|
|
ACCELS_CHANGED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
#define GET_PRIVATE(obj) (pika_action_get_private ((PikaAction *) (obj)))
|
|
|
|
typedef struct _PikaActionPrivate PikaActionPrivate;
|
|
|
|
struct _PikaActionPrivate
|
|
{
|
|
PikaContext *context;
|
|
/* This recursive pointer is needed for the finalize(). */
|
|
PikaAction *action;
|
|
PikaActionGroup *group;
|
|
|
|
gboolean sensitive;
|
|
gchar *disable_reason;
|
|
|
|
gboolean visible;
|
|
|
|
gchar *label;
|
|
gchar *short_label;
|
|
gchar *tooltip;
|
|
gchar *icon_name;
|
|
GIcon *icon;
|
|
|
|
gchar **accels;
|
|
gchar **default_accels;
|
|
|
|
gchar *menu_path;
|
|
|
|
PikaRGB *color;
|
|
PikaViewable *viewable;
|
|
PangoEllipsizeMode ellipsize;
|
|
gint max_width_chars;
|
|
|
|
GList *proxies;
|
|
};
|
|
|
|
|
|
static PikaActionPrivate *
|
|
pika_action_get_private (PikaAction *action);
|
|
static void pika_action_private_finalize (PikaActionPrivate *priv);
|
|
|
|
static void pika_action_label_notify (PikaAction *action,
|
|
const GParamSpec *pspec,
|
|
gpointer data);
|
|
|
|
static void pika_action_proxy_destroy (GtkWidget *proxy,
|
|
PikaAction *action);
|
|
static void pika_action_proxy_button_activate (GtkButton *button,
|
|
PikaAction *action);
|
|
|
|
static void pika_action_update_proxy_sensitive (PikaAction *action,
|
|
GtkWidget *proxy);
|
|
static void pika_action_update_proxy_visible (PikaAction *action,
|
|
GtkWidget *proxy);
|
|
static void pika_action_update_proxy_tooltip (PikaAction *action,
|
|
GtkWidget *proxy);
|
|
|
|
|
|
G_DEFINE_INTERFACE (PikaAction, pika_action, PIKA_TYPE_OBJECT)
|
|
|
|
static guint action_signals[LAST_SIGNAL];
|
|
|
|
|
|
static void
|
|
pika_action_default_init (PikaActionInterface *iface)
|
|
{
|
|
PikaRGB black;
|
|
|
|
action_signals[ACTIVATE] =
|
|
g_signal_new ("activate",
|
|
G_TYPE_FROM_INTERFACE (iface),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (PikaActionInterface, activate),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_VARIANT);
|
|
|
|
action_signals[ACCELS_CHANGED] =
|
|
g_signal_new ("accels-changed",
|
|
G_TYPE_FROM_INTERFACE (iface),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (PikaActionInterface, accels_changed),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_STRV);
|
|
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_object ("context",
|
|
NULL, NULL,
|
|
PIKA_TYPE_CONTEXT,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_boolean ("sensitive",
|
|
NULL, NULL,
|
|
TRUE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_boolean ("visible",
|
|
NULL, NULL,
|
|
TRUE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_string ("label",
|
|
NULL, NULL,
|
|
NULL,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_string ("short-label",
|
|
NULL, NULL,
|
|
NULL,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_string ("tooltip",
|
|
NULL, NULL,
|
|
NULL,
|
|
PIKA_PARAM_READWRITE));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_string ("icon-name",
|
|
NULL, NULL,
|
|
NULL,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_object ("icon",
|
|
NULL, NULL,
|
|
G_TYPE_ICON,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY));
|
|
|
|
pika_rgba_set (&black, 0.0, 0.0, 0.0, PIKA_OPACITY_OPAQUE);
|
|
g_object_interface_install_property (iface,
|
|
pika_param_spec_rgb ("color",
|
|
NULL, NULL,
|
|
TRUE, &black,
|
|
PIKA_PARAM_READWRITE));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_object ("viewable",
|
|
NULL, NULL,
|
|
PIKA_TYPE_VIEWABLE,
|
|
PIKA_PARAM_READWRITE));
|
|
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_enum ("ellipsize",
|
|
NULL, NULL,
|
|
PANGO_TYPE_ELLIPSIZE_MODE,
|
|
PANGO_ELLIPSIZE_NONE,
|
|
PIKA_PARAM_READWRITE));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_int ("max-width-chars",
|
|
NULL, NULL,
|
|
-1, G_MAXINT, -1,
|
|
PIKA_PARAM_READWRITE));
|
|
}
|
|
|
|
void
|
|
pika_action_init (PikaAction *action)
|
|
{
|
|
PikaActionPrivate *priv;
|
|
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
|
|
priv = GET_PRIVATE (action);
|
|
|
|
priv->action = action;
|
|
priv->group = NULL;
|
|
priv->sensitive = TRUE;
|
|
priv->visible = TRUE;
|
|
priv->label = NULL;
|
|
priv->short_label = NULL;
|
|
priv->tooltip = NULL;
|
|
priv->icon_name = NULL;
|
|
priv->icon = NULL;
|
|
priv->accels = NULL;
|
|
priv->default_accels = NULL;
|
|
priv->menu_path = NULL;
|
|
priv->ellipsize = PANGO_ELLIPSIZE_NONE;
|
|
priv->max_width_chars = -1;
|
|
priv->proxies = NULL;
|
|
|
|
g_signal_connect (action, "notify::label",
|
|
G_CALLBACK (pika_action_label_notify),
|
|
NULL);
|
|
g_signal_connect (action, "notify::short-label",
|
|
G_CALLBACK (pika_action_label_notify),
|
|
NULL);
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
void
|
|
pika_action_emit_activate (PikaAction *action,
|
|
GVariant *value)
|
|
{
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
|
|
if (value)
|
|
g_variant_ref_sink (value);
|
|
|
|
g_signal_emit (action, action_signals[ACTIVATE], 0, value);
|
|
|
|
if (value)
|
|
g_variant_unref (value);
|
|
}
|
|
|
|
void
|
|
pika_action_emit_change_state (PikaAction *action,
|
|
GVariant *value)
|
|
{
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
|
|
if (value)
|
|
g_variant_ref_sink (value);
|
|
|
|
g_signal_emit_by_name (action, "change-state", value);
|
|
|
|
if (value)
|
|
g_variant_unref (value);
|
|
}
|
|
|
|
const gchar *
|
|
pika_action_get_name (PikaAction *action)
|
|
{
|
|
return pika_object_get_name (PIKA_OBJECT (action));
|
|
}
|
|
|
|
PikaActionGroup *
|
|
pika_action_get_group (PikaAction *action)
|
|
{
|
|
return GET_PRIVATE (action)->group;
|
|
}
|
|
|
|
void
|
|
pika_action_set_label (PikaAction *action,
|
|
const gchar *label)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
if (g_strcmp0 (priv->label, label) != 0)
|
|
{
|
|
g_free (priv->label);
|
|
priv->label = g_strdup (label);
|
|
|
|
/* Set or update the proxy rendering. */
|
|
for (GList *list = priv->proxies; list; list = list->next)
|
|
pika_action_set_proxy (action, list->data);
|
|
|
|
g_object_notify (G_OBJECT (action), "label");
|
|
if (priv->short_label == NULL)
|
|
g_object_notify (G_OBJECT (action), "short-label");
|
|
}
|
|
}
|
|
|
|
const gchar *
|
|
pika_action_get_label (PikaAction *action)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
return priv->label;
|
|
}
|
|
|
|
void
|
|
pika_action_set_short_label (PikaAction *action,
|
|
const gchar *label)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
if (g_strcmp0 (priv->short_label, label) != 0)
|
|
{
|
|
g_free (priv->short_label);
|
|
priv->short_label = g_strdup (label);
|
|
|
|
/* Set or update the proxy rendering. */
|
|
for (GList *list = priv->proxies; list; list = list->next)
|
|
pika_action_set_proxy (action, list->data);
|
|
|
|
g_object_notify (G_OBJECT (action), "short-label");
|
|
}
|
|
}
|
|
|
|
const gchar *
|
|
pika_action_get_short_label (PikaAction *action)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
return priv->short_label ? priv->short_label : priv->label;
|
|
}
|
|
|
|
void
|
|
pika_action_set_tooltip (PikaAction *action,
|
|
const gchar *tooltip)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
if (g_strcmp0 (priv->tooltip, tooltip) != 0)
|
|
{
|
|
g_free (priv->tooltip);
|
|
priv->tooltip = g_strdup (tooltip);
|
|
|
|
pika_action_update_proxy_tooltip (action, NULL);
|
|
|
|
g_object_notify (G_OBJECT (action), "tooltip");
|
|
}
|
|
}
|
|
|
|
const gchar *
|
|
pika_action_get_tooltip (PikaAction *action)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
return priv->tooltip;
|
|
}
|
|
|
|
void
|
|
pika_action_set_icon_name (PikaAction *action,
|
|
const gchar *icon_name)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
if (g_strcmp0 (priv->icon_name, icon_name) != 0)
|
|
{
|
|
g_free (priv->icon_name);
|
|
priv->icon_name = g_strdup (icon_name);
|
|
|
|
/* Set or update the proxy rendering. */
|
|
for (GList *list = priv->proxies; list; list = list->next)
|
|
pika_action_set_proxy (action, list->data);
|
|
|
|
g_object_notify (G_OBJECT (action), "icon-name");
|
|
}
|
|
}
|
|
|
|
const gchar *
|
|
pika_action_get_icon_name (PikaAction *action)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
return priv->icon_name;
|
|
}
|
|
|
|
void
|
|
pika_action_set_gicon (PikaAction *action,
|
|
GIcon *icon)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
if (priv->icon != icon)
|
|
{
|
|
g_clear_object (&priv->icon);
|
|
priv->icon = g_object_ref (icon);
|
|
|
|
/* Set or update the proxy rendering. */
|
|
for (GList *list = priv->proxies; list; list = list->next)
|
|
pika_action_set_proxy (action, list->data);
|
|
|
|
g_object_notify (G_OBJECT (action), "icon");
|
|
}
|
|
}
|
|
|
|
GIcon *
|
|
pika_action_get_gicon (PikaAction *action)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
return priv->icon ? g_object_ref (priv->icon) : NULL;
|
|
}
|
|
|
|
void
|
|
pika_action_set_help_id (PikaAction *action,
|
|
const gchar *help_id)
|
|
{
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
|
|
g_object_set_qdata_full (G_OBJECT (action), PIKA_HELP_ID,
|
|
g_strdup (help_id),
|
|
(GDestroyNotify) g_free);
|
|
pika_action_update_proxy_tooltip (action, NULL);
|
|
}
|
|
|
|
const gchar *
|
|
pika_action_get_help_id (PikaAction *action)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_ACTION (action), NULL);
|
|
|
|
return g_object_get_qdata (G_OBJECT (action), PIKA_HELP_ID);
|
|
}
|
|
|
|
void
|
|
pika_action_set_visible (PikaAction *action,
|
|
gboolean visible)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
/* Only notify when the state actually changed. This is important for
|
|
* handlers such as visibility of menu items in PikaMenuModel which
|
|
* will assume that the action visibility changed. Otherwise we might
|
|
* remove items by mistake.
|
|
*/
|
|
if (priv->visible != visible)
|
|
{
|
|
priv->visible = visible;
|
|
|
|
pika_action_update_proxy_visible (action, NULL);
|
|
g_object_notify (G_OBJECT (action), "visible");
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
pika_action_get_visible (PikaAction *action)
|
|
{
|
|
return GET_PRIVATE (action)->visible;
|
|
}
|
|
|
|
gboolean
|
|
pika_action_is_visible (PikaAction *action)
|
|
{
|
|
gboolean visible;
|
|
|
|
visible = pika_action_get_visible (action);
|
|
|
|
if (visible)
|
|
{
|
|
/* TODO: check if the action group itself is visible.
|
|
* See implementation of gtk_action_is_visible().
|
|
*/
|
|
}
|
|
|
|
return visible;
|
|
}
|
|
|
|
void
|
|
pika_action_set_sensitive (PikaAction *action,
|
|
gboolean sensitive,
|
|
const gchar *reason)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
if (priv->sensitive != sensitive ||
|
|
(! sensitive && g_strcmp0 (reason, priv->disable_reason) != 0))
|
|
{
|
|
priv->sensitive = sensitive;
|
|
|
|
g_clear_pointer (&priv->disable_reason, g_free);
|
|
if (reason && ! sensitive)
|
|
priv->disable_reason = g_strdup (reason);
|
|
|
|
g_object_notify (G_OBJECT (action), "sensitive");
|
|
|
|
pika_action_update_proxy_sensitive (action, NULL);
|
|
|
|
g_object_notify (G_OBJECT (action), "enabled");
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
pika_action_get_sensitive (PikaAction *action,
|
|
const gchar **reason)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
gboolean sensitive;
|
|
|
|
sensitive = priv->sensitive;
|
|
|
|
if (reason)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
*reason = NULL;
|
|
|
|
if (! sensitive && priv->disable_reason != NULL)
|
|
*reason = (const gchar *) priv->disable_reason;
|
|
}
|
|
|
|
return sensitive;
|
|
}
|
|
|
|
gboolean
|
|
pika_action_is_sensitive (PikaAction *action,
|
|
const gchar **reason)
|
|
{
|
|
gboolean sensitive;
|
|
|
|
sensitive = pika_action_get_sensitive (action, reason);
|
|
|
|
if (sensitive)
|
|
{
|
|
/* TODO: check if the action group itself is sensitive.
|
|
* See implementation of gtk_action_is_sensitive().
|
|
*/
|
|
}
|
|
|
|
return sensitive;
|
|
}
|
|
|
|
/**
|
|
* pika_action_set_accels:
|
|
* @action: a #PikaAction
|
|
* @accels: accelerators in the format understood by gtk_accelerator_parse().
|
|
* The first accelerator is the main one.
|
|
*
|
|
* Set the accelerators to be associated with the given @action.
|
|
*
|
|
* Note that the #PikaAction API will emit the signal "accels-changed" whereas
|
|
* GtkApplication has no signal (that we could find) to connect to. It means we
|
|
* must always change accelerators with this function.
|
|
* Never use gtk_application_set_accels_for_action() directly!
|
|
*/
|
|
void
|
|
pika_action_set_accels (PikaAction *action,
|
|
const gchar **accels)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
|
|
if (accels != NULL && priv->accels != NULL &&
|
|
g_strv_equal (accels, (const gchar **) priv->accels))
|
|
return;
|
|
|
|
g_strfreev (priv->accels);
|
|
priv->accels = g_strdupv ((gchar **) accels);
|
|
|
|
g_signal_emit (action, action_signals[ACCELS_CHANGED], 0, accels);
|
|
}
|
|
|
|
/**
|
|
* pika_action_get_accels:
|
|
* @action: a #PikaAction
|
|
*
|
|
* Gets the accelerators that are currently associated with
|
|
* the given @action.
|
|
*
|
|
* Returns: (transfer full): accelerators for @action, as a %NULL-terminated
|
|
* array. Free with g_strfreev() when no longer needed
|
|
*/
|
|
const gchar **
|
|
pika_action_get_accels (PikaAction *action)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_ACTION (action), NULL);
|
|
|
|
return (const gchar **) GET_PRIVATE (action)->accels;
|
|
}
|
|
|
|
/**
|
|
* pika_action_get_default_accels:
|
|
* @action: a #PikaAction
|
|
*
|
|
* Gets the accelerators that are associated with the given @action by default.
|
|
* These might be different from pika_action_get_accels().
|
|
*
|
|
* Returns: (transfer full): default accelerators for @action, as a
|
|
* %NULL-terminated array. Free with g_strfreev() when
|
|
* no longer needed
|
|
*/
|
|
const gchar **
|
|
pika_action_get_default_accels (PikaAction *action)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_ACTION (action), NULL);
|
|
|
|
return (const gchar **) GET_PRIVATE (action)->default_accels;
|
|
}
|
|
|
|
/**
|
|
* pika_action_get_display_accels:
|
|
* @action: a #PikaAction
|
|
*
|
|
* Gets the accelerators that are currently associated with the given @action,
|
|
* in a format which can be presented to people on the GUI.
|
|
*
|
|
* Returns: (transfer full): accelerators for @action, as a %NULL-terminated
|
|
* array. Free with g_strfreev() when no longer needed
|
|
*/
|
|
gchar **
|
|
pika_action_get_display_accels (PikaAction *action)
|
|
{
|
|
gchar **accels;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (PIKA_IS_ACTION (action), NULL);
|
|
|
|
accels = g_strdupv (GET_PRIVATE (action)->accels);
|
|
|
|
for (i = 0; accels != NULL && accels[i] != NULL; i++)
|
|
{
|
|
guint accel_key = 0;
|
|
GdkModifierType accel_mods = 0;
|
|
|
|
gtk_accelerator_parse (accels[i], &accel_key, &accel_mods);
|
|
|
|
if (accel_key != 0 || accel_mods != 0)
|
|
{
|
|
gchar *accel;
|
|
|
|
accel = gtk_accelerator_get_label (accel_key, accel_mods);
|
|
|
|
g_free (accels[i]);
|
|
accels[i] = accel;
|
|
}
|
|
}
|
|
|
|
return accels;
|
|
}
|
|
|
|
/* This should return FALSE if the currently set accelerators are not the
|
|
* default accelerators or even if they are in different order (different
|
|
* primary accelerator in particular).
|
|
*/
|
|
gboolean
|
|
pika_action_use_default_accels (PikaAction *action)
|
|
{
|
|
gchar **default_accels;
|
|
gchar **accels;
|
|
|
|
g_return_val_if_fail (PIKA_IS_ACTION (action), TRUE);
|
|
|
|
default_accels = GET_PRIVATE (action)->default_accels;
|
|
accels = GET_PRIVATE (action)->accels;
|
|
|
|
if ((default_accels == NULL || g_strv_length (default_accels) == 0) &&
|
|
(accels == NULL || g_strv_length (accels) == 0))
|
|
return TRUE;
|
|
|
|
if (default_accels == NULL || accels == NULL ||
|
|
g_strv_length (default_accels) != g_strv_length (accels))
|
|
return FALSE;
|
|
|
|
/* An easy looking variant would be to simply compare with g_strv_equal() but
|
|
* this actually doesn't work because gtk_accelerator_parse() is liberal with
|
|
* the format (casing and abbreviations) and thus we need to have a canonical
|
|
* string version, or simply parse the accelerators down to get to the base
|
|
* key/modes, which is what I do here.
|
|
*/
|
|
for (gint i = 0; accels[i] != NULL; i++)
|
|
{
|
|
guint default_key;
|
|
GdkModifierType default_mods;
|
|
guint accelerator_key;
|
|
GdkModifierType accelerator_mods;
|
|
|
|
gtk_accelerator_parse (default_accels[i], &default_key, &default_mods);
|
|
gtk_accelerator_parse (accels[i], &accelerator_key, &accelerator_mods);
|
|
|
|
if (default_key != accelerator_key || default_mods != accelerator_mods)
|
|
return FALSE;
|
|
}
|
|
|
|
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)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_ACTION (action), NULL);
|
|
|
|
return GET_PRIVATE (action)->menu_path;
|
|
}
|
|
|
|
void
|
|
pika_action_activate (PikaAction *action)
|
|
{
|
|
g_return_if_fail (G_IS_ACTION (action));
|
|
|
|
g_action_activate (G_ACTION (action), NULL);
|
|
}
|
|
|
|
gint
|
|
pika_action_name_compare (PikaAction *action1,
|
|
PikaAction *action2)
|
|
{
|
|
return strcmp (pika_action_get_name (action1),
|
|
pika_action_get_name (action2));
|
|
}
|
|
|
|
gboolean
|
|
pika_action_is_gui_blacklisted (const gchar *action_name)
|
|
{
|
|
static const gchar *prefixes[] =
|
|
{
|
|
"<",
|
|
"tools-color-average-radius-",
|
|
"tools-paintbrush-size-",
|
|
"tools-paintbrush-aspect-ratio-",
|
|
"tools-paintbrush-angle-",
|
|
"tools-paintbrush-spacing-",
|
|
"tools-paintbrush-hardness-",
|
|
"tools-paintbrush-force-",
|
|
"tools-ink-blob-size-",
|
|
"tools-ink-blob-aspect-",
|
|
"tools-ink-blob-angle-",
|
|
"tools-mypaint-brush-radius-",
|
|
"tools-mypaint-brush-hardness-",
|
|
"tools-foreground-select-brush-size-",
|
|
"tools-transform-preview-opacity-",
|
|
"tools-warp-effect-size-",
|
|
"tools-warp-effect-hardness-"
|
|
};
|
|
|
|
static const gchar *actions[] =
|
|
{
|
|
"tools-brightness-contrast",
|
|
"tools-curves",
|
|
"tools-levels",
|
|
"tools-offset",
|
|
"tools-threshold",
|
|
"layers-mask-add-button"
|
|
};
|
|
|
|
gint i;
|
|
|
|
if (! (action_name && *action_name))
|
|
return TRUE;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (prefixes); i++)
|
|
{
|
|
if (g_str_has_prefix (action_name, prefixes[i]))
|
|
return TRUE;
|
|
}
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (actions); i++)
|
|
{
|
|
if (! strcmp (action_name, actions[i]))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
PikaViewable *
|
|
pika_action_get_viewable (PikaAction *action)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_ACTION (action), NULL);
|
|
|
|
return GET_PRIVATE (action)->viewable;
|
|
}
|
|
|
|
PikaContext *
|
|
pika_action_get_context (PikaAction *action)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_ACTION (action), NULL);
|
|
|
|
return GET_PRIVATE (action)->context;
|
|
}
|
|
|
|
|
|
/* Protected functions. */
|
|
|
|
/**
|
|
* pika_action_install_properties:
|
|
* @klass: the class structure for a type deriving from #GObject
|
|
*
|
|
* Installs the necessary properties for a class implementing
|
|
* #PikaAction. Please call this function in the *_class_init()
|
|
* function of the child class.
|
|
**/
|
|
void
|
|
pika_action_install_properties (GObjectClass *klass)
|
|
{
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_CONTEXT, "context");
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_SENSITIVE, "sensitive");
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_VISIBLE, "visible");
|
|
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_LABEL, "label");
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_SHORT_LABEL, "short-label");
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_TOOLTIP, "tooltip");
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_ICON_NAME, "icon-name");
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_ICON, "icon");
|
|
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_COLOR, "color");
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_VIEWABLE, "viewable");
|
|
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_ELLIPSIZE, "ellipsize");
|
|
g_object_class_override_property (klass, PIKA_ACTION_PROP_MAX_WIDTH_CHARS, "max-width-chars");
|
|
}
|
|
|
|
void
|
|
pika_action_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaActionPrivate *priv;
|
|
|
|
priv = GET_PRIVATE (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PIKA_ACTION_PROP_CONTEXT:
|
|
g_value_set_object (value, priv->context);
|
|
break;
|
|
case PIKA_ACTION_PROP_SENSITIVE:
|
|
g_value_set_boolean (value, priv->sensitive);
|
|
break;
|
|
case PIKA_ACTION_PROP_VISIBLE:
|
|
g_value_set_boolean (value, priv->visible);
|
|
break;
|
|
|
|
case PIKA_ACTION_PROP_LABEL:
|
|
g_value_set_string (value, priv->label);
|
|
break;
|
|
case PIKA_ACTION_PROP_SHORT_LABEL:
|
|
g_value_set_string (value, priv->short_label ? priv->short_label : priv->label);
|
|
break;
|
|
case PIKA_ACTION_PROP_TOOLTIP:
|
|
g_value_set_string (value, priv->tooltip);
|
|
break;
|
|
case PIKA_ACTION_PROP_ICON_NAME:
|
|
g_value_set_string (value, priv->icon_name);
|
|
break;
|
|
case PIKA_ACTION_PROP_ICON:
|
|
g_value_set_object (value, priv->icon);
|
|
break;
|
|
|
|
case PIKA_ACTION_PROP_COLOR:
|
|
g_value_set_boxed (value, priv->color);
|
|
break;
|
|
case PIKA_ACTION_PROP_VIEWABLE:
|
|
g_value_set_object (value, priv->viewable);
|
|
break;
|
|
case PIKA_ACTION_PROP_ELLIPSIZE:
|
|
g_value_set_enum (value, priv->ellipsize);
|
|
break;
|
|
case PIKA_ACTION_PROP_MAX_WIDTH_CHARS:
|
|
g_value_set_int (value, priv->max_width_chars);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
pika_action_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaActionPrivate *priv;
|
|
gboolean set_proxy = FALSE;
|
|
|
|
priv = GET_PRIVATE (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PIKA_ACTION_PROP_CONTEXT:
|
|
g_set_object (&priv->context, g_value_get_object (value));
|
|
break;
|
|
case PIKA_ACTION_PROP_SENSITIVE:
|
|
pika_action_set_sensitive (PIKA_ACTION (object),
|
|
g_value_get_boolean (value),
|
|
NULL);
|
|
break;
|
|
case PIKA_ACTION_PROP_VISIBLE:
|
|
pika_action_set_visible (PIKA_ACTION (object),
|
|
g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PIKA_ACTION_PROP_LABEL:
|
|
pika_action_set_label (PIKA_ACTION (object), g_value_get_string (value));
|
|
break;
|
|
case PIKA_ACTION_PROP_SHORT_LABEL:
|
|
pika_action_set_short_label (PIKA_ACTION (object), g_value_get_string (value));
|
|
break;
|
|
case PIKA_ACTION_PROP_TOOLTIP:
|
|
pika_action_set_tooltip (PIKA_ACTION (object), g_value_get_string (value));
|
|
break;
|
|
case PIKA_ACTION_PROP_ICON_NAME:
|
|
pika_action_set_icon_name (PIKA_ACTION (object), g_value_get_string (value));
|
|
break;
|
|
case PIKA_ACTION_PROP_ICON:
|
|
pika_action_set_gicon (PIKA_ACTION (object), g_value_get_object (value));
|
|
break;
|
|
|
|
case PIKA_ACTION_PROP_COLOR:
|
|
g_clear_pointer (&priv->color, g_free);
|
|
priv->color = g_value_dup_boxed (value);
|
|
set_proxy = TRUE;
|
|
break;
|
|
case PIKA_ACTION_PROP_VIEWABLE:
|
|
g_set_object (&priv->viewable, g_value_get_object (value));
|
|
set_proxy = TRUE;
|
|
break;
|
|
case PIKA_ACTION_PROP_ELLIPSIZE:
|
|
priv->ellipsize = g_value_get_enum (value);
|
|
set_proxy = TRUE;
|
|
break;
|
|
case PIKA_ACTION_PROP_MAX_WIDTH_CHARS:
|
|
priv->max_width_chars = g_value_get_int (value);
|
|
set_proxy = TRUE;
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
|
|
if (set_proxy)
|
|
{
|
|
/* Set or update the proxy rendering. */
|
|
for (GList *list = priv->proxies; list; list = list->next)
|
|
pika_action_set_proxy (PIKA_ACTION (object), list->data);
|
|
}
|
|
}
|
|
|
|
void
|
|
pika_action_set_proxy (PikaAction *action,
|
|
GtkWidget *proxy)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
GtkWidget *proxy_image = NULL;
|
|
GdkPixbuf *pixbuf = NULL;
|
|
GtkWidget *label;
|
|
|
|
if (! GTK_IS_MENU_ITEM (proxy) &&
|
|
! GTK_IS_TOOL_BUTTON (proxy) &&
|
|
! GTK_IS_BUTTON (proxy))
|
|
return;
|
|
|
|
/* Current implementation accepts GtkButton as proxies but don't modify their
|
|
* render. TODO?
|
|
*/
|
|
if (! GTK_IS_BUTTON (proxy))
|
|
{
|
|
if (priv->color)
|
|
{
|
|
if (GTK_IS_MENU_ITEM (proxy))
|
|
proxy_image = pika_menu_item_get_image (GTK_MENU_ITEM (proxy));
|
|
else
|
|
proxy_image = gtk_tool_button_get_label_widget (GTK_TOOL_BUTTON (proxy));
|
|
|
|
if (PIKA_IS_COLOR_AREA (proxy_image))
|
|
{
|
|
pika_color_area_set_color (PIKA_COLOR_AREA (proxy_image), priv->color);
|
|
proxy_image = NULL;
|
|
}
|
|
else
|
|
{
|
|
gint width, height;
|
|
|
|
proxy_image = pika_color_area_new (priv->color,
|
|
PIKA_COLOR_AREA_SMALL_CHECKS, 0);
|
|
pika_color_area_set_draw_border (PIKA_COLOR_AREA (proxy_image), TRUE);
|
|
|
|
if (priv->context)
|
|
pika_color_area_set_color_config (PIKA_COLOR_AREA (proxy_image),
|
|
priv->context->pika->config->color_management);
|
|
|
|
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
|
|
gtk_widget_set_size_request (proxy_image, width, height);
|
|
gtk_widget_show (proxy_image);
|
|
}
|
|
}
|
|
else if (priv->viewable)
|
|
{
|
|
if (GTK_IS_MENU_ITEM (proxy))
|
|
proxy_image = pika_menu_item_get_image (GTK_MENU_ITEM (proxy));
|
|
else
|
|
proxy_image = gtk_tool_button_get_label_widget (GTK_TOOL_BUTTON (proxy));
|
|
|
|
if (PIKA_IS_VIEW (proxy_image) &&
|
|
g_type_is_a (G_TYPE_FROM_INSTANCE (priv->viewable),
|
|
PIKA_VIEW (proxy_image)->renderer->viewable_type))
|
|
{
|
|
pika_view_set_viewable (PIKA_VIEW (proxy_image), priv->viewable);
|
|
proxy_image = NULL;
|
|
}
|
|
else
|
|
{
|
|
GtkIconSize size;
|
|
gint width, height;
|
|
gint border_width;
|
|
|
|
if (PIKA_IS_IMAGEFILE (priv->viewable))
|
|
{
|
|
size = GTK_ICON_SIZE_LARGE_TOOLBAR;
|
|
border_width = 0;
|
|
}
|
|
else
|
|
{
|
|
size = GTK_ICON_SIZE_MENU;
|
|
border_width = 1;
|
|
}
|
|
|
|
gtk_icon_size_lookup (size, &width, &height);
|
|
proxy_image = pika_view_new_full (priv->context, priv->viewable,
|
|
width, height, border_width,
|
|
FALSE, FALSE, FALSE);
|
|
gtk_widget_show (proxy_image);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PIKA_IS_PROCEDURE_ACTION (action) &&
|
|
/* Some special cases PikaProcedureAction have no procedure attached
|
|
* (e.g. "filters-recent-*" actions.
|
|
*/
|
|
G_IS_OBJECT (PIKA_PROCEDURE_ACTION (action)->procedure))
|
|
{
|
|
/* Special-casing procedure actions as plug-ins can create icons with
|
|
* pika_procedure_set_icon_pixbuf().
|
|
*/
|
|
g_object_get (PIKA_PROCEDURE_ACTION (action)->procedure,
|
|
"icon-pixbuf", &pixbuf,
|
|
NULL);
|
|
|
|
if (pixbuf != NULL)
|
|
{
|
|
gint width;
|
|
gint height;
|
|
|
|
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
|
|
|
|
if (width != gdk_pixbuf_get_width (pixbuf) ||
|
|
height != gdk_pixbuf_get_height (pixbuf))
|
|
{
|
|
GdkPixbuf *copy;
|
|
|
|
copy = gdk_pixbuf_scale_simple (pixbuf, width, height,
|
|
GDK_INTERP_BILINEAR);
|
|
g_object_unref (pixbuf);
|
|
pixbuf = copy;
|
|
}
|
|
|
|
proxy_image = gtk_image_new_from_pixbuf (pixbuf);
|
|
}
|
|
}
|
|
|
|
if (proxy_image == NULL)
|
|
{
|
|
if (GTK_IS_MENU_ITEM (proxy))
|
|
proxy_image = pika_menu_item_get_image (GTK_MENU_ITEM (proxy));
|
|
else
|
|
proxy_image = gtk_tool_button_get_label_widget (GTK_TOOL_BUTTON (proxy));
|
|
|
|
if (PIKA_IS_VIEW (proxy_image) || PIKA_IS_COLOR_AREA (proxy_image))
|
|
{
|
|
if (GTK_IS_MENU_ITEM (proxy))
|
|
pika_menu_item_set_image (GTK_MENU_ITEM (proxy), NULL, action);
|
|
else if (proxy_image)
|
|
gtk_widget_destroy (proxy_image);
|
|
|
|
g_object_notify (G_OBJECT (action), "icon-name");
|
|
}
|
|
|
|
proxy_image = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (proxy_image != NULL)
|
|
{
|
|
if (GTK_IS_MENU_ITEM (proxy))
|
|
{
|
|
pika_menu_item_set_image (GTK_MENU_ITEM (proxy), proxy_image, action);
|
|
}
|
|
else /* GTK_IS_TOOL_BUTTON (proxy) */
|
|
{
|
|
GtkWidget *prev_widget;
|
|
|
|
prev_widget = gtk_tool_button_get_label_widget (GTK_TOOL_BUTTON (proxy));
|
|
if (prev_widget)
|
|
gtk_widget_destroy (prev_widget);
|
|
|
|
gtk_tool_button_set_label_widget (GTK_TOOL_BUTTON (proxy), proxy_image);
|
|
}
|
|
}
|
|
else if (GTK_IS_LABEL (gtk_bin_get_child (GTK_BIN (proxy))) &&
|
|
GTK_IS_MENU_ITEM (proxy))
|
|
{
|
|
/* Ensure we rebuild the contents of the GtkMenuItem with an image (which
|
|
* might be NULL), a label (taken from action) and an optional shortcut
|
|
* (also taken from action).
|
|
*/
|
|
pika_menu_item_set_image (GTK_MENU_ITEM (proxy), NULL, action);
|
|
}
|
|
|
|
label = g_object_get_data (G_OBJECT (proxy), "pika-menu-item-label");
|
|
if (label)
|
|
{
|
|
gtk_label_set_ellipsize (GTK_LABEL (label), priv->ellipsize);
|
|
gtk_label_set_max_width_chars (GTK_LABEL (label), priv->max_width_chars);
|
|
}
|
|
|
|
if (! g_list_find (priv->proxies, proxy))
|
|
{
|
|
priv->proxies = g_list_prepend (priv->proxies, proxy);
|
|
g_signal_connect (proxy, "destroy",
|
|
(GCallback) pika_action_proxy_destroy,
|
|
action);
|
|
|
|
if (GTK_IS_BUTTON (proxy))
|
|
g_signal_connect (proxy, "clicked",
|
|
(GCallback) pika_action_proxy_button_activate,
|
|
action);
|
|
|
|
pika_action_update_proxy_sensitive (action, proxy);
|
|
}
|
|
|
|
g_clear_object (&pixbuf);
|
|
}
|
|
|
|
|
|
/* Friend functions */
|
|
|
|
/* This function is only meant to be run by the PikaActionGroup class. */
|
|
void
|
|
pika_action_set_group (PikaAction *action,
|
|
PikaActionGroup *group)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
/* We can't change groups! */
|
|
g_return_if_fail (priv->group == NULL);
|
|
|
|
priv->group = group;
|
|
}
|
|
|
|
/* This function is only meant to be run by the PikaActionGroup class. */
|
|
void
|
|
pika_action_set_default_accels (PikaAction *action,
|
|
const gchar **accels)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
/* This should be set only once and before any accelerator was set. */
|
|
g_return_if_fail (priv->accels == NULL);
|
|
g_return_if_fail (priv->default_accels == NULL);
|
|
|
|
priv->default_accels = g_strdupv ((gchar **) accels);
|
|
priv->accels = g_strdupv ((gchar **) accels);
|
|
|
|
g_signal_emit (action, action_signals[ACCELS_CHANGED], 0, accels);
|
|
}
|
|
|
|
/* This function is only meant to be run by the PikaMenuModel class. */
|
|
void
|
|
pika_action_set_menu_path (PikaAction *action,
|
|
const gchar *menu_path)
|
|
{
|
|
PikaActionPrivate *priv;
|
|
gchar **paths;
|
|
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
|
|
priv = GET_PRIVATE (action);
|
|
|
|
if (priv->menu_path != NULL)
|
|
/* There are cases where we put some actions in 2 menu paths, for instance:
|
|
* - filters-color-to-alpha in both /Layer/Transparency and /Colors
|
|
* - dialogs-histogram in both /Colors/Info and /Windows/Dockable Dialogs
|
|
*
|
|
* Anyway this is not an error, it's just how it is. Let's simply skip such
|
|
* cases silently and keep the first path as reference to show in helper
|
|
* widgets.
|
|
*/
|
|
return;
|
|
|
|
if (menu_path)
|
|
{
|
|
paths = pika_utils_break_menu_path (menu_path, NULL, NULL);
|
|
/* The 4 raw bytes are the "rightwards triangle arrowhead" unicode character. */
|
|
priv->menu_path = g_strjoinv (" \xF0\x9F\xA2\x92 ", paths);
|
|
g_strfreev (paths);
|
|
}
|
|
}
|
|
|
|
|
|
/* Private functions */
|
|
|
|
static PikaActionPrivate *
|
|
pika_action_get_private (PikaAction *action)
|
|
{
|
|
PikaActionPrivate *priv;
|
|
static GQuark private_key = 0;
|
|
|
|
g_return_val_if_fail (PIKA_IS_ACTION (action), NULL);
|
|
|
|
if (! private_key)
|
|
private_key = g_quark_from_static_string ("pika-action-priv");
|
|
|
|
priv = g_object_get_qdata ((GObject *) action, private_key);
|
|
|
|
if (! priv)
|
|
{
|
|
priv = g_slice_new0 (PikaActionPrivate);
|
|
|
|
g_object_set_qdata_full ((GObject *) action, private_key, priv,
|
|
(GDestroyNotify) pika_action_private_finalize);
|
|
}
|
|
|
|
return priv;
|
|
}
|
|
|
|
static void
|
|
pika_action_private_finalize (PikaActionPrivate *priv)
|
|
{
|
|
g_clear_pointer (&priv->disable_reason, g_free);
|
|
g_clear_object (&priv->context);
|
|
g_clear_pointer (&priv->color, g_free);
|
|
g_clear_object (&priv->viewable);
|
|
|
|
g_free (priv->label);
|
|
g_free (priv->short_label);
|
|
g_free (priv->tooltip);
|
|
g_free (priv->icon_name);
|
|
g_clear_object (&priv->icon);
|
|
|
|
g_strfreev (priv->accels);
|
|
g_strfreev (priv->default_accels);
|
|
|
|
g_free (priv->menu_path);
|
|
|
|
for (GList *iter = priv->proxies; iter; iter = iter->next)
|
|
{
|
|
/* TODO GAction: if an action associated to a proxy menu item disappears,
|
|
* shouldn't we also destroy the item itself (not just disconnect it)? It
|
|
* would now point to a non-existing action.
|
|
*/
|
|
g_signal_handlers_disconnect_by_func (iter->data,
|
|
pika_action_proxy_destroy,
|
|
priv->action);
|
|
g_signal_handlers_disconnect_by_func (iter->data,
|
|
pika_action_proxy_button_activate,
|
|
priv->action);
|
|
}
|
|
g_list_free (priv->proxies);
|
|
priv->proxies = NULL;
|
|
|
|
g_slice_free (PikaActionPrivate, priv);
|
|
}
|
|
|
|
static void
|
|
pika_action_set_proxy_label (PikaAction *action,
|
|
GtkWidget *proxy)
|
|
{
|
|
if (GTK_IS_MENU_ITEM (proxy))
|
|
{
|
|
GtkWidget *child = gtk_bin_get_child (GTK_BIN (proxy));
|
|
const gchar *label;
|
|
|
|
/* For menus, we assume that their position ensure some context, hence the
|
|
* short label is chosen in priority.
|
|
*/
|
|
label = pika_action_get_short_label (action);
|
|
|
|
if (GTK_IS_BOX (child))
|
|
{
|
|
child = g_object_get_data (G_OBJECT (proxy),
|
|
"pika-menu-item-label");
|
|
|
|
if (GTK_IS_LABEL (child))
|
|
gtk_label_set_text_with_mnemonic (GTK_LABEL (child), label);
|
|
}
|
|
else if (GTK_IS_LABEL (child))
|
|
{
|
|
gtk_menu_item_set_label (GTK_MENU_ITEM (proxy), label);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_action_label_notify (PikaAction *action,
|
|
const GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
for (GList *iter = GET_PRIVATE (action)->proxies; iter; iter = iter->next)
|
|
pika_action_set_proxy_label (action, iter->data);
|
|
}
|
|
|
|
static void
|
|
pika_action_proxy_destroy (GtkWidget *proxy,
|
|
PikaAction *action)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
|
|
priv->proxies = g_list_remove (priv->proxies, proxy);
|
|
}
|
|
|
|
static void
|
|
pika_action_proxy_button_activate (GtkButton *button,
|
|
PikaAction *action)
|
|
{
|
|
/* Activate with the default parameter value. */
|
|
pika_action_activate (action);
|
|
}
|
|
|
|
static void
|
|
pika_action_update_proxy_sensitive (PikaAction *action,
|
|
GtkWidget *proxy)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
gboolean sensitive = pika_action_is_sensitive (action, NULL);
|
|
|
|
if (proxy)
|
|
{
|
|
gtk_widget_set_sensitive (proxy, sensitive);
|
|
pika_action_update_proxy_tooltip (action, proxy);
|
|
}
|
|
else
|
|
{
|
|
for (GList *list = priv->proxies; list; list = list->next)
|
|
gtk_widget_set_sensitive (list->data, sensitive);
|
|
|
|
pika_action_update_proxy_tooltip (action, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_action_update_proxy_visible (PikaAction *action,
|
|
GtkWidget *proxy)
|
|
{
|
|
PikaActionPrivate *priv = GET_PRIVATE (action);
|
|
gboolean visible = pika_action_is_visible (action);
|
|
|
|
if (proxy)
|
|
{
|
|
gtk_widget_set_visible (proxy, visible);
|
|
}
|
|
else
|
|
{
|
|
for (GList *list = priv->proxies; list; list = list->next)
|
|
gtk_widget_set_visible (list->data, visible);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_action_update_proxy_tooltip (PikaAction *action,
|
|
GtkWidget *proxy)
|
|
{
|
|
PikaActionPrivate *priv;
|
|
const gchar *tooltip;
|
|
const gchar *help_id;
|
|
const gchar *reason = NULL;
|
|
gchar *escaped_reason = NULL;
|
|
gchar *markup;
|
|
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
|
|
priv = GET_PRIVATE (action);
|
|
tooltip = pika_action_get_tooltip (action);
|
|
help_id = pika_action_get_help_id (action);
|
|
|
|
pika_action_get_sensitive (action, &reason);
|
|
if (reason)
|
|
escaped_reason = g_markup_escape_text (reason, -1);
|
|
|
|
markup = g_strdup_printf ("%s%s" /* Action tooltip */
|
|
"<i><span weight='light'>%s</span></i>", /* Inactive reason */
|
|
tooltip,
|
|
escaped_reason && tooltip ? "\n" : "",
|
|
escaped_reason ? escaped_reason : "");
|
|
|
|
/* This hack makes sure we don't replace the tooltips of PikaButtons
|
|
* with extended callbacks (for Shift+Click etc.), because these
|
|
* buttons already have customly constructed multi-line tooltips
|
|
* which we want to keep.
|
|
*/
|
|
#define HAS_EXTENDED_ACTIONS(widget) \
|
|
(g_object_get_data (G_OBJECT (widget), "extended-actions") != NULL)
|
|
|
|
if (proxy != NULL)
|
|
{
|
|
if (! HAS_EXTENDED_ACTIONS (proxy))
|
|
pika_help_set_help_data_with_markup (proxy, markup, help_id);
|
|
}
|
|
else
|
|
{
|
|
for (GList *list = priv->proxies; list; list = list->next)
|
|
if (! HAS_EXTENDED_ACTIONS (list->data))
|
|
pika_help_set_help_data_with_markup (list->data, markup, help_id);
|
|
}
|
|
|
|
g_free (escaped_reason);
|
|
g_free (markup);
|
|
}
|