Contents of /trunk/src/relation_edit.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: 29748 byte(s)
List handling improved
1 /*
2 * Copyright (C) 2008 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 #include "appdata.h"
21
22 /* --------------- relation dialog for an item (node or way) ----------- */
23
24 typedef struct {
25 object_t *item;
26 appdata_t *appdata;
27 GtkWidget *dialog, *view;
28 GtkListStore *store;
29 } relitem_context_t;
30
31 enum {
32 RELITEM_COL_TYPE = 0,
33 RELITEM_COL_ROLE,
34 RELITEM_COL_NAME,
35 RELITEM_COL_DATA,
36 RELITEM_NUM_COLS
37 };
38
39 typedef struct role_chain_s {
40 char *role;
41 struct role_chain_s *next;
42 } role_chain_t;
43
44 static gboolean relation_add_item(GtkWidget *parent,
45 relation_t *relation, object_t *object) {
46 role_chain_t *chain = NULL, **chainP = &chain;
47
48 printf("add object of type %d to relation #" ITEM_ID_FORMAT "\n",
49 object->type, OSM_ID(relation));
50
51 /* ask the user for the role of the new object in this relation */
52
53 /* collect roles first */
54 member_t *member = relation->member;
55 while(member) {
56 if(member->role) {
57 /* check if this role has already been saved */
58 gboolean already_stored = FALSE;
59 role_chain_t *crole = chain;
60 while(crole) {
61 if(strcasecmp(crole->role, member->role) == 0) already_stored = TRUE;
62 crole = crole->next;
63 }
64
65 /* not stored yet: attach it */
66 if(!already_stored) {
67 *chainP = g_new0(role_chain_t, 1);
68 (*chainP)->role = g_strdup(member->role);
69 chainP = &(*chainP)->next;
70 }
71 }
72 member = member->next;
73 }
74
75 /* ------------------ role dialog ---------------- */
76 GtkWidget *dialog =
77 misc_dialog_new(MISC_DIALOG_NOSIZE,_("Select role"),
78 GTK_WINDOW(parent),
79 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
80 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
81 NULL);
82
83 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
84
85 char *type = osm_tag_get_by_key(OSM_TAG(relation), "type");
86
87 char *info_str = NULL;
88 if(type) info_str = g_strdup_printf(_("In relation of type: %s"), type);
89 else info_str = g_strdup_printf(_("In relation #" ITEM_ID_FORMAT),
90 OSM_ID(relation));
91 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox),
92 gtk_label_new(info_str));
93 g_free(info_str);
94
95 char *name = osm_tag_get_by_key(OSM_TAG(relation), "name");
96 if(name)
97 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox),
98 gtk_label_new(name));
99
100 GtkWidget *hbox = gtk_hbox_new(FALSE, 8);
101
102 #ifdef FREMANTLE
103 if(!chain)
104 #endif
105 gtk_box_pack_start_defaults(GTK_BOX(hbox), gtk_label_new(_("Role:")));
106
107 GtkWidget *entry = NULL;
108 if(chain) {
109 entry = combo_box_entry_new(_("Role"));
110
111 /* fill combo box with presets */
112 while(chain) {
113 role_chain_t *next = chain->next;
114 combo_box_append_text(entry, chain->role);
115 g_free(chain);
116 chain = next;
117 }
118 } else
119 entry = entry_new();
120
121 gtk_box_pack_start_defaults(GTK_BOX(hbox), entry);
122 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox);
123
124 gtk_widget_show_all(dialog);
125 if(GTK_RESPONSE_ACCEPT != gtk_dialog_run(GTK_DIALOG(dialog))) {
126 printf("user clicked cancel\n");
127 gtk_widget_destroy(dialog);
128 return FALSE;
129 }
130
131 printf("user clicked ok\n");
132
133 /* get role from dialog */
134 const char *ptr = NULL;
135
136 if(GTK_WIDGET_TYPE(entry) == combo_box_entry_type())
137 ptr = combo_box_get_active_text(entry);
138 else
139 ptr = gtk_entry_get_text(GTK_ENTRY(entry));
140
141 char *role = NULL;
142 if(ptr && strlen(ptr)) role = g_strdup(ptr);
143
144 gtk_widget_destroy(dialog);
145
146 /* search end of member chain */
147 member_t **memberP = &relation->member;
148 while(*memberP) memberP = &(*memberP)->next;
149
150 g_assert((object->type == NODE)||(object->type == WAY)||
151 (object->type == RELATION));
152
153 /* create new member */
154 *memberP = g_new0(member_t, 1);
155 (*memberP)->object = *object;
156 (*memberP)->role = role;
157
158 OSM_FLAGS(relation) |= OSM_FLAG_DIRTY;
159 return TRUE;
160 }
161
162 static void relation_remove_item(relation_t *relation, object_t *object) {
163
164 printf("remove object of type %d from relation #" ITEM_ID_FORMAT "\n",
165 object->type, OSM_ID(relation));
166
167 member_t **member = &relation->member;
168 while(*member) {
169 if(((*member)->object.type == object->type) &&
170 (((object->type == NODE) &&
171 (object->node == (*member)->object.node)) ||
172 ((object->type == WAY) &&
173 (object->way == (*member)->object.way)) ||
174 ((object->type == RELATION) &&
175 (object->relation == (*member)->object.relation)))) {
176
177 member_t *next = (*member)->next;
178 osm_member_free(*member);
179 *member = next;
180
181 OSM_FLAGS(relation) |= OSM_FLAG_DIRTY;
182
183 return;
184 } else
185 member = &(*member)->next;
186 }
187 g_assert(0);
188 }
189
190 /* try to find something descriptive */
191 static char *relation_get_descriptive_name(relation_t *relation) {
192 char *name = osm_tag_get_by_key(OSM_TAG(relation), "ref");
193 if (!name)
194 name = osm_tag_get_by_key(OSM_TAG(relation), "name");
195 if (!name)
196 name = osm_tag_get_by_key(OSM_TAG(relation), "note");
197 if (!name)
198 name = osm_tag_get_by_key(OSM_TAG(relation), "fix" "me");
199 if(!name)
200 name = g_strdup_printf("<ID #"ITEM_ID_FORMAT">", OSM_ID(relation));
201
202 return name;
203 }
204
205 static gboolean relation_info_dialog(GtkWidget *parent, appdata_t *appdata,
206 relation_t *relation) {
207
208 object_t object = { .type = RELATION };
209 object.relation = relation;
210 return info_dialog(parent, appdata, &object);
211 }
212
213 static char *relitem_get_role_in_relation(object_t *item, relation_t *relation) {
214 member_t *member = relation->member;
215 while(member) {
216 switch(member->object.type) {
217
218 case NODE:
219 if((item->type == NODE) && (item->node == member->object.node))
220 return member->role;
221 break;
222
223 case WAY:
224 if((item->type == WAY) && (item->way == member->object.way))
225 return member->role;
226 break;
227
228 default:
229 break;
230 }
231 member = member->next;
232 }
233 return NULL;
234 }
235
236 static gboolean relitem_is_in_relation(object_t *item, relation_t *relation) {
237 member_t *member = relation->member;
238 while(member) {
239 switch(member->object.type) {
240
241 case NODE:
242 if((item->type == NODE) && (item->node == member->object.node))
243 return TRUE;
244 break;
245
246 case WAY:
247 if((item->type == WAY) && (item->way == member->object.way))
248 return TRUE;
249 break;
250
251 default:
252 break;
253 }
254 member = member->next;
255 }
256 return FALSE;
257 }
258
259 static void changed(GtkTreeSelection *sel, gpointer user_data) {
260 relitem_context_t *context = (relitem_context_t*)user_data;
261
262 printf("relation-edit changed event\n");
263
264 /* we need to know what changed in order to let the user acknowlege it! */
265
266 /* walk the entire store */
267
268 GtkTreeIter iter;
269 gboolean done = FALSE, ok =
270 gtk_tree_model_get_iter_first(GTK_TREE_MODEL(context->store), &iter);
271 while(ok && !done) {
272 relation_t *relation = NULL;
273 gtk_tree_model_get(GTK_TREE_MODEL(context->store), &iter,
274 RELITEM_COL_DATA, &relation, -1);
275 g_assert(relation);
276
277 if(!relitem_is_in_relation(context->item, relation) &&
278 gtk_tree_selection_iter_is_selected(sel, &iter)) {
279
280 printf("selected: "ITEM_ID_FORMAT"\n", OSM_ID(relation));
281
282 /* either accept this or unselect again */
283 if(relation_add_item(context->dialog, relation, context->item))
284 gtk_list_store_set(context->store, &iter,
285 RELITEM_COL_ROLE,
286 relitem_get_role_in_relation(context->item, relation),
287 -1);
288 else
289 gtk_tree_selection_unselect_iter(sel, &iter);
290
291 done = TRUE;
292 } else if(relitem_is_in_relation(context->item, relation) &&
293 !gtk_tree_selection_iter_is_selected(sel, &iter)) {
294
295 printf("deselected: "ITEM_ID_FORMAT"\n", OSM_ID(relation));
296
297 relation_remove_item(relation, context->item);
298 gtk_list_store_set(context->store, &iter,
299 RELITEM_COL_ROLE, NULL,
300 -1);
301
302 done = TRUE;
303 }
304
305 if(!done)
306 ok = gtk_tree_model_iter_next(GTK_TREE_MODEL(context->store), &iter);
307 }
308 }
309
310 #ifndef FREMANTLE
311 /* we handle these events on our own in order to implement */
312 /* a very direct selection mechanism (multiple selections usually */
313 /* require the control key to be pressed). This interferes with */
314 /* fremantle finger scrolling, but fortunately the fremantle */
315 /* default behaviour already is what we want. */
316 static gboolean on_view_clicked(GtkWidget *widget, GdkEventButton *event,
317 gpointer user_data) {
318 if(event->window == gtk_tree_view_get_bin_window(GTK_TREE_VIEW(widget))) {
319 GtkTreePath *path;
320
321 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
322 event->x, event->y, &path, NULL, NULL, NULL)) {
323 GtkTreeSelection *sel =
324 gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
325
326 if(!gtk_tree_selection_path_is_selected(sel, path))
327 gtk_tree_selection_select_path(sel, path);
328 else
329 gtk_tree_selection_unselect_path(sel, path);
330 }
331 return TRUE;
332 }
333 return FALSE;
334 }
335 #endif
336
337 static GtkWidget *relation_item_list_widget(relitem_context_t *context) {
338 #ifndef FREMANTLE
339 context->view = gtk_tree_view_new();
340 #else
341 context->view = hildon_gtk_tree_view_new(HILDON_UI_MODE_EDIT);
342 #endif
343
344 #ifdef USE_HILDON
345 /* hildon hides these by default */
346 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(context->view), TRUE);
347 #endif
348
349 /* change list mode to "multiple" */
350 GtkTreeSelection *selection =
351 gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view));
352 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
353
354 #ifndef FREMANTLE
355 /* catch views button-press event for our custom handling */
356 g_signal_connect(context->view, "button-press-event",
357 G_CALLBACK(on_view_clicked), context);
358 #endif
359
360 /* --- "Name" column --- */
361 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
362 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
363 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(
364 "Name", renderer, "text", RELITEM_COL_NAME, NULL);
365 gtk_tree_view_column_set_expand(column, TRUE);
366 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
367
368 /* --- "Type" column --- */
369 renderer = gtk_cell_renderer_text_new();
370 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(context->view),
371 -1, "Type", renderer, "text", RELITEM_COL_TYPE, NULL);
372
373 /* --- "Role" column --- */
374 renderer = gtk_cell_renderer_text_new();
375 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(context->view),
376 -1, "Role", renderer, "text", RELITEM_COL_ROLE, NULL);
377
378 /* build and fill the store */
379 context->store = gtk_list_store_new(RELITEM_NUM_COLS,
380 G_TYPE_STRING, G_TYPE_STRING,
381 G_TYPE_STRING, G_TYPE_POINTER);
382
383 gtk_tree_view_set_model(GTK_TREE_VIEW(context->view),
384 GTK_TREE_MODEL(context->store));
385 g_object_unref(context->store);
386
387 // Debatable whether to sort by the "selected" or the "Name" column by
388 // default. Both are be useful, in different ways.
389 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(context->store),
390 RELITEM_COL_NAME, GTK_SORT_ASCENDING);
391
392 /* build a list of iters of all items that should be selected */
393
394 GtkTreeIter iter;
395 relation_t *relation = context->appdata->osm->relation;
396 while(relation) {
397 /* try to find something descriptive */
398 char *name = relation_get_descriptive_name(relation);
399
400 /* Append a row and fill in some data */
401 gtk_list_store_append(context->store, &iter);
402 gtk_list_store_set(context->store, &iter,
403 RELITEM_COL_TYPE, osm_tag_get_by_key(OSM_TAG(relation), "type"),
404 RELITEM_COL_ROLE, relitem_get_role_in_relation(context->item, relation),
405 RELITEM_COL_NAME, name,
406 RELITEM_COL_DATA, relation,
407 -1);
408
409 /* select all relations the current object is part of */
410 if(relitem_is_in_relation(context->item, relation))
411 gtk_tree_selection_select_iter(selection, &iter);
412
413 relation = relation->next;
414 }
415
416 g_signal_connect(G_OBJECT(selection), "changed",
417 G_CALLBACK(changed), context);
418
419 #ifndef FREMANTLE_PANNABLE_AREA
420 /* put view into a scrolled window */
421 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
422 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
423 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
424 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window),
425 GTK_SHADOW_ETCHED_IN);
426 gtk_container_add(GTK_CONTAINER(scrolled_window), context->view);
427 return scrolled_window;
428 #else
429 /* put view into a pannable area */
430 GtkWidget *pannable_area = hildon_pannable_area_new();
431 gtk_container_add(GTK_CONTAINER(pannable_area), context->view);
432 return pannable_area;
433 #endif
434 }
435
436 static void
437 on_dialog_destroy(GtkWidget *widget, gpointer user_data ) {
438 relitem_context_t *context = (relitem_context_t*)user_data;
439 g_free(context);
440 }
441
442 void relation_membership_dialog(GtkWidget *parent,
443 appdata_t *appdata, object_t *object) {
444 relitem_context_t *context = g_new0(relitem_context_t, 1);
445 map_t *map = appdata->map;
446 g_assert(map);
447
448 context->appdata = appdata;
449 context->item = object;
450
451 char *str = NULL;
452 switch(object->type) {
453 case NODE:
454 str = g_strdup_printf(_("Relation memberships of node #" ITEM_ID_FORMAT),
455 OBJECT_ID(*object));
456 break;
457 case WAY:
458 str = g_strdup_printf(_("Relation memberships of way #" ITEM_ID_FORMAT),
459 OBJECT_ID(*object));
460 break;
461 default:
462 g_assert((object->type == NODE) || (object->type == WAY));
463 break;
464 }
465
466 context->dialog =
467 misc_dialog_new(MISC_DIALOG_LARGE, str,
468 GTK_WINDOW(parent),
469 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
470 NULL);
471 g_free(str);
472
473 gtk_signal_connect(GTK_OBJECT(context->dialog), "destroy",
474 (GtkSignalFunc)on_dialog_destroy, context);
475
476 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
477 GTK_RESPONSE_CLOSE);
478
479 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
480 relation_item_list_widget(context), TRUE, TRUE, 0);
481
482 /* ----------------------------------- */
483
484 gtk_widget_show_all(context->dialog);
485 gtk_dialog_run(GTK_DIALOG(context->dialog));
486 gtk_widget_destroy(context->dialog);
487 }
488
489 /* -------------------- global relation list ----------------- */
490
491 typedef struct {
492 appdata_t *appdata;
493 GtkWidget *dialog, *list, *show_btn;
494 GtkListStore *store;
495 object_t *object; /* object this list relates to, NULL if global */
496 } relation_context_t;
497
498 enum {
499 RELATION_COL_TYPE = 0,
500 RELATION_COL_NAME,
501 RELATION_COL_MEMBERS,
502 RELATION_COL_DATA,
503 RELATION_NUM_COLS
504 };
505
506 static relation_t *get_selected_relation(relation_context_t *context) {
507 GtkTreeSelection *selection;
508 GtkTreeModel *model;
509 GtkTreeIter iter;
510
511 selection = list_get_selection(context->list);
512 if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
513 relation_t *relation;
514 gtk_tree_model_get(model, &iter, RELATION_COL_DATA, &relation, -1);
515 return(relation);
516 }
517 return NULL;
518 }
519
520 static void relation_list_selected(relation_context_t *context,
521 relation_t *selected) {
522
523 list_button_enable(context->list, LIST_BUTTON_USER0,
524 (selected != NULL) && (selected->member != NULL));
525 list_button_enable(context->list, LIST_BUTTON_USER1,
526 (selected != NULL) && (selected->member != NULL));
527
528 list_button_enable(context->list, LIST_BUTTON_REMOVE, selected != NULL);
529 list_button_enable(context->list, LIST_BUTTON_EDIT, selected != NULL);
530 }
531
532 static gboolean
533 relation_list_selection_func(GtkTreeSelection *selection, GtkTreeModel *model,
534 GtkTreePath *path, gboolean path_currently_selected,
535 gpointer userdata) {
536 relation_context_t *context = (relation_context_t*)userdata;
537 GtkTreeIter iter;
538
539 if(gtk_tree_model_get_iter(model, &iter, path)) {
540 g_assert(gtk_tree_path_get_depth(path) == 1);
541
542 relation_t *relation = NULL;
543 gtk_tree_model_get(model, &iter, RELATION_COL_DATA, &relation, -1);
544 relation_list_selected(context, relation);
545 }
546
547 return TRUE; /* allow selection state to change */
548 }
549
550 typedef struct {
551 relation_t *relation;
552 GtkWidget *dialog, *view;
553 GtkListStore *store;
554 } member_context_t;
555
556 enum {
557 MEMBER_COL_TYPE = 0,
558 MEMBER_COL_ID,
559 MEMBER_COL_NAME,
560 MEMBER_COL_ROLE,
561 MEMBER_COL_REF_ONLY,
562 MEMBER_COL_DATA,
563 MEMBER_NUM_COLS
564 };
565
566 static gboolean
567 member_list_selection_func(GtkTreeSelection *selection, GtkTreeModel *model,
568 GtkTreePath *path, gboolean path_currently_selected,
569 gpointer userdata) {
570 GtkTreeIter iter;
571
572 if(gtk_tree_model_get_iter(model, &iter, path)) {
573 g_assert(gtk_tree_path_get_depth(path) == 1);
574
575 member_t *member = NULL;
576 gtk_tree_model_get(model, &iter, MEMBER_COL_DATA, &member, -1);
577 if(member && member->object.type < NODE_ID)
578 return TRUE;
579 }
580
581 return FALSE;
582 }
583
584
585 static GtkWidget *member_list_widget(member_context_t *context) {
586 GtkWidget *vbox = gtk_vbox_new(FALSE,3);
587
588 #ifndef FREMANTLE_PANNABLE_AREA
589 context->view = gtk_tree_view_new();
590 #else
591 context->view = hildon_gtk_tree_view_new(HILDON_UI_MODE_EDIT);
592 #endif
593
594 gtk_tree_selection_set_select_function(
595 gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view)),
596 member_list_selection_func,
597 context, NULL);
598
599 /* --- "type" column --- */
600 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
601 g_object_set(renderer, "foreground", "grey", NULL);
602 GtkTreeViewColumn *column =
603 gtk_tree_view_column_new_with_attributes(_("Type"), renderer,
604 "text", MEMBER_COL_TYPE,
605 "foreground-set", MEMBER_COL_REF_ONLY, NULL);
606 gtk_tree_view_column_set_sort_column_id(column, MEMBER_COL_TYPE);
607 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
608
609 /* --- "id" column --- */
610 renderer = gtk_cell_renderer_text_new();
611 g_object_set(renderer, "foreground", "grey", NULL);
612 column = gtk_tree_view_column_new_with_attributes(_("Id"), renderer,
613 "text", MEMBER_COL_ID,
614 "foreground-set", MEMBER_COL_REF_ONLY, NULL);
615 gtk_tree_view_column_set_sort_column_id(column, MEMBER_COL_ID);
616 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
617
618
619 /* --- "Name" column --- */
620 renderer = gtk_cell_renderer_text_new();
621 g_object_set(renderer, "foreground", "grey", NULL);
622 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
623 column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
624 "text", MEMBER_COL_NAME,
625 "foreground-set", MEMBER_COL_REF_ONLY, NULL);
626 gtk_tree_view_column_set_expand(column, TRUE);
627 gtk_tree_view_column_set_sort_column_id(column, MEMBER_COL_NAME);
628 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
629
630 /* --- "role" column --- */
631 renderer = gtk_cell_renderer_text_new();
632 g_object_set(renderer, "foreground", "grey", NULL);
633 column = gtk_tree_view_column_new_with_attributes(_("Role"), renderer,
634 "text", MEMBER_COL_ROLE,
635 "foreground-set", MEMBER_COL_REF_ONLY, NULL);
636 gtk_tree_view_column_set_sort_column_id(column, MEMBER_COL_ROLE);
637 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
638
639
640 /* build and fill the store */
641 context->store = gtk_list_store_new(MEMBER_NUM_COLS,
642 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
643 G_TYPE_BOOLEAN, G_TYPE_POINTER);
644
645 gtk_tree_view_set_model(GTK_TREE_VIEW(context->view),
646 GTK_TREE_MODEL(context->store));
647
648 GtkTreeIter iter;
649 member_t *member = context->relation->member;
650 while(member) {
651 tag_t *tags = osm_object_get_tags(&member->object);
652 char *id = osm_object_id_string(&member->object);
653
654 /* try to find something descriptive */
655 char *name = NULL;
656 if(tags)
657 name = osm_tag_get_by_key(tags, "name");
658
659 /* Append a row and fill in some data */
660 gtk_list_store_append(context->store, &iter);
661 gtk_list_store_set(context->store, &iter,
662 MEMBER_COL_TYPE, osm_object_type_string(&member->object),
663 MEMBER_COL_ID, id,
664 MEMBER_COL_NAME, name,
665 MEMBER_COL_ROLE, member->role,
666 MEMBER_COL_REF_ONLY, member->object.type >= NODE_ID,
667 MEMBER_COL_DATA, member,
668 -1);
669
670 g_free(id);
671 member = member->next;
672 }
673
674 g_object_unref(context->store);
675
676 #ifndef FREMANTLE_PANNABLE_AREA
677 /* put it into a scrolled window */
678 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
679 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
680 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
681 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window),
682 GTK_SHADOW_ETCHED_IN);
683 gtk_container_add(GTK_CONTAINER(scrolled_window), context->view);
684
685 gtk_box_pack_start_defaults(GTK_BOX(vbox), scrolled_window);
686 #else
687 /* put view into a pannable area */
688 GtkWidget *pannable_area = hildon_pannable_area_new();
689 gtk_container_add(GTK_CONTAINER(pannable_area), context->view);
690 gtk_box_pack_start_defaults(GTK_BOX(vbox), pannable_area);
691 #endif
692
693 return vbox;
694 }
695
696 void relation_show_members(GtkWidget *parent, relation_t *relation) {
697 member_context_t *mcontext = g_new0(member_context_t, 1);
698 mcontext->relation = relation;
699
700 char *str = osm_tag_get_by_key(OSM_TAG(mcontext->relation), "name");
701 if(!str) str = osm_tag_get_by_key(OSM_TAG(mcontext->relation), "ref");
702 if(!str)
703 str = g_strdup_printf(_("Members of relation #" ITEM_ID_FORMAT),
704 OSM_ID(mcontext->relation));
705 else
706 str = g_strdup_printf(_("Members of relation \"%s\""), str);
707
708 mcontext->dialog =
709 misc_dialog_new(MISC_DIALOG_MEDIUM, str,
710 GTK_WINDOW(parent),
711 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
712 NULL);
713 g_free(str);
714
715 gtk_dialog_set_default_response(GTK_DIALOG(mcontext->dialog),
716 GTK_RESPONSE_CLOSE);
717
718 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(mcontext->dialog)->vbox),
719 member_list_widget(mcontext), TRUE, TRUE, 0);
720
721 /* ----------------------------------- */
722
723 gtk_widget_show_all(mcontext->dialog);
724 gtk_dialog_run(GTK_DIALOG(mcontext->dialog));
725 gtk_widget_destroy(mcontext->dialog);
726
727 g_free(mcontext);
728 }
729
730 /* user clicked "members" button in relation list */
731 static void on_relation_members(GtkWidget *but, relation_context_t *context) {
732 relation_t *sel = get_selected_relation(context);
733
734 if(sel) relation_show_members(context->dialog, sel);
735 }
736
737 /* user clicked "select" button in relation list */
738 static void on_relation_select(GtkWidget *but, relation_context_t *context) {
739 relation_t *sel = get_selected_relation(context);
740 map_item_deselect(context->appdata);
741
742 if(sel) {
743 map_relation_select(context->appdata, sel);
744
745 /* tell dialog to close as we want to see the selected relation */
746
747 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(but));
748 g_assert(GTK_IS_DIALOG(toplevel));
749
750 /* emit a "response" signal so we might close the dialog */
751 gtk_dialog_response(GTK_DIALOG(toplevel), GTK_RESPONSE_CLOSE);
752 }
753 }
754
755
756 static void on_relation_add(GtkWidget *but, relation_context_t *context) {
757 /* create a new relation */
758
759 relation_t *relation = osm_relation_new();
760 if(!relation_info_dialog(context->dialog, context->appdata, relation)) {
761 printf("tag edit cancelled\n");
762 osm_relation_free(relation);
763 } else {
764 osm_relation_attach(context->appdata->osm, relation);
765
766 /* append a row for the new data */
767
768 char *name = relation_get_descriptive_name(relation);
769
770 guint num = osm_relation_members_num(relation);
771
772 /* Append a row and fill in some data */
773 GtkTreeIter iter;
774 gtk_list_store_append(context->store, &iter);
775 gtk_list_store_set(context->store, &iter,
776 RELATION_COL_TYPE,
777 osm_tag_get_by_key(OSM_TAG(relation), "type"),
778 RELATION_COL_NAME, name,
779 RELATION_COL_MEMBERS, num,
780 RELATION_COL_DATA, relation,
781 -1);
782
783 gtk_tree_selection_select_iter(list_get_selection(context->list), &iter);
784 }
785 }
786
787 /* user clicked "edit..." button in relation list */
788 static void on_relation_edit(GtkWidget *but, relation_context_t *context) {
789 relation_t *sel = get_selected_relation(context);
790 if(!sel) return;
791
792 printf("edit relation #" ITEM_ID_FORMAT "\n", OSM_ID(sel));
793
794 if (!relation_info_dialog(context->dialog, context->appdata, sel))
795 return;
796
797 // Locate the changed item
798 GtkTreeIter iter;
799 gboolean valid = gtk_tree_model_get_iter_first(
800 GTK_TREE_MODEL(context->store), &iter);
801 while (valid) {
802 relation_t *row_rel;
803 gtk_tree_model_get(GTK_TREE_MODEL(context->store), &iter,
804 RELATION_COL_DATA, &row_rel,
805 -1);
806 if (row_rel == sel)
807 break;
808 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(context->store), &iter);
809 }
810 if (!valid)
811 return;
812
813 // Found it. Update all visible fields.
814 gtk_list_store_set(context->store, &iter,
815 RELATION_COL_TYPE, osm_tag_get_by_key(OSM_TAG(sel), "type"),
816 RELATION_COL_NAME, relation_get_descriptive_name(sel),
817 RELATION_COL_MEMBERS, osm_relation_members_num(sel),
818 -1);
819
820 // Order will probably have changed, so refocus
821 list_focus_on(context->list, &iter, TRUE);
822 }
823
824
825 /* remove the selected relation */
826 static void on_relation_remove(GtkWidget *but, relation_context_t *context) {
827 relation_t *sel = get_selected_relation(context);
828 if(!sel) return;
829
830 printf("remove relation #" ITEM_ID_FORMAT "\n", OSM_ID(sel));
831
832 gint members = osm_relation_members_num(sel);
833
834 if(members)
835 if(!yes_no_f(context->dialog, NULL, 0, 0,
836 _("Delete non-empty relation?"),
837 _("This relation still has %d members. "
838 "Delete it anyway?"), members))
839 return;
840
841 /* first remove selected row from list */
842 GtkTreeIter iter;
843 GtkTreeModel *model;
844 if(list_get_selected(context->list, &model, &iter))
845 gtk_list_store_remove(context->store, &iter);
846
847 /* then really delete it */
848 osm_relation_delete(context->appdata->osm, sel, FALSE);
849
850 relation_list_selected(context, NULL);
851 }
852
853 static GtkWidget *relation_list_widget(relation_context_t *context) {
854 context->list = list_new(LIST_HILDON_WITH_HEADERS);
855
856 list_set_selection_function(context->list, relation_list_selection_func,
857 context);
858
859 list_set_columns(context->list,
860 _("Name"), RELATION_COL_NAME, LIST_FLAG_ELLIPSIZE,
861 _("Type"), RELATION_COL_TYPE, 0,
862 _("Members"), RELATION_COL_MEMBERS, 0,
863 NULL);
864
865 /* build and fill the store */
866 context->store = gtk_list_store_new(RELATION_NUM_COLS,
867 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT,
868 G_TYPE_POINTER);
869
870 list_set_store(context->list, context->store);
871
872 // Sorting by ref/name by default is useful for places with lots of numbered
873 // bus routes. Especially for small screens.
874 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(context->store),
875 RELATION_COL_NAME, GTK_SORT_ASCENDING);
876
877 GtkTreeIter iter;
878 relation_t *relation = NULL;
879 relation_chain_t *rchain = NULL;
880
881 if(context->object)
882 rchain = osm_object_to_relation(context->appdata->osm, context->object);
883 else
884 relation = context->appdata->osm->relation;
885
886 while(relation || rchain) {
887 relation_t *rel = relation?relation:rchain->relation;
888
889 char *name = relation_get_descriptive_name(rel);
890 guint num = osm_relation_members_num(rel);
891
892 /* Append a row and fill in some data */
893 gtk_list_store_append(context->store, &iter);
894 gtk_list_store_set(context->store, &iter,
895 RELATION_COL_TYPE,
896 osm_tag_get_by_key(OSM_TAG(rel), "type"),
897 RELATION_COL_NAME, name,
898 RELATION_COL_MEMBERS, num,
899 RELATION_COL_DATA, rel,
900 -1);
901
902 if(relation) relation = relation->next;
903 if(rchain) rchain = rchain->next;
904 }
905
906 if(rchain)
907 osm_relation_chain_free(rchain);
908
909 g_object_unref(context->store);
910
911 list_set_static_buttons(context->list, LIST_BTN_NEW | LIST_BTN_WIDE,
912 G_CALLBACK(on_relation_add), G_CALLBACK(on_relation_edit),
913 G_CALLBACK(on_relation_remove), context);
914
915 list_set_user_buttons(context->list,
916 LIST_BUTTON_USER0, _("Members"), G_CALLBACK(on_relation_members),
917 LIST_BUTTON_USER1, _("Select"), G_CALLBACK(on_relation_select),
918 0);
919
920 relation_list_selected(context, NULL);
921
922 return context->list;
923 }
924
925 /* a global view on all relations */
926 void relation_list(GtkWidget *parent, appdata_t *appdata, object_t *object) {
927 relation_context_t *context = g_new0(relation_context_t, 1);
928 context->appdata = appdata;
929
930 char *str = NULL;
931 if(!object)
932 str = g_strdup(_("All relations"));
933 else {
934 str = g_strdup_printf(_("Relations of %s"), osm_object_string(object));
935 context->object = object;
936 }
937
938 context->dialog =
939 misc_dialog_new(MISC_DIALOG_LARGE, str,
940 GTK_WINDOW(parent),
941 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
942 NULL);
943
944 g_free(str);
945
946 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
947 GTK_RESPONSE_CLOSE);
948
949 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
950 relation_list_widget(context), TRUE, TRUE, 0);
951
952 /* ----------------------------------- */
953
954
955 gtk_widget_show_all(context->dialog);
956 gtk_dialog_run(GTK_DIALOG(context->dialog));
957
958 gtk_widget_destroy(context->dialog);
959 g_free(context);
960 }
961
962 // vim:et:ts=8:sw=2:sts=2:ai