GnuCash 2.4.99
window-autoclear.c
00001 /********************************************************************\
00002  * window-autoclear.c -- the autoclear window                       *
00003  * Copyright (C) 2010 Cristian KLEIN                                *
00004  *                                                                  *
00005  * This program is free software; you can redistribute it and/or    *
00006  * modify it under the terms of the GNU General Public License as   *
00007  * published by the Free Software Foundation; either version 2 of   *
00008  * the License, or (at your option) any later version.              *
00009  *                                                                  *
00010  * This program is distributed in the hope that it will be useful,  *
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00013  * GNU General Public License for more details.                     *
00014  *                                                                  *
00015  * You should have received a copy of the GNU General Public License*
00016  * along with this program; if not, contact:                        *
00017  *                                                                  *
00018  * Free Software Foundation           Voice:  +1-617-542-5942       *
00019  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00020  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00021 \********************************************************************/
00022 
00023 #include "config.h"
00024 
00025 #include <gtk/gtk.h>
00026 #include <glib/gi18n.h>
00027 
00028 #include "Scrub.h"
00029 #include "Scrub3.h"
00030 #include "dialog-account.h"
00031 #include "dialog-transfer.h"
00032 #include "dialog-utils.h"
00033 #include "gnc-amount-edit.h"
00034 #include "gnc-component-manager.h"
00035 #include "gnc-date-edit.h"
00036 #include "gnc-event.h"
00037 #include "gnc-gconf-utils.h"
00038 #include "gnc-gnome-utils.h"
00039 #include "gnc-main-window.h"
00040 #include "gnc-plugin-page-register.h"
00041 #include "gnc-ui.h"
00042 #include "guile-util.h"
00043 #include "window-autoclear.h"
00044 
00045 #define WINDOW_AUTOCLEAR_CM_CLASS "window-autoclear"
00046 
00047 static QofLogModule log_module = GNC_MOD_GUI;
00048 
00050 struct _AutoClearWindow
00051 {
00052     Account *account;        /* The account that we are auto-clearing */
00053 
00054     gint component_id;       /* id of component                       */
00055 
00056     GtkWidget *window;       /* The auto-clear window                 */
00057     GNCAmountEdit *end_value;/* The ending value                      */
00058     GtkWidget *ok_button;
00059     GtkWidget *cancel_button;
00060     GtkLabel *status_label;
00061 };
00062 
00064 void gnc_autoclear_window_ok_cb     (GtkWidget *widget,
00065                                      AutoClearWindow *data);
00066 void gnc_autoclear_window_cancel_cb (GtkWidget *widget,
00067                                      AutoClearWindow *data);
00068 
00069 /********************************************************************\
00070  * gnc_ui_autoclear_window_raise                                    *
00071  *   shows and raises an auto-clear window                          *
00072  *                                                                  *
00073  * Args:   autoClearData - the auto-clear window structure          *
00074 \********************************************************************/
00075 void
00076 gnc_ui_autoclear_window_raise(AutoClearWindow * autoClearData)
00077 {
00078     if (autoClearData == NULL)
00079         return;
00080 
00081     if (autoClearData->window == NULL)
00082         return;
00083 
00084     gtk_window_present(GTK_WINDOW(autoClearData->window));
00085 }
00086 
00087 static char *
00088 gnc_autoclear_make_window_name(Account *account)
00089 {
00090     char *fullname;
00091     char *title;
00092 
00093     fullname = gnc_account_get_full_name(account);
00094     title = g_strconcat(fullname, " - ", _("Auto-clear"), NULL);
00095 
00096     g_free(fullname);
00097 
00098     return title;
00099 }
00100 
00101 static gboolean
00102 ght_gnc_numeric_equal(gconstpointer v1, gconstpointer v2)
00103 {
00104     gnc_numeric n1 = *(gnc_numeric *)v1, n2 = *(gnc_numeric *)v2;
00105     return gnc_numeric_equal(n1, n2);
00106 }
00107 
00108 static guint
00109 ght_gnc_numeric_hash(gconstpointer v1)
00110 {
00111     gnc_numeric n1 = *(gnc_numeric *)v1;
00112     gdouble d1 = gnc_numeric_to_double(n1);
00113     return g_str_hash(&d1);
00114 }
00115 
00116 typedef struct _sack_foreach_data_t
00117 {
00118     gnc_numeric split_value;
00119     GList *reachable_list;
00120 } *sack_foreach_data_t;
00121 
00122 static void sack_foreach_func(gpointer key, gpointer value, gpointer user_data)
00123 {
00124     sack_foreach_data_t data = (sack_foreach_data_t)user_data;
00125     gnc_numeric thisvalue = *(gnc_numeric *)key;
00126 
00127     gnc_numeric reachable_value = gnc_numeric_add_fixed(thisvalue, data->split_value);
00128     data->reachable_list = g_list_append(data->reachable_list, g_memdup(&reachable_value, sizeof(gnc_numeric)));
00129     PINFO("    Sack: found %s, added %s\n", gnc_numeric_to_string(thisvalue), gnc_numeric_to_string(reachable_value));
00130 }
00131 
00132 void
00133 gnc_autoclear_window_ok_cb (GtkWidget *widget,
00134                             AutoClearWindow *data)
00135 {
00136     GList *node, *nc_list = 0, *toclear_list = 0;
00137     gnc_numeric toclear_value;
00138     GHashTable *sack;
00139 
00140     gtk_label_set_text(data->status_label, "Searching for splits to clear ...");
00141 
00142     /* Value we have to reach */
00143     toclear_value = gnc_amount_edit_get_amount(data->end_value);
00144     toclear_value = gnc_numeric_convert(toclear_value, xaccAccountGetCommoditySCU(data->account), GNC_HOW_RND_NEVER);
00145 
00146     /* Extract which splits are not cleared and compute the amount we have to clear */
00147     for (node = xaccAccountGetSplitList(data->account); node; node = node->next)
00148     {
00149         Split *split = (Split *)node->data;
00150         char recn;
00151         gnc_numeric value;
00152 
00153         recn = xaccSplitGetReconcile (split);
00154         value = xaccSplitGetAmount (split);
00155 
00156         if (recn == NREC)
00157             nc_list = g_list_append(nc_list, split);
00158         else
00159             toclear_value = gnc_numeric_sub_fixed(toclear_value, value);
00160     }
00161 
00162     /* Pretty print information */
00163     PINFO("Amount to clear: %s\n", gnc_numeric_to_string(toclear_value));
00164     PINFO("Available splits:\n");
00165     for (node = nc_list; node; node = node->next)
00166     {
00167         Split *split = (Split *)node->data;
00168         gnc_numeric value = xaccSplitGetAmount (split);
00169         PINFO("  %s\n", gnc_numeric_to_string(value));
00170     }
00171 
00172     /* Run knapsack */
00173     /* Entries in the hash table are:
00174      *  - key   = amount to which we know how to clear (freed by GHashTable)
00175      *  - value = last split we used to clear this amount (not managed by GHashTable)
00176      */
00177     PINFO("Knapsacking ...\n");
00178     sack = g_hash_table_new_full (ght_gnc_numeric_hash, ght_gnc_numeric_equal, g_free, NULL);
00179     for (node = nc_list; node; node = node->next)
00180     {
00181         Split *split = (Split *)node->data;
00182         gnc_numeric split_value = xaccSplitGetAmount(split);
00183 
00184         GList *node;
00185         struct _sack_foreach_data_t data[1];
00186         data->split_value = split_value;
00187         data->reachable_list = 0;
00188 
00189         PINFO("  Split value: %s\n", gnc_numeric_to_string(split_value));
00190 
00191         /* For each value in the sack, compute a new reachable value */
00192         g_hash_table_foreach (sack, sack_foreach_func, data);
00193 
00194         /* Add the value of the split itself to the reachable_list */
00195         data->reachable_list = g_list_append(data->reachable_list, g_memdup(&split_value, sizeof(gnc_numeric)));
00196 
00197         /* Add everything to the sack, looking out for duplicates */
00198         for (node = data->reachable_list; node; node = node->next)
00199         {
00200             gnc_numeric *reachable_value = node->data;
00201             Split *toinsert_split = split;
00202 
00203             PINFO("    Reachable value: %s ", gnc_numeric_to_string(*reachable_value));
00204 
00205             /* Check if it already exists */
00206             if (g_hash_table_lookup_extended(sack, reachable_value, NULL, NULL))
00207             {
00208                 /* If yes, we are in trouble, we reached an amount using two solutions */
00209                 toinsert_split = NULL;
00210                 PINFO("dup");
00211             }
00212             g_hash_table_insert (sack, reachable_value, toinsert_split);
00213             PINFO("\n");
00214         }
00215         g_list_free(data->reachable_list);
00216     }
00217 
00218     /* Check solution */
00219     PINFO("Rebuilding solution ...\n");
00220     while (!gnc_numeric_zero_p(toclear_value))
00221     {
00222         gpointer psplit = NULL;
00223 
00224         PINFO("  Left to clear: %s\n", gnc_numeric_to_string(toclear_value));
00225         if (g_hash_table_lookup_extended(sack, &toclear_value, NULL, &psplit))
00226         {
00227             if (psplit != NULL)
00228             {
00229                 /* Cast the gpointer to the kind of pointer we actually need */
00230                 Split *split = (Split *)psplit;
00231                 toclear_list = g_list_prepend(toclear_list, split);
00232                 toclear_value = gnc_numeric_sub_fixed(toclear_value,
00233                                                       xaccSplitGetAmount(split));
00234                 PINFO("    Cleared: %s -> %s\n",
00235                       gnc_numeric_to_string(xaccSplitGetAmount(split)),
00236                       gnc_numeric_to_string(toclear_value));
00237             }
00238             else
00239             {
00240                 /* We couldn't reconstruct the solution */
00241                 PINFO("    Solution not unique.\n");
00242                 gtk_label_set_text(data->status_label, "Cannot uniquely clear splits. Found multiple possibilities.");
00243                 return;
00244             }
00245         }
00246         else
00247         {
00248             PINFO("    No solution found.\n");
00249             gtk_label_set_text(data->status_label, "The selected amount cannot be cleared.");
00250             return;
00251         }
00252     }
00253     g_hash_table_destroy (sack);
00254 
00255     /* Show solution */
00256     PINFO("Clearing splits:\n");
00257     for (node = toclear_list; node; node = node->next)
00258     {
00259         Split *split = node->data;
00260         char recn;
00261         gnc_numeric value;
00262 
00263         recn = xaccSplitGetReconcile (split);
00264         value = xaccSplitGetAmount (split);
00265 
00266         PINFO("  %c %s\n", recn, gnc_numeric_to_string(value));
00267 
00268         xaccSplitSetReconcile (split, CREC);
00269     }
00270     if (toclear_list == 0)
00271         PINFO("  None\n");
00272 
00273     /* Free lists */
00274     g_list_free(nc_list);
00275     g_list_free(toclear_list);
00276 
00277     /* Close window */
00278     gtk_widget_destroy(data->window);
00279     g_free(data);
00280 }
00281 
00282 void
00283 gnc_autoclear_window_cancel_cb (GtkWidget *widget,
00284                                 AutoClearWindow *data)
00285 {
00286     /* Close window */
00287     gtk_widget_destroy(data->window);
00288     g_free(data);
00289 }
00290 
00291 /********************************************************************\
00292  * autoClearWindow                                                  *
00293  *   opens up the window to auto-clear an account                   *
00294  *                                                                  *
00295  * Args:   parent  - the parent of this window                      *
00296  *         account - the account to auto-clear                      *
00297  * Return: autoClearData - the instance of this AutoClearWindow     *
00298 \********************************************************************/
00299 AutoClearWindow *
00300 autoClearWindow (GtkWidget *parent, Account *account)
00301 {
00302     GtkBox *box;
00303     GtkLabel *label;
00304     GtkBuilder *builder;
00305     AutoClearWindow *data;
00306     char *title;
00307 
00308     data = g_new0 (AutoClearWindow, 1);
00309     data->account = account;
00310 
00311     /* Create the dialog box */
00312     builder = gtk_builder_new();
00313     gnc_builder_add_from_file (builder, "window-autoclear.glade", "Auto-clear Start Dialog");
00314     data->window = GTK_WIDGET(gtk_builder_get_object (builder, "Auto-clear Start Dialog"));
00315     title = gnc_autoclear_make_window_name (account);
00316     gtk_window_set_title(GTK_WINDOW(data->window), title);
00317     g_free (title);
00318 
00319     /* Add amount edit box */
00320     data->end_value = GNC_AMOUNT_EDIT(gnc_amount_edit_new());
00321     g_signal_connect(GTK_WIDGET(data->end_value), "activate",
00322                      G_CALLBACK(gnc_autoclear_window_ok_cb), data);
00323 
00324     box   = GTK_BOX(gtk_builder_get_object (builder, "end_value_box"));
00325     gtk_box_pack_start(box, GTK_WIDGET(data->end_value), TRUE, TRUE, 0);
00326 
00327     label = GTK_LABEL(gtk_builder_get_object (builder, "end_label"));
00328     gtk_label_set_mnemonic_widget(label, GTK_WIDGET(data->end_value));
00329     gtk_widget_grab_focus(GTK_WIDGET(data->end_value));
00330 
00331     data->status_label = GTK_LABEL(gtk_builder_get_object (builder, "status_label"));
00332 
00333     if (parent != NULL)
00334         gtk_window_set_transient_for (GTK_WINDOW (data->window), GTK_WINDOW (parent));
00335 
00336     gtk_builder_connect_signals(builder, data);
00337     g_object_unref(G_OBJECT(builder));
00338 
00339     return data;
00340 }
00341 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines