GnuCash c935c2f+
Loading...
Searching...
No Matches
assistant-csv-trans-import.cpp
Go to the documentation of this file.
1/*******************************************************************\
2 * assistant-csv-trans-import.c -- An assistant for importing *
3 * Transactions from a file. *
4 * *
5 * Copyright (C) 2012 Robert Fewell *
6 * Copyright (c) 2007 Benny Sperisen <lasindi@gmail.com> *
7 * *
8 * This program is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU General Public License as *
10 * published by the Free Software Foundation; either version 2 of *
11 * the License, or (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License*
19 * along with this program; if not, contact: *
20 * *
21 * Free Software Foundation Voice: +1-617-542-5942 *
22 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
23 * Boston, MA 02110-1301, USA gnu@gnu.org *
24\********************************************************************/
31#include <guid.hpp>
32
33#include <config.h>
34
35#include <gtk/gtk.h>
36#include <glib/gi18n.h>
37#include <stdexcept>
38#include <stdlib.h>
39#include <cstdint>
40
41#include "gnc-path.h"
42#include "gnc-ui.h"
43#include "gnc-uri-utils.h"
44#include "gnc-ui-util.h"
45#include "dialog-utils.h"
46
47#include "gnc-component-manager.h"
48
49#include "gnc-state.h"
50
52
54#include "import-main-matcher.h"
55#include "import-backend.h"
56#include "gnc-account-sel.h"
57
58#include "gnc-csv-gnumeric-popup.h"
59#include "go-charmap-sel.h"
60
62#include "gnc-import-tx.hpp"
63#include "gnc-tokenizer-fw.hpp"
64#include "gnc-tokenizer-csv.hpp"
65
66#include <algorithm>
67#include <exception>
68#include <iostream>
69#include <memory>
70#include <numeric>
71#include <string>
72#include <tuple>
73
74#include <gnc-locale-utils.hpp>
75#include <boost/locale.hpp>
76
77namespace bl = boost::locale;
78
79#define MIN_COL_WIDTH 70
80#define GNC_PREFS_GROUP "dialogs.import.csv"
81#define ASSISTANT_CSV_IMPORT_TRANS_CM_CLASS "assistant-csv-trans-import"
82
83/* This static indicates the debugging module that this .o belongs to. */
84static QofLogModule log_module = GNC_MOD_ASSISTANT;
85
86enum GncImportColumn {
87 MAPPING_STRING,
88 MAPPING_FULLPATH,
89 MAPPING_ACCOUNT
90};
91
92/* A note on memory management
93 *
94 * This source file is mixture of several completely different memory models
95 * - it defines a c++ class which is managed the c++ way
96 * - the c++ class encapsulates a gtk based assistant which is managed according to
97 * the GObject/Gtk memory model.
98 * - gnucash manages gui objects via its "Component Manager". Dialogs and windows are
99 * registered with this component manager when they are opened and the component
100 * manager handles the lifecycle of toplevel widgets. When a dialog is closed
101 * the component manager invokes a close handler which is responsible for cleaning up.
102 *
103 * Care must be taken in places where these models intersect. Here is how it is
104 * handled for this source file:
105 * - First in the context of the import assistant the gnucash component manager is
106 * merely a wrapper to keep track of which gui objects exist so they can be cleanly
107 * destroyed. But the component manager itself doesn't do any memory management
108 * on the objects it tracks. Instead it delegates this back to the objects themselves
109 * via callbacks which the objects have to supply. It merely helps in the coordination.
110 * So we can further ignore it in the memory management discussion.
111 *
112 * - Next there is only one entry point to start this assistant: gnc_file_csv_trans_import
113 * - The full assistant functionality is wrapped in a C++ class using RAII.
114 * - The entry point function will create one instance of this class using the c++
115 * "new" method. This in turn will create several objects like a (GObject managed)
116 * GtkAssistant, and a few C++ member objects.
117 * - The entry point function will also register the created object in the
118 * component manager. This works because the (plain C) component manager just stores
119 * the (C++) pointer to the object, it doesn't act on it directly in any way.
120 * - When the assistant is closed the component manager will invoke a
121 * close handler on the class pointer. We supply this close handler ourselves
122 * in csv_tximp_close_handler. Aside from some component management administration
123 * the essential action of this function is to (c++) "delete"
124 * the class object again. As the C++ class implements RAII this destruction will take care
125 * of freeing all the member objects it manages.
126 * - Note the component manager's only benefit in this context is that at gnucash shutdown
127 * all still open dialogs can be closed cleanly. Whether this benefit is enough to
128 * justify the added complexity is debatable. However currently the calling code is not
129 * c++ yet so we can't use RAII in the calling object to better handle this right now.
130 *
131 * - Let's zoom in on the c++ member objects and in particular the GtkAssistant and related objects.
132 * These are created the gtk way in the c++ class constructor. That means the main GtkAssistant widget
133 * will be responsible for the lifecycle of its child widgets.
134 * - Thanks to the RAII implementation the destruction of this widget is commanded in the c++ class
135 * destructor. This gets activated when the user clicks the assistant's close button via the component
136 * manager callback mechanism as mentioned above.
137 *
138 * - There is one case that needs some additional attention. At some point the csv importer assistant hands
139 * over control to a generic import matcher (created via gnc_gen_trans_assist_new). This generic import
140 * matcher unfortunately destroys itself when run. However it is not run in all our possible user scenarios.
141 * This means we sometimes have to free it and sometimes we don't. This could have been
142 * avoided if we didn't have to track the object across several gtk callback functions and
143 * instead just create it only right before using it. To handle this we start with RAII:
144 * the c++ class object assumes ownership of the generic import matcher object and the class destructor will
145 * attempt to free it. This is safe both if the object is effectively allocated or when it's nullified.
146 * Then to handle the case were the generic import matcher will free the matcher object, the c++ class object
147 * will release ownership of the generic pointer object right before starting the generic import matcher.
148 *
149 * TODO this is pretty confusing and should be cleaned up when we rewrite the generic importer.
150 */
151
153{
154public:
157
158 /* Delete copy and move constructor/assignments
159 * We don't want gui elements to be moved around or copied at all */
160 CsvImpTransAssist(const CsvImpTransAssist&) = delete; // copy constructor
161 CsvImpTransAssist& operator=(const CsvImpTransAssist&) = delete; // copy assignment
162 CsvImpTransAssist(CsvImpTransAssist&&) = delete; // move constructor
163 CsvImpTransAssist& operator=(CsvImpTransAssist&&) = delete; // move assignment
164
165 void assist_prepare_cb (GtkWidget *page);
166 void assist_file_page_prepare ();
167 void assist_preview_page_prepare ();
168 void assist_account_match_page_prepare ();
169 void assist_doc_page_prepare ();
170 void assist_match_page_prepare ();
171 void assist_summary_page_prepare ();
172 void assist_finish ();
173 void assist_compmgr_close ();
174
175 void file_activated_cb ();
176 void file_selection_changed_cb ();
177
178 void preview_settings_delete ();
179 void preview_settings_save ();
180 void preview_settings_name (GtkEntry* entry);
181 void preview_settings_load ();
182 void preview_update_skipped_rows ();
183 void preview_multi_split (bool multi);
184 void preview_update_separators (GtkWidget* widget);
186 void preview_update_account ();
187 void preview_update_encoding (const char* encoding);
188 void preview_update_date_format ();
189 void preview_update_currency_format ();
190 void preview_update_col_type (GtkComboBox* cbox);
191 void preview_update_fw_columns (GtkTreeView* treeview, GdkEventButton* event);
192
193 void preview_populate_settings_combo();
194 void preview_handle_save_del_sensitivity (GtkComboBox* combo);
195 void preview_split_column (int col, int offset);
196 void preview_refresh_table ();
197 void preview_refresh ();
198 void preview_validate_settings ();
199
200 void acct_match_via_button ();
201 bool acct_match_via_view_dblclick (GdkEventButton *event);
202 void acct_match_select(GtkTreeModel *model, GtkTreeIter* iter);
203 void acct_match_set_accounts ();
204
205 friend gboolean
206 fixed_context_menu_handler (GnumericPopupMenuElement const *element,
207 gpointer userdata);
208private:
209 /* helper functions to manage the context menu for fixed with columns */
210 uint32_t get_new_col_rel_pos (GtkTreeViewColumn *tcol, int dx);
211 void fixed_context_menu (GdkEventButton *event, int col, int dx);
212 /* helper function to calculate row colors for the preview table (to visualize status) */
213 void preview_row_fill_state_cells (GtkListStore *store, GtkTreeIter *iter,
214 ErrMap& err_msg, bool skip);
215 /* helper function to create preview header cell combo boxes listing available column types */
216 GtkWidget* preview_cbox_factory (GtkTreeModel* model, uint32_t colnum);
217 /* helper function to set rendering parameters for preview data columns */
218 void preview_style_column (uint32_t col_num, GtkTreeModel* model);
219 /* helper function to check for a valid filename as opposed to a directory */
220 bool check_for_valid_filename ();
221
222 GtkAssistant *csv_imp_asst;
223
224 GtkWidget *file_page;
225 GtkWidget *file_chooser;
226 std::string m_fc_file_name;
227 std::string m_final_file_name;
229 GtkWidget *preview_page;
230 GtkComboBox *settings_combo;
231 GtkWidget *save_button;
232 GtkWidget *del_button;
233 GtkWidget *acct_selector;
234 GtkWidget *combo_hbox;
235 GtkSpinButton *start_row_spin;
236 GtkSpinButton *end_row_spin;
237 GtkWidget *skip_alt_rows_button;
238 GtkWidget *skip_errors_button;
239 GtkWidget *csv_button;
240 GtkWidget *fixed_button;
241 GtkWidget *multi_split_cbutton;
242 GOCharmapSel *encselector;
243 GtkWidget *separator_table;
244 GtkCheckButton *sep_button[SEP_NUM_OF_TYPES];
245 GtkCheckButton *escape_cbutton;
246 GtkWidget *fw_instructions_hbox;
247 GtkCheckButton *custom_cbutton;
248 GtkEntry *custom_entry;
249 GtkComboBoxText *date_format_combo;
250 GtkComboBoxText *currency_format_combo;
251 GtkTreeView *treeview;
252 GtkLabel *instructions_label;
253 GtkImage *instructions_image;
254 bool encoding_selected_called;
256 int fixed_context_col;
257 int fixed_context_offset;
260 GtkWidget *account_match_page;
261 GtkWidget *account_match_view;
262 GtkWidget *account_match_label;
263 GtkWidget *account_match_btn;
265 GtkWidget *doc_page;
267 GtkWidget *match_page;
268 GtkWidget *match_label;
269 GNCImportMainMatcher *gnc_csv_importer_gui;
270 GtkWidget *help_button;
271 GtkWidget *cancel_button;
273 GtkWidget *summary_page;
274 GtkWidget *summary_label;
276 bool new_book;
277 std::unique_ptr<GncTxImport> tx_imp;
279 bool m_req_mapped_accts;
280};
281
282
283/*******************************************************
284 * Assistant call back functions
285 *******************************************************/
286
287extern "C"
288{
289void csv_tximp_assist_prepare_cb (GtkAssistant *assistant, GtkWidget *page, CsvImpTransAssist* info);
290void csv_tximp_assist_close_cb (GtkAssistant *gtkassistant, CsvImpTransAssist* info);
291void csv_tximp_assist_finish_cb (GtkAssistant *gtkassistant, CsvImpTransAssist* info);
292void csv_tximp_file_activated_cb (GtkFileChooser *chooser, CsvImpTransAssist *info);
293void csv_tximp_file_selection_changed_cb (GtkFileChooser *chooser, CsvImpTransAssist *info);
294void csv_tximp_preview_del_settings_cb (GtkWidget *button, CsvImpTransAssist *info);
295void csv_tximp_preview_save_settings_cb (GtkWidget *button, CsvImpTransAssist *info);
296void csv_tximp_preview_settings_sel_changed_cb (GtkComboBox *combo, CsvImpTransAssist *info);
297void csv_tximp_preview_settings_text_inserted_cb (GtkEditable *entry, gchar *new_text,
298 gint new_text_length, gint *position, CsvImpTransAssist *info);
299void csv_tximp_preview_settings_text_changed_cb (GtkEntry *entry, CsvImpTransAssist *info);
300void csv_tximp_preview_srow_cb (GtkSpinButton *spin, CsvImpTransAssist *info);
301void csv_tximp_preview_erow_cb (GtkSpinButton *spin, CsvImpTransAssist *info);
302void csv_tximp_preview_skiprows_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info);
303void csv_tximp_preview_skiperrors_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info);
304void csv_tximp_preview_multisplit_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info);
305void csv_tximp_preview_sep_button_cb (GtkWidget* widget, CsvImpTransAssist* info);
306void csv_tximp_preview_sep_fixed_sel_cb (GtkToggleButton* csv_button, CsvImpTransAssist* info);
307void csv_tximp_preview_acct_sel_cb (GtkWidget* widget, CsvImpTransAssist* info);
308void csv_tximp_preview_enc_sel_cb (GOCharmapSel* selector, const char* encoding,
309 CsvImpTransAssist* info);
310void csv_tximp_acct_match_button_clicked_cb (GtkWidget *widget, CsvImpTransAssist* info);
311bool csv_tximp_acct_match_view_clicked_cb (GtkWidget *widget, GdkEventButton *event, CsvImpTransAssist* info);
312}
313
314void
315csv_tximp_assist_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
316 CsvImpTransAssist* info)
317{
318 info->assist_prepare_cb(page);
319}
320
321void
322csv_tximp_assist_close_cb (GtkAssistant *assistant, CsvImpTransAssist* info)
323{
324 gnc_close_gui_component_by_data (ASSISTANT_CSV_IMPORT_TRANS_CM_CLASS, info);
325}
326
327void
328csv_tximp_assist_finish_cb (GtkAssistant *assistant, CsvImpTransAssist* info)
329{
330 info->assist_finish ();
331}
332
333void csv_tximp_file_activated_cb (GtkFileChooser *chooser, CsvImpTransAssist *info)
334{
335 info->file_activated_cb();
336}
337
338void csv_tximp_file_selection_changed_cb (GtkFileChooser *chooser, CsvImpTransAssist *info)
339{
340 info->file_selection_changed_cb();
341}
342
343void csv_tximp_preview_del_settings_cb (GtkWidget *button, CsvImpTransAssist *info)
344{
345 info->preview_settings_delete();
346}
347
348void csv_tximp_preview_save_settings_cb (GtkWidget *button, CsvImpTransAssist *info)
349{
350 info->preview_settings_save();
351}
352
353void csv_tximp_preview_settings_sel_changed_cb (GtkComboBox *combo, CsvImpTransAssist *info)
354{
355 info->preview_settings_load();
356}
357
358void
359csv_tximp_preview_settings_text_inserted_cb (GtkEditable *entry, gchar *new_text,
360 gint new_text_length, gint *position, CsvImpTransAssist *info)
361{
362 if (!new_text)
363 return;
364
365 /* Prevent entering [], which are invalid characters in key files */
366 auto base_txt = std::string (new_text);
367 auto mod_txt = base_txt;
368 std::replace (mod_txt.begin(), mod_txt.end(), '[', '(');
369 std::replace (mod_txt.begin(), mod_txt.end(), ']', ')');
370 if (base_txt == mod_txt)
371 return;
372 g_signal_handlers_block_by_func (entry, (gpointer) csv_tximp_preview_settings_text_inserted_cb, info);
373 gtk_editable_insert_text (entry, mod_txt.c_str(), mod_txt.size() , position);
374 g_signal_handlers_unblock_by_func (entry, (gpointer) csv_tximp_preview_settings_text_inserted_cb, info);
375
376 g_signal_stop_emission_by_name (entry, "insert_text");
377}
378
379void
380csv_tximp_preview_settings_text_changed_cb (GtkEntry *entry, CsvImpTransAssist *info)
381{
382 info->preview_settings_name(entry);
383}
384
385void csv_tximp_preview_srow_cb (GtkSpinButton *spin, CsvImpTransAssist *info)
386{
387 info->preview_update_skipped_rows();
388}
389
390void csv_tximp_preview_erow_cb (GtkSpinButton *spin, CsvImpTransAssist *info)
391{
392 info->preview_update_skipped_rows();
393}
394
395void csv_tximp_preview_skiprows_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info)
396{
397 info->preview_update_skipped_rows();
398}
399
400void csv_tximp_preview_skiperrors_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info)
401{
402 info->preview_update_skipped_rows();
403}
404
405void csv_tximp_preview_multisplit_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info)
406{
407 info->preview_multi_split (gtk_toggle_button_get_active (checkbox));
408}
409
410void csv_tximp_preview_sep_button_cb (GtkWidget* widget, CsvImpTransAssist* info)
411{
412 info->preview_update_separators(widget);
413}
414
415void csv_tximp_preview_sep_fixed_sel_cb (GtkToggleButton* csv_button, CsvImpTransAssist* info)
416{
417 info->preview_update_file_format();
418}
419
420void csv_tximp_preview_acct_sel_cb (GtkWidget* widget, CsvImpTransAssist* info)
421{
422 info->preview_update_account();
423}
424
425void csv_tximp_preview_enc_sel_cb (GOCharmapSel* selector, const char* encoding,
426 CsvImpTransAssist* info)
427{
428 info->preview_update_encoding(encoding);
429}
430
431static void csv_tximp_preview_date_fmt_sel_cb (GtkComboBox* format_selector, CsvImpTransAssist* info)
432{
433 info->preview_update_date_format();
434}
435
436static void csv_tximp_preview_currency_fmt_sel_cb (GtkComboBox* format_selector, CsvImpTransAssist* info)
437{
438 info->preview_update_currency_format();
439}
440
441static void csv_tximp_preview_col_type_changed_cb (GtkComboBox* cbox, CsvImpTransAssist* info)
442{
443 info->preview_update_col_type (cbox);
444}
445
446static bool
447csv_tximp_preview_treeview_clicked_cb (GtkTreeView* treeview, GdkEventButton* event,
448 CsvImpTransAssist* info)
449{
450 info->preview_update_fw_columns(treeview, event);
451 return false;
452}
453
454
455void csv_tximp_acct_match_button_clicked_cb (GtkWidget *widget, CsvImpTransAssist* info)
456{
457 info->acct_match_via_button();
458}
459
460bool csv_tximp_acct_match_view_clicked_cb (GtkWidget *widget, GdkEventButton *event, CsvImpTransAssist* info)
461{
462 return info->acct_match_via_view_dblclick(event);
463}
464
465
466/*******************************************************
467 * Assistant Constructor
468 *******************************************************/
469CsvImpTransAssist::CsvImpTransAssist ()
470{
471 auto builder = gtk_builder_new();
472 gnc_builder_add_from_file (builder , "assistant-csv-trans-import.glade", "start_row_adj");
473 gnc_builder_add_from_file (builder , "assistant-csv-trans-import.glade", "end_row_adj");
474 gnc_builder_add_from_file (builder , "assistant-csv-trans-import.glade", "account_match_store");
475 gnc_builder_add_from_file (builder , "assistant-csv-trans-import.glade", "csv_transaction_assistant");
476 csv_imp_asst = GTK_ASSISTANT(gtk_builder_get_object (builder, "csv_transaction_assistant"));
477
478 // Set the name for this assistant so it can be easily manipulated with css
479 gtk_widget_set_name (GTK_WIDGET(csv_imp_asst), "gnc-id-assistant-csv-transaction-import");
480 gnc_widget_style_context_add_class (GTK_WIDGET(csv_imp_asst), "gnc-class-imports");
481
482 /* Enable buttons on all page. */
483 gtk_assistant_set_page_complete (csv_imp_asst,
484 GTK_WIDGET(gtk_builder_get_object (builder, "start_page")),
485 true);
486 gtk_assistant_set_page_complete (csv_imp_asst,
487 GTK_WIDGET(gtk_builder_get_object (builder, "file_page")),
488 false);
489 gtk_assistant_set_page_complete (csv_imp_asst,
490 GTK_WIDGET(gtk_builder_get_object (builder, "preview_page")),
491 false);
492 gtk_assistant_set_page_complete (csv_imp_asst,
493 GTK_WIDGET(gtk_builder_get_object (builder, "account_match_page")),
494 false);
495 gtk_assistant_set_page_complete (csv_imp_asst,
496 GTK_WIDGET(gtk_builder_get_object (builder, "doc_page")),
497 true);
498 gtk_assistant_set_page_complete (csv_imp_asst,
499 GTK_WIDGET(gtk_builder_get_object (builder, "match_page")),
500 true);
501 gtk_assistant_set_page_complete (csv_imp_asst,
502 GTK_WIDGET(gtk_builder_get_object (builder, "summary_page")),
503 true);
504
505 /* File chooser Page */
506 file_page = GTK_WIDGET(gtk_builder_get_object (builder, "file_page"));
507 file_chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN);
508 g_signal_connect (G_OBJECT(file_chooser), "selection-changed",
509 G_CALLBACK(csv_tximp_file_selection_changed_cb), this);
510 g_signal_connect (G_OBJECT(file_chooser), "file-activated",
511 G_CALLBACK(csv_tximp_file_activated_cb), this);
512
513 auto box = GTK_WIDGET(gtk_builder_get_object (builder, "file_page"));
514 gtk_box_pack_start (GTK_BOX(box), file_chooser, TRUE, TRUE, 6);
515 gtk_widget_show (file_chooser);
516
517 /* Preview Settings Page */
518 {
519 preview_page = GTK_WIDGET(gtk_builder_get_object (builder, "preview_page"));
520
521 // Add Settings combo
522 auto settings_store = gtk_list_store_new (2, G_TYPE_POINTER, G_TYPE_STRING);
523 settings_combo = GTK_COMBO_BOX(gtk_combo_box_new_with_model_and_entry (GTK_TREE_MODEL(settings_store)));
524 g_object_unref (settings_store);
525 gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX(settings_combo), SET_NAME);
526 gtk_combo_box_set_active (GTK_COMBO_BOX(settings_combo), 0);
527
528 combo_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "combo_hbox"));
529 gtk_box_pack_start (GTK_BOX(combo_hbox), GTK_WIDGET(settings_combo), true, true, 6);
530 gtk_widget_show (GTK_WIDGET(settings_combo));
531
532 g_signal_connect (G_OBJECT(settings_combo), "changed",
533 G_CALLBACK(csv_tximp_preview_settings_sel_changed_cb), this);
534
535 // Additionally connect to the changed signal of the embedded GtkEntry
536 auto emb_entry = gtk_bin_get_child (GTK_BIN (settings_combo));
537 g_signal_connect (G_OBJECT(emb_entry), "changed",
538 G_CALLBACK(csv_tximp_preview_settings_text_changed_cb), this);
539 g_signal_connect (G_OBJECT(emb_entry), "insert-text",
540 G_CALLBACK(csv_tximp_preview_settings_text_inserted_cb), this);
541
542 // Add Save Settings button
543 save_button = GTK_WIDGET(gtk_builder_get_object (builder, "save_settings"));
544
545 // Add Delete Settings button
546 del_button = GTK_WIDGET(gtk_builder_get_object (builder, "delete_settings"));
547
548 /* The table containing the separator configuration widgets */
549 start_row_spin = GTK_SPIN_BUTTON(gtk_builder_get_object (builder, "start_row"));
550 end_row_spin = GTK_SPIN_BUTTON(gtk_builder_get_object (builder, "end_row"));
551 skip_alt_rows_button = GTK_WIDGET(gtk_builder_get_object (builder, "skip_rows"));
552 skip_errors_button = GTK_WIDGET(gtk_builder_get_object (builder, "skip_errors_button"));
553 multi_split_cbutton = GTK_WIDGET(gtk_builder_get_object (builder, "multi_split_button"));
554 separator_table = GTK_WIDGET(gtk_builder_get_object (builder, "separator_table"));
555 fw_instructions_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "fw_instructions_hbox"));
556
557 /* Load the separator buttons from the glade builder file into the
558 * sep_buttons array. */
559 const char* sep_button_names[] = {
560 "space_cbutton",
561 "tab_cbutton",
562 "comma_cbutton",
563 "colon_cbutton",
564 "semicolon_cbutton",
565 "hyphen_cbutton"
566 };
567 for (int i = 0; i < SEP_NUM_OF_TYPES; i++)
568 sep_button[i]
569 = GTK_CHECK_BUTTON(gtk_builder_get_object (builder, sep_button_names[i]));
570
571 /* Load and connect the custom separator checkbutton in the same way
572 * as the other separator buttons. */
573 custom_cbutton
574 = GTK_CHECK_BUTTON(gtk_builder_get_object (builder, "custom_cbutton"));
575
576 /* Load the entry for the custom separator entry. Connect it to the
577 * sep_button_clicked event handler as well. */
578 custom_entry = GTK_ENTRY(gtk_builder_get_object (builder, "custom_entry"));
579
580 /* Load and connect the escape checkbutton in the same way
581 * as the other separator buttons. */
582 escape_cbutton
583 = GTK_CHECK_BUTTON(gtk_builder_get_object (builder, "escape_cbutton"));
584
585 /* Add account selection widget */
586 acct_selector = gnc_account_sel_new();
587 auto account_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "account_hbox"));
588 gtk_box_pack_start (GTK_BOX(account_hbox), acct_selector, TRUE, TRUE, 6);
589 gtk_widget_show (acct_selector);
590
591 g_signal_connect(G_OBJECT(acct_selector), "account_sel_changed",
592 G_CALLBACK(csv_tximp_preview_acct_sel_cb), this);
593
594
595 /* Create the encoding selector widget and add it to the assistant */
596 encselector = GO_CHARMAP_SEL(go_charmap_sel_new(GO_CHARMAP_SEL_TO_UTF8));
597 /* Connect the selector to the encoding_selected event handler. */
598 g_signal_connect (G_OBJECT(encselector), "charmap_changed",
599 G_CALLBACK(csv_tximp_preview_enc_sel_cb), this);
600
601 auto encoding_container = GTK_CONTAINER(gtk_builder_get_object (builder, "encoding_container"));
602 gtk_container_add (encoding_container, GTK_WIDGET(encselector));
603 gtk_widget_set_hexpand (GTK_WIDGET(encselector), true);
604 gtk_widget_show_all (GTK_WIDGET(encoding_container));
605
606 /* The instructions label and image */
607 instructions_label = GTK_LABEL(gtk_builder_get_object (builder, "instructions_label"));
608 instructions_image = GTK_IMAGE(gtk_builder_get_object (builder, "instructions_image"));
609
610 /* Add in the date format combo box and hook it up to an event handler. */
611 date_format_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
612 for (auto& date_fmt : GncDate::c_formats)
613 gtk_combo_box_text_append_text (date_format_combo, _(date_fmt.m_fmt.c_str()));
614 gtk_combo_box_set_active (GTK_COMBO_BOX(date_format_combo), 0);
615 g_signal_connect (G_OBJECT(date_format_combo), "changed",
616 G_CALLBACK(csv_tximp_preview_date_fmt_sel_cb), this);
617
618 /* Add it to the assistant. */
619 auto date_format_container = GTK_CONTAINER(gtk_builder_get_object (builder, "date_format_container"));
620 gtk_container_add (date_format_container, GTK_WIDGET(date_format_combo));
621 gtk_widget_set_hexpand (GTK_WIDGET(date_format_combo), true);
622 gtk_widget_show_all (GTK_WIDGET(date_format_container));
623
624 /* Add in the currency format combo box and hook it up to an event handler. */
625 currency_format_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
626 for (int i = 0; i < num_currency_formats; i++)
627 {
628 gtk_combo_box_text_append_text (currency_format_combo, _(currency_format_user[i]));
629 }
630 /* Default will the locale */
631 gtk_combo_box_set_active (GTK_COMBO_BOX(currency_format_combo), 0);
632 g_signal_connect (G_OBJECT(currency_format_combo), "changed",
633 G_CALLBACK(csv_tximp_preview_currency_fmt_sel_cb), this);
634
635 /* Add it to the assistant. */
636 auto currency_format_container = GTK_CONTAINER(gtk_builder_get_object (builder, "currency_format_container"));
637 gtk_container_add (currency_format_container, GTK_WIDGET(currency_format_combo));
638 gtk_widget_set_hexpand (GTK_WIDGET(currency_format_combo), true);
639 gtk_widget_show_all (GTK_WIDGET(currency_format_container));
640
641 /* Connect the CSV/Fixed-Width radio button event handler. */
642 csv_button = GTK_WIDGET(gtk_builder_get_object (builder, "csv_button"));
643 fixed_button = GTK_WIDGET(gtk_builder_get_object (builder, "fixed_button"));
644
645 /* Load the data treeview and connect it to its resizing event handler. */
646 treeview = GTK_TREE_VIEW(gtk_builder_get_object (builder, "treeview"));
647 gtk_tree_view_set_headers_clickable (treeview, true);
648
649 /* This is true only after encoding_selected is called, so we must
650 * set it initially to false. */
651 encoding_selected_called = false;
652 }
653
654 /* Account Match Page */
655 account_match_page = GTK_WIDGET(gtk_builder_get_object (builder, "account_match_page"));
656 account_match_view = GTK_WIDGET(gtk_builder_get_object (builder, "account_match_view"));
657 account_match_label = GTK_WIDGET(gtk_builder_get_object (builder, "account_match_label"));
658 account_match_btn = GTK_WIDGET(gtk_builder_get_object (builder, "account_match_change"));
659
660 /* Doc Page */
661 doc_page = GTK_WIDGET(gtk_builder_get_object (builder, "doc_page"));
662
663 /* Matcher page */
664 match_page = GTK_WIDGET(gtk_builder_get_object (builder, "match_page"));
665 match_label = GTK_WIDGET(gtk_builder_get_object (builder, "match_label"));
666
667 /* Create the generic transaction importer GUI.
668 Note, this will call g_new0 internally. The returned object is g_freed again
669 either directly by the main matcher or in our assistant_finish code of the matcher
670 is never reached. */
671 gnc_csv_importer_gui = gnc_gen_trans_assist_new (GTK_WIDGET(csv_imp_asst),
672 match_page, nullptr, false, 42);
673
674 /* Summary Page */
675 summary_page = GTK_WIDGET(gtk_builder_get_object (builder, "summary_page"));
676 summary_label = GTK_WIDGET(gtk_builder_get_object (builder, "summary_label"));
677
678 gnc_restore_window_size (GNC_PREFS_GROUP,
679 GTK_WINDOW(csv_imp_asst), gnc_ui_get_main_window(nullptr));
680
681 gtk_builder_connect_signals (builder, this);
682 g_object_unref (G_OBJECT(builder));
683
684 gtk_widget_show_all (GTK_WIDGET(csv_imp_asst));
685 gnc_window_adjust_for_screen (GTK_WINDOW(csv_imp_asst));
686
687 /* In order to trigger a book options display on the creation of a new book,
688 * we need to detect when we are dealing with a new book. */
689 new_book = gnc_is_new_book();
690}
691
692
693/*******************************************************
694 * Assistant Destructor
695 *******************************************************/
696CsvImpTransAssist::~CsvImpTransAssist ()
697{
698 /* This function is safe to call on a null pointer */
699 gnc_gen_trans_list_delete (gnc_csv_importer_gui);
700 /* The call above frees gnc_csv_importer_gui but can't nullify it.
701 * Do it here so no one accidentally can access it still */
702 gnc_csv_importer_gui = nullptr;
703 gtk_widget_destroy (GTK_WIDGET(csv_imp_asst));
704}
705
706
707/**************************************************
708 * Code related to the file chooser page
709 **************************************************/
710
711/* check_for_valid_filename for a valid file to activate the "Next" button
712 */
713bool
714CsvImpTransAssist::check_for_valid_filename ()
715{
716 auto file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(file_chooser));
717 if (!file_name || g_file_test (file_name, G_FILE_TEST_IS_DIR))
718 {
719 g_free (file_name);
720 return false;
721 }
722
723 auto filepath = gnc_uri_get_path (file_name);
724 auto starting_dir = g_path_get_dirname (filepath);
725
726 m_fc_file_name = file_name;
727 gnc_set_default_directory (GNC_PREFS_GROUP, starting_dir);
728
729 DEBUG("file_name selected is %s", m_fc_file_name.c_str());
730 DEBUG("starting directory is %s", starting_dir);
731
732 g_free (filepath);
733 g_free (file_name);
734 g_free (starting_dir);
735
736 return true;
737}
738
739/* csv_tximp_file_activated_cb
740 *
741 * call back for file chooser widget
742 */
743void
744CsvImpTransAssist::file_activated_cb ()
745{
746 gtk_assistant_set_page_complete (csv_imp_asst, file_page, false);
747
748 /* Test for a valid filename and not a directory */
749 if (check_for_valid_filename ())
750 {
751 gtk_assistant_set_page_complete (csv_imp_asst, file_page, true);
752 gtk_assistant_next_page (csv_imp_asst);
753 }
754}
755
756/* csv_tximp_file_selection_changed_cb
757 *
758 * call back for file chooser widget
759 */
760void
761CsvImpTransAssist::file_selection_changed_cb ()
762{
763 /* Enable the "Next" button based on a valid filename */
764 gtk_assistant_set_page_complete (csv_imp_asst, file_page,
765 check_for_valid_filename ());
766}
767
768
769/**************************************************
770 * Code related to the preview page
771 **************************************************/
772
773/* Set the available presets in the settings combo box
774 */
775void CsvImpTransAssist::preview_populate_settings_combo()
776{
777 // Clear the list store
778 auto model = gtk_combo_box_get_model (settings_combo);
779 gtk_list_store_clear (GTK_LIST_STORE(model));
780
781 // Append the default entry
782 auto presets = get_import_presets_trans ();
783 for (auto preset : presets)
784 {
785 GtkTreeIter iter;
786 gtk_list_store_append (GTK_LIST_STORE(model), &iter);
787 /* FIXME we store the raw pointer to the preset, while it's
788 * managed by a shared pointer. This is dangerous because
789 * when the shared pointer goes out of scope, our pointer will dangle.
790 * For now this is safe, because the shared pointers in this case are
791 * long-lived, but this may need refactoring.
792 */
793 gtk_list_store_set (GTK_LIST_STORE(model), &iter, SET_GROUP, preset.get(), SET_NAME, _(preset->m_name.c_str()), -1);
794 }
795}
796
797/* Enable or disable the save and delete settings buttons
798 * depending on what is selected and entered as settings name
799 */
800void CsvImpTransAssist::preview_handle_save_del_sensitivity (GtkComboBox* combo)
801{
802 GtkTreeIter iter;
803 auto can_delete = false;
804 auto can_save = false;
805 auto entry = gtk_bin_get_child (GTK_BIN(combo));
806 auto entry_text = gtk_entry_get_text (GTK_ENTRY(entry));
807 /* Handle sensitivity of the delete and save button */
808 if (gtk_combo_box_get_active_iter (combo, &iter))
809 {
810 CsvTransImpSettings *preset;
811 GtkTreeModel *model = gtk_combo_box_get_model (combo);
812 gtk_tree_model_get (model, &iter, SET_GROUP, &preset, -1);
813
814 if (preset && !preset_is_reserved_name (preset->m_name))
815 {
816 /* Current preset is not read_only, so buttons can be enabled */
817 can_delete = true;
818 can_save = true;
819 }
820 }
821 else if (entry_text && (strlen (entry_text) > 0) &&
822 !preset_is_reserved_name (std::string(entry_text)))
823 can_save = true;
824
825 gtk_widget_set_sensitive (save_button, can_save);
826 gtk_widget_set_sensitive (del_button, can_delete);
827
828}
829
830void
831CsvImpTransAssist::preview_settings_name (GtkEntry* entry)
832{
833 auto text = gtk_entry_get_text (entry);
834 if (text)
835 tx_imp->settings_name(text);
836
837 auto box = gtk_widget_get_parent (GTK_WIDGET(entry));
838 auto combo = gtk_widget_get_parent (GTK_WIDGET(box));
839
840 preview_handle_save_del_sensitivity (GTK_COMBO_BOX(combo));
841}
842
843
844/* Use selected preset to configure the import. Triggered when
845 * a preset is selected in the settings combo.
846 */
847void
848CsvImpTransAssist::preview_settings_load ()
849{
850 // Get the Active Selection
851 GtkTreeIter iter;
852 if (!gtk_combo_box_get_active_iter (settings_combo, &iter))
853 return;
854
855 CsvTransImpSettings *preset = nullptr;
856 auto model = gtk_combo_box_get_model (settings_combo);
857 gtk_tree_model_get (model, &iter, SET_GROUP, &preset, -1);
858
859 if (!preset)
860 return;
861
862 tx_imp->settings (*preset);
863 if (preset->m_load_error)
864 gnc_error_dialog (GTK_WINDOW (csv_imp_asst),
865 "%s", _("There were problems reading some saved settings, continuing to load.\n"
866 "Please review and save again."));
867
868 preview_refresh ();
869 preview_handle_save_del_sensitivity (settings_combo);
870}
871
872/* Callback to delete a settings entry
873 */
874void
875CsvImpTransAssist::preview_settings_delete ()
876{
877 // Get the Active Selection
878 GtkTreeIter iter;
879 if (!gtk_combo_box_get_active_iter (settings_combo, &iter))
880 return;
881
882 CsvTransImpSettings *preset = nullptr;
883 auto model = gtk_combo_box_get_model (settings_combo);
884 gtk_tree_model_get (model, &iter, SET_GROUP, &preset, -1);
885
886 auto response = gnc_ok_cancel_dialog (GTK_WINDOW (csv_imp_asst),
887 GTK_RESPONSE_CANCEL,
888 "%s", _("Delete the Import Settings."));
889 if (response == GTK_RESPONSE_OK)
890 {
891 preset->remove();
892 preview_populate_settings_combo();
893 gtk_combo_box_set_active (settings_combo, 0); // Default
894 preview_refresh (); // Reset the widgets
895 }
896}
897
898/* Callback to save the current settings to the gnucash state file.
899 */
900void
901CsvImpTransAssist::preview_settings_save ()
902{
903 auto new_name = tx_imp->settings_name();
904
905 /* Check if the entry text matches an already existing preset */
906 GtkTreeIter iter;
907 if (!gtk_combo_box_get_active_iter (settings_combo, &iter))
908 {
909
910 auto model = gtk_combo_box_get_model (settings_combo);
911 bool valid = gtk_tree_model_get_iter_first (model, &iter);
912 while (valid)
913 {
914 // Walk through the list, reading each row
915 CsvTransImpSettings *preset;
916 gtk_tree_model_get (model, &iter, SET_GROUP, &preset, -1);
917
918 if (preset && (preset->m_name == std::string(new_name)))
919 {
920 auto response = gnc_ok_cancel_dialog (GTK_WINDOW (csv_imp_asst),
921 GTK_RESPONSE_OK,
922 "%s", _("Setting name already exists, overwrite?"));
923 if (response != GTK_RESPONSE_OK)
924 return;
925
926 break;
927 }
928 valid = gtk_tree_model_iter_next (model, &iter);
929 }
930 }
931
932 /* All checks passed, let's save this preset */
933 if (!tx_imp->save_settings())
934 {
935 gnc_info_dialog (GTK_WINDOW (csv_imp_asst),
936 "%s", _("The settings have been saved."));
937
938 // Update the settings store
939 preview_populate_settings_combo();
940 auto model = gtk_combo_box_get_model (settings_combo);
941
942 // Get the first entry in model
943 GtkTreeIter iter;
944 bool valid = gtk_tree_model_get_iter_first (model, &iter);
945 while (valid)
946 {
947 // Walk through the list, reading each row
948 gchar *name = nullptr;
949 gtk_tree_model_get (model, &iter, SET_NAME, &name, -1);
950
951 if (g_strcmp0 (name, new_name.c_str()) == 0) // Set Active, the one Saved.
952 gtk_combo_box_set_active_iter (settings_combo, &iter);
953
954 g_free (name);
955
956 valid = gtk_tree_model_iter_next (model, &iter);
957 }
958 }
959 else
960 gnc_error_dialog (GTK_WINDOW (csv_imp_asst),
961 "%s", _("There was a problem saving the settings, please try again."));
962}
963
964/* Callback triggered when user adjusts skip start lines
965 */
966void CsvImpTransAssist::preview_update_skipped_rows ()
967{
968 /* Update skip rows in the parser */
969 tx_imp->update_skipped_lines (gtk_spin_button_get_value_as_int (start_row_spin),
970 gtk_spin_button_get_value_as_int (end_row_spin),
971 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(skip_alt_rows_button)),
972 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(skip_errors_button)));
973
974 /* And adjust maximum number of lines that can be skipped at each end accordingly */
975 auto adj = gtk_spin_button_get_adjustment (end_row_spin);
976 gtk_adjustment_set_upper (adj, tx_imp->m_parsed_lines.size()
977 - tx_imp->skip_start_lines() -1);
978
979 adj = gtk_spin_button_get_adjustment (start_row_spin);
980 gtk_adjustment_set_upper (adj, tx_imp->m_parsed_lines.size()
981 - tx_imp->skip_end_lines() - 1);
982
983 preview_refresh_table ();
984}
985
986void CsvImpTransAssist::preview_multi_split (bool multi)
987{
988 tx_imp->multi_split(multi);
989 preview_refresh ();
990}
991
992
1000{
1001
1002 /* Only manipulate separator characters if the currently open file is
1003 * csv separated. */
1004 if (tx_imp->file_format() != GncImpFileFormat::CSV)
1005 return;
1006
1007 /* Add the corresponding characters to checked_separators for each
1008 * button that is checked. */
1009 auto checked_separators = std::string();
1010 const auto stock_sep_chars = std::string (" \t,:;-");
1011 for (int i = 0; i < SEP_NUM_OF_TYPES; i++)
1012 {
1013 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(sep_button[i])))
1014 checked_separators += stock_sep_chars[i];
1015 }
1016
1017 /* Add the custom separator if the user checked its button. */
1018 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(custom_cbutton)))
1019 {
1020 auto custom_sep = gtk_entry_get_text (custom_entry);
1021 if (custom_sep[0] != '\0') /* Don't add a blank separator (bad things will happen!). */
1022 checked_separators += custom_sep;
1023 }
1024
1025 /* Set the parse options using the checked_separators list. */
1026 tx_imp->separators (checked_separators);
1027 tx_imp->enable_escape (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(escape_cbutton)));
1028
1029 /* Parse the data using the new options. We don't want to reguess
1030 * the column types because we want to leave the user's
1031 * configurations intact. */
1032 try
1033 {
1034 tx_imp->tokenize (false);
1035 preview_refresh_table ();
1036 }
1037 catch (std::range_error &e)
1038 {
1039 /* Warn the user there was a problem and try to undo what caused
1040 * the error. (This will cause a reparsing and ideally a usable
1041 * configuration.) */
1042 gnc_error_dialog (GTK_WINDOW (csv_imp_asst), "Error in parsing");
1043 /* If we're here because the user changed the file format, we should just wait for the user
1044 * to update the configuration */
1045 if (!widget)
1046 return;
1047 /* If the user changed the custom separator, erase that custom separator. */
1048 if (widget == GTK_WIDGET(custom_entry))
1049 gtk_entry_set_text (GTK_ENTRY(widget), "");
1050 /* If the user checked a checkbutton, toggle that checkbutton back. */
1051 else
1052 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(widget),
1053 !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget)));
1054 return;
1055 }
1056}
1057
1058
1063{
1064 /* Set the parsing type correctly. */
1065 try
1066 {
1067 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(csv_button)))
1068 {
1069 tx_imp->file_format (GncImpFileFormat::CSV);
1070 g_signal_handlers_disconnect_by_func(G_OBJECT(treeview),
1071 (gpointer)csv_tximp_preview_treeview_clicked_cb, (gpointer)this);
1072 gtk_widget_set_visible (separator_table, true);
1073 gtk_widget_set_visible (fw_instructions_hbox, false);
1074 }
1075 else
1076 {
1077 tx_imp->file_format (GncImpFileFormat::FIXED_WIDTH);
1078 /* Enable context menu for adding/removing columns. */
1079 g_signal_connect (G_OBJECT(treeview), "button-press-event",
1080 G_CALLBACK(csv_tximp_preview_treeview_clicked_cb), (gpointer)this);
1081 gtk_widget_set_visible (separator_table, false);
1082 gtk_widget_set_visible (fw_instructions_hbox, true);
1083
1084 }
1085
1086 tx_imp->tokenize (false);
1087 preview_refresh_table ();
1088 }
1089 catch (std::range_error &e)
1090 {
1091 /* Parsing failed ... */
1092 gnc_error_dialog (GTK_WINDOW (csv_imp_asst), "%s", e.what());
1093 return;
1094 }
1095 catch (...)
1096 {
1097 // FIXME Handle file loading errors (possibly thrown by file_format above)
1098 PWARN("Got an error during file loading");
1099 }
1100}
1101
1102
1103void CsvImpTransAssist::preview_update_account ()
1104{;
1105 auto acct = gnc_account_sel_get_account( GNC_ACCOUNT_SEL(acct_selector) );
1106 tx_imp->base_account(acct);
1107 preview_refresh_table ();
1108}
1109
1110
1116void
1118{
1119 /* This gets called twice every time a new encoding is selected. The
1120 * second call actually passes the correct data; thus, we only do
1121 * something the second time this is called. */
1122
1123 /* If this is the second time the function is called ... */
1124 if (encoding_selected_called)
1125 {
1126 std::string previous_encoding = tx_imp->m_tokenizer->encoding();
1127 /* Try converting the new encoding and reparsing. */
1128 try
1129 {
1130 tx_imp->encoding (encoding);
1131 preview_refresh_table ();
1132 }
1133 catch (...)
1134 {
1135 /* If it fails, change back to the old encoding. */
1136 gnc_error_dialog (GTK_WINDOW (csv_imp_asst), "%s", _("Invalid encoding selected"));
1137 go_charmap_sel_set_encoding (encselector, previous_encoding.c_str());
1138 }
1139 }
1140
1141 encoding_selected_called = !encoding_selected_called;
1142}
1143
1144
1145void
1146CsvImpTransAssist::preview_update_date_format ()
1147{
1148 tx_imp->date_format (gtk_combo_box_get_active (GTK_COMBO_BOX(date_format_combo)));
1149 preview_refresh_table ();
1150}
1151
1152
1153void
1154CsvImpTransAssist::preview_update_currency_format ()
1155{
1156 tx_imp->currency_format (gtk_combo_box_get_active (GTK_COMBO_BOX(currency_format_combo)));
1157 preview_refresh_table ();
1158}
1159
1160static gboolean
1161csv_imp_preview_queue_rebuild_table (CsvImpTransAssist *assist)
1162{
1163 assist->preview_refresh_table ();
1164 return false;
1165}
1166
1167/* Internally used enum to access the columns in the comboboxes
1168 * the user can click to set a type for each column of the data
1169 */
1170enum PreviewHeaderComboCols { COL_TYPE_NAME, COL_TYPE_ID };
1171/* Internally used enum to access the first two (fixed) columns
1172 * in the model used to display the prased data.
1173 */
1174enum PreviewDataTableCols {
1175 PREV_COL_FCOLOR,
1176 PREV_COL_BCOLOR,
1177 PREV_COL_STRIKE,
1178 PREV_COL_ERROR,
1179 PREV_COL_ERR_ICON,
1180 PREV_N_FIXED_COLS };
1181
1189{
1190 /* Get the new text */
1191 GtkTreeIter iter;
1192 auto model = gtk_combo_box_get_model (cbox);
1193 gtk_combo_box_get_active_iter (cbox, &iter);
1194 auto new_col_type = GncTransPropType::NONE;
1195 gtk_tree_model_get (model, &iter, COL_TYPE_ID, &new_col_type, -1);
1196
1197 auto col_num = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT(cbox), "col-num"));
1198 tx_imp->set_column_type (col_num, new_col_type);
1199
1200 /* Delay rebuilding our data table to avoid critical warnings due to
1201 * pending events still acting on them after this event is processed.
1202 */
1203 g_idle_add ((GSourceFunc)csv_imp_preview_queue_rebuild_table, this);
1204
1205}
1206
1207/*======================================================================*/
1208/*================== Beginning of Gnumeric Code ========================*/
1209
1210/* The following is code copied from Gnumeric 1.7.8 licensed under the
1211 * GNU General Public License version 2 and/or version 3. It is from the file
1212 * gnumeric/src/dialogs/dialog-stf-fixed-page.c, and it has been
1213 * modified slightly to work within GnuCash. */
1214
1215/*
1216 * Copyright 2001 Almer S. Tigelaar <almer@gnome.org>
1217 * Copyright 2003 Morten Welinder <terra@gnome.org>
1218 *
1219 * This program is free software; you can redistribute it and/or modify
1220 * it under the terms of the GNU General Public License as published by
1221 * the Free Software Foundation; either version 2 of the License, or
1222 * (at your option) any later version.
1223 *
1224 * This program is distributed in the hope that it will be useful,
1225 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1226 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1227 * GNU General Public License for more details.
1228 *
1229 * You should have received a copy of the GNU General Public License
1230 * along with this program; if not, write to the Free Software
1231 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
1232 */
1233
1234enum
1235{
1236 CONTEXT_STF_IMPORT_MERGE_LEFT = 1,
1237 CONTEXT_STF_IMPORT_MERGE_RIGHT = 2,
1238 CONTEXT_STF_IMPORT_SPLIT = 3,
1239 CONTEXT_STF_IMPORT_WIDEN = 4,
1240 CONTEXT_STF_IMPORT_NARROW = 5
1241};
1242
1243static GnumericPopupMenuElement const popup_elements[] =
1244{
1245 {
1246 N_("Merge with column on _left"), "list-remove",
1247 0, 1 << CONTEXT_STF_IMPORT_MERGE_LEFT, CONTEXT_STF_IMPORT_MERGE_LEFT
1248 },
1249 {
1250 N_("Merge with column on _right"), "list-remove",
1251 0, 1 << CONTEXT_STF_IMPORT_MERGE_RIGHT, CONTEXT_STF_IMPORT_MERGE_RIGHT
1252 },
1253 { "", nullptr, 0, 0, 0 },
1254 {
1255 N_("_Split this column"), nullptr,
1256 0, 1 << CONTEXT_STF_IMPORT_SPLIT, CONTEXT_STF_IMPORT_SPLIT
1257 },
1258 { "", nullptr, 0, 0, 0 },
1259 {
1260 N_("_Widen this column"), "go-next",
1261 0, 1 << CONTEXT_STF_IMPORT_WIDEN, CONTEXT_STF_IMPORT_WIDEN
1262 },
1263 {
1264 N_("_Narrow this column"), "go-previous",
1265 0, 1 << CONTEXT_STF_IMPORT_NARROW, CONTEXT_STF_IMPORT_NARROW
1266 },
1267 { nullptr, nullptr, 0, 0, 0 },
1268};
1269
1270uint32_t CsvImpTransAssist::get_new_col_rel_pos (GtkTreeViewColumn *tcol, int dx)
1271{
1272 auto renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(tcol));
1273 auto cell = GTK_CELL_RENDERER(renderers->data);
1274 g_list_free (renderers);
1275 PangoFontDescription *font_desc;
1276 g_object_get (G_OBJECT(cell), "font_desc", &font_desc, nullptr);
1277
1278 PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET(treeview), "x");
1279 pango_layout_set_font_description (layout, font_desc);
1280 int width;
1281 pango_layout_get_pixel_size (layout, &width, nullptr);
1282 if (width < 1) width = 1;
1283 uint32_t charindex = (dx + width / 2) / width;
1284 g_object_unref (layout);
1285 pango_font_description_free (font_desc);
1286
1287 return charindex;
1288}
1289
1290gboolean
1291fixed_context_menu_handler (GnumericPopupMenuElement const *element,
1292 gpointer userdata)
1293{
1294 auto info = (CsvImpTransAssist*)userdata;
1295 auto fwtok = dynamic_cast<GncFwTokenizer*>(info->tx_imp->m_tokenizer.get());
1296
1297 switch (element->index)
1298 {
1299 case CONTEXT_STF_IMPORT_MERGE_LEFT:
1300 fwtok->col_delete (info->fixed_context_col - 1);
1301 break;
1302 case CONTEXT_STF_IMPORT_MERGE_RIGHT:
1303 fwtok->col_delete (info->fixed_context_col);
1304 break;
1305 case CONTEXT_STF_IMPORT_SPLIT:
1306 fwtok->col_split (info->fixed_context_col, info->fixed_context_offset);
1307 break;
1308 case CONTEXT_STF_IMPORT_WIDEN:
1309 fwtok->col_widen (info->fixed_context_col);
1310 break;
1311 case CONTEXT_STF_IMPORT_NARROW:
1312 fwtok->col_narrow (info->fixed_context_col);
1313 break;
1314 default:
1315 ; /* Nothing */
1316 }
1317
1318 try
1319 {
1320 info->tx_imp->tokenize (false);
1321 }
1322 catch(std::range_error& e)
1323 {
1324 gnc_error_dialog (GTK_WINDOW (info->csv_imp_asst), "%s", e.what());
1325 return false;
1326 }
1327 info->preview_refresh_table ();
1328 return true;
1329}
1330
1331void
1332CsvImpTransAssist::fixed_context_menu (GdkEventButton *event,
1333 int col, int offset)
1334{
1335 auto fwtok = dynamic_cast<GncFwTokenizer*>(tx_imp->m_tokenizer.get());
1336 fixed_context_col = col;
1337 fixed_context_offset = offset;
1338
1339 int sensitivity_filter = 0;
1340 if (!fwtok->col_can_delete (col - 1))
1341 sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_MERGE_LEFT);
1342 if (!fwtok->col_can_delete (col))
1343 sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_MERGE_RIGHT);
1344 if (!fwtok->col_can_split (col, offset))
1345 sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_SPLIT);
1346 if (!fwtok->col_can_widen (col))
1347 sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_WIDEN);
1348 if (!fwtok->col_can_narrow (col))
1349 sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_NARROW);
1350
1351 gnumeric_create_popup_menu (popup_elements, &fixed_context_menu_handler,
1352 this, 0,
1353 sensitivity_filter, event);
1354}
1355
1356/*===================== End of Gnumeric Code ===========================*/
1357/*======================================================================*/
1358void
1359CsvImpTransAssist::preview_split_column (int col, int offset)
1360{
1361 auto fwtok = dynamic_cast<GncFwTokenizer*>(tx_imp->m_tokenizer.get());
1362 fwtok->col_split (col, offset);
1363 try
1364 {
1365 tx_imp->tokenize (false);
1366 }
1367 catch (std::range_error& e)
1368 {
1369 gnc_error_dialog (GTK_WINDOW (csv_imp_asst), "%s", e.what());
1370 return;
1371 }
1372 preview_refresh_table();
1373}
1374
1375
1381void
1382CsvImpTransAssist::preview_update_fw_columns (GtkTreeView* treeview, GdkEventButton* event)
1383{
1384 /* Nothing to do if this was not triggered on our treeview body */
1385 if (event->window != gtk_tree_view_get_bin_window (treeview))
1386 return;
1387
1388 /* Find the column that was clicked. */
1389 GtkTreeViewColumn *tcol = nullptr;
1390 int cell_x = 0;
1391 auto success = gtk_tree_view_get_path_at_pos (treeview,
1392 (int)event->x, (int)event->y,
1393 nullptr, &tcol, &cell_x, nullptr);
1394 if (!success)
1395 return;
1396
1397 /* Stop if no column found in this treeview (-1) or
1398 * if column is the error messages column (0) */
1399 auto tcol_list = gtk_tree_view_get_columns(treeview);
1400 auto tcol_num = g_list_index (tcol_list, tcol);
1401 g_list_free (tcol_list);
1402 if (tcol_num <= 0)
1403 return;
1404
1405 /* Data columns in the treeview are offset by one
1406 * because the first column is the error column
1407 */
1408 auto dcol = tcol_num - 1;
1409 auto offset = get_new_col_rel_pos (tcol, cell_x);
1410 if (event->type == GDK_2BUTTON_PRESS && event->button == 1)
1411 /* Double clicks can split columns. */
1412 preview_split_column (dcol, offset);
1413 else if (event->type == GDK_BUTTON_PRESS && event->button == 3)
1414 /* Right clicking brings up a context menu. */
1415 fixed_context_menu (event, dcol, offset);
1416}
1417
1418
1419/* Convert state info (errors/skipped) in visual feedback to decorate the preview table */
1420void
1421CsvImpTransAssist::preview_row_fill_state_cells (GtkListStore *store, GtkTreeIter *iter,
1422 ErrMap& err_msgs, bool skip)
1423{
1424 /* Extract error status for all non-skipped lines */
1425 auto err_msg = std::string();
1426 const char *icon_name = nullptr;
1427 const char *fcolor = nullptr;
1428 const char *bcolor = nullptr;
1429 /* Skipped lines or issues with account resolution are not
1430 * errors at this stage. */
1431 auto non_acct_error = [](ErrPair curr_err)
1432 {
1433 return !((curr_err.first == GncTransPropType::ACCOUNT) ||
1434 (curr_err.first == GncTransPropType::TACCOUNT));
1435 };
1436 if (!skip && std::any_of(err_msgs.cbegin(), err_msgs.cend(), non_acct_error))
1437 {
1438 fcolor = "black";
1439 bcolor = "pink";
1440 err_msg = std::string(_("This line has the following parse issues:"));
1441 auto add_non_acct_err_bullet = [](std::string& a, ErrMap::value_type& b)->std::string
1442 {
1443 if ((b.first == GncTransPropType::ACCOUNT) ||
1444 (b.first == GncTransPropType::TACCOUNT))
1445 return std::move(a);
1446 else
1447 return std::move(a) + "\n• " + b.second;
1448
1449 };
1450 err_msg = std::accumulate (err_msgs.begin(), err_msgs.end(),
1451 std::move (err_msg), add_non_acct_err_bullet);
1452 icon_name = "dialog-error";
1453 }
1454 gtk_list_store_set (store, iter,
1455 PREV_COL_FCOLOR, fcolor,
1456 PREV_COL_BCOLOR, bcolor,
1457 PREV_COL_STRIKE, skip,
1458 PREV_COL_ERROR, err_msg.c_str(),
1459 PREV_COL_ERR_ICON, icon_name, -1);
1460}
1461
1462/* Helper function that creates a combo_box using a model
1463 * with valid column types and selects the given column type
1464 */
1465GtkWidget*
1466CsvImpTransAssist::preview_cbox_factory (GtkTreeModel* model, uint32_t colnum)
1467{
1468 GtkTreeIter iter;
1469 auto cbox = gtk_combo_box_new_with_model(model);
1470
1471 /* Set up a renderer for this combobox. */
1472 auto renderer = gtk_cell_renderer_text_new();
1473 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(cbox),
1474 renderer, true);
1475 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT(cbox),
1476 renderer, "text", COL_TYPE_NAME);
1477
1478 auto valid = gtk_tree_model_get_iter_first (model, &iter);
1479 while (valid)
1480 {
1481 gint stored_col_type;
1482 gtk_tree_model_get (model, &iter,
1483 COL_TYPE_ID, &stored_col_type, -1);
1484 if (stored_col_type == static_cast<int>( tx_imp->column_types()[colnum]))
1485 break;
1486 valid = gtk_tree_model_iter_next(model, &iter);
1487 }
1488 if (valid)
1489 gtk_combo_box_set_active_iter (GTK_COMBO_BOX(cbox), &iter);
1490
1491 g_object_set_data (G_OBJECT(cbox), "col-num", GUINT_TO_POINTER(colnum));
1492 g_signal_connect (G_OBJECT(cbox), "changed",
1493 G_CALLBACK(csv_tximp_preview_col_type_changed_cb), (gpointer)this);
1494
1495 gtk_widget_show (cbox);
1496 return cbox;
1497}
1498
1499void
1500CsvImpTransAssist::preview_style_column (uint32_t col_num, GtkTreeModel* model)
1501{
1502 auto col = gtk_tree_view_get_column (treeview, col_num);
1503 auto renderer = static_cast<GtkCellRenderer*>(gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(col))->data);
1504 /* First column -the error status column- is rendered differently */
1505 if (col_num == 0)
1506 {
1507 gtk_tree_view_column_set_attributes (col, renderer,
1508 "icon-name", PREV_COL_ERR_ICON,
1509 "cell-background", PREV_COL_BCOLOR, nullptr);
1510 g_object_set (G_OBJECT(renderer), "stock-size", GTK_ICON_SIZE_MENU, nullptr);
1511 g_object_set (G_OBJECT(col), "sizing", GTK_TREE_VIEW_COLUMN_FIXED,
1512 "fixed-width", 20, nullptr);
1513 gtk_tree_view_column_set_resizable (col, false);
1514 }
1515 else
1516 {
1517 gtk_tree_view_column_set_attributes (col, renderer,
1518 "foreground", PREV_COL_FCOLOR,
1519 "background", PREV_COL_BCOLOR,
1520 "strikethrough", PREV_COL_STRIKE,
1521 "text", col_num + PREV_N_FIXED_COLS -1, nullptr);
1522
1523 /* We want a monospace font fixed-width data is properly displayed. */
1524 g_object_set (G_OBJECT(renderer), "family", "monospace", nullptr);
1525
1526 /* Add a combobox to select column types as column header. Each uses the same
1527 * common model for the dropdown list. The selected value is taken
1528 * from the column_types vector. */
1529 auto cbox = preview_cbox_factory (GTK_TREE_MODEL(model), col_num - 1);
1530 gtk_tree_view_column_set_widget (col, cbox);
1531
1532 /* Enable resizing of the columns. */
1533 gtk_tree_view_column_set_resizable (col, true);
1534 gtk_tree_view_column_set_clickable (col, true);
1535 }
1536
1537}
1538
1539/* Helper to create a shared store for the header comboboxes in the preview treeview.
1540 * It holds the possible column types */
1541static GtkTreeModel*
1542make_column_header_model (bool multi_split)
1543{
1544 auto combostore = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
1545 for (auto col_type : gnc_csv_col_type_strs)
1546 {
1547 /* Only add column types that make sense in
1548 * the chosen import mode (multi-split vs two-split).
1549 */
1550 if (sanitize_trans_prop(col_type.first, multi_split) == col_type.first)
1551 {
1552 GtkTreeIter iter;
1553 gtk_list_store_append (combostore, &iter);
1554 gtk_list_store_set (combostore, &iter,
1555 COL_TYPE_NAME, _(col_type.second),
1556 COL_TYPE_ID, static_cast<int>(col_type.first), -1);
1557 }
1558 }
1559 return GTK_TREE_MODEL(combostore);
1560}
1561
1562/* Updates the preview treeview to show the data as parsed based on the user's
1563 * import parameters.
1564 */
1565void CsvImpTransAssist::preview_refresh_table ()
1566{
1567 preview_validate_settings ();
1568
1569 /* Create a new liststore to hold status and data from the file being imported.
1570 The first columns hold status information (row-color, row-errors, row-error-icon,...
1571 All following columns represent the tokenized data as strings. */
1572 auto ncols = PREV_N_FIXED_COLS + tx_imp->column_types().size();
1573 auto model_col_types = g_new (GType, ncols);
1574 model_col_types[PREV_COL_FCOLOR] = G_TYPE_STRING;
1575 model_col_types[PREV_COL_BCOLOR] = G_TYPE_STRING;
1576 model_col_types[PREV_COL_ERROR] = G_TYPE_STRING;
1577 model_col_types[PREV_COL_ERR_ICON] = G_TYPE_STRING;
1578 model_col_types[PREV_COL_STRIKE] = G_TYPE_BOOLEAN;
1579 for (guint i = PREV_N_FIXED_COLS; i < ncols; i++)
1580 model_col_types[i] = G_TYPE_STRING;
1581 auto store = gtk_list_store_newv (ncols, model_col_types);
1582 g_free (model_col_types);
1583
1584 /* Fill the data liststore with data from importer object. */
1585 for (auto parse_line : tx_imp->m_parsed_lines)
1586 {
1587 /* Fill the state cells */
1588 GtkTreeIter iter;
1589 gtk_list_store_append (store, &iter);
1590 preview_row_fill_state_cells (store, &iter,
1591 std::get<PL_ERROR>(parse_line), std::get<PL_SKIP>(parse_line));
1592
1593 /* Fill the data cells. */
1594 for (auto cell_str_it = std::get<PL_INPUT>(parse_line).cbegin(); cell_str_it != std::get<PL_INPUT>(parse_line).cend(); cell_str_it++)
1595 {
1596 uint32_t pos = PREV_N_FIXED_COLS + cell_str_it - std::get<PL_INPUT>(parse_line).cbegin();
1597 gtk_list_store_set (store, &iter, pos, cell_str_it->c_str(), -1);
1598 }
1599 }
1600 gtk_tree_view_set_model (treeview, GTK_TREE_MODEL(store));
1601 gtk_tree_view_set_tooltip_column (treeview, PREV_COL_ERROR);
1602
1603 /* Adjust treeview to go with the just created model. This consists of adding
1604 * or removing columns and resetting any parameters related to how
1605 * the columns and data should be rendered.
1606 */
1607
1608 /* Start with counting the current number of columns (ntcols)
1609 * we have in the treeview */
1610 auto ntcols = gtk_tree_view_get_n_columns (treeview);
1611
1612 /* Drop redundant columns if the model has less data columns than the new model
1613 * ntcols = n° of columns in treeview (1 error column + x data columns)
1614 * ncols = n° of columns in model (fixed state columns + x data columns)
1615 */
1616 while (ntcols > ncols - PREV_N_FIXED_COLS + 1)
1617 {
1618 auto col = gtk_tree_view_get_column (treeview, ntcols - 1);
1619 gtk_tree_view_column_clear (col);
1620 ntcols = gtk_tree_view_remove_column(treeview, col);
1621 }
1622
1623 /* Insert columns if the model has more data columns than the treeview. */
1624 while (ntcols < ncols - PREV_N_FIXED_COLS + 1)
1625 {
1626 /* Default cell renderer is text, except for the first (error) column */
1627 auto renderer = gtk_cell_renderer_text_new();
1628 if (ntcols == 0)
1629 renderer = gtk_cell_renderer_pixbuf_new(); // Error column uses an icon
1630 auto col = gtk_tree_view_column_new ();
1631 gtk_tree_view_column_pack_start (col, renderer, false);
1632 ntcols = gtk_tree_view_append_column (treeview, col);
1633 }
1634
1635 /* Reset column attributes as they are undefined after recreating the model */
1636 auto combostore = make_column_header_model (tx_imp->multi_split());
1637 for (uint32_t i = 0; i < ntcols; i++)
1638 preview_style_column (i, combostore);
1639
1640 /* Release our reference for the stores to allow proper memory management. */
1641 g_object_unref (store);
1642 g_object_unref (combostore);
1643
1644 /* Also reset the base account combo box as it's value may have changed due to column changes here */
1645 auto base_acct = gnc_account_sel_get_account(GNC_ACCOUNT_SEL(acct_selector));
1646 if (tx_imp->base_account() != base_acct)
1647 {
1648 g_signal_handlers_block_by_func (acct_selector, (gpointer) csv_tximp_preview_acct_sel_cb, this);
1649 gnc_account_sel_set_account(GNC_ACCOUNT_SEL(acct_selector),
1650 tx_imp->base_account() , false);
1651 g_signal_handlers_unblock_by_func (acct_selector, (gpointer) csv_tximp_preview_acct_sel_cb, this);
1652 }
1653
1654 /* Make the things actually appear. */
1655 gtk_widget_show_all (GTK_WIDGET(treeview));
1656}
1657
1658/* Update the preview page based on the current state of the importer.
1659 * Should be called when settings are changed.
1660 */
1661void
1662CsvImpTransAssist::preview_refresh ()
1663{
1664 // Cache skip settings. Updating the widgets one by one
1665 // triggers a callback the transfers all skip widgets'
1666 // values to settings. So by the time the next widget value
1667 // is to be set, that widget's 'new' setting has already been replaced by
1668 // its old setting preventing us from using it here sensibly.
1669 // Another solution might have been to delay callbacks from running
1670 // until after all values are set.
1671 auto skip_start_lines = tx_imp->skip_start_lines();
1672 auto skip_end_lines = tx_imp->skip_end_lines();
1673 auto skip_alt_lines = tx_imp->skip_alt_lines();
1674
1675 // Set start row
1676 auto adj = gtk_spin_button_get_adjustment (start_row_spin);
1677 gtk_adjustment_set_upper (adj, tx_imp->m_parsed_lines.size());
1678 gtk_spin_button_set_value (start_row_spin, skip_start_lines);
1679
1680 // Set end row
1681 adj = gtk_spin_button_get_adjustment (end_row_spin);
1682 gtk_adjustment_set_upper (adj, tx_imp->m_parsed_lines.size());
1683 gtk_spin_button_set_value (end_row_spin, skip_end_lines);
1684
1685 // Set Alternate rows
1686 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(skip_alt_rows_button),
1687 skip_alt_lines);
1688
1689 // Set multi-split indicator
1690 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(multi_split_cbutton),
1691 tx_imp->multi_split());
1692 gtk_widget_set_sensitive (acct_selector, !tx_imp->multi_split());
1693
1694 // Set Import Format
1695 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(csv_button),
1696 (tx_imp->file_format() == GncImpFileFormat::CSV));
1697 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(fixed_button),
1698 (tx_imp->file_format() != GncImpFileFormat::CSV));
1699
1700 // Set Date & Currency Format and Character encoding
1701 gtk_combo_box_set_active (GTK_COMBO_BOX(date_format_combo),
1702 tx_imp->date_format());
1703 gtk_combo_box_set_active (GTK_COMBO_BOX(currency_format_combo),
1704 tx_imp->currency_format());
1705 go_charmap_sel_set_encoding (encselector, tx_imp->encoding().c_str());
1706
1707 // Handle separator checkboxes and custom field, only relevant if the file format is csv
1708 // Note we defer the change signal until all buttons have been updated
1709 // An early update may result in an incomplete tokenize run and that would
1710 // cause our list of saved column types to be truncated
1711 if (tx_imp->file_format() == GncImpFileFormat::CSV)
1712 {
1713 auto separators = tx_imp->separators();
1714 auto enable_escape = tx_imp->enable_escape();
1715 const auto stock_sep_chars = std::string (" \t,:;-");
1716 for (int i = 0; i < SEP_NUM_OF_TYPES; i++)
1717 {
1718 g_signal_handlers_block_by_func (sep_button[i], (gpointer) csv_tximp_preview_sep_button_cb, this);
1719 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(sep_button[i]),
1720 separators.find (stock_sep_chars[i]) != std::string::npos);
1721 g_signal_handlers_unblock_by_func (sep_button[i], (gpointer) csv_tximp_preview_sep_button_cb, this);
1722 }
1723
1724 // If there are any other separators in the separators string,
1725 // add them as custom separators
1726 auto pos = separators.find_first_of (stock_sep_chars);
1727 while (!separators.empty() && pos != std::string::npos)
1728 {
1729 separators.erase(pos);
1730 pos = separators.find_first_of (stock_sep_chars);
1731 }
1732 g_signal_handlers_block_by_func (custom_cbutton, (gpointer) csv_tximp_preview_sep_button_cb, this);
1733 g_signal_handlers_block_by_func (custom_entry, (gpointer) csv_tximp_preview_sep_button_cb, this);
1734 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(custom_cbutton),
1735 !separators.empty());
1736 gtk_entry_set_text (GTK_ENTRY(custom_entry), separators.c_str());
1737
1738 g_signal_handlers_block_by_func (escape_cbutton, (gpointer) csv_tximp_preview_sep_button_cb, this);
1739 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(escape_cbutton),
1740 enable_escape);
1741 g_signal_handlers_unblock_by_func (escape_cbutton, (gpointer) csv_tximp_preview_sep_button_cb, this);
1742
1743 g_signal_handlers_unblock_by_func (custom_cbutton, (gpointer) csv_tximp_preview_sep_button_cb, this);
1744 g_signal_handlers_unblock_by_func (custom_entry, (gpointer) csv_tximp_preview_sep_button_cb, this);
1745 g_signal_handlers_unblock_by_func (custom_cbutton, (gpointer) csv_tximp_preview_sep_button_cb, this);
1746 try
1747 {
1748 tx_imp->tokenize (false);
1749 }
1750 catch(std::range_error& err)
1751 {
1752 PERR("CSV Tokenization Failed: %s", err.what());
1753 }
1754 }
1755
1756 // Repopulate the parsed data table
1757 preview_refresh_table ();
1758}
1759
1760/* Check if all selected data can be parsed sufficiently to continue
1761 */
1762void CsvImpTransAssist::preview_validate_settings ()
1763{
1764 /* Allow the user to proceed only if there are no inconsistencies in the settings */
1765 auto has_non_acct_errors = !tx_imp->verify (false).empty();
1766 auto error_msg = tx_imp->verify (m_req_mapped_accts);
1767 gtk_assistant_set_page_complete (csv_imp_asst, preview_page, !has_non_acct_errors);
1768 gtk_label_set_markup(GTK_LABEL(instructions_label), error_msg.c_str());
1769 gtk_widget_set_visible (GTK_WIDGET(instructions_image), !error_msg.empty());
1770
1771 /* Show or hide the account match page based on whether there are
1772 * accounts in the user data according to the importer configuration
1773 * only if there are no errors
1774 */
1775 if (!has_non_acct_errors)
1776 gtk_widget_set_visible (GTK_WIDGET(account_match_page),
1777 !tx_imp->accounts().empty());
1778}
1779
1780
1781/**************************************************
1782 * Code related to the account match page
1783 **************************************************/
1784
1785/* Populates the account match view with all potential
1786 * account names found in the parse data.
1787 */
1788void CsvImpTransAssist::acct_match_set_accounts ()
1789{
1790 auto store = gtk_tree_view_get_model (GTK_TREE_VIEW(account_match_view));
1791 gtk_list_store_clear (GTK_LIST_STORE(store));
1792
1793 auto accts = tx_imp->accounts();
1794 for (auto acct : accts)
1795 {
1796 GtkTreeIter acct_iter;
1797 gtk_list_store_append (GTK_LIST_STORE(store), &acct_iter);
1798 gtk_list_store_set (GTK_LIST_STORE(store), &acct_iter, MAPPING_STRING, acct.c_str(),
1799 MAPPING_FULLPATH, _("No Linked Account"), MAPPING_ACCOUNT, nullptr, -1);
1800 }
1801}
1802
1803static void
1804csv_tximp_acct_match_load_mappings (GtkTreeModel *mappings_store)
1805{
1806 // Set iter to first entry of store
1807 GtkTreeIter iter;
1808 auto valid = gtk_tree_model_get_iter_first (mappings_store, &iter);
1809
1810 // Walk through the store trying to match to a map
1811 while (valid)
1812 {
1813 // Walk through the list, reading each row
1814 Account *account = nullptr;
1815 gchar *map_string;
1816 gtk_tree_model_get (GTK_TREE_MODEL(mappings_store), &iter, MAPPING_STRING, &map_string, MAPPING_ACCOUNT, &account, -1);
1817
1818 // Look for an account matching the map_string
1819 // It may already be set in the tree model. If not we try to match the map_string with
1820 // - an entry in our saved account maps
1821 // - a full name of any of our existing accounts
1822 if (account ||
1823 (account = gnc_account_imap_find_any (gnc_get_current_book(), IMAP_CAT_CSV, map_string)) ||
1824 (account = gnc_account_lookup_by_full_name (gnc_get_current_root_account(), map_string)))
1825 {
1826 auto fullpath = gnc_account_get_full_name (account);
1827 gtk_list_store_set (GTK_LIST_STORE(mappings_store), &iter, MAPPING_FULLPATH, fullpath, -1);
1828 gtk_list_store_set (GTK_LIST_STORE(mappings_store), &iter, MAPPING_ACCOUNT, account, -1);
1829 g_free (fullpath);
1830 }
1831
1832 g_free (map_string);
1833 valid = gtk_tree_model_iter_next (mappings_store, &iter);
1834 }
1835}
1836
1837static bool
1838csv_tximp_acct_match_check_all (GtkTreeModel *model)
1839{
1840 // Set iter to first entry of store
1841 GtkTreeIter iter;
1842 auto valid = gtk_tree_model_get_iter_first (model, &iter);
1843
1844 // Walk through the store looking for nullptr accounts
1845 while (valid)
1846 {
1847 Account *account;
1848 gtk_tree_model_get (model, &iter, MAPPING_ACCOUNT, &account, -1);
1849 if (!account)
1850 return false;
1851
1852 valid = gtk_tree_model_iter_next (model, &iter);
1853 }
1854 return true;
1855}
1856
1857
1858/* Evaluate acct_name as a full account name. Try if it
1859 * contains a path to an existing parent account. If not,
1860 * alter the full path name to use a fake separator to
1861 * avoid calling multiple new account windows for each
1862 * non-existent parent account.
1863 */
1864static std::string
1865csv_tximp_acct_match_text_parse (std::string acct_name)
1866{
1868 auto sep_pos = acct_name.rfind(sep);
1869 if (sep_pos == std::string::npos)
1870 // No separators found in acct_name -> return as is
1871 return acct_name;
1872
1873 auto parent = acct_name.substr(0, sep_pos);
1874 auto root = gnc_get_current_root_account ();
1875
1876 if (gnc_account_lookup_by_full_name (root, parent.c_str()))
1877 // acct_name's parent matches an existing account -> acct_name as is
1878 return acct_name;
1879 else
1880 {
1881 // Acct name doesn't match an existing account
1882 // -> return the name with a fake separator to avoid
1883 // asking the user to create each intermediary account as well
1884 const gchar *alt_sep;
1885 if (g_strcmp0 (sep,":") == 0)
1886 alt_sep = "-";
1887 else
1888 alt_sep = ":";
1889 for (sep_pos = acct_name.find(sep); sep_pos != std::string::npos;
1890 sep_pos = acct_name.find(sep))
1891 acct_name.replace (sep_pos, strlen(sep), alt_sep);
1892 return acct_name;
1893 }
1894}
1895
1896void
1897CsvImpTransAssist::acct_match_select(GtkTreeModel *model, GtkTreeIter* iter)
1898{
1899 // Get the stored string and account (if any)
1900 gchar *text = nullptr;
1901 Account *account = nullptr;
1902 gtk_tree_model_get (model, iter, MAPPING_STRING, &text,
1903 MAPPING_ACCOUNT, &account, -1);
1904
1905 auto acct_name = csv_tximp_acct_match_text_parse (text);
1906 auto gnc_acc = gnc_import_select_account (GTK_WIDGET(csv_imp_asst), nullptr, true,
1907 acct_name.c_str(), nullptr, ACCT_TYPE_NONE, account, nullptr);
1908
1909 if (gnc_acc) // We may have canceled
1910 {
1911 auto fullpath = gnc_account_get_full_name (gnc_acc);
1912 gtk_list_store_set (GTK_LIST_STORE(model), iter,
1913 MAPPING_ACCOUNT, gnc_acc,
1914 MAPPING_FULLPATH, fullpath, -1);
1915
1916 // Update the account kvp mappings
1917 if (text && *text)
1918 {
1919 gnc_account_imap_delete_account (account, IMAP_CAT_CSV, text);
1920 gnc_account_imap_add_account (gnc_acc, IMAP_CAT_CSV, text, gnc_acc);
1921 }
1922
1923 // Force reparsing of account columns - may impact multi-currency mode
1924 auto col_types = tx_imp->column_types();
1925 auto col_type_it = std::find (col_types.cbegin(),
1926 col_types.cend(), GncTransPropType::ACCOUNT);
1927 if (col_type_it != col_types.cend())
1928 tx_imp->set_column_type(col_type_it - col_types.cbegin(),
1929 GncTransPropType::ACCOUNT, true);
1930 col_type_it = std::find (col_types.cbegin(),
1931 col_types.cend(), GncTransPropType::TACCOUNT);
1932 if (col_type_it != col_types.cend())
1933 tx_imp->set_column_type(col_type_it - col_types.cbegin(),
1934 GncTransPropType::TACCOUNT, true);
1935
1936 g_free (fullpath);
1937 }
1938 g_free (text);
1939
1940
1941 /* Enable the "Next" Assistant Button */
1942 auto all_checked = csv_tximp_acct_match_check_all (model);
1943 gtk_assistant_set_page_complete (csv_imp_asst, account_match_page,
1944 all_checked);
1945
1946 /* Update information message and whether to display account errors */
1947 m_req_mapped_accts = all_checked;
1948 auto errs = tx_imp->verify(m_req_mapped_accts);
1949 gtk_label_set_text (GTK_LABEL(account_match_label), errs.c_str());
1950}
1951
1952void
1953CsvImpTransAssist::acct_match_via_button ()
1954{
1955 auto model = gtk_tree_view_get_model (GTK_TREE_VIEW(account_match_view));
1956 auto selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(account_match_view));
1957
1958 GtkTreeIter iter;
1959 if (gtk_tree_selection_get_selected (selection, &model, &iter))
1960 acct_match_select (model, &iter);
1961}
1962
1963
1964/* This is the callback for the mouse click */
1965bool
1966CsvImpTransAssist::acct_match_via_view_dblclick (GdkEventButton *event)
1967{
1968 /* This is for a double click */
1969 if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
1970 {
1971 auto window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (account_match_view));
1972 if (event->window != window)
1973 return false;
1974
1975 /* Get tree path for row that was clicked, true if row exists */
1976 GtkTreePath *path;
1977 if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (account_match_view), (gint) event->x, (gint) event->y,
1978 &path, nullptr, nullptr, nullptr))
1979 {
1980 DEBUG("event->x is %d and event->y is %d", (gint)event->x, (gint)event->y);
1981
1982 auto model = gtk_tree_view_get_model (GTK_TREE_VIEW(account_match_view));
1983 GtkTreeIter iter;
1984 if (gtk_tree_model_get_iter (model, &iter, path))
1985 acct_match_select (model, &iter);
1986 gtk_tree_path_free (path);
1987 }
1988 return true;
1989 }
1990 return false;
1991}
1992
1993
1994/*******************************************************
1995 * Assistant page prepare functions
1996 *******************************************************/
1997
1998void
1999CsvImpTransAssist::assist_file_page_prepare ()
2000{
2001 /* Set the default directory */
2002 if (!m_final_file_name.empty())
2003 gtk_file_chooser_set_filename (GTK_FILE_CHOOSER(file_chooser),
2004 m_final_file_name.c_str());
2005 else
2006 {
2007 auto starting_dir = gnc_get_default_directory (GNC_PREFS_GROUP);
2008 if (starting_dir)
2009 {
2010 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(file_chooser), starting_dir);
2011 g_free (starting_dir);
2012 }
2013 }
2014
2015 /* Disable the "Next" Assistant Button */
2016 gtk_assistant_set_page_complete (csv_imp_asst, account_match_page, false);
2017}
2018
2019
2020void
2021CsvImpTransAssist::assist_preview_page_prepare ()
2022{
2023 auto go_back = false;
2024
2025 if (m_final_file_name != m_fc_file_name)
2026 {
2027 tx_imp = std::unique_ptr<GncTxImport>(new GncTxImport);
2028
2029 /* Assume data is CSV. User can later override to Fixed Width if needed */
2030 try
2031 {
2032 tx_imp->file_format (GncImpFileFormat::CSV);
2033 tx_imp->load_file (m_fc_file_name);
2034 tx_imp->tokenize (true);
2035 m_req_mapped_accts = false;
2036
2037 /* Get settings store and populate */
2038 preview_populate_settings_combo();
2039 gtk_combo_box_set_active (settings_combo, 0);
2040
2041 /* Disable the "Next" Assistant Button */
2042 gtk_assistant_set_page_complete (csv_imp_asst, preview_page, false);
2043 }
2044 catch (std::ifstream::failure& e)
2045 {
2046 /* File loading failed ... */
2047 gnc_error_dialog (GTK_WINDOW (csv_imp_asst), "%s", e.what());
2048 go_back = true;
2049 }
2050 catch (std::range_error &e)
2051 {
2052 /* Parsing failed ... */
2053 gnc_error_dialog (GTK_WINDOW (csv_imp_asst), "%s", _(e.what()));
2054 /* Stay in this step so user can override */
2055 go_back = false;
2056 }
2057 }
2058
2059 if (go_back)
2060 gtk_assistant_previous_page (csv_imp_asst);
2061 else
2062 {
2063 m_final_file_name = m_fc_file_name;
2064 preview_refresh ();
2065
2066 /* Load the data into the treeview. */
2067 g_idle_add ((GSourceFunc)csv_imp_preview_queue_rebuild_table, this);
2068 }
2069}
2070void
2071CsvImpTransAssist::assist_account_match_page_prepare ()
2072{
2073
2074 // Load the account strings into the store
2075 acct_match_set_accounts ();
2076
2077 // Match the account strings to account maps from previous imports
2078 auto store = gtk_tree_view_get_model (GTK_TREE_VIEW(account_match_view));
2079 csv_tximp_acct_match_load_mappings (store);
2080
2081 // Enable the view, possibly after an error
2082 gtk_widget_set_sensitive (account_match_view, true);
2083 gtk_widget_set_sensitive (account_match_btn, true);
2084
2085 /* Enable the "Next" Assistant Button */
2086 auto all_checked = csv_tximp_acct_match_check_all (store);
2087 gtk_assistant_set_page_complete (csv_imp_asst, account_match_page,
2088 all_checked);
2089
2090 /* Update information message and whether to display account errors */
2091 m_req_mapped_accts = all_checked;
2092 auto text = tx_imp->verify (m_req_mapped_accts);
2093 gtk_label_set_text (GTK_LABEL(account_match_label), text.c_str());
2094}
2095
2096
2097void
2098CsvImpTransAssist::assist_doc_page_prepare ()
2099{
2100 if (!tx_imp->verify (true).empty())
2101 {
2102 /* New accounts can change the multi-currency situation and hence
2103 * may require more column tweaks. If so
2104 * inform the user and go back to the preview page.
2105 */
2106 gtk_assistant_set_current_page (csv_imp_asst, 2);
2107 }
2108
2109 /* Block going back */
2110 gtk_assistant_commit (csv_imp_asst);
2111
2112 /* Before creating transactions, if this is a new book, let user specify
2113 * book options, since they affect how transactions are created */
2114 if (new_book)
2115 new_book = gnc_new_book_option_display (GTK_WIDGET(csv_imp_asst));
2116
2117 /* Add the Cancel button for the matcher */
2118 cancel_button = gtk_button_new_with_mnemonic (_("_Cancel"));
2119 gtk_assistant_add_action_widget (csv_imp_asst, cancel_button);
2120 auto button_area = gtk_widget_get_parent (cancel_button);
2121
2122 if (GTK_IS_HEADER_BAR(button_area))
2123 gtk_container_child_set (GTK_CONTAINER(button_area),
2124 cancel_button,
2125 "pack-type", GTK_PACK_START,
2126 nullptr);
2127
2128 g_signal_connect (cancel_button, "clicked",
2129 G_CALLBACK(csv_tximp_assist_close_cb), this);
2130 gtk_widget_show (GTK_WIDGET(cancel_button));
2131}
2132
2133
2134void
2135CsvImpTransAssist::assist_match_page_prepare ()
2136{
2137 /* Create transactions from the parsed data */
2138 try
2139 {
2140 tx_imp->create_transactions ();
2141 }
2142 catch (const GncCsvImpParseError& err)
2143 {
2144 /* Oops! This shouldn't happen when using the import assistant !
2145 * Inform the user and go back to the preview page.
2146 */
2147 auto err_msg = std::string(err.what());
2148 auto err_msgs = err.errors();
2149 auto add_bullet_item = [](std::string& a, ErrMap::value_type& b)->std::string { return std::move(a) + "\n• " + b.second; };
2150 err_msg = std::accumulate (err_msgs.begin(), err_msgs.end(), std::move (err_msg), add_bullet_item);
2151
2152 gnc_error_dialog (GTK_WINDOW (csv_imp_asst),
2153 _("An unexpected error has occurred while creating transactions. Please report this as a bug.\n\n"
2154 "Error message:\n%s"), err_msg.c_str());
2155 gtk_assistant_set_current_page (csv_imp_asst, 2);
2156 }
2157
2158 /* Block going back */
2159 gtk_assistant_commit (csv_imp_asst);
2160
2161 auto text = std::string( "<span size=\"medium\" color=\"red\"><b>");
2162 text += _("Double click on rows to change, then click on Apply to Import");
2163 text += "</b></span>";
2164 gtk_label_set_markup (GTK_LABEL(match_label), text.c_str());
2165
2166 /* Add the help button for the matcher */
2167 help_button = gtk_button_new_with_mnemonic (_("_Help"));
2168 gtk_assistant_add_action_widget (csv_imp_asst, help_button);
2169 auto button_area = gtk_widget_get_parent (help_button);
2170
2171 if (GTK_IS_HEADER_BAR(button_area))
2172 {
2173 gtk_container_child_set (GTK_CONTAINER(button_area),
2174 help_button,
2175 "pack-type", GTK_PACK_START,
2176 nullptr);
2177 }
2178 else
2179 {
2180 // align the help button on the left side
2181 gtk_widget_set_halign (GTK_WIDGET(button_area), GTK_ALIGN_FILL);
2182 gtk_widget_set_hexpand (GTK_WIDGET(button_area), TRUE);
2183 gtk_box_set_child_packing (GTK_BOX(button_area), help_button,
2184 FALSE, FALSE, 0, GTK_PACK_START);
2185 }
2186 g_signal_connect (help_button, "clicked",
2187 G_CALLBACK(on_matcher_help_clicked), gnc_csv_importer_gui);
2188
2189 gtk_widget_show (GTK_WIDGET(help_button));
2190
2191 /* Copy all of the transactions to the importer GUI. */
2192 for (auto trans_it : tx_imp->m_transactions)
2193 {
2194 auto draft_trans = trans_it.second;
2195 if (draft_trans->trans)
2196 {
2197 auto lsplit = GNCImportLastSplitInfo {
2198 draft_trans->m_price ? static_cast<gnc_numeric>(*draft_trans->m_price) : gnc_numeric{0, 0},
2199 draft_trans->m_taction ? draft_trans->m_taction->c_str() : nullptr,
2200 draft_trans->m_tmemo ? draft_trans->m_tmemo->c_str() : nullptr,
2201 draft_trans->m_tamount ? static_cast<gnc_numeric>(*draft_trans->m_tamount) : gnc_numeric{0, 0},
2202 draft_trans->m_taccount ? *draft_trans->m_taccount : nullptr,
2203 draft_trans->m_trec_state ? *draft_trans->m_trec_state : '\0',
2204 draft_trans->m_trec_date ? static_cast<time64>(GncDateTime(*draft_trans->m_trec_date, DayPart::neutral)) : 0,
2205 };
2206
2207//A tramsaction with no splits is invalid and will crash later.
2208 if (xaccTransGetSplit(draft_trans->trans, 0))
2209 gnc_gen_trans_list_add_trans_with_split_data (gnc_csv_importer_gui, std::move (draft_trans->trans),
2210 &lsplit);
2211 else
2212 xaccTransDestroy(draft_trans->trans);
2213 draft_trans->trans = nullptr;
2214 }
2215 }
2216 /* Show the matcher dialog */
2217 gnc_gen_trans_list_show_all (gnc_csv_importer_gui);
2218}
2219
2220
2221void
2222CsvImpTransAssist::assist_summary_page_prepare ()
2223{
2224 /* Remove the added buttons */
2225 gtk_assistant_remove_action_widget (csv_imp_asst, help_button);
2226 gtk_assistant_remove_action_widget (csv_imp_asst, cancel_button);
2227
2228 auto text = std::string("<span size=\"medium\"><b>");
2229 try
2230 {
2231 /* Translators: {1} will be replaced with a filename */
2232 text += (bl::format (std::string{_("The transactions were imported from file '{1}'.")}) % m_final_file_name).str();
2233 text += "</b></span>";
2234 }
2235 catch (const bl::conv::conversion_error& err)
2236 {
2237 PERR("Transcoding error: %s", err.what());
2238 text += "The transactions were imported from the file.</b></span>";
2239 }
2240 catch (const bl::conv::invalid_charset_error& err)
2241 {
2242 PERR("Invalid charset error: %s", err.what());
2243 text += "The transactions were imported from the file.</b></span>";
2244 }
2245 gtk_label_set_markup (GTK_LABEL(summary_label), text.c_str());
2246}
2247
2248
2249void
2250CsvImpTransAssist::assist_prepare_cb (GtkWidget *page)
2251{
2252 if (page == file_page)
2253 assist_file_page_prepare ();
2254 else if (page == preview_page)
2255 assist_preview_page_prepare ();
2256 else if (page == account_match_page)
2257 assist_account_match_page_prepare ();
2258 else if (page == doc_page)
2259 assist_doc_page_prepare ();
2260 else if (page == match_page)
2261 assist_match_page_prepare ();
2262 else if (page == summary_page)
2263 assist_summary_page_prepare ();
2264}
2265
2266
2267void
2268CsvImpTransAssist::assist_finish ()
2269{
2270 /* Start the import */
2271 if (!tx_imp->m_transactions.empty())
2272 {
2273 /* The call to gnc_gen_trans_assist_start below will free the
2274 * object passed into it. To avoid our c++ destructor from
2275 * attempting a second free on that object, we'll release
2276 * our own reference to it here before passing it to
2277 * gnc_gen_trans_assist_start.
2278 */
2279 auto local_csv_imp_gui = gnc_csv_importer_gui;
2280 gnc_csv_importer_gui = nullptr;
2281 gnc_gen_trans_assist_start (local_csv_imp_gui);
2282 }
2283}
2284
2285
2286void
2287CsvImpTransAssist::assist_compmgr_close ()
2288{
2289 gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(csv_imp_asst));
2290}
2291
2292
2293static void
2294csv_tximp_close_handler (gpointer user_data)
2295{
2296 auto info = (CsvImpTransAssist*)user_data;
2297 gnc_unregister_gui_component_by_data (ASSISTANT_CSV_IMPORT_TRANS_CM_CLASS, info);
2298 info->assist_compmgr_close();
2299 delete info;
2300}
2301
2302/********************************************************************\
2303 * gnc_file_csv_trans_import *
2304 * opens up a assistant to import accounts. *
2305 * *
2306 * Args: import_type *
2307 * Return: nothing *
2308\********************************************************************/
2309void
2311{
2312 auto info = new CsvImpTransAssist;
2313 gnc_register_gui_component (ASSISTANT_CSV_IMPORT_TRANS_CM_CLASS,
2314 nullptr, csv_tximp_close_handler,
2315 info);
2316}
void gnc_file_csv_trans_import(void)
The gnc_file_csv_trans_import() will let the user import the account tree or transactions to a delimi...
CSV Import Assistant.
void preview_update_separators(GtkWidget *widget)
Event handler for separator changes.
void preview_update_file_format()
Event handler for clicking one of the format type radio buttons.
void preview_update_encoding(const char *encoding)
Event handler for a new encoding.
void preview_update_fw_columns(GtkTreeView *treeview, GdkEventButton *event)
Event handler for clicking on column headers.
void preview_update_col_type(GtkComboBox *cbox)
Event handler for the user selecting a new column type.
GnuCash DateTime class.
GnuCash Date class.
The actual TxImport class It's intended to use in the following sequence of actions:
const preset_vec_trans & get_import_presets_trans(void)
Creates a vector of CsvTransImpSettings which combines.
CSV Import Settings.
bool preset_is_reserved_name(const std::string &name)
Check whether name can be used as a preset name.
Class to import transactions from CSV or fixed width files.
Functions to load, save and get gui state.
Class to convert a csv file into vector of string vectors.
Class convert a file with fixed with delimited contents into vector of string vectors.
utility functions for the GnuCash UI
Utility functions for convert uri in separate components and back.
gchar * gnc_account_get_full_name(const Account *account)
The gnc_account_get_full_name routine returns the fully qualified name of the account using the given...
Definition Account.cpp:3305
const gchar * gnc_get_account_separator_string(void)
Returns the account separation character chosen by the user.
Definition Account.cpp:205
@ ACCT_TYPE_NONE
Not a type.
Definition Account.h:105
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
void xaccTransDestroy(Transaction *trans)
Destroys a transaction.
Split * xaccTransGetSplit(const Transaction *trans, int i)
Return a pointer to the indexed split in this transaction's split list.
Account * gnc_account_lookup_by_full_name(const Account *any_acc, const gchar *name)
The gnc_account_lookup_full_name() subroutine works like gnc_account_lookup_by_name,...
Definition Account.cpp:3163
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
GNCImportMainMatcher * gnc_gen_trans_assist_new(GtkWidget *parent, GtkWidget *assistant_page, const gchar *heading, bool all_from_same_account, gint match_date_hardlimit)
Add the Transaction matcher to an existing page of an assistant.
void gnc_gen_trans_list_show_all(GNCImportMainMatcher *info)
Shows widgets.
void gnc_gen_trans_list_add_trans_with_split_data(GNCImportMainMatcher *gui, Transaction *trans, GNCImportLastSplitInfo *lsplit)
Add a newly imported Transaction to the Transaction Importer.
void gnc_gen_trans_assist_start(GNCImportMainMatcher *info)
This starts the import process for transaction from an assistant.
Account * gnc_import_select_account(GtkWidget *parent, const gchar *account_online_id_value, gboolean prompt_on_no_match, const gchar *account_human_description, const gnc_commodity *new_account_default_commodity, GNCAccountType new_account_default_type, Account *default_selection, gboolean *ok_pressed)
Must be called with a string containing a unique identifier for the account.
void gnc_gen_trans_list_delete(GNCImportMainMatcher *info)
Deletes the given object.
void on_matcher_help_clicked(GtkButton *button, gpointer user_data)
This allows for the transaction help dialog to be started from the assistant button callback.
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
Generic and very flexible account matcher/picker.
Generic importer backend interface.
Transaction matcher main window.
STRUCTS.
void remove(void)
Remove the preset from the state file.
Exception that will be thrown whenever a parsing error is encountered.