Compare commits

...

26 Commits

Author SHA1 Message Date
Matthias Clasen
543cf3f907 Add search support
Add a filter to GtkListBase, and move the selection
to the first matching item whenever the filter changes.
This is meant to be used with single selection and
a string filter that is hooked up to a search entry.
2020-06-01 08:40:36 -04:00
Matthias Clasen
e904668cb1 gtk-demo: Make gridview demo use rubberbanding 2020-06-01 08:39:52 -04:00
Matthias Clasen
15d494ddc4 Add rubberband api
Add an enable-rubberband property to GtkListView,
GtkGridView and GtkColumnView.
2020-06-01 08:39:52 -04:00
Matthias Clasen
92729a9551 listbase: Add rubberband selection
Implement the typical rubberband selection, including
autoscroll. This is only useful with multiselection,
and not very compatible with single-click-activate.
Therefore, it is not enabled by default, and needs
to be turned on with the enable-rubber-band property.
2020-06-01 08:39:52 -04:00
Matthias Clasen
e98f70d6d5 Add GtkMultiSelection
This is implemented using a private GtkSet helper.

Includes tests.
2020-06-01 08:39:52 -04:00
Matthias Clasen
84b6d14ad2 inspector: Expand the actions list 2020-06-01 08:19:37 -04:00
Matthias Clasen
3f5e859795 inspector: Expand the resource list
This is an experiment with adding a filler column.
2020-06-01 08:19:37 -04:00
Matthias Clasen
780a9c1a1f inspector: Expand the property list
It looks better this way.
2020-06-01 08:19:37 -04:00
Matthias Clasen
b9c3a15d60 inspector: Expand the object tree
This is how it used to look, and it looks better that way.
2020-06-01 08:19:37 -04:00
Matthias Clasen
076a40c9bb columnview: Take expand into account
When allocating columns, distribute extra space
to columns that have expand set to TRUE.
2020-06-01 08:19:37 -04:00
Matthias Clasen
d8ab29a431 columnview: Add a GtkColumnViewColumn:expand property
This will be used to determine how to distribute
available extra space in a column view.
2020-06-01 08:19:37 -04:00
Matthias Clasen
ea0b36b330 columnview: Add autoscroll
Autoscroll when the pointer gets close to the
edge during column resizing or reordering. This
is similar to what the treeview does, but it is
implemented using a tick callback, and has
variable speed.
2020-06-01 08:16:56 -04:00
Matthias Clasen
c8157480ce columnview: Allow to cancel reorder with Escape
The treeview does this too.
2020-06-01 08:16:56 -04:00
Matthias Clasen
3eb0f92705 columnview: Interactive column reordering
Allow rearranging columns by dragging, in the same
way the treeview does.

We add the "dnd" style class to the header while
it is dragged, and we move the header of the dragged
column to the end of its parents children, so that
it gets drawn on top.
2020-06-01 08:16:56 -04:00
Matthias Clasen
10ac5cd977 columnview: Add a GtkColumnViewColumn:reorderable property
This will be used for interactive column reordering
in the future.
2020-06-01 08:16:56 -04:00
Matthias Clasen
d41fe03b73 columnviewlayout: Use header allocation for titles
Normally, this will be identical to the column
allocation, but we will temporarily change it
during column reordering.
2020-06-01 08:16:56 -04:00
Matthias Clasen
bf8856e3fb columnviewcolumn: Add reordering helpers
Add helper functions that let us temporarily give
a different allocation to headers. These will be
used to implement interactive column reordering
in GtkColumnView.
2020-06-01 08:16:56 -04:00
Matthias Clasen
4a46e8331e columnviewtitle: Trigger action on release
This is necessary to make drag-to-reorder work
without triggering resorting.
2020-06-01 08:16:56 -04:00
Matthias Clasen
241b20c39f columnview: Interactive column resizing
This copies just enough of the treeview code to
get columns moving.
2020-06-01 08:10:53 -04:00
Matthias Clasen
151cd0a296 columnviewcolumn: Add a helper
We need to check whether clicks are in the headers
of columns, so let the column view get at the the
header widget.
2020-06-01 08:10:53 -04:00
Matthias Clasen
05b6becd2f columnview: Add a GtkColumnViewColumn:resizable property
This will be used for interactive column resizing
in the future.
2020-06-01 08:10:53 -04:00
Matthias Clasen
a2d6f1f0cb columnview: Add a helper
The column code needs to get access to the
listitem widgets that are children of the listview,
so add a getter.
2020-06-01 08:10:53 -04:00
Matthias Clasen
d2af96f9c8 columnview: Add GtkColumnViewColumn:fixed-width
Add a fixed-width property similar to the same property
of GtkTreeViewColumn.
2020-06-01 08:10:53 -04:00
Matthias Clasen
b1ab5e187d columnview: Implement horizontal scrolling
The listview inside always thinks it gets its full size,
and updates its horizontal adjustment accordingly.

So keep our own adjustment, and update it when allocating.
2020-06-01 07:47:49 -04:00
Matthias Clasen
fd51444967 columnview: Revise scroll-minimum handling
Tweak the behavior slightly. We don't show
a scrollbar as long as we have at least
min-size available, but we still give the
entire size to the child, up to nat-size.

This matches how viewports handle scroll-minimum.
2020-06-01 07:47:49 -04:00
Matthias Clasen
843999265b print backend: Fix list model handling in dispose
The print backends do some complicated dispose handling
where the implementations call gtk_print_backend_destroy().

Our tests (in particular, the templates test) trigger
situations where we use print backends after dispose,
and they can't handle the printers listmodel being
NULL at that time. So just remove the printers in
dispose, keep the empty liststore until finalize.
2020-06-01 07:46:58 -04:00
36 changed files with 3275 additions and 74 deletions

View File

@@ -422,9 +422,10 @@ create_color_grid (void)
g_object_unref (factory);
gtk_grid_view_set_max_columns (GTK_GRID_VIEW (gridview), 24);
gtk_grid_view_set_enable_rubberband (GTK_GRID_VIEW (gridview), TRUE);
model = G_LIST_MODEL (gtk_sort_list_model_new (create_colors_model (), NULL));
selection = G_LIST_MODEL (gtk_no_selection_new (model));
selection = G_LIST_MODEL (gtk_multi_selection_new (model));
gtk_grid_view_set_model (GTK_GRID_VIEW (gridview), selection);
g_object_unref (selection);
g_object_unref (model);

View File

@@ -70,6 +70,7 @@
<xi:include href="xml/gtkselectionmodel.xml" />
<xi:include href="xml/gtknoselection.xml" />
<xi:include href="xml/gtksingleselection.xml" />
<xi:include href="xml/gtkmultiselection.xml" />
<xi:include href="xml/gtkdirectorylist.xml" />
</chapter>

View File

@@ -392,6 +392,16 @@ gtk_single_selection_set_can_unselect
gtk_single_selection_get_type
</SECTION>
<SECTION>
<FILE>gtkmultiselection</FILE>
<TITLE>GtkMultiSeledction</TITLE>
GtkMultiSelection
gtk_multi_selection_new
gtk_multi_selection_copy
<SUBSECTION Private>
gtk_multi_selection_get_type
</SECTION>
<SECTION>
<FILE>gtklistitem</FILE>
<TITLE>GtkListItem</TITLE>
@@ -479,6 +489,11 @@ gtk_list_view_set_show_separators
gtk_list_view_get_show_separators
gtk_list_view_set_single_click_activate
gtk_list_view_get_single_click_activate
gtk_list_view_set_enable_rubberband
gtk_list_view_get_enable_rubberband
gtk_list_view_set_search_filter
gtk_list_view_get_search_filter
gtk_list_view_next_match
<SUBSECTION Standard>
GTK_LIST_VIEW
GTK_LIST_VIEW_CLASS
@@ -507,6 +522,11 @@ gtk_column_view_set_show_separators
gtk_column_view_sort_by_column
gtk_column_view_set_single_click_activate
gtk_column_view_get_single_click_activate
gtk_column_view_set_enable_rubberband
gtk_column_view_get_enable_rubberband
gtk_column_view_set_search_filter
gtk_column_view_get_search_filter
gtk_column_view_next_match
<SUBSECTION Standard>
GTK_COLUMN_VIEW
GTK_COLUMN_VIEW_CLASS
@@ -533,6 +553,12 @@ gtk_column_view_column_set_sorter
gtk_column_view_column_get_sorter
gtk_column_view_column_set_visible
gtk_column_view_column_get_visible
gtk_column_view_column_set_reorderable
gtk_column_view_column_get_reorderable
gtk_column_view_column_set_fixed_width
gtk_column_view_column_get_fixed_width
gtk_column_view_column_set_expand
gtk_column_view_column_get_expand
<SUBSECTION Standard>
GTK_COLUMN_VIEW_COLUMN
GTK_COLUMN_VIEW_COLUMN_CLASS
@@ -557,6 +583,11 @@ gtk_grid_view_set_min_columns
gtk_grid_view_get_min_columns
gtk_grid_view_set_single_click_activate
gtk_grid_view_get_single_click_activate
gtk_grid_view_set_enable_rubberband
gtk_grid_view_get_enable_rubberband
gtk_grid_view_set_search_filter
gtk_grid_view_get_search_filter
gtk_grid_view_next_match
<SUBSECTION Standard>
GTK_GRID_VIEW
GTK_GRID_VIEW_CLASS

View File

@@ -139,6 +139,7 @@ gtk_menu_button_get_type
gtk_message_dialog_get_type
gtk_mount_operation_get_type
gtk_multi_filter_get_type
gtk_multi_selection_get_type
gtk_multi_sorter_get_type
gtk_native_get_type
gtk_native_dialog_get_type

View File

@@ -174,6 +174,7 @@
#include <gtk/gtkmessagedialog.h>
#include <gtk/gtkmountoperation.h>
#include <gtk/gtkmultifilter.h>
#include <gtk/gtkmultiselection.h>
#include <gtk/gtkmultisorter.h>
#include <gtk/gtknative.h>
#include <gtk/gtknativedialog.h>

View File

