Contents of /trunk/src/undo.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 242 - (show 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 /*
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 /*
21 * TODO:
22 *
23 * + restore node memberships when restoring a way
24 * o save restore relation membership
25 * + handle permamently deleted objects (newly created, then deleted)
26 */
27
28 #include "appdata.h"
29
30 #define UNDO_ENABLE_CHECK if(!appdata->menu_item_map_undo) return;
31
32 /* return plain text of type */
33 static char *undo_type_string(type_t type) {
34 const struct { undo_type_t type; char *name; } types[] = {
35 { UNDO_DELETE, "deletion" },
36 { UNDO_CREATE, "creation" },
37 { UNDO_MODIFY, "modification" },
38 { 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 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 static void undo_object_free(osm_t *osm, object_t *obj) {
58 printf("free obj %p\n", obj);
59
60 if(obj->ptr) {
61 char *msg = osm_object_string(obj);
62 printf(" free object %s\n", msg);
63 g_free(msg);
64 } else
65 printf(" free object %s\n", osm_object_type_string(obj));
66
67 if(obj->ptr) {
68
69 switch(obj->type) {
70 case NODE:
71 osm_node_free(osm->node_hash, NULL, obj->node);
72 break;
73
74 case WAY:
75 osm_way_free(osm->way_hash, obj->way);
76 break;
77
78 default:
79 printf("ERROR: unsupported object %s\n",
80 osm_object_type_string(obj));
81 g_assert(0);
82 break;
83 }
84 }
85
86 g_free(obj);
87 }
88
89 static void undo_op_free(osm_t *osm, undo_op_t *op) {
90 printf(" free op: %s\n", undo_type_string(op->type));
91 if(op->object) undo_object_free(osm, op->object);
92 if(op->id_chain) undo_id_chain_free(op->id_chain);
93 g_free(op);
94 }
95
96 static void undo_state_free(osm_t *osm, undo_state_t *state) {
97 printf(" free state: %s\n", undo_type_string(state->type));
98
99 if(state->name)
100 g_free(state->name);
101
102 undo_op_t *op = state->op;
103 while(op) {
104 undo_op_t *next = op->next;
105 undo_op_free(osm, op);
106 op = next;
107 }
108
109 g_free(state);
110 }
111
112 /* free all undo states, thus forgetting the entire history */
113 /* called at program exit or e.g. at project change */
114 void undo_free(osm_t *osm, undo_state_t *state) {
115 printf("Freeing all UNDO states:\n");
116
117 while(state) {
118 undo_state_t *next = state->next;
119 undo_state_free(osm, state);
120 state = next;
121 }
122 }
123
124 /* append a new state to the chain of undo states */
125 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 undo_state_t **undo_stateP = &appdata->undo.state;
131 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 undo_state_t *second = appdata->undo.state->next;
142 undo_state_free(appdata->osm, appdata->undo.state);
143 appdata->undo.state = second;
144 }
145
146 printf("UNDO: current chain length = %d\n", undo_chain_length);
147
148 return new_state;
149 }
150
151 /* copy the base object parts to another object */
152 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 OBJECT_FLAGS(*dst) = OBJECT_FLAGS(*src);
158 OBJECT_VISIBLE(*dst) = OBJECT_VISIBLE(*src);
159 OBJECT_TAG(*dst) = osm_tags_copy(OBJECT_TAG(*src));
160 }
161
162 /* create a local copy of the entire object */
163 static object_t *undo_object_save(osm_t *osm, object_t *object,
164 item_id_chain_t **id_chain) {
165
166 switch(object->type) {
167 case NODE: {
168 object_t *ob = g_new0(object_t, 1);
169 ob->type = object->type;
170
171 /* fields ignored in this copy operation: */
172 /* ways, icon_buf, map_item_chain, next */
173
174 ob->node = g_new0(node_t, 1);
175 undo_object_copy_base(ob, object);
176
177 /* copy all important parts, omitting icon pointers etc. */
178 ob->node->lpos = object->node->lpos;
179 ob->node->pos = object->node->pos;
180 ob->node->zoom_max = object->node->zoom_max;
181
182 return ob;
183 } break;
184
185 case WAY: {
186 object_t *ob = g_new0(object_t, 1);
187 ob->type = object->type;
188
189 /* fields ignored in this copy operation: */
190 /* next (XXX: incomplete) */
191
192 ob->way = g_new0(way_t, 1);
193 undo_object_copy_base(ob, object);
194
195 /* the nodes are saved by reference, since they may also be */
196 /* deleted and restored and thus their address may change */
197 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 (*id_chain)->type = NODE;
202 (*id_chain)->id = OSM_ID(node_chain->node);
203
204 id_chain = &(*id_chain)->next;
205 node_chain = node_chain->next;
206 }
207
208 return ob;
209 } break;
210
211 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 /* 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 (*id_chain)->id = osm_object_get_id(&member->object);
227
228 id_chain = &(*id_chain)->next;
229 member = member->next;
230 }
231
232 return ob;
233 } break;
234
235 default:
236 printf("unsupported object of type %s\n",
237 osm_object_type_string(object));
238
239 break;
240 }
241
242 return NULL;
243 }
244
245 void undo_append_object(appdata_t *appdata, undo_type_t type,
246 object_t *object) {
247
248 UNDO_ENABLE_CHECK;
249
250 g_assert(appdata->undo.open);
251
252 /* 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 /* a simple stand-alone node deletion is just a single */
274 /* operation on the database/map so only one undo_op is saved */
275
276 /* 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
283 printf("UNDO: object %s already in undo_state: ignoring\n",
284 osm_object_string(object));
285 return;
286 }
287 op = op->next;
288 }
289
290 printf("UNDO: saving \"%s\" operation for object %s\n",
291 undo_type_string(type), osm_object_string(object));
292
293 /* create new operation for main object */
294 op = g_new0(undo_op_t, 1);
295 op->type = type;
296 op->object = undo_object_save(appdata->osm, object, &(op->id_chain));
297
298 /* 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 /* 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 }
315
316 void undo_append_way(appdata_t *appdata, undo_type_t type, way_t *way) {
317 object_t obj = { .type = WAY };
318 obj.way = way;
319
320 undo_append_object(appdata, type, &obj);
321 }
322
323 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 void undo_open_new_state(struct appdata_s *appdata, undo_type_t type,
331 object_t *object) {
332
333 UNDO_ENABLE_CHECK;
334
335 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 appdata->undo.open->name = osm_object_get_name(object);
345 printf(" name: %s\n", appdata->undo.open->name);
346 }
347
348 void undo_close_state(appdata_t *appdata) {
349 UNDO_ENABLE_CHECK;
350
351 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 /* 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 item_id_chain_t **id_chain) {
364
365 char *msg = osm_object_string(obj);
366 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 /* 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 g_assert(OSM_FLAGS(orig) & OSM_FLAG_DELETED);
376
377 /* permanently remove the node marked as "deleted" */
378 way_chain_t *wchain =
379 osm_node_delete(appdata->osm, &appdata->icon, orig, TRUE, TRUE);
380
381 /* the deleted node must not have been part of any way */
382 g_assert(!wchain);
383 }
384
385 /* then restore old node */
386 osm_node_restore(appdata->osm, obj->node);
387
388 obj->ptr = NULL;
389 } break;
390
391 case WAY: {
392 /* 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 g_assert(OSM_FLAGS(orig) & OSM_FLAG_DELETED);
397
398 /* permanently remove the way marked as "deleted" */
399 osm_way_delete(appdata->osm, &appdata->icon, orig, TRUE);
400 }
401
402 osm_way_restore(appdata->osm, obj->way, *id_chain);
403 *id_chain = NULL;
404
405 obj->ptr = NULL;
406 } break;
407
408 default:
409 printf("Unsupported object type\n");
410 g_assert(0);
411 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 undo_operation_object_restore(appdata, op->object, &(op->id_chain));
422 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 undo_state_t *state = appdata->undo.state;
434 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 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 /* 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 undo_state_t **stateP = &appdata->undo.state;
464 while(*stateP && (*stateP)->next) stateP = &(*stateP)->next;
465
466 undo_state_free(appdata->osm, *stateP);
467 *stateP = NULL;
468
469 /* redraw entire map */
470 map_clear(appdata, MAP_LAYER_ALL);
471 map_paint(appdata);
472 }