GnuCash  5.6-150-g038405b370+
assistant-csv-price-import.cpp
Go to the documentation of this file.
1 /*******************************************************************\
2  * assistant-csv-price-import.cpp -- An assistant for importing *
3  * Prices from a file. *
4  * *
5  * Copyright (C) 2017 Robert Fewell *
6  * *
7  * This program is free software; you can redistribute it and/or *
8  * modify it under the terms of the GNU General Public License as *
9  * published by the Free Software Foundation; either version 2 of *
10  * the License, or (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License*
18  * along with this program; if not, contact: *
19  * *
20  * Free Software Foundation Voice: +1-617-542-5942 *
21  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
22  * Boston, MA 02110-1301, USA gnu@gnu.org *
23 \********************************************************************/
30 #include <guid.hpp>
31 
32 #include "config.h"
33 
34 #include <gtk/gtk.h>
35 #include <glib/gi18n.h>
36 #include <stdlib.h>
37 #include <cstdint>
38 
39 #include "gnc-ui.h"
40 #include "gnc-uri-utils.h"
41 #include "gnc-ui-util.h"
42 #include "dialog-utils.h"
43 
44 #include "gnc-component-manager.h"
45 
46 #include "gnc-state.h"
47 
49 
50 #include "gnc-csv-gnumeric-popup.h"
51 #include "go-charmap-sel.h"
52 
53 #include <algorithm>
54 #include <exception>
55 #include <iostream>
56 #include <memory>
57 #include <string>
58 #include <tuple>
59 
61 #include "gnc-import-price.hpp"
62 #include "gnc-tokenizer-fw.hpp"
63 #include "gnc-tokenizer-csv.hpp"
64 
65 #define MIN_COL_WIDTH 70
66 #define GNC_PREFS_GROUP "dialogs.import.csv"
67 #define ASSISTANT_CSV_IMPORT_PRICE_CM_CLASS "assistant-csv-price-import"
68 
69 /* This static indicates the debugging module that this .o belongs to. */
70 static QofLogModule log_module = GNC_MOD_ASSISTANT;
71 
72 /* Note on memory management
73  *
74  * The same notes as for assistant-csv-trans-import.cpp apply to
75  * this assistant as well. Please read the note at the top of that
76  * file to understand important details about the use of several
77  * memory management models in one file.
78  */
79 
81 {
82 public:
85 
86  /* Delete copy and move constructor/assignments
87  * We don't want gui elements to be moved around or copied at all */
88  CsvImpPriceAssist(const CsvImpPriceAssist&) = delete; // copy constructor
89  CsvImpPriceAssist& operator=(const CsvImpPriceAssist&) = delete; // copy assignment
90  CsvImpPriceAssist(CsvImpPriceAssist&&) = delete; // move constructor
91  CsvImpPriceAssist& operator=(CsvImpPriceAssist&&) = delete; // move assignment
92 
93  void assist_prepare_cb (GtkWidget *page);
94  void assist_file_page_prepare ();
95  void assist_preview_page_prepare ();
96  void assist_confirm_page_prepare ();
97  void assist_summary_page_prepare ();
98  void assist_finish ();
99  void assist_compmgr_close ();
100 
101  void file_activated_cb ();
102  void file_selection_changed_cb ();
103 
104  void preview_settings_delete ();
105  void preview_settings_save ();
106  void preview_settings_name (GtkEntry* entry);
107  void preview_settings_load ();
108  void preview_update_skipped_rows ();
109  void preview_over_write (bool over);
110  void preview_update_separators (GtkWidget* widget);
112  void preview_update_encoding (const char* encoding);
113  void preview_update_date_format ();
114  void preview_update_currency_format ();
115  void preview_update_currency ();
116  void preview_update_commodity ();
117  void preview_reparse_col_type (GncPricePropType type);
118  void preview_update_col_type (GtkComboBox* cbox);
119  void preview_update_fw_columns (GtkTreeView* treeview, GdkEventButton* event);
120 
121  void preview_populate_settings_combo();
122  void preview_handle_save_del_sensitivity (GtkComboBox* combo);
123  void preview_split_column (int col, int offset);
124  void preview_refresh_table ();
125  void preview_refresh ();
126  void preview_validate_settings ();
127 
128  friend gboolean
129  fixed_context_menu_handler_price (GnumericPopupMenuElement const *element,
130  gpointer userdata);
131 private:
132  /* helper functions to manage the context menu for fixed with columns */
133  uint32_t get_new_col_rel_pos (GtkTreeViewColumn *tcol, int dx);
134  void fixed_context_menu (GdkEventButton *event, int col, int dx);
135  /* helper function to calculate row colors for the preview table (to visualize status) */
136  void preview_row_fill_state_cells (GtkListStore *store, GtkTreeIter *iter,
137  std::string& err_msg, bool skip);
138  /* helper function to create preview header cell combo boxes listing available column types */
139  GtkWidget* preview_cbox_factory (GtkTreeModel* model, uint32_t colnum);
140  /* helper function to set rendering parameters for preview data columns */
141  void preview_style_column (uint32_t col_num, GtkTreeModel* model);
142  /* helper function to check for a valid filename as opposed to a directory */
143  bool check_for_valid_filename ();
144 
145  GtkAssistant *csv_imp_asst;
146 
147  GtkWidget *file_page;
148  GtkWidget *file_chooser;
149  std::string m_fc_file_name;
150  std::string m_final_file_name;
152  GtkWidget *preview_page;
153  GtkComboBox *settings_combo;
154  GtkWidget *save_button;
155  GtkWidget *del_button;
157  GtkWidget *combo_hbox;
158  GtkSpinButton *start_row_spin;
159  GtkSpinButton *end_row_spin;
160  GtkWidget *skip_alt_rows_button;
161  GtkWidget *skip_errors_button;
162  GtkWidget *csv_button;
163  GtkWidget *fixed_button;
164  GtkWidget *over_write_cbutton;
165  GtkWidget *commodity_selector;
166  GtkWidget *currency_selector;
167  GOCharmapSel *encselector;
168  GtkWidget *separator_table;
169  GtkCheckButton *sep_button[SEP_NUM_OF_TYPES];
170  GtkWidget *fw_instructions_hbox;
171  GtkCheckButton *custom_cbutton;
172  GtkEntry *custom_entry;
173  GtkComboBoxText *date_format_combo;
174  GtkComboBoxText *currency_format_combo;
175  GtkTreeView *treeview;
176  GtkLabel *instructions_label;
177  GtkImage *instructions_image;
178  bool encoding_selected_called;
180  int fixed_context_col;
181  int fixed_context_offset;
184  GtkWidget *confirm_page;
186  GtkWidget *summary_page;
187  GtkWidget *summary_label;
189  std::unique_ptr<GncPriceImport> price_imp;
190 };
191 
192 
193 /*******************************************************
194  * Assistant call back functions
195  *******************************************************/
196 
197 extern "C"
198 {
199 void csv_price_imp_assist_prepare_cb (GtkAssistant *assistant, GtkWidget *page, CsvImpPriceAssist* info);
200 void csv_price_imp_assist_close_cb (GtkAssistant *gtkassistant, CsvImpPriceAssist* info);
201 void csv_price_imp_assist_finish_cb (GtkAssistant *gtkassistant, CsvImpPriceAssist* info);
202 void csv_price_imp_file_activated_changed_cb (GtkFileChooser *chooser, CsvImpPriceAssist *info);
203 void csv_price_imp_file_selection_changed_cb (GtkFileChooser *chooser, CsvImpPriceAssist *info);
204 void csv_price_imp_preview_del_settings_cb (GtkWidget *button, CsvImpPriceAssist *info);
205 void csv_price_imp_preview_save_settings_cb (GtkWidget *button, CsvImpPriceAssist *info);
206 void csv_price_imp_preview_settings_sel_changed_cb (GtkComboBox *combo, CsvImpPriceAssist *info);
207 void csv_price_imp_preview_settings_text_inserted_cb (GtkEditable *entry, gchar *new_text,
208  gint new_text_length, gint *position, CsvImpPriceAssist *info);
209 void csv_price_imp_preview_settings_text_changed_cb (GtkEntry *entry, CsvImpPriceAssist *info);
210 void csv_price_imp_preview_srow_cb (GtkSpinButton *spin, CsvImpPriceAssist *info);
211 void csv_price_imp_preview_erow_cb (GtkSpinButton *spin, CsvImpPriceAssist *info);
212 void csv_price_imp_preview_skiprows_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info);
213 void csv_price_imp_preview_skiperrors_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info);
214 void csv_price_imp_preview_overwrite_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info);
215 void csv_price_imp_preview_sep_button_cb (GtkWidget* widget, CsvImpPriceAssist* info);
216 void csv_price_imp_preview_sep_fixed_sel_cb (GtkToggleButton* csv_button, CsvImpPriceAssist* info);
217 void csv_price_imp_preview_acct_sel_cb (GtkWidget* widget, CsvImpPriceAssist* info);
218 void csv_price_imp_preview_enc_sel_cb (GOCharmapSel* selector, const char* encoding,
219  CsvImpPriceAssist* info);
220 }
221 
222 void
223 csv_price_imp_assist_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
224  CsvImpPriceAssist* info)
225 {
226  info->assist_prepare_cb(page);
227 }
228 
229 void
230 csv_price_imp_assist_close_cb (GtkAssistant *assistant, CsvImpPriceAssist* info)
231 {
232  gnc_close_gui_component_by_data (ASSISTANT_CSV_IMPORT_PRICE_CM_CLASS, info);
233 }
234 
235 void
236 csv_price_imp_assist_finish_cb (GtkAssistant *assistant, CsvImpPriceAssist* info)
237 {
238  info->assist_finish ();
239 }
240 
241 void csv_price_imp_file_activated_changed_cb (GtkFileChooser *chooser, CsvImpPriceAssist *info)
242 {
243  info->file_activated_cb();
244 }
245 
246 void csv_price_imp_file_selection_changed_cb (GtkFileChooser *chooser, CsvImpPriceAssist *info)
247 {
248  info->file_selection_changed_cb();
249 }
250 
251 void csv_price_imp_preview_del_settings_cb (GtkWidget *button, CsvImpPriceAssist *info)
252 {
253  info->preview_settings_delete();
254 }
255 
256 void csv_price_imp_preview_save_settings_cb (GtkWidget *button, CsvImpPriceAssist *info)
257 {
258  info->preview_settings_save();
259 }
260 
261 void csv_price_imp_preview_settings_sel_changed_cb (GtkComboBox *combo, CsvImpPriceAssist *info)
262 {
263  info->preview_settings_load();
264 }
265 
266 void
267 csv_price_imp_preview_settings_text_inserted_cb (GtkEditable *entry, gchar *new_text,
268  gint new_text_length, gint *position, CsvImpPriceAssist *info)
269 {
270  if (!new_text)
271  return;
272 
273  /* Prevent entering [], which are invalid characters in key files */
274  auto base_txt = std::string (new_text);
275  auto mod_txt = base_txt;
276  std::replace (mod_txt.begin(), mod_txt.end(), '[', '(');
277  std::replace (mod_txt.begin(), mod_txt.end(), ']', ')');
278  if (base_txt == mod_txt)
279  return;
280  g_signal_handlers_block_by_func (entry, (gpointer) csv_price_imp_preview_settings_text_inserted_cb, info);
281  gtk_editable_insert_text (entry, mod_txt.c_str(), mod_txt.size() , position);
282  g_signal_handlers_unblock_by_func (entry, (gpointer) csv_price_imp_preview_settings_text_inserted_cb, info);
283 
284  g_signal_stop_emission_by_name (entry, "insert_text");
285 }
286 
287 void
288 csv_price_imp_preview_settings_text_changed_cb (GtkEntry *entry, CsvImpPriceAssist *info)
289 {
290  info->preview_settings_name(entry);
291 }
292 
293 void csv_price_imp_preview_srow_cb (GtkSpinButton *spin, CsvImpPriceAssist *info)
294 {
295  info->preview_update_skipped_rows();
296 }
297 
298 void csv_price_imp_preview_erow_cb (GtkSpinButton *spin, CsvImpPriceAssist *info)
299 {
300  info->preview_update_skipped_rows();
301 }
302 
303 void csv_price_imp_preview_skiprows_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info)
304 {
305  info->preview_update_skipped_rows();
306 }
307 
308 void csv_price_imp_preview_skiperrors_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info)
309 {
310  info->preview_update_skipped_rows();
311 }
312 
313 void csv_price_imp_preview_overwrite_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info)
314 {
315  info->preview_over_write (gtk_toggle_button_get_active (checkbox));
316 }
317 
318 void csv_price_imp_preview_sep_button_cb (GtkWidget* widget, CsvImpPriceAssist* info)
319 {
320  info->preview_update_separators(widget);
321 }
322 
323 void csv_price_imp_preview_sep_fixed_sel_cb (GtkToggleButton* csv_button, CsvImpPriceAssist* info)
324 {
325  info->preview_update_file_format();
326 }
327 
328 void csv_price_imp_preview_enc_sel_cb (GOCharmapSel* selector, const char* encoding,
329  CsvImpPriceAssist* info)
330 {
331  info->preview_update_encoding(encoding);
332 }
333 
334 static void csv_price_imp_preview_date_fmt_sel_cb (GtkComboBox* format_selector, CsvImpPriceAssist* info)
335 {
336  info->preview_update_date_format();
337 }
338 
339 static void csv_price_imp_preview_currency_fmt_sel_cb (GtkComboBox* format_selector, CsvImpPriceAssist* info)
340 {
341  info->preview_update_currency_format();
342 }
343 
344 enum GncCommColumn {DISPLAYED_COMM, SORT_COMM, COMM_PTR, SEP};
345 
346 static void csv_price_imp_preview_currency_sel_cb (GtkComboBox* currency_selector, CsvImpPriceAssist* info)
347 {
348  info->preview_update_currency();
349 }
350 
351 static gboolean separator_row_func (GtkTreeModel *smodel, GtkTreeIter *siter, gpointer data)
352 {
353  gboolean sep_row;
354  GtkTreeModel *store;
355  GtkTreeIter iter;
356 
357  store = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT(smodel));
358 
359  gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT(smodel),
360  &iter, siter);
361 
362  gtk_tree_model_get (GTK_TREE_MODEL(store), &iter, SEP, &sep_row, -1);
363 
364  return sep_row;
365 }
366 
367 static void csv_price_imp_preview_commodity_sel_cb (GtkComboBox* commodity_selector, CsvImpPriceAssist* info)
368 {
369  info->preview_update_commodity();
370 }
371 
372 static void csv_price_imp_preview_col_type_changed_cb (GtkComboBox* cbox, CsvImpPriceAssist* info)
373 {
374  info->preview_update_col_type (cbox);
375 }
376 
377 static gboolean
378 csv_price_imp_preview_treeview_clicked_cb (GtkTreeView* treeview, GdkEventButton* event,
379  CsvImpPriceAssist* info)
380 {
381  info->preview_update_fw_columns(treeview, event);
382  return false;
383 }
384 
385 static
386 gnc_commodity *get_commodity_from_combo (GtkComboBox *combo)
387 {
388  GtkTreeModel *model, *sort_model;
389  GtkTreeIter iter, siter;
390  gchar *string;
391  gnc_commodity *comm;
392 
393  if (!gtk_combo_box_get_active_iter (combo, &siter))
394  return nullptr;
395 
396  sort_model = gtk_combo_box_get_model (combo);
397  model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT(sort_model));
398 
399  gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT(sort_model),
400  &iter, &siter);
401 
402  gtk_tree_model_get (GTK_TREE_MODEL(model), &iter,
403  DISPLAYED_COMM, &string, COMM_PTR, &comm, -1);
404 
405  PINFO("Commodity string is %s", string);
406 
407  g_free (string);
408  return comm;
409 }
410 
411 static void
412 set_commodity_for_combo (GtkComboBox *combo, gnc_commodity *comm)
413 {
414  GtkTreeModel *model, *sort_model;
415  GtkTreeIter iter, siter;
416  gnc_commodity *model_comm;
417  gboolean valid;
418 
419  sort_model = gtk_combo_box_get_model (combo);
420  model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT(sort_model));
421  valid = gtk_tree_model_get_iter_first (model, &iter);
422 
423  while (valid)
424  {
425  gtk_tree_model_get (model, &iter, COMM_PTR, &model_comm, -1);
426  if (model_comm == comm)
427  {
428  if (gtk_tree_model_sort_convert_child_iter_to_iter (GTK_TREE_MODEL_SORT(sort_model), &siter, &iter))
429  {
430  gtk_combo_box_set_active_iter (combo, &siter);
431  return;
432  }
433  }
434  /* Make iter point to the next row in the list store */
435  valid = gtk_tree_model_iter_next (model, &iter);
436  }
437  // Not found, set it to first iter
438  gtk_tree_model_get_iter_first (model, &iter);
439  if (gtk_tree_model_sort_convert_child_iter_to_iter (GTK_TREE_MODEL_SORT(sort_model), &siter, &iter))
440  gtk_combo_box_set_active_iter (combo, &siter);
441 }
442 
443 static
444 GtkTreeModel *get_model (bool all_commodity)
445 {
446  GtkTreeModel *store, *model;
447  const gnc_commodity_table *commodity_table = gnc_get_current_commodities ();
448  gnc_commodity *tmp_commodity = nullptr;
449  char *tmp_namespace = nullptr;
450  GList *namespace_list = gnc_commodity_table_get_namespaces (commodity_table);
451  GtkTreeIter iter;
452 
453  store = GTK_TREE_MODEL(gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING,
454  G_TYPE_POINTER, G_TYPE_BOOLEAN));
455  model = gtk_tree_model_sort_new_with_model (store);
456  // set sort order
457  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(model), SORT_COMM, GTK_SORT_ASCENDING);
458 
459  gtk_list_store_append (GTK_LIST_STORE(store), &iter);
460  gtk_list_store_set (GTK_LIST_STORE(store), &iter,
461  DISPLAYED_COMM, " ", SORT_COMM, " ", COMM_PTR, nullptr, SEP, false, -1);
462 
463  for (auto node = namespace_list; node; node = g_list_next (node))
464  {
465  tmp_namespace = (char*)node->data;
466  DEBUG("Looking at namespace %s", tmp_namespace);
467 
468  /* Hide the template entry */
469  if (g_utf8_collate (tmp_namespace, "template" ) != 0)
470  {
471  if ((g_utf8_collate (tmp_namespace, GNC_COMMODITY_NS_CURRENCY ) == 0) || (all_commodity == true))
472  {
473  auto comm_list = gnc_commodity_table_get_commodities (commodity_table, tmp_namespace);
474 
475  // if this is the CURRENCY, add a row to be identified as a separator row
476  if ((g_utf8_collate (tmp_namespace, GNC_COMMODITY_NS_CURRENCY) == 0) && (all_commodity == true))
477  {
478  gtk_list_store_append (GTK_LIST_STORE(store), &iter);
479  gtk_list_store_set (GTK_LIST_STORE(store), &iter, DISPLAYED_COMM, " ",
480  SORT_COMM, "CURRENCY-", COMM_PTR, nullptr, SEP, true, -1);
481  }
482 
483  for (auto node = comm_list; node; node = g_list_next (node))
484  {
485  const gchar *name_str;
486  gchar *sort_str;
487  tmp_commodity = (gnc_commodity*)node->data;
488  DEBUG("Looking at commodity %s", gnc_commodity_get_fullname (tmp_commodity));
489 
490  name_str = gnc_commodity_get_printname (tmp_commodity);
491 
492  if (g_utf8_collate (tmp_namespace, GNC_COMMODITY_NS_CURRENCY) == 0)
493  sort_str = g_strconcat ("CURRENCY-", name_str, nullptr);
494  else
495  sort_str = g_strconcat ("ALL-OTHER-", name_str, nullptr);
496 
497  DEBUG("Name string is '%s', Sort string is '%s'", name_str, sort_str);
498 
499  gtk_list_store_append (GTK_LIST_STORE(store), &iter);
500  gtk_list_store_set (GTK_LIST_STORE(store), &iter, DISPLAYED_COMM, name_str,
501  SORT_COMM, sort_str, COMM_PTR, tmp_commodity, SEP, false, -1);
502 
503  g_free (sort_str);
504  }
505  g_list_free (comm_list);
506  }
507  }
508  }
509  g_list_free (namespace_list);
510  g_object_unref (store);
511 
512  return model;
513 }
514 
515 
516 /*******************************************************
517  * Assistant Constructor
518  *******************************************************/
519 CsvImpPriceAssist::CsvImpPriceAssist ()
520 {
521  auto builder = gtk_builder_new();
522  gnc_builder_add_from_file (builder , "assistant-csv-price-import.glade", "start_row_adj");
523  gnc_builder_add_from_file (builder , "assistant-csv-price-import.glade", "end_row_adj");
524  gnc_builder_add_from_file (builder , "assistant-csv-price-import.glade", "liststore1");
525  gnc_builder_add_from_file (builder , "assistant-csv-price-import.glade", "liststore2");
526  gnc_builder_add_from_file (builder , "assistant-csv-price-import.glade", "CSV Price Assistant");
527  csv_imp_asst = GTK_ASSISTANT(gtk_builder_get_object (builder, "CSV Price Assistant"));
528 
529  // Set the name for this assistant so it can be easily manipulated with css
530  gtk_widget_set_name (GTK_WIDGET(csv_imp_asst), "gnc-id-assistant-csv-price-import");
531  gnc_widget_style_context_add_class (GTK_WIDGET(csv_imp_asst), "gnc-class-imports");
532 
533  /* Enable buttons on all page. */
534  gtk_assistant_set_page_complete (csv_imp_asst,
535  GTK_WIDGET(gtk_builder_get_object (builder, "start_page")),
536  true);
537  gtk_assistant_set_page_complete (csv_imp_asst,
538  GTK_WIDGET(gtk_builder_get_object (builder, "file_page")),
539  false);
540  gtk_assistant_set_page_complete (csv_imp_asst,
541  GTK_WIDGET(gtk_builder_get_object (builder, "preview_page")),
542  false);
543  gtk_assistant_set_page_complete (csv_imp_asst,
544  GTK_WIDGET(gtk_builder_get_object (builder, "confirm_page")),
545  true);
546  gtk_assistant_set_page_complete (csv_imp_asst,
547  GTK_WIDGET(gtk_builder_get_object (builder, "summary_page")),
548  true);
549 
550  /* File chooser Page */
551  file_page = GTK_WIDGET(gtk_builder_get_object (builder, "file_page"));
552  file_chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN);
553 
554  g_signal_connect (G_OBJECT(file_chooser), "selection-changed",
555  G_CALLBACK(csv_price_imp_file_selection_changed_cb), this);
556  g_signal_connect (G_OBJECT(file_chooser), "file-activated",
557  G_CALLBACK(csv_price_imp_file_activated_changed_cb), this);
558 
559  auto box = GTK_WIDGET(gtk_builder_get_object (builder, "file_page"));
560  gtk_box_pack_start (GTK_BOX(box), file_chooser, TRUE, TRUE, 6);
561  gtk_widget_show (file_chooser);
562 
563  /* Preview Settings Page */
564  {
565  preview_page = GTK_WIDGET(gtk_builder_get_object (builder, "preview_page"));
566 
567  // Add Settings combo
568  auto settings_store = gtk_list_store_new (2, G_TYPE_POINTER, G_TYPE_STRING);
569  settings_combo = GTK_COMBO_BOX(gtk_combo_box_new_with_model_and_entry (GTK_TREE_MODEL(settings_store)));
570  g_object_unref (settings_store);
571 
572  gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX(settings_combo), SET_NAME);
573  gtk_combo_box_set_active (GTK_COMBO_BOX(settings_combo), 0);
574 
575  combo_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "combo_hbox"));
576  gtk_box_pack_start (GTK_BOX(combo_hbox), GTK_WIDGET(settings_combo), true, true, 6);
577  gtk_widget_show (GTK_WIDGET(settings_combo));
578 
579  g_signal_connect (G_OBJECT(settings_combo), "changed",
580  G_CALLBACK(csv_price_imp_preview_settings_sel_changed_cb), this);
581 
582  // Additionally connect to the changed signal of the embedded GtkEntry
583  auto emb_entry = gtk_bin_get_child (GTK_BIN (settings_combo));
584  g_signal_connect (G_OBJECT(emb_entry), "changed",
585  G_CALLBACK(csv_price_imp_preview_settings_text_changed_cb), this);
586  g_signal_connect (G_OBJECT(emb_entry), "insert-text",
587  G_CALLBACK(csv_price_imp_preview_settings_text_inserted_cb), this);
588 
589  // Add Save Settings button
590  save_button = GTK_WIDGET(gtk_builder_get_object (builder, "save_settings"));
591 
592  // Add Delete Settings button
593  del_button = GTK_WIDGET(gtk_builder_get_object (builder, "delete_settings"));
594 
595  /* The table containing the separator configuration widgets */
596  start_row_spin = GTK_SPIN_BUTTON(gtk_builder_get_object (builder, "start_row"));
597  end_row_spin = GTK_SPIN_BUTTON(gtk_builder_get_object (builder, "end_row"));
598  skip_alt_rows_button = GTK_WIDGET(gtk_builder_get_object (builder, "skip_rows"));
599  skip_errors_button = GTK_WIDGET(gtk_builder_get_object (builder, "skip_errors_button"));
600  over_write_cbutton = GTK_WIDGET(gtk_builder_get_object (builder, "over_write_button"));
601  separator_table = GTK_WIDGET(gtk_builder_get_object (builder, "separator_table"));
602  fw_instructions_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "fw_instructions_hbox"));
603 
604  /* Load the separator buttons from the glade builder file into the
605  * sep_buttons array. */
606  const char* sep_button_names[] = {
607  "space_cbutton",
608  "tab_cbutton",
609  "comma_cbutton",
610  "colon_cbutton",
611  "semicolon_cbutton",
612  "hyphen_cbutton"
613  };
614  for (int i = 0; i < SEP_NUM_OF_TYPES; i++)
615  sep_button[i]
616  = (GtkCheckButton*)GTK_WIDGET(gtk_builder_get_object (builder, sep_button_names[i]));
617 
618  /* Load and connect the custom separator checkbutton in the same way
619  * as the other separator buttons. */
620  custom_cbutton
621  = (GtkCheckButton*)GTK_WIDGET(gtk_builder_get_object (builder, "custom_cbutton"));
622 
623  /* Load the entry for the custom separator entry. Connect it to the
624  * sep_button_clicked event handler as well. */
625  custom_entry = (GtkEntry*)GTK_WIDGET(gtk_builder_get_object (builder, "custom_entry"));
626 
627  /* Create the encoding selector widget and add it to the assistant */
628  encselector = GO_CHARMAP_SEL(go_charmap_sel_new(GO_CHARMAP_SEL_TO_UTF8));
629  /* Connect the selector to the encoding_selected event handler. */
630  g_signal_connect (G_OBJECT(encselector), "charmap_changed",
631  G_CALLBACK(csv_price_imp_preview_enc_sel_cb), this);
632 
633  auto encoding_container = GTK_CONTAINER(gtk_builder_get_object (builder, "encoding_container"));
634  gtk_container_add (encoding_container, GTK_WIDGET(encselector));
635  gtk_widget_set_hexpand (GTK_WIDGET(encselector), true);
636  gtk_widget_show_all (GTK_WIDGET(encoding_container));
637 
638  /* Add commodity selection widget */
639  commodity_selector = GTK_WIDGET(gtk_builder_get_object (builder, "commodity_cbox"));
640  gtk_combo_box_set_model (GTK_COMBO_BOX(commodity_selector), get_model (true));
641  gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX(commodity_selector),
642  separator_row_func, nullptr, nullptr);
643  g_signal_connect (G_OBJECT(commodity_selector), "changed",
644  G_CALLBACK(csv_price_imp_preview_commodity_sel_cb), this);
645 
646  /* Add currency selection widget */
647  currency_selector = GTK_WIDGET(gtk_builder_get_object (builder, "currency_cbox"));
648  gtk_combo_box_set_model (GTK_COMBO_BOX(currency_selector), get_model (false));
649  g_signal_connect(G_OBJECT(currency_selector), "changed",
650  G_CALLBACK(csv_price_imp_preview_currency_sel_cb), this);
651 
652  /* The instructions label and image */
653  instructions_label = GTK_LABEL(gtk_builder_get_object (builder, "instructions_label"));
654  instructions_image = GTK_IMAGE(gtk_builder_get_object (builder, "instructions_image"));
655 
656  /* Add in the date format combo box and hook it up to an event handler. */
657  date_format_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
658  for (auto& date_fmt : GncDate::c_formats)
659  gtk_combo_box_text_append_text (date_format_combo, _(date_fmt.m_fmt.c_str()));
660  gtk_combo_box_set_active (GTK_COMBO_BOX(date_format_combo), 0);
661  g_signal_connect (G_OBJECT(date_format_combo), "changed",
662  G_CALLBACK(csv_price_imp_preview_date_fmt_sel_cb), this);
663 
664  /* Add it to the assistant. */
665  auto date_format_container = GTK_CONTAINER(gtk_builder_get_object (builder, "date_format_container"));
666  gtk_container_add (date_format_container, GTK_WIDGET(date_format_combo));
667  gtk_widget_set_hexpand (GTK_WIDGET(date_format_combo), true);
668  gtk_widget_show_all (GTK_WIDGET(date_format_container));
669 
670  /* Add in the currency format combo box and hook it up to an event handler. */
671  currency_format_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
672  for (int i = 0; i < num_currency_formats_price; i++)
673  {
674  gtk_combo_box_text_append_text (currency_format_combo, _(currency_format_user_price[i]));
675  }
676  /* Default will the locale */
677  gtk_combo_box_set_active (GTK_COMBO_BOX(currency_format_combo), 0);
678  g_signal_connect (G_OBJECT(currency_format_combo), "changed",
679  G_CALLBACK(csv_price_imp_preview_currency_fmt_sel_cb), this);
680 
681  /* Add it to the assistant. */
682  auto currency_format_container = GTK_CONTAINER(gtk_builder_get_object (builder, "currency_format_container"));
683  gtk_container_add (currency_format_container, GTK_WIDGET(currency_format_combo));
684  gtk_widget_set_hexpand (GTK_WIDGET(currency_format_combo), true);
685  gtk_widget_show_all (GTK_WIDGET(currency_format_container));
686 
687  /* Connect the CSV/Fixed-Width radio button event handler. */
688  csv_button = GTK_WIDGET(gtk_builder_get_object (builder, "csv_button"));
689  fixed_button = GTK_WIDGET(gtk_builder_get_object (builder, "fixed_button"));
690 
691  /* Load the data treeview and connect it to its resizing event handler. */
692  treeview = (GtkTreeView*)GTK_WIDGET(gtk_builder_get_object (builder, "treeview"));
693  gtk_tree_view_set_headers_clickable (treeview, true);
694 
695  /* This is true only after encoding_selected is called, so we must
696  * set it initially to false. */
697  encoding_selected_called = false;
698  }
699 
700  /* Confirm Page */
701  confirm_page = GTK_WIDGET(gtk_builder_get_object (builder, "confirm_page"));
702 
703  /* Summary Page */
704  summary_page = GTK_WIDGET(gtk_builder_get_object (builder, "summary_page"));
705  summary_label = GTK_WIDGET(gtk_builder_get_object (builder, "summary_label"));
706 
707  gnc_restore_window_size (GNC_PREFS_GROUP,
708  GTK_WINDOW(csv_imp_asst), gnc_ui_get_main_window(nullptr));
709 
710  gtk_builder_connect_signals (builder, this);
711  g_object_unref (G_OBJECT(builder));
712 
713  gtk_widget_show_all (GTK_WIDGET(csv_imp_asst));
714  gnc_window_adjust_for_screen (GTK_WINDOW(csv_imp_asst));
715 }
716 
717 /*******************************************************
718  * Assistant Destructor
719  *******************************************************/
720 CsvImpPriceAssist::~CsvImpPriceAssist ()
721 {
722  gtk_widget_destroy (GTK_WIDGET(csv_imp_asst));
723 }
724 
725 /**************************************************
726  * Code related to the file chooser page
727  **************************************************/
728 
729 /* check_for_valid_filename for a valid file to activate the "Next" button
730  */
731 bool
732 CsvImpPriceAssist::check_for_valid_filename ()
733 {
734  auto file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(file_chooser));
735  if (!file_name || g_file_test (file_name, G_FILE_TEST_IS_DIR))
736  {
737  g_free (file_name);
738  return false;
739  }
740 
741  auto filepath = gnc_uri_get_path (file_name);
742  auto starting_dir = g_path_get_dirname (filepath);
743 
744  m_fc_file_name = file_name;
745  gnc_set_default_directory (GNC_PREFS_GROUP, starting_dir);
746 
747  DEBUG("file_name selected is %s", m_fc_file_name.c_str());
748  DEBUG("starting directory is %s", starting_dir);
749 
750  g_free (filepath);
751  g_free (file_name);
752  g_free (starting_dir);
753 
754  return true;
755 }
756 
757 /* csv_price_imp_file_activated_cb
758  *
759  * call back for file chooser widget
760  */
761 void
762 CsvImpPriceAssist::file_activated_cb ()
763 {
764  gtk_assistant_set_page_complete (csv_imp_asst, file_page, false);
765 
766  /* Test for a valid filename and not a directory */
767  if (check_for_valid_filename ())
768  {
769  gtk_assistant_set_page_complete (csv_imp_asst, file_page, true);
770  gtk_assistant_next_page (csv_imp_asst);
771  }
772 }
773 
774 /* csv_price_imp_file_selection_changed_cb
775  *
776  * call back for file chooser widget
777  */
778 void
779 CsvImpPriceAssist::file_selection_changed_cb ()
780 {
781  /* Enable the "Next" button based on a valid filename */
782  gtk_assistant_set_page_complete (csv_imp_asst, file_page,
783  check_for_valid_filename ());
784 }
785 
786 
787 /**************************************************
788  * Code related to the preview page
789  **************************************************/
790 
791 /* Set the available presets in the settings combo box
792  */
793 void CsvImpPriceAssist::preview_populate_settings_combo()
794 {
795  // Clear the list store
796  auto model = gtk_combo_box_get_model (settings_combo);
797  gtk_list_store_clear (GTK_LIST_STORE(model));
798 
799  // Append the default entry
800  auto presets = get_import_presets_price ();
801  for (const auto& preset : presets)
802  {
803  GtkTreeIter iter;
804  gtk_list_store_append (GTK_LIST_STORE(model), &iter);
805  /* FIXME we store the raw pointer to the preset, while it's
806  * managed by a shared pointer. This is dangerous because
807  * when the shared pointer goes out of scope, our pointer will dangle.
808  * For now this is safe, because the shared pointers in this case are
809  * long-lived, but this may need refactoring.
810  */
811  gtk_list_store_set (GTK_LIST_STORE(model), &iter, SET_GROUP, preset.get(), SET_NAME, _(preset->m_name.c_str()), -1);
812  }
813 }
814 
815 /* Enable or disable the save and delete settings buttons
816  * depending on what is selected and entered as settings name
817  */
818 void CsvImpPriceAssist::preview_handle_save_del_sensitivity (GtkComboBox* combo)
819 {
820  GtkTreeIter iter;
821  auto can_delete = false;
822  auto can_save = false;
823  auto entry = gtk_bin_get_child (GTK_BIN(combo));
824  auto entry_text = gtk_entry_get_text (GTK_ENTRY(entry));
825  /* Handle sensitivity of the delete and save button */
826  if (gtk_combo_box_get_active_iter (combo, &iter))
827  {
828  CsvPriceImpSettings *preset;
829  GtkTreeModel *model = gtk_combo_box_get_model (combo);
830  gtk_tree_model_get (model, &iter, SET_GROUP, &preset, -1);
831 
832  if (preset && !preset_is_reserved_name (preset->m_name))
833  {
834  /* Current preset is not read_only, so buttons can be enabled */
835  can_delete = true;
836  can_save = true;
837  }
838  }
839  else if (entry_text && (strlen (entry_text) > 0) &&
840  !preset_is_reserved_name (std::string(entry_text)))
841  can_save = true;
842 
843  gtk_widget_set_sensitive (save_button, can_save);
844  gtk_widget_set_sensitive (del_button, can_delete);
845 }
846 
847 void
848 CsvImpPriceAssist::preview_settings_name (GtkEntry* entry)
849 {
850  auto text = gtk_entry_get_text (entry);
851  if (text)
852  price_imp->settings_name(text);
853 
854  auto box = gtk_widget_get_parent (GTK_WIDGET(entry));
855  auto combo = gtk_widget_get_parent (GTK_WIDGET(box));
856 
857  preview_handle_save_del_sensitivity (GTK_COMBO_BOX(combo));
858 }
859 
860 /* Use selected preset to configure the import. Triggered when
861  * a preset is selected in the settings combo.
862  */
863 void
864 CsvImpPriceAssist::preview_settings_load ()
865 {
866  // Get the Active Selection
867  GtkTreeIter iter;
868  if (!gtk_combo_box_get_active_iter (settings_combo, &iter))
869  return;
870 
871  CsvPriceImpSettings *preset = nullptr;
872  auto model = gtk_combo_box_get_model (settings_combo);
873  gtk_tree_model_get (model, &iter, SET_GROUP, &preset, -1);
874 
875  if (!preset)
876  return;
877 
878  price_imp->settings (*preset);
879  if (preset->m_load_error)
880  gnc_error_dialog (GTK_WINDOW(csv_imp_asst),
881  "%s", _("There were problems reading some saved settings, continuing to load.\n"
882  "Please review and save again."));
883 
884  preview_refresh ();
885  preview_handle_save_del_sensitivity (settings_combo);
886 }
887 
888 /* Callback to delete a settings entry
889  */
890 void
891 CsvImpPriceAssist::preview_settings_delete ()
892 {
893  // Get the Active Selection
894  GtkTreeIter iter;
895  if (!gtk_combo_box_get_active_iter (settings_combo, &iter))
896  return;
897 
898  CsvPriceImpSettings *preset = nullptr;
899  auto model = gtk_combo_box_get_model (settings_combo);
900  gtk_tree_model_get (model, &iter, SET_GROUP, &preset, -1);
901 
902  auto response = gnc_ok_cancel_dialog (GTK_WINDOW(csv_imp_asst),
903  GTK_RESPONSE_CANCEL,
904  "%s", _("Delete the Import Settings."));
905  if (response == GTK_RESPONSE_OK)
906  {
907  preset->remove();
908  preview_populate_settings_combo();
909  gtk_combo_box_set_active (settings_combo, 0); // Default
910  preview_refresh (); // Reset the widgets
911  }
912 }
913 
914 /* Callback to save the current settings to the gnucash state file.
915  */
916 void
917 CsvImpPriceAssist::preview_settings_save ()
918 {
919  auto new_name = price_imp->settings_name();
920 
921  /* Check if the entry text matches an already existing preset */
922  GtkTreeIter iter;
923  if (!gtk_combo_box_get_active_iter (settings_combo, &iter))
924  {
925 
926  auto model = gtk_combo_box_get_model (settings_combo);
927  bool valid = gtk_tree_model_get_iter_first (model, &iter);
928  while (valid)
929  {
930  // Walk through the list, reading each row
931  CsvPriceImpSettings *preset;
932  gtk_tree_model_get (model, &iter, SET_GROUP, &preset, -1);
933 
934  if (preset && (preset->m_name == std::string(new_name)))
935  {
936  auto response = gnc_ok_cancel_dialog (GTK_WINDOW(csv_imp_asst),
937  GTK_RESPONSE_OK,
938  "%s", _("Setting name already exists, overwrite?"));
939  if (response != GTK_RESPONSE_OK)
940  return;
941 
942  break;
943  }
944  valid = gtk_tree_model_iter_next (model, &iter);
945  }
946  }
947 
948  /* All checks passed, let's save this preset */
949  if (!price_imp->save_settings())
950  {
951  gnc_info_dialog (GTK_WINDOW(csv_imp_asst),
952  "%s", _("The settings have been saved."));
953 
954  // Update the settings store
955  preview_populate_settings_combo();
956  auto model = gtk_combo_box_get_model (settings_combo);
957 
958  // Get the first entry in model
959  GtkTreeIter iter;
960  bool valid = gtk_tree_model_get_iter_first (model, &iter);
961  while (valid)
962  {
963  // Walk through the list, reading each row
964  gchar *name = nullptr;
965  gtk_tree_model_get (model, &iter, SET_NAME, &name, -1);
966 
967  if (g_strcmp0 (name, new_name.c_str()) == 0) // Set Active, the one Saved.
968  gtk_combo_box_set_active_iter (settings_combo, &iter);
969 
970  g_free (name);
971 
972  valid = gtk_tree_model_iter_next (model, &iter);
973  }
974  }
975  else
976  gnc_error_dialog (GTK_WINDOW(csv_imp_asst),
977  "%s", _("There was a problem saving the settings, please try again."));
978 }
979 
980 /* Callback triggered when user adjusts skip start lines
981  */
982 void CsvImpPriceAssist::preview_update_skipped_rows ()
983 {
984  /* Update skip rows in the parser */
985  price_imp->update_skipped_lines (gtk_spin_button_get_value_as_int (start_row_spin),
986  gtk_spin_button_get_value_as_int (end_row_spin),
987  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(skip_alt_rows_button)),
988  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(skip_errors_button)));
989 
990  /* And adjust maximum number of lines that can be skipped at each end accordingly */
991  auto adj = gtk_spin_button_get_adjustment (end_row_spin);
992  gtk_adjustment_set_upper (adj, price_imp->m_parsed_lines.size()
993  - price_imp->skip_start_lines() -1);
994 
995  adj = gtk_spin_button_get_adjustment (start_row_spin);
996  gtk_adjustment_set_upper (adj, price_imp->m_parsed_lines.size()
997  - price_imp->skip_end_lines() - 1);
998 
999  preview_refresh_table ();
1000 }
1001 
1002 /* Callback triggered when user clicks on Overwrite option
1003  */
1004 void CsvImpPriceAssist::preview_over_write (bool over)
1005 {
1006  price_imp->over_write (over);
1007 }
1008 
1017 {
1018  /* Only manipulate separator characters if the currently open file is
1019  * csv separated. */
1020  if (price_imp->file_format() != GncImpFileFormat::CSV)
1021  return;
1022 
1023  /* Add the corresponding characters to checked_separators for each
1024  * button that is checked. */
1025  auto checked_separators = std::string();
1026  const auto stock_sep_chars = std::string (" \t,:;-");
1027  for (int i = 0; i < SEP_NUM_OF_TYPES; i++)
1028  {
1029  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(sep_button[i])))
1030  checked_separators += stock_sep_chars[i];
1031  }
1032 
1033  /* Add the custom separator if the user checked its button. */
1034  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(custom_cbutton)))
1035  {
1036  auto custom_sep = gtk_entry_get_text (custom_entry);
1037  if (custom_sep[0] != '\0') /* Don't add a blank separator (bad things will happen!). */
1038  checked_separators += custom_sep;
1039  }
1040 
1041  /* Set the parse options using the checked_separators list. */
1042  price_imp->separators (checked_separators);
1043 
1044  /* if there are no separators, there will only be one column
1045  * so make sure column header is NONE */
1046  if (checked_separators.empty())
1047  price_imp->set_column_type_price (0, GncPricePropType::NONE);
1048 
1049  /* Parse the data using the new options. We don't want to reguess
1050  * the column types because we want to leave the user's
1051  * configurations intact. */
1052  try
1053  {
1054  price_imp->tokenize (false);
1055  preview_refresh_table ();
1056  }
1057  catch (std::range_error &e)
1058  {
1059  /* Warn the user there was a problem and try to undo what caused
1060  * the error. (This will cause a reparsing and ideally a usable
1061  * configuration.) */
1062  gnc_error_dialog (GTK_WINDOW(csv_imp_asst), "Error in parsing");
1063  /* If we're here because the user changed the file format, we should just wait for the user
1064  * to update the configuration */
1065  if (!widget)
1066  return;
1067  /* If the user changed the custom separator, erase that custom separator. */
1068  if (widget == GTK_WIDGET(custom_entry))
1069  gtk_entry_set_text (GTK_ENTRY(widget), "");
1070  /* If the user checked a checkbutton, toggle that checkbutton back. */
1071  else
1072  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(widget),
1073  !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget)));
1074  return;
1075  }
1076 }
1077 
1084 {
1085  /* Set the parsing type correctly. */
1086  try
1087  {
1088  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(csv_button)))
1089  {
1090  price_imp->file_format (GncImpFileFormat::CSV);
1091  g_signal_handlers_disconnect_by_func(G_OBJECT(treeview),
1092  (gpointer)csv_price_imp_preview_treeview_clicked_cb, (gpointer)this);
1093  gtk_widget_set_visible (separator_table, true);
1094  gtk_widget_set_visible (fw_instructions_hbox, false);
1095  }
1096  else
1097  {
1098  price_imp->file_format (GncImpFileFormat::FIXED_WIDTH);
1099  /* Enable context menu for adding/removing columns. */
1100  g_signal_connect (G_OBJECT(treeview), "button-press-event",
1101  G_CALLBACK(csv_price_imp_preview_treeview_clicked_cb), (gpointer)this);
1102  gtk_widget_set_visible (separator_table, false);
1103  gtk_widget_set_visible (fw_instructions_hbox, true);
1104 
1105  }
1106  price_imp->tokenize (false);
1107  preview_refresh_table ();
1108  }
1109  catch (std::range_error &e)
1110  {
1111  /* Parsing failed ... */
1112  gnc_error_dialog (GTK_WINDOW (csv_imp_asst), "%s", e.what());
1113  return;
1114  }
1115  catch (...)
1116  {
1117  // FIXME Handle file loading errors (possibly thrown by file_format above)
1118  PWARN("Got an error during file loading");
1119  }
1120 }
1121 
1128 void
1130 {
1131  /* This gets called twice every time a new encoding is selected. The
1132  * second call actually passes the correct data; thus, we only do
1133  * something the second time this is called. */
1134 
1135  /* If this is the second time the function is called ... */
1136  if (encoding_selected_called)
1137  {
1138  std::string previous_encoding = price_imp->m_tokenizer->encoding();
1139  /* Try converting the new encoding and reparsing. */
1140  try
1141  {
1142  price_imp->encoding (encoding);
1143  preview_refresh_table ();
1144  }
1145  catch (...)
1146  {
1147  /* If it fails, change back to the old encoding. */
1148  gnc_error_dialog (GTK_WINDOW (csv_imp_asst), "%s", _("Invalid encoding selected"));
1149  go_charmap_sel_set_encoding (encselector, previous_encoding.c_str());
1150  }
1151  }
1152  encoding_selected_called = !encoding_selected_called;
1153 }
1154 
1155 void
1156 CsvImpPriceAssist::preview_update_date_format ()
1157 {
1158  price_imp->date_format (gtk_combo_box_get_active (GTK_COMBO_BOX(date_format_combo)));
1159  preview_refresh_table ();
1160 }
1161 
1162 void
1163 CsvImpPriceAssist::preview_update_currency_format ()
1164 {
1165  price_imp->currency_format (gtk_combo_box_get_active (GTK_COMBO_BOX(currency_format_combo)));
1166  preview_refresh_table ();
1167 }
1168 
1169 void
1170 CsvImpPriceAssist::preview_update_currency ()
1171 {
1172  gnc_commodity *comm = get_commodity_from_combo (GTK_COMBO_BOX(currency_selector));
1173  price_imp->to_currency (comm);
1174  preview_refresh_table ();
1175 }
1176 
1177 void
1178 CsvImpPriceAssist::preview_update_commodity ()
1179 {
1180  gnc_commodity *comm = get_commodity_from_combo (GTK_COMBO_BOX(commodity_selector));
1181  price_imp->from_commodity (comm);
1182  preview_refresh_table ();
1183 }
1184 
1185 static gboolean
1186 csv_imp_preview_queue_rebuild_table (CsvImpPriceAssist *assist)
1187 {
1188  assist->preview_refresh_table ();
1189  return false;
1190 }
1191 
1192 /* Internally used enum to access the columns in the comboboxes
1193  * the user can click to set a type for each column of the data
1194  */
1195 enum PreviewHeaderComboCols { COL_TYPE_NAME, COL_TYPE_ID };
1196 /* Internally used enum to access the first two (fixed) columns
1197  * in the model used to display the prased data.
1198  */
1199 enum PreviewDataTableCols {
1200  PREV_COL_FCOLOR,
1201  PREV_COL_BCOLOR,
1202  PREV_COL_STRIKE,
1203  PREV_COL_ERROR,
1204  PREV_COL_ERR_ICON,
1205  PREV_N_FIXED_COLS };
1206 
1207 
1208 void
1209 CsvImpPriceAssist::preview_reparse_col_type (GncPricePropType type)
1210 {
1211  auto column_types = price_imp->column_types_price();
1212 
1213  // look for column type and force a reparse
1214  auto col_type = std::find (column_types.begin(),
1215  column_types.end(), type);
1216  if (col_type != column_types.end())
1217  {
1218  price_imp->set_column_type_price (col_type -column_types.begin(),
1219  type, true);
1220  }
1221 }
1222 
1233 {
1234  /* Get the new text */
1235  GtkTreeIter iter;
1236  auto model = gtk_combo_box_get_model (cbox);
1237  gtk_combo_box_get_active_iter (cbox, &iter);
1238  auto new_col_type = GncPricePropType::NONE;
1239  gtk_tree_model_get (model, &iter, COL_TYPE_ID, &new_col_type, -1);
1240 
1241  auto col_num = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT(cbox), "col-num"));
1242 
1243  auto column_types = price_imp->column_types_price();
1244  auto old_col_type = column_types.at(col_num);
1245 
1246  price_imp->set_column_type_price (col_num, new_col_type);
1247 
1248  // if old_col_type is TO_CURRENCY, force a reparse of commodity
1249  if (old_col_type == GncPricePropType::TO_CURRENCY)
1250  {
1251  // look for a from_commodity column to reparse
1252  preview_reparse_col_type (GncPricePropType::FROM_SYMBOL);
1253  preview_reparse_col_type (GncPricePropType::FROM_NAMESPACE);
1254  }
1255 
1256  // if old_col_type is FROM_SYMBOL, or FROM_NAMESPACE force a reparse of currency
1257  if ((old_col_type == GncPricePropType::FROM_SYMBOL) ||
1258  (old_col_type == GncPricePropType::FROM_NAMESPACE))
1259  {
1260  // look for a to_currency column to reparse
1261  preview_reparse_col_type (GncPricePropType::TO_CURRENCY);
1262  }
1263 
1264  /* Delay rebuilding our data table to avoid critical warnings due to
1265  * pending events still acting on them after this event is processed.
1266  */
1267  g_idle_add ((GSourceFunc)csv_imp_preview_queue_rebuild_table, this);
1268 }
1269 
1270 /*======================================================================*/
1271 /*================== Beginning of Gnumeric Code ========================*/
1272 
1273 /* The following is code copied from Gnumeric 1.7.8 licensed under the
1274  * GNU General Public License version 2 and/or version 3. It is from the file
1275  * gnumeric/gnucash/dialogs/dialog-stf-fixed-page.c, and it has been
1276  * modified slightly to work within GnuCash. */
1277 
1278 /*
1279  * Copyright 2001 Almer S. Tigelaar <almer@gnome.org>
1280  * Copyright 2003 Morten Welinder <terra@gnome.org>
1281  *
1282  * This program is free software; you can redistribute it and/or modify
1283  * it under the terms of the GNU General Public License as published by
1284  * the Free Software Foundation; either version 2 of the License, or
1285  * (at your option) any later version.
1286  *
1287  * This program is distributed in the hope that it will be useful,
1288  * but WITHOUT ANY WARRANTY; without even the implied warranty of
1289  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1290  * GNU General Public License for more details.
1291  *
1292  * You should have received a copy of the GNU General Public License
1293  * along with this program; if not, write to the Free Software
1294  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
1295  */
1296 
1297 enum
1298 {
1299  CONTEXT_STF_IMPORT_MERGE_LEFT = 1,
1300  CONTEXT_STF_IMPORT_MERGE_RIGHT = 2,
1301  CONTEXT_STF_IMPORT_SPLIT = 3,
1302  CONTEXT_STF_IMPORT_WIDEN = 4,
1303  CONTEXT_STF_IMPORT_NARROW = 5
1304 };
1305 
1306 static GnumericPopupMenuElement const popup_elements[] =
1307 {
1308  {
1309  N_("Merge with column on _left"), "list-remove",
1310  0, 1 << CONTEXT_STF_IMPORT_MERGE_LEFT, CONTEXT_STF_IMPORT_MERGE_LEFT
1311  },
1312  {
1313  N_("Merge with column on _right"), "list-remove",
1314  0, 1 << CONTEXT_STF_IMPORT_MERGE_RIGHT, CONTEXT_STF_IMPORT_MERGE_RIGHT
1315  },
1316  { "", nullptr, 0, 0, 0 },
1317  {
1318  N_("_Split this column"), nullptr,
1319  0, 1 << CONTEXT_STF_IMPORT_SPLIT, CONTEXT_STF_IMPORT_SPLIT
1320  },
1321  { "", nullptr, 0, 0, 0 },
1322  {
1323  N_("_Widen this column"), "go-next",
1324  0, 1 << CONTEXT_STF_IMPORT_WIDEN, CONTEXT_STF_IMPORT_WIDEN
1325  },
1326  {
1327  N_("_Narrow this column"), "go-previous",
1328  0, 1 << CONTEXT_STF_IMPORT_NARROW, CONTEXT_STF_IMPORT_NARROW
1329  },
1330  { nullptr, nullptr, 0, 0, 0 },
1331 };
1332 
1333 uint32_t CsvImpPriceAssist::get_new_col_rel_pos (GtkTreeViewColumn *tcol, int dx)
1334 {
1335  auto renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(tcol));
1336  auto cell = GTK_CELL_RENDERER(renderers->data);
1337  g_list_free (renderers);
1338  PangoFontDescription *font_desc;
1339  g_object_get (G_OBJECT(cell), "font_desc", &font_desc, nullptr);
1340 
1341  PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET(treeview), "x");
1342  pango_layout_set_font_description (layout, font_desc);
1343  int width;
1344  pango_layout_get_pixel_size (layout, &width, nullptr);
1345  if (width < 1) width = 1;
1346  uint32_t charindex = (dx + width / 2) / width;
1347  g_object_unref (layout);
1348  pango_font_description_free (font_desc);
1349 
1350  return charindex;
1351 }
1352 
1353 gboolean
1354 fixed_context_menu_handler_price (GnumericPopupMenuElement const *element,
1355  gpointer userdata)
1356 {
1357  auto info = (CsvImpPriceAssist*)userdata;
1358  auto fwtok = dynamic_cast<GncFwTokenizer*>(info->price_imp->m_tokenizer.get());
1359 
1360  switch (element->index)
1361  {
1362  case CONTEXT_STF_IMPORT_MERGE_LEFT:
1363  fwtok->col_delete (info->fixed_context_col - 1);
1364  break;
1365  case CONTEXT_STF_IMPORT_MERGE_RIGHT:
1366  fwtok->col_delete (info->fixed_context_col);
1367  break;
1368  case CONTEXT_STF_IMPORT_SPLIT:
1369  fwtok->col_split (info->fixed_context_col, info->fixed_context_offset);
1370  break;
1371  case CONTEXT_STF_IMPORT_WIDEN:
1372  fwtok->col_widen (info->fixed_context_col);
1373  break;
1374  case CONTEXT_STF_IMPORT_NARROW:
1375  fwtok->col_narrow (info->fixed_context_col);
1376  break;
1377  default:
1378  ; /* Nothing */
1379  }
1380 
1381  try
1382  {
1383  info->price_imp->tokenize (false);
1384  }
1385  catch(std::range_error& e)
1386  {
1387  gnc_error_dialog (GTK_WINDOW (info->csv_imp_asst), "%s", e.what());
1388  return false;
1389  }
1390  info->preview_refresh_table ();
1391  return true;
1392 }
1393 
1394 void
1395 CsvImpPriceAssist::fixed_context_menu (GdkEventButton *event,
1396  int col, int offset)
1397 {
1398  auto fwtok = dynamic_cast<GncFwTokenizer*>(price_imp->m_tokenizer.get());
1399  fixed_context_col = col;
1400  fixed_context_offset = offset;
1401 
1402  int sensitivity_filter = 0;
1403  if (!fwtok->col_can_delete (col - 1))
1404  sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_MERGE_LEFT);
1405  if (!fwtok->col_can_delete (col))
1406  sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_MERGE_RIGHT);
1407  if (!fwtok->col_can_split (col, offset))
1408  sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_SPLIT);
1409  if (!fwtok->col_can_widen (col))
1410  sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_WIDEN);
1411  if (!fwtok->col_can_narrow (col))
1412  sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_NARROW);
1413 
1414  gnumeric_create_popup_menu (popup_elements, &fixed_context_menu_handler_price,
1415  this, 0,
1416  sensitivity_filter, event);
1417 }
1418 
1419 /*===================== End of Gnumeric Code ===========================*/
1420 /*======================================================================*/
1421 void
1422 CsvImpPriceAssist::preview_split_column (int col, int offset)
1423 {
1424  auto fwtok = dynamic_cast<GncFwTokenizer*>(price_imp->m_tokenizer.get());
1425  fwtok->col_split (col, offset);
1426  try
1427  {
1428  price_imp->tokenize (false);
1429  }
1430  catch (std::range_error& e)
1431  {
1432  gnc_error_dialog (GTK_WINDOW (csv_imp_asst), "%s", e.what());
1433  return;
1434  }
1435  preview_refresh_table();
1436 }
1437 
1447 void
1448 CsvImpPriceAssist::preview_update_fw_columns (GtkTreeView* treeview, GdkEventButton* event)
1449 {
1450  /* Nothing to do if this was not triggered on our treeview body */
1451  if (event->window != gtk_tree_view_get_bin_window (treeview))
1452  return;
1453 
1454  /* Find the column that was clicked. */
1455  GtkTreeViewColumn *tcol = nullptr;
1456  int cell_x = 0;
1457  auto success = gtk_tree_view_get_path_at_pos (treeview,
1458  (int)event->x, (int)event->y,
1459  nullptr, &tcol, &cell_x, nullptr);
1460  if (!success)
1461  return;
1462 
1463  /* Stop if no column found in this treeview (-1) or
1464  * if column is the error messages column (0) */
1465  auto tcol_list = gtk_tree_view_get_columns(treeview);
1466  auto tcol_num = g_list_index (tcol_list, tcol);
1467  g_list_free (tcol_list);
1468  if (tcol_num <= 0)
1469  return;
1470 
1471  /* Data columns in the treeview are offset by one
1472  * because the first column is the error column
1473  */
1474  auto dcol = tcol_num - 1;
1475  auto offset = get_new_col_rel_pos (tcol, cell_x);
1476  if (event->type == GDK_2BUTTON_PRESS && event->button == 1)
1477  /* Double clicks can split columns. */
1478  preview_split_column (dcol, offset);
1479  else if (event->type == GDK_BUTTON_PRESS && event->button == 3)
1480  /* Right clicking brings up a context menu. */
1481  fixed_context_menu (event, dcol, offset);
1482 }
1483 
1484 /* Convert state info (errors/skipped) in visual feedback to decorate the preview table */
1485 void
1486 CsvImpPriceAssist::preview_row_fill_state_cells (GtkListStore *store, GtkTreeIter *iter,
1487  std::string& err_msg, bool skip)
1488 {
1489  /* Extract error status for all non-skipped lines */
1490  const char *c_err_msg = nullptr;
1491  const char *icon_name = nullptr;
1492  const char *fcolor = nullptr;
1493  const char *bcolor = nullptr;
1494  if (!skip && !err_msg.empty())
1495  {
1496  fcolor = "black";
1497  bcolor = "pink";
1498  c_err_msg = err_msg.c_str();
1499  icon_name = "dialog-error";
1500  }
1501  gtk_list_store_set (store, iter,
1502  PREV_COL_FCOLOR, fcolor,
1503  PREV_COL_BCOLOR, bcolor,
1504  PREV_COL_STRIKE, skip,
1505  PREV_COL_ERROR, c_err_msg,
1506  PREV_COL_ERR_ICON, icon_name, -1);
1507 }
1508 
1509 /* Helper function that creates a combo_box using a model
1510  * with valid column types and selects the given column type
1511  */
1512 GtkWidget*
1513 CsvImpPriceAssist::preview_cbox_factory (GtkTreeModel* model, uint32_t colnum)
1514 {
1515  GtkTreeIter iter;
1516  auto cbox = gtk_combo_box_new_with_model(model);
1517 
1518  /* Set up a renderer for this combobox. */
1519  auto renderer = gtk_cell_renderer_text_new();
1520  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(cbox),
1521  renderer, true);
1522  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT(cbox),
1523  renderer, "text", COL_TYPE_NAME);
1524 
1525  auto valid = gtk_tree_model_get_iter_first (model, &iter);
1526  while (valid)
1527  {
1528  gint stored_col_type;
1529  gtk_tree_model_get (model, &iter,
1530  COL_TYPE_ID, &stored_col_type, -1);
1531  if (stored_col_type == static_cast<int>( price_imp->column_types_price()[colnum]))
1532  break;
1533  valid = gtk_tree_model_iter_next(model, &iter);
1534  }
1535  if (valid)
1536  gtk_combo_box_set_active_iter (GTK_COMBO_BOX(cbox), &iter);
1537 
1538  g_object_set_data (G_OBJECT(cbox), "col-num", GUINT_TO_POINTER(colnum));
1539  g_signal_connect (G_OBJECT(cbox), "changed",
1540  G_CALLBACK(csv_price_imp_preview_col_type_changed_cb), (gpointer)this);
1541 
1542  gtk_widget_show (cbox);
1543  return cbox;
1544 }
1545 
1546 void
1547 CsvImpPriceAssist::preview_style_column (uint32_t col_num, GtkTreeModel* model)
1548 {
1549  auto col = gtk_tree_view_get_column (treeview, col_num);
1550  auto renderer = static_cast<GtkCellRenderer*>(gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(col))->data);
1551 
1552  /* First column -the error status column- is rendered differently */
1553  if (col_num == 0)
1554  {
1555  gtk_tree_view_column_set_attributes (col, renderer,
1556  "icon-name", PREV_COL_ERR_ICON,
1557  "cell-background", PREV_COL_BCOLOR, nullptr);
1558  g_object_set (G_OBJECT(renderer), "stock-size", GTK_ICON_SIZE_MENU, nullptr);
1559  g_object_set (G_OBJECT(col), "sizing", GTK_TREE_VIEW_COLUMN_FIXED,
1560  "fixed-width", 20, nullptr);
1561  gtk_tree_view_column_set_resizable (col, false);
1562  }
1563  else
1564  {
1565  gtk_tree_view_column_set_attributes (col, renderer,
1566  "foreground", PREV_COL_FCOLOR,
1567  "background", PREV_COL_BCOLOR,
1568  "strikethrough", PREV_COL_STRIKE,
1569  "text", col_num + PREV_N_FIXED_COLS -1, nullptr);
1570 
1571  /* We want a monospace font fixed-width data is properly displayed. */
1572  g_object_set (G_OBJECT(renderer), "family", "monospace", nullptr);
1573 
1574  /* Add a combobox to select column types as column header. Each uses the same
1575  * common model for the dropdown list. The selected value is taken
1576  * from the column_types vector. */
1577  auto cbox = preview_cbox_factory (GTK_TREE_MODEL(model), col_num - 1);
1578  gtk_tree_view_column_set_widget (col, cbox);
1579 
1580  /* Enable resizing of the columns. */
1581  gtk_tree_view_column_set_resizable (col, true);
1582  gtk_tree_view_column_set_clickable (col, true);
1583  }
1584 }
1585 
1586 /* Helper to create a shared store for the header comboboxes in the preview treeview.
1587  * It holds the possible column types */
1588 static GtkTreeModel*
1589 make_column_header_model_price (void)
1590 {
1591  auto combostore = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
1592  for (auto col_type : gnc_price_col_type_strs)
1593  {
1594  GtkTreeIter iter;
1595  gtk_list_store_append (combostore, &iter);
1596  gtk_list_store_set (combostore, &iter,
1597  COL_TYPE_NAME, _(col_type.second),
1598  COL_TYPE_ID, static_cast<int>(col_type.first), -1);
1599  }
1600  return GTK_TREE_MODEL(combostore);
1601 }
1602 
1603 /* Updates the preview treeview to show the data as parsed based on the user's
1604  * import parameters.
1605  */
1606 void CsvImpPriceAssist::preview_refresh_table ()
1607 {
1608  preview_validate_settings ();
1609 
1610  /* Create a new liststore to hold status and data from the file being imported.
1611  The first columns hold status information (row-color, row-errors, row-error-icon,...
1612  All following columns represent the tokenized data as strings. */
1613  auto ncols = PREV_N_FIXED_COLS + price_imp->column_types_price().size();
1614  auto model_col_types = g_new (GType, ncols);
1615  model_col_types[PREV_COL_FCOLOR] = G_TYPE_STRING;
1616  model_col_types[PREV_COL_BCOLOR] = G_TYPE_STRING;
1617  model_col_types[PREV_COL_ERROR] = G_TYPE_STRING;
1618  model_col_types[PREV_COL_ERR_ICON] = G_TYPE_STRING;
1619  model_col_types[PREV_COL_STRIKE] = G_TYPE_BOOLEAN;
1620  for (guint i = PREV_N_FIXED_COLS; i < ncols; i++)
1621  model_col_types[i] = G_TYPE_STRING;
1622  auto store = gtk_list_store_newv (ncols, model_col_types);
1623  g_free (model_col_types);
1624 
1625  /* Fill the data liststore with data from importer object. */
1626  for (auto parse_line : price_imp->m_parsed_lines)
1627  {
1628  /* Fill the state cells */
1629  GtkTreeIter iter;
1630  gtk_list_store_append (store, &iter);
1631  preview_row_fill_state_cells (store, &iter,
1632  std::get<PL_ERROR>(parse_line), std::get<PL_SKIP>(parse_line));
1633 
1634  /* Fill the data cells. */
1635  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++)
1636  {
1637  uint32_t pos = PREV_N_FIXED_COLS + cell_str_it - std::get<PL_INPUT>(parse_line).cbegin();
1638  gtk_list_store_set (store, &iter, pos, cell_str_it->c_str(), -1);
1639  }
1640  }
1641  gtk_tree_view_set_model (treeview, GTK_TREE_MODEL(store));
1642  gtk_tree_view_set_tooltip_column (treeview, PREV_COL_ERROR);
1643 
1644  /* Adjust treeview to go with the just created model. This consists of adding
1645  * or removing columns and resetting any parameters related to how
1646  * the columns and data should be rendered.
1647  */
1648 
1649  /* Start with counting the current number of columns (ntcols)
1650  * we have in the treeview */
1651  auto ntcols = gtk_tree_view_get_n_columns (treeview);
1652 
1653  /* Drop redundant columns if the model has less data columns than the new model
1654  * ntcols = n° of columns in treeview (1 error column + x data columns)
1655  * ncols = n° of columns in model (fixed state columns + x data columns)
1656  */
1657  while (ntcols > ncols - PREV_N_FIXED_COLS + 1)
1658  {
1659  auto col = gtk_tree_view_get_column (treeview, ntcols - 1);
1660  gtk_tree_view_column_clear (col);
1661  ntcols = gtk_tree_view_remove_column(treeview, col);
1662  }
1663 
1664  /* Insert columns if the model has more data columns than the treeview. */
1665  while (ntcols < ncols - PREV_N_FIXED_COLS + 1)
1666  {
1667  /* Default cell renderer is text, except for the first (error)
1668  column uses an icon */
1669  auto renderer = (ntcols == 0) ? gtk_cell_renderer_pixbuf_new () : gtk_cell_renderer_text_new ();
1670  auto col = gtk_tree_view_column_new ();
1671  gtk_tree_view_column_pack_start (col, renderer, false);
1672  ntcols = gtk_tree_view_append_column (treeview, col);
1673  }
1674 
1675  /* Reset column attributes as they are undefined after recreating the model */
1676  auto combostore = make_column_header_model_price ();
1677  for (uint32_t i = 0; i < ntcols; i++)
1678  preview_style_column (i, combostore);
1679 
1680  auto column_types = price_imp->column_types_price();
1681  auto any_of_type = [](std::vector<GncPricePropType>& column_types,
1682  GncPricePropType req_column_type) -> bool
1683  {
1684  return std::any_of (column_types.begin(), column_types.end(),
1685  [&req_column_type](GncPricePropType column_type) -> bool
1686  { return column_type == req_column_type; });
1687  };
1688 
1689  // look for a namespace column, clear the commodity combo
1690  if (any_of_type (column_types, GncPricePropType::FROM_NAMESPACE))
1691  {
1692  g_signal_handlers_block_by_func (commodity_selector, (gpointer) csv_price_imp_preview_commodity_sel_cb, this);
1693  set_commodity_for_combo (GTK_COMBO_BOX(commodity_selector), nullptr);
1694  g_signal_handlers_unblock_by_func (commodity_selector, (gpointer) csv_price_imp_preview_commodity_sel_cb, this);
1695  }
1696 
1697  // look for a symbol column, clear the commodity combo
1698  if (any_of_type (column_types, GncPricePropType::FROM_SYMBOL))
1699  {
1700  g_signal_handlers_block_by_func (commodity_selector, (gpointer) csv_price_imp_preview_commodity_sel_cb, this);
1701  set_commodity_for_combo (GTK_COMBO_BOX(commodity_selector), nullptr);
1702  g_signal_handlers_unblock_by_func (commodity_selector, (gpointer) csv_price_imp_preview_commodity_sel_cb, this);
1703  }
1704 
1705  // look for a currency column, clear the currency combo
1706  if (any_of_type (column_types, GncPricePropType::TO_CURRENCY))
1707  {
1708  g_signal_handlers_block_by_func (currency_selector, (gpointer) csv_price_imp_preview_currency_sel_cb, this);
1709  set_commodity_for_combo (GTK_COMBO_BOX(currency_selector), nullptr);
1710  g_signal_handlers_unblock_by_func (currency_selector, (gpointer) csv_price_imp_preview_currency_sel_cb, this);
1711  }
1712 
1713  /* Release our reference for the stores to allow proper memory management. */
1714  g_object_unref (store);
1715  g_object_unref (combostore);
1716 
1717  /* Make the things actually appear. */
1718  gtk_widget_show_all (GTK_WIDGET(treeview));
1719 }
1720 
1721 /* Update the preview page based on the current state of the importer.
1722  * Should be called when settings are changed.
1723  */
1724 void
1725 CsvImpPriceAssist::preview_refresh ()
1726 {
1727  // Cache skip settings. Updating the widgets one by one
1728  // triggers a callback that transfers all skip widgets'
1729  // values to settings. So by the time the next widget value
1730  // is to be set, that widget's 'new' setting has already been
1731  // replaced by its old setting preveneting us from using it
1732  // here sensibly.
1733 
1734  auto skip_start_lines = price_imp->skip_start_lines();
1735  auto skip_end_lines = price_imp->skip_end_lines();
1736  auto skip_alt_lines = price_imp->skip_alt_lines();
1737 
1738  // Set start row
1739  auto adj = gtk_spin_button_get_adjustment (start_row_spin);
1740  gtk_adjustment_set_upper (adj, price_imp->m_parsed_lines.size());
1741  gtk_spin_button_set_value (start_row_spin, skip_start_lines);
1742 
1743  // Set end row
1744  adj = gtk_spin_button_get_adjustment (end_row_spin);
1745  gtk_adjustment_set_upper (adj, price_imp->m_parsed_lines.size());
1746  gtk_spin_button_set_value (end_row_spin, skip_end_lines);
1747 
1748  // Set Alternate rows
1749  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(skip_alt_rows_button),
1750  skip_alt_lines);
1751 
1752  // Set over-write indicator
1753  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(over_write_cbutton),
1754  price_imp->over_write());
1755 
1756  // Set Import Format
1757  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(csv_button),
1758  (price_imp->file_format() == GncImpFileFormat::CSV));
1759  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(fixed_button),
1760  (price_imp->file_format() != GncImpFileFormat::CSV));
1761 
1762  // This section deals with the combo's and character encoding
1763  gtk_combo_box_set_active (GTK_COMBO_BOX(date_format_combo),
1764  price_imp->date_format());
1765  gtk_combo_box_set_active (GTK_COMBO_BOX(currency_format_combo),
1766  price_imp->currency_format());
1767  go_charmap_sel_set_encoding (encselector, price_imp->encoding().c_str());
1768 
1769  // Set the commodity and currency combos
1770  set_commodity_for_combo(GTK_COMBO_BOX(commodity_selector),
1771  price_imp->from_commodity());
1772 
1773  set_commodity_for_combo(GTK_COMBO_BOX(currency_selector),
1774  price_imp->to_currency());
1775 
1776  // Handle separator checkboxes and custom field, only relevant if the file format is csv
1777  // Note we defer the change signal until all buttons have been updated
1778  // An early update may result in an incomplete tokenize run that would
1779  // cause our list of saved column types to be truncated
1780  if (price_imp->file_format() == GncImpFileFormat::CSV)
1781  {
1782  auto separators = price_imp->separators();
1783  const auto stock_sep_chars = std::string (" \t,:;-");
1784 
1785  for (int i = 0; i < SEP_NUM_OF_TYPES; i++)
1786  {
1787  g_signal_handlers_block_by_func (sep_button[i], (gpointer) csv_price_imp_preview_sep_button_cb, this);
1788  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(sep_button[i]),
1789  separators.find (stock_sep_chars[i]) != std::string::npos);
1790  g_signal_handlers_unblock_by_func (sep_button[i], (gpointer) csv_price_imp_preview_sep_button_cb, this);
1791  }
1792 
1793  // If there are any other separators in the separators string,
1794  // add them as custom separators
1795  auto pos = separators.find_first_of (stock_sep_chars);
1796  while (!separators.empty() && pos != std::string::npos)
1797  {
1798  separators.erase(pos);
1799  pos = separators.find_first_of (stock_sep_chars);
1800  }
1801  g_signal_handlers_block_by_func (custom_cbutton, (gpointer) csv_price_imp_preview_sep_button_cb, this);
1802  g_signal_handlers_block_by_func (custom_entry, (gpointer) csv_price_imp_preview_sep_button_cb, this);
1803 
1804  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(custom_cbutton),
1805  !separators.empty());
1806  gtk_entry_set_text (GTK_ENTRY(custom_entry), separators.c_str());
1807 
1808  g_signal_handlers_unblock_by_func (custom_cbutton, (gpointer) csv_price_imp_preview_sep_button_cb, this);
1809  g_signal_handlers_unblock_by_func (custom_entry, (gpointer) csv_price_imp_preview_sep_button_cb, this);
1810  try
1811  {
1812  price_imp->tokenize (false);
1813  }
1814  catch (std::range_error& err)
1815  {
1816  PERR ("CSV Tokenization Failed: %s", err.what ());
1817  }
1818  }
1819  // Repopulate the parsed data table
1820  g_idle_add ((GSourceFunc)csv_imp_preview_queue_rebuild_table, this);
1821 }
1822 
1823 /* Check if all selected data can be parsed sufficiently to continue
1824  */
1825 void CsvImpPriceAssist::preview_validate_settings ()
1826 {
1827  /* Allow the user to proceed only if there are no inconsistencies in the settings */
1828  auto error_msg = price_imp->verify();
1829  gtk_assistant_set_page_complete (csv_imp_asst, preview_page, error_msg.empty());
1830  gtk_label_set_markup(GTK_LABEL(instructions_label), error_msg.c_str());
1831  gtk_widget_set_visible (GTK_WIDGET(instructions_image), !error_msg.empty());
1832 }
1833 
1834 /*******************************************************
1835  * Assistant page prepare functions
1836  *******************************************************/
1837 
1838 void
1839 CsvImpPriceAssist::assist_file_page_prepare ()
1840 {
1841  /* Disable the "Next" Assistant Button */
1842  gtk_assistant_set_page_complete (csv_imp_asst, file_page, false);
1843  gtk_assistant_set_page_complete (csv_imp_asst, preview_page, false);
1844 
1845  /* Set the default directory */
1846  if (!m_final_file_name.empty())
1847  gtk_file_chooser_set_filename (GTK_FILE_CHOOSER(file_chooser),
1848  m_final_file_name.c_str());
1849  else
1850  {
1851  auto starting_dir = gnc_get_default_directory (GNC_PREFS_GROUP);
1852  if (starting_dir)
1853  {
1854  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(file_chooser), starting_dir);
1855  g_free (starting_dir);
1856  }
1857  }
1858 }
1859 
1860 void
1861 CsvImpPriceAssist::assist_preview_page_prepare ()
1862 {
1863  auto go_back = false;
1864 
1865 
1866  if (m_final_file_name != m_fc_file_name)
1867  {
1868  /* Load the file into parse_data. */
1869  price_imp = std::unique_ptr<GncPriceImport>(new GncPriceImport);
1870  /* Assume data is CSV. User can later override to Fixed Width if needed */
1871  try
1872  {
1873  price_imp->file_format (GncImpFileFormat::CSV);
1874  price_imp->load_file (m_fc_file_name);
1875  price_imp->tokenize (true);
1876 
1877  /* Get settings store and populate */
1878  preview_populate_settings_combo();
1879  gtk_combo_box_set_active (settings_combo, 0);
1880 
1881  // set over_write to false as default
1882  price_imp->over_write (false);
1883 
1884  /* Disable the "Next" Assistant Button */
1885  gtk_assistant_set_page_complete (csv_imp_asst, preview_page, false);
1886  }
1887  catch (std::ifstream::failure& e)
1888  {
1889  /* File loading failed ... */
1890  gnc_error_dialog (GTK_WINDOW(csv_imp_asst), "%s", e.what());
1891  go_back = true;
1892  }
1893  catch (std::range_error &e)
1894  {
1895  /* Parsing failed ... */
1896  gnc_error_dialog (GTK_WINDOW(csv_imp_asst), "%s", _(e.what()));
1897  go_back = true;
1898  }
1899  }
1900 
1901  if (go_back)
1902  gtk_assistant_previous_page (csv_imp_asst);
1903  else
1904  {
1905  m_final_file_name = m_fc_file_name;
1906  preview_refresh ();
1907 
1908  /* Load the data into the treeview. */
1909  g_idle_add ((GSourceFunc)csv_imp_preview_queue_rebuild_table, this);
1910  }
1911 }
1912 
1913 void
1914 CsvImpPriceAssist::assist_confirm_page_prepare ()
1915 {
1916  /* Confirm Page */
1917 }
1918 
1919 void
1920 CsvImpPriceAssist::assist_summary_page_prepare ()
1921 {
1922  auto text = std::string("<span size=\"medium\"><b>");
1923  /* Translators: This is a ngettext(3) message, %d is the number of prices added */
1924  auto added_str = g_strdup_printf (ngettext ("%d added price",
1925  "%d added prices",
1926  price_imp->m_prices_added),
1927  price_imp->m_prices_added);
1928  /* Translators: This is a ngettext(3) message, %d is the number of duplicate prices */
1929  auto dupl_str = g_strdup_printf (ngettext ("%d duplicate price",
1930  "%d duplicate prices",
1931  price_imp->m_prices_duplicated),
1932  price_imp->m_prices_duplicated);
1933  /* Translators: This is a ngettext(3) message, %d is the number of replaced prices */
1934  auto repl_str = g_strdup_printf (ngettext ("%d replaced price",
1935  "%d replaced prices",
1936  price_imp->m_prices_replaced),
1937  price_imp->m_prices_replaced);
1938  auto msg = g_strdup_printf (
1939  _("The prices were imported from file '%s'.\n\n"
1940  "Import summary:\n"
1941  "- %s\n"
1942  "- %s\n"
1943  "- %s"),
1944  m_final_file_name.c_str(), added_str, dupl_str,repl_str);
1945  text += msg;
1946  text += "</b></span>";
1947 
1948  g_free (added_str);
1949  g_free (dupl_str);
1950  g_free (repl_str);
1951 
1952  gtk_label_set_markup (GTK_LABEL(summary_label), text.c_str());
1953 }
1954 
1955 void
1956 CsvImpPriceAssist::assist_prepare_cb (GtkWidget *page)
1957 {
1958  if (page == file_page)
1959  assist_file_page_prepare ();
1960  else if (page == preview_page)
1961  assist_preview_page_prepare ();
1962  else if (page == confirm_page)
1963  assist_confirm_page_prepare ();
1964  else if (page == summary_page)
1965  assist_summary_page_prepare ();
1966 }
1967 
1968 void
1969 CsvImpPriceAssist::assist_finish ()
1970 {
1971  /* Start the import */
1972  /* Create prices from the parsed data */
1973  try
1974  {
1975  price_imp->create_prices ();
1976  gnc_gui_refresh_all ();
1977  }
1978  catch (const std::invalid_argument& err)
1979  {
1980  /* Oops! This shouldn't happen when using the import assistant !
1981  * Inform the user and go back to the preview page.
1982  */
1983  gnc_error_dialog (GTK_WINDOW(csv_imp_asst),
1984  _("An unexpected error has occurred while creating prices. Please report this as a bug.\n\n"
1985  "Error message:\n%s"), err.what());
1986  gtk_assistant_set_current_page (csv_imp_asst, 2);
1987  }
1988 }
1989 
1990 void
1991 CsvImpPriceAssist::assist_compmgr_close ()
1992 {
1993  gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(csv_imp_asst));
1994 }
1995 
1996 static void
1997 csv_price_imp_close_handler (gpointer user_data)
1998 {
1999  auto info = (CsvImpPriceAssist*)user_data;
2000  gnc_unregister_gui_component_by_data (ASSISTANT_CSV_IMPORT_PRICE_CM_CLASS, info);
2001  info->assist_compmgr_close();
2002  delete info;
2003 }
2004 
2005 /********************************************************************\
2006  * gnc_file_csv_price_import *
2007  * opens up a assistant to import prices. *
2008  * *
2009  * Args: none *
2010  * Return: nothing *
2011 \********************************************************************/
2012 void
2014 {
2015  auto info = new CsvImpPriceAssist;
2016  gnc_register_gui_component (ASSISTANT_CSV_IMPORT_PRICE_CM_CLASS,
2017  nullptr, csv_price_imp_close_handler,
2018  info);
2019 }
Functions to load, save and get gui state.
void preview_update_separators(GtkWidget *widget)
Event handler for separator changes.
The actual PriceImport class It&#39;s intended to use in the following sequence of actions: ...
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
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
Class to convert a csv file into vector of string vectors.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
void remove(void)
Remove the preset from the state file.
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
void preview_update_col_type(GtkComboBox *cbox)
Event handler for the user selecting a new column type.
const preset_vec_price & get_import_presets_price(void)
Creates a vector of CsvPriceImpSettings which combines.
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
bool preset_is_reserved_name(const std::string &name)
Check whether name can be used as a preset name.
void preview_update_encoding(const char *encoding)
Event handler for a new encoding.
GList * gnc_commodity_table_get_namespaces(const gnc_commodity_table *table)
Return a list of all namespaces in the commodity table.
void preview_update_file_format()
Event handler for clicking one of the format type radio buttons.
void preview_update_fw_columns(GtkTreeView *treeview, GdkEventButton *event)
Event handler for clicking on column headers.
const char * gnc_commodity_get_fullname(const gnc_commodity *cm)
Retrieve the full name for the specified commodity.
Class to import prices from CSV or fixed width files.
Class convert a file with fixed with delimited contents into vector of string vectors.
CommodityList * gnc_commodity_table_get_commodities(const gnc_commodity_table *table, const char *name_space)
Return a list of all commodities in the commodity table that are in the given namespace.
const char * gnc_commodity_get_printname(const gnc_commodity *cm)
Retrieve the &#39;print&#39; name for the specified commodity.
Utility functions for convert uri in separate components and back.
void gnc_file_csv_price_import(void)
The gnc_file_csv_price_import() will let the user import the commodity prices from a file...
CSV Import Assistant.
static const std::vector< GncDateFormat > c_formats
A vector with all the date formats supported by the string constructor.
CSV Import Settings.