Please review Delete Account patch

David Jafferian - Sun Microsystems david.jafferian at east.sun.com
Wed Apr 6 11:27:11 EDT 2005


Hi -

Please review the attached patch for gnucash 1.9.0.  I am submitting
it to gnucash-devel because it is the first patch I've ever produced
for gnucash, so it probably needs some rework.

It was generated with the "cvs -q diff -u" command.  It contains diffs
for autogenerated files.  Perhaps these need to trimmed out ?

These are the files that I changed manually, with brief descriptions
of the changes :

macros/as-scrub-include.m4

	Modified to prevent an autogen failure.

src/backend/file/sixtp-utils.c

	Inserted a declaration for errno to prevent a compile failure.

src/engine/Account.c

	Implementation of the xaccAccountMoveAllSplits routine and
	two supporting static functions, which can be used to move all
	of the splits from one account to another account.

src/engine/Account.h

	Inserted declaration of the xaccAccountMoveAllSplits routine.

src/engine/gnc-numeric.h

	Changed "inline" to "__inline__" for gnc_numeric_add_fixed and
	gnc_numeric_sub_fixed, in order to prevent a compile failure.

src/gnome/window-acct-tree.c

	Major rework of the gnc_acct_tree_window_delete_common
	routine, in order to provide the user with some control over
	what happens when an account is deleted, along with the
	implementation of several supporting static functions.

The changes to the account deletion dialog were tested rather
thoroughly.  However, because the testing was limited to a single
user, single machine configuration, the xaccAccountMoveAllSplits
routine may need to be tested under a wider variety of environments.
Perhaps someone more familiar with the gnucash engine and backend
processing would be able to identify any potential problems through
inspection of its code.

The handling of the deletion of an account with read-only transactions
or with subaccounts with read-only transactions has not been tested.
I wanted to request comments on the changes before spending the time
needed to do such testing.

The patch includes the use of library routines which are deprecated
for GNOME 2, so the code would probably need to be reworked for
GnuCash 2.  I don't have a lot of experience coding GNOME applications
and suggestions are welcome.

Please also feel free to criticize any coding style that does not
adhere to GnuCash conventions or point out any other "faux pas".
Since I depend on gnucash, I would like to have the opportunity to
make a worthy contribution to its development, but doing so monetarily
is not well within my means at this time.

-------------- next part --------------
? configure.lineno
? src/engine/test/test-book-merge
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/Makefile.am,v
retrieving revision 1.61
diff -u -r1.61 Makefile.am
--- Makefile.am	23 Dec 2004 18:27:33 -0000	1.61
+++ Makefile.am	6 Apr 2005 14:15:16 -0000
@@ -33,7 +33,7 @@
 # Don't list any directories or you'll get *everything*, including the
 # CVS dirs.
 
-EXTRA_DIST = config.rpath  \
+EXTRA_DIST = config.rpath  config.rpath  \
   .cvsignore \
   ChangeLog.1 \
   HACKING \
Index: configure.in
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/configure.in,v
retrieving revision 1.401
diff -u -r1.401 configure.in
--- configure.in	29 Jan 2005 12:54:31 -0000	1.401
+++ configure.in	6 Apr 2005 14:15:16 -0000
@@ -1202,7 +1202,7 @@
 LIBOBJS_SEDSCRIPT="s,\.[[^.]]* ,$U&,g;s,\.[[^.]]*\$\$,$U&,"
 AC_SUBST(LIBOBJS_SEDSCRIPT)
 
