gpe-expenses  0.1.9
expenses-gtk.c
1 /***************************************************************************
2  * expenses-gtk.c
3  *
4  * Mon Nov 21 22:56:18 2005
5  * Copyright 2005 Neil Williams <linux@codehelp.co.uk>
6  * Copyright (C) 2002, 2003, 2004 Philip Blundell <philb@gnu.org>
7  * Copyright (C) 2005, 2006 Florian Boor <florian@kernelconcepts.de>
8  ****************************************************************************/
9 /*
10  This package is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 3 of the License, or
13  (at your option) any later version.
14 
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  GNU General Public License for more details.
19 
20  You should have received a copy of the GNU General Public License
21  along with this program. If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "config.h"
25 #include <stdlib.h>
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <glib/gprintf.h>
29 #include <qof.h>
30 #include <math.h>
31 #include <locale.h>
32  /* GTK and GPE includes */
33 #include <gtk/gtkmain.h>
34 #include <gpe/pixmaps.h>
35 #include <gpe/init.h>
36 #include <gpe/pim-categories.h>
37 #include <gpe/pim-categories-ui.h>
38 #include <gpe/spacing.h>
39 #include "expenses-gtk.h"
40 #include "qof-main.h"
41 #include "gpe-expenses.h"
42 #include "qof-expenses.h"
43 
44 #define _(String) gettext (String)
45 
47 #define SHOW_DECIMAL_PLACES 2
48 
49 static QofLogModule log_module = GPE_MOD_GUI;
50 
51 enum {
52  COL_EXP_DATE,
53  COL_EXP_TYPE,
54  COL_EXP_SYMBOL,
55  COL_EXP_AMOUNT,
56  COL_ENTITY,
57  COL_MAX
58 };
59 static void exp_refresh_list (GpeExpenseData *context);
60 
74 static inline void
75 compare_cache (gchar * text_entry, QofEntity * ent, const QofParam * param)
76 {
77  gchar * check;
78  check = qof_util_param_to_string (ent, param);
79  g_return_if_fail (check);
80  if (safe_strcmp(check, text_entry))
81  {
82  qof_util_param_edit ((QofInstance*)ent, param);
83  qof_util_param_set_string (ent, param, text_entry);
84  qof_util_param_commit ((QofInstance*)ent, param);
85  }
86  if (text_entry)
87  g_free (text_entry);
88  text_entry = NULL;
89 }
90 
97 static void
98 edit_ok_clicked(GtkWidget G_GNUC_UNUSED *widget, gpointer data)
99 {
100  GpeExpenseData *context;
101  QofCurrency * currency;
102  GtkTextBuffer *buf;
103  GtkTextIter start, end;
104  QofEntity *ent;
105  gchar * mnemonic;
106 
107  context = (GpeExpenseData*)data;
108  g_return_if_fail(context);
109  ent = context->entity;
110 
111  buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (context->text_view));
112  gtk_text_buffer_get_bounds (buf, &start, &end);
113  /* exp_note */
114  compare_cache (g_strdup(gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buf),
115  &start, &end, FALSE)), ent,
116  qof_class_get_parameter (GPE_QOF_EXPENSES, EXP_NOTE));
117  /* exp_vendor */
118  compare_cache ( g_strdup (gtk_entry_get_text
119  (GTK_ENTRY (context->vendor_entry))), ent,
120  qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_VENDOR));
121  /* exp_city */
122  compare_cache (g_strdup (gtk_entry_get_text
123  (GTK_ENTRY (context->city_entry))), ent,
124  qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_CITY));
125  /* exp_attendees */
126  compare_cache (g_strdup (gtk_entry_get_text
127  (GTK_ENTRY (context->attendees_entry))), ent,
128  qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_ATTENDEES));
129  /* exp_type */
130  compare_cache (g_strdup (ExpenseTypeasString
131  (gtk_combo_box_get_active(context->edit_type_list))), ent,
132  qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_TYPE));
133  /* exp_payment */
134  compare_cache (g_strdup (ExpensePaymentasString
135  (gtk_combo_box_get_active(context->payment_list))), ent,
136  qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_PAYMENT));
137  /* exp_category */
138  compare_cache (g_strdup (gtk_label_get_text (GTK_LABEL(context->cat_label))),
139  ent, qof_class_get_parameter (GPE_QOF_EXPENSES, EXP_CATEGORY));
140  /* exp_currency */
141  mnemonic = gtk_combo_box_get_active_text (context->currency_list);
142  currency = qof_currency_lookup_name ((QofInstance*)ent, mnemonic);
143  /* if currency is not found, preserve the cache version. */
144  if (currency)
145  compare_cache (g_strdup_printf ("%d", currency->pq_code), ent,
146  qof_class_get_parameter (GPE_QOF_EXPENSES, EXP_CURRENCY));
147  gtk_widget_destroy(context->window);
148  context->entity = NULL;
149  exp_refresh_list(context);
150 }
151 
152 static void
153 edit_cancel_clicked(GtkWidget G_GNUC_UNUSED *widget, gpointer data)
154 {
155  GpeExpenseData *context;
156 
157  context = (GpeExpenseData*)data;
158  g_return_if_fail(context);
159  exp_refresh_list(context);
160  gtk_widget_destroy(context->window);
161  context->entity = NULL;
162 }
163 
164 static void
165 edit_delete_clicked(GtkWidget G_GNUC_UNUSED *widget, gpointer data)
166 {
167  GpeExpenseData *context;
168 
169  context = (GpeExpenseData*)data;
170  g_return_if_fail(context);
171  qof_event_gen (context->entity, QOF_EVENT_DESTROY, NULL);
172  qof_entity_release(context->entity);
173  exp_refresh_list(context);
174  gtk_widget_destroy(context->window);
175  context->entity = NULL;
176 }
177 
178 static void
179 create_currency_list(gpointer G_GNUC_UNUSED key, gpointer value, gpointer user_data)
180 {
181  QofCurrency *curr;
182  GpeExpenseData *context;
183 
184  curr = (QofCurrency*)value;
185  context = (GpeExpenseData*)user_data;
186  gtk_combo_box_insert_text (context->currency_list, curr->pq_code,
187  curr->mnemonic);
188 }
189 
190 /* ported from gpe-contacts */
191 static gchar *
192 build_categories_string (GSList * catlist)
193 {
194  guint id;
195  GSList * iter;
196  const gchar *cat;
197  gchar *s = NULL;
198 
199  if (!catlist) { return g_strdup(""); }
200  for (iter = catlist; iter; iter = iter->next)
201  {
202  id = GPOINTER_TO_INT(iter->data);
203  cat = gpe_pim_category_name (id);
204 
205  if (cat)
206  {
207  if (s)
208  {
209  /* append as a comma-separated list */
210  gchar *ns = g_strdup_printf ("%s, %s", s, cat);
211  g_free (s);
212  s = ns;
213  }
214  else
215  s = g_strdup (cat);
216  }
217  }
218  return s;
219 }
220 
221 static void
222 set_selected_category (GtkWidget G_GNUC_UNUSED *ui, GSList *selected, gpointer user_data)
223 {
224  GpeExpenseData * context;
225  gchar * str;
226 
227  context = (GpeExpenseData*)user_data;
228  g_return_if_fail (context);
229 
230  str = build_categories_string (selected);
231  if (str)
232  {
233  gtk_label_set_markup (GTK_LABEL (context->cat_label), str);
234  g_free (str);
235  }
236 }
237 
238 /* based on gpe-contacts */
239 static void
240 on_categories_clicked (GtkButton G_GNUC_UNUSED *button, gpointer user_data)
241 {
242  GtkWidget * G_GNUC_UNUSED w;
243 
244  w = gpe_pim_categories_dialog (gpe_pim_categories_list (),
245  G_CALLBACK (set_selected_category), user_data);
246 }
247 
248 static gint
249 cat_compare (gconstpointer gpe_cat, gconstpointer qof_name)
250 {
251  struct gpe_pim_category * c;
252 
253  c = (struct gpe_pim_category *) gpe_cat;
254  return safe_strcasecmp (gpe_pim_category_name (c->id), qof_name);
255 }
256 
257 /* merge the QOF category list into the GPE PIM category list */
258 static void
259 cat_populate (const gchar * cat_name)
260 {
261  GSList * cat_list, *match;
262  gint max_list;
263 
264  match = NULL;
265  cat_list = gpe_pim_categories_list();
266  if (cat_list)
267  {
268  /* if the category name exists, leave it alone */
269  match = g_slist_find_custom (cat_list, cat_name, cat_compare);
270  }
271  if (!match)
272  {
273  DEBUG ("'%s' not found in GPE category list, adding.", cat_name);
274  gpe_pim_category_new (cat_name, &max_list);
275  }
276 }
277 
278 static void
279 edit_expense (GtkWidget G_GNUC_UNUSED *w, GpeExpenseData *context)
280 {
281  GtkWidget *table, *top_vbox;
282  GtkWidget *viewport, *scrolled_window;
283  GtkWidget *buttonbox, *buttonok, *buttoncancel, *buttondelete;
284  GtkWidget *type_label, *payment_label, *currency_label;
285  GtkWidget *city_label, *vendor_label, *note_label, *attendees_label;
286  GtkTextBuffer *buf;
287  guint gpe_spacing, pos, i;
288  const QofParam *param;
289  gboolean mileage;
290  gchar *text;
291 
292  g_return_if_fail(context);
293  if(!context->entity) { return; }
294  ENTER (" ");
295  gpe_spacing = 0;
296  pos = 0;
297  mileage = FALSE;
298  buttonok = gtk_button_new_from_stock (GTK_STOCK_SAVE);
299  buttoncancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
300  buttondelete = gtk_button_new_from_stock (GTK_STOCK_DELETE);
301 
302  table = gtk_table_new(10, 6, FALSE);
303  top_vbox = gtk_vbox_new (FALSE, 0);
304  context->text_view = gtk_text_view_new ();
305  buttonbox = gtk_hbox_new (FALSE, 0);
306  type_label = gtk_label_new (_("Type:"));
307  payment_label = gtk_label_new (_("Payment:"));
308  currency_label = gtk_label_new (_("Currency:"));
309  vendor_label = gtk_label_new(_("Vendor:"));
310  city_label = gtk_label_new(_("City:"));
311  note_label = gtk_label_new(_("Note:"));
312  attendees_label = gtk_label_new(_("Attendees"));
313  context->categories = gtk_button_new_with_label (_("Category"));
314  context->cat_label = gtk_label_new ("");
315  context->edit_type_list = GTK_COMBO_BOX(gtk_combo_box_new_text());
316  context->currency_list = GTK_COMBO_BOX(gtk_combo_box_new_text());
317  context->payment_list = GTK_COMBO_BOX(gtk_combo_box_new_text());
318  context->vendor_entry = GTK_ENTRY (gtk_entry_new());
319  context->city_entry = GTK_ENTRY (gtk_entry_new());
320  context->attendees_entry = GTK_ENTRY (gtk_entry_new());
321  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
322  viewport = gtk_viewport_new(NULL, NULL);
323 
324  context->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
325 
326  gtk_table_set_col_spacings (GTK_TABLE(table), gpe_spacing);
327  gtk_table_set_row_spacings (GTK_TABLE(table), gpe_spacing);
328  gtk_box_set_spacing (GTK_BOX(top_vbox), gpe_spacing);
329 
330  gtk_window_set_default_size (GTK_WINDOW (context->window), 240, 320);
331  gtk_window_set_title (GTK_WINDOW (context->window), _("Expenses"));
332 
333  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
334  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
335  gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
336  gtk_container_add (GTK_CONTAINER (viewport), table);
337  gtk_container_add (GTK_CONTAINER (scrolled_window), viewport);
338 
339  gtk_container_set_border_width(GTK_CONTAINER(buttonbox), 0);
340 
341  gtk_table_set_col_spacings (GTK_TABLE(table), gpe_spacing);
342  gtk_table_set_row_spacings (GTK_TABLE(table), gpe_spacing);
343  gtk_box_set_spacing (GTK_BOX(top_vbox), gpe_spacing);
344 
345  gtk_signal_connect (GTK_OBJECT (buttonok), "clicked",
346  GTK_SIGNAL_FUNC (edit_ok_clicked), context);
347  gtk_signal_connect (GTK_OBJECT (buttoncancel), "clicked",
348  GTK_SIGNAL_FUNC (edit_cancel_clicked), context);
349  gtk_signal_connect (GTK_OBJECT (buttondelete), "clicked",
350  GTK_SIGNAL_FUNC (edit_delete_clicked), context);
351 
352  gtk_box_pack_start (GTK_BOX (buttonbox), buttondelete, TRUE, FALSE, 0);
353  gtk_box_pack_start (GTK_BOX (buttonbox), buttoncancel, TRUE, FALSE, 0);
354  gtk_box_pack_start (GTK_BOX (buttonbox), buttonok, TRUE, FALSE, 0);
355 
356  gtk_misc_set_alignment (GTK_MISC (context->cat_label), 0.0, 0.5);
357  gtk_misc_set_alignment (GTK_MISC (type_label), 0.0, 0.5);
358  gtk_misc_set_alignment (GTK_MISC (payment_label), 0.0, 0.5);
359  gtk_misc_set_alignment (GTK_MISC (currency_label), 0.0, 0.5);
360  gtk_misc_set_alignment (GTK_MISC (vendor_label), 0.0, 0.5);
361  gtk_misc_set_alignment (GTK_MISC (city_label), 0.0, 0.5);
362  gtk_misc_set_alignment (GTK_MISC (attendees_label), 0.0, 0.5);
363 
364  i = 0;
365  param = qof_class_get_parameter(context->entity->e_type, EXP_TYPE);
366  i = ExpenseTypefromString(param->param_getfcn(context->entity, param));
367  gtk_combo_box_set_active(context->edit_type_list, i);
368  /* Currency and PaymentType disabled for mileage */
369  if(i == Mileage) { mileage = TRUE; }
370 
371  /* Category */
372  pos++;
373  gtk_table_attach (GTK_TABLE(table), context->categories, 0, 1, pos, pos+1,
374  GTK_FILL, GTK_FILL, 0, 0);
375  /* the label comes after the button as the label *is* the data. */
376  gtk_table_attach (GTK_TABLE(table), context->cat_label, 2, 3, pos, pos+1,
377  GTK_FILL, GTK_FILL, 0, 0);
378  pos++;
379  /* Type of expense */
380  i = 0;
381  gtk_table_attach(GTK_TABLE(table), type_label, 0, 1, pos, pos+1,
382  GTK_FILL, GTK_FILL, 0, 0);
383  while(0 != safe_strcmp(ExpenseTypeasString(i), ""))
384  {
385  gchar *check;
386 
387  gtk_combo_box_append_text (context->edit_type_list,
388  dgettext(LIBRARY_GETTEXT_PACKAGE, ExpenseTypeasString(i)));
389  check = qof_util_param_to_string(context->entity, param);
390  if(0 == safe_strcmp(check, ExpenseTypeasString(i)))
391  {
392  gtk_combo_box_set_active(context->edit_type_list, i);
393  }
394  g_free(check);
395  i++;
396  }
397  gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(context->edit_type_list), 1,
398  3, pos, pos+1, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
399 
400  pos++;
401  /* Payment method */
402  i = 0;
403  gtk_table_attach(GTK_TABLE(table), payment_label, 0, 1, pos, pos+1,
404  GTK_FILL, GTK_FILL, 0, 0);
405 
406  while((0 != safe_strcmp(ExpensePaymentasString(i), "")) &&
407  (!mileage))
408  {
409  gtk_combo_box_append_text(context->payment_list,
410  dgettext(LIBRARY_GETTEXT_PACKAGE, ExpensePaymentasString(i)));
411  i++;
412  }
413  gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(context->payment_list), 1,
414  3, pos, pos+1, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
415  pos++;
416  /* Currency (to be replaced when using mileage) */
417  if(!mileage)
418  {
419  gtk_table_attach(GTK_TABLE(table), currency_label, 0, 1, pos, pos+1,
420  GTK_FILL, GTK_FILL, 0, 0);
421  qof_currency_foreach(create_currency_list, context);
422  gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(context->currency_list), 1,
423  3, pos, pos+1, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
424  pos++;
425  }
426  /* Vendor */
427  gtk_table_attach(GTK_TABLE(table), vendor_label, 0, 1, pos, pos+1,
428  GTK_FILL, GTK_FILL, 0, 0);
429  gtk_table_attach(GTK_TABLE(table), GTK_WIDGET (context->vendor_entry),
430  1, 5, pos, pos+1, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
431  pos++;
432  /* City */
433  gtk_table_attach(GTK_TABLE(table), city_label, 0, 1, pos, pos+1,
434  GTK_FILL, GTK_FILL, 0, 0);
435  gtk_table_attach(GTK_TABLE(table), GTK_WIDGET (context->city_entry),
436  1, 5, pos, pos+1, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
437  pos++;
438  /* Attendees */
439  gtk_table_attach(GTK_TABLE(table), attendees_label, 0, 1, pos, pos+1,
440  GTK_FILL, GTK_FILL, 0, 0);
441  gtk_table_attach(GTK_TABLE(table), GTK_WIDGET (context->attendees_entry),
442  1, 5, pos, pos+1, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
443  pos++;
444  /* Note */
445  gtk_table_attach(GTK_TABLE(table), note_label, 0, 1, pos, pos+1,
446  GTK_FILL, GTK_FILL, 0, 0);
447  gtk_table_attach(GTK_TABLE(table), context->text_view, 1, 5, pos, pos+1,
448  GTK_FILL, GTK_FILL | GTK_EXPAND, 0, 0);
449  pos++;
450 
451  gtk_text_view_set_editable (GTK_TEXT_VIEW (context->text_view), TRUE);
452  gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (context->text_view), GTK_WRAP_WORD_CHAR);
453 
454  gtk_box_pack_start (GTK_BOX (top_vbox), scrolled_window, TRUE, TRUE, 0);
455  gtk_box_pack_start (GTK_BOX (top_vbox), buttonbox, FALSE, FALSE, 0);
456 
457  gtk_container_add (GTK_CONTAINER (context->window), top_vbox);
458 
459  /* use entity values to preset the edit window */
460  i = 0;
461  param = qof_class_get_parameter(context->entity->e_type, EXP_CATEGORY);
462  text = param->param_getfcn(context->entity, param);
463  if(text) {
464  /* check the QOF categories against the GPE categories
465  and add the OQF ones if they do not exist in GPE */
466  cat_populate (text);
467  PINFO ("setting cat_label=%s", text);
468  gtk_label_set_markup (GTK_LABEL(context->cat_label), text);
469  }
470 
471  param = qof_class_get_parameter(context->entity->e_type, EXP_PAYMENT);
472  i = ExpensePaymentfromString(param->param_getfcn(context->entity, param));
473  gtk_combo_box_set_active(context->payment_list, i);
474 
475  param = qof_class_get_parameter(context->entity->e_type, EXP_VENDOR);
476  text = param->param_getfcn(context->entity, param);
477  if(text) { gtk_entry_set_text(context->vendor_entry, text); }
478 
479  param = qof_class_get_parameter(context->entity->e_type, EXP_CITY);
480  text = param->param_getfcn(context->entity, param);
481  if(text) { gtk_entry_set_text(context->city_entry, text); }
482 
483  param = qof_class_get_parameter(context->entity->e_type, EXP_CURRENCY);
484  {
485  QofCurrency * currency;
486  gint32 check, (*int32_getter) (QofEntity*, const QofParam*);
487  int32_getter = (gint32 (*)(QofEntity*, const QofParam*)) param->param_getfcn;
488  check = int32_getter(context->entity, param);
489  currency = qof_currency_lookup ((QofInstance*)context->entity, check);
490  if (currency)
491  PINFO (" currency=%d mnemonic=%s", check, currency->mnemonic);
492  gtk_combo_box_set_active(context->currency_list, check);
493  }
494 
495  param = qof_class_get_parameter(context->entity->e_type, EXP_ATTENDEES);
496  text = param->param_getfcn(context->entity, param);
497  if(text) { gtk_entry_set_text(context->attendees_entry, text); }
498 
499  param = qof_class_get_parameter(context->entity->e_type, EXP_NOTE);
500  buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (context->text_view));
501  text = param->param_getfcn(context->entity, param);
502  if(text)
503  {
504  gtk_text_buffer_set_text(buf, text, strlen(text));
505  gtk_text_view_set_buffer(GTK_TEXT_VIEW (context->text_view), buf);
506  }
507 
508  g_signal_connect (G_OBJECT (context->categories), "clicked",
509  G_CALLBACK (on_categories_clicked), context);
510  gpe_set_window_icon (context->window, "icon");
511  gtk_widget_show_all(context->window);
512  LEAVE (" ");
513 }
514 
515 static void
516 open_about_dialog (void)
517 {
518  GtkAboutDialog* ab;
519  /* If you modify gpe-expenses, add your details here. */
520  const gchar * authors[4] = {"Neil Williams <linux@codehelp.co.uk>\n"
521  "Philip Blundell <philb@gnu.org>\n"
522  "Florian Boor <florian@kernelconcepts.de>\n"
523  , NULL };
524 
525  ab = GTK_ABOUT_DIALOG( gtk_about_dialog_new() );
526  gtk_about_dialog_set_copyright(ab,
527  "Copyright 2005-2007 Neil Williams <linux@codehelp.co.uk>");
528  gtk_about_dialog_set_version(ab, VERSION);
529  gtk_about_dialog_set_comments(ab,
530  /* Translators: line breaks allowed here. */
531  _("Expenses records for GPE. Supports payment types, "
532  "categories, expense types (mileage, meals, parking, etc.), "
533  "notes and currency selection."));
534  gtk_about_dialog_set_license (ab,
535  " This package is free software; you can redistribute it and/or modify\n"
536  " it under the terms of the GNU General Public License as published by\n"
537  " the Free Software Foundation; either version 3 of the License, or\n"
538  " (at your option) any later version.\n"
539  "\n"
540  " This program is distributed in the hope that it will be useful,\n"
541  " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
542  " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
543  " GNU General Public License for more details.\n"
544  "\n"
545  " You should have received a copy of the GNU General Public License\n"
546  " along with this program. If not, see <http://www.gnu.org/licenses/>.\n");
547  gtk_about_dialog_set_website (ab, "http://gpe-expenses.sourceforge.net/");
548  gtk_about_dialog_set_authors (ab, authors);
549  gtk_about_dialog_set_translator_credits (ab, _("translator-credits"));
550  gtk_about_dialog_set_logo (ab, gpe_try_find_icon("icon", NULL));
551  gpe_set_window_icon(GTK_WIDGET(ab), "icon");
552  gtk_dialog_run(GTK_DIALOG(ab));
553  gtk_widget_destroy (GTK_WIDGET(ab));
554 }
555 
556 /* Receives the selected expense, then list each
557  column parameter. Use data selection for column1,
558  string selection from list for column2, digit entry for column4.
559  Column3 is set via selection of column2.
560 */
561 static void
562 exp_show_entities(QofEntity *ent, gpointer data)
563 {
564  GpeExpenseData *context;
565  QofTime *qt;
566  QofDate *qd;
567  QofNumeric amount, (*numeric_getter) (QofEntity*, const QofParam*);
568  GtkTreeIter ent_data;
569  const QofParam *param;
570  gchar *type, *symbol, *date_string, *dis_string;
571  gdouble d_amount;
572 
573  context = (GpeExpenseData*)data;
574  g_return_if_fail(context);
575  symbol = "";
576  /* param_getfcn each parameter */
577  param = qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_DATE);
578  qt = (QofTime*)param->param_getfcn (ent, param);
579  qd = qof_date_from_qtime (qt);
580  date_string = qof_date_print (qd, QOF_DATE_FORMAT_CE);
581  qof_date_free (qd);
582  param = qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_TYPE);
583  dis_string = param->param_getfcn(ent, param);
584  type = dgettext(LIBRARY_GETTEXT_PACKAGE, dis_string);
585  /* If Mileage, use ExpenseDistance,
586  else use ExpenseCustomCurrency->symbol */
587  if(0 == safe_strcmp(dis_string, "Mileage"))
588  {
589  gint unit;
590  /* EXP_DISTANCE */
591  param = qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_DISTANCE);
592  unit = ExpenseDistancefromString(param->param_getfcn(ent, param));
593  switch (unit)
594  {
595  /* Translators: short form of 'miles' */
596  case 0 : { symbol = _("mi"); break; }
597  /* Translators: short form of 'kilometres' */
598  case 1 : { symbol = _("km"); break; }
599  }
600  }
601  else
602  {
603  QofCurrency * pqc;
604  gint32 curr_code, (*int32_getter) (QofEntity *, const QofParam *);
605  param = qof_class_get_parameter (GPE_QOF_EXPENSES, EXP_CURRENCY);
606  int32_getter = (gint32 (*)(QofEntity *, const QofParam *))
607  param->param_getfcn;
608  curr_code = int32_getter (ent, param);
609  pqc = qof_currency_lookup ((QofInstance*)ent, curr_code);
610  if (pqc)
611  symbol = g_strdup(pqc->symbol);
612  }
613  param = qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_AMOUNT);
614  numeric_getter = (QofNumeric (*)(QofEntity*, const QofParam*)) param->param_getfcn;
615  amount = numeric_getter(ent, param);
616  d_amount = qof_numeric_to_double(amount);
617  /* Columns: Date Type symbol Amount */
618  gtk_list_store_append(context->list_store, &ent_data);
619  gtk_list_store_set(context->list_store, &ent_data,
620  COL_EXP_DATE, date_string,
621  COL_EXP_TYPE, type,
622  COL_EXP_SYMBOL, symbol,
623  COL_EXP_AMOUNT, d_amount,
624  COL_ENTITY, ent,
625  -1 );
626  g_free(date_string);
627 }
628 
629 /* button == 1 left mouse button, 3 = right click. */
630 static gboolean
631 button_press_event (GtkWidget *widget, GdkEventButton *b, gpointer data)
632 {
633  GtkTreeViewColumn *col;
634  GtkTreePath *path;
635  GtkTreeIter iter;
636  QofEntity *ent;
637  gdouble amount;
638  GpeExpenseData *context;
639 
640  context = (GpeExpenseData*)data;
641  g_return_val_if_fail(context, FALSE);
642  amount = 0.00;
643  ent = NULL;
644  switch (b->button) {
645  case 1 : {
646  if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
647  b->x, b->y, &path, &col, NULL, NULL))
648  {
649  GtkTreeView * treeview;
650 
651  treeview = GTK_TREE_VIEW
652  (gtk_tree_view_new_with_model
653  (GTK_TREE_MODEL(context->list_store)));
654  gtk_tree_view_set_cursor (treeview, path, NULL, TRUE);
655  gtk_widget_grab_focus (GTK_WIDGET (treeview));
656  gtk_tree_model_get_iter (GTK_TREE_MODEL (context->list_store),
657  &iter, path);
658  gtk_tree_model_get(GTK_TREE_MODEL (context->list_store),
659  &iter, COL_ENTITY, &ent, -1);
660  gtk_tree_model_get(GTK_TREE_MODEL (context->list_store),
661  &iter, COL_EXP_AMOUNT, &amount, -1);
662  context->entity = ent;
663  gtk_tree_path_free (path);
664  }
665  break;
666  }
667  default : { break; }
668  }
669  return FALSE;
670 }
671 
681 static void
682 change_date (GtkCellRendererText G_GNUC_UNUSED *cell,
683  gchar G_GNUC_UNUSED *path_string, gchar *new_text, gpointer data)
684 {
685  void (*time_setter) (QofEntity*, QofTime*);
686  GpeExpenseData *context;
687  const QofParam *param;
688  QofTime *qt;
689  QofDate *qd;
690 
691  context = (GpeExpenseData*)data;
692  g_return_if_fail(context);
693  ENTER (" new_text=%s", new_text);
694  param = qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_DATE);
695  time_setter = (void(*)(QofEntity*, QofTime*))param->param_setfcn;
696  /* convert the string to a QofTime */
697  qd = qof_date_parse (new_text, QOF_DATE_FORMAT_CE);
698  qt = qof_date_to_qtime (qd);
699  qof_util_param_edit ((QofInstance*)context->entity, param);
700  if ((time_setter && qof_time_is_valid (qt)))
701  time_setter (context->entity, qt);
702  qof_util_param_commit ((QofInstance*)context->entity, param);
703  exp_refresh_list(context);
704  LEAVE (" ");
705 }
706 
707 static void
708 change_amount (GtkCellRendererText G_GNUC_UNUSED *cell,
709  gchar G_GNUC_UNUSED *path_string, gchar *new_text, gpointer data)
710 {
711  GpeExpenseData *context;
712  QofNumeric amount;
713  void (*numeric_setter) (QofEntity*, QofNumeric);
714  void (*i32_setter) (QofEntity*, gint32);
715  const QofParam *param;
716  gchar *numeric_char;
717  gdouble d_amount;
718  gint precision;
719  /* detect local currency */
720  QofCurrency * c;
721  struct lconv lc;
722  gchar * sy;
723  guint def_pq_code;
724 
725  context = (GpeExpenseData*)data;
726  g_return_if_fail(context);
727  ENTER (" ");
728  def_pq_code = -1;
729  param = qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_CURRENCY);
730  i32_setter = (void(*)(QofEntity*, gint32))param->param_setfcn;
731  /* use locale currency */
732  lc = *localeconv();
733  DEBUG (" sym=%s", lc.currency_symbol);
734  sy = g_strdup (lc.int_curr_symbol);
735  sy = g_strstrip (sy);
736  c = qof_currency_lookup_name ((QofInstance*)context->entity, sy);
737  if (c)
738  def_pq_code = c->pq_code;
739  g_free (sy);
740  if(i32_setter != NULL) { i32_setter(context->entity, def_pq_code); }
741  param = qof_class_get_parameter(GPE_QOF_EXPENSES, EXP_AMOUNT);
742  numeric_setter = (void(*)(QofEntity*, QofNumeric))param->param_setfcn;
743  d_amount = 0.00;
744  precision = pow(10, SHOW_DECIMAL_PLACES);
745  amount = qof_numeric_zero();
746  d_amount = strtod(new_text, NULL);
747  amount = qof_numeric_from_double (d_amount, precision,
748  QOF_HOW_DENOM_EXACT | QOF_HOW_RND_ROUND);
749  numeric_char = qof_numeric_to_string(amount);
750  DEBUG (" numeric_char=%s", numeric_char);
751  g_free(numeric_char);
752 
753  if ((qof_numeric_check (amount) == QOF_ERROR_OK) && (numeric_setter))
754  {
755  qof_util_param_edit ((QofInstance*)context->entity, param);
756  numeric_setter(context->entity, amount);
757  qof_util_param_commit ((QofInstance*)context->entity, param);
758  }
759  else
760  {
761  qof_util_param_edit ((QofInstance*)context->entity, param);
762  numeric_setter(context->entity, qof_numeric_zero ());
763  qof_util_param_commit ((QofInstance*)context->entity, param);
764  }
765  exp_refresh_list(context);
766  LEAVE (" ");
767 }
768 
769 static void
770 rounding_func (GtkTreeViewColumn G_GNUC_UNUSED *tree_column,
771  GtkCellRenderer *cell, GtkTreeModel *tree_model,
772  GtkTreeIter *iter, gpointer data)
773 {
774  GtkCellRendererText *cell_text;
775  gdouble d;
776  gchar *s;
777 
778  cell_text = (GtkCellRendererText *)cell;
779  d = 0.00;
780  s = NULL;
781  g_free(cell_text->text);
782  gtk_tree_model_get(tree_model, iter, GPOINTER_TO_INT(data), &d, -1);
783  s = g_strdup_printf("%%.%i", SHOW_DECIMAL_PLACES);
784  s = g_strconcat(s, "f", NULL);
785  cell_text->text = g_strdup_printf(s, d);
786  g_free(s);
787 }
788 
789 static gint
790 type_compare (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b,
791  gpointer G_GNUC_UNUSED user_data)
792 {
793  gint result;
794  gchar *str_a, *str_b;
795 
796  result = 0;
797  gtk_tree_model_get(model, a, COL_EXP_TYPE, &str_a, -1);
798  gtk_tree_model_get(model, b, COL_EXP_TYPE, &str_b, -1);
799  result = safe_strcmp(str_a, str_b);
800  g_free(str_a);
801  g_free(str_b);
802  return result;
803 }
804 
811 static gint
812 date_compare (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b,
813  gpointer G_GNUC_UNUSED user_data)
814 {
815  QofTime *qt_a, *qt_b;
816  QofEntity * ent_a, * ent_b;
817  const QofParam * param;
818  gint result;
819 
820  ent_a = ent_b = NULL;
821  result = 0;
822  gtk_tree_model_get(model, a, COL_ENTITY, &ent_a, -1);
823  gtk_tree_model_get(model, b, COL_ENTITY, &ent_b, -1);
824  param = qof_class_get_parameter (GPE_QOF_EXPENSES, EXP_DATE);
825  qt_a = param->param_getfcn (ent_a, param);
826  qt_b = param->param_getfcn (ent_b, param);
827  result = qof_time_cmp (qt_a, qt_b);
828  qof_time_free (qt_a);
829  qof_time_free (qt_b);
830  return result;
831 }
832 
833 static gint
834 amount_compare (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b,
835  gpointer G_GNUC_UNUSED user_data)
836 {
837  gint result;
838  gdouble *dbl_a, *dbl_b;
839 
840  result = 0;
841  gtk_tree_model_get(model, a, COL_EXP_AMOUNT, &dbl_a, -1);
842  gtk_tree_model_get(model, b, COL_EXP_AMOUNT, &dbl_b, -1);
843  if(dbl_a != dbl_b)
844  result = (dbl_a > dbl_b) ? 1 : -1;
845  return result;
846 }
847 
848 static GtkWidget*
849 set_list_view(GpeExpenseData *context)
850 {
851  GtkTreeView *view;
852  GtkTreeSortable *sort;
853  GtkWidget *scrolled;
854  GtkCellRenderer *date_rend, *type_rend, *symbol_rend, *amount_rend;
855  GtkTreeViewColumn *col;
856 
857  scrolled = gtk_scrolled_window_new (NULL, NULL);
858  context->list_store = gtk_list_store_new(COL_MAX, G_TYPE_STRING,
859  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE, G_TYPE_POINTER);
860  sort = GTK_TREE_SORTABLE (context->list_store);
861  view = GTK_TREE_VIEW (gtk_tree_view_new_with_model
862  (GTK_TREE_MODEL (sort)));
863 
864  date_rend = gtk_cell_renderer_text_new ();
865  col = gtk_tree_view_column_new_with_attributes ((_("Date")), date_rend,
866  "text", COL_EXP_DATE, NULL);
867  g_object_set(date_rend, "editable", TRUE, NULL);
868  g_object_set(col, "reorderable", TRUE, NULL);
869  g_object_set(col, "sort-indicator", TRUE, NULL);
870  gtk_tree_view_column_set_sort_column_id(col, COL_EXP_DATE);
871  gtk_tree_sortable_set_sort_func (sort, COL_EXP_DATE, date_compare, NULL, NULL);
872  gtk_tree_view_column_set_expand(col, TRUE);
873  gtk_tree_view_column_set_clickable(col, TRUE);
874  gtk_tree_view_insert_column (GTK_TREE_VIEW (view), col, -1);
875  g_signal_connect(date_rend, "edited",
876  (GCallback) change_date, context);
877 
878  type_rend = gtk_cell_renderer_text_new ();
879  col = gtk_tree_view_column_new_with_attributes (_("Type"), type_rend,
880  "text", COL_EXP_TYPE, NULL);
881  gtk_tree_view_column_set_sort_column_id(col, COL_EXP_TYPE);
882  gtk_tree_sortable_set_sort_func (sort, COL_EXP_TYPE, type_compare, NULL, NULL);
883  gtk_tree_view_column_set_expand(col, TRUE);
884  gtk_tree_view_column_set_clickable(col, TRUE);
885  g_object_set(col, "reorderable", TRUE, NULL);
886  g_object_set(col, "sort-indicator", TRUE, NULL);
887  gtk_tree_view_insert_column (GTK_TREE_VIEW (view), col, -1);
888 
889  symbol_rend = gtk_cell_renderer_text_new ();
890  col = gtk_tree_view_column_new_with_attributes ("", symbol_rend,
891  "text", COL_EXP_SYMBOL, NULL);
892  gtk_tree_view_column_set_expand(col, TRUE);
893  gtk_tree_view_column_set_clickable(col, TRUE);
894  gtk_tree_view_insert_column (GTK_TREE_VIEW (view), col, -1);
895 
896  amount_rend = gtk_cell_renderer_text_new ();
897  col = gtk_tree_view_column_new_with_attributes (_("Amount"), amount_rend,
898  "text", COL_EXP_AMOUNT, NULL);
899  g_object_set(amount_rend, "editable", TRUE, NULL);
900  g_object_set(col, "reorderable", TRUE, NULL);
901  g_object_set(col, "sort-indicator", TRUE, NULL);
902  gtk_tree_view_column_set_sort_column_id(col, COL_EXP_AMOUNT);
903  gtk_tree_sortable_set_sort_func (sort, COL_EXP_AMOUNT, amount_compare, NULL, NULL);
904  gtk_tree_view_column_set_expand(col, TRUE);
905  gtk_tree_view_column_set_clickable(col, TRUE);
906  gtk_tree_view_insert_column (GTK_TREE_VIEW (view), col, -1);
907  gtk_tree_view_column_set_cell_data_func(col, amount_rend,
908  rounding_func, GINT_TO_POINTER(COL_EXP_AMOUNT), NULL);
909  g_signal_connect(amount_rend, "edited",
910  (GCallback) change_amount, context);
911 
912  g_signal_connect (G_OBJECT (view), "button_press_event",
913  G_CALLBACK (button_press_event), context);
914 
915  gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET(view));
916  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
917  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
918  gtk_tree_sortable_set_sort_column_id (sort, COL_EXP_DATE,
919  GTK_SORT_DESCENDING);
920  return GTK_WIDGET(scrolled);
921 }
922 
923 static void
924 exp_refresh_list (GpeExpenseData *context)
925 {
926  gtk_list_store_clear(context->list_store);
927  /* Populate the list from qof_object_foreach */
928  qof_object_foreach(GPE_QOF_EXPENSES, context->book, exp_show_entities, context);
929 }
930 
931 static void
932 open_new_expense (GtkWidget G_GNUC_UNUSED *w, GpeExpenseData *context)
933 {
934  GtkTreeIter ent_data;
935  QofInstance *inst;
936  QofBook *book;
937  const QofParam *param;
938  void (*i32_setter) (QofEntity*, gint32);
939  /* detect local currency */
940  QofCurrency * c;
941  struct lconv lc;
942  gchar * sy;
943  guint def_pq_code;
944 
945  g_return_if_fail(context);
946  def_pq_code = -1;
947  book = qof_session_get_book(context->qof.input_session);
948  inst = (QofInstance*)qof_object_new_instance(GPE_QOF_EXPENSES, book);
949  context->entity = &inst->entity;
950  /* use locale currency */
951  lc = *localeconv();
952  DEBUG (" sym=%s", lc.currency_symbol);
953  sy = g_strdup (lc.int_curr_symbol);
954  sy = g_strstrip (sy);
955  c = qof_currency_lookup_name (inst, sy);
956  if (c)
957  def_pq_code = c->pq_code;
958  g_free (sy);
959  param = qof_class_get_parameter (GPE_QOF_EXPENSES, EXP_CURRENCY);
960  i32_setter = (void(*)(QofEntity*, gint32))param->param_setfcn;
961  qof_util_param_edit ((QofInstance*)context->entity, param);
962  i32_setter(context->entity, def_pq_code);
963  qof_util_param_commit ((QofInstance*)context->entity, param);
964  gtk_list_store_append(context->list_store, &ent_data);
965  exp_refresh_list(context);
966 }
967 
968 static GtkWidget*
969 set_toolbar (GpeExpenseData *context)
970 {
971  GtkWidget *toolbar;
972  GtkToolItem *new_exp, *quit_exp, *about_exp, *item, *edit_exp;
973  toolbar = gtk_toolbar_new ();
974  gtk_toolbar_set_orientation (GTK_TOOLBAR (toolbar), GTK_ORIENTATION_HORIZONTAL);
975 
976  new_exp = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
977  g_signal_connect(G_OBJECT(new_exp), "clicked",
978  G_CALLBACK (open_new_expense), context);
979  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), new_exp, -1);
980 
981  edit_exp = gtk_tool_button_new_from_stock(GTK_STOCK_PROPERTIES);
982  g_signal_connect(G_OBJECT(edit_exp), "clicked",
983  G_CALLBACK (edit_expense), context);
984  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), edit_exp, -1);
985 
986  item = gtk_separator_tool_item_new();
987  gtk_tool_item_set_expand(GTK_TOOL_ITEM(item), FALSE);
988  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
989 
990  about_exp = gtk_tool_button_new_from_stock(GTK_STOCK_ABOUT);
991  g_signal_connect(G_OBJECT(about_exp), "clicked",
992  G_CALLBACK (open_about_dialog), NULL);
993  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), about_exp, -1);
994 
995  item = gtk_separator_tool_item_new();
996  gtk_tool_item_set_expand(GTK_TOOL_ITEM(item), FALSE);
997  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
998 
999  quit_exp = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT);
1000  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), quit_exp, -1);
1001  g_signal_connect (G_OBJECT (quit_exp), "clicked",
1002  G_CALLBACK (gtk_main_quit), NULL);
1003  return GTK_WIDGET(toolbar);
1004 }
1005 
1006 void
1008 {
1009  GtkWidget *window;
1010  GtkWidget *vbox;
1011 
1012  g_return_if_fail(context);
1013  ENTER (" ");
1014  vbox = gtk_vbox_new (FALSE, 0);
1015 
1016  gpe_pim_categories_init ();
1017  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1018  gtk_window_set_default_size (GTK_WINDOW (window), 240, 320);
1019  gtk_window_set_title (GTK_WINDOW (window), _("Expenses"));
1020  gpe_set_window_icon (window, "icon");
1021 
1022  gtk_box_pack_start (GTK_BOX (vbox), set_toolbar(context), FALSE, FALSE, 0);
1023  g_signal_connect (G_OBJECT (window), "delete-event",
1024  G_CALLBACK (gtk_main_quit), NULL);
1025 
1026  gtk_box_pack_start (GTK_BOX (vbox), set_list_view(context), TRUE, TRUE, 0);
1027 
1028  gtk_container_add (GTK_CONTAINER (window), vbox);
1029  gtk_widget_show_all (window);
1030  /* Populate the list from qof_object_foreach */
1031  qof_object_foreach(GPE_QOF_EXPENSES, context->book,
1032  exp_show_entities, context);
1033  LEAVE (" ");
1034 }