Contents of /trunk/src/undo.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 242 - (hide annotations)
Fri Jul 24 09:48:42 2009 UTC (14 years, 10 months ago) by harbaum
File MIME type: text/plain
File size: 12637 byte(s)
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 }