Contents of /trunk/src/info.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 146 - (show annotations)
Thu Mar 26 12:35:38 2009 UTC (15 years, 1 month ago) by harbaum
File MIME type: text/plain
File size: 16736 byte(s)
Generic selection list widget
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 gboolean
41 view_selection_func(GtkTreeSelection *selection, GtkTreeModel *model,
42 GtkTreePath *path, gboolean path_currently_selected,
43 gpointer userdata) {
44 tag_context_t *context = (tag_context_t*)userdata;
45 GtkTreeIter iter;
46
47 if(gtk_tree_model_get_iter(model, &iter, path)) {
48 g_assert(gtk_tree_path_get_depth(path) == 1);
49
50 tag_t *tag;
51 gtk_tree_model_get(model, &iter, TAG_COL_DATA, &tag, -1);
52
53 /* you just cannot delete or edit the "created_by" tag */
54 if(strcasecmp(tag->key, "created_by") == 0) {
55 list_button_enable(context->list, LIST_BUTTON_REMOVE, FALSE);
56 list_button_enable(context->list, LIST_BUTTON_EDIT, FALSE);
57 } else {
58 list_button_enable(context->list, LIST_BUTTON_REMOVE, TRUE);
59 list_button_enable(context->list, LIST_BUTTON_EDIT, TRUE);
60 }
61 }
62
63 return TRUE; /* allow selection state to change */
64 }
65
66 static void update_collisions(GtkListStore *store, tag_t *tags) {
67 GtkTreeIter iter;
68 tag_t *tag = NULL;
69
70 /* walk the entire store to get all values */
71 if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter)) {
72 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, TAG_COL_DATA, &tag, -1);
73 g_assert(tag);
74 gtk_list_store_set(store, &iter,
75 TAG_COL_COLLISION, info_tag_key_collision(tags, tag), -1);
76
77 while(gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter)) {
78 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
79 TAG_COL_DATA, &tag, -1);
80 g_assert(tag);
81 gtk_list_store_set(store, &iter,
82 TAG_COL_COLLISION, info_tag_key_collision(tags, tag), -1);
83 }
84 }
85 }
86
87 static void on_tag_remove(GtkWidget *but, tag_context_t *context) {
88 GtkTreeModel *model;
89 GtkTreeIter iter;
90
91 GtkTreeSelection *selection = list_get_selection(context->list);
92 if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
93 tag_t *tag;
94 gtk_tree_model_get(model, &iter, TAG_COL_DATA, &tag, -1);
95
96 g_assert(tag);
97
98 /* de-chain */
99 printf("de-chaining tag %s/%s\n", tag->key, tag->value);
100 tag_t **prev = context->tag;
101 while(*prev != tag) prev = &((*prev)->next);
102 *prev = tag->next;
103
104 /* free tag itself */
105 osm_tag_free(tag);
106
107 /* and remove from store */
108 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
109
110 update_collisions(context->store, *context->tag);
111 }
112
113 /* disable remove and edit buttons */
114 list_button_enable(context->list, LIST_BUTTON_REMOVE, FALSE);
115 list_button_enable(context->list, LIST_BUTTON_EDIT, FALSE);
116 }
117
118 static gboolean tag_edit(tag_context_t *context) {
119
120 GtkTreeModel *model;
121 GtkTreeIter iter;
122 tag_t *tag;
123
124 GtkTreeSelection *sel = list_get_selection(context->list);
125 if(!sel) {
126 printf("got no selection object\n");
127 return FALSE;
128 }
129
130 if(!gtk_tree_selection_get_selected(sel, &model, &iter)) {
131 printf("nothing selected\n");
132 return FALSE;
133 }
134
135 gtk_tree_model_get(model, &iter, TAG_COL_DATA, &tag, -1);
136 printf("got %s/%s\n", tag->key, tag->value);
137
138 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Edit Tag"),
139 GTK_WINDOW(context->dialog), GTK_DIALOG_MODAL,
140 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
141 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
142 NULL);
143
144 #ifdef USE_HILDON
145 gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 100);
146 #else
147 gtk_window_set_default_size(GTK_WINDOW(dialog), 400, 100);
148 #endif
149
150 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
151 GTK_RESPONSE_ACCEPT);
152
153 GtkWidget *label, *key, *value;
154 GtkWidget *table = gtk_table_new(2, 2, FALSE);
155
156 gtk_table_attach(GTK_TABLE(table), label = gtk_label_new(_("Key:")),
157 0, 1, 0, 1, 0, 0, 0, 0);
158 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
159 gtk_table_attach_defaults(GTK_TABLE(table),
160 key = gtk_entry_new(), 1, 2, 0, 1);
161 gtk_entry_set_activates_default(GTK_ENTRY(key), TRUE);
162 HILDON_ENTRY_NO_AUTOCAP(key);
163
164 gtk_table_attach(GTK_TABLE(table), label = gtk_label_new(_("Value:")),
165 0, 1, 1, 2, 0, 0, 0, 0);
166 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
167 gtk_table_attach_defaults(GTK_TABLE(table),
168 value = gtk_entry_new(), 1, 2, 1, 2);
169 gtk_entry_set_activates_default(GTK_ENTRY(value), TRUE);
170 HILDON_ENTRY_NO_AUTOCAP(value);
171
172 gtk_entry_set_text(GTK_ENTRY(key), tag->key);
173 gtk_entry_set_text(GTK_ENTRY(value), tag->value);
174
175 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), table);
176
177 gtk_widget_show_all(dialog);
178
179 if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog))) {
180 free(tag->key); free(tag->value);
181 tag->key = strdup((char*)gtk_entry_get_text(GTK_ENTRY(key)));
182 tag->value = strdup((char*)gtk_entry_get_text(GTK_ENTRY(value)));
183 printf("setting %s/%s\n", tag->key, tag->value);
184
185 gtk_list_store_set(context->store, &iter,
186 TAG_COL_KEY, tag->key,
187 TAG_COL_VALUE, tag->value,
188 -1);
189
190 gtk_widget_destroy(dialog);
191
192 /* update collisions for all entries */
193 update_collisions(context->store, *context->tag);
194 return TRUE;
195 }
196
197 gtk_widget_destroy(dialog);
198 return FALSE;
199 }
200
201 static void on_tag_edit(GtkWidget *button, tag_context_t *context) {
202 tag_edit(context);
203 }
204
205 static void on_tag_last(GtkWidget *button, tag_context_t *context) {
206 static const char *type_name[] = { "illegal", "node", "way", "relation" };
207
208 if(yes_no_f(context->dialog,
209 context->appdata, MISC_AGAIN_ID_OVERWRITE_TAGS, 0,
210 _("Overwrite tags?"),
211 _("This will overwrite all tags of this %s with the "
212 "ones from the %s selected last.\n\n"
213 "Do you really want this?"),
214 type_name[context->type], type_name[context->type])) {
215
216 osm_tags_free(*context->tag);
217
218 if(context->type == NODE)
219 *context->tag = osm_tags_copy(context->appdata->map->last_node_tags, TRUE);
220 else
221 *context->tag = osm_tags_copy(context->appdata->map->last_way_tags, TRUE);
222
223 info_tags_replace(context);
224 }
225 }
226
227 static void on_tag_add(GtkWidget *button, tag_context_t *context) {
228 /* search end of tag chain */
229 tag_t **tag = context->tag;
230 while(*tag)
231 tag = &(*tag)->next;
232
233 /* create and append a new tag */
234 *tag = g_new0(tag_t, 1);
235 if(!*tag) {
236 errorf(GTK_WIDGET(context->appdata->window), _("Out of memory"));
237 return;
238 }
239
240 /* fill with some empty strings */
241 (*tag)->key = strdup("");
242 (*tag)->value = strdup("");
243
244 /* append a row for the new data */
245 GtkTreeIter iter;
246 gtk_list_store_append(context->store, &iter);
247 gtk_list_store_set(context->store, &iter,
248 TAG_COL_KEY, (*tag)->key,
249 TAG_COL_VALUE, (*tag)->value,
250 TAG_COL_COLLISION, FALSE,
251 TAG_COL_DATA, *tag,
252 -1);
253
254 gtk_tree_selection_select_iter(
255 list_get_selection(context->list), &iter);
256
257 if(!tag_edit(context)) {
258 printf("cancelled\n");
259 on_tag_remove(NULL, context);
260 }
261 }
262
263 void info_tags_replace(tag_context_t *context) {
264 gtk_list_store_clear(context->store);
265
266 GtkTreeIter iter;
267 tag_t *tag = *context->tag;
268 while(tag) {
269 gtk_list_store_append(context->store, &iter);
270 gtk_list_store_set(context->store, &iter,
271 TAG_COL_KEY, tag->key,
272 TAG_COL_VALUE, tag->value,
273 TAG_COL_COLLISION, info_tag_key_collision(*context->tag, tag),
274 TAG_COL_DATA, tag,
275 -1);
276 tag = tag->next;
277 }
278 }
279
280 static GtkWidget *tag_widget(tag_context_t *context) {
281 context->list = list_new();
282
283 list_set_static_buttons(context->list, G_CALLBACK(on_tag_add),
284 G_CALLBACK(on_tag_edit), G_CALLBACK(on_tag_remove), context);
285
286 list_set_selection_function(context->list, view_selection_func, context);
287
288 list_set_user_buttons(context->list,
289 LIST_BUTTON_USER0, _("Last..."), on_tag_last,
290 0);
291
292 /* setup both columns */
293 list_set_columns(context->list,
294 _("Key"), TAG_COL_KEY,
295 LIST_FLAG_EXPAND|LIST_FLAG_CAN_HIGHLIGHT, TAG_COL_COLLISION,
296 _("Value"), TAG_COL_VALUE,
297 LIST_FLAG_EXPAND,
298 NULL);
299
300 GtkWidget *presets = josm_presets_select(context->appdata, context);
301 if(presets)
302 list_set_custom_user_button(context->list, LIST_BUTTON_USER1, presets);
303
304 /* disable if no appropriate "last" tags have been stored or if the */
305 /* selected item isn't a node or way */
306 if(((context->type == NODE) &&
307 (!context->appdata->map->last_node_tags)) ||
308 ((context->type == WAY) &&
309 (!context->appdata->map->last_way_tags)) ||
310 ((context->type != NODE) && (context->type != WAY)))
311 list_button_enable(context->list, LIST_BUTTON_USER0, FALSE);
312
313 /* --------- build and fill the store ------------ */
314 context->store = gtk_list_store_new(TAG_NUM_COLS,
315 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
316
317 list_set_store(context->list, context->store);
318
319 GtkTreeIter iter;
320 tag_t *tag = *context->tag;
321 while(tag) {
322 /* Append a row and fill in some data */
323 gtk_list_store_append(context->store, &iter);
324 gtk_list_store_set(context->store, &iter,
325 TAG_COL_KEY, tag->key,
326 TAG_COL_VALUE, tag->value,
327 TAG_COL_COLLISION, info_tag_key_collision(*context->tag, tag),
328 TAG_COL_DATA, tag,
329 -1);
330 tag = tag->next;
331 }
332
333 g_object_unref(context->store);
334
335 return context->list;
336 }
337
338 /* edit tags of currently selected node or way or of the relation */
339 /* given */
340 gboolean info_dialog(GtkWidget *parent, appdata_t *appdata, relation_t *relation) {
341 if(!relation)
342 g_assert(appdata->map->selected.type != MAP_TYPE_ILLEGAL);
343
344 tag_context_t *context = g_new0(tag_context_t, 1);
345 user_t *user = NULL;
346 char *str = NULL;
347 time_t stime = 0;
348 tag_t *work_copy = NULL;
349
350 context->appdata = appdata;
351 context->tag = &work_copy;
352
353 if(!relation) {
354 switch(appdata->map->selected.type) {
355 case MAP_TYPE_NODE:
356 str = g_strdup_printf(_("Node #%ld"), appdata->map->selected.node->id);
357 user = appdata->map->selected.node->user;
358 work_copy = osm_tags_copy(appdata->map->selected.node->tag, FALSE);
359 stime = appdata->map->selected.node->time;
360 context->type = NODE;
361 context->presets_type = PRESETS_TYPE_NODE;
362 break;
363 case MAP_TYPE_WAY:
364 str = g_strdup_printf(_("Way #%ld"), appdata->map->selected.way->id);
365 user = appdata->map->selected.way->user;
366 work_copy = osm_tags_copy(appdata->map->selected.way->tag, FALSE);
367 stime = appdata->map->selected.way->time;
368 context->type = WAY;
369 context->presets_type = PRESETS_TYPE_WAY;
370
371 if(osm_way_get_last_node(appdata->map->selected.way) ==
372 osm_way_get_first_node(appdata->map->selected.way))
373 context->presets_type |= PRESETS_TYPE_CLOSEDWAY;
374
375 break;
376 default:
377 g_assert((appdata->map->selected.type == MAP_TYPE_NODE) ||
378 (appdata->map->selected.type == MAP_TYPE_WAY));
379 break;
380 }
381 } else {
382 str = g_strdup_printf(_("Relation #%ld"), relation->id);
383 user = relation->user;
384 work_copy = osm_tags_copy(relation->tag, FALSE);
385 stime = relation->time;
386 context->type = RELATION;
387 context->presets_type = PRESETS_TYPE_RELATION;
388 }
389
390 context->dialog = gtk_dialog_new_with_buttons(str,
391 GTK_WINDOW(parent), GTK_DIALOG_MODAL,
392 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
393 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
394 NULL);
395 g_free(str);
396
397 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
398 GTK_RESPONSE_ACCEPT);
399
400 /* making the dialog a little wider makes it less "crowded" */
401 #ifdef USE_HILDON
402 gtk_window_set_default_size(GTK_WINDOW(context->dialog), 500, 300);
403 #else
404 // Conversely, desktop builds should display a little narrower
405 gtk_window_set_default_size(GTK_WINDOW(context->dialog), 400, 300);
406 #endif
407
408 GtkWidget *label;
409 GtkWidget *table = gtk_table_new(2, 2, FALSE); // x, y
410
411 /* ------------ user ----------------- */
412 char *u_str = NULL;
413 if(user) u_str = g_strdup_printf(_("User: %s"), user->name);
414 else u_str = g_strdup_printf(_("User: ---"));
415 label = gtk_label_new(u_str);
416 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
417 g_free(u_str);
418
419 /* ------------ time ----------------- */
420
421 struct tm *loctime = localtime(&stime);
422 char time_str[32];
423 strftime(time_str, sizeof(time_str), "%x %X", loctime);
424 char *t_str = g_strdup_printf(_("Time: %s"), time_str);
425 label = gtk_label_new(t_str);
426 g_free(t_str);
427 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1);
428
429 /* ------------ coordinate (only for nodes) ----------------- */
430 if(!relation) {
431 if(appdata->map->selected.type == MAP_TYPE_NODE) {
432 char pos_str[32];
433 pos_lat_str(pos_str, sizeof(pos_str),appdata->map->selected.node->pos.lat);
434 label = gtk_label_new(pos_str);
435 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
436 pos_lat_str(pos_str, sizeof(pos_str),appdata->map->selected.node->pos.lon);
437 label = gtk_label_new(pos_str);
438 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 1, 2);
439 } else {
440 char *nodes_str = g_strdup_printf(_("Length: %u nodes"),
441 osm_way_number_of_nodes(appdata->map->selected.way));
442 label = gtk_label_new(nodes_str);
443 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
444 g_free(nodes_str);
445
446 char *type_str = g_strdup_printf("%s (%s)",
447 (osm_way_get_last_node(appdata->map->selected.way) ==
448 osm_way_get_first_node(appdata->map->selected.way))?
449 "closed way":"open way",
450 (appdata->map->selected.way->draw.flags & OSM_DRAW_FLAG_AREA)?
451 "area":"line");
452
453 label = gtk_label_new(type_str);
454 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 1, 2);
455 g_free(type_str);
456 }
457 } else {
458 /* relations tell something about their members */
459 gint nodes = 0, ways = 0, relations = 0;
460 member_t *member = relation->member;
461 while(member) {
462 switch(member->type) {
463 case NODE:
464 case NODE_ID:
465 nodes++;
466 break;
467 case WAY:
468 case WAY_ID:
469 ways++;
470 break;
471 case RELATION:
472 case RELATION_ID:
473 relations++;
474 break;
475
476 default:
477 break;
478 }
479
480 member = member->next;
481 }
482
483 char *str = g_strdup_printf(_("Members: %d nodes, %d ways, %d relations"),
484 nodes, ways, relations);
485
486 gtk_table_attach_defaults(GTK_TABLE(table), gtk_label_new(str), 0, 2, 1, 2);
487 g_free(str);
488 }
489
490 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context->dialog)->vbox), table,
491 FALSE, FALSE, 0);
492
493
494 /* ------------ tags ----------------- */
495
496 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
497 tag_widget(context), TRUE, TRUE, 0);
498
499 /* ----------------------------------- */
500
501 gtk_widget_show_all(context->dialog);
502 gboolean ok = FALSE;
503
504 if(gtk_dialog_run(GTK_DIALOG(context->dialog)) == GTK_RESPONSE_ACCEPT) {
505 ok = TRUE;
506
507 gtk_widget_destroy(context->dialog);
508
509 if(!relation) {
510 /* replace original tags with work copy */
511 switch(appdata->map->selected.type) {
512
513 case MAP_TYPE_NODE:
514 osm_tags_free(appdata->map->selected.node->tag);
515 appdata->map->selected.node->tag = osm_tags_copy(work_copy, TRUE);
516 break;
517
518 case MAP_TYPE_WAY:
519 osm_tags_free(appdata->map->selected.way->tag);
520 appdata->map->selected.way->tag = osm_tags_copy(work_copy, TRUE);
521 break;
522
523 default:
524 break;
525 }
526
527 /* since nodes being parts of ways but with no tags are invisible, */
528 /* the result of editing them may have changed their visibility */
529 map_item_redraw(appdata, &appdata->map->selected);
530 map_item_set_flags(&context->appdata->map->selected, OSM_FLAG_DIRTY, 0);
531 } else {
532 osm_tags_free(relation->tag);
533 relation->tag = osm_tags_copy(work_copy, TRUE);
534 relation->flags |= OSM_FLAG_DIRTY;
535 }
536 } else {
537 gtk_widget_destroy(context->dialog);
538 osm_tags_free(work_copy);
539 }
540
541 g_free(context);
542 return ok;
543 }