glom r1595 - in trunk: . glom
- From: arminb svn gnome org
- To: svn-commits-list gnome org
- Subject: glom r1595 - in trunk: . glom
- Date: Sat, 26 Apr 2008 10:25:09 +0100 (BST)
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"><b>Import Options</b></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"><b>Import Fields</b></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]