~ubuntu-branches/ubuntu/precise/glom/precise-updates

« back to all changes in this revision

Viewing changes to glom/import_csv/dialog_import_csv.cc

  • Committer: Bazaar Package Importer
  • Author(s): Chris Coulson
  • Date: 2009-10-09 16:50:36 UTC
  • mfrom: (1.1.42 upstream)
  • Revision ID: james.westby@ubuntu.com-20091009165036-orinvwmohk838xxl
Tags: 1.12.2-0ubuntu1
* New upstream version:
  - FFE LP: #391664
* debian/control:
  - Bump python-gnome2-extras-dev build-dep to >= 2.25.3.
  - Bump libgdamm3.0-dev build-dep to libgdamm4.0-dev >= 3.99.14.
  - Change libgda3-dev build-dep to libgda-4.0-dev.
  - Change libgda3-postgres dependency to libgda-4.0-postgres.
  - Bump libgtkmm-2.4-dev build-dep to >= 2.14.
  - Add build-dep on libgconfmm-2.6-dev.
  - Bump libgoocanvasmm-dev build-dep to >= 0.14.0.
  - Remove build-dep on libbakery-2.6-dev.
  - Bump postgresql-8.3 dependency to postgresql-8.4.
  - Change scrollkeeper build-dep to rarian-compat.
  - Rename libglom{0,-dev} -> libglom-1.12-{0,dev}. Upstream include
    APIVER in the library name now.
* debian/rules:
  - Update --with-postgres-utils configure flag to point to the new
    path.
  - Drop deprecated --disable-scrollkeeper configure flag.
  - Update DEB_SHLIBDEPS_INCLUDE with new libglom-1.12-0 package name.
  - Don't include /usr/share/cdbs/1/rules/simple-patchsys.mk - there
    are currently no patches.
* debian/libglom-1.12-0.install:
  - Updated for new version.
* debian/libglom-1.12-dev.install:
  - Install pc and header files.
* debian/glom-doc.install:
  - Updated for new version.
* debian/glom.install:
  - Updated for new version.
* Fix debian/watch.
* Dropped obsolete 10-distro-install-postgres-change.patch.
* Built against latest libgoocanvasmm (LP: #428445).
* Also closes LP: #230007, LP: #393229, LP: #393231, LP: #394507,
  LP: #394887, LP: #394894, LP: #397409, LP: #381563.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Glom
 
2
 *
 
3
 * Copyright (C) 2001-2004 Murray Cumming
 
4
 *
 
5
 * This program is free software; you can redistribute it and/or
 
6
 * modify it under the terms of the GNU General Public License as
 
7
 * published by the Free Software Foundation; either version 2 of the
 
8
 * License, or (at your option) any later version.
 
9
 *
 
10
 * This program is distributed in the hope that it will be useful, but
 
11
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
13
 * General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public
 
16
 * License along with this program; if not, write to the
 
17
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 
18
 * Boston, MA 02111-1307, USA.
 
19
 */
 
20
 
 
21
#include "dialog_import_csv.h"
 
22
#include <glom/import_csv/file_encodings.h>
 
23
#include <libglom/libglom_config.h>
 
24
 
 
25
#include <libglom/data_structure/glomconversions.h>
 
26
 
 
27
#include <gtkmm/messagedialog.h>
 
28
#include <gtkmm/cellrenderercombo.h>
 
29
#include <glom/utils_ui.h>
 
30
#include <glibmm/i18n.h>
 
31
#include <cerrno>
 
32
 
 
33
namespace
 
34
{
 
35
 
 
36
// When auto-detecting the encoding, we try to read the file in these
 
37
// encodings, in order:
 
38
const char* AUTODETECT_ENCODINGS_CHARSETS[] = {
 
39
  "UTF-8",
 
40
  "ISO-8859-1",
 
41
  "ISO-8859-15",
 
42
  "UTF-16",
 
43
  "UCS-2",
 
44
  "UCS-4"
 
45
};
 
46
 
 
47
const guint N_AUTODETECT_ENCODINGS_CHARSETS = sizeof(AUTODETECT_ENCODINGS_CHARSETS)/sizeof(AUTODETECT_ENCODINGS_CHARSETS[0]);
 
48
 
 
49
 
 
50
Glib::ustring encoding_display(const Glib::ustring& name, const Glib::ustring& charset)
 
51
{
 
52
  if(charset.empty())
 
53
    return name;
 
54
  else
 
55
    return name + " (" + charset + ")";
 
56
}
 
57
 
 
58
} //anonymous namespace
 
59
 
 
60
namespace Glom
 
61
{
 
62
 
 
63
Dialog_Import_CSV::Dialog_Import_CSV(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
 
64
: Gtk::Dialog(cobject),
 
65
  m_auto_detect_encoding(),
 
66
  m_cols_count(-1)
 
67
{
 
68
  builder->get_widget("import_csv_fields", m_sample_view);
 
69
  builder->get_widget("import_csv_target_table", m_target_table);
 
70
  builder->get_widget("import_csv_encoding", m_encoding_combo);
 
71
  builder->get_widget("import_csv_encoding_info", m_encoding_info);
 
72
  builder->get_widget("import_csv_first_line_as_title", m_first_line_as_title);
 
73
  builder->get_widget("import_csv_sample_rows", m_sample_rows);
 
74
  builder->get_widget("import_csv_advice_label", m_advice_label);
 
75
  builder->get_widget("import_csv_error_label", m_error_label);
 
76
#ifdef GLIBMM_EXCEPTIONS_ENABLED  
 
77
  if(!m_sample_view || !m_encoding_combo || !m_target_table || !m_encoding_info || !m_first_line_as_title || !m_sample_rows || !m_error_label)
 
78
    throw std::runtime_error("Missing widgets from glade file for Dialog_Import_CSV");
 
79
#endif
 
80
 
 
81
  //Fill the list of encodings:
 
82
  m_encoding_model = Gtk::ListStore::create(m_encoding_columns);
 
83
 
 
84
  Gtk::TreeModel::iterator iter = m_encoding_model->append();
 
85
  (*iter)[m_encoding_columns.m_col_name] = _("Auto Detect");
 
86
 
 
87
  // Separator:
 
88
  m_encoding_model->append();
 
89
 
 
90
  const FileEncodings::type_list_encodings list_encodings =  FileEncodings::get_list_of_encodings();
 
91
  for(FileEncodings::type_list_encodings::const_iterator encodings_iter = list_encodings.begin(); encodings_iter  != list_encodings.end(); encodings_iter ++)
 
92
  {
 
93
    const FileEncodings::Encoding encoding = *encodings_iter;
 
94
    if(encoding.get_name().empty())
 
95
      continue;
 
96
 
 
97
    iter = m_encoding_model->append();
 
98
    Gtk::TreeModel::Row row = *iter;
 
99
    row[m_encoding_columns.m_col_name] = encoding.get_name();
 
100
    row[m_encoding_columns.m_col_charset] = encoding.get_charset();
 
101
  }
 
102
 
 
103
  m_sample_rows->set_value(2); //A sensible default.
 
104
 
 
105
  Gtk::CellRendererText* renderer = Gtk::manage(new Gtk::CellRendererText);
 
106
  m_encoding_combo->set_model(m_encoding_model);
 
107
  m_encoding_combo->pack_start(*renderer);
 
108
  m_encoding_combo->set_cell_data_func(*renderer, sigc::bind(sigc::mem_fun(*this, &Dialog_Import_CSV::encoding_data_func), sigc::ref(*renderer)));
 
109
  m_encoding_combo->set_row_separator_func(sigc::mem_fun(*this, &Dialog_Import_CSV::row_separator_func));
 
110
  m_encoding_combo->set_active(0);
 
111
 
 
112
  m_encoding_combo->signal_changed().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_combo_encoding_changed));
 
113
 
 
114
  // TODO: Reset parser encoding on selection changed.
 
115
  m_parser = std::auto_ptr<CsvParser>(new CsvParser(get_current_encoding().c_str()));
 
116
  m_parser->signal_file_read_error().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_parser_file_read_error));
 
117
  m_parser->signal_have_display_name().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_parser_have_display_name));
 
118
  m_parser->signal_encoding_error().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_parser_encoding_error));
 
119
  m_parser->signal_line_scanned().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_parser_line_scanned));
 
120
  m_parser->signal_state_changed().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_parser_state_changed));
 
121
 
 
122
  m_first_line_as_title->set_active(false);
 
123
  m_first_line_as_title->signal_toggled().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_first_line_as_title_toggled));
 
124
  m_sample_rows->signal_changed().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_sample_rows_changed));
 
125
 
 
126
  m_sample_view->set_headers_visible(m_first_line_as_title->get_active());
 
127
 
 
128
 
 
129
  //Warn the user about the numeric and date formats expected:
 
130
 
 
131
  //A date that is really really the date that we mean:
 
132
  tm the_c_time;
 
133
  memset(&the_c_time, 0, sizeof(the_c_time));
 
134
 
 
135
  //We mean 22nd November 2008:
 
136
  the_c_time.tm_year = 2008 - 1900; //C years start are the AD year - 1900. So, 01 is 1901.
 
137
  the_c_time.tm_mon = 11 - 1; //C months start at 0.
 
138
  the_c_time.tm_mday = 22; //starts at 1
 
139
 
 
140
  //Get the ISO (not current locale) text representation:
 
141
  const Glib::ustring date_text = Glom::Conversions::format_date(the_c_time, std::locale() /* ignored */, true /* iso_format */);
 
