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