1 | /* Tree View/Editable Cells |
2 | * |
3 | * This demo demonstrates the use of editable cells in a GtkTreeView. If |
4 | * you're new to the GtkTreeView widgets and associates, look into |
5 | * the GtkListStore example first. It also shows how to use the |
6 | * GtkCellRenderer::editing-started signal to do custom setup of the |
7 | * editable widget. |
8 | * |
9 | * The cell renderers used in this demo are GtkCellRendererText, |
10 | * GtkCellRendererCombo and GtkCellRendererProgress. |
11 | */ |
12 | |
13 | #include <gtk/gtk.h> |
14 | #include <string.h> |
15 | #include <stdlib.h> |
16 | |
17 | typedef struct |
18 | { |
19 | int number; |
20 | char *product; |
21 | int yummy; |
22 | } |
23 | Item; |
24 | |
25 | enum |
26 | { |
27 | COLUMN_ITEM_NUMBER, |
28 | COLUMN_ITEM_PRODUCT, |
29 | COLUMN_ITEM_YUMMY, |
30 | NUM_ITEM_COLUMNS |
31 | }; |
32 | |
33 | enum |
34 | { |
35 | COLUMN_NUMBER_TEXT, |
36 | NUM_NUMBER_COLUMNS |
37 | }; |
38 | |
39 | static GArray *articles = NULL; |
40 | |
41 | static void |
42 | add_items (void) |
43 | { |
44 | Item foo; |
45 | |
46 | g_return_if_fail (articles != NULL); |
47 | |
48 | foo.number = 3; |
49 | foo.product = g_strdup (str: "bottles of coke" ); |
50 | foo.yummy = 20; |
51 | g_array_append_vals (array: articles, data: &foo, len: 1); |
52 | |
53 | foo.number = 5; |
54 | foo.product = g_strdup (str: "packages of noodles" ); |
55 | foo.yummy = 50; |
56 | g_array_append_vals (array: articles, data: &foo, len: 1); |
57 | |
58 | foo.number = 2; |
59 | foo.product = g_strdup (str: "packages of chocolate chip cookies" ); |
60 | foo.yummy = 90; |
61 | g_array_append_vals (array: articles, data: &foo, len: 1); |
62 | |
63 | foo.number = 1; |
64 | foo.product = g_strdup (str: "can vanilla ice cream" ); |
65 | foo.yummy = 60; |
66 | g_array_append_vals (array: articles, data: &foo, len: 1); |
67 | |
68 | foo.number = 6; |
69 | foo.product = g_strdup (str: "eggs" ); |
70 | foo.yummy = 10; |
71 | g_array_append_vals (array: articles, data: &foo, len: 1); |
72 | } |
73 | |
74 | static GtkTreeModel * |
75 | create_items_model (void) |
76 | { |
77 | int i = 0; |
78 | GtkListStore *model; |
79 | GtkTreeIter iter; |
80 | |
81 | /* create array */ |
82 | articles = g_array_sized_new (FALSE, FALSE, element_size: sizeof (Item), reserved_size: 1); |
83 | |
84 | add_items (); |
85 | |
86 | /* create list store */ |
87 | model = gtk_list_store_new (n_columns: NUM_ITEM_COLUMNS, G_TYPE_INT, G_TYPE_STRING, |
88 | G_TYPE_INT, G_TYPE_BOOLEAN); |
89 | |
90 | /* add items */ |
91 | for (i = 0; i < articles->len; i++) |
92 | { |
93 | gtk_list_store_append (list_store: model, iter: &iter); |
94 | |
95 | gtk_list_store_set (list_store: model, iter: &iter, |
96 | COLUMN_ITEM_NUMBER, |
97 | g_array_index (articles, Item, i).number, |
98 | COLUMN_ITEM_PRODUCT, |
99 | g_array_index (articles, Item, i).product, |
100 | COLUMN_ITEM_YUMMY, |
101 | g_array_index (articles, Item, i).yummy, |
102 | -1); |
103 | } |
104 | |
105 | return GTK_TREE_MODEL (model); |
106 | } |
107 | |
108 | static GtkTreeModel * |
109 | create_numbers_model (void) |
110 | { |
111 | #define N_NUMBERS 10 |
112 | int i = 0; |
113 | GtkListStore *model; |
114 | GtkTreeIter iter; |
115 | |
116 | /* create list store */ |
117 | model = gtk_list_store_new (n_columns: NUM_NUMBER_COLUMNS, G_TYPE_STRING, G_TYPE_INT); |
118 | |
119 | /* add numbers */ |
120 | for (i = 0; i < N_NUMBERS; i++) |
121 | { |
122 | char str[2]; |
123 | |
124 | str[0] = '0' + i; |
125 | str[1] = '\0'; |
126 | |
127 | gtk_list_store_append (list_store: model, iter: &iter); |
128 | |
129 | gtk_list_store_set (list_store: model, iter: &iter, |
130 | COLUMN_NUMBER_TEXT, str, |
131 | -1); |
132 | } |
133 | |
134 | return GTK_TREE_MODEL (model); |
135 | |
136 | #undef N_NUMBERS |
137 | } |
138 | |
139 | static void |
140 | add_item (GtkWidget *button, gpointer data) |
141 | { |
142 | Item foo; |
143 | GtkTreeIter current, iter; |
144 | GtkTreePath *path; |
145 | GtkTreeModel *model; |
146 | GtkTreeViewColumn *column; |
147 | GtkTreeView *treeview = (GtkTreeView *)data; |
148 | |
149 | g_return_if_fail (articles != NULL); |
150 | |
151 | foo.number = 0; |
152 | foo.product = g_strdup (str: "Description here" ); |
153 | foo.yummy = 50; |
154 | g_array_append_vals (array: articles, data: &foo, len: 1); |
155 | |
156 | /* Insert a new row below the current one */ |
157 | gtk_tree_view_get_cursor (tree_view: treeview, path: &path, NULL); |
158 | model = gtk_tree_view_get_model (tree_view: treeview); |
159 | if (path) |
160 | { |
161 | gtk_tree_model_get_iter (tree_model: model, iter: ¤t, path); |
162 | gtk_tree_path_free (path); |
163 | gtk_list_store_insert_after (GTK_LIST_STORE (model), iter: &iter, sibling: ¤t); |
164 | } |
165 | else |
166 | { |
167 | gtk_list_store_insert (GTK_LIST_STORE (model), iter: &iter, position: -1); |
168 | } |
169 | |
170 | /* Set the data for the new row */ |
171 | gtk_list_store_set (GTK_LIST_STORE (model), iter: &iter, |
172 | COLUMN_ITEM_NUMBER, foo.number, |
173 | COLUMN_ITEM_PRODUCT, foo.product, |
174 | COLUMN_ITEM_YUMMY, foo.yummy, |
175 | -1); |
176 | |
177 | /* Move focus to the new row */ |
178 | path = gtk_tree_model_get_path (tree_model: model, iter: &iter); |
179 | column = gtk_tree_view_get_column (tree_view: treeview, n: 0); |
180 | gtk_tree_view_set_cursor (tree_view: treeview, path, focus_column: column, FALSE); |
181 | |
182 | gtk_tree_path_free (path); |
183 | } |
184 | |
185 | static void |
186 | remove_item (GtkWidget *widget, gpointer data) |
187 | { |
188 | GtkTreeIter iter; |
189 | GtkTreeView *treeview = (GtkTreeView *)data; |
190 | GtkTreeModel *model = gtk_tree_view_get_model (tree_view: treeview); |
191 | GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view: treeview); |
192 | |
193 | if (gtk_tree_selection_get_selected (selection, NULL, iter: &iter)) |
194 | { |
195 | int i; |
196 | GtkTreePath *path; |
197 | |
198 | path = gtk_tree_model_get_path (tree_model: model, iter: &iter); |
199 | i = gtk_tree_path_get_indices (path)[0]; |
200 | gtk_list_store_remove (GTK_LIST_STORE (model), iter: &iter); |
201 | |
202 | g_array_remove_index (array: articles, index_: i); |
203 | |
204 | gtk_tree_path_free (path); |
205 | } |
206 | } |
207 | |
208 | static gboolean |
209 | separator_row (GtkTreeModel *model, |
210 | GtkTreeIter *iter, |
211 | gpointer data) |
212 | { |
213 | GtkTreePath *path; |
214 | int idx; |
215 | |
216 | path = gtk_tree_model_get_path (tree_model: model, iter); |
217 | idx = gtk_tree_path_get_indices (path)[0]; |
218 | |
219 | gtk_tree_path_free (path); |
220 | |
221 | return idx == 5; |
222 | } |
223 | |
224 | static void |
225 | editing_started (GtkCellRenderer *cell, |
226 | GtkCellEditable *editable, |
227 | const char *path, |
228 | gpointer data) |
229 | { |
230 | gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (editable), |
231 | func: separator_row, NULL, NULL); |
232 | } |
233 | |
234 | static void |
235 | cell_edited (GtkCellRendererText *cell, |
236 | const char *path_string, |
237 | const char *new_text, |
238 | gpointer data) |
239 | { |
240 | GtkTreeModel *model = (GtkTreeModel *)data; |
241 | GtkTreePath *path = gtk_tree_path_new_from_string (path: path_string); |
242 | GtkTreeIter iter; |
243 | |
244 | int column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell), "column" )); |
245 | |
246 | gtk_tree_model_get_iter (tree_model: model, iter: &iter, path); |
247 | |
248 | switch (column) |
249 | { |
250 | case COLUMN_ITEM_NUMBER: |
251 | { |
252 | int i; |
253 | |
254 | i = gtk_tree_path_get_indices (path)[0]; |
255 | g_array_index (articles, Item, i).number = atoi (nptr: new_text); |
256 | |
257 | gtk_list_store_set (GTK_LIST_STORE (model), iter: &iter, column, |
258 | g_array_index (articles, Item, i).number, -1); |
259 | } |
260 | break; |
261 | |
262 | case COLUMN_ITEM_PRODUCT: |
263 | { |
264 | int i; |
265 | char *old_text; |
266 | |
267 | gtk_tree_model_get (tree_model: model, iter: &iter, column, &old_text, -1); |
268 | g_free (mem: old_text); |
269 | |
270 | i = gtk_tree_path_get_indices (path)[0]; |
271 | g_free (g_array_index (articles, Item, i).product); |
272 | g_array_index (articles, Item, i).product = g_strdup (str: new_text); |
273 | |
274 | gtk_list_store_set (GTK_LIST_STORE (model), iter: &iter, column, |
275 | g_array_index (articles, Item, i).product, -1); |
276 | } |
277 | break; |
278 | |
279 | default: |
280 | g_assert_not_reached (); |
281 | } |
282 | |
283 | gtk_tree_path_free (path); |
284 | } |
285 | |
286 | static void |
287 | add_columns (GtkTreeView *treeview, |
288 | GtkTreeModel *items_model, |
289 | GtkTreeModel *numbers_model) |
290 | { |
291 | GtkCellRenderer *renderer; |
292 | |
293 | /* number column */ |
294 | renderer = gtk_cell_renderer_combo_new (); |
295 | g_object_set (object: renderer, |
296 | first_property_name: "model" , numbers_model, |
297 | "text-column" , COLUMN_NUMBER_TEXT, |
298 | "has-entry" , FALSE, |
299 | "editable" , TRUE, |
300 | NULL); |
301 | g_signal_connect (renderer, "edited" , |
302 | G_CALLBACK (cell_edited), items_model); |
303 | g_signal_connect (renderer, "editing-started" , |
304 | G_CALLBACK (editing_started), NULL); |
305 | g_object_set_data (G_OBJECT (renderer), key: "column" , GINT_TO_POINTER (COLUMN_ITEM_NUMBER)); |
306 | |
307 | gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), |
308 | position: -1, title: "Number" , cell: renderer, |
309 | "text" , COLUMN_ITEM_NUMBER, |
310 | NULL); |
311 | |
312 | /* product column */ |
313 | renderer = gtk_cell_renderer_text_new (); |
314 | g_object_set (object: renderer, |
315 | first_property_name: "editable" , TRUE, |
316 | NULL); |
317 | g_signal_connect (renderer, "edited" , |
318 | G_CALLBACK (cell_edited), items_model); |
319 | g_object_set_data (G_OBJECT (renderer), key: "column" , GINT_TO_POINTER (COLUMN_ITEM_PRODUCT)); |
320 | |
321 | gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), |
322 | position: -1, title: "Product" , cell: renderer, |
323 | "text" , COLUMN_ITEM_PRODUCT, |
324 | NULL); |
325 | |
326 | /* yummy column */ |
327 | renderer = gtk_cell_renderer_progress_new (); |
328 | g_object_set_data (G_OBJECT (renderer), key: "column" , GINT_TO_POINTER (COLUMN_ITEM_YUMMY)); |
329 | |
330 | gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), |
331 | position: -1, title: "Yummy" , cell: renderer, |
332 | "value" , COLUMN_ITEM_YUMMY, |
333 | NULL); |
334 | } |
335 | |
336 | GtkWidget * |
337 | do_editable_cells (GtkWidget *do_widget) |
338 | { |
339 | static GtkWidget *window = NULL; |
340 | |
341 | if (!window) |
342 | { |
343 | GtkWidget *vbox; |
344 | GtkWidget *hbox; |
345 | GtkWidget *sw; |
346 | GtkWidget *treeview; |
347 | GtkWidget *button; |
348 | GtkTreeModel *items_model; |
349 | GtkTreeModel *numbers_model; |
350 | |
351 | window = gtk_window_new (); |
352 | gtk_window_set_display (GTK_WINDOW (window), |
353 | display: gtk_widget_get_display (widget: do_widget)); |
354 | gtk_window_set_title (GTK_WINDOW (window), title: "Editable Cells" ); |
355 | g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window); |
356 | |
357 | vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 5); |
358 | gtk_widget_set_margin_start (widget: vbox, margin: 5); |
359 | gtk_widget_set_margin_end (widget: vbox, margin: 5); |
360 | gtk_widget_set_margin_top (widget: vbox, margin: 5); |
361 | gtk_widget_set_margin_bottom (widget: vbox, margin: 5); |
362 | gtk_window_set_child (GTK_WINDOW (window), child: vbox); |
363 | |
364 | gtk_box_append (GTK_BOX (vbox), |
365 | child: gtk_label_new (str: "Shopping list (you can edit the cells!)" )); |
366 | |
367 | sw = gtk_scrolled_window_new (); |
368 | gtk_scrolled_window_set_has_frame (GTK_SCROLLED_WINDOW (sw), TRUE); |
369 | gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), |
370 | hscrollbar_policy: GTK_POLICY_AUTOMATIC, |
371 | vscrollbar_policy: GTK_POLICY_AUTOMATIC); |
372 | gtk_box_append (GTK_BOX (vbox), child: sw); |
373 | |
374 | /* create models */ |
375 | items_model = create_items_model (); |
376 | numbers_model = create_numbers_model (); |
377 | |
378 | /* create tree view */ |
379 | treeview = gtk_tree_view_new_with_model (model: items_model); |
380 | gtk_widget_set_vexpand (widget: treeview, TRUE); |
381 | gtk_tree_selection_set_mode (selection: gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)), |
382 | type: GTK_SELECTION_SINGLE); |
383 | |
384 | add_columns (GTK_TREE_VIEW (treeview), items_model, numbers_model); |
385 | |
386 | g_object_unref (object: numbers_model); |
387 | g_object_unref (object: items_model); |
388 | |
389 | gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: treeview); |
390 | |
391 | /* some buttons */ |
392 | hbox = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 4); |
393 | gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); |
394 | gtk_box_append (GTK_BOX (vbox), child: hbox); |
395 | |
396 | button = gtk_button_new_with_label (label: "Add item" ); |
397 | g_signal_connect (button, "clicked" , |
398 | G_CALLBACK (add_item), treeview); |
399 | gtk_box_append (GTK_BOX (hbox), child: button); |
400 | |
401 | button = gtk_button_new_with_label (label: "Remove item" ); |
402 | g_signal_connect (button, "clicked" , |
403 | G_CALLBACK (remove_item), treeview); |
404 | gtk_box_append (GTK_BOX (hbox), child: button); |
405 | |
406 | gtk_window_set_default_size (GTK_WINDOW (window), width: 320, height: 200); |
407 | } |
408 | |
409 | if (!gtk_widget_get_visible (widget: window)) |
410 | gtk_widget_show (widget: window); |
411 | else |
412 | gtk_window_destroy (GTK_WINDOW (window)); |
413 | |
414 | return window; |
415 | } |
416 | |