Commit 30f09ea1 authored by Benjamin Otte's avatar Benjamin Otte Committed by Matthias Clasen

listitem: Make this a GObject

This splits GtkListItem into 2 parts:

1. GtkListItem
   This is purely a GObject with public API for developers who want to
   populate lists. There is no chance to cause conflict with GtkWidget
   properties that the list implementation assumed control over and
   defines a clear boundary.
2. GtkListItemWidget
   The widget part of the listitem. This is not only fully in control of
   the list machinery, the machinery can also use different widget
   implementations for different list widgets like I inted to for
   GtkColumnView.
parent db3e225f
......@@ -137,6 +137,7 @@ private_headers = [
'gtklistitemprivate.h',
'gtklistitemfactoryprivate.h',
'gtklistitemmanagerprivate.h',
'gtklistitemwidgetprivate.h',
'gtklockbuttonprivate.h',
'gtkmagnifierprivate.h',
'gtkmediafileprivate.h',
......
......@@ -23,6 +23,7 @@
#include "gtkadjustment.h"
#include "gtkintl.h"
#include "gtklistitemwidgetprivate.h"
#include "gtkorientableprivate.h"
#include "gtkscrollable.h"
#include "gtksingleselection.h"
......@@ -749,10 +750,10 @@ gtk_list_base_update_focus_tracker (GtkListBase *self)
guint pos;
focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self));
if (!GTK_IS_LIST_ITEM (focus_child))
if (!GTK_IS_LIST_ITEM_WIDGET (focus_child))
return;
pos = gtk_list_item_get_position (GTK_LIST_ITEM (focus_child));
pos = gtk_list_item_widget_get_position (GTK_LIST_ITEM_WIDGET (focus_child));
if (pos != gtk_list_item_tracker_get_position (priv->item_manager, priv->focus))
{
gtk_list_item_tracker_set_position (priv->item_manager,
......
......@@ -25,7 +25,6 @@
#endif
#include <gtk/gtkwidget.h>
#include <gtk/gtklistitem.h>
G_BEGIN_DECLS
......
......@@ -21,14 +21,7 @@
#include "gtklistitemprivate.h"
#include "gtkbinlayout.h"
#include "gtkcssnodeprivate.h"
#include "gtkeventcontrollerfocus.h"
#include "gtkgestureclick.h"
#include "gtkintl.h"
#include "gtkmain.h"
#include "gtkwidget.h"
#include "gtkwidgetprivate.h"
/**
* SECTION:gtklistitem
......@@ -53,24 +46,9 @@
* The #GtkListItem:item property is not %NULL.
*/
struct _GtkListItem
{
GtkWidget parent_instance;
GObject *item;
GtkWidget *child;
guint position;
guint activatable : 1;
guint selectable : 1;
guint selected : 1;
};
struct _GtkListItemClass
{
GtkWidgetClass parent_class;
void (* activate_signal) (GtkListItem *self);
GObjectClass parent_class;
};
enum
......@@ -86,81 +64,17 @@ enum
N_PROPS
};
enum
{
ACTIVATE_SIGNAL,
LAST_SIGNAL
};
G_DEFINE_TYPE (GtkListItem, gtk_list_item, GTK_TYPE_WIDGET)
G_DEFINE_TYPE (GtkListItem, gtk_list_item, G_TYPE_OBJECT)
static GParamSpec *properties[N_PROPS] = { NULL, };
static guint signals[LAST_SIGNAL] = { 0 };
static void
gtk_list_item_activate_signal (GtkListItem *self)
{
if (!self->activatable)
return;
gtk_widget_activate_action (GTK_WIDGET (self),
"list.activate-item",
"u",
self->position);
}
static gboolean
gtk_list_item_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GtkListItem *self = GTK_LIST_ITEM (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_can_focus (widget) ||
!self->selectable)
return FALSE;
return gtk_widget_grab_focus (widget);
}
static gboolean
gtk_list_item_grab_focus (GtkWidget *widget)
{
GtkListItem *self = GTK_LIST_ITEM (widget);
if (self->child && gtk_widget_grab_focus (self->child))
return TRUE;
return GTK_WIDGET_CLASS (gtk_list_item_parent_class)->grab_focus (widget);
}
static void
gtk_list_item_dispose (GObject *object)
{
GtkListItem *self = GTK_LIST_ITEM (object);
g_assert (self->item == NULL);
g_clear_pointer (&self->child, gtk_widget_unparent);
g_assert (self->owner == NULL); /* would hold a reference */
g_clear_object (&self->child);
G_OBJECT_CLASS (gtk_list_item_parent_class)->dispose (object);
}
......@@ -233,36 +147,11 @@ gtk_list_item_set_property (GObject *object,
}
}
static void
gtk_list_item_select_action (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
GtkListItem *self = GTK_LIST_ITEM (widget);
gboolean modify, extend;
if (!self->selectable)
return;
g_variant_get (parameter, "(bb)", &modify, &extend);
gtk_widget_activate_action (GTK_WIDGET (self),
"list.select-item",
"(ubb)",
self->position, modify, extend);
}
static void
gtk_list_item_class_init (GtkListItemClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
klass->activate_signal = gtk_list_item_activate_signal;
widget_class->focus = gtk_list_item_focus;
widget_class->grab_focus = gtk_list_item_grab_focus;
gobject_class->dispose = gtk_list_item_dispose;
gobject_class->get_property = gtk_list_item_get_property;
gobject_class->set_property = gtk_list_item_set_property;
......@@ -340,197 +229,25 @@ gtk_list_item_class_init (GtkListItemClass *klass)
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/**
* GtkListItem::activate-signal:
*
* This is a keybinding signal, which will cause this row to be activated.
*
* Do not use it, it is an implementation detail.
*
* If you want to be notified when the user activates a listitem (by key or not),
* look at the list widget this item is contained in.
*/
signals[ACTIVATE_SIGNAL] =
g_signal_new (I_("activate-keybinding"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkListItemClass, activate_signal),
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
widget_class->activate_signal = signals[ACTIVATE_SIGNAL];
/**
* GtkListItem|listitem.select:
* @modify: %TRUE to toggle the existing selection, %FALSE to select
* @extend: %TRUE to extend the selection
*
* Changes selection if the item is selectable.
* If the item is not selectable, nothing happens.
*
* This function will emit the list.select-item action and the resulting
* behavior, in particular the interpretation of @modify and @extend
* depends on the view containing this listitem. See for example
* GtkListView|list.select-item or GtkGridView|list.select-item.
*/
gtk_widget_class_install_action (widget_class,
"listitem.select",
"(bb)",
gtk_list_item_select_action);
gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Return, 0,
"activate-keybinding", 0);
gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_ISO_Enter, 0,
"activate-keybinding", 0);
gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Enter, 0,
"activate-keybinding", 0);
/* note that some of these may get overwritten by child widgets,
* such as GtkTreeExpander */
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, 0,
"listitem.select", "(bb)", TRUE, FALSE);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, GDK_CONTROL_MASK,
"listitem.select", "(bb)", TRUE, FALSE);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, GDK_SHIFT_MASK,
"listitem.select", "(bb)", TRUE, FALSE);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
"listitem.select", "(bb)", TRUE, FALSE);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, 0,
"listitem.select", "(bb)", TRUE, FALSE);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, GDK_CONTROL_MASK,
"listitem.select", "(bb)", TRUE, FALSE);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, GDK_SHIFT_MASK,
"listitem.select", "(bb)", TRUE, FALSE);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
"listitem.select", "(bb)", TRUE, FALSE);
/* This gets overwritten by gtk_list_item_new() but better safe than sorry */
gtk_widget_class_set_css_name (widget_class, I_("row"));
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
}
static void
gtk_list_item_click_gesture_pressed (GtkGestureClick *gesture,
int n_press,
double x,
double y,
GtkListItem *self)
{
GtkWidget *widget = GTK_WIDGET (self);
if (!self->selectable && !self->activatable)
{
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
return;
}
if (self->selectable)
{
GdkModifierType state;
GdkEvent *event;
gboolean extend, modify;
event = gtk_gesture_get_last_event (GTK_GESTURE (gesture),
gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)));
state = gdk_event_get_modifier_state (event);
extend = (state & GDK_SHIFT_MASK) != 0;
modify = (state & GDK_CONTROL_MASK) != 0;
gtk_widget_activate_action (widget,
"list.select-item",
"(ubb)",
self->position, modify, extend);
}
if (self->activatable)
{
if (n_press == 2)
{
gtk_widget_activate_action (widget,
"list.activate-item",
"u",
self->position);
}
}
gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_ACTIVE, FALSE);
if (gtk_widget_get_focus_on_click (widget))
gtk_widget_grab_focus (widget);
}
static void
gtk_list_item_enter_cb (GtkEventControllerFocus *controller,
GtkListItem *self)
{
GtkWidget *widget = GTK_WIDGET (self);
gtk_widget_activate_action (widget,
"list.scroll-to-item",
"u",
self->position);
}
static void
gtk_list_item_click_gesture_released (GtkGestureClick *gesture,
int n_press,
double x,
double y,
GtkListItem *self)
{
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
}
static void
gtk_list_item_click_gesture_canceled (GtkGestureClick *gesture,
GdkEventSequence *sequence,
GtkListItem *self)
{
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
}
static void
gtk_list_item_init (GtkListItem *self)
{
GtkEventController *controller;
GtkGesture *gesture;
self->selectable = TRUE;
self->activatable = TRUE;
gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
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, "pressed",
G_CALLBACK (gtk_list_item_click_gesture_pressed), self);
g_signal_connect (gesture, "released",
G_CALLBACK (gtk_list_item_click_gesture_released), self);
g_signal_connect (gesture, "cancel",
G_CALLBACK (gtk_list_item_click_gesture_canceled), self);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
controller = gtk_event_controller_focus_new ();
g_signal_connect (controller, "enter", G_CALLBACK (gtk_list_item_enter_cb), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
}
GtkListItem *
gtk_list_item_new (const char *css_name)
gtk_list_item_new (GtkListItemWidget *owner)
{
GtkListItem *result;
g_return_val_if_fail (css_name != NULL, NULL);
g_return_val_if_fail (owner != NULL, NULL);
result = g_object_new (GTK_TYPE_LIST_ITEM,
"css-name", css_name,
NULL);
result->owner = owner;
return result;
}
......@@ -590,12 +307,17 @@ gtk_list_item_set_child (GtkListItem *self,
if (self->child == child)
return;
g_clear_pointer (&self->child, gtk_widget_unparent);
if (self->child && self->owner)
gtk_list_item_widget_remove_child (self->owner, self->child);
g_clear_object (&self->child);
if (child)
{
gtk_widget_insert_after (child, GTK_WIDGET (self), NULL);
g_object_ref_sink (child);
self->child = child;
if (self->owner)
gtk_list_item_widget_add_child (self->owner, child);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
......@@ -615,8 +337,6 @@ gtk_list_item_set_item (GtkListItem *self,
if (item)
self->item = g_object_ref (item);
gtk_css_node_invalidate (gtk_widget_get_css_node (GTK_WIDGET (self)), GTK_CSS_CHANGE_ANIMATIONS);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
}
......@@ -680,11 +400,6 @@ gtk_list_item_set_selected (GtkListItem *self,
self->selected = selected;
if (selected)
gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE);
else
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
}
......
......@@ -21,7 +21,7 @@
#include "gtklistitemmanagerprivate.h"
#include "gtklistitemprivate.h"
#include "gtklistitemwidgetprivate.h"
#include "gtkwidgetprivate.h"
#define GTK_LIST_VIEW_MAX_LIST_ITEMS 200
......@@ -47,7 +47,7 @@ struct _GtkListItemManagerClass
struct _GtkListItemTracker
{
guint position;
GtkListItem *widget;
GtkListItemWidget *widget;
guint n_before;
guint n_after;
};
......@@ -595,7 +595,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
if (tracker->widget == NULL)
continue;
if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget)))
if (g_hash_table_lookup (change, gtk_list_item_widget_get_item (tracker->widget)))
break;
}
......@@ -678,7 +678,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
}
else if (tracker->position >= position)
{
if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget)))
if (g_hash_table_lookup (change, gtk_list_item_widget_get_item (tracker->widget)))
{
/* The item is gone. Guess a good new position */
tracker->position = position + (tracker->position - position) * added / removed;
......@@ -696,7 +696,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
/* item was put in its right place in the expensive loop above,
* and we updated its position while at it. So grab it from there.
*/
tracker->position = gtk_list_item_get_position (tracker->widget);
tracker->position = gtk_list_item_widget_get_position (tracker->widget);
}
}
else
......@@ -723,7 +723,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
item = gtk_list_item_manager_get_nth (self, tracker->position, NULL);
g_assert (item != NULL);
g_assert (item->widget);
tracker->widget = GTK_LIST_ITEM (item->widget);
tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget);
}
g_hash_table_iter_init (&iter, change);
......@@ -850,7 +850,7 @@ gtk_list_item_manager_set_factory (GtkListItemManager *self,
item = gtk_list_item_manager_get_nth (self, tracker->position, NULL);
g_assert (item);
tracker->widget = GTK_LIST_ITEM (item->widget);
tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget);
}
}
......@@ -922,23 +922,21 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
guint position,
GtkWidget *prev_sibling)
{
GtkListItem *result;
GtkWidget *result;
gpointer item;
gboolean selected;
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
result = gtk_list_item_new (self->item_css_name);
if (self->factory)
gtk_list_item_factory_setup (self->factory, result);
result = gtk_list_item_widget_new (self->factory,
self->item_css_name);
item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
selected = gtk_selection_model_is_selected (self->model, position);
if (self->factory)
gtk_list_item_factory_bind (self->factory, result, position, item, selected);
gtk_list_item_widget_bind (GTK_LIST_ITEM_WIDGET (result), position, item, selected);
g_object_unref (item);
gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling);
gtk_widget_insert_after (result, self->widget, prev_sibling);
return GTK_WIDGET (result);
}
......@@ -964,7 +962,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self,
guint position,
GtkWidget *prev_sibling)
{
GtkListItem *result;
GtkWidget *result;
gpointer item;
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
......@@ -974,11 +972,10 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self,
item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
if (g_hash_table_steal_extended (change, item, NULL, (gpointer *) &result))
{
if (self->factory)
gtk_list_item_factory_update (self->factory, result, position, FALSE);
gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling);
gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (result), position, FALSE);
gtk_widget_insert_after (result, self->widget, prev_sibling);
/* XXX: Should we let the listview do this? */
gtk_widget_queue_resize (GTK_WIDGET (result));
gtk_widget_queue_resize (result);
}
else
{
......@@ -986,7 +983,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self,
}
g_object_unref (item);
return GTK_WIDGET (result);
return result;
}
/**
......@@ -1013,8 +1010,7 @@ gtk_list_item_manager_move_list_item (GtkListItemManager *self,
item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
selected = gtk_selection_model_is_selected (self->model, position);
if (self->factory)
gtk_list_item_factory_rebind (self->factory, GTK_LIST_ITEM (list_item), position, item, selected);
gtk_list_item_widget_rebind (GTK_LIST_ITEM_WIDGET (list_item), position, item, selected);
gtk_widget_insert_after (list_item, _gtk_widget_get_parent (list_item), prev_sibling);
g_object_unref (item);
}
......@@ -1036,11 +1032,10 @@ gtk_list_item_manager_update_list_item (GtkListItemManager *self,
gboolean selected;
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
g_return_if_fail (GTK_IS_LIST_ITEM (item));
g_return_if_fail (GTK_IS_LIST_ITEM_WIDGET (item));
selected = gtk_selection_model_is_selected (self->model, position);
if (self->factory)
gtk_list_item_factory_update (self->factory, GTK_LIST_ITEM (item), position, selected);
gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (item), position, selected);
}
/*
......@@ -1060,21 +1055,16 @@ gtk_list_item_manager_release_list_item (GtkListItemManager *self,
GtkWidget *item)
{
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
g_return_if_fail (GTK_IS_LIST_ITEM (item));
g_return_if_fail (GTK_IS_LIST_ITEM_WIDGET (item));
if (change != NULL)
{
if (g_hash_table_insert (change, gtk_list_item_get_item (GTK_LIST_ITEM (item)), item))
if (g_hash_table_insert (change, gtk_list_item_widget_get_item (GTK_LIST_ITEM_WIDGET (item)), item))
return;
g_warning ("FIXME: Handle the same item multiple times in the list.\nLars says this totally should not happen, but here we are.");
}
if (self->factory)
{
gtk_list_item_factory_unbind (self->factory, GTK_LIST_ITEM (item));
gtk_list_item_factory_teardown (self->factory, GTK_LIST_ITEM (item));
}
gtk_widget_unparent (item);
}
......@@ -1136,7 +1126,7 @@ gtk_list_item_tracker_set_position (GtkListItemManager *self,
item = gtk_list_item_manager_get_nth (self, position, NULL);
if (item)
tracker->widget = GTK_LIST_ITEM (item->widget);
tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget);
gtk_widget_queue_resize (self->widget);
}
......
......@@ -23,7 +23,7 @@
#include "gtk/gtktypes.h"
#include "gtk/gtklistitemfactoryprivate.h"
#include "gtk/gtklistitemfactory.h"
#include "gtk/gtkrbtreeprivate.h"
#include "gtk/gtkselectionmodel.h"
......
......@@ -22,11 +22,26 @@
#include "gtklistitem.h"