142
  const Glib::ustring advice = Glib::ustring::compose(_("Note that the source file should contain numbers and dates in international ISO format. For instance, 22nd November 2008 should be %1."), date_text);
 
143
  m_advice_label->set_text(advice);
 
144
  std::cout << "DEBUG: advice=" << advice << std::endl;
 
145
 
 
146
  clear();
 
147
}
 
148
 
 
149
CsvParser::State Dialog_Import_CSV::get_parser_state() const
 
150
{
 
151
  return m_parser->get_state();
 
152
}
 
153
 
 
154
Glib::ustring Dialog_Import_CSV::get_target_table_name() const
 
155
{
 
156
  return m_target_table->get_text();
 
157
}
 
158
 
 
159
const Glib::ustring& Dialog_Import_CSV::get_file_uri() const
 
160
{
 
161
  return m_file_uri;
 
162
}
 
163
 
 
164
void Dialog_Import_CSV::import(const Glib::ustring& uri, const Glib::ustring& into_table)
 
165
{
 
166
  clear();
 
167
 
 
168
  Document* document = get_document();
 
169
  if(!document)
 
170
  {
 
171
    show_error_dialog(_("No Document Available"), _("You need to open a document to import the data into a table."));
 
172
  }
 
173
  else
 
174
  {
 
175
    // Make the relevant widgets sensitive. We will make them insensitive
 
176
    // again when a (non-recoverable) error occurs.
 
177
    m_sample_view->set_sensitive();
 
178
    m_encoding_combo->set_sensitive();
 
179
    m_first_line_as_title->set_sensitive();
 
180
    m_sample_rows->set_sensitive();
 
181
 
 
182
    set_title(_("Import From CSV File"));
 
183
    m_target_table->set_markup("<b>" + Glib::Markup::escape_text(into_table) + "</b>");
 
184
 
 
185
    m_field_model = Gtk::ListStore::create(m_field_columns);
 
186
    Gtk::TreeModel::iterator tree_iter = m_field_model->append();
 
187
    (*tree_iter)[m_field_columns.m_col_field_name] = _("<None>");
 
188
 
 
189
    const Document::type_vec_fields fields(document->get_table_fields(into_table));
 
190
    for(Document::type_vec_fields::const_iterator iter = fields.begin(); iter != fields.end(); ++ iter)
 
191
    {
 
192
      sharedptr<Field> field = *iter;
 
193
      if(!field)
 
194
        continue;
 
195
 
 
196
      // Don't allow the primary key to be selected when it is an auto
 
197
      // increment key, since the value for the primary key is chosen
 
198
      // automatically anyway.
 
199
      if(!field->get_primary_key() || !field->get_auto_increment())
 
200
      {
 
201
        Gtk::TreeModel::iterator tree_iter = m_field_model->append();
 
202
        (*tree_iter)[m_field_columns.m_col_field] = *iter;
 
203
        (*tree_iter)[m_field_columns.m_col_field_name] = (*iter)->get_name();
 
204
      }
 
205
    }
 
206
 
 
207
    // Create the sorted version of this model, 
 
208
    // so the user sees the fields in alphabetical order:
 
209
    m_field_model_sorted = Gtk::TreeModelSort::create(m_field_model);
 
210
    m_field_model_sorted->set_sort_column(m_field_columns.m_col_field_name, Gtk::SORT_ASCENDING);
 
211
 
 
212
    m_file_uri = uri;
 
213
    m_parser->set_file_and_start_parsing(uri);
 
214
  }
 
215
}
 
216
 
 
217
guint Dialog_Import_CSV::get_column_count() const
 
218
{
 
219
  return m_cols_count;
 
220
}
 
221
 
 
222
sharedptr<const Field> Dialog_Import_CSV::get_field_for_column(guint col) const
 
223
{
 
224
  return m_fields[col];
 
225
}
 
226
 
 
227
const Glib::ustring& Dialog_Import_CSV::get_data(guint row, guint col)
 
228
{
 
229
  if(m_first_line_as_title->get_active())
 
230
    ++row;
 
231
 
 
232
  return m_parser->get_data(row, col);
 
233
}
 
234
 
 
235
CsvParser& Dialog_Import_CSV::get_parser()
 
236
{
 
237
  return *(m_parser.get());
 
238
}
 
239
 
 
240
void Dialog_Import_CSV::clear()
 