-AC_OUTPUT( intl/Makefile po/Makefile.in 
+AC_OUTPUT(  intl/Makefile po/Makefile.in 
 	  m4/Makefile
           dnl # Makefiles
           Makefile
Index: mkinstalldirs
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/mkinstalldirs,v
retrieving revision 1.1
diff -u -r1.1 mkinstalldirs
--- mkinstalldirs	10 Jan 2000 03:32:50 -0000	1.1
+++ mkinstalldirs	6 Apr 2005 14:15:16 -0000
@@ -1,32 +1,111 @@
-#!/bin/sh
+#! /bin/sh
 # mkinstalldirs --- make directory hierarchy
 # Author: Noah Friedman <friedman at prep.ai.mit.edu>
 # Created: 1993-05-16
-# Last modified: 1994-03-25
 # Public domain
 
 errstatus=0
+dirmode=""
 
-for file in ${1+"$@"} ; do 
-   set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
-   shift
-
-   pathcomp=
-   for d in ${1+"$@"} ; do
-     pathcomp="$pathcomp$d"
-     case "$pathcomp" in
-       -* ) pathcomp=./$pathcomp ;;
-     esac
-
-     if test ! -d "$pathcomp"; then
-        echo "mkdir $pathcomp" 1>&2
-        mkdir "$pathcomp" || errstatus=$?
-     fi
+usage="\
+Usage: mkinstalldirs [-h] [--help] [-m mode] dir ..."
 
-     pathcomp="$pathcomp/"
-   done
+# process command line arguments
+while test $# -gt 0 ; do
+  case $1 in
+    -h | --help | --h*)         # -h for help
+      echo "$usage" 1>&2
+      exit 0
+      ;;
+    -m)                         # -m PERM arg
+      shift
+      test $# -eq 0 && { echo "$usage" 1>&2; exit 1; }
+      dirmode=$1
+      shift
+      ;;
+    --)                         # stop option processing
+      shift
+      break
+      ;;
+    -*)                         # unknown option
+      echo "$usage" 1>&2
+      exit 1
+      ;;
+    *)                          # first non-opt arg
+      break
+      ;;
+  esac
+done
+
+for file
+do
+  if test -d "$file"; then
+    shift
+  else
+    break
+  fi
+done
+
+case $# in
+  0) exit 0 ;;
+esac
+
+case $dirmode in
+  '')
+    if mkdir -p -- . 2>/dev/null; then
+      echo "mkdir -p -- $*"
+      exec mkdir -p -- "$@"
+    fi
+    ;;
+  *)
+    if mkdir -m "$dirmode" -p -- . 2>/dev/null; then
+      echo "mkdir -m $dirmode -p -- $*"
+      exec mkdir -m "$dirmode" -p -- "$@"
+    fi
+    ;;
+esac
+
+for file
+do
+  set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
+  shift
+
+  pathcomp=
+  for d
+  do
+    pathcomp="$pathcomp$d"
+    case $pathcomp in
+      -*) pathcomp=./$pathcomp ;;
+    esac
+
+    if test ! -d "$pathcomp"; then
+      echo "mkdir $pathcomp"
+
+      mkdir "$pathcomp" || lasterr=$?
+
+      if test ! -d "$pathcomp"; then
+  	errstatus=$lasterr
+      else
+  	if test ! -z "$dirmode"; then
+	  echo "chmod $dirmode $pathcomp"
+    	  lasterr=""
+  	  chmod "$dirmode" "$pathcomp" || lasterr=$?
+
+  	  if test ! -z "$lasterr"; then
+  	    errstatus=$lasterr
+  	  fi
+  	fi
+      fi
+    fi
+
+    pathcomp="$pathcomp/"
+  done
 done
 
 exit $errstatus
 
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# End:
 # mkinstalldirs ends here
Index: m4/Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/m4/Makefile.am,v
retrieving revision 1.4
diff -u -r1.4 Makefile.am
--- m4/Makefile.am	24 Jun 2002 17:49:34 -0000	1.4
+++ m4/Makefile.am	6 Apr 2005 14:15:18 -0000
@@ -1 +1 @@
-EXTRA_DIST = codeset.m4 gettext.m4 glibc21.m4 iconv.m4 isc-posix.m4 lcmessage.m4 lib-ld.m4 lib-link.m4 lib-prefix.m4 progtest.m4  gettext.m4  
+EXTRA_DIST = codeset.m4 gettext.m4 glibc21.m4 iconv.m4 intdiv0.m4 intmax.m4 inttypes.m4 inttypes_h.m4 inttypes-pri.m4 isc-posix.m4 lcmessage.m4 lib-ld.m4 lib-link.m4 lib-prefix.m4 longdouble.m4 longlong.m4 nls.m4 po.m4 printf-posix.m4 progtest.m4 signed.m4 size_max.m4 stdint_h.m4 uintmax_t.m4 ulonglong.m4 wchar_t.m4 wint_t.m4 xsize.m4  codeset.m4 gettext.m4 glibc21.m4 iconv.m4 isc-posix.m4 lcmessage.m4 lib-ld.m4 lib-link.m4 lib-prefix.m4 progtest.m4  gettext.m4  
Index: macros/as-scrub-include.m4
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/macros/as-scrub-include.m4,v
retrieving revision 1.2
diff -u -r1.2 as-scrub-include.m4
--- macros/as-scrub-include.m4	19 Nov 2002 00:52:22 -0000	1.2
+++ macros/as-scrub-include.m4	6 Apr 2005 14:15:18 -0000
@@ -26,7 +26,7 @@
   dnl line
   INCLUDE_DIRS=`echo $INCLUDE_DIRS | sed -e 's/.*<...> search starts here://' | sed -e 's/End of search list.*//'`
   for dir in $INCLUDE_DIRS; do
