mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-26 13:41:07 +00:00
a86923de94
The group role that we've used before has some implications of semantic grouping, whereas these containers are mainly about layout, so use the generic role instead. This should not affect the translation to AT-SPI at all. The affected containers are: box, grid, centerbox, scrolledwindow, viewport, windowhandle, aspectframe. The role of GtkTreeExpander has been changed to button instead, since it acts as a button.
1018 lines
32 KiB
C
1018 lines
32 KiB
C
/*
|
|
* Copyright © 2019 Benjamin Otte
|
|
*
|
|
* 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.1 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
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Benjamin Otte <otte@gnome.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtktreeexpander.h"
|
|
|
|
#include "gtkaccessible.h"
|
|
#include "gtkboxlayout.h"
|
|
#include "gtkbuiltiniconprivate.h"
|
|
#include "gtkdropcontrollermotion.h"
|
|
#include "gtkenums.h"
|
|
#include "gtkgestureclick.h"
|
|
#include <glib/gi18n-lib.h>
|
|
#include "gtktreelistmodel.h"
|
|
#include "gtkprivate.h"
|
|
|
|
/**
|
|
* GtkTreeExpander:
|
|
*
|
|
* `GtkTreeExpander` is a widget that provides an expander for a list.
|
|
*
|
|
* It is typically placed as a bottommost child into a `GtkListView`
|
|
* to allow users to expand and collapse children in a list with a
|
|
* [class@Gtk.TreeListModel]. `GtkTreeExpander` provides the common UI
|
|
* elements, gestures and keybindings for this purpose.
|
|
*
|
|
* On top of this, the "listitem.expand", "listitem.collapse" and
|
|
* "listitem.toggle-expand" actions are provided to allow adding custom
|
|
* UI for managing expanded state.
|
|
*
|
|
* It is important to mention that you want to set the
|
|
* [property@Gtk.ListItem:focusable] property to FALSE when using this
|
|
* widget, as you want the keyboard focus to be in the treexpander, and not
|
|
* inside the list to make use of the keybindings.
|
|
*
|
|
* The `GtkTreeListModel` must be set to not be passthrough. Then it
|
|
* will provide [class@Gtk.TreeListRow] items which can be set via
|
|
* [method@Gtk.TreeExpander.set_list_row] on the expander.
|
|
* The expander will then watch that row item automatically.
|
|
* [method@Gtk.TreeExpander.set_child] sets the widget that displays
|
|
* the actual row contents.
|
|
*
|
|
* `GtkTreeExpander` can be modified with properties such as
|
|
* [property@Gtk.TreeExpander:indent-for-icon],
|
|
* [property@Gtk.TreeExpander:indent-for-depth], and
|
|
* [property@Gtk.TreeExpander:hide-expander] to achieve a different appearance.
|
|
* This can even be done to influence individual rows, for example by binding
|
|
* the [property@Gtk.TreeExpander:hide-expander] property to the item count of
|
|
* the model of the treelistrow, to hide the expander for rows without children,
|
|
* even if the row is expandable.
|
|
*
|
|
* ## CSS nodes
|
|
*
|
|
* ```
|
|
* treeexpander
|
|
* ├── [indent]*
|
|
* ├── [expander]
|
|
* ╰── <child>
|
|
* ```
|
|
*
|
|
* `GtkTreeExpander` has zero or one CSS nodes with the name "expander" that
|
|
* should display the expander icon. The node will be `:checked` when it
|
|
* is expanded. If the node is not expandable, an "indent" node will be
|
|
* displayed instead.
|
|
*
|
|
* For every level of depth, another "indent" node is prepended.
|
|
*
|
|
* ## Accessibility
|
|
*
|
|
* Until GTK 4.10, `GtkTreeExpander` used the `GTK_ACCESSIBLE_ROLE_GROUP` role.
|
|
*
|
|
* Since GTK 4.12, `GtkTreeExpander` uses the `GTK_ACCESSIBLE_ROLE_BUTTON` role.
|
|
* Toggling it will change the `GTK_ACCESSIBLE_STATE_EXPANDED` state.
|
|
*/
|
|
|
|
struct _GtkTreeExpander
|
|
{
|
|
GtkWidget parent_instance;
|
|
|
|
GtkTreeListRow *list_row;
|
|
GtkWidget *child;
|
|
|
|
GtkWidget *expander_icon;
|
|
guint notify_handler;
|
|
|
|
gboolean hide_expander;
|
|
gboolean indent_for_depth;
|
|
gboolean indent_for_icon;
|
|
|
|
guint expand_timer;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CHILD,
|
|
PROP_HIDE_EXPANDER,
|
|
PROP_INDENT_FOR_DEPTH,
|
|
PROP_INDENT_FOR_ICON,
|
|
PROP_ITEM,
|
|
PROP_LIST_ROW,
|
|
|
|
N_PROPS
|
|
};
|
|
|
|
G_DEFINE_TYPE (GtkTreeExpander, gtk_tree_expander, GTK_TYPE_WIDGET)
|
|
|
|
static GParamSpec *properties[N_PROPS] = { NULL, };
|
|
|
|
|
|
static void
|
|
gtk_tree_expander_click_gesture_released (GtkGestureClick *gesture,
|
|
int n_press,
|
|
double x,
|
|
double y,
|
|
gpointer unused)
|
|
{
|
|
GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
|
|
|
|
if (gtk_widget_contains (widget, x, y))
|
|
{
|
|
gtk_widget_activate_action (widget, "listitem.toggle-expand", NULL);
|
|
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_update_for_list_row (GtkTreeExpander *self)
|
|
{
|
|
if (self->list_row == NULL)
|
|
{
|
|
GtkWidget *child;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
|
|
child != self->child;
|
|
child = gtk_widget_get_first_child (GTK_WIDGET (self)))
|
|
{
|
|
gtk_widget_unparent (child);
|
|
}
|
|
self->expander_icon = NULL;
|
|
|
|
gtk_accessible_reset_state (GTK_ACCESSIBLE (self), GTK_ACCESSIBLE_STATE_EXPANDED);
|
|
}
|
|
else
|
|
{
|
|
GtkWidget *child;
|
|
guint i, depth;
|
|
|
|
depth = self->indent_for_depth ? gtk_tree_list_row_get_depth (self->list_row) : 0;
|
|
if (gtk_tree_list_row_is_expandable (self->list_row) && !self->hide_expander)
|
|
{
|
|
if (self->expander_icon == NULL)
|
|
{
|
|
GtkGesture *gesture;
|
|
|
|
self->expander_icon =
|
|
g_object_new (GTK_TYPE_BUILTIN_ICON,
|
|
"css-name", "expander",
|
|
"accessible-role", GTK_ACCESSIBLE_ROLE_NONE,
|
|
NULL);
|
|
|
|
gesture = gtk_gesture_click_new ();
|
|
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
|
|
GTK_PHASE_BUBBLE);
|
|
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
|
|
FALSE);
|
|
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
|
|
GDK_BUTTON_PRIMARY);
|
|
g_signal_connect (gesture, "released",
|
|
G_CALLBACK (gtk_tree_expander_click_gesture_released), NULL);
|
|
gtk_widget_add_controller (self->expander_icon, GTK_EVENT_CONTROLLER (gesture));
|
|
|
|
gtk_widget_insert_before (self->expander_icon,
|
|
GTK_WIDGET (self),
|
|
self->child);
|
|
}
|
|
|
|
if (gtk_tree_list_row_get_expanded (self->list_row))
|
|
{
|
|
gtk_widget_set_state_flags (self->expander_icon, GTK_STATE_FLAG_CHECKED, FALSE);
|
|
gtk_accessible_update_state (GTK_ACCESSIBLE (self),
|
|
GTK_ACCESSIBLE_STATE_EXPANDED, TRUE,
|
|
-1);
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_unset_state_flags (self->expander_icon, GTK_STATE_FLAG_CHECKED);
|
|
gtk_accessible_update_state (GTK_ACCESSIBLE (self),
|
|
GTK_ACCESSIBLE_STATE_EXPANDED, FALSE,
|
|
-1);
|
|
}
|
|
|
|
child = gtk_widget_get_prev_sibling (self->expander_icon);
|
|
}
|
|
else
|
|
{
|
|
g_clear_pointer (&self->expander_icon, gtk_widget_unparent);
|
|
|
|
if (self->indent_for_icon)
|
|
depth++;
|
|
|
|
if (self->child)
|
|
child = gtk_widget_get_prev_sibling (self->child);
|
|
else
|
|
child = gtk_widget_get_last_child (GTK_WIDGET (self));
|
|
}
|
|
|
|
for (i = 0; i < depth; i++)
|
|
{
|
|
if (child)
|
|
child = gtk_widget_get_prev_sibling (child);
|
|
else
|
|
{
|
|
GtkWidget *indent =
|
|
g_object_new (GTK_TYPE_BUILTIN_ICON,
|
|
"css-name", "indent",
|
|
"accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION,
|
|
NULL);
|
|
|
|
gtk_widget_insert_after (indent, GTK_WIDGET (self), NULL);
|
|
}
|
|
}
|
|
|
|
/* The level property is >= 1 */
|
|
gtk_accessible_update_property (GTK_ACCESSIBLE (self),
|
|
GTK_ACCESSIBLE_PROPERTY_LEVEL, depth + 1,
|
|
-1);
|
|
|
|
while (child)
|
|
{
|
|
GtkWidget *prev = gtk_widget_get_prev_sibling (child);
|
|
gtk_widget_unparent (child);
|
|
child = prev;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_list_row_notify_cb (GtkTreeListRow *list_row,
|
|
GParamSpec *pspec,
|
|
GtkTreeExpander *self)
|
|
{
|
|
if (pspec->name == g_intern_static_string ("expanded"))
|
|
{
|
|
if (self->expander_icon)
|
|
{
|
|
if (gtk_tree_list_row_get_expanded (list_row))
|
|
{
|
|
gtk_widget_set_state_flags (self->expander_icon, GTK_STATE_FLAG_CHECKED, FALSE);
|
|
gtk_accessible_update_state (GTK_ACCESSIBLE (self->expander_icon),
|
|
GTK_ACCESSIBLE_STATE_EXPANDED, TRUE,
|
|
-1);
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_unset_state_flags (self->expander_icon, GTK_STATE_FLAG_CHECKED);
|
|
gtk_accessible_update_state (GTK_ACCESSIBLE (self->expander_icon),
|
|
GTK_ACCESSIBLE_STATE_EXPANDED, FALSE,
|
|
-1);
|
|
}
|
|
}
|
|
}
|
|
else if (pspec->name == g_intern_static_string ("item"))
|
|
{
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
|
|
}
|
|
else
|
|
{
|
|
/* can this happen other than when destroying the row? */
|
|
gtk_tree_expander_update_for_list_row (self);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gtk_tree_expander_focus (GtkWidget *widget,
|
|
GtkDirectionType direction)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
|
|
|
|
/* The idea of this function is the following:
|
|
* 1. If any child can take focus, do not ever attempt
|
|
* to take focus.
|
|
* 2. Otherwise, if this item is selectable or activatable,
|
|
* allow focusing this widget.
|
|
*
|
|
* This makes sure every item in a list is focusable for
|
|
* activation and selection handling, but no useless widgets
|
|
* get focused and moving focus is as fast as possible.
|
|
*/
|
|
if (self->child)
|
|
{
|
|
if (gtk_widget_get_focus_child (widget))
|
|
return FALSE;
|
|
if (gtk_widget_child_focus (self->child, direction))
|
|
return TRUE;
|
|
}
|
|
|
|
if (gtk_widget_is_focus (widget))
|
|
return FALSE;
|
|
|
|
if (!gtk_widget_get_focusable (widget))
|
|
return FALSE;
|
|
|
|
gtk_widget_grab_focus (widget);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gtk_tree_expander_grab_focus (GtkWidget *widget)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
|
|
|
|
if (self->child && gtk_widget_grab_focus (self->child))
|
|
return TRUE;
|
|
|
|
return GTK_WIDGET_CLASS (gtk_tree_expander_parent_class)->grab_focus (widget);
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_clear_list_row (GtkTreeExpander *self)
|
|
{
|
|
if (self->list_row == NULL)
|
|
return;
|
|
|
|
g_signal_handler_disconnect (self->list_row, self->notify_handler);
|
|
self->notify_handler = 0;
|
|
g_clear_object (&self->list_row);
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_dispose (GObject *object)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (object);
|
|
|
|
if (self->expand_timer)
|
|
{
|
|
g_source_remove (self->expand_timer);
|
|
self->expand_timer = 0;
|
|
}
|
|
|
|
gtk_tree_expander_clear_list_row (self);
|
|
gtk_tree_expander_update_for_list_row (self);
|
|
|
|
g_clear_pointer (&self->child, gtk_widget_unparent);
|
|
|
|
g_assert (self->expander_icon == NULL);
|
|
|
|
G_OBJECT_CLASS (gtk_tree_expander_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_CHILD:
|
|
g_value_set_object (value, self->child);
|
|
break;
|
|
|
|
case PROP_HIDE_EXPANDER:
|
|
g_value_set_boolean (value, gtk_tree_expander_get_hide_expander (self));
|
|
break;
|
|
|
|
case PROP_INDENT_FOR_DEPTH:
|
|
g_value_set_boolean (value, gtk_tree_expander_get_indent_for_depth (self));
|
|
break;
|
|
|
|
case PROP_INDENT_FOR_ICON:
|
|
g_value_set_boolean (value, gtk_tree_expander_get_indent_for_icon (self));
|
|
break;
|
|
|
|
case PROP_ITEM:
|
|
g_value_take_object (value, gtk_tree_expander_get_item (self));
|
|
break;
|
|
|
|
case PROP_LIST_ROW:
|
|
g_value_set_object (value, self->list_row);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_CHILD:
|
|
gtk_tree_expander_set_child (self, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_HIDE_EXPANDER:
|
|
gtk_tree_expander_set_hide_expander (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_INDENT_FOR_DEPTH:
|
|
gtk_tree_expander_set_indent_for_depth (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_INDENT_FOR_ICON:
|
|
gtk_tree_expander_set_indent_for_icon (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_LIST_ROW:
|
|
gtk_tree_expander_set_list_row (self, g_value_get_object (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_expand (GtkWidget *widget,
|
|
const char *action_name,
|
|
GVariant *parameter)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
|
|
|
|
if (self->list_row == NULL)
|
|
return;
|
|
|
|
gtk_tree_list_row_set_expanded (self->list_row, TRUE);
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_collapse (GtkWidget *widget,
|
|
const char *action_name,
|
|
GVariant *parameter)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
|
|
|
|
if (self->list_row == NULL)
|
|
return;
|
|
|
|
gtk_tree_list_row_set_expanded (self->list_row, FALSE);
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_toggle_expand (GtkWidget *widget,
|
|
const char *action_name,
|
|
GVariant *parameter)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
|
|
gboolean expand;
|
|
|
|
if (self->list_row == NULL)
|
|
return;
|
|
|
|
expand = !gtk_tree_list_row_get_expanded (self->list_row);
|
|
|
|
if (expand)
|
|
gtk_widget_activate_action (widget, "listitem.scroll-to", NULL);
|
|
|
|
gtk_tree_list_row_set_expanded (self->list_row, expand);
|
|
}
|
|
|
|
static gboolean
|
|
expand_collapse_right (GtkWidget *widget,
|
|
GVariant *args,
|
|
gpointer unused)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
|
|
|
|
if (self->list_row == NULL)
|
|
return FALSE;
|
|
|
|
gtk_tree_list_row_set_expanded (self->list_row, gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
expand_collapse_left (GtkWidget *widget,
|
|
GVariant *args,
|
|
gpointer unused)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
|
|
|
|
if (self->list_row == NULL)
|
|
return FALSE;
|
|
|
|
gtk_tree_list_row_set_expanded (self->list_row, gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_class_init (GtkTreeExpanderClass *klass)
|
|
{
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
widget_class->focus = gtk_tree_expander_focus;
|
|
widget_class->grab_focus = gtk_tree_expander_grab_focus;
|
|
|
|
gobject_class->dispose = gtk_tree_expander_dispose;
|
|
gobject_class->get_property = gtk_tree_expander_get_property;
|
|
gobject_class->set_property = gtk_tree_expander_set_property;
|
|
|
|
/**
|
|
* GtkTreeExpander:child: (attributes org.gtk.Property.get=gtk_tree_expander_get_child org.gtk.Property.set=gtk_tree_expander_set_child)
|
|
*
|
|
* The child widget with the actual contents.
|
|
*/
|
|
properties[PROP_CHILD] =
|
|
g_param_spec_object ("child", NULL, NULL,
|
|
GTK_TYPE_WIDGET,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GtkTreeExpander:hide-expander: (attributes org.gtk.Property.get=gtk_tree_expander_get_hide_expander org.gtk.Property.set=gtk_tree_expander_set_hide_expander)
|
|
*
|
|
* Whether the expander icon should be hidden in a GtkTreeListRow.
|
|
* Note that this property simply hides the icon. The actions and keybinding
|
|
* (i.e. collapse and expand) are not affected by this property.
|
|
*
|
|
* A common use for this property would be to bind to the number of children in a
|
|
* GtkTreeListRow's model in order to hide the expander when a row has no children.
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
properties[PROP_HIDE_EXPANDER] =
|
|
g_param_spec_boolean ("hide-expander", NULL, NULL,
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkTreeExpander:indent-for-depth: (attributes org.gtk.Property.get=gtk_tree_expander_get_indent_for_depth org.gtk.Property.set=gtk_tree_expander_set_indent_for_depth)
|
|
*
|
|
* TreeExpander indents the child according to its depth.
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
properties[PROP_INDENT_FOR_DEPTH] =
|
|
g_param_spec_boolean ("indent-for-depth", NULL, NULL,
|
|
TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkTreeExpander:indent-for-icon: (attributes org.gtk.Property.get=gtk_tree_expander_get_indent_for_icon org.gtk.Property.set=gtk_tree_expander_set_indent_for_icon)
|
|
*
|
|
* TreeExpander indents the child by the width of an expander-icon if it is not expandable.
|
|
*
|
|
* Since: 4.6
|
|
*/
|
|
properties[PROP_INDENT_FOR_ICON] =
|
|
g_param_spec_boolean ("indent-for-icon", NULL, NULL,
|
|
TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkTreeExpander:item: (attributes org.gtk.Property.get=gtk_tree_expander_get_item)
|
|
*
|
|
* The item held by this expander's row.
|
|
*/
|
|
properties[PROP_ITEM] =
|
|
g_param_spec_object ("item", NULL, NULL,
|
|
G_TYPE_OBJECT,
|
|
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GtkTreeExpander:list-row: (attributes org.gtk.Property.get=gtk_tree_expander_get_list_row org.gtk.Property.set=gtk_tree_expander_set_list_row)
|
|
*
|
|
* The list row to track for expander state.
|
|
*/
|
|
properties[PROP_LIST_ROW] =
|
|
g_param_spec_object ("list-row", NULL, NULL,
|
|
GTK_TYPE_TREE_LIST_ROW,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
|
|
|
/**
|
|
* GtkTreeExpander|listitem.expand:
|
|
*
|
|
* Expands the expander if it can be expanded.
|
|
*/
|
|
gtk_widget_class_install_action (widget_class,
|
|
"listitem.expand",
|
|
NULL,
|
|
gtk_tree_expander_expand);
|
|
|
|
/**
|
|
* GtkTreeExpander|listitem.collapse:
|
|
*
|
|
* Collapses the expander.
|
|
*/
|
|
gtk_widget_class_install_action (widget_class,
|
|
"listitem.collapse",
|
|
NULL,
|
|
gtk_tree_expander_collapse);
|
|
|
|
/**
|
|
* GtkTreeExpander|listitem.toggle-expand:
|
|
*
|
|
* Tries to expand the expander if it was collapsed or collapses it if
|
|
* it was expanded.
|
|
*/
|
|
gtk_widget_class_install_action (widget_class,
|
|
"listitem.toggle-expand",
|
|
NULL,
|
|
gtk_tree_expander_toggle_expand);
|
|
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_plus, 0,
|
|
"listitem.expand", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Add, 0,
|
|
"listitem.expand", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_asterisk, 0,
|
|
"listitem.expand", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Multiply, 0,
|
|
"listitem.expand", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_minus, 0,
|
|
"listitem.collapse", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Subtract, 0,
|
|
"listitem.collapse", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_slash, 0,
|
|
"listitem.collapse", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Divide, 0,
|
|
"listitem.collapse", NULL);
|
|
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_Right, GDK_SHIFT_MASK,
|
|
expand_collapse_right, NULL);
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Right, GDK_SHIFT_MASK,
|
|
expand_collapse_right, NULL);
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_Right, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
|
|
expand_collapse_right, NULL);
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Right, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
|
|
expand_collapse_right, NULL);
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_Left, GDK_SHIFT_MASK,
|
|
expand_collapse_left, NULL);
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Left, GDK_SHIFT_MASK,
|
|
expand_collapse_left, NULL);
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_Left, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
|
|
expand_collapse_left, NULL);
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Left, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
|
|
expand_collapse_left, NULL);
|
|
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, GDK_CONTROL_MASK,
|
|
"listitem.toggle-expand", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, GDK_CONTROL_MASK,
|
|
"listitem.toggle-expand", NULL);
|
|
|
|
#if 0
|
|
/* These can't be implements yet. */
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_BackSpace, 0, go_to_parent_row, NULL, NULL);
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_BackSpace, GDK_CONTROL_MASK, go_to_parent_row, NULL, NULL);
|
|
#endif
|
|
|
|
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
|
|
gtk_widget_class_set_css_name (widget_class, I_("treeexpander"));
|
|
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_BUTTON);
|
|
}
|
|
|
|
static gboolean
|
|
gtk_tree_expander_expand_timeout (gpointer data)
|
|
{
|
|
GtkTreeExpander *self = GTK_TREE_EXPANDER (data);
|
|
|
|
if (self->list_row != NULL)
|
|
gtk_tree_list_row_set_expanded (self->list_row, TRUE);
|
|
|
|
self->expand_timer = 0;
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
#define TIMEOUT_EXPAND 500
|
|
|
|
static void
|
|
gtk_tree_expander_drag_enter (GtkDropControllerMotion *motion,
|
|
double x,
|
|
double y,
|
|
GtkTreeExpander *self)
|
|
{
|
|
if (self->list_row == NULL)
|
|
return;
|
|
|
|
if (!gtk_tree_list_row_get_expanded (self->list_row) &&
|
|
!self->expand_timer)
|
|
{
|
|
self->expand_timer = g_timeout_add (TIMEOUT_EXPAND, (GSourceFunc) gtk_tree_expander_expand_timeout, self);
|
|
gdk_source_set_static_name_by_id (self->expand_timer, "[gtk] gtk_tree_expander_expand_timeout");
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_drag_leave (GtkDropControllerMotion *motion,
|
|
GtkTreeExpander *self)
|
|
{
|
|
if (self->expand_timer)
|
|
{
|
|
g_source_remove (self->expand_timer);
|
|
self->expand_timer = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_tree_expander_init (GtkTreeExpander *self)
|
|
{
|
|
GtkEventController *controller;
|
|
|
|
gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
|
|
self->indent_for_icon = TRUE;
|
|
self->indent_for_depth = TRUE;
|
|
|
|
controller = gtk_drop_controller_motion_new ();
|
|
g_signal_connect (controller, "enter", G_CALLBACK (gtk_tree_expander_drag_enter), self);
|
|
g_signal_connect (controller, "leave", G_CALLBACK (gtk_tree_expander_drag_leave), self);
|
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_new:
|
|
*
|
|
* Creates a new `GtkTreeExpander`
|
|
*
|
|
* Returns: a new `GtkTreeExpander`
|
|
**/
|
|
GtkWidget *
|
|
gtk_tree_expander_new (void)
|
|
{
|
|
return g_object_new (GTK_TYPE_TREE_EXPANDER,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_get_child: (attributes org.gtk.Method.get_property=child)
|
|
* @self: a `GtkTreeExpander`
|
|
*
|
|
* Gets the child widget displayed by @self.
|
|
*
|
|
* Returns: (nullable) (transfer none): The child displayed by @self
|
|
**/
|
|
GtkWidget *
|
|
gtk_tree_expander_get_child (GtkTreeExpander *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_TREE_EXPANDER (self), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_set_child: (attributes org.gtk.Method.set_property=child)
|
|
* @self: a `GtkTreeExpander`
|
|
* @child: (nullable): a `GtkWidget`
|
|
*
|
|
* Sets the content widget to display.
|
|
*/
|
|
void
|
|
gtk_tree_expander_set_child (GtkTreeExpander *self,
|
|
GtkWidget *child)
|
|
{
|
|
g_return_if_fail (GTK_IS_TREE_EXPANDER (self));
|
|
g_return_if_fail (child == NULL || self->child == child || gtk_widget_get_parent (child) == NULL);
|
|
|
|
if (self->child == child)
|
|
return;
|
|
|
|
g_clear_pointer (&self->child, gtk_widget_unparent);
|
|
|
|
if (child)
|
|
{
|
|
self->child = child;
|
|
gtk_widget_set_parent (child, GTK_WIDGET (self));
|
|
|
|
gtk_accessible_update_relation (GTK_ACCESSIBLE (self),
|
|
GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->child, NULL,
|
|
-1);
|
|
}
|
|
else
|
|
{
|
|
gtk_accessible_reset_relation (GTK_ACCESSIBLE (self), GTK_ACCESSIBLE_RELATION_LABELLED_BY);
|
|
}
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHILD]);
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_get_item: (attributes org.gtk.Method.get_property=item)
|
|
* @self: a `GtkTreeExpander`
|
|
*
|
|
* Forwards the item set on the `GtkTreeListRow` that @self is managing.
|
|
*
|
|
* This call is essentially equivalent to calling:
|
|
*
|
|
* ```c
|
|
* gtk_tree_list_row_get_item (gtk_tree_expander_get_list_row (@self));
|
|
* ```
|
|
*
|
|
* Returns: (nullable) (transfer full) (type GObject): The item of the row
|
|
*/
|
|
gpointer
|
|
gtk_tree_expander_get_item (GtkTreeExpander *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_TREE_EXPANDER (self), NULL);
|
|
|
|
if (self->list_row == NULL)
|
|
return NULL;
|
|
|
|
return gtk_tree_list_row_get_item (self->list_row);
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_get_list_row: (attributes org.gtk.Method.get_property=list-row)
|
|
* @self: a `GtkTreeExpander`
|
|
*
|
|
* Gets the list row managed by @self.
|
|
*
|
|
* Returns: (nullable) (transfer none): The list row displayed by @self
|
|
*/
|
|
GtkTreeListRow *
|
|
gtk_tree_expander_get_list_row (GtkTreeExpander *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_TREE_EXPANDER (self), NULL);
|
|
|
|
return self->list_row;
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_set_list_row: (attributes org.gtk.Method.set_property=list-row)
|
|
* @self: a `GtkTreeExpander` widget
|
|
* @list_row: (nullable): a `GtkTreeListRow`
|
|
*
|
|
* Sets the tree list row that this expander should manage.
|
|
*/
|
|
void
|
|
gtk_tree_expander_set_list_row (GtkTreeExpander *self,
|
|
GtkTreeListRow *list_row)
|
|
{
|
|
g_return_if_fail (GTK_IS_TREE_EXPANDER (self));
|
|
g_return_if_fail (list_row == NULL || GTK_IS_TREE_LIST_ROW (list_row));
|
|
|
|
if (self->list_row == list_row)
|
|
return;
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
gtk_tree_expander_clear_list_row (self);
|
|
|
|
if (list_row)
|
|
{
|
|
self->list_row = g_object_ref (list_row);
|
|
self->notify_handler = g_signal_connect (list_row,
|
|
"notify",
|
|
G_CALLBACK (gtk_tree_expander_list_row_notify_cb),
|
|
self);
|
|
}
|
|
|
|
gtk_tree_expander_update_for_list_row (self);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LIST_ROW]);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
|
|
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_get_indent_for_depth: (attributes org.gtk.Method.get_property=indent-for-depth)
|
|
* @self: a `GtkTreeExpander`
|
|
*
|
|
* TreeExpander indents each level of depth with an additional indent.
|
|
*
|
|
* Returns: TRUE if the child should be indented . Otherwise FALSE.
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
gboolean
|
|
gtk_tree_expander_get_indent_for_depth (GtkTreeExpander *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_TREE_EXPANDER (self), FALSE);
|
|
|
|
return self->indent_for_depth;
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_set_indent_for_depth: (attributes org.gtk.Method.set_property=indent-for-depth)
|
|
* @self: a `GtkTreeExpander` widget
|
|
* @indent_for_depth: TRUE if the child should be indented. Otherwise FALSE.
|
|
*
|
|
* Sets if the TreeExpander should indent the child according to its depth.
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
void
|
|
gtk_tree_expander_set_indent_for_depth (GtkTreeExpander *self,
|
|
gboolean indent_for_depth)
|
|
{
|
|
g_return_if_fail (GTK_IS_TREE_EXPANDER (self));
|
|
|
|
if (indent_for_depth == self->indent_for_depth)
|
|
return;
|
|
|
|
self->indent_for_depth = indent_for_depth;
|
|
|
|
gtk_tree_expander_update_for_list_row (self);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INDENT_FOR_DEPTH]);
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_get_indent_for_icon: (attributes org.gtk.Method.get_property=indent-for-icon)
|
|
* @self: a `GtkTreeExpander`
|
|
*
|
|
* TreeExpander indents the child by the width of an expander-icon if it is not expandable.
|
|
*
|
|
* Returns: TRUE if the child should be indented when not expandable. Otherwise FALSE.
|
|
*
|
|
* Since: 4.6
|
|
*/
|
|
gboolean
|
|
gtk_tree_expander_get_indent_for_icon (GtkTreeExpander *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_TREE_EXPANDER (self), FALSE);
|
|
|
|
return self->indent_for_icon;
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_set_indent_for_icon: (attributes org.gtk.Method.set_property=indent-for-icon)
|
|
* @self: a `GtkTreeExpander` widget
|
|
* @indent_for_icon: TRUE if the child should be indented without expander. Otherwise FALSE.
|
|
*
|
|
* Sets if the TreeExpander should indent the child by the width of an expander-icon when it is not expandable.
|
|
*
|
|
* Since: 4.6
|
|
*/
|
|
void
|
|
gtk_tree_expander_set_indent_for_icon (GtkTreeExpander *self,
|
|
gboolean indent_for_icon)
|
|
{
|
|
g_return_if_fail (GTK_IS_TREE_EXPANDER (self));
|
|
|
|
if (indent_for_icon == self->indent_for_icon)
|
|
return;
|
|
|
|
self->indent_for_icon = indent_for_icon;
|
|
|
|
gtk_tree_expander_update_for_list_row (self);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INDENT_FOR_ICON]);
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_get_hide_expander: (attributes org.gtk.Method.get_property=hide-expander)
|
|
* @self: a `GtkTreeExpander`
|
|
*
|
|
* Gets whether the TreeExpander should be hidden in a GtkTreeListRow.
|
|
*
|
|
* Returns: TRUE if the expander icon should be hidden. Otherwise FALSE.
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
gboolean
|
|
gtk_tree_expander_get_hide_expander (GtkTreeExpander *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_TREE_EXPANDER (self), FALSE);
|
|
|
|
return self->hide_expander;
|
|
}
|
|
|
|
/**
|
|
* gtk_tree_expander_set_hide_expander: (attributes org.gtk.Method.set_property=hide-expander)
|
|
* @self: a `GtkTreeExpander` widget
|
|
* @hide_expander: TRUE if the expander should be hidden. Otherwise FALSE.
|
|
*
|
|
* Sets whether the expander icon should be visible in a GtkTreeListRow.
|
|
*
|
|
* Since: 4.10
|
|
*/
|
|
void
|
|
gtk_tree_expander_set_hide_expander (GtkTreeExpander *self,
|
|
gboolean hide_expander)
|
|
{
|
|
g_return_if_fail (GTK_IS_TREE_EXPANDER (self));
|
|
|
|
if (hide_expander == self->hide_expander)
|
|
return;
|
|
|
|
self->hide_expander = hide_expander;
|
|
|
|
gtk_tree_expander_update_for_list_row (self);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HIDE_EXPANDER]);
|
|
}
|