241
{
 
242
  // TODO: Do we explicitely need to cancel async operations?
 
243
  // TODO: Disconnect idle handlers
 
244
  m_sample_model.reset();
 
245
  m_sample_view->remove_all_columns();
 
246
  m_sample_view->set_model(m_sample_model);
 
247
  m_field_model.reset();
 
248
  m_field_model_sorted.reset();
 
249
  m_fields.clear();
 
250
  m_file_uri.clear();
 
251
  m_parser->clear();
 
252
  //m_parser.reset(0);
 
253
  m_encoding_info->set_text("");
 
254
  m_sample_view->set_sensitive(false);
 
255
  m_encoding_combo->set_sensitive(false);
 
256
  m_first_line_as_title->set_sensitive(false);
 
257
  m_sample_rows->set_sensitive(false);
 
258
 
 
259
  validate_primary_key();
 
260
}
 
261
 
 
262
void Dialog_Import_CSV::show_error_dialog(const Glib::ustring&, const Glib::ustring& secondary)
 
263
{
 
264
  Utils::show_ok_dialog(_("Error Importing CSV File"),
 
265
     secondary, *this, Gtk::MESSAGE_ERROR);
 
266
}
 
267
 
 
268
void Dialog_Import_CSV::encoding_data_func(const Gtk::TreeModel::iterator& iter, Gtk::CellRendererText& renderer)
 
269
{
 
270
  const Glib::ustring name = (*iter)[m_encoding_columns.m_col_name];
 
271
  const Glib::ustring charset = (*iter)[m_encoding_columns.m_col_charset];
 
272
 
 
273
  renderer.set_property("text", encoding_display(name, charset));
 
274
}
 
275
 
 
276
bool Dialog_Import_CSV::row_separator_func(const Glib::RefPtr<Gtk::TreeModel>& /* model */, const Gtk::TreeModel::iterator& iter) const
 
277
{
 
278
  return (*iter)[m_encoding_columns.m_col_name] == "";
 
279
}
 
280
 
 
281
 
 
282
void Dialog_Import_CSV::on_combo_encoding_changed()
 
283
{
 
284
  const int active = m_encoding_combo->get_active_row_number();
 
285
 
 
286
  switch(active)
 
287
  {
 
288
  case -1: // No active item
 
289
    g_assert_not_reached();
 
290
    break;
 
291
  case 0: // Auto-Detect
 
292
    // Begin with first encoding
 
293
    m_auto_detect_encoding = 0;
 
294
    break;
 
295
  default: // Some specific encoding
 
296
    m_auto_detect_encoding = -1;
 
297
    break;
 
298
  }
 
299
 
 
300
  // Parse from beginning with new encoding:
 
301
  m_parser->set_encoding(get_current_encoding());
 
302
  m_parser->set_file_and_start_parsing(m_file_uri);
 
303
}
 
304
 
 
305
void Dialog_Import_CSV::on_first_line_as_title_toggled()
 
306
{
 
307
  // Ignore if we don't have a model yet, we will take care of the option
 
308
  // later when inserting items into it.
 
309
  if(!m_sample_model)
 
310
    return;
 
311
 
 
312
  if(m_first_line_as_title->get_active())
 
313
  {
 
314
    m_sample_view->set_headers_visible(true);
 
315
    Gtk::TreeModel::Path path("1");
 
316
    Gtk::TreeModel::iterator iter = m_sample_model->get_iter(path);
 
317
 
 
318
    // Remove the first row from the view
 
319
    if(iter && (*iter)[m_sample_columns.m_col_row] == 0)
 
320
    {
 
321
      m_sample_model->erase(iter);
 
322
 
 
323
      // Add another row to the end, if one is loaded.
 
324
      const guint last_index = m_sample_model->children().size();
 
325
      iter = m_sample_model->append();
 
326
      (*iter)[m_sample_columns.m_col_row] = last_index;
 
327
    }
 
328
  }
 
329
  else
 
330
  {
 
331
    m_sample_view->set_headers_visible(false);
 
332
 
 
333
    // Check whether row 0 is displayed
 
334
    Gtk::TreeModel::Path path("1");
 
335
    Gtk::TreeModel::iterator iter = m_sample_model->get_iter(path);
 
336
 
 
337
    //if((!iter || (*iter)[m_sample_columns.m_col_row] != 0) && !m_parser->get_rows_empty() && m_sample_rows->get_value_as_int() > 0)
 
338
    if((!iter || (*iter)[m_sample_columns.m_col_row] != 0) &&
 
339
        m_sample_rows->get_value_as_int() > 0)
 
340
    {
 
341
      // Add first row to model
 
342
      if(!iter)
 
343
        iter = m_sample_model->append();
 
344
      else
 
345
        iter = m_sample_model->insert(iter);
 
346
 
 
347
      (*iter)[m_sample_columns.m_col_row] = 0;
 
348
 
 
349
      // Remove last row if we hit the limit
 
350
      const guint sample_rows = m_sample_model->children().size() - 1;
 
351
      if(sample_rows > static_cast<guint>(m_sample_rows->get_value_as_int()))
 
352
      {
 
353
        //m_sample_model->erase(m_sample_model->children().rbegin());
 
354
        path[0] = sample_rows;
 
355
        iter = m_sample_model->get_iter(path);
 
356
        m_sample_model->erase(iter);
 
357
      }
 
358
    }
 
359
  }
 
360
}
 
