From 2af48fcb16bd024ee7f475c824baa595e891c894 Mon Sep 17 00:00:00 2001 From: David Hampton Date: Tue, 13 Mar 2007 03:43:24 +0000 Subject: [PATCH] Convert the check printing code from Scheme to C. Add support for using the GtkPrint API when compiled against gtk+ 2.10 or later. Add support for reading check description files instead of hard coding check descriptions. git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@15709 57a11ea4-9604-0410-9ed3-97b8803252fd --- Makefile.am | 2 +- checks/Makefile.am | 17 + checks/deluxe.chk | 22 + checks/gnomeprint/deluxe.chk | 22 + checks/gnomeprint/quicken.chk | 28 + checks/gnomeprint/quicken_wallet.chk | 40 + checks/quicken.chk | 28 + checks/quicken_wallet.chk | 40 + configure.in | 4 +- src/app-utils/app-utils.i | 3 + src/app-utils/gnc-ui-util.c | 107 ++ src/app-utils/gnc-ui-util.h | 3 + src/gnome/dialog-print-check.c | 1427 ++++++++++++++++++++++++-- src/gnome/glade/print.glade | 91 +- src/scm/Makefile.am | 2 +- src/scm/main.scm | 5 - src/scm/printing/Makefile.am | 25 - src/scm/printing/number-to-words.scm | 113 -- src/scm/printing/print-check.scm | 216 ---- 19 files changed, 1747 insertions(+), 448 deletions(-) create mode 100644 checks/Makefile.am create mode 100644 checks/deluxe.chk create mode 100644 checks/gnomeprint/deluxe.chk create mode 100644 checks/gnomeprint/quicken.chk create mode 100644 checks/gnomeprint/quicken_wallet.chk create mode 100644 checks/quicken.chk create mode 100644 checks/quicken_wallet.chk delete mode 100644 src/scm/printing/Makefile.am delete mode 100644 src/scm/printing/number-to-words.scm delete mode 100644 src/scm/printing/print-check.scm diff --git a/Makefile.am b/Makefile.am index 96e5e0a09d..c323b119b9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,5 @@ -SUBDIRS = . doc lib src intl-scm packaging po accounts +SUBDIRS = . doc lib src intl-scm packaging po accounts checks docdir = ${GNC_DOC_INSTALL_DIR} diff --git a/checks/Makefile.am b/checks/Makefile.am new file mode 100644 index 0000000000..d190455da1 --- /dev/null +++ b/checks/Makefile.am @@ -0,0 +1,17 @@ + +checksdir = ${GNC_CHECKS_DIR} + +if HAVE_GTK_2_10 +checks_DATA = \ + deluxe.chk \ + quicken.chk \ + quicken_wallet.chk +else +checks_DATA = \ + gnomeprint/deluxe.chk \ + gnomeprint/quicken.chk \ + gnomeprint/quicken_wallet.chk +endif + +EXTRA_DIST = \ + ${checks_DATA} diff --git a/checks/deluxe.chk b/checks/deluxe.chk new file mode 100644 index 0000000000..618bd6a560 --- /dev/null +++ b/checks/deluxe.chk @@ -0,0 +1,22 @@ +[Top] +Title = Deluxe(tm) Personal Checks US-Letter +Rotation = -90.0 +Translation = 4;492 +Show_Grid = 0 +Show_Boxes = 0 + +[Check Items] +Type_1 = PAYEE +Coords_1 = 126.0;85.0 + +Type_2 = AMOUNT_WORDS +Coords_2 = 90.0;107.0 + +Type_3 = AMOUNT_NUMBER +Coords_3 = 395.0;85.0 + +Type_4 = DATE +Coords_4 = 343.0;54.0 + +Type_5 = MEMO +Coords_5 = 100.0;159.0 diff --git a/checks/gnomeprint/deluxe.chk b/checks/gnomeprint/deluxe.chk new file mode 100644 index 0000000000..b8a7d43bda --- /dev/null +++ b/checks/gnomeprint/deluxe.chk @@ -0,0 +1,22 @@ +[Top] +Title = Deluxe(tm) Personal Checks US-Letter +Rotation = 90 +Translation = 232;300 +Show_Grid = 0 +Show_Boxes = 0 + +[Check Items] +Type_1 = PAYEE +Coords_1 = 126.0;147.0 + +Type_2 = AMOUNT_WORDS +Coords_2 = 90.0;125.0 + +Type_3 = AMOUNT_NUMBER +Coords_3 = 395.0;147.0 + +Type_4 = DATE +Coords_4 = 343.0;178.0 + +Type_5 = MEMO +Coords_5 = 100.0;73.0 diff --git a/checks/gnomeprint/quicken.chk b/checks/gnomeprint/quicken.chk new file mode 100644 index 0000000000..375e30da33 --- /dev/null +++ b/checks/gnomeprint/quicken.chk @@ -0,0 +1,28 @@ +[Top] +Title = Quicken/QuickBooks (tm) US-Letter +Rotation = 0.0 +Translation = 0.0;0.0 +Show_Grid = 0 +Show_Boxes = 0 + +[Check Positions] +Height = 252.0 +Name_1 = Top +Name_2 = Middle +Name_3 = Bottom + +[Check Items] +Type_1 = PAYEE +Coords_1 = 90.0;150.0;400.0;20.0 + +Type_2 = AMOUNT_WORDS +Coords_2 = 90.0;120.0 + +Type_3 = AMOUNT_NUMBER +Coords_3 = 500.0;150.0 + +Type_4 = DATE +Coords_4 = 500.0;185.0 + +Type_5 = MEMO +Coords_5 = 50.0;40.0 diff --git a/checks/gnomeprint/quicken_wallet.chk b/checks/gnomeprint/quicken_wallet.chk new file mode 100644 index 0000000000..abf3294c44 --- /dev/null +++ b/checks/gnomeprint/quicken_wallet.chk @@ -0,0 +1,40 @@ +[Top] +Title = Quicken(tm) Wallet Checks w/ side stub +Rotation = 0.0 +Translation = 0.0;0.0 +Show_Grid = 0 +Show_Boxes = 0 + +[Check Positions] +Height = 204.0 +Name_1 = Top +Name_2 = Middle +Name_3 = Bottom + +[Check Items] +Type_1 = PAYEE +Coords_1 = 231.0;140.0 + +Type_2 = AMOUNT_WORDS +Coords_2 = 195.0;125.0 + +Type_3 = AMOUNT_NUMBER +Coords_3 = 518.0;137.0 + +Type_4 = DATE +Coords_4 = 504.0;151.0 + +Type_5 = MEMO +Coords_5 = 216.0;37.0 + +Type_6 = DATE +Coords_6 = 36.0;151.0 + +Type_7 = PAYEE +Coords_7 = 26.0;126.0 + +Type_8 = AMOUNT_NUMBER +Coords_8 = 50.0;90.0 + +Type_9 = MEMO +Coords_9 = 28.0;65.0 diff --git a/checks/quicken.chk b/checks/quicken.chk new file mode 100644 index 0000000000..074f3c58f7 --- /dev/null +++ b/checks/quicken.chk @@ -0,0 +1,28 @@ +[Top] +Title = Quicken/QuickBooks (tm) US-Letter +Rotation = 0.0 +Translation = 0.0;4.0 +Show_Grid = 0 +Show_Boxes = 0 + +[Check Positions] +Height = 252.0 +Name_1 = Top +Name_2 = Middle +Name_3 = Bottom + +[Check Items] +Type_1 = PAYEE +Coords_1 = 90.0;102.0;400.0;20.0 + +Type_2 = AMOUNT_WORDS +Coords_2 = 90.0;132.0 + +Type_3 = AMOUNT_NUMBER +Coords_3 = 500.0;102.0 + +Type_4 = DATE +Coords_4 = 500.0;67.0 + +Type_5 = MEMO +Coords_5 = 50.0;212.0 diff --git a/checks/quicken_wallet.chk b/checks/quicken_wallet.chk new file mode 100644 index 0000000000..cfb6c16a08 --- /dev/null +++ b/checks/quicken_wallet.chk @@ -0,0 +1,40 @@ +[Top] +Title = Quicken(tm) Wallet Checks w/ side stub +Rotation = 0.0 +Translation = 0.0;4.0 +Show_Grid = 0 +Show_Boxes = 0 + +[Check Positions] +Height = 204.0 +Name_1 = Top +Name_2 = Middle +Name_3 = Bottom + +[Check Items] +Type_1 = PAYEE +Coords_1 = 231.0;64.0 + +Type_2 = AMOUNT_WORDS +Coords_2 = 195.0;79.0 + +Type_3 = AMOUNT_NUMBER +Coords_3 = 518.0;67.0 + +Type_4 = DATE +Coords_4 = 504.0;53.0 + +Type_5 = MEMO +Coords_5 = 216.0;167.0 + +Type_6 = DATE +Coords_6 = 36.0;53.0 + +Type_7 = PAYEE +Coords_7 = 26.0;78.0 + +Type_8 = AMOUNT_NUMBER +Coords_8 = 50.0;114.0 + +Type_9 = MEMO +Coords_9 = 28.0;139.0 diff --git a/configure.in b/configure.in index b80a53affd..818fafe235 100644 --- a/configure.in +++ b/configure.in @@ -572,11 +572,13 @@ GNC_SHAREDIR='${pkgdatadir}' GNC_LIBEXECDIR='${libexecdir}/gnucash' GNC_ACCOUNTS_DIR='${GNC_SHAREDIR}/accounts' +GNC_CHECKS_DIR='${GNC_SHAREDIR}/checks' GNC_GLADE_DIR='${GNC_SHAREDIR}/glade' GNC_UI_DIR='${GNC_SHAREDIR}/ui' GNC_PIXMAP_DIR='${GNC_SHAREDIR}/pixmaps' AC_SUBST(GNC_ACCOUNTS_DIR) +AC_SUBST(GNC_CHECKS_DIR) AC_SUBST(GNC_CONFIGDIR) AC_SUBST(GNC_DOC_INSTALL_DIR) AC_SUBST(GNC_GLADE_DIR) @@ -1401,6 +1403,7 @@ AC_CONFIG_FILES(po/Makefile.in accounts/pt_PT/Makefile accounts/sk/Makefile accounts/tr_TR/Makefile + checks/Makefile doc/Makefile doc/examples/Makefile intl-scm/Makefile @@ -1497,7 +1500,6 @@ AC_CONFIG_FILES(po/Makefile.in src/report/utility-reports/test/Makefile src/scm/Makefile src/scm/gnumeric/Makefile - src/scm/printing/Makefile src/tax/Makefile src/tax/us/Makefile src/tax/us/test/Makefile diff --git a/src/app-utils/app-utils.i b/src/app-utils/app-utils.i index 4099ec4319..8ea8d8a61e 100644 --- a/src/app-utils/app-utils.i +++ b/src/app-utils/app-utils.i @@ -70,6 +70,9 @@ GNCPrintAmountInfo gnc_commodity_print_info (const gnc_commodity *commodity, GNCPrintAmountInfo gnc_share_print_info_places (int decplaces); const char * xaccPrintAmount (gnc_numeric val, GNCPrintAmountInfo info); +gchar *number_to_words(gdouble val, gint64 denom); +const gchar *printable_value (gdouble val, gint denom); + gboolean gnc_reverse_balance (const Account *account); gboolean gnc_is_euro_currency(const gnc_commodity * currency); diff --git a/src/app-utils/gnc-ui-util.c b/src/app-utils/gnc-ui-util.c index 74f2f4e532..174f659eaa 100644 --- a/src/app-utils/gnc-ui-util.c +++ b/src/app-utils/gnc-ui-util.c @@ -1568,6 +1568,113 @@ xaccPrintAmount (gnc_numeric val, GNCPrintAmountInfo info) } +/********************************************************************\ + ********************************************************************/ + +#define FUDGE .00001 + +static gchar *small_numbers[] = { + N_("Zero"), N_("One"), N_("Two"), N_("Three"), N_("Four"), + N_("Five"), N_("Six"), N_("Seven"), N_("Eight"), N_("Nine"), + N_("Ten"), N_("Eleven"), N_("Twelve"), N_("Thirteen"), N_("Fourteen"), + N_("Fifteen"), N_("Sixteen"), N_("Seventeen"), N_("Eighteen"), N_("Nineteen"), + N_("Twenty")}; +static gchar *medium_numbers[] = { + N_("Zero"), N_("Ten"), N_("Twenty"), N_("Thirty"), N_("Forty"), + N_("Fifty"), N_("Sixty"), N_("Seventy"), N_("Eighty"), N_("Ninety")}; +static gchar *big_numbers[] = { + N_("Hundred"), N_("Thousand"), N_("Million"), N_("Billion"), + N_("Trillion"), N_("Quadrillion"), N_("Quintillion")}; + +static gchar * +integer_to_words(gint64 val) +{ + gint64 log_val, pow_val, this_part; + GString *result; + gchar *tmp; + + if (val == 0) + return g_strdup("zero"); + if (val < 0) + val = -val; + + result = g_string_sized_new(100); + + while (val >= 1000) { + log_val = log10(val) / 3 + FUDGE; + pow_val = exp10(log_val * 3) + FUDGE; + this_part = val / pow_val; + val -= this_part * pow_val; + tmp = integer_to_words(this_part); + g_string_append_printf(result, "%s %s ", tmp, + gettext(big_numbers[log_val])); + g_free(tmp); + } + + if (val >= 100) { + this_part = val / 100; + val -= this_part * 100; + g_string_append_printf(result, "%s %s ", + gettext(small_numbers[this_part]), + gettext(big_numbers[0])); + } + + if (val > 20) { + this_part = val / 10; + val -= this_part * 10; + g_string_append(result, gettext(medium_numbers[this_part])); + g_string_append_c(result, ' '); + } + + if (val > 0) { + this_part = val; + val -= this_part; + g_string_append(result, gettext(small_numbers[this_part])); + g_string_append_c(result, ' '); + } + + result = g_string_truncate(result, result->len - 1); + return g_string_free(result, FALSE); +} + +gchar * +number_to_words(gdouble val, gint64 denom) +{ + gint64 int_part, frac_part; + gchar *int_string, *full_string; + + if (val < 0) val = -val; + if (denom < 0) denom = -denom; + + int_part = trunc(val); + frac_part = round((val - int_part) * denom); + + int_string = integer_to_words(int_part); + full_string = + g_strdup_printf(_("%s and %lld/%lld"), int_string, frac_part, denom); + g_free(int_string); + return full_string; +} + +gchar * +numeric_to_words(gnc_numeric val) +{ + return number_to_words(gnc_numeric_to_double(val), + gnc_numeric_denom(val)); +} + +const gchar * +printable_value (gdouble val, gint denom) +{ + GNCPrintAmountInfo info; + gnc_numeric num; + + num = gnc_numeric_create(round(val * denom), denom); + info = gnc_share_print_info_places(log10(denom)); + return xaccPrintAmount (num, info); +} + + /********************************************************************\ * xaccParseAmount * * parses amount strings using locale data * diff --git a/src/app-utils/gnc-ui-util.h b/src/app-utils/gnc-ui-util.h index d5dfee626b..6ff5a0ffd2 100644 --- a/src/app-utils/gnc-ui-util.h +++ b/src/app-utils/gnc-ui-util.h @@ -275,6 +275,9 @@ GNCPrintAmountInfo gnc_integral_print_info (void); const char * xaccPrintAmount (gnc_numeric val, GNCPrintAmountInfo info); int xaccSPrintAmount (char *buf, gnc_numeric val, GNCPrintAmountInfo info); +const gchar *printable_value(gdouble val, gint denom); +gchar *number_to_words(gdouble val, gint64 denom); +gchar *numeric_to_words(gnc_numeric val); /* xaccParseAmount parses in_str to obtain a numeric result. The * routine will parse as much of in_str as it can to obtain a single diff --git a/src/gnome/dialog-print-check.c b/src/gnome/dialog-print-check.c index 537d759684..594c6ae52a 100644 --- a/src/gnome/dialog-print-check.c +++ b/src/gnome/dialog-print-check.c @@ -27,7 +27,9 @@ #include #include #include +#include +#include "qof.h" #include "gnc-date.h" #include "gnc-gconf-utils.h" #include "gnc-numeric.h" @@ -37,10 +39,15 @@ #include "print-session.h" #include "gnc-ui.h" #include "gnc-date-format.h" +#include "gnc-ui-util.h" +#include "gnc-path.h" +#include "gnc-filepath-utils.h" +#include "gnc-gkeyfile-utils.h" -#define CHECK_PRINT_NUM_FORMATS 4 -#define CHECK_PRINT_NUM_POSITIONS 4 -#define CHECK_PRINT_NUM_UNITS 4 +#define USE_GTKPRINT HAVE_GTK_2_10 + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "gnc.printing.checks" #define GCONF_SECTION "dialogs/print_checks" #define KEY_CHECK_FORMAT "check_format" @@ -54,17 +61,134 @@ #define KEY_CUSTOM_TRANSLATION "custom_translation" #define KEY_CUSTOM_ROTATION "custom_rotation" #define KEY_CUSTOM_UNITS "custom_units" +#define KEY_SHOW_DATE_FMT "show_date_format" + + +#define DEFAULT_FONT "sans 12" +#define CHECK_FMT_DIR "checks" +#define DEGREES_TO_RADIANS (G_PI / 180.0) + +#define KF_GROUP_TOP "Top" +#define KF_GROUP_POS "Check Positions" +#define KF_GROUP_ITEMS "Check Items" +#define KF_KEY_TITLE "Title" +#define KF_KEY_ROTATION "Rotation" +#define KF_KEY_TRANSLATION "Translation" +#define KF_KEY_FONT "Font" +#define KF_KEY_ALIGN "Align" +#define KF_KEY_SHOW_GRID "Show_Grid" +#define KF_KEY_SHOW_BOXES "Show_Boxes" +#define KF_KEY_NAME "Name" +#define KF_KEY_HEIGHT "Height" +#define KF_KEY_TYPE "Type" +#define KF_KEY_COORDS "Coords" +#define KF_KEY_TEXT "Text" +#define KF_KEY_FILENAME "Filename" + +#if USE_GTKPRINT +# define GncPrintContext GtkPrintContext +static GtkPrintSettings *settings = NULL; +#else +# define GncPrintContext GnomePrintContext +#endif + /* Used by glade_xml_signal_autoconnect_full */ void gnc_ui_print_check_response_cb(GtkDialog * dialog, gint response, PrintCheckDialog * pcd); -void gnc_print_check_combobox_changed(GtkComboBox *widget, PrintCheckDialog * pcd); +void gnc_print_check_format_changed(GtkComboBox *widget, PrintCheckDialog * pcd); +void gnc_print_check_position_changed(GtkComboBox *widget, PrintCheckDialog * pcd); static void gnc_ui_print_save_dialog(PrintCheckDialog * pcd); static void gnc_ui_print_restore_dialog(PrintCheckDialog * pcd); void gnc_ui_print_restore_dialog(PrintCheckDialog * pcd); + +/** This enum defines the types of items that gnucash knows how to + * print on checks. Most refer to specific fields from a gnucash + * transaction and split, but some are generic items unrelated to + * gnucash. */ +#define ENUM_CHECK_ITEM_TYPE(_) \ + _(NONE,) \ + _(PAYEE,) \ + _(DATE,) \ + _(MEMO,) \ + _(AMOUNT_NUMBER,) \ + _(AMOUNT_WORDS,) \ + _(TEXT,) \ + _(PICTURE,) + +DEFINE_ENUM(CheckItemType, ENUM_CHECK_ITEM_TYPE) +FROM_STRING_DEC(CheckItemType, ENUM_CHECK_ITEM_TYPE) +FROM_STRING_FUNC(CheckItemType, ENUM_CHECK_ITEM_TYPE) + +/** This data structure describes a single item printed on a check. + * It is build from a description in a text file. */ +typedef struct _check_item { + + CheckItemType type; /**< What type of item is this? */ + + gdouble x, y; /**< The x/y coordinates where this item + * should be printed. The origin for this + * coordinate is determined by the print + * system used. */ + + gdouble w, h; /**< Optional. The width and height of this + * item. Text will be clipped to this + * size. Pictures will be shrunk if + * necessary to fit. */ + + gchar *filename; /**< The filename for picture items. Otherwise + * unused. */ + + gchar *text; /**< The text to be displayed (for text based + * items.) Otherwise unused. */ + + gchar *font; /**< The font to use for text based item. This + * overrides any font in the check format. + * Unused for non-text items. */ + + PangoAlignment align; /**< The alignment of a text based item. Only + * used for text based items when a width is + * specified. */ +} check_item_t; + +/** This data structure describes an entire page of checks. Depending + * upon the check format, the page may contain multiple checks or + * only a single check. The data structure is build from a + * description in a text file. */ +typedef struct _check_format { + + gchar *title; /**< Title of this check format. Displayed to + * user in the dialog box. */ + + gboolean show_grid; /**< Print a grid pattern on the page */ + + gboolean show_boxes; /**< Print boxes when width and height are + * known. */ + + gdouble rotation; /**< Rotate entire page by this amount */ + + gdouble trans_x; /**< Move entire page by this X amount */ + + gdouble trans_y; /**< Move entire page by this Y amount */ + + gchar *font; /**< Default font for this page of checks */ + + gdouble height; /**< Height of one check on a page */ + + GSList *positions; /**< Names of the checks on the page. */ + + GSList *items; /**< List of items printed on each check */ +} check_format_t; + +/** This data structure is used to manage the print check dialog, and + * the overall check printing process. It contains pointers to many + * of the widgets in the dialog, pointers to the check descriptions + * that have been read, and also contains the data from the gnucash + * transaction/split that is to be printed. */ struct _print_check_dialog { GladeXML * xml; GtkWidget * dialog; + GtkWindow * caller_window; GncPluginPageRegister *plugin_page; const char *payee; @@ -73,7 +197,9 @@ struct _print_check_dialog { const char *memo; GtkWidget * format_combobox; + gint format_max; GtkWidget * position_combobox; + gint position_max; GtkWidget * custom_table; GtkSpinButton * payee_x, * payee_y; GtkSpinButton * date_x, * date_y; @@ -90,6 +216,8 @@ struct _print_check_dialog { gchar *format_string; + GSList *formats_list; + check_format_t *selected_format; }; @@ -216,6 +344,490 @@ gnc_ui_print_restore_dialog(PrintCheckDialog * pcd) } +static gdouble +pcd_get_custom_multip(PrintCheckDialog * pcd) +{ + gint selected; + + selected = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->units_combobox)); + switch (selected) { + default: + return 72.0; /* inches */ + case 1: + return 28.346; /* cm */ + case 2: + return 2.8346; /* mm */ + case 3: + return 1.0; /* points */ + } +} + + +/** This is an auxiliary debugging function for converting an array of doubles + * into a printable string. */ +static gchar * +doubles_to_string(gdouble * dd, gint len) +{ + GString *str; + gint i; + + str = g_string_new_len(NULL, 50); + for (i = 0; i < len; i++) + g_string_append_printf(str, "%f ", dd[i]); + return g_string_free(str, FALSE); +} + + +/** Read the information describing the placement for each item to be printed + * on the check. This information is all relative to the upper left hand + * corner of a "check". See the format_read_multicheck_info() function for + * determining if there are multiple checks on a single page of paper. This + * data is build into a linked list and saved as part of the check format + * information. These items will be printed in the same order they are read, + * meaning that items listed later in the date file can be printed over top + * of items that appear earlier in the file. */ +static GSList * +format_read_item_placement(const gchar * file, + GKeyFile * key_file, check_format_t * format) +{ + check_item_t *data = NULL; + GError *error = NULL; + GSList *list = NULL; + gchar *key, *value, *name; + int item_num; + gdouble *dd; + gsize dd_len; + + /* Read until failure. */ + for (item_num = 1;; item_num++) { + + /* Create the per-item data structure */ + data = g_new0(check_item_t, 1); + if (NULL == data) + return list; + + /* Get the item type */ + key = g_strdup_printf("%s_%d", KF_KEY_TYPE, item_num); + value = g_key_file_get_string(key_file, KF_GROUP_ITEMS, key, &error); + if (error) { + if ((error->domain == G_KEY_FILE_ERROR) + && (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { + /* This is the expected exit from this function. */ + goto cleanup; + } + goto failed; + } + g_debug("Check file %s, group %s, key %s, value: %s", + file, KF_GROUP_ITEMS, key, value); + g_free(key); + + /* Convert the type from a string to an enum, ignoring case. */ + name = g_utf8_strup(value, -1); + data->type = CheckItemTypefromString(name); + g_free(name); + g_free(value); + + + /* Get the item location */ + key = g_strdup_printf("%s_%d", KF_KEY_COORDS, item_num); + dd = g_key_file_get_double_list(key_file, KF_GROUP_ITEMS, + key, &dd_len, &error); + if (error) + goto failed; + value = doubles_to_string(dd, dd_len); + g_debug("Check file %s, group %s, key %s, length %d; values: %s", + file, KF_GROUP_ITEMS, key, dd_len, value); + g_free(value); + + /* Must have "x;y" or "x;y;w;h". */ + switch (dd_len) { + case 4: + data->w = dd[2]; + data->h = dd[3]; + /* fall through */ + case 2: + data->x = dd[0]; + data->y = dd[1]; + break; + default: + g_warning + ("Check file %s, group %s, key %s, error: 2 or 4 values only", + file, KF_GROUP_ITEMS, key); + goto cleanup; + } + g_free(dd); + g_free(key); + + /* Any text item can specify a font, and can also an alignment if a + * width was provided for the item. These values are optional and do + * not cause a failure if they are missing. */ + if (data->type != PICTURE) { + key = g_strdup_printf("%s_%d", KF_KEY_FONT, item_num); + data->font = + g_key_file_get_string(key_file, KF_GROUP_ITEMS, key, &error); + if (!error) { + g_debug("Check file %s, group %s, key %s, value: %s", + file, KF_GROUP_ITEMS, key, data->font); + } else { + if (!((error->domain == G_KEY_FILE_ERROR) + && (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND))) + g_warning("Check file %s, group %s, key %s, error: %s", + file, KF_GROUP_ITEMS, key, error->message); + g_clear_error(&error); + } + g_free(key); + + value = + g_key_file_get_string(key_file, KF_GROUP_ITEMS, KF_KEY_ALIGN, + &error); + if (!error) { + g_debug("Check file %s, group %s, key %s, value: %s", + file, KF_GROUP_ITEMS, key, value); + name = g_utf8_strdown(value, -1); + if (strcmp(name, "right") == 0) + data->align = PANGO_ALIGN_RIGHT; + else if (strcmp(name, "center") == 0) + data->align = PANGO_ALIGN_CENTER; + else + data->align = PANGO_ALIGN_LEFT; + g_free(name); + g_free(value); + } else { + if (!((error->domain == G_KEY_FILE_ERROR) + && (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND))) + g_warning("Check file %s, group %s, key %s, error: %s", + file, KF_GROUP_ITEMS, key, error->message); + data->align = PANGO_ALIGN_LEFT; + g_clear_error(&error); + } + } + /* Get any extra data for specific items. */ + switch (data->type) { + case PICTURE: + key = g_strdup_printf("%s_%d", KF_KEY_FILENAME, item_num); + data->filename = + g_key_file_get_string(key_file, KF_GROUP_ITEMS, key, + &error); + if (error) + goto failed; + g_debug("Check file %s, group %s, key %s, value: %s", + file, KF_GROUP_ITEMS, key, data->filename); + g_free(key); + break; + case TEXT: + key = g_strdup_printf("%s_%d", KF_KEY_TEXT, item_num); + data->text = + g_key_file_get_string(key_file, KF_GROUP_ITEMS, key, + &error); + if (error) + goto failed; + g_debug("Check file %s, group %s, key %s, value: %s", + file, KF_GROUP_ITEMS, key, data->text); + g_free(key); + break; + default: + break; + } + + list = g_slist_append(list, data); + data = NULL; + } + + /* Should never be reached. */ + return list; + + failed: + g_warning("Check file %s, group %s, key %s, error: %s", + file, KF_GROUP_ITEMS, key, error->message); + cleanup: + if (error) + g_error_free(error); + if (data) + g_free(data); + if (key) + g_free(key); + return list; +} + + +/** Free the data describing the placement of a single item on a check. */ +static void +format_free_item_placement(check_item_t * data) +{ + if (data->font) + g_free(data->font); + if (data->text) + g_free(data->text); + if (data->filename) + g_free(data->filename); + g_free(data); +} + + +/** Read the information describing whether a page contains multiple checks or + * a single check. If there are multiple checks on a page, this functions + * builds a linked list of the position names and their offsets (from the + * upper left corner of the page). */ +static GSList * +format_read_multicheck_info(const gchar * file, + GKeyFile * key_file, check_format_t * format) +{ + GError *error = NULL; + GSList *list = NULL; + gchar *key, *name; + int i; + + key = g_strdup_printf("%s", KF_KEY_HEIGHT); + format->height = g_key_file_get_double(key_file, KF_GROUP_POS, key, &error); + if (error) { + if ((error->domain == G_KEY_FILE_ERROR) + && ((error->code == G_KEY_FILE_ERROR_GROUP_NOT_FOUND) + || (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND))) { + g_clear_error(&error); + format->height = 0.0; + } else { + g_warning("Check file %s, error reading group %s, key %s: %s", + file, KF_GROUP_POS, key, error->message); + g_free(key); + return list; + } + } + + for (i = 1;; i++) { + key = g_strdup_printf("%s_%d", KF_KEY_NAME, i); + name = g_key_file_get_string(key_file, KF_GROUP_POS, key, &error); + if (error) { + if ((error->domain == G_KEY_FILE_ERROR) + && ((error->code == G_KEY_FILE_ERROR_GROUP_NOT_FOUND) + || (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND))) { + /* This is the expected exit from this function. */ + g_free(key); + return list; + } + g_warning("Check file %s, error reading group %s, key %s: %s", + file, KF_GROUP_POS, key, error->message); + g_free(key); + return list; + } + g_free(key); + + list = g_slist_append(list, name); + } + + /* Should never be reached. */ + return list; +} + + +/** Free the data describing the placement of multiple checks on a page. */ +static void +free_check_position(gchar * name) +{ + g_free(name); +} + + +/** Read the information describing the general layout of a page of checks. + * All items in this section are optional except or the name of the check + * style. */ +static gboolean +format_read_general_info(const gchar * file, + GKeyFile * key_file, check_format_t * format) +{ + GError *error = NULL; + gchar *value; + double *dd; + gsize dd_len; + + format->title = + g_key_file_get_string(key_file, KF_GROUP_TOP, KF_KEY_TITLE, &error); + if (!error) { + g_debug("Check file %s, group %s, key %s, value: %s", + file, KF_GROUP_TOP, KF_KEY_TITLE, format->title); + } else { + g_warning("Check file %s, group %s, key %s, error: %s", + file, KF_GROUP_TOP, KF_KEY_TITLE, error->message); + return FALSE; + } + + format->show_grid = + g_key_file_get_boolean(key_file, KF_GROUP_TOP, KF_KEY_SHOW_GRID, + &error); + if (!error) { + g_debug("Check file %s, group %s, key %s, value: %d", + file, KF_GROUP_TOP, KF_KEY_SHOW_GRID, format->show_grid); + } else { + if (!((error->domain == G_KEY_FILE_ERROR) + && (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND))) + g_warning("Check file %s, group %s, key %s, error: %s", + file, KF_GROUP_TOP, KF_KEY_SHOW_GRID, error->message); + format->show_grid = FALSE; + g_clear_error(&error); + } + + format->show_boxes = + g_key_file_get_boolean(key_file, KF_GROUP_TOP, KF_KEY_SHOW_BOXES, + &error); + if (!error) { + g_debug("Check file %s, group %s, key %s, value: %d", + file, KF_GROUP_TOP, KF_KEY_SHOW_BOXES, format->show_boxes); + } else { + if (!((error->domain == G_KEY_FILE_ERROR) + && (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND))) + g_warning("Check file %s, group %s, key %s, error: %s", + file, KF_GROUP_TOP, KF_KEY_SHOW_BOXES, error->message); + format->show_boxes = FALSE; + g_clear_error(&error); + } + + format->font = + g_key_file_get_string(key_file, KF_GROUP_TOP, KF_KEY_FONT, &error); + if (!error) { + g_debug("Check file %s, group %s, key %s, value: %s", + file, KF_GROUP_TOP, KF_KEY_FONT, format->font); + } else { + if (!((error->domain == G_KEY_FILE_ERROR) + && (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND))) + g_warning("Check file %s, group %s, key %s, error: %s", + file, KF_GROUP_TOP, KF_KEY_FONT, error->message); + format->font = g_strdup(DEFAULT_FONT); + g_clear_error(&error); + } + + format->rotation = + g_key_file_get_double(key_file, KF_GROUP_TOP, KF_KEY_ROTATION, &error); + if (!error) { + g_debug("Check file %s, group %s, key %s, value: %f", + file, KF_GROUP_TOP, KF_KEY_ROTATION, format->rotation); + } else { + if (!((error->domain == G_KEY_FILE_ERROR) + && (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND))) + g_warning("Check file %s, group %s, key %s, error: %s", + file, KF_GROUP_TOP, KF_KEY_ROTATION, error->message); + format->rotation = 0.0; + g_clear_error(&error); + } + + dd = g_key_file_get_double_list(key_file, KF_GROUP_TOP, KF_KEY_TRANSLATION, + &dd_len, &error); + if (!error) { + value = doubles_to_string(dd, dd_len); + g_debug("Check file %s, group %s, key %s, length %d; values: %s", + file, KF_GROUP_TOP, KF_KEY_TRANSLATION, dd_len, value); + g_free(value); + + if (dd_len == 2) { + format->trans_x = dd[0]; + format->trans_y = dd[1]; + } else { + g_warning("Check file %s, error top %s, key %s: 2 values only", + file, KF_GROUP_TOP, KF_KEY_TRANSLATION); + } + g_free(dd); + } else { + if (!((error->domain == G_KEY_FILE_ERROR) + && (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND))) + g_warning("Check file %s, group top %s, key %s: %s", + file, KF_GROUP_ITEMS, KF_KEY_TRANSLATION, error->message); + g_clear_error(&error); + } + + return TRUE; +} + + +/** Free all of the information describing a page of checks. */ +static void +free_check_format(check_format_t * data) +{ + g_free(data->title); + g_free(data->font); + g_slist_foreach(data->positions, (GFunc) free_check_position, NULL); + g_slist_free(data->positions); + g_slist_foreach(data->items, (GFunc) format_free_item_placement, NULL); + g_slist_free(data->items); + g_free(data); +} + + +/** Read a single check format file and append the resulting format to the + * list of all known formats. This function calls other functions to read + * each section of the data file. */ +static void +read_one_check_format(PrintCheckDialog * pcd, + const gchar * dirname, const gchar * file) +{ + gchar *pathname; + GKeyFile *key_file; + check_format_t *format; + + pathname = g_build_filename(dirname, file, (char *)NULL); + key_file = gnc_key_file_load_from_file(pathname, FALSE, FALSE); + g_free(pathname); + if (!key_file) { + g_warning("Check file %s, cannot load file", file); + return; + } + + format = g_new0(check_format_t, 1); + if (format_read_general_info(file, key_file, format)) { + format->positions = format_read_multicheck_info(file, key_file, format); + format->items = format_read_item_placement(file, key_file, format); + } + + g_key_file_free(key_file); + if ((NULL == format->title) || (NULL == format->items)) { + g_warning("Check file %s, no items read. Dropping file.", file); + free_check_format(format); + return; + } + + pcd->formats_list = g_slist_append(pcd->formats_list, format); +} + + +/** Iterate over a single check directory, throwing out any backup files and + * then calling a helper function to read and parse the check format withing + * the file. */ +static void +read_one_check_directory(PrintCheckDialog * pcd, const gchar * dirname) +{ + GDir *dir; + const gchar *filename; + + dir = g_dir_open(dirname, 0, NULL); + if (dir) { + while ((filename = g_dir_read_name(dir)) != NULL) { + if (g_str_has_prefix(filename, "#")) + continue; + if (g_str_has_suffix(filename, ".chk")) + read_one_check_format(pcd, dirname, filename); + } + g_dir_close(dir); + } +} + + +/** Read all check formats. This function first looks in the system directory + * for check files, and then looks in the user's .gnucash directory for any + * custom check files. */ +static void +read_formats(PrintCheckDialog * pcd) +{ + gchar *dirname, *pkgdatadir; + + pkgdatadir = gnc_path_get_pkgdatadir(); + dirname = g_build_filename(pkgdatadir, CHECK_FMT_DIR, (char *)NULL); + read_one_check_directory(pcd, dirname); + g_free(dirname); + g_free(pkgdatadir); + + dirname = gnc_build_dotgnucash_path(CHECK_FMT_DIR); + read_one_check_directory(pcd, dirname); + g_free(dirname); +} + + /********************************************************************\ * gnc_ui_print_check_dialog_create * make a new print check dialog and wait for it. @@ -232,6 +844,8 @@ gnc_ui_print_check_dialog_create(GncPluginPageRegister *plugin_page, GladeXML *xml; GtkWidget *table; GtkWindow *window; + GSList *elem; + GtkListStore *store; pcd = g_new0(PrintCheckDialog, 1); pcd->plugin_page = plugin_page; @@ -246,6 +860,8 @@ gnc_ui_print_check_dialog_create(GncPluginPageRegister *plugin_page, pcd->xml = xml; pcd->dialog = glade_xml_get_widget (xml, "Print Check Dialog"); + read_formats(pcd); + /* now pick out the relevant child widgets */ pcd->format_combobox = glade_xml_get_widget (xml, "check_format_combobox"); pcd->position_combobox = glade_xml_get_widget (xml, "check_position_combobox"); @@ -274,17 +890,643 @@ gnc_ui_print_check_dialog_create(GncPluginPageRegister *plugin_page, window = GTK_WINDOW(GNC_PLUGIN_PAGE(plugin_page)->window); gtk_window_set_transient_for(GTK_WINDOW(pcd->dialog), window); + pcd->caller_window = GTK_WINDOW(window); /* Create and attach the date-format chooser */ table = glade_xml_get_widget (xml, "options_table"); pcd->date_format = gnc_date_format_new_without_label(); gtk_table_attach_defaults(GTK_TABLE(table), pcd->date_format, 1, 3, 2, 7); + /* Update the combo boxes bases on the available check formats */ + store = gtk_list_store_new (1, G_TYPE_STRING); + gtk_combo_box_set_model(GTK_COMBO_BOX(pcd->format_combobox), + GTK_TREE_MODEL(store)); + for (elem = pcd->formats_list; elem; elem = g_slist_next(elem)) { + gtk_combo_box_append_text(GTK_COMBO_BOX(pcd->format_combobox), + ((check_format_t*)elem->data)->title); + } + gtk_combo_box_append_text(GTK_COMBO_BOX(pcd->format_combobox), _("Custom")); + pcd->format_max = g_slist_length(pcd->formats_list); /* -1 for 0 base, +1 for custom entry*/ + +#if USE_GTKPRINT + gtk_widget_destroy(glade_xml_get_widget (xml, "lower_left")); +#else + gtk_widget_destroy(glade_xml_get_widget (xml, "upper_left")); +#endif + gnc_ui_print_restore_dialog(pcd); gnc_restore_window_size(GCONF_SECTION, GTK_WINDOW(pcd->dialog)); gtk_widget_show_all(pcd->dialog); } +/********************************************************************\ + * Print check contents to the page. +\********************************************************************/ + + +/** Draw a grid pattern on the page to be printed. This grid is helpful when + * figuring out the offsets for where to print various items on the page. */ +static void +draw_grid(GncPrintContext * context, gint width, gint height) +{ + const double dash_pattern[2] = { 1.0, 5.0 }; +#if USE_GTKPRINT + PangoFontDescription *desc; + PangoLayout *layout; + cairo_t *cr; +#endif + gchar *text; + gint i; + +#if USE_GTKPRINT + /* Initialize for printing text */ + layout = gtk_print_context_create_pango_layout(context); + desc = pango_font_description_from_string("sans 12"); + pango_layout_set_font_description(layout, desc); + pango_font_description_free(desc); + pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); + pango_layout_set_width(layout, -1); +#endif + + /* Set up the line to draw with. */ +#if USE_GTKPRINT + cr = gtk_print_context_get_cairo_context(context); + cairo_save(cr); + cairo_set_line_width(cr, 1.0); + cairo_set_line_cap(cr, CAIRO_LINE_JOIN_ROUND); + cairo_set_dash(cr, dash_pattern, 2, 0); +#else + gnome_print_gsave(context); + gnome_print_setlinewidth(context, 1.0); + gnome_print_setlinecap(context, 2); + gnome_print_setdash(context, 2, dash_pattern, 0); +#endif + + /* Draw horizontal lines */ + for (i = -200; i < (height + 200); i += 50) { + text = g_strdup_printf("%d", (int)i); +#if USE_GTKPRINT + cairo_move_to(cr, -200, i); + cairo_line_to(cr, width + 200, i); + cairo_stroke(cr); + pango_layout_set_text(layout, text, -1); + cairo_move_to(cr, 0, i); + pango_cairo_show_layout(cr, layout); +#else + gnome_print_line_stroked(context, -200, i, width + 200, i); + gnome_print_moveto(context, 0, i); + gnome_print_show(context, text); +#endif + g_free(text); + } + + /* Draw vertical lines */ + for (i = -200; i < (width + 200); i += 50) { + text = g_strdup_printf("%d", (int)i); +#if USE_GTKPRINT + cairo_move_to(cr, i, -200); + cairo_line_to(cr, i, height + 200); + cairo_stroke(cr); + pango_layout_set_text(layout, text, -1); + cairo_move_to(cr, i, 0); + pango_cairo_show_layout(cr, layout); +#else + gnome_print_line_stroked(context, i, -200, i, height + 200); + gnome_print_moveto(context, i, 0); + gnome_print_show(context, text); +#endif + g_free(text); + } + + /* Clean up after ourselves */ +#if USE_GTKPRINT + cairo_restore(cr); + g_object_unref(layout); +#else + gnome_print_gsave(context); +#endif +} + + +/** Print a single line of text to the printed page. If a width and height + * are specified, the line will be wrapped at the specified width, and the + * resulting text will be clipped if it does not fit in the space + * available. */ +static gdouble +draw_text(GncPrintContext * context, const gchar * text, check_item_t * data, + PangoFontDescription *default_desc) +{ +#if USE_GTKPRINT + PangoFontDescription *desc; + PangoLayout *layout; + cairo_t *cr; + gint layout_height, layout_width; + gdouble width, height; + + /* Initialize for printing text */ + layout = gtk_print_context_create_pango_layout(context); + if (data->font) { + desc = pango_font_description_from_string(data->font); + pango_layout_set_font_description(layout, desc); + pango_font_description_free(desc); + } else { + pango_layout_set_font_description(layout, default_desc); + } + pango_layout_set_alignment(layout, + data->w ? data->align : PANGO_ALIGN_LEFT); + pango_layout_set_width(layout, data->w ? data->w * PANGO_SCALE : -1); + pango_layout_set_text(layout, text, -1); + pango_layout_get_size(layout, &layout_width, &layout_height); + width = (gdouble) layout_width / PANGO_SCALE; + height = (gdouble) layout_height / PANGO_SCALE; + + cr = gtk_print_context_get_cairo_context(context); + cairo_save(cr); + + /* Clip text to the enclosing rectangle */ + if (data->w && data->h) { + g_debug("Text clip rectangle, coords %f,%f, size %f,%f", + data->x, data->y - data->h, data->w, data->h); + cairo_rectangle(cr, data->x, data->y - data->h, data->w, data->h); + cairo_clip_preserve(cr); + } + + /* Draw the text */ + g_debug("Text move to %f,%f, print '%s'", data->x, data->y, text); + cairo_move_to(cr, data->x, data->y - height); + pango_cairo_show_layout(cr, layout); + + /* Clean up after ourselves */ + cairo_restore(cr); + g_object_unref(layout); + return width; +#else + g_debug("Text move to %f,%f, print '%s'", data->x, data->y, text); + gnome_print_moveto(context, data->x, data->y); + gnome_print_show(context, text); + return 0.0; +#endif +} + + +/** Print a single image to the printed page. This picture will be scaled + * down to fit in the specified size rectangle. Scaling is done with the + * proportions locked 1:1 so as not to distort the image. */ +#if USE_GTKPRINT +static void +draw_picture(GtkPrintContext * context, check_item_t * data) +{ + cairo_t *cr; + GdkPixbuf *pixbuf, *scaled_pixbuf; + GtkImage *image; + gint pix_w, pix_h; + gdouble scale_w, scale_h, scale; + + cr = gtk_print_context_get_cairo_context(context); + cairo_save(cr); + + /* Get the picture. */ + image = GTK_IMAGE(gtk_image_new_from_file(data->filename)); + pixbuf = gtk_image_get_pixbuf(image); + pix_w = gdk_pixbuf_get_width(pixbuf); + pix_h = gdk_pixbuf_get_height(pixbuf); + + /* Draw the enclosing rectangle */ + if (data->w && data->h) { + cairo_rectangle(cr, data->x, data->y - data->h, data->w, data->h); + g_debug("Picture clip rectangle, user coords %f,%f, user size %f,%f", + data->x, data->y - data->h, data->w, data->h); + } else { + cairo_rectangle(cr, data->x, data->y - pix_h, pix_w, pix_h); + g_debug("Picture clip rectangle, user coords %f,%f, pic size %d,%d", + data->x, data->y - data->h, pix_w, pix_h); + } + cairo_clip_preserve(cr); + + /* Scale down to fit. Never scale up. */ + scale_w = scale_h = 1; + if (data->w && (pix_w > data->w)) + scale_w = data->w / pix_w; + if (data->h && (pix_h > data->h)) + scale_h = data->h / pix_h; + scale = MIN(scale_w, scale_h); + + if (scale != 1) { + scaled_pixbuf = gdk_pixbuf_scale_simple(pixbuf, pix_w * scale, + pix_h * scale, + GDK_INTERP_BILINEAR); + pix_h = gdk_pixbuf_get_height(scaled_pixbuf); + gdk_cairo_set_source_pixbuf(cr, scaled_pixbuf, data->x, + data->y - pix_h); + g_object_unref(scaled_pixbuf); + } else { + gdk_cairo_set_source_pixbuf(cr, pixbuf, data->x, data->y - pix_h); + } + cairo_paint(cr); + + /* Clean up after ourselves */ + cairo_restore(cr); + gtk_widget_destroy(GTK_WIDGET(image)); +} +#endif + + +#define DATE_FMT_HEIGHT 8 +#define DATE_FMT_SLOP 2 + +/* There is a new Canadian requirement that all software that prints the date + * on a check must also print the format of that date underneath using a 6-8 + * point font. This function implements that requirement. It requires the + * font description used in printing the date so that it can print in the same + * font using a smaller point size. It also requires width of the printed + * date as an argument, allowing it to center the format string under the + * date. + * + * Note: This code only prints a date if the user has explicitly requested it + * via a preference (gconf) setting. This is because gnucash has no way of + * knowing if the user's checks already have a date format printed on them. + */ +static void +draw_date_format(GncPrintContext * context, const gchar *date_format, + check_item_t * data, PangoFontDescription *default_desc, + gdouble width) +{ +#if USE_GTKPRINT + PangoFontDescription *date_desc; + check_item_t date_item; + gchar *text = NULL, *expanded = NULL; + const gchar *thislocale, *c; + GString *cdn_fmt; + + thislocale = setlocale(LC_ALL, NULL); + if (!gnc_gconf_get_bool(GCONF_SECTION, KEY_SHOW_DATE_FMT, NULL)) + return; + + date_desc = pango_font_description_copy_static(default_desc); + pango_font_description_set_size(date_desc, DATE_FMT_HEIGHT * PANGO_SCALE); + date_item = *data; + date_item.y += (DATE_FMT_HEIGHT + DATE_FMT_SLOP); + date_item.w = width; + date_item.h = DATE_FMT_HEIGHT + DATE_FMT_SLOP; + date_item.align = PANGO_ALIGN_CENTER; + + /* This is a date format string. It should only contain ascii. */ + cdn_fmt = g_string_new_len(NULL, 50); + for (c = date_format; c && *c; ) { + if ((c[0] != '%') || (c[1] == '\0')) { + c += 1; + continue; + } + switch (c[1]) { + case 'F': + cdn_fmt = g_string_append(cdn_fmt, "YYYYMMDD"); + break; + case 'Y': + cdn_fmt = g_string_append(cdn_fmt, "YYYY"); + break; + case 'y': + cdn_fmt = g_string_append(cdn_fmt, "YY"); + break; + case 'm': + cdn_fmt = g_string_append(cdn_fmt, "MM"); + break; + case 'd': + case 'e': + cdn_fmt = g_string_append(cdn_fmt, "DD"); + break; + case 'x': + expanded = g_strdup_printf("%s%s", + qof_date_format_get_string(QOF_DATE_FORMAT_LOCALE), + c + 2); + c = expanded; + continue; + default: + break; + } + c += 2; + } + + text = g_string_free(cdn_fmt, FALSE); + draw_text(context, text, &date_item, date_desc); + g_free(text); + if (expanded) + g_free(expanded); + pango_font_description_free(date_desc); +#endif +} + + +/** Print each of the items that in the description of a single check. This + * function uses helper functions to print text based and picture based + * items. */ +static void +draw_page_items(GncPrintContext * context, + gint page_nr, check_format_t * format, gpointer user_data) +{ + PrintCheckDialog *pcd = (PrintCheckDialog *) user_data; + PangoFontDescription *default_desc, *date_desc; + GNCPrintAmountInfo info; + const gchar *date_format; + gchar *text = NULL, buf[100]; + GSList *elem; + check_item_t *item, date_item; + gdouble width; + GDate *date; + + default_desc = pango_font_description_from_string(format->font); + + /* Now put the actual data onto the page. */ + for (elem = format->items; elem; elem = g_slist_next(elem)) { + item = elem->data; + + switch (item->type) { + case DATE: + date = g_date_new(); + g_date_set_time(date, pcd->date); + date_format = + gnc_date_format_get_custom(GNC_DATE_FORMAT + (pcd->date_format)); + g_date_strftime(buf, 100, date_format, date); + width = draw_text(context, buf, item, default_desc); + draw_date_format(context, date_format, item, default_desc, width); + g_date_free(date); + break; + + case PAYEE: + draw_text(context, pcd->payee, item, default_desc); + break; + + case MEMO: + draw_text(context, pcd->memo, item, default_desc); + break; + + case AMOUNT_NUMBER: + info = gnc_default_print_info(FALSE); + draw_text(context, xaccPrintAmount(pcd->amount, info), + item, default_desc); + break; + + case AMOUNT_WORDS: + text = numeric_to_words(pcd->amount); + draw_text(context, text, item, default_desc); + g_free(text); + break; + + case TEXT: + draw_text(context, item->text, item, default_desc); + break; + +#if USE_GTKPRINT + case PICTURE: + draw_picture(context, item); + break; +#endif + + default: + text = g_strdup_printf("(unknown check field %d)", item->type); + draw_text(context, text, item, default_desc); + g_free(text); + } + } + + pango_font_description_free(default_desc); +} + + +#if USE_GTKPRINT +/** Print each of the items that in the description of a single check. This + * function uses helper functions to print text based and picture based + * items. */ +static void +draw_page_boxes(GncPrintContext * context, + gint page_nr, check_format_t * format, gpointer user_data) +{ + cairo_t *cr; + GSList *elem; + check_item_t *item; + + cr = gtk_print_context_get_cairo_context(context); + + /* Now put the actual data onto the page. */ + for (elem = format->items; elem; elem = g_slist_next(elem)) { + item = elem->data; + if (!item->w || !item->h) + continue; + cairo_rectangle(cr, item->x, item->y - item->h, item->w, item->h); + cairo_stroke(cr); + } +} + + +/** Print an entire page based upon the layout in a check description file. + * This function takes care of translating/rotating the page, calling the function to print the grid + * pattern (if requested), an + +each of the items that in the description of a single check. This + * function uses helper functions to print text based and picture based + * items. */ +static void +draw_page_format(GncPrintContext * context, + gint page_nr, check_format_t * format, gpointer user_data) +{ + PrintCheckDialog *pcd = (PrintCheckDialog *) user_data; + cairo_t *cr; + gint i; + gdouble x, y, multip; + + cr = gtk_print_context_get_cairo_context(context); + cairo_translate(cr, format->trans_x, format->trans_y); + g_debug("Page translated by %f,%f", format->trans_x, format->trans_y); + cairo_rotate(cr, format->rotation * DEGREES_TO_RADIANS); + g_debug("Page rotated by %f degrees", format->rotation); + + /* The grid is useful when determining check layouts */ + if (format->show_grid) { + draw_grid(context, + gtk_print_context_get_width(context), + gtk_print_context_get_height(context)); + } + + /* Translate all subsequent check items if requested. */ + i = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->position_combobox)); + if ((i >= 0) && (i < pcd->position_max)) { + y = format->height * i; + cairo_translate(cr, 0, y); + g_debug("Position translated by %f (pre-defined)", y); + } else { + multip = pcd_get_custom_multip(pcd); + x = multip * gtk_spin_button_get_value(pcd->translation_x); + y = multip * gtk_spin_button_get_value(pcd->translation_y); + cairo_translate(cr, x, y); + g_debug("Position translated by %f (custom)", y); + } + + /* Draw layout boxes if requested. Also useful when determining check + * layouts. */ + if (format->show_boxes) + draw_page_boxes(context, page_nr, format, user_data); + + /* Draw the actual check data. */ + draw_page_items(context, page_nr, format, user_data); +} + +#else + +static void +draw_page_format(PrintSession * ps, check_format_t * format, gpointer user_data) +{ + PrintCheckDialog *pcd = (PrintCheckDialog *) user_data; + GnomePrintConfig *config; + gint i; + GSList *elem; + check_item_t *item; + gdouble width, height, x, y, multip; + + config = gnome_print_job_get_config(ps->job); + gnome_print_job_get_page_size_from_config(config, &width, &height); + + gnome_print_gsave(ps->context); + gnome_print_translate(ps->context, format->trans_x, format->trans_y); + g_debug("Page translated by %f,%f", format->trans_x, format->trans_y); + gnome_print_rotate(ps->context, format->rotation); + g_debug("Page rotated by %f degrees", format->rotation); + + /* The grid is useful when determining check layouts */ + if (format->show_grid) { + draw_grid(ps->context, width, height); + } + + /* Translate all subsequent check items if requested. */ + i = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->position_combobox)); + if ((i >= 0) && (i < pcd->position_max)) { + y = height - (format->height * (i + 1)); + gnome_print_translate(ps->context, 0, y); + g_debug("Check translated by %f (pre-defined)", y); + } else { + multip = pcd_get_custom_multip(pcd); + x = multip * gtk_spin_button_get_value(pcd->translation_x); + y = multip * gtk_spin_button_get_value(pcd->translation_y); + gnome_print_translate(ps->context, x, y); + g_debug("Check translated by %f (custom)", y); + } + + /* Draw layout boxes if requested. Also useful when determining check + * layouts. */ + if (format->show_boxes) { + for (elem = format->items; elem; elem = g_slist_next(elem)) { + item = elem->data; + if (!item->w || !item->h) + continue; + gnome_print_rect_stroked(ps->context, item->x, item->y, item->w, + item->h); + } + } + + draw_page_items(ps->context, 1, format, user_data); +} +#endif + +static void +draw_page_custom(GncPrintContext * context, gint page_nr, gpointer user_data) +{ + PrintCheckDialog *pcd = (PrintCheckDialog *) user_data; + GNCPrintAmountInfo info; + PangoFontDescription *desc; +#if USE_GTKPRINT + cairo_t *cr; +#endif + const gchar *date_format; + gchar *text = NULL, buf[100]; + check_item_t item = { 0 }; + gdouble x, y, multip, degrees; + GDate *date; + + desc = pango_font_description_from_string("sans 12"); + + multip = pcd_get_custom_multip(pcd); + degrees = gtk_spin_button_get_value(pcd->check_rotation); +#if USE_GTKPRINT + cr = gtk_print_context_get_cairo_context(context); + cairo_rotate(cr, degrees * DEGREES_TO_RADIANS); +#else + gnome_print_rotate(context, degrees); +#endif + g_debug("Page rotated by %f degrees", degrees); + + x = multip * gtk_spin_button_get_value(pcd->translation_x); + y = multip * gtk_spin_button_get_value(pcd->translation_y); +#if USE_GTKPRINT + cairo_translate(cr, x, y); +#else + gnome_print_translate(context, x, y); +#endif + g_debug("Page translated by %f,%f", x, y); + + item.x = multip * gtk_spin_button_get_value(pcd->payee_x); + item.y = multip * gtk_spin_button_get_value(pcd->payee_y); + draw_text(context, pcd->payee, &item, desc); + + item.x = multip * gtk_spin_button_get_value(pcd->date_x); + item.y = multip * gtk_spin_button_get_value(pcd->date_y); + date = g_date_new(); + g_date_set_time(date, pcd->date); + date_format = gnc_date_format_get_custom(GNC_DATE_FORMAT(pcd->date_format)); + g_date_strftime(buf, 100, date_format, date); + draw_text(context, buf, &item, desc); + g_date_free(date); + + item.x = multip * gtk_spin_button_get_value(pcd->words_x); + item.y = multip * gtk_spin_button_get_value(pcd->words_y); + info = gnc_default_print_info(FALSE); + draw_text(context, xaccPrintAmount(pcd->amount, info), &item, desc); + + item.x = multip * gtk_spin_button_get_value(pcd->number_x); + item.y = multip * gtk_spin_button_get_value(pcd->number_y); + text = numeric_to_words(pcd->amount); + draw_text(context, text, &item, desc); + g_free(text); + + item.x = multip * gtk_spin_button_get_value(pcd->memo_x); + item.y = multip * gtk_spin_button_get_value(pcd->memo_y); + draw_text(context, pcd->memo, &item, desc); + + pango_font_description_free(desc); +} + +#if USE_GTKPRINT +/* Print a page of checks. Today, check printing only prints one check at a + * time. When its extended to print multiple checks, this will need to take + * into account the number of checks to print, the number of checks on a page, + * and the starting check position on the page. This function is called once + * by the GtkPrint code once for each page to be printed. */ +static void +draw_page(GtkPrintOperation * operation, + GtkPrintContext * context, gint page_nr, gpointer user_data) +{ + PrintCheckDialog *pcd = (PrintCheckDialog *) user_data; + check_format_t *format; + + format = pcd->selected_format; + if (format) + draw_page_format(context, page_nr, format, user_data); + else + draw_page_custom(context, page_nr, user_data); +} + + +/* Compute the number of pages required to complete this print operation. + * Today, check printing only prints one check at a time. When its extended to + * print multiple checks, this will need to take into account the number of + * checks to print, the number of checks on a page, and the starting check + * position on the page. This function is called once by the GtkPrint code to + * determine the number of pages required to complete the print operation. */ +static void +begin_print(GtkPrintOperation * operation, + GtkPrintContext * context, gpointer user_data) +{ + gtk_print_operation_set_n_pages(operation, 1); +} + /********************************************************************\ * gnc_ui_print_check_dialog_ok_cb \********************************************************************/ @@ -292,79 +1534,53 @@ gnc_ui_print_check_dialog_create(GncPluginPageRegister *plugin_page, static void gnc_ui_print_check_dialog_ok_cb(PrintCheckDialog * pcd) { - SCM make_check_format = scm_c_eval_string("make-print-check-format"); - SCM print_check = scm_c_eval_string("gnc:print-check"); - SCM format_data; - SCM fmt, posn, cust_format, date_format; - int sel_option; - double multip = 72.0; + GtkPrintOperation *print; + GtkPrintOperationResult res; - char * formats[] = { "quicken", "deluxe", "wallet", "custom" }; - char * positions[] = { "top", "middle", "bottom", "custom" }; + print = gtk_print_operation_new(); - sel_option = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->format_combobox)); - if (-1 == sel_option) - return; - fmt = scm_str2symbol(formats[sel_option]); + if (settings != NULL) + gtk_print_operation_set_print_settings(print, settings); + gtk_print_operation_set_unit(print, GTK_UNIT_POINTS); + gtk_print_operation_set_use_full_page(print, TRUE); + g_signal_connect(print, "begin_print", G_CALLBACK(begin_print), NULL); + g_signal_connect(print, "draw_page", G_CALLBACK(draw_page), pcd); - sel_option = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->position_combobox)); - if (-1 == sel_option) - return; - posn = scm_str2symbol(positions[sel_option]); - - sel_option = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->units_combobox)); - switch(sel_option) { - case -1: return; - case 0: multip = 72.0; break; /* inches */ - case 1: multip = 28.346; break; /* cm */ - case 2: multip = 2.8346; break; /* mm */ - case 3: multip = 1.0; break; /* points */ + res = gtk_print_operation_run(print, + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, + pcd->caller_window, NULL); + + if (res == GTK_PRINT_OPERATION_RESULT_APPLY) { + if (settings != NULL) + g_object_unref(settings); + settings = g_object_ref(gtk_print_operation_get_print_settings(print)); + } + + g_object_unref(print); +} + +#else + +static void +gnc_ui_print_check_dialog_ok_cb(PrintCheckDialog * pcd) +{ + PrintSession *ps; + check_format_t *format; + + ps = gnc_print_session_create(TRUE); + + format = pcd->selected_format; + if (format) { + draw_page_format(ps, format, pcd); + } else { + draw_page_custom(ps->context, 1, pcd); } - - date_format = scm_makfrom0str - (gnc_date_format_get_custom(GNC_DATE_FORMAT(pcd->date_format))); - - cust_format = - SCM_LIST7 - (scm_cons(scm_str2symbol("payee"), - SCM_LIST2(scm_make_real(multip*gtk_spin_button_get_value(pcd->payee_x)), - scm_make_real(multip*gtk_spin_button_get_value(pcd->payee_y)))), - scm_cons(scm_str2symbol("date"), - SCM_LIST2(scm_make_real(multip*gtk_spin_button_get_value(pcd->date_x)), - scm_make_real(multip*gtk_spin_button_get_value(pcd->date_y)))), - scm_cons(scm_str2symbol("amount-words"), - SCM_LIST2(scm_make_real(multip*gtk_spin_button_get_value(pcd->words_x)), - scm_make_real(multip*gtk_spin_button_get_value(pcd->words_y)))), - scm_cons(scm_str2symbol("amount-number"), - SCM_LIST2(scm_make_real(multip*gtk_spin_button_get_value(pcd->number_x)), - scm_make_real(multip*gtk_spin_button_get_value(pcd->number_y)))), - scm_cons(scm_str2symbol("memo"), - SCM_LIST2(scm_make_real(multip*gtk_spin_button_get_value(pcd->memo_x)), - scm_make_real(multip*gtk_spin_button_get_value(pcd->memo_y)))), - scm_cons(scm_str2symbol("translate"), - SCM_LIST2(scm_make_real(multip*gtk_spin_button_get_value(pcd->translation_x)), - scm_make_real(multip*gtk_spin_button_get_value(pcd->translation_y)))), - scm_cons(scm_str2symbol("rotate"), - scm_make_real(gtk_spin_button_get_value(pcd->check_rotation)))); - - /* hide the window */ - gtk_widget_hide(pcd->dialog); - - /* now call the callback passed in from the scheme side with - the format as an arg */ - format_data = scm_apply(make_check_format, - SCM_LIST4(fmt, posn, date_format, cust_format), - SCM_EOL); - - scm_apply(print_check, - SCM_LIST5(format_data, - scm_makfrom0str(pcd->payee), - scm_make_real(gnc_numeric_to_double (pcd->amount)), - scm_ulong2num(pcd->date), - scm_makfrom0str(pcd->memo)), - SCM_EOL); - + + gnc_print_session_done(ps); + gnc_print_session_destroy(ps); } +#endif + static void @@ -376,29 +1592,71 @@ gnc_print_check_set_sensitive (GtkWidget *widget, gpointer data) void -gnc_print_check_combobox_changed (GtkComboBox *widget, +gnc_print_check_position_changed (GtkComboBox *widget, PrintCheckDialog * pcd) { gboolean sensitive; gint value; - value = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->format_combobox)); + value = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->position_combobox)); if (-1 == value) return; - sensitive = (value == (CHECK_PRINT_NUM_FORMATS - 1)); + sensitive = (value == pcd->position_max); + gtk_widget_set_sensitive(GTK_WIDGET(pcd->translation_label), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(pcd->translation_x), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(pcd->translation_y), sensitive); +} + +void +gnc_print_check_format_changed (GtkComboBox *widget, + PrintCheckDialog * pcd) +{ + GtkListStore *store; + gboolean sensitive; + gint fnum, pnum; + check_format_t *format; + GSList *elem; + + fnum = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->format_combobox)); + if (-1 == fnum) + return; + pnum = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->position_combobox)); + + /* Update the positions combobox */ + format = g_slist_nth_data(pcd->formats_list, fnum); + pcd->selected_format = format; + g_signal_handlers_block_by_func(pcd->position_combobox, + gnc_print_check_position_changed, pcd); + store = gtk_list_store_new (1, G_TYPE_STRING); + gtk_combo_box_set_model(GTK_COMBO_BOX(pcd->position_combobox), + GTK_TREE_MODEL(store)); + if (format) { + pcd->position_max = g_slist_length(format->positions); /* -1 for 0 base, +1 for custom entry */ + for (elem = format->positions; elem; elem = g_slist_next(elem)) { + gtk_combo_box_append_text(GTK_COMBO_BOX(pcd->position_combobox), elem->data); + } + } else { + pcd->position_max = 0; + } + gtk_combo_box_append_text(GTK_COMBO_BOX(pcd->position_combobox), _("Custom")); + pnum = MIN(pnum, pcd->position_max); + gtk_combo_box_set_active(GTK_COMBO_BOX(pcd->position_combobox), pnum); + g_signal_handlers_unblock_by_func(pcd->position_combobox, + gnc_print_check_position_changed, pcd); + + /* If there's only one thing in the position combobox, make it insensitive */ + sensitive = (pcd->position_max > 0); + gtk_widget_set_sensitive(GTK_WIDGET(pcd->position_combobox), sensitive); + + /* Update the custom page */ + sensitive = (fnum == pcd->format_max); gtk_container_foreach(GTK_CONTAINER(pcd->custom_table), gnc_print_check_set_sensitive, GINT_TO_POINTER(sensitive)); if (sensitive == TRUE) return; - value = gtk_combo_box_get_active(GTK_COMBO_BOX(pcd->position_combobox)); - if (-1 == value) - return; - sensitive = (value == (CHECK_PRINT_NUM_POSITIONS - 1)); - gtk_widget_set_sensitive(GTK_WIDGET(pcd->translation_label), sensitive); - gtk_widget_set_sensitive(GTK_WIDGET(pcd->translation_x), sensitive); - gtk_widget_set_sensitive(GTK_WIDGET(pcd->translation_y), sensitive); + gnc_print_check_position_changed(widget, pcd); } void @@ -414,7 +1672,8 @@ gnc_ui_print_check_response_cb(GtkDialog * dialog, case GTK_RESPONSE_OK: gnc_ui_print_check_dialog_ok_cb(pcd); gnc_ui_print_save_dialog(pcd); - /* fall through */ + gnc_save_window_size(GCONF_SECTION, GTK_WINDOW(dialog)); + break; case GTK_RESPONSE_CANCEL: gnc_save_window_size(GCONF_SECTION, GTK_WINDOW(dialog)); @@ -423,5 +1682,7 @@ gnc_ui_print_check_response_cb(GtkDialog * dialog, gtk_widget_destroy(pcd->dialog); g_object_unref(pcd->xml); + g_slist_foreach(pcd->formats_list, (GFunc)free_check_format, NULL); + g_slist_free(pcd->formats_list); g_free(pcd); } diff --git a/src/gnome/glade/print.glade b/src/gnome/glade/print.glade index 2ae57a2213..bec93895ad 100644 --- a/src/gnome/glade/print.glade +++ b/src/gnome/glade/print.glade @@ -193,7 +193,7 @@ Quicken(tm) Wallet Checks w/ side stub Custom False True - + 1 @@ -213,7 +213,7 @@ Bottom Custom False True - + 1 @@ -257,7 +257,7 @@ Custom True - 9 + 11 3 False 0 @@ -853,6 +853,91 @@ Points + + + + True + The origin point is the upper left-hand corner of the page. + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 3 + 9 + 10 + fill + + + + + + + True + The origin point is the lower left-hand corner of the page. + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 3 + 10 + 11 + fill + + + + + + + True + Degrees + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 2 + 3 + 7 + 8 + 6 + fill + + + False diff --git a/src/scm/Makefile.am b/src/scm/Makefile.am index 8d3a92bca9..8523333052 100644 --- a/src/scm/Makefile.am +++ b/src/scm/Makefile.am @@ -1,5 +1,5 @@ -SUBDIRS = gnumeric printing +SUBDIRS = gnumeric gncscmdir = ${GNC_SCM_INSTALL_DIR} gncscmmoddir = ${GNC_SHAREDIR}/guile-modules/gnucash diff --git a/src/scm/main.scm b/src/scm/main.scm index 6b144b4645..e4cbe265cb 100644 --- a/src/scm/main.scm +++ b/src/scm/main.scm @@ -67,10 +67,6 @@ ;; from main-window.scm (export gnc:main-window-properties-cb) -;; from printing/print-check.scm -(export make-print-check-format) -(export gnc:print-check) - ;; Get the Makefile.am/configure.in generated variables. (load-from-path "build-config.scm") @@ -220,7 +216,6 @@ ;; Now we can load a bunch of files. (load-from-path "command-line.scm") ;; depends on app-utils (N_, etc.)... - (load-from-path "printing/print-check.scm") ;; depends on simple-obj... (gnc:initialize-config-vars) ;; in command-line.scm ;; handle unrecognized command line args diff --git a/src/scm/printing/Makefile.am b/src/scm/printing/Makefile.am deleted file mode 100644 index 2f1677f7ca..0000000000 --- a/src/scm/printing/Makefile.am +++ /dev/null @@ -1,25 +0,0 @@ -gncscmdir = ${GNC_SCM_INSTALL_DIR}/printing -gncscm_DATA = print-check.scm - -gncscmmoddir = ${GNC_SHAREDIR}/guile-modules/gnucash/printing -gncscmmod_DATA = number-to-words.scm - -if GNUCASH_SEPARATE_BUILDDIR -SCM_FILE_LINKS = \ - ${gncscmmod_DATA} \ - ${gncscm_DATA} -endif - -.scm-links: -if GNUCASH_SEPARATE_BUILDDIR - for X in ${SCM_FILE_LINKS} ; do \ - $(LN_S) -f ${srcdir}/$$X . ; \ - done -endif - touch .scm-links - -noinst_DATA = .scm-links - -EXTRA_DIST = ${gncscmmod_DATA} ${gncscm_DATA} -CLEANFILES = .scm-links -DISTCLEANFILES = ${SCM_FILE_LINKS} diff --git a/src/scm/printing/number-to-words.scm b/src/scm/printing/number-to-words.scm deleted file mode 100644 index 89b77ee1b6..0000000000 --- a/src/scm/printing/number-to-words.scm +++ /dev/null @@ -1,113 +0,0 @@ -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; number-to-words.scm -;;; convert a number into a sentence for check printing -;;; -;;; Copyright 2000 Bill Gribble -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(define-module (gnucash printing number-to-words)) -(export integer-to-words) -(export printable-value) -(export number-to-words) - -(define (integer-to-words val) - (let ((current-string "") - (small-numbers - #("zero" "one" "two" "three" "four" "five" - "six" "seven" "eight" "nine" "ten" - "eleven" "twelve" "thirteen" "fourteen" "fifteen" - "sixteen" "seventeen" "eighteen" "nineteen" "twenty")) - (medium-numbers - #("zero" "ten" "twenty" "thirty" "forty" "fifty" - "sixty" "seventy" "eighty" "ninety")) - (big-numbers - #("hundred" "thousand" "million" "billion" "trillion" - "quadrillion" "quintillion"))) - (cond - ((< val 20) - (vector-ref small-numbers val)) - - ((< val 100) - (let ((this-part (quotient val 10)) - (that-part (remainder val 10))) - (set! current-string (vector-ref medium-numbers this-part)) - (if (> that-part 0) - (set! current-string - (string-append current-string "-" - (vector-ref small-numbers that-part)))) - current-string)) - ((< val 1000) - (let ((this-part (quotient val 100)) - (that-part (remainder val 100))) - (set! current-string - (string-append current-string - (vector-ref small-numbers this-part) " " - (vector-ref big-numbers 0))) - (if (> that-part 0) - (set! current-string - (string-append current-string - " " (integer-to-words that-part)))) - current-string)) - (#t - (let* ((log-val (inexact->exact - (truncate (+ .00001 (/ (log10 val) 3))))) - (this-part (quotient val - (inexact->exact - (truncate - (+ .00001 (expt 10 (* 3 log-val))))))) - (that-part (remainder val - (inexact->exact - (truncate - (+ .00001 (expt 10 (* 3 log-val)))))))) - (if (> this-part 0) - (set! current-string - (string-append (integer-to-words this-part) - " " (vector-ref big-numbers log-val)))) - (if (> that-part 0) - (set! current-string - (string-append current-string - " " (integer-to-words that-part)))) - current-string))))) - -;; return a string with the number properly truncated and zero padded -;; for check printing -(define (printable-value val frac-denom) - (let* ((int-part (inexact->exact (truncate val))) - (frac-part (inexact->exact - (truncate - (+ (/ .5 frac-denom) (* frac-denom - (- val int-part))))))) - (with-output-to-string - (lambda () - (write int-part) (display ".") - (if (< frac-part 10) (display "0")) - (write frac-part))))) - - -(define (number-to-words val frac-denom) - (let* ((negative? - (if (< val 0) - (begin (set! val (- val)) - #t) - #f)) - (int-part (inexact->exact (truncate val))) - (frac-part (inexact->exact - (truncate - (+ (/ .5 frac-denom) (* frac-denom (- val int-part)))))) - (result-string "")) - (set! result-string - (string-append (integer-to-words int-part) " and " - (with-output-to-string - (lambda () - (write frac-part) - (display "/") - (write frac-denom))))) - (string-set! result-string 0 - (char-upcase (string-ref result-string 0))) - result-string)) - - - - - - diff --git a/src/scm/printing/print-check.scm b/src/scm/printing/print-check.scm deleted file mode 100644 index d2ce203511..0000000000 --- a/src/scm/printing/print-check.scm +++ /dev/null @@ -1,216 +0,0 @@ -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; print-check.scm -;;; print a check from a transaction. -;;; -;;; Copyright 2000 Bill Gribble -;;; June 2004 - D. Reiser - added capability to print wallet checks -;;; with left-side stubs -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(use-modules (gnucash printing number-to-words)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; class -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(define - (make-simple-class - 'print-check-format - '(format - position - date-format - custom-info))) - -(define print-check-format? - (record-predicate )) - -(define print-check-format:format - (simple-obj-getter 'format)) - -(define print-check-format:set-format! - (simple-obj-setter 'format)) - -(define print-check-format:position - (simple-obj-getter 'position)) - -(define print-check-format:set-position! - (simple-obj-setter 'position)) - -(define print-check-format:date-format - (simple-obj-getter 'date-format)) - -(define print-check-format:set-date-format! - (simple-obj-setter 'date-format)) - -(define print-check-format:custom-info - (simple-obj-getter 'custom-info)) - -(define print-check-format:set-custom-info! - (simple-obj-setter 'custom-info)) - -(define (make-print-check-format fmt pos dateformat cust) - (let ((retval (make-simple-obj ))) - (print-check-format:set-format! retval fmt) - (print-check-format:set-position! retval pos) - (print-check-format:set-date-format! retval dateformat) - (print-check-format:set-custom-info! retval cust) - retval)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; stock formats -;; units for stock formats and positions are points (72/inch) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(define gnc:*stock-check-formats* - '((deluxe . ((payee . (126.0 147.0)) - (amount-words . (90.0 125.0)) - (amount-number . (395.0 147.0)) - (date . (343.0 178.0)) - (memo . (100.0 73.0)) - (rotate . 90.0) - (translate . (232.0 300.0)) - (offset . 0.0))) ;;declaration of offset preempts top/middle/bottom dialog choice - (quicken . ((payee . (90.0 150.0)) - (amount-words . (90.0 120.0)) - (amount-number . (500.0 150.0)) - (date . (500.0 185.0)) - (memo . (50.0 40.0)) - (top . 540.0) - (middle . 288.0) - (bottom . 36.0))) - (wallet . ((payee . (231.0 140.0)) ;;these coord. for placement above amount-word line - ;;use 202.0 94.0 for placement in address area - (amount-words . (195.0 125.0)) - (amount-number . (518.0 137.0)) - (date . (504.0 151.0)) - (memo . (216.0 37.0)) - (date-stub . (36.0 151.0)) - (payee-stub . (28.0 126.0)) - (amount-stub . (50.0 90.0)) - (memo-stub . (28.0 65.0)) - (top . 588.0) - (middle . 384.0) - (bottom . 180.0))) - (custom . ((top . 540.0) ;;set default perforation location for custom print layout - (middle . 288.0) - (bottom . 36.0))) - )) - -(define (gnc:print-check format-info payee amount date memo) - (let* ((int-part (inexact->exact (truncate amount))) - (frac-part (inexact->exact - (truncate - (+ (/ .5 100) (* 100 (- amount int-part)))))) - (ps (gnc-print-session-create #t)) - (format #f) - (offset #f) - (date-string "") - (payee-stub-text "") - (memo-stub-text "")) - - (if (not (null? ps)) - (begin - (if (not (eq? (print-check-format:format format-info) 'custom)) - (begin - (set! format (assq (print-check-format:format format-info) - gnc:*stock-check-formats*)) - (if (pair? format) - (begin - (set! format (cdr format)) - (let ((off (assq 'offset format))) - (if off (set! offset (cdr off))))))) - (set! format (print-check-format:custom-info format-info))) - - (if (not (eq? (print-check-format:format format-info) 'custom)) - (begin - (if (not (or offset (eq? (print-check-format:position format-info) 'custom))) - (begin - (set! offset - (cdr (assq (print-check-format:position format-info) - (cdr (assq (print-check-format:format format-info) - gnc:*stock-check-formats*))))) - (if (pair? offset) - (set! offset (cdr offset)))) - (set! offset - (caddr (assq 'translate - (print-check-format:custom-info format-info)))))) - (set! offset 0.0)) - - (let ((fmt (print-check-format:date-format format-info))) - (begin - (set! date-string (strftime fmt (localtime date))))) - - (display "offset is ") (display offset) (newline) - (let ((translate-pos (assq 'translate format))) - (if translate-pos - (begin - (display "translate by ") (display (cadr translate-pos)) - (display " ") (display (caddr translate-pos)) (newline) - (gnc-print-session-translate ps (cadr translate-pos) - (caddr translate-pos))))) - - (let ((rotate-angle (assq 'rotate format))) - (if rotate-angle (gnc-print-session-rotate ps (cdr rotate-angle)))) - - (let ((date-pos (assq 'date format))) - (gnc-print-session-moveto ps (cadr date-pos) - (+ offset (caddr date-pos))) - (gnc-print-session-text ps date-string)) - - (let ((payee-pos (assq 'payee format))) - (gnc-print-session-moveto ps (cadr payee-pos) - (+ offset (caddr payee-pos))) - (gnc-print-session-text ps payee)) - - (let ((number-pos (assq 'amount-number format))) - (gnc-print-session-moveto ps (cadr number-pos) - (+ offset (caddr number-pos))) - (gnc-print-session-text ps (printable-value amount 100))) - - (let ((words-pos (assq 'amount-words format))) - (gnc-print-session-moveto ps (cadr words-pos) - (+ offset (caddr words-pos))) - (gnc-print-session-text ps (number-to-words amount 100))) - - (if (not (eq? (print-check-format:format format-info) 'wallet)) - (let ((memo-pos (assq 'memo format))) - (gnc-print-session-moveto ps (cadr memo-pos) - (+ offset (caddr memo-pos))) - (gnc-print-session-text ps memo))) - - (if (eq? (print-check-format:format format-info) 'wallet) - (begin - (let ((memo-pos (assq 'memo format))) - (gnc-print-session-moveto ps (cadr memo-pos) - (+ offset (caddr memo-pos))) - (if (< (string-length memo) 28) - (gnc-print-session-text ps memo) - (gnc-print-session-text ps (substring memo 0 27)))) - (let ((memostub-pos (assq 'memo-stub format))) - (gnc-print-session-moveto ps (cadr memostub-pos) - (+ offset (caddr memostub-pos))) - (if (< (string-length memo) 22) - (set! memo-stub-text memo) - (set! memo-stub-text (substring memo 0 20))) - (gnc-print-session-text ps memo-stub-text)) - - (let ((datestub-pos (assq 'date-stub format))) - (gnc-print-session-moveto ps (cadr datestub-pos) - (+ offset (caddr datestub-pos))) - (gnc-print-session-text ps date-string)) - - (let ((payeestub-pos (assq 'payee-stub format))) - (gnc-print-session-moveto ps (cadr payeestub-pos) - (+ offset (caddr payeestub-pos))) - (if (< (string-length payee) 22) - (set! payee-stub-text payee) - (set! payee-stub-text (substring payee 0 20))) - (gnc-print-session-text ps payee-stub-text)) - - (let ((amountstub-pos (assq 'amount-stub format))) - (gnc-print-session-moveto ps (cadr amountstub-pos) - (+ offset (caddr amountstub-pos))) - (gnc-print-session-text ps (printable-value amount 100))))) - - (gnc-print-session-done ps))))) -