Contents of /trunk/src/list.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 322 - (show annotations)
Mon Dec 21 16:13:51 2009 UTC (14 years, 5 months ago) by harbaum
File MIME type: text/plain
File size: 16901 byte(s)
List handling improved
1 /*
2 * Copyright (C) 2008-2009 Till Harbaum <till@harbaum.org>.
3 *
4 * This file is part of OSM2Go.
5 *
6 * OSM2Go is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * OSM2Go is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with OSM2Go. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*
21 * list.c - generic implementation of a list style widget:
22 *
23 * +---------+-----------+
24 * | Key | Key |
25 * +---------+-----------+
26 * | Test1 Test2 ^|
27 * | Test3 Test4 #|
28 * | ||
29 * | v|
30 * +---------------------+
31 * ( Add )( Edit )(Remove)
32 */
33
34 #include "appdata.h"
35
36 #include <stdarg.h>
37
38 typedef struct {
39 GtkWidget *view;
40 GtkMenu *menu;
41
42 struct {
43 gboolean(*func)(GtkTreeSelection *, GtkTreeModel *,
44 GtkTreePath *, gboolean,
45 gpointer);
46 gpointer data;
47 } sel;
48
49 struct {
50 void(*func)(GtkTreeSelection *, gpointer);
51 gpointer data;
52 } change;
53
54 GtkWidget *table;
55
56 struct {
57 gpointer data;
58 GtkWidget *widget[6];
59 int flags;
60 } button;
61
62 } list_priv_t;
63
64 #ifdef FREMANTLE_USE_POPUP
65
66 static void cmenu_init(GtkWidget *list) {
67 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
68 g_assert(priv);
69
70 /* Create needed handles. */
71 priv->menu = GTK_MENU(gtk_menu_new());
72
73 gtk_widget_tap_and_hold_setup(priv->view, GTK_WIDGET(priv->menu), NULL, 0);
74 }
75
76 static GtkWidget *cmenu_append(GtkWidget *list, char *label,
77 GCallback cb, gpointer data) {
78 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
79 g_assert(priv);
80
81 GtkWidget *menu_item;
82
83 /* Setup the map context menu. */
84 gtk_menu_append(priv->menu, menu_item
85 = gtk_menu_item_new_with_label(label));
86
87 hildon_gtk_widget_set_theme_size(menu_item,
88 (HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH));
89
90 gtk_signal_connect(GTK_OBJECT(menu_item), "activate",
91 GTK_SIGNAL_FUNC(cb), data);
92
93 gtk_widget_show_all(GTK_WIDGET(priv->menu));
94
95 return menu_item;
96 }
97
98 #endif
99
100 GtkWidget *list_get_view(GtkWidget *list) {
101 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
102 g_assert(priv);
103 return priv->view;
104 }
105
106 /* a list supports up to three user defined buttons besides */
107 /* add, edit and remove */
108 void list_set_user_buttons(GtkWidget *list, ...) {
109 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
110 g_assert(priv);
111
112 va_list ap;
113
114 /* make space for user buttons */
115 if(!(priv->button.flags & LIST_BTN_WIDE))
116 gtk_table_resize(GTK_TABLE(priv->table), 2, 3);
117 else
118 gtk_table_resize(GTK_TABLE(priv->table), 1, 5);
119
120 va_start(ap, list);
121 list_button_t id = va_arg(ap, list_button_t);
122 while(id) {
123 char *label = va_arg(ap, char*);
124 GCallback cb = va_arg(ap, GCallback);
125
126 priv->button.widget[id] = button_new_with_label(label);
127 if(!(priv->button.flags & LIST_BTN_WIDE))
128 gtk_table_attach_defaults(GTK_TABLE(priv->table), priv->button.widget[id],
129 id-LIST_BUTTON_USER0, id-LIST_BUTTON_USER0+1, 1, 2);
130 else
131 gtk_table_attach_defaults(GTK_TABLE(priv->table), priv->button.widget[id],
132 3+id-LIST_BUTTON_USER0, 3+id-LIST_BUTTON_USER0+1, 0, 1);
133
134 gtk_signal_connect(GTK_OBJECT(priv->button.widget[id]), "clicked",
135 GTK_SIGNAL_FUNC(cb), priv->button.data);
136
137 id = va_arg(ap, list_button_t);
138 }
139
140 va_end(ap);
141 }
142
143 void list_set_columns(GtkWidget *list, ...) {
144 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
145 g_assert(priv);
146 va_list ap;
147
148 va_start(ap, list);
149 char *name = va_arg(ap, char*);
150 while(name) {
151 int hlkey = -1, key = va_arg(ap, int);
152 int flags = va_arg(ap, int);
153
154 if(flags & LIST_FLAG_CAN_HIGHLIGHT)
155 hlkey = va_arg(ap, int);
156
157 GtkTreeViewColumn *column = NULL;
158
159 if(flags & LIST_FLAG_TOGGLE) {
160 GCallback cb = va_arg(ap, GCallback);
161 gpointer data = va_arg(ap, gpointer);
162
163 GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new();
164 column = gtk_tree_view_column_new_with_attributes(
165 name, renderer, "active", key, NULL);
166 g_signal_connect(renderer, "toggled", cb, data);
167
168 } else if(flags & LIST_FLAG_STOCK_ICON) {
169 GtkCellRenderer *pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
170 column = gtk_tree_view_column_new_with_attributes(name,
171 pixbuf_renderer, "stock_id", key, NULL);
172 } else {
173 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
174
175 if(flags & LIST_FLAG_CAN_HIGHLIGHT)
176 g_object_set(renderer, "background", "red", NULL );
177
178 if(flags & LIST_FLAG_ELLIPSIZE)
179 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
180
181 column = gtk_tree_view_column_new_with_attributes(name, renderer,
182 "text", key,
183 (flags & LIST_FLAG_CAN_HIGHLIGHT)?"background-set":NULL, hlkey,
184 NULL);
185
186 gtk_tree_view_column_set_expand(column,
187 flags & (LIST_FLAG_EXPAND | LIST_FLAG_ELLIPSIZE));
188 }
189
190 gtk_tree_view_column_set_sort_column_id(column, key);
191 gtk_tree_view_insert_column(GTK_TREE_VIEW(priv->view), column, -1);
192
193 name = va_arg(ap, char*);
194 }
195
196 va_end(ap);
197 }
198
199 static GtkWidget *list_button_get(GtkWidget *list, list_button_t id) {
200 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
201 g_assert(priv);
202
203 return priv->button.widget[id];
204 }
205
206 void list_button_connect(GtkWidget *list, list_button_t id,
207 GCallback cb, gpointer data) {
208 GtkWidget *but = list_button_get(list, id);
209 gtk_signal_connect(GTK_OBJECT(but), "clicked", GTK_SIGNAL_FUNC(cb), data);
210 }
211
212 /* put a custom widget into one of the button slots */
213 void list_set_custom_user_button(GtkWidget *list, list_button_t id,
214 GtkWidget *widget) {
215 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
216 g_assert(priv);
217 g_assert((id >= 3) && (id < 6));
218
219 /* make space for user buttons */
220 gtk_table_resize(GTK_TABLE(priv->table), 2, 3);
221
222 if(!(priv->button.flags & LIST_BTN_WIDE))
223 gtk_table_attach_defaults(GTK_TABLE(priv->table), widget,
224 id-LIST_BUTTON_USER0, id-LIST_BUTTON_USER0+1, 1, 2);
225 else
226 gtk_table_attach_defaults(GTK_TABLE(priv->table), widget,
227 3+id-LIST_BUTTON_USER0, 3+id-LIST_BUTTON_USER0+1, 0, 1);
228
229 priv->button.widget[id] = widget;
230 }
231
232 GtkTreeSelection *list_get_selection(GtkWidget *list) {
233 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
234 g_assert(priv);
235
236 GtkTreeSelection *sel =
237 gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->view));
238
239 return sel;
240 }
241
242 /* returns true if something is selected. on in mode multiple returns */
243 /* true if exactly one item is selected */
244 gboolean list_get_selected(GtkWidget *list, GtkTreeModel **model,
245 GtkTreeIter *iter) {
246 gboolean retval = FALSE;
247 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
248 g_assert(priv);
249
250 #if 1
251 // this copes with multiple selections ...
252 GtkTreeSelection *sel =
253 gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->view));
254
255 GList *slist = gtk_tree_selection_get_selected_rows(sel, model);
256
257 if(g_list_length(slist) == 1)
258 retval = gtk_tree_model_get_iter(*model, iter, slist->data);
259
260 g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
261 g_list_free(slist);
262
263 return retval;
264 #else
265 // ... this doesn't
266 return gtk_tree_selection_get_selected(sel, model, iter);
267 #endif
268 }
269
270 void list_button_enable(GtkWidget *list, list_button_t id, gboolean enable) {
271 GtkWidget *but = list_button_get(list, id);
272 if(but) gtk_widget_set_sensitive(but, enable);
273 }
274
275 void list_set_store(GtkWidget *list, GtkListStore *store) {
276 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
277 g_assert(priv);
278
279 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->view), GTK_TREE_MODEL(store));
280 }
281
282 void list_set_selection_function(GtkWidget *list, GtkTreeSelectionFunc func,
283 gpointer data) {
284 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
285 g_assert(priv);
286
287 priv->sel.func = func;
288 priv->sel.data = data;
289 }
290
291 /* default selection function enables edit and remove if a row is being */
292 /* selected */
293 static gboolean
294 list_selection_function(GtkTreeSelection *selection, GtkTreeModel *model,
295 GtkTreePath *path, gboolean path_currently_selected,
296 gpointer list) {
297 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
298 g_assert(priv);
299
300 GtkTreeIter iter;
301
302 if(gtk_tree_model_get_iter(model, &iter, path)) {
303 g_assert(gtk_tree_path_get_depth(path) == 1);
304
305 /* this is now handled by the "changed" event handler */
306 // list_button_enable(GTK_WIDGET(list), LIST_BUTTON_REMOVE, TRUE);
307 // list_button_enable(GTK_WIDGET(list), LIST_BUTTON_EDIT, TRUE);
308 }
309
310 if(priv->sel.func)
311 return priv->sel.func(selection, model, path, path_currently_selected,
312 priv->sel.data);
313
314 return TRUE; /* allow selection state to change */
315 }
316
317 static void on_row_activated(GtkTreeView *treeview,
318 GtkTreePath *path,
319 GtkTreeViewColumn *col,
320 gpointer userdata) {
321 GtkTreeIter iter;
322 GtkTreeModel *model = gtk_tree_view_get_model(treeview);
323
324 printf("row activated\n");
325
326 if(gtk_tree_model_get_iter(model, &iter, path)) {
327 list_priv_t *priv = g_object_get_data(G_OBJECT(userdata), "priv");
328 g_assert(priv);
329
330 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(treeview));
331 g_assert(GTK_IS_DIALOG(toplevel));
332
333 /* emit a "response accept" signal so we might close the */
334 /* dialog */
335 gtk_dialog_response(GTK_DIALOG(toplevel), GTK_RESPONSE_ACCEPT);
336 }
337 }
338
339 void list_set_static_buttons(GtkWidget *list, int flags,
340 GCallback cb_new, GCallback cb_edit,
341 GCallback cb_remove, gpointer data) {
342 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
343 g_assert(priv);
344
345 priv->button.data = data;
346 priv->button.flags = flags;
347
348 /* add the three default buttons, but keep the disabled for now */
349 if(cb_new) {
350 priv->button.widget[0] =
351 button_new_with_label(_((flags&LIST_BTN_NEW)?"New":"Add"));
352 gtk_table_attach_defaults(GTK_TABLE(priv->table),
353 priv->button.widget[0], 0, 1, 0, 1);
354 gtk_signal_connect(GTK_OBJECT(priv->button.widget[0]), "clicked",
355 GTK_SIGNAL_FUNC(cb_new), data);
356 gtk_widget_set_sensitive(priv->button.widget[0], TRUE);
357 }
358
359 if(cb_edit) {
360 #ifdef FREMANTLE_USE_POPUP
361 priv->button.widget[1] = cmenu_append(list, _("Edit"),
362 GTK_SIGNAL_FUNC(cb_edit), data);
363 #else
364 priv->button.widget[1] = button_new_with_label(_("Edit"));
365 gtk_table_attach_defaults(GTK_TABLE(priv->table),
366 priv->button.widget[1], 1, 2, 0, 1);
367 gtk_signal_connect(GTK_OBJECT(priv->button.widget[1]), "clicked",
368 GTK_SIGNAL_FUNC(cb_edit), data);
369 #endif
370 gtk_widget_set_sensitive(priv->button.widget[1], FALSE);
371 }
372
373 if(cb_remove) {
374 #ifdef FREMANTLE_USE_POPUP
375 priv->button.widget[2] = cmenu_append(list, _("Remove"),
376 GTK_SIGNAL_FUNC(cb_remove), data);
377 #else
378 priv->button.widget[2] = button_new_with_label(_("Remove"));
379 gtk_table_attach_defaults(GTK_TABLE(priv->table),
380 priv->button.widget[2], 2, 3, 0, 1);
381 gtk_signal_connect(GTK_OBJECT(priv->button.widget[2]), "clicked",
382 GTK_SIGNAL_FUNC(cb_remove), data);
383 #endif
384 gtk_widget_set_sensitive(priv->button.widget[2], FALSE);
385 }
386 }
387
388 GtkTreeModel *list_get_model(GtkWidget *list) {
389 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
390 g_assert(priv);
391
392 return gtk_tree_view_get_model(GTK_TREE_VIEW(priv->view));
393 }
394
395 void list_pre_inplace_edit_tweak (GtkTreeModel *model) {
396 // Remove any current sort ordering, leaving items where they are.
397 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
398 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
399 GTK_SORT_ASCENDING);
400 }
401
402
403 /* Refocus a GtkTreeView an item specified by iter, unselecting the current
404 selection and optionally highlighting the new one. Typically called after
405 making an edit to an item with a covering sub-dialog. */
406
407 void list_focus_on(GtkWidget *list, GtkTreeIter *iter, gboolean highlight) {
408 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
409 g_assert(priv);
410 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->view));
411
412 // Handle de/reselection
413 GtkTreeSelection *sel =
414 gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->view));
415 gtk_tree_selection_unselect_all(sel);
416
417 // Scroll to it, since it might now be out of view.
418 GtkTreePath *path = gtk_tree_model_get_path(model, iter);
419 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(priv->view), path,
420 NULL, FALSE, 0, 0);
421 gtk_tree_path_free(path);
422
423 // reselect
424 if (highlight)
425 gtk_tree_selection_select_iter(sel, iter);
426 }
427
428 static gint on_list_destroy(GtkWidget *list, gpointer data) {
429 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
430 g_assert(priv);
431
432 g_free(priv);
433
434 return FALSE;
435 }
436
437 static void changed(GtkTreeSelection *treeselection, gpointer user_data) {
438 GtkWidget *list = (GtkWidget*)user_data;
439 list_priv_t *priv = g_object_get_data(G_OBJECT(list), "priv");
440
441 GtkTreeModel *model;
442 GtkTreeIter iter;
443 gboolean selected = list_get_selected(list, &model, &iter);
444
445 /* scroll to selected entry if exactly one is selected */
446 if(selected) {
447 /* check if the entry isn't already visible */
448 GtkTreePath *start = NULL, *end = NULL;
449 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
450
451 gtk_tree_view_get_visible_range(GTK_TREE_VIEW(priv->view), &start, &end);
452
453 /* check if path is before start of visible area or behin end of it */
454 if((start && (gtk_tree_path_compare(path, start)) < 0) ||
455 (end && (gtk_tree_path_compare(path, end) > 0)))
456 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(priv->view),
457 path, NULL, TRUE, 0.5, 0.5);
458
459 if(start) gtk_tree_path_free(start);
460 if(end) gtk_tree_path_free(end);
461 gtk_tree_path_free(path);
462 }
463
464 /* the change event handler is overridden */
465 if(priv->change.func) {
466 priv->change.func(treeselection, priv->change.data);
467 return;
468 }
469
470 list_button_enable(GTK_WIDGET(list), LIST_BUTTON_REMOVE, selected);
471 list_button_enable(GTK_WIDGET(list), LIST_BUTTON_EDIT, selected);
472 }
473
474 /* a generic list widget with "add", "edit" and "remove" buttons as used */
475 /* for all kinds of lists in osm2go */
476 #ifdef USE_HILDON
477 GtkWidget *list_new(gboolean show_headers)
478 #else
479 GtkWidget *list_new(void)
480 #endif
481 {
482 list_priv_t *priv = g_new0(list_priv_t, 1);
483
484 GtkWidget *vbox = gtk_vbox_new(FALSE,3);
485 g_object_set_data(G_OBJECT(vbox), "priv", priv);
486 g_signal_connect(G_OBJECT(vbox), "destroy",
487 G_CALLBACK(on_list_destroy), priv);
488
489 #ifndef FREMANTLE_PANNABLE_AREA
490 priv->view = gtk_tree_view_new();
491 #else
492 priv->view = hildon_gtk_tree_view_new(HILDON_UI_MODE_EDIT);
493 #endif
494
495 #ifdef USE_HILDON
496 if(show_headers) {
497 /* hildon hides these by default */
498 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(priv->view), TRUE);
499 }
500 #endif
501
502 GtkTreeSelection *sel =
503 gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->view));
504
505 gtk_tree_selection_set_select_function(sel,
506 list_selection_function, vbox, NULL);
507
508 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(changed), vbox);
509
510 #ifndef FREMANTLE_PANNABLE_AREA
511 /* put view into a scrolled window */
512 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
513 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
514 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
515 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window),
516 GTK_SHADOW_ETCHED_IN);
517 gtk_container_add(GTK_CONTAINER(scrolled_window), priv->view);
518 gtk_box_pack_start_defaults(GTK_BOX(vbox), scrolled_window);
519 #else
520 /* put view into a pannable area */
521 GtkWidget *pannable_area = hildon_pannable_area_new();
522 gtk_container_add(GTK_CONTAINER(pannable_area), priv->view);
523 gtk_box_pack_start_defaults(GTK_BOX(vbox), pannable_area);
524
525 #ifdef FREMANTLE_USE_POPUP
526 cmenu_init(vbox);
527 #endif
528 #endif
529
530 /* make list react on clicks (double clicks on pre-fremantle) */
531 g_signal_connect_after(GTK_OBJECT(priv->view), "row-activated",
532 (GCallback)on_row_activated, vbox);
533
534 /* add button box */
535 priv->table = gtk_table_new(1, 3, TRUE);
536
537 gtk_box_pack_start(GTK_BOX(vbox), priv->table, FALSE, FALSE, 0);
538
539 return vbox;
540 }
541
542 void list_override_changed_event(GtkWidget *list,
543 void(*handler)(GtkTreeSelection*,gpointer), gpointer data) {
544 list_priv_t *priv = g_new0(list_priv_t, 1);
545 g_assert(priv);
546
547 priv->change.func = handler;
548 priv->change.data = data;
549 }