GnuCash c935c2f+
Loading...
Searching...
No Matches
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. */
70static 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{
82public:
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);
131private:
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
198extern "C"
199{
200void csv_price_imp_assist_prepare_cb (GtkAssistant *assistant, GtkWidget *page, CsvImpPriceAssist* info);
201void csv_price_imp_assist_close_cb (GtkAssistant *gtkassistant, CsvImpPriceAssist* info);
202void csv_price_imp_assist_finish_cb (GtkAssistant *gtkassistant, CsvImpPriceAssist* info);
203void csv_price_imp_file_activated_changed_cb (GtkFileChooser *chooser, CsvImpPriceAssist *info);
204void csv_price_imp_file_selection_changed_cb (GtkFileChooser *chooser, CsvImpPriceAssist *info);
205void csv_price_imp_preview_del_settings_cb (GtkWidget *button, CsvImpPriceAssist *info);
206void csv_price_imp_preview_save_settings_cb (GtkWidget *button, CsvImpPriceAssist *info);
207void csv_price_imp_preview_settings_sel_changed_cb (GtkComboBox *combo, CsvImpPriceAssist *info);
208void csv_price_imp_preview_settings_text_inserted_cb (GtkEditable *entry, gchar *new_text,
209 gint new_text_length, gint *position, CsvImpPriceAssist *info);
210void csv_price_imp_preview_settings_text_changed_cb (GtkEntry *entry, CsvImpPriceAssist *info);
211void csv_price_imp_preview_srow_cb (GtkSpinButton *spin, CsvImpPriceAssist *info);
212void csv_price_imp_preview_erow_cb (GtkSpinButton *spin, CsvImpPriceAssist *info);
213void csv_price_imp_preview_skiprows_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info);
214void csv_price_imp_preview_skiperrors_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info);
215void csv_price_imp_preview_overwrite_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info);
216void csv_price_imp_preview_sep_button_cb (GtkWidget* widget, CsvImpPriceAssist* info);
217void csv_price_imp_preview_sep_fixed_sel_cb (GtkToggleButton* csv_button, CsvImpPriceAssist* info);
218void csv_price_imp_preview_acct_sel_cb (GtkWidget* widget, CsvImpPriceAssist* info);
219void csv_price_imp_preview_enc_sel_cb (GOCharmapSel* selector, const char* encoding,
220 CsvImpPriceAssist* info);
221}
222
223void
224csv_price_imp_assist_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
225 CsvImpPriceAssist* info)
226{
227 info->assist_prepare_cb(page);
228}
229
230void
231csv_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
236void
237csv_price_imp_assist_finish_cb (GtkAssistant *assistant, CsvImpPriceAssist* info)
238{
239 info->assist_finish ();
240}
241
242void csv_price_imp_file_activated_changed_cb (GtkFileChooser *chooser, CsvImpPriceAssist *info)
243{
244 info->file_activated_cb();
245}
246
247void csv_price_imp_file_selection_changed_cb (GtkFileChooser *chooser, CsvImpPriceAssist *info)
248{
249 info->file_selection_changed_cb();
250}
251
252void csv_price_imp_preview_del_settings_cb (GtkWidget *button, CsvImpPriceAssist *info)
253{
254 info->preview_settings_delete();
255}
256
257void csv_price_imp_preview_save_settings_cb (GtkWidget *button, CsvImpPriceAssist *info)
258{
259 info->preview_settings_save();
260}
261
262void csv_price_imp_preview_settings_sel_changed_cb (GtkComboBox *combo, CsvImpPriceAssist *info)
263{
264 info->preview_settings_load();
265}
266
267void
268csv_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
288void
289csv_price_imp_preview_settings_text_changed_cb (GtkEntry *entry, CsvImpPriceAssist *info)
290{
291 info->preview_settings_name(entry);
292}
293
294void csv_price_imp_preview_srow_cb (GtkSpinButton *spin, CsvImpPriceAssist *info)
295{
296 info->preview_update_skipped_rows();
297}
298
299void csv_price_imp_preview_erow_cb (GtkSpinButton *spin, CsvImpPriceAssist *info)
300{
301 info->preview_update_skipped_rows();
302}
303
304void csv_price_imp_preview_skiprows_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info)
305{
306 info->preview_update_skipped_rows();
307}
308
309void csv_price_imp_preview_skiperrors_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info)
310{
311 info->preview_update_skipped_rows();
312}
313
314void csv_price_imp_preview_overwrite_cb (GtkToggleButton *checkbox, CsvImpPriceAssist *info)
315{
316 info->preview_over_write (gtk_toggle_button_get_active (checkbox));
317}
318
319void csv_price_imp_preview_sep_button_cb (GtkWidget* widget, CsvImpPriceAssist* info)
320{
321 info->preview_update_separators(widget);
322}
323
324void csv_price_imp_preview_sep_fixed_sel_cb (GtkToggleButton* csv_button, CsvImpPriceAssist* info)
325{
326 info->preview_update_file_format();
327}
328
329void csv_price_imp_preview_enc_sel_cb (GOCharmapSel* selector, const char* encoding,
330 CsvImpPriceAssist* info)
331{
332 info->preview_update_encoding(encoding);
333}
334
335static void csv_price_imp_preview_date_fmt_sel_cb (GtkComboBox* format_selector, CsvImpPriceAssist* info)
336{
337 info->preview_update_date_format();
338}
339
340static void csv_price_imp_preview_currency_fmt_sel_cb (GtkComboBox* format_selector, CsvImpPriceAssist* info)
341{
342 info->preview_update_currency_format();
343}
344
345enum GncCommColumn {DISPLAYED_COMM, SORT_COMM, COMM_PTR, SEP};
346
347static void csv_price_imp_preview_currency_sel_cb (GtkComboBox* currency_selector, CsvImpPriceAssist* info)
348{
349 info->preview_update_currency();
350}
351
352static 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
368static void csv_price_imp_preview_commodity_sel_cb (GtkComboBox* commodity_selector, CsvImpPriceAssist* info)
369{
370 info->preview_update_commodity();
371}
372
373static void csv_price_imp_preview_col_type_changed_cb (GtkComboBox* cbox, CsvImpPriceAssist* info)
374{
375 info->preview_update_col_type (cbox);
376}
377
378static gboolean
379csv_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
386static
387gnc_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
412static void
413set_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
444static
445GtkTreeModel *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 *******************************************************/
520CsvImpPriceAssist::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 *******************************************************/
726CsvImpPriceAssist::~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 */
737bool
738CsvImpPriceAssist::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 */
767void
768CsvImpPriceAssist::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 */
784void
785CsvImpPriceAssist::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 */
799void 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 */
824void 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
853void
854CsvImpPriceAssist::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 */
869void
870CsvImpPriceAssist::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 */
896void
897CsvImpPriceAssist::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 */
922void
923CsvImpPriceAssist::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 */
988void 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 */
1010void 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
1135void
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
1162void
1163CsvImpPriceAssist::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
1169void
1170CsvImpPriceAssist::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
1176void
1177CsvImpPriceAssist::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
1184void
1185CsvImpPriceAssist::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
1192static gboolean
1193csv_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 */
1202enum 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 */
1206enum 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
1215void
1216CsvImpPriceAssist::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
1304enum
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
1313static 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
1340uint32_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
1360gboolean
1361fixed_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
1401void
1402CsvImpPriceAssist::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/*======================================================================*/
1428void
1429CsvImpPriceAssist::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
1454void
1455CsvImpPriceAssist::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 */
1492void
1493CsvImpPriceAssist::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 */
1519GtkWidget*
1520CsvImpPriceAssist::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
1553void
1554CsvImpPriceAssist::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 */
1595static GtkTreeModel*
1596make_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 */
1613void 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 */
1731void
1732CsvImpPriceAssist::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 */
1838void 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
1851void
1852CsvImpPriceAssist::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
1873void
1874CsvImpPriceAssist::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
1927void
1928CsvImpPriceAssist::assist_confirm_page_prepare ()
1929{
1930 /* Confirm Page */
1931}
1932
1933void
1934CsvImpPriceAssist::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
1969void
1970CsvImpPriceAssist::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
1982void
1983CsvImpPriceAssist::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
2004void
2005CsvImpPriceAssist::assist_compmgr_close ()
2006{
2007 gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(csv_imp_asst));
2008}
2009
2010static void
2011csv_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\********************************************************************/
2026void
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}
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.
void preview_update_separators(GtkWidget *widget)
Event handler for separator changes.
void preview_update_encoding(const char *encoding)
Event handler for a new encoding.
void preview_update_fw_columns(GtkTreeView *treeview, GdkEventButton *event)
Event handler for clicking on column headers.
void preview_update_col_type(GtkComboBox *cbox)
Event handler for the user selecting a new column type.
void preview_update_file_format()
Event handler for clicking one of the format type radio buttons.
GnuCash Date class.
The actual PriceImport class It's intended to use in the following sequence of actions:
const preset_vec_price & get_import_presets_price(void)
Creates a vector of CsvPriceImpSettings which combines.
CSV Import Settings.
bool preset_is_reserved_name(const std::string &name)
Check whether name can be used as a preset name.
Class to import prices from CSV or fixed width files.
Functions to load, save and get gui state.
Class to convert a csv file into vector of string vectors.
Class convert a file with fixed with delimited contents into vector of string vectors.
utility functions for the GnuCash UI
Utility functions for convert uri in separate components and back.
const char * gnc_commodity_get_fullname(const gnc_commodity *cm)
Retrieve the full name for the specified commodity.
const char * gnc_commodity_get_printname(const gnc_commodity *cm)
Retrieve the 'print' name for the specified commodity.
GList * gnc_commodity_table_get_namespaces(const gnc_commodity_table *table)
Return a list of all namespaces in the commodity table.
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.
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
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.