GnuCash  5.6-150-g038405b370+
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 
53 #include "import-account-matcher.h"
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 
77 namespace 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. */
84 static QofLogModule log_module = GNC_MOD_ASSISTANT;
85 
86 enum 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 {
154 public:
156  ~CsvImpTransAssist ();
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);
208 private:
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 
287 extern "C"
288 {
289 void csv_tximp_assist_prepare_cb (GtkAssistant *assistant, GtkWidget *page, CsvImpTransAssist* info);
290 void csv_tximp_assist_close_cb (GtkAssistant *gtkassistant, CsvImpTransAssist* info);
291 void csv_tximp_assist_finish_cb (GtkAssistant *gtkassistant, CsvImpTransAssist* info);
292 void csv_tximp_file_activated_cb (GtkFileChooser *chooser, CsvImpTransAssist *info);
293 void csv_tximp_file_selection_changed_cb (GtkFileChooser *chooser, CsvImpTransAssist *info);
294 void csv_tximp_preview_del_settings_cb (GtkWidget *button, CsvImpTransAssist *info);
295 void csv_tximp_preview_save_settings_cb (GtkWidget *button, CsvImpTransAssist *info);
296 void csv_tximp_preview_settings_sel_changed_cb (GtkComboBox *combo, CsvImpTransAssist *info);
297 void csv_tximp_preview_settings_text_inserted_cb (GtkEditable *entry, gchar *new_text,
298  gint new_text_length, gint *position, CsvImpTransAssist *info);
299 void csv_tximp_preview_settings_text_changed_cb (GtkEntry *entry, CsvImpTransAssist *info);
300 void csv_tximp_preview_srow_cb (GtkSpinButton *spin, CsvImpTransAssist *info);
301 void csv_tximp_preview_erow_cb (GtkSpinButton *spin, CsvImpTransAssist *info);
302 void csv_tximp_preview_skiprows_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info);
303 void csv_tximp_preview_skiperrors_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info);
304 void csv_tximp_preview_multisplit_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info);
305 void csv_tximp_preview_sep_button_cb (GtkWidget* widget, CsvImpTransAssist* info);
306 void csv_tximp_preview_sep_fixed_sel_cb (GtkToggleButton* csv_button, CsvImpTransAssist* info);
307 void csv_tximp_preview_acct_sel_cb (GtkWidget* widget, CsvImpTransAssist* info);
308 void csv_tximp_preview_enc_sel_cb (GOCharmapSel* selector, const char* encoding,
309  CsvImpTransAssist* info);
310 void csv_tximp_acct_match_button_clicked_cb (GtkWidget *widget, CsvImpTransAssist* info);
311 bool csv_tximp_acct_match_view_clicked_cb (GtkWidget *widget, GdkEventButton *event, CsvImpTransAssist* info);
312 }
313 
314 void
315 csv_tximp_assist_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
316  CsvImpTransAssist* info)
317 {
318  info->assist_prepare_cb(page);
319 }
320 
321 void
322 csv_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 
327 void
328 csv_tximp_assist_finish_cb (GtkAssistant *assistant, CsvImpTransAssist* info)
329 {
330  info->assist_finish ();
331 }
332 
333 void csv_tximp_file_activated_cb (GtkFileChooser *chooser, CsvImpTransAssist *info)
334 {
335  info->file_activated_cb();
336 }
337 
338 void csv_tximp_file_selection_changed_cb (GtkFileChooser *chooser, CsvImpTransAssist *info)
339 {
340  info->file_selection_changed_cb();
341 }
342 
343 void csv_tximp_preview_del_settings_cb (GtkWidget *button, CsvImpTransAssist *info)
344 {
345  info->preview_settings_delete();
346 }
347 
348 void csv_tximp_preview_save_settings_cb (GtkWidget *button, CsvImpTransAssist *info)
349 {
350  info->preview_settings_save();
351 }
352 
353 void csv_tximp_preview_settings_sel_changed_cb (GtkComboBox *combo, CsvImpTransAssist *info)
354 {
355  info->preview_settings_load();
356 }
357 
358 void
359 csv_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 
379 void
380 csv_tximp_preview_settings_text_changed_cb (GtkEntry *entry, CsvImpTransAssist *info)
381 {
382  info->preview_settings_name(entry);
383 }
384 
385 void csv_tximp_preview_srow_cb (GtkSpinButton *spin, CsvImpTransAssist *info)
386 {
387  info->preview_update_skipped_rows();
388 }
389 
390 void csv_tximp_preview_erow_cb (GtkSpinButton *spin, CsvImpTransAssist *info)
391 {
392  info->preview_update_skipped_rows();
393 }
394 
395 void csv_tximp_preview_skiprows_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info)
396 {
397  info->preview_update_skipped_rows();
398 }
399 
400 void csv_tximp_preview_skiperrors_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info)
401 {
402  info->preview_update_skipped_rows();
403 }
404 
405 void csv_tximp_preview_multisplit_cb (GtkToggleButton *checkbox, CsvImpTransAssist *info)
406 {
407  info->preview_multi_split (gtk_toggle_button_get_active (checkbox));
408 }
409 
410 void csv_tximp_preview_sep_button_cb (GtkWidget* widget, CsvImpTransAssist* info)
411 {
412  info->preview_update_separators(widget);
413 }
414 
415 void csv_tximp_preview_sep_fixed_sel_cb (GtkToggleButton* csv_button, CsvImpTransAssist* info)
416 {
417  info->preview_update_file_format();
418 }
419 
420 void csv_tximp_preview_acct_sel_cb (GtkWidget* widget, CsvImpTransAssist* info)
421 {
422  info->preview_update_account();
423 }
424 
425 void csv_tximp_preview_enc_sel_cb (GOCharmapSel* selector, const char* encoding,
426  CsvImpTransAssist* info)
427 {
428  info->preview_update_encoding(encoding);
429 }
430 
431 static void csv_tximp_preview_date_fmt_sel_cb (GtkComboBox* format_selector, CsvImpTransAssist* info)
432 {
433  info->preview_update_date_format();
434 }
435 
436 static void csv_tximp_preview_currency_fmt_sel_cb (GtkComboBox* format_selector, CsvImpTransAssist* info)
437 {
438  info->preview_update_currency_format();
439 }
440 
441 static void csv_tximp_preview_col_type_changed_cb (GtkComboBox* cbox, CsvImpTransAssist* info)
442 {
443  info->preview_update_col_type (cbox);
444 }
445 
446 static bool
447 csv_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 
455 void csv_tximp_acct_match_button_clicked_cb (GtkWidget *widget, CsvImpTransAssist* info)
456 {
457  info->acct_match_via_button();
458 }
459 
460 bool 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  *******************************************************/
469 CsvImpTransAssist::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  *******************************************************/
696 CsvImpTransAssist::~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  */
713 bool
714 CsvImpTransAssist::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  */
743 void
744 CsvImpTransAssist::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  */
760 void
761 CsvImpTransAssist::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  */
775 void 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  */
800 void 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 
830 void
831 CsvImpTransAssist::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  */
847 void
848 CsvImpTransAssist::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  */
874 void
875 CsvImpTransAssist::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  */
900 void
901 CsvImpTransAssist::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  */
966 void 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 
986 void 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 
1103 void 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 
1116 void
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 
1145 void
1146 CsvImpTransAssist::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 
1153 void
1154 CsvImpTransAssist::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 
1160 static gboolean
1161 csv_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  */
1170 enum 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  */
1174 enum 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 
1234 enum
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 
1243 static 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 
1270 uint32_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 
1290 gboolean
1291 fixed_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 
1331 void
1332 CsvImpTransAssist::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 /*======================================================================*/
1358 void
1359 CsvImpTransAssist::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 
1381 void
1382 CsvImpTransAssist::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 */
1420 void
1421 CsvImpTransAssist::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  */
1465 GtkWidget*
1466 CsvImpTransAssist::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 
1499 void
1500 CsvImpTransAssist::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 */
1541 static GtkTreeModel*
1542 make_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  */
1565 void 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  */
1661 void
1662 CsvImpTransAssist::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  */
1762 void 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  */
1788 void 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 
1803 static void
1804 csv_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 
1837 static bool
1838 csv_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  */
1864 static std::string
1865 csv_tximp_acct_match_text_parse (std::string acct_name)
1866 {
1867  auto sep = gnc_get_account_separator_string ();
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 
1896 void
1897 CsvImpTransAssist::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 
1952 void
1953 CsvImpTransAssist::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 */
1965 bool
1966 CsvImpTransAssist::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 
1998 void
1999 CsvImpTransAssist::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 
2020 void
2021 CsvImpTransAssist::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 }
2070 void
2071 CsvImpTransAssist::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 
2097 void
2098 CsvImpTransAssist::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 
2134 void
2135 CsvImpTransAssist::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 
2221 void
2222 CsvImpTransAssist::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 
2249 void
2250 CsvImpTransAssist::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 
2267 void
2268 CsvImpTransAssist::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 
2286 void
2287 CsvImpTransAssist::assist_compmgr_close ()
2288 {
2289  gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(csv_imp_asst));
2290 }
2291 
2292 
2293 static void
2294 csv_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 \********************************************************************/
2309 void
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 }
CSV Import Settings.
Functions to load, save and get gui state.
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...
GnuCash DateTime class.
Split * xaccTransGetSplit(const Transaction *trans, int i)
Return a pointer to the indexed split in this transaction&#39;s split list.
void gnc_gen_trans_list_show_all(GNCImportMainMatcher *info)
Shows widgets.
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
utility functions for the GnuCash UI
STRUCTS.
CSV Import Assistant.
Class to convert a csv file into vector of string vectors.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
Exception that will be thrown whenever a parsing error is encountered.
Generic importer backend interface.
void remove(void)
Remove the preset from the state file.
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...
Transaction matcher main window.
void preview_update_separators(GtkWidget *widget)
Event handler for separator changes.
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
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.
Class to import transactions from CSV or fixed width files.
Generic and very flexible account matcher/picker.
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.
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
void preview_update_encoding(const char *encoding)
Event handler for a new encoding.
void xaccTransDestroy(Transaction *trans)
Destroys a transaction.
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
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.
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
bool preset_is_reserved_name(const std::string &name)
Check whether name can be used as a preset name.
void gnc_gen_trans_assist_start(GNCImportMainMatcher *info)
This starts the import process for transaction from an assistant.
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, but uses fully-qualified names using the given separator.
Definition: Account.cpp:3163
The actual TxImport class It&#39;s intended to use in the following sequence of actions: ...
Class convert a file with fixed with delimited contents into vector of string vectors.
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.
const preset_vec_trans & get_import_presets_trans(void)
Creates a vector of CsvTransImpSettings which combines.
void gnc_gen_trans_list_delete(GNCImportMainMatcher *info)
Deletes the given object.
Utility functions for convert uri in separate components and back.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
Not a type.
Definition: Account.h:105
static const std::vector< GncDateFormat > c_formats
A vector with all the date formats supported by the string constructor.
const gchar * gnc_get_account_separator_string(void)
Returns the account separation character chosen by the user.
Definition: Account.cpp:205
void preview_update_file_format()
Event handler for clicking one of the format type radio buttons.