361
 
 
362
void Dialog_Import_CSV::on_sample_rows_changed()
 
363
{
 
364
  // Ignore if we don't have a model yet, we will take care of the option
 
365
  // later when inserting items into it.
 
366
  if(!m_sample_model)
 
367
    return;
 
368
 
 
369
  const guint current_sample_rows = m_sample_model->children().size() - 1;
 
370
  const guint new_sample_rows = m_sample_rows->get_value_as_int();
 
371
 
 
372
  if(current_sample_rows > new_sample_rows)
 
373
  {
 
374
    // +1 for the "target field" row
 
375
    Gtk::TreeModel::Path path(1);
 
376
    path[0] = new_sample_rows + 1;
 
377
    Gtk::TreeModel::iterator iter = m_sample_model->get_iter(path);
 
378
 
 
379
    while(iter != m_sample_model->children().end())
 
380
      iter = m_sample_model->erase(iter);
 
381
  }
 
382
  else
 
383
  {
 
384
    // Find index of first row to add
 
385
    guint row_index = current_sample_rows;
 
386
    if(m_first_line_as_title->get_active())
 
387
      ++row_index;
 
388
 
 
389
    for(guint i = current_sample_rows; i < new_sample_rows; ++i, ++row_index)
 
390
    {
 
391
      Gtk::TreeModel::iterator iter = m_sample_model->append();
 
392
      (*iter)[m_sample_columns.m_col_row] = row_index;
 
393
    }
 
394
  }
 
395
}
 
396
 
 
397
Glib::ustring Dialog_Import_CSV::get_current_encoding() const
 
398
{
 
399
  Gtk::TreeModel::iterator iter = m_encoding_combo->get_active();
 
400
  const Glib::ustring encoding = (*iter)[m_encoding_columns.m_col_charset];
 
401
 
 
402
  if(encoding.empty())
 
403
  {
 
404
    // Auto-Detect:
 
405
    g_assert(m_auto_detect_encoding != -1);
 
406
    return AUTODETECT_ENCODINGS_CHARSETS[m_auto_detect_encoding];
 
407
  }
 
408
 
 
409
  return encoding;
 
410
}
 
411
 
 
412
void Dialog_Import_CSV::begin_parse()
 
413
{
 
414
  if(m_auto_detect_encoding != -1)
 
415
  {
 
416
    const char* encoding_charset = AUTODETECT_ENCODINGS_CHARSETS[m_auto_detect_encoding];
 
417
    const Glib::ustring encoding_name = FileEncodings::get_name_of_charset(encoding_charset);
 
418
    m_encoding_info->set_text(Glib::ustring::compose(_("Encoding detected as: %1"), encoding_display(encoding_name, encoding_charset)));
 
419
  }
 
420
  else
 
421
    m_encoding_info->set_text("");
 
422
 
 
423
  // Clear sample preview since we reparse everything, perhaps with
 
424
  // another encoding.
 
425
  m_sample_model.reset();
 
426
  m_sample_view->remove_all_columns();
 
427
  m_sample_view->set_model(m_sample_model); // Empty model
 
428
  m_parser->clear();
 
429
 
 
430
  m_parser->set_encoding(get_current_encoding().c_str());
 
431
 
 
432
  // Allow the Import button to be pressed when a field for the primary key
 
433
  // field is set. When the import button is pressed without the file being
 
434
  // fully loaded, the import progress waits for us to load the rest.
 
435
  validate_primary_key();
 
436
}
 
437
 
 
438
void Dialog_Import_CSV::on_parser_encoding_error()
 
439
{
 
440
  m_parser->clear();
 
441
  // Clear sample preview (TODO: Let it visible, and only remove when reparsing?)
 
442
  m_sample_model.reset();
 
443
  m_sample_view->remove_all_columns();
 
444
  m_sample_view->set_model(m_sample_model); // Empty model
 
445
 
 
446
  // Don't allow the import button to be pressed when an error occured. This
 
447
  // would not make sense since we cleared all the parsed row data anyway.
 
448
  validate_primary_key();
 
449
 
 
450
  // If we are auto-detecting the encoding, then try the next one
 
451
  if(m_auto_detect_encoding != -1)
 
452
  {
 
453
    ++ m_auto_detect_encoding;
 
454
    if(static_cast<guint>(m_auto_detect_encoding) < N_AUTODETECT_ENCODINGS_CHARSETS)
 
455
      begin_parse();
 
456
    else
 
457
      m_encoding_info->set_text(_("Encoding detection failed. Please manually choose one from the box."));
 
458
  }
 
459
  else
 
460
  {
 
461
    m_encoding_info->set_text(_("The file contains data not in the specified encoding. Please choose another one, or try \"Auto Detect\"."));
 
462
  }
 
463
}
 
