kexi

kexicsvimportdialog.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
00003 
00004    This work is based on kspread/dialogs/kspread_dlg_csv.cc
00005    and will be merged back with KOffice libraries.
00006 
00007    Copyright (C) 2002-2003 Norbert Andres <nandres@web.de>
00008              (C) 2002-2003 Ariya Hidayat <ariya@kde.org>
00009              (C) 2002     Laurent Montel <montel@kde.org>
00010              (C) 1999 David Faure <faure@kde.org>
00011 
00012    This library is free software; you can redistribute it and/or
00013    modify it under the terms of the GNU Library General Public
00014    License as published by the Free Software Foundation; either
00015    version 2 of the License, or (at your option) any later version.
00016 
00017    This library is distributed in the hope that it will be useful,
00018    but WITHOUT ANY WARRANTY; without even the implied warranty of
00019    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00020    Library General Public License for more details.
00021 
00022    You should have received a copy of the GNU Library General Public License
00023    along with this library; see the file COPYING.LIB.  If not, write to
00024    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00025  * Boston, MA 02110-1301, USA.
00026 */
00027 
00028 #include <qbuttongroup.h>
00029 #include <qcheckbox.h>
00030 #include <qclipboard.h>
00031 #include <qlabel.h>
00032 #include <qlineedit.h>
00033 #include <qmime.h>
00034 #include <qpushbutton.h>
00035 #include <qradiobutton.h>
00036 #include <qtable.h>
00037 #include <qlayout.h>
00038 #include <qfiledialog.h>
00039 #include <qpainter.h>
00040 #include <qtextcodec.h>
00041 #include <qtimer.h>
00042 #include <qfontmetrics.h>
00043 
00044 #include <kapplication.h>
00045 #include <kdebug.h>
00046 #include <kdialogbase.h>
00047 #include <kfiledialog.h>
00048 #include <klocale.h>
00049 #include <kmessagebox.h>
00050 #include <kglobalsettings.h>
00051 #include <kiconloader.h>
00052 #include <kcharsets.h>
00053 #include <knuminput.h>
00054 #include <kprogress.h>
00055 
00056 #include <kexiutils/identifier.h>
00057 #include <kexiutils/utils.h>
00058 #include <core/kexi.h>
00059 #include <core/kexiproject.h>
00060 #include <core/kexipart.h>
00061 #include <core/kexipartinfo.h>
00062 #include <core/keximainwindow.h>
00063 #include <core/kexiguimsghandler.h>
00064 #include <kexidb/connection.h>
00065 #include <kexidb/tableschema.h>
00066 #include <kexidb/transaction.h>
00067 #include <widget/kexicharencodingcombobox.h>
00068 
00069 #include "kexicsvimportdialog.h"
00070 #include "kexicsvimportoptionsdlg.h"
00071 #include "kexicsvwidgets.h"
00072 
00073 #ifdef Q_WS_WIN
00074 #include <krecentdirs.h>
00075 #include <windows.h>
00076 #endif
00077 
00078 #if 0
00079 #include <kspread_cell.h>
00080 #include <kspread_doc.h>
00081 #include <kspread_sheet.h>
00082 #include <kspread_undo.h>
00083 #include <kspread_view.h>
00084 #endif
00085 
00086 #define _IMPORT_ICON "table" /*todo: change to "file_import" or so*/
00087 #define _TEXT_TYPE 0
00088 #define _NUMBER_TYPE 1
00089 #define _DATE_TYPE 2
00090 #define _TIME_TYPE 3
00091 #define _DATETIME_TYPE 4
00092 #define _PK_FLAG 5
00093 
00094 //extra:
00095 #define _NO_TYPE_YET -1 //allows to accept a number of empty cells, before something non-empty
00096 #define _FP_NUMBER_TYPE 255 //_NUMBER_TYPE variant
00097 #define MAX_COLUMNS 100 //max 100 columns is reasonable
00098 
00099 class KexiCSVImportDialogTable : public QTable
00100 {
00101 public:
00102     KexiCSVImportDialogTable( QWidget * parent = 0, const char * name = 0 )
00103     : QTable(parent, name) {
00104         f = font();
00105         f.setBold(true);
00106     }
00107     virtual void paintCell( QPainter * p, int row, int col, const QRect & cr, bool selected, const QColorGroup & cg ) {
00108         if (row==0)
00109             p->setFont(f);
00110         else
00111             p->setFont(font());
00112         QTable::paintCell(p, row, col, cr, selected, cg);
00113     }
00114     virtual void setColumnWidth( int col, int w ) {
00115         //make columns a bit wider
00116         QTable::setColumnWidth( col, w + 16 );
00117     }
00118     QFont f;
00119 };
00120 
00122 void installRecursiveEventFilter(QObject *filter, QObject *object)
00123 {
00124     object->installEventFilter(filter);
00125 
00126     if (!object->children())
00127         return;
00128 
00129     QObjectList list = *object->children();
00130     for(QObject *obj = list.first(); obj; obj = list.next())
00131         installRecursiveEventFilter(filter, obj);
00132 }
00133 
00134 KexiCSVImportDialog::KexiCSVImportDialog( Mode mode, KexiMainWindow* mainWin, 
00135     QWidget * parent, const char * name
00136 )
00137  : KDialogBase( 
00138     KDialogBase::Plain, 
00139     i18n( "Import CSV Data File" )
00141     ,
00142     (mode==File ? User1 : (ButtonCode)0) |Ok|Cancel, 
00143     Ok,
00144     parent, 
00145     name ? name : "KexiCSVImportDialog", 
00146     true, 
00147     false,
00148     KGuiItem( i18n("&Options"))
00149   ),
00150     m_mainWin(mainWin),
00151     m_cancelled( false ),
00152     m_adjustRows( 0 ),
00153     m_startline( 0 ),
00154     m_textquote( QString(KEXICSV_DEFAULT_TEXT_QUOTE)[0] ),
00155     m_mode(mode),
00156     m_prevSelectedCol(-1),
00157     m_columnsAdjusted(false),
00158     m_1stRowForFieldNamesDetected(false),
00159     m_firstFillTableCall(true),
00160     m_blockUserEvents(false),
00161     m_primaryKeyColumn(-1),
00162     m_dialogCancelled(false),
00163     m_conn(0),
00164     m_destinationTableSchema(0)
00165 {
00166     setWFlags(getWFlags() | Qt::WStyle_Maximize | Qt::WStyle_SysMenu);
00167     hide();
00168     setButtonOK(KGuiItem( i18n("&Import..."), _IMPORT_ICON));
00169 
00170     m_typeNames.resize(5);
00171     m_typeNames[0] = i18n("text");
00172     m_typeNames[1] = i18n("number");
00173     m_typeNames[2] = i18n("date");
00174     m_typeNames[3] = i18n("time");
00175     m_typeNames[4] = i18n("date/time");
00176 
00177     kapp->config()->setGroup("ImportExport");
00178     m_maximumRowsForPreview = kapp->config()->readNumEntry("MaximumRowsForPreviewInImportDialog", MAX_COLUMNS);
00179 
00180     m_pkIcon = SmallIcon("key");
00181 
00182     m_uniquenessTest.setAutoDelete(true);
00183 
00184     setIcon(DesktopIcon(_IMPORT_ICON));
00185     setSizeGripEnabled( TRUE );
00186 
00187     m_encoding = QString::fromLatin1(KGlobal::locale()->encoding());
00188     m_file = 0;
00189     m_inputStream = 0;
00190     
00191     QVBoxLayout *lyr = new QVBoxLayout(plainPage(), 0, KDialogBase::spacingHint(), "lyr");
00192 
00193     m_infoLbl = new KexiCSVInfoLabel(
00194         m_mode==File ? i18n("Preview of data from file:")
00195         : i18n("Preview of data from clipboard:"),
00196         plainPage()
00197     );
00198     lyr->addWidget( m_infoLbl );
00199 
00200     QWidget* page = new QFrame( plainPage(), "page" );
00201     QGridLayout *glyr= new QGridLayout( page, 4, 5, 0, KDialogBase::spacingHint(), "glyr");
00202     lyr->addWidget( page );
00203 
00204     // Delimiter: comma, semicolon, tab, space, other
00205     m_delimiterWidget = new KexiCSVDelimiterWidget(true /*lineEditOnBottom*/, page);
00206     glyr->addMultiCellWidget( m_delimiterWidget, 1, 2, 0, 0 );
00207 
00208     QLabel *delimiterLabel = new QLabel(m_delimiterWidget, i18n("Delimiter:"), page);
00209     delimiterLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
00210     glyr->addMultiCellWidget( delimiterLabel, 0, 0, 0, 0 );
00211 
00212     // Format: number, text, currency,
00213     m_formatComboText = i18n( "Format for column %1:" );
00214     m_formatCombo = new KComboBox(page, "m_formatCombo");
00215     m_formatCombo->insertItem(i18n("Text"));
00216     m_formatCombo->insertItem(i18n("Number"));
00217     m_formatCombo->insertItem(i18n("Date"));
00218     m_formatCombo->insertItem(i18n("Time"));
00219     m_formatCombo->insertItem(i18n("Date/Time"));
00220     glyr->addMultiCellWidget( m_formatCombo, 1, 1, 1, 1 );
00221 
00222     m_formatLabel = new QLabel(m_formatCombo, "", page);
00223     m_formatLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
00224     glyr->addWidget( m_formatLabel, 0, 1 );
00225 
00226     m_primaryKeyField = new QCheckBox( i18n( "Primary key" ), page, "m_primaryKeyField" );
00227     glyr->addWidget( m_primaryKeyField, 2, 1 );
00228     connect(m_primaryKeyField, SIGNAL(toggled(bool)), this, SLOT(slotPrimaryKeyFieldToggled(bool)));
00229 
00230     m_comboQuote = new KexiCSVTextQuoteComboBox( page );
00231     glyr->addWidget( m_comboQuote, 1, 2 );
00232 
00233     TextLabel2 = new QLabel( m_comboQuote, i18n( "Text quote:" ), page, "TextLabel2" );
00234     TextLabel2->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred );
00235     TextLabel2->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
00236     glyr->addWidget( TextLabel2, 0, 2 );
00237 
00238     m_startAtLineSpinBox = new KIntSpinBox( page, "m_startAtLineSpinBox" );
00239     m_startAtLineSpinBox->setMinValue(1);
00240     m_startAtLineSpinBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
00241     m_startAtLineSpinBox->setMinimumWidth(QFontMetrics(m_startAtLineSpinBox->font()).width("8888888"));
00242     glyr->addWidget( m_startAtLineSpinBox, 1, 3 );
00243 
00244     m_startAtLineLabel = new QLabel( m_startAtLineSpinBox, "", 
00245         page, "TextLabel3" );
00246     m_startAtLineLabel->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred );
00247     m_startAtLineLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
00248     glyr->addWidget( m_startAtLineLabel, 0, 3 );
00249 
00250     QSpacerItem* spacer_2 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
00251     glyr->addItem( spacer_2, 0, 4 );
00252 
00253     m_ignoreDuplicates = new QCheckBox( page, "m_ignoreDuplicates" );
00254     m_ignoreDuplicates->setText( i18n( "Ignore duplicated delimiters" ) );
00255     glyr->addMultiCellWidget( m_ignoreDuplicates, 2, 2, 2, 4 );
00256 
00257     m_1stRowForFieldNames = new QCheckBox( page, "m_1stRowForFieldNames" );
00258     m_1stRowForFieldNames->setText( i18n( "First row contains column names" ) );
00259     glyr->addMultiCellWidget( m_1stRowForFieldNames, 3, 3, 2, 4 );
00260 
00261     m_table = new KexiCSVImportDialogTable( plainPage(), "m_table" );
00262     lyr->addWidget( m_table );
00263 
00264     m_table->setSizePolicy( QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding, 1, 1) );
00265     m_table->setNumRows( 0 );
00266     m_table->setNumCols( 0 );
00267 
00269 /*
00270 if ( m_mode == Clipboard )
00271   {
00272     setCaption( i18n( "Inserting From Clipboard" ) );
00273     QMimeSource * mime = QApplication::clipboard()->data();
00274     if ( !mime )
00275     {
00276       KMessageBox::information( this, i18n("There is no data in the clipboard.") );
00277       m_cancelled = true;
00278       return;
00279     }
00280 
00281     if ( !mime->provides( "text/plain" ) )
00282     {
00283       KMessageBox::information( this, i18n("There is no usable data in the clipboard.") );
00284       m_cancelled = true;
00285       return;
00286     }
00287     m_fileArray = QByteArray(mime->encodedData( "text/plain" ) );
00288   }
00289   else if ( mode == File )
00290   {*/
00291     m_dateRegExp1 = QRegExp("\\d{1,4}[/\\-\\.]\\d{1,2}[/\\-\\.]\\d{1,2}");
00292     m_dateRegExp2 = QRegExp("\\d{1,2}[/\\-\\.]\\d{1,2}[/\\-\\.]\\d{1,4}");
00293     m_timeRegExp1 = QRegExp("\\d{1,2}:\\d{1,2}:\\d{1,2}");
00294     m_timeRegExp2 = QRegExp("\\d{1,2}:\\d{1,2}");
00295     m_fpNumberRegExp = QRegExp("\\d*[,\\.]\\d+");
00296 
00297     if (m_mode == File) {
00298         QStringList mimetypes( csvMimeTypes() );
00299 #ifdef Q_WS_WIN
00300 
00301         QString recentDir = KGlobalSettings::documentPath();
00302         m_fname = QFileDialog::getOpenFileName( 
00303             KFileDialog::getStartURL(":CSVImportExport", recentDir).path(),
00304             KexiUtils::fileDialogFilterStrings(mimetypes, false),
00305             page, "KexiCSVImportDialog", i18n("Open CSV Data File"));
00306         if ( !m_fname.isEmpty() ) {
00307             //save last visited path
00308             KURL url;
00309             url.setPath( m_fname );
00310             if (url.isLocalFile())
00311                 KRecentDirs::add(":CSVImportExport", url.directory());
00312         }
00313 #else
00314         m_fname = KFileDialog::getOpenFileName(":CSVImportExport", mimetypes.join(" "), this);
00315 #endif
00316         //cancel action !
00317         if ( m_fname.isEmpty() )
00318         {
00319             actionButton( Ok )->setEnabled( false );
00320             m_cancelled = true;
00321             if (parentWidget())
00322                 parentWidget()->raise();
00323             return;
00324         }
00325     }
00326     else if (m_mode == Clipboard) {
00327         QCString subtype("plain");
00328         m_data = QApplication::clipboard()->text(subtype, QClipboard::Clipboard);
00329 /* debug
00330         for (int i=0;QApplication::clipboard()->data(QClipboard::Clipboard)->format(i);i++)
00331             kdDebug() << i << ": " 
00332                 << QApplication::clipboard()->data(QClipboard::Clipboard)->format(i) << endl;
00333 */
00334     }
00335     else {
00336         return;
00337     }
00338 
00339     m_loadingProgressDlg = 0;
00340     m_importingProgressDlg = 0;
00341     if (m_mode == File) {
00342         m_loadingProgressDlg = new KProgressDialog(
00343             this, "m_loadingProgressDlg", i18n("Loading CSV Data"), i18n("Loading CSV Data from \"%1\"...")
00344             .arg(QDir::convertSeparators(m_fname)), true);
00345         m_loadingProgressDlg->progressBar()->setTotalSteps( m_maximumRowsForPreview+1 );
00346         m_loadingProgressDlg->show();
00347     }
00348 
00349     if (m_mode==Clipboard) {
00350         m_infoLbl->setIcon("editpaste");
00351     }
00352     updateRowCountInfo();
00353 
00354     m_table->setSelectionMode(QTable::NoSelection);
00355 
00356     connect(m_formatCombo, SIGNAL(activated(int)),
00357       this, SLOT(formatChanged(int)));
00358     connect(m_delimiterWidget, SIGNAL(delimiterChanged(const QString&)),
00359       this, SLOT(delimiterChanged(const QString&)));
00360     connect(m_startAtLineSpinBox, SIGNAL(valueChanged ( int )),
00361       this, SLOT(startlineSelected(int)));
00362     connect(m_comboQuote, SIGNAL(activated(int)),
00363       this, SLOT(textquoteSelected(int)));
00364     connect(m_table, SIGNAL(currentChanged(int, int)),
00365       this, SLOT(currentCellChanged(int, int)));
00366     connect(m_table, SIGNAL(valueChanged(int,int)),
00367       this, SLOT(cellValueChanged(int,int)));
00368     connect(m_ignoreDuplicates, SIGNAL(stateChanged(int)),
00369       this, SLOT(ignoreDuplicatesChanged(int)));
00370     connect(m_1stRowForFieldNames, SIGNAL(stateChanged(int)),
00371       this, SLOT(slot1stRowForFieldNamesChanged(int)));
00372 
00373     connect(this, SIGNAL(user1Clicked()), this, SLOT(optionsButtonClicked()));
00374 
00375     installRecursiveEventFilter(this, this);
00376 
00377     initLater();
00378 }
00379 
00380 KexiCSVImportDialog::~KexiCSVImportDialog()
00381 {
00382     delete m_file;
00383 }
00384 
00385 void KexiCSVImportDialog::initLater()
00386 {
00387     if (!openData())
00388         return;
00389 
00390     QChar detectedDelimiter;
00391     if (m_mode==File) { //only detect for File mode
00392         // try to detect delimiter
00393         // \t has priority, then , then ;
00394         for (uint i=0; i < QMIN(4096, m_data.length()); i++) {
00395             const QChar c(m_data[i]);
00396             if (c=='\t') {
00397                 detectedDelimiter = c;
00398                 break;
00399             }
00400             else if (c==',' && detectedDelimiter!='\t') {
00401                 detectedDelimiter = c;
00402             }
00403             else if (c==';' && detectedDelimiter!='\t' && detectedDelimiter!=',') {
00404                 detectedDelimiter = c;
00405             }
00406         }
00407     }
00408     if (detectedDelimiter.isNull())
00409         detectedDelimiter = m_mode==File 
00410             ? KEXICSV_DEFAULT_FILE_DELIMITER[0] : KEXICSV_DEFAULT_CLIPBOARD_DELIMITER[0]; //<-- defaults
00411 
00412     m_delimiterWidget->setDelimiter(QString(detectedDelimiter));
00413 //  delimiterChanged(detectedDelimiter); // this will cause fillTable()
00414     m_columnsAdjusted = false;
00415     fillTable();
00416     delete m_loadingProgressDlg;
00417     m_loadingProgressDlg = 0;
00418     if (m_dialogCancelled) {
00419 //      m_loadingProgressDlg->hide();
00420     //  m_loadingProgressDlg->close();
00421         QTimer::singleShot(0, this, SLOT(reject()));
00422         return;
00423     }
00424 
00425     currentCellChanged(0, 0);
00426 
00427 //  updateGeometry();
00428     adjustSize();
00429     KDialog::centerOnScreen( this ); 
00430 
00431     if (m_loadingProgressDlg)
00432         m_loadingProgressDlg->hide();
00433     show();
00434     m_table->setFocus();
00435 }
00436 
00437 bool KexiCSVImportDialog::openData()
00438 {
00439     if (m_mode!=File) //data already loaded, no encoding stuff needed
00440         return true;
00441 
00442     delete m_inputStream;
00443     m_inputStream = 0;
00444     if (m_file) {
00445         m_file->close();
00446         delete m_file;
00447     }
00448     m_file = new QFile(m_fname);
00449     if (!m_file->open(IO_ReadOnly))
00450     {
00451         m_file->close();
00452         delete m_file;
00453         m_file = 0;
00454         KMessageBox::sorry( this, i18n("Cannot open input file <nobr>\"%1\"</nobr>.")
00455             .arg(QDir::convertSeparators(m_fname)) );
00456         actionButton( Ok )->setEnabled( false );
00457         m_cancelled = true;
00458         if (parentWidget())
00459             parentWidget()->raise();
00460         return false;
00461     }
00462     return true;
00463 }
00464 
00465 bool KexiCSVImportDialog::cancelled() const
00466 {
00467     return m_cancelled;
00468 }
00469 
00470 void KexiCSVImportDialog::fillTable()
00471 {
00472     KexiUtils::WaitCursor wc(true);
00473     repaint();
00474     m_blockUserEvents = true;
00475     QPushButton *pb = actionButton(KDialogBase::Cancel);
00476     if (pb)
00477         pb->setEnabled(true); //allow to cancel
00478     KexiUtils::WaitCursor wait;
00479 
00480     if (m_table->numRows()>0) //to accept editor
00481         m_table->setCurrentCell(0,0);
00482 
00483     int row, column, maxColumn;
00484     QString field = QString::null;
00485 
00486     for (row = 0; row < m_table->numRows(); ++row)
00487         for (column = 0; column < m_table->numCols(); ++column)
00488             m_table->clearCell(row, column);
00489 
00490     m_detectedTypes.clear();
00491     m_detectedTypes.resize(1024, _NO_TYPE_YET);//_TEXT_TYPE);
00492     m_uniquenessTest.clear();
00493     m_uniquenessTest.resize(1024);
00494     m_1stRowForFieldNamesDetected = true;
00495 
00496     if (true != loadRows(field, row, column, maxColumn, true))
00497         return;
00498 
00499     m_1stRowForFieldNamesDetected = false;
00500 
00501     // file with only one line without '\n'
00502     if (field.length() > 0)
00503     {
00504         setText(row - m_startline, column, field, true);
00505         ++row;
00506         field = QString::null;
00507     }
00508 
00509     adjustRows( row - m_startline - (m_1stRowForFieldNames->isChecked()?1:0) );
00510 
00511     maxColumn = QMAX( maxColumn, column );
00512     m_table->setNumCols(maxColumn);
00513 
00514     for (column = 0; column < m_table->numCols(); ++column)
00515     {
00516 //      QString header = m_table->horizontalHeader()->label(column);
00517 //      if (header != i18n("Text") && header != i18n("Number") &&
00518 //          header != i18n("Date") && header != i18n("Currency"))
00519 //      const int detectedType = m_detectedTypes[column+1];
00520 //      m_table->horizontalHeader()->setLabel(column, m_typeNames[ detectedType ]); //i18n("Text"));
00521         updateColumnText(column);
00522         if (!m_columnsAdjusted)
00523             m_table->adjustColumn(column);
00524     }
00525     m_columnsAdjusted = true;
00526 
00527     if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) {
00528         if (_NUMBER_TYPE != m_detectedTypes[ m_primaryKeyColumn ]) {
00529             m_primaryKeyColumn = -1;
00530         }
00531     }
00532 
00533     m_prevSelectedCol = -1;
00534     m_table->setCurrentCell(0,0);
00535     currentCellChanged(0, 0);
00536     if (m_primaryKeyColumn != -1)
00537         m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon);
00538 
00539     const int count = QMAX(0, m_table->numRows()-1+m_startline);
00540     const bool allRowsLoaded = count < m_maximumRowsForPreview;
00541     if (allRowsLoaded) {
00542         m_startAtLineSpinBox->setMaxValue(count);
00543         m_startAtLineSpinBox->setValue(m_startline+1);
00544     }
00545     m_startAtLineLabel->setText(i18n( "Start at line%1:").arg(
00546             allRowsLoaded ? QString(" (1-%1)").arg(count)
00547             : QString::null //we do not know what's real count
00548     ));
00549 
00550     m_blockUserEvents = false;
00551     repaint();
00552     m_table->verticalScrollBar()->repaint();//avoid missing repaint
00553     m_table->horizontalScrollBar()->repaint();//avoid missing repaint
00554 }
00555 
00556 tristate KexiCSVImportDialog::loadRows(QString &field, int &row, int &column, int &maxColumn, 
00557     bool inGUI)
00558 {
00559     enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
00560          S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;
00561     const QChar delimiter(m_delimiterWidget->delimiter()[0]);
00562     field = QString::null;
00563     const bool ignoreDups = m_ignoreDuplicates->isChecked();
00564     bool lastCharDelimiter = false;
00565     bool nextRow = false;
00566     row = column = 1;
00567     maxColumn = 0;
00568     QChar x;
00569     delete m_inputStream;
00570     if ( m_mode == Clipboard ) {
00571         m_inputStream = new QTextStream(m_data, IO_ReadOnly);
00572     }
00573     else {
00574         m_file->at(0); //always seek at 0 because loadRows() is called many times
00575         m_inputStream = new QTextStream(m_file);
00576         if (m_encoding != QString::fromLatin1(KGlobal::locale()->encoding())) {
00577             QTextCodec *codec = KGlobal::charsets()->codecForName(m_encoding);
00578             if (codec)
00579                 m_inputStream->setCodec(codec); //QTextCodec::codecForName("CP1250"));
00580         }
00581     }
00582     int progressStep = 0;
00583     if (m_importingProgressDlg)
00584         progressStep = QMAX( 1, m_importingProgressDlg->progressBar()->totalSteps()/200 );
00585     int offset = 0;
00586     for (;!m_inputStream->atEnd(); offset++)
00587     {
00588         if (column >= m_maximumRowsForPreview)
00589             return true;
00590 
00591         if (m_importingProgressDlg && ((offset % progressStep) < 5)) {
00592             //update progr. bar dlg on final exporting
00593             m_importingProgressDlg->progressBar()->setValue(offset);
00594             qApp->processEvents();
00595             if (m_importingProgressDlg->wasCancelled()) {
00596                 delete m_importingProgressDlg;
00597                 m_importingProgressDlg = 0;
00598                 return ::cancelled;
00599             }
00600         }
00601 
00602         (*m_inputStream) >> x; // read one char
00603 
00604         if (x == '\r') {
00605             continue; // eat '\r', to handle RFC-compliant files
00606         }
00607 
00608         switch (state)
00609         {
00610         case S_START :
00611             if (x == m_textquote)
00612             {
00613                 state = S_QUOTED_FIELD;
00614             }
00615             else if (x == delimiter)
00616             {
00617                 setText(row - m_startline, column, field, inGUI);
00618                 field = QString::null;
00619                 if ((ignoreDups == false) || (lastCharDelimiter == false))
00620                     ++column;
00621                 lastCharDelimiter = true;
00622             }
00623             else if (x == '\n')
00624             {
00625                 if (!inGUI) {
00626                     //fill remaining empty fields (database wants them explicity)
00627                     for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) {
00628                         setText(row - m_startline, additionalColumn, QString::null, inGUI);
00629                     }
00630                 }
00631                 nextRow = true;
00632                 maxColumn = QMAX( maxColumn, column );
00633                 column = 1;
00634             }
00635             else
00636             {
00637                 field += x;
00638                 state = S_MAYBE_NORMAL_FIELD;
00639             }
00640             break;
00641         case S_QUOTED_FIELD :
00642             if (x == m_textquote)
00643             {
00644                 state = S_MAYBE_END_OF_QUOTED_FIELD;
00645             }
00646 /*allow \n inside quoted fields
00647             else if (x == '\n')
00648             {
00649                 setText(row - m_startline, column, field, inGUI);
00650                 field = "";
00651                 if (x == '\n')
00652                 {
00653                     nextRow = true;
00654                     maxColumn = QMAX( maxColumn, column );
00655                     column = 1;
00656                 }
00657                 else
00658                 {
00659                     if ((ignoreDups == false) || (lastCharDelimiter == false))
00660                         ++column;
00661                     lastCharDelimiter = true;
00662                 }
00663                 state = S_START;
00664             }*/
00665             else
00666             {
00667                 field += x;
00668             }
00669             break;
00670         case S_MAYBE_END_OF_QUOTED_FIELD :
00671             if (x == m_textquote)
00672             {
00673                 field += x; //no, this was just escaped quote character
00674                 state = S_QUOTED_FIELD;
00675             }
00676             else if (x == delimiter || x == '\n')
00677             {
00678                 setText(row - m_startline, column, field, inGUI);
00679                 field = QString::null;
00680                 if (x == '\n')
00681                 {
00682                     nextRow = true;
00683                     maxColumn = QMAX( maxColumn, column );
00684                     column = 1;
00685                 }
00686                 else
00687                 {
00688                     if ((ignoreDups == false) || (lastCharDelimiter == false))
00689                         ++column;
00690                     lastCharDelimiter = true;
00691                 }
00692                 state = S_START;
00693             }
00694             else
00695             {
00696                 state = S_END_OF_QUOTED_FIELD;
00697             }
00698             break;
00699         case S_END_OF_QUOTED_FIELD :
00700             if (x == delimiter || x == '\n')
00701             {
00702                 setText(row - m_startline, column, field, inGUI);
00703                 field = QString::null;
00704                 if (x == '\n')
00705                 {
00706                     nextRow = true;
00707                     maxColumn = QMAX( maxColumn, column );
00708                     column = 1;
00709                 }
00710                 else
00711                 {
00712                     if ((ignoreDups == false) || (lastCharDelimiter == false))
00713                         ++column;
00714                     lastCharDelimiter = true;
00715                 }
00716                 state = S_START;
00717             }
00718             else
00719             {
00720                 state = S_END_OF_QUOTED_FIELD;
00721             }
00722             break;
00723         case S_MAYBE_NORMAL_FIELD :
00724             if (x == m_textquote)
00725             {
00726                 field = QString::null;
00727                 state = S_QUOTED_FIELD;
00728                 break;
00729             }
00730         case S_NORMAL_FIELD :
00731             if (x == delimiter || x == '\n')
00732             {
00733                 setText(row - m_startline, column, field, inGUI);
00734                 field = QString::null;
00735                 if (x == '\n')
00736                 {
00737                     nextRow = true;
00738                     maxColumn = QMAX( maxColumn, column );
00739                     column = 1;
00740                 }
00741                 else
00742                 {
00743                     if ((ignoreDups == false) || (lastCharDelimiter == false))
00744                         ++column;
00745                     lastCharDelimiter = true;
00746                 }
00747                 state = S_START;
00748             }
00749             else
00750             {
00751                 field += x;
00752             }
00753         }
00754         if (x != delimiter)
00755             lastCharDelimiter = false;
00756 
00757         if (nextRow) {
00758             nextRow = false;
00759             if (!inGUI && row==1 && m_1stRowForFieldNames->isChecked()) {
00760                 // do not save to the database 1st row if it contains column names
00761                 m_importingStatement->clearArguments();
00762             }
00763             else if (!saveRow(inGUI))
00764                 return false;
00765             ++row;
00766         }
00767 
00768         if (m_firstFillTableCall && row==2 
00769             && !m_1stRowForFieldNames->isChecked() && m_1stRowForFieldNamesDetected) 
00770         {
00771             //'1st row for field name' flag detected: reload table
00772             m_1stRowForFieldNamesDetected = false;
00773             m_table->setNumRows( 0 );
00774             m_firstFillTableCall = false; //this trick is allowed only once, on startup
00775             m_1stRowForFieldNames->setChecked(true); //this will reload table
00776             //slot1stRowForFieldNamesChanged(1);
00777             m_blockUserEvents = false;
00778             repaint();
00779             return false;
00780         }
00781 
00782         if (!m_importingProgressDlg && row % 20 == 0) {
00783             qApp->processEvents();
00784             //only for GUI mode:
00785             if (!m_firstFillTableCall && m_loadingProgressDlg && m_loadingProgressDlg->wasCancelled()) {
00786                 delete m_loadingProgressDlg;
00787                 m_loadingProgressDlg = 0;
00788                 m_dialogCancelled = true;
00789                 reject();
00790                 return false;
00791             }
00792         }
00793 
00794         if (!m_firstFillTableCall && m_loadingProgressDlg) {
00795             m_loadingProgressDlg->progressBar()->setValue(QMIN(m_maximumRowsForPreview, row));
00796         }
00797 
00798         if ( inGUI && row > (m_maximumRowsForPreview + (m_1stRowForFieldNamesDetected?1:0)) ) {
00799             kexipluginsdbg << "KexiCSVImportDialog::fillTable() loading stopped at row #" 
00800                 << m_maximumRowsForPreview << endl;
00801             break;
00802         }
00803     }
00804     return true;
00805 }
00806 
00807 void KexiCSVImportDialog::updateColumnText(int col)
00808 {
00809     QString colName;
00810     if (col<(int)m_columnNames.count() && (m_1stRowForFieldNames->isChecked() || m_changedColumnNames[col]))
00811         colName = m_columnNames[ col ];
00812     if (colName.isEmpty()) {
00813         colName = i18n("Column %1").arg(col+1); //will be changed to a valid identifier on import
00814         m_changedColumnNames[ col ] = false;
00815     }
00816     int detectedType = m_detectedTypes[col];
00817     if (detectedType==_FP_NUMBER_TYPE)
00818         detectedType=_NUMBER_TYPE; //we're simplifying that for now
00819     else if (detectedType==_NO_TYPE_YET) {
00820         m_detectedTypes[col]=_TEXT_TYPE; //entirely empty column
00821         detectedType=_TEXT_TYPE;
00822     }
00823     m_table->horizontalHeader()->setLabel(col, 
00824         i18n("Column %1").arg(col+1) + "  \n(" + m_typeNames[ detectedType ] + ")  ");
00825     m_table->setText(0, col, colName);
00826     m_table->horizontalHeader()->adjustHeaderSize();
00827 
00828     //check uniqueness
00829     QValueList<int> *list = m_uniquenessTest[col];
00830     if (m_primaryKeyColumn==-1 && list && !list->isEmpty()) {
00831         qHeapSort(*list);
00832         QValueList<int>::ConstIterator it=list->constBegin();
00833         int prevValue = *it;
00834         ++it;
00835         for(; it!=list->constEnd() && prevValue!=(*it); ++it)
00836             prevValue=(*it);
00837         if (it!=list->constEnd()) {
00838             //duplicates:
00839             list->clear();
00840         }
00841         else {
00842             //a candidate for PK (autodetected)!
00843             if (-1==m_primaryKeyColumn) {
00844                 m_primaryKeyColumn=col;
00845             }
00846         }
00847     }
00848     if (list) //not needed now: conserve memory
00849         list->clear();
00850 }
00851 
00852 void KexiCSVImportDialog::detectTypeAndUniqueness(int row, int col, const QString& text)
00853 {
00854     int intValue;
00855     const int type = m_detectedTypes[col];
00856     if (row==1 || type!=_TEXT_TYPE) {
00857         bool found = false;
00858         if (text.isEmpty() && type==_NO_TYPE_YET)
00859             found = true; //real type should be found later
00860         //detect type because it's 1st row or all prev. rows were not text
00861         //-number?
00862         if (!found && (row==1 || type==_NUMBER_TYPE || type==_NO_TYPE_YET)) {
00863             bool ok = text.isEmpty();//empty values allowed
00864             if (!ok)
00865                 intValue = text.toInt(&ok);
00866             if (ok && (row==1 || type==_NO_TYPE_YET)) {
00867                 m_detectedTypes[col]=_NUMBER_TYPE;
00868                 found = true; //yes
00869             }
00870         }
00871         //-FP number?
00872         if (!found && (row==1 || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) {
00873             bool ok = text.isEmpty() || m_fpNumberRegExp.exactMatch(text);
00874             if (!ok)
00875                 text.toInt(&ok);
00876             if (ok && (row==1 || type==_NO_TYPE_YET)) {
00877                 m_detectedTypes[col]=_FP_NUMBER_TYPE;
00878                 found = true; //yes
00879             }
00880         }
00881         //-date?
00882         if (!found && (row==1 || type==_DATE_TYPE || type==_NO_TYPE_YET)) {
00883             if ((row==1 || type==_NO_TYPE_YET)
00884                 && (text.isEmpty() || m_dateRegExp1.exactMatch(text) || m_dateRegExp2.exactMatch(text)))
00885             {
00886                 m_detectedTypes[col]=_DATE_TYPE;
00887                 found = true; //yes
00888             }
00889         }
00890         //-time?
00891         if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) {
00892             if ((row==1 || type==_NO_TYPE_YET)
00893                 && (text.isEmpty() || m_timeRegExp1.exactMatch(text) || m_timeRegExp2.exactMatch(text)))
00894             {
00895                 m_detectedTypes[col]=_TIME_TYPE;
00896                 found = true; //yes
00897             }
00898         }
00899         //-date/time?
00900         if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) {
00901             if (row==1 || type==_NO_TYPE_YET) {
00902                 bool detected = text.isEmpty();
00903                 if (!detected) {
00904                     const QStringList dateTimeList( QStringList::split(" ", text) );
00905                     bool ok = dateTimeList.count()>=2;
00908                     if (ok) {
00909                         //try all combinations
00910                         QString datePart( dateTimeList[0].stripWhiteSpace() );
00911                         QString timePart( dateTimeList[1].stripWhiteSpace() );
00912                         ok = (m_dateRegExp1.exactMatch(datePart) || m_dateRegExp2.exactMatch(datePart))
00913                             && (m_timeRegExp1.exactMatch(timePart) || m_timeRegExp2.exactMatch(timePart));
00914                     }
00915                     detected = ok;
00916                 }
00917                 if (detected) {
00918                     m_detectedTypes[col]=_DATETIME_TYPE;
00919                     found = true; //yes
00920                 }
00921             }
00922         }
00923         if (!found && type==_NO_TYPE_YET && !text.isEmpty()) {
00924             //eventually, a non-emptytext after a while
00925             m_detectedTypes[col]=_TEXT_TYPE;
00926             found = true; //yes
00927         }
00928         //default: text type (already set)
00929     }
00930     //check uniqueness for this value
00931     QValueList<int> *list = m_uniquenessTest[col];
00932     if (row==1 && (!list || !list->isEmpty()) && !text.isEmpty() && _NUMBER_TYPE == m_detectedTypes[col]) {
00933         if (!list) {
00934             list = new QValueList<int>();
00935             m_uniquenessTest.insert(col, list);
00936         }
00937         list->append( intValue );
00938     }
00939     else {
00940         //the value is empty or uniqueness test failed in the past
00941         if (list && !list->isEmpty())
00942             list->clear(); //indicate that uniqueness test failed
00943     }
00944 }
00945 
00946 void KexiCSVImportDialog::setText(int row, int col, const QString& text, bool inGUI)
00947 {
00948     if (!inGUI) {
00949         //save text directly to database buffer
00950         if (col==1) { //1st col
00951             m_importingStatement->clearArguments();
00952             if (m_implicitPrimaryKeyAdded)
00953                 *m_importingStatement << QVariant(); //id will be autogenerated here
00954         }
00955         const int detectedType = m_detectedTypes[col-1];
00956         if (detectedType==_NUMBER_TYPE) {
00957             *m_importingStatement << ( text.isEmpty() ? QVariant() : text.toInt() );
00959         }
00960         else if (detectedType==_FP_NUMBER_TYPE) {
00961             *m_importingStatement << ( text.isEmpty() ? QVariant() : text.toDouble() );
00962         }
00963         else if (detectedType==_DATE_TYPE) {
00964             QDate date( QDate::fromString(text, Qt::ISODate) ); //same as m_dateRegExp1
00965             if (!date.isValid() && m_dateRegExp2.exactMatch(text)) //dd-mm-yyyy
00966                 date = QDate(text.mid(6,4).toInt(), text.mid(3,2).toInt(), text.left(2).toInt());
00967             *m_importingStatement << date;
00968         }
00969         else if (detectedType==_TIME_TYPE) {
00970             QTime time( QTime::fromString(text, Qt::ISODate) ); //same as m_timeRegExp1
00971             if (!time.isValid() && m_timeRegExp2.exactMatch(text)) //hh:mm:ss
00972                 time = QTime(text.left(2).toInt(), text.mid(3,2).toInt(), text.mid(6,2).toInt());
00973             *m_importingStatement << time;
00974         }
00975         else if (detectedType==_DATETIME_TYPE) {
00976             const QStringList dateTimeList( QStringList::split(" ", text) );
00979             if (dateTimeList.count()>=2) {
00980                 //try all combinations
00981                 QString datePart( dateTimeList[0].stripWhiteSpace() );
00982                 QString timePart( dateTimeList[1].stripWhiteSpace() );
00983                 QDateTime dateTime;
00984                 dateTime.setDate( QDate::fromString(datePart, Qt::ISODate) ); //same as m_dateRegExp1
00985                 if (!dateTime.date().isValid() && m_dateRegExp2.exactMatch(datePart)) //dd-mm-yyyy
00986                     dateTime.setDate( QDate(datePart.mid(6,4).toInt(), 
00987                         datePart.mid(3,2).toInt(), datePart.left(2).toInt()) );
00988                 dateTime.setTime( QTime::fromString(timePart, Qt::ISODate) ); //same as m_timeRegExp1
00989                 if (!dateTime.time().isValid() && m_timeRegExp2.exactMatch(timePart)) //hh:mm:ss
00990                     dateTime.setTime( QTime(timePart.left(2).toInt(), 
00991                         timePart.mid(3,2).toInt(), timePart.mid(6,2).toInt()) );
00992                 *m_importingStatement << dateTime;
00993             }
00994         }
00995         else //_TEXT_TYPE and the rest
00996             *m_importingStatement << text;
00997         return;
00998     }
00999     //save text to GUI (table view)
01000     if (m_table->numCols() < col) {
01001         m_table->setNumCols(col);
01002         if ((int)m_columnNames.size() < m_table->numCols()) {
01003             m_columnNames.resize(m_table->numCols()+10);
01004             m_changedColumnNames.resize(m_table->numCols()+10);
01005         }
01006     }
01007 
01008     if (m_1stRowForFieldNames->isChecked()) {
01009         if ((row+m_startline)==1) {//this is for column name
01010             if ((col-1) < (int)m_changedColumnNames.size() && false==m_changedColumnNames[col-1]) {
01011                 //this column has no custom name entered by a user
01012                 //-get the name from the data cell
01013                 QString colName(text.simplifyWhiteSpace());
01014                 if (!colName.isEmpty()) {
01015                     if (colName.left(1)>="0" && colName.left(1)<="9")
01016                         colName.prepend(i18n("Column")+" ");
01017                     m_columnNames[ col-1 ] = colName;
01018                 }
01019             }
01020             return;
01021         }
01022     }
01023     else {
01024         if ((row+m_startline)==1) {//this row is for column name
01025             if (m_1stRowForFieldNamesDetected && !m_1stRowForFieldNames->isChecked()) {
01026                 QString f( text.simplifyWhiteSpace() );
01027                 if (f.isEmpty() || !f[0].isLetter())
01028                     m_1stRowForFieldNamesDetected = false; //this couldn't be a column name
01029             }
01030         }
01031         row++; //1st row was for column names
01032     }
01033 
01034     if (row < 2) // skipped by the user
01035         return;
01036 
01037     if (m_table->numRows() < row) {
01038 //      if (m_maximumRowsForPreview >= row+100)
01039         m_table->setNumRows(row+100); /* We add more rows at a time to limit recalculations */
01040         //else
01041 //          m_table->setNumRows(m_maximumRowsForPreview);
01042         m_table->verticalHeader()->setLabel(0, i18n("Column name")+"   ");
01043         m_adjustRows=1;
01044     }
01045 
01046     m_table->setText(row - 1, col - 1, text);
01047     m_table->verticalHeader()->setLabel(row-1, QString::number(row-1));
01048 
01049     detectTypeAndUniqueness(row-1, col-1, text);
01050 }
01051 
01052 bool KexiCSVImportDialog::saveRow(bool inGUI)
01053 {
01054     if (inGUI) {
01055         //nothing to do
01056         return true;
01057     }
01058     //save db buffer
01059     bool res = m_importingStatement->execute();
01060 //todo: move
01061     m_importingStatement->clearArguments();
01062     return res;
01063 //  return m_conn->insertRecord(*m_destinationTableSchema, m_dbRowBuffer);
01064 }
01065 
01066 void KexiCSVImportDialog::adjustRows(int iRows)
01067 {
01068     if (m_adjustRows)
01069     {
01070         m_table->setNumRows( iRows );
01071         m_adjustRows=0;
01072         for (int i = 0; i<iRows; i++)
01073             m_table->adjustRow(i);
01074     }
01075 }
01076 
01077 void KexiCSVImportDialog::formatChanged(int id)
01078 {
01079     if (id==_PK_FLAG) {
01080         if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) {
01081             m_table->setPixmap(0, m_primaryKeyColumn, QPixmap());
01082         }
01083         if (m_primaryKeyField->isChecked()) {
01084             m_primaryKeyColumn = m_table->currentColumn();
01085             m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon);
01086         }
01087         else
01088             m_primaryKeyColumn = -1;
01089         return;
01090     }
01091     else {
01092         m_detectedTypes[m_table->currentColumn()]=id;
01093         m_primaryKeyField->setEnabled( _NUMBER_TYPE == id );
01094         m_primaryKeyField->setChecked( m_primaryKeyColumn == m_table->currentColumn() && m_primaryKeyField->isEnabled() );
01095     }
01096     updateColumnText(m_table->currentColumn());
01097 }
01098 
01099 void KexiCSVImportDialog::delimiterChanged(const QString& delimiter)
01100 {
01101     Q_UNUSED(delimiter);
01102     m_columnsAdjusted = false;
01103     //delayed, otherwise combobox won't be repainted
01104     QTimer::singleShot(10, this, SLOT(fillTable()));
01105 }
01106 
01107 void KexiCSVImportDialog::textquoteSelected(int)
01108 {
01109     const QString tq(m_comboQuote->textQuote());
01110     if (tq.isEmpty())
01111         m_textquote = 0;
01112     else
01113         m_textquote = tq[0];
01114 
01115     //delayed, otherwise combobox won't be repainted
01116     QTimer::singleShot(10, this, SLOT(fillTable()));
01117 }
01118 
01119 void KexiCSVImportDialog::startlineSelected(int startline)
01120 {
01121 //  const int startline = line.toInt() - 1;
01122     if (m_startline == (startline-1))
01123         return;
01124     m_startline = startline-1;
01125     m_adjustRows=1;
01126     fillTable();
01127     m_table->setFocus();
01128 }
01129 
01130 void KexiCSVImportDialog::currentCellChanged(int, int col)
01131 {
01132     if (m_prevSelectedCol==col)
01133         return;
01134     m_prevSelectedCol = col;
01135     int type = m_detectedTypes[col];
01136     if (type==_FP_NUMBER_TYPE)
01137         type=_NUMBER_TYPE; //we're simplifying that for now
01138 
01139     m_formatCombo->setCurrentItem( type );
01140     m_formatLabel->setText( m_formatComboText.arg(col+1) );
01141     m_primaryKeyField->setEnabled( _NUMBER_TYPE == m_detectedTypes[col]);
01142     m_primaryKeyField->blockSignals(true); //block to disable executing slotPrimaryKeyFieldToggled()
01143      m_primaryKeyField->setChecked( m_primaryKeyColumn == col );
01144     m_primaryKeyField->blockSignals(false);
01145 }
01146 
01147 void KexiCSVImportDialog::cellValueChanged(int row,int col)
01148 {
01149     if (row==0) {//column name has changed
01150         m_columnNames[ col ] = m_table->text(row, col);
01151         m_changedColumnNames.setBit( col );
01152     }
01153 }
01154 
01155 void KexiCSVImportDialog::accept()
01156 {
01158 
01159     KexiGUIMessageHandler msg; 
01160 
01161     const uint numRows( m_table->numRows() );
01162     if (numRows == 0)
01163         return; //impossible
01164 
01165     if (numRows == 1) {
01166         if (KMessageBox::No == KMessageBox::questionYesNo(this, 
01167             i18n("Data set contains no rows. Do you want to import empty table?")))
01168             return;
01169     }
01170 
01171     KexiProject* project = m_mainWin->project();
01172     if (!project) {
01173         msg.showErrorMessage(i18n("No project available."));
01174         return;
01175     }
01176     m_conn = project->dbConnection(); //cache this pointer
01177     if (!m_conn) {
01178         msg.showErrorMessage(i18n("No database connection available."));
01179         return;
01180     }
01181     KexiPart::Part *part = Kexi::partManager().partForMimeType("kexi/table");
01182     if (!part) {
01183         msg.showErrorMessage(&Kexi::partManager());
01184         return;
01185     }
01186 
01187     //get suggested name based on the file name
01188     QString suggestedName;
01189     if (m_mode==File) {
01190         suggestedName = KURL::fromPathOrURL(m_fname).fileName();
01191         //remove extension
01192         if (!suggestedName.isEmpty()) {
01193             const int idx = suggestedName.findRev(".");
01194             if (idx!=-1)
01195                 suggestedName = suggestedName.mid(0, idx ).simplifyWhiteSpace();
01196         }
01197     }
01198 
01199     //-new part item
01200     KexiPart::Item* partItemForSavedTable = project->createPartItem(part->info(), suggestedName);
01201     if (!partItemForSavedTable) {
01202     //      msg.showErrorMessage(project);
01203         return;
01204     }
01205 
01206 #define _ERR \
01207     { project->deleteUnstoredItem(partItemForSavedTable); \
01208       m_conn = 0; \
01209       delete m_destinationTableSchema; \
01210       m_destinationTableSchema = 0; \
01211     return; }
01212 
01213     //-ask for table name/title
01214     // (THIS IS FROM KexiMainWindowImpl::saveObject())
01215     bool allowOverwriting = true;
01216     tristate res = m_mainWin->getNewObjectInfo( partItemForSavedTable, part, allowOverwriting );
01217     if (~res || !res) {
01219         _ERR;
01220     }
01221     //(allowOverwriting is now set to true, if user accepts overwriting, 
01222     // and overwriting will be needed)
01223 
01224 //  KexiDB::SchemaData sdata(part->info()->projectPartID());
01225 //  sdata.setName( partItem->name() );
01226 
01227     //-create table schema (and thus schema object)
01228     //-assign information (THIS IS FROM KexiDialogBase::storeNewData())
01229     m_destinationTableSchema = new KexiDB::TableSchema(partItemForSavedTable->name());
01230     m_destinationTableSchema->setCaption( partItemForSavedTable->caption() );
01231     m_destinationTableSchema->setDescription( partItemForSavedTable->description() );
01232     const uint numCols( m_table->numCols() );
01233 
01234     m_implicitPrimaryKeyAdded = false;
01235     //add PK if user wanted it
01236     int msgboxResult;
01237     if (m_primaryKeyColumn==-1
01238         && KMessageBox::No != (msgboxResult = KMessageBox::questionYesNoCancel(this, 
01239             i18n("No Primary Key (autonumber) has been defined.\n"
01240             "Should it be automatically defined on import (recommended)?\n\n"
01241             "Note: An imported table without a Primary Key may not be editable (depending on database type)."),
01242             QString::null, KGuiItem(i18n("Add Database Primary Key to a Table", "Add Primary Key"), "key"),
01243             KGuiItem(i18n("Do Not Add Database Primary Key to a Table", "Do Not Add")))))
01244     {
01245         if (msgboxResult == KMessageBox::Cancel)
01246             _ERR; //cancel accepting
01247 
01248         //add implicit PK field
01250         m_implicitPrimaryKeyAdded = true;
01251 
01252         QString fieldName("id");
01253         QString fieldCaption("Id");
01254 
01255         QStringList colnames;
01256         for (uint col = 0; col < numCols; col++)
01257             colnames.append( m_table->text(0, col).lower().simplifyWhiteSpace() );
01258 
01259         if (colnames.find(fieldName)!=colnames.end()) {
01260             int num = 1;
01261             while (colnames.find(fieldName+QString::number(num))!=colnames.end())
01262                 num++;
01263             fieldName += QString::number(num);
01264             fieldCaption += QString::number(num);
01265         }
01266         KexiDB::Field *field = new KexiDB::Field(
01267             fieldName,
01268             KexiDB::Field::Integer,
01269             KexiDB::Field::NoConstraints,
01270             KexiDB::Field::NoOptions,
01271             0,0, //uint length=0, uint precision=0,
01272             QVariant(), //QVariant defaultValue=QVariant(),
01273             fieldCaption
01274         ); //no description and width for now
01275         field->setPrimaryKey(true);
01276         field->setAutoIncrement(true);
01277         m_destinationTableSchema->addField( field );
01278     }
01279 
01280     for (uint col = 0; col < numCols; col++) {
01281         QString fieldCaption( m_table->text(0, col).simplifyWhiteSpace() );
01282         QString fieldName( KexiUtils::string2Identifier( fieldCaption ) );
01283         if (m_destinationTableSchema->field(fieldName)) {
01284             QString fixedFieldName;
01285             uint i = 2; //"apple 2, apple 3, etc. if there're many "apple" names
01286             do {
01287                 fixedFieldName = fieldName + "_" + QString::number(i);
01288                 if (!m_destinationTableSchema->field(fixedFieldName))
01289                     break;
01290                 i++;
01291             } while (true);
01292             fieldName = fixedFieldName;
01293             fieldCaption += (" " + QString::number(i));
01294         }
01295         const int detectedType = m_detectedTypes[col];
01296         KexiDB::Field::Type fieldType;
01297         if (detectedType==_DATE_TYPE)
01298             fieldType = KexiDB::Field::Date;
01299         if (detectedType==_TIME_TYPE)
01300             fieldType = KexiDB::Field::Time;
01301         if (detectedType==_DATETIME_TYPE)
01302             fieldType = KexiDB::Field::DateTime;
01303         else if (detectedType==_NUMBER_TYPE)
01304             fieldType = KexiDB::Field::Integer;
01305         else if (detectedType==_FP_NUMBER_TYPE)
01306             fieldType = KexiDB::Field::Double;
01308         else //_TEXT_TYPE and the rest
01309             fieldType = KexiDB::Field::Text;
01311 
01312         KexiDB::Field *field = new KexiDB::Field(
01313             fieldName,
01314             fieldType,
01315             KexiDB::Field::NoConstraints,
01316             KexiDB::Field::NoOptions,
01317             0,0, //uint length=0, uint precision=0,
01318             QVariant(), //QVariant defaultValue=QVariant(),
01319             fieldCaption
01320         ); //no description and width for now
01321 
01322         if ((int)col == m_primaryKeyColumn) {
01323             field->setPrimaryKey(true);
01324             field->setAutoIncrement(true);
01325         }
01326         m_destinationTableSchema->addField( field );
01327     }
01328 
01329     KexiDB::Transaction transaction = m_conn->beginTransaction();
01330     if (transaction.isNull()) {
01331         msg.showErrorMessage(m_conn);
01332         _ERR;
01333     }
01334     KexiDB::TransactionGuard tg(transaction);
01335 
01336     //-create physical table
01337     if (!m_conn->createTable(m_destinationTableSchema, allowOverwriting)) {
01338             msg.showErrorMessage(m_conn);
01339         _ERR;
01340     }
01341 
01342 #define _DROP_DEST_TABLE_AND_RETURN \
01343     { \
01344     if (m_importingProgressDlg) \
01345         m_importingProgressDlg->hide(); \
01346     project->deleteUnstoredItem(partItemForSavedTable); \
01347     m_conn->dropTable(m_destinationTableSchema); /*alsoRemoveSchema*/ \
01348     m_destinationTableSchema = 0; \
01349     m_conn = 0; \
01350     return; \
01351     }
01352 
01353     m_importingStatement = m_conn->prepareStatement(
01354         KexiDB::PreparedStatement::InsertStatement, *m_destinationTableSchema);
01355     if (!m_importingStatement) {
01356         msg.showErrorMessage(m_conn);
01357         _DROP_DEST_TABLE_AND_RETURN;
01358     }
01359 
01360     if (m_file) {
01361         if (!m_importingProgressDlg) {
01362             m_importingProgressDlg = new KProgressDialog( this, "m_importingProgressDlg", 
01363                 i18n("Importing CSV Data"), QString::null, true );
01364         }
01365         m_importingProgressDlg->setLabel(
01366             i18n("Importing CSV Data from <nobr>\"%1\"</nobr> into \"%2\" table...")
01367             .arg(QDir::convertSeparators(m_fname)).arg(m_destinationTableSchema->name()) );
01368         m_importingProgressDlg->progressBar()->setTotalSteps( QFileInfo(*m_file).size() );
01369         m_importingProgressDlg->show();
01370     }
01371 
01372     int row, column, maxColumn;
01373     QString field = QString::null;
01374 
01375     // main job
01376     res = loadRows(field, row, column, maxColumn, false  );
01377 
01378     delete m_importingProgressDlg;
01379   m_importingProgressDlg = 0;
01380     if (true != res) {
01381         //importing cancelled or failed
01382         if (!res) //do not display err msg when res == cancelled
01383             msg.showErrorMessage(m_conn);
01384         _DROP_DEST_TABLE_AND_RETURN;
01385     }
01386 
01387     // file with only one line without '\n'
01388     if (field.length() > 0)
01389     {
01390         setText(row - m_startline, column, field, false );
01391         //fill remaining empty fields (database wants them explicity)
01392         for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) {
01393             setText(row - m_startline, additionalColumn, QString::null, false );
01394         }
01395         if (!saveRow(false )) {
01396             msg.showErrorMessage(m_conn);
01397             _DROP_DEST_TABLE_AND_RETURN;
01398         }
01399         ++row;
01400         field = QString::null;
01401     }
01402 
01403     if (!tg.commit()) {
01404         msg.showErrorMessage(m_conn);
01405         _DROP_DEST_TABLE_AND_RETURN;
01406     }
01407 
01408     //-now we can store the item
01409     partItemForSavedTable->setIdentifier( m_destinationTableSchema->id() );
01410     project->addStoredItem( part->info(), partItemForSavedTable );
01411 
01412     QDialog::accept();
01413     KMessageBox::information(this, i18n("Data has been successfully imported to table \"%1\".")
01414         .arg(m_destinationTableSchema->name()));
01415     parentWidget()->raise();
01416     m_conn = 0;
01417 }
01418 
01419 int KexiCSVImportDialog::getHeader(int col)
01420 {
01421     QString header = m_table->horizontalHeader()->label(col);
01422 
01423     if (header == i18n("Text type for column", "Text"))
01424         return TEXT;
01425     else if (header == i18n("Numeric type for column", "Number"))
01426         return NUMBER;
01427     else if (header == i18n("Currency type for column", "Currency"))
01428         return CURRENCY;
01429     else
01430         return DATE;
01431 }
01432 
01433 QString KexiCSVImportDialog::getText(int row, int col)
01434 {
01435     return m_table->text(row, col);
01436 }
01437 
01438 void KexiCSVImportDialog::ignoreDuplicatesChanged(int)
01439 {
01440     fillTable();
01441 }
01442 
01443 void KexiCSVImportDialog::slot1stRowForFieldNamesChanged(int)
01444 {
01445     m_adjustRows=1;
01446     if (m_1stRowForFieldNames->isChecked() && m_startline>0 && m_startline>=(m_startAtLineSpinBox->maxValue()-1))
01447         m_startline--;
01448     fillTable();
01449 }
01450 
01451 void KexiCSVImportDialog::optionsButtonClicked()
01452 {
01453     KexiCSVImportOptionsDialog dlg(m_encoding, this);
01454     if (QDialog::Accepted != dlg.exec())
01455         return;
01456 
01457     if (m_encoding != dlg.encodingComboBox()->selectedEncoding()) {
01458         m_encoding = dlg.encodingComboBox()->selectedEncoding();
01459         if (!openData())
01460             return;
01461         fillTable();
01462     }
01463 }
01464 
01465 bool KexiCSVImportDialog::eventFilter ( QObject * watched, QEvent * e )
01466 {
01467     QEvent::Type t = e->type();
01468     // temporary disable keyboard and mouse events for time-consuming tasks
01469     if (m_blockUserEvents && (t==QEvent::KeyPress || t==QEvent::KeyRelease 
01470         || t==QEvent::MouseButtonPress || t==QEvent::MouseButtonDblClick
01471         || t==QEvent::Paint ))
01472         return true;
01473 
01474     if (watched == m_startAtLineSpinBox && t==QEvent::KeyPress) {
01475         QKeyEvent *ke = static_cast<QKeyEvent*>(e);
01476         if (ke->key()==Key_Enter || ke->key()==Key_Return) {
01477             m_table->setFocus();
01478             return true;
01479         }
01480     }
01481     return QDialog::eventFilter( watched, e );
01482 }
01483 
01484 void KexiCSVImportDialog::slotPrimaryKeyFieldToggled(bool on)
01485 {
01486     Q_UNUSED(on);
01487     formatChanged(_PK_FLAG);
01488 }
01489 
01490 void KexiCSVImportDialog::updateRowCountInfo()
01491 {
01493     m_infoLbl->setFileName( m_fname );
01494 }
01495 
01496 #include "kexicsvimportdialog.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys