forked from AuroraMiddleware/gtk
9cb8d21cb5
For the same reasoning as the preceding commit. Also don't make GtkColumnView focusable. Its internal list view is already focusable, which is enough to take care of the empty view case.
1821 lines
55 KiB
C
1821 lines
55 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 "gtkcolumnviewlayoutprivate.h"
|
|
#include "gtkcolumnviewsorterprivate.h"
|
|
#include "gtkcssnodeprivate.h"
|
|
#include "gtkdropcontrollermotion.h"
|
|
#include "gtklistviewprivate.h"
|
|
#include "gtkmain.h"
|
|
#include "gtkprivate.h"
|
|
#include "gtkscrollable.h"
|
|
#include "gtkwidgetprivate.h"
|
|
#include "gtksizerequest.h"
|
|
#include "gtkadjustment.h"
|
|
#include "gtkgesturedrag.h"
|
|
#include "gtkeventcontrollermotion.h"
|
|
#include "gtkdragsourceprivate.h"
|
|
#include "gtkeventcontrollerkey.h"
|
|
#include "gtkgestureclick.h"
|
|
|
|
/**
|
|
* GtkColumnView:
|
|
*
|
|
* `GtkColumnView` presents a large dynamic list of items using multiple columns
|
|
* with headers.
|
|
*
|
|
* `GtkColumnView` uses the factories of its columns to generate a cell widget for
|
|
* each column, for each visible item and displays them together as the row for
|
|
* this item.
|
|
*
|
|
* The [property@Gtk.ColumnView:show-row-separators] and
|
|
* [property@Gtk.ColumnView:show-column-separators] properties offer a simple way
|
|
* to display separators between the rows or columns.
|
|
*
|
|
* `GtkColumnView` allows the user to select items according to the selection
|
|
* characteristics of the model. For models that allow multiple selected items,
|
|
* it is possible to turn on *rubberband selection*, using
|
|
* [property@Gtk.ColumnView:enable-rubberband].
|
|
*
|
|
* The column view supports sorting that can be customized by the user by
|
|
* clicking on column headers. To set this up, the `GtkSorter` returned by
|
|
* [method@Gtk.ColumnView.get_sorter] must be attached to a sort model for the
|
|
* data that the view is showing, and the columns must have sorters attached to
|
|
* them by calling [method@Gtk.ColumnViewColumn.set_sorter]. The initial sort
|
|
* order can be set with [method@Gtk.ColumnView.sort_by_column].
|
|
*
|
|
* The column view also supports interactive resizing and reordering of
|
|
* columns, via Drag-and-Drop of the column headers. This can be enabled or
|
|
* disabled with the [property@Gtk.ColumnView:reorderable] and
|
|
* [property@Gtk.ColumnViewColumn:resizable] properties.
|
|
*
|
|
* To learn more about the list widget framework, see the
|
|
* [overview](section-list-widget.html).
|
|
*
|
|
* # CSS nodes
|
|
*
|
|
* ```
|
|
* columnview[.column-separators][.rich-list][.navigation-sidebar][.data-table]
|
|
* ├── header
|
|
* │ ├── <column header>
|
|
* ┊ ┊
|
|
* │ ╰── <column header>
|
|
* │
|
|
* ├── listview
|
|
* │
|
|
* ┊
|
|
* ╰── [rubberband]
|
|
* ```
|
|
*
|
|
* `GtkColumnView` uses a single CSS node named columnview. It may carry the
|
|
* .column-separators style class, when [property@Gtk.ColumnView:show-column-separators]
|
|
* property is set. Header widgets appear below a node with name header.
|
|
* The rows are contained in a `GtkListView` widget, so there is a listview
|
|
* node with the same structure as for a standalone `GtkListView` widget.
|
|
* If [property@Gtk.ColumnView:show-row-separators] is set, it will be passed
|
|
* on to the list view, causing its CSS node to carry the .separators style class.
|
|
* For rubberband selection, a node with name rubberband is used.
|
|
*
|
|
* The main columnview node may also carry style classes to select
|
|
* the style of [list presentation](section-list-widget.html#list-styles):
|
|
* .rich-list, .navigation-sidebar or .data-table.
|
|
*
|
|
* # Accessibility
|
|
*
|
|
* `GtkColumnView` uses the %GTK_ACCESSIBLE_ROLE_TREE_GRID role, header title
|
|
* widgets are using the %GTK_ACCESSIBLE_ROLE_COLUMN_HEADER role. The row widgets
|
|
* are using the %GTK_ACCESSIBLE_ROLE_ROW role, and individual cells are using
|
|
* the %GTK_ACCESSIBLE_ROLE_GRID_CELL role
|
|
*/
|
|
|
|
/* We create a subclass of GtkListView for the sole purpose of overriding
|
|
* some parameters for item creation.
|
|
*/
|
|
|
|
#define GTK_TYPE_COLUMN_LIST_VIEW (gtk_column_list_view_get_type ())
|
|
G_DECLARE_FINAL_TYPE (GtkColumnListView, gtk_column_list_view, GTK, COLUMN_LIST_VIEW, GtkListView)
|
|
|
|
struct _GtkColumnListView
|
|
{
|
|
GtkListView parent_instance;
|
|
};
|
|
|
|
struct _GtkColumnListViewClass
|
|
{
|
|
GtkListViewClass parent_class;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GtkColumnListView, gtk_column_list_view, GTK_TYPE_LIST_VIEW)
|
|
|
|
static void
|
|
gtk_column_list_view_init (GtkColumnListView *view)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gtk_column_list_view_class_init (GtkColumnListViewClass *klass)
|
|
{
|
|
GtkListBaseClass *list_base_class = GTK_LIST_BASE_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
|
|
list_base_class->list_item_name = "row";
|
|
list_base_class->list_item_role = GTK_ACCESSIBLE_ROLE_ROW;
|
|
|
|
gtk_widget_class_set_css_name (widget_class, I_("listview"));
|
|
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST);
|
|
}
|
|
|
|
|
|
struct _GtkColumnView
|
|
{
|
|
GtkWidget parent_instance;
|
|
|
|
GListStore *columns;
|
|
|
|
GtkWidget *header;
|
|
|
|
GtkListView *listview;
|
|
GtkColumnListItemFactory *factory;
|
|
|
|
GtkSorter *sorter;
|
|
|
|
GtkAdjustment *hadjustment;
|
|
|
|
guint reorderable : 1;
|
|
guint show_column_separators : 1;
|
|
guint in_column_resize : 1;
|
|
guint in_column_reorder : 1;
|
|
|
|
int drag_pos;
|
|
int drag_x;
|
|
int drag_offset;
|
|
int drag_column_x;
|
|
|
|
guint autoscroll_id;
|
|
double autoscroll_x;
|
|
double autoscroll_delta;
|
|
|
|
GtkGesture *drag_gesture;
|
|
};
|
|
|
|
struct _GtkColumnViewClass
|
|
{
|
|
GtkWidgetClass parent_class;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_COLUMNS,
|
|
PROP_HADJUSTMENT,
|
|
PROP_HSCROLL_POLICY,
|
|
PROP_MODEL,
|
|
PROP_SHOW_ROW_SEPARATORS,
|
|
PROP_SHOW_COLUMN_SEPARATORS,
|
|
PROP_SORTER,
|
|
PROP_VADJUSTMENT,
|
|
PROP_VSCROLL_POLICY,
|
|
PROP_SINGLE_CLICK_ACTIVATE,
|
|
PROP_REORDERABLE,
|
|
PROP_ENABLE_RUBBERBAND,
|
|
|
|
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 char *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;
|
|
}
|
|
|
|
static gboolean
|
|
gtk_column_view_scrollable_get_border (GtkScrollable *scrollable,
|
|
GtkBorder *border)
|
|
{
|
|
GtkColumnView *self = GTK_COLUMN_VIEW (scrollable);
|
|
|
|
border->top = gtk_widget_get_height (self->header);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_scrollable_interface_init (GtkScrollableInterface *iface)
|
|
{
|
|
iface->get_border = gtk_column_view_scrollable_get_border;
|
|
}
|
|
|
|
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, gtk_column_view_scrollable_interface_init))
|
|
|
|
static GParamSpec *properties[N_PROPS] = { NULL, };
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static void
|
|
gtk_column_view_measure (GtkWidget *widget,
|
|
GtkOrientation orientation,
|
|
int for_size,
|
|
int *minimum,
|
|
int *natural,
|
|
int *minimum_baseline,
|
|
int *natural_baseline)
|
|
{
|
|
GtkColumnView *self = GTK_COLUMN_VIEW (widget);
|
|
|
|
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
{
|
|
gtk_column_view_measure_across (self, minimum, natural);
|
|
}
|
|
else
|
|
{
|
|
int header_min, header_nat, list_min, list_nat;
|
|
|
|
gtk_widget_measure (GTK_WIDGET (self->header),
|
|
orientation, for_size,
|
|
&header_min, &header_nat,
|
|
NULL, NULL);
|
|
gtk_widget_measure (GTK_WIDGET (self->listview),
|
|
orientation, for_size,
|
|
&list_min, &list_nat,
|
|
NULL, NULL);
|
|
*minimum = header_min + list_min;
|
|
*natural = header_nat + list_nat;
|
|
}
|
|
}
|
|
|
|
void
|
|
gtk_column_view_distribute_width (GtkColumnView *self,
|
|
int width,
|
|
GtkRequestedSize *sizes)
|
|
{
|
|
GtkScrollablePolicy scroll_policy;
|
|
int col_min, col_nat, extra, col_size;
|
|
int n, n_expand, expand_size, n_extra;
|
|
guint i;
|
|
|
|
n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
|
|
n_expand = 0;
|
|
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
GtkColumnViewColumn *column;
|
|
|
|
column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
if (gtk_column_view_column_get_visible (column))
|
|
{
|
|
gtk_column_view_column_measure (column, &sizes[i].minimum_size, &sizes[i].natural_size);
|
|
if (gtk_column_view_column_get_expand (column))
|
|
n_expand++;
|
|
}
|
|
else
|
|
sizes[i].minimum_size = sizes[i].natural_size = 0;
|
|
g_object_unref (column);
|
|
}
|
|
|
|
gtk_column_view_measure_across (self, &col_min, &col_nat);
|
|
|
|
scroll_policy = gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview));
|
|
if (scroll_policy == GTK_SCROLL_MINIMUM)
|
|
extra = MAX (width - col_min, 0);
|
|
else
|
|
extra = MAX (width - col_min, col_nat - col_min);
|
|
|
|
extra = gtk_distribute_natural_allocation (extra, n, sizes);
|
|
if (n_expand > 0)
|
|
{
|
|
expand_size = extra / n_expand;
|
|
n_extra = extra % n_expand;
|
|
}
|
|
else
|
|
expand_size = n_extra = 0;
|
|
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
GtkColumnViewColumn *column;
|
|
|
|
column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
if (gtk_column_view_column_get_visible (column))
|
|
{
|
|
col_size = sizes[i].minimum_size;
|
|
if (gtk_column_view_column_get_expand (column))
|
|
{
|
|
col_size += expand_size;
|
|
if (n_extra > 0)
|
|
{
|
|
col_size++;
|
|
n_extra--;
|
|
}
|
|
}
|
|
sizes[i].minimum_size = col_size;
|
|
}
|
|
|
|
g_object_unref (column);
|
|
}
|
|
}
|
|
|
|
static int
|
|
gtk_column_view_allocate_columns (GtkColumnView *self,
|
|
int width)
|
|
{
|
|
gboolean rtl;
|
|
guint i, n;
|
|
int total_width, x;
|
|
GtkRequestedSize *sizes;
|
|
|
|
rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
|
|
|
|
n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
|
|
|
|
sizes = g_newa (GtkRequestedSize, n);
|
|
|
|
gtk_column_view_distribute_width (self, width, sizes);
|
|
|
|
total_width = 0;
|
|
for (i = 0; i < n; i++)
|
|
total_width += sizes[i].minimum_size;
|
|
|
|
x = rtl ? total_width : 0;
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
GtkColumnViewColumn *column;
|
|
int col_size;
|
|
|
|
column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
if (gtk_column_view_column_get_visible (column))
|
|
{
|
|
col_size = sizes[i].minimum_size;
|
|
|
|
if (rtl)
|
|
x -= col_size;
|
|
|
|
gtk_column_view_column_allocate (column, x, col_size);
|
|
if (self->in_column_reorder && i == self->drag_pos)
|
|
gtk_column_view_column_set_header_position (column, self->drag_x);
|
|
|
|
if (!rtl)
|
|
x += col_size;
|
|
}
|
|
|
|
g_object_unref (column);
|
|
}
|
|
|
|
return total_width;
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_allocate (GtkWidget *widget,
|
|
int width,
|
|
int height,
|
|
int baseline)
|
|
{
|
|
GtkColumnView *self = GTK_COLUMN_VIEW (widget);
|
|
int full_width, header_height, min, nat, x;
|
|
|
|
x = gtk_adjustment_get_value (self->hadjustment);
|
|
full_width = gtk_column_view_allocate_columns (self, width);
|
|
|
|
gtk_widget_measure (self->header, GTK_ORIENTATION_VERTICAL, full_width, &min, &nat, NULL, NULL);
|
|
if (gtk_scrollable_get_vscroll_policy (GTK_SCROLLABLE (self->listview)) == GTK_SCROLL_MINIMUM)
|
|
header_height = min;
|
|
else
|
|
header_height = nat;
|
|
gtk_widget_allocate (self->header, full_width, header_height, -1,
|
|
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (-x, 0)));
|
|
|
|
gtk_widget_allocate (GTK_WIDGET (self->listview),
|
|
full_width, height - header_height, -1,
|
|
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (-x, header_height)));
|
|
|
|
gtk_adjustment_configure (self->hadjustment, x, 0, full_width, width * 0.1, width * 0.9, width);
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_activate_cb (GtkListView *listview,
|
|
guint pos,
|
|
GtkColumnView *self)
|
|
{
|
|
g_signal_emit (self, signals[ACTIVATE], 0, pos);
|
|
}
|
|
|
|
static void
|
|
adjustment_value_changed_cb (GtkAdjustment *adjustment,
|
|
GtkColumnView *self)
|
|
{
|
|
gtk_widget_queue_allocate (GTK_WIDGET (self));
|
|
}
|
|
|
|
static void
|
|
clear_adjustment (GtkColumnView *self)
|
|
{
|
|
if (self->hadjustment == NULL)
|
|
return;
|
|
|
|
g_signal_handlers_disconnect_by_func (self->hadjustment, adjustment_value_changed_cb, self);
|
|
g_clear_object (&self->hadjustment);
|
|
}
|
|
|
|
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 (&self->header, gtk_widget_unparent);
|
|
|
|
g_clear_pointer ((GtkWidget **) &self->listview, gtk_widget_unparent);
|
|
g_clear_object (&self->factory);
|
|
|
|
g_clear_object (&self->sorter);
|
|
clear_adjustment (self);
|
|
|
|
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, self->hadjustment);
|
|
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_ROW_SEPARATORS:
|
|
g_value_set_boolean (value, gtk_list_view_get_show_separators (self->listview));
|
|
break;
|
|
|
|
case PROP_SHOW_COLUMN_SEPARATORS:
|
|
g_value_set_boolean (value, self->show_column_separators);
|
|
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;
|
|
|
|
case PROP_SORTER:
|
|
g_value_set_object (value, self->sorter);
|
|
break;
|
|
|
|
case PROP_SINGLE_CLICK_ACTIVATE:
|
|
g_value_set_boolean (value, gtk_column_view_get_single_click_activate (self));
|
|
break;
|
|
|
|
case PROP_REORDERABLE:
|
|
g_value_set_boolean (value, gtk_column_view_get_reorderable (self));
|
|
break;
|
|
|
|
case PROP_ENABLE_RUBBERBAND:
|
|
g_value_set_boolean (value, gtk_column_view_get_enable_rubberband (self));
|
|
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);
|
|
GtkAdjustment *adjustment;
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_HADJUSTMENT:
|
|
adjustment = g_value_get_object (value);
|
|
if (adjustment == NULL)
|
|
adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
|
g_object_ref_sink (adjustment);
|
|
|
|
if (self->hadjustment != adjustment)
|
|
{
|
|
clear_adjustment (self);
|
|
|
|
self->hadjustment = adjustment;
|
|
|
|
g_signal_connect (adjustment, "value-changed", G_CALLBACK (adjustment_value_changed_cb), self);
|
|
|
|
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_ROW_SEPARATORS:
|
|
gtk_column_view_set_show_row_separators (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_SHOW_COLUMN_SEPARATORS:
|
|
gtk_column_view_set_show_column_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;
|
|
|
|
case PROP_SINGLE_CLICK_ACTIVATE:
|
|
gtk_column_view_set_single_click_activate (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_REORDERABLE:
|
|
gtk_column_view_set_reorderable (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_ENABLE_RUBBERBAND:
|
|
gtk_column_view_set_enable_rubberband (self, g_value_get_boolean (value));
|
|
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;
|
|
|
|
widget_class->focus = gtk_widget_focus_child;
|
|
widget_class->grab_focus = gtk_widget_grab_focus_child;
|
|
widget_class->measure = gtk_column_view_measure;
|
|
widget_class->size_allocate = gtk_column_view_allocate;
|
|
|
|
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: (attributes org.gtk.Property.get=gtk_column_view_get_columns)
|
|
*
|
|
* The list of columns.
|
|
*/
|
|
properties[PROP_COLUMNS] =
|
|
g_param_spec_object ("columns", NULL, NULL,
|
|
G_TYPE_LIST_MODEL,
|
|
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GtkColumnView:model: (attributes org.gtk.Property.get=gtk_column_view_get_model org.gtk.Property.set=gtk_column_view_set_model)
|
|
*
|
|
* Model for the items displayed.
|
|
*/
|
|
properties[PROP_MODEL] =
|
|
g_param_spec_object ("model", NULL, NULL,
|
|
GTK_TYPE_SELECTION_MODEL,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GtkColumnView:show-row-separators: (attributes org.gtk.Property.get=gtk_column_view_get_show_row_separators org.gtk.Property.set=gtk_column_view_set_show_row_separators)
|
|
*
|
|
* Show separators between rows.
|
|
*/
|
|
properties[PROP_SHOW_ROW_SEPARATORS] =
|
|
g_param_spec_boolean ("show-row-separators", NULL, NULL,
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkColumnView:show-column-separators: (attributes org.gtk.Property.get=gtk_column_view_get_show_column_separators org.gtk.Property.set=gtk_column_view_set_show_column_separators)
|
|
*
|
|
* Show separators between columns.
|
|
*/
|
|
properties[PROP_SHOW_COLUMN_SEPARATORS] =
|
|
g_param_spec_boolean ("show-column-separators", NULL, NULL,
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkColumnView:sorter: (attributes org.gtk.Property.get=gtk_column_view_get_sorter)
|
|
*
|
|
* Sorter with the sorting choices of the user.
|
|
*/
|
|
properties[PROP_SORTER] =
|
|
g_param_spec_object ("sorter", NULL, NULL,
|
|
GTK_TYPE_SORTER,
|
|
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GtkColumnView:single-click-activate: (attributes org.gtk.Property.get=gtk_column_view_get_single_click_activate org.gtk.Property.set=gtk_column_view_set_single_click_activate)
|
|
*
|
|
* Activate rows on single click and select them on hover.
|
|
*/
|
|
properties[PROP_SINGLE_CLICK_ACTIVATE] =
|
|
g_param_spec_boolean ("single-click-activate", NULL, NULL,
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkColumnView:reorderable: (attributes org.gtk.Property.get=gtk_column_view_get_reorderable org.gtk.Property.set=gtk_column_view_set_reorderable)
|
|
*
|
|
* Whether columns are reorderable.
|
|
*/
|
|
properties[PROP_REORDERABLE] =
|
|
g_param_spec_boolean ("reorderable", NULL, NULL,
|
|
TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GtkColumnView:enable-rubberband: (attributes org.gtk.Property.get=gtk_column_view_get_enable_rubberband org.gtk.Property.set=gtk_column_view_set_enable_rubberband)
|
|
*
|
|
* Allow rubberband selection.
|
|
*/
|
|
properties[PROP_ENABLE_RUBBERBAND] =
|
|
g_param_spec_boolean ("enable-rubberband", NULL, NULL,
|
|
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
|
|
*
|
|
* 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 [method@Gtk.ListItem.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_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TREE_GRID);
|
|
}
|
|
|
|
static void update_column_resize (GtkColumnView *self,
|
|
double x);
|
|
static void update_column_reorder (GtkColumnView *self,
|
|
double x);
|
|
|
|
static gboolean
|
|
autoscroll_cb (GtkWidget *widget,
|
|
GdkFrameClock *frame_clock,
|
|
gpointer data)
|
|
{
|
|
GtkColumnView *self = data;
|
|
|
|
gtk_adjustment_set_value (self->hadjustment,
|
|
gtk_adjustment_get_value (self->hadjustment) + self->autoscroll_delta);
|
|
|
|
self->autoscroll_x += self->autoscroll_delta;
|
|
|
|
if (self->in_column_resize)
|
|
update_column_resize (self, self->autoscroll_x);
|
|
else if (self->in_column_reorder)
|
|
update_column_reorder (self, self->autoscroll_x);
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
add_autoscroll (GtkColumnView *self,
|
|
double x,
|
|
double delta)
|
|
{
|
|
self->autoscroll_x = x;
|
|
self->autoscroll_delta = delta;
|
|
|
|
if (self->autoscroll_id == 0)
|
|
self->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), autoscroll_cb, self, NULL);
|
|
}
|
|
|
|
static void
|
|
remove_autoscroll (GtkColumnView *self)
|
|
{
|
|
if (self->autoscroll_id != 0)
|
|
{
|
|
gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->autoscroll_id);
|
|
self->autoscroll_id = 0;
|
|
}
|
|
}
|
|
|
|
#define SCROLL_EDGE_SIZE 30
|
|
|
|
static void
|
|
update_autoscroll (GtkColumnView *self,
|
|
double x)
|
|
{
|
|
double width;
|
|
double delta;
|
|
double vx, vy;
|
|
|
|
/* x is in header coordinates */
|
|
gtk_widget_translate_coordinates (self->header, GTK_WIDGET (self), x, 0, &vx, &vy);
|
|
width = gtk_widget_get_width (GTK_WIDGET (self));
|
|
|
|
if (vx < SCROLL_EDGE_SIZE)
|
|
delta = - (SCROLL_EDGE_SIZE - vx)/3.0;
|
|
else if (width - vx < SCROLL_EDGE_SIZE)
|
|
delta = (SCROLL_EDGE_SIZE - (width - vx))/3.0;
|
|
else
|
|
delta = 0;
|
|
|
|
if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
|
|
delta = - delta;
|
|
|
|
if (delta != 0)
|
|
add_autoscroll (self, x, delta);
|
|
else
|
|
remove_autoscroll (self);
|
|
}
|
|
|
|
|
|
#define DRAG_WIDTH 8
|
|
|
|
static gboolean
|
|
gtk_column_view_in_resize_rect (GtkColumnView *self,
|
|
GtkColumnViewColumn *column,
|
|
double x,
|
|
double y)
|
|
{
|
|
GtkWidget *header;
|
|
graphene_rect_t rect;
|
|
int width;
|
|
|
|
header = gtk_column_view_column_get_header (column);
|
|
|
|
if (!gtk_widget_compute_bounds (header, self->header, &rect))
|
|
return FALSE;
|
|
|
|
gtk_column_view_column_get_allocation (column, NULL, &width);
|
|
rect.size.width = width;
|
|
|
|
rect.origin.x += rect.size.width - DRAG_WIDTH / 2;
|
|
rect.size.width = DRAG_WIDTH;
|
|
|
|
return graphene_rect_contains_point (&rect, &(graphene_point_t) { x, y});
|
|
}
|
|
|
|
static gboolean
|
|
gtk_column_view_in_header (GtkColumnView *self,
|
|
GtkColumnViewColumn *column,
|
|
double x,
|
|
double y)
|
|
{
|
|
GtkWidget *header;
|
|
graphene_rect_t rect;
|
|
|
|
header = gtk_column_view_column_get_header (column);
|
|
|
|
if (!gtk_widget_compute_bounds (header, self->header, &rect))
|
|
return FALSE;
|
|
|
|
return graphene_rect_contains_point (&rect, &(graphene_point_t) { x, y});
|
|
}
|
|
|
|
static void
|
|
set_resize_cursor (GtkColumnView *self,
|
|
gboolean set)
|
|
{
|
|
int i, n;
|
|
|
|
n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
GtkColumnViewColumn *column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
GtkWidget *header = gtk_column_view_column_get_header (column);
|
|
if (set)
|
|
gtk_widget_set_cursor_from_name (header, "col-resize");
|
|
else
|
|
gtk_widget_set_cursor (header, NULL);
|
|
g_object_unref (column);
|
|
}
|
|
}
|
|
|
|
static void
|
|
header_drag_begin (GtkGestureDrag *gesture,
|
|
double start_x,
|
|
double start_y,
|
|
GtkColumnView *self)
|
|
{
|
|
int i, n;
|
|
|
|
self->drag_pos = -1;
|
|
|
|
n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
|
|
for (i = n - 1; !self->in_column_resize && i >= 0; i--)
|
|
{
|
|
GtkColumnViewColumn *column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
|
|
if (!gtk_column_view_column_get_visible (column))
|
|
{
|
|
g_object_unref (column);
|
|
continue;
|
|
}
|
|
|
|
if (i + 1 < n &&
|
|
gtk_column_view_column_get_resizable (column) &&
|
|
gtk_column_view_in_resize_rect (self, column, start_x, start_y))
|
|
{
|
|
int size;
|
|
|
|
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
|
|
if (!gtk_widget_has_focus (GTK_WIDGET (self)))
|
|
gtk_widget_grab_focus (GTK_WIDGET (self));
|
|
|
|
gtk_column_view_column_get_allocation (column, NULL, &size);
|
|
gtk_column_view_column_set_fixed_width (column, size);
|
|
|
|
self->drag_pos = i;
|
|
self->drag_x = start_x - size;
|
|
self->in_column_resize = TRUE;
|
|
|
|
set_resize_cursor (self, TRUE);
|
|
|
|
g_object_unref (column);
|
|
|
|
break;
|
|
}
|
|
|
|
g_object_unref (column);
|
|
}
|
|
|
|
for (i = 0; !self->in_column_resize && i < n; i++)
|
|
{
|
|
GtkColumnViewColumn *column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
|
|
if (!gtk_column_view_column_get_visible (column))
|
|
{
|
|
g_object_unref (column);
|
|
continue;
|
|
}
|
|
|
|
if (gtk_column_view_get_reorderable (self) &&
|
|
gtk_column_view_in_header (self, column, start_x, start_y))
|
|
{
|
|
int pos;
|
|
|
|
gtk_column_view_column_get_allocation (column, &pos, NULL);
|
|
|
|
self->drag_pos = i;
|
|
self->drag_offset = start_x - pos;
|
|
|
|
g_object_unref (column);
|
|
break;
|
|
}
|
|
|
|
g_object_unref (column);
|
|
}
|
|
}
|
|
|
|
static void
|
|
header_drag_end (GtkGestureDrag *gesture,
|
|
double offset_x,
|
|
double offset_y,
|
|
GtkColumnView *self)
|
|
{
|
|
double start_x, x;
|
|
|
|
gtk_gesture_drag_get_start_point (gesture, &start_x, NULL);
|
|
x = start_x + offset_x;
|
|
|
|
remove_autoscroll (self);
|
|
|
|
if (self->in_column_resize)
|
|
{
|
|
set_resize_cursor (self, FALSE);
|
|
self->in_column_resize = FALSE;
|
|
}
|
|
else if (self->in_column_reorder)
|
|
{
|
|
GdkEventSequence *sequence;
|
|
GtkColumnViewColumn *column;
|
|
GtkWidget *header;
|
|
int i;
|
|
|
|
self->in_column_reorder = FALSE;
|
|
|
|
if (self->drag_pos == -1)
|
|
return;
|
|
|
|
column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
|
|
header = gtk_column_view_column_get_header (column);
|
|
gtk_widget_remove_css_class (header, "dnd");
|
|
|
|
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
|
|
if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
|
|
{
|
|
g_object_unref (column);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++)
|
|
{
|
|
GtkColumnViewColumn *col = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
|
|
if (gtk_column_view_column_get_visible (col))
|
|
{
|
|
int pos, size;
|
|
|
|
gtk_column_view_column_get_allocation (col, &pos, &size);
|
|
if (pos <= x && x <= pos + size)
|
|
{
|
|
gtk_column_view_insert_column (self, i, column);
|
|
g_object_unref (col);
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_object_unref (col);
|
|
}
|
|
|
|
g_object_unref (column);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_column_resize (GtkColumnView *self,
|
|
double x)
|
|
{
|
|
GtkColumnViewColumn *column;
|
|
|
|
column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
|
|
gtk_column_view_column_set_fixed_width (column, MAX (x - self->drag_x, 0));
|
|
g_object_unref (column);
|
|
}
|
|
|
|
static void
|
|
update_column_reorder (GtkColumnView *self,
|
|
double x)
|
|
{
|
|
GtkColumnViewColumn *column;
|
|
int width;
|
|
int size;
|
|
|
|
column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
|
|
width = gtk_widget_get_allocated_width (GTK_WIDGET (self->header));
|
|
gtk_column_view_column_get_allocation (column, NULL, &size);
|
|
|
|
self->drag_x = CLAMP (x - self->drag_offset, 0, width - size);
|
|
|
|
gtk_widget_queue_allocate (GTK_WIDGET (self));
|
|
gtk_column_view_column_queue_resize (column);
|
|
g_object_unref (column);
|
|
}
|
|
|
|
static void
|
|
header_drag_update (GtkGestureDrag *gesture,
|
|
double offset_x,
|
|
double offset_y,
|
|
GtkColumnView *self)
|
|
{
|
|
GdkEventSequence *sequence;
|
|
double start_x, x;
|
|
|
|
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
|
|
if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
|
|
return;
|
|
|
|
if (self->drag_pos == -1)
|
|
return;
|
|
|
|
if (!self->in_column_resize && !self->in_column_reorder)
|
|
{
|
|
if (gtk_drag_check_threshold_double (GTK_WIDGET (self), 0, 0, offset_x, 0))
|
|
{
|
|
GtkColumnViewColumn *column;
|
|
GtkWidget *header;
|
|
|
|
column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
|
|
header = gtk_column_view_column_get_header (column);
|
|
|
|
gtk_widget_insert_after (header, self->header, gtk_widget_get_last_child (self->header));
|
|
gtk_widget_add_css_class (header, "dnd");
|
|
|
|
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
|
|
if (!gtk_widget_has_focus (GTK_WIDGET (self)))
|
|
gtk_widget_grab_focus (GTK_WIDGET (self));
|
|
|
|
self->in_column_reorder = TRUE;
|
|
|
|
g_object_unref (column);
|
|
}
|
|
}
|
|
|
|
gtk_gesture_drag_get_start_point (gesture, &start_x, NULL);
|
|
x = start_x + offset_x;
|
|
|
|
if (self->in_column_resize)
|
|
update_column_resize (self, x);
|
|
else if (self->in_column_reorder)
|
|
update_column_reorder (self, x);
|
|
|
|
if (self->in_column_resize || self->in_column_reorder)
|
|
update_autoscroll (self, x);
|
|
}
|
|
|
|
static void
|
|
header_motion (GtkEventControllerMotion *controller,
|
|
double x,
|
|
double y,
|
|
GtkColumnView *self)
|
|
{
|
|
gboolean cursor_set = FALSE;
|
|
int i, n;
|
|
|
|
if (self->in_column_resize)
|
|
return;
|
|
|
|
n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
GtkColumnViewColumn *column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
|
|
if (!gtk_column_view_column_get_visible (column))
|
|
{
|
|
g_object_unref (column);
|
|
continue;
|
|
}
|
|
|
|
if (i + 1 < n &&
|
|
gtk_column_view_column_get_resizable (column) &&
|
|
gtk_column_view_in_resize_rect (self, column, x, y))
|
|
{
|
|
gtk_widget_set_cursor_from_name (self->header, "col-resize");
|
|
cursor_set = TRUE;
|
|
}
|
|
|
|
g_object_unref (column);
|
|
}
|
|
|
|
if (!cursor_set)
|
|
gtk_widget_set_cursor (self->header, NULL);
|
|
}
|
|
|
|
static gboolean
|
|
header_key_pressed (GtkEventControllerKey *controller,
|
|
guint keyval,
|
|
guint keycode,
|
|
GdkModifierType modifiers,
|
|
GtkColumnView *self)
|
|
{
|
|
if (self->in_column_reorder)
|
|
{
|
|
if (keyval == GDK_KEY_Escape)
|
|
gtk_gesture_set_state (self->drag_gesture, GTK_EVENT_SEQUENCE_DENIED);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
header_pressed (GtkGestureClick *gesture,
|
|
int n_press,
|
|
double x,
|
|
double y,
|
|
GtkColumnView *self)
|
|
{
|
|
int i, n;
|
|
|
|
if (n_press != 2)
|
|
return;
|
|
|
|
n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
|
|
for (i = n - 1; i >= 0; i--)
|
|
{
|
|
GtkColumnViewColumn *column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
|
|
g_object_unref (column);
|
|
|
|
if (i + 1 < n &&
|
|
gtk_column_view_column_get_resizable (column) &&
|
|
gtk_column_view_in_resize_rect (self, column, x, y))
|
|
{
|
|
gtk_gesture_set_state (self->drag_gesture, GTK_EVENT_SEQUENCE_DENIED);
|
|
gtk_column_view_column_set_fixed_width (column, -1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_drag_motion (GtkDropControllerMotion *motion,
|
|
double x,
|
|
double y,
|
|
gpointer unused)
|
|
{
|
|
GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (motion));
|
|
GtkColumnView *self = GTK_COLUMN_VIEW (widget);
|
|
double hx, hy;
|
|
|
|
gtk_widget_translate_coordinates (widget, self->header, x, 0, &hx, &hy);
|
|
update_autoscroll (GTK_COLUMN_VIEW (widget), hx);
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_drag_leave (GtkDropControllerMotion *motion,
|
|
gpointer unused)
|
|
{
|
|
GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (motion));
|
|
|
|
remove_autoscroll (GTK_COLUMN_VIEW (widget));
|
|
}
|
|
|
|
static void
|
|
gtk_column_view_init (GtkColumnView *self)
|
|
{
|
|
GtkEventController *controller;
|
|
|
|
self->columns = g_list_store_new (GTK_TYPE_COLUMN_VIEW_COLUMN);
|
|
|
|
self->header = gtk_list_item_widget_new (NULL, "header", GTK_ACCESSIBLE_ROLE_ROW);
|
|
gtk_widget_set_can_focus (self->header, FALSE);
|
|
gtk_widget_set_layout_manager (self->header, gtk_column_view_layout_new (self));
|
|
gtk_widget_set_parent (self->header, GTK_WIDGET (self));
|
|
|
|
controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
|
|
g_signal_connect (controller, "pressed", G_CALLBACK (header_pressed), self);
|
|
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
|
|
gtk_widget_add_controller (self->header, controller);
|
|
|
|
controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
|
|
g_signal_connect (controller, "drag-begin", G_CALLBACK (header_drag_begin), self);
|
|
g_signal_connect (controller, "drag-update", G_CALLBACK (header_drag_update), self);
|
|
g_signal_connect (controller, "drag-end", G_CALLBACK (header_drag_end), self);
|
|
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
|
|
gtk_widget_add_controller (self->header, controller);
|
|
self->drag_gesture = GTK_GESTURE (controller);
|
|
|
|
controller = gtk_event_controller_motion_new ();
|
|
g_signal_connect (controller, "motion", G_CALLBACK (header_motion), self);
|
|
gtk_widget_add_controller (self->header, controller);
|
|
|
|
controller = gtk_event_controller_key_new ();
|
|
g_signal_connect (controller, "key-pressed", G_CALLBACK (header_key_pressed), self);
|
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
|
|
|
controller = gtk_drop_controller_motion_new ();
|
|
g_signal_connect (controller, "motion", G_CALLBACK (gtk_column_view_drag_motion), NULL);
|
|
g_signal_connect (controller, "leave", G_CALLBACK (gtk_column_view_drag_leave), NULL);
|
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
|
|
|
self->sorter = GTK_SORTER (gtk_column_view_sorter_new ());
|
|
self->factory = gtk_column_list_item_factory_new (self);
|
|
self->listview = GTK_LIST_VIEW (g_object_new (GTK_TYPE_COLUMN_LIST_VIEW, NULL));
|
|
gtk_list_view_set_factory (self->listview, GTK_LIST_ITEM_FACTORY (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_css_node_add_class (gtk_widget_get_css_node (GTK_WIDGET (self)),
|
|
g_quark_from_static_string (I_("view")));
|
|
|
|
gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
|
|
|
|
self->reorderable = TRUE;
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_new:
|
|
* @model: (nullable) (transfer full): the list model to use
|
|
*
|
|
* Creates a new `GtkColumnView`.
|
|
*
|
|
* You most likely want to call [method@Gtk.ColumnView.append_column]
|
|
* to add columns next.
|
|
*
|
|
* Returns: a new `GtkColumnView`
|
|
**/
|
|
GtkWidget *
|
|
gtk_column_view_new (GtkSelectionModel *model)
|
|
{
|
|
GtkWidget *result;
|
|
|
|
g_return_val_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model), NULL);
|
|
|
|
result = g_object_new (GTK_TYPE_COLUMN_VIEW,
|
|
"model", model,
|
|
NULL);
|
|
|
|
/* consume the reference */
|
|
g_clear_object (&model);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_get_model: (attributes org.gtk.Method.get_property=model)
|
|
* @self: a `GtkColumnView`
|
|
*
|
|
* Gets the model that's currently used to read the items displayed.
|
|
*
|
|
* Returns: (nullable) (transfer none): The model in use
|
|
*/
|
|
GtkSelectionModel *
|
|
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: (attributes org.gtk.Method.set_property=model)
|
|
* @self: a `GtkColumnView`
|
|
* @model: (nullable) (transfer none): the model to use
|
|
*
|
|
* Sets the model to use.
|
|
*
|
|
* This must be a [iface@Gtk.SelectionModel].
|
|
*/
|
|
void
|
|
gtk_column_view_set_model (GtkColumnView *self,
|
|
GtkSelectionModel *model)
|
|
{
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
g_return_if_fail (model == NULL || GTK_IS_SELECTION_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: (attributes org.gtk.Method.get_property=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
|
|
* ::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_row_separators: (attributes org.gtk.Method.set_property=show-row-separators)
|
|
* @self: a `GtkColumnView`
|
|
* @show_row_separators: %TRUE to show row separators
|
|
*
|
|
* Sets whether the list should show separators
|
|
* between rows.
|
|
*/
|
|
void
|
|
gtk_column_view_set_show_row_separators (GtkColumnView *self,
|
|
gboolean show_row_separators)
|
|
{
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
|
|
if (gtk_list_view_get_show_separators (self->listview) == show_row_separators)
|
|
return;
|
|
|
|
gtk_list_view_set_show_separators (self->listview, show_row_separators);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_ROW_SEPARATORS]);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_get_show_row_separators: (attributes org.gtk.Method.get_property=show-row-separators)
|
|
* @self: a `GtkColumnView`
|
|
*
|
|
* Returns whether the list should show separators
|
|
* between rows.
|
|
*
|
|
* Returns: %TRUE if the list shows separators
|
|
*/
|
|
gboolean
|
|
gtk_column_view_get_show_row_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_set_show_column_separators: (attributes org.gtk.Method.set_property=show-column-separators)
|
|
* @self: a `GtkColumnView`
|
|
* @show_column_separators: %TRUE to show column separators
|
|
*
|
|
* Sets whether the list should show separators
|
|
* between columns.
|
|
*/
|
|
void
|
|
gtk_column_view_set_show_column_separators (GtkColumnView *self,
|
|
gboolean show_column_separators)
|
|
{
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
|
|
if (self->show_column_separators == show_column_separators)
|
|
return;
|
|
|
|
self->show_column_separators = show_column_separators;
|
|
|
|
if (show_column_separators)
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "column-separators");
|
|
else
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "column-separators");
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_COLUMN_SEPARATORS]);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_get_show_column_separators: (attributes org.gtk.Method.get_property=show-column-separators)
|
|
* @self: a `GtkColumnView`
|
|
*
|
|
* Returns whether the list should show separators
|
|
* between columns.
|
|
*
|
|
* Returns: %TRUE if the list shows column separators
|
|
*/
|
|
gboolean
|
|
gtk_column_view_get_show_column_separators (GtkColumnView *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
|
|
|
|
return self->show_column_separators;
|
|
}
|
|
|
|
/**
|
|
* 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_sorter_remove_column (GTK_COLUMN_VIEW_SORTER (self->sorter), column);
|
|
gtk_column_view_column_set_column_view (column, NULL);
|
|
g_list_store_remove (self->columns, i);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_insert_column:
|
|
* @self: a `GtkColumnView`
|
|
* @position: the position to insert @column at
|
|
* @column: the `GtkColumnViewColumn` to insert
|
|
*
|
|
* Inserts a column at the given position in the columns of @self.
|
|
*
|
|
* If @column is already a column of @self, it will be repositioned.
|
|
*/
|
|
void
|
|
gtk_column_view_insert_column (GtkColumnView *self,
|
|
guint position,
|
|
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_get_column_view (column) == self);
|
|
g_return_if_fail (position <= g_list_model_get_n_items (G_LIST_MODEL (self->columns)));
|
|
int old_position = -1;
|
|
|
|
g_object_ref (column);
|
|
|
|
if (gtk_column_view_column_get_column_view (column) == self)
|
|
{
|
|
guint i;
|
|
|
|
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)
|
|
{
|
|
old_position = i;
|
|
g_list_store_remove (self->columns, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_list_store_insert (self->columns, position, column);
|
|
|
|
gtk_column_view_column_set_column_view (column, self);
|
|
|
|
if (old_position != -1 && position != old_position)
|
|
gtk_column_view_column_set_position (column, position);
|
|
|
|
gtk_column_view_column_queue_resize (column);
|
|
|
|
g_object_unref (column);
|
|
}
|
|
|
|
void
|
|
gtk_column_view_measure_across (GtkColumnView *self,
|
|
int *minimum,
|
|
int *natural)
|
|
{
|
|
guint i;
|
|
int min, nat;
|
|
|
|
min = 0;
|
|
nat = 0;
|
|
|
|
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++)
|
|
{
|
|
GtkColumnViewColumn *column;
|
|
int col_min, col_nat;
|
|
|
|
column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
|
|
gtk_column_view_column_measure (column, &col_min, &col_nat);
|
|
min += col_min;
|
|
nat += col_nat;
|
|
|
|
g_object_unref (column);
|
|
}
|
|
|
|
*minimum = min;
|
|
*natural = nat;
|
|
}
|
|
|
|
GtkListItemWidget *
|
|
gtk_column_view_get_header_widget (GtkColumnView *self)
|
|
{
|
|
return GTK_LIST_ITEM_WIDGET (self->header);
|
|
}
|
|
|
|
GtkListView *
|
|
gtk_column_view_get_list_view (GtkColumnView *self)
|
|
{
|
|
return GTK_LIST_VIEW (self->listview);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_get_sorter: (attributes org.gtk.Method.get_property=sorter)
|
|
* @self: a `GtkColumnView`
|
|
*
|
|
* Returns a special sorter that reflects the users sorting
|
|
* choices in the column view.
|
|
*
|
|
* To allow users to customizable sorting by clicking on column
|
|
* headers, this sorter needs to be set on the sort model underneath
|
|
* the model that is displayed by the view.
|
|
*
|
|
* See [method@Gtk.ColumnViewColumn.set_sorter] for setting up
|
|
* per-column sorting.
|
|
*
|
|
* Here is an example:
|
|
* ```c
|
|
* gtk_column_view_column_set_sorter (column, sorter);
|
|
* gtk_column_view_append_column (view, column);
|
|
* sorter = g_object_ref (gtk_column_view_get_sorter (view)));
|
|
* model = gtk_sort_list_model_new (store, sorter);
|
|
* selection = gtk_no_selection_new (model);
|
|
* gtk_column_view_set_model (view, selection);
|
|
* ```
|
|
*
|
|
* Returns: (nullable) (transfer none): the `GtkSorter` of @self
|
|
*/
|
|
GtkSorter *
|
|
gtk_column_view_get_sorter (GtkColumnView *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL);
|
|
|
|
return self->sorter;
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_sort_by_column:
|
|
* @self: a `GtkColumnView`
|
|
* @column: (nullable): the `GtkColumnViewColumn` to sort by
|
|
* @direction: the direction to sort in
|
|
*
|
|
* Sets the sorting of the view.
|
|
*
|
|
* This function should be used to set up the initial sorting.
|
|
* At runtime, users can change the sorting of a column view
|
|
* by clicking on the list headers.
|
|
*
|
|
* This call only has an effect if the sorter returned by
|
|
* [method@Gtk.ColumnView.get_sorter] is set on a sort model,
|
|
* and [method@Gtk.ColumnViewColumn.set_sorter] has been called
|
|
* on @column to associate a sorter with the column.
|
|
*
|
|
* If @column is %NULL, the view will be unsorted.
|
|
*/
|
|
void
|
|
gtk_column_view_sort_by_column (GtkColumnView *self,
|
|
GtkColumnViewColumn *column,
|
|
GtkSortType direction)
|
|
{
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
g_return_if_fail (column == NULL || GTK_IS_COLUMN_VIEW_COLUMN (column));
|
|
g_return_if_fail (column == NULL || gtk_column_view_column_get_column_view (column) == self);
|
|
|
|
if (column == NULL)
|
|
gtk_column_view_sorter_clear (GTK_COLUMN_VIEW_SORTER (self->sorter));
|
|
else
|
|
gtk_column_view_sorter_set_column (GTK_COLUMN_VIEW_SORTER (self->sorter),
|
|
column,
|
|
direction == GTK_SORT_DESCENDING);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_set_single_click_activate: (attributes org.gtk.Method.set_property=single-click-activate)
|
|
* @self: a `GtkColumnView`
|
|
* @single_click_activate: %TRUE to activate items on single click
|
|
*
|
|
* Sets whether rows should be activated on single click and
|
|
* selected on hover.
|
|
*/
|
|
void
|
|
gtk_column_view_set_single_click_activate (GtkColumnView *self,
|
|
gboolean single_click_activate)
|
|
{
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
|
|
if (single_click_activate == gtk_list_view_get_single_click_activate (self->listview))
|
|
return;
|
|
|
|
gtk_list_view_set_single_click_activate (self->listview, single_click_activate);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SINGLE_CLICK_ACTIVATE]);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_get_single_click_activate: (attributes org.gtk.Method.get_property=single-click-activate)
|
|
* @self: a `GtkColumnView`
|
|
*
|
|
* Returns whether rows will be activated on single click and
|
|
* selected on hover.
|
|
*
|
|
* Returns: %TRUE if rows are activated on single click
|
|
*/
|
|
gboolean
|
|
gtk_column_view_get_single_click_activate (GtkColumnView *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
|
|
|
|
return gtk_list_view_get_single_click_activate (self->listview);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_set_reorderable: (attributes org.gtk.Method.set_property=reorderable)
|
|
* @self: a `GtkColumnView`
|
|
* @reorderable: whether columns should be reorderable
|
|
*
|
|
* Sets whether columns should be reorderable by dragging.
|
|
*/
|
|
void
|
|
gtk_column_view_set_reorderable (GtkColumnView *self,
|
|
gboolean reorderable)
|
|
{
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
|
|
if (self->reorderable == reorderable)
|
|
return;
|
|
|
|
self->reorderable = reorderable;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REORDERABLE]);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_get_reorderable: (attributes org.gtk.Method.get_property=reorderable)
|
|
* @self: a `GtkColumnView`
|
|
*
|
|
* Returns whether columns are reorderable.
|
|
*
|
|
* Returns: %TRUE if columns are reorderable
|
|
*/
|
|
gboolean
|
|
gtk_column_view_get_reorderable (GtkColumnView *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), TRUE);
|
|
|
|
return self->reorderable;
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_set_enable_rubberband: (attributes org.gtk.Method.set_property=enable-rubberband)
|
|
* @self: a `GtkColumnView`
|
|
* @enable_rubberband: %TRUE to enable rubberband selection
|
|
*
|
|
* Sets whether selections can be changed by dragging with the mouse.
|
|
*/
|
|
void
|
|
gtk_column_view_set_enable_rubberband (GtkColumnView *self,
|
|
gboolean enable_rubberband)
|
|
{
|
|
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
|
|
|
if (enable_rubberband == gtk_list_view_get_enable_rubberband (self->listview))
|
|
return;
|
|
|
|
gtk_list_view_set_enable_rubberband (self->listview, enable_rubberband);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLE_RUBBERBAND]);
|
|
}
|
|
|
|
/**
|
|
* gtk_column_view_get_enable_rubberband: (attributes org.gtk.Method.get_property=enable-rubberband)
|
|
* @self: a `GtkColumnView`
|
|
*
|
|
* Returns whether rows can be selected by dragging with the mouse.
|
|
*
|
|
* Returns: %TRUE if rubberband selection is enabled
|
|
*/
|
|
gboolean
|
|
gtk_column_view_get_enable_rubberband (GtkColumnView *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
|
|
|
|
return gtk_list_view_get_enable_rubberband (self->listview);
|
|
}
|