Merge branch 'wip/otte/bitset' into 'master'

Improve selection handling API for rubberbanding

See merge request GNOME/gtk!2086
This commit is contained in:
Matthias Clasen 2020-06-26 12:43:49 +00:00
commit e04191a5ea
35 changed files with 21000 additions and 1761 deletions

View File

@ -30,7 +30,6 @@ struct _GtkColor
char *name;
GdkRGBA color;
int h, s, v;
gboolean selected;
};
enum {
@ -43,7 +42,6 @@ enum {
PROP_HUE,
PROP_SATURATION,
PROP_VALUE,
PROP_SELECTED,
N_COLOR_PROPS
};
@ -206,10 +204,6 @@ gtk_color_get_property (GObject *object,
g_value_set_int (value, self->v);
break;
case PROP_SELECTED:
g_value_set_boolean (value, self->selected);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@ -239,10 +233,6 @@ gtk_color_set_property (GObject *object,
self->v = round (100 * v);
break;
case PROP_SELECTED:
self->selected = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@ -284,8 +274,6 @@ gtk_color_class_init (GtkColorClass *klass)
g_param_spec_int ("saturation", NULL, NULL, 0, 100, 0, G_PARAM_READABLE);
color_properties[PROP_VALUE] =
g_param_spec_int ("value", NULL, NULL, 0, 100, 0, G_PARAM_READABLE);
color_properties[PROP_SELECTED] =
g_param_spec_boolean ("selected", NULL, NULL, FALSE, G_PARAM_READWRITE);
g_object_class_install_properties (gobject_class, N_COLOR_PROPS, color_properties);
}
@ -673,7 +661,7 @@ create_color_grid (void)
model = G_LIST_MODEL (gtk_sort_list_model_new (gtk_color_list_new (0), NULL));
selection = G_LIST_MODEL (gtk_property_selection_new (model, "selected"));
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

@ -48,6 +48,7 @@
<chapter id="Lists">
<title>GListModel support</title>
<xi:include href="xml/gtkbitset.xml" />
<xi:include href="xml/gtkexpression.xml" />
<xi:include href="xml/gtkfilterlistmodel.xml" />
<section>
@ -72,7 +73,6 @@
<xi:include href="xml/gtknoselection.xml" />
<xi:include href="xml/gtksingleselection.xml" />
<xi:include href="xml/gtkmultiselection.xml" />
<xi:include href="xml/gtkpropertyselection.xml" />
</section>
<xi:include href="xml/gtkbookmarklist.xml" />
<xi:include href="xml/gtkdirectorylist.xml" />

View File

@ -339,21 +339,65 @@ gtk_list_box_get_type
gtk_list_box_row_get_type
</SECTION>
<SECTION>
<FILE>gtkbitset</FILE>
<TITLE>GtkBitset</TITLE>
GtkBitset
gtk_bitset_ref
gtk_bitset_unref
gtk_bitset_new_empty
gtk_bitset_copy
<SUBSECTION>
gtk_bitset_contains
gtk_bitset_is_empty
gtk_bitset_equals
gtk_bitset_get_minimum
gtk_bitset_get_maximum
<SUBSECTION>
gtk_bitset_remove_all
gtk_bitset_add
gtk_bitset_remove
gtk_bitset_add_range
gtk_bitset_remove_range
gtk_bitset_add_range_closed
gtk_bitset_remove_range_closed
gtk_bitset_add_rectangle
gtk_bitset_remove_rectangle
gtk_bitset_union
gtk_bitset_intersect
gtk_bitset_subtract
gtk_bitset_difference
gtk_bitset_shift_left
gtk_bitset_shift_right
gtk_bitset_slice
<SUBSECTION>
GtkBitsetIter
gtk_bitset_iter_init_first
gtk_bitset_iter_init_last
gtk_bitset_iter_init_at
gtk_bitset_iter_next
gtk_bitset_iter_previous
gtk_bitset_iter_get_value
gtk_bitset_iter_is_valid
<SUBSECTION Private>
GTK_TYPE_BITSET
gtk_bitset_get_type
</SECTION>
<SECTION>
<FILE>gtkselectionmodel</FILE>
<TITLE>GtkSelectionModel</TITLE>
GtkSelectionModel
gtk_selection_model_is_selected
gtk_selection_model_get_selection
gtk_selection_model_get_selection_in_range
gtk_selection_model_select_item
gtk_selection_model_unselect_item
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
gtk_selection_model_set_selection
<SUBSECTION>
gtk_selection_model_selection_changed
<SUBSECTION Standard>
@ -404,17 +448,6 @@ gtk_multi_selection_new
gtk_multi_selection_get_type
</SECTION>
<SECTION>
<FILE>gtkpropertyselection</FILE>
<TITLE>GtkPropertySelection</TITLE>
GtkPropertySelection
gtk_property_selection_new
gtk_property_selection_get_model
gtk_property_selection_get_property
<SUBSECTION Private>
gtk_property_selection_get_type
</SECTION>
<SECTION>
<FILE>gtklistitem</FILE>
<TITLE>GtkListItem</TITLE>

View File

@ -18,6 +18,7 @@ gtk_aspect_frame_get_type
gtk_assistant_get_type
gtk_assistant_page_get_type
gtk_bin_layout_get_type
gtk_bitset_get_type
gtk_bookmark_list_get_type
gtk_box_get_type
gtk_box_layout_get_type
@ -171,7 +172,6 @@ gtk_print_operation_preview_get_type
gtk_print_settings_get_type
@DISABLE_ON_W32@gtk_print_unix_dialog_get_type
gtk_progress_bar_get_type
gtk_property_selection_get_type
gtk_radio_button_get_type
gtk_range_get_type
gtk_recent_manager_get_type

View File

@ -176,7 +176,6 @@ private_headers = [
'gtkroundedboxprivate.h',
'gtkscalerprivate.h',
'gtksearchentryprivate.h',
'gtkset.h',
'gtksettingsprivate.h',
'gtkshortcutcontrollerprivate.h',
'gtkshortcutsshortcutprivate.h',
@ -216,6 +215,7 @@ private_headers = [
'gtkwin32themeprivate.h',
'gtkwindowprivate.h',
'gtk-text-input-client-protocol.h',
'roaring.h',
]
images = [

View File

@ -47,6 +47,7 @@
#include <gtk/gtkaspectframe.h>
#include <gtk/gtkassistant.h>
#include <gtk/gtkbinlayout.h>
#include <gtk/gtkbitset.h>
#include <gtk/gtkbookmarklist.h>
#include <gtk/gtkborder.h>
#include <gtk/gtkboxlayout.h>
@ -200,7 +201,6 @@
#include <gtk/gtkprintoperationpreview.h>
#include <gtk/gtkprintsettings.h>
#include <gtk/gtkprogressbar.h>
#include <gtk/gtkpropertyselection.h>
#include <gtk/gtkradiobutton.h>
#include <gtk/gtkrange.h>
#include <gtk/gtkrecentmanager.h>

865
gtk/gtkbitset.c Normal file
View File

@ -0,0 +1,865 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkbitset.h"
#include "roaring.c"
/**
* SECTION:gtkbitset
* @Short_description: Sets of integers
* @Title: GtkBitset
* @See_also: GtkSelectionModel
*
* #GtkBitset is a data structure for representing a set of unsigned integers.
* Another name for this data structure is "bitmap".
*
* This version is based on [roaring bitmaps](https://roaringbitmap.org/).
*
* A bitset allows adding a set of integers and provides support for set operations
* like unions, intersections and checks for equality or if a value is contained
* in the set. #GtkBitset also contains various functions to query metadata about
* the bitset, such as the minimum or maximum values or its size.
*
* The fastest way to iterate values in a bitset is #GtkBitsetIter which allows
* quick iteration of all the values in a bitset.
*
* The main use case #GtkBitset is implementing complex selections for #GtkSelectionModel.
*/
/**
* GtkBitset: (ref-func gtk_bitset_ref) (unref-func gtk_bitset_unref)
*
* The `GtkBitset` structure contains only private data.
*/
struct _GtkBitset
{
int ref_count;
roaring_bitmap_t roaring;
};
G_DEFINE_BOXED_TYPE (GtkBitset, gtk_bitset,
gtk_bitset_ref,
gtk_bitset_unref)
/**
* gtk_bitset_ref:
* @self: (allow-none): a #GtkBitset
*
* Acquires a reference on the given #GtkBitset.
*
* Returns: (transfer none): the #GtkBitset with an additional reference
*/
GtkBitset *
gtk_bitset_ref (GtkBitset *self)
{
g_return_val_if_fail (self != NULL, NULL);
self->ref_count += 1;
return self;
}
/**
* gtk_bitset_unref:
* @self: (allow-none): a #GtkBitset
*
* Releases a reference on the given #GtkBitset.
*
* If the reference was the last, the resources associated to the @self are
* freed.
*/
void
gtk_bitset_unref (GtkBitset *self)
{
g_return_if_fail (self != NULL);
g_return_if_fail (self->ref_count > 0);
self->ref_count -= 1;
if (self->ref_count > 0)
return;
ra_clear (&self->roaring.high_low_container);
g_slice_free (GtkBitset, self);
}
/**
* gtk_bitset_contains:
* @self: a #GtkBitset
* @value: the value to check
*
* Checks if the given @value has been added to @bitset
*
* Returns: %TRUE if @self contains @value
**/
gboolean
gtk_bitset_contains (const GtkBitset *self,
guint value)
{
g_return_val_if_fail (self != NULL, FALSE);
return roaring_bitmap_contains (&self->roaring, value);
}
/**
* gtk_bitset_is_empty:
* @self: a #GtkBitset
*
* Check if no value is contained in bitset.
*
* Returns: %TRUE if @self is empty
**/
gboolean
gtk_bitset_is_empty (const GtkBitset *self)
{
g_return_val_if_fail (self != NULL, TRUE);
return roaring_bitmap_is_empty (&self->roaring);
}
/**
* gtk_bitset_equals:
* @self: a #GtkBitset
* @other: another #GtkBitset
*
* Returns %TRUE if @self and @other contain the same values.
*
* Returns: %TRUE if @self and @other contain the same values
**/
gboolean
gtk_bitset_equals (const GtkBitset *self,
const GtkBitset *other)
{
g_return_val_if_fail (self != NULL, other == NULL);
g_return_val_if_fail (other != NULL, FALSE);
if (self == other)
return TRUE;
return roaring_bitmap_equals (&self->roaring, &other->roaring);
}
/**
* gtk_bitset_get_minimum:
* @self: a #GtkBitset
*
* Returns the smallest value in @self. If @self is empty,
* G_MAXUINT is returned.
*
* Returns: The smallest value in @self
**/
guint
gtk_bitset_get_minimum (const GtkBitset *self)
{
g_return_val_if_fail (self != NULL, G_MAXUINT);
return roaring_bitmap_minimum (&self->roaring);
}
/**
* gtk_bitset_get_maximum:
* @self: a #GtkBitset
*
* Returns the largest value in @self. If @self is empty,
* 0 is returned.
*
* Returns: The largest value in @self
**/
guint
gtk_bitset_get_maximum (const GtkBitset *self)
{
g_return_val_if_fail (self != NULL, 0);
return roaring_bitmap_maximum (&self->roaring);
}
/**
* gtk_bitset_new_empty:
*
* Creates a new empty bitset.
*
* Returns: A new empty bitset.
**/
GtkBitset *
gtk_bitset_new_empty (void)
{
GtkBitset *self;
self = g_slice_new0 (GtkBitset);
self->ref_count = 1;
ra_init (&self->roaring.high_low_container);
return self;
}
/**
* gtk_bitset_copy:
* @self: a #GtkBitset
*
* Creates a copy of @self.
*
* Returns: (transfer full) A new bitset that contains the same
* values as @self
**/
GtkBitset *
gtk_bitset_copy (const GtkBitset *self)
{
GtkBitset *copy;
g_return_val_if_fail (self != NULL, NULL);
copy = gtk_bitset_new_empty ();
roaring_bitmap_overwrite (&copy->roaring, &self->roaring);
return copy;
}
/**
* gtk_bitset_remove_all:
* @self: a #GtkBitset
*
* Removes all values from the bitset so that it is empty again.
**/
void
gtk_bitset_remove_all (GtkBitset *self)
{
g_return_if_fail (self != NULL);
roaring_bitmap_clear (&self->roaring);
}
/**
* gtk_bitset_add:
* @self: a #GtkBitset
* @value: value to add
*
* Adds @value to @self if it wasn't part of it before.
*
* Returns: %TRUE if @value was not part of @self and @self
* was changed.
**/
gboolean
gtk_bitset_add (GtkBitset *self,
guint value)
{
g_return_val_if_fail (self != NULL, FALSE);
return roaring_bitmap_add_checked (&self->roaring, value);
}
/**
* gtk_bitset_remove:
* @self: a #GtkBitset
* @value: value to add
*
* Adds @value to @self if it wasn't part of it before.
*
* Returns: %TRUE if @value was part of @self and @self
* was changed.
**/
gboolean
gtk_bitset_remove (GtkBitset *self,
guint value)
{
g_return_val_if_fail (self != NULL, FALSE);
return roaring_bitmap_remove_checked (&self->roaring, value);
}
/**
* gtk_bitset_add_range:
* @self: a #GtkBitset
* @start: first value to add
* @n_items: number of consecutive values to add
*
* Adds all values from @start (inclusive) to @start + @n_items (exclusive)
* in @self.
**/
void
gtk_bitset_add_range (GtkBitset *self,
guint start,
guint n_items)
{
g_return_if_fail (self != NULL);
if (n_items == 0)
return;
/* overflow check, the == 0 is to allow add_range(G_MAXUINT, 1); */
g_return_if_fail (start + n_items == 0 || start + n_items > start);
roaring_bitmap_add_range_closed (&self->roaring, start, start + n_items - 1);
}
/**
* gtk_bitset_remove_range:
* @self: a #GtkBitset
* @start: first value to remove
* @n_items: number of consecutive values to remove
*
* Removes all values from @start (inclusive) to @start + @n_items (exclusive)
* in @self.
**/
void
gtk_bitset_remove_range (GtkBitset *self,
guint start,
guint n_items)
{
g_return_if_fail (self != NULL);
if (n_items == 0)
return;
/* overflow check, the == 0 is to allow add_range(G_MAXUINT, 1); */
g_return_if_fail (start + n_items == 0 || start + n_items > start);
roaring_bitmap_remove_range_closed (&self->roaring, start, start + n_items - 1);
}
/**
* gtk_bitset_add_range_closed:
* @self: a #GtkBitset
* @first: first value to add
* @last: last value to add
*
* Adds the closed range [@first, @last], so @first, @last and all values inbetween.
* @first must be smaller than @last.
**/
void
gtk_bitset_add_range_closed (GtkBitset *self,
guint first,
guint last)
{
g_return_if_fail (self != NULL);
g_return_if_fail (first <= last);
roaring_bitmap_add_range_closed (&self->roaring, first, last);
}
/**
* gtk_bitset_remove_range_closed:
* @self: a #GtkBitset
* @first: first value to remove
* @last: last value to remove
*
* Removes the closed range [@first, @last], so @first, @last and all values inbetween.
* @first must be smaller than @last.
**/
void
gtk_bitset_remove_range_closed (GtkBitset *self,
guint first,
guint last)
{
g_return_if_fail (self != NULL);
g_return_if_fail (first <= last);
roaring_bitmap_remove_range_closed (&self->roaring, first, last);
}
/**
* gtk_bitset_add_rectangle:
* @self: a #GtkBitset
* @start: first value to add
* @width: width of the rectangle
* @height: height of the rectangle
* @stride: rowstride of the rectangle
*
* Interprets the values as a 2-dimensional boolean grid with the given @stride
* and inside that grid, adds a rectangle with the given @width and @height.
**/
void
gtk_bitset_add_rectangle (GtkBitset *self,
guint start,
guint width,
guint height,
guint stride)
{
guint i;
g_return_if_fail (self != NULL);
g_return_if_fail (width <= stride);
g_return_if_fail (G_MAXUINT - start >= height * stride);
if (width == 0 || height == 0)
return;
for (i = 0; i < height; i++)
gtk_bitset_add_range (self, i * stride + start, width);
}
/**
* gtk_bitset_remove_rectangle:
* @self: a #GtkBitset
* @start: first value to remove
* @width: width of the rectangle
* @height: height of the rectangle
* @stride: rowstride of the rectangle
*
* Interprets the values as a 2-dimensional boolean grid with the given @stride
* and inside that grid, removes a rectangle with the given @width and @height.
**/
void
gtk_bitset_remove_rectangle (GtkBitset *self,
guint start,
guint width,
guint height,
guint stride)
{
guint i;
g_return_if_fail (self != NULL);
g_return_if_fail (width <= stride);
g_return_if_fail (G_MAXUINT - start >= height * stride);
if (width == 0 || height == 0)
return;
for (i = 0; i < height; i++)
gtk_bitset_remove_range (self, i * stride + start, width);
}
/**
* gtk_bitset_union:
* @self: a #GtkBitset
* @other: the #GtkBitset to union with
*
* Sets @self to be the union of @self and @other, that is add all values
* from @other into @self that weren't part of it.
*
* It is allowed for @self and @other to be the same bitset. Nothing will
* happen in that case.
**/
void
gtk_bitset_union (GtkBitset *self,
const GtkBitset *other)
{
g_return_if_fail (self != NULL);
g_return_if_fail (other != NULL);
if (self == other)
return;
roaring_bitmap_or_inplace (&self->roaring, &other->roaring);
}
/**
* gtk_bitset_intersect:
* @self: a #GtkBitset
* @other: the #GtkBitset to intersect with
*
* Sets @self to be the intersection of @self and @other, that is remove all values
* from @self that are not part of @other.
*
* It is allowed for @self and @other to be the same bitset. Nothing will
* happen in that case.
**/
void
gtk_bitset_intersect (GtkBitset *self,
const GtkBitset *other)
{
g_return_if_fail (self != NULL);
g_return_if_fail (other != NULL);
if (self == other)
return;
roaring_bitmap_and_inplace (&self->roaring, &other->roaring);
}
/**
* gtk_bitset_subtract:
* @self: a #GtkBitset
* @other: the #GtkBitset to subtract
*
* Sets @self to be the subtraction of @other from @self, that is remove
* all values from @self that are part of @other.
*
* It is allowed for @self and @other to be the same bitset. The bitset
* will be emptied in that case.
**/
void
gtk_bitset_subtract (GtkBitset *self,
const GtkBitset *other)
{
g_return_if_fail (self != NULL);
g_return_if_fail (other != NULL);
if (self == other)
{
roaring_bitmap_clear (&self->roaring);
return;
}
roaring_bitmap_andnot_inplace (&self->roaring, &other->roaring);
}
/**
* gtk_bitset_difference:
* @self: a #GtkBitset
* @other: the #GtkBitset to compute the difference from
*
* Sets @self to be the symmetric difference of @self and @other, that
* is set @self to contain all values that were either contained in @self
* or in @other, but not in both.
* This operation is also called an XOR.
*
* It is allowed for @self and @other to be the same bitset. The bitset
* will be emptied in that case.
**/
void
gtk_bitset_difference (GtkBitset *self,
const GtkBitset *other)
{
g_return_if_fail (self != NULL);
g_return_if_fail (other != NULL);
if (self == other)
{
roaring_bitmap_clear (&self->roaring);
return;
}
roaring_bitmap_xor_inplace (&self->roaring, &other->roaring);
}
/**
* gtk_bitset_shift_left:
* @self: a $GtkBitset
* @amount: amount to shift all values to the left
*
* Shifts all values in @self to the left by @amount. Values
* smaller than @amount are discarded.
**/
void
gtk_bitset_shift_left (GtkBitset *self,
guint amount)
{
GtkBitset *original;
GtkBitsetIter iter;
guint value;
gboolean loop;
g_return_if_fail (self != NULL);
if (amount == 0)
return;
original = gtk_bitset_copy (self);
gtk_bitset_remove_all (self);
for (loop = gtk_bitset_iter_init_at (&iter, original, amount, &value);
loop;
loop = gtk_bitset_iter_next (&iter, &value))
{
gtk_bitset_add (self, value - amount);
}
gtk_bitset_unref (original);
}
/**
* gtk_bitset_shift_left:
* @self: a $GtkBitset
* @amount: amount to shift all values to the right
*
* Shifts all values in @self to the right by @amount. Values
* that end up too large to be held in a #guint are discarded.
**/
void
gtk_bitset_shift_right (GtkBitset *self,
guint amount)
{
GtkBitset *original;
GtkBitsetIter iter;
guint value;
gboolean loop;
g_return_if_fail (self != NULL);
if (amount == 0)
return;
original = gtk_bitset_copy (self);
gtk_bitset_remove_all (self);
for (loop = gtk_bitset_iter_init_at (&iter, original, amount, &value);
loop && value >= G_MAXUINT - amount;
loop = gtk_bitset_iter_next (&iter, &value))
{
gtk_bitset_add (self, value + amount);
}
gtk_bitset_unref (original);
}
/**
* gtk_bitset_slice:
* @self: a #GtkBitset
* @position: position at which to slice
* @removed: number of values to remove
* @added: number of values to add
*
* This is a support function for #GListModel handling, by mirroring
* the #GlistModel::items-changed signal.
*
* First, it "cuts" the values from @position to @removed from
* the bitset. That is, it removes all those values and shifts
* all larger values to the left by @removed places.
*
* Then, it "pastes" new room into the bitset by shifting all values
* larger than @position by @added spaces to the right. This frees
* up space that can then be filled using
**/
void
gtk_bitset_slice (GtkBitset *self,
guint position,
guint removed,
guint added)
{
g_return_if_fail (self != NULL);
/* overflow */
g_return_if_fail (position + removed >= position);
g_return_if_fail (position + added >= position);
gtk_bitset_remove_range (self, position, removed);
if (removed != added)
{
GtkBitset *shift = gtk_bitset_copy (self);
gtk_bitset_remove_range (shift, 0, position);
gtk_bitset_remove_range (self, position, G_MAXUINT - position + 1);
if (added > removed)
gtk_bitset_shift_right (shift, added - removed);
else
gtk_bitset_shift_left (shift, removed - added);
gtk_bitset_union (self, shift);
gtk_bitset_unref (shift);
}
}
G_STATIC_ASSERT (sizeof (GtkBitsetIter) >= sizeof (roaring_uint32_iterator_t));
/**
* gtk_bitset_iter_init_first:
* @iter: (out): a pointer to a preallocated #GtkBitsetIter
* @set: a #GtkBitset
* @value: (out) (optional): Set to the first value in @set
*
* Initializes an iterator for @set and points it to the first
* value in @set. If @set is empty, %FALSE is returned and @value is set to
* %G_MAXUINT.
*
* Returns: %TRUE if a @set isn't empty.
**/
gboolean
gtk_bitset_iter_init_first (GtkBitsetIter *iter,
const GtkBitset *set,
guint *value)
{
roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter;
g_return_val_if_fail (iter != NULL, FALSE);
g_return_val_if_fail (set != NULL, FALSE);
roaring_init_iterator (&set->roaring, riter);
if (value)
*value = riter->has_value ? riter->current_value : 0;
return riter->has_value;
}
/**
* gtk_bitset_iter_init_last:
* @iter: (out): a pointer to a preallocated #GtkBitsetIter
* @set: a #GtkBitset
* @value: (out) (optional): Set to the last value in @set
*
* Initializes an iterator for @set and points it to the last
* value in @set. If @set is empty, %FALSE is returned.
*
* Returns: %TRUE if a @set isn't empty.
**/
gboolean
gtk_bitset_iter_init_last (GtkBitsetIter *iter,
const GtkBitset *set,
guint *value)
{
roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter;
g_return_val_if_fail (iter != NULL, FALSE);
g_return_val_if_fail (set != NULL, FALSE);
roaring_init_iterator_last (&set->roaring, riter);
if (value)
*value = riter->has_value ? riter->current_value : 0;
return riter->has_value;
}
/**
* gtk_bitset_iter_init_at:
* @iter: a #GtkBitsetIter
* @target: target value to start iterating at
* @value: (out) (optional): Set to the found value in @set
*
* Initializes @iter to point to @target. If @target is not found, finds
* the next value after it. If no value >= @target exists in @set, this
* function returns %FALSE.
*
* Returns: %TRUE if a value was found.
**/
gboolean
gtk_bitset_iter_init_at (GtkBitsetIter *iter,
const GtkBitset *set,
guint target,
guint *value)
{
roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter;
g_return_val_if_fail (iter != NULL, FALSE);
g_return_val_if_fail (set != NULL, FALSE);
roaring_init_iterator (&set->roaring, riter);
if (!roaring_move_uint32_iterator_equalorlarger (riter, target))
{
if (value)
*value = 0;
return FALSE;
}
if (value)
*value = riter->current_value;
return TRUE;
}
/**
* gtk_bitset_iter_next:
* @iter: (out): a pointer to a valid #GtkBitsetIter
* @value: (out) (optional): Set to the next value
*
* Moves @iter to the next value in the set. If it was already
* pointing to the last value in the set, %FALSE is returned and
* @iter is invalidated.
*
* Returns: %TRUE if a next value existed
**/
gboolean
gtk_bitset_iter_next (GtkBitsetIter *iter,
guint *value)
{
roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter;
g_return_val_if_fail (iter != NULL, FALSE);
if (!roaring_advance_uint32_iterator (riter))
{
if (value)
*value = 0;
return FALSE;
}
if (value)
*value = riter->current_value;
return TRUE;
}
/**
* gtk_bitset_iter_previous:
* @iter: (out): a pointer to a valid #GtkBitsetIter
* @value: (out) (optional): Set to the previous value
*
* Moves @iter to the previous value in the set. If it was already
* pointing to the first value in the set, %FALSE is returned and
* @iter is invalidated.
*
* Returns: %TRUE if a previous value existed
**/
gboolean
gtk_bitset_iter_previous (GtkBitsetIter *iter,
guint *value)
{
roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter;
g_return_val_if_fail (iter != NULL, FALSE);
if (!roaring_previous_uint32_iterator (riter))
{
if (value)
*value = 0;
return FALSE;
}
if (value)
*value = riter->current_value;
return TRUE;
}
/**
* gtk_bitset_iter_get_value:
* @iter: a #GtkBitsetIter
*
* Gets the current value that @iter points to.
*
* If @iter is not valid and gtk_bitset_iter_is_valid() returns
* %FALSE, this function returns 0.
*
* Returns: The current value pointer to by @iter
**/
guint
gtk_bitset_iter_get_value (const GtkBitsetIter *iter)
{
roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter;
g_return_val_if_fail (iter != NULL, 0);
if (!riter->has_value)
return 0;
return riter->current_value;
}
/**
* gtk_bitset_iter_is_valid:
* @iter: a #GtkBitsetIter
*
* Checks if @iter points to a valid value
*
* Returns: %TRUE if @iter points to a valid value
**/
gboolean
gtk_bitset_iter_is_valid (const GtkBitsetIter *iter)
{
roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter;
g_return_val_if_fail (iter != NULL, FALSE);
return riter->has_value;
}

151
gtk/gtkbitset.h Normal file
View File

@ -0,0 +1,151 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_BITSET_H__
#define __GTK_BITSET_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_BITSET (gtk_bitset_get_type ())
GDK_AVAILABLE_IN_ALL
GType gtk_bitset_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GtkBitset * gtk_bitset_ref (GtkBitset *self);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_unref (GtkBitset *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_contains (const GtkBitset *self,
guint value);
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_is_empty (const GtkBitset *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_equals (const GtkBitset *self,
const GtkBitset *other);
GDK_AVAILABLE_IN_ALL
guint gtk_bitset_get_minimum (const GtkBitset *self);
GDK_AVAILABLE_IN_ALL
guint gtk_bitset_get_maximum (const GtkBitset *self);
GDK_AVAILABLE_IN_ALL
GtkBitset * gtk_bitset_new_empty (void);
GDK_AVAILABLE_IN_ALL
GtkBitset * gtk_bitset_copy (const GtkBitset *self);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_remove_all (GtkBitset *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_add (GtkBitset *self,
guint value);
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_remove (GtkBitset *self,
guint value);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_add_range (GtkBitset *self,
guint start,
guint n_items);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_remove_range (GtkBitset *self,
guint start,
guint n_items);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_add_range_closed (GtkBitset *self,
guint first,
guint last);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_remove_range_closed (GtkBitset *self,
guint first,
guint last);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_add_rectangle (GtkBitset *self,
guint start,
guint width,
guint height,
guint stride);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_remove_rectangle (GtkBitset *self,
guint start,
guint width,
guint height,
guint stride);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_union (GtkBitset *self,
const GtkBitset *other);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_intersect (GtkBitset *self,
const GtkBitset *other);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_subtract (GtkBitset *self,
const GtkBitset *other);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_difference (GtkBitset *self,
const GtkBitset *other);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_shift_left (GtkBitset *self,
guint amount);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_shift_right (GtkBitset *self,
guint amount);
GDK_AVAILABLE_IN_ALL
void gtk_bitset_slice (GtkBitset *self,
guint position,
guint removed,
guint added);
typedef struct {gpointer private_data[10]; } GtkBitsetIter;
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_iter_init_first (GtkBitsetIter *iter,
const GtkBitset *set,
guint *value);
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_iter_init_last (GtkBitsetIter *iter,
const GtkBitset *set,
guint *value);
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_iter_init_at (GtkBitsetIter *iter,
const GtkBitset *set,
guint target,
guint *value);
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_iter_next (GtkBitsetIter *iter,
guint *value);
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_iter_previous (GtkBitsetIter *iter,
guint *value);
GDK_AVAILABLE_IN_ALL
guint gtk_bitset_iter_get_value (const GtkBitsetIter *iter);
GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_iter_is_valid (const GtkBitsetIter *iter);
G_END_DECLS
#endif /* __GTK_BITSET_H__ */

View File

@ -21,6 +21,7 @@
#include "gtkgridview.h"
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtklistbaseprivate.h"
#include "gtklistitemfactory.h"
@ -452,6 +453,36 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base,
return TRUE;
}
static GtkBitset *
gtk_grid_view_get_items_in_rect (GtkListBase *base,
const GdkRectangle *rect)
{
GtkGridView *self = GTK_GRID_VIEW (base);
guint first_row, last_row, first_column, last_column, n_items;
GtkBitset *result;
result = gtk_bitset_new_empty ();
n_items = gtk_list_base_get_n_items (base);
if (n_items == 0)
return result;
first_column = floor (rect->x / self->column_width);
last_column = floor ((rect->x + rect->width) / self->column_width);
if (!gtk_grid_view_get_cell_at_y (self, rect->y, &first_row, NULL, NULL))
first_row = rect->y < 0 ? 0 : n_items - 1;
if (!gtk_grid_view_get_cell_at_y (self, rect->y + rect->height, &last_row, NULL, NULL))
last_row = rect->y < 0 ? 0 : n_items - 1;
gtk_bitset_add_rectangle (result,
first_row + first_column,
last_column - first_column + 1,
(last_row - first_row) / self->n_columns + 1,
self->n_columns);
return result;
}
static guint
gtk_grid_view_move_focus_along (GtkListBase *base,
guint pos,
@ -687,43 +718,6 @@ cell_set_size (Cell *cell,
gtk_rb_tree_node_mark_dirty (cell);
}
static void
gtk_grid_view_size_allocate_child (GtkGridView *self,
GtkWidget *child,
int x,
int y,
int width,
int height)
{
GtkAllocation child_allocation;
if (gtk_list_base_get_orientation (GTK_LIST_BASE (self)) == GTK_ORIENTATION_VERTICAL)
{
child_allocation.x = x;
child_allocation.y = y;
child_allocation.width = width;
child_allocation.height = height;
}
else if (_gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_LTR)
{
child_allocation.x = y;
child_allocation.y = x;
child_allocation.width = height;
child_allocation.height = width;
}
else
{
int mirror_point = gtk_widget_get_width (GTK_WIDGET (self));
child_allocation.x = mirror_point - y - height;
child_allocation.y = x;
child_allocation.width = height;
child_allocation.height = width;
}
gtk_widget_size_allocate (child, &child_allocation, -1);
}
static int
gtk_grid_view_compute_total_height (GtkGridView *self)
{
@ -753,8 +747,6 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
int x, y;
guint i;
gtk_list_base_allocate_rubberband (GTK_LIST_BASE (widget));
orientation = gtk_list_base_get_orientation (GTK_LIST_BASE (self));
scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation);
opposite_orientation = OPPOSITE_ORIENTATION (orientation);
@ -873,7 +865,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
{
row_height += cell->size;
gtk_grid_view_size_allocate_child (self,
gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
cell->parent.widget,
x + ceil (self->column_width * i),
y,
@ -914,6 +906,8 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
}
}
}
gtk_list_base_allocate_rubberband (GTK_LIST_BASE (widget));
}
static void
@ -1037,6 +1031,7 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
list_base_class->list_item_augment_func = cell_augment;
list_base_class->get_allocation_along = gtk_grid_view_get_allocation_along;
list_base_class->get_allocation_across = gtk_grid_view_get_allocation_across;
list_base_class->get_items_in_rect = gtk_grid_view_get_items_in_rect;
list_base_class->get_position_from_allocation = gtk_grid_view_get_position_from_allocation;
list_base_class->move_focus_along = gtk_grid_view_move_focus_along;
list_base_class->move_focus_across = gtk_grid_view_move_focus_across;

View File

@ -22,6 +22,8 @@
#include "gtklistbaseprivate.h"
#include "gtkadjustment.h"
#include "gtkbitset.h"
#include "gtkdragsource.h"
#include "gtkdropcontrollermotion.h"
#include "gtkgesturedrag.h"
#include "gtkgizmoprivate.h"
@ -30,7 +32,6 @@
#include "gtkmultiselection.h"
#include "gtkorientable.h"
#include "gtkscrollable.h"
#include "gtkset.h"
#include "gtksingleselection.h"
#include "gtksnapshot.h"
#include "gtkstylecontextprivate.h"
@ -41,24 +42,15 @@ typedef struct _RubberbandData RubberbandData;
struct _RubberbandData
{
GtkWidget *widget;
GtkSet *active;
double x1, y1;
double x2, y2;
gboolean modify;
gboolean extend;
GtkWidget *widget; /* The rubberband widget */
GtkListItemTracker *start_tracker; /* The item we started dragging on */
double start_align_across; /* alignment in horizontal direction */
double start_align_along; /* alignment in vertical direction */
double pointer_x, pointer_y; /* mouse coordinates in widget space */
};
static void
rubberband_data_free (gpointer data)
{
RubberbandData *rdata = data;
g_clear_pointer (&rdata->widget, gtk_widget_unparent);
g_clear_pointer (&rdata->active, gtk_set_free);
g_free (rdata);
}
typedef struct _GtkListBasePrivate GtkListBasePrivate;
struct _GtkListBasePrivate
@ -1241,8 +1233,6 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_backslash, GDK_CONTROL_MASK, "list.unselect-all", NULL);
}
static void gtk_list_base_update_rubberband_selection (GtkListBase *self);
static gboolean
autoscroll_cb (GtkWidget *widget,
GdkFrameClock *frame_clock,
@ -1263,15 +1253,6 @@ autoscroll_cb (GtkWidget *widget,
delta_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]) - value;
if (priv->rubberband)
{
priv->rubberband->x2 += delta_x;
priv->rubberband->y2 += delta_y;
gtk_list_base_update_rubberband_selection (self);
}
gtk_widget_queue_draw (GTK_WIDGET (self));
if (delta_x != 0 || delta_y != 0)
{
return G_SOURCE_CONTINUE;
@ -1290,8 +1271,14 @@ add_autoscroll (GtkListBase *self,
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
priv->autoscroll_delta_x = delta_x;
priv->autoscroll_delta_y = delta_y;
if (gtk_list_base_adjustment_is_flipped (self, GTK_ORIENTATION_HORIZONTAL))
priv->autoscroll_delta_x = -delta_x;
else
priv->autoscroll_delta_x = delta_x;
if (gtk_list_base_adjustment_is_flipped (self, GTK_ORIENTATION_VERTICAL))
priv->autoscroll_delta_y = -delta_y;
else
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);
@ -1346,76 +1333,214 @@ update_autoscroll (GtkListBase *self,
remove_autoscroll (self);
}
/**
* gtk_list_base_size_allocate_child:
* @self: The listbase
* @child: The child
* @x: top left coordinate in the across direction
* @y: top right coordinate in the along direction
* @width: size in the across direction
* @height: size in the along direction
*
* Allocates a child widget in the list coordinate system,
* but with the coordinates already offset by the scroll
* offset.
**/
void
gtk_list_base_size_allocate_child (GtkListBase *self,
GtkWidget *child,
int x,
int y,
int width,
int height)
{
GtkAllocation child_allocation;
if (gtk_list_base_get_orientation (GTK_LIST_BASE (self)) == GTK_ORIENTATION_VERTICAL)
{
if (_gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_LTR)
{
child_allocation.x = x;
child_allocation.y = y;
child_allocation.width = width;
child_allocation.height = height;
}
else
{
int mirror_point = gtk_widget_get_width (GTK_WIDGET (self));
child_allocation.x = mirror_point - x - width;
child_allocation.y = y;
child_allocation.width = width;
child_allocation.height = height;
}
}
else
{
if (_gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_LTR)
{
child_allocation.x = y;
child_allocation.y = x;
child_allocation.width = height;
child_allocation.height = width;
}
else
{
int mirror_point = gtk_widget_get_width (GTK_WIDGET (self));
child_allocation.x = mirror_point - y - height;
child_allocation.y = x;
child_allocation.width = height;
child_allocation.height = width;
}
}
gtk_widget_size_allocate (child, &child_allocation, -1);
}
static void
gtk_list_base_widget_to_list (GtkListBase *self,
double x_widget,
double y_widget,
int *across_out,
int *along_out)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkWidget *widget = GTK_WIDGET (self);
if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
x_widget = gtk_widget_get_width (widget) - x_widget;
gtk_list_base_get_adjustment_values (self, OPPOSITE_ORIENTATION (priv->orientation), across_out, NULL, NULL);
gtk_list_base_get_adjustment_values (self, priv->orientation, along_out, NULL, NULL);
if (priv->orientation == GTK_ORIENTATION_VERTICAL)
{
*across_out += x_widget;
*along_out += y_widget;
}
else
{
*across_out += y_widget;
*along_out += x_widget;
}
}
static GtkBitset *
gtk_list_base_get_items_in_rect (GtkListBase *self,
const GdkRectangle *rect)
{
return GTK_LIST_BASE_GET_CLASS (self)->get_items_in_rect (self, rect);
}
static gboolean
gtk_list_base_get_rubberband_coords (GtkListBase *self,
GdkRectangle *rect)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
int x1, x2, y1, y2;
if (!priv->rubberband)
return FALSE;
if (priv->rubberband->start_tracker == NULL)
{
x1 = 0;
y1 = 0;
}
else
{
guint pos = gtk_list_item_tracker_get_position (priv->item_manager, priv->rubberband->start_tracker);
if (gtk_list_base_get_allocation_along (self, pos, &y1, &y2) &&
gtk_list_base_get_allocation_across (self, pos, &x1, &x2))
{
x1 += x2 * priv->rubberband->start_align_across;
y1 += y2 * priv->rubberband->start_align_along;
}
else
{
x1 = 0;
y1 = 0;
}
}
gtk_list_base_widget_to_list (self,
priv->rubberband->pointer_x, priv->rubberband->pointer_y,
&x2, &y2);
rect->x = MIN (x1, x2);
rect->y = MIN (y1, y2);
rect->width = ABS (x1 - x2) + 1;
rect->height = ABS (y1 - y2) + 1;
return TRUE;
}
void
gtk_list_base_allocate_rubberband (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkRequisition min_size;
GdkRectangle rect;
double x, y;
int min, nat;
int offset_x, offset_y;
if (!priv->rubberband)
if (!gtk_list_base_get_rubberband_coords (self, &rect))
return;
gtk_widget_measure (priv->rubberband->widget,
GTK_ORIENTATION_HORIZONTAL, -1,
&min, &nat, NULL, NULL);
gtk_widget_get_preferred_size (priv->rubberband->widget, &min_size, NULL);
rect.width = MAX (min_size.width, rect.width);
rect.height = MAX (min_size.height, rect.height);
x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
gtk_list_base_get_adjustment_values (self, OPPOSITE_ORIENTATION (priv->orientation), &offset_x, NULL, NULL);
gtk_list_base_get_adjustment_values (self, priv->orientation, &offset_y, NULL, NULL);
rect.x -= offset_x;
rect.y -= offset_y;
rect.x = MIN (priv->rubberband->x1, priv->rubberband->x2) - x;
rect.y = MIN (priv->rubberband->y1, priv->rubberband->y2) - y;
rect.width = ABS (priv->rubberband->x1 - priv->rubberband->x2) + 1;
rect.height = ABS (priv->rubberband->y1 - priv->rubberband->y2) + 1;
gtk_widget_size_allocate (priv->rubberband->widget, &rect, -1);
gtk_list_base_size_allocate_child (self,
priv->rubberband->widget,
rect.x, rect.y, rect.width, rect.height);
}
static void
gtk_list_base_start_rubberband (GtkListBase *self,
double x,
double y,
gboolean modify,
gboolean extend)
double y)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
double value_x, value_y;
cairo_rectangle_int_t item_area;
int list_x, list_y;
guint pos;
if (priv->rubberband)
return;
value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
gtk_list_base_widget_to_list (self, x, y, &list_x, &list_y);
if (!gtk_list_base_get_position_from_allocation (self, list_x, list_y, &pos, &item_area))
{
g_warning ("Could not start rubberbanding: No item\n");
return;
}
priv->rubberband = g_new0 (RubberbandData, 1);
priv->rubberband->x1 = priv->rubberband->x2 = x + value_x;
priv->rubberband->y1 = priv->rubberband->y2 = y + value_y;
priv->rubberband->start_tracker = gtk_list_item_tracker_new (priv->item_manager);
gtk_list_item_tracker_set_position (priv->item_manager, priv->rubberband->start_tracker, pos, 0, 0);
priv->rubberband->start_align_across = (double) (list_x - item_area.x) / item_area.width;
priv->rubberband->start_align_along = (double) (list_y - item_area.y) / item_area.height;
priv->rubberband->modify = modify;
priv->rubberband->extend = extend;
priv->rubberband->pointer_x = x;
priv->rubberband->pointer_y = y;
priv->rubberband->widget = gtk_gizmo_new ("rubberband",
NULL, NULL, NULL, NULL, NULL, NULL);
gtk_widget_set_parent (priv->rubberband->widget, GTK_WIDGET (self));
priv->rubberband->active = gtk_set_new ();
}
static void
range_cb (guint position,
guint *start,
guint *n_items,
gboolean *selected,
gpointer data)
{
GtkSet *set = data;
gtk_set_find_range (set, position, gtk_set_get_max (set) + 1, start, n_items, selected);
}
static void
gtk_list_base_stop_rubberband (GtkListBase *self)
gtk_list_base_stop_rubberband (GtkListBase *self,
gboolean modify,
gboolean extend)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkListItemManagerItem *item;
@ -1433,22 +1558,102 @@ gtk_list_base_stop_rubberband (GtkListBase *self)
}
model = gtk_list_item_manager_get_model (priv->item_manager);
if (model != NULL)
{
GtkBitset *selected, *mask;
GdkRectangle rect;
GtkBitset *rubberband_selection;
if (priv->rubberband->modify)
{
gtk_selection_model_unselect_callback (model, range_cb, priv->rubberband->active);
}
else
{
gtk_selection_model_select_callback (model,
!priv->rubberband->extend,
range_cb, priv->rubberband->active);
if (!gtk_list_base_get_rubberband_coords (self, &rect))
return;
rubberband_selection = gtk_list_base_get_items_in_rect (self, &rect);
if (gtk_bitset_is_empty (rubberband_selection))
{
gtk_bitset_unref (rubberband_selection);
return;
}
if (modify && extend) /* Ctrl + Shift */
{
GtkBitset *current;
guint min = gtk_bitset_get_minimum (rubberband_selection);
guint max = gtk_bitset_get_maximum (rubberband_selection);
/* toggle the rubberband, keep the rest */
current = gtk_selection_model_get_selection_in_range (model, min, max - min + 1);
selected = gtk_bitset_copy (current);
gtk_bitset_unref (current);
gtk_bitset_intersect (selected, rubberband_selection);
gtk_bitset_difference (selected, rubberband_selection);
mask = gtk_bitset_ref (rubberband_selection);
}
else if (modify) /* Ctrl */
{
/* select the rubberband, keep the rest */
selected = gtk_bitset_ref (rubberband_selection);
mask = gtk_bitset_ref (rubberband_selection);
}
else if (extend) /* Shift */
{
/* unselect the rubberband, keep the rest */
selected = gtk_bitset_new_empty ();
mask = gtk_bitset_ref (rubberband_selection);
}
else /* no modifer */
{
/* select the rubberband, clear the rest */
selected = gtk_bitset_ref (rubberband_selection);
mask = gtk_bitset_new_empty ();
gtk_bitset_add_range (mask, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
}
gtk_selection_model_set_selection (model, selected, mask);
gtk_bitset_unref (selected);
gtk_bitset_unref (mask);
gtk_bitset_unref (rubberband_selection);
}
g_clear_pointer (&priv->rubberband, rubberband_data_free);
gtk_list_item_tracker_free (priv->item_manager, priv->rubberband->start_tracker);
g_clear_pointer (&priv->rubberband->widget, gtk_widget_unparent);
g_free (priv->rubberband);
priv->rubberband = NULL;
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);
GtkListItemManagerItem *item;
GdkRectangle rect;
guint pos;
GtkBitset *rubberband_selection;
if (!gtk_list_base_get_rubberband_coords (self, &rect))
return;
rubberband_selection = gtk_list_base_get_items_in_rect (self, &rect);
pos = 0;
for (item = gtk_list_item_manager_get_first (priv->item_manager);
item != NULL;
item = gtk_rb_tree_node_get_next (item))
{
if (item->widget)
{
if (gtk_bitset_contains (rubberband_selection, pos))
gtk_widget_set_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE, FALSE);
else
gtk_widget_unset_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE);
}
pos += item->n_items;
}
gtk_bitset_unref (rubberband_selection);
}
static void
@ -1461,51 +1666,14 @@ gtk_list_base_update_rubberband (GtkListBase *self,
if (!priv->rubberband)
return;
priv->rubberband->x2 = x + gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
priv->rubberband->y2 = y + gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
priv->rubberband->pointer_x = x;
priv->rubberband->pointer_y = y;
gtk_list_base_update_rubberband_selection (self);
update_autoscroll (self, x, y);
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;
GtkListItemManagerItem *item;
gtk_list_base_allocate_rubberband (self);
gtk_widget_get_allocation (priv->rubberband->widget, &rect);
for (item = gtk_list_item_manager_get_first (priv->item_manager);
item != NULL;
item = gtk_rb_tree_node_get_next (item))
{
guint pos;
if (!item->widget)
continue;
pos = gtk_list_item_manager_get_item_position (priv->item_manager, item);
gtk_widget_get_allocation (item->widget, &alloc);
if (gdk_rectangle_intersect (&rect, &alloc, &alloc))
{
gtk_set_add_item (priv->rubberband->active, pos);
gtk_widget_set_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE, FALSE);
}
else
{
gtk_set_remove_item (priv->rubberband->active, pos);
gtk_widget_unset_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE);
}
}
gtk_widget_queue_allocate (GTK_WIDGET (self));
}
static void
@ -1529,27 +1697,25 @@ get_selection_modifiers (GtkGesture *gesture,
*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)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
double start_x, start_y;
gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
if (!priv->rubberband)
{
if (!gtk_drag_check_threshold (GTK_WIDGET (self), 0, 0, offset_x, offset_y))
return;
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
gtk_list_base_start_rubberband (self, start_x, start_y);
}
gtk_list_base_update_rubberband (self, start_x + offset_x, start_y + offset_y);
}
@ -1559,8 +1725,11 @@ gtk_list_base_drag_end (GtkGestureDrag *gesture,
double offset_y,
GtkListBase *self)
{
gboolean modify, extend;
gtk_list_base_drag_update (gesture, offset_x, offset_y, self);
gtk_list_base_stop_rubberband (self);
get_selection_modifiers (GTK_GESTURE (gesture), &modify, &extend);
gtk_list_base_stop_rubberband (self, modify, extend);
}
void
@ -1577,7 +1746,6 @@ gtk_list_base_set_enable_rubberband (GtkListBase *self,
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));

