Compare commits

...

53 Commits

Author SHA1 Message Date
Matthias Clasen
8833158176 Redo the sorting in testlistview
Sort the entire treelistmodel, not individual
directories. This lets us change sort order and
(almost) get working persistence for selection.
2019-01-06 21:25:13 -05:00
Matthias Clasen
964e5a9fe2 typo fix 2019-01-06 20:29:37 -05:00
Matthias Clasen
615337863b testsuite: Add selection model tests 2019-01-06 20:27:27 -05:00
Matthias Clasen
437fa857f4 multi-selection: Implement persistence
This is not very efficient for Ctrl-A.
No way around it.
2019-01-06 20:27:27 -05:00
Matthias Clasen
9757582e8d set: Add an iter 2019-01-06 20:27:27 -05:00
Matthias Clasen
21ccf55aed set: Fix gtk_set_remove_range
This was showing up in obvious test cases such as
gtk_set_add_range (set, 3, 1);
gtk_set_remove_range (set, 3, 1);
not yielding an empty set.
2019-01-06 20:27:27 -05:00
Matthias Clasen
62fce3bdf5 Remove debug spew 2019-01-06 17:45:18 -05:00
Matthias Clasen
30db87b120 Emit selection-changed for autoselection
This was missed in the case that the child model
goes from empty to non-empty.
2019-01-06 17:45:18 -05:00
Matthias Clasen
9e2eb047d0 Restrict splice tests to new-enough GLib
Copied from the sort list model tests.
2019-01-06 12:28:10 -05:00
Matthias Clasen
a68ee2d4f1 testsuite: Add slice model tests
Do the same style of tests we do for all the
other list models. These tests found the issue
fixed in the previous commit.
2019-01-06 12:26:37 -05:00
Matthias Clasen
ce1ac00c59 slice model: Don't report excessive changes
We were not skipping an unchanged initial segment
of the slice in some cases. Fix that.
2019-01-06 12:24:57 -05:00
Matthias Clasen
75a1bafcc0 single selection: Be robust
Make gtk_single_selection_set_selected work without
a model. It gets called a internally, and that might
just happen when there's no model.
2019-01-06 10:52:20 -05:00
Matthias Clasen
c70130edc5 Drop unused variables 2019-01-06 10:52:20 -05:00
Matthias Clasen
596b9202ce Don't warn when setting properties to their default value
That makes our tests choke, and is not really necessary.
2019-01-06 10:52:20 -05:00
Matthias Clasen
c8f27d1ac6 Add missing headers to the public-headers list
Without this, the types don't end up in gtk_test_list_all_types,
and then tests like the defaultvalue test don't check them.
2019-01-06 10:52:07 -05:00
Matthias Clasen
a0f8bf0f6b testlistview: Allow changing sort order
This is a good test case for persistence of selection.
2019-01-05 22:52:34 -05:00
Matthias Clasen
d0ef5f5db4 selection: Make the model an interface property
This is in line with what we do for other wrapper models.
2019-01-05 22:52:34 -05:00
Matthias Clasen
dfd280acd0 gtk-demo: Add a listview demo
Duplicate the listbox demo with GtkListView,
and give both demos a "more messages" button,
to demonstrate scalability.
2019-01-05 21:13:26 -05:00
Matthias Clasen
c97f62a471 Fix up selection constructor return types
Follow the pattern we use for the other list model
types, and return the exact type.
2019-01-05 18:54:05 -05:00
Matthias Clasen
135a875c25 Add a no selection
This is a GtkSelectionModel that never selects anything.

The equivalent of GTK_SELECTION_MODE_NONE.
2019-01-05 17:47:39 -05:00
Matthias Clasen
09cbb5154f font chooser: Add back row activation
This one is easy and doesn't need any list view features.
2019-01-05 14:53:27 -05:00
Matthias Clasen
9e2250e35d list view: Be more careful with selection
I've seen crashes in the font chooser with
the selection-changed signal getting emitted
early, and row being NULL.
2019-01-05 14:38:20 -05:00
Matthias Clasen
d844c98c8a Fix an accidental change
This was just to test multiselection locally.
2019-01-05 14:37:59 -05:00
Matthias Clasen
779fb56213 font chooser: Port to GtkListView
This works, as far as the required features are present
in GtkListview yet. Still missing:
- keynav
- row activation
- scroll to selection
2019-01-05 14:36:31 -05:00
Matthias Clasen
ad25afc298 typo fix 2019-01-05 09:06:25 -05:00
Matthias Clasen
18d7a7daaa gtk: Add GtkMultiSelection
This is a selection modelt that allows selecting
more than one item.

The behavior is not 100% right yet for extending
selections, but it basically works.
2019-01-05 08:49:23 -05:00
Benjamin Otte
be4fe26fef listitem: Add a press gesture to select the item
This requires a bunch of refactoring because so far the ListItem had no
knowledge of the manager, but it needs the manager so it can access the
selection to trigger the select/unselect operation.
2019-01-05 00:30:22 +01:00
Benjamin Otte
46fa122c98 listview: Add initial support for displaying selections 2019-01-05 00:30:22 +01:00
Benjamin Otte
2b780de342 gtk: Add GtkSingleSelection
GtkSingleSelection is a GtkSelectionModel that allows selecting a single
item.
2019-01-05 00:30:22 +01:00
Benjamin Otte
ec0483bdfe gtk: Add GtkSelectionModel
The selection model is a list model interface that takes care of
selections and is to be used by the list model widgets to manage their
selections.
2019-01-05 00:30:22 +01:00
Benjamin Otte
1547d81eb9 listview: Reset listitems' CSS animations when rebinding
This way, newly displayed rows don't play an unselect animation (text
fading in) when they are unselected, but the row was previously used for
a selected item.
2019-01-05 00:30:22 +01:00
Benjamin Otte
498351784c listview: Add selection properties to ListItem
This just brings the infrastructure into place, we're not using the
properties yet.
2019-01-05 00:30:22 +01:00
Benjamin Otte
1f07f9bbae listview: Try to keep the list items in order when scrolling
Instead of just destroying all items and then recreating them (or even
hide()ing and then show()ing them again (or even even repositioning
them in the widget tree)), just try to reust them in the order they are.

This works surprisingly well when scrolling and most/all widgets
just moved.
2019-01-05 00:30:22 +01:00
Benjamin Otte
aa71d8fc51 listlistmodel: Add gtk_list_list_model_item_moved()
Use it to fix a case that just said g_warning ("oops").

Apparently I had forgotten the case where a container moved a child
in the widget tree.
2019-01-05 00:30:22 +01:00
Benjamin Otte
44e4ddfdf0 listitemmanager: Switch from "insert_before" to "insert_after" argumnet
We reorder widgets start to end, so when reusing a list item, we
correctly know the previous sibling for that list item, but not the
next sibling yet. We just know the widget it should ultimately be in
front of.
So we can do a more correct guess of the list item's place in the widget
tree if we think about where to place an item like this.

Actually using this change will come in the next commit.
2019-01-05 00:30:22 +01:00
Benjamin Otte
3481dddbc2 testlistview: Create widgets only once
Previously, we were recreating all widgets every time the list item was
rebound, which caused a lot of extra work every time we scrolled.

Now we keep the widgets around and only set their properties again when
the item changes.
2019-01-05 00:30:22 +01:00
Benjamin Otte
841d6cb327 testlistview: Show the row number
Always show the current row. This is mostly useful for debugging, not
for beauty.
2019-01-05 00:30:22 +01:00
Benjamin Otte
b21fd5fd22 listview: Only allocate necesary rows
This is the big one.

The listview only allocates 200 rows around the visible row now.
Everything else is kept in ListRow instances with row->widget == NULL.

For rows without a widget, we assign the median height of the child
widgets as the row's height and then do all calculations as if there
were widgets that had requested that height (like setting adjustment
values or reacting to adjustment value changes).

When the view is scrolled, we bind the 200 rows to the new visible area,
so that the part of the listview that can be seen is always allocated.
2019-01-05 00:30:20 +01:00
Benjamin Otte
660a6f4e8e listview: Change anchor handling again
The anchor is now a tuple of { listitem, align }.

Using the actual list item allows keeping the anchor across changes
in position (ie when lists get resorted) while still being able to fall
back to positions (list items store their position) when an item gets
removed.

The align value is in the range [0..1] and defines where in the visible
area to do the alignment.
0.0 means to align the top of the row with the top of the visible area,
1.0 aligns the bottom of the widget with the visible area and 0.5 keeps
the center of the widget at the center of the visible area.
It works conceptually the same as percentages in CSS background-position
(where the background area and the background image's size are matched
the same way) or CSS transform-origin.
2019-01-05 00:24:55 +01:00
Benjamin Otte
72902be840 listview: Change how binding is done
We now don't let the functions create widgets for the item from the
listmodel, instead we hand out a GtkListItem for them to add a widget
to.

GtkListItems are created in advance and can only be filled in by the
binding code by gtk_container_add()ing a widget.
However, they are GObjects, so they can provide properties that the
binding code can make use of - either via notify signals or GBinding.
2019-01-05 00:24:55 +01:00
Benjamin Otte
2a7746dba6 listitem: Add gtk_list_item_get_position()
Also refactor the whole list item management yet again.

Now, list item APIs doesn't have bind/unbind functions anymore, but only
property setters.

The item factory is the only one doing the binding.
As before, the item manager manages when items need to be bound.
2019-01-05 00:24:55 +01:00
Benjamin Otte
badad04eb7 tests: Make animating listview do random resorts 2019-01-05 00:24:55 +01:00
Benjamin Otte
81690c7049 listview: Change change management
Add a GtkListItemManagerChange object that tracks all removed list
rows during an item-changed signal so they can be added back later.
2019-01-05 00:24:55 +01:00
Benjamin Otte
ae1331ee92 listview: Make the listitemmanager stricter
Require that items created with the manager get destroyed via the
manager.

To that purpose, renamed create_list_item() to acquire_list_item() and
add a matching release_list_item() function.

This way, the manager can in the future keep track of all items and
cache information about them.
2019-01-05 00:24:55 +01:00
Benjamin Otte
e2354e5f3b listview: Add GtkListItem
GtkListItem is a generic row widget that is supposed to replace
GtkListBoxRow and GtkFlowBoxChild.
2019-01-05 00:24:55 +01:00
Benjamin Otte
2d472da14b listview: Add GtkListItemManager
It's all stubs for now, but here's the basic ideas about what
this object is supposed to do:

(1) It's supposed to be handling all the child GtkWidgets that are
    used by the listview, so that the listview can concern
    itself with how many items it needs and where to put them.
(2) It's meant to do the caching of widgets that are not (currently)
    used.
(3) It's meant to track items that remain in the model across
    items-changed emissions and just change position.
(2) It's code that can be shared between listview and potential
    other widgets like a GridView.

It's also free to assume that the number of items it's supposed to
manage doesn't grow too much, so it's free to use O(N) algorithms.
2019-01-05 00:24:55 +01:00
Benjamin Otte
7edeef2f71 listview: Implement an anchor
The anchor selection is very basic: just anchor the top row.

That's vastly better than any other widget already though.
2019-01-05 00:24:55 +01:00
Benjamin Otte
07ec4f9de0 tests: Add a test for a permanently changing listview
This is mostly for dealing with proper anchoring and can be used to
check that things don't scroll or that selection and focus handling
properly works.

For comparison purposes, a ListBox is provided next to it.
2019-01-05 00:24:55 +01:00
Benjamin Otte
b00b9500e7 listview: Implement GtkScrollable
Scrolling in a very basic form is also supported
2019-01-05 00:24:53 +01:00
Benjamin Otte
d7e64c4423 listview: Make widget actually do something
The thing we're actually doing is create and maintain a widget for every
row. That's it.

Also add a testcase using this. The testcase quickly allocates too many
rows though and then becomes unresponsive though. You have been warned.
2019-01-05 00:13:20 +01:00
Benjamin Otte
5596063220 listview: Introduce GtkListItemFactory
Thisis the abstraction I intend to use for creating widgets and binding
them to the item out of the listview.

For now this is a very dumb wrapper around the functions that exist in
the API.

But it leaves the freedom to turn this into public API, make an
interface out of it and most of all write different implementations, in
particular one that uses GtkBuilder.
2019-01-04 23:57:45 +01:00
Benjamin Otte
3485a4582c gtk: Add a GtkListView skeleton 2019-01-04 23:57:45 +01:00
Benjamin Otte
f1c34aefe0 main: Report correct target for button release events
Button release events should not go to the widget below the pointer, but
to the widget that received the original button press.

Fixes #24
2019-01-04 23:57:45 +01:00
45 changed files with 8330 additions and 560 deletions

View File

@@ -179,6 +179,7 @@
<file>links.c</file>
<file>listbox.c</file>
<file>list_store.c</file>
<file>listview.c</file>
<file>markup.c</file>
<file>menus.c</file>
<file>modelbutton.c</file>
@@ -223,6 +224,11 @@
<file>messages.txt</file>
<file>apple-red.png</file>
</gresource>
<gresource prefix="/listview">
<file>listview.ui</file>
<file>messages.txt</file>
<file>apple-red.png</file>
</gresource>
<gresource prefix="/popover">
<file>popover.ui</file>
</gresource>

View File

@@ -9,16 +9,11 @@
#include <stdlib.h>
#include <string.h>
#include "message.h"
static GdkPixbuf *avatar_pixbuf_other;
static GtkWidget *window = NULL;
#define GTK_TYPE_MESSAGE (gtk_message_get_type ())
#define GTK_MESSAGE(message) (G_TYPE_CHECK_INSTANCE_CAST ((message), GTK_TYPE_MESSAGE, GtkMessage))
#define GTK_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MESSAGE, GtkMessageClass))
#define GTK_IS_MESSAGE(message) (G_TYPE_CHECK_INSTANCE_TYPE ((message), GTK_TYPE_MESSAGE))
#define GTK_IS_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MESSAGE))
#define GTK_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MESSAGE, GtkMessageClass))
#define GTK_TYPE_MESSAGE_ROW (gtk_message_row_get_type ())
#define GTK_MESSAGE_ROW(message_row) (G_TYPE_CHECK_INSTANCE_CAST ((message_row), GTK_TYPE_MESSAGE_ROW, GtkMessageRow))
#define GTK_MESSAGE_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MESSAGE_ROW, GtkMessageRowClass))
@@ -26,33 +21,10 @@ static GtkWidget *window = NULL;
#define GTK_IS_MESSAGE_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MESSAGE_ROW))
#define GTK_MESSAGE_ROW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MESSAGE_ROW, GtkMessageRowClass))
typedef struct _GtkMessage GtkMessage;
typedef struct _GtkMessageClass GtkMessageClass;
typedef struct _GtkMessageRow GtkMessageRow;
typedef struct _GtkMessageRowClass GtkMessageRowClass;
typedef struct _GtkMessageRowPrivate GtkMessageRowPrivate;
struct _GtkMessage
{
GObject parent;
guint id;
char *sender_name;
char *sender_nick;
char *message;
gint64 time;
guint reply_to;
char *resent_by;
int n_favorites;
int n_reshares;
};
struct _GtkMessageClass
{
GObjectClass parent_class;
};
struct _GtkMessageRow
{
GtkListBoxRow parent;
@@ -83,84 +55,10 @@ struct _GtkMessageRowPrivate
GtkButton *expand_button;
};
GType gtk_message_get_type (void) G_GNUC_CONST;
GType gtk_message_row_get_type (void) G_GNUC_CONST;
G_DEFINE_TYPE (GtkMessage, gtk_message, G_TYPE_OBJECT);
static void
gtk_message_finalize (GObject *obj)
{
GtkMessage *msg = GTK_MESSAGE (obj);
g_free (msg->sender_name);
g_free (msg->sender_nick);
g_free (msg->message);
g_free (msg->resent_by);
G_OBJECT_CLASS (gtk_message_parent_class)->finalize (obj);
}
static void
gtk_message_class_init (GtkMessageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gtk_message_finalize;
}
static void
gtk_message_init (GtkMessage *msg)
{
}
static void
gtk_message_parse (GtkMessage *msg, const char *str)
{
char **strv;
int i;
strv = g_strsplit (str, "|", 0);
i = 0;
msg->id = strtol (strv[i++], NULL, 10);
msg->sender_name = g_strdup (strv[i++]);
msg->sender_nick = g_strdup (strv[i++]);
msg->message = g_strdup (strv[i++]);
msg->time = strtol (strv[i++], NULL, 10);
if (strv[i])
{
msg->reply_to = strtol (strv[i++], NULL, 10);
if (strv[i])
{
if (*strv[i])
msg->resent_by = g_strdup (strv[i]);
i++;
if (strv[i])
{
msg->n_favorites = strtol (strv[i++], NULL, 10);
if (strv[i])
{
msg->n_reshares = strtol (strv[i++], NULL, 10);
}
}
}
}
g_strfreev (strv);
}
static GtkMessage *
gtk_message_new (const char *str)
{
GtkMessage *msg;
msg = g_object_new (gtk_message_get_type (), NULL);
gtk_message_parse (msg, str);
return msg;
}
G_DEFINE_TYPE_WITH_PRIVATE (GtkMessageRow, gtk_message_row, GTK_TYPE_LIST_BOX_ROW);
static void
gtk_message_row_update (GtkMessageRow *row)
{
@@ -333,15 +231,48 @@ row_activated (GtkListBox *listbox, GtkListBoxRow *row)
gtk_message_row_expand (GTK_MESSAGE_ROW (row));
}
static void
update_count (GtkListBox *listbox, GtkLabel *label)
{
GList *children = gtk_container_get_children (GTK_CONTAINER (listbox));
guint n_items = g_list_length (children);
g_list_free (children);
char *text = g_strdup_printf ("%u rows", n_items);
gtk_label_set_label (label, text);
g_free (text);
}
static GtkWidget *header_label;
static void
add_more (GtkListBox *listbox)
{
GBytes *data;
char **lines;
int i;
data = g_resources_lookup_data ("/listbox/messages.txt", 0, NULL);
lines = g_strsplit (g_bytes_get_data (data, NULL), "\n", 0);
for (i = 0; lines[i] != NULL && *lines[i]; i++)
{
GtkMessage *message = gtk_message_new (lines[i]);
GtkMessageRow *row = gtk_message_row_new (message);
gtk_container_add (GTK_CONTAINER (listbox), GTK_WIDGET (row));
}
g_strfreev (lines);
g_bytes_unref (data);
update_count (listbox, GTK_LABEL (header_label));
}
GtkWidget *
do_listbox (GtkWidget *do_widget)
{
GtkWidget *scrolled, *listbox, *vbox, *label;
GtkMessage *message;
GtkMessageRow *row;
GBytes *data;
char **lines;
int i;
GtkWidget *header, *more;
if (!window)
{
@@ -350,15 +281,27 @@ do_listbox (GtkWidget *do_widget)
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "List Box");
gtk_window_set_default_size (GTK_WINDOW (window),
400, 600);
gtk_window_set_default_size (GTK_WINDOW (window), 400, 600);
/* NULL window variable when window is closed */
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_widget_destroyed),
&window);
listbox = gtk_list_box_new ();
header = gtk_header_bar_new ();
gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (header), TRUE);
gtk_header_bar_set_title (GTK_HEADER_BAR (header), "List View");
header_label = gtk_label_new ("");
gtk_header_bar_pack_start (GTK_HEADER_BAR (header), header_label);
more = gtk_button_new_from_icon_name ("list-add");
g_signal_connect_swapped (more, "clicked", G_CALLBACK (add_more), listbox);
gtk_header_bar_pack_start (GTK_HEADER_BAR (header), more);
gtk_window_set_titlebar (GTK_WINDOW (window), header);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
gtk_container_add (GTK_CONTAINER (window), vbox);
label = gtk_label_new ("Messages from Gtk+ and friends");
@@ -367,26 +310,14 @@ do_listbox (GtkWidget *do_widget)
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand (scrolled, TRUE);
gtk_box_pack_start (GTK_BOX (vbox), scrolled);
listbox = gtk_list_box_new ();
gtk_container_add (GTK_CONTAINER (scrolled), listbox);
gtk_list_box_set_sort_func (GTK_LIST_BOX (listbox), (GtkListBoxSortFunc)gtk_message_row_sort, listbox, NULL);
gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (listbox), FALSE);
g_signal_connect (listbox, "row-activated", G_CALLBACK (row_activated), NULL);
data = g_resources_lookup_data ("/listbox/messages.txt", 0, NULL);
lines = g_strsplit (g_bytes_get_data (data, NULL), "\n", 0);
for (i = 0; lines[i] != NULL && *lines[i]; i++)
{
message = gtk_message_new (lines[i]);
row = gtk_message_row_new (message);
gtk_widget_show (GTK_WIDGET (row));
gtk_container_add (GTK_CONTAINER (listbox), GTK_WIDGET (row));
}
g_strfreev (lines);
g_bytes_unref (data);
add_more (listbox);
update_count (listbox, header_label);
}
if (!gtk_widget_get_visible (window))

323
demos/gtk-demo/listview.c Normal file
View File