@@ -34,6 +34,13 @@
#include "gtkprivate.h"
#include "gtkscrollable.h"
#include "gtkwidgetprivate.h"
#include "gtksizerequest.h"
#include "gtkadjustment.h"
#include "gtkgesturedrag.h"
#include "gtkeventcontrollermotion.h"
#include "gtkdragsource.h"
#include "gtkeventcontrollerkey.h"
#include "gtklistbaseprivate.h"
/**
* SECTION:gtkcolumnview
@@ -63,6 +70,21 @@ struct _GtkColumnView
GtkColumnListItemFactory *factory;
GtkSorter *sorter;
GtkAdjustment *hadjustment;
gboolean in_column_resize;
gboolean in_column_reorder;
int drag_pos;
int drag_x;
int drag_offset;
int drag_column_x;
GtkGesture *drag_gesture;
guint autoscroll_id;
double autoscroll_x;
double autoscroll_delta;
};
struct _GtkColumnViewClass
@@ -82,6 +104,8 @@ enum
PROP_VADJUSTMENT,
PROP_VSCROLL_POLICY,
PROP_SINGLE_CLICK_ACTIVATE,
PROP_ENABLE_RUBBERBAND,
PROP_SEARCH_FILTER,
N_PROPS
};
@@ -169,48 +193,77 @@ gtk_column_view_allocate_columns (GtkColumnView *self,
int width)
{
GtkScrollablePolicy scroll_policy;
int col_min, col_nat, widget_min, widget_nat, extra, col_size, x;
int col_min, col_nat, extra, col_size, x;
int n, n_expand, expand_size, n_extra;
guint i;
GtkRequestedSize *sizes;
gtk_column_view_measure_across (self, &col_min, &col_nat);
gtk_widget_measure (GTK_WIDGET (self),
GTK_ORIENTATION_HORIZONTAL, -1,
&widget_min, &widget_nat,
NULL, NULL);
scroll_policy = gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview));
if (scroll_policy == GTK_SCROLL_MINIMUM)
{
extra = widget_min - col_min;
col_size = col_min;
}
else
{
extra = widget_nat - col_nat;
col_size = col_nat;
}
width -= extra;
width = MAX (width, col_size);
x = 0;
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++)
n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
n_expand = 0;
sizes = g_newa (GtkRequestedSize, n);
for (i = 0; i < n; i++)
{
GtkColumnViewColumn *column;
column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
gtk_column_view_column_measure (column, &col_min, &col_nat);
if (scroll_policy == GTK_SCROLL_MINIMUM)
col_size = col_min;
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
col_size = col_nat;
sizes[i].minimum_size = sizes[i].natural_size = 0;
g_object_unref (column);
}
gtk_column_view_column_allocate (column, x, col_size);
x += col_size;
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;
x = 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--;
}
}
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);
x += col_size;
}
g_object_unref (column);
}
return width + extra;
return x;
}
static void
@@ -220,8 +273,9 @@ gtk_column_view_allocate (GtkWidget *widget,
int baseline)
{
GtkColumnView *self = GTK_COLUMN_VIEW (widget);
int full_width, header_height, min, nat;
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);
@@ -229,11 +283,14 @@ gtk_column_view_allocate (GtkWidget *widget,
header_height = min;
else
header_height = nat;
gtk_widget_allocate (self->header, full_width, header_height, -1, NULL);
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 (0, header_height)));
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
@@ -244,6 +301,23 @@ gtk_column_view_activate_cb (GtkListView *listview,
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)
{
@@ -262,6 +336,7 @@ gtk_column_view_dispose (GObject *object)
g_clear_object (&self->factory);
g_clear_object (&self->sorter);
clear_adjustment (self);
G_OBJECT_CLASS (gtk_column_view_parent_class)->dispose (object);
}
@@ -291,7 +366,7 @@ gtk_column_view_get_property (GObject *object,
break;
case PROP_HADJUSTMENT:
g_value_set_object (value, gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (self->listview)));
g_value_set_object (value, self->hadjustment);
break;
case PROP_HSCROLL_POLICY:
@@ -322,6 +397,14 @@ gtk_column_view_get_property (GObject *object,
g_value_set_boolean (value, gtk_column_view_get_single_click_activate (self));
break;
case PROP_ENABLE_RUBBERBAND:
g_value_set_boolean (value, gtk_column_view_get_enable_rubberband (self));
break;
case PROP_SEARCH_FILTER:
g_value_set_object (value, gtk_column_view_get_search_filter (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -335,13 +418,24 @@ gtk_column_view_set_property (GObject *object,
GParamSpec *pspec)
{
GtkColumnView *self = GTK_COLUMN_VIEW (object);
GtkAdjustment *adjustment;
switch (property_id)
{
case PROP_HADJUSTMENT:
if (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (self->listview)) != g_value_get_object (value))
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)
{
gtk_scrollable_set_hadjustment (GTK_SCROLLABLE (self->listview), g_value_get_object (value));
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;
@@ -382,6 +476,14 @@ gtk_column_view_set_property (GObject *object,
gtk_column_view_set_single_click_activate (self, g_value_get_boolean (value));
break;
case PROP_ENABLE_RUBBERBAND:
gtk_column_view_set_enable_rubberband (self, g_value_get_boolean (value));
break;
case PROP_SEARCH_FILTER:
gtk_column_view_set_search_filter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -478,6 +580,30 @@ gtk_column_view_class_init (GtkColumnViewClass *klass)
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkColumnView:enable-rubberband:
*
* Allow rubberband selection
*/
properties[PROP_ENABLE_RUBBERBAND] =
g_param_spec_boolean ("enable-rubberband",
P_("Enable rubberband selection"),
P_("Allow selecting items by dragging with the mouse"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkColumnView:search-filter:
*
* Filter used for search
*/
properties[PROP_SEARCH_FILTER] =
g_param_spec_object ("search-filter",
P_("Search filter"),
P_("Filter used for searching"),
GTK_TYPE_FILTER,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/**
@@ -507,9 +633,366 @@ gtk_column_view_class_init (GtkColumnViewClass *klass)
gtk_widget_class_set_css_name (widget_class, I_("treeview"));
}
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 DRAG_WIDTH 6
static gboolean
gtk_column_view_in_resize_rect (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;
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
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 = 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 (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;
g_object_unref (column);
break;
}
if (gtk_column_view_column_get_reorderable (column) &&
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)
{
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_style_context_remove_class (gtk_widget_get_style_context (header), "dnd");
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
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);
}
#define SCROLL_EDGE_SIZE 15
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 (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_style_context_add_class (gtk_widget_get_style_context (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)
{
double value, page_size, upper;
value = gtk_adjustment_get_value (self->hadjustment);
page_size = gtk_adjustment_get_page_size (self->hadjustment);
upper = gtk_adjustment_get_upper (self->hadjustment);
if (x - value < SCROLL_EDGE_SIZE && value > 0)
add_autoscroll (self, x, - (SCROLL_EDGE_SIZE - (x - value))/3.0);
else if (value + page_size - x < SCROLL_EDGE_SIZE && value + page_size < upper)
add_autoscroll (self, x, (SCROLL_EDGE_SIZE - (value + page_size - x))/3.0);
else
remove_autoscroll (self);
}
}
static void
header_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkColumnView *self)
{
gboolean cursor_set = FALSE;
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);
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
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");
@@ -517,6 +1000,22 @@ gtk_column_view_init (GtkColumnView *self)
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_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);
self->sorter = gtk_column_view_sorter_new ();
self->factory = gtk_column_list_item_factory_new (self);
self->listview = GTK_LIST_VIEW (gtk_list_view_new_with_factory (
@@ -530,6 +1029,7 @@ gtk_column_view_init (GtkColumnView *self)
g_quark_from_static_string (I_("view")));
gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
}
/**
@@ -782,6 +1282,12 @@ 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:
* @self: a #GtkColumnView
@@ -879,3 +1385,102 @@ gtk_column_view_get_single_click_activate (GtkColumnView *self)
return gtk_list_view_get_single_click_activate (self->listview);
}
/**
* gtk_column_view_set_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:
* @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);
}
/**
* gtk_column_view_set_search_filter:
* @self: a #GtkColumnView
* @filter: (nullable): the filter to use for search, or %NULL
*
* Sets a search filter.
*
* The selection will be moved to first item matching the
* filter whenever the filter changes.
*
* This can be used with single selection and a string
* filter that is connected to a search entry.
*/
void
gtk_column_view_set_search_filter (GtkColumnView *self,
GtkFilter *filter)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
g_return_if_fail (filter == NULL || GTK_IS_FILTER (filter));
if (filter == gtk_list_view_get_search_filter (GTK_LIST_VIEW (self->listview)))
return;
gtk_list_view_set_search_filter (GTK_LIST_VIEW (self->listview), filter);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_FILTER]);
}
/**
* gtk_column_view_get_search_filter:
* @self: a #GtkColumnView
*
* Gets the search filter that was set with
* gtk_column_view_set_search_filter().
*
* Returns: (transfer none): The search filter of @self
*/
GtkFilter *
gtk_column_view_get_search_filter (GtkColumnView *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL);
return gtk_list_view_get_search_filter (GTK_LIST_VIEW (self->listview));
}
/**
* gtk_column_view_select_next_match:
* @self: a #GtkColumnView
* @forward: whether to move forward or back
*
* Moves the selection to the next item matching the
* search filter set with gtk_column_view_set_search_filter().
*/
void
gtk_column_view_select_next_match (GtkColumnView *self,
gboolean forward)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
gtk_list_view_select_next_match (GTK_LIST_VIEW (self->listview), forward);
}

View File

