glom r1610 - in trunk: . docs/user-guide/C glom



Author: arminb
Date: Tue May 13 20:56:44 2008
New Revision: 1610
URL: http://svn.gnome.org/viewvc/glom?rev=1610&view=rev

Log:
2008-05-13  Armin Burgmeier  <armin openismus com>

	* glom/dialog_import_csv.h:
	* glom/dialog_import_csv.cc: Added missing functionality.

	* glom/glom.glade:
	* glom/dialog_import_csv_progress.h:
	* glom/dialog_import_csv_progress.cc:
	* glom/Makefile.am: Dialog showing progress of the import and error
	messages, performing the actual import.

	* glom/frame_glom.cc (on_menu_Tables_ImportIntoTable): Show the
	progress dialog after the user has finished the settings in the
	"Import from CSV" dialog, refresh display after import so the list and
	details show the imported values.

	* docs/user-guide/C/glom.xml: Added an initial explanation for the
	Import Dialog.


Added:
   trunk/glom/dialog_import_csv_progress.cc
   trunk/glom/dialog_import_csv_progress.h
Modified:
   trunk/ChangeLog
   trunk/docs/user-guide/C/glom.xml
   trunk/glom/Makefile.am
   trunk/glom/dialog_import_csv.cc
   trunk/glom/dialog_import_csv.h
   trunk/glom/frame_glom.cc
   trunk/glom/glom.glade

Modified: trunk/docs/user-guide/C/glom.xml
==============================================================================
--- trunk/docs/user-guide/C/glom.xml	(original)
+++ trunk/docs/user-guide/C/glom.xml	Tue May 13 20:56:44 2008
@@ -677,6 +677,11 @@
 <para></para>
 </sect2>
 
+<sect2 id="dialog_import_csv">
+<title>Dialog: Import Into Table</title>
+<para>This dialog allows the user to import a CSV (comma separated value) file into the current database table. It shows the first few rows of the file to import and allows the user to select the target field in the database for each column in the CSV file. For auto-incremented primary keys, the primary cannot be used as target field, otherwise one column must be imported into the primary key field.</para>
+</sect2>
+
 </sect1>
 
 <!-- ============= Bugs ================================== -->

Modified: trunk/glom/Makefile.am
==============================================================================
--- trunk/glom/Makefile.am	(original)
+++ trunk/glom/Makefile.am	Tue May 13 20:56:44 2008
@@ -42,6 +42,7 @@
                combobox_relationship.h combobox_relationship.cc \
                dialog_connection.h dialog_connection.cc \
 	       dialog_import_csv.h dialog_import_csv.cc \
+	       dialog_import_csv_progress.h dialog_import_csv_progress.cc \
 	       dialog_existing_or_new.h dialog_existing_or_new.cc \
                dialog_invalid_data.h dialog_invalid_data.cc \
                filechooser_export.h filechooser_export.cc \

Modified: trunk/glom/dialog_import_csv.cc
==============================================================================
--- trunk/glom/dialog_import_csv.cc	(original)
+++ trunk/glom/dialog_import_csv.cc	Tue May 13 20:56:44 2008
@@ -185,7 +185,8 @@
   refGlade->get_widget("import_csv_encoding_info", m_encoding_info);
   refGlade->get_widget("import_csv_first_line_as_title", m_first_line_as_title);
   refGlade->get_widget("import_csv_sample_rows", m_sample_rows);
-  if(!m_sample_view || !m_encoding_combo || !m_target_table || !m_encoding_info || !m_first_line_as_title || !m_sample_rows)
+  refGlade->get_widget("import_csv_error_label", m_error_label);
+  if(!m_sample_view || !m_encoding_combo || !m_target_table || !m_encoding_info || !m_first_line_as_title || !m_sample_rows || !m_error_label)
     throw std::runtime_error("Missing widgets from glade file for Dialog_Import_CSV");
 
   m_encoding_model = Gtk::ListStore::create(m_encoding_columns);