464
 
 
465
void Dialog_Import_CSV::on_parser_line_scanned(CsvParser::type_row_strings row, unsigned int row_number)
 
466
{
 
467
  // This is the first line read if there is no model yet:
 
468
  if(!m_sample_model)
 
469
  {
 
470
    setup_sample_model(row);
 
471
    Gtk::TreeModel::iterator iter = m_sample_model->append();
 
472
    // -1 means the row to select target fields (see special handling in cell data funcs)
 
473
    (*iter)[m_sample_columns.m_col_row] = -1;
 
474
  }
 
475
 
 
476
  // Add the row to the treeview if there are not yet as much sample rows
 
477
  // as the user has chosen (note the first row is to choose the target fields,
 
478
  // not a sample row, which is why we do -1 here).
 
479
  const guint sample_rows = m_sample_model->children().size() - 1;
 
480
 
 
481
  // Don't add if this is the first line and m_first_line_as_title is active:
 
482
  if(row_number > 1 || !m_first_line_as_title->get_active())
 
483
  {
 
484
    if(sample_rows < static_cast<guint>(m_sample_rows->get_value_as_int()))
 
485
    {
 
486
      Gtk::TreeModel::iterator tree_iter = m_sample_model->append();
 
487
      (*tree_iter)[m_sample_columns.m_col_row] = row_number;
 
488
    }
 
489
  }
 
490
}
 
491
 
 
492
void Dialog_Import_CSV::setup_sample_model(const CsvParser::type_row_strings& row)
 
493
{
 
494
  m_sample_model = Gtk::ListStore::create(m_sample_columns);
 
495
  m_sample_view->set_model(m_sample_model);
 
496
 
 
497
  // Create field vector that contains the fields into which to import
 
498
  // the data.
 
499
  m_fields.resize(row.size());
 
500
 
 
501
  // Start with a column showing the line number.
 
502
  Gtk::CellRendererText* text = Gtk::manage(new Gtk::CellRendererText);
 
503
  Gtk::TreeViewColumn* col = Gtk::manage(new Gtk::TreeViewColumn(_("Line")));
 
504
  col->pack_start(*text, false);
 
505
  col->set_cell_data_func(*text, sigc::mem_fun(*this, &Dialog_Import_CSV::line_data_func));
 
506
  m_sample_view->append_column(*col);
 
507
 
 
508
  m_cols_count = row.size();
 
509
 
 
510
  for(guint i = 0; i < m_cols_count; ++ i)
 
511
  {
 
512
    const Glib::ustring& data = row[i];
 
513
    m_sample_view->append_column(*Gtk::manage(create_sample_column(data, i)));
 
514
  }
 
515
}
 
516
 
 
517
Gtk::TreeViewColumn* Dialog_Import_CSV::create_sample_column(const Glib::ustring& title, guint index)
 
518
{
 
519
  Gtk::TreeViewColumn* col = new Gtk::TreeViewColumn(title);
 
520
  Gtk::CellRendererCombo* cell = create_sample_cell(index);
 
521
  col->pack_start(*Gtk::manage(cell), true);
 
522
  col->set_cell_data_func(*cell, sigc::bind(sigc::mem_fun(*this, &Dialog_Import_CSV::field_data_func), index));
 
523
  col->set_sizing(Gtk::TREE_VIEW_COLUMN_AUTOSIZE);
 
524
  return col;
 
525
}
 
526
 
 
527
Gtk::CellRendererCombo* Dialog_Import_CSV::create_sample_cell(guint index)
 
528
{
 
529
  Gtk::CellRendererCombo* cell = new Gtk::CellRendererCombo;
 
530
#ifdef GLIBMM_PROPERTIES_ENABLED
 
531
  cell->property_model() = m_field_model_sorted;
 
532
  cell->property_text_column() = 0;
 
533
  cell->property_has_entry() = false;
 
534
#else
 
535
  cell->set_property("model", m_field_model_sorted);
 
536
  cell->set_property("text-column", 0);
 
537
  cell->set_property("has_entry", false);
 
538
#endif
 
539
  cell->signal_edited().connect(sigc::bind(sigc::mem_fun(*this, &Dialog_Import_CSV::on_field_edited), index));
 
540
 
 
541
  return cell;
 
542
}
 
543
 
 
544
void Dialog_Import_CSV::line_data_func(Gtk::CellRenderer* renderer, const Gtk::TreeModel::iterator& iter)
 
