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