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