glom r1595 - in trunk: . glom



Author: arminb
Date: Sat Apr 26 09:25:09 2008
New Revision: 1595
URL: http://svn.gnome.org/viewvc/glom?rev=1595&view=rev

Log:
2008-04-26  Armin Burgmeier  <armin openismus com>

	* glom/glom.glade:
	* glom/dialog_import_csv.h:
	* glom/dialog_import_csv.cc:
	* glom/Makefile.am: Added yet unfinished "Import from CSV" dialog.

	* glom/frame_glom.h:
	* glom/frame_glom.cc: Added Frame_Glom::on_menu_Tables_ImportIntoTable
	to launch the import dialog.

	* glom/application.cc (init_menus): Added an action for the "Import
	Into Table" menu item.


Added:
   trunk/glom/dialog_import_csv.cc
   trunk/glom/dialog_import_csv.h
Modified:
   trunk/ChangeLog
   trunk/glom/Makefile.am
   trunk/glom/application.cc
   trunk/glom/frame_glom.cc
   trunk/glom/frame_glom.h
   trunk/glom/glom.glade

Modified: trunk/glom/Makefile.am
==============================================================================
--- trunk/glom/Makefile.am	(original)
+++ trunk/glom/Makefile.am	Sat Apr 26 09:25:09 2008
@@ -39,7 +39,8 @@
                combobox_fields.h combobox_fields.cc \
                combobox_relationship.h combobox_relationship.cc \
                dialog_connection.h dialog_connection.cc \
-               dialog_existing_or_new.h dialog_existing_or_new.cc \
+	       dialog_import_csv.h dialog_import_csv.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 \
                box_reports.h box_reports.cc \

Modified: trunk/glom/application.cc
==============================================================================
--- trunk/glom/application.cc	(original)
+++ trunk/glom/application.cc	Sat Apr 26 09:25:09 2008
@@ -392,6 +392,8 @@
   m_listDeveloperActions.push_back(action);
 #endif // !GLOM_ENABLE_CLIENT_ONLY
 
+  action = Gtk::Action::create("GlomAction_Menu_ImportIntoTable", _("Import _Into Table"));
+  m_refActionGroup_Others->add(action, sigc::mem_fun(*m_pFrame, &Frame_Glom::on_menu_Tables_ImportIntoTable));
 
   //"Reports" menu:
   m_refActionGroup_Others->add( Gtk::Action::create("Glom_Menu_Reports", _("_Reports")) );
@@ -501,6 +503,7 @@
     "        <menuitem action='GlomAction_Menu_EditTables' />"
     "        <menuitem action='GlomAction_Menu_AddRelatedTable' />"
 #endif // !GLOM_ENABLE_CLIENT_ONLY
+    "        <menuitem action='GlomAction_Menu_ImportIntoTable' />"
     "     </menu>"
     "     <menu action='Glom_Menu_Reports'>"
     "        <placeholder name='Menu_Reports_Dynamic' />"

