Contents of /trunk/src/relation_edit.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 99 - (show annotations)
Thu Feb 26 17:07:17 2009 UTC (15 years, 4 months ago) by achadwick
File MIME type: text/plain
File size: 36429 byte(s)
Relation editor lists are now sorted by name.
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 /* UI sizes */
23
24 #ifdef USE_HILDON
25 // Making the dialog a little wider makes it less "crowded"
26 static const guint LIST_OF_RELATIONS_DIALOG_WIDTH = 500;
27 static const guint LIST_OF_RELATIONS_DIALOG_HEIGHT = 300;
28 static const guint LIST_OF_MEMBERS_DIALOG_WIDTH = 500;
29 static const guint LIST_OF_MEMBERS_DIALOG_HEIGHT = 300;
30 #else
31 // Desktop mode dialogs should be narrower and taller
32 static const guint LIST_OF_RELATIONS_DIALOG_WIDTH = 475;
33 static const guint LIST_OF_RELATIONS_DIALOG_HEIGHT = 350;
34 static const guint LIST_OF_MEMBERS_DIALOG_WIDTH = 450;
35 static const guint LIST_OF_MEMBERS_DIALOG_HEIGHT = 350;
36 #endif
37
38
39 /* ------------------------- some generic treeview helpers -----------------------*/
40
41
42 static void
43 list_pre_inplace_edit_tweak (GtkTreeModel *model)
44 {
45 // Remove any current sort ordering, leaving items where they are.
46 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
47 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
48 GTK_SORT_ASCENDING);
49 }
50
51
52 /* Refocus a GtkTreeView an item specified by iter, unselecting the current
53 selection and optionally highlighting the new one. Typically called after
54 making an edit to an item with a covering sub-dialog. */
55
56 static void
57 list_focus_on (GtkTreeView *view, GtkTreeModel *model,
58 GtkTreeIter *iter, gboolean highlight)
59 {
60 // Handle de/reselection
61 GtkTreeSelection *sel = gtk_tree_view_get_selection(view);
62 gtk_tree_selection_unselect_all(sel);
63
64 // Scroll to it, since it might now be out of view.
65 GtkTreePath *path = gtk_tree_model_get_path(model, iter);
66 gtk_tree_view_scroll_to_cell(view, path, NULL, FALSE, 0, 0);
67 gtk_tree_path_free(path);
68
69 // reselect
70 if (highlight)
71 gtk_tree_selection_select_iter(sel, iter);
72 }
73
74
75 /* --------------- relation dialog for an item (node or way) ----------- */
76
77 typedef struct {
78 relation_item_t *item;
79 appdata_t *appdata;
80 GtkWidget *dialog, *view;
81 GtkListStore *store;
82 GtkWidget *but_add, *but_edit, *but_remove;
83 } relitem_context_t;
84
85 enum {
86 RELITEM_COL_SELECTED = 0,
87 RELITEM_COL_TYPE,
88 RELITEM_COL_ROLE,
89 RELITEM_COL_NAME,
90 RELITEM_COL_DATA,
91 RELITEM_NUM_COLS
92 };
93
94 typedef struct role_chain_s {
95 char *role;
96 struct role_chain_s *next;
97 } role_chain_t;
98
99 static gboolean relation_add_item(GtkWidget *parent,
100 relation_t *relation, relation_item_t *item) {
101 role_chain_t *chain = NULL, **chainP = &chain;
102
103 printf("add item of type %d to relation #%ld\n",
104 item->type, relation->id);
105
106 /* ask the user for the role of the new item in this relation */
107
108 /* collect roles first */
109 member_t *member = relation->member;
110 while(member) {
111 if(member->role) {
112 /* check if this role has already been saved */
113 gboolean already_stored = FALSE;
114 role_chain_t *crole = chain;
115 while(crole) {
116 if(strcasecmp(crole->role, member->role) == 0) already_stored = TRUE;
117 crole = crole->next;
118 }
119
120 /* not stored yet: attach it */
121 if(!already_stored) {
122 *chainP = g_new0(role_chain_t, 1);
123 (*chainP)->role = g_strdup(member->role);
124 chainP = &(*chainP)->next;
125 }
126 }
127 member = member->next;
128 }
129
130 /* ------------------ role dialog ---------------- */
131 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Select role"),
132 GTK_WINDOW(parent), GTK_DIALOG_MODAL,
133 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
134 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
135 NULL);
136
137 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
138
139 char *type = osm_tag_get_by_key(relation->tag, "type");
140
141 char *info_str = NULL;
142 if(type) info_str = g_strdup_printf(_("In relation of type: %s"), type);
143 else info_str = g_strdup_printf(_("In relation #%ld"), relation->id);
144 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox),
145 gtk_label_new(info_str));
146 g_free(info_str);
147
148 char *name = osm_tag_get_by_key(relation->tag, "name");
149 if(name)
150 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox),
151 gtk_label_new(name));
152
153 GtkWidget *hbox = gtk_hbox_new(FALSE, 8);
154 gtk_box_pack_start_defaults(GTK_BOX(hbox), gtk_label_new(_("Role:")));
155
156 GtkWidget *entry = NULL;
157 if(chain) {
158 entry = gtk_combo_box_entry_new_text();
159
160 /* fill combo box with presets */
161 while(chain) {
162 role_chain_t *next = chain->next;
163 gtk_combo_box_append_text(GTK_COMBO_BOX(entry), chain->role);
164 g_free(chain);
165 chain = next;
166 }
167 } else
168 entry = gtk_entry_new();
169
170 gtk_box_pack_start_defaults(GTK_BOX(hbox), entry);
171 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox);
172
173 gtk_widget_show_all(dialog);
174 if(GTK_RESPONSE_ACCEPT != gtk_dialog_run(GTK_DIALOG(dialog))) {
175 printf("user clicked cancel\n");
176 gtk_widget_destroy(dialog);
177 return FALSE;
178 }
179
180 printf("user clicked ok\n");
181
182 /* get role from dialog */
183 char *ptr = NULL;
184
185 if(GTK_IS_COMBO_BOX(entry))
186 ptr = gtk_combo_box_get_active_text(GTK_COMBO_BOX(entry));
187 else
188 ptr = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
189
190 char *role = NULL;
191 if(ptr && strlen(ptr)) role = g_strdup(ptr);
192
193 gtk_widget_destroy(dialog);
194
195 /* search end of member chain */
196 member_t **memberP = &relation->member;
197 while(*memberP) memberP = &(*memberP)->next;
198
199 /* create new member */
200 *memberP = g_new0(member_t, 1);
201 (*memberP)->type = item->type;
202 (*memberP)->role = role;
203 switch(item->type) {
204 case NODE:
205 (*memberP)->node = item->node;
206 break;
207 case WAY:
208 (*memberP)->way = item->way;
209 break;
210 case RELATION:
211 (*memberP)->relation = item->relation;
212 break;
213 default:
214 g_assert((item->type == NODE)||(item->type == WAY)||
215 (item->type == RELATION));
216 break;
217 }
218
219 relation->flags |= OSM_FLAG_DIRTY;
220 return TRUE;
221 }
222
223 static void relation_remove_item(relation_t *relation, relation_item_t *item) {
224
225 printf("remove item of type %d from relation #%ld\n",
226 item->type, relation->id);
227
228 member_t **member = &relation->member;
229 while(*member) {
230 if(((*member)->type == item->type) &&
231 (((item->type == NODE) && (item->node == (*member)->node)) ||
232 ((item->type == WAY) && (item->way == (*member)->way)) ||
233 ((item->type == RELATION) && (item->relation == (*member)->relation)))) {
234
235 member_t *next = (*member)->next;
236 osm_member_free(*member);
237 *member = next;
238
239 relation->flags |= OSM_FLAG_DIRTY;
240
241 return;
242 } else
243 member = &(*member)->next;
244 }
245 g_assert(0);
246 }
247
248 static void relation_item_list_selected(relitem_context_t *context,
249 gboolean selected) {
250
251 if(context->but_remove)
252 gtk_widget_set_sensitive(context->but_remove, selected);
253 if(context->but_edit)
254 gtk_widget_set_sensitive(context->but_edit, selected);
255 }
256
257 static gboolean
258 relation_item_list_selection_func(GtkTreeSelection *selection, GtkTreeModel *model,
259 GtkTreePath *path, gboolean path_currently_selected,
260 gpointer userdata) {
261 relitem_context_t *context = (relitem_context_t*)userdata;
262 GtkTreeIter iter;
263
264 if(gtk_tree_model_get_iter(model, &iter, path)) {
265 g_assert(gtk_tree_path_get_depth(path) == 1);
266 relation_item_list_selected(context, TRUE);
267 }
268
269 return TRUE; /* allow selection state to change */
270 }
271
272 /* try to find something descriptive */
273 static char *relation_get_descriptive_name(relation_t *relation) {
274 char *name = osm_tag_get_by_key(relation->tag, "ref");
275 if (!name)
276 name = osm_tag_get_by_key(relation->tag, "name");
277 if (!name)
278 name = osm_tag_get_by_key(relation->tag, "note");
279 if (!name)
280 name = osm_tag_get_by_key(relation->tag, "fix" "me");
281 return name;
282 }
283
284 static void on_relation_item_add(GtkWidget *but, relitem_context_t *context) {
285 /* create a new relation */
286
287 relation_t *relation = osm_relation_new();
288 if(!info_dialog(context->dialog, context->appdata, relation)) {
289 printf("tag edit cancelled\n");
290 osm_relation_free(relation);
291 } else {
292 osm_relation_attach(context->appdata->osm, relation);
293
294 /* add to list */
295
296 /* append a row for the new data */
297 char *name = relation_get_descriptive_name(relation);
298
299 GtkTreeIter iter;
300 gtk_list_store_append(context->store, &iter);
301 gtk_list_store_set(context->store, &iter,
302 RELITEM_COL_SELECTED, FALSE,
303 RELITEM_COL_TYPE,
304 osm_tag_get_by_key(relation->tag, "type"),
305 RELITEM_COL_NAME, name,
306 RELITEM_COL_DATA, relation,
307 -1);
308
309 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(
310 GTK_TREE_VIEW(context->view)), &iter);
311
312 /* scroll to end */
313 // GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment();
314 /* xyz */
315 }
316 }
317
318 static relation_t *get_selection(relitem_context_t *context) {
319 GtkTreeSelection *selection;
320 GtkTreeModel *model;
321 GtkTreeIter iter;
322
323 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view));
324 if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
325 relation_t *relation;
326 gtk_tree_model_get(model, &iter, RELITEM_COL_DATA, &relation, -1);
327 return(relation);
328 }
329 return NULL;
330 }
331
332 static void on_relation_item_edit(GtkWidget *but, relitem_context_t *context) {
333 relation_t *sel = get_selection(context);
334 if(!sel) return;
335
336 printf("edit relation item #%ld\n", sel->id);
337
338 if (!info_dialog(context->dialog, context->appdata, sel))
339 return;
340
341 // Locate the changed item
342 GtkTreeIter iter;
343 gboolean valid = gtk_tree_model_get_iter_first(
344 GTK_TREE_MODEL(context->store), &iter);
345 while (valid) {
346 relation_t *row_rel;
347 gtk_tree_model_get(GTK_TREE_MODEL(context->store), &iter,
348 RELITEM_COL_DATA, &row_rel,
349 -1);
350 if (row_rel == sel)
351 break;
352 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(context->store), &iter);
353 }
354 if (!valid)
355 return;
356
357 // Found it. Update all visible fields that belong to the relation iself.
358 gtk_list_store_set(context->store, &iter,
359 RELITEM_COL_TYPE, osm_tag_get_by_key(sel->tag, "type"),
360 RELITEM_COL_NAME, relation_get_descriptive_name(sel),
361 -1);
362
363 // Order will probably have changed, so refocus
364 list_focus_on(GTK_TREE_VIEW(context->view), GTK_TREE_MODEL(context->store),
365 &iter, TRUE);
366 }
367
368 /* remove the selected relation */
369 static void on_relation_item_remove(GtkWidget *but, relitem_context_t *context) {
370 relation_t *sel = get_selection(context);
371 if(!sel) return;
372
373 printf("remove relation #%ld\n", sel->id);
374
375 gint members = osm_relation_members_num(sel);
376
377 if(members)
378 if(!yes_no_f(context->dialog, NULL, 0, 0,
379 _("Delete non-empty relation?"),
380 _("This relation still has %d members. "
381 "Delete it anyway?"), members))
382 return;
383
384 /* first remove selected row from list */
385 GtkTreeIter iter;
386 GtkTreeSelection *selection =
387 gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view));
388 if(gtk_tree_selection_get_selected(selection, NULL, &iter))
389 gtk_list_store_remove(context->store, &iter);
390
391 /* then really delete it */
392 osm_relation_delete(context->appdata->osm, sel, FALSE);
393
394 relation_item_list_selected(context, FALSE);
395 }
396
397 static char *relitem_get_role_in_relation(relation_item_t *item, relation_t *relation) {
398 member_t *member = relation->member;
399 while(member) {
400 switch(member->type) {
401
402 case NODE:
403 if((item->type == NODE) && (item->node == member->node))
404 return member->role;
405 break;
406
407 case WAY:
408 if((item->type == WAY) && (item->way == member->way))
409 return member->role;
410 break;
411
412 default:
413 break;
414 }
415 member = member->next;
416 }
417 return NULL;
418 }
419
420 static void
421 relitem_toggled(GtkCellRendererToggle *cell, const gchar *path_str,
422 relitem_context_t *context) {
423 GtkTreePath *path;
424 GtkTreeIter iter;
425
426 path = gtk_tree_path_new_from_string(path_str);
427 gtk_tree_model_get_iter(GTK_TREE_MODEL(context->store), &iter, path);
428 gtk_tree_path_free(path);
429
430 /* get current enabled flag */
431 gboolean enabled;
432 relation_t *relation = NULL;
433 gtk_tree_model_get(GTK_TREE_MODEL(context->store), &iter,
434 RELITEM_COL_SELECTED, &enabled,
435 RELITEM_COL_DATA, &relation,
436 -1);
437
438 list_pre_inplace_edit_tweak(GTK_TREE_MODEL(context->store));
439
440 if(!enabled) {
441 printf("will now become be part of this relation\n");
442 if(relation_add_item(context->dialog, relation, context->item))
443 gtk_list_store_set(context->store, &iter,
444 RELITEM_COL_SELECTED, TRUE,
445 RELITEM_COL_ROLE,
446 relitem_get_role_in_relation(context->item, relation),
447 -1);
448 } else {
449 printf("item will not be part of this relation anymore\n");
450 relation_remove_item(relation, context->item);
451 gtk_list_store_set(context->store, &iter,
452 RELITEM_COL_SELECTED, FALSE,
453 RELITEM_COL_ROLE, NULL,
454 -1);
455 }
456
457 }
458
459 static gboolean relitem_is_in_relation(relation_item_t *item, relation_t *relation) {
460 member_t *member = relation->member;
461 while(member) {
462 switch(member->type) {
463
464 case NODE:
465 if((item->type == NODE) && (item->node == member->node))
466 return TRUE;
467 break;
468
469 case WAY:
470 if((item->type == WAY) && (item->way == member->way))
471 return TRUE;
472 break;
473
474 default:
475 break;
476 }
477 member = member->next;
478 }
479 return FALSE;
480 }
481
482 static GtkWidget *relation_item_list_widget(relitem_context_t *context) {
483 GtkWidget *vbox = gtk_vbox_new(FALSE,3);
484 context->view = gtk_tree_view_new();
485
486 gtk_tree_selection_set_select_function(
487 gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view)),
488 relation_item_list_selection_func,
489 context, NULL);
490
491
492 /* --- "selected" column --- */
493 GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new();
494 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(
495 _(""), renderer, "active", RELITEM_COL_SELECTED, NULL);
496 g_signal_connect(renderer, "toggled", G_CALLBACK(relitem_toggled), context);
497 gtk_tree_view_column_set_sort_column_id(column, RELITEM_COL_SELECTED);
498 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
499
500 /* --- "Type" column --- */
501 renderer = gtk_cell_renderer_text_new();
502 column = gtk_tree_view_column_new_with_attributes(_("Type"), renderer,
503 "text", RELITEM_COL_TYPE, NULL);
504 gtk_tree_view_column_set_sort_column_id(column, RELITEM_COL_TYPE);
505 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
506
507 /* --- "Role" column --- */
508 renderer = gtk_cell_renderer_text_new();
509 column = gtk_tree_view_column_new_with_attributes(_("Role"), renderer,
510 "text", RELITEM_COL_ROLE, NULL);
511 gtk_tree_view_column_set_sort_column_id(column, RELITEM_COL_ROLE);
512 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
513
514 /* --- "Name" column --- */
515 renderer = gtk_cell_renderer_text_new();
516 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
517 column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
518 "text", RELITEM_COL_NAME, NULL);
519 gtk_tree_view_column_set_expand(column, TRUE);
520 gtk_tree_view_column_set_sort_column_id(column, RELITEM_COL_NAME);
521 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
522
523
524 /* build and fill the store */
525 context->store = gtk_list_store_new(RELITEM_NUM_COLS,
526 G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING,
527 G_TYPE_STRING, G_TYPE_POINTER);
528
529 gtk_tree_view_set_model(GTK_TREE_VIEW(context->view),
530 GTK_TREE_MODEL(context->store));
531
532 // Debatable whether to sort by the "selected" or the "Name" column by
533 // default. Both are be useful, in different ways.
534 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(context->store),
535 RELITEM_COL_NAME, GTK_SORT_ASCENDING);
536
537 GtkTreeIter iter;
538 relation_t *relation = context->appdata->osm->relation;
539 while(relation) {
540 /* try to find something descriptive */
541 char *name = relation_get_descriptive_name(relation);
542
543 /* Append a row and fill in some data */
544 gtk_list_store_append(context->store, &iter);
545 gtk_list_store_set(context->store, &iter,
546 RELITEM_COL_SELECTED, relitem_is_in_relation(context->item, relation),
547 RELITEM_COL_TYPE, osm_tag_get_by_key(relation->tag, "type"),
548 RELITEM_COL_ROLE, relitem_get_role_in_relation(context->item, relation),
549 RELITEM_COL_NAME, name,
550 RELITEM_COL_DATA, relation,
551 -1);
552
553 relation = relation->next;
554 }
555
556 g_object_unref(context->store);
557
558 /* put it into a scrolled window */
559 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
560 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
561 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
562 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window),
563 GTK_SHADOW_ETCHED_IN);
564 gtk_container_add(GTK_CONTAINER(scrolled_window), context->view);
565
566 gtk_box_pack_start_defaults(GTK_BOX(vbox), scrolled_window);
567
568 /* ------- button box ------------ */
569
570 GtkWidget *hbox = gtk_hbox_new(TRUE,3);
571
572 context->but_add = gtk_button_new_with_label(_("Add..."));
573 // gtk_widget_set_sensitive(context->but_add, FALSE);
574 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->but_add);
575 gtk_signal_connect(GTK_OBJECT(context->but_add), "clicked",
576 GTK_SIGNAL_FUNC(on_relation_item_add), context);
577
578 context->but_edit = gtk_button_new_with_label(_("Edit..."));
579 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->but_edit);
580 gtk_signal_connect(GTK_OBJECT(context->but_edit), "clicked",
581 GTK_SIGNAL_FUNC(on_relation_item_edit), context);
582
583 context->but_remove = gtk_button_new_with_label(_("Remove"));
584 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->but_remove);
585 gtk_signal_connect(GTK_OBJECT(context->but_remove), "clicked",
586 GTK_SIGNAL_FUNC(on_relation_item_remove), context);
587
588 relation_item_list_selected(context, FALSE);
589
590 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
591 return vbox;
592 }
593
594 void relation_add_dialog(appdata_t *appdata, relation_item_t *relitem) {
595 relitem_context_t *context = g_new0(relitem_context_t, 1);
596 map_t *map = appdata->map;
597 g_assert(map);
598
599 context->appdata = appdata;
600 context->item = relitem;
601
602 char *str = NULL;
603 switch(relitem->type) {
604 case NODE:
605 str = g_strdup_printf(_("Relations for node #%ld"), relitem->node->id);
606 break;
607 case WAY:
608 str = g_strdup_printf(_("Relations for way #%ld"), relitem->way->id);
609 break;
610 default:
611 g_assert((relitem->type == NODE) || (relitem->type == WAY));
612 break;
613 }
614
615 context->dialog = gtk_dialog_new_with_buttons(str,
616 GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL,
617 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
618 NULL);
619 g_free(str);
620
621 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
622 GTK_RESPONSE_CLOSE);
623
624 gtk_window_set_default_size(GTK_WINDOW(context->dialog),
625 LIST_OF_RELATIONS_DIALOG_WIDTH,
626 LIST_OF_RELATIONS_DIALOG_HEIGHT);
627 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
628 relation_item_list_widget(context), TRUE, TRUE, 0);
629
630 /* ----------------------------------- */
631
632 gtk_widget_show_all(context->dialog);
633 gtk_dialog_run(GTK_DIALOG(context->dialog));
634 gtk_widget_destroy(context->dialog);
635
636 g_free(context);
637 }
638
639 /* -------------------- global relation list ----------------- */
640
641 typedef struct {
642 appdata_t *appdata;
643 GtkWidget *dialog, *view;
644 GtkWidget *but_members, *but_add, *but_edit, *but_remove;
645 GtkListStore *store;
646 } relation_context_t;
647
648 enum {
649 RELATION_COL_ID = 0,
650 RELATION_COL_TYPE,
651 RELATION_COL_NAME,
652 RELATION_COL_MEMBERS,
653 RELATION_COL_DATA,
654 RELATION_NUM_COLS
655 };
656
657 static relation_t *get_selected_relation(relation_context_t *context) {
658 GtkTreeSelection *selection;
659 GtkTreeModel *model;
660 GtkTreeIter iter;
661
662 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view));
663 if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
664 relation_t *relation;
665 gtk_tree_model_get(model, &iter, RELATION_COL_DATA, &relation, -1);
666 return(relation);
667 }
668 return NULL;
669 }
670
671 static void relation_list_selected(relation_context_t *context,
672 relation_t *selected) {
673
674 if(context->but_members)
675 gtk_widget_set_sensitive(context->but_members,
676 (selected != NULL) && (selected->member != NULL));
677
678 if(context->but_remove)
679 gtk_widget_set_sensitive(context->but_remove, selected != NULL);
680 if(context->but_edit)
681 gtk_widget_set_sensitive(context->but_edit, selected != NULL);
682 }
683
684 static gboolean
685 relation_list_selection_func(GtkTreeSelection *selection, GtkTreeModel *model,
686 GtkTreePath *path, gboolean path_currently_selected,
687 gpointer userdata) {
688 relation_context_t *context = (relation_context_t*)userdata;
689 GtkTreeIter iter;
690
691 if(gtk_tree_model_get_iter(model, &iter, path)) {
692 g_assert(gtk_tree_path_get_depth(path) == 1);
693
694 relation_t *relation = NULL;
695 gtk_tree_model_get(model, &iter, RELATION_COL_DATA, &relation, -1);
696 relation_list_selected(context, relation);
697 }
698
699 return TRUE; /* allow selection state to change */
700 }
701
702 typedef struct {
703 relation_t *relation;
704 GtkWidget *dialog, *view;
705 GtkListStore *store;
706 } member_context_t;
707
708 enum {
709 MEMBER_COL_TYPE = 0,
710 MEMBER_COL_ID,
711 MEMBER_COL_NAME,
712 MEMBER_COL_ROLE,
713 MEMBER_COL_REF_ONLY,
714 MEMBER_COL_DATA,
715 MEMBER_NUM_COLS
716 };
717
718 static gboolean
719 member_list_selection_func(GtkTreeSelection *selection, GtkTreeModel *model,
720 GtkTreePath *path, gboolean path_currently_selected,
721 gpointer userdata) {
722 GtkTreeIter iter;
723
724 if(gtk_tree_model_get_iter(model, &iter, path)) {
725 g_assert(gtk_tree_path_get_depth(path) == 1);
726
727 member_t *member = NULL;
728 gtk_tree_model_get(model, &iter, MEMBER_COL_DATA, &member, -1);
729 if(member && member->type < NODE_ID)
730 return TRUE;
731 }
732
733 return FALSE;
734 }
735
736
737 static GtkWidget *member_list_widget(member_context_t *context) {
738 GtkWidget *vbox = gtk_vbox_new(FALSE,3);
739 context->view = gtk_tree_view_new();
740
741 gtk_tree_selection_set_select_function(
742 gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view)),
743 member_list_selection_func,
744 context, NULL);
745
746 /* --- "type" column --- */
747 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
748 g_object_set(renderer, "foreground", "grey", NULL);
749 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(context->view),
750 -1, _("Type"), renderer, "text", MEMBER_COL_TYPE,
751 "foreground-set", MEMBER_COL_REF_ONLY, NULL);
752
753
754 /* --- "id" column --- */
755 renderer = gtk_cell_renderer_text_new();
756 g_object_set(renderer, "foreground", "grey", NULL);
757 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(context->view),
758 -1, _("Id"), renderer, "text", MEMBER_COL_ID,
759 "foreground-set", MEMBER_COL_REF_ONLY, NULL);
760
761 /* --- "Name" column --- */
762 renderer = gtk_cell_renderer_text_new();
763 g_object_set(renderer, "foreground", "grey", NULL);
764 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
765 GtkTreeViewColumn *column =
766 gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
767 "text", MEMBER_COL_NAME,
768 "foreground-set", MEMBER_COL_REF_ONLY, NULL);
769 gtk_tree_view_column_set_expand(column, TRUE);
770 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
771
772 /* --- "role" column --- */
773 renderer = gtk_cell_renderer_text_new();
774 g_object_set(renderer, "foreground", "grey", NULL);
775 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(context->view),
776 -1, _("Role"), renderer, "text", MEMBER_COL_ROLE,
777 "foreground-set", MEMBER_COL_REF_ONLY, NULL);
778
779 /* build and fill the store */
780 context->store = gtk_list_store_new(MEMBER_NUM_COLS,
781 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
782 G_TYPE_BOOLEAN, G_TYPE_POINTER);
783
784 gtk_tree_view_set_model(GTK_TREE_VIEW(context->view),
785 GTK_TREE_MODEL(context->store));
786
787 GtkTreeIter iter;
788 member_t *member = context->relation->member;
789 while(member) {
790 tag_t *tags = osm_object_get_tags(member->type, member->ptr);
791 char *id = osm_id_string(member->type, member->ptr);
792
793 /* try to find something descriptive */
794 char *name = NULL;
795 if(tags)
796 name = osm_tag_get_by_key(tags, "name");
797
798 /* Append a row and fill in some data */
799 gtk_list_store_append(context->store, &iter);
800 gtk_list_store_set(context->store, &iter,
801 MEMBER_COL_TYPE, osm_type_string(member->type),
802 MEMBER_COL_ID, id,
803 MEMBER_COL_NAME, name,
804 MEMBER_COL_ROLE, member->role,
805 MEMBER_COL_REF_ONLY, member->type >= NODE_ID,
806 MEMBER_COL_DATA, member,
807 -1);
808
809 g_free(id);
810 member = member->next;
811 }
812
813 g_object_unref(context->store);
814
815 /* put it into a scrolled window */
816 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
817 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
818 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
819 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window),
820 GTK_SHADOW_ETCHED_IN);
821 gtk_container_add(GTK_CONTAINER(scrolled_window), context->view);
822
823 gtk_box_pack_start_defaults(GTK_BOX(vbox), scrolled_window);
824
825 return vbox;
826 }
827
828 /* user clicked "members..." button in relation list */
829 static void on_relation_members(GtkWidget *but, relation_context_t *context) {
830 member_context_t *mcontext = g_new0(member_context_t, 1);
831
832 /* display members list */
833 mcontext->relation = get_selected_relation(context);
834 if(!mcontext->relation) return;
835
836 char *str = osm_tag_get_by_key(mcontext->relation->tag, "name");
837 if(!str) str = osm_tag_get_by_key(mcontext->relation->tag, "ref");
838 if(!str)
839 str = g_strdup_printf(_("Members of relation #%ld"),
840 mcontext->relation->id);
841 else
842 str = g_strdup_printf(_("Members of relation \"%s\""), str);
843
844 mcontext->dialog =
845 gtk_dialog_new_with_buttons(str,
846 GTK_WINDOW(context->dialog), GTK_DIALOG_MODAL,
847 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
848 NULL);
849 g_free(str);
850
851 gtk_dialog_set_default_response(GTK_DIALOG(mcontext->dialog),
852 GTK_RESPONSE_CLOSE);
853
854 gtk_window_set_default_size(GTK_WINDOW(mcontext->dialog),
855 LIST_OF_MEMBERS_DIALOG_WIDTH,
856 LIST_OF_MEMBERS_DIALOG_HEIGHT);
857
858 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(mcontext->dialog)->vbox),
859 member_list_widget(mcontext), TRUE, TRUE, 0);
860
861 /* ----------------------------------- */
862
863 gtk_widget_show_all(mcontext->dialog);
864 gtk_dialog_run(GTK_DIALOG(mcontext->dialog));
865 gtk_widget_destroy(mcontext->dialog);
866
867 g_free(mcontext);
868 }
869
870 static void on_relation_add(GtkWidget *but, relation_context_t *context) {
871 /* create a new relation */
872
873 relation_t *relation = osm_relation_new();
874 if(!info_dialog(context->dialog, context->appdata, relation)) {
875 printf("tag edit cancelled\n");
876 osm_relation_free(relation);
877 } else {
878 osm_relation_attach(context->appdata->osm, relation);
879
880 /* append a row for the new data */
881
882 char *name = relation_get_descriptive_name(relation);
883
884 guint num = osm_relation_members_num(relation);
885
886 /* Append a row and fill in some data */
887 GtkTreeIter iter;
888 gtk_list_store_append(context->store, &iter);
889 gtk_list_store_set(context->store, &iter,
890 RELATION_COL_ID, relation->id,
891 RELATION_COL_TYPE,
892 osm_tag_get_by_key(relation->tag, "type"),
893 RELATION_COL_NAME, name,
894 RELATION_COL_MEMBERS, num,
895 RELATION_COL_DATA, relation,
896 -1);
897
898 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(
899 GTK_TREE_VIEW(context->view)), &iter);
900
901 /* scroll to end */
902 // GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment();
903 /* xyz */
904 }
905 }
906
907 /* user clicked "edit..." button in relation list */
908 static void on_relation_edit(GtkWidget *but, relation_context_t *context) {
909 relation_t *sel = get_selected_relation(context);
910 if(!sel) return;
911
912 printf("edit relation #%ld\n", sel->id);
913
914 if (!info_dialog(context->dialog, context->appdata, sel))
915 return;
916
917 // Locate the changed item
918 GtkTreeIter iter;
919 gboolean valid = gtk_tree_model_get_iter_first(
920 GTK_TREE_MODEL(context->store), &iter);
921 while (valid) {
922 relation_t *row_rel;
923 gtk_tree_model_get(GTK_TREE_MODEL(context->store), &iter,
924 RELATION_COL_DATA, &row_rel,
925 -1);
926 if (row_rel == sel)
927 break;
928 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(context->store), &iter);
929 }
930 if (!valid)
931 return;
932
933 // Found it. Update all visible fields.
934 gtk_list_store_set(context->store, &iter,
935 RELATION_COL_ID, sel->id,
936 RELATION_COL_TYPE, osm_tag_get_by_key(sel->tag, "type"),
937 RELATION_COL_NAME, relation_get_descriptive_name(sel),
938 RELATION_COL_MEMBERS, osm_relation_members_num(sel),
939 -1);
940
941 // Order will probably have changed, so refocus
942 list_focus_on(GTK_TREE_VIEW(context->view), GTK_TREE_MODEL(context->store),
943 &iter, TRUE);
944 }
945
946
947 /* remove the selected relation */
948 static void on_relation_remove(GtkWidget *but, relation_context_t *context) {
949 relation_t *sel = get_selected_relation(context);
950 if(!sel) return;
951
952 printf("remove relation #%ld\n", sel->id);
953
954 gint members = osm_relation_members_num(sel);
955
956 if(members)
957 if(!yes_no_f(context->dialog, NULL, 0, 0,
958 _("Delete non-empty relation?"),
959 _("This relation still has %d members. "
960 "Delete it anyway?"), members))
961 return;
962
963 /* first remove selected row from list */
964 GtkTreeIter iter;
965 GtkTreeSelection *selection =
966 gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view));
967 if(gtk_tree_selection_get_selected(selection, NULL, &iter))
968 gtk_list_store_remove(context->store, &iter);
969
970 /* then really delete it */
971 osm_relation_delete(context->appdata->osm, sel, FALSE);
972
973 relation_list_selected(context, NULL);
974 }
975
976 static GtkWidget *relation_list_widget(relation_context_t *context) {
977 GtkWidget *vbox = gtk_vbox_new(FALSE,3);
978 context->view = gtk_tree_view_new();
979
980 gtk_tree_selection_set_select_function(
981 gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view)),
982 relation_list_selection_func,
983 context, NULL);
984
985 /* --- "id" column --- */
986 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
987 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(
988 _("Id"), renderer, "text", RELATION_COL_ID, NULL);
989 gtk_tree_view_column_set_sort_column_id(column, RELATION_COL_ID);
990 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
991
992 /* --- "Type" column --- */
993 renderer = gtk_cell_renderer_text_new();
994 column = gtk_tree_view_column_new_with_attributes(_("Type"), renderer,
995 "text", RELATION_COL_TYPE, NULL);
996 gtk_tree_view_column_set_sort_column_id(column, RELATION_COL_TYPE);
997 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
998
999 /* --- "Name" column --- */
1000 renderer = gtk_cell_renderer_text_new();
1001 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1002 column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
1003 "text", RELATION_COL_NAME, NULL);
1004 gtk_tree_view_column_set_expand(column, TRUE);
1005 gtk_tree_view_column_set_sort_column_id(column, RELATION_COL_NAME);
1006 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
1007
1008 /* --- "members" column --- */
1009 renderer = gtk_cell_renderer_text_new();
1010 column = gtk_tree_view_column_new_with_attributes(_("Members"), renderer,
1011 "text", RELATION_COL_MEMBERS, NULL);
1012 gtk_tree_view_column_set_sort_column_id(column, RELATION_COL_MEMBERS);
1013 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
1014
1015 /* build and fill the store */
1016 context->store = gtk_list_store_new(RELATION_NUM_COLS,
1017 G_TYPE_ITEM_ID_T, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT,
1018 G_TYPE_POINTER);
1019
1020 gtk_tree_view_set_model(GTK_TREE_VIEW(context->view),
1021 GTK_TREE_MODEL(context->store));
1022
1023 // Sorting by ref/name by default is useful for places with lots of numbered
1024 // bus routes. Especially for small screens.
1025 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(context->store),
1026 RELATION_COL_NAME, GTK_SORT_ASCENDING);
1027
1028 GtkTreeIter iter;
1029 relation_t *relation = context->appdata->osm->relation;
1030 while(relation) {
1031 char *name = relation_get_descriptive_name(relation);
1032
1033 guint num = osm_relation_members_num(relation);
1034
1035 /* Append a row and fill in some data */
1036 gtk_list_store_append(context->store, &iter);
1037 gtk_list_store_set(context->store, &iter,
1038 RELATION_COL_ID, relation->id,
1039 RELATION_COL_TYPE,
1040 osm_tag_get_by_key(relation->tag, "type"),
1041 RELATION_COL_NAME, name,
1042 RELATION_COL_MEMBERS, num,
1043 RELATION_COL_DATA, relation,
1044 -1);
1045
1046 relation = relation->next;
1047 }
1048
1049 g_object_unref(context->store);
1050
1051 /* put it into a scrolled window */
1052 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1053 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1054 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1055 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window),
1056 GTK_SHADOW_ETCHED_IN);
1057 gtk_container_add(GTK_CONTAINER(scrolled_window), context->view);
1058
1059 gtk_box_pack_start_defaults(GTK_BOX(vbox), scrolled_window);
1060
1061 /* ------- button box ------------ */
1062
1063 GtkWidget *hbox = gtk_hbox_new(TRUE,3);
1064
1065 context->but_add = gtk_button_new_with_label(_("Add..."));
1066 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->but_add);
1067 gtk_signal_connect(GTK_OBJECT(context->but_add), "clicked",
1068 GTK_SIGNAL_FUNC(on_relation_add), context);
1069
1070 context->but_edit = gtk_button_new_with_label(_("Edit..."));
1071 gtk_widget_set_sensitive(context->but_edit, FALSE);
1072 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->but_edit);
1073 gtk_signal_connect(GTK_OBJECT(context->but_edit), "clicked",
1074 GTK_SIGNAL_FUNC(on_relation_edit), context);
1075
1076 context->but_members = gtk_button_new_with_label(_("Members..."));
1077 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->but_members);
1078 gtk_signal_connect(GTK_OBJECT(context->but_members), "clicked",
1079 GTK_SIGNAL_FUNC(on_relation_members), context);
1080
1081 context->but_remove = gtk_button_new_with_label(_("Remove"));
1082 gtk_widget_set_sensitive(context->but_remove, FALSE);
1083 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->but_remove);
1084 gtk_signal_connect(GTK_OBJECT(context->but_remove), "clicked",
1085 GTK_SIGNAL_FUNC(on_relation_remove), context);
1086
1087 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1088
1089 relation_list_selected(context, NULL);
1090
1091 return vbox;
1092 }
1093
1094 /* a global view on all relations */
1095 void relation_list(appdata_t *appdata) {
1096 relation_context_t *context = g_new0(relation_context_t, 1);
1097 context->appdata = appdata;
1098
1099 printf("relation list\n");
1100
1101 context->dialog =
1102 gtk_dialog_new_with_buttons(_("All relations"),
1103 GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL,
1104 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1105 NULL);
1106
1107 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
1108 GTK_RESPONSE_CLOSE);
1109
1110 gtk_window_set_default_size(GTK_WINDOW(context->dialog),
1111 LIST_OF_RELATIONS_DIALOG_WIDTH,
1112 LIST_OF_RELATIONS_DIALOG_HEIGHT);
1113
1114 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
1115 relation_list_widget(context), TRUE, TRUE, 0);
1116
1117 /* ----------------------------------- */
1118
1119 gtk_widget_show_all(context->dialog);
1120 gtk_dialog_run(GTK_DIALOG(context->dialog));
1121 gtk_widget_destroy(context->dialog);
1122
1123 g_free(context);
1124 }
1125
1126
1127 // vim:et:ts=8:sw=2:sts=2:ai