Merge branch 'misc-multiselection' into 'master'

Misc multiselection

See merge request GNOME/gtk!2055
This commit is contained in:
Matthias Clasen 2020-06-06 17:54:42 +00:00
commit 1567db1f02
6 changed files with 183 additions and 12 deletions

View File

@ -350,6 +350,9 @@ gtk_selection_model_select_range
gtk_selection_model_unselect_range
gtk_selection_model_select_all
gtk_selection_model_unselect_all
GtkSelectionCallback
gtk_selection_model_select_callback
gtk_selection_model_unselect_callback
gtk_selection_model_query_range
<SUBSECTION>
gtk_selection_model_selection_changed

View File

@ -23,7 +23,6 @@
#include "gtkintl.h"
#include "gtkselectionmodel.h"
#include "gtksingleselection.h"
#include "gtkset.h"
/**
@ -34,6 +33,12 @@
*
* GtkMultiSelection is an implementation of the #GtkSelectionModel interface
* that allows selecting multiple elements.
*
* Note that due to the way the selection is stored, newly added items are
* always unselected, even if they were just removed from the model, and were
* selected before. In particular this means that changing the sort order of
* an underlying sort model will clear the selection. In other words, the
* selection is *not persistent*.
*/
struct _GtkMultiSelection
@ -175,32 +180,40 @@ gtk_multi_selection_add_or_remove (GtkSelectionModel *model,
gpointer data)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
guint pos, start, n;
guint pos, start, n_items;
gboolean in;
guint min, max;
guint n;
n = g_list_model_get_n_items (G_LIST_MODEL (self));
min = G_MAXUINT;
max = 0;
pos = 0;
do
for (pos = 0; pos < n; pos = start + n_items)
{
callback (pos, &start, &n, &in, data);
callback (pos, &start, &n_items, &in, data);
if (n_items == 0)
break;
g_assert (start <= pos && pos < start + n_items);
if (in)
{
if (start < min)
min = start;
if (start + n - 1 > max)
max = start + n - 1;
if (start + n_items - 1 > max)
max = start + n_items - 1;
if (add)
gtk_set_add_range (self->selected, start, n);
gtk_set_add_range (self->selected, start, n_items);
else
gtk_set_remove_range (self->selected, start, n);
gtk_set_remove_range (self->selected, start, n_items);
}
pos = start + n;
pos = start + n_items;
}
while (n > 0);
if (min <= max)
gtk_selection_model_selection_changed (model, min, max - min + 1);

View File

