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