@@ -0,0 +1,323 @@
/* List View
*
* GtkListView allows lists with complicated layouts, using
* models to hold the data, and creating rows on demand.
*/
#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
#include "message.h"
static GdkPixbuf *avatar_pixbuf_other;
static GtkWidget *window = NULL;
#define GTK_TYPE_MSG_ROW (gtk_msg_row_get_type ())
#define GTK_MSG_ROW(msg_row) (G_TYPE_CHECK_INSTANCE_CAST ((msg_row), GTK_TYPE_MSG_ROW, GtkMsgRow))
#define GTK_MSG_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MSG_ROW, GtkMsgRowClass))
#define GTK_IS_MSG_ROW(msg_row) (G_TYPE_CHECK_INSTANCE_TYPE ((msg_row), GTK_TYPE_MSG_ROW))
#define GTK_IS_MSG_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MSG_ROW))
#define GTK_MSG_ROW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MSG_ROW, GtkMsgRowClass))
typedef struct _GtkMsgRow GtkMsgRow;
typedef struct _GtkMsgRowClass GtkMsgRowClass;
struct _GtkMsgRow
{
GtkBin parent;
GtkMessage *message;
GtkRevealer *details_revealer;
GtkImage *avatar_image;
GtkWidget *extra_buttons_box;
GtkLabel *content_label;
GtkLabel *source_name;
GtkLabel *source_nick;
GtkLabel *short_time_label;
GtkLabel *detailed_time_label;
GtkBox *resent_box;
GtkLinkButton *resent_by_button;
GtkLabel *n_favorites_label;
GtkLabel *n_reshares_label;
GtkButton *expand_button;
};
struct _GtkMsgRowClass
{
GtkBinClass parent_class;
};
static GType gtk_msg_row_get_type (void) G_GNUC_CONST;
G_DEFINE_TYPE (GtkMsgRow, gtk_msg_row, GTK_TYPE_BIN);
static void
gtk_msg_row_update (GtkMsgRow *row)
{
GDateTime *t;
char *s;
gtk_label_set_text (row->source_name, row->message->sender_name);
gtk_label_set_text (row->source_nick, row->message->sender_nick);
gtk_label_set_text (row->content_label, row->message->message);
t = g_date_time_new_from_unix_utc (row->message->time);
s = g_date_time_format (t, "%e %b %y");
gtk_label_set_text (row->short_time_label, s);
g_free (s);
s = g_date_time_format (t, "%X - %e %b %Y");
gtk_label_set_text (row->detailed_time_label, s);
g_free (s);
g_date_time_unref (t);
gtk_widget_set_visible (GTK_WIDGET(row->n_favorites_label),
row->message->n_favorites != 0);
s = g_strdup_printf ("<b>%d</b>\nFavorites", row->message->n_favorites);
gtk_label_set_markup (row->n_favorites_label, s);
g_free (s);
gtk_widget_set_visible (GTK_WIDGET(row->n_reshares_label),
row->message->n_reshares != 0);
s = g_strdup_printf ("<b>%d</b>\nReshares", row->message->n_reshares);
gtk_label_set_markup (row->n_reshares_label, s);
g_free (s);
gtk_widget_set_visible (GTK_WIDGET (row->resent_box), row->message->resent_by != NULL);
if (row->message->resent_by)
gtk_button_set_label (GTK_BUTTON (row->resent_by_button), row->message->resent_by);
if (strcmp (row->message->sender_nick, "@GTKtoolkit") == 0)
{
gtk_image_set_from_icon_name (row->avatar_image, "gtk3-demo");
gtk_image_set_icon_size (row->avatar_image, GTK_ICON_SIZE_LARGE);
}
else
gtk_image_set_from_pixbuf (row->avatar_image, avatar_pixbuf_other);
}
static void
gtk_msg_row_expand (GtkMsgRow *row)
{
gboolean expand;
expand = !gtk_revealer_get_reveal_child (row->details_revealer);
gtk_revealer_set_reveal_child (row->details_revealer, expand);
if (expand)
gtk_button_set_label (row->expand_button, "Hide");
else
gtk_button_set_label (row->expand_button, "Expand");
}
static void
expand_clicked (GtkMsgRow *row,
GtkButton *button)
{
gtk_msg_row_expand (row);
}
static void
reshare_clicked (GtkMsgRow *row,
GtkButton *button)
{
row->message->n_reshares++;
gtk_msg_row_update (row);
}
static void
favorite_clicked (GtkMsgRow *row,
GtkButton *button)
{
row->message->n_favorites++;
gtk_msg_row_update (row);
}
static void
gtk_msg_row_state_flags_changed (GtkWidget *widget,
GtkStateFlags previous_state_flags)
{
GtkMsgRow *row = GTK_MSG_ROW (widget);
GtkStateFlags flags;
flags = gtk_widget_get_state_flags (widget);
gtk_widget_set_visible (row->extra_buttons_box,
flags & (GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_SELECTED));
GTK_WIDGET_CLASS (gtk_msg_row_parent_class)->state_flags_changed (widget, previous_state_flags);
}
static void
gtk_msg_row_finalize (GObject *obj)
{
G_OBJECT_CLASS (gtk_msg_row_parent_class)->finalize(obj);
}
static void
gtk_msg_row_class_init (GtkMsgRowClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gtk_msg_row_finalize;
gtk_widget_class_set_template_from_resource (widget_class, "/listview/listview.ui");
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, content_label);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, source_name);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, source_nick);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, short_time_label);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, detailed_time_label);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, extra_buttons_box);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, details_revealer);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, avatar_image);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, resent_box);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, resent_by_button);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, n_reshares_label);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, n_favorites_label);
gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, expand_button);
gtk_widget_class_bind_template_callback (widget_class, expand_clicked);
gtk_widget_class_bind_template_callback (widget_class, reshare_clicked);
gtk_widget_class_bind_template_callback (widget_class, favorite_clicked);
widget_class->state_flags_changed = gtk_msg_row_state_flags_changed;
}
static void
row_activated (GtkGestureMultiPress *gesture,
int n_press,
double x,
double y,
GtkMsgRow *row)
{
if (n_press == 2)
gtk_msg_row_expand (row);
}
static void
gtk_msg_row_init (GtkMsgRow *row)
{
GtkGesture *double_click;
gtk_widget_init_template (GTK_WIDGET (row));
double_click = gtk_gesture_multi_press_new ();
g_signal_connect (double_click, "pressed", G_CALLBACK (row_activated), row);
gtk_widget_add_controller (GTK_WIDGET (row), GTK_EVENT_CONTROLLER (double_click));
}
static int
gtk_message_sort (gconstpointer a, gconstpointer b, gpointer data)
{
return ((const GtkMessage *)b)->time - ((const GtkMessage *)a)->time;
}
static void
bind_msg_row (GObject *list_item, GParamSpec *pspec, gpointer data)
{
GtkMessage *message = (GtkMessage *)gtk_list_item_get_item (GTK_LIST_ITEM (list_item));
GtkMsgRow *row = (GtkMsgRow *) gtk_bin_get_child (GTK_BIN (list_item));
row->message = message;
if (message)
gtk_msg_row_update (row);
}
static void
setup_row (GtkListItem *item,
gpointer data)
{
g_signal_connect (item, "notify::item", G_CALLBACK (bind_msg_row), data);
gtk_container_add (GTK_CONTAINER (item), g_object_new (gtk_msg_row_get_type (), NULL));
}
static void
update_count (GListModel *model, guint position, guint removed, guint added, GtkLabel *label)
{
guint n_items = g_list_model_get_n_items (model);
char *text = g_strdup_printf ("%u rows", n_items);
gtk_label_set_label (label, text);
g_free (text);
}
static void
add_more (GListModel *model)
{
GBytes *data;
char **lines;
int i;
data = g_resources_lookup_data ("/listview/messages.txt", 0, NULL);
lines = g_strsplit (g_bytes_get_data (data, NULL), "\n", 0);
for (i = 0; lines[i] != NULL && *lines[i]; i++)
{
GtkMessage *message = gtk_message_new (lines[i]);
g_list_store_append (G_LIST_STORE (model), message);
}
g_strfreev (lines);
g_bytes_unref (data);
}
GtkWidget *
do_listview (GtkWidget *do_widget)
{
GtkWidget *scrolled, *listview, *vbox, *label;
GtkWidget *header, *header_label, *more;
GListModel *model;
if (!window)
{
avatar_pixbuf_other = gdk_pixbuf_new_from_resource_at_scale ("/listbox/apple-red.png", 32, 32, FALSE, NULL);
model = G_LIST_MODEL (g_list_store_new (gtk_message_get_type ()));
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_default_size (GTK_WINDOW (window), 400, 600);
/* NULL window variable when window is closed */
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_widget_destroyed),
&window);
header = gtk_header_bar_new ();
gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (header), TRUE);
gtk_header_bar_set_title (GTK_HEADER_BAR (header), "List View");
header_label = gtk_label_new ("");
gtk_header_bar_pack_start (GTK_HEADER_BAR (header), header_label);
more = gtk_button_new_from_icon_name ("list-add");
g_signal_connect_swapped (more, "clicked", G_CALLBACK (add_more), model);
g_signal_connect (model, "items-changed", G_CALLBACK (update_count), header_label);
gtk_header_bar_pack_start (GTK_HEADER_BAR (header), more);
gtk_window_set_titlebar (GTK_WINDOW (window), header);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
gtk_container_add (GTK_CONTAINER (window), vbox);
label = gtk_label_new ("Messages from Gtk+ and friends");
gtk_box_pack_start (GTK_BOX (vbox), label);
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand (scrolled, TRUE);
gtk_box_pack_start (GTK_BOX (vbox), scrolled);
listview = gtk_list_view_new ();
gtk_container_add (GTK_CONTAINER (scrolled), listview);
gtk_list_view_set_functions (GTK_LIST_VIEW (listview), setup_row, NULL, NULL, NULL);
gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (gtk_sort_list_model_new (model, gtk_message_sort, NULL, NULL)));
add_more (model);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_widget_destroy (window);
return window;
}

302
demos/gtk-demo/listview.ui Normal file
View File

@@ -0,0 +1,302 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="gtk40">
<!-- interface-requires gtk+ 3.10 -->
<!-- interface-requires gtkdemo 3.10 -->
<object class="GtkMenu" id="menu1">
<child>
<object class="GtkMenuItem" id="menuitem1">
<property name="label" translatable="yes">Email message</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem2">
<property name="label" translatable="yes">Embed message</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
<template class="GtkMsgRow" parent="GtkBin">
<child>
<object class="GtkGrid" id="grid1">
<property name="hexpand">1</property>
<child>
<object class="GtkImage" id="avatar_image">
<property name="width-request">32</property>
<property name="height-request">32</property>
<property name="halign">center</property>
<property name="valign">start</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="margin-start">8</property>
<property name="margin-end">8</property>
<property name="icon-name">image-missing</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
<property name="height">5</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box1">
<property name="hexpand">1</property>
<property name="baseline-position">top</property>
<child>
<object class="GtkButton" id="button2">
<property name="can-focus">1</property>
<property name="receives-default">1</property>
<property name="valign">baseline</property>
<property name="relief">none</property>
<child>
<object class="GtkLabel" id="source_name">
<property name="valign">baseline</property>
<property name="label" translatable="0">Username</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="source_nick">
<property name="valign">baseline</property>
<property name="label" translatable="0">@nick</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="short_time_label">
<property name="valign">baseline</property>
<property name="label" translatable="yes">38m</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="content_label">
<property name="halign">start</property>
<property name="valign">start</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="label" translatable="0">Message</property>
<property name="wrap">1</property>
<accessibility>
<role type="static"/>
</accessibility>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="resent_box">
<child>
<object class="GtkImage" id="image2">
<property name="icon-name">media-playlist-repeat</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="label" translatable="yes">Resent by</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLinkButton" id="resent_by_button">
<property name="label" translatable="0">reshareer</property>
<property name="can-focus">1</property>
<property name="receives-default">1</property>
<property name="relief">none</property>
<property name="uri">http://www.gtk.org</property>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box3">
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="expand_button">
<property name="label" translatable="yes">Expand</property>
<property name="can-focus">1</property>
<property name="receives-default">1</property>
<property name="relief">none</property>
<signal name="clicked" handler="expand_clicked" swapped="yes"/>
</object>
</child>
<child>
<object class="GtkBox" id="extra_buttons_box">
<property name="visible">0</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="reply-button">
<property name="label" translatable="yes">Reply</property>
<property name="can-focus">1</property>
<property name="receives-default">1</property>
<property name="relief">none</property>
</object>
</child>
<child>
<object class="GtkButton" id="reshare-button">
<property name="label" translatable="yes">Reshare</property>
<property name="can-focus">1</property>
<property name="receives-default">1</property>
<property name="relief">none</property>
<signal name="clicked" handler="reshare_clicked" swapped="yes"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="favorite-buttton">
<property name="label" translatable="yes">Favorite</property>
<property name="can-focus">1</property>
<property name="receives-default">1</property>
<property name="relief">none</property>
<signal name="clicked" handler="favorite_clicked" swapped="yes"/>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkMenuButton" id="more-button">
<property name="can-focus">1</property>
<property name="receives-default">1</property>
<property name="relief">none</property>
<property name="popup">menu1</property>
<child>
<object class="GtkLabel" id="label7">
<property name="label" translatable="yes">More...</property>
</object>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkRevealer" id="details_revealer">
<child>
<object class="GtkBox" id="box5">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="box7">
<property name="margin-top">2</property>
<property name="margin-bottom">2</property>
<property name="spacing">8</property>
<child>
<object class="GtkFrame" id="frame1">
<property name="shadow-type">none</property>
<child>
<object class="GtkLabel" id="n_reshares_label">
<property name="label" translatable="0">&lt;b&gt;2&lt;/b&gt;
Reshares</property>
<property name="use-markup">1</property>
</object>
</child>
<child type="label_item"/>
</object>
</child>
<child>
<object class="GtkFrame" id="frame2">
<property name="shadow-type">none</property>
<child>
<object class="GtkLabel" id="n_favorites_label">
<property name="label" translatable="0">&lt;b&gt;2&lt;/b&gt;
FAVORITES</property>
<property name="use-markup">1</property>
</object>
</child>
<child type="label_item"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box6">
<child>
<object class="GtkLabel" id="detailed_time_label">
<property name="label" translatable="0">4:25 AM - 14 Jun 13 </property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="button5">
<property name="label" translatable="yes">Details</property>
<property name="can-focus">1</property>
<property name="receives-default">1</property>
<property name="relief">none</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">4</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

View File

@@ -36,6 +36,7 @@ demos = files([
'infobar.c',
'links.c',
'listbox.c',
'listview.c',
'flowbox.c',
'list_store.c',
'markup.c',
@@ -76,7 +77,7 @@ demos = files([
gtkdemo_deps = [ libgtk_dep, ]
extra_demo_sources = files(['main.c', 'gtkfishbowl.c', 'fontplane.c', 'gtkgears.c', 'puzzlepiece.c'])
extra_demo_sources = files(['main.c', 'gtkfishbowl.c', 'fontplane.c', 'gtkgears.c', 'puzzlepiece.c', 'message.c'])
if harfbuzz_dep.found() and pangoft_dep.found()
demos += files('font_features.c')

79
demos/gtk-demo/message.c Normal file
View File

@@ -0,0 +1,79 @@
#include <gtk/gtk.h>
#include "message.h"
#include <stdlib.h>
#include <string.h>
G_DEFINE_TYPE (GtkMessage, gtk_message, G_TYPE_OBJECT);
static void
gtk_message_finalize (GObject *obj)
{
GtkMessage *msg = GTK_MESSAGE (obj);
g_free (msg->sender_name);
g_free (msg->sender_nick);
g_free (msg->message);
g_free (msg->resent_by);
G_OBJECT_CLASS (gtk_message_parent_class)->finalize (obj);
}
static void
gtk_message_class_init (GtkMessageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gtk_message_finalize;
}
static void
gtk_message_init (GtkMessage *msg)
{
}
static void
gtk_message_parse (GtkMessage *msg, const char *str)
{
char **strv;
int i;
strv = g_strsplit (str, "|", 0);
i = 0;
msg->id = strtol (strv[i++], NULL, 10);
msg->sender_name = g_strdup (strv[i++]);
msg->sender_nick = g_strdup (strv[i++]);
msg->message = g_strdup (strv[i++]);
msg->time = strtol (strv[i++], NULL, 10);
if (strv[i])
{
msg->reply_to = strtol (strv[i++], NULL, 10);
if (strv[i])
{
if (*strv[i])
msg->resent_by = g_strdup (strv[i]);
i++;
if (strv[i])
{
msg->n_favorites = strtol (strv[i++], NULL, 10);
if (strv[i])
{
msg->n_reshares = strtol (strv[i++], NULL, 10);
}
}
}
}
g_strfreev (strv);
}
GtkMessage *
gtk_message_new (const char *str)
{
GtkMessage *msg;
msg = g_object_new (gtk_message_get_type (), NULL);
gtk_message_parse (msg, str);
return msg;
}

34
demos/gtk-demo/message.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#define GTK_TYPE_MESSAGE (gtk_message_get_type ())
#define GTK_MESSAGE(message) (G_TYPE_CHECK_INSTANCE_CAST ((message), GTK_TYPE_MESSAGE, GtkMessage))
#define GTK_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MESSAGE, GtkMessageClass))
#define GTK_IS_MESSAGE(message) (G_TYPE_CHECK_INSTANCE_TYPE ((message), GTK_TYPE_MESSAGE))
#define GTK_IS_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MESSAGE))
#define GTK_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MESSAGE, GtkMessageClass))
typedef struct _GtkMessage GtkMessage;
typedef struct _GtkMessageClass GtkMessageClass;
struct _GtkMessage
{
GObject parent;
guint id;
char *sender_name;
char *sender_nick;
char *message;
gint64 time;
guint reply_to;
char *resent_by;
int n_favorites;
int n_reshares;
};
struct _GtkMessageClass
{
GObjectClass parent_class;
};
GType gtk_message_get_type (void) G_GNUC_CONST;
GtkMessage * gtk_message_new (const char *str);

View File

@@ -83,6 +83,7 @@
<xi:include href="xml/gtkrevealer.xml" />
<xi:include href="xml/gtklistbox.xml" />
<xi:include href="xml/gtkflowbox.xml" />
<xi:include href="xml/gtklistview.xml" />
<xi:include href="xml/gtkstack.xml" />
<xi:include href="xml/gtkstackswitcher.xml" />
<xi:include href="xml/gtkstacksidebar.xml" />

View File

@@ -442,6 +442,68 @@ gtk_list_box_get_type
gtk_list_box_row_get_type
</SECTION>
<SECTION>
<FILE>gtkselectionmodel</FILE>
<TITLE>GtkSelectionModel</TITLE>
GtkSelectionModel
gtk_selection_model_is_selected
gtk_selection_model_select_item
gtk_selection_model_unselect_item
gtk_selection_model_select_range
gtk_selection_model_unselect_range
gtk_selection_model_select_all
gtk_selection_model_unselect_all
<SUBSECTION>
gtk_selection_model_selection_changed
<SUBSECTION Standard>
GTK_SELECTION_MODEL
GTK_SELECTION_MODEL_CLASS
GTK_SELECTION_MODEL_GET_CLASS
GTK_IS_SELECTION_MODEL
GTK_IS_SELECTION_MODEL_CLASS
GTK_TYPE_SELECTION_MODEL
<SUBSECTION Private>
gtk_selection_model_get_type
</SECTION>
<SECTION>
<FILE>gtklistitem</FILE>
<TITLE>GtkListItem</TITLE>
GtkListItem
gtk_list_item_get_item
gtk_list_item_get_position
gtk_list_item_get_selected
gtk_list_item_get_selectable
gtk_list_item_set_selectable
<SUBSECTION Standard>
GTK_LIST_ITEM
GTK_LIST_ITEM_CLASS
GTK_LIST_ITEM_GET_CLASS
GTK_IS_LIST_ITEM
GTK_IS_LIST_ITEM_CLASS
GTK_TYPE_LIST_ITEM
<SUBSECTION Private>
gtk_list_item_get_type
</SECTION>
<SECTION>
<FILE>gtklistview</FILE>
<TITLE>GtkListView</TITLE>
GtkListView
gtk_list_view_new
gtk_list_view_set_model
gtk_list_view_get_model
<SUBSECTION Standard>
GTK_LIST_VIEW
GTK_LIST_VIEW_CLASS
GTK_LIST_VIEW_GET_CLASS
GTK_IS_LIST_VIEW
GTK_IS_LIST_VIEW_CLASS
GTK_TYPE_LIST_VIEW
<SUBSECTION Private>
gtk_list_view_get_type
</SECTION>
<SECTION>
<FILE>gtkbuildable</FILE>
GtkBuildable

View File

@@ -139,7 +139,9 @@
#include <gtk/gtklevelbar.h>
#include <gtk/gtklinkbutton.h>
#include <gtk/gtklistbox.h>
#include <gtk/gtklistitem.h>
#include <gtk/gtkliststore.h>
#include <gtk/gtklistview.h>
#include <gtk/gtklockbutton.h>
#include <gtk/gtkmain.h>
#include <gtk/gtkmaplistmodel.h>
@@ -155,7 +157,9 @@
#include <gtk/gtkmessagedialog.h>
#include <gtk/gtkmodelbutton.h>
#include <gtk/gtkmountoperation.h>
#include <gtk/gtkmultiselection.h>
#include <gtk/gtknativedialog.h>
#include <gtk/gtknoselection.h>
#include <gtk/gtknotebook.h>
#include <gtk/gtkorientable.h>
#include <gtk/gtkoverlay.h>
@@ -186,6 +190,7 @@
#include <gtk/gtksearchbar.h>
#include <gtk/gtksearchentry.h>
#include <gtk/gtkselection.h>
#include <gtk/gtkselectionmodel.h>
#include <gtk/gtkseparator.h>
#include <gtk/gtkseparatormenuitem.h>
#include <gtk/gtkseparatortoolitem.h>
@@ -196,6 +201,7 @@
#include <gtk/gtkshortcutsshortcut.h>
#include <gtk/gtkshortcutswindow.h>
#include <gtk/gtkshow.h>
#include <gtk/gtksingleselection.h>
#include <gtk/gtkslicelistmodel.h>
#include <gtk/gtksnapshot.h>
#include <gtk/gtksortlistmodel.h>

View File

@@ -29,7 +29,6 @@
#include "gtkadjustment.h"
#include "gtkbuildable.h"
#include "gtkbox.h"
#include "gtkcellrenderertext.h"
#include "gtkcssnumbervalueprivate.h"
#include "gtkentry.h"
#include "gtksearchentry.h"
@@ -46,8 +45,6 @@
#include "gtkspinbutton.h"
#include "gtkstylecontextprivate.h"
#include "gtktextview.h"
#include "gtktreeselection.h"
#include "gtktreeview.h"
#include "gtkwidget.h"
#include "gtksettings.h"
#include "gtkdialog.h"
@@ -55,6 +52,10 @@
#include "gtkcombobox.h"
#include "gtkgesturemultipress.h"
#include "gtkeventcontrollerscroll.h"
#include "gtklistview.h"
#include "gtkfilterlistmodel.h"
#include "gtksingleselection.h"
#include "gtkselectionmodel.h"
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
#include <pango/pangofc-font.h>
@@ -100,13 +101,12 @@ struct _GtkFontChooserWidgetPrivate
GtkWidget *stack;
GtkWidget *grid;
GtkWidget *search_entry;
GtkWidget *family_face_list;
GtkTreeViewColumn *family_face_column;
GtkCellRenderer *family_face_cell;
GtkWidget *list_scrolled_window;
GtkWidget *list_stack;
GtkTreeModel *model;
GtkTreeModel *filter_model;
GtkWidget *family_face_listview;
GListModel *listmodel;
GListModel *filtermodel;
GListModel *selectionmodel;
GtkWidget *preview;
GtkWidget *preview2;
@@ -127,8 +127,7 @@ struct _GtkFontChooserWidgetPrivate
PangoFontDescription *font_desc;
char *font_features;
PangoLanguage *language;
GtkTreeIter font_iter; /* invalid if font not available or pointer into model
(not filter_model) to the row containing font */
guint font_position;
GtkFontFilterFunc filter_func;
gpointer filter_data;
GDestroyNotify filter_data_destroy;
@@ -150,14 +149,6 @@ enum {
PROP_TWEAK_ACTION
};
/* Keep in line with GtkTreeStore defined in gtkfontchooserwidget.ui */
enum {
FAMILY_COLUMN,
FACE_COLUMN,
FONT_DESC_COLUMN,
PREVIEW_TITLE_COLUMN
};
static void gtk_font_chooser_widget_set_property (GObject *object,
guint prop_id,
const GValue *value,
@@ -173,7 +164,7 @@ static void gtk_font_chooser_widget_display_changed (GtkWidget *widge
static gboolean gtk_font_chooser_widget_find_font (GtkFontChooserWidget *fontchooser,
const PangoFontDescription *font_desc,
GtkTreeIter *iter);
guint *position);
static void gtk_font_chooser_widget_ensure_selection (GtkFontChooserWidget *fontchooser);
static gchar *gtk_font_chooser_widget_get_font (GtkFontChooserWidget *fontchooser);
@@ -183,7 +174,7 @@ static void gtk_font_chooser_widget_set_font (GtkFontChooserWidget *
static PangoFontDescription *gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *fontchooser);
static void gtk_font_chooser_widget_merge_font_desc(GtkFontChooserWidget *fontchooser,
const PangoFontDescription *font_desc,
GtkTreeIter *iter);
guint position);
static void gtk_font_chooser_widget_take_font_desc (GtkFontChooserWidget *fontchooser,
PangoFontDescription *font_desc);
@@ -191,29 +182,26 @@ static void gtk_font_chooser_widget_take_font_desc (GtkFontChoo
static const gchar *gtk_font_chooser_widget_get_preview_text (GtkFontChooserWidget *fontchooser);
static void gtk_font_chooser_widget_set_preview_text (GtkFontChooserWidget *fontchooser,
const gchar *text);
static PangoAttrList *gtk_font_chooser_widget_get_preview_attributes (GtkFontChooserWidget *fontchooser,
const PangoFontDescription *font_desc);
static gboolean gtk_font_chooser_widget_get_show_preview_entry (GtkFontChooserWidget *fontchooser);
static void gtk_font_chooser_widget_set_show_preview_entry (GtkFontChooserWidget *fontchooser,
gboolean show_preview_entry);
static void gtk_font_chooser_widget_set_cell_size (GtkFontChooserWidget *fontchooser);
static void gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser,
gboolean force);
static void gtk_font_chooser_widget_populate_features (GtkFontChooserWidget *fontchooser);
static gboolean visible_func (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer user_data);
static void gtk_font_chooser_widget_cell_data_func (GtkTreeViewColumn *column,
GtkCellRenderer *cell,
GtkTreeModel *tree_model,
GtkTreeIter *iter,
gpointer user_data);
static gboolean visible_func (gpointer item,
gpointer user_data);
static void gtk_font_chooser_widget_set_level (GtkFontChooserWidget *fontchooser,
GtkFontChooserLevel level);
static GtkFontChooserLevel gtk_font_chooser_widget_get_level (GtkFontChooserWidget *fontchooser);
static void gtk_font_chooser_widget_set_language (GtkFontChooserWidget *fontchooser,
const char *language);
static void selection_changed (GtkTreeSelection *selection,
static void selection_changed (GtkSelectionModel *selection,
guint position,
guint n_items,
GtkFontChooserWidget *fontchooser);
static void update_font_features (GtkFontChooserWidget *fontchooser);
@@ -226,48 +214,37 @@ G_DEFINE_TYPE_WITH_CODE (GtkFontChooserWidget, gtk_font_chooser_widget, GTK_TYPE
typedef struct _GtkDelayedFontDescription GtkDelayedFontDescription;
struct _GtkDelayedFontDescription {
PangoFontFace *face;
GObject parent;
PangoFontFamily *family;
PangoFontFace *face;
char *title;
PangoFontDescription *desc;
guint ref_count;
};
typedef GObjectClass GtkDelayedFontDescriptionClass;
#define GTK_TYPE_DELAYED_FONT_DESCRIPTION (gtk_delayed_font_description_get_type ())
static GType gtk_delayed_font_description_get_type (void);
G_DEFINE_TYPE (GtkDelayedFontDescription, gtk_delayed_font_description, G_TYPE_OBJECT)
static GtkDelayedFontDescription *
gtk_delayed_font_description_new (PangoFontFace *face)
gtk_delayed_font_description_new (PangoFontFamily *family,
PangoFontFace *face,
const char *title)
{
GtkDelayedFontDescription *result;
result = g_slice_new0 (GtkDelayedFontDescription);
result = g_object_new (GTK_TYPE_DELAYED_FONT_DESCRIPTION, NULL);
result->family = g_object_ref (family);
result->face = g_object_ref (face);
result->title = g_strdup (title);
result->desc = NULL;
result->ref_count = 1;
return result;
}
static GtkDelayedFontDescription *
gtk_delayed_font_description_ref (GtkDelayedFontDescription *desc)
{
desc->ref_count++;
return desc;
}
static void
gtk_delayed_font_description_unref (GtkDelayedFontDescription *desc)
{
desc->ref_count--;
if (desc->ref_count > 0)
return;
g_object_unref (desc->face);
if (desc->desc)
pango_font_description_free (desc->desc);
g_slice_free (GtkDelayedFontDescription, desc);
}
static const PangoFontDescription *
gtk_delayed_font_description_get (GtkDelayedFontDescription *desc)
{
@@ -277,12 +254,40 @@ gtk_delayed_font_description_get (GtkDelayedFontDescription *desc)
return desc->desc;
}
#define GTK_TYPE_DELAYED_FONT_DESCRIPTION (gtk_delayed_font_description_get_type ())
GType gtk_delayed_font_description_get_type (void);
static const char *
gtk_delayed_font_description_get_title (GtkDelayedFontDescription *desc)
{
return desc->title;
}
static void
gtk_delayed_font_description_init (GtkDelayedFontDescription *d)
{
}
static void
gtk_delayed_font_description_finalize (GObject *object)
{
GtkDelayedFontDescription *desc = (GtkDelayedFontDescription *)object;
g_object_unref (desc->family);
g_object_unref (desc->face);
g_free (desc->title);
if (desc->desc)
pango_font_description_free (desc->desc);
G_OBJECT_CLASS (gtk_delayed_font_description_parent_class)->finalize (object);
}
static void
gtk_delayed_font_description_class_init (GtkDelayedFontDescriptionClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = gtk_delayed_font_description_finalize;
}
G_DEFINE_BOXED_TYPE (GtkDelayedFontDescription, gtk_delayed_font_description,
gtk_delayed_font_description_ref,
gtk_delayed_font_description_unref)
static void
gtk_font_chooser_widget_set_property (GObject *object,
guint prop_id,
@@ -360,7 +365,7 @@ gtk_font_chooser_widget_get_property (GObject *object,
static void
gtk_font_chooser_widget_refilter_font_list (GtkFontChooserWidget *fontchooser)
{
gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (fontchooser->priv->filter_model));
gtk_filter_list_model_refilter (GTK_FILTER_LIST_MODEL (fontchooser->priv->filtermodel));
gtk_font_chooser_widget_ensure_selection (fontchooser);
}
@@ -426,6 +431,13 @@ output_cb (GtkSpinButton *spin,
return TRUE;
}
static GtkDelayedFontDescription *
gtk_font_chooser_widget_get_desc (GtkFontChooserWidget *fontchooser)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
return (GtkDelayedFontDescription *)g_list_model_get_item (priv->selectionmodel, priv->font_position);
}
static void
gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *fontchooser)
{
@@ -436,21 +448,15 @@ gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *fontchooser)
gint i, n_sizes;
gdouble value, spin_value;
if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (priv->model), &priv->font_iter))
if (priv->font_position != GTK_INVALID_LIST_POSITION)
{
PangoFontFace *face;
GtkDelayedFontDescription *desc = gtk_font_chooser_widget_get_desc (fontchooser);
gtk_tree_model_get (priv->model, &priv->font_iter,
FACE_COLUMN, &face,
-1);
pango_font_face_list_sizes (face, &font_sizes, &n_sizes);
pango_font_face_list_sizes (desc->face, &font_sizes, &n_sizes);
/* It seems not many fonts actually have a sane set of sizes */
for (i = 0; i < n_sizes; i++)
font_sizes[i] = font_sizes[i] / PANGO_SCALE;
g_object_unref (face);
}
else
{
@@ -510,58 +516,6 @@ gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *fontchooser)
g_free (font_sizes);
}
static void
row_activated_cb (GtkTreeView *view,
GtkTreePath *path,
GtkTreeViewColumn *column,
gpointer user_data)
{
GtkFontChooserWidget *fontchooser = user_data;
gchar *fontname;
fontname = gtk_font_chooser_widget_get_font (fontchooser);
_gtk_font_chooser_font_activated (GTK_FONT_CHOOSER (fontchooser), fontname);
g_free (fontname);
}
static void
cursor_changed_cb (GtkTreeView *treeview,
gpointer user_data)
{
GtkFontChooserWidget *fontchooser = user_data;
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
GtkDelayedFontDescription *desc;
GtkTreeIter filter_iter, iter;
GtkTreePath *path = NULL;
gtk_tree_view_get_cursor (treeview, &path, NULL);
if (!path)
return;
if (!gtk_tree_model_get_iter (priv->filter_model, &filter_iter, path))
{
gtk_tree_path_free (path);
return;
}
gtk_tree_path_free (path);
gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (priv->filter_model),
&iter,
&filter_iter);
gtk_tree_model_get (priv->model, &iter,
FONT_DESC_COLUMN, &desc,
-1);
pango_font_description_set_variations (priv->font_desc, NULL);
gtk_font_chooser_widget_merge_font_desc (fontchooser,
gtk_delayed_font_description_get (desc),
&iter);
gtk_delayed_font_description_unref (desc);
}
static void
resize_by_scroll_cb (GtkEventControllerScroll *controller,
double dx,
@@ -608,7 +562,7 @@ rows_changed_cb (GtkFontChooserWidget *fontchooser)
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
const char *page;
if (gtk_tree_model_iter_n_children (priv->filter_model, NULL) == 0)
if (g_list_model_get_n_items (priv->selectionmodel) == 0)
page = "empty";
else
page = "list";
@@ -719,13 +673,8 @@ gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass)
"/org/gtk/libgtk/ui/gtkfontchooserwidget.ui");
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, search_entry);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, family_face_list);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, family_face_column);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, family_face_cell);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, list_scrolled_window);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, family_face_listview);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, list_stack);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, model);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, filter_model);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, preview);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, preview2);
gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, size_label);
@@ -740,13 +689,8 @@ gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass)
gtk_widget_class_bind_template_callback (widget_class, text_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, stop_search_cb);
gtk_widget_class_bind_template_callback (widget_class, cursor_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, row_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, gtk_font_chooser_widget_set_cell_size);
gtk_widget_class_bind_template_callback (widget_class, rows_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, size_change_cb);
gtk_widget_class_bind_template_callback (widget_class, output_cb);
gtk_widget_class_bind_template_callback (widget_class, selection_changed);
gtk_widget_class_bind_template_callback (widget_class, resize_by_scroll_cb);
gtk_widget_class_set_css_name (widget_class, I_("fontchooser"));
@@ -820,6 +764,66 @@ axis_free (gpointer v)
g_free (a);
}
static void
bind_row (GtkListItem *list_item,
gpointer data)
{
GtkFontChooserWidget *fontchooser = data;
GtkWidget *row;
GtkDelayedFontDescription *item;
const PangoFontDescription *desc;
PangoAttrList *attrs;
const char *title;
item = (GtkDelayedFontDescription *) gtk_list_item_get_item (list_item);
desc = gtk_delayed_font_description_get (item);
attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser, desc);
title = gtk_delayed_font_description_get_title (item);
row = gtk_bin_get_child (GTK_BIN (list_item));
gtk_label_set_label (GTK_LABEL (row), title);
gtk_label_set_attributes (GTK_LABEL (row), attrs);
pango_attr_list_unref (attrs);
}
static void
row_activated_cb (GtkGestureMultiPress *gesture,
int n_press,
double x,
double y,
GtkFontChooserWidget *fontchooser)
{
gchar *fontname;
if (n_press == 1)
return;
fontname = gtk_font_chooser_widget_get_font (fontchooser);
_gtk_font_chooser_font_activated (GTK_FONT_CHOOSER (fontchooser), fontname);
g_free (fontname);
}
static void
setup_row (GtkListItem *list_item,
gpointer data)
{
GtkFontChooserWidget *fontchooser = data;
GtkWidget *row;
GtkEventController *double_click;
row = gtk_label_new ("");
gtk_label_set_xalign (GTK_LABEL (row), 0.0);
g_object_set (row, "margin", 10, NULL);
double_click = gtk_gesture_multi_press_new ();
g_signal_connect (double_click, "pressed",
G_CALLBACK (row_activated_cb), fontchooser);
gtk_widget_add_controller (row, double_click);
gtk_container_add (GTK_CONTAINER (list_item), row);
}
static void
gtk_font_chooser_widget_init (GtkFontChooserWidget *fontchooser)
{
@@ -854,19 +858,17 @@ gtk_font_chooser_widget_init (GtkFontChooserWidget *fontchooser)
gtk_adjustment_set_upper (gtk_range_get_adjustment (GTK_RANGE (priv->size_slider)),
(gdouble)(G_MAXINT / PANGO_SCALE));
/* Setup treeview/model auxilary functions */
gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter_model),
visible_func, (gpointer)priv, NULL);
gtk_tree_view_column_set_cell_data_func (priv->family_face_column,
priv->family_face_cell,
gtk_font_chooser_widget_cell_data_func,
fontchooser,
NULL);
priv->tweak_action = G_ACTION (g_simple_action_new_stateful ("tweak", NULL, g_variant_new_boolean (FALSE)));
g_signal_connect (priv->tweak_action, "change-state", G_CALLBACK (change_tweak), fontchooser);
priv->listmodel = G_LIST_MODEL (g_list_store_new (GTK_TYPE_DELAYED_FONT_DESCRIPTION));
priv->filtermodel = G_LIST_MODEL (gtk_filter_list_model_new (priv->listmodel, visible_func, fontchooser, NULL));
priv->selectionmodel = G_LIST_MODEL (gtk_single_selection_new (priv->filtermodel));
g_signal_connect_swapped (priv->selectionmodel, "items-changed", G_CALLBACK (rows_changed_cb), fontchooser);
g_signal_connect (priv->selectionmodel, "selection-changed", G_CALLBACK (selection_changed), fontchooser);
gtk_list_view_set_model (GTK_LIST_VIEW (priv->family_face_listview), priv->selectionmodel);
gtk_list_view_set_functions (GTK_LIST_VIEW (priv->family_face_listview), setup_row, bind_row, fontchooser, NULL);
/* Load data and set initial style-dependent parameters */
gtk_font_chooser_widget_load_fonts (fontchooser, TRUE);
@@ -874,7 +876,6 @@ gtk_font_chooser_widget_init (GtkFontChooserWidget *fontchooser)
gtk_font_chooser_widget_populate_features (fontchooser);
#endif
gtk_font_chooser_widget_set_cell_size (fontchooser);
gtk_font_chooser_widget_take_font_desc (fontchooser, NULL);
gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (priv->search_entry),
@@ -909,7 +910,6 @@ gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser,
gboolean force)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
GtkListStore *list_store;
gint n_families, i;
PangoFontFamily **families;
guint fontconfig_timestamp;
@@ -932,8 +932,6 @@ gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser,
if (!need_reload && !force)
return;
list_store = GTK_LIST_STORE (priv->model);
if (priv->font_map)
font_map = priv->font_map;
else
@@ -942,14 +940,11 @@ gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser,
qsort (families, n_families, sizeof (PangoFontFamily *), cmp_families);
g_signal_handlers_block_by_func (priv->family_face_list, cursor_changed_cb, fontchooser);
g_signal_handlers_block_by_func (priv->filter_model, rows_changed_cb, fontchooser);
gtk_list_store_clear (list_store);
g_list_store_remove_all (G_LIST_STORE (priv->listmodel));
/* Iterate over families and faces */
for (i = 0; i < n_families; i++)
{
GtkTreeIter iter;
PangoFontFace **faces;
int j, n_faces;
const gchar *fam_name = pango_font_family_get_name (families[i]);
@@ -969,17 +964,12 @@ gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser,
else
title = g_strdup (fam_name);
desc = gtk_delayed_font_description_new (faces[j]);
desc = gtk_delayed_font_description_new (families[i], faces[j], title);
gtk_list_store_insert_with_values (list_store, &iter, -1,
FAMILY_COLUMN, families[i],
FACE_COLUMN, faces[j],
FONT_DESC_COLUMN, desc,
PREVIEW_TITLE_COLUMN, title,
-1);
g_list_store_append (G_LIST_STORE (priv->listmodel), desc);
g_object_unref (desc);
g_free (title);
gtk_delayed_font_description_unref (desc);
if ((priv->level & GTK_FONT_CHOOSER_LEVEL_STYLE) == 0)
break;
@@ -990,46 +980,28 @@ gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser,
g_free (families);
rows_changed_cb (fontchooser);
g_signal_handlers_unblock_by_func (priv->filter_model, rows_changed_cb, fontchooser);
g_signal_handlers_unblock_by_func (priv->family_face_list, cursor_changed_cb, fontchooser);
/* now make sure the font list looks right */
if (!gtk_font_chooser_widget_find_font (fontchooser, priv->font_desc, &priv->font_iter))
memset (&priv->font_iter, 0, sizeof (GtkTreeIter));
if (!gtk_font_chooser_widget_find_font (fontchooser, priv->font_desc, &priv->font_position))
priv->font_position = GTK_INVALID_LIST_POSITION;
gtk_font_chooser_widget_ensure_selection (fontchooser);
}
static gboolean
visible_func (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer user_data)
visible_func (gpointer item, gpointer user_data)
{
GtkFontChooserWidgetPrivate *priv = user_data;
GtkDelayedFontDescription *desc = (GtkDelayedFontDescription *)item;
GtkFontChooserWidget *fontchooser = user_data;
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
gboolean result = TRUE;
const gchar *search_text;
gchar **split_terms;
gchar *font_name, *font_name_casefold;
gchar *font_name_casefold;
guint i;
if (priv->filter_func != NULL)
{
PangoFontFamily *family;
PangoFontFace *face;
gtk_tree_model_get (model, iter,
FAMILY_COLUMN, &family,
FACE_COLUMN, &face,
-1);
result = priv->filter_func (family, face, priv->filter_data);
g_object_unref (family);
g_object_unref (face);
if (!result)
if (!priv->filter_func (desc->family, desc->face, priv->filter_data))
return FALSE;
}
@@ -1038,15 +1010,11 @@ visible_func (GtkTreeModel *model,
if (strlen (search_text) == 0)
return TRUE;
gtk_tree_model_get (model, iter,
PREVIEW_TITLE_COLUMN, &font_name,
-1);
if (font_name == NULL)
if (desc->title == NULL)
return FALSE;
split_terms = g_strsplit (search_text, " ", 0);
font_name_casefold = g_utf8_casefold (font_name, -1);
font_name_casefold = g_utf8_casefold (desc->title, -1);
for (i = 0; split_terms[i] && result; i++)
{
@@ -1059,7 +1027,6 @@ visible_func (GtkTreeModel *model,
}
g_free (font_name_casefold);
g_free (font_name);
g_strfreev (split_terms);
return result;
@@ -1069,11 +1036,10 @@ visible_func (GtkTreeModel *model,
static int
gtk_font_chooser_widget_get_preview_text_height (GtkFontChooserWidget *fontchooser)
{
GtkWidget *treeview = fontchooser->priv->family_face_list;
GtkStyleContext *context;
double dpi, font_size;
context = gtk_widget_get_style_context (treeview);
context = gtk_widget_get_style_context (fontchooser->priv->family_face_listview);
dpi = _gtk_css_number_value_get (_gtk_style_context_peek_property (context,
GTK_CSS_PROPERTY_DPI),
100);
@@ -1105,65 +1071,6 @@ gtk_font_chooser_widget_get_preview_attributes (GtkFontChooserWidget *font
return attrs;
}
static void
gtk_font_chooser_widget_cell_data_func (GtkTreeViewColumn *column,
GtkCellRenderer *cell,
GtkTreeModel *tree_model,
GtkTreeIter *iter,
gpointer user_data)
{
GtkFontChooserWidget *fontchooser = user_data;
GtkDelayedFontDescription *desc;
PangoAttrList *attrs;
char *preview_title;
gtk_tree_model_get (tree_model, iter,
PREVIEW_TITLE_COLUMN, &preview_title,
FONT_DESC_COLUMN, &desc,
-1);
attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser,
gtk_delayed_font_description_get (desc));
g_object_set (cell,
"xpad", 20,
"ypad", 10,
"attributes", attrs,
"text", preview_title,
NULL);
gtk_delayed_font_description_unref (desc);
pango_attr_list_unref (attrs);
g_free (preview_title);
}
static void
gtk_font_chooser_widget_set_cell_size (GtkFontChooserWidget *fontchooser)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
PangoAttrList *attrs;
GtkRequisition size;
gtk_cell_renderer_set_fixed_size (priv->family_face_cell, -1, -1);
attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser, NULL);
g_object_set (priv->family_face_cell,
"xpad", 20,
"ypad", 10,
"attributes", attrs,
"text", "x",
NULL);
pango_attr_list_unref (attrs);
gtk_cell_renderer_get_preferred_size (priv->family_face_cell,
priv->family_face_list,
&size,
NULL);
gtk_cell_renderer_set_fixed_size (priv->family_face_cell, size.width, size.height);
}
static void
gtk_font_chooser_widget_finalize (GObject *object)
{
@@ -1188,6 +1095,10 @@ gtk_font_chooser_widget_finalize (GObject *object)
g_free (priv->font_features);
g_object_unref (priv->selectionmodel);
g_object_unref (priv->filtermodel);
g_object_unref (priv->listmodel);
G_OBJECT_CLASS (gtk_font_chooser_widget_parent_class)->finalize (object);
}
@@ -1201,53 +1112,40 @@ my_pango_font_family_equal (const char *familya,
static gboolean
gtk_font_chooser_widget_find_font (GtkFontChooserWidget *fontchooser,
const PangoFontDescription *font_desc,
/* out arguments */
GtkTreeIter *iter)
guint *position)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
gboolean valid;
int i;
if (pango_font_description_get_family (font_desc) == NULL)
return FALSE;
for (valid = gtk_tree_model_get_iter_first (priv->model, iter);
valid;
valid = gtk_tree_model_iter_next (priv->model, iter))
for (i = 0; i < g_list_model_get_n_items (priv->selectionmodel); i++)
{
GtkDelayedFontDescription *desc;
PangoFontDescription *merged;
PangoFontFamily *family;
gtk_tree_model_get (priv->model, iter,
FAMILY_COLUMN, &family,
FONT_DESC_COLUMN, &desc,
-1);
desc = (GtkDelayedFontDescription *)g_list_model_get_item (priv->selectionmodel, i);
if (!my_pango_font_family_equal (pango_font_description_get_family (font_desc),
pango_font_family_get_name (family)))
{
gtk_delayed_font_description_unref (desc);
g_object_unref (family);
continue;
}
pango_font_family_get_name (desc->family)))
continue;
merged = pango_font_description_copy_static (gtk_delayed_font_description_get (desc));
pango_font_description_merge_static (merged, font_desc, FALSE);
if (pango_font_description_equal (merged, font_desc))
{
gtk_delayed_font_description_unref (desc);
pango_font_description_free (merged);
g_object_unref (family);
break;
*position = i;
return TRUE;
}
gtk_delayed_font_description_unref (desc);
pango_font_description_free (merged);
g_object_unref (family);
}
return valid;
*position = GTK_INVALID_LIST_POSITION;
return FALSE;
}
static void
@@ -1288,36 +1186,24 @@ static PangoFontFamily *
gtk_font_chooser_widget_get_family (GtkFontChooser *chooser)
{
GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser);
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
PangoFontFamily *family;
GtkDelayedFontDescription *desc = gtk_font_chooser_widget_get_desc (fontchooser);
if (!gtk_list_store_iter_is_valid (GTK_LIST_STORE (priv->model), &priv->font_iter))
return NULL;
if (desc)
return desc->family;
gtk_tree_model_get (priv->model, &priv->font_iter,
FAMILY_COLUMN, &family,
-1);
g_object_unref (family);
return family;
return NULL;
}
static PangoFontFace *
gtk_font_chooser_widget_get_face (GtkFontChooser *chooser)
{
GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser);
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
PangoFontFace *face;
GtkDelayedFontDescription *desc = gtk_font_chooser_widget_get_desc (fontchooser);
if (!gtk_list_store_iter_is_valid (GTK_LIST_STORE (priv->model), &priv->font_iter))
return NULL;
if (desc)
return desc->face;
gtk_tree_model_get (priv->model, &priv->font_iter,
FACE_COLUMN, &face,
-1);
g_object_unref (face);
return face;
return NULL;
}
static gint
@@ -1347,10 +1233,8 @@ static PangoFontDescription *
gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *fontchooser)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->family_face_list));
if (gtk_tree_selection_count_selected_rows (selection) > 0)
if (priv->font_position != GTK_INVALID_LIST_POSITION)
return fontchooser->priv->font_desc;
return NULL;
@@ -1368,35 +1252,27 @@ gtk_font_chooser_widget_set_font (GtkFontChooserWidget *fontchooser,
static void
gtk_font_chooser_widget_update_font_name (GtkFontChooserWidget *fontchooser,
GtkTreeSelection *selection)
GtkSelectionModel *selection)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
GtkTreeModel *model;
GtkTreeIter iter;
PangoFontFamily *family;
PangoFontFace *face;
GtkDelayedFontDescription *desc;
const PangoFontDescription *font_desc;
PangoAttrList *attrs;
const char *fam_name;
const char *face_name;
char *title;
guint position;
gtk_tree_selection_get_selected (selection, &model, &iter);
gtk_tree_model_get (model, &iter,
FAMILY_COLUMN, &family,
FACE_COLUMN, &face,
FONT_DESC_COLUMN, &desc,
-1);
position = gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (selection));
desc = (GtkDelayedFontDescription *)g_list_model_get_item (G_LIST_MODEL (selection), position);
fam_name = pango_font_family_get_name (family);
face_name = pango_font_face_get_face_name (face);
if (!desc)
return;
fam_name = pango_font_family_get_name (desc->family);
face_name = pango_font_face_get_face_name (desc->face);
font_desc = gtk_delayed_font_description_get (desc);
g_object_unref (family);
g_object_unref (face);
gtk_delayed_font_description_unref (desc);
if ((priv->level & GTK_FONT_CHOOSER_LEVEL_STYLE) != 0)
title = g_strconcat (fam_name, " ", face_name, NULL);
else
@@ -1411,18 +1287,32 @@ gtk_font_chooser_widget_update_font_name (GtkFontChooserWidget *fontchooser,
}
static void
selection_changed (GtkTreeSelection *selection,
selection_changed (GtkSelectionModel *model,
guint position,
guint n_items,
GtkFontChooserWidget *fontchooser)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
guint selected;
g_object_notify (G_OBJECT (fontchooser), "font");
g_object_notify (G_OBJECT (fontchooser), "font-desc");
if (gtk_tree_selection_count_selected_rows (selection) > 0)
selected = gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (model));
if (selected != GTK_INVALID_LIST_POSITION)
{
gtk_font_chooser_widget_update_font_name (fontchooser, selection);
GtkDelayedFontDescription *desc = (GtkDelayedFontDescription *)g_list_model_get_item (priv->selectionmodel, position);
gtk_font_chooser_widget_update_font_name (fontchooser, model);
g_simple_action_set_enabled (G_SIMPLE_ACTION (priv->tweak_action), TRUE);
if (desc)
{
pango_font_description_set_variations (priv->font_desc, NULL);
gtk_font_chooser_widget_merge_font_desc (fontchooser,
gtk_delayed_font_description_get (desc),
selected);
}
}
else
{
@@ -1434,29 +1324,6 @@ selection_changed (GtkTreeSelection *selection,
static void
gtk_font_chooser_widget_ensure_selection (GtkFontChooserWidget *fontchooser)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
GtkTreeSelection *selection;
GtkTreeIter filter_iter;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->family_face_list));
if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (priv->model), &priv->font_iter) &&
gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter_model),
&filter_iter,
&priv->font_iter))
{
GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->filter_model),
&filter_iter);
gtk_tree_selection_select_iter (selection, &filter_iter);
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->family_face_list),
path, NULL, FALSE, 0.0, 0.0);
gtk_tree_path_free (path);
}
else
{
gtk_tree_selection_unselect_all (selection);
}
}
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
@@ -2265,13 +2132,12 @@ update_font_features (GtkFontChooserWidget *fontchooser)
static void
gtk_font_chooser_widget_merge_font_desc (GtkFontChooserWidget *fontchooser,
const PangoFontDescription *font_desc,
GtkTreeIter *iter)
guint position)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
PangoFontMask mask;
g_assert (font_desc != NULL);
/* iter may be NULL if the font doesn't exist on the list */
mask = pango_font_description_get_set_fields (font_desc);
@@ -2295,13 +2161,9 @@ gtk_font_chooser_widget_merge_font_desc (GtkFontChooserWidget *fontchooser
{
gboolean has_tweak = FALSE;
if (&priv->font_iter != iter)
if (priv->font_position != position)
{
if (iter == NULL)
memset (&priv->font_iter, 0, sizeof (GtkTreeIter));
else
memcpy (&priv->font_iter, iter, sizeof (GtkTreeIter));
priv->font_position = position;
gtk_font_chooser_widget_ensure_selection (fontchooser);
}
@@ -2336,16 +2198,16 @@ gtk_font_chooser_widget_take_font_desc (GtkFontChooserWidget *fontchooser,
if (mask & (PANGO_FONT_MASK_FAMILY | PANGO_FONT_MASK_STYLE | PANGO_FONT_MASK_VARIANT |
PANGO_FONT_MASK_WEIGHT | PANGO_FONT_MASK_STRETCH))
{
GtkTreeIter iter;
guint pos;
if (gtk_font_chooser_widget_find_font (fontchooser, font_desc, &iter))
gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, &iter);
if (gtk_font_chooser_widget_find_font (fontchooser, font_desc, &pos))
gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, pos);
else
gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, NULL);
gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, GTK_INVALID_LIST_POSITION);
}
else
{
gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, &priv->font_iter);
gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, priv->font_position);
}
pango_font_description_free (font_desc);
@@ -2370,9 +2232,7 @@ gtk_font_chooser_widget_set_preview_text (GtkFontChooserWidget *fontchooser,
g_object_notify (G_OBJECT (fontchooser), "preview-text");
/* XXX: There's no API to tell the treeview that a column has changed,
* so we just */
gtk_widget_queue_draw (priv->family_face_list);
gtk_widget_queue_draw (priv->family_face_listview);
}
static gboolean
@@ -2414,7 +2274,7 @@ gtk_font_chooser_widget_set_font_map (GtkFontChooser *chooser,
if (!fontmap)
fontmap = pango_cairo_font_map_get_default ();
context = gtk_widget_get_pango_context (priv->family_face_list);
context = gtk_widget_get_pango_context (priv->family_face_listview);
pango_context_set_font_map (context, fontmap);
context = gtk_widget_get_pango_context (priv->preview);

459
gtk/gtklistitem.c Normal file
View File

@@ -0,0 +1,459 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistitemprivate.h"
#include "gtkcssnodeprivate.h"
#include "gtkgesturemultipress.h"
#include "gtkintl.h"
#include "gtkmain.h"
#include "gtkwidgetprivate.h"
/**
* SECTION:gtklistitem
* @title: GtkListItem
* @short_description: Widget used to represent items of a ListModel
* @see_also: #GtkListView, #GListModel
*
* #GtkListItem is the widget that GTK list-handling containers such
* as #GtkListView create to represent items in a #GListModel.
* They are managed by the container and cannot be created by application
* code.
*
* #GtkListIems are container widgets that need to be populated by
* application code. The container provides functions to do that.
*
* #GtkListItems exist in 2 stages:
*
* 1. The unbound stage where the listitem is not currently connected to
* an item in the list. In that case, the GtkListItem:item property is
* set to %NULL.
*
* 2. The bound stage where the listitem references an item from the list.
* The GtkListItem:item property is not %NULL.
*/
struct _GtkListItem
{
GtkBin parent_instance;
GtkListItemManager *manager; /* no ref, the manager refs us */
GObject *item;
guint position;
guint selectable : 1;
guint selected : 1;
};
enum
{
PROP_0,
PROP_ITEM,
PROP_POSITION,
PROP_SELECTABLE,
PROP_SELECTED,
N_PROPS
};
G_DEFINE_TYPE (GtkListItem, gtk_list_item, GTK_TYPE_BIN)
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
gtk_list_item_dispose (GObject *object)
{
GtkListItem *self = GTK_LIST_ITEM (object);
g_assert (self->item == NULL);
G_OBJECT_CLASS (gtk_list_item_parent_class)->dispose (object);
}
static void
gtk_list_item_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkListItem *self = GTK_LIST_ITEM (object);
switch (property_id)
{
case PROP_ITEM:
g_value_set_object (value, self->item);
break;
case PROP_POSITION:
g_value_set_uint (value, self->position);
break;
case PROP_SELECTABLE:
g_value_set_boolean (value, self->selectable);
break;
case PROP_SELECTED:
g_value_set_boolean (value, self->selected);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_item_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkListItem *self = GTK_LIST_ITEM (object);
switch (property_id)
{
case PROP_SELECTABLE:
gtk_list_item_set_selectable (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_item_class_init (GtkListItemClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = gtk_list_item_dispose;
gobject_class->get_property = gtk_list_item_get_property;
gobject_class->set_property = gtk_list_item_set_property;
/**
* GtkListItem:item:
*
* Displayed item
*/
properties[PROP_ITEM] =
g_param_spec_object ("item",
P_("Item"),
P_("Displayed item"),
G_TYPE_OBJECT,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListItem:position:
*
* Position in the item
*/
properties[PROP_POSITION] =
g_param_spec_uint ("position",
P_("Position"),
P_("Position of the item"),
0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListItem:selectable:
*
* If the item can be selected by the user
*/
properties[PROP_SELECTABLE] =
g_param_spec_boolean ("selectable",
P_("Selectable"),
P_("If the item can be selected by the user"),
TRUE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListItem:selected:
*
* If the item is currently selected
*/
properties[PROP_SELECTED] =
g_param_spec_boolean ("selected",
P_("Selected"),
P_("If the item is currently selected"),
FALSE,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/* This gets overwritten by gtk_list_item_new() but better safe than sorry */
gtk_widget_class_set_css_name (widget_class, I_("row"));
}
static void
gtk_list_item_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
int n_press,
double x,
double y,
GtkListItem *self)
{
GtkWidget *widget = GTK_WIDGET (self);
GdkModifierType state;
GdkModifierType mask;
gboolean extend = FALSE, modify = FALSE;
if (!self->selectable)
{
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
return;
}
if (gtk_get_current_event_state (&state))
{
mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_MODIFY_SELECTION);
if ((state & mask) == mask)
modify = TRUE;
mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_EXTEND_SELECTION);
if ((state & mask) == mask)
extend = TRUE;
}
gtk_list_item_manager_select (self->manager, self, modify, extend);
gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_ACTIVE, FALSE);
if (gtk_widget_get_focus_on_click (widget))
gtk_widget_grab_focus (widget);
}
static void
gtk_list_item_multipress_gesture_released (GtkGestureMultiPress *gesture,
int n_press,
double x,
double y,
GtkListItem *self)
{
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
}
static void
gtk_list_item_multipress_gesture_canceled (GtkGestureMultiPress *gesture,
GdkEventSequence *sequence,
GtkListItem *self)
{
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
}
static void
gtk_list_item_init (GtkListItem *self)
{
GtkGesture *gesture;
gtk_widget_set_has_surface (GTK_WIDGET (self), FALSE);
self->selectable = TRUE;
gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
gesture = gtk_gesture_multi_press_new ();
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
GTK_PHASE_BUBBLE);
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
FALSE);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
GDK_BUTTON_PRIMARY);
g_signal_connect (gesture, "pressed",
G_CALLBACK (gtk_list_item_multipress_gesture_pressed), self);
g_signal_connect (gesture, "released",
G_CALLBACK (gtk_list_item_multipress_gesture_released), self);
g_signal_connect (gesture, "cancel",
G_CALLBACK (gtk_list_item_multipress_gesture_canceled), self);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
}
GtkListItem *
gtk_list_item_new (GtkListItemManager *manager,
const char *css_name)
{
GtkListItem *result;
g_return_val_if_fail (css_name != NULL, NULL);
result = g_object_new (GTK_TYPE_LIST_ITEM,
"css-name", css_name,
NULL);
result->manager = manager;
return result;
}
/**
* gtk_list_item_get_item:
* @self: a #GtkListItem
*
* Gets the item that is currently displayed in model that @self is
* currently bound to or %NULL if @self is unbound.
*
* Returns: (nullable) (transfer none) (type GObject): The item displayed
**/
gpointer
gtk_list_item_get_item (GtkListItem *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM (self), NULL);
return self->item;
}
/**
* gtk_list_item_get_position:
* @self: a #GtkListItem
*
* Gets the position in the model that @self currently displays.
* If @self is unbound, 0 is returned.
*
* Returns: The position of this item
**/
guint
gtk_list_item_get_position (GtkListItem *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM (self), 0);
return self->position;
}
void
gtk_list_item_set_item (GtkListItem *self,
gpointer item)
{
g_return_if_fail (GTK_IS_LIST_ITEM (self));
g_return_if_fail (item == NULL || G_IS_OBJECT (item));
if (self->item == item)
return;
g_clear_object (&self->item);
if (item)
self->item = g_object_ref (item);
gtk_css_node_invalidate (gtk_widget_get_css_node (GTK_WIDGET (self)), GTK_CSS_CHANGE_ANIMATIONS);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
}
void
gtk_list_item_set_position (GtkListItem *self,
guint position)
{
g_return_if_fail (GTK_IS_LIST_ITEM (self));
if (self->position == position)
return;
self->position = position;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_POSITION]);
}
/**
* gtk_list_item_get_selected:
* @self: a #GtkListItem
*
* Checks if the item is displayed as selected. The selected state is
* maintained by the container and its list model and cannot be set
* otherwise.
*
* Returns: %TRUE if the item is selected.
**/
gboolean
gtk_list_item_get_selected (GtkListItem *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM (self), FALSE);
return self->selected;
}
void
gtk_list_item_set_selected (GtkListItem *self,
gboolean selected)
{
g_return_if_fail (GTK_IS_LIST_ITEM (self));
if (self->selected == selected)
return;
self->selected = selected;
if (selected)
gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE);
else
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
}
/**
* gtk_list_item_get_selectable:
* @self: a #GtkListItem
*
* Checks if a list item has been set to be selectable via
* gtk_list_item_set_selectable().
*
* Do not confuse this function with gtk_list_item_get_selected().
*
* Returns: %TRUE if the item is selectable
**/
gboolean
gtk_list_item_get_selectable (GtkListItem *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM (self), FALSE);
return self->selectable;
}
/**
* gtk_list_item_set_selectable:
* @self: a #GtkListItem
* @selectable: if the item should be selectable
*
* Sets @self to be selectable. If an item is selectable, clicking
* on the item or using the keyboard will try to select or unselect
* the item. If this succeeds is up to the model to determine, as
* it is managing the selected state.
*
* Note that this means that making an item non-selectable has no
* influence on the selected state at all. A non-selectable item
* may still be selected.
*
* By default, list items are selectable. When rebinding them to
* a new item, they will also be reset to be selectable by GTK.
**/
void
gtk_list_item_set_selectable (GtkListItem *self,
gboolean selectable)
{
g_return_if_fail (GTK_IS_LIST_ITEM (self));
if (self->selectable == selectable)
return;
self->selectable = selectable;
gtk_widget_set_can_focus (GTK_WIDGET (self), self->selectable);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTABLE]);
}

51
gtk/gtklistitem.h Normal file
View File

@@ -0,0 +1,51 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_ITEM_H__
#define __GTK_LIST_ITEM_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkbin.h>
G_BEGIN_DECLS
#define GTK_TYPE_LIST_ITEM (gtk_list_item_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkListItem, gtk_list_item, GTK, LIST_ITEM, GtkBin)
GDK_AVAILABLE_IN_ALL
gpointer gtk_list_item_get_item (GtkListItem *self);
GDK_AVAILABLE_IN_ALL
guint gtk_list_item_get_position (GtkListItem *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_list_item_get_selected (GtkListItem *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_list_item_get_selectable (GtkListItem *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_item_set_selectable (GtkListItem *self,
gboolean selectable);
G_END_DECLS
#endif /* __GTK_LIST_ITEM_H__ */

150
gtk/gtklistitemfactory.c Normal file
View File

@@ -0,0 +1,150 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistitemfactoryprivate.h"
#include "gtklistitemprivate.h"
struct _GtkListItemFactory
{
GObject parent_instance;
GtkListItemSetupFunc setup_func;
GtkListItemBindFunc bind_func;
gpointer user_data;
GDestroyNotify user_destroy;
};
struct _GtkListItemFactoryClass
{
GObjectClass parent_class;
};
G_DEFINE_TYPE (GtkListItemFactory, gtk_list_item_factory, G_TYPE_OBJECT)
static void
gtk_list_item_factory_finalize (GObject *object)
{
GtkListItemFactory *self = GTK_LIST_ITEM_FACTORY (object);
if (self->user_destroy)
self->user_destroy (self->user_data);
G_OBJECT_CLASS (gtk_list_item_factory_parent_class)->finalize (object);
}
static void
gtk_list_item_factory_class_init (GtkListItemFactoryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gtk_list_item_factory_finalize;
}
static void
gtk_list_item_factory_init (GtkListItemFactory *self)
{
}
GtkListItemFactory *
gtk_list_item_factory_new (GtkListItemSetupFunc setup_func,
GtkListItemBindFunc bind_func,
gpointer user_data,
GDestroyNotify user_destroy)
{
GtkListItemFactory *self;
g_return_val_if_fail (setup_func || bind_func, NULL);
g_return_val_if_fail (user_data != NULL || user_destroy == NULL, NULL);
self = g_object_new (GTK_TYPE_LIST_ITEM_FACTORY, NULL);
self->setup_func = setup_func;
self->bind_func = bind_func;
self->user_data = user_data;
self->user_destroy = user_destroy;
return self;
}
void
gtk_list_item_factory_setup (GtkListItemFactory *self,
GtkListItem *list_item)
{
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self));
if (self->setup_func)
self->setup_func (list_item, self->user_data);
}
void
gtk_list_item_factory_bind (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected)
{
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self));
g_return_if_fail (GTK_IS_LIST_ITEM (list_item));
g_object_freeze_notify (G_OBJECT (list_item));
gtk_list_item_set_item (list_item, item);
gtk_list_item_set_position (list_item, position);
gtk_list_item_set_selected (list_item, selected);
if (self->bind_func)
self->bind_func (list_item, self->user_data);
g_object_thaw_notify (G_OBJECT (list_item));
}
void
gtk_list_item_factory_update (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gboolean selected)
{
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self));
g_return_if_fail (GTK_IS_LIST_ITEM (list_item));
g_object_freeze_notify (G_OBJECT (list_item));
gtk_list_item_set_position (list_item, position);
gtk_list_item_set_selected (list_item, selected);
g_object_thaw_notify (G_OBJECT (list_item));
}
void
gtk_list_item_factory_unbind (GtkListItemFactory *self,
GtkListItem *list_item)
{
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self));
g_return_if_fail (GTK_IS_LIST_ITEM (list_item));
g_object_freeze_notify (G_OBJECT (list_item));
gtk_list_item_set_item (list_item, NULL);
gtk_list_item_set_position (list_item, 0);
g_object_thaw_notify (G_OBJECT (list_item));
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_ITEM_FACTORY_H__
#define __GTK_LIST_ITEM_FACTORY_H__
#include <gtk/gtklistitem.h>
#include <gtk/gtklistview.h>
G_BEGIN_DECLS
#define GTK_TYPE_LIST_ITEM_FACTORY (gtk_list_item_factory_get_type ())
#define GTK_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_ITEM_FACTORY, GtkListItemFactory))
#define GTK_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_ITEM_FACTORY, GtkListItemFactoryClass))
#define GTK_IS_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_ITEM_FACTORY))
#define GTK_IS_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_ITEM_FACTORY))
#define GTK_LIST_ITEM_FACTORY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_ITEM_FACTORY, GtkListItemFactoryClass))
typedef struct _GtkListItemFactory GtkListItemFactory;
typedef struct _GtkListItemFactoryClass GtkListItemFactoryClass;
GType gtk_list_item_factory_get_type (void) G_GNUC_CONST;
GtkListItemFactory * gtk_list_item_factory_new (GtkListItemSetupFunc setup_func,
GtkListItemBindFunc bind_func,
gpointer user_data,
GDestroyNotify user_destroy);
void gtk_list_item_factory_setup (GtkListItemFactory *self,
GtkListItem *list_item);
void gtk_list_item_factory_bind (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected);
void gtk_list_item_factory_update (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gboolean selected);
void gtk_list_item_factory_unbind (GtkListItemFactory *self,
GtkListItem *list_item);
G_END_DECLS
#endif /* __GTK_LIST_ITEM_FACTORY_H__ */

