1/* List Box/Complex
2 *
3 * GtkListBox allows lists with complicated layouts, using
4 * regular widgets supporting sorting and filtering.
5 */
6
7#include <gtk/gtk.h>
8#include <stdlib.h>
9#include <string.h>
10
11static GdkTexture *avatar_texture_other;
12static GtkWidget *window = NULL;
13
14#define GTK_TYPE_MESSAGE (gtk_message_get_type ())
15#define GTK_MESSAGE(message) (G_TYPE_CHECK_INSTANCE_CAST ((message), GTK_TYPE_MESSAGE, GtkMessage))
16#define GTK_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MESSAGE, GtkMessageClass))
17#define GTK_IS_MESSAGE(message) (G_TYPE_CHECK_INSTANCE_TYPE ((message), GTK_TYPE_MESSAGE))
18#define GTK_IS_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MESSAGE))
19#define GTK_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MESSAGE, GtkMessageClass))
20
21#define GTK_TYPE_MESSAGE_ROW (gtk_message_row_get_type ())
22#define GTK_MESSAGE_ROW(message_row) (G_TYPE_CHECK_INSTANCE_CAST ((message_row), GTK_TYPE_MESSAGE_ROW, GtkMessageRow))
23#define GTK_MESSAGE_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MESSAGE_ROW, GtkMessageRowClass))
24#define GTK_IS_MESSAGE_ROW(message_row) (G_TYPE_CHECK_INSTANCE_TYPE ((message_row), GTK_TYPE_MESSAGE_ROW))
25#define GTK_IS_MESSAGE_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MESSAGE_ROW))
26#define GTK_MESSAGE_ROW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MESSAGE_ROW, GtkMessageRowClass))
27
28typedef struct _GtkMessage GtkMessage;
29typedef struct _GtkMessageClass GtkMessageClass;
30typedef struct _GtkMessageRow GtkMessageRow;
31typedef struct _GtkMessageRowClass GtkMessageRowClass;
32typedef struct _GtkMessageRowPrivate GtkMessageRowPrivate;
33
34
35struct _GtkMessage
36{
37 GObject parent;
38
39 guint id;
40 char *sender_name;
41 char *sender_nick;
42 char *message;
43 gint64 time;
44 guint reply_to;
45 char *resent_by;
46 int n_favorites;
47 int n_reshares;
48};
49
50struct _GtkMessageClass
51{
52 GObjectClass parent_class;
53};
54
55struct _GtkMessageRow
56{
57 GtkListBoxRow parent;
58
59 GtkMessageRowPrivate *priv;
60};
61
62struct _GtkMessageRowClass
63{
64 GtkListBoxRowClass parent_class;
65};
66
67struct _GtkMessageRowPrivate
68{
69 GtkMessage *message;
70 GtkRevealer *details_revealer;
71 GtkImage *avatar_image;
72 GtkWidget *extra_buttons_box;
73 GtkLabel *content_label;
74 GtkLabel *source_name;
75 GtkLabel *source_nick;
76 GtkLabel *short_time_label;
77 GtkLabel *detailed_time_label;
78 GtkBox *resent_box;
79 GtkLinkButton *resent_by_button;
80 GtkLabel *n_favorites_label;
81 GtkLabel *n_reshares_label;
82 GtkButton *expand_button;
83};
84
85GType gtk_message_get_type (void) G_GNUC_CONST;
86GType gtk_message_row_get_type (void) G_GNUC_CONST;
87
88G_DEFINE_TYPE (GtkMessage, gtk_message, G_TYPE_OBJECT);
89
90static void
91gtk_message_finalize (GObject *obj)
92{
93 GtkMessage *msg = GTK_MESSAGE (obj);
94
95 g_free (mem: msg->sender_name);
96 g_free (mem: msg->sender_nick);
97 g_free (mem: msg->message);
98 g_free (mem: msg->resent_by);
99
100 G_OBJECT_CLASS (gtk_message_parent_class)->finalize (obj);
101}
102static void
103gtk_message_class_init (GtkMessageClass *klass)
104{
105 GObjectClass *object_class = G_OBJECT_CLASS (klass);
106 object_class->finalize = gtk_message_finalize;
107}
108
109static void
110gtk_message_init (GtkMessage *msg)
111{
112}
113
114static void
115gtk_message_parse (GtkMessage *msg, const char *str)
116{
117 char **strv;
118 int i;
119
120 strv = g_strsplit (string: str, delimiter: "|", max_tokens: 0);
121
122 i = 0;
123 msg->id = strtol (nptr: strv[i++], NULL, base: 10);
124 msg->sender_name = g_strdup (str: strv[i++]);
125 msg->sender_nick = g_strdup (str: strv[i++]);
126 msg->message = g_strdup (str: strv[i++]);
127 msg->time = strtol (nptr: strv[i++], NULL, base: 10);
128 if (strv[i])
129 {
130 msg->reply_to = strtol (nptr: strv[i++], NULL, base: 10);
131 if (strv[i])
132 {
133 if (*strv[i])
134 msg->resent_by = g_strdup (str: strv[i]);
135 i++;
136 if (strv[i])
137 {
138 msg->n_favorites = strtol (nptr: strv[i++], NULL, base: 10);
139 if (strv[i])
140 {
141 msg->n_reshares = strtol (nptr: strv[i++], NULL, base: 10);
142 }
143
144 }
145 }
146 }
147
148 g_strfreev (str_array: strv);
149}
150
151static GtkMessage *
152gtk_message_new (const char *str)
153{
154 GtkMessage *msg;
155 msg = g_object_new (object_type: gtk_message_get_type (), NULL);
156 gtk_message_parse (msg, str);
157 return msg;
158}
159
160G_DEFINE_TYPE_WITH_PRIVATE (GtkMessageRow, gtk_message_row, GTK_TYPE_LIST_BOX_ROW);
161
162
163static void
164gtk_message_row_update (GtkMessageRow *row)
165{
166 GtkMessageRowPrivate *priv = row->priv;
167 GDateTime *t;
168 char *s;
169
170 gtk_label_set_text (self: priv->source_name, str: priv->message->sender_name);
171 gtk_label_set_text (self: priv->source_nick, str: priv->message->sender_nick);
172 gtk_label_set_text (self: priv->content_label, str: priv->message->message);
173 t = g_date_time_new_from_unix_utc (t: priv->message->time);
174 s = g_date_time_format (datetime: t, format: "%e %b %y");
175 gtk_label_set_text (self: priv->short_time_label, str: s);
176 g_free (mem: s);
177 s = g_date_time_format (datetime: t, format: "%X - %e %b %Y");
178 gtk_label_set_text (self: priv->detailed_time_label, str: s);
179 g_free (mem: s);
180 g_date_time_unref (datetime: t);
181
182 gtk_widget_set_visible (GTK_WIDGET(priv->n_favorites_label),
183 visible: priv->message->n_favorites != 0);
184 s = g_strdup_printf (format: "<b>%d</b>\nFavorites", priv->message->n_favorites);
185 gtk_label_set_markup (self: priv->n_favorites_label, str: s);
186 g_free (mem: s);
187
188 gtk_widget_set_visible (GTK_WIDGET(priv->n_reshares_label),
189 visible: priv->message->n_reshares != 0);
190 s = g_strdup_printf (format: "<b>%d</b>\nReshares", priv->message->n_reshares);
191 gtk_label_set_markup (self: priv->n_reshares_label, str: s);
192 g_free (mem: s);
193
194 gtk_widget_set_visible (GTK_WIDGET (priv->resent_box), visible: priv->message->resent_by != NULL);
195 if (priv->message->resent_by)
196 gtk_button_set_label (GTK_BUTTON (priv->resent_by_button), label: priv->message->resent_by);
197
198 if (strcmp (s1: priv->message->sender_nick, s2: "@GTKtoolkit") == 0)
199 gtk_image_set_from_icon_name (image: priv->avatar_image, icon_name: "org.gtk.Demo4");
200 else
201 gtk_image_set_from_paintable (image: priv->avatar_image, paintable: GDK_PAINTABLE (ptr: avatar_texture_other));
202
203}
204
205static void
206gtk_message_row_expand (GtkMessageRow *row)
207{
208 GtkMessageRowPrivate *priv = row->priv;
209 gboolean expand;
210
211 expand = !gtk_revealer_get_reveal_child (revealer: priv->details_revealer);
212
213 gtk_revealer_set_reveal_child (revealer: priv->details_revealer, reveal_child: expand);
214 if (expand)
215 gtk_button_set_label (button: priv->expand_button, label: "Hide");
216 else
217 gtk_button_set_label (button: priv->expand_button, label: "Expand");
218}
219
220static void
221expand_clicked (GtkMessageRow *row,
222 GtkButton *button)
223{
224 gtk_message_row_expand (row);
225}
226
227static void
228reshare_clicked (GtkMessageRow *row,
229 GtkButton *button)
230{
231 GtkMessageRowPrivate *priv = row->priv;
232
233 priv->message->n_reshares++;
234 gtk_message_row_update (row);
235}
236
237static void
238favorite_clicked (GtkMessageRow *row,
239 GtkButton *button)
240{
241 GtkMessageRowPrivate *priv = row->priv;
242
243 priv->message->n_favorites++;
244 gtk_message_row_update (row);
245}
246
247static void
248gtk_message_row_state_flags_changed (GtkWidget *widget,
249 GtkStateFlags previous_state_flags)
250{
251 GtkMessageRowPrivate *priv = GTK_MESSAGE_ROW (widget)->priv;
252 GtkStateFlags flags;
253 gboolean visible;
254
255 flags = gtk_widget_get_state_flags (widget);
256
257 visible = flags & (GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_SELECTED) ? TRUE : FALSE;
258 gtk_widget_set_visible (widget: priv->extra_buttons_box, visible);
259
260 GTK_WIDGET_CLASS (gtk_message_row_parent_class)->state_flags_changed (widget, previous_state_flags);
261}
262
263static void
264gtk_message_row_finalize (GObject *obj)
265{
266 GtkMessageRowPrivate *priv = GTK_MESSAGE_ROW (obj)->priv;
267 g_object_unref (object: priv->message);
268 G_OBJECT_CLASS (gtk_message_row_parent_class)->finalize(obj);
269}
270
271static void
272gtk_message_row_class_init (GtkMessageRowClass *klass)
273{
274 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
275 GObjectClass *object_class = G_OBJECT_CLASS (klass);
276
277 object_class->finalize = gtk_message_row_finalize;
278
279 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/listbox/listbox.ui");
280 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, content_label);
281 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, source_name);
282 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, source_nick);
283 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, short_time_label);
284 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, detailed_time_label);
285 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, extra_buttons_box);
286 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, details_revealer);
287 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, avatar_image);
288 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, resent_box);
289 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, resent_by_button);
290 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, n_reshares_label);
291 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, n_favorites_label);
292 gtk_widget_class_bind_template_child_private (widget_class, GtkMessageRow, expand_button);
293 gtk_widget_class_bind_template_callback (widget_class, expand_clicked);
294 gtk_widget_class_bind_template_callback (widget_class, reshare_clicked);
295 gtk_widget_class_bind_template_callback (widget_class, favorite_clicked);
296
297 widget_class->state_flags_changed = gtk_message_row_state_flags_changed;
298}
299
300static void
301gtk_message_row_init (GtkMessageRow *row)
302{
303 row->priv = gtk_message_row_get_instance_private (self: row);
304
305 gtk_widget_init_template (GTK_WIDGET (row));
306}
307
308static GtkMessageRow *
309gtk_message_row_new (GtkMessage *message)
310{
311 GtkMessageRow *row;
312
313 row = g_object_new (object_type: gtk_message_row_get_type (), NULL);
314 row->priv->message = message;
315 gtk_message_row_update (row);
316
317 return row;
318}
319
320static int
321gtk_message_row_sort (GtkMessageRow *a, GtkMessageRow *b, gpointer data)
322{
323 return b->priv->message->time - a->priv->message->time;
324}
325
326static void
327row_activated (GtkListBox *listbox, GtkListBoxRow *row)
328{
329 gtk_message_row_expand (GTK_MESSAGE_ROW (row));
330}
331
332GtkWidget *
333do_listbox (GtkWidget *do_widget)
334{
335 GtkWidget *scrolled, *listbox, *vbox, *label;
336 GtkMessage *message;
337 GtkMessageRow *row;
338 GBytes *data;
339 char **lines;
340 int i;
341
342 if (!window)
343 {
344 avatar_texture_other = gdk_texture_new_from_resource (resource_path: "/listbox/apple-red.png");
345
346 window = gtk_window_new ();
347 gtk_window_set_display (GTK_WINDOW (window),
348 display: gtk_widget_get_display (widget: do_widget));
349 gtk_window_set_title (GTK_WINDOW (window), title: "List Box — Complex");
350 gtk_window_set_default_size (GTK_WINDOW (window), width: 400, height: 600);
351 g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window);
352
353 vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 12);
354 gtk_window_set_child (GTK_WINDOW (window), child: vbox);
355 label = gtk_label_new (str: "Messages from GTK and friends");
356 gtk_box_append (GTK_BOX (vbox), child: label);
357 scrolled = gtk_scrolled_window_new ();
358 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), hscrollbar_policy: GTK_POLICY_NEVER, vscrollbar_policy: GTK_POLICY_AUTOMATIC);
359 gtk_widget_set_vexpand (widget: scrolled, TRUE);
360 gtk_box_append (GTK_BOX (vbox), child: scrolled);
361 listbox = gtk_list_box_new ();
362 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled), child: listbox);
363
364 gtk_list_box_set_sort_func (GTK_LIST_BOX (listbox), sort_func: (GtkListBoxSortFunc)gtk_message_row_sort, user_data: listbox, NULL);
365 gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (listbox), FALSE);
366 g_signal_connect (listbox, "row-activated", G_CALLBACK (row_activated), NULL);
367
368 data = g_resources_lookup_data (path: "/listbox/messages.txt", lookup_flags: 0, NULL);
369 lines = g_strsplit (string: g_bytes_get_data (bytes: data, NULL), delimiter: "\n", max_tokens: 0);
370
371 for (i = 0; lines[i] != NULL && *lines[i]; i++)
372 {
373 message = gtk_message_new (str: lines[i]);
374 row = gtk_message_row_new (message);
375 gtk_widget_show (GTK_WIDGET (row));
376 gtk_list_box_insert (GTK_LIST_BOX (listbox), GTK_WIDGET (row), position: -1);
377 }
378
379 g_strfreev (str_array: lines);
380 g_bytes_unref (bytes: data);
381 }
382
383 if (!gtk_widget_get_visible (widget: window))
384 gtk_widget_show (widget: window);
385 else
386 gtk_window_destroy (GTK_WINDOW (window));
387
388 return window;
389}
390

source code of gtk/demos/gtk-demo/listbox.c