877 lines
27 KiB
C
877 lines
27 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.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 "libpikawidgets/pikawidgets.h"
|
|
|
|
#include "core/pika.h"
|
|
|
|
#include "pikaaction.h"
|
|
#include "pikaenumaction.h"
|
|
#include "pikahelp-ids.h"
|
|
#include "pikamenu.h"
|
|
#include "pikamenumodel.h"
|
|
#include "pikamenushell.h"
|
|
#include "pikaprocedureaction.h"
|
|
#include "pikaradioaction.h"
|
|
#include "pikauimanager.h"
|
|
#include "pikawidgets-utils.h"
|
|
|
|
#define PIKA_MENU_ACTION_KEY "pika-menu-action"
|
|
|
|
|
|
/**
|
|
* PikaMenu:
|
|
*
|
|
* Our own menu widget.
|
|
*
|
|
* We cannot use the simpler gtk_menu_new_from_model() because it lacks
|
|
* tooltip support and unfortunately GTK does not plan to implement this:
|
|
* https://gitlab.gnome.org/GNOME/gtk/-/issues/785
|
|
*
|
|
* This is why we need to implement our own PikaMenu subclass. It looks very
|
|
* minimal right now, but the whole point is that it is also a PikaMenuShell
|
|
* (where all the real work, with proxy items, syncing with actions and such,
|
|
* happens).
|
|
*/
|
|
|
|
|
|
struct _PikaMenuPrivate
|
|
{
|
|
GTree *submenus;
|
|
|
|
GHashTable *sections;
|
|
};
|
|
|
|
|
|
static void pika_menu_iface_init (PikaMenuShellInterface *iface);
|
|
|
|
static void pika_menu_finalize (GObject *object);
|
|
|
|
static void pika_menu_append (PikaMenuShell *shell,
|
|
PikaMenuModel *model);
|
|
static void pika_menu_add_ui (PikaMenuShell *shell,
|
|
const gchar **paths,
|
|
const gchar *action_name,
|
|
gboolean top);
|
|
static void pika_menu_remove_ui (PikaMenuShell *shell,
|
|
const gchar **paths,
|
|
const gchar *action_name);
|
|
static void pika_menu_model_deleted (PikaMenuShell *shell);
|
|
|
|
static void pika_menu_add_action (PikaMenu *menu,
|
|
const gchar *action_name,
|
|
gboolean long_label,
|
|
GtkWidget *sibling,
|
|
gboolean top,
|
|
GtkRadioMenuItem **group);
|
|
static void pika_menu_remove_action (PikaMenu *menu,
|
|
const gchar *action_name);
|
|
static void pika_menu_append_section (PikaMenu *menu,
|
|
PikaMenuModel *model,
|
|
GtkWidget *start_separator);
|
|
|
|
static void pika_menu_section_items_changed (GMenuModel *model,
|
|
gint position,
|
|
gint removed,
|
|
gint added,
|
|
PikaMenu *menu);
|
|
|
|
static void pika_menu_submenu_notify_title (PikaMenuModel *model,
|
|
const GParamSpec *pspec,
|
|
GtkMenuItem *item);
|
|
static void pika_menu_submenu_notify_color (PikaMenuModel *model,
|
|
const GParamSpec *pspec,
|
|
GtkMenuItem *item);
|
|
|
|
static void pika_menu_toggle_item_toggled (GtkWidget *item,
|
|
GAction *action);
|
|
|
|
static void pika_menu_toggle_action_toggled (PikaAction *action,
|
|
GtkCheckMenuItem *item);
|
|
static void pika_menu_action_notify_visible (PikaAction *action,
|
|
const GParamSpec *pspec,
|
|
GtkWidget *item);
|
|
|
|
static void pika_menu_help_fun (const gchar *bogus_help_id,
|
|
gpointer help_data);
|
|
static void pika_menu_hide_double_separators (PikaMenu *menu);
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (PikaMenu, pika_menu, GTK_TYPE_MENU,
|
|
G_ADD_PRIVATE (PikaMenu)
|
|
G_IMPLEMENT_INTERFACE (PIKA_TYPE_MENU_SHELL, pika_menu_iface_init))
|
|
|
|
#define parent_class pika_menu_parent_class
|
|
|
|
|
|
/* Class functions */
|
|
|
|
static void
|
|
pika_menu_class_init (PikaMenuClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = pika_menu_finalize;
|
|
object_class->get_property = pika_menu_shell_get_property;
|
|
object_class->set_property = pika_menu_shell_set_property;
|
|
|
|
pika_menu_shell_install_properties (object_class);
|
|
}
|
|
|
|
static void
|
|
pika_menu_init (PikaMenu *menu)
|
|
{
|
|
menu->priv = pika_menu_get_instance_private (menu);
|
|
|
|
menu->priv->submenus = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL,
|
|
g_free, NULL);
|
|
menu->priv->sections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
|
g_object_unref, NULL);
|
|
|
|
pika_menu_shell_init (PIKA_MENU_SHELL (menu));
|
|
|
|
pika_help_connect (GTK_WIDGET (menu), pika_menu_help_fun,
|
|
PIKA_HELP_MAIN, menu, NULL);
|
|
}
|
|
|
|
static void
|
|
pika_menu_iface_init (PikaMenuShellInterface *iface)
|
|
{
|
|
iface->append = pika_menu_append;
|
|
iface->add_ui = pika_menu_add_ui;
|
|
iface->remove_ui = pika_menu_remove_ui;
|
|
iface->model_deleted = pika_menu_model_deleted;
|
|
}
|
|
|
|
static void
|
|
pika_menu_finalize (GObject *object)
|
|
{
|
|
PikaMenu *menu = PIKA_MENU (object);
|
|
|
|
g_clear_pointer (&menu->priv->submenus, g_tree_unref);
|
|
g_hash_table_unref (menu->priv->sections);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
pika_menu_append (PikaMenuShell *shell,
|
|
PikaMenuModel *model)
|
|
{
|
|
static GtkRadioMenuItem *group = NULL;
|
|
PikaMenu *menu = PIKA_MENU (shell);
|
|
PikaUIManager *manager = pika_menu_shell_get_manager (PIKA_MENU_SHELL (shell));
|
|
gint n_items;
|
|
|
|
g_return_if_fail (GTK_IS_CONTAINER (shell));
|
|
|
|
n_items = g_menu_model_get_n_items (G_MENU_MODEL (model));
|
|
for (gint i = 0; i < n_items; i++)
|
|
{
|
|
GMenuModel *subsection;
|
|
GMenuModel *submenu;
|
|
gchar *label = NULL;
|
|
gchar *action_name = NULL;
|
|
|
|
subsection = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SECTION);
|
|
submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SUBMENU);
|
|
g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
|
|
g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
|
|
|
|
if (subsection != NULL)
|
|
{
|
|
GtkWidget *item;
|
|
|
|
group = NULL;
|
|
|
|
item = gtk_separator_menu_item_new ();
|
|
gtk_container_add (GTK_CONTAINER (shell), item);
|
|
gtk_widget_show (item);
|
|
|
|
/* Don't use pika_menu_shell_append() here because we don't want to
|
|
* override the main model for this menu.
|
|
* Instead we keep track of each subsection model and their position.
|
|
*/
|
|
pika_menu_append_section (menu, PIKA_MENU_MODEL (subsection), item);
|
|
|
|
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
|
|
* pika_menu_append() 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 = pika_menu_new (manager);
|
|
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), subcontainer);
|
|
pika_menu_shell_append (PIKA_MENU_SHELL (subcontainer), PIKA_MENU_MODEL (submenu));
|
|
gtk_widget_show (subcontainer);
|
|
|
|
g_tree_insert (menu->priv->submenus,
|
|
pika_utils_make_canonical_menu_label (label),
|
|
subcontainer);
|
|
g_signal_connect (submenu, "notify::title",
|
|
G_CALLBACK (pika_menu_submenu_notify_title),
|
|
item);
|
|
g_signal_connect (submenu, "notify::color",
|
|
G_CALLBACK (pika_menu_submenu_notify_color),
|
|
item);
|
|
}
|
|
else
|
|
{
|
|
gchar *label_variant = NULL;
|
|
|
|
g_return_if_fail (action_name != NULL);
|
|
|
|
g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, "label-variant", "s", &label_variant);
|
|
pika_menu_add_action (menu, action_name,
|
|
/* By default, we use the short label in menus,
|
|
* unless "label-variant" attribute is set to
|
|
* "long".
|
|
*/
|
|
g_strcmp0 (label_variant, "long") == 0,
|
|
NULL, FALSE, &group);
|
|
g_free (label_variant);
|
|
}
|
|
|
|
g_free (label);
|
|
g_free (action_name);
|
|
g_clear_object (&submenu);
|
|
g_clear_object (&subsection);
|
|
}
|
|
|
|
pika_menu_hide_double_separators (menu);
|
|
}
|
|
|
|
static void
|
|
pika_menu_add_ui (PikaMenuShell *shell,
|
|
const gchar **paths,
|
|
const gchar *action_name,
|
|
gboolean top)
|
|
{
|
|
PikaMenu *menu = PIKA_MENU (shell);
|
|
PikaUIManager *manager = pika_menu_shell_get_manager (PIKA_MENU_SHELL (shell));
|
|
GtkWidget *submenu;
|
|
|
|
g_return_if_fail (paths != NULL && paths[0] != NULL);
|
|
|
|
submenu = g_tree_lookup (menu->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 = pika_menu_new (manager);
|
|
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
|
|
gtk_widget_show (submenu);
|
|
|
|
g_tree_insert (menu->priv->submenus, g_strdup (paths[0]), submenu);
|
|
}
|
|
|
|
pika_menu_add_ui (PIKA_MENU_SHELL (submenu), paths + 1, action_name, top);
|
|
|
|
pika_menu_hide_double_separators (menu);
|
|
}
|
|
|
|
static void
|
|
pika_menu_remove_ui (PikaMenuShell *shell,
|
|
const gchar **paths,
|
|
const gchar *action_name)
|
|
{
|
|
PikaMenu *menu = PIKA_MENU (shell);
|
|
|
|
g_return_if_fail (paths != NULL);
|
|
|
|
if (paths[0] == NULL)
|
|
{
|
|
pika_menu_remove_action (menu, action_name);
|
|
}
|
|
else
|
|
{
|
|
GtkWidget *submenu = NULL;
|
|
|
|
submenu = g_tree_lookup (menu->priv->submenus, paths[0]);
|
|
|
|
g_return_if_fail (submenu != NULL);
|
|
|
|
pika_menu_remove_ui (PIKA_MENU_SHELL (submenu), paths + 1, action_name);
|
|
}
|
|
|
|
pika_menu_hide_double_separators (menu);
|
|
}
|
|
|
|
static void
|
|
pika_menu_model_deleted (PikaMenuShell *shell)
|
|
{
|
|
/* This will unref the sub-models, hence will disconnect the "items-changed"
|
|
* signal handlers.
|
|
*/
|
|
g_hash_table_remove_all (PIKA_MENU (shell)->priv->sections);
|
|
}
|
|
|
|
|
|
/* Public functions */
|
|
|
|
GtkWidget *
|
|
pika_menu_new (PikaUIManager *manager)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_UI_MANAGER (manager), NULL);
|
|
|
|
return g_object_new (PIKA_TYPE_MENU,
|
|
"manager", manager,
|
|
NULL);
|
|
}
|
|
|
|
void
|
|
pika_menu_merge (PikaMenu *menu,
|
|
PikaMenu *menu2,
|
|
gboolean top)
|
|
{
|
|
GList *children;
|
|
GList *iter;
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (menu2));
|
|
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 (menu2), item);
|
|
if (top)
|
|
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
|
|
else
|
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
|
|
g_object_unref (item);
|
|
}
|
|
|
|
gtk_widget_destroy (GTK_WIDGET (menu2));
|
|
g_list_free (children);
|
|
}
|
|
|
|
|
|
/* Private functions */
|
|
|
|
static void
|
|
pika_menu_add_action (PikaMenu *menu,
|
|
const gchar *action_name,
|
|
gboolean long_label,
|
|
GtkWidget *sibling,
|
|
gboolean top,
|
|
GtkRadioMenuItem **group)
|
|
{
|
|
PikaUIManager *manager;
|
|
PikaAction *action;
|
|
const gchar *action_label;
|
|
GtkWidget *item;
|
|
gboolean visible;
|
|
|
|
g_return_if_fail (PIKA_IS_MENU (menu));
|
|
|
|
manager = pika_menu_shell_get_manager (PIKA_MENU_SHELL (menu));
|
|
action = pika_ui_manager_find_action (manager, NULL, action_name);
|
|
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
|
|
if (long_label)
|
|
action_label = pika_action_get_label (action);
|
|
else
|
|
action_label = pika_action_get_short_label (action);
|
|
g_return_if_fail (action_label != NULL);
|
|
|
|
if (PIKA_IS_TOGGLE_ACTION (action))
|
|
{
|
|
if (PIKA_IS_RADIO_ACTION (action))
|
|
item = gtk_radio_menu_item_new_with_mnemonic_from_widget (group ? *group : NULL,
|
|
action_label);
|
|
else
|
|
item = gtk_check_menu_item_new_with_mnemonic (action_label);
|
|
|
|
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
|
|
pika_toggle_action_get_active (PIKA_TOGGLE_ACTION (action)));
|
|
|
|
if (group)
|
|
{
|
|
if (PIKA_IS_RADIO_ACTION (action))
|
|
*group = GTK_RADIO_MENU_ITEM (item);
|
|
else
|
|
*group = NULL;
|
|
}
|
|
|
|
g_signal_connect (item, "toggled",
|
|
G_CALLBACK (pika_menu_toggle_item_toggled),
|
|
action);
|
|
g_signal_connect_object (action, "toggled",
|
|
G_CALLBACK (pika_menu_toggle_action_toggled),
|
|
item, 0);
|
|
}
|
|
else if (PIKA_IS_PROCEDURE_ACTION (action) ||
|
|
PIKA_IS_ENUM_ACTION (action))
|
|
{
|
|
item = gtk_menu_item_new_with_mnemonic (action_label);
|
|
|
|
if (group)
|
|
*group = NULL;
|
|
|
|
g_signal_connect_swapped (item, "activate",
|
|
G_CALLBACK (pika_action_activate),
|
|
action);
|
|
}
|
|
else
|
|
{
|
|
item = gtk_menu_item_new_with_mnemonic (action_label);
|
|
|
|
if (group)
|
|
*group = NULL;
|
|
|
|
g_signal_connect_swapped (item, "activate",
|
|
G_CALLBACK (pika_action_activate),
|
|
action);
|
|
}
|
|
|
|
pika_action_set_proxy (action, item);
|
|
g_object_set_data (G_OBJECT (item), PIKA_MENU_ACTION_KEY, action);
|
|
|
|
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 (menu));
|
|
|
|
for (GList *iter = children; iter; iter = iter->next)
|
|
{
|
|
if (iter->data == sibling)
|
|
break;
|
|
position++;
|
|
}
|
|
if (! top)
|
|
position++;
|
|
|
|
gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position);
|
|
|
|
g_list_free (children);
|
|
}
|
|
else
|
|
{
|
|
if (top)
|
|
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
|
|
else
|
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
|
|
}
|
|
|
|
visible = pika_action_is_visible (action);
|
|
gtk_widget_set_visible (item, visible);
|
|
if (visible && GTK_IS_MENU (menu))
|
|
{
|
|
GtkWidget *parent = GTK_WIDGET (menu);
|
|
|
|
while (parent != NULL && PIKA_IS_MENU (parent))
|
|
{
|
|
/* 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.
|
|
*/
|
|
GtkWidget *menu_item = gtk_menu_get_attach_widget (GTK_MENU (parent));
|
|
|
|
if (menu_item == NULL)
|
|
break;
|
|
|
|
if (G_TYPE_FROM_INSTANCE (menu_item) == GTK_TYPE_MENU_ITEM)
|
|
gtk_widget_show (menu_item);
|
|
|
|
parent = gtk_widget_get_parent (menu_item);
|
|
}
|
|
}
|
|
|
|
g_signal_connect_object (action, "notify::visible",
|
|
G_CALLBACK (pika_menu_action_notify_visible),
|
|
item, 0);
|
|
}
|
|
|
|
static void
|
|
pika_menu_remove_action (PikaMenu *menu,
|
|
const gchar *action_name)
|
|
{
|
|
PikaUIManager *manager;
|
|
GList *children;
|
|
PikaAction *action;
|
|
|
|
g_return_if_fail (PIKA_IS_MENU (menu));
|
|
|
|
manager = pika_menu_shell_get_manager (PIKA_MENU_SHELL (menu));
|
|
action = pika_ui_manager_find_action (manager, NULL, action_name);
|
|
|
|
g_return_if_fail (PIKA_IS_ACTION (action));
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (menu));
|
|
|
|
for (GList *iter = children; iter; iter = iter->next)
|
|
{
|
|
GtkWidget *child = iter->data;
|
|
PikaAction *item_action;
|
|
|
|
item_action = g_object_get_data (G_OBJECT (child), PIKA_MENU_ACTION_KEY);
|
|
if (item_action == action)
|
|
{
|
|
gtk_widget_destroy (child);
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_list_free (children);
|
|
}
|
|
|
|
static void
|
|
pika_menu_append_section (PikaMenu *menu,
|
|
PikaMenuModel *model,
|
|
GtkWidget *start_separator)
|
|
{
|
|
g_hash_table_insert (menu->priv->sections, g_object_ref (model), start_separator);
|
|
pika_menu_append (PIKA_MENU_SHELL (menu), model);
|
|
|
|
g_signal_connect_object (model, "items-changed",
|
|
G_CALLBACK (pika_menu_section_items_changed),
|
|
menu, 0);
|
|
}
|
|
|
|
static void
|
|
pika_menu_section_items_changed (GMenuModel *model,
|
|
gint position,
|
|
gint removed,
|
|
gint added,
|
|
PikaMenu *menu)
|
|
{
|
|
GList *children;
|
|
GList *iter;
|
|
GtkWidget *separator;
|
|
gboolean found = FALSE;
|
|
gint count = position;
|
|
gint real_pos = 0;
|
|
|
|
separator = g_hash_table_lookup (menu->priv->sections, model);
|
|
g_return_if_fail (separator != NULL);
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (menu));
|
|
for (iter = children; iter; iter = iter->next)
|
|
{
|
|
real_pos++;
|
|
|
|
if (! found)
|
|
{
|
|
if (iter->data == separator)
|
|
found = TRUE;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (count > 0)
|
|
{
|
|
/* Assume we don't have sections within a section, i.e. in particular
|
|
* we don't have more separators, which would mess the count!
|
|
*/
|
|
count--;
|
|
continue;
|
|
}
|
|
else if (removed > 0)
|
|
{
|
|
gtk_widget_destroy (iter->data);
|
|
removed--;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (added > 0)
|
|
{
|
|
gchar *action_name = NULL;
|
|
gchar *label_variant = NULL;
|
|
|
|
g_menu_model_get_item_attribute (G_MENU_MODEL (model), position,
|
|
G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
|
|
g_menu_model_get_item_attribute (G_MENU_MODEL (model), position,
|
|
"label-variant", "s", &label_variant);
|
|
|
|
g_return_if_fail (action_name != NULL);
|
|
pika_menu_add_action (menu, action_name,
|
|
g_strcmp0 (label_variant, "long") == 0,
|
|
iter ? iter->data : NULL,
|
|
iter ? TRUE : FALSE, NULL);
|
|
g_free (action_name);
|
|
g_free (label_variant);
|
|
|
|
added--;
|
|
position++;
|
|
}
|
|
g_list_free (children);
|
|
|
|
pika_menu_hide_double_separators (menu);
|
|
}
|
|
|
|
static void
|
|
pika_menu_submenu_notify_title (PikaMenuModel *model,
|
|
const GParamSpec *pspec,
|
|
GtkMenuItem *item)
|
|
{
|
|
gchar *title;
|
|
|
|
g_object_get (model,
|
|
"title", &title,
|
|
NULL);
|
|
gtk_menu_item_set_label (item, title);
|
|
g_free (title);
|
|
}
|
|
|
|
static void
|
|
pika_menu_submenu_notify_color (PikaMenuModel *model,
|
|
const GParamSpec *pspec,
|
|
GtkMenuItem *item)
|
|
{
|
|
PikaRGB *color = NULL;
|
|
GtkWidget *image = NULL;
|
|
gint width, height;
|
|
|
|
g_object_get (model,
|
|
"color", &color,
|
|
NULL);
|
|
|
|
if (color)
|
|
{
|
|
image = pika_color_area_new (color, PIKA_COLOR_AREA_SMALL_CHECKS, 0);
|
|
pika_color_area_set_draw_border (PIKA_COLOR_AREA (image), TRUE);
|
|
|
|
/* TODO: the color area should be color-managed. */
|
|
/*pika_color_area_set_color_config (PIKA_COLOR_AREA (image),*/
|
|
/*pika->config->color_management);*/
|
|
|
|
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
|
|
gtk_widget_set_size_request (image, width, height);
|
|
gtk_widget_show (image);
|
|
}
|
|
|
|
pika_menu_item_set_image (item, image, NULL);
|
|
g_free (color);
|
|
}
|
|
|
|
static void
|
|
pika_menu_toggle_item_toggled (GtkWidget *item,
|
|
GAction *action)
|
|
{
|
|
gboolean active = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
|
|
|
|
g_signal_handlers_block_by_func (action,
|
|
G_CALLBACK (pika_menu_toggle_action_toggled),
|
|
item);
|
|
pika_toggle_action_set_active (PIKA_TOGGLE_ACTION (action), active);
|
|
g_signal_handlers_unblock_by_func (action,
|
|
G_CALLBACK (pika_menu_toggle_action_toggled),
|
|
item);
|
|
}
|
|
|
|
static void
|
|
pika_menu_toggle_action_toggled (PikaAction *action,
|
|
GtkCheckMenuItem *item)
|
|
{
|
|
gboolean active = pika_toggle_action_get_active (PIKA_TOGGLE_ACTION (action));
|
|
|
|
g_signal_handlers_block_by_func (item,
|
|
G_CALLBACK (pika_menu_toggle_item_toggled),
|
|
action);
|
|
gtk_check_menu_item_set_active (item, active);
|
|
g_signal_handlers_unblock_by_func (item,
|
|
G_CALLBACK (pika_menu_toggle_item_toggled),
|
|
action);
|
|
}
|
|
|
|
static void
|
|
pika_menu_action_notify_visible (PikaAction *action,
|
|
const GParamSpec *pspec,
|
|
GtkWidget *item)
|
|
{
|
|
GtkWidget *container;
|
|
|
|
gtk_widget_set_visible (item, pika_action_is_visible (action));
|
|
|
|
container = gtk_widget_get_parent (item);
|
|
if (pika_action_is_visible (PIKA_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);
|
|
}
|
|
}
|
|
|
|
pika_menu_hide_double_separators (PIKA_MENU (container));
|
|
}
|
|
|
|
static void
|
|
pika_menu_help_fun (const gchar *bogus_help_id,
|
|
gpointer help_data)
|
|
{
|
|
gchar *help_id = NULL;
|
|
PikaMenu *menu;
|
|
Pika *pika;
|
|
GtkWidget *item;
|
|
gchar *help_domain = NULL;
|
|
gchar *help_string = NULL;
|
|
gchar *domain_separator;
|
|
|
|
g_return_if_fail (PIKA_IS_MENU (help_data));
|
|
|
|
menu = PIKA_MENU (help_data);
|
|
pika = pika_menu_shell_get_manager (PIKA_MENU_SHELL (menu))->pika;
|
|
|
|
g_return_if_fail (PIKA_IS_PIKA (pika));
|
|
|
|
item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (menu));
|
|
|
|
if (item)
|
|
help_id = g_object_get_qdata (G_OBJECT (item), PIKA_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);
|
|
}
|
|
|
|
pika_help (pika, NULL, help_domain, help_string);
|
|
|
|
g_free (help_domain);
|
|
g_free (help_string);
|
|
g_free (help_id);
|
|
}
|
|
|
|
/* With successive sections, we will end up with double separators (end one then
|
|
* start one of the next section). Moreover sometimes, empty sections (e.g.
|
|
* because items are expected to be added later) would make even 3 to 4
|
|
* separators next to each other. This renders very ugly. We need to call this
|
|
* function to hide and show separators after changes.
|
|
*
|
|
* This also hides start and end separators in the menu.
|
|
*/
|
|
static void
|
|
pika_menu_hide_double_separators (PikaMenu *menu)
|
|
{
|
|
GList *children;
|
|
GList *iter;
|
|
GtkWidget *prev_item = NULL;
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (menu));
|
|
for (iter = children; iter; iter = iter->next)
|
|
{
|
|
GtkWidget *item = iter->data;
|
|
|
|
if (GTK_IS_SEPARATOR_MENU_ITEM (item))
|
|
{
|
|
if (prev_item == NULL ||
|
|
GTK_IS_SEPARATOR_MENU_ITEM (prev_item))
|
|
{
|
|
gtk_widget_hide (item);
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_show (item);
|
|
prev_item = item;
|
|
}
|
|
}
|
|
else if (gtk_widget_get_visible (item))
|
|
{
|
|
prev_item = item;
|
|
}
|
|
}
|
|
|
|
if (prev_item != NULL && GTK_IS_SEPARATOR_MENU_ITEM (prev_item))
|
|
gtk_widget_hide (prev_item);
|
|
|
|
g_list_free (children);
|
|
}
|