From: Matthias Clasen Date: Thu, 26 Dec 2019 05:06:48 +0000 (-0500) Subject: combobox: Replace GtkTreeMenu with a popover X-Git-Tag: archive/raspbian/4.4.1+ds1-2+rpi1^2~18^2~20^2~486^2~27 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=e424246134e6cc57a411f416dae1043cdcfd28a6;p=gtk4.git combobox: Replace GtkTreeMenu with a popover This does not currently try to reproduce the exact placement, since GtkPopover doesn't have to have the necessary placement hints. --- diff --git a/gtk/gtkcombobox.c b/gtk/gtkcombobox.c index c10a6835ce..caf9a04f3e 100644 --- a/gtk/gtkcombobox.c +++ b/gtk/gtkcombobox.c @@ -37,7 +37,7 @@ #include "gtkmenushellprivate.h" #include "gtkprivate.h" #include "gtktogglebutton.h" -#include "gtktreemenu.h" +#include "gtktreepopoverprivate.h" #include "gtktypebuiltins.h" #include "gtkeventcontrollerkey.h" @@ -361,6 +361,7 @@ gtk_combo_box_size_allocate (GtkWidget *widget, { GtkComboBox *combo_box = GTK_COMBO_BOX (widget); GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (combo_box); + gint menu_width; gtk_widget_size_allocate (priv->box, &(GtkAllocation) { @@ -368,25 +369,19 @@ gtk_combo_box_size_allocate (GtkWidget *widget, width, height }, baseline); - if (gtk_widget_get_visible (priv->popup_widget)) - { - gint menu_width; - - gtk_widget_set_size_request (priv->popup_widget, -1, -1); + gtk_widget_set_size_request (priv->popup_widget, -1, -1); - if (priv->popup_fixed_width) - gtk_widget_measure (priv->popup_widget, GTK_ORIENTATION_HORIZONTAL, -1, - &menu_width, NULL, NULL, NULL); - else - gtk_widget_measure (priv->popup_widget, GTK_ORIENTATION_HORIZONTAL, -1, - NULL, &menu_width, NULL, NULL); + if (priv->popup_fixed_width) + gtk_widget_measure (priv->popup_widget, GTK_ORIENTATION_HORIZONTAL, -1, + &menu_width, NULL, NULL, NULL); + else + gtk_widget_measure (priv->popup_widget, GTK_ORIENTATION_HORIZONTAL, -1, + NULL, &menu_width, NULL, NULL); - gtk_widget_set_size_request (priv->popup_widget, - MAX (width, menu_width), -1); + gtk_widget_set_size_request (priv->popup_widget, + MAX (width, menu_width), -1); - /* reposition the menu after giving it a new width */ - gtk_menu_reposition (GTK_MENU (priv->popup_widget)); - } + gtk_native_check_resize (GTK_NATIVE (priv->popup_widget)); } static void @@ -834,7 +829,6 @@ gtk_combo_box_init (GtkComboBox *combo_box) { GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (combo_box); GtkStyleContext *context; - GtkTreeMenu *menu; GtkEventController *controller; priv->active = -1; @@ -854,20 +848,16 @@ gtk_combo_box_init (GtkComboBox *combo_box) priv->id_column = -1; g_type_ensure (GTK_TYPE_ICON); - g_type_ensure (GTK_TYPE_TREE_MENU); + g_type_ensure (GTK_TYPE_TREE_POPOVER); gtk_widget_init_template (GTK_WIDGET (combo_box)); context = gtk_widget_get_style_context (priv->button); gtk_style_context_remove_class (context, "toggle"); gtk_style_context_add_class (context, "combo"); - menu = GTK_TREE_MENU (priv->popup_widget); - _gtk_tree_menu_set_row_separator_func (menu, - (GtkTreeViewRowSeparatorFunc)gtk_combo_box_row_separator_func, - combo_box, NULL); - gtk_menu_attach_to_widget (GTK_MENU (menu), - GTK_WIDGET (combo_box), - NULL); + gtk_tree_popover_set_row_separator_func (GTK_TREE_POPOVER (priv->popup_widget), + (GtkTreeViewRowSeparatorFunc)gtk_combo_box_row_separator_func, + combo_box, NULL); controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL | GTK_EVENT_CONTROLLER_SCROLL_DISCRETE); @@ -1174,8 +1164,7 @@ gtk_combo_box_menu_hide (GtkWidget *menu, gtk_combo_box_child_hide (menu,user_data); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), - FALSE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE); } static gboolean @@ -1217,54 +1206,19 @@ tree_column_row_is_sensitive (GtkComboBox *combo_box, } static void -update_menu_sensitivity (GtkComboBox *combo_box, - GtkWidget *menu) -{ - GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (combo_box); - GList *children, *child; - GtkWidget *item, *submenu; - GtkWidget *cell_view; - gboolean sensitive; - - if (!priv->model) - return; - - children = gtk_menu_shell_get_items (GTK_MENU_SHELL (menu)); - - for (child = children; child; child = child->next) - { - item = GTK_WIDGET (child->data); - cell_view = gtk_bin_get_child (GTK_BIN (item)); - - if (!GTK_IS_CELL_VIEW (cell_view)) - continue; - - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)); - if (submenu != NULL) - { - gtk_widget_set_sensitive (item, TRUE); - update_menu_sensitivity (combo_box, submenu); - } - else - { - sensitive = cell_layout_is_sensitive (GTK_CELL_LAYOUT (cell_view)); - gtk_widget_set_sensitive (item, sensitive); - } - } - - g_list_free (children); -} - -static void -gtk_combo_box_menu_popup (GtkComboBox *combo_box) +gtk_combo_box_menu_popup (GtkComboBox *combo_box) { GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (combo_box); +#if 0 gint active_item; GtkWidget *active; int width, min_width, nat_width; +#endif - update_menu_sensitivity (combo_box, priv->popup_widget); + gtk_tree_popover_open_submenu (GTK_TREE_POPOVER (priv->popup_widget), "main"); + gtk_popover_popup (GTK_POPOVER (priv->popup_widget)); +#if 0 active_item = -1; if (gtk_tree_row_reference_valid (priv->active_row)) { @@ -1276,7 +1230,7 @@ gtk_combo_box_menu_popup (GtkComboBox *combo_box) } /* FIXME handle nested menus better */ - gtk_menu_set_active (GTK_MENU (priv->popup_widget), active_item); + //gtk_tree_popover_set_active (GTK_TREE_POPOVER (priv->popup_widget), active_item); width = gtk_widget_get_width (GTK_WIDGET (combo_box)); gtk_widget_set_size_request (priv->popup_widget, -1, -1); @@ -1294,8 +1248,6 @@ gtk_combo_box_menu_popup (GtkComboBox *combo_box) gtk_menu_update_scroll_offset, NULL); - g_object_set (priv->popup_widget, "menu-type-hint", GDK_SURFACE_TYPE_HINT_COMBO, NULL); - if (priv->cell_view == NULL) { g_object_set (priv->popup_widget, @@ -1374,14 +1326,7 @@ gtk_combo_box_menu_popup (GtkComboBox *combo_box) GDK_GRAVITY_NORTH_WEST, NULL); } - - /* Re-get the active item before selecting it, as a popped-up handler – like - * that of FileChooserButton in folder mode – can refilter the model, making - * the original active item pointer invalid. This seems ugly and makes some - * of the above code pointless in such cases, so hopefully we can FIXME. */ - active = gtk_menu_get_active (GTK_MENU (priv->popup_widget)); - if (active && gtk_widget_get_visible (active)) - gtk_menu_shell_select_item (GTK_MENU_SHELL (priv->popup_widget), active); +#endif } /** @@ -1470,7 +1415,7 @@ gtk_combo_box_popdown (GtkComboBox *combo_box) g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); - gtk_menu_popdown (GTK_MENU (priv->popup_widget)); + gtk_popover_popdown (GTK_POPOVER (priv->popup_widget)); } static void @@ -2045,7 +1990,7 @@ gtk_combo_box_set_active_internal (GtkComboBox *combo_box, if (!path) { - gtk_menu_set_active (GTK_MENU (priv->popup_widget), -1); + gtk_tree_popover_set_active (GTK_TREE_POPOVER (priv->popup_widget), -1); if (priv->cell_view) gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), NULL); @@ -2062,13 +2007,11 @@ gtk_combo_box_set_active_internal (GtkComboBox *combo_box, priv->active_row = gtk_tree_row_reference_new (priv->model, path); - /* FIXME handle nested menus better */ - gtk_menu_set_active (GTK_MENU (priv->popup_widget), - gtk_tree_path_get_indices (path)[0]); + gtk_tree_popover_set_active (GTK_TREE_POPOVER (priv->popup_widget), + gtk_tree_path_get_indices (path)[0]); if (priv->cell_view) - gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), - path); + gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), path); } g_signal_emit (combo_box, combo_box_signals[CHANGED], 0); @@ -2176,8 +2119,7 @@ gtk_combo_box_set_model (GtkComboBox *combo_box, G_CALLBACK (gtk_combo_box_model_row_changed), combo_box); - _gtk_tree_menu_set_model (GTK_TREE_MENU (priv->popup_widget), - priv->model); + gtk_tree_popover_set_model (GTK_TREE_POPOVER (priv->popup_widget), priv->model); if (priv->cell_view) gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), @@ -2499,8 +2441,7 @@ gtk_combo_box_dispose (GObject* object) g_signal_handlers_disconnect_by_func (priv->popup_widget, gtk_combo_box_menu_hide, combo_box); - gtk_menu_detach (GTK_MENU (priv->popup_widget)); - priv->popup_widget = NULL; + g_clear_pointer (&priv->popup_widget, gtk_widget_unparent); } gtk_combo_box_unset_model (combo_box); @@ -2681,9 +2622,9 @@ gtk_combo_box_set_row_separator_func (GtkComboBox *combo_box, priv->row_separator_data = data; priv->row_separator_destroy = destroy; - /* Make the TreeMenu rebuild itself using the new separator func */ - _gtk_tree_menu_set_model (GTK_TREE_MENU (priv->popup_widget), NULL); - _gtk_tree_menu_set_model (GTK_TREE_MENU (priv->popup_widget), priv->model); + gtk_tree_popover_set_row_separator_func (GTK_TREE_POPOVER (priv->popup_widget), + (GtkTreeViewRowSeparatorFunc)gtk_combo_box_row_separator_func, + combo_box, NULL); gtk_widget_queue_draw (GTK_WIDGET (combo_box)); } diff --git a/gtk/gtktreemenu.c b/gtk/gtktreemenu.c deleted file mode 100644 index c216ce0f10..0000000000 --- a/gtk/gtktreemenu.c +++ /dev/null @@ -1,1367 +0,0 @@ -/* gtktreemenu.c - * - * Copyright (C) 2010 Openismus GmbH - * - * Authors: - * Tristan Van Berkom - * - * Based on some GtkComboBox menu code by Kristian Rietveld - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library. If not, see . - */ - -/* - * SECTION:gtktreemenu - * @Short_Description: A GtkMenu automatically created from a #GtkTreeModel - * @Title: GtkTreeMenu - * - * The #GtkTreeMenu is used to display a drop-down menu allowing selection - * of every row in the model and is used by the #GtkComboBox for its drop-down - * menu. - */ - -#include "config.h" -#include "gtkintl.h" -#include "gtktreemenu.h" -#include "gtkmarshalers.h" -#include "gtkmenuitem.h" -#include "gtkseparatormenuitem.h" -#include "gtkcellareabox.h" -#include "gtkcellareacontext.h" -#include "gtkcelllayout.h" -#include "gtkcellview.h" -#include "gtkmenushellprivate.h" -#include "gtkprivate.h" - -/* GObjectClass */ -static void gtk_tree_menu_constructed (GObject *object); -static void gtk_tree_menu_dispose (GObject *object); -static void gtk_tree_menu_finalize (GObject *object); -static void gtk_tree_menu_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void gtk_tree_menu_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); - -/* GtkWidgetClass */ -static void gtk_tree_menu_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline); - -/* GtkCellLayoutIface */ -static void gtk_tree_menu_cell_layout_init (GtkCellLayoutIface *iface); -static GtkCellArea *gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout); - - -/* TreeModel/DrawingArea callbacks and building menus/submenus */ -static inline void rebuild_menu (GtkTreeMenu *menu); -static void gtk_tree_menu_populate (GtkTreeMenu *menu); -static GtkWidget *gtk_tree_menu_create_item (GtkTreeMenu *menu, - GtkTreeIter *iter, - gboolean header_item); -static void gtk_tree_menu_create_submenu (GtkTreeMenu *menu, - GtkWidget *item, - GtkTreePath *path); -static void gtk_tree_menu_set_area (GtkTreeMenu *menu, - GtkCellArea *area); -static GtkWidget *gtk_tree_menu_get_path_item (GtkTreeMenu *menu, - GtkTreePath *path); -static gboolean gtk_tree_menu_path_in_menu (GtkTreeMenu *menu, - GtkTreePath *path, - gboolean *header_item); -static void row_inserted_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - GtkTreeMenu *menu); -static void row_deleted_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeMenu *menu); -static void row_reordered_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gint *new_order, - GtkTreeMenu *menu); -static void row_changed_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - GtkTreeMenu *menu); -static void context_size_changed_cb (GtkCellAreaContext *context, - GParamSpec *pspec, - GtkWidget *menu); -static void area_apply_attributes_cb (GtkCellArea *area, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gboolean is_expander, - gboolean is_expanded, - GtkTreeMenu *menu); -static void item_activated_cb (GtkMenuItem *item, - GtkTreeMenu *menu); -static void submenu_activated_cb (GtkTreeMenu *submenu, - const gchar *path, - GtkTreeMenu *menu); -static void gtk_tree_menu_set_model_internal (GtkTreeMenu *menu, - GtkTreeModel *model); - - - -struct _GtkTreeMenuPrivate -{ - /* TreeModel and parent for this menu */ - GtkTreeModel *model; - GtkTreeRowReference *root; - - /* CellArea and context for this menu */ - GtkCellArea *area; - GtkCellAreaContext *context; - - /* Signals */ - gulong size_changed_id; - gulong apply_attributes_id; - gulong row_inserted_id; - gulong row_deleted_id; - gulong row_reordered_id; - gulong row_changed_id; - - /* Flags */ - guint32 menu_with_header : 1; - - /* Row separators */ - GtkTreeViewRowSeparatorFunc row_separator_func; - gpointer row_separator_data; - GDestroyNotify row_separator_destroy; -}; - -enum { - PROP_0, - PROP_MODEL, - PROP_ROOT, - PROP_CELL_AREA -}; - -enum { - SIGNAL_MENU_ACTIVATE, - N_SIGNALS -}; - -static guint tree_menu_signals[N_SIGNALS] = { 0 }; -static GQuark tree_menu_path_quark = 0; - -G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, _gtk_tree_menu, GTK_TYPE_MENU, - G_ADD_PRIVATE (GtkTreeMenu) - G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, - gtk_tree_menu_cell_layout_init)); - -static void -_gtk_tree_menu_init (GtkTreeMenu *menu) -{ - menu->priv = _gtk_tree_menu_get_instance_private (menu); - - gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE); -} - -static void -_gtk_tree_menu_class_init (GtkTreeMenuClass *class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (class); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); - - tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path"); - - object_class->constructed = gtk_tree_menu_constructed; - object_class->dispose = gtk_tree_menu_dispose; - object_class->finalize = gtk_tree_menu_finalize; - object_class->set_property = gtk_tree_menu_set_property; - object_class->get_property = gtk_tree_menu_get_property; - - widget_class->measure = gtk_tree_menu_measure; - - /* - * GtkTreeMenu::menu-activate: - * @menu: a #GtkTreeMenu - * @path: the #GtkTreePath string for the item which was activated - * @user_data: the user data - * - * This signal is emitted to notify that a menu item in the #GtkTreeMenu - * was activated and provides the path string from the #GtkTreeModel - * to specify which row was selected. - */ - tree_menu_signals[SIGNAL_MENU_ACTIVATE] = - g_signal_new (I_("menu-activate"), - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - 0, /* No class closure here */ - NULL, NULL, - NULL, - G_TYPE_NONE, 1, G_TYPE_STRING); - - /* - * GtkTreeMenu:model: - * - * The #GtkTreeModel from which the menu is constructed. - */ - g_object_class_install_property (object_class, - PROP_MODEL, - g_param_spec_object ("model", - P_("TreeMenu model"), - P_("The model for the tree menu"), - GTK_TYPE_TREE_MODEL, - GTK_PARAM_READWRITE)); - - /* - * GtkTreeMenu:root: - * - * The #GtkTreePath that is the root for this menu, or %NULL. - * - * The #GtkTreeMenu recursively creates submenus for #GtkTreeModel - * rows that have children and the "root" for each menu is provided - * by the parent menu. - * - * If you dont provide a root for the #GtkTreeMenu then the whole - * model will be added to the menu. Specifying a root allows you - * to build a menu for a given #GtkTreePath and its children. - */ - g_object_class_install_property (object_class, - PROP_ROOT, - g_param_spec_boxed ("root", - P_("TreeMenu root row"), - P_("The TreeMenu will display children of the " - "specified root"), - GTK_TYPE_TREE_PATH, - GTK_PARAM_READWRITE)); - - /* - * GtkTreeMenu:cell-area: - * - * The #GtkCellArea used to render cells in the menu items. - * - * You can provide a different cell area at object construction - * time, otherwise the #GtkTreeMenu will use a #GtkCellAreaBox. - */ - g_object_class_install_property (object_class, - PROP_CELL_AREA, - g_param_spec_object ("cell-area", - P_("Cell Area"), - P_("The GtkCellArea used to layout cells"), - GTK_TYPE_CELL_AREA, - GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); -} - -/**************************************************************** - * GObjectClass * - ****************************************************************/ -static void -gtk_tree_menu_constructed (GObject *object) -{ - GtkTreeMenu *menu = GTK_TREE_MENU (object); - GtkTreeMenuPrivate *priv = menu->priv; - - G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->constructed (object); - - if (!priv->area) - { - GtkCellArea *area = gtk_cell_area_box_new (); - - gtk_tree_menu_set_area (menu, area); - } - - priv->context = gtk_cell_area_create_context (priv->area); - - priv->size_changed_id = - g_signal_connect (priv->context, "notify", - G_CALLBACK (context_size_changed_cb), menu); -} - -static void -gtk_tree_menu_dispose (GObject *object) -{ - GtkTreeMenu *menu; - GtkTreeMenuPrivate *priv; - - menu = GTK_TREE_MENU (object); - priv = menu->priv; - - _gtk_tree_menu_set_model (menu, NULL); - gtk_tree_menu_set_area (menu, NULL); - - if (priv->context) - { - /* Disconnect signals */ - g_signal_handler_disconnect (priv->context, priv->size_changed_id); - - g_object_unref (priv->context); - priv->context = NULL; - priv->size_changed_id = 0; - } - - G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->dispose (object); -} - -static void -gtk_tree_menu_finalize (GObject *object) -{ - GtkTreeMenu *menu; - GtkTreeMenuPrivate *priv; - - menu = GTK_TREE_MENU (object); - priv = menu->priv; - - _gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL); - - if (priv->root) - gtk_tree_row_reference_free (priv->root); - - G_OBJECT_CLASS (_gtk_tree_menu_parent_class)->finalize (object); -} - -static void -gtk_tree_menu_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GtkTreeMenu *menu = GTK_TREE_MENU (object); - - switch (prop_id) - { - case PROP_MODEL: - _gtk_tree_menu_set_model (menu, g_value_get_object (value)); - break; - - case PROP_ROOT: - _gtk_tree_menu_set_root (menu, g_value_get_boxed (value)); - break; - - case PROP_CELL_AREA: - /* Construct-only, can only be assigned once */ - gtk_tree_menu_set_area (menu, (GtkCellArea *)g_value_get_object (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -gtk_tree_menu_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GtkTreeMenu *menu = GTK_TREE_MENU (object); - GtkTreeMenuPrivate *priv = menu->priv; - - switch (prop_id) - { - case PROP_MODEL: - g_value_set_object (value, priv->model); - break; - - case PROP_ROOT: - g_value_set_boxed (value, priv->root); - break; - - case PROP_CELL_AREA: - g_value_set_object (value, priv->area); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -/**************************************************************** - * GtkWidgetClass * - ****************************************************************/ - -/* We tell all the menu items to reserve space for the submenu - * indicator if there is at least one submenu, this way we ensure - * that every internal cell area gets allocated the - * same width (and requested height for the same appropriate width). - */ -static void -sync_reserve_submenu_size (GtkTreeMenu *menu) -{ - GList *children, *l; - gboolean has_submenu = FALSE; - - children = gtk_container_get_children (GTK_CONTAINER (menu)); - for (l = children; l; l = l->next) - { - GtkMenuItem *item = l->data; - - if (gtk_menu_item_get_submenu (item) != NULL) - { - has_submenu = TRUE; - break; - } - } - - for (l = children; l; l = l->next) - { - GtkMenuItem *item = l->data; - - gtk_menu_item_set_reserve_indicator (item, has_submenu); - } - - g_list_free (children); -} - - -static void -gtk_tree_menu_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline) -{ - GtkTreeMenu *menu = GTK_TREE_MENU (widget); - GtkTreeMenuPrivate *priv = menu->priv; - - /* We leave the requesting work up to the cellviews which operate in the same - * context, reserving space for the submenu indicator if any of the items have - * submenus ensures that every cellview will receive the same allocated width. - * - * Since GtkMenu does hieght-for-width correctly, we know that the width of - * every cell will be requested before the height-for-widths are requested. - */ - g_signal_handler_block (priv->context, priv->size_changed_id); - - sync_reserve_submenu_size (menu); - - GTK_WIDGET_CLASS (_gtk_tree_menu_parent_class)->measure (widget, - orientation, - for_size, - minimum, natural, - minimum_baseline, natural_baseline); - - g_signal_handler_unblock (priv->context, priv->size_changed_id); -} - - -/**************************************************************** - * GtkCellLayoutIface * - ****************************************************************/ -static void -gtk_tree_menu_cell_layout_init (GtkCellLayoutIface *iface) -{ - iface->get_area = gtk_tree_menu_cell_layout_get_area; -} - -static GtkCellArea * -gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout) -{ - GtkTreeMenu *menu = GTK_TREE_MENU (layout); - GtkTreeMenuPrivate *priv = menu->priv; - - return priv->area; -} - - -/**************************************************************** - * TreeModel callbacks/populating menus * - ****************************************************************/ -static GtkWidget * -gtk_tree_menu_get_path_item (GtkTreeMenu *menu, - GtkTreePath *search) -{ - GtkWidget *item = NULL; - GList *children, *l; - - children = gtk_container_get_children (GTK_CONTAINER (menu)); - - for (l = children; item == NULL && l != NULL; l = l->next) - { - GtkWidget *child = l->data; - GtkTreePath *path = NULL; - - if (GTK_IS_SEPARATOR_MENU_ITEM (child)) - { - GtkTreeRowReference *row = - g_object_get_qdata (G_OBJECT (child), tree_menu_path_quark); - - if (row) - { - path = gtk_tree_row_reference_get_path (row); - - if (!path) - /* Return any first child where its row-reference became invalid, - * this is because row-references get null paths before we recieve - * the "row-deleted" signal. - */ - item = child; - } - } - else - { - GtkWidget *view = gtk_bin_get_child (GTK_BIN (child)); - - /* It's always a cellview */ - if (GTK_IS_CELL_VIEW (view)) - path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view)); - - if (!path) - /* Return any first child where its row-reference became invalid, - * this is because row-references get null paths before we recieve - * the "row-deleted" signal. - */ - item = child; - } - - if (path) - { - if (gtk_tree_path_compare (search, path) == 0) - item = child; - - gtk_tree_path_free (path); - } - } - - g_list_free (children); - - return item; -} - -static gboolean -gtk_tree_menu_path_in_menu (GtkTreeMenu *menu, - GtkTreePath *path, - gboolean *header_item) -{ - GtkTreeMenuPrivate *priv = menu->priv; - gboolean in_menu = FALSE; - gboolean is_header = FALSE; - - /* Check if the is in root of the model */ - if (gtk_tree_path_get_depth (path) == 1 && !priv->root) - in_menu = TRUE; - /* If we are a submenu, compare the parent path */ - else if (priv->root) - { - GtkTreePath *root_path = gtk_tree_row_reference_get_path (priv->root); - GtkTreePath *search_path = gtk_tree_path_copy (path); - - if (root_path) - { - if (priv->menu_with_header && - gtk_tree_path_compare (root_path, search_path) == 0) - { - in_menu = TRUE; - is_header = TRUE; - } - else if (gtk_tree_path_get_depth (search_path) > 1) - { - gtk_tree_path_up (search_path); - - if (gtk_tree_path_compare (root_path, search_path) == 0) - in_menu = TRUE; - } - } - gtk_tree_path_free (root_path); - gtk_tree_path_free (search_path); - } - - if (header_item) - *header_item = is_header; - - return in_menu; -} - -static GtkWidget * -gtk_tree_menu_path_needs_submenu (GtkTreeMenu *menu, - GtkTreePath *search) -{ - GtkWidget *item = NULL; - GList *children, *l; - GtkTreePath *parent_path; - - if (gtk_tree_path_get_depth (search) <= 1) - return NULL; - - parent_path = gtk_tree_path_copy (search); - gtk_tree_path_up (parent_path); - - children = gtk_container_get_children (GTK_CONTAINER (menu)); - - for (l = children; item == NULL && l != NULL; l = l->next) - { - GtkWidget *child = l->data; - GtkTreePath *path = NULL; - - /* Separators dont get submenus, if it already has a submenu then let - * the submenu handle inserted rows */ - if (!GTK_IS_SEPARATOR_MENU_ITEM (child) && - !gtk_menu_item_get_submenu (GTK_MENU_ITEM (child))) - { - GtkWidget *view = gtk_bin_get_child (GTK_BIN (child)); - - /* It's always a cellview */ - if (GTK_IS_CELL_VIEW (view)) - path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view)); - } - - if (path) - { - if (gtk_tree_path_compare (parent_path, path) == 0) - item = child; - - gtk_tree_path_free (path); - } - } - - g_list_free (children); - gtk_tree_path_free (parent_path); - - return item; -} - -static GtkWidget * -find_empty_submenu (GtkTreeMenu *menu) -{ - GtkTreeMenuPrivate *priv = menu->priv; - GList *children, *l; - GtkWidget *submenu = NULL; - - children = gtk_container_get_children (GTK_CONTAINER (menu)); - - for (l = children; submenu == NULL && l != NULL; l = l->next) - { - GtkWidget *child = l->data; - GtkTreePath *path = NULL; - GtkTreeIter iter; - - /* Separators dont get submenus, if it already has a submenu then let - * the submenu handle inserted rows */ - if (!GTK_IS_SEPARATOR_MENU_ITEM (child)) - { - GtkWidget *view = gtk_bin_get_child (GTK_BIN (child)); - - /* It's always a cellview */ - if (GTK_IS_CELL_VIEW (view)) - path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view)); - } - - if (path) - { - if (gtk_tree_model_get_iter (priv->model, &iter, path) && - !gtk_tree_model_iter_has_child (priv->model, &iter)) - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (child)); - - gtk_tree_path_free (path); - } - } - - g_list_free (children); - - return submenu; -} - -static void -row_inserted_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - GtkTreeMenu *menu) -{ - GtkTreeMenuPrivate *priv = menu->priv; - gint *indices, index, depth; - GtkWidget *item; - - /* If the iter should be in this menu then go ahead and insert it */ - if (gtk_tree_menu_path_in_menu (menu, path, NULL)) - { - /* Get the index of the path for this depth */ - indices = gtk_tree_path_get_indices (path); - depth = gtk_tree_path_get_depth (path); - index = indices[depth -1]; - - /* Menus with a header include a menuitem for its root node - * and a separator menu item */ - if (priv->menu_with_header) - index += 2; - - item = gtk_tree_menu_create_item (menu, iter, FALSE); - gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index); - - /* Resize everything */ - gtk_cell_area_context_reset (menu->priv->context); - } - else - { - /* Create submenus for iters if we need to */ - item = gtk_tree_menu_path_needs_submenu (menu, path); - if (item) - { - GtkTreePath *item_path = gtk_tree_path_copy (path); - - gtk_tree_path_up (item_path); - gtk_tree_menu_create_submenu (menu, item, item_path); - gtk_tree_path_free (item_path); - } - } -} - -static void -row_deleted_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeMenu *menu) -{ - GtkWidget *item; - - /* If it's the header item we leave it to the parent menu - * to remove us from its menu - */ - item = gtk_tree_menu_get_path_item (menu, path); - - if (item) - { - /* Get rid of the deleted item */ - gtk_widget_destroy (item); - - /* Resize everything */ - gtk_cell_area_context_reset (menu->priv->context); - } - else - { - /* It's up to the parent menu to destroy a child menu that becomes empty - * since the topmost menu belongs to the user and is allowed to have no contents */ - GtkWidget *submenu = find_empty_submenu (menu); - if (submenu) - gtk_widget_destroy (submenu); - } -} - -static void -row_reordered_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gint *new_order, - GtkTreeMenu *menu) -{ - GtkTreeMenuPrivate *priv = menu->priv; - gboolean this_menu = FALSE; - - if (gtk_tree_path_get_depth (path) == 0 && !priv->root) - this_menu = TRUE; - else if (priv->root) - { - GtkTreePath *root_path = - gtk_tree_row_reference_get_path (priv->root); - - if (gtk_tree_path_compare (root_path, path) == 0) - this_menu = TRUE; - - gtk_tree_path_free (root_path); - } - - if (this_menu) - rebuild_menu (menu); -} - -static gint -menu_item_position (GtkTreeMenu *menu, - GtkWidget *item) -{ - GList *children; - gint position; - - children = gtk_container_get_children (GTK_CONTAINER (menu)); - position = g_list_index (children, item); - g_list_free (children); - - return position; -} - -static void -row_changed_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - GtkTreeMenu *menu) -{ - GtkTreeMenuPrivate *priv = menu->priv; - gboolean is_separator = FALSE; - GtkWidget *item; - GList *children; - - item = gtk_tree_menu_get_path_item (menu, path); - - if (priv->root) - { - GtkTreePath *root_path = - gtk_tree_row_reference_get_path (priv->root); - - if (root_path && gtk_tree_path_compare (root_path, path) == 0) - { - if (item) - { - /* Destroy the header item and then the following separator */ - gtk_widget_destroy (item); - children = gtk_menu_shell_get_items (GTK_MENU_SHELL (menu)); - gtk_widget_destroy (children->data); - g_list_free (children); - - priv->menu_with_header = FALSE; - } - - gtk_tree_path_free (root_path); - } - } - - if (item) - { - if (priv->row_separator_func) - is_separator = priv->row_separator_func (model, iter, priv->row_separator_data); - - if (is_separator != GTK_IS_SEPARATOR_MENU_ITEM (item)) - { - gint position = menu_item_position (menu, item); - - gtk_widget_destroy (item); - item = gtk_tree_menu_create_item (menu, iter, FALSE); - gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position); - } - } -} - -static void -context_size_changed_cb (GtkCellAreaContext *context, - GParamSpec *pspec, - GtkWidget *menu) -{ - if (!strcmp (pspec->name, "minimum-width") || - !strcmp (pspec->name, "natural-width") || - !strcmp (pspec->name, "minimum-height") || - !strcmp (pspec->name, "natural-height")) - gtk_widget_queue_resize (menu); -} - -static gboolean -area_is_sensitive (GtkCellArea *area) -{ - GList *cells, *list; - gboolean sensitive = FALSE; - - cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area)); - - for (list = cells; list; list = list->next) - { - g_object_get (list->data, "sensitive", &sensitive, NULL); - - if (sensitive) - break; - } - g_list_free (cells); - - return sensitive; -} - -static void -area_apply_attributes_cb (GtkCellArea *area, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gboolean is_expander, - gboolean is_expanded, - GtkTreeMenu *menu) -{ - /* If the menu for this iter has a submenu */ - GtkTreeMenuPrivate *priv = menu->priv; - GtkTreePath *path; - GtkWidget *item; - gboolean is_header; - gboolean sensitive; - GList *children; - - path = gtk_tree_model_get_path (tree_model, iter); - - if (gtk_tree_menu_path_in_menu (menu, path, &is_header)) - { - item = gtk_tree_menu_get_path_item (menu, path); - - /* If there is no submenu, go ahead and update item sensitivity, - * items with submenus are always sensitive */ - if (item && !gtk_menu_item_get_submenu (GTK_MENU_ITEM (item))) - { - sensitive = area_is_sensitive (priv->area); - - gtk_widget_set_sensitive (item, sensitive); - - if (is_header) - { - /* For header items we need to set the sensitivity - * of the following separator item - */ - children = gtk_menu_shell_get_items (GTK_MENU_SHELL (menu)); - if (children && children->next) - { - GtkWidget *separator = children->next->data; - - gtk_widget_set_sensitive (separator, sensitive); - } - g_list_free (children); - } - } - } - - gtk_tree_path_free (path); -} - -static void -gtk_tree_menu_set_area (GtkTreeMenu *menu, - GtkCellArea *area) -{ - GtkTreeMenuPrivate *priv = menu->priv; - - if (priv->area) - { - g_signal_handler_disconnect (priv->area, - priv->apply_attributes_id); - priv->apply_attributes_id = 0; - - g_object_unref (priv->area); - } - - priv->area = area; - - if (priv->area) - { - g_object_ref_sink (priv->area); - - priv->apply_attributes_id = - g_signal_connect (priv->area, "apply-attributes", - G_CALLBACK (area_apply_attributes_cb), menu); - } -} - -static void -gtk_tree_menu_create_submenu (GtkTreeMenu *menu, - GtkWidget *item, - GtkTreePath *path) -{ - GtkTreeMenuPrivate *priv = menu->priv; - GtkWidget *view; - GtkWidget *submenu; - - view = gtk_bin_get_child (GTK_BIN (item)); - gtk_cell_view_set_draw_sensitive (GTK_CELL_VIEW (view), TRUE); - - submenu = _gtk_tree_menu_new_with_area (priv->area); - - _gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu), - priv->row_separator_func, - priv->row_separator_data, - priv->row_separator_destroy); - - gtk_tree_menu_set_model_internal (GTK_TREE_MENU (submenu), priv->model); - _gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); - - g_signal_connect (submenu, "menu-activate", - G_CALLBACK (submenu_activated_cb), menu); -} - -static GtkWidget * -gtk_tree_menu_create_item (GtkTreeMenu *menu, - GtkTreeIter *iter, - gboolean header_item) -{ - GtkTreeMenuPrivate *priv = menu->priv; - GtkWidget *item, *view; - GtkTreePath *path; - gboolean is_separator = FALSE; - - path = gtk_tree_model_get_path (priv->model, iter); - - if (priv->row_separator_func) - is_separator = - priv->row_separator_func (priv->model, iter, - priv->row_separator_data); - - if (is_separator) - { - item = gtk_separator_menu_item_new (); - - g_object_set_qdata_full (G_OBJECT (item), - tree_menu_path_quark, - gtk_tree_row_reference_new (priv->model, path), - (GDestroyNotify)gtk_tree_row_reference_free); - } - else - { - view = gtk_cell_view_new_with_context (priv->area, priv->context); - item = gtk_menu_item_new (); - - gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model); - gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path); - - gtk_container_add (GTK_CONTAINER (item), view); - - g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu); - - /* Add a GtkTreeMenu submenu to render the children of this row */ - if (header_item == FALSE && - gtk_tree_model_iter_has_child (priv->model, iter)) - gtk_tree_menu_create_submenu (menu, item, path); - } - - gtk_tree_path_free (path); - - return item; -} - -static inline void -rebuild_menu (GtkTreeMenu *menu) -{ - GtkTreeMenuPrivate *priv = menu->priv; - - /* Destroy all the menu items */ - gtk_container_foreach (GTK_CONTAINER (menu), - (GtkCallback) gtk_widget_destroy, NULL); - - /* Populate */ - if (priv->model) - gtk_tree_menu_populate (menu); -} - - -static void -gtk_tree_menu_populate (GtkTreeMenu *menu) -{ - GtkTreeMenuPrivate *priv = menu->priv; - GtkTreePath *path = NULL; - GtkTreeIter parent; - GtkTreeIter iter; - gboolean valid = FALSE; - GtkWidget *menu_item; - - if (!priv->model) - return; - - if (priv->root) - path = gtk_tree_row_reference_get_path (priv->root); - - if (path) - { - if (gtk_tree_model_get_iter (priv->model, &parent, path)) - valid = gtk_tree_model_iter_children (priv->model, &iter, &parent); - - gtk_tree_path_free (path); - } - else - { - valid = gtk_tree_model_iter_children (priv->model, &iter, NULL); - } - - /* Create a menu item for every row at the current depth, add a GtkTreeMenu - * submenu for iters/items that have children */ - while (valid) - { - menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE); - - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); - - valid = gtk_tree_model_iter_next (priv->model, &iter); - } -} - -static void -item_activated_cb (GtkMenuItem *item, - GtkTreeMenu *menu) -{ - GtkCellView *view; - GtkTreePath *path; - gchar *path_str; - - /* Only activate leafs, not parents */ - if (!gtk_menu_item_get_submenu (item)) - { - view = GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item))); - path = gtk_cell_view_get_displayed_row (view); - path_str = gtk_tree_path_to_string (path); - - g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str); - - g_free (path_str); - gtk_tree_path_free (path); - } -} - -static void -submenu_activated_cb (GtkTreeMenu *submenu, - const gchar *path, - GtkTreeMenu *menu) -{ - g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path); -} - -/* Sets the model without rebuilding the menu, prevents - * infinite recursion while building submenus (we wait - * until the root is set and then build the menu) */ -static void -gtk_tree_menu_set_model_internal (GtkTreeMenu *menu, - GtkTreeModel *model) -{ - GtkTreeMenuPrivate *priv; - - priv = menu->priv; - - if (priv->model != model) - { - if (priv->model) - { - /* Disconnect signals */ - g_signal_handler_disconnect (priv->model, - priv->row_inserted_id); - g_signal_handler_disconnect (priv->model, - priv->row_deleted_id); - g_signal_handler_disconnect (priv->model, - priv->row_reordered_id); - g_signal_handler_disconnect (priv->model, - priv->row_changed_id); - priv->row_inserted_id = 0; - priv->row_deleted_id = 0; - priv->row_reordered_id = 0; - priv->row_changed_id = 0; - - g_object_unref (priv->model); - } - - priv->model = model; - - if (priv->model) - { - g_object_ref (priv->model); - - /* Connect signals */ - priv->row_inserted_id = g_signal_connect (priv->model, "row-inserted", - G_CALLBACK (row_inserted_cb), menu); - priv->row_deleted_id = g_signal_connect (priv->model, "row-deleted", - G_CALLBACK (row_deleted_cb), menu); - priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered", - G_CALLBACK (row_reordered_cb), menu); - priv->row_changed_id = g_signal_connect (priv->model, "row-changed", - G_CALLBACK (row_changed_cb), menu); - } - } -} - -/**************************************************************** - * API * - ****************************************************************/ - -/** - * _gtk_tree_menu_new: - * - * Creates a new #GtkTreeMenu. - * - * Returns: A newly created #GtkTreeMenu with no model or root. - */ -GtkWidget * -_gtk_tree_menu_new (void) -{ - return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, NULL); -} - -/* - * _gtk_tree_menu_new_with_area: - * @area: the #GtkCellArea to use to render cells in the menu - * - * Creates a new #GtkTreeMenu using @area to render its cells. - * - * Returns: A newly created #GtkTreeMenu with no model or root. - */ -GtkWidget * -_gtk_tree_menu_new_with_area (GtkCellArea *area) -{ - return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, - "cell-area", area, - NULL); -} - -/* - * _gtk_tree_menu_new_full: - * @area: (allow-none): the #GtkCellArea to use to render cells in the menu, or %NULL. - * @model: (allow-none): the #GtkTreeModel to build the menu hierarchy from, or %NULL. - * @root: (allow-none): the #GtkTreePath indicating the root row for this menu, or %NULL. - * - * Creates a new #GtkTreeMenu hierarchy from the provided @model and @root using @area to render its cells. - * - * Returns: A newly created #GtkTreeMenu. - */ -GtkWidget * -_gtk_tree_menu_new_full (GtkCellArea *area, - GtkTreeModel *model, - GtkTreePath *root) -{ - return (GtkWidget *)g_object_new (GTK_TYPE_TREE_MENU, - "cell-area", area, - "model", model, - "root", root, - NULL); -} - -/* - * _gtk_tree_menu_set_model: - * @menu: a #GtkTreeMenu - * @model: (allow-none): the #GtkTreeModel to build the menu hierarchy from, or %NULL. - * - * Sets @model to be used to build the menu hierarhcy. - */ -void -_gtk_tree_menu_set_model (GtkTreeMenu *menu, - GtkTreeModel *model) -{ - g_return_if_fail (GTK_IS_TREE_MENU (menu)); - g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model)); - - gtk_tree_menu_set_model_internal (menu, model); - - rebuild_menu (menu); -} - -/* - * _gtk_tree_menu_get_model: - * @menu: a #GtkTreeMenu - * - * Gets the @model currently used for the menu hierarhcy. - * - * Returns: (transfer none): the #GtkTreeModel which is used - * for @menu’s hierarchy. - */ -GtkTreeModel * -_gtk_tree_menu_get_model (GtkTreeMenu *menu) -{ - GtkTreeMenuPrivate *priv; - - g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL); - - priv = menu->priv; - - return priv->model; -} - -/* - * _gtk_tree_menu_set_root: - * @menu: a #GtkTreeMenu - * @path: (allow-none): the #GtkTreePath which is the root of @menu, or %NULL. - * - * Sets the root of a @menu’s hierarchy to be @path. @menu must already - * have a model set and @path must point to a valid path inside the model. - */ -void -_gtk_tree_menu_set_root (GtkTreeMenu *menu, - GtkTreePath *path) -{ - GtkTreeMenuPrivate *priv; - - g_return_if_fail (GTK_IS_TREE_MENU (menu)); - g_return_if_fail (menu->priv->model != NULL || path == NULL); - - priv = menu->priv; - - if (priv->root) - gtk_tree_row_reference_free (priv->root); - - if (path) - priv->root = gtk_tree_row_reference_new (priv->model, path); - else - priv->root = NULL; - - rebuild_menu (menu); -} - -/* - * _gtk_tree_menu_get_root: - * @menu: a #GtkTreeMenu - * - * Gets the @root path for @menu’s hierarchy, or returns %NULL if @menu - * has no model or is building a hierarchy for the entire model. * - * - * Returns: (transfer full) (allow-none): A newly created #GtkTreePath - * pointing to the root of @menu which must be freed with gtk_tree_path_free(). - */ -GtkTreePath * -_gtk_tree_menu_get_root (GtkTreeMenu *menu) -{ - GtkTreeMenuPrivate *priv; - - g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL); - - priv = menu->priv; - - if (priv->root) - return gtk_tree_row_reference_get_path (priv->root); - - return NULL; -} - -/* - * _gtk_tree_menu_get_row_separator_func: - * @menu: a #GtkTreeMenu - * - * Gets the current #GtkTreeViewRowSeparatorFunc separator function. - * - * Returns: the current row separator function. - */ -GtkTreeViewRowSeparatorFunc -_gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu) -{ - GtkTreeMenuPrivate *priv; - - g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL); - - priv = menu->priv; - - return priv->row_separator_func; -} - -/* - * _gtk_tree_menu_set_row_separator_func: - * @menu: a #GtkTreeMenu - * @func: (allow-none): a #GtkTreeViewRowSeparatorFunc, or %NULL to unset the separator function. - * @data: (allow-none): user data to pass to @func, or %NULL - * @destroy: (allow-none): destroy notifier for @data, or %NULL - * - * Sets the row separator function, which is used to determine - * whether a row should be drawn as a separator. If the row separator - * function is %NULL, no separators are drawn. This is the default value. - */ -void -_gtk_tree_menu_set_row_separator_func (GtkTreeMenu *menu, - GtkTreeViewRowSeparatorFunc func, - gpointer data, - GDestroyNotify destroy) -{ - GtkTreeMenuPrivate *priv; - - g_return_if_fail (GTK_IS_TREE_MENU (menu)); - - priv = menu->priv; - - if (priv->row_separator_destroy) - priv->row_separator_destroy (priv->row_separator_data); - - priv->row_separator_func = func; - priv->row_separator_data = data; - priv->row_separator_destroy = destroy; - - rebuild_menu (menu); -} diff --git a/gtk/gtktreemenu.h b/gtk/gtktreemenu.h deleted file mode 100644 index 360a28dceb..0000000000 --- a/gtk/gtktreemenu.h +++ /dev/null @@ -1,100 +0,0 @@ -/* gtktreemenu.h - * - * Copyright (C) 2010 Openismus GmbH - * - * Authors: - * Tristan Van Berkom - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library. If not, see . - */ - -#ifndef __GTK_TREE_MENU_H__ -#define __GTK_TREE_MENU_H__ - -#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) -#error "Only can be included directly." -#endif - -#include -#include -#include -#include - -G_BEGIN_DECLS - -#define GTK_TYPE_TREE_MENU (_gtk_tree_menu_get_type ()) -#define GTK_TREE_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TREE_MENU, GtkTreeMenu)) -#define GTK_TREE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TREE_MENU, GtkTreeMenuClass)) -#define GTK_IS_TREE_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TREE_MENU)) -#define GTK_IS_TREE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TREE_MENU)) -#define GTK_TREE_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TREE_MENU, GtkTreeMenuClass)) - -typedef struct _GtkTreeMenu GtkTreeMenu; -typedef struct _GtkTreeMenuClass GtkTreeMenuClass; -typedef struct _GtkTreeMenuPrivate GtkTreeMenuPrivate; - -struct _GtkTreeMenu -{ - GtkMenu parent_instance; - - /*< private >*/ - GtkTreeMenuPrivate *priv; -}; - -struct _GtkTreeMenuClass -{ - GtkMenuClass parent_class; - - /*< private >*/ - /* Padding for future expansion */ - void (*_gtk_reserved1) (void); - void (*_gtk_reserved2) (void); - void (*_gtk_reserved3) (void); - void (*_gtk_reserved4) (void); - void (*_gtk_reserved5) (void); - void (*_gtk_reserved6) (void); -}; - -GType _gtk_tree_menu_get_type (void) G_GNUC_CONST; - -GtkWidget *_gtk_tree_menu_new (void); -GtkWidget *_gtk_tree_menu_new_with_area (GtkCellArea *area); -GtkWidget *_gtk_tree_menu_new_full (GtkCellArea *area, - GtkTreeModel *model, - GtkTreePath *root); -void _gtk_tree_menu_set_model (GtkTreeMenu *menu, - GtkTreeModel *model); -GtkTreeModel *_gtk_tree_menu_get_model (GtkTreeMenu *menu); -void _gtk_tree_menu_set_root (GtkTreeMenu *menu, - GtkTreePath *path); -GtkTreePath *_gtk_tree_menu_get_root (GtkTreeMenu *menu); -gint _gtk_tree_menu_get_wrap_width (GtkTreeMenu *menu); -void _gtk_tree_menu_set_wrap_width (GtkTreeMenu *menu, - gint width); -gint _gtk_tree_menu_get_row_span_column (GtkTreeMenu *menu); -void _gtk_tree_menu_set_row_span_column (GtkTreeMenu *menu, - gint row_span); -gint _gtk_tree_menu_get_column_span_column (GtkTreeMenu *menu); -void _gtk_tree_menu_set_column_span_column (GtkTreeMenu *menu, - gint column_span); - -GtkTreeViewRowSeparatorFunc _gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu); -void _gtk_tree_menu_set_row_separator_func (GtkTreeMenu *menu, - GtkTreeViewRowSeparatorFunc func, - gpointer data, - GDestroyNotify destroy); - -G_END_DECLS - -#endif /* __GTK_TREE_MENU_H__ */ diff --git a/gtk/gtktreepopover.c b/gtk/gtktreepopover.c new file mode 100644 index 0000000000..a9803a7d87 --- /dev/null +++ b/gtk/gtktreepopover.c @@ -0,0 +1,868 @@ +/* + * Copyright © 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include "gtktreepopoverprivate.h" + +#include "gtktreemodel.h" +#include "gtkcellarea.h" +#include "gtkcelllayout.h" +#include "gtkcellview.h" +#include "gtkbin.h" +#include "gtkintl.h" +#include "gtkprivate.h" +#include "gtkgizmoprivate.h" +#include "gtkiconprivate.h" + +// TODO +// positioning + sizing + +struct _GtkTreePopover +{ + GtkPopover parent_instance; + + GtkTreeModel *model; + + GtkCellArea *area; + GtkCellAreaContext *context; + + gulong size_changed_id; + gulong row_inserted_id; + gulong row_deleted_id; + gulong row_changed_id; + gulong row_reordered_id; + gulong apply_attributes_id; + + GtkTreeViewRowSeparatorFunc row_separator_func; + gpointer row_separator_data; + GDestroyNotify row_separator_destroy; + + GtkWidget *active_item; +}; + +enum { + PROP_0, + PROP_MODEL, + PROP_CELL_AREA, + + NUM_PROPERTIES +}; + +enum { + MENU_ACTIVATE, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS]; + +static void gtk_tree_popover_cell_layout_init (GtkCellLayoutIface *iface); +static void gtk_tree_popover_set_area (GtkTreePopover *popover, + GtkCellArea *area); +static void rebuild_menu (GtkTreePopover *popover); +static void context_size_changed_cb (GtkCellAreaContext *context, + GParamSpec *pspec, + GtkWidget *popover); +static GtkWidget * gtk_tree_popover_create_item (GtkTreePopover *popover, + GtkTreePath *path, + GtkTreeIter *iter, + gboolean header_item); +static GtkWidget * gtk_tree_popover_get_path_item (GtkTreePopover *popover, + GtkTreePath *search); +static void gtk_tree_popover_set_active_item (GtkTreePopover *popover, + GtkWidget *item); + +G_DEFINE_TYPE_WITH_CODE (GtkTreePopover, gtk_tree_popover, GTK_TYPE_POPOVER, + G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, + gtk_tree_popover_cell_layout_init)); + +static void +gtk_tree_popover_constructed (GObject *object) +{ + GtkTreePopover *popover = GTK_TREE_POPOVER (object); + + G_OBJECT_CLASS (gtk_tree_popover_parent_class)->constructed (object); + + if (!popover->area) + { + GtkCellArea *area = gtk_cell_area_box_new (); + gtk_tree_popover_set_area (popover, area); + } + + popover->context = gtk_cell_area_create_context (popover->area); + + popover->size_changed_id = g_signal_connect (popover->context, "notify", + G_CALLBACK (context_size_changed_cb), popover); +} + +static void +gtk_tree_popover_dispose (GObject *object) +{ + GtkTreePopover *popover = GTK_TREE_POPOVER (object); + + gtk_tree_popover_set_model (popover, NULL); + gtk_tree_popover_set_area (popover, NULL); + + if (popover->context) + { + g_signal_handler_disconnect (popover->context, popover->size_changed_id); + popover->size_changed_id = 0; + + g_clear_object (&popover->context); + } + + G_OBJECT_CLASS (gtk_tree_popover_parent_class)->dispose (object); +} + +static void +gtk_tree_popover_finalize (GObject *object) +{ + GtkTreePopover *popover = GTK_TREE_POPOVER (object); + + if (popover->row_separator_destroy) + popover->row_separator_destroy (popover->row_separator_data); + + G_OBJECT_CLASS (gtk_tree_popover_parent_class)->finalize (object); +} + +static void +gtk_tree_popover_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkTreePopover *popover = GTK_TREE_POPOVER (object); + + switch (prop_id) + { + case PROP_MODEL: + gtk_tree_popover_set_model (popover, g_value_get_object (value)); + break; + + case PROP_CELL_AREA: + gtk_tree_popover_set_area (popover, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tree_popover_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkTreePopover *popover = GTK_TREE_POPOVER (object); + + switch (prop_id) + { + case PROP_MODEL: + g_value_set_object (value, popover->model); + break; + + case PROP_CELL_AREA: + g_value_set_object (value, popover->area); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tree_popover_class_init (GtkTreePopoverClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->constructed = gtk_tree_popover_constructed; + object_class->dispose = gtk_tree_popover_dispose; + object_class->finalize = gtk_tree_popover_finalize; + object_class->set_property = gtk_tree_popover_set_property; + object_class->get_property = gtk_tree_popover_get_property; + + g_object_class_install_property (object_class, + PROP_MODEL, + g_param_spec_object ("model", + P_("model"), + P_("The model for the popover"), + GTK_TYPE_TREE_MODEL, + GTK_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_CELL_AREA, + g_param_spec_object ("cell-area", + P_("Cell Area"), + P_("The GtkCellArea used to layout cells"), + GTK_TYPE_CELL_AREA, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + signals[MENU_ACTIVATE] = + g_signal_new (I_("menu-activate"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 1, G_TYPE_STRING); +} + +static void +gtk_tree_popover_add_submenu (GtkTreePopover *popover, + GtkWidget *submenu, + const char *name) +{ + GtkWidget *stack = gtk_bin_get_child (GTK_BIN (popover)); + gtk_stack_add_named (GTK_STACK (stack), submenu, name); +} + +static GtkWidget * +gtk_tree_popover_get_submenu (GtkTreePopover *popover, + const char *name) +{ + GtkWidget *stack = gtk_bin_get_child (GTK_BIN (popover)); + + return gtk_stack_get_child_by_name (GTK_STACK (stack), name); +} + +void +gtk_tree_popover_open_submenu (GtkTreePopover *popover, + const char *name) +{ + GtkWidget *stack = gtk_bin_get_child (GTK_BIN (popover)); + gtk_stack_set_visible_child_name (GTK_STACK (stack), name); +} + +static void +gtk_tree_popover_init (GtkTreePopover *popover) +{ + GtkWidget *stack; + GtkStyleContext *style_context; + + stack = gtk_stack_new (); + gtk_stack_set_vhomogeneous (GTK_STACK (stack), FALSE); + gtk_stack_set_transition_type (GTK_STACK (stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT); + gtk_stack_set_interpolate_size (GTK_STACK (stack), TRUE); + gtk_container_add (GTK_CONTAINER (popover), stack); + + style_context = gtk_widget_get_style_context (GTK_WIDGET (popover)); + gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_MENU); +} + +static GtkCellArea * +gtk_tree_popover_cell_layout_get_area (GtkCellLayout *layout) +{ + return GTK_TREE_POPOVER (layout)->area; +} + +static void +gtk_tree_popover_cell_layout_init (GtkCellLayoutIface *iface) +{ + iface->get_area = gtk_tree_popover_cell_layout_get_area; +} + +static void +insert_at_position (GtkBox *box, + GtkWidget *child, + int position) +{ + GtkWidget *sibling = NULL; + + if (position > 0) + { + int i; + + sibling = gtk_widget_get_first_child (GTK_WIDGET (box)); + for (i = 1; i < position; i++) + sibling = gtk_widget_get_next_sibling (sibling); + } + + gtk_box_insert_child_after (box, child, sibling); +} + +static GtkWidget * +ensure_submenu (GtkTreePopover *popover, + GtkTreePath *path) +{ + GtkWidget *box; + char *name; + + if (path) + name = gtk_tree_path_to_string (path); + else + name = NULL; + + box = gtk_tree_popover_get_submenu (popover, name ? name : "main"); + if (!box) + { + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_tree_popover_add_submenu (popover, box, name ? name : "main"); + if (path) + { + GtkTreeIter iter; + GtkWidget *item; + gtk_tree_model_get_iter (popover->model, &iter, path); + item = gtk_tree_popover_create_item (popover, path, &iter, TRUE); + gtk_container_add (GTK_CONTAINER (box), item); + gtk_container_add (GTK_CONTAINER (box), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL)); + } + + } + + g_free (name); + + return box; +} + +static void +row_inserted_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GtkTreePopover *popover) +{ + gint *indices, depth, index; + GtkWidget *item; + GtkWidget *box; + + indices = gtk_tree_path_get_indices (path); + depth = gtk_tree_path_get_depth (path); + index = indices[depth - 1]; + + item = gtk_tree_popover_create_item (popover, path, iter, FALSE); + if (depth == 1) + { + box = ensure_submenu (popover, NULL); + insert_at_position (GTK_BOX (box), item, index); + } + else + { + GtkTreePath *ppath; + + ppath = gtk_tree_path_copy (path); + gtk_tree_path_up (ppath); + + box = ensure_submenu (popover, ppath); + insert_at_position (GTK_BOX (box), item, index + 2); + + gtk_tree_path_free (ppath); + } + + gtk_cell_area_context_reset (popover->context); +} + +static void +row_deleted_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreePopover *popover) +{ + GtkWidget *item; + + item = gtk_tree_popover_get_path_item (popover, path); + + if (item) + { + gtk_widget_destroy (item); + gtk_cell_area_context_reset (popover->context); + } +} + +static void +row_changed_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GtkTreePopover *popover) +{ + gboolean is_separator = FALSE; + GtkWidget *item; + gint *indices, depth, index; + + item = gtk_tree_popover_get_path_item (popover, path); + + if (!item) + return; + + indices = gtk_tree_path_get_indices (path); + depth = gtk_tree_path_get_depth (path); + index = indices[depth - 1]; + + if (popover->row_separator_func) + is_separator = popover->row_separator_func (model, iter, popover->row_separator_data); + + if (is_separator != GTK_IS_SEPARATOR (item)) + { + GtkWidget *box= gtk_widget_get_parent (item); + + gtk_widget_destroy (item); + + item = gtk_tree_popover_create_item (popover, path, iter, FALSE); + + if (depth == 1) + insert_at_position (GTK_BOX (box), item, index); + else + insert_at_position (GTK_BOX (box), item, index + 2); + } +} + +static void +row_reordered_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gint *new_order, + GtkTreePopover *popover) +{ + rebuild_menu (popover); +} + +static void +context_size_changed_cb (GtkCellAreaContext *context, + GParamSpec *pspec, + GtkWidget *popover) +{ + if (!strcmp (pspec->name, "minimum-width") || + !strcmp (pspec->name, "natural-width") || + !strcmp (pspec->name, "minimum-height") || + !strcmp (pspec->name, "natural-height")) + gtk_widget_queue_resize (popover); +} + +static gboolean +area_is_sensitive (GtkCellArea *area) +{ + GList *cells, *list; + gboolean sensitive = FALSE; + + cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area)); + + for (list = cells; list; list = list->next) + { + g_object_get (list->data, "sensitive", &sensitive, NULL); + + if (sensitive) + break; + } + g_list_free (cells); + + return sensitive; +} + +static GtkWidget * +gtk_tree_popover_get_path_item (GtkTreePopover *popover, + GtkTreePath *search) +{ + GtkWidget *stack = gtk_bin_get_child (GTK_BIN (popover)); + GtkWidget *item = NULL; + GList *children, *l; + + children = gtk_container_get_children (GTK_CONTAINER (stack)); + + for (l = children; !item && l; l = l->next) + { + GtkWidget *child; + + for (child = gtk_widget_get_first_child (GTK_WIDGET (l->data)); + !item && child; + child = gtk_widget_get_next_sibling (child)) + { + GtkTreePath *path = NULL; + + if (GTK_IS_SEPARATOR (child)) + { + GtkTreeRowReference *row = g_object_get_data (G_OBJECT (child), "gtk-tree-path"); + + if (row) + { + path = gtk_tree_row_reference_get_path (row); + if (!path) + item = child; + } + } + else + { + GtkWidget *view = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "view")); + + path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view)); + + if (!path) + item = child; + } + + if (path) + { + if (gtk_tree_path_compare (search, path) == 0) + item = child; + gtk_tree_path_free (path); + } + } + } + + g_list_free (children); + + return item; +} + +static void +area_apply_attributes_cb (GtkCellArea *area, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded, + GtkTreePopover *popover) +{ + GtkTreePath*path; + GtkWidget *item; + gboolean sensitive; + GtkTreeIter dummy; + gboolean has_submenu = FALSE; + + if (gtk_tree_model_iter_children (popover->model, &dummy, iter)) + has_submenu = TRUE; + + path = gtk_tree_model_get_path (tree_model, iter); + item = gtk_tree_popover_get_path_item (popover, path); + + if (item) + { + sensitive = area_is_sensitive (popover->area); + gtk_widget_set_sensitive (item, sensitive || has_submenu); + } + + gtk_tree_path_free (path); +} + +static void +gtk_tree_popover_set_area (GtkTreePopover *popover, + GtkCellArea *area) +{ + if (popover->area) + { + g_signal_handler_disconnect (popover->area, popover->apply_attributes_id); + popover->apply_attributes_id = 0; + g_clear_object (&popover->area); + } + + popover->area = area; + + if (popover->area) + { + g_object_ref_sink (popover->area); + popover->apply_attributes_id = g_signal_connect (popover->area, "apply-attributes", + G_CALLBACK (area_apply_attributes_cb), popover); + } +} + +static void +item_activated_cb (GtkGesture *gesture, + guint n_press, + double x, + double y, + GtkTreePopover *popover) +{ + GtkWidget *item; + GtkCellView *view; + GtkTreePath *path; + gchar *path_str; + gboolean is_header = FALSE; + gboolean has_submenu = FALSE; + + item = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); + is_header = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "is-header")); + + view = GTK_CELL_VIEW (g_object_get_data (G_OBJECT (item), "view")); + + path = gtk_cell_view_get_displayed_row (view); + + if (is_header) + { + gtk_tree_path_up (path); + } + else + { + GtkTreeIter iter; + GtkTreeIter dummy; + + gtk_tree_model_get_iter (popover->model, &iter, path); + if (gtk_tree_model_iter_children (popover->model, &dummy, &iter)) + has_submenu = TRUE; + } + + path_str = gtk_tree_path_to_string (path); + + if (is_header || has_submenu) + { + gtk_tree_popover_open_submenu (popover, path_str ? path_str : "main"); + } + else + { + g_signal_emit (popover, signals[MENU_ACTIVATE], 0, path_str); + gtk_popover_popdown (GTK_POPOVER (popover)); + } + + g_free (path_str); + gtk_tree_path_free (path); +} + +static void +enter_cb (GtkEventController *controller, + double x, + double y, + GdkCrossingMode mode, + GdkNotifyType type, + GtkTreePopover *popover) +{ + GtkWidget *item; + item = gtk_event_controller_get_widget (controller); + + if (gtk_event_controller_motion_contains_pointer (GTK_EVENT_CONTROLLER_MOTION (controller))) + { + gtk_tree_popover_set_active_item (popover, item); + } +} + +static void +leave_cb (GtkEventController *controller, + GdkCrossingMode mode, + GdkNotifyType type, + GtkTreePopover *popover) +{ +} + +static GtkWidget * +gtk_tree_popover_create_item (GtkTreePopover *popover, + GtkTreePath *path, + GtkTreeIter *iter, + gboolean header_item) +{ + GtkWidget *item, *view; + gboolean is_separator = FALSE; + + if (popover->row_separator_func) + is_separator = popover->row_separator_func (popover->model, iter, popover->row_separator_data); + + if (is_separator) + { + item = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + g_object_set_data_full (G_OBJECT (item), "gtk-tree-path", + gtk_tree_row_reference_new (popover->model, path), + (GDestroyNotify)gtk_tree_row_reference_free); + } + else + { + GtkEventController *controller; + GtkTreeIter dummy; + gboolean has_submenu = FALSE; + GtkWidget *indicator; + + if (!header_item && + gtk_tree_model_iter_children (popover->model, &dummy, iter)) + has_submenu = TRUE; + + view = gtk_cell_view_new_with_context (popover->area, popover->context); + gtk_cell_view_set_model (GTK_CELL_VIEW (view), popover->model); + gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path); + gtk_widget_set_hexpand (view, TRUE); + + item = gtk_gizmo_new ("modelbutton", NULL, NULL, NULL, NULL); + gtk_widget_set_layout_manager (item, gtk_box_layout_new (GTK_ORIENTATION_HORIZONTAL)); + gtk_style_context_add_class (gtk_widget_get_style_context (item), "flat"); + + if (header_item) + { + indicator = gtk_icon_new ("arrow"); + gtk_icon_set_image (GTK_ICON (indicator), GTK_CSS_IMAGE_BUILTIN_ARROW_LEFT); + gtk_style_context_add_class (gtk_widget_get_style_context (indicator), "left"); + gtk_widget_set_parent (indicator, item); + } + + gtk_widget_set_parent (view, item); + + indicator = gtk_icon_new (has_submenu ? "arrow" : "none"); + gtk_icon_set_image (GTK_ICON (indicator), has_submenu ? GTK_CSS_IMAGE_BUILTIN_ARROW_RIGHT + : GTK_CSS_IMAGE_BUILTIN_NONE); + gtk_style_context_add_class (gtk_widget_get_style_context (indicator), "right"); + gtk_widget_set_parent (indicator, item); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + g_signal_connect (controller, "pressed", G_CALLBACK (item_activated_cb), popover); + gtk_widget_add_controller (item, GTK_EVENT_CONTROLLER (controller)); + + controller = gtk_event_controller_motion_new (); + g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), popover); + g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), popover); + gtk_widget_add_controller (item, controller); + + g_object_set_data (G_OBJECT (item), "is-header", GINT_TO_POINTER (header_item)); + g_object_set_data (G_OBJECT (item), "view", view); + } + + return item; +} + +static void +populate (GtkTreePopover *popover, + GtkTreeIter *parent) +{ + GtkTreeIter iter; + gboolean valid = FALSE; + + if (!popover->model) + return; + + valid = gtk_tree_model_iter_children (popover->model, &iter, parent); + + while (valid) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (popover->model, &iter); + row_inserted_cb (popover->model, path, &iter, popover); + + populate (popover, &iter); + + valid = gtk_tree_model_iter_next (popover->model, &iter); + gtk_tree_path_free (path); + } +} + +static void +gtk_tree_popover_populate (GtkTreePopover *popover) +{ + populate (popover, NULL); +} + +static void +rebuild_menu (GtkTreePopover *popover) +{ + GtkWidget *stack = gtk_bin_get_child (GTK_BIN (popover)); + gtk_container_foreach (GTK_CONTAINER (stack), (GtkCallback) gtk_widget_destroy, NULL); + + if (popover->model) + gtk_tree_popover_populate (popover); +} + +void +gtk_tree_popover_set_model (GtkTreePopover *popover, + GtkTreeModel *model) +{ + if (popover->model == model) + return; + + if (popover->model) + { + g_signal_handler_disconnect (popover->model, popover->row_inserted_id); + g_signal_handler_disconnect (popover->model, popover->row_deleted_id); + g_signal_handler_disconnect (popover->model, popover->row_changed_id); + g_signal_handler_disconnect (popover->model, popover->row_reordered_id); + popover->row_inserted_id = 0; + popover->row_deleted_id = 0; + popover->row_changed_id = 0; + popover->row_reordered_id = 0; + + g_object_unref (popover->model); + } + + popover->model = model; + + if (popover->model) + { + g_object_ref (popover->model); + + popover->row_inserted_id = g_signal_connect (popover->model, "row-inserted", + G_CALLBACK (row_inserted_cb), popover); + popover->row_deleted_id = g_signal_connect (popover->model, "row-deleted", + G_CALLBACK (row_deleted_cb), popover); + popover->row_changed_id = g_signal_connect (popover->model, "row-changed", + G_CALLBACK (row_changed_cb), popover); + popover->row_reordered_id = g_signal_connect (popover->model, "rows-reordered", + G_CALLBACK (row_reordered_cb), popover); + } + + rebuild_menu (popover); +} + +void +gtk_tree_popover_set_row_separator_func (GtkTreePopover *popover, + GtkTreeViewRowSeparatorFunc func, + gpointer data, + GDestroyNotify destroy) +{ + if (popover->row_separator_destroy) + popover->row_separator_destroy (popover->row_separator_data); + + popover->row_separator_func = func; + popover->row_separator_data = data; + popover->row_separator_destroy = destroy; + + rebuild_menu (popover); +} + +static void +gtk_tree_popover_set_active_item (GtkTreePopover *popover, + GtkWidget *item) +{ + if (popover->active_item == item) + return; + + if (popover->active_item) + { + gtk_widget_unset_state_flags (popover->active_item, GTK_STATE_FLAG_SELECTED); + g_object_remove_weak_pointer (G_OBJECT (popover->active_item), (gpointer *)&popover->active_item); + } + + popover->active_item = item; + + if (popover->active_item) + { + g_object_add_weak_pointer (G_OBJECT (popover->active_item), (gpointer *)&popover->active_item); + gtk_widget_set_state_flags (popover->active_item, GTK_STATE_FLAG_SELECTED, FALSE); + } +} + +void +gtk_tree_popover_set_active (GtkTreePopover *popover, + int item) +{ + GtkWidget *box; + GtkWidget *child; + int pos; + + if (item == -1) + { + gtk_tree_popover_set_active_item (popover, NULL); + return; + } + + box = gtk_tree_popover_get_submenu (popover, "main"); + if (!box) + return; + + for (child = gtk_widget_get_first_child (box), pos = 0; + child; + child = gtk_widget_get_next_sibling (child), pos++) + { + if (pos == item) + { + gtk_tree_popover_set_active_item (popover, child); + break; + } + } +} + diff --git a/gtk/gtktreepopoverprivate.h b/gtk/gtktreepopoverprivate.h new file mode 100644 index 0000000000..a43bfcb15f --- /dev/null +++ b/gtk/gtktreepopoverprivate.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Author: Matthias Clasen + */ + +#ifndef __GTK_TREE_POPOPVER_PRIVATE_H__ +#define __GTK_TREE_POPOVER_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TREE_POPOVER (gtk_tree_popover_get_type ()) +G_DECLARE_FINAL_TYPE (GtkTreePopover, gtk_tree_popover, GTK, TREE_POPOVER, GtkPopover) + +void gtk_tree_popover_set_model (GtkTreePopover *popover, + GtkTreeModel *model); +void gtk_tree_popover_set_row_separator_func (GtkTreePopover *popover, + GtkTreeViewRowSeparatorFunc func, + gpointer data, + GDestroyNotify destroy); +void gtk_tree_popover_set_active (GtkTreePopover *popover, + int item); +void gtk_tree_popover_open_submenu (GtkTreePopover *popover, + const char *name); + +G_END_DECLS + +#endif /* __GTK_TREE_POPOVER_PRIVATE_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 827c7b2708..00a3068864 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -393,10 +393,10 @@ gtk_public_sources = files([ 'gtktooltipwindow.c', 'gtktreednd.c', 'gtktreelistmodel.c', - 'gtktreemenu.c', 'gtktreemodel.c', 'gtktreemodelfilter.c', 'gtktreemodelsort.c', + 'gtktreepopover.c', 'gtktreeselection.c', 'gtktreesortable.c', 'gtktreestore.c', diff --git a/gtk/ui/gtkcombobox.ui b/gtk/ui/gtkcombobox.ui index 41b5754b75..24cafedb21 100644 --- a/gtk/ui/gtkcombobox.ui +++ b/gtk/ui/gtkcombobox.ui @@ -24,8 +24,10 @@ - + area + GtkComboBox + 0