545
{
 
546
  const int row = (*iter)[m_sample_columns.m_col_row];
 
547
  Gtk::CellRendererText* renderer_text = dynamic_cast<Gtk::CellRendererText*>(renderer);
 
548
#ifdef GLIBMM_EXCEPTIONS_ENABLED
 
549
  if(!renderer_text)
 
550
    throw std::logic_error("CellRenderer is not a CellRendererText in line_data_func");
 
551
#endif
 
552
 
 
553
  if(row == -1)
 
554
    renderer_text->set_property("text", Glib::ustring(_("Target Field")));
 
555
  else
 
556
    renderer_text->set_property("text", Glib::ustring::compose("%1", row + 1));
 
557
}
 
558
 
 
559
void Dialog_Import_CSV::field_data_func(Gtk::CellRenderer* renderer, const Gtk::TreeModel::iterator& iter, guint column_number)
 
560
{
 
561
  const int row = (*iter)[m_sample_columns.m_col_row];
 
562
  Gtk::CellRendererCombo* renderer_combo = dynamic_cast<Gtk::CellRendererCombo*>(renderer);
 
563
#ifdef GLIBMM_EXCEPTIONS_ENABLED
 
564
  if(!renderer_combo) throw std::logic_error("CellRenderer is not a CellRendererCombo in field_data_func");
 
565
#endif
 
566
 
 
567
  Glib::ustring text;
 
568
  bool editable = false;
 
569
 
 
570
  if(row == -1)
 
571
  {
 
572
    sharedptr<Field> field = m_fields[column_number];
 
573
    if(field)
 
574
      text = field->get_name();
 
575
    else
 
576
      text = _("<None>");
 
577
 
 
578
    editable = true;
 
579
  }
 
580
  else
 
581
  {
 
582
    // Convert to currently chosen field, if any, and back, too see how it
 
583
    // looks like when imported:
 
584
 
 
585
    if(column_number < m_fields.size())
 
586
    {
 
587
      sharedptr<Field> field = m_fields[column_number];
 
588
 
 
589
      if(row != -1) // && static_cast<unsigned int>(row) < m_parser->get_rows_count())
 
590
      {
 
591
          const Glib::ustring& orig_text = m_parser->get_data(row, column_number);
 
592
 
 
593
          if(field)
 
594
          {
 
595
            if(field->get_glom_type() != Field::TYPE_IMAGE)
 
596
            {
 
597
              /* Exported data is always stored in postgres format */
 
598
              bool success = false;
 
599
              const Gnome::Gda::Value value = field->from_file_format(orig_text, success);
 
600
              if(!success)
 
601
                text = _("<Import Failure>");
 
602
              else
 
603
                text = Glom::Conversions::get_text_for_gda_value(field->get_glom_type(), value);
 
604
            }
 
605
            else
 
606
            {
 
607
              // TODO: It is too slow to create the picture here. Maybe we should
 
608
              // create it once and cache it. We could also think about using a
 
609
              // GtkCellRendererPixbuf to show it, then.
 
610
              if(!orig_text.empty() && orig_text != "NULL")
 
611
                text = _("<Picture>");
 
612
            }
 
613
          }
 
614
          else
 
615
          {
 
616
            // TODO: Should we unescape the field's content?
 
617
            text = orig_text;
 
618
          }
 
619
 
 
620
          if(text.length() > 32)
 
621
          {
 
622
            text.erase(32);
 
623
            text.append("…");
 
624
          }
 
625
 
 
626
          editable = false;
 
627
      }
 
628
    }
 
629
  }
 
630
  
 
631
  renderer_combo->set_property("text", text);
 
632
  renderer_combo->set_property("editable", editable);
 
633
}
 
634
 
 
635
 
 
636
/**  Parse a row from a .cvs file. Note that this "line" might have newline 
 
637
  *  characters inside one field value, inside quotes.
 
638
  **/
 
639
void Dialog_Import_CSV::on_field_edited(const Glib::ustring& path, const Glib::ustring& new_text, guint column_number)
 
