diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /kexi/plugins/importexport/csv/kexicsvimportdialog.cpp | |
download | koffice-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.cpp | 1662 |
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" |