summaryrefslogtreecommitdiffstats
path: root/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-01-20 01:29:50 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-01-20 01:29:50 +0000
commit8362bf63dea22bbf6736609b0f49c152f975eb63 (patch)
tree0eea3928e39e50fae91d4e68b21b1e6cbae25604 /kexi/plugins/importexport/csv/kexicsvimportdialog.cpp
downloadkoffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz
koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kexi/plugins/importexport/csv/kexicsvimportdialog.cpp')
-rw-r--r--kexi/plugins/importexport/csv/kexicsvimportdialog.cpp1662
1 files changed, 1662 insertions, 0 deletions
diff --git a/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp b/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp
new file mode 100644
index 00000000..16a9d416
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp
@@ -0,0 +1,1662 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This work is based on kspread/dialogs/kspread_dlg_csv.cc
+ and will be merged back with KOffice libraries.
+
+ Copyright (C) 2002-2003 Norbert Andres <nandres@web.de>
+ Copyright (C) 2002-2003 Ariya Hidayat <ariya@kde.org>
+ Copyright (C) 2002 Laurent Montel <montel@kde.org>
+ Copyright (C) 1999 David Faure <faure@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qclipboard.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qmime.h>
+#include <qpushbutton.h>
+#include <qradiobutton.h>
+#include <qtable.h>
+#include <qlayout.h>
+#include <qfiledialog.h>
+#include <qpainter.h>
+#include <qtextcodec.h>
+#include <qtimer.h>
+#include <qfontmetrics.h>
+#include <qtooltip.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kdialogbase.h>
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <kcharsets.h>
+#include <knuminput.h>
+#include <kprogress.h>
+#include <kactivelabel.h>
+
+#include <kexiutils/identifier.h>
+#include <kexiutils/utils.h>
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <core/kexipart.h>
+#include <core/kexipartinfo.h>
+#include <core/keximainwindow.h>
+#include <core/kexiguimsghandler.h>
+#include <kexidb/connection.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/transaction.h>
+#include <widget/kexicharencodingcombobox.h>
+
+#include "kexicsvimportdialog.h"
+#include "kexicsvwidgets.h"
+
+#ifdef Q_WS_WIN
+#include <krecentdirs.h>
+#include <windows.h>
+#endif
+
+#if 0
+#include <kspread_cell.h>
+#include <kspread_doc.h>
+#include <kspread_sheet.h>
+#include <kspread_undo.h>
+#include <kspread_view.h>
+#endif
+
+#define _IMPORT_ICON "table" /*todo: change to "file_import" or so*/
+#define _TEXT_TYPE 0
+#define _NUMBER_TYPE 1
+#define _DATE_TYPE 2
+#define _TIME_TYPE 3
+#define _DATETIME_TYPE 4
+#define _PK_FLAG 5
+
+//extra:
+#define _NO_TYPE_YET -1 //allows to accept a number of empty cells, before something non-empty
+#define _FP_NUMBER_TYPE 255 //_NUMBER_TYPE variant
+#define MAX_ROWS_TO_PREVIEW 100 //max 100 rows is reasonable
+#define MAX_BYTES_TO_PREVIEW 10240 //max 10KB is reasonable
+#define MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER 4096
+
+class KexiCSVImportDialogTable : public QTable
+{
+public:
+ KexiCSVImportDialogTable( QWidget * parent = 0, const char * name = 0 )
+ : QTable(parent, name) {
+ f = font();
+ f.setBold(true);
+ }
+ virtual void paintCell( QPainter * p, int row, int col, const QRect & cr, bool selected, const QColorGroup & cg ) {
+ if (row==0)
+ p->setFont(f);
+ else
+ p->setFont(font());
+ QTable::paintCell(p, row, col, cr, selected, cg);
+ }
+ virtual void setColumnWidth( int col, int w ) {
+ //make columns a bit wider
+ QTable::setColumnWidth( col, w + 16 );
+ }
+ QFont f;
+};
+
+//! Helper used to temporary disable keyboard and mouse events
+void installRecursiveEventFilter(QObject *filter, QObject *object)
+{
+ object->installEventFilter(filter);
+
+ if (!object->children())
+ return;
+
+ QObjectList list = *object->children();
+ for(QObject *obj = list.first(); obj; obj = list.next())
+ installRecursiveEventFilter(filter, obj);
+}
+
+KexiCSVImportDialog::KexiCSVImportDialog( Mode mode, KexiMainWindow* mainWin,
+ QWidget * parent, const char * name
+)
+ : KDialogBase(
+ KDialogBase::Plain,
+ i18n( "Import CSV Data File" )
+//! @todo use "Paste CSV Data From Clipboard" caption for mode==Clipboard
+ ,
+ (mode==File ? User1 : (ButtonCode)0) |Ok|Cancel,
+ Ok,
+ parent,
+ name ? name : "KexiCSVImportDialog",
+ true,
+ false,
+ KGuiItem( i18n("&Options"))
+ ),
+ m_mainWin(mainWin),
+ m_cancelled( false ),
+ m_adjustRows( true ),
+ m_startline( 0 ),
+ m_textquote( QString(KEXICSV_DEFAULT_FILE_TEXT_QUOTE)[0] ),
+ m_mode(mode),
+ m_prevSelectedCol(-1),
+ m_columnsAdjusted(false),
+ m_1stRowForFieldNamesDetected(false),
+ m_firstFillTableCall(true),
+ m_blockUserEvents(false),
+ m_primaryKeyColumn(-1),
+ m_dialogCancelled(false),
+ m_conn(0),
+ m_destinationTableSchema(0),
+ m_allRowsLoadedInPreview(false),
+ m_stoppedAt_MAX_BYTES_TO_PREVIEW(false)
+{
+ setWFlags(getWFlags() | Qt::WStyle_Maximize | Qt::WStyle_SysMenu);
+ hide();
+ setButtonOK(KGuiItem( i18n("&Import..."), _IMPORT_ICON));
+
+ m_typeNames.resize(5);
+ m_typeNames[0] = i18n("text");
+ m_typeNames[1] = i18n("number");
+ m_typeNames[2] = i18n("date");
+ m_typeNames[3] = i18n("time");
+ m_typeNames[4] = i18n("date/time");
+
+ kapp->config()->setGroup("ImportExport");
+ m_maximumRowsForPreview = kapp->config()->readNumEntry("MaximumRowsForPreviewInImportDialog", MAX_ROWS_TO_PREVIEW);
+ m_maximumBytesForPreview = kapp->config()->readNumEntry("MaximumBytesForPreviewInImportDialog", MAX_BYTES_TO_PREVIEW);
+
+ m_pkIcon = SmallIcon("key");
+
+ m_uniquenessTest.setAutoDelete(true);
+
+ setIcon(DesktopIcon(_IMPORT_ICON));
+ setSizeGripEnabled( TRUE );
+
+// m_encoding = QString::fromLatin1(KGlobal::locale()->encoding());
+// m_stripWhiteSpaceInTextValuesChecked = true;
+ m_file = 0;
+ m_inputStream = 0;
+
+ QVBoxLayout *lyr = new QVBoxLayout(plainPage(), 0, KDialogBase::spacingHint(), "lyr");
+
+ m_infoLbl = new KexiCSVInfoLabel(
+ m_mode==File ? i18n("Preview of data from file:")
+ : i18n("Preview of data from clipboard:"),
+ plainPage()
+ );
+ lyr->addWidget( m_infoLbl );
+
+ QWidget* page = new QFrame( plainPage(), "page" );
+ QGridLayout *glyr= new QGridLayout( page, 4, 5, 0, KDialogBase::spacingHint(), "glyr");
+ lyr->addWidget( page );
+
+ // Delimiter: comma, semicolon, tab, space, other
+ m_delimiterWidget = new KexiCSVDelimiterWidget(true /*lineEditOnBottom*/, page);
+ m_detectDelimiter = true;
+ glyr->addMultiCellWidget( m_delimiterWidget, 1, 2, 0, 0 );
+
+ QLabel *delimiterLabel = new QLabel(m_delimiterWidget, i18n("Delimiter:"), page);
+ delimiterLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
+ glyr->addMultiCellWidget( delimiterLabel, 0, 0, 0, 0 );
+
+ // Format: number, text, currency,
+ m_formatComboText = i18n( "Format for column %1:" );
+ m_formatCombo = new KComboBox(page, "m_formatCombo");
+ m_formatCombo->insertItem(i18n("Text"));
+ m_formatCombo->insertItem(i18n("Number"));
+ m_formatCombo->insertItem(i18n("Date"));
+ m_formatCombo->insertItem(i18n("Time"));
+ m_formatCombo->insertItem(i18n("Date/Time"));
+ glyr->addMultiCellWidget( m_formatCombo, 1, 1, 1, 1 );
+
+ m_formatLabel = new QLabel(m_formatCombo, "", page);
+ m_formatLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
+ glyr->addWidget( m_formatLabel, 0, 1 );
+
+ m_primaryKeyField = new QCheckBox( i18n( "Primary key" ), page, "m_primaryKeyField" );
+ glyr->addWidget( m_primaryKeyField, 2, 1 );
+ connect(m_primaryKeyField, SIGNAL(toggled(bool)), this, SLOT(slotPrimaryKeyFieldToggled(bool)));
+
+ m_comboQuote = new KexiCSVTextQuoteComboBox( page );
+ glyr->addWidget( m_comboQuote, 1, 2 );
+
+ TextLabel2 = new QLabel( m_comboQuote, i18n( "Text quote:" ), page, "TextLabel2" );
+ TextLabel2->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred );
+ TextLabel2->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
+ glyr->addWidget( TextLabel2, 0, 2 );
+
+ m_startAtLineSpinBox = new KIntSpinBox( page, "m_startAtLineSpinBox" );
+ m_startAtLineSpinBox->setMinValue(1);
+ m_startAtLineSpinBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ m_startAtLineSpinBox->setMinimumWidth(QFontMetrics(m_startAtLineSpinBox->font()).width("8888888"));
+ glyr->addWidget( m_startAtLineSpinBox, 1, 3 );
+
+ m_startAtLineLabel = new QLabel( m_startAtLineSpinBox, "",
+ page, "TextLabel3" );
+ m_startAtLineLabel->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred );
+ m_startAtLineLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
+ glyr->addWidget( m_startAtLineLabel, 0, 3 );
+
+ QSpacerItem* spacer_2 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
+ glyr->addItem( spacer_2, 0, 4 );
+
+ m_ignoreDuplicates = new QCheckBox( page, "m_ignoreDuplicates" );
+ m_ignoreDuplicates->setText( i18n( "Ignore duplicated delimiters" ) );
+ glyr->addMultiCellWidget( m_ignoreDuplicates, 2, 2, 2, 4 );
+
+ m_1stRowForFieldNames = new QCheckBox( page, "m_1stRowForFieldNames" );
+ m_1stRowForFieldNames->setText( i18n( "First row contains column names" ) );
+ glyr->addMultiCellWidget( m_1stRowForFieldNames, 3, 3, 2, 4 );
+
+ m_table = new KexiCSVImportDialogTable( plainPage(), "m_table" );
+ lyr->addWidget( m_table );
+
+ m_table->setSizePolicy( QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding, 1, 1) );
+ m_table->setNumRows( 0 );
+ m_table->setNumCols( 0 );
+
+/** @todo reuse Clipboard too! */
+/*
+if ( m_mode == Clipboard )
+ {
+ setCaption( i18n( "Inserting From Clipboard" ) );
+ QMimeSource * mime = QApplication::clipboard()->data();
+ if ( !mime )
+ {
+ KMessageBox::information( this, i18n("There is no data in the clipboard.") );
+ m_cancelled = true;
+ return;
+ }
+
+ if ( !mime->provides( "text/plain" ) )
+ {
+ KMessageBox::information( this, i18n("There is no usable data in the clipboard.") );
+ m_cancelled = true;
+ return;
+ }
+ m_fileArray = QByteArray(mime->encodedData( "text/plain" ) );
+ }
+ else if ( mode == File )
+ {*/
+ m_dateRegExp = QRegExp("(\\d{1,4})([/\\-\\.])(\\d{1,2})([/\\-\\.])(\\d{1,4})");
+ m_timeRegExp1 = QRegExp("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})");
+ m_timeRegExp2 = QRegExp("(\\d{1,2}):(\\d{1,2})");
+ m_fpNumberRegExp = QRegExp("[\\-]{0,1}\\d*[,\\.]\\d+");
+ QString caption( i18n("Open CSV Data File") );
+
+ if (m_mode == File) {
+ QStringList mimetypes( csvMimeTypes() );
+#ifdef Q_WS_WIN
+ //! @todo remove
+ QString recentDir = KGlobalSettings::documentPath();
+ m_fname = QFileDialog::getOpenFileName(
+ KFileDialog::getStartURL(":CSVImportExport", recentDir).path(),
+ KexiUtils::fileDialogFilterStrings(mimetypes, false),
+ page, "KexiCSVImportDialog", caption);
+ if ( !m_fname.isEmpty() ) {
+ //save last visited path
+ KURL url;
+ url.setPath( m_fname );
+ if (url.isLocalFile())
+ KRecentDirs::add(":CSVImportExport", url.directory());
+ }
+#else
+ m_fname = KFileDialog::getOpenFileName(":CSVImportExport", mimetypes.join(" "),
+ this, caption);
+#endif
+ //cancel action !
+ if ( m_fname.isEmpty() )
+ {
+ actionButton( Ok )->setEnabled( false );
+ m_cancelled = true;
+ if (parentWidget())
+ parentWidget()->raise();
+ return;
+ }
+ }
+ else if (m_mode == Clipboard) {
+ QCString subtype("plain");
+ m_clipboardData = QApplication::clipboard()->text(subtype, QClipboard::Clipboard);
+/* debug
+ for (int i=0;QApplication::clipboard()->data(QClipboard::Clipboard)->format(i);i++)
+ kdDebug() << i << ": "
+ << QApplication::clipboard()->data(QClipboard::Clipboard)->format(i) << endl;
+*/
+ }
+ else {
+ return;
+ }
+
+ m_loadingProgressDlg = 0;
+ m_importingProgressDlg = 0;
+ if (m_mode == File) {
+ m_loadingProgressDlg = new KProgressDialog(
+ this, "m_loadingProgressDlg", i18n("Loading CSV Data"), i18n("Loading CSV Data from \"%1\"...")
+ .arg(QDir::convertSeparators(m_fname)), true);
+ m_loadingProgressDlg->progressBar()->setTotalSteps( m_maximumRowsForPreview+1 );
+ m_loadingProgressDlg->show();
+ }
+
+ if (m_mode==Clipboard) {
+ m_infoLbl->setIcon("editpaste");
+ }
+ //updateRowCountInfo();
+
+ m_table->setSelectionMode(QTable::NoSelection);
+
+ connect(m_formatCombo, SIGNAL(activated(int)),
+ this, SLOT(formatChanged(int)));
+ connect(m_delimiterWidget, SIGNAL(delimiterChanged(const QString&)),
+ this, SLOT(delimiterChanged(const QString&)));
+ connect(m_startAtLineSpinBox, SIGNAL(valueChanged ( int )),
+ this, SLOT(startlineSelected(int)));
+ connect(m_comboQuote, SIGNAL(activated(int)),
+ this, SLOT(textquoteSelected(int)));
+ connect(m_table, SIGNAL(currentChanged(int, int)),
+ this, SLOT(currentCellChanged(int, int)));
+ connect(m_table, SIGNAL(valueChanged(int,int)),
+ this, SLOT(cellValueChanged(int,int)));
+ connect(m_ignoreDuplicates, SIGNAL(stateChanged(int)),
+ this, SLOT(ignoreDuplicatesChanged(int)));
+ connect(m_1stRowForFieldNames, SIGNAL(stateChanged(int)),
+ this, SLOT(slot1stRowForFieldNamesChanged(int)));
+
+ connect(this, SIGNAL(user1Clicked()), this, SLOT(optionsButtonClicked()));
+
+ installRecursiveEventFilter(this, this);
+
+ initLater();
+}
+
+KexiCSVImportDialog::~KexiCSVImportDialog()
+{
+ delete m_file;
+}
+
+void KexiCSVImportDialog::initLater()
+{
+ if (!openData())
+ return;
+
+// delimiterChanged(detectedDelimiter); // this will cause fillTable()
+ m_columnsAdjusted = false;
+ fillTable();
+ delete m_loadingProgressDlg;
+ m_loadingProgressDlg = 0;
+ if (m_dialogCancelled) {
+// m_loadingProgressDlg->hide();
+ // m_loadingProgressDlg->close();
+ QTimer::singleShot(0, this, SLOT(reject()));
+ return;
+ }
+
+ currentCellChanged(0, 0);
+
+// updateGeometry();
+ adjustSize();
+ KDialog::centerOnScreen( this );
+
+ if (m_loadingProgressDlg)
+ m_loadingProgressDlg->hide();
+ show();
+ m_table->setFocus();
+}
+
+bool KexiCSVImportDialog::openData()
+{
+ if (m_mode!=File) //data already loaded, no encoding stuff needed
+ return true;
+
+ delete m_inputStream;
+ m_inputStream = 0;
+ if (m_file) {
+ m_file->close();
+ delete m_file;
+ }
+ m_file = new QFile(m_fname);
+ if (!m_file->open(IO_ReadOnly))
+ {
+ m_file->close();
+ delete m_file;
+ m_file = 0;
+ KMessageBox::sorry( this, i18n("Cannot open input file <nobr>\"%1\"</nobr>.")
+ .arg(QDir::convertSeparators(m_fname)) );
+ actionButton( Ok )->setEnabled( false );
+ m_cancelled = true;
+ if (parentWidget())
+ parentWidget()->raise();
+ return false;
+ }
+ return true;
+}
+
+bool KexiCSVImportDialog::cancelled() const
+{
+ return m_cancelled;
+}
+
+void KexiCSVImportDialog::fillTable()
+{
+ KexiUtils::WaitCursor wc(true);
+ repaint();
+ m_blockUserEvents = true;
+ QPushButton *pb = actionButton(KDialogBase::Cancel);
+ if (pb)
+ pb->setEnabled(true); //allow to cancel
+ KexiUtils::WaitCursor wait;
+
+ if (m_table->numRows()>0) //to accept editor
+ m_table->setCurrentCell(0,0);
+
+ int row, column, maxColumn;
+ QString field = QString::null;
+
+ for (row = 0; row < m_table->numRows(); ++row)
+ for (column = 0; column < m_table->numCols(); ++column)
+ m_table->clearCell(row, column);
+
+ m_detectedTypes.clear();
+ m_detectedTypes.resize(1024, _NO_TYPE_YET);//_TEXT_TYPE);
+ m_uniquenessTest.clear();
+ m_uniquenessTest.resize(1024);
+ m_1stRowForFieldNamesDetected = true;
+
+ if (true != loadRows(field, row, column, maxColumn, true))
+ return;
+
+ m_1stRowForFieldNamesDetected = false;
+
+ // file with only one line without '\n'
+ if (field.length() > 0)
+ {
+ setText(row - m_startline, column, field, true);
+ ++row;
+ field = QString::null;
+ }
+
+ adjustRows( row - m_startline - (m_1stRowForFieldNames->isChecked()?1:0) );
+
+ maxColumn = QMAX( maxColumn, column );
+ m_table->setNumCols(maxColumn);
+
+ for (column = 0; column < m_table->numCols(); ++column)
+ {
+// QString header = m_table->horizontalHeader()->label(column);
+// if (header != i18n("Text") && header != i18n("Number") &&
+// header != i18n("Date") && header != i18n("Currency"))
+// const int detectedType = m_detectedTypes[column+1];
+// m_table->horizontalHeader()->setLabel(column, m_typeNames[ detectedType ]); //i18n("Text"));
+ updateColumnText(column);
+ if (!m_columnsAdjusted)
+ m_table->adjustColumn(column);
+ }
+ m_columnsAdjusted = true;
+
+ if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) {
+ if (_NUMBER_TYPE != m_detectedTypes[ m_primaryKeyColumn ]) {
+ m_primaryKeyColumn = -1;
+ }
+ }
+
+ m_prevSelectedCol = -1;
+ m_table->setCurrentCell(0,0);
+ currentCellChanged(0, 0);
+ if (m_primaryKeyColumn != -1)
+ m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon);
+
+ const int count = QMAX(0, m_table->numRows()-1+m_startline);
+ m_allRowsLoadedInPreview = count < m_maximumRowsForPreview && !m_stoppedAt_MAX_BYTES_TO_PREVIEW;
+ if (m_allRowsLoadedInPreview) {
+ m_startAtLineSpinBox->setMaxValue(count);
+ m_startAtLineSpinBox->setValue(m_startline+1);
+ }
+ m_startAtLineLabel->setText(i18n( "Start at line%1:").arg(
+ m_allRowsLoadedInPreview ? QString(" (1-%1)").arg(count)
+ : QString::null //we do not know what's real count
+ ));
+ updateRowCountInfo();
+
+ m_blockUserEvents = false;
+ repaint();
+ m_table->verticalScrollBar()->repaint();//avoid missing repaint
+ m_table->horizontalScrollBar()->repaint();//avoid missing repaint
+}
+
+QString KexiCSVImportDialog::detectDelimiterByLookingAtFirstBytesOfFile(QTextStream& inputStream)
+{
+ m_file->at(0);
+
+ // try to detect delimiter
+ // \t has priority, then ; then ,
+ const QIODevice::Offset origOffset = inputStream.device()->at();
+ QChar c, prevChar=0;
+ int detectedDelimiter = 0;
+ bool insideQuote = false;
+
+ //characters by priority
+ const int CH_TAB_AFTER_QUOTE = 500;
+ const int CH_SEMICOLON_AFTER_QUOTE = 499;
+ const int CH_COMMA_AFTER_QUOTE = 498;
+ const int CH_TAB = 200; // \t
+ const int CH_SEMICOLON = 199; // ;
+ const int CH_COMMA = 198; // ,
+
+ QValueList<int> tabsPerLine, semicolonsPerLine, commasPerLine;
+ int tabs = 0, semicolons = 0, commas = 0;
+ int line = 0;
+ for (uint i=0; !inputStream.atEnd() && i < MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER; i++) {
+ (*m_inputStream) >> c; // read one char
+ if (prevChar=='"') {
+ if (c!='"') //real quote (not double "")
+ insideQuote = !insideQuote;
+ }
+ if (insideQuote) {
+ prevChar = c;
+ continue;
+ }
+ if (c==' ')
+ continue;
+ if (c=='\n') {//end of line
+ //remember # of tabs/semicolons/commas in this line
+ tabsPerLine += tabs;
+ tabs = 0;
+ semicolonsPerLine += semicolons;
+ semicolons = 0;
+ commasPerLine += commas;
+ commas = 0;
+ line++;
+ }
+ else if (c=='\t') {
+ tabs++;
+ detectedDelimiter = QMAX( prevChar=='"' ? CH_TAB_AFTER_QUOTE : CH_TAB, detectedDelimiter );
+ }
+ else if (c==';') {
+ semicolons++;
+ detectedDelimiter = QMAX( prevChar=='"' ? CH_SEMICOLON_AFTER_QUOTE : CH_SEMICOLON, detectedDelimiter );
+ }
+ else if (c==',') {
+ commas++;
+ detectedDelimiter = QMAX( prevChar=='"' ? CH_COMMA_AFTER_QUOTE : CH_COMMA, detectedDelimiter );
+ }
+ prevChar = c;
+ }
+
+ inputStream.device()->at(origOffset); //restore orig. offset
+
+ //now, try to find a delimiter character that exists the same number of times in all the checked lines
+ //this detection method has priority over others
+ QValueList<int>::ConstIterator it;
+ if (tabsPerLine.count()>1) {
+ tabs = tabsPerLine.isEmpty() ? 0 : tabsPerLine.first();
+ for (it=tabsPerLine.constBegin(); it!=tabsPerLine.constEnd(); ++it) {
+ if (tabs != *it)
+ break;
+ }
+ if (tabs>0 && it==tabsPerLine.constEnd())
+ return "\t";
+ }
+ if (semicolonsPerLine.count()>1) {
+ semicolons = semicolonsPerLine.isEmpty() ? 0 : semicolonsPerLine.first();
+ for (it=semicolonsPerLine.constBegin(); it!=semicolonsPerLine.constEnd(); ++it) {
+ if (semicolons != *it)
+ break;
+ }
+ if (semicolons > 0 && it==semicolonsPerLine.constEnd())
+ return ";";
+ }
+ if (commasPerLine.count()>1) {
+ commas = commasPerLine.first();
+ for (it=commasPerLine.constBegin(); it!=commasPerLine.constEnd(); ++it) {
+ if (commas != *it)
+ break;
+ }
+ if (commas > 0 && it==commasPerLine.constEnd())
+ return ",";
+ }
+ //now return the winning character by looking at CH_* symbol
+ if (detectedDelimiter == CH_TAB_AFTER_QUOTE || detectedDelimiter == CH_TAB)
+ return "\t";
+ if (detectedDelimiter == CH_SEMICOLON_AFTER_QUOTE || detectedDelimiter == CH_SEMICOLON)
+ return ";";
+ if (detectedDelimiter == CH_COMMA_AFTER_QUOTE || detectedDelimiter == CH_COMMA)
+ return ",";
+
+ return KEXICSV_DEFAULT_FILE_DELIMITER; //<-- default
+}
+
+tristate KexiCSVImportDialog::loadRows(QString &field, int &row, int &column, int &maxColumn,
+ bool inGUI)
+{
+ enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
+ S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;
+ field = QString::null;
+ const bool ignoreDups = m_ignoreDuplicates->isChecked();
+ bool lastCharDelimiter = false;
+ bool nextRow = false;
+ row = column = 1;
+ maxColumn = 0;
+ QChar x;
+ const bool hadInputStream = m_inputStream!=0;
+ delete m_inputStream;
+ if ( m_mode == Clipboard ) {
+ m_inputStream = new QTextStream(m_clipboardData, IO_ReadOnly);
+ if (!hadInputStream)
+ m_delimiterWidget->setDelimiter(KEXICSV_DEFAULT_CLIPBOARD_DELIMITER);
+ }
+ else {
+ m_file->at(0); //always seek at 0 because loadRows() is called many times
+ m_inputStream = new QTextStream(m_file);
+ if (m_options.defaultEncodingExplicitySet) {
+ QTextCodec *codec = KGlobal::charsets()->codecForName(m_options.encoding);
+ if (codec)
+ m_inputStream->setCodec(codec); //QTextCodec::codecForName("CP1250"));
+ }
+ if (m_detectDelimiter) {
+ const QString delimiter( detectDelimiterByLookingAtFirstBytesOfFile(*m_inputStream) );
+ if (m_delimiterWidget->delimiter() != delimiter)
+ m_delimiterWidget->setDelimiter( delimiter );
+ }
+ }
+ const QChar delimiter(m_delimiterWidget->delimiter()[0]);
+ m_stoppedAt_MAX_BYTES_TO_PREVIEW = false;
+ int progressStep = 0;
+ if (m_importingProgressDlg)
+ progressStep = QMAX( 1, m_importingProgressDlg->progressBar()->totalSteps()/200 );
+ int offset = 0;
+ for (;!m_inputStream->atEnd(); offset++)
+ {
+//disabled: this breaks wide spreadsheets
+// if (column >= m_maximumRowsForPreview)
+// return true;
+
+ if (m_importingProgressDlg && ((offset % progressStep) < 5)) {
+ //update progr. bar dlg on final exporting
+ m_importingProgressDlg->progressBar()->setValue(offset);
+ qApp->processEvents();
+ if (m_importingProgressDlg->wasCancelled()) {
+ delete m_importingProgressDlg;
+ m_importingProgressDlg = 0;
+ return ::cancelled;
+ }
+ }
+
+ (*m_inputStream) >> x; // read one char
+
+ if (x == '\r') {
+ continue; // eat '\r', to handle RFC-compliant files
+ }
+ if (offset==0 && x.unicode()==0xfeff) {
+ // Ignore BOM, the "Byte Order Mark"
+ // (http://en.wikipedia.org/wiki/Byte_Order_Mark, // http://www.unicode.org/charts/PDF/UFFF0.pdf)
+ // Probably fixed in Qt4.
+ continue;
+ }
+
+ switch (state)
+ {
+ case S_START :
+ if (x == m_textquote)
+ {
+ state = S_QUOTED_FIELD;
+ }
+ else if (x == delimiter)
+ {
+ setText(row - m_startline, column, field, inGUI);
+ field = QString::null;
+ if ((ignoreDups == false) || (lastCharDelimiter == false))
+ ++column;
+ lastCharDelimiter = true;
+ }
+ else if (x == '\n')
+ {
+ if (!inGUI) {
+ //fill remaining empty fields (database wants them explicitly)
+ for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) {
+ setText(row - m_startline, additionalColumn, QString::null, inGUI);
+ }
+ }
+ nextRow = true;
+ maxColumn = QMAX( maxColumn, column );
+ column = 1;
+ }
+ else
+ {
+ field += x;
+ state = S_MAYBE_NORMAL_FIELD;
+ }
+ break;
+ case S_QUOTED_FIELD :
+ if (x == m_textquote)
+ {
+ state = S_MAYBE_END_OF_QUOTED_FIELD;
+ }
+/*allow \n inside quoted fields
+ else if (x == '\n')
+ {
+ setText(row - m_startline, column, field, inGUI);
+ field = "";
+ if (x == '\n')
+ {
+ nextRow = true;
+ maxColumn = QMAX( maxColumn, column );
+ column = 1;
+ }
+ else
+ {
+ if ((ignoreDups == false) || (lastCharDelimiter == false))
+ ++column;
+ lastCharDelimiter = true;
+ }
+ state = S_START;
+ }*/
+ else
+ {
+ field += x;
+ }
+ break;
+ case S_MAYBE_END_OF_QUOTED_FIELD :
+ if (x == m_textquote)
+ {
+ field += x; //no, this was just escaped quote character
+ state = S_QUOTED_FIELD;
+ }
+ else if (x == delimiter || x == '\n')
+ {
+ setText(row - m_startline, column, field, inGUI);
+ field = QString::null;
+ if (x == '\n')
+ {
+ nextRow = true;
+ maxColumn = QMAX( maxColumn, column );
+ column = 1;
+ }
+ else
+ {
+ if ((ignoreDups == false) || (lastCharDelimiter == false))
+ ++column;
+ lastCharDelimiter = true;
+ }
+ state = S_START;
+ }
+ else
+ {
+ state = S_END_OF_QUOTED_FIELD;
+ }
+ break;
+ case S_END_OF_QUOTED_FIELD :
+ if (x == delimiter || x == '\n')
+ {
+ setText(row - m_startline, column, field, inGUI);
+ field = QString::null;
+ if (x == '\n')
+ {
+ nextRow = true;
+ maxColumn = QMAX( maxColumn, column );
+ column = 1;
+ }
+ else
+ {
+ if ((ignoreDups == false) || (lastCharDelimiter == false))
+ ++column;
+ lastCharDelimiter = true;
+ }
+ state = S_START;
+ }
+ else
+ {
+ state = S_END_OF_QUOTED_FIELD;
+ }
+ break;
+ case S_MAYBE_NORMAL_FIELD :
+ if (x == m_textquote)
+ {
+ field = QString::null;
+ state = S_QUOTED_FIELD;
+ break;
+ }
+ case S_NORMAL_FIELD :
+ if (x == delimiter || x == '\n')
+ {
+ setText(row - m_startline, column, field, inGUI);
+ field = QString::null;
+ if (x == '\n')
+ {
+ nextRow = true;
+ maxColumn = QMAX( maxColumn, column );
+ column = 1;
+ }
+ else
+ {
+ if ((ignoreDups == false) || (lastCharDelimiter == false))
+ ++column;
+ lastCharDelimiter = true;
+ }
+ state = S_START;
+ }
+ else
+ {
+ field += x;
+ }
+ }
+ if (x != delimiter)
+ lastCharDelimiter = false;
+
+ if (nextRow) {
+ if (!inGUI && row==1 && m_1stRowForFieldNames->isChecked()) {
+ // do not save to the database 1st row if it contains column names
+ m_importingStatement->clearArguments();
+ }
+ else if (!saveRow(inGUI))
+ return false;
+
+ ++row;
+ }
+
+ if (m_firstFillTableCall && row==2
+ && !m_1stRowForFieldNames->isChecked() && m_1stRowForFieldNamesDetected)
+ {
+ //'1st row for field name' flag detected: reload table
+ m_1stRowForFieldNamesDetected = false;
+ m_table->setNumRows( 0 );
+ m_firstFillTableCall = false; //this trick is allowed only once, on startup
+ m_1stRowForFieldNames->setChecked(true); //this will reload table
+ //slot1stRowForFieldNamesChanged(1);
+ m_blockUserEvents = false;
+ repaint();
+ return false;
+ }
+
+ if (!m_importingProgressDlg && row % 20 == 0) {
+ qApp->processEvents();
+ //only for GUI mode:
+ if (!m_firstFillTableCall && m_loadingProgressDlg && m_loadingProgressDlg->wasCancelled()) {
+ delete m_loadingProgressDlg;
+ m_loadingProgressDlg = 0;
+ m_dialogCancelled = true;
+ reject();
+ return false;
+ }
+ }
+
+ if (!m_firstFillTableCall && m_loadingProgressDlg) {
+ m_loadingProgressDlg->progressBar()->setValue(QMIN(m_maximumRowsForPreview, row));
+ }
+
+ if ( inGUI && row > (m_maximumRowsForPreview + (m_1stRowForFieldNamesDetected?1:0)) ) {
+ kexipluginsdbg << "KexiCSVImportDialog::fillTable() loading stopped at row #"
+ << m_maximumRowsForPreview << endl;
+ break;
+ }
+ if (nextRow) {
+ nextRow = false;
+ //additional speedup: stop processing now if too many bytes were loaded for preview
+ kexipluginsdbg << offset << endl;
+ if (inGUI && offset >= m_maximumBytesForPreview && row >= 2) {
+ m_stoppedAt_MAX_BYTES_TO_PREVIEW = true;
+ return true;
+ }
+ }
+ }
+ return true;
+}
+
+void KexiCSVImportDialog::updateColumnText(int col)
+{
+ QString colName;
+ if (col<(int)m_columnNames.count() && (m_1stRowForFieldNames->isChecked() || m_changedColumnNames[col]))
+ colName = m_columnNames[ col ];
+ if (colName.isEmpty()) {
+ colName = i18n("Column %1").arg(col+1); //will be changed to a valid identifier on import
+ m_changedColumnNames[ col ] = false;
+ }
+ int detectedType = m_detectedTypes[col];
+ if (detectedType==_FP_NUMBER_TYPE)
+ detectedType=_NUMBER_TYPE; //we're simplifying that for now
+ else if (detectedType==_NO_TYPE_YET) {
+ m_detectedTypes[col]=_TEXT_TYPE; //entirely empty column
+ detectedType=_TEXT_TYPE;
+ }
+ m_table->horizontalHeader()->setLabel(col,
+ i18n("Column %1").arg(col+1) + " \n(" + m_typeNames[ detectedType ] + ") ");
+ m_table->setText(0, col, colName);
+ m_table->horizontalHeader()->adjustHeaderSize();
+
+ //check uniqueness
+ QValueList<int> *list = m_uniquenessTest[col];
+ if (m_primaryKeyColumn==-1 && list && !list->isEmpty()) {
+ qHeapSort(*list);
+ QValueList<int>::ConstIterator it=list->constBegin();
+ int prevValue = *it;
+ ++it;
+ for(; it!=list->constEnd() && prevValue!=(*it); ++it)
+ prevValue=(*it);
+ if (it!=list->constEnd()) {
+ //duplicates:
+ list->clear();
+ }
+ else {
+ //a candidate for PK (autodetected)!
+ if (-1==m_primaryKeyColumn) {
+ m_primaryKeyColumn=col;
+ }
+ }
+ }
+ if (list) //not needed now: conserve memory
+ list->clear();
+}
+
+void KexiCSVImportDialog::detectTypeAndUniqueness(int row, int col, const QString& text)
+{
+ int intValue;
+ const int type = m_detectedTypes[col];
+ if (row==1 || type!=_TEXT_TYPE) {
+ bool found = false;
+ if (text.isEmpty() && type==_NO_TYPE_YET)
+ found = true; //real type should be found later
+ //detect type because it's 1st row or all prev. rows were not text
+ //-FP number? (trying before "number" type is a must)
+ if (!found && (row==1 || type==_NUMBER_TYPE || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) {
+ bool ok = text.isEmpty() || m_fpNumberRegExp.exactMatch(text);
+ //if (!ok)
+ // text.toDouble(&ok);
+ if (ok && (row==1 || type==_NUMBER_TYPE || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) {
+ m_detectedTypes[col]=_FP_NUMBER_TYPE;
+ found = true; //yes
+ }
+ }
+ //-number?
+ if (!found && (row==1 || type==_NUMBER_TYPE || type==_NO_TYPE_YET)) {
+ bool ok = text.isEmpty();//empty values allowed
+ if (!ok)
+ intValue = text.toInt(&ok);
+ if (ok && (row==1 || type==_NO_TYPE_YET)) {
+ m_detectedTypes[col]=_NUMBER_TYPE;
+ found = true; //yes
+ }
+ }
+ //-date?
+ if (!found && (row==1 || type==_DATE_TYPE || type==_NO_TYPE_YET)) {
+ if ((row==1 || type==_NO_TYPE_YET)
+ && (text.isEmpty() || m_dateRegExp.exactMatch(text)))
+ {
+ m_detectedTypes[col]=_DATE_TYPE;
+ found = true; //yes
+ }
+ }
+ //-time?
+ if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) {
+ if ((row==1 || type==_NO_TYPE_YET)
+ && (text.isEmpty() || m_timeRegExp1.exactMatch(text) || m_timeRegExp2.exactMatch(text)))
+ {
+ m_detectedTypes[col]=_TIME_TYPE;
+ found = true; //yes
+ }
+ }
+ //-date/time?
+ if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) {
+ if (row==1 || type==_NO_TYPE_YET) {
+ bool detected = text.isEmpty();
+ if (!detected) {
+ const QStringList dateTimeList( QStringList::split(" ", text) );
+ bool ok = dateTimeList.count()>=2;
+//! @todo also support ISODateTime's "T" separator?
+//! @todo also support timezones?
+ if (ok) {
+ //try all combinations
+ QString datePart( dateTimeList[0].stripWhiteSpace() );
+ QString timePart( dateTimeList[1].stripWhiteSpace() );
+ ok = m_dateRegExp.exactMatch(datePart)
+ && (m_timeRegExp1.exactMatch(timePart) || m_timeRegExp2.exactMatch(timePart));
+ }
+ detected = ok;
+ }
+ if (detected) {
+ m_detectedTypes[col]=_DATETIME_TYPE;
+ found = true; //yes
+ }
+ }
+ }
+ if (!found && type==_NO_TYPE_YET && !text.isEmpty()) {
+ //eventually, a non-emptytext after a while
+ m_detectedTypes[col]=_TEXT_TYPE;
+ found = true; //yes
+ }
+ //default: text type (already set)
+ }
+ //check uniqueness for this value
+ QValueList<int> *list = m_uniquenessTest[col];
+ if (row==1 && (!list || !list->isEmpty()) && !text.isEmpty() && _NUMBER_TYPE == m_detectedTypes[col]) {
+ if (!list) {
+ list = new QValueList<int>();
+ m_uniquenessTest.insert(col, list);
+ }
+ list->append( intValue );
+ }
+ else {
+ //the value is empty or uniqueness test failed in the past
+ if (list && !list->isEmpty())
+ list->clear(); //indicate that uniqueness test failed
+ }
+}
+
+bool KexiCSVImportDialog::parseDate(const QString& text, QDate& date)
+{
+ if (!m_dateRegExp.exactMatch(text))
+ return false;
+ //dddd - dd - dddd
+ //1 2 3 4 5 <- pos
+ const int d1 = m_dateRegExp.cap(1).toInt(), d3 = m_dateRegExp.cap(3).toInt(), d5 = m_dateRegExp.cap(5).toInt();
+ if (m_dateRegExp.cap(2)=="/") //probably separator for american format mm/dd/yyyy
+ date = QDate(d5, d1, d3);
+ else {
+ if (d5 > 31) //d5 == year
+ date = QDate(d5, d3, d1);
+ else //d1 == year
+ date = QDate(d1, d3, d5);
+ }
+ return date.isValid();
+}
+
+bool KexiCSVImportDialog::parseTime(const QString& text, QTime& time)
+{
+ time = QTime::fromString(text, Qt::ISODate); //same as m_timeRegExp1
+ if (time.isValid())
+ return true;
+ if (m_timeRegExp2.exactMatch(text)) { //hh:mm:ss
+ time = QTime(m_timeRegExp2.cap(1).toInt(), m_timeRegExp2.cap(3).toInt(), m_timeRegExp2.cap(5).toInt());
+ return true;
+ }
+ return false;
+}
+
+void KexiCSVImportDialog::setText(int row, int col, const QString& text, bool inGUI)
+{
+ if (!inGUI) {
+ //save text directly to database buffer
+ if (col==1) { //1st col
+ m_importingStatement->clearArguments();
+ if (m_implicitPrimaryKeyAdded)
+ *m_importingStatement << QVariant(); //id will be autogenerated here
+ }
+ const int detectedType = m_detectedTypes[col-1];
+ if (detectedType==_NUMBER_TYPE) {
+ *m_importingStatement << ( text.isEmpty() ? QVariant() : text.toInt() );
+//! @todo what about time and float/double types and different integer subtypes?
+ }
+ else if (detectedType==_FP_NUMBER_TYPE) {
+ //replace ',' with '.'
+ QCString t(text.latin1());
+ const int textLen = t.length();
+ for (int i=0; i<textLen; i++) {
+ if (t.at(i)==',') {
+ t.at(i) = '.';
+ break;
+ }
+ }
+ *m_importingStatement << ( t.isEmpty() ? QVariant() : t.toDouble() );
+ }
+ else if (detectedType==_DATE_TYPE) {
+ QDate date;
+ if (parseDate(text, date))
+ *m_importingStatement << date;
+ }
+ else if (detectedType==_TIME_TYPE) {
+ QTime time;
+ if (parseTime(text, time))
+ *m_importingStatement << time;
+ }
+ else if (detectedType==_DATETIME_TYPE) {
+ QStringList dateTimeList( QStringList::split(" ", text) );
+ if (dateTimeList.count()<2)
+ dateTimeList = QStringList::split("T", text); //also support ISODateTime's "T" separator
+//! @todo also support timezones?
+ if (dateTimeList.count()>=2) {
+ QString datePart( dateTimeList[0].stripWhiteSpace() );
+ QDate date;
+ if (parseDate(datePart, date)) {
+ QString timePart( dateTimeList[1].stripWhiteSpace() );
+ QTime time;
+ if (parseTime(timePart, time))
+ *m_importingStatement << QDateTime(date, time);
+ }
+ }
+ }
+ else //_TEXT_TYPE and the rest
+ *m_importingStatement << (m_options.stripWhiteSpaceInTextValuesChecked ? text.stripWhiteSpace() : text);
+ return;
+ }
+ //save text to GUI (table view)
+ if (m_table->numCols() < col) {
+ m_table->setNumCols(col);
+ if ((int)m_columnNames.size() < m_table->numCols()) {
+ m_columnNames.resize(m_table->numCols()+10);
+ m_changedColumnNames.resize(m_table->numCols()+10);
+ }
+ }
+
+ if (m_1stRowForFieldNames->isChecked()) {
+ if ((row+m_startline)==1) {//this is for column name
+ if ((col-1) < (int)m_changedColumnNames.size() && false==m_changedColumnNames[col-1]) {
+ //this column has no custom name entered by a user
+ //-get the name from the data cell
+ QString colName(text.simplifyWhiteSpace());
+ if (!colName.isEmpty()) {
+ if (colName.left(1)>="0" && colName.left(1)<="9")
+ colName.prepend(i18n("Column")+" ");
+ m_columnNames[ col-1 ] = colName;
+ }
+ }
+ return;
+ }
+ }
+ else {
+ if ((row+m_startline)==1) {//this row is for column name
+ if (m_1stRowForFieldNamesDetected && !m_1stRowForFieldNames->isChecked()) {
+ QString f( text.simplifyWhiteSpace() );
+ if (f.isEmpty() || !f[0].isLetter())
+ m_1stRowForFieldNamesDetected = false; //this couldn't be a column name
+ }
+ }
+ row++; //1st row was for column names
+ }
+
+ if (row < 2) // skipped by the user
+ return;
+
+ if (m_table->numRows() < row) {
+// if (m_maximumRowsForPreview >= row+100)
+ m_table->setNumRows(row+100); /* We add more rows at a time to limit recalculations */
+ //else
+// m_table->setNumRows(m_maximumRowsForPreview);
+ m_table->verticalHeader()->setLabel(0, i18n("Column name")+" ");
+ m_adjustRows=true;
+ }
+
+ m_table->setText(row - 1, col - 1, (m_options.stripWhiteSpaceInTextValuesChecked ? text.stripWhiteSpace() : text));
+ m_table->verticalHeader()->setLabel(row-1, QString::number(row-1));
+
+ detectTypeAndUniqueness(row-1, col-1, text);
+}
+
+bool KexiCSVImportDialog::saveRow(bool inGUI)
+{
+ if (inGUI) {
+ //nothing to do
+ return true;
+ }
+ //save db buffer
+ bool res = m_importingStatement->execute();
+//todo: move
+ m_importingStatement->clearArguments();
+ return res;
+// return m_conn->insertRecord(*m_destinationTableSchema, m_dbRowBuffer);
+}
+
+void KexiCSVImportDialog::adjustRows(int iRows)
+{
+ if (m_adjustRows)
+ {
+ m_table->setNumRows( iRows );
+ m_adjustRows=false;
+ for (int i = 0; i<iRows; i++)
+ m_table->adjustRow(i);
+ }
+}
+
+void KexiCSVImportDialog::formatChanged(int id)
+{
+ if (id==_PK_FLAG) {
+ if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) {
+ m_table->setPixmap(0, m_primaryKeyColumn, QPixmap());
+ }
+ if (m_primaryKeyField->isChecked()) {
+ m_primaryKeyColumn = m_table->currentColumn();
+ m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon);
+ }
+ else
+ m_primaryKeyColumn = -1;
+ return;
+ }
+ else {
+ m_detectedTypes[m_table->currentColumn()]=id;
+ m_primaryKeyField->setEnabled( _NUMBER_TYPE == id );
+ m_primaryKeyField->setChecked( m_primaryKeyColumn == m_table->currentColumn() && m_primaryKeyField->isEnabled() );
+ }
+ updateColumnText(m_table->currentColumn());
+}
+
+void KexiCSVImportDialog::delimiterChanged(const QString& delimiter)
+{
+ Q_UNUSED(delimiter);
+ m_columnsAdjusted = false;
+ m_detectDelimiter = false; //selected by hand: do not detect in the future
+ //delayed, otherwise combobox won't be repainted
+ fillTableLater();
+}
+
+void KexiCSVImportDialog::textquoteSelected(int)
+{
+ const QString tq(m_comboQuote->textQuote());
+ if (tq.isEmpty())
+ m_textquote = 0;
+ else
+ m_textquote = tq[0];
+
+ kexipluginsdbg << "KexiCSVImportDialog::textquoteSelected(): " << m_textquote << endl;
+
+ //delayed, otherwise combobox won't be repainted
+ fillTableLater();
+}
+
+void KexiCSVImportDialog::fillTableLater()
+{
+ m_table->setNumRows( 0 );
+ QTimer::singleShot(10, this, SLOT(fillTable()));
+}
+
+void KexiCSVImportDialog::startlineSelected(int startline)
+{
+// const int startline = line.toInt() - 1;
+ if (m_startline == (startline-1))
+ return;
+ m_startline = startline-1;
+ m_adjustRows=true;
+ fillTable();
+ m_table->setFocus();
+}
+
+void KexiCSVImportDialog::currentCellChanged(int, int col)
+{
+ if (m_prevSelectedCol==col)
+ return;
+ m_prevSelectedCol = col;
+ int type = m_detectedTypes[col];
+ if (type==_FP_NUMBER_TYPE)
+ type=_NUMBER_TYPE; //we're simplifying that for now
+
+ m_formatCombo->setCurrentItem( type );
+ m_formatLabel->setText( m_formatComboText.arg(col+1) );
+ m_primaryKeyField->setEnabled( _NUMBER_TYPE == m_detectedTypes[col]);
+ m_primaryKeyField->blockSignals(true); //block to disable executing slotPrimaryKeyFieldToggled()
+ m_primaryKeyField->setChecked( m_primaryKeyColumn == col );
+ m_primaryKeyField->blockSignals(false);
+}
+
+void KexiCSVImportDialog::cellValueChanged(int row,int col)
+{
+ if (row==0) {//column name has changed
+ m_columnNames[ col ] = m_table->text(row, col);
+ m_changedColumnNames.setBit( col );
+ }
+}
+
+void KexiCSVImportDialog::accept()
+{
+//! @todo MOVE MOST OF THIS TO CORE/ (KexiProject?) after KexiDialogBase code is moved to non-gui place
+
+ KexiGUIMessageHandler msg; //! @todo make it better integrated with main window
+
+ const uint numRows( m_table->numRows() );
+ if (numRows == 0)
+ return; //impossible
+
+ if (numRows == 1) {
+ if (KMessageBox::No == KMessageBox::questionYesNo(this,
+ i18n("Data set contains no rows. Do you want to import empty table?")))
+ return;
+ }
+
+ KexiProject* project = m_mainWin->project();
+ if (!project) {
+ msg.showErrorMessage(i18n("No project available."));
+ return;
+ }
+ m_conn = project->dbConnection(); //cache this pointer
+ if (!m_conn) {
+ msg.showErrorMessage(i18n("No database connection available."));
+ return;
+ }
+ KexiPart::Part *part = Kexi::partManager().partForMimeType("kexi/table");
+ if (!part) {
+ msg.showErrorMessage(&Kexi::partManager());
+ return;
+ }
+
+ //get suggested name based on the file name
+ QString suggestedName;
+ if (m_mode==File) {
+ suggestedName = KURL::fromPathOrURL(m_fname).fileName();
+ //remove extension
+ if (!suggestedName.isEmpty()) {
+ const int idx = suggestedName.findRev(".");
+ if (idx!=-1)
+ suggestedName = suggestedName.mid(0, idx ).simplifyWhiteSpace();
+ }
+ }
+
+ //-new part item
+ KexiPart::Item* partItemForSavedTable = project->createPartItem(part->info(), suggestedName);
+ if (!partItemForSavedTable) {
+ // msg.showErrorMessage(project);
+ return;
+ }
+
+#define _ERR \
+ { project->deleteUnstoredItem(partItemForSavedTable); \
+ m_conn = 0; \
+ delete m_destinationTableSchema; \
+ m_destinationTableSchema = 0; \
+ return; }
+
+ //-ask for table name/title
+ // (THIS IS FROM KexiMainWindowImpl::saveObject())
+ bool allowOverwriting = true;
+ tristate res = m_mainWin->getNewObjectInfo( partItemForSavedTable, part, allowOverwriting );
+ if (~res || !res) {
+ //! @todo: err
+ _ERR;
+ }
+ //(allowOverwriting is now set to true, if user accepts overwriting,
+ // and overwriting will be needed)
+
+// KexiDB::SchemaData sdata(part->info()->projectPartID());
+// sdata.setName( partItem->name() );
+
+ //-create table schema (and thus schema object)
+ //-assign information (THIS IS FROM KexiDialogBase::storeNewData())
+ m_destinationTableSchema = new KexiDB::TableSchema(partItemForSavedTable->name());
+ m_destinationTableSchema->setCaption( partItemForSavedTable->caption() );
+ m_destinationTableSchema->setDescription( partItemForSavedTable->description() );
+ const uint numCols( m_table->numCols() );
+
+ m_implicitPrimaryKeyAdded = false;
+ //add PK if user wanted it
+ int msgboxResult;
+ if (m_primaryKeyColumn==-1
+ && KMessageBox::No != (msgboxResult = KMessageBox::questionYesNoCancel(this,
+ i18n("No Primary Key (autonumber) has been defined.\n"
+ "Should it be automatically defined on import (recommended)?\n\n"
+ "Note: An imported table without a Primary Key may not be editable (depending on database type)."),
+ QString::null, KGuiItem(i18n("Add Database Primary Key to a Table", "Add Primary Key"), "key"),
+ KGuiItem(i18n("Do Not Add Database Primary Key to a Table", "Do Not Add")))))
+ {
+ if (msgboxResult == KMessageBox::Cancel)
+ _ERR; //cancel accepting
+
+ //add implicit PK field
+//! @todo make this field hidden (what about e.g. pgsql?)
+ m_implicitPrimaryKeyAdded = true;
+
+ QString fieldName("id");
+ QString fieldCaption("Id");
+
+ QStringList colnames;
+ for (uint col = 0; col < numCols; col++)
+ colnames.append( m_table->text(0, col).lower().simplifyWhiteSpace() );
+
+ if (colnames.find(fieldName)!=colnames.end()) {
+ int num = 1;
+ while (colnames.find(fieldName+QString::number(num))!=colnames.end())
+ num++;
+ fieldName += QString::number(num);
+ fieldCaption += QString::number(num);
+ }
+ KexiDB::Field *field = new KexiDB::Field(
+ fieldName,
+ KexiDB::Field::Integer,
+ KexiDB::Field::NoConstraints,
+ KexiDB::Field::NoOptions,
+ 0,0, //uint length=0, uint precision=0,
+ QVariant(), //QVariant defaultValue=QVariant(),
+ fieldCaption
+ ); //no description and width for now
+ field->setPrimaryKey(true);
+ field->setAutoIncrement(true);
+ m_destinationTableSchema->addField( field );
+ }
+
+ for (uint col = 0; col < numCols; col++) {
+ QString fieldCaption( m_table->text(0, col).simplifyWhiteSpace() );
+ QString fieldName( KexiUtils::string2Identifier( fieldCaption ) );
+ if (m_destinationTableSchema->field(fieldName)) {
+ QString fixedFieldName;
+ uint i = 2; //"apple 2, apple 3, etc. if there're many "apple" names
+ do {
+ fixedFieldName = fieldName + "_" + QString::number(i);
+ if (!m_destinationTableSchema->field(fixedFieldName))
+ break;
+ i++;
+ } while (true);
+ fieldName = fixedFieldName;
+ fieldCaption += (" " + QString::number(i));
+ }
+ const int detectedType = m_detectedTypes[col];
+ KexiDB::Field::Type fieldType;
+ if (detectedType==_DATE_TYPE)
+ fieldType = KexiDB::Field::Date;
+ if (detectedType==_TIME_TYPE)
+ fieldType = KexiDB::Field::Time;
+ if (detectedType==_DATETIME_TYPE)
+ fieldType = KexiDB::Field::DateTime;
+ else if (detectedType==_NUMBER_TYPE)
+ fieldType = KexiDB::Field::Integer;
+ else if (detectedType==_FP_NUMBER_TYPE)
+ fieldType = KexiDB::Field::Double;
+//! @todo what about time and float/double types and different integer subtypes?
+ else //_TEXT_TYPE and the rest
+ fieldType = KexiDB::Field::Text;
+//! @todo what about long text?
+
+ KexiDB::Field *field = new KexiDB::Field(
+ fieldName,
+ fieldType,
+ KexiDB::Field::NoConstraints,
+ KexiDB::Field::NoOptions,
+ 0,0, //uint length=0, uint precision=0,
+ QVariant(), //QVariant defaultValue=QVariant(),
+ fieldCaption
+ ); //no description and width for now
+
+ if ((int)col == m_primaryKeyColumn) {
+ field->setPrimaryKey(true);
+ field->setAutoIncrement(true);
+ }
+ m_destinationTableSchema->addField( field );
+ }
+
+ KexiDB::Transaction transaction = m_conn->beginTransaction();
+ if (transaction.isNull()) {
+ msg.showErrorMessage(m_conn);
+ _ERR;
+ }
+ KexiDB::TransactionGuard tg(transaction);
+
+ //-create physical table
+ if (!m_conn->createTable(m_destinationTableSchema, allowOverwriting)) {
+ msg.showErrorMessage(m_conn);
+ _ERR;
+ }
+
+#define _DROP_DEST_TABLE_AND_RETURN \
+ { \
+ if (m_importingProgressDlg) \
+ m_importingProgressDlg->hide(); \
+ project->deleteUnstoredItem(partItemForSavedTable); \
+ m_conn->dropTable(m_destinationTableSchema); /*alsoRemoveSchema*/ \
+ m_destinationTableSchema = 0; \
+ m_conn = 0; \
+ return; \
+ }
+
+ m_importingStatement = m_conn->prepareStatement(
+ KexiDB::PreparedStatement::InsertStatement, *m_destinationTableSchema);
+ if (!m_importingStatement) {
+ msg.showErrorMessage(m_conn);
+ _DROP_DEST_TABLE_AND_RETURN;
+ }
+
+ if (m_file) {
+ if (!m_importingProgressDlg) {
+ m_importingProgressDlg = new KProgressDialog( this, "m_importingProgressDlg",
+ i18n("Importing CSV Data"), QString::null, true );
+ }
+ m_importingProgressDlg->setLabel(
+ i18n("Importing CSV Data from <nobr>\"%1\"</nobr> into \"%2\" table...")
+ .arg(QDir::convertSeparators(m_fname)).arg(m_destinationTableSchema->name()) );
+ m_importingProgressDlg->progressBar()->setTotalSteps( QFileInfo(*m_file).size() );
+ m_importingProgressDlg->show();
+ }
+
+ int row, column, maxColumn;
+ QString field = QString::null;
+
+ // main job
+ res = loadRows(field, row, column, maxColumn, false /*!gui*/ );
+
+ delete m_importingProgressDlg;
+ m_importingProgressDlg = 0;
+ if (true != res) {
+ //importing cancelled or failed
+ if (!res) //do not display err msg when res == cancelled
+ msg.showErrorMessage(m_conn);
+ _DROP_DEST_TABLE_AND_RETURN;
+ }
+
+ // file with only one line without '\n'
+ if (field.length() > 0)
+ {
+ setText(row - m_startline, column, field, false /*!gui*/);
+ //fill remaining empty fields (database wants them explicitly)
+ for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) {
+ setText(row - m_startline, additionalColumn, QString::null, false /*!gui*/);
+ }
+ if (!saveRow(false /*!gui*/)) {
+ msg.showErrorMessage(m_conn);
+ _DROP_DEST_TABLE_AND_RETURN;
+ }
+ ++row;
+ field = QString::null;
+ }
+
+ if (!tg.commit()) {
+ msg.showErrorMessage(m_conn);
+ _DROP_DEST_TABLE_AND_RETURN;
+ }
+
+ //-now we can store the item
+ partItemForSavedTable->setIdentifier( m_destinationTableSchema->id() );
+ project->addStoredItem( part->info(), partItemForSavedTable );
+
+ QDialog::accept();
+ KMessageBox::information(this, i18n("Data has been successfully imported to table \"%1\".")
+ .arg(m_destinationTableSchema->name()));
+ parentWidget()->raise();
+ m_conn = 0;
+}
+
+int KexiCSVImportDialog::getHeader(int col)
+{
+ QString header = m_table->horizontalHeader()->label(col);
+
+ if (header == i18n("Text type for column", "Text"))
+ return TEXT;
+ else if (header == i18n("Numeric type for column", "Number"))
+ return NUMBER;
+ else if (header == i18n("Currency type for column", "Currency"))
+ return CURRENCY;
+ else
+ return DATE;
+}
+
+QString KexiCSVImportDialog::getText(int row, int col)
+{
+ return m_table->text(row, col);
+}
+
+void KexiCSVImportDialog::ignoreDuplicatesChanged(int)
+{
+ fillTable();
+}
+
+void KexiCSVImportDialog::slot1stRowForFieldNamesChanged(int)
+{
+ m_adjustRows=true;
+ if (m_1stRowForFieldNames->isChecked() && m_startline>0 && m_startline>=(m_startAtLineSpinBox->maxValue()-1))
+ m_startline--;
+ fillTable();
+}
+
+void KexiCSVImportDialog::optionsButtonClicked()
+{
+ KexiCSVImportOptionsDialog dlg(m_options, this);
+ if (QDialog::Accepted != dlg.exec())
+ return;
+
+ KexiCSVImportOptions newOptions( dlg.options() );
+ if (m_options != newOptions) {
+ m_options = newOptions;
+ if (!openData())
+ return;
+ fillTable();
+ }
+}
+
+bool KexiCSVImportDialog::eventFilter ( QObject * watched, QEvent * e )
+{
+ QEvent::Type t = e->type();
+ // temporary disable keyboard and mouse events for time-consuming tasks
+ if (m_blockUserEvents && (t==QEvent::KeyPress || t==QEvent::KeyRelease
+ || t==QEvent::MouseButtonPress || t==QEvent::MouseButtonDblClick
+ || t==QEvent::Paint ))
+ return true;
+
+ if (watched == m_startAtLineSpinBox && t==QEvent::KeyPress) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) {
+ m_table->setFocus();
+ return true;
+ }
+ }
+ return QDialog::eventFilter( watched, e );
+}
+
+void KexiCSVImportDialog::slotPrimaryKeyFieldToggled(bool on)
+{
+ Q_UNUSED(on);
+ formatChanged(_PK_FLAG);
+}
+
+void KexiCSVImportDialog::updateRowCountInfo()
+{
+ m_infoLbl->setFileName( m_fname );
+ if (m_allRowsLoadedInPreview) {
+ m_infoLbl->setCommentText(
+ i18n("row count", "(rows: %1)").arg( m_table->numRows()-1+m_startline ) );
+ QToolTip::remove( m_infoLbl );
+ }
+ else {
+ m_infoLbl->setCommentText(
+ i18n("row count", "(rows: more than %1)").arg( m_table->numRows()-1+m_startline ) );
+ QToolTip::add( m_infoLbl->commentLabel(), i18n("Not all rows are visible on this preview") );
+ }
+}
+
+#include "kexicsvimportdialog.moc"