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