QOF 0.8.4
|
00001 /*************************************************************************** 00002 * qofundo.c 00003 * 00004 * Thu Aug 25 09:19:17 2005 00005 * Copyright 2005,2006,2008 Neil Williams 00006 * linux@codehelp.co.uk 00007 ****************************************************************************/ 00008 /* 00009 * This program is free software; you can redistribute it and/or modify 00010 * it under the terms of the GNU General Public License as published by 00011 * the Free Software Foundation; either version 2 of the License, or 00012 * (at your option) any later version. 00013 * 00014 * This program is distributed in the hope that it will be useful, 00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00017 * GNU General Public License for more details. 00018 * 00019 * You should have received a copy of the GNU General Public License 00020 * along with this program; if not, write to the Free Software 00021 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA 00022 */ 00023 00024 #include "config.h" 00025 #include <glib.h> 00026 #include <qof.h> 00027 #include <stdio.h> 00028 #include <stdlib.h> 00029 #include <libintl.h> 00030 #include <locale.h> 00031 #include <errno.h> 00032 #include "qofbook-p.h" 00033 #include "qofundo-p.h" 00034 #include "qofundo.h" 00035 00036 static QofLogModule log_module = QOF_MOD_UNDO; 00037 00038 typedef enum 00039 { 00040 UNDO_NOOP = 0, 00041 UNDO_CREATE, 00042 UNDO_DELETE, 00043 UNDO_MODIFY 00044 } QofUndoAction; 00045 00046 struct QofUndoEntity_t 00047 { 00048 const QofParam *param; /* static anyway so only store a pointer */ 00049 const GUID *guid; /* enable re-creation of this entity */ 00050 QofIdType type; /* ditto param, static. */ 00051 gchar *value; /* cached string? */ 00052 gchar *path; /* for KVP */ 00053 QofIdType choice; /* For QOF_TYPE_CHOICE */ 00054 QofUndoAction how; /* how to act on the undo */ 00055 }; 00056 00057 struct QofUndoOperation_t 00058 { 00059 const gchar *label; 00060 QofTime *qt; 00061 GList *entity_list; /* GList of qof_undo_entity* */ 00062 }; 00063 00064 static void 00065 set_param (QofEntity * ent, const QofParam * param, 00066 gchar * value) 00067 { 00068 gchar *tail; 00069 QofNumeric cli_numeric; 00070 gboolean cli_bool; 00071 gint32 cli_i32; 00072 gint64 cli_i64; 00073 QofTime *cli_time; 00074 GUID *cm_guid; 00075 void (*string_setter) (QofEntity *, gchar *); 00076 void (*time_setter) (QofEntity *, QofTime *); 00077 void (*i32_setter) (QofEntity *, gint32); 00078 void (*i64_setter) (QofEntity *, gint64); 00079 void (*numeric_setter) (QofEntity *, QofNumeric); 00080 void (*boolean_setter) (QofEntity *, gboolean); 00081 void (*guid_setter) (QofEntity *, const GUID *); 00082 00083 if (0 == safe_strcmp (param->param_type, QOF_TYPE_STRING)) 00084 { 00085 string_setter = 00086 (void (*)(QofEntity *, gchar *)) param->param_setfcn; 00087 if (string_setter) 00088 { 00089 param->param_setfcn (ent, value); 00090 } 00091 } 00092 if (0 == safe_strcmp (param->param_type, QOF_TYPE_GUID)) 00093 { 00094 cm_guid = g_new (GUID, 1); 00095 if (TRUE == string_to_guid (value, cm_guid)) 00096 { 00097 guid_setter = 00098 (void (*)(QofEntity *, const GUID *)) param->param_setfcn; 00099 if (guid_setter != NULL) 00100 { 00101 guid_setter (ent, cm_guid); 00102 } 00103 } 00104 } 00105 if ((0 == safe_strcmp (param->param_type, QOF_TYPE_NUMERIC)) || 00106 (safe_strcmp (param->param_type, QOF_TYPE_DEBCRED) == 0)) 00107 { 00108 numeric_setter = 00109 (void (*)(QofEntity *, QofNumeric)) param->param_setfcn; 00110 qof_numeric_from_string (value, &cli_numeric); 00111 if (numeric_setter != NULL) 00112 { 00113 numeric_setter (ent, cli_numeric); 00114 } 00115 } 00116 if (0 == safe_strcmp (param->param_type, QOF_TYPE_BOOLEAN)) 00117 { 00118 cli_bool = FALSE; 00119 if (qof_util_bool_to_int (value) == 1) 00120 { 00121 cli_bool = TRUE; 00122 } 00123 boolean_setter = 00124 (void (*)(QofEntity *, gboolean)) param->param_setfcn; 00125 if (boolean_setter != NULL) 00126 { 00127 boolean_setter (ent, cli_bool); 00128 } 00129 } 00130 if (0 == safe_strcmp (param->param_type, QOF_TYPE_INT32)) 00131 { 00132 errno = 0; 00133 cli_i32 = (gint32) strtol (value, &tail, 0); 00134 if (errno == 0) 00135 { 00136 i32_setter = 00137 (void (*)(QofEntity *, gint32)) param->param_setfcn; 00138 if (i32_setter != NULL) 00139 { 00140 i32_setter (ent, cli_i32); 00141 } 00142 } 00143 else 00144 { 00145 PERR (" Cannot convert %s into a number: " 00146 "an overflow has been detected.", value); 00147 } 00148 } 00149 if (0 == safe_strcmp (param->param_type, QOF_TYPE_INT64)) 00150 { 00151 errno = 0; 00152 cli_i64 = (gint64) strtol (value, &tail, 0); 00153 if (errno == 0) 00154 { 00155 i64_setter = 00156 (void (*)(QofEntity *, gint64)) param->param_setfcn; 00157 if (i64_setter != NULL) 00158 { 00159 i64_setter (ent, cli_i64); 00160 } 00161 } 00162 else 00163 { 00164 PERR (" Cannot convert %s into a number: " 00165 "an overflow has been detected.", value); 00166 } 00167 } 00168 if (0 ==safe_strcmp (param->param_type, QOF_TYPE_TIME)) 00169 { 00170 QofDate *qd; 00171 00172 qd = qof_date_parse (value, QOF_DATE_FORMAT_UTC); 00173 cli_time = qof_date_to_qtime (qd); 00174 time_setter = 00175 (void (*)(QofEntity *, QofTime *)) param->param_setfcn; 00176 if ((time_setter != NULL) && qof_time_is_valid (cli_time)) 00177 { 00178 time_setter (ent, cli_time); 00179 } 00180 } 00181 if (0 == safe_strcmp (param->param_type, QOF_TYPE_CHAR)) 00182 { 00183 param->param_setfcn (ent, value); 00184 } 00185 } 00186 00187 void 00188 qof_undo_set_param (QofEntity * ent, const QofParam * param, 00189 gchar * value) 00190 { 00191 qof_undo_modify ((QofInstance*)ent, param); 00192 set_param (ent, param, value); 00193 qof_undo_commit ((QofInstance*)ent, param); 00194 } 00195 00196 static void 00197 undo_from_kvp_helper (const gchar * path, KvpValue * content, 00198 gpointer data) 00199 { 00200 QofUndoEntity *undo_entity; 00201 00202 undo_entity = (QofUndoEntity *) data; 00203 undo_entity->path = g_strdup (path); 00204 undo_entity->value = kvp_value_to_bare_string (content); 00205 } 00206 00207 QofUndoEntity * 00208 qof_prepare_undo (QofEntity * ent, const QofParam * param) 00209 { 00210 QofUndoEntity *undo_entity; 00211 KvpFrame *undo_frame; 00212 00213 undo_frame = NULL; 00214 undo_entity = g_new0 (QofUndoEntity, 1); 00215 undo_entity->guid = qof_entity_get_guid (ent); 00216 undo_entity->param = param; 00217 undo_entity->how = UNDO_MODIFY; 00218 undo_entity->type = ent->e_type; 00219 undo_entity->value = qof_util_param_to_string (ent, param); 00220 if (0 == (safe_strcmp (param->param_type, QOF_TYPE_KVP))) 00221 { 00222 undo_frame = kvp_frame_copy (param->param_getfcn (ent, param)); 00223 kvp_frame_for_each_slot (undo_frame, undo_from_kvp_helper, 00224 undo_entity); 00225 } 00226 /* need to do COLLECT and CHOICE */ 00227 return undo_entity; 00228 } 00229 00230 static void 00231 qof_reinstate_entity (QofUndoEntity * undo_entity, QofBook * book) 00232 { 00233 const QofParam *undo_param; 00234 QofCollection *coll; 00235 QofEntity *ent; 00236 00237 undo_param = undo_entity->param; 00238 if (!undo_param) 00239 return; 00240 PINFO (" reinstate:%s", undo_entity->type); 00241 coll = qof_book_get_collection (book, undo_entity->type); 00242 if (!coll) 00243 return; 00244 ent = qof_collection_lookup_entity (coll, undo_entity->guid); 00245 if (!ent) 00246 return; 00247 PINFO (" undoing %s %s", undo_param->param_name, undo_entity->value); 00248 set_param (ent, undo_param, undo_entity->value); 00249 } 00250 00251 static void 00252 qof_recreate_entity (QofUndoEntity * undo_entity, QofBook * book) 00253 { 00254 QofEntity *ent; 00255 const GUID *guid; 00256 QofIdType type; 00257 QofInstance *inst; 00258 00259 guid = undo_entity->guid; 00260 type = undo_entity->type; 00261 g_return_if_fail (guid || type); 00262 inst = (QofInstance *) qof_object_new_instance (type, book); 00263 ent = (QofEntity *) inst; 00264 qof_entity_set_guid (ent, guid); 00265 } 00266 00267 static void 00268 qof_dump_entity (QofUndoEntity * undo_entity, QofBook * book) 00269 { 00270 QofCollection *coll; 00271 QofEntity *ent; 00272 const GUID *guid; 00273 QofIdType type; 00274 00275 type = undo_entity->type; 00276 guid = undo_entity->guid; 00277 g_return_if_fail (type || book); 00278 coll = qof_book_get_collection (book, type); 00279 ent = qof_collection_lookup_entity (coll, guid); 00280 qof_entity_release (ent); 00281 } 00282 00283 void 00284 qof_book_undo (QofBook * book) 00285 { 00286 QofUndoOperation *undo_operation; 00287 QofUndoEntity *undo_entity; 00288 QofUndo *book_undo; 00289 GList *ent_list; 00290 00291 book_undo = book->undo_data; 00292 if (book_undo->index_position > 1) 00293 book_undo->index_position--; 00294 else 00295 book_undo->index_position = 0; 00296 undo_operation = 00297 (QofUndoOperation 00298 *) (g_list_nth (book_undo->undo_list, 00299 book_undo->index_position))->data; 00300 g_return_if_fail (undo_operation); 00301 ent_list = undo_operation->entity_list; 00302 while (ent_list != NULL) 00303 { 00304 undo_entity = (QofUndoEntity *) ent_list->data; 00305 if (!undo_entity) 00306 break; 00307 switch (undo_entity->how) 00308 { 00309 case UNDO_MODIFY: 00310 { 00311 qof_reinstate_entity (undo_entity, book); 00312 break; 00313 } 00314 case UNDO_CREATE: 00315 { 00316 qof_recreate_entity (undo_entity, book); 00317 break; 00318 } 00319 case UNDO_DELETE: 00320 { 00321 qof_dump_entity (undo_entity, book); 00322 break; 00323 } 00324 case UNDO_NOOP: 00325 { 00326 break; 00327 } 00328 } 00329 ent_list = g_list_next (ent_list); 00330 } 00331 } 00332 00333 void 00334 qof_book_redo (QofBook * book) 00335 { 00336 QofUndoOperation *undo_operation; 00337 QofUndoEntity *undo_entity; 00338 QofUndo *book_undo; 00339 GList *ent_list; 00340 gint length; 00341 00342 book_undo = book->undo_data; 00343 undo_operation = 00344 (QofUndoOperation 00345 *) (g_list_nth (book_undo->undo_list, 00346 book_undo->index_position))->data; 00347 if (!undo_operation) 00348 return; 00349 ent_list = undo_operation->entity_list; 00350 while (ent_list != NULL) 00351 { 00352 undo_entity = (QofUndoEntity *) ent_list->data; 00353 if (!undo_entity) 00354 break; 00355 switch (undo_entity->how) 00356 { 00357 case UNDO_MODIFY: 00358 { 00359 qof_reinstate_entity (undo_entity, book); 00360 break; 00361 } 00362 case UNDO_CREATE: 00363 { 00364 qof_dump_entity (undo_entity, book); 00365 break; 00366 } 00367 case UNDO_DELETE: 00368 { 00369 qof_recreate_entity (undo_entity, book); 00370 break; 00371 } 00372 case UNDO_NOOP: 00373 { 00374 break; 00375 } 00376 } 00377 ent_list = g_list_next (ent_list); 00378 } 00379 length = g_list_length (book_undo->undo_list); 00380 if (book_undo->index_position < length) 00381 book_undo->index_position++; 00382 else 00383 book_undo->index_position = length; 00384 } 00385 00386 void 00387 qof_book_clear_undo (QofBook * book) 00388 { 00389 QofUndoOperation *operation; 00390 QofUndo *book_undo; 00391 00392 if (!book) 00393 return; 00394 book_undo = book->undo_data; 00395 while (book_undo != NULL) 00396 { 00397 operation = (QofUndoOperation *) book_undo->undo_list->data; 00398 if(operation->entity_list) 00399 g_list_free (operation->entity_list); 00400 book_undo->undo_list = g_list_next (book_undo->undo_list); 00401 } 00402 book_undo->index_position = 0; 00403 g_free (book_undo->undo_label); 00404 } 00405 00406 gboolean 00407 qof_book_can_undo (QofBook * book) 00408 { 00409 QofUndo *book_undo; 00410 gint length; 00411 00412 book_undo = book->undo_data; 00413 length = g_list_length (book_undo->undo_list); 00414 if ((book_undo->index_position == 0) || (length == 0)) 00415 return FALSE; 00416 return TRUE; 00417 } 00418 00419 gboolean 00420 qof_book_can_redo (QofBook * book) 00421 { 00422 QofUndo *book_undo; 00423 gint length; 00424 00425 book_undo = book->undo_data; 00426 length = g_list_length (book_undo->undo_list); 00427 if ((book_undo->index_position == length) || (length == 0)) 00428 return FALSE; 00429 return TRUE; 00430 } 00431 00432 QofUndoOperation * 00433 qof_undo_new_operation (QofBook * book, gchar * label) 00434 { 00435 QofUndoOperation *undo_operation; 00436 QofUndo *book_undo; 00437 00438 undo_operation = NULL; 00439 book_undo = book->undo_data; 00440 undo_operation = g_new0 (QofUndoOperation, 1); 00441 undo_operation->label = label; 00442 undo_operation->qt = qof_time_get_current(); 00443 undo_operation->entity_list = NULL; 00444 g_list_foreach (book_undo->undo_cache, 00445 qof_undo_new_entry, undo_operation); 00446 return undo_operation; 00447 } 00448 00449 void 00450 qof_undo_new_entry (gpointer cache, gpointer operation) 00451 { 00452 QofUndoOperation *undo_operation; 00453 QofUndoEntity *undo_entity; 00454 00455 g_return_if_fail (operation || cache); 00456 undo_operation = (QofUndoOperation *) operation; 00457 undo_entity = (QofUndoEntity *) cache; 00458 g_return_if_fail (undo_operation || undo_entity); 00459 undo_operation->entity_list = 00460 g_list_prepend (undo_operation->entity_list, undo_entity); 00461 } 00462 00463 void 00464 qof_undo_create (QofInstance * instance) 00465 { 00466 QofUndoEntity *undo_entity; 00467 QofBook *book; 00468 QofUndo *book_undo; 00469 00470 if (!instance) 00471 return; 00472 book = instance->book; 00473 book_undo = book->undo_data; 00474 undo_entity = g_new0 (QofUndoEntity, 1); 00475 // to undo a create, use a delete. 00476 undo_entity->how = UNDO_DELETE; 00477 undo_entity->guid = qof_instance_get_guid (instance); 00478 undo_entity->type = instance->entity.e_type; 00479 book_undo->undo_cache = 00480 g_list_prepend (book_undo->undo_cache, undo_entity); 00481 } 00482 00483 static void 00484 undo_get_entity (QofParam * param, gpointer data) 00485 { 00486 QofBook *book; 00487 QofUndo *book_undo; 00488 QofInstance *instance; 00489 QofUndoEntity *undo_entity; 00490 00491 instance = (QofInstance *) data; 00492 book = instance->book; 00493 book_undo = book->undo_data; 00494 g_return_if_fail (instance || param); 00495 undo_entity = qof_prepare_undo (&instance->entity, param); 00496 book_undo->undo_cache = 00497 g_list_prepend (book_undo->undo_cache, undo_entity); 00498 } 00499 00500 void 00501 qof_undo_delete (QofInstance * instance) 00502 { 00503 QofUndoEntity *undo_entity; 00504 QofIdType type; 00505 QofUndo *book_undo; 00506 QofBook *book; 00507 00508 if (!instance) 00509 return; 00510 book = instance->book; 00511 book_undo = book->undo_data; 00512 // now need to store each parameter in a second entity, MODIFY. 00513 type = instance->entity.e_type; 00514 qof_class_param_foreach (type, undo_get_entity, instance); 00515 undo_entity = g_new0 (QofUndoEntity, 1); 00516 // to undo a delete, use a create. 00517 undo_entity->how = UNDO_CREATE; 00518 undo_entity->guid = qof_instance_get_guid (instance); 00519 undo_entity->type = type; 00520 book_undo->undo_cache = 00521 g_list_prepend (book_undo->undo_cache, undo_entity); 00522 } 00523 00524 void 00525 qof_undo_modify (QofInstance * instance, const QofParam * param) 00526 { 00527 QofBook *book; 00528 QofUndo *book_undo; 00529 QofUndoEntity *undo_entity; 00530 00531 if (!instance || !param) 00532 return; 00533 book = instance->book; 00534 book_undo = book->undo_data; 00535 // handle if record is called without a commit. 00536 undo_entity = qof_prepare_undo (&instance->entity, param); 00537 book_undo->undo_cache = 00538 g_list_prepend (book_undo->undo_cache, undo_entity); 00539 // set the initial state that undo will reinstate. 00540 if (book_undo->index_position == 0) 00541 { 00542 book_undo->undo_list = g_list_prepend (book_undo->undo_list, 00543 qof_undo_new_operation (book, "initial")); 00544 book_undo->index_position++; 00545 } 00546 } 00547 00548 void 00549 qof_undo_commit (QofInstance * instance, const QofParam * param) 00550 { 00551 QofUndoEntity *undo_entity; 00552 QofUndo *book_undo; 00553 QofBook *book; 00554 00555 if (!instance || !param) 00556 return; 00557 book = instance->book; 00558 book_undo = book->undo_data; 00559 undo_entity = qof_prepare_undo (&instance->entity, param); 00560 book_undo->undo_cache = 00561 g_list_prepend (book_undo->undo_cache, undo_entity); 00562 } 00563 00564 void 00565 qof_book_start_operation (QofBook * book, gchar * label) 00566 { 00567 QofUndo *book_undo; 00568 00569 book_undo = book->undo_data; 00570 if (book_undo->undo_operation_open && book_undo->undo_cache) 00571 { 00572 g_list_free (book_undo->undo_cache); 00573 book_undo->undo_operation_open = FALSE; 00574 if (book_undo->undo_label) 00575 g_free (book_undo->undo_label); 00576 } 00577 book_undo->undo_label = g_strdup (label); 00578 book_undo->undo_operation_open = TRUE; 00579 } 00580 00581 void 00582 qof_book_end_operation (QofBook * book) 00583 { 00584 QofUndo *book_undo; 00585 00586 book_undo = book->undo_data; 00587 book_undo->undo_list = g_list_prepend (book_undo->undo_list, 00588 qof_undo_new_operation (book, book_undo->undo_label)); 00589 book_undo->index_position++; 00590 g_list_free (book_undo->undo_cache); 00591 book_undo->undo_operation_open = FALSE; 00592 } 00593 00594 QofTime * 00595 qof_book_undo_first_modified (QofBook * book) 00596 { 00597 QofUndoOperation *undo_operation; 00598 QofUndo *book_undo; 00599 00600 book_undo = book->undo_data; 00601 undo_operation = 00602 (QofUndoOperation *) g_list_last (book_undo->undo_list); 00603 return undo_operation->qt; 00604 } 00605 00606 gint 00607 qof_book_undo_count (QofBook * book) 00608 { 00609 QofUndo *book_undo; 00610 00611 book_undo = book->undo_data; 00612 return g_list_length (book_undo->undo_list); 00613 } 00614 00615 /* ====================== END OF FILE ======================== */