@@ -224,7 +225,7 @@
   Document_Glom* document = get_document();
   if(!document)
   {
-    show_error_dialog(_("No document available"), _("You need to open a document to import the data into"));
+    show_error_dialog(_("No document available"), _("You need to open a document to import the data into a table"));
   }
   else
   {
@@ -234,9 +235,8 @@
     m_encoding_combo->set_sensitive(true);
     m_first_line_as_title->set_sensitive(true);
     m_sample_rows->set_sensitive(true);
-    set_response_sensitive(Gtk::RESPONSE_ACCEPT, true);
 
-    set_title("Import from CSV file");
+    set_title(_("Import from CSV file"));
     m_target_table->set_markup("<b>" + Glib::Markup::escape_text(into_table) + "</b>");
 
     m_field_model = Gtk::ListStore::create(m_field_columns);
@@ -246,9 +246,15 @@
     Document_Glom::type_vecFields fields(document->get_table_fields(into_table));
     for(Document_Glom::type_vecFields::const_iterator iter = fields.begin(); iter != fields.end(); ++ iter)
     {
-      Gtk::TreeIter tree_iter = m_field_model->append();
-      (*tree_iter)[m_field_columns.m_col_field] = *iter;
-      (*tree_iter)[m_field_columns.m_col_field_name] = (*iter)->get_name();
+      // Don't allow the primary key to be selected when it is an auto
+      // increment key, since the value for the primary key is chosen
+      // automatically anyway.
+      if(!(*iter)->get_primary_key() || !(*iter)->get_auto_increment())
+      {
+        Gtk::TreeIter tree_iter = m_field_model->append();
+        (*tree_iter)[m_field_columns.m_col_field] = *iter;
+        (*tree_iter)[m_field_columns.m_col_field_name] = (*iter)->get_name();
+      }
     }
 
     m_file = Gio::File::create_for_uri(uri);
@@ -257,13 +263,37 @@
     // Query the display name of the file to set in the title
     m_file->query_info_async(sigc::mem_fun(*this, &Dialog_Import_CSV::on_query_info), G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
 
-    m_state = PARSING;
+    set_state(PARSING);
   }
 }
 
+unsigned int Dialog_Import_CSV::get_row_count() const
+{
+  if(m_first_line_as_title->get_active() && m_rows.size() > 1)
+    return m_rows.size() - 1;
+  return m_rows.size();
+}
+
+unsigned int Dialog_Import_CSV::get_column_count() const
+{
+  return m_fields.size();
+}
+
+const sharedptr<Field>& Dialog_Import_CSV::get_field_for_column(unsigned int col)
+{
+  return m_fields[col];
+}
+
+const Glib::ustring& Dialog_Import_CSV::get_data(unsigned int row, unsigned int col)
+{
+  if(m_first_line_as_title->get_active()) ++ row;
+  return m_rows[row][col];
+}
+
 void Dialog_Import_CSV::clear()
 {
   // TODO: Do we explicitely need to cancel async operations?
+  // TODO: Disconnect idle handlers
   m_sample_model.reset();
   m_sample_view->remove_all_columns();
   m_sample_view->set_model(m_sample_model);
@@ -272,6 +302,7 @@
   m_fields.clear();
   m_file.reset();
   m_stream.reset();
+  m_filename.clear();
   m_buffer.reset(NULL);
   m_raw.clear();
   m_parser.reset(NULL);
@@ -281,9 +312,9 @@
   m_encoding_combo->set_sensitive(false);
   m_first_line_as_title->set_sensitive(false);
   m_sample_rows->set_sensitive(false);
-  set_response_sensitive(Gtk::RESPONSE_ACCEPT, false);
 
-  m_state = NONE;
+  set_state(NONE);
+  validate_primary_key();
 }
 
 void Dialog_Import_CSV::show_error_dialog(const Glib::ustring& primary, const Glib::ustring& secondary)
@@ -304,7 +335,8 @@
   try
   {
     Glib::RefPtr<Gio::FileInfo> info = m_file->query_info_finish(result);
-    set_title(info->get_display_name() + " - Import from CSV file");
+    m_filename = info->get_display_name();
+    set_title(m_filename + _(" - Import from CSV file"));
   }
   catch(const Glib::Exception& ex)
   {
@@ -334,21 +366,21 @@
   try
   {
     gssize size = m_stream->read_finish(result);
-    if(size > 0)
-    {
-      m_raw.insert(m_raw.end(), m_buffer->buf, m_buffer->buf + size);
+    m_raw.insert(m_raw.end(), m_buffer->buf, m_buffer->buf + size);
 
-      // If the parser already exists, but it is currently not parsing because it waits
-      // for new input, then continue parsing.
-      if(m_parser.get() && !m_parser->idle_connection.connected())
-        m_parser->idle_connection = Glib::signal_idle().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_idle_parse));
-
-      // If the parser does not exist yet, then create a new parser, except when the
-      // current encoding does not work for the file in which case the user first
-      // has to choose another encoding.
-      else if(!m_parser.get() && m_state != ENCODING_ERROR)
-        begin_parse();
+    // If the parser already exists, but it is currently not parsing because it waits
+    // for new input, then continue parsing.
+    if(m_parser.get() && !m_parser->idle_connection.connected())
+      m_parser->idle_connection = Glib::signal_idle().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_idle_parse));
+
+    // If the parser does not exist yet, then create a new parser, except when the
+    // current encoding does not work for the file in which case the user first
+    // has to choose another encoding.
+    else if(!m_parser.get() && m_state != ENCODING_ERROR)
+      begin_parse();
 
+    if(size > 0)
+    {
       // Read the next few bytes
       m_stream->read_async(m_buffer->buf, sizeof(m_buffer->buf), sigc::mem_fun(*this, &Dialog_Import_CSV::on_stream_read));
     }
