mirror of
https://gitlab.gnome.org/GNOME/gimp.git
synced 2025-10-06 01:12:40 +02:00
I only translated the undo menu into GtkBuilder's .ui format for now. The only missing part is that the icon is now shown. Note that in various parts, I don't rely anymore on a bogus menu action (i.e. "undo-popup" action in this case) which does nothing but has an associated label and icon. I simply add the label and icon as submenu attribute directly in the .ui file, which is translatable and whose strings should be parsed by gettext. Eventually I'll just get rid of all the various "*-popup" or "*-menu" bogus actions.
805 lines
25 KiB
C
805 lines
25 KiB
C
/* GIMP - The GNU Image Manipulation Program
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* gimpmenushell.c
|
|
* Copyright (C) 2023 Jehan
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libgimpbase/gimpbase.h"
|
|
#include "libgimpwidgets/gimpwidgets.h"
|
|
|
|
#include "widgets-types.h"
|
|
|
|
#include "core/gimp.h"
|
|
|
|
#include "gimpaction.h"
|
|
#include "gimpenumaction.h"
|
|
#include "gimphelp-ids.h"
|
|
#include "gimpmenu.h"
|
|
#include "gimpmenushell.h"
|
|
#include "gimpprocedureaction.h"
|
|
#include "gimpradioaction.h"
|
|
#include "gimptoggleaction.h"
|
|
#include "gimpuimanager.h"
|
|
|
|
|
|
#define GET_PRIVATE(obj) (gimp_menu_shell_get_private ((GimpMenuShell *) (obj)))
|
|
|
|
typedef struct _GimpMenuShellPrivate GimpMenuShellPrivate;
|
|
|
|
struct _GimpMenuShellPrivate
|
|
{
|
|
GimpUIManager *manager;
|
|
gchar *update_signal;
|
|
|
|
GTree *submenus;
|
|
GTree *placeholders;
|
|
|
|
GRegex *path_regex;
|
|
};
|
|
|
|
|
|
static GimpMenuShellPrivate *
|
|
gimp_menu_shell_get_private (GimpMenuShell *menu_shell);
|
|
static void gimp_menu_shell_private_finalize (GimpMenuShellPrivate *priv);
|
|
|
|
static void gimp_menu_shell_help_fun (const gchar *bogus_help_id,
|
|
gpointer help_data);
|
|
static void gimp_menu_shell_ui_added (GimpUIManager *manager,
|
|
const gchar *path,
|
|
const gchar *action_name,
|
|
const gchar *placeholder_key,
|
|
gboolean top,
|
|
GimpMenuShell *shell);
|
|
|
|
static void gimp_menu_shell_radio_item_toggled (GtkWidget *item,
|
|
GAction *action);
|
|
static void gimp_menu_shell_action_activate (GtkMenuItem *item,
|
|
GimpAction *action);
|
|
|
|
static void gimp_menu_shell_toggle_action_changed (GimpAction *action,
|
|
GVariant *value G_GNUC_UNUSED,
|
|
GtkCheckMenuItem *item);
|
|
static void gimp_menu_shell_action_notify_sensitive (GimpAction *action,
|
|
const GParamSpec *pspec,
|
|
GtkCheckMenuItem *item);
|
|
static void gimp_menu_shell_action_notify_visible (GimpAction *action,
|
|
const GParamSpec *pspec,
|
|
GtkWidget *item);
|
|
|
|
static void gimp_menu_shell_append_model (GimpMenuShell *shell,
|
|
GMenuModel *model);
|
|
static void gimp_menu_shell_add_ui (GimpMenuShell *shell,
|
|
const gchar **paths,
|
|
const gchar *action_name,
|
|
const gchar *placeholder_key,
|
|
gboolean top);
|
|
static void gimp_menu_shell_add_action (GimpMenuShell *shell,
|
|
const gchar *action_name,
|
|
const gchar *placeholder_key,
|
|
gboolean top,
|
|
GtkRadioMenuItem **group);
|
|
static void gimp_menu_shell_add_placeholder (GimpMenuShell *shell,
|
|
const gchar *label);
|
|
|
|
static gchar ** gimp_menu_shell_break_path (GimpMenuShell *shell,
|
|
const gchar *path);
|
|
static gchar * gimp_menu_shell_make_canonical_path (const gchar *path);
|
|
|
|
|
|
G_DEFINE_INTERFACE (GimpMenuShell, gimp_menu_shell, GTK_TYPE_MENU_SHELL)
|
|
|
|
|
|
static void
|
|
gimp_menu_shell_default_init (GimpMenuShellInterface *iface)
|
|
{
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_object ("manager",
|
|
NULL, NULL,
|
|
GIMP_TYPE_UI_MANAGER,
|
|
GIMP_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY));
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
void
|
|
gimp_menu_shell_fill (GimpMenuShell *shell,
|
|
GMenuModel *model,
|
|
const gchar *update_signal)
|
|
{
|
|
g_return_if_fail (GTK_IS_CONTAINER (shell));
|
|
|
|
gtk_container_foreach (GTK_CONTAINER (shell),
|
|
(GtkCallback) gtk_widget_destroy,
|
|
NULL);
|
|
|
|
gimp_menu_shell_append_model (shell, model);
|
|
|
|
if (update_signal != NULL)
|
|
{
|
|
GimpMenuShellPrivate *priv = GET_PRIVATE (shell);
|
|
|
|
if (priv->update_signal != NULL)
|
|
{
|
|
g_free (priv->update_signal);
|
|
g_signal_handlers_disconnect_by_func (priv->manager,
|
|
G_CALLBACK (gimp_menu_shell_ui_added),
|
|
shell);
|
|
}
|
|
|
|
priv->update_signal = g_strdup (update_signal);
|
|
g_signal_connect (priv->manager, update_signal,
|
|
G_CALLBACK (gimp_menu_shell_ui_added),
|
|
shell);
|
|
gimp_ui_manager_foreach_ui (priv->manager,
|
|
(GimpUIMenuCallback) gimp_menu_shell_ui_added,
|
|
shell);
|
|
}
|
|
}
|
|
|
|
void
|
|
gimp_menu_shell_merge (GimpMenuShell *shell,
|
|
GimpMenuShell *shell2,
|
|
gboolean top)
|
|
{
|
|
GList *children;
|
|
GList *iter;
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (shell2));
|
|
iter = top ? g_list_last (children) : children;
|
|
for (; iter; iter = top ? iter->prev : iter->next)
|
|
{
|
|
GtkWidget *item = iter->data;
|
|
|
|
g_object_ref (item);
|
|
gtk_container_remove (GTK_CONTAINER (shell2), item);
|
|
if (top)
|
|
gtk_menu_shell_prepend (GTK_MENU_SHELL (shell), item);
|
|
else
|
|
gtk_menu_shell_append (GTK_MENU_SHELL (shell), item);
|
|
g_object_unref (item);
|
|
}
|
|
|
|
g_list_free (children);
|
|
gtk_widget_destroy (GTK_WIDGET (shell2));
|
|
}
|
|
|
|
|
|
/* Protected functions. */
|
|
|
|
void
|
|
gimp_menu_shell_init (GimpMenuShell *shell)
|
|
{
|
|
GimpMenuShellPrivate *priv;
|
|
|
|
g_return_if_fail (GIMP_IS_MENU_SHELL (shell));
|
|
|
|
priv = GET_PRIVATE (shell);
|
|
|
|
priv->submenus = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL,
|
|
g_free, NULL);
|
|
priv->placeholders = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL,
|
|
g_free, NULL);
|
|
priv->update_signal = NULL;
|
|
priv->path_regex = g_regex_new ("/+", 0, 0, NULL);
|
|
|
|
gimp_help_connect (GTK_WIDGET (shell),
|
|
gimp_menu_shell_help_fun,
|
|
GIMP_HELP_MAIN,
|
|
shell,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* gimp_menu_shell_install_properties:
|
|
* @klass: the class structure for a type deriving from #GObject
|
|
*
|
|
* Installs the necessary properties for a class implementing
|
|
* #GimpMenuShell. Please call this function in the *_class_init()
|
|
* function of the child class.
|
|
**/
|
|
void
|
|
gimp_menu_shell_install_properties (GObjectClass *klass)
|
|
{
|
|
g_object_class_override_property (klass, GIMP_MENU_SHELL_PROP_MANAGER, "manager");
|
|
}
|
|
|
|
void
|
|
gimp_menu_shell_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GimpMenuShellPrivate *priv;
|
|
|
|
priv = GET_PRIVATE (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case GIMP_MENU_SHELL_PROP_MANAGER:
|
|
g_value_set_object (value, priv->manager);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
gimp_menu_shell_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GimpMenuShellPrivate *priv;
|
|
|
|
priv = GET_PRIVATE (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case GIMP_MENU_SHELL_PROP_MANAGER:
|
|
g_set_weak_pointer (&priv->manager, g_value_get_object (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* Private functions */
|
|
|
|
static GimpMenuShellPrivate *
|
|
gimp_menu_shell_get_private (GimpMenuShell *menu_shell)
|
|
{
|
|
GimpMenuShellPrivate *priv;
|
|
static GQuark private_key = 0;
|
|
|
|
g_return_val_if_fail (GIMP_IS_MENU_SHELL (menu_shell), NULL);
|
|
|
|
if (! private_key)
|
|
private_key = g_quark_from_static_string ("gimp-menu_shell-priv");
|
|
|
|
priv = g_object_get_qdata ((GObject *) menu_shell, private_key);
|
|
|
|
if (! priv)
|
|
{
|
|
priv = g_slice_new0 (GimpMenuShellPrivate);
|
|
|
|
g_object_set_qdata_full ((GObject *) menu_shell, private_key, priv,
|
|
(GDestroyNotify) gimp_menu_shell_private_finalize);
|
|
}
|
|
|
|
return priv;
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_private_finalize (GimpMenuShellPrivate *priv)
|
|
{
|
|
g_tree_unref (priv->submenus);
|
|
g_tree_unref (priv->placeholders);
|
|
|
|
g_free (priv->update_signal);
|
|
g_clear_pointer (&priv->path_regex, g_regex_unref);
|
|
|
|
g_slice_free (GimpMenuShellPrivate, priv);
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_help_fun (const gchar *bogus_help_id,
|
|
gpointer help_data)
|
|
{
|
|
GimpMenuShellPrivate *priv;
|
|
gchar *help_id = NULL;
|
|
GimpMenuShell *shell;
|
|
Gimp *gimp;
|
|
GtkWidget *item;
|
|
gchar *help_domain = NULL;
|
|
gchar *help_string = NULL;
|
|
gchar *domain_separator;
|
|
|
|
g_return_if_fail (GIMP_IS_MENU_SHELL (help_data));
|
|
|
|
shell = GIMP_MENU_SHELL (help_data);
|
|
priv = GET_PRIVATE (shell);
|
|
gimp = priv->manager->gimp;
|
|
|
|
g_return_if_fail (GIMP_IS_GIMP (gimp));
|
|
|
|
item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (shell));
|
|
|
|
if (item)
|
|
help_id = g_object_get_qdata (G_OBJECT (item), GIMP_HELP_ID);
|
|
|
|
if (help_id == NULL || strlen (help_id) == 0)
|
|
help_id = (gchar *) bogus_help_id;
|
|
|
|
help_id = g_strdup (help_id);
|
|
|
|
domain_separator = strchr (help_id, '?');
|
|
|
|
if (domain_separator)
|
|
{
|
|
*domain_separator = '\0';
|
|
|
|
help_domain = g_strdup (help_id);
|
|
help_string = g_strdup (domain_separator + 1);
|
|
}
|
|
else
|
|
{
|
|
help_string = g_strdup (help_id);
|
|
}
|
|
|
|
gimp_help (gimp, NULL, help_domain, help_string);
|
|
|
|
g_free (help_domain);
|
|
g_free (help_string);
|
|
g_free (help_id);
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_ui_added (GimpUIManager *manager,
|
|
const gchar *path,
|
|
const gchar *action_name,
|
|
const gchar *placeholder_key,
|
|
gboolean top,
|
|
GimpMenuShell *shell)
|
|
{
|
|
gchar **paths = gimp_menu_shell_break_path (shell, path);
|
|
|
|
gimp_menu_shell_add_ui (shell, (const gchar **) paths,
|
|
action_name, placeholder_key, top);
|
|
|
|
g_strfreev (paths);
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_radio_item_toggled (GtkWidget *item,
|
|
GAction *action)
|
|
{
|
|
gboolean active = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
|
|
|
|
gimp_toggle_action_set_active (GIMP_TOGGLE_ACTION (action), active);
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_action_activate (GtkMenuItem *item,
|
|
GimpAction *action)
|
|
{
|
|
gimp_action_activate (action);
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_toggle_action_changed (GimpAction *action,
|
|
/* Unused because this is used for 2 signals
|
|
* where the GVariant refers to different data.
|
|
*/
|
|
GVariant *value G_GNUC_UNUSED,
|
|
GtkCheckMenuItem *item)
|
|
{
|
|
gchar *action_name;
|
|
gboolean active;
|
|
|
|
action_name = g_strdup (gtk_actionable_get_action_name (GTK_ACTIONABLE (item)));
|
|
active = gimp_toggle_action_get_active (GIMP_TOGGLE_ACTION (action));
|
|
|
|
/* Make sure we don't activate the action. */
|
|
gtk_actionable_set_action_name (GTK_ACTIONABLE (item), NULL);
|
|
|
|
gtk_check_menu_item_set_active (item, active);
|
|
|
|
gtk_actionable_set_action_name (GTK_ACTIONABLE (item), action_name);
|
|
g_free (action_name);
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_action_notify_sensitive (GimpAction *action,
|
|
const GParamSpec *pspec,
|
|
GtkCheckMenuItem *item)
|
|
{
|
|
gtk_widget_set_sensitive (GTK_WIDGET (item),
|
|
gimp_action_is_sensitive (action, NULL));
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_action_notify_visible (GimpAction *action,
|
|
const GParamSpec *pspec,
|
|
GtkWidget *item)
|
|
{
|
|
GtkWidget *container;
|
|
|
|
gtk_widget_set_visible (item, gimp_action_is_visible (action));
|
|
|
|
container = gtk_widget_get_parent (item);
|
|
if (gimp_action_is_visible (GIMP_ACTION (action)))
|
|
{
|
|
GtkWidget *widget = gtk_menu_get_attach_widget (GTK_MENU (container));
|
|
|
|
/* We must show the GtkMenuItem associated as submenu to the parent
|
|
* container.
|
|
*/
|
|
if (G_TYPE_FROM_INSTANCE (widget) == GTK_TYPE_MENU_ITEM)
|
|
gtk_widget_show (widget);
|
|
}
|
|
else
|
|
{
|
|
GList *children = gtk_container_get_children (GTK_CONTAINER (container));
|
|
gboolean all_invisible = TRUE;
|
|
|
|
for (GList *iter = children; iter; iter = iter->next)
|
|
{
|
|
if (gtk_widget_get_visible (iter->data))
|
|
{
|
|
all_invisible = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
g_list_free (children);
|
|
|
|
if (all_invisible)
|
|
{
|
|
GtkWidget *widget;
|
|
|
|
/* No need to leave empty submenus. */
|
|
widget = gtk_menu_get_attach_widget (GTK_MENU (container));
|
|
if (G_TYPE_FROM_INSTANCE (widget) == GTK_TYPE_MENU_ITEM)
|
|
gtk_widget_hide (widget);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_append_model (GimpMenuShell *shell,
|
|
GMenuModel *model)
|
|
{
|
|
GimpMenuShellPrivate *priv;
|
|
static GtkRadioMenuItem *group = NULL;
|
|
gint n_items;
|
|
|
|
g_return_if_fail (GTK_IS_CONTAINER (shell));
|
|
|
|
priv = GET_PRIVATE (shell);
|
|
n_items = g_menu_model_get_n_items (model);
|
|
for (gint i = 0; i < n_items; i++)
|
|
{
|
|
GMenuModel *subsection;
|
|
GMenuModel *submenu;
|
|
GtkWidget *item;
|
|
gchar *label = NULL;
|
|
gchar *action_name = NULL;
|
|
|
|
subsection = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
|
|
submenu = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU);
|
|
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
|
|
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
|
|
|
|
if (subsection != NULL)
|
|
{
|
|
group = NULL;
|
|
|
|
item = gtk_separator_menu_item_new ();
|
|
gtk_container_add (GTK_CONTAINER (shell), item);
|
|
gtk_widget_show (item);
|
|
|
|
gimp_menu_shell_append_model (shell, subsection);
|
|
|
|
item = gtk_separator_menu_item_new ();
|
|
gtk_container_add (GTK_CONTAINER (shell), item);
|
|
gtk_widget_show (item);
|
|
}
|
|
else if (submenu != NULL)
|
|
{
|
|
GtkWidget *subcontainer;
|
|
GtkWidget *item;
|
|
|
|
group = NULL;
|
|
|
|
/* I don't show the item on purpose because
|
|
* gimp_menu_shell_append_model() will show the parent item if any of
|
|
* the added actions are visible.
|
|
*/
|
|
item = gtk_menu_item_new_with_mnemonic (label);
|
|
gtk_container_add (GTK_CONTAINER (shell), item);
|
|
|
|
subcontainer = gimp_menu_new (priv->manager);
|
|
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), subcontainer);
|
|
gimp_menu_shell_append_model (GIMP_MENU_SHELL (subcontainer), submenu);
|
|
gtk_widget_show (subcontainer);
|
|
|
|
g_tree_insert (priv->submenus,
|
|
gimp_menu_shell_make_canonical_path (label),
|
|
subcontainer);
|
|
}
|
|
else if (action_name == NULL)
|
|
{
|
|
/* Special case: we use items with no action and a label as
|
|
* placeholder which allows us to specify a placement in menus, which
|
|
* might not be only top or bottom.
|
|
*/
|
|
g_return_if_fail (label != NULL);
|
|
|
|
group = NULL;
|
|
|
|
gimp_menu_shell_add_placeholder (shell, label);
|
|
}
|
|
else
|
|
{
|
|
gimp_menu_shell_add_action (shell, action_name, NULL, FALSE, &group);
|
|
}
|
|
g_free (label);
|
|
g_free (action_name);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_add_ui (GimpMenuShell *shell,
|
|
const gchar **paths,
|
|
const gchar *action_name,
|
|
const gchar *placeholder_key,
|
|
gboolean top)
|
|
{
|
|
g_return_if_fail (paths != NULL);
|
|
|
|
if (paths[0] == NULL)
|
|
{
|
|
gimp_menu_shell_add_action (shell, action_name, placeholder_key, top, NULL);
|
|
}
|
|
else
|
|
{
|
|
GimpMenuShellPrivate *priv = GET_PRIVATE (shell);
|
|
GtkWidget *submenu = NULL;
|
|
|
|
submenu = g_tree_lookup (priv->submenus, paths[0]);
|
|
|
|
if (submenu == NULL)
|
|
{
|
|
GtkWidget *item;
|
|
|
|
item = gtk_menu_item_new_with_mnemonic (paths[0]);
|
|
gtk_container_add (GTK_CONTAINER (shell), item);
|
|
|
|
submenu = gimp_menu_new (priv->manager);
|
|
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
|
|
gtk_widget_show (submenu);
|
|
|
|
g_tree_insert (priv->submenus, g_strdup (paths[0]), submenu);
|
|
}
|
|
|
|
gimp_menu_shell_add_ui (GIMP_MENU_SHELL (submenu), paths + 1,
|
|
action_name, placeholder_key, top);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_add_action (GimpMenuShell *shell,
|
|
const gchar *action_name,
|
|
const gchar *placeholder_key,
|
|
gboolean top,
|
|
GtkRadioMenuItem **group)
|
|
{
|
|
GimpMenuShellPrivate *priv;
|
|
GApplication *app;
|
|
GAction *action;
|
|
const gchar *action_label;
|
|
GtkWidget *item;
|
|
GtkWidget *sibling = NULL;
|
|
gboolean visible;
|
|
|
|
g_return_if_fail (GIMP_IS_MENU_SHELL (shell));
|
|
|
|
priv = GET_PRIVATE (shell);
|
|
|
|
app = priv->manager->gimp->app;
|
|
|
|
if (g_str_has_prefix (action_name, "app."))
|
|
action = g_action_map_lookup_action (G_ACTION_MAP (app), action_name + 4);
|
|
else
|
|
action = g_action_map_lookup_action (G_ACTION_MAP (app), action_name);
|
|
|
|
g_return_if_fail (GIMP_IS_ACTION (action));
|
|
|
|
action_label = gimp_action_get_label (GIMP_ACTION (action));
|
|
g_return_if_fail (action_label != NULL);
|
|
|
|
if (GIMP_IS_RADIO_ACTION (action))
|
|
{
|
|
item = gtk_radio_menu_item_new_with_mnemonic_from_widget (group ? *group : NULL, action_label);
|
|
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
|
|
gimp_toggle_action_get_active (GIMP_TOGGLE_ACTION (action)));
|
|
|
|
if (group)
|
|
*group = GTK_RADIO_MENU_ITEM (item);
|
|
|
|
g_signal_connect (item, "toggled",
|
|
G_CALLBACK (gimp_menu_shell_radio_item_toggled),
|
|
action);
|
|
g_signal_connect (action, "change-state",
|
|
G_CALLBACK (gimp_menu_shell_toggle_action_changed),
|
|
item);
|
|
}
|
|
else if (GIMP_IS_TOGGLE_ACTION (action))
|
|
{
|
|
item = gtk_check_menu_item_new_with_mnemonic (action_label);
|
|
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
|
|
gimp_toggle_action_get_active (GIMP_TOGGLE_ACTION (action)));
|
|
if (group)
|
|
*group = NULL;
|
|
|
|
g_signal_connect (action, "change-state",
|
|
G_CALLBACK (gimp_menu_shell_toggle_action_changed),
|
|
item);
|
|
}
|
|
else if (GIMP_IS_PROCEDURE_ACTION (action) ||
|
|
GIMP_IS_ENUM_ACTION (action))
|
|
{
|
|
item = gtk_menu_item_new_with_mnemonic (action_label);
|
|
|
|
if (group)
|
|
*group = NULL;
|
|
|
|
g_signal_connect (item, "activate",
|
|
G_CALLBACK (gimp_menu_shell_action_activate),
|
|
action);
|
|
}
|
|
else
|
|
{
|
|
item = gtk_menu_item_new_with_mnemonic (action_label);
|
|
|
|
if (group)
|
|
*group = NULL;
|
|
}
|
|
|
|
gtk_actionable_set_action_name (GTK_ACTIONABLE (item), action_name);
|
|
gimp_action_set_proxy (GIMP_ACTION (action), item);
|
|
|
|
gtk_widget_set_sensitive (GTK_WIDGET (item),
|
|
gimp_action_is_sensitive (GIMP_ACTION (action), NULL));
|
|
g_signal_connect_object (action, "notify::sensitive",
|
|
G_CALLBACK (gimp_menu_shell_action_notify_sensitive),
|
|
item, 0);
|
|
|
|
if (placeholder_key)
|
|
{
|
|
sibling = g_tree_lookup (priv->placeholders, placeholder_key);
|
|
|
|
if (! sibling)
|
|
g_warning ("%s: no placeholder item '%s'.", G_STRFUNC, placeholder_key);
|
|
}
|
|
|
|
if (sibling)
|
|
{
|
|
GList *children;
|
|
gint position = 0;
|
|
|
|
/* I am assuming that the order of the children list reflects the
|
|
* position, though it is not clearly specified in the function docs. Yet
|
|
* I could find no other function giving me the position of some child in
|
|
* a container.
|
|
*/
|
|
children = gtk_container_get_children (GTK_CONTAINER (shell));
|
|
|
|
for (GList *iter = children; iter; iter = iter->next)
|
|
{
|
|
if (iter->data == sibling)
|
|
break;
|
|
position++;
|
|
}
|
|
if (! top)
|
|
position++;
|
|
|
|
gtk_menu_shell_insert (GTK_MENU_SHELL (shell), item, position);
|
|
|
|
g_list_free (children);
|
|
}
|
|
else
|
|
{
|
|
if (top)
|
|
gtk_menu_shell_prepend (GTK_MENU_SHELL (shell), item);
|
|
else
|
|
gtk_menu_shell_append (GTK_MENU_SHELL (shell), item);
|
|
}
|
|
|
|
visible = gimp_action_is_visible (GIMP_ACTION (action));
|
|
gtk_widget_set_visible (item, visible);
|
|
if (visible && GTK_IS_MENU (shell))
|
|
{
|
|
GtkWidget *parent = gtk_menu_get_attach_widget (GTK_MENU (shell));
|
|
|
|
/* Note that this is not the container we must show, but the menu item
|
|
* attached to the parent, in order not to leave empty submenus.
|
|
*/
|
|
if (parent != NULL &&
|
|
G_TYPE_FROM_INSTANCE (parent) == GTK_TYPE_MENU_ITEM)
|
|
gtk_widget_show (parent);
|
|
}
|
|
g_signal_connect_object (action, "notify::visible",
|
|
G_CALLBACK (gimp_menu_shell_action_notify_visible),
|
|
item, 0);
|
|
}
|
|
|
|
static void
|
|
gimp_menu_shell_add_placeholder (GimpMenuShell *shell,
|
|
const gchar *label)
|
|
{
|
|
GimpMenuShellPrivate *priv = GET_PRIVATE (shell);
|
|
GtkWidget *item;
|
|
|
|
/* Placeholders are inserted yet never shown, on purpose. */
|
|
item = gtk_menu_item_new_with_mnemonic (label);
|
|
gtk_container_add (GTK_CONTAINER (shell), item);
|
|
|
|
g_tree_insert (priv->placeholders, g_strdup (label), item);
|
|
}
|
|
|
|
static gchar **
|
|
gimp_menu_shell_break_path (GimpMenuShell *shell,
|
|
const gchar *path)
|
|
{
|
|
GimpMenuShellPrivate *priv;
|
|
gchar **paths;
|
|
gint start = 0;
|
|
|
|
g_return_val_if_fail (path != NULL, NULL);
|
|
|
|
priv = GET_PRIVATE (shell);
|
|
|
|
/* Get rid of leading slashes. */
|
|
while (path[start] == '/' && path[start] != '\0')
|
|
start++;
|
|
|
|
/* Get rid of empty last item because of trailing slashes. */
|
|
paths = g_regex_split (priv->path_regex, path + start, 0);
|
|
if (strlen (paths[g_strv_length (paths) - 1]) == 0)
|
|
{
|
|
g_free (paths[g_strv_length (paths) - 1]);
|
|
paths[g_strv_length (paths) - 1] = NULL;
|
|
}
|
|
|
|
for (int i = 0; paths[i]; i++)
|
|
{
|
|
gchar *canon_path;
|
|
|
|
canon_path = gimp_menu_shell_make_canonical_path (paths[i]);
|
|
g_free (paths[i]);
|
|
paths[i] = canon_path;
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
|
|
static gchar *
|
|
gimp_menu_shell_make_canonical_path (const gchar *path)
|
|
{
|
|
gchar **split_path;
|
|
gchar *canon_path;
|
|
|
|
/* The first underscore of each path item is a mnemonic. */
|
|
split_path = g_strsplit (path, "_", 2);
|
|
canon_path = g_strjoinv ("", split_path);
|
|
g_strfreev (split_path);
|
|
|
|
return canon_path;
|
|
}
|