2019-11-01 06:31:38 +00:00
|
|
|
/*
|
|
|
|
* Copyright © 2018 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 "gtklistitemwidgetprivate.h"
|
|
|
|
|
|
|
|
#include "gtkbinlayout.h"
|
|
|
|
#include "gtkeventcontrollerfocus.h"
|
2019-12-14 01:56:32 +00:00
|
|
|
#include "gtkeventcontrollermotion.h"
|
2019-11-01 06:31:38 +00:00
|
|
|
#include "gtkgestureclick.h"
|
|
|
|
#include "gtkintl.h"
|
|
|
|
#include "gtklistitemfactoryprivate.h"
|
|
|
|
#include "gtklistitemprivate.h"
|
2019-12-22 00:07:11 +00:00
|
|
|
#include "gtklistbaseprivate.h"
|
2019-11-01 06:31:38 +00:00
|
|
|
#include "gtkmain.h"
|
2019-11-03 02:34:56 +00:00
|
|
|
#include "gtkselectionmodel.h"
|
2019-11-01 06:31:38 +00:00
|
|
|
#include "gtkwidget.h"
|
|
|
|
#include "gtkwidgetprivate.h"
|
|
|
|
|
2019-11-03 02:17:37 +00:00
|
|
|
typedef struct _GtkListItemWidgetPrivate GtkListItemWidgetPrivate;
|
|
|
|
struct _GtkListItemWidgetPrivate
|
|
|
|
{
|
|
|
|
GtkListItemFactory *factory;
|
|
|
|
GtkListItem *list_item;
|
2019-11-03 02:34:56 +00:00
|
|
|
|
|
|
|
GObject *item;
|
|
|
|
guint position;
|
|
|
|
gboolean selected;
|
2019-12-14 01:56:32 +00:00
|
|
|
gboolean single_click_activate;
|
2019-11-03 02:17:37 +00:00
|
|
|
};
|
|
|
|
|
2019-11-03 02:59:04 +00:00
|
|
|
enum {
|
|
|
|
PROP_0,
|
|
|
|
PROP_FACTORY,
|
2019-12-14 01:56:32 +00:00
|
|
|
PROP_SINGLE_CLICK_ACTIVATE,
|
2019-11-03 02:59:04 +00:00
|
|
|
|
|
|
|
N_PROPS
|
|
|
|
};
|
|
|
|
|
2019-11-01 06:31:38 +00:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
ACTIVATE_SIGNAL,
|
|
|
|
LAST_SIGNAL
|
|
|
|
};
|
|
|
|
|
2019-11-03 02:17:37 +00:00
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GtkListItemWidget, gtk_list_item_widget, GTK_TYPE_WIDGET)
|
2019-11-01 06:31:38 +00:00
|
|
|
|
2019-11-03 02:59:04 +00:00
|
|
|
static GParamSpec *properties[N_PROPS] = { NULL, };
|
2019-11-01 06:31:38 +00:00
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_activate_signal (GtkListItemWidget *self)
|
|
|
|
{
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
2019-11-03 02:34:56 +00:00
|
|
|
if (priv->list_item && !priv->list_item->activatable)
|
2019-11-01 06:31:38 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
gtk_widget_activate_action (GTK_WIDGET (self),
|
|
|
|
"list.activate-item",
|
|
|
|
"u",
|
2019-11-03 02:34:56 +00:00
|
|
|
priv->position);
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gtk_list_item_widget_focus (GtkWidget *widget,
|
|
|
|
GtkDirectionType direction)
|
|
|
|
{
|
2020-06-03 16:12:00 +00:00
|
|
|
GtkWidget *child, *focus_child;
|
2019-11-01 06:31:38 +00:00
|
|
|
|
|
|
|
/* 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.
|
|
|
|
*/
|
2020-06-03 16:12:00 +00:00
|
|
|
|
|
|
|
focus_child = gtk_widget_get_focus_child (widget);
|
|
|
|
if (focus_child && gtk_widget_child_focus (focus_child, direction))
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
for (child = focus_child ? gtk_widget_get_next_sibling (focus_child)
|
|
|
|
: gtk_widget_get_first_child (widget);
|
|
|
|
child;
|
|
|
|
child = gtk_widget_get_next_sibling (child))
|
2019-11-01 06:31:38 +00:00
|
|
|
{
|
2020-06-03 16:12:00 +00:00
|
|
|
if (gtk_widget_child_focus (child, direction))
|
2019-11-01 06:31:38 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2020-06-03 16:12:00 +00:00
|
|
|
if (focus_child)
|
2019-11-01 06:31:38 +00:00
|
|
|
return FALSE;
|
|
|
|
|
2020-06-03 16:12:00 +00:00
|
|
|
if (gtk_widget_is_focus (widget))
|
2019-11-01 06:31:38 +00:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return gtk_widget_grab_focus (widget);
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gtk_list_item_widget_grab_focus (GtkWidget *widget)
|
|
|
|
{
|
|
|
|
GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget);
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
2020-06-03 16:12:00 +00:00
|
|
|
GtkWidget *child;
|
2019-11-01 06:31:38 +00:00
|
|
|
|
2020-06-03 16:12:00 +00:00
|
|
|
for (child = gtk_widget_get_first_child (widget);
|
|
|
|
child;
|
|
|
|
child = gtk_widget_get_next_sibling (child))
|
|
|
|
{
|
|
|
|
if (gtk_widget_grab_focus (child))
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (priv->list_item == NULL ||
|
|
|
|
!priv->list_item->selectable)
|
|
|
|
return FALSE;
|
2019-11-01 06:31:38 +00:00
|
|
|
|
|
|
|
return GTK_WIDGET_CLASS (gtk_list_item_widget_parent_class)->grab_focus (widget);
|
|
|
|
}
|
|
|
|
|
2022-06-28 14:34:24 +00:00
|
|
|
static void
|
|
|
|
gtk_list_item_widget_setup_func (gpointer object,
|
|
|
|
gpointer data)
|
|
|
|
{
|
|
|
|
GtkListItemWidget *self = data;
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
GtkListItem *list_item = object;
|
|
|
|
|
|
|
|
priv->list_item = list_item;
|
|
|
|
list_item->owner = self;
|
|
|
|
|
|
|
|
if (list_item->child)
|
|
|
|
gtk_list_item_widget_add_child (self, list_item->child);
|
|
|
|
|
|
|
|
gtk_list_item_widget_set_activatable (self, list_item->activatable);
|
|
|
|
|
|
|
|
gtk_list_item_do_notify (list_item,
|
|
|
|
priv->item != NULL,
|
|
|
|
priv->position != GTK_INVALID_LIST_POSITION,
|
|
|
|
priv->selected);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_setup_factory (GtkListItemWidget *self)
|
|
|
|
{
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
GtkListItem *list_item;
|
|
|
|
|
|
|
|
list_item = gtk_list_item_new ();
|
|
|
|
|
|
|
|
gtk_list_item_factory_setup (priv->factory,
|
|
|
|
G_OBJECT (list_item),
|
|
|
|
priv->item != NULL,
|
|
|
|
gtk_list_item_widget_setup_func,
|
|
|
|
self);
|
|
|
|
|
|
|
|
g_assert (priv->list_item == list_item);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_teardown_func (gpointer object,
|
|
|
|
gpointer data)
|
|
|
|
{
|
|
|
|
GtkListItemWidget *self = data;
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
GtkListItem *list_item = object;
|
|
|
|
|
|
|
|
g_assert (priv->list_item == list_item);
|
|
|
|
|
|
|
|
priv->list_item = NULL;
|
|
|
|
list_item->owner = NULL;
|
|
|
|
|
|
|
|
if (list_item->child)
|
|
|
|
gtk_list_item_widget_remove_child (self, list_item->child);
|
|
|
|
|
|
|
|
gtk_list_item_widget_set_activatable (self, FALSE);
|
|
|
|
|
|
|
|
gtk_list_item_do_notify (list_item,
|
|
|
|
priv->item != NULL,
|
|
|
|
priv->position != GTK_INVALID_LIST_POSITION,
|
|
|
|
priv->selected);
|
|
|
|
|
|
|
|
gtk_list_item_set_child (list_item, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_teardown_factory (GtkListItemWidget *self)
|
|
|
|
{
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
2022-08-14 20:55:27 +00:00
|
|
|
GtkListItem *list_item = priv->list_item;
|
2022-06-28 14:34:24 +00:00
|
|
|
|
|
|
|
gtk_list_item_factory_teardown (priv->factory,
|
2022-08-14 20:55:27 +00:00
|
|
|
G_OBJECT (list_item),
|
2022-06-28 14:34:24 +00:00
|
|
|
priv->item != NULL,
|
|
|
|
gtk_list_item_widget_teardown_func,
|
|
|
|
self);
|
|
|
|
|
|
|
|
g_assert (priv->list_item == NULL);
|
2022-08-14 20:55:27 +00:00
|
|
|
g_object_unref (list_item);
|
2022-06-28 14:34:24 +00:00
|
|
|
}
|
|
|
|
|
2019-11-01 06:31:38 +00:00
|
|
|
static void
|
2019-11-03 02:59:04 +00:00
|
|
|
gtk_list_item_widget_root (GtkWidget *widget)
|
2019-11-01 06:31:38 +00:00
|
|
|
{
|
2019-11-03 02:59:04 +00:00
|
|
|
GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget);
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
2019-11-01 06:31:38 +00:00
|
|
|
|
2019-11-03 02:59:04 +00:00
|
|
|
GTK_WIDGET_CLASS (gtk_list_item_widget_parent_class)->root (widget);
|
|
|
|
|
|
|
|
if (priv->factory)
|
2022-06-28 14:34:24 +00:00
|
|
|
gtk_list_item_widget_setup_factory (self);
|
2019-11-03 02:59:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_unroot (GtkWidget *widget)
|
|
|
|
{
|
|
|
|
GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget);
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
|
|
|
GTK_WIDGET_CLASS (gtk_list_item_widget_parent_class)->unroot (widget);
|
|
|
|
|
2019-11-03 02:17:37 +00:00
|
|
|
if (priv->list_item)
|
2022-06-28 14:34:24 +00:00
|
|
|
gtk_list_item_widget_teardown_factory (self);
|
2019-11-03 02:59:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_set_property (GObject *object,
|
|
|
|
guint property_id,
|
|
|
|
const GValue *value,
|
|
|
|
GParamSpec *pspec)
|
|
|
|
{
|
|
|
|
GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (object);
|
|
|
|
|
|
|
|
switch (property_id)
|
|
|
|
{
|
|
|
|
case PROP_FACTORY:
|
|
|
|
gtk_list_item_widget_set_factory (self, g_value_get_object (value));
|
|
|
|
break;
|
|
|
|
|
2019-12-14 01:56:32 +00:00
|
|
|
case PROP_SINGLE_CLICK_ACTIVATE:
|
|
|
|
gtk_list_item_widget_set_single_click_activate (self, g_value_get_boolean (value));
|
|
|
|
break;
|
|
|
|
|
2019-11-03 02:59:04 +00:00
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
break;
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
2019-11-03 02:59:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_dispose (GObject *object)
|
|
|
|
{
|
|
|
|
GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (object);
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
|
|
|
g_assert (priv->list_item == NULL);
|
|
|
|
|
2019-11-03 02:34:56 +00:00
|
|
|
g_clear_object (&priv->item);
|
2019-11-03 02:17:37 +00:00
|
|
|
g_clear_object (&priv->factory);
|
2019-11-01 06:31:38 +00:00
|
|
|
|
|
|
|
G_OBJECT_CLASS (gtk_list_item_widget_parent_class)->dispose (object);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_select_action (GtkWidget *widget,
|
|
|
|
const char *action_name,
|
|
|
|
GVariant *parameter)
|
|
|
|
{
|
|
|
|
GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget);
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
2019-11-01 06:31:38 +00:00
|
|
|
gboolean modify, extend;
|
|
|
|
|
2019-11-03 02:34:56 +00:00
|
|
|
if (priv->list_item && !priv->list_item->selectable)
|
2019-11-01 06:31:38 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
g_variant_get (parameter, "(bb)", &modify, &extend);
|
|
|
|
|
|
|
|
gtk_widget_activate_action (GTK_WIDGET (self),
|
|
|
|
"list.select-item",
|
|
|
|
"(ubb)",
|
2019-11-03 02:34:56 +00:00
|
|
|
priv->position, modify, extend);
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_class_init (GtkListItemWidgetClass *klass)
|
|
|
|
{
|
|
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
|
|
|
|
klass->activate_signal = gtk_list_item_widget_activate_signal;
|
|
|
|
|
|
|
|
widget_class->focus = gtk_list_item_widget_focus;
|
|
|
|
widget_class->grab_focus = gtk_list_item_widget_grab_focus;
|
2019-11-03 02:59:04 +00:00
|
|
|
widget_class->root = gtk_list_item_widget_root;
|
|
|
|
widget_class->unroot = gtk_list_item_widget_unroot;
|
2019-11-01 06:31:38 +00:00
|
|
|
|
2019-11-03 02:59:04 +00:00
|
|
|
gobject_class->set_property = gtk_list_item_widget_set_property;
|
2019-11-01 06:31:38 +00:00
|
|
|
gobject_class->dispose = gtk_list_item_widget_dispose;
|
|
|
|
|
2019-11-03 02:59:04 +00:00
|
|
|
properties[PROP_FACTORY] =
|
2022-05-11 12:19:39 +00:00
|
|
|
g_param_spec_object ("factory", NULL, NULL,
|
2019-11-03 02:59:04 +00:00
|
|
|
GTK_TYPE_LIST_ITEM_FACTORY,
|
|
|
|
G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
|
2019-12-14 01:56:32 +00:00
|
|
|
properties[PROP_SINGLE_CLICK_ACTIVATE] =
|
2022-05-11 12:19:39 +00:00
|
|
|
g_param_spec_boolean ("single-click-activate", NULL, NULL,
|
2019-12-14 01:56:32 +00:00
|
|
|
FALSE,
|
|
|
|
G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
|
2019-11-03 02:59:04 +00:00
|
|
|
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
|
|
|
|
2019-11-01 06:31:38 +00:00
|
|
|
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 (GtkListItemWidgetClass, activate_signal),
|
|
|
|
NULL, NULL,
|
|
|
|
NULL,
|
|
|
|
G_TYPE_NONE, 0);
|
|
|
|
|
2020-12-04 23:12:20 +00:00
|
|
|
gtk_widget_class_set_activate_signal (widget_class, signals[ACTIVATE_SIGNAL]);
|
2019-11-01 06:31:38 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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_widget_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_widget_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
|
2019-11-03 02:17:37 +00:00
|
|
|
gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture,
|
|
|
|
int n_press,
|
|
|
|
double x,
|
|
|
|
double y,
|
|
|
|
GtkListItemWidget *self)
|
2019-11-01 06:31:38 +00:00
|
|
|
{
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
2019-11-01 06:31:38 +00:00
|
|
|
GtkWidget *widget = GTK_WIDGET (self);
|
|
|
|
|
2019-11-03 02:34:56 +00:00
|
|
|
if (priv->list_item && !priv->list_item->selectable && !priv->list_item->activatable)
|
2019-11-01 06:31:38 +00:00
|
|
|
{
|
|
|
|
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-06 04:23:04 +00:00
|
|
|
if (!priv->list_item || priv->list_item->activatable)
|
|
|
|
{
|
2020-12-28 14:41:30 +00:00
|
|
|
if (n_press == 2 && !priv->single_click_activate)
|
2020-06-06 04:23:04 +00:00
|
|
|
{
|
2021-06-13 12:03:39 +00:00
|
|
|
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
|
2020-06-06 04:23:04 +00:00
|
|
|
gtk_widget_activate_action (GTK_WIDGET (self),
|
|
|
|
"list.activate-item",
|
|
|
|
"u",
|
|
|
|
priv->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);
|
|
|
|
}
|
2019-12-22 00:07:11 +00:00
|
|
|
|
2020-06-06 04:23:04 +00:00
|
|
|
static void
|
|
|
|
gtk_list_item_widget_click_gesture_released (GtkGestureClick *gesture,
|
|
|
|
int n_press,
|
|
|
|
double x,
|
|
|
|
double y,
|
|
|
|
GtkListItemWidget *self)
|
|
|
|
{
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
2021-06-13 12:02:06 +00:00
|
|
|
if (!priv->list_item || priv->list_item->activatable)
|
2020-12-28 14:41:30 +00:00
|
|
|
{
|
2021-06-13 12:02:06 +00:00
|
|
|
if (n_press == 1 && priv->single_click_activate)
|
|
|
|
{
|
2021-06-13 12:03:39 +00:00
|
|
|
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
|
2021-06-13 12:02:06 +00:00
|
|
|
gtk_widget_activate_action (GTK_WIDGET (self),
|
|
|
|
"list.activate-item",
|
|
|
|
"u",
|
|
|
|
priv->position);
|
|
|
|
return;
|
|
|
|
}
|
2020-12-28 14:41:30 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 04:23:04 +00:00
|
|
|
if (!priv->list_item || priv->list_item->selectable)
|
2019-11-01 06:31:38 +00:00
|
|
|
{
|
|
|
|
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 (GTK_WIDGET (self),
|
|
|
|
"list.select-item",
|
|
|
|
"(ubb)",
|
2019-11-03 02:34:56 +00:00
|
|
|
priv->position, modify, extend);
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 04:23:04 +00:00
|
|
|
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_enter_cb (GtkEventControllerFocus *controller,
|
|
|
|
GtkListItemWidget *self)
|
|
|
|
{
|
|
|
|
GtkWidget *widget = GTK_WIDGET (self);
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
2019-11-01 06:31:38 +00:00
|
|
|
|
|
|
|
gtk_widget_activate_action (widget,
|
|
|
|
"list.scroll-to-item",
|
|
|
|
"u",
|
2019-11-03 02:34:56 +00:00
|
|
|
priv->position);
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
|
|
|
|
2019-12-14 01:56:32 +00:00
|
|
|
static void
|
|
|
|
gtk_list_item_widget_hover_cb (GtkEventControllerMotion *controller,
|
|
|
|
double x,
|
|
|
|
double y,
|
|
|
|
GtkListItemWidget *self)
|
|
|
|
{
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
|
|
|
if (!priv->single_click_activate)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!priv->list_item || priv->list_item->selectable)
|
|
|
|
{
|
|
|
|
gtk_widget_activate_action (GTK_WIDGET (self),
|
|
|
|
"list.select-item",
|
|
|
|
"(ubb)",
|
|
|
|
priv->position, FALSE, FALSE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-01 06:31:38 +00:00
|
|
|
static void
|
|
|
|
gtk_list_item_widget_click_gesture_canceled (GtkGestureClick *gesture,
|
|
|
|
GdkEventSequence *sequence,
|
|
|
|
GtkListItemWidget *self)
|
|
|
|
{
|
|
|
|
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_init (GtkListItemWidget *self)
|
|
|
|
{
|
|
|
|
GtkEventController *controller;
|
|
|
|
GtkGesture *gesture;
|
|
|
|
|
|
|
|
gtk_widget_set_focusable (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_widget_click_gesture_pressed), self);
|
|
|
|
g_signal_connect (gesture, "released",
|
|
|
|
G_CALLBACK (gtk_list_item_widget_click_gesture_released), self);
|
|
|
|
g_signal_connect (gesture, "cancel",
|
|
|
|
G_CALLBACK (gtk_list_item_widget_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_widget_enter_cb), self);
|
|
|
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
2019-12-14 01:56:32 +00:00
|
|
|
|
|
|
|
controller = gtk_event_controller_motion_new ();
|
|
|
|
g_signal_connect (controller, "enter", G_CALLBACK (gtk_list_item_widget_hover_cb), self);
|
|
|
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
GtkWidget *
|
|
|
|
gtk_list_item_widget_new (GtkListItemFactory *factory,
|
2020-10-15 03:34:51 +00:00
|
|
|
const char *css_name,
|
|
|
|
GtkAccessibleRole role)
|
2019-11-01 06:31:38 +00:00
|
|
|
{
|
|
|
|
g_return_val_if_fail (css_name != NULL, NULL);
|
|
|
|
|
2019-11-03 02:59:04 +00:00
|
|
|
return g_object_new (GTK_TYPE_LIST_ITEM_WIDGET,
|
|
|
|
"css-name", css_name,
|
2020-10-15 03:34:51 +00:00
|
|
|
"accessible-role", role,
|
2019-11-03 02:59:04 +00:00
|
|
|
"factory", factory,
|
|
|
|
NULL);
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
|
|
|
|
2022-06-28 14:34:24 +00:00
|
|
|
typedef struct {
|
|
|
|
GtkListItemWidget *widget;
|
|
|
|
guint position;
|
|
|
|
gpointer item;
|
|
|
|
gboolean selected;
|
|
|
|
} GtkListItemWidgetUpdate;
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_widget_update_func (gpointer object,
|
|
|
|
gpointer data)
|
|
|
|
{
|
|
|
|
GtkListItemWidgetUpdate *update = data;
|
|
|
|
GtkListItem *list_item = object;
|
|
|
|
/* Track notify manually instead of freeze/thaw_notify for performance reasons. */
|
|
|
|
gboolean notify_item = FALSE, notify_position = FALSE, notify_selected = FALSE;
|
|
|
|
GtkListItemWidget *self = update->widget;
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
|
|
|
/* FIXME: It's kinda evil to notify external objects from here... */
|
|
|
|
|
|
|
|
if (g_set_object (&priv->item, update->item))
|
|
|
|
notify_item = TRUE;
|
|
|
|
|
|
|
|
if (priv->position != update->position)
|
|
|
|
{
|
|
|
|
priv->position = update->position;
|
|
|
|
notify_position = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (priv->selected != update->selected)
|
|
|
|
{
|
|
|
|
priv->selected = update->selected;
|
|
|
|
notify_selected = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (list_item)
|
|
|
|
gtk_list_item_do_notify (list_item, notify_item, notify_position, notify_selected);
|
|
|
|
}
|
|
|
|
|
2019-11-01 06:31:38 +00:00
|
|
|
void
|
|
|
|
gtk_list_item_widget_update (GtkListItemWidget *self,
|
|
|
|
guint position,
|
2019-11-02 17:56:36 +00:00
|
|
|
gpointer item,
|
2019-11-01 06:31:38 +00:00
|
|
|
gboolean selected)
|
|
|
|
{
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
2022-06-28 14:34:24 +00:00
|
|
|
GtkListItemWidgetUpdate update = { self, position, item, selected };
|
2022-05-07 01:36:22 +00:00
|
|
|
gboolean was_selected;
|
|
|
|
|
|
|
|
was_selected = priv->selected;
|
2019-11-03 02:17:37 +00:00
|
|
|
|
2019-11-03 02:59:04 +00:00
|
|
|
if (priv->list_item)
|
2022-06-28 14:34:24 +00:00
|
|
|
{
|
|
|
|
gtk_list_item_factory_update (priv->factory,
|
|
|
|
G_OBJECT (priv->list_item),
|
|
|
|
priv->item != NULL,
|
|
|
|
item != NULL,
|
|
|
|
gtk_list_item_widget_update_func,
|
|
|
|
&update);
|
|
|
|
}
|
2019-11-03 02:59:04 +00:00
|
|
|
else
|
2022-06-28 14:34:24 +00:00
|
|
|
{
|
|
|
|
gtk_list_item_widget_update_func (NULL, &update);
|
|
|
|
}
|
2019-11-01 06:31:38 +00:00
|
|
|
|
2022-05-07 01:36:22 +00:00
|
|
|
/* don't look at selected variable, it's not reentrancy safe */
|
|
|
|
if (was_selected != priv->selected)
|
|
|
|
{
|
|
|
|
if (priv->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);
|
|
|
|
|
|
|
|
gtk_accessible_update_state (GTK_ACCESSIBLE (self),
|
|
|
|
GTK_ACCESSIBLE_STATE_SELECTED, priv->selected,
|
|
|
|
-1);
|
|
|
|
}
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
|
|
|
|
2019-11-03 02:59:04 +00:00
|
|
|
void
|
|
|
|
gtk_list_item_widget_set_factory (GtkListItemWidget *self,
|
|
|
|
GtkListItemFactory *factory)
|
|
|
|
{
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
|
|
|
if (priv->factory == factory)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (priv->factory)
|
|
|
|
{
|
|
|
|
if (priv->list_item)
|
2022-06-28 14:34:24 +00:00
|
|
|
gtk_list_item_widget_teardown_factory (self);
|
2019-11-03 02:59:04 +00:00
|
|
|
g_clear_object (&priv->factory);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (factory)
|
|
|
|
{
|
|
|
|
priv->factory = g_object_ref (factory);
|
|
|
|
|
|
|
|
if (gtk_widget_get_root (GTK_WIDGET (self)))
|
2022-06-28 14:34:24 +00:00
|
|
|
gtk_list_item_widget_setup_factory (self);
|
2019-11-03 02:34:56 +00:00
|
|
|
}
|
2019-11-03 02:59:04 +00:00
|
|
|
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
|
2019-11-03 01:23:46 +00:00
|
|
|
}
|
|
|
|
|
2019-12-14 01:56:32 +00:00
|
|
|
void
|
|
|
|
gtk_list_item_widget_set_single_click_activate (GtkListItemWidget *self,
|
|
|
|
gboolean single_click_activate)
|
|
|
|
{
|
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
|
|
|
if (priv->single_click_activate == single_click_activate)
|
|
|
|
return;
|
|
|
|
|
|
|
|
priv->single_click_activate = single_click_activate;
|
|
|
|
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SINGLE_CLICK_ACTIVATE]);
|
|
|
|
}
|
|
|
|
|
2021-05-17 13:05:05 +00:00
|
|
|
void
|
|
|
|
gtk_list_item_widget_set_activatable (GtkListItemWidget *self,
|
|
|
|
gboolean activatable)
|
|
|
|
{
|
|
|
|
if (activatable)
|
|
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "activatable");
|
|
|
|
else
|
|
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "activatable");
|
|
|
|
}
|
|
|
|
|
2019-11-01 06:31:38 +00:00
|
|
|
void
|
|
|
|
gtk_list_item_widget_add_child (GtkListItemWidget *self,
|
|
|
|
GtkWidget *child)
|
|
|
|
{
|
|
|
|
gtk_widget_set_parent (child, GTK_WIDGET (self));
|
|
|
|
}
|
|
|
|
|
2020-06-05 16:39:00 +00:00
|
|
|
void
|
|
|
|
gtk_list_item_widget_reorder_child (GtkListItemWidget *self,
|
|
|
|
GtkWidget *child,
|
|
|
|
guint position)
|
|
|
|
{
|
|
|
|
GtkWidget *widget = GTK_WIDGET (self);
|
|
|
|
GtkWidget *sibling = NULL;
|
|
|
|
|
|
|
|
if (position > 0)
|
|
|
|
{
|
|
|
|
GtkWidget *c;
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
for (c = gtk_widget_get_first_child (widget), i = 0;
|
|
|
|
c;
|
|
|
|
c = gtk_widget_get_next_sibling (c), i++)
|
|
|
|
{
|
|
|
|
if (i + 1 == position)
|
|
|
|
{
|
|
|
|
sibling = c;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-24 02:53:21 +00:00
|
|
|
if (child != sibling)
|
2020-10-20 01:54:07 +00:00
|
|
|
gtk_widget_insert_after (child, widget, sibling);
|
2020-06-05 16:39:00 +00:00
|
|
|
}
|
|
|
|
|
2019-11-01 06:31:38 +00:00
|
|
|
void
|
|
|
|
gtk_list_item_widget_remove_child (GtkListItemWidget *self,
|
|
|
|
GtkWidget *child)
|
|
|
|
{
|
|
|
|
gtk_widget_unparent (child);
|
|
|
|
}
|
|
|
|
|
2019-11-03 01:23:46 +00:00
|
|
|
GtkListItem *
|
|
|
|
gtk_list_item_widget_get_list_item (GtkListItemWidget *self)
|
|
|
|
{
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
|
|
|
return priv->list_item;
|
2019-11-03 01:23:46 +00:00
|
|
|
}
|
|
|
|
|
2019-11-01 06:31:38 +00:00
|
|
|
guint
|
|
|
|
gtk_list_item_widget_get_position (GtkListItemWidget *self)
|
|
|
|
{
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
2019-11-03 02:34:56 +00:00
|
|
|
return priv->position;
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
gpointer
|
|
|
|
gtk_list_item_widget_get_item (GtkListItemWidget *self)
|
|
|
|
{
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
2019-11-03 02:34:56 +00:00
|
|
|
return priv->item;
|
2019-11-01 06:31:38 +00:00
|
|
|
}
|
|
|
|
|
2019-11-02 17:56:36 +00:00
|
|
|
gboolean
|
|
|
|
gtk_list_item_widget_get_selected (GtkListItemWidget *self)
|
|
|
|
{
|
2019-11-03 02:17:37 +00:00
|
|
|
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
|
|
|
|
2019-11-03 02:34:56 +00:00
|
|
|
return priv->selected;
|
2019-11-02 17:56:36 +00:00
|
|
|
}
|
|
|
|
|