QOF  0.8.7
qofundo.c
1 /***************************************************************************
2  * qofundo.c
3  *
4  * Thu Aug 25 09:19:17 2005
5  * Copyright 2005,2006,2008 Neil Williams
6  * linux@codehelp.co.uk
7  ****************************************************************************/
8 /*
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA
22  */
23 
24 #include "config.h"
25 #include <glib.h>
26 #include <qof.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <libintl.h>
30 #include <locale.h>
31 #include <errno.h>
32 #include "qofbook-p.h"
33 #include "qofundo-p.h"
34 #include "qofundo.h"
35 
36 static QofLogModule log_module = QOF_MOD_UNDO;
37 
38 typedef enum
39 {
40  UNDO_NOOP = 0,
41  UNDO_CREATE,
42  UNDO_DELETE,
43  UNDO_MODIFY
44 } QofUndoAction;
45 
46 struct QofUndoEntity_t
47 {
48  const QofParam *param; /* static anyway so only store a pointer */
49  const GUID *guid; /* enable re-creation of this entity */
50  QofIdType type; /* ditto param, static. */
51  gchar *value; /* cached string? */
52  gchar *path; /* for KVP */
53  QofIdType choice; /* For QOF_TYPE_CHOICE */
54  QofUndoAction how; /* how to act on the undo */
55 };
56 
57 struct QofUndoOperation_t
58 {
59  const gchar *label;
60  QofTime *qt;
61  GList *entity_list; /* GList of qof_undo_entity* */
62 };
63 
64 static void
65 set_param (QofEntity * ent, const QofParam * param,
66  gchar * value)
67 {
68  gchar *tail;
69  QofNumeric cli_numeric;
70  gboolean cli_bool;
71  gint32 cli_i32;
72  gint64 cli_i64;
73  QofTime *cli_time;
74  GUID *cm_guid;
75  void (*string_setter) (QofEntity *, gchar *);
76  void (*time_setter) (QofEntity *, QofTime *);
77  void (*i32_setter) (QofEntity *, gint32);
78  void (*i64_setter) (QofEntity *, gint64);
79  void (*numeric_setter) (QofEntity *, QofNumeric);
80  void (*boolean_setter) (QofEntity *, gboolean);
81  void (*guid_setter) (QofEntity *, const GUID *);
82 
83  if (0 == safe_strcmp (param->param_type, QOF_TYPE_STRING))
84  {
85  string_setter =
86  (void (*)(QofEntity *, gchar *)) param->param_setfcn;
87  if (string_setter)
88  {
89  param->param_setfcn (ent, value);
90  }
91  }
92  if (0 == safe_strcmp (param->param_type, QOF_TYPE_GUID))
93  {
94  cm_guid = g_new (GUID, 1);
95  if (TRUE == string_to_guid (value, cm_guid))
96  {
97  guid_setter =
98  (void (*)(QofEntity *, const GUID *)) param->param_setfcn;
99  if (guid_setter != NULL)
100  {
101  guid_setter (ent, cm_guid);
102  }
103  }
104  }
105  if ((0 == safe_strcmp (param->param_type, QOF_TYPE_NUMERIC)) ||
106  (safe_strcmp (param->param_type, QOF_TYPE_DEBCRED) == 0))
107  {
108  numeric_setter =
109  (void (*)(QofEntity *, QofNumeric)) param->param_setfcn;
110  qof_numeric_from_string (value, &cli_numeric);
111  if (numeric_setter != NULL)
112  {
113  numeric_setter (ent, cli_numeric);
114  }
115  }
116  if (0 == safe_strcmp (param->param_type, QOF_TYPE_BOOLEAN))
117  {
118  cli_bool = FALSE;
119  if (qof_util_bool_to_int (value) == 1)
120  {
121  cli_bool = TRUE;
122  }
123  boolean_setter =
124  (void (*)(QofEntity *, gboolean)) param->param_setfcn;
125  if (boolean_setter != NULL)
126  {
127  boolean_setter (ent, cli_bool);
128  }
129  }
130  if (0 == safe_strcmp (param->param_type, QOF_TYPE_INT32))
131  {
132  errno = 0;
133  cli_i32 = (gint32) strtol (value, &tail, 0);
134  if (errno == 0)
135  {
136  i32_setter =
137  (void (*)(QofEntity *, gint32)) param->param_setfcn;
138  if (i32_setter != NULL)
139  {
140  i32_setter (ent, cli_i32);
141  }
142  }
143  else
144  {
145  PERR (" Cannot convert %s into a number: "
146  "an overflow has been detected.", value);
147  }
148  }
149  if (0 == safe_strcmp (param->param_type, QOF_TYPE_INT64))
150  {
151  errno = 0;
152  cli_i64 = (gint64) strtol (value, &tail, 0);
153  if (errno == 0)
154  {
155  i64_setter =
156  (void (*)(QofEntity *, gint64)) param->param_setfcn;
157  if (i64_setter != NULL)
158  {
159  i64_setter (ent, cli_i64);
160  }
161  }
162  else
163  {
164  PERR (" Cannot convert %s into a number: "
165  "an overflow has been detected.", value);
166  }
167  }
168  if (0 ==safe_strcmp (param->param_type, QOF_TYPE_TIME))
169  {
170  QofDate *qd;
171 
172  qd = qof_date_parse (value, QOF_DATE_FORMAT_UTC);
173  cli_time = qof_date_to_qtime (qd);
174  time_setter =
175  (void (*)(QofEntity *, QofTime *)) param->param_setfcn;
176  if ((time_setter != NULL) && qof_time_is_valid (cli_time))
177  {
178  time_setter (ent, cli_time);
179  }
180  }
181  if (0 == safe_strcmp (param->param_type, QOF_TYPE_CHAR))
182  {
183  param->param_setfcn (ent, value);
184  }
185 }
186 
187 void
188 qof_undo_set_param (QofEntity * ent, const QofParam * param,
189  gchar * value)
190 {
191  qof_undo_modify ((QofInstance*)ent, param);
192  set_param (ent, param, value);
193  qof_undo_commit ((QofInstance*)ent, param);
194 }
195 
196 static void
197 undo_from_kvp_helper (const gchar * path, KvpValue * content,
198  gpointer data)
199 {
200  QofUndoEntity *undo_entity;
201 
202  undo_entity = (QofUndoEntity *) data;
203  undo_entity->path = g_strdup (path);
204  undo_entity->value = kvp_value_to_bare_string (content);
205 }
206 
207 QofUndoEntity *
208 qof_prepare_undo (QofEntity * ent, const QofParam * param)
209 {
210  QofUndoEntity *undo_entity;
211  KvpFrame *undo_frame;
212 
213  undo_frame = NULL;
214  undo_entity = g_new0 (QofUndoEntity, 1);
215  undo_entity->guid = qof_entity_get_guid (ent);
216  undo_entity->param = param;
217  undo_entity->how = UNDO_MODIFY;
218  undo_entity->type = ent->e_type;
219  undo_entity->value = qof_util_param_to_string (ent, param);
220  if (0 == (safe_strcmp (param->param_type, QOF_TYPE_KVP)))
221  {
222  undo_frame = kvp_frame_copy (param->param_getfcn (ent, param));
223  kvp_frame_for_each_slot (undo_frame, undo_from_kvp_helper,
224  undo_entity);
225  }
226  /* need to do COLLECT and CHOICE */
227  return undo_entity;
228 }
229 
230 static void
231 qof_reinstate_entity (QofUndoEntity * undo_entity, QofBook * book)
232 {
233  const QofParam *undo_param;
234  QofCollection *coll;
235  QofEntity *ent;
236 
237  undo_param = undo_entity->param;
238  if (!undo_param)
239  return;
240  PINFO (" reinstate:%s", undo_entity->type);
241  coll = qof_book_get_collection (book, undo_entity->type);
242  if (!coll)
243  return;
244  ent = qof_collection_lookup_entity (coll, undo_entity->guid);
245  if (!ent)
246  return;
247  PINFO (" undoing %s %s", undo_param->param_name, undo_entity->value);
248  set_param (ent, undo_param, undo_entity->value);
249 }
250 
251 static void
252 qof_recreate_entity (QofUndoEntity * undo_entity, QofBook * book)
253 {
254  QofEntity *ent;
255  const GUID *guid;
256  QofIdType type;
257  QofInstance *inst;
258 
259  guid = undo_entity->guid;
260  type = undo_entity->type;
261  g_return_if_fail (guid || type);
262  inst = (QofInstance *) qof_object_new_instance (type, book);
263  ent = (QofEntity *) inst;
264  qof_entity_set_guid (ent, guid);
265 }
266 
267 static void
268 qof_dump_entity (QofUndoEntity * undo_entity, QofBook * book)
269 {
270  QofCollection *coll;
271  QofEntity *ent;
272  const GUID *guid;
273  QofIdType type;
274 
275  type = undo_entity->type;
276  guid = undo_entity->guid;
277  g_return_if_fail (type || book);
278  coll = qof_book_get_collection (book, type);
279  ent = qof_collection_lookup_entity (coll, guid);
280  qof_entity_release (ent);
281 }
282 
283 void
285 {
286  QofUndoOperation *undo_operation;
287  QofUndoEntity *undo_entity;
288  QofUndo *book_undo;
289  GList *ent_list;
290 
291  book_undo = book->undo_data;
292  if (book_undo->index_position > 1)
293  book_undo->index_position--;
294  else
295  book_undo->index_position = 0;
296  undo_operation =
297  (QofUndoOperation
298  *) (g_list_nth (book_undo->undo_list,
299  book_undo->index_position))->data;
300  g_return_if_fail (undo_operation);
301  ent_list = undo_operation->entity_list;
302  while (ent_list != NULL)
303  {
304  undo_entity = (QofUndoEntity *) ent_list->data;
305  if (!undo_entity)
306  break;
307  switch (undo_entity->how)
308  {
309  case UNDO_MODIFY:
310  {
311  qof_reinstate_entity (undo_entity, book);
312  break;
313  }
314  case UNDO_CREATE:
315  {
316  qof_recreate_entity (undo_entity, book);
317  break;
318  }
319  case UNDO_DELETE:
320  {
321  qof_dump_entity (undo_entity, book);
322  break;
323  }
324  case UNDO_NOOP:
325  {
326  break;
327  }
328  }
329  ent_list = g_list_next (ent_list);
330  }
331 }
332 
333 void
335 {
336  QofUndoOperation *undo_operation;
337  QofUndoEntity *undo_entity;
338  QofUndo *book_undo;
339  GList *ent_list;
340  gint length;
341 
342  book_undo = book->undo_data;
343  undo_operation =
344  (QofUndoOperation
345  *) (g_list_nth (book_undo->undo_list,
346  book_undo->index_position))->data;
347  if (!undo_operation)
348  return;
349  ent_list = undo_operation->entity_list;
350  while (ent_list != NULL)
351  {
352  undo_entity = (QofUndoEntity *) ent_list->data;
353  if (!undo_entity)
354  break;
355  switch (undo_entity->how)
356  {
357  case UNDO_MODIFY:
358  {
359  qof_reinstate_entity (undo_entity, book);
360  break;
361  }
362  case UNDO_CREATE:
363  {
364  qof_dump_entity (undo_entity, book);
365  break;
366  }
367  case UNDO_DELETE:
368  {
369  qof_recreate_entity (undo_entity, book);
370  break;
371  }
372  case UNDO_NOOP:
373  {
374  break;
375  }
376  }
377  ent_list = g_list_next (ent_list);
378  }
379  length = g_list_length (book_undo->undo_list);
380  if (book_undo->index_position < length)
381  book_undo->index_position++;
382  else
383  book_undo->index_position = length;
384 }
385 
386 void
388 {
389  QofUndoOperation *operation;
390  QofUndo *book_undo;
391 
392  if (!book)
393  return;
394  book_undo = book->undo_data;
395  while (book_undo != NULL)
396  {
397  operation = (QofUndoOperation *) book_undo->undo_list->data;
398  if(operation->entity_list)
399  g_list_free (operation->entity_list);
400  book_undo->undo_list = g_list_next (book_undo->undo_list);
401  }
402  book_undo->index_position = 0;
403  g_free (book_undo->undo_label);
404 }
405 
406 gboolean
408 {
409  QofUndo *book_undo;
410  gint length;
411 
412  book_undo = book->undo_data;
413  length = g_list_length (book_undo->undo_list);
414  if ((book_undo->index_position == 0) || (length == 0))
415  return FALSE;
416  return TRUE;
417 }
418 
419 gboolean
421 {
422  QofUndo *book_undo;
423  gint length;
424 
425  book_undo = book->undo_data;
426  length = g_list_length (book_undo->undo_list);
427  if ((book_undo->index_position == length) || (length == 0))
428  return FALSE;
429  return TRUE;
430 }
431 
432 QofUndoOperation *
433 qof_undo_new_operation (QofBook * book, gchar * label)
434 {
435  QofUndoOperation *undo_operation;
436  QofUndo *book_undo;
437 
438  undo_operation = NULL;
439  book_undo = book->undo_data;
440  undo_operation = g_new0 (QofUndoOperation, 1);
441  undo_operation->label = label;
442  undo_operation->qt = qof_time_get_current();
443  undo_operation->entity_list = NULL;
444  g_list_foreach (book_undo->undo_cache,
445  qof_undo_new_entry, undo_operation);
446  return undo_operation;
447 }
448 
449 void
450 qof_undo_new_entry (gpointer cache, gpointer operation)
451 {
452  QofUndoOperation *undo_operation;
453  QofUndoEntity *undo_entity;
454 
455  g_return_if_fail (operation || cache);
456  undo_operation = (QofUndoOperation *) operation;
457  undo_entity = (QofUndoEntity *) cache;
458  g_return_if_fail (undo_operation || undo_entity);
459  undo_operation->entity_list =
460  g_list_prepend (undo_operation->entity_list, undo_entity);
461 }
462 
463 void
465 {
466  QofUndoEntity *undo_entity;
467  QofBook *book;
468  QofUndo *book_undo;
469 
470  if (!instance)
471  return;
472  book = instance->book;
473  book_undo = book->undo_data;
474  undo_entity = g_new0 (QofUndoEntity, 1);
475  // to undo a create, use a delete.
476  undo_entity->how = UNDO_DELETE;
477  undo_entity->guid = qof_instance_get_guid (instance);
478  undo_entity->type = instance->entity.e_type;
479  book_undo->undo_cache =
480  g_list_prepend (book_undo->undo_cache, undo_entity);
481 }
482 
483 static void
484 undo_get_entity (QofParam * param, gpointer data)
485 {
486  QofBook *book;
487  QofUndo *book_undo;
488  QofInstance *instance;
489  QofUndoEntity *undo_entity;
490 
491  instance = (QofInstance *) data;
492  book = instance->book;
493  book_undo = book->undo_data;
494  g_return_if_fail (instance || param);
495  undo_entity = qof_prepare_undo (&instance->entity, param);
496  book_undo->undo_cache =
497  g_list_prepend (book_undo->undo_cache, undo_entity);
498 }
499 
500 void
502 {
503  QofUndoEntity *undo_entity;
504  QofIdType type;
505  QofUndo *book_undo;
506  QofBook *book;
507 
508  if (!instance)
509  return;
510  book = instance->book;
511  book_undo = book->undo_data;
512  // now need to store each parameter in a second entity, MODIFY.
513  type = instance->entity.e_type;
514  qof_class_param_foreach (type, undo_get_entity, instance);
515  undo_entity = g_new0 (QofUndoEntity, 1);
516  // to undo a delete, use a create.
517  undo_entity->how = UNDO_CREATE;
518  undo_entity->guid = qof_instance_get_guid (instance);
519  undo_entity->type = type;
520  book_undo->undo_cache =
521  g_list_prepend (book_undo->undo_cache, undo_entity);
522 }
523 
524 void
525 qof_undo_modify (QofInstance * instance, const QofParam * param)
526 {
527  QofBook *book;
528  QofUndo *book_undo;
529  QofUndoEntity *undo_entity;
530 
531  if (!instance || !param)
532  return;
533  book = instance->book;
534  book_undo = book->undo_data;
535  // handle if record is called without a commit.
536  undo_entity = qof_prepare_undo (&instance->entity, param);
537  book_undo->undo_cache =
538  g_list_prepend (book_undo->undo_cache, undo_entity);
539  // set the initial state that undo will reinstate.
540  if (book_undo->index_position == 0)
541  {
542  book_undo->undo_list = g_list_prepend (book_undo->undo_list,
543  qof_undo_new_operation (book, "initial"));
544  book_undo->index_position++;
545  }
546 }
547 
548 void
549 qof_undo_commit (QofInstance * instance, const QofParam * param)
550 {
551  QofUndoEntity *undo_entity;
552  QofUndo *book_undo;
553  QofBook *book;
554 
555  if (!instance || !param)
556  return;
557  book = instance->book;
558  book_undo = book->undo_data;
559  undo_entity = qof_prepare_undo (&instance->entity, param);
560  book_undo->undo_cache =
561  g_list_prepend (book_undo->undo_cache, undo_entity);
562 }
563 
564 void
565 qof_book_start_operation (QofBook * book, gchar * label)
566 {
567  QofUndo *book_undo;
568 
569  book_undo = book->undo_data;
570  if (book_undo->undo_operation_open && book_undo->undo_cache)
571  {
572  g_list_free (book_undo->undo_cache);
573  book_undo->undo_operation_open = FALSE;
574  if (book_undo->undo_label)
575  g_free (book_undo->undo_label);
576  }
577  book_undo->undo_label = g_strdup (label);
578  book_undo->undo_operation_open = TRUE;
579 }
580 
581 void
583 {
584  QofUndo *book_undo;
585 
586  book_undo = book->undo_data;
587  book_undo->undo_list = g_list_prepend (book_undo->undo_list,
588  qof_undo_new_operation (book, book_undo->undo_label));
589  book_undo->index_position++;
590  g_list_free (book_undo->undo_cache);
591  book_undo->undo_operation_open = FALSE;
592 }
593 
594 QofTime *
596 {
597  QofUndoOperation *undo_operation;
598  QofUndo *book_undo;
599 
600  book_undo = book->undo_data;
601  undo_operation =
602  (QofUndoOperation *) g_list_last (book_undo->undo_list);
603  return undo_operation->qt;
604 }
605 
606 gint
608 {
609  QofUndo *book_undo;
610 
611  book_undo = book->undo_data;
612  return g_list_length (book_undo->undo_list);
613 }
614 
615 /* ====================== END OF FILE ======================== */
#define QOF_DATE_FORMAT_UTC
QOF UTC format, xsd:date compatible. QOF_UTC_DATE_FORMAT "%Y-%m-%dT%H:%M:%SZ".
Definition: qofdate.h:277
#define PERR(format, args...)
Definition: qoflog.h:183
gpointer qof_object_new_instance(QofIdTypeConst type_name, QofBook *book)
Definition: qofobject.c:42
void qof_undo_delete(QofInstance *instance)
Definition: qofundo.c:501
const gchar * QofIdType
Definition: qofid.h:81
#define PINFO(format, args...)
Definition: qoflog.h:199
QofCollection * qof_book_get_collection(QofBook *book, QofIdType entity_type)
Definition: qofbook.c:220
struct _QofNumeric QofNumeric
A rational-number type.
Definition: qofnumeric.h:61
gboolean qof_book_can_redo(QofBook *book)
event handler for redo widget
Definition: qofundo.c:420
void qof_class_param_foreach(QofIdTypeConst obj_name, QofParamForeachCB cb, gpointer user_data)
Definition: qofclass.c:268
void qof_book_start_operation(QofBook *book, gchar *label)
Start recording operation.
Definition: qofundo.c:565
gboolean qof_book_can_undo(QofBook *book)
event handler for undo widget
Definition: qofundo.c:407
gboolean string_to_guid(const gchar *string, GUID *guid)
void qof_book_end_operation(QofBook *book)
End recording the current operation.
Definition: qofundo.c:582
KvpFrame * kvp_frame_copy(const KvpFrame *frame)
Definition: kvpframe.c:153
struct _KvpFrame KvpFrame
Definition: kvpframe.h:74
gint qof_util_bool_to_int(const gchar *val)
Definition: qofutil.c:252
QofUndo * undo_data
Definition: qofbook-p.h:90
Private QofBook interface.
QofTime * qof_date_to_qtime(const QofDate *qd)
Definition: qofdate.c:926
Full range replacement for struct tm.
Definition: qofdate.h:138
QofTime * qof_time_get_current(void)
Get the current QofTime.
Definition: qoftime.c:362
QOF undo handling.
struct QofCollection_s QofCollection
Definition: qofid.h:138
const GUID * qof_instance_get_guid(QofInstance *inst)
Definition: qofinstance.c:79
QofDate * qof_date_parse(const gchar *str, QofDateFormat df)
Convert a timestamp to a QofTime.
Definition: qofdate.c:557
struct _KvpValue KvpValue
Definition: kvpframe.h:78
gboolean qof_numeric_from_string(const gchar *str, QofNumeric *n)
Definition: qofnumeric.c:1116
QofEntity * qof_collection_lookup_entity(QofCollection *col, const GUID *guid)
Definition: qofid.c:292
gchar * qof_util_param_to_string(QofEntity *ent, const QofParam *param)
Converts a parameter to a string for storage or display.
Definition: qofutil.c:464
void qof_entity_release(QofEntity *ent)
Definition: qofid.c:79
gchar * kvp_value_to_bare_string(const KvpValue *val)
General purpose function to convert any KvpValue to a string.
Definition: kvpframe.c:1834
void qof_book_clear_undo(QofBook *book)
Free the entire undo list for this book.
Definition: qofundo.c:387
QofEntity entity
Definition: qofinstance-p.h:39
QofTime * qof_book_undo_first_modified(QofBook *book)
HIG compliance aid to report time of first change.
Definition: qofundo.c:595
void kvp_frame_for_each_slot(KvpFrame *f, KvpValueForeachCB proc, gpointer data)
Definition: kvpframe.c:1642
void qof_book_undo(QofBook *book)
Set parameter values from before the previous event.
Definition: qofundo.c:284
Definition: guid.h:53
struct QofTime64 QofTime
Use a 64-bit signed int QofTime.
Definition: qoftime.h:112
gint qof_book_undo_count(QofBook *book)
Number of undo operations available.
Definition: qofundo.c:607
const GUID * qof_entity_get_guid(QofEntity *ent)
Definition: qofid.c:105
void qof_undo_create(QofInstance *instance)
Definition: qofundo.c:464
void qof_entity_set_guid(QofEntity *ent, const GUID *guid)
Definition: qofid.c:92
gint safe_strcmp(const gchar *da, const gchar *db)
Definition: qofutil.c:75
void qof_undo_commit(QofInstance *instance, const QofParam *param)
Definition: qofundo.c:549
void qof_book_redo(QofBook *book)
Set parameter values from after the previous event.
Definition: qofundo.c:334
QofBook * book
Definition: qofinstance-p.h:42
void qof_undo_modify(QofInstance *instance, const QofParam *param)
Definition: qofundo.c:525
const gchar * QofLogModule
Definition: qofid.h:85
void qof_undo_set_param(QofEntity *ent, const QofParam *param, gchar *value)
Set a value in this parameter of the entity.
Definition: qofundo.c:188