423
gtk/gtklistitemmanager.c Normal file
View File

@@ -0,0 +1,423 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistitemmanagerprivate.h"
#include "gtklistitemprivate.h"
#include "gtkwidgetprivate.h"
struct _GtkListItemManager
{
GObject parent_instance;
GtkWidget *widget;
GtkSelectionModel *model;
GtkListItemFactory *factory;
};
struct _GtkListItemManagerClass
{
GObjectClass parent_class;
};
struct _GtkListItemManagerChange
{
GHashTable *items;
};
G_DEFINE_TYPE (GtkListItemManager, gtk_list_item_manager, G_TYPE_OBJECT)
static void
gtk_list_item_manager_dispose (GObject *object)
{
GtkListItemManager *self = GTK_LIST_ITEM_MANAGER (object);
g_clear_object (&self->model);
g_clear_object (&self->factory);
G_OBJECT_CLASS (gtk_list_item_manager_parent_class)->dispose (object);
}
static void
gtk_list_item_manager_class_init (GtkListItemManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_list_item_manager_dispose;
}
static void
gtk_list_item_manager_init (GtkListItemManager *self)
{
}
GtkListItemManager *
gtk_list_item_manager_new (GtkWidget *widget)
{
GtkListItemManager *self;
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
self = g_object_new (GTK_TYPE_LIST_ITEM_MANAGER, NULL);
self->widget = widget;
return self;
}
void
gtk_list_item_manager_set_factory (GtkListItemManager *self,
GtkListItemFactory *factory)
{
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory));
if (self->factory == factory)
return;
g_clear_object (&self->factory);
self->factory = g_object_ref (factory);
}
GtkListItemFactory *
gtk_list_item_manager_get_factory (GtkListItemManager *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
return self->factory;
}
void
gtk_list_item_manager_set_model (GtkListItemManager *self,
GtkSelectionModel *model)
{
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
g_clear_object (&self->model);
if (model)
self->model = g_object_ref (model);
}
GtkSelectionModel *
gtk_list_item_manager_get_model (GtkListItemManager *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
return self->model;
}
void
gtk_list_item_manager_select (GtkListItemManager *self,
GtkListItem *item,
gboolean modify,
gboolean extend)
{
guint pos = gtk_list_item_get_position (item);
if (modify)
{
if (gtk_list_item_get_selected (item))
gtk_selection_model_unselect_item (self->model, pos);
else
gtk_selection_model_select_item (self->model, pos, FALSE, extend);
}
else
{
gtk_selection_model_select_item (self->model, pos, TRUE, extend);
}
}
#if 0
/*
* gtk_list_item_manager_get_size:
* @self: a #GtkListItemManager
*
* Queries the number of widgets currently handled by @self.
*
* This includes both widgets that have been acquired and
* those currently waiting to be used again.
*
* Returns: Number of widgets handled by @self
**/
guint
gtk_list_item_manager_get_size (GtkListItemManager *self)
{
return g_hash_table_size (self->pool);
}
#endif
/*
* gtk_list_item_manager_begin_change:
* @self: a #GtkListItemManager
*
* Begins a change operation in response to a model's items-changed
* signal.
* During an ongoing change operation, list items will not be discarded
* when released but will be kept around in anticipation of them being
* added back in a different posiion later.
*
* Once it is known that no more list items will be reused,
* gtk_list_item_manager_end_change() should be called. This should happen
* as early as possible, so the list items held for the change can be
* reqcquired.
*
* Returns: The object to use for this change
**/
GtkListItemManagerChange *
gtk_list_item_manager_begin_change (GtkListItemManager *self)
{
GtkListItemManagerChange *change;
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
change = g_slice_new (GtkListItemManagerChange);
change->items = g_hash_table_new (g_direct_hash, g_direct_equal);
return change;
}
/*
* gtk_list_item_manager_end_change:
* @self: a #GtkListItemManager
* @change: a change
*
* Ends a change operation begun with gtk_list_item_manager_begin_change()
* and releases all list items still cached.
**/
void
gtk_list_item_manager_end_change (GtkListItemManager *self,
GtkListItemManagerChange *change)
{
GHashTableIter iter;
gpointer list_item;
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
g_hash_table_iter_init (&iter, change->items);
while (g_hash_table_iter_next (&iter, NULL, &list_item))
{
gtk_list_item_manager_release_list_item (self, NULL, list_item);
}
g_hash_table_unref (change->items);
g_slice_free (GtkListItemManagerChange, change);
}
/*
* gtk_list_item_manager_change_contains:
* @change: a #GtkListItemManagerChange
* @list_item: The item that may have been released into this change set
*
* Checks if @list_item has been released as part of @change but not been
* reacquired yet.
*
* This is useful to test before calling gtk_list_item_manager_end_change()
* if special actions need to be performed when important list items - like
* the focused item - are about to be deleted.
*
* Returns: %TRUE if the item is part of this change
**/
gboolean
gtk_list_item_manager_change_contains (GtkListItemManagerChange *change,
GtkWidget *list_item)
{
g_return_val_if_fail (change != NULL, FALSE);
g_return_val_if_fail (GTK_IS_LIST_ITEM (list_item), FALSE);
return g_hash_table_lookup (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (list_item))) == list_item;
}
/*
* gtk_list_item_manager_acquire_list_item:
* @self: a #GtkListItemManager
* @position: the row in the model to create a list item for
* @prev_sibling: the widget this widget should be inserted before or %NULL
* if it should be the first widget
*
* Creates a list item widget to use for @position. No widget may
* yet exist that is used for @position.
*
* When the returned item is no longer needed, the caller is responsible
* for calling gtk_list_item_manager_release_list_item().
* A particular case is when the row at @position is removed. In that case,
* all list items in the removed range must be released before
* gtk_list_item_manager_model_changed() is called.
*
* Returns: a properly setup widget to use in @position
**/
GtkWidget *
gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
guint position,
GtkWidget *prev_sibling)
{
GtkListItem *result;
gpointer item;
gboolean selected;
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
result = gtk_list_item_new (self, "row");
gtk_list_item_factory_setup (self->factory, result);
item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
selected = gtk_selection_model_is_selected (self->model, position);
gtk_list_item_factory_bind (self->factory, result, position, item, selected);
g_object_unref (item);
gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling);
return GTK_WIDGET (result);
}
/**
* gtk_list_item_manager_try_acquire_list_item_from_change:
* @self: a #GtkListItemManager
* @position: the row in the model to create a list item for
* @prev_sibling: the widget this widget should be inserted after or %NULL
* if it should be the first widget
*
* Like gtk_list_item_manager_acquire_list_item(), but only tries to acquire list
* items from those previously released as part of @change.
* If no matching list item is found, %NULL is returned and the caller should use
* gtk_list_item_manager_acquire_list_item().
*
* Returns: (nullable): a properly setup widget to use in @position or %NULL if
* no item for reuse existed
**/
GtkWidget *
gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self,
GtkListItemManagerChange *change,
guint position,
GtkWidget *prev_sibling)
{
GtkListItem *result;
gpointer item;
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
/* XXX: can we avoid temporarily allocating items on failure? */
item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
if (g_hash_table_steal_extended (change->items, item, NULL, (gpointer *) &result))
{
gtk_list_item_factory_update (self->factory, result, position, FALSE);
gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling);
/* XXX: Should we let the listview do this? */
gtk_widget_queue_resize (GTK_WIDGET (result));
}
else
{
result = NULL;
}
g_object_unref (item);
return GTK_WIDGET (result);
}
/**
* gtk_list_item_manager_move_list_item:
* @self: a #GtkListItemManager
* @list_item: an acquired #GtkListItem that should be moved to represent
* a different row
* @position: the new position of that list item
* @prev_sibling: the new previous sibling
*
* Moves the widget to represent a new position in the listmodel without
* releasing the item.
*
* This is most useful when scrolling.
**/
void
gtk_list_item_manager_move_list_item (GtkListItemManager *self,
GtkWidget *list_item,
guint position,
GtkWidget *prev_sibling)
{
gpointer item;
gboolean selected;
item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
selected = gtk_selection_model_is_selected (self->model, position);
gtk_list_item_factory_bind (self->factory, GTK_LIST_ITEM (list_item), position, item, selected);
gtk_widget_insert_after (list_item, _gtk_widget_get_parent (list_item), prev_sibling);
g_object_unref (item);
}
/**
* gtk_list_item_manager_update_list_item:
* @self: a #GtkListItemManager
* @item: a #GtkListItem that has been acquired
* @position: the new position of that list item
*
* Updates the position of the given @item. This function must be called whenever
* the position of an item changes, like when new items are added before it.
**/
void
gtk_list_item_manager_update_list_item (GtkListItemManager *self,
GtkWidget *item,
guint position)
{
gboolean selected;
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
g_return_if_fail (GTK_IS_LIST_ITEM (item));
selected = gtk_selection_model_is_selected (self->model, position);
gtk_list_item_factory_update (self->factory, GTK_LIST_ITEM (item), position, selected);
}
/*
* gtk_list_item_manager_release_list_item:
* @self: a #GtkListItemManager
* @change: (allow-none): The change associated with this release or
* %NULL if this is a final removal
* @item: an item previously acquired with
* gtk_list_item_manager_acquire_list_item()
*
* Releases an item that was previously acquired via
* gtk_list_item_manager_acquire_list_item() and is no longer in use.
**/
void
gtk_list_item_manager_release_list_item (GtkListItemManager *self,
GtkListItemManagerChange *change,
GtkWidget *item)
{
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
g_return_if_fail (GTK_IS_LIST_ITEM (item));
if (change != NULL)
{
if (g_hash_table_insert (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (item)), item))
return;
g_warning ("FIXME: Handle the same item multiple times in the list.\nLars says this totally should not happen, but here we are.");
}
gtk_list_item_factory_unbind (self->factory, GTK_LIST_ITEM (item));
gtk_widget_unparent (item);
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_ITEM_MANAGER_H__
#define __GTK_LIST_ITEM_MANAGER_H__
#include "gtk/gtktypes.h"
#include "gtk/gtklistitemfactoryprivate.h"
#include "gtk/gtkselectionmodel.h"
G_BEGIN_DECLS
#define GTK_TYPE_LIST_ITEM_MANAGER (gtk_list_item_manager_get_type ())
#define GTK_LIST_ITEM_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_ITEM_MANAGER, GtkListItemManager))
#define GTK_LIST_ITEM_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_ITEM_MANAGER, GtkListItemManagerClass))
#define GTK_IS_LIST_ITEM_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_ITEM_MANAGER))
#define GTK_IS_LIST_ITEM_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_ITEM_MANAGER))
#define GTK_LIST_ITEM_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_ITEM_MANAGER, GtkListItemManagerClass))
typedef struct _GtkListItemManager GtkListItemManager;
typedef struct _GtkListItemManagerClass GtkListItemManagerClass;
typedef struct _GtkListItemManagerChange GtkListItemManagerChange;
GType gtk_list_item_manager_get_type (void) G_GNUC_CONST;
GtkListItemManager * gtk_list_item_manager_new (GtkWidget *widget);
void gtk_list_item_manager_set_factory (GtkListItemManager *self,
GtkListItemFactory *factory);
GtkListItemFactory * gtk_list_item_manager_get_factory (GtkListItemManager *self);
void gtk_list_item_manager_set_model (GtkListItemManager *self,
GtkSelectionModel *model);
GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemManager *self);
guint gtk_list_item_manager_get_size (GtkListItemManager *self);
void gtk_list_item_manager_select (GtkListItemManager *self,
GtkListItem *item,
gboolean modify,
gboolean extend);
GtkListItemManagerChange *
gtk_list_item_manager_begin_change (GtkListItemManager *self);
void gtk_list_item_manager_end_change (GtkListItemManager *self,
GtkListItemManagerChange *change);
gboolean gtk_list_item_manager_change_contains (GtkListItemManagerChange *change,
GtkWidget *list_item);
GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
guint position,
GtkWidget *prev_sibling);
GtkWidget * gtk_list_item_manager_try_reacquire_list_item
(GtkListItemManager *self,
GtkListItemManagerChange *change,
guint position,
GtkWidget *prev_sibling);
void gtk_list_item_manager_update_list_item (GtkListItemManager *self,
GtkWidget *item,
guint position);
void gtk_list_item_manager_move_list_item (GtkListItemManager *self,
GtkWidget *list_item,
guint position,
GtkWidget *prev_sibling);
void gtk_list_item_manager_release_list_item (GtkListItemManager *self,
GtkListItemManagerChange *change,
GtkWidget *widget);
G_END_DECLS
#endif /* __GTK_LIST_ITEM_MANAGER_H__ */

41
gtk/gtklistitemprivate.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_ITEM_PRIVATE_H__
#define __GTK_LIST_ITEM_PRIVATE_H__
#include "gtklistitem.h"
#include "gtklistitemmanagerprivate.h"
G_BEGIN_DECLS
GtkListItem * gtk_list_item_new (GtkListItemManager *manager,
const char *css_name);
void gtk_list_item_set_item (GtkListItem *self,
gpointer item);
void gtk_list_item_set_position (GtkListItem *self,
guint position);
void gtk_list_item_set_selected (GtkListItem *self,
gboolean selected);
G_END_DECLS
#endif /* __GTK_LIST_ITEM_PRIVATE_H__ */

View File

@@ -206,15 +206,12 @@ gtk_list_list_model_new_with_size (GType item_type,
return result;
}
void
gtk_list_list_model_item_added (GtkListListModel *self,
gpointer item)
static guint
gtk_list_list_model_find (GtkListListModel *self,
gpointer item)
{
gpointer x;
guint position;
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
g_return_if_fail (item != NULL);
gpointer x;
position = 0;
for (x = self->get_first (self->data);
@@ -222,7 +219,17 @@ gtk_list_list_model_item_added (GtkListListModel *self,
x = self->get_next (x, self->data))
position++;
gtk_list_list_model_item_added_at (self, position);
return position;
}
void
gtk_list_list_model_item_added (GtkListListModel *self,
gpointer item)
{
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
g_return_if_fail (item != NULL);
gtk_list_list_model_item_added_at (self, gtk_list_list_model_find (self, item));
}
void
@@ -241,26 +248,49 @@ void
gtk_list_list_model_item_removed (GtkListListModel *self,
gpointer previous)
{
gpointer x;
guint position;
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
if (previous == NULL)
position = 0;
else
position = 1 + gtk_list_list_model_find (self, previous);
gtk_list_list_model_item_removed_at (self, position);
}
void
gtk_list_list_model_item_moved (GtkListListModel *self,
gpointer item,
gpointer previous_previous)
{
guint position, previous_position;
guint min, max;
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
g_return_if_fail (item != previous_previous);
position = gtk_list_list_model_find (self, item);
if (previous_previous == NULL)
{
position = 0;
previous_position = 0;
}
else
{
position = 1;
for (x = self->get_first (self->data);
x != previous;
x = self->get_next (x, self->data))
position++;
previous_position = gtk_list_list_model_find (self, previous_previous);
if (position > previous_position)
previous_position++;
}
gtk_list_list_model_item_removed_at (self, position);
/* item didn't move */
if (position == previous_position)
return;
min = MIN (position, previous_position);
max = MAX (position, previous_position) + 1;
g_list_model_items_changed (G_LIST_MODEL (self), min, max - min, max - min);
}
void

View File

@@ -64,6 +64,9 @@ void gtk_list_list_model_item_removed (GtkListListMode
gpointer previous);
void gtk_list_list_model_item_removed_at (GtkListListModel *self,
guint position);
void gtk_list_list_model_item_moved (GtkListListModel *self,
gpointer item,
gpointer previous_previous);
void gtk_list_list_model_clear (GtkListListModel *self);

1421
gtk/gtklistview.c Normal file

File diff suppressed because it is too large Load Diff

82
gtk/gtklistview.h Normal file
View File

@@ -0,0 +1,82 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_VIEW_H__
#define __GTK_LIST_VIEW_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkwidget.h>
#include <gtk/gtklistitem.h>
G_BEGIN_DECLS
/**
* GtkListItemSetupFunc:
* @item: the #GtkListItem to set up
* @user_data: (closure): user data
*
* Called whenever a new list item needs to be setup for managing a row in
* the list.
*
* At this point, the list item is not bound yet, so gtk_list_item_get_item()
* will return %NULL.
* The list item will later be bound to an item via the #GtkListItemBindFunc.
*/
typedef void (* GtkListItemSetupFunc) (GtkListItem *item, gpointer user_data);
/**
* GtkListItemBindFunc:
* @item: the #GtkListItem to bind
* @user_data: (closure): user data
*
* Binds a#GtkListItem previously set up via a #GtkListItemSetupFunc to
* an @item.
*
* Rebinding a @item to different @items is supported as well as
* unbinding it by setting @item to %NULL.
*/
typedef void (* GtkListItemBindFunc) (GtkListItem *item,
gpointer user_data);
#define GTK_TYPE_LIST_VIEW (gtk_list_view_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkListView, gtk_list_view, GTK, LIST_VIEW, GtkWidget)
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_list_view_new (void);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_list_view_get_model (GtkListView *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_set_model (GtkListView *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_set_functions (GtkListView *self,
GtkListItemSetupFunc setup_func,
GtkListItemBindFunc bind_func,
gpointer user_data,
GDestroyNotify user_destroy);
G_END_DECLS
#endif /* __GTK_LIST_VIEW_H__ */

View File

@@ -1633,11 +1633,11 @@ handle_pointing_event (GdkEvent *event)
if (event->any.type == GDK_BUTTON_RELEASE)
{
old_target = target;
target = gtk_widget_pick (GTK_WIDGET (toplevel), x, y);
if (target == NULL)
target = GTK_WIDGET (toplevel);
gtk_synthesize_crossing_events (toplevel, old_target, target, event,
GtkWidget *new_target;
new_target = gtk_widget_pick (GTK_WIDGET (toplevel), x, y);
if (new_target == NULL)
new_target = GTK_WIDGET (toplevel);
gtk_synthesize_crossing_events (toplevel, target, new_target, event,
GDK_CROSSING_UNGRAB);
gtk_window_maybe_update_cursor (toplevel, NULL, device);
}

374
gtk/gtkmultiselection.c Normal file
View File

@@ -0,0 +1,374 @@
/*
* 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;
};
/*
* We store a set of positions for selected items. This can be maintained
* efficiently as long as it consists of a small number of ranges. In
* degenerate cases such as 'every second item in the list', it will
* be O(|model|).
*
* To implement persistence across add/remove changes in the underlying
* model (for example, resorting), we mark the selected objects, which
* is also going to be O(|model|) in the 'select all' case.
*/
struct _GtkMultiSelectionClass
{
GObjectClass parent_class;
};
enum {
PROP_0,
/* selectionmodel */
PROP_MODEL,
N_PROPS = PROP_MODEL
};
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 void
mark_selected (GtkMultiSelection *self, gboolean in)
{
GtkSetIter iter;
guint pos;
gtk_set_iter_init (&iter, self->selected);
while (gtk_set_iter_next (&iter, &pos))
{
/* Mark the object as being selected in this multiselection.
* See gtk_multi_selection_items_changed_cb, where this is
* used to identify objects that were removed and readded.
*/
GObject *obj = g_list_model_get_item (self->model, pos);
g_object_set_data (obj, "GtkMultiSelection", in ? self : NULL);
g_object_unref (obj);
}
}
static void
mark_range (GtkMultiSelection *self, guint first, guint n_items, gboolean in)
{
guint pos;
for (pos = first; pos < first + n_items; pos++)
{
GObject *obj = g_list_model_get_item (self->model, pos);
g_object_set_data (obj, "GtkMultiSelection", in ? self : NULL);
g_object_unref (obj);
}
}
static gboolean
gtk_multi_selection_select_range (GtkSelectionModel *model,
guint position,
guint n_items,
gboolean exclusive)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
if (exclusive)
{
mark_selected (self, FALSE);
gtk_set_remove_all (self->selected);
}
mark_range (self, position, n_items, TRUE);
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);
mark_range (self, position, n_items, FALSE);
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,
gboolean extend)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
guint pos, n_items;
if (extend && self->last_selected != GTK_INVALID_LIST_POSITION)
{
pos = MIN (position, self->last_selected);
n_items = MAX (position, self->last_selected) - pos + 1;
}
else
{
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_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;
}
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)
{
guint pos;
gtk_set_remove_range (self->selected, position, removed);
gtk_set_shift (self->selected, position, (int)added - (int)removed);
for (pos = position; pos < position + added; pos++)
{
GObject *obj = g_list_model_get_item (self->model, pos);
if (g_object_get_data (obj, "GtkMultiSelection") == self)
gtk_set_add_item (self->selected, pos);
g_object_unref (obj);
}
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:
gtk_multi_selection_clear_model (self);
self->model = g_value_dup_object (value);
if (self->model)
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;
g_object_class_override_property (gobject_class, PROP_MODEL, "model");
}
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
**/
GtkMultiSelection *
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);
}

37
gtk/gtkmultiselection.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* 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>
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
GtkMultiSelection * gtk_multi_selection_new (GListModel *model);
G_END_DECLS
#endif /* __GTK_MULTI_SELECTION_H__ */

205
gtk/gtknoselection.c Normal file
View File

@@ -0,0 +1,205 @@
/*
* 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 "gtknoselection.h"
#include "gtkintl.h"
#include "gtkselectionmodel.h"
/**
* SECTION:gtknoselection
* @Short_description: A selection model that does not allow selecting items
* @Title: GtkNoSelection
* @see_also: #GtkSelectionModel
*
* GtkNoSelection is an implementation of the #GtkSelectionModel interface
* that never selects any items. It can be used where a #GtkSelectionModel
* is needed, but selection is not desired.
*/
struct _GtkNoSelection
{
GObject parent_instance;
GListModel *model;
};
struct _GtkNoSelectionClass
{
GObjectClass parent_class;
};
enum {
PROP_0,
/* selectionmodel */
PROP_MODEL,
N_PROPS = PROP_MODEL
};
static GType
gtk_no_selection_get_item_type (GListModel *list)
{
return g_list_model_get_item_type (GTK_NO_SELECTION (list)->model);
}
static guint
gtk_no_selection_get_n_items (GListModel *list)
{
return g_list_model_get_n_items (GTK_NO_SELECTION (list)->model);
}
static gpointer
gtk_no_selection_get_item (GListModel *list,
guint position)
{
return g_list_model_get_item (GTK_NO_SELECTION (list)->model, position);
}
static void
gtk_no_selection_list_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_no_selection_get_item_type;
iface->get_n_items = gtk_no_selection_get_n_items;
iface->get_item = gtk_no_selection_get_item;
}
static void
gtk_no_selection_selection_model_init (GtkSelectionModelInterface *iface)
{
/* the default implementation does what we want */
}
G_DEFINE_TYPE_EXTENDED (GtkNoSelection, gtk_no_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_no_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
gtk_no_selection_selection_model_init))
static void
gtk_no_selection_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkNoSelection *self)
{
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
}
static void
gtk_no_selection_clear_model (GtkNoSelection *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model,
gtk_no_selection_items_changed_cb,
self);
g_clear_object (&self->model);
}
static void
gtk_no_selection_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkNoSelection *self = GTK_NO_SELECTION (object);
switch (prop_id)
{
case PROP_MODEL:
gtk_no_selection_clear_model (self);
self->model = g_value_dup_object (value);
if (self->model)
g_signal_connect (self->model, "items-changed",
G_CALLBACK (gtk_no_selection_items_changed_cb), self);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_no_selection_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkNoSelection *self = GTK_NO_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_no_selection_dispose (GObject *object)
{
gtk_no_selection_clear_model (GTK_NO_SELECTION (object));
G_OBJECT_CLASS (gtk_no_selection_parent_class)->dispose (object);
}
static void
gtk_no_selection_class_init (GtkNoSelectionClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->get_property = gtk_no_selection_get_property;
gobject_class->set_property = gtk_no_selection_set_property;
gobject_class->dispose = gtk_no_selection_dispose;
g_object_class_override_property (gobject_class, PROP_MODEL, "model");
}
static void
gtk_no_selection_init (GtkNoSelection *self)
{
}
/**
* gtk_no_selection_new:
* @model: (transfer none): the #GListModel to manage
*
* Creates a new selection to handle @model.
*
* Returns: (transfer full) (type GtkNoSelection): a new #GtkNoSelection
**/
GtkNoSelection *
gtk_no_selection_new (GListModel *model)
{
g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
return g_object_new (GTK_TYPE_NO_SELECTION,
"model", model,
NULL);
}

37
gtk/gtknoselection.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* 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_NO_SELECTION_H__
#define __GTK_NO_SELECTION_H__
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_NO_SELECTION (gtk_no_selection_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkNoSelection, gtk_no_selection, GTK, NO_SELECTION, GObject)
GDK_AVAILABLE_IN_ALL
GtkNoSelection * gtk_no_selection_new (GListModel *model);
G_END_DECLS
#endif /* __GTK_NO_SELECTION_H__ */

290
gtk/gtkselectionmodel.c Normal file
View File

@@ -0,0 +1,290 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkselectionmodel.h"
#include "gtkintl.h"
#include "gtkmarshalers.h"
/**
* SECTION:gtkselectionmodel
* @Title: GtkSelectionModel
* @Short_description: An extension of the list model interface that handles selections
* @See_also: #GListModel, #GtkSingleSelection
*
* #GtkSelectionModel is an interface that extends the #GListModel interface by adding
* support for selections. This support is then used by widgets using list models to add
* the ability to select and unselect various items.
*
* GTK provides default implementations of the mode common selection modes such as
* #GtkSingleSelection, so you will only need to implement this interface if you want
* detailed control about how selections should be handled.
*
* A #GtkSelectionModel supports a single boolean per row indicating if a row is selected
* or not. This can be queried via gtk_selection_model_is_selected(). When the selected
* state of one or more rows changes, the model will emit the
* GtkSelectionModel::selection-changed signal by calling the
* gtk_selection_model_selection_changed() function. The positions given in that signal
* may have their selection state changed, though that is not a requirement.
* If new items added to the model via the #GListModel::items-changed signal are selected
* or not is up to the implementation.
*
* Additionally, the interface can expose functionality to select and unselect items.
* If these functions are implemented, GTK's list widgets will allow users to select and
* unselect items. However, #GtkSelectionModels are free to only implement them
* partially or not at all. In that case the widgets will not support the unimplemented
* operations.
*
* When selecting or unselecting is supported by a model, the return values of the
* selection functions do NOT indicate if selection or unselection happened. They are
* only meant to indicate complete failure, like when this mode of selecting is not
* supported by the model.
* Selections may happen asynchronously, so the only reliable way to find out when an
* item was selected is to listen to the signals that indicate selection.
*/
G_DEFINE_INTERFACE (GtkSelectionModel, gtk_selection_model, G_TYPE_LIST_MODEL)
enum {
SELECTION_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static gboolean
gtk_selection_model_default_is_selected (GtkSelectionModel *model,
guint position)
{
return FALSE;
}
static gboolean
gtk_selection_model_default_select_item (GtkSelectionModel *model,
guint position,
gboolean exclusive,
gboolean extend)
{
return FALSE;
}
static gboolean
gtk_selection_model_default_unselect_item (GtkSelectionModel *model,
guint position)
{
return FALSE;
}
static gboolean
gtk_selection_model_default_select_range (GtkSelectionModel *model,
guint position,
guint n_items,
gboolean exclusive)
{
return FALSE;
}
static gboolean
gtk_selection_model_default_unselect_range (GtkSelectionModel *model,
guint position,
guint n_items)
{
return FALSE;
}
static gboolean
gtk_selection_model_default_select_all (GtkSelectionModel *model)
{
return gtk_selection_model_select_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)), FALSE);
}
static gboolean
gtk_selection_model_default_unselect_all (GtkSelectionModel *model)
{
return gtk_selection_model_unselect_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));;
}
static void
gtk_selection_model_default_init (GtkSelectionModelInterface *iface)
{
iface->is_selected = gtk_selection_model_default_is_selected;
iface->select_item = gtk_selection_model_default_select_item;
iface->unselect_item = gtk_selection_model_default_unselect_item;
iface->select_range = gtk_selection_model_default_select_range;
iface->unselect_range = gtk_selection_model_default_unselect_range;
iface->select_all = gtk_selection_model_default_select_all;
iface->unselect_all = gtk_selection_model_default_unselect_all;
/**
* GtkSelectionModel::selection-changed
* @model: a #GtkSelectionModel
* @position: The first item that may have changed
* @n_items: number of items with changes
*
* Emitted when the selection state of some of the items in @model changes.
*
* Note that this signal does not specify the new selection state of the items,
* they need to be queried manually.
* It is also not necessary for a model to change the selection state of any of
* the items in the selection model, though it would be rather useless to emit
* such a signal.
*/
signals[SELECTION_CHANGED] =
g_signal_new ("selection-changed",
GTK_TYPE_SELECTION_MODEL,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
_gtk_marshal_VOID__UINT_UINT,
G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
g_signal_set_va_marshaller (signals[SELECTION_CHANGED],
GTK_TYPE_SELECTION_MODEL,
_gtk_marshal_VOID__UINT_UINTv);
g_object_interface_install_property (iface,
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));
}
/**
* gtk_selection_model_is_selected:
* @model: a #GtkSelectionModel
* @position: the position of the item to query
*
* Checks if the given item is selected.
*
* Returns: %TRUE if the item is selected
**/
gboolean
gtk_selection_model_is_selected (GtkSelectionModel *model,
guint position)
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->is_selected (model, position);
}
gboolean
gtk_selection_model_select_item (GtkSelectionModel *model,
guint position,
gboolean exclusive,
gboolean extend)
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->select_item (model, position, exclusive, extend);
}
gboolean
gtk_selection_model_unselect_item (GtkSelectionModel *model,
guint position)
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->unselect_item (model, position);
}
gboolean
gtk_selection_model_select_range (GtkSelectionModel *model,
guint position,
guint n_items,
gboolean exclusive)
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->select_range (model, position, n_items, exclusive);
}
gboolean
gtk_selection_model_unselect_range (GtkSelectionModel *model,
guint position,
guint n_items)
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->unselect_range (model, position, n_items);
}
gboolean
gtk_selection_model_select_all (GtkSelectionModel *model)
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->select_all (model);
}
gboolean
gtk_selection_model_unselect_all (GtkSelectionModel *model)
{
GtkSelectionModelInterface *iface;
g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
iface = GTK_SELECTION_MODEL_GET_IFACE (model);
return iface->unselect_all (model);
}
GListModel *
gtk_selection_model_get_model (GtkSelectionModel *model)
{
GListModel *child;
g_object_get (model, "model", &child, NULL);
if (child)
g_object_unref (child);
return child;
}
void
gtk_selection_model_selection_changed (GtkSelectionModel *model,
guint position,
guint n_items)
{
g_return_if_fail (GTK_IS_SELECTION_MODEL (model));
g_signal_emit (model, signals[SELECTION_CHANGED], 0, position, n_items);
}

122
gtk/gtkselectionmodel.h Normal file
View File

@@ -0,0 +1,122 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_SELECTION_MODEL_H__
#define __GTK_SELECTION_MODEL_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gdk/gdk.h>
G_BEGIN_DECLS
#define GTK_TYPE_SELECTION_MODEL (gtk_selection_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_INTERFACE (GtkSelectionModel, gtk_selection_model, GTK, SELECTION_MODEL, GListModel)
/**
* GtkSelectionModelInterface:
* @is_selected: Return if the item at the given position is selected.
* @select_item: Select the item in the given position. If the operation
* is known to fail, return %FALSE.
* @unselect_item: Unselect the item in the given position. If the
* operation is known to fail, return %FALSE.
* @select_range: Select all items in the given range. If the operation
* is unsupported or known to fail for all items, return %FALSE.
* @unselect_range: Unselect all items in the given range. If the
* operation is unsupported or known to fail for all items, return
* %FALSE.
* @select_all: Select all items in the model. If the operation is
* unsupported or known to fail for all items, return %FALSE.
* @unselect_all: Unselect all items in the model. If the operation is
* unsupported or known to fail for all items, return %FALSE.
*
* The list of virtual functions for the #GtkSelectionModel interface.
* All getter functions are mandatory to implement, but the model does
* not need to implement any functions to support selecting or unselecting
* items. Of course, if the model does not do that, it means that users
* cannot select or unselect items in a list widgets using the model.
*/
struct _GtkSelectionModelInterface
{
/*< private >*/
GTypeInterface g_iface;
/*< public >*/
gboolean (* is_selected) (GtkSelectionModel *model,
guint position);
gboolean (* select_item) (GtkSelectionModel *model,
guint position,
gboolean exclusive,
gboolean extend);
gboolean (* unselect_item) (GtkSelectionModel *model,
guint position);
gboolean (* select_range) (GtkSelectionModel *model,
guint position,
guint n_items,
gboolean exclusive);
gboolean (* unselect_range) (GtkSelectionModel *model,
guint position,
guint n_items);
gboolean (* select_all) (GtkSelectionModel *model);
gboolean (* unselect_all) (GtkSelectionModel *model);
};
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_is_selected (GtkSelectionModel *model,
guint position);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_select_item (GtkSelectionModel *model,
guint position,
gboolean exclusive,
gboolean extend);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_unselect_item (GtkSelectionModel *model,
guint position);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_select_range (GtkSelectionModel *model,
guint position,
guint n_items,
gboolean exclusive);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_unselect_range (GtkSelectionModel *model,
guint position,
guint n_items);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_select_all (GtkSelectionModel *model);
GDK_AVAILABLE_IN_ALL
gboolean gtk_selection_model_unselect_all (GtkSelectionModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_selection_model_get_model (GtkSelectionModel *model);
/* for implementations only */
GDK_AVAILABLE_IN_ALL
void gtk_selection_model_selection_changed (GtkSelectionModel *model,
guint position,
guint n_items);
G_END_DECLS
#endif /* __GTK_SELECTION_MODEL_H__ */

292
gtk/gtkset.c Normal file
View File

@@ -0,0 +1,292 @@
/*
* 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;
}
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_add_item (GtkSet *set,
guint item)
{
gtk_set_add_range (set, item, 1);
}
void
gtk_set_remove_item (GtkSet *set,
guint item)
{
gtk_set_remove_range (set, item, 1);
}
/* This is peculiar operation: Replace every number n >= first by n + shift
* This is only supported for negative shift 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

63
gtk/gtkset.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* 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);
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_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__ */

559
gtk/gtksingleselection.c Normal file
View File

@@ -0,0 +1,559 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtksingleselection.h"
#include "gtkintl.h"
#include "gtkselectionmodel.h"
/**
* SECTION:gtksingleselection
* @Short_description: A selection model that allows selecting a single item
* @Title: GtkSingleSelection
* @see_also: #GtkSelectionModel
*
* GtkSingleSelection is an implementation of the #GtkSelectionModel interface
* that allows selecting a single element. It is the default selection method
* used by list widgets in GTK.
*/
struct _GtkSingleSelection
{
GObject parent_instance;
GListModel *model;
guint selected;
gpointer selected_item;
guint autoselect : 1;
guint can_unselect : 1;
};
struct _GtkSingleSelectionClass
{
GObjectClass parent_class;
};
enum {
PROP_0,
PROP_AUTOSELECT,
PROP_CAN_UNSELECT,
PROP_SELECTED,
/* selectionmodel */
PROP_MODEL,
N_PROPS = PROP_MODEL
};
static GParamSpec *properties[N_PROPS] = { NULL, };
static GType
gtk_single_selection_get_item_type (GListModel *list)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (list);
return g_list_model_get_item_type (self->model);
}
static guint
gtk_single_selection_get_n_items (GListModel *list)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (list);
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_single_selection_get_item (GListModel *list,
guint position)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (list);
return g_list_model_get_item (self->model, position);
}
static void
gtk_single_selection_list_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_single_selection_get_item_type;
iface->get_n_items = gtk_single_selection_get_n_items;
iface->get_item = gtk_single_selection_get_item;
}
static gboolean
gtk_single_selection_is_selected (GtkSelectionModel *model,
guint position)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
return self->selected == position;
}
static gboolean
gtk_single_selection_select_item (GtkSelectionModel *model,
guint position,
gboolean exclusive,
gboolean extend)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
/* XXX: Should we check that position < n_items here? */
gtk_single_selection_set_selected (self, position);
return TRUE;
}
static gboolean
gtk_single_selection_unselect_item (GtkSelectionModel *model,
guint position)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
if (!self->can_unselect)
return FALSE;
if (self->selected == position)
gtk_single_selection_set_selected (self, GTK_INVALID_LIST_POSITION);
return TRUE;
}
static void
gtk_single_selection_selection_model_init (GtkSelectionModelInterface *iface)
{
iface->is_selected = gtk_single_selection_is_selected;
iface->select_item = gtk_single_selection_select_item;
iface->unselect_item = gtk_single_selection_unselect_item;
}
G_DEFINE_TYPE_EXTENDED (GtkSingleSelection, gtk_single_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_single_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
gtk_single_selection_selection_model_init))
static void
gtk_single_selection_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkSingleSelection *self)
{
gboolean emit_selection_changed = FALSE;
g_object_freeze_notify (G_OBJECT (self));
if (self->selected_item == NULL)
{
if (self->autoselect)
{
self->selected_item = g_list_model_get_item (self->model, 0);
if (self->selected_item)
{
self->selected = 0;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
emit_selection_changed = TRUE;
}
}
}
else if (self->selected < position)
{
/* unchanged */
}
else if (self->selected >= position + removed)
{
self->selected += added - removed;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
}
else
{
guint i;
for (i = 0; i < added; i++)
{
gpointer item = g_list_model_get_item (model, position + i);
if (item == self->selected_item)
{
/* the item moved */
if (self->selected != position + i)
{
self->selected = position + i;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
}
break;
}
}
if (i == added)
{
/* the item really was deleted */
g_clear_object (&self->selected_item);
if (self->autoselect)
{
self->selected = position + (self->selected - position) * added / removed;
self->selected_item = g_list_model_get_item (self->model, self->selected);
if (self->selected_item == NULL && position > 0)
{
self->selected = position - 1;
self->selected_item = g_list_model_get_item (self->model, self->selected);
g_assert (self->selected_item);
}
emit_selection_changed = TRUE;
}
else
{
g_clear_object (&self->selected_item);
self->selected = GTK_INVALID_LIST_POSITION;
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
}
}
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
if (emit_selection_changed && self->selected != GTK_INVALID_LIST_POSITION)
gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), self->selected, 1);
g_object_thaw_notify (G_OBJECT (self));
}
static void
gtk_single_selection_clear_model (GtkSingleSelection *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model,
gtk_single_selection_items_changed_cb,
self);
g_clear_object (&self->model);
}
static void
gtk_single_selection_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (object);
switch (prop_id)
{
case PROP_AUTOSELECT:
gtk_single_selection_set_autoselect (self, g_value_get_boolean (value));
break;
case PROP_CAN_UNSELECT:
gtk_single_selection_set_can_unselect (self, g_value_get_boolean (value));
break;
case PROP_MODEL:
gtk_single_selection_clear_model (self);
self->model = g_value_dup_object (value);
if (self->model)
g_signal_connect (self->model, "items-changed",
G_CALLBACK (gtk_single_selection_items_changed_cb), self);
if (self->autoselect)
gtk_single_selection_set_selected (self, 0);
break;
case PROP_SELECTED:
gtk_single_selection_set_selected (self, g_value_get_uint (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_single_selection_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (object);
switch (prop_id)
{
case PROP_AUTOSELECT:
g_value_set_boolean (value, self->autoselect);
break;
case PROP_CAN_UNSELECT:
g_value_set_boolean (value, self->can_unselect);
break;
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_SELECTED:
g_value_set_uint (value, self->selected);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_single_selection_dispose (GObject *object)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (object);
gtk_single_selection_clear_model (self);
self->selected = GTK_INVALID_LIST_POSITION;
g_clear_object (&self->selected_item);
G_OBJECT_CLASS (gtk_single_selection_parent_class)->dispose (object);
}
static void
gtk_single_selection_class_init (GtkSingleSelectionClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->get_property = gtk_single_selection_get_property;
gobject_class->set_property = gtk_single_selection_set_property;
gobject_class->dispose = gtk_single_selection_dispose;
g_object_class_override_property (gobject_class, PROP_MODEL, "model");
/**
* GtkSingleSelection:autoselect
*
* If the selection will always select an item
*/
properties[PROP_AUTOSELECT] =
g_param_spec_boolean ("autoselect",
P_("Autoselect"),
P_("If the selection will always select an item"),
TRUE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkSingleSelection:can-unselect
*
* If unselecting the selected item is allowed
*/
properties[PROP_CAN_UNSELECT] =
g_param_spec_boolean ("can-unselect",
P_("Can unselect"),
P_("If unselecting the selected item is allowed"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkSingleSelection:selected
*
* Position of the selected item
*/
properties[PROP_SELECTED] =
g_param_spec_uint ("selected",
P_("Selected"),
P_("Position of the selected item"),
0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
gtk_single_selection_init (GtkSingleSelection *self)
{
self->selected = GTK_INVALID_LIST_POSITION;
self->autoselect = TRUE;
}
/**
* gtk_single_selection_new:
* @model: (transfer none): the #GListModel to manage
*
* Creates a new selection to handle @model.
*
* Returns: (transfer full) (type GtkSingleSelection): a new #GtkSingleSelection
**/
GtkSingleSelection *
gtk_single_selection_new (GListModel *model)
{
g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
return g_object_new (GTK_TYPE_SINGLE_SELECTION,
"model", model,
NULL);
}
/**
* gtk_single_selection_get_selected:
* @self: a #GtkSingleSelection
*
* Gets the position of the selected item. If no item is selected,
* #GTK_INVALID_LIST_POSITION is returned.
*
* Returns: The position of the selected item
**/
guint
gtk_single_selection_get_selected (GtkSingleSelection *self)
{
g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), GTK_INVALID_LIST_POSITION);
return self->selected;
}
/**
* gtk_single_selection_set_selected:
* @self: a #GtkSingleSelection
* @position: the item to select or #GTK_INVALID_LIST_POSITION
*
* Selects the item at the given position. If the list does not have an item at
* @position or #GTK_INVALID_LIST_POSITION is given, the behavior depends on the
* value of the GtkSingleSelection:autoselect property: If it is set, no change
* will occur and the old item will stay selected. If it is unset, the selection
* will be unset and no item will be selected.
**/
void
gtk_single_selection_set_selected (GtkSingleSelection *self,
guint position)
{
gpointer new_selected = NULL;
guint old_position;
g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
if (self->selected == position)
return;
if (self->model)
new_selected = g_list_model_get_item (self->model, position);
if (new_selected == NULL)
position = GTK_INVALID_LIST_POSITION;
if (self->selected == position)
return;
old_position = self->selected;
self->selected = position;
g_clear_object (&self->selected_item);
self->selected_item = new_selected;
if (old_position == GTK_INVALID_LIST_POSITION)
gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), position, 1);
else if (position == GTK_INVALID_LIST_POSITION)
gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), old_position, 1);
else if (position < old_position)
gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), position, old_position - position + 1);
else
gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), old_position, position - old_position + 1);
}
/**
* gtk_single_selection_get_autoselect:
* @self: a #GtkSingleSelection
*
* Checks if autoselect has been enabled or disabled via
* gtk_single_selection_set_autoselect().
*
* Returns: %TRUE if autoselect is enabled
**/
gboolean
gtk_single_selection_get_autoselect (GtkSingleSelection *self)
{
g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), TRUE);
return self->autoselect;
}
/**
* gtk_single_selection_set_autoselect:
* @self: a #GtkSingleSelection
* @autoselect: %TRUE to always select an item
*
* If @autoselect is %TRUE, @self will enforce that an item is always
* selected. It will select a new item when the currently selected
* item is deleted and it will disallow unselecting the current item.
**/
void
gtk_single_selection_set_autoselect (GtkSingleSelection *self,
gboolean autoselect)
{
g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
if (self->autoselect == autoselect)
return;
self->autoselect = autoselect;
g_object_freeze_notify (G_OBJECT (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_AUTOSELECT]);
if (self->autoselect && !self->selected_item)
gtk_single_selection_set_selected (self, 0);
g_object_thaw_notify (G_OBJECT (self));
}
/**
* gtk_single_selection_get_can_unselect:
* @self: a #GtkSingleSelection
*
* If %TRUE, gtk_selection_model_unselect_item() is supported and allows
* unselecting the selected item.
*
* Returns: %TRUE to support unselecting
**/
gboolean
gtk_single_selection_get_can_unselect (GtkSingleSelection *self)
{
g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), FALSE);
return self->can_unselect;
}
/**
* gtk_single_selection_set_can_unselect:
* @self: a #GtkSingleSelection
* @can_unselect: %TRUE to allow unselecting
*
* If %TRUE, unselecting the current item via
* gtk_selection_model_unselect_item() is supported.
*
* Note that setting GtkSingleSelection:autoselect will cause the
* unselecting to not work, so it practically makes no sense to set
* both at the same time the same time..
**/
void
gtk_single_selection_set_can_unselect (GtkSingleSelection *self,
gboolean can_unselect)
{
g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
if (self->can_unselect == can_unselect)
return;
self->can_unselect = can_unselect;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CAN_UNSELECT]);
}