@@ -27,6 +27,7 @@
#include <gtk/gtktypes.h>
#include <gtk/gtksortlistmodel.h>
#include <gtk/gtksorter.h>
#include <gtk/gtkfilter.h>
G_BEGIN_DECLS
@@ -92,6 +93,22 @@ void gtk_column_view_set_single_click_activate (GtkColumnView
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_get_single_click_activate (GtkColumnView *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_set_enable_rubberband (GtkColumnView *self,
gboolean enable_rubberband);
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_get_enable_rubberband (GtkColumnView *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_set_search_filter (GtkColumnView *self,
GtkFilter *filter);
GDK_AVAILABLE_IN_ALL
GtkFilter * gtk_column_view_get_search_filter (GtkColumnView *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_select_next_match (GtkColumnView *self,
gboolean forward);
G_END_DECLS
#endif /* __GTK_COLUMN_VIEW_H__ */

View File

@@ -53,10 +53,18 @@ gtk_column_view_cell_measure (GtkWidget *widget,
int *minimum_baseline,
int *natural_baseline)
{
GtkColumnViewCell *cell = GTK_COLUMN_VIEW_CELL (widget);
GtkWidget *child = gtk_widget_get_first_child (widget);
if (child)
gtk_widget_measure (child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
int fixed_width = gtk_column_view_column_get_fixed_width (cell->column);
if (fixed_width > -1)
*minimum = *natural = fixed_width;
}
}
static void

View File

@@ -60,8 +60,14 @@ struct _GtkColumnViewColumn
int natural_size_request;
int allocation_offset;
int allocation_size;
int header_position;
gboolean visible;
int fixed_width;
guint visible : 1;
guint resizable : 1;
guint expand : 1;
guint reorderable : 1;
/* This list isn't sorted - this is just caching for performance */
GtkColumnViewCell *first_cell; /* no reference, just caching */
@@ -80,6 +86,10 @@ enum
PROP_TITLE,
PROP_SORTER,
PROP_VISIBLE,
PROP_RESIZABLE,
PROP_EXPAND,
PROP_REORDERABLE,
PROP_FIXED_WIDTH,
N_PROPS
};
@@ -133,6 +143,22 @@ gtk_column_view_column_get_property (GObject *object,
g_value_set_boolean (value, self->visible);
break;
case PROP_RESIZABLE:
g_value_set_boolean (value, self->resizable);
break;
case PROP_EXPAND:
g_value_set_boolean (value, self->expand);
break;
case PROP_REORDERABLE:
g_value_set_boolean (value, self->reorderable);
break;
case PROP_FIXED_WIDTH:
g_value_set_int (value, self->fixed_width);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -165,6 +191,22 @@ gtk_column_view_column_set_property (GObject *object,
gtk_column_view_column_set_visible (self, g_value_get_boolean (value));
break;
case PROP_RESIZABLE:
gtk_column_view_column_set_resizable (self, g_value_get_boolean (value));
break;
case PROP_EXPAND:
gtk_column_view_column_set_expand (self, g_value_get_boolean (value));
break;
case PROP_REORDERABLE:
gtk_column_view_column_set_reorderable (self, g_value_get_boolean (value));
break;
case PROP_FIXED_WIDTH:
gtk_column_view_column_set_fixed_width (self, g_value_get_int (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -240,6 +282,55 @@ gtk_column_view_column_class_init (GtkColumnViewColumnClass *klass)
TRUE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkColumnViewColumn:resizable:
*
* Whether this column is resizable
*/
properties[PROP_RESIZABLE] =
g_param_spec_boolean ("resizable",
P_("Resizable"),
P_("Whether this column is resizable"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkColumnViewColumn:expand:
*
* Column gets share of extra width allocated to the view
*/
properties[PROP_EXPAND] =
g_param_spec_boolean ("expand",
P_("Expand"),
P_("column gets share of extra width"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkColumnViewColumn:reorderable:
*
* Whether this column is reorderable
*/
properties[PROP_REORDERABLE] =
g_param_spec_boolean ("reorderable",
P_("Reorderable"),
P_("Whether this column is reorderable"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkColumnViewColumn:fixed-width:
*
* If not -1, this is the width that the column is allocated,
* regardless of the size of its content.
*/
properties[PROP_FIXED_WIDTH] =
g_param_spec_int ("fixed-width",
P_("Fixed width"),
P_("Fixed width of this column"),
-1, G_MAXINT, -1,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
@@ -249,6 +340,10 @@ gtk_column_view_column_init (GtkColumnViewColumn *self)
self->minimum_size_request = -1;
self->natural_size_request = -1;
self->visible = TRUE;
self->resizable = FALSE;
self->expand = FALSE;
self->reorderable = FALSE;
self->fixed_width = -1;
}
/**
@@ -357,6 +452,12 @@ gtk_column_view_column_measure (GtkColumnViewColumn *self,
int *minimum,
int *natural)
{
if (self->fixed_width > -1)
{
self->minimum_size_request = self->fixed_width;
self->natural_size_request = self->fixed_width;
}
if (self->minimum_size_request < 0)
{
GtkColumnViewCell *cell;
@@ -399,6 +500,7 @@ gtk_column_view_column_allocate (GtkColumnViewColumn *self,
{
self->allocation_offset = offset;
self->allocation_size = size;
self->header_position = offset;
}
void
@@ -721,3 +823,207 @@ gtk_column_view_column_get_visible (GtkColumnViewColumn *self)
return self->visible;
}
/**
* gtk_column_view_column_set_resizable:
* @self: a #GtkColumnViewColumn
* @resizable: whether this column should be resizable
*
* Sets whether this column should be resizable by dragging.
*/
void
gtk_column_view_column_set_resizable (GtkColumnViewColumn *self,
gboolean resizable)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
if (self->resizable == resizable)
return;
self->resizable = resizable;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_RESIZABLE]);
}
/**
* gtk_column_view_get_resizable:
* @self: a #GtkColumnView
*
* Returns whether this column is resizable.
*
* Returns: %TRUE if this column is resizable
*/
gboolean
gtk_column_view_column_get_resizable (GtkColumnViewColumn *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), TRUE);
return self->resizable;
}
/**
* gtk_column_view_column_set_expand:
* @self: a #GtkColumnViewColumn
* @expand: %TRUE if this column should expand to fill available sace
*
* Sets the column to take available extra space.
*
* The extra space is shared equally amongst all columns that
* have the expand set to %TRUE.
*/
void
gtk_column_view_column_set_expand (GtkColumnViewColumn *self,
gboolean expand)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
if (self->expand == expand)
return;
self->expand = expand;
if (self->visible && self->view)
gtk_widget_queue_resize (GTK_WIDGET (self->view));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPAND]);
}
/**
* gtk_column_view_get_expand:
* @self: a #GtkColumnViewColumn
*
* Returns whether this column should expand.
*
* Returns: %TRUE if this column expands
*/
gboolean
gtk_column_view_column_get_expand (GtkColumnViewColumn *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), TRUE);
return self->expand;
}
/**
* gtk_column_view_column_set_reorderable:
* @self: a #GtkColumnViewColumn
* @reorderable: whether this column should be reorderable
*
* Sets whether this column should be reorderable by dragging.
*/
void
gtk_column_view_column_set_reorderable (GtkColumnViewColumn *self,
gboolean reorderable)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (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:
* @self: a #GtkColumnView
*
* Returns whether this column is reorderable.
*
* Returns: %TRUE if this column is reorderable
*/
gboolean
gtk_column_view_column_get_reorderable (GtkColumnViewColumn *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), TRUE);
return self->reorderable;
}
/**
* gtk_column_view_column_set_fixed_width:
* @self: a #GtkColumnViewColumn
* @fixed_width: the new fixed width, or -1
*
* If @fixed_width is not -1, sets the fixed width of @column;
* otherwise unsets it.
*
* Setting a fixed width overrides the automatically calculated
* width. Interactive resizing also sets the “fixed-width” property.
*/
void
gtk_column_view_column_set_fixed_width (GtkColumnViewColumn *self,
int fixed_width)
{
GtkOverflow overflow;
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
g_return_if_fail (fixed_width >= -1);
if (self->fixed_width == fixed_width)
return;
self->fixed_width = fixed_width;
if (fixed_width > -1)
overflow = GTK_OVERFLOW_HIDDEN;
else
overflow = GTK_OVERFLOW_VISIBLE;
if (self->header &&
overflow != gtk_widget_get_overflow (GTK_WIDGET (self->header)))
{
GtkColumnViewCell *cell;
gtk_widget_set_overflow (GTK_WIDGET (self->header), overflow);
for (cell = self->first_cell; cell; cell = gtk_column_view_cell_get_next (cell))
gtk_widget_set_overflow (GTK_WIDGET (cell), overflow);
}
gtk_column_view_column_queue_resize (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FIXED_WIDTH]);
}
/**
* gtk_column_view_column_get_fixed_width:
* @self: a #GtkColumnViewColumn
*
* Gets the fixed width of the column.
*
* Returns: the fixed with of the column
*/
int
gtk_column_view_column_get_fixed_width (GtkColumnViewColumn *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), -1);
return self->fixed_width;
}
GtkWidget *
gtk_column_view_column_get_header (GtkColumnViewColumn *self)
{
return self->header;
}
void
gtk_column_view_column_set_header_position (GtkColumnViewColumn *self,
int offset)
{
self->header_position = offset;
}
void
gtk_column_view_column_get_header_allocation (GtkColumnViewColumn *self,
int *offset,
int *size)
{
if (offset)
*offset = self->header_position;
if (size)
*size = self->allocation_size;
}

View File

