Parent Directory | Revision Log
Fremantleized relation membership dialog
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 xyz */ |
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 | /* scroll to end */ |
786 | // GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(); |
787 | /* xyz */ |
788 | } |
789 | } |
790 | |
791 | /* user clicked "edit..." button in relation list */ |
792 | static void on_relation_edit(GtkWidget *but, relation_context_t *context) { |
793 | relation_t *sel = get_selected_relation(context); |
794 | if(!sel) return; |
795 | |
796 | printf("edit relation #" ITEM_ID_FORMAT "\n", OSM_ID(sel)); |
797 | |
798 | if (!relation_info_dialog(context->dialog, context->appdata, sel)) |
799 | return; |
800 | |
801 | // Locate the changed item |
802 | GtkTreeIter iter; |
803 | gboolean valid = gtk_tree_model_get_iter_first( |
804 | GTK_TREE_MODEL(context->store), &iter); |
805 | while (valid) { |
806 | relation_t *row_rel; |
807 | gtk_tree_model_get(GTK_TREE_MODEL(context->store), &iter, |
808 | RELATION_COL_DATA, &row_rel, |
809 | -1); |
810 | if (row_rel == sel) |
811 | break; |
812 | valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(context->store), &iter); |
813 | } |
814 | if (!valid) |
815 | return; |
816 | |
817 | // Found it. Update all visible fields. |
818 | gtk_list_store_set(context->store, &iter, |
819 | RELATION_COL_TYPE, osm_tag_get_by_key(OSM_TAG(sel), "type"), |
820 | RELATION_COL_NAME, relation_get_descriptive_name(sel), |
821 | RELATION_COL_MEMBERS, osm_relation_members_num(sel), |
822 | -1); |
823 | |
824 | // Order will probably have changed, so refocus |
825 | list_focus_on(context->list, &iter, TRUE); |
826 | } |
827 | |
828 | |
829 | /* remove the selected relation */ |
830 | static void on_relation_remove(GtkWidget *but, relation_context_t *context) { |
831 | relation_t *sel = get_selected_relation(context); |
832 | if(!sel) return; |
833 | |
834 | printf("remove relation #" ITEM_ID_FORMAT "\n", OSM_ID(sel)); |
835 | |
836 | gint members = osm_relation_members_num(sel); |
837 | |
838 | if(members) |
839 | if(!yes_no_f(context->dialog, NULL, 0, 0, |
840 | _("Delete non-empty relation?"), |
841 | _("This relation still has %d members. " |
842 | "Delete it anyway?"), members)) |
843 | return; |
844 | |
845 | /* first remove selected row from list */ |
846 | GtkTreeIter iter; |
847 | GtkTreeModel *model; |
848 | if(list_get_selected(context->list, &model, &iter)) |
849 | gtk_list_store_remove(context->store, &iter); |
850 | |
851 | /* then really delete it */ |
852 | osm_relation_delete(context->appdata->osm, sel, FALSE); |
853 | |
854 | relation_list_selected(context, NULL); |
855 | } |
856 | |
857 | static GtkWidget *relation_list_widget(relation_context_t *context) { |
858 | context->list = list_new(LIST_HILDON_WITH_HEADERS); |
859 | |
860 | list_set_selection_function(context->list, relation_list_selection_func, |
861 | context); |
862 | |
863 | list_set_columns(context->list, |
864 | _("Name"), RELATION_COL_NAME, LIST_FLAG_ELLIPSIZE, |
865 | _("Type"), RELATION_COL_TYPE, 0, |
866 | _("Members"), RELATION_COL_MEMBERS, 0, |
867 | NULL); |
868 | |
869 | /* build and fill the store */ |
870 | context->store = gtk_list_store_new(RELATION_NUM_COLS, |
871 | G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, |
872 | G_TYPE_POINTER); |
873 | |
874 | list_set_store(context->list, context->store); |
875 | |
876 | // Sorting by ref/name by default is useful for places with lots of numbered |
877 | // bus routes. Especially for small screens. |
878 | gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(context->store), |
879 | RELATION_COL_NAME, GTK_SORT_ASCENDING); |
880 | |
881 | GtkTreeIter iter; |
882 | relation_t *relation = NULL; |
883 | relation_chain_t *rchain = NULL; |
884 | |
885 | if(context->object) |
886 | rchain = osm_object_to_relation(context->appdata->osm, context->object); |
887 | else |
888 | relation = context->appdata->osm->relation; |
889 | |
890 | while(relation || rchain) { |
891 | relation_t *rel = relation?relation:rchain->relation; |
892 | |
893 | char *name = relation_get_descriptive_name(rel); |
894 | guint num = osm_relation_members_num(rel); |
895 | |
896 | /* Append a row and fill in some data */ |
897 | gtk_list_store_append(context->store, &iter); |
898 | gtk_list_store_set(context->store, &iter, |
899 | RELATION_COL_TYPE, |
900 | osm_tag_get_by_key(OSM_TAG(rel), "type"), |
901 | RELATION_COL_NAME, name, |
902 | RELATION_COL_MEMBERS, num, |
903 | RELATION_COL_DATA, rel, |
904 | -1); |
905 | |
906 | if(relation) relation = relation->next; |
907 | if(rchain) rchain = rchain->next; |
908 | } |
909 | |
910 | if(rchain) |
911 | osm_relation_chain_free(rchain); |
912 | |
913 | g_object_unref(context->store); |
914 | |
915 | list_set_static_buttons(context->list, LIST_BTN_NEW | LIST_BTN_WIDE, |
916 | G_CALLBACK(on_relation_add), G_CALLBACK(on_relation_edit), |
917 | G_CALLBACK(on_relation_remove), context); |
918 | |
919 | list_set_user_buttons(context->list, |
920 | LIST_BUTTON_USER0, _("Members"), G_CALLBACK(on_relation_members), |
921 | LIST_BUTTON_USER1, _("Select"), G_CALLBACK(on_relation_select), |
922 | 0); |
923 | |
924 | relation_list_selected(context, NULL); |
925 | |
926 | return context->list; |
927 | } |
928 | |
929 | /* a global view on all relations */ |
930 | void relation_list(GtkWidget *parent, appdata_t *appdata, object_t *object) { |
931 | relation_context_t *context = g_new0(relation_context_t, 1); |
932 | context->appdata = appdata; |
933 | |
934 | char *str = NULL; |
935 | if(!object) |
936 | str = g_strdup(_("All relations")); |
937 | else { |
938 | str = g_strdup_printf(_("Relations of %s"), osm_object_string(object)); |
939 | context->object = object; |
940 | } |
941 | |
942 | context->dialog = |
943 | misc_dialog_new(MISC_DIALOG_LARGE, str, |
944 | GTK_WINDOW(parent), |
945 | GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, |
946 | NULL); |
947 | |
948 | g_free(str); |
949 | |
950 | gtk_dialog_set_default_response(GTK_DIALOG(context->dialog), |
951 | GTK_RESPONSE_CLOSE); |
952 | |
953 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context->dialog)->vbox), |
954 | relation_list_widget(context), TRUE, TRUE, 0); |
955 | |
956 | /* ----------------------------------- */ |
957 | |
958 | |
959 | gtk_widget_show_all(context->dialog); |
960 | gtk_dialog_run(GTK_DIALOG(context->dialog)); |
961 | |
962 | gtk_widget_destroy(context->dialog); |
963 | g_free(context); |
964 | } |
965 | |
966 | // vim:et:ts=8:sw=2:sts=2:ai |