65
gtk/gtksingleselection.h Normal file
View File

@@ -0,0 +1,65 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_SINGLE_SELECTION_H__
#define __GTK_SINGLE_SELECTION_H__
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_SINGLE_SELECTION (gtk_single_selection_get_type ())
/**
* GTK_INVALID_LIST_POSITION:
*
* The value used to refer to a guaranteed invalid position in a #GListModel. This
* value may be returned from some functions, others may accept it as input.
* Its interpretion may differ for different functions.
*
* Refer to each function's documentation for if this value is allowed and what it
* does.
*/
#define GTK_INVALID_LIST_POSITION (G_MAXUINT)
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkSingleSelection, gtk_single_selection, GTK, SINGLE_SELECTION, GObject)
GDK_AVAILABLE_IN_ALL
GtkSingleSelection * gtk_single_selection_new (GListModel *model);
GDK_AVAILABLE_IN_ALL
guint gtk_single_selection_get_selected (GtkSingleSelection *self);
GDK_AVAILABLE_IN_ALL
void gtk_single_selection_set_selected (GtkSingleSelection *self,
guint position);
GDK_AVAILABLE_IN_ALL
gboolean gtk_single_selection_get_autoselect (GtkSingleSelection *self);
GDK_AVAILABLE_IN_ALL
void gtk_single_selection_set_autoselect (GtkSingleSelection *self,
gboolean autoselect);
GDK_AVAILABLE_IN_ALL
gboolean gtk_single_selection_get_can_unselect (GtkSingleSelection *self);
GDK_AVAILABLE_IN_ALL
void gtk_single_selection_set_can_unselect (GtkSingleSelection *self,
gboolean can_unselect);
G_END_DECLS
#endif /* __GTK_SINGLE_SELECTION_H__ */

View File

@@ -157,13 +157,19 @@ gtk_slice_list_model_items_changed_cb (GListModel *model,
else
{
guint n_after, n_before;
guint skip;
if (position > self->offset)
skip = position - self->offset;
else
skip = 0;
n_after = g_list_model_get_n_items (self->model);
n_before = n_after - added + removed;
n_after = CLAMP (n_after, self->offset, self->offset + self->size) - self->offset;
n_before = CLAMP (n_before, self->offset, self->offset + self->size) - self->offset;
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_before, n_after);
g_list_model_items_changed (G_LIST_MODEL (self), skip, n_before - skip, n_after - skip);
}
}

View File

