QOF 0.8.2
|
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 gint length; 00291 00292 book_undo = book->undo_data; 00293 length = g_list_length (book_undo->undo_list); 00294 if (book_undo->index_position > 1) 00295 book_undo->index_position--; 00296 else 00297 book_undo->index_position = 0; 00298 undo_operation = 00299 (QofUndoOperation 00300 *) (g_list_nth (book_undo->undo_list, 00301 book_undo->index_position))->data; 00302 g_return_if_fail (undo_operation); 00303 ent_list = undo_operation->entity_list; 00304 while (ent_list != NULL) 00305 { 00306 undo_entity = (QofUndoEntity *) ent_list->data; 00307 if (!undo_entity) 00308 break; 00309 switch (undo_entity->how) 00310 { 00311 case UNDO_MODIFY: 00312 { 00313 qof_reinstate_entity (undo_entity, book); 00314 break; 00315 } 00316 case UNDO_CREATE: 00317 { 00318 qof_recreate_entity (undo_entity, book); 00319 break; 00320 } 00321 case UNDO_DELETE: 00322 { 00323 qof_dump_entity (undo_entity, book); 00324 break; 00325 } 00326 case UNDO_NOOP: 00327 { 00328 break; 00329 } 00330 } 00331 ent_list = g_list_next (ent_list); 00332 } 00333 } 00334 00335 void 00336 qof_book_redo (QofBook * book) 00337 { 00338 QofUndoOperation *undo_operation; 00339 QofUndoEntity *undo_entity; 00340 QofUndo *book_undo; 00341 GList *ent_list; 00342 gint length; 00343 00344 book_undo = book->undo_data; 00345 undo_operation = 00346 (QofUndoOperation 00347 *) (g_list_nth (book_undo->undo_list, 00348 book_undo->index_position))->data; 00349 if (!undo_operation) 00350 return; 00351 ent_list = undo_operation->entity_list; 00352 while (ent_list != NULL) 00353 { 00354 undo_entity = (QofUndoEntity *) ent_list->data; 00355 if (!undo_entity) 00356 break; 00357 switch (undo_entity->how) 00358 { 00359 case UNDO_MODIFY: 00360 { 00361 qof_reinstate_entity (undo_entity, book); 00362 break; 00363 } 00364 case UNDO_CREATE: 00365 { 00366 qof_dump_entity (undo_entity, book); 00367 break; 00368 } 00369 case UNDO_DELETE: 00370 { 00371 qof_recreate_entity (undo_entity, book); 00372 break; 00373 } 00374 case UNDO_NOOP: 00375 { 00376 break; 00377 } 00378 } 00379 ent_list = g_list_next (ent_list); 00380 } 00381 length = g_list_length (book_undo->undo_list); 00382 if (book_undo->index_position < length) 00383 book_undo->index_position++; 00384 else 00385 book_undo->index_position = length; 00386 } 00387 00388 void 00389 qof_book_clear_undo (QofBook * book) 00390 { 00391 QofUndoOperation *operation; 00392 QofUndo *book_undo; 00393 00394 if (!book) 00395 return; 00396 book_undo = book->undo_data; 00397 while (book_undo != NULL) 00398 { 00399 operation = (QofUndoOperation *) book_undo->undo_list->data; 00400 if(operation->entity_list) 00401 g_list_free (operation->entity_list); 00402 book_undo->undo_list = g_list_next (book_undo->undo_list); 00403 } 00404 book_undo->index_position = 0; 00405 g_free (book_undo->undo_label); 00406 } 00407 00408 gboolean 00409 qof_book_can_undo (QofBook * book) 00410 { 00411 QofUndo *book_undo; 00412 gint length; 00413 00414 book_undo = book->undo_data; 00415 length = g_list_length (book_undo->undo_list); 00416 if ((book_undo->index_position == 0) || (length == 0)) 00417 return FALSE; 00418 return TRUE; 00419 } 00420 00421 gboolean 00422 qof_book_can_redo (QofBook * book) 00423 { 00424 QofUndo *book_undo; 00425 gint length; 00426 00427 book_undo = book->undo_data; 00428 length = g_list_length (book_undo->undo_list); 00429 if ((book_undo->index_position == length) || (length == 0)) 00430 return FALSE; 00431 return TRUE; 00432 } 00433 00434 QofUndoOperation * 00435 qof_undo_new_operation (QofBook * book, gchar * label) 00436 { 00437 QofUndoOperation *undo_operation; 00438 QofUndo *book_undo; 00439 00440 undo_operation = NULL; 00441 book_undo = book->undo_data; 00442 undo_operation = g_new0 (QofUndoOperation, 1); 00443 undo_operation->label = label; 00444 undo_operation->qt = qof_time_get_current(); 00445 undo_operation->entity_list = NULL; 00446 g_list_foreach (book_undo->undo_cache, 00447 qof_undo_new_entry, undo_operation); 00448 return undo_operation; 00449 } 00450 00451 void 00452 qof_undo_new_entry (gpointer cache, gpointer operation) 00453 { 00454 QofUndoOperation *undo_operation; 00455 QofUndoEntity *undo_entity; 00456 00457 g_return_if_fail (operation || cache); 00458 undo_operation = (QofUndoOperation *) operation; 00459 undo_entity = (QofUndoEntity *) cache; 00460 g_return_if_fail (undo_operation || undo_entity); 00461 undo_operation->entity_list = 00462 g_list_prepend (undo_operation->entity_list, undo_entity); 00463 } 00464 00465 void 00466 qof_undo_create (QofInstance * instance) 00467 { 00468 QofUndoEntity *undo_entity; 00469 QofBook *book; 00470 QofUndo *book_undo; 00471 00472 if (!instance) 00473 return; 00474 book = instance->book; 00475 book_undo = book->undo_data; 00476 undo_entity = g_new0 (QofUndoEntity, 1); 00477 // to undo a create, use a delete. 00478 undo_entity->how = UNDO_DELETE; 00479 undo_entity->guid = qof_instance_get_guid (instance); 00480 undo_entity->type = instance->entity.e_type; 00481 book_undo->undo_cache = 00482 g_list_prepend (book_undo->undo_cache, undo_entity); 00483 } 00484 00485 static void 00486 undo_get_entity (QofParam * param, gpointer data) 00487 { 00488 QofBook *book; 00489 QofUndo *book_undo; 00490 QofInstance *instance; 00491 QofUndoEntity *undo_entity; 00492 00493 instance = (QofInstance *) data; 00494 book = instance->book; 00495 book_undo = book->undo_data; 00496 g_return_if_fail (instance || param); 00497 undo_entity = qof_prepare_undo (&instance->entity, param); 00498 book_undo->undo_cache = 00499 g_list_prepend (book_undo->undo_cache, undo_entity); 00500 } 00501 00502 void 00503 qof_undo_delete (QofInstance * instance) 00504 { 00505 QofUndoEntity *undo_entity; 00506 QofIdType type; 00507 QofUndo *book_undo; 00508 QofBook *book; 00509 00510 if (!instance) 00511 return; 00512 book = instance->book; 00513 book_undo = book->undo_data; 00514 // now need to store each parameter in a second entity, MODIFY. 00515 type = instance->entity.e_type; 00516 qof_class_param_foreach (type, undo_get_entity, instance); 00517 undo_entity = g_new0 (QofUndoEntity, 1); 00518 // to undo a delete, use a create. 00519 undo_entity->how = UNDO_CREATE; 00520 undo_entity->guid = qof_instance_get_guid (instance); 00521 undo_entity->type = type; 00522 book_undo->undo_cache = 00523 g_list_prepend (book_undo->undo_cache, undo_entity); 00524 } 00525 00526 void 00527 qof_undo_modify (QofInstance * instance, const QofParam * param) 00528 { 00529 QofBook *book; 00530 QofUndo *book_undo; 00531 QofUndoEntity *undo_entity; 00532 00533 if (!instance || !param) 00534 return; 00535 book = instance->book; 00536 book_undo = book->undo_data; 00537 // handle if record is called without a commit. 00538 undo_entity = qof_prepare_undo (&instance->entity, param); 00539 book_undo->undo_cache = 00540 g_list_prepend (book_undo->undo_cache, undo_entity); 00541 // set the initial state that undo will reinstate. 00542 if (book_undo->index_position == 0) 00543 { 00544 book_undo->undo_list = g_list_prepend (book_undo->undo_list, 00545 qof_undo_new_operation (book, "initial")); 00546 book_undo->index_position++; 00547 } 00548 } 00549 00550 void 00551 qof_undo_commit (QofInstance * instance, const QofParam * param) 00552 { 00553 QofUndoEntity *undo_entity; 00554 QofUndo *book_undo; 00555 QofBook *book; 00556 00557 if (!instance || !param) 00558 return; 00559 book = instance->book; 00560 book_undo = book->undo_data; 00561 undo_entity = qof_prepare_undo (&instance->entity, param); 00562 book_undo->undo_cache = 00563 g_list_prepend (book_undo->undo_cache, undo_entity); 00564 } 00565 00566 void 00567 qof_book_start_operation (QofBook * book, gchar * label) 00568 { 00569 QofUndo *book_undo; 00570 00571 book_undo = book->undo_data; 00572 if (book_undo->undo_operation_open && book_undo->undo_cache) 00573 { 00574 g_list_free (book_undo->undo_cache); 00575 book_undo->undo_operation_open = FALSE; 00576 if (book_undo->undo_label) 00577 g_free (book_undo->undo_label); 00578 } 00579 book_undo->undo_label = g_strdup (label); 00580 book_undo->undo_operation_open = TRUE; 00581 } 00582 00583 void 00584 qof_book_end_operation (QofBook * book) 00585 { 00586 QofUndo *book_undo; 00587 00588 book_undo = book->undo_data; 00589 book_undo->undo_list = g_list_prepend (book_undo->undo_list, 00590 qof_undo_new_operation (book, book_undo->undo_label)); 00591 book_undo->index_position++; 00592 g_list_free (book_undo->undo_cache); 00593 book_undo->undo_operation_open = FALSE; 00594 } 00595 00596 QofTime * 00597 qof_book_undo_first_modified (QofBook * book) 00598 { 00599 QofUndoOperation *undo_operation; 00600 QofUndo *book_undo; 00601 00602 book_undo = book->undo_data; 00603 undo_operation = 00604 (QofUndoOperation *) g_list_last (book_undo->undo_list); 00605 return undo_operation->qt; 00606 } 00607 00608 gint 00609 qof_book_undo_count (QofBook * book) 00610 { 00611 QofUndo *book_undo; 00612 00613 book_undo = book->undo_data; 00614 return g_list_length (book_undo->undo_list); 00615 } 00616 00617 /* ====================== END OF FILE ======================== */