Contents of /trunk/src/list.c

Parent Directory Parent Directory | Revision Log Revision Log


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