forked from AuroraMiddleware/gtk
Merge branch 'columnview-rubberbanding' into 'master'
columnview: Implement rubberbanding See merge request GNOME/gtk!2008
This commit is contained in:
commit
fa37225a42
@ -422,9 +422,10 @@ create_color_grid (void)
|
||||
g_object_unref (factory);
|
||||
|
||||
gtk_grid_view_set_max_columns (GTK_GRID_VIEW (gridview), 24);
|
||||
gtk_grid_view_set_enable_rubberband (GTK_GRID_VIEW (gridview), TRUE);
|
||||
|
||||
model = G_LIST_MODEL (gtk_sort_list_model_new (create_colors_model (), NULL));
|
||||
selection = G_LIST_MODEL (gtk_no_selection_new (model));
|
||||
selection = G_LIST_MODEL (gtk_multi_selection_new (model));
|
||||
gtk_grid_view_set_model (GTK_GRID_VIEW (gridview), selection);
|
||||
g_object_unref (selection);
|
||||
g_object_unref (model);
|
||||
|
@ -70,6 +70,7 @@
|
||||
<xi:include href="xml/gtkselectionmodel.xml" />
|
||||
<xi:include href="xml/gtknoselection.xml" />
|
||||
<xi:include href="xml/gtksingleselection.xml" />
|
||||
<xi:include href="xml/gtkmultiselection.xml" />
|
||||
<xi:include href="xml/gtkdirectorylist.xml" />
|
||||
</chapter>
|
||||
|
||||
|
@ -392,6 +392,16 @@ gtk_single_selection_set_can_unselect
|
||||
gtk_single_selection_get_type
|
||||
</SECTION>
|
||||
|
||||
<SECTION>
|
||||
<FILE>gtkmultiselection</FILE>
|
||||
<TITLE>GtkMultiSeledction</TITLE>
|
||||
GtkMultiSelection
|
||||
gtk_multi_selection_new
|
||||
gtk_multi_selection_copy
|
||||
<SUBSECTION Private>
|
||||
gtk_multi_selection_get_type
|
||||
</SECTION>
|
||||
|
||||
<SECTION>
|
||||
<FILE>gtklistitem</FILE>
|
||||
<TITLE>GtkListItem</TITLE>
|
||||
@ -479,6 +489,8 @@ gtk_list_view_set_show_separators
|
||||
gtk_list_view_get_show_separators
|
||||
gtk_list_view_set_single_click_activate
|
||||
gtk_list_view_get_single_click_activate
|
||||
gtk_list_view_set_enable_rubberband
|
||||
gtk_list_view_get_enable_rubberband
|
||||
<SUBSECTION Standard>
|
||||
GTK_LIST_VIEW
|
||||
GTK_LIST_VIEW_CLASS
|
||||
@ -509,6 +521,8 @@ gtk_column_view_set_single_click_activate
|
||||
gtk_column_view_get_single_click_activate
|
||||
gtk_column_view_set_reorderable
|
||||
gtk_column_view_get_reorderable
|
||||
gtk_column_view_set_enable_rubberband
|
||||
gtk_column_view_get_enable_rubberband
|
||||
<SUBSECTION Standard>
|
||||
GTK_COLUMN_VIEW
|
||||
GTK_COLUMN_VIEW_CLASS
|
||||
@ -568,6 +582,8 @@ gtk_grid_view_set_min_columns
|
||||
gtk_grid_view_get_min_columns
|
||||
gtk_grid_view_set_single_click_activate
|
||||
gtk_grid_view_get_single_click_activate
|
||||
gtk_grid_view_set_enable_rubberband
|
||||
gtk_grid_view_get_enable_rubberband
|
||||
<SUBSECTION Standard>
|
||||
GTK_GRID_VIEW
|
||||
GTK_GRID_VIEW_CLASS
|
||||
|
@ -139,6 +139,7 @@ gtk_menu_button_get_type
|
||||
gtk_message_dialog_get_type
|
||||
gtk_mount_operation_get_type
|
||||
gtk_multi_filter_get_type
|
||||
gtk_multi_selection_get_type
|
||||
gtk_multi_sorter_get_type
|
||||
gtk_native_get_type
|
||||
gtk_native_dialog_get_type
|
||||
|
@ -174,6 +174,7 @@
|
||||
#include <gtk/gtkmessagedialog.h>
|
||||
#include <gtk/gtkmountoperation.h>
|
||||
#include <gtk/gtkmultifilter.h>
|
||||
#include <gtk/gtkmultiselection.h>
|
||||
#include <gtk/gtkmultisorter.h>
|
||||
#include <gtk/gtknative.h>
|
||||
#include <gtk/gtknativedialog.h>
|
||||
|
@ -106,6 +106,7 @@ enum
|
||||
PROP_VSCROLL_POLICY,
|
||||
PROP_SINGLE_CLICK_ACTIVATE,
|
||||
PROP_REORDERABLE,
|
||||
PROP_ENABLE_RUBBERBAND,
|
||||
|
||||
N_PROPS
|
||||
};
|
||||
@ -401,6 +402,10 @@ gtk_column_view_get_property (GObject *object,
|
||||
g_value_set_boolean (value, gtk_column_view_get_reorderable (self));
|
||||
break;
|
||||
|
||||
case PROP_ENABLE_RUBBERBAND:
|
||||
g_value_set_boolean (value, gtk_column_view_get_enable_rubberband (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
@ -476,6 +481,10 @@ gtk_column_view_set_property (GObject *object,
|
||||
gtk_column_view_set_reorderable (self, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
case PROP_ENABLE_RUBBERBAND:
|
||||
gtk_column_view_set_enable_rubberband (self, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
@ -584,6 +593,18 @@ gtk_column_view_class_init (GtkColumnViewClass *klass)
|
||||
TRUE,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* GtkColumnView:enable-rubberband:
|
||||
*
|
||||
* Allow rubberband selection
|
||||
*/
|
||||
properties[PROP_ENABLE_RUBBERBAND] =
|
||||
g_param_spec_boolean ("enable-rubberband",
|
||||
P_("Enable rubberband selection"),
|
||||
P_("Allow selecting items by dragging with the mouse"),
|
||||
FALSE,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
||||
|
||||
/**
|
||||
@ -1404,3 +1425,40 @@ gtk_column_view_get_reorderable (GtkColumnView *self)
|
||||
|
||||
return self->reorderable;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_column_view_set_enable_rubberband:
|
||||
* @self: a #GtkColumnView
|
||||
* @enable_rubberband: %TRUE to enable rubberband selection
|
||||
*
|
||||
* Sets whether selections can be changed by dragging with the mouse.
|
||||
*/
|
||||
void
|
||||
gtk_column_view_set_enable_rubberband (GtkColumnView *self,
|
||||
gboolean enable_rubberband)
|
||||
{
|
||||
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
|
||||
|
||||
if (enable_rubberband == gtk_list_view_get_enable_rubberband (self->listview))
|
||||
return;
|
||||
|
||||
gtk_list_view_set_enable_rubberband (self->listview, enable_rubberband);
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLE_RUBBERBAND]);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_column_view_get_enable_rubberband:
|
||||
* @self: a #GtkColumnView
|
||||
*
|
||||
* Returns whether rows can be selected by dragging with the mouse.
|
||||
*
|
||||
* Returns: %TRUE if rubberband selection is enabled
|
||||
*/
|
||||
gboolean
|
||||
gtk_column_view_get_enable_rubberband (GtkColumnView *self)
|
||||
{
|
||||
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
|
||||
|
||||
return gtk_list_view_get_enable_rubberband (self->listview);
|
||||
}
|
||||
|
@ -93,11 +93,16 @@ GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_column_view_get_single_click_activate (GtkColumnView *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
|
||||
void gtk_column_view_set_reorderable (GtkColumnView *self,
|
||||
gboolean reorderable);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_column_view_get_reorderable (GtkColumnView *self);
|
||||
|
||||
void gtk_column_view_set_enable_rubberband (GtkColumnView *self,
|
||||
gboolean enable_rubberband);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_column_view_get_enable_rubberband (GtkColumnView *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@ -91,6 +91,7 @@ enum
|
||||
PROP_MIN_COLUMNS,
|
||||
PROP_MODEL,
|
||||
PROP_SINGLE_CLICK_ACTIVATE,
|
||||
PROP_ENABLE_RUBBERBAND,
|
||||
|
||||
N_PROPS
|
||||
};
|
||||
@ -713,6 +714,8 @@ 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);
|
||||
@ -914,6 +917,10 @@ gtk_grid_view_get_property (GObject *object,
|
||||
g_value_set_boolean (value, gtk_list_item_manager_get_single_click_activate (self->item_manager));
|
||||
break;
|
||||
|
||||
case PROP_ENABLE_RUBBERBAND:
|
||||
g_value_set_boolean (value, gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
@ -950,6 +957,10 @@ gtk_grid_view_set_property (GObject *object,
|
||||
gtk_grid_view_set_single_click_activate (self, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
case PROP_ENABLE_RUBBERBAND:
|
||||
gtk_grid_view_set_enable_rubberband (self, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
@ -1062,6 +1073,18 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
|
||||
FALSE,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
/**
|
||||
* GtkGridView:enable-rubberband:
|
||||
*
|
||||
* Allow rubberband selection
|
||||
*/
|
||||
properties[PROP_ENABLE_RUBBERBAND] =
|
||||
g_param_spec_boolean ("enable-rubberband",
|
||||
P_("Enable rubberband selection"),
|
||||
P_("Allow selecting items by dragging with the mouse"),
|
||||
FALSE,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
||||
|
||||
/**
|
||||
@ -1370,3 +1393,40 @@ gtk_grid_view_get_single_click_activate (GtkGridView *self)
|
||||
|
||||
return gtk_list_item_manager_get_single_click_activate (self->item_manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_grid_view_set_enable_rubberband:
|
||||
* @self: a #GtkGridView
|
||||
* @enable_rubberband: %TRUE to enable rubberband selection
|
||||
*
|
||||
* Sets whether selections can be changed by dragging with the mouse.
|
||||
*/
|
||||
void
|
||||
gtk_grid_view_set_enable_rubberband (GtkGridView *self,
|
||||
gboolean enable_rubberband)
|
||||
{
|
||||
g_return_if_fail (GTK_IS_GRID_VIEW (self));
|
||||
|
||||
if (enable_rubberband == gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)))
|
||||
return;
|
||||
|
||||
gtk_list_base_set_enable_rubberband (GTK_LIST_BASE (self), enable_rubberband);
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLE_RUBBERBAND]);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_grid_view_get_enable_rubberband:
|
||||
* @self: a #GtkGridView
|
||||
*
|
||||
* Returns whether rows can be selected by dragging with the mouse.
|
||||
*
|
||||
* Returns: %TRUE if rubberband selection is enabled
|
||||
*/
|
||||
gboolean
|
||||
gtk_grid_view_get_enable_rubberband (GtkGridView *self)
|
||||
{
|
||||
g_return_val_if_fail (GTK_IS_GRID_VIEW (self), FALSE);
|
||||
|
||||
return gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self));
|
||||
}
|
||||
|
@ -73,6 +73,11 @@ guint gtk_grid_view_get_max_columns (GtkGridView
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_grid_view_set_max_columns (GtkGridView *self,
|
||||
guint max_columns);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_grid_view_set_enable_rubberband (GtkGridView *self,
|
||||
gboolean enable_rubberband);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_grid_view_get_enable_rubberband (GtkGridView *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_grid_view_set_single_click_activate (GtkGridView *self,
|
||||
|
@ -28,6 +28,34 @@
|
||||
#include "gtkscrollable.h"
|
||||
#include "gtksingleselection.h"
|
||||
#include "gtktypebuiltins.h"
|
||||
#include "gtkgesturedrag.h"
|
||||
#include "gtkwidgetprivate.h"
|
||||
#include "gtkstylecontextprivate.h"
|
||||
#include "gtksnapshot.h"
|
||||
#include "gtkmultiselection.h"
|
||||
#include "gtkgizmoprivate.h"
|
||||
|
||||
typedef struct _RubberbandData RubberbandData;
|
||||
|
||||
struct _RubberbandData
|
||||
{
|
||||
GtkWidget *widget;
|
||||
GtkSelectionModel *selection;
|
||||
double x1, y1;
|
||||
double x2, y2;
|
||||
gboolean modify;
|
||||
gboolean extend;
|
||||
};
|
||||
|
||||
static void
|
||||
rubberband_data_free (gpointer data)
|
||||
{
|
||||
RubberbandData *rdata = data;
|
||||
|
||||
g_clear_pointer (&rdata->widget, gtk_widget_unparent);
|
||||
g_clear_object (&rdata->selection);
|
||||
g_free (rdata);
|
||||
}
|
||||
|
||||
typedef struct _GtkListBasePrivate GtkListBasePrivate;
|
||||
|
||||
@ -50,6 +78,14 @@ struct _GtkListBasePrivate
|
||||
GtkListItemTracker *selected;
|
||||
/* the item that has input focus */
|
||||
GtkListItemTracker *focus;
|
||||
|
||||
gboolean enable_rubberband;
|
||||
GtkGesture *drag_gesture;
|
||||
RubberbandData *rubberband;
|
||||
|
||||
guint autoscroll_id;
|
||||
double autoscroll_delta_x;
|
||||
double autoscroll_delta_y;
|
||||
};
|
||||
|
||||
enum
|
||||
@ -1198,6 +1234,336 @@ 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,
|
||||
gpointer data)
|
||||
{
|
||||
GtkListBase *self = data;
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
double value;
|
||||
|
||||
value = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
|
||||
gtk_adjustment_set_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL], value + priv->autoscroll_delta_x);
|
||||
|
||||
value = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
|
||||
gtk_adjustment_set_value (priv->adjustment[GTK_ORIENTATION_VERTICAL], value + priv->autoscroll_delta_y);
|
||||
|
||||
if (priv->rubberband)
|
||||
{
|
||||
priv->rubberband->x2 += priv->autoscroll_delta_x;
|
||||
priv->rubberband->y2 += priv->autoscroll_delta_y;
|
||||
gtk_list_base_update_rubberband_selection (self);
|
||||
}
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
add_autoscroll (GtkListBase *self,
|
||||
double delta_x,
|
||||
double delta_y)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
|
||||
priv->autoscroll_delta_x = delta_x;
|
||||
priv->autoscroll_delta_y = delta_y;
|
||||
|
||||
if (priv->autoscroll_id == 0)
|
||||
priv->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), autoscroll_cb, self, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
remove_autoscroll (GtkListBase *self)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
|
||||
if (priv->autoscroll_id != 0)
|
||||
{
|
||||
gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->autoscroll_id);
|
||||
priv->autoscroll_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gtk_list_base_allocate_rubberband (GtkListBase *self)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
GdkRectangle rect;
|
||||
double x, y;
|
||||
int min, nat;
|
||||
|
||||
if (!priv->rubberband)
|
||||
return;
|
||||
|
||||
gtk_widget_measure (priv->rubberband->widget,
|
||||
GTK_ORIENTATION_HORIZONTAL, -1,
|
||||
&min, &nat, NULL, NULL);
|
||||
|
||||
x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
|
||||
y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_base_start_rubberband (GtkListBase *self,
|
||||
double x,
|
||||
double y,
|
||||
gboolean modify,
|
||||
gboolean extend)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
double value_x, value_y;
|
||||
GtkSelectionModel *selection;
|
||||
|
||||
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]);
|
||||
|
||||
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->modify = modify;
|
||||
priv->rubberband->extend = extend;
|
||||
|
||||
priv->rubberband->widget = gtk_gizmo_new ("rubberband",
|
||||
NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
gtk_widget_set_parent (priv->rubberband->widget, GTK_WIDGET (self));
|
||||
|
||||
selection = gtk_list_item_manager_get_model (priv->item_manager);
|
||||
if ((modify || extend) &&
|
||||
GTK_IS_MULTI_SELECTION (selection))
|
||||
priv->rubberband->selection = GTK_SELECTION_MODEL (gtk_multi_selection_copy (selection));
|
||||
|
||||
if (!modify && !extend)
|
||||
gtk_selection_model_unselect_all (selection);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_base_stop_rubberband (GtkListBase *self)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
|
||||
if (!priv->rubberband)
|
||||
return;
|
||||
|
||||
g_clear_pointer (&priv->rubberband, rubberband_data_free);
|
||||
remove_autoscroll (self);
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
#define SCROLL_EDGE_SIZE 15
|
||||
|
||||
static void
|
||||
gtk_list_base_update_rubberband (GtkListBase *self,
|
||||
double x,
|
||||
double y)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
double value_x, value_y, page_size, upper;
|
||||
double delta_x, delta_y;
|
||||
|
||||
if (!priv->rubberband)
|
||||
return;
|
||||
|
||||
value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
|
||||
value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
|
||||
|
||||
priv->rubberband->x2 = x + value_x;
|
||||
priv->rubberband->y2 = y + value_y;
|
||||
|
||||
gtk_list_base_update_rubberband_selection (self);
|
||||
|
||||
page_size = gtk_adjustment_get_page_size (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
|
||||
upper = gtk_adjustment_get_upper (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
|
||||
|
||||
if (x < SCROLL_EDGE_SIZE && value_x > 0)
|
||||
delta_x = - (SCROLL_EDGE_SIZE - x)/3.0;
|
||||
else if (page_size - x < SCROLL_EDGE_SIZE && value_x + page_size < upper)
|
||||
delta_x = (SCROLL_EDGE_SIZE - (page_size - x))/3.0;
|
||||
else
|
||||
delta_x = 0;
|
||||
|
||||
page_size = gtk_adjustment_get_page_size (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
|
||||
upper = gtk_adjustment_get_upper (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
|
||||
|
||||
if (y < SCROLL_EDGE_SIZE && value_y > 0)
|
||||
delta_y = - (SCROLL_EDGE_SIZE - y)/3.0;
|
||||
else if (page_size - y < SCROLL_EDGE_SIZE && value_y + page_size < upper)
|
||||
delta_y = (SCROLL_EDGE_SIZE - (page_size - y))/3.0;
|
||||
else
|
||||
delta_y = 0;
|
||||
|
||||
if (delta_x != 0 || delta_y != 0)
|
||||
add_autoscroll (self, delta_x, delta_y);
|
||||
else
|
||||
remove_autoscroll (self);
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_base_update_rubberband_selection (GtkListBase *self)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
GdkRectangle rect;
|
||||
GdkRectangle alloc;
|
||||
GtkSelectionModel *model;
|
||||
GtkListItemManagerItem *item;
|
||||
|
||||
gtk_list_base_allocate_rubberband (self);
|
||||
gtk_widget_get_allocation (priv->rubberband->widget, &rect);
|
||||
|
||||
model = gtk_list_item_manager_get_model (priv->item_manager);
|
||||
|
||||
for (item = gtk_list_item_manager_get_first (priv->item_manager);
|
||||
item != NULL;
|
||||
item = gtk_rb_tree_node_get_next (item))
|
||||
{
|
||||
guint pos;
|
||||
gboolean selected;
|
||||
gboolean was_selected;
|
||||
|
||||
if (!item->widget)
|
||||
continue;
|
||||
|
||||
pos = gtk_list_item_manager_get_item_position (priv->item_manager, item);
|
||||
|
||||
gtk_widget_get_allocation (item->widget, &alloc);
|
||||
|
||||
if (priv->rubberband->selection)
|
||||
was_selected = gtk_selection_model_is_selected (priv->rubberband->selection, pos);
|
||||
else
|
||||
was_selected = FALSE;
|
||||
|
||||
selected = gdk_rectangle_intersect (&rect, &alloc, &alloc);
|
||||
|
||||
if (priv->rubberband->modify)
|
||||
{
|
||||
if (was_selected)
|
||||
{
|
||||
if (selected)
|
||||
gtk_selection_model_unselect_item (model, pos);
|
||||
else
|
||||
gtk_selection_model_select_item (model, pos, FALSE);
|
||||
}
|
||||
else
|
||||
gtk_selection_model_unselect_item (model, pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (selected || was_selected)
|
||||
gtk_selection_model_select_item (model, pos, FALSE);
|
||||
else
|
||||
gtk_selection_model_unselect_item (model, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
get_selection_modifiers (GtkGesture *gesture,
|
||||
gboolean *modify,
|
||||
gboolean *extend)
|
||||
{
|
||||
GdkEventSequence *sequence;
|
||||
GdkEvent *event;
|
||||
GdkModifierType state;
|
||||
|
||||
*modify = FALSE;
|
||||
*extend = FALSE;
|
||||
|
||||
sequence = gtk_gesture_get_last_updated_sequence (gesture);
|
||||
event = gtk_gesture_get_last_event (gesture, sequence);
|
||||
state = gdk_event_get_modifier_state (event);
|
||||
if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
|
||||
*modify = TRUE;
|
||||
if ((state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
|
||||
*extend = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_base_drag_begin (GtkGestureDrag *gesture,
|
||||
double start_x,
|
||||
double start_y,
|
||||
GtkListBase *self)
|
||||
{
|
||||
gboolean modify;
|
||||
gboolean extend;
|
||||
|
||||
get_selection_modifiers (GTK_GESTURE (gesture), &modify, &extend);
|
||||
gtk_list_base_start_rubberband (self, start_x, start_y, modify, extend);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_base_drag_update (GtkGestureDrag *gesture,
|
||||
double offset_x,
|
||||
double offset_y,
|
||||
GtkListBase *self)
|
||||
{
|
||||
double start_x, start_y;
|
||||
gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
|
||||
gtk_list_base_update_rubberband (self, start_x + offset_x, start_y + offset_y);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_base_drag_end (GtkGestureDrag *gesture,
|
||||
double offset_x,
|
||||
double offset_y,
|
||||
GtkListBase *self)
|
||||
{
|
||||
gtk_list_base_drag_update (gesture, offset_x, offset_y, self);
|
||||
gtk_list_base_stop_rubberband (self);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_list_base_set_enable_rubberband (GtkListBase *self,
|
||||
gboolean enable)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
|
||||
if (priv->enable_rubberband == enable)
|
||||
return;
|
||||
|
||||
priv->enable_rubberband = enable;
|
||||
|
||||
if (enable)
|
||||
{
|
||||
priv->drag_gesture = gtk_gesture_drag_new ();
|
||||
g_signal_connect (priv->drag_gesture, "drag-begin", G_CALLBACK (gtk_list_base_drag_begin), self);
|
||||
g_signal_connect (priv->drag_gesture, "drag-update", G_CALLBACK (gtk_list_base_drag_update), self);
|
||||
g_signal_connect (priv->drag_gesture, "drag-end", G_CALLBACK (gtk_list_base_drag_end), self);
|
||||
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (priv->drag_gesture));
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_widget_remove_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (priv->drag_gesture));
|
||||
priv->drag_gesture = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_list_base_get_enable_rubberband (GtkListBase *self)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
|
||||
return priv->enable_rubberband;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_base_init_real (GtkListBase *self,
|
||||
GtkListBaseClass *g_class)
|
||||
@ -1551,4 +1917,3 @@ gtk_list_base_set_model (GtkListBase *self,
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
@ -99,5 +99,10 @@ 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);
|
||||
|
||||
#endif /* __GTK_LIST_BASE_PRIVATE_H__ */
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "gtkintl.h"
|
||||
#include "gtklistitemfactoryprivate.h"
|
||||
#include "gtklistitemprivate.h"
|
||||
#include "gtklistbaseprivate.h"
|
||||
#include "gtkmain.h"
|
||||
#include "gtkselectionmodel.h"
|
||||
#include "gtkwidget.h"
|
||||
@ -323,6 +324,8 @@ gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture,
|
||||
{
|
||||
GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self);
|
||||
GtkWidget *widget = GTK_WIDGET (self);
|
||||
GtkWidget * parent = gtk_widget_get_parent (widget);
|
||||
gboolean rubberband;
|
||||
|
||||
if (priv->list_item && !priv->list_item->selectable && !priv->list_item->activatable)
|
||||
{
|
||||
@ -330,7 +333,12 @@ gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture,
|
||||
return;
|
||||
}
|
||||
|
||||
if (!priv->list_item || priv->list_item->selectable)
|
||||
if (GTK_IS_LIST_BASE (parent))
|
||||
rubberband = gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (parent));
|
||||
else
|
||||
rubberband = FALSE;
|
||||
|
||||
if (!rubberband && (!priv->list_item || priv->list_item->selectable))
|
||||
{
|
||||
GdkModifierType state;
|
||||
GdkEvent *event;
|
||||
|
@ -85,6 +85,7 @@ enum
|
||||
PROP_MODEL,
|
||||
PROP_SHOW_SEPARATORS,
|
||||
PROP_SINGLE_CLICK_ACTIVATE,
|
||||
PROP_ENABLE_RUBBERBAND,
|
||||
|
||||
N_PROPS
|
||||
};
|
||||
@ -516,6 +517,8 @@ 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);
|
||||
@ -643,6 +646,10 @@ gtk_list_view_get_property (GObject *object,
|
||||
g_value_set_boolean (value, gtk_list_item_manager_get_single_click_activate (self->item_manager));
|
||||
break;
|
||||
|
||||
case PROP_ENABLE_RUBBERBAND:
|
||||
g_value_set_boolean (value, gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
@ -675,6 +682,10 @@ gtk_list_view_set_property (GObject *object,
|
||||
gtk_list_view_set_single_click_activate (self, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
case PROP_ENABLE_RUBBERBAND:
|
||||
gtk_list_view_set_enable_rubberband (self, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
@ -771,6 +782,18 @@ gtk_list_view_class_init (GtkListViewClass *klass)
|
||||
FALSE,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
/**
|
||||
* GtkListView:enable-rubberband:
|
||||
*
|
||||
* Allow rubberband selection
|
||||
*/
|
||||
properties[PROP_ENABLE_RUBBERBAND] =
|
||||
g_param_spec_boolean ("enable-rubberband",
|
||||
P_("Enable rubberband selection"),
|
||||
P_("Allow selecting items by dragging with the mouse"),
|
||||
FALSE,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
||||
|
||||
/**
|
||||
@ -1033,3 +1056,40 @@ gtk_list_view_get_single_click_activate (GtkListView *self)
|
||||
|
||||
return gtk_list_item_manager_get_single_click_activate (self->item_manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_list_view_set_enable_rubberband:
|
||||
* @self: a #GtkListView
|
||||
* @enable_rubberband: %TRUE to enable rubberband selection
|
||||
*
|
||||
* Sets whether selections can be changed by dragging with the mouse.
|
||||
*/
|
||||
void
|
||||
gtk_list_view_set_enable_rubberband (GtkListView *self,
|
||||
gboolean enable_rubberband)
|
||||
{
|
||||
g_return_if_fail (GTK_IS_LIST_VIEW (self));
|
||||
|
||||
if (enable_rubberband == gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)))
|
||||
return;
|
||||
|
||||
gtk_list_base_set_enable_rubberband (GTK_LIST_BASE (self), enable_rubberband);
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLE_RUBBERBAND]);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_list_view_get_enable_rubberband:
|
||||
* @self: a #GtkListView
|
||||
*
|
||||
* Returns whether rows can be selected by dragging with the mouse.
|
||||
*
|
||||
* Returns: %TRUE if rubberband selection is enabled
|
||||
*/
|
||||
gboolean
|
||||
gtk_list_view_get_enable_rubberband (GtkListView *self)
|
||||
{
|
||||
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), FALSE);
|
||||
|
||||
return gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self));
|
||||
}
|
||||
|
@ -75,6 +75,12 @@ void gtk_list_view_set_single_click_activate (GtkListView
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_list_view_get_single_click_activate (GtkListView *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_list_view_set_enable_rubberband (GtkListView *self,
|
||||
gboolean enable_rubberband);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_list_view_get_enable_rubberband (GtkListView *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_LIST_VIEW_H__ */
|
||||
|
380
gtk/gtkmultiselection.c
Normal file
380
gtk/gtkmultiselection.c
Normal file
@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Copyright © 2019 Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Matthias Clasen <mclasen@redhat.com>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkmultiselection.h"
|
||||
|
||||
#include "gtkintl.h"
|
||||
#include "gtkselectionmodel.h"
|
||||
#include "gtksingleselection.h"
|
||||
#include "gtkset.h"
|
||||
|
||||
/**
|
||||
* SECTION:gtkmultiselection
|
||||
* @Short_description: A selection model that allows selecting a multiple items
|
||||
* @Title: GtkMultiSelection
|
||||
* @see_also: #GtkSelectionModel
|
||||
*
|
||||
* GtkMultiSelection is an implementation of the #GtkSelectionModel interface
|
||||
* that allows selecting multiple elements.
|
||||
*/
|
||||
|
||||
struct _GtkMultiSelection
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
GListModel *model;
|
||||
|
||||
GtkSet *selected;
|
||||
guint last_selected;
|
||||
};
|
||||
|
||||
struct _GtkMultiSelectionClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_MODEL,
|
||||
|
||||
N_PROPS,
|
||||
};
|
||||
|
||||
static GParamSpec *properties[N_PROPS] = { NULL, };
|
||||
|
||||
static GType
|
||||
gtk_multi_selection_get_item_type (GListModel *list)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (list);
|
||||
|
||||
return g_list_model_get_item_type (self->model);
|
||||
}
|
||||
|
||||
static guint
|
||||
gtk_multi_selection_get_n_items (GListModel *list)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (list);
|
||||
|
||||
return g_list_model_get_n_items (self->model);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
gtk_multi_selection_get_item (GListModel *list,
|
||||
guint position)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (list);
|
||||
|
||||
return g_list_model_get_item (self->model, position);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_multi_selection_list_model_init (GListModelInterface *iface)
|
||||
{
|
||||
iface->get_item_type = gtk_multi_selection_get_item_type;
|
||||
iface->get_n_items = gtk_multi_selection_get_n_items;
|
||||
iface->get_item = gtk_multi_selection_get_item;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_multi_selection_is_selected (GtkSelectionModel *model,
|
||||
guint position)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
|
||||
|
||||
return gtk_set_contains (self->selected, position);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_multi_selection_select_range (GtkSelectionModel *model,
|
||||
guint position,
|
||||
guint n_items,
|
||||
gboolean exclusive)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
|
||||
|
||||
if (exclusive)
|
||||
gtk_set_remove_all (self->selected);
|
||||
gtk_set_add_range (self->selected, position, n_items);
|
||||
gtk_selection_model_selection_changed (model, position, n_items);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_multi_selection_unselect_range (GtkSelectionModel *model,
|
||||
guint position,
|
||||
guint n_items)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
|
||||
|
||||
gtk_set_remove_range (self->selected, position, n_items);
|
||||
gtk_selection_model_selection_changed (model, position, n_items);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_multi_selection_select_item (GtkSelectionModel *model,
|
||||
guint position,
|
||||
gboolean exclusive)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
|
||||
guint pos, n_items;
|
||||
|
||||
pos = position;
|
||||
n_items = 1;
|
||||
|
||||
self->last_selected = position;
|
||||
return gtk_multi_selection_select_range (model, pos, n_items, exclusive);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_multi_selection_unselect_item (GtkSelectionModel *model,
|
||||
guint position)
|
||||
{
|
||||
return gtk_multi_selection_unselect_range (model, position, 1);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_multi_selection_select_all (GtkSelectionModel *model)
|
||||
{
|
||||
return gtk_multi_selection_select_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)), FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_multi_selection_unselect_all (GtkSelectionModel *model)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
|
||||
self->last_selected = GTK_INVALID_LIST_POSITION;
|
||||
return gtk_multi_selection_unselect_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_multi_selection_query_range (GtkSelectionModel *model,
|
||||
guint position,
|
||||
guint *start_range,
|
||||
guint *n_items,
|
||||
gboolean *selected)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
|
||||
guint upper_bound = g_list_model_get_n_items (self->model);
|
||||
|
||||
gtk_set_find_range (self->selected, position, upper_bound, start_range, n_items, selected);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_multi_selection_selection_model_init (GtkSelectionModelInterface *iface)
|
||||
{
|
||||
iface->is_selected = gtk_multi_selection_is_selected;
|
||||
iface->select_item = gtk_multi_selection_select_item;
|
||||
iface->unselect_item = gtk_multi_selection_unselect_item;
|
||||
iface->select_range = gtk_multi_selection_select_range;
|
||||
iface->unselect_range = gtk_multi_selection_unselect_range;
|
||||
iface->select_all = gtk_multi_selection_select_all;
|
||||
iface->unselect_all = gtk_multi_selection_unselect_all;
|
||||
iface->query_range = gtk_multi_selection_query_range;
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (GtkMultiSelection, gtk_multi_selection, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
|
||||
gtk_multi_selection_list_model_init)
|
||||
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
|
||||
gtk_multi_selection_selection_model_init))
|
||||
|
||||
static void
|
||||
gtk_multi_selection_items_changed_cb (GListModel *model,
|
||||
guint position,
|
||||
guint removed,
|
||||
guint added,
|
||||
GtkMultiSelection *self)
|
||||
{
|
||||
gtk_set_remove_range (self->selected, position, removed);
|
||||
gtk_set_shift (self->selected, position, (int)added - (int)removed);
|
||||
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_multi_selection_clear_model (GtkMultiSelection *self)
|
||||
{
|
||||
if (self->model == NULL)
|
||||
return;
|
||||
|
||||
g_signal_handlers_disconnect_by_func (self->model,
|
||||
gtk_multi_selection_items_changed_cb,
|
||||
self);
|
||||
g_clear_object (&self->model);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_multi_selection_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_MODEL:
|
||||
self->model = g_value_dup_object (value);
|
||||
g_warn_if_fail (self->model != NULL);
|
||||
g_signal_connect (self->model,
|
||||
"items-changed",
|
||||
G_CALLBACK (gtk_multi_selection_items_changed_cb),
|
||||
self);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_multi_selection_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_MODEL:
|
||||
g_value_set_object (value, self->model);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_multi_selection_dispose (GObject *object)
|
||||
{
|
||||
GtkMultiSelection *self = GTK_MULTI_SELECTION (object);
|
||||
|
||||
gtk_multi_selection_clear_model (self);
|
||||
|
||||
g_clear_pointer (&self->selected, gtk_set_free);
|
||||
self->last_selected = GTK_INVALID_LIST_POSITION;
|
||||
|
||||
G_OBJECT_CLASS (gtk_multi_selection_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_multi_selection_class_init (GtkMultiSelectionClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
gobject_class->get_property = gtk_multi_selection_get_property;
|
||||
gobject_class->set_property = gtk_multi_selection_set_property;
|
||||
gobject_class->dispose = gtk_multi_selection_dispose;
|
||||
|
||||
/**
|
||||
* GtkMultiSelection:model
|
||||
*
|
||||
* The list managed by this selection
|
||||
*/
|
||||
properties[PROP_MODEL] =
|
||||
g_param_spec_object ("model",
|
||||
P_("Model"),
|
||||
P_("List managed by this selection"),
|
||||
G_TYPE_LIST_MODEL,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_multi_selection_init (GtkMultiSelection *self)
|
||||
{
|
||||
self->selected = gtk_set_new ();
|
||||
self->last_selected = GTK_INVALID_LIST_POSITION;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_multi_selection_new:
|
||||
* @model: (transfer none): the #GListModel to manage
|
||||
*
|
||||
* Creates a new selection to handle @model.
|
||||
*
|
||||
* Returns: (transfer full) (type GtkMultiSelection): a new #GtkMultiSelection
|
||||
**/
|
||||
GListModel *
|
||||
gtk_multi_selection_new (GListModel *model)
|
||||
{
|
||||
g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
|
||||
|
||||
return g_object_new (GTK_TYPE_MULTI_SELECTION,
|
||||
"model", model,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_multi_selection_copy:
|
||||
* @selection: the #GtkSelectionModel to copy
|
||||
*
|
||||
* Creates a #GtkMultiSelection that has the same underlying
|
||||
* model and the same selected items as @selection.
|
||||
*
|
||||
* Returns: (transfer full): a new #GtkMultiSelection
|
||||
*/
|
||||
GtkMultiSelection *
|
||||
gtk_multi_selection_copy (GtkSelectionModel *selection)
|
||||
{
|
||||
GtkMultiSelection *copy;
|
||||
GListModel *model;
|
||||
|
||||
g_object_get (selection, "model", &model, NULL);
|
||||
|
||||
copy = GTK_MULTI_SELECTION (gtk_multi_selection_new (model));
|
||||
|
||||
if (GTK_IS_MULTI_SELECTION (selection))
|
||||
{
|
||||
GtkMultiSelection *multi = GTK_MULTI_SELECTION (selection);
|
||||
|
||||
gtk_set_free (copy->selected);
|
||||
copy->selected = gtk_set_copy (multi->selected);
|
||||
copy->last_selected = multi->last_selected;
|
||||
}
|
||||
else
|
||||
{
|
||||
guint pos, n;
|
||||
guint start, n_items;
|
||||
gboolean selected;
|
||||
|
||||
n = g_list_model_get_n_items (model);
|
||||
n_items = 0;
|
||||
for (pos = 0; pos < n; pos += n_items)
|
||||
{
|
||||
gtk_selection_model_query_range (selection, pos, &start, &n_items, &selected);
|
||||
if (selected)
|
||||
gtk_selection_model_select_range (GTK_SELECTION_MODEL (copy), start, n_items, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
g_object_unref (model);
|
||||
|
||||
return copy;
|
||||
}
|
41
gtk/gtkmultiselection.h
Normal file
41
gtk/gtkmultiselection.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2019 Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Matthias Clasen <mclasen@redhat.com>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_MULTI_SELECTION_H__
|
||||
#define __GTK_MULTI_SELECTION_H__
|
||||
|
||||
#include <gtk/gtktypes.h>
|
||||
#include <gtk/gtkselectionmodel.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_MULTI_SELECTION (gtk_multi_selection_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
G_DECLARE_FINAL_TYPE (GtkMultiSelection, gtk_multi_selection, GTK, MULTI_SELECTION, GObject)
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GListModel * gtk_multi_selection_new (GListModel *model);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkMultiSelection * gtk_multi_selection_copy (GtkSelectionModel *selection);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_MULTI_SELECTION_H__ */
|
351
gtk/gtkset.c
Normal file
351
gtk/gtkset.c
Normal file
@ -0,0 +1,351 @@
|
||||
/*
|
||||
* Copyright © 2019 Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Matthias Clasen <mclasen@redhat.com>
|
||||
*/
|
||||
|
||||
#include "gtkset.h"
|
||||
|
||||
/* Store a set of unsigned integers as a sorted array of ranges.
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint first;
|
||||
guint n_items;
|
||||
} Range;
|
||||
|
||||
struct _GtkSet
|
||||
{
|
||||
GArray *ranges;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkSet *set;
|
||||
Range *current;
|
||||
int idx;
|
||||
guint pos;
|
||||
} GtkRealSetIter;
|
||||
|
||||
GtkSet *
|
||||
gtk_set_new (void)
|
||||
{
|
||||
GtkSet *set;
|
||||
|
||||
set = g_new (GtkSet, 1);
|
||||
set->ranges = g_array_new (FALSE, FALSE, sizeof (Range));
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
GtkSet *
|
||||
gtk_set_copy (GtkSet *set)
|
||||
{
|
||||
GtkSet *copy;
|
||||
|
||||
copy = g_new (GtkSet, 1);
|
||||
copy->ranges = g_array_copy (set->ranges);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_set_free (GtkSet *set)
|
||||
{
|
||||
g_array_free (set->ranges, TRUE);
|
||||
g_free (set);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_set_contains (GtkSet *set,
|
||||
guint item)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < set->ranges->len; i++)
|
||||
{
|
||||
Range *r = &g_array_index (set->ranges, Range, i);
|
||||
|
||||
if (item < r->first)
|
||||
return FALSE;
|
||||
|
||||
if (item < r->first + r->n_items)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_set_remove_all (GtkSet *set)
|
||||
{
|
||||
g_array_set_size (set->ranges, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
range_compare (Range *r, Range *s)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (r->first + r->n_items < s->first)
|
||||
ret = -1;
|
||||
else if (s->first + s->n_items < r->first)
|
||||
ret = 1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_set_add_range (GtkSet *set,
|
||||
guint first_item,
|
||||
guint n_items)
|
||||
{
|
||||
int i;
|
||||
Range s;
|
||||
int first = -1;
|
||||
int last = -1;
|
||||
|
||||
s.first = first_item;
|
||||
s.n_items = n_items;
|
||||
|
||||
for (i = 0; i < set->ranges->len; i++)
|
||||
{
|
||||
Range *r = &g_array_index (set->ranges, Range, i);
|
||||
int cmp = range_compare (&s, r);
|
||||
|
||||
if (cmp < 0)
|
||||
break;
|
||||
|
||||
if (cmp == 0)
|
||||
{
|
||||
if (first < 0)
|
||||
first = i;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (first > -1)
|
||||
{
|
||||
Range *r;
|
||||
guint start;
|
||||
guint end;
|
||||
|
||||
r = &g_array_index (set->ranges, Range, first);
|
||||
start = MIN (s.first, r->first);
|
||||
|
||||
r = &g_array_index (set->ranges, Range, last);
|
||||
end = MAX (s.first + s.n_items - 1, r->first + r->n_items - 1);
|
||||
|
||||
s.first = start;
|
||||
s.n_items = end - start + 1;
|
||||
|
||||
g_array_remove_range (set->ranges, first, last - first + 1);
|
||||
g_array_insert_val (set->ranges, first, s);
|
||||
}
|
||||
else
|
||||
g_array_insert_val (set->ranges, i, s);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_set_remove_range (GtkSet *set,
|
||||
guint first_item,
|
||||
guint n_items)
|
||||
{
|
||||
Range s;
|
||||
int i;
|
||||
int first = -1;
|
||||
int last = -1;
|
||||
|
||||
s.first = first_item;
|
||||
s.n_items = n_items;
|
||||
|
||||
for (i = 0; i < set->ranges->len; i++)
|
||||
{
|
||||
Range *r = &g_array_index (set->ranges, Range, i);
|
||||
int cmp = range_compare (&s, r);
|
||||
|
||||
if (cmp < 0)
|
||||
break;
|
||||
|
||||
if (cmp == 0)
|
||||
{
|
||||
if (first < 0)
|
||||
first = i;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (first > -1)
|
||||
{
|
||||
Range *r;
|
||||
Range a[2];
|
||||
int k = 0;
|
||||
|
||||
r = &g_array_index (set->ranges, Range, first);
|
||||
if (r->first < s.first)
|
||||
{
|
||||
a[k].first = r->first;
|
||||
a[k].n_items = s.first - r->first;
|
||||
k++;
|
||||
}
|
||||
r = &g_array_index (set->ranges, Range, last);
|
||||
if (r->first + r->n_items > s.first + s.n_items)
|
||||
{
|
||||
a[k].first = s.first + s.n_items;
|
||||
a[k].n_items = r->first + r->n_items - a[k].first;
|
||||
k++;
|
||||
}
|
||||
g_array_remove_range (set->ranges, first, last - first + 1);
|
||||
if (k > 0)
|
||||
g_array_insert_vals (set->ranges, first, a, k);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gtk_set_find_range (GtkSet *set,
|
||||
guint position,
|
||||
guint upper_bound,
|
||||
guint *start,
|
||||
guint *n_items,
|
||||
gboolean *contained)
|
||||
{
|
||||
int i;
|
||||
int last = 0;
|
||||
|
||||
if (position >= upper_bound)
|
||||
{
|
||||
*start = 0;
|
||||
*n_items = 0;
|
||||
*contained = FALSE;
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < set->ranges->len; i++)
|
||||
{
|
||||
Range *r = &g_array_index (set->ranges, Range, i);
|
||||
|
||||
if (position < r->first)
|
||||
{
|
||||
*start = last;
|
||||
*n_items = r->first - last;
|
||||
*contained = FALSE;
|
||||
|
||||
return;
|
||||
}
|
||||
else if (r->first <= position && position < r->first + r->n_items)
|
||||
{
|
||||
*start = r->first;
|
||||
*n_items = r->n_items;
|
||||
*contained = TRUE;
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
last = r->first + r->n_items;
|
||||
}
|
||||
|
||||
*start = last;
|
||||
*n_items = upper_bound - last;
|
||||
*contained = FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_set_add_item (GtkSet *set,
|
||||
guint item)
|
||||
{
|
||||
gtk_set_add_range (set, item, 1);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_set_remove_item (GtkSet *set,
|
||||
guint item)
|
||||
{
|
||||
gtk_set_remove_range (set, item, 1);
|
||||
}
|
||||
|
||||
/* This is peculiar operation: Replace every number n >= first by n + shift
|
||||
* This is only supported for negative shifts if the shifting does not cause
|
||||
* any ranges to overlap.
|
||||
*/
|
||||
void
|
||||
gtk_set_shift (GtkSet *set,
|
||||
guint first,
|
||||
int shift)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < set->ranges->len; i++)
|
||||
{
|
||||
Range *r = &g_array_index (set->ranges, Range, i);
|
||||
if (r->first >= first)
|
||||
r->first += shift;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gtk_set_iter_init (GtkSetIter *iter,
|
||||
GtkSet *set)
|
||||
{
|
||||
GtkRealSetIter *ri = (GtkRealSetIter *)iter;
|
||||
|
||||
ri->set = set;
|
||||
ri->idx = -1;
|
||||
ri->current = 0;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_set_iter_next (GtkSetIter *iter,
|
||||
guint *item)
|
||||
{
|
||||
GtkRealSetIter *ri = (GtkRealSetIter *)iter;
|
||||
|
||||
if (ri->idx == -1)
|
||||
{
|
||||
next_range:
|
||||
ri->idx++;
|
||||
|
||||
if (ri->idx == ri->set->ranges->len)
|
||||
return FALSE;
|
||||
|
||||
ri->current = &g_array_index (ri->set->ranges, Range, ri->idx);
|
||||
ri->pos = ri->current->first;
|
||||
}
|
||||
else
|
||||
{
|
||||
ri->pos++;
|
||||
if (ri->pos == ri->current->first + ri->current->n_items)
|
||||
goto next_range;
|
||||
}
|
||||
|
||||
*item = ri->pos;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#if 0
|
||||
void
|
||||
gtk_set_dump (GtkSet *set)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < set->ranges->len; i++)
|
||||
{
|
||||
Range *r = &g_array_index (set->ranges, Range, i);
|
||||
g_print (" %u:%u", r->first, r->n_items);
|
||||
}
|
||||
g_print ("\n");
|
||||
}
|
||||
#endif
|
70
gtk/gtkset.h
Normal file
70
gtk/gtkset.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright © 2019 Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Matthias Clasen <mclasen@redhat.com>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_SET_H__
|
||||
#define __GTK_SET_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
typedef struct _GtkSet GtkSet;
|
||||
typedef struct _GtkSetIter GtkSetIter;
|
||||
|
||||
struct _GtkSetIter
|
||||
{
|
||||
gpointer dummy1;
|
||||
gpointer dummy2;
|
||||
int dummy3;
|
||||
int dummy4;
|
||||
};
|
||||
|
||||
GtkSet *gtk_set_new (void);
|
||||
void gtk_set_free (GtkSet *set);
|
||||
GtkSet *gtk_set_copy (GtkSet *set);
|
||||
|
||||
gboolean gtk_set_contains (GtkSet *set,
|
||||
guint item);
|
||||
|
||||
void gtk_set_remove_all (GtkSet *set);
|
||||
void gtk_set_add_item (GtkSet *set,
|
||||
guint item);
|
||||
void gtk_set_remove_item (GtkSet *set,
|
||||
guint item);
|
||||
void gtk_set_add_range (GtkSet *set,
|
||||
guint first,
|
||||
guint n);
|
||||
void gtk_set_remove_range (GtkSet *set,
|
||||
guint first,
|
||||
guint n);
|
||||
void gtk_set_find_range (GtkSet *set,
|
||||
guint position,
|
||||
guint upper_bound,
|
||||
guint *start,
|
||||
guint *n_items,
|
||||
gboolean *contained);
|
||||
|
||||
void gtk_set_shift (GtkSet *set,
|
||||
guint first,
|
||||
int shift);
|
||||
|
||||
void gtk_set_iter_init (GtkSetIter *iter,
|
||||
GtkSet *set);
|
||||
gboolean gtk_set_iter_next (GtkSetIter *iter,
|
||||
guint *item);
|
||||
|
||||
#endif /* __GTK_SET_H__ */
|
@ -134,6 +134,7 @@ gtk_private_sources = files([
|
||||
'gtkscaler.c',
|
||||
'gtksearchengine.c',
|
||||
'gtksearchenginemodel.c',
|
||||
'gtkset.c',
|
||||
'gtksizerequestcache.c',
|
||||
'gtkstyleanimation.c',
|
||||
'gtkstylecascade.c',
|
||||
@ -301,6 +302,7 @@ gtk_public_sources = files([
|
||||
'gtkmodules.c',
|
||||
'gtkmountoperation.c',
|
||||
'gtkmultifilter.c',
|
||||
'gtkmultiselection.c',
|
||||
'gtkmultisorter.c',
|
||||
'gtknativedialog.c',
|
||||
'gtknomediafile.c',
|
||||
@ -574,6 +576,7 @@ gtk_public_headers = files([
|
||||
'gtkmessagedialog.h',
|
||||
'gtkmountoperation.h',
|
||||
'gtkmultifilter.h',
|
||||
'gtkmultiselection.h',
|
||||
'gtkmultisorter.h',
|
||||
'gtknative.h',
|
||||
'gtknativedialog.h',
|
||||
|
@ -114,7 +114,8 @@ test_type (gconstpointer data)
|
||||
}
|
||||
else if (g_type_is_a (type, GTK_TYPE_FILTER_LIST_MODEL) ||
|
||||
g_type_is_a (type, GTK_TYPE_NO_SELECTION) ||
|
||||
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION))
|
||||
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION) ||
|
||||
g_type_is_a (type, GTK_TYPE_MULTI_SELECTION))
|
||||
{
|
||||
GListStore *list_store = g_list_store_new (G_TYPE_OBJECT);
|
||||
instance = g_object_new (type,
|
||||
@ -277,7 +278,8 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
|
||||
if ((g_type_is_a (type, GTK_TYPE_FILTER_LIST_MODEL) ||
|
||||
g_type_is_a (type, GTK_TYPE_NO_SELECTION) ||
|
||||
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION)) &&
|
||||
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION) ||
|
||||
g_type_is_a (type, GTK_TYPE_MULTI_SELECTION)) &&
|
||||
strcmp (pspec->name, "model") == 0)
|
||||
continue;
|
||||
|
||||
|
@ -39,6 +39,7 @@ tests = [
|
||||
['listbox'],
|
||||
['main'],
|
||||
['maplistmodel'],
|
||||
['multiselection'],
|
||||
['notify'],
|
||||
['no-gtk-init'],
|
||||
['object'],
|
||||
|
393
testsuite/gtk/multiselection.c
Normal file
393
testsuite/gtk/multiselection.c
Normal file
@ -0,0 +1,393 @@
|
||||
/*
|
||||
* Copyright (C) 2019, Red Hat, Inc.
|
||||
* Authors: Matthias Clasen <mclasen@redhat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
static GQuark number_quark;
|
||||
static GQuark changes_quark;
|
||||
static GQuark selection_quark;
|
||||
|
||||
static guint
|
||||
get (GListModel *model,
|
||||
guint position)
|
||||
{
|
||||
GObject *object = g_list_model_get_item (model, position);
|
||||
g_assert (object != NULL);
|
||||
return GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark));
|
||||
}
|
||||
|
||||
static char *
|
||||
model_to_string (GListModel *model)
|
||||
{
|
||||
GString *string = g_string_new (NULL);
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < g_list_model_get_n_items (model); i++)
|
||||
{
|
||||
if (i > 0)
|
||||
g_string_append (string, " ");
|
||||
g_string_append_printf (string, "%u", get (model, i));
|
||||
}
|
||||
|
||||
return g_string_free (string, FALSE);
|
||||
}
|
||||
|
||||
static char *
|
||||
selection_to_string (GListModel *model)
|
||||
{
|
||||
GString *string = g_string_new (NULL);
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < g_list_model_get_n_items (model); i++)
|
||||
{
|
||||
if (!gtk_selection_model_is_selected (GTK_SELECTION_MODEL (model), i))
|
||||
continue;
|
||||
|
||||
if (string->len > 0)
|
||||
g_string_append (string, " ");
|
||||
g_string_append_printf (string, "%u", get (model, i));
|
||||
}
|
||||
|
||||
return g_string_free (string, FALSE);
|
||||
}
|
||||
|
||||
static GListStore *
|
||||
new_store (guint start,
|
||||
guint end,
|
||||
guint step);
|
||||
|
||||
static GObject *
|
||||
make_object (guint number)
|
||||
{
|
||||
GObject *object;
|
||||
|
||||
/* 0 cannot be differentiated from NULL, so don't use it */
|
||||
g_assert (number != 0);
|
||||
|
||||
object = g_object_new (G_TYPE_OBJECT, NULL);
|
||||
g_object_set_qdata (object, number_quark, GUINT_TO_POINTER (number));
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
static void
|
||||
splice (GListStore *store,
|
||||
guint pos,
|
||||
guint removed,
|
||||
guint *numbers,
|
||||
guint added)
|
||||
{
|
||||
GObject **objects;
|
||||
guint i;
|
||||
|
||||
objects = g_new0 (GObject *, added);
|
||||
|
||||
for (i = 0; i < added; i++)
|
||||
objects[i] = make_object (numbers[i]);
|
||||
|
||||
g_list_store_splice (store, pos, removed, (gpointer *) objects, added);
|
||||
|
||||
for (i = 0; i < added; i++)
|
||||
g_object_unref (objects[i]);
|
||||
|
||||
g_free (objects);
|
||||
}
|
||||
|
||||
static void
|
||||
add (GListStore *store,
|
||||
guint number)
|
||||
{
|
||||
GObject *object = make_object (number);
|
||||
g_list_store_append (store, object);
|
||||
g_object_unref (object);
|
||||
}
|
||||
|
||||
static void
|
||||
insert (GListStore *store,
|
||||
guint position,
|
||||
guint number)
|
||||
{
|
||||
GObject *object = make_object (number);
|
||||
g_list_store_insert (store, position, object);
|
||||
g_object_unref (object);
|
||||
}
|
||||
|
||||
#define assert_model(model, expected) G_STMT_START{ \
|
||||
char *s = model_to_string (G_LIST_MODEL (model)); \
|
||||
if (!g_str_equal (s, expected)) \
|
||||
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
|
||||
#model " == " #expected, s, "==", expected); \
|
||||
g_free (s); \
|
||||
}G_STMT_END
|
||||
|
||||
#define ignore_changes(model) G_STMT_START{ \
|
||||
GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
|
||||
g_string_set_size (changes, 0); \
|
||||
}G_STMT_END
|
||||
|
||||
#define assert_changes(model, expected) G_STMT_START{ \
|
||||
GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
|
||||
if (!g_str_equal (changes->str, expected)) \
|
||||
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
|
||||
#model " == " #expected, changes->str, "==", expected); \
|
||||
g_string_set_size (changes, 0); \
|
||||
}G_STMT_END
|
||||
|
||||
#define assert_selection(model, expected) G_STMT_START{ \
|
||||
char *s = selection_to_string (G_LIST_MODEL (model)); \
|
||||
if (!g_str_equal (s, expected)) \
|
||||
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
|
||||
#model " == " #expected, s, "==", expected); \
|
||||
g_free (s); \
|
||||
}G_STMT_END
|
||||
|
||||
#define ignore_selection_changes(model) G_STMT_START{ \
|
||||
GString *changes = g_object_get_qdata (G_OBJECT (model), selection_quark); \
|
||||
g_string_set_size (changes, 0); \
|
||||
}G_STMT_END
|
||||
|
||||
#define assert_selection_changes(model, expected) G_STMT_START{ \
|
||||
GString *changes = g_object_get_qdata (G_OBJECT (model), selection_quark); \
|
||||
if (!g_str_equal (changes->str, expected)) \
|
||||
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
|
||||
#model " == " #expected, changes->str, "==", expected); \
|
||||
g_string_set_size (changes, 0); \
|
||||
}G_STMT_END
|
||||
|
||||
static GListStore *
|
||||
new_empty_store (void)
|
||||
{
|
||||
return g_list_store_new (G_TYPE_OBJECT);
|
||||
}
|
||||
|
||||
static GListStore *
|
||||
new_store (guint start,
|
||||
guint end,
|
||||
guint step)
|
||||
{
|
||||
GListStore *store = new_empty_store ();
|
||||
guint i;
|
||||
|
||||
for (i = start; i <= end; i += step)
|
||||
add (store, i);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
static void
|
||||
items_changed (GListModel *model,
|
||||
guint position,
|
||||
guint removed,
|
||||
guint added,
|
||||
GString *changes)
|
||||
{
|
||||
g_assert (removed != 0 || added != 0);
|
||||
|
||||
if (changes->len)
|
||||
g_string_append (changes, ", ");
|
||||
|
||||
if (removed == 1 && added == 0)
|
||||
{
|
||||
g_string_append_printf (changes, "-%u", position);
|
||||
}
|
||||
else if (removed == 0 && added == 1)
|
||||
{
|
||||
g_string_append_printf (changes, "+%u", position);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_string_append_printf (changes, "%u", position);
|
||||
if (removed > 0)
|
||||
g_string_append_printf (changes, "-%u", removed);
|
||||
if (added > 0)
|
||||
g_string_append_printf (changes, "+%u", added);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
selection_changed (GListModel *model,
|
||||
guint position,
|
||||
guint n_items,
|
||||
GString *changes)
|
||||
{
|
||||
if (changes->len)
|
||||
g_string_append (changes, ", ");
|
||||
|
||||
g_string_append_printf (changes, "%u:%u", position, n_items);
|
||||
}
|
||||
|
||||
static void
|
||||
free_changes (gpointer data)
|
||||
{
|
||||
GString *changes = data;
|
||||
|
||||
/* all changes must have been checked via assert_changes() before */
|
||||
g_assert_cmpstr (changes->str, ==, "");
|
||||
|
||||
g_string_free (changes, TRUE);
|
||||
}
|
||||
|
||||
static GtkSelectionModel *
|
||||
new_model (GListStore *store)
|
||||
{
|
||||
GtkSelectionModel *result;
|
||||
GString *changes;
|
||||
|
||||
result = GTK_SELECTION_MODEL (gtk_multi_selection_new (G_LIST_MODEL (store)));
|
||||
|
||||
changes = g_string_new ("");
|
||||
g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes);
|
||||
g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes);
|
||||
|
||||
changes = g_string_new ("");
|
||||
g_object_set_qdata_full (G_OBJECT(result), selection_quark, changes, free_changes);
|
||||
g_signal_connect (result, "selection-changed", G_CALLBACK (selection_changed), changes);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
test_create (void)
|
||||
{
|
||||
GtkSelectionModel *selection;
|
||||
GListStore *store;
|
||||
|
||||
store = new_store (1, 5, 2);
|
||||
selection = new_model (store);
|
||||
|
||||
assert_model (selection, "1 3 5");
|
||||
assert_changes (selection, "");
|
||||
assert_selection (selection, "");
|
||||
assert_selection_changes (selection, "");
|
||||
|
||||
g_object_unref (store);
|
||||
assert_model (selection, "1 3 5");
|
||||
assert_changes (selection, "");
|
||||
assert_selection (selection, "");
|
||||
assert_selection_changes (selection, "");
|
||||
|
||||
g_object_unref (selection);
|
||||
}
|
||||
|
||||
static void
|
||||
test_changes (void)
|
||||
{
|
||||
GtkSelectionModel *selection;
|
||||
GListStore *store;
|
||||
|
||||
store = new_store (1, 5, 1);
|
||||
selection = new_model (store);
|
||||
assert_model (selection, "1 2 3 4 5");
|
||||
assert_changes (selection, "");
|
||||
assert_selection (selection, "");
|
||||
assert_selection_changes (selection, "");
|
||||
|
||||
g_list_store_remove (store, 3);
|
||||
assert_model (selection, "1 2 3 5");
|
||||
assert_changes (selection, "-3");
|
||||
assert_selection (selection, "");
|
||||
assert_selection_changes (selection, "");
|
||||
|
||||
insert (store, 3, 99);
|
||||
assert_model (selection, "1 2 3 99 5");
|
||||
assert_changes (selection, "+3");
|
||||
assert_selection (selection, "");
|
||||
assert_selection_changes (selection, "");
|
||||
|
||||
splice (store, 3, 2, (guint[]) { 97 }, 1);
|
||||
assert_model (selection, "1 2 3 97");
|
||||
assert_changes (selection, "3-2+1");
|
||||
assert_selection (selection, "");
|
||||
assert_selection_changes (selection, "");
|
||||
|
||||
g_object_unref (store);
|
||||
g_object_unref (selection);
|
||||
}
|
||||
|
||||
static void
|
||||
test_selection (void)
|
||||
{
|
||||
GtkSelectionModel *selection;
|
||||
GListStore *store;
|
||||
gboolean ret;
|
||||
|
||||
store = new_store (1, 5, 1);
|
||||
selection = new_model (store);
|
||||
assert_selection (selection, "");
|
||||
assert_selection_changes (selection, "");
|
||||
|
||||
ret = gtk_selection_model_select_item (selection, 3, FALSE);
|
||||
g_assert_true (ret);
|
||||
assert_selection (selection, "4");
|
||||
assert_selection_changes (selection, "3:1");
|
||||
|
||||
ret = gtk_selection_model_unselect_item (selection, 3);
|
||||
g_assert_true (ret);
|
||||
assert_selection (selection, "");
|
||||
assert_selection_changes (selection, "3:1");
|
||||
|
||||
ret = gtk_selection_model_select_item (selection, 1, FALSE);
|
||||
g_assert_true (ret);
|
||||
assert_selection (selection, "2");
|
||||
assert_selection_changes (selection, "1:1");
|
||||
|
||||
ret = gtk_selection_model_select_range (selection, 3, 2, FALSE);
|
||||
g_assert_true (ret);
|
||||
assert_selection (selection, "2 4 5");
|
||||
assert_selection_changes (selection, "3:2");
|
||||
|
||||
ret = gtk_selection_model_unselect_range (selection, 3, 2);
|
||||
g_assert_true (ret);
|
||||
assert_selection (selection, "2");
|
||||
assert_selection_changes (selection, "3:2");
|
||||
|
||||
ret = gtk_selection_model_select_all (selection);
|
||||
g_assert_true (ret);
|
||||
assert_selection (selection, "1 2 3 4 5");
|
||||
assert_selection_changes (selection, "0:5");
|
||||
|
||||
ret = gtk_selection_model_unselect_all (selection);
|
||||
g_assert_true (ret);
|
||||
assert_selection (selection, "");
|
||||
assert_selection_changes (selection, "0:5");
|
||||
|
||||
g_object_unref (store);
|
||||
g_object_unref (selection);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
setlocale (LC_ALL, "C");
|
||||
g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=%s");
|
||||
|
||||
number_quark = g_quark_from_static_string ("Hell and fire was spawned to be released.");
|
||||
changes_quark = g_quark_from_static_string ("What did I see? Can I believe what I saw?");
|
||||
selection_quark = g_quark_from_static_string ("Mana mana, badibidibi");
|
||||
|
||||
g_test_add_func ("/multiselection/create", test_create);
|
||||
#if GLIB_CHECK_VERSION (2, 58, 0) /* g_list_store_splice() is broken before 2.58 */
|
||||
g_test_add_func ("/multiselection/changes", test_changes);
|
||||
#endif
|
||||
g_test_add_func ("/multiselection/selection", test_selection);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
@ -457,7 +457,8 @@ test_type (gconstpointer data)
|
||||
}
|
||||
else if (g_type_is_a (type, GTK_TYPE_FILTER_LIST_MODEL) ||
|
||||
g_type_is_a (type, GTK_TYPE_NO_SELECTION) ||
|
||||
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION))
|
||||
g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION) ||
|
||||
g_type_is_a (type, GTK_TYPE_MULTI_SELECTION))
|
||||
{
|
||||
GListStore *list_store = g_list_store_new (G_TYPE_OBJECT);
|
||||
instance = g_object_new (type,
|
||||
|
@ -71,7 +71,8 @@ test_finalize_object (gconstpointer data)
|
||||
}
|
||||
else if (g_type_is_a (test_type, GTK_TYPE_FILTER_LIST_MODEL) ||
|
||||
g_type_is_a (test_type, GTK_TYPE_NO_SELECTION) ||
|
||||
g_type_is_a (test_type, GTK_TYPE_SINGLE_SELECTION))
|
||||
g_type_is_a (test_type, GTK_TYPE_SINGLE_SELECTION) ||
|
||||
g_type_is_a (test_type, GTK_TYPE_MULTI_SELECTION))
|
||||
{
|
||||
GListStore *list_store = g_list_store_new (G_TYPE_OBJECT);
|
||||
object = g_object_new (test_type,
|
||||
|
Loading…
Reference in New Issue
Block a user