@@ -455,6 +487,35 @@
 
 void Dialog_Import_CSV::on_sample_rows_changed()
 {
+  // Ignore if we don't have a model yet, we will take care of the option
+  // later when inserting items into it.
+  if(!m_sample_model) return;
+
+  unsigned int current_sample_rows = m_sample_model->children().size() - 1;
+  unsigned int new_sample_rows = m_sample_rows->get_value_as_int();
+
+  if(current_sample_rows > new_sample_rows)
+  {
+    // +1 for the "target field" row
+    unsigned int sample_index = new_sample_rows + 1;
+    Gtk::TreePath path(&sample_index, &sample_index + 1);
+    Gtk::TreeIter iter = m_sample_model->get_iter(path);
+
+    while(iter != m_sample_model->children().end())
+      iter = m_sample_model->erase(iter);
+  }
+  else
+  {
+    // Find index of first row to add
+    unsigned int row_index = current_sample_rows;
+    if(m_first_line_as_title->get_active()) ++ row_index;
+
+    for(unsigned int i = current_sample_rows; i < new_sample_rows && row_index < m_rows.size(); ++ i, ++ row_index)
+    {
+      Gtk::TreeIter iter = m_sample_model->append();
+      (*iter)[m_sample_columns.m_col_row] = row_index;
+    }
+  }
 }
 
 const char* Dialog_Import_CSV::get_current_encoding() const
@@ -491,20 +552,31 @@
   m_rows.clear();
 
   m_parser.reset(new Parser(get_current_encoding()));
-  m_state = PARSING;
+  set_state(PARSING);
+
+  // Allow the Import button to be pressed when a field for the primary key
+  // field is set. When the import button is pressed without the file being
+  // fully loaded, the import progress waits for us to load the rest.
+  validate_primary_key();
+
   m_parser->idle_connection = Glib::signal_idle().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_idle_parse));
 }
 
 void Dialog_Import_CSV::encoding_error()
 {
   m_parser.reset(NULL);
-  m_state = ENCODING_ERROR;
   // Clear sample preview (TODO: Let it visible, and only remove when reparsing?)
   m_sample_model.reset();
   m_sample_view->remove_all_columns();
   m_sample_view->set_model(m_sample_model); // Empty model
   m_rows.clear();
 
+  set_state(ENCODING_ERROR);
+
+  // Don't allow the import button to be pressed when an error occured. This
+  // would not make sense since we cleared all the parsed row data anyway.
+  validate_primary_key();
+
   // If we are auto-detecting the encoding, then try the next one
   if(m_auto_detect_encoding != -1)
   {
@@ -522,15 +594,18 @@
 
 bool Dialog_Import_CSV::on_idle_parse()
 {
+  // The amount of bytes to process in one pass of the idle handler
+  static const unsigned int CONVERT_BUFFER_SIZE = 1024;
+
   const char* inbuffer = &m_raw[m_parser->input_position];
   char* inbuf = const_cast<char*>(inbuffer);
   gsize inbytes = m_raw.size() - m_parser->input_position;
-  char outbuffer[1024];
+  char outbuffer[CONVERT_BUFFER_SIZE];
   char* outbuf = outbuffer;
-  gsize outbytes = 1024;
+  gsize outbytes = CONVERT_BUFFER_SIZE;
 
   std::size_t result = m_parser->conv.iconv(&inbuf, &inbytes, &outbuf, &outbytes);
-  bool more_to_process = inbytes != 0;
+  bool more_to_process = (inbytes != 0);
 
   if(result == static_cast<size_t>(-1))
   {
@@ -544,9 +619,20 @@
     // If EINVAL is set, this means that an incomplete multibyte sequence was at
     // the end of the input. We might have some more bytes, but those do not make
     // up a whole character, so we need to wait for more input.
-    // TODO: Error out if at end of file
     if(errno == EINVAL)
-      more_to_process = false;
+    {
+      if(!m_stream)
+      {
+        // This means that we already reached the end of the file. The file
+        // should not end with an incomplete multibyte sequence.
+        encoding_error();
+        return false;
+      }
+      else
+      {
+        more_to_process = false;
+      }
+    }
   }
 
   m_parser->input_position += (inbuf - inbuffer);
@@ -554,7 +640,7 @@
   // We now have outbuf - outbuffer bytes of valid UTF-8 in outbuffer.
   const char* prev = outbuffer;
   const char* pos;
-  const char to_find[] = { '\n', '\0' };
+  const char to_find[] = { '\r', '\n', '\0' };
 
   while( (pos = std::find_first_of<const char*>(prev, outbuf, to_find, to_find + sizeof(to_find))) != outbuf)
   {
@@ -570,22 +656,36 @@
     else
     {
       m_parser->current_line.append(prev, pos - prev);
-      //++ m_parser->line_number;
-      handle_line(m_parser->current_line); //, m_parser->line_number);
+      ++ m_parser->line_number;
+      if(!m_parser->current_line.empty())
+        handle_line(m_parser->current_line, m_parser->line_number);
       m_parser->current_line.clear();
+      // Skip linebreak
       prev = pos + 1;
+      // Skip DOS-style linebreak (\r\n)
+      if(*pos == '\r' && prev != outbuf && *prev == '\n') ++ prev;
     }
   }
 
   // Append last chunk of this line
   m_parser->current_line.append(prev, outbuf - prev);
-  // TODO: Handle that last line if there is no more input
+  if(!m_stream && m_raw.size() == m_parser->input_position)
+  {
+    ++ m_parser->line_number;
+    // Handle last line, if nonempty
+    if(m_parser->current_line.empty())
+      handle_line(m_parser->current_line, m_parser->line_number);
+
+    // Parsed whole file, done
+    m_parser.reset(NULL);
+    set_state(PARSED);
+  }
 
   // Continue if there are more bytes to process
   return more_to_process;
 }
 
-void Dialog_Import_CSV::handle_line(const Glib::ustring& line)
+void Dialog_Import_CSV::handle_line(const Glib::ustring& line, unsigned int line_number)
 {
   if(line.empty()) return;
 
@@ -651,17 +751,15 @@
   // as the user has chosen (note the first row is to choose the target fields,
   // not a sample row, which is why we do -1 here).
   unsigned int sample_rows = m_sample_model->children().size() - 1;
-  
-  // If the first line is interpreted as column titles, then it is not
-  // a sample row
-  if(m_first_line_as_title->get_active() && sample_rows > 0)
-    -- sample_rows;
 
-  // TODO: Don't add if this is the first line and first line as title is set.
-  if(sample_rows < static_cast<unsigned int>(m_sample_rows->get_value_as_int()))
+  // Don't add if this is the first line and m_first_line_as_title is active
+  if(line_number > 1 || !m_first_line_as_title->get_active())
   {
-    Gtk::TreeIter iter = m_sample_model->append();
-    (*iter)[m_sample_columns.m_col_row] = m_rows.size() - 1;
+    if(sample_rows < static_cast<unsigned int>(m_sample_rows->get_value_as_int()))
+    {
+      Gtk::TreeIter iter = m_sample_model->append();
+      (*iter)[m_sample_columns.m_col_row] = m_rows.size() - 1;
+    }
   }
 }
 
@@ -745,9 +843,69 @@
         path.next();
       }
 
+      validate_primary_key();
       break;
     }
   }
 }
 
+void Dialog_Import_CSV::set_state(State state)
+{
+  if(m_state != state)
+  {
+    m_state = state;
+    m_signal_state_changed.emit();
+  }
+}
+
+void Dialog_Import_CSV::validate_primary_key()
+{
+  if(m_state == NONE || m_state == ENCODING_ERROR)
+  {
+    m_error_label->hide();
+    set_response_sensitive(Gtk::RESPONSE_ACCEPT, false);
+  }
+  else
+  {
+    // Allow the import button to be pressed when the value for the primary key
+    // has been chosen:
+    sharedptr<Field> primary_key = get_field_primary_key_for_table(get_target_table_name());
+    bool primary_key_selected;
+
+    if(!primary_key->get_auto_increment())
+    {
+      // If m_rows is empty, then no single line was read from the file yet,
+      // and the m_fields array is not up to date. It is set in handle_line()
+      // when the first line is parsed.
+      primary_key_selected = false;
+      if(!m_rows.empty())
+      {
+        for(std::vector<sharedptr<Field> >::iterator iter = m_fields.begin(); iter != m_fields.end(); ++ iter)
+        {
+          if(*iter == primary_key)
+          {
+            primary_key_selected = true;
+            break;
+          }
+        }
+      }
+
+      if(!primary_key_selected)
+        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())));
+    }
+    else
+    {
+      // auto_increment primary keys always work since their value is
+      // assigned automatically.
+      primary_key_selected = true;
+    }
+
+    set_response_sensitive(Gtk::RESPONSE_ACCEPT, primary_key_selected);
+    if(primary_key_selected)
+      m_error_label->hide();
+    else
+      m_error_label->show();
+  }
+}
+
 } //namespace Glom