@ -342,6 +342,15 @@ gtk_selection_model_unselect_all (GtkSelectionModel *model)
return iface->unselect_all (model);
}
/**
* gtk_selection_model_select_callback:
* @model: a #GtkSelectionModel
* @callback: a #GtkSelectionCallback to determine items to select
* @data: data to pass to @callback
*
* Requests to select all items for which @callback returns
* @selected as TRUE.
*/
gboolean
gtk_selection_model_select_callback (GtkSelectionModel *model,
GtkSelectionCallback callback,
@ -352,6 +361,15 @@ gtk_selection_model_select_callback (GtkSelectionModel *model,
return GTK_SELECTION_MODEL_GET_IFACE (model)->select_callback (model, callback, data);
}
/**
* gtk_selection_model_unselect_callback:
* @model: a #GtkSelectionModel
* @callback: a #GtkSelectionCallback to determine items to select
* @data: data to pass to @callback
*
* Requests to unselect all items for which @callback returns
* @selected as TRUE.
*/
gboolean
gtk_selection_model_unselect_callback (GtkSelectionModel *model,
GtkSelectionCallback callback,

View File

@ -33,6 +33,29 @@ G_BEGIN_DECLS
GDK_AVAILABLE_IN_ALL
G_DECLARE_INTERFACE (GtkSelectionModel, gtk_selection_model, GTK, SELECTION_MODEL, GListModel)
/**
* GtkSelectionCallback:
* @position: the position to query
* @start_range: (out): returns the position of the first element of the range
* @n_items: (out): returns the size of the range
* @selected: (out): returns whether items in @range are selected
* @data: callback data
*
* Callback type for determining items to operate on with
* gtk_selection_model_select_callback() or
* gtk_selection_model_unselect_callback().
*
* The callback determines a range of consecutive items around
* @position which should either all
* be changed, in which case @selected is set to %TRUE, or all not
* be changed, in which case @selected is set to %FALSE.
*
* @start_range and @n_items are set to return the range.
*
* The callback will be called repeatedly to find all ranges
* to operate on until it has exhausted the items of the model,
* or until it returns an empty range (ie @n_items == 0).
*/
typedef void (* GtkSelectionCallback) (guint position,
guint *start_range,
guint *n_items,
@ -128,6 +151,7 @@ GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_select_callback (GtkSelectionModel *model,
GtkSelectionCallback callback,
gpointer data);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_unselect_callback (GtkSelectionModel *model,
GtkSelectionCallback callback,
gpointer data);

View File

@ -30,9 +30,14 @@
* @Title: GtkSingleSelection
* @see_also: #GtkSelectionModel
*
* GtkSingleSelection is an implementation of the #GtkSelectionModel interface
* GtkSingleSelection is an implementation of the #GtkSelectionModel interface
* that allows selecting a single element. It is the default selection method
* used by list widgets in GTK.
*
* Note that the selection is *persistent* -- if the selected item is removed
* and re-added in the same ::items-changed emission, it stays selected. In
* particular, this means that changing the sort order of an underlying sort
* model will preserve the selection.
*/
struct _GtkSingleSelection
{

View File

@ -372,6 +372,10 @@ test_selection (void)
g_object_unref (selection);
}
/* Verify that select_range with exclusive = TRUE
* sends a selection-changed signal that covers
* preexisting items that got unselected
*/
static void
test_select_range (void)
{
@ -403,6 +407,108 @@ test_select_range (void)
g_object_unref (selection);
}
/* Test that removing and readding items
* clears the selected state.
*/
static void
test_readd (void)
{
GtkSelectionModel *selection;
GListStore *store;
gboolean ret;
store = new_store (1, 5, 1);
selection = new_model (store);
assert_model (selection, "1 2 3 4 5");
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_range (selection, 2, 2, FALSE);
g_assert_true (ret);
assert_model (selection, "1 2 3 4 5");
assert_selection (selection, "3 4");
assert_selection_changes (selection, "2:2");
g_list_model_items_changed (G_LIST_MODEL (store), 1, 3, 3);
assert_changes (selection, "1-3+3");
assert_selection (selection, "");
g_object_unref (store);
g_object_unref (selection);
}
typedef struct {
guint start;
guint n;
gboolean in;
} SelectionData;
static void
select_some (guint position,
guint *start,
guint *n,
gboolean *selected,
gpointer data)
{
SelectionData *sdata = data;
guint i;
for (i = 0; sdata[i].n != 0; i++)
{
if (sdata[i].start <= position &&
position < sdata[i].start + sdata[i].n)
break;
}
*start = sdata[i].start;
*n = sdata[i].n;
*selected = sdata[i].in;
}
static void
test_callback (void)
{
GtkSelectionModel *selection;
gboolean ret;
GListStore *store;
SelectionData data[] = {
{ 0, 2, FALSE },
{ 2, 3, TRUE },
{ 5, 2, FALSE },
{ 6, 3, TRUE },
{ 9, 1, FALSE },
{ 0, 0, FALSE }
};
SelectionData more_data[] = {
{ 0, 3, FALSE },
{ 3, 1, TRUE },
{ 4, 3, FALSE },
{ 7, 1, TRUE },
{ 0, 0, FALSE }
};
store = new_store (1, 10, 1);
selection = new_model (store);
assert_model (selection, "1 2 3 4 5 6 7 8 9 10");
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_callback (selection, select_some, data);
g_assert_true (ret);
assert_selection (selection, "3 4 5 7 8 9");
assert_selection_changes (selection, "2:7");
ret = gtk_selection_model_unselect_callback (selection, select_some, more_data);
g_assert_true (ret);
assert_selection (selection, "3 5 7 9");
assert_selection_changes (selection, "3:5");
g_object_unref (store);
g_object_unref (selection);
}
int
main (int argc, char *argv[])
@ -421,6 +527,8 @@ main (int argc, char *argv[])
#endif
g_test_add_func ("/multiselection/selection", test_selection);
g_test_add_func ("/multiselection/select-range", test_select_range);
g_test_add_func ("/multiselection/readd", test_readd);
g_test_add_func ("/multiselection/callback", test_callback);
return g_test_run ();
}