@@ -78,6 +78,30 @@ void gtk_column_view_column_set_visible (GtkColu
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_column_get_visible (GtkColumnViewColumn *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_column_set_fixed_width (GtkColumnViewColumn *self,
int fixed_width);
GDK_AVAILABLE_IN_ALL
int gtk_column_view_column_get_fixed_width (GtkColumnViewColumn *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_column_set_resizable (GtkColumnViewColumn *self,
gboolean resizable);
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_column_get_resizable (GtkColumnViewColumn *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_column_set_expand (GtkColumnViewColumn *self,
gboolean expand);
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_column_get_expand (GtkColumnViewColumn *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_column_set_reorderable (GtkColumnViewColumn *self,
gboolean reorderable);
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_column_get_reorderable (GtkColumnViewColumn *self);
G_END_DECLS
#endif /* __GTK_COLUMN_VIEW_COLUMN_H__ */

View File

@@ -33,6 +33,7 @@ void gtk_column_view_column_add_cell (GtkColu
void gtk_column_view_column_remove_cell (GtkColumnViewColumn *self,
GtkColumnViewCell *cell);
GtkColumnViewCell * gtk_column_view_column_get_first_cell (GtkColumnViewColumn *self);
GtkWidget * gtk_column_view_column_get_header (GtkColumnViewColumn *self);
void gtk_column_view_column_queue_resize (GtkColumnViewColumn *self);
void gtk_column_view_column_measure (GtkColumnViewColumn *self,
@@ -47,4 +48,10 @@ void gtk_column_view_column_get_allocation (GtkColu
void gtk_column_view_column_notify_sort (GtkColumnViewColumn *self);
void gtk_column_view_column_set_header_position (GtkColumnViewColumn *self,
int offset);
void gtk_column_view_column_get_header_allocation (GtkColumnViewColumn *self,
int *offset,
int *size);
#endif /* __GTK_COLUMN_VIEW_COLUMN_PRIVATE_H__ */

View File

@@ -118,11 +118,16 @@ gtk_column_view_layout_allocate (GtkLayoutManager *layout_manager,
int col_x, col_width;
if (GTK_IS_COLUMN_VIEW_CELL (child))
column = gtk_column_view_cell_get_column (GTK_COLUMN_VIEW_CELL (child));
{
column = gtk_column_view_cell_get_column (GTK_COLUMN_VIEW_CELL (child));
gtk_column_view_column_get_allocation (column, &col_x, &col_width);
}
else
column = gtk_column_view_title_get_column (GTK_COLUMN_VIEW_TITLE (child));
{
column = gtk_column_view_title_get_column (GTK_COLUMN_VIEW_TITLE (child));
gtk_column_view_column_get_header_allocation (column, &col_x, &col_width);
}
gtk_column_view_column_get_allocation (column, &col_x, &col_width);
gtk_widget_size_allocate (child, &(GtkAllocation) { col_x, 0, col_width, height }, baseline);
}
}

View File

@@ -21,11 +21,13 @@
#define __GTK_COLUMN_VIEW_PRIVATE_H__
#include "gtk/gtkcolumnview.h"
#include "gtk/gtklistview.h"
#include "gtk/gtkcolumnviewsorterprivate.h"
#include "gtk/gtklistitemwidgetprivate.h"
GtkListItemWidget * gtk_column_view_get_header_widget (GtkColumnView *self);
GtkListView * gtk_column_view_get_list_view (GtkColumnView *self);
void gtk_column_view_measure_across (GtkColumnView *self,
int *minimum,

View File

@@ -58,10 +58,18 @@ gtk_column_view_title_measure (GtkWidget *widget,
int *minimum_baseline,
int *natural_baseline)
{
GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget);
GtkWidget *child = gtk_widget_get_first_child (widget);
if (child)
gtk_widget_measure (child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
int fixed_width = gtk_column_view_column_get_fixed_width (self->column);
if (fixed_width > -1)
*minimum = *natural = fixed_width;
}
}
static void
@@ -112,11 +120,11 @@ gtk_column_view_title_resize_func (GtkWidget *widget)
}
static void
click_pressed_cb (GtkGestureClick *gesture,
guint n_press,
gdouble x,
gdouble y,
GtkWidget *widget)
click_released_cb (GtkGestureClick *gesture,
guint n_press,
gdouble x,
gdouble y,
GtkWidget *widget)
{
GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget);
GtkSorter *sorter;
@@ -150,7 +158,7 @@ gtk_column_view_title_init (GtkColumnViewTitle *self)
gtk_box_append (GTK_BOX (self->box), self->sort);
gesture = gtk_gesture_click_new ();
g_signal_connect (gesture, "pressed", G_CALLBACK (click_pressed_cb), self);
g_signal_connect (gesture, "released", G_CALLBACK (click_released_cb), self);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
}

View File

@@ -91,6 +91,8 @@ enum
PROP_MIN_COLUMNS,
PROP_MODEL,
PROP_SINGLE_CLICK_ACTIVATE,
PROP_ENABLE_RUBBERBAND,
PROP_SEARCH_FILTER,
N_PROPS
};
@@ -914,6 +916,14 @@ gtk_grid_view_get_property (GObject *object,
g_value_set_boolean (value, gtk_list_item_manager_get_single_click_activate (self->item_manager));
break;
case PROP_ENABLE_RUBBERBAND:
g_value_set_boolean (value, gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)));
break;
case PROP_SEARCH_FILTER:
g_value_set_object (value, gtk_grid_view_get_search_filter (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -950,6 +960,14 @@ gtk_grid_view_set_property (GObject *object,
gtk_grid_view_set_single_click_activate (self, g_value_get_boolean (value));
break;
case PROP_ENABLE_RUBBERBAND:
gtk_grid_view_set_enable_rubberband (self, g_value_get_boolean (value));
break;
case PROP_SEARCH_FILTER:
gtk_grid_view_set_search_filter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -1062,6 +1080,30 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGridView:enable-rubberband:
*
* Allow rubberband selection
*/
properties[PROP_ENABLE_RUBBERBAND] =
g_param_spec_boolean ("enable-rubberband",
P_("Enable rubberband selection"),
P_("Allow selecting items by dragging with the mouse"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGridView:search-filter:
*
* Filter used for search
*/
properties[PROP_SEARCH_FILTER] =
g_param_spec_object ("search-filter",
P_("Search filter"),
P_("Filter used for searching"),
GTK_TYPE_FILTER,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/**
@@ -1370,3 +1412,102 @@ gtk_grid_view_get_single_click_activate (GtkGridView *self)
return gtk_list_item_manager_get_single_click_activate (self->item_manager);
}
/**
* gtk_grid_view_set_enable_rubberband:
* @self: a #GtkGridView
* @enable_rubberband: %TRUE to enable rubberband selection
*
* Sets whether selections can be changed by dragging with the mouse.
*/
void
gtk_grid_view_set_enable_rubberband (GtkGridView *self,
gboolean enable_rubberband)
{
g_return_if_fail (GTK_IS_GRID_VIEW (self));
if (enable_rubberband == gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)))
return;
gtk_list_base_set_enable_rubberband (GTK_LIST_BASE (self), enable_rubberband);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLE_RUBBERBAND]);
}
/**
* gtk_grid_view_get_enable_rubberband:
* @self: a #GtkGridView
*
* Returns whether rows can be selected by dragging with the mouse.
*
* Returns: %TRUE if rubberband selection is enabled
*/
gboolean
gtk_grid_view_get_enable_rubberband (GtkGridView *self)
{
g_return_val_if_fail (GTK_IS_GRID_VIEW (self), FALSE);
return gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self));
}
/**
* gtk_grid_view_set_search_filter:
* @self: a #GtkGridView
* @filter: (nullable): the filter to use for search, or %NULL
*
* Sets a search filter.
*
* The selection will be moved to first item matching the
* filter whenever the filter changes.
*
* This can be used with single selection and a string
* filter that is connected to a search entry.
*/
void
gtk_grid_view_set_search_filter (GtkGridView *self,
GtkFilter *filter)
{
g_return_if_fail (GTK_IS_GRID_VIEW (self));
g_return_if_fail (filter == NULL || GTK_IS_FILTER (filter));
if (filter == gtk_list_base_get_search_filter (GTK_LIST_BASE (self)))
return;
gtk_list_base_set_search_filter (GTK_LIST_BASE (self), filter);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_FILTER]);
}
/**
* gtk_grid_view_get_search_filter:
* @self: a #GtkGridView
*
* Gets the search filter that was set with
* gtk_grid_view_set_search_filter().
*
* Returns: (transfer none): The search filter of @self
*/
GtkFilter *
gtk_grid_view_get_search_filter (GtkGridView *self)
{
g_return_val_if_fail (GTK_IS_GRID_VIEW (self), NULL);
return gtk_list_base_get_search_filter (GTK_LIST_BASE (self));
}
/**
* gtk_grid_view_select_next_match:
* @self: a #GtkGridView
* @forward: whether to move forward or back
*
* Moves the selection to the next item matching the
* search filter set with gtk_grid_view_set_search_filter().
*/
void
gtk_grid_view_select_next_match (GtkGridView *self,
gboolean forward)
{
g_return_if_fail (GTK_IS_GRID_VIEW (self));
gtk_list_base_select_next_match (GTK_LIST_BASE (self), forward);
}

View File

@@ -25,6 +25,7 @@
#endif
#include <gtk/gtklistbase.h>
#include <gtk/gtkfilter.h>
G_BEGIN_DECLS
@@ -73,6 +74,11 @@ guint gtk_grid_view_get_max_columns (GtkGridView
GDK_AVAILABLE_IN_ALL
void gtk_grid_view_set_max_columns (GtkGridView *self,
guint max_columns);
GDK_AVAILABLE_IN_ALL
void gtk_grid_view_set_enable_rubberband (GtkGridView *self,
gboolean enable_rubberband);
GDK_AVAILABLE_IN_ALL
gboolean gtk_grid_view_get_enable_rubberband (GtkGridView *self);
GDK_AVAILABLE_IN_ALL
void gtk_grid_view_set_single_click_activate (GtkGridView *self,
@@ -80,6 +86,16 @@ void gtk_grid_view_set_single_click_activate (GtkGridView
GDK_AVAILABLE_IN_ALL
gboolean gtk_grid_view_get_single_click_activate (GtkGridView *self);
GDK_AVAILABLE_IN_ALL
void gtk_grid_view_set_search_filter (GtkGridView *self,
GtkFilter *filter);
GDK_AVAILABLE_IN_ALL
GtkFilter * gtk_grid_view_get_search_filter (GtkGridView *self);
GDK_AVAILABLE_IN_ALL
void gtk_grid_view_select_next_match (GtkGridView *self,
gboolean forward);
G_END_DECLS

View File

@@ -28,6 +28,12 @@
#include "gtkscrollable.h"
#include "gtksingleselection.h"
#include "gtktypebuiltins.h"
#include "gtkgesturedrag.h"
#include "gtkwidgetprivate.h"
#include "gtkcssnodeprivate.h"
#include "gtkstylecontextprivate.h"
#include "gtksnapshot.h"
#include "gtkmultiselection.h"
typedef struct _GtkListBasePrivate GtkListBasePrivate;
@@ -50,6 +56,24 @@ struct _GtkListBasePrivate
GtkListItemTracker *selected;
/* the item that has input focus */
GtkListItemTracker *focus;
gboolean enable_rubberband;
gboolean doing_rubberband;
double rb_x1;
double rb_y1;
double rb_x2;
double rb_y2;
GtkGesture *drag_gesture;
GtkCssNode *rubberband_node;
GtkSelectionModel *old_selection;
gboolean modify;
gboolean extend;
guint autoscroll_id;
double autoscroll_delta_x;
double autoscroll_delta_y;
GtkFilter *search_filter;
};
enum
@@ -523,6 +547,8 @@ gtk_list_base_focus (GtkWidget *widget,
}
}
static void gtk_list_base_clear_search_filter (GtkListBase *self);
static void
gtk_list_base_dispose (GObject *object)
{
@@ -551,6 +577,8 @@ gtk_list_base_dispose (GObject *object)
g_clear_object (&priv->model);
gtk_list_base_clear_search_filter (self);
G_OBJECT_CLASS (gtk_list_base_parent_class)->dispose (object);
}
@@ -764,26 +792,18 @@ gtk_list_base_update_focus_tracker (GtkListBase *self)
}
}
static void
gtk_list_base_scroll_to_item (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
static gboolean
gtk_list_base_scroll_to_position (GtkListBase *self,
guint pos)
{
GtkListBase *self = GTK_LIST_BASE (widget);
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
int start, end;
double align_along, align_across;
GtkPackType side_along, side_across;
guint pos;
if (!g_variant_check_format_string (parameter, "u", FALSE))
return;
g_variant_get (parameter, "u", &pos);
/* figure out primary orientation and if position is valid */
if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self), pos, &start, &end))
return;
return FALSE;
end += start;
gtk_list_base_compute_scroll_align (self,
@@ -794,7 +814,7 @@ gtk_list_base_scroll_to_item (GtkWidget *widget,
/* now do the same thing with the other orientation */
if (!gtk_list_base_get_allocation_across (GTK_LIST_BASE (self), pos, &start, &end))
return;
return FALSE;
end += start;
gtk_list_base_compute_scroll_align (self,
@@ -808,13 +828,32 @@ gtk_list_base_scroll_to_item (GtkWidget *widget,
align_across, side_across,
align_along, side_along);
/* HACK HACK HACK
*
* GTK has no way to track the focused child. But we now that when a listitem
* gets focus, it calls this action. So we update our focus tracker from here
* because it's the closest we can get to accurate tracking.
*/
gtk_list_base_update_focus_tracker (self);
return TRUE;
}
static void
gtk_list_base_scroll_to_item (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
GtkListBase *self = GTK_LIST_BASE (widget);
guint pos;
if (!g_variant_check_format_string (parameter, "u", FALSE))
return;
g_variant_get (parameter, "u", &pos);
if (gtk_list_base_scroll_to_position (self, pos))
{
/* HACK HACK HACK
*
* GTK has no way to track the focused child. But we know that when a listitem
* gets focus, it calls this action. So we update our focus tracker from here
* because it's the closest we can get to accurate tracking.
*/
gtk_list_base_update_focus_tracker (self);
}
}
static void
@@ -863,6 +902,17 @@ gtk_list_base_unselect_all (GtkWidget *widget,
gtk_selection_model_unselect_all (selection_model);
}
static void
gtk_list_base_next_match_action (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
gboolean forward;
g_variant_get (parameter, "(b)", &forward);
gtk_list_base_select_next_match (GTK_LIST_BASE (widget), forward);
}
static gboolean
gtk_list_base_move_cursor_to_start (GtkWidget *widget,
GVariant *args,
@@ -1061,6 +1111,20 @@ gtk_list_base_add_custom_move_binding (GtkWidgetClass *widget_class,
"(bbb)", TRUE, TRUE, TRUE);
}
static void gtk_list_base_snapshot_rubberband (GtkListBase *self,
GtkSnapshot *snapshot);
static void
gtk_list_base_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkListBase *self = GTK_LIST_BASE (widget);
GTK_WIDGET_CLASS (gtk_list_base_parent_class)->snapshot (widget, snapshot);
gtk_list_base_snapshot_rubberband (self, snapshot);
}
static void
gtk_list_base_class_init (GtkListBaseClass *klass)
{
@@ -1069,6 +1133,7 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
gpointer iface;
widget_class->focus = gtk_list_base_focus;
widget_class->snapshot = gtk_list_base_snapshot;
gobject_class->dispose = gtk_list_base_dispose;
gobject_class->get_property = gtk_list_base_get_property;
@@ -1162,6 +1227,11 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
NULL,
gtk_list_base_unselect_all);
gtk_widget_class_install_action (widget_class,
"list.next-match",
"(b)",
gtk_list_base_next_match_action);
gtk_list_base_add_move_binding (widget_class, GDK_KEY_Up, GTK_ORIENTATION_VERTICAL, -1);
gtk_list_base_add_move_binding (widget_class, GDK_KEY_KP_Up, GTK_ORIENTATION_VERTICAL, -1);
gtk_list_base_add_move_binding (widget_class, GDK_KEY_Down, GTK_ORIENTATION_VERTICAL, 1);
@@ -1184,6 +1254,341 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_slash, GDK_CONTROL_MASK, "list.select-all", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_A, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "list.unselect-all", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_backslash, GDK_CONTROL_MASK, "list.unselect-all", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_g, GDK_CONTROL_MASK, "list.next-match", "(b)", TRUE);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_g, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "list.next-match", "(b)", FALSE);
}
static void gtk_list_base_update_rubberband_selection (GtkListBase *self);
static gboolean
autoscroll_cb (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer data)
{
GtkListBase *self = data;
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
double value;
value = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
gtk_adjustment_set_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL], value + priv->autoscroll_delta_x);
value = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
gtk_adjustment_set_value (priv->adjustment[GTK_ORIENTATION_VERTICAL], value + priv->autoscroll_delta_y);
if (priv->doing_rubberband)
{
priv->rb_x2 += priv->autoscroll_delta_x;
priv->rb_y2 += priv->autoscroll_delta_y;
gtk_list_base_update_rubberband_selection (self);
}
gtk_widget_queue_draw (GTK_WIDGET (self));
return G_SOURCE_CONTINUE;
}
static void
add_autoscroll (GtkListBase *self,
double delta_x,
double delta_y)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
priv->autoscroll_delta_x = delta_x;
priv->autoscroll_delta_y = delta_y;
if (priv->autoscroll_id == 0)
priv->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), autoscroll_cb, self, NULL);
}
static void
remove_autoscroll (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
if (priv->autoscroll_id != 0)
{
gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->autoscroll_id);
priv->autoscroll_id = 0;
}
}
static void
gtk_list_base_start_rubberband (GtkListBase *self,
double x,
double y,
gboolean modify,
gboolean extend)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkCssNode *widget_node;
double value_x, value_y;
GtkSelectionModel *selection;
if (priv->doing_rubberband)
return;
value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
priv->rb_x1 = priv->rb_x2 = x + value_x;
priv->rb_y1 = priv->rb_y2 = y + value_y;
priv->modify = modify;
priv->extend = extend;
widget_node = gtk_widget_get_css_node (GTK_WIDGET (self));
priv->rubberband_node = gtk_css_node_new ();
gtk_css_node_set_name (priv->rubberband_node,
g_quark_from_static_string ("rubberband"));
gtk_css_node_set_parent (priv->rubberband_node, widget_node);
gtk_css_node_set_state (priv->rubberband_node, gtk_css_node_get_state (widget_node));
g_object_unref (priv->rubberband_node);
selection = gtk_list_item_manager_get_model (priv->item_manager);
if (modify)
priv->old_selection = GTK_SELECTION_MODEL (gtk_multi_selection_copy (selection));
priv->doing_rubberband = TRUE;
}
static void
gtk_list_base_stop_rubberband (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
if (!priv->doing_rubberband)
return;
priv->doing_rubberband = FALSE;
gtk_css_node_set_parent (priv->rubberband_node, NULL);
priv->rubberband_node = NULL;
g_clear_object (&priv->old_selection);
remove_autoscroll (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
#define SCROLL_EDGE_SIZE 15
static void
gtk_list_base_update_rubberband (GtkListBase *self,
double x,
double y)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
double value_x, value_y, page_size, upper;
double delta_x, delta_y;
if (!priv->doing_rubberband)
return;
value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
priv->rb_x2 = x + value_x;
priv->rb_y2 = y + value_y;
gtk_list_base_update_rubberband_selection (self);
page_size = gtk_adjustment_get_page_size (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
upper = gtk_adjustment_get_upper (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
if (x < SCROLL_EDGE_SIZE && value_x > 0)
delta_x = - (SCROLL_EDGE_SIZE - x)/3.0;
else if (page_size - x < SCROLL_EDGE_SIZE && value_x + page_size < upper)
delta_x = (SCROLL_EDGE_SIZE - (page_size - x))/3.0;
else
delta_x = 0;
page_size = gtk_adjustment_get_page_size (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
upper = gtk_adjustment_get_upper (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
if (y < SCROLL_EDGE_SIZE && value_y > 0)
delta_y = - (SCROLL_EDGE_SIZE - y)/3.0;
else if (page_size - y < SCROLL_EDGE_SIZE && value_y + page_size < upper)
delta_y = (SCROLL_EDGE_SIZE - (page_size - y))/3.0;
else
delta_y = 0;
if (delta_x != 0 || delta_y != 0)
add_autoscroll (self, delta_x, delta_y);
else
remove_autoscroll (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
gtk_list_base_update_rubberband_selection (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GdkRectangle rect;
GdkRectangle alloc;
double value_x, value_y;
GtkSelectionModel *model;
GtkListItemManagerItem *item;
value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
rect.x = MIN (priv->rb_x1, priv->rb_x2) - value_x;
rect.y = MIN (priv->rb_y1, priv->rb_y2) - value_y;
rect.width = ABS (priv->rb_x1 - priv->rb_x2) + 1;
rect.height = ABS (priv->rb_y1 - priv->rb_y2) + 1;
model = gtk_list_item_manager_get_model (priv->item_manager);
for (item = gtk_list_item_manager_get_first (priv->item_manager);
item != NULL;
item = gtk_rb_tree_node_get_next (item))
{
guint pos;
gboolean was_selected, selected;
if (!item->widget)
continue;
pos = gtk_list_item_manager_get_item_position (priv->item_manager, item);
gtk_widget_get_allocation (item->widget, &alloc);
selected = gdk_rectangle_intersect (&rect, &alloc, &alloc);
if (priv->modify)
{
was_selected = gtk_selection_model_is_selected (priv->old_selection, pos);
selected = selected ^ was_selected;
}
if (selected)
gtk_selection_model_select_item (model, pos, FALSE);
else if (!priv->extend)
gtk_selection_model_unselect_item (model, pos);
}
}
static void
gtk_list_base_snapshot_rubberband (GtkListBase *self,
GtkSnapshot *snapshot)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkStyleContext *context;
GdkRectangle rect;
double value_x, value_y;
if (!priv->doing_rubberband)
return;
value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
rect.x = MIN (priv->rb_x1, priv->rb_x2) - value_x;
rect.y = MIN (priv->rb_y1, priv->rb_y2) - value_y;
rect.width = ABS (priv->rb_x1 - priv->rb_x2) + 1;
rect.height = ABS (priv->rb_y1 - priv->rb_y2) + 1;
context = gtk_widget_get_style_context (GTK_WIDGET (self));
gtk_style_context_save_to_node (context, priv->rubberband_node);
gtk_snapshot_render_background (snapshot, context, rect.x, rect.y, rect.width, rect.height);
gtk_snapshot_render_frame (snapshot, context, rect.x, rect.y, rect.width, rect.height);
gtk_style_context_restore (context);
}
static void
get_selection_modifiers (GtkGesture *gesture,
gboolean *modify,
gboolean *extend)
{
GdkEventSequence *sequence;
GdkEvent *event;
GdkModifierType state;
*modify = FALSE;
*extend = FALSE;
sequence = gtk_gesture_get_last_updated_sequence (gesture);
event = gtk_gesture_get_last_event (gesture, sequence);
state = gdk_event_get_modifier_state (event);
if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
*modify = TRUE;
if ((state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
*extend = TRUE;
}
static void
gtk_list_base_drag_begin (GtkGestureDrag *gesture,
double start_x,
double start_y,
GtkListBase *self)
{
gboolean modify;
gboolean extend;
get_selection_modifiers (GTK_GESTURE (gesture), &modify, &extend);
gtk_list_base_start_rubberband (self, start_x, start_y, modify, extend);
}
static void
gtk_list_base_drag_update (GtkGestureDrag *gesture,
double offset_x,
double offset_y,
GtkListBase *self)
{
double start_x, start_y;
gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
gtk_list_base_update_rubberband (self, start_x + offset_x, start_y + offset_y);
}
static void
gtk_list_base_drag_end (GtkGestureDrag *gesture,
double offset_x,
double offset_y,
GtkListBase *self)
{
gtk_list_base_drag_update (gesture, offset_x, offset_y, self);
gtk_list_base_stop_rubberband (self);
}
void
gtk_list_base_set_enable_rubberband (GtkListBase *self,
gboolean enable)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
if (priv->enable_rubberband == enable)
return;
priv->enable_rubberband = enable;
if (enable)
{
priv->drag_gesture = gtk_gesture_drag_new ();
g_signal_connect (priv->drag_gesture, "drag-begin", G_CALLBACK (gtk_list_base_drag_begin), self);
g_signal_connect (priv->drag_gesture, "drag-update", G_CALLBACK (gtk_list_base_drag_update), self);
g_signal_connect (priv->drag_gesture, "drag-end", G_CALLBACK (gtk_list_base_drag_end), self);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (priv->drag_gesture));
}
else
{
gtk_widget_remove_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (priv->drag_gesture));
priv->drag_gesture = NULL;
}
}
gboolean
gtk_list_base_get_enable_rubberband (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
return priv->enable_rubberband;
}
static void
@@ -1538,3 +1943,183 @@ gtk_list_base_set_model (GtkListBase *self,
return TRUE;
}
static guint
find_first_selected (GtkSelectionModel *model)
{
guint i, start, n_items;
gboolean selected;
n_items = 0;
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (model)); i += n_items)
{
gtk_selection_model_query_range (model, i, &start, &n_items, &selected);
if (selected)
return i;
}
return GTK_INVALID_LIST_POSITION;
}
static gboolean
match_item (GListModel *model,
GtkFilter *filter,
guint position)
{
gpointer item;
gboolean result;
item = g_list_model_get_item (model, position);
result = gtk_filter_match (filter, item);
g_object_unref (item);
return result;
}
static guint
find_next_match (GListModel *model,
GtkFilter *filter,
guint start,
gboolean forward)
{
guint i;
if (start == GTK_INVALID_LIST_POSITION)
start = 0;
if (forward)
for (i = start; i < g_list_model_get_n_items (model); i++)
{
if (match_item (model, filter, i))
return i;
}
else
for (i = start; ; i--)
{
if (match_item (model, filter, i))
return i;
if (i == 0)
break;
}
return GTK_INVALID_LIST_POSITION;
}
static void
gtk_list_base_search_filter_changed_cb (GtkFilter *filter,
GtkFilterChange change,
GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkSelectionModel *model = gtk_list_item_manager_get_model (priv->item_manager);
if (model == NULL)
return;
if (gtk_filter_get_strictness (priv->search_filter) == GTK_FILTER_MATCH_NONE)
gtk_selection_model_unselect_all (model);
else
{
guint position;
switch (change)
{
case GTK_FILTER_CHANGE_DIFFERENT:
case GTK_FILTER_CHANGE_LESS_STRICT:
position = find_next_match (G_LIST_MODEL (model), priv->search_filter, 0, TRUE);
break;
case GTK_FILTER_CHANGE_MORE_STRICT:
position = find_first_selected (model);
if (position == GTK_INVALID_LIST_POSITION)
position = 0;
position = find_next_match (G_LIST_MODEL (model), priv->search_filter, position, TRUE);
break;
default:
g_assert_not_reached ();
}
if (position == GTK_INVALID_LIST_POSITION)
gtk_selection_model_unselect_all (model);
else
gtk_list_base_grab_focus_on_item (self, position, TRUE, FALSE, FALSE);
}
}
static void
gtk_list_base_clear_search_filter (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
if (priv->search_filter == NULL)
return;
g_signal_handlers_disconnect_by_func (priv->search_filter,
gtk_list_base_search_filter_changed_cb,
self);
g_clear_object (&priv->search_filter);
}
void
gtk_list_base_set_search_filter (GtkListBase *self,
GtkFilter *filter)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
if (priv->search_filter == filter)
return;
gtk_list_base_clear_search_filter (self);
if (filter)
{
priv->search_filter = g_object_ref (filter);
g_signal_connect (priv->search_filter, "changed",
G_CALLBACK (gtk_list_base_search_filter_changed_cb), self);
gtk_list_base_search_filter_changed_cb (priv->search_filter, GTK_FILTER_CHANGE_DIFFERENT, self);
}
gtk_widget_action_set_enabled (GTK_WIDGET (self), "list.next-match", filter != NULL);
}
GtkFilter *
gtk_list_base_get_search_filter (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
return priv->search_filter;
}
gboolean
gtk_list_base_select_next_match (GtkListBase *self,
gboolean forward)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkSelectionModel *model = gtk_list_item_manager_get_model (priv->item_manager);
guint position;
if (!priv->search_filter)
return FALSE;
position = find_first_selected (model);
if (position == GTK_INVALID_LIST_POSITION)
return FALSE;
if (forward)
position = position + 1;
else if (position > 0)
position = position - 1;
else
return FALSE;
position = find_next_match (G_LIST_MODEL (model), priv->search_filter, position, forward);
if (position == GTK_INVALID_LIST_POSITION)
{
gtk_widget_error_bell (GTK_WIDGET (self));
return FALSE;
}
gtk_list_base_grab_focus_on_item (self, position, TRUE, FALSE, FALSE);
return TRUE;
}

View File

@@ -23,6 +23,7 @@
#include "gtklistbase.h"
#include "gtklistitemmanagerprivate.h"
#include "gtkfilter.h"
#include "gtkprivate.h"
struct _GtkListBase
@@ -99,5 +100,13 @@ gboolean gtk_list_base_grab_focus_on_item (GtkListBase
gboolean select,
gboolean modify,
gboolean extend);
void gtk_list_base_set_enable_rubberband (GtkListBase *self,
gboolean enable);
gboolean gtk_list_base_get_enable_rubberband (GtkListBase *self);
void gtk_list_base_set_search_filter (GtkListBase *self,
GtkFilter *filter);
GtkFilter * gtk_list_base_get_search_filter (GtkListBase *self);
gboolean gtk_list_base_select_next_match (GtkListBase *self,
gboolean forward);
#endif /* __GTK_LIST_BASE_PRIVATE_H__ */

View File

@@ -29,6 +29,7 @@
#include "gtkintl.h"
#include "gtklistitemfactoryprivate.h"
#include "gtklistitemprivate.h"
#include "gtklistbaseprivate.h"
#include "gtkmain.h"
#include "gtkselectionmodel.h"
#include "gtkwidget.h"
@@ -309,6 +310,8 @@ gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture,
{
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
GtkWidget *widget = GTK_WIDGET (self);
GtkWidget * parent = gtk_widget_get_parent (widget);
gboolean rubberband;
if (priv->list_item && !priv->list_item->selectable && !priv->list_item->activatable)
{
@@ -316,7 +319,12 @@ gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture,
return;
}
if (!priv->list_item || priv->list_item->selectable)
if (GTK_IS_LIST_BASE (parent))
rubberband = gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (parent));
else
rubberband = FALSE;
if (!rubberband && (!priv->list_item || priv->list_item->selectable))
{
GdkModifierType state;
GdkEvent *event;

View File

@@ -85,6 +85,8 @@ enum
PROP_MODEL,
PROP_SHOW_SEPARATORS,
PROP_SINGLE_CLICK_ACTIVATE,
PROP_ENABLE_RUBBERBAND,
PROP_SEARCH_FILTER,
N_PROPS
};
@@ -643,6 +645,14 @@ gtk_list_view_get_property (GObject *object,
g_value_set_boolean (value, gtk_list_item_manager_get_single_click_activate (self->item_manager));
break;
case PROP_ENABLE_RUBBERBAND:
g_value_set_boolean (value, gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)));
break;
case PROP_SEARCH_FILTER:
g_value_set_object (value, gtk_list_view_get_search_filter (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -675,6 +685,14 @@ gtk_list_view_set_property (GObject *object,
gtk_list_view_set_single_click_activate (self, g_value_get_boolean (value));
break;
case PROP_ENABLE_RUBBERBAND:
gtk_list_view_set_enable_rubberband (self, g_value_get_boolean (value));
break;
case PROP_SEARCH_FILTER:
gtk_list_view_set_search_filter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -771,6 +789,30 @@ gtk_list_view_class_init (GtkListViewClass *klass)
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkListView:enable-rubberband:
*
* Allow rubberband selection
*/
properties[PROP_ENABLE_RUBBERBAND] =
g_param_spec_boolean ("enable-rubberband",
P_("Enable rubberband selection"),
P_("Allow selecting items by dragging with the mouse"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkListView:search-filter:
*
* Filter used for search
*/
properties[PROP_SEARCH_FILTER] =
g_param_spec_object ("search-filter",
P_("Search filter"),
P_("Filter used for searching"),
GTK_TYPE_FILTER,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/**
@@ -1033,3 +1075,103 @@ gtk_list_view_get_single_click_activate (GtkListView *self)
return gtk_list_item_manager_get_single_click_activate (self->item_manager);
}
/**
* gtk_list_view_set_enable_rubberband:
* @self: a #GtkListView
* @enable_rubberband: %TRUE to enable rubberband selection
*
* Sets whether selections can be changed by dragging with the mouse.
*/
void
gtk_list_view_set_enable_rubberband (GtkListView *self,
gboolean enable_rubberband)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
if (enable_rubberband == gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)))
return;
gtk_list_base_set_enable_rubberband (GTK_LIST_BASE (self), enable_rubberband);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLE_RUBBERBAND]);
}
/**
* gtk_list_view_get_enable_rubberband:
* @self: a #GtkListView
*
* Returns whether rows can be selected by dragging with the mouse.
*
* Returns: %TRUE if rubberband selection is enabled
*/
gboolean
gtk_list_view_get_enable_rubberband (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), FALSE);
return gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self));
}
/**
* gtk_list_view_set_search_filter:
* @self: a #GtkListView
* @filter: (nullable): the filter ot use for search, or %NULL
*
* Sets a search filter.
*
* The selection will be moved to first item matching the
* filter whenever the filter changes.
*
* This can be used with single selection and a string
* filter that is connected to a search entry.
*/
void
gtk_list_view_set_search_filter (GtkListView *self,
GtkFilter *filter)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
g_return_if_fail (filter == NULL || GTK_IS_FILTER (filter));
if (filter == gtk_list_base_get_search_filter (GTK_LIST_BASE (self)))
return;
gtk_list_base_set_search_filter (GTK_LIST_BASE (self), filter);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_FILTER]);
}
/**
* gtk_list_view_get_search_filter:
* @self: a #GtkListView
*
* Gets the search filter that was set with
* gtk_list_view_set_search_filter().
*
* Returns: (transfer none): The search filter of @self
*/
GtkFilter *
gtk_list_view_get_search_filter (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
return gtk_list_base_get_search_filter (GTK_LIST_BASE (self));
}
/**
* gtk_list_view_select_next_match:
* @self: a #GtkListView
* @forward: whether to move forward or back
*
* Moves the selection to the next item matching the
* search filter set with gtk_list_view_set_search_filter().
*/
void
gtk_list_view_select_next_match (GtkListView *self,
gboolean forward)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
gtk_list_base_select_next_match (GTK_LIST_BASE (self), forward);
}