Modified: trunk/glom/dialog_import_csv.h
==============================================================================
--- trunk/glom/dialog_import_csv.h	(original)
+++ trunk/glom/dialog_import_csv.h	Tue May 13 20:56:44 2008
@@ -43,9 +43,29 @@
     public Base_DB
 {
 public:
+  typedef sigc::signal<void> SignalStateChanged;
+
+  enum State {
+    NONE,
+    PARSING,
+    ENCODING_ERROR,
+    PARSED
+  };
+
   Dialog_Import_CSV(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade);
 
   void import(const Glib::ustring& uri, const Glib::ustring& into_table);
+  
+  State get_state() const { return m_state; }
+  Glib::ustring get_target_table_name() const { return m_target_table->get_text(); }
+  const Glib::ustring& get_filename() const { return m_filename; }
+
+  unsigned int get_row_count() const;
+  unsigned int get_column_count() const;
+  const sharedptr<Field>& get_field_for_column(unsigned int col);
+  const Glib::ustring& get_data(unsigned int row, unsigned int col);
+
+  SignalStateChanged signal_state_changed() const { return m_signal_state_changed; }
 
 protected:
   void clear();
@@ -66,12 +86,15 @@
   void encoding_error();
 
   bool on_idle_parse();
-  void handle_line(const Glib::ustring& line);
+  void handle_line(const Glib::ustring& line, unsigned int line_number);
 
   void line_data_func(Gtk::CellRenderer* renderer, const Gtk::TreeIter& iter);
   void field_data_func(Gtk::CellRenderer* renderer, const Gtk::TreeIter& iter, unsigned int column_number);
   void on_field_edited(const Glib::ustring& path, const Glib::ustring& new_text, unsigned int column_number);
 
+  void set_state(State state);
+  void validate_primary_key();
+
   class EncodingColumns: public Gtk::TreeModelColumnRecord
   {
   public:
@@ -111,9 +134,11 @@
   Gtk::Label* m_encoding_info;
   Gtk::CheckButton* m_first_line_as_title;
   Gtk::SpinButton* m_sample_rows;
+  Gtk::Label* m_error_label;
 
   Glib::RefPtr<Gio::File> m_file;
   Glib::RefPtr<Gio::FileInputStream> m_stream;
+  Glib::ustring m_filename;
 
   // The raw data in the original encoding. We keep this so we can convert
   // from the user-selected encoding to UTF-8 every time the user changes
@@ -137,26 +162,21 @@
     std::vector<char>::size_type input_position;
     std::string current_line;
     sigc::connection idle_connection;
+    unsigned int line_number;
 
-    Parser(const char* encoding): conv("UTF-8", encoding), input_position(0) {}
+    Parser(const char* encoding): conv("UTF-8", encoding), input_position(0), line_number(0) {}
     ~Parser() { idle_connection.disconnect(); }
   };
 
   std::auto_ptr<Parser> m_parser;
-  
-  enum State {
-    NONE,
-    PARSING,
-    ENCODING_ERROR,
-    PARSED
-  };
-
   State m_state;
 
   // Parsed data:
   std::vector<std::vector<Glib::ustring> > m_rows;
   // The fields into which to import the data:
   std::vector<sharedptr<Field> > m_fields;
+
+  SignalStateChanged m_signal_state_changed;
 };
 
 } //namespace Glom