Added: trunk/glom/dialog_import_csv.cc
==============================================================================
--- (empty file)
+++ trunk/glom/dialog_import_csv.cc	Sat Apr 26 09:25:09 2008
@@ -0,0 +1,753 @@
+/* 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.h"
+#include "config.h"
+
+#include <glom/libglom/data_structure/glomconversions.h>
+
+#include <gtkmm/messagedialog.h>
+#include <gtkmm/cellrenderercombo.h>
+#include <glibmm/i18n.h>
+#include <cerrno>
+
+namespace
+{
+
+const gunichar DELIMITER = ',';
+
+// TODO: Perhaps we should change this to std::string to allow binary data, such
+// as images.
+Glib::ustring::const_iterator advance_escape(const Glib::ustring::const_iterator& iter, const Glib::ustring::const_iterator& end, gunichar& c)
+{
+  // TODO: Throw an error if there is nothing to be escaped (iter == end)?
+  Glib::ustring::const_iterator walk = iter;
+  if(walk == end) return walk;
+
+  if(*walk == 'x')
+  {
+    // Hexadecimal number, insert according unichar
+    ++ walk;
+    unsigned int num = 0;
+
+    // TODO: Limit?
+    Glib::ustring::const_iterator pos = walk;
+    for(; (walk != end) && isxdigit(*walk); ++ walk)
+    {
+      num *= 16;
+
+      if(isdigit(*walk))
+        num += *walk - '0';
+      else if(isupper(*walk))
+        num += *walk - 'A' + 10;
+      else
+        num += *walk - 'a' + 10;
+    }
+
+    c = num;
+    return walk;
+  }
+  else if(isdigit(*walk) && *walk != '8' && *walk != '9')
+  {
+    unsigned int num = 0;
+
+    // TODO: Limit?
+    for(; (walk != end) && isdigit(*walk) && *walk != '8' && *walk != '9'; ++ walk)
+    {
+      num *= 8;
+      num += *walk - '0';
+    }
+
+    c = num;
+    return walk;
+  }
+  else
+  {
+    switch(*walk)
+    {
+    case 'n':
+      c = '\n';
+      break;
+    case '\\':
+      c = '\\';
+      break;
+    case 'r':
+      c = '\r';
+      break;
+    case 'a':
+      c = '\a';
+      break;
+    case 'v':
+      c = '\f';
+      break;
+    case 't':
+      c = '\t';
+      break;
+    default:
+      // Unrecognized escape sequence, TODO: throw error?
+      c = *walk;
+      break;
+    }
+
+    ++ walk;
+    return walk;
+  }
+}
+
+Glib::ustring::const_iterator advance_field(const Glib::ustring::const_iterator& iter, const Glib::ustring::const_iterator& end, Glib::ustring& field)
+{
+  Glib::ustring::const_iterator walk = iter;
+  gunichar quote_char = 0;
+
+  field.clear();
+
+  while(walk != end)
+  {
+    gunichar c = *walk;
+
+    // Escaped stuff in quoted strings:
+    if(quote_char && c == '\\')
+    {
+      ++ walk;
+      walk = advance_escape(walk, end, c);
+      field.append(1, c);
+    }
+    // End of quoted string
+    else if(quote_char && c == quote_char)
+    {
+      quote_char = 0;
+      ++ walk;
+    }
+    // Begin of quoted string. This allows stuff such as "foo"'bar'baz in a field here,
+    // but it can easily be avoided if it's a problem, by checking walk against iter.
+    else if(!quote_char && (c == '\'' || c == '\"'))
+    {
+      quote_char = c;
+      ++ walk;
+    }
+    // End of field:
+    else if(!quote_char && c == DELIMITER)
+    {
+      break;
+    }
+    else
+    {
+      field.append(1, c);
+      ++ walk;
+    }
+  }
+
+  // TODO: Throw error if still inside a quoted string?
+  return walk;
+}
+
+// When auto-detecting the encoding, we try to read the file in these
+// encodings, in order:
+const char* ENCODINGS[] = {
+  "UTF-8",
+  "ISO-8859-1",
+  "ISO-8859-15",
+  "UTF-16",
+  "UCS-2",
+  "UCS-4"
+};
+
+const unsigned int N_ENCODINGS = sizeof(ENCODINGS)/sizeof(ENCODINGS[0]);
+
+}
+
+namespace Glom
+{
+
+Dialog_Import_CSV::Dialog_Import_CSV(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade)
+: Gtk::Dialog(cobject), m_auto_detect_encoding(0), m_state(NONE)
+{
+  refGlade->get_widget("import_csv_fields", m_sample_view);
+  refGlade->get_widget("import_csv_target_table", m_target_table);
+  refGlade->get_widget("import_csv_encoding", m_encoding_combo);
+  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)
+    throw std::runtime_error("Missing widgets from glade file for Dialog_Import_CSV");
+
+  m_encoding_model = Gtk::ListStore::create(m_encoding_columns);
+
+  Gtk::TreeIter iter = m_encoding_model->append();
+  (*iter)[m_encoding_columns.m_col_encoding] = _("Auto Detect");
+
+  // Separator:
+  m_encoding_model->append();
+
+  for(unsigned int i = 0; i < N_ENCODINGS; ++ i)
+  {
+    iter = m_encoding_model->append();
+    (*iter)[m_encoding_columns.m_col_encoding] = ENCODINGS[i];
+  }
+
+  Gtk::CellRendererText* renderer = Gtk::manage(new Gtk::CellRendererText);
+  m_encoding_combo->set_model(m_encoding_model);
+  m_encoding_combo->pack_start(*renderer);
+  m_encoding_combo->add_attribute(renderer->property_text(), m_encoding_columns.m_col_encoding);
+  m_encoding_combo->set_row_separator_func(sigc::mem_fun(*this, &Dialog_Import_CSV::row_separator_func));
+  m_encoding_combo->set_active(0);
+  m_encoding_combo->signal_changed().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_encoding_changed));
+  m_first_line_as_title->signal_toggled().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_first_line_as_title_toggled));
+  m_sample_rows->signal_changed().connect(sigc::mem_fun(*this, &Dialog_Import_CSV::on_sample_rows_changed));
+
+  m_sample_view->set_headers_visible(m_first_line_as_title->get_active());
+
+  clear();
+}
+
+void Dialog_Import_CSV::import(const Glib::ustring& uri, const Glib::ustring& into_table)
+{
+  clear();
+
+  Document_Glom* document = get_document();
+  if(!document)
+  {
+    show_error_dialog(_("No document available"), _("You need to open a document to import the data into"));
+  }
+  else
+  {
+    // Make the relevant widgets sensitive. We will make them insensitive
+    // again when a (nonrecoverable) error occurs.
+    m_sample_view->set_sensitive(true);
+    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");
+    m_target_table->set_markup("<b>" + Glib::Markup::escape_text(into_table) + "</b>");
+
+    m_field_model = Gtk::ListStore::create(m_field_columns);
+    Gtk::TreeIter tree_iter = m_field_model->append();
+    (*tree_iter)[m_field_columns.m_col_field_name] = _("<None>");
+
+    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();
+    }
+
+    m_file = Gio::File::create_for_uri(uri);
+    m_file->read_async(sigc::mem_fun(*this, &Dialog_Import_CSV::on_file_read));
+
+    // 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;
+  }
+}
+
+void Dialog_Import_CSV::clear()
+{
+  // TODO: Do we explicitely need to cancel async operations?
+  m_sample_model.reset();
+  m_sample_view->remove_all_columns();
+  m_sample_view->set_model(m_sample_model);
+  m_field_model.reset();
+  m_rows.clear();
+  m_fields.clear();
+  m_file.reset();
+  m_stream.reset();
+  m_buffer.reset(NULL);
+  m_raw.clear();
+  m_parser.reset(NULL);
+
+  m_encoding_info->set_text("");
+  m_sample_view->set_sensitive(false);
+  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;
+}
+
+void Dialog_Import_CSV::show_error_dialog(const Glib::ustring& primary, const Glib::ustring& secondary)
+{
+  Gtk::MessageDialog dialog(*this, primary, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
+  dialog.set_title("Error importing CSV file");
+  dialog.set_secondary_text(secondary);
+  dialog.run();
+}
+
+bool Dialog_Import_CSV::row_separator_func(const Glib::RefPtr<Gtk::TreeModel>& model, const Gtk::TreeIter& iter) const
+{
+  return (*iter)[m_encoding_columns.m_col_encoding] == "";
+}
+
+void Dialog_Import_CSV::on_query_info(const Glib::RefPtr<Gio::AsyncResult>& result)
+{
+  try
+  {
+    Glib::RefPtr<Gio::FileInfo> info = m_file->query_info_finish(result);
+    set_title(info->get_display_name() + " - Import from CSV file");
+  }
+  catch(const Glib::Exception& ex)
+  {
+    std::cerr << "Failed to fetch display name of uri " << m_file->get_uri() << ": " << ex.what() << std::endl;
+  }
+}
+
+void Dialog_Import_CSV::on_file_read(const Glib::RefPtr<Gio::AsyncResult>& result)
+{
+  try
+  {
+    m_stream = m_file->read_finish(result);
+
+    m_buffer.reset(new Buffer);
+    m_stream->read_async(m_buffer->buf, sizeof(m_buffer->buf), sigc::mem_fun(*this, &Dialog_Import_CSV::on_stream_read));
+  }
+  catch(const Glib::Exception& error)
+  {
+    show_error_dialog(_("Could not open file"), Glib::ustring::compose(_("The file at \"%1\" could not be opened: %2"), m_file->get_uri(), error.what()));
+    clear();
+    // TODO: Response?
+  }
+}
+
+void Dialog_Import_CSV::on_stream_read(const Glib::RefPtr<Gio::AsyncResult>& result)
+{
+  try
+  {
+    gssize size = m_stream->read_finish(result);
+    if(size > 0)
+    {
+      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();
+
+      // 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));
+    }
+    else
+    {
+      // Finished reading
+      m_buffer.reset(NULL);
+      m_stream.reset();
+      m_file.reset();
+    }
+  }
+  catch(const Glib::Exception& error)
+  {
+    show_error_dialog(_("Could not read file"), Glib::ustring::compose(_("The file at \"%1\" could not be read: %2"), m_file->get_uri(), error.what()));
+    clear();
+    // TODO: Response?
+  }
+}
+
+void Dialog_Import_CSV::on_encoding_changed()
+{
+  // Reset current parsing process
+  m_parser.reset(NULL);
+
+  int active = m_encoding_combo->get_active_row_number();
+  switch(active)
+  {
+  case -1: // No active item
+  case 1: // Separator
+    g_assert_not_reached();
+    break;
+  case 0: // Auto-Detect
+    // Begin with first encoding
+    m_auto_detect_encoding = 0;
+    break;
+  default: // Some specific encoding
+    m_auto_detect_encoding = -1;
+    break;
+  }
+
+  // Parse from beginning with new encoding if we have already some data to
+  // parse.
+  if(!m_raw.empty())
+    begin_parse();
+}
+
+void Dialog_Import_CSV::on_first_line_as_title_toggled()
+{
+  // 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;
+
+  if(m_first_line_as_title->get_active())
+  {
+    m_sample_view->set_headers_visible(true);
+    unsigned int index = 1;
+    Gtk::TreePath path(&index, &index + 1);
+    Gtk::TreeIter iter = m_sample_model->get_iter(path);
+
+    // Remove the first row from the view
+    if(iter && (*iter)[m_sample_columns.m_col_row] == 0)
+    {
+      m_sample_model->erase(iter);
+
+      // Add another row to the end, if one is loaded.
+      unsigned int last_index = m_sample_model->children().size();
+      if(last_index < m_rows.size())
+      {
+        iter = m_sample_model->append();
+        (*iter)[m_sample_columns.m_col_row] = last_index;
+      }
+    }
+  }
+  else
+  {
+    m_sample_view->set_headers_visible(false);
+
+    // Check whether row 0 is displayed
+    unsigned int index = 1;
+    Gtk::TreePath path(&index, &index + 1);
+    Gtk::TreeIter iter = m_sample_model->get_iter(path);
+
+    if((!iter || (*iter)[m_sample_columns.m_col_row] != 0) && !m_rows.empty() && m_sample_rows->get_value_as_int() > 0)
+    {
+      // Add first row to model
+      if(!iter)
+        iter = m_sample_model->append();
+      else
+        iter = m_sample_model->insert(iter);
+      (*iter)[m_sample_columns.m_col_row] = 0;
+
+      // Remove last row if we hit the limit
+      unsigned int sample_rows = m_sample_model->children().size() - 1;
+      if(sample_rows > static_cast<unsigned int>(m_sample_rows->get_value_as_int()))
+      {
+        //m_sample_model->erase(m_sample_model->children().rbegin());
+        path[0] = sample_rows;
+        iter = m_sample_model->get_iter(path);
+        m_sample_model->erase(iter);
+      }
+    }
+  }
+}
+
+void Dialog_Import_CSV::on_sample_rows_changed()
+{
+}
+
+const char* Dialog_Import_CSV::get_current_encoding() const
+{
+  int active = m_encoding_combo->get_active_row_number();
+  switch(active)
+  {
+  case -1: // No active item
+  case 1: // Separator
+    g_assert_not_reached();
+    break;
+  case 0: // Auto-Detect
+    g_assert(m_auto_detect_encoding != -1);
+    return ENCODINGS[m_auto_detect_encoding];
+  default: // Some specific encoding
+    g_assert(active >= 2);
+    g_assert(static_cast<unsigned int>(active - 2) < N_ENCODINGS);
+    return ENCODINGS[active - 2];
+  }
+}
+
+void Dialog_Import_CSV::begin_parse()
+{
+  if(m_auto_detect_encoding != -1)
+    m_encoding_info->set_text(Glib::ustring::compose(_("Encoding detected as: %1"), Glib::ustring(get_current_encoding())));
+  else
+    m_encoding_info->set_text("");
+
+  // Clear sample preview since we reparse everything, perhaps with
+  // another encoding.
+  m_sample_model.reset();
+  m_sample_view->remove_all_columns();
+  m_sample_view->set_model(m_sample_model); // Empty model
+  m_rows.clear();
+
+  m_parser.reset(new Parser(get_current_encoding()));
+  m_state = PARSING;
+  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();
+
+  // If we are auto-detecting the encoding, then try the next one
+  if(m_auto_detect_encoding != -1)
+  {
+    ++ m_auto_detect_encoding;
+    if(static_cast<unsigned int>(m_auto_detect_encoding) < N_ENCODINGS)
+      begin_parse();
+    else
+      m_encoding_info->set_text(_("Encoding detection failed. Please manually choose one from the box to the left."));
+  }
+  else
+  {
+    m_encoding_info->set_text(_("The file contains data not in the specified encoding. Please choose another one, or try \"Auto Detect\"."));
+  }
+}
+
+bool Dialog_Import_CSV::on_idle_parse()
+{
+  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* outbuf = outbuffer;
+  gsize outbytes = 1024;
+
+  std::size_t result = m_parser->conv.iconv(&inbuf, &inbytes, &outbuf, &outbytes);
+  bool more_to_process = inbytes != 0;
+
+  if(result == static_cast<size_t>(-1))
+  {
+    if(errno == EILSEQ)
+    {
+      // Invalid text in the current encoding.
+      encoding_error();
+      return false;
+    }
+
+    // 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;
+  }
+
+  m_parser->input_position += (inbuf - inbuffer);
+
+  // 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' };
+
+  while( (pos = std::find_first_of<const char*>(prev, outbuf, to_find, to_find + sizeof(to_find))) != outbuf)
+  {
+    if(*pos == '\0')
+    {
+      // There is a nullbyte in the conversion. As normal text files don't
+      // contain nullbytes, this only occurs when converting for example a UTF-16
+      // file from ISO-8859-1 to UTF-8 (note that the UTF-16 file is valid ISO-8859-1,
+      // it just contains lots of nullbytes). We therefore produce an error here.
+      encoding_error();
+      return false;
+    }
+    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->current_line.clear();
+      prev = pos + 1;
+    }
+  }
+
+  // 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
+
+  // Continue if there are more bytes to process
+  return more_to_process;
+}
+
+void Dialog_Import_CSV::handle_line(const Glib::ustring& line)
+{
+  if(line.empty()) return;
+
+  m_rows.push_back(std::vector<Glib::ustring>());
+  std::vector<Glib::ustring>& row = m_rows.back();
+
+  Glib::ustring field;
+  //Gtk::TreeModelColumnRecord record;
+
+  // Parse first field
+  Glib::ustring::const_iterator iter = advance_field(line.begin(), line.end(), field);
+  row.push_back(field);
+
+  // Parse more fields
+  while(iter != line.end())
+  {
+    // Skip delimiter
+    ++ iter;
+
+    // Read field
+    iter = advance_field(iter, line.end(), field);
+
+    // Add field to current row
+    row.push_back(field);
+  }
+
+  if(!m_sample_model)
+  {
+    // This is the first line read if there is no model yet
+    m_sample_model = Gtk::ListStore::create(m_sample_columns);
+    m_sample_view->set_model(m_sample_model);
+
+    // Create field vector that contains the fields into which to import
+    // the data.
+    m_fields.resize(row.size());
+
+    Gtk::CellRendererText* text = Gtk::manage(new Gtk::CellRendererText);
+    Gtk::TreeViewColumn* col = Gtk::manage(new Gtk::TreeViewColumn(_("Line")));
+    col->pack_start(*text, false);
+    col->set_cell_data_func(*text, sigc::mem_fun(*this, &Dialog_Import_CSV::line_data_func));
+    m_sample_view->append_column(*col);
+
+    for(unsigned int i = 0; i < row.size(); ++ i)
+    {
+      Gtk::CellRendererCombo* cell = Gtk::manage(new Gtk::CellRendererCombo);
+      Gtk::TreeViewColumn* col = Gtk::manage(new Gtk::TreeViewColumn(row[i]));
+      col->pack_start(*cell, true);
+      col->set_cell_data_func(*cell, sigc::bind(sigc::mem_fun(*this, &Dialog_Import_CSV::field_data_func), i));
+      col->set_sizing(Gtk::TREE_VIEW_COLUMN_AUTOSIZE);
+      cell->property_model() = m_field_model;
+      cell->property_text_column() = 0;
+      cell->property_has_entry() = false;
+      cell->signal_edited().connect(sigc::bind(sigc::mem_fun(*this, &Dialog_Import_CSV::on_field_edited), i));
+      m_sample_view->append_column(*col);
+    }
+
+    Gtk::TreeIter iter = m_sample_model->append();
+    // -1 means the row to select target fields (see special handling in cell data funcs)
+    (*iter)[m_sample_columns.m_col_row] = -1;
+  }
+
+  // Add the row to the treeview if there are not yet as much sample rows
+  // 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()))
+  {
+    Gtk::TreeIter iter = m_sample_model->append();
+    (*iter)[m_sample_columns.m_col_row] = m_rows.size() - 1;
+  }
+}
+
+void Dialog_Import_CSV::line_data_func(Gtk::CellRenderer* renderer, const Gtk::TreeIter& iter)
+{
+  int row = (*iter)[m_sample_columns.m_col_row];
+  Gtk::CellRendererText* renderer_text = dynamic_cast<Gtk::CellRendererText*>(renderer);
+  if(!renderer_text) throw std::logic_error("CellRenderer is not a CellRendererText in line_data_func");
+
+  if(row == -1)
+    renderer_text->set_property("text", Glib::ustring(_("Target Field")));
+  else
+    renderer_text->set_property("text", Glib::ustring::compose("%1", row + 1));
+}
+
+void Dialog_Import_CSV::field_data_func(Gtk::CellRenderer* renderer, const Gtk::TreeIter& iter, unsigned int column_number)
+{
+  int row = (*iter)[m_sample_columns.m_col_row];
+  Gtk::CellRendererCombo* renderer_combo = dynamic_cast<Gtk::CellRendererCombo*>(renderer);
+  if(!renderer_combo) throw std::logic_error("CellRenderer is not a CellRendererCombo in field_data_func");
+
+  if(row == -1)
+  {
+    sharedptr<Field> field = m_fields[column_number];
+    if(field)
+      renderer_combo->set_property("text", field->get_name());
+    else
+      renderer_combo->set_property("text", Glib::ustring("<None>"));
+
+    renderer_combo->set_property("editable", true);
+  }
+  else
+  {
+    // Convert to currently chosen field, if any, and back, too see how it
+    // looks like when imported
+    sharedptr<Field> field = m_fields[column_number];
+    Glib::ustring text = m_rows[row][column_number];
+    if(field)
+    {
+      bool success;
+      Gnome::Gda::Value value = Glom::Conversions::parse_value(field->get_glom_type(), text, success);
+      if(!success) text = _("<Import failure>");
+      else text = Glom::Conversions::get_text_for_gda_value(field->get_glom_type(), value);
+    }
+
+    renderer_combo->set_property("text", text);
+    renderer_combo->set_property("editable", false);
+  }
+}
+
+void Dialog_Import_CSV::on_field_edited(const Glib::ustring& path, const Glib::ustring& new_text, unsigned int column_number)
+{
+  Gtk::TreePath treepath(path);
+  Gtk::TreeIter iter = m_sample_model->get_iter(treepath);
+
+  // Lookup field indicated by new_text
+  const Gtk::TreeNodeChildren& children = m_field_model->children();
+  for(Gtk::TreeIter field_iter = children.begin(); field_iter != children.end(); ++ field_iter)
+  {
+    if( (*field_iter)[m_field_columns.m_col_field_name] == new_text)
+    {
+      sharedptr<Field> field = (*field_iter)[m_field_columns.m_col_field];
+      // Check whether another column is already using that field
+      std::vector<sharedptr<Field> >::iterator vec_field_iter = std::find(m_fields.begin(), m_fields.end(), field);
+      // Reset the old column since two different columns cannot be imported into the same field
+      if(vec_field_iter != m_fields.end()) *vec_field_iter = sharedptr<Field>();
+
+      m_fields[column_number] = field;
+      
+      // Update the rows, so they are redrawn, doing a conversion to the new type.
+      const Gtk::TreeNodeChildren& sample_children = m_sample_model->children();
+      // Create a TreePath with initial index 0. We need a TreePath for the row_changed() call
+      unsigned int first_index = 0;
+      Gtk::TreePath path(&first_index, &first_index + 1);
+
+      for(Gtk::TreeIter sample_iter = sample_children.begin(); sample_iter != sample_children.end(); ++ sample_iter)
+      {
+        if(sample_iter != iter)
+          m_sample_model->row_changed(path, sample_iter);
+
+        path.next();
+      }
+
+      break;
+    }
+  }
+}
+
+} //namespace Glom

Added: trunk/glom/dialog_import_csv.h
==============================================================================
--- (empty file)
+++ trunk/glom/dialog_import_csv.h	Sat Apr 26 09:25:09 2008
@@ -0,0 +1,165 @@
+/* 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_H
+#define GLOM_DIALOG_IMPORT_CSV_H
+
+#include "base_db.h"
+
+#include <memory>
+#include <giomm/asyncresult.h>
+#include <giomm/file.h>
+#include <giomm/inputstream.h>
+#include <gtkmm/dialog.h>
+#include <gtkmm/treeview.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/spinbutton.h>
+#include <libgdamm/datamodelimport.h>
+#include <libglademm/xml.h>
+
+namespace Glom
+{
+
+class Dialog_Import_CSV
+  : public Gtk::Dialog,
+    public Base_DB
+{
+public:
+  Dialog_Import_CSV(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade);
+
+  void import(const Glib::ustring& uri, const Glib::ustring& into_table);
+
+protected:
+  void clear();
+  void show_error_dialog(const Glib::ustring& primary, const Glib::ustring& secondary);
+
+  bool row_separator_func(const Glib::RefPtr<Gtk::TreeModel>& model, const Gtk::TreeIter& iter) const;
+
+  void on_query_info(const Glib::RefPtr<Gio::AsyncResult>& result);
+  void on_file_read(const Glib::RefPtr<Gio::AsyncResult>& result);
+  void on_stream_read(const Glib::RefPtr<Gio::AsyncResult>& result);
+
+  void on_encoding_changed();
+  void on_first_line_as_title_toggled();
+  void on_sample_rows_changed();
+
+  const char* get_current_encoding() const;
+  void begin_parse();
+  void encoding_error();
+
+  bool on_idle_parse();
+  void handle_line(const Glib::ustring& line);
+
+  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);
+
+  class EncodingColumns: public Gtk::TreeModelColumnRecord
+  {
+  public:
+    EncodingColumns() { add(m_col_encoding); }
+
+    Gtk::TreeModelColumn<Glib::ustring> m_col_encoding;
+  };
+  
+  class FieldColumns: public Gtk::TreeModelColumnRecord
+  {
+  public:
+    FieldColumns() { add(m_col_field_name); add(m_col_field); }
+
+    Gtk::TreeModelColumn<Glib::ustring> m_col_field_name;
+    Gtk::TreeModelColumn<sharedptr<Field> > m_col_field;
+  };
+
+  class SampleColumns: public Gtk::TreeModelColumnRecord
+  {
+  public:
+    SampleColumns() { add(m_col_row); }
+
+    Gtk::TreeModelColumn<int> m_col_row;
+  };
+
+  EncodingColumns m_encoding_columns;
+  Glib::RefPtr<Gtk::ListStore> m_encoding_model;
+
+  FieldColumns m_field_columns;
+  Glib::RefPtr<Gtk::ListStore> m_field_model;
+
+  SampleColumns m_sample_columns;
+  Glib::RefPtr<Gtk::ListStore> m_sample_model;
+  Gtk::TreeView* m_sample_view;
+  Gtk::Label* m_target_table;
+  Gtk::ComboBox* m_encoding_combo;
+  Gtk::Label* m_encoding_info;
+  Gtk::CheckButton* m_first_line_as_title;
+  Gtk::SpinButton* m_sample_rows;
+
+  Glib::RefPtr<Gio::File> m_file;
+  Glib::RefPtr<Gio::FileInputStream> m_stream;
+
+  // 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
+  // the encoding.
+  std::vector<char> m_raw;
+
+  struct Buffer {
+    char buf[1024];
+  };
+  std::auto_ptr<Buffer> m_buffer;
+
+  // Index into the ENCODINGS array (see dialog_import_csv.cc) for the
+  // encoding that we currently try to read the data with, or -1 if
+  // auto-detection is disabled.
+  int m_auto_detect_encoding;
+
+  // We use the low-level Glib::IConv routines to progressively convert the
+  // input data in an idle handler.
+  struct Parser {
+    Glib::IConv conv;
+    std::vector<char>::size_type input_position;
+    std::string current_line;
+    sigc::connection idle_connection;
+
+    Parser(const char* encoding): conv("UTF-8", encoding), input_position(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;
+};
+
+} //namespace Glom
+
+#endif //GLOM_DIALOG_IMPORT_CSV_H
+

Modified: trunk/glom/frame_glom.cc
==============================================================================
--- trunk/glom/frame_glom.cc	(original)
+++ trunk/glom/frame_glom.cc	Sat Apr 26 09:25:09 2008
@@ -22,6 +22,7 @@
 
 #include "frame_glom.h"
 #include "application.h"
+#include "dialog_import_csv.h"
 #include <glom/libglom/appstate.h>
 
 #ifndef GLOM_ENABLE_CLIENT_ONLY
@@ -764,6 +765,42 @@
   m_dialog_addrelatedtable->show();
 }
 
+#endif
+
+void Frame_Glom::on_menu_Tables_ImportIntoTable()
+{
+  if(m_table_name.empty())
+  {
+    Gtk::MessageDialog dialog(*get_app_window(), "There is no table to import data into", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
+    dialog.run();
+  }
+  else
+  {
+    Gtk::FileChooserDialog file_chooser(*get_app_window(), _("Choose a CSV file to open"), Gtk::FILE_CHOOSER_ACTION_OPEN);
+    file_chooser.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+    file_chooser.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_ACCEPT);
+
+    if(file_chooser.run() == Gtk::RESPONSE_ACCEPT)
+    {
+      Dialog_Import_CSV* dialog = 0;
+      Glib::RefPtr<Gnome::Glade::Xml> refXml = Gnome::Glade::Xml::create(Utils::get_glade_file_path("glom.glade"), "dialog_import_csv");
+      refXml->get_widget_derived("dialog_import_csv", dialog);
+      add_view(dialog);
+
+      file_chooser.hide();
+
+      
+      dialog->import(file_chooser.get_uri(), m_table_name);
+      dialog->run();
+      
+      remove_view(dialog);
+      delete dialog;
+    }
+  }
+}
+
+#ifndef GLOM_ENABLE_CLIENT_ONLY
+
 void Frame_Glom::on_dialog_add_related_table_response(int response)
 {
   if(!m_dialog_addrelatedtable)

Modified: trunk/glom/frame_glom.h
==============================================================================
--- trunk/glom/frame_glom.h	(original)
+++ trunk/glom/frame_glom.h	Sat Apr 26 09:25:09 2008
@@ -97,6 +97,10 @@
 #ifndef GLOM_ENABLE_CLIENT_ONLY
   void on_menu_Tables_EditTables();
   void on_menu_Tables_AddRelatedTable();
+#endif // !GLOM_ENABLE_CLIENT_ONLY
+  void on_menu_Tables_ImportIntoTable();
+
+#ifndef GLOM_ENABLE_CLIENT_ONLY
   void on_menu_Reports_EditReports();
   void on_menu_File_EditPrintLayouts();
   void on_menu_developer_database_preferences();

Modified: trunk/glom/glom.glade
==============================================================================
--- trunk/glom/glom.glade	(original)
+++ trunk/glom/glom.glade	Sat Apr 26 09:25:09 2008
@@ -1335,7 +1335,319 @@
       </widget>
     </child>
   </widget>
- <widget class="GtkDialog" id="dialog_data_invalid_format">
+  <widget class="GtkDialog" id="dialog_import_csv">
+    <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="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox5">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="spacing">6</property>
+            <child>
+              <widget class="GtkFrame" id="frame1">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment2">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkTable" id="table1">
+                        <property name="visible">True</property>
+                        <property name="n_rows">3</property>
+                        <property name="n_columns">3</property>
+                        <property name="column_spacing">6</property>
+                        <property name="row_spacing">6</property>
+                        <child>
+                          <widget class="GtkLabel" id="import_csv_target_table">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                            <property name="max_width_chars">0</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">3</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="import_csv_encoding_info">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">Detected as: UTF-8</property>
+                            <property name="wrap">True</property>
+                            <property name="width_chars">30</property>
+                            <property name="max_width_chars">30</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">2</property>
+                            <property name="right_attach">3</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkCheckButton" id="import_csv_first_line_as_title">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="label" translatable="yes">_First line as title</property>
+                            <property name="use_underline">True</property>
+                            <property name="response_id">0</property>
+                            <property name="draw_indicator">True</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">3</property>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                            <property name="y_options"></property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label4">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">_Encoding:</property>
+                            <property name="use_underline">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkComboBox" id="import_csv_encoding">
+                            <property name="visible">True</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="y_options"></property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label10">
+                            <property name="visible">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                            <property name="x_options">GTK_SHRINK</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label11">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">Import Into _Table:</property>
+                            <property name="use_underline">True</property>
+                          </widget>
+                          <packing>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label8">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Import Options&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkFrame" id="frame3">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment3">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkVBox" id="vbox2">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkHBox" id="hbox5">
+                            <property name="visible">True</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <widget class="GtkLabel" id="label6">
+                                <property name="visible">True</property>
+                                <property name="label" translatable="yes">Number of sample rows:</property>
+                              </widget>
+                              <packing>
+                                <property name="expand">False</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkAlignment" id="alignment1">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="xscale">0</property>
+                                <child>
+                                  <widget class="GtkSpinButton" id="import_csv_sample_rows">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="width_chars">9</property>
+                                    <property name="adjustment">5 0 100 1 10 10</property>
+                                  </widget>
+                                </child>
+                              </widget>
+                              <packing>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkScrolledWindow" id="scrolledwindow4">
+                            <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="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>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label9">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Import Fields&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area5">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="import_csv_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_import_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="response_id">-3</property>
+                <child>
+                  <widget class="GtkHBox" id="hbox4">
+                    <property name="visible">True</property>
+                    <child>
+                      <widget class="GtkImage" id="image6">
+                        <property name="visible">True</property>
+                        <property name="stock">gtk-convert</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="label7">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">_Import</property>
+                        <property name="use_underline">True</property>
+                      </widget>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkButton" id="import_csv_help_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-help</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-11</property>
+              </widget>
+              <packing>
+                <property name="position">2</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkDialog" id="dialog_data_invalid_format">
     <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
     <child internal-child="vbox">
       <widget class="GtkVBox" id="vbox17">



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