forked from AuroraMiddleware/gtk
9927d9bda2
It's a GtkListItemWidget subclass that tracks the column it belongs to and allows the column to track it. We also use this subclass to implement sizing support so columns share the same size and get resized in sync.
530 lines
16 KiB
C
530 lines
16 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 "gtkcolumnviewprivate.h"
|
|
|
|
#include "gtkboxlayout.h"
|
|
#include "gtkbuildable.h"
|
|
#include "gtkcolumnlistitemfactoryprivate.h"
|
|
#include "gtkcolumnviewcolumnprivate.h"
|
|
#include "gtkintl.h"
|
|
#include "gtklistview.h"
|
|
#include "gtkmain.h"
|
|
#include "gtkprivate.h"
|
|
#include "gtkscrollable.h"
|
|
#include "gtkwidgetprivate.h"
|
|
|
|
/**
|
|
* SECTION:gtkcolumnview
|
|
* @title: GtkColumnView
|
|
* @short_description: A widget for displaying lists in multiple columns
|
|
* @see_also: #GtkColumnViewColumn, #GtkTreeView
|
|
*
|
|
* GtkColumnView is a widget to present a view into a large dynamic list of items
|
|
* using multiple columns.
|
|
*/
|
|
|
|
struct _GtkColumnView
|
|
{
|
|
GtkWidget parent_instance;
|
|
|
|
GListStore *columns;
|
|
|
|
GtkListView *listview;
|
|
GtkColumnListItemFactory *factory;
|
|
};
|
|
|
|
struct _GtkColumnViewClass
|
|
{
|
|
GtkWidgetClass parent_class;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_COLUMNS,
|
|
PROP_HADJUSTMENT,
|
|
PROP_HSCROLL_POLICY,
|
|
PROP_MODEL,
|
|
PROP_SHOW_SEPARATORS,
|
|
PROP_VADJUSTMENT,
|
|
PROP_VSCROLL_POLICY,
|
|
|
|
N_PROPS
|
|
};
|
|
|
|
enum {
|
|
ACTIVATE,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static GtkBuildableIface *parent_buildable_iface;
|
|
|
|
static void
|
|
gtk_column_view_buildable_add_child (GtkBuildable *buildable,
|
|
GtkBuilder *builder,
|
|
GObject *child,
|
|
const gchar *type)
|
|
{
|
|
if (GTK_IS_COLUMN_VIEW_COLUMN (child))
|
|
{
|
|
if (type != NULL)
|
|
{
|
|
GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type);
|
|
}
|
|
else
|
|
{
|
|
gtk_column_view_append_column (GTK_COLUMN_VIEW (buildable),
|
|
GTK_COLUMN_VIEW_COLUMN (child));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
parent_buildable_iface->add_child (buildable, builder, child, type);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_buildable_interface_init (GtkBuildableIface *iface)
|
|
{
|
|
parent_buildable_iface = g_type_interface_peek_parent (iface);
|
|
|
|
iface->add_child = gtk_column_view_buildable_add_child;
|
|
}
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GtkColumnView, gtk_column_view, GTK_TYPE_WIDGET,
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_column_view_buildable_interface_init)
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
|
|
|
|
static GParamSpec *properties[N_PROPS] = { NULL, };
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static void
|
|
gtk_column_view_activate_cb (GtkListView *listview,
|
|
guint pos,
|
|
GtkColumnView *self)
|
|
{
|
|
g_signal_emit (self, signals[ACTIVATE], 0, pos);
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_dispose (GObject *object)
|
|
{
|
|
GtkColumnView *self = GTK_COLUMN_VIEW (object);
|
|
|
|
while (g_list_model_get_n_items (G_LIST_MODEL (self->columns)) > 0)
|
|
{
|
|
GtkColumnViewColumn *column = g_list_model_get_item (G_LIST_MODEL (self->columns), 0);
|
|
gtk_column_view_remove_column (self, column);
|
|
g_object_unref (column);
|
|
}
|
|
|
|
g_clear_pointer ((GtkWidget **) &self->listview, gtk_widget_unparent);
|
|
g_clear_object (&self->factory);
|
|
|
|
G_OBJECT_CLASS (gtk_column_view_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_finalize (GObject *object)
|
|
{
|
|
GtkColumnView *self = GTK_COLUMN_VIEW (object);
|
|
|
|
g_object_unref (self->columns);
|
|
|
|
G_OBJECT_CLASS (gtk_column_view_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkColumnView *self = GTK_COLUMN_VIEW (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_COLUMNS:
|
|
g_value_set_object (value, self->columns);
|
|
break;
|
|
|
|
case PROP_HADJUSTMENT:
|
|
g_value_set_object (value, gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (self->listview)));
|
|
break;
|
|
|
|
case PROP_HSCROLL_POLICY:
|
|
g_value_set_enum (value, gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview)));
|
|
break;
|
|
|
|
case PROP_MODEL:
|
|
g_value_set_object (value, gtk_list_view_get_model (self->listview));
|
|
break;
|
|
|
|
case PROP_SHOW_SEPARATORS:
|
|
g_value_set_boolean (value, gtk_list_view_get_show_separators (self->listview));
|
|
break;
|
|
|
|
case PROP_VADJUSTMENT:
|
|
g_value_set_object (value, gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self->listview)));
|
|
break;
|
|
|
|
case PROP_VSCROLL_POLICY:
|
|
g_value_set_enum (value, gtk_scrollable_get_vscroll_policy (GTK_SCROLLABLE (self->listview)));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkColumnView *self = GTK_COLUMN_VIEW (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_HADJUSTMENT:
|
|
if (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (self->listview)) != g_value_get_object (value))
|
|
{
|
|
gtk_scrollable_set_hadjustment (GTK_SCROLLABLE (self->listview), g_value_get_object (value));
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HADJUSTMENT]);
|
|
}
|
|
break;
|
|
|
|
case PROP_HSCROLL_POLICY:
|
|
if (gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview)) != g_value_get_enum (value))
|
|
{
|
|
gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (self->listview), g_value_get_enum (value));
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HSCROLL_POLICY]);
|
|
}
|
|
break;
|
|
|
|
case PROP_MODEL:
|
|
gtk_column_view_set_model (self, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_SHOW_SEPARATORS:
|
|
gtk_column_view_set_show_separators (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_VADJUSTMENT:
|
|
if (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self->listview)) != g_value_get_object (value))
|
|
{
|
|
gtk_scrollable_set_vadjustment (GTK_SCROLLABLE (self->listview), g_value_get_object (value));
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VADJUSTMENT]);
|
|
}
|
|
break;
|
|
|
|
case PROP_VSCROLL_POLICY:
|
|
if (gtk_scrollable_get_vscroll_policy (GTK_SCROLLABLE (self->listview)) != g_value_get_enum (value))
|
|
{
|
|
gtk_scrollable_set_vscroll_policy (GTK_SCROLLABLE (self->listview), g_value_get_enum (value));
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VSCROLL_POLICY]);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_class_init (GtkColumnViewClass *klass)
|
|
{
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
gpointer iface;
|
|
|
|
gobject_class->dispose = gtk_column_view_dispose;
|
|
gobject_class->finalize = gtk_column_view_finalize;
|
|
gobject_class->get_property = gtk_column_view_get_property;
|
|
gobject_class->set_property = gtk_column_view_set_property;
|
|
|
|
/* GtkScrollable implementation */
|
|
iface = g_type_default_interface_peek (GTK_TYPE_SCROLLABLE);
|
|
properties[PROP_HADJUSTMENT] =
|
|
g_param_spec_override ("hadjustment",
|
|
g_object_interface_find_property (iface, "hadjustment"));
|
|
properties[PROP_HSCROLL_POLICY] =
|
|
g_param_spec_override ("hscroll-policy",
|
|
g_object_interface_find_property (iface, "hscroll-policy"));
|
|
properties[PROP_VADJUSTMENT] =
|
|
g_param_spec_override ("vadjustment",
|
|
g_object_interface_find_property (iface, "vadjustment"));
|
|
properties[PROP_VSCROLL_POLICY] =
|
|
g_param_spec_override ("vscroll-policy",
|
|
g_object_interface_find_property (iface, "vscroll-policy"));
|
|
|
|
/**
|
|
* GtkColumnView:columns:
|
|
*
|
|
* The list of columns
|
|
*/
|
|
properties[PROP_COLUMNS] =
|
|
g_param_spec_object ("columns",
|
|
P_("Columns"),
|
|
P_("List of columns"),
|
|
G_TYPE_LIST_MODEL,
|
|
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GtkColumnView:model:
|
|
*
|
|
* Model for the items displayed
|
|
*/
|
|
properties[PROP_MODEL] =
|
|
g_param_spec_object ("model",
|
|
P_("Model"),
|
|
P_("Model for the items displayed"),
|
|
G_TYPE_LIST_MODEL,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GtkColumnView:show-separators:
|
|
*
|
|
* Show separators between rows
|
|
*/
|
|
properties[PROP_SHOW_SEPARATORS] =
|
|
g_param_spec_boolean ("show-separators",
|
|
P_("Show separators"),
|
|
P_("Show separators between rows"),
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
|
|
|
/**
|
|
* GtkColumnView::activate:
|
|
* @self: The #GtkColumnView
|
|
* @position: position of item to activate
|
|
*
|
|
* The ::activate signal is emitted when a row has been activated by the user,
|
|
* usually via activating the GtkListBase|list.activate-item action.
|
|
*
|
|
* This allows for a convenient way to handle activation in a columnview.
|
|
* See gtk_list_item_set_activatable() for details on how to use this signal.
|
|
*/
|
|
signals[ACTIVATE] =
|
|
g_signal_new (I_("activate"),
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_UINT);
|
|
g_signal_set_va_marshaller (signals[ACTIVATE],
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
g_cclosure_marshal_VOID__UINTv);
|
|
|
|
gtk_widget_class_set_css_name (widget_class, I_("columnview"));
|
|
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_init (GtkColumnView *self)
|
|
{
|
|
self->columns = g_list_store_new (GTK_TYPE_COLUMN_VIEW_COLUMN);
|
|
|
|
self->factory = gtk_column_list_item_factory_new (self);
|
|
self->listview = GTK_LIST_VIEW (gtk_list_view_new_with_factory (
|
|
GTK_LIST_ITEM_FACTORY (g_object_ref (self->factory))));
|
|
gtk_widget_set_hexpand (GTK_WIDGET (self->listview), TRUE);
|
|
gtk_widget_set_vexpand (GTK_WIDGET (self->listview), TRUE);
|
|
g_signal_connect (self->listview, "activate", G_CALLBACK (gtk_column_view_activate_cb), self);
|
|
gtk_widget_set_parent (GTK_WIDGET (self->listview), GTK_WIDGET (self));
|
|
|
|
gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_new:
|
|
*
|
|
* Creates a new empty #GtkColumnView.
|
|
*
|
|
* You most likely want to call gtk_column_view_set_factory() to
|
|
* set up a way to map its items to widgets and gtk_column_view_set_model()
|
|
* to set a model to provide items next.
|
|
*
|
|
* Returns: a new #GtkColumnView
|
|
**/
|
|
GtkWidget *
|
|
gtk_column_view_new (void)
|
|
{
|
|
return g_object_new (GTK_TYPE_COLUMN_VIEW, NULL);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_get_model:
|
|
* @self: a #GtkColumnView
|
|
*
|
|
* Gets the model that's currently used to read the items displayed.
|
|
*
|
|
* Returns: (nullable) (transfer none): The model in use
|
|
**/
|
|
GListModel *
|
|
gtk_column_view_get_model (GtkColumnView *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL);
|
|
|
|
return gtk_list_view_get_model (self->listview);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_set_model:
|
|
* @self: a #GtkColumnView
|
|
* @model: (allow-none) (transfer none): the model to use or %NULL for none
|
|
*
|
|
* Sets the #GListModel to use.
|
|
*
|
|
* If the @model is a #GtkSelectionModel, it is used for managing the selection.
|
|
* Otherwise, @self creates a #GtkSingleSelection for the selection.
|
|
**/
|
|
void
|
|
gtk_column_view_set_model (GtkColumnView *self,
|
|
GListModel *model)
|
|
{
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
|
|
|
|
if (gtk_list_view_get_model (self->listview) == model)
|
|
return;
|
|
|
|
gtk_list_view_set_model (self->listview, model);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_get_columns:
|
|
* @self: a #GtkColumnView
|
|
*
|
|
* Gets the list of columns in this column view. This list is constant over
|
|
* the lifetime of @self and can be used to monitor changes to the columns
|
|
* of @self by connecting to the GListModel:items-changed signal.
|
|
*
|
|
* Returns: (transfer none): The list managing the columns
|
|
**/
|
|
GListModel *
|
|
gtk_column_view_get_columns (GtkColumnView *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL);
|
|
|
|
return G_LIST_MODEL (self->columns);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_set_show_separators:
|
|
* @self: a #GtkColumnView
|
|
* @show_separators: %TRUE to show separators
|
|
*
|
|
* Sets whether the list should show separators
|
|
* between rows.
|
|
*/
|
|
void
|
|
gtk_column_view_set_show_separators (GtkColumnView *self,
|
|
gboolean show_separators)
|
|
{
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
|
|
if (gtk_list_view_get_show_separators (self->listview) == show_separators)
|
|
return;
|
|
|
|
gtk_list_view_set_show_separators (self->listview, show_separators);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SEPARATORS]);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_get_show_separators:
|
|
* @self: a #GtkColumnView
|
|
*
|
|
* Returns whether the list box should show separators
|
|
* between rows.
|
|
*
|
|
* Returns: %TRUE if the list box shows separators
|
|
*/
|
|
gboolean
|
|
gtk_column_view_get_show_separators (GtkColumnView *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
|
|
|
|
return gtk_list_view_get_show_separators (self->listview);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_append_column:
|
|
* @self: a #GtkColumnView
|
|
* @column: a #GtkColumnViewColumn that hasn't been added to a
|
|
* #GtkColumnView yet
|
|
*
|
|
* Appends the @column to the end of the columns in @self.
|
|
**/
|
|
void
|
|
gtk_column_view_append_column (GtkColumnView *self,
|
|
GtkColumnViewColumn *column)
|
|
{
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column));
|
|
g_return_if_fail (gtk_column_view_column_get_column_view (column) == NULL);
|
|
|
|
gtk_column_view_column_set_column_view (column, self);
|
|
g_list_store_append (self->columns, column);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_remove_column:
|
|
* @self: a #GtkColumnView
|
|
* @column: a #GtkColumnViewColumn that's part of @self
|
|
*
|
|
* Removes the @column from the list of columns of @self.
|
|
**/
|
|
void
|
|
gtk_column_view_remove_column (GtkColumnView *self,
|
|
GtkColumnViewColumn *column)
|
|
{
|
|
guint i;
|
|
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column));
|
|
g_return_if_fail (gtk_column_view_column_get_column_view (column) == self);
|
|
|
|
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++)
|
|
{
|
|
GtkColumnViewColumn *item = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
|
|
g_object_unref (item);
|
|
if (item == column)
|
|
break;
|
|
}
|
|
|
|
gtk_column_view_column_set_column_view (column, NULL);
|
|
g_list_store_remove (self->columns, i);
|
|
}
|
|
|