Added: trunk/glom/dialog_import_csv_progress.cc
==============================================================================
--- (empty file)
+++ trunk/glom/dialog_import_csv_progress.cc	Tue May 13 20:56:44 2008
@@ -0,0 +1,251 @@
+/* Glom
+ *
+ * Copyright (C) 2001-2004 Murray Cumming
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "dialog_import_csv_progress.h"
+#include "config.h"
+
+#include <glom/libglom/data_structure/glomconversions.h>
+#include <glibmm/i18n.h>
+
+namespace Glom
+{
+
+Dialog_Import_CSV_Progress::Dialog_Import_CSV_Progress(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade)
+: Gtk::Dialog(cobject), m_data_source(NULL), m_current_row(0)
+{
+  refGlade->get_widget("import_csv_progress_progress_bar", m_progress_bar);
+  refGlade->get_widget("import_csv_progress_textview", m_text_view);
+
+  if(!m_progress_bar || !m_text_view)
+    throw std::runtime_error("Missing widgets from glade file for Dialog_Import_CSV_Progress");
+}
+
+bool Dialog_Import_CSV_Progress::init_db_details(const Glib::ustring& table_name)
+{
+  const bool result = Base_DB_Table_Data::init_db_details(table_name);
+  m_field_primary_key = get_field_primary_key_for_table(table_name);
+  return result;
+}
+
+void Dialog_Import_CSV_Progress::import(Dialog_Import_CSV& data_source)
+{
+  // Cancel any running operations
+  clear();
+
+  // Begin with first row from source
+  m_data_source = &data_source;
+  m_current_row = 0;
+
+  switch(data_source.get_state())
+  {
+  case Dialog_Import_CSV::PARSING:
+    // Wait for the parsing to finish. We do not start importing before the file has been
+    // parsed completely since we would not to rollback our changes in case of a
+    // parsing error.
+    m_progress_bar->set_text(Glib::ustring::compose(_("Parsing CSV file %1"), data_source.get_filename()));
+    m_ready_connection = data_source.signal_state_changed().connect(sigc::mem_fun(*this, &Dialog_Import_CSV_Progress::on_state_changed));
+    break;
+  case Dialog_Import_CSV::PARSED:
+    begin_import();
+    break;
+  default:
+    // This function should not be called with data_source being in other states
+    g_assert_not_reached();
+    break;
+  }
+
+  set_response_sensitive(Gtk::RESPONSE_CANCEL, true);
+  set_response_sensitive(Gtk::RESPONSE_OK, false);
+}
+
+void Dialog_Import_CSV_Progress::clear()
+{
+  // Cancel any running import
+  m_progress_connection.disconnect();
+  m_ready_connection.disconnect();
+
+  m_data_source = NULL;
+  m_current_row = 0;
+}
+
+void Dialog_Import_CSV_Progress::add_text(const Glib::ustring& text)
+{
+  m_text_view->get_buffer()->insert(m_text_view->get_buffer()->end(), text);
+  m_text_view->scroll_to(m_text_view->get_buffer()->get_insert());
+}
+
+void Dialog_Import_CSV_Progress::begin_import()
+{
+  m_progress_bar->set_text(Glib::ustring::compose("%1 / %2", m_current_row, m_data_source->get_row_count()));
+  m_progress_bar->set_fraction(0.0);
+
+  m_progress_connection = Glib::signal_idle().connect(sigc::mem_fun(*this, &Dialog_Import_CSV_Progress::on_idle_import));
+}
+
+void Dialog_Import_CSV_Progress::on_state_changed()
+{
+  switch(m_data_source->get_state())
+  {
+  case Dialog_Import_CSV::ENCODING_ERROR:
+    // Cancel on error
+    response(Gtk::RESPONSE_CANCEL);
+    break;
+  case Dialog_Import_CSV::PARSED:
+    // Begin importing when fully parsed
+    begin_import();
+    //m_progress_connection = Glib::signal_idle().connect(sigc::mem_fun(*this, &Dialog_Import_CSV_Progress::on_idle_import));
+    break;
+  default:
+    // We only install the signal in state PARSING, and we should not change
+    // back to NONE state, so we must have changed to one of the states handled
+    // above, otherwise something went wrong.
+    g_assert_not_reached();
+    break;
+  }
+}
+
+bool Dialog_Import_CSV_Progress::on_idle_import()
+{
+  m_progress_bar->set_text(Glib::ustring::compose("%1 / %2", m_current_row, m_data_source->get_row_count()));
+  m_progress_bar->set_fraction(static_cast<double>(m_current_row) / static_cast<double>(m_data_source->get_row_count()));
+
+  if(m_current_row == m_data_source->get_row_count())
+  {
+    // Don't response immediately, so the user has a chance to read the
+    // warnings and errors, if any.
+    set_response_sensitive(Gtk::RESPONSE_CANCEL, false);
+    set_response_sensitive(Gtk::RESPONSE_OK, true);
+    add_text(_("Import complete\n"));
+    return false;
+  }
+
+  // Update the current row values map:
+  for(unsigned int i = 0; i < m_data_source->get_column_count(); ++ i)
+  {
+    const sharedptr<Field>& field = m_data_source->get_field_for_column(i);
+    if(field)
+    {
+      bool success;
+      Gnome::Gda::Value value = Glom::Conversions::parse_value(field->get_glom_type(), m_data_source->get_data(m_current_row, i), success);
+      if(success)
+      {
+        // Make the value empty if the value is not unique.
+        if(field->get_unique_key())
+        {
+          sharedptr<LayoutItem_Field> layout_item = sharedptr<LayoutItem_Field>::create();
+          layout_item->set_full_field_details(field);
+          if(!get_field_value_is_unique(m_table_name, layout_item, value))
+          {
+            value = Gnome::Gda::Value();
+
+            Glib::ustring message(Glib::ustring::compose(_("Warning importing row %1: The value for field %2 must be unique, but is already in use. Don't importing the value.\n"), m_current_row + 1, field->get_name()));
+            add_text(message);
+          }
+        }
+
+        m_current_row_values[field->get_name()] = value;
+      }
+      else
+      {
+        Glib::ustring message(Glib::ustring::compose(_("Warning importing row %1: The value for field %2, \"%3\" could not be converted to the field's type. Don't importing the value.\n"), m_current_row + 1, field->get_name(), m_data_source->get_data(m_current_row, i)));
+        add_text(message);
+      }
+    }
+  }
+
+  // Choose the primary key value
+  Gnome::Gda::Value primary_key_value;
+  if(m_field_primary_key->get_auto_increment())
+  {
+    primary_key_value = get_next_auto_increment_value(m_table_name, m_field_primary_key->get_name());
+  }
+  else
+  {
+    // No auto-increment primary key: Check for uniqueness
+    Gnome::Gda::Value primary_key_value = m_current_row_values[m_field_primary_key->get_name()];
+    sharedptr<LayoutItem_Field> layout_item = sharedptr<LayoutItem_Field>::create();
+    layout_item->set_full_field_details(m_field_primary_key);
+
+    if(!get_field_value_is_unique(m_table_name, layout_item, primary_key_value))
+      primary_key_value = Gnome::Gda::Value();
+  }
+  
+  if(Glom::Conversions::value_is_empty(primary_key_value))
+  {
+    Glib::ustring message(Glib::ustring::compose(_("Error importing row %1: Cannot import the row since the primary key is empty.\n"), m_current_row + 1));
+    add_text(message);
+  }
+  else
+  {
+    record_new(true /* use_entered_data */, primary_key_value);
+  }
+
+  m_current_row_values.clear();
+
+  ++ m_current_row;
+  return true;
+}
+
+void Dialog_Import_CSV_Progress::on_response(int response_id)
+{
+  // Don't continue importing when the user already cancelled, or closed the
+  // window via delete event.
+  clear();
+}
+
+Gnome::Gda::Value Dialog_Import_CSV_Progress::get_entered_field_data(const sharedptr<const LayoutItem_Field>& field) const
+{
+  type_mapValues::const_iterator iter = m_current_row_values.find(field->get_name());
+  if(iter == m_current_row_values.end()) return Gnome::Gda::Value();
+  return iter->second;
+}
+
+void Dialog_Import_CSV_Progress::set_entered_field_data(const sharedptr<const LayoutItem_Field>& field, const Gnome::Gda::Value&  value)
+{
+  m_current_row_values[field->get_name()] = value;
+}
+
+sharedptr<Field> Dialog_Import_CSV_Progress::get_field_primary_key() const
+{
+  return m_field_primary_key;
+}
+
+Gnome::Gda::Value Dialog_Import_CSV_Progress::get_primary_key_value_selected() const
+{
+  type_mapValues::const_iterator iter = m_current_row_values.find(m_field_primary_key->get_name());
+  if(iter == m_current_row_values.end()) return Gnome::Gda::Value();
+  return iter->second;
+}
+
+// These don't make sense in Dialog_Import_CSV_Progress, and thus should not
+// be called. We need to implement them though, because they are pure abstract
+// in Base_DB_Table_Data.
+void Dialog_Import_CSV_Progress::set_primary_key_value(const Gtk::TreeModel::iterator& row, const Gnome::Gda::Value& value)
+{
+  // This is actually called by Base_DB_Table_Data::record_new(), but we can safely ignore it.
+  //throw std::logic_error("Dialog_Import_CSV_Progress::set_primary_key_value() called");
+}
+
+Gnome::Gda::Value Dialog_Import_CSV_Progress::get_primary_key_value(const Gtk::TreeModel::iterator& row) const
+{
+  throw std::logic_error("Dialog_Import_CSV_Progress::get_primary_key_value() called");
+}
+
+} //namespace Glom

