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 | |
11 | static GdkTexture *avatar_texture_other; |
12 | static 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 | |
28 | typedef struct _GtkMessage GtkMessage; |
29 | typedef struct _GtkMessageClass GtkMessageClass; |
30 | typedef struct _GtkMessageRow GtkMessageRow; |
31 | typedef struct _GtkMessageRowClass GtkMessageRowClass; |
32 | typedef struct _GtkMessageRowPrivate GtkMessageRowPrivate; |
33 | |
34 | |
35 | struct _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 | |
50 | struct _GtkMessageClass |
51 | { |
52 | GObjectClass parent_class; |
53 | }; |
54 | |
55 | struct _GtkMessageRow |
56 | { |
57 | GtkListBoxRow parent; |
58 | |
59 | GtkMessageRowPrivate *priv; |
60 | }; |
61 | |
62 | struct _GtkMessageRowClass |
63 | { |
64 | GtkListBoxRowClass parent_class; |
65 | }; |
66 | |
67 | struct _GtkMessageRowPrivate |
68 | { |
69 | GtkMessage *message; |
70 | GtkRevealer *details_revealer; |
71 | GtkImage *avatar_image; |
72 | GtkWidget *; |
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 | |
85 | GType gtk_message_get_type (void) G_GNUC_CONST; |
86 | GType gtk_message_row_get_type (void) G_GNUC_CONST; |
87 | |
88 | G_DEFINE_TYPE (GtkMessage, gtk_message, G_TYPE_OBJECT); |
89 | |
90 | static void |
91 | gtk_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 | } |
102 | static void |
103 | gtk_message_class_init (GtkMessageClass *klass) |
104 | { |
105 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
106 | object_class->finalize = gtk_message_finalize; |
107 | } |
108 | |
109 | static void |
110 | gtk_message_init (GtkMessage *msg) |
111 | { |
112 | } |
113 | |
114 | static void |
115 | gtk_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 | |
151 | static GtkMessage * |
152 | gtk_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 | |
160 | G_DEFINE_TYPE_WITH_PRIVATE (GtkMessageRow, gtk_message_row, GTK_TYPE_LIST_BOX_ROW); |
161 | |
162 | |
163 | static void |
164 | gtk_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 | |
205 | static void |
206 | gtk_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 | |
220 | static void |
221 | expand_clicked (GtkMessageRow *row, |
222 | GtkButton *button) |
223 | { |
224 | gtk_message_row_expand (row); |
225 | } |
226 | |
227 | static void |
228 | reshare_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 | |
237 | static void |
238 | favorite_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 | |
247 | static void |
248 | gtk_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 | |
263 | static void |
264 | gtk_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 | |
271 | static void |
272 | gtk_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 | |
300 | static void |
301 | gtk_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 | |
308 | static GtkMessageRow * |
309 | gtk_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 | |
320 | static int |
321 | gtk_message_row_sort (GtkMessageRow *a, GtkMessageRow *b, gpointer data) |
322 | { |
323 | return b->priv->message->time - a->priv->message->time; |
324 | } |
325 | |
326 | static void |
327 | row_activated (GtkListBox *listbox, GtkListBoxRow *row) |
328 | { |
329 | gtk_message_row_expand (GTK_MESSAGE_ROW (row)); |
330 | } |
331 | |
332 | GtkWidget * |
333 | do_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 | |