diff options
Diffstat (limited to 'vcl/unx/gtk3/glomenu.cxx')
-rw-r--r-- | vcl/unx/gtk3/glomenu.cxx | 694 |
1 files changed, 694 insertions, 0 deletions
diff --git a/vcl/unx/gtk3/glomenu.cxx b/vcl/unx/gtk3/glomenu.cxx new file mode 100644 index 0000000000..a391649bbb --- /dev/null +++ b/vcl/unx/gtk3/glomenu.cxx @@ -0,0 +1,694 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <o3tl/safeint.hxx> + +#include <unx/gtk/glomenu.h> + +struct GLOMenu +{ + GMenuModel const parent_instance; + + GArray *items; +}; + +typedef GMenuModelClass GLOMenuClass; + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic ignored "-Wdeprecated-volatile" +#endif +#endif +#endif +G_DEFINE_TYPE (GLOMenu, g_lo_menu, G_TYPE_MENU_MODEL); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +namespace { + +struct item +{ + GHashTable* attributes; // Item attributes. + GHashTable* links; // Item links. +}; + +} + +static void +g_lo_menu_struct_item_init (struct item *menu_item) +{ + menu_item->attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, reinterpret_cast<GDestroyNotify>(g_variant_unref)); + menu_item->links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); +} + +/* We treat attribute names the same as GSettings keys: + * - only lowercase ascii, digits and '-' + * - must start with lowercase + * - must not end with '-' + * - no consecutive '-' + * - not longer than 1024 chars + */ +static bool +valid_attribute_name (const gchar *name) +{ + gint i; + + if (!g_ascii_islower (name[0])) + return false; + + for (i = 1; name[i]; i++) + { + if (name[i] != '-' && + !g_ascii_islower (name[i]) && + !g_ascii_isdigit (name[i])) + return false; + + if (name[i] == '-' && name[i + 1] == '-') + return false; + } + + if (name[i - 1] == '-') + return false; + + if (i > 1024) + return false; + + return true; +} + +/* + * GLOMenu + */ + +static gboolean +g_lo_menu_is_mutable (GMenuModel*) +{ + // Menu is always mutable. + return true; +} + +static gint +g_lo_menu_get_n_items (GMenuModel *model) +{ + g_return_val_if_fail (model != nullptr, 0); + GLOMenu *menu = G_LO_MENU (model); + g_return_val_if_fail (menu->items != nullptr, 0); + + return menu->items->len; +} + +gint +g_lo_menu_get_n_items_from_section (GLOMenu *menu, + gint section) +{ + g_return_val_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len, 0); + + GLOMenu *model = g_lo_menu_get_section (menu, section); + + g_return_val_if_fail (model != nullptr, 0); + + gint length = model->items->len; + + g_object_unref (model); + + return length; +} + +static void +g_lo_menu_get_item_attributes (GMenuModel *model, + gint position, + GHashTable **table) +{ + GLOMenu *menu = G_LO_MENU (model); + *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).attributes); +} + +static void +g_lo_menu_get_item_links (GMenuModel *model, + gint position, + GHashTable **table) +{ + GLOMenu *menu = G_LO_MENU (model); + *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).links); +} + +void +g_lo_menu_insert (GLOMenu *menu, + gint position, + const gchar *label) +{ + g_lo_menu_insert_section (menu, position, label, nullptr); +} + +void +g_lo_menu_insert_in_section (GLOMenu *menu, + gint section, + gint position, + const gchar *label) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + g_return_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len); + + GLOMenu *model = g_lo_menu_get_section (menu, section); + + g_return_if_fail (model != nullptr); + + g_lo_menu_insert (model, position, label); + + g_object_unref (model); +} + +GLOMenu * +g_lo_menu_new() +{ + return G_LO_MENU( g_object_new (G_TYPE_LO_MENU, nullptr) ); +} + +static void +g_lo_menu_set_attribute_value (GLOMenu *menu, + gint position, + const gchar *attribute, + GVariant *value) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + g_return_if_fail (attribute != nullptr); + g_return_if_fail (valid_attribute_name (attribute)); + + if (position >= static_cast<gint>(menu->items->len)) + return; + + struct item menu_item = g_array_index (menu->items, struct item, position); + + if (value != nullptr) + g_hash_table_insert (menu_item.attributes, g_strdup (attribute), g_variant_ref_sink (value)); + else + g_hash_table_remove (menu_item.attributes, attribute); +} + +static GVariant* +g_lo_menu_get_attribute_value_from_item_in_section (GLOMenu *menu, + gint section, + gint position, + const gchar *attribute, + const GVariantType *type) +{ + GMenuModel *model = G_MENU_MODEL (g_lo_menu_get_section (menu, section)); + + g_return_val_if_fail (model != nullptr, nullptr); + + GVariant *value = g_menu_model_get_item_attribute_value (model, + position, + attribute, + type); + + g_object_unref (model); + + return value; +} + +void +g_lo_menu_set_label (GLOMenu *menu, + gint position, + const gchar *label) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + + GVariant *value; + + if (label != nullptr) + value = g_variant_new_string (label); + else + value = nullptr; + + g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_LABEL, value); +} + +void +g_lo_menu_set_icon (GLOMenu *menu, + gint position, + const GIcon *icon) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + + GVariant *value; + + if (icon != nullptr) + { +#if GLIB_CHECK_VERSION(2,38,0) + value = g_icon_serialize (const_cast<GIcon*>(icon)); +#else + value = nullptr; +#endif + } + else + value = nullptr; + +#ifndef G_MENU_ATTRIBUTE_ICON +# define G_MENU_ATTRIBUTE_ICON "icon" +#endif + + g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_ICON, value); + if (value) + g_variant_unref (value); +} + +void +g_lo_menu_set_label_to_item_in_section (GLOMenu *menu, + gint section, + gint position, + const gchar *label) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + + GLOMenu *model = g_lo_menu_get_section (menu, section); + + g_return_if_fail (model != nullptr); + + g_lo_menu_set_label (model, position, label); + + // Notify the update. + g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1); + + g_object_unref (model); +} + +void +g_lo_menu_set_icon_to_item_in_section (GLOMenu *menu, + gint section, + gint position, + const GIcon *icon) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + + GLOMenu *model = g_lo_menu_get_section (menu, section); + + g_return_if_fail (model != nullptr); + + g_lo_menu_set_icon (model, position, icon); + + // Notify the update. + g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1); + + g_object_unref (model); +} + +gchar * +g_lo_menu_get_label_from_item_in_section (GLOMenu *menu, + gint section, + gint position) +{ + g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr); + + GVariant *label_value = g_lo_menu_get_attribute_value_from_item_in_section (menu, + section, + position, + G_MENU_ATTRIBUTE_LABEL, + G_VARIANT_TYPE_STRING); + + gchar *label = nullptr; + + if (label_value) + { + label = g_variant_dup_string (label_value, nullptr); + g_variant_unref (label_value); + } + + return label; +} + +void +g_lo_menu_set_action_and_target_value (GLOMenu *menu, + gint position, + const gchar *action, + GVariant *target_value) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + + GVariant *action_value; + + if (action != nullptr) + { + action_value = g_variant_new_string (action); + } + else + { + action_value = nullptr; + target_value = nullptr; + } + + g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_ACTION, action_value); + g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_TARGET, target_value); + g_lo_menu_set_attribute_value (menu, position, G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION, nullptr); + + g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 1); +} + +void +g_lo_menu_set_action_and_target_value_to_item_in_section (GLOMenu *menu, + gint section, + gint position, + const gchar *command, + GVariant *target_value) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + + GLOMenu *model = g_lo_menu_get_section (menu, section); + + g_return_if_fail (model != nullptr); + + g_lo_menu_set_action_and_target_value (model, position, command, target_value); + + g_object_unref (model); +} + +void +g_lo_menu_set_accelerator_to_item_in_section (GLOMenu *menu, + gint section, + gint position, + const gchar *accelerator) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + + GLOMenu *model = g_lo_menu_get_section (menu, section); + + g_return_if_fail (model != nullptr); + + GVariant *value; + + if (accelerator != nullptr) + value = g_variant_new_string (accelerator); + else + value = nullptr; + + g_lo_menu_set_attribute_value (model, position, G_LO_MENU_ATTRIBUTE_ACCELERATOR, value); + + // Notify the update. + g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1); + + g_object_unref (model); +} + +gchar * +g_lo_menu_get_accelerator_from_item_in_section (GLOMenu *menu, + gint section, + gint position) +{ + g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr); + + GVariant *accel_value = g_lo_menu_get_attribute_value_from_item_in_section (menu, + section, + position, + G_LO_MENU_ATTRIBUTE_ACCELERATOR, + G_VARIANT_TYPE_STRING); + + gchar *accel = nullptr; + + if (accel_value != nullptr) + { + accel = g_variant_dup_string (accel_value, nullptr); + g_variant_unref (accel_value); + } + + return accel; +} + +void +g_lo_menu_set_command_to_item_in_section (GLOMenu *menu, + gint section, + gint position, + const gchar *command) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + + GLOMenu *model = g_lo_menu_get_section (menu, section); + + g_return_if_fail (model != nullptr); + + GVariant *value; + + if (command != nullptr) + value = g_variant_new_string (command); + else + value = nullptr; + + g_lo_menu_set_attribute_value (model, position, G_LO_MENU_ATTRIBUTE_COMMAND, value); + + // Notify the update. + g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1); + + g_object_unref (model); +} + +gchar * +g_lo_menu_get_command_from_item_in_section (GLOMenu *menu, + gint section, + gint position) +{ + g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr); + + GVariant *command_value = g_lo_menu_get_attribute_value_from_item_in_section (menu, + section, + position, + G_LO_MENU_ATTRIBUTE_COMMAND, + G_VARIANT_TYPE_STRING); + + gchar *command = nullptr; + + if (command_value != nullptr) + { + command = g_variant_dup_string (command_value, nullptr); + g_variant_unref (command_value); + } + + return command; +} + +static void +g_lo_menu_set_link (GLOMenu *menu, + gint position, + const gchar *link, + GMenuModel *model) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + g_return_if_fail (link != nullptr); + g_return_if_fail (valid_attribute_name (link)); + + if (position < 0 || o3tl::make_unsigned(position) >= menu->items->len) + position = menu->items->len - 1; + + struct item menu_item = g_array_index (menu->items, struct item, position); + + if (model != nullptr) + g_hash_table_insert (menu_item.links, g_strdup (link), g_object_ref (model)); + else + g_hash_table_remove (menu_item.links, link); +} + +void +g_lo_menu_insert_section (GLOMenu *menu, + gint position, + const gchar *label, + GMenuModel *section) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + + if (position < 0 || o3tl::make_unsigned(position) > menu->items->len) + position = menu->items->len; + + struct item menu_item; + + g_lo_menu_struct_item_init(&menu_item); + + g_array_insert_val (menu->items, position, menu_item); + + g_lo_menu_set_label (menu, position, label); + g_lo_menu_set_link (menu, position, G_MENU_LINK_SECTION, section); + + g_menu_model_items_changed (G_MENU_MODEL (menu), position, 0, 1); +} + +void +g_lo_menu_new_section (GLOMenu *menu, + gint position, + const gchar *label) +{ + GMenuModel *section = G_MENU_MODEL (g_lo_menu_new()); + + g_lo_menu_insert_section (menu, position, label, section); + + g_object_unref (section); +} + +GLOMenu * +g_lo_menu_get_section (GLOMenu *menu, + gint section) +{ + g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr); + + return G_LO_MENU (G_MENU_MODEL_CLASS (g_lo_menu_parent_class) + ->get_item_link (G_MENU_MODEL (menu), section, G_MENU_LINK_SECTION)); +} + +void +g_lo_menu_new_submenu_in_item_in_section (GLOMenu *menu, + gint section, + gint position) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + g_return_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len); + + GLOMenu* model = g_lo_menu_get_section (menu, section); + + g_return_if_fail (model != nullptr); + + if (0 <= position && o3tl::make_unsigned(position) < model->items->len) { + GMenuModel* submenu = G_MENU_MODEL (g_lo_menu_new()); + + g_lo_menu_set_link (model, position, G_MENU_LINK_SUBMENU, submenu); + + g_object_unref (submenu); + + g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1); + + g_object_unref (model); + } +} + +GLOMenu * +g_lo_menu_get_submenu_from_item_in_section (GLOMenu *menu, + gint section, + gint position) +{ + g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr); + g_return_val_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len, nullptr); + + GLOMenu *model = g_lo_menu_get_section (menu, section); + + g_return_val_if_fail (model != nullptr, nullptr); + + GLOMenu *submenu = nullptr; + + if (0 <= position && o3tl::make_unsigned(position) < model->items->len) + submenu = G_LO_MENU (G_MENU_MODEL_CLASS (g_lo_menu_parent_class) + ->get_item_link (G_MENU_MODEL (model), position, G_MENU_LINK_SUBMENU)); + //submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), position, G_MENU_LINK_SUBMENU); + + g_object_unref (model); + + return submenu; +} + +void +g_lo_menu_set_submenu_action_to_item_in_section (GLOMenu *menu, + gint section, + gint position, + const gchar *action) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + + GMenuModel *model = G_MENU_MODEL (g_lo_menu_get_section (menu, section)); + + g_return_if_fail (model != nullptr); + + GVariant *value; + + if (action != nullptr) + value = g_variant_new_string (action); + else + value = nullptr; + + g_lo_menu_set_attribute_value (G_LO_MENU (model), position, G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION, value); + + // Notify the update. + g_menu_model_items_changed (model, position, 1, 1); + + g_object_unref (model); +} + +static void +g_lo_menu_clear_item (struct item *menu_item) +{ + if (menu_item->attributes != nullptr) + g_hash_table_unref (menu_item->attributes); + if (menu_item->links != nullptr) + g_hash_table_unref (menu_item->links); +} + +void +g_lo_menu_remove (GLOMenu *menu, + gint position) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + g_return_if_fail (0 <= position && o3tl::make_unsigned(position) < menu->items->len); + + g_lo_menu_clear_item (&g_array_index (menu->items, struct item, position)); + g_array_remove_index (menu->items, position); + g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 0); +} + +void +g_lo_menu_remove_from_section (GLOMenu *menu, + gint section, + gint position) +{ + g_return_if_fail (G_IS_LO_MENU (menu)); + g_return_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len); + + GLOMenu *model = g_lo_menu_get_section (menu, section); + + g_return_if_fail (model != nullptr); + + g_lo_menu_remove (model, position); + + g_object_unref (model); +} + +static void +g_lo_menu_finalize (GObject *object) +{ + GLOMenu *menu = G_LO_MENU (object); + struct item *items; + gint n_items; + gint i; + + n_items = menu->items->len; + items = reinterpret_cast<struct item *>(g_array_free (menu->items, FALSE)); + for (i = 0; i < n_items; i++) + g_lo_menu_clear_item (&items[i]); + g_free (items); + + G_OBJECT_CLASS (g_lo_menu_parent_class) + ->finalize (object); +} + +static void +g_lo_menu_init (GLOMenu *menu) +{ + menu->items = g_array_new (FALSE, FALSE, sizeof (struct item)); +} + +static void +g_lo_menu_class_init (GLOMenuClass *klass) +{ + GMenuModelClass *model_class = G_MENU_MODEL_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = g_lo_menu_finalize; + + model_class->is_mutable = g_lo_menu_is_mutable; + model_class->get_n_items = g_lo_menu_get_n_items; + model_class->get_item_attributes = g_lo_menu_get_item_attributes; + model_class->get_item_links = g_lo_menu_get_item_links; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |