Compare commits

...

42 Commits

Author SHA1 Message Date
Matthias Clasen
d17f224e71 Add search support
Add a filter to GtkListBase, and move the selection
to the first matching item whenever the filter changes.
This is meant to be used with single selection and
a string filter that is hooked up to a search entry.
2020-05-30 22:27:54 -04:00
Matthias Clasen
b73be6e344 printdialog: Port to column view
This is not 100% complete. The search is not there yet.
2020-05-30 22:27:54 -04:00
Matthias Clasen
4a80f23213 printer: Fix the default value of icon-name
Just set this to "printer", so we don't have
to fix it up in the print dialog.
2020-05-30 20:01:14 -04:00
Matthias Clasen
3b8888acda printbackend: Add a list model getter
Now that we have a list model for printers,
we can start using it.
2020-05-30 20:01:14 -04:00
Matthias Clasen
786c95e503 printbackend: Use a list store 2020-05-30 20:01:14 -04:00
Matthias Clasen
7007c72fc5 inspector: Expand the actions list 2020-05-30 20:01:14 -04:00
Matthias Clasen
9eaeb81433 inspector: Expand the resource list
This is an experiment with adding a filler column.
2020-05-30 20:01:14 -04:00
Matthias Clasen
3bddbcdd52 inspector: Expand the property list
It looks better this way.
2020-05-30 20:01:14 -04:00
Matthias Clasen
13d5d60db0 inspector: Expand the object tree
This is how it used to look, and it looks better that way.
2020-05-30 20:01:14 -04:00
Matthias Clasen
1b24528b9c columnview: Take expand into account
When allocating columns, distribute extra space
to columns that have expand set to TRUE.
2020-05-30 20:01:14 -04:00
Matthias Clasen
6e7ebc49eb columnviewcolumn: Add an expand property
This will be used to determine how to distribute
available extra space in a column view.
2020-05-30 20:01:14 -04:00
Matthias Clasen
bdc6770bd9 gtk-demo: Add more scrolling benchmarks
Add a listview and gridview to the scrolling
benchmarks.
2020-05-30 20:01:14 -04:00
Matthias Clasen
8d2fef1e23 gtk-demo: Make gridview demo use rubberbanding 2020-05-30 20:01:14 -04:00
Matthias Clasen
90668f7e01 Add rubberband api
Add an enable-rubberband property to GtkListView,
GtkGridView and GtkColumnView.
2020-05-30 20:01:14 -04:00
Matthias Clasen
74f72a146c listbase: Add rubberband selection
Implement the typical rubberband selection, including
autoscroll. This is only useful with multiselection,
and not very compatible with single-click-activate.
Therefore, it is not enabled by default, and needs
to be turned on with the enable-rubber-band property.
2020-05-30 20:01:14 -04:00
Matthias Clasen
5553bf1c39 Add GtkMultiSelection
This is implemented using a private GtkSet helper.
2020-05-30 20:01:14 -04:00
Matthias Clasen
77a3f81971 Add a selection model test
The test shows that we are failing to emit
::selection-changed in some circumstances.
2020-05-30 20:01:14 -04:00
Matthias Clasen
b3e0353e6b testcolumnview: Reordering in the column editor
Use Ctrl-Up/Down to move the column around.
2020-05-30 20:01:14 -04:00
Matthias Clasen
780915e318 testcolumnview: Flesh out column editor
Turn the column list into an editor with
controls for visibility, resizability, reorderability
and width.
2020-05-30 20:01:14 -04:00
Matthias Clasen
7242b801ff columnviewtitle: Display a context menu
When the ::header-menu property is set on the
column, use the menu model to create and show
a context menu.
2020-05-30 20:01:14 -04:00
Matthias Clasen
88669080c4 columnviewcolumn: Add a menu property
Add a ::header-menu property that will be used
to create a context menu for the header of the
column.
2020-05-30 20:01:14 -04:00
Matthias Clasen
51e8dc924b columnview: Add autoscroll
Autoscroll when the pointer gets close to the
edge during column resizing or reordering. This
is similar to what the treeview does, but it is
implemented using a tick callback, and has
variable speed.
2020-05-30 20:01:14 -04:00
Matthias Clasen
29b5d3ab38 columnview: Allow to cancel reorder with Escape
The treeview does this too.
2020-05-30 20:01:14 -04:00
Matthias Clasen
bb06fffab1 columnview: Interactive column reordering
Allow rearranging columns by dragging, in the same
way the treeview does.

We add the "dnd" style class to the header while
it is dragged, and we move the header of the dragged
column to the end of its parents children, so that
it gets drawn on top.
2020-05-30 20:01:14 -04:00
Matthias Clasen
94c5b00231 columnviewcolumn: Add a reorderable property
This will be used for interactive column reordering
in the future.
2020-05-30 20:01:14 -04:00
Matthias Clasen
59e49dafd7 columnviewlayout: Use header allocation for titles
Normally, this will be identical to the column
allocation, but we will temporarily change it
during column reordering.
2020-05-30 20:01:14 -04:00
Matthias Clasen
d5ce5b53d6 columnviewcolumn: Add reordering helpers
Add helper functions that let us temporarily give
a different allocation to headers. These will be
used to implement interactive column reordering
in GtkColumnView.
2020-05-30 20:01:14 -04:00
Matthias Clasen
915bdf24a7 columnviewtitle: Invert on release
This is necessary to make drag-to-reorder work
without triggering inversion.
2020-05-30 20:01:14 -04:00
Matthias Clasen
114054266b columnview: Interactive column resizing
This copies just enough of the treeview code to
get columns moving.
2020-05-30 20:01:14 -04:00
Matthias Clasen
dcea6d6cda columnviewcolumn: Add a helper
We need to check whether clicks are in the headers
of columns, so let the column view get at the the
header widget.
2020-05-30 20:01:14 -04:00
Matthias Clasen
a118267ab7 columnviewcolumn: Add a resizable property
This will be used for interactive column resizing
in the future.
2020-05-30 20:01:14 -04:00
Matthias Clasen
cc63c581c3 columnview: Add a helper
The column code needs to get access to the
listitem widgets that are children of the listview,
so add a getter.
2020-05-30 20:01:14 -04:00
Matthias Clasen
dbc1a8ba11 columnview: Add column reordering
Add an API to allow reordering columns.
2020-05-30 20:01:14 -04:00
Matthias Clasen
515a86645a columnview: Implement horizontal scrolling
The listview inside always thinks it gets its full size,
and updates its horizontal adjustment accordingly.

So keep our own adjustment, and update it in size_allocate.
2020-05-30 20:01:14 -04:00
Matthias Clasen
cbabb85e6d columnview: Revise scroll-minimum handling
Tweak the behavior slightly. We don't show
a scrollbar as long as we have at least
min-size available, but we still give the
entire size to the child, up to nat-size.

This matches how viewports handle scroll-minimum.
2020-05-30 20:01:14 -04:00
Matthias Clasen
1031625bd4 columnviewcolumn: Add a visible property
This lets us hide columns, which is useful.
2020-05-30 20:01:14 -04:00
Matthias Clasen
65ed3aa1d9 inspector: Touch up list styling
This is just the minimal amount of work to make
headers recognizable.
2020-05-30 20:01:14 -04:00
Matthias Clasen
ed04c46078 inspector: Use a column view for actions
A straight conversion from list box to column view.
2020-05-30 20:01:14 -04:00
Matthias Clasen
1b2df91e6d inspector: Make the resource list sortable
This is using a GtkTreeListRowSorter to keep expanded
state of the tree while changing the sorting.
2020-05-30 20:01:14 -04:00
Matthias Clasen
f745c0c2aa inspector: Use a column view for the resource list
A conversion from tree view to column view.
2020-05-30 20:01:14 -04:00
Matthias Clasen
2ed46450ea inspector: Use a column view for properties
Just a straight conversion from list box to column view.
2020-05-30 20:01:14 -04:00
Matthias Clasen
6d168079d4 inspector: Add columns to the object tree
Add columnview columns in the object tree.
We do the same for treeview columns.
2020-05-30 20:01:14 -04:00
52 changed files with 5651 additions and 1574 deletions

View File

@@ -12,7 +12,7 @@ static GtkWidget *window = NULL;
static GtkWidget *scrolledwindow;
static int selected;
#define N_WIDGET_TYPES 4
#define N_WIDGET_TYPES 6
static int hincrement = 5;
@@ -64,6 +64,7 @@ populate_icons (void)
gtk_grid_attach (GTK_GRID (grid), create_icon (), left, top, 1, 1);
hincrement = 0;
vincrement = 5;
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow),
GTK_POLICY_NEVER,
@@ -100,6 +101,7 @@ populate_text (gboolean hilight)
gtk_text_view_set_buffer (GTK_TEXT_VIEW (textview), buffer);
hincrement = 0;
vincrement = 5;
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow),
GTK_POLICY_NEVER,
@@ -124,6 +126,7 @@ populate_image (void)
gtk_picture_set_can_shrink (GTK_PICTURE (image), FALSE);
hincrement = 5;
vincrement = 5;
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow),
GTK_POLICY_AUTOMATIC,
@@ -131,6 +134,42 @@ populate_image (void)
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolledwindow), image);
}
extern GtkWidget *create_weather_view (void);
static void
populate_list (void)
{
GtkWidget *list;
list = create_weather_view ();
hincrement = 5;
vincrement = 0;
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolledwindow), list);
}
extern GtkWidget *create_color_grid (void);
static void
populate_grid (void)
{
GtkWidget *list;
list = create_color_grid ();
hincrement = 0;
vincrement = 5;
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolledwindow), list);
}
static void
set_widget_type (int type)
{
@@ -164,6 +203,16 @@ set_widget_type (int type)
populate_image ();
break;
case 4:
gtk_window_set_title (GTK_WINDOW (window), "Scrolling a list");
populate_list ();
break;
case 5:
gtk_window_set_title (GTK_WINDOW (window), "Scrolling a grid");
populate_grid ();
break;
default:
g_assert_not_reached ();
}

View File

@@ -310,6 +310,7 @@ setup_simple_listitem_cb (GtkListItemFactory *factory,
color_expression = gtk_property_expression_new (GTK_TYPE_LIST_ITEM, expression, "item");
picture = gtk_picture_new ();
gtk_widget_set_size_request (picture, 32, 32);
gtk_expression_bind (color_expression, picture, "paintable", NULL);
gtk_list_item_set_child (list_item, picture);
@@ -404,6 +405,34 @@ set_item (GBinding *binding,
return TRUE;
}
GtkWidget *
create_color_grid (void)
{
GtkWidget *gridview;
GtkListItemFactory *factory;
GListModel *model, *selection;
gridview = gtk_grid_view_new ();
gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL);
gtk_scrollable_set_vscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL);
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_simple_listitem_cb), NULL);
gtk_grid_view_set_factory (GTK_GRID_VIEW (gridview), factory);
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_multi_selection_new (model));
gtk_grid_view_set_model (GTK_GRID_VIEW (gridview), selection);
g_object_unref (selection);
g_object_unref (model);
return gridview;
}
static GtkWidget *window = NULL;
GtkWidget *
@@ -415,7 +444,7 @@ do_listview_colors (GtkWidget *do_widget)
GtkListItemFactory *factory;
GListStore *factories;
GListModel *model;
GtkNoSelection *selection;
GtkSorter *sorter;
GtkSorter *multi_sorter;
GListStore *sorters;
@@ -435,17 +464,10 @@ do_listview_colors (GtkWidget *do_widget)
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_window_set_child (GTK_WINDOW (window), sw);
gridview = gtk_grid_view_new ();
gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL);
gtk_scrollable_set_vscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL);
gtk_grid_view_set_max_columns (GTK_GRID_VIEW (gridview), 24);
model = G_LIST_MODEL (gtk_sort_list_model_new (create_colors_model (), NULL));
selection = gtk_no_selection_new (model);
gtk_grid_view_set_model (GTK_GRID_VIEW (gridview), G_LIST_MODEL (selection));
gridview = create_color_grid ();
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), gridview);
g_object_unref (selection);
model = gtk_grid_view_get_model (GTK_GRID_VIEW (gridview));
g_object_get (model, "model", &model, NULL);
sorters = g_list_store_new (GTK_TYPE_SORTER);
@@ -559,7 +581,6 @@ do_listview_colors (GtkWidget *do_widget)
G_BINDING_SYNC_CREATE,
set_item, NULL,
NULL, NULL);
g_object_unref (model);
}

View File

@@ -275,13 +275,33 @@ bind_widget (GtkListItem *list_item,
static GtkWidget *window = NULL;
GtkWidget *
create_weather_view (void)
{
GtkWidget *listview;
GListModel *model, *selection;
listview = gtk_list_view_new_with_factory (
gtk_functions_list_item_factory_new (setup_widget,
bind_widget,
NULL, NULL));
gtk_orientable_set_orientation (GTK_ORIENTABLE (listview), GTK_ORIENTATION_HORIZONTAL);
gtk_list_view_set_show_separators (GTK_LIST_VIEW (listview), TRUE);
model = create_weather_model ();
selection = G_LIST_MODEL (gtk_no_selection_new (model));
gtk_list_view_set_model (GTK_LIST_VIEW (listview), selection);
g_object_unref (selection);
g_object_unref (model);
return listview;
}
GtkWidget *
do_listview_weather (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkWidget *listview, *sw;;
GListModel *model, *selection;
window = gtk_window_new ();
gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
@@ -293,19 +313,7 @@ do_listview_weather (GtkWidget *do_widget)
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_window_set_child (GTK_WINDOW (window), sw);
listview = gtk_list_view_new_with_factory (
gtk_functions_list_item_factory_new (setup_widget,
bind_widget,
NULL, NULL));
gtk_orientable_set_orientation (GTK_ORIENTABLE (listview), GTK_ORIENTATION_HORIZONTAL);
gtk_list_view_set_show_separators (GTK_LIST_VIEW (listview), TRUE);
model = create_weather_model ();
selection = G_LIST_MODEL (gtk_no_selection_new (model));
gtk_list_view_set_model (GTK_LIST_VIEW (listview), selection);
g_object_unref (selection);
g_object_unref (model);
listview = create_weather_view ();
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listview);
}

View File

@@ -479,6 +479,11 @@ gtk_list_view_set_show_separators
gtk_list_view_get_show_separators
gtk_list_view_set_single_click_activate
gtk_list_view_get_single_click_activate
gtk_list_view_set_enable_rubberband
gtk_list_view_get_enable_rubberband
gtk_list_view_set_search_filter
gtk_list_view_get_search_filter
gtk_list_view_next_match
<SUBSECTION Standard>
GTK_LIST_VIEW
GTK_LIST_VIEW_CLASS
@@ -496,6 +501,7 @@ gtk_list_view_get_type
GtkColumnView
gtk_column_view_new
gtk_column_view_append_column
gtk_column_view_insert_column
gtk_column_view_remove_column
gtk_column_view_get_columns
gtk_column_view_get_model
@@ -506,6 +512,11 @@ gtk_column_view_set_show_separators
gtk_column_view_sort_by_column
gtk_column_view_set_single_click_activate
gtk_column_view_get_single_click_activate
gtk_column_view_set_enable_rubberband
gtk_column_view_get_enable_rubberband
gtk_column_view_set_search_filter
gtk_column_view_get_search_filter
gtk_column_view_next_match
<SUBSECTION Standard>
GTK_COLUMN_VIEW
GTK_COLUMN_VIEW_CLASS
@@ -530,6 +541,10 @@ gtk_column_view_column_set_title
gtk_column_view_column_get_title
gtk_column_view_column_set_sorter
gtk_column_view_column_get_sorter
gtk_column_view_column_set_visible
gtk_column_view_column_get_visible
gtk_column_view_column_set_fixed_width
gtk_column_view_column_get_fixed_width
<SUBSECTION Standard>
GTK_COLUMN_VIEW_COLUMN
GTK_COLUMN_VIEW_COLUMN_CLASS
@@ -554,6 +569,11 @@ gtk_grid_view_set_min_columns
gtk_grid_view_get_min_columns
gtk_grid_view_set_single_click_activate
gtk_grid_view_get_single_click_activate
gtk_grid_view_set_enable_rubberband
gtk_grid_view_get_enable_rubberband
gtk_grid_view_set_search_filter
gtk_grid_view_get_search_filter
gtk_grid_view_next_match
<SUBSECTION Standard>
GTK_GRID_VIEW
GTK_GRID_VIEW_CLASS

View File

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

View File

@@ -34,6 +34,13 @@
#include "gtkprivate.h"
#include "gtkscrollable.h"
#include "gtkwidgetprivate.h"
#include "gtksizerequest.h"
#include "gtkadjustment.h"
#include "gtkgesturedrag.h"
#include "gtkeventcontrollermotion.h"
#include "gtkdragsource.h"
#include "gtkeventcontrollerkey.h"
#include "gtklistbaseprivate.h"
/**
* SECTION:gtkcolumnview
@@ -63,6 +70,21 @@ struct _GtkColumnView
GtkColumnListItemFactory *factory;
GtkSorter *sorter;
GtkAdjustment *hadjustment;
gboolean in_column_resize;
gboolean in_column_reorder;
int drag_pos;
int drag_x;
int drag_offset;
int drag_column_x;
GtkGesture *drag_gesture;
guint autoscroll_id;
double autoscroll_x;
double autoscroll_delta;
};
struct _GtkColumnViewClass
@@ -82,6 +104,8 @@ enum
PROP_VADJUSTMENT,
PROP_VSCROLL_POLICY,
PROP_SINGLE_CLICK_ACTIVATE,
PROP_ENABLE_RUBBERBAND,
PROP_SEARCH_FILTER,
N_PROPS
};
@@ -169,48 +193,77 @@ gtk_column_view_allocate_columns (GtkColumnView *self,
int width)
{
GtkScrollablePolicy scroll_policy;
int col_min, col_nat, widget_min, widget_nat, extra, col_size, x;
int col_min, col_nat, extra, col_size, x;
int n, n_expand, expand_size, n_extra;
guint i;
GtkRequestedSize *sizes;
gtk_column_view_measure_across (self, &col_min, &col_nat);
gtk_widget_measure (GTK_WIDGET (self),
GTK_ORIENTATION_HORIZONTAL, -1,
&widget_min, &widget_nat,
NULL, NULL);
scroll_policy = gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview));
if (scroll_policy == GTK_SCROLL_MINIMUM)
{
extra = widget_min - col_min;
col_size = col_min;
}
else
{
extra = widget_nat - col_nat;
col_size = col_nat;
}
width -= extra;
width = MAX (width, col_size);
x = 0;
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++)
n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
n_expand = 0;
sizes = g_newa (GtkRequestedSize, n);
for (i = 0; i < n; i++)
{
GtkColumnViewColumn *column;
column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
gtk_column_view_column_measure (column, &col_min, &col_nat);
if (scroll_policy == GTK_SCROLL_MINIMUM)
col_size = col_min;
if (gtk_column_view_column_get_visible (column))
{
gtk_column_view_column_measure (column, &sizes[i].minimum_size, &sizes[i].natural_size);
if (gtk_column_view_column_get_expand (column))
n_expand++;
}
else
col_size = col_nat;
sizes[i].minimum_size = sizes[i].natural_size = 0;
g_object_unref (column);
}
gtk_column_view_column_allocate (column, x, col_size);
x += col_size;
gtk_column_view_measure_across (self, &col_min, &col_nat);
scroll_policy = gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview));
if (scroll_policy == GTK_SCROLL_MINIMUM)
extra = MAX (width - col_min, 0);
else
extra = MAX (width - col_min, col_nat - col_min);
extra = gtk_distribute_natural_allocation (extra, n, sizes);
if (n_expand > 0)
{
expand_size = extra / n_expand;
n_extra = extra % n_expand;
}
else
expand_size = n_extra = 0;
x = 0;
for (i = 0; i < n; i++)
{
GtkColumnViewColumn *column;
column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
if (gtk_column_view_column_get_visible (column))
{
col_size = sizes[i].minimum_size;
if (gtk_column_view_column_get_expand (column))
{
col_size += expand_size;
if (n_extra > 0)
{
col_size++;
n_extra--;
}
}
gtk_column_view_column_allocate (column, x, col_size);
if (self->in_column_reorder && i == self->drag_pos)
gtk_column_view_column_set_header_position (column, self->drag_x);
x += col_size;
}
g_object_unref (column);
}
return width + extra;
return x;
}
static void
@@ -220,8 +273,9 @@ gtk_column_view_allocate (GtkWidget *widget,
int baseline)
{
GtkColumnView *self = GTK_COLUMN_VIEW (widget);
int full_width, header_height, min, nat;
int full_width, header_height, min, nat, x;
x = gtk_adjustment_get_value (self->hadjustment);
full_width = gtk_column_view_allocate_columns (self, width);
gtk_widget_measure (self->header, GTK_ORIENTATION_VERTICAL, full_width, &min, &nat, NULL, NULL);
@@ -229,11 +283,14 @@ gtk_column_view_allocate (GtkWidget *widget,
header_height = min;
else
header_height = nat;
gtk_widget_allocate (self->header, full_width, header_height, -1, NULL);
gtk_widget_allocate (self->header, full_width, header_height, -1,
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (-x, 0)));
gtk_widget_allocate (GTK_WIDGET (self->listview),
full_width, height - header_height, -1,
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (0, header_height)));
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (-x, header_height)));
gtk_adjustment_configure (self->hadjustment, x, 0, full_width, width * 0.1, width * 0.9, width);
}
static void
@@ -244,6 +301,23 @@ gtk_column_view_activate_cb (GtkListView *listview,
g_signal_emit (self, signals[ACTIVATE], 0, pos);
}
static void
adjustment_value_changed_cb (GtkAdjustment *adjustment,
GtkColumnView *self)
{
gtk_widget_queue_allocate (GTK_WIDGET (self));
}
static void
clear_adjustment (GtkColumnView *self)
{
if (self->hadjustment == NULL)
return;
g_signal_handlers_disconnect_by_func (self->hadjustment, adjustment_value_changed_cb, self);
g_clear_object (&self->hadjustment);
}
static void
gtk_column_view_dispose (GObject *object)
{
@@ -262,6 +336,7 @@ gtk_column_view_dispose (GObject *object)
g_clear_object (&self->factory);
g_clear_object (&self->sorter);
clear_adjustment (self);
G_OBJECT_CLASS (gtk_column_view_parent_class)->dispose (object);
}
@@ -291,7 +366,7 @@ gtk_column_view_get_property (GObject *object,
break;
case PROP_HADJUSTMENT:
g_value_set_object (value, gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (self->listview)));
g_value_set_object (value, self->hadjustment);
break;
case PROP_HSCROLL_POLICY:
@@ -322,6 +397,14 @@ gtk_column_view_get_property (GObject *object,
g_value_set_boolean (value, gtk_column_view_get_single_click_activate (self));
break;
case PROP_ENABLE_RUBBERBAND:
g_value_set_boolean (value, gtk_column_view_get_enable_rubberband (self));
break;
case PROP_SEARCH_FILTER:
g_value_set_object (value, gtk_column_view_get_search_filter (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -335,13 +418,24 @@ gtk_column_view_set_property (GObject *object,
GParamSpec *pspec)
{
GtkColumnView *self = GTK_COLUMN_VIEW (object);
GtkAdjustment *adjustment;
switch (property_id)
{
case PROP_HADJUSTMENT:
if (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (self->listview)) != g_value_get_object (value))
adjustment = g_value_get_object (value);
if (adjustment == NULL)
adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
g_object_ref_sink (adjustment);
if (self->hadjustment != adjustment)
{
gtk_scrollable_set_hadjustment (GTK_SCROLLABLE (self->listview), g_value_get_object (value));
clear_adjustment (self);
self->hadjustment = adjustment;
g_signal_connect (adjustment, "value-changed", G_CALLBACK (adjustment_value_changed_cb), self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HADJUSTMENT]);
}
break;
@@ -382,6 +476,14 @@ gtk_column_view_set_property (GObject *object,
gtk_column_view_set_single_click_activate (self, g_value_get_boolean (value));
break;
case PROP_ENABLE_RUBBERBAND:
gtk_column_view_set_enable_rubberband (self, g_value_get_boolean (value));
break;
case PROP_SEARCH_FILTER:
gtk_column_view_set_search_filter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -478,6 +580,30 @@ gtk_column_view_class_init (GtkColumnViewClass *klass)
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkColumnView:enable-rubberband:
*
* Allow rubberband selection
*/
properties[PROP_ENABLE_RUBBERBAND] =
g_param_spec_boolean ("enable-rubberband",
P_("Enable rubberband selection"),
P_("Allow selecting items by dragging with the mouse"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkColumnView:search-filter:
*
* Filter used for search
*/
properties[PROP_SEARCH_FILTER] =
g_param_spec_object ("search-filter",
P_("Search filter"),
P_("Filter used for searching"),
GTK_TYPE_FILTER,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/**
@@ -507,9 +633,366 @@ gtk_column_view_class_init (GtkColumnViewClass *klass)
gtk_widget_class_set_css_name (widget_class, I_("treeview"));
}
static void update_column_resize (GtkColumnView *self,
double x);
static void update_column_reorder (GtkColumnView *self,
double x);
static gboolean
autoscroll_cb (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer data)
{
GtkColumnView *self = data;
gtk_adjustment_set_value (self->hadjustment,
gtk_adjustment_get_value (self->hadjustment) + self->autoscroll_delta);
self->autoscroll_x += self->autoscroll_delta;
if (self->in_column_resize)
update_column_resize (self, self->autoscroll_x);
else if (self->in_column_reorder)
update_column_reorder (self, self->autoscroll_x);
return G_SOURCE_CONTINUE;
}
static void
add_autoscroll (GtkColumnView *self,
double x,
double delta)
{
self->autoscroll_x = x;
self->autoscroll_delta = delta;
if (self->autoscroll_id == 0)
self->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), autoscroll_cb, self, NULL);
}
static void
remove_autoscroll (GtkColumnView *self)
{
if (self->autoscroll_id != 0)
{
gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->autoscroll_id);
self->autoscroll_id = 0;
}
}
#define DRAG_WIDTH 6
static gboolean
gtk_column_view_in_resize_rect (GtkColumnView *self,
GtkColumnViewColumn *column,
double x,
double y)
{
GtkWidget *header;
graphene_rect_t rect;
header = gtk_column_view_column_get_header (column);
if (!gtk_widget_compute_bounds (header, self->header, &rect))
return FALSE;
rect.origin.x += rect.size.width - DRAG_WIDTH / 2;
rect.size.width = DRAG_WIDTH;
return graphene_rect_contains_point (&rect, &(graphene_point_t) { x, y});
}
static gboolean
gtk_column_view_in_header (GtkColumnView *self,
GtkColumnViewColumn *column,
double x,
double y)
{
GtkWidget *header;
graphene_rect_t rect;
header = gtk_column_view_column_get_header (column);
if (!gtk_widget_compute_bounds (header, self->header, &rect))
return FALSE;
return graphene_rect_contains_point (&rect, &(graphene_point_t) { x, y});
}
static void
header_drag_begin (GtkGestureDrag *gesture,
double start_x,
double start_y,
GtkColumnView *self)
{
int i, n;
self->drag_pos = -1;
n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
for (i = 0; !self->in_column_resize && i < n; i++)
{
GtkColumnViewColumn *column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
if (!gtk_column_view_column_get_visible (column))
{
g_object_unref (column);
continue;
}
if (i + 1 < n &&
gtk_column_view_column_get_resizable (column) &&
gtk_column_view_in_resize_rect (self, column, start_x, start_y))
{
int size;
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
if (!gtk_widget_has_focus (GTK_WIDGET (self)))
gtk_widget_grab_focus (GTK_WIDGET (self));
gtk_column_view_column_get_allocation (column, NULL, &size);
gtk_column_view_column_set_fixed_width (column, size);
self->drag_pos = i;
self->drag_x = start_x - size;
self->in_column_resize = TRUE;
g_object_unref (column);
break;
}
if (gtk_column_view_column_get_reorderable (column) &&
gtk_column_view_in_header (self, column, start_x, start_y))
{
int pos;
gtk_column_view_column_get_allocation (column, &pos, NULL);
self->drag_pos = i;
self->drag_offset = start_x - pos;
g_object_unref (column);
break;
}
g_object_unref (column);
}
}
static void
header_drag_end (GtkGestureDrag *gesture,
double offset_x,
double offset_y,
GtkColumnView *self)
{
double start_x, x;
gtk_gesture_drag_get_start_point (gesture, &start_x, NULL);
x = start_x + offset_x;
remove_autoscroll (self);
if (self->in_column_resize)
{
self->in_column_resize = FALSE;
}
else if (self->in_column_reorder)
{
GdkEventSequence *sequence;
GtkColumnViewColumn *column;
GtkWidget *header;
int i;
self->in_column_reorder = FALSE;
if (self->drag_pos == -1)
return;
column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
header = gtk_column_view_column_get_header (column);
gtk_style_context_remove_class (gtk_widget_get_style_context (header), "dnd");
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
return;
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++)
{
GtkColumnViewColumn *col = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
if (gtk_column_view_column_get_visible (col))
{
int pos, size;
gtk_column_view_column_get_allocation (col, &pos, &size);
if (pos <= x && x <= pos + size)
{
gtk_column_view_insert_column (self, i, column);
g_object_unref (col);
break;
}
}
g_object_unref (col);
}
g_object_unref (column);
}
}
static void
update_column_resize (GtkColumnView *self,
double x)
{
GtkColumnViewColumn *column;
column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
gtk_column_view_column_set_fixed_width (column, MAX (x - self->drag_x, 0));
g_object_unref (column);
}
static void
update_column_reorder (GtkColumnView *self,
double x)
{
GtkColumnViewColumn *column;
int width;
int size;
column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
width = gtk_widget_get_allocated_width (GTK_WIDGET (self->header));
gtk_column_view_column_get_allocation (column, NULL, &size);
self->drag_x = CLAMP (x - self->drag_offset, 0, width - size);
gtk_widget_queue_allocate (GTK_WIDGET (self));
gtk_column_view_column_queue_resize (column);
g_object_unref (column);
}
#define SCROLL_EDGE_SIZE 15
static void
header_drag_update (GtkGestureDrag *gesture,
double offset_x,
double offset_y,
GtkColumnView *self)
{
GdkEventSequence *sequence;
double start_x, x;
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
return;
if (self->drag_pos == -1)
return;
if (!self->in_column_resize && !self->in_column_reorder)
{
if (gtk_drag_check_threshold (GTK_WIDGET (self), 0, 0, offset_x, 0))
{
GtkColumnViewColumn *column;
GtkWidget *header;
column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
header = gtk_column_view_column_get_header (column);
gtk_widget_insert_after (header, self->header, gtk_widget_get_last_child (self->header));
gtk_style_context_add_class (gtk_widget_get_style_context (header), "dnd");
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
if (!gtk_widget_has_focus (GTK_WIDGET (self)))
gtk_widget_grab_focus (GTK_WIDGET (self));
self->in_column_reorder = TRUE;
g_object_unref (column);
}
}
gtk_gesture_drag_get_start_point (gesture, &start_x, NULL);
x = start_x + offset_x;
if (self->in_column_resize)
update_column_resize (self, x);
else if (self->in_column_reorder)
update_column_reorder (self, x);
if (self->in_column_resize || self->in_column_reorder)
{
double value, page_size, upper;
value = gtk_adjustment_get_value (self->hadjustment);
page_size = gtk_adjustment_get_page_size (self->hadjustment);
upper = gtk_adjustment_get_upper (self->hadjustment);
if (x - value < SCROLL_EDGE_SIZE && value > 0)
add_autoscroll (self, x, - (SCROLL_EDGE_SIZE - (x - value))/3.0);
else if (value + page_size - x < SCROLL_EDGE_SIZE && value + page_size < upper)
add_autoscroll (self, x, (SCROLL_EDGE_SIZE - (value + page_size - x))/3.0);
else
remove_autoscroll (self);
}
}
static void
header_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkColumnView *self)
{
gboolean cursor_set = FALSE;
int i, n;
n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
for (i = 0; i < n; i++)
{
GtkColumnViewColumn *column = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
if (!gtk_column_view_column_get_visible (column))
{
g_object_unref (column);
continue;
}
if (i + 1 < n &&
gtk_column_view_column_get_resizable (column) &&
gtk_column_view_in_resize_rect (self, column, x, y))
{
gtk_widget_set_cursor_from_name (self->header, "col-resize");
cursor_set = TRUE;
}
g_object_unref (column);
}
if (!cursor_set)
gtk_widget_set_cursor (self->header, NULL);
}
static gboolean
header_key_pressed (GtkEventControllerKey *controller,
guint keyval,
guint keycode,
GdkModifierType modifiers,
GtkColumnView *self)
{
if (self->in_column_reorder)
{
if (keyval == GDK_KEY_Escape)
gtk_gesture_set_state (self->drag_gesture, GTK_EVENT_SEQUENCE_DENIED);
return TRUE;
}
return FALSE;
}
static void
gtk_column_view_init (GtkColumnView *self)
{
GtkEventController *controller;
self->columns = g_list_store_new (GTK_TYPE_COLUMN_VIEW_COLUMN);
self->header = gtk_list_item_widget_new (NULL, "header");
@@ -517,6 +1000,22 @@ gtk_column_view_init (GtkColumnView *self)
gtk_widget_set_layout_manager (self->header, gtk_column_view_layout_new (self));
gtk_widget_set_parent (self->header, GTK_WIDGET (self));
controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
g_signal_connect (controller, "drag-begin", G_CALLBACK (header_drag_begin), self);
g_signal_connect (controller, "drag-update", G_CALLBACK (header_drag_update), self);
g_signal_connect (controller, "drag-end", G_CALLBACK (header_drag_end), self);
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
gtk_widget_add_controller (self->header, controller);
self->drag_gesture = GTK_GESTURE (controller);
controller = gtk_event_controller_motion_new ();
g_signal_connect (controller, "motion", G_CALLBACK (header_motion), self);
gtk_widget_add_controller (self->header, controller);
controller = gtk_event_controller_key_new ();
g_signal_connect (controller, "key-pressed", G_CALLBACK (header_key_pressed), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
self->sorter = gtk_column_view_sorter_new ();
self->factory = gtk_column_list_item_factory_new (self);
self->listview = GTK_LIST_VIEW (gtk_list_view_new_with_factory (
@@ -691,6 +1190,7 @@ gtk_column_view_remove_column (GtkColumnView *self,
g_object_unref (item);
if (item == column)
break;
}
gtk_column_view_sorter_remove_column (GTK_COLUMN_VIEW_SORTER (self->sorter), column);
@@ -698,6 +1198,45 @@ gtk_column_view_remove_column (GtkColumnView *self,
g_list_store_remove (self->columns, i);
}
void
gtk_column_view_insert_column (GtkColumnView *self,
guint position,
GtkColumnViewColumn *column)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column));
g_return_if_fail (gtk_column_view_column_get_column_view (column) == NULL ||
gtk_column_view_column_get_column_view (column) == self);
g_return_if_fail (position <= g_list_model_get_n_items (G_LIST_MODEL (self->columns)));
g_object_ref (column);
if (gtk_column_view_column_get_column_view (column) == self)
{
guint i;
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++)
{
GtkColumnViewColumn *item = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
g_object_unref (item);
if (item == column)
{
g_list_store_remove (self->columns, i);
break;
}
}
}
else
gtk_column_view_column_set_column_view (column, self);
g_list_store_insert (self->columns, position, column);
gtk_column_view_column_queue_resize (column);
g_object_unref (column);
}
void
gtk_column_view_measure_across (GtkColumnView *self,
int *minimum,
@@ -732,6 +1271,12 @@ gtk_column_view_get_header_widget (GtkColumnView *self)
return GTK_LIST_ITEM_WIDGET (self->header);
}
GtkListView *
gtk_column_view_get_list_view (GtkColumnView *self)
{
return GTK_LIST_VIEW (self->listview);
}
/**
* gtk_column_view_get_sorter:
* @self: a #GtkColumnView
@@ -829,3 +1374,102 @@ gtk_column_view_get_single_click_activate (GtkColumnView *self)
return gtk_list_view_get_single_click_activate (self->listview);
}
/**
* gtk_column_view_set_enable_rubberband:
* @self: a #GtkColumnView
* @enable_rubberband: %TRUE to enable rubberband selection
*
* Sets whether selections can be changed by dragging with the mouse.
*/
void
gtk_column_view_set_enable_rubberband (GtkColumnView *self,
gboolean enable_rubberband)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
if (enable_rubberband == gtk_list_view_get_enable_rubberband (self->listview))
return;
gtk_list_view_set_enable_rubberband (self->listview, enable_rubberband);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLE_RUBBERBAND]);
}
/**
* gtk_column_view_get_enable_rubberband:
* @self: a #GtkColumnView
*
* Returns whether rows can be selected by dragging with the mouse.
*
* Returns: %TRUE if rubberband selection is enabled
*/
gboolean
gtk_column_view_get_enable_rubberband (GtkColumnView *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
return gtk_list_view_get_enable_rubberband (self->listview);
}
/**
* gtk_column_view_set_search_filter:
* @self: a #GtkColumnView
* @filter: (nullable): the filter to use for search, or %NULL
*
* Sets a search filter.
*
* The selection will be moved to first item matching the
* filter whenever the filter changes.
*
* This can be used with single selection and a string
* filter that is connected to a search entry.
*/
void
gtk_column_view_set_search_filter (GtkColumnView *self,
GtkFilter *filter)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
g_return_if_fail (filter == NULL || GTK_IS_FILTER (filter));
if (filter == gtk_list_view_get_search_filter (GTK_LIST_VIEW (self->listview)))
return;
gtk_list_view_set_search_filter (GTK_LIST_VIEW (self->listview), filter);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_FILTER]);
}
/**
* gtk_column_view_get_search_filter:
* @self: a #GtkColumnView
*
* Gets the search filter that was set with
* gtk_column_view_set_search_filter().
*
* Returns: (transfer none): The search filter of @self
*/
GtkFilter *
gtk_column_view_get_search_filter (GtkColumnView *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL);
return gtk_list_view_get_search_filter (GTK_LIST_VIEW (self->listview));
}
/**
* gtk_column_view_select_next_match:
* @self: a #GtkColumnView
* @forward: whether to move forward or back
*
* Moves the selection to the next item matching the
* search filter set with gtk_column_view_set_search_filter().
*/
void
gtk_column_view_select_next_match (GtkColumnView *self,
gboolean forward)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
gtk_list_view_select_next_match (GTK_LIST_VIEW (self->listview), forward);
}

View File

@@ -27,6 +27,7 @@
#include <gtk/gtktypes.h>
#include <gtk/gtksortlistmodel.h>
#include <gtk/gtksorter.h>
#include <gtk/gtkfilter.h>
G_BEGIN_DECLS
@@ -61,6 +62,10 @@ void gtk_column_view_append_column (GtkColumnView
GDK_AVAILABLE_IN_ALL
void gtk_column_view_remove_column (GtkColumnView *self,
GtkColumnViewColumn *column);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_insert_column (GtkColumnView *self,
guint position,
GtkColumnViewColumn *column);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_column_view_get_model (GtkColumnView *self);
@@ -87,6 +92,22 @@ void gtk_column_view_set_single_click_activate (GtkColumnView
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_get_single_click_activate (GtkColumnView *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_set_enable_rubberband (GtkColumnView *self,
gboolean enable_rubberband);
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_get_enable_rubberband (GtkColumnView *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_set_search_filter (GtkColumnView *self,
GtkFilter *filter);
GDK_AVAILABLE_IN_ALL
GtkFilter * gtk_column_view_get_search_filter (GtkColumnView *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_select_next_match (GtkColumnView *self,
gboolean forward);
G_END_DECLS
#endif /* __GTK_COLUMN_VIEW_H__ */

View File

@@ -53,10 +53,18 @@ gtk_column_view_cell_measure (GtkWidget *widget,
int *minimum_baseline,
int *natural_baseline)
{
GtkColumnViewCell *cell = GTK_COLUMN_VIEW_CELL (widget);
GtkWidget *child = gtk_widget_get_first_child (widget);
if (child)
gtk_widget_measure (child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
int fixed_width = gtk_column_view_column_get_fixed_width (cell->column);
if (fixed_width > -1)
*minimum = *natural = fixed_width;
}
}
static void
@@ -156,6 +164,7 @@ gtk_column_view_cell_new (GtkColumnViewColumn *column)
cell = g_object_new (GTK_TYPE_COLUMN_VIEW_CELL,
"factory", gtk_column_view_column_get_factory (column),
"visible", gtk_column_view_column_get_visible (column),
NULL);
cell->column = g_object_ref (column);

View File

@@ -60,6 +60,16 @@ struct _GtkColumnViewColumn
int natural_size_request;
int allocation_offset;
int allocation_size;
int header_position;
int fixed_width;
guint visible : 1;
guint resizable : 1;
guint expand : 1;
guint reorderable : 1;
GMenuModel *menu;
/* This list isn't sorted - this is just caching for performance */
GtkColumnViewCell *first_cell; /* no reference, just caching */
@@ -77,6 +87,12 @@ enum
PROP_FACTORY,
PROP_TITLE,
PROP_SORTER,
PROP_VISIBLE,
PROP_RESIZABLE,
PROP_EXPAND,
PROP_REORDERABLE,
PROP_FIXED_WIDTH,
PROP_HEADER_MENU,
N_PROPS
};
@@ -96,6 +112,7 @@ gtk_column_view_column_dispose (GObject *object)
g_clear_object (&self->factory);
g_clear_object (&self->sorter);
g_clear_pointer (&self->title, g_free);
g_clear_object (&self->menu);
G_OBJECT_CLASS (gtk_column_view_column_parent_class)->dispose (object);
}
@@ -126,6 +143,30 @@ gtk_column_view_column_get_property (GObject *object,
g_value_set_object (value, self->sorter);
break;
case PROP_VISIBLE:
g_value_set_boolean (value, self->visible);
break;
case PROP_RESIZABLE:
g_value_set_boolean (value, self->resizable);
break;
case PROP_EXPAND:
g_value_set_boolean (value, self->expand);
break;
case PROP_REORDERABLE:
g_value_set_boolean (value, self->reorderable);
break;
case PROP_FIXED_WIDTH:
g_value_set_int (value, self->fixed_width);
break;
case PROP_HEADER_MENU:
g_value_set_object (value, self->menu);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -154,6 +195,30 @@ gtk_column_view_column_set_property (GObject *object,
gtk_column_view_column_set_sorter (self, g_value_get_object (value));
break;
case PROP_VISIBLE:
gtk_column_view_column_set_visible (self, g_value_get_boolean (value));
break;
case PROP_RESIZABLE:
gtk_column_view_column_set_resizable (self, g_value_get_boolean (value));
break;
case PROP_EXPAND:
gtk_column_view_column_set_expand (self, g_value_get_boolean (value));
break;
case PROP_REORDERABLE:
gtk_column_view_column_set_reorderable (self, g_value_get_boolean (value));
break;
case PROP_FIXED_WIDTH:
gtk_column_view_column_set_fixed_width (self, g_value_get_int (value));
break;
case PROP_HEADER_MENU:
gtk_column_view_column_set_header_menu (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -217,6 +282,79 @@ gtk_column_view_column_class_init (GtkColumnViewColumnClass *klass)
GTK_TYPE_SORTER,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkColumnViewColumn:visible:
*
* Whether this column is visible
*/
properties[PROP_VISIBLE] =
g_param_spec_boolean ("visible",
P_("Visible"),
P_("Whether this column is visible"),
TRUE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkColumnViewColumn:resizable:
*
* Whether this column is resizable
*/
properties[PROP_RESIZABLE] =
g_param_spec_boolean ("resizable",
P_("Resizable"),
P_("Whether this column is resizable"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkColumnViewColumn:expand:
*
* Column gets share of extra width allocated to the view
*/
properties[PROP_EXPAND] =
g_param_spec_boolean ("expand",
P_("Expand"),
P_("column gets share of extra width"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkColumnViewColumn:reorderable:
*
* Whether this column is reorderable
*/
properties[PROP_REORDERABLE] =
g_param_spec_boolean ("reorderable",
P_("Reorderable"),
P_("Whether this column is reorderable"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkColumnViewColumn:fixed-width:
*
* If not -1, this is the width that the column is allocated,
* regardless of the size of its content.
*/
properties[PROP_FIXED_WIDTH] =
g_param_spec_int ("fixed-width",
P_("Fixed width"),
P_("Fixed width of this column"),
-1, G_MAXINT, -1,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkColumnViewColumn:header-menu:
*
* Menu model used to create the context menu for the column header.
*/
properties[PROP_HEADER_MENU] =
g_param_spec_object ("header-menu",
P_("Header menu"),
P_("Menu to use on the title of this column"),
G_TYPE_MENU_MODEL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
@@ -225,6 +363,11 @@ gtk_column_view_column_init (GtkColumnViewColumn *self)
{
self->minimum_size_request = -1;
self->natural_size_request = -1;
self->visible = TRUE;
self->resizable = FALSE;
self->expand = FALSE;
self->reorderable = FALSE;
self->fixed_width = -1;
}
/**
@@ -333,6 +476,12 @@ gtk_column_view_column_measure (GtkColumnViewColumn *self,
int *minimum,
int *natural)
{
if (self->fixed_width > -1)
{
self->minimum_size_request = self->fixed_width;
self->natural_size_request = self->fixed_width;
}
if (self->minimum_size_request < 0)
{
GtkColumnViewCell *cell;
@@ -375,6 +524,7 @@ gtk_column_view_column_allocate (GtkColumnViewColumn *self,
{
self->allocation_offset = offset;
self->allocation_size = size;
self->header_position = offset;
}
void
@@ -647,3 +797,298 @@ gtk_column_view_column_notify_sort (GtkColumnViewColumn *self)
if (self->header)
gtk_column_view_title_update (GTK_COLUMN_VIEW_TITLE (self->header));
}
/**
* gtk_column_view_column_set_visible:
* @self: a #GtkColumnViewColumn
* @visible: whether this column should be visible
*
* Sets whether this column should be visible in views.
*/
void
gtk_column_view_column_set_visible (GtkColumnViewColumn *self,
gboolean visible)
{
GtkColumnViewCell *cell;
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
if (self->visible == visible)
return;
self->visible = visible;
self->minimum_size_request = -1;
self->natural_size_request = -1;
if (self->header)
gtk_widget_set_visible (GTK_WIDGET (self->header), visible);
for (cell = self->first_cell; cell; cell = gtk_column_view_cell_get_next (cell))
{
gtk_widget_set_visible (GTK_WIDGET (cell), visible);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VISIBLE]);
}
/**
* gtk_column_view_get_visible:
* @self: a #GtkColumnViewColumn
*
* Returns whether this column is visible.
*
* Returns: %TRUE if this column is visible
*/
gboolean
gtk_column_view_column_get_visible (GtkColumnViewColumn *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), TRUE);
return self->visible;
}
/**
* gtk_column_view_column_set_resizable:
* @self: a #GtkColumnViewColumn
* @resizable: whether this column should be resizable
*
* Sets whether this column should be resizable by dragging.
*/
void
gtk_column_view_column_set_resizable (GtkColumnViewColumn *self,
gboolean resizable)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
if (self->resizable == resizable)
return;
self->resizable = resizable;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_RESIZABLE]);
}
/**
* gtk_column_view_get_resizable:
* @self: a #GtkColumnView
*
* Returns whether this column is resizable.
*
* Returns: %TRUE if this column is resizable
*/
gboolean
gtk_column_view_column_get_resizable (GtkColumnViewColumn *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), TRUE);
return self->resizable;
}
/**
* gtk_column_view_column_set_expand:
* @self: a #GtkColumnViewColumn
* @expand: %TRUE if this column should expand to fill available sace
*
* Sets the column to take available extra space.
*
* The extra space is shared equally amongst all columns that
* have the expand set to %TRUE.
*/
void
gtk_column_view_column_set_expand (GtkColumnViewColumn *self,
gboolean expand)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
if (self->expand == expand)
return;
self->expand = expand;
if (self->visible && self->view)
gtk_widget_queue_resize (GTK_WIDGET (self->view));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPAND]);
}
/**
* gtk_column_view_get_expand:
* @self: a #GtkColumnViewColumn
*
* Returns whether this column should expand.
*
* Returns: %TRUE if this column expands
*/
gboolean
gtk_column_view_column_get_expand (GtkColumnViewColumn *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), TRUE);
return self->expand;
}
/**
* gtk_column_view_column_set_reorderable:
* @self: a #GtkColumnViewColumn
* @reorderable: whether this column should be reorderable
*
* Sets whether this column should be reorderable by dragging.
*/
void
gtk_column_view_column_set_reorderable (GtkColumnViewColumn *self,
gboolean reorderable)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
if (self->reorderable == reorderable)
return;
self->reorderable = reorderable;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REORDERABLE]);
}
/**
* gtk_column_view_get_reorderable:
* @self: a #GtkColumnView
*
* Returns whether this column is reorderable.
*
* Returns: %TRUE if this column is reorderable
*/
gboolean
gtk_column_view_column_get_reorderable (GtkColumnViewColumn *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), TRUE);
return self->reorderable;
}
/**
* gtk_column_view_column_set_fixed_width:
* @self: a #GtkColumnViewColumn
* @fixed_width: the new fixed width, or -1
*
* If @fixed_width is not -1, sets the fixed width of @column;
* otherwise unsets it.
*
* Setting a fixed width overrides the automatically calculated
* width. Interactive resizing also sets the “fixed-width” property.
*/
void
gtk_column_view_column_set_fixed_width (GtkColumnViewColumn *self,
int fixed_width)
{
GtkOverflow overflow;
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
g_return_if_fail (fixed_width >= -1);
if (self->fixed_width == fixed_width)
return;
self->fixed_width = fixed_width;
if (fixed_width > -1)
overflow = GTK_OVERFLOW_HIDDEN;
else
overflow = GTK_OVERFLOW_VISIBLE;
if (overflow != gtk_widget_get_overflow (GTK_WIDGET (self->header)))
{
GtkColumnViewCell *cell;
if (self->header)
gtk_widget_set_overflow (GTK_WIDGET (self->header), overflow);
for (cell = self->first_cell; cell; cell = gtk_column_view_cell_get_next (cell))
gtk_widget_set_overflow (GTK_WIDGET (cell), overflow);
}
gtk_column_view_column_queue_resize (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FIXED_WIDTH]);
}
/**
* gtk_column_view_column_get_fixed_width:
* @self: a #GtkColumnViewColumn
*
* Gets the fixed width of the column.
*
* Returns: the fixed with of the column
*/
int
gtk_column_view_column_get_fixed_width (GtkColumnViewColumn *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), -1);
return self->fixed_width;
}
GtkWidget *
gtk_column_view_column_get_header (GtkColumnViewColumn *self)
{
return self->header;
}
void
gtk_column_view_column_set_header_position (GtkColumnViewColumn *self,
int offset)
{
self->header_position = offset;
}
void
gtk_column_view_column_get_header_allocation (GtkColumnViewColumn *self,
int *offset,
int *size)
{
if (offset)
*offset = self->header_position;
if (size)
*size = self->allocation_size;
}
/**
* gtk_column_view_column_set_header_menu:
* @self: a #GtkColumnViewColumn
* @menu: (allow-none): a #GMenuModel, or %NULL
*
* Sets the menu model that is used to create the context menu
* for the column header.
*/
void
gtk_column_view_column_set_header_menu (GtkColumnViewColumn *self,
GMenuModel *menu)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self));
g_return_if_fail (menu == NULL || G_IS_MENU_MODEL (menu));
if (!g_set_object (&self->menu, menu))
return;
if (self->header)
gtk_column_view_title_update (GTK_COLUMN_VIEW_TITLE (self->header));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEADER_MENU]);
}
/**
* gtk_column_view_column_get_header_menu:
* @self: a #GtkColumnViewColumn
*
* Gets the menu model that is used to create the context menu
* for the column header.
*
* Returns: the #GMenuModel, or %NULL
*/
GMenuModel *
gtk_column_view_column_get_header_menu (GtkColumnViewColumn *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (self), NULL);
return self->menu;
}

View File

@@ -72,6 +72,41 @@ void gtk_column_view_column_set_sorter (GtkColu
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_column_view_column_get_sorter (GtkColumnViewColumn *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_column_set_visible (GtkColumnViewColumn *self,
gboolean visible);
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_column_get_visible (GtkColumnViewColumn *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_column_set_fixed_width (GtkColumnViewColumn *self,
int fixed_width);
GDK_AVAILABLE_IN_ALL
int gtk_column_view_column_get_fixed_width (GtkColumnViewColumn *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_column_set_resizable (GtkColumnViewColumn *self,
gboolean resizable);
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_column_get_resizable (GtkColumnViewColumn *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_column_set_expand (GtkColumnViewColumn *self,
gboolean expand);
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_column_get_expand (GtkColumnViewColumn *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_column_set_reorderable (GtkColumnViewColumn *self,
gboolean reorderable);
GDK_AVAILABLE_IN_ALL
gboolean gtk_column_view_column_get_reorderable (GtkColumnViewColumn *self);
GDK_AVAILABLE_IN_ALL
void gtk_column_view_column_set_header_menu (GtkColumnViewColumn *self,
GMenuModel *menu);
GDK_AVAILABLE_IN_ALL
GMenuModel * gtk_column_view_column_get_header_menu (GtkColumnViewColumn *self);
G_END_DECLS
#endif /* __GTK_COLUMN_VIEW_COLUMN_H__ */

View File

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

View File

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

View File

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

View File

@@ -30,6 +30,8 @@
#include "gtkbox.h"
#include "gtkimage.h"
#include "gtkgestureclick.h"
#include "gtkpopovermenu.h"
#include "gtknative.h"
struct _GtkColumnViewTitle
{
@@ -40,6 +42,7 @@ struct _GtkColumnViewTitle
GtkWidget *box;
GtkWidget *title;
GtkWidget *sort;
GtkWidget *popup_menu;
};
struct _GtkColumnViewTitleClass
@@ -58,10 +61,18 @@ gtk_column_view_title_measure (GtkWidget *widget,
int *minimum_baseline,
int *natural_baseline)
{
GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget);
GtkWidget *child = gtk_widget_get_first_child (widget);
if (child)
gtk_widget_measure (child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
int fixed_width = gtk_column_view_column_get_fixed_width (self->column);
if (fixed_width > -1)
*minimum = *natural = fixed_width;
}
}
static void
@@ -70,10 +81,14 @@ gtk_column_view_title_size_allocate (GtkWidget *widget,
int height,
int baseline)
{
GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget);
GtkWidget *child = gtk_widget_get_first_child (widget);
if (child)
gtk_widget_allocate (child, width, height, baseline, NULL);
if (self->popup_menu)
gtk_native_check_resize (GTK_NATIVE (self->popup_menu));
}
static void
@@ -82,6 +97,7 @@ gtk_column_view_title_dispose (GObject *object)
GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (object);
g_clear_pointer (&self->box, gtk_widget_unparent);
g_clear_pointer (&self->popup_menu, gtk_widget_unparent);
g_clear_object (&self->column);
@@ -112,13 +128,8 @@ gtk_column_view_title_resize_func (GtkWidget *widget)
}
static void
click_pressed_cb (GtkGestureClick *gesture,
guint n_press,
gdouble x,
gdouble y,
GtkWidget *widget)
activate_sort (GtkColumnViewTitle *self)
{
GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget);
GtkSorter *sorter;
GtkColumnView *view;
GtkColumnViewSorter *view_sorter;
@@ -132,6 +143,56 @@ click_pressed_cb (GtkGestureClick *gesture,
gtk_column_view_sorter_add_column (view_sorter, self->column);
}
static void
show_menu (GtkColumnViewTitle *self,
double x,
double y)
{
if (!self->popup_menu)
{
GMenuModel *model;
model = gtk_column_view_column_get_header_menu (self->column);
if (!model)
return;
self->popup_menu = gtk_popover_menu_new_from_model (model);
gtk_widget_set_parent (self->popup_menu, GTK_WIDGET (self));
gtk_popover_set_position (GTK_POPOVER (self->popup_menu), GTK_POS_BOTTOM);
gtk_popover_set_has_arrow (GTK_POPOVER (self->popup_menu), FALSE);
gtk_widget_set_halign (self->popup_menu, GTK_ALIGN_START);
}
if (x != -1 && y != -1)
{
GdkRectangle rect = { x, y, 1, 1 };
gtk_popover_set_pointing_to (GTK_POPOVER (self->popup_menu), &rect);
}
else
gtk_popover_set_pointing_to (GTK_POPOVER (self->popup_menu), NULL);
gtk_popover_popup (GTK_POPOVER (self->popup_menu));
}
static void
click_released_cb (GtkGestureClick *gesture,
guint n_press,
double x,
double y,
GtkWidget *widget)
{
GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget);
guint button;
button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
if (button == GDK_BUTTON_PRIMARY)
activate_sort (self);
else if (button == GDK_BUTTON_SECONDARY)
show_menu (self, x, y);
}
static void
gtk_column_view_title_init (GtkColumnViewTitle *self)
{
@@ -150,7 +211,8 @@ gtk_column_view_title_init (GtkColumnViewTitle *self)
gtk_box_append (GTK_BOX (self->box), self->sort);
gesture = gtk_gesture_click_new ();
g_signal_connect (gesture, "pressed", G_CALLBACK (click_pressed_cb), self);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 0);
g_signal_connect (gesture, "released", G_CALLBACK (click_released_cb), self);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
}
@@ -199,6 +261,8 @@ gtk_column_view_title_update (GtkColumnViewTitle *self)
}
else
gtk_widget_hide (self->sort);
g_clear_pointer (&self->popup_menu, gtk_widget_unparent);
}
GtkColumnViewColumn *

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

371
gtk/gtkmultiselection.c Normal file
View File

@@ -0,0 +1,371 @@
/*
* 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);
}
GtkMultiSelection *
gtk_multi_selection_copy (GtkSelectionModel *selection)
{
GtkMultiSelection *copy;
GListModel *model;
g_object_get (selection, "model", &model, NULL);
copy = GTK_MULTI_SELECTION (gtk_multi_selection_new (model));
if (GTK_IS_MULTI_SELECTION (selection))
{
GtkMultiSelection *multi = GTK_MULTI_SELECTION (selection);
gtk_set_free (copy->selected);
copy->selected = gtk_set_copy (multi->selected);
copy->last_selected = multi->last_selected;
}
else
{
guint pos, n;
guint start, n_items;
gboolean selected;
n = g_list_model_get_n_items (model);
n_items = 0;
for (pos = 0; pos < n; pos += n_items)
{
gtk_selection_model_query_range (selection, pos, &start, &n_items, &selected);
if (selected)
gtk_selection_model_select_range (GTK_SELECTION_MODEL (copy), start, n_items, FALSE);
}
}
g_object_unref (model);
return copy;
}

41
gtk/gtkmultiselection.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* Copyright © 2019 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __GTK_MULTI_SELECTION_H__
#define __GTK_MULTI_SELECTION_H__
#include <gtk/gtktypes.h>
#include <gtk/gtkselectionmodel.h>
G_BEGIN_DECLS
#define GTK_TYPE_MULTI_SELECTION (gtk_multi_selection_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkMultiSelection, gtk_multi_selection, GTK, MULTI_SELECTION, GObject)
GDK_AVAILABLE_IN_ALL
GListModel * gtk_multi_selection_new (GListModel *model);
GDK_AVAILABLE_IN_ALL
GtkMultiSelection * gtk_multi_selection_copy (GtkSelectionModel *model);
G_END_DECLS
#endif /* __GTK_MULTI_SELECTION_H__ */

View File

@@ -41,7 +41,7 @@ static void gtk_print_backend_get_property (GObject *object,
struct _GtkPrintBackendPrivate
{
GHashTable *printers;
GListStore *printers;
guint printer_list_requested : 1;
guint printer_list_done : 1;
GtkPrintBackendStatus status;
@@ -331,9 +331,7 @@ gtk_print_backend_init (GtkPrintBackend *backend)
priv = backend->priv = gtk_print_backend_get_instance_private (backend);
priv->printers = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_object_unref);
priv->printers = g_list_store_new (GTK_TYPE_PRINTER);
priv->auth_info_required = NULL;
priv->auth_info = NULL;
}
@@ -350,11 +348,7 @@ gtk_print_backend_dispose (GObject *object)
/* We unref the printers in dispose, not in finalize so that
* we can break refcount cycles with gtk_print_backend_destroy
*/
if (priv->printers)
{
g_hash_table_destroy (priv->printers);
priv->printers = NULL;
}
g_clear_object (&priv->printers);
backend_parent_class->dispose (object);
}
@@ -411,58 +405,25 @@ fallback_printer_get_capabilities (GtkPrinter *printer)
return 0;
}
static void
printer_hash_to_sorted_active_list (const gchar *key,
gpointer value,
GList **out_list)
{
GtkPrinter *printer;
printer = GTK_PRINTER (value);
if (gtk_printer_get_name (printer) == NULL)
return;
if (!gtk_printer_is_active (printer))
return;
*out_list = g_list_insert_sorted (*out_list, value, (GCompareFunc) gtk_printer_compare);
}
void
gtk_print_backend_add_printer (GtkPrintBackend *backend,
GtkPrinter *printer)
{
GtkPrintBackendPrivate *priv;
g_return_if_fail (GTK_IS_PRINT_BACKEND (backend));
priv = backend->priv;
if (!priv->printers)
return;
g_hash_table_insert (priv->printers,
g_strdup (gtk_printer_get_name (printer)),
g_object_ref (printer));
g_list_store_append (backend->priv->printers, printer);
}
void
gtk_print_backend_remove_printer (GtkPrintBackend *backend,
GtkPrinter *printer)
{
GtkPrintBackendPrivate *priv;
guint position;
g_return_if_fail (GTK_IS_PRINT_BACKEND (backend));
priv = backend->priv;
if (!priv->printers)
return;
g_hash_table_remove (priv->printers,
gtk_printer_get_name (printer));
if (g_list_store_find (backend->priv->printers, printer, &position))
g_list_store_remove (backend->priv->printers, position);
}
void
@@ -488,54 +449,67 @@ gtk_print_backend_set_list_done (GtkPrintBackend *backend)
GList *
gtk_print_backend_get_printer_list (GtkPrintBackend *backend)
{
GtkPrintBackendPrivate *priv;
GList *result;
GList *result = NULL;
guint i;
g_return_val_if_fail (GTK_IS_PRINT_BACKEND (backend), NULL);
priv = backend->priv;
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (backend->priv->printers)); i++)
{
GtkPrinter *printer = g_list_model_get_item (G_LIST_MODEL (backend->priv->printers), i);
result = g_list_prepend (result, printer);
g_object_unref (printer);
}
result = NULL;
if (priv->printers != NULL)
g_hash_table_foreach (priv->printers,
(GHFunc) printer_hash_to_sorted_active_list,
&result);
if (!priv->printer_list_requested && priv->printers != NULL)
if (!backend->priv->printer_list_requested)
{
if (GTK_PRINT_BACKEND_GET_CLASS (backend)->request_printer_list)
GTK_PRINT_BACKEND_GET_CLASS (backend)->request_printer_list (backend);
priv->printer_list_requested = TRUE;
backend->priv->printer_list_requested = TRUE;
}
return result;
}
gboolean
gtk_print_backend_printer_list_is_done (GtkPrintBackend *print_backend)
GListModel *
gtk_print_backend_get_printers (GtkPrintBackend *backend)
{
g_return_val_if_fail (GTK_IS_PRINT_BACKEND (print_backend), TRUE);
if (!backend->priv->printer_list_requested)
{
if (GTK_PRINT_BACKEND_GET_CLASS (backend)->request_printer_list)
GTK_PRINT_BACKEND_GET_CLASS (backend)->request_printer_list (backend);
backend->priv->printer_list_requested = TRUE;
}
return print_backend->priv->printer_list_done;
return G_LIST_MODEL (backend->priv->printers);
}
gboolean
gtk_print_backend_printer_list_is_done (GtkPrintBackend *backend)
{
g_return_val_if_fail (GTK_IS_PRINT_BACKEND (backend), TRUE);
return backend->priv->printer_list_done;
}
GtkPrinter *
gtk_print_backend_find_printer (GtkPrintBackend *backend,
const gchar *printer_name)
{
GtkPrintBackendPrivate *priv;
GtkPrinter *printer;
GtkPrinter *result = NULL;
guint i;
g_return_val_if_fail (GTK_IS_PRINT_BACKEND (backend), NULL);
priv = backend->priv;
for (i = 0; !result && i < g_list_model_get_n_items (G_LIST_MODEL (backend->priv->printers)); i++)
{
GtkPrinter *printer = g_list_model_get_item (G_LIST_MODEL (backend->priv->printers), i);
if (strcmp (gtk_printer_get_name (printer), printer_name) == 0)
result = printer;
g_object_unref (printer);
}
if (priv->printers)
printer = g_hash_table_lookup (priv->printers, printer_name);
else
printer = NULL;
return printer;
return result;
}
void
@@ -754,7 +728,7 @@ request_password (GtkPrintBackend *backend,
}
void
gtk_print_backend_destroy (GtkPrintBackend *print_backend)
gtk_print_backend_destroy (GtkPrintBackend *backend)
{
/* The lifecycle of print backends and printers are tied, such that
* the backend owns the printers, but the printers also ref the backend.
@@ -762,5 +736,5 @@ gtk_print_backend_destroy (GtkPrintBackend *print_backend)
* will be around. However, this results in a cycle, which we break
* with this call, which causes the print backend to release its printers.
*/
g_object_run_dispose (G_OBJECT (print_backend));
g_object_run_dispose (G_OBJECT (backend));
}

View File

@@ -149,6 +149,8 @@ GType gtk_print_backend_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GList *gtk_print_backend_get_printer_list (GtkPrintBackend *print_backend);
GDK_AVAILABLE_IN_ALL
GListModel *gtk_print_backend_get_printers (GtkPrintBackend *print_backend);
GDK_AVAILABLE_IN_ALL
gboolean gtk_print_backend_printer_list_is_done (GtkPrintBackend *print_backend);
GDK_AVAILABLE_IN_ALL
GtkPrinter *gtk_print_backend_find_printer (GtkPrintBackend *print_backend,

View File

@@ -169,7 +169,7 @@ gtk_printer_class_init (GtkPrinterClass *class)
g_param_spec_string ("icon-name",
P_("Icon Name"),
P_("The icon name to use for the printer"),
"",
"printer",
GTK_PARAM_READABLE));
g_object_class_install_property (G_OBJECT_CLASS (class),
PROP_JOB_COUNT,
@@ -340,7 +340,7 @@ gtk_printer_get_property (GObject *object,
if (priv->icon_name)
g_value_set_string (value, priv->icon_name);
else
g_value_set_static_string (value, "");
g_value_set_static_string (value, "printer");
break;
case PROP_JOB_COUNT:
g_value_set_int (value, priv->job_count);

View File

@@ -35,7 +35,6 @@
#include "gtkspinbutton.h"
#include "gtkimage.h"
#include "gtktreeselection.h"
#include "gtknotebook.h"
#include "gtkscrolledwindow.h"
#include "gtkcombobox.h"
@@ -136,14 +135,12 @@ static void gtk_print_unix_dialog_get_property (GObject *object,
GValue *value,
GParamSpec *pspec);
static void unschedule_idle_mark_conflicts (GtkPrintUnixDialog *dialog);
static void selected_printer_changed (GtkTreeSelection *selection,
GtkPrintUnixDialog *dialog);
static void selected_printer_changed (GtkPrintUnixDialog *dialog);
static void clear_per_printer_ui (GtkPrintUnixDialog *dialog);
static void printer_added_cb (GtkPrintBackend *backend,
GtkPrinter *printer,
GtkPrintUnixDialog *dialog);
static void printer_removed_cb (GtkPrintBackend *backend,
GtkPrinter *printer,
static void printer_added_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkPrintUnixDialog *dialog);
static void printer_status_cb (GtkPrintBackend *backend,
GtkPrinter *printer,
@@ -171,12 +168,10 @@ static void draw_collate (GtkDrawingArea *da,
int width,
int height,
gpointer data);
static gboolean is_printer_active (GtkTreeModel *model,
GtkTreeIter *iter,
GtkPrintUnixDialog *dialog);
static gint default_printer_list_sort_func (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
static gboolean is_printer_active (gpointer item,
gpointer data);
static int default_printer_list_sort_func (gconstpointer a,
gconstpointer b,
gpointer user_data);
static gboolean paper_size_row_is_separator (GtkTreeModel *model,
GtkTreeIter *iter,
@@ -198,20 +193,10 @@ static gboolean dialog_get_collate (GtkPrintUnixDialog *dialog);
static gboolean dialog_get_reverse (GtkPrintUnixDialog *dialog);
static gint dialog_get_n_copies (GtkPrintUnixDialog *dialog);
static void set_cell_sensitivity_func (GtkTreeViewColumn *tree_column,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data);
static gboolean set_active_printer (GtkPrintUnixDialog *dialog,
const gchar *printer_name);
static void redraw_page_layout_preview (GtkPrintUnixDialog *dialog);
static void load_print_backends (GtkPrintUnixDialog *dialog);
static gboolean printer_compare (GtkTreeModel *model,
gint column,
const gchar *key,
GtkTreeIter *iter,
gpointer search_data);
static GListModel *load_print_backends (GtkPrintUnixDialog *dialog);
/* GtkBuildable */
static void gtk_print_unix_dialog_buildable_init (GtkBuildableIface *iface);
@@ -272,22 +257,11 @@ struct _GtkPrintUnixDialog
GtkWidget *notebook;
GtkWidget *printer_treeview;
GtkTreeViewColumn *printer_icon_column;
GtkTreeViewColumn *printer_name_column;
GtkTreeViewColumn *printer_location_column;
GtkTreeViewColumn *printer_status_column;
GtkCellRenderer *printer_icon_renderer;
GtkCellRenderer *printer_name_renderer;
GtkCellRenderer *printer_location_renderer;
GtkCellRenderer *printer_status_renderer;
GtkWidget *printer_list;
GtkPrintCapabilities manual_capabilities;
GtkPrintCapabilities printer_capabilities;
GtkTreeModel *printer_list;
GtkTreeModelFilter *printer_list_filter;
GtkPageSetup *page_setup;
gboolean page_setup_set;
gboolean embed_page_setup;
@@ -491,19 +465,9 @@ gtk_print_unix_dialog_class_init (GtkPrintUnixDialogClass *class)
"/org/gtk/libgtk/ui/gtkprintunixdialog.ui");
/* GtkTreeView / GtkTreeModel */
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_treeview);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_list);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_list_filter);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, page_setup_list);
gtk_widget_class_bind_template_child(widget_class, GtkPrintUnixDialog, page_setup_list);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, custom_paper_list);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_icon_column);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_name_column);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_location_column);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_status_column);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_icon_renderer);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_name_renderer);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_location_renderer);
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, printer_status_renderer);
/* General Widgetry */
gtk_widget_class_bind_template_child (widget_class, GtkPrintUnixDialog, notebook);
@@ -558,7 +522,6 @@ gtk_print_unix_dialog_class_init (GtkPrintUnixDialogClass *class)
gtk_widget_class_bind_template_callback (widget_class, redraw_page_layout_preview);
gtk_widget_class_bind_template_callback (widget_class, error_dialogs);
gtk_widget_class_bind_template_callback (widget_class, emit_ok_response);
gtk_widget_class_bind_template_callback (widget_class, selected_printer_changed);
gtk_widget_class_bind_template_callback (widget_class, page_range_entry_focus_changed);
gtk_widget_class_bind_template_callback (widget_class, update_page_range_entry_sensitivity);
gtk_widget_class_bind_template_callback (widget_class, update_print_at_entry_sensitivity);
@@ -729,11 +692,24 @@ error_dialogs (GtkPrintUnixDialog *dialog,
}
}
static char *
get_printer_key (GtkPrinter *printer)
{
return g_strconcat ("", gtk_printer_get_name (printer), " ", gtk_printer_get_location (printer), NULL);
}
static void
gtk_print_unix_dialog_init (GtkPrintUnixDialog *dialog)
{
GtkTreeSortable *sort;
GtkWidget *widget;
GListModel *model;
GListModel *sorted;
GListModel *filtered;
GListModel *selection;
GtkSorter *sorter;
GtkFilter *filter;
GtkFilter *filter1;
GtkExpression *expression;
dialog->print_backends = NULL;
dialog->current_page = -1;
@@ -767,43 +743,7 @@ gtk_print_unix_dialog_init (GtkPrintUnixDialog *dialog)
gtk_widget_set_visible (dialog->selection_radio, FALSE);
gtk_widget_set_visible (dialog->conflicts_widget, FALSE);
/* Treeview auxiliary functions need to be setup here */
gtk_tree_model_filter_set_visible_func (dialog->printer_list_filter,
(GtkTreeModelFilterVisibleFunc) is_printer_active,
dialog,
NULL);
sort = GTK_TREE_SORTABLE (dialog->printer_list);
gtk_tree_sortable_set_default_sort_func (sort,
default_printer_list_sort_func,
NULL,
NULL);
gtk_tree_sortable_set_sort_column_id (sort,
GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
GTK_SORT_ASCENDING);
gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (dialog->printer_treeview),
printer_compare, NULL, NULL);
gtk_tree_view_column_set_cell_data_func (dialog->printer_icon_column,
dialog->printer_icon_renderer,
set_cell_sensitivity_func, NULL, NULL);
gtk_tree_view_column_set_cell_data_func (dialog->printer_name_column,
dialog->printer_name_renderer,
set_cell_sensitivity_func, NULL, NULL);
gtk_tree_view_column_set_cell_data_func (dialog->printer_location_column,
dialog->printer_location_renderer,
set_cell_sensitivity_func, NULL, NULL);
gtk_tree_view_column_set_cell_data_func (dialog->printer_status_column,
dialog->printer_status_renderer,
set_cell_sensitivity_func, NULL, NULL);
/* Paper size combo auxiliary funcs */
/* Paper size combo auxilary funcs */
gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (dialog->paper_size_combo),
paper_size_row_is_separator, NULL, NULL);
gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (dialog->paper_size_combo),
@@ -811,7 +751,39 @@ gtk_print_unix_dialog_init (GtkPrintUnixDialog *dialog)
page_name_func, NULL, NULL);
/* Load backends */
load_print_backends (dialog);
model = load_print_backends (dialog);
sorter = gtk_custom_sorter_new (default_printer_list_sort_func, NULL, NULL);
sorted = G_LIST_MODEL (gtk_sort_list_model_new (model, sorter));
g_object_unref (sorter);
filter = gtk_every_filter_new ();
filter1 = gtk_string_filter_new ();
gtk_string_filter_set_match_mode (GTK_STRING_FILTER (filter1), GTK_STRING_FILTER_MATCH_MODE_SUBSTRING);
gtk_string_filter_set_ignore_case (GTK_STRING_FILTER (filter1), TRUE);
expression = gtk_cclosure_expression_new (G_TYPE_STRING,
NULL, 0, NULL,
G_CALLBACK (get_printer_key),
NULL, NULL);
gtk_string_filter_set_expression (GTK_STRING_FILTER (filter1), expression);
gtk_expression_unref (expression);
gtk_multi_filter_append (GTK_MULTI_FILTER (filter), filter1);
filter1 = gtk_custom_filter_new (is_printer_active, dialog, NULL);
gtk_multi_filter_append (GTK_MULTI_FILTER (filter), filter1);
filtered = G_LIST_MODEL (gtk_filter_list_model_new (sorted, filter));
g_object_unref (filter);
selection = G_LIST_MODEL (gtk_single_selection_new (filtered));
gtk_single_selection_set_autoselect (GTK_SINGLE_SELECTION (selection), FALSE);
gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (selection), GTK_INVALID_LIST_POSITION);
gtk_column_view_set_model (GTK_COLUMN_VIEW (dialog->printer_list), selection);
g_signal_connect (selection, "items-changed", G_CALLBACK (printer_added_cb), dialog);
g_signal_connect_swapped (selection, "notify::selected", G_CALLBACK (selected_printer_changed), dialog);
g_object_unref (selection);
g_object_unref (filtered);
g_object_unref (model);
/* Load custom papers */
_gtk_print_load_custom_papers (dialog->custom_paper_list);
@@ -884,21 +856,8 @@ disconnect_printer_details_request (GtkPrintUnixDialog *dialog,
dialog->request_details_tag = 0;
set_busy_cursor (dialog, FALSE);
if (details_failed)
gtk_list_store_set (GTK_LIST_STORE (dialog->printer_list),
g_object_get_data (G_OBJECT (dialog->request_details_printer),
"gtk-print-tree-iter"),
PRINTER_LIST_COL_STATE,
_("Getting printer information failed"),
-1);
else
gtk_list_store_set (GTK_LIST_STORE (dialog->printer_list),
g_object_get_data (G_OBJECT (dialog->request_details_printer),
"gtk-print-tree-iter"),
PRINTER_LIST_COL_STATE,
gtk_printer_get_state_message (dialog->request_details_printer),
-1);
g_object_unref (dialog->request_details_printer);
dialog->request_details_printer = NULL;
gtk_printer_set_state_message (dialog->request_details_printer, _("Getting printer information failed"));
g_clear_object (&dialog->request_details_printer);
}
}
@@ -906,8 +865,6 @@ static void
gtk_print_unix_dialog_finalize (GObject *object)
{
GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG (object);
GtkPrintBackend *backend;
GList *node;
unschedule_idle_mark_conflicts (dialog);
disconnect_printer_details_request (dialog, FALSE);
@@ -933,18 +890,6 @@ gtk_print_unix_dialog_finalize (GObject *object)
g_clear_pointer (&dialog->waiting_for_printer, (GDestroyNotify)g_free);
g_clear_pointer (&dialog->format_for_printer, (GDestroyNotify)g_free);
for (node = dialog->print_backends; node != NULL; node = node->next)
{
backend = GTK_PRINT_BACKEND (node->data);
g_signal_handlers_disconnect_by_func (backend, printer_added_cb, dialog);
g_signal_handlers_disconnect_by_func (backend, printer_removed_cb, dialog);
g_signal_handlers_disconnect_by_func (backend, printer_status_cb, dialog);
gtk_print_backend_destroy (backend);
g_object_unref (backend);
}
g_list_free (dialog->print_backends);
dialog->print_backends = NULL;
@@ -953,17 +898,6 @@ gtk_print_unix_dialog_finalize (GObject *object)
G_OBJECT_CLASS (gtk_print_unix_dialog_parent_class)->finalize (object);
}
static void
printer_removed_cb (GtkPrintBackend *backend,
GtkPrinter *printer,
GtkPrintUnixDialog *dialog)
{
GtkTreeIter *iter;
iter = g_object_get_data (G_OBJECT (printer), "gtk-print-tree-iter");
gtk_list_store_remove (GTK_LIST_STORE (dialog->printer_list), iter);
}
static void
gtk_print_unix_dialog_buildable_init (GtkBuildableIface *iface)
{
@@ -985,157 +919,68 @@ gtk_print_unix_dialog_buildable_get_internal_child (GtkBuildable *buildable,
return parent_buildable_iface->get_internal_child (buildable, builder, childname);
}
/* This function controls "sensitive" property of GtkCellRenderer
* based on pause state of printers.
*/
void set_cell_sensitivity_func (GtkTreeViewColumn *tree_column,
GtkCellRenderer *cell,
GtkTreeModel *tree_model,
GtkTreeIter *iter,
gpointer data)
{
GtkPrinter *printer;
gtk_tree_model_get (tree_model, iter,
PRINTER_LIST_COL_PRINTER_OBJ, &printer,
-1);
if (printer != NULL && !gtk_printer_is_accepting_jobs (printer))
g_object_set (cell, "sensitive", FALSE, NULL);
else
g_object_set (cell, "sensitive", TRUE, NULL);
g_clear_object (&printer);
}
static void
printer_status_cb (GtkPrintBackend *backend,
GtkPrinter *printer,
GtkPrintUnixDialog *dialog)
{
GtkTreeIter *iter;
GtkTreeSelection *selection;
GIcon *icon;
iter = g_object_get_data (G_OBJECT (printer), "gtk-print-tree-iter");
icon = g_themed_icon_new ("printer");
g_themed_icon_prepend_name (G_THEMED_ICON (icon), gtk_printer_get_icon_name (printer));
gtk_list_store_set (GTK_LIST_STORE (dialog->printer_list), iter,
PRINTER_LIST_COL_ICON, icon,
PRINTER_LIST_COL_STATE, gtk_printer_get_state_message (printer),
PRINTER_LIST_COL_JOBS, gtk_printer_get_job_count (printer),
PRINTER_LIST_COL_LOCATION, gtk_printer_get_location (printer),
-1);
g_object_unref (icon);
GListModel *model;
/* When the pause state change then we need to update sensitive property
* of GTK_RESPONSE_OK button inside of selected_printer_changed function.
*/
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->printer_treeview));
dialog->internal_printer_change = TRUE;
selected_printer_changed (selection, dialog);
dialog->internal_printer_change = FALSE;
selected_printer_changed (dialog);
model = gtk_column_view_get_model (GTK_COLUMN_VIEW (dialog->printer_list));
if (gtk_print_backend_printer_list_is_done (backend) &&
gtk_printer_is_default (printer) &&
(gtk_tree_selection_count_selected_rows (selection) == 0))
gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (model)) == GTK_INVALID_LIST_POSITION)
set_active_printer (dialog, gtk_printer_get_name (printer));
}
static void
printer_added_cb (GtkPrintBackend *backend,
GtkPrinter *printer,
printer_added_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkPrintUnixDialog *dialog)
{
GtkTreeIter iter, filter_iter;
GtkTreeSelection *selection;
GtkTreePath *path;
GIcon *icon;
guint i;
gtk_list_store_append (GTK_LIST_STORE (dialog->printer_list), &iter);
g_object_set_data_full (G_OBJECT (printer),
"gtk-print-tree-iter",
gtk_tree_iter_copy (&iter),
(GDestroyNotify) gtk_tree_iter_free);
icon = g_themed_icon_new ("printer");
g_themed_icon_prepend_name (G_THEMED_ICON (icon), gtk_printer_get_icon_name (printer));
gtk_list_store_set (GTK_LIST_STORE (dialog->printer_list), &iter,
PRINTER_LIST_COL_ICON, icon,
PRINTER_LIST_COL_NAME, gtk_printer_get_name (printer),
PRINTER_LIST_COL_STATE, gtk_printer_get_state_message (printer),
PRINTER_LIST_COL_JOBS, gtk_printer_get_job_count (printer),
PRINTER_LIST_COL_LOCATION, gtk_printer_get_location (printer),
PRINTER_LIST_COL_PRINTER_OBJ, printer,
-1);
g_object_unref (icon);
gtk_tree_model_filter_convert_child_iter_to_iter (dialog->printer_list_filter,
&filter_iter, &iter);
path = gtk_tree_model_get_path (GTK_TREE_MODEL (dialog->printer_list_filter), &filter_iter);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->printer_treeview));
if (dialog->waiting_for_printer != NULL &&
strcmp (gtk_printer_get_name (printer), dialog->waiting_for_printer) == 0)
for (i = position; i < position + added; i++)
{
dialog->internal_printer_change = TRUE;
gtk_tree_selection_select_iter (selection, &filter_iter);
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (dialog->printer_treeview),
path, NULL, TRUE, 0.5, 0.0);
dialog->internal_printer_change = FALSE;
g_free (dialog->waiting_for_printer);
dialog->waiting_for_printer = NULL;
}
else if (is_default_printer (dialog, printer) &&
gtk_tree_selection_count_selected_rows (selection) == 0)
{
dialog->internal_printer_change = TRUE;
gtk_tree_selection_select_iter (selection, &filter_iter);
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (dialog->printer_treeview),
path, NULL, TRUE, 0.5, 0.0);
dialog->internal_printer_change = FALSE;
}
GtkPrinter *printer = g_list_model_get_item (model, i);
gtk_tree_path_free (path);
if (dialog->waiting_for_printer != NULL &&
strcmp (gtk_printer_get_name (printer), dialog->waiting_for_printer) == 0)
{
gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (model), i);
g_free (dialog->waiting_for_printer);
dialog->waiting_for_printer = NULL;
g_object_unref (printer);
return;
}
else if (is_default_printer (dialog, printer) &&
gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (model)) == GTK_INVALID_LIST_POSITION)
{
gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (model), i);
g_object_unref (printer);
return;
}
g_object_unref (printer);
}
}
static void
printer_list_initialize (GtkPrintUnixDialog *dialog,
GtkPrintBackend *print_backend)
{
GList *list;
GList *node;
g_return_if_fail (print_backend != NULL);
g_signal_connect_object (print_backend, "printer-added",
(GCallback) printer_added_cb, G_OBJECT (dialog), 0);
g_signal_connect_object (print_backend, "printer-removed",
(GCallback) printer_removed_cb, G_OBJECT (dialog), 0);
g_signal_connect_object (print_backend, "printer-status-changed",
(GCallback) printer_status_cb, G_OBJECT (dialog), 0);
list = gtk_print_backend_get_printer_list (print_backend);
node = list;
while (node != NULL)
{
printer_added_cb (print_backend, node->data, dialog);
node = node->next;
}
g_list_free (list);
}
static void
static GListModel *
load_print_backends (GtkPrintUnixDialog *dialog)
{
GList *node;
GListStore *lists;
GListModel *model;
lists = g_list_store_new (G_TYPE_LIST_MODEL);
if (g_module_supported ())
dialog->print_backends = gtk_print_backend_load_modules ();
@@ -1143,8 +988,17 @@ load_print_backends (GtkPrintUnixDialog *dialog)
for (node = dialog->print_backends; node != NULL; node = node->next)
{
GtkPrintBackend *backend = node->data;
printer_list_initialize (dialog, backend);
g_signal_connect_object (backend, "printer-status-changed",
G_CALLBACK (printer_status_cb), G_OBJECT (dialog), 0);
g_list_store_append (lists, gtk_print_backend_get_printers (backend));
}
model = G_LIST_MODEL (gtk_flatten_list_model_new (GTK_TYPE_PRINTER, G_LIST_MODEL (lists)));
g_object_unref (lists);
return model;
}
static void
@@ -1226,19 +1080,11 @@ gtk_print_unix_dialog_get_property (GObject *object,
}
static gboolean
is_printer_active (GtkTreeModel *model,
GtkTreeIter *iter,
GtkPrintUnixDialog *dialog)
is_printer_active (gpointer item, gpointer data)
{
GtkPrinter *printer = item;
GtkPrintUnixDialog *dialog = data;
gboolean result;
GtkPrinter *printer;
gtk_tree_model_get (model, iter,
PRINTER_LIST_COL_PRINTER_OBJ, &printer,
-1);
if (printer == NULL)
return FALSE;
result = gtk_printer_is_active (printer);
@@ -1255,61 +1101,44 @@ is_printer_active (GtkTreeModel *model,
gtk_printer_accepts_ps (printer));
}
g_object_unref (printer);
return result;
}
static gint
default_printer_list_sort_func (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
static int
default_printer_list_sort_func (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
gchar *a_name;
gchar *b_name;
GtkPrinter *a_printer;
GtkPrinter *b_printer;
gint result;
gtk_tree_model_get (model, a,
PRINTER_LIST_COL_NAME, &a_name,
PRINTER_LIST_COL_PRINTER_OBJ, &a_printer,
-1);
gtk_tree_model_get (model, b,
PRINTER_LIST_COL_NAME, &b_name,
PRINTER_LIST_COL_PRINTER_OBJ, &b_printer,
-1);
GtkPrinter *a_printer = (gpointer)a;
GtkPrinter *b_printer = (gpointer)b;
const char *a_name;
const char *b_name;
if (a_printer == NULL && b_printer == NULL)
result = 0;
return 0;
else if (a_printer == NULL)
result = G_MAXINT;
return 1;
else if (b_printer == NULL)
result = G_MININT;
else if (gtk_printer_is_virtual (a_printer) && gtk_printer_is_virtual (b_printer))
result = 0;
return -1;
if (gtk_printer_is_virtual (a_printer) && gtk_printer_is_virtual (b_printer))
return 0;
else if (gtk_printer_is_virtual (a_printer) && !gtk_printer_is_virtual (b_printer))
result = G_MININT;
return -1;
else if (!gtk_printer_is_virtual (a_printer) && gtk_printer_is_virtual (b_printer))
result = G_MAXINT;
else if (a_name == NULL && b_name == NULL)
result = 0;
return 1;
a_name = gtk_printer_get_name (a_printer);
b_name = gtk_printer_get_name (b_printer);
if (a_name == NULL && b_name == NULL)
return 0;
else if (a_name == NULL && b_name != NULL)
result = 1;
return 1;
else if (a_name != NULL && b_name == NULL)
result = -1;
else
result = g_ascii_strcasecmp (a_name, b_name);
return -1;
g_free (a_name);
g_free (b_name);
if (a_printer)
g_object_unref (a_printer);
if (b_printer)
g_object_unref (b_printer);
return result;
return g_ascii_strcasecmp (a_name, b_name);
}
static GtkWidget *
@@ -1697,8 +1526,6 @@ update_dialog_from_capabilities (GtkPrintUnixDialog *dialog)
gtk_widget_set_visible (button, (caps & GTK_PRINT_CAPABILITY_PREVIEW) != 0);
update_collate_icon (NULL, dialog);
gtk_tree_model_filter_refilter (dialog->printer_list_filter);
}
static gboolean
@@ -1901,17 +1728,12 @@ mark_conflicts (GtkPrintUnixDialog *dialog)
if (printer)
{
g_signal_handler_block (dialog->options,
dialog->options_changed_handler);
g_signal_handler_block (dialog->options, dialog->options_changed_handler);
gtk_printer_option_set_clear_conflicts (dialog->options);
have_conflict = _gtk_printer_mark_conflicts (printer, dialog->options);
have_conflict = _gtk_printer_mark_conflicts (printer,
dialog->options);
g_signal_handler_unblock (dialog->options,
dialog->options_changed_handler);
g_signal_handler_unblock (dialog->options, dialog->options_changed_handler);
}
if (have_conflict)
@@ -1989,20 +1811,14 @@ printer_details_acquired (GtkPrinter *printer,
disconnect_printer_details_request (dialog, !success);
if (success)
{
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->printer_treeview));
selected_printer_changed (selection, dialog);
}
selected_printer_changed (dialog);
}
static void
selected_printer_changed (GtkTreeSelection *selection,
GtkPrintUnixDialog *dialog)
selected_printer_changed (GtkPrintUnixDialog *dialog)
{
GListModel *model = gtk_column_view_get_model (GTK_COLUMN_VIEW (dialog->printer_list));
GtkPrinter *printer;
GtkTreeIter iter, filter_iter;
/* Whenever the user selects a printer we stop looking for
* the printer specified in the initial settings
@@ -2016,17 +1832,7 @@ selected_printer_changed (GtkTreeSelection *selection,
disconnect_printer_details_request (dialog, FALSE);
printer = NULL;
if (gtk_tree_selection_get_selected (selection, NULL, &filter_iter))
{
gtk_tree_model_filter_convert_iter_to_child_iter (dialog->printer_list_filter,
&iter,
&filter_iter);
gtk_tree_model_get (dialog->printer_list, &iter,
PRINTER_LIST_COL_PRINTER_OBJ, &printer,
-1);
}
printer = gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (model));
/* sets GTK_RESPONSE_OK button sensitivity depending on whether the printer
* accepts/rejects jobs
@@ -2036,33 +1842,24 @@ selected_printer_changed (GtkTreeSelection *selection,
if (!gtk_printer_is_accepting_jobs (printer))
gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE);
else if (dialog->current_printer == printer && gtk_printer_has_details (printer))
gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
}
if (printer != NULL && !gtk_printer_has_details (printer))
{
gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE);
dialog->request_details_tag =
g_signal_connect (printer, "details-acquired",
G_CALLBACK (printer_details_acquired), dialog);
/* take the reference */
dialog->request_details_printer = printer;
dialog->request_details_tag = g_signal_connect (printer, "details-acquired",
G_CALLBACK (printer_details_acquired), dialog);
dialog->request_details_printer = g_object_ref (printer);
set_busy_cursor (dialog, TRUE);
gtk_list_store_set (GTK_LIST_STORE (dialog->printer_list),
g_object_get_data (G_OBJECT (printer), "gtk-print-tree-iter"),
PRINTER_LIST_COL_STATE, _("Getting printer information…"),
-1);
gtk_printer_set_state_message (printer, _("Getting printer information…"));
gtk_printer_request_details (printer);
return;
}
if (printer == dialog->current_printer)
{
if (printer)
g_object_unref (printer);
return;
}
return;
if (dialog->options)
{
@@ -2075,7 +1872,7 @@ selected_printer_changed (GtkTreeSelection *selection,
if (printer != NULL && gtk_printer_is_accepting_jobs (printer))
gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
dialog->current_printer = printer;
dialog->current_printer = g_object_ref (printer);
if (printer != NULL)
{
@@ -2114,86 +1911,7 @@ selected_printer_changed (GtkTreeSelection *selection,
update_paper_sizes (dialog);
dialog->internal_page_setup_change = FALSE;
g_object_notify ( G_OBJECT(dialog), "selected-printer");
}
static gboolean
printer_compare (GtkTreeModel *model,
gint column,
const gchar *key,
GtkTreeIter *iter,
gpointer search_data)
{
gboolean matches = FALSE;
if (key != NULL)
{
gchar *name = NULL;
gchar *location = NULL;
gchar *casefold_key = NULL;
gchar *casefold_name = NULL;
gchar *casefold_location = NULL;
gchar **keys;
gchar *tmp1, *tmp2;
gint i;
gtk_tree_model_get (model, iter,
PRINTER_LIST_COL_NAME, &name,
PRINTER_LIST_COL_LOCATION, &location,
-1);
casefold_key = g_utf8_casefold (key, -1);
if (name != NULL)
{
casefold_name = g_utf8_casefold (name, -1);
g_free (name);
}
if (location != NULL)
{
casefold_location = g_utf8_casefold (location, -1);
g_free (location);
}
if (casefold_name != NULL ||
casefold_location != NULL)
{
keys = g_strsplit_set (casefold_key, " \t", 0);
if (keys != NULL)
{
matches = TRUE;
for (i = 0; keys[i] != NULL; i++)
{
if (keys[i][0] != '\0')
{
tmp1 = tmp2 = NULL;
if (casefold_name != NULL)
tmp1 = g_strstr_len (casefold_name, -1, keys[i]);
if (casefold_location != NULL)
tmp2 = g_strstr_len (casefold_location, -1, keys[i]);
if (tmp1 == NULL && tmp2 == NULL)
{
matches = FALSE;
break;
}
}
}
g_strfreev (keys);
}
}
g_free (casefold_location);
g_free (casefold_name);
g_free (casefold_key);
}
return !matches;
g_object_notify (G_OBJECT (dialog), "selected-printer");
}
static void
@@ -3488,42 +3206,28 @@ static gboolean
set_active_printer (GtkPrintUnixDialog *dialog,
const gchar *printer_name)
{
GtkTreeModel *model;
GtkTreeIter iter, filter_iter;
GtkTreeSelection *selection;
GListModel *model;
GtkPrinter *printer;
guint i;
model = GTK_TREE_MODEL (dialog->printer_list);
model = gtk_column_view_get_model (GTK_COLUMN_VIEW (dialog->printer_list));
if (gtk_tree_model_get_iter_first (model, &iter))
for (i = 0; i < g_list_model_get_n_items (model); i++)
{
do
printer = g_list_model_get_item (model, i);
if (strcmp (gtk_printer_get_name (printer), printer_name) == 0)
{
gtk_tree_model_get (GTK_TREE_MODEL (dialog->printer_list), &iter,
PRINTER_LIST_COL_PRINTER_OBJ, &printer,
-1);
if (printer == NULL)
continue;
gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (model), i);
if (strcmp (gtk_printer_get_name (printer), printer_name) == 0)
{
gtk_tree_model_filter_convert_child_iter_to_iter (dialog->printer_list_filter,
&filter_iter, &iter);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->printer_treeview));
dialog->internal_printer_change = TRUE;
gtk_tree_selection_select_iter (selection, &filter_iter);
dialog->internal_printer_change = FALSE;
g_free (dialog->waiting_for_printer);
dialog->waiting_for_printer = NULL;
g_object_unref (printer);
return TRUE;
}
g_free (dialog->waiting_for_printer);
dialog->waiting_for_printer = NULL;
g_object_unref (printer);
return TRUE;
}
} while (gtk_tree_model_iter_next (model, &iter));
g_object_unref (printer);
}
return FALSE;
@@ -3697,13 +3401,8 @@ gtk_print_unix_dialog_set_manual_capabilities (GtkPrintUnixDialog *dialog,
if (dialog->current_printer)
{
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->printer_treeview));
g_clear_object (&dialog->current_printer);
dialog->internal_printer_change = TRUE;
selected_printer_changed (selection, dialog);
dialog->internal_printer_change = FALSE;
selected_printer_changed (dialog);
}
g_object_notify (G_OBJECT (dialog), "manual-capabilities");

351
gtk/gtkset.c Normal file
View File

@@ -0,0 +1,351 @@
/*
* Copyright © 2019 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#include "gtkset.h"
/* Store a set of unsigned integers as a sorted array of ranges.
*/
typedef struct
{
guint first;
guint n_items;
} Range;
struct _GtkSet
{
GArray *ranges;
};
typedef struct
{
GtkSet *set;
Range *current;
int idx;
guint pos;
} GtkRealSetIter;
GtkSet *
gtk_set_new (void)
{
GtkSet *set;
set = g_new (GtkSet, 1);
set->ranges = g_array_new (FALSE, FALSE, sizeof (Range));
return set;
}
GtkSet *
gtk_set_copy (GtkSet *set)
{
GtkSet *copy;
copy = g_new (GtkSet, 1);
copy->ranges = g_array_copy (set->ranges);
return copy;
}
void
gtk_set_free (GtkSet *set)
{
g_array_free (set->ranges, TRUE);
g_free (set);
}
gboolean
gtk_set_contains (GtkSet *set,
guint item)
{
int i;
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
if (item < r->first)
return FALSE;
if (item < r->first + r->n_items)
return TRUE;
}
return FALSE;
}
void
gtk_set_remove_all (GtkSet *set)
{
g_array_set_size (set->ranges, 0);
}
static int
range_compare (Range *r, Range *s)
{
int ret = 0;
if (r->first + r->n_items < s->first)
ret = -1;
else if (s->first + s->n_items < r->first)
ret = 1;
return ret;
}
void
gtk_set_add_range (GtkSet *set,
guint first_item,
guint n_items)
{
int i;
Range s;
int first = -1;
int last = -1;
s.first = first_item;
s.n_items = n_items;
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
int cmp = range_compare (&s, r);
if (cmp < 0)
break;
if (cmp == 0)
{
if (first < 0)
first = i;
last = i;
}
}
if (first > -1)
{
Range *r;
guint start;
guint end;
r = &g_array_index (set->ranges, Range, first);
start = MIN (s.first, r->first);
r = &g_array_index (set->ranges, Range, last);
end = MAX (s.first + s.n_items - 1, r->first + r->n_items - 1);
s.first = start;
s.n_items = end - start + 1;
g_array_remove_range (set->ranges, first, last - first + 1);
g_array_insert_val (set->ranges, first, s);
}
else
g_array_insert_val (set->ranges, i, s);
}
void
gtk_set_remove_range (GtkSet *set,
guint first_item,
guint n_items)
{
Range s;
int i;
int first = -1;
int last = -1;
s.first = first_item;
s.n_items = n_items;
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
int cmp = range_compare (&s, r);
if (cmp < 0)
break;
if (cmp == 0)
{
if (first < 0)
first = i;
last = i;
}
}
if (first > -1)
{
Range *r;
Range a[2];
int k = 0;
r = &g_array_index (set->ranges, Range, first);
if (r->first < s.first)
{
a[k].first = r->first;
a[k].n_items = s.first - r->first;
k++;
}
r = &g_array_index (set->ranges, Range, last);
if (r->first + r->n_items > s.first + s.n_items)
{
a[k].first = s.first + s.n_items;
a[k].n_items = r->first + r->n_items - a[k].first;
k++;
}
g_array_remove_range (set->ranges, first, last - first + 1);
if (k > 0)
g_array_insert_vals (set->ranges, first, a, k);
}
}
void
gtk_set_find_range (GtkSet *set,
guint position,
guint upper_bound,
guint *start,
guint *n_items,
gboolean *contained)
{
int i;
int last = 0;
if (position >= upper_bound)
{
*start = 0;
*n_items = 0;
*contained = FALSE;
return;
}
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
if (position < r->first)
{
*start = last;
*n_items = r->first - last;
*contained = FALSE;
return;
}
else if (r->first <= position && position < r->first + r->n_items)
{
*start = r->first;
*n_items = r->n_items;
*contained = TRUE;
return;
}
else
last = r->first + r->n_items;
}
*start = last;
*n_items = upper_bound - last;
*contained = FALSE;
}
void
gtk_set_add_item (GtkSet *set,
guint item)
{
gtk_set_add_range (set, item, 1);
}
void
gtk_set_remove_item (GtkSet *set,
guint item)
{
gtk_set_remove_range (set, item, 1);
}
/* This is peculiar operation: Replace every number n >= first by n + shift
* This is only supported for negatie if the shifting does not cause any
* ranges to overlap.
*/
void
gtk_set_shift (GtkSet *set,
guint first,
int shift)
{
int i;
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
if (r->first >= first)
r->first += shift;
}
}
void
gtk_set_iter_init (GtkSetIter *iter,
GtkSet *set)
{
GtkRealSetIter *ri = (GtkRealSetIter *)iter;
ri->set = set;
ri->idx = -1;
ri->current = 0;
}
gboolean
gtk_set_iter_next (GtkSetIter *iter,
guint *item)
{
GtkRealSetIter *ri = (GtkRealSetIter *)iter;
if (ri->idx == -1)
{
next_range:
ri->idx++;
if (ri->idx == ri->set->ranges->len)
return FALSE;
ri->current = &g_array_index (ri->set->ranges, Range, ri->idx);
ri->pos = ri->current->first;
}
else
{
ri->pos++;
if (ri->pos == ri->current->first + ri->current->n_items)
goto next_range;
}
*item = ri->pos;
return TRUE;
}
#if 0
void
gtk_set_dump (GtkSet *set)
{
int i;
for (i = 0; i < set->ranges->len; i++)
{
Range *r = &g_array_index (set->ranges, Range, i);
g_print (" %u:%u", r->first, r->n_items);
}
g_print ("\n");
}
#endif

70
gtk/gtkset.h Normal file
View File

@@ -0,0 +1,70 @@
/*
* Copyright © 2019 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __GTK_SET_H__
#define __GTK_SET_H__
#include <glib.h>
typedef struct _GtkSet GtkSet;
typedef struct _GtkSetIter GtkSetIter;
struct _GtkSetIter
{
gpointer dummy1;
gpointer dummy2;
int dummy3;
int dummy4;
};
GtkSet *gtk_set_new (void);
void gtk_set_free (GtkSet *set);
GtkSet *gtk_set_copy (GtkSet *set);
gboolean gtk_set_contains (GtkSet *set,
guint item);
void gtk_set_remove_all (GtkSet *set);
void gtk_set_add_item (GtkSet *set,
guint item);
void gtk_set_remove_item (GtkSet *set,
guint item);
void gtk_set_add_range (GtkSet *set,
guint first,
guint n);
void gtk_set_remove_range (GtkSet *set,
guint first,
guint n);
void gtk_set_find_range (GtkSet *set,
guint position,
guint upper_bound,
guint *start,
guint *n_items,
gboolean *contained);
void gtk_set_shift (GtkSet *set,
guint first,
int shift);
void gtk_set_iter_init (GtkSetIter *iter,
GtkSet *set);
gboolean gtk_set_iter_next (GtkSetIter *iter,
guint *item);
#endif /* __GTK_SET_H__ */

View File

@@ -96,12 +96,14 @@ variant_editor_new (const GVariantType *type,
else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
{
editor = gtk_entry_new ();
gtk_editable_set_width_chars (GTK_EDITABLE (editor), 10);
g_signal_connect (editor, "notify::text", G_CALLBACK (variant_editor_changed_cb), d);
}
else
{
editor = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
entry = gtk_entry_new ();
gtk_editable_set_width_chars (GTK_EDITABLE (entry), 10);
gtk_box_append (GTK_BOX (editor), entry);
label = gtk_label_new (g_variant_type_peek_string (type));
gtk_box_append (GTK_BOX (editor), label);
@@ -284,7 +286,8 @@ constructed (GObject *object)
row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
activate = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
gtk_box_append (GTK_BOX (row), activate);
gtk_size_group_add_widget (r->priv->sg, activate);
if (r->priv->sg)
gtk_size_group_add_widget (r->priv->sg, activate);
r->priv->activate_button = gtk_button_new_with_label (_("Activate"));
g_signal_connect (r->priv->activate_button, "clicked", G_CALLBACK (activate_action), r);
@@ -307,7 +310,8 @@ constructed (GObject *object)
r->priv->state_type = g_variant_type_copy (g_variant_get_type (state));
row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
label = gtk_label_new (_("Set State"));
gtk_size_group_add_widget (r->priv->sg, label);
if (r->priv->sg)
gtk_size_group_add_widget (r->priv->sg, label);
gtk_box_append (GTK_BOX (row), label);
r->priv->state_entry = variant_editor_new (r->priv->state_type, state_changed, r);
variant_editor_set_value (r->priv->state_entry, state);
@@ -327,7 +331,7 @@ finalize (GObject *object)
GtkInspectorActionEditor *r = GTK_INSPECTOR_ACTION_EDITOR (object);
g_free (r->priv->name);
g_object_unref (r->priv->sg);
g_clear_object (&r->priv->sg);
if (r->priv->state_type)
g_variant_type_free (r->priv->state_type);
g_signal_handlers_disconnect_by_func (r->priv->group, action_enabled_changed_cb, r);

View File

@@ -0,0 +1,61 @@
#include "action-holder.h"
struct _ActionHolder {
GObject instance;
GActionGroup *group;
char *name;
};
G_DEFINE_TYPE (ActionHolder, action_holder, G_TYPE_OBJECT)
static void
action_holder_init (ActionHolder *holder)
{
}
static void
action_holder_finalize (GObject *object)
{
ActionHolder *holder = ACTION_HOLDER (object);
g_object_unref (holder->group);
g_free (holder->name);
G_OBJECT_CLASS (action_holder_parent_class)->finalize (object);
}
static void
action_holder_class_init (ActionHolderClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = action_holder_finalize;
}
ActionHolder *
action_holder_new (GActionGroup *group,
const char *name)
{
ActionHolder *holder;
holder = g_object_new (ACTION_TYPE_HOLDER, NULL);
holder->group = g_object_ref (group);
holder->name = g_strdup (name);
return holder;
}
GActionGroup *
action_holder_get_group (ActionHolder *holder)
{
return holder->group;
}
const char *
action_holder_get_name (ActionHolder *holder)
{
return holder->name;
}

View File

@@ -0,0 +1,17 @@
#ifndef __ACTION_HOLDER_H__
#define __ACTION_HOLDER_H__
#include <gtk/gtk.h>
#define ACTION_TYPE_HOLDER (action_holder_get_type ())
G_DECLARE_FINAL_TYPE (ActionHolder, action_holder, ACTION, HOLDER, GObject)
ActionHolder * action_holder_new (GActionGroup *group,
const char *name);
GActionGroup *action_holder_get_group (ActionHolder *holder);
const char *action_holder_get_name (ActionHolder *holder);
#endif /* __ACTION_HOLDER_H__ */

View File

@@ -20,6 +20,7 @@
#include "actions.h"
#include "action-editor.h"
#include "action-holder.h"
#include "gtkapplication.h"
#include "gtkapplicationwindow.h"
@@ -37,13 +38,11 @@
struct _GtkInspectorActionsPrivate
{
GtkWidget *list;
GtkSizeGroup *name;
GtkSizeGroup *enabled;
GtkSizeGroup *parameter;
GtkSizeGroup *state;
GtkSizeGroup *activate;
GActionGroup *group;
GtkWidget *button;
GActionGroup *group;
GListModel *actions;
GtkColumnViewColumn *name;
};
enum {
@@ -61,105 +60,170 @@ gtk_inspector_actions_init (GtkInspectorActions *sl)
}
static void
add_action (GtkInspectorActions *sl,
GActionGroup *group,
const gchar *name)
action_added_cb (GActionGroup *group,
const gchar *action_name,
GtkInspectorActions *sl)
{
gboolean enabled;
const gchar *parameter;
GVariant *state;
gchar *state_string;
GtkWidget *row;
GtkWidget *label;
GtkWidget *box;
char *key = g_strdup (name);
GtkWidget *editor;
ActionHolder *holder = action_holder_new (group, action_name);
g_list_store_append (G_LIST_STORE (sl->priv->actions), holder);
g_object_unref (holder);
}
static void
setup_name_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_list_item_set_child (list_item, label);
}
static void
bind_name_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
gpointer item;
GtkWidget *label;
item = gtk_list_item_get_item (list_item);
label = gtk_list_item_get_child (list_item);
gtk_label_set_label (GTK_LABEL (label), action_holder_get_name (ACTION_HOLDER (item)));
}
static void
setup_enabled_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (label), 0.5);
gtk_list_item_set_child (list_item, label);
}
static void
bind_enabled_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
gpointer item;
GtkWidget *label;
GActionGroup *group;
const char *name;
gboolean enabled;
item = gtk_list_item_get_item (list_item);
label = gtk_list_item_get_child (list_item);
group = action_holder_get_group (ACTION_HOLDER (item));
name = action_holder_get_name (ACTION_HOLDER (item));
enabled = g_action_group_get_action_enabled (group, name);
gtk_label_set_label (GTK_LABEL (label), enabled ? "+" : "-");
}
static void
setup_parameter_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (label), 0.5);
gtk_list_item_set_child (list_item, label);
gtk_style_context_add_class (gtk_widget_get_style_context (label), "cell");
}
static void
bind_parameter_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
gpointer item;
GtkWidget *label;
GActionGroup *group;
const char *name;
const char *parameter;
item = gtk_list_item_get_item (list_item);
label = gtk_list_item_get_child (list_item);
group = action_holder_get_group (ACTION_HOLDER (item));
name = action_holder_get_name (ACTION_HOLDER (item));
parameter = (const gchar *)g_action_group_get_action_parameter_type (group, name);
gtk_label_set_label (GTK_LABEL (label), parameter);
}
static void
setup_state_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new (NULL);
gtk_widget_set_margin_start (label, 5);
gtk_widget_set_margin_end (label, 5);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_list_item_set_child (list_item, label);
gtk_style_context_add_class (gtk_widget_get_style_context (label), "cell");
}
static void
bind_state_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
gpointer item;
GtkWidget *label;
GActionGroup *group;
const char *name;
GVariant *state;
char *state_string;
item = gtk_list_item_get_item (list_item);
label = gtk_list_item_get_child (list_item);
group = action_holder_get_group (ACTION_HOLDER (item));
name = action_holder_get_name (ACTION_HOLDER (item));
state = g_action_group_get_action_state (group, name);
if (state)
state_string = g_variant_print (state, FALSE);
else
state_string = g_strdup ("");
row = gtk_list_box_row_new ();
g_object_set_data_full (G_OBJECT (row), "key", key, g_free);
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box);
label = gtk_label_new (name);
gtk_widget_add_css_class (label, "cell");
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_size_group_add_widget (sl->priv->name, label);
gtk_box_append (GTK_BOX (box), label);
label = gtk_label_new (enabled ? "+" : "-");
gtk_widget_add_css_class (label, "cell");
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_widget_set_halign (label, GTK_ALIGN_CENTER);
gtk_size_group_add_widget (sl->priv->enabled, label);
gtk_box_append (GTK_BOX (box), label);
g_object_set_data (G_OBJECT (row), "enabled", label);
label = gtk_label_new (parameter);
gtk_widget_add_css_class (label, "cell");
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_size_group_add_widget (sl->priv->parameter, label);
gtk_box_append (GTK_BOX (box), label);
label = gtk_label_new (state_string);
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_widget_add_css_class (label, "cell");
gtk_size_group_add_widget (sl->priv->state, label);
gtk_box_append (GTK_BOX (box), label);
g_object_set_data (G_OBJECT (row), "state", label);
editor = gtk_inspector_action_editor_new (group, name, sl->priv->activate);
gtk_widget_add_css_class (editor, "cell");
gtk_box_append (GTK_BOX (box), editor);
g_object_set_data (G_OBJECT (row), "editor", editor);
gtk_list_box_insert (GTK_LIST_BOX (sl->priv->list), row, -1);
gtk_label_set_label (GTK_LABEL (label), state_string);
g_free (state_string);
}
static GtkWidget *
find_row (GtkInspectorActions *sl,
const char *action_name)
{
GtkWidget *row = NULL;
GtkWidget *widget;
const char *key;
for (widget = gtk_widget_get_first_child (sl->priv->list);
widget;
widget = gtk_widget_get_next_sibling (widget))
{
if (!GTK_IS_LIST_BOX_ROW (widget))
continue;
key = g_object_get_data (G_OBJECT (widget), "key");
if (g_str_equal (key, action_name))
{
row = widget;
break;
}
}
return row;
if (state)
g_variant_unref (state);
}
static void
action_added_cb (GActionGroup *group,
const gchar *action_name,
GtkInspectorActions *sl)
bind_changes_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
add_action (sl, group, action_name);
gpointer item;
GActionGroup *group;
const char *name;
GtkWidget *editor;
item = gtk_list_item_get_item (list_item);
group = action_holder_get_group (ACTION_HOLDER (item));
name = action_holder_get_name (ACTION_HOLDER (item));
editor = gtk_inspector_action_editor_new (group, name, NULL);
gtk_style_context_add_class (gtk_widget_get_style_context (editor), "cell");
gtk_list_item_set_child (list_item, editor);
}
static void
unbind_changes_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
gtk_list_item_set_child (list_item, NULL);
}
static void
@@ -167,21 +231,37 @@ action_removed_cb (GActionGroup *group,
const gchar *action_name,
GtkInspectorActions *sl)
{
GtkWidget *row;
int i;
row = find_row (sl, action_name);
if (row)
gtk_list_box_remove (GTK_LIST_BOX (sl->priv->list), row);
for (i = 0; i < g_list_model_get_n_items (sl->priv->actions); i++)
{
ActionHolder *holder = g_list_model_get_item (sl->priv->actions, i);
if (group == action_holder_get_group (holder) &&
strcmp (action_name, action_holder_get_name (holder)) == 0)
g_list_store_remove (G_LIST_STORE (sl->priv->actions), i);
g_object_unref (holder);
}
}
static void
set_row_enabled (GtkWidget *row,
gboolean enabled)
notify_action_changed (GtkInspectorActions *sl,
GActionGroup *group,
const char *action_name)
{
GtkWidget *label;
int i;
label = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "enabled"));
gtk_label_set_label (GTK_LABEL (label), enabled ? "+" : "-" );
for (i = 0; i < g_list_model_get_n_items (sl->priv->actions); i++)
{
ActionHolder *holder = g_list_model_get_item (sl->priv->actions, i);
if (group == action_holder_get_group (holder) &&
strcmp (action_name, action_holder_get_name (holder)) == 0)
g_list_model_items_changed (sl->priv->actions, i, 1, 1);
g_object_unref (holder);
}
}
static void
@@ -190,26 +270,7 @@ action_enabled_changed_cb (GActionGroup *group,
gboolean enabled,
GtkInspectorActions *sl)
{
GtkWidget *row;
row = find_row (sl, action_name);
set_row_enabled (row, enabled);
}
static void
set_row_state (GtkWidget *row,
GVariant *state)
{
gchar *state_string;
GtkWidget *label;
if (state)
state_string = g_variant_print (state, FALSE);
else
state_string = g_strdup ("");
label = GTK_WIDGET (g_object_get_data (G_OBJECT (row), "state"));
gtk_label_set_label (GTK_LABEL (label), state_string);
g_free (state_string);
notify_action_changed (sl, group, action_name);
}
static void
@@ -218,35 +279,14 @@ action_state_changed_cb (GActionGroup *group,
GVariant *state,
GtkInspectorActions *sl)
{
GtkWidget *row;
row = find_row (sl, action_name);
set_row_state (row, state);
notify_action_changed (sl, group, action_name);
}
static void
refresh_all (GtkInspectorActions *sl)
{
GtkWidget *widget;
for (widget = gtk_widget_get_first_child (sl->priv->list);
widget;
widget = gtk_widget_get_next_sibling (widget))
{
const char *name = g_object_get_data (G_OBJECT (widget), "key");
gboolean enabled;
GVariant *state;
GtkInspectorActionEditor *r;
enabled = g_action_group_get_action_enabled (sl->priv->group, name);
state = g_action_group_get_action_state (sl->priv->group, name);
set_row_enabled (widget, enabled);
set_row_state (widget, state);
r = (GtkInspectorActionEditor*)g_object_get_data (G_OBJECT (widget), "editor");
gtk_inspector_action_editor_update (r, enabled, state);
}
guint n = g_list_model_get_n_items (sl->priv->actions);
g_list_model_items_changed (sl->priv->actions, 0, n, n);
}
static void
@@ -283,7 +323,7 @@ add_group (GtkInspectorActions *sl,
names = g_action_group_list_actions (group);
for (i = 0; names[i]; i++)
add_action (sl, group, names[i]);
action_added_cb (group, names[i], sl);
g_strfreev (names);
g_set_object (&sl->priv->group, group);
@@ -305,7 +345,6 @@ gtk_inspector_actions_set_object (GtkInspectorActions *sl,
{
GtkWidget *stack;
GtkStackPage *page;
GtkWidget *child;
stack = gtk_widget_get_parent (GTK_WIDGET (sl));
page = gtk_stack_get_page (GTK_STACK (stack), GTK_WIDGET (sl));
@@ -315,8 +354,7 @@ gtk_inspector_actions_set_object (GtkInspectorActions *sl,
if (sl->priv->group)
remove_group (sl, page, sl->priv->group);
while ((child = gtk_widget_get_first_child (sl->priv->list)))
gtk_list_box_remove (GTK_LIST_BOX (sl->priv->list), child);
g_list_store_remove_all (G_LIST_STORE (sl->priv->actions));
if (GTK_IS_APPLICATION (object))
add_group (sl, page, G_ACTION_GROUP (object));
@@ -328,6 +366,8 @@ gtk_inspector_actions_set_object (GtkInspectorActions *sl,
if (muxer)
add_group (sl, page, G_ACTION_GROUP (muxer));
}
gtk_column_view_sort_by_column (GTK_COLUMN_VIEW (sl->priv->list), sl->priv->name, GTK_SORT_ASCENDING);
}
static void
@@ -370,13 +410,48 @@ set_property (GObject *object,
}
}
static char *
holder_name (gpointer item)
{
return g_strdup (action_holder_get_name (ACTION_HOLDER (item)));
}
static void
constructed (GObject *object)
{
GtkInspectorActions *sl = GTK_INSPECTOR_ACTIONS (object);
GtkSorter *sorter;
GListModel *sorted;
GListModel *model;
g_signal_connect_swapped (sl->priv->button, "clicked",
G_CALLBACK (refresh_all), sl);
sorter = gtk_string_sorter_new (gtk_cclosure_expression_new (G_TYPE_STRING,
NULL,
0, NULL,
(GCallback)holder_name,
NULL, NULL));
gtk_column_view_column_set_sorter (sl->priv->name, sorter);
g_object_unref (sorter);
sl->priv->actions = G_LIST_MODEL (g_list_store_new (ACTION_TYPE_HOLDER));
sorted = G_LIST_MODEL (gtk_sort_list_model_new (sl->priv->actions,
gtk_column_view_get_sorter (GTK_COLUMN_VIEW (sl->priv->list))));
model = G_LIST_MODEL (gtk_no_selection_new (sorted));
gtk_column_view_set_model (GTK_COLUMN_VIEW (sl->priv->list), model);
g_object_unref (sorted);
g_object_unref (model);
}
static void
finalize (GObject *object)
{
GtkInspectorActions *sl = GTK_INSPECTOR_ACTIONS (object);
g_object_unref (sl->priv->actions);
G_OBJECT_CLASS (gtk_inspector_actions_parent_class)->finalize (object);
}
static void
@@ -385,6 +460,7 @@ gtk_inspector_actions_class_init (GtkInspectorActionsClass *klass)
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = finalize;
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->constructed = constructed;
@@ -396,10 +472,16 @@ gtk_inspector_actions_class_init (GtkInspectorActionsClass *klass)
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/actions.ui");
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorActions, list);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorActions, name);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorActions, enabled);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorActions, parameter);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorActions, state);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorActions, activate);
gtk_widget_class_bind_template_callback (widget_class, setup_name_cb);
gtk_widget_class_bind_template_callback (widget_class, bind_name_cb);
gtk_widget_class_bind_template_callback (widget_class, setup_enabled_cb);
gtk_widget_class_bind_template_callback (widget_class, bind_enabled_cb);
gtk_widget_class_bind_template_callback (widget_class, setup_parameter_cb);
gtk_widget_class_bind_template_callback (widget_class, bind_parameter_cb);
gtk_widget_class_bind_template_callback (widget_class, setup_state_cb);
gtk_widget_class_bind_template_callback (widget_class, bind_state_cb);
gtk_widget_class_bind_template_callback (widget_class, bind_changes_cb);
gtk_widget_class_bind_template_callback (widget_class, unbind_changes_cb);
}
// vim: set et sw=2 ts=2:

View File

@@ -1,98 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="gtk40">
<object class="GtkListStore" id="model">
<columns>
<column type="gchararray"/>
<column type="gchararray"/>
<column type="gboolean"/>
<column type="gchararray"/>
<column type="gchararray"/>
<column type="gpointer"/>
</columns>
</object>
<template class="GtkInspectorActions" parent="GtkBox">
<property name="orientation">vertical</property>
<style>
<class name="view"/>
</style>
<child>
<object class="GtkBox">
<style>
<class name="header"/>
</style>
<child>
<object class="GtkLabel" id="name_heading">
<property name="label" translatable="yes">Name</property>
<property name="xalign">0</property>
</object>
</child>
<child>
<object class="GtkLabel" id="enabled_heading">
<property name="label" translatable="yes">Enabled</property>
<property name="xalign">0</property>
</object>
</child>
<child>
<object class="GtkLabel" id="parameter_heading">
<property name="label" translatable="yes">Parameter Type</property>
<property name="xalign">0</property>
</object>
</child>
<child>
<object class="GtkLabel" id="state_heading">
<property name="label" translatable="yes">State</property>
<property name="xalign">0</property>
</object>
</child>
<child>
<object class="GtkLabel" id="changes_heading">
<property name="label" translatable="yes"></property>
<property name="xalign">0</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="hscrollbar-policy">never</property>
<child>
<object class="GtkListBox" id="list">
<object class="GtkColumnView" id="list">
<style>
<class name="list"/>
</style>
<property name="selection-mode">none</property>
<child>
<object class="GtkColumnViewColumn" id="name">
<property name="title" translatable="yes">Name</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_name_cb"/>
<signal name="bind" handler="bind_name_cb"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="enabled">
<property name="title" translatable="yes">Enabled</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_enabled_cb"/>
<signal name="bind" handler="bind_enabled_cb"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="parameter">
<property name="title" translatable="yes">Parameter Type</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_parameter_cb"/>
<signal name="bind" handler="bind_parameter_cb"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="state">
<property name="title" translatable="yes">State</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_state_cb"/>
<signal name="bind" handler="bind_state_cb"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="changes">
<property name="title"></property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="bind" handler="bind_changes_cb"/>
<signal name="unbind" handler="unbind_changes_cb"/>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
<object class="GtkSizeGroup" id="name">
<property name="mode">horizontal</property>
<widgets>
<widget name="name_heading"/>
</widgets>
</object>
<object class="GtkSizeGroup" id="enabled">
<property name="mode">horizontal</property>
<widgets>
<widget name="enabled_heading"/>
</widgets>
</object>
<object class="GtkSizeGroup" id="parameter">
<property name="mode">horizontal</property>
<widgets>
<widget name="parameter_heading"/>
</widgets>
</object>
<object class="GtkSizeGroup" id="state">
<property name="mode">horizontal</property>
<widgets>
<widget name="state_heading"/>
</widgets>
</object>
<object class="GtkSizeGroup" id="activate">
<property name="mode">horizontal</property>
</object>
</interface>

View File

@@ -1,21 +1,29 @@
/* some style for the inspector */
.header {
background: lightgray;
border: 1px solid gray;
.list header {
background: white;
border: 1px solid lightgray;
}
.header>* {
.list header button {
background: none;
}
.list header button.dnd {
background: gray;
}
.list header>* {
padding: 2px;
font-weight: bold;
}
.header sort_indicator {
.list header sort_indicator {
min-width: 16px;
}
.header>*:not(:last-child) {
border-right: 1px solid gray;
.list header>*:not(:last-child) {
border-right: 1px solid lightgray;
}
.list .cell {

View File

@@ -38,4 +38,7 @@ inspector_sources = files(
'updatesoverlay.c',
'visual.c',
'window.c',
'prop-holder.c',
'resource-holder.c',
'action-holder.c'
)

View File

@@ -336,6 +336,29 @@ object_tree_tree_view_get_children (GObject *object)
return G_LIST_MODEL (result);
}
static GListModel *
object_tree_column_view_get_children (GObject *object)
{
GtkColumnView *view = GTK_COLUMN_VIEW (object);
GListStore *result_list;
GtkFlattenListModel *result;
GListModel *columns, *sublist;
result_list = g_list_store_new (G_TYPE_LIST_MODEL);
columns = gtk_column_view_get_columns (view);
g_list_store_append (result_list, columns);
sublist = object_tree_widget_get_children (object);
g_list_store_append (result_list, sublist);
g_object_unref (sublist);
result = gtk_flatten_list_model_new (G_TYPE_OBJECT, G_LIST_MODEL (result_list));
g_object_unref (result_list);
return G_LIST_MODEL (result);
}
static GListModel *
object_tree_icon_view_get_children (GObject *object)
{
@@ -499,6 +522,11 @@ static const ObjectTreeClassFuncs object_tree_class_funcs[] = {
object_tree_widget_get_parent,
object_tree_tree_view_get_children
},
{
gtk_column_view_get_type,
object_tree_widget_get_parent,
object_tree_column_view_get_children
},
{
gtk_combo_box_get_type,
object_tree_widget_get_parent,

View File

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

145
gtk/inspector/prop-holder.c Normal file
View File

@@ -0,0 +1,145 @@
#include "prop-holder.h"
enum {
PROP_OBJECT = 1,
PROP_PSPEC,
PROP_NAME,
NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES];
struct _PropHolder {
GObject instance;
GObject *object;
GParamSpec *pspec;
};
G_DEFINE_TYPE (PropHolder, prop_holder, G_TYPE_OBJECT)
static void
prop_holder_init (PropHolder *holder)
{
}
static void
prop_holder_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
PropHolder *holder = PROP_HOLDER (object);
switch (prop_id)
{
case PROP_OBJECT:
holder->object = g_value_dup_object (value);
break;
case PROP_PSPEC:
holder->pspec = g_value_get_param (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
prop_holder_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
PropHolder *holder = PROP_HOLDER (object);
switch (prop_id)
{
case PROP_OBJECT:
g_value_set_object (value, holder->object);
break;
case PROP_PSPEC:
g_value_set_param (value, holder->pspec);
break;
case PROP_NAME:
g_value_set_string (value, holder->pspec->name);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
prop_holder_finalize (GObject *object)
{
PropHolder *holder = PROP_HOLDER (object);
g_object_unref (holder->object);
G_OBJECT_CLASS (prop_holder_parent_class)->finalize (object);
}
static void
prop_holder_class_init (PropHolderClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = prop_holder_finalize;
object_class->set_property = prop_holder_set_property;
object_class->get_property = prop_holder_get_property;
properties[PROP_OBJECT] =
g_param_spec_object ("object", "object", "object",
G_TYPE_OBJECT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
properties[PROP_PSPEC] =
g_param_spec_param ("pspec", "pspec", "pspec",
G_TYPE_PARAM,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
properties[PROP_NAME] =
g_param_spec_string ("name", "name", "name",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
}
PropHolder *
prop_holder_new (GObject *object,
GParamSpec *pspec)
{
PropHolder *holder;
holder = g_object_new (PROP_TYPE_HOLDER,
"object", object,
"pspec", pspec,
NULL);
return holder;
}
GObject *
prop_holder_get_object (PropHolder *holder)
{
return holder->object;
}
GParamSpec *
prop_holder_get_pspec (PropHolder *holder)
{
return holder->pspec;
}
const char *
prop_holder_get_name (PropHolder *holder)
{
return holder->pspec->name;
}

View File

@@ -0,0 +1,17 @@
#ifndef __PROP_HOLDER_H__
#define __PROP_HOLDER_H__
#include <gtk/gtk.h>
#define PROP_TYPE_HOLDER (prop_holder_get_type ())
G_DECLARE_FINAL_TYPE (PropHolder, prop_holder, PROP, HOLDER, GObject)
PropHolder * prop_holder_new (GObject *object,
GParamSpec *pspeC);
GObject *prop_holder_get_object (PropHolder *holder);
GParamSpec *prop_holder_get_pspec (PropHolder *holder);
const char *prop_holder_get_name (PropHolder *holder);
#endif /* __PROP_HOLDER_H__ */

View File

@@ -44,6 +44,7 @@
#include "gtkroot.h"
#include "gtkgestureclick.h"
#include "gtkstylecontext.h"
#include "prop-holder.h"
enum
{
@@ -52,11 +53,6 @@ enum
PROP_SEARCH_ENTRY
};
typedef enum {
COLUMN_NAME,
COLUMN_ORIGIN
} SortColumn;
struct _GtkInspectorPropListPrivate
{
GObject *object;
@@ -64,17 +60,11 @@ struct _GtkInspectorPropListPrivate
GtkInspectorObjectTree *object_tree;
GtkWidget *search_entry;
GtkWidget *search_stack;
GtkWidget *list2;
GtkWidget *name_sort_indicator;
GtkWidget *origin_sort_indicator;
GtkWidget *name_heading;
GtkWidget *origin_heading;
SortColumn sort_column;
GtkSortType sort_direction;
GtkSizeGroup *names;
GtkSizeGroup *types;
GtkSizeGroup *values;
GtkSizeGroup *origins;
GtkWidget *list;
GtkFilter *filter;
GtkColumnViewColumn *name;
GtkColumnViewColumn *type;
GtkColumnViewColumn *origin;
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorPropList, gtk_inspector_prop_list, GTK_TYPE_BOX)
@@ -94,103 +84,66 @@ show_search_entry (GtkInspectorPropList *pl)
pl->priv->search_entry);
}
static void
apply_sort (GtkInspectorPropList *pl,
SortColumn column,
GtkSortType direction)
static char *
holder_prop (gpointer item)
{
const char *icon_name;
GParamSpec *prop = prop_holder_get_pspec (PROP_HOLDER (item));
icon_name = direction == GTK_SORT_ASCENDING ? "pan-down-symbolic"
: "pan-up-symbolic";
if (column == COLUMN_NAME)
{
gtk_image_clear (GTK_IMAGE (pl->priv->origin_sort_indicator));
gtk_image_set_from_icon_name (GTK_IMAGE (pl->priv->name_sort_indicator),
icon_name);
}
else
{
gtk_image_clear (GTK_IMAGE (pl->priv->name_sort_indicator));
gtk_image_set_from_icon_name (GTK_IMAGE (pl->priv->origin_sort_indicator),
icon_name);
}
pl->priv->sort_column = column;
pl->priv->sort_direction = direction;
gtk_list_box_invalidate_sort (GTK_LIST_BOX (pl->priv->list2));
return g_strdup (prop->name);
}
static void
sort_changed (GtkGestureClick *gesture,
int n_press,
double x,
double y,
GtkInspectorPropList *pl)
static char *
holder_type (gpointer item)
{
SortColumn column;
GtkSortType direction;
GtkWidget *widget;
GParamSpec *prop = prop_holder_get_pspec (PROP_HOLDER (item));
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
if (widget == pl->priv->name_heading)
column = COLUMN_NAME;
else
column = COLUMN_ORIGIN;
if (pl->priv->sort_column == column &&
pl->priv->sort_direction == GTK_SORT_ASCENDING)
direction = GTK_SORT_DESCENDING;
else
direction = GTK_SORT_ASCENDING;
apply_sort (pl, column, direction);
return g_strdup (g_type_name (prop->value_type));
}
static const char *
row_get_column (GtkListBoxRow *row,
SortColumn column)
static char *
holder_origin (gpointer item)
{
GParamSpec *prop = g_object_get_data (G_OBJECT (row), "pspec");
GParamSpec *prop = prop_holder_get_pspec (PROP_HOLDER (item));
if (column == COLUMN_NAME)
return prop->name;
else
return g_type_name (prop->owner_type);
}
static int
sort_func (GtkListBoxRow *row1,
GtkListBoxRow *row2,
gpointer user_data)
{
GtkInspectorPropList *pl = user_data;
const char *s1 = row_get_column (row1, pl->priv->sort_column);
const char *s2 = row_get_column (row2, pl->priv->sort_column);
int ret = strcmp (s1, s2);
return pl->priv->sort_direction == GTK_SORT_ASCENDING ? ret : -ret;
}
static gboolean
filter_func (GtkListBoxRow *row,
gpointer data)
{
GtkInspectorPropList *pl = data;
GParamSpec *pspec = (GParamSpec *)g_object_get_data (G_OBJECT (row), "pspec");
const char *text = gtk_editable_get_text (GTK_EDITABLE (pl->priv->search_entry));
return g_str_has_prefix (pspec->name, text);
return g_strdup (g_type_name (prop->owner_type));
}
static void
gtk_inspector_prop_list_init (GtkInspectorPropList *pl)
{
GtkSorter *sorter;
pl->priv = gtk_inspector_prop_list_get_instance_private (pl);
gtk_widget_init_template (GTK_WIDGET (pl));
apply_sort (pl, COLUMN_NAME, GTK_SORT_ASCENDING);
pl->priv->filter = gtk_string_filter_new ();
gtk_string_filter_set_match_mode (GTK_STRING_FILTER (pl->priv->filter), GTK_STRING_FILTER_MATCH_MODE_PREFIX);
sorter = gtk_string_sorter_new (gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
0, NULL,
(GCallback)holder_prop,
NULL, NULL));
gtk_string_filter_set_expression (GTK_STRING_FILTER (pl->priv->filter),
gtk_string_sorter_get_expression (GTK_STRING_SORTER (sorter)));
gtk_column_view_column_set_sorter (pl->priv->name, sorter);
g_object_unref (sorter);
sorter = gtk_string_sorter_new (gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
0, NULL,
(GCallback)holder_type,
NULL, NULL));
gtk_column_view_column_set_sorter (pl->priv->type, sorter);
g_object_unref (sorter);
sorter = gtk_string_sorter_new (gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
0, NULL,
(GCallback)holder_origin,
NULL, NULL));
gtk_column_view_column_set_sorter (pl->priv->origin, sorter);
g_object_unref (sorter);
}
static void
@@ -253,6 +206,7 @@ show_object (GtkInspectorPropEditor *editor,
gtk_inspector_object_tree_activate_object (pl->priv->object_tree, object);
}
static void cleanup_object (GtkInspectorPropList *pl);
static void
@@ -265,6 +219,16 @@ finalize (GObject *object)
G_OBJECT_CLASS (gtk_inspector_prop_list_parent_class)->finalize (object);
}
static void
update_filter (GtkInspectorPropList *pl,
GtkSearchEntry *entry)
{
const char *text;
text = gtk_editable_get_text (GTK_EDITABLE (entry));
gtk_string_filter_set_search (GTK_STRING_FILTER (pl->priv->filter), text);
}
static void
constructed (GObject *object)
{
@@ -278,10 +242,7 @@ constructed (GObject *object)
g_signal_connect_swapped (pl->priv->search_entry, "search-started",
G_CALLBACK (show_search_entry), pl);
g_signal_connect_swapped (pl->priv->search_entry, "search-changed",
G_CALLBACK (gtk_list_box_invalidate_filter), pl->priv->list2);
gtk_list_box_set_filter_func (GTK_LIST_BOX (pl->priv->list2), filter_func, pl, NULL);
gtk_list_box_set_sort_func (GTK_LIST_BOX (pl->priv->list2), sort_func, pl, NULL);
G_CALLBACK (update_filter), pl);
}
static void
@@ -298,7 +259,7 @@ update_key_capture (GtkInspectorPropList *pl)
focus = gtk_root_get_focus (GTK_ROOT (toplevel));
if (GTK_IS_EDITABLE (focus) &&
gtk_widget_is_ancestor (focus, pl->priv->list2))
gtk_widget_is_ancestor (focus, pl->priv->list))
capture_widget = NULL;
else
capture_widget = toplevel;
@@ -344,6 +305,120 @@ unroot (GtkWidget *widget)
GTK_WIDGET_CLASS (gtk_inspector_prop_list_parent_class)->unroot (widget);
}
static void
setup_name_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_list_item_set_child (list_item, label);
gtk_style_context_add_class (gtk_widget_get_style_context (label), "cell");
}
static void
bind_name_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GObject *item;
GtkWidget *label;
item = gtk_list_item_get_item (list_item);
label = gtk_list_item_get_child (list_item);
gtk_label_set_label (GTK_LABEL (label), prop_holder_get_name (PROP_HOLDER (item)));
}
static void
setup_type_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_list_item_set_child (list_item, label);
gtk_style_context_add_class (gtk_widget_get_style_context (label), "cell");
}
static void
bind_type_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GObject *item;
GtkWidget *label;
GParamSpec *prop;
const char *type;
item = gtk_list_item_get_item (list_item);
label = gtk_list_item_get_child (list_item);
prop = prop_holder_get_pspec (PROP_HOLDER (item));
type = g_type_name (G_PARAM_SPEC_VALUE_TYPE (prop));
gtk_label_set_label (GTK_LABEL (label), type);
}
static void
setup_origin_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_list_item_set_child (list_item, label);
gtk_style_context_add_class (gtk_widget_get_style_context (label), "cell");
}
static void
bind_origin_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GObject *item;
GtkWidget *label;
GParamSpec *prop;
const char *origin;
item = gtk_list_item_get_item (list_item);
label = gtk_list_item_get_child (list_item);
prop = prop_holder_get_pspec (PROP_HOLDER (item));
origin = g_type_name (prop->owner_type);
gtk_label_set_label (GTK_LABEL (label), origin);
}
static void
bind_value_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item,
gpointer data)
{
GObject *item;
GtkWidget *widget;
GObject *object;
const char *name;
item = gtk_list_item_get_item (list_item);
object = prop_holder_get_object (PROP_HOLDER (item));
name = prop_holder_get_name (PROP_HOLDER (item));
widget = gtk_inspector_prop_editor_new (object, name, NULL);
g_signal_connect (widget, "show-object", G_CALLBACK (show_object), data);
gtk_list_item_set_child (list_item, widget);
gtk_style_context_add_class (gtk_widget_get_style_context (widget), "cell");
}
static void
unbind_value_cb (GtkSignalListItemFactory *factory,
GtkListItem *list_item,
gpointer data)
{
gtk_list_item_set_child (list_item, NULL);
}
static void
gtk_inspector_prop_list_class_init (GtkInspectorPropListClass *klass)
{
@@ -369,16 +444,18 @@ gtk_inspector_prop_list_class_init (GtkInspectorPropListClass *klass)
GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/prop-list.ui");
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, list2);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, names);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, types);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, values);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, origins);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, name_heading);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, name_sort_indicator);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, origin_heading);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, origin_sort_indicator);
gtk_widget_class_bind_template_callback (widget_class, sort_changed);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, list);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, name);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, type);
gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorPropList, origin);
gtk_widget_class_bind_template_callback (widget_class, setup_name_cb);
gtk_widget_class_bind_template_callback (widget_class, bind_name_cb);
gtk_widget_class_bind_template_callback (widget_class, setup_type_cb);
gtk_widget_class_bind_template_callback (widget_class, bind_type_cb);
gtk_widget_class_bind_template_callback (widget_class, setup_origin_cb);
gtk_widget_class_bind_template_callback (widget_class, bind_origin_cb);
gtk_widget_class_bind_template_callback (widget_class, bind_value_cb);
gtk_widget_class_bind_template_callback (widget_class, unbind_value_cb);
}
/* Like g_strdup_value_contents, but keeps the type name separate */
@@ -483,88 +560,6 @@ strdup_value_contents (const GValue *value,
}
}
static GtkWidget *
gtk_inspector_prop_list_create_row (GtkInspectorPropList *pl,
GParamSpec *prop)
{
GValue gvalue = {0};
gchar *value;
gchar *type;
gchar *attribute = NULL;
gboolean writable;
GtkWidget *row;
GtkWidget *box;
GtkWidget *label;
GtkWidget *widget;
g_value_init (&gvalue, prop->value_type);
g_object_get_property (pl->priv->object, prop->name, &gvalue);
strdup_value_contents (&gvalue, &value, &type);
if (GTK_IS_CELL_RENDERER (pl->priv->object))
{
gpointer *layout;
GtkCellArea *area;
gint column = -1;
area = NULL;
layout = g_object_get_data (pl->priv->object, "gtk-inspector-cell-layout");
if (layout)
area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (layout));
if (area)
column = gtk_cell_area_attribute_get_column (area,
GTK_CELL_RENDERER (pl->priv->object),
prop->name);
if (column != -1)
attribute = g_strdup_printf ("%d", column);
}
writable = ((prop->flags & G_PARAM_WRITABLE) != 0) &&
((prop->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
row = gtk_list_box_row_new ();
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
g_object_set_data (G_OBJECT (row), "pspec", prop);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box);
label = gtk_label_new (prop->name);
gtk_widget_add_css_class (label, "cell");
gtk_widget_set_sensitive (label, writable);
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_size_group_add_widget (pl->priv->names, label);
gtk_box_append (GTK_BOX (box), label);
label = gtk_label_new (type ? type : "");
gtk_widget_add_css_class (label, "cell");
gtk_widget_set_sensitive (label, writable);
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_size_group_add_widget (pl->priv->types, label);
gtk_box_append (GTK_BOX (box), label);
label = gtk_label_new (g_type_name (prop->owner_type));
gtk_widget_add_css_class (label, "cell");
gtk_widget_set_sensitive (label, writable);
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_size_group_add_widget (pl->priv->origins, label);
gtk_box_append (GTK_BOX (box), label);
widget = gtk_inspector_prop_editor_new (pl->priv->object, prop->name, pl->priv->values);
gtk_widget_add_css_class (widget, "cell");
gtk_box_append (GTK_BOX (box), widget);
g_signal_connect (widget, "show-object", G_CALLBACK (show_object), pl);
g_free (value);
g_free (type);
g_free (attribute);
g_value_unset (&gvalue);
return row;
}
static void
cleanup_object (GtkInspectorPropList *pl)
{
@@ -583,7 +578,10 @@ gtk_inspector_prop_list_set_object (GtkInspectorPropList *pl,
GParamSpec **props;
guint num_properties;
guint i;
GtkWidget *w;
GListStore *store;
GListModel *list;
GListModel *filtered;
GtkSortListModel *sorted;
if (!object)
return FALSE;
@@ -600,19 +598,19 @@ gtk_inspector_prop_list_set_object (GtkInspectorPropList *pl,
pl->priv->object = object;
while ((w = gtk_widget_get_first_child (pl->priv->list2)))
gtk_list_box_remove (GTK_LIST_BOX (pl->priv->list2), w);
store = g_list_store_new (PROP_TYPE_HOLDER);
for (i = 0; i < num_properties; i++)
{
GParamSpec *prop = props[i];
GtkWidget *row;
PropHolder *holder;
if (! (prop->flags & G_PARAM_READABLE))
continue;
row = gtk_inspector_prop_list_create_row (pl, prop);
gtk_list_box_insert (GTK_LIST_BOX (pl->priv->list2), row, -1);
holder = prop_holder_new (object, prop);
g_list_store_append (store, holder);
g_object_unref (holder);
}
g_free (props);
@@ -620,8 +618,21 @@ gtk_inspector_prop_list_set_object (GtkInspectorPropList *pl,
if (GTK_IS_WIDGET (object))
g_signal_connect_object (object, "destroy", G_CALLBACK (cleanup_object), pl, G_CONNECT_SWAPPED);
filtered = G_LIST_MODEL (gtk_filter_list_model_new (G_LIST_MODEL (store), pl->priv->filter));
sorted = gtk_sort_list_model_new (filtered, NULL);
list = G_LIST_MODEL (gtk_no_selection_new (G_LIST_MODEL (sorted)));
gtk_column_view_set_model (GTK_COLUMN_VIEW (pl->priv->list), list);
gtk_sort_list_model_set_sorter (sorted, gtk_column_view_get_sorter (GTK_COLUMN_VIEW (pl->priv->list)));
gtk_column_view_sort_by_column (GTK_COLUMN_VIEW (pl->priv->list), pl->priv->name, GTK_SORT_ASCENDING);
gtk_widget_show (GTK_WIDGET (pl));
g_object_unref (list);
g_object_unref (sorted);
g_object_unref (filtered);
g_object_unref (store);
return TRUE;
}

View File

@@ -8,87 +8,61 @@
<style>
<class name="view"/>
</style>
<child>
<object class="GtkBox">
<style>
<class name="header"/>
</style>
<child>
<object class="GtkBox" id="name_heading">
<property name="hexpand">0</property>
<child>
<object class="GtkGestureClick">
<signal name="pressed" handler="sort_changed" swapped="no"/>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Name</property>
<property name="xalign">0</property>
<property name="hexpand">1</property>
</object>
</child>
<child>
<object class="GtkImage" id="name_sort_indicator">
<style>
<class name="sort_indicator"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="type_heading">
<property name="label">Type</property>
<property name="xalign">0</property>
</object>
</child>
<child>
<object class="GtkBox" id="origin_heading">
<property name="hexpand">0</property>
<child>
<object class="GtkGestureClick">
<signal name="pressed" handler="sort_changed" swapped="no"/>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Defined at</property>
<property name="xalign">0</property>
<property name="hexpand">1</property>
</object>
</child>
<child>
<object class="GtkImage" id="origin_sort_indicator">
<style>
<class name="sort_indicator"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="value_heading">
<property name="label">Value</property>
<property name="xalign">0</property>
<property name="hexpand">0</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="hscrollbar-policy">never</property>
<child>
<object class="GtkListBox" id="list2">
<object class="GtkColumnView" id="list">
<style>
<class name="list"/>
</style>
<property name="selection-mode">none</property>
<child>
<object class="GtkColumnViewColumn" id="name">
<property name="title" translatable="yes">Name</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_name_cb"/>
<signal name="bind" handler="bind_name_cb"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="type">
<property name="title" translatable="yes">Type</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_type_cb"/>
<signal name="bind" handler="bind_type_cb"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="origin">
<property name="title" translatable="yes">Defined At</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_origin_cb"/>
<signal name="bind" handler="bind_origin_cb"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn">
<property name="title" translatable="yes">Value</property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="bind" handler="bind_value_cb"/>
<signal name="unbind" handler="unbind_value_cb"/>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
@@ -96,28 +70,4 @@
</object>
</child>
</template>
<object class="GtkSizeGroup" id="names">
<property name="mode">horizontal</property>
<widgets>
<widget name="name_heading"/>
</widgets>
</object>
<object class="GtkSizeGroup" id="types">
<property name="mode">horizontal</property>
<widgets>
<widget name="type_heading"/>
</widgets>
</object>
<object class="GtkSizeGroup" id="origins">
<property name="mode">horizontal</property>
<widgets>
<widget name="origin_heading"/>
</widgets>
</object>
<object class="GtkSizeGroup" id="values">
<property name="mode">horizontal</property>
<widgets>
<widget name="value_heading"/>
</widgets>
</object>
</interface>

View File

@@ -0,0 +1,106 @@
#include "resource-holder.h"
struct _ResourceHolder {
GObject instance;
char *name;
char *path;
int count;
gsize size;
GListModel *children;
ResourceHolder *parent;
};
G_DEFINE_TYPE (ResourceHolder, resource_holder, G_TYPE_OBJECT)
static void
resource_holder_init (ResourceHolder *holder)
{
}
static void
resource_holder_finalize (GObject *object)
{
ResourceHolder *holder = RESOURCE_HOLDER (object);
g_free (holder->name);
g_free (holder->path);
g_clear_object (&holder->children);
G_OBJECT_CLASS (resource_holder_parent_class)->finalize (object);
}
static void
resource_holder_class_init (ResourceHolderClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = resource_holder_finalize;
}
ResourceHolder *
resource_holder_new (const char *name,
const char *path,
int count,
gsize size,
GListModel *children)
{
ResourceHolder *holder;
holder = g_object_new (RESOURCE_TYPE_HOLDER, NULL);
holder->name = g_strdup (name);
holder->path = g_strdup (path);
holder->count = count;
holder->size = size;
g_set_object (&holder->children, children);
if (children)
{
int i;
for (i = 0; i < g_list_model_get_n_items (children); i++)
{
ResourceHolder *child = g_list_model_get_item (children, i);
child->parent = holder;
g_object_unref (child);
}
}
return holder;
}
const char *
resource_holder_get_name (ResourceHolder *holder)
{
return holder->name;
}
const char *
resource_holder_get_path (ResourceHolder *holder)
{
return holder->path;
}
int
resource_holder_get_count (ResourceHolder *holder)
{
return holder->count;
}
gsize
resource_holder_get_size (ResourceHolder *holder)
{
return holder->size;
}
GListModel *
resource_holder_get_children (ResourceHolder *holder)
{
return holder->children;
}
ResourceHolder *
resource_holder_get_parent (ResourceHolder *holder)
{
return holder->parent;
}

View File

@@ -0,0 +1,24 @@
#ifndef __RESOURCE_HOLDER_H__
#define __RESOURCE_HOLDER_H__
#include <gtk/gtk.h>
#define RESOURCE_TYPE_HOLDER (resource_holder_get_type ())
G_DECLARE_FINAL_TYPE (ResourceHolder, resource_holder, RESOURCE, HOLDER, GObject)
ResourceHolder * resource_holder_new (const char *name,
const char *path,
int count,
gsize size,
GListModel *children);
const char *resource_holder_get_name (ResourceHolder *holder);
const char *resource_holder_get_path (ResourceHolder *holder);
int resource_holder_get_count (ResourceHolder *holder);
gsize resource_holder_get_size (ResourceHolder *holder);
GListModel *resource_holder_get_children (ResourceHolder *holder);
ResourceHolder *resource_holder_get_parent (ResourceHolder *holder);
#endif /* __RESOURCE_HOLDER_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,9 @@
<object class="GtkSearchEntry" id="search_entry">
<property name="max-width-chars">40</property>
<signal name="search-changed" handler="on_search_changed"/>
<signal name="next-match" handler="next_match"/>
<signal name="previous-match" handler="previous_match"/>
<signal name="stop-search" handler="stop_search"/>
</object>
</child>
<child>
@@ -54,56 +57,45 @@
<property name="vexpand">1</property>
<property name="hscrollbar-policy">never</property>
<child>
<object class="GtkTreeView" id="tree">
<property name="model">model</property>
<property name="enable-search">0</property>
<property name="enable-grid-lines">vertical</property>
<signal name="row-activated" handler="row_activated"/>
<child internal-child="selection">
<object class="GtkTreeSelection">
<signal name="changed" handler="on_selection_changed"/>
</object>
</child>
<object class="GtkColumnView" id="list">
<signal name="activate" handler="on_row_activated"/>
<child>
<object class="GtkTreeViewColumn" id="path_column">
<object class="GtkColumnViewColumn" id="path">
<property name="title" translatable="yes">Path</property>
<property name="resizable">1</property>
<property name="sort-column-id">0</property>
<child>
<object class="GtkCellRendererText">
<property name="scale">0.8</property>
<property name="ellipsize">end</property>
<property name="width-chars">10</property>
<property name="max-width-chars">5</property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_name_cb"/>
<signal name="bind" handler="bind_name_cb"/>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="count_column">
<object class="GtkColumnViewColumn" id="count">
<property name="title" translatable="yes">Count</property>
<property name="resizable">1</property>
<property name="sort-column-id">1</property>
<child>
<object class="GtkCellRendererText" id="count_renderer">
<property name="scale">0.8</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_count_cb"/>
<signal name="bind" handler="bind_count_cb"/>
</object>
</child>
</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="size_column">
<object class="GtkColumnViewColumn" id="size">
<property name="title" translatable="yes">Size</property>
<property name="resizable">1</property>
<property name="sort-column-id">2</property>
<child>
<object class="GtkCellRendererText" id="size_renderer">
<property name="scale">0.8</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup_size_cb"/>
<signal name="bind" handler="bind_size_cb"/>
</object>
</child>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="filler">
<property name="expand">1</property>
</object>
</child>
</object>

View File

@@ -135,6 +135,7 @@ gtk_private_sources = files([
'gtkscaler.c',
'gtksearchengine.c',
'gtksearchenginemodel.c',
'gtkset.c',
'gtksizerequestcache.c',
'gtkstyleanimation.c',
'gtkstylecascade.c',
@@ -303,6 +304,7 @@ gtk_public_sources = files([
'gtkmodules.c',
'gtkmountoperation.c',
'gtkmultifilter.c',
'gtkmultiselection.c',
'gtkmultisorter.c',
'gtknativedialog.c',
'gtknomediafile.c',
@@ -577,6 +579,7 @@ gtk_public_headers = files([
'gtkmessagedialog.h',
'gtkmountoperation.h',
'gtkmultifilter.h',
'gtkmultiselection.h',
'gtkmultisorter.h',
'gtknative.h',
'gtknativedialog.h',

View File

@@ -17,19 +17,6 @@
<column type="gboolean"/>
</columns>
</object>
<object class="GtkListStore" id="printer_list">
<columns>
<column type="GIcon"/>
<column type="gchararray"/>
<column type="gchararray"/>
<column type="gint"/>
<column type="gchararray"/>
<column type="GObject"/>
</columns>
</object>
<object class="GtkTreeModelFilter" id="printer_list_filter">
<property name="child-model">printer_list</property>
</object>
<object class="GtkAdjustment" id="scale_spin_adjustment">
<property name="lower">1</property>
<property name="upper">1000</property>
@@ -72,58 +59,131 @@
<property name="has-frame">1</property>
<property name="vexpand">1</property>
<child>
<object class="GtkTreeView" id="printer_treeview">
<property name="model">printer_list_filter</property>
<signal name="row-activated" handler="emit_ok_response" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection1">
<property name="mode">browse</property>
<signal name="changed" handler="selected_printer_changed" swapped="no"/>
</object>
</child>
<object class="GtkColumnView" id="printer_list">
<property name="can-focus">1</property>
<property name="halign">fill</property>
<child>
<object class="GtkTreeViewColumn" id="printer_icon_column">
<child>
<object class="GtkCellRendererPixbuf" id="printer_icon_renderer"/>
<attributes>
<attribute name="gicon">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="printer_name_column">
<property name="title" translatable="yes">Printer</property>
<child>
<object class="GtkCellRendererText" id="printer_name_renderer"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="printer_location_column">
<property name="title" translatable="yes" comments="this is the header for the location column in the print dialog">Location</property>
<child>
<object class="GtkCellRendererText" id="printer_location_renderer"/>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="printer_status_column">
<property name="title" translatable="yes" comments="this is the header for the printer status column in the print dialog">Status</property>
<child>
<object class="GtkCellRendererText" id="printer_status_renderer">
<property name="ellipsize">end</property>
<object class="GtkColumnViewColumn">
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkImage">
<binding name="icon-name">
<lookup name="icon-name" type="GtkPrinter">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
<binding name="sensitive">
<lookup name="accepting-jobs" type="GtkPrinter">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn">
<property name="title" translatable="yes">Name</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="name" type="GtkPrinter">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
<binding name="sensitive">
<lookup name="accepting-jobs" type="GtkPrinter">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn">
<property name="title" translatable="yes" comments="this is the header for the location column in the print dialog">Location</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="location" type="GtkPrinter">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
<binding name="sensitive">
<lookup name="accepting-jobs" type="GtkPrinter">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn">
<property name="title" translatable="yes" comments="this is the header for the printer status column in the print dialog">Status</property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<property name="ellipsize">end</property>
<binding name="label">
<lookup name="state-message" type="GtkPrinter">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
<binding name="sensitive">
<lookup name="accepting-jobs" type="GtkPrinter">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
</object>

View File

@@ -488,8 +488,9 @@ const char *ui_file =
"<interface>\n" \
" <template class='GtkListItem'>\n" \
" <property name='child'>\n" \
" <object class='GtkCheckButton'>\n" \
" <binding name='active'>\n" \
" <object class='GtkImage'>\n" \
" <property name='icon-name'>object-select-symbolic</property>\n" \
" <binding name='visible'>\n" \
" <closure type='gboolean' function='get_boolean'>\n" \
" <lookup name='item' type='GtkTreeListRow'><lookup name='item'>GtkListItem</lookup></lookup>\n" \
" <constant type='gchararray'>" attr "</constant>" \
@@ -606,12 +607,13 @@ struct {
#define G_FILE_ATTRIBUTE_RECENT_MODIFIED "recent::modified" /* int64 (time_t) */
#endif
const char *factory_ui =
const char *factory_ui_name =
"<?xml version='1.0' encoding='UTF-8'?>\n"
"<interface>\n"
" <template class='GtkListItem'>\n"
" <property name='child'>\n"
" <object class='GtkLabel'>\n"
" <property name='xalign'>0</property>\n"
" <binding name='label'>\n"
" <lookup name='title' type='GtkColumnViewColumn'>\n"
" <lookup name='item'>GtkListItem</lookup>\n"
@@ -622,6 +624,124 @@ const char *factory_ui =
" </template>\n"
"</interface>\n";
const char *factory_ui_visible =
"<?xml version='1.0' encoding='UTF-8'?>\n"
"<interface>\n"
" <template class='GtkListItem'>\n"
" <property name='child'>\n"
" <object class='GtkCheckButton'>\n"
" <signal name='toggled' handler='column_visible_toggled' object='GtkListItem'/>\n"
" <binding name='active'>\n"
" <lookup name='visible' type='GtkColumnViewColumn'>\n"
" <lookup name='item'>GtkListItem</lookup>\n"
" </lookup>\n"
" </binding>\n"
" </object>\n"
" </property>\n"
" </template>\n"
"</interface>\n";
const char *factory_ui_resizable =
"<?xml version='1.0' encoding='UTF-8'?>\n"
"<interface>\n"
" <template class='GtkListItem'>\n"
" <property name='child'>\n"
" <object class='GtkCheckButton'>\n"
" <signal name='toggled' handler='column_resizable_toggled' object='GtkListItem'/>\n"
" <binding name='active'>\n"
" <lookup name='resizable' type='GtkColumnViewColumn'>\n"
" <lookup name='item'>GtkListItem</lookup>\n"
" </lookup>\n"
" </binding>\n"
" </object>\n"
" </property>\n"
" </template>\n"
"</interface>\n";
const char *factory_ui_reorderable =
"<?xml version='1.0' encoding='UTF-8'?>\n"
"<interface>\n"
" <template class='GtkListItem'>\n"
" <property name='child'>\n"
" <object class='GtkCheckButton'>\n"
" <signal name='toggled' handler='column_reorderable_toggled' object='GtkListItem'/>\n"
" <binding name='active'>\n"
" <lookup name='reorderable' type='GtkColumnViewColumn'>\n"
" <lookup name='item'>GtkListItem</lookup>\n"
" </lookup>\n"
" </binding>\n"
" </object>\n"
" </property>\n"
" </template>\n"
"</interface>\n";
const char *factory_ui_width =
"<?xml version='1.0' encoding='UTF-8'?>\n"
"<interface>\n"
" <template class='GtkListItem'>\n"
" <property name='child'>\n"
" <object class='GtkSpinButton'>\n"
" <property name='digits'>0</property>\n"
" <property name='numeric'>1</property>\n"
" <property name='adjustment'>\n"
" <object class='GtkAdjustment'>\n"
" <property name='lower'>-1</property>\n"
" <property name='upper'>10000</property>\n"
" <property name='step-increment'>1</property>\n"
" <property name='page-increment'>10</property>\n"
" </object>\n"
" </property>\n"
" <signal name='value-changed' handler='column_width_changed' object='GtkListItem'/>\n"
" <binding name='value'>\n"
" <closure type='gdouble' function='cast_to_double'>\n"
" <lookup name='fixed-width' type='GtkColumnViewColumn'>\n"
" <lookup name='item'>GtkListItem</lookup>\n"
" </lookup>\n"
" </closure>\n"
" </binding>\n"
" </object>\n"
" </property>\n"
" </template>\n"
"</interface>\n";
static void
column_visible_toggled (GtkListItem *item, GtkToggleButton *button)
{
GtkColumnViewColumn *column = gtk_list_item_get_item (item);
gtk_column_view_column_set_visible (column, gtk_toggle_button_get_active (button));
}
static void
column_resizable_toggled (GtkListItem *item, GtkToggleButton *button)
{
GtkColumnViewColumn *column = gtk_list_item_get_item (item);
gtk_column_view_column_set_resizable (column, gtk_toggle_button_get_active (button));
}
static void
column_reorderable_toggled (GtkListItem *item, GtkToggleButton *button)
{
GtkColumnViewColumn *column = gtk_list_item_get_item (item);
gtk_column_view_column_set_reorderable (column, gtk_toggle_button_get_active (button));
}
static void
column_width_changed (GtkListItem *item, GtkSpinButton *button)
{
GtkColumnViewColumn *column = gtk_list_item_get_item (item);
gtk_column_view_column_set_fixed_width (column, gtk_spin_button_get_value_as_int (button));
}
static double
cast_to_double (gpointer _this, int value)
{
return (double)value;
}
static GtkBuilderScope *
create_scope (void)
{
@@ -634,6 +754,11 @@ create_scope (void)
ADD_SYMBOL (get_object);
ADD_SYMBOL (get_string);
ADD_SYMBOL (get_boolean);
ADD_SYMBOL (column_visible_toggled);
ADD_SYMBOL (column_resizable_toggled);
ADD_SYMBOL (column_reorderable_toggled);
ADD_SYMBOL (column_width_changed);
ADD_SYMBOL (cast_to_double);
return scope;
#undef ADD_SYMBOL
@@ -668,11 +793,60 @@ search_changed_cb (GtkSearchEntry *entry,
gtk_filter_changed (custom_filter, GTK_FILTER_CHANGE_DIFFERENT);
}
static void
move_column (GtkColumnView *list, gboolean down)
{
GListModel *columns;
guint position;
GtkColumnViewColumn *selected;
GtkColumnView *view;
columns = gtk_column_view_get_model (list);
position = gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (columns));
selected = gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (columns));
view = gtk_column_view_column_get_column_view (selected);
if (down && position + 1 < g_list_model_get_n_items (columns))
position++;
else if (!down && position > 0)
position--;
else
return;
gtk_column_view_insert_column (view, position, selected);
gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (columns), position);
gtk_widget_grab_focus (GTK_WIDGET (list));
}
static gboolean
key_pressed_cb (GtkEventControllerKey *controller,
guint keyval,
guint keycode,
GdkModifierType modifiers,
GtkColumnView *list)
{
gboolean down;
if ((modifiers & GDK_CONTROL_MASK) == 0)
return FALSE;
if (keyval == GDK_KEY_Down)
down = TRUE;
else if (keyval == GDK_KEY_Up)
down = FALSE;
else
return FALSE;
move_column (list, down);
return TRUE;
}
int
main (int argc, char *argv[])
{
GListModel *toplevels;
GtkWidget *win, *hbox, *vbox, *sw, *view, *list, *search_entry, *statusbar;
GtkWidget *win, *paned, *vbox, *sw, *view, *list, *search_entry, *statusbar;
GListModel *dirmodel;
GtkTreeListModel *tree;
GtkFilterListModel *filter;
@@ -683,17 +857,21 @@ main (int argc, char *argv[])
GtkBuilderScope *scope;
GtkBuilder *builder;
GError *error = NULL;
GtkColumnViewColumn *column;
GListModel *model;
GtkEventController *controller;
gtk_init ();
win = gtk_window_new ();
gtk_window_set_default_size (GTK_WINDOW (win), 800, 600);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_window_set_child (GTK_WINDOW (win), hbox);
paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
gtk_paned_set_wide_handle (GTK_PANED (paned), TRUE);
gtk_window_set_child (GTK_WINDOW (win), paned);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_box_append (GTK_BOX (hbox), vbox);
gtk_paned_set_start_child (GTK_PANED (paned), vbox);
search_entry = gtk_search_entry_new ();
gtk_box_append (GTK_BOX (vbox), search_entry);
@@ -750,13 +928,58 @@ main (int argc, char *argv[])
g_object_unref (sort);
g_object_unref (tree);
list = gtk_list_view_new_with_factory (
gtk_builder_list_item_factory_new_from_bytes (scope, g_bytes_new_static (factory_ui, strlen (factory_ui))));
gtk_list_view_set_model (GTK_LIST_VIEW (list), gtk_column_view_get_columns (GTK_COLUMN_VIEW (view)));
gtk_box_append (GTK_BOX (hbox), list);
list = gtk_column_view_new ();
column = gtk_column_view_column_new_with_factory ("Name",
gtk_builder_list_item_factory_new_from_bytes (scope, g_bytes_new_static (factory_ui_name, strlen (factory_ui_name))));
gtk_column_view_column_set_resizable (column, FALSE);
gtk_column_view_column_set_reorderable (column, FALSE);
gtk_column_view_append_column (GTK_COLUMN_VIEW (list), column);
g_object_unref (column);
column = gtk_column_view_column_new_with_factory ("Visible",
gtk_builder_list_item_factory_new_from_bytes (scope, g_bytes_new_static (factory_ui_visible, strlen (factory_ui_visible))));
gtk_column_view_column_set_resizable (column, FALSE);
gtk_column_view_column_set_reorderable (column, FALSE);
gtk_column_view_append_column (GTK_COLUMN_VIEW (list), column);
g_object_unref (column);
column = gtk_column_view_column_new_with_factory ("Resizable",
gtk_builder_list_item_factory_new_from_bytes (scope, g_bytes_new_static (factory_ui_resizable, strlen (factory_ui_resizable))));
gtk_column_view_column_set_resizable (column, FALSE);
gtk_column_view_column_set_reorderable (column, FALSE);
gtk_column_view_append_column (GTK_COLUMN_VIEW (list), column);
g_object_unref (column);
column = gtk_column_view_column_new_with_factory ("Reorderable",
gtk_builder_list_item_factory_new_from_bytes (scope, g_bytes_new_static (factory_ui_reorderable, strlen (factory_ui_reorderable))));
gtk_column_view_column_set_resizable (column, FALSE);
gtk_column_view_column_set_reorderable (column, FALSE);
gtk_column_view_append_column (GTK_COLUMN_VIEW (list), column);
g_object_unref (column);
column = gtk_column_view_column_new_with_factory ("Width",
gtk_builder_list_item_factory_new_from_bytes (scope, g_bytes_new_static (factory_ui_width, strlen (factory_ui_width))));
gtk_column_view_column_set_resizable (column, FALSE);
gtk_column_view_column_set_reorderable (column, FALSE);
gtk_column_view_append_column (GTK_COLUMN_VIEW (list), column);
g_object_unref (column);
model = G_LIST_MODEL (gtk_single_selection_new (gtk_column_view_get_columns (GTK_COLUMN_VIEW (view))));
gtk_column_view_set_model (GTK_COLUMN_VIEW (list), model);
g_object_unref (model);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list);
gtk_paned_set_end_child (GTK_PANED (paned), sw);
g_object_unref (scope);
controller = gtk_event_controller_key_new ();
g_signal_connect (controller, "key-pressed", G_CALLBACK (key_pressed_cb), list);
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
gtk_widget_add_controller (list, controller);
gtk_widget_show (win);
toplevels = gtk_window_get_toplevels ();

View File

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

View File

@@ -0,0 +1,393 @@
/*
* Copyright (C) 2019, Red Hat, Inc.
* Authors: Matthias Clasen <mclasen@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <locale.h>
#include <gtk/gtk.h>
static GQuark number_quark;
static GQuark changes_quark;
static GQuark selection_quark;
static guint
get (GListModel *model,
guint position)
{
GObject *object = g_list_model_get_item (model, position);
g_assert (object != NULL);
return GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark));
}
static char *
model_to_string (GListModel *model)
{
GString *string = g_string_new (NULL);
guint i;
for (i = 0; i < g_list_model_get_n_items (model); i++)
{
if (i > 0)
g_string_append (string, " ");
g_string_append_printf (string, "%u", get (model, i));
}
return g_string_free (string, FALSE);
}
static char *
selection_to_string (GListModel *model)
{
GString *string = g_string_new (NULL);
guint i;
for (i = 0; i < g_list_model_get_n_items (model); i++)
{
if (!gtk_selection_model_is_selected (GTK_SELECTION_MODEL (model), i))
continue;
if (string->len > 0)
g_string_append (string, " ");
g_string_append_printf (string, "%u", get (model, i));
}
return g_string_free (string, FALSE);
}
static GListStore *
new_store (guint start,
guint end,
guint step);
static GObject *
make_object (guint number)
{
GObject *object;
/* 0 cannot be differentiated from NULL, so don't use it */
g_assert (number != 0);
object = g_object_new (G_TYPE_OBJECT, NULL);
g_object_set_qdata (object, number_quark, GUINT_TO_POINTER (number));
return object;
}
static void
splice (GListStore *store,
guint pos,
guint removed,
guint *numbers,
guint added)
{
GObject **objects;
guint i;
objects = g_new0 (GObject *, added);
for (i = 0; i < added; i++)
objects[i] = make_object (numbers[i]);
g_list_store_splice (store, pos, removed, (gpointer *) objects, added);
for (i = 0; i < added; i++)
g_object_unref (objects[i]);
g_free (objects);
}
static void
add (GListStore *store,
guint number)
{
GObject *object = make_object (number);
g_list_store_append (store, object);
g_object_unref (object);
}
static void
insert (GListStore *store,
guint position,
guint number)
{
GObject *object = make_object (number);
g_list_store_insert (store, position, object);
g_object_unref (object);
}
#define assert_model(model, expected) G_STMT_START{ \
char *s = model_to_string (G_LIST_MODEL (model)); \
if (!g_str_equal (s, expected)) \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#model " == " #expected, s, "==", expected); \
g_free (s); \
}G_STMT_END
#define ignore_changes(model) G_STMT_START{ \
GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
g_string_set_size (changes, 0); \
}G_STMT_END
#define assert_changes(model, expected) G_STMT_START{ \
GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
if (!g_str_equal (changes->str, expected)) \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#model " == " #expected, changes->str, "==", expected); \
g_string_set_size (changes, 0); \
}G_STMT_END
#define assert_selection(model, expected) G_STMT_START{ \
char *s = selection_to_string (G_LIST_MODEL (model)); \
if (!g_str_equal (s, expected)) \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#model " == " #expected, s, "==", expected); \
g_free (s); \
}G_STMT_END
#define ignore_selection_changes(model) G_STMT_START{ \
GString *changes = g_object_get_qdata (G_OBJECT (model), selection_quark); \
g_string_set_size (changes, 0); \
}G_STMT_END
#define assert_selection_changes(model, expected) G_STMT_START{ \
GString *changes = g_object_get_qdata (G_OBJECT (model), selection_quark); \
if (!g_str_equal (changes->str, expected)) \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#model " == " #expected, changes->str, "==", expected); \
g_string_set_size (changes, 0); \
}G_STMT_END
static GListStore *
new_empty_store (void)
{
return g_list_store_new (G_TYPE_OBJECT);
}
static GListStore *
new_store (guint start,
guint end,
guint step)
{
GListStore *store = new_empty_store ();
guint i;
for (i = start; i <= end; i += step)
add (store, i);
return store;
}
static void
items_changed (GListModel *model,
guint position,
guint removed,
guint added,
GString *changes)
{
g_assert (removed != 0 || added != 0);
if (changes->len)
g_string_append (changes, ", ");
if (removed == 1 && added == 0)
{
g_string_append_printf (changes, "-%u", position);
}
else if (removed == 0 && added == 1)
{
g_string_append_printf (changes, "+%u", position);
}
else
{
g_string_append_printf (changes, "%u", position);
if (removed > 0)
g_string_append_printf (changes, "-%u", removed);
if (added > 0)
g_string_append_printf (changes, "+%u", added);
}
}
static void
selection_changed (GListModel *model,
guint position,
guint n_items,
GString *changes)
{
if (changes->len)
g_string_append (changes, ", ");
g_string_append_printf (changes, "%u:%u", position, n_items);
}
static void
free_changes (gpointer data)
{
GString *changes = data;
/* all changes must have been checked via assert_changes() before */
g_assert_cmpstr (changes->str, ==, "");
g_string_free (changes, TRUE);
}
static GtkSelectionModel *
new_model (GListStore *store)
{
GtkSelectionModel *result;
GString *changes;
result = GTK_SELECTION_MODEL (gtk_multi_selection_new (G_LIST_MODEL (store)));
changes = g_string_new ("");
g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes);
g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes);
changes = g_string_new ("");
g_object_set_qdata_full (G_OBJECT(result), selection_quark, changes, free_changes);
g_signal_connect (result, "selection-changed", G_CALLBACK (selection_changed), changes);
return result;
}
static void
test_create (void)
{
GtkSelectionModel *selection;
GListStore *store;
store = new_store (1, 5, 2);
selection = new_model (store);
assert_model (selection, "1 3 5");
assert_changes (selection, "");
assert_selection (selection, "");
assert_selection_changes (selection, "");
g_object_unref (store);
assert_model (selection, "1 3 5");
assert_changes (selection, "");
assert_selection (selection, "");
assert_selection_changes (selection, "");
g_object_unref (selection);
}
static void
test_changes (void)
{
GtkSelectionModel *selection;
GListStore *store;
store = new_store (1, 5, 1);
selection = new_model (store);
assert_model (selection, "1 2 3 4 5");
assert_changes (selection, "");
assert_selection (selection, "");
assert_selection_changes (selection, "");
g_list_store_remove (store, 3);
assert_model (selection, "1 2 3 5");
assert_changes (selection, "-3");
assert_selection (selection, "");
assert_selection_changes (selection, "");
insert (store, 3, 99);
assert_model (selection, "1 2 3 99 5");
assert_changes (selection, "+3");
assert_selection (selection, "");
assert_selection_changes (selection, "");
splice (store, 3, 2, (guint[]) { 97 }, 1);
assert_model (selection, "1 2 3 97");
assert_changes (selection, "3-2+1");
assert_selection (selection, "");
assert_selection_changes (selection, "");
g_object_unref (store);
g_object_unref (selection);
}
static void
test_selection (void)
{
GtkSelectionModel *selection;
GListStore *store;
gboolean ret;
store = new_store (1, 5, 1);
selection = new_model (store);
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_item (selection, 3, FALSE);
g_assert_true (ret);
assert_selection (selection, "4");
assert_selection_changes (selection, "3:1");
ret = gtk_selection_model_unselect_item (selection, 3);
g_assert_true (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "3:1");
ret = gtk_selection_model_select_item (selection, 1, FALSE);
g_assert_true (ret);
assert_selection (selection, "2");
assert_selection_changes (selection, "1:1");
ret = gtk_selection_model_select_range (selection, 3, 2, FALSE);
g_assert_true (ret);
assert_selection (selection, "2 4 5");
assert_selection_changes (selection, "3:2");
ret = gtk_selection_model_unselect_range (selection, 3, 2);
g_assert_true (ret);
assert_selection (selection, "2");
assert_selection_changes (selection, "3:2");
ret = gtk_selection_model_select_all (selection);
g_assert_true (ret);
assert_selection (selection, "1 2 3 4 5");
assert_selection_changes (selection, "0:5");
ret = gtk_selection_model_unselect_all (selection);
g_assert_true (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "0:5");
g_object_unref (store);
g_object_unref (selection);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
setlocale (LC_ALL, "C");
g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=%s");
number_quark = g_quark_from_static_string ("Hell and fire was spawned to be released.");
changes_quark = g_quark_from_static_string ("What did I see? Can I believe what I saw?");
selection_quark = g_quark_from_static_string ("Mana mana, badibidibi");
g_test_add_func ("/multiselection/create", test_create);
#if GLIB_CHECK_VERSION (2, 58, 0) /* g_list_store_splice() is broken before 2.58 */
g_test_add_func ("/multiselection/changes", test_changes);
#endif
g_test_add_func ("/multiselection/selection", test_selection);
return g_test_run ();
}