640
{
 
641
  Gtk::TreeModel::Path treepath(path);
 
642
  Gtk::TreeModel::iterator iter = m_sample_model->get_iter(treepath);
 
643
 
 
644
  // Lookup field indicated by new_text
 
645
  const Gtk::TreeNodeChildren& children = m_field_model->children();
 
646
  for(Gtk::TreeModel::iterator field_iter = children.begin(); field_iter != children.end(); ++ field_iter)
 
647
  {
 
648
    if( (*field_iter)[m_field_columns.m_col_field_name] == new_text)
 
649
    {
 
650
      sharedptr<Field> field = (*field_iter)[m_field_columns.m_col_field];
 
651
      // Check whether another column is already using that field
 
652
      type_vec_fields::iterator vec_field_iter = std::find(m_fields.begin(), m_fields.end(), field);
 
653
      // Reset the old column since two different columns cannot be imported into the same field
 
654
      if(vec_field_iter != m_fields.end()) *vec_field_iter = sharedptr<Field>();
 
655
 
 
656
      m_fields[column_number] = field;
 
657
 
 
658
      // Update the rows, so they are redrawn, doing a conversion to the new type.
 
659
      const Gtk::TreeNodeChildren& sample_children = m_sample_model->children();
 
660
      // Create a TreeModel::Path with initial index 0. We need a TreeModel::Path for the row_changed() call
 
661
      Gtk::TreeModel::Path path("0");
 
662
 
 
663
      for(Gtk::TreeModel::iterator sample_iter = sample_children.begin(); sample_iter != sample_children.end(); ++ sample_iter)
 
664
      {
 
665
        if(sample_iter != iter)
 
666
          m_sample_model->row_changed(path, sample_iter);
 
667
 
 
668
        path.next();
 
669
      }
 
670
 
 
671
      validate_primary_key();
 
672
      break;
 
673
    }
 
674
  }
 
675
}
 
676
 
 
677
void Dialog_Import_CSV::validate_primary_key()
 
678
{
 
679
  if(get_parser_state() == (CsvParser::STATE_NONE | CsvParser::STATE_ENCODING_ERROR))
 
680
  {
 
681
    m_error_label->hide();
 
682
    set_response_sensitive(Gtk::RESPONSE_ACCEPT, false);
 
683
  }
 
684
  else
 
685
  {
 
686
    // Allow the import button to be pressed when the value for the primary key
 
687
    // has been chosen:
 
688
    sharedptr<Field> primary_key = get_field_primary_key_for_table(get_target_table_name());
 
689
    bool primary_key_selected = false;
 
690
 
 
691
    if(primary_key && !primary_key->get_auto_increment())
 
692
    {
 
693
      // If m_rows is empty, then no single line was read from the file yet,
 
694
      // and the m_fields array is not up to date. It is set in handle_line()
 
695
      // when the first line is parsed.
 
696
      primary_key_selected = false;
 
697
      if(!m_parser->get_rows_empty())
 
698
      {
 
699
        for(type_vec_fields::iterator iter = m_fields.begin(); iter != m_fields.end(); ++ iter)
 
700
        {
 
701
          if(*iter == primary_key)
 
702
          {
 
703
            primary_key_selected = true;
 
704
            break;
 
705
          }
 
706
        }
 
707
      }
 
708
 
 
709
      if(!primary_key_selected)
 
710
        m_error_label->set_markup(Glib::ustring::compose(_("One column needs to be assigned the table's primary key (<b>%1</b>) as target field before importing"), Glib::Markup::escape_text(primary_key->get_name())));
 
711
    }
 
712
    else
 
713
    {
 
714
      // auto_increment primary keys always work since their value is
 
715
      // assigned automatically.
 
716
      primary_key_selected = true;
 
717
    }
 
718
 
 
719
    set_response_sensitive(Gtk::RESPONSE_ACCEPT, primary_key_selected);
 
720
    if(primary_key_selected)
 
721
      m_error_label->hide();
 
722
    else
 
723
      m_error_label->show();
 
724
  }
 
725
}
 
726
 
 
727
void Dialog_Import_CSV::on_parser_file_read_error(const Glib::ustring& error_message)
 
728
{
 
729
  std::string filename;
 
730
#ifdef GLIBMM_EXCEPTIONS_ENABLED
 
731
  try
 
732
  {
 
733
    filename = Glib::filename_from_uri(m_file_uri);
 
734
  }
 
735
  catch(const Glib::ConvertError& ex)
 
736
  {
 
737
    std::cerr << "Glib::filename_from_uri() failed: " << ex.what() << std::endl;
 
738
  }
 
739
#else
 
740
  std::auto_ptr<Glib::Error> error;
 
741
  filename = Glib::filename_from_uri(m_file_uri, error);
 
742
#endif
 
743
  
 
744
  show_error_dialog(_("Could Not Open file"),
 
745
    Glib::ustring::compose(_("The file at \"%1\" could not be opened: %2"), filename, error_message) );
 
746
}
 
747
 
 
748
void Dialog_Import_CSV::on_parser_have_display_name(const Glib::ustring& display_name)
 
749
{
 
750
  set_title(display_name + _(" - Import From CSV File"));
 
751
}
 
752
 
 
753
void Dialog_Import_CSV::on_parser_state_changed()
 
754
{
 
755
  //Remit (via our similarly-named signal) this so that the progress dialog can respond:
 
756
  signal_state_changed().emit();
 
757
}
 
758
 
 
759
Dialog_Import_CSV::type_signal_state_changed Dialog_Import_CSV::signal_state_changed() const
 
760
{
 
761
  return m_signal_state_changed;
 
762
}
 
763
 
 
764
} //namespace Glom
 
765