Parent Directory
|
Revision Log
Another Fremantle submenu redesign
1 | harbaum | 54 | /* |
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 | harbaum | 237 | /* |
21 | * TODO: | ||
22 | * | ||
23 | harbaum | 238 | * + restore node memberships when restoring a way |
24 | harbaum | 239 | * o save restore relation membership |
25 | * + handle permamently deleted objects (newly created, then deleted) | ||
26 | harbaum | 237 | */ |
27 | |||
28 | harbaum | 54 | #include "appdata.h" |
29 | |||
30 | harbaum | 236 | #define UNDO_ENABLE_CHECK if(!appdata->menu_item_map_undo) return; |
31 | |||
32 | harbaum | 64 | /* return plain text of type */ |
33 | harbaum | 238 | static char *undo_type_string(type_t type) { |
34 | harbaum | 64 | const struct { undo_type_t type; char *name; } types[] = { |
35 | harbaum | 238 | { UNDO_DELETE, "deletion" }, |
36 | { UNDO_CREATE, "creation" }, | ||
37 | { UNDO_MODIFY, "modification" }, | ||
38 | harbaum | 64 | { 0, NULL } |
39 | }; | ||
40 | |||
41 | int i; | ||
42 | for(i=0;types[i].name;i++) | ||
43 | if(type == types[i].type) | ||
44 | return types[i].name; | ||
45 | |||
46 | return NULL; | ||
47 | } | ||
48 | |||
49 | harbaum | 240 | static void undo_id_chain_free(item_id_chain_t *chain) { |
50 | while(chain) { | ||
51 | item_id_chain_t *next = chain->next; | ||
52 | g_free(chain); | ||
53 | chain = next; | ||
54 | } | ||
55 | } | ||
56 | |||
57 | harbaum | 238 | static void undo_object_free(osm_t *osm, object_t *obj) { |
58 | harbaum | 237 | printf("free obj %p\n", obj); |
59 | |||
60 | harbaum | 235 | if(obj->ptr) { |
61 | char *msg = osm_object_string(obj); | ||
62 | harbaum | 237 | printf(" free object %s\n", msg); |
63 | harbaum | 235 | g_free(msg); |
64 | } else | ||
65 | harbaum | 237 | printf(" free object %s\n", osm_object_type_string(obj)); |
66 | harbaum | 235 | |
67 | if(obj->ptr) { | ||
68 | harbaum | 64 | |
69 | switch(obj->type) { | ||
70 | case NODE: | ||
71 | harbaum | 239 | osm_node_free(osm->node_hash, NULL, obj->node); |
72 | harbaum | 64 | break; |
73 | harbaum | 69 | |
74 | case WAY: | ||
75 | harbaum | 239 | osm_way_free(osm->way_hash, obj->way); |
76 | harbaum | 69 | break; |
77 | harbaum | 64 | |
78 | default: | ||
79 | printf("ERROR: unsupported object %s\n", | ||
80 | harbaum | 155 | osm_object_type_string(obj)); |
81 | harbaum | 64 | g_assert(0); |
82 | break; | ||
83 | } | ||
84 | } | ||
85 | |||
86 | g_free(obj); | ||
87 | } | ||
88 | |||
89 | harbaum | 238 | static void undo_op_free(osm_t *osm, undo_op_t *op) { |
90 | harbaum | 237 | printf(" free op: %s\n", undo_type_string(op->type)); |
91 | harbaum | 240 | if(op->object) undo_object_free(osm, op->object); |
92 | if(op->id_chain) undo_id_chain_free(op->id_chain); | ||
93 | harbaum | 64 | g_free(op); |
94 | } | ||
95 | |||
96 | harbaum | 238 | static void undo_state_free(osm_t *osm, undo_state_t *state) { |
97 | harbaum | 237 | printf(" free state: %s\n", undo_type_string(state->type)); |
98 | harbaum | 64 | |
99 | harbaum | 237 | if(state->name) |
100 | g_free(state->name); | ||
101 | harbaum | 235 | |
102 | harbaum | 64 | undo_op_t *op = state->op; |
103 | while(op) { | ||
104 | undo_op_t *next = op->next; | ||
105 | harbaum | 238 | undo_op_free(osm, op); |
106 | harbaum | 64 | op = next; |
107 | } | ||
108 | |||
109 | harbaum | 54 | g_free(state); |
110 | } | ||
111 | |||
112 | harbaum | 64 | /* free all undo states, thus forgetting the entire history */ |
113 | /* called at program exit or e.g. at project change */ | ||
114 | harbaum | 238 | void undo_free(osm_t *osm, undo_state_t *state) { |
115 | harbaum | 64 | printf("Freeing all UNDO states:\n"); |
116 | |||
117 | harbaum | 54 | while(state) { |
118 | undo_state_t *next = state->next; | ||
119 | harbaum | 238 | undo_state_free(osm, state); |
120 | harbaum | 54 | state = next; |
121 | } | ||
122 | } | ||
123 | |||
124 | harbaum | 64 | /* append a new state to the chain of undo states */ |
125 | harbaum | 54 | static undo_state_t *undo_append_state(appdata_t *appdata) { |
126 | undo_state_t *new_state = NULL; | ||
127 | |||
128 | /* create new undo state at end of undo chain */ | ||
129 | int undo_chain_length = 0; | ||
130 | harbaum | 68 | undo_state_t **undo_stateP = &appdata->undo.state; |
131 | harbaum | 54 | while(*undo_stateP) { |
132 | undo_chain_length++; | ||
133 | undo_stateP = &(*undo_stateP)->next; | ||
134 | } | ||
135 | |||
136 | /* append new entry to chain */ | ||
137 | new_state = *undo_stateP = g_new0(undo_state_t, 1); | ||
138 | |||
139 | /* delete first entry if the chain is too long */ | ||
140 | if(undo_chain_length >= UNDO_QUEUE_LEN) { | ||
141 | harbaum | 68 | undo_state_t *second = appdata->undo.state->next; |
142 | harbaum | 238 | undo_state_free(appdata->osm, appdata->undo.state); |
143 | harbaum | 68 | appdata->undo.state = second; |
144 | harbaum | 54 | } |
145 | |||
146 | printf("UNDO: current chain length = %d\n", undo_chain_length); | ||
147 | |||
148 | return new_state; | ||
149 | } | ||
150 | |||
151 | harbaum | 240 | /* copy the base object parts to another object */ |
152 | harbaum | 239 | static void undo_object_copy_base(object_t *dst, object_t *src) { |
153 | OBJECT_ID(*dst) = OBJECT_ID(*src); | ||
154 | OBJECT_VERSION(*dst) = OBJECT_VERSION(*src); | ||
155 | OBJECT_TIME(*dst) = OBJECT_TIME(*src); | ||
156 | OBJECT_USER(*dst) = OBJECT_USER(*src); | ||
157 | harbaum | 240 | OBJECT_FLAGS(*dst) = OBJECT_FLAGS(*src); |
158 | OBJECT_VISIBLE(*dst) = OBJECT_VISIBLE(*src); | ||
159 | harbaum | 242 | OBJECT_TAG(*dst) = osm_tags_copy(OBJECT_TAG(*src)); |
160 | harbaum | 239 | } |
161 | |||
162 | harbaum | 64 | /* create a local copy of the entire object */ |
163 | harbaum | 239 | static object_t *undo_object_save(osm_t *osm, object_t *object, |
164 | item_id_chain_t **id_chain) { | ||
165 | |||
166 | harbaum | 155 | switch(object->type) { |
167 | harbaum | 64 | case NODE: { |
168 | harbaum | 155 | object_t *ob = g_new0(object_t, 1); |
169 | ob->type = object->type; | ||
170 | harbaum | 69 | |
171 | harbaum | 64 | /* fields ignored in this copy operation: */ |
172 | /* ways, icon_buf, map_item_chain, next */ | ||
173 | |||
174 | harbaum | 155 | ob->node = g_new0(node_t, 1); |
175 | harbaum | 239 | undo_object_copy_base(ob, object); |
176 | |||
177 | harbaum | 64 | /* copy all important parts, omitting icon pointers etc. */ |
178 | harbaum | 155 | ob->node->lpos = object->node->lpos; |
179 | ob->node->pos = object->node->pos; | ||
180 | ob->node->zoom_max = object->node->zoom_max; | ||
181 | harbaum | 69 | |
182 | return ob; | ||
183 | harbaum | 64 | } break; |
184 | |||
185 | harbaum | 69 | case WAY: { |
186 | harbaum | 155 | object_t *ob = g_new0(object_t, 1); |
187 | ob->type = object->type; | ||
188 | harbaum | 69 | |
189 | /* fields ignored in this copy operation: */ | ||
190 | harbaum | 239 | /* next (XXX: incomplete) */ |
191 | harbaum | 69 | |
192 | harbaum | 155 | ob->way = g_new0(way_t, 1); |
193 | harbaum | 239 | undo_object_copy_base(ob, object); |
194 | |||
195 | harbaum | 237 | /* the nodes are saved by reference, since they may also be */ |
196 | /* deleted and restored and thus their address may change */ | ||
197 | harbaum | 239 | node_chain_t *node_chain = object->way->node_chain; |
198 | g_assert(id_chain); | ||
199 | while(node_chain) { | ||
200 | *id_chain = g_new0(item_id_chain_t, 1); | ||
201 | harbaum | 240 | (*id_chain)->type = NODE; |
202 | harbaum | 239 | (*id_chain)->id = OSM_ID(node_chain->node); |
203 | harbaum | 237 | |
204 | harbaum | 239 | id_chain = &(*id_chain)->next; |
205 | node_chain = node_chain->next; | ||
206 | harbaum | 237 | } |
207 | |||
208 | harbaum | 69 | return ob; |
209 | } break; | ||
210 | |||
211 | harbaum | 239 | case RELATION: { |
212 | object_t *ob = g_new0(object_t, 1); | ||
213 | ob->type = object->type; | ||
214 | |||
215 | /* fields ignored in this copy operation: */ | ||
216 | /* next */ | ||
217 | |||
218 | ob->relation = g_new0(relation_t, 1); | ||
219 | undo_object_copy_base(ob, object); | ||
220 | |||
221 | harbaum | 240 | /* save members reference */ |
222 | member_t *member = object->relation->member; | ||
223 | while(member) { | ||
224 | *id_chain = g_new0(item_id_chain_t, 1); | ||
225 | (*id_chain)->type = member->object.type; | ||
226 | harbaum | 242 | (*id_chain)->id = osm_object_get_id(&member->object); |
227 | harbaum | 239 | |
228 | harbaum | 240 | id_chain = &(*id_chain)->next; |
229 | member = member->next; | ||
230 | } | ||
231 | harbaum | 239 | |
232 | return ob; | ||
233 | } break; | ||
234 | |||
235 | harbaum | 64 | default: |
236 | harbaum | 239 | printf("unsupported object of type %s\n", |
237 | harbaum | 155 | osm_object_type_string(object)); |
238 | harbaum | 239 | |
239 | harbaum | 64 | break; |
240 | } | ||
241 | |||
242 | harbaum | 69 | return NULL; |
243 | harbaum | 64 | } |
244 | |||
245 | harbaum | 235 | void undo_append_object(appdata_t *appdata, undo_type_t type, |
246 | object_t *object) { | ||
247 | harbaum | 69 | |
248 | harbaum | 236 | UNDO_ENABLE_CHECK; |
249 | harbaum | 69 | |
250 | harbaum | 235 | g_assert(appdata->undo.open); |
251 | |||
252 | harbaum | 239 | /* deleting an object will affect all relations it's part of */ |
253 | /* therefore handle them first and save their state to undo */ | ||
254 | /* the modifications */ | ||
255 | if(type == UNDO_DELETE) { | ||
256 | relation_chain_t *rchain = | ||
257 | osm_object_to_relation(appdata->osm, object); | ||
258 | |||
259 | while(rchain) { | ||
260 | relation_chain_t *next = rchain->next; | ||
261 | object_t obj = { .type = RELATION }; | ||
262 | obj.relation = rchain->relation; | ||
263 | |||
264 | /* store relation modification as undo operation by recursing */ | ||
265 | /* into this */ | ||
266 | undo_append_object(appdata, UNDO_MODIFY, &obj); | ||
267 | |||
268 | g_free(rchain); | ||
269 | rchain = next; | ||
270 | } | ||
271 | } | ||
272 | |||
273 | harbaum | 64 | /* a simple stand-alone node deletion is just a single */ |
274 | /* operation on the database/map so only one undo_op is saved */ | ||
275 | harbaum | 235 | |
276 | harbaum | 237 | /* check if this object already is in operaton chain */ |
277 | undo_op_t *op = appdata->undo.open->op; | ||
278 | while(op) { | ||
279 | if(osm_object_is_same(op->object, object)) { | ||
280 | /* this must be the same operation!! */ | ||
281 | g_assert(op->type == type); | ||
282 | harbaum | 236 | |
283 | printf("UNDO: object %s already in undo_state: ignoring\n", | ||
284 | osm_object_string(object)); | ||
285 | return; | ||
286 | } | ||
287 | harbaum | 237 | op = op->next; |
288 | harbaum | 236 | } |
289 | |||
290 | harbaum | 239 | printf("UNDO: saving \"%s\" operation for object %s\n", |
291 | harbaum | 236 | undo_type_string(type), osm_object_string(object)); |
292 | |||
293 | harbaum | 239 | /* create new operation for main object */ |
294 | harbaum | 237 | op = g_new0(undo_op_t, 1); |
295 | op->type = type; | ||
296 | harbaum | 239 | op->object = undo_object_save(appdata->osm, object, &(op->id_chain)); |
297 | harbaum | 236 | |
298 | harbaum | 237 | /* prepend operation to chain, so that the undo works in reverse order */ |
299 | op->next = appdata->undo.open->op; | ||
300 | appdata->undo.open->op = op; | ||
301 | |||
302 | harbaum | 236 | /* if the deleted object is a way, then check if this affects */ |
303 | /* a node */ | ||
304 | if((type == UNDO_DELETE) && (object->type == WAY)) { | ||
305 | node_chain_t *chain = object->way->node_chain; | ||
306 | while(chain) { | ||
307 | /* this node must only be part of this way */ | ||
308 | if(!osm_node_in_other_way(appdata->osm, object->way, chain->node)) | ||
309 | undo_append_node(appdata, UNDO_DELETE, chain->node); | ||
310 | |||
311 | chain = chain->next; | ||
312 | } | ||
313 | } | ||
314 | harbaum | 54 | } |
315 | harbaum | 64 | |
316 | harbaum | 235 | void undo_append_way(appdata_t *appdata, undo_type_t type, way_t *way) { |
317 | harbaum | 236 | object_t obj = { .type = WAY }; |
318 | harbaum | 235 | obj.way = way; |
319 | |||
320 | undo_append_object(appdata, type, &obj); | ||
321 | } | ||
322 | |||
323 | harbaum | 236 | void undo_append_node(appdata_t *appdata, undo_type_t type, node_t *node) { |
324 | object_t obj = { .type = NODE }; | ||
325 | obj.node = node; | ||
326 | |||
327 | undo_append_object(appdata, type, &obj); | ||
328 | } | ||
329 | |||
330 | harbaum | 235 | void undo_open_new_state(struct appdata_s *appdata, undo_type_t type, |
331 | object_t *object) { | ||
332 | harbaum | 236 | |
333 | UNDO_ENABLE_CHECK; | ||
334 | |||
335 | harbaum | 235 | g_assert(!appdata->undo.open); |
336 | |||
337 | printf("UNDO: open new state for %s\n", | ||
338 | osm_object_string(object)); | ||
339 | |||
340 | /* create a new undo state */ | ||
341 | appdata->undo.open = undo_append_state(appdata); | ||
342 | appdata->undo.open->type = type; | ||
343 | |||
344 | harbaum | 237 | appdata->undo.open->name = osm_object_get_name(object); |
345 | printf(" name: %s\n", appdata->undo.open->name); | ||
346 | harbaum | 235 | } |
347 | |||
348 | void undo_close_state(appdata_t *appdata) { | ||
349 | harbaum | 236 | UNDO_ENABLE_CHECK; |
350 | |||
351 | harbaum | 235 | g_assert(appdata->undo.open); |
352 | |||
353 | printf("UNDO: closing state\n"); | ||
354 | |||
355 | appdata->undo.open = NULL; | ||
356 | } | ||
357 | |||
358 | |||
359 | /* --------------------- restoring ---------------------- */ | ||
360 | |||
361 | harbaum | 239 | /* restore state of an object (or even restore it after deletion) */ |
362 | static void undo_operation_object_restore(appdata_t *appdata, object_t *obj, | ||
363 | harbaum | 240 | item_id_chain_t **id_chain) { |
364 | harbaum | 64 | |
365 | harbaum | 155 | char *msg = osm_object_string(obj); |
366 | harbaum | 64 | printf("UNDO deletion of object %s\n", msg); |
367 | g_free(msg); | ||
368 | |||
369 | switch(obj->type) { | ||
370 | case NODE: { | ||
371 | /* there must be an "deleted" entry which needs to be */ | ||
372 | harbaum | 239 | /* removed or no entry at all (since a new one has been deleted) */ |
373 | node_t *orig = osm_get_node_by_id(appdata->osm, OBJECT_ID(*obj)); | ||
374 | if(orig) { | ||
375 | harbaum | 240 | g_assert(OSM_FLAGS(orig) & OSM_FLAG_DELETED); |
376 | harbaum | 237 | |
377 | harbaum | 239 | /* permanently remove the node marked as "deleted" */ |
378 | way_chain_t *wchain = | ||
379 | osm_node_delete(appdata->osm, &appdata->icon, orig, TRUE, TRUE); | ||
380 | harbaum | 237 | |
381 | harbaum | 239 | /* the deleted node must not have been part of any way */ |
382 | g_assert(!wchain); | ||
383 | } | ||
384 | harbaum | 64 | |
385 | /* then restore old node */ | ||
386 | harbaum | 155 | osm_node_restore(appdata->osm, obj->node); |
387 | harbaum | 238 | |
388 | harbaum | 155 | obj->ptr = NULL; |
389 | harbaum | 64 | } break; |
390 | |||
391 | harbaum | 235 | case WAY: { |
392 | harbaum | 239 | /* there must be an "deleted" entry which needs to be */ |
393 | /* removed or no entry at all (since a new one has been deleted) */ | ||
394 | way_t *orig = osm_get_way_by_id(appdata->osm, OBJECT_ID(*obj)); | ||
395 | if(orig) { | ||
396 | harbaum | 240 | g_assert(OSM_FLAGS(orig) & OSM_FLAG_DELETED); |
397 | harbaum | 237 | |
398 | harbaum | 239 | /* permanently remove the way marked as "deleted" */ |
399 | osm_way_delete(appdata->osm, &appdata->icon, orig, TRUE); | ||
400 | } | ||
401 | harbaum | 237 | |
402 | harbaum | 240 | osm_way_restore(appdata->osm, obj->way, *id_chain); |
403 | *id_chain = NULL; | ||
404 | harbaum | 237 | |
405 | obj->ptr = NULL; | ||
406 | harbaum | 235 | } break; |
407 | |||
408 | harbaum | 64 | default: |
409 | printf("Unsupported object type\n"); | ||
410 | harbaum | 235 | g_assert(0); |
411 | harbaum | 64 | break; |
412 | } | ||
413 | } | ||
414 | |||
415 | /* undo a single operation */ | ||
416 | static void undo_operation(appdata_t *appdata, undo_op_t *op) { | ||
417 | printf("UNDO operation: %s\n", undo_type_string(op->type)); | ||
418 | |||
419 | switch(op->type) { | ||
420 | case UNDO_DELETE: | ||
421 | harbaum | 240 | undo_operation_object_restore(appdata, op->object, &(op->id_chain)); |
422 | harbaum | 64 | break; |
423 | |||
424 | default: | ||
425 | printf("unsupported UNDO operation\n"); | ||
426 | g_assert(0); | ||
427 | break; | ||
428 | } | ||
429 | } | ||
430 | |||
431 | /* undo the last undo_state */ | ||
432 | void undo(appdata_t *appdata) { | ||
433 | harbaum | 68 | undo_state_t *state = appdata->undo.state; |
434 | harbaum | 64 | printf("user selected undo\n"); |
435 | |||
436 | /* search last (newest) entry */ | ||
437 | while(state && state->next) state = state->next; | ||
438 | |||
439 | if(!state) { | ||
440 | banner_show_info(appdata, _("No further undo data")); | ||
441 | return; | ||
442 | } | ||
443 | |||
444 | |||
445 | printf("UNDO state: %s\n", undo_type_string(state->type)); | ||
446 | |||
447 | harbaum | 238 | char *msg = g_strdup_printf(_("Undoing %s of %s"), |
448 | undo_type_string(state->type), | ||
449 | state->name); | ||
450 | banner_show_info(appdata, msg); | ||
451 | g_free(msg); | ||
452 | |||
453 | harbaum | 64 | /* since the operations list was built by prepending new */ |
454 | /* entries, just going through the list will run the operations */ | ||
455 | /* in reverse order. That's exactly what we want! */ | ||
456 | undo_op_t *op = state->op; | ||
457 | while(op) { | ||
458 | undo_operation(appdata, op); | ||
459 | op = op->next; | ||
460 | } | ||
461 | |||
462 | /* remove this entry from chain */ | ||
463 | harbaum | 68 | undo_state_t **stateP = &appdata->undo.state; |
464 | harbaum | 64 | while(*stateP && (*stateP)->next) stateP = &(*stateP)->next; |
465 | |||
466 | harbaum | 238 | undo_state_free(appdata->osm, *stateP); |
467 | harbaum | 64 | *stateP = NULL; |
468 | harbaum | 238 | |
469 | /* redraw entire map */ | ||
470 | map_clear(appdata, MAP_LAYER_ALL); | ||
471 | map_paint(appdata); | ||
472 | harbaum | 64 | } |