dialog-fincalc.c

00001 /********************************************************************\
00002  * dialog-fincalc.c : dialog for a financial calculator             *
00003  * Copyright (C) 2000 Dave Peticolas <dave@krondo.com>              *
00004  * Copyright (c) 2006 David Hampton <hampton@employees.org>         *
00005  *                                                                  *
00006  * This program is free software; you can redistribute it and/or    *
00007  * modify it under the terms of the GNU General Public License as   *
00008  * published by the Free Software Foundation; either version 2 of   *
00009  * the License, or (at your option) any later version.              *
00010  *                                                                  *
00011  * This program is distributed in the hope that it will be useful,  *
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00014  * GNU General Public License for more details.                     *
00015  *                                                                  *
00016  * You should have received a copy of the GNU General Public License*
00017  * along with this program; if not, contact:                        *
00018  *                                                                  *
00019  * Free Software Foundation           Voice:  +1-617-542-5942       *
00020  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00021  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00022 \********************************************************************/
00023 
00024 #include "config.h"
00025 
00026 #include <gtk/gtk.h>
00027 #include <glib/gi18n.h>
00028 #include <locale.h>
00029 #include <time.h>
00030 
00031 #include "dialog-fincalc.h"
00032 #include "dialog-utils.h"
00033 #include "finproto.h"
00034 #include "finvar.h"
00035 #include "gnc-amount-edit.h"
00036 #include "gnc-commodity.h"
00037 #include "gnc-component-manager.h"
00038 #include "gnc-date-edit.h"
00039 #include "gnc-engine.h"
00040 #include "gnc-gui-query.h"
00041 
00042 
00043 #define DIALOG_FINCALC_CM_CLASS "dialog-fincalc"
00044 #define GCONF_SECTION "dialogs/fincalc"
00045 
00046 typedef enum
00047 {
00048   PAYMENT_PERIODS = 0,
00049   INTEREST_RATE,
00050   PRESENT_VALUE,
00051   PERIODIC_PAYMENT,
00052   FUTURE_VALUE,
00053   NUM_FIN_CALC_VALUES
00054 } FinCalcValue;
00055 
00056 
00058 struct _FinCalcDialog
00059 {
00060   GladeXML *xml;
00061   GtkWidget *dialog;
00062 
00063   GtkWidget *amounts[NUM_FIN_CALC_VALUES];
00064 
00065   GtkWidget *calc_button;
00066 
00067   GtkWidget *compounding_combo;
00068   GtkWidget *payment_combo;
00069 
00070   GtkWidget *end_of_period_radio;
00071   GtkWidget *discrete_compounding_radio;
00072 
00073   GtkWidget *payment_total_label;
00074 
00075   financial_info financial_info;
00076 };
00077 
00078 static unsigned int periods[] =
00079 {
00080     1, /* annual */
00081     2, /* semi-annual */
00082     3, /* tri-annual */
00083     4, /* quarterly */
00084     6, /* bi-monthly */
00085    12, /* monthly */
00086    24, /* semi-monthly */
00087    26, /* bi-weekly */
00088    52, /* weekly */
00089   360, /* daily (360) */
00090   365, /* daily (365) */
00091 };
00092 
00093 /* This static indicates the debugging module that this .o belongs to.  */
00094 static QofLogModule log_module = GNC_MOD_GUI;
00095 
00096 
00098 void fincalc_update_calc_button_cb(GtkWidget *unused, FinCalcDialog *fcd);
00099 void fincalc_calc_clicked_cb(GtkButton *button, FinCalcDialog *fcd);
00100 void fincalc_compounding_radio_toggled(GtkToggleButton *togglebutton, gpointer data);
00101 void fincalc_amount_clear_clicked_cb(GtkButton *button, GNCAmountEdit *edit);
00102 void fincalc_response_cb (GtkDialog *dialog, gint response, FinCalcDialog *fcd);
00103 
00106 /* Ensure the given argument is one of the values in the periods array
00107  * above and return the index of the value. */
00108 static int
00109 normalize_period(unsigned int *period)
00110 {
00111   int i;
00112 
00113   g_return_val_if_fail (period, 0);
00114 
00115   for (i = (sizeof(periods) / sizeof(unsigned int)) - 1; i >= 0; i--)
00116     if (*period >= periods[i])
00117     {
00118       *period = periods[i];
00119       return i;
00120     }
00121 
00122   *period = periods[0];
00123 
00124   return 0;
00125 }
00126 
00127 /* Copy the values in the financial_info structure to the GUI */
00128 static void
00129 fi_to_gui(FinCalcDialog *fcd)
00130 {
00131   const gnc_commodity *commodity;
00132   static char string[64];
00133   gnc_numeric total;
00134   gnc_numeric npp;
00135   gnc_numeric pmt;
00136   int i;
00137 
00138   if (fcd == NULL)
00139     return;
00140 
00141   npp = gnc_numeric_create (fcd->financial_info.npp, 1);
00142 
00143   gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT(fcd->amounts[PAYMENT_PERIODS]),
00144                               npp);
00145   gnc_amount_edit_set_damount (GNC_AMOUNT_EDIT(fcd->amounts[INTEREST_RATE]),
00146                                fcd->financial_info.ir);
00147   gnc_amount_edit_set_damount (GNC_AMOUNT_EDIT(fcd->amounts[PRESENT_VALUE]),
00148                                fcd->financial_info.pv);
00149   gnc_amount_edit_set_damount (GNC_AMOUNT_EDIT(fcd->amounts[PERIODIC_PAYMENT]),
00150                                fcd->financial_info.pmt);
00151   gnc_amount_edit_set_damount (GNC_AMOUNT_EDIT(fcd->amounts[FUTURE_VALUE]),
00152                                -fcd->financial_info.fv);
00153 
00154   pmt = double_to_gnc_numeric (fcd->financial_info.pmt, 100000, GNC_RND_ROUND);
00155 
00156   commodity = gnc_default_currency ();
00157 
00158   total = gnc_numeric_mul (npp, pmt, gnc_commodity_get_fraction (commodity),
00159                            GNC_RND_ROUND);
00160 
00161   xaccSPrintAmount (string, total, gnc_default_print_info (FALSE));
00162   gtk_label_set_text (GTK_LABEL(fcd->payment_total_label), string);
00163 
00164   i = normalize_period(&fcd->financial_info.CF);
00165   gtk_combo_box_set_active(GTK_COMBO_BOX(fcd->compounding_combo), i);
00166 
00167   i = normalize_period(&fcd->financial_info.PF);
00168   gtk_combo_box_set_active(GTK_COMBO_BOX(fcd->payment_combo), i);
00169 
00170   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fcd->end_of_period_radio),
00171                                !fcd->financial_info.bep);
00172 
00173   gtk_toggle_button_set_active
00174     (GTK_TOGGLE_BUTTON(fcd->discrete_compounding_radio),
00175      fcd->financial_info.disc);
00176 }
00177 
00178 /* Copy the values in the GUI to the financial_info structure */
00179 static void
00180 gui_to_fi(FinCalcDialog *fcd)
00181 {
00182   GtkToggleButton *toggle;
00183   gnc_numeric npp;
00184   int i;
00185 
00186   if (fcd == NULL)
00187     return;
00188 
00189   npp =
00190     gnc_amount_edit_get_amount(GNC_AMOUNT_EDIT(fcd->amounts[PAYMENT_PERIODS]));
00191   fcd->financial_info.npp = npp.num;
00192 
00193   fcd->financial_info.ir =
00194     gnc_amount_edit_get_damount(GNC_AMOUNT_EDIT(fcd->amounts[INTEREST_RATE]));
00195 
00196   fcd->financial_info.pv =
00197     gnc_amount_edit_get_damount(GNC_AMOUNT_EDIT(fcd->amounts[PRESENT_VALUE]));
00198 
00199   fcd->financial_info.pmt =
00200     gnc_amount_edit_get_damount(GNC_AMOUNT_EDIT(fcd->amounts[PERIODIC_PAYMENT]));
00201 
00202   fcd->financial_info.fv =
00203     gnc_amount_edit_get_damount(GNC_AMOUNT_EDIT(fcd->amounts[FUTURE_VALUE]));
00204   fcd->financial_info.fv = -fcd->financial_info.fv;
00205 
00206   i = gtk_combo_box_get_active(GTK_COMBO_BOX(fcd->compounding_combo));
00207   fcd->financial_info.CF = periods[i];
00208 
00209   i = gtk_combo_box_get_active(GTK_COMBO_BOX(fcd->payment_combo));
00210   fcd->financial_info.PF = periods[i];
00211 
00212   toggle = GTK_TOGGLE_BUTTON(fcd->end_of_period_radio);
00213   fcd->financial_info.bep = !gtk_toggle_button_get_active(toggle);
00214 
00215   toggle = GTK_TOGGLE_BUTTON(fcd->discrete_compounding_radio);
00216   fcd->financial_info.disc = gtk_toggle_button_get_active(toggle);
00217 
00218   fcd->financial_info.prec = gnc_locale_decimal_places ();
00219 }
00220 
00221 /* Set the sensitivity of the calculation buttons based on the argument. */
00222 void
00223 fincalc_update_calc_button_cb(GtkWidget *unused, FinCalcDialog *fcd)
00224 {
00225   const gchar *text;
00226   gint i;
00227 
00228   if (fcd == NULL)
00229     return;
00230 
00231   for (i = 0; i < NUM_FIN_CALC_VALUES; i++) {
00232     text = gtk_entry_get_text(GTK_ENTRY(fcd->amounts[i]));
00233     if ((text == NULL) || (*text == '\0')) {
00234       gtk_widget_set_sensitive(GTK_WIDGET(fcd->calc_button), TRUE);
00235       return;
00236     }
00237   }
00238 
00239   gtk_widget_set_sensitive(GTK_WIDGET(fcd->calc_button), FALSE);
00240 }
00241 
00242 /* Free the calc button list and free the FinCalcDialog structure. */
00243 static void
00244 fincalc_dialog_destroy(GtkObject *object, gpointer data)
00245 {
00246   FinCalcDialog *fcd = data;
00247 
00248   if (fcd == NULL)
00249     return;
00250 
00251   gnc_unregister_gui_component_by_data (DIALOG_FINCALC_CM_CLASS, fcd);
00252 
00253   g_object_unref(fcd->xml);
00254   g_free(fcd);
00255 }
00256 
00257 void
00258 fincalc_compounding_radio_toggled(GtkToggleButton *togglebutton, gpointer data)
00259 {
00260   FinCalcDialog *fcd = data;
00261   gboolean sensitive;
00262 
00263   if (fcd == NULL)
00264     return;
00265 
00266   fincalc_update_calc_button_cb(GTK_WIDGET(togglebutton), fcd);
00267 
00268   sensitive = gtk_toggle_button_get_active (togglebutton);
00269 
00270   gtk_widget_set_sensitive (fcd->compounding_combo, sensitive);
00271 }
00272 
00273 void
00274 fincalc_amount_clear_clicked_cb(GtkButton *button, GNCAmountEdit *edit)
00275 {
00276   gtk_entry_set_text(GTK_ENTRY (edit), "");
00277 }
00278 
00279 static void
00280 init_fi(FinCalcDialog *fcd)
00281 {
00282   struct lconv *lc;
00283 
00284   if (fcd == NULL)
00285     return;
00286 
00287   lc = gnc_localeconv();
00288 
00289   fcd->financial_info.npp = 12;
00290   fcd->financial_info.ir = 8.5;
00291   fcd->financial_info.pv = 15000.0;
00292   fcd->financial_info.pmt = -400.0;
00293   fcd->financial_info.CF = 12;
00294   fcd->financial_info.PF = 12;
00295   fcd->financial_info.bep = FALSE;
00296   fcd->financial_info.disc = TRUE;
00297   fcd->financial_info.prec = lc->frac_digits;
00298 
00299   fi_calc_future_value(&fcd->financial_info);
00300 }
00301 
00302 /* Determine whether the value can be calculated. If it can, return
00303  * NULL. Otherwise, return a string describing the reason and the offending
00304  * entry in error_item. */
00305 static const char *
00306 can_calc_value(FinCalcDialog *fcd, FinCalcValue value, int *error_item)
00307 {
00308   const char *missing = _("This program can only calculate one value at a time. "
00309                           "You must enter values for all but one quantity.");
00310   const char *bad_exp = _("GnuCash cannot determine the value in one of the fields. "
00311                           "You must enter a valid expression.");
00312   const char *string;
00313   gnc_numeric nvalue;
00314   unsigned int i;
00315 
00316   if (fcd == NULL)
00317     return NULL;
00318 
00319   /* Check for missing values */
00320   for (i = 0; i < NUM_FIN_CALC_VALUES; i++)
00321     if (i != value)
00322     {
00323       string = gtk_entry_get_text(GTK_ENTRY(fcd->amounts[i]));
00324       if ((string == NULL) || (*string == '\0'))
00325       {
00326         *error_item = i;
00327         return missing;
00328       }
00329 
00330       if (!gnc_amount_edit_evaluate (GNC_AMOUNT_EDIT (fcd->amounts[i])))
00331       {
00332         *error_item = i;
00333         return bad_exp;
00334       }
00335     }
00336 
00337   /* Check for zero interest */
00338   switch (value)
00339   {
00340     case PAYMENT_PERIODS:
00341     case PRESENT_VALUE:
00342     case PERIODIC_PAYMENT:
00343     case FUTURE_VALUE:
00344       nvalue = gnc_amount_edit_get_amount
00345         (GNC_AMOUNT_EDIT (fcd->amounts[INTEREST_RATE]));
00346       if (gnc_numeric_zero_p (nvalue))
00347       {
00348         *error_item = INTEREST_RATE;
00349         return _("The interest rate cannot be zero.");
00350       }
00351       break;
00352     default:
00353       break;
00354   }
00355 
00356   /* Check for zero payment periods */
00357   switch (value)
00358   {
00359     case INTEREST_RATE:
00360     case PRESENT_VALUE:
00361     case PERIODIC_PAYMENT:
00362     case FUTURE_VALUE:
00363       nvalue = gnc_amount_edit_get_amount
00364         (GNC_AMOUNT_EDIT(fcd->amounts[PAYMENT_PERIODS]));
00365       if (gnc_numeric_zero_p (nvalue))
00366       {
00367         *error_item = PAYMENT_PERIODS;
00368         return _("The number of payments cannot be zero.");
00369       }
00370       if (gnc_numeric_negative_p (nvalue))
00371       {
00372         *error_item = PAYMENT_PERIODS;
00373         return _("The number of payments cannot be negative.");
00374       }
00375       break;
00376     default:
00377       break;
00378   }
00379 
00380   return NULL;
00381 }
00382 
00383 static void
00384 calc_value(FinCalcDialog *fcd, FinCalcValue value)
00385 {
00386   const char *string;
00387   int error_item = 0;
00388 
00389   if (fcd == NULL)
00390     return;
00391 
00392   string = can_calc_value(fcd, value, &error_item);
00393   if (string != NULL)
00394   {
00395     GtkWidget *entry;
00396 
00397     gnc_error_dialog(fcd->dialog, "%s", string);
00398     if (error_item == 0)
00399       entry = fcd->amounts[0];
00400     else
00401       entry = fcd->amounts[error_item];
00402     gtk_widget_grab_focus (entry);
00403     return;
00404   }
00405 
00406   gui_to_fi(fcd);
00407 
00408   switch (value)
00409   {
00410     case PAYMENT_PERIODS:
00411       fi_calc_num_payments(&fcd->financial_info);
00412       break;
00413     case INTEREST_RATE:
00414       fi_calc_interest(&fcd->financial_info);
00415       break;
00416     case PRESENT_VALUE:
00417       fi_calc_present_value(&fcd->financial_info);
00418       break;
00419     case PERIODIC_PAYMENT:
00420       fi_calc_payment(&fcd->financial_info);
00421       break;
00422     case FUTURE_VALUE:
00423       fi_calc_future_value(&fcd->financial_info);
00424       break;
00425     default:
00426       PERR("Unknown financial variable");
00427       break;
00428   }
00429 
00430   fi_to_gui(fcd);
00431 
00432   gtk_widget_set_sensitive(GTK_WIDGET(fcd->calc_button), FALSE);
00433 }
00434 
00435 void
00436 fincalc_calc_clicked_cb(GtkButton *button, FinCalcDialog *fcd)
00437 {
00438   const gchar *text;
00439   gint i;
00440 
00441   for (i = 0; i < NUM_FIN_CALC_VALUES; i++) {
00442     text = gtk_entry_get_text(GTK_ENTRY(fcd->amounts[i]));
00443     if ((text != NULL) && (*text != '\0'))
00444       continue;
00445     calc_value(fcd, i);
00446     return;
00447   }
00448 }
00449 
00450 void fincalc_response_cb (GtkDialog *dialog,
00451                           gint response,
00452                           FinCalcDialog *fcd)
00453 {
00454   switch (response) {
00455     case GTK_RESPONSE_OK:
00456       /* Do something here whenever the hidden schedule button is clicked. */
00457       /* Fall through */
00458 
00459     case GTK_RESPONSE_CLOSE:
00460       gnc_save_window_size(GCONF_SECTION, GTK_WINDOW(dialog));
00461       break;
00462 
00463     default:
00464       /* Cancel, destroy, etc.  Do nothing. */
00465       break;
00466   }
00467 
00468   gnc_close_gui_component_by_data (DIALOG_FINCALC_CM_CLASS, fcd);
00469 }
00470 
00471 
00472 static void
00473 close_handler (gpointer user_data)
00474 {
00475   FinCalcDialog *fcd = user_data;
00476 
00477   gtk_widget_destroy (fcd->dialog);
00478 }
00479 
00480 static gboolean
00481 show_handler (const char *class, gint component_id,
00482               gpointer user_data, gpointer iter_data)
00483 {
00484   FinCalcDialog *fcd = user_data;
00485 
00486   if (!fcd)
00487     return(FALSE);
00488   gtk_window_present (GTK_WINDOW(fcd->dialog));
00489   return(TRUE);
00490 }
00491 
00492 
00505 static void
00506 fincalc_init_gae (GNCAmountEdit *edit,
00507                   gint min_places,
00508                   gint max_places,
00509                   gint fraction)
00510 {
00511   GNCPrintAmountInfo print_info;
00512 
00513   print_info = gnc_integral_print_info ();
00514   print_info.min_decimal_places = min_places;
00515   print_info.max_decimal_places = max_places;
00516 
00517   gnc_amount_edit_set_print_info (edit, print_info);
00518   gnc_amount_edit_set_fraction (edit, fraction);
00519   gnc_amount_edit_set_evaluate_on_enter (edit, TRUE);
00520   gtk_entry_set_alignment (GTK_ENTRY(edit), 1.0);
00521 }
00522 
00528 static void
00529 fincalc_init_commodity_gae (GNCAmountEdit *edit)
00530 {
00531   GNCPrintAmountInfo print_info;
00532   gnc_commodity *commodity;
00533   gint fraction;
00534 
00535   commodity = gnc_default_currency();
00536   fraction = gnc_commodity_get_fraction(commodity);
00537   print_info = gnc_commodity_print_info (commodity, FALSE);
00538 
00539   gnc_amount_edit_set_print_info (edit, print_info);
00540   gnc_amount_edit_set_fraction (edit, fraction);
00541   gnc_amount_edit_set_evaluate_on_enter (edit, TRUE);
00542   gtk_entry_set_alignment (GTK_ENTRY(edit), 1.0);
00543 }
00544 
00545 void
00546 gnc_ui_fincalc_dialog_create(void)
00547 {
00548   FinCalcDialog *fcd;
00549   GtkWidget *button;
00550   GtkWidget *combo;
00551   GtkWidget *edit;
00552   GladeXML  *xml;
00553 
00554   if (gnc_forall_gui_components (DIALOG_FINCALC_CM_CLASS,
00555                                  show_handler, NULL))
00556       return;
00557 
00558 
00559   fcd = g_new0(FinCalcDialog, 1);
00560 
00561   xml = gnc_glade_xml_new ("fincalc.glade", "Financial Calculator Dialog");
00562 
00563   fcd->xml = xml;
00564   fcd->dialog = glade_xml_get_widget (xml, "Financial Calculator Dialog");
00565 
00566   gnc_register_gui_component (DIALOG_FINCALC_CM_CLASS,
00567                               NULL, close_handler, fcd);
00568 
00569   g_signal_connect (G_OBJECT (fcd->dialog), "destroy",
00570                     G_CALLBACK (fincalc_dialog_destroy), fcd);
00571 
00572 
00573   edit = glade_xml_get_widget (xml, "payment_periods_edit");
00574   fincalc_init_gae (GNC_AMOUNT_EDIT (edit), 0, 0, 1);
00575   fcd->amounts[PAYMENT_PERIODS] = edit;
00576 
00577   edit = glade_xml_get_widget (xml, "interest_rate_edit");
00578   fincalc_init_gae (GNC_AMOUNT_EDIT (edit), 2, 5, 100000);
00579   fcd->amounts[INTEREST_RATE] = edit;
00580 
00581   edit = glade_xml_get_widget (xml, "present_value_edit");
00582   fincalc_init_commodity_gae (GNC_AMOUNT_EDIT (edit));
00583   fcd->amounts[PRESENT_VALUE] = edit;
00584 
00585   edit = glade_xml_get_widget (xml, "period_payment_edit");
00586   fincalc_init_commodity_gae (GNC_AMOUNT_EDIT (edit));
00587   fcd->amounts[PERIODIC_PAYMENT] = edit;
00588 
00589   edit = glade_xml_get_widget (xml, "future_value_edit");
00590   fincalc_init_commodity_gae (GNC_AMOUNT_EDIT (edit));
00591   fcd->amounts[FUTURE_VALUE] = edit;
00592 
00593 
00594   fcd->calc_button = glade_xml_get_widget (xml, "calc_button");
00595 
00596 
00597   combo = glade_xml_get_widget (xml, "compounding_combo");
00598   fcd->compounding_combo = combo;
00599   g_signal_connect(fcd->compounding_combo, "changed",
00600                    G_CALLBACK (fincalc_update_calc_button_cb), fcd);
00601 
00602   combo = glade_xml_get_widget (xml, "payment_combo");
00603   fcd->payment_combo = combo;
00604   g_signal_connect(fcd->compounding_combo, "changed",
00605                    G_CALLBACK (fincalc_update_calc_button_cb), fcd);
00606 
00607   button = glade_xml_get_widget (xml, "period_payment_radio");
00608   fcd->end_of_period_radio = button;
00609 
00610   button = glade_xml_get_widget (xml, "discrete_compounding_radio");
00611   fcd->discrete_compounding_radio = button;
00612 
00613   fcd->payment_total_label = glade_xml_get_widget (xml, "payment_total_label");
00614 
00615   button = glade_xml_get_widget (xml, "schedule_button");
00616   gtk_widget_hide (button);
00617 
00618   init_fi(fcd);
00619 
00620   fi_to_gui(fcd);
00621 
00622   gtk_widget_grab_focus(fcd->amounts[PAYMENT_PERIODS]);
00623 
00624   /* Connect all signals specified in glade. */
00625   glade_xml_signal_autoconnect_full( xml,
00626                                      gnc_glade_autoconnect_full_func,
00627                                      fcd);
00628 
00629   gnc_restore_window_size(GCONF_SECTION, GTK_WINDOW(fcd->dialog));
00630   gtk_widget_show(fcd->dialog);
00631 }
00632 
00633 void
00634 gnc_ui_fincalc_dialog_destroy(FinCalcDialog *fcd)
00635 {
00636   if (fcd == NULL)
00637     return;
00638 
00639   gnc_close_gui_component_by_data (DIALOG_FINCALC_CM_CLASS, fcd);
00640 }

Generated on Fri Jul 25 05:05:13 2008 for GnuCash by  doxygen 1.5.2