Contents of /trunk/src/relation_edit.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 73 - (show annotations)
Thu Feb 12 14:27:52 2009 UTC (15 years, 3 months ago) by harbaum
File MIME type: text/plain
File size: 15590 byte(s)
Create new relations, more intuitive role editing
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 relation_item_t *item;
26 appdata_t *appdata;
27 GtkWidget *dialog, *view;
28 GtkListStore *store;
29 GtkWidget *but_add, *but_edit, *but_remove;
30 } relitem_context_t;
31
32 enum {
33 RELITEM_COL_SELECTED = 0,
34 RELITEM_COL_TYPE,
35 RELITEM_COL_ROLE,
36 RELITEM_COL_NAME,
37 RELITEM_COL_DATA,
38 RELITEM_NUM_COLS
39 };
40
41 typedef struct role_chain_s {
42 char *role;
43 struct role_chain_s *next;
44 } role_chain_t;
45
46 static gboolean relation_add_item(GtkWidget *parent,
47 relation_t *relation, relation_item_t *item) {
48 role_chain_t *chain = NULL, **chainP = &chain;
49
50 printf("add item of type %d to relation #%ld\n",
51 item->type, relation->id);
52
53 /* ask the user for the role of the new item in this relation */
54
55 /* collect roles first */
56 member_t *member = relation->member;
57 while(member) {
58 if(member->role) {
59 /* check if this role has already been saved */
60 gboolean already_stored = FALSE;
61 role_chain_t *crole = chain;
62 while(crole) {
63 if(strcasecmp(crole->role, member->role) == 0) already_stored = TRUE;
64 crole = crole->next;
65 }
66
67 /* not stored yet: attach it */
68 if(!already_stored) {
69 *chainP = g_new0(role_chain_t, 1);
70 (*chainP)->role = g_strdup(member->role);
71 chainP = &(*chainP)->next;
72 }
73 }
74 member = member->next;
75 }
76
77 /* ------------------ role dialog ---------------- */
78 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Select role"),
79 GTK_WINDOW(parent), GTK_DIALOG_MODAL,
80 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
81 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
82 NULL);
83
84 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
85
86 char *type = osm_tag_get_by_key(relation->tag, "type");
87
88 char *info_str = NULL;
89 if(type) info_str = g_strdup_printf(_("In relation of type: %s"), type);
90 else info_str = g_strdup_printf(_("In relation #%ld"), relation->id);
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(relation->tag, "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 gtk_box_pack_start_defaults(GTK_BOX(hbox), gtk_label_new(_("Role:")));
102
103 GtkWidget *entry = NULL;
104 if(chain) {
105 entry = gtk_combo_box_entry_new_text();
106
107 /* fill combo box with presets */
108 while(chain) {
109 role_chain_t *next = chain->next;
110 gtk_combo_box_append_text(GTK_COMBO_BOX(entry), chain->role);
111 g_free(chain);
112 chain = next;
113 }
114 } else
115 entry = gtk_entry_new();
116
117 gtk_box_pack_start_defaults(GTK_BOX(hbox), entry);
118 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox);
119
120 gtk_widget_show_all(dialog);
121 if(GTK_RESPONSE_ACCEPT != gtk_dialog_run(GTK_DIALOG(dialog))) {
122 printf("user clicked cancel\n");
123 gtk_widget_destroy(dialog);
124 return FALSE;
125 }
126
127 printf("user clicked ok\n");
128
129 /* get role from dialog */
130 char *ptr = NULL;
131
132 if(GTK_IS_COMBO_BOX(entry))
133 ptr = gtk_combo_box_get_active_text(GTK_COMBO_BOX(entry));
134 else
135 ptr = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
136
137 char *role = NULL;
138 if(ptr && strlen(ptr)) role = g_strdup(ptr);
139
140 gtk_widget_destroy(dialog);
141
142 /* search end of member chain */
143 member_t **memberP = &relation->member;
144 while(*memberP) memberP = &(*memberP)->next;
145
146 /* create new member */
147 *memberP = g_new0(member_t, 1);
148 (*memberP)->type = item->type;
149 (*memberP)->role = role;
150 switch(item->type) {
151 case NODE:
152 (*memberP)->node = item->node;
153 break;
154 case WAY:
155 (*memberP)->way = item->way;
156 break;
157 case RELATION:
158 (*memberP)->relation = item->relation;
159 break;
160 default:
161 g_assert((item->type == NODE)||(item->type == WAY)||
162 (item->type == RELATION));
163 break;
164 }
165
166 relation->flags |= OSM_FLAG_DIRTY;
167 return TRUE;
168 }
169
170 static void relation_remove_item(relation_t *relation, relation_item_t *item) {
171
172 printf("remove item of type %d from relation #%ld\n",
173 item->type, relation->id);
174
175 member_t **member = &relation->member;
176 while(*member) {
177 if(((*member)->type == item->type) &&
178 (((item->type == NODE) && (item->node == (*member)->node)) ||
179 ((item->type == WAY) && (item->way == (*member)->way)) ||
180 ((item->type == RELATION) && (item->relation == (*member)->relation)))) {
181
182 member_t *next = (*member)->next;
183 osm_member_free(*member);
184 *member = next;
185
186 relation->flags |= OSM_FLAG_DIRTY;
187
188 return;
189 } else
190 member = &(*member)->next;
191 }
192 g_assert(0);
193 }
194
195 static void relation_list_selected(relitem_context_t *context,
196 gboolean selected) {
197
198 if(context->but_remove)
199 gtk_widget_set_sensitive(context->but_remove, FALSE);
200 if(context->but_edit)
201 gtk_widget_set_sensitive(context->but_edit, selected);
202 }
203
204 static gboolean
205 relation_list_selection_func(GtkTreeSelection *selection, GtkTreeModel *model,
206 GtkTreePath *path, gboolean path_currently_selected,
207 gpointer userdata) {
208 relitem_context_t *context = (relitem_context_t*)userdata;
209 GtkTreeIter iter;
210
211 if(gtk_tree_model_get_iter(model, &iter, path)) {
212 g_assert(gtk_tree_path_get_depth(path) == 1);
213 relation_list_selected(context, TRUE);
214 }
215
216 return TRUE; /* allow selection state to change */
217 }
218
219 static void on_relation_add(GtkWidget *but, relitem_context_t *context) {
220 /* create a new relation */
221
222 relation_t *relation = osm_relation_new();
223 if(!info_dialog(context->dialog, context->appdata, relation)) {
224 printf("tag edit cancelled\n");
225 osm_relation_free(relation);
226 } else {
227 osm_relation_attach(context->appdata->osm, relation);
228
229 /* add to list */
230
231 /* append a row for the new data */
232 /* try to find something descriptive */
233 char *name = osm_tag_get_by_key(relation->tag, "name");
234 if(!name) name = osm_tag_get_by_key(relation->tag, "ref");
235
236 GtkTreeIter iter;
237 gtk_list_store_append(context->store, &iter);
238 gtk_list_store_set(context->store, &iter,
239 RELITEM_COL_SELECTED, FALSE,
240 RELITEM_COL_TYPE,
241 osm_tag_get_by_key(relation->tag, "type"),
242 RELITEM_COL_NAME, name,
243 RELITEM_COL_DATA, relation,
244 -1);
245
246 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(
247 GTK_TREE_VIEW(context->view)), &iter);
248
249 /* scroll to end */
250 // GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment();
251 /* xyz */
252 }
253 }
254
255 static relation_t *get_selection(relitem_context_t *context) {
256 GtkTreeSelection *selection;
257 GtkTreeModel *model;
258 GtkTreeIter iter;
259
260 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view));
261 if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
262 relation_t *relation;
263 gtk_tree_model_get(model, &iter, RELITEM_COL_DATA, &relation, -1);
264 return(relation);
265 }
266 return NULL;
267 }
268
269 static void on_relation_edit(GtkWidget *but, relitem_context_t *context) {
270 relation_t *sel = get_selection(context);
271 if(!sel) return;
272
273 printf("edit relation #%ld\n", sel->id);
274
275 info_dialog(context->dialog, context->appdata, sel);
276 }
277
278 static void on_relation_remove(GtkWidget *but, relitem_context_t *context) {
279 relation_t *sel = get_selection(context);
280 if(!sel) return;
281
282 printf("remove relation #%ld\n", sel->id);
283 }
284
285 static char *relitem_get_role_in_relation(relation_item_t *item, relation_t *relation) {
286 member_t *member = relation->member;
287 while(member) {
288 switch(member->type) {
289
290 case NODE:
291 if((item->type == NODE) && (item->node == member->node))
292 return member->role;
293 break;
294
295 case WAY:
296 if((item->type == WAY) && (item->way == member->way))
297 return member->role;
298 break;
299
300 default:
301 break;
302 }
303 member = member->next;
304 }
305 return NULL;
306 }
307
308 static void
309 relitem_toggled(GtkCellRendererToggle *cell, const gchar *path_str,
310 relitem_context_t *context) {
311 GtkTreePath *path;
312 GtkTreeIter iter;
313
314 path = gtk_tree_path_new_from_string(path_str);
315 gtk_tree_model_get_iter(GTK_TREE_MODEL(context->store), &iter, path);
316 gtk_tree_path_free(path);
317
318 /* get current enabled flag */
319 gboolean enabled;
320 relation_t *relation = NULL;
321 gtk_tree_model_get(GTK_TREE_MODEL(context->store), &iter,
322 RELITEM_COL_SELECTED, &enabled,
323 RELITEM_COL_DATA, &relation,
324 -1);
325
326 if(!enabled) {
327 printf("will now become be part of this relation\n");
328 if(relation_add_item(context->dialog, relation, context->item))
329 gtk_list_store_set(context->store, &iter,
330 RELITEM_COL_SELECTED, TRUE,
331 RELITEM_COL_ROLE,
332 relitem_get_role_in_relation(context->item, relation),
333 -1);
334 } else {
335 printf("item will not be part of this relation anymore\n");
336 relation_remove_item(relation, context->item);
337 gtk_list_store_set(context->store, &iter,
338 RELITEM_COL_SELECTED, FALSE,
339 RELITEM_COL_ROLE, NULL,
340 -1);
341 }
342 }
343
344 static gboolean relitem_is_in_relation(relation_item_t *item, relation_t *relation) {
345 member_t *member = relation->member;
346 while(member) {
347 switch(member->type) {
348
349 case NODE:
350 if((item->type == NODE) && (item->node == member->node))
351 return TRUE;
352 break;
353
354 case WAY:
355 if((item->type == WAY) && (item->way == member->way))
356 return TRUE;
357 break;
358
359 default:
360 break;
361 }
362 member = member->next;
363 }
364 return FALSE;
365 }
366
367 static GtkWidget *relation_list(relitem_context_t *context) {
368 GtkWidget *vbox = gtk_vbox_new(FALSE,3);
369 context->view = gtk_tree_view_new();
370
371 gtk_tree_selection_set_select_function(
372 gtk_tree_view_get_selection(GTK_TREE_VIEW(context->view)),
373 relation_list_selection_func,
374 context, NULL);
375
376
377 /* --- "selected" column --- */
378 GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new();
379 g_signal_connect(renderer, "toggled", G_CALLBACK(relitem_toggled), context);
380 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(context->view),
381 -1, _(""), renderer,
382 "active", RELITEM_COL_SELECTED,
383 NULL);
384
385 /* --- "Type" column --- */
386 renderer = gtk_cell_renderer_text_new();
387 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(context->view),
388 -1, _("Type"), renderer, "text", RELITEM_COL_TYPE, NULL);
389
390 /* --- "Role" column --- */
391 renderer = gtk_cell_renderer_text_new();
392 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(context->view),
393 -1, _("Role"), renderer, "text", RELITEM_COL_ROLE, NULL);
394
395 /* --- "Name" column --- */
396 renderer = gtk_cell_renderer_text_new();
397 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
398 GtkTreeViewColumn *column =
399 gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
400 "text", RELITEM_COL_NAME, NULL);
401 gtk_tree_view_column_set_expand(column, TRUE);
402 gtk_tree_view_insert_column(GTK_TREE_VIEW(context->view), column, -1);
403
404
405 /* build and fill the store */
406 context->store = gtk_list_store_new(RELITEM_NUM_COLS,
407 G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING,
408 G_TYPE_STRING, G_TYPE_POINTER);
409
410 gtk_tree_view_set_model(GTK_TREE_VIEW(context->view),
411 GTK_TREE_MODEL(context->store));
412
413 GtkTreeIter iter;
414 relation_t *relation = context->appdata->osm->relation;
415 while(relation) {
416 /* try to find something descriptive */
417 char *name = osm_tag_get_by_key(relation->tag, "name");
418 if(!name) name = osm_tag_get_by_key(relation->tag, "ref");
419
420 /* Append a row and fill in some data */
421 gtk_list_store_append(context->store, &iter);
422 gtk_list_store_set(context->store, &iter,
423 RELITEM_COL_SELECTED, relitem_is_in_relation(context->item, relation),
424 RELITEM_COL_TYPE, osm_tag_get_by_key(relation->tag, "type"),
425 RELITEM_COL_ROLE, relitem_get_role_in_relation(context->item, relation),
426 RELITEM_COL_NAME, name,
427 RELITEM_COL_DATA, relation,
428 -1);
429
430 relation = relation->next;
431 }
432
433 g_object_unref(context->store);
434
435 /* put it into a scrolled window */
436 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
437 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
438 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
439 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window),
440 GTK_SHADOW_ETCHED_IN);
441 gtk_container_add(GTK_CONTAINER(scrolled_window), context->view);
442
443 gtk_box_pack_start_defaults(GTK_BOX(vbox), scrolled_window);
444
445 /* ------- button box ------------ */
446
447 GtkWidget *hbox = gtk_hbox_new(TRUE,3);
448
449 context->but_add = gtk_button_new_with_label(_("Add..."));
450 // gtk_widget_set_sensitive(context->but_add, FALSE);
451 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->but_add);
452 gtk_signal_connect(GTK_OBJECT(context->but_add), "clicked",
453 GTK_SIGNAL_FUNC(on_relation_add), context);
454
455 context->but_edit = gtk_button_new_with_label(_("Edit..."));
456 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->but_edit);
457 gtk_signal_connect(GTK_OBJECT(context->but_edit), "clicked",
458 GTK_SIGNAL_FUNC(on_relation_edit), context);
459
460 context->but_remove = gtk_button_new_with_label(_("Remove"));
461 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->but_remove);
462 gtk_signal_connect(GTK_OBJECT(context->but_remove), "clicked",
463 GTK_SIGNAL_FUNC(on_relation_remove), context);
464
465 relation_list_selected(context, FALSE);
466
467 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
468 return vbox;
469 }
470
471 void relation_add_dialog(appdata_t *appdata, relation_item_t *relitem) {
472 relitem_context_t *context = g_new0(relitem_context_t, 1);
473 map_t *map = appdata->map;
474 g_assert(map);
475
476 context->appdata = appdata;
477 context->item = relitem;
478
479 char *str = NULL;
480 switch(relitem->type) {
481 case NODE:
482 str = g_strdup_printf(_("Relations for node #%ld"), relitem->node->id);
483 break;
484 case WAY:
485 str = g_strdup_printf(_("Relations for way #%ld"), relitem->way->id);
486 break;
487 default:
488 g_assert((relitem->type == NODE) || (relitem->type == WAY));
489 break;
490 }
491
492 context->dialog = gtk_dialog_new_with_buttons(str,
493 GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL,
494 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
495 NULL);
496 g_free(str);
497
498 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
499 GTK_RESPONSE_ACCEPT);
500
501 /* making the dialog a little wider makes it less "crowded" */
502 #ifdef USE_HILDON
503 gtk_window_set_default_size(GTK_WINDOW(context->dialog), 500, 300);
504 #else
505 gtk_window_set_default_size(GTK_WINDOW(context->dialog), 400, 200);
506 #endif
507 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
508 relation_list(context), TRUE, TRUE, 0);
509
510 /* ----------------------------------- */
511
512 gtk_widget_show_all(context->dialog);
513 gtk_dialog_run(GTK_DIALOG(context->dialog));
514 gtk_widget_destroy(context->dialog);
515
516 g_free(context);
517 }