1/* simple.c
2 * Copyright (C) 2017 Red Hat, Inc
3 * Author: Benjamin Otte
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18#include <gtk/gtk.h>
19
20
21#define STRING_TYPE_HOLDER (string_holder_get_type ())
22G_DECLARE_FINAL_TYPE (StringHolder, string_holder, STRING, HOLDER, GObject)
23
24struct _StringHolder {
25 GObject parent_instance;
26 char *title;
27 char *icon;
28 char *description;
29};
30
31G_DEFINE_TYPE (StringHolder, string_holder, G_TYPE_OBJECT);
32
33static void
34string_holder_init (StringHolder *holder)
35{
36}
37
38static void
39string_holder_finalize (GObject *object)
40{
41 StringHolder *holder = STRING_HOLDER (ptr: object);
42
43 g_free (mem: holder->title);
44 g_free (mem: holder->icon);
45 g_free (mem: holder->description);
46
47 G_OBJECT_CLASS (string_holder_parent_class)->finalize (object);
48}
49
50static void
51string_holder_class_init (StringHolderClass *class)
52{
53 GObjectClass *object_class = G_OBJECT_CLASS (class);
54
55 object_class->finalize = string_holder_finalize;
56}
57
58static StringHolder *
59string_holder_new (const char *title, const char *icon, const char *description)
60{
61 StringHolder *holder = g_object_new (STRING_TYPE_HOLDER, NULL);
62 holder->title = g_strdup (str: title);
63 holder->icon = g_strdup (str: icon);
64 holder->description = g_strdup (str: description);
65 return holder;
66}
67
68static void
69strings_setup_item_single_line (GtkSignalListItemFactory *factory,
70 GtkListItem *item)
71{
72 GtkWidget *box, *image, *title;
73
74 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 10);
75
76 image = gtk_image_new ();
77 title = gtk_label_new (str: "");
78 gtk_label_set_xalign (GTK_LABEL (title), xalign: 0.0);
79
80 gtk_box_append (GTK_BOX (box), child: image);
81 gtk_box_append (GTK_BOX (box), child: title);
82
83 g_object_set_data (G_OBJECT (item), key: "title", data: title);
84 g_object_set_data (G_OBJECT (item), key: "image", data: image);
85
86 gtk_list_item_set_child (self: item, child: box);
87}
88
89static void
90strings_setup_item_full (GtkSignalListItemFactory *factory,
91 GtkListItem *item)
92{
93 GtkWidget *box, *box2, *image, *title, *description;
94
95 image = gtk_image_new ();
96 title = gtk_label_new (str: "");
97 gtk_label_set_xalign (GTK_LABEL (title), xalign: 0.0);
98 description = gtk_label_new (str: "");
99 gtk_label_set_xalign (GTK_LABEL (description), xalign: 0.0);
100 gtk_widget_add_css_class (widget: description, css_class: "dim-label");
101
102 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 10);
103 box2 = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 2);
104
105 gtk_box_append (GTK_BOX (box), child: image);
106 gtk_box_append (GTK_BOX (box), child: box2);
107 gtk_box_append (GTK_BOX (box2), child: title);
108 gtk_box_append (GTK_BOX (box2), child: description);
109
110 g_object_set_data (G_OBJECT (item), key: "title", data: title);
111 g_object_set_data (G_OBJECT (item), key: "image", data: image);
112 g_object_set_data (G_OBJECT (item), key: "description", data: description);
113
114 gtk_list_item_set_child (self: item, child: box);
115}
116
117static void
118strings_bind_item (GtkSignalListItemFactory *factory,
119 GtkListItem *item)
120{
121 GtkWidget *image, *title, *description;
122 StringHolder *holder;
123
124 holder = gtk_list_item_get_item (self: item);
125
126 title = g_object_get_data (G_OBJECT (item), key: "title");
127 image = g_object_get_data (G_OBJECT (item), key: "image");
128 description = g_object_get_data (G_OBJECT (item), key: "description");
129
130 gtk_label_set_label (GTK_LABEL (title), str: holder->title);
131 if (image)
132 {
133 gtk_image_set_from_icon_name (GTK_IMAGE (image), icon_name: holder->icon);
134 gtk_widget_set_visible (widget: image, visible: holder->icon != NULL);
135 }
136 if (description)
137 {
138 gtk_label_set_label (GTK_LABEL (description), str: holder->description);
139 gtk_widget_set_visible (widget: description , visible: holder->description != NULL);
140 }
141}
142
143static GtkListItemFactory *
144strings_factory_new (gboolean full)
145{
146 GtkListItemFactory *factory;
147
148 factory = gtk_signal_list_item_factory_new ();
149 if (full)
150 g_signal_connect (factory, "setup", G_CALLBACK (strings_setup_item_full), NULL);
151 else
152 g_signal_connect (factory, "setup", G_CALLBACK (strings_setup_item_single_line), NULL);
153 g_signal_connect (factory, "bind", G_CALLBACK (strings_bind_item), NULL);
154
155 return factory;
156}
157
158static GListModel *
159strings_model_new (const char *const *titles,
160 const char *const *icons,
161 const char *const *descriptions)
162{
163 GListStore *store;
164 int i;
165
166 store = g_list_store_new (STRING_TYPE_HOLDER);
167 for (i = 0; titles[i]; i++)
168 {
169 StringHolder *holder = string_holder_new (title: titles[i],
170 icon: icons ? icons[i] : NULL,
171 description: descriptions ? descriptions[i] : NULL);
172 g_list_store_append (store, item: holder);
173 g_object_unref (object: holder);
174 }
175
176 return G_LIST_MODEL (ptr: store);
177}
178
179static GtkWidget *
180drop_down_new_from_strings (const char *const *titles,
181 const char *const *icons,
182 const char *const *descriptions)
183{
184 GtkWidget *widget;
185 GListModel *model;
186 GtkListItemFactory *factory;
187 GtkListItemFactory *list_factory;
188
189 g_return_val_if_fail (titles != NULL, NULL);
190 g_return_val_if_fail (icons == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)titles), NULL);
191 g_return_val_if_fail (descriptions == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)descriptions), NULL);
192
193 model = strings_model_new (titles, icons, descriptions);
194 factory = strings_factory_new (FALSE);
195 if (icons != NULL || descriptions != NULL)
196 list_factory = strings_factory_new (TRUE);
197 else
198 list_factory = NULL;
199
200 widget = g_object_new (GTK_TYPE_DROP_DOWN,
201 first_property_name: "model", model,
202 "factory", factory,
203 "list-factory", list_factory,
204 NULL);
205
206 g_object_unref (object: model);
207 g_object_unref (object: factory);
208 if (list_factory)
209 g_object_unref (object: list_factory);
210
211 return widget;
212}
213
214static char *
215get_family_name (gpointer item)
216{
217 return g_strdup (str: pango_font_family_get_name (PANGO_FONT_FAMILY (item)));
218}
219
220static char *
221get_title (gpointer item)
222{
223 return g_strdup (str: STRING_HOLDER (ptr: item)->title);
224}
225
226static gboolean
227quit_cb (GtkWindow *window,
228 gpointer data)
229{
230 *((gboolean*)data) = TRUE;
231
232 g_main_context_wakeup (NULL);
233
234 return TRUE;
235}
236
237#define GTK_TYPE_STRING_PAIR (gtk_string_pair_get_type ())
238G_DECLARE_FINAL_TYPE (GtkStringPair, gtk_string_pair, GTK, STRING_PAIR, GObject)
239
240struct _GtkStringPair {
241 GObject parent_instance;
242 char *id;
243 char *string;
244};
245
246enum {
247 PROP_ID = 1,
248 PROP_STRING,
249 PROP_NUM_PROPERTIES
250};
251
252G_DEFINE_TYPE (GtkStringPair, gtk_string_pair, G_TYPE_OBJECT);
253
254static void
255gtk_string_pair_init (GtkStringPair *pair)
256{
257}
258
259static void
260gtk_string_pair_finalize (GObject *object)
261{
262 GtkStringPair *pair = GTK_STRING_PAIR (ptr: object);
263
264 g_free (mem: pair->id);
265 g_free (mem: pair->string);
266
267 G_OBJECT_CLASS (gtk_string_pair_parent_class)->finalize (object);
268}
269
270static void
271gtk_string_pair_set_property (GObject *object,
272 guint property_id,
273 const GValue *value,
274 GParamSpec *pspec)
275{
276 GtkStringPair *pair = GTK_STRING_PAIR (ptr: object);
277
278 switch (property_id)
279 {
280 case PROP_STRING:
281 g_free (mem: pair->string);
282 pair->string = g_value_dup_string (value);
283 break;
284
285 case PROP_ID:
286 g_free (mem: pair->id);
287 pair->id = g_value_dup_string (value);
288 break;
289
290 default:
291 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
292 break;
293 }
294}
295
296static void
297gtk_string_pair_get_property (GObject *object,
298 guint property_id,
299 GValue *value,
300 GParamSpec *pspec)
301{
302 GtkStringPair *pair = GTK_STRING_PAIR (ptr: object);
303
304 switch (property_id)
305 {
306 case PROP_STRING:
307 g_value_set_string (value, v_string: pair->string);
308 break;
309
310 case PROP_ID:
311 g_value_set_string (value, v_string: pair->id);
312 break;
313
314 default:
315 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
316 break;
317 }
318}
319
320static void
321gtk_string_pair_class_init (GtkStringPairClass *class)
322{
323 GObjectClass *object_class = G_OBJECT_CLASS (class);
324 GParamSpec *pspec;
325
326 object_class->finalize = gtk_string_pair_finalize;
327 object_class->set_property = gtk_string_pair_set_property;
328 object_class->get_property = gtk_string_pair_get_property;
329
330 pspec = g_param_spec_string (name: "string", nick: "String", blurb: "String",
331 NULL,
332 flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
333
334 g_object_class_install_property (oclass: object_class, property_id: PROP_STRING, pspec);
335
336 pspec = g_param_spec_string (name: "id", nick: "ID", blurb: "ID",
337 NULL,
338 flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
339
340 g_object_class_install_property (oclass: object_class, property_id: PROP_ID, pspec);
341}
342
343static GtkStringPair *
344gtk_string_pair_new (const char *id,
345 const char *string)
346{
347 return g_object_new (GTK_TYPE_STRING_PAIR,
348 first_property_name: "id", id,
349 "string", string,
350 NULL);
351}
352
353static const char *
354gtk_string_pair_get_string (GtkStringPair *pair)
355{
356 return pair->string;
357}
358
359static const char *
360gtk_string_pair_get_id (GtkStringPair *pair)
361{
362 return pair->id;
363}
364
365static void
366setup_no_item (GtkSignalListItemFactory *factory,
367 GtkListItem *item)
368{
369}
370
371static void
372setup_list_item (GtkSignalListItemFactory *factory,
373 GtkListItem *item)
374{
375 GtkWidget *label;
376
377 label = gtk_label_new (str: "");
378 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START);
379 gtk_list_item_set_child (self: item, child: label);
380}
381
382static void
383bind_list_item (GtkSignalListItemFactory *factory,
384 GtkListItem *item)
385{
386 GtkStringPair *pair;
387 GtkWidget *label;
388
389 pair = gtk_list_item_get_item (self: item);
390 label = gtk_list_item_get_child (self: item);
391
392 gtk_label_set_text (GTK_LABEL (label), str: gtk_string_pair_get_string (pair));
393}
394
395static void
396selected_changed (GtkDropDown *dropdown,
397 GParamSpec *pspec,
398 gpointer data)
399{
400 GListModel *model;
401 guint selected;
402 GtkStringPair *pair;
403
404 model = gtk_drop_down_get_model (self: dropdown);
405 selected = gtk_drop_down_get_selected (self: dropdown);
406
407 pair = g_list_model_get_item (list: model, position: selected);
408
409 g_print (format: "selected %s\n", gtk_string_pair_get_id (pair));
410
411 g_object_unref (object: pair);
412}
413
414static void
415selected_changed2 (GtkDropDown *dropdown,
416 GParamSpec *pspec,
417 gpointer data)
418{
419 GListModel *model;
420 guint selected;
421 GtkStringPair *pair;
422 GtkWidget *entry = data;
423
424 model = gtk_drop_down_get_model (self: dropdown);
425 selected = gtk_drop_down_get_selected (self: dropdown);
426
427 pair = g_list_model_get_item (list: model, position: selected);
428
429 gtk_editable_set_text (GTK_EDITABLE (entry), text: gtk_string_pair_get_string (pair));
430
431 g_object_unref (object: pair);
432}
433
434int
435main (int argc, char *argv[])
436{
437 GtkWidget *window, *button, *box, *spin, *check;
438 GListModel *model;
439 GtkExpression *expression;
440 const char * const times[] = { "1 minute", "2 minutes", "5 minutes", "20 minutes", NULL };
441 const char * const many_times[] = {
442 "1 minute", "2 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes",
443 "25 minutes", "30 minutes", "35 minutes", "40 minutes", "45 minutes", "50 minutes",
444 "55 minutes", "1 hour", "2 hours", "3 hours", "5 hours", "6 hours", "7 hours",
445 "8 hours", "9 hours", "10 hours", "11 hours", "12 hours", NULL
446 };
447 const char * const device_titles[] = { "Digital Output", "Headphones", "Digital Output", "Analog Output", NULL };
448 const char * const device_icons[] = { "audio-card-symbolic", "audio-headphones-symbolic", "audio-card-symbolic", "audio-card-symbolic", NULL };
449 const char * const device_descriptions[] = {
450 "Built-in Audio", "Built-in audio", "Thinkpad Tunderbolt 3 Dock USB Audio", "Thinkpad Tunderbolt 3 Dock USB Audio", NULL
451 };
452 gboolean quit = FALSE;
453 GListStore *store;
454 GtkListItemFactory *factory;
455 GtkWidget *entry;
456 GtkWidget *hbox;
457
458 gtk_init ();
459
460 window = gtk_window_new ();
461 gtk_window_set_title (GTK_WINDOW (window), title: "hello world");
462 gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
463 g_signal_connect (window, "close-request", G_CALLBACK (quit_cb), &quit);
464
465 box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 10);
466 gtk_widget_set_margin_start (widget: box, margin: 10);
467 gtk_widget_set_margin_end (widget: box, margin: 10);
468 gtk_widget_set_margin_top (widget: box, margin: 10);
469 gtk_widget_set_margin_bottom (widget: box, margin: 10);
470 gtk_window_set_child (GTK_WINDOW (window), child: box);
471
472 button = gtk_drop_down_new (NULL, NULL);
473
474 model = G_LIST_MODEL (ptr: pango_cairo_font_map_get_default ());
475 gtk_drop_down_set_model (self: GTK_DROP_DOWN (ptr: button), model);
476 gtk_drop_down_set_selected (self: GTK_DROP_DOWN (ptr: button), position: 0);
477
478 expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
479 n_params: 0, NULL,
480 callback_func: (GCallback)get_family_name,
481 NULL, NULL);
482 gtk_drop_down_set_expression (self: GTK_DROP_DOWN (ptr: button), expression);
483 gtk_expression_unref (self: expression);
484 gtk_box_append (GTK_BOX (box), child: button);
485
486 spin = gtk_spin_button_new_with_range (min: -1, max: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: model)), step: 1);
487 gtk_widget_set_halign (widget: spin, align: GTK_ALIGN_START);
488 g_object_bind_property (source: button, source_property: "selected", target: spin, target_property: "value", flags: G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
489 gtk_box_append (GTK_BOX (box), child: spin);
490
491 check = gtk_check_button_new_with_label (label: "Enable search");
492 g_object_bind_property (source: button, source_property: "enable-search", target: check, target_property: "active", flags: G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
493 gtk_box_append (GTK_BOX (box), child: check);
494
495 g_object_unref (object: model);
496
497 button = drop_down_new_from_strings (titles: times, NULL, NULL);
498 gtk_box_append (GTK_BOX (box), child: button);
499
500 button = drop_down_new_from_strings (titles: many_times, NULL, NULL);
501 gtk_box_append (GTK_BOX (box), child: button);
502
503 button = drop_down_new_from_strings (titles: many_times, NULL, NULL);
504 gtk_drop_down_set_enable_search (self: GTK_DROP_DOWN (ptr: button), TRUE);
505 expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
506 n_params: 0, NULL,
507 callback_func: (GCallback)get_title,
508 NULL, NULL);
509 gtk_drop_down_set_expression (self: GTK_DROP_DOWN (ptr: button), expression);
510 gtk_expression_unref (self: expression);
511 gtk_box_append (GTK_BOX (box), child: button);
512
513 button = drop_down_new_from_strings (titles: device_titles, icons: device_icons, descriptions: device_descriptions);
514 gtk_box_append (GTK_BOX (box), child: button);
515
516 button = gtk_drop_down_new (NULL, NULL);
517
518 store = g_list_store_new (GTK_TYPE_STRING_PAIR);
519 g_list_store_append (store, item: gtk_string_pair_new (id: "1", string: "One"));
520 g_list_store_append (store, item: gtk_string_pair_new (id: "2", string: "Two"));
521 g_list_store_append (store, item: gtk_string_pair_new (id: "2.5", string: "Two ½"));
522 g_list_store_append (store, item: gtk_string_pair_new (id: "3", string: "Three"));
523 gtk_drop_down_set_model (self: GTK_DROP_DOWN (ptr: button), model: G_LIST_MODEL (ptr: store));
524
525 factory = gtk_signal_list_item_factory_new ();
526 g_signal_connect (factory, "setup", G_CALLBACK (setup_list_item), NULL);
527 g_signal_connect (factory, "bind", G_CALLBACK (bind_list_item), NULL);
528 gtk_drop_down_set_factory (self: GTK_DROP_DOWN (ptr: button), factory);
529 g_object_unref (object: factory);
530
531 g_signal_connect (button, "notify::selected", G_CALLBACK (selected_changed), NULL);
532
533 gtk_box_append (GTK_BOX (box), child: button);
534
535 hbox = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
536 gtk_widget_add_css_class (widget: hbox, css_class: "linked");
537
538 entry = gtk_entry_new ();
539 button = gtk_drop_down_new (NULL, NULL);
540
541 gtk_drop_down_set_model (self: GTK_DROP_DOWN (ptr: button), model: G_LIST_MODEL (ptr: store));
542
543 factory = gtk_signal_list_item_factory_new ();
544 g_signal_connect (factory, "setup", G_CALLBACK (setup_no_item), NULL);
545 gtk_drop_down_set_factory (self: GTK_DROP_DOWN (ptr: button), factory);
546 g_object_unref (object: factory);
547
548 factory = gtk_signal_list_item_factory_new ();
549 g_signal_connect (factory, "setup", G_CALLBACK (setup_list_item), NULL);
550 g_signal_connect (factory, "bind", G_CALLBACK (bind_list_item), NULL);
551 gtk_drop_down_set_list_factory (self: GTK_DROP_DOWN (ptr: button), factory);
552 g_object_unref (object: factory);
553
554 g_signal_connect (button, "notify::selected", G_CALLBACK (selected_changed2), entry);
555
556 gtk_box_append (GTK_BOX (hbox), child: entry);
557 gtk_box_append (GTK_BOX (hbox), child: button);
558
559 gtk_box_append (GTK_BOX (box), child: hbox);
560
561 g_object_unref (object: store);
562
563 gtk_window_present (GTK_WINDOW (window));
564
565 while (!quit)
566 g_main_context_iteration (NULL, TRUE);
567
568 return 0;
569}
570

source code of gtk/tests/testdropdown.c