View File

@@ -25,6 +25,7 @@
#endif
#include <gtk/gtklistbase.h>
#include <gtk/gtkfilter.h>
G_BEGIN_DECLS
@@ -75,6 +76,22 @@ void gtk_list_view_set_single_click_activate (GtkListView
GDK_AVAILABLE_IN_ALL
gboolean gtk_list_view_get_single_click_activate (GtkListView *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_set_enable_rubberband (GtkListView *self,
gboolean enable_rubberband);
GDK_AVAILABLE_IN_ALL
gboolean gtk_list_view_get_enable_rubberband (GtkListView *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_set_search_filter (GtkListView *self,
GtkFilter *filter);
GDK_AVAILABLE_IN_ALL
GtkFilter * gtk_list_view_get_search_filter (GtkListView *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_select_next_match (GtkListView *self,
gboolean forward);
G_END_DECLS
#endif /* __GTK_LIST_VIEW_H__ */

380
gtk/gtkmultiselection.c Normal file
View File

@@ -0,0 +1,380 @@
/*
* Copyright © 2019 Red Hat, Inc.
*
* 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: Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include "gtkmultiselection.h"
#include "gtkintl.h"
#include "gtkselectionmodel.h"
#include "gtksingleselection.h"
#include "gtkset.h"
/**
* SECTION:gtkmultiselection
* @Short_description: A selection model that allows selecting a multiple items
* @Title: GtkMultiSelection
* @see_also: #GtkSelectionModel
*
* GtkMultiSelection is an implementation of the #GtkSelectionModel interface
* that allows selecting multiple elements.
*/
struct _GtkMultiSelection
{
GObject parent_instance;
GListModel *model;
GtkSet *selected;
guint last_selected;
};
struct _GtkMultiSelectionClass
{
GObjectClass parent_class;
};
enum {
PROP_0,
PROP_MODEL,
N_PROPS,
};
static GParamSpec *properties[N_PROPS] = { NULL, };
static GType
gtk_multi_selection_get_item_type (GListModel *list)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (list);
return g_list_model_get_item_type (self->model);
}
static guint
gtk_multi_selection_get_n_items (GListModel *list)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (list);
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_multi_selection_get_item (GListModel *list,
guint position)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (list);
return g_list_model_get_item (self->model, position);
}
static void
gtk_multi_selection_list_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_multi_selection_get_item_type;
iface->get_n_items = gtk_multi_selection_get_n_items;
iface->get_item = gtk_multi_selection_get_item;
}
static gboolean
gtk_multi_selection_is_selected (GtkSelectionModel *model,
guint position)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
return gtk_set_contains (self->selected, position);
}
static gboolean
gtk_multi_selection_select_range (GtkSelectionModel *model,
guint position,
guint n_items,
gboolean exclusive)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
if (exclusive)
gtk_set_remove_all (self->selected);
gtk_set_add_range (self->selected, position, n_items);
gtk_selection_model_selection_changed (model, position, n_items);
return TRUE;
}
static gboolean
gtk_multi_selection_unselect_range (GtkSelectionModel *model,
guint position,
guint n_items)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
gtk_set_remove_range (self->selected, position, n_items);
gtk_selection_model_selection_changed (model, position, n_items);
return TRUE;
}
static gboolean
gtk_multi_selection_select_item (GtkSelectionModel *model,
guint position,
gboolean exclusive)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
guint pos, n_items;
pos = position;
n_items = 1;
self->last_selected = position;
return gtk_multi_selection_select_range (model, pos, n_items, exclusive);
}
static gboolean
gtk_multi_selection_unselect_item (GtkSelectionModel *model,
guint position)
{
return gtk_multi_selection_unselect_range (model, position, 1);
}
static gboolean
gtk_multi_selection_select_all (GtkSelectionModel *model)
{
return gtk_multi_selection_select_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)), FALSE);
}
static gboolean
gtk_multi_selection_unselect_all (GtkSelectionModel *model)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
self->last_selected = GTK_INVALID_LIST_POSITION;
return gtk_multi_selection_unselect_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
}
static void
gtk_multi_selection_query_range (GtkSelectionModel *model,
guint position,
guint *start_range,
guint *n_items,
gboolean *selected)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
guint upper_bound = g_list_model_get_n_items (self->model);
gtk_set_find_range (self->selected, position, upper_bound, start_range, n_items, selected);
}
static void
gtk_multi_selection_selection_model_init (GtkSelectionModelInterface *iface)
{
iface->is_selected = gtk_multi_selection_is_selected;
iface->select_item = gtk_multi_selection_select_item;
iface->unselect_item = gtk_multi_selection_unselect_item;
iface->select_range = gtk_multi_selection_select_range;
iface->unselect_range = gtk_multi_selection_unselect_range;
iface->select_all = gtk_multi_selection_select_all;
iface->unselect_all = gtk_multi_selection_unselect_all;
iface->query_range = gtk_multi_selection_query_range;
}
G_DEFINE_TYPE_EXTENDED (GtkMultiSelection, gtk_multi_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_multi_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
gtk_multi_selection_selection_model_init))
static void
gtk_multi_selection_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkMultiSelection *self)
{
gtk_set_remove_range (self->selected, position, removed);
gtk_set_shift (self->selected, position, (int)added - (int)removed);
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
}
static void
gtk_multi_selection_clear_model (GtkMultiSelection *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model,
gtk_multi_selection_items_changed_cb,
self);
g_clear_object (&self->model);
}
static void
gtk_multi_selection_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (object);
switch (prop_id)
{
case PROP_MODEL:
self->model = g_value_dup_object (value);
g_warn_if_fail (self->model != NULL);
g_signal_connect (self->model,
"items-changed",
G_CALLBACK (gtk_multi_selection_items_changed_cb),
self);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_multi_selection_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (object);
switch (prop_id)
{
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_multi_selection_dispose (GObject *object)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (object);
gtk_multi_selection_clear_model (self);
g_clear_pointer (&self->selected, gtk_set_free);
self->last_selected = GTK_INVALID_LIST_POSITION;
G_OBJECT_CLASS (gtk_multi_selection_parent_class)->dispose (object);
}
static void
gtk_multi_selection_class_init (GtkMultiSelectionClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->get_property = gtk_multi_selection_get_property;
gobject_class->set_property = gtk_multi_selection_set_property;
gobject_class->dispose = gtk_multi_selection_dispose;
/**
* GtkMultiSelection:model
*
* The list managed by this selection
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("List managed by this selection"),
G_TYPE_LIST_MODEL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
gtk_multi_selection_init (GtkMultiSelection *self)
{
self->selected = gtk_set_new ();
self->last_selected = GTK_INVALID_LIST_POSITION;
}
/**
* gtk_multi_selection_new:
* @model: (transfer none): the #GListModel to manage
*
* Creates a new selection to handle @model.
*
* Returns: (transfer full) (type GtkMultiSelection): a new #GtkMultiSelection
**/
GListModel *
gtk_multi_selection_new (GListModel *model)
{
g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
return g_object_new (GTK_TYPE_MULTI_SELECTION,
"model", model,
NULL);
}
/**
* gtk_multi_selection_copy:
* @selection: the #GtkSelectionModel to copy
*
* Creates a #GtkMultiSelection that has the same underlying
* model and the same selected items as @selection.
*
* Returns: (transfer full): a new #GtkMultiSelection
*/
GtkMultiSelection *
gtk_multi_selection_copy (GtkSelectionModel *selection)
{
GtkMultiSelection *copy;
GListModel *model;
g_object_get (selection, "model", &model, NULL);
copy = GTK_MULTI_SELECTION (gtk_multi_selection_new (model));
if (GTK_IS_MULTI_SELECTION (selection))
{
GtkMultiSelection *multi = GTK_MULTI_SELECTION (selection);
gtk_set_free (copy->selected);
copy->selected = gtk_set_copy (multi->selected);
copy->last_selected = multi->last_selected;
}
else
{
guint pos, n;
guint start, n_items;
gboolean selected;
n = g_list_model_get_n_items (model);
n_items = 0;
for (pos = 0; pos < n; pos += n_items)
{
gtk_selection_model_query_range (selection, pos, &start, &n_items, &selected);
if (selected)
gtk_selection_model_select_range (GTK_SELECTION_MODEL (copy), start, n_items, FALSE);
}
}
g_object_unref (model);
return copy;
}

41
gtk/gtkmultiselection.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* Copyright © 2019 Red Hat, Inc.
*
* 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: Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __GTK_MULTI_SELECTION_H__
#define __GTK_MULTI_SELECTION_H__
#include <gtk/gtktypes.h>
#include <gtk/gtkselectionmodel.h>
G_BEGIN_DECLS
#define GTK_TYPE_MULTI_SELECTION (gtk_multi_selection_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkMultiSelection, gtk_multi_selection, GTK, MULTI_SELECTION, GObject)
GDK_AVAILABLE_IN_ALL
GListModel * gtk_multi_selection_new (GListModel *model);
GDK_AVAILABLE_IN_ALL
GtkMultiSelection * gtk_multi_selection_copy (GtkSelectionModel *selection);
G_END_DECLS
#endif /* __GTK_MULTI_SELECTION_H__ */

View File

@@ -29,6 +29,7 @@
#include "gtkprintbackendprivate.h"
static void gtk_print_backend_finalize (GObject *object);
static void gtk_print_backend_dispose (GObject *object);
static void gtk_print_backend_set_property (GObject *object,
guint prop_id,
@@ -251,6 +252,7 @@ gtk_print_backend_class_init (GtkPrintBackendClass *class)
backend_parent_class = g_type_class_peek_parent (class);
object_class->finalize = gtk_print_backend_finalize;
object_class->dispose = gtk_print_backend_dispose;
object_class->set_property = gtk_print_backend_set_property;
object_class->get_property = gtk_print_backend_get_property;
@@ -348,11 +350,21 @@ gtk_print_backend_dispose (GObject *object)
/* We unref the printers in dispose, not in finalize so that
* we can break refcount cycles with gtk_print_backend_destroy
*/
g_clear_object (&priv->printers);
g_list_store_remove_all (priv->printers);
backend_parent_class->dispose (object);
}
static void
gtk_print_backend_finalize (GObject *object)
{
GtkPrintBackend *backend = GTK_PRINT_BACKEND (object);
GtkPrintBackendPrivate *priv = backend->priv;
g_clear_object (&priv->printers);
backend_parent_class->finalize (object);
}
static void
fallback_printer_request_details (GtkPrinter *printer)
@@ -361,7 +373,7 @@ fallback_printer_request_details (GtkPrinter *printer)
static gboolean
fallback_printer_mark_conflicts (GtkPrinter *printer,
GtkPrinterOptionSet *options)
GtkPrinterOptionSet *options)
{
return FALSE;
}

351
gtk/gtkset.c Normal file
View File

@@ -0,0 +1,351 @@
/*
* Copyright © 2019 Red Hat, Inc.
*
* 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: Matthias Clasen <mclasen@redhat.com>
*/
#include "gtkset.h"
/* Store a set of unsigned integers as a sorted array of ranges.
*/
typedef struct
{
guint first;
guint n_items;
} Range;
struct _GtkSet
{
GArray *ranges;
};
typedef struct
{
GtkSet *set;
Range *current;
int idx;
guint pos;
} GtkRealSetIter;
GtkSet *
gtk_set_new (void)
{
GtkSet *set;
set = g_new (GtkSet, 1);
set->ranges = g_array_new (FALSE, FALSE, sizeof (Range));
return set;
}
GtkSet *
gtk_set_copy (GtkSet *set)
{
GtkSet *copy;
copy = g_new (GtkSet, 1);
copy->ranges = g_array_copy (set->ranges);
return copy;
}
void
gtk_set_free (GtkSet *set)
{
g_array_free (set->ranges, TRUE);
g_free (set);
}
gboolean
gtk_set_contains (GtkSet *set,
guint item)
{
int i;
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
if (item < r->first)
return FALSE;
if (item < r->first + r->n_items)
return TRUE;
}
return FALSE;
}
void
gtk_set_remove_all (GtkSet *set)
{
g_array_set_size (set->ranges, 0);
}
static int
range_compare (Range *r, Range *s)
{
int ret = 0;
if (r->first + r->n_items < s->first)
ret = -1;
else if (s->first + s->n_items < r->first)
ret = 1;
return ret;
}
void
gtk_set_add_range (GtkSet *set,
guint first_item,
guint n_items)
{
int i;
Range s;
int first = -1;
int last = -1;
s.first = first_item;
s.n_items = n_items;
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
int cmp = range_compare (&s, r);
if (cmp < 0)
break;
if (cmp == 0)
{
if (first < 0)
first = i;
last = i;
}
}
if (first > -1)
{
Range *r;
guint start;
guint end;
r = &g_array_index (set->ranges, Range, first);
start = MIN (s.first, r->first);
r = &g_array_index (set->ranges, Range, last);
end = MAX (s.first + s.n_items - 1, r->first + r->n_items - 1);
s.first = start;
s.n_items = end - start + 1;
g_array_remove_range (set->ranges, first, last - first + 1);
g_array_insert_val (set->ranges, first, s);
}
else
g_array_insert_val (set->ranges, i, s);
}
void
gtk_set_remove_range (GtkSet *set,
guint first_item,
guint n_items)
{
Range s;
int i;
int first = -1;
int last = -1;
s.first = first_item;
s.n_items = n_items;
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
int cmp = range_compare (&s, r);
if (cmp < 0)
break;
if (cmp == 0)
{
if (first < 0)
first = i;
last = i;
}
}
if (first > -1)
{
Range *r;
Range a[2];
int k = 0;
r = &g_array_index (set->ranges, Range, first);
if (r->first < s.first)
{
a[k].first = r->first;
a[k].n_items = s.first - r->first;
k++;
}
r = &g_array_index (set->ranges, Range, last);
if (r->first + r->n_items > s.first + s.n_items)
{
a[k].first = s.first + s.n_items;
a[k].n_items = r->first + r->n_items - a[k].first;
k++;
}
g_array_remove_range (set->ranges, first, last - first + 1);
if (k > 0)
g_array_insert_vals (set->ranges, first, a, k);
}
}
void
gtk_set_find_range (GtkSet *set,
guint position,
guint upper_bound,
guint *start,
guint *n_items,
gboolean *contained)
{
int i;
int last = 0;
if (position >= upper_bound)
{
*start = 0;
*n_items = 0;
*contained = FALSE;
return;
}
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
if (position < r->first)
{
*start = last;
*n_items = r->first - last;
*contained = FALSE;
return;
}
else if (r->first <= position && position < r->first + r->n_items)
{
*start = r->first;
*n_items = r->n_items;
*contained = TRUE;
return;
}
else
last = r->first + r->n_items;
}
*start = last;
*n_items = upper_bound - last;
*contained = FALSE;
}
void
gtk_set_add_item (GtkSet *set,
guint item)
{
gtk_set_add_range (set, item, 1);
}
void
gtk_set_remove_item (GtkSet *set,
guint item)
{
gtk_set_remove_range (set, item, 1);
}
/* This is peculiar operation: Replace every number n >= first by n + shift
* This is only supported for negative shifts if the shifting does not cause
* any ranges to overlap.
*/
void
gtk_set_shift (GtkSet *set,
guint first,
int shift)
{
int i;
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
if (r->first >= first)
r->first += shift;
}
}
void
gtk_set_iter_init (GtkSetIter *iter,
GtkSet *set)
{
GtkRealSetIter *ri = (GtkRealSetIter *)iter;
ri->set = set;
ri->idx = -1;
ri->current = 0;
}
gboolean
gtk_set_iter_next (GtkSetIter *iter,
guint *item)
{
GtkRealSetIter *ri = (GtkRealSetIter *)iter;
if (ri->idx == -1)
{
next_range:
ri->idx++;
if (ri->idx == ri->set->ranges->len)
return FALSE;
ri->current = &g_array_index (ri->set->ranges, Range, ri->idx);
ri->pos = ri->current->first;
}
else
{
ri->pos++;
if (ri->pos == ri->current->first + ri->current->n_items)
goto next_range;
}
*item = ri->pos;
return TRUE;
}
#if 0
void
gtk_set_dump (GtkSet *set)
{
int i;
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
g_print (" %u:%u", r->first, r->n_items);
}
g_print ("\n");
}
#endif

