1313 lines
46 KiB
C
1313 lines
46 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
|
|
*
|
|
* pikamenu_model.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 "widgets-types.h"
|
|
|
|
#include "libpikabase/pikabase.h"
|
|
#include "libpikacolor/pikacolor.h"
|
|
#include "libpikawidgets/pikawidgets.h"
|
|
|
|
#include "core/pika.h"
|
|
|
|
#include "pikaaction.h"
|
|
#include "pikaactiongroup.h"
|
|
#include "pikamenumodel.h"
|
|
#include "pikamenushell.h"
|
|
#include "pikaradioaction.h"
|
|
#include "pikauimanager.h"
|
|
#include "pikawidgets-utils.h"
|
|
|
|
|
|
/**
|
|
* PikaMenuModel:
|
|
*
|
|
* PikaMenuModel implements GMenuModel. We initialize an object from another
|
|
* GMenuModel (usually a GMenu), auto-fill with various data from the
|
|
* PikaAction, when they are not in GAction API, e.g. labels, but action
|
|
* visibility.
|
|
*
|
|
* The object will also synchronize automatically with changes from the actions,
|
|
* but also PikaUIManager for dynamic contents and will trigger an
|
|
* "items-changed" when necessary. This allows for such variant to be used in
|
|
* GTK API which has no knowledge of PikaAction or PikaUIManager enhancements.
|
|
*/
|
|
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_MANAGER,
|
|
PROP_MODEL,
|
|
PROP_PATH,
|
|
PROP_IS_SECTION,
|
|
PROP_TITLE,
|
|
PROP_COLOR,
|
|
};
|
|
|
|
struct _PikaMenuModelPrivate
|
|
{
|
|
PikaUIManager *manager;
|
|
GMenuModel *model;
|
|
|
|
gchar *path;
|
|
gboolean is_section;
|
|
/* If this PikaMenuModel represents a submenu for a bigger menu, this object
|
|
* will not be NULL.
|
|
*/
|
|
GMenuItem *submenu_item;
|
|
PikaRGB *submenu_color;
|
|
|
|
GList *items;
|
|
|
|
GHashTable *named_sections;
|
|
};
|
|
|
|
|
|
static void pika_menu_model_finalize (GObject *object);
|
|
static void pika_menu_model_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
static void pika_menu_model_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static GVariant * pika_menu_model_get_item_attribute_value (GMenuModel *model,
|
|
gint item_index,
|
|
const gchar *attribute,
|
|
const GVariantType *expected_type);
|
|
static void pika_menu_model_get_item_attributes (GMenuModel *model,
|
|
gint item_index,
|
|
GHashTable **attributes);
|
|
static GMenuModel * pika_menu_model_get_item_link (GMenuModel *model,
|
|
gint item_index,
|
|
const gchar *link);
|
|
static void pika_menu_model_get_item_links (GMenuModel *model,
|
|
gint item_index,
|
|
GHashTable **links);
|
|
static gint pika_menu_model_get_n_items (GMenuModel *model);
|
|
static gint pika_menu_model_get_position (PikaMenuModel *model,
|
|
const gchar *action_name,
|
|
gboolean *visible);;
|
|
static gboolean pika_menu_model_is_mutable (GMenuModel *model);
|
|
|
|
static void pika_menu_model_initialize (PikaMenuModel *model,
|
|
GMenuModel *gmodel);
|
|
static gchar * pika_menu_model_handles_subpath (PikaMenuModel *model,
|
|
const gchar *canonical_path,
|
|
const gchar *mnemonic_path);
|
|
|
|
static GMenuItem * pika_menu_model_get_item (PikaMenuModel *model,
|
|
gint idx);
|
|
static GMenuItem * pika_menu_model_get_menu_item_rec (PikaMenuModel *model,
|
|
const gchar *path,
|
|
PikaMenuModel **menu,
|
|
GMenuItem *item);
|
|
|
|
static void pika_menu_model_notify_group_label (PikaRadioAction *action,
|
|
const GParamSpec *pspec,
|
|
GMenuItem *item);
|
|
static void pika_menu_model_action_notify_visible (PikaAction *action,
|
|
GParamSpec *pspec,
|
|
PikaMenuModel *model);
|
|
static void pika_menu_model_action_notify_label (PikaAction *action,
|
|
GParamSpec *pspec,
|
|
GMenuItem *item);
|
|
|
|
static gboolean pika_menu_model_ui_added (PikaUIManager *manager,
|
|
const gchar *path,
|
|
const gchar *action_name,
|
|
gboolean top,
|
|
PikaMenuModel *model);
|
|
static gboolean pika_menu_model_ui_removed (PikaUIManager *manager,
|
|
const gchar *path,
|
|
const gchar *action_name,
|
|
PikaMenuModel *model);
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (PikaMenuModel, pika_menu_model, G_TYPE_MENU_MODEL,
|
|
G_ADD_PRIVATE (PikaMenuModel))
|
|
|
|
#define parent_class pika_menu_model_parent_class
|
|
|
|
|
|
/* Class functions */
|
|
|
|
static void
|
|
pika_menu_model_class_init (PikaMenuModelClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GMenuModelClass *model_class = G_MENU_MODEL_CLASS (klass);
|
|
|
|
object_class->finalize = pika_menu_model_finalize;
|
|
object_class->get_property = pika_menu_model_get_property;
|
|
object_class->set_property = pika_menu_model_set_property;
|
|
|
|
model_class->get_item_attribute_value = pika_menu_model_get_item_attribute_value;
|
|
model_class->get_item_attributes = pika_menu_model_get_item_attributes;
|
|
model_class->get_item_link = pika_menu_model_get_item_link;
|
|
model_class->get_item_links = pika_menu_model_get_item_links;
|
|
model_class->get_n_items = pika_menu_model_get_n_items;
|
|
model_class->is_mutable = pika_menu_model_is_mutable;
|
|
|
|
g_object_class_install_property (object_class, PROP_MANAGER,
|
|
g_param_spec_object ("manager",
|
|
NULL, NULL,
|
|
PIKA_TYPE_UI_MANAGER,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (object_class, PROP_MODEL,
|
|
g_param_spec_object ("model",
|
|
NULL, NULL,
|
|
G_TYPE_MENU_MODEL,
|
|
PIKA_PARAM_READWRITE));
|
|
g_object_class_install_property (object_class, PROP_PATH,
|
|
g_param_spec_string ("path",
|
|
NULL, NULL, NULL,
|
|
PIKA_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY));
|
|
g_object_class_install_property (object_class, PROP_IS_SECTION,
|
|
g_param_spec_boolean ("section",
|
|
NULL, NULL, FALSE,
|
|
PIKA_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY));
|
|
/* Titles are only relevant if the model is that of a submenu. */
|
|
g_object_class_install_property (object_class, PROP_TITLE,
|
|
g_param_spec_string ("title",
|
|
NULL, NULL, NULL,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY));
|
|
g_object_class_install_property (object_class, PROP_COLOR,
|
|
pika_param_spec_rgb ("color",
|
|
NULL, NULL,
|
|
TRUE, &(PikaRGB) {},
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY));
|
|
}
|
|
|
|
static void
|
|
pika_menu_model_init (PikaMenuModel *model)
|
|
{
|
|
model->priv = pika_menu_model_get_instance_private (model);
|
|
|
|
model->priv->items = NULL;
|
|
model->priv->path = NULL;
|
|
model->priv->is_section = FALSE;
|
|
model->priv->submenu_item = NULL;
|
|
model->priv->submenu_color = NULL;
|
|
|
|
model->priv->named_sections = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, NULL);
|
|
}
|
|
|
|
static void
|
|
pika_menu_model_finalize (GObject *object)
|
|
{
|
|
PikaMenuModel *model = PIKA_MENU_MODEL (object);
|
|
|
|
g_clear_weak_pointer (&model->priv->manager);
|
|
g_clear_object (&model->priv->model);
|
|
g_list_free_full (model->priv->items, g_object_unref);
|
|
g_free (model->priv->path);
|
|
g_free (model->priv->submenu_color);
|
|
g_hash_table_destroy (model->priv->named_sections);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
void
|
|
pika_menu_model_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaMenuModel *model = PIKA_MENU_MODEL (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_MANAGER:
|
|
g_value_set_object (value, model->priv->manager);
|
|
break;
|
|
case PROP_MODEL:
|
|
g_value_set_object (value, model->priv->model);
|
|
break;
|
|
case PROP_TITLE:
|
|
{
|
|
gchar *title;
|
|
|
|
g_menu_item_get_attribute (model->priv->submenu_item, G_MENU_ATTRIBUTE_LABEL, "s", &title);
|
|
g_value_set_string (value, title);
|
|
g_free (title);
|
|
}
|
|
break;
|
|
case PROP_COLOR:
|
|
g_value_set_boxed (value, model->priv->submenu_color);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
pika_menu_model_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaMenuModel *model = PIKA_MENU_MODEL (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_MANAGER:
|
|
g_set_weak_pointer (&model->priv->manager, g_value_get_object (value));
|
|
break;
|
|
case PROP_MODEL:
|
|
model->priv->model = g_value_dup_object (value);
|
|
pika_menu_model_initialize (model, model->priv->model);
|
|
break;
|
|
case PROP_PATH:
|
|
model->priv->path = g_value_dup_string (value);
|
|
break;
|
|
case PROP_IS_SECTION:
|
|
model->priv->is_section = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_TITLE:
|
|
pika_menu_model_set_title (model, model->priv->path,
|
|
g_value_get_string (value));
|
|
break;
|
|
case PROP_COLOR:
|
|
pika_menu_model_set_color (model, model->priv->path,
|
|
g_value_get_boxed (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GVariant*
|
|
pika_menu_model_get_item_attribute_value (GMenuModel *model,
|
|
gint item_index,
|
|
const gchar *attribute,
|
|
const GVariantType *expected_type)
|
|
{
|
|
PikaMenuModel *m = PIKA_MENU_MODEL (model);
|
|
GMenuItem *item;
|
|
|
|
item = pika_menu_model_get_item (m, item_index);
|
|
|
|
return g_menu_item_get_attribute_value (item, attribute, expected_type);
|
|
}
|
|
|
|
static void
|
|
pika_menu_model_get_item_attributes (GMenuModel *model,
|
|
gint item_index,
|
|
GHashTable **attributes)
|
|
{
|
|
PikaMenuModel *m = PIKA_MENU_MODEL (model);
|
|
GMenuItem *item;
|
|
GVariant *value;
|
|
|
|
item = pika_menu_model_get_item (m, item_index);
|
|
|
|
*attributes = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
|
|
(GDestroyNotify) g_variant_unref);
|
|
|
|
value = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_LABEL, NULL);
|
|
if (value)
|
|
g_hash_table_insert (*attributes, G_MENU_ATTRIBUTE_LABEL, value);
|
|
|
|
value = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_ACTION, NULL);
|
|
if (value)
|
|
g_hash_table_insert (*attributes, G_MENU_ATTRIBUTE_ACTION, value);
|
|
|
|
value = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_ICON, NULL);
|
|
if (value)
|
|
g_hash_table_insert (*attributes, G_MENU_ATTRIBUTE_ICON, value);
|
|
|
|
value = g_menu_item_get_attribute_value (item, G_MENU_LINK_SUBMENU, NULL);
|
|
if (value)
|
|
g_hash_table_insert (*attributes, G_MENU_LINK_SUBMENU, value);
|
|
|
|
value = g_menu_item_get_attribute_value (item, G_MENU_LINK_SECTION, NULL);
|
|
if (value)
|
|
g_hash_table_insert (*attributes, G_MENU_LINK_SECTION, value);
|
|
|
|
value = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_TARGET, NULL);
|
|
if (value)
|
|
g_hash_table_insert (*attributes, G_MENU_ATTRIBUTE_TARGET, value);
|
|
|
|
value = g_menu_item_get_attribute_value (item, "hidden-when", NULL);
|
|
if (value)
|
|
g_hash_table_insert (*attributes, "hidden-when", value);
|
|
}
|
|
|
|
static GMenuModel*
|
|
pika_menu_model_get_item_link (GMenuModel *model,
|
|
gint item_index,
|
|
const gchar *link)
|
|
{
|
|
PikaMenuModel *m = PIKA_MENU_MODEL (model);
|
|
GMenuItem *item;
|
|
|
|
item = pika_menu_model_get_item (m, item_index);
|
|
|
|
return g_menu_item_get_link (item, link);
|
|
}
|
|
|
|
|
|
static void
|
|
pika_menu_model_get_item_links (GMenuModel *model,
|
|
gint item_index,
|
|
GHashTable **links)
|
|
{
|
|
PikaMenuModel *m = PIKA_MENU_MODEL (model);
|
|
GMenuModel *subsection;
|
|
GMenuModel *submenu;
|
|
GMenuItem *item;
|
|
|
|
*links = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
|
|
(GDestroyNotify) g_object_unref);
|
|
|
|
item = pika_menu_model_get_item (m, item_index);
|
|
subsection = g_menu_item_get_link (item, G_MENU_LINK_SECTION);
|
|
submenu = g_menu_item_get_link (item, G_MENU_LINK_SUBMENU);
|
|
|
|
if (subsection)
|
|
g_hash_table_insert (*links, G_MENU_LINK_SECTION, g_object_ref (subsection));
|
|
if (submenu)
|
|
g_hash_table_insert (*links, G_MENU_LINK_SUBMENU, g_object_ref (submenu));
|
|
|
|
g_clear_object (&subsection);
|
|
g_clear_object (&submenu);
|
|
}
|
|
|
|
static gint
|
|
pika_menu_model_get_n_items (GMenuModel *model)
|
|
{
|
|
PikaMenuModel *m = PIKA_MENU_MODEL (model);
|
|
|
|
return pika_menu_model_get_position (m, NULL, NULL);
|
|
}
|
|
|
|
/* This function has 2 usage:
|
|
* - Either you call it with @action_name == NULL, then it returns the
|
|
* total number of visible items in this model.
|
|
* - Or it returns the position of @action_name and its visible state.
|
|
*/
|
|
static gint
|
|
pika_menu_model_get_position (PikaMenuModel *model,
|
|
const gchar *action_name,
|
|
gboolean *visible)
|
|
{
|
|
GList *iter;
|
|
gint len = 0;
|
|
|
|
for (iter = model->priv->items; iter; iter = iter->next)
|
|
{
|
|
GMenuModel *subsection;
|
|
GMenuModel *submenu;
|
|
const gchar *cur_action_name = NULL;
|
|
|
|
subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION);
|
|
submenu = g_menu_item_get_link (iter->data, G_MENU_LINK_SUBMENU);
|
|
|
|
if (subsection || submenu)
|
|
{
|
|
len++;
|
|
}
|
|
/* Count neither placeholders (items with no action name), nor invisible
|
|
* actions.
|
|
*/
|
|
else if (g_menu_item_get_attribute (iter->data,
|
|
G_MENU_ATTRIBUTE_ACTION,
|
|
"&s", &cur_action_name))
|
|
{
|
|
PikaAction *cur_action = NULL;
|
|
const gchar *real_action_name = NULL;
|
|
|
|
if (cur_action_name != NULL)
|
|
{
|
|
real_action_name = strstr (cur_action_name, ".");
|
|
if (real_action_name != NULL)
|
|
real_action_name++;
|
|
else
|
|
real_action_name = cur_action_name;
|
|
|
|
cur_action = pika_ui_manager_find_action (model->priv->manager,
|
|
NULL, cur_action_name);
|
|
}
|
|
|
|
if (cur_action_name != NULL &&
|
|
g_strcmp0 (action_name, real_action_name) == 0)
|
|
{
|
|
if (visible)
|
|
{
|
|
if (cur_action != NULL)
|
|
*visible = pika_action_is_visible (cur_action);
|
|
else
|
|
/* This may happen when editing a menu item for an action
|
|
* which got removed.
|
|
*/
|
|
*visible = FALSE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
else if (cur_action != NULL && pika_action_is_visible (cur_action))
|
|
{
|
|
len++;
|
|
}
|
|
}
|
|
|
|
g_clear_object (&subsection);
|
|
g_clear_object (&submenu);
|
|
}
|
|
|
|
g_return_val_if_fail (action_name == NULL || iter != NULL, -1);
|
|
|
|
return len;
|
|
}
|
|
|
|
static gboolean
|
|
pika_menu_model_is_mutable (GMenuModel* model)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* Public functions. */
|
|
|
|
PikaMenuModel *
|
|
pika_menu_model_new (PikaUIManager *manager,
|
|
GMenuModel *model)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_UI_MANAGER (manager), NULL);
|
|
|
|
return g_object_new (PIKA_TYPE_MENU_MODEL,
|
|
"manager", manager,
|
|
"model", model,
|
|
NULL);
|
|
}
|
|
|
|
PikaMenuModel *
|
|
pika_menu_model_get_submodel (PikaMenuModel *model,
|
|
const gchar *path)
|
|
{
|
|
PikaMenuModel *submodel;
|
|
gchar *submenus;
|
|
gchar *submenu;
|
|
gchar *subsubmenus;
|
|
|
|
submodel = g_object_ref (model);
|
|
|
|
if (path == NULL)
|
|
return submodel;
|
|
|
|
submenus = g_strdup (path);
|
|
subsubmenus = submenus;
|
|
|
|
while (subsubmenus && strlen (subsubmenus) > 0)
|
|
{
|
|
gint n_items;
|
|
gint i;
|
|
|
|
submenu = subsubmenus;
|
|
while (*submenu == '/')
|
|
submenu++;
|
|
|
|
subsubmenus = strstr (submenu, "/");
|
|
if (subsubmenus)
|
|
*(subsubmenus++) = '\0';
|
|
|
|
if (strlen (submenu) == 0)
|
|
break;
|
|
|
|
n_items = g_menu_model_get_n_items (G_MENU_MODEL (submodel));
|
|
for (i = 0; i < n_items; i++)
|
|
{
|
|
GMenuModel *subsubmodel;
|
|
gchar *label = NULL;
|
|
gchar *canon_label = NULL;
|
|
|
|
subsubmodel = g_menu_model_get_item_link (G_MENU_MODEL (submodel), i, G_MENU_LINK_SUBMENU);
|
|
g_menu_model_get_item_attribute (G_MENU_MODEL (submodel), i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
|
|
if (label)
|
|
canon_label = pika_utils_make_canonical_menu_label (label);
|
|
|
|
if (subsubmodel && g_strcmp0 (canon_label, submenu) == 0)
|
|
{
|
|
g_object_unref (submodel);
|
|
submodel = PIKA_MENU_MODEL (subsubmodel);
|
|
|
|
g_free (label);
|
|
g_free (canon_label);
|
|
break;
|
|
}
|
|
g_clear_object (&subsubmodel);
|
|
g_free (label);
|
|
g_free (canon_label);
|
|
}
|
|
g_return_val_if_fail (i < n_items, NULL);
|
|
}
|
|
|
|
g_free (submenus);
|
|
|
|
return submodel;
|
|
}
|
|
|
|
const gchar *
|
|
pika_menu_model_get_path (PikaMenuModel *model)
|
|
{
|
|
return model->priv->path;
|
|
}
|
|
|
|
void
|
|
pika_menu_model_set_title (PikaMenuModel *model,
|
|
const gchar *path,
|
|
const gchar *title)
|
|
{
|
|
GMenuItem *item;
|
|
PikaMenuModel *submenu = NULL;
|
|
|
|
item = pika_menu_model_get_menu_item_rec (model, path, &submenu, NULL);
|
|
|
|
if (item != NULL)
|
|
{
|
|
g_menu_item_set_label (item, title);
|
|
g_object_notify (G_OBJECT (submenu), "title");
|
|
}
|
|
}
|
|
|
|
void
|
|
pika_menu_model_set_color (PikaMenuModel *model,
|
|
const gchar *path,
|
|
const PikaRGB *color)
|
|
{
|
|
GMenuItem *item;
|
|
PikaMenuModel *submenu = NULL;
|
|
|
|
item = pika_menu_model_get_menu_item_rec (model, path, &submenu, NULL);
|
|
|
|
if (item != NULL)
|
|
{
|
|
if (color == NULL)
|
|
g_clear_pointer (&submenu->priv->submenu_color, g_free);
|
|
else if (submenu->priv->submenu_color == NULL)
|
|
submenu->priv->submenu_color = g_new (PikaRGB, 1);
|
|
|
|
if (color != NULL)
|
|
*submenu->priv->submenu_color = *color;
|
|
|
|
g_object_notify (G_OBJECT (submenu), "color");
|
|
}
|
|
}
|
|
|
|
|
|
/* Private functions. */
|
|
|
|
static PikaMenuModel *
|
|
pika_menu_model_new_section (PikaUIManager *manager,
|
|
GMenuModel *model,
|
|
const gchar *path)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_UI_MANAGER (manager), NULL);
|
|
|
|
return g_object_new (PIKA_TYPE_MENU_MODEL,
|
|
"manager", manager,
|
|
"model", model,
|
|
"path", path,
|
|
"section", TRUE,
|
|
NULL);
|
|
}
|
|
|
|
static PikaMenuModel *
|
|
pika_menu_model_new_submenu (PikaUIManager *manager,
|
|
GMenuModel *model,
|
|
const gchar *path)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_UI_MANAGER (manager), NULL);
|
|
|
|
return g_object_new (PIKA_TYPE_MENU_MODEL,
|
|
"manager", manager,
|
|
"model", model,
|
|
"path", path,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
pika_menu_model_initialize (PikaMenuModel *model,
|
|
GMenuModel *gmodel)
|
|
{
|
|
gint n_items;
|
|
|
|
g_return_if_fail (PIKA_IS_MENU_MODEL (model));
|
|
g_return_if_fail (gmodel == NULL || G_IS_MENU_MODEL (gmodel));
|
|
|
|
n_items = gmodel != NULL ? g_menu_model_get_n_items (gmodel) : 0;
|
|
for (int i = 0; i < n_items; i++)
|
|
{
|
|
GMenuModel *subsection;
|
|
GMenuModel *submenu;
|
|
gchar *label = NULL;
|
|
gchar *action_name = NULL;
|
|
GMenuItem *item = NULL;
|
|
|
|
subsection = g_menu_model_get_item_link (G_MENU_MODEL (gmodel), i, G_MENU_LINK_SECTION);
|
|
submenu = g_menu_model_get_item_link (G_MENU_MODEL (gmodel), i, G_MENU_LINK_SUBMENU);
|
|
g_menu_model_get_item_attribute (G_MENU_MODEL (gmodel), i,
|
|
G_MENU_ATTRIBUTE_LABEL, "s", &label);
|
|
g_menu_model_get_item_attribute (G_MENU_MODEL (gmodel), i,
|
|
G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
|
|
|
|
if (subsection != NULL)
|
|
{
|
|
PikaMenuModel *submodel;
|
|
gchar *section_name = NULL;
|
|
|
|
submodel = pika_menu_model_new_section (model->priv->manager, subsection,
|
|
model->priv->path);
|
|
item = g_menu_item_new_section (label, G_MENU_MODEL (submodel));
|
|
|
|
if (g_menu_model_get_item_attribute (G_MENU_MODEL (gmodel), i,
|
|
"section-name", "s", §ion_name))
|
|
g_hash_table_insert (model->priv->named_sections,
|
|
(gpointer) section_name,
|
|
item);
|
|
|
|
g_object_unref (submodel);
|
|
}
|
|
else if (submenu != NULL && label == NULL)
|
|
{
|
|
PikaMenuModel *submodel;
|
|
PikaAction *action;
|
|
gchar *canon_label;
|
|
const gchar *group_label;
|
|
gchar *path;
|
|
|
|
g_return_if_fail (action_name != NULL);
|
|
|
|
action = pika_ui_manager_find_action (model->priv->manager, NULL, action_name);
|
|
/* As a special case, when a submenu has no label, we expect it to
|
|
* have an action attribute, which must be for a radio action. In such
|
|
* a case, we'll use the radio actions' group label as submenu title.
|
|
* See e.g.: menus/gradient-editor-menu.ui
|
|
*/
|
|
g_return_if_fail (PIKA_IS_RADIO_ACTION (action));
|
|
|
|
group_label = pika_radio_action_get_group_label (PIKA_RADIO_ACTION (action));
|
|
canon_label = pika_utils_make_canonical_menu_label (group_label);
|
|
|
|
path = g_strdup_printf ("%s/%s",
|
|
model->priv->path ? model->priv->path : "",
|
|
canon_label);
|
|
g_free (canon_label);
|
|
|
|
submodel = pika_menu_model_new_submenu (model->priv->manager, submenu, path);
|
|
item = g_menu_item_new_submenu (group_label, G_MENU_MODEL (submodel));
|
|
g_signal_connect_object (action, "notify::group-label",
|
|
G_CALLBACK (pika_menu_model_notify_group_label),
|
|
item, 0);
|
|
|
|
g_object_unref (submodel);
|
|
g_free (path);
|
|
}
|
|
else if (submenu != NULL)
|
|
{
|
|
PikaMenuModel *submodel;
|
|
gchar *canon_label;
|
|
gchar *path;
|
|
const gchar *en_label;
|
|
|
|
g_return_if_fail (label != NULL);
|
|
en_label = g_object_get_data (G_OBJECT (submenu),
|
|
"pika-ui-manager-menu-model-en-label");
|
|
g_return_if_fail (en_label != NULL);
|
|
canon_label = pika_utils_make_canonical_menu_label (en_label);
|
|
path = g_strdup_printf ("%s/%s",
|
|
model->priv->path ? model->priv->path : "",
|
|
canon_label);
|
|
g_free (canon_label);
|
|
|
|
submodel = pika_menu_model_new_submenu (model->priv->manager, submenu, path);
|
|
item = g_menu_item_new_submenu (label, G_MENU_MODEL (submodel));
|
|
submodel->priv->submenu_item = item;
|
|
|
|
g_object_unref (submodel);
|
|
g_free (path);
|
|
}
|
|
else
|
|
{
|
|
PikaAction *action;
|
|
gchar *label_variant = NULL;
|
|
|
|
item = g_menu_item_new_from_model (G_MENU_MODEL (gmodel), i);
|
|
|
|
g_return_if_fail (action_name != NULL);
|
|
|
|
action = pika_ui_manager_find_action (model->priv->manager, NULL, action_name);
|
|
|
|
if (model->priv->manager->store_action_paths)
|
|
/* Special-case the main menu manager when constructing it as
|
|
* this is the only one which should set the menu path.
|
|
*/
|
|
pika_action_set_menu_path (action, pika_menu_model_get_path (model));
|
|
|
|
g_signal_connect_object (action,
|
|
"notify::visible",
|
|
G_CALLBACK (pika_menu_model_action_notify_visible),
|
|
model, 0);
|
|
|
|
g_menu_item_get_attribute (item, "label-variant", "s",
|
|
&label_variant);
|
|
if (g_strcmp0 (label_variant, "long") == 0)
|
|
g_menu_item_set_label (item, pika_action_get_label (action));
|
|
else
|
|
g_menu_item_set_label (item, pika_action_get_short_label (action));
|
|
|
|
g_signal_connect_object (action,
|
|
"notify::short-label",
|
|
G_CALLBACK (pika_menu_model_action_notify_label),
|
|
item, 0);
|
|
g_signal_connect_object (action,
|
|
"notify::label",
|
|
G_CALLBACK (pika_menu_model_action_notify_label),
|
|
item, 0);
|
|
|
|
/* We want PikaRadioAction to be GTK_MENU_TRACKER_ITEM_ROLE_RADIO,
|
|
* in order to be displayed as radio menu items (as used to be
|
|
* GtkRadioAction) even in the gtk_application_set_menubar() code path.
|
|
*
|
|
* GTK do this by checking that the item has a "target" parameter,
|
|
* which we don't add in our .ui file. Instead let's do this
|
|
* programmatically, hence avoiding human errors.
|
|
* See: gtk/gtkmenutrackeritem.c
|
|
*/
|
|
if (PIKA_IS_RADIO_ACTION (action))
|
|
{
|
|
gint target;
|
|
|
|
g_object_get (action, "value", &target, NULL);
|
|
g_menu_item_set_attribute (item, G_MENU_ATTRIBUTE_TARGET, "i", target);
|
|
}
|
|
|
|
g_free (label_variant);
|
|
}
|
|
|
|
if (item)
|
|
model->priv->items = g_list_append (model->priv->items, item);
|
|
|
|
g_free (label);
|
|
g_free (action_name);
|
|
g_clear_object (&subsection);
|
|
g_clear_object (&submenu);
|
|
}
|
|
|
|
if (! model->priv->is_section)
|
|
{
|
|
g_signal_connect_object (model->priv->manager, "ui-added",
|
|
G_CALLBACK (pika_menu_model_ui_added),
|
|
model, 0);
|
|
g_signal_connect_object (model->priv->manager, "ui-removed",
|
|
G_CALLBACK (pika_menu_model_ui_removed),
|
|
model, 0);
|
|
pika_ui_manager_foreach_ui (model->priv->manager,
|
|
(PikaUIMenuCallback) pika_menu_model_ui_added,
|
|
model);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns the directory to create as a new submodel to @model, with
|
|
* @canonical_path being the fully canonical path name (all slashes unique,
|
|
* leading slash, no trailing slash, section name removed and no mnemonic), e.g.
|
|
* "/some/path/name" and @mnemonic_path is the fully canonical path name, except
|
|
* that it contains mnemonics, e.g. "/_some/p_ath/_name".
|
|
*
|
|
* The code relies on these canonicalized characteristics so you must make sure
|
|
* to feed properly formatted paths.
|
|
*
|
|
* The return value is the canonical path of the first submenu to create,
|
|
* including mnemonics, e.g. if @model was for path "/some", then this function
|
|
* returns "p_ath".
|
|
*/
|
|
static gchar *
|
|
pika_menu_model_handles_subpath (PikaMenuModel *model,
|
|
const gchar *canonical_path,
|
|
const gchar *mnemonic_path)
|
|
{
|
|
gchar *new_dir;
|
|
gchar *end_new_dir;
|
|
gint n_slash = 0;
|
|
|
|
if (model->priv->path != NULL &&
|
|
(! g_str_has_prefix (canonical_path, model->priv->path) ||
|
|
canonical_path[strlen (model->priv->path)] != '/'))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
for (GList *iter = model->priv->items; iter; iter = iter->next)
|
|
{
|
|
GMenuModel *submenu = NULL;
|
|
GMenuModel *subsection = NULL;
|
|
GMenuItem *item = iter->data;
|
|
|
|
subsection = g_menu_item_get_link (item, G_MENU_LINK_SECTION);
|
|
|
|
if (subsection != NULL)
|
|
{
|
|
/* Checking if a subsection is a sub-path only gives a partial view of
|
|
* the whole menu. So we only handle the negative result (which means
|
|
* we found a submenu model which will handle this path instead).
|
|
*/
|
|
new_dir = pika_menu_model_handles_subpath (PIKA_MENU_MODEL (subsection),
|
|
canonical_path, mnemonic_path);
|
|
if (new_dir == NULL)
|
|
{
|
|
g_clear_object (&subsection);
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
g_free (new_dir);
|
|
}
|
|
}
|
|
else if ((submenu = g_menu_item_get_link (item, G_MENU_LINK_SUBMENU)) != NULL)
|
|
{
|
|
gchar *subpath;
|
|
|
|
subpath = g_strdup_printf ("%s/", PIKA_MENU_MODEL (submenu)->priv->path);
|
|
|
|
if (g_strcmp0 (canonical_path, PIKA_MENU_MODEL (submenu)->priv->path) == 0 ||
|
|
g_str_has_prefix (canonical_path, subpath))
|
|
{
|
|
/* A submodel will handle the new path. */
|
|
g_free (subpath);
|
|
g_clear_object (&subsection);
|
|
g_clear_object (&submenu);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
g_free (subpath);
|
|
}
|
|
|
|
g_clear_object (&subsection);
|
|
g_clear_object (&submenu);
|
|
}
|
|
|
|
if (model->priv->path != NULL)
|
|
{
|
|
n_slash = 1;
|
|
new_dir = model->priv->path;
|
|
while ((new_dir = strstr (new_dir + 1, "/")) != NULL)
|
|
n_slash++;
|
|
}
|
|
|
|
new_dir = (gchar *) mnemonic_path + 1;
|
|
while (n_slash-- > 0)
|
|
new_dir = strstr (new_dir, "/") + 1;
|
|
|
|
end_new_dir = strstr (new_dir, "/");
|
|
if (end_new_dir)
|
|
new_dir = g_strndup (new_dir, end_new_dir - new_dir);
|
|
else
|
|
new_dir = g_strdup (new_dir);
|
|
|
|
return new_dir;
|
|
}
|
|
|
|
static GMenuItem *
|
|
pika_menu_model_get_item (PikaMenuModel *model,
|
|
gint idx)
|
|
{
|
|
PikaMenuModel *m = PIKA_MENU_MODEL (model);
|
|
gint cur = -1;
|
|
|
|
for (GList *iter = m->priv->items; iter; iter = iter->next)
|
|
{
|
|
GMenuModel *subsection;
|
|
GMenuModel *submenu;
|
|
const gchar *action_name = NULL;
|
|
|
|
subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION);
|
|
submenu = g_menu_item_get_link (iter->data, G_MENU_LINK_SUBMENU);
|
|
|
|
if (subsection || submenu)
|
|
{
|
|
cur++;
|
|
}
|
|
/* Do not count invisible actions. */
|
|
else if (g_menu_item_get_attribute (iter->data,
|
|
G_MENU_ATTRIBUTE_ACTION,
|
|
"&s", &action_name))
|
|
{
|
|
PikaAction *action;
|
|
|
|
action = pika_ui_manager_find_action (model->priv->manager, NULL,
|
|
action_name);
|
|
|
|
if (pika_action_is_visible (action))
|
|
cur++;
|
|
}
|
|
|
|
g_clear_object (&subsection);
|
|
g_clear_object (&submenu);
|
|
|
|
if (cur == idx)
|
|
return iter->data;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static GMenuItem *
|
|
pika_menu_model_get_menu_item_rec (PikaMenuModel *model,
|
|
const gchar *path,
|
|
PikaMenuModel **menu,
|
|
GMenuItem *item)
|
|
{
|
|
g_return_val_if_fail (item == model->priv->submenu_item, NULL);
|
|
g_return_val_if_fail (menu != NULL && *menu == NULL, NULL);
|
|
|
|
if (pika_utils_are_menu_path_identical (path, model->priv->path, NULL, NULL, NULL))
|
|
{
|
|
*menu = model;
|
|
return item;
|
|
}
|
|
else
|
|
{
|
|
GList *iter;
|
|
GMenuItem *found = NULL;
|
|
|
|
for (iter = model->priv->items; iter; iter = iter->next)
|
|
{
|
|
GMenuItem *subitem = iter->data;
|
|
GMenuModel *submenu = NULL;
|
|
GMenuModel *section = NULL;
|
|
|
|
submenu = g_menu_item_get_link (subitem, G_MENU_LINK_SUBMENU);
|
|
section = g_menu_item_get_link (subitem, G_MENU_LINK_SECTION);
|
|
|
|
if (section != NULL)
|
|
{
|
|
found = pika_menu_model_get_menu_item_rec (PIKA_MENU_MODEL (section), path, menu, NULL);
|
|
}
|
|
else if (submenu != NULL)
|
|
{
|
|
gchar *subpath;
|
|
|
|
subpath = g_strdup_printf ("%s/", PIKA_MENU_MODEL (submenu)->priv->path);
|
|
|
|
if (g_strcmp0 (path, PIKA_MENU_MODEL (submenu)->priv->path) == 0 ||
|
|
g_str_has_prefix (path, subpath))
|
|
{
|
|
found = pika_menu_model_get_menu_item_rec (PIKA_MENU_MODEL (submenu), path, menu, subitem);
|
|
}
|
|
|
|
g_free (subpath);
|
|
}
|
|
|
|
g_clear_object (&submenu);
|
|
g_clear_object (§ion);
|
|
|
|
if (found != NULL)
|
|
break;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_menu_model_notify_group_label (PikaRadioAction *action,
|
|
const GParamSpec *pspec,
|
|
GMenuItem *item)
|
|
{
|
|
g_menu_item_set_label (item, pika_radio_action_get_group_label (action));
|
|
}
|
|
|
|
static void
|
|
pika_menu_model_action_notify_visible (PikaAction *action,
|
|
GParamSpec *pspec,
|
|
PikaMenuModel *model)
|
|
{
|
|
gint pos;
|
|
gboolean visible;
|
|
|
|
pos = pika_menu_model_get_position (model, pika_action_get_name (action),
|
|
&visible);
|
|
|
|
if (visible)
|
|
g_menu_model_items_changed (G_MENU_MODEL (model), pos, 0, 1);
|
|
else
|
|
g_menu_model_items_changed (G_MENU_MODEL (model), pos, 1, 0);
|
|
}
|
|
|
|
static void
|
|
pika_menu_model_action_notify_label (PikaAction *action,
|
|
GParamSpec *pspec,
|
|
GMenuItem *item)
|
|
{
|
|
gchar *label_variant = NULL;
|
|
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
g_return_if_fail (G_IS_MENU_ITEM (item));
|
|
|
|
g_menu_item_get_attribute (item, "label-variant", "s", &label_variant);
|
|
if (g_strcmp0 (label_variant, "long") == 0)
|
|
g_menu_item_set_label (item, pika_action_get_label (action));
|
|
else
|
|
g_menu_item_set_label (item, pika_action_get_short_label (action));
|
|
g_free (label_variant);
|
|
}
|
|
|
|
static gboolean
|
|
pika_menu_model_ui_added (PikaUIManager *manager,
|
|
const gchar *path,
|
|
const gchar *action_name,
|
|
gboolean top,
|
|
PikaMenuModel *model)
|
|
{
|
|
gchar *canonical_path = NULL;
|
|
gchar *mnemonic_path = NULL;
|
|
gchar *section_name = NULL;
|
|
gboolean added = FALSE;
|
|
PikaMenuModel *mod_model = g_object_ref (model);
|
|
gchar *new_dir;
|
|
|
|
if (pika_utils_are_menu_path_identical (path, model->priv->path, &canonical_path,
|
|
&mnemonic_path, §ion_name))
|
|
{
|
|
GApplication *app = model->priv->manager->pika->app;
|
|
GAction *action;
|
|
gchar *detailed_action_name;
|
|
const gchar *action_prefix = "app";
|
|
GMenuItem *item;
|
|
|
|
action = g_action_map_lookup_action (G_ACTION_MAP (app), action_name);
|
|
if (action == NULL)
|
|
{
|
|
action = (GAction *) pika_ui_manager_find_action (manager, NULL, action_name);
|
|
if (action != NULL)
|
|
{
|
|
PikaActionGroup *group;
|
|
|
|
group = pika_action_get_group (PIKA_ACTION (action));
|
|
if (group != NULL)
|
|
action_prefix = pika_action_group_get_name (group);
|
|
}
|
|
}
|
|
|
|
g_return_val_if_fail (action != NULL, FALSE);
|
|
|
|
added = TRUE;
|
|
|
|
if (section_name != NULL)
|
|
{
|
|
GMenuItem *section_item;
|
|
|
|
section_item = g_hash_table_lookup (model->priv->named_sections, section_name);
|
|
if (section_item)
|
|
{
|
|
g_object_unref (mod_model);
|
|
mod_model = PIKA_MENU_MODEL (g_menu_item_get_link (section_item, G_MENU_LINK_SECTION));
|
|
}
|
|
}
|
|
|
|
g_signal_handlers_disconnect_by_func (action,
|
|
G_CALLBACK (pika_menu_model_action_notify_visible),
|
|
mod_model);
|
|
detailed_action_name = g_strdup_printf ("%s.%s", action_prefix, g_action_get_name (action));
|
|
item = g_menu_item_new (pika_action_get_short_label (PIKA_ACTION (action)), detailed_action_name);
|
|
/* TODO: add also G_MENU_ATTRIBUTE_ICON attribute? */
|
|
g_free (detailed_action_name);
|
|
|
|
if (model->priv->manager->store_action_paths)
|
|
pika_action_set_menu_path (PIKA_ACTION (action), pika_menu_model_get_path (model));
|
|
|
|
if (top)
|
|
{
|
|
mod_model->priv->items = g_list_prepend (mod_model->priv->items, item);
|
|
}
|
|
else
|
|
{
|
|
mod_model->priv->items = g_list_append (mod_model->priv->items, item);
|
|
}
|
|
|
|
if (added)
|
|
{
|
|
gint position = pika_menu_model_get_position (mod_model, action_name, NULL);
|
|
|
|
g_signal_connect_object (action,
|
|
"notify::visible",
|
|
G_CALLBACK (pika_menu_model_action_notify_visible),
|
|
mod_model, 0);
|
|
g_signal_connect_object (action,
|
|
"notify::short-label",
|
|
G_CALLBACK (pika_menu_model_action_notify_label),
|
|
item, 0);
|
|
g_signal_connect_object (action,
|
|
"notify::label",
|
|
G_CALLBACK (pika_menu_model_action_notify_label),
|
|
item, 0);
|
|
g_menu_model_items_changed (G_MENU_MODEL (mod_model), position, 0, 1);
|
|
}
|
|
else
|
|
{
|
|
g_object_unref (item);
|
|
}
|
|
}
|
|
else if ((new_dir = pika_menu_model_handles_subpath (model, canonical_path, mnemonic_path)))
|
|
{
|
|
PikaMenuModel *submodel;
|
|
GMenuItem *item;
|
|
gchar *canon_label;
|
|
gchar *submodel_path;
|
|
|
|
canon_label = pika_utils_make_canonical_menu_label (new_dir);
|
|
submodel_path = g_strdup_printf ("%s/%s",
|
|
model->priv->path ? model->priv->path : "",
|
|
canon_label);
|
|
|
|
submodel = pika_menu_model_new_submenu (model->priv->manager, NULL, submodel_path);
|
|
item = g_menu_item_new_submenu (new_dir, G_MENU_MODEL (submodel));
|
|
|
|
if (model->priv->path == NULL)
|
|
model->priv->items = g_list_insert (model->priv->items, item,
|
|
g_list_length (model->priv->items) - 2);
|
|
else
|
|
model->priv->items = g_list_append (model->priv->items, item);
|
|
|
|
g_free (canon_label);
|
|
g_object_unref (submodel);
|
|
g_free (submodel_path);
|
|
g_free (new_dir);
|
|
|
|
g_menu_model_items_changed (G_MENU_MODEL (model),
|
|
pika_menu_model_get_position (model, NULL, NULL),
|
|
1, 0);
|
|
}
|
|
|
|
g_clear_object (&mod_model);
|
|
g_free (canonical_path);
|
|
g_free (mnemonic_path);
|
|
g_free (section_name);
|
|
|
|
return added;
|
|
}
|
|
|
|
static gboolean
|
|
pika_menu_model_ui_removed (PikaUIManager *manager,
|
|
const gchar *path,
|
|
const gchar *action_name,
|
|
PikaMenuModel *model)
|
|
{
|
|
gchar *section_name = NULL;
|
|
gboolean removed = FALSE;
|
|
|
|
if (pika_utils_are_menu_path_identical (path, model->priv->path, NULL, NULL, §ion_name))
|
|
{
|
|
GApplication *app = model->priv->manager->pika->app;
|
|
GMenuItem *item = NULL;
|
|
GMenuModel *subsection = NULL;
|
|
GAction *action;
|
|
GList *iter;
|
|
|
|
action = g_action_map_lookup_action (G_ACTION_MAP (app), action_name);
|
|
|
|
removed = TRUE;
|
|
|
|
for (iter = model->priv->items; iter; iter = iter->next)
|
|
{
|
|
const gchar *action;
|
|
|
|
subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION);
|
|
|
|
if (subsection != NULL)
|
|
{
|
|
if (pika_menu_model_ui_removed (manager, path, action_name,
|
|
PIKA_MENU_MODEL (subsection)))
|
|
break;
|
|
else
|
|
g_clear_object (&subsection);
|
|
}
|
|
else if (g_menu_item_get_attribute (iter->data,
|
|
G_MENU_ATTRIBUTE_ACTION,
|
|
"&s", &action) &&
|
|
/* "action" attribute will start with "app." prefix. */
|
|
g_strcmp0 (action + 4, action_name) == 0)
|
|
{
|
|
item = iter->data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (item)
|
|
{
|
|
gint position;
|
|
|
|
position = pika_menu_model_get_position (model, action_name, NULL);
|
|
|
|
if (action != NULL)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (action,
|
|
G_CALLBACK (pika_menu_model_action_notify_visible),
|
|
model);
|
|
g_signal_handlers_disconnect_by_func (action,
|
|
G_CALLBACK (pika_menu_model_action_notify_label),
|
|
item);
|
|
}
|
|
g_object_unref (item);
|
|
|
|
model->priv->items = g_list_delete_link (model->priv->items, iter);
|
|
|
|
g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 0);
|
|
}
|
|
else
|
|
{
|
|
removed = FALSE;
|
|
|
|
if (subsection == NULL && ! model->priv->is_section)
|
|
g_warning ("%s: no item for action name '%s'.", G_STRFUNC, action_name);
|
|
}
|
|
/* else: removed in a subsection. */
|
|
|
|
g_clear_object (&subsection);
|
|
}
|
|
g_free (section_name);
|
|
|
|
return removed;
|
|
}
|