Added: trunk/glom/dialog_import_csv_progress.h
==============================================================================
--- (empty file)
+++ trunk/glom/dialog_import_csv_progress.h	Tue May 13 20:56:44 2008
@@ -0,0 +1,83 @@
+/* Glom
+ *
+ * Copyright (C) 2001-2004 Murray Cumming
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GLOM_DIALOG_IMPORT_CSV_PROGRESS_H
+#define GLOM_DIALOG_IMPORT_CSV_PROGRESS_H
+
+#include "base_db_table_data.h"
+
+#include "dialog_import_csv.h"
+
+namespace Glom
+{
+
+class Dialog_Import_CSV_Progress
+  : public Gtk::Dialog,
+    public Base_DB_Table_Data
+{
+public:
+  Dialog_Import_CSV_Progress(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade);
+
+  virtual bool init_db_details(const Glib::ustring& table_name);
+
+  // Reads the data from the Dialog_Import_CSV. We might want to wrap the
+  // parsed data within a separate class.
+  void import(Dialog_Import_CSV& data_source);
+
+protected:
+  void clear();
+  void add_text(const Glib::ustring& text);
+
+  void begin_import();
+
+  void on_state_changed();
+  bool on_idle_import();
+
+  virtual void on_response(int response_id); // Override from Gtk::Dialog
+
+  virtual Gnome::Gda::Value get_entered_field_data(const sharedptr<const LayoutItem_Field>& field) const; // Override from Base_DB_Table_Data
+  virtual void set_entered_field_data(const sharedptr<const LayoutItem_Field>& field, const Gnome::Gda::Value&  value); // Override from Base_DB
+
+  virtual sharedptr<Field> get_field_primary_key() const; // Override from Base_DB_Table_Data
+  virtual Gnome::Gda::Value get_primary_key_value_selected() const; // Override from Base_DB_Table_Data
+  virtual void set_primary_key_value(const Gtk::TreeModel::iterator& row, const Gnome::Gda::Value& value); // Override from Base_DB_Table_Data
+  virtual Gnome::Gda::Value get_primary_key_value(const Gtk::TreeModel::iterator& row) const; // Override from Base_DB_Table_Data
+
+  sharedptr<Field> m_field_primary_key;
+  Dialog_Import_CSV* m_data_source;
+  unsigned int m_current_row;
+
+  // We use this for implementing get_entered_field_data and
+  // set_entered_field_data, required by Base_DB_Table_Data::record_new().
+  // It just holds the values for the fields in the current row.
+  typedef std::map<Glib::ustring, Gnome::Gda::Value> type_mapValues;
+  type_mapValues m_current_row_values;
+
+  Gtk::ProgressBar* m_progress_bar;
+  Gtk::TextView* m_text_view;
+
+  sigc::connection m_progress_connection;
+  sigc::connection m_ready_connection;
+};
+
+} //namespace Glom
+
+#endif //GLOM_DIALOG_IMPORT_CSV_PROGRESS_H
+

Modified: trunk/glom/frame_glom.cc
==============================================================================
--- trunk/glom/frame_glom.cc	(original)
+++ trunk/glom/frame_glom.cc	Tue May 13 20:56:44 2008
@@ -23,6 +23,7 @@
 #include "frame_glom.h"
 #include "application.h"
 #include "dialog_import_csv.h"
+#include "dialog_import_csv_progress.h"
 #include <glom/libglom/appstate.h>
 
 #ifndef GLOM_ENABLE_CLIENT_ONLY
@@ -790,10 +791,31 @@
 
       file_chooser.hide();
 
-      
       dialog->import(file_chooser.get_uri(), m_table_name);
-      dialog->run();
-      
+      while(Glom::Utils::dialog_run_with_help(dialog, "dialog_import_csv") == Gtk::RESPONSE_ACCEPT)
+      {
+        dialog->hide();
+        Dialog_Import_CSV_Progress* progress_dialog = 0;
+      Glib::RefPtr<Gnome::Glade::Xml> refXml = Gnome::Glade::Xml::create(Utils::get_glade_file_path("glom.glade"), "dialog_import_csv_progress");
+        refXml->get_widget_derived("dialog_import_csv_progress", progress_dialog);
+        add_view(progress_dialog);
+
+        progress_dialog->init_db_details(dialog->get_target_table_name());
+        progress_dialog->import(*dialog);
+        const int response = progress_dialog->run();
+
+        remove_view(progress_dialog);
+        delete progress_dialog;
+
+        // Force update from database so the newly added entries are shown
+        show_table_refresh();
+
+        // Re-show chooser dialog when an error occured or when the user
+        // cancelled.
+        if(response == Gtk::RESPONSE_OK)
+          break;
+      }
+
       remove_view(dialog);
       delete dialog;
     }

Modified: trunk/glom/glom.glade
==============================================================================
--- trunk/glom/glom.glade	(original)
+++ trunk/glom/glom.glade	Tue May 13 20:56:44 2008
@@ -1339,8 +1339,8 @@
     <property name="border_width">5</property>
     <property name="title" translatable="yes">bla.blub - Import from CSV</property>
     <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
-    <property name="default_width">400</property>
-    <property name="default_height">300</property>
+    <property name="default_width">550</property>
+    <property name="default_height">400</property>
     <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
     <property name="has_separator">False</property>
     <child internal-child="vbox">
@@ -1359,6 +1359,7 @@
                 <child>
                   <widget class="GtkAlignment" id="alignment2">
                     <property name="visible">True</property>
+                    <property name="top_padding">6</property>
                     <property name="left_padding">12</property>
                     <child>
                       <widget class="GtkTable" id="table1">
@@ -1491,6 +1492,7 @@
                 <child>
                   <widget class="GtkAlignment" id="alignment3">
                     <property name="visible">True</property>
+                    <property name="top_padding">6</property>
                     <property name="left_padding">12</property>
                     <child>
                       <widget class="GtkVBox" id="vbox2">
@@ -1543,7 +1545,6 @@
                               <widget class="GtkTreeView" id="import_csv_fields">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
-                                <property name="headers_clickable">True</property>
                               </widget>
                             </child>
                           </widget>
@@ -1570,6 +1571,16 @@
                 <property name="position">1</property>
               </packing>
             </child>
+            <child>
+              <widget class="GtkLabel" id="import_csv_error_label">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">label</property>
+              </widget>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
           </widget>
           <packing>
             <property name="position">1</property>
@@ -1799,4 +1810,98 @@
       </widget>
     </child>
   </widget>
+  <widget class="GtkDialog" id="dialog_import_csv_progress">
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Importing data</property>
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox7">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="vbox3">
+            <property name="visible">True</property>
+            <property name="spacing">6</property>
+            <child>
+              <widget class="GtkLabel" id="label5">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Please wait, your data is being importedâ</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkProgressBar" id="import_csv_progress_progress_bar">
+                <property name="visible">True</property>
+                <property name="show_text">True</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkScrolledWindow" id="scrolledwindow5">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                <property name="shadow_type">GTK_SHADOW_IN</property>
+                <child>
+                  <widget class="GtkTextView" id="import_csv_progress_textview">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="editable">False</property>
+                    <property name="wrap_mode">GTK_WRAP_WORD_CHAR</property>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area7">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="import_csv_progress_cancel_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-6</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="import_csv_progress_ok_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-ok</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-5</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
 </glade-interface>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]