diff --git a/ChangeLog b/ChangeLog index 284796b8c0..a1f5b4734a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,755 @@ +1999-12-26 Dave Peticolas + + * src/gnome/window-report.c (reportWindow): Add a properties + button to the html window icon bar to allow the report parameters + dialog to be brought up. + (reportWindow): Add a title to the properties dialog. + + * src/gnome/window-html.c (htmlWindow): Add arguments to allow the + caller to add its own icons to the window. + +1999-12-25 Dave Peticolas + + * src/gnome/global-options.c (gnc_show_options_dialog): Added + title. + + * src/gnome/window-main.c: removed eperl reports menu items + + * src/gnome/window-report.c: new file. Report-specific gnome + gui code will reside here. + +1999-12-24 Dave Peticolas + + * src/gnome/window-html.c: take out help and report specific code. + + * src/gnome/dialog-options.c: removed global options code. + These routines now with with arbitrary options databases. + + * src/gnome/global-options.c: new function. global options are + implemented here. This uses dialog-options and option-util + functions. + + * src/gnome/option-util.c: removed global options code. + + * src/gnome/window-register.c (gnc_build_ledger_style_menu): set + the style menu to the default on creation. + +1999-12-23 Dave Peticolas + + * src/gnome/account-tree.c (gnc_account_tree_refresh): thaw after + all changes. + +1999-12-22 Dave Peticolas + + * src/gnome/option-util.c: many new functions. Rewrote this to + add the concept of option databases, a data structure which holds + a collection of options and allows access to them. This will be + used to hold, e.g., options for a report window. The old functions + now just call the new ones with the 'global' configuation option + database as the database argument. + + * src/gnome/dialog-report.c: Rename dialog-trans-report.? to + dialog-report.?. This will construct a generic report dialog. + +1999-12-21 Robert Graham Merkel + + * src/gnome/dialog-trans-report.c: New file. GNOME code for + creating a dialog box for information on the transaction report. + At the moment only contains a stub for the actual dialog box code. + + * src/gnome/dialog-trans-report.h: New file. Headers for + the dialog box stuff, obviously! + + * src/gnome/Makefile.in: modified to compile dialog-trans-report.c + +1999-12-21 Dave Peticolas + + * src/gnome/window-reconcile.c: enhance this window with + new, edit, and delete transaction buttons. + +1999-12-20 Heath Martin + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_size_allocate): Configure the item_editor on a + resize. + + * src/register/gnome/gnucash-cursor.c + (gnucash_cursor_get_pixel_coords): Don't use the horizontal scroll + offset. + + * src/register/gnome/gnucash-item-edit.c + (item_edit_get_pixel_coords): Ditto. + + * src/register/gnome/gnucash-item-list.c (gnc_item_list_new): Set + the "x" coordinate on creation. + +1999-12-20 Dave Peticolas + + * src/gnome/window-register.c (regWindowLedger): get the default + register type guile option to set the register style. + (regRefresh): set the toolbar button display based on the guile + option value. + +1999-12-19 Dave Peticolas + + * src/MultiLedger.c (xaccRegisterRefreshAllGUI): new function. + call gui refresh on all registers. + + * src/MultiLedger.h: remove unecessary externs. + +1999-12-19 Heath Martin + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_update_adjustments): Update the horizontal + adjustments, too. + (gnucash_register_new): Attach the horizontal scrollbar to the + horizontal adjustment. + + * src/register/gnome/gnucash-header.c (gnucash_header_set_arg): + Hook up the hadjustment from the sheet so that they scroll + horizontally together. + +1999-12-19 Dave Peticolas + + * src/engine/util.c: applied Grant Likely's patch to the number + formatting routines. + + * src/register/splitreg.c: applied Grant Likely's patch to make + stock register have 4 decimal places of accuracy. + + * Applied Christopher Brown's html and QIFIO.c patch. + +1999-12-19 Robert Graham Merkel + + * src/src/report/transaction-report.scm: New file. Will contain + code for doing a transaction report on an account. At this stage + all it does is display unformatted raw scheme lists. + + * README.guile-hackers: Added general information about Scheme and + a pointer to net resources about it. Also explained a little + about g-wrap and gave a pointer to the documentation for it. + +1999-12-18 Heath Martin + + * src/register/gnome/gnucash-header.c + (gnucash_header_reconfigure): Don't set the width of the widget, + let it find it's own width. + + * src/register/gnome/gnucash-style.c + (gnucash_sheet_style_set_dimensions): Reworked extensively. It + now tries very hard to fit the cursors in the register window. + Supports a number of layout options for the cursors, as detailed + in the source. + +1999-12-18 Dave Peticolas + + * src/gnome/window-register.c (gnc_register_create_tool_bar): + Don't cause a legister refresh. + +1999-12-17 Dave Peticolas + + * src/gnome/dialog-edit.c (gnc_ui_EditAccWindow_ok_cb): refresh + all the registers. + + * src/gnome/dialog-add.c (gnc_ui_accWindow_create_account): + refresh all the registers. + + * src/gnome/window-register.c (regRefresh): reload the transfer + cells. + + * src/SplitLedger.c (xaccSRLoadXferCells): new function. Load the + transfer cells in a split register. + + * src/register/gnome/combocell-gnome.c (xaccClearComboCellMenu): + new function. clear all items in the combocell menu. + +1999-12-16 Heath Martin + + * src/register/table-allgui.c (gnc_table_traverse_update): Use the + exit flags from the cellblock to decide how to set + exit_register, and don't change entries in the traverse arrays + to positive. + + * src/register/splitreg.c (configTraverse): Mark all the exit rows + and columns, too. + + * src/register/cellblock.h: Add variables to the CellBlock struct + that flag the exit cells. We can't use negative entries in the + traverse arrays anymore, since left traverses sometimes need + valid negative directions. + +1999-12-16 Dave Peticolas + + * src/register/pricecell.c (xaccPriceCellPrintValue): new + function. return a pointer to a static string buffer with the + cell amount printed. + (xaccSetPriceCellValue): simplify with above function. + (PriceLeave): new leave callback for price cells. Pretty-prints + cell value using xaccPriceCellPrintValue. + + * src/register/gnome/gnucash-sheet.c (gnucash_sheet_cursor_move): + have this function take physical instead of virtual coordinates. + This function now 'does the move' using wrapVerifyCursorPosition. + It has smarts that were in the three functions below. + It returns TRUE if the current cell changed. + (gnucash_button_press_event): simplify + (gnucash_sheet_key_press_event): simplify + (gnucash_sheet_goto_virt_row_col): simplify + +1999-12-15 Dave Peticolas + + * src/register/table-allgui.c (gnc_table_traverse_update): take + out the reverify fields. These are unnecessary for the auto modes + (at least for gnome) and complicate the leave semantics. + + * src/SplitLedger.c (xaccSRGetCurrentTrans): use the register + physical row and not the table. needed for auto mode movement. + + * src/register/table-allgui.c (gnc_table_leave_update): do the + wrapverify after we check for changes. I'm not sure if wrapverify + could change the outcome of this, but doing the check after the + wrap verify makes no sense since the row and col values aren't + meaningful anymore. + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_deactivate_cursor_cell): always call the leave + update, even for "bad" locations. We need the wrap verify that + happens in the gnc_table_leave_update. + +1999-12-15 Robert Graham Merkel + + * src/gnome/dialog-add.c (xaccSetDefaultNewaccountCurrency): Added + an interface to set the default currency for new accounts. + + * src/scm/prefs.scm (gnc:make-string-option) added a new option + generator for strings, used this to add a "default new account + currency" option + + * src/gnome/top-level.c (gnc_configure_newacc_currency_cb) + callback to update the default currency when options are changed + (default_configure_newacc_currency) does the work of updating + (gnucash_ui_init) registered the new callback + +1999-12-14 Dave Peticolas + + * src/register/table-allgui.c (gnc_table_traverse_update): a fix + for the auto modes. + + * src/SplitLedger.c (xaccSRCountRows): Look for the cursor + position using the transaction, as well as the split. Useful when + changing modes when you are on a blank split in multi-line. + (xaccSRGetCurrentTrans): new function. Gets current transaction. + Works when you are on blank splits as well. + (LedgerTraverse): save the old register values and then restore + them. + +1999-12-13 Dave Peticolas + + * src/register/gnome/gnucash-header.c + (gnucash_header_reconfigure): check for a valid header row before + proceeding. + +1999-12-13 Heath Martin + + * src/register/gnome/gnucash-cursor.c (gnucash_cursor_set): Set + the type and row arguments in the header. + + * src/register/gnome/gnucash-header.c (gnucash_header_draw): Tweak + the coords on the separator line a bit. Draw the header based on + header->type and header->row. + (gnucash_header_set_arg): Add two new arguments to support setting + the type of the header from the current cursor and the row within + the cursor to draw. + + * src/register/gnome/gnucash-style.c + (gnucash_sheet_style_compile): Set the header font. + +1999-12-13 Dave Peticolas + + * src/gnome/option-util.c (gnc_lookup_multichoice_option): Rob + Merkel's patch. + + * src/engine/date.c (printDate): Rob Merkel's patch to i18n date + formats. + + * src/gnome/top-level.c (gnc_configure_date_format): Rob Merkel's + patch to i18n date formats. + +1999-12-12 Heath Martin + + * src/register/table-allgui.c (gnc_table_traverse_update): Put an + assert(0) in the default case, since now we handle all possible + cases. + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_key_press_event): Remove hack to wrap around when + moving the cursor. splitreg.c takes care of this, after a + fashion. + +1999-12-12 Dave Peticolas + + * src/register/gnome/combocell-gnome.c (disconnect_list_signals): + if the list has been destroyed, don't bother. + (connect_list_signals): ditto. + +1999-12-10 Heath Martin + + * src/register/splitreg.c (configTraverse): Configure left + traverses, too. + + * src/register/cellblock.[c,h] (xaccInitCellBlock): Allocate left + traverse matrices. + (FreeCellBlockMem): Free left traverse matrices. + (xaccNextLeft): New function. + + * src/register/table-allgui.c (gnc_table_traverse_update): Add + support for left traverses. + +1999-12-10 Dave Peticolas + + * src/gnome/window-register.c (jump_cb): new callback. Used to + jump to 'other' account. + + * src/gnome/option-util.c: modify multichoice + functions. multichoice options are now a list of vectors, + where each vector contains the value, name, and description. + + * src/gnome/dialog-options.c (gnc_option_set_ui_widget): Rob + Merkel's patch to add the option name to a multichoice option. + +1999-12-09 Dave Peticolas + + * src/gnome/dialog-utils.c (gnc_build_option_menu): change this + function so that the callback includes the index of the selection + as well as the user_data. + + * src/gnome/option-util.c + (gnc_option_value_num_permissible_values): new function. return # + of permissible values. + (gnc_option_value_permissible_value_index): new function. search + for SCM value in permissible value list. + (gnc_option_value_permissible_value_name): new function. return + name of indexth permissible value. + (gnc_option_value_permissible_value_help): new function. return + help of indexth permissible value. + + * src/gnome/dialog-options.c (gnc_option_set_ui_value): add + multichoice option. + (gnc_option_get_ui_value): add multichoice option. + (gnc_option_multichoice_cb): new function. callback for + multichoice buttons. We use an object data with key + "gnc_multichoice_index" to store the index with the option menu. + (gnc_option_create_multichoice_widget): new function. create the + widget for multichoice options. + (gnc_option_set_ui_widget): add multichoice option. + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_block_destroy): Don't free the entries and colors, + that is done by gnucash_sheet_block_clear_entries now. + (gnucash_sheet_block_clear_entries): Set the freed pointers to + NULL for safety. + +1999-12-09 Heath Martin + + * src/register/gnome/gnucash-color.c (color_hash): The argument is + an argb, not a GdkColor. So we'll just use its value directly. + This fixes a bug in that we were getting lots of failed color + lookups in the hash table, and therefore lots of redundant + insertions. + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_block_clear_entries): Free entries, fg_colors, + bg_colors here. We were leaking [fg,bg]_colors. + + +1999-12-09 Dave Peticolas + + * src/gnome/window-register.c (regWindowLedger): set the window + policy to allow shrinking and growing. + + * src/SplitLedger.c (xaccSRSaveRegEntry): change the memo + before you create the 'other' split, so it gets the same memo. + Also, if we are committing the blank split, insert it into the + account before we create the 'other' split, so the other split + can get a parent account, too. + +1999-12-08 Dave Peticolas + + * src/scm/main.scm (gnc:ui-finish): destroy the ui subwindows + before doing a file-query-save. The register windows need to + be closed before saving, in case there are edited transactions. + + * src/gnome/top-level.c (gnc_ui_destroy_all_subwindows): new + function. used by guile to destroy all non-main windows. + + * src/gnome/window-help.c (gnc_ui_destroy_html_windows): new + function. destroy any help or report windows open. + +1999-12-08 Heath Martin + + * src/register/gnome/gnucash-cursor.c (gnucash_item_cursor_draw): + Draw an inner rectangle in the cell cursor. This balances the + cursor top/bottom versus sides, and fixes some ugliness especially + in double line mode. + +1999-12-08 Dave Peticolas + + * src/SplitLedger.c (xaccSRLoadRegister): restore original + position a bit more accurately. + +1999-12-07 Dave Peticolas + + * src/scm/text-export.scm (gnc:account-transactions-export-as-text): + return #f as value so the traversal keeps going. (See Group.h). + + * src/g-wrap/gnc-helpers.c: use gh_long2scm instead of the + long long version. This is incorrect for very large values, + but longlong doesn't seem to be working right now. + + * src/engine/LedgerUtils.c (accListHasAccount): new function. + search for account in list. + + * src/MultiLedger.c: Only add the leader if it wasn't in the list. + + * src/gnome/window-register.c (gnc_register_create_menu_bar): + Added an option to scrub the account. Changed Register menu + to Account menu. + + * src/engine/Transaction.c (MARK_SPLIT): mark the account group + as not saved. Ensures we get a "do you want to save" dialog. + + * src/register/gnome/gnucash-sheet.c (compute_optimal_height): + request height for DEFAULT_REGISTER_ROWS rows. + + * src/gnome/window-register.c (deleteCB): fancy deletes. + +1999-12-06 Dave Peticolas + + * src/SplitLedger.c (xaccSRSaveRegEntry): Insert the blank split + into the account when it is committed, not when it is created. + Otherwise, the blank split gets saved to a file if you save with + an open register. Also, it gets stuck in the reconcile window if + you open that with an open register. + + * src/register/pricecell.c (xaccSetDebCredCellValue): simplify + and fix value setting bug. + + * src/SplitLedger.c (xaccSRCancelCursorTransChanges): new + function. Cancel the changes to the current transaction. + + * src/motif/RegWindow.c (cancelCB): use SR functions instead. + + * src/g-wrap/gnc.gwp: add wrapper for xaccGroupGetBalance + + * src/gnome/window-register.c (regWindowLedger): take out the size + code altogether. + +1999-12-06 Heath Martin + + * src/register/gnome/gnucash-sheet.c (DEFAULT_REGISTER_WIDTH): + Increase this a little, so at least a checking account register is + drawn correctly. This is a hack for now anyway until the + computations in gnucash-style.c are reworked. + + * src/gnome/window-register.c (regWindowLedger): Comment out the + call to gtk_widget_set_usize (), so the register window finds it's + own natural size. + + * src/register/gnome/gnucash-header.c + (gnucash_header_reconfigure): Compute w, h correctly. + +1999-12-05 Dave Peticolas + + * src/gnome/window-register.c (closeCB): Query the user as to + whether or not to save a changed transaction. + + * src/MultiLedger.c (xaccLedgerDisplayClose): Only refresh + if there were really changes. + + * src/SplitLedger.c (LedgerDestroy): Paranoia checks. + + * src/gnome/window-register.c (regWindowLedger): catch the register + "activate_cursor" signal. Upon receipt, record the transaction and + move to the next virtual row. + + * src/register/gnome/gnucash-sheet.c + (gnucash_register_goto_next_virt_row): new function. Cause the + register cursor to advance to the next virtual row. + + * src/register/gnome/gnucash-sheet.h: Add a "activate_cursor" + callback to the register that is emitted when the user pressed + return. + +1999-12-04 Dave Peticolas + + * src/gnome/window-main.c (gnc_ui_mainWindow_scrub): new function. + Scrub the current account. + (gnc_ui_mainWindow_toolbar_open_subs): new function. + Open subaccounts. + + * src/gnome/window-register.c (gnc_register_raise): new function. + Cause the given register window to be raised to the top. + + * src/gnome/scripts_menu.c (gnc_extensions_menu_add_item): We have + to register the scheme scripts to make sure they aren't + garbage-collected. + (gnc_extensions_shutdown): New function. Call when shutting down + to free memory structures and unregister scheme scripts. + + * src/gnome/window-register.c (gnc_register_create_popup_menu): new + function. Create a popup menu for the register. + (regWindowLedger): Add the popup menu to the register. + + * src/register/gnome/gnucash-sheet.c (gnucash_button_press_event): + return FALSE for unused events so they are passed up the hierarchy. + + * src/gnome/window-adjust.c (adjBWindow): use fully qualified + account name in window title. + + * src/gnome/window-register.c (regWindowLedger): use fully + qualified name in the title of the window. + + * src/gnome/dialog-utils.c (gnc_ui_get_account_full_name): new + function. Construct the fully-qualified account name using the + given separator string. + + * src/engine/Account.c (xaccAccountGetParentAccount): new function. + Return the parent account of an account. + + * src/engine/Group.c (xaccGroupGetParentAccount): new function. + Return the parent account of a group. + + * src/register/gnome/gnucash-sheet.c (gnucash_sheet_key_press_event): + respond to keypad directional keys. + +1999-12-03 Dave Peticolas + + * src/SplitLedger.c (xaccSRLoadRegister): When we refresh, look + for the pending transaction. If we don't find it, clear the + pending transaction to prevent it being committed later. + + * src/g-wrap/gnc.gwp: added binding for reportWindowDirect + + * src/gnome/window-register.c (helpCB): help window should not + be parented. + + * src/gnome/window-main.c (gnc_ui_help_cb): help window should + not be parented. + (gnc_ui_reports_cb): reports window should not be parented. + + * src/engine/Transaction.c (xaccTransBeginEdit): Check for an + already open transaction. + (xaccTransIsOpen): new function. returns true if transaction + is open for editing. + + * src/gnome/window-register.c (regWindowLedger): Use a gnome dock + to hold the window contents. This is more flexible. + (gnc_register_create_tool_bar): use graphical buttons. Also, don't + create the handle box, the gnome dock item will handle that. + (gnc_register_create_menu_bar): don't create the handle box. + + * src/reports/Sheet.c: Rob Merkel's patch for locating eperl + + * src/gnome/account-tree.c (gnc_account_tree_refresh): tighter + bounds on adjustment value. Prevents quirks when refreshing to + a smaller number of accounts. + + * configure.in: Rob Merkel's eperl and gtkxmhtml build patches + + * Makefile.in: Rob Merkel's patch to enforce gtkxmhtml requirement + + * src/gnome/window-register.c (deleteCB): use + xaccSRDeleteCurrentSplit to delete the split. + +1999-12-02 Dave Peticolas + + * src/register/gnome/combocell-gnome.c (moveCombo): disconnect + the list signals. + + * src/SplitLedger.c (LedgerMoveCursor): update cursor_phys_row and + cursor_virt_row in the SplitRegiter. + (xaccSRDeleteCurrentSplit): new function. delete the current split + in the register. + (xaccSRCountRows): don't move the saved physical and virtual rows + if the current split was NULL. + + * Makefile.in: Added Tyson Dowd's rules to rerun autoconf + and configure when needed. + +1999-12-01 Rob Browning + + * src/scm/txn-create.scm: remove trailing garbage. + + * src/scm/srfi: new directory containing source for various Scheme + Requests for Implementation. We now have srfi-8 and srfi-1. + These are very useful. Docs are available at + http://srfi.schemers.org/. + + * src/scm/report/folio.scm: new file implementing + Reports/report-folio.phtml. Not finished, but not loaded either. + + * src/scm/report/dummy.scm: new file to test the report system. + Shows the current date. + + * src/scm/report/balance-and-pnl.scm: new file to generate balance + sheet and profit and loss reports. Implements + Reports/report-baln.phtml and Reports/report-pnl.phtml. The code + in here could be better, but it's not awful. It's mostly a + straight eperl port. + + * src/scm/report.scm: new file implementing the initial + scheme-based report system. + + * src/scm/main.scm: use the new depend mechanism and load the new + srfis + + * src/scm/extensions.scm: reformat so all the code doesn't go off + the right side of a normal screen (with tabs set to 8, etc). + Linas might veto this, but I thought it was worthwhile. + + * src/scm/depend.scm: new file adding a support/depend mechanism. + Modify other files to start using it. + +1999-11-30 Dave Peticolas + + * src/gnome/window-help.c: extensive rewrite. Revamped look to use + dockable toolbar. Handle motion keys. Escape key closes window. + Graphics are now loaded. + +1999-11-28 Dave Peticolas + + * src/register/table-allgui.c (doMoveCursor): don't scroll when we + update the cursor after the the callback. + + * src/register/table-gnome.c (doRefreshCursorGUI): same as below + + * src/register/table-motif.c (doRefreshCursorGUI): same as below + + * src/register/table-allgui.c (xaccRefreshCursorGUI): add a + do_scroll argument to determine whether the gui should scroll to + make the cursor visible. This lets us cut down on flashing. + + * src/register/gnome/gnucash-sheet.c (gnucash_sheet_table_load): + try to avoid scrolling + (gnucash_sheet_cursor_set_from_table): remove redundant call to + update adjustments. + + * src/gnome/window-register.c (cancelCB): use + xaccSplitRegisterClearChangeFlag. + (gnc_register_create_tool_bar): right-align the displayed balances + + * src/register/splitreg.c (xaccSplitRegisterClearChangeFlag): new + function. Clear the change flags of the register. + + * src/SplitLedger.c (xaccSRCancelCursorChanges): new + function. Cancel the changes made to the current cursor. + +1999-11-27 Dave Peticolas + + * src/gnome/window-register.c (regWindowLedger): set default sort + order to the standard order. + (gnc_build_ledger_sort_order_menu): add the standard order to the + list of options. + (gnc_ledger_sort_cb): handle the standard order + + * src/engine/Query.c (xaccQuerySetSortOrder): added BY_STANDARD and + BY_NONE sort orders. + + * src/register/splitreg.c (configLayout): configure the single + cursor last so the header is the most reasonable. This is a + hack. Eventually, we should probably switch headers dyamically. + + * src/register/gnome/gnucash-sheet.c + (gnucash_register_goto_virt_row_col): replaces and generalizes + gnucash_sheet_go_to_last_row. + + * src/gnome/window-register.c (gnc_register_jump_to_blank): new + function to jump to the blank split. + (new_trans_cb): use gnc_register_jump_to_blank + (regWindowLedger): jump to the blank split with above function + before returning + + * src/SplitLedger.c (xaccSRGetSplitRowCol): new function + (xaccSRGetBlankSplit): new function + + * src/register/splitreg.c (configLabels): apply Rob Walker's patch + to fix the labels. + + * src/gnome/window-register.c (gnc_register_date_cb): set start + date to the first second of the day selected and the end date to + the last second of the day selected. + + * src/engine/Query.c (xaccQueryGetSplits): fixed a problem to + prevent returning one split too many. We need to check for the + max date before we add the split. + +1999-11-23 Dave Peticolas + + * src/register/gnome/gnucash-sheet.c (gnucash_sheet_go_to_last_row): + jump to the last row in the sheet. This will be the blank split. + + * src/gnome/window-register.c (gnc_register_create_menu_bar): add + a menu item for moving to the blank split + (regWindowLedger): jump to the last row, i.e., the blank split, + before you return. + + * src/register/gnome/gnucash-style.c (gnucash_style_layout_init): + make the reconcile cell visible in mult-line mode. It's not + active, but it looks better. + + * src/SplitLedger.c (LedgerMoveCursor): tell xaccSRSaveRegEntry + which transaction we are moving to. + (xaccSRSaveRegEntry): add an argument to indicate which + transaction will be current after the save. This allows us to + commit right away if we are changing transactions and keeps the + register gui in sync with the engine. + + * src/register/table-allgui.c (doMoveCursor): refresh the gui + cursor after the second makePassive if we are moving the gui. + This fixes a refresh bug that happens if the splits get reordered. + + * configure.in and src/gnome/Makefile.in: Rob Merkel's patch + to check for gtkxmhtml + + * src/scm: applied Rob Walker's patch to add a version argument + + * src/gnome/account-tree.c (gnc_account_tree_init): auto resize + the first column + + * src/gnome/window-main.c (mainWindow): give focus to the + account tree before we exit + +1999-11-22 Rob Browning + + * src/register/table-motif.c (modifyCB): *trivial* modification to + support modify_verify cursor_position arg. I'm not in a position + to easily deal with the XmBae callback complexity. This should + just leave the status-quo. + + * src/motif/RegWindow.c (dateChangeCB): ditto. + + * src/register/table-allgui.[hc]: quickfill fixes, support the new + modify_verify cursor_position arg. + + * src/register/gnome/gnucash-sheet.c: quickfill fixes, use the new + modify_verify cursor_position arg. + + * ChangeLog: merge all the src/register/gnome/ChangeLog entries + into this file and delete that file to finish up what Dave started + on 1999-11-20. Aside from being cleaner, it also makes emacs (and + I suspect other editors) automatically find the right file when + you do a "C-x 4 a" (changelog-add-entry). + + * src/register/*cell.h: added position (int *) parameter to + all cell MV calls. This allows the callback to reposition the + cursor within the text if needed. This was required by the + quickfill fix, but is probably useful elsewhere. + + * src/register/quickfillcell.c (quick_modify): fixed so that + cursor tracks as you type along with some other important bugs + (quickfill divergences now cause the remainder of the stale fill + to be deleted, etc.) + 1999-11-21 Dave Peticolas * src/gnome/window-register.c: (recordCB): when when record @@ -12,6 +764,9 @@ 1999-11-20 Dave Peticolas + * src/register/gnome/ChangeLog: we're going to stop using this + changlog file and put all changes in the top-level ChangLog file. + * src/gnome/reconcile-list.[ch]: Use a hash table to remember reconcile flags after a refresh. Also remember scroll position. Change row styles so active row isn't highlited. @@ -60,10 +815,54 @@ * src/gnome/: modify dialogs to use parented dialogs + * src/register/gnome/gnucash-cursor.c: (gnucash_item_cursor_draw): + Heath's mod which removes the outer rectangle. + + * src/register/gnome/gnucash-item-edit.c: (item_edit_update): + Heath's mod to make the combo list draw correctly + 1999-11-18 Dave Peticolas - * src/engine/util.c: (PrtAmtComma): fixed rounding error - when remainder is close to 1. + * src/engine/util.c: (PrtAmtComma): fixed + rounding error when remainder is close to 1. + +1999-11-17 Dave Peticolas + + * src/register/gnome/gnucash-header.[ch]: removed type + variable. The header should always be type GNUCASH_CURSOR_HEADER + + * src/register/gnome/gnucash-style.c: + (gnucash_sheet_style_compile): use the cell labels for the header + style labels + + * src/register/gnome/gnucash-item-edit.[ch]: Added a toggle button + for popping the combo list. + + * src/register/gnome/gnucash-style.c: (gnucash_style_layout_init) + make the width of the 'num' column of the split cursor the same as + the others. + +1999-11-15 Dave Peticolas + + * src/register/gnome/gnucash-sheet.c: (gnucash_sheet_delete_cb) + and + (gnucash_sheet_insert_cb): use malloc/free instead of glib + routines for newval. newval may be free()d by routines in the + register code. You can't mix-n-match. + + * src/register/gnome/gnucash-sheet.c (gnucash_sheet_destroy) and + (gnucash_sheet_new): sink the entry and unref it after the item + editor is destroyed. This fixes a memory leak, the entry was not + being finalized. + + * src/register/gnome/gnucash-sheet.c add + (gnucash_sheet_key_press): allow outside entities to pass + keystrokes to the sheet. Used by the combocell. + + * src/register/gnome/combocell-gnome-c: extensive modifications to + support the new list item. + + * src/register/gnome/gnucash-item-edit.[ch]: ditto 1999-11-12 Dave Peticolas @@ -88,6 +887,13 @@ * src/gnome/account-tree.c: (gnc_account_tree_select_account): scroll to make sure account is visible. + * src/register/gnome/gnucash-item-edit.c: (item_edit_draw): make + the cursor larger and allow it to be seen even when it's at the + end of a too-long-to-fit-in-cell string. + + * src/register/gnome/gnucash-item-list.[ch]: new code to support + the combo cell. + 1999-11-10 Dave Peticolas * src/gnome/dialog-filebox.c: Use GtkFileSelection instead @@ -101,6 +907,13 @@ * src/gnome/window-main.: added Save As menu item. + * src/register/gnome/combocell-gnome.c: reformatted to be + idiomatic + + * src/register/gnome/gnucash-sheet.c: + (gnucash_sheet_key_press_event): Added support for pageup/down and + home/end keypressed. + 1999-11-07 Dave Peticolas * src/Refresh.c: Added this code for refreshing account-related @@ -109,6 +922,39 @@ * src/gnome/window-register.c: fixed deleting of blank split + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_activate_cursor_cell): handle enter updates when + new_text is non-null by updating the cell from the table and *not* + src/register/gnome/starting the editing widget. + + * gnucash-grid.c (draw_cell): used CELL_HPADDING for right justify + adjustment on x value instead of CELL_VPADDING. + +1999-11-05 Dave Peticolas + + * src/register/gnome/gnucash-grid.c (gnucash_grid_draw): use + g_return_if_fail for error checks. + + * src/register/gnome/gnucash-grid.c (gnucash_grid_unrealize): + Sanity checks before unrefing the gc's. + + * src/register/gnome/gnucash-header.c (gnucash_header_unrealize): + sanity checks + + * src/register/gnome/gnucash-currsor.c (gnucash_cursor_unrealize): + sanity checks + + * src/register/gnome/gnucash-sheet.h (GnucashRegisterClass): + parent class should be GtkTableClass + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_update_adjustments): update page_increment, so + clicking on the rest of the scrollbar scrolls one page + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_key_press_event): call gnc_table_traverse_update on + up/down motion. + 1999-11-04 Dave Peticolas * Add this changelog entry which I should have done a long @@ -131,6 +977,35 @@ * Remove dependencies on gtksheet. Hooray! + * src/register/gnome Various: a few fixes here and there to make + everything compile cleanly under -Wall. Mainly removing unused + local variables, and changing g_return_if_fail to + g_return_val_if_fail for functions which return a value. Added a + few prototypes that were missing. Cleaned up #includes. + + * src/register/gnome/gnucash-sheet.c (gnucash_register_new): Make + the vertical scrollbar flush with the top of the register. + + * src/register/gnome/Makefile.in: removed motif and qt + targets. Made gnome target the default. + +1999-08-30 Heath Martin + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_key_press_event): Be sure to set new_p_row in the + case GDK_Tab, since the function gnc_table_traverse_update checks + the proposed new row/col, so we need to initialize properly. + +1999-08-28 Heath Martin + + * TODO: New file. + + * src/register/gnome/gnucash-style.c (gnucash_style_init): Removed + the dependency on gnome-print for now. We may want to use the + gnome-print library in the future. + + * Makefile.in (LIBS): Ditto + 1999-08-25 Rob Browning * Add ./README.gnome-hackers. @@ -159,3 +1034,228 @@ * Add function (current-gnc-compile-flavor) to gnc.gwp so that we can have conditional blocks. Possible return values are 'gnome and 'motif. + +1999-08-23 Heath Martin + + * src/register/gnome/gnucash-header.c (gnucash_header_draw): Added + a separator line between the header and the register entries. + + * src/register/gnome/gnucash-item-edit.c (item_edit_new): Attempt + to implement the comboboxes. For some reason, I can't get combos + to work, something related to how the widget size is computed. + Comment this out for now. + + * src/register/gnome/gnucash-item-edit.c (item_edit_set_arg): + Ditto + +1999-08-20 Heath Martin + + * src/register/gnome/gnucash-grid.c (draw_cell): Set the + background/foreground color on a per cell basis. This obsoletes + the bg/fg colors in the styles for now. + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_block_clear_entries): Fixed a big memory leak here. + (gnucash_sheet_block_set_entries): Support for background and + foreground colors for the cells. Set these from the table. + +1999-08-19 Heath Martin + + * src/register/gnome/gnucash-style.c (gnucash_style_layout_init): + Layout the stock registers. + +1999-08-18 Heath Martin + + * src/register/gnome/gnucash-grid.c (gnucash_grid_draw): When the + block is active, if a cell doesn't have a user-entered string, + then draw the label for that cell type as a hint for what should + be entered. TODO: Don't draw the label if the cell isn't an + input/output cell, or whatever. + + * src/register/gnome/gnucash-style.c + (gnucash_sheet_style_compile): Initialize the labels[][] array in + the style. + +1999-08-17 Heath Martin + + * src/register/gnome/gnucash-cursor.c (configure_bounds): Do this + the right way. + + * src/register/gnome/gnucash-item-edit.c (item_edit_draw): Added + the clip rectangle, and made it so the text scrolls to keep the + cursor in view. + + * src/register/gnome/gnucash-grid.c (gnucash_grid_draw): Added the + clip rectangle. + +1999-08-14 Heath Martin + + * src/register/gnome/gnucash-cursor.c + (gnucash_cursor_get_pixel_coords): Take into account the alignment + offsets. + + * src/register/gnome/gnucash-item-edit.c + (item_edit_get_pixel_coords): Take into account the alignment + offsets. + + * src/register/gnome/gnucash-grid.c + (gnucash_grid_find_block_origin_by_pixel): Fixed an off-by-one + mistake. + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_make_cell_visible): New function + (gnucash_sheet_cursor_move): Make the cell visible. + (gnucash_sheet_set_top_row): Added support for aligning on the top + or bottom row. + (gnucash_sheet_update_adjustments): Fixed this so the sheet can't + scroll past the bottom row. + +1999-08-12 Heath Martin + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_activate_cursor_cell): With Alexandru's patches, we + seem to need to grab the focus. We'll probably need this + elsewhere. + +1999-08-11 Heath Martin + + * src/register/gnome/gnucash-sheet.c (gnucash_button_press_event): + New function. + + * src/register/gnome/gnucash-grid.c + (gnucash_grid_find_block_origin_by_pixel): Add a return value. + (gnucash_grid_find_cell_origin_by_pixel): New function. + +1999-08-10 Heath Martin + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_key_press_event): Added up/down movement. + (gnucash_sheet_style_set_dimensions): Change the way we set + dimensions. + (gnucash_sheet_size_allocate): Recompute style dimensions. + + * src/register/gnome/gnucash-item-edit.c + (item_edit_get_pixel_coords): We need to compute pixels relative + to the canvas origin, not the window origin. Duh. (Note that + canvas sends coordinates to a draw function relative to the canvas + origin.) + + * src/register/gnome/gnucash-cursor.c + (gnucash_cursor_get_pixel_coords): Ditto. + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_deactivate_cursor_cell): New function. + (gnucash_sheet_activate_cursor_cell): + (gnucash_sheet_cursor_move): + (gnucash_sheet_start_editing_at_cursor): + (gnucash_sheet_stop_editing): + +1999-08-09 Heath Martin + + * src/register/gnome/combocell-gnome.c: New file, everything is a + no-op for now, we just need the stubs. + + * src/register/gnome/gnucash-item-edit.[ch]: New file. Implements + an entry for the sheet. + +1999-08-08 Heath Martin + + * src/register/gnome/gnucash-cursor.[ch] : New file. Implements a + block/cell cursor. + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_style_get_cell_pixel_rel_coords): New function. + +1999-08-06 Heath Martin + + * src/register/gnome/gnucash-grid.c (gnucash_grid_draw): Updated + to reflect the changes below. + (gnucash_grid_find_block_origin_by_pixel): Bug fix (off by one). + + * src/register/gnome/gnucash-sheet.c (gnucash_sheet_table_load): + Another overhaul of the structure. We need to keep a local copy + of the table, to keep what we need to redraw from changing + underneath us. So we needed some memory management and some minor + API adjustments. + (gnucash_sheet_resize): + (gnucash_sheet_resize_row): + (gnucash_sheet_destroy_row): + (gnucash_sheet_block_new_row): + (gnucash_sheet_block_set_from_table): + (gnucash_sheet_block_set_entries): + (gnucash_sheet_block_clear_entries): + (gnucash_sheet_insert_block_row): + (gnucash_sheet_get_block): + +1999-08-04 Heath Martin + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_compute_visible_range): New + + * src/register/gnome/gnucash-grid.c (gnucash_grid_draw): Basic + support for drawing the text of a cell. + (gnucash_grid_draw): Added support for justifications. + +1999-08-03 Heath Martin + + * src/register/gnome/gnucash-sheet.c (gnucash_register_new): Make + register into an independent widget. + + * src/register/gnome/gnucash-sheet.c, gnucash-grid.c: Overhaul of + the structure. We don't need to keep a list of blocks, or a hash + list of styles. All the info we need is in the Table, and there + are only five styles associated to a given table (from + splitreg.h). Many functions touched to reflect the change. Also, + keep track of the SplitRegister associated to this table. + +1999-08-02 Heath Martin + + * src/register/gnome/gnucash-color.c (gnucash_color_init): move + the color allocation and hash table routines from table-gnome.c to + here. + + * src/register/gnome/gnucash-sheet.c (gnucash_sheet_block_append): + fixed a silly bug. + (gnucash_sheet_block_style_new): Fix up the key. + (gnucash_register_new): This is complex widget that already has + the vertical/horizontal scrollbars attached. + + * src/register/gnome/gnucash-grid.c (gnucash_grid_find_block): Be + sure we get a valid sheet_block. Our register window may be + larger than the number of blocks. + + * src/register/gnome/gnucash-sheet.c (gnucash_sheet_load): Initial + load of the table into the register. + + * src/register/gnome/gnucash-color.[c,h]: steal these files from gnumeric + +1999-08-01 Heath Martin + + * src/register/gnome/gnucash-sheet.c + (gnucash_sheet_style_recompile): make height, width a member of + the style, so we don't have to change every block when the style + gets recompiled (on sheet allocation changes for example). + + * src/register/gnome/gnucash-grid.c (gnucash_grid_get_type): First + hack at getting a grid going. + (gnucash_grid_class_init): Ditto + (gnucash_grid_set_arg): + (gnucash_grid_init): + (gnucash_grid_draw): + (gnucash_grid_find_block): + (gnucash_grid_update): + (gnucash_grid_unrealize): + (gnucash_grid_realize): + + * src/register/gnome/gnucash-sheet.c (gnucash_sheet_new): New + (gnucash_sheet_get_type): New + (gnucash_sheet_class_init): New + (gnucash_sheet_create): New + (gnucash_sheet_realize): New + (gnucash_sheet_block_new): New + (gnucash_sheet_block_destroy): New + (gnucash_sheet_vadjustment_value_changed): New + (gnucash_sheet_set_top_block): New + (gnucash_sheet_block_get_pixels): New + (gnucash_sheet_get_block): New + (gnucash_sheet_style_init): etc, per block style handling diff --git a/Docs/En/tidy-up b/Docs/En/tidy-up new file mode 100755 index 0000000000..4831a5ee55 --- /dev/null +++ b/Docs/En/tidy-up @@ -0,0 +1,7 @@ +#!/bin/ksh +# $ID$ +# If you have Dave Raggett's "tidy" utility, this will tidy up +# the HTML files here. +for i in *.html ; do + tidy -m -i $i +donediff -u 'pristine/gnucash/Docs/En/xacc-about.html' 'working/gnucash/Docs/En/xacc-about.html' diff --git a/Docs/En/xacc-about.html b/Docs/En/xacc-about.html index 228aa40d64..a1bdcdfac1 100644 --- a/Docs/En/xacc-about.html +++ b/Docs/En/xacc-about.html @@ -1,286 +1,472 @@ - + + + About GnuCash - +

About GnuCash

-
- - GnuCash is a program to keep track of your finances. Its - features include: - + GnuCash is a program to keep track of your finances. Its + features include: +
    -
  • Multiple accounts, which can be open at - the same time. Create one xacc account - for each of your bank accounts. -
  • Each account keeps a running balance and - a reconciled balance, so you can keep track - of the checks that have cleared your account. -
  • A simple interface. If you can use the - register in the back of your checkbook, - you can use xacc. -
  • Automatic account reconciling. At the end - of the month, open up the reconcile window, - enter your bank statement's ending balance, - and check off the transactions that appear - in the bank statement. This makes it easy - to track down any discrepancies. -
  • QuickFill... if you begin typing - in the description field, and the text matches a - previous transaction, hitting TAB will copy - that previous transaction. Handy if you have - similar transactions on a regular basis. -
  • Stock/Mutual Fund Portfolios. Track stocks - individually (one per account) or in portfolio - of accounts (a group of accounts that can be - displayed together). -
  • Support for multiple currencies and currency - trading accounts. (partial, still broken). - Bank accounts may be established in different - currencies, and trades with indicated prices - may be made, much as stocks would be traded. -
  • Quicken File Import. - Import Quicken Version 3.0 QIF files. -
  • Reports. Display - or output as HTML Balance or Profit&Loss reports. +
  • Multiple accounts, which can be open at the same + time. Create an xacc account for each of your bank + accounts.
  • + +
  • Each account maintains both a running balance and a + reconciled balance, so you can keep track of the checks that + have cleared your account.
  • + +
  • + A simple interface. + +

    If you can use the register in the back of your + checkbook, you can use xacc.

    +
  • + +
  • + Automated Tools for + Reconciling Accounts. + +

    At the end of the month, open up the reconcile + window, enter the ending balance from your bank statement, + and check off the transactions that appear in the bank + statement. This agrees what you have recorded in GnuCash + with what your bank has reported, and makes it easier to + track down any discrepancies.

    +
  • + +
  • + QuickFill. + +

    If you begin typing in the description field, and the + text matches a previous transaction, hitting TAB + will copy in that previous transaction. This is a handy + time saver if you regularly create similar + transactions.

    +
  • + +
  • + Stock/Mutual Fund Portfolios. + +

    Track stocks individually (one per account) or in + portfolio of accounts (a group of accounts that can be + displayed together).

    + +

    There are tools to + automatically collect stock quotes.

    +
  • + +
  • + Support for multiple + currencies and currency trading accounts. + (partial, still broken). + +

    Bank accounts may be established in different + currencies, and trades at varying exchange rates may be + made, in much the same way stocks trade at varying + prices.

    +
  • + +
  • + Quicken File + Import. + +

    Imports Quicken-style QIF files.

    +
  • + +
  • Reports. Display + or output as HTML Balance or Profit&Loss reports.

Advanced Features

- GnuCash offers some features not usually found - in simpler accounting programs. + +

GnuCash offers some features not found in simpler + accounting programs.

+
    -
  • Sub-accounts: A master account can have a hierarchy - of detail accounts underneath it. This allows similar - account types (e.g. Cash, Bank, Stock) to be grouped - into one master account (e.g. Assets). -
  • Double Entry: - Every transaction can appear in two - accounts; one account is debited and the other is - credited with exactly the same amount. With - double-entry, a transaction edited in one window - will be automatically updated in all other windows - showing that transaction, and in both of the - accounts. -
  • Income/Expense Account Types - (Categories). When used properly - with the double-entry feature, these can be used - to create both Balance Sheet and Profits & Losses - reports. For example, savings account interest, - stock dividends, or paychecks can be marked as - both a deposit in a bank account, and as income in - an Income account type, using the double-entry - (transfer) feature. Similarly, credit card charges - can be noted in the credit card account, as well - as in a corresponding expense account. -
  • General Ledger: Multiple accounts can be displayed - in one register window at the same time. This can - ease the trouble of tracking down typing/entry errors. - It also provides a convenient way of viewing a - portfolio of many stocks, by showing all transactions - in that portfolio. +
  • + Account Hierarchy + +

    A master account can have a hierarchy of more detailed + accounts arranged underneath it. This allows related + account types (e.g. - Cash, Bank, Stock) to be + grouped under one master account ( e.g. - + Assets).

    +
  • + +
  • + Double Entry + +

    Every transaction involves two accounts, and each + transaction is required to balance. This provides + assurance that the overall set of books will add up + correctly, and prevents out-of-balance errors + altogether.

    +
  • + +
  • + Income/Expense Account + Types + +

    Intuit's Quicken + product has what they call "categories" that are used to + track incomes and expenses. These may be used to create + Profits & Losses reports.

    +
  • + +
  • + General Ledger + +

    Multiple accounts may be displayed in one register + window at the same time. This can make it easier to track + down data errors. It also provides a convenient way of + viewing a portfolio of many stocks, by showing all + transactions in that portfolio.

    +
  • + +
  • Handling of multiple + currencies

Version

- This version is gnucash-1.3.x, a somewhat unstable, buggy version. - The latest stable release is 1.2.x, you should be using that. -

- Versions 1.0.x and 1.2.x are considered to be stable revisions with all - currently known bugs fixed. Versions 1.3.x are considered to be - development versions, which may have many bugs. The next stable - series will be 1.4.x -

+

The versioning scheme for X-Accountant parallels that of + the Linux kernel, where "even" sub-versions indicate versions + that are intended to be stable, only seeing maintenance to fix + bugs, and "odd" sub-versions indicate an "experimental" stream + that seeks to add enhancement.

+ +

The present "experimental" stream is gnucash-1.3.x, which + is somewhat unstable.

+ +

The latest stable release is 1.2.x; if you don't intend to + do development work, you should be using either this version, + or an older 1.0.x version. These versions are fairly stable, + with all currently known bugs fixed.

+ +

Once the 1.3.x series stabilizes, the next stable series + will be 1.4.x, and experimentation will likely continue on + 1.5.x.

Lead Developers

+
-
Robin Clark <rclark@hmc.edu> -
wrote the original X-Accountant in Motif - as a school project, taking it to version 0.9 by October 1997. +
Robin Clark <rclark@hmc.edu>
-
Linas Vepstas <linas@linas.org> -
liked what he saw: the GUI was slick, - the code was documented and well structured, and it was all GPL'ed. - And so he re-wrote it: adding cell-widgets to XbaeMatrix, so that - the combobox and arrows would make an even slicker GUI, rewrote the - X-Accountant internals to add double-entry, an account heirarchy, - split out a transaction mini-engine, add support for stocks, and spiff - up the help menus. This was version 1.0 as of January 1998. Since - then, for version 1.1, the engine was expanded & refined, and the - register window code completely redesigned and made mostly - Motif-(and GUI-)independent. Did some prototype OFX work. +
wrote the original X-Accountant in Motif as a school + project, taking it to version 0.9 by October 1997.
-
Jeremy Collins <jcollins@gnucash.org> -
publicized the GnoMoney project - widely and broadly, and then changed its name to GnuCash. Jeremy - created the gnucash.org web site, registered the domain, got the - initial GTK/gnome code working. +
Linas Vepstas <linas@linas.org>
-
Rob Browning <rlb@cs.utexas.edu> -
abused everyone for not using perl, - and then added guile/scheme support. Rob maintains the build - infrastructure, is handling the whole guile/perl extension language - thing, and is dealing with configuration & configurability. +
liked what he saw: the GUI was slick, the code was + documented and well structured, and it was all GPL'ed. And so + he re-wrote it: adding cell-widgets to XbaeMatrix, so that + the combobox and arrows would make an even slicker GUI, + rewrote the X-Accountant internals to add double-entry, an + account heirarchy, split out a transaction mini-engine, add + support for stocks, and spiff up the help menus. This was + version 1.0 as of January 1998. Since then, for version 1.1, + the engine was expanded & refined, and the register + window code completely redesigned and made mostly Motif-(and + GUI-)independent. Did some prototype OFX work.
+ +
Jeremy Collins <jcollins@gnucash.org>
+ +
publicized the GnoMoney project widely and broadly, and + then changed its name to GnuCash. Jeremy created the + gnucash.org web site, registered the domain, got the initial + GTK/gnome code working.
+ +
Rob Browning <rlb@cs.utexas.edu>
+ +
abused everyone for not using perl, and then added + guile/scheme support. Rob maintains the build infrastructure, + is handling the whole guile/perl extension language thing, + and is dealing with configuration & configurability.
+ +
Dirk Schoenberger <schoenberger@signsoft.com>
+ +
is working on the Qt/KDE port
+ +
Dave Peticolas <peticola@cs.ucdavis.edu>
+ +
hacks obsessively on GnuCash. But he can stop anytime + he wants to. Really.
-
Dave Peticolas -
did a whole lot and should write a bio here.
-

-

Fixes & Patches

-
Andrew Arensburger <arensb@cfar.umd.edu> for FreeBSD & other patches -
Matt Armstrong <matt_armstrong@bigfoot.com> for misc fixes -
Fred Baube <fred@moremagic.com> for attempted Java port/MoneyDance -
Christopher B. Browne <cbbrowne@hex.net> for perl stock scripts -
Graham Chapman <grahamc@zeta.org.au> for the xacc-rpts addon package -
George Chen <georgec@sco.com> for MS-Money QIF's & fixes -
Jeremey Collins <jcollins@gnucash.org> for GnoMoney & GTK port -
Patrick Condron <pcondon@rackspace.com> for webserver and T1 connection. -
Ciaran Deignan <Ciaran.Deignan@bull.net> for AIX binary version -
Tyson Dowd <tyson@tyse.net> for config/make patches & debian maint. -
Koen D'Hondt <ripley@xs4all.nl> for Solaris patches to XmHTML -
Bob Drzyzgula <bob@mostly.com> for budgeting design notes -
Jan-Uwe Finck <ju_finck@mail.netwave.de> for German message translation -
Ron Forrester <rjf@aracnet.com> for gnome patches -
Dave Freese <DFreese@osc.uscg.mil> for leap-year fix -
Otto Hammersmith <otto@bug.redhat.com> for RedHat RPM version -
Alexandru Harsanyi <haral@codec.ro> for misc core dumps & lockups. -
Jon K}re Hellan <jk@isdn-a33.itea.ntnu.no> misc core dump fixes -
Prakash Kailasa <PrakashK@bigfoot.com> for gnome build fixes -
Tom Kludy <tkludy@csd.sgi.com> for SGI Irix port -
Sven Kuenzler <sk@xgm.de> for SuSE README file -
Ted Lemon <mellon@andare.fugue.com> for NetBSD port -
Yannick Le Ny <y-le-ny@ifrance.com> pour la traduction en francais -
G. Allen Morris III <gam3@ann.softgams.com> for QIF core dump -
Peter Norton <spacey@inch.com> for a valiant attempt at a GTK port -
OmNiBuS <webmaster@obsidian.uia.net> web site graphics & content -
Myroslav Opyr <mopyr@IPM.Lviv.UA> for misc patches -
Alain Peyrat <Alain.Peyrat@nmu.alcatel.fr> for configure.in patches -
Gavin Porter <maufk@csv.warwick.ac.uk> for euro style dates -
Ron Record <rr@sco.com> for SCO Unixware & OpenServer binaries -
Christopher Seawood <cls@seawood.org> for XbaeMatrix core dump -
Mike Simons <msimons@fsimons01.erols.com> misc configure.in patches -
Richard Skelton <rich@brake.demon.co.uk> for Solaris cleanup -
Henning Spruth <spruth@bigfoot.com> for German text & euro date rework -
Ken Yamaguchi <gooch@ic.EECS.Berkeley.EDU> QIF import fixes; MYM import
- - - + Andrew Arensburger <arensb@cfar.umd.edu> for FreeBSD + & other patches
+ Matt Armstrong <matt_armstrong@bigfoot.com> for misc + fixes
+ Fred Baube <fred@moremagic.com> for attempted Java + port/MoneyDance
+ Christopher B. + Browne <cbbrowne@hex.net> for perl stock scripts, + Guile-based QIF import code
+ Graham Chapman <grahamc@zeta.org.au> for the xacc-rpts + addon package
+ George Chen <georgec@sco.com> for MS-Money QIF's & + fixes
+ Jeremey Collins <jcollins@gnucash.org> for GnoMoney + & GTK port
+ Patrick Condron <pcondon@rackspace.com> for webserver + and T1 connection.
+ Ciaran Deignan <Ciaran.Deignan@bull.net> for AIX binary + version
+ Tyson Dowd <tyson@tyse.net> for config/make patches + & debian maint.
+ Koen D'Hondt <ripley@xs4all.nl> for Solaris patches to + XmHTML
+ Bob Drzyzgula <bob@mostly.com> for budgeting design + notes
+ Jan-Uwe Finck <ju_finck@mail.netwave.de> for German + message translation
+ Ron Forrester <rjf@aracnet.com> for gnome patches
+ Dave Freese <DFreese@osc.uscg.mil> for leap-year fix +
+ Otto Hammersmith <otto@bug.redhat.com> for RedHat RPM + version
+ Alexandru Harsanyi <haral@codec.ro> for misc core dumps + & lockups.
+ Jon K}re Hellan <jk@isdn-a33.itea.ntnu.no> misc core + dump fixes
+ Prakash Kailasa <PrakashK@bigfoot.com> for gnome build + fixes
+ Tom Kludy <tkludy@csd.sgi.com> for SGI Irix port
+ Sven Kuenzler <sk@xgm.de> for SuSE README file
+ Ted Lemon <mellon@andare.fugue.com> for NetBSD port
+ Yannick Le Ny <y-le-ny@ifrance.com> pour la traduction + en francais
+ G. Allen Morris III <gam3@ann.softgams.com> for QIF core + dump
+ Peter Norton <spacey@inch.com> for a valiant attempt at + a GTK port
+ OmNiBuS <webmaster@obsidian.uia.net> web site graphics + & content
+ Myroslav Opyr <mopyr@IPM.Lviv.UA> for misc patches
+ Alain Peyrat <Alain.Peyrat@nmu.alcatel.fr> for + configure.in patches
+ Gavin Porter <maufk@csv.warwick.ac.uk> for euro style + dates
+ Ron Record <rr@sco.com> for SCO Unixware & + OpenServer binaries
+ Christopher Seawood <cls@seawood.org> for XbaeMatrix + core dump
+ Mike Simons <msimons@fsimons01.erols.com> misc + configure.in patches
+ Richard Skelton <rich@brake.demon.co.uk> for Solaris + cleanup
+ Henning Spruth <spruth@bigfoot.com> for German text + & euro date rework
+ Ken Yamaguchi <gooch@ic.EECS.Berkeley.EDU> QIF import + fixes; MYM import
+

Supported Operating Systems

- gnucash-1.0.18 (xacc-1.0.18) is known to work in the following configs: -
Linux 2.0.x -- Intel w/ RedHat Motif -
Linux 2.0.x -- Intel w/ Lesstif v0.81 -
Linux Debian -- Intel w/ Lesstif v0.81 -
SGI IRIX -- MIPS -
IBM AIX 4.1.5 -- RS/6000 -
SCO Unixware 7 -- Intel -
SCO OpenServer 5.0.4 -- Intel -
NetBSD -- Intel -

+ gnucash-1.0.18 (xacc-1.0.18) is known to work on the following + systems: - - +

    +
  • Linux 2.0.x -- Intel w/ RedHat Motif
  • - - +
  • Linux 2.0.x -- Intel w/ Lesstif v0.81
  • - - - - - - - +
  • Linux Debian -- Intel w/ Lesstif v0.81
  • - - - +
  • SGI IRIX -- MIPS
  • - +
  • IBM AIX 4.1.5 -- RS/6000
  • + +
  • SCO Unixware 7 -- Intel
  • + +
  • SCO OpenServer 5.0.4 -- Intel
  • + +
  • NetBSD -- Intel
  • +
+ +

+ + + +

History

- The table below shows some historical lines-of-code and number-of-files - counts for the X-Accountant/GnuCash development project -
- - - - -
Historical Development Stats
Version - engine - register - ledger - motif - gnome - qt - prefs (scm) - docs (html) - misc - Total + The table below shows some historical lines-of-code and + number-of-files counts for the X-Accountant/GnuCash development + project
+ -
xacc-0.9
Sept 97 -
- - - - - - 34 files
(7.5+0.9) -
- - - - - - 5 files
(0.4) -
- - 39 files
(8.8) + + - - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Historical Development Stats +
xacc-0.9w
Dec 97 -
- - - - - - 51 files
(13.8+1.5) -
- - - - - - 9 files
(0.8) -
- - 60 files
(16.1) +
Version
xacc-1.0.17
Feb 98 -
- - - - - - 52 files
(14.8+1.8) -
- - - - - - 12 files
(1.4) -
- - 64 files
(18.0) +
engine
gnucash-1.1.15
Aug 98 -
24 files
(6.2+1.5) -
31 files
(6.1+1.7) -
5 files
(1.4+0.4) -
30 files
(7.4+0.7) -
17 files
(3.4+0.5) -
16 files
(1.2+0.2) -
3 files
(0.3) -
16 files
(1.9) -
not counted
(>1.0) -
142 files
(32.9) +
registerledgermotifgnomeqtprefs (scm)docs (html)miscTotal
xacc-0.9
+ Sept 97
---34 files
+ (7.5+0.9)
---5 files
+ (0.4)
-39 files
+ (8.8)
xacc-0.9w
+ Dec 97
---51 files
+ (13.8+1.5)
---9 files
+ (0.8)
-60 files
+ (16.1)
xacc-1.0.17
+ Feb 98
---52 files
+ (14.8+1.8)
---12 files
+ (1.4)
-64 files
+ (18.0)
gnucash-1.1.15
+ Aug 98
24 files
+ (6.2+1.5)
31 files
+ (6.1+1.7)
5 files
+ (1.4+0.4)
30 files
+ (7.4+0.7)
17 files
+ (3.4+0.5)
16 files
+ (1.2+0.2)
3 files
+ (0.3)
16 files
+ (1.9)
not counted
+ (>1.0)
142 files
+ (32.9)
-

- Each cell contains: +

Each cell contains:

-
number of *c and *.h files -
(KLOCS in *.c + KLOCS in *.h), -

- where KLOC == kilo-lines-of-code, as reported by wc. + number of *c and *.h files
+ (KLOCS in *.c + KLOCS in *.h),

+ +

where KLOC == kilo-lines-of-code, as reported by + wc.

+ +

Return to Main Documentation + Page.

+ diff --git a/Docs/En/xacc-acctypes.html b/Docs/En/xacc-acctypes.html index 311931d1da..ae54973af3 100644 --- a/Docs/En/xacc-acctypes.html +++ b/Docs/En/xacc-acctypes.html @@ -1,41 +1,99 @@ - + + + Account Types - - + +

Account Types

+
-
Bank -
The bank account type denotes a savings or checking account - held at a bank. Often interest bearing. -
Cash -
The cash account type is used to denote a shoe-box or pillowcase - stuffed with cash. -
Credit -
The Credit card account is used to denote credit (e.g. amex) and - debit (e.g. visa, mastercard) card accounts. -
Asset, Liability -
Asset and liability accounts indicate generic, generalized accounts - that are none of the above. -
Stock, Mutual Fund -
Stock and Mutual Fund accounts will typically be shown in registers - which show three columns: price, number of shares, and value. -
Income, Expense -
Income and expense accounts are used to denote income and expenses. - Thus, when data in these accounts are displayed, the sign of the - entries is reversed from its usual meaning. -
Equity -
Equity account is used to balance the balance sheet. - -
Currency -
Currency Accounts are used for trading currencies. - In many ways, they behave like stocks, except that the computation - of the value is different. Note that transfers cannot be made - directly between two accounts denominated in different currencies. - Such transfers may only be made into currency trading accounts. - (Safety checks may be a bit broken still ... ) +
Cash
+ +
The cash account type is used to denote the cash + that you store in your wallet, shoebox, piggybank, or + mattress.
+ +
Bank
+ +
The Bank account type denotes a savings or + checking account held at a bank or other financial + institution. Such accounts often bear interest.
+ +
Credit
+ +
The Credit card account is used to denote credit + card accounts, whether involving floating lines of credit as + with VISA, MasterCard, or Discover, or others like American + Express, that do not permit you to maintain continuing + balances.
+ +
The introduction of Check + Cards where payments are withdrawn directly from a + checking account makes the selection less clear; it is + probably more appropriate to treat a "Check Card" as a + Bank account, as it does withdraw amounts directly from + such an account, not really involving any granting of + credit.
+ +
Asset, Liability
+ +
Asset and Liability accounts are used for + tracking things that are of value, but that are not so + directly like cash.
+ +
For instance, you might collect the costs of purchasing a + house into an asset account entitled My House, or the + cost of a car into My Car, or collect together the + value of your Computer Equipment.
+ +
And the home mortgage or car loan would be represented by + liability accounts, Home Mortgage and Car + Loan, to be drawn down as payments are made on these + loans.
+ +
Stock, Mutual Fund
+ +
+ Stock and Mutual Fund accounts will typically be shown in + registers which show three columns: + +
    +
  • Price
  • + +
  • Number of shares
  • + +
  • Value
  • +
+
+ +
Income, Expense
+ +
Income and Expense accounts are used to + collect incomes and expenses.
+ +
Equity
+ +
Equity accounts are used to balance the balance + sheet.
+ +
Currency
+ +
Currency Accounts are used for trading + currencies. In many ways, they behave like stocks, except + that the computation of the value is different. Note that + transfers cannot be made directly between two accounts + denominated in different currencies. Such transfers may only + be made into currency trading accounts. (Safety checks may be + a bit broken still ... )
+ +

Return to Main Documentation + Page.

+ diff --git a/Docs/En/xacc-accwin.html b/Docs/En/xacc-accwin.html index cca469ad63..005693bca2 100644 --- a/Docs/En/xacc-accwin.html +++ b/Docs/En/xacc-accwin.html @@ -1,36 +1,42 @@ - + + + New Account Window - - + +

New Account Window

- This is what a new account window looks like: -

-
- -
-

- Pick an sort order - of the account when it appears in a report or in the - Chart of Accounts. -

- The picure below shows an example for a stock or currency trading account. - Note that the Security field is not greyed out, and that you can - enter a value. That value is typically a stock-ticker symbol, - or a three-letter ISO currency code. -

-
- -
+

This is what a new account window looks like:

+

+

Pick an Account + Type.

+ +

The Currency field should typically be a three-letter ISO curency code + (e.g. - USD for U.S. Dollars). The + Account Code is a number that determines the sort order of the account + when it appears in a report or in the Chart of Accounts.

+ +

The picure below shows an example for a stock or currency + trading account. Note that the Security field is not greyed + out, and that you can enter a value. That value is typically a + stock-ticker symbol, or a + three-letter ISO currency code.

+ +


+
+

+ +

Return to Main Documentation + Page.

+ diff --git a/Docs/En/xacc-adjbwin.html b/Docs/En/xacc-adjbwin.html index 3235eb8dfc..4654cd015b 100644 --- a/Docs/En/xacc-adjbwin.html +++ b/Docs/En/xacc-adjbwin.html @@ -1,17 +1,21 @@ - + + + Adjust Balance Window - - -

THIS FILE IS EMPTY

- Adjust Balance window. Use this to adjust the balance. - Enter a dollar amount, and a register entry will be created - that sets the balance to the new balance. - Add more documentation here. + +

THIS FILE IS EMPTY

+ Adjust Balance window. Use this to adjust the balance. Enter a + dollar amount, and a register entry will be created that sets + the balance to the new balance. Add more documentation here.
- + +

Return to Main Documentation + Page.

+ diff --git a/Docs/En/xacc-apar.html b/Docs/En/xacc-apar.html index ba228a4cbc..bdaa24ad5a 100644 --- a/Docs/En/xacc-apar.html +++ b/Docs/En/xacc-apar.html @@ -1,54 +1,103 @@ - - + - + + + Accounts Payable/Accounts Receivable

Accounts Payable & Accounts Receivable

- A/R and A/P are kind of deep, -

Anyways, let's consider A/R. We can't really relate to A/P - because we always pay our bills on time, don't we ? - :-)

+

A/R (Accounts Receivable) and A/P (Accounts Payable) are + somewhat deep to understand.

-

So anyways, let's say we give our customers 30 days to - pay.

+

Let us first examine A/R. After all, we really shouldn't + really need to relate to A/P because we always pay + our bills on time, don't we ? :-)

-

When we make a sale, the two accounts affected are Sales - (an income account) and A/R. A/R is an asset, but it's not - liquid, and it's not quite cash.

+

So, as a first cut, let us assume we don't require + customers to pay instantly, in cash, but rather issue + them an invoice, and give them 30 days to pay the bills. (Then + we can start charging interest and sending out harassing + letters :-)).

+ +

When we make a sale, the two accounts affected are + Sales (an income account) and Accounts Receivable. + Accounts Receivable is an asset, but it's not liquid, and it's + not quite cash.

Then when they come by to pay their bill, dropping off a - big bag of twenty-dollar bills, we transfer the amount from A/R - to Cash.

+ large sack of twenty-dollar bills (or, more likely, a + check/cheque), we transfer the amount from A/R to Cash.

-

The reason we do it in two steps is that we've decided to +

The reason we do this in two steps is that we've decided to do our accounting on an accrual basis and not on a cash basis, - bcos, well, most of our transactions are not cash, they're - obligations.

+ because most of our transactions are not solely based on cash + changing hands, but rather based on establishing + obligations.

+ +

In more sophisticated systems, there may be a whole + sequence of documents generated and tracked:

+ +
    +
  • A customer sends in a Purchase Order, thus + authorizing a purchase.
  • + +
  • We set up a Work Order to schedule production of + whatever the customer is buying
  • + +
  • We issue a Shipping Notice, to ship to goods to + the customer
  • + +
  • Once shipped, we issue an Invoice, representing + the request to pay
  • +

We report sales in our sales figures as soon as we make - them, but if the auditor wants to know about whether we're - gonna get stuck with bad debts, we break down those A/R's by - how old they are: 0-30 says, 31-60 says, etc. At some point - when a particular debt is "written off", like when the cheesing - bastards go bankrupt, we dock both A/R and Sales, so we're - going back and patching up (or rather, "patching down") the - Sales account to show that the Sale was never made good.

+ them, but if the auditor wants to know about whether we'll get + stuck with bad debts, we break down those A/R's based on the + "ages" of the debts, commonly segmented into three or four + periods, of 0-30 days, 31-60 days, etc.

-

We can use the same technique for things that we prepay. If - we have to plunk down six months' rent in advance, that is an - "accrued asset", and while it put a healthy dent in the Cash - account, it does show on the books as an asset. And if we've - been collecting payroll taxes from our employees and keeping - them in a special bank account, the money's not really ours, so - we have a growth in the Cash account on one side, and a growth - in an Accrued Liability, namely, Payroll Taxes Payable, on the - toher side. When we send the quarterly check to the Feds so - that they can make payroll too, our liability drops and so does - our Cash account.

+

At some point when a particular debt is "written off," + perhaps when the cheesing bastards go bankrupt, we dock both + A/R and Sales, so we're going back and patching up (or rather, + less happily, "patching down") the Sales account to show that + the Sale was never made good. It is common to set up a Bad + Debt Expense account so that such incidences are recorded + in a way that makes it clearer that sales went bad + .

+ +

The above things, reversed, reflect how Accounts Payables + work; just switch customer with supplier, and see how the roles + reverse. If we buy materials "on account," accrual accounting + requires that we record that we incur the expense immediately, + and rather than reducing cash, we put the "credit" into the + Accounts Payable account. Three weeks later, the invoice + comes in, and we issue a payment, and so Debit AP, Credit + Cash.

+ +

We can also use analagous techniques for expenses that we + prepay. If we have to pay out down six months of rent in + advance, that is treated as an "accrued asset," Prepaid + Rent, and while it puts an unfortunate dent in the Cash + account, it does show on the books as an asset that + will decline over time to zero. Each month, we draw the account + down via Debit Rent Expense, Credit Prepaid + Rent. Similarly, we've been collecting payroll taxes + from our employees and keeping them in a special bank account, + that money is not really ours, so we have a growth in + the Cash account on one side, and a growth in an Accrued + Liability, namely, Payroll Taxes Payable, on the other + side. When we send the quarterly check to the Government so + that they can make their payroll, our Payroll + Taxes Payable drops as does the balance in the Checking + Account.

+ +

Return to Main Documentation + Page.

diff --git a/Docs/En/xacc-currency.html b/Docs/En/xacc-currency.html index 76a5bc7493..a5382f7260 100644 --- a/Docs/En/xacc-currency.html +++ b/Docs/En/xacc-currency.html @@ -1,28 +1,34 @@ - - + - + + + Currency Handling and Double Entry Bookkeeping -

Double Entry Bookkeeping

+

Currency Handling and Double Entry Bookkeeping

-

A Double Entry bookkeeping system stores both values, and - requires that all transactions balance:

+

A Double Entry bookkeeping + system stores both values, and requires that all + transactions balance, as described in the Double Entry Identity.

-

Double entry states that value1+value2+value3+ ... = - 0 where each value is recorded in a different account.

- -

A value is

+

When we introduce the notion of having multiple currencies, + or stocks that may vary in price, the identities get a little + more complicated, as we add in the following formulae:

    -
  • value=exch-rate*amount (for currency accounts) - value=price*num-shares (for stocks/mutual-funds)
  • +
  • value=exch-rate*amount (for currency + accounts)
  • + +
  • value=price*num-shares (for + stocks/mutual-funds)
-

Thus, buying a widget in Japan and using US Dollars to - pay for it would appear thus:

+

Thus, buying a widget in Japan and using US Dollars to pay + for it would appear thus:

  • @@ -46,10 +52,1153 @@
-

The engine links together all three of these values (1.0, - 150, 0.00667) permanently and makes it imposssible to change - one without changing another, so that the grand total is always - zero.

+

The engine links together all three of these values + (1.0, 150, 0.00667) permanently and makes it imposssible + to change one without changing another, so that the grand total + is always zero.

+ +

ISO Currency Codes

+ + +

More currencies than you thought possible...

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ISO CodeFull Description
+
ADPAndoran peseta
AEDUnited Arab Emirates Dirham
AFAAfghani
ALLAlbanian Lek
AMDArmenian Dram
ANGWest Indian Guilder
AOKAngolan Kwanza
ARAArgentinian Austral
ARSArgentina Peso
ATSAustrian Schilling
AUDAustralian Dollar
AWGAruban Guilder
AZMAzerbaijan Manat
BADBosnia-Herzogovinian Dinar
BBDBarbados Dollar
BDTBangladesh Taka
BEFBelgian Franc
BGLBulgarian Lev
BHDBahrain Dinar
BIFBurundi Franc
BMDBermudan Dollar
BNDBrunei Dollar
BOBBolivian Boliviano
BRLBrazilian Real
BRRBrazil
BSDBahaman Dollar
BWPBotswana Pula
BYRBelorussian Ruble
BZDBelize Dollar
CADCanadian Dollar
CDPSanto Domiongo
CHFSwiss Franc
CLPCHILEAN PESO
CNYChina
COPColombian Peso
CRCCosta Rica Colon
CUPCuban Peso
CVECape Verde Escudo
CYPCyprus Pound
CZKCzech Krona
DEMGerman Mark
DJFDjibouti Franc
DKKDanish Krone
DOPDominican Peso
DRPDominican Republic Peso
DZDAlgerian Dinar
ECSECUADORIAN SUCRE
ECSEcuador Sucre
ECUEuropean Currency Unit
EEKEstonian Krone
EGPEgyptian Pound
ESPSpanish Peseta
ETBEthiopian Birr
EURCurrency of EMU member states
FIMFinnish Mark
FJDFiji Dollar
FKPFalkland Pound
FRFFrench Franc
GBPBritish Pound
GEKGeorgian Kupon
GHCGhanian Cedi
GIPGibraltar Pound
GMDGambian Dalasi
GNFGuinea Franc
GRDGreek Drachma
GTQGuatemalan Quedzal
GWPGuinea Peso
GYDGuyanese Dollar
HKDHong Kong Dollar
HNLHonduran Lempira
HRDCroatian Dinar
HTGHaitian Gourde
HUFHungarian forint
IDRIndeonesian Rupiah
IEPIrish Pound
ILSIsraeli Scheckel
INRIndian Rupee
IQDIraqui Dinar
IRRIranian Rial
ISKIceland Krona
ITLItalian Lira
JMDJAMAICAN DOLLAR
JODJordanian Dinar
JPYJapanese Yen
KESKenyan Shilling
KHRCambodian Riel
KISKirghizstan Som
KMFComoros Franc
KPWNorth Korean Won
KRWSouth Korean Won
KWDKuwaiti Dinar
KYDCayman Dollar
KZTKazakhstani Tenge
LAKLaotian Kip
LBPLebanese Pound
LKRSri Lankan Rupee
LRDLiberian Dollar
LSLLesotho Loti
LTLLithuanian Lita
LUFLuxembourgian Franc
LVLLatvian Lat
LYDLibyan Dinar
MADMoroccan Dirham
MDLMoldavian Lei
MGFMadagascan Franc
MNCMonaco
MNTMongolian Tugrik
MOPMacao Pataca
MROMauritanian Ouguiya
MTLMaltese Lira
MURMauritius Rupee
MVRMaldive Rufiyaa
MWKMalawi Kwacha
MXNMexican Peso (new)
MXPMexican Peso (old)
MYRMalaysian Ringgit
MZMMozambique Metical
NGNNigerian Naira
NICNicaragua
NIONicaraguan Cordoba
NISIsreal
NLGDutch Guilder
NOKNorwegian Krone
NPRNepalese Rupee
NZDNew Zealand Dollars
OMROmani Rial
PABPanamanian Balboa
PEIPeruvian Inti
PENPeruvian Sol - New
PESPeruvian Sol
PGKPapua New Guinea Kina
PHPPhilippino Peso
PKRPakistan Rupee
PLNPolish Zloty
PLZPoland
PTEPortuguese Escudo
PYGParaguayan Guarani
QARQatar Riyal
RMBChinese Renminbi Yuan
ROLRoumanian Lei
RURRussian Rouble
RWFRwanda Franc
SARSaudi Riyal
SBDSolomon Islands Dollar
SCRSeychelles Rupee
SDPSudanese Pound
SEKSwedish Krona
SGDSingapore Dollar
SHPSt.Helena Pound
SITSlovenian Tolar
SKKSlovakian Krona
SLLLeone
SOLPeru
SOSSomalian Shilling
SRGSurinam Guilder
STDSao Tome / Principe Dobra
SURRussian Ruble (old)
SVCEl Salvador Colon
SYPSyrian Pound
SZLSwaziland Lilangeni
THBThailand Baht
TJRTadzhikistani Ruble
TMMTurkmenistani Manat
TNDTunisian Dinar
TOPTongan Pa'anga
TPETimor Escudo
TRLTurkish Lira
TTDTrinidad and Tobago Dollar
TWDNew Taiwan Dollar
TZSTanzanian Shilling
UAKUkrainian Karbowanez
UGSUgandan Shilling
USDAmerican Dollar
UYPUruguayan New Peso
UYUUruguay
VEBVenezuelan Bolivar
VNDVietnamese Dong
VUVVanuatu Vatu
WSTSamoan Tala
XAFGabon C.f.A Franc
XCDEast Carribean Dollar
XOFBenin C.f.A. Franc
YERYemeni Ryal
ZARSouth African Rand
ZMKZambian Kwacha
ZRZZaire
ZWDZimbabwean Dollar
+ +

Return to Main Documentation + Page.

diff --git a/Docs/En/xacc-date.html b/Docs/En/xacc-date.html index 60eedd2aed..32f5a5b983 100644 --- a/Docs/En/xacc-date.html +++ b/Docs/En/xacc-date.html @@ -1,45 +1,44 @@ - - + - + + + Date Data Input

Date Input

The date cell handles the following accelerator keys: -
-       '+':
-       '=':     increment day 
 
-       '_':
-       '-':      decrement day 
+    
    +
  • +, = increment day
  • - '}': - ']': increment month +
  • _ , - decrement day
  • - '{': - '[': decrment month +
  • } , ] increment month
  • - 'M': - 'm': begining of month +
  • { , [ decrment month
  • - 'H': - 'h': end of month +
  • M , m begining of month
  • - 'Y': - 'y': begining of year +
  • H , h end of month
  • - 'R': - 'r': end of year +
  • Y , y begining of year
  • - 'T': - 't': today +
  • R , r end of year
  • -
- GnuCash can be compiled to use either European style dates or - US Style dates. Grep for UK_DATES in - dates.h +
  • T , t today GnuCash can be compiled to use + either European style dates or US Style dates.
  • + +
  • + Grep for UK_DATES in dates.h + + +

    Return to Main Documentation + Page.

    +
  • + diff --git a/Docs/En/xacc-double.html b/Docs/En/xacc-double.html index 669a5fdbef..683e92e93a 100644 --- a/Docs/En/xacc-double.html +++ b/Docs/En/xacc-double.html @@ -1,65 +1,120 @@ - + - Using the Double Entry Feature + + + Understanding Double Entry Accounting - - -

    What is Double Entry?

    - Double entry is an accounting methodology used by professionals - to make sure that all accounts are properly balanced. When accounts - balance, the likelihood that a data-entry error has been made is much - less. For large, complex accounts with many transactions, it is easy - to make errors that might go undetected for a long time. Double-entry - is a crucial technology for catching those errors. -

    - A double-entry transaction is a transaction that appears in two - accounts. One account is debited by an amount exactly equal to - what the other is credited. Thus, all transactions are always - transfers between two accounts. Since they always appear with - a plus sign in one account, and a minus sign in the other, the - total over all accounts will always be zero, and thus, balanced - accounts are guaranteed. -

    - Double-entry is already familiar to most people as a transfer - from one bank account to another, where money is withdrawn from - one and deposited in another. Far less familiar is the idea that - double entry can be used to track income and expenses as well as - bank transfers. See the - "Income/Expense" help window - for more information. -

    Using Double Entry

    - To use the double entry, click on a box in the column marked - "Transfer From" on the left-hand side of the register. A menu - will drop down, listing all of the accounts from which a transfer - may be made. Select one. When you record the transaction, - the double-entry will automatically be made, and the transaction - automatically appear in all windows showing the transfered-from - and the transferred-to accounts. -

    - To change a double-entry transaction, edit it in any window in - which it appears. Any changes made will be automatically - reflected in both accounts and all windows displaying the - transaction. Similarly, when a double-entry transaction is - deleted, it is deleted from both accounts; balances are - automatically recalculated for both accounts. -

    - To change the transfer account, simply select a new account - from the pull-down menu. When you record the transaction, - it will automatically be selected from the old account, and - inserted into the new account. - -

    Scrubbing Clean

    - GnuCash can be configured to be strict about double entry, - or to be loose. In loose mode, you can create unbalanced transactions, - that is, transactions that don't pair up with a matching entry, - and thus don't balance to zero. To clean up these unbalanced transactions, - you can scrub the account clean by choosing "Scrub" from the - window menu. This will examine each transaction; if the transaction doesn't - balance, a split entry will be created and placed into an account - named "Unbalanced". You can then review these splits and move them - to thier proper accounts. + +

    What is Double Entry Accounting?

    +

    Double entry is an accounting methodology used by + professional accountants to make sure that all accounts and + indeed each transaction is properly balanced. When + these are all required to balance, the likelihood of data-entry + errors is greatly reduced. For large, complex sets of accounts + with many transactions, it is distressingly easy to make errors + that may go undetected for a long time. Double-entry is a + crucial technology that has been used since the 13th century to + avoid such errors.

    + +

    A double-entry transaction is a transaction that contains + entries for two (or more) accounts that balance against one + another. One account is debited by an amount exactly + equal to what the other is credited. By ensuring that + each transaction balances, a balanced set of accounts is + guaranteed. This doesn't prevent you from having errors, but + certainly eliminates the large class of I forgot to enter + that part of the transaction errors.

    + +

    Double-entry may be introduced in a more intuitive way via + the notion of a transfer from one bank account to another, + where an amount is taken out of one bank account and deposited + in the other. This is effectively the "rule" of double entry + accounting; if you add something in to one account, you + have to have another component to that transaction to + balance this.

    + +

    Not-quite-an-aside: If you look at your bank + statements, they are typically written up from the + bank's perspective, which is exactly opposite to + yours. For instance, when you put money in, establishing a + deposit, this establishes a DEBT on their part.

    + +

    The perhaps less obvious extension is the notion that + double entry can be used to represent income and expenses as + well as bank transfers. See the + Income/Expense page for more information.

    + +

    In a traditional system that records + debits and credits separately, the identity that all + transactions are required to satisfy is that Total of + Debits = Total of Credits.

    + +

    X-Accountant treats "Debits" as positive values, and + "Credits" as negative values, so that this identity simplifies + to value1 + value2 + + value3 + ... = 0

    + +

    Using Double Entry

    + +

    To use the double entry system, click on a box in the + column marked "Transfer From" on the left-hand side of the + register. A menu will drop down, listing all of the accounts + from which a transfer may be made. Select one. When you record + the transaction, the double-entry will automatically be made, + and the transaction automatically appear in all windows showing + the transfered-from and the transferred-to accounts.

    + +

    To change a double-entry transaction, edit the + transaction in any window in which it appears. Any changes made + will be automatically reflected in both accounts and all + windows displaying the transaction. Similarly, when a + double-entry transaction is deleted, it will be deleted from + both accounts; balances are automatically recalculated for + both accounts.

    + +

    To change the transfer account, simply select a new account + from the pull-down menu. When you record the transaction, it + will automatically be selected from the old account, and + inserted into the new account.

    + +

    Scrubbing Clean

    + +

    GnuCash can be configured to be strict about double entry, + or you may configure it to be "loose."

    + +

    In "loose" mode, you can create unbalanced + transactions, that is, transactions where the "splits" + don't balance to zero. That discards the validation that comes + from using the more strict double entry scheme, which is + probably not a really wise move. In effect:

    + +
      +
    • If you aren't sure of what you're doing, you likely do + not want to discard the validation of double entry, + as this helps you keep your accounts balanced even when + you're not perfectly clear on this.
    • + +
    • If you are an accounting whiz, you'll know that + it's really important to keep things in balance, + and again will prefer double entry.
    • +
    + +

    But if you decide to "outsmart the system," and have a + number of unbalanced transactions, you'll probably want to + clean this up at some point. To clean up these unbalanced + transactions, you Scrub the account clean by choosing + Scrub from the window menu. This will examine each + transaction; if the transaction doesn't balance, a split entry + will be created and placed into an account named + Unbalanced. You may then review these splits and move them + to their proper accounts.

    + +

    Return to Main Documentation + Page.

    + diff --git a/Docs/En/xacc-expense.html b/Docs/En/xacc-expense.html index 1c7e2c5eab..2959ba9bce 100644 --- a/Docs/En/xacc-expense.html +++ b/Docs/En/xacc-expense.html @@ -1,68 +1,182 @@ - + + + + Using Expense/Income Accounts - - -

    What is an Income/Expense Account?

    - The words "Income" and "Expense" are beguilingly simple; everyone - knows what they mean. Don't let this fool you: they have a - special meaning when applied to double-entry accounting. -

    - To properly record income and expenses in a double entry system, - two special accounts must be created: one of type "Income" and one - of type "Expense". Income such salary, wages, bank interest and - stock dividends are then recorded as transfers from an income - account to a bank (or generally, asset) account. Similarly, - expenses are recorded as transfers from a credit card account - (or generally, a liability account). -

    - Why are accounts of type "Income" and "Expense" considered - special? The answer lies in the nature of double-entry. - With a double-entry transaction, one account is always credited, - and another account is always debited. When salary is deposited - in a bank account, the bank account is credited, and the - income account is debited. In order to make income appear - positive (as it is), and expenses look negative, the meaning - of "debit" and "credit" is reversed for income and expense - accounts. This makes them special. -

    - Another way in which income and expense accounts are special - is that their account totals do not appear on a balance - sheet. A balance sheet shows "Net Worth": the sum of all - assets minus all liabilities. Since income and expenses are - neither assets nor liabilities, they do not appear on the - balance sheet. There is a different kind of report, a - "Profit and Loss" (P&L) report, that shows income and - expenses. The total profit (or loss) is the total income - minus total expenses. Since assets and liabilities are neither - income or expenses, they do not appear on a P&L statement. -

    - Even though the accounts may be "special", you do not need - to do anything "special" - to use income and expense accounts. Everything is handled - automatically, as long as you remember to transfer income - and expenses between income/expense accounts and ordinary - bank/asset/credit-card/liability accounts. -

    Using Income/Expense Accounts

    - To use an income/expense account, simply create one from the - "New Account" dialogue window, and then be sure to transfer - income/expenses to it as you record paychecks, interest, etc. -

    - If you have a complex account arrangement, you may want to - create multiple income/expense accounts. One can be used to - record salary, and only salary, another to record only bank - interest, and a third only to record stock dividends. This - partitioning is particularly useful when tax-time rolls around. + +

    What is an Income/Expense Account?

    + The words "Income" and "Expense" are beguilingly simple; + everyone thinks they know what they mean. Don't let this fool + you: they have a somewhat special meaning when applied to + double-entry accounting. -

    Notes

    - Users of Quicken (TM) products should realize that what Quicken - calls "Categories" are really just Income/Expense accounts. - Thus, if you are used to specifying a category in Quicken, - just create an income/expense account of the same name in - X-Accountant, and use that. +

    If you have used other personal finance software, be aware + that Quicken calls + them "Categories."

    +

    In a double entry system, two kinds of accounts must be + created: some of type "Income" and some of type "Expense". + Income such as salary, wages, bank interest and stock dividends + are then recorded as transfers from an income account to a bank + (or generally, asset) account. Similarly, expenses are recorded + as transfers from a credit card account (or generally, a + liability account).

    + +

    Another way of describing the requirement for "double + entry" is that when you receive an income, two things + happen:

    + +
      +
    • You receive a sum of money, and must record + that effect on your bank account.
    • + +
    • You have received an income, and must record that effect + on an income account.
    • +
    + +

    Why are accounts of type "Income" and "Expense" considered + special? The answer lies in the nature of double-entry. With a + simple double-entry transaction, one account is always + credited, and another account is always debited. When salary is + deposited in a bank account, the bank account is credited, and + the income account is debited, thus:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    AccountDebitCredit
    Chequing Account1,600.00 +
    Salary + 1,600.00
    + +

    This can be readily extended to a greater number of "split" + items thus:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AccountDebitCredit
    Chequing Account1,300.00 +
    Income Taxes200.00 +
    Health Plan100.00 +
    Salary + 1,600.00
    + +

    There may be a whole lot more than two entries in the + transaction, but the total sum of the Debits, $1,600.00, still + equals the total sum of the credits, $1,600.00.

    + +

    If, as with X-Accountant, everything is forced onto + one column, so that debits are represented by positive + values, and credits are represented by negative values, + the income/expense accounts do a slightly non-intuitive thing + and you see incomes as negative values. That + appear contrary to intuition, but is necessary in order + for balances to balance.

    + +

    Another way in which income and expense accounts are + special is that their account totals do not appear on a balance + sheet. A balance sheet shows "Net Worth": the sum of all assets + minus all liabilities. Since income and expenses are neither + assets nor liabilities, they do not appear on the balance + sheet; only their effects on equity. There is a + different kind of report, a "Profit and Loss" (P&L) report, + that shows income and expenses. The total profit (or loss) is + the total income minus total expenses. Since assets and + liabilities are neither income or expenses, they do not appear + on a P&L statement.

    + +

    Even though the accounts may be somewhat "special", you do + not need to do anything particularly special to use income and + expense accounts. X-Accountant handles the values + automatically, so that if you record properly the effects of + the transactions on your bank account or credit card, the + income/expense side of the transaction should also be handled + correctly. The time when things get "peculiar," and when you + need to more deeply understand this, is when amounts are + transferred between income/expense accounts. (The + causes for such transfers tend to be somewhat peculiar, so + it's pretty fair for this to be a pretty odd situation.)

    + +

    Using Income/Expense Accounts

    + +

    To use an income/expense account, simply create one from + the "New Account" dialogue window, and then be sure to transfer + income/expenses to it as you record paychecks, interest, + etc.

    + +

    You will doubtless wish to create quite a number of income + and expense accounts; it may be worth looking at the Sample Chart of Accounts + for ideas.

    + +

    This partitioning of incomes and expenses proves + particularly useful for North Americans when income tax + time rolls around.

    + +

    Return to Main Documentation + Page.

    + diff --git a/Docs/En/xacc-gpl.html b/Docs/En/xacc-gpl.html index 66ec5f4268..491ba2c470 100644 --- a/Docs/En/xacc-gpl.html +++ b/Docs/En/xacc-gpl.html @@ -1,334 +1,341 @@ - + + + + GNU General Public License - - + +

    GNU General Public License

    +

    Version 2, June 1991

    -
    - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 675 Mass Ave, Cambridge, MA 02139, USA

    - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed.

    -

    -

    +

    + Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 + Mass Ave, Cambridge, MA 02139, USA + +

    Everyone is permitted to copy and distribute verbatim + copies of this license document, but changing it is not + allowed.

    +
    +

    Preamble

    -

    - The licenses for most software are designed to take away your - freedom to share and change it. By contrast, the GNU General Public - License is intended to guarantee your freedom to share and change free - software--to make sure the software is free for all its users. This - General Public License applies to most of the Free Software - Foundation's software and to any other program whose authors commit to - using it. (Some other Free Software Foundation software is covered by - the GNU Library General Public License instead.) You can apply it to - your programs, too. +

    The licenses for most software are designed to take away + your freedom to share and change it. By contrast, the GNU + General Public License is intended to guarantee your freedom to + share and change free software--to make sure the software is + free for all its users. This General Public License applies to + most of the Free Software Foundation's software and to any + other program whose authors commit to using it. (Some other + Free Software Foundation software is covered by the GNU Library + General Public License instead.) You can apply it to your + programs, too.

    -

    - When we speak of free software, we are referring to freedom, not - price. Our General Public Licenses are designed to make sure that you - have the freedom to distribute copies of free software (and charge for - this service if you wish), that you receive source code or can get it - if you want it, that you can change the software or use pieces of it - in new free programs; and that you know you can do these things. +

    When we speak of free software, we are referring to + freedom, not price. Our General Public Licenses are designed to + make sure that you have the freedom to distribute copies of + free software (and charge for this service if you wish), that + you receive source code or can get it if you want it, that you + can change the software or use pieces of it in new free + programs; and that you know you can do these things.

    -

    - To protect your rights, we need to make restrictions that forbid - anyone to deny you these rights or to ask you to surrender the rights. - These restrictions translate to certain responsibilities for you if you - distribute copies of the software, or if you modify it. +

    To protect your rights, we need to make restrictions that + forbid anyone to deny you these rights or to ask you to + surrender the rights. These restrictions translate to certain + responsibilities for you if you distribute copies of the + software, or if you modify it.

    -

    - For example, if you distribute copies of such a program, whether - gratis or for a fee, you must give the recipients all the rights that - you have. You must make sure that they, too, receive or can get the - source code. And you must show them these terms so they know their - rights. +

    For example, if you distribute copies of such a program, + whether gratis or for a fee, you must give the recipients all + the rights that you have. You must make sure that they, too, + receive or can get the source code. And you must show them + these terms so they know their rights.

    -

    - We protect your rights with two steps: (1) copyright the software, and - (2) offer you this license which gives you legal permission to copy, - distribute and/or modify the software. +

    We protect your rights with two steps: (1) copyright the + software, and (2) offer you this license which gives you legal + permission to copy, distribute and/or modify the software.

    -

    - Also, for each author's protection and ours, we want to make certain - that everyone understands that there is no warranty for this free - software. If the software is modified by someone else and passed on, we - want its recipients to know that what they have is not the original, so - that any problems introduced by others will not reflect on the original - authors' reputations. +

    Also, for each author's protection and ours, we want to + make certain that everyone understands that there is no + warranty for this free software. If the software is modified by + someone else and passed on, we want its recipients to know that + what they have is not the original, so that any problems + introduced by others will not reflect on the original authors' + reputations.

    -

    - Finally, any free program is threatened constantly by software - patents. We wish to avoid the danger that redistributors of a free - program will individually obtain patent licenses, in effect making the - program proprietary. To prevent this, we have made it clear that any - patent must be licensed for everyone's free use or not licensed at all. +

    Finally, any free program is threatened constantly by + software patents. We wish to avoid the danger that + redistributors of a free program will individually obtain + patent licenses, in effect making the program proprietary. To + prevent this, we have made it clear that any patent must be + licensed for everyone's free use or not licensed at all.

    -

    - The precise terms and conditions for copying, distribution and - modification follow. -

    -
    -

    GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

    +

    The precise terms and conditions for copying, distribution + and modification follow.

    +
    -

    - 0. This License applies to any program or other work which contains - a notice placed by the copyright holder saying it may be distributed - under the terms of this General Public License. The "Program", below, - refers to any such program or work, and a "work based on the Program" - means either the Program or any derivative work under copyright law: - that is to say, a work containing the Program or a portion of it, - either verbatim or with modifications and/or translated into another - language. (Hereinafter, translation is included without limitation in - the term "modification".) Each licensee is addressed as "you". +

    GNU GENERAL PUBLIC LICENSE TERMS AND + CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

    -

    - Activities other than copying, distribution and modification are not - covered by this License; they are outside its scope. The act of - running the Program is not restricted, and the output from the Program - is covered only if its contents constitute a work based on the - Program (independent of having been made by running the Program). - Whether that is true depends on what the Program does. +

    0. This License applies to any program or other work which + contains a notice placed by the copyright holder saying it may + be distributed under the terms of this General Public License. + The "Program", below, refers to any such program or work, and a + "work based on the Program" means either the Program or any + derivative work under copyright law: that is to say, a work + containing the Program or a portion of it, either verbatim or + with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the + term "modification".) Each licensee is addressed as "you".

    -

    - 1. You may copy and distribute verbatim copies of the Program's - source code as you receive it, in any medium, provided that you - conspicuously and appropriately publish on each copy an appropriate - copyright notice and disclaimer of warranty; keep intact all the - notices that refer to this License and to the absence of any warranty; - and give any other recipients of the Program a copy of this License - along with the Program. +

    Activities other than copying, distribution and + modification are not covered by this License; they are outside + its scope. The act of running the Program is not restricted, + and the output from the Program is covered only if its contents + constitute a work based on the Program (independent of having + been made by running the Program). Whether that is true depends + on what the Program does.

    -

    - You may charge a fee for the physical act of transferring a copy, and - you may at your option offer warranty protection in exchange for a fee. +

    1. You may copy and distribute verbatim copies of the + Program's source code as you receive it, in any medium, + provided that you conspicuously and appropriately publish on + each copy an appropriate copyright notice and disclaimer of + warranty; keep intact all the notices that refer to this + License and to the absence of any warranty; and give any other + recipients of the Program a copy of this License along with the + Program.

    -

    - 2. You may modify your copy or copies of the Program or any portion - of it, thus forming a work based on the Program, and copy and - distribute such modifications or work under the terms of Section 1 - above, provided that you also meet all of these conditions: +

    You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee.

    + +

    2. You may modify your copy or copies of the Program or any + portion of it, thus forming a work based on the Program, and + copy and distribute such modifications or work under the terms + of Section 1 above, provided that you also meet all of these + conditions:

    - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. -

    - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. -

    - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) -

    + a) You must cause the modified files to carry prominent + notices stating that you changed the files and the date of + any change. + +

    b) You must cause any work that you distribute or + publish, that in whole or in part contains or is derived from + the Program or any part thereof, to be licensed as a whole at + no charge to all third parties under the terms of this + License.

    + +

    c) If the modified program normally reads commands + interactively when run, you must cause it, when started + running for such interactive use in the most ordinary way, to + print or display an announcement including an appropriate + copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may + redistribute the program under these conditions, and telling + the user how to view a copy of this License. (Exception: if + the Program itself is interactive but does not normally print + such an announcement, your work based on the Program is not + required to print an announcement.)

    +
    + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the + Program, and can be reasonably considered independent and + separate works in themselves, then this License, and its terms, + do not apply to those sections when you distribute them as + separate works. But when you distribute the same sections as + part of a whole which is a work based on the Program, the + distribution of the whole must be on the terms of this License, + whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote + it. -
    - These requirements apply to the modified work as a whole. If - identifiable sections of that work are not derived from the Program, - and can be reasonably considered independent and separate works in - themselves, then this License, and its terms, do not apply to those - sections when you distribute them as separate works. But when you - distribute the same sections as part of a whole which is a work based - on the Program, the distribution of the whole must be on the terms of - this License, whose permissions for other licensees extend to the - entire whole, and thus to each and every part regardless of who wrote it. +

    Thus, it is not the intent of this section to claim rights + or contest your rights to work written entirely by you; rather, + the intent is to exercise the right to control the distribution + of derivative or collective works based on the Program.

    -

    - Thus, it is not the intent of this section to claim rights or contest - your rights to work written entirely by you; rather, the intent is to - exercise the right to control the distribution of derivative or - collective works based on the Program. +

    In addition, mere aggregation of another work not based on + the Program with the Program (or with a work based on the + Program) on a volume of a storage or distribution medium does + not bring the other work under the scope of this License.

    -

    - In addition, mere aggregation of another work not based on the Program - with the Program (or with a work based on the Program) on a volume of - a storage or distribution medium does not bring the other work under - the scope of this License. - -

    - 3. You may copy and distribute the Program (or a work based on it, - under Section 2) in object code or executable form under the terms of - Sections 1 and 2 above provided that you also do one of the following: +

    3. You may copy and distribute the Program (or a work based + on it, under Section 2) in object code or executable form under + the terms of Sections 1 and 2 above provided that you also do + one of the following:

    - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, -

    - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, -

    - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) + a) Accompany it with the complete corresponding + machine-readable source code, which must be distributed under + the terms of Sections 1 and 2 above on a medium customarily + used for software interchange; or, + +

    b) Accompany it with a written offer, valid for at least + three years, to give any third party, for a charge no more + than your cost of physically performing source distribution, + a complete machine-readable copy of the corresponding source + code, to be distributed under the terms of Sections 1 and 2 + above on a medium customarily used for software interchange; + or,

    + +

    c) Accompany it with the information you received as to + the offer to distribute corresponding source code. (This + alternative is allowed only for noncommercial distribution + and only if you received the program in object code or + executable form with such an offer, in accord with Subsection + b above.)

    -

    - The source code for a work means the preferred form of the work for - making modifications to it. For an executable work, complete source - code means all the source code for all modules it contains, plus any - associated interface definition files, plus the scripts used to - control compilation and installation of the executable. However, as a - special exception, the source code distributed need not include - anything that is normally distributed (in either source or binary - form) with the major components (compiler, kernel, and so on) of the - operating system on which the executable runs, unless that component - itself accompanies the executable. +

    The source code for a work means the preferred form of the + work for making modifications to it. For an executable work, + complete source code means all the source code for all modules + it contains, plus any associated interface definition files, + plus the scripts used to control compilation and installation + of the executable. However, as a special exception, the source + code distributed need not include anything that is normally + distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating + system on which the executable runs, unless that component + itself accompanies the executable.

    -

    - If distribution of executable or object code is made by offering - access to copy from a designated place, then offering equivalent - access to copy the source code from the same place counts as - distribution of the source code, even though third parties are not - compelled to copy the source along with the object code. -

    +

    If distribution of executable or object code is made by + offering access to copy from a designated place, then offering + equivalent access to copy the source code from the same place + counts as distribution of the source code, even though third + parties are not compelled to copy the source along with the + object code.

    +
    + 4. You may not copy, modify, sublicense, or distribute the + Program except as expressly provided under this License. Any + attempt otherwise to copy, modify, sublicense or distribute the + Program is void, and will automatically terminate your rights + under this License. However, parties who have received copies, + or rights, from you under this License will not have their + licenses terminated so long as such parties remain in full + compliance. -
    - 4. You may not copy, modify, sublicense, or distribute the Program - except as expressly provided under this License. Any attempt - otherwise to copy, modify, sublicense or distribute the Program is - void, and will automatically terminate your rights under this License. - However, parties who have received copies, or rights, from you under - this License will not have their licenses terminated so long as such - parties remain in full compliance. +

    5. You are not required to accept this License, since you + have not signed it. However, nothing else grants you permission + to modify or distribute the Program or its derivative works. + These actions are prohibited by law if you do not accept this + License. Therefore, by modifying or distributing the Program + (or any work based on the Program), you indicate your + acceptance of this License to do so, and all its terms and + conditions for copying, distributing or modifying the Program + or works based on it.

    -

    - 5. You are not required to accept this License, since you have not - signed it. However, nothing else grants you permission to modify or - distribute the Program or its derivative works. These actions are - prohibited by law if you do not accept this License. Therefore, by - modifying or distributing the Program (or any work based on the - Program), you indicate your acceptance of this License to do so, and - all its terms and conditions for copying, distributing or modifying - the Program or works based on it. +

    6. Each time you redistribute the Program (or any work + based on the Program), the recipient automatically receives a + license from the original licensor to copy, distribute or + modify the Program subject to these terms and conditions. You + may not impose any further restrictions on the recipients' + exercise of the rights granted herein. You are not responsible + for enforcing compliance by third parties to this License.

    -

    - 6. Each time you redistribute the Program (or any work based on the - Program), the recipient automatically receives a license from the - original licensor to copy, distribute or modify the Program subject to - these terms and conditions. You may not impose any further - restrictions on the recipients' exercise of the rights granted herein. - You are not responsible for enforcing compliance by third parties to - this License. +

    7. If, as a consequence of a court judgment or allegation + of patent infringement or for any other reason (not limited to + patent issues), conditions are imposed on you (whether by court + order, agreement or otherwise) that contradict the conditions + of this License, they do not excuse you from the conditions of + this License. If you cannot distribute so as to satisfy + simultaneously your obligations under this License and any + other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent license + would not permit royalty-free redistribution of the Program by + all those who receive copies directly or indirectly through + you, then the only way you could satisfy both it and this + License would be to refrain entirely from distribution of the + Program.

    -

    - 7. If, as a consequence of a court judgment or allegation of patent - infringement or for any other reason (not limited to patent issues), - conditions are imposed on you (whether by court order, agreement or - otherwise) that contradict the conditions of this License, they do not - excuse you from the conditions of this License. If you cannot - distribute so as to satisfy simultaneously your obligations under this - License and any other pertinent obligations, then as a consequence you - may not distribute the Program at all. For example, if a patent - license would not permit royalty-free redistribution of the Program by - all those who receive copies directly or indirectly through you, then - the only way you could satisfy both it and this License would be to - refrain entirely from distribution of the Program. +

    If any portion of this section is held invalid or + unenforceable under any particular circumstance, the balance of + the section is intended to apply and the section as a whole is + intended to apply in other circumstances.

    -

    - If any portion of this section is held invalid or unenforceable under - any particular circumstance, the balance of the section is intended to - apply and the section as a whole is intended to apply in other - circumstances. +

    It is not the purpose of this section to induce you to + infringe any patents or other property right claims or to + contest validity of any such claims; this section has the sole + purpose of protecting the integrity of the free software + distribution system, which is implemented by public license + practices. Many people have made generous contributions to the + wide range of software distributed through that system in + reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to + distribute software through any other system and a licensee + cannot impose that choice.

    +
    + This section is intended to make thoroughly clear what is + believed to be a consequence of the rest of this License. -

    - It is not the purpose of this section to induce you to infringe any - patents or other property right claims or to contest validity of any - such claims; this section has the sole purpose of protecting the - integrity of the free software distribution system, which is - implemented by public license practices. Many people have made - generous contributions to the wide range of software distributed - through that system in reliance on consistent application of that - system; it is up to the author/donor to decide if he or she is willing - to distribute software through any other system and a licensee cannot - impose that choice. -

    +

    8. If the distribution and/or use of the Program is + restricted in certain countries either by patents or by + copyrighted interfaces, the original copyright holder who + places the Program under this License may add an explicit + geographical distribution limitation excluding those countries, + so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License.

    -
    - This section is intended to make thoroughly clear what is believed to - be a consequence of the rest of this License. +

    9. The Free Software Foundation may publish revised and/or + new versions of the General Public License from time to time. + Such new versions will be similar in spirit to the present + version, but may differ in detail to address new problems or + concerns.

    -

    - 8. If the distribution and/or use of the Program is restricted in - certain countries either by patents or by copyrighted interfaces, the - original copyright holder who places the Program under this License - may add an explicit geographical distribution limitation excluding - those countries, so that distribution is permitted only in or among - countries not thus excluded. In such case, this License incorporates - the limitation as if written in the body of this License. +

    Each version is given a distinguishing version number. If + the Program specifies a version number of this License which + applies to it and "any later version", you have the option of + following the terms and conditions either of that version or of + any later version published by the Free Software Foundation. If + the Program does not specify a version number of this License, + you may choose any version ever published by the Free Software + Foundation.

    -

    - 9. The Free Software Foundation may publish revised and/or new versions - of the General Public License from time to time. Such new versions will - be similar in spirit to the present version, but may differ in detail to - address new problems or concerns. - -

    - Each version is given a distinguishing version number. If the Program - specifies a version number of this License which applies to it and "any - later version", you have the option of following the terms and conditions - either of that version or of any later version published by the Free - Software Foundation. If the Program does not specify a version number of - this License, you may choose any version ever published by the Free Software - Foundation. - -

    - 10. If you wish to incorporate parts of the Program into other free - programs whose distribution conditions are different, write to the author - to ask for permission. For software which is copyrighted by the Free - Software Foundation, write to the Free Software Foundation; we sometimes - make exceptions for this. Our decision will be guided by the two goals - of preserving the free status of all derivatives of our free software and - of promoting the sharing and reuse of software generally. -

    +

    10. If you wish to incorporate parts of the Program into + other free programs whose distribution conditions are + different, write to the author to ask for permission. For + software which is copyrighted by the Free Software Foundation, + write to the Free Software Foundation; we sometimes make + exceptions for this. Our decision will be guided by the two + goals of preserving the free status of all derivatives of our + free software and of promoting the sharing and reuse of + software generally.

    NO WARRANTY

    -

    - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY - FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN - OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES - PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED - OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS - TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE - PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, - REPAIR OR CORRECTION. +

    11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE + IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY + APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE + COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS + IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS + WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE + COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

    -

    - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING - WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR - REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, - INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING - OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED - TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY - YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER - PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE - POSSIBILITY OF SUCH DAMAGES. +

    12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED + TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO + MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, + BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, + INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR + INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS + OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED + BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE + WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY + HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

    END OF TERMS AND CONDITIONS

    -
    + +

    Return to Main Documentation + Page.

    + diff --git a/Docs/En/xacc-groups.html b/Docs/En/xacc-groups.html index 4c7589b192..7dbc04c882 100644 --- a/Docs/En/xacc-groups.html +++ b/Docs/En/xacc-groups.html @@ -1,19 +1,24 @@ - + + + + Chart of Accounts and Account Numbering - - -

    Chart of Accounts

    - Typically accounts are arranged as a tree, in heirarchical form. - The main trunks represent entire categories or groups, while the - leaves of the tree denote individual bank accounts or expense - categories. When a summary report is requested, typically only - the main branches are shown in the report, not each of the - individual accounts. For example, a chart of accounts might look - like the following: + +

    Chart of Accounts

    + Typically accounts are arranged as a tree, in hierarchical + form. The main trunks represent entire categories or groups, + while the leaves of the tree denote individual bank accounts or + expense categories. + +

    When a summary report is requested, typically only the main + branches are shown in the report, not each of the individual + accounts. For example, a chart of accounts might look like the + following:

             300             Expenses
              |
    @@ -30,168 +35,336 @@
              :   :
     
    - Note that accounts are coded: when a report is generated, - the sort order is determined entirely by the numbering. - By tradition and common practice, accounts that are not - leaf accounts have a number that ends in zero; each higher - level has one more zero in the account code. +

    Note that accounts not only have names; they have + codes; when a report is generated, the + sort order is determined entirely by the numbering. A sensible + hierarchy generally will have the "leaf" accounts end in + non-zero digits, whilst parent nodes have increasing numbers of + zeros. For instance, "cash" accounts might logically be + arranged thus:

    -

    - When you create a new account, - GnuCash offers a guess - at what it thinks the account code might be; you are free - to change this. GnuCash does not prevent duplicate numbering, - although you are encouraged to avoid this. Account codes - are treated as numbers, base-36: thus, if you run out of numbers, - you can use the letters, a through z. - - -

    A Bigger Example

    - A "typical" chart of accounts is shown below. Each account - is of a given account type. - This example is a combination of some typical business - and personal accounts. -

      -
    • Assets +
    • + Overall Assets: 100 +
        -
      • Cash On Hand +
      • + Overall Cash: 110 +
          -
        • Checking account -
        • Money Market Account -
        • Certificate of Deposit +
        • Cash in Wallet: 111
        • + +
        • Cash in Sock in Closet: 112
        • + +
        • Cash in Mattress: 113
        • + +
        • Petty Cash Box: 114
        -
      • Fixed Assets +
      • + +
      • + Overall Banking Assets 120 +
          -
        • Furniture -
        • Computers -
        • Jewelry, Collectibles -
        • Tools, Machinery +
        • Checking Account 121
        • + +
        • Savings Account 122
        -
      • Investments -
          -
        • Stocks -
        • Bonds -
        • Mutual Funds -
        • Real Estate -
        -
      -
    • Liabilities -
        -
      • Taxes -
          -
        • Federal Income Tax -
        • Social Security -
        • Medicare -
        • FUTA -
        • State Income Tax -
        -
      • Accounts Payable -
          -
        • MasterCard -
        • Visa -
        • American Express -
        • Diner's Club -
        -
      • Loans -
          -
        • Debentures -
        • School Loan -
        • Uncle Harry's Tide-me-over -
        -
      -
    • Equity -
        -
      • Retained Earnings -
      • Current Year Earnings -
      • Historical Adjustments +
      -
    • Income +

      When you create a new + account, GnuCash offers a guess at what it thinks the + account code might be; you are free to change this. GnuCash + does not prevent duplicate numbering, although we would + encourage you to avoid this. Account codes are treated as + numbers in base-36, thus, if you run out of numbers, you + can use the letters, a through z.

      + +

      A Sample Chart of Accounts

      + + +

      A "typical" chart of accounts is shown below. Each + account is of a given account + type. This example is a combination of some typical + business and personal accounts.

      +
        -
      • Interest Income +
      • + Assets +
          -
        • Bank Account Interest -
        • Certificate of Deposit -
        • Bond Interest +
        • + Cash On Hand + +
            +
          • Checking account
          • + +
          • Money Market Account
          • + +
          • Certificate of Deposit
          • +
          +
        • + +
        • + Fixed Assets + +
            +
          • Furniture
          • + +
          • Computers
          • + +
          • Jewelry, Collectibles
          • + +
          • Tools, Machinery
          • +
          +
        • + +
        • + Investments + +
            +
          • Stocks
          • + +
          • Bonds
          • + +
          • Mutual Funds
          • + +
          • Real Estate
          • +
          +
        -
      • Dividends +
      • + +
      • + Liabilities +
          -
        • Stock -
        • Mutual Fund +
        • + Taxes + +
            +
          • Federal Income Tax
          • + +
          • Social Security
          • + +
          • Medicare
          • + +
          • FUTA
          • + +
          • State Income Tax
          • +
          +
        • + +
        • + Accounts Payable + +
            +
          • MasterCard
          • + +
          • Visa
          • + +
          • American Express
          • + +
          • Diner's Club
          • +
          +
        • + +
        • + Loans + +
            +
          • Debentures
          • + +
          • School Loan
          • + +
          • Uncle Harry's Tide-me-over
          • +
          +
        -
      • Consulting +
      • + +
      • + Equity +
          -
        • ABC Design -
        • PQR Infomatics +
        • Retained Earnings
        • + +
        • Current Year Earnings
        • + +
        • Historical Adjustments
        -
      • Salary +
      • + +
      • + Income +
          -
        • My Day Job +
        • + Interest Income + +
            +
          • Bank Account Interest
          • + +
          • Certificate of Deposit
          • + +
          • Bond Interest
          • +
          +
        • + +
        • + Dividends + +
            +
          • Stock
          • + +
          • Mutual Fund
          • +
          +
        • + +
        • + Consulting + +
            +
          • ABC Design
          • + +
          • PQR Infomatics
          • +
          +
        • + +
        • + Salary + +
            +
          • My Day Job
          • +
          +
        • + +
        • + Commissions + +
            +
          • Royalties
          • +
          +
        -
      • Commissions +
      • + +
      • + Expenses +
          -
        • Royalties +
        • + Rent and Utilities + +
            +
          • Rent
          • + +
          • Rent Late Fees
          • + +
          • Electricity
          • + +
          • Gas
          • + +
          • Phone
          • + +
          • Internet
          • + +
          • Cable TV
          • +
          +
        • + +
        • + Office Expenses + +
            +
          • Accounting
          • + +
          • Legal
          • + +
          • Software
          • + +
          • Postage
          • + +
          • Bank Charges
          • + +
          • Credit Card Charges
          • + +
          • Toner, Paper, Paper Clips
          • +
          +
        • + +
        • + Auto Expenses + +
            +
          • Gas
          • + +
          • Insurance
          • + +
          • Repair
          • + +
          • Rental
          • +
          +
        • + +
        • + Taxes + +
            +
          • Social Security
          • + +
          • Unemployment
          • + +
          • IRS penalties
          • +
          +
        • + +
        • + Wages & Salaries + +
            +
          • Consulting
          • + +
          • Wages
          • + +
          • Health Insurance
          • +
          +
        • + +
        • + Travel + +
            +
          • Air
          • + +
          • Hotel
          • + +
          • Meals
          • + +
          • Auto
          • +
          +
        • + +
        • + Marketing + +
            +
          • Advertising
          • + +
          • Trade Shows
          • + +
          • Give Aways
          • +
          +
        +
      -
    • Expenses -
        -
      • Rent and Utilities -
          -
        • Rent -
        • Rent Late Fees -
        • Electricity -
        • Gas -
        • Phone -
        • Internet -
        • Cable TV -
        -
      • Office Expenses -
          -
        • Accounting -
        • Legal -
        • Software -
        • Postage -
        • Bank Charges -
        • Credit Card Charges -
        • Toner, Paper, Paper Clips -
        -
      • Auto Expenses -
          -
        • Gas -
        • Insurance -
        • Repair -
        • Rental -
        -
      • Taxes -
          -
        • Social Security -
        • Unemployment -
        • IRS penalties -
        -
      • Wages & Salaries -
          -
        • Consulting -
        • Wages -
        • Health Insurance -
        -
      • Travel -
          -
        • Air -
        • Hotel -
        • Meals -
        • Auto -
        -
      • Marketing -
          -
        • Advertising -
        • Trade Shows -
        • Give Aways -
        -
      +

      Return to Main Documentation + Page.

      +
    - - + diff --git a/Docs/En/xacc-main.html b/Docs/En/xacc-main.html index 148077399c..ef57df0490 100644 --- a/Docs/En/xacc-main.html +++ b/Docs/En/xacc-main.html @@ -1,37 +1,70 @@ - + + + + GnuCash Help Main Window - - - For help on a specific topic: + + +

    Main Help Overview

    + For help on a specific topic: +
    - + diff --git a/Docs/En/xacc-mainwin.html b/Docs/En/xacc-mainwin.html index 4dcc34b435..65df8372ec 100644 --- a/Docs/En/xacc-mainwin.html +++ b/Docs/En/xacc-mainwin.html @@ -1,41 +1,49 @@ - + + + + Main Window - - -

    THIS FILE IS ALMOST EMPTY

    - This is the main account window. Control accounts - from here. Add more documentation. -

    - Below is a picture of the main window, with only the - main accounts shown. Note how "ABC Bank" has been selected - by highlighting. To show the detail accounts, - click on the arrows on the left. -

    - -
    -

    - Here is the main window, with the detail accounts showing. -

    -
    - -
    + + +

    Main GnuCash Window

    + +

    This is the main account window. You control your set of + accounts from here.

    + +

    Below is a picture of the main window, with only the main + accounts shown. Note how ABC Bank has been selected by + highlighting. To show the detail accounts, click on the arrows + on the left.

    + +


    +

    Here is the main window, with the detail accounts + showing.

    + +


    +
    +


    - The "Open Subaccounts" menu item is interesting only if - you choose an account with sub-accounts (detail accounts). - Accounts with sub-accounts will always have arrows on thier - left. By choosing the "Open Subaccounts" menu item, a - general ledger window is opened, which displays all transactions - for the lead and the detail accounts. Note that the - general ledger window is more complicated and harder to use than - the individual account registers. The general ledger window - allows a more comprehensive overview of accounts in a smaller - space. Because of its increased complixity, it use is recommended - only for accounting experts. - + +

    The Open Subaccounts menu item is interesting only + if you choose an account with sub-accounts (detail accounts). + Accounts with sub-accounts will always have arrows on thier + left. By choosing the Open Subaccounts menu item, a + general ledger window is opened, which displays all + transactions for the lead and the detail accounts. Note that + the general ledger window is more complicated and harder to use + than the individual account registers. The general ledger + window allows a more comprehensive overview of accounts in a + smaller space. Because of its increased complexity, it use is + recommended only for accounting experts.

    + +

    Return to Main Documentation + Page.

    + diff --git a/Docs/En/xacc-print.html b/Docs/En/xacc-print.html index a1efdf95cf..e72dbaeb7a 100644 --- a/Docs/En/xacc-print.html +++ b/Docs/En/xacc-print.html @@ -1,25 +1,39 @@ - + + + + Printing and Web Serving - - -

    Printing and Web Serving

    - Currently, GnuCash does not support any sort of printer output. - However, you can create a printout of the register window contents - by opening a register window and selecting "Print... To File" from the - "File" menu. -

    - Following the latest fashionable trends, GnuCash can also act as a - cheesy web server! From an open register window, select - "Print ... To WWW" from the "File" menu. Then aim your browser at - http://localhost:1080/ or - http://yourmachinename.com:1080/and you will see your - register window. But, remember, we said "cheesy": to view the - page again, you have to pick the menu item again. -

    + + +

    Printing

    + +

    At present, GnuCash does not support any sort of printer + output. However, you may create a printout of the register + window contents by opening a register window and selecting + Print... To File from the "File" menu.

    + +

    GnuCash as Web Server

    + +

    Following the latest fashionable trends of Internet + Hype, GnuCash can also act as a cheesy web server! From an + open register window, select Print ... To WWW from the + File menu. Then aim your browser at + http://localhost:1080/ or + http://yourmachinename.com:1080/and you will see your + register window.

    + +

    But, remember, we said cheesy; in order to view + the page a second time, you have to pick the menu item + again to "reload" things and allow prepare it for another + data dump.


    - + +

    Return to Main Documentation + Page.

    + diff --git a/Docs/En/xacc-quicken.html b/Docs/En/xacc-quicken.html index ad8a05ef39..0daf35f938 100644 --- a/Docs/En/xacc-quicken.html +++ b/Docs/En/xacc-quicken.html @@ -1,64 +1,99 @@ - + + - Quicken User's Guide + + + A Guide for Former Users of Quicken - - -

    Quicken (TM) User's Guide

    - Not all accounting systems use the same words for the same - concepts. Below follows some notes for - users accustomed to Quicken products. -

    Categories

    - What Quicken calls "Categories" are really just - Income/Expense accounts. - Thus, if you are used to specifying a category in Quicken, - just create an income/expense account of the same name in - X-Accountant, and use that as a category. + +

    Quicken (TM) User's Guide

    -

    Quicken QIF File Import

    - X-Accountant supports the import of Quicken Import Files - (QIF). (Note: Only Quicken Version 3.0 QIF has been tested). - Due to the curious nature of QIF, please read this section - carefully, or you may be disappointed with the results. -

    - To create a Quicken QIF file, start Quicken (yes, it works - Wine, and probably Wabi too), choose the menu "File" - and select the "Export..." menu entry. Under Quicken - Version 3.0, you can only export one account at a time, - and thus, exporting will prove to be a tedious process. - When exporting, note that there are a series of check - boxes marked "Transactions", "Accounts List", "Category - List", etc. For best results, you will want to make - sure that these three are checked. If you do *not* - check the "Accounts List" box, then the name of your - account will be lost. -

    - To import a Quicken QIF file, choose the menu "File" - and select the entry "Import QIF". The imported wile - will be merged with whatever other data you currently - have in X-Accountant. This merge allows multiple - Quicken accounts to be imported and merged into - one account group. Note that during merge, a scan - is made for duplicate transactions, and duplicates - are removed. A duplicate transaction is one - where the date, description (payee), memo, quantity, share price, - and debited/credited accounts or categories match exactly. - Thus, the merge should be safe unless you have multiple - transactions on the same date, to the same account, - for the same amount, with the same description and memo. -

    - Note that when the "Accounts List" and "Category List" - is exported from Quicken, all accounts and - categories will be exported, even if they are empty, - and contain no transactions. When these are imported, - they will appear as accounts with a balance of zero. - If you do not need or use these accounts, delete them. - A future X-Accountant enhancement will allow you to - delete them en-masse, or to make them invisible without - deleting them. - +

    Not all accounting systems use the same words for the same + concepts. The following are some notes that may be helpful to + users accustomed to Intuit's products.

    +

    Categories

    + + +

    What Quicken calls Categories are really just Income/Expense accounts. Thus, if + you are used to specifying a category in Quicken, just create + an income/expense account of the same name in X-Accountant, and + use that as the name of the account.

    + +

    Quicken QIF File Import

    + X-Accountant supports the import of Quicken + Import Files (QIF). (Note: Only Quicken Version 3.0 QIF has + been tested). + +

    Note that the QIF format is representive of a somewhat + peculiar data model that is not as expressive as one might + wish; please read this section carefully, or you may be + disappointed with the results.

    + +

    To create a Quicken QIF file, start Quicken (yes, it works + on Wine, and probably + Wabi too), choose the menu "File" and select the + Export... menu entry. Quicken can only export one account + to each data file, which means that if you have many accounts, + this may prove to be a somewhat tedious process. (On the other + hand, it should be a whole lot less tedious than + entering the data from scratch.)

    + +

    When exporting, note that there are a series of check boxes + marked Transactions, Accounts List, + Category List, and so forth. For best results, you will + want to make sure that these three are checked. If you do + not check the Accounts List box, then the name of + your account will be lost.

    + +

    To import a Quicken QIF file, choose the menu File + and select the entry Import QIF. The imported file + will be merged with whatever other data you currently have in + X-Accountant. This merge allows multiple Quicken accounts to be + imported and merged into one account group.

    + +

    Note that during merge, a scan is made for duplicate + transactions, and duplicates are removed. A duplicate + transaction is one where the date, description (payee), memo, + quantity, share price, and debited/credited accounts or + categories match exactly. Thus, the merge should be + safe unless you have multiple transactions on the same date, to + the same account, for the same amount, with the same + description and memo.

    + +

    This unfortunately can occur, the typical + scenario involving where you make multiple withdrawals of + identical amounts from ATM machines on the same day.

    + +

    An ongoing project is to build an alternative import + utility in Guile that will be more flexible, and which, by + virtue of being stored as a set of Scheme scripts, may be + modified without needing to recompile the whole application. At + this point, it successfully parses QIF files; what remains is + the (rather complex task) of determine appropriate + correspondences between the Quicken Categories and the + GnuCash equivalents.

    + +

    Further details about the QIF Interchange Format may be + found + here.

    + +

    Note that when the Accounts List and Category + List is exported from Quicken, all accounts and + categories will be exported, even if they are empty and contain + no transactions. When these are imported, they will appear as + accounts with a balance of zero. If you do not plan to use such + accounts, feel free to delete them. Future enhancements may + allow you to delete them "en masse," or to make them invisible + without deleting them.

    + +

    Return to Main Documentation + Page.

    + diff --git a/Docs/En/xacc-recnwin.html b/Docs/En/xacc-recnwin.html index 5f0860937d..34774bcf58 100644 --- a/Docs/En/xacc-recnwin.html +++ b/Docs/En/xacc-recnwin.html @@ -1,46 +1,93 @@ - + + + + Reconcile Window - - -

    Reconcile Window

    - The Reconcile window is used to reconcile cleared transactins from - a bank statement. Enter a dollar amount from your last bank - statement in the box, and then click OK. The window will then show - all unreconciled transactions since your last bank statment. - The reconcile window looks like this: -

    -
    - -
    -

    - The Reconcile window is for reconciling the user's records at the - end of the month when the bank statement comes. For example, if - you write a check for something, enter the transaction. When you - know that the check has cleared, you can click the field between - the description and payment fields, and it changes from a 'n' to - a 'c', indicating the transaction has cleared. At the end of the - month, open the reconcile window, and xacc will prompt you to enter - the ending balance from the bank statement. Then the reconcile - window will pop-up, and you will see a credit and a debit column - that lists all the non-reconciled transactions. You can then - check off the transactions that appear in the bank statement, and - verify the amount fields are correct. At the bottom of the window - is a difference field, which should be $0.00 when you are done - reconciling. If it isn't then you missed a transaction, or one - of the amounts is wrong. When you press "Ok", then the 'n' or - 'c' in the transactions that were checked off will change to a - 'y'. When you change anything in a reconciled transaction, a - verify dialog box should pop-up, but this doesn't seem to be - happening anymore. Also, the "cleared" total at the bottom seems - to display $0.00 regardless of what transactions are cleared or - reconciled. The "cleared" total should display the total of only - the transactions that have been cleared ('c') or reconciled ('y'). + +

    Reconcile Window

    + The Reconcile window is used to reconcile cleared transactins + from a bank statement. Enter a dollar amount from your last + bank statement in the box, and then click OK . The + window will then show all unreconciled transactions since your + last bank statment. The reconcile window looks like this: + +


    +

    + +

    The Reconcile window is used to reconcile the user's + records at the end of the month. This involves validating the + transactions in GnuCash against the transactions indicated on + your bank statement.

    + +

    For example, when you write a check for something, you + enter the transaction into GnuCash.

    + +

    At the end of the month, you receive your bank statement, + perhaps including cancelled checks.

    + +
      +
    • Open the reconcile window, and GnuCash will prompt you to + enter the ending balance from the bank statement.
    • + +
    • Then the reconcile window will pop-up, and you will see a + credit and a debit column that lists all the non-reconciled + transactions.
      +
      +
    • + +
    • + Now, examine each item on the bank statement. + +

      If a check has cleared, as indicated by your bank + statement, you should click the field between the + description and payment fields, and it will then change + from n to c, indicating the transaction + has cleared.

      + +

      You then repeat this for each item that appears on the + bank statement, verifying that the amounts match with the + amounts in GnuCash, and marking off transactions in GnuCash + as they are reconciled.

      +
    • + +
    • At the bottom of the window is a difference field, which + should be $0.00 when you are done reconciling. If it isn't, + then you have either missed transactions, or some amounts may + be incorrect in GnuCash. (Or, probably rather less likely, + the bank may have made an error.)
      +
      +
    • + +
    • When you have completed marking off all the items on the + bank statement, and when the difference heads to $0.00, you + should press Ok, then the n or c + in the transactions that were checked off will change to a + y, indicating that they have been validated to agree + between the bank statement and your own records.
      +
      +
    • +
    + +

    When you change anything in a transaction that has been + reconciled, a dialog box warning you that such changes are + unwise should pop up, but this doesn't seem to be happening + anymore. Also, the "cleared" total at the bottom seems to + display $0.00 regardless of what transactions are + cleared or reconciled.

    + +

    The "cleared" total should display the total of only the + transactions that have been cleared (c) or reconciled + (y).


    - + +

    Return to Main Documentation + Page.

    + diff --git a/Docs/En/xacc-regwin.html b/Docs/En/xacc-regwin.html index 7eaabf8d08..0b198353f1 100644 --- a/Docs/En/xacc-regwin.html +++ b/Docs/En/xacc-regwin.html @@ -1,83 +1,104 @@ - + + + + Register Window - - + +

    Register Window

    This is the "Register" or the "Ledger" window. Enter - transactions here. Add more documentation to this page. + transactions here. Add more documentation to this page. -

    - This is what the register window should look like:

    +

    This is what the register window should look like:


    - -
    - -
    - +
    +
    +
    +

    Reconciliation

    + At the bottom of the account window, there are two running + balances, the "cleared & reconciled" balance, and the + "total" balance... the "cleared & reconciled" balance + should correspond to how much money the bank thinks you have in + your account, and the "total" balance includes outstanding + transactions. + +

    Reconciliation can be done in the Reconcile Window

    + +

    (Not yet implemented) Transactions marked with y + can not be edited without first changing this flag. This is + because a reconciled transaction should be thought of as + something that has been checked over, and therefore correct. + Changing such a transaction should be difficult: we want to + avoid accidental errors.

    + +

    (Not yet implemented) Transactions marked with f + are frozen, and cannot be edited under any circumstances. These + have been "frozen" into an accounting period. That is, the + books have been closed for this period; they may no longer be + modified.

    - At the bottom of the account window, there are two running balances, - the "cleared & reconciled" balance, and the "total" balance... the - "cleared & reconciled" balance should correspond to how much money - the bank thinks you have in your account, and the "total" balance - includes outstanding transactions. -

    - Reconciliation can be done in the - Reconcile Window -

    -(Not yet implemented) Transactions marked with 'y' can not be edited -without first changing this flag. This is because a reconciled -transaction should be thought of as something that has been checked -over, and therefore correct. Changing such a transaction should -be difficult: we want to avoid accidental errors. -

    -(Not yet implemented) Transactions marked with 'f' are frozen, -can cannot be edited under any circumstances. These have been -frozen into an accounting period. That is, the books have been closed -for this period; they can no longer be reopened for editing. -

    -

    Stock Portfolios

    - You can do stock transaction either from a single-stock window, - or from a portfolio-view window, shown below. -

    - -
    - -
    - The portfolio ledger can be a bit daunting at first sight. - If you have trouble understanding it, then stick to creating - accounts which have a single stock in them. - Some important points to remember about the portfolio window: -

      -
    • Its shows all stocks for your portfolio, not just some. - The share amounts shown are for each particular stock. -
    • Notice that it uses a two line display. Debited - accounts and debited amounts are on the upper line, and - credited amounts and accounts are on the lower line. -
    • If you buy or sell a stock with money from the brokerage - account, the total balance will not change, since the - value of the stock equals the amount of money exchanged. -
    • If you buy or sell a stock with money from the brokerage - account, the value of the transaction will appear twice, - once in red, and once in black. If shares are purchased, - the amount of money debited from the brokerage account will be - in red, and the value of the shares in black. If shares are - sold, then value of the shares is in red, and the money - credited to the brokerage account in black. -
    • If you are having trouble indicating a share purchase/sale - in the portfolio ledger, then make sure that "Transfer From" - and "Transfer To" accounts are in the right order. A - transfer from a stock account is always interpreted - as a sale, even if you entered the data as a purchase. - The vice-versa is also true. -
    • Someday, X-Accountant will be enhanced with a simplified - portfolio ledger window. +

      You can do stock transaction either from a single-stock + window, or from a portfolio-view window, shown below.

      + +


      +

      + +

      The portfolio ledger can be a bit daunting at first sight. + If you have trouble understanding it, then stick to creating + accounts which track a single stock. Some important points to + remember about the portfolio window:

      + +
        +
      • + It shows all stocks in your portfolio, not just + one. + +

        The share amounts shown are for each particular + stock.

        +
      • + +
      • + Note that it uses a two line display. + +

        Debited accounts and debited amounts are on the upper + line, and credited amounts and accounts are on the lower + line.

        +
      • + +
      • If you buy or sell a stock with money from the brokerage + account, the total balance will not change, since the value + of the stock equals the amount of money exchanged.
      • + +
      • If you buy or sell a stock with money from the brokerage + account, the value of the transaction will appear twice, once + in red, and once in black. If shares are purchased, the + amount of money debited from the brokerage account will be in + red, and the value of the shares in black. If shares are + sold, then value of the shares is in red, and the money + credited to the brokerage account in black.
      • + +
      • If you are having trouble indicating a share + purchase/sale in the portfolio ledger, then make sure that + "Transfer From" and "Transfer To" accounts are in the right + order. A transfer from a stock account is always interpreted + as a sale, even if you entered the data as a purchase. The + vice-versa is also true.
      • + +
      • Eventually, X-Accountant may be enhanced to provide a + simplified portfolio ledger window.
      + +

      Return to Main Documentation + Page.

      + diff --git a/Docs/En/xacc-reports.html b/Docs/En/xacc-reports.html index de5bfb9736..7ca5d9c33e 100644 --- a/Docs/En/xacc-reports.html +++ b/Docs/En/xacc-reports.html @@ -1,28 +1,42 @@ - + + + + Reports - - + +

      Reports

      - There are two types of reports: a Balance Sheet and - a Profit and Loss Statement. -

      - A Balance Sheet shows Assets, Liabilities and Equity. The sum of - all assets should equal the sum of Liabilities and Equity. -

      - A Profit and Loss Statement shows Income and Expenses. The - sum of all Income minus all expenses is the Profit or Loss. -

      - The change in Equity from day to day (year to year) should - equal that day's (year's) profit or loss. -

      + There are two types of reports: a Balance Sheet and a + Profit and Loss Statement. + +

      A Balance Sheet shows Assets, Liabilities and Equity. The + sum of all Assets equals the sum of Liabilities and Equity.

      + +

      A Profit and Loss Statement shows Income and Expenses. The sum of all + Income minus all expenses is the Profit or Loss, which is the + change in equity for the period in question.

      + +

      The change in Equity from day to day (year to year) + represents that day's (year's) profit or loss.

      + +

      Return to Main Documentation + Page.

      + diff --git a/Docs/En/xacc-ticker.html b/Docs/En/xacc-ticker.html index db67f0ccec..70cd5bacbe 100644 --- a/Docs/En/xacc-ticker.html +++ b/Docs/En/xacc-ticker.html @@ -1,68 +1,83 @@ - + + + + Stock Ticker - - -

      Stock Pricing and Ticker Symbols

      -

      - GnuCash currently provides rudimentary automated stcok quote - abilities. If an account is properly configured, and the - host computer is connected to the internet, the binary - gnc-prices can be used to load stock or mutual fund - price quotes from various web sites directly into GnuCash. -

      - To make use of this feature, the following must be done: -

        -
      • Create an account and mark it as being of type "Mutual Fund" - or "Stock". -
      • Enter a valid ticker symbol in the box marked "Security:" -
      • Select a quote source from the pull-down menu. Currently - supported quote sources include Yahoo, Fidelity - Investments, T. Rowe Price and the Vanguard - Group. Note that Yahoo will provide price quotes for - most mutual funds, including Fidelity, T.Rowe Price and - Vanguard, and that the quoted prices/NAV should be - identical to those on the source sites. -
      -

      - A sample image is shown below: -

      -
      - -

      - To update the prices stored in a gnucash account file, run the - command line command gnc-price, specifying the filename; - for example: + +

      Stock Pricing and Ticker Symbols

      + +

      GnuCash currently provides a somewhat rudimentary automated + stock quote gathering abilities. If an account is properly + configured, and the host computer is connected to the internet, + the binary gnc-prices may be used to load stock and + mutual fund price quotes from various web sites directly into + GnuCash.

      + +

      To make use of this facility, the following must be + done:

      + +
        +
      • Create an account and mark it as being of type "Mutual + Fund" or "Stock".
      • + +
      • Enter a valid ticker symbol in the box marked + "Security:"
      • + +
      • Select a quote source from the pull-down menu. Currently + supported quote sources include Yahoo, Fidelity + Investments, T. Rowe Price and the Vanguard + Group. Note that Yahoo will provide price quotes for many + mutual funds including Fidelity, T.Rowe Price and + Vanguard, and that the quoted prices/NAV should be identical + to those on the source sites.
      • +
      + +

      A sample image is shown below:

      +
      + + +

      To update the prices stored in a gnucash account file, run + the command line command gnc-price, specifying the + filename; for example:

           gnc-prices myaccts.xac
       
      - Running this command will print various diagnostic messages to - the screen while it loads data. It will work only when the host - computer is attached to the internet. It will work if the host - is behind a masq-style firewall. It does not currently work from - behind proxy or socks-style firewalls. The command can be run - many times in one day; however, it will update the accounts - at most once with the most recent trading days price data. Thus, - if gnc-prices is run on Friday, Saturday, Sunday and Monday, - only two price entires will be made: one containing Friday's - data, and one containing Monday's data, since the Saturday and - Sunday runs will only retreive the Friday closing price. - To keep gnc-prices from updating one account, while allowing it - to update another account, merely mark the data source for that - account as "(none)". You can do this from the "Edit Account" - window. -

      - After running gnc-prices for a few days, your accounts will - begin to resemble the following: -

      -
      - - +

      Running this command will print various diagnostic messages + to the screen while it loads data. It will work only when the + host computer is attached to the internet. It can + function in conjunction with masquerading-style firewalls, but + is not currently able to use proxy servers to get through proxy + or socks-style firewalls.

      + +

      The command can be run many times in one day; however, it + will update the accounts at most once with the most recent + trading days price data.

      + +

      Thus, if gnc-prices is run on Friday, Saturday, + Sunday and Monday, only two price entires will be made: one + containing Friday's data, and one containing Monday's data, + since the Saturday and Sunday runs will only retrieve the + Friday closing price.

      + +

      To keep gnc-prices from updating one account, while + allowing it to update another account, merely mark the data + source for that account as (none). You can do this + from the Edit Account window.

      + +

      After running gnc-prices for a few days, your + accounts will begin to resemble the following:

      +
      +
      - + +

      Return to Main Documentation + Page.

      + diff --git a/Docs/En/xacc-y2k.html b/Docs/En/xacc-y2k.html index 973599d126..1b21ebec4b 100644 --- a/Docs/En/xacc-y2k.html +++ b/Docs/En/xacc-y2k.html @@ -1,28 +1,47 @@ - + + + + GnuCash Y2K Readiness - +

      GnuCash Y2K Readiness

      - Gnucash version 1.1.25 and later store all dates as seconds and - nanoseconds, where the seconds are stored in a 64-bit signed integer. - This should suffice to store dates in the distant past as well as the - distant future, as long as they are less than several dozen times the - age of the universe. -

      - The file format for version 1.1.25 and later stores dates in the - above-described fashion. -

      - Some internal routines use the time_t type to express - seconds. Note that on most OS'es, this is a 32-bit quantity, and - is limited to the Unix era (Dec 1901 to Jan 2038). -

      - Backup and log files are time-stamped using the standard Unix - ctime() routine, which takes a time_t argument. - Thus, the backup and log mechanism may experience trouble in 2038. +

      Gnucash version 1.1.25 and later store all dates as seconds + and nanoseconds, where the seconds are stored in a 64-bit + signed integer. This should suffice to store dates in the + distant past as well as the distant future, as long as they are + less than several dozen times the present estimates of the age + of the universe.

      +

      The file format for version 1.1.25 and later stores dates + in the above-described fashion.

      + +

      Some internal routines use the time_t type to + express seconds. Note that on most OSes, this is a 32-bit + quantity, and is limited to the Unix epoch, roughly December + 1901 thru Jan 2038. It is reasonable to expect that this will + migrate to 64 bit values by that time.

      + +

      Backup and log files are time-stamped using the standard + Unix ctime() routine, which takes a time_t + argument. Thus, the backup and log mechanism may experience + trouble in 2038, assuming your Unix continues to be in service + at that time without remediation.

      + +

      Note that GnuCash also correctly recognizes February 29th, + 2000 as a "leap day," another of the "critical Y2K dates."

      + +

      Y2K issues are described in more detail at Linux and Year + 2000.

      + +

      Return to Main Documentation + Page.

      + diff --git a/Docs/Fr/tidy-up b/Docs/Fr/tidy-up new file mode 100755 index 0000000000..4e0a6da9d4 --- /dev/null +++ b/Docs/Fr/tidy-up @@ -0,0 +1,7 @@ +#!/bin/ksh +# $ID$ +# If you have Dave Raggett's "tidy" utility, this will tidy up +# the HTML files here. +for i in *.html ; do + tidy -m -i $i +donediff -u 'pristine/gnucash/Docs/Fr/xacc-about-fr.html' 'working/gnucash/Docs/Fr/xacc-about-fr.html' diff --git a/Docs/bofa-mym.html b/Docs/bofa-mym.html index 93991f0eef..61b85b9f7b 100644 --- a/Docs/bofa-mym.html +++ b/Docs/bofa-mym.html @@ -1,36 +1,41 @@ + Importing MYM Files -

      Managing Your Money --> GNUCash

      -I have finally put the Perl script that allowed me to use GNUCash up on a web -site. I had a couple years of data in Managing Your Money 2.x that I didn't -want to reenter. The script will output a single QIF file with all -transactions, accounts, and categories. (Currently only non-investment -transactions are handled.) The QIF file can be imported to xacc-1.0.18 if a -small patch is applied to QIFIO.c. The site is +

      Managing Your Money --> GNUCash

      -

      - http://www-cad.eecs.berkeley.edu/~gooch/mymdump.html +I have finally put the Perl script that allowed me to use GNUCash +up on a web site. I had a couple years of data in Managing Your +Money 2.x that I didn't want to reenter. The script will output a +single QIF file with all transactions, accounts, and categories. +(Currently only non-investment transactions are handled.) The QIF +file can be imported to xacc-1.0.18 if a small patch is applied to +QIFIO.c. The site is + +

      +MyMdump

      Duplicate Transactions

      -I also have a script that I use to remove duplicate transactions at the QIF -level. I use this script because Xacc is very strict about duplicates (which -is good) and because editing imported transactions will cause a re-import of -the same transactions to produce duplicates. (I download the same month's -transactions from my bank several times each month, so each downloaded QIF -file--after the first--contains transactions I have already imported. I -don't want to rely on the bank sending me the transactions in the same order or -with the same formatting.) The site is -

      - http://www-cad.eecs.berkeley.edu/~gooch/qifuniq.html -

      +I also have a script that I use to remove duplicate transactions at +the QIF level. I use this script because Xacc is very strict about +duplicates (which is good) and because editing imported +transactions will cause a re-import of the same transactions to +produce duplicates. (I download the same month's transactions from +my bank several times each month, so each downloaded QIF +file--after the first--contains transactions I have already +imported. I don't want to rely on the bank sending me the +transactions in the same order or with the same formatting.) The +site is -I hope others find these scripts useful. -

      -Ken Yamaguchi October 1998 +

      +qifuniq

      +

      I hope others find these scripts useful.

      + +

      Ken Yamaguchi October 1998

      + diff --git a/Docs/gnucash.css b/Docs/gnucash.css new file mode 100644 index 0000000000..148c57cfbc --- /dev/null +++ b/Docs/gnucash.css @@ -0,0 +1,35 @@ +/* Style Sheet */ +/* $Id$ */ + +BODY{ background-color:#EEFFEE; +/* background-image: url(http://www.isomedia.com/homes/tboyle/greenpaper.gif); */ + background-repeat: repeat-y;} + +SPAN.indent{margin-left:0.4in;} + +/* This may be a bit excessively loud... */ +H1, H2, H3, H4 {font-family: Optima, Lucida, Helvetica, sans-serif; + color: green; background-color: transparent; + font-weight: bolder; } + +/* Utopia, Helvetica, Optima, Lucida Typewriter */ + +H1{ font-size: 18pt;} +H2{ font-size: 16pt;} +H3{ font-size: 14pt;} +H4{ font-size: 12pt;} + +/* And make the main title big, centre it */ + +H1.title {font-size: 24pt; text-align: center; color: maroon; +background-color: transparent; font-family: Optima, Lucida, Helvetica, +sans-serif;} + +DEL {font-weight: bold} +INS {background-color: white} + +/* Zowie way of presenting PRE stuff */ + +PRE,TT.LITERAL,P.LITERALLAYOUT{ font-family:Lucida Typewriter, Courier, +monospace; font-weight: normal; background-color: gray; color: white; +border-width: thin; white-space:pre; } diff --git a/Makefile.in b/Makefile.in index e3958ab53b..883c4ba47b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -51,7 +51,7 @@ include @top_srcdir@/Makefile.common motif-static: motif.static gnome-static: gnome.static -build-flavor: +build-flavor: config.status @cd lib; $(MAKE) ${FLAVOR} @cd src; $(MAKE) ${FLAVOR} ln -sf gnucash.${FLAVOR} gnucash.bin @@ -64,17 +64,40 @@ motif.static: ${MAKE} FLAVOR=motif.static build-flavor gnome: - ${MAKE} FLAVOR=gnome build-flavor + ${MAKE} @GNOME_TARGET@ gnome.static: + ${MAKE} @GNOME_STATIC_TARGET@ + +gnome.real: + ${MAKE} FLAVOR=gnome build-flavor + +gnome.static.real: ${MAKE} FLAVOR=gnome.static build-flavor +#GNOME_TARGET if gnome build disabled due to missing libraries + +gnome.disable: + echo "Gnome build disabled - see configure.log for details" + false + qt: ${MAKE} FLAVOR=qt build-flavor depend: @echo make depend is now superfluous. +configure: configure.in + autoconf + +config.status: configure + if [ -f ./config.status ]; then \ + ./config.status --recheck; \ + else \ + echo "======> You need to run configure!"; \ + exit 1; \ + fi + CLEAN_SUBDIRS += lib src TRASH += TAGS *~ *.o *.bak @@ -155,6 +178,7 @@ install: $(INSTALL_DATA) Docs/*.gif ${GNC_SHAREDIR}/Docs # $(INSTALL_DATA) Docs/*.jpg ${GNC_SHAREDIR}/Docs $(INSTALL_DATA) Docs/*.xpm ${GNC_SHAREDIR}/Docs + $(INSTALL_DATA) Docs/*.css ${GNC_SHAREDIR}/Docs @mkdir -p ${GNC_SHAREDIR}/Docs/images # $(INSTALL_DATA) Docs/logos/*.* ${GNC_SHAREDIR}/Docs/images @mkdir -p ${GNC_SHAREDIR}/Docs/logos @@ -193,9 +217,6 @@ install: # ${INSTALL_DATA} $$file ${GNC_CONFIGDIR}/$$dest; \ # done -.PHONY: default install-private install motif motif-static gnome gnome-static qt +.PHONY: default install-private install +.PHONY: motif motif-static motif.static gnome gnome-static gnome.static qt .PHONY: depend dist clean distclean - -# Local Variables: -# tab-width: 2 -# End: diff --git a/README.guile-hackers b/README.guile-hackers index b01122d15c..6a6822cdb7 100644 --- a/README.guile-hackers +++ b/README.guile-hackers @@ -15,6 +15,53 @@ from the guile side. Given that and a reasonable understanding of GTK/GNOME, you should be able to follow what I've done. +Introduction To Scheme and guile(rgmerk) +-------------------------------- +Please skip this if you already know what Scheme is and why it's +so cool . . . + +Scheme is a dialect of LISP (List Programming), one of the earliest +programming languages. It makes so many things easy it's just not +funny. It can be a little confusing for people raised on C and Java, +but any time taken to learn it is made up for with easier-to-write, +easier-to-debug, more reusable, and more robust code. + +Guile is an implementation of standard Scheme which is easily +embeddable in C, making multi-language development relatively +straightforward. You can easily access data and procedures from +either end. Guile supports a superset of R4RS (the Scheme standard). +For initial experimentation, you can use Guile as an interactive Scheme +shell to play around with the system. + +SLIB is a a library for Scheme implementations (including guile) +that implements a large collection of useful data structures +and algorithms. + +FIXME: Starting gnucash as a guile shell. .. + +While the Guile documentation (in info format) explains +Guile specifics, it doesn't have much information about Scheme, +the language. The Internet Scheme Repository: + +http://www.cs.indiana.edu/scheme-repository/home.html + +has quite a useful collection of information, including +FAQs, online copies of the Scheme standard (which is actually +quite readable and useful), and pointers to web tutorials +and other resources. + +g-wrap +------ + +g-wrap is the tool used to automate the wrapping of C functions +to make them callable from the guile code. Gnucash installs +its own local copy of g-wrap. Documentation in info format +is available in gnucash/lib/g-wrap/doc/g-wrap.info. Available +C functions are wrapped in gnucash/src/g-wrap/gnc.gwp. Pointers +are wrapped using some stuff in gnucash/lib/g-wrap/guile/pointer.scm +which is not documented but looks reasonably straightforward. + + Garbage collection: ------------------- diff --git a/TODO b/TODO index 5f5b0e7d33..c571646517 100644 --- a/TODO +++ b/TODO @@ -82,6 +82,22 @@ Done, except for the percentage bit. 56) mnemonics don't work in the menus +57) fix auto single/double modes + +58) transactions cut/copy/paste + +59) stock split functionality + +60) account combobox needs to reflect account structure + +61) allocate columns widths better in gnome + +63) ability to reparent accounts + +64) ability to merge accounts + +65) better mousing on the gnome register + Fixed bugs: ----------- @@ -200,3 +216,4 @@ Non-memory-leak requires major redesign, and is in version 1.1 pull-down. Feb 98 fixed +62) refresh accounts in register combobox when accounts change diff --git a/configure b/configure index babacd9d55..7e1d573919 100755 --- a/configure +++ b/configure @@ -23,6 +23,8 @@ ac_help="$ac_help --with-perl=FILE which perl executable to use " ac_help="$ac_help --with-perl-includes=DIR specify where to look for perl includes" +ac_help="$ac_help + --with-eperl=FILE which eperl executable to use " ac_help="$ac_help --with-swig=FILE which swig executable to use " ac_help="$ac_help @@ -592,7 +594,7 @@ ac_configure=$ac_aux_dir/configure # This should be Cygnus configure. # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # ./install, which can be erroneously created by make from ./install.sh. echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6 -echo "configure:596: checking for a BSD compatible install" >&5 +echo "configure:598: checking for a BSD compatible install" >&5 if test -z "$INSTALL"; then if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -647,7 +649,7 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' # Extract the first word of "ranlib", so it can be a program name with args. set dummy ranlib; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:651: checking for $ac_word" >&5 +echo "configure:653: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -679,7 +681,7 @@ fi # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:683: checking for $ac_word" >&5 +echo "configure:685: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -709,7 +711,7 @@ if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:713: checking for $ac_word" >&5 +echo "configure:715: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -760,7 +762,7 @@ fi # Extract the first word of "cl", so it can be a program name with args. set dummy cl; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:764: checking for $ac_word" >&5 +echo "configure:766: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -792,7 +794,7 @@ fi fi echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6 -echo "configure:796: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5 +echo "configure:798: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5 ac_ext=c # CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. @@ -803,12 +805,12 @@ cross_compiling=$ac_cv_prog_cc_cross cat > conftest.$ac_ext << EOF -#line 807 "configure" +#line 809 "configure" #include "confdefs.h" main(){return(0);} EOF -if { (eval echo configure:812: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:814: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then ac_cv_prog_cc_works=yes # If we can't run a trivial program, we are probably using a cross compiler. if (./conftest; exit) 2>/dev/null; then @@ -834,12 +836,12 @@ if test $ac_cv_prog_cc_works = no; then { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; } fi echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6 -echo "configure:838: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5 +echo "configure:840: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5 echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6 cross_compiling=$ac_cv_prog_cc_cross echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6 -echo "configure:843: checking whether we are using GNU C" >&5 +echo "configure:845: checking whether we are using GNU C" >&5 if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -848,7 +850,7 @@ else yes; #endif EOF -if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:852: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then +if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:854: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then ac_cv_prog_gcc=yes else ac_cv_prog_gcc=no @@ -867,7 +869,7 @@ ac_test_CFLAGS="${CFLAGS+set}" ac_save_CFLAGS="$CFLAGS" CFLAGS= echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6 -echo "configure:871: checking whether ${CC-cc} accepts -g" >&5 +echo "configure:873: checking whether ${CC-cc} accepts -g" >&5 if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -900,7 +902,7 @@ fi echo $ac_n "checking for POSIXized ISC""... $ac_c" 1>&6 -echo "configure:904: checking for POSIXized ISC" >&5 +echo "configure:906: checking for POSIXized ISC" >&5 if test -d /etc/conf/kconfig.d && grep _POSIX_VERSION /usr/include/sys/unistd.h >/dev/null 2>&1 then @@ -921,14 +923,14 @@ else fi echo $ac_n "checking whether byte ordering is bigendian""... $ac_c" 1>&6 -echo "configure:925: checking whether byte ordering is bigendian" >&5 +echo "configure:927: checking whether byte ordering is bigendian" >&5 if eval "test \"`echo '$''{'ac_cv_c_bigendian'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else ac_cv_c_bigendian=unknown # See if sys/param.h defines the BYTE_ORDER macro. cat > conftest.$ac_ext < #include @@ -939,11 +941,11 @@ int main() { #endif ; return 0; } EOF -if { (eval echo configure:943: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then +if { (eval echo configure:945: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then rm -rf conftest* # It does; now see whether it defined to BIG_ENDIAN or not. cat > conftest.$ac_ext < #include @@ -954,7 +956,7 @@ int main() { #endif ; return 0; } EOF -if { (eval echo configure:958: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then +if { (eval echo configure:960: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then rm -rf conftest* ac_cv_c_bigendian=yes else @@ -974,7 +976,7 @@ if test "$cross_compiling" = yes; then { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } else cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null +if { (eval echo configure:993: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null then ac_cv_c_bigendian=no else @@ -1011,7 +1013,7 @@ EOF fi echo $ac_n "checking whether ${MAKE-make} sets \${MAKE}""... $ac_c" 1>&6 -echo "configure:1015: checking whether ${MAKE-make} sets \${MAKE}" >&5 +echo "configure:1017: checking whether ${MAKE-make} sets \${MAKE}" >&5 set dummy ${MAKE-make}; ac_make=`echo "$2" | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_prog_make_${ac_make}_set'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -1041,12 +1043,12 @@ fi for ac_func in stpcpy do echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 -echo "configure:1045: checking for $ac_func" >&5 +echo "configure:1047: checking for $ac_func" >&5 if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:1075: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_func_$ac_func=yes" else @@ -1179,7 +1181,7 @@ fi # Extract the first word of "perl", so it can be a program name with args. set dummy perl; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:1183: checking for $ac_word" >&5 +echo "configure:1185: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_path_PERL'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -1250,11 +1252,74 @@ if test ! -d ${PERLINCL}/CORE; then fi +# Check for eperl (rgmerk) +# Permits compilation without eperl, but if it can't find eperl you +# must either use the --with-eperl option or, at a last resort, +# it'll use /usr/bin/eperl. + +# Extract the first word of "eperl", so it can be a program name with args. +set dummy eperl; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1264: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_EPERL'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$EPERL" in + /*) + ac_cv_path_EPERL="$EPERL" # Let the user override the test with a path. + ;; + ?:/*) + ac_cv_path_EPERL="$EPERL" # Let the user override the test with a dos path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_EPERL="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_EPERL" && ac_cv_path_EPERL="no" + ;; +esac +fi +EPERL="$ac_cv_path_EPERL" +if test -n "$EPERL"; then + echo "$ac_t""$EPERL" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +# Check whether --with-eperl or --without-eperl was given. +if test "${with_eperl+set}" = set; then + withval="$with_eperl" + EPERL="${with_eperl}" +fi + + +if test x"$EPERL" = xno; then + echo "configure: warning: Can't find eperl. Try using the --with-eperl flag." 1>&2 + EPERL="/usr/bin/eperl" +fi + +# for some reason the code needs both the full path and the basename. +# these get dumped into src/guile/pathconfig.h + +EPERL_PATH="$EPERL" +EPERL_NAME=`basename $EPERL` + + + +### ------------------------------------------------------------------- + # Check for 'swig' # Extract the first word of "swig", so it can be a program name with args. set dummy swig; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:1258: checking for $ac_word" >&5 +echo "configure:1323: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_path_SWIG'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -1313,7 +1378,7 @@ then # Extract the first word of "gnome-config", so it can be a program name with args. set dummy gnome-config; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:1317: checking for $ac_word" >&5 +echo "configure:1382: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_path_GNOME_CONFIG_BIN'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -1359,7 +1424,7 @@ LIBS="$LIBS -lm" # important. echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6 -echo "configure:1363: checking how to run the C preprocessor" >&5 +echo "configure:1428: checking how to run the C preprocessor" >&5 # On Suns, sometimes $CPP names a directory. if test -n "$CPP" && test -d "$CPP"; then CPP= @@ -1374,13 +1439,13 @@ else # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. cat > conftest.$ac_ext < Syntax Error EOF ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" -{ (eval echo configure:1384: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +{ (eval echo configure:1449: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` if test -z "$ac_err"; then : @@ -1391,13 +1456,13 @@ else rm -rf conftest* CPP="${CC-cc} -E -traditional-cpp" cat > conftest.$ac_ext < Syntax Error EOF ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" -{ (eval echo configure:1401: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +{ (eval echo configure:1466: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` if test -z "$ac_err"; then : @@ -1408,13 +1473,13 @@ else rm -rf conftest* CPP="${CC-cc} -nologo -E" cat > conftest.$ac_ext < Syntax Error EOF ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" -{ (eval echo configure:1418: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +{ (eval echo configure:1483: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` if test -z "$ac_err"; then : @@ -1443,7 +1508,7 @@ echo "$ac_t""$CPP" 1>&6 # Uses ac_ vars as temps to allow command line to override cache and checks. # --without-x overrides everything else, but does not touch the cache. echo $ac_n "checking for X""... $ac_c" 1>&6 -echo "configure:1447: checking for X" >&5 +echo "configure:1512: checking for X" >&5 # Check whether --with-x or --without-x was given. if test "${with_x+set}" = set; then @@ -1505,12 +1570,12 @@ if test "$ac_x_includes" = NO; then # First, try using that file with no special directory specified. cat > conftest.$ac_ext < EOF ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" -{ (eval echo configure:1514: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +{ (eval echo configure:1579: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` if test -z "$ac_err"; then rm -rf conftest* @@ -1579,14 +1644,14 @@ if test "$ac_x_libraries" = NO; then ac_save_LIBS="$LIBS" LIBS="-l$x_direct_test_library $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:1655: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* LIBS="$ac_save_LIBS" # We can link X programs with no special library path. @@ -1692,17 +1757,17 @@ else case "`(uname -sr) 2>/dev/null`" in "SunOS 5"*) echo $ac_n "checking whether -R must be followed by a space""... $ac_c" 1>&6 -echo "configure:1696: checking whether -R must be followed by a space" >&5 +echo "configure:1761: checking whether -R must be followed by a space" >&5 ac_xsave_LIBS="$LIBS"; LIBS="$LIBS -R$x_libraries" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:1771: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* ac_R_nospace=yes else @@ -1718,14 +1783,14 @@ rm -f conftest* else LIBS="$ac_xsave_LIBS -R $x_libraries" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:1794: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* ac_R_space=yes else @@ -1757,7 +1822,7 @@ rm -f conftest* # libraries were built with DECnet support. And karl@cs.umb.edu says # the Alpha needs dnet_stub (dnet does not exist). echo $ac_n "checking for dnet_ntoa in -ldnet""... $ac_c" 1>&6 -echo "configure:1761: checking for dnet_ntoa in -ldnet" >&5 +echo "configure:1826: checking for dnet_ntoa in -ldnet" >&5 ac_lib_var=`echo dnet'_'dnet_ntoa | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -1765,7 +1830,7 @@ else ac_save_LIBS="$LIBS" LIBS="-ldnet $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:1845: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -1798,7 +1863,7 @@ fi if test $ac_cv_lib_dnet_dnet_ntoa = no; then echo $ac_n "checking for dnet_ntoa in -ldnet_stub""... $ac_c" 1>&6 -echo "configure:1802: checking for dnet_ntoa in -ldnet_stub" >&5 +echo "configure:1867: checking for dnet_ntoa in -ldnet_stub" >&5 ac_lib_var=`echo dnet_stub'_'dnet_ntoa | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -1806,7 +1871,7 @@ else ac_save_LIBS="$LIBS" LIBS="-ldnet_stub $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:1886: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -1846,12 +1911,12 @@ fi # The nsl library prevents programs from opening the X display # on Irix 5.2, according to dickey@clark.net. echo $ac_n "checking for gethostbyname""... $ac_c" 1>&6 -echo "configure:1850: checking for gethostbyname" >&5 +echo "configure:1915: checking for gethostbyname" >&5 if eval "test \"`echo '$''{'ac_cv_func_gethostbyname'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:1943: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_func_gethostbyname=yes" else @@ -1895,7 +1960,7 @@ fi if test $ac_cv_func_gethostbyname = no; then echo $ac_n "checking for gethostbyname in -lnsl""... $ac_c" 1>&6 -echo "configure:1899: checking for gethostbyname in -lnsl" >&5 +echo "configure:1964: checking for gethostbyname in -lnsl" >&5 ac_lib_var=`echo nsl'_'gethostbyname | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -1903,7 +1968,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lnsl $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:1983: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -1944,12 +2009,12 @@ fi # -lsocket must be given before -lnsl if both are needed. # We assume that if connect needs -lnsl, so does gethostbyname. echo $ac_n "checking for connect""... $ac_c" 1>&6 -echo "configure:1948: checking for connect" >&5 +echo "configure:2013: checking for connect" >&5 if eval "test \"`echo '$''{'ac_cv_func_connect'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2041: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_func_connect=yes" else @@ -1993,7 +2058,7 @@ fi if test $ac_cv_func_connect = no; then echo $ac_n "checking for connect in -lsocket""... $ac_c" 1>&6 -echo "configure:1997: checking for connect in -lsocket" >&5 +echo "configure:2062: checking for connect in -lsocket" >&5 ac_lib_var=`echo socket'_'connect | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2001,7 +2066,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lsocket $X_EXTRA_LIBS $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2081: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2036,12 +2101,12 @@ fi # gomez@mi.uni-erlangen.de says -lposix is necessary on A/UX. echo $ac_n "checking for remove""... $ac_c" 1>&6 -echo "configure:2040: checking for remove" >&5 +echo "configure:2105: checking for remove" >&5 if eval "test \"`echo '$''{'ac_cv_func_remove'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2133: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_func_remove=yes" else @@ -2085,7 +2150,7 @@ fi if test $ac_cv_func_remove = no; then echo $ac_n "checking for remove in -lposix""... $ac_c" 1>&6 -echo "configure:2089: checking for remove in -lposix" >&5 +echo "configure:2154: checking for remove in -lposix" >&5 ac_lib_var=`echo posix'_'remove | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2093,7 +2158,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lposix $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2173: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2128,12 +2193,12 @@ fi # BSDI BSD/OS 2.1 needs -lipc for XOpenDisplay. echo $ac_n "checking for shmat""... $ac_c" 1>&6 -echo "configure:2132: checking for shmat" >&5 +echo "configure:2197: checking for shmat" >&5 if eval "test \"`echo '$''{'ac_cv_func_shmat'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2225: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_func_shmat=yes" else @@ -2177,7 +2242,7 @@ fi if test $ac_cv_func_shmat = no; then echo $ac_n "checking for shmat in -lipc""... $ac_c" 1>&6 -echo "configure:2181: checking for shmat in -lipc" >&5 +echo "configure:2246: checking for shmat in -lipc" >&5 ac_lib_var=`echo ipc'_'shmat | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2185,7 +2250,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lipc $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2265: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2229,7 +2294,7 @@ fi # libraries we check for below, so use a different variable. # --interran@uluru.Stanford.EDU, kb@cs.umb.edu. echo $ac_n "checking for IceConnectionNumber in -lICE""... $ac_c" 1>&6 -echo "configure:2233: checking for IceConnectionNumber in -lICE" >&5 +echo "configure:2298: checking for IceConnectionNumber in -lICE" >&5 ac_lib_var=`echo ICE'_'IceConnectionNumber | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2237,7 +2302,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lICE $X_EXTRA_LIBS $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2317: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2370,7 +2435,7 @@ fi # the XmHTML widget needs libz, libjpeg, libpng and libm # it also uses #ifdef's not #if's so DONT #def to zero. echo $ac_n "checking for deflateEnd in -lz""... $ac_c" 1>&6 -echo "configure:2374: checking for deflateEnd in -lz" >&5 +echo "configure:2439: checking for deflateEnd in -lz" >&5 ac_lib_var=`echo z'_'deflateEnd | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2378,7 +2443,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lz $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2458: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2413,7 +2478,7 @@ else fi echo $ac_n "checking for jpeg_read_scanlines in -ljpeg""... $ac_c" 1>&6 -echo "configure:2417: checking for jpeg_read_scanlines in -ljpeg" >&5 +echo "configure:2482: checking for jpeg_read_scanlines in -ljpeg" >&5 ac_lib_var=`echo jpeg'_'jpeg_read_scanlines | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2421,7 +2486,7 @@ else ac_save_LIBS="$LIBS" LIBS="-ljpeg $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2501: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2456,7 +2521,7 @@ else fi echo $ac_n "checking for png_read_image in -lpng""... $ac_c" 1>&6 -echo "configure:2460: checking for png_read_image in -lpng" >&5 +echo "configure:2525: checking for png_read_image in -lpng" >&5 ac_lib_var=`echo png'_'png_read_image | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2464,7 +2529,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lpng $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2544: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2505,7 +2570,7 @@ fi # LIBS="-lXmu -lXt -lXext $X_PRE_LIBS -lX11 $X_LIBS $X_EXTRA_LIBS $LIBS" echo $ac_n "checking for XpmReadFileToXpmImage in -lXpm""... $ac_c" 1>&6 -echo "configure:2509: checking for XpmReadFileToXpmImage in -lXpm" >&5 +echo "configure:2574: checking for XpmReadFileToXpmImage in -lXpm" >&5 ac_lib_var=`echo Xpm'_'XpmReadFileToXpmImage | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2513,7 +2578,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lXpm $X_PRE_LIBS -lX11 $X_LIBS $X_EXTRA_LIBS $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2593: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2555,7 +2620,7 @@ fi # Don't build the xmhtml source if user already has it installed... # this is ugly, there must be a nicer way of setting this up ... echo $ac_n "checking for XmHTMLTextScrollToLine in -lXmHTML""... $ac_c" 1>&6 -echo "configure:2559: checking for XmHTMLTextScrollToLine in -lXmHTML" >&5 +echo "configure:2624: checking for XmHTMLTextScrollToLine in -lXmHTML" >&5 ac_lib_var=`echo XmHTML'_'XmHTMLTextScrollToLine | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2563,7 +2628,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lXmHTML $X_PRE_LIBS $MOTIF_LIBS $X_EXTRA_LIBS $X_LIBS $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2643: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2598,6 +2663,110 @@ fi +### ------------------------------------------------------------------------- +## gtk-xmhtml +# if gtk-xmhtml header or library not found, gnome builds are disabled +# make gnome will run make gnome.disabled which echoes a warning message + +## XXX - need to do this because gtk-xmhtml requires glibconfig.h +## and I don't want CPPFLAGS permanently altered. +## If there's a better way to tackle this please let me know! + +OLDCPPFLAGS="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS `$GNOME_CONFIG_BIN --cflags glib`" + + +# gnome targets if all goes well +# XXX - should we shift these to the rest of the gnome configuration +# section? + +GNOME_TARGET="gnome.real" +GNOME_STATIC_TARGET="gnome.static.real" + +#AC_CHECK_HEADER might work, but I'm not sure it uses CPPFLAGS +# this guarantees it - it works. Promise!! +echo $ac_n "checking gtk-xmhtml/gtk-xmhtml.h""... $ac_c" 1>&6 +echo "configure:2690: checking gtk-xmhtml/gtk-xmhtml.h" >&5 +cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:2697: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + echo "$ac_t""yes" 1>&6 +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + echo "configure: warning: Cannot find gtk-xmhtml.h -- gnome build disabled (not required for motif)" 1>&2 + GNOME_TARGET="gnome.disabled" + GNOME_STATIC_TARGET="gnome.disabled" +fi +rm -f conftest* + +#undo damage to CPPFLAGS +CPPFLAGS="$OLDCPPFLAGS" + +OLDLDFLAGS="$LDFLAGS" +LDFLAGS="$LDFLAGS `$GNOME_CONFIG_BIN --libs gtkxmhtml`" + +#check for gtkxmhtml, export library link to variable GTK_XMHTML +echo $ac_n "checking for gtk_xmhtml_new in -lgtkxmhtml""... $ac_c" 1>&6 +echo "configure:2721: checking for gtk_xmhtml_new in -lgtkxmhtml" >&5 +ac_lib_var=`echo gtkxmhtml'_'gtk_xmhtml_new | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lgtkxmhtml $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + GTK_XMHTML="gtkxmhtml" +else + echo "$ac_t""no" 1>&6 +echo "configure: warning: Cannotfind libgtkxmhtml -- gnome build disabled (not required for motif)" 1>&2 + GNOME_TARGET="gnome.disabled" + GNOME_STATIC_TARGET="gnome.disabled" +fi + + +LDFLAGS="$OLDLDFLAGS" + +# XXX - should we export these here or later in the configure script? + + + + ### -------------------------------------------------------------------------- ## Nana # XXX - There should probably be a --without-nana option (e.g., for @@ -2607,17 +2776,17 @@ fi # See if the header file can be found. ac_safe=`echo "nana.h" | sed 'y%./+-%__p_%'` echo $ac_n "checking for nana.h""... $ac_c" 1>&6 -echo "configure:2611: checking for nana.h" >&5 +echo "configure:2780: checking for nana.h" >&5 if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else cat > conftest.$ac_ext < EOF ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" -{ (eval echo configure:2621: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +{ (eval echo configure:2790: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` if test -z "$ac_err"; then rm -rf conftest* @@ -2640,7 +2809,7 @@ else fi echo $ac_n "checking for L_buffer_create in -lnana""... $ac_c" 1>&6 -echo "configure:2644: checking for L_buffer_create in -lnana" >&5 +echo "configure:2813: checking for L_buffer_create in -lnana" >&5 ac_lib_var=`echo nana'_'L_buffer_create | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2648,7 +2817,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lnana $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2832: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2692,7 +2861,7 @@ fi ### -------------------------------------------------------------------------- # If readline exists, just assume that guile needs it. It probably does. echo $ac_n "checking for readline in -lreadline""... $ac_c" 1>&6 -echo "configure:2696: checking for readline in -lreadline" >&5 +echo "configure:2865: checking for readline in -lreadline" >&5 ac_lib_var=`echo readline'_'readline | sed 'y%./+-%__p_%'` if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 @@ -2700,7 +2869,7 @@ else ac_save_LIBS="$LIBS" LIBS="-lreadline $LIBS" cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:2884: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* eval "ac_cv_lib_$ac_lib_var=yes" else @@ -2757,7 +2926,7 @@ fi if test "${with_guile_config+set}" = set; then withval="$with_guile_config" echo $ac_n "checking for guile-config""... $ac_c" 1>&6 -echo "configure:2761: checking for guile-config" >&5 +echo "configure:2930: checking for guile-config" >&5 echo "$ac_t""${with_guile_config}" 1>&6 GUILE_CONFIG="$with_guile_config" LIBS="`${GUILE_CONFIG} link` ${LIBS}" @@ -2770,7 +2939,7 @@ if test x"$GUILE_CONFIG" = x; then # Extract the first word of "guile-config", so it can be a program name with args. set dummy guile-config; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:2774: checking for $ac_word" >&5 +echo "configure:2943: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_path_GUILE_CONFIG'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -2826,7 +2995,7 @@ if test x"$GUILE_CONFIG" = x; then GNC_LIBS_SAFE=${LIBS} echo $ac_n "checking for guile""... $ac_c" 1>&6 -echo "configure:2830: checking for guile" >&5 +echo "configure:2999: checking for guile" >&5 if eval "test \"`echo '$''{'ac_cv_lib_guile'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -2839,14 +3008,14 @@ else else LIBS="${GNC_TEST_LIBS} ${GNC_LIBS_SAFE}" cat > conftest.$ac_ext < int main() { gh_eval_file; ; return 0; } EOF -if { (eval echo configure:2850: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then +if { (eval echo configure:3019: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then rm -rf conftest* GUILELIBS="${GNC_TEST_LIBS}" else @@ -2873,7 +3042,7 @@ if test x"$GUILE_CONFIG" != x; then # Extract the first word of "guile", so it can be a program name with args. set dummy guile; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:2877: checking for $ac_word" >&5 +echo "configure:3046: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_path_GUILE_BIN'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -2912,7 +3081,7 @@ if test x"$GUILE_BIN" = x && test x"$with_guile" != x; then # Extract the first word of "guile", so it can be a program name with args. set dummy guile; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:2916: checking for $ac_word" >&5 +echo "configure:3085: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_path_GUILE_BIN'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -2951,7 +3120,7 @@ if test x"${GUILE_BIN}" = x; then # Extract the first word of "guile", so it can be a program name with args. set dummy guile; ac_word=$2 echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 -echo "configure:2955: checking for $ac_word" >&5 +echo "configure:3124: checking for $ac_word" >&5 if eval "test \"`echo '$''{'ac_cv_path_GUILE_BIN'+set}'`\" = set"; then echo $ac_n "(cached) $ac_c" 1>&6 else @@ -3043,6 +3212,14 @@ fi + +# HACK : inserts the path to gnucash.pm, which is used in the +# reporting code and is defined in gnucash.h + +GNC_RUNTIME_PERLLIBPATH=`eval echo ${GNC_LIBDIR}` + + + # We have to handle these here because they are the *runtime* (not install # time) defaults, and they're substituted into bootstrap.scm and gnucash.h # directly, so: @@ -3179,6 +3356,7 @@ trap 'rm -fr `echo "Makefile src/register/Makefile src/register/gnome/Makefile src/reports/Makefile + src/reports/pathconfig.h src/swig/Makefile src/swig/perl5/Makefile lib/Makefile @@ -3225,6 +3403,9 @@ s%@SET_MAKE@%$SET_MAKE%g s%@OPT_STYLE_INSTALL@%$OPT_STYLE_INSTALL%g s%@PERL@%$PERL%g s%@PERLINCL@%$PERLINCL%g +s%@EPERL@%$EPERL%g +s%@EPERL_PATH@%$EPERL_PATH%g +s%@EPERL_NAME@%$EPERL_NAME%g s%@SWIG@%$SWIG%g s%@GNOME_CONFIG_BIN@%$GNOME_CONFIG_BIN%g s%@CPP@%$CPP%g @@ -3234,6 +3415,9 @@ s%@X_LIBS@%$X_LIBS%g s%@X_EXTRA_LIBS@%$X_EXTRA_LIBS%g s%@XMHTML_TARGET@%$XMHTML_TARGET%g s%@XMHTML_INC@%$XMHTML_INC%g +s%@GTK_XMHTML@%$GTK_XMHTML%g +s%@GNOME_TARGET@%$GNOME_TARGET%g +s%@GNOME_STATIC_TARGET@%$GNOME_STATIC_TARGET%g s%@GUILE_CONFIG@%$GUILE_CONFIG%g s%@GUILE_BIN@%$GUILE_BIN%g s%@GUILE_INC@%$GUILE_INC%g @@ -3244,6 +3428,7 @@ s%@GNC_BINDIR@%$GNC_BINDIR%g s%@GNC_LIBDIR@%$GNC_LIBDIR%g s%@GNC_CONFIGDIR@%$GNC_CONFIGDIR%g s%@GNC_SHAREDIR@%$GNC_SHAREDIR%g +s%@GNC_RUNTIME_PERLLIBPATH@%$GNC_RUNTIME_PERLLIBPATH%g s%@GNC_RUNTIME_SHAREDIR@%$GNC_RUNTIME_SHAREDIR%g s%@GNC_RUNTIME_CONFIGDIR@%$GNC_RUNTIME_CONFIGDIR%g s%@ABSOLUTE_TOP_SRCDIR@%$ABSOLUTE_TOP_SRCDIR%g @@ -3301,6 +3486,7 @@ CONFIG_FILES=\${CONFIG_FILES-"Makefile src/register/Makefile src/register/gnome/Makefile src/reports/Makefile + src/reports/pathconfig.h src/swig/Makefile src/swig/perl5/Makefile lib/Makefile @@ -3487,6 +3673,6 @@ test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1 chmod +x gnucash -make -f Makefile.config.finish prefix=${prefix} \ - GNC_SHAREDIR=${GNC_SHAREDIR} \ - GNC_CONFIGDIR=${GNC_SHAREDIR} +${MAKE-make} -f Makefile.config.finish prefix=${prefix} \ + GNC_SHAREDIR=${GNC_SHAREDIR} \ + GNC_CONFIGDIR=${GNC_SHAREDIR} diff --git a/configure.in b/configure.in index 0ed64cfa2e..ff5036db06 100644 --- a/configure.in +++ b/configure.in @@ -108,6 +108,31 @@ if test ! -d ${PERLINCL}/CORE; then fi AC_SUBST(PERLINCL) +# Check for eperl (rgmerk) +# Permits compilation without eperl, but if it can't find eperl you +# must either use the --with-eperl option or, at a last resort, +# it'll use /usr/bin/eperl. + +AC_PATH_PROG(EPERL,eperl,no) +AC_ARG_WITH(eperl, + [ --with-eperl=FILE which eperl executable to use ], + EPERL="${with_eperl}") + +if test x"$EPERL" = xno; then + AC_MSG_WARN([Can't find eperl. Try using the --with-eperl flag.]) + EPERL="/usr/bin/eperl" +fi + +# for some reason the code needs both the full path and the basename. +# these get dumped into src/guile/pathconfig.h + +EPERL_PATH="$EPERL" +EPERL_NAME=`basename $EPERL` +AC_SUBST(EPERL_PATH) +AC_SUBST(EPERL_NAME) + +### ------------------------------------------------------------------- + # Check for 'swig' AC_PATH_PROG(SWIG,swig,no) # Sets @SWIG@ AC_ARG_WITH(swig, @@ -237,6 +262,54 @@ AC_CHECK_LIB(XmHTML, XmHTMLTextScrollToLine, AC_SUBST(XMHTML_TARGET) AC_SUBST(XMHTML_INC) +### ------------------------------------------------------------------------- +## gtk-xmhtml +# if gtk-xmhtml header or library not found, gnome builds are disabled +# make gnome will run make gnome.disabled which echoes a warning message + +## XXX - need to do this because gtk-xmhtml requires glibconfig.h +## and I don't want CPPFLAGS permanently altered. +## If there's a better way to tackle this please let me know! + +OLDCPPFLAGS="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS `$GNOME_CONFIG_BIN --cflags glib`" + + +# gnome targets if all goes well +# XXX - should we shift these to the rest of the gnome configuration +# section? + +GNOME_TARGET="gnome.real" +GNOME_STATIC_TARGET="gnome.static.real" + +#AC_CHECK_HEADER might work, but I'm not sure it uses CPPFLAGS +# this guarantees it - it works. Promise!! +AC_MSG_CHECKING([gtk-xmhtml/gtk-xmhtml.h]) +AC_TRY_CPP([#include ], AC_MSG_RESULT(yes) , + AC_MSG_WARN([Cannot find gtk-xmhtml.h -- gnome build disabled (not required for motif)]) + GNOME_TARGET="gnome.disabled" + GNOME_STATIC_TARGET="gnome.disabled") + +#undo damage to CPPFLAGS +CPPFLAGS="$OLDCPPFLAGS" + +OLDLDFLAGS="$LDFLAGS" +LDFLAGS="$LDFLAGS `$GNOME_CONFIG_BIN --libs gtkxmhtml`" + +#check for gtkxmhtml, export library link to variable GTK_XMHTML +AC_CHECK_LIB(gtkxmhtml, gtk_xmhtml_new, + GTK_XMHTML="gtkxmhtml", + AC_MSG_WARN([Cannotfind libgtkxmhtml -- gnome build disabled (not required for motif)]) + GNOME_TARGET="gnome.disabled" + GNOME_STATIC_TARGET="gnome.disabled") +AC_SUBST(GTK_XMHTML) +LDFLAGS="$OLDLDFLAGS" + +# XXX - should we export these here or later in the configure script? +AC_SUBST(GNOME_TARGET) +AC_SUBST(GNOME_STATIC_TARGET) + + ### -------------------------------------------------------------------------- ## Nana # XXX - There should probably be a --without-nana option (e.g., for @@ -394,6 +467,14 @@ AC_SUBST(GNC_LIBDIR) AC_SUBST(GNC_CONFIGDIR) AC_SUBST(GNC_SHAREDIR) + +# HACK : inserts the path to gnucash.pm, which is used in the +# reporting code and is defined in gnucash.h + +GNC_RUNTIME_PERLLIBPATH=`eval echo ${GNC_LIBDIR}` + +AC_SUBST(GNC_RUNTIME_PERLLIBPATH) + # We have to handle these here because they are the *runtime* (not install # time) defaults, and they're substituted into bootstrap.scm and gnucash.h # directly, so: @@ -429,6 +510,7 @@ AC_OUTPUT(Makefile src/register/Makefile src/register/gnome/Makefile src/reports/Makefile + src/reports/pathconfig.h src/swig/Makefile src/swig/perl5/Makefile lib/Makefile @@ -438,6 +520,6 @@ AC_OUTPUT(Makefile chmod +x gnucash -make -f Makefile.config.finish prefix=${prefix} \ - GNC_SHAREDIR=${GNC_SHAREDIR} \ - GNC_CONFIGDIR=${GNC_SHAREDIR} +${MAKE-make} -f Makefile.config.finish prefix=${prefix} \ + GNC_SHAREDIR=${GNC_SHAREDIR} \ + GNC_CONFIGDIR=${GNC_SHAREDIR} diff --git a/lib/Makefile.in b/lib/Makefile.in index d4369b9746..8e4ab0053f 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -69,13 +69,13 @@ dist: chmod +x g-wrap/missing -motif: g-wrap-install +motif motif.static: g-wrap-install @cd ComboBox-1.33; $(MAKE) default @cd Xbae-4.6.2-linas; $(MAKE) default -gnome: g-wrap-install +gnome gnome.static: g-wrap-install -qt: g-wrap-install +qt: g-wrap-install -.PHONY: all gnome motif qt dist +.PHONY: all gnome gnome.static motif motif.static qt dist diff --git a/src/AccWindow.h b/src/AccWindow.h index 84f7df50f3..689879b81d 100644 --- a/src/AccWindow.h +++ b/src/AccWindow.h @@ -49,4 +49,5 @@ EditNotesWindow * editNotesWindow (Account *acc); void xaccDestroyEditAccWindow (Account *); void xaccDestroyEditNotesWindow (Account *); +void xaccSetDefaultNewaccountCurrency(char *new_default_currency); #endif /* __XACC_NEWACCWINDOW_H__ */ diff --git a/src/Makefile.in b/src/Makefile.in index d8f75ce5ef..af782d02a8 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -52,23 +52,23 @@ include @top_srcdir@/Makefile.common default: $(OBJS) -motif: ${MOTIF_OBJS} +motif motif.static: ${MOTIF_OBJS} @cd engine; $(MAKE) default @cd register; $(MAKE) motif @cd reports; $(MAKE) default @cd g-wrap; $(MAKE) motif @cd swig; $(MAKE) motif @cd guile; $(MAKE) default - @cd motif; $(MAKE) motif + @cd motif; $(MAKE) $@ -gnome: ${GNOME_OBJS} +gnome gnome.static: ${GNOME_OBJS} @cd engine; $(MAKE) default @cd register; $(MAKE) gnome @cd reports; $(MAKE) default @cd g-wrap; $(MAKE) gnome @cd swig; $(MAKE) gnome @cd guile; $(MAKE) default - @cd gnome; $(MAKE) gnome + @cd gnome; $(MAKE) $@ qt: $(QT_OBJS) @cd engine; $(MAKE) default @@ -79,8 +79,4 @@ qt: $(QT_OBJS) @cd guile; $(MAKE) default @cd qt; $(MAKE) qt -.PHONY: default qt gnome motif all - -# Local Variables: -# tab-width: 2 -# End: +.PHONY: default qt gnome gnome.static motif motif.static all diff --git a/src/MultiLedger.c b/src/MultiLedger.c index a8f4e3d5cb..9feed13339 100644 --- a/src/MultiLedger.c +++ b/src/MultiLedger.c @@ -340,7 +340,9 @@ xaccLedgerDisplayGeneral (Account *lead_acc, Account **acclist, int ledger_type) /* set up the query filter */ regData->query = xaccMallocQuery(); xaccQuerySetAccounts (regData->query, regData->displayed_accounts); - xaccQueryAddAccount (regData->query, regData->leader); + if ((regData->leader != NULL) && + !accListHasAccount(regData->displayed_accounts, regData->leader)) + xaccQueryAddAccount (regData->query, regData->leader); /* by default, display only thirty transactions */ xaccQuerySetMaxSplits (regData->query, MAX_QUERY_SPLITS); @@ -352,10 +354,10 @@ xaccLedgerDisplayGeneral (Account *lead_acc, Account **acclist, int ledger_type) * The main register window itself * \******************************************************************/ - /* MallocBasicRegister will malloc & initialize the register, + /* xaccMallocSplitRegister will malloc & initialize the register, * but will not do the gui init */ regData->ledger = xaccMallocSplitRegister (ledger_type); - + regData->dirty = 1; xaccLedgerDisplayRefresh (regData); @@ -414,7 +416,26 @@ xaccLedgerDisplayRefresh (xaccLedgerDisplay *regData) if (regData->redraw) { (regData->redraw) (regData); } +} +/********************************************************************\ + * refresh all the register windows, but only with the gui callback * +\********************************************************************/ + +void +xaccRegisterRefreshAllGUI (void) +{ + xaccLedgerDisplay *regData; + int n; + + if (!fullList) return; + + n = 0; regData = fullList[n]; + while (regData) { + if (regData->redraw) + (regData->redraw) (regData); + n++; regData = fullList[n]; + } } /********************************************************************\ @@ -614,9 +635,7 @@ xaccDestroyLedgerDisplay (Account *acc) * frees memory allocated for an regWindow, and other cleanup * * stuff * * * - * Args: mw - the widget that called us * - * cd - regData - the data struct for this register * - * cb - * + * Args: regData - ledger display structure * * Return: none * \********************************************************************/ void @@ -628,13 +647,11 @@ xaccLedgerDisplayClose (xaccLedgerDisplay *regData) acc = regData->leader; /* Save any unsaved changes */ - xaccSRSaveRegEntry (regData->ledger); - - /* refresh the register windows if there were changes */ - xaccSRRedrawRegEntry (regData->ledger); + if (xaccSRSaveRegEntry (regData->ledger, NULL)) + xaccSRRedrawRegEntry (regData->ledger); xaccDestroySplitRegister (regData->ledger); - + /* whether this is a single or multi-account window, remove it */ REMOVE_FROM_LIST (xaccLedgerDisplay, regList, acc, leader); REMOVE_FROM_LIST (xaccLedgerDisplay, ledgerList, acc, leader); diff --git a/src/MultiLedger.h b/src/MultiLedger.h index c6bc707053..71ae2e2f65 100644 --- a/src/MultiLedger.h +++ b/src/MultiLedger.h @@ -79,53 +79,66 @@ struct _xaccLedgerDisplay { /* * opens up a register window to display a single account */ -extern xaccLedgerDisplay * xaccLedgerDisplaySimple (Account *acc); +xaccLedgerDisplay * xaccLedgerDisplaySimple (Account *acc); /* * opens up a register window to display the parent account * and all of its children. */ -extern xaccLedgerDisplay * xaccLedgerDisplayAccGroup (Account *acc); +xaccLedgerDisplay * xaccLedgerDisplayAccGroup (Account *acc); /* * display list of accounts in a general ledger. */ -extern xaccLedgerDisplay * xaccLedgerDisplayGeneral - (Account *lead_acc, Account **acclist, int ledger_type); +xaccLedgerDisplay * xaccLedgerDisplayGeneral (Account *lead_acc, + Account **acclist, + int ledger_type); /* * redisplay/redraw all windows that contain any transactions * that are associated with the indicated account. */ -extern void xaccAccountDisplayRefresh (Account *acc); -extern void xaccAccListDisplayRefresh (Account **acc); +void xaccAccountDisplayRefresh (Account *acc); +void xaccAccListDisplayRefresh (Account **acc); /* * redisplay/redraw all windows that contain this transaction * (or any of its member splits). */ -extern void xaccTransDisplayRefresh (Transaction *trans); +void xaccTransDisplayRefresh (Transaction *trans); /* * redisplay/redraw only the indicated window. * both routines do same thing, they differ only by the argument they * take. */ -extern void xaccLedgerDisplayRefresh (xaccLedgerDisplay *); -extern void xaccRegisterRefresh (SplitRegister *); +void xaccLedgerDisplayRefresh (xaccLedgerDisplay *); +void xaccRegisterRefresh (SplitRegister *); + +/* + * Call the user refresh callback for all registers. This does not + * perform a full refresh, i.e., it does not reload transactions. + * This is just for updating gui controls. + */ +void xaccRegisterRefreshAllGUI (void); + +/* + * return true if acc is a member of the ledger. + */ +int ledgerIsMember (xaccLedgerDisplay *reg, Account * acc); /* * close the window */ -extern void xaccLedgerDisplayClose (xaccLedgerDisplay *); +void xaccLedgerDisplayClose (xaccLedgerDisplay *); /********************************************************************\ * sort of a quick hack involving the layout of the register. \********************************************************************/ -extern void xaccRegisterCountHack (SplitRegister *splitreg); +void xaccRegisterCountHack (SplitRegister *splitreg); -extern void xaccDestroyLedgerDisplay (Account *acc); +void xaccDestroyLedgerDisplay (Account *acc); #endif /* __MULTI_LEDGER_H__ */ diff --git a/src/SplitLedger.c b/src/SplitLedger.c index 5df5831c24..5bcb4a3ebd 100644 --- a/src/SplitLedger.c +++ b/src/SplitLedger.c @@ -19,8 +19,8 @@ * (2) it must be committed if the user edits it, and * a new blank split must be created. * (3) it must be deleted when the ledger window is closed. - * To implement the above, the register "user_hook" is used - * to store the blank split with the register window structures. + * To implement the above, the register "user_data" is used + * to store an SRInfo structure containing the blank split. * * ===================================================================== * Some notes on Commit/Rollback: @@ -105,22 +105,37 @@ #define BUFSIZE 1024 +typedef struct _SRInfo SRInfo; +struct _SRInfo +{ + /* The blank split at the bottom of the register */ + Split * blank_split; + + /* The currently open transaction, if any */ + Transaction *pending_trans; + + /* A transaction used to remember where to expand the cursor */ + Transaction *cursor_hint_trans; + + /* The default account where new splits are added */ + Account *default_source_account; +}; + + /* ======================================================== */ -/* the force_double_entry_awareness flag controls how the +/* The force_double_entry_awareness flag controls how the * register behaves if the user failed to specify a transfer-to - * account when creting a new split. What it does is simple, + * account when creating a new split. What it does is simple, * although it can lead to some confusion to the user. * If this flag is set, then any new split will be put into - * exactly the same account as the split immediately above it. - * If the spluit immediately above is the leader, then what - * happens visually is that it appears as if there are two - * transactions, one debiting and one crediting this acdunt - * by exactly the same amount. Thus, the user is forced to - * deal with this somewhat nutty situation. + * the leader account. What happens visually is that it appears + * as if there are two transactions, one debiting and one crediting + * this acount by exactly the same amount. Thus, the user is forced + * to deal with this somewhat nutty situation. * * If this flag is *not* set, then the split just sort of - * hangs out, without beloinging to any account. This will - * of course lead to a lkedger that fails to balance. + * hangs out, without belonging to any account. This will + * of course lead to a ledger that fails to balance. * Bummer, duude ! * * hack alert -- this flag should really be made a configurable @@ -132,6 +147,43 @@ static int force_double_entry_awareness = 0; /* This static indicates the debugging module that this .o belongs to. */ static short module = MOD_LEDGER; +/* The routines below create, access, and destroy the SRInfo structure + * used by SplitLedger routines to store data for a particular register. + * This is the only code that should access the user_data member of a + * SplitRegister directly. If additional user data is needed, just add + * it to the SRInfo structure above. */ +static void +xaccSRInitRegisterData(SplitRegister *reg) +{ + assert(reg != NULL); + + /* calloc initializes to 0 */ + reg->user_data = calloc(1, sizeof(SRInfo)); + assert(reg->user_data != NULL); +} + +static void +xaccSRDestroyRegisterData(SplitRegister *reg) +{ + assert(reg != NULL); + + if (reg->user_data != NULL) + free(reg->user_data); + + reg->user_data = NULL; +} + +static SRInfo * +xaccSRGetInfo(SplitRegister *reg) +{ + assert(reg != NULL); + + if (reg->user_data == NULL) + xaccSRInitRegisterData(reg); + + return (SRInfo *) reg->user_data; +} + /* ======================================================== */ /* this callback gets called when the user clicks on the gui * in such a way as to leave the current transaction, and to @@ -163,15 +215,30 @@ LedgerMoveCursor (Table *table, int new_phys_row = *p_new_phys_row; int new_phys_col = *p_new_phys_col; SplitRegister *reg = (SplitRegister *) client_data; + SRInfo *info = xaccSRGetInfo(reg); + Transaction *newtrans = NULL; + Locator *locator; + Split *split; int style; PINFO ("LedgerMoveCursor(): start calback %d %d \n", - new_phys_row, new_phys_col); + new_phys_row, new_phys_col); + + /* The split where we are moving to */ + split = xaccGetUserData (reg->table, new_phys_row, new_phys_col); + if (split != NULL) + newtrans = xaccSplitGetParent(split); + /* commit the contents of the cursor into the database */ - xaccSRSaveRegEntry (reg); + xaccSRSaveRegEntry (reg, newtrans); xaccSRRedrawRegEntry (reg); + PINFO ("LedgerMoveCursor(): after redraw %d %d \n", - new_phys_row, new_phys_col); + new_phys_row, new_phys_col); + + reg->cursor_phys_row = new_phys_row; + locator = table->locators[new_phys_row][new_phys_col]; + reg->cursor_virt_row = locator->virt_row; /* if auto-expansion is enabled, we need to redraw the register * to expand out the splits at the new location. We do some @@ -193,7 +260,7 @@ LedgerMoveCursor (Table *table, if (NULL == split) { reg->cursor_phys_row = new_phys_row; // reg->cursor_virt_row = reg->table->current_cursor_virt_row; - reg->user_hack = (void *) xaccSplitGetParent (oldsplit); + info->cursor_hint_trans = xaccSplitGetParent (oldsplit); } xaccRegisterRefresh (reg); gnc_refresh_main_window(); @@ -208,22 +275,26 @@ LedgerMoveCursor (Table *table, /* ======================================================== */ /* this callback gets called when the user clicks on the gui * in such a way as to leave the current transaction, and to - * go to a new one. It is called to verify what the cordinates - * of the new cell will be. It really applies only for auto-expansion, - * where we need to calculate the coords of the target cell. + * go to a new one. It is called to verify what the coordinates + * of the new cell will be. It currently does nothing. */ static void LedgerTraverse (Table *table, - int *p_new_phys_row, - int *p_new_phys_col, - void * client_data) + int *p_new_phys_row, + int *p_new_phys_col, + void * client_data) { int new_phys_row = *p_new_phys_row; int new_phys_col = *p_new_phys_col; SplitRegister *reg = (SplitRegister *) client_data; + SRInfo *info = xaccSRGetInfo(reg); int style; + /* For now, just do nothing. The auto mode handling is done entirely + * by LedgerMoveCursor above. */ + return; + /* if auto-expansion is enabled, we need to redraw the register * to expand out the splits at the new location. We do some * tomfoolery here to trick the code into expanding the new location. @@ -235,6 +306,15 @@ LedgerTraverse (Table *table, (REG_DOUBLE_DYNAMIC == style)) { Split *split, *oldsplit; + int save_num_phys_rows, save_num_virt_rows; + int save_cursor_phys_row, save_cursor_virt_row; + + /* Save the values that xaccSRCountRows will futz up */ + save_num_phys_rows = reg->num_phys_rows; + save_num_virt_rows = reg->num_virt_rows; + save_cursor_phys_row = reg->cursor_phys_row; + save_cursor_virt_row = reg->cursor_virt_row; + ENTER ("LedgerTraverse with %d %d \n", new_phys_row , new_phys_col); oldsplit = xaccSRGetCurrentSplit (reg); split = xaccGetUserData (reg->table, new_phys_row, new_phys_col); @@ -244,7 +324,7 @@ LedgerTraverse (Table *table, if (NULL == split) { reg->cursor_phys_row = new_phys_row; // reg->cursor_virt_row = reg->table->current_cursor_virt_row; - reg->user_hack = (void *) xaccSplitGetParent (oldsplit); + info->cursor_hint_trans = xaccSplitGetParent (oldsplit); } xaccRegisterCountHack (reg); @@ -253,6 +333,12 @@ LedgerTraverse (Table *table, LEAVE ("LedgerTraverse with %d \n", reg->cursor_phys_row); /* indicate what row we *should* go to */ *p_new_phys_row = reg->cursor_phys_row; + + /* Restore the values */ + reg->num_phys_rows = save_num_phys_rows; + reg->num_virt_rows = save_num_virt_rows; + reg->cursor_phys_row = save_cursor_phys_row; + reg->cursor_virt_row = save_cursor_virt_row; } } @@ -261,35 +347,70 @@ LedgerTraverse (Table *table, static void LedgerDestroy (SplitRegister *reg) { + SRInfo *info = xaccSRGetInfo(reg); Transaction *trans; /* be sure to destroy the "blank split" */ - if (reg->user_hook) { - Split *split; - - split = (Split *) (reg->user_hook); - + if (info->blank_split) { /* split destroy will automatically remove it * from its parent account */ - trans = xaccSplitGetParent (split); + trans = xaccSplitGetParent (info->blank_split); + + /* Make sure we don't commit this below */ + if (trans == info->pending_trans) + info->pending_trans = NULL; + xaccTransBeginEdit (trans, 1); xaccTransDestroy (trans); xaccTransCommitEdit (trans); - reg->user_hook = NULL; + info->blank_split = NULL; } /* be sure to take care of any open transactions */ - if (reg->user_huck) { - trans = (Transaction *) (reg->user_huck); - - /* I suppose we could also rollback here ... its not clear what - * the desirable behaviour should be from the user's point of view - * when they close a window with an uncommitted edit in it ... - * Maybe we should prompt them ?? - */ - xaccTransCommitEdit (trans); - reg->user_huck = NULL; + if (info->pending_trans) { + /* Committing this should have been taken care of by + * xaccLedgerDisplayClose. But, we'll check again. */ + if (xaccTransIsOpen(info->pending_trans)) + xaccTransCommitEdit (info->pending_trans); + + info->pending_trans = NULL; } + + xaccSRDestroyRegisterData(reg); +} + +/* ======================================================== */ + +Transaction * +xaccSRGetCurrentTrans (SplitRegister *reg) +{ + Split *split; + int pr, pc; + int vr, vc; + + split = xaccSRGetCurrentSplit (reg); + if (split != NULL) + return xaccSplitGetParent(split); + + /* Split is blank. Assume it is the blank split of a multi-line + * transaction. Go back one row to find a split in the transaction. */ + pr = reg->cursor_phys_row; + pc = reg->table->current_cursor_phys_col; + vr = reg->table->locators[pr][pc]->virt_row; + vc = reg->table->locators[pr][pc]->virt_col; + vr --; + if ((0 > vr) || (0 > vc)) { + PERR ("Internal Error: SaveRegEntry(): bad row \n"); + return NULL; + } + + split = (Split *) reg->table->user_data[vr][vc]; + if (split == NULL) { + PERR ("Internal Error: SaveRegEntry(): no parent \n"); + return NULL; + } + + return xaccSplitGetParent(split); } /* ======================================================== */ @@ -310,6 +431,332 @@ xaccSRGetCurrentSplit (SplitRegister *reg) /* ======================================================== */ +Split * +xaccSRGetBlankSplit (SplitRegister *reg) +{ + SRInfo *info = xaccSRGetInfo(reg); + + return info->blank_split; +} + +/* ======================================================== */ + +gncBoolean +xaccSRGetSplitRowCol (SplitRegister *reg, Split *split, + int *virt_row, int *virt_col) +{ + Table *table = reg->table; + int v_row, v_col; + Split *s; + + for (v_row = 1; v_row < table->num_virt_rows; v_row++) + for (v_col = 0; v_col < table->num_virt_cols; v_col++) + { + s = (Split *) table->user_data[v_row][v_col]; + + if (s == split) + { + if (virt_row != NULL) + *virt_row = v_row; + if (virt_col != NULL) + *virt_col = v_col; + + return GNC_T; + } + } + + return GNC_F; +} + +/* ======================================================== */ + +void +xaccSRDeleteCurrentSplit (SplitRegister *reg) +{ + SRInfo *info = xaccSRGetInfo(reg); + Split *split, *s; + Transaction *trans; + int i, num_splits; + Account *account, **affected_accounts; + + /* get the current split based on cursor position */ + split = xaccSRGetCurrentSplit(reg); + if (split == NULL) + return; + + /* If we just deleted the blank split, clean up. The user is + * allowed to delete the blank split as a method for discarding any + * edits they may have made to it. */ + if (split == info->blank_split) + { + account = xaccSplitGetAccount(split); + xaccAccountDisplayRefresh(account); + return; + } + + /* make a copy of all of the accounts that will be + * affected by this deletion, so that we can update + * their register windows after the deletion. + */ + trans = xaccSplitGetParent(split); + num_splits = xaccTransCountSplits(trans); + affected_accounts = (Account **) malloc((num_splits + 1) * + sizeof(Account *)); + assert(affected_accounts != NULL); + + for (i = 0; i < num_splits; i++) + { + s = xaccTransGetSplit(trans, i); + affected_accounts[i] = xaccSplitGetAccount(s); + } + affected_accounts[num_splits] = NULL; + + account = xaccSplitGetAccount(split); + + xaccTransBeginEdit(trans, 1); + xaccAccountBeginEdit(account, 1); + xaccSplitDestroy(split); + xaccAccountCommitEdit(account); + xaccTransCommitEdit(trans); + + /* Check pending transaction */ + if (trans == info->pending_trans) + info->pending_trans = NULL; + + gnc_account_list_ui_refresh(affected_accounts); + + free(affected_accounts); + + gnc_refresh_main_window (); +} + +/* ======================================================== */ + +void +xaccSRDeleteCurrentTrans (SplitRegister *reg) +{ + SRInfo *info = xaccSRGetInfo(reg); + Split *split, *s; + Transaction *trans; + int i, num_splits; + Account *account, **affected_accounts; + + /* get the current split based on cursor position */ + split = xaccSRGetCurrentSplit(reg); + if (split == NULL) + return; + + /* If we just deleted the blank split, clean up. The user is + * allowed to delete the blank split as a method for discarding any + * edits they may have made to it. */ + if (split == info->blank_split) + { + trans = xaccSplitGetParent (info->blank_split); + account = xaccSplitGetAccount(split); + + /* Make sure we don't commit this later on */ + if (trans == info->pending_trans) + info->pending_trans = NULL; + + xaccTransBeginEdit (trans, 1); + xaccTransDestroy (trans); + xaccTransCommitEdit (trans); + + info->blank_split = NULL; + + xaccAccountDisplayRefresh(account); + return; + } + + /* make a copy of all of the accounts that will be + * affected by this deletion, so that we can update + * their register windows after the deletion. + */ + trans = xaccSplitGetParent(split); + num_splits = xaccTransCountSplits(trans); + affected_accounts = (Account **) malloc((num_splits + 1) * + sizeof(Account *)); + assert(affected_accounts != NULL); + + for (i = 0; i < num_splits; i++) + { + s = xaccTransGetSplit(trans, i); + affected_accounts[i] = xaccSplitGetAccount(s); + } + affected_accounts[num_splits] = NULL; + + xaccTransBeginEdit(trans, 1); + xaccTransDestroy(trans); + xaccTransCommitEdit(trans); + + /* Check pending transaction */ + if (trans == info->pending_trans) + info->pending_trans = NULL; + + gnc_account_list_ui_refresh(affected_accounts); + + free(affected_accounts); + + gnc_refresh_main_window (); +} + +/* ======================================================== */ + +void +xaccSREmptyCurrentTrans (SplitRegister *reg) +{ + SRInfo *info = xaccSRGetInfo(reg); + Split *split, *s; + Split **splits; + Transaction *trans; + int i, num_splits; + Account *account, **affected_accounts; + + /* get the current split based on cursor position */ + split = xaccSRGetCurrentSplit(reg); + if (split == NULL) + return; + + /* If we just deleted the blank split, clean up. The user is + * allowed to delete the blank split as a method for discarding any + * edits they may have made to it. */ + if (split == info->blank_split) + { + trans = xaccSplitGetParent (info->blank_split); + account = xaccSplitGetAccount(split); + + /* Make sure we don't commit this later on */ + if (trans == info->pending_trans) + info->pending_trans = NULL; + + xaccTransBeginEdit (trans, 1); + xaccTransDestroy (trans); + xaccTransCommitEdit (trans); + + info->blank_split = NULL; + + xaccAccountDisplayRefresh(account); + return; + } + + /* make a copy of all of the accounts that will be + * affected by this deletion, so that we can update + * their register windows after the deletion. + */ + trans = xaccSplitGetParent(split); + num_splits = xaccTransCountSplits(trans); + affected_accounts = (Account **) malloc((num_splits + 1) * + sizeof(Account *)); + splits = calloc(num_splits, sizeof(Split *)); + + assert(affected_accounts != NULL); + assert(splits != NULL); + + for (i = 0; i < num_splits; i++) + { + s = xaccTransGetSplit(trans, i); + splits[i] = s; + affected_accounts[i] = xaccSplitGetAccount(s); + } + affected_accounts[num_splits] = NULL; + + xaccTransBeginEdit(trans, 1); + for (i = 0; i < num_splits; i++) + if (splits[i] != split) + xaccSplitDestroy(splits[i]); + xaccTransCommitEdit(trans); + + /* Check pending transaction */ + if (trans == info->pending_trans) + info->pending_trans = NULL; + + gnc_account_list_ui_refresh(affected_accounts); + + free(affected_accounts); + free(splits); + + gnc_refresh_main_window (); +} + +/* ======================================================== */ + +void +xaccSRCancelCursorSplitChanges (SplitRegister *reg) +{ + Split * split; + unsigned int changed; + + changed = xaccSplitRegisterGetChangeFlag(reg); + if (!changed) + return; + + /* We're just cancelling the current split here, not the transaction */ + /* When cancelling edits, reload the cursor from the transaction */ + split = xaccSRGetCurrentSplit(reg); + xaccSRLoadRegEntry(reg, split); + xaccRefreshTableGUI(reg->table); + xaccSplitRegisterClearChangeFlag(reg); +} + +/* ======================================================== */ + +void +xaccSRCancelCursorTransChanges (SplitRegister *reg) +{ + SRInfo *info = xaccSRGetInfo(reg); + Transaction *trans; + Split *split; + int i, num_splits = 0, more_splits = 0; + Account **affected_accounts = NULL; + + /* Get the currently open transaction, rollback the edits on it, and + * then repaint everything. To repaint everything, make a note of + * all of the accounts that will be affected by this rollback. Geez, + * there must be some easier way of doing redraw notification. */ + trans = info->pending_trans; + + if (!xaccTransIsOpen(trans)) + { + xaccSRCancelCursorSplitChanges(reg); + return; + } + + num_splits = xaccTransCountSplits (trans); + affected_accounts = (Account **) malloc ((num_splits+1) * + sizeof (Account *)); + + for (i = 0; i < num_splits; i++) + { + split = xaccTransGetSplit (trans, i); + affected_accounts[i] = xaccSplitGetAccount (split); + } + affected_accounts[num_splits] = NULL; + + xaccTransRollbackEdit (trans); + + /* and do some more redraw, for the new set of accounts .. */ + more_splits = xaccTransCountSplits (trans); + affected_accounts = (Account **) realloc (affected_accounts, + (more_splits+num_splits+1) * + sizeof (Account *)); + + for (i = 0; i < more_splits; i++) + { + split = xaccTransGetSplit (trans, i); + affected_accounts[i+num_splits] = xaccSplitGetAccount (split); + } + affected_accounts[num_splits+more_splits] = NULL; + + xaccAccListDisplayRefresh (affected_accounts); + free (affected_accounts); + + info->pending_trans = NULL; + + gnc_refresh_main_window (); +} + +/* ======================================================== */ + void xaccSRRedrawRegEntry (SplitRegister *reg) { @@ -339,11 +786,12 @@ xaccSRRedrawRegEntry (SplitRegister *reg) /* ======================================================== */ /* Copy from the register object to the engine */ -void -xaccSRSaveRegEntry (SplitRegister *reg) +gncBoolean +xaccSRSaveRegEntry (SplitRegister *reg, Transaction *newtrans) { + SRInfo *info = xaccSRGetInfo(reg); Split *split; - Transaction *trans, *oldtrans; + Transaction *trans; unsigned int changed; int style; @@ -351,71 +799,48 @@ xaccSRSaveRegEntry (SplitRegister *reg) * of the split & transaction fields. This will help * cut down on uneccessary register redraws. */ changed = xaccSplitRegisterGetChangeFlag (reg); - if (!changed) return; + if (!changed) return GNC_F; style = (reg->type) & REG_STYLE_MASK; /* get the handle to the current split and transaction */ split = xaccSRGetCurrentSplit (reg); - ENTER ("xaccSRSaveRegEntry(): save split is %p \n", split); - if (!split) { - int vr, vc; - Split *s; - /* If we were asked to save data for a row for which there - * is no associated split, then assume that this was a row - * that was set aside for adding splits to an existing - * transaction. The pre-existing transaction will be the - * one in the row(s) immediately above. Therefore, get - * the cursor location; subtract one row, and get the - * associated transaction. We will then create a new - * split, copy the row contents to that split, and - * append the split to the pre-existing transaction. - */ - vr = reg->table->current_cursor_virt_row; - vc = reg->table->current_cursor_virt_col; - vr --; - if ((0 > vr) || (0 > vc)) { - PERR ("Internal Error: SaveRegEntry(): bad row \n"); - return; - } - s = (Split *) reg->table->user_data[vr][vc]; - if (!s) { - PERR ("Internal Error: SaveRegEntry(): no parent \n"); - return; - } - trans = xaccSplitGetParent (s); + trans = xaccSRGetCurrentTrans (reg); + if (trans == NULL) + return GNC_F; + + ENTER ("xaccSRSaveRegEntry(): save split is %p \n", split); + + /* determine whether we should commit the pending transaction */ + if (info->pending_trans != trans) { + if (xaccTransIsOpen (info->pending_trans)) + xaccTransCommitEdit (info->pending_trans); + xaccTransBeginEdit (trans, 0); + info->pending_trans = trans; + } + + /* If we are committing the blank split, add it to the account now */ + if (xaccTransGetSplit(trans, 0) == info->blank_split) + xaccAccountInsertSplit (info->default_source_account, info->blank_split); + + if (split == NULL) { + /* If we were asked to save data for a row for which there is no + * associated split, then assume that this was a row that was + * set aside for adding splits to an existing transaction. + * xaccSRGetCurrent will handle this case, too. We will create + * a new split, copy the row contents to that split, and append + * the split to the pre-existing transaction. */ - /* determine whether we should commit the previous edit */ - oldtrans = (Transaction *) (reg->user_huck); - if (oldtrans != trans) { - xaccTransCommitEdit (oldtrans); - xaccTransBeginEdit (trans, 0); - reg->user_huck = (void *) trans; - } - split = xaccMallocSplit (); xaccTransAppendSplit (trans, split); - if (force_double_entry_awareness) { - Account * acc; - acc = xaccSplitGetAccount (s); - xaccAccountInsertSplit (acc, split); - } + if (force_double_entry_awareness) + xaccAccountInsertSplit (info->default_source_account, split); assert (reg->table->current_cursor); reg->table->current_cursor->user_data = (void *) split; - - } else { - trans = xaccSplitGetParent (split); - - /* determine whether we should commit the previous edit */ - oldtrans = (Transaction *) (reg->user_huck); - if (oldtrans != trans) { - xaccTransCommitEdit (oldtrans); - xaccTransBeginEdit (trans, 0); - reg->user_huck = (void *) trans; - } } + DEBUG ("xaccSRSaveRegEntry(): updating trans addr=%p\n", trans); /* copy the contents from the cursor to the split */ @@ -452,6 +877,11 @@ xaccSRSaveRegEntry (SplitRegister *reg) xaccSplitSetAction (split, reg->actionCell->cell.value); } + if (MOD_MEMO & changed) { + DEBUG ("xaccSRSaveRegEntry(): MOD_MEMO: %s\n", reg->memoCell->value); + xaccSplitSetMemo (split, reg->memoCell->value); + } + /* -------------------------------------------------------------- */ /* OK, the handling of transfers gets complicated because it * depends on what was displayed to the user. For a multi-line @@ -523,11 +953,6 @@ xaccSRSaveRegEntry (SplitRegister *reg) /* hack alert -- implement this */ } - if (MOD_MEMO & changed) { - DEBUG ("xaccSRSaveRegEntry(): MOD_MEMO: %s\n", reg->memoCell->value); - xaccSplitSetMemo (split, reg->memoCell->value); - } - /* The AMNT and NAMNT updates only differ by sign. Basically, * the split and transaction cursors show minus the quants that * the single and double cursors show, and so when updates happen, @@ -596,15 +1021,21 @@ xaccSRSaveRegEntry (SplitRegister *reg) PINFO ("xaccSRSaveRegEntry(): finished saving split %s of trans %s \n", xaccSplitGetMemo(split), xaccTransGetDescription(trans)); - /* if the modified split is the "blank split", - * then it is now an official part of the account. - * Set user_hook to null, so that we can be sure of - * getting a new split. - */ + /* If the modified split is the "blank split", then it is now an + * official part of the account. Set the blank split to NULL, so + * we can be sure of getting a new split. */ split = xaccTransGetSplit (trans, 0); - if (split == ((Split *) (reg->user_hook))) { - reg->user_hook = NULL; + if (split == info->blank_split) + info->blank_split = NULL; + + /* If the new transaction is different from the current, + * commit the current and set the pending transaction to NULL. */ + if (trans != newtrans) { + xaccTransCommitEdit (trans); + info->pending_trans = NULL; } + + return GNC_T; } /* ======================================================== */ @@ -758,7 +1189,6 @@ void xaccSRLoadRegEntry (SplitRegister *reg, Split *split) { xaccSRLoadTransEntry (reg, split, 0); - /* xaccSRLoadSplitEntry (reg, split, 0); */ /* copy cursor contents into the table */ xaccCommitCursor (reg->table); @@ -768,11 +1198,13 @@ xaccSRLoadRegEntry (SplitRegister *reg, Split *split) void xaccSRCountRows (SplitRegister *reg, Split **slist, - Account *default_source_acc) + Account *default_source_acc) { + SRInfo *info = xaccSRGetInfo(reg); int i; - Split *split=NULL; - Split *save_current_split=NULL; + Split *split = NULL; + Split *save_current_split = NULL; + Transaction *save_current_trans = NULL; int save_cursor_phys_row = -1; int save_cursor_virt_row = -1; Table *table; @@ -781,6 +1213,7 @@ xaccSRCountRows (SplitRegister *reg, Split **slist, int style; int multi_line, dynamic; CellBlock *lead_cursor; + gncBoolean found_split = GNC_F; table = reg->table; style = (reg->type) & REG_STYLE_MASK; @@ -797,10 +1230,9 @@ xaccSRCountRows (SplitRegister *reg, Split **slist, * a pointer to the currently edited split; we restore the * cursor to this location when we are done. */ save_current_split = xaccSRGetCurrentSplit (reg); - if (NULL == save_current_split) { - save_cursor_phys_row = reg->cursor_phys_row; - save_cursor_virt_row = reg->cursor_virt_row; - } + save_current_trans = xaccSRGetCurrentTrans (reg); + save_cursor_phys_row = reg->cursor_phys_row; + save_cursor_virt_row = reg->cursor_virt_row; /* num_phys_rows is the number of rows in all the cursors. * num_virt_rows is the number of cursors (including the header). @@ -823,14 +1255,24 @@ xaccSRCountRows (SplitRegister *reg, Split **slist, } while (split) { /* do not count the blank split */ - if (split != ((Split *) reg->user_hook)) { + if (split != info->blank_split) { Transaction *trans; int do_expand = 0; /* lets determine where to locate the cursor ... */ - if (split == save_current_split) { - save_cursor_phys_row = num_phys_rows; - save_cursor_virt_row = num_virt_rows; + if (!found_split) { + /* Check to see if we find a perfect match */ + if (split == save_current_split) { + save_cursor_phys_row = num_phys_rows; + save_cursor_virt_row = num_virt_rows; + found_split = GNC_T; + } + /* Otherwise, check for a close match. This could happen if + we are collapsing from multi-line to single, e.g. */ + else if (xaccSplitGetParent(split) == save_current_trans) { + save_cursor_phys_row = num_phys_rows; + save_cursor_virt_row = num_virt_rows; + } } /* if multi-line, then show all splits. If dynamic then @@ -841,7 +1283,7 @@ xaccSRCountRows (SplitRegister *reg, Split **slist, (dynamic && xaccIsPeerSplit(split,save_current_split)); if (NULL == save_current_split) { trans = xaccSplitGetParent (split); - do_expand = do_expand || (trans == reg->user_hack); + do_expand = do_expand || (trans == info->cursor_hint_trans); } if (do_expand) @@ -869,13 +1311,21 @@ xaccSRCountRows (SplitRegister *reg, Split **slist, secondary = xaccTransGetSplit (trans, j); if (secondary != split) { - /* lets determine where to locate the cursor ... */ - if (secondary == save_current_split) { + /* lets determine where to locate the cursor ... */ + if (!found_split) { + /* Check to see if we find a perfect match. We have to + * check the transaction in case the split is NULL (blank). + */ + if ((secondary == save_current_split) && + (trans == save_current_trans)) { save_cursor_phys_row = num_phys_rows; save_cursor_virt_row = num_virt_rows; - } - num_virt_rows ++; - num_phys_rows += reg->split_cursor->numRows; + found_split = GNC_T; + } + } + + num_virt_rows ++; + num_phys_rows += reg->split_cursor->numRows; } j++; } while (secondary); @@ -892,13 +1342,12 @@ xaccSRCountRows (SplitRegister *reg, Split **slist, /* ---------------------------------------------------------- */ /* the "blank split", if it exists, is at the end */ - if (reg->user_hook) { - split = (Split *) reg->user_hook; - + if (info->blank_split != NULL) { /* lets determine where to locate the cursor ... */ - if (split == save_current_split) { + if (!found_split && info->blank_split == save_current_split) { save_cursor_phys_row = num_phys_rows; save_cursor_virt_row = num_virt_rows; + found_split = GNC_T; } } @@ -936,17 +1385,22 @@ xaccSRCountRows (SplitRegister *reg, Split **slist, void xaccSRLoadRegister (SplitRegister *reg, Split **slist, - Account *default_source_acc) + Account *default_source_acc) { + SRInfo *info = xaccSRGetInfo(reg); int i = 0; Split *split=NULL, *last_split=NULL; Split *save_current_split=NULL; Table *table; int phys_row; + int save_phys_col; int vrow; int type, style; int multi_line, dynamic; CellBlock *lead_cursor; + gncBoolean found_pending = GNC_F; + + info->default_source_account = default_source_acc; table = reg->table; type = (reg->type) & REG_TYPE_MASK; @@ -963,10 +1417,11 @@ xaccSRLoadRegister (SplitRegister *reg, Split **slist, /* count the number of rows */ xaccSRCountRows (reg, slist, default_source_acc); - /* save the current cursor location; we do this by saving - * a pointer to the currently edited split; we restore the - * cursor to this location when we are done. */ + /* save the current cursor location; we do this by saving a pointer + * to the currently edited split and physical column; we restore + * the cursor to this location when we are done. */ save_current_split = xaccSRGetCurrentSplit (reg); + save_phys_col = table->current_cursor_phys_col; /* disable move callback -- we con't want the cascade of * callbacks while we are fiddling with loading the register */ @@ -995,14 +1450,17 @@ xaccSRLoadRegister (SplitRegister *reg, Split **slist, } while (split) { + if (info->pending_trans == xaccSplitGetParent (split)) + found_pending = GNC_T; + /* do not load the blank split */ - if (split != ((Split *) reg->user_hook)) { + if (split != info->blank_split) { Transaction *trans; int do_expand; PINFO ("xaccSRLoadRegister(): " "load trans %d at phys row %d \n", i, phys_row); - + /* if multi-line, then show all splits. If dynamic then * show all splits only if this is the hot split. */ @@ -1011,7 +1469,7 @@ xaccSRLoadRegister (SplitRegister *reg, Split **slist, (dynamic && xaccIsPeerSplit(split,save_current_split)); if (NULL == save_current_split) { trans = xaccSplitGetParent (split); - do_expand = do_expand || (trans == reg->user_hack); + do_expand = do_expand || (trans == info->cursor_hint_trans); } if (do_expand) @@ -1056,7 +1514,7 @@ xaccSRLoadRegister (SplitRegister *reg, Split **slist, } } else { PINFO ("xaccSRLoadRegister(): " - "skip trans %d (user hook) \n", i); + "skip trans %d (blank split) \n", i); } @@ -1066,10 +1524,11 @@ xaccSRLoadRegister (SplitRegister *reg, Split **slist, } /* add the "blank split" at the end. We use either the blank - * split we've cached away previously in "user_hook", or we create - * a new one, as needed. */ - if (reg->user_hook) { - split = (Split *) reg->user_hook; + * split or we create a new one, as needed. */ + if (info->blank_split != NULL) { + split = info->blank_split; + if (info->pending_trans == xaccSplitGetParent(split)) + found_pending = GNC_T; } else { Transaction *trans; double last_price = 0.0; @@ -1079,8 +1538,7 @@ xaccSRLoadRegister (SplitRegister *reg, Split **slist, xaccTransSetDateToday (trans); xaccTransCommitEdit (trans); split = xaccTransGetSplit (trans, 0); - xaccAccountInsertSplit (default_source_acc, split); - reg->user_hook = (void *) split; + info->blank_split = split; reg->destroy = LedgerDestroy; /* kind of a cheesy hack to get the price on the last split right @@ -1121,8 +1579,28 @@ xaccSRLoadRegister (SplitRegister *reg, Split **slist, phys_row += lead_cursor->numRows; } - /* restor the cursor to its rightful position */ - xaccMoveCursorGUI (table, reg->cursor_phys_row, 0); + /* restore the cursor to its rightful position */ + i = 0; + while ((save_phys_col + i < reg->num_cols) || (save_phys_col > 0)) { + if (gnc_register_cell_valid(table, reg->cursor_phys_row, + save_phys_col + i)) { + xaccMoveCursorGUI (table, reg->cursor_phys_row, save_phys_col + i); + break; + } + if (gnc_register_cell_valid(table, reg->cursor_phys_row, + save_phys_col - i)) { + xaccMoveCursorGUI (table, reg->cursor_phys_row, save_phys_col - i); + break; + } + i++; + } + + /* If we didn't find the pending transaction, it was removed + * from the account. It might not even exist any more. + * Make sure we don't access it. */ + if (!found_pending) + info->pending_trans = NULL; + xaccRefreshTableGUI (table); /* enable callback for cursor user-driven moves */ @@ -1179,9 +1657,10 @@ LoadXferCell (ComboCell *cell, /* ======================================================== */ -void xaccLoadXferCell (ComboCell *cell, - AccountGroup *grp, - Account *base_account) +static void +xaccLoadXferCell (ComboCell *cell, + AccountGroup *grp, + Account *base_account) { char *curr, *secu; @@ -1189,8 +1668,24 @@ void xaccLoadXferCell (ComboCell *cell, secu = xaccAccountGetSecurity (base_account); if (secu && (0x0 == secu[0])) secu = 0x0; + xaccClearComboCellMenu (cell); xaccAddComboCellMenuItem (cell, ""); LoadXferCell (cell, grp, curr, secu); } +/* ======================================================== */ + +void +xaccSRLoadXferCells (SplitRegister *reg, Account *base_account) +{ + AccountGroup *group; + + group = xaccGetAccountRoot(base_account); + + assert((group != NULL) && (base_account != NULL)); + + xaccLoadXferCell(reg->xfrmCell, group, base_account); + xaccLoadXferCell(reg->mxfrmCell, group, base_account); +} + /* ======================= end of file =================== */ diff --git a/src/engine/Account.c b/src/engine/Account.c index 292795a01c..9502390d1b 100644 --- a/src/engine/Account.c +++ b/src/engine/Account.c @@ -1066,6 +1066,13 @@ xaccAccountGetParent (Account *acc) return (acc->parent); } +Account * +xaccAccountGetParentAccount (Account * acc) +{ + if (!acc) return NULL; + return xaccGroupGetParentAccount(acc->parent); +} + int xaccAccountGetType (Account *acc) { diff --git a/src/engine/Account.h b/src/engine/Account.h index 1b9697788d..04efe350be 100644 --- a/src/engine/Account.h +++ b/src/engine/Account.h @@ -140,6 +140,7 @@ char * xaccAccountGetCurrency (Account *); char * xaccAccountGetSecurity (Account *); AccountGroup * xaccAccountGetChildren (Account *); AccountGroup * xaccAccountGetParent (Account *); +Account * xaccAccountGetParentAccount (Account *); AccInfo * xaccAccountGetAccInfo (Account *); double xaccAccountGetBalance (Account *); diff --git a/src/engine/Group.c b/src/engine/Group.c index 6a589f0636..e866062dd1 100644 --- a/src/engine/Group.c +++ b/src/engine/Group.c @@ -108,6 +108,15 @@ xaccAccountGroupMarkSaved (AccountGroup *grp) } } +/********************************************************************\ +\********************************************************************/ +void +xaccAccountGroupMarkNotSaved (AccountGroup *grp) +{ + if (!grp) return; + grp->saved = GNC_F; +} + /********************************************************************\ \********************************************************************/ int @@ -764,6 +773,13 @@ xaccGroupGetAccount (AccountGroup *grp, int i) return (grp->account[i]); } +Account * +xaccGroupGetParentAccount (AccountGroup * grp) +{ + if (!grp) return NULL; + return grp->parent; +} + double xaccGroupGetBalance (AccountGroup * grp) { diff --git a/src/engine/Group.h b/src/engine/Group.h index 885114a3bb..6f8448d291 100644 --- a/src/engine/Group.h +++ b/src/engine/Group.h @@ -54,9 +54,13 @@ void xaccMergeAccounts (AccountGroup *grp); * The xaccAccountGroupMarkSaved() subroutine will mark * the entire group as having been saved, including * all of the child accounts. + * + * The xaccAccountGroupMarkNotSaved() subroutine will mark + * the given group as not having been saved. */ int xaccAccountGroupNotSaved (AccountGroup *grp); void xaccAccountGroupMarkSaved (AccountGroup *grp); +void xaccAccountGroupMarkNotSaved (AccountGroup *grp); /* * The xaccRemoveAccount() subroutine will remove the indicated @@ -168,6 +172,11 @@ void xaccConsolidateGrpTransactions (AccountGroup *); Account * xaccGroupGetAccount (AccountGroup *, int); +/* The xaccGroupGetParentAccount() subroutine returns the parent + * account of the group, or NULL. + */ +Account * xaccGroupGetParentAccount (AccountGroup *); + /* * The xaccGroupGetNextFreeCode() method will try to guess a reasonable * candidate for the next unused account code in this group. diff --git a/src/engine/LedgerUtils.c b/src/engine/LedgerUtils.c index 1db2079d6f..42631ebc96 100644 --- a/src/engine/LedgerUtils.c +++ b/src/engine/LedgerUtils.c @@ -32,6 +32,24 @@ static short module = MOD_ENGINE; /* ------------------------------------------------------ */ +gncBoolean accListHasAccount (Account **list, Account *findme) +{ + Account *acc; + int nacc = 0; + if (!list || !findme) return GNC_F; + + acc = list[0]; + while (acc) { + if (acc == findme) + return GNC_T; + nacc++; + acc = list[nacc]; + } + return GNC_F; +} + +/* ------------------------------------------------------ */ + int accListCount (Account **list) { Account *acc; diff --git a/src/engine/LedgerUtils.h b/src/engine/LedgerUtils.h index a93d878cad..0b4245a2d5 100644 --- a/src/engine/LedgerUtils.h +++ b/src/engine/LedgerUtils.h @@ -21,12 +21,14 @@ #ifndef __XACC_LEDGER_UTILS_H__ #define __XACC_LEDGER_UTILS_H__ +#include "gnc-common.h" #include "config.h" #include "Account.h" /** PROTOTYPES ******************************************************/ +gncBoolean accListHasAccount (Account **list, Account *findme); int accListCount (Account **list); Account ** accListCopy (Account **list); Account ** xaccGroupToList (Account *); diff --git a/src/engine/QIFIO.c b/src/engine/QIFIO.c index 44fd03afa6..1a8cc9f89e 100644 --- a/src/engine/QIFIO.c +++ b/src/engine/QIFIO.c @@ -49,6 +49,13 @@ #define WFLAGS (O_WRONLY | O_CREAT | O_TRUNC) #define RFLAGS O_RDONLY +/* Cleared values found in QIF files */ +#define QRECCLEAR '*' /* Cleared, per Quicken */ +#define QRECREC 'x' /* Reconciled, per Quicken */ +#define QRECRECM 'X' /* Reconciled, per MS Money */ +#define QRECBUDG '?' /* Budgeted amount, per CBB */ +#define QRECBUDP '!' /* OLD Budgeted amount per CBB */ + /** GLOBALS *********************************************************/ /* XXX hack alert -- this default currency should be made configurable @@ -60,6 +67,9 @@ static int error_code=0; /* error code, if error occurred */ /* This static indicates the debugging module that this .o belongs to. */ static short module = MOD_IO; +static int FindDateDelimiter (char *str); +static void TryToFixDate (struct tm *date); +static int FavorDateType (int value); /*******************************************************/ @@ -397,7 +407,7 @@ char * xaccReadQIFAccList (int fd, AccountGroup *grp, int cat) * parses date of the form MM/DD/YY * * * * Args: str -- pointer to string rep of date * - * Return: void * + * Return: time_t -- date in format used by UNIX time_t * \********************************************************************/ time_t @@ -406,15 +416,22 @@ xaccParseQIFDate (char * str) char * tok; time_t secs; struct tm dat; + int favechar = 0; /* Which delimiter do we have? */ - if (!str) return 0; - tok = strchr (str, '/'); + if (!str) return 0; /* If the string is null, we're done. */ + + /* First, figure out the delimiter. */ + /* Choices: "." or "-" or "/" */ + favechar = FindDateDelimiter(str); + if (!favechar) return 0; + + tok = strchr (str, favechar); if (!tok) return 0; *tok = 0x0; dat.tm_mon = atoi (str) - 1; str = tok+sizeof(char); - tok = strchr (str, '/'); + tok = strchr (str, favechar); if (!tok) return 0; *tok = 0x0; dat.tm_mday = atoi (str); @@ -428,6 +445,8 @@ xaccParseQIFDate (char * str) *tok = 0x0; dat.tm_year = atoi (str); + TryToFixDate(&dat); + /* a quickie Y2K fix: assume two digit dates with * a value less than 50 are in the 21st century. */ if (50 > dat.tm_year) dat.tm_year += 100; @@ -441,6 +460,120 @@ xaccParseQIFDate (char * str) return secs; } + +/********************************************************************\ + * FindDateDelimiter * + * determines which character is the delimiter for a date * + * typical would be "MM/DD/YY", "MM-DD-YY", "MM.DD.YY" * + * * + * Args: str -- pointer to string rep of date * + * Return: int containing '/', '.', '-', or NULL indicating failure * +\********************************************************************/ + +static int FindDateDelimiter (char *str) { + char *tok; + if (!str) return 0; + tok = strchr (str, '/'); + if (tok) + return '/'; + tok = strchr (str, '.'); + if (tok) + return '.'; + tok = strchr (str, '-'); + if (tok) + return '-'; + return 0; +} + +/********************************************************************\ + * TryToFixDate * + * Swaps around date components based on some heuristics * + * * + * Args: date -- pointer to time_t structure * + * Return: void * +\********************************************************************/ +static void TryToFixDate (struct tm *date) +{ + int first, second, third; + int st_first, st_second, st_third; + int mon, mday, year; + int results; + int which[5] = {0,0,0,0,0}; + + first = date->tm_mon; + second = date->tm_mday; + third = date->tm_year; + + /* See what sort of date is favored by each component */ + st_first = FavorDateType(first); + st_second = FavorDateType(second); + st_third = FavorDateType(third); + + /* Plunk the values down into which[] */ + which[st_first] = 1; + which[st_second] = 2; + which[st_third] = 3; + + switch (which[4]) { /* Year */ + case 1: + year = first; + break; + case 2: + year = second; + break; + case 3: + year = third; + break; + default: + return; /* No date component looks like a year --> ABORT */ + } + switch (which[2]) { /* month */ + case 1: + mon = first; + break; + case 2: + mon = second; + break; + case 3: + mon = third; + break; + default: + return; /* No date component looks like a month --> ABORT */ + } + switch (which[1]) { /* mday */ + case 1: + mday = first; + break; + case 2: + mday = second; + break; + case 3: + mday = third; + break; + default: + return; /* No date component looks like a day of the month - ABORT */ + } + + date->tm_mon = mon; + date->tm_mday = mday; + date->tm_year = year; + + return; +} + +static int FavorDateType (int value) +{ + int favoring; + favoring = 2; /* Month */ + if (value > 30) + favoring = 4; /* Year */ + if (value < 0) + favoring = 4; /* Year */ + if (value < 31) + if (value > 11) + favoring = 1; /* Day of month */ +} + /********************************************************************\ \********************************************************************/ @@ -643,6 +776,7 @@ xaccReadQIFTransaction (int fd, Account *acc, int guess_name, Account *sub_acc = 0x0; Account *xfer_acc = 0x0; double adjust = 0.0; + char secondchar; if (!acc) return NULL; @@ -659,17 +793,24 @@ xaccReadQIFTransaction (int fd, Account *acc, int guess_name, while (qifline) { switch (qifline[0]) { - case 'C': - /* C == Cleared / Reconciled */ - /* Quicken uses C* and Cx, while MS Money uses CX. - * C* means cleared (but not yet reconciled) - * Cx or CX means reconciled - */ - if (('x' == qifline[1]) || ('X' == qifline[1])) { - xaccSplitSetReconcile (source_split, YREC); - } else { - xaccSplitSetReconcile (source_split, CREC); - } + case 'C': /* Cleared flag */ + secondchar = qifline[1]; + switch(secondchar) + { + case QRECCLEAR: + xaccSplitSetReconcile(source_split, CREC); + break; + case QRECREC: + case QRECRECM: + xaccSplitSetReconcile(source_split, YREC); + break; + case QRECBUDP: + case QRECBUDG: + xaccSplitSetReconcile(source_split, NREC); + break; + default: + xaccSplitSetReconcile(source_split, NREC); + } break; case 'D': /* D == date */ { @@ -729,7 +870,7 @@ xaccReadQIFTransaction (int fd, Account *acc, int guess_name, if (!strncmp (qifline, "NSell", 5)) isneg = 1; if (!strncmp (qifline, "NSell", 5)) share_xfer = 1; if (!strncmp (qifline, "NBuy", 4)) share_xfer = 1; - + /* if a recognized action, convert to our cannonical names */ XACC_ACTION ("Buy", "Buy") XACC_ACTION ("Sell", "Sell") @@ -749,7 +890,7 @@ xaccReadQIFTransaction (int fd, Account *acc, int guess_name, /* O == adjustments */ /* hack alert -- sometimes adjustments are quite large. * I have no clue why, and what to do about it. For what - * its worth, I can prove that Quicken version 3.0 makes + * it's worth, I can prove that Quicken version 3.0 makes * math errors ... */ { double pute; @@ -870,49 +1011,48 @@ xaccReadQIFTransaction (int fd, Account *acc, int guess_name, return qifline; } - /* fundamentally differnt handling for securities and non-securities */ + /* fundamentally different handling for securities and non-securities */ if (is_security) { - - /* if the transaction is a sale/purchase of a security, - * then it is a defacto transfer between the brokerage account - * and the stock account. */ - if (share_xfer) { - if (!split) { - split = xaccMallocSplit (); - xaccTransAppendSplit (trans, split); - } - - /* Insert the transaction into the main brokerage - * account as a debit, unless an alternate account - * was specified. */ - if (xfer_acc) { - xaccAccountInsertSplit (xfer_acc, split); + /* if the transaction is a sale/purchase of a security, + * then it is a defacto transfer between the brokerage account + * and the stock account. */ + if (share_xfer) { + if (!split) { + split = xaccMallocSplit (); + xaccTransAppendSplit (trans, split); + } + + /* Insert the transaction into the main brokerage + * account as a debit, unless an alternate account + * was specified. */ + if (xfer_acc) { + xaccAccountInsertSplit (xfer_acc, split); + } else { + xaccAccountInsertSplit (acc, split); + } + + /* normally, the security account is pointed at by + * sub_acc; the security account is credited. + * But, just in case its missing, avoid a core dump */ + if (sub_acc) { + /* xxx hack alert --- is this right ??? */ + xaccAccountInsertSplit (sub_acc, source_split); + } + } else { + + /* else, we are here if its not a share transfer. + * It is probably dividend or other income */ + + /* if a transfer account is specified, the transfer + * account gets the dividend credit; otherwise, the + * main account gets it */ + if (xfer_acc) { + xaccAccountInsertSplit (xfer_acc, source_split); } else { - xaccAccountInsertSplit (acc, split); + xaccAccountInsertSplit (acc, source_split); } - - /* normally, the security account is pointed at by - * sub_acc; the security account is credited. - * But, just in case its missing, avoid a core dump */ - if (sub_acc) { -/* xxx hack alert --- is this right ??? */ - xaccAccountInsertSplit (sub_acc, source_split); - } - } else { - - /* else, we are here if its not a share transfer. - * It is probably dividend or other income */ - - /* if a transfer account is specified, the transfer - * account gets the dividend credit; otherwise, the - * main account gets it */ - if (xfer_acc) { - xaccAccountInsertSplit (xfer_acc, source_split); - } else { - xaccAccountInsertSplit (acc, source_split); - } - } - + } + } else { /* if we are here, its not a security, but an ordinary account */ /* if a transfer account was specified, it is the debited account */ diff --git a/src/engine/Query.c b/src/engine/Query.c index 3d27f7e88f..915dc71b94 100644 --- a/src/engine/Query.c +++ b/src/engine/Query.c @@ -225,6 +225,8 @@ xaccQuerySetDateRangeL (Query *q, long long early, long long late) q->latest.tv_sec = late; } +/* ================================================== */ + /* ================================================== */ /* Note that the sort order for a transaction that is * currently being edited is based on its old values, @@ -254,6 +256,10 @@ xaccQuerySetDateRangeL (Query *q, long long early, long long late) if ( !(ta) && !(tb) ) return 0; \ +#define CSTANDARD { \ + retval = xaccSplitDateOrder(sa, sb); \ + if (retval) return retval; \ +} #define CDATE { \ /* if dates differ, return */ \ @@ -328,7 +334,7 @@ xaccQuerySetDateRangeL (Query *q, long long early, long long late) } \ } -#define CAMOUNT { \ +#define CAMOUNT { \ double fa, fb; \ fa = ((*sa)->damount) * ((*sa)->share_price); \ fb = ((*sb)->damount) * ((*sb)->share_price); \ @@ -340,6 +346,8 @@ xaccQuerySetDateRangeL (Query *q, long long early, long long late) } \ } +#define CNONE + #define DECLARE(ONE,TWO,THREE) \ static int Sort_##ONE##_##TWO##_##THREE \ (Split **sa, Split **sb) \ @@ -392,6 +400,7 @@ sub recur { /* ================================================== */ /* Define the sorting comparison functions */ +DECLARE (STANDARD, NONE, NONE) DECLARE (DESC, MEMO, AMOUNT) DECLARE (DESC, MEMO, NUM) DECLARE (DESC, MEMO, DATE) @@ -468,6 +477,7 @@ xaccQuerySetSortOrder (Query *q, int arga, int argb, int argc) if (!q) return; q->changed = 1; + DECIDE (STANDARD, NONE, NONE) DECIDE (DESC, MEMO, AMOUNT) DECIDE (DESC, MEMO, NUM) DECIDE (DESC, MEMO, DATE) @@ -587,12 +597,12 @@ xaccQueryGetSplits (Query *q) /* now go through the splits */ j=0; s = acc->splits[0]; while (s) { - if (s->parent->date_posted.tv_sec >= q->earliest.tv_sec) { - nsplits ++; - } if (s->parent->date_posted.tv_sec > q->latest.tv_sec) { break; } + if (s->parent->date_posted.tv_sec >= q->earliest.tv_sec) { + nsplits ++; + } j++; s = acc->splits[j]; } i++; acc = q->acc_list[i]; @@ -610,12 +620,12 @@ xaccQueryGetSplits (Query *q) /* now go through the splits */ j=0; s = acc->splits[0]; while (s) { - if (s->parent->date_posted.tv_sec >= q->earliest.tv_sec) { - slist[k] = s; k++; - } if (s->parent->date_posted.tv_sec > q->latest.tv_sec) { break; } + if (s->parent->date_posted.tv_sec >= q->earliest.tv_sec) { + slist[k] = s; k++; + } j++; s = acc->splits[j]; } i++; acc = q->acc_list[i]; diff --git a/src/engine/Query.h b/src/engine/Query.h index dc529bc4a5..fce3537b61 100644 --- a/src/engine/Query.h +++ b/src/engine/Query.h @@ -38,11 +38,13 @@ typedef struct _Query Query; /* sorting orders */ enum { + BY_STANDARD, BY_DATE, BY_NUM, BY_AMOUNT, BY_MEMO, - BY_DESC + BY_DESC, + BY_NONE }; Query * xaccMallocQuery (void); diff --git a/src/engine/Transaction.c b/src/engine/Transaction.c index 6fd62f4f48..746c827da2 100644 --- a/src/engine/Transaction.c +++ b/src/engine/Transaction.c @@ -201,9 +201,10 @@ xaccConfigGetForceDoubleEntry (void) /********************************************************************\ \********************************************************************/ -#define MARK_SPLIT(split) { \ - Account *acc = (Account *) ((split)->acc); \ - if (acc) acc->changed |= ACC_INVALIDATE_ALL; \ +#define MARK_SPLIT(split) { \ + Account *acc = (Account *) ((split)->acc); \ + if (acc) acc->changed |= ACC_INVALIDATE_ALL; \ + if (acc) xaccAccountGroupMarkNotSaved(acc->parent); \ } static void @@ -869,9 +870,14 @@ xaccSplitRebalance (Split *split) void xaccTransBeginEdit (Transaction *trans, int defer) { + char open; + assert (trans); + open = trans->open; trans->open = BEGIN_EDIT; if (defer) trans->open |= DEFER_REBALANCE; + if (open & BEGIN_EDIT) return; + xaccOpenLog (); xaccTransWriteLog (trans, 'B'); @@ -1097,6 +1103,13 @@ xaccTransRollbackEdit (Transaction *trans) LEAVE ("xaccTransRollbackEdit(): trans addr=%p\n", trans); } +gncBoolean +xaccTransIsOpen (Transaction *trans) +{ + if (trans == NULL) return GNC_F; + + return ((trans->open & BEGIN_EDIT) != 0); +} /********************************************************************\ \********************************************************************/ diff --git a/src/engine/Transaction.h b/src/engine/Transaction.h index 8ef7824300..e4ae28fdbc 100644 --- a/src/engine/Transaction.h +++ b/src/engine/Transaction.h @@ -136,11 +136,16 @@ void xaccTransDestroy (Transaction *); * started. This includes restoring any deleted splits, removing * any added splits, and undoing the effects of xaccTransDestroy, * as well as restoring prices, memo's descriptions, etc. + * + * The xaccTransIsOpen() method returns GNC_T if the transaction + * is open for editing. Otherwise, it returns false. */ void xaccTransBeginEdit (Transaction *, int defer); void xaccTransCommitEdit (Transaction *); void xaccTransRollbackEdit (Transaction *); +gncBoolean xaccTransIsOpen (Transaction *trans); + /* Convert a day, month, and year to a Timespec */ Timespec gnc_dmy2timespec(int day, int month, int year); diff --git a/src/engine/date.c b/src/engine/date.c index 031d6163bd..ed7bcf5d9d 100644 --- a/src/engine/date.c +++ b/src/engine/date.c @@ -35,13 +35,43 @@ #include "config.h" #include "date.h" +#include "util.h" + +/* This is now user configured through the gnome options system() */ +static DateFormat dateFormat = DATE_FORMAT_US; + +/* This static indicates the debugging module that this .o belongs to. */ +static short module = MOD_ENGINE; -/* hack alert -- this should be turned into user-configurable parameter .. */ -DateFormat dateFormat = DATE_FORMAT_US; /********************************************************************\ \********************************************************************/ + +/** + * setDateFormat + * set date format to one of US, UK, CE, OR ISO + * checks to make sure it's a legal value + * Args: DateFormat: enumeration indicating preferred format + * returns: nothing + * + * Globals: dateFormat + **/ + +void setDateFormat(DateFormat df) +{ + if(df >= DATE_FORMAT_FIRST && df <= DATE_FORMAT_LAST) + { + dateFormat = df; + } + else + { /* hack alert - is this what we should be doing here? */ + PERR("non-existent date format set"); + } + + return; +} + /** * printDate * Convert a date as day / month / year integers into a localized string @@ -72,6 +102,7 @@ printDate (char * buff, int day, int month, int year) */ switch(dateFormat) { + case DATE_FORMAT_UK: sprintf (buff, "%2d/%2d/%-4d", day, month, year); break; @@ -81,11 +112,24 @@ printDate (char * buff, int day, int month, int year) case DATE_FORMAT_ISO: sprintf (buff, "%04d-%02d-%02d", year, month, day); break; + case DATE_FORMAT_LOCALE: + { + struct tm tm_str; + tm_str.tm_mday = day; + tm_str.tm_mon = month - 1; /*tm_mon = 0 through 11 */ + tm_str.tm_year = year - 1900; /* this is what the standard + * says, it's not a Y2K thing + */ + strftime(buff, MAX_DATE_LENGTH, "%x", &tm_str); + } + break; + case DATE_FORMAT_US: default: sprintf (buff, "%2d/%2d/%-4d", month, day, year); break; } + return; } void diff --git a/src/engine/date.h b/src/engine/date.h index f9ec2e073c..3c20bcbcd3 100644 --- a/src/engine/date.h +++ b/src/engine/date.h @@ -81,13 +81,17 @@ typedef enum DATE_FORMAT_US, /* United states: mm/dd/yyyy */ DATE_FORMAT_UK, /* Britain: dd/mm/yyyy */ DATE_FORMAT_CE, /* Continental Europe: dd.mm.yyyy */ - DATE_FORMAT_ISO /* ISO: yyyy-mm-dd */ + DATE_FORMAT_ISO, /* ISO: yyyy-mm-dd */ + DATE_FORMAT_LOCALE /* Take from locale information */ } DateFormat; +#define DATE_FORMAT_FIRST DATE_FORMAT_US +#define DATE_FORMAT_LAST DATE_FORMAT_LOCALE /* the maximum length of a string created by sprtDate() */ #define MAX_DATE_LENGTH 11 /** PROTOTYPES ******************************************************/ +void setDateFormat(DateFormat df); void printDate (char * buff, int day, int month, int year); void printDateSecs (char * buff, time_t secs); @@ -102,6 +106,7 @@ void xaccTransSetDateStr (Transaction *trans, char *str); time_t xaccDMYToSec (int day, int month, int year); time_t xaccScanDateS (const char *buff); + /** GLOBALS *********************************************************/ extern DateFormat dateFormat; diff --git a/src/engine/util.c b/src/engine/util.c index ebfd79495c..98b15b204f 100644 --- a/src/engine/util.c +++ b/src/engine/util.c @@ -49,7 +49,7 @@ int loglevel[MODULE_MAX] = 2, /* IO */ 4, /* REGISTER */ 2, /* LEDGER */ - 4, /* HTML */ + 2, /* HTML */ 2, /* GUI */ 4, /* SCRUB */ 4, /* GTK_REG */ @@ -150,6 +150,44 @@ ultostr (unsigned long val, int base) return strdup (buf); } +/********************************************************************\ + * utility function to convert floating point value to a string +\********************************************************************/ + +static int +util_fptostr(char *buf, double val, int prec) +{ + int i; + char formatString[10]; + char prefix[] = "%0."; + char postfix[] = "f"; + + /* This routine can only handle precision between 0 and 9, so + * clamp precision to that range */ + if (prec > 9) prec = 9; + if (prec < 0) prec = 0; + + /* Make sure that the output does not resemble "-0.00" by forcing + * val to 0.0 when we have a very small negative number */ + if ((val <= 0.0) && (val > -pow(0.1, prec+1) * 5.0)) + val = 0.0; + + /* Create a format string to pass into sprintf. By doing this, + * we can get sprintf to convert the number to a string, rather + * than maintaining conversion code ourselves. */ + i = 0; + strcpy(&formatString[i], prefix); + i += strlen(prefix); + formatString[i] = '0' + prec; /* add prec to ASCII code for '0' */ + i += 1; + strcpy(&formatString[i], postfix); + i += strlen(postfix); + + sprintf(buf, formatString, val); + + return strlen(buf); +} + /********************************************************************\ * stpcpy for those platforms that don't have it. \********************************************************************/ @@ -170,7 +208,6 @@ stpcpy (char *dest, const char *src) * returned from the localconv() subroutine \********************************************************************/ -/* The PrtAmtComma() routine prints a comma-separated currency value */ /* THOU_SEP is a comma in U.S. but a period in some parts of Europe */ /* CENT_SEP is a period in U.S. but a comma in some parts of Europe */ @@ -178,60 +215,60 @@ stpcpy (char *dest, const char *src) #define CENT_SEP '.' static int -PrtAmtComma (char * buf, double val, int prec) +PrintAmt(char *buf, double val, int prec, int use_commas) { - int i, ival, ncommas = 0; - double tmp, amt=0.0; - char *start = buf; + int i, stringLength, numWholeDigits, commaCount; + char tempBuf[50]; + char *bufPtr = buf; - /* check if we're printing infinity */ - if (!finite(val)) { - strcpy (buf, "inf"); - return 3; - } + /* check if we're printing infinity */ + if (!finite(val)) { + strcpy (buf, "inf"); + return 3; + } - /* Round to 100'ths or 1000'nths now. Must do this before we start printing. */ - if (2 == prec) val += 0.005; - if (3 == prec) val += 0.0005; - - /* count number of commas */ - tmp = val; - while (tmp > 1000.0) { - tmp *= 0.001; - ncommas ++; - } - - /* print digits in groups of three, separated by commas */ - for (i=ncommas; i>=0; i--) { - int j; - - amt *= 1000.0; - tmp = val; - for (j=i; j>0; j--) tmp *= 0.001; - tmp -= amt; - ival = tmp; - if (i !=ncommas) { - buf += sprintf (buf, "%03d", ival); - } else { - buf += sprintf (buf, "%d", ival); + util_fptostr(tempBuf, val, prec); + + if (!use_commas) + { + /* If we're not using commas, then the whole string is copied */ + strcpy(buf, tempBuf); + } + else + { + /* Determine where the decimal place is, if there is one */ + stringLength = strlen(tempBuf); + numWholeDigits = -1; + for (i = 0; i < stringLength; i++) { + if (tempBuf[i] == '.') { + numWholeDigits = i; + break; + } } - *buf = THOU_SEP; buf++; - amt += ival; - } + + if (numWholeDigits < 0) + numWholeDigits = stringLength; /* Can't find decimal place, it's + * a whole number */ - /* place decimal point */ - buf --; *buf = CENT_SEP; buf++; + /* We now know the number of whole digits, now insert commas while + * copying them from the temp buffer to the destination */ + bufPtr = buf; + for (i = 0; i < numWholeDigits; i++, bufPtr++) { + *bufPtr = tempBuf[i]; + commaCount = (numWholeDigits - i) - 1; + if ( (commaCount % 3 == 0) && + (commaCount != 0) && + (tempBuf[i] != '-')) + { + bufPtr++; + *bufPtr = ','; + } + } + + strcpy(bufPtr, &tempBuf[numWholeDigits]); + } /* endif */ - /* print two or three decimal places */ - if (3 == prec) { - ival = 1000.0 * (val-amt); - buf += sprintf (buf, "%03d", ival); - } else { - ival = 100.0 * (val-amt); - buf += sprintf (buf, "%02d", ival); - } - - return (buf-start); + return strlen(buf); } int @@ -244,32 +281,18 @@ xaccSPrintAmount (char * bufp, double val, short shrs) if (DEQ(val, 0.0)) val = 0.0; - if (0.0 > val) { - bufp[0] = '-'; - bufp ++; - val = -val; - } - if (shrs & PRTSHR) { - if (shrs & PRTSEP) { - bufp += PrtAmtComma (bufp, val, 3); - } else { - bufp += sprintf( bufp, "%.3f", val ); - } + bufp += PrintAmt(orig_bufp, val, 4, shrs & PRTSEP); if (shrs & PRTSYM) { /* stpcpy returns pointer to end of string, not like strcpy */ bufp = stpcpy (bufp, " shrs"); } } else { - if (shrs & PRTSYM) { bufp += sprintf( bufp, "%s ", CURRENCY_SYMBOL); } - if (shrs & PRTSEP) { - bufp += PrtAmtComma (bufp, val, 2); - } else { - bufp += sprintf( bufp, "%.2f", val ); - } + + bufp += PrintAmt(orig_bufp, val, 2, shrs & PRTSEP); } /* return length of printed string */ diff --git a/src/gnome/Makefile.in b/src/gnome/Makefile.in index 1769695bd2..8b7bc4b1cd 100644 --- a/src/gnome/Makefile.in +++ b/src/gnome/Makefile.in @@ -41,8 +41,8 @@ CFLAGS = @CFLAGS@ ${INCLPATH} LDFLAGS = @LDFLAGS@ GUILELIBS = @GUILELIBS@ -LIBS = -L$(prefix)/lib @LIBS@ -lgtkxmhtml \ - $(shell ${GNOME_CONFIG_BIN} --libs gnomeui) $(GUILELIBS) \ +LIBS = -L$(prefix)/lib @LIBS@ \ + $(shell ${GNOME_CONFIG_BIN} --libs gnomeui @GTK_XMHTML@) $(GUILELIBS) \ @top_srcdir@/lib/g-wrap-install/lib/libgwrapguile.a ifeq (${HAVE_PLOTUTILS},1) @@ -65,10 +65,11 @@ OTHER_OBJS += $(wildcard @top_srcdir@/src/register/gnome/obj/gnome/*.o) # See Makefile.common for information about these variables. GNOME_SRCS := top-level.c window-main.c window-register.c window-adjust.c \ window-help.c cursors.c account-tree.c \ - window-reconcile.c option-util.c \ + window-reconcile.c option-util.c window-html.c \ dialog-options.c dialog-filebox.c dialog-transfer.c \ dialog-add.c dialog-edit.c dialog-utils.c \ - scripts_menu.c query-user.c reconcile-list.c + scripts_menu.c query-user.c reconcile-list.c \ + window-report.c global-options.c ###################################################################### all: gnome @@ -82,5 +83,5 @@ gnome: @top_srcdir@/gnucash.gnome $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) gnome.static: @top_srcdir@/gnucash.gnome.static -@top_srcdir@/gnucash.motif.static: ${GNOME_OBJS} ${OTHER_OBJS} +@top_srcdir@/gnucash.gnome.static: ${GNOME_OBJS} ${OTHER_OBJS} $(CC) -static $(LDFLAGS) -o $@ $^ $(LIBS) diff --git a/src/gnome/account-tree.c b/src/gnome/account-tree.c index 24dae67f62..9a42fbe07a 100644 --- a/src/gnome/account-tree.c +++ b/src/gnome/account-tree.c @@ -117,6 +117,7 @@ gnc_account_tree_init(GNCAccountTree *tree) gtk_clist_set_shadow_type(GTK_CLIST(tree), GTK_SHADOW_IN); gtk_clist_column_titles_passive(GTK_CLIST(tree)); + gtk_clist_set_column_auto_resize(GTK_CLIST(tree), 0, TRUE); gtk_clist_set_column_justification(GTK_CLIST(tree), tree->balance_column, GTK_JUSTIFY_RIGHT); @@ -286,8 +287,6 @@ gnc_account_tree_refresh(GNCAccountTree * tree) tree->root_account), gncGetCurrentGroup()); - gtk_clist_thaw(clist); - gtk_clist_columns_autosize(clist); gnc_account_tree_update_column_visibility(tree); @@ -297,10 +296,13 @@ gnc_account_tree_refresh(GNCAccountTree * tree) if (adjustment != NULL) { - save_value = CLAMP(save_value, adjustment->lower, adjustment->upper); + save_value = CLAMP(save_value, adjustment->lower, + adjustment->upper - adjustment->page_size); gtk_adjustment_set_value(adjustment, save_value); } + gtk_clist_thaw(clist); + g_hash_table_destroy(expanded_accounts); } diff --git a/src/gnome/dialog-add.c b/src/gnome/dialog-add.c index ac811db27b..c32ecd4feb 100644 --- a/src/gnome/dialog-add.c +++ b/src/gnome/dialog-add.c @@ -35,6 +35,7 @@ #include "AccWindow.h" #include "MainWindow.h" #include "FileDialog.h" +#include "Refresh.h" #include "window-main.h" #include "dialog-utils.h" #include "account-tree.h" @@ -47,7 +48,9 @@ static short module = MOD_GUI; static int _accWindow_last_used_account_type = BANK; + static gchar * default_currency = "USD"; +static gboolean default_currency_dynamically_allocated = FALSE; struct _accwindow @@ -360,8 +363,10 @@ gnc_ui_accWindow_create_account(Account * account, Account * parent, xaccAccountCommitEdit (account); - gnc_account_tree_insert_account(gnc_get_current_account_tree(), - account); + gnc_account_tree_insert_account(gnc_get_current_account_tree(), account); + + /* Refresh register so they have this account in their lists */ + gnc_group_ui_refresh(gncGetCurrentGroup()); } @@ -527,5 +532,24 @@ xaccDestroyEditNotesWindow (Account *acc) } +/*********************************************************************\ + * xaccSetDefaultNewaccountCurrency * + * Set the default currency for new accounts * + * intended to be called by option handling code * + * * + * Args: new default_currency * + * Globals: default_currency, default_currency_dynamically_allocated * + * Return value: none * +\*********************************************************************/ +void +xaccSetDefaultNewaccountCurrency(char *new_default_currency) +{ + if (default_currency_dynamically_allocated) + g_free(default_currency); + + default_currency = g_strdup(new_default_currency); + default_currency_dynamically_allocated = TRUE; +} + /********************** END OF FILE *********************************\ \********************************************************************/ diff --git a/src/gnome/dialog-edit.c b/src/gnome/dialog-edit.c index d1aeb589d0..0511b02180 100644 --- a/src/gnome/dialog-edit.c +++ b/src/gnome/dialog-edit.c @@ -32,6 +32,8 @@ #include "AccWindow.h" #include "MainWindow.h" +#include "Refresh.h" +#include "FileDialog.h" #include "dialog-utils.h" #include "messages.h" #include "util.h" @@ -149,6 +151,7 @@ gnc_ui_EditAccWindow_ok_cb(GtkWidget * widget, gnc_ui_free_field_strings(&strings); gnc_refresh_main_window(); + gnc_group_ui_refresh(gncGetCurrentGroup()); gnome_dialog_close(GNOME_DIALOG(editAccData->dialog)); } @@ -166,14 +169,21 @@ editAccWindow(Account *acc) { EditAccWindow * editAccData; GtkWidget *vbox, *widget, *dialog; + char *name, *title; FETCH_FROM_LIST (EditAccWindow, editAccList, acc, account, editAccData); - dialog = gnome_dialog_new(EDIT_ACCT_STR, + name = gnc_ui_get_account_full_name(acc, ":"); + title = g_strconcat(name, " - ", EDIT_ACCT_STR, NULL); + + dialog = gnome_dialog_new(title, GNOME_STOCK_BUTTON_OK, GNOME_STOCK_BUTTON_CANCEL, NULL); + g_free(name); + g_free(title); + editAccData->dialog = dialog; editAccData->account = acc; diff --git a/src/gnome/dialog-filebox.c b/src/gnome/dialog-filebox.c index 3f60fc48fa..d0db4f98d1 100644 --- a/src/gnome/dialog-filebox.c +++ b/src/gnome/dialog-filebox.c @@ -25,8 +25,9 @@ #include -#include "config.h" +#include "top-level.h" +#include "config.h" #include "FileBox.h" #include "messages.h" #include "util.h" @@ -78,10 +79,13 @@ fileBox(const char * title, const char * filter) fb_info.file_box = GTK_FILE_SELECTION(gtk_file_selection_new(title)); fb_info.file_name = NULL; - +/* hack alert - this was filtering directory names as well as file + * names, so I think we should not do this by default (rgmerk) + */ +#if 0 if (filter != NULL) gtk_file_selection_complete(fb_info.file_box, filter); - +#endif gtk_window_set_modal(GTK_WINDOW(fb_info.file_box), TRUE); gtk_window_set_transient_for(GTK_WINDOW(fb_info.file_box), GTK_WINDOW(gnc_get_ui_data())); diff --git a/src/gnome/dialog-options.c b/src/gnome/dialog-options.c index 752b861153..0541f60072 100644 --- a/src/gnome/dialog-options.c +++ b/src/gnome/dialog-options.c @@ -1,5 +1,5 @@ /********************************************************************\ - * dialog-options.h -- GNOME option handling * + * dialog-options.c -- GNOME option handling * * Copyright (C) 1998,1999 Linas Vepstas * * * * This program is free software; you can redistribute it and/or * @@ -25,14 +25,21 @@ #include "query-user.h" #include "util.h" - /* This static indicates the debugging module that this .o belongs to. */ static short module = MOD_GUI; -static GnomePropertyBox *options_dialog = NULL; - -static void +/********************************************************************\ + * gnc_option_set_ui_value * + * sets the GUI representation of an option with either its * + * current guile value, or its default value * + * * + * Args: option - option structure containing option * + * use_default - if true, use the default value, otherwise * + * use the current value * + * Return: nothing * +\********************************************************************/ +void gnc_option_set_ui_value(GNCOption *option, gboolean use_default) { gboolean bad_value = FALSE; @@ -64,13 +71,27 @@ gnc_option_set_ui_value(GNCOption *option, gboolean use_default) { if (gh_string_p(value)) { - char *string = gh_scm2newstr(gh_call0(getter), NULL); + char *string = gh_scm2newstr(value, NULL); gtk_entry_set_text(GTK_ENTRY(option->widget), string); free(string); } else bad_value = TRUE; } + else if (safe_strcmp(type, "multichoice") == 0) + { + int index; + + index = gnc_option_value_permissible_value_index(option, value); + if (index < 0) + bad_value = TRUE; + else + { + gtk_option_menu_set_history(GTK_OPTION_MENU(option->widget), index); + gtk_object_set_data(GTK_OBJECT(option->widget), "gnc_multichoice_index", + GINT_TO_POINTER(index)); + } + } else { PERR("gnc_option_set_ui_value: Unknown type. Ignoring.\n"); @@ -84,12 +105,14 @@ gnc_option_set_ui_value(GNCOption *option, gboolean use_default) free(type); } -void -_gnc_option_refresh_ui(SCM guile_option) -{ - gnc_option_set_ui_value(gnc_get_option_by_SCM(guile_option), FALSE); -} +/********************************************************************\ + * gnc_option_get_ui_value * + * returns the SCM representation of the GUI option value * + * * + * Args: option - option structure containing option * + * Return: SCM handle to GUI option value * +\********************************************************************/ SCM gnc_option_get_ui_value(GNCOption *option) { @@ -116,9 +139,20 @@ gnc_option_get_ui_value(GNCOption *option) result = gh_str02scm(string); g_free(string); } + else if (safe_strcmp(type, "multichoice") == 0) + { + gpointer _index; + int index; + + _index = gtk_object_get_data(GTK_OBJECT(option->widget), + "gnc_multichoice_index"); + index = GPOINTER_TO_INT(_index); + + result = gnc_option_value_permissible_value(option, index); + } else { - PERR("_gnc_option_get_guile_value: " + PERR("gnc_option_get_ui_value: " "Unknown type for refresh. Ignoring.\n"); } @@ -130,12 +164,15 @@ gnc_option_get_ui_value(GNCOption *option) static void default_button_cb(GtkButton *button, gpointer data) { + GtkWidget *pbox; GNCOption *option = data; gnc_option_set_ui_value(option, TRUE); option->changed = TRUE; - gnome_property_box_changed(options_dialog); + + pbox = gtk_widget_get_toplevel(GTK_WIDGET(button)); + gnome_property_box_changed(GNOME_PROPERTY_BOX(pbox)); } static GtkWidget * @@ -155,19 +192,85 @@ gnc_option_create_default_button(GNCOption *option) static void gnc_option_toggled_cb(GtkToggleButton *button, gpointer data) { + GtkWidget *pbox; GNCOption *option = data; option->changed = TRUE; - gnome_property_box_changed(options_dialog); + + pbox = gtk_widget_get_toplevel(GTK_WIDGET(button)); + gnome_property_box_changed(GNOME_PROPERTY_BOX(pbox)); } static void gnc_option_changed_cb(GtkEditable *editable, gpointer data) { + GtkWidget *pbox; GNCOption *option = data; option->changed = TRUE; - gnome_property_box_changed(options_dialog); + + pbox = gtk_widget_get_toplevel(GTK_WIDGET(editable)); + gnome_property_box_changed(GNOME_PROPERTY_BOX(pbox)); +} + +static void +gnc_option_multichoice_cb(GtkWidget *w, gint index, gpointer data) +{ + GtkWidget *pbox, *omenu; + GNCOption *option = data; + gpointer _current; + gint current; + + _current = gtk_object_get_data(GTK_OBJECT(option->widget), + "gnc_multichoice_index"); + current = GPOINTER_TO_INT(_current); + + if (current == index) + return; + + gtk_option_menu_set_history(GTK_OPTION_MENU(option->widget), index); + gtk_object_set_data(GTK_OBJECT(option->widget), "gnc_multichoice_index", + GINT_TO_POINTER(index)); + + option->changed = TRUE; + + omenu = gtk_object_get_data(GTK_OBJECT(w), "gnc_option_menu"); + pbox = gtk_widget_get_toplevel(omenu); + gnome_property_box_changed(GNOME_PROPERTY_BOX(pbox)); +} + +static GtkWidget * +gnc_option_create_multichoice_widget(GNCOption *option) +{ + GtkWidget *widget; + GNCOptionInfo *info; + int num_values; + int i; + + num_values = gnc_option_value_num_permissible_values(option); + + g_return_val_if_fail(num_values >= 0, NULL); + + info = g_new0(GNCOptionInfo, num_values); + + for (i = 0; i < num_values; i++) + { + info[i].name = gnc_option_value_permissible_value_name(option, i); + info[i].tip = gnc_option_value_permissible_value_description(option, i); + info[i].callback = gnc_option_multichoice_cb; + info[i].user_data = option; + } + + widget = gnc_build_option_menu(info, num_values); + + for (i = 0; i < num_values; i++) + { + free(info[i].name); + free(info[i].tip); + } + g_free(info); + + return widget; } static GtkWidget * @@ -223,6 +326,27 @@ gnc_option_set_ui_widget(GNCOption *option) gnc_option_create_default_button(option), FALSE, FALSE, 0); } + else if (safe_strcmp(type, "multichoice") == 0) + { + GtkWidget *label; + gchar *colon_name; + + colon_name = g_strconcat(name, ":", NULL); + label= gtk_label_new(colon_name); + gtk_misc_set_alignment(GTK_MISC(label), 0.95, 0.5); + g_free(colon_name); + + enclosing = gtk_hbox_new(FALSE, 5); + + value = gnc_option_create_multichoice_widget(option); + option->widget = value; + gnc_option_set_ui_value(option, FALSE); + gtk_box_pack_start(GTK_BOX(enclosing), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(enclosing), value, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(enclosing), + gnc_option_create_default_button(option), + FALSE, FALSE, 0); + } else { PERR("gnc_option_set_ui_widget: Unknown type. Ignoring.\n"); @@ -249,7 +373,8 @@ gnc_options_dialog_add_option(GtkWidget *page, GNCOption *option) } static void -gnc_options_dialog_append_page(GNCOptionSection *section) +gnc_options_dialog_append_page(GnomePropertyBox *propertybox, + GNCOptionSection *section) { GNCOption *option; GtkWidget *page_label; @@ -257,14 +382,14 @@ gnc_options_dialog_append_page(GNCOptionSection *section) gint num_options; gint i; - page_label = gtk_label_new(section->section_name); + page_label = gtk_label_new(gnc_option_section_name(section)); gtk_widget_show(page_label); page_content_box = gtk_vbox_new(FALSE, 5); gtk_container_set_border_width(GTK_CONTAINER(page_content_box), 5); gtk_widget_show(page_content_box); - gnome_property_box_append_page(options_dialog, page_content_box, page_label); + gnome_property_box_append_page(propertybox, page_content_box, page_label); num_options = gnc_option_section_num_options(section); for (i = 0; i < num_options; i++) @@ -274,71 +399,27 @@ gnc_options_dialog_append_page(GNCOptionSection *section) } } -static void -build_options_dialog_contents() + +/********************************************************************\ + * gnc_build_options_dialog_contents * + * builds an options dialog given a property box and an options * + * database * + * * + * Args: propertybox - gnome property box to use * + * odb - option database to use * + * Return: nothing * +\********************************************************************/ +void +gnc_build_options_dialog_contents(GnomePropertyBox *propertybox, + GNCOptionDB *odb) { GNCOptionSection *section; - gint num_sections = gnc_num_option_sections(); + gint num_sections = gnc_option_db_num_sections(odb); gint i; for (i = 0; i < num_sections; i++) { - section = gnc_get_option_section(i); - gnc_options_dialog_append_page(section); + section = gnc_option_db_get_section(odb, i); + gnc_options_dialog_append_page(propertybox, section); } } - -static void -gnc_options_dialog_apply_cb(GnomePropertyBox *propertybox, - gint arg1, gpointer user_data) -{ - if (arg1 == -1) - gnc_options_commit(); -} - -static void -gnc_options_dialog_help_cb(GnomePropertyBox *propertybox, - gint arg1, gpointer user_data) -{ - gnome_ok_dialog("Help on properties"); -} - -/* Options dialog... this should house all of the config options */ -/* like where the docs reside, and whatever else is deemed necessary */ -void -gnc_show_options_dialog() -{ - if (gnc_num_option_sections() == 0) - { - gnc_warning_dialog("No options!"); - return; - } - - if (gnc_options_dirty()) - { - if (options_dialog != NULL) - gtk_widget_destroy(GTK_WIDGET(options_dialog)); - - options_dialog = NULL; - } - - if (options_dialog == NULL) - { - options_dialog = GNOME_PROPERTY_BOX(gnome_property_box_new()); - gnome_dialog_close_hides(GNOME_DIALOG(options_dialog), TRUE); - - build_options_dialog_contents(); - gnc_options_clean(); - - gtk_signal_connect(GTK_OBJECT(options_dialog), "apply", - GTK_SIGNAL_FUNC(gnc_options_dialog_apply_cb), - NULL); - - gtk_signal_connect(GTK_OBJECT(options_dialog), "help", - GTK_SIGNAL_FUNC(gnc_options_dialog_help_cb), - NULL); - } - - gtk_widget_show(GTK_WIDGET(options_dialog)); - gdk_window_raise(GTK_WIDGET(options_dialog)->window); -} diff --git a/src/gnome/dialog-options.h b/src/gnome/dialog-options.h index 8675bc64a2..71f899a10d 100644 --- a/src/gnome/dialog-options.h +++ b/src/gnome/dialog-options.h @@ -26,13 +26,11 @@ #include "option-util.h" -void gnc_show_options_dialog(); +void gnc_build_options_dialog_contents(GnomePropertyBox *propertybox, + GNCOptionDB *odb); -SCM gnc_option_get_ui_value(GNCOption *option); - -/* private */ - -void _gnc_option_refresh_ui(SCM option); +SCM gnc_option_get_ui_value(GNCOption *option); +void gnc_option_set_ui_value(GNCOption *option, gboolean use_default); #endif /* __OPTIONS_DIALOG_H__ */ diff --git a/src/gnome/dialog-utils.c b/src/gnome/dialog-utils.c index 158ce5bcae..312402b26a 100644 --- a/src/gnome/dialog-utils.c +++ b/src/gnome/dialog-utils.c @@ -617,6 +617,34 @@ gnc_ui_get_account_full_balance(Account *account) } +char * gnc_ui_get_account_full_name(Account *account, const char *separator) +{ + Account *a; + gchar **names, *name; + gint num = 0; + + assert(account != NULL); + + /* Figure out how many names */ + for (a = account; a != NULL; a = xaccAccountGetParentAccount(a)) + num++; + + /* allocate the memory, plus one for null termination */ + names = g_new0(gchar *, num + 1); + + /* Make the names array */ + for (a = account, num--; a != NULL; + a = xaccAccountGetParentAccount(a), num--) + names[num] = xaccAccountGetName(a); + + name = g_strjoinv(separator, names); + + g_free(names); + + return name; +} + + char * gnc_ui_get_account_field_value_string(Account *account, int field) { assert(account != NULL); @@ -667,6 +695,22 @@ void gnc_set_tooltip(GtkWidget *w, const gchar *tip) } +static void +gnc_option_menu_cb(GtkWidget *w, gpointer data) +{ + GNCOptionCallback cb; + gpointer _index; + gint index; + + cb = gtk_object_get_data(GTK_OBJECT(w), "gnc_option_cb"); + + _index = gtk_object_get_data(GTK_OBJECT(w), "gnc_option_index"); + index = GPOINTER_TO_INT(_index); + + cb(w, index, data); +} + + /********************************************************************\ * gnc_ui_create_option_button * * create an option button given the option structure * @@ -692,11 +736,25 @@ gnc_build_option_menu(GNCOptionInfo *option_info, gint num_options) for (i = 0; i < num_options; i++) { menu_item = gtk_menu_item_new_with_label(option_info[i].name); + gnc_set_tooltip(menu_item, option_info[i].tip); gtk_widget_show(menu_item); - gtk_signal_connect(GTK_OBJECT(menu_item), "activate", - GTK_SIGNAL_FUNC(option_info[i].callback), - option_info[i].user_data); + gtk_object_set_data(GTK_OBJECT(menu_item), + "gnc_option_cb", + option_info[i].callback); + + gtk_object_set_data(GTK_OBJECT(menu_item), + "gnc_option_index", + GINT_TO_POINTER(i)); + + gtk_object_set_data(GTK_OBJECT(menu_item), + "gnc_option_menu", + omenu); + + if (option_info[i].callback != NULL) + gtk_signal_connect(GTK_OBJECT(menu_item), "activate", + GTK_SIGNAL_FUNC(gnc_option_menu_cb), + option_info[i].user_data); gtk_menu_append(GTK_MENU(menu), menu_item); } diff --git a/src/gnome/dialog-utils.h b/src/gnome/dialog-utils.h index 8699f0c7eb..00f9c344ef 100644 --- a/src/gnome/dialog-utils.h +++ b/src/gnome/dialog-utils.h @@ -82,12 +82,17 @@ enum }; +/* option button callback function */ +typedef void (*GNCOptionCallback) (GtkWidget *, gint index, + gpointer user_data); + /* Structure for building option buttons */ typedef struct _GNCOptionInfo GNCOptionInfo; struct _GNCOptionInfo { char *name; - gpointer callback; + char *tip; + GNCOptionCallback callback; gpointer user_data; }; @@ -95,6 +100,8 @@ struct _GNCOptionInfo /**** PROTOTYPES *************************************************/ char * gnc_ui_get_account_field_name(int field); +char * gnc_ui_get_account_full_name(Account *account, const char *separator); + char * gnc_ui_get_account_field_value_string(Account *account, int field); double gnc_ui_get_account_full_balance(Account *account); diff --git a/src/gnome/global-options.c b/src/gnome/global-options.c new file mode 100644 index 0000000000..ac5fe9050c --- /dev/null +++ b/src/gnome/global-options.c @@ -0,0 +1,273 @@ +/********************************************************************\ + * global-options.c -- GNOME global option handling * + * Copyright (C) 1998,1999 Linas Vepstas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * +\********************************************************************/ + +#include + +#include "global-options.h" +#include "dialog-options.h" +#include "dialog-utils.h" +#include "option-util.h" +#include "query-user.h" +#include "guile-util.h" +#include "util.h" + +/* This static indicates the debugging module that this .o belongs to. */ +static short module = MOD_GUI; + +static GNCOptionDB *global_options = NULL; +static SCM guile_global_options = SCM_UNDEFINED; + + +/********************************************************************\ + * gnc_options_init * + * initialize the options structures from the guile side * + * * + * Args: none * + * Returns: nothing * +\********************************************************************/ +void +gnc_options_init() +{ + SCM func = gh_eval_str("gnc:send-global-options"); + + if (gh_procedure_p(func)) + gh_call0(func); + else + { + PERR("gnc_options_init: no guile options!"); + } + + global_options = gnc_option_db_new(); + gnc_option_db_init(global_options, guile_global_options); +} + + +/********************************************************************\ + * gnc_options_shutdown * + * unregister the scheme options and free the structure memory * + * * + * Args: none * + * Returns: nothing * +\********************************************************************/ +void +gnc_options_shutdown() +{ + gnc_option_db_destroy(global_options); + global_options = NULL; + + gnc_unregister_c_side_scheme_ptr(guile_global_options); + guile_global_options = SCM_UNDEFINED; +} + + +/********************************************************************\ + * gnc_register_option_change_callback * + * register a callback to be called whenever an option changes * + * this is rather heavy-weight, since all handlers will be called * + * whenever any option changes. We may need to refine it later. * + * * + * Args: callback - the callback function * + * Returns: nothing * +\********************************************************************/ +void +gnc_register_option_change_callback(OptionChangeCallback callback, + gpointer user_data) +{ + gnc_option_db_register_change_callback(global_options, callback, user_data); +} + + +/********************************************************************\ + * gnc_get_option_by_name * + * returns an option given section name and name * + * * + * Args: section_name - name of section to search for * + * name - name to search for * + * Returns: given option, or NULL if none * +\********************************************************************/ +GNCOption * +gnc_get_option_by_name(char *section_name, char *name) +{ + return gnc_option_db_get_option_by_name(global_options, + section_name, name); +} + + +/********************************************************************\ + * gnc_get_option_by_SCM * + * returns an option given SCM handle. Uses section and name. * + * * + * Args: guile_option - SCM handle of option * + * Returns: given option, or NULL if none * +\********************************************************************/ +GNCOption * +gnc_get_option_by_SCM(SCM guile_option) +{ + return gnc_option_db_get_option_by_SCM(global_options, guile_option); +} + + +/********************************************************************\ + * gnc_lookup_boolean_option * + * looks up a boolean option. If present, returns its value, * + * otherwise returns the default. * + * * + * Args: section - section name of option * + * name - name of option * + * default - default value if not found * + * Return: gboolean option value * +\********************************************************************/ +gboolean +gnc_lookup_boolean_option(char *section, char *name, gboolean default_value) +{ + return gnc_option_db_lookup_boolean_option(global_options, section, + name, default_value); +} + + +/********************************************************************\ + * gnc_lookup_string_option * + * looks up a string option. If present, returns its malloc'ed * + * value, otherwise returns the strdup'ed default, or NULL if * + * default was NULL. * + * * + * Args: section - section name of option * + * name - name of option * + * default - default value if not found * + * Return: char * option value * +\********************************************************************/ +char * +gnc_lookup_string_option(char *section, char *name, char *default_value) +{ + return gnc_option_db_lookup_string_option(global_options, section, + name, default_value); +} + + +/********************************************************************\ + * gnc_lookup_multichoice_option * + * looks up a multichoice option. If present, returns its * + * name as a malloc'ed string * + * value, otherwise returns the strdup'ed default, or NULL if * + * default was NULL. * + * * + * Args: section - section name of option * + * name - name of option * + * default - default value if not found * + * Return: char * option value * +\********************************************************************/ +char * +gnc_lookup_multichoice_option(char *section, char *name, char *default_value) +{ + return gnc_option_db_lookup_multichoice_option(global_options, section, + name, default_value); +} + + +/********************************************************************\ + * _gnc_option_refresh_ui * + * sets the GUI representation of an option with its current * + * current guile value. this is intended for use by guile only * + * * + * Args: option - SCM handle to option * + * Return: nothing * +\********************************************************************/ +void +_gnc_option_refresh_ui(SCM guile_option) +{ + GNCOption *option; + + option = gnc_option_db_get_option_by_SCM(global_options, guile_option); + gnc_option_set_ui_value(option, FALSE); +} + + +/********************************************************************\ + * _gnc_register_global_options * + * registers the global options. Intended to be called from guile.* + * * + * Args: options - the guile global options * + * Returns: nothing * +\********************************************************************/ +void +_gnc_register_global_options(SCM options) +{ + guile_global_options = options; + gnc_register_c_side_scheme_ptr(options); +} + + +static void +gnc_options_dialog_apply_cb(GnomePropertyBox *propertybox, + gint arg1, gpointer user_data) +{ + if (arg1 == -1) + gnc_option_db_commit(global_options); +} + +static void +gnc_options_dialog_help_cb(GnomePropertyBox *propertybox, + gint arg1, gpointer user_data) +{ + gnome_ok_dialog("Help on properties"); +} + +/* Options dialog... this should house all of the config options */ +/* like where the docs reside, and whatever else is deemed necessary */ +void +gnc_show_options_dialog() +{ + static GnomePropertyBox *options_dialog = NULL; + + if (gnc_option_db_num_sections(global_options) == 0) + { + gnc_warning_dialog("No options!"); + return; + } + + if (gnc_option_db_dirty(global_options)) + { + if (options_dialog != NULL) + gtk_widget_destroy(GTK_WIDGET(options_dialog)); + + options_dialog = NULL; + } + + if (options_dialog == NULL) + { + options_dialog = GNOME_PROPERTY_BOX(gnome_property_box_new()); + gnome_dialog_close_hides(GNOME_DIALOG(options_dialog), TRUE); + + gnc_build_options_dialog_contents(options_dialog, global_options); + gnc_option_db_clean(global_options); + + gtk_window_set_title(GTK_WINDOW(options_dialog), "GnuCash Preferences"); + + gtk_signal_connect(GTK_OBJECT(options_dialog), "apply", + GTK_SIGNAL_FUNC(gnc_options_dialog_apply_cb), + NULL); + + gtk_signal_connect(GTK_OBJECT(options_dialog), "help", + GTK_SIGNAL_FUNC(gnc_options_dialog_help_cb), + NULL); + } + + gtk_widget_show(GTK_WIDGET(options_dialog)); + gdk_window_raise(GTK_WIDGET(options_dialog)->window); +} diff --git a/src/gnome/global-options.h b/src/gnome/global-options.h new file mode 100644 index 0000000000..976440dc77 --- /dev/null +++ b/src/gnome/global-options.h @@ -0,0 +1,53 @@ +/********************************************************************\ + * global-options.h -- GNOME global option handling * + * Copyright (C) 1998,1999 Linas Vepstas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * +\********************************************************************/ + +#ifndef __GLOBAL_OPTIONS_H__ +#define __GLOBAL_OPTIONS_H__ + +#include + +#include "option-util.h" + + +void gnc_options_init(); +void gnc_options_shutdown(); +void gnc_show_options_dialog(); + +void gnc_register_option_change_callback(OptionChangeCallback callback, + gpointer user_data); + +GNCOption * gnc_get_option_by_name(char *section_name, char *name); +GNCOption * gnc_get_option_by_SCM(SCM guile_option); + +gboolean gnc_lookup_boolean_option(char *section, char *name, + gboolean default_value); + +char * gnc_lookup_string_option(char *section, char *name, + char *default_value); + +char * gnc_lookup_multichoice_option(char *section, char *name, + char *default_value); + +/* private */ + +void _gnc_option_refresh_ui(SCM option); +void _gnc_register_global_options(SCM options); + + +#endif /* __GLOBAL_OPTIONS_H__ */ diff --git a/src/gnome/option-util.c b/src/gnome/option-util.c index 5dc509fb0b..0711f23d4e 100644 --- a/src/gnome/option-util.c +++ b/src/gnome/option-util.c @@ -28,6 +28,29 @@ /****** Structures *************************************************/ +struct _GNCOptionSection +{ + char * section_name; + + GSList * options; +}; + +struct _GNCOptionDB +{ + GSList *option_sections; + + gboolean options_dirty; + + GSList *change_callbacks; +}; + +typedef struct _ChangeCBInfo ChangeCBInfo; +struct _ChangeCBInfo +{ + OptionChangeCallback callback; + gpointer user_data; +}; + typedef struct _Getters Getters; struct _Getters { @@ -40,65 +63,114 @@ struct _Getters SCM setter; SCM default_getter; SCM value_validator; + SCM permissible_values; }; /****** Globals ****************************************************/ -static Getters getters = {0, 0, 0, 0, 0, 0, 0, 0, 0}; - -static GSList *option_sections = NULL; -static gboolean options_dirty = FALSE; - -static GSList *change_callbacks = NULL; +static Getters getters = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /* This static indicates the debugging module this .o belongs to. */ static short module = MOD_GUI; +static GPtrArray *option_dbs = NULL; + /*******************************************************************/ -/********************************************************************\ - * gnc_options_init * - * initialize the options structures from the guile side * - * * - * Args: none * - * Returns: nothing * -\********************************************************************/ -void -gnc_options_init() +static GNCOptionDBHandle +gnc_option_db_get_handle(GNCOptionDB *odb) { - SCM func = gh_eval_str("gnc:send-ui-options"); + int i; - option_sections = NULL; - options_dirty = FALSE; - change_callbacks = NULL; + for (i = 0; i < option_dbs->len; i++) + if (odb == g_ptr_array_index(option_dbs, i)) + return i; - if (gh_procedure_p(func)) - gh_call0(func); - else - { - PERR("gnc_options_init: no guile options!"); - } + return -1; } /********************************************************************\ - * gnc_options_shutdown * - * unregister the scheme options and free the structure memory * + * gnc_option_db_new * + * allocate a new option database and initialize its values * * * * Args: none * + * Returns: a new option database * +\********************************************************************/ +GNCOptionDB * +gnc_option_db_new() +{ + GNCOptionDB *odb; + + odb = g_new0(GNCOptionDB, 1); + + odb->option_sections = NULL; + odb->options_dirty = FALSE; + odb->change_callbacks = NULL; + + if (option_dbs == NULL) + option_dbs = g_ptr_array_new(); + + g_ptr_array_add(option_dbs, odb); + + return odb; +} + + +/********************************************************************\ + * gnc_option_db_init * + * initialize the options structures from the guile side * + * * + * Args: odb - the option database to initialize * + * options - the guile options to initialize with * * Returns: nothing * \********************************************************************/ void -gnc_options_shutdown() +gnc_option_db_init(GNCOptionDB *odb, SCM options) +{ + SCM func = gh_eval_str("gnc:send-options"); + GNCOptionDBHandle handle; + + handle = gnc_option_db_get_handle(odb); + if (handle < 0) + { + PERR("Can't find option database in list.\n"); + return; + } + + gh_call2(func, gh_int2scm(handle), options); +} + + +static void +gnc_option_free_cbinfo(gpointer data, gpointer not_used) +{ + g_free(data); +} + +/********************************************************************\ + * gnc_option_db_destroy * + * unregister the scheme options and free all the memory * + * associated with an option database, including the database * + * itself * + * * + * Args: options database to destroy * + * Returns: nothing * +\********************************************************************/ +void +gnc_option_db_destroy(GNCOptionDB *odb) { GSList *section_node; GSList *option_node; GNCOptionSection *section; GNCOption *option; - section_node = option_sections; + if (odb == NULL) + return; + + section_node = odb->option_sections; while (section_node != NULL) { section = section_node->data; @@ -121,44 +193,73 @@ gnc_options_shutdown() section_node = section_node->next; } - if (option_sections != NULL) - g_slist_free(section->options); + g_slist_foreach(odb->change_callbacks, gnc_option_free_cbinfo, NULL); - option_sections = NULL; - options_dirty = FALSE; + g_slist_free(odb->option_sections); + g_slist_free(odb->change_callbacks); + + odb->option_sections = NULL; + odb->change_callbacks = NULL; + odb->options_dirty = FALSE; + + if (!g_ptr_array_remove(option_dbs, odb)) + { + PERR("Option database not present in list.\n"); + } + + if (option_dbs->len == 0) + { + g_ptr_array_free(option_dbs, FALSE); + option_dbs = NULL; + } + + g_free(odb); } /********************************************************************\ - * gnc_register_option_change_callback * + * gnc_option_db_register_change_callback * * register a callback to be called whenever an option changes * * this is rather heavy-weight, since all handlers will be called * * whenever any option changes. We may need to refine it later. * * * - * Args: callback - the callback function * + * Args: odb - the option database to register with * + * callback - the callback function to register * * Returns: nothing * \********************************************************************/ void -gnc_register_option_change_callback(void (*callback) (void)) +gnc_option_db_register_change_callback(GNCOptionDB *odb, + OptionChangeCallback callback, + gpointer data) { - change_callbacks = g_slist_prepend(change_callbacks, callback); + ChangeCBInfo *cbinfo; + + assert(odb != NULL); + + cbinfo = g_new0(ChangeCBInfo, 1); + cbinfo->callback = callback; + cbinfo->user_data = data; + + odb->change_callbacks = g_slist_prepend(odb->change_callbacks, cbinfo); } + static void -gnc_call_option_change_callbacks() +gnc_call_option_change_callbacks(GNCOptionDB *odb) { - GSList *node = change_callbacks; - void (*callback) (void); + GSList *node = odb->change_callbacks; + ChangeCBInfo *cbinfo; while (node != NULL) { - callback = node->data; - (*callback)(); + cbinfo = node->data; + (cbinfo->callback)(cbinfo->user_data); node = node->next; } } + static void initialize_getters() { @@ -167,18 +268,20 @@ initialize_getters() if (getters_initialized) return; - getters.section = gh_eval_str("gnc:configuration-option-section"); - getters.name = gh_eval_str("gnc:configuration-option-name"); - getters.type = gh_eval_str("gnc:configuration-option-type"); - getters.sort_tag = gh_eval_str("gnc:configuration-option-sort-tag"); + getters.section = gh_eval_str("gnc:option-section"); + getters.name = gh_eval_str("gnc:option-name"); + getters.type = gh_eval_str("gnc:option-type"); + getters.sort_tag = gh_eval_str("gnc:option-sort-tag"); getters.documentation = - gh_eval_str("gnc:configuration-option-documentation"); - getters.getter = gh_eval_str("gnc:configuration-option-getter"); - getters.setter = gh_eval_str("gnc:configuration-option-setter"); + gh_eval_str("gnc:option-documentation"); + getters.getter = gh_eval_str("gnc:option-getter"); + getters.setter = gh_eval_str("gnc:option-setter"); getters.default_getter = - gh_eval_str("gnc:configuration-option-default-getter"); + gh_eval_str("gnc:option-default-getter"); getters.value_validator = - gh_eval_str("gnc:configuration-option-value-validator"); + gh_eval_str("gnc:option-value-validator"); + getters.permissible_values = + gh_eval_str("gnc:option-permissible-values"); getters_initialized = TRUE; } @@ -343,6 +446,198 @@ gnc_option_value_validator(GNCOption *option) } +/********************************************************************\ + * gnc_option_value_num_permissible_values * + * returns the number of permissible values in the option, or * + * -1 if there are no values available. * + * * + * Args: option - the GNCOption * + * Returns: number of permissible options or -1 * +\********************************************************************/ +int +gnc_option_value_num_permissible_values(GNCOption *option) +{ + SCM values; + + initialize_getters(); + + values = gnc_guile_call1_to_list(getters.permissible_values, + option->guile_option); + + if (values == SCM_UNDEFINED) + return -1; + + return gh_length(values); +} + + +/********************************************************************\ + * gnc_option_value_permissible_value_index * + * returns the index of the permissible value matching the * + * provided value, or -1 if it couldn't be found * + * * + * Args: option - the GNCOption * + * value - the SCM handle of the value * + * Returns: index of permissible value, or -1 * +\********************************************************************/ +int +gnc_option_value_permissible_value_index(GNCOption *option, SCM search_value) +{ + SCM values, vector, value; + int num_values, i; + + if (!gh_symbol_p(search_value)) + return -1; + + initialize_getters(); + + values = gnc_guile_call1_to_list(getters.permissible_values, + option->guile_option); + + if (values == SCM_UNDEFINED) + return -1; + + num_values = gh_length(values); + + for (i = 0; i < num_values; i++) + { + vector = gh_list_ref(values, gh_int2scm(i)); + if (!gh_vector_p(vector)) + continue; + + value = gh_vector_ref(vector, gh_int2scm(0)); + + if (gh_eq_p(value, search_value)) + return i; + } + + return -1; +} + + +/********************************************************************\ + * gnc_option_value_permissible_value * + * returns the SCM handle to the indexth permissible value in the * + * option, or SCM_UNDEFINED if the index was out of range or * + * there was some other problem. * + * * + * Args: option - the GNCOption * + * index - the index of the permissible value * + * Returns: SCM handle to option value or SCM_UNDEFINED * +\********************************************************************/ +SCM +gnc_option_value_permissible_value(GNCOption *option, int index) +{ + SCM values, vector, value; + + if (index < 0) + return SCM_UNDEFINED; + + initialize_getters(); + + values = gnc_guile_call1_to_list(getters.permissible_values, + option->guile_option); + + if (values == SCM_UNDEFINED) + return SCM_UNDEFINED; + + if (index >= gh_length(values)) + return SCM_UNDEFINED; + + vector = gh_list_ref(values, gh_int2scm(index)); + if (!gh_vector_p(vector)) + return SCM_UNDEFINED; + + value = gh_vector_ref(vector, gh_int2scm(0)); + if (!gh_symbol_p(value)) + return SCM_UNDEFINED; + + return value; +} + + +/********************************************************************\ + * gnc_option_value_permissible_value_name * + * returns the malloc'd name of the indexth permissible value in * + * the option, or NULL if the index was out of range or there are * + * no values available. * + * * + * Args: option - the GNCOption * + * index - the index of the permissible value * + * Returns: malloc'd name of permissible value or NULL * +\********************************************************************/ +char * +gnc_option_value_permissible_value_name(GNCOption *option, int index) +{ + SCM values, vector, name; + + if (index < 0) + return NULL; + + initialize_getters(); + + values = gnc_guile_call1_to_list(getters.permissible_values, + option->guile_option); + + if (values == SCM_UNDEFINED) + return NULL; + + if (index >= gh_length(values)) + return NULL; + + vector = gh_list_ref(values, gh_int2scm(index)); + if (!gh_vector_p(vector)) + return NULL; + + name = gh_vector_ref(vector, gh_int2scm(1)); + if (!gh_string_p(name)) + return NULL; + + return gh_scm2newstr(name, NULL); +} + + +/********************************************************************\ + * gnc_option_value_permissible_value_description * + * returns the malloc'd description of the indexth permissible * + * value in the option, or NULL if the index was out of range or * + * there are no values available. * + * * + * Args: option - the GNCOption * + * index - the index of the permissible value * + * Returns: malloc'd description of permissible value or NULL * +\********************************************************************/ +char * +gnc_option_value_permissible_value_description(GNCOption *option, int index) +{ + SCM values, vector, help; + + if (index < 0) + return NULL; + + initialize_getters(); + + values = gnc_guile_call1_to_list(getters.permissible_values, + option->guile_option); + + if (values == SCM_UNDEFINED) + return NULL; + + if (index >= gh_length(values)) + return NULL; + + vector = gh_list_ref(values, gh_int2scm(index)); + if (!gh_vector_p(vector)) + return NULL; + + help = gh_vector_ref(vector, gh_int2scm(2)); + if (!gh_string_p(help)) + return NULL; + + return gh_scm2newstr(help, NULL); +} + + static gint compare_sections(gconstpointer a, gconstpointer b) { @@ -394,47 +689,58 @@ compare_option_names(gconstpointer a, gconstpointer b) } #endif + /********************************************************************\ - * gnc_options_dirty * - * returns true if guile has registered more options since the * - * options dialog was created. * + * gnc_option_db_dirty * + * returns true if guile has registered more options into the * + * database since the last time the database was cleaned. * * * * Returns: dirty flag * \********************************************************************/ gboolean -gnc_options_dirty() +gnc_option_db_dirty(GNCOptionDB *odb) { - return options_dirty; + assert(odb != NULL); + + return odb->options_dirty; } /********************************************************************\ - * gnc_options_clean * - * resets the dirty flag * + * gnc_option_db_clean * + * resets the dirty flag of the option database * * * \********************************************************************/ void -gnc_options_clean() +gnc_option_db_clean(GNCOptionDB *odb) { - options_dirty = FALSE; + assert(odb != NULL); + + odb->options_dirty = FALSE; } /********************************************************************\ - * gnc_register_option_ui * - * registers an option with the GUI. Intended to be called from * - * guile. * + * _gnc_option_db_register_option * + * registers an option with an option database. Intended to be * + * called from guile. * * * - * Args: option - the guile option * + * Args: odb - the option database * + * option - the guile option * * Returns: nothing * \********************************************************************/ void -_gnc_register_option_ui(SCM guile_option) +_gnc_option_db_register_option(GNCOptionDBHandle handle, SCM guile_option) { + GNCOptionDB *odb; GNCOption *option; GNCOptionSection *section; - options_dirty = TRUE; + odb = g_ptr_array_index(option_dbs, handle); + + assert(odb != NULL); + + odb->options_dirty = TRUE; /* Make the option structure */ option = g_new0(GNCOption, 1); @@ -447,7 +753,7 @@ _gnc_register_option_ui(SCM guile_option) if (option->guile_option_id == SCM_UNDEFINED) { g_free(option); - PERR("_gnc_register_option_ui: couldn't register\n"); + PERR("_gnc_option_db_register_option: couldn't register\n"); return; } @@ -460,7 +766,7 @@ _gnc_register_option_ui(SCM guile_option) { GSList *old; - old = g_slist_find_custom(option_sections, section, compare_sections); + old = g_slist_find_custom(odb->option_sections, section, compare_sections); if (old != NULL) { @@ -470,8 +776,8 @@ _gnc_register_option_ui(SCM guile_option) section = old->data; } else - option_sections = g_slist_insert_sorted(option_sections, section, - compare_sections); + odb->option_sections = g_slist_insert_sorted(odb->option_sections, + section, compare_sections); } section->options = g_slist_insert_sorted(section->options, option, @@ -480,30 +786,46 @@ _gnc_register_option_ui(SCM guile_option) /********************************************************************\ - * gnc_num_options_sections * - * returns the number of option sections registered so far * + * gnc_option_db_num_sections * + * returns the number of option sections registered so far in the * + * database * * * - * Args: none * + * Args: odb - the database to count sections for * * Returns: number of option sections * \********************************************************************/ guint -gnc_num_option_sections() +gnc_option_db_num_sections(GNCOptionDB *odb) { - return g_slist_length(option_sections); + return g_slist_length(odb->option_sections); } /********************************************************************\ - * gnc_get_option_section * - * returns the ith option section, or NULL * + * gnc_option_db_get_section * + * returns the ith option section in the database, or NULL * * * - * Args: i - index of section * + * Args: odb - the option database * + * i - index of section * * Returns: ith option sectioin * \********************************************************************/ GNCOptionSection * -gnc_get_option_section(gint i) +gnc_option_db_get_section(GNCOptionDB *odb, gint i) { - return g_slist_nth_data(option_sections, i); + return g_slist_nth_data(odb->option_sections, i); +} + + +/********************************************************************\ + * gnc_option_section_name * + * returns the name of the options section * + * * + * Args: section - section to get name of * + * Returns: name of option section * +\********************************************************************/ +char * +gnc_option_section_name(GNCOptionSection *section) +{ + return section->section_name; } @@ -530,6 +852,7 @@ gnc_option_section_num_options(GNCOptionSection *section) * Returns: ith option in section * \********************************************************************/ GNCOption * + gnc_get_option_section_option(GNCOptionSection *section, int i) { return g_slist_nth_data(section->options, i); @@ -537,15 +860,17 @@ gnc_get_option_section_option(GNCOptionSection *section, int i) /********************************************************************\ - * gnc_get_option_by_name * + * gnc_option_db_get_option_by_name * * returns an option given section name and name * * * - * Args: section_name - name of section to search for * + * Args: odb - option database to search in * + * section_name - name of section to search for * * name - name to search for * * Returns: given option, or NULL if none * \********************************************************************/ GNCOption * -gnc_get_option_by_name(char *section_name, char *name) +gnc_option_db_get_option_by_name(GNCOptionDB *odb, char *section_name, + char *name) { GSList *section_node; GSList *option_node; @@ -557,7 +882,7 @@ gnc_get_option_by_name(char *section_name, char *name) section_key.section_name = section_name; - section_node = g_slist_find_custom(option_sections, §ion_key, + section_node = g_slist_find_custom(odb->option_sections, §ion_key, compare_sections); if (section_node == NULL) @@ -585,14 +910,15 @@ gnc_get_option_by_name(char *section_name, char *name) /********************************************************************\ - * gnc_get_option_by_SCM * + * gnc_option_db_get_option_by_SCM * * returns an option given SCM handle. Uses section and name. * * * - * Args: guile_option - SCM handle of option * + * Args: odb - option database to search in * + * guile_option - SCM handle of option * * Returns: given option, or NULL if none * \********************************************************************/ GNCOption * -gnc_get_option_by_SCM(SCM guile_option) +gnc_option_db_get_option_by_SCM(GNCOptionDB *odb, SCM guile_option) { GNCOption option_key; GNCOption *option; @@ -604,7 +930,7 @@ gnc_get_option_by_SCM(SCM guile_option) section_name = gnc_option_section(&option_key); name = gnc_option_name(&option_key); - option = gnc_get_option_by_name(section_name, name); + option = gnc_option_db_get_option_by_name(odb, section_name, name); if (section_name != NULL) free(section_name); @@ -684,15 +1010,15 @@ gnc_commit_option(GNCOption *option) /********************************************************************\ - * gnc_options_commit * + * gnc_option_db_commit * * commits the options which have changed, and which are valid * * for those which are not valid, error dialogs are shown. * * * - * Args: none * + * Args: odb - option database to commit * * Return: nothing * \********************************************************************/ void -gnc_options_commit() +gnc_option_db_commit(GNCOptionDB *odb) { GSList *section_node; GSList *option_node; @@ -700,7 +1026,9 @@ gnc_options_commit() GNCOption *option; gboolean changed_something = FALSE; - section_node = option_sections; + assert(odb != NULL); + + section_node = odb->option_sections; while (section_node != NULL) { section = section_node->data; @@ -724,28 +1052,30 @@ gnc_options_commit() } if (changed_something) - gnc_call_option_change_callbacks(); + gnc_call_option_change_callbacks(odb); } /********************************************************************\ - * gnc_lookup_boolean_option * + * gnc_option_db_lookup_boolean_option * * looks up a boolean option. If present, returns its value, * * otherwise returns the default. * * * - * Args: section - section name of option * + * Args: odb - option database to search in * + * section - section name of option * * name - name of option * * default - default value if not found * * Return: gboolean option value * \********************************************************************/ gboolean -gnc_lookup_boolean_option(char *section, char *name, gboolean default_value) +gnc_option_db_lookup_boolean_option(GNCOptionDB *odb, char *section, + char *name, gboolean default_value) { GNCOption *option; SCM getter; SCM value; - option = gnc_get_option_by_name(section, name); + option = gnc_option_db_get_option_by_name(odb, section, name); if (option == NULL) return default_value; @@ -764,24 +1094,26 @@ gnc_lookup_boolean_option(char *section, char *name, gboolean default_value) /********************************************************************\ - * gnc_lookup_string_option * + * gnc_option_db_lookup_string_option * * looks up a string option. If present, returns its malloc'ed * * value, otherwise returns the strdup'ed default, or NULL if * * default was NULL. * * * - * Args: section - section name of option * + * Args: odb - option database to search in * + * section - section name of option * * name - name of option * * default - default value if not found * * Return: char * option value * \********************************************************************/ char * -gnc_lookup_string_option(char *section, char *name, char *default_value) +gnc_option_db_lookup_string_option(GNCOptionDB *odb, char *section, + char *name, char *default_value) { GNCOption *option; SCM getter; SCM value; - option = gnc_get_option_by_name(section, name); + option = gnc_option_db_get_option_by_name(odb, section, name); if (option != NULL) { @@ -799,3 +1131,44 @@ gnc_lookup_string_option(char *section, char *name, char *default_value) return strdup(default_value); } + + +/********************************************************************\ + * gnc_option_db_lookup_multichoice_option * + * looks up a multichoice option. If present, returns its * + * name as a malloc'ed string * + * value, otherwise returns the strdup'ed default, or NULL if * + * default was NULL. * + * * + * Args: odb - option database to search in * + * section - section name of option * + * name - name of option * + * default - default value if not found * + * Return: char * option value * +\********************************************************************/ +char * +gnc_option_db_lookup_multichoice_option(GNCOptionDB *odb, char *section, + char *name, char *default_value) +{ + GNCOption *option; + SCM getter; + SCM value; + + option = gnc_option_db_get_option_by_name(odb, section, name); + + if (option != NULL) + { + getter = gnc_option_getter(option); + if (getter != SCM_UNDEFINED) + { + value = gh_call0(getter); + if (gh_symbol_p(value)) + return gh_symbol2newstr(value, NULL); + } + } + + if (default_value == NULL) + return NULL; + + return strdup(default_value); +} diff --git a/src/gnome/option-util.h b/src/gnome/option-util.h index 806e0a436e..2be0eb0b19 100644 --- a/src/gnome/option-util.h +++ b/src/gnome/option-util.h @@ -23,7 +23,6 @@ #include #include - typedef struct _GNCOption GNCOption; struct _GNCOption { @@ -41,20 +40,21 @@ struct _GNCOption }; typedef struct _GNCOptionSection GNCOptionSection; -struct _GNCOptionSection -{ - char * section_name; +typedef struct _GNCOptionDB GNCOptionDB; - GSList * options; -}; +typedef int GNCOptionDBHandle; +typedef void (*OptionChangeCallback)(gpointer user_data); /***** Prototypes ********************************************************/ -void gnc_options_init(); -void gnc_options_shutdown(); +GNCOptionDB * gnc_option_db_new(); +void gnc_option_db_init(GNCOptionDB *odb, SCM options); +void gnc_option_db_destroy(GNCOptionDB *odb); -void gnc_register_option_change_callback(void (*callback) (void)); +void gnc_option_db_register_change_callback(GNCOptionDB *odb, + OptionChangeCallback callback, + gpointer data); char * gnc_option_section(GNCOption *option); char * gnc_option_name(GNCOption *option); @@ -65,30 +65,48 @@ SCM gnc_option_getter(GNCOption *option); SCM gnc_option_setter(GNCOption *option); SCM gnc_option_default_getter(GNCOption *option); SCM gnc_option_value_validator(GNCOption *option); +int gnc_option_value_num_permissible_values(GNCOption *option); +int gnc_option_value_permissible_value_index(GNCOption *option, SCM value); +SCM gnc_option_value_permissible_value(GNCOption *option, int index); +char * gnc_option_value_permissible_value_name(GNCOption *option, int index); +char * gnc_option_value_permissible_value_description(GNCOption *option, + int index); -guint gnc_num_option_sections(); -guint gnc_option_section_num_options(GNCOptionSection *section); +guint gnc_option_db_num_sections(GNCOptionDB *odb); -GNCOptionSection * gnc_get_option_section(gint i); +char * gnc_option_section_name(GNCOptionSection *section); +guint gnc_option_section_num_options(GNCOptionSection *section); + +GNCOptionSection * gnc_option_db_get_section(GNCOptionDB *odb, gint i); GNCOption * gnc_get_option_section_option(GNCOptionSection *section, int i); -GNCOption * gnc_get_option_by_name(char *section_name, char *name); -GNCOption * gnc_get_option_by_SCM(SCM guile_option); -gboolean gnc_options_dirty(); -void gnc_options_clean(); +GNCOption * gnc_option_db_get_option_by_name(GNCOptionDB *odb, + char *section_name, char *name); -void gnc_options_commit(); +GNCOption * gnc_option_db_get_option_by_SCM(GNCOptionDB *odb, + SCM guile_option); -gboolean gnc_lookup_boolean_option(char *section, char *name, - gboolean default_value); -char * gnc_lookup_string_option(char *section, char *name, - char *default_value); +gboolean gnc_option_db_dirty(GNCOptionDB *odb); +void gnc_option_db_clean(GNCOptionDB *odb); +void gnc_option_db_commit(GNCOptionDB *odb); + +gboolean gnc_option_db_lookup_boolean_option(GNCOptionDB *odb, + char *section, char *name, + gboolean default_value); + +char * gnc_option_db_lookup_string_option(GNCOptionDB *odb, char *section, + char *name, char *default_value); + +char * gnc_option_db_lookup_multichoice_option(GNCOptionDB *odb, + char *section, char *name, + char *default_value); /* private */ -void _gnc_register_option_ui(SCM guile_option); +void _gnc_option_db_register_option(GNCOptionDBHandle handle, + SCM guile_option); #endif /* __OPTION_UTIL_H__ */ diff --git a/src/gnome/reconcile-list.c b/src/gnome/reconcile-list.c index 1af71aa810..d671d24171 100644 --- a/src/gnome/reconcile-list.c +++ b/src/gnome/reconcile-list.c @@ -104,6 +104,8 @@ gnc_reconcile_list_init(GNCReconcileList *list) list->num_columns = 0; list->reconciled = g_hash_table_new(NULL, NULL); list->current_row = -1; + list->current_split = NULL; + list->no_toggle = FALSE; while (titles[list->num_columns] != NULL) list->num_columns++; @@ -138,7 +140,6 @@ gnc_reconcile_list_init(GNCReconcileList *list) GtkStyle *style = gtk_widget_get_style(GTK_WIDGET(list)); list->reconciled_style = gtk_style_copy(style); - list->normal_style = gtk_style_copy(style); #if !USE_NO_COLOR style = list->reconciled_style; @@ -151,12 +152,6 @@ gnc_reconcile_list_init(GNCReconcileList *list) gdk_colormap_alloc_color(cm, &style->fg[GTK_STATE_NORMAL], FALSE, TRUE); style->fg[GTK_STATE_SELECTED] = style->fg[GTK_STATE_NORMAL]; - style->bg[GTK_STATE_SELECTED] = style->white; - - list->normal_style->fg[GTK_STATE_SELECTED] = style->black; - list->normal_style->bg[GTK_STATE_SELECTED] = style->white; - - gtk_widget_set_style(GTK_WIDGET(list), list->normal_style); #endif } } @@ -221,10 +216,18 @@ gnc_reconcile_list_toggle(GNCReconcileList *list) assert(GTK_IS_GNC_RECONCILE_LIST(list)); assert(list->reconciled != NULL); + if (list->no_toggle) + return; + row = list->current_row; split = gtk_clist_get_row_data(GTK_CLIST(list), row); current = g_hash_table_lookup(list->reconciled, split); + if (list->current_split != split) + list->current_split = split; + else + list->current_split = NULL; + if (current == NULL) { reconciled = TRUE; @@ -282,12 +285,6 @@ gnc_reconcile_list_destroy(GtkObject *object) list->reconciled_style = NULL; } - if (list->normal_style != NULL) - { - gtk_style_unref(list->normal_style); - list->normal_style = NULL; - } - if (list->reconciled != NULL) { g_hash_table_destroy(list->reconciled); @@ -319,6 +316,13 @@ gnc_reconcile_list_get_num_splits(GNCReconcileList *list) return list->num_splits; } +Split * +gnc_reconcile_list_get_current_split(GNCReconcileList *list) +{ + assert(GTK_IS_GNC_RECONCILE_LIST(list)); + + return list->current_split; +} /********************************************************************\ * gnc_reconcile_list_refresh * @@ -333,6 +337,8 @@ gnc_reconcile_list_refresh(GNCReconcileList *list) GtkCList *clist = GTK_CLIST(list); GtkAdjustment *adjustment; gfloat save_value = 0.0; + Split *old_split; + gint new_row; assert(GTK_IS_GNC_RECONCILE_LIST(list)); @@ -344,12 +350,13 @@ gnc_reconcile_list_refresh(GNCReconcileList *list) gtk_clist_clear(clist); + old_split = list->current_split; list->num_splits = 0; + list->current_row = -1; + list->current_split = NULL; gnc_reconcile_list_fill(list); - gtk_clist_thaw(clist); - gtk_clist_columns_autosize(clist); if (adjustment != NULL) @@ -357,6 +364,20 @@ gnc_reconcile_list_refresh(GNCReconcileList *list) save_value = CLAMP(save_value, adjustment->lower, adjustment->upper); gtk_adjustment_set_value(adjustment, save_value); } + + if (old_split != NULL) + { + new_row = gtk_clist_find_row_from_data(clist, old_split); + if (new_row >= 0) + { + list->no_toggle = TRUE; + gtk_clist_select_row(clist, new_row, 0); + list->no_toggle = FALSE; + list->current_split = old_split; + } + } + + gtk_clist_thaw(clist); } @@ -429,6 +450,26 @@ gnc_reconcile_list_commit(GNCReconcileList *list) } +/********************************************************************\ + * gnc_reconcile_list_unselect_all * + * unselect all splits in the list * + * * + * Args: list - list to unselect all * + * Returns: nothing * +\********************************************************************/ +void +gnc_reconcile_list_unselect_all(GNCReconcileList *list) +{ + GtkCList *clist = GTK_CLIST(list); + + list->no_toggle = TRUE; + gtk_clist_unselect_all(clist); + list->no_toggle = FALSE; + + list->current_split = NULL; +} + + static void gnc_reconcile_list_fill(GNCReconcileList *list) { diff --git a/src/gnome/reconcile-list.h b/src/gnome/reconcile-list.h index 10ff71c190..30cc204bd1 100644 --- a/src/gnome/reconcile-list.h +++ b/src/gnome/reconcile-list.h @@ -51,11 +51,13 @@ struct _GNCReconcileList gint num_columns; gint current_row; + Split *current_split; + + gboolean no_toggle; GHashTable *reconciled; GtkStyle *reconciled_style; - GtkStyle *normal_style; Account *account; }; @@ -81,12 +83,16 @@ gint gnc_reconcile_list_get_row_height(GNCReconcileList *list); gint gnc_reconcile_list_get_num_splits(GNCReconcileList *list); +Split * gnc_reconcile_list_get_current_split(GNCReconcileList *list); + void gnc_reconcile_list_refresh (GNCReconcileList *list); double gnc_reconcile_list_reconciled_balance(GNCReconcileList *list); void gnc_reconcile_list_commit(GNCReconcileList *list); +void gnc_reconcile_list_unselect_all(GNCReconcileList *list); + #ifdef __cplusplus } diff --git a/src/gnome/scripts_menu.c b/src/gnome/scripts_menu.c index 74959e07f3..e5f6e1dbbe 100644 --- a/src/gnome/scripts_menu.c +++ b/src/gnome/scripts_menu.c @@ -19,55 +19,105 @@ #include "scripts_menu.h" -#include - #include "top-level.h" + +#include "guile-util.h" #include "util.h" + +typedef struct _ScriptInfo ScriptInfo; +struct _ScriptInfo +{ + SCM script; + + GnomeUIInfo info[2]; +}; + /* This static indicates the debugging module that this .o belongs to. */ static short module = MOD_GUI; -/* FIXME: is this always kosher? Will SCM's always fit in a gpointer? */ +static GSList *script_list = NULL; + static void gnc_extensions_menu_cb( GtkWidget *w, gpointer p) { - SCM closure = (SCM) p; + ScriptInfo *si = (ScriptInfo *) p; - if (!p) + if (si == NULL) return; - gh_call0(closure); + gh_call0(si->script); } -void -gnc_extensions_menu_add_item(char name[], - char hint[], - gpointer data) + +static ScriptInfo * +gnc_extensions_create_script_info(char *name, char *hint, SCM script) { - GnomeUIInfo item_info[2]; - GnomeUIInfo tmpi; + ScriptInfo *si; + + si = g_new0(ScriptInfo, 1); + si->script = script; + gnc_register_c_side_scheme_ptr(script); + + script_list = g_slist_prepend(script_list, si); + + si->info[0].type = GNOME_APP_UI_ITEM; + si->info[0].label = g_strdup(name); + si->info[0].hint = g_strdup(hint); + si->info[0].moreinfo = gnc_extensions_menu_cb; + si->info[0].user_data = si; + si->info[0].unused_data = NULL; + si->info[0].pixmap_type = GNOME_APP_PIXMAP_NONE; + si->info[0].pixmap_info = NULL; + si->info[0].accelerator_key = 0; + si->info[0].ac_mods = (GdkModifierType) 0; + si->info[0].widget = NULL; - tmpi.type = GNOME_APP_UI_ITEM; - tmpi.label = N_(name); - tmpi.hint = N_(hint); - tmpi.moreinfo = gnc_extensions_menu_cb; - tmpi.user_data = data; - tmpi.unused_data = NULL; - tmpi.pixmap_type = GNOME_APP_PIXMAP_NONE; - tmpi.pixmap_info = NULL; - tmpi.accelerator_key = 0; - tmpi.ac_mods = (GdkModifierType) 0; - tmpi.widget = NULL; - item_info[0] = tmpi; - - tmpi.type = GNOME_APP_UI_ENDOFINFO; - tmpi.label = NULL; - tmpi.moreinfo = NULL; - item_info[1] = tmpi; - - PINFO ("gnc_extensions_menu_add_item(): %s %s %p\n", name, hint, data); - gnome_app_insert_menus(GNOME_APP(gnc_get_ui_data()), "Extensions/", - item_info); - gnome_app_install_menu_hints(GNOME_APP(gnc_get_ui_data()), item_info); + si->info[1].type = GNOME_APP_UI_ENDOFINFO; + si->info[1].label = NULL; + si->info[1].moreinfo = NULL; + + return si; +} + + +void +gnc_extensions_menu_add_item(char *name, char *hint, SCM script) +{ + ScriptInfo *si; + + g_return_if_fail(gh_procedure_p(script)); + + si = gnc_extensions_create_script_info(name, hint, script); + + PINFO ("gnc_extensions_menu_add_item(): %s %s %p\n", name, hint, si); + + gnome_app_insert_menus(GNOME_APP(gnc_get_ui_data()), + "Extensions/", si->info); + gnome_app_install_menu_hints(GNOME_APP(gnc_get_ui_data()), si->info); +} + + +static void +cleanup_script_info(gpointer script_info, gpointer not_used) +{ + ScriptInfo *si = (ScriptInfo *) script_info; + + gnc_register_c_side_scheme_ptr(si->script); + + g_free(si->info[0].label); + g_free(si->info[0].hint); + g_free(si); +} + + +void +gnc_extensions_shutdown() +{ + g_slist_foreach(script_list, cleanup_script_info, NULL); + + g_slist_free(script_list); + + script_list = NULL; } diff --git a/src/gnome/scripts_menu.h b/src/gnome/scripts_menu.h index 5f56b9746a..59aba8443c 100644 --- a/src/gnome/scripts_menu.h +++ b/src/gnome/scripts_menu.h @@ -21,10 +21,9 @@ #define __SCRIPTS_MENU_H__ #include +#include -void gnc_extensions_menu_add_item(char name[], char hint[], gpointer data); - -GnomeUIInfo *create_scripts_menu_data(); -void destroy_scripts_menu_data(); +void gnc_extensions_menu_add_item(char *name, char *hint, SCM script); +void gnc_extensions_shutdown(); #endif diff --git a/src/gnome/top-level.c b/src/gnome/top-level.c index 1bfa9725a8..f652558736 100644 --- a/src/gnome/top-level.c +++ b/src/gnome/top-level.c @@ -26,21 +26,31 @@ #include "top-level.h" #include "window-main.h" -#include "option-util.h" +#include "global-options.h" #include "gnucash-sheet.h" #include "gnucash-color.h" #include "gnucash-style.h" +#include "scripts_menu.h" +#include "window-help.h" +#include "window-report.h" #include "FileIO.h" #include "FileBox.h" #include "FileDialog.h" #include "MainWindow.h" #include "Destroy.h" +#include "Refresh.h" #include "messages.h" #include "TransLog.h" #include "util.h" +#include "date.h" +#include "AccWindow.h" /** PROTOTYPES ******************************************************/ +static void gnc_configure_date_format_cb(gpointer); +static void gnc_configure_date_format(void); +static void gnc_configure_newacc_currency_cb(gpointer); +static void gnc_configure_newacc_currency(void); /** GLOBALS *********************************************************/ /* This static indicates the debugging module that this .o belongs to. */ @@ -99,6 +109,13 @@ gnucash_ui_init() gnc_options_init(); + gnc_configure_date_format(); + gnc_register_option_change_callback(gnc_configure_date_format_cb, NULL); + + gnc_configure_newacc_currency(); + gnc_register_option_change_callback(gnc_configure_newacc_currency_cb, + NULL); + mainWindow(); gnucash_style_init(); @@ -118,7 +135,7 @@ gnc_ui_shutdown (void) if (gnome_is_running && !gnome_is_terminating) { gnome_is_terminating = TRUE; - xaccGroupWindowDestroy(gncGetCurrentGroup()); + gnc_ui_destroy_all_subwindows(); gtk_widget_hide(app); gtk_main_quit(); } @@ -126,6 +143,16 @@ gnc_ui_shutdown (void) /* ============================================================== */ +void +gnc_ui_destroy_all_subwindows (void) +{ + xaccGroupWindowDestroy(gncGetCurrentGroup()); + gnc_ui_destroy_help_windows(); + gnc_ui_destroy_report_windows(); +} + +/* ============================================================== */ + void gnc_ui_destroy (void) { @@ -133,6 +160,7 @@ gnc_ui_destroy (void) return; gnc_options_shutdown(); + gnc_extensions_shutdown(); if (app != NULL) { @@ -182,4 +210,102 @@ gnucash_ui_select_file() return (1); } +/* ============================================================== */ + +/* gnc_configure_date_format_cb + * Callback called when options change - sets dateFormat to the current + * value on the scheme side and refreshes register windows + * + * Args: Nothing + * Returns: Nothing + */ +static void +gnc_configure_date_format_cb(gpointer data) +{ + gnc_configure_date_format(); + gnc_group_ui_refresh(gncGetCurrentGroup()); +} + + +/* gnc_configure_date_format + * sets dateFormat to the current value on the scheme side + * + * Args: Nothing + * Returns: Nothing + */ +static void +gnc_configure_date_format (void) +{ + char *format_code = gnc_lookup_multichoice_option("International", + "Date Format", + "us"); + + DateFormat df; + + if( safe_strcmp(format_code, "us") == 0) + { + df = DATE_FORMAT_US; + } + + else if( safe_strcmp(format_code, "uk") == 0) + { + df = DATE_FORMAT_UK; + } + + else if( safe_strcmp(format_code, "ce") == 0) + { + df = DATE_FORMAT_CE; + } + + else if( safe_strcmp(format_code, "iso") == 0) + { + df = DATE_FORMAT_ISO; + } + + else if( safe_strcmp(format_code, "locale") == 0) + { + df = DATE_FORMAT_LOCALE; + } + + else + { + PERR("Incorrect date format code"); + return; + } + + setDateFormat(df); + free(format_code); +} + +/* gnc_configure_date_format_cb + * Callback called when options change - sets default currency to + * the current value on the scheme size + * + * Args: Nothing + * Returns: Nothing + */ +static void +gnc_configure_newacc_currency_cb(gpointer data) +{ + gnc_configure_newacc_currency(); +} + +/* gnc_configure_newacc_currency + * sets the default currency for new accounts to the + * current value on the scheme side + * + * Args: Nothing + * Returns: Nothing + */ +static void +gnc_configure_newacc_currency(void) +{ + char *newacc_def_currency = + gnc_lookup_string_option("International", + "Default Currency", + "USD"); + xaccSetDefaultNewaccountCurrency(newacc_def_currency); + free(newacc_def_currency); +} + /****************** END OF FILE **********************/ diff --git a/src/gnome/window-adjust.c b/src/gnome/window-adjust.c index 6cdca8ccf4..46bc9d488e 100644 --- a/src/gnome/window-adjust.c +++ b/src/gnome/window-adjust.c @@ -33,6 +33,7 @@ #include "AdjBWindow.h" #include "Refresh.h" #include "window-reconcile.h" +#include "dialog-utils.h" #include "query-user.h" #include "messages.h" #include "util.h" @@ -141,17 +142,19 @@ adjBWindow(Account *account) { GtkWidget *dialog, *frame, *vbox; AdjBWindow *adjBData; - gchar *title; + gchar *title, *name; FETCH_FROM_LIST(AdjBWindow, adjBList, account, account, adjBData); - title = g_strconcat(xaccAccountGetName(account), ": ", ADJ_BALN_STR, NULL); + name = gnc_ui_get_account_full_name(account, ":"); + title = g_strconcat(name, " - ", ADJ_BALN_STR, NULL); dialog = gnome_dialog_new(title, GNOME_STOCK_BUTTON_OK, GNOME_STOCK_BUTTON_CANCEL, NULL); + g_free(name); g_free(title); adjBData->account = account; diff --git a/src/gnome/window-help.c b/src/gnome/window-help.c index e97dbda406..4a71161c3b 100644 --- a/src/gnome/window-help.c +++ b/src/gnome/window-help.c @@ -1,5 +1,5 @@ /********************************************************************\ - * HelpWindow.c -- a help window for hypertext help. * + * window-help.c -- a help window for hypertext help. * * Copyright (C) 1997 Robin D. Clark * * Copyright (C) 1998 Linas Vepstas * * Copyright (C) 1999 Jeremy Collins ( gtk-xmhtml port ) * @@ -24,227 +24,161 @@ * Huntington Beach, CA 92648-4632 * \********************************************************************/ -#include -#include -#include -#include -#include - #include -#include - -#include #include "config.h" -#if HAVE_XPM -# include -#endif - -#include "File.h" #include "window-help.h" -#include "top-level.h" -#include "ui-callbacks.h" -#include "messages.h" +#include "window-html.h" #include "Sheet.h" +#include "File.h" +#include "messages.h" #include "util.h" -extern GtkWidget app; static short module = MOD_HTML; -/********************************************************************\ - * HTML History functions * - * hack alert -- these are gui-independent, and should be moved - * to somewhere were the gtk, Qt gui's can make use of them -\********************************************************************/ -typedef struct _HTMLHistory { - struct _HTMLHistory *next; - struct _HTMLHistory *last; - char *htmlfile; - char *text; -} HTMLHistory; +static HTMLWindow *helpwindow = NULL; -/* insert an htmlfile into history. Return TRUE if this - * is the first element in the history. If not last element - * in history, all next pages are deleted */ -static int -historyInsert( HTMLHistory **history, const char *htmlfile ) - { - if( (*history) != NULL ) - { - HTMLHistory *temp; - - /* delete all next pages: */ - temp = (*history)->next; - while( temp ) - { - (*history)->next = temp->next; - if (temp->htmlfile) free(temp->htmlfile); - if (temp->text) free(temp->text); - _free(temp); - temp = (*history)->next; - } - - /* Add new node to history: */ - temp = (HTMLHistory *)_malloc(sizeof(HTMLHistory)); - if (htmlfile) { - temp->htmlfile = strdup (htmlfile); - } else { - temp->htmlfile = NULL; - } - temp->text = NULL; - temp->next = NULL; - temp->last = (*history); - (*history)->next = temp; - (*history) = temp; - - return FALSE; - } - else - { - /* This must be the first node in the history... */ - (*history) = (HTMLHistory *)_malloc(sizeof(HTMLHistory)); - if (htmlfile) { - (*history)->htmlfile = strdup (htmlfile); - } else { - (*history)->htmlfile = NULL; - } - (*history)->text = NULL; - (*history)->last = NULL; - (*history)->next = NULL; - - /* ...so return TRUE */ - return TRUE; - } - } - -/* Move forward in history, and return current htmlfile */ -static char * -historyFwd( HTMLHistory **history ) - { - if( (*history) != NULL ) { - if( (*history)->next != NULL ) { - (*history) = (*history)->next; - } - return (*history)->htmlfile; - } - else - return NULL; - } - -/* Move back in history, and return current htmlfile */ -static char * -historyBack( HTMLHistory **history ) - { - if( (*history) != NULL ) { - if( (*history)->last != NULL ) { - (*history) = (*history)->last; - } - return (*history)->htmlfile; - } - else - return NULL; - } - -#ifdef NOT_USED -/* Return current htmlfile */ -static char * -historyCurrent( HTMLHistory **history ) - { - if( (*history) != NULL ) - return (*history)->htmlfile; - else - return NULL; - } -#endif - -/* Remove all entries from history: */ -#if 0 -static void -historyClear( HTMLHistory **history ) - { - /* move to beginning of history: */ - while( (*history)->last != NULL ) - (*history) = (*history)->last; - - /* delete all next pages: */ - while( (*history)->next != NULL ) - { - HTMLHistory *temp = (*history)->next; - - (*history)->next = temp->next; - if(temp->htmlfile) free(temp->htmlfile); - if(temp->text) free(temp->text); - _free(temp); - } - - /* delete current page: */ - if ((*history)->htmlfile) free((*history)->htmlfile); - if ((*history)->text) free((*history)->text); - _free(*history); - (*history) = NULL; - } -#endif - -/********************************************************************\ - * HTML Window stuff... * -\********************************************************************/ - -/** GLOBALS *********************************************************/ - -struct _HTMLWindow { - GtkWidget *htmlwidget; - GtkWidget *data_target_frame; - GtkWidget *menu_target_frame; - short installed_data_frame_form_cb; - short installed_menu_frame_form_cb; - HTMLHistory *history; +typedef struct _HelpData HelpData; +struct _HelpData +{ + gchar *htmlfile; + gchar *label; + gchar *text; }; -typedef struct _HTMLWindow HTMLWindow; -HTMLWindow *helpwindow = NULL; -HTMLWindow *reportwindow = NULL; - -/** PROTOTYPES ******************************************************/ -static void htmlWindow( GtkWidget *parent, HTMLWindow **hwinp, - const char * const title, - const char * const htmlfile, - const char * const text); -static void htmlBackCB( GtkWidget *widget, gpointer data ); -static void htmlFwdCB( GtkWidget *widget, gpointer data ); -static void htmlAnchorCB( GtkWidget *widget, XmHTMLAnchorCallbackStruct *acbs, gpointer data ); - -#if HAVE_LIBXMHTML -static char * xaccJumpToLabel (GtkWidget *widget, const char * jumpfile, char * text); -#endif /* HAVE_XMHTML */ - -/********************************************************************\ - * reportWindow * - * opens up a report window, and displays html * - * * - * Args: parent - the parent widget * - * title - the title of the window * - * htmlfile - the file name of the help file to display * - * Return: none * -\********************************************************************/ -void -reportWindow( GtkWidget *parent, const char *title, const char *file) +static HelpData * +help_data_new() { - if (!reportwindow) { - reportwindow = (HTMLWindow *) g_malloc (sizeof (HTMLWindow)); - reportwindow->htmlwidget = 0; - reportwindow->data_target_frame = 0; - reportwindow->menu_target_frame = 0; - reportwindow->installed_data_frame_form_cb = 0; - reportwindow->installed_menu_frame_form_cb = 0; - reportwindow->history = NULL; - } - - htmlWindow (parent, &reportwindow, title, file, NULL); -} + HelpData *hd; + hd = g_new0(HelpData, 1); + + return hd; +} + +static void +help_data_destroy(HTMLHistoryData history_data) +{ + HelpData *hd = history_data; + + g_free(hd->htmlfile); + hd->htmlfile = NULL; + + g_free(hd->label); + hd->label = NULL; + + g_free(hd->text); + hd->text = NULL; + + g_free(hd); +} + +static void +help_data_set_file(HelpData *hd, const gchar *htmlfile) +{ + g_free(hd->htmlfile); + hd->htmlfile = g_strdup(htmlfile); +} + +static void +help_data_set_label(HelpData *hd, const gchar *label) +{ + g_free(hd->label); + hd->label = g_strdup(label); +} + +static void +help_data_set_text(HelpData *hd, const gchar *text) +{ + g_free(hd->text); + hd->text = g_strdup(text); +} + + +static HTMLHistoryData +helpAnchorCB(XmHTMLAnchorCallbackStruct *acbs, HTMLHistoryData history_data) +{ + HelpData *hd; + + switch(acbs->url_type) + { + /* a local file with a possible jump to label */ + case ANCHOR_FILE_LOCAL: + hd = help_data_new(); + help_data_set_file(hd, acbs->href); + return hd; + + /* other types are unsupported, but it would be fun if they were ... */ + case ANCHOR_FTP: + PERR(" this help window doesn't support ftp: %s\n", acbs->href); + break; + case ANCHOR_HTTP: + PERR (" this help window doesn't support http: %s\n", acbs->href); + break; + case ANCHOR_MAILTO: + PERR(" this help window doesn't support email: %s\n", acbs->href); + break; + case ANCHOR_UNKNOWN: + default: + PERR(" don't know this type of url: %s\n", acbs->href); + break; + } + + return NULL; +} + +static void +helpJumpCB(HTMLHistoryData history_data, char **set_text, char **set_label) +{ + HelpData *hd = (HelpData *) history_data; + char *text = NULL; + char *label = NULL; + + *set_text = NULL; + *set_label = NULL; + + if (hd->text != NULL) + { + *set_text = hd->text; + *set_label = hd->label; + return; + } + + if (hd->htmlfile == NULL) + return; + + /* see if this anchor contains a jump */ + label = strpbrk(hd->htmlfile, "#?"); + if (label != NULL) + { + help_data_set_label(hd, label); + + /* truncate # from name */ + hd->htmlfile[label - hd->htmlfile] = 0x0; + } + + /* see if the anchor is an "active gnucash page" */ + if (strstr(hd->htmlfile, ".phtml")) + text = gncReport(hd->htmlfile); + + /* if text to display wasn't specified, use the truncated name to read */ + if (text == NULL) + text = gncReadFile(hd->htmlfile); + + if (text != NULL) + { + help_data_set_text(hd, text); + free(text); + } + + *set_text = hd->text; + *set_label = hd->label; +} + + /********************************************************************\ * helpWindow * * opens up a help window, and displays html * @@ -255,322 +189,35 @@ reportWindow( GtkWidget *parent, const char *title, const char *file) * Return: none * \********************************************************************/ void -helpWindow( GtkWidget *parent, const char *title, const char *htmlfile ) +helpWindow(GtkWidget *parent, const char *title, const char *htmlfile) { - if (!helpwindow) { - helpwindow = (HTMLWindow *) malloc (sizeof (HTMLWindow)); - helpwindow->htmlwidget = 0; - helpwindow->data_target_frame = 0; - helpwindow->menu_target_frame = 0; - helpwindow->installed_data_frame_form_cb = 0; - helpwindow->installed_menu_frame_form_cb = 0; - helpwindow->history = NULL; - } + HelpData *hd; + + if (helpwindow == NULL) + helpwindow = gnc_html_window_new(help_data_destroy, helpAnchorCB, + helpJumpCB); - htmlWindow (parent, &helpwindow, title, htmlfile, NULL); + hd = help_data_new(); + help_data_set_file(hd, htmlfile); + + htmlWindow(parent, &helpwindow, title, hd, NULL, 0); } - + + /********************************************************************\ - * helpWindow * - * opens up a help window, and displays html * + * gnc_ui_destroy_help_windows * + * destroys any open help windows * * * - * Args: parent - the parent widget * - * title - the title of the window * - * htmlfile - the file name of the help file to display * + * Args: none * * Return: none * \********************************************************************/ -static void -htmlWindow( GtkWidget *parent, - HTMLWindow **hwinp, - const char * const title, - const char * const htmlfile, - const char * const htmltext) +void +gnc_ui_destroy_help_windows() { - HTMLWindow *hw = *hwinp; - char * text=0x0; - - // gnc_set_busy_cursor( parent ); - - historyInsert (&(hw->history), htmlfile); - if (htmltext) text = strdup (htmltext); - hw->history->text = text; - - /* if the help window isn't open, then open it... otherwise, load - * new help page into current help window */ - - /************ CREATE HTML WINDOW HERE *****************/ - /******************************************************/ - { - GtkWidget *window; - GtkWidget *html; - GtkWidget *button; - GtkWidget *hbuttonbox1; - GtkWidget *vbox1; - - window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_object_set_data (GTK_OBJECT (window), "window", window); - gtk_window_set_title (GTK_WINDOW (window), "Gnucash Help System"); - gtk_window_set_policy (GTK_WINDOW (window), TRUE, TRUE, FALSE); - - html = gtk_xmhtml_new(); - - hw->htmlwidget = html; - - text = xaccJumpToLabel( hw->htmlwidget, htmlfile, text); - hw->history->text = text; - - vbox1 = gtk_vbox_new (FALSE, 0); - gtk_object_set_data (GTK_OBJECT (window), "vbox1", vbox1); - gtk_widget_show (vbox1); - gtk_container_add (GTK_CONTAINER (window), vbox1); - gtk_container_border_width (GTK_CONTAINER (vbox1), 6); - - /* Pack our html widget into the window */ - //gtk_container_add(GTK_CONTAINER(window), html); - gtk_box_pack_start(GTK_BOX(vbox1), html, TRUE, TRUE, 5); - - hbuttonbox1 = gtk_hbutton_box_new (); - gtk_object_set_data (GTK_OBJECT (window), "hbuttonbox1", hbuttonbox1); - gtk_widget_show (hbuttonbox1); - gtk_box_pack_start (GTK_BOX (vbox1), hbuttonbox1, FALSE, FALSE, 0); - gtk_container_border_width (GTK_CONTAINER (hbuttonbox1), 5); - gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox1), GTK_BUTTONBOX_SPREAD); - - button = gtk_button_new_with_label ("Back"); - gtk_object_set_data (GTK_OBJECT (window), "back", button); - gtk_widget_show (button); - gtk_container_add (GTK_CONTAINER (hbuttonbox1), button); - - gtk_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(htmlBackCB), - hw); - - button = gtk_button_new_with_label ("Contents"); - gtk_object_set_data (GTK_OBJECT (window), "contents", button); - gtk_widget_show (button); - gtk_container_add (GTK_CONTAINER (hbuttonbox1), button); - - button = gtk_button_new_with_label ("Forward"); - gtk_object_set_data (GTK_OBJECT (window), "forward", button); - gtk_widget_show (button); - gtk_container_add (GTK_CONTAINER (hbuttonbox1), button); - - gtk_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(htmlFwdCB), - hw); - - hw->data_target_frame = hw->htmlwidget; - - gtk_widget_show(html); - gtk_widget_show(window); - - gtk_widget_set_usize(GTK_WIDGET(window), 675, 400); - - gtk_signal_connect(GTK_OBJECT(hw->htmlwidget), "activate", - GTK_SIGNAL_FUNC(htmlAnchorCB), - hw); - - } + gnc_html_window_destroy(helpwindow); + helpwindow = NULL; + DEBUG("help windows destroyed.\n"); } -/********************************************************************\ - * callback functions... * -\********************************************************************/ - - -/********************************************************************\ - * htmlBackCB - called when user clicks "Back" button... shows last * - * help page in history * - * * - * Args: mw - * - * cd - * - * cb - * - * Return: none * -\********************************************************************/ -static void -htmlBackCB( GtkWidget *widget, gpointer data ) - { - HTMLWindow *hw = (HTMLWindow *) data; - char *file = historyBack(&(hw->history)); - char *text = hw->history->text; - -#if HAVE_LIBXMHTML - text = xaccJumpToLabel( hw->htmlwidget, file, text); - hw->history->text = text; -#endif - } - -/********************************************************************\ - * htmlFwdCB - called when user clicks "Forward" button... shows * - * next help page in the history * - * * - * Args: mw - * - * cd - * - * cb - * - * Return: none * -\********************************************************************/ -static void -htmlFwdCB( GtkWidget *widget, gpointer data ) - { - HTMLWindow *hw = (HTMLWindow *) data; - char *file = historyFwd(&(hw->history)); - char *text = hw->history->text; -#if HAVE_LIBXMHTML - text = xaccJumpToLabel( hw->htmlwidget, file, text); - hw->history->text = text; -#endif - } - -/********************************************************************\ - * closeHtmlWin - called when the help window is closed * - * * - * Args: mw - * - * cd - * - * cb - * - * Return: none * -\********************************************************************/ -#if 0 -static void -closeHtmlWin( GtkWidget *widget, gpointer data ) -{ - HTMLWindow **hw = (HTMLWindow **) data; - - /* Delete the history: */ - historyClear (&((*hw)->history)); - (*hw)->history=NULL; - (*hw)->htmlwidget=0; - free (*hw); - (*hw) = NULL; -} -#endif - -/********************************************************************\ - * htmlAnchorCB - called when user clicks on html anchor tag * - * * - * Args: mw - the html widget that called us * - * cd - * - * cb - the anchor call-back struct * - * Return: none * -\********************************************************************/ -static void -htmlAnchorCB( GtkWidget *widget, XmHTMLAnchorCallbackStruct *acbs, gpointer data) - { - HTMLWindow *hw = (HTMLWindow *) data; - -#if HAVE_LIBXMHTML -// XmHTMLAnchorCallbackStruct *acbs = (XmHTMLAnchorCallbackStruct *) data; - - if(acbs->reason != XmCR_ACTIVATE) return; - - switch(acbs->url_type) { - - /* a named anchor on a page that is already displayed */ - case ANCHOR_JUMP: { -// XmHTMLAnchorScrollToName(widget, acbs->href); - } - break; - - /* a local file with a possible jump to label */ - case ANCHOR_FILE_LOCAL: { - historyInsert(&(hw->history), acbs->href); -/* hack alert -- the target widget thing is a hack */ - hw->history->text = xaccJumpToLabel (hw->data_target_frame, acbs->href, NULL); - } - break; - - /* other types are unsupported, but it would be fun if they were ... */ - case ANCHOR_FTP: - PERR(" this help window doesn't support ftp: %s\n", acbs->href); - break; - case ANCHOR_HTTP: - PERR (" this help window doesn't support http: %s\n", acbs->href); - break; - case ANCHOR_MAILTO: - PERR(" this help window doesn't support email: %s\n", acbs->href); - break; - case ANCHOR_UNKNOWN: - default: - PERR(" don't know this type of url: %s\n", acbs->href); - break; - } - -#endif - } - -/********************************************************************\ - * utility functions... * -\********************************************************************/ - -#if HAVE_LIBXMHTML -static char * -xaccJumpToLabel (GtkWidget *widget, const char * jumpfile, char * text) -{ - char *label=0x0, *file=0x0; - - if (jumpfile) { - file = strdup (jumpfile); - - /* see if this anchor contains a jump */ - label = strpbrk (file, "#?"); - if (label) { - file [label - file] = 0x0; /* truncate # from name */ - } - - /* see if the anchor is an "active gnucash page" */ - if (strstr (file, ".phtml")) { - text = gncReport (file); - } - - /* if text to display wasn't specified, use the truncated name to read */ - if (!text) text = gncReadFile (file); - } - if (!text) return NULL; - - - gtk_xmhtml_source(GTK_XMHTML(widget), text); - -#if 0 - if (label) { - XmHTMLAnchorScrollToName(mw, label); - } else { - XmHTMLTextScrollToLine(mw, 0); -} -#endif - - if (file) free (file); - - return text; -} - -/********************************************************************\ - * HTML functions... * -\********************************************************************/ - -/********************************************************************\ - * htmlReadImageProc * - * * - * Args: file - the name of the html file to read * - * Return: none * - * Global: helpPath - the path to the help files * -\********************************************************************/ -#if 0 -static XmImageInfo * -htmlReadImageProc (GtkWidget *widget, String file) - { - char *filename; - XmImageInfo *retval = NULL; - - /* construct absolute path -- twiddle the relative path we recieved */ - filename = gncFindFile (file); - - /* use the default proc for the hard work */ -// retval = XmHTMLImageDefaultProc(widget, filename, NULL, 0); - - free(filename); - return retval; -} -#endif /* 0 */ -#endif /* HAVE_XMHTML */ - /* ----------------------- END OF FILE --------------------- */ diff --git a/src/gnome/window-help.h b/src/gnome/window-help.h index cba7efc438..8fc7dde4c8 100644 --- a/src/gnome/window-help.h +++ b/src/gnome/window-help.h @@ -1,5 +1,5 @@ /********************************************************************\ - * HelpWindow.h -- a help window for hypertext help. * + * window-help.h -- a help window for hypertext help. * * Copyright (C) 1997 Robin D. Clark * * * * This program is free software; you can redistribute it and/or * @@ -22,8 +22,8 @@ * Huntington Beach, CA 92648-4632 * \********************************************************************/ -#ifndef __HELPWINDOW_H__ -#define __HELPWINDOW_H__ +#ifndef __WINDOW_HELP_H__ +#define __WINDOW_HELP_H__ #include @@ -31,8 +31,9 @@ /** PROTOTYPES ******************************************************/ -void helpWindow( GtkWidget *parent, const char *title, const char *htmlfile ); -void reportWindow( GtkWidget *parent, const char *title, const char *text ); +void helpWindow(GtkWidget *parent, const char *title, const char *htmlfile); + +void gnc_ui_destroy_help_windows(); #endif diff --git a/src/gnome/window-html.c b/src/gnome/window-html.c new file mode 100644 index 0000000000..c00e1e4476 --- /dev/null +++ b/src/gnome/window-html.c @@ -0,0 +1,750 @@ +/********************************************************************\ + * window-html.c -- an html window for gnucash * + * Copyright (C) 1997 Robin D. Clark * + * Copyright (C) 1998 Linas Vepstas * + * Copyright (C) 1999 Jeremy Collins ( gtk-xmhtml port ) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + * * + * Author: Rob Clark * + * Internet: rclark@cs.hmc.edu * + * Address: 609 8th Street * + * Huntington Beach, CA 92648-4632 * +\********************************************************************/ + +#include + +#include "top-level.h" + +#include "window-html.h" +#include "File.h" +#include "util.h" + +static short module = MOD_HTML; + + +/********************************************************************\ + * HTML History functions * + * hack alert -- these are gui-independent, and should be moved * + * to a central location * +\********************************************************************/ +typedef struct _HTMLHistoryNode HTMLHistoryNode; +struct _HTMLHistoryNode +{ + HTMLHistoryNode *next; + HTMLHistoryNode *last; + + HTMLHistoryData history_data; +}; + +typedef struct _HTMLHistory HTMLHistory; +struct _HTMLHistory +{ + HTMLHistoryNode *current_node; + HTMLHistoryDestroyDataFunc destroy; +}; + + + +static HTMLHistoryNode * +history_node_new(const HTMLHistoryData history_data) +{ + HTMLHistoryNode *new; + + new = malloc(sizeof(HTMLHistoryNode)); + assert(new != NULL); + + new->history_data = history_data; + new->last = NULL; + new->next = NULL; + + return new; +} + +/* Insert a history element into history. Return TRUE if this + * is the first element in the history. If not last element + * in history, all next pages are deleted */ +static gncBoolean +historyInsert(HTMLHistory *history, const HTMLHistoryData history_data) +{ + HTMLHistoryNode *new; + HTMLHistoryNode *temp; + + assert(history != NULL); + + new = history_node_new(history_data); + + if (history->current_node == NULL) + { + history->current_node = new; + return GNC_T; + } + + /* delete all next pages: */ + temp = history->current_node->next; + while(temp != NULL) + { + history->current_node->next = temp->next; + if (temp->history_data != NULL) + (history->destroy)(temp->history_data); + free(temp); + + temp = history->current_node->next; + } + + new->last = history->current_node; + history->current_node->next = new; + history->current_node = new; + + return GNC_F; +} + +/* Move forward in history, and return current history data */ +static HTMLHistoryData +historyFwd(HTMLHistory *history) +{ + if (history == NULL) + return NULL; + if (history->current_node == NULL) + return NULL; + + if (history->current_node->next != NULL) + history->current_node = history->current_node->next; + + return history->current_node->history_data; +} + +/* Move back in history, and return current history data */ +static HTMLHistoryData +historyBack(HTMLHistory *history) +{ + if (history == NULL) + return NULL; + if (history->current_node == NULL) + return NULL; + + if (history->current_node->last != NULL) + history->current_node = history->current_node->last; + + return history->current_node->history_data; +} + +/* Remove all entries from history: */ +static void +historyClear(HTMLHistory *history) +{ + if (history == NULL) + return; + if (history->current_node == NULL) + return; + + /* move to beginning of history: */ + while(history->current_node->last != NULL ) + history->current_node = history->current_node->last; + + /* delete all next pages: */ + while(history->current_node->next != NULL ) + { + HTMLHistoryNode *temp = history->current_node->next; + + history->current_node->next = temp->next; + if (temp->history_data != NULL) + (history->destroy)(temp->history_data); + free(temp); + } + + /* delete current page: */ + if (history->current_node->history_data != NULL) + (history->destroy)(history->current_node->history_data); + free(history->current_node); + history->current_node = NULL; +} + +static HTMLHistoryData +historyData(HTMLHistory *history) +{ + if (history == NULL) + return NULL; + if (history->current_node == NULL) + return NULL; + + return history->current_node->history_data; +} + +static HTMLHistory * +historyNew(HTMLHistoryDestroyDataFunc destroy) +{ + HTMLHistory *history; + + history = malloc(sizeof(HTMLHistory)); + assert(history != NULL); + + history->current_node = NULL; + history->destroy = destroy; + + return history; +} + +static void +historyDestroy(HTMLHistory *history) +{ + if (history == NULL) + return; + + historyClear(history); + free(history); +} + + +/********************************************************************\ + * HTML Window stuff... * +\********************************************************************/ + +struct _HTMLWindow +{ + GtkWidget *window; + GtkWidget *htmlwidget; + + GtkWidget *forward; + GtkWidget *back; + + HTMLHistory *history; + + HTMLAnchorCB anchor_cb; + HTMLJumpCB jump_cb; +}; + + +/** PROTOTYPES ******************************************************/ +static void htmlBackCB(GtkWidget *widget, gpointer data); +static void htmlFwdCB(GtkWidget *widget, gpointer data); +static void htmlAnchorCB(GtkWidget *widget, + XmHTMLAnchorCallbackStruct *acbs, + gpointer data); + +static gboolean htmlKeyCB(GtkWidget *widget, GdkEventKey *event, + gpointer user_data); +static void closeHtmlWinCB(GtkWidget *widget, gpointer data); +static void destroyHtmlWinCB(GtkWidget *widget, gpointer data); + +static XmImageInfo * htmlReadImageProc(GtkWidget *widget, String file, + gpointer data); + +static void htmlSetButtonStates(HTMLWindow *hw); + + +/********************************************************************\ + * gnc_html_window_history_data * + * return the current history data for the window * + * * + * Args: none * + * Return: history data for the window * +\********************************************************************/ +HTMLHistoryData +gnc_html_window_history_data(HTMLWindow *hw) +{ + if (hw == NULL) + return NULL; + + return historyData(hw->history); +} + + +/********************************************************************\ + * gnc_html_window_new * + * g_malloc and initialize HTMLWindow structure * + * * + * Args: none * + * Return: g_malloc'd initialized HTMLWindow structure * +\********************************************************************/ +HTMLWindow * +gnc_html_window_new(HTMLHistoryDestroyDataFunc destroy, + HTMLAnchorCB anchor_cb, HTMLJumpCB jump_cb) +{ + HTMLWindow *hw; + + hw = g_new0(HTMLWindow, 1); + + hw->history = historyNew(destroy); + + hw->anchor_cb = anchor_cb; + hw->jump_cb = jump_cb; + + return hw; +} + + +/********************************************************************\ + * gnc_html_window_destroy * + * destroy an HTMLWindow structure * + * * + * Args: hw - the htmlwindow structure to destroy * + * Return: none * +\********************************************************************/ +void +gnc_html_window_destroy(HTMLWindow *hw) +{ + if (hw == NULL) + return; + + if (hw->window != NULL) + gtk_widget_destroy(hw->window); + else + { + historyDestroy(hw->history); + g_free(hw); + } +} + + +static GtkWidget * +create_html_toolbar(HTMLWindow *hw, GnomeUIInfo *user_buttons, + gint num_buttons) +{ + GnomeUIInfo *toolbar_info; + GtkWidget *toolbar; + gint num_start, num_end; + gint i; + + GnomeUIInfo toolbar_start[] = + { + { GNOME_APP_UI_ITEM, + "Back", + "Move back one step in the history.", + htmlBackCB, hw, + NULL, + GNOME_APP_PIXMAP_STOCK, + GNOME_STOCK_PIXMAP_BACK, + 0, 0, NULL + }, + { GNOME_APP_UI_ITEM, + "Forward", + "Move forward one step in the history.", + htmlFwdCB, hw, + NULL, + GNOME_APP_PIXMAP_STOCK, + GNOME_STOCK_PIXMAP_FORWARD, + 0, 0, NULL + } + }; + + GnomeUIInfo toolbar_end[] = + { + { GNOME_APP_UI_ITEM, + "Close", + "Close this HTML window.", + closeHtmlWinCB, hw, + NULL, + GNOME_APP_PIXMAP_STOCK, + GNOME_STOCK_PIXMAP_CLOSE, + 0, 0, NULL + }, + GNOMEUIINFO_END + }; + + num_start = sizeof(toolbar_start) / sizeof(GnomeUIInfo); + num_end = sizeof(toolbar_end) / sizeof(GnomeUIInfo); + + toolbar_info = g_new0(GnomeUIInfo, num_start + num_buttons + num_end); + + for (i = 0; i < num_start; i++) + toolbar_info[i] = toolbar_start[i]; + + for (i = 0; i < num_buttons; i++) + toolbar_info[i + num_start] = user_buttons[i]; + + for (i = 0; i < num_end; i++) + toolbar_info[i + num_start + num_buttons] = toolbar_end[i]; + + toolbar = gtk_toolbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_BOTH); + + gnome_app_fill_toolbar(GTK_TOOLBAR(toolbar), toolbar_info, NULL); + + hw->back = toolbar_info[0].widget; + hw->forward = toolbar_info[1].widget; + + g_free(toolbar_info); + + return toolbar; +} + + +/********************************************************************\ + * htmlWindow * + * opens up an html window, and displays html * + * * + * Args: parent - the parent widget * + * hwp - the htmlwindow structure pointer * + * title - the title of the window * + * history_data - the history data * + * new_buttons - array of buttons to add to icon bar * + * num_buttons - number of buttons in list * + * Return: none * +\********************************************************************/ +void +htmlWindow(GtkWidget * parent, + HTMLWindow ** hwp, + const char * const title, + HTMLHistoryData history_data, + GnomeUIInfo *user_buttons, + gint num_buttons) +{ + HTMLWindow *hw = *hwp; + + historyInsert(hw->history, history_data); + + /* If the help window is already created, just load the new + * page into the existing widget and raise the window. */ + if (hw->htmlwidget != NULL) + { + gnc_html_load(hw); + + if (hw->window->window != NULL) + gdk_window_raise(hw->window->window); + + htmlSetButtonStates(hw); + + return; + } + + /************ CREATE HTML WINDOW HERE *****************/ + { + GtkWidget *window; + GtkWidget *html; + GtkWidget *dock; + GtkWidget *dock_item; + GtkWidget *toolbar; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + hw->window = window; + gtk_window_set_title(GTK_WINDOW (window), title); + gtk_window_set_policy(GTK_WINDOW (window), TRUE, TRUE, FALSE); + gtk_window_set_default_size(GTK_WINDOW(window), 675, 400); + + dock = gnome_dock_new(); + gtk_container_add(GTK_CONTAINER(window), dock); + + dock_item = gnome_dock_item_new("toolbar", GNOME_DOCK_ITEM_BEH_EXCLUSIVE); + + toolbar = create_html_toolbar(hw, user_buttons, num_buttons); + gtk_container_set_border_width(GTK_CONTAINER(toolbar), 2); + gtk_container_add(GTK_CONTAINER(dock_item), toolbar); + + gnome_dock_add_item (GNOME_DOCK(dock), GNOME_DOCK_ITEM(dock_item), + GNOME_DOCK_TOP, 0, 0, 0, TRUE); + + html = gtk_xmhtml_new(); + hw->htmlwidget = html; + gnome_dock_set_client_area(GNOME_DOCK(dock), html); + gtk_xmhtml_set_image_procs(GTK_XMHTML(html), htmlReadImageProc, + NULL, NULL, NULL); + + gnc_html_load(hw); + + gtk_signal_connect(GTK_OBJECT(hw->htmlwidget), "activate", + GTK_SIGNAL_FUNC(htmlAnchorCB), hw); + + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(destroyHtmlWinCB), hwp); + + gtk_signal_connect(GTK_OBJECT(window), "key_press_event", + GTK_SIGNAL_FUNC(htmlKeyCB), hw); + + gtk_widget_show_all(window); + } + + htmlSetButtonStates(hw); +} + + +/********************************************************************\ + * htmlSetButtonStates - set the sensitivity of the back/forward * + * buttons based on the history state * + * * + * Args: hw - the html window structure * + * Return: none * +\********************************************************************/ +static void +htmlSetButtonStates(HTMLWindow *hw) +{ + gboolean more_to_go; + + more_to_go = + (hw->history != NULL) && + (hw->history->current_node != NULL) && + (hw->history->current_node->last != NULL); + + gtk_widget_set_sensitive(hw->back, more_to_go); + + more_to_go = + (hw->history != NULL) && + (hw->history->current_node != NULL) && + (hw->history->current_node->next != NULL); + + gtk_widget_set_sensitive(hw->forward, more_to_go); +} + + +/********************************************************************\ + * htmlKeyCB - called when user presses a key * + * * + * Args: widget - the back button * + * data - html window structure * + * Return: none * +\********************************************************************/ +static gboolean +htmlKeyCB( GtkWidget *widget, GdkEventKey *event, gpointer data ) +{ + HTMLWindow *hw = (HTMLWindow *) data; + GtkXmHTML *html = GTK_XMHTML(hw->htmlwidget); + GtkAdjustment *adj; + gfloat value; + + if (html->vsba == NULL) + return FALSE; + + adj = GTK_ADJUSTMENT(html->vsba); + + value = adj->value; + + switch (event->keyval) + { + case GDK_KP_Up: + case GDK_Up: + value -= adj->step_increment; + break; + case GDK_KP_Down: + case GDK_Down: + value += adj->step_increment; + break; + case GDK_KP_Page_Up: + case GDK_Page_Up: + value -= adj->page_increment; + break; + case GDK_KP_Page_Down: + case GDK_Page_Down: + case GDK_space: + value += adj->page_increment; + break; + case GDK_KP_Home: + case GDK_Home: + value = adj->lower; + break; + case GDK_KP_End: + case GDK_End: + value = adj->upper; + break; + case GDK_Escape: + gtk_widget_destroy(hw->window); + return TRUE; + default: + return FALSE; + } + + value = CLAMP(value, adj->lower, adj->upper - adj->page_size); + + gtk_adjustment_set_value(adj, value); + + return TRUE; +} + + +/********************************************************************\ + * htmlBackCB - called when user clicks "Back" button... shows last * + * help page in history * + * * + * Args: widget - the back button * + * data - html window structure * + * Return: none * +\********************************************************************/ +static void +htmlBackCB(GtkWidget *widget, gpointer data) +{ + HTMLWindow *hw = (HTMLWindow *) data; + HTMLHistoryData history_data; + + history_data = historyBack(hw->history); + + gnc_html_load(hw); + + htmlSetButtonStates(hw); +} + + +/********************************************************************\ + * htmlFwdCB - called when user clicks "Forward" button... shows * + * next help page in the history * + * * + * Args: widget - the forward button * + * data - html window structure * + * Return: none * +\********************************************************************/ +static void +htmlFwdCB(GtkWidget *widget, gpointer data) +{ + HTMLWindow *hw = (HTMLWindow *) data; + HTMLHistoryData history_data; + + history_data = historyFwd(hw->history); + + gnc_html_load(hw); + + htmlSetButtonStates(hw); +} + + +/********************************************************************\ + * closeHtmlWinCB - callback for closing html window * + * * + * Args: widget - the widget that called us * + * data - html window structure pointer * + * Return: none * +\********************************************************************/ +static void +closeHtmlWinCB(GtkWidget *widget, gpointer data) +{ + HTMLWindow *hw = (HTMLWindow *) data; + + gtk_widget_destroy(hw->window); +} + + +/********************************************************************\ + * destroyHtmlWinCB - called when the help window is destroyed * + * * + * Args: widget - the widget that called us * + * data - html window structure pointer * + * Return: none * +\********************************************************************/ +static void +destroyHtmlWinCB(GtkWidget *widget, gpointer data) +{ + HTMLWindow **hwp = (HTMLWindow **) data; + HTMLWindow *hw = *hwp; + + /* Delete the history: */ + historyDestroy(hw->history); + hw->history = NULL; + + hw->htmlwidget = NULL; + + g_free(hw); + *hwp = NULL; + + DEBUG("HTML window destroyed.\n"); +} + + +/********************************************************************\ + * htmlAnchorCB - called when user clicks on html anchor tag * + * * + * Args: widget - the html widget that called us * + * acbs - callback structure * + * data - html window structure * + * Return: none * +\********************************************************************/ +static void +htmlAnchorCB(GtkWidget *widget, XmHTMLAnchorCallbackStruct *acbs, + gpointer data) +{ + HTMLWindow *hw = (HTMLWindow *) data; + HTMLHistoryData history_data; + + if (acbs->reason != XmCR_ACTIVATE) return; + + switch(acbs->url_type) + { + /* a named anchor on a page that is already displayed */ + case ANCHOR_JUMP: + XmHTMLAnchorScrollToName(widget, acbs->href); + break; + + default: + if (hw->anchor_cb == NULL) + return; + + history_data = (hw->anchor_cb)(acbs, historyData(hw->history)); + if (history_data == NULL) + return; + + historyInsert(hw->history, history_data); + gnc_html_load(hw); + break; + } + + htmlSetButtonStates(hw); +} + + +/********************************************************************\ + * gnc_html_load - load the current location into the html window * + * * + * Args: hw - the html window structure * + * Return: none * +\********************************************************************/ +void +gnc_html_load(HTMLWindow *hw) +{ + HTMLHistoryData history_data; + char *label = NULL; + char *text = NULL; + + if (hw == NULL) + return; + if (hw->jump_cb == NULL) + return; + + history_data = historyData(hw->history); + + (hw->jump_cb)(history_data, &text, &label); + + if (text == NULL) + return; + + gtk_xmhtml_source(GTK_XMHTML(hw->htmlwidget), text); + + if (label != NULL) + XmHTMLAnchorScrollToName(hw->htmlwidget, label); + else + XmHTMLTextScrollToLine(hw->htmlwidget, 0); +} + + +/********************************************************************\ + * htmlReadImageProc - callback function for the html widget * + * used to find an image file * + * * + * Args: widget - the html widget * + * file - the name of the image file to read * + * data - some data, not used * + * Return: none * +\********************************************************************/ +static XmImageInfo * +htmlReadImageProc (GtkWidget *widget, String file, gpointer data) +{ + char *filename; + XmImageInfo *retval = NULL; + + /* construct absolute path -- twiddle the relative path we received */ + filename = gncFindFile(file); + + /* use the default proc for the hard work */ + retval = XmHTMLImageDefaultProc(widget, filename, NULL, 0); + + free(filename); + return retval; +} + +/* ----------------------- END OF FILE --------------------- */ diff --git a/src/gnome/window-html.h b/src/gnome/window-html.h new file mode 100644 index 0000000000..c1e27a8dc2 --- /dev/null +++ b/src/gnome/window-html.h @@ -0,0 +1,62 @@ +/********************************************************************\ + * window-html -- an html window for gnucash. * + * Copyright (C) 1997 Robin D. Clark * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + * * + * Author: Rob Clark * + * Internet: rclark@cs.hmc.edu * + * Address: 609 8th Street * + * Huntington Beach, CA 92648-4632 * +\********************************************************************/ + +#ifndef __WINDOW_HTML_H__ +#define __WINDOW_HTML_H__ + +#include +#include + + +typedef struct _HTMLWindow HTMLWindow; +typedef void * HTMLHistoryData; + +typedef void (*HTMLHistoryDestroyDataFunc)(HTMLHistoryData); + +typedef HTMLHistoryData (*HTMLAnchorCB)(XmHTMLAnchorCallbackStruct *acbs, + HTMLHistoryData history_data); + +typedef void (*HTMLJumpCB)(HTMLHistoryData history_data, + char **text, char **label); + + +HTMLHistoryData gnc_html_window_history_data(HTMLWindow *hw); + +HTMLWindow * gnc_html_window_new(HTMLHistoryDestroyDataFunc destroy, + HTMLAnchorCB anchor_cb, HTMLJumpCB jump_cb); + + +void gnc_html_window_destroy(HTMLWindow *hw); + +void htmlWindow(GtkWidget * parent, + HTMLWindow ** hwp, + const char * const title, + HTMLHistoryData history_data, + GnomeUIInfo *user_buttons, + gint num_buttons); + +void gnc_html_load(HTMLWindow *hw); + + +#endif diff --git a/src/gnome/window-main.c b/src/gnome/window-main.c index 978de8dbf9..3b764b7c02 100644 --- a/src/gnome/window-main.c +++ b/src/gnome/window-main.c @@ -27,7 +27,7 @@ #include "AccWindow.h" #include "AdjBWindow.h" -#include "dialog-options.h" +#include "global-options.h" #include "FileDialog.h" #include "g-wrap.h" #include "gnucash.h" @@ -35,14 +35,17 @@ #include "Destroy.h" #include "messages.h" #include "RegWindow.h" +#include "Refresh.h" #include "version.h" #include "window-main.h" #include "window-mainP.h" #include "window-reconcile.h" +#include "window-register.h" #include "window-help.h" #include "account-tree.h" #include "dialog-transfer.h" #include "dialog-edit.h" +#include "Scrub.h" #include "util.h" #include "gnc.h" @@ -50,6 +53,7 @@ /* This static indicates the debugging module that this .o belongs to. */ static short module = MOD_GUI; + enum { FMB_NEW, FMB_OPEN, @@ -85,24 +89,6 @@ static GnomeUIInfo filemenu[] = { GNOMEUIINFO_END }; -static GnomeUIInfo reportsmenu[] = { - { - GNOME_APP_UI_ITEM, - N_("_Balance..."), N_("Balance Report"), - gnc_ui_reports_cb, "report-baln.phtml", NULL, - GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_PREF, - 0, 0, NULL - }, - { - GNOME_APP_UI_ITEM, - N_("_Profit & Loss..."), N_("Profit & Loss Report"), - gnc_ui_reports_cb, "report-pnl.phtml", NULL, - GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_PREF, - 0, 0, NULL - }, - GNOMEUIINFO_END -}; - static GnomeUIInfo optionsmenu[] = { { GNOME_APP_UI_ITEM, @@ -123,17 +109,50 @@ static GnomeUIInfo optionsmenu[] = { GNOMEUIINFO_END }; +static GnomeUIInfo scrubmenu[] = { + { + GNOME_APP_UI_ITEM, + N_("_Scrub Account"), N_("Scrub the account clean"), + gnc_ui_mainWindow_scrub, NULL, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + { + GNOME_APP_UI_ITEM, + N_("Scrub S_ubaccounts"), N_("Scrub the account and all its " + "subaccounts clean"), + gnc_ui_mainWindow_scrub_sub, NULL, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + { + GNOME_APP_UI_ITEM, + N_("Scrub _All"), N_("Scrub all the accounts clean"), + gnc_ui_mainWindow_scrub_all, NULL, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + GNOMEUIINFO_END +}; + static GnomeUIInfo accountsmenu[] = { { GNOME_APP_UI_ITEM, - N_("_View..."), N_("View Account"), + N_("_View..."), N_("View account"), gnc_ui_mainWindow_toolbar_open, NULL, NULL, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_OPEN, 'v', GDK_CONTROL_MASK, NULL }, { GNOME_APP_UI_ITEM, - N_("_Edit..."), N_("Edit Account"), + N_("View S_ubaccounts..."), N_("View account and subaccounts"), + gnc_ui_mainWindow_toolbar_open_subs, NULL, NULL, + GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_OPEN, + 0, 0, NULL + }, + { + GNOME_APP_UI_ITEM, + N_("_Edit..."), N_("Edit account information"), gnc_ui_mainWindow_toolbar_edit, NULL, NULL, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_PROP, 'e', GDK_CONTROL_MASK, NULL @@ -175,6 +194,8 @@ static GnomeUIInfo accountsmenu[] = { GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_PIXMAP_REMOVE, 'r', GDK_CONTROL_MASK, NULL }, + GNOMEUIINFO_SEPARATOR, + GNOMEUIINFO_SUBTREE(N_("_Scrub"), scrubmenu), GNOMEUIINFO_END }; @@ -199,7 +220,6 @@ static GnomeUIInfo scriptsmenu[] = { static GnomeUIInfo mainmenu[] = { GNOMEUIINFO_MENU_FILE_TREE(filemenu), GNOMEUIINFO_SUBTREE(N_("_Accounts"), accountsmenu), - GNOMEUIINFO_SUBTREE(N_("_Reports"), reportsmenu), GNOMEUIINFO_SUBTREE(N_("_Options"), optionsmenu), GNOMEUIINFO_SUBTREE(N_("_Extensions"), scriptsmenu), GNOMEUIINFO_MENU_HELP_TREE(helpmenu), @@ -397,13 +417,7 @@ gnc_ui_about_cb (GtkWidget *widget, gpointer data) static void gnc_ui_help_cb ( GtkWidget *widget, gpointer data ) { - helpWindow( GTK_WIDGET(gnc_get_ui_data()), HELP_STR, HH_MAIN ); -} - -static void -gnc_ui_reports_cb(GtkWidget *widget, gpointer report) -{ - reportWindow (widget, "duuuude", report); + helpWindow(NULL, HELP_STR, HH_MAIN); } static void @@ -425,8 +439,9 @@ gnc_ui_delete_account ( Account *account ) xaccRemoveAccount(account); xaccFreeAccount(account); - /* Step 4: Refresh the toolbar */ + /* Step 4: Refresh things */ gnc_ui_refresh_statusbar(); + gnc_group_ui_refresh(gncGetCurrentGroup()); } static void @@ -447,15 +462,37 @@ gnc_ui_delete_account_cb ( GtkWidget *widget, gpointer data ) static void gnc_ui_mainWindow_toolbar_open ( GtkWidget *widget, gpointer data ) { + RegWindow *regData; Account *account = gnc_get_current_account(); - if(account) + if (account == NULL) { - PINFO ("calling regWindowSimple(%p)\n", account); - regWindowSimple ( account ); - } - else gnc_error_dialog("You must select an account to open first."); + return; + } + + PINFO ("calling regWindowSimple(%p)\n", account); + + regData = regWindowSimple(account); + gnc_register_raise(regData); +} + +static void +gnc_ui_mainWindow_toolbar_open_subs(GtkWidget *widget, gpointer data) +{ + RegWindow *regData; + Account *account = gnc_get_current_account(); + + if (account == NULL) + { + gnc_error_dialog("You must select an account to open first."); + return; + } + + PINFO ("calling regWindowAccGroup(%p)\n", account); + + regData = regWindowAccGroup(account); + gnc_register_raise(regData); } static void @@ -505,6 +542,54 @@ gnc_ui_mainWindow_adjust_balance(GtkWidget *widget, gpointer data) gnc_error_dialog("You must select an account to adjust first."); } +static void +gnc_ui_mainWindow_scrub(GtkWidget *widget, gpointer data) +{ + Account *account = gnc_get_current_account(); + + if (account == NULL) + { + gnc_error_dialog("You must select an account to scrub first."); + return; + } + + xaccAccountScrubOrphans(account); + xaccAccountScrubImbalance(account); + + gnc_account_ui_refresh(account); + gnc_refresh_main_window(); +} + +static void +gnc_ui_mainWindow_scrub_sub(GtkWidget *widget, gpointer data) +{ + Account *account = gnc_get_current_account(); + + if (account == NULL) + { + gnc_error_dialog("You must select an account to scrub first."); + return; + } + + xaccAccountTreeScrubOrphans(account); + xaccAccountTreeScrubImbalance(account); + + gnc_account_ui_refresh(account); + gnc_refresh_main_window(); +} + +static void +gnc_ui_mainWindow_scrub_all(GtkWidget *widget, gpointer data) +{ + AccountGroup *group = gncGetCurrentGroup(); + + xaccGroupScrubOrphans(group); + xaccGroupScrubImbalance(group); + + gnc_group_ui_refresh(group); + gnc_refresh_main_window(); +} + static void gnc_ui_options_cb(GtkWidget *widget, gpointer data) { @@ -582,11 +667,14 @@ gnc_account_tree_activate_cb(GNCAccountTree *tree, Account *account, gpointer user_data) { - regWindowSimple(account); + RegWindow *regData; + + regData = regWindowSimple(account); + gnc_register_raise(regData); } static void -gnc_configure_account_tree() +gnc_configure_account_tree(gpointer data) { GtkObject *app; GNCAccountTree *tree; @@ -713,8 +801,9 @@ mainWindow() account_tree = gnc_account_tree_new(); gtk_object_set_data (GTK_OBJECT (app), "account_tree", account_tree); - gnc_configure_account_tree(); - gnc_register_option_change_callback(gnc_configure_account_tree); + gnc_configure_account_tree(NULL); + gnc_register_option_change_callback(gnc_configure_account_tree, NULL); + gtk_signal_connect(GTK_OBJECT (account_tree), "activate_account", GTK_SIGNAL_FUNC (gnc_account_tree_activate_cb), NULL); @@ -773,15 +862,8 @@ mainWindow() gtk_widget_show(scrolled_win); gnc_refresh_main_window(); + + gtk_widget_grab_focus(account_tree); } /********************* END OF FILE **********************************/ - -/* - Local Variables: - indent-tabs-mode: nil - mode: c - c-indentation-style: gnu - eval: (c-set-offset 'block-open '-) - End: -*/ diff --git a/src/gnome/window-mainP.h b/src/gnome/window-mainP.h index aab99d35e7..7ccdde44c6 100644 --- a/src/gnome/window-mainP.h +++ b/src/gnome/window-mainP.h @@ -25,7 +25,7 @@ #ifndef __WINDOW_MAINP_H__ #define __WINDOW_MAINP_H__ -#include +#include #include "top-level.h" @@ -35,14 +35,18 @@ static void gnc_ui_refresh_statusbar(void); static void gnc_ui_exit_cb(GtkWidget *widget, gpointer data); static void gnc_ui_about_cb(GtkWidget *widget, gpointer data); static void gnc_ui_help_cb(GtkWidget *widget, gpointer data); -static void gnc_ui_reports_cb(GtkWidget *widget, gpointer report); static void gnc_ui_add_account(GtkWidget *widget, gpointer data); static void gnc_ui_delete_account_cb(GtkWidget *widget, gpointer data); static void gnc_ui_mainWindow_toolbar_open(GtkWidget *widget, gpointer data); +static void gnc_ui_mainWindow_toolbar_open_subs(GtkWidget *widget, + gpointer data); static void gnc_ui_mainWindow_toolbar_edit(GtkWidget *widget, gpointer data); static void gnc_ui_mainWindow_reconcile(GtkWidget *widget, gpointer data); static void gnc_ui_mainWindow_transfer(GtkWidget *widget, gpointer data); static void gnc_ui_mainWindow_adjust_balance(GtkWidget *widget, gpointer data); +static void gnc_ui_mainWindow_scrub(GtkWidget *widget, gpointer data); +static void gnc_ui_mainWindow_scrub_sub(GtkWidget *widget, gpointer data); +static void gnc_ui_mainWindow_scrub_all(GtkWidget *widget, gpointer data); static void gnc_ui_options_cb(GtkWidget *widget, gpointer data); static void gnc_ui_filemenu_cb(GtkWidget *widget, gpointer menuItem); diff --git a/src/gnome/window-reconcile.c b/src/gnome/window-reconcile.c index 808988b8f5..58f3446c7e 100644 --- a/src/gnome/window-reconcile.c +++ b/src/gnome/window-reconcile.c @@ -33,7 +33,10 @@ #include "MainWindow.h" #include "RegWindow.h" #include "window-reconcile.h" +#include "window-register.h" +#include "dialog-utils.h" #include "reconcile-list.h" +#include "Refresh.h" #include "query-user.h" #include "window-help.h" #include "messages.h" @@ -58,6 +61,9 @@ struct _RecnWindow GtkWidget *debit; /* Debit matrix show unreconciled debit */ GtkWidget *credit; /* Credit matrix, shows credits... */ + GtkWidget *edit_button; /* Edit transaction button */ + GtkWidget *delete_button; /* Delete transaction button */ + char * symbol; /* Currency symbol or 's' for shares */ }; @@ -68,6 +74,8 @@ static void recnClose(GtkWidget *w, gpointer data); static void recnOkCB(GtkWidget *w, gpointer data); static void recnCancelCB(GtkWidget *w, gpointer data); +static void gnc_reconcile_window_set_button_sensitivity(RecnWindow *recnData); + /** GLOBALS *********************************************************/ static RecnWindow **recnList = NULL; @@ -97,6 +105,8 @@ recnRefresh(Account *account) gnc_reconcile_list_refresh(GNC_RECONCILE_LIST(recnData->debit)); gnc_reconcile_list_refresh(GNC_RECONCILE_LIST(recnData->credit)); + gnc_reconcile_window_set_button_sensitivity(recnData); + recnRecalculateBalance(recnData); } @@ -159,7 +169,7 @@ recnRecalculateBalance(RecnWindow *recnData) * or "Cancel" * * * * Args: parent - the parent of this window * - * acc - the account to reconcile * + * account - the account to reconcile * * diff - returns the amount from ending balance field * * Return: True, if the user presses "Ok", else False * \********************************************************************/ @@ -272,15 +282,50 @@ startRecnWindow(GtkWidget *parent, Account *account, double *diff) } +static void +gnc_reconcile_window_set_button_sensitivity(RecnWindow *recnData) +{ + gboolean sensitive = FALSE; + GNCReconcileList *list; + + list = GNC_RECONCILE_LIST(recnData->debit); + if (gnc_reconcile_list_get_current_split(list) != NULL) + sensitive = TRUE; + + list = GNC_RECONCILE_LIST(recnData->credit); + if (gnc_reconcile_list_get_current_split(list) != NULL) + sensitive = TRUE; + + gtk_widget_set_sensitive(recnData->edit_button, sensitive); + gtk_widget_set_sensitive(recnData->delete_button, sensitive); +} + static void gnc_reconcile_window_list_cb(GNCReconcileList *list, Split *split, gpointer data) { RecnWindow *recnData = (RecnWindow *) data; + gnc_reconcile_window_set_button_sensitivity(recnData); recnRecalculateBalance(recnData); } +static void +gnc_reconcile_window_focus_cb(GtkWidget *widget, GdkEventFocus *event, + gpointer data) +{ + RecnWindow *recnData = (RecnWindow *) data; + GNCReconcileList *this_list, *debit, *credit; + + this_list = GNC_RECONCILE_LIST(widget); + + debit = GNC_RECONCILE_LIST(recnData->debit); + credit = GNC_RECONCILE_LIST(recnData->credit); + + /* clear the *other* list so we always have no more than one selection */ + gnc_reconcile_list_unselect_all((this_list == debit) ? credit : debit); +} + static GtkWidget * gnc_reconcile_window_create_list_frame(Account *account, GNCReconcileListType type, @@ -305,6 +350,8 @@ gnc_reconcile_window_create_list_frame(Account *account, gtk_signal_connect(GTK_OBJECT(list), "toggle_reconciled", GTK_SIGNAL_FUNC(gnc_reconcile_window_list_cb), recnData); + gtk_signal_connect(GTK_OBJECT(list), "focus_in_event", + GTK_SIGNAL_FUNC(gnc_reconcile_window_focus_cb), recnData); scrollWin = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrollWin), @@ -331,6 +378,23 @@ gnc_reconcile_window_create_list_frame(Account *account, } +static Split * +gnc_reconcile_window_get_current_split(RecnWindow *recnData) +{ + GNCReconcileList *list; + Split *split; + + list = GNC_RECONCILE_LIST(recnData->debit); + split = gnc_reconcile_list_get_current_split(list); + if (split != NULL) + return split; + + list = GNC_RECONCILE_LIST(recnData->credit); + split = gnc_reconcile_list_get_current_split(list); + + return split; +} + static void gnc_ui_reconcile_window_help_cb(GtkWidget *widget, gpointer data) { @@ -350,6 +414,94 @@ gnc_ui_reconcile_window_change_cb(GtkButton *button, gpointer data) } } +static void +gnc_ui_reconcile_window_new_cb(GtkButton *button, gpointer data) +{ + RecnWindow *recnData = (RecnWindow *) data; + RegWindow *regData; + + regData = regWindowSimple(recnData->account); + if (regData == NULL) + return; + + gnc_register_raise(regData); + gnc_register_jump_to_blank(regData); +} + +static void +gnc_ui_reconcile_window_delete_cb(GtkButton *button, gpointer data) +{ + RecnWindow *recnData = (RecnWindow *) data; + Account **affected_accounts; + Transaction *trans; + Split *split, *s; + int i, num_splits; + + split = gnc_reconcile_window_get_current_split(recnData); + /* This should never be true, but be paranoid */ + if (split == NULL) + return; + + { + gchar * buf = "Are you sure you want to delete the current transaction?"; + gboolean result; + + result = gnc_verify_dialog_parented(GTK_WINDOW(recnData->dialog), + buf, GNC_F); + + if (!result) + return; + } + + /* make a copy of all of the accounts that will be + * affected by this deletion, so that we can update + * their register windows after the deletion. + */ + trans = xaccSplitGetParent(split); + num_splits = xaccTransCountSplits(trans); + affected_accounts = (Account **) malloc((num_splits + 1) * + sizeof(Account *)); + assert(affected_accounts != NULL); + + for (i = 0; i < num_splits; i++) + { + s = xaccTransGetSplit(trans, i); + affected_accounts[i] = xaccSplitGetAccount(s); + } + affected_accounts[num_splits] = NULL; + + xaccTransBeginEdit(trans, 1); + xaccTransDestroy(trans); + xaccTransCommitEdit(trans); + + gnc_account_list_ui_refresh(affected_accounts); + + free(affected_accounts); + + gnc_refresh_main_window (); +} + +static void +gnc_ui_reconcile_window_edit_cb(GtkButton *button, gpointer data) +{ + RecnWindow *recnData = (RecnWindow *) data; + RegWindow *regData; + Split *split; + + split = gnc_reconcile_window_get_current_split(recnData); + /* This should never be true, but be paranoid */ + if (split == NULL) + return; + + regData = regWindowSimple(recnData->account); + if (regData == NULL) + return; + + gnc_register_raise(regData); + gnc_register_jump_to_split(regData, split); +} + + /********************************************************************\ * recnWindow * * opens up the window to reconcile an account * @@ -436,55 +588,94 @@ recnWindow(GtkWidget *parent, Account *account) gtk_box_pack_start(GTK_BOX(debcred_area), debits_frame, TRUE, FALSE, 0); gtk_box_pack_start(GTK_BOX(debcred_area), credits_frame, TRUE, FALSE, 0); - { - GtkWidget *hbox = gtk_hbox_new(FALSE, 5); + { + GtkWidget *hbox, *title_vbox, *value_vbox, *button; + GtkWidget *totals_hbox, *frame, *title, *value, *bbox; - GtkWidget *prev_title = gtk_label_new(PREV_BALN_C_STR); - GtkWidget *end_title = gtk_label_new(END_BALN_C_STR); - GtkWidget *space = gtk_label_new(""); - GtkWidget *prev_value = gtk_label_new(""); - GtkWidget *end_value = gtk_label_new(""); + /* lower horizontal bar below reconcile lists */ + hbox = gtk_hbox_new(FALSE, 5); + gtk_box_pack_start(GTK_BOX(main_area), hbox, FALSE, FALSE, 0); - GtkWidget *change_end = gtk_button_new(); - GtkWidget *difference_frame = gtk_frame_new(NULL); - GtkWidget *difference_box = gtk_hbox_new(FALSE, 5); - GtkWidget *difference_label = gtk_label_new(DIFF_C_STR); - GtkWidget *difference_value = gtk_label_new(""); + bbox = gtk_hbutton_box_new(); + gtk_box_pack_start(GTK_BOX(hbox), bbox, FALSE, FALSE, 0); - gtk_container_add(GTK_CONTAINER(change_end), end_value); - gtk_container_set_border_width(GTK_CONTAINER(change_end), 4); - gtk_button_set_relief(GTK_BUTTON(change_end), GTK_RELIEF_HALF); + button = gtk_button_new_with_label("New"); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gnc_set_tooltip(button, "Add a new transaction to the account"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(gnc_ui_reconcile_window_new_cb), + recnData); - gtk_signal_connect(GTK_OBJECT(change_end), "clicked", - GTK_SIGNAL_FUNC(gnc_ui_reconcile_window_change_cb), - recnData); + button = gtk_button_new_with_label("Edit"); + recnData->edit_button = button; + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gnc_set_tooltip(button, "Edit the current transaction"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(gnc_ui_reconcile_window_edit_cb), + recnData); + + button = gtk_button_new_with_label("Delete"); + recnData->delete_button = button; + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gnc_set_tooltip(button, "Delete the current transaction"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(gnc_ui_reconcile_window_delete_cb), + recnData); - gtk_misc_set_alignment(GTK_MISC(prev_title), 0.95, 0.5); - gtk_misc_set_alignment(GTK_MISC(end_title), 0.95, 0.5); - gtk_misc_set_alignment(GTK_MISC(difference_label), 0.95, 0.5); + button = gtk_button_new_with_label("Edit Info..."); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gnc_set_tooltip(button, "Adjust the ending balance"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(gnc_ui_reconcile_window_change_cb), + recnData); - recnData->starting = prev_value; - recnData->ending = end_value; - recnData->difference = difference_value; + /* frame to hold totals */ + frame = gtk_frame_new(NULL); + gtk_box_pack_end(GTK_BOX(hbox), frame, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(main_area), hbox, FALSE, FALSE, 0); + /* hbox to hold title/value vboxes */ + totals_hbox = gtk_hbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(frame), totals_hbox); + gtk_container_set_border_width(GTK_CONTAINER(totals_hbox), 5); - gtk_box_pack_start(GTK_BOX(hbox), prev_title, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), prev_value, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), space, FALSE, FALSE, 3); - gtk_box_pack_start(GTK_BOX(hbox), end_title, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), change_end, FALSE, FALSE, 0); - gtk_box_pack_end(GTK_BOX(hbox), difference_frame, FALSE, FALSE, 0); + /* vbox to hold titles */ + title_vbox = gtk_vbox_new(TRUE, 3); + gtk_box_pack_start(GTK_BOX(totals_hbox), title_vbox, FALSE, FALSE, 0); - gtk_frame_set_shadow_type(GTK_FRAME(difference_frame), GTK_SHADOW_IN); - gtk_container_add(GTK_CONTAINER(difference_frame), difference_box); + /* vbox to hold values */ + value_vbox = gtk_vbox_new(TRUE, 3); + gtk_box_pack_start(GTK_BOX(totals_hbox), value_vbox, TRUE, TRUE, 0); - gtk_container_set_border_width(GTK_CONTAINER(difference_box), 5); - gtk_box_pack_start(GTK_BOX(difference_box), difference_label, - TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(difference_box), difference_value, - FALSE, FALSE, 0); - } + /* previous balance title/value */ + title = gtk_label_new(PREV_BALN_C_STR); + gtk_misc_set_alignment(GTK_MISC(title), 0.95, 0.5); + gtk_box_pack_start(GTK_BOX(title_vbox), title, FALSE, FALSE, 0); + + value = gtk_label_new(""); + recnData->starting = value; + gtk_misc_set_alignment(GTK_MISC(value), 0.95, 0.5); + gtk_box_pack_start(GTK_BOX(value_vbox), value, FALSE, FALSE, 0); + + /* ending balance title/value */ + title = gtk_label_new(END_BALN_C_STR); + gtk_misc_set_alignment(GTK_MISC(title), 0.95, 0.5); + gtk_box_pack_start(GTK_BOX(title_vbox), title, FALSE, FALSE, 0); + + value = gtk_label_new(""); + recnData->ending = value; + gtk_misc_set_alignment(GTK_MISC(value), 0.95, 0.5); + gtk_box_pack_start(GTK_BOX(value_vbox), value, FALSE, FALSE, 0); + + /* difference title/value */ + title = gtk_label_new(DIFF_C_STR); + gtk_misc_set_alignment(GTK_MISC(title), 0.95, 0.5); + gtk_box_pack_start(GTK_BOX(title_vbox), title, FALSE, FALSE, 0); + + value = gtk_label_new(""); + recnData->difference = value; + gtk_misc_set_alignment(GTK_MISC(value), 0.95, 0.5); + gtk_box_pack_start(GTK_BOX(value_vbox), value, FALSE, FALSE, 0); + } /* Set up the data */ recnRefresh(account); diff --git a/src/gnome/window-register.c b/src/gnome/window-register.c index 3a7cc6dcc2..f3e4cb4c5d 100644 --- a/src/gnome/window-register.c +++ b/src/gnome/window-register.c @@ -1,5 +1,5 @@ /*******************************************************************\ - * RegWindow.c -- the register window for xacc (X-Accountant) * + * window-register.c -- the register window for GnuCash * * Copyright (C) 1997 Robin D. Clark * * Copyright (C) 1997, 1998 Linas Vepstas * * Copyright (C) 1998 Rob Browning * @@ -39,6 +39,7 @@ #include "MainWindow.h" #include "Refresh.h" #include "RegWindow.h" +#include "Scrub.h" #include "window-reconcile.h" #include "AccWindow.h" #include "window-help.h" @@ -50,6 +51,7 @@ #include "table-gnome.h" #include "table-html.h" #include "gnucash-sheet.h" +#include "global-options.h" #include "util.h" @@ -78,6 +80,8 @@ struct _RegWindow /* Top level window */ GtkWidget * window; + GtkWidget * toolbar; + GtkWidget * balance_label; GtkWidget * cleared_label; @@ -87,6 +91,8 @@ struct _RegWindow StyleData * style_cb_data; SortData * sort_cb_data; + GnucashRegister *reg; + /* Do we close the ledger when the window closes? */ gncBoolean close_ledger; }; @@ -102,6 +108,7 @@ static void regRefresh(xaccLedgerDisplay *ledger); static void regDestroy(xaccLedgerDisplay *ledger); static void closeRegWindow(GtkWidget * mw, RegWindow * regData); +static void gnc_register_check_close(RegWindow *regData); static void startRecnCB(GtkWidget *w, gpointer data); static void xferCB(GtkWidget *w, gpointer data); @@ -113,6 +120,8 @@ static void recordCB(GtkWidget *w, gpointer data); static void cancelCB(GtkWidget *w, gpointer data); static void closeCB(GtkWidget *w, gpointer data); +static gboolean gnc_register_include_date(RegWindow *regData, time_t date); + /********************************************************************\ * regWindowSimple * @@ -154,8 +163,90 @@ regWindowAccGroup(Account *account) } +/********************************************************************\ + * gnc_register_raise * + * raise an existing register window to the front * + * * + * Args: regData - the register data structure * + * Return: nothing * +\********************************************************************/ +void +gnc_register_raise(RegWindow *regData) +{ + if (regData == NULL) + return; + + if (regData->window == NULL) + return; + + gtk_widget_show(regData->window); + + if (regData->window->window == NULL) + return; + + gdk_window_raise(regData->window->window); +} + + +/********************************************************************\ + * gnc_register_jump_to_split * + * move the cursor to the split, if present in register * + * * + * Args: regData - the register data structure * + * split - the split to jump to * + * Return: nothing * +\********************************************************************/ +void +gnc_register_jump_to_split(RegWindow *regData, Split *split) +{ + Transaction *trans; + int vrow, vcol; + + trans = xaccSplitGetParent(split); + if (trans != NULL) + if (gnc_register_include_date(regData, xaccTransGetDate(trans))) + { + regData->ledger->dirty = 1; + xaccLedgerDisplayRefresh(regData->ledger); + } + + if (xaccSRGetSplitRowCol(regData->ledger->ledger, split, &vrow, &vcol)) + gnucash_register_goto_virt_row_col(regData->reg, vrow, vcol); +} + + +static int +gnc_register_get_default_type(SplitRegister *reg) +{ + char *style_string; + int new_style = REG_SINGLE_LINE; + int type = reg->type; + + type &= ~REG_STYLE_MASK; + + style_string = gnc_lookup_multichoice_option("Register", + "Default Register Mode", + "single_line"); + + if (safe_strcmp(style_string, "single_line") == 0) + new_style = REG_SINGLE_LINE; + else if (safe_strcmp(style_string, "double_line") == 0) + new_style = REG_DOUBLE_LINE; + else if (safe_strcmp(style_string, "multi_line") == 0) + new_style = REG_MULTI_LINE; + else if (safe_strcmp(style_string, "auto_single") == 0) + new_style = REG_SINGLE_DYNAMIC; + else if (safe_strcmp(style_string, "auto_double") == 0) + new_style = REG_DOUBLE_DYNAMIC; + + type |= new_style; + + return type; +} + + static void -ledger_change_style_cb(GtkWidget *w, gpointer data) +ledger_change_style_cb(GtkWidget *w, gint index, gpointer data) { StyleData *style_data = (StyleData *) data; xaccLedgerDisplay *ld = style_data->regData->ledger; @@ -174,7 +265,9 @@ ledger_change_style_cb(GtkWidget *w, gpointer data) static GtkWidget * gnc_build_ledger_style_menu(RegWindow *regData) { + GtkWidget *omenu; gint num_items; + int style; gint i; static StyleData style_data[] = @@ -188,11 +281,17 @@ gnc_build_ledger_style_menu(RegWindow *regData) static GNCOptionInfo style_items[] = { - { "Single Line", ledger_change_style_cb, NULL }, - { "Double Line", ledger_change_style_cb, NULL }, - { "Multi Line", ledger_change_style_cb, NULL }, - { "Auto Single", ledger_change_style_cb, NULL }, - { "Auto Double", ledger_change_style_cb, NULL } + { "Single Line", "Show transactions on single lines", + ledger_change_style_cb, NULL }, + { "Double Line", "Show transactions on two lines with more information", + ledger_change_style_cb, NULL }, + { "Multi Line", "Show transactions on multiple lines with one line " + "for each split in the transaction", + ledger_change_style_cb, NULL }, + { "Auto Single", "Single line mode with multi-line cursor", + ledger_change_style_cb, NULL }, + { "Auto Double", "Double line mode with multi-line cursor", + ledger_change_style_cb, NULL } }; num_items = sizeof(style_items) / sizeof(GNCOptionInfo); @@ -207,18 +306,33 @@ gnc_build_ledger_style_menu(RegWindow *regData) style_items[i].user_data = ®Data->style_cb_data[i]; } - return gnc_build_option_menu(style_items, num_items); + omenu = gnc_build_option_menu(style_items, num_items); + + style = gnc_register_get_default_type(regData->ledger->ledger); + style &= REG_STYLE_MASK; + + for (i = 0; i < num_items; i++) + if (style == regData->style_cb_data[i].style_code) + { + gtk_option_menu_set_history(GTK_OPTION_MENU(omenu), i); + break; + } + + return omenu; } static void -gnc_ledger_sort_cb(GtkWidget *w, gpointer data) +gnc_ledger_sort_cb(GtkWidget *w, gint index, gpointer data) { SortData *sortData = (SortData *) data; Query *query = sortData->regData->ledger->query; switch(sortData->sort_code) { + case BY_STANDARD: + xaccQuerySetSortOrder(query, BY_STANDARD, BY_NONE, BY_NONE); + break; case BY_DATE: xaccQuerySetSortOrder(query, BY_DATE, BY_NUM, BY_AMOUNT); break; @@ -250,6 +364,7 @@ gnc_build_ledger_sort_order_menu(RegWindow *regData) static SortData sort_data[] = { + { NULL, BY_STANDARD }, { NULL, BY_DATE }, { NULL, BY_NUM }, { NULL, BY_AMOUNT }, @@ -259,11 +374,18 @@ gnc_build_ledger_sort_order_menu(RegWindow *regData) static GNCOptionInfo sort_items[] = { - { "Sort by date", gnc_ledger_sort_cb, NULL }, - { "Sort by num", gnc_ledger_sort_cb, NULL }, - { "Sort by amount", gnc_ledger_sort_cb, NULL }, - { "Sort by memo", gnc_ledger_sort_cb, NULL }, - { "Sort by description", gnc_ledger_sort_cb, NULL } + { "Standard order", "Keep normal account order", + gnc_ledger_sort_cb, NULL }, + { "Sort by date", "Sort by date, then num, then amount", + gnc_ledger_sort_cb, NULL }, + { "Sort by num", "Sort by num, then date, then amount", + gnc_ledger_sort_cb, NULL }, + { "Sort by amount", "Sort by amount, then date, then num", + gnc_ledger_sort_cb, NULL }, + { "Sort by memo", "Sort by memo, then date, then num", + gnc_ledger_sort_cb, NULL }, + { "Sort by description", "Sort by description, then date, then num", + gnc_ledger_sort_cb, NULL } }; num_items = sizeof(sort_items) / sizeof(GNCOptionInfo); @@ -281,22 +403,63 @@ gnc_build_ledger_sort_order_menu(RegWindow *regData) return gnc_build_option_menu(sort_items, num_items); } +static time_t +gnc_register_min_day_time(time_t time_val) +{ + struct tm *time_struct; + + /* Get the equivalent time structure */ + time_struct = localtime(&time_val); + + /* First second of the day */ + time_struct->tm_sec = 0; + time_struct->tm_min = 0; + time_struct->tm_hour = 0; + + return mktime(time_struct); +} + +static time_t +gnc_register_max_day_time(time_t time_val) +{ + struct tm *time_struct; + + /* Get the equivalent time structure */ + time_struct = localtime(&time_val); + + /* Last second of the day */ + time_struct->tm_sec = 59; + time_struct->tm_min = 59; + time_struct->tm_hour = 23; + + return mktime(time_struct); +} + +static void +gnc_register_set_date_range(RegWindow *regData) +{ + time_t start; + time_t end; + + start = gnome_date_edit_get_date(GNOME_DATE_EDIT(regData->start_date)); + end = gnome_date_edit_get_date(GNOME_DATE_EDIT(regData->end_date)); + + start = gnc_register_min_day_time(start); + end = gnc_register_max_day_time(end); + + xaccQuerySetDateRange(regData->ledger->query, start, end); +} static void gnc_register_date_cb(GtkWidget *widget, gpointer data) { RegWindow *regData = (RegWindow *) data; - time_t start; - time_t end; assert(regData != NULL); assert(regData->ledger != NULL); assert(regData->ledger->query != NULL); - start = gnome_date_edit_get_date(GNOME_DATE_EDIT(regData->start_date)); - end = gnome_date_edit_get_date(GNOME_DATE_EDIT(regData->end_date)); - - xaccQuerySetDateRange(regData->ledger->query, start, end); + gnc_register_set_date_range(regData); regData->ledger->dirty = 1; xaccLedgerDisplayRefresh(regData->ledger); @@ -320,27 +483,43 @@ gnc_register_create_tool_bar(RegWindow *regData) GtkWidget *hbox; hbox = gtk_hbox_new(FALSE, 5); - gtk_container_set_border_width(GTK_CONTAINER(hbox), 1); /* Transaction Buttons */ { GtkWidget *toolbar; + GnomeUIInfo toolbar_info[] = + { + { + GNOME_APP_UI_ITEM, + "Record", "Record the current transaction", + recordCB, regData, NULL, + GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_PIXMAP_ADD, + 0, 0, NULL + }, + { + GNOME_APP_UI_ITEM, + "Cancel", "Cancel the current transaction", + cancelCB, regData, NULL, + GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_PIXMAP_UNDO, + 0, 0, NULL + }, + { + GNOME_APP_UI_ITEM, + "Delete", "Delete the current transaction", + deleteCB, regData, NULL, + GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_PIXMAP_TRASH, + 0, 0, NULL + }, + GNOMEUIINFO_END + }; - toolbar = gtk_toolbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_TEXT); - gtk_box_pack_start(GTK_BOX(hbox), toolbar, TRUE, TRUE, 0); - gtk_widget_show(toolbar); + toolbar = gtk_toolbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_BOTH); - gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), - "Record", "Record the current transaction", - NULL, NULL, recordCB, regData); + gnome_app_fill_toolbar(GTK_TOOLBAR(toolbar), toolbar_info, NULL); - gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), - "Cancel", "Cancel the current edit", - NULL, NULL, cancelCB, regData); + gtk_box_pack_start(GTK_BOX(hbox), toolbar, FALSE, FALSE, 0); - gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), - "Delete", "Delete the current transaction", - NULL, NULL, deleteCB, regData); + regData->toolbar = toolbar; } { @@ -368,9 +547,11 @@ gnc_register_create_tool_bar(RegWindow *regData) vbox = gtk_vbox_new(TRUE, 2); gtk_box_pack_start(GTK_BOX(balance_hbox), vbox, FALSE, FALSE, 0); label = gtk_label_new(""); + gtk_misc_set_alignment(GTK_MISC(label), 0.95, 0.5); regData->balance_label = label; gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); label = gtk_label_new(""); + gtk_misc_set_alignment(GTK_MISC(label), 0.95, 0.5); regData->cleared_label = label; gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); } @@ -449,29 +630,97 @@ gnc_register_create_tool_bar(RegWindow *regData) time_val = xaccQueryGetEarliestDateFound(regData->ledger->query); if (time_val < time(NULL)) gnome_date_edit_set_time(GNOME_DATE_EDIT(regData->start_date), time_val); - gnc_register_date_cb(date, regData); + + gnc_register_set_date_range(regData); } - { - GtkWidget *handle_box; - - handle_box = gtk_handle_box_new(); - gtk_container_add(GTK_CONTAINER(handle_box), hbox); - gtk_widget_show_all(handle_box); - - return handle_box; - } + return hbox; } +void +gnc_register_jump_to_blank(RegWindow *regData) +{ + SplitRegister *sr = regData->ledger->ledger; + Split *blank; + int vrow, vcol; + + blank = xaccSRGetBlankSplit(sr); + if (blank == NULL) + return; + + if (xaccSRGetSplitRowCol(sr, blank, &vrow, &vcol)) + gnucash_register_goto_virt_row_col(regData->reg, vrow, vcol); +} + + +static void +new_trans_cb(GtkWidget *widget, gpointer data) +{ + RegWindow *regData = (RegWindow *) data; + + gnc_register_jump_to_blank(regData); +} + +static void +jump_cb(GtkWidget *widget, gpointer data) +{ + RegWindow *regData = (RegWindow *) data; + Account *account; + Split *split; + + split = xaccSRGetCurrentSplit(regData->ledger->ledger); + if (split == NULL) + return; + + account = xaccSplitGetAccount(split); + if (account == NULL) + return; + + if (account == regData->ledger->leader) + { + split = xaccGetOtherSplit(split); + if (split == NULL) + return; + + account = xaccSplitGetAccount(split); + if (account == NULL) + return; + if (account == regData->ledger->leader) + return; + } + + regData = regWindowSimple(account); + if (regData == NULL) + return; + + gnc_register_raise(regData); + gnc_register_jump_to_split(regData, split); +} + +static void +gnc_register_scrub_cb(GtkWidget *widget, gpointer data) +{ + RegWindow *regData = (RegWindow *) data; + Account *account = regData->ledger->leader; + + if (account == NULL) + return; + + xaccAccountTreeScrubOrphans(account); + xaccAccountTreeScrubImbalance(account); + + gnc_account_ui_refresh(account); + gnc_refresh_main_window(); +} + static GtkWidget * gnc_register_create_menu_bar(RegWindow *regData) { GtkWidget *menubar; GtkAccelGroup *accel_group; - GtkWidget *handle_box; - GnomeUIInfo register_menu[] = + GnomeUIInfo account_menu[] = { { GNOME_APP_UI_ITEM, @@ -502,6 +751,14 @@ gnc_register_create_menu_bar(RegWindow *regData) 0, 0, NULL }, GNOMEUIINFO_SEPARATOR, + { + GNOME_APP_UI_ITEM, + N_("_Scrub"), N_("Scrub the account and its subaccounts clean"), + gnc_register_scrub_cb, regData, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + GNOMEUIINFO_SEPARATOR, { GNOME_APP_UI_ITEM, "_Close", "Close this register window", @@ -535,6 +792,22 @@ gnc_register_create_menu_bar(RegWindow *regData) GNOME_APP_PIXMAP_NONE, NULL, 0, 0, NULL }, + GNOMEUIINFO_SEPARATOR, + { + GNOME_APP_UI_ITEM, + "_New", "Edit the new new transaction", + new_trans_cb, regData, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + { + GNOME_APP_UI_ITEM, + "_Jump", "Jump to the corresponding transaction in " + "the other account", + jump_cb, regData, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, GNOMEUIINFO_END }; @@ -552,33 +825,93 @@ gnc_register_create_menu_bar(RegWindow *regData) GnomeUIInfo register_window_menu[] = { - GNOMEUIINFO_SUBTREE("_Register", register_menu), + GNOMEUIINFO_SUBTREE("_Account", account_menu), GNOMEUIINFO_SUBTREE("_Transaction", transaction_menu), GNOMEUIINFO_MENU_HELP_TREE(help_menu), GNOMEUIINFO_END }; menubar = gtk_menu_bar_new(); - gtk_widget_show(menubar); accel_group = gtk_accel_group_new(); gtk_accel_group_attach(accel_group, GTK_OBJECT(regData->window)); gnome_app_fill_menu(GTK_MENU_SHELL(menubar), register_window_menu, - accel_group, TRUE, 0); + accel_group, TRUE, 0); - handle_box = gtk_handle_box_new(); - gtk_container_add(GTK_CONTAINER(handle_box), menubar); - gtk_widget_show(handle_box); + return menubar; +} - return handle_box; + +static GtkWidget * +gnc_register_create_popup_menu(RegWindow *regData) +{ + GtkWidget *popup; + + GnomeUIInfo transaction_menu[] = + { + { + GNOME_APP_UI_ITEM, + "_Record", "Record the current transaction", + recordCB, regData, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + { + GNOME_APP_UI_ITEM, + "_Cancel", "Cancel the current edit", + cancelCB, regData, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + { + GNOME_APP_UI_ITEM, + "_Delete", "Delete the current transaction", + deleteCB, regData, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + GNOMEUIINFO_SEPARATOR, + { + GNOME_APP_UI_ITEM, + "_New", "Edit the new new transaction", + new_trans_cb, regData, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + { + GNOME_APP_UI_ITEM, + "_Jump", "Jump to the corresponding transaction in " + "the other account", + jump_cb, regData, NULL, + GNOME_APP_PIXMAP_NONE, NULL, + 0, 0, NULL + }, + GNOMEUIINFO_END + }; + + popup = gnome_popup_menu_new(transaction_menu); + + return popup; } +static void +gnc_register_record_cb(GnucashRegister *reg, gpointer data) +{ + /* First record the transaction. This will perform a refresh. */ + recordCB(GTK_WIDGET(reg), data); + + /* Now move down. */ + gnucash_register_goto_next_virt_row(reg); +} + static void gnc_register_destroy_cb(GtkWidget *widget, gpointer data) { - closeRegWindow(widget, (RegWindow *) data); + RegWindow *regData = (RegWindow *) data; + + closeRegWindow(widget, regData); } /********************************************************************\ @@ -593,10 +926,11 @@ regWindowLedger(xaccLedgerDisplay *ledger) { RegWindow *regData = NULL; GtkWidget *register_window; - GtkWidget *register_vbox; + GtkWidget *register_dock; GtkWidget *table_frame; xaccQuerySetMaxSplits(ledger->query, INT_MAX); + xaccQuerySetSortOrder(ledger->query, BY_STANDARD, BY_NONE, BY_NONE); regData = (RegWindow *) (ledger->gui_hook); if (regData != NULL) @@ -609,8 +943,8 @@ regWindowLedger(xaccLedgerDisplay *ledger) ledger->destroy = regDestroy; register_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - register_vbox = gtk_vbox_new(FALSE, 0); - gtk_container_add(GTK_CONTAINER(register_window), register_vbox); + register_dock = gnome_dock_new(); + gtk_container_add(GTK_CONTAINER(register_window), register_dock); regData->ledger = ledger; regData->close_ledger = GNC_T; @@ -623,7 +957,8 @@ regWindowLedger(xaccLedgerDisplay *ledger) if (ledger->leader) { - char * acc_name = xaccAccountGetName(ledger->leader); + char * acc_name = gnc_ui_get_account_full_name(ledger->leader, ":"); + switch (ledger->type) { case GENERAL_LEDGER: @@ -637,6 +972,8 @@ regWindowLedger(xaccLedgerDisplay *ledger) asprintf(&windowname, "%s Register", acc_name); break; } + + g_free(acc_name); } else asprintf(&windowname, "%s", "General Ledger"); @@ -653,82 +990,109 @@ regWindowLedger(xaccLedgerDisplay *ledger) GTK_SIGNAL_FUNC (gnc_register_destroy_cb), (gpointer) regData); - gtk_box_pack_start(GTK_BOX(register_vbox), - gnc_register_create_menu_bar(regData), FALSE, FALSE, 0); + /* The menu bar */ + { + GtkWidget *dock_item; + GtkWidget *menubar; + + dock_item = gnome_dock_item_new("menu", GNOME_DOCK_ITEM_BEH_EXCLUSIVE); + + menubar = gnc_register_create_menu_bar(regData); + gtk_container_set_border_width(GTK_CONTAINER(menubar), 2); + gtk_container_add(GTK_CONTAINER(dock_item), menubar); + + gnome_dock_add_item (GNOME_DOCK(register_dock), GNOME_DOCK_ITEM(dock_item), + GNOME_DOCK_TOP, 0, 0, 0, TRUE); + } /* The CreateTable will do the actual gui init, returning a widget */ { GtkWidget *register_widget; + GtkWidget *popup; table_frame = gtk_frame_new(NULL); - gtk_box_pack_start(GTK_BOX(register_vbox), table_frame, TRUE, TRUE, 0); + gnome_dock_set_client_area(GNOME_DOCK(register_dock), table_frame); register_widget = gnucash_register_new(ledger->ledger->table); xaccCreateTable(register_widget, ledger->ledger); gtk_container_add(GTK_CONTAINER(table_frame), register_widget); + + regData->reg = GNUCASH_REGISTER(register_widget); + + gtk_signal_connect(GTK_OBJECT(register_widget), "activate_cursor", + GTK_SIGNAL_FUNC(gnc_register_record_cb), regData); + + popup = gnc_register_create_popup_menu(regData); + gnucash_register_attach_popup(GNUCASH_REGISTER(register_widget), + popup, regData); } - /* The toolbar on the bottom */ - gtk_box_pack_end(GTK_BOX(register_vbox), - gnc_register_create_tool_bar(regData), FALSE, FALSE, 0); + /* The tool bar */ + { + GtkWidget *dock_item; + GtkWidget *toolbar; + + dock_item = gnome_dock_item_new("menu", GNOME_DOCK_ITEM_BEH_EXCLUSIVE); + + toolbar = gnc_register_create_tool_bar(regData); + gtk_container_set_border_width(GTK_CONTAINER(toolbar), 2); + gtk_container_add(GTK_CONTAINER(dock_item), toolbar); + + gnome_dock_add_item (GNOME_DOCK(register_dock), GNOME_DOCK_ITEM(dock_item), + GNOME_DOCK_BOTTOM, 1, 0, 0, TRUE); + } /* be sure to initialize the gui elements associated with the cursor */ - xaccCreateCursor(ledger->ledger->table, ledger->ledger->single_cursor); - xaccCreateCursor(ledger->ledger->table, ledger->ledger->double_cursor); - xaccCreateCursor(ledger->ledger->table, ledger->ledger->trans_cursor); - xaccCreateCursor(ledger->ledger->table, ledger->ledger->split_cursor); - - /* complete GUI initialization */ - { - AccountGroup *group; - Account *base_account; - - group = xaccGetAccountRoot(ledger->leader); - base_account = ledger->leader; - - assert((group != NULL) && (base_account != NULL)); - - xaccLoadXferCell(ledger->ledger->xfrmCell, group, base_account); - xaccLoadXferCell(ledger->ledger->mxfrmCell, group, base_account); - } - - { - Table *table = ledger->ledger->table; - CellBlock *header; - short *widths; - int list_width = 0; - int i; - - /* The 0'th row of the handlers is defined as the header */ - header = table->handlers[0][0]; - widths = header->widths; - - for(i = 0; i < table->num_phys_cols; i++) - { - /* Widths are in units of characters, not pixels, so we have - this hack. It should be fixed later... */ - list_width += widths[i] * 5; - } - - gtk_widget_set_usize(table_frame, list_width + 219, 500); - } + xaccConfigSplitRegister(ledger->ledger, + gnc_register_get_default_type(ledger->ledger)); + /* Allow grow, allow shrink, no auto-shrink */ + gtk_window_set_policy(GTK_WINDOW(register_window), TRUE, TRUE, FALSE); gtk_widget_show_all(register_window); ledger->dirty = 1; xaccLedgerDisplayRefresh(ledger); - gnc_unset_busy_cursor(gnc_get_ui_data()); + + gnc_register_jump_to_blank(regData); return regData; } +static void +gnc_reg_refresh_toolbar(RegWindow *regData) +{ + GtkToolbarStyle tbstyle = GTK_TOOLBAR_BOTH; + char *style_string; + + if ((regData == NULL) || (regData->toolbar == NULL)) + return; + + style_string = gnc_lookup_multichoice_option("Register", + "Toolbar Buttons", + "icons_and_text"); + + if (safe_strcmp(style_string, "icons_and_text") == 0) + tbstyle = GTK_TOOLBAR_BOTH; + else if (safe_strcmp(style_string, "icons_only") == 0) + tbstyle = GTK_TOOLBAR_ICONS; + else if (safe_strcmp(style_string, "text_only") == 0) + tbstyle = GTK_TOOLBAR_TEXT; + + gtk_toolbar_set_style(GTK_TOOLBAR(regData->toolbar), tbstyle); +} + + static void regRefresh(xaccLedgerDisplay *ledger) { RegWindow *regData = (RegWindow *) (ledger->gui_hook); + gnc_reg_refresh_toolbar(regData); + + xaccSRLoadXferCells(ledger->ledger, ledger->leader); + if (regData->window != NULL) { gtk_label_set_text(GTK_LABEL(regData->balance_label), @@ -751,6 +1115,8 @@ regDestroy(xaccLedgerDisplay *ledger) if (regData) { + gnc_register_check_close(regData); + /* It will be closed elsewhere */ regData->close_ledger = GNC_F; @@ -881,6 +1247,34 @@ startRecnCB(GtkWidget * w, gpointer data) } +static gboolean +gnc_register_include_date(RegWindow *regData, time_t date) +{ + time_t start, end; + gboolean changed = FALSE; + + start = gnome_date_edit_get_date(GNOME_DATE_EDIT(regData->start_date)); + end = gnome_date_edit_get_date(GNOME_DATE_EDIT(regData->end_date)); + + if (date < start) + { + gnome_date_edit_set_time(GNOME_DATE_EDIT(regData->start_date), date); + changed = TRUE; + } + + if (date > end) + { + gnome_date_edit_set_time(GNOME_DATE_EDIT(regData->end_date), date); + changed = TRUE; + } + + if (changed) + gnc_register_set_date_range(regData); + + return changed; +} + + /********************************************************************\ * recordCB * * * @@ -892,49 +1286,140 @@ static void recordCB(GtkWidget *w, gpointer data) { RegWindow *regData = (RegWindow *) data; + gncBoolean really_saved; Transaction *trans; - - xaccSRSaveRegEntry(regData->ledger->ledger); + Split *split; + + split = xaccSRGetCurrentSplit(regData->ledger->ledger); + trans = xaccSplitGetParent(split); + + really_saved = xaccSRSaveRegEntry(regData->ledger->ledger, NULL); + if (!really_saved) + return; - trans = (Transaction *) (regData->ledger->ledger->user_huck); if (trans != NULL) - { - time_t start, end, new; - gboolean changed = FALSE; - - xaccTransCommitEdit(trans); - regData->ledger->ledger->user_huck = NULL; - - start = gnome_date_edit_get_date(GNOME_DATE_EDIT(regData->start_date)); - end = gnome_date_edit_get_date(GNOME_DATE_EDIT(regData->end_date)); - new = xaccTransGetDate(trans); - - if (new < start) - { - start = new; - gnome_date_edit_set_time(GNOME_DATE_EDIT(regData->start_date), start); - changed = TRUE; - } - - if (new > end) - { - end = new; - gnome_date_edit_set_time(GNOME_DATE_EDIT(regData->end_date), end); - changed = TRUE; - } - - if (changed) - { - xaccQuerySetDateRange(regData->ledger->query, start, end); - regData->ledger->dirty = 1; - } - } + gnc_register_include_date(regData, xaccTransGetDate(trans)); xaccSRRedrawRegEntry(regData->ledger->ledger); gnc_refresh_main_window (); } +typedef enum +{ + DELETE_TRANS, + DELETE_SPLITS, + DELETE_CANCEL +} DeleteType; + + +static void +gnc_transaction_delete_toggle_cb(GtkToggleButton *button, gpointer data) +{ + GtkWidget *text = gtk_object_get_user_data(GTK_OBJECT(button)); + gchar *s = data; + gint pos = 0; + + gtk_editable_delete_text(GTK_EDITABLE(text), 0, -1); + gtk_editable_insert_text(GTK_EDITABLE(text), s, strlen(s), &pos); +} + + +/********************************************************************\ + * gnc_transaction_delete_query * + * creates and displays a dialog which asks the user wheter they * + * want to delete a whole transaction, or just a split. * + * It returns a DeleteType code indicating the user's choice. * + * * + * Args: parent - the parent window the dialog should use * + * Returns: DeleteType choice indicator * + \*******************************************************************/ +DeleteType +gnc_transaction_delete_query(GtkWindow *parent) +{ + GtkWidget *dialog; + GtkWidget *dvbox; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *trans_button; + GtkWidget *splits_button; + GtkWidget *text; + GSList *group; + gint pos = 0; + gint result; + + gchar *whole = "Delete the whole transaction"; + gchar *splits = "Delete all the splits"; + gchar *usual = + "This selection will delete the whole transaction. " + "This is what you usually want."; + + gchar *warn = + "Warning: Just deleting all the splits will make your " + "account unbalanced. You probably shouldn't do this unless " + "you're going to immediately add another split to bring things " + "back into balance."; + + dialog = gnome_dialog_new("Delete Transaction", + GNOME_STOCK_BUTTON_OK, + GNOME_STOCK_BUTTON_CANCEL, + NULL); + + gnome_dialog_set_default(GNOME_DIALOG(dialog), 0); + gnome_dialog_close_hides(GNOME_DIALOG(dialog), TRUE); + gnome_dialog_set_parent(GNOME_DIALOG(dialog), parent); + + dvbox = GNOME_DIALOG(dialog)->vbox; + + frame = gtk_frame_new(NULL); + gtk_container_border_width(GTK_CONTAINER(frame), 5); + + vbox = gtk_vbox_new(TRUE, 3); + gtk_container_border_width(GTK_CONTAINER(vbox), 5); + gtk_container_add(GTK_CONTAINER(frame), vbox); + + text = gtk_text_new(NULL, NULL); + + trans_button = gtk_radio_button_new_with_label(NULL, whole); + gtk_object_set_user_data(GTK_OBJECT(trans_button), text); + gtk_box_pack_start(GTK_BOX(vbox), trans_button, TRUE, TRUE, 0); + + gtk_signal_connect(GTK_OBJECT(trans_button), "toggled", + GTK_SIGNAL_FUNC(gnc_transaction_delete_toggle_cb), usual); + + group = gtk_radio_button_group(GTK_RADIO_BUTTON(trans_button)); + splits_button = gtk_radio_button_new_with_label(group, splits); + gtk_object_set_user_data(GTK_OBJECT(splits_button), text); + gtk_box_pack_start(GTK_BOX(vbox), splits_button, TRUE, TRUE, 0); + + gtk_signal_connect(GTK_OBJECT(splits_button), "toggled", + GTK_SIGNAL_FUNC(gnc_transaction_delete_toggle_cb), warn); + + gtk_box_pack_start(GTK_BOX(dvbox), frame, TRUE, TRUE, 0); + + gtk_editable_insert_text(GTK_EDITABLE(text), usual, strlen(warn), &pos); + gtk_text_set_line_wrap(GTK_TEXT(text), TRUE); + gtk_text_set_word_wrap(GTK_TEXT(text), TRUE); + gtk_text_set_editable(GTK_TEXT(text), FALSE); + gtk_box_pack_start(GTK_BOX(dvbox), text, FALSE, FALSE, 0); + + gtk_widget_show_all(dvbox); + + result = gnome_dialog_run_and_close(GNOME_DIALOG(dialog)); + + if (result != 0) + return DELETE_CANCEL; + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(trans_button))) + return DELETE_TRANS; + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(splits_button))) + return DELETE_SPLITS; + + return DELETE_CANCEL; +} + + /********************************************************************\ * deleteCB * * * @@ -946,24 +1431,46 @@ static void deleteCB(GtkWidget *widget, gpointer data) { RegWindow *regData = (RegWindow *) data; - Split *split, *s; + CursorType cursor_type; Transaction *trans; - int i, num_splits; - Account *account, **affected_accounts; - + char *buf = NULL; + Split *split; + gint result; + int style; + /* get the current split based on cursor position */ split = xaccSRGetCurrentSplit(regData->ledger->ledger); if (split == NULL) + { + xaccSRCancelCursorSplitChanges(regData->ledger->ledger); + return; + } + + trans = xaccSplitGetParent(split); + style = regData->ledger->ledger->type & REG_STYLE_MASK; + cursor_type = xaccSplitRegisterGetCursorType(regData->ledger->ledger); + + /* Deleting the blank split just cancels */ + { + Split *blank_split = xaccSRGetBlankSplit(regData->ledger->ledger); + Transaction *blank_trans = xaccSplitGetParent(blank_split); + + if (trans == blank_trans) + { + xaccSRCancelCursorTransChanges(regData->ledger->ledger); + return; + } + } + + if (cursor_type == CURSOR_NONE) return; - /* ask for user confirmation before performing permanent damage */ + /* On a split cursor, just delete the one split. */ + if (cursor_type == CURSOR_SPLIT) { - char *buf = NULL; - gint result; - - trans = xaccSplitGetParent (split); + /* ask for user confirmation before performing permanent damage */ asprintf(&buf, TRANS_DEL_MSG, xaccSplitGetMemo(split), - xaccTransGetDescription(trans)); + xaccTransGetDescription(trans)); assert(buf != NULL); @@ -974,47 +1481,53 @@ deleteCB(GtkWidget *widget, gpointer data) if (!result) return; - } - /* If we just deleted the blank split, clean up. The user is - * allowed to delete the blank split as a method for discarding any - * edits they may have made to it. */ - if (split == regData->ledger->ledger->user_hook) - { - account = xaccSplitGetAccount(split); - xaccAccountDisplayRefresh(account); + xaccSRDeleteCurrentSplit(regData->ledger->ledger); return; } - /* make a copy of all of the accounts that will be - * affected by this deletion, so that we can update - * their register windows after the deletion. - */ - num_splits = xaccTransCountSplits(trans); - affected_accounts = (Account **) malloc((num_splits + 1) * - sizeof(Account *)); - assert(affected_accounts != NULL); + assert(cursor_type == CURSOR_TRANS); - for (i=0; i < num_splits; i++) + /* On a transaction cursor with 2 or fewer splits in single or double + * mode, we just delete the whole transaction, kerblooie */ + if ((xaccTransCountSplits(trans) <= 2) && + ((style == REG_SINGLE_LINE) || (style == REG_DOUBLE_LINE))) { - s = xaccTransGetSplit(trans, i); - affected_accounts[i] = xaccSplitGetAccount(s); + buf = "Are you sure you want to delete the current transaction?"; + + result = gnc_verify_dialog_parented(GTK_WINDOW(regData->window), + buf, GNC_F); + + if (!result) + return; + + xaccSRDeleteCurrentTrans(regData->ledger->ledger); + return; } - affected_accounts[num_splits] = NULL; - - account = xaccSplitGetAccount(split); - xaccTransBeginEdit(trans, 1); - xaccAccountBeginEdit(account, 1); - xaccSplitDestroy(split); - xaccAccountCommitEdit(account); - xaccTransCommitEdit(trans); + /* At this point we are on a transaction cursor with more than 2 splits. + * We give the user two choices: delete the whole transaction or delete + * all the splits except the transaction split. */ + { + DeleteType del_type; - gnc_account_list_ui_refresh(affected_accounts); + del_type = gnc_transaction_delete_query(GTK_WINDOW(regData->window)); - free(affected_accounts); + if (del_type == DELETE_CANCEL) + return; - gnc_refresh_main_window (); + if (del_type == DELETE_TRANS) + { + xaccSRDeleteCurrentTrans(regData->ledger->ledger); + return; + } + + if (del_type == DELETE_SPLITS) + { + xaccSREmptyCurrentTrans(regData->ledger->ledger); + return; + } + } } @@ -1029,16 +1542,35 @@ static void cancelCB(GtkWidget *w, gpointer data) { RegWindow *regData = (RegWindow *) data; - Split * split; - - /* We're just cancelling the current split here, not the transaction */ - /* When cancelling edits, reload the cursor from the transaction */ - split = xaccSRGetCurrentSplit(regData->ledger->ledger); - xaccSRLoadRegEntry(regData->ledger->ledger, split); - xaccRefreshTableGUI(regData->ledger->ledger->table); + + xaccSRCancelCursorTransChanges(regData->ledger->ledger); } +/********************************************************************\ + * gnc_register_check_close * + * * + * Args: regData - the data struct for this register * + * Return: none * +\********************************************************************/ +static void +gnc_register_check_close(RegWindow *regData) +{ + unsigned int changed; + + changed = xaccSplitRegisterGetChangeFlag(regData->ledger->ledger); + if (changed) + { + if (gnc_verify_dialog_parented + (GTK_WINDOW(regData->window), + "The current transaction has been changed.\n" + "Would you like to record it?", GNC_T)) + recordCB(regData->window, regData); + else + xaccSRCancelCursorSplitChanges(regData->ledger->ledger); + } +} + /********************************************************************\ * closeCB * * * @@ -1051,6 +1583,8 @@ closeCB(GtkWidget *widget, gpointer data) { RegWindow *regData = (RegWindow *) data; + gnc_register_check_close(regData); + gtk_widget_destroy(regData->window); } @@ -1065,7 +1599,7 @@ closeCB(GtkWidget *widget, gpointer data) static void helpCB(GtkWidget *widget, gpointer data) { - helpWindow(GTK_WIDGET(gnc_get_ui_data()), HELP_STR, HH_REGWIN); + helpWindow(NULL, HELP_STR, HH_REGWIN); } /************************** END OF FILE **************************/ diff --git a/src/gnome/window-register.h b/src/gnome/window-register.h new file mode 100644 index 0000000000..95a921a31e --- /dev/null +++ b/src/gnome/window-register.h @@ -0,0 +1,29 @@ +/*******************************************************************\ + * window-register.h -- public GnuCash register functions * + * Copyright (C) 1998,1999 Linas Vepstas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * +\********************************************************************/ + +#ifndef __WINDOW_REGISTER_H__ +#define __WINDOW_REGISTER_H__ + +#include "top-level.h" + +void gnc_register_raise(RegWindow *regData); +void gnc_register_jump_to_blank(RegWindow *regData); +void gnc_register_jump_to_split(RegWindow *regData, Split *split); + +#endif diff --git a/src/gnome/window-report.c b/src/gnome/window-report.c new file mode 100644 index 0000000000..4e5b950352 --- /dev/null +++ b/src/gnome/window-report.c @@ -0,0 +1,341 @@ +/********************************************************************\ + * window-report.c -- a report window for hypertext help. * + * Copyright (C) 1997 Robin D. Clark * + * Copyright (C) 1998 Linas Vepstas * + * Copyright (C) 1999 Jeremy Collins ( gtk-xmhtml port ) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + * * + * Author: Rob Clark * + * Internet: rclark@cs.hmc.edu * + * Address: 609 8th Street * + * Huntington Beach, CA 92648-4632 * +\********************************************************************/ + +#include + +#include "config.h" + +#include "window-report.h" +#include "window-html.h" +#include "option-util.h" +#include "guile-util.h" +#include "dialog-options.h" +#include "util.h" + +static short module = MOD_HTML; + +static HTMLWindow *reportwindow = NULL; + + +typedef struct _ReportData ReportData; +struct _ReportData +{ + gchar *text; + + GNCOptionDB *odb; + + GtkWidget *option_dialog; + + SCM rendering_thunk; + SCM guile_options; +}; + + +static ReportData * +report_data_new() +{ + ReportData *rd; + + rd = g_new0(ReportData, 1); + + rd->guile_options = SCM_UNDEFINED; + rd->rendering_thunk = SCM_UNDEFINED; + + return rd; +} + +static void +report_data_destroy(HTMLHistoryData history_data) +{ + ReportData *rd = history_data; + + g_free(rd->text); + rd->text = NULL; + + gnc_option_db_destroy(rd->odb); + rd->odb = NULL; + + if (rd->option_dialog != NULL) + gtk_widget_destroy(rd->option_dialog); + rd->option_dialog = NULL; + + if (rd->guile_options != SCM_UNDEFINED) + gnc_unregister_c_side_scheme_ptr(rd->guile_options); + rd->guile_options = SCM_UNDEFINED; + + if (rd->rendering_thunk != SCM_UNDEFINED) + gnc_unregister_c_side_scheme_ptr(rd->rendering_thunk); + rd->rendering_thunk = SCM_UNDEFINED; + + g_free(rd); +} + +static void +report_data_set_text(ReportData *rd, const gchar *text) +{ + g_free(rd->text); + rd->text = g_strdup(text); +} + +static void +report_data_set_rendering_thunk(ReportData *rd, const SCM rendering_thunk) +{ + if (rd->rendering_thunk != SCM_UNDEFINED) + gnc_unregister_c_side_scheme_ptr(rd->rendering_thunk); + + rd->rendering_thunk = rendering_thunk; +} + +static void +gnc_options_dialog_apply_cb(GnomePropertyBox *propertybox, + gint arg1, gpointer user_data) +{ + ReportData *rd = user_data; + + if (arg1 == -1) + gnc_option_db_commit(rd->odb); +} + +static void +gnc_options_dialog_help_cb(GnomePropertyBox *propertybox, + gint arg1, gpointer user_data) +{ + gnome_ok_dialog("Set the report options you want using this dialog."); +} + + +static void +report_data_set_guile_options(ReportData *rd, const SCM guile_options) +{ + GnomePropertyBox *prop_box; + + if (rd->guile_options != SCM_UNDEFINED) + { + gnc_unregister_c_side_scheme_ptr(rd->guile_options); + gnc_option_db_destroy(rd->odb); + } + + if (rd->option_dialog != NULL) + gtk_widget_destroy(rd->option_dialog); + + if (gh_scm2bool(gh_not(guile_options))) + { + rd->guile_options = SCM_UNDEFINED; + rd->option_dialog = NULL; + return; + } + + rd->guile_options = guile_options; + gnc_register_c_side_scheme_ptr(guile_options); + + rd->odb = gnc_option_db_new(); + + gnc_option_db_init(rd->odb, guile_options); + + rd->option_dialog = gnome_property_box_new(); + gnome_dialog_close_hides(GNOME_DIALOG(rd->option_dialog), TRUE); + + prop_box = GNOME_PROPERTY_BOX(rd->option_dialog); + gnc_build_options_dialog_contents(prop_box, rd->odb); + + gnc_option_db_clean(rd->odb); + + gtk_signal_connect(GTK_OBJECT(rd->option_dialog), "apply", + GTK_SIGNAL_FUNC(gnc_options_dialog_apply_cb), rd); + + gtk_signal_connect(GTK_OBJECT(rd->option_dialog), "help", + GTK_SIGNAL_FUNC(gnc_options_dialog_help_cb), rd); +} + + +static HTMLHistoryData +reportAnchorCB(XmHTMLAnchorCallbackStruct *acbs, + HTMLHistoryData history_data) +{ + switch(acbs->url_type) + { + /* a local file with a possible jump to label */ + case ANCHOR_FILE_LOCAL: + PERR(" this report window doesn't support ftp: %s\n", acbs->href); + break; + /* other types are unsupported, but it would be fun if they were ... */ + case ANCHOR_FTP: + PERR(" this report window doesn't support ftp: %s\n", acbs->href); + break; + case ANCHOR_HTTP: + PERR (" this report window doesn't support http: %s\n", acbs->href); + break; + case ANCHOR_MAILTO: + PERR(" this report window doesn't support email: %s\n", acbs->href); + break; + case ANCHOR_UNKNOWN: + default: + PERR(" don't know this type of url: %s\n", acbs->href); + break; + } + + return NULL; +} + +static void +reportJumpCB(HTMLHistoryData history_data, char **set_text, char **set_label) +{ + ReportData *rd = (ReportData *) history_data; + char *text; + SCM text_scm; + + *set_text = NULL; + *set_label = NULL; + + if (rd->text != NULL) + { + *set_text = rd->text; + return; + } + + if (!gh_procedure_p(rd->rendering_thunk)) + return; + + text_scm = gh_call0(rd->rendering_thunk); + + if (!gh_string_p(text_scm)) + return; + + text = gh_scm2newstr(text_scm, NULL); + if (text == NULL) + return; + + report_data_set_text(rd, text); + free(text); + + *set_text = rd->text; +} + + +static void +gnc_report_options_changed_cb(gpointer data) +{ + HTMLWindow *hw = data; + HTMLHistoryData hd; + ReportData *rd; + + hd = gnc_html_window_history_data(hw); + if (hd == NULL) + return; + + rd = (ReportData *) hd; + report_data_set_text(rd, NULL); + + gnc_html_load(hw); +} + + +static void +gnc_report_properties_cb(GtkWidget *widget, gpointer data) +{ + ReportData *rd = data; + + if (rd->option_dialog == NULL) + return; + + gtk_widget_show_all(rd->option_dialog); + gdk_window_raise(GTK_WIDGET(rd->option_dialog)->window); +} + + +/********************************************************************\ + * reportWindow * + * opens up a report window, and displays html * + * * + * Args: title - the title of the window * + * text - the html text to display * + * Return: none * +\********************************************************************/ +void +reportWindow(const char *title, SCM rendering_thunk, SCM guile_options) +{ + ReportData *rd; + + if (reportwindow == NULL) + reportwindow = gnc_html_window_new(report_data_destroy, reportAnchorCB, + reportJumpCB); + + rd = report_data_new(); + report_data_set_rendering_thunk(rd, rendering_thunk); + report_data_set_guile_options(rd, guile_options); + + if (rd->odb != NULL) + gnc_option_db_register_change_callback(rd->odb, + gnc_report_options_changed_cb, + reportwindow); + + if (rd->option_dialog != NULL) + { + gchar *prop_title; + + prop_title = g_strconcat(title, " (Parameters)", NULL); + gtk_window_set_title(GTK_WINDOW(rd->option_dialog), prop_title); + g_free(prop_title); + } + + { + GnomeUIInfo user_buttons[] = + { + { GNOME_APP_UI_ITEM, + "Properties", + "Set the properties for this report.", + gnc_report_properties_cb, rd, + NULL, + GNOME_APP_PIXMAP_STOCK, + GNOME_STOCK_PIXMAP_PROPERTIES, + 0, 0, NULL + } + }; + + gint num_buttons = sizeof(user_buttons) / sizeof(GnomeUIInfo); + + htmlWindow(NULL, &reportwindow, title, rd, user_buttons, num_buttons); + } +} + + +/********************************************************************\ + * gnc_ui_destroy_report_windows * + * destroys any open report windows * + * * + * Args: none * + * Return: none * +\********************************************************************/ +void +gnc_ui_destroy_report_windows() +{ + gnc_html_window_destroy(reportwindow); + reportwindow = NULL; + + DEBUG("report windows destroyed.\n"); +} + +/* ----------------------- END OF FILE --------------------- */ diff --git a/src/gnome/window-report.h b/src/gnome/window-report.h new file mode 100644 index 0000000000..0c998d8fd5 --- /dev/null +++ b/src/gnome/window-report.h @@ -0,0 +1,40 @@ +/********************************************************************\ + * window-report.h -- a report window for hypertext help. * + * Copyright (C) 1997 Robin D. Clark * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + * * + * Author: Rob Clark * + * Internet: rclark@cs.hmc.edu * + * Address: 609 8th Street * + * Huntington Beach, CA 92648-4632 * +\********************************************************************/ + +#ifndef __WINDOW_REPORT_H__ +#define __WINDOW_REPORT_H__ + +#include +#include + +#include "config.h" + + +/** PROTOTYPES ******************************************************/ + +void reportWindow(const char *title, SCM rendering_thunk, SCM guile_options); + +void gnc_ui_destroy_report_windows(); + +#endif diff --git a/src/register/Makefile.in b/src/register/Makefile.in index a93dfc4272..a6e54147c0 100644 --- a/src/register/Makefile.in +++ b/src/register/Makefile.in @@ -45,6 +45,7 @@ COMMON_SRCS := basiccell.c cellblock.c \ MOTIF_SRCS := table-motif.c combocell-motif.c GNOME_SRCS := table-gnome.c QT_SRCS := table-qt.cpp combocell-qt.cpp +CLEAN_SUBDIRS := gnome ###################################################################### all: diff --git a/src/register/basiccell.h b/src/register/basiccell.h index b96218d3ce..07f7ecaf2e 100644 --- a/src/register/basiccell.h +++ b/src/register/basiccell.h @@ -195,7 +195,8 @@ struct _BasicCell { const char * (*modify_verify) (BasicCell *, const char *old_value, const char *add_str, - const char *new_value); + const char *new_value, + int *cursor_position); const char * (*leave_cell) (BasicCell *, const char * current); diff --git a/src/register/cellblock.c b/src/register/cellblock.c index 65bffc7800..48bfc3bc28 100644 --- a/src/register/cellblock.c +++ b/src/register/cellblock.c @@ -47,6 +47,8 @@ CellBlock * xaccMallocCellBlock (int numrows, int numcols) arr->cell_types = NULL; arr->right_traverse_r = NULL; arr->right_traverse_c = NULL; + arr->left_traverse_r = NULL; + arr->left_traverse_c = NULL; arr->widths = NULL; arr->alignments = NULL; xaccInitCellBlock (arr, numrows, numcols); @@ -80,7 +82,7 @@ FreeCellBlockMem (CellBlock *arr) } } - /* free traversal chain */ + /* free right traversal chain */ if (arr->right_traverse_r) { for (i=0; iright_traverse_r[i]) free (arr->right_traverse_r[i]); @@ -92,6 +94,18 @@ FreeCellBlockMem (CellBlock *arr) } } + /* free left traversal chain */ + if (arr->left_traverse_r) { + for (i=0; ileft_traverse_r[i]) free (arr->left_traverse_r[i]); + } + } + if (arr->left_traverse_c) { + for (i=0; ileft_traverse_c[i]) free (arr->left_traverse_c[i]); + } + } + /* free widths, alignments */ if (arr->widths) free (arr->widths); if (arr->alignments) free (arr->alignments); @@ -123,7 +137,7 @@ xaccInitCellBlock (CellBlock *arr, int numrows, int numcols) } } - /* malloc new traversal arrays */ + /* malloc new right traversal arrays */ arr->right_traverse_r = (short **) malloc (numrows * sizeof (short *)); arr->right_traverse_c = (short **) malloc (numrows * sizeof (short *)); for (i=0; ilast_reenter_traverse_row = numrows-1; arr->last_reenter_traverse_col = numcols-1; + /* malloc new left traversal arrays */ + arr->left_traverse_r = (short **) malloc (numrows * sizeof (short *)); + arr->left_traverse_c = (short **) malloc (numrows * sizeof (short *)); + for (i=0; ileft_traverse_r)[i] = (short *) malloc (numcols * sizeof (short)); + (arr->left_traverse_c)[i] = (short *) malloc (numcols * sizeof (short)); + for (j=0; jleft_traverse_r)[i][j] = i; + (arr->left_traverse_c)[i][j] = j-1; + } + /* at start of row, wrap to previous row */ + (arr->left_traverse_r)[i][numcols-1] = i-1; + (arr->left_traverse_c)[i][numcols-1] = numcols-1; + } + /* at start of block, wrap back to end */ + (arr->right_traverse_r)[0][0] = numrows-1; + (arr->right_traverse_c)[0][0] = numcols-1; + + /* first is last ... */ + arr->last_left_reenter_traverse_row = 0; + arr->last_left_reenter_traverse_col = 0; + arr->widths = (short *) malloc (numcols * sizeof(short)); arr->alignments = (Alignments *) malloc (numcols * sizeof(Alignments)); @@ -202,4 +239,37 @@ xaccNextRight (CellBlock *arr, int row, int col, } + +void +xaccNextLeft (CellBlock *arr, int row, int col, + int next_row, int next_col) +{ + if (!arr) return; + + /* avoid embarrasement if cell incorrectly specified */ + if ((0 > row) || (0 > col)) return; + if ((row >= arr->numRows) || (col >= arr->numCols)) return; + + /* -1 is a valid value for next ... it signifies that traversal + * should go to next tab group, so do not check for neg values. + * if ((0 > next_row) || (0 > next_col)) return; + */ + + /* if the "next" location to hop to is larger than the cursor, that + * just means that we should hop to the next cursor. Thus, large + * values for next *are* valid. + * if ((next_row >= arr->numRows) || (next_col >= arr->numCols)) return; + */ + + (arr->left_traverse_r)[row][col] = next_row; + (arr->left_traverse_c)[row][col] = next_col; + + /* if traversing out (neg values) record this as the last ... */ + if ((0 > next_row) || (0 > next_col)) { + arr->last_left_reenter_traverse_row = row; + arr->last_left_reenter_traverse_col = col; + } + +} + /* --------------- end of file ----------------- */ diff --git a/src/register/cellblock.h b/src/register/cellblock.h index bf1fe22284..c2d6c635cf 100644 --- a/src/register/cellblock.h +++ b/src/register/cellblock.h @@ -99,6 +99,12 @@ struct _CellBlock { short **right_traverse_r; short **right_traverse_c; + short **left_traverse_r; + short **left_traverse_c; + short right_exit_r; + short right_exit_c; + short left_exit_r; + short left_exit_c; /* the above arrays have dimension of numRows*numCols. * the are automatically created and managed by the routines below. * The control the tab-traversal order through this cell block. @@ -106,8 +112,7 @@ struct _CellBlock { * on the keyboard will take input-focus to cell (inext,jnext), where * inext = right_traverse_r[i][j] and jnext = right_traverse_c[i][j]. * - * Note that left-traversing arrays could be defined (for when - * shift-tab is hit), but we haven't (yet) done so. + * (exit_r, exit_c) is the last cell of this tab group. */ /* the last-reneter row and column should contain the very last @@ -118,6 +123,9 @@ struct _CellBlock { short last_reenter_traverse_row; short last_reenter_traverse_col; + short last_left_reenter_traverse_row; + short last_left_reenter_traverse_col; + void * user_data; /* above is a pointer to anything the programmer-user of this struct * wants it to be. Handy for stuff. diff --git a/src/register/combocell-motif.c b/src/register/combocell-motif.c index 0d8d2903c2..c8ff688de6 100644 --- a/src/register/combocell-motif.c +++ b/src/register/combocell-motif.c @@ -148,6 +148,40 @@ void xaccDestroyComboCell (ComboCell *cell) /* =============================================== */ +void +xaccClearComboCellMenu (ComboCell * cell) +{ + int n; + char ** arr; + + if (!cell) return; + + arr = cell->menuitems; + n = 0; + while (arr[n]) n++; + if (n == 0) + return; + + n = 0; + while (arr[n]) { + free (arr[n]); + n++; + } + free (arr); + + cell->menuitems = (char **) malloc (sizeof (char *)); + cell->menuitems[0] = NULL; + + if (!cell->cell.realize) { + PopBox *box; + + box = (PopBox *) cell->cell.gui_private; + XmComboBoxDeleteAllItems (box->combobox); + } +} + +/* =============================================== */ + void xaccAddComboCellMenuItem (ComboCell *cell, char * menustr) { diff --git a/src/register/combocell.h b/src/register/combocell.h index 6a534f7be5..a48d342204 100644 --- a/src/register/combocell.h +++ b/src/register/combocell.h @@ -52,6 +52,7 @@ void xaccDestroyComboCell (ComboCell *); void xaccSetComboCellValue (ComboCell *, const char *); +void xaccClearComboCellMenu (ComboCell *); void xaccAddComboCellMenuItem (ComboCell *, char * menustr); #endif /* __XACC_COMBO_CELL_H__ */ diff --git a/src/register/datecell.c b/src/register/datecell.c index 19b326c5d5..c7f519c860 100644 --- a/src/register/datecell.c +++ b/src/register/datecell.c @@ -141,7 +141,8 @@ static const char * DateMV (BasicCell *_cell, const char *oldval, const char *change, - const char *newval) + const char *newval, + int *cursor_position) { DateCell *cell = (DateCell *) _cell; struct tm *date; diff --git a/src/register/gnome/combocell-gnome.c b/src/register/gnome/combocell-gnome.c index 160e9412e3..b58dbe7b1f 100644 --- a/src/register/gnome/combocell-gnome.c +++ b/src/register/gnome/combocell-gnome.c @@ -111,6 +111,7 @@ select_item_cb (GNCItemList *item_list, char *item_string, gpointer data) PopBox *box = (PopBox *) cell->cell.gui_private; gnucash_sheet_modify_current_cell(box->sheet, item_string); + item_edit_hide_list (box->item_edit); } static void @@ -119,7 +120,15 @@ key_press_item_cb (GNCItemList *item_list, GdkEventKey *event, gpointer data) ComboCell *cell = (ComboCell *) data; PopBox *box = (PopBox *) cell->cell.gui_private; - gtk_widget_event(GTK_WIDGET(box->sheet), (GdkEvent *) event); + switch(event->keyval) { + case GDK_Escape: + item_edit_hide_list (box->item_edit); + break; + default: + gtk_widget_event(GTK_WIDGET(box->sheet), + (GdkEvent *) event); + break; + } } static void @@ -130,6 +139,9 @@ disconnect_list_signals (ComboCell *cell) if (!box->list_signals_connected) return; + if (GTK_OBJECT_DESTROYED(GTK_OBJECT(box->item_list))) + return; + gtk_signal_disconnect(GTK_OBJECT(box->item_list), box->select_item_signal); @@ -147,6 +159,9 @@ connect_list_signals (ComboCell *cell) if (box->list_signals_connected) return; + if (GTK_OBJECT_DESTROYED(GTK_OBJECT(box->item_list))) + return; + box->select_item_signal = gtk_signal_connect(GTK_OBJECT(box->item_list), "select_item", GTK_SIGNAL_FUNC(select_item_cb), @@ -213,6 +228,32 @@ void xaccDestroyComboCell (ComboCell *cell) /* =============================================== */ +void +xaccClearComboCellMenu (ComboCell * cell) +{ + PopBox *box; + + if (cell == NULL) + return; + + box = (PopBox *) cell->cell.gui_private; + if (box == NULL) + return; + if (box->menustrings == NULL) + return; + + g_list_foreach(box->menustrings, (GFunc) g_free, NULL); + g_list_free(box->menustrings); + box->menustrings = NULL; + + if (box->item_list != NULL) + gnc_item_list_clear(box->item_list); + + box->list_in_sync = TRUE; +} + +/* =============================================== */ + static void gnc_append_string_to_list(gpointer _string, gpointer _item_list) { @@ -266,7 +307,7 @@ xaccSetComboCellValue (ComboCell *cell, const char *str) static const char * ComboMV (BasicCell *_cell, const char *oldval, const char *change, - const char *newval) + const char *newval, int *cursor_position) { xaccSetBasicCellValue (_cell, newval); @@ -307,6 +348,8 @@ moveCombo (BasicCell *bcell, int phys_row, int phys_col) { PopBox *box = (PopBox *) bcell->gui_private; + disconnect_list_signals((ComboCell *) bcell); + gnome_canvas_item_set(GNOME_CANVAS_ITEM(box->item_edit), "is_combo", FALSE, NULL); diff --git a/src/register/gnome/gnucash-color.c b/src/register/gnome/gnucash-color.c index deac72327c..9500d40f49 100644 --- a/src/register/gnome/gnucash-color.c +++ b/src/register/gnome/gnucash-color.c @@ -40,13 +40,12 @@ GdkColor gn_white, gn_black, gn_light_gray, gn_dark_gray, gn_red; static GHashTable *color_hash_table = NULL; - static guint color_hash (gconstpointer v) { - const GdkColor *c = (GdkColor *) v; + const uint32 *c = (uint32 *) v; - return (c->red << 16) | (c->green << 8) | (c->blue); + return *c; } @@ -116,8 +115,9 @@ gnucash_color_argb_to_gdk (uint32 argb) if (!color) { color = g_new0(GdkColor, 1); newkey = g_new0(uint32, 1); - *newkey = key; + *newkey = key; + color->red = (argb & 0xff0000) >> 8; color->green = argb & 0xff00; color->blue = (argb & 0xff) << 8; diff --git a/src/register/gnome/gnucash-cursor.c b/src/register/gnome/gnucash-cursor.c index 9839c0593a..4250f3cf01 100644 --- a/src/register/gnome/gnucash-cursor.c +++ b/src/register/gnome/gnucash-cursor.c @@ -39,17 +39,6 @@ enum { }; -#if 0 -static void -gnucash_cursor_update (GnomeCanvasItem *item, double *affine, - ArtSVP *clip_path, int flags) -{ - if (GNOME_CANVAS_ITEM_CLASS(gnucash_cursor_parent_class)->update) - (*GNOME_CANVAS_ITEM_CLASS(gnucash_cursor_parent_class)->update) - (item, affine, clip_path, flags); -} -#endif - static void gnucash_cursor_get_pixel_coords (GnucashCursor *cursor, gint *x, gint *y, gint *w, gint *h) @@ -58,12 +47,12 @@ gnucash_cursor_get_pixel_coords (GnucashCursor *cursor, gint *x, gint *y, GnucashItemCursor *item_cursor = GNUCASH_ITEM_CURSOR(cursor->cursor[GNUCASH_CURSOR_BLOCK]); - gnome_canvas_get_scroll_offsets (GNOME_CANVAS(cursor->sheet), x, y); + gnome_canvas_get_scroll_offsets (GNOME_CANVAS(cursor->sheet), NULL, y); *y += gnucash_sheet_row_get_distance (sheet, sheet->top_block, item_cursor->row) + sheet->top_block_offset; - *x += gnucash_sheet_col_get_distance (sheet, sheet->left_block, + *x = gnucash_sheet_col_get_distance (sheet, sheet->left_block, item_cursor->col) + sheet->left_block_offset; @@ -255,12 +244,17 @@ gnucash_item_cursor_draw (GnomeCanvasItem *item, GdkDrawable *drawable, dw = item_cursor->w; dh = item_cursor->h; - gdk_gc_set_foreground (cursor->gc, &gn_light_gray); + gdk_gc_set_line_attributes (cursor->gc, 1, GDK_LINE_SOLID, -1, -1); + gdk_gc_set_foreground (cursor->gc, &gn_light_gray); gdk_draw_rectangle (drawable, cursor->gc, FALSE, dx+1, dy+1, dw-2, dh-2); + + gdk_gc_set_foreground (cursor->gc, &gn_black); + gdk_draw_rectangle (drawable, cursor->gc, FALSE, + dx+2, dy+2, dw-4, dh-4); } } @@ -333,6 +327,12 @@ gnucash_cursor_set (GnucashCursor *cursor, gint block_row, gint block_col, gnucash_cursor_configure (cursor); + gnome_canvas_item_set (GNOME_CANVAS_ITEM(sheet->header_item), + "GnucashHeader::cursor_type", + cursor->style->cursor_type, + "GnucashHeader::cursor_row", cell_row, + NULL); + gnucash_cursor_request_redraw (cursor); } @@ -505,16 +505,9 @@ gnucash_cursor_class_init (GnucashCursorClass *cursor_class) object_class->set_arg = gnucash_cursor_set_arg; object_class->destroy = gnucash_cursor_destroy; -/* GnomeCanvasItem method overrides */ - + /* GnomeCanvasItem method overrides */ item_class->realize = gnucash_cursor_realize; item_class->unrealize = gnucash_cursor_unrealize; -/* - item_class->update = gnucash_cursor_update; - item_class->point = gnucash_cursor_point; - item_class->translate = gnucash_cursor_translate; - item_class->event = gnucash_cursor_event; -*/ } @@ -589,5 +582,3 @@ gnucash_cursor_new (GnomeCanvasGroup *parent) c-basic-offset: 8 End: */ - - diff --git a/src/register/gnome/gnucash-header.c b/src/register/gnome/gnucash-header.c index 54cb2ba856..efd4947c5d 100644 --- a/src/register/gnome/gnucash-header.c +++ b/src/register/gnome/gnucash-header.c @@ -31,7 +31,9 @@ static GnomeCanvasItem *gnucash_header_parent_class; enum { ARG_0, - ARG_SHEET, + ARG_SHEET, /* the sheet this header is associated with */ + ARG_CURSOR_TYPE, /* the type of the current cursor */ + ARG_CURSOR_ROW, /* the row within the current cursor */ }; @@ -64,73 +66,77 @@ gnucash_header_draw (GnomeCanvasItem *item, GdkDrawable *drawable, GdkFont *font; gdk_gc_set_foreground(header->gc, &gn_white); - gdk_draw_rectangle(drawable, header->gc, TRUE, 0, 0, width, height); + gdk_draw_rectangle(drawable, header->gc, TRUE, 0, 0, width, height); gdk_gc_set_line_attributes (header->gc, 1, GDK_LINE_SOLID, -1, -1); gdk_gc_set_foreground (header->gc, &gn_black); gdk_draw_rectangle (drawable, header->gc, FALSE, -x, -y, style->width-1, style->height); gdk_draw_line (drawable, header->gc, - -x, style->height+1, style->width-1, style->height+1); + -x, style->height-1, style->width-1, style->height-1); gdk_gc_set_line_attributes (header->gc, 1, GDK_LINE_SOLID, -1, -1); gdk_gc_set_background (header->gc, &gn_white); gdk_gc_set_foreground (header->gc, &gn_black); - font = gnucash_default_font; + font = style->header_font; xpaint = -x; ypaint = -y; - for (i = 0; i < 1; i++) { - for (j = 0; j < style->ncols; j++) { - w = style->pixel_widths[i][j]; - h = style->pixel_heights[i][j]; + i = header->row; + + /* TODO: This routine is duplicated in several places. Can we + abstract at least the cell drawing routine? That way we'll be + sure everything is drawn consistently, and cut down on maintenance + issues. + */ + for (j = 0; j < style->ncols; j++) { + w = style->pixel_widths[i][j]; + h = style->pixel_heights[i][j]; + + gdk_draw_rectangle (drawable, header->gc, FALSE, + xpaint, ypaint, w, h); + + text = style->labels[i][j]; + + if (text) { + gint x_offset, y_offset; + GdkRectangle rect; - gdk_draw_rectangle (drawable, header->gc, FALSE, - xpaint, ypaint, w, h); - - text = style->labels[i][j]; - - if (text) { - gint x_offset, y_offset; - GdkRectangle rect; - - switch (style->alignments[i][j ]) { - default: - case GTK_JUSTIFY_LEFT: - case GTK_JUSTIFY_FILL: - case GTK_JUSTIFY_CENTER: - x_offset = CELL_HPADDING; - y_offset = h - CELL_VPADDING; - break; - case GTK_JUSTIFY_RIGHT: - y_offset = h - CELL_VPADDING; - x_offset = w - CELL_VPADDING - - gdk_string_measure(font, - text); - break; - } - - rect.x = xpaint + CELL_HPADDING; - rect.y = ypaint + CELL_VPADDING; - rect.width = w - 2*CELL_HPADDING; - rect.height = h; - - gdk_gc_set_clip_rectangle (header->gc, &rect); - - gdk_draw_string (drawable, - font, - header->gc, - xpaint + x_offset, - ypaint + y_offset, - text); - gdk_gc_set_clip_rectangle (header->gc, NULL); - + switch (style->alignments[i][j ]) { + default: + case GTK_JUSTIFY_LEFT: + case GTK_JUSTIFY_FILL: + case GTK_JUSTIFY_CENTER: + x_offset = CELL_HPADDING; + y_offset = h - CELL_VPADDING; + break; + case GTK_JUSTIFY_RIGHT: + y_offset = h - CELL_VPADDING; + x_offset = w - CELL_VPADDING + - gdk_string_measure(font, + text); + break; } - xpaint += w; + rect.x = xpaint + CELL_HPADDING; + rect.y = ypaint + CELL_VPADDING; + rect.width = w - 2*CELL_HPADDING; + rect.height = h; + + gdk_gc_set_clip_rectangle (header->gc, &rect); + + gdk_draw_string (drawable, + font, + header->gc, + xpaint + x_offset, + ypaint + y_offset, + text); + gdk_gc_set_clip_rectangle (header->gc, NULL); + } - ypaint += h; + + xpaint += w; } } @@ -143,6 +149,7 @@ gnucash_header_request_redraw (GnucashHeader *header) gnome_canvas_request_redraw (canvas, 0, 0, INT_MAX, INT_MAX); } + static void gnucash_header_realize (GnomeCanvasItem *item) { @@ -165,10 +172,13 @@ gnucash_header_unrealize (GnomeCanvasItem *item) { GnucashHeader *header = GNUCASH_HEADER (item); - if (header->gc != NULL) { - gdk_gc_unref (header->gc); - header->gc = NULL; - } + if (header->gc != NULL) { + gdk_gc_unref (header->gc); + header->gc = NULL; + } + + gdk_cursor_destroy (header->resize_cursor); + gdk_cursor_destroy (header->normal_cursor); if (GNOME_CANVAS_ITEM_CLASS (gnucash_header_parent_class)->unrealize) (*GNOME_CANVAS_ITEM_CLASS @@ -184,10 +194,10 @@ gnucash_header_destroy (GtkObject *object) if (GTK_OBJECT_CLASS (gnucash_header_parent_class)->destroy) (*GTK_OBJECT_CLASS (gnucash_header_parent_class)->destroy)(object); + } -/* FIXME: for now, let's only draw one row, to avoid resizing. */ void gnucash_header_reconfigure (GnucashHeader *header) { @@ -195,13 +205,18 @@ gnucash_header_reconfigure (GnucashHeader *header) int w, h; double old_w, old_h; - header->style = header->sheet->cursor_style[GNUCASH_CURSOR_HEADER]; + header->style = header->sheet->cursor_style[header->type]; - if (header->style == NULL) - return; + if (header->style == NULL) + return; - w = header->style->width + 1; - h = header->style->pixel_heights[0][0] + 2; + /* Check for a valid header row. This can be invalid during + arg setting. */ + if (header->row < 0 || header->row >= header->style->nrows) + return; + + w = header->style->width; + h = header->style->pixel_heights[header->row][0]; gnome_canvas_get_scroll_region(canvas, NULL, NULL, &old_w, &old_h); @@ -209,24 +224,90 @@ gnucash_header_reconfigure (GnucashHeader *header) gnome_canvas_set_scroll_region(GNOME_CANVAS(canvas), 0, 0, w, h); - gtk_widget_set_usize (GTK_WIDGET(canvas), w, h); + gtk_widget_set_usize (GTK_WIDGET(canvas), -1, h); } gnucash_header_request_redraw (header); } +static double +gnucash_header_point (GnomeCanvasItem *item, + double x, double y, int cx, int cy, + GnomeCanvasItem **actual_item) +{ + *actual_item = item; + return 0.0; +} + + +static int +pointer_on_resize_line (GnucashHeader *header, int x, int y) +{ + SheetBlockStyle *style = header->style; + int i, j; + int pixels = 0; + + for (j = 0; j < style->ncols; j++) { + if (x >= pixels - 1 && x <= pixels+1) + return TRUE; + pixels += style->pixel_widths[header->row][j]; + } + + return FALSE; +} + + +static gint +gnucash_header_event (GnomeCanvasItem *item, GdkEvent *event) +{ + GnucashHeader *header = GNUCASH_HEADER(item); + GnomeCanvas *canvas = item->canvas; + int x, y; + + switch (event->type) { + case GDK_MOTION_NOTIFY: + + gnome_canvas_w2c (canvas, event->motion.x, event->motion.y, + &x, &y); + + if (pointer_on_resize_line(header, x, y)) + gdk_window_set_cursor (GTK_WIDGET(canvas)->window, + header->resize_cursor); + else + gdk_window_set_cursor (GTK_WIDGET(canvas)->window, + header->normal_cursor); + + break; + } + + + return TRUE; +} + + static void gnucash_header_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) { GnucashHeader *header; + GtkLayout *layout; gint needs_update = FALSE; header = GNUCASH_HEADER (o); + layout = GTK_LAYOUT(GNOME_CANVAS_ITEM(header)->canvas); switch (arg_id){ case ARG_SHEET: header->sheet = GTK_VALUE_POINTER (*arg); + gtk_layout_set_hadjustment (layout, header->sheet->hadj); + needs_update = TRUE; + break; + case ARG_CURSOR_TYPE: + header->type = GTK_VALUE_INT (*arg); + needs_update = TRUE; + break; + case ARG_CURSOR_ROW: + header->row = GTK_VALUE_INT (*arg); needs_update = TRUE; break; default: @@ -242,6 +323,8 @@ static void gnucash_header_init (GnucashHeader *header) { header->sheet = NULL; + header->resize_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); + header->normal_cursor = gdk_cursor_new (GDK_X_CURSOR); } @@ -259,14 +342,20 @@ gnucash_header_class_init (GnucashHeaderClass *header_class) gtk_object_add_arg_type ("GnucashHeader::sheet", GTK_TYPE_POINTER, GTK_ARG_WRITABLE, ARG_SHEET); + gtk_object_add_arg_type ("GnucashHeader::cursor_type", GTK_TYPE_INT, + GTK_ARG_WRITABLE, ARG_CURSOR_TYPE); + gtk_object_add_arg_type ("GnucashHeader::cursor_row", GTK_TYPE_INT, + GTK_ARG_WRITABLE, ARG_CURSOR_ROW); object_class->set_arg = gnucash_header_set_arg; object_class->destroy = gnucash_header_destroy; - item_class->realize = gnucash_header_realize; - item_class->unrealize = gnucash_header_unrealize; - item_class->update = gnucash_header_update; - item_class->draw = gnucash_header_draw; + item_class->realize = gnucash_header_realize; + item_class->unrealize = gnucash_header_unrealize; + item_class->update = gnucash_header_update; + item_class->draw = gnucash_header_draw; + item_class->event = gnucash_header_event; + item_class->point = gnucash_header_point; } @@ -322,6 +411,9 @@ gnucash_header_new (GnucashSheet *sheet) item = gnome_canvas_item_new (group, gnucash_header_get_type (), "GnucashHeader::sheet", sheet, + "GnucashHeader::cursor_type", + GNUCASH_CURSOR_HEADER, + "GnucashHeader::cursor_row", 0, NULL); sheet->header_item = item; @@ -336,5 +428,3 @@ gnucash_header_new (GnucashSheet *sheet) c-basic-offset: 8 End: */ - - diff --git a/src/register/gnome/gnucash-header.h b/src/register/gnome/gnucash-header.h index 27463b0ff6..eaadcced91 100644 --- a/src/register/gnome/gnucash-header.h +++ b/src/register/gnome/gnucash-header.h @@ -31,9 +31,14 @@ typedef struct { GnomeCanvasItem canvas_item; GnucashSheet *sheet; - SheetBlockStyle *style; + + int type; + int row; + GdkGC *gc; + GdkCursor *normal_cursor; + GdkCursor *resize_cursor; } GnucashHeader; diff --git a/src/register/gnome/gnucash-item-edit.c b/src/register/gnome/gnucash-item-edit.c index 9c5d86c7b4..e3098c8ddb 100644 --- a/src/register/gnome/gnucash-item-edit.c +++ b/src/register/gnome/gnucash-item-edit.c @@ -80,9 +80,9 @@ item_edit_get_pixel_coords (ItemEdit *item_edit, int *x, int *y, GnucashSheet *sheet = item_edit->sheet; int xd, yd; - gnome_canvas_get_scroll_offsets (GNOME_CANVAS(sheet), &xd, &yd); + gnome_canvas_get_scroll_offsets (GNOME_CANVAS(sheet), NULL, &yd); - xd += gnucash_sheet_col_get_distance(sheet, sheet->left_block, + xd = gnucash_sheet_col_get_distance(sheet, sheet->left_block, item_edit->virt_col) + sheet->left_block_offset; yd += gnucash_sheet_row_get_distance (sheet, sheet->top_block, @@ -275,7 +275,7 @@ item_edit_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, toggle_x = x + w - (toggle_width + 3); toggle_y = y + 5; - item_edit->combo_toggle.toggle_offset = toggle_width + 3; + item_edit->combo_toggle.toggle_offset = toggle_width + 3; item_edit_show_combo_toggle(item_edit, toggle_x, toggle_y, toggle_width, toggle_height, @@ -507,7 +507,7 @@ item_edit_configure (ItemEdit *item_edit) item_edit->style = gnucash_sheet_get_style (item_edit->sheet, item_edit->virt_row, - item_edit->virt_col); + item_edit->virt_col); cursor = GNUCASH_ITEM_CURSOR (GNUCASH_CURSOR(sheet->cursor)->cursor[GNUCASH_CURSOR_CELL]); @@ -566,22 +566,11 @@ static void item_edit_combo_toggled (GtkToggleButton *button, gpointer data) { ItemEdit *item_edit = ITEM_EDIT(data); - GtkArrowType arrow_type; - GtkShadowType shadow_type; item_edit->show_list = gtk_toggle_button_get_active(button); - if (!item_edit->show_list) { - item_edit_hide_list(item_edit); - arrow_type = GTK_ARROW_DOWN; - shadow_type = GTK_SHADOW_IN; - } - else { - arrow_type = GTK_ARROW_UP; - shadow_type = GTK_SHADOW_OUT; - } - - gtk_arrow_set(item_edit->combo_toggle.arrow, arrow_type, shadow_type); + if (!item_edit->show_list) + item_edit_hide_list(item_edit); item_edit_configure (item_edit); } @@ -655,12 +644,6 @@ item_edit_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) disconnect_combo_signals(item_edit); - gtk_arrow_set(item_edit->combo_toggle.arrow, - GTK_ARROW_DOWN, GTK_SHADOW_IN); - - gtk_toggle_button_set_active - (item_edit->combo_toggle.combo_button, FALSE); - item_edit_hide_list(item_edit); item_edit_hide_combo_toggle(item_edit); } @@ -800,6 +783,11 @@ item_edit_show_list (ItemEdit *item_edit, gint x, gint y, "height", (gdouble) height, "anchor", anchor, NULL); + + gtk_arrow_set(item_edit->combo_toggle.arrow, + GTK_ARROW_UP, GTK_SHADOW_OUT); + + gtk_widget_grab_focus(GTK_WIDGET(item_edit->item_list->clist)); } @@ -814,6 +802,14 @@ item_edit_hide_list (ItemEdit *item_edit) /* safely out of the way */ gnome_canvas_item_set(GNOME_CANVAS_ITEM(item_edit->item_list), "x", -10000.0, NULL); + + gtk_arrow_set(item_edit->combo_toggle.arrow, + GTK_ARROW_DOWN, GTK_SHADOW_IN); + + gtk_widget_grab_focus(GTK_WIDGET(item_edit->sheet)); + + gtk_toggle_button_set_active + (item_edit->combo_toggle.combo_button, FALSE); } diff --git a/src/register/gnome/gnucash-item-list.c b/src/register/gnome/gnucash-item-list.c index 59a91593aa..3d3eee8659 100644 --- a/src/register/gnome/gnucash-item-list.c +++ b/src/register/gnome/gnucash-item-list.c @@ -261,6 +261,7 @@ gnc_item_list_new(GnomeCanvasGroup *parent) item = gnome_canvas_item_new(parent, gnc_item_list_get_type(), "widget", hbox, "size_pixels", TRUE, + "x", -10000.0, NULL); item_list = GNC_ITEM_LIST(item); diff --git a/src/register/gnome/gnucash-sheet.c b/src/register/gnome/gnucash-sheet.c index ca17ce8a0e..db31ea78cf 100644 --- a/src/register/gnome/gnucash-sheet.c +++ b/src/register/gnome/gnucash-sheet.c @@ -35,7 +35,8 @@ #include "util.h" #define DEFAULT_REGISTER_HEIGHT 400 -#define DEFAULT_REGISTER_WIDTH 640 +#define DEFAULT_REGISTER_WIDTH 630 +#define DEFAULT_REGISTER_ROWS 15 static void gnucash_sheet_cell_set_from_table (GnucashSheet *sheet, @@ -50,10 +51,8 @@ static void gnucash_sheet_start_editing_at_cursor (GnucashSheet *sheet, gboolean blankp, gboolean cursorp); -static void gnucash_sheet_cursor_move (GnucashSheet *sheet, - gint virt_row, gint virt_col, - gint cell_row, gint cell_col, - gboolean changed_cells); +static gboolean gnucash_sheet_cursor_move (GnucashSheet *sheet, + gint phys_row, gint phys_col); static void gnucash_sheet_deactivate_cursor_cell (GnucashSheet *sheet); static void gnucash_sheet_activate_cursor_cell (GnucashSheet *sheet, @@ -65,8 +64,16 @@ static void gnucash_sheet_block_destroy (GnucashSheet *sheet, gint virt_row, static void gnucash_sheet_update_adjustments (GnucashSheet *sheet); +/* Register signals */ +enum +{ + ACTIVATE_CURSOR, + LAST_SIGNAL +}; + static GnomeCanvasClass *sheet_parent_class; static GtkTableClass *register_parent_class; +static guint register_signals[LAST_SIGNAL]; gint @@ -124,7 +131,7 @@ gnucash_sheet_cursor_set (GnucashSheet *sheet, int virt_row, int virt_col, } void -gnucash_sheet_cursor_set_from_table (GnucashSheet *sheet) +gnucash_sheet_cursor_set_from_table (GnucashSheet *sheet, gncBoolean do_scroll) { Table *table; gint cell_row, cell_col; @@ -139,26 +146,25 @@ gnucash_sheet_cursor_set_from_table (GnucashSheet *sheet) p_row = table->current_cursor_phys_row; p_col = table->current_cursor_phys_col; - if (p_row >= 0 && p_row < table->num_phys_rows - && p_col >= 0 && p_col < table->num_phys_cols) { + if (p_row < 0 || p_row >= table->num_phys_rows || + p_col < 0 || p_col >= table->num_phys_cols) + return; - cell_row = table->locators[p_row][p_col]->phys_row_offset; - cell_col = table->locators[p_row][p_col]->phys_col_offset; + cell_row = table->locators[p_row][p_col]->phys_row_offset; + cell_col = table->locators[p_row][p_col]->phys_col_offset; + if (do_scroll) gnucash_sheet_make_cell_visible(sheet, - table->current_cursor_virt_row, - table->current_cursor_virt_col, - cell_row, - cell_col); + table->current_cursor_virt_row, + table->current_cursor_virt_col, + cell_row, + cell_col); - gnucash_sheet_update_adjustments(sheet); - - gnucash_sheet_cursor_set(sheet, - table->current_cursor_virt_row, - table->current_cursor_virt_col, - cell_row, - cell_col); - } + gnucash_sheet_cursor_set(sheet, + table->current_cursor_virt_row, + table->current_cursor_virt_col, + cell_row, + cell_col); } @@ -208,19 +214,16 @@ gnucash_sheet_deactivate_cursor_cell (GnucashSheet *sheet) gnucash_sheet_stop_editing (sheet); - if (gnc_register_cell_valid (table, p_row, p_col)) { - old_text = gnucash_sheet_block_get_text(sheet, virt_row, - virt_col, cell_row, - cell_col); + old_text = gnucash_sheet_block_get_text(sheet, virt_row, + virt_col, cell_row, + cell_col); - new_text = gnc_table_leave_update(table, p_row, p_col, - old_text); + new_text = gnc_table_leave_update(table, p_row, p_col, old_text); - if (new_text) - gnucash_sheet_cell_set_from_table (sheet, virt_row, - virt_col, cell_row, - cell_col); - } + if (new_text) + gnucash_sheet_cell_set_from_table (sheet, virt_row, + virt_col, cell_row, + cell_col); gnucash_sheet_redraw_block (sheet, virt_row, virt_col); } @@ -236,8 +239,7 @@ gnucash_sheet_activate_cursor_cell (GnucashSheet *sheet, int virt_row, virt_col, cell_row, cell_col; SheetBlockStyle *style; - - /* Hmm, this shouldn't happen, but let's be sure */ + /* Sanity check */ if (sheet->editing) gnucash_sheet_deactivate_cursor_cell (sheet); @@ -246,8 +248,14 @@ gnucash_sheet_activate_cursor_cell (GnucashSheet *sheet, gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_row, &virt_col, &cell_row, &cell_col); + /* This should be a no-op */ wrapVerifyCursorPosition (table, p_row, p_col); + gnucash_cursor_get_phys (GNUCASH_CURSOR(sheet->cursor), + &p_row, &p_col); + gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), + &virt_row, &virt_col, &cell_row, &cell_col); + style = gnucash_sheet_get_style (sheet, virt_row, virt_col); if (style->cursor_type == GNUCASH_CURSOR_HEADER || !gnc_register_cell_valid (table, p_row, p_col) ) @@ -266,18 +274,48 @@ gnucash_sheet_activate_cursor_cell (GnucashSheet *sheet, } -static void -gnucash_sheet_cursor_move (GnucashSheet *sheet, gint virt_row, gint virt_col, - gint cell_row, gint cell_col, - gboolean changed_cells) +static gboolean +gnucash_sheet_cursor_move (GnucashSheet *sheet, gint phys_row, gint phys_col) { - if (!gnucash_sheet_cell_valid (sheet, - virt_row, virt_col, - cell_row, cell_col)) - return; + int old_virt_row, old_virt_col, old_cell_row, old_cell_col; + int virt_row, virt_col, cell_row, cell_col; + int old_phys_row, old_phys_col; + gboolean changed_cells; + Table *table; + table = sheet->table; + + /* Get the old cursor position */ + gnucash_cursor_get_phys (GNUCASH_CURSOR(sheet->cursor), + &old_phys_row, &old_phys_col); + + gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), + &old_virt_row, &old_virt_col, + &old_cell_row, &old_cell_col); + + /* Turn off the editing controls */ gnucash_sheet_deactivate_cursor_cell (sheet); + /* Do the move. This may result in table restructuring due to + * commits, auto modes, etc. */ + wrapVerifyCursorPosition (table, phys_row, phys_col); + + /* A complete reload can leave us with editing back on */ + if (sheet->editing) + gnucash_sheet_deactivate_cursor_cell (sheet); + + /* Find out where we really landed. We have to get the new + * physical position as well, as the table may have been + * restructured. */ + gnucash_cursor_get_phys (GNUCASH_CURSOR(sheet->cursor), + &phys_row, &phys_col); + + gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), + &virt_row, &virt_col, + &cell_row, &cell_col); + + /* We should be at our new location now. Show it on screen and + * configure the cursor. */ gnucash_sheet_make_cell_visible (sheet, virt_row, virt_col, cell_row, cell_col); @@ -286,14 +324,14 @@ gnucash_sheet_cursor_move (GnucashSheet *sheet, gint virt_row, gint virt_col, virt_row, virt_col, cell_row, cell_col); + changed_cells = + (phys_row != old_phys_row) || + (phys_col != old_phys_col); + + /* Now turn on the editing controls. */ gnucash_sheet_activate_cursor_cell (sheet, changed_cells); -} - -int -gnucash_sheet_can_move_cursor (GnucashSheet *gsheet) -{ - return TRUE; + return changed_cells; } @@ -440,24 +478,38 @@ gnucash_sheet_make_cell_visible (GnucashSheet *sheet, } -/* FIXME: do the horizontal adjustment, too? */ static void gnucash_sheet_update_adjustments (GnucashSheet *sheet) { - GtkAdjustment *adj; + GtkAdjustment *vadj; + GtkAdjustment *hadj; + GnucashCursor *cursor; g_return_if_fail (sheet != NULL); g_return_if_fail (GNUCASH_IS_SHEET (sheet)); g_return_if_fail (sheet->vadj != NULL); + g_return_if_fail (sheet->hadj != NULL); + g_return_if_fail (sheet->cursor != NULL); - adj = sheet->vadj; + vadj = sheet->vadj; + hadj = sheet->hadj; + cursor = GNUCASH_CURSOR(sheet->cursor); - adj->lower = 1; - adj->upper = MAX(sheet->bottom_block, sheet->num_virt_rows); - adj->page_size = sheet->bottom_block - sheet->top_block + 1; - adj->page_increment = adj->page_size - 1; + vadj->lower = 1; + vadj->upper = MAX(sheet->bottom_block, sheet->num_virt_rows); + vadj->page_size = sheet->bottom_block - sheet->top_block + 1; + vadj->page_increment = vadj->page_size - 1; - gtk_signal_emit_by_name (GTK_OBJECT(adj), "changed"); + gtk_signal_emit_by_name (GTK_OBJECT(vadj), "changed"); + + if (cursor->style) { + hadj->lower = 0; + hadj->upper = cursor->style->width; + hadj->page_size = GTK_WIDGET(sheet)->allocation.width; + hadj->page_increment = hadj->page_size; + hadj->step_increment = hadj->page_size / 100.0; + gtk_adjustment_changed (hadj); + } } @@ -659,16 +711,37 @@ gnucash_sheet_create (Table *table) * maybe just default the height? */ static gint -compute_optimal_width (Table *table) +compute_optimal_width (GnucashSheet *sheet) { + SheetBlockStyle *style; + + if ((sheet == NULL) || (sheet->cursor_style == NULL)) + return DEFAULT_REGISTER_WIDTH; + + style = sheet->cursor_style[GNUCASH_CURSOR_HEADER]; + + if ((style == NULL) || (style->widths == NULL)) + return DEFAULT_REGISTER_WIDTH; + return DEFAULT_REGISTER_WIDTH; } +/* Compute the height needed to show DEFAULT_REGISTER_ROWS rows */ static gint -compute_optimal_height (Table *table) +compute_optimal_height (GnucashSheet *sheet) { - return DEFAULT_REGISTER_HEIGHT; + SheetBlockStyle *style; + + if ((sheet == NULL) || (sheet->cursor_style == NULL)) + return DEFAULT_REGISTER_HEIGHT; + + style = sheet->cursor_style[GNUCASH_CURSOR_HEADER]; + + if ((style == NULL) || (style->pixel_heights == NULL)) + return DEFAULT_REGISTER_HEIGHT; + + return (style->pixel_heights[0][0] * DEFAULT_REGISTER_ROWS); } @@ -677,8 +750,8 @@ gnucash_sheet_size_request (GtkWidget *widget, GtkRequisition *requisition) { GnucashSheet *sheet = GNUCASH_SHEET(widget); - requisition->width = compute_optimal_width (sheet->table); - requisition->height = compute_optimal_height (sheet->table); + requisition->width = compute_optimal_width (sheet); + requisition->height = compute_optimal_height (sheet); } @@ -694,6 +767,9 @@ gnucash_sheet_modify_current_cell(GnucashSheet *sheet, const gchar *new_text) char *newval; char *change; + int current_position; + int new_position; + gnucash_cursor_get_phys(GNUCASH_CURSOR(sheet->cursor), &p_row, &p_col); gnucash_cursor_get_virt(GNUCASH_CURSOR(sheet->cursor), @@ -710,8 +786,14 @@ gnucash_sheet_modify_current_cell(GnucashSheet *sheet, const gchar *new_text) change = strdup(new_text); assert((newval != NULL) && (change != NULL)); + current_position = + gtk_editable_get_position (GTK_EDITABLE(sheet->entry)); + + new_position = current_position; + retval = gnc_table_modify_update (table, p_row, p_col, - old_text, change, newval); + old_text, change, newval, + &new_position); gnucash_sheet_cell_set_from_table (sheet, v_row, v_col, c_row, c_col); @@ -721,6 +803,11 @@ gnucash_sheet_modify_current_cell(GnucashSheet *sheet, const gchar *new_text) gtk_entry_set_text (GTK_ENTRY (sheet->entry), retval); + if(new_position != current_position) { + gtk_editable_set_position (GTK_EDITABLE(sheet->entry), + new_position); + } + gtk_signal_handler_unblock (GTK_OBJECT (sheet->entry), sheet->insert_signal); @@ -780,7 +867,8 @@ gnucash_sheet_insert_cb (GtkWidget *widget, const gchar *new_text, strncpy (change, new_text, new_text_length); retval = gnc_table_modify_update (table, p_row, p_col, old_text, - change, newval); + change, newval, + position); gnucash_sheet_cell_set_from_table (sheet, v_row, v_col, c_row, c_col); @@ -793,11 +881,11 @@ gnucash_sheet_insert_cb (GtkWidget *widget, const gchar *new_text, gtk_signal_handler_block(GTK_OBJECT (sheet->entry), sheet->insert_signal); + gtk_entry_set_text (GTK_ENTRY (sheet->entry), retval); - gtk_editable_set_position (GTK_EDITABLE(sheet->entry), - *position + new_text_length + 1); - gtk_signal_handler_unblock ( GTK_OBJECT (sheet->entry), - sheet->insert_signal); + + gtk_signal_handler_unblock (GTK_OBJECT (sheet->entry), + sheet->insert_signal); gtk_signal_emit_stop_by_name (GTK_OBJECT(sheet->entry), "insert_text"); @@ -828,6 +916,7 @@ gnucash_sheet_delete_cb (GtkWidget *widget, const char *old_text; char *newval = NULL; const char *retval = NULL; + int new_position = start_pos; gnucash_cursor_get_phys (GNUCASH_CURSOR(sheet->cursor), &p_row, &p_col); @@ -850,7 +939,7 @@ gnucash_sheet_delete_cb (GtkWidget *widget, strcat (newval, &old_text[end_pos]); retval = gnc_table_modify_update (table, p_row, p_col, old_text, - NULL, newval); + NULL, newval, &new_position); gnucash_sheet_cell_set_from_table (sheet, v_row, v_col, c_row, c_col); @@ -864,6 +953,12 @@ gnucash_sheet_delete_cb (GtkWidget *widget, gtk_signal_handler_block ( GTK_OBJECT (sheet->entry), sheet->insert_signal); gtk_entry_set_text (GTK_ENTRY (sheet->entry), retval); + + if(new_position != start_pos) { + gtk_editable_set_position (GTK_EDITABLE(sheet->entry), + new_position); + } + gtk_signal_handler_unblock ( GTK_OBJECT (sheet->entry), sheet->insert_signal); @@ -885,7 +980,7 @@ gnucash_sheet_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GnucashSheet *sheet = GNUCASH_SHEET(widget); gint i; - + if (GTK_WIDGET_CLASS(sheet_parent_class)->size_allocate) (*GTK_WIDGET_CLASS (sheet_parent_class)->size_allocate) (widget, allocation); @@ -895,6 +990,7 @@ gnucash_sheet_size_allocate (GtkWidget *widget, GtkAllocation *allocation) sheet->cursor_style[i]); gnucash_cursor_configure (GNUCASH_CURSOR (sheet->cursor)); + item_edit_configure (ITEM_EDIT(sheet->item_editor)); gnucash_header_reconfigure (GNUCASH_HEADER(sheet->header_item)); gnucash_sheet_update_adjustments (sheet); } @@ -957,18 +1053,15 @@ static gint gnucash_button_press_event (GtkWidget *widget, GdkEventButton *event) { GnucashSheet *sheet; - gboolean changed_cells; int xoffset, yoffset; + gboolean changed_cells; int x, y; /* physical coordinates */ int current_p_row, current_p_col, new_p_row, new_p_col; /* virtual coordinates */ - int current_v_row, current_v_col, new_v_row, new_v_col; - - /* cell coordinates */ - int current_c_row, current_c_col, new_c_row, new_c_col; + int new_v_row, new_v_col, new_c_row, new_c_col; Table *table; gncBoolean exit_register; @@ -978,7 +1071,7 @@ gnucash_button_press_event (GtkWidget *widget, GdkEventButton *event) g_return_val_if_fail(event != NULL, TRUE); if (event->type != GDK_BUTTON_PRESS || event->button != 1) - return TRUE; + return FALSE; sheet = GNUCASH_SHEET (widget); table = sheet->table; @@ -989,18 +1082,14 @@ gnucash_button_press_event (GtkWidget *widget, GdkEventButton *event) gnucash_cursor_get_phys (GNUCASH_CURSOR(sheet->cursor), ¤t_p_row, ¤t_p_col); - gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), - ¤t_v_row, ¤t_v_col, - ¤t_c_row, ¤t_c_col); - x = xoffset + event->x; y = yoffset + event->y; if (!gnucash_grid_find_cell_origin_by_pixel(GNUCASH_GRID(sheet->grid), x, y, &new_v_row, &new_v_col, - &new_c_row, &new_c_col, - NULL, NULL)) + &new_c_row, &new_c_col, + NULL, NULL)) return TRUE; new_p_row = table->rev_locators[new_v_row][new_v_col]->phys_row + @@ -1023,25 +1112,12 @@ gnucash_button_press_event (GtkWidget *widget, GdkEventButton *event) if (!gnc_register_cell_valid (table, new_p_row, new_p_col)) return TRUE; - new_v_row = table->locators[new_p_row][new_p_col]->virt_row; - new_v_col = table->locators[new_p_row][new_p_col]->virt_col; - - new_c_row = table->locators[new_p_row][new_p_col]->phys_row_offset; - new_c_col = table->locators[new_p_row][new_p_col]->phys_col_offset; - - changed_cells = - (new_v_row != current_v_row) || - (new_v_col != current_v_col) || - (new_c_row != current_c_row) || - (new_c_col != current_c_col); - - gnucash_sheet_cursor_move (sheet, - new_v_row, new_v_col, - new_c_row, new_c_col, - changed_cells); + changed_cells = gnucash_sheet_cursor_move (sheet, + new_p_row, new_p_col); item_edit_set_cursor_pos (ITEM_EDIT(sheet->item_editor), x, y, changed_cells); + return TRUE; } @@ -1049,23 +1125,13 @@ gnucash_button_press_event (GtkWidget *widget, GdkEventButton *event) static gint gnucash_sheet_key_press_event (GtkWidget *widget, GdkEventKey *event) { + Table *table; GnucashSheet *sheet; CellBlock *header; int direction = 0; gboolean pass_on = FALSE; - gboolean changed_cells; - - /* physical coordinates */ - int current_p_row, current_p_col, new_p_row, new_p_col; - - /* virtual coordinates */ - int current_v_row, current_v_col, new_v_row, new_v_col; - - /* cell coordinates */ - int current_c_row, current_c_col, new_c_row, new_c_col; - - Table *table; gncBoolean exit_register; + int current_p_row, current_p_col, new_p_row, new_p_col; g_return_val_if_fail(widget != NULL, TRUE); g_return_val_if_fail(GNUCASH_IS_SHEET(widget), TRUE); @@ -1078,18 +1144,23 @@ gnucash_sheet_key_press_event (GtkWidget *widget, GdkEventKey *event) gnucash_cursor_get_phys (GNUCASH_CURSOR(sheet->cursor), ¤t_p_row, ¤t_p_col); - gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), - ¤t_v_row, ¤t_v_col, - ¤t_c_row, ¤t_c_col); - /* Calculate tentative physical values */ switch (event->keyval) { case GDK_Tab: - direction = GNC_TABLE_TRAVERSE_RIGHT; - new_p_row = current_p_row; - new_p_col = MIN(current_p_col + 1, - table->num_phys_cols - 1); - break; + case GDK_ISO_Left_Tab: + if (event->state & GDK_SHIFT_MASK) { + direction = GNC_TABLE_TRAVERSE_LEFT; + new_p_row = current_p_row; + new_p_col = MAX(current_p_col - 1, 0); + } + else { + direction = GNC_TABLE_TRAVERSE_RIGHT; + new_p_row = current_p_row; + new_p_col = MIN(current_p_col + 1, + table->num_phys_cols - 1); + } + break; + case GDK_KP_Page_Up: case GDK_Page_Up: direction = GNC_TABLE_TRAVERSE_UP; new_p_col = 0; @@ -1097,6 +1168,7 @@ gnucash_sheet_key_press_event (GtkWidget *widget, GdkEventKey *event) sheet->vadj->page_increment, header->numRows); break; + case GDK_KP_Page_Down: case GDK_Page_Down: direction = GNC_TABLE_TRAVERSE_DOWN; new_p_col = 0; @@ -1104,12 +1176,14 @@ gnucash_sheet_key_press_event (GtkWidget *widget, GdkEventKey *event) sheet->vadj->page_increment, table->num_phys_rows - 1); break; + case GDK_KP_Up: case GDK_Up: direction = GNC_TABLE_TRAVERSE_UP; new_p_col = current_p_col; new_p_row = MAX(current_p_row - 1, header->numRows); break; + case GDK_KP_Down: case GDK_Down: direction = GNC_TABLE_TRAVERSE_DOWN; new_p_col = current_p_col; @@ -1135,48 +1209,92 @@ gnucash_sheet_key_press_event (GtkWidget *widget, GdkEventKey *event) (table, current_p_row, current_p_col, direction, &new_p_row, &new_p_col); - /* If that would leave the register and we're not going right, abort */ - if (exit_register && (direction != GNC_TABLE_TRAVERSE_RIGHT)) - return TRUE; - - /* Hack: try to wrap around. Shouldn't splitreg.c do this? */ - if (exit_register) { - new_p_row = table->rev_locators[current_v_row] - [current_v_col]->phys_row; - new_p_col = 0; - exit_register = gnc_table_traverse_update - (table, current_p_row, current_p_col, - GNC_TABLE_TRAVERSE_POINTER, &new_p_row, &new_p_col); - } - - /* Give up */ - if (exit_register) + /* If that would leave the register and we're not tabbing, abort */ + if (exit_register && + direction != GNC_TABLE_TRAVERSE_RIGHT && + direction != GNC_TABLE_TRAVERSE_LEFT) return TRUE; /* Shouldn't gnc_table_traverse_update fill in valid cells ? */ if (!gnc_register_cell_valid (table, new_p_row, new_p_col)) return TRUE; - new_v_row = table->locators[new_p_row][new_p_col]->virt_row; - new_v_col = table->locators[new_p_row][new_p_col]->virt_col; - - new_c_row = table->locators[new_p_row][new_p_col]->phys_row_offset; - new_c_col = table->locators[new_p_row][new_p_col]->phys_col_offset; - - changed_cells = - (new_v_row != current_v_row) || - (new_v_col != current_v_col) || - (new_c_row != current_c_row) || - (new_c_col != current_c_col); - - gnucash_sheet_cursor_move(sheet, new_v_row, new_v_col, - new_c_row, new_c_col, changed_cells); - + gnucash_sheet_cursor_move (sheet, new_p_row, new_p_col); + /* return true because we handled the key press */ return TRUE; } +static void +gnucash_sheet_goto_virt_row_col (GnucashSheet *sheet, + int new_v_row, int new_v_col) +{ + Table *table; + gncBoolean exit_register; + int current_p_row, current_p_col, new_p_row, new_p_col; + + g_return_if_fail(GNUCASH_IS_SHEET(sheet)); + + table = sheet->table; + + gnucash_cursor_get_phys (GNUCASH_CURSOR(sheet->cursor), + ¤t_p_row, ¤t_p_col); + + new_p_row = table->rev_locators[new_v_row][new_v_col]->phys_row; + new_p_col = table->rev_locators[new_v_row][new_v_col]->phys_col; + + /* It's not really a pointer traverse, but it seems the most + * appropriate here. */ + exit_register = gnc_table_traverse_update + (table, current_p_row, current_p_col, + GNC_TABLE_TRAVERSE_POINTER, &new_p_row, &new_p_col); + + if (exit_register) + return; + + /* Shouldn't gnc_table_traverse_update fill in valid cells ? */ + if (!gnc_register_cell_valid (table, new_p_row, new_p_col)) + return; + + gnucash_sheet_cursor_move (sheet, new_p_row, new_p_col); +} + + +void +gnucash_register_goto_virt_row_col (GnucashRegister *reg, int v_row, int v_col) +{ + GnucashSheet *sheet; + + g_return_if_fail(GNUCASH_IS_REGISTER(reg)); + + sheet = GNUCASH_SHEET(reg->sheet); + + gnucash_sheet_goto_virt_row_col(sheet, v_row, v_col); +} + + +void +gnucash_register_goto_next_virt_row (GnucashRegister *reg) +{ + GnucashSheet *sheet; + int v_row, v_col, c_row, c_col; + + g_return_if_fail(GNUCASH_IS_REGISTER(reg)); + + sheet = GNUCASH_SHEET(reg->sheet); + + gnucash_cursor_get_virt(GNUCASH_CURSOR(sheet->cursor), + &v_row, &v_col, &c_row, &c_col); + + v_row++; + if (v_row >= sheet->num_virt_rows) + return; + + gnucash_sheet_goto_virt_row_col(sheet, v_row, v_col); +} + + SheetBlock * gnucash_sheet_get_block (GnucashSheet *sheet, gint virt_row, gint virt_col) { @@ -1213,7 +1331,7 @@ gnucash_sheet_block_get_text (GnucashSheet *sheet, gint virt_row, } -void +static void gnucash_sheet_block_clear_entries (SheetBlock *block) { gint i,j; @@ -1230,6 +1348,13 @@ gnucash_sheet_block_clear_entries (SheetBlock *block) g_free (block->fg_colors[i]); g_free (block->bg_colors[i]); } + g_free (block->entries); + g_free (block->fg_colors); + g_free (block->bg_colors); + + block->entries = NULL; + block->fg_colors = NULL; + block->bg_colors = NULL; } } @@ -1288,10 +1413,7 @@ gnucash_sheet_block_set_from_table (GnucashSheet *sheet, gint virt_row, table = sheet->table; - if (block->entries) { - gnucash_sheet_block_clear_entries (block); - g_free (block->entries); - } + gnucash_sheet_block_clear_entries (block); if (block->style) gnucash_style_unref (block->style); @@ -1364,9 +1486,6 @@ gnucash_sheet_block_destroy (GnucashSheet *sheet, gint virt_row, gint virt_col) if (block) { gnucash_sheet_block_clear_entries (block); - g_free (block->entries); - g_free (block->fg_colors); - g_free (block->bg_colors); if (block->style) gnucash_style_unref (block->style); @@ -1471,14 +1590,11 @@ gnucash_sheet_table_load (GnucashSheet *sheet) for (j = 0; j < table->num_virt_cols; j++) gnucash_sheet_block_set_from_table (sheet, i, j); - - /* FIXME: try to keep the cursor row in the same visual position */ - /* FIXME: Probably we should set the bottom row instead */ - gnucash_sheet_set_top_row (sheet, 0, GNUCASH_ALIGN_TOP); + gnucash_sheet_set_top_row (sheet, sheet->top_block, GNUCASH_ALIGN_TOP); gnucash_sheet_compute_visible_range(sheet); - gnucash_sheet_cursor_set_from_table (sheet); + gnucash_sheet_cursor_set_from_table (sheet, TRUE); gnucash_sheet_activate_cursor_cell (sheet, TRUE); } @@ -1550,6 +1666,7 @@ gnucash_sheet_init (GnucashSheet *sheet) sheet->item_editor = NULL; sheet->entry = NULL; sheet->editing = FALSE; + sheet->width = 0; sheet->blocks = g_hash_table_new(block_hash, block_compare); } @@ -1619,12 +1736,12 @@ gnucash_sheet_new (Table *table) /* The entry widgets */ sheet->entry = gtk_entry_new (); gtk_widget_ref(sheet->entry); - gtk_object_sink(GTK_OBJECT(sheet->entry)); + gtk_object_sink(GTK_OBJECT(sheet->entry)); /* set up the editor */ sheet->item_editor = item_edit_new(sheet_group, sheet, sheet->entry); - gnome_canvas_item_hide (GNOME_CANVAS_ITEM(sheet->item_editor)); + gnome_canvas_item_hide (GNOME_CANVAS_ITEM(sheet->item_editor)); widget = GTK_WIDGET (sheet); return widget; @@ -1643,6 +1760,18 @@ gnucash_register_class_init (GnucashRegisterClass *class) table_class = (GtkTableClass *) class; register_parent_class = gtk_type_class (gtk_table_get_type ()); + + register_signals[ACTIVATE_CURSOR] = + gtk_signal_new("activate_cursor", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET(GnucashRegisterClass, + activate_cursor), + gtk_marshal_NONE__NONE, + GTK_TYPE_NONE, 0); + + gtk_object_class_add_signals(object_class, register_signals, + LAST_SIGNAL); } @@ -1685,6 +1814,42 @@ gnucash_register_get_type (void) } +static gint +gnucash_register_key_press_cb(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + GnucashRegister *reg = GNUCASH_REGISTER(data); + + g_return_val_if_fail(widget != NULL, TRUE); + g_return_val_if_fail(GNUCASH_IS_SHEET(widget), TRUE); + g_return_val_if_fail(event != NULL, TRUE); + + switch (event->keyval) { + case GDK_Return: + gtk_signal_emit_by_name(GTK_OBJECT(reg), + "activate_cursor"); + return TRUE; + break; + default: + return FALSE; + } +} + + +void +gnucash_register_attach_popup(GnucashRegister *reg, GtkWidget *popup, + gpointer data) +{ + g_return_if_fail(GNUCASH_IS_REGISTER(reg)); + g_return_if_fail(GTK_IS_WIDGET(popup)); + g_return_if_fail(reg->sheet != NULL); + g_return_if_fail(reg->header_canvas != NULL); + + gnome_popup_menu_attach(popup, reg->sheet, data); + gnome_popup_menu_attach(popup, reg->header_canvas, data); +} + + GtkWidget * gnucash_register_new (Table *table) { @@ -1701,6 +1866,10 @@ gnucash_register_new (Table *table) reg->sheet = sheet; GNUCASH_SHEET(sheet)->reg = widget; + gtk_signal_connect (GTK_OBJECT(sheet), "key_press_event", + GTK_SIGNAL_FUNC(gnucash_register_key_press_cb), + reg); + header_canvas = gnucash_header_new (GNUCASH_SHEET(sheet)); reg->header_canvas = header_canvas; @@ -1725,7 +1894,7 @@ gnucash_register_new (Table *table) reg->vscrollbar = scrollbar; gtk_widget_show(scrollbar); - scrollbar = gtk_hscrollbar_new(NULL); + scrollbar = gtk_hscrollbar_new(GNUCASH_SHEET(sheet)->hadj); gtk_table_attach (GTK_TABLE(widget), GTK_WIDGET(scrollbar), 0, 1, 2, 3, GTK_EXPAND | GTK_SHRINK | GTK_FILL, diff --git a/src/register/gnome/gnucash-sheet.h b/src/register/gnome/gnucash-sheet.h index 6cfe19c9b3..2deb7d2489 100644 --- a/src/register/gnome/gnucash-sheet.h +++ b/src/register/gnome/gnucash-sheet.h @@ -56,6 +56,8 @@ typedef enum { } GnucashSheetAlignment; +typedef struct _CellLayoutInfo CellLayoutInfo; + typedef struct { gint nrows; @@ -75,12 +77,16 @@ typedef struct row/column not displayed */ gint **pixel_widths; - gchar ***labels; + CellLayoutInfo **layout_info; + + gchar ***labels; /* for the header */ + GdkFont *header_font; GtkJustification **alignments; GdkFont ***fonts; + GdkColor ***active_bg_color; GdkColor ***inactive_bg_color; @@ -141,6 +147,8 @@ typedef struct { gint top_block_offset; gint left_block_offset; + gint width; /* the width in pixels of the sheet */ + gint alignment; gint editing; @@ -194,12 +202,10 @@ void gnucash_sheet_cursor_set (GnucashSheet *gsheet, const char * gnucash_sheet_modify_current_cell(GnucashSheet *sheet, const gchar *new_text); -void gnucash_sheet_cursor_set_from_table (GnucashSheet *sheet); +void gnucash_sheet_cursor_set_from_table (GnucashSheet *sheet, + gncBoolean do_scroll); -void gnucash_sheet_move_cursor (GnucashSheet *sheet, - int col, int row); - -int gnucash_sheet_can_move_cursor (GnucashSheet *sheet); +void gnucash_sheet_move_cursor (GnucashSheet *sheet, int col, int row); void gnucash_sheet_set_cursor_bounds (GnucashSheet *sheet, int start_col, int start_row, @@ -211,6 +217,14 @@ void gnucash_sheet_make_cell_visible (GnucashSheet *sheet, gint virt_row, gint virt_col, gint cell_row, gint cell_col); +void gnucash_register_goto_virt_row_col (GnucashRegister *reg, + int v_row, int v_col); + +void gnucash_register_goto_next_virt_row (GnucashRegister *reg); + +void gnucash_register_attach_popup(GnucashRegister *reg, GtkWidget *popup, + gpointer data); + typedef struct { GnomeCanvasClass parent_class; @@ -230,6 +244,9 @@ typedef struct { typedef struct { GtkTableClass parent_class; + + void (*activate_cursor) (GnucashRegister *reg); + } GnucashRegisterClass; #endif diff --git a/src/register/gnome/gnucash-style.c b/src/register/gnome/gnucash-style.c index de1eb13c3b..8cd2c60d25 100644 --- a/src/register/gnome/gnucash-style.c +++ b/src/register/gnome/gnucash-style.c @@ -29,6 +29,70 @@ GdkFont *gnucash_default_font = NULL; GdkFont *gnucash_italic_font = NULL; +#define CHARS_FIXED (1 << 0) /* We use this to set the cell width */ +#define PIXELS_FIXED (1 << 1) /* We use this as the cell width */ +#define RIGHT_ALIGNED (1 << 2) /* Right align with a specified cell */ +#define LEFT_ALIGNED (1 << 3) /* Left align with a specified cell */ +#define FILL (1 << 4) /* Allow this cell to expand to fill */ +#define CHARS_MIN (1 << 5) /* Cell is no smaller than this, + but won't expand further */ +#define PIXELS_MIN (1 << 6) +#define CHARS_MAX (1 << 7) /* Cell is no larger that this, + but won't shrink further */ +#define PIXELS_MAX (1 << 8) +#define STRING_FIXED (1 << 9) /* Fit this string */ +#define STRING_MIN (1 << 10) /* Fit this string */ +#define SAME_SIZE (1 << 11) /* Make the cell the same size + as a specified cell */ + + +struct _CellLayoutInfo +{ + unsigned int flags; + int chars_width; + int pixels_width; + int chars_min; + int pixels_min; + int chars_max; + int pixels_max; + short left_align_r; /* the alignment cells */ + short left_align_c; + short right_align_r; + short right_align_c; + short size_r; /* same size as this cell */ + short size_c; + char *string; +}; + + +#define SET_CELL_PERC(r, n) { \ + for (i=0; icell_perc[i][j] = perc[i][j]; \ +} + + +#define SET_CELL_DATA(r, n) { \ + for (i=0; icell_perc[i][j] = perc[i][j]; \ + style->layout_info[i][j].flags = li[i][j].flags; \ + style->layout_info[i][j].chars_width = li[i][j].chars_width;\ + style->layout_info[i][j].pixels_width = li[i][j].pixels_width;\ + style->layout_info[i][j].chars_min = li[i][j].chars_min;\ + style->layout_info[i][j].pixels_min = li[i][j].pixels_min;\ + style->layout_info[i][j].chars_max = li[i][j].chars_max;\ + style->layout_info[i][j].pixels_max = li[i][j].pixels_max;\ + style->layout_info[i][j].right_align_r = li[i][j].right_align_r;\ + style->layout_info[i][j].right_align_c = li[i][j].right_align_c;\ + style->layout_info[i][j].left_align_r = li[i][j].left_align_r;\ + style->layout_info[i][j].left_align_c = li[i][j].left_align_c; \ + style->layout_info[i][j].size_r = li[i][j].size_r; \ + style->layout_info[i][j].size_c = li[i][j].size_c; \ + style->layout_info[i][j].string = g_strdup(li[i][j].string); \ + } \ +} + /* FIXME: read this from a config file */ /* keep this in sync with splitreg.c */ void @@ -49,53 +113,89 @@ gnucash_style_layout_init (SheetBlockStyle *style) switch (style->cursor_type) { case GNUCASH_CURSOR_HEADER: case GNUCASH_CURSOR_SINGLE: - style->cell_perc [0][0] = 0.10; - style->cell_perc [0][1] = 0.07; - style->cell_perc [0][2] = 0.15; - style->cell_perc [0][3] = 0.30; - style->cell_perc [0][4] = 0.02; - style->cell_perc [0][5] = 0.12; - style->cell_perc [0][6] = 0.12; - style->cell_perc [0][7] = 0.12; + { + int i, j; + double perc[1][8] = {{0.10, 0.07, 0.15, 0.30, 0.02, 0.12, 0.12, 0.12}}; + CellLayoutInfo li[1][8] = + {{{STRING_FIXED, 0, 0,0,0,0,0,0,0,0,0, 0, 0, " 88/88/8888"}, + {CHARS_MIN | CHARS_MAX, 0, 0, 3, 0, 5,0,0,0,0,0, 0, 0, NULL}, + {STRING_MIN | CHARS_MAX | FILL, 0, 0, 0 ,0, 10 ,0,0,0,0,0, 0, 0, "Transfer From"}, + {CHARS_MIN | FILL, 0, 0,20,0,0,0,0,0,0,0, 0,0, NULL}, + {STRING_FIXED, 1, 0,0,0,0,0,0,0,0,0, 0, 0, "R"}, + {CHARS_MIN | CHARS_MAX | FILL, 0, 0, 9,0, 10,0,0,0,0,0, 0, 0, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + }}; + + SET_CELL_DATA (1, 8); + } + break; case GNUCASH_CURSOR_DOUBLE: - style->cell_perc [0][0] = 0.10; - style->cell_perc [0][1] = 0.07; - style->cell_perc [0][2] = 0.15; - style->cell_perc [0][3] = 0.30; - style->cell_perc [0][4] = 0.02; - style->cell_perc [0][5] = 0.12; - style->cell_perc [0][6] = 0.12; - style->cell_perc [0][7] = 0.12; - - style->cell_perc [1][0] = 0.10; - style->cell_perc [1][1] = 0.07; - style->cell_perc [1][2] = 0.15; - style->cell_perc [1][3] = 0.68; - style->cell_perc [1][4] = 0.0; - style->cell_perc [1][5] = 0.0; - style->cell_perc [1][6] = 0.0; - style->cell_perc [1][7] = 0.0; + { + int i, j; + double perc[2][8] = {{0.10, 0.07, 0.15, 0.30, 0.02, 0.12, 0.12, 0.12}, + {0.10, 0.07, 0.15, 0.68, 0.0, 0.0, 0.0, 0.0}}; + CellLayoutInfo li[2][8] = + {{{STRING_FIXED, 0, 0,0,0,0,0,0,0,0,0, 0, 0, " 88/88/8888"}, + {CHARS_MIN | CHARS_MAX, 0, 0, 3, 0, 5,0,0,0,0,0, 0, 0, NULL}, + {STRING_MIN | CHARS_MAX | FILL, 0, 0, 0 ,0, 10 ,0,0,0,0,0, 0, 0, "Transfer From"}, + {CHARS_MIN | FILL, 0, 0,20,0,0,0,0,0,0,0, 0,0, NULL}, + {STRING_FIXED, 1, 0,0,0,0,0,0,0,0,0, 0, 0, "R"}, + {CHARS_MIN | CHARS_MAX | FILL, 0, 0, 9,0, 10,0,0,0,0,0, 0, 0, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}}, + {{LEFT_ALIGNED|RIGHT_ALIGNED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {LEFT_ALIGNED|RIGHT_ALIGNED, 0, 0, 0,0,0,0,0,1,0,1, 0,0, NULL}, + {LEFT_ALIGNED|RIGHT_ALIGNED, 0, 0, 0,0,0,0,0,2,0,2, 0,0, NULL}, + {LEFT_ALIGNED|RIGHT_ALIGNED, 0, 0, 0,0,0,0,0,3,0,7, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + }}; + + SET_CELL_DATA (2, 8); + } + break; case GNUCASH_CURSOR_TRANS: - style->cell_perc [0][0] = 0.10; - style->cell_perc [0][1] = 0.07; - style->cell_perc [0][2] = 0.15; - style->cell_perc [0][3] = 0.30; - style->cell_perc [0][4] = 0.02; - style->cell_perc [0][5] = 0.12; - style->cell_perc [0][6] = 0.12; - style->cell_perc [0][7] = 0.12; + { + int i, j; + double perc[1][8] = {{0.10, 0.07, 0.15, 0.30, 0.02, 0.12, 0.12, 0.12}}; + CellLayoutInfo li[1][8] = + {{{STRING_FIXED, 0, 0,0,0,0,0,0,0,0,0, 0, 0, " 88/88/8888"}, + {CHARS_MIN | CHARS_MAX, 0, 0, 3, 0, 5,0,0,0,0,0, 0, 0, NULL}, + {STRING_MIN | CHARS_MAX | FILL, 0, 0, 0 ,0, 10 ,0,0,0,0,0, 0, 0, "Transfer From"}, + {CHARS_MIN | FILL, 0, 0,20,0,0,0,0,0,0,0, 0,0, NULL}, + {STRING_FIXED, 1, 0,0,0,0,0,0,0,0,0, 0, 0, "R"}, + {CHARS_MIN | CHARS_MAX | FILL, 0, 0, 9,0, 10,0,0,0,0,0, 0, 0, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + }}; + + SET_CELL_DATA (1, 8); + } + break; case GNUCASH_CURSOR_SPLIT: - style->cell_perc [0][0] = 0.10; - style->cell_perc [0][1] = 0.07; - style->cell_perc [0][2] = 0.15; - style->cell_perc [0][3] = 0.32; - style->cell_perc [0][4] = 0.0; - style->cell_perc [0][5] = 0.12; - style->cell_perc [0][6] = 0.12; - style->cell_perc [0][7] = 0.12; + { + int i, j; + double perc[1][8] = {{0.10, 0.07, 0.15, 0.30, 0.02, 0.12, 0.12, 0.12}}; + CellLayoutInfo li[1][8] = + {{{STRING_FIXED, 0, 0,0,0,0,0,0,0,0,0, 0, 0, " 88/88/8888"}, + {CHARS_MIN | CHARS_MAX, 0, 0, 3, 0, 5,0,0,0,0,0, 0, 0, NULL}, + {STRING_MIN | CHARS_MAX | FILL, 0, 0, 0 ,0, 10 ,0,0,0,0,0, 0, 0, "Transfer From"}, + {CHARS_MIN | FILL, 0, 0,20,0,0,0,0,0,0,0, 0,0, NULL}, + {STRING_FIXED, 1, 0,0,0,0,0,0,0,0,0, 0, 0, "R"}, + {CHARS_MIN | CHARS_MAX | FILL, 0, 0, 9,0, 10,0,0,0,0,0, 0, 0, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + }}; + + SET_CELL_DATA (1, 8); + } + break; default: break; @@ -108,57 +208,104 @@ gnucash_style_layout_init (SheetBlockStyle *style) { case GNUCASH_CURSOR_HEADER: case GNUCASH_CURSOR_SINGLE: - style->cell_perc [0][0] = 0.09; - style->cell_perc [0][1] = 0.06; - style->cell_perc [0][2] = 0.11; - style->cell_perc [0][3] = 0.23; - style->cell_perc [0][4] = 0.01; - style->cell_perc [0][5] = 0.10; - style->cell_perc [0][6] = 0.10; - style->cell_perc [0][7] = 0.07; - style->cell_perc [0][8] = 0.07; - style->cell_perc [0][9] = 0.07; - style->cell_perc [0][10] = 0.09; - break; - case GNUCASH_CURSOR_DOUBLE: - style->cell_perc [0][0] = 0.09; - style->cell_perc [0][1] = 0.06; - style->cell_perc [0][2] = 0.11; - style->cell_perc [0][3] = 0.23; - style->cell_perc [0][4] = 0.01; - style->cell_perc [0][5] = 0.10; - style->cell_perc [0][6] = 0.10; - style->cell_perc [0][7] = 0.07; - style->cell_perc [0][8] = 0.07; - style->cell_perc [0][9] = 0.07; - style->cell_perc [0][10] = 0.09; + { + int i, j; + double perc[1][11] = {{0.09, 0.06, 0.11, 0.23, 0.01, 0.10, 0.10, 0.07, 0.07, 0.07, 0.09}}; + CellLayoutInfo li[1][11] = + {{{STRING_FIXED, 0, 0,0,0,0,0,0,0,0,0, 0, 0, " 88/88/8888"}, + {CHARS_MIN | CHARS_MAX, 0, 0, 3, 0, 5,0,0,0,0,0, 0, 0, NULL}, + {STRING_MIN | CHARS_MAX | FILL, 0, 0, 0 ,0, 10 ,0,0,0,0,0, 0, 0, "Transfer From"}, + {CHARS_MIN | FILL, 0, 0,20,0,0,0,0,0,0,0, 0,0, NULL}, + {STRING_FIXED, 1, 0,0,0,0,0,0,0,0,0, 0, 0, "R"}, + {CHARS_MIN | CHARS_MAX | FILL, 0, 0, 9,0, 10,0,0,0,0,0, 0, 0, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + }}; + + SET_CELL_DATA (1, 11); + } + + break; + case GNUCASH_CURSOR_DOUBLE: + { + int i, j; + double perc[2][11] = {{0.09, 0.06, 0.11, 0.23, 0.01, 0.10, 0.10, 0.07, 0.07, 0.07, 0.09}, + {0.0, 0.15, 0.11, 0.74, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}}; + CellLayoutInfo li[2][11] = + {{{STRING_FIXED, 0, 0,0,0,0,0,0,0,0,0, 0, 0, " 88/88/8888"}, + {CHARS_MIN | CHARS_MAX, 0, 0, 3, 0, 5,0,0,0,0,0, 0, 0, NULL}, + {STRING_MIN | CHARS_MAX | FILL, 0, 0, 0 ,0, 10 ,0,0,0,0,0, 0, 0, "Transfer From"}, + {CHARS_MIN | FILL, 0, 0,20,0,0,0,0,0,0,0, 0,0, NULL}, + {STRING_FIXED, 1, 0,0,0,0,0,0,0,0,0, 0, 0, "R"}, + {CHARS_MIN | CHARS_MAX | FILL, 0, 0, 9,0, 10,0,0,0,0,0, 0, 0, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}}, + {{LEFT_ALIGNED|RIGHT_ALIGNED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {LEFT_ALIGNED|RIGHT_ALIGNED, 0, 0, 0,0,0,0,0,1,0,1, 0,0, NULL}, + {LEFT_ALIGNED|RIGHT_ALIGNED, 0, 0, 0,0,0,0,0,2,0,2, 0,0, NULL}, + {LEFT_ALIGNED|RIGHT_ALIGNED, 0, 0, 0,0,0,0,0,3,0,10, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + {PIXELS_FIXED, 0, 0, 0,0,0,0,0,0,0,0, 0,0, NULL}, + }}; + + SET_CELL_DATA (2, 11); + } - style->cell_perc [1][1] = 0.15; - style->cell_perc [1][2] = 0.11; - style->cell_perc [1][3] = 0.74; break; case GNUCASH_CURSOR_TRANS: - style->cell_perc [0][0] = 0.09; - style->cell_perc [0][1] = 0.06; - style->cell_perc [0][2] = 0.11; - style->cell_perc [0][3] = 0.23; - style->cell_perc [0][4] = 0.01; - style->cell_perc [0][5] = 0.10; - style->cell_perc [0][6] = 0.10; - style->cell_perc [0][7] = 0.07; - style->cell_perc [0][8] = 0.07; - style->cell_perc [0][9] = 0.07; - style->cell_perc [0][10] = 0.09; + { + int i, j; + double perc[1][11] = {{0.09, 0.06, 0.11, 0.23, 0.01, 0.10, 0.10, 0.07, 0.07, 0.07, 0.09}}; + CellLayoutInfo li[1][11] = + {{{STRING_FIXED, 0, 0,0,0,0,0,0,0,0,0, 0, 0, " 88/88/8888"}, + {CHARS_MIN | CHARS_MAX, 0, 0, 3, 0, 5,0,0,0,0,0, 0, 0, NULL}, + {STRING_MIN | CHARS_MAX | FILL, 0, 0, 0 ,0, 10 ,0,0,0,0,0, 0, 0, "Transfer From"}, + {CHARS_MIN | FILL, 0, 0,20,0,0,0,0,0,0,0, 0,0, NULL}, + {STRING_FIXED, 1, 0,0,0,0,0,0,0,0,0, 0, 0, "R"}, + {CHARS_MIN | CHARS_MAX | FILL, 0, 0, 9,0, 10,0,0,0,0,0, 0, 0, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + }}; + + SET_CELL_DATA (1, 11); + } + break; case GNUCASH_CURSOR_SPLIT: - style->cell_perc [0][0] = 0.0; - style->cell_perc [0][1] = 0.15; - style->cell_perc [0][2] = 0.11; - style->cell_perc [0][3] = 0.24; - style->cell_perc [0][4] = 0.0; - style->cell_perc [0][5] = 0.10; - style->cell_perc [0][6] = 0.10; - style->cell_perc [0][7] = 0.36; + { + int i, j; + double perc[1][11] = {{0.0, 0.15, 0.11, 0.24, 0.0, 0.10, 0.10, 0.36, 0.0, 0.0, 0.0}}; + CellLayoutInfo li[1][11] = + {{{STRING_FIXED, 0, 0,0,0,0,0,0,0,0,0, 0, 0, " 88/88/8888"}, + {CHARS_MIN | CHARS_MAX, 0, 0, 3, 0, 5,0,0,0,0,0, 0, 0, NULL}, + {STRING_MIN | CHARS_MAX | FILL, 0, 0, 0 ,0, 10 ,0,0,0,0,0, 0, 0, "Transfer From"}, + {CHARS_MIN | FILL, 0, 0,20,0,0,0,0,0,0,0, 0,0, NULL}, + {STRING_FIXED, 1, 0,0,0,0,0,0,0,0,0, 0, 0, "R"}, + {CHARS_MIN | CHARS_MAX | FILL, 0, 0, 9,0, 10,0,0,0,0,0, 0, 0, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + {SAME_SIZE, 0, 0, 0,0,0,0,0,0,0,0, 0,5, NULL}, + }}; + + SET_CELL_DATA (1, 11); + } + break; } default: @@ -166,12 +313,289 @@ gnucash_style_layout_init (SheetBlockStyle *style) } } +static int +compute_row_width (SheetBlockStyle *style, int row, int col1, int col2) +{ + int j; + int width = 0; + + col1 = MAX(0, col1); + col2 = MIN(col2, style->ncols-1); + + for (j = col1; j <= col2; j++) + width += style->pixel_widths[row][j]; + + return width; +} + + +/* + * This sets the initial sizes of the cells, based on cell_perc's and the + * width allocation of the sheet + */ +static void +set_dimensions_pass_one (GnucashSheet *sheet, SheetBlockStyle *style, int row) +{ + int i = row, j; + + for (j = 0; j < style->ncols; j++) { + style->pixel_widths[i][j] = style->cell_perc[i][j] * style->width + 0.5; + style->pixel_heights[i][j] = + style->fonts[i][j]->ascent + + style->fonts[i][j]->descent + + 2*CELL_VPADDING; + } + style->height += style->pixel_heights[i][0]; +} + +#define C_WIDTH gdk_string_measure (style->fonts[i][j], "X") + + +/* Now we add/subtract what's required to/from individual cells, + * taking into account min/max char/pixel/string widths + * + * At the end we set all SAME_SIZE cells. + */ +static void +set_dimensions_pass_two (GnucashSheet *sheet, SheetBlockStyle *style, int row) +{ + int i = row, j; + unsigned int flags; + + for (j = 0; j < style->ncols; j++) { + flags = style->layout_info[i][j].flags; + + if (flags & CHARS_FIXED) { + style->pixel_widths[i][j] = + style->layout_info[i][j].chars_width * C_WIDTH + 2*CELL_HPADDING; + style->layout_info[i][j].pixels_width = style->pixel_widths[i][j]; + style->layout_info[i][j].flags |= PIXELS_FIXED; + } + else if (flags & PIXELS_FIXED) { + style->pixel_widths[i][j] = style->layout_info[i][j].pixels_width; + } + else if ((flags & STRING_FIXED) && style->layout_info[i][j].string){ + style->pixel_widths[i][j] = + gdk_string_measure (style->fonts[i][j], style->layout_info[i][j].string) + 2*CELL_HPADDING; + style->layout_info[i][j].pixels_width = style->pixel_widths[i][j]; + style->layout_info[i][j].flags |= PIXELS_FIXED; + } + + + if (flags & CHARS_MIN) { + style->layout_info[i][j].pixels_min = + style->layout_info[i][j].chars_min * C_WIDTH+ 2*CELL_HPADDING; + style->layout_info[i][j].flags |= PIXELS_MIN; + + style->pixel_widths[i][j] = + MAX (style->layout_info[i][j].pixels_min, style->pixel_widths[i][j]); + } + else if (flags & PIXELS_MIN) { + style->pixel_widths[i][j] = + MAX (style->layout_info[i][j].pixels_min, style->pixel_widths[i][j]); + } + else if ((flags & STRING_MIN) && style->layout_info[i][j].string) { + style->layout_info[i][j].pixels_min = + gdk_string_measure (style->fonts[i][j], style->layout_info[i][j].string)+ 2*CELL_HPADDING; + + style->layout_info[i][j].flags |= PIXELS_MIN; + style->pixel_widths[i][j] = + MAX (style->layout_info[i][j].pixels_min, style->pixel_widths[i][j]); + } + + + if (flags & CHARS_MAX) { + style->layout_info[i][j].pixels_max = + style->layout_info[i][j].chars_max * C_WIDTH+ 2*CELL_HPADDING; + style->layout_info[i][j].flags |= PIXELS_MAX; + + style->pixel_widths[i][j] = + MIN (style->layout_info[i][j].pixels_max, style->pixel_widths[i][j]); + } + else if (flags & PIXELS_MAX) { + style->pixel_widths[i][j] = + MIN (style->layout_info[i][j].pixels_max, style->pixel_widths[i][j]); + } + } + + for (j = 0; j < style->ncols; j++) + if (style->layout_info[i][j].flags & SAME_SIZE) { + int r = style->layout_info[i][j].size_r; + int c = style->layout_info[i][j].size_c; + style->pixel_widths[i][j] = style->pixel_widths[r][c]; + } +} + + +/* This is a subcase of pass_three: we have a very small amount of + * space to reallocate; let's just put it in the largest FILL cell, + * and damn the consequences; in the wierd case that there is no FILL + * cell at all, just stick it in the largest cell. + */ +static void +set_dimensions_plan_b (GnucashSheet *sheet, SheetBlockStyle *style, + int row, int space) +{ + int i = row, j; + int fill_col = -1; + int fill_w = 0; + int col = 0; + int w = 0; + + for (j = 0; j < style->ncols; j++) { + + if (style->layout_info[i][j].flags & FILL) + if (fill_w < style->pixel_widths[i][j]) { + fill_col = j; + fill_w = style->pixel_widths[i][j]; + } + + if (w < style->pixel_widths[i][j]) { + col = j; + w = style->pixel_widths[i][j]; + } + } + + if (fill_col > -1) + style->pixel_widths[i][fill_col] -= space; + else + style->pixel_widths[i][col] -= space; +} + + + +/* OK, this is the tricky one: we need to add/subtract left over or + * excess space from the FILL cells. We'll try to adjust each one + * the same amount, but this is subject to any _MIN/_MAX + * restrictions, and we need to be careful with SAME_SIZE cells, too. + * + */ +static void +set_dimensions_pass_three (GnucashSheet *sheet, SheetBlockStyle *style, + int row, int ideal_width) +{ + int i = row, j; + int w = 0; + int space = compute_row_width (style, row, 0, style->ncols-1) - ideal_width; + int cellspace; + int nfills; + + int *adjustments; + int *done; + + adjustments = g_new0 (int, style->ncols); + done = g_new0 (int, style->ncols); + + while (space != 0) { + + nfills = 0; + /* count the number of fill cells we have left to work with */ + for (j = 0; j < style->ncols; j++) { + if ( (style->layout_info[i][j].flags & FILL) && !done[j]) + nfills++; + else if (style->layout_info[i][j].flags & SAME_SIZE) { + int r = style->layout_info[i][j].size_r; + int c = style->layout_info[i][j].size_c; + if ((style->layout_info[r][c].flags & FILL) && !done[c]) + nfills++; + } + } + + /* no more place to put/take away extra space; give up */ + if (nfills == 0) + break; + + + /* We take care of small amounts of space in a special case */ + if ( (space < 0 ? -1 : 1)*space < nfills) { + set_dimensions_plan_b (sheet, style, row, space); + break; + } + + + /* this is how much we should ideally adjust each cell */ + cellspace = space/nfills; + + /* loop through again and do the best we can */ + for (j = 0; j < style->ncols; j++) + if ((style->layout_info[i][j].flags & FILL) && !done[j]) { + if (cellspace > 0) { + if (style->layout_info[i][j].flags & PIXELS_MIN) { + int allowed = style->pixel_widths[i][j] + - style->layout_info[i][j].pixels_min; + adjustments[j] = MIN (allowed, cellspace); + done[j] = (allowed < cellspace); + } + else adjustments[j] = cellspace; + space -= adjustments[j]; + } + else { + if (style->layout_info[i][j].flags & PIXELS_MAX) { + int allowed = style->pixel_widths[i][j] + - style->layout_info[i][j].pixels_min; + adjustments[j] = MAX (allowed, cellspace); + done[j] = (allowed >cellspace); + } + else adjustments[j] = cellspace; + space -= adjustments[j]; + } + } + + /* adjust everything, including SAME_SIZE cells */ + for (j = 0; j < style->ncols; j++) { + if (style->layout_info[i][j].flags & SAME_SIZE) { + int r = style->layout_info[i][j].size_r; + int c = style->layout_info[i][j].size_c; + style->pixel_widths[i][j] -= adjustments[c]; + space -= adjustments[c]; + } + style->pixel_widths[i][j] -= adjustments[j]; + } + + for (j = 0; j < style->ncols; j++) + adjustments[j] = 0; + } + + /* clean up */ + g_free (adjustments); + g_free (done); +} + + +static void +set_dimensions_aligned (GnucashSheet *sheet, SheetBlockStyle *style, int row) +{ + int i = row, j; + int width; + int c1, c2; + int r; + + for (j = 0; j < style->ncols; j++) { + + if (!(style->layout_info[i][j].flags & (LEFT_ALIGNED | RIGHT_ALIGNED))) { + style->layout_info[i][j].pixels_max = 0; + style->layout_info[i][j].flags |= PIXELS_FIXED; + style->pixel_widths[i][j] = 0; + } + else { + r = style->layout_info[i][j].right_align_r; + c1 = style->layout_info[i][j].left_align_c; + c2 = style->layout_info[i][j].right_align_c; + + style->layout_info[i][j].pixels_width = compute_row_width (style, r, c1, c2); + style->layout_info[i][j].flags |= PIXELS_FIXED; + style->pixel_widths[i][j] = style->layout_info[i][j].pixels_width; + } + } +} + void gnucash_sheet_style_set_dimensions (GnucashSheet *sheet, SheetBlockStyle *style) { - gint i, j; + int i; + int ideal_width; g_return_if_fail (sheet != NULL); g_return_if_fail (GNUCASH_IS_SHEET (sheet)); @@ -179,18 +603,20 @@ gnucash_sheet_style_set_dimensions (GnucashSheet *sheet, style->height = 0; style->width = GTK_WIDGET (sheet)->allocation.width; + ideal_width = style->width; - for (i = 0; i < style->nrows; i++) { - for (j = 0; j < style->ncols; j++) { - style->pixel_widths[i][j] = - style->cell_perc[i][j] * style->width + 0.5; + /* First set the top rows */ + set_dimensions_pass_one (sheet, style, 0); + set_dimensions_pass_two (sheet, style, 0); + set_dimensions_pass_three (sheet, style, 0, ideal_width); - style->pixel_heights[i][j] = - style->fonts[i][j]->ascent + - style->fonts[i][j]->descent + - 2*CELL_VPADDING; - } - style->height += style->pixel_heights[i][0]; + /* style->width is fixed now */ + style->width = compute_row_width (style, 0, 0, style->ncols-1); + + /* For now, let's treat the other rows as ALIGNED_ cells only */ + for (i = 1; i < style->nrows; i++) { + set_dimensions_pass_one (sheet, style, i); + set_dimensions_aligned (sheet, style, i); } } @@ -206,6 +632,9 @@ gnucash_sheet_style_destroy (SheetBlockStyle *style) g_free(style->widths[i]); g_free(style->pixel_heights[i]); g_free(style->pixel_widths[i]); + for (j = 0; j < style->ncols; j++) + g_free (style->layout_info[i][j].string); + g_free(style->layout_info[i]); g_free(style->alignments[i]); g_free(style->fonts[i]); g_free(style->active_bg_color[i]); @@ -219,6 +648,7 @@ gnucash_sheet_style_destroy (SheetBlockStyle *style) g_free(style->widths); g_free(style->pixel_heights); g_free(style->pixel_widths); + g_free(style->layout_info); g_free(style->alignments); g_free(style->fonts); g_free(style->active_bg_color); @@ -255,6 +685,7 @@ gnucash_sheet_style_compile (GnucashSheet *sheet, CellBlock *cellblock, style->widths = g_new0(gint *, cellblock->numRows); style->pixel_heights = g_new0(gint *, cellblock->numRows); style->pixel_widths = g_new0 (gint *, cellblock->numRows); + style->layout_info = g_new0 (CellLayoutInfo *, cellblock->numRows); style->alignments = g_new0 (GtkJustification *, cellblock->numRows); style->fonts = g_new0 (GdkFont **, cellblock->numRows); style->active_bg_color = g_new0 (GdkColor **, cellblock->numRows); @@ -266,6 +697,7 @@ gnucash_sheet_style_compile (GnucashSheet *sheet, CellBlock *cellblock, style->widths[i] = g_new0(gint, style->ncols); style->pixel_heights[i] = g_new0(gint , style->ncols); style->pixel_widths[i] = g_new0 (gint, style->ncols); + style->layout_info[i] = g_new0 (CellLayoutInfo, style->ncols); style->alignments[i] = g_new0 (GtkJustification, style->ncols); style->fonts[i] = g_new0 (GdkFont *, style->ncols); style->active_bg_color[i] = g_new0 (GdkColor *, style->ncols); @@ -279,27 +711,28 @@ gnucash_sheet_style_compile (GnucashSheet *sheet, CellBlock *cellblock, for (i = 0; i < style->nrows; i++) for (j = 0; j < style->ncols; j++) { gint type = cellblock->cell_types[i][j]; - char *label; + char *label; style->widths[i][j] = cellblock->widths[j]; style->fonts[i][j] = gnucash_default_font; + style->header_font = gnucash_default_font; if (type > -1) label = sr->header_label_cells[type]->value; - else if (cursor_type == GNUCASH_CURSOR_HEADER) - label = cellblock->cells[i][j]->value; - else - label = ""; - - style->labels[i][j] = g_strdup(label); - + else if (cursor_type == GNUCASH_CURSOR_HEADER) + label = cellblock->cells[i][j]->value; + else + label = ""; + + style->labels[i][j] = g_strdup(label); + style->active_bg_color[i][j] = gnucash_color_argb_to_gdk (cellblock->active_bg_color); style->inactive_bg_color[i][j] = gnucash_color_argb_to_gdk (cellblock->passive_bg_color); - + switch (cellblock->alignments[j]) { case ALIGN_RIGHT: - style->alignments[ i] [j] = GTK_JUSTIFY_RIGHT; + style->alignments[i][j] = GTK_JUSTIFY_RIGHT; break; case ALIGN_CENTER: style->alignments[i][j] = GTK_JUSTIFY_CENTER; diff --git a/src/register/pricecell.c b/src/register/pricecell.c index b4d41c920f..96ef9ff9be 100644 --- a/src/register/pricecell.c +++ b/src/register/pricecell.c @@ -38,6 +38,7 @@ #define VERY_SMALL (1.0e-20) static void PriceSetValue (BasicCell *, const char *); +static char * xaccPriceCellPrintValue (PriceCell *cell); /* set the color of the text to red, if teh value is negative */ /* hack alert -- the actual color should probably be configurable */ @@ -66,7 +67,8 @@ static const char * PriceMV (BasicCell *_cell, const char * oldval, const char *change, - const char *newval) + const char *newval, + int *cursor_position) { PriceCell *cell = (PriceCell *) _cell; @@ -93,6 +95,25 @@ PriceMV (BasicCell *_cell, /* ================================================ */ +static const char * +PriceLeave (BasicCell *_cell, const char *val) +{ + PriceCell *cell = (PriceCell *) _cell; + double amount; + char *newval; + + newval = xaccPriceCellPrintValue(cell); + + /* If they are identical, return the original */ + if (strcmp(newval, val) == 0) + return val; + + /* Otherwise, return the new one. */ + return strdup(newval); +} + +/* ================================================ */ + PriceCell * xaccMallocPriceCell (void) { @@ -117,6 +138,7 @@ xaccInitPriceCell (PriceCell *cell) cell->cell.use_fg_color = 1; cell->cell.modify_verify = PriceMV; + cell->cell.leave_cell = PriceLeave; cell->cell.set_value = PriceSetValue; } @@ -132,35 +154,48 @@ xaccDestroyPriceCell (PriceCell *cell) /* ================================================ */ +static char * +xaccPriceCellPrintValue (PriceCell *cell) +{ + static char buff[PRTBUF]; + char tmpfmt[PRTBUF]; + char tmpval[PRTBUF]; + char *monet; + + if (cell->blank_zero && + (VERY_SMALL > cell->amount) && ((-VERY_SMALL) < cell->amount)) { + + strcpy(buff, ""); + return buff; + } + + /* check for monetary-style format not natively supported by printf */ + /* hack alert -- this type of extended function should be abstracted + * out to a gnc_sprintf type function, however, this is much + * easier said than done */ + monet = strstr (cell->prt_format, "%m"); + if (monet) { + strcpy (tmpfmt, cell->prt_format); + monet = strstr (tmpfmt, "%m"); + *(monet+1) = 's'; + xaccSPrintAmount (tmpval, cell->amount, PRTSEP); + snprintf (buff, PRTBUF, tmpfmt, tmpval); + } else { + snprintf (buff, PRTBUF, cell->prt_format, cell->amount); + } + + return buff; +} + +/* ================================================ */ + void xaccSetPriceCellValue (PriceCell * cell, double amt) { - char buff[PRTBUF]; + char *buff; + cell->amount = amt; + buff = xaccPriceCellPrintValue (cell); - /* if amount is zero, and blanking is set, then print blank */ - if (cell->blank_zero && (VERY_SMALL > amt) && ((-VERY_SMALL) < amt)) { - buff[0] = 0x0; - } else { - char *monet; - - /* check for monetary-style format not natively supported by printf */ - /* hack alert -- this type of extended function should be abstracted - * out to a gnc_sprintf type function, however, this is much - * easier said than done */ - monet = strstr (cell->prt_format, "%m"); - if (monet) { - char tmpfmt[PRTBUF]; - char tmpval[PRTBUF]; - strcpy (tmpfmt, cell->prt_format); - monet = strstr (tmpfmt, "%m"); - *(monet+1) = 's'; - xaccSPrintAmount (tmpval, amt, PRTSEP); - - snprintf (buff, PRTBUF, tmpfmt, tmpval); - } else { - snprintf (buff, PRTBUF, cell->prt_format, amt); - } - } SET ( &(cell->cell), buff); /* set the cell color to red if the value is negative */ @@ -183,21 +218,20 @@ void xaccSetPriceCellFormat (PriceCell * cell, char * fmt) void xaccSetDebCredCellValue (PriceCell * deb, PriceCell * cred, double amt) { - deb->amount = -amt; - cred->amount = amt; - deb->cell.fg_color = 0xff0000; cred->cell.fg_color = 0x0; - if (cred->blank_zero && (VERY_SMALL > amt) && ((-VERY_SMALL) < amt)) { - SET ( &(cred->cell), ""); - SET ( &(deb->cell), ""); - } else if (0.0 < amt) { xaccSetPriceCellValue (cred, amt); - SET ( &(deb->cell), ""); + xaccSetPriceCellValue (deb, 0.0); + if (!deb->blank_zero) { + SET(&deb->cell, ""); + } } else { - SET ( &(cred->cell), ""); + xaccSetPriceCellValue (cred, 0.0); + if (!cred->blank_zero) { + SET(&deb->cell, ""); + } xaccSetPriceCellValue (deb, -amt); } } diff --git a/src/register/quickfillcell.c b/src/register/quickfillcell.c index 5f928053e8..fc3fdebe69 100644 --- a/src/register/quickfillcell.c +++ b/src/register/quickfillcell.c @@ -64,44 +64,105 @@ quick_enter (BasicCell *_cell, const char *val) static const char * quick_modify (BasicCell *_cell, - const char *oldval, - const char *change, - const char *newval) + const char *oldval, + const char *change, + const char *newval, + int *cursor_position) { + /* The modifications to fix up completion cursor positioning and + * stale match handling below are somewhat tortured. I suspect we + * can do better, but this works for now... */ + QuickFillCell *cell = (QuickFillCell *) _cell; - char * retval = (char *) newval; + QuickFill *initial_match_point = cell->qf; + const char * retval = newval; /* if user typed the very first letter into this * cell, then make sure that the quick-fill is set to * the root. Alternately, if user erased all of the * text in the cell, and has just started typing, - * then make sure that the quick-fill root is also reset - */ + * then make sure that the quick-fill root is also reset */ if (newval) { - if ((0x0 != newval[0]) && (0x0 == newval[1])) { + if (change && (0x0 != newval[0]) && (0x0 == newval[1])) { + /* the first char is being inserted */ + cell->qf = cell->qfRoot; + } else if('\0' == newval[0]) { + /* the last char is being deleted */ cell->qf = cell->qfRoot; } } /* if change is null, then user is deleting text; - * otehrwise, they are inserting text. */ + * otherwise, they are inserting text. */ if (change) { int i; char c; + int previous_match_len = 0; /* search for best-matching quick-fill string */ i=0; c = change[i]; - while (c) { + while (c && cell->qf) { cell->qf = xaccGetQuickFill (cell->qf, c); i++; c = change[i]; } - /* if a match found, return it */ - if (cell->qf) retval = strdup (cell->qf->text); - } + /* Figure out how long the previous match was. This allows us + * to position the cursor incrementally, or DTRT when the user + * diverges from a quickfill match. */ + { + QuickFill *qcursor = cell->qfRoot; + while(qcursor && (qcursor != initial_match_point)) { + qcursor = xaccGetQuickFill (qcursor, oldval[previous_match_len++]); + } + } + if (cell->qf) { + /* we have a match, return it and reposition the cursor */ + *cursor_position = previous_match_len + strlen(change); + retval = strdup (cell->qf->text); + } else { + /* do some research to figure out if the failure is because + * the current change just diverged from a match, in which + * case we should return everything that matched before this + * insertion plus the new change, or because the oldval hasn't + * matched for a while (i.e. we're no longer quickfilling), in + * which case we should just return the suggested newval. */ + + int oldval_match_len = 0; + + /* find out how much of oldval is a quickfill entry. */ + { + QuickFill *qcursor = cell->qfRoot; + while(qcursor) { + qcursor = xaccGetQuickFill (qcursor, oldval[oldval_match_len]); + oldval_match_len++; + } + oldval_match_len--; + } + + if(oldval_match_len == strlen(oldval)) { + /* If oldval was quick-fill match in its entirety, then the + * current "change" is the first divergence. Accordingly, + * we need to remove any "post-divergence", stale quickfill + * bits that reside after the cursor and then append the new + * change text. */ + + char *unmatched = + (char *) malloc(previous_match_len + strlen(change) + 1); + + strncpy(unmatched, oldval, previous_match_len); + unmatched[previous_match_len] = '\0'; + strcat(unmatched, change); + *cursor_position = strlen(unmatched); + + retval = unmatched; + + } + } + } + SET (&(cell->cell), retval); return retval; } diff --git a/src/register/splitreg.c b/src/register/splitreg.c index a4c24ffc79..c05ce073b9 100644 --- a/src/register/splitreg.c +++ b/src/register/splitreg.c @@ -50,7 +50,6 @@ static short module = MOD_REGISTER; #define RECN_CELL 3 #define SHRS_CELL 4 #define BALN_CELL 5 - #define ACTN_CELL 6 #define XFRM_CELL 7 #define XTO_CELL 8 @@ -105,6 +104,13 @@ static short module = MOD_REGISTER; #define SHRS_CELL_ALIGN ALIGN_RIGHT #define BALN_CELL_ALIGN ALIGN_RIGHT +/* Use 4 decimal places for everything that isn't a $ value + * Perhaps this should be changed to store precision selection + * in a config file. */ +#define SHRS_CELL_FORMAT "%0.4f" +#define PRICE_CELL_FORMAT "%0.4f" +#define DEBIT_CELL_FORMAT "%0.4f" +#define CREDIT_CELL_FORMAT "%0.4f" /* ============================================== */ @@ -128,20 +134,19 @@ configLabels (SplitRegister *reg) LABEL (DATE, DATE_STR); LABEL (NUM, NUM_STR); - LABEL (ACTN, NUM_STR); + LABEL (DESC, DESC_STR); + LABEL (RECN, "R"); + LABEL (SHRS, TOT_SHRS_STR); + LABEL (BALN, BALN_STR); + LABEL (ACTN, ACTION_STR); LABEL (XFRM, XFRM_STR); LABEL (MXFRM, XFRM_STR); LABEL (XTO, XFTO_STR); - LABEL (DESC, DESC_STR); - LABEL (MEMO, DESC_STR); - LABEL (RECN, "R"); - LABEL (DEBT, DEBIT_STR); + LABEL (MEMO, MEMO_STR); LABEL (CRED, CREDIT_STR); - + LABEL (DEBT, DEBIT_STR); LABEL (PRIC, PRICE_STR); LABEL (VALU, VALUE_STR); - LABEL (SHRS, TOT_SHRS_STR); - LABEL (BALN, BALN_STR); /* setup custom labels for the debit/credit columns */ @@ -290,21 +295,19 @@ configAction (SplitRegister *reg) \ if ((0<=row) && (0<=col)) { \ curs->cells [row][col] = (handler); \ - curs->cell_types[row][col] = NAME##_CELL; \ + curs->cell_types[row][col] = NAME##_CELL; \ header->widths[col] = NAME##_CELL_WIDTH; \ - header->alignments[col] = NAME##_CELL_ALIGN; \ + header->alignments[col] = NAME##_CELL_ALIGN; \ curs->widths[col] = NAME##_CELL_WIDTH; \ - curs->alignments[col] = NAME##_CELL_ALIGN; \ + curs->alignments[col] = NAME##_CELL_ALIGN; \ if (hcell) { \ - if (1 == reg->num_header_rows) { \ - header->cells[0][col] = hcell; \ - } else { \ + if (row < reg->num_header_rows) { \ header->cells[row][col] = hcell; \ } \ } \ } \ } - + /* BASIC & FANCY macros initialize cells in the register */ #define BASIC(NAME,CN,col,row) { \ @@ -351,18 +354,6 @@ configLayout (SplitRegister *reg) case INCOME_LEDGER: /* hack alert do xto cell too */ case GENERAL_LEDGER: /* hack alert do xto cell too */ { - - /* basic common 8 column config */ - curs = reg->single_cursor; - FANCY (DATE, date, 0, 0); - BASIC (NUM, num, 1, 0); - FANCY (MXFRM, mxfrm, 2, 0); - FANCY (DESC, desc, 3, 0); - BASIC (RECN, recn, 4, 0); - FANCY (DEBT, debit, 5, 0); - FANCY (CRED, credit, 6, 0); - FANCY (BALN, balance, 7, 0); - curs = reg->double_cursor; FANCY (DATE, date, 0, 0); BASIC (NUM, num, 1, 0); @@ -389,15 +380,8 @@ configLayout (SplitRegister *reg) BASIC (MEMO, memo, 3, 0); FANCY (NDEBT, ndebit, 5, 0); FANCY (NCRED, ncredit, 6, 0); - break; - } - /* --------------------------------------------------------- */ - case STOCK_REGISTER: - case PORTFOLIO: - case CURRENCY_REGISTER: - { - /* 11 column config */ + /* basic common 8 column config */ curs = reg->single_cursor; FANCY (DATE, date, 0, 0); BASIC (NUM, num, 1, 0); @@ -406,11 +390,16 @@ configLayout (SplitRegister *reg) BASIC (RECN, recn, 4, 0); FANCY (DEBT, debit, 5, 0); FANCY (CRED, credit, 6, 0); - FANCY (PRIC, price, 7, 0); - FANCY (VALU, value, 8, 0); - FANCY (SHRS, shrs, 9, 0); - FANCY (BALN, balance, 10, 0); + FANCY (BALN, balance, 7, 0); + break; + } + + /* --------------------------------------------------------- */ + case STOCK_REGISTER: + case PORTFOLIO: + case CURRENCY_REGISTER: + { /* prep the second row of the double style */ curs = reg->double_cursor; FANCY (DATE, date, 0, 0); @@ -447,6 +436,21 @@ configLayout (SplitRegister *reg) BASIC (MEMO, memo, 3, 0); FANCY (NDEBT, ndebit, 5, 0); FANCY (NCRED, ncredit, 6, 0); + + /* 11 column config */ + curs = reg->single_cursor; + FANCY (DATE, date, 0, 0); + BASIC (NUM, num, 1, 0); + FANCY (MXFRM, mxfrm, 2, 0); + FANCY (DESC, desc, 3, 0); + BASIC (RECN, recn, 4, 0); + FANCY (DEBT, debit, 5, 0); + FANCY (CRED, credit, 6, 0); + FANCY (PRIC, price, 7, 0); + FANCY (VALU, value, 8, 0); + FANCY (SHRS, shrs, 9, 0); + FANCY (BALN, balance, 10, 0); + break; } /* --------------------------------------------------------- */ @@ -464,15 +468,20 @@ configLayout (SplitRegister *reg) /* hack alert -- if show_tamount or show_samount is set then don't traverse there */ /* hack alert -- fix show_txfrm also ... */ + +/* Right Traversals */ + #define FIRST_RIGHT(r,c) { \ prev_r = r; prev_c = c; \ } -#define NEXT_RIGHT(r,c) { \ + +#define NEXT_RIGHT(r,c) { \ xaccNextRight (curs, prev_r, prev_c, (r), (c)); \ prev_r = r; prev_c = c; \ } + #define TRAVERSE_NON_NULL_CELLS() { \ i = prev_r; \ for (j=prev_c+1; jnumCols; j++) { \ @@ -495,6 +504,7 @@ configLayout (SplitRegister *reg) } \ } + #define FIRST_NON_NULL(r,c) { \ i = r; \ for (j=c; jnumCols; j++) { \ @@ -508,6 +518,7 @@ configLayout (SplitRegister *reg) } \ } + #define NEXT_NON_NULL(r,c) { \ i = r; \ for (j=c+1; jnumCols; j++) { \ @@ -521,6 +532,11 @@ configLayout (SplitRegister *reg) } \ } +#define EXIT_RIGHT() { \ + curs->right_exit_r = prev_r; curs->right_exit_c = prev_c; \ +} + + #define NEXT_SPLIT() { \ i = 0; \ for (j=0; jsplit_cursor->numCols; j++) { \ @@ -534,6 +550,83 @@ configLayout (SplitRegister *reg) } \ } + +/* Left Traversals */ + +#define LAST_LEFT(r,c) { \ + prev_r = r; prev_c = c; \ +} + +#define NEXT_LEFT(r,c) { \ + xaccNextLeft (curs, prev_r, prev_c, (r), (c)); \ + prev_r = r; prev_c = c; \ +} + +#define TRAVERSE_NON_NULL_CELLS_LEFT() { \ + i = prev_r; \ + for (j=prev_c -1; j>=0; j--) { \ + if ((reg->nullCell != curs->cells[i][j]) && \ + (reg->recnCell != curs->cells[i][j]) && \ + (XACC_CELL_ALLOW_INPUT & curs->cells[i][j]->input_output)) \ + { \ + NEXT_LEFT (i, j); \ + } \ + } \ + for (i=prev_r-1; i>=0; i--) { \ + for (j=curs->numCols-1; j>=0; j--) { \ + if ((reg->nullCell != curs->cells[i][j]) && \ + (reg->recnCell != curs->cells[i][j]) && \ + (XACC_CELL_ALLOW_INPUT & curs->cells[i][j]->input_output)) \ + { \ + NEXT_LEFT (i, j); \ + } \ + } \ + } \ +} + +#define LAST_NON_NULL(r,c) { \ + i = r; \ + for (j=c; j>=0; j--) { \ + if ((reg->nullCell != curs->cells[i][j]) && \ + (reg->recnCell != curs->cells[i][j]) && \ + (XACC_CELL_ALLOW_INPUT & curs->cells[i][j]->input_output)) \ + { \ + LAST_LEFT (i, j); \ + break; \ + } \ + } \ +} + +#define PREVIOUS_NON_NULL(r,c) { \ + i = r; \ + for (j=c-1; j>=0; j--) { \ + if ((reg->nullCell != curs->cells[i][j]) && \ + (reg->recnCell != curs->cells[i][j]) && \ + (XACC_CELL_ALLOW_INPUT & curs->cells[i][j]->input_output)) \ + { \ + NEXT_LEFT (i, j); \ + break; \ + } \ + } \ +} + +#define EXIT_LEFT() { \ + curs->left_exit_r = prev_r; curs->left_exit_c = prev_c; \ +} + +#define PREVIOUS_SPLIT() { \ + i = reg->split_cursor->numRows-1; \ + for (j=reg->split_cursor->numCols-1; j>=0; j--) { \ + if ((reg->nullCell != reg->split_cursor->cells[i][j]) && \ + (reg->recnCell != reg->split_cursor->cells[i][j]) && \ + (XACC_CELL_ALLOW_INPUT & reg->split_cursor->cells[i][j]->input_output)) \ + { \ + NEXT_LEFT (i-1, j); \ + break; \ + } \ + } \ +} + static void configTraverse (SplitRegister *reg) { @@ -547,28 +640,63 @@ configTraverse (SplitRegister *reg) FIRST_NON_NULL (0, 0); first_r = prev_r; first_c = prev_c; TRAVERSE_NON_NULL_CELLS (); + /* set the exit row, col */ + EXIT_RIGHT (); /* wrap back to start of row after hitting the commit button */ - NEXT_RIGHT (-1-first_r, -1-first_c); + NEXT_RIGHT (first_r, first_c); + + /* left traverses */ + LAST_NON_NULL (curs->numRows-1, curs->numCols - 1); + first_r = prev_r; first_c = prev_c; + TRAVERSE_NON_NULL_CELLS_LEFT(); + EXIT_LEFT (); + NEXT_LEFT (first_r, first_c); curs = reg->double_cursor; /* lead in with the date cell, return to the date cell */ FIRST_NON_NULL (0, 0); first_r = prev_r; first_c = prev_c; TRAVERSE_NON_NULL_CELLS (); + /* set the exit row,col */ + EXIT_RIGHT (); /* for double-line, hop back one row */ - NEXT_RIGHT (-1-first_r, -1-first_c); + NEXT_RIGHT (first_r, first_c); + + /* left traverses */ + LAST_NON_NULL (curs->numRows-1, curs->numCols - 1); + first_r = prev_r; first_c = prev_c; + TRAVERSE_NON_NULL_CELLS_LEFT (); + EXIT_LEFT (); + NEXT_LEFT (first_r, first_c); curs = reg->trans_cursor; FIRST_NON_NULL (0,0); TRAVERSE_NON_NULL_CELLS (); + /* set the exit row, col */ + EXIT_RIGHT (); /* hop to start of next row (the split cursor) */ NEXT_SPLIT(); + /* left_traverses */ + LAST_NON_NULL (curs->numRows-1, curs->numCols - 1); + TRAVERSE_NON_NULL_CELLS_LEFT (); + EXIT_LEFT (); + PREVIOUS_SPLIT (); + + printf ("SPLIT\n"); curs = reg->split_cursor; FIRST_NON_NULL (0,0); TRAVERSE_NON_NULL_CELLS (); + /* set the exit row, col */ + EXIT_RIGHT (); /* hop to start of next row (the split cursor) */ NEXT_SPLIT(); + + /* left_traverses */ + LAST_NON_NULL (curs->numRows-1, curs->numCols - 1); + TRAVERSE_NON_NULL_CELLS_LEFT (); + EXIT_LEFT (); + PREVIOUS_SPLIT (); } /* ============================================== */ @@ -681,9 +809,7 @@ xaccInitSplitRegister (SplitRegister *reg, int type) CellBlock *header; int phys_r, phys_c; - reg->user_hook = NULL; - reg->user_hack = NULL; - reg->user_huck = NULL; + reg->user_data = NULL; reg->destroy = NULL; reg->type = type; @@ -788,18 +914,18 @@ xaccInitSplitRegister (SplitRegister *reg, int type) xaccSetPriceCellValue (reg->ndebitCell, 0.0); xaccSetPriceCellValue (reg->ncreditCell, 0.0); - /* use three decimal places to print share-related info. - * The format is a printf-style format for a double. */ - xaccSetPriceCellFormat (reg->shrsCell, "%.3f"); - xaccSetPriceCellFormat (reg->priceCell, "%.3f"); + /* The format for share-related info is a printf-style + * format string for a double. */ + xaccSetPriceCellFormat (reg->shrsCell, SHRS_CELL_FORMAT); + xaccSetPriceCellFormat (reg->priceCell, PRICE_CELL_FORMAT); - /* use three decimal places for share quantities in stock ledgers */ + /* number format for share quantities in stock ledgers */ switch (type & REG_TYPE_MASK) { case STOCK_REGISTER: case PORTFOLIO: case CURRENCY_REGISTER: - xaccSetPriceCellFormat (reg->debitCell, "%.3f"); - xaccSetPriceCellFormat (reg->creditCell, "%.3f"); + xaccSetPriceCellFormat (reg->debitCell, DEBIT_CELL_FORMAT); + xaccSetPriceCellFormat (reg->creditCell, CREDIT_CELL_FORMAT); break; default: break; @@ -874,9 +1000,7 @@ xaccDestroySplitRegister (SplitRegister *reg) (*(reg->destroy)) (reg); } reg->destroy = NULL; - reg->user_hook = NULL; - reg->user_hack = NULL; - reg->user_huck = NULL; + reg->user_data = NULL; xaccDestroyTable (reg->table); reg->table = NULL; @@ -966,4 +1090,57 @@ xaccSplitRegisterGetChangeFlag (SplitRegister *reg) return changed; } +/* ============================================== */ + +void +xaccSplitRegisterClearChangeFlag (SplitRegister *reg) +{ + reg->dateCell->cell.changed = 0; + reg->numCell->changed = 0; + reg->descCell->cell.changed = 0; + reg->recnCell->changed = 0; + + reg->actionCell->cell.changed = 0; + reg->xfrmCell->cell.changed = 0; + reg->mxfrmCell->cell.changed = 0; + reg->xtoCell->cell.changed = 0; + reg->memoCell->changed = 0; + reg->creditCell->cell.changed = 0; + reg->debitCell->cell.changed = 0; + reg->priceCell->cell.changed = 0; + reg->valueCell->cell.changed = 0; + + reg->ncreditCell->cell.changed = 0; + reg->ndebitCell->cell.changed = 0; +} + +/* ============================================== */ + +CursorType +xaccSplitRegisterGetCursorType (SplitRegister *reg) +{ + Table *table; + CellBlock *cursor; + + assert(reg); + + table = reg->table; + if (table == NULL) + return CURSOR_NONE; + + cursor = table->current_cursor; + if (cursor == NULL) + return CURSOR_NONE; + + if ((cursor == reg->single_cursor) || + (cursor == reg->double_cursor) || + (cursor == reg->trans_cursor)) + return CURSOR_TRANS; + + if (cursor == reg->split_cursor) + return CURSOR_SPLIT; + + return CURSOR_NONE; +} + /* ============ END OF FILE ===================== */ diff --git a/src/register/splitreg.h b/src/register/splitreg.h index 9de6ba009a..cd10a7fc9d 100644 --- a/src/register/splitreg.h +++ b/src/register/splitreg.h @@ -11,8 +11,6 @@ * * Handles splits * - * If the SHOW_TDETAIL flag is set, then transaction blah blah is shown. - * Otherwise it is not (useful when not displaying splits, gives old-styule reg). * Hack alert -- finish documenting this * * The xaccConfigSplitRegister() subroutine allows the configuration @@ -107,6 +105,14 @@ #define MOD_NEW 0x2000 #define MOD_ALL 0x3fff +/* Types of cursors */ +typedef enum +{ + CURSOR_SPLIT, + CURSOR_TRANS, + CURSOR_NONE +} CursorType; + /* The value of NUM_CELLS should be larger than the number of * cells defined in the structure below! */ @@ -161,11 +167,9 @@ struct _SplitRegister { BasicCell *header_label_cells[NUM_CELLS]; - /* user_hook allows users of this object to hang + /* user_data allows users of this object to hang * private data onto it */ - void *user_hook; - void *user_hack; - void *user_huck; + void *user_data; /* The destroy callback gives user's a chance * to free up any associated user_hook data */ @@ -181,6 +185,13 @@ void xaccDestroySplitRegister (SplitRegister *); /* returns non-zero value if updates have been made to data */ unsigned int xaccSplitRegisterGetChangeFlag (SplitRegister *); +/* Clears all change flags in the register. Does not alter values */ +void xaccSplitRegisterClearChangeFlag (SplitRegister *reg); + +/* Returns the type of the current cursor */ +CursorType xaccSplitRegisterGetCursorType (SplitRegister *reg); + + #endif /* __XACC_SPLITREG_H__ */ /* ============ END OF FILE ===================== */ diff --git a/src/register/table-allgui.c b/src/register/table-allgui.c index 3a491e33b4..d7e1c844ec 100644 --- a/src/register/table-allgui.c +++ b/src/register/table-allgui.c @@ -92,9 +92,6 @@ xaccInitTable (Table * table) table->prev_phys_traverse_row = -1; table->prev_phys_traverse_col = -1; - table->reverify_phys_row = -1; - table->reverify_phys_col = -1; - /* call the "derived" class constructor */ TABLE_PRIVATE_DATA_INIT (table); } @@ -438,7 +435,7 @@ doMoveCursor (Table *table, int new_phys_row, int new_phys_col, int do_move_gui) ENTER("doMoveCursor(): new_phys=(%d %d) do_move_gui=%d\n", new_phys_row, new_phys_col, do_move_gui); - /* Change the cell background colors to thier "passive" values. + /* Change the cell background colors to their "passive" values. * This denotes that the cursor has left this location (which means more or * less the same thing as "the current location is no longer being edited.") */ @@ -451,11 +448,13 @@ doMoveCursor (Table *table, int new_phys_row, int new_phys_col, int do_move_gui) (table->move_cursor) (table, &new_phys_row, &new_phys_col, table->client_data); - /* The above callback can cause this routine to be called recursively. - * As a result of this recursion, the cursor may have gotten repositioned. - * we need to make sure we make passive again. - */ + /* The above callback can cause this routine to be called + * recursively. As a result of this recursion, the cursor may + * have gotten repositioned. We need to make sure we make + * passive again. */ makePassive (table); + if (do_move_gui) + xaccRefreshCursorGUI(table, FALSE); } /* check for out-of-bounds conditions (which may be deliberate) */ @@ -681,7 +680,7 @@ xaccRefreshHeader (Table *table) /* ==================================================== */ /* verifyCursorPosition checks the location of the cursor * with respect to a row/column position, and repositions - * the cursor if necessary. This includes saving any uncomited + * the cursor if necessary. This includes saving any uncommited * data in the old cursor, and then moving the cursor and its * GUI. */ @@ -801,8 +800,9 @@ wrapVerifyCursorPosition (Table *table, int row, int col) (save_phys_col != table->current_cursor_phys_col)) { /* make sure *both* the old and the new cursor rows get redrawn */ - xaccRefreshCursorGUI (table); - doRefreshCursorGUI (table, save_curs, save_phys_row, save_phys_col); + xaccRefreshCursorGUI (table, TRUE); + doRefreshCursorGUI (table, save_curs, + save_phys_row, save_phys_col, FALSE); } LEAVE ("wrapVerifyCursorPosition()\n"); } @@ -810,11 +810,12 @@ wrapVerifyCursorPosition (Table *table, int row, int col) /* ==================================================== */ void -xaccRefreshCursorGUI (Table * table) +xaccRefreshCursorGUI (Table * table, gncBoolean do_scroll) { doRefreshCursorGUI (table, table->current_cursor, - table->current_cursor_phys_row, - table->current_cursor_phys_col); + table->current_cursor_phys_row, + table->current_cursor_phys_col, + do_scroll); } /* ==================================================== */ @@ -984,20 +985,15 @@ gnc_table_leave_update(Table *table, int row, int col, (arr->cells[rel_row][rel_col])->changed = 0xffffffff; } - /* Do the verify last, which in general asumes that cell handlers - are up to date (i.e. the leave has been processed.) */ - - wrapVerifyCursorPosition (table, - table->reverify_phys_row, - table->reverify_phys_col); - /* return the result of the final decisionmaking */ if (strcmp (table->entries[row][col], callback_text)) { - retval = table->entries[row][col]; + retval = table->entries[row][col]; } else { retval = NULL; } + LEAVE("gnc_table_leave_update(): return %s\n", retval); + return retval; } @@ -1007,7 +1003,7 @@ const char * gnc_table_modify_update(Table *table, int row, int col, const char *oldval, const char *change, - char *newval) + char *newval, int *cursor_position) { /* returned result should not be touched by the caller */ /* NULL return value means the edit was rejected */ @@ -1018,14 +1014,16 @@ gnc_table_modify_update(Table *table, int row, int col, const int rel_row = table->locators[row][col]->phys_row_offset; const int rel_col = table->locators[row][col]->phys_col_offset; - const char * (*mv) (BasicCell *, const char *, const char *, const char *); + const char * (*mv) (BasicCell *, + const char *, const char *, const char *, int *); const char *retval = NULL; ENTER ("gnc_table_modify_update()\n"); /* OK, if there is a callback for this cell, call it */ mv = arr->cells[rel_row][rel_col]->modify_verify; if (mv) { - retval = (*mv) (arr->cells[rel_row][rel_col], oldval, change, newval); + retval = (*mv) (arr->cells[rel_row][rel_col], + oldval, change, newval, cursor_position); /* if the callback returned a non-null value, allow the edit */ if (retval) { @@ -1089,27 +1087,25 @@ gnc_table_traverse_update(Table *table, int row, int col, /* process forward-moving traversals */ switch(dir) { case GNC_TABLE_TRAVERSE_RIGHT: + case GNC_TABLE_TRAVERSE_LEFT: { /* cannot compute the cell location until we have checked that * row and column have valid values. compute the cell location. */ - const int rel_row = table->locators[row][col]->phys_row_offset; - const int rel_col = table->locators[row][col]->phys_col_offset; + int next_row, next_col; + const int rel_row = table->locators[row][col]->phys_row_offset; + const int rel_col = table->locators[row][col]->phys_col_offset; - int next_row = arr->right_traverse_r[rel_row][rel_col]; - int next_col = arr->right_traverse_c[rel_row][rel_col]; + if (dir == GNC_TABLE_TRAVERSE_RIGHT) { + *dest_row = row - rel_row + arr->right_traverse_r[rel_row][rel_col]; + *dest_col = col - rel_col + arr->right_traverse_c[rel_row][rel_col]; + exit_register= ((rel_row == arr->right_exit_r) && (rel_col == arr->right_exit_c)); + } else { + *dest_row = row - rel_row + arr->left_traverse_r[rel_row][rel_col]; + *dest_col = col - rel_col + arr->left_traverse_c[rel_row][rel_col]; + exit_register = ((rel_row == arr->left_exit_r) && (rel_col == arr->left_exit_c)); + } - /* if we are at the end of the traversal chain, hop out of this - * tab group, and into the next. */ - if ((0 > next_row) || (0 > next_col)) { - /* reverse the sign of next_row, col to be positive. */ - *dest_row = row - rel_row - next_row - 1; - *dest_col = col - rel_col - next_col - 1; - exit_register = TRUE; - } else { - *dest_row = row - rel_row + next_row; - *dest_col = col - rel_col + next_col; - } } break; @@ -1181,40 +1177,19 @@ gnc_table_traverse_update(Table *table, int row, int col, break; default: - /* FIXME: Handle left-movement */ + /* shouldn't be reached */ + assert(0); break; } - /* OK, now we do a fancy trick to get the auto-expanding registers - to work right. The trick is that as one transaction is expanded - or collapsed, the rows all get renumbered. Now, we can't tell - the code to directly hope to the new renumbered position, because - we haven't completed all of our work yet. In particular, we - haven't left the current cell, and this means we haven't yet - saved those cell contents. Only after saving, can we reconfigure - the table. And only after we reconfigure the table can we move - the since only then will we know where & how to move it to. Sooo - ... here's what we do. We compute where we should have hopped to - in the reconfigured table, and save that off in the "reverify" - fields. Then we hop to the boring old place we would have hopped - to if there had been no reconfiguring going on. Later on, after - we've reconfigured, we will move the cursor to the "reverify" - position, and viola, we'll be in the right place. */ - - if (table->traverse) { - int nr = *dest_row; - int nc = *dest_col; - table->reverify_phys_row = nr; - table->reverify_phys_col = nc; - (table->traverse) (table, &nr, &nc, table->client_data); - *dest_row = nr; - *dest_col = nc; - } - + /* Call the table traverse callback for any modifications. */ + if (table->traverse) + (table->traverse) (table, dest_row, dest_col, table->client_data); + table->prev_phys_traverse_row = *dest_row; table->prev_phys_traverse_col = *dest_col; - LEAVE("gnc_table_traverse_update(): exit_register=%d\n", exit_register); + LEAVE("gnc_table_traverse_update(): dest_row = %d, dest_col = %d, exit_register=%d\n", *dest_row, *dest_col, exit_register); return(exit_register); } diff --git a/src/register/table-allgui.h b/src/register/table-allgui.h index 790f45d014..9407d2f9d3 100644 --- a/src/register/table-allgui.h +++ b/src/register/table-allgui.h @@ -239,9 +239,6 @@ struct _Table { int prev_phys_traverse_row; int prev_phys_traverse_col; - int reverify_phys_row; - int reverify_phys_col; - /* Since we are using C not C++, but we need inheritance, * cock it up with a #defined thingy that the "derived class" * can specify. @@ -339,10 +336,11 @@ int gnc_register_cell_valid(Table *table, int row, int col); void -doRefreshCursorGUI (Table * table, CellBlock *curs, int from_row, int from_col); +doRefreshCursorGUI (Table * table, CellBlock *curs, + int from_row, int from_col, gncBoolean do_scroll); void -xaccRefreshCursorGUI (Table * table); +xaccRefreshCursorGUI (Table * table, gncBoolean do_scroll); /* * gnc_table_enter_update() is a utility function used to determine @@ -367,7 +365,8 @@ const char * gnc_table_modify_update(Table *table, int row, int col, const char *oldval, const char *change, - char *newval); + char *newval, + int *cursor_position); gncBoolean gnc_table_traverse_update(Table *table, int row, int col, gncTableTraversalDir dir, diff --git a/src/register/table-gnome.c b/src/register/table-gnome.c index 4cbf993cea..7d1585a5a3 100644 --- a/src/register/table-gnome.c +++ b/src/register/table-gnome.c @@ -114,7 +114,7 @@ xaccCreateTable (GtkWidget *widget, void *data) xaccRefreshHeader (table); gnucash_sheet_table_load (sheet); - gnucash_sheet_cursor_set_from_table (sheet); + gnucash_sheet_cursor_set_from_table (sheet, TRUE); gnucash_sheet_redraw_all (sheet); return; @@ -142,13 +142,11 @@ xaccRefreshTableGUI (Table * table) void -doRefreshCursorGUI (Table * table, CellBlock *curs, int from_row, int from_col) +doRefreshCursorGUI (Table * table, CellBlock *curs, + int from_row, int from_col, gncBoolean do_scroll) { GnucashSheet *sheet; - int phys_row, phys_col; - int to_row, to_col; gint virt_row, virt_col; - int i,j; if (!table) return; @@ -160,14 +158,17 @@ doRefreshCursorGUI (Table * table, CellBlock *curs, int from_row, int from_col) /* if the current cursor is undefined, there is nothing to do. */ if (!curs) return; if ((0 > from_row) || (0 > from_col)) return; + if ((from_row >= table->num_phys_rows) || + (from_col >= table->num_phys_cols)) + return; sheet = GNUCASH_SHEET(table->table_widget); - /* compute the physical bounds of the current cursor */ + /* compute the physical bounds of the current cursor */ virt_row = table->locators[from_row][from_col]->virt_row; virt_col = table->locators[from_row][from_col]->virt_col; - gnucash_sheet_cursor_set_from_table (sheet); + gnucash_sheet_cursor_set_from_table (sheet, do_scroll); gnucash_sheet_block_set_from_table (sheet, virt_row, virt_col); gnucash_sheet_redraw_block (sheet, virt_row, virt_col); } diff --git a/src/register/table-gnome.h b/src/register/table-gnome.h index d04f0ca67b..cabb399a4f 100644 --- a/src/register/table-gnome.h +++ b/src/register/table-gnome.h @@ -32,6 +32,8 @@ #include +#include "gnc-common.h" + /* We use C not C++ in this project, but we none-the-less need * the general mechanism of inheritance. The three #defines @@ -89,7 +91,7 @@ typedef struct _Table Table; void xaccCreateTable (GtkWidget *, void *); -void doRefreshCursorGUI (Table *, CellBlock *, int, int); +void doRefreshCursorGUI (Table *, CellBlock *, int, int, gncBoolean); void xaccRefreshTableGUI (Table *); #endif __XACC_TABLE_GNOME_H__ diff --git a/src/register/table-motif.c b/src/register/table-motif.c index 6c959ae397..9a6e8d5f0e 100644 --- a/src/register/table-motif.c +++ b/src/register/table-motif.c @@ -200,6 +200,7 @@ modifyCB (Widget mw, XtPointer cd, XtPointer cb) int len = 1; const char *retval; char *newval; + ENTER("modifyCB()\n"); /* accept edits by default, unless the cell handler rejects them */ @@ -216,7 +217,11 @@ modifyCB (Widget mw, XtPointer cd, XtPointer cb) if (change) strcat (newval, change); strcat (newval, &oldval[(cbs->verify->endPos)]); - retval = gnc_table_modify_update(table, row, col, oldval, change, newval); + { + int cursor_position = 0; /* Unused at present. */ + retval = gnc_table_modify_update(table, row, col, oldval, change, newval, + &cursor_position); + } if (retval && (retval != newval)) { DEBUG ("modifyCB() new text: %s\n", retval); @@ -629,7 +634,8 @@ xaccRefreshTableGUI (Table * table) /* ==================================================== */ void -doRefreshCursorGUI (Table * table, CellBlock *curs, int from_row, int from_col) +doRefreshCursorGUI (Table * table, CellBlock *curs, int from_row, int from_col, + gncBoolean do_scroll) { int phys_row, phys_col; int to_row, to_col; @@ -638,6 +644,9 @@ doRefreshCursorGUI (Table * table, CellBlock *curs, int from_row, int from_col) /* if the current cursor is undefined, there is nothing to do. */ if (!curs) return; if ((0 > from_row) || (0 > from_col)) return; + if ((from_row >= table->num_phys_rows) || + (from_col >= table->num_phys_cols)) + return; /* compute the physical bounds of the current cursor */ phys_row = from_row; diff --git a/src/register/table-motif.h b/src/register/table-motif.h index cd232306e8..99a2385213 100644 --- a/src/register/table-motif.h +++ b/src/register/table-motif.h @@ -32,6 +32,9 @@ #include +#include "gnc-common.h" + + /* We use C not C++ in this project, but we none-the-less need * the general mechanism of inheritance. The three #defines * below implement that. @@ -97,7 +100,7 @@ void xaccRefreshTableGUI (Table *); * redrawn. Thus, the use of this routine should result in significantly * less screen color flashing than the use of the full-table refresh routine. */ -void xaccRefreshCursorGUI (Table *); +void xaccRefreshCursorGUI (Table *, gncBoolean do_scroll); #endif /* __XACC_TABLE_MOTIF_H__ */ /* ================== end of file ======================= */ diff --git a/src/register/textcell.c b/src/register/textcell.c index d7cbcdfb1e..402e5dcc4d 100644 --- a/src/register/textcell.c +++ b/src/register/textcell.c @@ -38,7 +38,8 @@ static const char * TextMV (struct _BasicCell *_cell, const char *oldval, const char *change, - const char *newval) + const char *newval, + int *cursor_position) { BasicCell *cell = (BasicCell *) _cell; diff --git a/src/scm/bs-interp.scm b/src/scm/bs-interp.scm index b418d1c0b1..6d0b75564a 100644 --- a/src/scm/bs-interp.scm +++ b/src/scm/bs-interp.scm @@ -2,7 +2,7 @@ ;; Load the necessary files for use in interpreter mode. -(primitive-load "bootstrap.scm") +(primitive-load (getenv "GNC_BOOTSTRAP_SCM")) (gnc:load "startup.scm") (gnc:load "main.scm") (gnc:startup) diff --git a/src/scm/command-line.scm b/src/scm/command-line.scm index 22a25f07f5..ada60b43de 100644 --- a/src/scm/command-line.scm +++ b/src/scm/command-line.scm @@ -6,6 +6,11 @@ (define gnc:*arg-defs* (list + (cons + "version" + (cons 'boolean + (lambda (val) + (gnc:config-var-value-set! gnc:*arg-show-version* #f val)))) (cons "usage" (cons 'boolean @@ -92,6 +97,9 @@ (gnc:debug "got string arg returning " (car args) " and " (cdr args)) (list (car args) (cdr args))) +(define (gnc:prefs-show-version) + (display "GnuCash 1.3 development version") (newline)) + (define (gnc:prefs-show-usage) (display "usage: gnucash [ option ... ] [ datafile ]") (newline)) diff --git a/src/scm/depend.scm b/src/scm/depend.scm new file mode 100644 index 0000000000..33b16d2cb7 --- /dev/null +++ b/src/scm/depend.scm @@ -0,0 +1,15 @@ + +(define gnc:*_supported-files_* (make-hash-table 101)) +;; Record of files that have already been loaded. We don't do +;; anything other than record the name that the file tells us (via +;; gnc:support) that it provides. The size of this table should +;; roughly depend on the number of .scm files in the source tree. + +(define (gnc:support name) + (hash-set! gnc:*_supported-files_* name #t)) + +(define (gnc:depend name) + (let ((supported? (hash-ref gnc:*_supported-files_* name))) + (if supported? + #t + (gnc:load name)))) diff --git a/src/scm/doc.scm b/src/scm/doc.scm index 9d8d283ec8..4db3af55da 100644 --- a/src/scm/doc.scm +++ b/src/scm/doc.scm @@ -1,4 +1,5 @@ +(gnc:support "doc.scm") + (define (gnc:find-doc-file file) (gnc:find-in-directories file (gnc:config-var-value-get gnc:*doc-path*))) - diff --git a/src/scm/extensions.scm b/src/scm/extensions.scm index 61f45208a6..54a9675587 100644 --- a/src/scm/extensions.scm +++ b/src/scm/extensions.scm @@ -1,4 +1,6 @@ +(gnc:support "extensions.scm") + (define (gnc:extensions-menu-test-func) (display "Extension called from scheme.\n")) @@ -33,37 +35,38 @@ (lambda () (gnc:extensions-test-add-accs win))) - (gnc:extensions-menu-add-item "Test choose item from list dialog" - "Test choose item from list dialog" - (lambda () - (let ((result (gnc:choose-item-from-list-dialog - "Choose item from list test dialog" - (list - (cons "Item 1" - (lambda () - (display "Item 1 selected") (newline) - #f)) - (cons "Item 2" - (lambda () - (display "Item 2 selected") (newline) - #f)) - (cons "Item 3 (and close dialog)" - (lambda () - (display "Item 3 selected -- close") (newline) - 'some-interesting-result)))))) - - (cond - ((eq? result #f) - (gnc:error-dialog - "Fatal error in choose item from list dialog.")) - ((eq? result 'cancel) - (gnc:error-dialog "Choose item from list dialog canceled.")) - (else - (gnc:error-dialog - (call-with-output-string (lambda (string-port) - (display "Choose item result: " - string-port) - (write result string-port))))))))) + (gnc:extensions-menu-add-item + "Test choose item from list dialog" + "Test choose item from list dialog" + (lambda () + (let ((result (gnc:choose-item-from-list-dialog + "Choose item from list test dialog" + (list + (cons "Item 1" + (lambda () + (display "Item 1 selected") (newline) + #f)) + (cons "Item 2" + (lambda () + (display "Item 2 selected") (newline) + #f)) + (cons "Item 3 (and close dialog)" + (lambda () + (display "Item 3 selected -- close") (newline) + 'some-interesting-result)))))) + + (cond + ((eq? result #f) + (gnc:error-dialog + "Fatal error in choose item from list dialog.")) + ((eq? result 'cancel) + (gnc:error-dialog "Choose item from list dialog canceled.")) + (else + (gnc:error-dialog + (call-with-output-string (lambda (string-port) + (display "Choose item result: " + string-port) + (write result string-port))))))))) (gnc:extensions-menu-add-item "Test verify dialog" diff --git a/src/scm/importqif.scm b/src/scm/importqif.scm index 96c59d6526..12daa1c93d 100644 --- a/src/scm/importqif.scm +++ b/src/scm/importqif.scm @@ -1,6 +1,8 @@ ;;; $Id$ ;;; Import QIF File +(gnc:support "importqif.scm") + (define testing? #f) ;;; Should we do testing? (define favorite-currency "USD") ;;;; This may need to change... diff --git a/src/scm/main.scm b/src/scm/main.scm index b489e89568..3aa328004e 100644 --- a/src/scm/main.scm +++ b/src/scm/main.scm @@ -3,24 +3,49 @@ (gnc:debug "starting up.") (if (not (gnc:handle-command-line-args)) (gnc:shutdown 1)) - + + ;; Load the srfis + (gnc:load "srfi/srfi-8.guile.scm") + (gnc:load "srfi/srfi-1.unclear.scm") + (gnc:load "srfi/srfi-1.r5rs.scm") + + (gnc:load "depend.scm") + ;; Now we can load a bunch of files. - - (gnc:load "doc.scm") - (gnc:load "extensions.scm") ; Should this be here or somewhere else? - (gnc:load "text-export.scm") - (gnc:load "importqif.scm") - (gnc:load "test.scm") - + + (gnc:depend "doc.scm") + (gnc:depend "extensions.scm") ; Should this be here or somewhere else? + (gnc:depend "text-export.scm") + (gnc:depend "importqif.scm") + (gnc:depend "test.scm") + (gnc:depend "report.scm") + + ;; FIXME: These do not belong here, but for now, we're putting them + ;; here. Later we need a generalization of gnc:load that takes a + ;; path specifier, and then we should have a gnc:*report-path* that + ;; determines where we look to load report files. For now, though, + ;; I just want to get things going... + ;; + ;; Just load these since we might want to redefine them on the fly + ;; and we're going to change this mechanism anyway... + (gnc:load "report/dummy.scm") + (gnc:load "report/balance-and-pnl.scm") + (gnc:load "report/transaction-report.scm") + ;; Load the system and user configs (if (not (gnc:load-system-config-if-needed)) (gnc:shutdown 1)) - + (if (not (gnc:load-user-config-if-needed)) (gnc:shutdown 1)) (gnc:hook-run-danglers gnc:*startup-hook*) - + + (if (gnc:config-var-value-get gnc:*arg-show-version*) + (begin + (gnc:prefs-show-version) + (gnc:shutdown 0))) + (if (or (gnc:config-var-value-get gnc:*arg-show-usage*) (gnc:config-var-value-get gnc:*arg-show-help*)) (begin @@ -44,6 +69,7 @@ (define (gnc:ui-finish) (gnc:debug "UI Shutdown hook.") + (gnc:ui-destroy-all-subwindows) (gnc:file-query-save) (gnc:file-quit)) @@ -55,7 +81,7 @@ (if (not (= (gnc:lowlev-app-init) 0)) (gnc:shutdown 0)) - + (if (pair? gnc:*command-line-files*) ;; You can only open single files right now... (gnc:ui-open-file (car gnc:*command-line-files*))) @@ -65,5 +91,5 @@ (gnc:ui-main) (gnc:hook-remove-dangler gnc:*ui-shutdown-hook* gnc:ui-finish) - + (gnc:shutdown 0)) diff --git a/src/scm/prefs.scm b/src/scm/prefs.scm index 439c54ff78..eb2627b042 100644 --- a/src/scm/prefs.scm +++ b/src/scm/prefs.scm @@ -46,7 +46,7 @@ ;; This will be an alist ;; (k v) -> (section-name list-of-option-items) -(define (gnc:make-configuration-option +(define (gnc:make-option ;; The category of this option section name @@ -62,8 +62,14 @@ generate-restore-form ;; Validation func should accept a value and return (#t value) ;; on success, and (#f "failure-message") on failure. If #t, - ;; the supplied value will be used to set the option. - value-validator) + ;; the supplied value will be used by the gui to set the option. + value-validator + ;; permissible-values (multichoice options only) + ;; a list of vectors giving the value, a name, + ;; and description of the permissible values. + ;; Values are unevaluated Scheme symbols, names and + ;; descriptions are strings which are used by the GUI. + permissible-values) (vector section name sort-tag @@ -73,7 +79,24 @@ setter default-getter generate-restore-form - value-validator)) + value-validator + permissible-values)) + +(define (gnc:make-string-option + section + name + sort-tag + documentation-string + default-value) + (let ((option default-value)) + (gnc:make-option section name sort-tag 'string documentation-string + (lambda () option) + (lambda (x) (set! option x)) + (lambda () default-value) + #f + (lambda (x) (cond ((string? x)(list #t x)) + (else (list #f #f)))) + #f ))) (define (gnc:make-simple-boolean-option section @@ -82,56 +105,93 @@ documentation-string default-value) (let ((option default-value)) - (gnc:make-configuration-option section name sort-tag 'boolean - documentation-string - (lambda () option) - (lambda (x) (set! option x)) - (lambda () default-value) - #f - (lambda (x) (list #t x))))) + (gnc:make-option section name sort-tag 'boolean documentation-string + (lambda () option) + (lambda (x) (set! option x)) + (lambda () default-value) + #f + (lambda (x) (list #t x)) + #f ))) -(define (gnc:configuration-option-section option) +(define (gnc:make-multichoice-option + section + name + sort-tag + documentation-string + default-value + ok-values) + (let ((option default-value)) + (define (multichoice-legal val p-vals) + (cond ((null? p-vals) #f) + ((eq? val (vector-ref (car p-vals) 0)) #t) + (else multichoice-legal (cdr p-vals)))) + + (gnc:make-option + section name sort-tag 'multichoice documentation-string + (lambda () option) + (lambda (x) + (if (multichoice-legal x ok-values) + (set! option x) + (gnc:error "Illegal Multichoice option set"))) + (lambda () default-value) + #f + (lambda (x) + (if (multichoice-legal x ok-values) + (list #t x) + (list #f #f))) + ok-values))) + +(define (gnc:option-section option) (vector-ref option 0)) -(define (gnc:configuration-option-name option) +(define (gnc:option-name option) (vector-ref option 1)) -(define (gnc:configuration-option-sort-tag option) +(define (gnc:option-sort-tag option) (vector-ref option 2)) -(define (gnc:configuration-option-type option) +(define (gnc:option-type option) (vector-ref option 3)) -(define (gnc:configuration-option-documentation option) +(define (gnc:option-documentation option) (vector-ref option 4)) -(define (gnc:configuration-option-getter option) +(define (gnc:option-getter option) (vector-ref option 5)) -(define (gnc:configuration-option-setter option) +(define (gnc:option-setter option) (vector-ref option 6)) -(define (gnc:configuration-option-default-getter option) +(define (gnc:option-default-getter option) (vector-ref option 7)) -(define (gnc:configuration-option-generate-restore-form option) +(define (gnc:option-generate-restore-form option) (vector-ref option 8)) -(define (gnc:configuration-option-value-validator option) +(define (gnc:option-value-validator option) (vector-ref option 9)) +(define (gnc:option-permissible-values option) + (vector-ref option 10)) + +(define (gnc:register-option options new-option) + (let* ((section (gnc:option-section new-option)) + (existing-entry (assoc-ref options section)) + (new-entry (if existing-entry + (cons new-option existing-entry) + (list new-option)))) + (assoc-set! options section new-entry))) + +(define (gnc:register-configuration-option new-option) + (set! gnc:*options-entries* + (gnc:register-option gnc:*options-entries* new-option))) -(define (gnc:register-configuration-option new-item) - - (let* ((section (gnc:configuration-option-section new-item)) - (existing-entry (assoc-ref gnc:*options-entries* section))) - (if existing-entry - (set! gnc:*options-entries* - (assoc-set! gnc:*options-entries* - section - (cons new-item existing-entry))) - (set! gnc:*options-entries* - (assoc-set! gnc:*options-entries* - section - (list new-item)))))) - -(define (gnc:send-ui-section-options section-info) +(define (gnc:send-option-section db_handle section-info) ;; section-info is a pair (section-name . list-of-options) - (for-each gnc:register-option-ui (cdr section-info))) + (for-each + (lambda (option) + (gnc:option-db-register-option db_handle option)) + (cdr section-info))) -(define (gnc:send-ui-options) - (for-each gnc:send-ui-section-options gnc:*options-entries*)) +(define (gnc:send-options db_handle options) + (for-each + (lambda (section-info) + (gnc:send-option-section db_handle section-info)) + options)) + +(define (gnc:send-global-options) + (gnc:register-global-options gnc:*options-entries*)) (gnc:register-configuration-option @@ -230,6 +290,52 @@ "Account Fields" "Show account balance" "h" "Show the account balance column in the account tree." #t)) +(gnc:register-configuration-option + (gnc:make-multichoice-option + "International" "Date Format" + "a" "Date Format Display" 'us + (list #(us "US" "US-style: mm/dd/yyyy") + #(uk "UK" "UK-style dd/mm/yyyy") + #(ce "Europe" "Continental Europe: dd.mm.yyyy") + #(iso "ISO" "ISO Standard: yyyy-mm-dd") + #(locale "Locale" "Take from system locale")))) + +;; hack alert - we should probably get the default new account currency +;; from the locale +;; I haven't figured out if I can do this in scheme or need a C hook yet +(gnc:register-configuration-option + (gnc:make-string-option + "International" "Default Currency" + "b" "Default Currency For New Accounts" "USD")) + +(gnc:register-configuration-option + (gnc:make-multichoice-option + "Register" "Default Register Mode" + "a" "Choose the default mode for register windows" + 'single_line + (list #(single_line "Single Line" "Show transactions on single lines") + #(double_line "Double Line" + "Show transactions on two lines with more information") + #(multi_line "Multi Line" "Show transactions on multiple lines with one line for each split in the transaction") + #(auto_single "Auto Single" "Single line mode with multi-line cursor") + #(auto_double "Auto Double" "Double line mode with multi-line cursor") + ))) + +(gnc:register-configuration-option + (gnc:make-multichoice-option + "Register" "Toolbar Buttons" + "b" "Choose whether to display icons, text, or both for toolbar buttons" + 'icons_and_text + (list #(icons_and_text "Icons and Text" "Show both icons and text") + #(icons_only "Icons only" "Show icons only") + #(text_only "Text only" "Show text only")))) + +(define gnc:*arg-show-version* + (gnc:make-config-var + "Show version." + (lambda (var value) (if (boolean? value) (list value) #f)) + eq? + #f)) (define gnc:*arg-show-usage* (gnc:make-config-var diff --git a/src/scm/report.scm b/src/scm/report.scm new file mode 100644 index 0000000000..f639ac1448 --- /dev/null +++ b/src/scm/report.scm @@ -0,0 +1,96 @@ + +(gnc:support "report.scm") + +;; We use a hash to store the report info so that whenever a report is +;; requested, we'll look up the action to take dynamically. That +;; makes it easier for us to allow changing the report definitions on +;; the fly later, and it should have no appreciable performance +;; effect. + +(define *gnc:_report-info_* (make-hash-table 23)) +;; This hash should contain all the reports available and will be used +;; to generate the reports menu whenever a new window opens and to +;; figure out what to do when a report needs to be generated. +;; +;; The key is the string naming the report and the value is the +;; rendering thunk. + +(define (gnc:run-report report-name) + ;; Return a string consisting of the contents of the report. + + (define (display-report-list-item item port) + (cond + ((string? item) (display item port)) + ((null? item) #t) + ((list? item) (map (lambda (item) (display-report-list-item item port)) + item)) + (else (gnc:warn "gnc:run-report - " item " is the wrong type.")))) + + (let ((report (hash-ref *gnc:_report-info_* report-name))) + (if (not report) + #f + (let ((options (gnc:report-options report)) + (rendering-thunk (gnc:report-rendering-thunk report))) + (call-with-output-string + (lambda (port) + (for-each + (lambda (item) (display-report-list-item item port)) + (rendering-thunk options)))))))) + +(define (gnc:report-menu-setup win) + ;; This should be on a reports menu later... + (hash-for-each + (lambda (report-info) + (let ((name (car report-info)) + (report (cdr report-info))) + (gnc:extensions-menu-add-item + (string-append "Report: " name) + (string-append "Display the " name " report.") + (lambda () + (gnc:report-window (string-append "Report: " name) + (lambda () (gnc:run-report name)) + (gnc:report-options report)))))) + *gnc:_report-info_*)) + +(define (gnc:define-report version name options rendering-thunk) + ;; For now the version is ignored, but in the future it'll let us + ;; change behaviors without breaking older reports. + ;; + ;; FIXME: If we wanted to be uber-dynamic we might want to consider + ;; re-generating the menus whenever this function is called. + + ;; The rendering-thunk should be a function that generates the report + ;; + ;; This code must return as its final value a collection of strings in + ;; the form of a list of elements where each element (recursively) is + ;; either a string, or a list containing nothing more than strings and + ;; lists of strings. Any null lists will be ignored. The final html + ;; output will be produced by an in-order traversal of the tree + ;; represented by the list. i.e. ("a" (("b" "c") "d") "e") produces + ;; "abcde" in the output. + ;; + ;; For those who speak BNF-ish the output should look like + ;; + ;; report -> string-list + ;; string-list -> ( items ) | () + ;; items -> item items | item + ;; item -> string | string-list + ;; + ;; Valid examples: + ;; + ;; ("" "") + ;; ("" " some text " "") + ;; ("" ("some" ("other" " text")) "") + (let ((report (vector version name options rendering-thunk))) + (hash-set! *gnc:_report-info_* name report))) + +(define (gnc:report-version report) + (vector-ref report 0)) +(define (gnc:report-name report) + (vector-ref report 1)) +(define (gnc:report-options report) + (vector-ref report 2)) +(define (gnc:report-rendering-thunk report) + (vector-ref report 3)) + +(gnc:hook-add-dangler gnc:*main-window-opened-hook* gnc:report-menu-setup) diff --git a/src/scm/report/balance-and-pnl.scm b/src/scm/report/balance-and-pnl.scm new file mode 100644 index 0000000000..c073bf9296 --- /dev/null +++ b/src/scm/report/balance-and-pnl.scm @@ -0,0 +1,169 @@ +;; -*-scheme-*- + +(use-modules (ice-9 slib)) +(require 'stdio) + +(gnc:depend "text-export.scm") + +(let () + ;; Just a private scope. + + (define (render-level-2-account level-2-account level-2-balance) + (let ((account-name (gnc:account-get-name level-2-account)) + (type-name (gnc:account-get-type-string + (gnc:account-get-type level-2-account)))) + (string-append + "
    " account-name "" type-name + (sprintf #f " $%10.2f\n" + level-2-balance)))) + + (define (render-level-1-account account level-1-balance level-2-balance) + (let ((name (gnc:account-get-name account)) + (type (gnc:account-get-type-string (gnc:account-get-type account)))) + (string-append + "
    " name "" type + (sprintf #f " $%10.2f" level-2-balance) + (sprintf #f "  $%10.2f \n" + level-1-balance) + "
       \n"))) ;; blank line + + (define (render-total level-0-balance) + (string-append + "
       \n" ;; blank line + "
    Net " + " " + (sprintf #f "  $%10.2f \n" + level-0-balance))) + + (define (generate-balance-sheet-or-pnl report-name + report-description + balance-sheet?) + + ;; currency symbol that is printed is a dollar sign, for now + ;; currency amounts get printed with two decimal places + ;; balance sheet doesn't print income or expense + ;; top-level accounts get printed in right-most column + + ;; This code could definitely be more "schemy", but for now I mostly + ;; just translated it directly from the old ePerl with a few + ;; schemifications. + + (let ((level-0-balance 0) + (level-1-balance 0) + (level-2-balance 0) + (current-group (gnc:get-current-group)) + (output '())) + + (define (handle-level-2-account account) + (let ((type (gnc:account-type->symbol (gnc:account-get-type account))) + (balance (gnc:account-get-balance account))) + + (if (not balance-sheet?) (set! balance (- balance))) + + (if (not (or (and balance-sheet? + (not (eq? type 'INCOME)) + (not (eq? type 'EXPENSE))) + (and (not balance-sheet?) + (or (eq? type 'INCOME) + (eq? type 'EXPENSE))))) + ;; Ignore + '() + + ;; add in balances for any sub-sub groups + (let ((grandchildren (gnc:account-get-children account))) + + (if (not (pointer-token-null? grandchildren)) + (set! balance + ((if balance-sheet? + -) + balance (gnc:group-get-balance grandchildren)))) + + (set! level-2-balance (+ level-2-balance balance)) + (set! level-1-balance (+ level-1-balance level-2-balance)) + (let ((result (render-level-2-account account level-2-balance))) + (set! level-2-balance 0) + result))))) + + (define (handle-level-1-account account) + (let ((type (gnc:account-type->symbol (gnc:account-get-type account)))) + + (if (not (or (and balance-sheet? + (not (eq? type 'INCOME)) + (not (eq? type 'EXPENSE))) + (and (not balance-sheet?) + (or (eq? type 'INCOME) + (eq? type 'EXPENSE))))) + + ;; Ignore + '() + + (let ((childrens-output (gnc:group-map-accounts + handle-level-2-account + (gnc:account-get-children account))) + (account-balance (gnc:account-get-balance account))) + + (if (not balance-sheet?) + (set! account-balance (- account-balance))) + + (set! level-2-balance (+ level-2-balance account-balance)) + (set! level-1-balance (+ level-1-balance account-balance)) + (set! level-0-balance (+ level-0-balance level-1-balance)) + + (let ((level-1-output (render-level-1-account account + level-1-balance + level-2-balance))) + (set! level-1-balance 0) + (set! level-2-balance 0) + (list childrens-output level-1-output)))))) + + (if (not (pointer-token-null? current-group)) + (set! output + (list + (gnc:group-map-accounts handle-level-1-account current-group) + (render-total level-0-balance)))) + + (list + "" + "" + "" report-name "" + "" + + "" + report-description + "

    " + + "" + "" + "
    " report-name "
    Account NameType Balance" + + output + + "
    " + "" + ""))) + + + (gnc:define-report + ;; version + 1 + ;; Menu name + "Balance sheet" + ;; Options (none currently) + #f + ;; Code to generate the report + (lambda (options) + (generate-balance-sheet-or-pnl "Balance Sheet" + "This page shows your net worth." + #t))) + + (gnc:define-report + ;; version + 1 + ;; Menu name + "Profit and Loss" + ;; Options (none currently) + #f + ;; Code to generate the report + (lambda (options) + (generate-balance-sheet-or-pnl "Profit and Loss" + "This page shows your profits and losses." + #f)))) diff --git a/src/scm/report/dummy.scm b/src/scm/report/dummy.scm new file mode 100644 index 0000000000..2a434d30fb --- /dev/null +++ b/src/scm/report/dummy.scm @@ -0,0 +1,60 @@ +;; -*-scheme-*- + +(begin + + (define gnc:*dummy-options* '()) + + (define (gnc:register-dummy-option new-option) + (set! gnc:*dummy-options* + (gnc:register-option gnc:*dummy-options* new-option)) + new-option) + + (define boolop + (gnc:register-dummy-option + (gnc:make-simple-boolean-option + "Page One" "Boolean Option" + "a" "This is a boolean option." #t))) + + (define multop + (gnc:register-dummy-option + (gnc:make-multichoice-option + "Page Two" "Multi Choice Option" + "a" "This is a multi choice option." 'us + (list #(us "US" "US-style: mm/dd/yyyy") + #(uk "UK" "UK-style dd/mm/yyyy") + #(ce "Europe" "Continental Europe: dd.mm.yyyy") + #(iso "ISO" "ISO Standard: yyyy-mm-dd") + #(locale "Locale" "Take from system locale"))))) + + (define strop + (gnc:register-dummy-option + (gnc:make-string-option + "Page Two" "String Option" + "b" "This is a string option" "Hello, World."))) + + (define (op-value op) + (let ((getter (gnc:option-getter op))) + (getter))) + + (gnc:define-report + ;; version + 1 + ;; Menu name + "Dummy" + ;; Options + gnc:*dummy-options* + ;; Rendering thunk. See report.scm for details. + (lambda (options) + (list + "" + "" + "The current time is " (strftime "%c" (localtime (current-time))) "." + "
    " + "The boolean op is " (if (op-value boolop) "true." "false.") + "
    " + "The multi op is " (symbol->string (op-value multop)) + "
    " + "The string op is " (op-value strop) + "" + ""))) + ) diff --git a/src/scm/report/folio.scm b/src/scm/report/folio.scm new file mode 100644 index 0000000000..8596e87cdb --- /dev/null +++ b/src/scm/report/folio.scm @@ -0,0 +1,163 @@ + +;; I haven't finished converting this yet... + +(gnc:define-report + ;; version + 1 + ;; Menu name + "Folio" + ;; Options + #f + ;; Rendering thunk. See report.scm for details. + (lambda (options) + (list + "" + "" + "Portfolio Valuation" + "" + + "" + "This page shows the valuation of your stock/mutual fund portfolio." + "
    " + "You can create custom reports by editing the file" + "Reports/report-folio.phtml" + "

    " + + ;; currency symbol that is printed is a dollar sign, for now + ;; currency amounts get printed with two decimal places + + ;; require ... + ;; hack alert -- need a require here, since the folowing routine(s) + ;; are identical to those in gnc-price script. + ;; -------------------------------------------------- + ;; @account_list = &account_flatlist ($account_group); + ;; This rouine accepts a pointer to a group, returns + ;; a flat list of all of the children in the group. + +sub account_flatlist +{ + local ($grp) = $_[0]; + local ($naccts) = gnucash::xaccGroupGetNumAccounts ($grp); + local ($n); + local (@acctlist, @childlist); + local ($children); + + foreach $n (0..$naccts-1) { + $acct = gnucash::xaccGroupGetAccount ($grp, $n); + push (@acctlist, $acct); + $children = gnucash::xaccAccountGetChildren ($acct); + if ($children) { + @childlist = &account_flatlist ($children); + push (@acctlist, @childlist); + } + } + + return (@acctlist); +} + +;; -------------------------------------------------- +;; $split = &get_last_split ($account); +;; returns the most recent split in the account. + +sub get_last_split +{ + local ($acct) = $_[0]; + local ($query, $splitlist, $split); + + $query = gnucash::xaccMallocQuery(); + gnucash::xaccQueryAddAccount ($query, $acct); + gnucash::xaccQuerySetMaxSplits ($query, 1); + $splitlist = gnucash::xaccQueryGetSplits ($query); + + $split = gnucash::IthSplit ($splitlist, 0); +} + +;; -------------------------------------------------- + +;; get a flat list of all the accounts ... +@acclist = &account_flatlist ($topgroup); + +;; get the most recent price date .. +$latest = -1.0e20; +$earliest = 1.0e20; +foreach $acct (@acclist) +{ + $accntype = &gnucash::xaccAccountGetType($acct); + if (($accntype == $gnucash::STOCK) || + ($accntype == $gnucash::MUTUAL)) { + $split = &get_last_split ($acct); + $trans = gnucash::xaccSplitGetParent ($split); + $secs = gnucash::xaccTransGetDate ($trans); + if ($latest < $secs) { $latest = $secs; } + if ($earliest > $secs) { $earliest = $secs; } + } +} + +$ldayte = gnucash::xaccPrintDateSecs ($latest); +$edayte = gnucash::xaccPrintDateSecs ($earliest); + +:> + + + + +
    Stock Portfolio Valuation +
    Earliest Price <:= $edayte :>     Latest Price <:= $ldayte :> +
    Name +Ticker +Shares +Recent Price +Value +Cost +Profit/Loss + +<: + +$totvalue = 0; +$totcost = 0; + +foreach $acct (@acclist) +{ + + $accntype = &gnucash::xaccAccountGetType($acct); + if (($accntype == $gnucash::STOCK) || + ($accntype == $gnucash::MUTUAL)) { + + $accname = &gnucash::xaccAccountGetName($acct); + $ticker = &gnucash::xaccAccountGetSecurity ($acct); + $accbaln = &gnucash::xaccAccountGetBalance($acct); + + $split = &get_last_split ($acct); + $price = gnucash::xaccSplitGetSharePrice ($split); + $shares = gnucash::xaccSplitGetShareBalance ($split); + $value = gnucash::xaccSplitGetBalance ($split); + $cost = gnucash::xaccSplitGetCostBasis ($split); + $profit = $accbaln - $cost; + + $totvalue += $value; + $totcost += $cost; + + print "
    $accname"; + print "$ticker"; + printf "%10.3f", $shares; + printf "\$%10.2f\n", $price; + printf "\$%10.2f\n", $value; + printf "\$%10.2f\n", $cost; + printf "\$%10.2f\n", $profit; + } +} + +print "
       \n"; ;; blank line +print "   \n"; ;; blank line + +print "
    Net "; +print "  "; +printf "  \$%10.2f \n", $totvalue; +printf "  \$%10.2f \n", $totcost; +printf "  \$%10.2f \n", $totvalue-$totcost; + +:> + +
    + + diff --git a/src/scm/report/transaction-report.scm b/src/scm/report/transaction-report.scm new file mode 100644 index 0000000000..e32b65a3bb --- /dev/null +++ b/src/scm/report/transaction-report.scm @@ -0,0 +1,69 @@ +;; -*-scheme-*- + + +;; something like +;; for(i = first; i < last; i+= step) { thunk(i);} + +(define (gnc:for-loop thunk first last step) + (cond ((< first last) (thunk first) + (gnc:for-loop thunk (+ first step) last step)) + (else #f))) + +;; applies thunk to each split in account account +(define (gnc:for-each-split-in-account account thunk) + (gnc:for-loop (lambda (x) (thunk (gnc:account-get-split account x))) + 0 (gnc:account-get-split-count account) 1)) + + +(define (gnc:split-get-corresponding-account-name-and-values split) + (list (cons "Not implemented yet." 0))) + +(define (gnc:split-get-transaction-date split) + (gnc:transaction-get-date-posted (gnc:split-get-parent split))) + +(define (gnc:split-get-description-from-parent split) + (gnc:transaction-get-description (gnc:split-get-parent split))) + +(define (gnc:make-split-scheme-data split) + (vector (gnc:split-get-memo split) + (gnc:split-get-action split) + (gnc:split-get-description-from-parent split) + (gnc:split-get-transaction-date split) + (gnc:split-get-reconcile-state split) + (gnc:split-get-reconciled-date split) + (gnc:split-get-share-amount split) + (gnc:split-get-share-amount split) + (gnc:split-get-share-price split) + (gnc:split-get-value split) + (gnc:split-get-docref split) + (gnc:split-get-corresponding-account-name-and-values split))) + +(define (gnc:split-represent-scheme-data-textually split) + (call-with-output-string (lambda (x) (write (gnc:make-split-scheme-data split) x)))) + +(gnc:define-report +;; version + 1 + ;; Name + "Account Transactions" + ;; Options + #f + ;; renderer + (lambda (options) + (let ( (test-account (gnc:group-get-account (gnc:get-current-group) 0)) + (prefix (list "" "" "

    "))
    +	  (suffix  (list "
    " "" "")) + (report-lines (list))) + + (gnc:for-each-split-in-account + test-account + (lambda (split) +; (newline) +; (write report-lines) + (set! report-lines (append! report-lines (list (gnc:split-represent-scheme-data-textually split)))))) +; (write prefix) +; (newline) +; (write suffix) +; (newline) +; (write report-lines) + (append prefix report-lines suffix)))) diff --git a/src/scm/srfi/README b/src/scm/srfi/README new file mode 100644 index 0000000000..e725923a35 --- /dev/null +++ b/src/scm/srfi/README @@ -0,0 +1,33 @@ + +These files are copies from a distribution I'm building that includes +some of the srfis implemented for both rscheme and guile. I've +changed a few things to be more GnuCash specific... + +Below is the README from that distribution. + +Each SRFI may have the following related files: + + srfi-N.html Offical upstream SRFI document. + srfi-N.ref.scm Untouched reference implementation, if any. + + srfi-N.rXrs.scm Reference implementation w/o non-standard code. + srfi-N..pre.scm Preload file for architecture . + srfi-N..post.scm Postload file for architecture . + srfi-N..scm Specialized version for architecture . + + srfi-N.unclear.scm Standard definitions of unclearly defined functions. + srfi-N..unclear.scm Unclearly defined functions for architecture . + +Here means something like guile or rscheme, and if you find a +specialized file, then you don't need the rXrs version. However, if +there is a preload and/or a postload file, then you should load those +along with the rXrs file to get a complete implementation. + +The .unclear.scm files will contain code to (often trivially) +implement functions required by the SRFI but whose "correct" +implemention is not well defined. This will includes things like an +"error" function. It is expected that you will often want to just +ignore the .unclear.scm files and define the functions they contain +locally in a way that makes sense for your particular project. If +there is a .unclear.scm file for your architecture, then you should +probably choose that over the more general file. diff --git a/src/scm/srfi/srfi-1.r5rs.scm b/src/scm/srfi/srfi-1.r5rs.scm new file mode 100644 index 0000000000..914b282037 --- /dev/null +++ b/src/scm/srfi/srfi-1.r5rs.scm @@ -0,0 +1,1614 @@ +;;; SRFI-1 list-processing library -*- Scheme -*- +;;; Reference implementation +;;; +;;; Copyright (c) 1998, 1999 by Olin Shivers. You may do as you please with +;;; this code as long as you do not remove this copyright notice or +;;; hold me liable for its use. Please send bug reports to shivers@ai.mit.edu. +;;; -Olin + +;;; Modifications to make the code more portable are +;;; Copyright 1999, Rob Browning . You may do as +;;; you please with this code as long as you do not remove this +;;; copyright notice or hold me liable for its use. + +;;; This is a library of list- and pair-processing functions. I wrote it after +;;; carefully considering the functions provided by the libraries found in +;;; R4RS/R5RS Scheme, MIT Scheme, Gambit, RScheme, MzScheme, slib, Common +;;; Lisp, Bigloo, guile, T, APL and the SML standard basis. It is a pretty +;;; rich toolkit, providing a superset of the functionality found in any of +;;; the various Schemes I considered. + +;;; This implementation is intended as a portable reference implementation +;;; for SRFI-1. See the porting notes below for more information. + +;;; Exported: +;;; xcons tree-copy make-list list-tabulate cons* list-copy +;;; proper-list? circular-list? dotted-list? not-pair? null-list? list= +;;; circular-list length+ +;;; iota +;;; first second third fourth fifth sixth seventh eighth ninth tenth +;;; car+cdr +;;; take drop +;;; take-right drop-right +;;; take! drop-right! +;;; split-at split-at! +;;; last last-pair +;;; zip unzip1 unzip2 unzip3 unzip4 unzip5 +;;; count +;;; append! append-reverse append-reverse! concatenate concatenate! +;;; unfold fold pair-fold reduce +;;; unfold-right fold-right pair-fold-right reduce-right +;;; append-map append-map! map! pair-for-each filter-map map-in-order +;;; filter partition remove +;;; filter! partition! remove! +;;; find find-tail any every list-index +;;; take-while drop-while take-while! +;;; span break span! break! +;;; delete delete! +;;; alist-cons alist-copy +;;; delete-duplicates delete-duplicates! +;;; alist-delete alist-delete! +;;; reverse! +;;; lset<= lset= lset-adjoin +;;; lset-union lset-intersection lset-difference lset-xor lset-diff+intersection +;;; lset-union! lset-intersection! lset-difference! lset-xor! lset-diff+intersection! +;;; +;;; In principle, the following R4RS list- and pair-processing procedures +;;; are also part of this package's exports, although they are not defined +;;; in this file: +;;; Primitives: cons pair? null? car cdr set-car! set-cdr! +;;; Non-primitives: list length append reverse cadr ... cddddr list-ref +;;; memq memv assq assv +;;; (The non-primitives are defined in this file, but commented out.) +;;; +;;; These R4RS procedures have extended definitions in SRFI-1 and are defined +;;; in this file: +;;; map for-each member assoc +;;; +;;; The remaining two R4RS list-processing procedures are not included: +;;; list-tail (use drop) +;;; list? (use proper-list?) + + +;;; A note on recursion and iteration/reversal: +;;; Many iterative list-processing algorithms naturally compute the elements +;;; of the answer list in the wrong order (left-to-right or head-to-tail) from +;;; the order needed to cons them into the proper answer (right-to-left, or +;;; tail-then-head). One style or idiom of programming these algorithms, then, +;;; loops, consing up the elements in reverse order, then destructively +;;; reverses the list at the end of the loop. I do not do this. The natural +;;; and efficient way to code these algorithms is recursively. This trades off +;;; intermediate temporary list structure for intermediate temporary stack +;;; structure. In a stack-based system, this improves cache locality and +;;; lightens the load on the GC system. Don't stand on your head to iterate! +;;; Recurse, where natural. Multiple-value returns make this even more +;;; convenient, when the recursion/iteration has multiple state values. + +;;; Porting: +;;; This is carefully tuned code; do not modify casually. +;;; - It is careful to share storage when possible; +;;; - Side-effecting code tries not to perform redundant writes. +;;; +;;; That said, a port of this library to a specific Scheme system might wish +;;; to tune this code to exploit particulars of the implementation. +;;; The single most important compiler-specific optimisation you could make +;;; to this library would be to add rewrite rules or transforms to: +;;; - transform applications of n-ary procedures (e.g. LIST=, CONS*, APPEND, +;;; LSET-UNION) into multiple applications of a primitive two-argument +;;; variant. +;;; - transform applications of the mapping functions (MAP, FOR-EACH, FOLD, +;;; ANY, EVERY) into open-coded loops. The killer here is that these +;;; functions are n-ary. Handling the general case is quite inefficient, +;;; requiring many intermediate data structures to be allocated and +;;; discarded. +;;; - transform applications of procedures that take optional arguments +;;; into calls to variants that do not take optional arguments. This +;;; eliminates unnecessary consing and parsing of the rest parameter. +;;; +;;; These transforms would provide BIG speedups. In particular, the n-ary +;;; mapping functions are particularly slow and cons-intensive, and are good +;;; candidates for tuning. I have coded fast paths for the single-list cases, +;;; but what you really want to do is exploit the fact that the compiler +;;; usually knows how many arguments are being passed to a particular +;;; application of these functions -- they are usually explicitly called, not +;;; passed around as higher-order values. If you can arrange to have your +;;; compiler produce custom code or custom linkages based on the number of +;;; arguments in the call, you can speed these functions up a *lot*. But this +;;; kind of compiler technology no longer exists in the Scheme world as far as +;;; I can see. +;;; +;;; Note that this code is, of course, dependent upon standard bindings for +;;; the R5RS procedures -- i.e., it assumes that the variable CAR is bound +;;; to the procedure that takes the car of a list. If your Scheme +;;; implementation allows user code to alter the bindings of these procedures +;;; in a manner that would be visible to these definitions, then there might +;;; be trouble. You could consider horrible kludgery along the lines of +;;; (define fact +;;; (let ((= =) (- -) (* *)) +;;; (letrec ((real-fact (lambda (n) +;;; (if (= n 0) 1 (* n (real-fact (- n 1))))))) +;;; real-fact))) +;;; Or you could consider shifting to a reasonable Scheme system that, say, +;;; has a module system protecting code from this kind of lossage. +;;; +;;; This code does a fair amount of run-time argument checking. If your +;;; Scheme system has a sophisticated compiler that can eliminate redundant +;;; error checks, this is no problem. However, if not, these checks incur +;;; some performance overhead -- and, in a safe Scheme implementation, they +;;; are in some sense redundant: if we don't check to see that the PROC +;;; parameter is a procedure, we'll find out anyway three lines later when +;;; we try to call the value. It's pretty easy to rip all this argument +;;; checking code out if it's inappropriate for your implementation -- just +;;; nuke every call to CHECK-ARG. +;;; +;;; On the other hand, if you *do* have a sophisticated compiler that will +;;; actually perform soft-typing and eliminate redundant checks (Rice's systems +;;; being the only possible candidate of which I'm aware), leaving these checks +;;; in can *help*, since their presence can be elided in redundant cases, +;;; and in cases where they are needed, performing the checks early, at +;;; procedure entry, can "lift" a check out of a loop. +;;; +;;; Finally, I have only checked the properties that can portably be checked +;;; with R5RS Scheme -- and this is not complete. You may wish to alter +;;; the CHECK-ARG parameter checks to perform extra, implementation-specific +;;; checks, such as procedure arity for higher-order values. +;;; +;;; The code has only these non-R4RS dependencies: +;;; A few calls to an ERROR procedure; +;;; Uses of the R5RS multiple-value procedure VALUES and the m-v binding +;;; RECEIVE macro (which isn't R5RS, but is a trivial macro). +;;; Many calls to a parameter-checking procedure check-arg: +;;; (define (check-arg pred val caller) +;;; (let lp ((val val)) +;;; (if (pred val) val (lp (error "Bad argument" val pred caller))))) +;;; +;;; Most of these procedures use the NULL-LIST? test to trigger the +;;; base case in the inner loop or recursion. The NULL-LIST? function +;;; is defined to be a careful one -- it raises an error if passed a +;;; non-nil, non-pair value. The spec allows an implementation to use +;;; a less-careful implementation that simply defines NULL-LIST? to +;;; be NOT-PAIR?. This would speed up the inner loops of these procedures +;;; at the expense of having them silently accept dotted lists. + +;;; A note on dotted lists: +;;; I, personally, take the view that the only consistent view of lists +;;; in Scheme is the view that *everything* is a list -- values such as +;;; 3 or "foo" or 'bar are simply empty dotted lists. This is due to the +;;; fact that Scheme actually has no true list type. It has a pair type, +;;; and there is an *interpretation* of the trees built using this type +;;; as lists. +;;; +;;; I lobbied to have these list-processing procedures hew to this +;;; view, and accept any value as a list argument. I was overwhelmingly +;;; overruled during the SRFI discussion phase. So I am inserting this +;;; text in the reference lib and the SRFI spec as a sort of "minority +;;; opinion" dissent. +;;; +;;; Many of the procedures in this library can be trivially redefined +;;; to handle dotted lists, just by changing the NULL-LIST? base-case +;;; check to NOT-PAIR?, meaning that any non-pair value is taken to be +;;; an empty list. For most of these procedures, that's all that is +;;; required. +;;; +;;; However, we have to do a little more work for some procedures that +;;; *produce* lists from other lists. Were we to extend these procedures to +;;; accept dotted lists, we would have to define how they terminate the lists +;;; produced as results when passed a dotted list. I designed a coherent set +;;; of termination rules for these cases; this was posted to the SRFI-1 +;;; discussion list. I additionally wrote an earlier version of this library +;;; that implemented that spec. It has been discarded during later phases of +;;; the definition and implementation of this library. +;;; +;;; The argument *against* defining these procedures to work on dotted +;;; lists is that dotted lists are the rare, odd case, and that by +;;; arranging for the procedures to handle them, we lose error checking +;;; in the cases where a dotted list is passed by accident -- e.g., when +;;; the programmer swaps a two arguments to a list-processing function, +;;; one being a scalar and one being a list. For example, +;;; (member '(1 3 5 7 9) 7) +;;; This would quietly return #f if we extended MEMBER to accept dotted +;;; lists. +;;; +;;; The SRFI discussion record contains more discussion on this topic. + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Modifications from the "official" implementation. +;;; +;;; Removed all non r5rs-isms that I detected (i.e :optional and let-optionals). +;;; +;;; Renamed error to srfi-1:error +;;; Renamed check-arg to srfi-1:check-arg +;;; + + +;;; Constructors +;;;;;;;;;;;;;;;; + +;;; Occasionally useful as a value to be passed to a fold or other +;;; higher-order procedure. +(define (xcons d a) (cons a d)) + +;;;; Recursively copy every cons. +;(define (tree-copy x) +; (let recur ((x x)) +; (if (not (pair? x)) x +; (cons (recur (car x)) (recur (cdr x)))))) + +;;; Make a list of length LEN. + +(define (make-list len . maybe-elt) + (srfi-1:check-arg (lambda (n) (and (integer? n) (>= n 0))) len make-list) + (let ((elt (cond ((null? maybe-elt) #f) ; Default value + ((null? (cdr maybe-elt)) (car maybe-elt)) + (else (srfi-1:error "Too many arguments to MAKE-LIST" + (cons len maybe-elt)))))) + (do ((i len (- i 1)) + (ans '() (cons elt ans))) + ((<= i 0) ans)))) + + +;(define (list . ans) ans) ; R4RS + + +;;; Make a list of length LEN. Elt i is (PROC i) for 0 <= i < LEN. + +(define (list-tabulate len proc) + (srfi-1:check-arg (lambda (n) (and (integer? n) (>= n 0))) len list-tabulate) + (srfi-1:check-arg procedure? proc list-tabulate) + (do ((i (- len 1) (- i 1)) + (ans '() (cons (proc i) ans))) + ((< i 0) ans))) + +;;; (cons* a1 a2 ... an) = (cons a1 (cons a2 (cons ... an))) +;;; (cons* a1) = a1 (cons* a1 a2 ...) = (cons a1 (cons* a2 ...)) +;;; +;;; (cons first (unfold not-pair? car cdr rest values)) + +(define (cons* first . rest) + (let recur ((x first) (rest rest)) + (if (pair? rest) + (cons x (recur (car rest) (cdr rest))) + x))) + +;;; (unfold not-pair? car cdr lis values) + +(define (list-copy lis) + (let recur ((lis lis)) + (if (pair? lis) + (cons (car lis) (recur (cdr lis))) + lis))) + +;;; IOTA count [start step] (start start+step ... start+(count-1)*step) + +(define (iota count . maybe-start+step) + + (define (helper start step) + (srfi-1:check-arg number? start iota) + (srfi-1:check-arg number? step iota) + (let ((last-val (+ start (* (- count 1) step)))) + (do ((count count (- count 1)) + (val last-val (- val step)) + (ans '() (cons val ans))) + ((<= count 0) ans)))) + + (srfi-1:check-arg integer? count iota) + (if (< count 0) (srfi-1:error "Negative step count" iota count)) + + (if (pair? maybe-start+step) + (helper (car maybe-start+step) (cadr maybe-start+step)) + (helper 0 1))) + +;;; I thought these were lovely, but the public at large did not share my +;;; enthusiasm... +;;; :IOTA to (0 ... to-1) +;;; :IOTA from to (from ... to-1) +;;; :IOTA from to step (from from+step ...) + +;;; IOTA: to (1 ... to) +;;; IOTA: from to (from+1 ... to) +;;; IOTA: from to step (from+step from+2step ...) + +;(define (%parse-iota-args arg1 rest-args proc) +; (let ((check (lambda (n) (srfi-1:check-arg integer? n proc)))) +; (check arg1) +; (if (pair? rest-args) +; (let ((arg2 (check (car rest-args))) +; (rest (cdr rest-args))) +; (if (pair? rest) +; (let ((arg3 (check (car rest))) +; (rest (cdr rest))) +; (if (pair? rest) (srfi-1:error "Too many parameters" proc arg1 rest-args) +; (values arg1 arg2 arg3))) +; (values arg1 arg2 1))) +; (values 0 arg1 1)))) +; +;(define (iota: arg1 . rest-args) +; (receive (from to step) (%parse-iota-args arg1 rest-args iota:) +; (let* ((numsteps (floor (/ (- to from) step))) +; (last-val (+ from (* step numsteps)))) +; (if (< numsteps 0) (srfi-1:error "Negative step count" iota: from to step)) +; (do ((steps-left numsteps (- steps-left 1)) +; (val last-val (- val step)) +; (ans '() (cons val ans))) +; ((<= steps-left 0) ans))))) +; +; +;(define (:iota arg1 . rest-args) +; (receive (from to step) (%parse-iota-args arg1 rest-args :iota) +; (let* ((numsteps (ceiling (/ (- to from) step))) +; (last-val (+ from (* step (- numsteps 1))))) +; (if (< numsteps 0) (srfi-1:error "Negative step count" :iota from to step)) +; (do ((steps-left numsteps (- steps-left 1)) +; (val last-val (- val step)) +; (ans '() (cons val ans))) +; ((<= steps-left 0) ans))))) + + + +(define (circular-list val1 . vals) + (let ((ans (cons val1 vals))) + (set-cdr! (last-pair ans) ans) + ans)) + +;;; ::= () ; Empty proper list +;;; | (cons ) ; Proper-list pair +;;; Note that this definition rules out circular lists -- and this +;;; function is required to detect this case and return false. + +(define (proper-list? x) + (let lp ((x x) (lag x)) + (if (pair? x) + (let ((x (cdr x))) + (if (pair? x) + (let ((x (cdr x)) + (lag (cdr lag))) + (and (not (eq? x lag)) (lp x lag))) + (null? x))) + (null? x)))) + + +;;; A dotted list is a finite list (possibly of length 0) terminated +;;; by a non-nil value. Any non-cons, non-nil value (e.g., "foo" or 5) +;;; is a dotted list of length 0. +;;; +;;; ::= ; Empty dotted list +;;; | (cons ) ; Proper-list pair + +(define (dotted-list? x) + (let lp ((x x) (lag x)) + (if (pair? x) + (let ((x (cdr x))) + (if (pair? x) + (let ((x (cdr x)) + (lag (cdr lag))) + (and (not (eq? x lag)) (lp x lag))) + (not (null? x)))) + (not (null? x))))) + +(define (circular-list? x) + (let lp ((x x) (lag x)) + (and (pair? x) + (let ((x (cdr x))) + (and (pair? x) + (let ((x (cdr x)) + (lag (cdr lag))) + (or (eq? x lag) (lp x lag)))))))) + +(define (not-pair? x) (not (pair? x))) ; Inline me. + +;;; This is a legal definition which is fast and sloppy: +;;; (define null-list? not-pair?) +;;; but we'll provide a more careful one: +(define (null-list? l) + (cond ((pair? l) #f) + ((null? l) #t) + (else (srfi-1:error "null-pair?: argument out of domain" l)))) + + +(define (list= = . lists) + (or (null? lists) ; special case + + (let lp1 ((list-a (car lists)) (others (cdr lists))) + (or (null? others) + (let ((list-b (car others)) + (others (cdr others))) + (if (eq? list-a list-b) ; EQ? => LIST= + (lp1 list-b others) + (let lp2 ((list-a list-a) (list-b list-b)) + (if (null-list? list-a) + (and (null-list? list-b) + (lp1 list-b others)) + (and (not (null-list? list-b)) + (= (car list-a) (car list-b)) + (lp2 (cdr list-a) (cdr list-b))))))))))) + + + +;;; R4RS, so commented out. +;(define (length x) ; LENGTH may diverge or +; (let lp ((x x) (len 0)) ; raise an error if X is +; (if (pair? x) ; a circular list. This version +; (lp (cdr x) (+ len 1)) ; diverges. +; len))) + +(define (length+ x) ; Returns #f if X is circular. + (let lp ((x x) (lag x) (len 0)) + (if (pair? x) + (let ((x (cdr x)) + (len (+ len 1))) + (if (pair? x) + (let ((x (cdr x)) + (lag (cdr lag)) + (len (+ len 1))) + (and (not (eq? x lag)) (lp x lag len))) + len)) + len))) + +(define (zip list1 . more-lists) (apply map list list1 more-lists)) + + +;;; Selectors +;;;;;;;;;;;;; + +;;; R4RS non-primitives: +;(define (caar x) (car (car x))) +;(define (cadr x) (car (cdr x))) +;(define (cdar x) (cdr (car x))) +;(define (cddr x) (cdr (cdr x))) +; +;(define (caaar x) (caar (car x))) +;(define (caadr x) (caar (cdr x))) +;(define (cadar x) (cadr (car x))) +;(define (caddr x) (cadr (cdr x))) +;(define (cdaar x) (cdar (car x))) +;(define (cdadr x) (cdar (cdr x))) +;(define (cddar x) (cddr (car x))) +;(define (cdddr x) (cddr (cdr x))) +; +;(define (caaaar x) (caaar (car x))) +;(define (caaadr x) (caaar (cdr x))) +;(define (caadar x) (caadr (car x))) +;(define (caaddr x) (caadr (cdr x))) +;(define (cadaar x) (cadar (car x))) +;(define (cadadr x) (cadar (cdr x))) +;(define (caddar x) (caddr (car x))) +;(define (cadddr x) (caddr (cdr x))) +;(define (cdaaar x) (cdaar (car x))) +;(define (cdaadr x) (cdaar (cdr x))) +;(define (cdadar x) (cdadr (car x))) +;(define (cdaddr x) (cdadr (cdr x))) +;(define (cddaar x) (cddar (car x))) +;(define (cddadr x) (cddar (cdr x))) +;(define (cdddar x) (cdddr (car x))) +;(define (cddddr x) (cdddr (cdr x))) + + +(define first car) +(define second cadr) +(define third caddr) +(define fourth cadddr) +(define (fifth x) (car (cddddr x))) +(define (sixth x) (cadr (cddddr x))) +(define (seventh x) (caddr (cddddr x))) +(define (eighth x) (cadddr (cddddr x))) +(define (ninth x) (car (cddddr (cddddr x)))) +(define (tenth x) (cadr (cddddr (cddddr x)))) + +(define (car+cdr pair) (values (car pair) (cdr pair))) + +;;; take & drop + +(define (take lis k) + (srfi-1:check-arg integer? k take) + (let recur ((lis lis) (k k)) + (if (zero? k) '() + (cons (car lis) + (recur (cdr lis) (- k 1)))))) + +(define (drop lis k) + (srfi-1:check-arg integer? k drop) + (let iter ((lis lis) (k k)) + (if (zero? k) lis (iter (cdr lis) (- k 1))))) + +(define (take! lis k) + (srfi-1:check-arg integer? k take!) + (if (zero? k) '() + (begin (set-cdr! (drop lis (- k 1)) '()) + lis))) + +;;; TAKE-RIGHT and DROP-RIGHT work by getting two pointers into the list, +;;; off by K, then chasing down the list until the lead pointer falls off +;;; the end. + +(define (take-right lis k) + (srfi-1:check-arg integer? k take-right) + (let lp ((lag lis) (lead (drop lis k))) + (if (pair? lead) + (lp (cdr lag) (cdr lead)) + lag))) + +(define (drop-right lis k) + (srfi-1:check-arg integer? k drop-right) + (let recur ((lag lis) (lead (drop lis k))) + (if (pair? lead) + (cons (car lag) (recur (cdr lag) (cdr lead))) + '()))) + +;;; In this function, LEAD is actually K+1 ahead of LAG. This lets +;;; us stop LAG one step early, in time to smash its cdr to (). +(define (drop-right! lis k) + (srfi-1:check-arg integer? k drop-right!) + (let ((lead (drop lis k))) + (if (pair? lead) + + (let lp ((lag lis) (lead (cdr lead))) ; Standard case + (if (pair? lead) + (lp (cdr lag) (cdr lead)) + (begin (set-cdr! lag '()) + lis))) + + '()))) ; Special case dropping everything -- no cons to side-effect. + +;(define (list-ref lis i) (car (drop lis i))) ; R4RS + +;;; These use the APL convention, whereby negative indices mean +;;; "from the right." I liked them, but they didn't win over the +;;; SRFI reviewers. +;;; K >= 0: Take and drop K elts from the front of the list. +;;; K <= 0: Take and drop -K elts from the end of the list. + +;(define (take lis k) +; (srfi-1:check-arg integer? k take) +; (if (negative? k) +; (list-tail lis (+ k (length lis))) +; (let recur ((lis lis) (k k)) +; (if (zero? k) '() +; (cons (car lis) +; (recur (cdr lis) (- k 1))))))) +; +;(define (drop lis k) +; (srfi-1:check-arg integer? k drop) +; (if (negative? k) +; (let recur ((lis lis) (nelts (+ k (length lis)))) +; (if (zero? nelts) '() +; (cons (car lis) +; (recur (cdr lis) (- nelts 1))))) +; (list-tail lis k))) +; +; +;(define (take! lis k) +; (srfi-1:check-arg integer? k take!) +; (cond ((zero? k) '()) +; ((positive? k) +; (set-cdr! (list-tail lis (- k 1)) '()) +; lis) +; (else (list-tail lis (+ k (length lis)))))) +; +;(define (drop! lis k) +; (srfi-1:check-arg integer? k drop!) +; (if (negative? k) +; (let ((nelts (+ k (length lis)))) +; (if (zero? nelts) '() +; (begin (set-cdr! (list-tail lis (- nelts 1)) '()) +; lis))) +; (list-tail lis k))) + +(define (split-at x k) + (srfi-1:check-arg integer? k split-at) + (let recur ((lis x) (k k)) + (if (zero? k) (values '() lis) + (receive (prefix suffix) (recur (cdr lis) (- k 1)) + (values (cons (car lis) prefix) suffix))))) + +(define (split-at! x k) + (srfi-1:check-arg integer? k split-at!) + (if (zero? k) (values '() x) + (let* ((prev (drop x (- k 1))) + (suffix (cdr prev))) + (set-cdr! prev '()) + (values x suffix)))) + + +(define (last lis) (car (last-pair lis))) + +(define (last-pair lis) + (srfi-1:check-arg pair? lis last-pair) + (let lp ((lis lis)) + (let ((tail (cdr lis))) + (if (pair? tail) (lp tail) lis)))) + + +;;; Unzippers -- 1 through 5 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define (unzip1 lis) (map car lis)) + +(define (unzip2 lis) + (let recur ((lis lis)) + (if (null-list? lis) (values lis lis) ; Use NOT-PAIR? to handle + (let ((elt (car lis))) ; dotted lists. + (receive (a b) (recur (cdr lis)) + (values (cons (car elt) a) + (cons (cadr elt) b))))))) + +(define (unzip3 lis) + (let recur ((lis lis)) + (if (null-list? lis) (values lis lis lis) + (let ((elt (car lis))) + (receive (a b c) (recur (cdr lis)) + (values (cons (car elt) a) + (cons (cadr elt) b) + (cons (caddr elt) c))))))) + +(define (unzip4 lis) + (let recur ((lis lis)) + (if (null-list? lis) (values lis lis lis lis) + (let ((elt (car lis))) + (receive (a b c d) (recur (cdr lis)) + (values (cons (car elt) a) + (cons (cadr elt) b) + (cons (caddr elt) c) + (cons (cadddr elt) d))))))) + +(define (unzip5 lis) + (let recur ((lis lis)) + (if (null-list? lis) (values lis lis lis lis lis) + (let ((elt (car lis))) + (receive (a b c d e) (recur (cdr lis)) + (values (cons (car elt) a) + (cons (cadr elt) b) + (cons (caddr elt) c) + (cons (cadddr elt) d) + (cons (car (cddddr elt)) e))))))) + + +;;; append! append-reverse append-reverse! concatenate concatenate! +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define (append! . lists) + ;; First, scan through lists looking for a non-empty one. + (let lp ((lists lists) (prev '())) + (if (not (pair? lists)) prev + (let ((first (car lists)) + (rest (cdr lists))) + (if (not (pair? first)) (lp rest first) + + ;; Now, do the splicing. + (let lp2 ((tail-cons (last-pair first)) + (rest rest)) + (if (pair? rest) + (let ((next (car rest)) + (rest (cdr rest))) + (set-cdr! tail-cons next) + (lp2 (if (pair? next) (last-pair next) tail-cons) + rest)) + first))))))) + +;;; APPEND is R4RS. +;(define (append . lists) +; (if (pair? lists) +; (let recur ((list1 (car lists)) (lists (cdr lists))) +; (if (pair? lists) +; (let ((tail (recur (car lists) (cdr lists)))) +; (fold-right cons tail list1)) ; Append LIST1 & TAIL. +; list1)) +; '())) + +;(define (append-reverse rev-head tail) (fold cons tail rev-head)) + +;(define (append-reverse! rev-head tail) +; (pair-fold (lambda (pair tail) (set-cdr! pair tail) pair) +; tail +; rev-head)) + +;;; Hand-inline the FOLD and PAIR-FOLD ops for speed. + +(define (append-reverse rev-head tail) + (let lp ((rev-head rev-head) (tail tail)) + (if (null-list? rev-head) tail + (lp (cdr rev-head) (cons (car rev-head) tail))))) + +(define (append-reverse! rev-head tail) + (let lp ((rev-head rev-head) (tail tail)) + (if (null-list? rev-head) tail + (let ((next-rev (cdr rev-head))) + (set-cdr! rev-head tail) + (lp next-rev rev-head))))) + + +(define (concatenate lists) (reduce-right append '() lists)) +(define (concatenate! lists) (reduce-right append! '() lists)) + +;;; Fold/map internal utilities +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; These little internal utilities are used by the general +;;; fold & mapper funs for the n-ary cases . It'd be nice if they got inlined. +;;; One the other hand, the n-ary cases are painfully inefficient as it is. +;;; An aggressive implementation should simply re-write these functions +;;; for raw efficiency; I have written them for as much clarity, portability, +;;; and simplicity as can be achieved. +;;; +;;; I use the dreaded call/cc to do local aborts. A good compiler could +;;; handle this with extreme efficiency. An implementation that provides +;;; a one-shot, non-persistent continuation grabber could help the compiler +;;; out by using that in place of the call/cc's in these routines. +;;; +;;; These functions have funky definitions that are precisely tuned to +;;; the needs of the fold/map procs -- for example, to minimize the number +;;; of times the argument lists need to be examined. + +;;; Return (map cdr lists). +;;; However, if any element of LISTS is empty, just abort and return '(). +(define (%cdrs lists) + (call-with-current-continuation + (lambda (abort) + (let recur ((lists lists)) + (if (pair? lists) + (let ((lis (car lists))) + (if (null-list? lis) (abort '()) + (cons (cdr lis) (recur (cdr lists))))) + '()))))) + +(define (%cars+ lists last-elt) ; (append! (map car lists) (list last-elt)) + (let recur ((lists lists)) + (if (pair? lists) (cons (caar lists) (recur (cdr lists))) (list last-elt)))) + +;;; LISTS is a (not very long) non-empty list of lists. +;;; Return two lists: the cars & the cdrs of the lists. +;;; However, if any of the lists is empty, just abort and return [() ()]. + +(define (%cars+cdrs lists) + (call-with-current-continuation + (lambda (abort) + (let recur ((lists lists)) + (if (pair? lists) + (receive (list other-lists) (car+cdr lists) + (if (null-list? list) (abort '() '()) ; LIST is empty -- bail out + (receive (a d) (car+cdr list) + (receive (cars cdrs) (recur other-lists) + (values (cons a cars) (cons d cdrs)))))) + (values '() '())))))) + +;;; Like %CARS+CDRS, but we pass in a final elt tacked onto the end of the +;;; cars list. What a hack. +(define (%cars+cdrs+ lists cars-final) + (call-with-current-continuation + (lambda (abort) + (let recur ((lists lists)) + (if (pair? lists) + (receive (list other-lists) (car+cdr lists) + (if (null-list? list) (abort '() '()) ; LIST is empty -- bail out + (receive (a d) (car+cdr list) + (receive (cars cdrs) (recur other-lists) + (values (cons a cars) (cons d cdrs)))))) + (values (list cars-final) '())))))) + +;;; Like %CARS+CDRS, but blow up if any list is empty. +(define (%cars+cdrs/no-test lists) + (let recur ((lists lists)) + (if (pair? lists) + (receive (list other-lists) (car+cdr lists) + (receive (a d) (car+cdr list) + (receive (cars cdrs) (recur other-lists) + (values (cons a cars) (cons d cdrs))))) + (values '() '())))) + + +;;; count +;;;;;;;;; +(define (count pred list1 . lists) + (srfi-1:check-arg procedure? pred count) + (if (pair? lists) + + ;; N-ary case + (let lp ((list1 list1) (lists lists) (i 0)) + (if (null-list? list1) i + (receive (as ds) (%cars+cdrs lists) + (if (null? as) i + (lp (cdr list1) ds + (if (apply pred (car list1) as) (+ i 1) i)))))) + + ;; Fast path + (let lp ((lis list1) (i 0)) + (if (null-list? lis) i + (lp (cdr lis) (if (pred (car lis)) (+ i 1) i)))))) + + +;;; fold/unfold +;;;;;;;;;;;;;;; + +(define (unfold-right p f g seed . maybe-tail) + (srfi-1:check-arg procedure? p unfold-right) + (srfi-1:check-arg procedure? f unfold-right) + (srfi-1:check-arg procedure? g unfold-right) + (let lp ((seed seed) + (ans (if (pair? maybe-tail) (car maybe-tail) '()))) + (if (p seed) ans + (lp (g seed) + (cons (f seed) ans))))) + + +(define (unfold p f g seed . maybe-tail-gen) + (srfi-1:check-arg procedure? p unfold) + (srfi-1:check-arg procedure? f unfold) + (srfi-1:check-arg procedure? g unfold) + (if (pair? maybe-tail-gen) + + (let ((tail-gen (car maybe-tail-gen))) + (if (pair? (cdr maybe-tail-gen)) + (apply error "Too many arguments" unfold p f g seed maybe-tail-gen) + + (let recur ((seed seed)) + (if (p seed) (tail-gen seed) + (cons (f seed) (recur (g seed))))))) + + (let recur ((seed seed)) + (if (p seed) '() + (cons (f seed) (recur (g seed))))))) + + +(define (fold kons knil lis1 . lists) + (srfi-1:check-arg procedure? kons fold) + (if (pair? lists) + (let lp ((lists (cons lis1 lists)) (ans knil)) ; N-ary case + (receive (cars+ans cdrs) (%cars+cdrs+ lists ans) + (if (null? cars+ans) ans ; Done. + (lp cdrs (apply kons cars+ans))))) + + (let lp ((lis lis1) (ans knil)) ; Fast path + (if (null-list? lis) ans + (lp (cdr lis) (kons (car lis) ans)))))) + + +(define (fold-right kons knil lis1 . lists) + (srfi-1:check-arg procedure? kons fold-right) + (if (pair? lists) + (let recur ((lists (cons lis1 lists))) ; N-ary case + (let ((cdrs (%cdrs lists))) + (if (null? cdrs) knil + (apply kons (%cars+ lists (recur cdrs)))))) + + (let recur ((lis lis1)) ; Fast path + (if (null-list? lis) knil + (let ((head (car lis))) + (kons head (recur (cdr lis)))))))) + + +(define (pair-fold-right f zero lis1 . lists) + (srfi-1:check-arg procedure? f pair-fold-right) + (if (pair? lists) + (let recur ((lists (cons lis1 lists))) ; N-ary case + (let ((cdrs (%cdrs lists))) + (if (null? cdrs) zero + (apply f (append! lists (list (recur cdrs))))))) + + (let recur ((lis lis1)) ; Fast path + (if (null-list? lis) zero (f lis (recur (cdr lis))))))) + +(define (pair-fold f zero lis1 . lists) + (srfi-1:check-arg procedure? f pair-fold) + (if (pair? lists) + (let lp ((lists (cons lis1 lists)) (ans zero)) ; N-ary case + (let ((tails (%cdrs lists))) + (if (null? tails) ans + (lp tails (apply f (append! lists (list ans))))))) + + (let lp ((lis lis1) (ans zero)) + (if (null-list? lis) ans + (let ((tail (cdr lis))) ; Grab the cdr now, + (lp tail (f lis ans))))))) ; in case F SET-CDR!s LIS. + + +;;; REDUCE and REDUCE-RIGHT only use RIDENTITY in the empty-list case. +;;; These cannot meaningfully be n-ary. + +(define (reduce f ridentity lis) + (srfi-1:check-arg procedure? f reduce) + (if (null-list? lis) ridentity + (fold f (car lis) (cdr lis)))) + +(define (reduce-right f ridentity lis) + (srfi-1:check-arg procedure? f reduce-right) + (if (null-list? lis) ridentity + (let recur ((head (car lis)) (lis (cdr lis))) + (if (pair? lis) + (f head (recur (car lis) (cdr lis))) + head)))) + + + +;;; Mappers: append-map append-map! pair-for-each map! filter-map map-in-order +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define (append-map f lis1 . lists) + (really-append-map append-map append f lis1 lists)) +(define (append-map! f lis1 . lists) + (really-append-map append-map! append! f lis1 lists)) + +(define (really-append-map who appender f lis1 lists) + (srfi-1:check-arg procedure? f who) + (if (pair? lists) + (receive (cars cdrs) (%cars+cdrs (cons lis1 lists)) + (if (null? cars) '() + (let recur ((cars cars) (cdrs cdrs)) + (let ((vals (apply f cars))) + (receive (cars2 cdrs2) (%cars+cdrs cdrs) + (if (null? cars2) vals + (appender vals (recur cars2 cdrs2)))))))) + + ;; Fast path + (if (null-list? lis1) '() + (let recur ((elt (car lis1)) (rest (cdr lis1))) + (let ((vals (f elt))) + (if (null-list? rest) vals + (appender vals (recur (car rest) (cdr rest))))))))) + + +(define (pair-for-each proc lis1 . lists) + (srfi-1:check-arg procedure? proc pair-for-each) + (if (pair? lists) + + (let lp ((lists (cons lis1 lists))) + (let ((tails (%cdrs lists))) + (if (pair? tails) + (begin (apply proc lists) + (lp tails))))) + + ;; Fast path. + (let lp ((lis lis1)) + (if (not (null-list? lis)) + (let ((tail (cdr lis))) ; Grab the cdr now, + (proc lis) ; in case PROC SET-CDR!s LIS. + (lp tail)))))) + +;;; We stop when LIS1 runs out, not when any list runs out. +(define (map! f lis1 . lists) + (srfi-1:check-arg procedure? f map!) + (if (pair? lists) + (let lp ((lis1 lis1) (lists lists)) + (if (not (null-list? lis1)) + (receive (heads tails) (%cars+cdrs/no-test lists) + (set-car! lis1 (apply f (car lis1) heads)) + (lp (cdr lis1) tails)))) + + ;; Fast path. + (pair-for-each (lambda (pair) (set-car! pair (f (car pair)))) lis1)) + lis1) + + +;;; Map F across L, and save up all the non-false results. +(define (filter-map f lis1 . lists) + (srfi-1:check-arg procedure? f filter-map) + (if (pair? lists) + (let recur ((lists (cons lis1 lists))) + (receive (cars cdrs) (%cars+cdrs lists) + (if (pair? cars) + (cond ((apply f cars) => (lambda (x) (cons x (recur cdrs)))) + (else (recur cdrs))) ; Tail call in this arm. + '()))) + + ;; Fast path. + (let recur ((lis lis1)) + (if (null-list? lis) lis + (let ((tail (recur (cdr lis)))) + (cond ((f (car lis)) => (lambda (x) (cons x tail))) + (else tail))))))) + + +;;; Map F across lists, guaranteeing to go left-to-right. +;;; NOTE: Some implementations of R5RS MAP are compliant with this spec; +;;; in which case this procedure may simply be defined as a synonym for MAP. + +(define (map-in-order f lis1 . lists) + (srfi-1:check-arg procedure? f map-in-order) + (if (pair? lists) + (let recur ((lists (cons lis1 lists))) + (receive (cars cdrs) (%cars+cdrs lists) + (if (pair? cars) + (let ((x (apply f cars))) ; Do head first, + (cons x (recur cdrs))) ; then tail. + '()))) + + ;; Fast path. + (let recur ((lis lis1)) + (if (null-list? lis) lis + (let ((tail (cdr lis)) + (x (f (car lis)))) ; Do head first, + (cons x (recur tail))))))) ; then tail. + + +;;; We extend MAP to handle arguments of unequal length. +(define map map-in-order) + + +;;; filter, remove, partition +;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; FILTER, REMOVE, PARTITION and their destructive counterparts do not +;;; disorder the elements of their argument. + +;; This FILTER shares the longest tail of L that has no deleted elements. +;; If Scheme had multi-continuation calls, they could be made more efficient. + +(define (filter pred lis) ; Sleazing with EQ? makes this + (srfi-1:check-arg procedure? pred filter) ; one faster. + (let recur ((lis lis)) + (if (null-list? lis) lis ; Use NOT-PAIR? to handle dotted lists. + (let ((head (car lis)) + (tail (cdr lis))) + (if (pred head) + (let ((new-tail (recur tail))) ; Replicate the RECUR call so + (if (eq? tail new-tail) lis + (cons head new-tail))) + (recur tail)))))) ; this one can be a tail call. + + +;;; Another version that shares longest tail. +;(define (filter pred lis) +; (receive (ans no-del?) +; ;; (recur l) returns L with (pred x) values filtered. +; ;; It also returns a flag NO-DEL? if the returned value +; ;; is EQ? to L, i.e. if it didn't have to delete anything. +; (let recur ((l l)) +; (if (null-list? l) (values l #t) +; (let ((x (car l)) +; (tl (cdr l))) +; (if (pred x) +; (receive (ans no-del?) (recur tl) +; (if no-del? +; (values l #t) +; (values (cons x ans) #f))) +; (receive (ans no-del?) (recur tl) ; Delete X. +; (values ans #f)))))) +; ans)) + + + +;(define (filter! pred lis) ; Things are much simpler +; (let recur ((lis lis)) ; if you are willing to +; (if (pair? lis) ; push N stack frames & do N +; (cond ((pred (car lis)) ; SET-CDR! writes, where N is +; (set-cdr! lis (recur (cdr lis))); the length of the answer. +; lis) +; (else (recur (cdr lis)))) +; lis))) + + +;;; This implementation of FILTER! +;;; - doesn't cons, and uses no stack; +;;; - is careful not to do redundant SET-CDR! writes, as writes to memory are +;;; usually expensive on modern machines, and can be extremely expensive on +;;; modern Schemes (e.g., ones that have generational GC's). +;;; It just zips down contiguous runs of in and out elts in LIS doing the +;;; minimal number of SET-CDR!s to splice the tail of one run of ins to the +;;; beginning of the next. + +(define (filter! pred lis) + (srfi-1:check-arg procedure? pred filter!) + (let lp ((ans lis)) + (cond ((null-list? ans) ans) ; Scan looking for + ((not (pred (car ans))) (lp (cdr ans))) ; first cons of result. + + ;; ANS is the eventual answer. + ;; SCAN-IN: (CDR PREV) = LIS and (CAR PREV) satisfies PRED. + ;; Scan over a contiguous segment of the list that + ;; satisfies PRED. + ;; SCAN-OUT: (CAR PREV) satisfies PRED. Scan over a contiguous + ;; segment of the list that *doesn't* satisfy PRED. + ;; When the segment ends, patch in a link from PREV + ;; to the start of the next good segment, and jump to + ;; SCAN-IN. + (else (letrec ((scan-in (lambda (prev lis) + (if (pair? lis) + (if (pred (car lis)) + (scan-in lis (cdr lis)) + (scan-out prev (cdr lis)))))) + (scan-out (lambda (prev lis) + (let lp ((lis lis)) + (if (pair? lis) + (if (pred (car lis)) + (begin (set-cdr! prev lis) + (scan-in lis (cdr lis))) + (lp (cdr lis))) + (set-cdr! prev lis)))))) + (scan-in ans (cdr ans)) + ans))))) + + + +;;; Answers share common tail with LIS where possible; +;;; the technique is slightly subtle. + +(define (partition pred lis) + (srfi-1:check-arg procedure? pred partition) + (let recur ((lis lis)) + (if (null-list? lis) (values lis lis) ; Use NOT-PAIR? to handle dotted lists. + (let ((elt (car lis)) + (tail (cdr lis))) + (receive (in out) (recur tail) + (if (pred elt) + (values (if (pair? out) (cons elt in) lis) out) + (values in (if (pair? in) (cons elt out) lis)))))))) + + + +;(define (partition! pred lis) ; Things are much simpler +; (let recur ((lis lis)) ; if you are willing to +; (if (null-list? lis) (values lis lis) ; push N stack frames & do N +; (let ((elt (car lis))) ; SET-CDR! writes, where N is +; (receive (in out) (recur (cdr lis)) ; the length of LIS. +; (cond ((pred elt) +; (set-cdr! lis in) +; (values lis out)) +; (else (set-cdr! lis out) +; (values in lis)))))))) + + +;;; This implementation of PARTITION! +;;; - doesn't cons, and uses no stack; +;;; - is careful not to do redundant SET-CDR! writes, as writes to memory are +;;; usually expensive on modern machines, and can be extremely expensive on +;;; modern Schemes (e.g., ones that have generational GC's). +;;; It just zips down contiguous runs of in and out elts in LIS doing the +;;; minimal number of SET-CDR!s to splice these runs together into the result +;;; lists. + +(define (partition! pred lis) + (srfi-1:check-arg procedure? pred partition!) + (if (null-list? lis) (values lis lis) + + ;; This pair of loops zips down contiguous in & out runs of the + ;; list, splicing the runs together. The invariants are + ;; SCAN-IN: (cdr in-prev) = LIS. + ;; SCAN-OUT: (cdr out-prev) = LIS. + (letrec ((scan-in (lambda (in-prev out-prev lis) + (let lp ((in-prev in-prev) (lis lis)) + (if (pair? lis) + (if (pred (car lis)) + (lp lis (cdr lis)) + (begin (set-cdr! out-prev lis) + (scan-out in-prev lis (cdr lis)))) + (set-cdr! out-prev lis))))) ; Done. + + (scan-out (lambda (in-prev out-prev lis) + (let lp ((out-prev out-prev) (lis lis)) + (if (pair? lis) + (if (pred (car lis)) + (begin (set-cdr! in-prev lis) + (scan-in lis out-prev (cdr lis))) + (lp lis (cdr lis))) + (set-cdr! in-prev lis)))))) ; Done. + + ;; Crank up the scan&splice loops. + (if (pred (car lis)) + ;; LIS begins in-list. Search for out-list's first pair. + (let lp ((prev-l lis) (l (cdr lis))) + (cond ((not (pair? l)) (values lis l)) + ((pred (car l)) (lp l (cdr l))) + (else (scan-out prev-l l (cdr l)) + (values lis l)))) ; Done. + + ;; LIS begins out-list. Search for in-list's first pair. + (let lp ((prev-l lis) (l (cdr lis))) + (cond ((not (pair? l)) (values l lis)) + ((pred (car l)) + (scan-in l prev-l (cdr l)) + (values l lis)) ; Done. + (else (lp l (cdr l))))))))) + + +;;; Inline us, please. +(define (remove pred l) (filter (lambda (x) (not (pred x))) l)) +(define (remove! pred l) (filter! (lambda (x) (not (pred x))) l)) + + + +;;; Here's the taxonomy for the DELETE/ASSOC/MEMBER functions. +;;; (I don't actually think these are the world's most important +;;; functions -- the procedural FILTER/REMOVE/FIND/FIND-TAIL variants +;;; are far more general.) +;;; +;;; Function Action +;;; --------------------------------------------------------------------------- +;;; remove pred lis Delete by general predicate +;;; delete x lis [=] Delete by element comparison +;;; +;;; find pred lis Search by general predicate +;;; find-tail pred lis Search by general predicate +;;; member x lis [=] Search by element comparison +;;; +;;; assoc key lis [=] Search alist by key comparison +;;; alist-delete key alist [=] Alist-delete by key comparison + +(define (delete x lis . maybe-=) + (let ((= (if (pair? maybe-=) (car maybe-=) equal?))) + (filter (lambda (y) (not (= x y))) lis))) + +(define (delete! x lis . maybe-=) + (let ((= (if (pair? maybe-=) (car maybe-=) equal?))) + (filter! (lambda (y) (not (= x y))) lis))) + +;;; Extended from R4RS to take an optional comparison argument. +(define (member x lis . maybe-=) + (let ((= (if (pair? maybe-=) (car maybe-=) equal?))) + (find-tail (lambda (y) (= x y)) lis))) + +;;; R4RS, hence we don't bother to define. +;;; The MEMBER and then FIND-TAIL call should definitely +;;; be inlined for MEMQ & MEMV. +;(define (memq x lis) (member x lis eq?)) +;(define (memv x lis) (member x lis eqv?)) + + +;;; right-duplicate deletion +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; delete-duplicates delete-duplicates! +;;; +;;; Beware -- these are N^2 algorithms. To efficiently remove duplicates +;;; in long lists, sort the list to bring duplicates together, then use a +;;; linear-time algorithm to kill the dups. Or use an algorithm based on +;;; element-marking. The former gives you O(n lg n), the latter is linear. + +(define (delete-duplicates lis . maybe-=) + (let ((elt= (if (pair? maybe-=) (car maybe-=) equal?))) + (srfi-1:check-arg procedure? elt= delete-duplicates) + (let recur ((lis lis)) + (if (null-list? lis) lis + (let* ((x (car lis)) + (tail (cdr lis)) + (new-tail (recur (delete x tail elt=)))) + (if (eq? tail new-tail) lis (cons x new-tail))))))) + +(define (delete-duplicates! lis maybe-=) + (let ((elt= (if (pair? maybe-=) (car maybe-=) equal?))) + (srfi-1:check-arg procedure? elt= delete-duplicates!) + (let recur ((lis lis)) + (if (null-list? lis) lis + (let* ((x (car lis)) + (tail (cdr lis)) + (new-tail (recur (delete! x tail elt=)))) + (if (eq? tail new-tail) lis (cons x new-tail))))))) + + +;;; alist stuff +;;;;;;;;;;;;;;; + +;;; Extended from R4RS to take an optional comparison argument. +(define (assoc x lis . maybe-=) + (let ((= (if (pair? maybe-=) (car maybe-=) equal?))) + (find (lambda (entry) (= x (car entry))) lis))) + +(define (alist-cons key datum alist) (cons (cons key datum) alist)) + +(define (alist-copy alist) + (map (lambda (elt) (cons (car elt) (cdr elt))) + alist)) + +(define (alist-delete key alist . maybe-=) + (let ((= (if (pair? maybe-=) (car maybe-=) equal?))) + (filter (lambda (elt) (not (= key (car elt)))) alist))) + +(define (alist-delete! key alist . maybe-=) + (let ((= (if (pair? maybe-=) (car maybe-=) equal?))) + (filter! (lambda (elt) (not (= key (car elt)))) alist))) + + +;;; find find-tail take-while drop-while span break any every list-index +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define (find pred list) + (cond ((find-tail pred list) => car) + (else #f))) + +(define (find-tail pred list) + (srfi-1:check-arg procedure? pred find-tail) + (let lp ((list list)) + (and (not (null-list? list)) + (if (pred (car list)) list + (lp (cdr list)))))) + +(define (take-while pred lis) + (srfi-1:check-arg procedure? pred take-while) + (let recur ((lis lis)) + (if (null-list? lis) '() + (let ((x (car lis))) + (if (pred x) + (cons x (recur (cdr lis))) + '()))))) + +(define (drop-while pred lis) + (srfi-1:check-arg procedure? pred drop-while) + (let lp ((lis lis)) + (if (null-list? lis) '() + (if (pred (car lis)) + (lp (cdr lis)) + lis)))) + +(define (take-while! pred lis) + (srfi-1:check-arg procedure? pred take-while!) + (if (or (null-list? lis) (not (pred (car lis)))) '() + (begin (let lp ((prev lis) (rest (cdr lis))) + (if (pair? rest) + (let ((x (car rest))) + (if (pred x) (lp rest (cdr rest)) + (set-cdr! prev '()))))) + lis))) + +(define (span pred lis) + (srfi-1:check-arg procedure? pred span) + (let recur ((lis lis)) + (if (null-list? lis) (values '() '()) + (let ((x (car lis))) + (if (pred x) + (receive (prefix suffix) (recur (cdr lis)) + (values (cons x prefix) suffix)) + (values '() lis)))))) + +(define (span! pred lis) + (srfi-1:check-arg procedure? pred span!) + (if (or (null-list? lis) (not (pred (car lis)))) (values '() lis) + (let ((suffix (let lp ((prev lis) (rest (cdr lis))) + (if (null-list? rest) rest + (let ((x (car rest))) + (if (pred x) (lp rest (cdr rest)) + (begin (set-cdr! prev '()) + rest))))))) + (values lis suffix)))) + + +(define (break pred lis) (span (lambda (x) (not (pred x))) lis)) +(define (break! pred lis) (span! (lambda (x) (not (pred x))) lis)) + +(define (any pred lis1 . lists) + (srfi-1:check-arg procedure? pred any) + (if (pair? lists) + + ;; N-ary case + (receive (heads tails) (%cars+cdrs (cons lis1 lists)) + (and (pair? heads) + (let lp ((heads heads) (tails tails)) + (receive (next-heads next-tails) (%cars+cdrs tails) + (if (pair? next-heads) + (or (apply pred heads) (lp next-heads next-tails)) + (apply pred heads)))))) ; Last PRED app is tail call. + + ;; Fast path + (and (not (null-list? lis1)) + (let lp ((head (car lis1)) (tail (cdr lis1))) + (if (null-list? tail) + (pred head) ; Last PRED app is tail call. + (or (pred head) (lp (car tail) (cdr tail)))))))) + + +;(define (every pred list) ; Simple definition. +; (let lp ((list list)) ; Doesn't return the last PRED value. +; (or (not (pair? list)) +; (and (pred (car list)) +; (lp (cdr list)))))) + +(define (every pred lis1 . lists) + (srfi-1:check-arg procedure? pred every) + (if (pair? lists) + + ;; N-ary case + (receive (heads tails) (%cars+cdrs (cons lis1 lists)) + (or (not (pair? heads)) + (let lp ((heads heads) (tails tails)) + (receive (next-heads next-tails) (%cars+cdrs tails) + (if (pair? next-heads) + (and (apply pred heads) (lp next-heads next-tails)) + (apply pred heads)))))) ; Last PRED app is tail call. + + ;; Fast path + (or (null-list? lis1) + (let lp ((head (car lis1)) (tail (cdr lis1))) + (if (null-list? tail) + (pred head) ; Last PRED app is tail call. + (and (pred head) (lp (car tail) (cdr tail)))))))) + +(define (list-index pred lis1 . lists) + (srfi-1:check-arg procedure? pred list-index) + (if (pair? lists) + + ;; N-ary case + (let lp ((lists (cons lis1 lists)) (n 0)) + (receive (heads tails) (%cars+cdrs lists) + (and (pair? heads) + (if (apply pred heads) n + (lp tails (+ n 1)))))) + + ;; Fast path + (let lp ((lis lis1) (n 0)) + (and (not (null-list? lis)) + (if (pred (car lis)) n (lp (cdr lis) (+ n 1))))))) + +;;; Reverse +;;;;;;;;;;; + +;R4RS, so not defined here. +;(define (reverse lis) (fold cons '() lis)) + +;(define (reverse! lis) +; (pair-fold (lambda (pair tail) (set-cdr! pair tail) pair) '() lis)) + +(define (reverse! lis) + (let lp ((lis lis) (ans '())) + (if (null-list? lis) ans + (let ((tail (cdr lis))) + (set-cdr! lis ans) + (lp tail lis))))) + +;;; Lists-as-sets +;;;;;;;;;;;;;;;;; + +;;; This is carefully tuned code; do not modify casually. +;;; - It is careful to share storage when possible; +;;; - Side-effecting code tries not to perform redundant writes. +;;; - It tries to avoid linear-time scans in special cases where constant-time +;;; computations can be performed. +;;; - It relies on similar properties from the other list-lib procs it calls. +;;; For example, it uses the fact that the implementations of MEMBER and +;;; FILTER in this source code share longest common tails between args +;;; and results to get structure sharing in the lset procedures. + +(define (%lset2<= = lis1 lis2) (every (lambda (x) (member x lis2 =)) lis1)) + +(define (lset<= = . lists) + (srfi-1:check-arg procedure? = lset<=) + (or (not (pair? lists)) ; 0-ary case + (let lp ((s1 (car lists)) (rest (cdr lists))) + (or (not (pair? rest)) + (let ((s2 (car rest)) (rest (cdr rest))) + (and (or (eq? s2 s1) ; Fast path + (%lset2<= = s1 s2)) ; Real test + (lp s2 rest))))))) + +(define (lset= = . lists) + (srfi-1:check-arg procedure? = lset=) + (or (not (pair? lists)) ; 0-ary case + (let lp ((s1 (car lists)) (rest (cdr lists))) + (or (not (pair? rest)) + (let ((s2 (car rest)) + (rest (cdr rest))) + (and (or (eq? s1 s2) ; Fast path + (and (%lset2<= = s1 s2) (%lset2<= = s2 s1))) ; Real test + (lp s2 rest))))))) + + +(define (lset-adjoin = lis . elts) + (srfi-1:check-arg procedure? = lset-adjoin) + (fold (lambda (elt ans) (if (member elt ans =) ans (cons elt ans))) + lis elts)) + + +(define (lset-union = . lists) + (srfi-1:check-arg procedure? = lset-union) + (reduce (lambda (lis ans) ; Compute ANS + LIS. + (cond ((null? lis) ans) ; Don't copy any lists + ((null? ans) lis) ; if we don't have to. + ((eq? lis ans) ans) + (else + (fold (lambda (elt ans) (if (any (lambda (x) (= x elt)) ans) + ans + (cons elt ans))) + ans lis)))) + '() lists)) + +(define (lset-union! = . lists) + (srfi-1:check-arg procedure? = lset-union!) + (reduce (lambda (lis ans) ; Splice new elts of LIS onto the front of ANS. + (cond ((null? lis) ans) ; Don't copy any lists + ((null? ans) lis) ; if we don't have to. + ((eq? lis ans) ans) + (else + (pair-fold (lambda (pair ans) + (let ((elt (car pair))) + (if (any (lambda (x) (= x elt)) ans) + ans + (begin (set-cdr! pair ans) pair)))) + ans lis)))) + '() lists)) + + +(define (lset-intersection = lis1 . lists) + (srfi-1:check-arg procedure? = lset-intersection) + (let ((lists (delete lis1 lists eq?))) ; Throw out any LIS1 vals. + (cond ((any null-list? lists) '()) ; Short cut + ((null? lists) lis1) ; Short cut + (else (filter (lambda (x) + (every (lambda (lis) (member x lis =)) lists)) + lis1))))) + +(define (lset-intersection! = lis1 . lists) + (srfi-1:check-arg procedure? = lset-intersection!) + (let ((lists (delete lis1 lists eq?))) ; Throw out any LIS1 vals. + (cond ((any null-list? lists) '()) ; Short cut + ((null? lists) lis1) ; Short cut + (else (filter! (lambda (x) + (every (lambda (lis) (member x lis =)) lists)) + lis1))))) + + +(define (lset-difference = lis1 . lists) + (srfi-1:check-arg procedure? = lset-difference) + (let ((lists (filter pair? lists))) ; Throw out empty lists. + (cond ((null? lists) lis1) ; Short cut + ((memq lis1 lists) '()) ; Short cut + (else (filter (lambda (x) + (every (lambda (lis) (not (member x lis =))) + lists)) + lis1))))) + +(define (lset-difference! = lis1 . lists) + (srfi-1:check-arg procedure? = lset-difference!) + (let ((lists (filter pair? lists))) ; Throw out empty lists. + (cond ((null? lists) lis1) ; Short cut + ((memq lis1 lists) '()) ; Short cut + (else (filter! (lambda (x) + (every (lambda (lis) (not (member x lis =))) + lists)) + lis1))))) + + +(define (lset-xor = . lists) + (srfi-1:check-arg procedure? = lset-xor) + (reduce (lambda (b a) ; Compute A xor B: + ;; Note that this code relies on the constant-time + ;; short-cuts provided by LSET-DIFF+INTERSECTION, + ;; LSET-DIFFERENCE & APPEND to provide constant-time short + ;; cuts for the cases A = (), B = (), and A eq? B. It takes + ;; a careful case analysis to see it, but it's carefully + ;; built in. + + ;; Compute a-b and a^b, then compute b-(a^b) and + ;; cons it onto the front of a-b. + (receive (a-b a-int-b) (lset-diff+intersection = a b) + (cond ((null? a-b) (lset-difference b a =)) + ((null? a-int-b) (append b a)) + (else (fold (lambda (xb ans) + (if (member xb a-int-b =) ans (cons xb ans))) + a-b + b))))) + '() lists)) + + +(define (lset-xor! = . lists) + (srfi-1:check-arg procedure? = lset-xor!) + (reduce (lambda (b a) ; Compute A xor B: + ;; Note that this code relies on the constant-time + ;; short-cuts provided by LSET-DIFF+INTERSECTION, + ;; LSET-DIFFERENCE & APPEND to provide constant-time short + ;; cuts for the cases A = (), B = (), and A eq? B. It takes + ;; a careful case analysis to see it, but it's carefully + ;; built in. + + ;; Compute a-b and a^b, then compute b-(a^b) and + ;; cons it onto the front of a-b. + (receive (a-b a-int-b) (lset-diff+intersection! = a b) + (cond ((null? a-b) (lset-difference! b a =)) + ((null? a-int-b) (append! b a)) + (else (pair-fold (lambda (b-pair ans) + (if (member (car b-pair) a-int-b =) ans + (begin (set-cdr! b-pair ans) b-pair))) + a-b + b))))) + '() lists)) + + +(define (lset-diff+intersection = lis1 . lists) + (srfi-1:check-arg procedure? = lset-diff+intersection) + (cond ((every null-list? lists) (values lis1 '())) ; Short cut + ((memq lis1 lists) (values '() lis1)) ; Short cut + (else (partition (lambda (elt) + (not (any (lambda (lis) (member elt lis =)) + lists))) + lis1)))) + +(define (lset-diff+intersection! = lis1 . lists) + (srfi-1:check-arg procedure? = lset-diff+intersection!) + (cond ((every null-list? lists) (values lis1 '())) ; Short cut + ((memq lis1 lists) (values '() lis1)) ; Short cut + (else (partition! (lambda (elt) + (not (any (lambda (lis) (member elt lis =)) + lists))) + lis1)))) diff --git a/src/scm/srfi/srfi-1.unclear.scm b/src/scm/srfi/srfi-1.unclear.scm new file mode 100644 index 0000000000..302b4f1266 --- /dev/null +++ b/src/scm/srfi/srfi-1.unclear.scm @@ -0,0 +1,18 @@ + +;;; I'm maintaining the license that Olin put on the SRFI-1 reference +;;; code. +;;; +;;; Copyright 1999, Rob Browning . You may do as +;;; you please with this code as long as you do not remove this +;;; copyright notice or hold me liable for its use. + +;; This has been modified for GnuCash to use guile's built in error +;; function. + +(define (srfi-1:error msg . args) + (apply error msg args)) + +(define (srfi-1:check-arg pred val caller) + (if (pred val) + val + (srfi-1:error "Bad argument" val "to function" caller))) diff --git a/src/scm/srfi/srfi-8.guile.scm b/src/scm/srfi/srfi-8.guile.scm new file mode 100644 index 0000000000..b60db3d5c8 --- /dev/null +++ b/src/scm/srfi/srfi-8.guile.scm @@ -0,0 +1,17 @@ + +;;; I'm maintaining the license that Olin put on the SRFI-1 reference +;;; code. +;;; +;;; Copyright 1999, Rob Browning . You may do as +;;; you please with this code as long as you do not remove this +;;; copyright notice or hold me liable for its use. + +(use-modules (ice-9 slib)) +(require 'macro-by-example) +(require 'values) + +(define-syntax receive + (syntax-rules () + ((receive formals expression body ...) + (call-with-values (lambda () expression) + (lambda formals body ...))))) diff --git a/src/scm/test.scm b/src/scm/test.scm index 9b27aba65b..359f752322 100644 --- a/src/scm/test.scm +++ b/src/scm/test.scm @@ -1,3 +1,6 @@ + +(gnc:support "test.scm") + (define (gnc:test-load group) (let ((cash (list (gnc:malloc-account) diff --git a/src/scm/text-export.scm b/src/scm/text-export.scm index ab3a90451b..ce6235c52b 100644 --- a/src/scm/text-export.scm +++ b/src/scm/text-export.scm @@ -1,4 +1,7 @@ ;;; $Id$ + +(gnc:support "text-export.scm") + (require 'pretty-print) (define (gnc:group-map-accounts thunk group) (let loop ((num-accounts (gnc:group-get-num-accounts group)) @@ -49,7 +52,7 @@ (let* ((accinfo (gnc:account-get-acc-info a)) (invacct (gnc:cast-to-inv-acct accinfo))) (if (not (pointer-token-null? invacct)) - (gnc:inv-acct-get-price-source invacct) + (gnc:inv-acct-get-price-src invacct) #f)) (list 'children (gnc:group-map-accounts @@ -80,7 +83,7 @@ (gnc:account-staged-transaction-traversal account 1 - (lambda (t) (pretty-print (gnc:transaction->output-form t))))) + (lambda (t) (pretty-print (gnc:transaction->output-form t)) #f))) (define (gnc:transaction->output-form transaction) (list diff --git a/src/scm/txn-create.scm b/src/scm/txn-create.scm index 9f71ed0323..7ab565e32d 100644 --- a/src/scm/txn-create.scm +++ b/src/scm/txn-create.scm @@ -62,4 +62,4 @@ (gnc:trans-commit-edit Txn))) (define (gnc:test-load-txns accg) - #f)diff -u 'pristine/gnucash/src/scm/utilities.scm' 'working/gnucash/src/scm/utilities.scm' + #f) diff --git a/src/ui-callbacks.h b/src/ui-callbacks.h index 9db1a9df41..a1f636bc55 100644 --- a/src/ui-callbacks.h +++ b/src/ui-callbacks.h @@ -37,4 +37,6 @@ void gnc_error_dialog( const char *message ); void gnc_set_busy_cursor( gncUIWidget w ); void gnc_unset_busy_cursor( gncUIWidget w ); +void gnc_ui_destroy_all_subwindows( void ); + #endif