70
gtk/gtkset.h Normal file
View File

@@ -0,0 +1,70 @@
/*
* Copyright © 2019 Red Hat, Inc.
*
* 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: Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __GTK_SET_H__
#define __GTK_SET_H__
#include <glib.h>
typedef struct _GtkSet GtkSet;
typedef struct _GtkSetIter GtkSetIter;
struct _GtkSetIter
{
gpointer dummy1;
gpointer dummy2;
int dummy3;
int dummy4;
};
GtkSet *gtk_set_new (void);
void gtk_set_free (GtkSet *set);
GtkSet *gtk_set_copy (GtkSet *set);
gboolean gtk_set_contains (GtkSet *set,
guint item);
void gtk_set_remove_all (GtkSet *set);
void gtk_set_add_item (GtkSet *set,
guint item);
void gtk_set_remove_item (GtkSet *set,
guint item);
void gtk_set_add_range (GtkSet *set,
guint first,
guint n);
void gtk_set_remove_range (GtkSet *set,
guint first,
guint n);
void gtk_set_find_range (GtkSet *set,
guint position,
guint upper_bound,
guint *start,
guint *n_items,
gboolean *contained);
void gtk_set_shift (GtkSet *set,
guint first,
int shift);
void gtk_set_iter_init (GtkSetIter *iter,
GtkSet *set);
gboolean gtk_set_iter_next (GtkSetIter *iter,
guint *item);
#endif /* __GTK_SET_H__ */

