Contents of /trunk/src/info.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: 17784 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 enum {
23 TAG_COL_KEY = 0,
24 TAG_COL_VALUE,
25 TAG_COL_COLLISION,
26 TAG_COL_DATA,
27 TAG_NUM_COLS
28 };
29
30 gboolean info_tag_key_collision(tag_t *tags, tag_t *tag) {
31 while(tags) {
32 if((tags != tag) && (strcasecmp(tags->key, tag->key) == 0))
33 return TRUE;
34
35 tags = tags->next;
36 }
37 return FALSE;
38 }
39
40 static void changed(GtkTreeSelection *treeselection, gpointer user_data) {
41 GtkWidget *list = (GtkWidget*)user_data;
42
43 GtkTreeModel *model;
44 GtkTreeIter iter;
45 gboolean selected = list_get_selected(list, &model, &iter);
46
47 if(selected) {
48 tag_t *tag;
49 gtk_tree_model_get(model, &iter, TAG_COL_DATA, &tag, -1);
50
51 /* you just cannot delete or edit the "created_by" tag */
52 if(!tag || strcasecmp(tag->key, "created_by") == 0)
53 selected = FALSE;
54 }
55
56 list_button_enable(GTK_WIDGET(list), LIST_BUTTON_REMOVE, selected);
57 list_button_enable(GTK_WIDGET(list), LIST_BUTTON_EDIT, selected);
58 }
59
60
61 static void update_collisions(GtkListStore *store, tag_t *tags) {
62 GtkTreeIter iter;
63 tag_t *tag = NULL;
64
65 /* walk the entire store to get all values */
66 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter)) {
67 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, TAG_COL_DATA, &tag, -1);
68 g_assert(tag);
69 gtk_list_store_set(store, &iter,
70 TAG_COL_COLLISION, info_tag_key_collision(tags, tag), -1);
71
72 while(gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter)) {
73 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
74 TAG_COL_DATA, &tag, -1);
75 g_assert(tag);
76 gtk_list_store_set(store, &iter,
77 TAG_COL_COLLISION, info_tag_key_collision(tags, tag), -1);
78 }
79 }
80 }
81
82 static void on_tag_remove(GtkWidget *but, tag_context_t *context) {
83 GtkTreeModel *model;
84 GtkTreeIter iter;
85
86 GtkTreeSelection *selection = list_get_selection(context->list);
87 if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
88 tag_t *tag;
89 gtk_tree_model_get(model, &iter, TAG_COL_DATA, &tag, -1);
90
91 g_assert(tag);
92
93 /* de-chain */
94 printf("de-chaining tag %s/%s\n", tag->key, tag->value);
95 tag_t **prev = context->tag;
96 while(*prev != tag) prev = &((*prev)->next);
97 *prev = tag->next;
98
99 /* free tag itself */
100 osm_tag_free(tag);
101
102 /* and remove from store */
103 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
104
105 update_collisions(context->store, *context->tag);
106 }
107 }
108
109 static gboolean tag_edit(tag_context_t *context) {
110
111 GtkTreeModel *model;
112 GtkTreeIter iter;
113 tag_t *tag;
114
115 GtkTreeSelection *sel = list_get_selection(context->list);
116 if(!sel) {
117 printf("got no selection object\n");
118 return FALSE;
119 }
120
121 if(!gtk_tree_selection_get_selected(sel, &model, &iter)) {
122 printf("nothing selected\n");
123 return FALSE;
124 }
125
126 gtk_tree_model_get(model, &iter, TAG_COL_DATA, &tag, -1);
127 printf("got %s/%s\n", tag->key, tag->value);
128
129 GtkWidget *dialog = misc_dialog_new(MISC_DIALOG_SMALL, _("Edit Tag"),
130 GTK_WINDOW(context->dialog),
131 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
132 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
133 NULL);
134
135 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
136 GTK_RESPONSE_ACCEPT);
137
138 GtkWidget *label, *key, *value;
139 GtkWidget *table = gtk_table_new(2, 2, FALSE);
140
141 gtk_table_attach(GTK_TABLE(table), label = gtk_label_new(_("Key:")),
142 0, 1, 0, 1, 0, 0, 0, 0);
143 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
144 gtk_table_attach_defaults(GTK_TABLE(table),
145 key = entry_new(), 1, 2, 0, 1);
146 gtk_entry_set_activates_default(GTK_ENTRY(key), TRUE);
147 HILDON_ENTRY_NO_AUTOCAP(key);
148
149 gtk_table_attach(GTK_TABLE(table), label = gtk_label_new(_("Value:")),
150 0, 1, 1, 2, 0, 0, 0, 0);
151 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
152 gtk_table_attach_defaults(GTK_TABLE(table),
153 value = entry_new(), 1, 2, 1, 2);
154 gtk_entry_set_activates_default(GTK_ENTRY(value), TRUE);
155 HILDON_ENTRY_NO_AUTOCAP(value);
156
157 gtk_entry_set_text(GTK_ENTRY(key), tag->key);
158 gtk_entry_set_text(GTK_ENTRY(value), tag->value);
159
160 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), table);
161
162 gtk_widget_show_all(dialog);
163
164 if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog))) {
165 free(tag->key); free(tag->value);
166 tag->key = strdup((char*)gtk_entry_get_text(GTK_ENTRY(key)));
167 tag->value = strdup((char*)gtk_entry_get_text(GTK_ENTRY(value)));
168 printf("setting %s/%s\n", tag->key, tag->value);
169
170 gtk_list_store_set(context->store, &iter,
171 TAG_COL_KEY, tag->key,
172 TAG_COL_VALUE, tag->value,
173 -1);
174
175 gtk_widget_destroy(dialog);
176
177 /* update collisions for all entries */
178 update_collisions(context->store, *context->tag);
179 return TRUE;
180 }
181
182 gtk_widget_destroy(dialog);
183 return FALSE;
184 }
185
186 static void on_tag_edit(GtkWidget *button, tag_context_t *context) {
187 tag_edit(context);
188 }
189
190 static void on_tag_last(GtkWidget *button, tag_context_t *context) {
191 static const char *type_name[] = { "illegal", "node", "way", "relation" };
192
193 if(yes_no_f(context->dialog,
194 context->appdata, MISC_AGAIN_ID_OVERWRITE_TAGS, 0,
195 _("Overwrite tags?"),
196 _("This will overwrite all tags of this %s with the "
197 "ones from the %s selected last.\n\n"
198 "Do you really want this?"),
199 type_name[context->object.type], type_name[context->object.type])) {
200
201 osm_tags_free(*context->tag);
202
203 if(context->object.type == NODE)
204 *context->tag = osm_tags_copy(context->appdata->map->last_node_tags);
205 else
206 *context->tag = osm_tags_copy(context->appdata->map->last_way_tags);
207
208 info_tags_replace(context);
209 }
210 }
211
212 static void on_tag_add(GtkWidget *button, tag_context_t *context) {
213
214 /* search end of tag chain */
215 tag_t **tag = context->tag;
216 while(*tag)
217 tag = &(*tag)->next;
218
219 /* create and append a new tag */
220 *tag = g_new0(tag_t, 1);
221 if(!*tag) {
222 errorf(GTK_WIDGET(context->appdata->window), _("Out of memory"));
223 return;
224 }
225
226 /* fill with some empty strings */
227 (*tag)->key = strdup("");
228 (*tag)->value = strdup("");
229
230 /* append a row for the new data */
231 GtkTreeIter iter;
232 gtk_list_store_append(context->store, &iter);
233 gtk_list_store_set(context->store, &iter,
234 TAG_COL_KEY, (*tag)->key,
235 TAG_COL_VALUE, (*tag)->value,
236 TAG_COL_COLLISION, FALSE,
237 TAG_COL_DATA, *tag,
238 -1);
239
240 gtk_tree_selection_select_iter(
241 list_get_selection(context->list), &iter);
242
243 if(!tag_edit(context)) {
244 printf("cancelled\n");
245 on_tag_remove(NULL, context);
246 }
247 }
248
249 void info_tags_replace(tag_context_t *context) {
250 gtk_list_store_clear(context->store);
251
252 GtkTreeIter iter;
253 tag_t *tag = *context->tag;
254 while(tag) {
255 gtk_list_store_append(context->store, &iter);
256 gtk_list_store_set(context->store, &iter,
257 TAG_COL_KEY, tag->key,
258 TAG_COL_VALUE, tag->value,
259 TAG_COL_COLLISION, info_tag_key_collision(*context->tag, tag),
260 TAG_COL_DATA, tag,
261 -1);
262 tag = tag->next;
263 }
264 }
265
266 static void on_relations(GtkWidget *button, tag_context_t *context) {
267 relation_membership_dialog(context->dialog, context->appdata,
268 &context->object);
269 }
270
271 static GtkWidget *tag_widget(tag_context_t *context) {
272 context->list = list_new(LIST_HILDON_WITHOUT_HEADERS);
273
274 list_set_static_buttons(context->list, 0, G_CALLBACK(on_tag_add),
275 G_CALLBACK(on_tag_edit), G_CALLBACK(on_tag_remove), context);
276
277 list_override_changed_event(context->list, changed, context->list);
278
279 list_set_user_buttons(context->list,
280 LIST_BUTTON_USER0, _("Last"), on_tag_last,
281 LIST_BUTTON_USER2, _("Relations"), on_relations,
282 0);
283
284 /* "relations of a relation" is something we can't handle correctly */
285 /* at the moment */
286 if(context->object.type == RELATION)
287 list_button_enable(context->list, LIST_BUTTON_USER2, FALSE);
288
289 /* setup both columns */
290 list_set_columns(context->list,
291 _("Key"), TAG_COL_KEY,
292 LIST_FLAG_ELLIPSIZE|LIST_FLAG_CAN_HIGHLIGHT, TAG_COL_COLLISION,
293 _("Value"), TAG_COL_VALUE,
294 LIST_FLAG_ELLIPSIZE,
295 NULL);
296
297 GtkWidget *presets = josm_build_presets_button(context->appdata, context);
298 if(presets)
299 list_set_custom_user_button(context->list, LIST_BUTTON_USER1, presets);
300
301 /* disable if no appropriate "last" tags have been stored or if the */
302 /* selected item isn't a node or way */
303 if(((context->object.type == NODE) &&
304 (!context->appdata->map->last_node_tags)) ||
305 ((context->object.type == WAY) &&
306 (!context->appdata->map->last_way_tags)) ||
307 ((context->object.type != NODE) && (context->object.type != WAY)))
308 list_button_enable(context->list, LIST_BUTTON_USER0, FALSE);
309
310 /* --------- build and fill the store ------------ */
311 context->store = gtk_list_store_new(TAG_NUM_COLS,
312 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
313
314 list_set_store(context->list, context->store);
315
316 GtkTreeIter iter;
317
318 tag_t *tag = *context->tag;
319 while(tag) {
320 /* Append a row and fill in some data */
321 gtk_list_store_append(context->store, &iter);
322 gtk_list_store_set(context->store, &iter,
323 TAG_COL_KEY, tag->key,
324 TAG_COL_VALUE, tag->value,
325 TAG_COL_COLLISION, info_tag_key_collision(*context->tag, tag),
326 TAG_COL_DATA, tag,
327 -1);
328 tag = tag->next;
329 }
330
331 g_object_unref(context->store);
332
333 return context->list;
334 }
335
336 static void on_relation_members(GtkWidget *but, tag_context_t *context) {
337 g_assert(context->object.type == RELATION);
338 relation_show_members(context->dialog, context->object.relation);
339 }
340
341 static void table_attach(GtkWidget *table, GtkWidget *child, int x, int y) {
342 gtk_table_attach_defaults(GTK_TABLE(table), child, x, x+1, y, y+1);
343 }
344
345 static GtkWidget *details_widget(tag_context_t *context, gboolean big) {
346 GtkWidget *table = gtk_table_new(big?4:2, 2, FALSE); // y, x
347
348 user_t *user = OBJECT_USER(context->object);
349 time_t stime = OBJECT_TIME(context->object);
350 GtkWidget *label;
351
352 /* ------------ user ----------------- */
353 if(user) {
354 if(big) table_attach(table, gtk_label_new(_("User:")), 0, 0);
355
356 label = gtk_label_new(user->name);
357 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
358 table_attach(table, label, big?1:0, 0);
359 }
360
361 /* ------------ time ----------------- */
362
363 if(big) table_attach(table, gtk_label_new(_("Date/Time:")), 0, 1);
364 struct tm *loctime = localtime(&stime);
365 char time_str[32];
366 strftime(time_str, sizeof(time_str), "%x %X", loctime);
367 label = gtk_label_new(time_str);
368 table_attach(table, label, 1, big?1:0);
369
370 /* ------------ coordinate (only for nodes) ----------------- */
371 switch(context->object.type) {
372 case NODE: {
373 char pos_str[32];
374 pos_lat_str(pos_str, sizeof(pos_str), context->object.node->pos.lat);
375 label = gtk_label_new(pos_str);
376 if(big) table_attach(table, gtk_label_new(_("Latitude:")), 0, 2);
377 table_attach(table, label, big?1:0, big?2:1);
378 pos_lat_str(pos_str, sizeof(pos_str), context->object.node->pos.lon);
379 label = gtk_label_new(pos_str);
380 if(big) table_attach(table, gtk_label_new(_("Longitude:")), 0, 3);
381 table_attach(table, label, 1, big?3:1);
382 } break;
383
384 case WAY: {
385 char *nodes_str = g_strdup_printf(_("%s%u nodes"),
386 big?"":_("Length: "),
387 osm_way_number_of_nodes(context->object.way));
388 label = gtk_label_new(nodes_str);
389 if(big) table_attach(table, gtk_label_new(_("Length:")), 0, 2);
390 table_attach(table, label, big?1:0, big?2:1);
391 g_free(nodes_str);
392
393 char *type_str = g_strdup_printf("%s (%s)",
394 (osm_way_get_last_node(context->object.way) ==
395 osm_way_get_first_node(context->object.way))?
396 "closed way":"open way",
397 (context->object.way->draw.flags & OSM_DRAW_FLAG_AREA)?
398 "area":"line");
399
400 label = gtk_label_new(type_str);
401 if(big) table_attach(table, gtk_label_new(_("Type:")), 0, 3);
402 table_attach(table, label, 1, big?3:1);
403 g_free(type_str);
404 } break;
405
406 case RELATION: {
407 /* relations tell something about their members */
408 gint nodes = 0, ways = 0, relations = 0;
409 member_t *member = context->object.relation->member;
410 while(member) {
411 switch(member->object.type) {
412 case NODE:
413 case NODE_ID:
414 nodes++;
415 break;
416 case WAY:
417 case WAY_ID:
418 ways++;
419 break;
420 case RELATION:
421 case RELATION_ID:
422 relations++;
423 break;
424
425 default:
426 break;
427 }
428
429 member = member->next;
430 }
431
432 char *str =
433 g_strdup_printf(_("Members: %d nodes, %d ways, %d relations"),
434 nodes, ways, relations);
435
436 GtkWidget *member_btn = button_new_with_label(str);
437 gtk_signal_connect(GTK_OBJECT(member_btn), "clicked",
438 GTK_SIGNAL_FUNC(on_relation_members), context);
439
440 gtk_table_attach_defaults(GTK_TABLE(table), member_btn, 0, 2,
441 big?2:1, big?4:2);
442
443 g_free(str);
444 break;
445
446 default:
447 printf("ERROR: No node, way or relation\n");
448 g_assert(0);
449 break;
450 } }
451
452 return table;
453 }
454
455 #ifdef FREMANTLE
456 /* put additional infos into a seperate dialog for fremantle as */
457 /* screen space is sparse there */
458 static void info_more(tag_context_t *context) {
459 GtkWidget *dialog =
460 misc_dialog_new(MISC_DIALOG_SMALL, _("Object details"),
461 GTK_WINDOW(context->dialog),
462 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
463 NULL);
464
465 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
466 GTK_RESPONSE_CANCEL);
467
468 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
469 details_widget(context, TRUE),
470 FALSE, FALSE, 0);
471 gtk_widget_show_all(dialog);
472 gtk_dialog_run(GTK_DIALOG(dialog));
473 gtk_widget_destroy(dialog);
474 }
475 #endif
476
477 /* edit tags of currently selected node or way or of the relation */
478 /* given */
479 gboolean info_dialog(GtkWidget *parent, appdata_t *appdata, object_t *object) {
480
481 tag_context_t *context = g_new0(tag_context_t, 1);
482 char *str = NULL;
483 tag_t *work_copy = NULL;
484
485 context->appdata = appdata;
486 context->tag = &work_copy;
487
488 /* use implicit selection if not explicitely given */
489 if(!object) {
490 g_assert((appdata->map->selected.object.type == NODE) ||
491 (appdata->map->selected.object.type == WAY) ||
492 (appdata->map->selected.object.type == RELATION));
493
494 context->object = appdata->map->selected.object;
495 } else
496 context->object = *object;
497
498 // str = osm_object_string(&context->object);
499 // str[0] = g_ascii_toupper (str[0]);
500
501 g_assert(osm_object_is_real(&context->object));
502
503 work_copy = osm_tags_copy(OBJECT_TAG(context->object));
504
505 switch(context->object.type) {
506 case NODE:
507 str = g_strdup_printf(_("Node #" ITEM_ID_FORMAT),
508 OBJECT_ID(context->object));
509 context->presets_type = PRESETS_TYPE_NODE;
510 break;
511
512 case WAY:
513 str = g_strdup_printf(_("Way #" ITEM_ID_FORMAT),
514 OBJECT_ID(context->object));
515 context->presets_type = PRESETS_TYPE_WAY;
516
517 if(osm_way_get_last_node(context->object.way) ==
518 osm_way_get_first_node(context->object.way))
519 context->presets_type |= PRESETS_TYPE_CLOSEDWAY;
520
521 break;
522
523 case RELATION:
524 str = g_strdup_printf(_("Relation #" ITEM_ID_FORMAT),
525 OBJECT_ID(context->object));
526 context->presets_type = PRESETS_TYPE_RELATION;
527 break;
528
529 default:
530 g_assert((context->object.type == NODE) ||
531 (context->object.type == WAY) ||
532 (context->object.type == RELATION));
533 break;
534 }
535
536 context->dialog = misc_dialog_new(MISC_DIALOG_LARGE, str,
537 GTK_WINDOW(parent),
538 #ifdef FREMANTLE
539 _("More"), GTK_RESPONSE_HELP,
540 #endif
541 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
542 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
543 NULL);
544 g_free(str);
545
546 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
547 GTK_RESPONSE_ACCEPT);
548
549 #ifndef FREMANTLE
550 /* -------- details box --------- */
551 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
552 details_widget(context, FALSE),
553 FALSE, FALSE, 0);
554 #endif
555
556 /* ------------ tags ----------------- */
557
558 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
559 tag_widget(context), TRUE, TRUE, 0);
560
561 /* ----------------------------------- */
562
563 gtk_widget_show_all(context->dialog);
564 gboolean ok = FALSE, quit = FALSE;
565
566 do {
567 switch(gtk_dialog_run(GTK_DIALOG(context->dialog))) {
568 case GTK_RESPONSE_ACCEPT:
569 quit = TRUE;
570 ok = TRUE;
571 break;
572 #ifdef FREMANTLE
573 case GTK_RESPONSE_HELP:
574 info_more(context);
575 break;
576 #endif
577
578 default:
579 quit = TRUE;
580 break;
581 }
582
583 } while(!quit);
584
585 gtk_widget_destroy(context->dialog);
586
587 if(ok) {
588 if(osm_object_is_real(&context->object)) {
589 osm_tags_free(OBJECT_TAG(context->object));
590 OBJECT_TAG(context->object) = osm_tags_copy(work_copy);
591 }
592
593 /* since nodes being parts of ways but with no tags are invisible, */
594 /* the result of editing them may have changed their visibility */
595 if(!object && context->object.type != RELATION)
596 map_item_redraw(appdata, &appdata->map->selected);
597
598 osm_object_set_flags(&context->object, OSM_FLAG_DIRTY, 0);
599 } else
600 osm_tags_free(work_copy);
601
602 g_free(context);
603 return ok;
604 }