QOF 0.7.5
|
00001 /*************************************************************************** 00002 * qofundo.c 00003 * 00004 * Thu Aug 25 09:19:17 2005 00005 * Copyright 2005,2006 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 #ifndef QOF_DISABLE_DEPRECATED 00182 if (0 == safe_strcmp (param->param_type, QOF_TYPE_DATE)) 00183 { 00184 Timespec cli_date; 00185 time_t cli_time_t; 00186 void (*date_setter) (QofEntity *, Timespec); 00187 struct tm cli_time; 00188 00189 date_setter = 00190 (void (*)(QofEntity *, Timespec)) param->param_setfcn; 00191 strptime (value, QOF_UTC_DATE_FORMAT, &cli_time); 00192 cli_time_t = mktime (&cli_time); 00193 timespecFromTime_t (&cli_date, cli_time_t); 00194 if (date_setter != NULL) 00195 { 00196 date_setter (ent, cli_date); 00197 } 00198 } 00199 #endif 00200 if (0 == safe_strcmp (param->param_type, QOF_TYPE_CHAR)) 00201 { 00202 param->param_setfcn (ent, value); 00203 } 00204 } 00205 00206 void 00207 qof_undo_set_param (QofEntity * ent, const QofParam * param, 00208 gchar * value) 00209 { 00210 qof_undo_modify ((QofInstance*)ent, param); 00211 set_param (ent, param, value); 00212 qof_undo_commit ((QofInstance*)ent, param); 00213 } 00214 00215 static void 00216 undo_from_kvp_helper (const gchar * path, KvpValue * content, 00217 gpointer data) 00218 { 00219 QofUndoEntity *undo_entity; 00220 00221 undo_entity = (QofUndoEntity *) data; 00222 undo_entity->path = g_strdup (path); 00223 undo_entity->value = kvp_value_to_bare_string (content); 00224 } 00225 00226 QofUndoEntity * 00227 qof_prepare_undo (QofEntity * ent, const QofParam * param) 00228 { 00229 QofUndoEntity *undo_entity; 00230 KvpFrame *undo_frame; 00231 00232 undo_frame = NULL; 00233 undo_entity = g_new0 (QofUndoEntity, 1); 00234 undo_entity->guid = qof_entity_get_guid (ent); 00235 undo_entity->param = param; 00236 undo_entity->how = UNDO_MODIFY; 00237 undo_entity->type = ent->e_type; 00238 undo_entity->value = 00239 qof_book_merge_param_as_string ((QofParam*) param, ent); 00240 if (0 == (safe_strcmp (param->param_type, QOF_TYPE_KVP))) 00241 { 00242 undo_frame = kvp_frame_copy (param->param_getfcn (ent, param)); 00243 kvp_frame_for_each_slot (undo_frame, undo_from_kvp_helper, 00244 undo_entity); 00245 } 00246 /* need to do COLLECT and CHOICE */ 00247 return undo_entity; 00248 } 00249 00250 static void 00251 qof_reinstate_entity (QofUndoEntity * undo_entity, QofBook * book) 00252 { 00253 const QofParam *undo_param; 00254 QofCollection *coll; 00255 QofEntity *ent; 00256 00257 undo_param = undo_entity->param; 00258 if (!undo_param) 00259 return; 00260 PINFO (" reinstate:%s", undo_entity->type); 00261 coll = qof_book_get_collection (book, undo_entity->type); 00262 if (!coll) 00263 return; 00264 ent = qof_collection_lookup_entity (coll, undo_entity->guid); 00265 if (!ent) 00266 return; 00267 PINFO (" undoing %s %s", undo_param->param_name, undo_entity->value); 00268 set_param (ent, undo_param, undo_entity->value); 00269 } 00270 00271 static void 00272 qof_recreate_entity (QofUndoEntity * undo_entity, QofBook * book) 00273 { 00274 QofEntity *ent; 00275 const GUID *guid; 00276 QofIdType type; 00277 QofInstance *inst; 00278 00279 guid = undo_entity->guid; 00280 type = undo_entity->type; 00281 g_return_if_fail (guid || type); 00282 inst = (QofInstance *) qof_object_new_instance (type, book); 00283 ent = (QofEntity *) inst; 00284 qof_entity_set_guid (ent, guid); 00285 } 00286 00287 static void 00288 qof_dump_entity (QofUndoEntity * undo_entity, QofBook * book) 00289 { 00290 QofCollection *coll; 00291 QofEntity *ent; 00292 const GUID *guid; 00293 QofIdType type; 00294 00295 type = undo_entity->type; 00296 guid = undo_entity->guid; 00297 g_return_if_fail (type || book); 00298 coll = qof_book_get_collection (book, type); 00299 ent = qof_collection_lookup_entity (coll, guid); 00300 qof_entity_release (ent); 00301 } 00302 00303 void 00304 qof_book_undo (QofBook * book) 00305 { 00306 QofUndoOperation *undo_operation; 00307 QofUndoEntity *undo_entity; 00308 QofUndo *book_undo; 00309 GList *ent_list; 00310 gint length; 00311 00312 book_undo = book->undo_data; 00313 length = g_list_length (book_undo->undo_list); 00314 if (book_undo->index_position > 1) 00315 book_undo->index_position--; 00316 else 00317 book_undo->index_position = 0; 00318 undo_operation = 00319 (QofUndoOperation 00320 *) (g_list_nth (book_undo->undo_list, 00321 book_undo->index_position))->data; 00322 g_return_if_fail (undo_operation); 00323 ent_list = undo_operation->entity_list; 00324 while (ent_list != NULL) 00325 { 00326 undo_entity = (QofUndoEntity *) ent_list->data; 00327 if (!undo_entity) 00328 break; 00329 switch (undo_entity->how) 00330 { 00331 case UNDO_MODIFY: 00332 { 00333 qof_reinstate_entity (undo_entity, book); 00334 break; 00335 } 00336 case UNDO_CREATE: 00337 { 00338 qof_recreate_entity (undo_entity, book); 00339 break; 00340 } 00341 case UNDO_DELETE: 00342 { 00343 qof_dump_entity (undo_entity, book); 00344 break; 00345 } 00346 case UNDO_NOOP: 00347 { 00348 break; 00349 } 00350 } 00351 ent_list = g_list_next (ent_list); 00352 } 00353 } 00354 00355 void 00356 qof_book_redo (QofBook * book) 00357 { 00358 QofUndoOperation *undo_operation; 00359 QofUndoEntity *undo_entity; 00360 QofUndo *book_undo; 00361 GList *ent_list; 00362 gint length; 00363 00364 book_undo = book->undo_data; 00365 undo_operation = 00366 (QofUndoOperation 00367 *) (g_list_nth (book_undo->undo_list, 00368 book_undo->index_position))->data; 00369 if (!undo_operation) 00370 return; 00371 ent_list = undo_operation->entity_list; 00372 while (ent_list != NULL) 00373 { 00374 undo_entity = (QofUndoEntity *) ent_list->data; 00375 if (!undo_entity) 00376 break; 00377 switch (undo_entity->how) 00378 { 00379 case UNDO_MODIFY: 00380 { 00381 qof_reinstate_entity (undo_entity, book); 00382 break; 00383 } 00384 case UNDO_CREATE: 00385 { 00386 qof_dump_entity (undo_entity, book); 00387 break; 00388 } 00389 case UNDO_DELETE: 00390 { 00391 qof_recreate_entity (undo_entity, book); 00392 break; 00393 } 00394 case UNDO_NOOP: 00395 { 00396 break; 00397 } 00398 } 00399 ent_list = g_list_next (ent_list); 00400 } 00401 length = g_list_length (book_undo->undo_list); 00402 if (book_undo->index_position < length) 00403 book_undo->index_position++; 00404 else 00405 book_undo->index_position = length; 00406 } 00407 00408 void 00409 qof_book_clear_undo (QofBook * book) 00410 { 00411 QofUndoOperation *operation; 00412 QofUndo *book_undo; 00413 00414 if (!book) 00415 return; 00416 book_undo = book->undo_data; 00417 while (book_undo != NULL) 00418 { 00419 operation = (QofUndoOperation *) book_undo->undo_list->data; 00420 if(operation->entity_list) 00421 g_list_free (operation->entity_list); 00422 book_undo->undo_list = g_list_next (book_undo->undo_list); 00423 } 00424 book_undo->index_position = 0; 00425 g_free (book_undo->undo_label); 00426 } 00427 00428 gboolean 00429 qof_book_can_undo (QofBook * book) 00430 { 00431 QofUndo *book_undo; 00432 gint length; 00433 00434 book_undo = book->undo_data; 00435 length = g_list_length (book_undo->undo_list); 00436 if ((book_undo->index_position == 0) || (length == 0)) 00437 return FALSE; 00438 return TRUE; 00439 } 00440 00441 gboolean 00442 qof_book_can_redo (QofBook * book) 00443 { 00444 QofUndo *book_undo; 00445 gint length; 00446 00447 book_undo = book->undo_data; 00448 length = g_list_length (book_undo->undo_list); 00449 if ((book_undo->index_position == length) || (length == 0)) 00450 return FALSE; 00451 return TRUE; 00452 } 00453 00454 QofUndoOperation * 00455 qof_undo_new_operation (QofBook * book, gchar * label) 00456 { 00457 QofUndoOperation *undo_operation; 00458 QofUndo *book_undo; 00459 00460 undo_operation = NULL; 00461 book_undo = book->undo_data; 00462 undo_operation = g_new0 (QofUndoOperation, 1); 00463 undo_operation->label = label; 00464 undo_operation->qt = qof_time_get_current(); 00465 undo_operation->entity_list = NULL; 00466 g_list_foreach (book_undo->undo_cache, 00467 qof_undo_new_entry, undo_operation); 00468 return undo_operation; 00469 } 00470 00471 void 00472 qof_undo_new_entry (gpointer cache, gpointer operation) 00473 { 00474 QofUndoOperation *undo_operation; 00475 QofUndoEntity *undo_entity; 00476 00477 g_return_if_fail (operation || cache); 00478 undo_operation = (QofUndoOperation *) operation; 00479 undo_entity = (QofUndoEntity *) cache; 00480 g_return_if_fail (undo_operation || undo_entity); 00481 undo_operation->entity_list = 00482 g_list_prepend (undo_operation->entity_list, undo_entity); 00483 } 00484 00485 void 00486 qof_undo_create (QofInstance * instance) 00487 { 00488 QofUndoEntity *undo_entity; 00489 QofBook *book; 00490 QofUndo *book_undo; 00491 00492 if (!instance) 00493 return; 00494 book = instance->book; 00495 book_undo = book->undo_data; 00496 undo_entity = g_new0 (QofUndoEntity, 1); 00497 // to undo a create, use a delete. 00498 undo_entity->how = UNDO_DELETE; 00499 undo_entity->guid = qof_instance_get_guid (instance); 00500 undo_entity->type = instance->entity.e_type; 00501 book_undo->undo_cache = 00502 g_list_prepend (book_undo->undo_cache, undo_entity); 00503 } 00504 00505 static void 00506 undo_get_entity (QofParam * param, gpointer data) 00507 { 00508 QofBook *book; 00509 QofUndo *book_undo; 00510 QofInstance *instance; 00511 QofUndoEntity *undo_entity; 00512 00513 instance = (QofInstance *) data; 00514 book = instance->book; 00515 book_undo = book->undo_data; 00516 g_return_if_fail (instance || param); 00517 undo_entity = qof_prepare_undo (&instance->entity, param); 00518 book_undo->undo_cache = 00519 g_list_prepend (book_undo->undo_cache, undo_entity); 00520 } 00521 00522 void 00523 qof_undo_delete (QofInstance * instance) 00524 { 00525 QofUndoEntity *undo_entity; 00526 QofIdType type; 00527 QofUndo *book_undo; 00528 QofBook *book; 00529 00530 if (!instance) 00531 return; 00532 book = instance->book; 00533 book_undo = book->undo_data; 00534 // now need to store each parameter in a second entity, MODIFY. 00535 type = instance->entity.e_type; 00536 qof_class_param_foreach (type, undo_get_entity, instance); 00537 undo_entity = g_new0 (QofUndoEntity, 1); 00538 // to undo a delete, use a create. 00539 undo_entity->how = UNDO_CREATE; 00540 undo_entity->guid = qof_instance_get_guid (instance); 00541 undo_entity->type = type; 00542 book_undo->undo_cache = 00543 g_list_prepend (book_undo->undo_cache, undo_entity); 00544 } 00545 00546 void 00547 qof_undo_modify (QofInstance * instance, const QofParam * param) 00548 { 00549 QofBook *book; 00550 QofUndo *book_undo; 00551 QofUndoEntity *undo_entity; 00552 00553 if (!instance || !param) 00554 return; 00555 book = instance->book; 00556 book_undo = book->undo_data; 00557 // handle if record is called without a commit. 00558 undo_entity = qof_prepare_undo (&instance->entity, param); 00559 book_undo->undo_cache = 00560 g_list_prepend (book_undo->undo_cache, undo_entity); 00561 // set the initial state that undo will reinstate. 00562 if (book_undo->index_position == 0) 00563 { 00564 book_undo->undo_list = g_list_prepend (book_undo->undo_list, 00565 qof_undo_new_operation (book, "initial")); 00566 book_undo->index_position++; 00567 } 00568 } 00569 00570 void 00571 qof_undo_commit (QofInstance * instance, const QofParam * param) 00572 { 00573 QofUndoEntity *undo_entity; 00574 QofUndo *book_undo; 00575 QofBook *book; 00576 00577 if (!instance || !param) 00578 return; 00579 book = instance->book; 00580 book_undo = book->undo_data; 00581 undo_entity = qof_prepare_undo (&instance->entity, param); 00582 book_undo->undo_cache = 00583 g_list_prepend (book_undo->undo_cache, undo_entity); 00584 } 00585 00586 void 00587 qof_book_start_operation (QofBook * book, gchar * label) 00588 { 00589 QofUndo *book_undo; 00590 00591 book_undo = book->undo_data; 00592 if (book_undo->undo_operation_open && book_undo->undo_cache) 00593 { 00594 g_list_free (book_undo->undo_cache); 00595 book_undo->undo_operation_open = FALSE; 00596 if (book_undo->undo_label) 00597 g_free (book_undo->undo_label); 00598 } 00599 book_undo->undo_label = g_strdup (label); 00600 book_undo->undo_operation_open = TRUE; 00601 } 00602 00603 void 00604 qof_book_end_operation (QofBook * book) 00605 { 00606 QofUndo *book_undo; 00607 00608 book_undo = book->undo_data; 00609 book_undo->undo_list = g_list_prepend (book_undo->undo_list, 00610 qof_undo_new_operation (book, book_undo->undo_label)); 00611 book_undo->index_position++; 00612 g_list_free (book_undo->undo_cache); 00613 book_undo->undo_operation_open = FALSE; 00614 } 00615 00616 QofTime * 00617 qof_book_undo_first_modified (QofBook * book) 00618 { 00619 QofUndoOperation *undo_operation; 00620 QofUndo *book_undo; 00621 00622 book_undo = book->undo_data; 00623 undo_operation = 00624 (QofUndoOperation *) g_list_last (book_undo->undo_list); 00625 return undo_operation->qt; 00626 } 00627 00628 gint 00629 qof_book_undo_count (QofBook * book) 00630 { 00631 QofUndo *book_undo; 00632 00633 book_undo = book->undo_data; 00634 return g_list_length (book_undo->undo_list); 00635 } 00636 00637 /* ====================== END OF FILE ======================== */