-    GIVEN_CFLAGS=$(echo $GIVEN_CFLAGS | sed -e "s;-I$dir ;;" | sed -e "s;-I$dir$;;")
+    GIVEN_CFLAGS=`echo $GIVEN_CFLAGS | sed -e "s;-I$dir ;;" | sed -e "s;-I$dir$;;"`
   done
   [$1]=$GIVEN_CFLAGS
 ])
Index: src/backend/file/sixtp-utils.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/file/sixtp-utils.c,v
retrieving revision 1.11
diff -u -r1.11 sixtp-utils.c
--- src/backend/file/sixtp-utils.c	26 Jun 2004 14:52:11 -0000	1.11
+++ src/backend/file/sixtp-utils.c	6 Apr 2005 14:15:18 -0000
@@ -407,6 +407,8 @@
   }
   else
   {
+    extern int errno;
+
     /* FIXME: there's no way to report this error to the caller. */
     gnc_unsetenv("TZ");
     if(errno != 0)
Index: src/engine/Account.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/Account.c,v
retrieving revision 1.282
diff -u -r1.282 Account.c
--- src/engine/Account.c	30 Jan 2005 22:45:25 -0000	1.282
+++ src/engine/Account.c	6 Apr 2005 14:15:19 -0000
@@ -950,6 +950,87 @@
   LEAVE ("(acc=%p, split=%p)", acc, split);
 }
 
+/********************************************************************\
+\********************************************************************/
+
+static void
+xaccPreSplitMove (Split *split, gpointer dummy)
+{
+  xaccTransBeginEdit (xaccSplitGetParent (split));
+}
+
+static void
+xaccPostSplitMove (Split *split, Account *accto)
+{
+  Transaction *trans;
+
+  split->acc = accto;
+  split->amount = gnc_numeric_convert (split->amount,
+				       xaccAccountGetCommoditySCU(accto),
+				       GNC_HOW_RND_ROUND);
+  trans = xaccSplitGetParent (split);
+  xaccTransCommitEdit (trans);
+  gnc_engine_gen_event (&trans->inst.entity, GNC_EVENT_MODIFY);
+}
+
+void
+xaccAccountMoveAllSplits (Account *accfrom, Account *accto)
+{
+  /* Handle special cases. */
+  if (!accfrom) return;
+  if (!accto) return;
+  if (!accfrom->splits) return;
+  if (accfrom == accto) return;
+
+  ENTER ("(accfrom=%p, accto=%p)", accfrom, accto);
+
+  /* check for book mix-up */
+  g_return_if_fail (accfrom->inst.book == accto->inst.book);
+
+  /* Begin editing both accounts and all transactions in accfrom. */
+  g_list_foreach(accfrom->splits, (GFunc)xaccPreSplitMove, NULL);
+  xaccAccountBeginEdit(accfrom);
+  xaccAccountBeginEdit(accto);
+
+  /* Concatenate accfrom's lists of splits and lots to accto's lists. */
+  accto->splits = g_list_concat(accto->splits, accfrom->splits);
+  accto->lots = g_list_concat(accto->lots, accfrom->lots);
+
+  /* Set appropriate flags. */
+  accfrom->balance_dirty = TRUE;
+  accfrom->sort_dirty = FALSE;
+  accto->balance_dirty = TRUE;
+  accto->sort_dirty = TRUE;
+
+  /*
+   * Change each split's account back pointer to accto.
+   * Convert each split's amount to accto's commodity.
+   * Commit to editing each transaction.
+   */
+  g_list_foreach(accfrom->splits, (GFunc)xaccPostSplitMove, (gpointer)accto);
+
+  /* Finally empty accfrom. */
+  accfrom->splits = NULL;
+  accto->lots = NULL;
+
+  /*
+   * DNJ - I don't really understand why this is necessary,
+   *       but xaccAccountInsertSplit does it.
+   */
+  if (accto->inst.editlevel == 1)
+  {
+    accto->splits = g_list_sort(accto->splits, split_sort_func);
+    accto->sort_dirty = FALSE;
+  }
+
+  /* Commit to editing both accounts. */
+  mark_account (accfrom);
+  mark_account (accto);
+  xaccAccountCommitEdit(accfrom);
+  xaccAccountCommitEdit(accto);
+  LEAVE ("(accfrom=%p, accto=%p)", accfrom, accto);
+}
+
 
 /********************************************************************\
  * xaccAccountRecomputeBalance                                      *
Index: src/engine/Account.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/Account.h,v
retrieving revision 1.139
diff -u -r1.139 Account.h
--- src/engine/Account.h	30 Jan 2005 21:06:20 -0000	1.139
+++ src/engine/Account.h	6 Apr 2005 14:15:19 -0000
@@ -495,6 +495,10 @@
  *    pointer. */
 SplitList*      xaccAccountGetSplitList (Account *account);
 