View File

@@ -642,6 +642,49 @@ test_query_range (void)
g_object_unref (selection);
}
static void
selection_changed_cb (GtkSelectionModel *model,
guint position,
guint n_items,
gpointer data)
{
int *counter = data;
(*counter)++;
}
/* Test that the selection emits selection-changed when an item
* is autoselected due to the underlying store going from empty
* to non-empty.
*/
static void
test_empty (void)
{
GListStore *store;
GtkSingleSelection *selection;
int counter;
GtkFilter *filter;
store = g_list_store_new (GTK_TYPE_FILTER);
counter = 0;
selection = gtk_single_selection_new (G_LIST_MODEL (store));
g_signal_connect (selection, "selection-changed", G_CALLBACK (selection_changed_cb), &counter);
g_assert_cmpint (gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (selection)), ==, GTK_INVALID_LIST_POSITION);
filter = gtk_string_filter_new ();
g_list_store_append (store, filter);
g_object_unref (filter);
g_assert_cmpint (gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (selection)), ==, 0);
g_assert_cmpint (counter, ==, 1);
g_object_unref (selection);
g_object_unref (store);
}
int
main (int argc, char *argv[])
{
@@ -660,6 +703,7 @@ main (int argc, char *argv[])
g_test_add_func ("/singleselection/persistence", test_persistence);
g_test_add_func ("/singleselection/query-range", test_query_range);
g_test_add_func ("/singleselection/changes", test_changes);
g_test_add_func ("/singleselection/empty", test_empty);
return g_test_run ();
}