@@ -6583,7 +6583,7 @@ gtk_widget_reposition_after (GtkWidget *widget,
if (parent->priv->children_observer)
{
if (prev_previous)
g_warning ("oops");
gtk_list_list_model_item_moved (parent->priv->children_observer, widget, prev_previous);
else
gtk_list_list_model_item_added (parent->priv->children_observer, widget);
}

View File

@@ -137,6 +137,7 @@ gtk_private_sources = files([
'gtksearchengine.c',
'gtksearchenginemodel.c',
'gtksearchenginesimple.c',
'gtkset.c',
'gtksizerequestcache.c',
'gtkstyleanimation.c',
'gtkstylecascade.c',
@@ -266,8 +267,12 @@ gtk_public_sources = files([
'gtklevelbar.c',
'gtklinkbutton.c',
'gtklistbox.c',
'gtklistitem.c',
'gtklistitemfactory.c',
'gtklistitemmanager.c',
'gtklistlistmodel.c',
'gtkliststore.c',
'gtklistview.c',
'gtklockbutton.c',
'gtkmain.c',
'gtkmaplistmodel.c',
@@ -285,8 +290,10 @@ gtk_public_sources = files([
'gtkmodelmenuitem.c',
'gtkmodules.c',
'gtkmountoperation.c',
'gtkmultiselection.c',
'gtknativedialog.c',
'gtknomediafile.c',
'gtknoselection.c',
'gtknotebook.c',
'gtkorientable.c',
'gtkoverlay.c',
@@ -324,6 +331,7 @@ gtk_public_sources = files([
'gtksearchbar.c',
'gtksearchentry.c',
'gtkselection.c',
'gtkselectionmodel.c',
'gtkseparator.c',
'gtkseparatormenuitem.c',
'gtkseparatortoolitem.c',
@@ -335,6 +343,7 @@ gtk_public_sources = files([
'gtkshortcutswindow.c',
'gtkshow.c',
'gtksidebarrow.c',
'gtksingleselection.c',
'gtksizegroup.c',
'gtksizerequest.c',
'gtkslicelistmodel.c',
@@ -513,7 +522,9 @@ gtk_public_headers = files([
'gtklevelbar.h',
'gtklinkbutton.h',
'gtklistbox.h',
'gtklistitem.h',
'gtkliststore.h',
'gtklistview.h',
'gtklockbutton.h',
'gtkmain.h',
'gtkmaplistmodel.h',
@@ -527,10 +538,12 @@ gtk_public_headers = files([
'gtkmenushell.h',
'gtkmenutoolbutton.h',
'gtkmessagedialog.h',
'gtkmultiselection.h',
'gtkmodelbutton.h',
'gtkmountoperation.h',
'gtknativedialog.h',
'gtknotebook.h',
'gtknoselection.h',
'gtkorientable.h',
'gtkoverlay.h',
'gtkpadcontroller.h',
@@ -560,6 +573,7 @@ gtk_public_headers = files([
'gtksearchbar.h',
'gtksearchentry.h',
'gtkselection.h',
'gtkselectionmodel.h',
'gtkseparator.h',
'gtkseparatormenuitem.h',
'gtkseparatortoolitem.h',
@@ -570,6 +584,7 @@ gtk_public_headers = files([
'gtkshortcutsshortcut.h',
'gtkshortcutswindow.h',
'gtkshow.h',
'gtksingleselection.h',
'gtksizegroup.h',
'gtksizerequest.h',
'gtkslicelistmodel.h',

View File

@@ -1,23 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="gtk40">
<!-- interface-requires gtk+ 3.10 -->
<object class="GtkListStore" id="model">
<columns>
<!-- column-name family -->
<column type="PangoFontFamily"/>
<!-- column-name face -->
<column type="PangoFontFace"/>
<!-- column-name description -->
<column type="GtkDelayedFontDescription"/>
<!-- column-name preview-title -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkTreeModelFilter" id="filter_model">
<property name="child-model">model</property>
<signal name="row-deleted" handler="rows_changed_cb" swapped="yes"/>
<signal name="row-inserted" handler="rows_changed_cb" swapped="yes"/>
</object>
<object class="GtkAdjustment" id="slider_adjustment">
<property name="upper">100</property>
<property name="step-increment">1</property>
@@ -63,7 +46,7 @@
<property name="row-spacing">6</property>
<property name="column-spacing">6</property>
<child>
<object class="GtkScrolledWindow" id="list_scrolled_window">
<object class="GtkScrolledWindow">
<property name="width-request">400</property>
<property name="height-request">300</property>
<property name="can-focus">1</property>
@@ -72,38 +55,13 @@
<property name="hscrollbar-policy">never</property>
<property name="shadow-type">etched-in</property>
<child>
<object class="GtkTreeView" id="family_face_list">
<property name="can-focus">1</property>
<property name="model">filter_model</property>
<property name="headers-visible">0</property>
<property name="enable-search">0</property>
<property name="fixed-height-mode">1</property>
<signal name="cursor-changed" handler="cursor_changed_cb" swapped="no"/>
<signal name="row-activated" handler="row_activated_cb" swapped="no"/>
<signal name="style-updated" handler="gtk_font_chooser_widget_set_cell_size" object="GtkFontChooserWidget" after="yes" swapped="yes"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection1">
<property name="mode">browse</property>
<signal name="changed" handler="selection_changed"/>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="family_face_column">
<property name="sizing">fixed</property>
<property name="title" translatable="yes">Font Family</property>
<child>
<object class="GtkCellRendererText" id="family_face_cell">
<property name="ellipsize">end</property>
</object>
</child>
</object>
</child>
<object class="GtkListView" id="family_face_listview">
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
<property name="top-attach">0</property>
<property name="width">3</property>
</packing>
</child>

View File

@@ -62,6 +62,8 @@ gtk_tests = [
['testlist2'],
['testlist3'],
['testlist4'],
['testlistview'],
['testlistview-animating'],
['testlevelbar'],
['testlockbutton'],
['testmenubutton'],

View File

@@ -0,0 +1,192 @@
#include <gtk/gtk.h>
#ifdef SMALL
#define AVERAGE 15
#define VARIANCE 10
#else
#define AVERAGE 300
#define VARIANCE 200
#endif
static void
update_label (GtkListItem *list_item,
GParamSpec *pspec,
GtkLabel *label)
{
gpointer item;
char *s;
item = gtk_list_item_get_item (list_item);
if (item)
s = g_strdup_printf ("%u: %s",
gtk_list_item_get_position (list_item),
(const char *) g_object_get_data (item, "message"));
else
s = NULL;
gtk_label_set_text (label, s);
g_free (s);
}
static void
setup_list_item (GtkListItem *list_item,
gpointer unused)
{
GtkWidget *label = gtk_label_new ("");
g_signal_connect (list_item, "notify", G_CALLBACK (update_label), label);
gtk_container_add (GTK_CONTAINER (list_item), label);
}
static GtkWidget *
create_widget_for_listbox (gpointer item,
gpointer unused)
{
const char *message = g_object_get_data (item, "message");
GtkWidget *widget;
widget = gtk_label_new (message);
return widget;
}
static gboolean reverse_sort;
static int
compare (gconstpointer first,
gconstpointer second,
gpointer unused)
{
int diff = (GPOINTER_TO_UINT (g_object_get_data ((gpointer) first, "counter")) % 1000)
- (GPOINTER_TO_UINT (g_object_get_data ((gpointer) second, "counter")) % 1000);
if (reverse_sort)
return -diff;
else
return diff;
}
static void
add (GListStore *store)
{
static guint counter;
GObject *o;
char *message;
guint pos;
counter++;
o = g_object_new (G_TYPE_OBJECT, NULL);
g_object_set_data (o, "counter", GUINT_TO_POINTER (counter));
message = g_strdup_printf ("Item %u", counter);
g_object_set_data_full (o, "message", message, g_free);
pos = g_random_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
g_list_store_insert (store, pos, o);
g_object_unref (o);
}
static void
delete (GListStore *store)
{
guint pos;
pos = g_random_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)));
g_list_store_remove (store, pos);
}
static gboolean
do_stuff (gpointer store)
{
if (g_random_int_range (AVERAGE - VARIANCE, AVERAGE + VARIANCE) < g_list_model_get_n_items (store))
delete (store);
else
add (store);
return G_SOURCE_CONTINUE;
}
static gboolean
revert_sort (gpointer sort)
{
reverse_sort = !reverse_sort;
gtk_sort_list_model_resort (sort);
return G_SOURCE_CONTINUE;
}
int
main (int argc, char *argv[])
{
GtkWidget *win, *hbox, *vbox, *sw, *listview, *listbox, *label;
GListStore *store;
GtkSortListModel *sort;
guint i;
gtk_init ();
store = g_list_store_new (G_TYPE_OBJECT);
for (i = 0; i < AVERAGE; i++)
add (store);
sort = gtk_sort_list_model_new (G_LIST_MODEL (store),
compare,
NULL, NULL);
win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (win), 400, 600);
g_signal_connect (win, "destroy", G_CALLBACK (gtk_main_quit), win);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
gtk_container_add (GTK_CONTAINER (win), hbox);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
gtk_container_add (GTK_CONTAINER (hbox), vbox);
label = gtk_label_new ("GtkListView");
gtk_container_add (GTK_CONTAINER (vbox), label);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_set_hexpand (sw, TRUE);
gtk_widget_set_vexpand (sw, TRUE);
gtk_container_add (GTK_CONTAINER (vbox), sw);
listview = gtk_list_view_new ();
gtk_list_view_set_functions (GTK_LIST_VIEW (listview),
setup_list_item,
NULL,
NULL, NULL);
gtk_container_add (GTK_CONTAINER (sw), listview);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
gtk_container_add (GTK_CONTAINER (hbox), vbox);
label = gtk_label_new ("GtkListBox");
gtk_container_add (GTK_CONTAINER (vbox), label);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_set_hexpand (sw, TRUE);
gtk_widget_set_vexpand (sw, TRUE);
gtk_container_add (GTK_CONTAINER (vbox), sw);
listbox = gtk_list_box_new ();
gtk_container_add (GTK_CONTAINER (sw), listbox);
gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (sort));
gtk_list_box_bind_model (GTK_LIST_BOX (listbox),
G_LIST_MODEL (sort),
create_widget_for_listbox,
NULL, NULL);
g_timeout_add (100, do_stuff, store);
g_timeout_add_seconds (3, revert_sort, sort);
gtk_widget_show (win);
gtk_main ();
g_object_unref (store);
return 0;
}

493
tests/testlistview.c Normal file
View File

@@ -0,0 +1,493 @@
#include <gtk/gtk.h>
#define ROWS 30
GSList *pending = NULL;
guint active = 0;
static void
got_files (GObject *enumerate,
GAsyncResult *res,
gpointer store);
static gboolean
start_enumerate (GListStore *store)
{
GFileEnumerator *enumerate;
GFile *file = g_object_get_data (G_OBJECT (store), "file");
GError *error = NULL;
enumerate = g_file_enumerate_children (file,
G_FILE_ATTRIBUTE_STANDARD_TYPE
"," G_FILE_ATTRIBUTE_STANDARD_ICON
"," G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
0,
NULL,
&error);
if (enumerate == NULL)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TOO_MANY_OPEN_FILES) && active)
{
g_clear_error (&error);
pending = g_slist_prepend (pending, g_object_ref (store));
return TRUE;
}
g_clear_error (&error);
g_object_unref (store);
return FALSE;
}
if (active > 20)
{
g_object_unref (enumerate);
pending = g_slist_prepend (pending, g_object_ref (store));
return TRUE;
}
active++;
g_file_enumerator_next_files_async (enumerate,
g_file_is_native (file) ? 5000 : 100,
G_PRIORITY_DEFAULT_IDLE,
NULL,
got_files,
g_object_ref (store));
g_object_unref (enumerate);
return TRUE;
}
static void
got_files (GObject *enumerate,
GAsyncResult *res,
gpointer store)
{
GList *l, *files;
GFile *file = g_object_get_data (store, "file");
GPtrArray *array;
files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (enumerate), res, NULL);
if (files == NULL)
{
g_object_unref (store);
if (pending)
{
GListStore *store = pending->data;
pending = g_slist_remove (pending, store);
start_enumerate (store);
}
active--;
return;
}
array = g_ptr_array_new ();
g_ptr_array_new_with_free_func (g_object_unref);
for (l = files; l; l = l->next)
{
GFileInfo *info = l->data;
GFile *child;
child = g_file_get_child (file, g_file_info_get_name (info));
g_object_set_data_full (G_OBJECT (info), "file", child, g_object_unref);
g_ptr_array_add (array, info);
}
g_list_free (files);
g_list_store_splice (store, g_list_model_get_n_items (store), 0, array->pdata, array->len);
g_ptr_array_unref (array);
g_file_enumerator_next_files_async (G_FILE_ENUMERATOR (enumerate),
g_file_is_native (file) ? 5000 : 100,
G_PRIORITY_DEFAULT_IDLE,
NULL,
got_files,
store);
}
static int
compare_files (gconstpointer first,
gconstpointer second,
gpointer unused)
{
GFile *first_file, *second_file;
char *first_path, *second_path;
int result;
#if 0
GFileType first_type, second_type;
/* This is a bit slow, because each g_file_query_file_type() does a stat() */
first_type = g_file_query_file_type (G_FILE (first), G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL);
second_type = g_file_query_file_type (G_FILE (second), G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL);
if (first_type == G_FILE_TYPE_DIRECTORY && second_type != G_FILE_TYPE_DIRECTORY)
return -1;
if (first_type != G_FILE_TYPE_DIRECTORY && second_type == G_FILE_TYPE_DIRECTORY)
return 1;
#endif
first_file = g_object_get_data (G_OBJECT (first), "file");
second_file = g_object_get_data (G_OBJECT (second), "file");
first_path = g_file_get_path (first_file);
second_path = g_file_get_path (second_file);
result = strcasecmp (first_path, second_path);
g_free (first_path);
g_free (second_path);
return result;
}
static GListModel *
create_list_model_for_directory (gpointer file)
{
GListStore *store;
if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY)
return NULL;
store = g_list_store_new (G_TYPE_FILE_INFO);
g_object_set_data_full (G_OBJECT (store), "file", g_object_ref (file), g_object_unref);
if (!start_enumerate (store))
return NULL;
return G_LIST_MODEL (store);
}
typedef struct _RowData RowData;
struct _RowData
{
GtkWidget *depth_box;
GtkWidget *expander;
GtkWidget *icon;
GtkWidget *name;
GtkTreeListRow *current_item;
GBinding *expander_binding;
};
static void row_data_notify_item (GtkListItem *item,
GParamSpec *pspec,
RowData *data);
static void
row_data_unbind (RowData *data)
{
if (data->current_item == NULL)
return;
g_binding_unbind (data->expander_binding);
g_clear_object (&data->current_item);
}
static void
row_data_bind (RowData *data,
GtkTreeListRow *item)
{
GFileInfo *info;
GIcon *icon;
guint depth;
row_data_unbind (data);
if (item == NULL)
return;
data->current_item = g_object_ref (item);
depth = gtk_tree_list_row_get_depth (item);
gtk_widget_set_size_request (data->depth_box, 16 * depth, 0);
gtk_widget_set_sensitive (data->expander, gtk_tree_list_row_is_expandable (item));
data->expander_binding = g_object_bind_property (item, "expanded", data->expander, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
info = gtk_tree_list_row_get_item (item);
icon = g_file_info_get_icon (info);
gtk_widget_set_visible (data->icon, icon != NULL);
if (icon)
gtk_image_set_from_gicon (GTK_IMAGE (data->icon), icon);
gtk_label_set_label (GTK_LABEL (data->name), g_file_info_get_display_name (info));
g_object_unref (info);
}
static void
row_data_notify_item (GtkListItem *item,
GParamSpec *pspec,
RowData *data)
{
row_data_bind (data, gtk_list_item_get_item (item));
}
static void
row_data_free (gpointer _data)
{
RowData *data = _data;
row_data_unbind (data);
g_slice_free (RowData, data);
}
static void
setup_widget (GtkListItem *list_item,
gpointer unused)
{
GtkWidget *box, *child;
RowData *data;
data = g_slice_new0 (RowData);
g_signal_connect (list_item, "notify::item", G_CALLBACK (row_data_notify_item), data);
g_object_set_data_full (G_OBJECT (list_item), "row-data", data, row_data_free);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
gtk_container_add (GTK_CONTAINER (list_item), box);
child = gtk_label_new (NULL);
gtk_label_set_width_chars (GTK_LABEL (child), 5);
gtk_label_set_xalign (GTK_LABEL (child), 1.0);
g_object_bind_property (list_item, "position", child, "label", G_BINDING_SYNC_CREATE);
gtk_container_add (GTK_CONTAINER (box), child);
data->depth_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_container_add (GTK_CONTAINER (box), data->depth_box);
child = g_object_new (GTK_TYPE_BOX, "css-name", "expander", NULL);
gtk_container_add (GTK_CONTAINER (box), child);
data->expander = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "css-name", "title", NULL);
gtk_button_set_relief (GTK_BUTTON (data->expander), GTK_RELIEF_NONE);
gtk_container_add (GTK_CONTAINER (child), data->expander);
child = g_object_new (GTK_TYPE_SPINNER, "css-name", "arrow", NULL);
gtk_container_add (GTK_CONTAINER (data->expander), child);
data->icon = gtk_image_new ();
gtk_container_add (GTK_CONTAINER (box), data->icon);
data->name = gtk_label_new (NULL);
gtk_container_add (GTK_CONTAINER (box), data->name);
}
static GListModel *
create_list_model_for_file_info (gpointer file_info,
gpointer unused)
{
GFile *file = g_object_get_data (file_info, "file");
if (file == NULL)
return NULL;
return create_list_model_for_directory (file);
}
static gboolean
update_statusbar (GtkStatusbar *statusbar)
{
GListModel *model = g_object_get_data (G_OBJECT (statusbar), "model");
GString *string = g_string_new (NULL);
guint n;
gboolean result = G_SOURCE_REMOVE;
gtk_statusbar_remove_all (statusbar, 0);
n = g_list_model_get_n_items (model);
g_string_append_printf (string, "%u", n);
if (GTK_IS_FILTER_LIST_MODEL (model))
{
guint n_unfiltered = g_list_model_get_n_items (gtk_filter_list_model_get_model (GTK_FILTER_LIST_MODEL (model)));
if (n != n_unfiltered)
g_string_append_printf (string, "/%u", n_unfiltered);
}
g_string_append (string, " items");
if (pending || active)
{
g_string_append_printf (string, " (%u directories remaining)", active + g_slist_length (pending));
result = G_SOURCE_CONTINUE;
}
gtk_statusbar_push (statusbar, 0, string->str);
g_free (string->str);
return result;
}
static gboolean
match_file (gpointer item, gpointer data)
{
GtkWidget *search_entry = data;
GFileInfo *info = gtk_tree_list_row_get_item (item);
GFile *file = g_object_get_data (G_OBJECT (info), "file");
char *path;
gboolean result;
path = g_file_get_path (file);
result = strstr (path, gtk_entry_get_text (GTK_ENTRY (search_entry))) != NULL;
g_object_unref (info);
g_free (path);
return result;
}
static gboolean invert_sort;
static void
toggle_sort (GtkButton *button, GtkSortListModel *sort)
{
invert_sort = !invert_sort;
gtk_button_set_icon_name (button, invert_sort ? "view-sort-descending" : "view-sort-ascending");
gtk_sort_list_model_resort (sort);
}
static int
sort_tree (gconstpointer a, gconstpointer b, gpointer data)
{
GtkTreeListRow *ra = (GtkTreeListRow *) a;
GtkTreeListRow *rb = (GtkTreeListRow *) b;
GtkTreeListRow *pa, *pb;
guint da, db;
int i;
GFile *ia, *ib;
int cmp = 0;
da = gtk_tree_list_row_get_depth (ra);
db = gtk_tree_list_row_get_depth (rb);
if (da > db)
for (i = 0; i < da - db; i++)
{
ra = gtk_tree_list_row_get_parent (ra);
if (ra) g_object_unref (ra);
}
if (db > da)
for (i = 0; i < db - da; i++)
{
rb = gtk_tree_list_row_get_parent (rb);
if (rb) g_object_unref (rb);
}
/* now ra and rb are ancestors of a and b at the same depth */
if (ra == rb)
return da - db;
pa = ra;
pb = rb;
do {
ra = pa;
rb = pb;
pa = gtk_tree_list_row_get_parent (ra);
pb = gtk_tree_list_row_get_parent (rb);
if (pa) g_object_unref (pa);
if (pb) g_object_unref (pb);
} while (pa != pb);
/* now ra and rb are ancestors of a and b that have a common parent */
ia = gtk_tree_list_row_get_item (ra);
ib = gtk_tree_list_row_get_item (rb);
cmp = compare_files (ia, ib, NULL);
g_object_unref (ia);
g_object_unref (ib);
if (invert_sort)
cmp = -cmp;
return cmp;
}
int
main (int argc, char *argv[])
{
GtkWidget *win, *vbox, *sw, *listview, *search_entry, *statusbar;
GtkWidget *hbox, *button;
GListModel *dirmodel;
GtkTreeListModel *tree;
GtkFilterListModel *filter;
GtkSortListModel *sort;
GtkSelectionModel *selection;
GFile *root;
gtk_init ();
win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (win), 400, 600);
g_signal_connect (win, "destroy", G_CALLBACK (gtk_main_quit), win);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (win), vbox);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_container_add (GTK_CONTAINER (vbox), hbox);
search_entry = gtk_search_entry_new ();
gtk_container_add (GTK_CONTAINER (hbox), search_entry);
gtk_widget_set_hexpand (search_entry, TRUE);
button = gtk_button_new_from_icon_name ("view-sort-ascending");
gtk_container_add (GTK_CONTAINER (hbox), button);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_set_vexpand (sw, TRUE);
gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), sw);
gtk_container_add (GTK_CONTAINER (vbox), sw);
listview = gtk_list_view_new ();
gtk_list_view_set_functions (GTK_LIST_VIEW (listview),
setup_widget,
NULL,
NULL, NULL);
gtk_container_add (GTK_CONTAINER (sw), listview);
if (argc > 1)
root = g_file_new_for_commandline_arg (argv[1]);
else
root = g_file_new_for_path (g_get_current_dir ());
dirmodel = create_list_model_for_directory (root);
tree = gtk_tree_list_model_new (FALSE,
dirmodel,
TRUE,
create_list_model_for_file_info,
NULL, NULL);
g_object_unref (dirmodel);
g_object_unref (root);
sort = gtk_sort_list_model_new (G_LIST_MODEL (tree), sort_tree, NULL, NULL);
g_signal_connect (button, "clicked", G_CALLBACK (toggle_sort), sort);
filter = gtk_filter_list_model_new (G_LIST_MODEL (sort),
match_file,
search_entry,
NULL);
g_signal_connect_swapped (search_entry, "search-changed", G_CALLBACK (gtk_filter_list_model_refilter), filter);
selection = GTK_SELECTION_MODEL (gtk_single_selection_new (G_LIST_MODEL (filter)));
gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (selection));
statusbar = gtk_statusbar_new ();
gtk_widget_add_tick_callback (statusbar, (GtkTickCallback) update_statusbar, NULL, NULL);
g_object_set_data (G_OBJECT (statusbar), "model", filter);
g_signal_connect_swapped (filter, "items-changed", G_CALLBACK (update_statusbar), statusbar);
update_statusbar (GTK_STATUSBAR (statusbar));
gtk_container_add (GTK_CONTAINER (vbox), statusbar);
g_object_unref (tree);
g_object_unref (filter);
gtk_widget_show (win);
gtk_main ();
return 0;
}

View File

@@ -33,8 +33,10 @@ tests = [
['listbox'],
['main'],
['maplistmodel'],
['multiselection'],
['notify'],
['no-gtk-init'],
['noselection'],
['object'],
['objects-finalize'],
['papersize'],
@@ -44,6 +46,8 @@ tests = [
['regression-tests'],
['scrolledwindow'],
['searchbar'],
['singleselection'],
['slicelistmodel'],
['sortlistmodel'],
['spinbutton'],
['stylecontext'],

View File

@@ -0,0 +1,436 @@
/*
* 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[added];
guint i;
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]);
}
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 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, 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, 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);
}
static int
sort_inverse (gconstpointer a, gconstpointer b, gpointer data)
{
int ia = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (a), number_quark));
int ib = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (b), number_quark));
return ib - ia;
}
static void
test_persistence (void)
{
GtkSelectionModel *selection;
GListStore *store;
store = new_store (2, 1, 1);
add (store, 1);
add (store, 3);
add (store, 5);
add (store, 4);
add (store, 2);
selection = new_model (store);
gtk_selection_model_select_range (selection, 0, 3, FALSE);
assert_selection (selection, "1 3 5");
assert_selection_changes (selection, "0:3");
g_assert_true (gtk_selection_model_is_selected (selection, 0));
g_assert_true (gtk_selection_model_is_selected (selection, 1));
g_assert_true (gtk_selection_model_is_selected (selection, 2));
g_assert_false (gtk_selection_model_is_selected (selection, 3));
g_assert_false (gtk_selection_model_is_selected (selection, 4));
g_list_store_sort (store, sort_inverse, NULL);
assert_selection (selection, "5 3 1");
assert_selection_changes (selection, "");
g_assert_true (gtk_selection_model_is_selected (selection, 0));
g_assert_false (gtk_selection_model_is_selected (selection, 1));
g_assert_true (gtk_selection_model_is_selected (selection, 2));
g_assert_false (gtk_selection_model_is_selected (selection, 3));
g_assert_true (gtk_selection_model_is_selected (selection, 4));
ignore_changes (selection);
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 ("/singleselection/changes", test_changes);
#endif
g_test_add_func ("/multiselection/selection", test_selection);
g_test_add_func ("/multiselection/persistence", test_persistence);
return g_test_run ();
}

373
testsuite/gtk/noselection.c Normal file
View File

@@ -0,0 +1,373 @@
/*
* 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 (i > 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[added];
guint i;
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]);
}
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 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 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_no_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, FALSE);
g_assert_false (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_unselect_item (selection, 3);
g_assert_false (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_range (selection, 3, 2, FALSE);
g_assert_false (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_unselect_range (selection, 4, 2);
g_assert_false (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_all (selection);
g_assert_false (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_unselect_all (selection);
g_assert_false (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "");
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 ("/noselection/create", test_create);
#if GLIB_CHECK_VERSION (2, 58, 0) /* g_list_store_splice() is broken before 2.58 */
g_test_add_func ("/noselection/changes", test_changes);
#endif
g_test_add_func ("/noselection/selection", test_selection);
return g_test_run ();
}

View File

@@ -0,0 +1,489 @@
/*
* 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[added];
guint i;
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]);
}
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 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, gboolean autoselect, gboolean can_unselect)
{
GtkSelectionModel *result;
GString *changes;
result = GTK_SELECTION_MODEL (gtk_single_selection_new (G_LIST_MODEL (store)));
/* We want to return an empty selection unless autoselect is true,
* so undo the initial selection due to autoselect defaulting to TRUE.
*/
gtk_single_selection_set_autoselect (GTK_SINGLE_SELECTION (result), FALSE);
gtk_single_selection_set_can_unselect (GTK_SINGLE_SELECTION (result), TRUE);
gtk_selection_model_unselect_item (result, 0);
gtk_single_selection_set_autoselect (GTK_SINGLE_SELECTION (result), autoselect);
gtk_single_selection_set_can_unselect (GTK_SINGLE_SELECTION (result), can_unselect);
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, FALSE, FALSE);
g_assert_false (gtk_single_selection_get_autoselect (GTK_SINGLE_SELECTION (selection)));
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, FALSE, FALSE);
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, TRUE, FALSE);
assert_selection (selection, "1");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_item (selection, 3, FALSE, FALSE);
g_assert_true (ret);
assert_selection (selection, "4");
assert_selection_changes (selection, "0:4");
ret = gtk_selection_model_unselect_item (selection, 3);
g_assert_false (ret);
assert_selection (selection, "4");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_item (selection, 1, FALSE, FALSE);
g_assert_true (ret);
assert_selection (selection, "2");
assert_selection_changes (selection, "1:3");
ret = gtk_selection_model_select_range (selection, 3, 2, FALSE);
g_assert_false (ret);
assert_selection (selection, "2");
assert_selection_changes (selection, "");
ret = gtk_selection_model_unselect_range (selection, 4, 2);
g_assert_false (ret);
assert_selection (selection, "2");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_all (selection);
g_assert_false (ret);
assert_selection (selection, "2");
assert_selection_changes (selection, "");
ret = gtk_selection_model_unselect_all (selection);
g_assert_false (ret);
assert_selection (selection, "2");
assert_selection_changes (selection, "");
g_object_unref (store);
g_object_unref (selection);
}
static void
test_autoselect (void)
{
GtkSelectionModel *selection;
GListStore *store;
store = new_store (2, 1, 1);
selection = new_model (store, TRUE, FALSE);
assert_selection (selection, "");
assert_selection_changes (selection, "");
add (store, 1);
assert_selection (selection, "1");
assert_selection_changes (selection, "0:1");
splice (store, 0, 1, (guint[]) { 97 }, 1);
assert_selection (selection, "97");
assert_selection_changes (selection, "0:1");
ignore_changes (selection);
g_object_unref (store);
g_object_unref (selection);
}
static void
test_can_unselect (void)
{
GtkSelectionModel *selection;
GListStore *store;
gboolean ret;
store = new_store (1, 5, 1);
selection = new_model (store, TRUE, FALSE);
assert_selection (selection, "1");
assert_selection_changes (selection, "");
ret = gtk_selection_model_unselect_item (selection, 0);
g_assert_false (ret);
assert_selection (selection, "1");
assert_selection_changes (selection, "");
gtk_single_selection_set_can_unselect (GTK_SINGLE_SELECTION (selection), TRUE);
assert_selection (selection, "1");
ret = gtk_selection_model_unselect_item (selection, 0);
g_assert_true (ret);
assert_selection (selection, "");
assert_selection_changes (selection, "0:1");
ignore_changes (selection);
g_object_unref (store);
g_object_unref (selection);
}
static int
sort_inverse (gconstpointer a, gconstpointer b, gpointer data)
{
int ia = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (a), number_quark));
int ib = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (b), number_quark));
return ib - ia;
}
static void
test_persistence (void)
{
GtkSelectionModel *selection;
GListStore *store;
store = new_store (1, 5, 1);
selection = new_model (store, TRUE, FALSE);
assert_selection (selection, "1");
assert_selection_changes (selection, "");
g_assert_true (gtk_selection_model_is_selected (selection, 0));
g_assert_false (gtk_selection_model_is_selected (selection, 4));
g_list_store_sort (store, sort_inverse, NULL);
assert_selection (selection, "1");
assert_selection_changes (selection, "");
g_assert_false (gtk_selection_model_is_selected (selection, 0));
g_assert_true (gtk_selection_model_is_selected (selection, 4));
ignore_changes (selection);
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 ("/singleselection/create", test_create);
#if GLIB_CHECK_VERSION (2, 58, 0) /* g_list_store_splice() is broken before 2.58 */
g_test_add_func ("/singleselection/changes", test_changes);
#endif
g_test_add_func ("/singleselection/selection", test_selection);
g_test_add_func ("/singleselection/autoselect", test_autoselect);
g_test_add_func ("/singleselection/can-unselect", test_can_unselect);
g_test_add_func ("/singleselection/persistence", test_persistence);
return g_test_run ();
}

View File

@@ -0,0 +1,341 @@
/*
* 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 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 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[added];
guint i;
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]);
}
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 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
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
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 GtkSliceListModel *
new_model (GListStore *store, guint offset, guint size)
{
GtkSliceListModel *result;
GString *changes;
result = gtk_slice_list_model_new_for_type (G_TYPE_OBJECT);
if (store)
gtk_slice_list_model_set_model (result, G_LIST_MODEL (store));
gtk_slice_list_model_set_offset (result, offset);
gtk_slice_list_model_set_size (result, size);
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);
return result;
}
static void
test_create_empty (void)
{
GtkSliceListModel *slice;
slice = new_model (NULL, 0, 0);
assert_model (slice, "");
assert_changes (slice, "");
g_object_unref (slice);
}
static void
test_create (void)
{
GtkSliceListModel *slice;
GListStore *store;
store = new_store (1, 5, 2);
slice = new_model (store, 0, 10);
assert_model (slice, "1 3 5");
assert_changes (slice, "");
g_object_unref (store);
assert_model (slice, "1 3 5");
assert_changes (slice, "");
g_object_unref (slice);
}
static void
test_set_model (void)
{
GtkSliceListModel *slice;
GListStore *store;
slice = new_model (NULL, 0, 2);
assert_model (slice, "");
assert_changes (slice, "");
store = new_store (1, 7, 2);
gtk_slice_list_model_set_model (slice, G_LIST_MODEL (store));
assert_model (slice, "1 3");
assert_changes (slice, "0+2");
gtk_slice_list_model_set_model (slice, NULL);
assert_model (slice, "");
assert_changes (slice, "0-2");
g_object_unref (store);
g_object_unref (slice);
}
static void
test_set_slice (void)
{
GtkSliceListModel *slice;
GListStore *store;
store = new_store (1, 7, 2);
slice = new_model (store, 0, 3);
assert_model (slice, "1 3 5");
assert_changes (slice, "");
gtk_slice_list_model_set_offset (slice, 1);
assert_model (slice, "3 5 7");
assert_changes (slice, "0-3+3");
gtk_slice_list_model_set_size (slice, 2);
assert_model (slice, "3 5");
assert_changes (slice, "-2");
gtk_slice_list_model_set_size (slice, 10);
assert_model (slice, "3 5 7");
assert_changes (slice, "+2");
g_object_unref (store);
g_object_unref (slice);
}
static void
test_changes (void)
{
GtkSliceListModel *slice;
GListStore *store;
store = new_store (1, 20, 1);
slice = new_model (store, 10, 5);
assert_model (slice, "11 12 13 14 15");
assert_changes (slice, "");
g_list_store_remove (store, 19);
assert_changes (slice, "");
g_list_store_remove (store, 1);
assert_model (slice, "12 13 14 15 16");
assert_changes (slice, "0-5+5");
insert (store, 12, 99);
assert_model (slice, "12 13 99 14 15");
assert_changes (slice, "2-3+3");
splice (store, 13, 6, (guint[]) { 97 }, 1);
assert_model (slice, "12 13 99 97");
assert_changes (slice, "3-2+1");
splice (store, 13, 1, (guint[]) { 36, 37, 38 }, 3);
assert_model (slice, "12 13 99 36 37");
assert_changes (slice, "3-1+2");
g_list_store_remove_all (store);
assert_model (slice, "");
assert_changes (slice, "0-5");
g_object_unref (store);
g_object_unref (slice);
}
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?");
g_test_add_func ("/slicelistmodel/create_empty", test_create_empty);
g_test_add_func ("/slicelistmodel/create", test_create);
g_test_add_func ("/slicelistmodel/set-model", test_set_model);
g_test_add_func ("/slicelistmodel/set-slice", test_set_slice);
#if GLIB_CHECK_VERSION (2, 58, 0) /* g_list_store_splice() is broken before 2.58 */
g_test_add_func ("/slicelistmodel/changes", test_changes);
#endif
return g_test_run ();
}