00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
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;
00049 const GUID *guid;
00050 QofIdType type;
00051 gchar *value;
00052 gchar *path;
00053 QofIdType choice;
00054 QofUndoAction how;
00055 };
00056
00057 struct QofUndoOperation_t
00058 {
00059 const gchar *label;
00060 QofTime *qt;
00061 GList *entity_list;
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
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
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
00515 type = instance->entity.e_type;
00516 qof_class_param_foreach (type, undo_get_entity, instance);
00517 undo_entity = g_new0 (QofUndoEntity, 1);
00518
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
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
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