1 | /* Combo Boxes |
2 | * #Keywords: GtkCellRenderer |
3 | * |
4 | * The GtkComboBox widget allows to select one option out of a list. |
5 | * The GtkComboBoxEntry additionally allows the user to enter a value |
6 | * that is not in the list of options. |
7 | * |
8 | * How the options are displayed is controlled by cell renderers. |
9 | */ |
10 | |
11 | #include <glib/gi18n.h> |
12 | #include <gtk/gtk.h> |
13 | |
14 | enum |
15 | { |
16 | ICON_NAME_COL, |
17 | TEXT_COL |
18 | }; |
19 | |
20 | static GtkTreeModel * |
21 | create_icon_store (void) |
22 | { |
23 | const char *icon_names[6] = { |
24 | "dialog-warning" , |
25 | "process-stop" , |
26 | "document-new" , |
27 | "edit-clear" , |
28 | NULL, |
29 | "document-open" |
30 | }; |
31 | const char *labels[6] = { |
32 | N_("Warning" ), |
33 | N_("Stop" ), |
34 | N_("New" ), |
35 | N_("Clear" ), |
36 | NULL, |
37 | N_("Open" ) |
38 | }; |
39 | |
40 | GtkTreeIter iter; |
41 | GtkListStore *store; |
42 | int i; |
43 | |
44 | store = gtk_list_store_new (n_columns: 2, G_TYPE_STRING, G_TYPE_STRING); |
45 | |
46 | for (i = 0; i < G_N_ELEMENTS (icon_names); i++) |
47 | { |
48 | if (icon_names[i]) |
49 | { |
50 | gtk_list_store_append (list_store: store, iter: &iter); |
51 | gtk_list_store_set (list_store: store, iter: &iter, |
52 | ICON_NAME_COL, icon_names[i], |
53 | TEXT_COL, _(labels[i]), |
54 | -1); |
55 | } |
56 | else |
57 | { |
58 | gtk_list_store_append (list_store: store, iter: &iter); |
59 | gtk_list_store_set (list_store: store, iter: &iter, |
60 | ICON_NAME_COL, NULL, |
61 | TEXT_COL, "separator" , |
62 | -1); |
63 | } |
64 | } |
65 | |
66 | return GTK_TREE_MODEL (store); |
67 | } |
68 | |
69 | /* A GtkCellLayoutDataFunc that demonstrates how one can control |
70 | * sensitivity of rows. This particular function does nothing |
71 | * useful and just makes the second row insensitive. |
72 | */ |
73 | static void |
74 | set_sensitive (GtkCellLayout *cell_layout, |
75 | GtkCellRenderer *cell, |
76 | GtkTreeModel *tree_model, |
77 | GtkTreeIter *iter, |
78 | gpointer data) |
79 | { |
80 | GtkTreePath *path; |
81 | int *indices; |
82 | gboolean sensitive; |
83 | |
84 | path = gtk_tree_model_get_path (tree_model, iter); |
85 | indices = gtk_tree_path_get_indices (path); |
86 | sensitive = indices[0] != 1; |
87 | gtk_tree_path_free (path); |
88 | |
89 | g_object_set (object: cell, first_property_name: "sensitive" , sensitive, NULL); |
90 | } |
91 | |
92 | /* A GtkTreeViewRowSeparatorFunc that demonstrates how rows can be |
93 | * rendered as separators. This particular function does nothing |
94 | * useful and just turns the fourth row into a separator. |
95 | */ |
96 | static gboolean |
97 | is_separator (GtkTreeModel *model, |
98 | GtkTreeIter *iter, |
99 | gpointer data) |
100 | { |
101 | GtkTreePath *path; |
102 | gboolean result; |
103 | |
104 | path = gtk_tree_model_get_path (tree_model: model, iter); |
105 | result = gtk_tree_path_get_indices (path)[0] == 4; |
106 | gtk_tree_path_free (path); |
107 | |
108 | return result; |
109 | } |
110 | |
111 | static GtkTreeModel * |
112 | create_capital_store (void) |
113 | { |
114 | struct { |
115 | const char *group; |
116 | const char *capital; |
117 | } capitals[] = { |
118 | { "A - B" , NULL }, |
119 | { NULL, "Albany" }, |
120 | { NULL, "Annapolis" }, |
121 | { NULL, "Atlanta" }, |
122 | { NULL, "Augusta" }, |
123 | { NULL, "Austin" }, |
124 | { NULL, "Baton Rouge" }, |
125 | { NULL, "Bismarck" }, |
126 | { NULL, "Boise" }, |
127 | { NULL, "Boston" }, |
128 | { "C - D" , NULL }, |
129 | { NULL, "Carson City" }, |
130 | { NULL, "Charleston" }, |
131 | { NULL, "Cheyenne" }, |
132 | { NULL, "Columbia" }, |
133 | { NULL, "Columbus" }, |
134 | { NULL, "Concord" }, |
135 | { NULL, "Denver" }, |
136 | { NULL, "Des Moines" }, |
137 | { NULL, "Dover" }, |
138 | { "E - J" , NULL }, |
139 | { NULL, "Frankfort" }, |
140 | { NULL, "Harrisburg" }, |
141 | { NULL, "Hartford" }, |
142 | { NULL, "Helena" }, |
143 | { NULL, "Honolulu" }, |
144 | { NULL, "Indianapolis" }, |
145 | { NULL, "Jackson" }, |
146 | { NULL, "Jefferson City" }, |
147 | { NULL, "Juneau" }, |
148 | { "K - O" , NULL }, |
149 | { NULL, "Lansing" }, |
150 | { NULL, "Lincoln" }, |
151 | { NULL, "Little Rock" }, |
152 | { NULL, "Madison" }, |
153 | { NULL, "Montgomery" }, |
154 | { NULL, "Montpelier" }, |
155 | { NULL, "Nashville" }, |
156 | { NULL, "Oklahoma City" }, |
157 | { NULL, "Olympia" }, |
158 | { "P - S" , NULL }, |
159 | { NULL, "Phoenix" }, |
160 | { NULL, "Pierre" }, |
161 | { NULL, "Providence" }, |
162 | { NULL, "Raleigh" }, |
163 | { NULL, "Richmond" }, |
164 | { NULL, "Sacramento" }, |
165 | { NULL, "Salem" }, |
166 | { NULL, "Salt Lake City" }, |
167 | { NULL, "Santa Fe" }, |
168 | { NULL, "Springfield" }, |
169 | { NULL, "St. Paul" }, |
170 | { "T - Z" , NULL }, |
171 | { NULL, "Tallahassee" }, |
172 | { NULL, "Topeka" }, |
173 | { NULL, "Trenton" }, |
174 | { NULL, NULL } |
175 | }; |
176 | |
177 | GtkTreeIter iter, iter2; |
178 | GtkTreeStore *store; |
179 | int i; |
180 | |
181 | store = gtk_tree_store_new (n_columns: 1, G_TYPE_STRING); |
182 | |
183 | for (i = 0; capitals[i].group || capitals[i].capital; i++) |
184 | { |
185 | if (capitals[i].group) |
186 | { |
187 | gtk_tree_store_append (tree_store: store, iter: &iter, NULL); |
188 | gtk_tree_store_set (tree_store: store, iter: &iter, 0, capitals[i].group, -1); |
189 | } |
190 | else if (capitals[i].capital) |
191 | { |
192 | gtk_tree_store_append (tree_store: store, iter: &iter2, parent: &iter); |
193 | gtk_tree_store_set (tree_store: store, iter: &iter2, 0, capitals[i].capital, -1); |
194 | } |
195 | } |
196 | |
197 | return GTK_TREE_MODEL (store); |
198 | } |
199 | |
200 | static void |
201 | is_capital_sensitive (GtkCellLayout *cell_layout, |
202 | GtkCellRenderer *cell, |
203 | GtkTreeModel *tree_model, |
204 | GtkTreeIter *iter, |
205 | gpointer data) |
206 | { |
207 | gboolean sensitive; |
208 | |
209 | sensitive = !gtk_tree_model_iter_has_child (tree_model, iter); |
210 | |
211 | g_object_set (object: cell, first_property_name: "sensitive" , sensitive, NULL); |
212 | } |
213 | |
214 | static void |
215 | fill_combo_entry (GtkWidget *combo) |
216 | { |
217 | gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), text: "One" ); |
218 | gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), text: "Two" ); |
219 | gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), text: "2\302\275" ); |
220 | gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), text: "Three" ); |
221 | } |
222 | |
223 | |
224 | /* A simple validating entry */ |
225 | |
226 | #define TYPE_MASK_ENTRY (mask_entry_get_type ()) |
227 | #define MASK_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_MASK_ENTRY, MaskEntry)) |
228 | #define MASK_ENTRY_CLASS(vtable) (G_TYPE_CHECK_CLASS_CAST ((vtable), TYPE_MASK_ENTRY, MaskEntryClass)) |
229 | #define IS_MASK_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_MASK_ENTRY)) |
230 | #define IS_MASK_ENTRY_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE ((vtable), TYPE_MASK_ENTRY)) |
231 | #define MASK_ENTRY_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((inst), TYPE_MASK_ENTRY, MaskEntryClass)) |
232 | |
233 | |
234 | typedef struct _MaskEntry MaskEntry; |
235 | struct _MaskEntry |
236 | { |
237 | GtkEntry entry; |
238 | const char *mask; |
239 | }; |
240 | |
241 | typedef struct _MaskEntryClass MaskEntryClass; |
242 | struct _MaskEntryClass |
243 | { |
244 | GtkEntryClass parent_class; |
245 | }; |
246 | |
247 | |
248 | static void mask_entry_editable_init (GtkEditableInterface *iface); |
249 | |
250 | static GType mask_entry_get_type (void); |
251 | G_DEFINE_TYPE_WITH_CODE (MaskEntry, mask_entry, GTK_TYPE_ENTRY, |
252 | G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, |
253 | mask_entry_editable_init)); |
254 | |
255 | |
256 | static void |
257 | mask_entry_set_background (MaskEntry *entry) |
258 | { |
259 | if (entry->mask) |
260 | { |
261 | if (!g_regex_match_simple (pattern: entry->mask, string: gtk_editable_get_text (GTK_EDITABLE (entry)), compile_options: 0, match_options: 0)) |
262 | { |
263 | PangoAttrList *attrs; |
264 | |
265 | attrs = pango_attr_list_new (); |
266 | pango_attr_list_insert (list: attrs, attr: pango_attr_foreground_new (red: 65535, green: 32767, blue: 32767)); |
267 | gtk_entry_set_attributes (GTK_ENTRY (entry), attrs); |
268 | pango_attr_list_unref (list: attrs); |
269 | return; |
270 | } |
271 | } |
272 | |
273 | gtk_entry_set_attributes (GTK_ENTRY (entry), NULL); |
274 | } |
275 | |
276 | |
277 | static void |
278 | mask_entry_changed (GtkEditable *editable) |
279 | { |
280 | mask_entry_set_background (MASK_ENTRY (editable)); |
281 | } |
282 | |
283 | |
284 | static void |
285 | mask_entry_init (MaskEntry *entry) |
286 | { |
287 | entry->mask = NULL; |
288 | } |
289 | |
290 | |
291 | static void |
292 | mask_entry_class_init (MaskEntryClass *klass) |
293 | { } |
294 | |
295 | |
296 | static void |
297 | mask_entry_editable_init (GtkEditableInterface *iface) |
298 | { |
299 | iface->changed = mask_entry_changed; |
300 | } |
301 | |
302 | |
303 | GtkWidget * |
304 | do_combobox (GtkWidget *do_widget) |
305 | { |
306 | static GtkWidget *window = NULL; |
307 | GtkWidget *vbox, *frame, *box, *combo, *entry; |
308 | GtkTreeModel *model; |
309 | GtkCellRenderer *renderer; |
310 | GtkTreePath *path; |
311 | GtkTreeIter iter; |
312 | |
313 | if (!window) |
314 | { |
315 | window = gtk_window_new (); |
316 | gtk_window_set_display (GTK_WINDOW (window), |
317 | display: gtk_widget_get_display (widget: do_widget)); |
318 | gtk_window_set_title (GTK_WINDOW (window), title: "Combo Boxes" ); |
319 | g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window); |
320 | |
321 | vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 2); |
322 | gtk_widget_set_margin_start (widget: vbox, margin: 10); |
323 | gtk_widget_set_margin_end (widget: vbox, margin: 10); |
324 | gtk_widget_set_margin_top (widget: vbox, margin: 10); |
325 | gtk_widget_set_margin_bottom (widget: vbox, margin: 10); |
326 | gtk_window_set_child (GTK_WINDOW (window), child: vbox); |
327 | |
328 | /* A combobox demonstrating cell renderers, separators and |
329 | * insensitive rows |
330 | */ |
331 | frame = gtk_frame_new (label: "Items with icons" ); |
332 | gtk_box_append (GTK_BOX (vbox), child: frame); |
333 | |
334 | box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
335 | gtk_widget_set_margin_start (widget: box, margin: 5); |
336 | gtk_widget_set_margin_end (widget: box, margin: 5); |
337 | gtk_widget_set_margin_top (widget: box, margin: 5); |
338 | gtk_widget_set_margin_bottom (widget: box, margin: 5); |
339 | gtk_frame_set_child (GTK_FRAME (frame), child: box); |
340 | |
341 | model = create_icon_store (); |
342 | combo = gtk_combo_box_new_with_model (model); |
343 | g_object_unref (object: model); |
344 | gtk_box_append (GTK_BOX (box), child: combo); |
345 | |
346 | renderer = gtk_cell_renderer_pixbuf_new (); |
347 | gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell: renderer, FALSE); |
348 | gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell: renderer, |
349 | "icon-name" , ICON_NAME_COL, |
350 | NULL); |
351 | |
352 | gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo), |
353 | cell: renderer, |
354 | func: set_sensitive, |
355 | NULL, NULL); |
356 | |
357 | renderer = gtk_cell_renderer_text_new (); |
358 | gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell: renderer, TRUE); |
359 | gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell: renderer, |
360 | "text" , TEXT_COL, |
361 | NULL); |
362 | |
363 | gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo), |
364 | cell: renderer, |
365 | func: set_sensitive, |
366 | NULL, NULL); |
367 | |
368 | gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo), |
369 | func: is_separator, NULL, NULL); |
370 | |
371 | gtk_combo_box_set_active (GTK_COMBO_BOX (combo), index_: 0); |
372 | |
373 | /* A combobox demonstrating trees. |
374 | */ |
375 | frame = gtk_frame_new (label: "Where are we ?" ); |
376 | gtk_box_append (GTK_BOX (vbox), child: frame); |
377 | |
378 | box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
379 | gtk_widget_set_margin_start (widget: box, margin: 5); |
380 | gtk_widget_set_margin_end (widget: box, margin: 5); |
381 | gtk_widget_set_margin_top (widget: box, margin: 5); |
382 | gtk_widget_set_margin_bottom (widget: box, margin: 5); |
383 | gtk_frame_set_child (GTK_FRAME (frame), child: box); |
384 | |
385 | model = create_capital_store (); |
386 | combo = gtk_combo_box_new_with_model (model); |
387 | g_object_unref (object: model); |
388 | gtk_box_append (GTK_BOX (box), child: combo); |
389 | |
390 | renderer = gtk_cell_renderer_text_new (); |
391 | gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell: renderer, TRUE); |
392 | gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell: renderer, |
393 | "text" , 0, |
394 | NULL); |
395 | gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo), |
396 | cell: renderer, |
397 | func: is_capital_sensitive, |
398 | NULL, NULL); |
399 | |
400 | path = gtk_tree_path_new_from_indices (first_index: 0, 8, -1); |
401 | gtk_tree_model_get_iter (tree_model: model, iter: &iter, path); |
402 | gtk_tree_path_free (path); |
403 | gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), iter: &iter); |
404 | |
405 | /* A GtkComboBoxEntry with validation */ |
406 | frame = gtk_frame_new (label: "Editable" ); |
407 | gtk_box_append (GTK_BOX (vbox), child: frame); |
408 | |
409 | box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
410 | gtk_widget_set_margin_start (widget: box, margin: 5); |
411 | gtk_widget_set_margin_end (widget: box, margin: 5); |
412 | gtk_widget_set_margin_top (widget: box, margin: 5); |
413 | gtk_widget_set_margin_bottom (widget: box, margin: 5); |
414 | gtk_frame_set_child (GTK_FRAME (frame), child: box); |
415 | |
416 | combo = gtk_combo_box_text_new_with_entry (); |
417 | fill_combo_entry (combo); |
418 | gtk_box_append (GTK_BOX (box), child: combo); |
419 | |
420 | entry = g_object_new (TYPE_MASK_ENTRY, NULL); |
421 | MASK_ENTRY (entry)->mask = "^([0-9]*|One|Two|2\302\275|Three)$" ; |
422 | |
423 | gtk_combo_box_set_child (GTK_COMBO_BOX (combo), child: entry); |
424 | |
425 | /* A combobox with string IDs */ |
426 | frame = gtk_frame_new (label: "String IDs" ); |
427 | gtk_box_append (GTK_BOX (vbox), child: frame); |
428 | |
429 | box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
430 | gtk_widget_set_margin_start (widget: box, margin: 5); |
431 | gtk_widget_set_margin_end (widget: box, margin: 5); |
432 | gtk_widget_set_margin_top (widget: box, margin: 5); |
433 | gtk_widget_set_margin_bottom (widget: box, margin: 5); |
434 | gtk_frame_set_child (GTK_FRAME (frame), child: box); |
435 | |
436 | combo = gtk_combo_box_text_new (); |
437 | gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), id: "never" , text: "Not visible" ); |
438 | gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), id: "when-active" , text: "Visible when active" ); |
439 | gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), id: "always" , text: "Always visible" ); |
440 | gtk_box_append (GTK_BOX (box), child: combo); |
441 | |
442 | entry = gtk_entry_new (); |
443 | g_object_bind_property (source: combo, source_property: "active-id" , |
444 | target: entry, target_property: "text" , |
445 | flags: G_BINDING_BIDIRECTIONAL); |
446 | gtk_box_append (GTK_BOX (box), child: entry); |
447 | } |
448 | |
449 | if (!gtk_widget_get_visible (widget: window)) |
450 | gtk_widget_show (widget: window); |
451 | else |
452 | gtk_window_destroy (GTK_WINDOW (window)); |
453 | |
454 | return window; |
455 | } |
456 | |