Compare commits
53 Commits
add-mutter
...
wip/matthi
Author | SHA1 | Date | |
---|---|---|---|
|
8833158176 | ||
|
964e5a9fe2 | ||
|
615337863b | ||
|
437fa857f4 | ||
|
9757582e8d | ||
|
21ccf55aed | ||
|
62fce3bdf5 | ||
|
30db87b120 | ||
|
9e2eb047d0 | ||
|
a68ee2d4f1 | ||
|
ce1ac00c59 | ||
|
75a1bafcc0 | ||
|
c70130edc5 | ||
|
596b9202ce | ||
|
c8f27d1ac6 | ||
|
a0f8bf0f6b | ||
|
d0ef5f5db4 | ||
|
dfd280acd0 | ||
|
c97f62a471 | ||
|
135a875c25 | ||
|
09cbb5154f | ||
|
9e2250e35d | ||
|
d844c98c8a | ||
|
779fb56213 | ||
|
ad25afc298 | ||
|
18d7a7daaa | ||
|
be4fe26fef | ||
|
46fa122c98 | ||
|
2b780de342 | ||
|
ec0483bdfe | ||
|
1547d81eb9 | ||
|
498351784c | ||
|
1f07f9bbae | ||
|
aa71d8fc51 | ||
|
44e4ddfdf0 | ||
|
3481dddbc2 | ||
|
841d6cb327 | ||
|
b21fd5fd22 | ||
|
660a6f4e8e | ||
|
72902be840 | ||
|
2a7746dba6 | ||
|
badad04eb7 | ||
|
81690c7049 | ||
|
ae1331ee92 | ||
|
e2354e5f3b | ||
|
2d472da14b | ||
|
7edeef2f71 | ||
|
07ec4f9de0 | ||
|
b00b9500e7 | ||
|
d7e64c4423 | ||
|
5596063220 | ||
|
3485a4582c | ||
|
f1c34aefe0 |
@@ -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>
|
||||
|
@@ -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
323
demos/gtk-demo/listview.c
Normal 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
302
demos/gtk-demo/listview.ui
Normal 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"><b>2</b>
|
||||
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"><b>2</b>
|
||||
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>
|
@@ -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
79
demos/gtk-demo/message.c
Normal 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
34
demos/gtk-demo/message.h
Normal 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);
|
@@ -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" />
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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
459
gtk/gtklistitem.c
Normal 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
51
gtk/gtklistitem.h
Normal 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
150
gtk/gtklistitemfactory.c
Normal 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));
|
||||
}
|
64
gtk/gtklistitemfactoryprivate.h
Normal file
64
gtk/gtklistitemfactoryprivate.h
Normal 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
423
gtk/gtklistitemmanager.c
Normal 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);
|
||||
}
|
88
gtk/gtklistitemmanagerprivate.h
Normal file
88
gtk/gtklistitemmanagerprivate.h
Normal 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
41
gtk/gtklistitemprivate.h
Normal 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__ */
|
@@ -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
|
||||
|
@@ -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
1421
gtk/gtklistview.c
Normal file
File diff suppressed because it is too large
Load Diff
82
gtk/gtklistview.h
Normal file
82
gtk/gtklistview.h
Normal 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__ */
|
@@ -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
374
gtk/gtkmultiselection.c
Normal 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
37
gtk/gtkmultiselection.h
Normal 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
205
gtk/gtknoselection.c
Normal 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
37
gtk/gtknoselection.h
Normal 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
290
gtk/gtkselectionmodel.c
Normal 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
122
gtk/gtkselectionmodel.h
Normal 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
292
gtk/gtkset.c
Normal 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
63
gtk/gtkset.h
Normal 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
559
gtk/gtksingleselection.c
Normal 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
65
gtk/gtksingleselection.h
Normal 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__ */
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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',
|
||||
|
@@ -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>
|
||||
|
@@ -62,6 +62,8 @@ gtk_tests = [
|
||||
['testlist2'],
|
||||
['testlist3'],
|
||||
['testlist4'],
|
||||
['testlistview'],
|
||||
['testlistview-animating'],
|
||||
['testlevelbar'],
|
||||
['testlockbutton'],
|
||||
['testmenubutton'],
|
||||
|
192
tests/testlistview-animating.c
Normal file
192
tests/testlistview-animating.c
Normal 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
493
tests/testlistview.c
Normal 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;
|
||||
}
|
@@ -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'],
|
||||
|
436
testsuite/gtk/multiselection.c
Normal file
436
testsuite/gtk/multiselection.c
Normal 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
373
testsuite/gtk/noselection.c
Normal 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 ();
|
||||
}
|
489
testsuite/gtk/singleselection.c
Normal file
489
testsuite/gtk/singleselection.c
Normal 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 ();
|
||||
}
|
341
testsuite/gtk/slicelistmodel.c
Normal file
341
testsuite/gtk/slicelistmodel.c
Normal 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 ();
|
||||
}
|
Reference in New Issue
Block a user