View File

@ -54,6 +54,8 @@ struct _GtkListBaseClass
int along,
guint *pos,
cairo_rectangle_int_t *area);
GtkBitset * (* get_items_in_rect) (GtkListBase *self,
const cairo_rectangle_int_t *rect);
guint (* move_focus_along) (GtkListBase *self,
guint pos,
int steps);
@ -99,10 +101,17 @@ 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_allocate_rubberband (GtkListBase *self);
void gtk_list_base_size_allocate_child (GtkListBase *self,
GtkWidget *child,
int x,
int y,
int width,
int height);
#endif /* __GTK_LIST_BASE_PRIVATE_H__ */

View File

@ -88,10 +88,10 @@ GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemMana
guint gtk_list_item_manager_get_size (GtkListItemManager *self);
void gtk_list_item_manager_set_single_click_activate
(GtkListItemManager *self,
gboolean single_click_activate);
(GtkListItemManager *self,
gboolean single_click_activate);
gboolean gtk_list_item_manager_get_single_click_activate
(GtkListItemManager *self);
(GtkListItemManager *self);
GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self);
void gtk_list_item_tracker_free (GtkListItemManager *self,

View File

@ -21,6 +21,7 @@
#include "gtklistview.h"
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtklistbaseprivate.h"
#include "gtklistitemmanagerprivate.h"
@ -374,6 +375,36 @@ gtk_list_view_get_allocation_across (GtkListBase *base,
return TRUE;
}
static GtkBitset *
gtk_list_view_get_items_in_rect (GtkListBase *base,
const cairo_rectangle_int_t *rect)
{
GtkListView *self = GTK_LIST_VIEW (base);
guint first, last, n_items;
GtkBitset *result;
ListRow *row;
result = gtk_bitset_new_empty ();
n_items = gtk_list_base_get_n_items (base);
if (n_items == 0)
return result;
row = gtk_list_view_get_row_at_y (self, rect->y, NULL);
if (row)
first = gtk_list_item_manager_get_item_position (self->item_manager, row);
else
first = rect->y < 0 ? 0 : n_items - 1;
row = gtk_list_view_get_row_at_y (self, rect->y + rect->height, NULL);
if (row)
last = gtk_list_item_manager_get_item_position (self->item_manager, row);
else
last = rect->y < 0 ? 0 : n_items - 1;
gtk_bitset_add_range_closed (result, first, last);
return result;
}
static guint
gtk_list_view_move_focus_along (GtkListBase *base,
guint pos,
@ -553,43 +584,6 @@ gtk_list_view_measure (GtkWidget *widget,
gtk_list_view_measure_across (widget, orientation, for_size, minimum, natural);
}
static void
gtk_list_view_size_allocate_child (GtkListView *self,
GtkWidget *child,
int x,
int y,
int width,
int height)
{
GtkAllocation child_allocation;
if (gtk_list_base_get_orientation (GTK_LIST_BASE (self)) == GTK_ORIENTATION_VERTICAL)
{
child_allocation.x = x;
child_allocation.y = y;
child_allocation.width = width;
child_allocation.height = height;
}
else if (_gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_LTR)
{
child_allocation.x = y;
child_allocation.y = x;
child_allocation.width = height;
child_allocation.height = width;
}
else
{
int mirror_point = gtk_widget_get_width (GTK_WIDGET (self));
child_allocation.x = mirror_point - y - height;
child_allocation.y = x;
child_allocation.width = height;
child_allocation.height = width;
}
gtk_widget_size_allocate (child, &child_allocation, -1);
}
static void
gtk_list_view_size_allocate (GtkWidget *widget,
int width,
@ -604,8 +598,6 @@ gtk_list_view_size_allocate (GtkWidget *widget,
GtkOrientation orientation, opposite_orientation;
GtkScrollablePolicy scroll_policy;
gtk_list_base_allocate_rubberband (GTK_LIST_BASE (self));
orientation = gtk_list_base_get_orientation (GTK_LIST_BASE (self));
opposite_orientation = OPPOSITE_ORIENTATION (orientation);
scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation);
@ -685,7 +677,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
{
if (row->parent.widget)
{
gtk_list_view_size_allocate_child (self,
gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
row->parent.widget,
x,
y,
@ -695,6 +687,8 @@ gtk_list_view_size_allocate (GtkWidget *widget,
y += row->height * row->parent.n_items;
}
gtk_list_base_allocate_rubberband (GTK_LIST_BASE (self));
}
static void
@ -810,6 +804,7 @@ gtk_list_view_class_init (GtkListViewClass *klass)
list_base_class->list_item_augment_func = list_row_augment;
list_base_class->get_allocation_along = gtk_list_view_get_allocation_along;
list_base_class->get_allocation_across = gtk_list_view_get_allocation_across;
list_base_class->get_items_in_rect = gtk_list_view_get_items_in_rect;
list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation;
list_base_class->move_focus_along = gtk_list_view_move_focus_along;
list_base_class->move_focus_across = gtk_list_view_move_focus_across;

View File

@ -21,9 +21,9 @@
#include "gtkmultiselection.h"
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtkselectionmodel.h"
#include "gtkset.h"
/**
* SECTION:gtkmultiselection
@ -47,7 +47,8 @@ struct _GtkMultiSelection
GListModel *model;
GtkSet *selected;
GtkBitset *selected;
GHashTable *items; /* item => position */
};
struct _GtkMultiSelectionClass
@ -103,175 +104,106 @@ gtk_multi_selection_is_selected (GtkSelectionModel *model,
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
return gtk_set_contains (self->selected, position);
return gtk_bitset_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);
guint min = G_MAXUINT;
guint max = 0;
if (exclusive)
{
min = gtk_set_get_min (self->selected);
max = gtk_set_get_max (self->selected);
gtk_set_remove_all (self->selected);
}
gtk_set_add_range (self->selected, position, n_items);
min = MIN (position, min);
max = MAX (max, position + n_items - 1);
gtk_selection_model_selection_changed (model, min, max - min + 1);
return TRUE;
}
static gboolean
gtk_multi_selection_unselect_range (GtkSelectionModel *model,
guint position,
guint n_items)
static GtkBitset *
gtk_multi_selection_get_selection_in_range (GtkSelectionModel *model,
guint pos,
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;
return gtk_bitset_ref (self->selected);
}
static gboolean
gtk_multi_selection_select_item (GtkSelectionModel *model,
guint position,
gboolean exclusive)
static void
gtk_multi_selection_toggle_selection (GtkMultiSelection *self,
GtkBitset *changes)
{
return gtk_multi_selection_select_range (model, position, 1, exclusive);
}
GListModel *model = G_LIST_MODEL (self);
GtkBitsetIter change_iter, selected_iter;
GtkBitset *selected;
guint change_pos, selected_pos;
gboolean more;
static gboolean
gtk_multi_selection_unselect_item (GtkSelectionModel *model,
guint position)
{
return gtk_multi_selection_unselect_range (model, position, 1);
}
gtk_bitset_difference (self->selected, changes);
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);
}
selected = gtk_bitset_copy (changes);
gtk_bitset_intersect (selected, self->selected);
static gboolean
gtk_multi_selection_unselect_all (GtkSelectionModel *model)
{
return gtk_multi_selection_unselect_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
}
if (!gtk_bitset_iter_init_first (&selected_iter, selected, &selected_pos))
selected_pos = G_MAXUINT;
static gboolean
gtk_multi_selection_add_or_remove (GtkSelectionModel *model,
gboolean unselect_rest,
gboolean add,
GtkSelectionCallback callback,
gpointer data)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
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;
if (unselect_rest)
for (more = gtk_bitset_iter_init_first (&change_iter, changes, &change_pos);
more;
more = gtk_bitset_iter_next (&change_iter, &change_pos))
{
min = gtk_set_get_min (self->selected);
max = gtk_set_get_max (self->selected);
gtk_set_remove_all (self->selected);
}
gpointer item = g_list_model_get_item (model, change_pos);
for (pos = 0; pos < n; pos = start + n_items)
{
callback (pos, &start, &n_items, &in, data);
if (n_items == 0)
break;
g_assert (start <= pos && pos < start + n_items);
if (in)
if (change_pos < selected_pos)
{
if (start < min)
min = start;
if (start + n_items - 1 > max)
max = start + n_items - 1;
if (add)
gtk_set_add_range (self->selected, start, n_items);
else
gtk_set_remove_range (self->selected, start, n_items);
g_hash_table_remove (self->items, item);
g_object_unref (item);
}
else
{
g_assert (change_pos == selected_pos);
pos = start + n_items;
g_hash_table_insert (self->items, item, GUINT_TO_POINTER (change_pos));
if (!gtk_bitset_iter_next (&selected_iter, &selected_pos))
selected_pos = G_MAXUINT;
}
}
gtk_bitset_unref (selected);
}
static gboolean
gtk_multi_selection_set_selection (GtkSelectionModel *model,
GtkBitset *selected,
GtkBitset *mask)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
GtkBitset *changes;
guint min, max, n_items;
/* changes = (self->selected XOR selected) AND mask
* But doing it this way avoids looking at all values outside the mask
*/
changes = gtk_bitset_copy (selected);
gtk_bitset_difference (changes, self->selected);
gtk_bitset_intersect (changes, mask);
min = gtk_bitset_get_minimum (changes);
max = gtk_bitset_get_maximum (changes);
/* sanity check */
n_items = g_list_model_get_n_items (self->model);
if (max >= n_items)
{
gtk_bitset_remove_range_closed (changes, n_items, max);
max = gtk_bitset_get_maximum (changes);
}
/* actually do the change */
gtk_multi_selection_toggle_selection (self, changes);
gtk_bitset_unref (changes);
if (min <= max)
gtk_selection_model_selection_changed (model, min, max - min + 1);
return TRUE;
}
static gboolean
gtk_multi_selection_select_callback (GtkSelectionModel *model,
gboolean unselect_rest,
GtkSelectionCallback callback,
gpointer data)
{
return gtk_multi_selection_add_or_remove (model, unselect_rest, TRUE, callback, data);
}
static gboolean
gtk_multi_selection_unselect_callback (GtkSelectionModel *model,
GtkSelectionCallback callback,
gpointer data)
{
return gtk_multi_selection_add_or_remove (model, FALSE, FALSE, callback, data);
}
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->select_callback = gtk_multi_selection_select_callback;
iface->unselect_callback = gtk_multi_selection_unselect_callback;
iface->query_range = gtk_multi_selection_query_range;
iface->get_selection_in_range = gtk_multi_selection_get_selection_in_range;
iface->set_selection = gtk_multi_selection_set_selection;
}
G_DEFINE_TYPE_EXTENDED (GtkMultiSelection, gtk_multi_selection, G_TYPE_OBJECT, 0,
@ -287,8 +219,57 @@ gtk_multi_selection_items_changed_cb (GListModel *model,
guint added,
GtkMultiSelection *self)
{
gtk_set_remove_range (self->selected, position, removed);
gtk_set_shift (self->selected, position, (int)added - (int)removed);
GHashTableIter iter;
gpointer item, pos_pointer;
GHashTable *pending = NULL;
guint i;
gtk_bitset_slice (self->selected, position, removed, added);
g_hash_table_iter_init (&iter, self->items);
while (g_hash_table_iter_next (&iter, &item, &pos_pointer))
{
guint pos = GPOINTER_TO_UINT (pos_pointer);
if (pos < position)
continue;
else if (pos >= position + removed)
g_hash_table_iter_replace (&iter, GUINT_TO_POINTER (pos - removed + added));
else /* if pos is in the removed range */
{
if (added == 0)
{
g_hash_table_iter_remove (&iter);
}
else
{
g_hash_table_iter_steal (&iter);
if (pending == NULL)
pending = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
g_hash_table_add (pending, item);
}
}
}
for (i = position; pending != NULL && i < position + added; i++)
{
item = g_list_model_get_item (model, i);
if (g_hash_table_contains (pending, item))
{
gtk_bitset_add (self->selected, i);
g_hash_table_insert (self->items, item, GUINT_TO_POINTER (i));
g_hash_table_remove (pending, item);
if (g_hash_table_size (pending) == 0)
g_clear_pointer (&pending, g_hash_table_unref);
}
else
{
g_object_unref (item);
}
}
g_clear_pointer (&pending, g_hash_table_unref);
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
}
@ -306,9 +287,9 @@ gtk_multi_selection_clear_model (GtkMultiSelection *self)
static void
gtk_multi_selection_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (object);
@ -332,9 +313,9 @@ gtk_multi_selection_set_property (GObject *object,
static void
gtk_multi_selection_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (object);
@ -357,7 +338,8 @@ gtk_multi_selection_dispose (GObject *object)
gtk_multi_selection_clear_model (self);
g_clear_pointer (&self->selected, gtk_set_free);
g_clear_pointer (&self->selected, gtk_bitset_unref);
g_clear_pointer (&self->items, g_hash_table_unref);
G_OBJECT_CLASS (gtk_multi_selection_parent_class)->dispose (object);
}
@ -389,7 +371,8 @@ gtk_multi_selection_class_init (GtkMultiSelectionClass *klass)
static void
gtk_multi_selection_init (GtkMultiSelection *self)
{
self->selected = gtk_set_new ();
self->selected = gtk_bitset_new_empty ();
self->items = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
}
/**

View File

@ -21,6 +21,7 @@
#include "gtknoselection.h"
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtkselectionmodel.h"
@ -96,25 +97,19 @@ gtk_no_selection_is_selected (GtkSelectionModel *model,
return FALSE;
}
static void
gtk_no_selection_query_range (GtkSelectionModel *model,
guint position,
guint *start_range,
guint *n_range,
gboolean *selected)
static GtkBitset *
gtk_no_selection_get_selection_in_range (GtkSelectionModel *model,
guint pos,
guint n_items)
{
GtkNoSelection *self = GTK_NO_SELECTION (model);
*start_range = 0;
*n_range = g_list_model_get_n_items (self->model);
*selected = FALSE;
return gtk_bitset_new_empty ();
}
static void
gtk_no_selection_selection_model_init (GtkSelectionModelInterface *iface)
{
iface->is_selected = gtk_no_selection_is_selected;
iface->query_range = gtk_no_selection_query_range;
iface->is_selected = gtk_no_selection_is_selected;
iface->get_selection_in_range = gtk_no_selection_get_selection_in_range;
}
G_DEFINE_TYPE_EXTENDED (GtkNoSelection, gtk_no_selection, G_TYPE_OBJECT, 0,

View File

@ -1,532 +0,0 @@
/*
* Copyright © 2020 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 "gtkpropertyselection.h"
#include "gtkintl.h"
#include "gtkselectionmodel.h"
/**
* SECTION:gtkpropertyselection
* @Short_description: A selection model that uses an item property
* @Title: GtkPropertySelection
* @see_also: #GtkSelectionModel
*
* GtkPropertySelection is an implementation of the #GtkSelectionModel
* interface that stores the selected state for each item in a property
* of the item.
*
* The property named by #GtkPropertySelection:property must be writable
* boolean property of the item type. GtkPropertySelection preserves the
* selected state of items when they are added to the model, but it does
* not listen to changes of the property while the item is a part of the
* model. It assumes that it has *exclusive* access to the property.
*
* The advantage of storing the selected state in item properties is that
* the state is *persistent* -- when an item is removed and re-added to
* the model, it will still have the same selection state. In particular,
* this makes the selection persist across changes of the sort order if
* the underlying model is a #GtkSortListModel.
*/
struct _GtkPropertySelection
{
GObject parent_instance;
GListModel *model;
char *property;
};
struct _GtkPropertySelectionClass
{
GObjectClass parent_class;
};
enum {
PROP_0,
PROP_MODEL,
PROP_PROPERTY,
N_PROPS,
};
static GParamSpec *properties[N_PROPS] = { NULL, };
static GType
gtk_property_selection_get_item_type (GListModel *list)
{
GtkPropertySelection *self = GTK_PROPERTY_SELECTION (list);
return g_list_model_get_item_type (self->model);
}
static guint
gtk_property_selection_get_n_items (GListModel *list)
{
GtkPropertySelection *self = GTK_PROPERTY_SELECTION (list);
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_property_selection_get_item (GListModel *list,
guint position)
{
GtkPropertySelection *self = GTK_PROPERTY_SELECTION (list);
return g_list_model_get_item (self->model, position);
}
static void
gtk_property_selection_list_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_property_selection_get_item_type;
iface->get_n_items = gtk_property_selection_get_n_items;
iface->get_item = gtk_property_selection_get_item;
}
static gboolean
is_selected (GtkPropertySelection *self,
guint position)
{
gpointer item;
gboolean ret;
item = g_list_model_get_item (self->model, position);
g_object_get (item, self->property, &ret, NULL);
g_object_unref (item);
return ret;
}
static void
set_selected (GtkPropertySelection *self,
guint position,
gboolean selected)
{
gpointer item;
item = g_list_model_get_item (self->model, position);
g_object_set (item, self->property, selected, NULL);
g_object_unref (item);
}
static gboolean
gtk_property_selection_is_selected (GtkSelectionModel *model,
guint position)
{
return is_selected (GTK_PROPERTY_SELECTION (model), position);
}
static gboolean
gtk_property_selection_select_range (GtkSelectionModel *model,
guint position,
guint n_items,
gboolean exclusive)
{
GtkPropertySelection *self = GTK_PROPERTY_SELECTION (model);
guint i;
guint n;
n = g_list_model_get_n_items (G_LIST_MODEL (self));
if (exclusive)
{
for (i = 0; i < n; i++)
set_selected (self, i, FALSE);
}
for (i = position; i < position + n_items; i++)
set_selected (self, i, TRUE);
/* FIXME: do better here */
if (exclusive)
gtk_selection_model_selection_changed (model, 0, n);
else
gtk_selection_model_selection_changed (model, position, n_items);
return TRUE;
}
static gboolean
gtk_property_selection_unselect_range (GtkSelectionModel *model,
guint position,
guint n_items)
{
GtkPropertySelection *self = GTK_PROPERTY_SELECTION (model);
guint i;
for (i = position; i < position + n_items; i++)
set_selected (self, i, FALSE);
gtk_selection_model_selection_changed (model, position, n_items);
return TRUE;
}
static gboolean
gtk_property_selection_select_item (GtkSelectionModel *model,
guint position,
gboolean exclusive)
{
return gtk_property_selection_select_range (model, position, 1, exclusive);
}
static gboolean
gtk_property_selection_unselect_item (GtkSelectionModel *model,
guint position)
{
return gtk_property_selection_unselect_range (model, position, 1);
}
static gboolean
gtk_property_selection_select_all (GtkSelectionModel *model)
{
return gtk_property_selection_select_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)), FALSE);
}
static gboolean
gtk_property_selection_unselect_all (GtkSelectionModel *model)
{
return gtk_property_selection_unselect_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
}
static gboolean
gtk_property_selection_add_or_remove (GtkSelectionModel *model,
gboolean unselect_rest,
gboolean add,
GtkSelectionCallback callback,
gpointer data)
{
GtkPropertySelection *self = GTK_PROPERTY_SELECTION (model);
guint pos, start, n, n_items;
gboolean in;
guint min, max;
guint i;
n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (unselect_rest)
{
for (i = 0; i < n_items; i++)
set_selected (self, i, FALSE);
}
min = G_MAXUINT;
max = 0;
pos = 0;
do
{
callback (pos, &start, &n, &in, data);
if (in)
{
if (start < min)
min = start;
if (start + n - 1 > max)
max = start + n - 1;
for (i = start; i < start + n; i++)
set_selected (self, i, add);
}
pos = start + n;
}
while (n > 0);
/* FIXME: do better here */
if (unselect_rest)
gtk_selection_model_selection_changed (model, 0, n_items);
else if (min <= max)
gtk_selection_model_selection_changed (model, min, max - min + 1);
return TRUE;
}
static gboolean
gtk_property_selection_select_callback (GtkSelectionModel *model,
gboolean unselect_rest,
GtkSelectionCallback callback,
gpointer data)
{
return gtk_property_selection_add_or_remove (model, unselect_rest, TRUE, callback, data);
}
static gboolean
gtk_property_selection_unselect_callback (GtkSelectionModel *model,
GtkSelectionCallback callback,
gpointer data)
{
return gtk_property_selection_add_or_remove (model, FALSE, FALSE, callback, data);
}
static void
gtk_property_selection_query_range (GtkSelectionModel *model,
guint position,
guint *start_range,
guint *n_items,
gboolean *selected)
{
GtkPropertySelection *self = GTK_PROPERTY_SELECTION (model);
guint n;
gboolean sel;
guint start, end;
n = g_list_model_get_n_items (G_LIST_MODEL (self));
sel = is_selected (self, position);
start = position;
while (start > 0)
{
if (is_selected (self, start - 1) != sel)
break;
start--;
}
end = position;
while (end + 1 < n)
{
if (is_selected (self, end + 1) != sel)
break;
end++;
}
*start_range = start;
*n_items = end - start + 1;
*selected = sel;
}
static void
gtk_property_selection_selection_model_init (GtkSelectionModelInterface *iface)
{
iface->is_selected = gtk_property_selection_is_selected;
iface->select_item = gtk_property_selection_select_item;
iface->unselect_item = gtk_property_selection_unselect_item;
iface->select_range = gtk_property_selection_select_range;
iface->unselect_range = gtk_property_selection_unselect_range;
iface->select_all = gtk_property_selection_select_all;
iface->unselect_all = gtk_property_selection_unselect_all;
iface->select_callback = gtk_property_selection_select_callback;
iface->unselect_callback = gtk_property_selection_unselect_callback;
iface->query_range = gtk_property_selection_query_range;
}
G_DEFINE_TYPE_EXTENDED (GtkPropertySelection, gtk_property_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_property_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
gtk_property_selection_selection_model_init))
static void
gtk_property_selection_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkPropertySelection *self)
{
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
}
static void
gtk_property_selection_clear_model (GtkPropertySelection *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model,
gtk_property_selection_items_changed_cb,
self);
g_clear_object (&self->model);
}
static void
gtk_property_selection_real_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPropertySelection *self = GTK_PROPERTY_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_property_selection_items_changed_cb),
self);
break;
case PROP_PROPERTY:
{
GObjectClass *class;
GParamSpec *prop;
self->property = g_value_dup_string (value);
g_warn_if_fail (self->property != NULL);
class = g_type_class_ref (g_list_model_get_item_type (self->model));
prop = g_object_class_find_property (class, self->property);
g_warn_if_fail (prop != NULL &&
prop->value_type == G_TYPE_BOOLEAN &&
((prop->flags & (G_PARAM_READABLE|G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY)) ==
(G_PARAM_READABLE|G_PARAM_WRITABLE)));
g_type_class_unref (class);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_property_selection_real_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkPropertySelection *self = GTK_PROPERTY_SELECTION (object);
switch (prop_id)
{
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_PROPERTY:
g_value_set_string (value, self->property);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_property_selection_dispose (GObject *object)
{
GtkPropertySelection *self = GTK_PROPERTY_SELECTION (object);
gtk_property_selection_clear_model (self);
g_free (self->property);
G_OBJECT_CLASS (gtk_property_selection_parent_class)->dispose (object);
}
static void
gtk_property_selection_class_init (GtkPropertySelectionClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->get_property = gtk_property_selection_real_get_property;
gobject_class->set_property = gtk_property_selection_real_set_property;
gobject_class->dispose = gtk_property_selection_dispose;
/**
* GtkPropertySelection: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);
properties[PROP_PROPERTY] =
g_param_spec_string ("property",
P_("Property"),
P_("Item property to store selection state in"),
NULL,
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_property_selection_init (GtkPropertySelection *self)
{
}
/**
* gtk_property_selection_new:
* @model: (transfer none): the #GListModel to manage
* @property: the item property to use
*
* Creates a new property selection to handle @model.
*
* @property must be the name of a writable boolean property
* of the item type of @model.
*
* Note that GtkPropertySelection does not monitor the property
* for changes while the item is part of the model, but it does
* inherit the initial value when an item is added to the model.
*
* Returns: (transfer full) (type GtkPropertySelection): a new #GtkPropertySelection
**/
GListModel *
gtk_property_selection_new (GListModel *model,
const char *property)
{
g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
return g_object_new (GTK_TYPE_PROPERTY_SELECTION,
"model", model,
"property", property,
NULL);
}
/**
* gtk_property_selection_get_model:
* @self: a #GtkPropertySelection
*
* Gets the underlying model.
*
* Returns: (transfer none): the underlying model
*/
GListModel *
gtk_property_selection_get_model (GtkPropertySelection *self)
{
g_return_val_if_fail (GTK_IS_PROPERTY_SELECTION (self), NULL);
return self->model;
}
/**
* gtk_property_selection_get_property:
* @self: a #GtkPropertySelection
*
* Gets the name of the item property that @self stores
* the selection in.
*
* Returns: the name of the property
*/
const char *
gtk_property_selection_get_property (GtkPropertySelection *self)
{
g_return_val_if_fail (GTK_IS_PROPERTY_SELECTION (self), NULL);
return self->property;
}

View File

@ -1,45 +0,0 @@
/*
* 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_PROPERTY_SELECTION_H__
#define __GTK_PROPERTY_SELECTION_H__
#include <gtk/gtktypes.h>
#include <gtk/gtkselectionmodel.h>
G_BEGIN_DECLS
#define GTK_TYPE_PROPERTY_SELECTION (gtk_property_selection_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkPropertySelection, gtk_property_selection, GTK, PROPERTY_SELECTION, GObject)
GDK_AVAILABLE_IN_ALL
GListModel * gtk_property_selection_new (GListModel *model,
const char *property);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_property_selection_get_model (GtkPropertySelection *self);
GDK_AVAILABLE_IN_ALL
const char * gtk_property_selection_get_property (GtkPropertySelection *self);
G_END_DECLS
#endif /* __GTK_PROPERTY_SELECTION_H__ */

View File

@ -21,6 +21,7 @@
#include "gtkselectionmodel.h"
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtkmarshalers.h"
@ -79,7 +80,33 @@ static gboolean
gtk_selection_model_default_is_selected (GtkSelectionModel *model,
guint position)
{
return FALSE;
GtkBitset *bitset;
gboolean selected;
bitset = gtk_selection_model_get_selection_in_range (model, position, 1);
selected = gtk_bitset_contains (bitset, position);
gtk_bitset_unref (bitset);
return selected;
}
static GtkBitset *
gtk_selection_model_default_get_selection_in_range (GtkSelectionModel *model,
guint position,
guint n_items)
{
GtkBitset *bitset;
guint i;
bitset = gtk_bitset_new_empty ();
for (i = position; i < position + n_items; i++)
{
if (gtk_selection_model_is_selected (model, i))
gtk_bitset_add (bitset, i);
}
return bitset;
}
static gboolean
@ -87,13 +114,48 @@ gtk_selection_model_default_select_item (GtkSelectionModel *model,
guint position,
gboolean unselect_rest)
{
return FALSE;
GtkBitset *selected;
GtkBitset *mask;
gboolean result;
selected = gtk_bitset_new_empty ();
gtk_bitset_add (selected, position);
if (unselect_rest)
{
mask = gtk_bitset_new_empty ();
gtk_bitset_add_range (mask, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
}
else
{
mask = gtk_bitset_ref (selected);
}
result = gtk_selection_model_set_selection (model, selected, mask);
gtk_bitset_unref (selected);
gtk_bitset_unref (mask);
return result;
}
static gboolean
gtk_selection_model_default_unselect_item (GtkSelectionModel *model,
guint position)
{
return FALSE;
GtkBitset *selected;
GtkBitset *mask;
gboolean result;
selected = gtk_bitset_new_empty ();
mask = gtk_bitset_new_empty ();
gtk_bitset_add (mask, position);
result = gtk_selection_model_set_selection (model, selected, mask);
gtk_bitset_unref (selected);
gtk_bitset_unref (mask);
return result;
}
static gboolean
@ -102,7 +164,28 @@ gtk_selection_model_default_select_range (GtkSelectionModel *model,
guint n_items,
gboolean unselect_rest)
{
return FALSE;
GtkBitset *selected;
GtkBitset *mask;
gboolean result;
selected = gtk_bitset_new_empty ();
gtk_bitset_add_range (selected, position, n_items);
if (unselect_rest)
{
mask = gtk_bitset_new_empty ();
gtk_bitset_add_range (mask, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
}
else
{
mask = gtk_bitset_ref (selected);
}
result = gtk_selection_model_set_selection (model, selected, mask);
gtk_bitset_unref (selected);
gtk_bitset_unref (mask);
return result;
}
static gboolean
@ -110,24 +193,20 @@ gtk_selection_model_default_unselect_range (GtkSelectionModel *model,
guint position,
guint n_items)
{
return FALSE;
}
GtkBitset *selected;
GtkBitset *mask;
gboolean result;
static gboolean
gtk_selection_model_default_select_callback (GtkSelectionModel *model,
gboolean unselect_rest,
GtkSelectionCallback callback,
gpointer data)
{
return FALSE;
}
selected = gtk_bitset_new_empty ();
mask = gtk_bitset_new_empty ();
gtk_bitset_add_range (mask, position, n_items);
static gboolean
gtk_selection_model_default_unselect_callback (GtkSelectionModel *model,
GtkSelectionCallback callback,
gpointer data)
{
return FALSE;
result = gtk_selection_model_set_selection (model, selected, mask);
gtk_bitset_unref (selected);
gtk_bitset_unref (mask);
return result;
}
static gboolean
@ -142,40 +221,26 @@ gtk_selection_model_default_unselect_all (GtkSelectionModel *model)
return gtk_selection_model_unselect_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
}
static void
gtk_selection_model_default_query_range (GtkSelectionModel *model,
guint position,
guint *start_range,
guint *n_items,
gboolean *selected)
static gboolean
gtk_selection_model_default_set_selection (GtkSelectionModel *model,
GtkBitset *selected,
GtkBitset *mask)
{
*start_range = position;
if (position >= g_list_model_get_n_items (G_LIST_MODEL (model)))
{
*n_items = 0;
*selected = FALSE;
}
else
{
*n_items = 1;
*selected = gtk_selection_model_is_selected (model, position);
}
return FALSE;
}
static void
gtk_selection_model_default_init (GtkSelectionModelInterface *iface)
{
iface->is_selected = gtk_selection_model_default_is_selected;
iface->get_selection_in_range = gtk_selection_model_default_get_selection_in_range;
iface->select_item = gtk_selection_model_default_select_item;
iface->unselect_item = gtk_selection_model_default_unselect_item;
iface->select_range = gtk_selection_model_default_select_range;
iface->unselect_range = gtk_selection_model_default_unselect_range;
iface->select_all = gtk_selection_model_default_select_all;
iface->unselect_all = gtk_selection_model_default_unselect_all;
iface->select_callback = gtk_selection_model_default_select_callback;
iface->unselect_callback = gtk_selection_model_default_unselect_callback;
iface->query_range = gtk_selection_model_default_query_range;
iface->set_selection = gtk_selection_model_default_set_selection;
/**
* GtkSelectionModel::selection-changed
@ -225,6 +290,62 @@ gtk_selection_model_is_selected (GtkSelectionModel *model,
return iface->is_selected (model, position);
}
/**
* gtk_selection_model_get_selection:
* @model: a #GtkSelectionModel
*
* Gets the set containing all currently selected items in the model.
*
* This function may be slow, so if you are only interested in single item,
* consider using gtk_selection_model_is_selected() or if you are only
* interested in a few consider gtk_selection_model_get_selection_in_range().
*
* Returns: (transfer full): a #GtkBitset containing all the values currently
* selected in @model. If no items are selected, the bitset is empty.
* The bitset must not be modified.
**/
GtkBitset *
gtk_selection_model_get_selection (GtkSelectionModel *model)
{
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), gtk_bitset_new_empty ());
return gtk_selection_model_get_selection_in_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
}
/**
* gtk_selection_model_get_selection_in_range:
* @model: a #GtkSelectionModel
* @position: start of the queired range
* @n_items: number of items in the queried range
*
* Gets a set containing a set where the values in the range [position,
* position + n_items) match the selected state of the items in that range.
* All values outside that range are undefined.
*
* This function is an optimization for gtk_selection_model_get_selection() when
* you are only interested in part of the model's selected state. A common use
* case is in response to the :selection-changed signal.
*
* Returns: A #GtkBitset that matches the selection state for the given state
* with all other values being undefined.
* The bitset must not be modified.
**/
GtkBitset *
gtk_selection_model_get_selection_in_range (GtkSelectionModel *model,
guint position,
guint n_items)
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), gtk_bitset_new_empty ());
if (n_items == 0)
return gtk_bitset_new_empty ();
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->get_selection_in_range (model, position, n_items);
}
/**
* gtk_selection_model_select_item:
* @model: a #GtkSelectionModel
@ -232,6 +353,9 @@ gtk_selection_model_is_selected (GtkSelectionModel *model,
* @unselect_rest: whether previously selected items should be unselected
*
* Requests to select an item in the model.
*
* Returns: %TRUE if this action was supported and no fallback should be
* tried. This does not mean the item was selected.
*/
gboolean
gtk_selection_model_select_item (GtkSelectionModel *model,
@ -240,7 +364,7 @@ gtk_selection_model_select_item (GtkSelectionModel *model,
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->select_item (model, position, unselect_rest);
@ -252,6 +376,9 @@ gtk_selection_model_select_item (GtkSelectionModel *model,
* @position: the position of the item to unselect
*
* Requests to unselect an item in the model.
*
* Returns: %TRUE if this action was supported and no fallback should be
* tried. This does not mean the item was unselected.
*/
gboolean
gtk_selection_model_unselect_item (GtkSelectionModel *model,
@ -259,7 +386,7 @@ gtk_selection_model_unselect_item (GtkSelectionModel *model,
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->unselect_item (model, position);
@ -273,6 +400,9 @@ gtk_selection_model_unselect_item (GtkSelectionModel *model,
* @unselect_rest: whether previously selected items should be unselected
*
* Requests to select a range of items in the model.
*
* Returns: %TRUE if this action was supported and no fallback should be
* tried. This does not mean the range was selected.
*/
gboolean
gtk_selection_model_select_range (GtkSelectionModel *model,
@ -282,7 +412,7 @@ gtk_selection_model_select_range (GtkSelectionModel *model,
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->select_range (model, position, n_items, unselect_rest);
@ -295,6 +425,9 @@ gtk_selection_model_select_range (GtkSelectionModel *model,
* @n_items: the number of items to unselect
*
* Requests to unselect a range of items in the model.
*
* Returns: %TRUE if this action was supported and no fallback should be
* tried. This does not mean the range was unselected.
*/
gboolean
gtk_selection_model_unselect_range (GtkSelectionModel *model,
@ -303,7 +436,7 @@ gtk_selection_model_unselect_range (GtkSelectionModel *model,
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->unselect_range (model, position, n_items);
@ -314,13 +447,16 @@ gtk_selection_model_unselect_range (GtkSelectionModel *model,
* @model: a #GtkSelectionModel
*
* Requests to select all items in the model.
*
* Returns: %TRUE if this action was supported and no fallback should be
* tried. This does not mean that all items are now selected.
*/
gboolean
gtk_selection_model_select_all (GtkSelectionModel *model)
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->select_all (model);
@ -331,96 +467,74 @@ gtk_selection_model_select_all (GtkSelectionModel *model)
* @model: a #GtkSelectionModel
*
* Requests to unselect all items in the model.
*
* Returns: %TRUE if this action was supported and no fallback should be
* tried. This does not mean that all items are now unselected.
*/
gboolean
gtk_selection_model_unselect_all (GtkSelectionModel *model)
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->unselect_all (model);
}
/**
* gtk_selection_model_select_callback:
* gtk_selection_model_set_selection:
* @model: a #GtkSelectionModel
* @unselect_rest: whether previously selected items should be unselected
* @callback: (scope call): a #GtkSelectionCallback to determine items to select
* @data: data to pass to @callback
* @selected: bitmask specifying if items should be selected or
* unselected
* @mask: bitmask specifying which items should be updated
*
* Requests to select all items for which @callback returns
* @selected as TRUE.
*/
* This is the most advanced selection updating method that allows
* the most fine-grained control over selection changes.
* If you can, you should try the simpler versions, as implementations
* are more likely to implement support for those.
*
* Requests that the selection state of all positions set in @mask be
* updated to the respecitve value in the @selected bitmask.
*
* In pseudocode, it would look something like this:
*
* |[<!-- language="C" -->
* for (i = 0; i < n_items; i++)
* {
* // don't change values not in the mask
* if (!gtk_bitset_contains (mask, i))
* continue;
*
* if (gtk_bitset_contains (selected, i))
* select_item (i);
* else
* unselect_item (i);
* }
*
* gtk_selection_model_selection_changed (model, first_changed_item, n_changed_items);
* ]|
*
* @mask and @selected must not be modified. They may refer to the same bitset,
* which would mean that every item in the set should be selected.
*
* Returns: %TRUE if this action was supported and no fallback should be
* tried. This does not mean that all items were updated according
* to the inputs.
**/
gboolean
gtk_selection_model_select_callback (GtkSelectionModel *model,
gboolean unselect_rest,
GtkSelectionCallback callback,
gpointer data)
{
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE);
return GTK_SELECTION_MODEL_GET_IFACE (model)->select_callback (model, unselect_rest, callback, data);
}
/**
* gtk_selection_model_unselect_callback:
* @model: a #GtkSelectionModel
* @callback: (scope call): 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,
gpointer data)
{
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE);
return GTK_SELECTION_MODEL_GET_IFACE (model)->unselect_callback (model, callback, data);
}
/**
* gtk_selection_model_query_range:
* @model: a #GtkSelectionModel
* @position: the position inside the range
* @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
*
* This function allows to query the selection status of multiple elements at once.
* It is passed a position and returns a range of elements of uniform selection status.
*
* If @position is greater than the number of items in @model, @n_items is set to 0.
* Otherwise the returned range is guaranteed to include the passed-in position, so
* @n_items will be >= 1.
*
* Positions directly adjacent to the returned range may have the same selection
* status as the returned range.
*
* This is an optimization function to make iterating over a model faster when few
* items are selected. However, it is valid behavior for implementations to use a
* naive implementation that only ever returns a single element.
*/
void
gtk_selection_model_query_range (GtkSelectionModel *model,
guint position,
guint *start_range,
guint *n_items,
gboolean *selected)
gtk_selection_model_set_selection (GtkSelectionModel *model,
GtkBitset *selected,
GtkBitset *mask)
{
GtkSelectionModelInterface *iface;
g_return_if_fail (GTK_IS_SELECTION_MODEL (model));
g_return_if_fail (start_range != NULL);
g_return_if_fail (n_items != NULL);
g_return_if_fail (selected != NULL);
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE);
g_return_val_if_fail (selected != NULL, FALSE);
g_return_val_if_fail (mask != NULL, FALSE);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->query_range (model, position, start_range, n_items, selected);
return iface->set_selection (model, selected, mask);
}
/**

View File

@ -24,7 +24,7 @@
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gdk/gdk.h>
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
@ -33,39 +33,12 @@ 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,
gboolean *selected,
gpointer data);
/**
* GtkSelectionModelInterface:
* @is_selected: Return if the item at the given position is selected.
* @get_selection_in_range: Return a bitset with all currently selected
* items in the given range. By default, this function will call
* #GtkSelectionModel::is_selected() on all items in the given range.
* @select_item: Select the item in the given position. If the operation
* is known to fail, return %FALSE.
* @unselect_item: Unselect the item in the given position. If the
@ -79,12 +52,22 @@ typedef void (* GtkSelectionCallback) (guint position,
* unsupported or known to fail for all items, return %FALSE.
* @unselect_all: Unselect all items in the model. If the operation is
* unsupported or known to fail for all items, return %FALSE.
* @set_selection: Set selection state of all items in mask to selected.
* See gtk_selection_model_set_selection() for a detailed explanation
* of this function.
*
* The list of virtual functions for the #GtkSelectionModel interface.
* All getter functions are mandatory to implement, but the model does
* not need to implement any functions to support selecting or unselecting
* items. Of course, if the model does not do that, it means that users
* cannot select or unselect items in a list widgets using the model.
* No function must be implemented, but unless #GtkSelectionModel::is_selected()
* is implemented, it will not be possible to select items in the set.
*
* The model does not need to implement any functions to support either
* selecting or unselecting items. Of course, if the model does not do that,
* it means that users cannot select or unselect items in a list widget
* using the model.
*
* All selection functions fall back to #GtkSelectionModel::set_selection()
* so it is sufficient to implement just that function for full selection
* support.
*/
struct _GtkSelectionModelInterface
{
@ -94,6 +77,9 @@ struct _GtkSelectionModelInterface
/*< public >*/
gboolean (* is_selected) (GtkSelectionModel *model,
guint position);
GtkBitset * (* get_selection_in_range) (GtkSelectionModel *model,
guint position,
guint n_items);
gboolean (* select_item) (GtkSelectionModel *model,
guint position,
@ -109,23 +95,21 @@ struct _GtkSelectionModelInterface
guint n_items);
gboolean (* select_all) (GtkSelectionModel *model);
gboolean (* unselect_all) (GtkSelectionModel *model);
gboolean (* select_callback) (GtkSelectionModel *model,
gboolean unselect_rest,
GtkSelectionCallback callback,
gpointer data);
gboolean (* unselect_callback) (GtkSelectionModel *model,
GtkSelectionCallback callback,
gpointer data);
void (* query_range) (GtkSelectionModel *model,
guint position,
guint *start_range,
guint *n_items,
gboolean *selected);
gboolean (* set_selection) (GtkSelectionModel *model,
GtkBitset *selected,
GtkBitset *mask);
};
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_is_selected (GtkSelectionModel *model,
guint position);
GDK_AVAILABLE_IN_ALL
GtkBitset * gtk_selection_model_get_selection (GtkSelectionModel *model);
GDK_AVAILABLE_IN_ALL
GtkBitset * gtk_selection_model_get_selection_in_range
(GtkSelectionModel *model,
guint position,
guint n_items);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_select_item (GtkSelectionModel *model,
@ -147,23 +131,10 @@ GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_select_all (GtkSelectionModel *model);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_unselect_all (GtkSelectionModel *model);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_select_callback (GtkSelectionModel *model,
gboolean unselect_rest,
GtkSelectionCallback callback,
gpointer data);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_unselect_callback (GtkSelectionModel *model,
GtkSelectionCallback callback,
gpointer data);
GDK_AVAILABLE_IN_ALL
void gtk_selection_model_query_range (GtkSelectionModel *model,
guint position,
guint *start_range,
guint *n_items,
gboolean *selected);
gboolean gtk_selection_model_set_selection (GtkSelectionModel *model,
GtkBitset *selected,
GtkBitset *mask);
/* for implementations only */
GDK_AVAILABLE_IN_ALL

View File

@ -1,383 +0,0 @@
/*
* 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;
}
gboolean
gtk_set_is_empty (GtkSet *set)
{
return set->ranges->len == 0;
}
guint
gtk_set_get_min (GtkSet *set)
{
Range *r;
if (gtk_set_is_empty (set))
return 0;
r = &g_array_index (set->ranges, Range, 0);
return r->first;
}
guint
gtk_set_get_max (GtkSet *set)
{
Range *r;
if (gtk_set_is_empty (set))
return 0;
r = &g_array_index (set->ranges, Range, set->ranges->len - 1);
return r->first + r->n_items - 1;
}
#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

View File

@ -1,74 +0,0 @@
/*
* 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);
gboolean gtk_set_is_empty (GtkSet *set);
guint gtk_set_get_min (GtkSet *set);
guint gtk_set_get_max (GtkSet *set);
#endif /* __GTK_SET_H__ */

View File

@ -21,6 +21,7 @@
#include "gtksingleselection.h"
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtkselectionmodel.h"
@ -110,6 +111,21 @@ gtk_single_selection_is_selected (GtkSelectionModel *model,
return self->selected == position;
}
static GtkBitset *
gtk_single_selection_get_selection_in_range (GtkSelectionModel *model,
guint position,
guint n_items)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
GtkBitset *result;
result = gtk_bitset_new_empty ();
if (self->selected != GTK_INVALID_LIST_POSITION)
gtk_bitset_add (result, self->selected);
return result;
}
static gboolean
gtk_single_selection_select_item (GtkSelectionModel *model,
guint position,
@ -138,57 +154,13 @@ gtk_single_selection_unselect_item (GtkSelectionModel *model,
return TRUE;
}
static void
gtk_single_selection_query_range (GtkSelectionModel *model,
guint position,
guint *start_range,
guint *n_range,
gboolean *selected)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
guint n_items;
n_items = g_list_model_get_n_items (self->model);
if (position >= n_items)
{
*start_range = position;
*n_range = 0;
*selected = FALSE;
}
else if (self->selected == GTK_INVALID_LIST_POSITION)
{
*start_range = 0;
*n_range = n_items;
*selected = FALSE;
}
else if (position < self->selected)
{
*start_range = 0;
*n_range = self->selected;
*selected = FALSE;
}
else if (position > self->selected)
{
*start_range = self->selected + 1;
*n_range = n_items - *start_range;
*selected = FALSE;
}
else
{
*start_range = self->selected;
*n_range = 1;
*selected = TRUE;
}
}
static void
gtk_single_selection_selection_model_init (GtkSelectionModelInterface *iface)
{
iface->is_selected = gtk_single_selection_is_selected;
iface->get_selection_in_range = gtk_single_selection_get_selection_in_range;
iface->select_item = gtk_single_selection_select_item;
iface->unselect_item = gtk_single_selection_unselect_item;
iface->query_range = gtk_single_selection_query_range;
}
G_DEFINE_TYPE_EXTENDED (GtkSingleSelection, gtk_single_selection, G_TYPE_OBJECT, 0,

View File

@ -34,6 +34,7 @@
G_BEGIN_DECLS
typedef struct _GtkAdjustment GtkAdjustment;
typedef struct _GtkBitset GtkBitset;
typedef struct _GtkBuilder GtkBuilder;
typedef struct _GtkBuilderScope GtkBuilderScope;
typedef struct _GtkClipboard GtkClipboard;

View File

@ -134,7 +134,6 @@ gtk_private_sources = files([
'gtkscaler.c',
'gtksearchengine.c',
'gtksearchenginemodel.c',
'gtkset.c',
'gtksizerequestcache.c',
'gtkstyleanimation.c',
'gtkstylecascade.c',
@ -164,6 +163,7 @@ gtk_public_sources = files([
'gtkaspectframe.c',
'gtkassistant.c',
'gtkbinlayout.c',
'gtkbitset.c',
'gtkbookmarklist.c',
'gtkborder.c',
'gtkboxlayout.c',
@ -329,7 +329,6 @@ gtk_public_sources = files([
'gtkprintsettings.c',
'gtkprogressbar.c',
'gtkpropertylookuplistmodel.c',
'gtkpropertyselection.c',
'gtkradiobutton.c',
'gtkrange.c',
'gtktreerbtree.c',
@ -451,6 +450,7 @@ gtk_public_headers = files([
'gtkaspectframe.h',
'gtkassistant.h',
'gtkbinlayout.h',
'gtkbitset.h',
'gtkbookmarklist.h',
'gtkborder.h',
'gtkbox.h',
@ -606,7 +606,6 @@ gtk_public_headers = files([
'gtkprintoperationpreview.h',
'gtkprintsettings.h',
'gtkprogressbar.h',
'gtkpropertyselection.h',
'gtkradiobutton.h',
'gtkrange.h',
'gtkrecentmanager.h',

11453
gtk/roaring.c Normal file

File diff suppressed because it is too large Load Diff

7249
gtk/roaring.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -255,7 +255,6 @@ gtk/gtkprintoperation-win32.c
gtk/gtkprintunixdialog.c
gtk/gtkprogressbar.c
gtk/gtkpropertylookuplistmodel.c
gtk/gtkpropertyselection.c
gtk/gtkradiobutton.c
gtk/gtkrange.c
gtk/gtkrecentmanager.c

View File

@ -257,7 +257,6 @@ gtk/gtkprintoperation-win32.c
gtk/gtkprintunixdialog.c
gtk/gtkprogressbar.c
gtk/gtkpropertylookuplistmodel.c
gtk/gtkpropertyselection.c
gtk/gtkradiobutton.c
gtk/gtkrange.c
gtk/gtkrecentmanager.c

369
testsuite/gtk/bitset.c Normal file
View File

@ -0,0 +1,369 @@
/* GtkRBTree tests.
*
* Copyright (C) 2011, Red Hat, Inc.
* Authors: Benjamin Otte <otte@gnome.org>
*
* 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>
#define LARGE_VALUE (1000 * 1000)
static GtkBitset *
create_powers_of_10 (void)
{
GtkBitset *set;
guint i;
set = gtk_bitset_new_empty ();
for (i = 1; i <= LARGE_VALUE; i *= 10)
gtk_bitset_add (set, i);
return set;
}
static GtkBitset *
create_powers_of_10_ranges (void)
{
GtkBitset *set;
guint i, j;
set = gtk_bitset_new_empty ();
for (i = 1, j = 0; i <= LARGE_VALUE; i *= 10, j++)
gtk_bitset_add_range (set, i - j, 2 * j);
return set;
}
static GtkBitset *
create_large_range (void)
{
GtkBitset *set;
set = gtk_bitset_new_empty ();
gtk_bitset_add_range (set, 0, LARGE_VALUE);
return set;
}
static GtkBitset *
create_large_rectangle (void)
{
GtkBitset *set;
set = gtk_bitset_new_empty ();
gtk_bitset_add_rectangle (set, 0, 900, 900, 1000);
return set;
}
static struct {
GtkBitset * (* create) (void);
guint n_elements;
guint minimum;
guint maximum;
} bitsets[] =
{
{ gtk_bitset_new_empty, 0, G_MAXUINT, 0 },
{ create_powers_of_10, 7, 1, LARGE_VALUE },
{ create_powers_of_10_ranges, 42, 9, LARGE_VALUE + 5, },
{ create_large_range, LARGE_VALUE, 0, LARGE_VALUE - 1 },
{ create_large_rectangle, 900 * 900, 0, 899899 }
};
/* UTILITIES */
/* TEST */
static void
test_is_empty (void)
{
guint i;
GtkBitset *set;
for (i = 0; i < G_N_ELEMENTS (bitsets); i++)
{
set = bitsets[i].create();
if (bitsets[i].n_elements == 0)
g_assert_true (gtk_bitset_is_empty (set));
else
g_assert_false (gtk_bitset_is_empty (set));
gtk_bitset_unref (set);
}
}
static void
test_minimum (void)
{
guint i;
GtkBitset *set;
GtkBitsetIter iter;
gboolean success;
guint value;
for (i = 0; i < G_N_ELEMENTS (bitsets); i++)
{
set = bitsets[i].create();
g_assert_cmpint (gtk_bitset_get_minimum (set), ==, bitsets[i].minimum);
success = gtk_bitset_iter_init_first (&iter, set, &value);
if (success)
{
g_assert_false (bitsets[i].n_elements == 0);
g_assert_cmpint (value, ==, bitsets[i].minimum);
}
else
{
g_assert_true (bitsets[i].n_elements == 0);
g_assert_cmpint (value, ==, 0);
}
g_assert_cmpint (gtk_bitset_iter_is_valid (&iter), ==, success);
g_assert_cmpint (gtk_bitset_iter_get_value (&iter), ==, value);
gtk_bitset_unref (set);
}
}
static void
test_maximum (void)
{
guint i;
GtkBitset *set;
GtkBitsetIter iter;
gboolean success;
guint value;
for (i = 0; i < G_N_ELEMENTS (bitsets); i++)
{
set = bitsets[i].create();
g_assert_cmpint (gtk_bitset_get_maximum (set), ==, bitsets[i].maximum);
success = gtk_bitset_iter_init_last (&iter, set, &value);
if (success)
{
g_assert_false (bitsets[i].n_elements == 0);
g_assert_cmpint (value, ==, bitsets[i].maximum);
}
else
{
g_assert_true (bitsets[i].n_elements == 0);
g_assert_cmpint (value, ==, 0);
}
g_assert_cmpint (gtk_bitset_iter_is_valid (&iter), ==, success);
g_assert_cmpint (gtk_bitset_iter_get_value (&iter), ==, value);
gtk_bitset_unref (set);
}
}
static void
test_equals (void)
{
guint i, j;
GtkBitset *iset, *jset;
for (i = 0; i < G_N_ELEMENTS (bitsets); i++)
{
iset = bitsets[i].create();
g_assert_true (gtk_bitset_equals (iset, iset));
for (j = 0; j < G_N_ELEMENTS (bitsets); j++)
{
jset = bitsets[j].create();
if (i == j)
g_assert_true (gtk_bitset_equals (iset, jset));
else
g_assert_false (gtk_bitset_equals (iset, jset));
gtk_bitset_unref (jset);
}
gtk_bitset_unref (iset);
}
}
static void
test_union (void)
{
guint i, j, k, min, max;
GtkBitset *iset, *jset, *testset;
for (i = 0; i < G_N_ELEMENTS (bitsets); i++)
{
iset = bitsets[i].create();
g_assert_true (gtk_bitset_equals (iset, iset));
for (j = 0; j < G_N_ELEMENTS (bitsets); j++)
{
jset = bitsets[j].create();
testset = gtk_bitset_copy (iset);
gtk_bitset_union (testset, jset);
min = MIN (gtk_bitset_get_minimum (iset), gtk_bitset_get_minimum (jset));
g_assert_cmpint (min, <=, gtk_bitset_get_minimum (testset));
max = MAX (gtk_bitset_get_maximum (iset), gtk_bitset_get_maximum (jset));
g_assert_cmpint (max, >=, gtk_bitset_get_maximum (testset));
for (k = min; k <= max; k++)
{
g_assert_cmpint (gtk_bitset_contains (iset, k) || gtk_bitset_contains (jset, k), ==, gtk_bitset_contains (testset, k));
}
gtk_bitset_unref (testset);
gtk_bitset_unref (jset);
}
gtk_bitset_unref (iset);
}
}
static void
test_intersect (void)
{
guint i, j, k, min, max;
GtkBitset *iset, *jset, *testset;
for (i = 0; i < G_N_ELEMENTS (bitsets); i++)
{
iset = bitsets[i].create();
g_assert_true (gtk_bitset_equals (iset, iset));
for (j = 0; j < G_N_ELEMENTS (bitsets); j++)
{
jset = bitsets[j].create();
testset = gtk_bitset_copy (iset);
gtk_bitset_intersect (testset, jset);
min = MIN (gtk_bitset_get_minimum (iset), gtk_bitset_get_minimum (jset));
g_assert_cmpint (min, <=, gtk_bitset_get_minimum (testset));
max = MAX (gtk_bitset_get_maximum (iset), gtk_bitset_get_maximum (jset));
g_assert_cmpint (max, >=, gtk_bitset_get_maximum (testset));
for (k = min; k <= max; k++)
{
g_assert_cmpint (gtk_bitset_contains (iset, k) && gtk_bitset_contains (jset, k), ==, gtk_bitset_contains (testset, k));
}
gtk_bitset_unref (testset);
gtk_bitset_unref (jset);
}
gtk_bitset_unref (iset);
}
}
static void
test_difference (void)
{
guint i, j, k, min, max;
GtkBitset *iset, *jset, *testset;
for (i = 0; i < G_N_ELEMENTS (bitsets); i++)
{
iset = bitsets[i].create();
g_assert_true (gtk_bitset_equals (iset, iset));
for (j = 0; j < G_N_ELEMENTS (bitsets); j++)
{
jset = bitsets[j].create();
testset = gtk_bitset_copy (iset);
gtk_bitset_difference (testset, jset);
min = MIN (gtk_bitset_get_minimum (iset), gtk_bitset_get_minimum (jset));
g_assert_cmpint (min, <=, gtk_bitset_get_minimum (testset));
max = MAX (gtk_bitset_get_maximum (iset), gtk_bitset_get_maximum (jset));
g_assert_cmpint (max, >=, gtk_bitset_get_maximum (testset));
for (k = min; k <= max; k++)
{
g_assert_cmpint (gtk_bitset_contains (iset, k) ^ gtk_bitset_contains (jset, k), ==, gtk_bitset_contains (testset, k));
}
gtk_bitset_unref (testset);
gtk_bitset_unref (jset);
}
gtk_bitset_unref (iset);
}
}
static void
test_subtract (void)
{
guint i, j, k, min, max;
GtkBitset *iset, *jset, *testset;
for (i = 0; i < G_N_ELEMENTS (bitsets); i++)
{
iset = bitsets[i].create();
g_assert_true (gtk_bitset_equals (iset, iset));
for (j = 0; j < G_N_ELEMENTS (bitsets); j++)
{
jset = bitsets[j].create();
testset = gtk_bitset_copy (iset);
gtk_bitset_subtract (testset, jset);
min = MIN (gtk_bitset_get_minimum (iset), gtk_bitset_get_minimum (jset));
g_assert_cmpint (min, <=, gtk_bitset_get_minimum (testset));
max = MAX (gtk_bitset_get_maximum (iset), gtk_bitset_get_maximum (jset));
g_assert_cmpint (max, >=, gtk_bitset_get_maximum (testset));
for (k = min; k <= max; k++)
{
g_assert_cmpint (gtk_bitset_contains (iset, k) && !gtk_bitset_contains (jset, k), ==, gtk_bitset_contains (testset, k));
}
gtk_bitset_unref (testset);
gtk_bitset_unref (jset);
}
gtk_bitset_unref (iset);
}
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
setlocale (LC_ALL, "C");
g_test_add_func ("/bitset/is_empty", test_is_empty);
g_test_add_func ("/bitset/minimum", test_minimum);
g_test_add_func ("/bitset/maximum", test_maximum);
g_test_add_func ("/bitset/equals", test_equals);
g_test_add_func ("/bitset/union", test_union);
g_test_add_func ("/bitset/intersect", test_intersect);
g_test_add_func ("/bitset/difference", test_difference);
g_test_add_func ("/bitset/subtract", test_subtract);
return g_test_run ();
}

View File

@ -104,9 +104,6 @@ test_type (gconstpointer data)
g_type_is_a (type, GTK_TYPE_SHORTCUT_ACTION))
return;
if (g_type_is_a (type, GTK_TYPE_PROPERTY_SELECTION))
return;
klass = g_type_class_ref (type);
if (g_type_is_a (type, GTK_TYPE_SETTINGS))

View File

@ -12,6 +12,7 @@ tests = [
['accessible'],
['action'],
['adjustment'],
['bitset'],
['bitmask', ['../../gtk/gtkallocatedbitmask.c'], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']],
['builder', [], [], gtk_tests_export_dynamic_ldflag],
['builderparser'],

View File

@ -396,7 +396,7 @@ test_select_range (void)
ret = gtk_selection_model_select_range (selection, 3, 2, FALSE);
g_assert_true (ret);
assert_selection (selection, "3 4 5");
assert_selection_changes (selection, "3:2");
assert_selection_changes (selection, "4:1");
ret = gtk_selection_model_select_range (selection, 0, 1, TRUE);
g_assert_true (ret);
@ -408,7 +408,7 @@ test_select_range (void)
}
/* Test that removing and readding items
* clears the selected state.
* doesn't clear the selected state.
*/
static void
test_readd (void)
@ -432,62 +432,19 @@ test_readd (void)
g_list_model_items_changed (G_LIST_MODEL (store), 1, 3, 3);
assert_changes (selection, "1-3+3");
assert_selection (selection, "");
assert_selection (selection, "3 4");
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)
test_set_selection (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 }
};
GtkBitset *selected, *mask;
store = new_store (1, 10, 1);
@ -496,13 +453,26 @@ test_callback (void)
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_callback (selection, FALSE, select_some, data);
selected = gtk_bitset_new_empty ();
gtk_bitset_add_range (selected, 2, 3);
gtk_bitset_add_range (selected, 6, 3);
mask = gtk_bitset_new_empty ();
gtk_bitset_add_range (mask, 0, 100); /* too big on purpose */
ret = gtk_selection_model_set_selection (selection, selected, mask);
g_assert_true (ret);
gtk_bitset_unref (selected);
gtk_bitset_unref (mask);
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);
selected = gtk_bitset_new_empty ();
mask = gtk_bitset_new_empty ();
gtk_bitset_add (mask, 3);
gtk_bitset_add (mask, 7);
ret = gtk_selection_model_set_selection (selection, selected, mask);
g_assert_true (ret);
gtk_bitset_unref (selected);
gtk_bitset_unref (mask);
assert_selection (selection, "3 5 7 9");
assert_selection_changes (selection, "3:5");
@ -528,7 +498,7 @@ main (int argc, char *argv[])
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);
g_test_add_func ("/multiselection/set_selection", test_set_selection);
return g_test_run ();
}

View File

@ -425,10 +425,6 @@ test_type (gconstpointer data)
g_type_is_a (type, GTK_TYPE_NAMED_ACTION))
return;
/* needs special item type in underlying model */
if (g_type_is_a (type, GTK_TYPE_PROPERTY_SELECTION))
return;
klass = g_type_class_ref (type);
if (g_type_is_a (type, GTK_TYPE_SETTINGS))

View File

@ -80,8 +80,7 @@ test_finalize_object (gconstpointer data)
NULL);
g_object_unref (list_store);
}
else if (g_type_is_a (test_type, GTK_TYPE_LAYOUT_CHILD) ||
g_type_is_a (test_type, GTK_TYPE_PROPERTY_SELECTION))
else if (g_type_is_a (test_type, GTK_TYPE_LAYOUT_CHILD))
{
char *msg = g_strdup_printf ("Skipping %s", g_type_name (test_type));
g_test_skip (msg);

View File

@ -593,28 +593,30 @@ test_persistence (void)
}
static void
check_query_range (GtkSelectionModel *selection)
check_get_selection (GtkSelectionModel *selection)
{
guint i, j;
guint position, n_items;
gboolean selected;
GtkBitset *set;
guint i, n_items;
/* check that range always contains position, and has uniform selection */
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (selection)); i++)
{
gtk_selection_model_query_range (selection, i, &position, &n_items, &selected);
g_assert_cmpint (position, <=, i);
g_assert_cmpint (i, <, position + n_items);
for (j = position; j < position + n_items; j++)
g_assert_true (selected == gtk_selection_model_is_selected (selection, j));
}
set = gtk_selection_model_get_selection (selection);
/* check that out-of-range returns the correct invalid values */
i = MIN (i, g_random_int ());
gtk_selection_model_query_range (selection, i, &position, &n_items, &selected);
g_assert_cmpint (position, ==, i);
g_assert_cmpint (n_items, ==, 0);
g_assert_true (!selected);
n_items = g_list_model_get_n_items (G_LIST_MODEL (selection));
if (n_items == 0)
{
g_assert_true (gtk_bitset_is_empty (set));
}
else
{
for (i = 0; i < n_items; i++)
{
g_assert_cmpint (gtk_bitset_contains (set, i), ==, gtk_selection_model_is_selected (selection, i));
}
/* check that out-of-range has no bits set */
g_assert_cmpint (gtk_bitset_get_maximum (set), <, g_list_model_get_n_items (G_LIST_MODEL (selection)));
}
gtk_bitset_unref (set);
}
static void
@ -625,16 +627,16 @@ test_query_range (void)
store = new_store (1, 5, 1);
selection = new_model (store, TRUE, TRUE);
check_query_range (selection);
check_get_selection (selection);
gtk_selection_model_unselect_item (selection, 0);
check_query_range (selection);
check_get_selection (selection);
gtk_selection_model_select_item (selection, 2, TRUE);
check_query_range (selection);
check_get_selection (selection);
gtk_selection_model_select_item (selection, 4, TRUE);
check_query_range (selection);
check_get_selection (selection);
ignore_selection_changes (selection);