View File

@@ -62,6 +62,7 @@
<child>
<object class="GtkColumnViewColumn" id="changes">
<property name="title"></property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="bind" handler="bind_changes_cb"/>

View File

@@ -45,6 +45,7 @@
<child>
<object class="GtkColumnViewColumn">
<property name="title">Type</property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_type_cb"/>
@@ -57,6 +58,7 @@
<child>
<object class="GtkColumnViewColumn">
<property name="title">Name</property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_name_cb"/>
@@ -68,6 +70,7 @@
<child>
<object class="GtkColumnViewColumn">
<property name="title">Label</property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_label_cb"/>

View File

@@ -54,6 +54,7 @@
<child>
<object class="GtkColumnViewColumn">
<property name="title" translatable="yes">Value</property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="bind" handler="bind_value_cb"/>

View File

@@ -62,6 +62,7 @@
<child>
<object class="GtkColumnViewColumn" id="path">
<property name="title" translatable="yes">Path</property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_name_cb"/>
@@ -92,6 +93,11 @@
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="filler">
<property name="expand">1</property>
</object>
</child>
</object>
</child>
</object>

View File

@@ -134,6 +134,7 @@ gtk_private_sources = files([
'gtkscaler.c',
'gtksearchengine.c',
'gtksearchenginemodel.c',
'gtkset.c',
'gtksizerequestcache.c',
'gtkstyleanimation.c',
'gtkstylecascade.c',
@@ -301,6 +302,7 @@ gtk_public_sources = files([
'gtkmodules.c',
'gtkmountoperation.c',
'gtkmultifilter.c',
'gtkmultiselection.c',
'gtkmultisorter.c',
'gtknativedialog.c',
'gtknomediafile.c',
@@ -574,6 +576,7 @@ gtk_public_headers = files([
'gtkmessagedialog.h',
'gtkmountoperation.h',
'gtkmultifilter.h',
'gtkmultiselection.h',
'gtkmultisorter.h',
'gtknative.h',
'gtknativedialog.h',

View File

@@ -114,7 +114,8 @@ test_type (gconstpointer data)
}
else if (g_type_is_a (type, GTK_TYPE_FILTER_LIST_MODEL) ||
g_type_is_a (type, GTK_TYPE_NO_SELECTION) ||
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION))
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION) ||
g_type_is_a (type, GTK_TYPE_MULTI_SELECTION))
{
GListStore *list_store = g_list_store_new (G_TYPE_OBJECT);
instance = g_object_new (type,
@@ -277,7 +278,8 @@ G_GNUC_END_IGNORE_DEPRECATIONS
if ((g_type_is_a (type, GTK_TYPE_FILTER_LIST_MODEL) ||
g_type_is_a (type, GTK_TYPE_NO_SELECTION) ||
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION)) &&
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION) ||
g_type_is_a (type, GTK_TYPE_MULTI_SELECTION)) &&
strcmp (pspec->name, "model") == 0)
continue;

View File

@@ -39,6 +39,7 @@ tests = [
['listbox'],
['main'],
['maplistmodel'],
['multiselection'],
['notify'],
['no-gtk-init'],
['object'],

View File

@@ -0,0 +1,393 @@
/*
* Copyright (C) 2019, Red Hat, Inc.
* Authors: Matthias Clasen <mclasen@redhat.com>
*
* 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 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/>.
*/
#include <locale.h>
#include <gtk/gtk.h>
static GQuark number_quark;
static GQuark changes_quark;
static GQuark selection_quark;
static guint
get (GListModel *model,
guint position)
{
GObject *object = g_list_model_get_item (model, position);
g_assert (object != NULL);
return GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark));
}
static char *
model_to_string (GListModel *model)
{
GString *string = g_string_new (NULL);
guint i;
for (i = 0; i < g_list_model_get_n_items (model); i++)
{
if (i > 0)
g_string_append (string, " ");
g_string_append_printf (string, "%u", get (model, i));
}
return g_string_free (string, FALSE);
}
static char *
selection_to_string (GListModel *model)
{
GString *string = g_string_new (NULL);
guint i;
for (i = 0; i < g_list_model_get_n_items (model); i++)
{
if (!gtk_selection_model_is_selected (GTK_SELECTION_MODEL (model), i))
continue;
if (string->len > 0)
g_string_append (string, " ");
g_string_append_printf (string, "%u", get (model, i));
}
return g_string_free (string, FALSE);
}
static GListStore *
new_store (guint start,
guint end,
guint step);
static GObject *
make_object (guint number)
{
GObject *object;
/* 0 cannot be differentiated from NULL, so don't use it */
g_assert (number != 0);
object = g_object_new (G_TYPE_OBJECT, NULL);
g_object_set_qdata (object, number_quark, GUINT_TO_POINTER (number));
return object;
}
static void
splice (GListStore *store,
guint pos,
guint removed,
guint *numbers,
guint added)
{
GObject **objects;
guint i;
objects = g_new0 (GObject *, added);
for (i = 0; i < added; i++)
objects[i] = make_object (numbers[i]);
g_list_store_splice (store, pos, removed, (gpointer *) objects, added);
for (i = 0; i < added; i++)
g_object_unref (objects[i]);
g_free (objects);
}
static void
add (GListStore *store,
guint number)
{
GObject *object = make_object (number);
g_list_store_append (store, object);
g_object_unref (object);
}
static void
insert (GListStore *store,
guint position,
guint number)
{
GObject *object = make_object (number);
g_list_store_insert (store, position, object);
g_object_unref (object);
}
#define assert_model(model, expected) G_STMT_START{ \
char *s = model_to_string (G_LIST_MODEL (model)); \
if (!g_str_equal (s, expected)) \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#model " == " #expected, s, "==", expected); \
g_free (s); \
}G_STMT_END
#define ignore_changes(model) G_STMT_START{ \
GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
g_string_set_size (changes, 0); \
}G_STMT_END
#define assert_changes(model, expected) G_STMT_START{ \
GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
if (!g_str_equal (changes->str, expected)) \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#model " == " #expected, changes->str, "==", expected); \
g_string_set_size (changes, 0); \
}G_STMT_END
#define assert_selection(model, expected) G_STMT_START{ \
char *s = selection_to_string (G_LIST_MODEL (model)); \
if (!g_str_equal (s, expected)) \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#model " == " #expected, s, "==", expected); \
g_free (s); \
}G_STMT_END
#define ignore_selection_changes(model) G_STMT_START{ \
GString *changes = g_object_get_qdata (G_OBJECT (model), selection_quark); \
g_string_set_size (changes, 0); \
}G_STMT_END
#define assert_selection_changes(model, expected) G_STMT_START{ \
GString *changes = g_object_get_qdata (G_OBJECT (model), selection_quark); \
if (!g_str_equal (changes->str, expected)) \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#model " == " #expected, changes->str, "==", expected); \
g_string_set_size (changes, 0); \
}G_STMT_END
static GListStore *
new_empty_store (void)
{
return g_list_store_new (G_TYPE_OBJECT);
}
static GListStore *
new_store (guint start,
guint end,
guint step)
{
GListStore *store = new_empty_store ();
guint i;
for (i = start; i <= end; i += step)
add (store, i);
return store;
}
static void
items_changed (GListModel *model,
guint position,
guint removed,
guint added,
GString *changes)
{
g_assert (removed != 0 || added != 0);
if (changes->len)
g_string_append (changes, ", ");
if (removed == 1 && added == 0)
{
g_string_append_printf (changes, "-%u", position);
}
else if (removed == 0 && added == 1)
{
g_string_append_printf (changes, "+%u", position);
}
else
{
g_string_append_printf (changes, "%u", position);
if (removed > 0)
g_string_append_printf (changes, "-%u", removed);
if (added > 0)
g_string_append_printf (changes, "+%u", added);
}
}
static void
selection_changed (GListModel *model,
guint position,
guint n_items,
GString *changes)
{
if (changes->len)
g_string_append (changes, ", ");
g_string_append_printf (changes, "%u:%u", position, n_items);
}
static void
free_changes (gpointer data)
{
GString *changes = data;
/* all changes must have been checked via assert_changes() before */
g_assert_cmpstr (changes->str, ==, "");
g_string_free (changes, TRUE);
}
static GtkSelectionModel *
new_model (GListStore *store)
{
GtkSelectionModel *result;
GString *changes;
result = GTK_SELECTION_MODEL (gtk_multi_selection_new (G_LIST_MODEL (store)));
changes = g_string_new ("");
g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes);
g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes);
changes = g_string_new ("");
g_object_set_qdata_full (G_OBJECT(result), selection_quark, changes, free_changes);
g_signal_connect (result, "selection-changed", G_CALLBACK (selection_changed), changes);
return result;
}
static void
test_create (void)
{
GtkSelectionModel *selection;
GListStore *store;
store = new_store (1, 5, 2);
selection = new_model (store);
assert_model (selection, "1 3 5");
assert_changes (selection, "");
assert_selection (selection, "");
assert_selection_changes (selection, "");
g_object_unref (store);
assert_model (selection, "1 3 5");
assert_changes (selection, "");
assert_selection (selection, "");
assert_selection_changes (selection, "");
g_object_unref (selection);
}
static void
test_changes (void)
{
GtkSelectionModel *selection;
GListStore *store;
store = new_store (1, 5, 1);
selection = new_model (store);
assert_model (selection, "1 2 3 4 5");
assert_changes (selection, "");
assert_selection (selection, "");
assert_selection_changes (selection, "");
g_list_store_remove (store, 3);
assert_model (selection, "1 2 3 5");
assert_changes (selection, "-3");
assert_selection (selection, "");
assert_selection_changes (selection, "");
insert (store, 3, 99);
assert_model (selection, "1 2 3 99 5");
assert_changes (selection, "+3");
assert_selection (selection, "");
assert_selection_changes (selection, "");
splice (store, 3, 2, (guint[]) { 97 }, 1);
assert_model (selection, "1 2 3 97");
assert_changes (selection, "3-2+1");
assert_selection (selection, "");
assert_selection_changes (selection, "");
g_object_unref (store);
g_object_unref (selection);
}
static void
test_selection (void)
{
GtkSelectionModel *selection;
GListStore *store;
gboolean ret;
store = new_store (1, 5, 1);
selection = new_model (store);
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_item (selection, 3, FALSE);
g_assert_true (ret);
assert_selection (selection, "4");
assert_selection_changes (selection, "3:1");
ret = gtk_selection_model_unselect_item (selection, 3);
g_assert_true (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "3:1");
ret = gtk_selection_model_select_item (selection, 1, FALSE);
g_assert_true (ret);
assert_selection (selection, "2");
assert_selection_changes (selection, "1:1");
ret = gtk_selection_model_select_range (selection, 3, 2, FALSE);
g_assert_true (ret);
assert_selection (selection, "2 4 5");
assert_selection_changes (selection, "3:2");
ret = gtk_selection_model_unselect_range (selection, 3, 2);
g_assert_true (ret);
assert_selection (selection, "2");
assert_selection_changes (selection, "3:2");
ret = gtk_selection_model_select_all (selection);
g_assert_true (ret);
assert_selection (selection, "1 2 3 4 5");
assert_selection_changes (selection, "0:5");
ret = gtk_selection_model_unselect_all (selection);
g_assert_true (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "0:5");
g_object_unref (store);
g_object_unref (selection);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
setlocale (LC_ALL, "C");
g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=%s");
number_quark = g_quark_from_static_string ("Hell and fire was spawned to be released.");
changes_quark = g_quark_from_static_string ("What did I see? Can I believe what I saw?");
selection_quark = g_quark_from_static_string ("Mana mana, badibidibi");
g_test_add_func ("/multiselection/create", test_create);
#if GLIB_CHECK_VERSION (2, 58, 0) /* g_list_store_splice() is broken before 2.58 */
g_test_add_func ("/multiselection/changes", test_changes);
#endif
g_test_add_func ("/multiselection/selection", test_selection);
return g_test_run ();
}

View File

@@ -457,7 +457,8 @@ test_type (gconstpointer data)
}
else if (g_type_is_a (type, GTK_TYPE_FILTER_LIST_MODEL) ||
g_type_is_a (type, GTK_TYPE_NO_SELECTION) ||
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION))
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION) ||
g_type_is_a (type, GTK_TYPE_MULTI_SELECTION))
{
GListStore *list_store = g_list_store_new (G_TYPE_OBJECT);
instance = g_object_new (type,

View File

@@ -71,7 +71,8 @@ test_finalize_object (gconstpointer data)
}
else if (g_type_is_a (test_type, GTK_TYPE_FILTER_LIST_MODEL) ||
g_type_is_a (test_type, GTK_TYPE_NO_SELECTION) ||
g_type_is_a (test_type, GTK_TYPE_SINGLE_SELECTION))
g_type_is_a (test_type, GTK_TYPE_SINGLE_SELECTION) ||
g_type_is_a (test_type, GTK_TYPE_MULTI_SELECTION))
{
GListStore *list_store = g_list_store_new (G_TYPE_OBJECT);
object = g_object_new (test_type,