+/** The xaccAccountMoveAllSplits() routine reassigns each of the splits
+ *  in accfrom to accto. */
+void xaccAccountMoveAllSplits (Account *accfrom, Account *accto);
+
 /** \warning  Unimplemented */
 gpointer xaccAccountForEachSplit(Account *account,
                                  SplitCallback,
Index: src/engine/gnc-numeric.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/gnc-numeric.h,v
retrieving revision 1.23
diff -u -r1.23 gnc-numeric.h
--- src/engine/gnc-numeric.h	1 Nov 2004 01:37:09 -0000	1.23
+++ src/engine/gnc-numeric.h	6 Apr 2005 14:15:20 -0000
@@ -417,7 +417,7 @@
  * Shortcut for common case: gnc_numeric_add(a, b, GNC_DENOM_AUTO,
  *                        GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
  */
-static inline
+static __inline__
 gnc_numeric gnc_numeric_add_fixed(gnc_numeric a, gnc_numeric b) {
    return gnc_numeric_add(a, b, GNC_DENOM_AUTO,
                          GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
@@ -427,7 +427,7 @@
  * Shortcut for most common case: gnc_numeric_sub(a, b, GNC_DENOM_AUTO,
  *                        GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
  */
-static inline 
+static __inline__
 gnc_numeric gnc_numeric_sub_fixed(gnc_numeric a, gnc_numeric b) {
   return gnc_numeric_sub(a, b, GNC_DENOM_AUTO,
                          GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
Index: src/gnome/window-acct-tree.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/window-acct-tree.c,v
retrieving revision 1.54
diff -u -r1.54 window-acct-tree.c
--- src/gnome/window-acct-tree.c	26 Jun 2004 14:52:12 -0000	1.54
+++ src/gnome/window-acct-tree.c	6 Apr 2005 14:15:20 -0000
@@ -37,6 +37,7 @@
 #include "dialog-utils.h"
 #include "druid-stock-split.h"
 #include "global-options.h"
+#include "gnc-account-sel.h"
 #include "gnc-account-tree.h"
 #include "gnc-book.h"
 #include "gnc-component-manager.h"
@@ -428,81 +429,434 @@
   return (gpointer)(helper_res->has_splits || helper_res->has_ro_splits);
 }
 
+/***
+ *** The OK button of a Delete Account dialog is insensitive if
+ *** and only if a sensitive account selector contains no accounts.
+ ***/
+static void
+set_ok_sensitivity(GtkWidget *dialog)
+{
+  gpointer dmas, tmas;
+  gboolean sensitive;
+
+  dmas = gtk_object_get_data(GTK_OBJECT(dialog), "dmas");
+  tmas = gtk_object_get_data(GTK_OBJECT(dialog), "tmas");
+  sensitive = (((NULL == dmas) ||
+		(!GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(dmas)) ||
+		 GTK_LIST(GNC_ACCOUNT_SEL(dmas)->combo->list)->children)) &&
+	       ((NULL == tmas) ||
+		(!GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(tmas)) ||
+		 GTK_LIST(GNC_ACCOUNT_SEL(tmas)->combo->list)->children)));
+  gnome_dialog_set_sensitive(GNOME_DIALOG(dialog), 0, sensitive);
+}
+
+/***
+ *** GNCAccountSel has an odd habit of adding a
+ *** blank item when its list is otherwise empty.
+ ***/
+
+static void
+exclude_account(GtkWidget *item,
+		gpointer name)
+{
+  char *text;
+
+  gtk_label_get(GTK_LABEL(GTK_BIN(item)->child), &text);
+  if ((0 == strlen(text)) || (0 == strcmp(text, name))) {
+    gtk_widget_destroy(GTK_WIDGET(item));
+  }
+}
+
+static void
+exclude_account_subtree(GtkWidget *item,
+			gpointer prefix)
+{
+  char *text;
+
+  gtk_label_get(GTK_LABEL(GTK_BIN(item)->child), &text);
+  if ((0 == strlen(text)) || 0 == strncmp(text, prefix, strlen(prefix))) {
+    gtk_widget_destroy(GTK_WIDGET(item));
+  }
+}
+
+static gint
+compare_listitem_text(gconstpointer item,
+		      gconstpointer entrytext)
+{
+  char *text;
+
+  gtk_label_get(GTK_LABEL(GTK_BIN(item)->child), &text);
+  return strcmp(text, entrytext);
+}
+
+static void
+populate_gas_list(GtkObject *dialog,
+		  GNCAccountSel *gas,
+		  gboolean exclude_subaccounts)
+{
+  GtkList *list;
+  GtkEntry *entry;
+  gpointer name, filter;
+
+  list = GTK_LIST(gas->combo->list);
+  entry = GTK_ENTRY(gas->combo->entry);
+  name = gtk_object_get_data(dialog, "name");
+  filter = gtk_object_get_data(dialog, "filter");
+
+  /* Setting the account type filter triggers GNCAccountSel population. */
+  gnc_account_sel_set_acct_filters (gas, filter);
+
+  /* Accounts to be deleted must be removed. */
+  gtk_container_foreach(GTK_CONTAINER(list), (exclude_subaccounts ?
+					      exclude_account_subtree :
+					      exclude_account), name);
+
+  /* The entry widget may need to be reset. */
+  if (NULL == g_list_find_custom(list->children, 
+				 gtk_entry_get_text(entry),
+				 compare_listitem_text)) {
+    gtk_entry_set_text(entry, "");
+    gtk_list_select_item(list, 0);
+  }
+
+  /* The sensitivity of the OK button needs to be reevaluated. */
+  set_ok_sensitivity(GTK_WIDGET(dialog));
+}
+
+static void
+populate_tmas_list(GtkToggleButton *dmrb,
+		   gpointer tmas)
+{
+  /* Cannot move transactions to subaccounts if they are to be deleted. */
+  populate_gas_list(GTK_OBJECT(gtk_widget_get_toplevel(GTK_WIDGET(dmrb))),
+		    tmas, !gtk_toggle_button_get_active(dmrb));
+}
+
+static GtkWidget *
+create_options_frame(GtkObject *dialog,
+		     const gchar *label,
+		     const gchar *gas_key,
+		     gboolean exclude_subaccounts)
+{
+  GtkWidget *frame;
+  GtkWidget *vbox;
+  GNCAccountSel *gas;
+
+  frame = gtk_frame_new(label);
+  gtk_container_border_width(GTK_CONTAINER(frame), 5);
+  gtk_box_pack_start(GTK_BOX(GNOME_DIALOG(dialog)->vbox),
+		     frame, FALSE, FALSE, 0);
+  vbox = gtk_vbox_new(TRUE, 3);
+  gtk_container_border_width(GTK_CONTAINER(vbox), 5);
+  gtk_container_add(GTK_CONTAINER(frame), vbox);
+  gas = GNC_ACCOUNT_SEL(gnc_account_sel_new());
+  gtk_object_set_data(dialog, gas_key, gas);
+  populate_gas_list(dialog, gas, exclude_subaccounts);
+  gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(gas), FALSE, FALSE, 0);
+  return frame;
+}
+
+static void
+set_insensitive_iff_rb_active(GtkToggleButton *b, GtkWidget *widget)
+{
+  gtk_widget_set_sensitive(widget, !gtk_toggle_button_get_active(b));
+  set_ok_sensitivity(gtk_widget_get_toplevel(widget));
+}
+
+static GtkWidget *
+create_rb_group(GtkBox *vbox,
+		GNCAccountSel *gas,
+		const gchar *delete_label,
+		const gchar *move_label)
+{
+  GtkWidget *rb;
+
+  /* Create the delete radio button */
+  rb = gtk_radio_button_new_with_label(NULL, delete_label);
+  gtk_box_pack_start(vbox, rb, FALSE, FALSE, 0);
+
+  /* The account selector is insensitive when delete is active */
+  gtk_signal_connect(GTK_OBJECT(rb), "toggled",
+		     GTK_SIGNAL_FUNC(set_insensitive_iff_rb_active), gas);
+
+  /* Create the move radio button and set it active */
+  rb = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(rb),
+						   move_label);
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
+  gtk_box_pack_start(vbox, rb, FALSE, FALSE, 0);
+  return rb;
+}
+
 static void
 gnc_acct_tree_window_delete_common (Account *account)
 {
-  if (account)
-  {
-    const char *no_splits_no_children =
-			    _("Are you sure you want to delete the %s account?");
-    const char *no_splits = _("Are you sure you want to delete the %s\n"
-			      "account and all its children?");
-    const char *acct_has_splits = 
-			 _("This account contains transactions.  Are you sure you\n"
-			   "want to delete the %s account?");
-    const char *child_has_splits =
-			 _("One (or more) children of this account contain\n"
-			   "transactions.  Are you sure you want to delete the\n"
-			   "%s account and all its children?");
-    const char *acct_has_ro_splits =
-      			_("This account contains read-only transactions.  You "
-			  "may not delete %s.");
-    const char *child_has_ro_splits =
-      			_("One (or more) children of this account contains "
-			  "read-only transactions.  You may not delete %s.");
-    const char *format;
+  if (NULL == account) {
+    const char *message = _("To delete an account, you must first\n"
+                            "choose an account to delete.\n");
+    gnc_error_dialog(NULL, message);
+  } else {
     char *name;
     GList *splits;
+    GList *ptr;
+    AccountGroup *children;
+    delete_helper_t delete_res = { FALSE, FALSE };
+    GnomeDialog *dialog = NULL;
+    GNCAccountSel *dmas = NULL; /* descendant move account selector */
+    GNCAccountSel *tmas = NULL; /* transaction move account selector */
 
     name = xaccAccountGetFullName(account, gnc_get_account_separator ());
-    if (!name)
+    if (!name) {
       name = g_strdup ("");
+    }
+
+    splits = xaccAccountGetSplitList(account);
+
+    /* Check for RO txns -- if there are any, disallow deletion */
+    for (ptr = splits ; ptr ; ptr = ptr->next) {
+      Split *s = ptr->data;
+      Transaction *txn = xaccSplitGetParent (s);
+      if (xaccTransGetReadOnly (txn)) {
+	const char *acct_has_ro_splits =
+	  _("This account contains read-only transactions.\n"
+	    "You may not delete %s.");
+	
+	gnc_error_dialog (NULL, acct_has_ro_splits, name);
+	g_free(name);
+	return;
+      }
+    }
 
-    if ((splits = xaccAccountGetSplitList(account)) != NULL) {
-      /* Check for RO txns -- if there are any, disallow deletion */
-      for ( ; splits ; splits = splits->next) {
-	Split *s = splits->data;
-	Transaction *txn = xaccSplitGetParent (s);
-	if (xaccTransGetReadOnly (txn)) {
-	  gnc_error_dialog (NULL, acct_has_ro_splits, name);
-	  return;
+    children = xaccAccountGetChildren(account);
+    if (0 == xaccGroupGetNumAccounts(children)) {
+      children = NULL;
+    }
+
+    /*
+     * If the account has transactions or child accounts then conduct a
+     * dialog to allow the user to specify what should be done with them.
+     */
+    if ((NULL != splits) || (NULL != children)) {
+      GList *filter;
+      char *label_text;
+      GtkWidget *main_vbox;
+      GtkWidget *label;
+      GtkWidget *dmrb = NULL; /* descendant move radio button */
+
+      dialog = GNOME_DIALOG(gnome_dialog_new (_("Delete Account"),
+					      GNOME_STOCK_BUTTON_OK,
+					      GNOME_STOCK_BUTTON_CANCEL,
+					      NULL));
+      main_vbox = dialog->vbox;
+      gtk_container_border_width(GTK_CONTAINER(main_vbox), 5);
+      label_text = g_strdup_printf(_("Deleting account %s"), name);
+      label = gtk_label_new(label_text);
+      g_free(label_text);
+      gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
+      gtk_box_pack_start(GTK_BOX(main_vbox), label, FALSE, FALSE, 0);
+
+      /*
+       * Reparent only to accounts of the same
+       * type as the one being deleted.
+       */
+      filter = g_list_prepend(NULL, (gpointer)xaccAccountGetType(account));
+      gtk_object_set_data(GTK_OBJECT(dialog), "filter", filter);
+      gtk_object_set_data(GTK_OBJECT(dialog), "name", name);
+      /*
+       * If the account has children then create a frame
+       * for selecting options on what to do with them.
+       */
+      if (children != NULL) {
+	GtkWidget *dof; /* descendant options frame */
+	GtkBox *dovb;   /* descendant options vertical box */
+
+	dof = create_options_frame(GTK_OBJECT(dialog),
+				   _("This account has subaccounts."),
+				   "dmas", TRUE);
+	dovb = GTK_BOX(GTK_BIN(dof)->child);
+	dmas = GNC_ACCOUNT_SEL(gtk_object_get_data(GTK_OBJECT(dialog),
+						   "dmas"));
+
+	/*
+	 * Check for RO txns in descendants --
+	 * disallow deletion of descendants if there are any
+	 */
+	xaccGroupForEachAccount(children, delete_account_helper,
+				&delete_res, TRUE);
+	if (delete_res.has_ro_splits) {
+	  const char *child_has_ro_splits =
+	    _("One or more subaccounts contain read-only\n"
+	      "transactions so the subaccounts must be moved to :");
+	  GtkWidget *ddpl; /* descendant delete prohibited label */
+
+	  ddpl = gtk_label_new(child_has_ro_splits);
+	  gtk_label_set_justify(GTK_LABEL(ddpl), GTK_JUSTIFY_LEFT);
+	  gtk_box_pack_start(dovb, ddpl, FALSE, FALSE, 0);
+
+	} else {
+	  dmrb = create_rb_group(dovb, dmas,
+				 _("Delete all descendants"),
+				 _("Reparent children to :"));
 	}
       }
-      format = acct_has_splits;
-    } else {
-      AccountGroup *children;
-      delete_helper_t delete_res = { FALSE, FALSE };
-
-      children = xaccAccountGetChildren(account);
-      xaccGroupForEachAccount(children, delete_account_helper, &delete_res, TRUE);
-
-      /* Check for RO txns in the children -- disallow deletion if there are any */
-      if (delete_res.has_ro_splits) {
-	gnc_error_dialog (NULL, child_has_ro_splits, name);
+
+      /*
+       * If the account or its descendants have transactions then
+       * create a frame for selecting options on what to do with them.
+       */
+      if (splits || delete_res.has_splits) {
+	GtkWidget *tof; /* transaction options frame */
+	GtkBox *tovb;   /* transaction options vertical box */
+
+	tof = create_options_frame(GTK_OBJECT(dialog),
+				   _("Transactions exist for"
+				     " account(s) to be deleted."),
+				   "tmas", FALSE);
+	tovb = GTK_BOX(GTK_BIN(tof)->child);
+	tmas = GNC_ACCOUNT_SEL(gtk_object_get_data(GTK_OBJECT(dialog),
+						   "tmas"));
+	(void)create_rb_group(tovb, tmas,
+			      _("Delete all transactions"),
+			      _("Move transactions to :"));
+
+	if (NULL != dmrb) {
+	  gtk_signal_connect(GTK_OBJECT(dmrb), "toggled",
+			     GTK_SIGNAL_FUNC(populate_tmas_list), tmas);
+	  /*
+	   * The transaction options frame should be sensitive only when
+	   * the account to be deleted has transactions or its subaccounts
+	   * are to be deleted and one of those has transactions.
+	   */
+	  if (NULL == splits) {
+	    gtk_widget_set_sensitive(tof, FALSE);
+	    gtk_signal_connect(GTK_OBJECT(dmrb), "toggled",
+			       GTK_SIGNAL_FUNC(set_insensitive_iff_rb_active),
+			       tof);
+	  }
+	}
+      }
+
+      /* default to cancel */
+      gnome_dialog_set_default(dialog, 1);
+
+      /* Hide on close, then destroy after selections have been retrieved */
+      gnome_dialog_close_hides(dialog, TRUE);
+
+      gtk_widget_show_all(GTK_WIDGET(dialog));
+
+      /*
+       * Note that one effect of the modal dialog is preventing
+       * the account selectors from being repopulated.
+       */
+      gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+
+      if (gnome_dialog_run_and_close(dialog) != 0) {
+	/* Account deletion is cancelled, so clean up and return. */
+	gtk_widget_destroy(GTK_WIDGET(dialog));
+	g_list_free (filter);
+	g_free(name);
 	return;
+      }
+      g_list_free (filter);
+    } /* (NULL != splits) || (NULL != children) */
 
-      } else if (delete_res.has_splits) 
-	format= child_has_splits;
-      else
-	format = children ? no_splits : no_splits_no_children;
-    }
+    /*
+     * Present a message to the user which specifies what will be
+     * deleted and what will be reparented, then ask for verification.
+     */
+    {
+      const char *format = _("The %s account will be deleted.");
+      Account *da = NULL; /* descendant adopter */
+      Account *ta = NULL; /* transaction adopter */
+      char *lines[5];
+      char *message;
+      int i = 0;
+
+      lines[0] = g_strdup_printf(format, name);
+      if (dmas) {
+	if (GTK_WIDGET_IS_SENSITIVE(dmas) &&
+	    (NULL !=
+	     (da = gnc_account_sel_get_account(dmas)))) {
+	  char *daname;
+	  
+	  daname = xaccAccountGetFullName(da, gnc_get_account_separator ());
+	  format = _("All of its subaccounts will be adopted by\n"
+		     "the %s account.");
+	  lines[++i] = g_strdup_printf(format, daname);
+	} else {
+	  format = _("All of its subaccounts will also be deleted.");
+	  lines[++i] = g_strdup_printf(format);
+	}
+      }
+      if (tmas) {
+	if (GTK_WIDGET_IS_SENSITIVE(tmas) &&
+	    (NULL !=
+	     (ta = gnc_account_sel_get_account(tmas)))) {
+	  char *taname;
+	  
+	  taname = xaccAccountGetFullName(ta, gnc_get_account_separator ());
+	  format = _("All transactions will be reassigned to\n"
+		     "the %s account.");
+	  lines[++i] = g_strdup_printf(format, taname);
+	} else if (delete_res.has_splits && (NULL == da)) {
+	  format = _("All transactions in these accounts will be deleted.");
+	  lines[++i] = g_strdup_printf(format);
+	} else if (splits) {
+	  format = _("All transactions in the %s\n"
+		     "account will be deleted.");
+	  lines[++i] = g_strdup_printf(format, name);
+	}
+      }
+      lines[++i] = _("Are you sure you want to do this ?");
+      lines[i] = NULL;
+      i--; /* Don't try to free the constant question. */
+      message = g_strjoinv("\n", lines);
+      while (i--) {
+	g_free(lines[i]);
+      }
 
-    if (gnc_verify_dialog(NULL, FALSE, format, name)) {
-      gnc_suspend_gui_refresh ();
-      
-      xaccAccountBeginEdit (account);
-      xaccAccountDestroy (account);
-      
-      gnc_resume_gui_refresh ();
+      if (NULL != dialog) {
+	gtk_widget_destroy(GTK_WIDGET(dialog));
+      }
+      dialog = GNOME_DIALOG(gnome_message_box_new(message,
+						  GNOME_MESSAGE_BOX_QUESTION,
+						  GNOME_STOCK_BUTTON_OK,
+						  GNOME_STOCK_BUTTON_CANCEL,
+						  NULL));
+
+      /* default to cancel */
+      gnome_dialog_set_default(dialog, 1);
+
+      if (0 == gnome_dialog_run_and_close(dialog)) {
+	gnc_suspend_gui_refresh ();
+	xaccAccountBeginEdit (account);
+	if (NULL != da) {
+	  /* Ensure that an AccountGroup exists in the adopter. */
+	  xaccAccountInsertSubAccount (da, NULL);
+	  /* Reparent the children of the account being deleted. */
+	  xaccGroupConcatGroup (xaccAccountGetChildren(da), children);
+	  /* Ensure that splits of subaccounts stay where they are. */
+	  children = NULL;
+	}
+	if (NULL != ta) {
+	  /* Move the splits of the account to be deleted. */
+	  xaccAccountMoveAllSplits (account, ta);
+	  /* Move the splits of its subaccounts, if any. */
+	  xaccGroupForEachAccount (children,
+				   (gpointer (*)(Account *, gpointer))
+				   xaccAccountMoveAllSplits,
+				   ta, TRUE);
+	}
+	/*
+	 * Finally, delete the account, any subaccounts it may still
+	 * have, and any splits it or its subaccounts may still have.
+	 */
+	xaccAccountDestroy (account);
+	gnc_resume_gui_refresh ();
+      }
+      g_free(message);
     }
     g_free(name);
   }
-  else
-  {
-    const char *message = _("To delete an account, you must first\n"
-                            "choose an account to delete.\n");
-    gnc_error_dialog(NULL, message);
-  }
 }
 
 static void
-------------- next part --------------

-- 
David N. Jafferian
+1 781.442.1284 (x21284)
Solaris Kernel and Drivers
Product Technical Support
Sun Microsystems, Inc.



More information about the gnucash-devel mailing list