/* HomeBank -- Free, easy, personal accounting for everyone. * Copyright (C) 1995-2024 Maxime DOYEN * * This file is part of HomeBank. * * HomeBank 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. * * HomeBank is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty ofdeftransaction_amountchanged * 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, see . */ #include "homebank.h" #include "ui-split.h" #include "ui-transaction.h" #include "ui-archive.h" #include "gtk-dateentry.h" #include "ui-payee.h" #include "ui-category.h" #include "ui-account.h" #include "hb-split.h" /****************************************************************************/ /* Debug macros */ /****************************************************************************/ #define MYDEBUG 0 #if MYDEBUG #define DB(x) (x); #else #define DB(x); #endif /* our global datas */ extern struct HomeBank *GLOBALS; extern struct Preferences *PREFS; #define GTK_RESPONSE_SPLIT_REM 10888 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ static void list_split_cell_data_func_number (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { GtkTreePath *path; gint *indices; gchar num[16]; path = gtk_tree_model_get_path(model, iter); indices = gtk_tree_path_get_indices(path); //num = gtk_tree_path_to_string(path); g_snprintf(num, 15, "%d", 1 + *indices); gtk_tree_path_free(path); g_object_set(renderer, "text", num, NULL); } static void list_split_cell_data_func_amount (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { Split *split; gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; gchar *color; guint32 kcur; gtk_tree_model_get(model, iter, 0, &split, -1); kcur = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(col), "kcur_data")); hb_strfmon(buf, G_ASCII_DTOSTR_BUF_SIZE-1, split->amount, kcur, FALSE); color = get_normal_color_amount(split->amount); g_object_set(renderer, "foreground", color, "text", buf, NULL); } static void list_split_cell_data_func_memo (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { Split *split; gtk_tree_model_get(model, iter, 0, &split, -1); g_object_set(renderer, "text", split->memo, NULL); } static void list_split_cell_data_func_category (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { Split *split; Category *cat; gtk_tree_model_get(model, iter, 0, &split, -1); cat = da_cat_get(split->kcat); if( cat != NULL ) { g_object_set(renderer, "text", cat->fullname, NULL); } else g_object_set(renderer, "text", "", NULL); } static void list_split_populate(GtkWidget *treeview, GPtrArray *splits) { GtkTreeModel *model; GtkTreeIter iter; Split *split; gint count, i; DB( g_print("\n[list_split] populate\n") ); count = da_splits_length (splits); if( count <= 0 ) return; model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview)); gtk_list_store_clear (GTK_LIST_STORE(model)); g_object_ref(model); /* Make sure the model stays with us after the tree view unrefs it */ gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), NULL); /* Detach model from view */ /* populate */ for(i=0 ; i < count ; i++) { split = da_splits_get(splits, i); DB( g_print(" append split %d : %d, %.2f, '%s'\n", i, split->kcat, split->amount, split->memo) ); gtk_list_store_append (GTK_LIST_STORE(model), &iter); gtk_list_store_set (GTK_LIST_STORE(model), &iter, 0, split, -1); } gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), model); /* Re-attach model to view */ g_object_unref(model); } static GtkWidget * list_split_new(guint32 kcur) { GtkListStore *store; GtkWidget *treeview; GtkCellRenderer *renderer; GtkTreeViewColumn *column; DB( g_print("\n[ui_split_listview] new\n") ); // create list store store = gtk_list_store_new(1, G_TYPE_POINTER ); // treeview treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); g_object_unref(store); gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (treeview), PREFS->grid_lines); //column 0: line number renderer = gtk_cell_renderer_text_new (); g_object_set(renderer, "xalign", 1.0, NULL); column = gtk_tree_view_column_new_with_attributes("#", renderer, NULL); gtk_tree_view_column_set_alignment (column, 1.0); gtk_tree_view_column_set_cell_data_func(column, renderer, list_split_cell_data_func_number, NULL, NULL); gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), column); // column 1: category renderer = gtk_cell_renderer_text_new (); g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, "ellipsize-set", TRUE, //taken from nemo, not exactly a resize to content, but good compromise "width-chars", 20, NULL); column = gtk_tree_view_column_new_with_attributes(_("Category"), renderer, NULL); //gtk_tree_view_column_set_alignment (column, 0.5); gtk_tree_view_column_set_resizable(column, TRUE); //gtk_tree_view_column_set_sort_column_id (column, sortcolumnid); //gtk_tree_view_column_set_fixed_width( column, HB_MINWIDTH_LIST); gtk_tree_view_column_set_expand (column, TRUE); gtk_tree_view_column_set_cell_data_func(column, renderer, list_split_cell_data_func_category, NULL, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), column); // column 2: memo renderer = gtk_cell_renderer_text_new (); g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, "ellipsize-set", TRUE, //taken from nemo, not exactly a resize to content, but good compromise "width-chars", 20, NULL); column = gtk_tree_view_column_new_with_attributes(_("Memo"), renderer, NULL); //gtk_tree_view_column_set_alignment (column, 0.5); gtk_tree_view_column_set_resizable(column, TRUE); //gtk_tree_view_column_set_sort_column_id (column, sortcolumnid); //gtk_tree_view_column_set_fixed_width( column, HB_MINWIDTH_LIST); gtk_tree_view_column_set_expand (column, TRUE); gtk_tree_view_column_set_cell_data_func(column, renderer, list_split_cell_data_func_memo, NULL, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), column); // column 3: amount renderer = gtk_cell_renderer_text_new (); g_object_set(renderer, "xalign", 1.0, NULL); column = gtk_tree_view_column_new_with_attributes(_("Amount"), renderer, NULL); g_object_set_data(G_OBJECT(column), "kcur_data", GINT_TO_POINTER(kcur)); gtk_tree_view_column_set_alignment (column, 1.0); gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); //gtk_tree_view_column_set_sort_column_id (column, sortcolumnid); //gtk_tree_view_column_set_fixed_width( column, HB_MINWIDTH_LIST); gtk_tree_view_column_set_cell_data_func(column, renderer, list_split_cell_data_func_amount, NULL, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), column); // column empty //column = gtk_tree_view_column_new(); //gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), column); // treeviewattribute gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(treeview), TRUE); gtk_tree_view_set_reorderable (GTK_TREE_VIEW(treeview), TRUE); gtk_tree_view_set_enable_search(GTK_TREE_VIEW(treeview), FALSE); //gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(store), ui_acc_listview_compare_func, NULL, NULL); //gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING); return treeview; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /*static gboolean ui_split_dialog_cb_output_amount(GtkSpinButton *spin, gpointer data) { GtkAdjustment *adjustment; gchar *text; gdouble value; //gpointer position; DB( g_print("\n[ui_split_dialog] amount output\n") ); adjustment = gtk_spin_button_get_adjustment (spin); value = gtk_adjustment_get_value (adjustment); if( value == 0.0 ) { gtk_entry_set_text (GTK_ENTRY (spin), ""); } else { text = g_strdup_printf ("%+.*f", gtk_spin_button_get_digits(spin), value); DB( g_print(" output '%s'\n", text) ); gtk_entry_set_text (GTK_ENTRY (spin), text); //gtk_editable_delete_text (GTK_EDITABLE(spin), 0, -1); //gtk_editable_insert_text (GTK_EDITABLE(spin), text, -1, &position); g_free (text); } return TRUE; }*/ static void ui_split_dialog_filter_text_handler (GtkEntry *entry, const gchar *text, gint length, gint *position, gpointer data) { GtkEditable *editable = GTK_EDITABLE(entry); gint i, count=0; gchar *result = g_new0 (gchar, length+1); for (i=0; i < length; i++) { if (text[i]=='|') continue; result[count++] = text[i]; } if (count > 0) { g_signal_handlers_block_by_func (G_OBJECT (editable), G_CALLBACK (ui_split_dialog_filter_text_handler), data); gtk_editable_insert_text (editable, result, count, position); g_signal_handlers_unblock_by_func (G_OBJECT (editable), G_CALLBACK (ui_split_dialog_filter_text_handler), data); } g_signal_stop_emission_by_name (G_OBJECT (editable), "insert-text"); g_free (result); } static void ui_split_dialog_cb_eval_order(struct ui_split_dialog_data *data) { GtkTreeModel *model; GtkTreeIter iter; gboolean valid; guint i; DB( g_print("\n[ui_split_dialog] eval order\n") ); model = gtk_tree_view_get_model(GTK_TREE_VIEW(data->LV_split)); i=1; valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter); while (valid) { Split *split; gtk_tree_model_get (model, &iter, 0, &split, -1); split->pos = i; DB( g_print(" split pos: %d '%s' %.2f\n", i, split->memo, split->amount) ); i++; valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter); } da_splits_sort(data->tmp_splits); } static gboolean ui_split_dialog_cb_amount_focus_out (GtkEditable *spin_button, GdkEvent *event, gpointer user_data) { struct ui_split_dialog_data *data; const gchar *txt; DB( g_print("\n[ui_split_dialog] cb amount focus out\n") ); data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(spin_button), GTK_TYPE_WINDOW)), "inst_data"); txt = gtk_entry_get_text(GTK_ENTRY(spin_button)); data->amountsign = SPLIT_AMT_SIGN_OFF; if( *txt == '+' ) { data->amountsign = SPLIT_AMT_SIGN_INC; } else if( *txt == '-' ) { data->amountsign = SPLIT_AMT_SIGN_EXP; } DB( g_print(" txt='%s'\n amt=%.8f\n sign=%d\n", txt, gtk_spin_button_get_value (GTK_SPIN_BUTTON(spin_button)), data->amountsign) ); return FALSE; } static void ui_split_dialog_update(GtkWidget *widget, gpointer user_data) { struct ui_split_dialog_data *data; gboolean tmpval; guint count; DB( g_print("\n[ui_split_dialog] update\n") ); data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(widget), GTK_TYPE_WINDOW)), "inst_data"); count = da_splits_length (data->tmp_splits); //btn: edit/rem tmpval = gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(data->LV_split)), NULL, NULL); gtk_widget_set_sensitive (data->BT_edit, (data->isedited) ? FALSE : tmpval); gtk_widget_set_sensitive (data->BT_rem, (data->isedited) ? FALSE : tmpval); //btn: remall tmpval = (count > 1) ? TRUE : FALSE; gtk_widget_set_sensitive (data->BT_remall, (data->isedited) ? FALSE : tmpval); //btn: add/apply /*amount = gtk_spin_button_get_value(GTK_SPIN_BUTTON(data->ST_amount)); tmpval = hb_amount_round(amount, 2) != 0.0 ? TRUE : FALSE; gtk_widget_set_sensitive (data->BT_apply, tmpval); */ //btn: add tmpval = ( count >= TXN_MAX_SPLIT ) ? FALSE : TRUE; gtk_widget_set_sensitive (data->BT_add, tmpval); if( data->isedited ) tmpval = TRUE; gtk_widget_set_sensitive (data->PO_cat, tmpval); gtk_widget_set_sensitive (data->ST_memo, tmpval); gtk_widget_set_sensitive (data->ST_amount, tmpval); //btn: show/hide gtk_widget_set_sensitive (data->LV_split, !data->isedited); hb_widget_visible (data->BT_add, !data->isedited); hb_widget_visible (data->IM_edit, data->isedited); hb_widget_visible (data->BT_apply, data->isedited); hb_widget_visible (data->BT_cancel, data->isedited); } static void ui_split_dialog_edit_end(GtkWidget *widget, gpointer user_data) { struct ui_split_dialog_data *data; DB( g_print("\n[ui_split_dialog] edit_end\n") ); data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(widget), GTK_TYPE_WINDOW)), "inst_data"); //ui_cat_comboboxentry_set_active(GTK_COMBO_BOX(data->PO_cat), 0); ui_cat_entry_popover_set_active(GTK_BOX(data->PO_cat), 0); if( data->mode == SPLIT_MODE_EMPTY ) gtk_spin_button_set_value(GTK_SPIN_BUTTON(data->ST_amount), 0.0); gtk_entry_set_text(GTK_ENTRY(data->ST_memo), ""); gtk_widget_grab_focus(data->ST_amount); data->isedited = FALSE; } static void ui_split_dialog_edit_start(GtkWidget *widget, gpointer user_data) { struct ui_split_dialog_data *data; GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; DB( g_print("\n[ui_split_dialog] edit_start\n") ); data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(widget), GTK_TYPE_WINDOW)), "inst_data"); model = gtk_tree_view_get_model(GTK_TREE_VIEW(data->LV_split)); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->LV_split)); if (gtk_tree_selection_get_selected(selection, &model, &iter)) { Split *split; gchar *txt; gtk_tree_model_get(model, &iter, 0, &split, -1); //ui_cat_comboboxentry_set_active(GTK_COMBO_BOX(data->PO_cat), split->kcat); ui_cat_entry_popover_set_active(GTK_BOX(data->PO_cat), split->kcat); gtk_spin_button_set_value(GTK_SPIN_BUTTON(data->ST_amount), split->amount); txt = (split->memo != NULL) ? split->memo : ""; gtk_entry_set_text(GTK_ENTRY(data->ST_memo), txt); data->isedited = TRUE; ui_split_dialog_update (data->dialog, user_data); } } static void ui_split_dialog_cancel_cb(GtkWidget *widget, gpointer user_data) { //struct ui_split_dialog_data *data; DB( g_print("\n[ui_split_dialog] cancel\n") ); //data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(widget), GTK_TYPE_WINDOW)), "inst_data"); ui_split_dialog_edit_end(widget, user_data); ui_split_dialog_update (widget, user_data); } static void ui_split_dialog_apply_cb(GtkWidget *widget, gpointer user_data) { struct ui_split_dialog_data *data; GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; DB( g_print("--------\n[ui_split_dialog] apply\n") ); data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(widget), GTK_TYPE_WINDOW)), "inst_data"); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->LV_split)); if (gtk_tree_selection_get_selected(selection, &model, &iter)) { Split *split; gdouble amount; gtk_tree_model_get(model, &iter, 0, &split, -1); DB( g_print(" update spin\n") ); gtk_spin_button_update (GTK_SPIN_BUTTON(data->ST_amount)); amount = gtk_spin_button_get_value(GTK_SPIN_BUTTON(data->ST_amount)); if(amount) { //split->kcat = ui_cat_comboboxentry_get_key_add_new(GTK_COMBO_BOX(data->PO_cat)); split->kcat = ui_cat_entry_popover_get_key_add_new(GTK_BOX(data->PO_cat)); g_free(split->memo); split->memo = g_strdup((gchar *)gtk_entry_get_text(GTK_ENTRY(data->ST_memo))); //split->amount = amount; //#1910819 must round frac digit split->amount = hb_amount_round(amount, data->cur->frac_digits); } } ui_split_dialog_edit_end(widget, user_data); ui_split_dialog_compute (widget, data); ui_split_dialog_update (widget, user_data); } static void ui_split_dialog_deleteall_cb(GtkWidget *widget, gpointer user_data) { struct ui_split_dialog_data *data; gint result; DB( g_print("\n[ui_split_dialog] deleteall_cb\n") ); data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(widget), GTK_TYPE_WINDOW)), "inst_data"); result = ui_dialog_msg_confirm_alert( GTK_WINDOW(data->dialog), NULL, _("Do you want to delete all split lines"), _("_Delete"), TRUE ); if(result == GTK_RESPONSE_OK) { gtk_list_store_clear (GTK_LIST_STORE(gtk_tree_view_get_model (GTK_TREE_VIEW(data->LV_split)))); da_split_destroy(data->tmp_splits); data->tmp_splits = da_split_new (); ui_split_dialog_compute (widget, data); ui_split_dialog_update (widget, user_data); } } static void ui_split_dialog_delete_cb(GtkWidget *widget, gpointer user_data) { struct ui_split_dialog_data *data; GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; DB( g_print("\n[ui_split_dialog] delete_cb\n") ); data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(widget), GTK_TYPE_WINDOW)), "inst_data"); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->LV_split)); if (gtk_tree_selection_get_selected(selection, &model, &iter)) { Split *split; gtk_tree_model_get(model, &iter, 0, &split, -1); //todo: not implemented yet gtk_list_store_remove(GTK_LIST_STORE(model), &iter); da_splits_delete(data->tmp_splits, split); } ui_split_dialog_compute (widget, data); ui_split_dialog_update (widget, user_data); } static void ui_split_dialog_add_cb(GtkWidget *widget, gpointer user_data) { struct ui_split_dialog_data *data; GtkTreeModel *model; GtkTreeIter iter; Split *split; guint count; gdouble amount; DB( g_print("--------\n[ui_split_dialog] add\n") ); data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(widget), GTK_TYPE_WINDOW)), "inst_data"); count = da_splits_length (data->tmp_splits); DB( g_print(" n_split: %d (of %d)\n", count, TXN_MAX_SPLIT) ); if( count <= TXN_MAX_SPLIT ) { split = da_split_malloc (); //5.4.4 DB( g_print(" update spin\n") ); gtk_spin_button_update (GTK_SPIN_BUTTON(data->ST_amount)); amount = gtk_spin_button_get_value(GTK_SPIN_BUTTON(data->ST_amount)); if(amount) { DB( g_print(" raw amt=%.2f\n", amount) ); switch( data->amountsign ) { case SPLIT_AMT_SIGN_EXP: if( amount > 0) amount *= -1; break; case SPLIT_AMT_SIGN_INC: if( amount < 0) amount *= -1; break; default: if( hb_amount_type_match(amount, data->txntype) == FALSE ) amount *= -1; break; } DB( g_print(" final amt=%.2f\n", amount) ); //split->amount = amount; //#1910819 must round frac digit split->amount = hb_amount_round(amount, data->cur->frac_digits); //split->kcat = ui_cat_comboboxentry_get_key_add_new(GTK_COMBO_BOX(data->PO_cat)); split->kcat = ui_cat_entry_popover_get_key_add_new(GTK_BOX(data->PO_cat)); split->memo = g_strdup((gchar *)gtk_entry_get_text(GTK_ENTRY(data->ST_memo))); //#1977686 add into memo autocomplete if( da_transaction_insert_memo(split->memo, data->date) ) { GtkEntryCompletion *completion; GtkTreeModel *model; GtkTreeIter iter; completion = gtk_entry_get_completion (GTK_ENTRY(data->ST_memo)); model = gtk_entry_completion_get_model (completion); gtk_list_store_insert_with_values(GTK_LIST_STORE(model), &iter, -1, 0, split->memo, -1); } DB( g_print(" append split : %d, %.2f, %s\n", split->kcat, split->amount, split->memo) ); da_splits_append (data->tmp_splits, split); model = gtk_tree_view_get_model(GTK_TREE_VIEW(data->LV_split)); gtk_list_store_append (GTK_LIST_STORE(model), &iter); gtk_list_store_set (GTK_LIST_STORE(model), &iter, 0, split, -1); ui_split_dialog_compute (widget, data); } // 0 amount not allowed into splits else { da_split_free(split); } } else { g_warning("split error: limit of %d reached", TXN_MAX_SPLIT); } ui_split_dialog_edit_end(widget, user_data); ui_split_dialog_update (widget, user_data); } static void ui_split_dialog_cb_amount_activate(GtkWidget *widget, gpointer user_data) { struct ui_split_dialog_data *data; DB( g_print("\n[ui_split_dialog] cb amount activate\n") ); data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(widget), GTK_TYPE_WINDOW)), "inst_data"); //we trigger the focus-out-event on spinbutton, with grab the add button //because we also do things before the legacy spinbutton fucntion gtk_widget_grab_focus(data->BT_add); if( data->isedited == TRUE ) ui_split_dialog_apply_cb(widget, NULL); else ui_split_dialog_add_cb(widget, NULL); } static void ui_split_rowactivated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data) { DB( g_print("\n[ui_split_dialog] rowactivated\n") ); ui_split_dialog_edit_start(GTK_WIDGET(treeview), NULL); } static void ui_split_selection(GtkTreeSelection *treeselection, gpointer user_data) { DB( g_print("\n[ui_split_dialog] selection\n") ); ui_split_dialog_update (GTK_WIDGET(gtk_tree_selection_get_tree_view (treeselection)), user_data); } void ui_split_dialog_compute(GtkWidget *widget, gpointer user_data) { struct ui_split_dialog_data *data; gint i, count, nbvalid; gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; gboolean sensitive; GtkTreeModel *model; GtkTreeIter iter; gboolean valid; DB( g_print("\n[ui_split_dialog] compute\n") ); data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GTK_WIDGET(widget), GTK_TYPE_WINDOW)), "inst_data"); nbvalid = 0; data->sumsplit = 0.0; data->remsplit = 0.0; model = gtk_tree_view_get_model(GTK_TREE_VIEW(data->LV_split)); i=0; valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter); while (valid) { Split *split; gtk_tree_model_get (model, &iter, 0, &split, -1); data->sumsplit += split->amount; if( hb_amount_round(split->amount, data->cur->frac_digits) != 0.0 ) nbvalid++; /* Make iter point to the next row in the list store */ i++; valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter); } count = i; DB( g_print(" n_count=%d, n_valid=%d\n", count, nbvalid ) ); data->remsplit = data->amount - data->sumsplit; //validation: 2 split min, if 0 split //Accept button is not disabled to enable empty the splits sensitive = FALSE; if( (count == 0) || nbvalid >= 2 ) sensitive = TRUE; gtk_widget_hide(data->IB_inflimit); gtk_widget_hide(data->IB_wrnsum); gtk_widget_hide(data->IB_errtype); if( count >= TXN_MAX_SPLIT ) { gtk_widget_show_all(data->IB_inflimit); //#GTK+710888: hack waiting a GTK fix gtk_widget_queue_resize (data->IB_inflimit); } if( data->mode == SPLIT_MODE_AMOUNT ) { if( hb_amount_round(data->remsplit, data->cur->frac_digits) == 0.0 ) { g_sprintf(buf, "----"); gtk_spin_button_set_value(GTK_SPIN_BUTTON(data->ST_amount), 0); } else { //g_snprintf(buf, G_ASCII_DTOSTR_BUF_SIZE-1, data->cur->format, data->remsplit); hb_strfmon(buf, G_ASCII_DTOSTR_BUF_SIZE-1, data->remsplit, data->cur->key, FALSE); //#1845841 bring back checkpoint with initial amount + init remainder //revert, because block any edition/inherit //sensitive = (count > 1) ? FALSE : sensitive; //but keep prefill remainder gtk_spin_button_set_value(GTK_SPIN_BUTTON(data->ST_amount), data->remsplit); gtk_widget_show_all(data->IB_wrnsum); //#GTK+710888: hack waiting a GTK fix gtk_widget_queue_resize (data->IB_wrnsum); } gtk_label_set_label(GTK_LABEL(data->LB_remain), buf); //g_snprintf(buf, G_ASCII_DTOSTR_BUF_SIZE-1, data->cur->format, data->amount); hb_strfmon(buf, G_ASCII_DTOSTR_BUF_SIZE-1, data->amount, data->cur->key, FALSE); gtk_label_set_label(GTK_LABEL(data->LB_txnamount), buf); } //g_snprintf(buf, G_ASCII_DTOSTR_BUF_SIZE-1, data->cur->format, data->sumsplit); hb_strfmon(buf, G_ASCII_DTOSTR_BUF_SIZE-1, data->sumsplit, data->cur->key, FALSE); gtk_label_set_text(GTK_LABEL(data->LB_sumsplit), buf); //if split sum sign do not match if( hb_amount_type_match(data->sumsplit, data->txntype) == FALSE ) { //#1885413 enable sign invert from split dialog //sensitive = FALSE; gtk_widget_show_all(data->IB_errtype); //#GTK+710888: hack waiting a GTK fix gtk_widget_queue_resize (data->IB_errtype); } gtk_dialog_set_response_sensitive(GTK_DIALOG(data->dialog), GTK_RESPONSE_ACCEPT, sensitive); } static void ui_split_dialog_setup(struct ui_split_dialog_data *data) { guint count; DB( g_print("\n[ui_split_dialog] set\n") ); count = da_splits_length(data->tmp_splits); data->nbsplit = count > 1 ? count-1 : 0; DB( g_print(" n_count = %d\n", count) ); list_split_populate (data->LV_split, data->tmp_splits); data->isedited = FALSE; gtk_spin_button_set_digits (GTK_SPIN_BUTTON(data->ST_amount), data->cur->frac_digits); //5.5 done in popover //ui_cat_comboboxentry_populate(GTK_COMBO_BOX(data->PO_cat), GLOBALS->h_cat); ui_split_dialog_compute(data->dialog, data); ui_split_dialog_update (data->dialog, data); } GtkWidget *ui_split_view_dialog (GtkWidget *parent, Transaction *ope) { GtkWidget *dialog, *content, *table, *scrollwin, *treeview; gint w, h, dw, dh, row; DB( g_print("\n[ui_split_dialog] new view only\n") ); if( ope->splits == NULL ) return NULL; dialog = gtk_dialog_new_with_buttons (_("Transaction splits"), GTK_WINDOW(parent), 0, _("_Close"), GTK_RESPONSE_ACCEPT, NULL); //store our dialog private data DB( g_print(" window=%p\n", dialog) ); g_signal_connect (dialog, "destroy", G_CALLBACK (gtk_widget_destroyed), &dialog); //set a nice dialog size gtk_window_get_size(GTK_WINDOW(parent), &w, &h); dh = (h/PHI); //ratio 3:2 dw = (dh * 3) / 2; DB( g_print(" parent w=%d h=%d => diag w=%d h=%d\n", w, h, dw, dh) ); gtk_window_set_default_size (GTK_WINDOW(dialog), dw, dh); //dialog contents content = gtk_dialog_get_content_area(GTK_DIALOG (dialog)); table = gtk_grid_new (); hb_widget_set_margin(GTK_WIDGET(table), SPACING_SMALL); gtk_grid_set_row_spacing (GTK_GRID (table), SPACING_TINY); gtk_grid_set_column_spacing (GTK_GRID (table), SPACING_TINY); gtk_box_pack_start (GTK_BOX (content), table, TRUE, TRUE, 0); row = 0; scrollwin = make_scrolled_window(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_set_size_request(scrollwin, HB_MINWIDTH_LIST, HB_MINHEIGHT_LIST); gtk_widget_set_hexpand (scrollwin, TRUE); gtk_widget_set_vexpand (scrollwin, TRUE); treeview = list_split_new(ope->kcur); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrollwin), treeview); gtk_grid_attach (GTK_GRID (table), scrollwin, 0, row, 4, 1); //setup list_split_populate(treeview, ope->splits); gtk_widget_show_all (dialog); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_window_destroy (GTK_WINDOW(dialog)); return NULL; } GtkWidget *ui_split_dialog (GtkWidget *parent, GPtrArray **src_splits, gint txntype, guint32 date, gdouble amount, guint32 kcur, void (update_callbackFunction(GtkWidget*, gdouble))) { struct ui_split_dialog_data *data; GtkWidget *dialog, *content, *table, *box, *scrollwin, *bar; GtkWidget *label, *widget; gint row; DB( g_print("\n[ui_split_dialog] new\n") ); data = g_malloc0(sizeof(struct ui_split_dialog_data)); if(!data) return NULL; dialog = gtk_dialog_new_with_buttons (_("Transaction splits"), GTK_WINDOW(parent), 0, _("_Cancel"), GTK_RESPONSE_CANCEL, NULL); //store our dialog private data g_object_set_data(G_OBJECT(dialog), "inst_data", (gpointer)data); DB( g_print(" window=%p, inst_data=%p\n", dialog, data) ); g_signal_connect (dialog, "destroy", G_CALLBACK (gtk_widget_destroyed), &dialog); data->dialog = dialog; gtk_dialog_add_button(GTK_DIALOG(dialog), _("_OK"), GTK_RESPONSE_ACCEPT); data->date = date; data->cur = da_cur_get (kcur); DB( g_print(" kcur: %d %d %s\n", data->cur->key, data->cur->frac_digits, data->cur->format) ); //todo: init should move //clone splits or create new data->src_splits = *src_splits; data->txntype = txntype; data->mode = (hb_amount_round(amount, data->cur->frac_digits) != 0.0) ? SPLIT_MODE_AMOUNT : SPLIT_MODE_EMPTY; data->amount = amount; data->sumsplit = amount; DB( g_print(" amount : %f\n", data->amount) ); DB( g_print(" txntype: %s\n", data->txntype == TXN_TYPE_EXPENSE ? "expense" : "income" )); DB( g_print(" mode : %s\n", data->mode == SPLIT_MODE_AMOUNT ? "amount" : "empty" )); if( *src_splits != NULL ) data->tmp_splits = da_splits_clone(*src_splits); else data->tmp_splits = da_split_new(); //dialog contents content = gtk_dialog_get_content_area(GTK_DIALOG (dialog)); table = gtk_grid_new (); hb_widget_set_margin(GTK_WIDGET(table), SPACING_LARGE); gtk_grid_set_row_spacing (GTK_GRID (table), SPACING_TINY); gtk_grid_set_column_spacing (GTK_GRID (table), SPACING_TINY); gtk_box_pack_start (GTK_BOX (content), table, TRUE, TRUE, 0); row = 0; scrollwin = make_scrolled_window(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_set_size_request(scrollwin, HB_MINWIDTH_LIST, HB_MINHEIGHT_LIST); gtk_widget_set_hexpand (scrollwin, TRUE); gtk_widget_set_vexpand (scrollwin, TRUE); data->LV_split = list_split_new(kcur); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrollwin), data->LV_split); gtk_grid_attach (GTK_GRID (table), scrollwin, 0, row, 4, 1); box = gtk_box_new (GTK_ORIENTATION_VERTICAL, SPACING_TINY); gtk_widget_set_valign (box, GTK_ALIGN_CENTER); gtk_grid_attach (GTK_GRID (table), box, 4, row, 1, 1); widget = make_image_button(ICONNAME_LIST_DELETE_ALL, _("Delete all")); data->BT_remall = widget; gtk_box_pack_end (GTK_BOX (box), widget, FALSE, FALSE, 0); widget = make_image_button(ICONNAME_LIST_DELETE, _("Delete")); data->BT_rem = widget; gtk_box_pack_end (GTK_BOX(box), widget, FALSE, FALSE, 0); widget = make_image_button(ICONNAME_HB_OPE_EDIT, _("Edit")); data->BT_edit = widget; gtk_box_pack_end (GTK_BOX(box), widget, FALSE, FALSE, 0); row++; label = gtk_label_new(_("Category")); gimp_label_set_attributes (GTK_LABEL (label), PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, -1); gtk_grid_attach (GTK_GRID (table), label, 0, row, 1, 1); label = gtk_label_new(_("Memo")); gimp_label_set_attributes (GTK_LABEL (label), PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, -1); gtk_grid_attach (GTK_GRID (table), label, 1, row, 1, 1); //5.7.1 gchar *typelabel = _("Amount"); if( txntype == TXN_TYPE_EXPENSE ) typelabel = _("Expense"); else if( txntype == TXN_TYPE_INCOME ) typelabel = _("Income"); label = gtk_label_new(typelabel); gimp_label_set_attributes (GTK_LABEL (label), PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, -1); gtk_grid_attach (GTK_GRID (table), label, 2, row, 1, 1); row++; //widget = ui_cat_comboboxentry_new(NULL); widget = ui_cat_entry_popover_new(NULL); data->PO_cat = widget; gtk_widget_set_hexpand(widget, TRUE); gtk_grid_attach (GTK_GRID (table), widget, 0, row, 1, 1); //1977686 //widget = make_string(NULL); widget = make_memo_entry(NULL); data->ST_memo= widget; gtk_widget_set_hexpand(widget, TRUE); gtk_grid_attach (GTK_GRID (table), widget, 1, row, 1, 1); widget = make_amount(NULL); data->ST_amount = widget; gtk_grid_attach (GTK_GRID (table), widget, 2, row, 1, 1); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, SPACING_TINY); gtk_grid_attach (GTK_GRID (table), box, 3, row, 1, 1); widget = gtk_image_new_from_icon_name (ICONNAME_INFO, GTK_ICON_SIZE_BUTTON); gtk_widget_set_tooltip_text(widget, _("Prefix with -/+ to force the sign")); gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0); widget = gtk_image_new_from_icon_name (ICONNAME_HB_OPE_EDIT, GTK_ICON_SIZE_BUTTON); data->IM_edit = widget; gtk_box_pack_start (GTK_BOX(box), widget, TRUE, TRUE, 0); widget = make_image_button(ICONNAME_LIST_ADD, _("Add")); data->BT_add = widget; gtk_box_pack_start (GTK_BOX(box), widget, FALSE, FALSE, 0); widget = make_image_button(ICONNAME_EMBLEM_OK, _("Apply")); data->BT_apply = widget; gtk_box_pack_start (GTK_BOX(box), widget, FALSE, FALSE, 0); widget = make_image_button(ICONNAME_WINDOW_CLOSE, _("Cancel")); data->BT_cancel = widget; gtk_box_pack_start (GTK_BOX(box), widget, FALSE, FALSE, 0); if( data->mode == SPLIT_MODE_AMOUNT ) { row++; label = gtk_label_new(_("Transaction amount:")); gtk_widget_set_halign (label, GTK_ALIGN_END); gtk_grid_attach (GTK_GRID (table), label, 1, row, 1, 1); widget = gtk_label_new(NULL); gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); data->LB_txnamount = widget; gtk_grid_attach (GTK_GRID (table), widget, 2, row, 1, 1); row++; label = gtk_label_new(_("Unassigned:")); gtk_widget_set_halign (label, GTK_ALIGN_END); gtk_grid_attach (GTK_GRID (table), label, 1, row, 1, 1); widget = gtk_label_new(NULL); gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); data->LB_remain = widget; gtk_grid_attach (GTK_GRID (table), widget, 2, row, 1, 1); row++; widget = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); gtk_grid_attach (GTK_GRID (table), widget, 1, row, 2, 1); } row++; label = gtk_label_new(_("Sum of splits:")); gtk_widget_set_halign (label, GTK_ALIGN_END); gtk_grid_attach (GTK_GRID (table), label, 1, row, 1, 1); widget = gtk_label_new(NULL); gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); data->LB_sumsplit = widget; gtk_grid_attach (GTK_GRID (table), widget, 2, row, 1, 1); row++; bar = gtk_info_bar_new (); data->IB_inflimit = bar; gtk_info_bar_set_message_type (GTK_INFO_BAR (bar), GTK_MESSAGE_INFO); label = gtk_label_new (_("Number of splits limit is reached")); gtk_box_pack_start (GTK_BOX (gtk_info_bar_get_content_area (GTK_INFO_BAR (bar))), label, TRUE, TRUE, 0); gtk_grid_attach (GTK_GRID (table), bar, 0, row, 4, 1); row++; bar = gtk_info_bar_new (); data->IB_errtype = bar; gtk_info_bar_set_message_type (GTK_INFO_BAR (bar), GTK_MESSAGE_WARNING); label = gtk_label_new (_("Warning: sum of splits and transaction type don't match")); gtk_box_pack_start (GTK_BOX (gtk_info_bar_get_content_area (GTK_INFO_BAR (bar))), label, TRUE, TRUE, 0); gtk_grid_attach (GTK_GRID (table), bar, 0, row, 4, 1); row++; bar = gtk_info_bar_new (); data->IB_wrnsum = bar; gtk_info_bar_set_message_type (GTK_INFO_BAR (bar), GTK_MESSAGE_ERROR); label = gtk_label_new (_("Warning: sum of splits and transaction amount don't match")); gtk_box_pack_start (GTK_BOX (gtk_info_bar_get_content_area (GTK_INFO_BAR (bar))), label, TRUE, TRUE, 0); gtk_grid_attach (GTK_GRID (table), bar, 0, row, 4, 1); //connect all our signals g_signal_connect (gtk_tree_view_get_selection(GTK_TREE_VIEW(data->LV_split)), "changed", G_CALLBACK (ui_split_selection), data); g_signal_connect (GTK_TREE_VIEW(data->LV_split), "row-activated", G_CALLBACK (ui_split_rowactivated), data); g_signal_connect (data->BT_edit , "clicked", G_CALLBACK (ui_split_dialog_edit_start), NULL); g_signal_connect (data->BT_rem , "clicked", G_CALLBACK (ui_split_dialog_delete_cb), NULL); g_signal_connect (data->BT_remall, "clicked", G_CALLBACK (ui_split_dialog_deleteall_cb), NULL); g_signal_connect (data->ST_memo , "insert-text", G_CALLBACK(ui_split_dialog_filter_text_handler), data); g_signal_connect (data->ST_amount, "focus-out-event", G_CALLBACK (ui_split_dialog_cb_amount_focus_out), data); g_signal_connect (data->ST_amount, "activate", G_CALLBACK (ui_split_dialog_cb_amount_activate), NULL); g_signal_connect (data->BT_add , "clicked", G_CALLBACK (ui_split_dialog_add_cb), NULL); g_signal_connect (data->BT_apply , "clicked", G_CALLBACK (ui_split_dialog_apply_cb), NULL); g_signal_connect (data->BT_cancel, "clicked", G_CALLBACK (ui_split_dialog_cancel_cb), NULL); //gtk_window_set_default_size(GTK_WINDOW(dialog), 480, -1); gtk_widget_show_all (dialog); //setup, init and show dialog ui_split_dialog_setup(data); //wait for the user gint result = gtk_dialog_run (GTK_DIALOG (dialog)); switch (result) { // sum split and alter txn amount case GTK_RESPONSE_ACCEPT: if( da_splits_length(data->tmp_splits) ) { ui_split_dialog_cb_eval_order(data); // here we swap src_splits <> tmp_splits *src_splits = data->tmp_splits; data->tmp_splits = data->src_splits; update_callbackFunction(parent, data->sumsplit); } else { //delete split and revert back original amount da_split_destroy(*src_splits); *src_splits = NULL; update_callbackFunction(parent, data->amount); } break; /*case GTK_RESPONSE_SPLIT_REM: da_split_destroy(*src_splits); *src_splits = NULL; update_callbackFunction(parent, data->sumsplit); break; */ default: //do_nothing_since_dialog_was_cancelled (); break; } // debug /*#if MYDEBUG == 1 { guint i; for(i=0;iope_splits[i]; if(data->ope_splits[i] == NULL) break; g_print(" split %d : %d, %.2f, %s\n", i, split->kcat, split->amount, split->memo); } } #endif*/ // cleanup and destroy //GLOBALS->changes_count += data->change; gtk_window_destroy (GTK_WINDOW(dialog)); da_split_destroy (data->tmp_splits); g_free(data); return NULL; }