GnuCash  5.6-150-g038405b370+
gncEntryLedgerControl.c
Go to the documentation of this file.
1 
7 /*
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 2 of
11  * the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, contact:
20  *
21  * Free Software Foundation Voice: +1-617-542-5942
22  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
23  * Boston, MA 02110-1301, USA gnu@gnu.org
24  */
25 
26 #include <config.h>
27 
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 
31 #include "Account.h"
32 #include "combocell.h"
33 #include "dialog-account.h"
34 #include "dialog-utils.h"
35 #include "gnc-component-manager.h"
36 #include "gnc-prefs.h"
37 #include "gnc-ui.h"
38 #include "gnc-ui-util.h"
39 #include "gnc-gui-query.h"
40 #include "gnc-warnings.h"
41 #include "table-allgui.h"
42 #include "pricecell.h"
43 #include "dialog-tax-table.h"
44 #include "checkboxcell.h"
45 
46 #include "gncEntryLedgerP.h"
47 #include "gncEntryLedgerControl.h"
48 
49 
50 static gboolean
51 gnc_entry_ledger_save (GncEntryLedger *ledger, gboolean do_commit)
52 {
53  GncEntry *blank_entry;
54  GncEntry *entry;
55 
56  if (!ledger) return FALSE;
57 
58  blank_entry = gnc_entry_ledger_get_blank_entry (ledger);
59 
60  entry = gnc_entry_ledger_get_current_entry (ledger);
61  if (entry == NULL) return FALSE;
62 
63  /* Try to avoid heavy-weight updates if nothing has changed */
64  if (!gnc_table_current_cursor_changed (ledger->table, FALSE))
65  {
66  if (!do_commit) return FALSE;
67 
68  if (entry == blank_entry)
69  {
70  if (ledger->blank_entry_edited)
71  {
72  ledger->last_date_entered = gncEntryGetDateGDate (entry);
73  ledger->blank_entry_guid = *guid_null ();
74  ledger->blank_entry_edited = FALSE;
75  blank_entry = NULL;
76  }
77  else
78  return FALSE;
79  }
80 
81  return TRUE;
82  }
83 
84  gnc_suspend_gui_refresh ();
85 
86  if (!gncEntryIsOpen (entry))
87  gncEntryBeginEdit (entry);
88 
89  gnc_table_save_cells (ledger->table, entry);
90 
91  if (entry == blank_entry)
92  {
93  time64 time = gnc_time (NULL);
94  gncEntrySetDateEntered (blank_entry, time);
95 
96  switch (ledger->type)
97  {
98  case GNCENTRY_ORDER_ENTRY:
99  gncOrderAddEntry (ledger->order, blank_entry);
100  break;
101  case GNCENTRY_INVOICE_ENTRY:
102  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
103  /* Anything entered on an invoice entry must be part of the invoice! */
104  gncInvoiceAddEntry (ledger->invoice, blank_entry);
105  break;
106  case GNCENTRY_BILL_ENTRY:
107  case GNCENTRY_EXPVOUCHER_ENTRY:
108  case GNCENTRY_VEND_CREDIT_NOTE_ENTRY:
109  case GNCENTRY_EMPL_CREDIT_NOTE_ENTRY:
110  /* Anything entered on an invoice entry must be part of the invoice! */
111  gncBillAddEntry (ledger->invoice, blank_entry);
112  break;
113  default:
114  /* Nothing to do for viewers */
115  g_warning ("blank entry traversed in a viewer");
116  break;
117  }
118  }
119 
120  if (entry == blank_entry)
121  {
122  if (do_commit)
123  {
124  ledger->blank_entry_guid = *guid_null ();
125  blank_entry = NULL;
126  ledger->last_date_entered = gncEntryGetDateGDate (entry);
127  }
128  else
129  ledger->blank_entry_edited = TRUE;
130  }
131 
132  if (do_commit)
133  gncEntryCommitEdit (entry);
134 
135  gnc_table_clear_current_cursor_changes (ledger->table);
136 
137  gnc_resume_gui_refresh ();
138 
139  return TRUE;
140 }
141 
142 static gboolean
143 gnc_entry_ledger_verify_acc_cell_ok (GncEntryLedger *ledger,
144  const char *cell_name,
145  const char *cell_msg)
146 {
147  ComboCell *cell;
148  const char *name;
149 
150  cell = (ComboCell *) gnc_table_layout_get_cell (ledger->table->layout,
151  cell_name);
152  g_return_val_if_fail (cell, TRUE);
153  name = cell->cell.value;
154  if (!name || *name == '\0')
155  {
156  const char *format = ("%s %s");
157  const char *gen_msg = _("Invalid Entry: You need to supply an account in the right currency for this position.");
158 
159  gnc_error_dialog (GTK_WINDOW (ledger->parent), format, gen_msg, cell_msg);
160  return FALSE;
161  }
162  return TRUE;
163 }
164 
168 static gboolean
169 gnc_entry_ledger_verify_can_save (GncEntryLedger *ledger)
170 {
171  gnc_numeric value;
172 
173  /* Compute the value and tax value of the current cursor */
174  gnc_entry_ledger_compute_value (ledger, &value, NULL);
175 
176  /* If there is a value, make sure there is an account */
177  if (! gnc_numeric_zero_p (value))
178  {
179  switch (ledger->type)
180  {
181  case GNCENTRY_INVOICE_ENTRY:
182  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
183  if (!gnc_entry_ledger_verify_acc_cell_ok (ledger, ENTRY_IACCT_CELL,
184  _("This account should usually be of type income.")))
185  return FALSE;
186  break;
187  case GNCENTRY_BILL_ENTRY:
188  case GNCENTRY_EXPVOUCHER_ENTRY:
189  case GNCENTRY_VEND_CREDIT_NOTE_ENTRY:
190  case GNCENTRY_EMPL_CREDIT_NOTE_ENTRY:
191  if (!gnc_entry_ledger_verify_acc_cell_ok (ledger, ENTRY_BACCT_CELL,
192  _("This account should usually be of type expense or asset.")))
193  return FALSE;
194  break;
195  default:
196  g_warning ("Unhandled ledger type");
197  break;
198  }
199  }
200 
201  return TRUE;
202 }
203 
204 static void gnc_entry_ledger_move_cursor (VirtualLocation *p_new_virt_loc,
205  gpointer user_data)
206 {
207  GncEntryLedger *ledger = user_data;
208  VirtualLocation new_virt_loc = *p_new_virt_loc;
209  GncEntry *new_entry;
210  GncEntry *old_entry;
211  gboolean saved;
212 
213  if (!ledger) return;
214 
215  old_entry = gnc_entry_ledger_get_current_entry (ledger);
216  new_entry = gnc_entry_ledger_get_entry (ledger, new_virt_loc.vcell_loc);
217 
218  gnc_suspend_gui_refresh ();
219  saved = gnc_entry_ledger_save (ledger, old_entry != new_entry);
220  gnc_resume_gui_refresh ();
221 
222  /* redrawing can muck everything up */
223  if (saved)
224  {
225  VirtualCellLocation vcell_loc;
226 
227  /* redraw */
228  gnc_entry_ledger_display_refresh (ledger);
229 
230  if (ledger->traverse_to_new)
231  new_entry = gnc_entry_ledger_get_blank_entry (ledger);
232 
233  /* if the entry we were going to is still in the register,
234  * then it may have moved. Find out where it is now. */
235  if (gnc_entry_ledger_find_entry (ledger, new_entry, &vcell_loc))
236  {
237  new_virt_loc.vcell_loc = vcell_loc;
238  }
239  else
240  new_virt_loc.vcell_loc = ledger->table->current_cursor_loc.vcell_loc;
241  }
242 
243  gnc_table_find_close_valid_cell (ledger->table, &new_virt_loc, FALSE);
244 
245  *p_new_virt_loc = new_virt_loc;
246 }
247 
252 static QofQuery *new_query_for_entry_desc(GncEntryLedger *reg, const char* desc, gboolean use_invoice)
253 {
254  QofQuery *query = NULL;
255  QofQueryPredData *predData = NULL;
256  GSList *param_list = NULL;
257  GSList *primary_sort_params = NULL;
258  const char* should_be_null = (use_invoice ? ENTRY_BILL : ENTRY_INVOICE);
259 
260  g_assert(reg);
261  g_assert(desc);
262 
263  /* The query itself and its book */
264  query = qof_query_create_for (GNC_ID_ENTRY);
265  qof_query_set_book (query, reg->book);
266 
267  /* Predicate data: We want to compare one string, namely the given
268  * argument */
269  predData =
270  qof_query_string_predicate (QOF_COMPARE_EQUAL, desc,
271  QOF_STRING_MATCH_CASEINSENSITIVE, FALSE);
272 
273  /* Search Parameter: We want to query on the ENTRY_DESC column */
274  param_list = qof_query_build_param_list (ENTRY_DESC, NULL);
275 
276  /* Register this in the query */
277  qof_query_add_term (query, param_list, predData, QOF_QUERY_FIRST_TERM);
278 
279  /* For invoice entries, Entry->Bill must be NULL, and vice versa */
281  qof_query_build_param_list (should_be_null,
282  QOF_PARAM_GUID, NULL),
283  NULL, QOF_QUERY_AND);
284 
285  /* Set the sort order: By DATE_ENTERED, increasing, and returning
286  * only one single resulting item. */
287  primary_sort_params = qof_query_build_param_list(ENTRY_DATE_ENTERED, NULL);
288  qof_query_set_sort_order (query, primary_sort_params, NULL, NULL);
289  qof_query_set_sort_increasing (query, TRUE, TRUE, TRUE);
290 
291  qof_query_set_max_results(query, 1);
292 
293  return query;
294 }
295 
298 static GncEntry*
299 find_entry_in_book_by_desc(GncEntryLedger *reg, const char* desc)
300 {
301  GncEntry *result = NULL;
302  gboolean use_invoice;
303  QofQuery *query;
304  GList *entries = NULL;
305 
306  switch (reg->type)
307  {
308  case GNCENTRY_INVOICE_ENTRY:
309  case GNCENTRY_INVOICE_VIEWER:
310  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
311  case GNCENTRY_CUST_CREDIT_NOTE_VIEWER:
312  use_invoice = TRUE;
313  break;
314  default:
315  use_invoice = FALSE;
316  break;
317  };
318 
319  query = new_query_for_entry_desc(reg, desc, use_invoice);
320  entries = qof_query_run(query);
321 
322  /* Do we have a non-empty result? */
323  if (entries)
324  {
325  /* That's the result. */
326  result = (GncEntry*) entries->data;
327  /*g_warning("Found %d GncEntry items", g_list_length (entries));*/
328  }
329 
330  qof_query_destroy(query);
331  return result;
332 }
333 
334 #if 0
335 
338 static GncEntry*
339 gnc_find_entry_in_reg_by_desc(GncEntryLedger *reg, const char* desc)
340 {
341  int virt_row, virt_col;
342  int num_rows, num_cols;
343  GncEntry *last_entry;
344 
345  g_assert(reg);
346  g_assert(reg->table);
347  if (!reg || !reg->table)
348  return NULL;
349 
350  num_rows = reg->table->num_virt_rows;
351  num_cols = reg->table->num_virt_cols;
352 
353  last_entry = NULL;
354 
355  for (virt_row = num_rows - 1; virt_row >= 0; virt_row--)
356  for (virt_col = num_cols - 1; virt_col >= 0; virt_col--)
357  {
358  GncEntry *entry;
359  VirtualCellLocation vcell_loc = { virt_row, virt_col };
360 
361  entry = gnc_entry_ledger_get_entry(reg, vcell_loc);
362 
363  if (entry == last_entry)
364  continue;
365 
366  if (g_strcmp0 (desc, gncEntryGetDescription (entry)) == 0)
367  return entry;
368 
369  last_entry = entry;
370  }
371 
372  return NULL;
373 }
374 #endif
375 
376 static void set_value_combo_cell(BasicCell *cell, const char *new_value)
377 {
378  if (!cell || !new_value)
379  return;
380  if (g_strcmp0 (new_value, gnc_basic_cell_get_value (cell)) == 0)
381  return;
382 
383  gnc_combo_cell_set_value ((ComboCell *) cell, new_value);
384  gnc_basic_cell_set_changed (cell, TRUE);
385 }
386 
387 static void set_value_price_cell(BasicCell *cell, gnc_numeric new_value)
388 {
389  PriceCell *pcell = (PriceCell*) cell;
390  if (!cell)
391  return;
392  if (gnc_numeric_equal (new_value, gnc_price_cell_get_value(pcell)))
393  return;
394 
395  gnc_price_cell_set_value (pcell, new_value);
396  gnc_basic_cell_set_changed (cell, TRUE);
397 }
398 
399 static gboolean
400 gnc_entry_ledger_auto_completion (GncEntryLedger *ledger,
401  gncTableTraversalDir dir,
402  VirtualLocation *p_new_virt_loc)
403 {
404  GncEntry *entry;
405  GncEntry *blank_entry;
406  GncEntry *auto_entry;
407  const char* cell_name;
408  const char *desc;
409  BasicCell *cell = NULL;
410  char *account_name = NULL;
411 
412  g_assert(ledger);
413  g_assert(ledger->table);
414  blank_entry = gnc_entry_ledger_get_blank_entry (ledger);
415 
416  /* auto-completion is only triggered by a tab out */
417  if (dir != GNC_TABLE_TRAVERSE_RIGHT)
418  return FALSE;
419 
420  entry = gnc_entry_ledger_get_current_entry (ledger);
421  if (entry == NULL)
422  return FALSE;
423 
424  cell_name = gnc_table_get_current_cell_name (ledger->table);
425 
426  /* Auto-completion is done only in an entry ledger */
427  switch (ledger->type)
428  {
429  case GNCENTRY_ORDER_ENTRY:
430  case GNCENTRY_INVOICE_ENTRY:
431  case GNCENTRY_BILL_ENTRY:
432  case GNCENTRY_EXPVOUCHER_ENTRY:
433  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
434  case GNCENTRY_VEND_CREDIT_NOTE_ENTRY:
435  case GNCENTRY_EMPL_CREDIT_NOTE_ENTRY:
436  break;
437  default:
438  return FALSE;
439  }
440 
441  /* Further conditions before we actually do auto-completion: */
442  /* There must be a blank entry */
443  if (blank_entry == NULL)
444  return FALSE;
445 
446  /* we must be on the blank entry */
447  if (entry != blank_entry)
448  return FALSE;
449 
450  /* and leaving the description cell */
451  if (!gnc_cell_name_equal (cell_name, ENTRY_DESC_CELL))
452  return FALSE;
453 
454  /* nothing but the date and description should be changed */
455  /* FIXME, this should be refactored. */
456  if (gnc_table_layout_get_cell_changed (ledger->table->layout,
457  ENTRY_ACTN_CELL, TRUE)
458  || gnc_table_layout_get_cell_changed (ledger->table->layout,
459  ENTRY_QTY_CELL, TRUE)
460  || gnc_table_layout_get_cell_changed (ledger->table->layout,
461  ENTRY_PRIC_CELL, TRUE)
462  || gnc_table_layout_get_cell_changed (ledger->table->layout,
463  ENTRY_DISC_CELL, TRUE)
464  || gnc_table_layout_get_cell_changed (ledger->table->layout,
465  ENTRY_DISTYPE_CELL, TRUE)
466  || gnc_table_layout_get_cell_changed (ledger->table->layout,
467  ENTRY_DISHOW_CELL, TRUE)
468  || gnc_table_layout_get_cell_changed (ledger->table->layout,
469  ENTRY_IACCT_CELL, TRUE)
470  || gnc_table_layout_get_cell_changed (ledger->table->layout,
471  ENTRY_BACCT_CELL, TRUE)
472  || gnc_table_layout_get_cell_changed (ledger->table->layout,
473  ENTRY_TAXABLE_CELL, TRUE)
474  || gnc_table_layout_get_cell_changed (ledger->table->layout,
475  ENTRY_TAXINCLUDED_CELL, TRUE)
476  || gnc_table_layout_get_cell_changed (ledger->table->layout,
477  ENTRY_TAXTABLE_CELL, TRUE)
478  || gnc_table_layout_get_cell_changed (ledger->table->layout,
479  ENTRY_VALUE_CELL, TRUE)
480  || gnc_table_layout_get_cell_changed (ledger->table->layout,
481  ENTRY_TAXVAL_CELL, TRUE)
482  || gnc_table_layout_get_cell_changed (ledger->table->layout,
483  ENTRY_BILLABLE_CELL, TRUE)
484  || gnc_table_layout_get_cell_changed (ledger->table->layout,
485  ENTRY_PAYMENT_CELL, TRUE))
486  return FALSE;
487 
488  /* and the description should indeed be changed */
489  if (!gnc_table_layout_get_cell_changed (ledger->table->layout,
490  ENTRY_DESC_CELL, TRUE))
491  return FALSE;
492 
493  /* to a non-empty value */
494  desc = gnc_table_layout_get_cell_value (ledger->table->layout, ENTRY_DESC_CELL);
495  if ((desc == NULL) || (*desc == '\0'))
496  return FALSE;
497 
498  /* Ok, we are sure we want to trigger auto-completion. Now find an
499  * entry to copy the values from. */
500  auto_entry =
501  /* Use this for book-wide auto-completion of the invoice entries */
502  find_entry_in_book_by_desc(ledger, desc);
503 
504  if (auto_entry == NULL)
505  return FALSE;
506 
507  /* now perform the completion */
508  gnc_suspend_gui_refresh ();
509 
510  /* Auto-complete the action field */
511  cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_ACTN_CELL);
512  set_value_combo_cell (cell, gncEntryGetAction (auto_entry));
513 
514  /* Auto-complete the account field */
515  switch (ledger->type)
516  {
517  case GNCENTRY_INVOICE_ENTRY:
518  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
519  cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_IACCT_CELL);
520  account_name = gnc_get_account_name_for_register (gncEntryGetInvAccount(auto_entry));
521  break;
522  case GNCENTRY_EXPVOUCHER_ENTRY:
523  case GNCENTRY_BILL_ENTRY:
524  case GNCENTRY_VEND_CREDIT_NOTE_ENTRY:
525  case GNCENTRY_EMPL_CREDIT_NOTE_ENTRY:
526  cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_BACCT_CELL);
527  account_name = gnc_get_account_name_for_register (gncEntryGetBillAccount(auto_entry));
528  break;
529  case GNCENTRY_ORDER_ENTRY:
530  default:
531  cell = NULL;
532  account_name = NULL;
533  break;
534  }
535  set_value_combo_cell (cell, account_name);
536  g_free (account_name);
537 
538  /* Auto-complete quantity cell. Note that this requires some care because
539  * credit notes store quantities with a reversed sign. So we need to figure
540  * out if the original document from which we extract the autofill entry
541  * was a credit note or not. */
542  {
543  gboolean orig_is_cn;
544  switch (ledger->type)
545  {
546  case GNCENTRY_INVOICE_ENTRY:
547  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
548  orig_is_cn = gncInvoiceGetIsCreditNote (gncEntryGetInvoice (auto_entry));
549  break;
550  default:
551  orig_is_cn = gncInvoiceGetIsCreditNote (gncEntryGetBill (auto_entry));
552  break;
553  }
554  cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_QTY_CELL);
555  set_value_price_cell (cell, gncEntryGetDocQuantity (auto_entry, orig_is_cn));
556  }
557 
558  /* Auto-complete price cell */
559  {
560  gnc_numeric price;
561  switch (ledger->type)
562  {
563  case GNCENTRY_INVOICE_ENTRY:
564  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
565  price = gncEntryGetInvPrice (auto_entry);
566  break;
567  default:
568  price = gncEntryGetBillPrice (auto_entry);
569  break;
570  }
571 
572  /* Auto-complete price cell */
573  cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_PRIC_CELL);
574  set_value_price_cell (cell, price);
575  }
576 
577  /* We intentionally skip the discount column */
578 
579  /* Taxable?, Tax-include?, Tax table */
580  {
581  gboolean taxable = FALSE, taxincluded = FALSE;
582  GncTaxTable *taxtable = NULL;
583  switch (ledger->type)
584  {
585  case GNCENTRY_INVOICE_ENTRY:
586  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
587  taxable = gncEntryGetInvTaxable (auto_entry);
588  taxincluded = gncEntryGetInvTaxIncluded (auto_entry);
589  taxtable = gncEntryGetInvTaxTable (auto_entry);
590  break;
591  case GNCENTRY_BILL_ENTRY:
592  case GNCENTRY_VEND_CREDIT_NOTE_ENTRY:
593  taxable = gncEntryGetBillTaxable (auto_entry);
594  taxincluded = gncEntryGetBillTaxIncluded (auto_entry);
595  taxtable = gncEntryGetBillTaxTable (auto_entry);
596  break;
597  default:
598  break;
599  }
600 
601  switch (ledger->type)
602  {
603  case GNCENTRY_INVOICE_ENTRY:
604  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
605  case GNCENTRY_BILL_ENTRY:
606  case GNCENTRY_VEND_CREDIT_NOTE_ENTRY:
607  /* Taxable? cell */
608  cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_TAXABLE_CELL);
609  gnc_checkbox_cell_set_flag ((CheckboxCell *) cell, taxable);
610  gnc_basic_cell_set_changed (cell, TRUE);
611 
612  /* taxincluded? cell */
613  cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_TAXINCLUDED_CELL);
614  gnc_checkbox_cell_set_flag ((CheckboxCell *) cell, taxincluded);
615  gnc_basic_cell_set_changed (cell, TRUE);
616 
617  /* Taxable? cell */
618  cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_TAXTABLE_CELL);
619  set_value_combo_cell(cell, gncTaxTableGetName (taxtable));
620  break;
621  default:
622  break;
623  }
624  }
625 
626 
627  gnc_resume_gui_refresh ();
628 
629  /* now move to the non-empty amount column unless config setting says not */
630  if ( !gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL_REGISTER,
631  GNC_PREF_TAB_TRANS_MEMORISED) )
632  {
633  VirtualLocation new_virt_loc;
634  const char *cell_name = ENTRY_QTY_CELL;
635 
636  if (gnc_table_get_current_cell_location (ledger->table, cell_name,
637  &new_virt_loc))
638  *p_new_virt_loc = new_virt_loc;
639  }
640 
641  return TRUE;
642 }
643 
644 static gboolean gnc_entry_ledger_traverse (VirtualLocation *p_new_virt_loc,
645  gncTableTraversalDir dir,
646  gpointer user_data)
647 {
648  GncEntryLedger *ledger = user_data;
649  GncEntry *entry, *new_entry;
650  gint response;
651  VirtualLocation virt_loc;
652  int changed;
653  char const *cell_name;
654  gboolean exact_traversal;
655 
656  if (!ledger) return FALSE;
657 
658  exact_traversal = (dir == GNC_TABLE_TRAVERSE_POINTER);
659 
660  entry = gnc_entry_ledger_get_current_entry (ledger);
661  if (!entry)
662  return FALSE;
663 
664  /* no changes, make sure we aren't going off the end */
665  changed = gnc_table_current_cursor_changed (ledger->table, FALSE);
666  if (!changed)
667  return FALSE;
668 
669  virt_loc = *p_new_virt_loc;
670 
671  cell_name = gnc_table_get_current_cell_name (ledger->table);
672 
673  /* See if we are leaving the account field */
674  do
675  {
676  ComboCell *cell;
677  char *name;
678  char *cell_name = NULL;
679 
680  switch (ledger->type)
681  {
682  case GNCENTRY_INVOICE_ENTRY:
683  case GNCENTRY_INVOICE_VIEWER:
684  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
685  case GNCENTRY_CUST_CREDIT_NOTE_VIEWER:
686  cell_name = ENTRY_IACCT_CELL;
687  break;
688  case GNCENTRY_BILL_ENTRY:
689  case GNCENTRY_BILL_VIEWER:
690  case GNCENTRY_EXPVOUCHER_ENTRY:
691  case GNCENTRY_EXPVOUCHER_VIEWER:
692  case GNCENTRY_VEND_CREDIT_NOTE_ENTRY:
693  case GNCENTRY_VEND_CREDIT_NOTE_VIEWER:
694  case GNCENTRY_EMPL_CREDIT_NOTE_ENTRY:
695  case GNCENTRY_EMPL_CREDIT_NOTE_VIEWER:
696  cell_name = ENTRY_BACCT_CELL;
697  break;
698  default:
699  g_warning ("Unhandled ledger type");
700  break;
701  }
702 
703  if (!cell_name)
704  break;
705 
706  if (!gnc_cell_name_equal (cell_name, cell_name))
707  break;
708 
709  if (!gnc_table_layout_get_cell_changed (ledger->table->layout,
710  cell_name, FALSE))
711  break;
712 
713  cell = (ComboCell *) gnc_table_layout_get_cell (ledger->table->layout,
714  cell_name);
715  if (!cell)
716  break;
717 
718  name = cell->cell.value;
719  if (!name || *name == '\0')
720  break;
721 
722  /* Create the account if necessary. Also checks for a placeholder */
723  if (!gnc_entry_ledger_get_account_by_name (ledger, (BasicCell *) cell,
724  cell->cell.value,
725  &ledger->full_refresh))
726  return TRUE;
727 
728  }
729  while (FALSE);
730 
731 
732  /* See if we are leaving the TaxTable field */
733  do
734  {
735  ComboCell *cell;
737  char *name;
738 
739  if (!gnc_cell_name_equal (cell_name, ENTRY_TAXTABLE_CELL))
740  break;
741 
742  if (!gnc_table_layout_get_cell_changed (ledger->table->layout,
743  ENTRY_TAXTABLE_CELL, FALSE))
744  break;
745 
746  cell = (ComboCell *) gnc_table_layout_get_cell (ledger->table->layout,
747  ENTRY_TAXTABLE_CELL);
748  if (!cell)
749  break;
750 
751  name = cell->cell.value;
752  if (!name || *name == '\0')
753  break;
754 
755  table = gncTaxTableLookupByName (ledger->book, cell->cell.value);
756  if (table)
757  break;
758 
759  {
760  const char *format = _("The tax table %s does not exist. "
761  "Would you like to create it?");
762  if (!gnc_verify_dialog (GTK_WINDOW (ledger->parent), TRUE, format, name))
763  break;
764  }
765 
766  ledger->full_refresh = FALSE;
767 
768  table = gnc_ui_tax_table_new_from_name (GTK_WINDOW (ledger->parent), ledger->book, name);
769  if (!table)
770  break;
771 
772  ledger->full_refresh = TRUE;
773 
774  name = (char *)gncTaxTableGetName (table);
775  gnc_combo_cell_set_value (cell, name);
776  gnc_basic_cell_set_changed (&cell->cell, TRUE);
777 
778  }
779  while (FALSE);
780 
781 
782  /* See if we are tabbing off the end of the very last line
783  * (i.e. the blank entry)
784  */
785  do
786  {
787  VirtualLocation virt_loc;
788 
789  if (!changed && !ledger->blank_entry_edited)
790  break;
791 
792  if (dir != GNC_TABLE_TRAVERSE_RIGHT)
793  break;
794 
795  virt_loc = ledger->table->current_cursor_loc;
796  if (gnc_table_move_vertical_position (ledger->table, &virt_loc, 1))
797  break;
798 
799  virt_loc = ledger->table->current_cursor_loc;
800  if (gnc_table_move_tab (ledger->table, &virt_loc, TRUE))
801  break;
802 
803  *p_new_virt_loc = ledger->table->current_cursor_loc;
804 
805  /* Yep, we're trying to leave the blank entry -- make sure
806  * we are allowed to do so by verifying the current cursor.
807  * If the current cursor is ok, then move on!
808  */
809 
810  /* Verify that the cursor is ok. If we can't save the cell, don't move! */
811  if (!gnc_entry_ledger_verify_can_save (ledger))
812  {
813  return TRUE;
814  }
815 
816  (p_new_virt_loc->vcell_loc.virt_row)++;
817  p_new_virt_loc->phys_row_offset = 0;
818  p_new_virt_loc->phys_col_offset = 0;
819 
820  ledger->traverse_to_new = TRUE;
821 
822  /* If we're here, we're tabbing off the end of the 'blank entry' */
823  return FALSE;
824 
825  }
826  while (FALSE);
827 
828  /* Now see if we are changing cursors. If not, we may be able to
829  * auto-complete. */
830  if (!gnc_table_virtual_cell_out_of_bounds (ledger->table,
831  virt_loc.vcell_loc))
832  {
833  if (gnc_entry_ledger_auto_completion (ledger, dir, p_new_virt_loc))
834  return FALSE;
835  }
836 
837  /* Check for going off the end */
838  gnc_table_find_close_valid_cell (ledger->table, &virt_loc, exact_traversal);
839 
840  /* Same entry, no problem -- we're just moving backwards in the cursor */
841  new_entry = gnc_entry_ledger_get_entry (ledger, virt_loc.vcell_loc);
842  if (entry == new_entry)
843  {
844  *p_new_virt_loc = virt_loc;
845 
846  return FALSE;
847  }
848 
849  /* If we are here, then we are trying to leave the cursor. Make sure
850  * the cursor we are leaving is valid. If so, ask the user if the
851  * changes should be recorded. If not, don't go anywhere.
852  */
853 
854  /* Verify this cursor -- if it's not valid, don't let them move on */
855  if (!gnc_entry_ledger_verify_can_save (ledger))
856  {
857  *p_new_virt_loc = ledger->table->current_cursor_loc;
858  return TRUE;
859  }
860 
861  /*
862  * XXX GNCENTRY_INVOICE_EDIT processing to be added:
863  * 1) check if the qty field changed.
864  * 2) if so, check if this entry is part of an order.
865  * 3) if so, ask if they want to change the entry or
866  * split the entry into two parts.
867  */
868 
869  /* Ok, we are changing lines and the current entry has
870  * changed. We only ask them what they want to do in
871  * limited cases -- usually just let the change go through.
872  */
873  {
874  GtkWidget *dialog;
875  const char *title = _("Save the current entry?");
876  const char *message =
877  _("The current entry has been changed. However, this entry is "
878  "part of an existing order. Would you like to record the change "
879  "and effectively change your order?");
880 
881  switch (ledger->type)
882  {
883  case GNCENTRY_INVOICE_ENTRY:
884  case GNCENTRY_CUST_CREDIT_NOTE_ENTRY:
885  if (gncEntryGetOrder (entry) != NULL)
886  {
887  dialog = gtk_message_dialog_new(GTK_WINDOW(ledger->parent),
888  GTK_DIALOG_DESTROY_WITH_PARENT,
889  GTK_MESSAGE_QUESTION,
890  GTK_BUTTONS_NONE,
891  "%s", title);
892  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
893  "%s", message);
894  gtk_dialog_add_buttons(GTK_DIALOG(dialog),
895  _("_Don't Record"), GTK_RESPONSE_REJECT,
896  _("_Cancel"), GTK_RESPONSE_CANCEL,
897  _("_Record"), GTK_RESPONSE_ACCEPT,
898  NULL);
899  response = gnc_dialog_run(GTK_DIALOG(dialog), GNC_PREF_WARN_INV_ENTRY_MOD);
900  gtk_widget_destroy(dialog);
901  break;
902  }
903 
904  /* FALL THROUGH */
905  default:
906  response = GTK_RESPONSE_ACCEPT;
907  break;
908  }
909  }
910 
911  switch (response)
912  {
913  case GTK_RESPONSE_ACCEPT:
914  break;
915 
916  case GTK_RESPONSE_REJECT:
917  {
918  VirtualCellLocation vcell_loc;
919  GncEntry *new_entry;
920 
921  new_entry = gnc_entry_ledger_get_entry (ledger, virt_loc.vcell_loc);
922 
923  gnc_entry_ledger_cancel_cursor_changes (ledger);
924 
925  if (gnc_entry_ledger_find_entry (ledger, new_entry, &vcell_loc))
926  virt_loc.vcell_loc = vcell_loc;
927 
928  gnc_table_find_close_valid_cell (ledger->table, &virt_loc,
929  exact_traversal);
930 
931  *p_new_virt_loc = virt_loc;
932  }
933 
934  break;
935 
936  case GTK_RESPONSE_CANCEL:
937  default:
938  return TRUE;
939  }
940 
941  return FALSE;
942 }
943 
944 TableControl * gnc_entry_ledger_control_new (void)
945 {
946  TableControl * control;
947 
948  control = gnc_table_control_new ();
949  control->move_cursor = gnc_entry_ledger_move_cursor;
950  control->traverse = gnc_entry_ledger_traverse;
951 
952  return control;
953 }
954 
955 
956 void gnc_entry_ledger_cancel_cursor_changes (GncEntryLedger *ledger)
957 {
958  VirtualLocation virt_loc;
959 
960  if (ledger == NULL)
961  return;
962 
963  virt_loc = ledger->table->current_cursor_loc;
964 
965  if (!gnc_table_current_cursor_changed (ledger->table, FALSE))
966  return;
967 
968  /* When cancelling edits, reload the cursor from the entry. */
969  gnc_table_clear_current_cursor_changes (ledger->table);
970 
971  if (gnc_table_find_close_valid_cell (ledger->table, &virt_loc, FALSE))
972  gnc_table_move_cursor_gui (ledger->table, virt_loc);
973 
974  gnc_table_refresh_gui (ledger->table, TRUE);
975 }
976 
977 static gboolean
978 gnc_entry_ledger_check_close_internal (GtkWidget *parent,
979  GncEntryLedger *ledger,
980  gboolean dontask)
981 {
982  const char *message = _("The current entry has been changed. "
983  "Would you like to save it?");
984  VirtualLocation virt_loc;
985 
986  virt_loc = ledger->table->current_cursor_loc;
987 
988  if (gnc_entry_ledger_traverse (&virt_loc, GNC_TABLE_TRAVERSE_POINTER,
989  ledger))
990  return FALSE;
991 
992  if (!gnc_entry_ledger_verify_can_save (ledger))
993  return FALSE;
994 
995  if (dontask || gnc_verify_dialog (GTK_WINDOW (parent), TRUE, "%s", message))
996  gnc_entry_ledger_save (ledger, TRUE);
997  else
998  gnc_entry_ledger_cancel_cursor_changes (ledger);
999 
1000  return TRUE;
1001 }
1002 
1003 gboolean
1004 gnc_entry_ledger_commit_entry (GncEntryLedger *ledger)
1005 {
1006  if (!ledger) return TRUE;
1007 
1008  return gnc_entry_ledger_check_close_internal (NULL, ledger, TRUE);
1009 }
1010 
1011 gboolean
1012 gnc_entry_ledger_check_close (GtkWidget *parent, GncEntryLedger *ledger)
1013 {
1014  if (!ledger) return TRUE;
1015 
1016  if (gnc_entry_ledger_changed (ledger))
1017  {
1018  gboolean dontask = FALSE;
1019 
1020  if (ledger->type == GNCENTRY_INVOICE_ENTRY ||
1021  ledger->type == GNCENTRY_CUST_CREDIT_NOTE_ENTRY)
1022  {
1023  gboolean inv_value;
1024  gboolean only_inv_changed = FALSE;
1025 
1026  if (gnc_table_current_cursor_changed (ledger->table, FALSE) == 1 &&
1027  gnc_table_layout_get_cell_changed (ledger->table->layout,
1028  ENTRY_INV_CELL, TRUE))
1029  only_inv_changed = TRUE;
1030 
1031  inv_value = gnc_entry_ledger_get_checkmark (ledger, ENTRY_INV_CELL);
1032 
1033  if (inv_value && only_inv_changed)
1034  {
1035  /* If the only change is that the 'inv' entry was clicked
1036  * "on", then just accept the change it without question.
1037  */
1038  dontask = TRUE;
1039  }
1040  }
1041 
1042  return gnc_entry_ledger_check_close_internal (parent, ledger, dontask);
1043 
1044  }
1045  return TRUE;
1046 }
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
Equivalence predicate: Returns TRUE (1) if a and b represent the same number.
void qof_query_add_term(QofQuery *q, QofQueryParamList *param_list, QofQueryPredData *pred_data, QofQueryOp op)
This is the general function that adds a new Query Term to a query.
Definition: qofquery.cpp:681
This file contains the functions to present a gui to the user for creating a new account or editing a...
utility functions for the GnuCash UI
void qof_query_set_sort_order(QofQuery *q, QofQueryParamList *params1, QofQueryParamList *params2, QofQueryParamList *params3)
When a query is run, the results are sorted before being returned.
Definition: qofquery.cpp:1249
void gnc_table_move_cursor_gui(Table *table, VirtualLocation new_virt_loc)
will move the cursor and its GUI to the indicated location.
Definition: table-allgui.c:887
GncEntry * gnc_entry_ledger_get_entry(GncEntryLedger *ledger, VirtualCellLocation vcell_loc)
Returns the GncEntry at the given location, or NULL if the location is not valid. ...
gboolean gnc_table_find_close_valid_cell(Table *table, VirtualLocation *virt_loc, gboolean exact_pointer)
Find a close valid cell.
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
void qof_query_set_sort_increasing(QofQuery *q, gboolean prim_inc, gboolean sec_inc, gboolean tert_inc)
When a query is run, the results are sorted before being returned.
Definition: qofquery.cpp:1280
void qof_query_set_max_results(QofQuery *q, int n)
Set the maximum number of results that should be returned.
Definition: qofquery.cpp:1289
char * gnc_get_account_name_for_register(const Account *account)
Get either the full name of the account or the simple name, depending on the configuration parameter ...
void gnc_table_refresh_gui(Table *table, gboolean do_scroll)
Refresh the whole GUI from the table.
Definition: table-gnome.c:165
Account handling public routines.
void qof_query_destroy(QofQuery *query)
Frees the resources associate with a Query object.
The ComboCell object implements a cell handler with a "combination-box" pull-down menu in it...
Definition: combocell.h:52
GncEntry * gnc_entry_ledger_get_current_entry(GncEntryLedger *ledger)
Returns the Entry where the cursor is currently located.
GtkWidget * parent
A Hint for where to display.
The PriceCell object implements a cell handler that stores a single double-precision value...
Definition: pricecell.h:54
void qof_query_set_book(QofQuery *query, QofBook *book)
Set the book to be searched.
void gncBillAddEntry(GncInvoice *bill, GncEntry *entry)
Call this function when adding an entry to a bill instead of an invoice.
Definition: gncInvoice.c:719
GDate gncEntryGetDateGDate(const GncEntry *entry)
Returns the day of this entry.
Definition: gncEntry.c:918
gboolean gnc_entry_ledger_check_close(GtkWidget *parent, GncEntryLedger *ledger)
This will ask the user if they really want to make a change.
gboolean gnc_table_move_vertical_position(Table *table, VirtualLocation *virt_loc, int phys_row_offset)
Moves away from virtual location virt_loc by phys_row_offset physical rows.
void qof_query_add_guid_match(QofQuery *q, QofQueryParamList *param_list, const GncGUID *guid, QofQueryOp op)
DOCUMENT ME !!
Definition: qofquery.cpp:1310
Generic api to store and retrieve preferences.
GList * qof_query_run(QofQuery *query)
Perform the query, return the results.
const GncGUID * guid_null(void)
Returns a GncGUID which is guaranteed to never reference any entity.
Definition: guid.cpp:130
gboolean gnc_table_virtual_cell_out_of_bounds(Table *table, VirtualCellLocation vcell_loc)
checks the given location and returns true if it is out of bounds of the table.
Definition: table-allgui.c:207
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
Declarations for the Table object.
gboolean gnc_price_cell_set_value(PriceCell *cell, gnc_numeric amount)
updates amount, returns TRUE if string representation actually changed
Definition: pricecell.c:219
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:261
The CheckboxCell object implements a cell handler that will toggle between yes and no values when cli...
Definition: checkboxcell.h:40
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
A Query.
Definition: qofquery.cpp:74
GncEntry * gnc_entry_ledger_get_blank_entry(GncEntryLedger *ledger)
Exported Functions.
gnc_numeric gnc_price_cell_get_value(PriceCell *cell)
return the value of a price cell
Definition: pricecell.c:208
#define QOF_QUERY_FIRST_TERM
First/only term is same as &#39;and&#39;.
Definition: qofquery.h:102
gnc_numeric gncEntryGetDocQuantity(const GncEntry *entry, gboolean is_cn)
Get the quantity as on the physical document.
Definition: gncEntry.c:952
gboolean gnc_entry_ledger_commit_entry(GncEntryLedger *ledger)
This will act just like hitting &#39;return&#39; to record an entry.
modtime is the internal date of the last modtime See libgnucash/engine/TaxTableBillTermImmutability.txt for an explanation of the following Code that handles refcount, parent, child, invisible and children is identical to that in ::GncBillTerm