~ubuntu-branches/ubuntu/precise/koffice/precise

« back to all changes in this revision

Viewing changes to kexi/plugins/importexport/csv/kexicsvimportdialog.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jonathan Riddell
  • Date: 2006-04-20 21:38:53 UTC
  • mfrom: (1.1.3 upstream)
  • Revision ID: james.westby@ubuntu.com-20060420213853-j5lxluqvymxt2zny
Tags: 1:1.5.0-0ubuntu2
UbuntuĀ uploadĀ 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* This file is part of the KDE project
 
2
   Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
 
3
 
 
4
   This work is based on kspread/dialogs/kspread_dlg_csv.cc
 
5
   and will be merged back with KOffice libraries.
 
6
 
 
7
   Copyright (C) 2002-2003 Norbert Andres <nandres@web.de>
 
8
                         (C) 2002-2003 Ariya Hidayat <ariya@kde.org>
 
9
                         (C) 2002         Laurent Montel <montel@kde.org>
 
10
                         (C) 1999 David Faure <faure@kde.org>
 
11
 
 
12
   This library is free software; you can redistribute it and/or
 
13
   modify it under the terms of the GNU Library General Public
 
14
   License as published by the Free Software Foundation; either
 
15
   version 2 of the License, or (at your option) any later version.
 
16
 
 
17
   This library is distributed in the hope that it will be useful,
 
18
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
19
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
20
   Library General Public License for more details.
 
21
 
 
22
   You should have received a copy of the GNU Library General Public License
 
23
   along with this library; see the file COPYING.LIB.  If not, write to
 
24
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 
25
 * Boston, MA 02110-1301, USA.
 
26
*/
 
27
 
 
28
#include <qbuttongroup.h>
 
29
#include <qcheckbox.h>
 
30
#include <qclipboard.h>
 
31
#include <qlabel.h>
 
32
#include <qlineedit.h>
 
33
#include <qmime.h>
 
34
#include <qpushbutton.h>
 
35
#include <qradiobutton.h>
 
36
#include <qtable.h>
 
37
#include <qlayout.h>
 
38
#include <qfiledialog.h>
 
39
#include <qpainter.h>
 
40
#include <qtextcodec.h>
 
41
#include <qtimer.h>
 
42
#include <qfontmetrics.h>
 
43
 
 
44
#include <kapplication.h>
 
45
#include <kdebug.h>
 
46
#include <kdialogbase.h>
 
47
#include <kfiledialog.h>
 
48
#include <klocale.h>
 
49
#include <kmessagebox.h>
 
50
#include <kglobalsettings.h>
 
51
#include <kiconloader.h>
 
52
#include <kcharsets.h>
 
53
#include <knuminput.h>
 
54
#include <kprogress.h>
 
55
 
 
56
#include <kexiutils/identifier.h>
 
57
#include <kexiutils/utils.h>
 
58
#include <core/kexi.h>
 
59
#include <core/kexiproject.h>
 
60
#include <core/kexipart.h>
 
61
#include <core/kexipartinfo.h>
 
62
#include <core/keximainwindow.h>
 
63
#include <core/kexiguimsghandler.h>
 
64
#include <kexidb/connection.h>
 
65
#include <kexidb/tableschema.h>
 
66
#include <kexidb/transaction.h>
 
67
#include <widget/kexicharencodingcombobox.h>
 
68
 
 
69
#include "kexicsvimportdialog.h"
 
70
#include "kexicsvimportoptionsdlg.h"
 
71
#include "kexicsvwidgets.h"
 
72
 
 
73
#ifdef Q_WS_WIN
 
74
#include <krecentdirs.h>
 
75
#include <windows.h>
 
76
#endif
 
77
 
 
78
#if 0
 
79
#include <kspread_cell.h>
 
80
#include <kspread_doc.h>
 
81
#include <kspread_sheet.h>
 
82
#include <kspread_undo.h>
 
83
#include <kspread_view.h>
 
84
#endif
 
85
 
 
86
#define _IMPORT_ICON "table" /*todo: change to "file_import" or so*/
 
87
#define _TEXT_TYPE 0
 
88
#define _NUMBER_TYPE 1
 
89
#define _DATE_TYPE 2
 
90
#define _TIME_TYPE 3
 
91
#define _DATETIME_TYPE 4
 
92
#define _PK_FLAG 5
 
93
 
 
94
//extra:
 
95
#define _NO_TYPE_YET -1 //allows to accept a number of empty cells, before something non-empty
 
96
#define _FP_NUMBER_TYPE 255 //_NUMBER_TYPE variant
 
97
#define MAX_COLUMNS 100 //max 100 columns is reasonable
 
98
 
 
99
class KexiCSVImportDialogTable : public QTable
 
100
{
 
101
public:
 
102
        KexiCSVImportDialogTable( QWidget * parent = 0, const char * name = 0 )
 
103
        : QTable(parent, name) {
 
104
                f = font();
 
105
                f.setBold(true);
 
106
        }
 
107
        virtual void paintCell( QPainter * p, int row, int col, const QRect & cr, bool selected, const QColorGroup & cg ) {
 
108
                if (row==0)
 
109
                        p->setFont(f);
 
110
                else
 
111
                        p->setFont(font());
 
112
                QTable::paintCell(p, row, col, cr, selected, cg);
 
113
        }
 
114
        virtual void setColumnWidth( int col, int w ) {
 
115
                //make columns a bit wider
 
116
                QTable::setColumnWidth( col, w + 16 );
 
117
        }
 
118
        QFont f;
 
119
};
 
120
 
 
121
//! Helper used to temporary disable keyboard and mouse events 
 
122
void installRecursiveEventFilter(QObject *filter, QObject *object)
 
123
{
 
124
        object->installEventFilter(filter);
 
125
 
 
126
        if (!object->children())
 
127
                return;
 
128
 
 
129
        QObjectList list = *object->children();
 
130
        for(QObject *obj = list.first(); obj; obj = list.next())
 
131
                installRecursiveEventFilter(filter, obj);
 
132
}
 
133
 
 
134
KexiCSVImportDialog::KexiCSVImportDialog( Mode mode, KexiMainWindow* mainWin, 
 
135
        QWidget * parent, const char * name
 
136
)
 
137
 : KDialogBase( 
 
138
        KDialogBase::Plain, 
 
139
        i18n( "Import CSV Data File" )
 
140
//! @todo use "Paste CSV Data From Clipboard" caption for mode==Clipboard
 
141
        ,
 
142
        (mode==File ? User1 : (ButtonCode)0) |Ok|Cancel, 
 
143
        Ok,
 
144
        parent, 
 
145
        name ? name : "KexiCSVImportDialog", 
 
146
        true, 
 
147
        false,
 
148
        KGuiItem( i18n("&Options"))
 
149
  ),
 
150
        m_mainWin(mainWin),
 
151
        m_cancelled( false ),
 
152
        m_adjustRows( 0 ),
 
153
        m_startline( 0 ),
 
154
        m_textquote( QString(KEXICSV_DEFAULT_TEXT_QUOTE)[0] ),
 
155
        m_mode(mode),
 
156
        m_prevSelectedCol(-1),
 
157
        m_columnsAdjusted(false),
 
158
        m_1stRowForFieldNamesDetected(false),
 
159
        m_firstFillTableCall(true),
 
160
        m_blockUserEvents(false),
 
161
        m_primaryKeyColumn(-1),
 
162
        m_dialogCancelled(false),
 
163
        m_conn(0),
 
164
        m_destinationTableSchema(0)
 
165
{
 
166
        setWFlags(getWFlags() | Qt::WStyle_Maximize | Qt::WStyle_SysMenu);
 
167
        hide();
 
168
        setButtonOK(KGuiItem( i18n("&Import..."), _IMPORT_ICON));
 
169
 
 
170
        m_typeNames.resize(5);
 
171
        m_typeNames[0] = i18n("text");
 
172
        m_typeNames[1] = i18n("number");
 
173
        m_typeNames[2] = i18n("date");
 
174
        m_typeNames[3] = i18n("time");
 
175
        m_typeNames[4] = i18n("date/time");
 
176
 
 
177
        kapp->config()->setGroup("ImportExport");
 
178
        m_maximumRowsForPreview = kapp->config()->readNumEntry("MaximumRowsForPreviewInImportDialog", MAX_COLUMNS);
 
179
 
 
180
        m_pkIcon = SmallIcon("key");
 
181
 
 
182
        m_uniquenessTest.setAutoDelete(true);
 
183
 
 
184
        setIcon(DesktopIcon(_IMPORT_ICON));
 
185
        setSizeGripEnabled( TRUE );
 
186
 
 
187
        m_encoding = QString::fromLatin1(KGlobal::locale()->encoding());
 
188
        m_file = 0;
 
189
        m_inputStream = 0;
 
190
        
 
191
        QVBoxLayout *lyr = new QVBoxLayout(plainPage(), 0, KDialogBase::spacingHint(), "lyr");
 
192
 
 
193
        m_infoLbl = new KexiCSVInfoLabel(
 
194
                m_mode==File ? i18n("Preview of data from file:")
 
195
                : i18n("Preview of data from clipboard:"),
 
196
                plainPage()
 
197
        );
 
198
        lyr->addWidget( m_infoLbl );
 
199
 
 
200
        QWidget* page = new QFrame( plainPage(), "page" );
 
201
        QGridLayout *glyr= new QGridLayout( page, 4, 5, 0, KDialogBase::spacingHint(), "glyr");
 
202
        lyr->addWidget( page );
 
203
 
 
204
        // Delimiter: comma, semicolon, tab, space, other
 
205
        m_delimiterWidget = new KexiCSVDelimiterWidget(true /*lineEditOnBottom*/, page);
 
206
        glyr->addMultiCellWidget( m_delimiterWidget, 1, 2, 0, 0 );
 
207
 
 
208
        QLabel *delimiterLabel = new QLabel(m_delimiterWidget, i18n("Delimiter:"), page);
 
209
        delimiterLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
 
210
        glyr->addMultiCellWidget( delimiterLabel, 0, 0, 0, 0 );
 
211
 
 
212
        // Format: number, text, currency,
 
213
        m_formatComboText = i18n( "Format for column %1:" );
 
214
        m_formatCombo = new KComboBox(page, "m_formatCombo");
 
215
        m_formatCombo->insertItem(i18n("Text"));
 
216
        m_formatCombo->insertItem(i18n("Number"));
 
217
        m_formatCombo->insertItem(i18n("Date"));
 
218
        m_formatCombo->insertItem(i18n("Time"));
 
219
        m_formatCombo->insertItem(i18n("Date/Time"));
 
220
        glyr->addMultiCellWidget( m_formatCombo, 1, 1, 1, 1 );
 
221
 
 
222
        m_formatLabel = new QLabel(m_formatCombo, "", page);
 
223
        m_formatLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
 
224
        glyr->addWidget( m_formatLabel, 0, 1 );
 
225
 
 
226
        m_primaryKeyField = new QCheckBox( i18n( "Primary key" ), page, "m_primaryKeyField" );
 
227
        glyr->addWidget( m_primaryKeyField, 2, 1 );
 
228
        connect(m_primaryKeyField, SIGNAL(toggled(bool)), this, SLOT(slotPrimaryKeyFieldToggled(bool)));
 
229
 
 
230
        m_comboQuote = new KexiCSVTextQuoteComboBox( page );
 
231
        glyr->addWidget( m_comboQuote, 1, 2 );
 
232
 
 
233
        TextLabel2 = new QLabel( m_comboQuote, i18n( "Text quote:" ), page, "TextLabel2" );
 
234
        TextLabel2->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred );
 
235
        TextLabel2->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
 
236
        glyr->addWidget( TextLabel2, 0, 2 );
 
237
 
 
238
        m_startAtLineSpinBox = new KIntSpinBox( page, "m_startAtLineSpinBox" );
 
239
        m_startAtLineSpinBox->setMinValue(1);
 
240
        m_startAtLineSpinBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
 
241
        m_startAtLineSpinBox->setMinimumWidth(QFontMetrics(m_startAtLineSpinBox->font()).width("8888888"));
 
242
        glyr->addWidget( m_startAtLineSpinBox, 1, 3 );
 
243
 
 
244
        m_startAtLineLabel = new QLabel( m_startAtLineSpinBox, "", 
 
245
                page, "TextLabel3" );
 
246
        m_startAtLineLabel->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred );
 
247
        m_startAtLineLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
 
248
        glyr->addWidget( m_startAtLineLabel, 0, 3 );
 
249
 
 
250
        QSpacerItem* spacer_2 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
 
251
        glyr->addItem( spacer_2, 0, 4 );
 
252
 
 
253
        m_ignoreDuplicates = new QCheckBox( page, "m_ignoreDuplicates" );
 
254
        m_ignoreDuplicates->setText( i18n( "Ignore duplicated delimiters" ) );
 
255
        glyr->addMultiCellWidget( m_ignoreDuplicates, 2, 2, 2, 4 );
 
256
 
 
257
        m_1stRowForFieldNames = new QCheckBox( page, "m_1stRowForFieldNames" );
 
258
        m_1stRowForFieldNames->setText( i18n( "First row contains column names" ) );
 
259
        glyr->addMultiCellWidget( m_1stRowForFieldNames, 3, 3, 2, 4 );
 
260
 
 
261
        m_table = new KexiCSVImportDialogTable( plainPage(), "m_table" );
 
262
        lyr->addWidget( m_table );
 
263
 
 
264
        m_table->setSizePolicy( QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding, 1, 1) );
 
265
        m_table->setNumRows( 0 );
 
266
        m_table->setNumCols( 0 );
 
267
 
 
268
/** @todo reuse Clipboard too! */
 
269
/*
 
270
if ( m_mode == Clipboard )
 
271
  {
 
272
        setCaption( i18n( "Inserting From Clipboard" ) );
 
273
        QMimeSource * mime = QApplication::clipboard()->data();
 
274
        if ( !mime )
 
275
        {
 
276
          KMessageBox::information( this, i18n("There is no data in the clipboard.") );
 
277
          m_cancelled = true;
 
278
          return;
 
279
        }
 
280
 
 
281
        if ( !mime->provides( "text/plain" ) )
 
282
        {
 
283
          KMessageBox::information( this, i18n("There is no usable data in the clipboard.") );
 
284
          m_cancelled = true;
 
285
          return;
 
286
        }
 
287
        m_fileArray = QByteArray(mime->encodedData( "text/plain" ) );
 
288
  }
 
289
  else if ( mode == File )
 
290
  {*/
 
291
        m_dateRegExp1 = QRegExp("\\d{1,4}[/\\-\\.]\\d{1,2}[/\\-\\.]\\d{1,2}");
 
292
        m_dateRegExp2 = QRegExp("\\d{1,2}[/\\-\\.]\\d{1,2}[/\\-\\.]\\d{1,4}");
 
293
        m_timeRegExp1 = QRegExp("\\d{1,2}:\\d{1,2}:\\d{1,2}");
 
294
        m_timeRegExp2 = QRegExp("\\d{1,2}:\\d{1,2}");
 
295
        m_fpNumberRegExp = QRegExp("\\d*[,\\.]\\d+");
 
296
 
 
297
        if (m_mode == File) {
 
298
                QStringList mimetypes( csvMimeTypes() );
 
299
#ifdef Q_WS_WIN
 
300
                //! @todo remove
 
301
                QString recentDir = KGlobalSettings::documentPath();
 
302
                m_fname = QFileDialog::getOpenFileName( 
 
303
                        KFileDialog::getStartURL(":CSVImportExport", recentDir).path(),
 
304
                        KexiUtils::fileDialogFilterStrings(mimetypes, false),
 
305
                        page, "KexiCSVImportDialog", i18n("Open CSV Data File"));
 
306
                if ( !m_fname.isEmpty() ) {
 
307
                        //save last visited path
 
308
                        KURL url;
 
309
                        url.setPath( m_fname );
 
310
                        if (url.isLocalFile())
 
311
                                KRecentDirs::add(":CSVImportExport", url.directory());
 
312
                }
 
313
#else
 
314
                m_fname = KFileDialog::getOpenFileName(":CSVImportExport", mimetypes.join(" "), this);
 
315
#endif
 
316
                //cancel action !
 
317
                if ( m_fname.isEmpty() )
 
318
                {
 
319
                        actionButton( Ok )->setEnabled( false );
 
320
                        m_cancelled = true;
 
321
                        if (parentWidget())
 
322
                                parentWidget()->raise();
 
323
                        return;
 
324
                }
 
325
        }
 
326
        else if (m_mode == Clipboard) {
 
327
                QCString subtype("plain");
 
328
                m_data = QApplication::clipboard()->text(subtype, QClipboard::Clipboard);
 
329
/* debug
 
330
                for (int i=0;QApplication::clipboard()->data(QClipboard::Clipboard)->format(i);i++)
 
331
                        kdDebug() << i << ": " 
 
332
                                << QApplication::clipboard()->data(QClipboard::Clipboard)->format(i) << endl;
 
333
*/
 
334
        }
 
335
        else {
 
336
                return;
 
337
        }
 
338
 
 
339
        m_loadingProgressDlg = 0;
 
340
        m_importingProgressDlg = 0;
 
341
        if (m_mode == File) {
 
342
                m_loadingProgressDlg = new KProgressDialog(
 
343
                        this, "m_loadingProgressDlg", i18n("Loading CSV Data"), i18n("Loading CSV Data from \"%1\"...")
 
344
                        .arg(QDir::convertSeparators(m_fname)), true);
 
345
                m_loadingProgressDlg->progressBar()->setTotalSteps( m_maximumRowsForPreview+1 );
 
346
                m_loadingProgressDlg->show();
 
347
        }
 
348
 
 
349
        if (m_mode==Clipboard) {
 
350
                m_infoLbl->setIcon("editpaste");
 
351
        }
 
352
        updateRowCountInfo();
 
353
 
 
354
        m_table->setSelectionMode(QTable::NoSelection);
 
355
 
 
356
        connect(m_formatCombo, SIGNAL(activated(int)),
 
357
          this, SLOT(formatChanged(int)));
 
358
        connect(m_delimiterWidget, SIGNAL(delimiterChanged(const QString&)),
 
359
          this, SLOT(delimiterChanged(const QString&)));
 
360
        connect(m_startAtLineSpinBox, SIGNAL(valueChanged ( int )),
 
361
          this, SLOT(startlineSelected(int)));
 
362
        connect(m_comboQuote, SIGNAL(activated(int)),
 
363
          this, SLOT(textquoteSelected(int)));
 
364
        connect(m_table, SIGNAL(currentChanged(int, int)),
 
365
          this, SLOT(currentCellChanged(int, int)));
 
366
        connect(m_table, SIGNAL(valueChanged(int,int)),
 
367
          this, SLOT(cellValueChanged(int,int)));
 
368
        connect(m_ignoreDuplicates, SIGNAL(stateChanged(int)),
 
369
          this, SLOT(ignoreDuplicatesChanged(int)));
 
370
        connect(m_1stRowForFieldNames, SIGNAL(stateChanged(int)),
 
371
          this, SLOT(slot1stRowForFieldNamesChanged(int)));
 
372
 
 
373
        connect(this, SIGNAL(user1Clicked()), this, SLOT(optionsButtonClicked()));
 
374
 
 
375
        installRecursiveEventFilter(this, this);
 
376
 
 
377
        initLater();
 
378
}
 
379
 
 
380
KexiCSVImportDialog::~KexiCSVImportDialog()
 
381
{
 
382
        delete m_file;
 
383
}
 
384
 
 
385
void KexiCSVImportDialog::initLater()
 
386
{
 
387
        if (!openData())
 
388
                return;
 
389
 
 
390
        QChar detectedDelimiter;
 
391
        if (m_mode==File) { //only detect for File mode
 
392
                // try to detect delimiter
 
393
                // \t has priority, then , then ;
 
394
                for (uint i=0; i < QMIN(4096, m_data.length()); i++) {
 
395
                        const QChar c(m_data[i]);
 
396
                        if (c=='\t') {
 
397
                                detectedDelimiter = c;
 
398
                                break;
 
399
                        }
 
400
                        else if (c==',' && detectedDelimiter!='\t') {
 
401
                                detectedDelimiter = c;
 
402
                        }
 
403
                        else if (c==';' && detectedDelimiter!='\t' && detectedDelimiter!=',') {
 
404
                                detectedDelimiter = c;
 
405
                        }
 
406
                }
 
407
        }
 
408
        if (detectedDelimiter.isNull())
 
409
                detectedDelimiter = m_mode==File 
 
410
                        ? KEXICSV_DEFAULT_FILE_DELIMITER[0] : KEXICSV_DEFAULT_CLIPBOARD_DELIMITER[0]; //<-- defaults
 
411
 
 
412
        m_delimiterWidget->setDelimiter(QString(detectedDelimiter));
 
413
//      delimiterChanged(detectedDelimiter); // this will cause fillTable()
 
414
        m_columnsAdjusted = false;
 
415
        fillTable();
 
416
        delete m_loadingProgressDlg;
 
417
        m_loadingProgressDlg = 0;
 
418
        if (m_dialogCancelled) {
 
419
//              m_loadingProgressDlg->hide();
 
420
        //      m_loadingProgressDlg->close();
 
421
                QTimer::singleShot(0, this, SLOT(reject()));
 
422
                return;
 
423
        }
 
424
 
 
425
        currentCellChanged(0, 0);
 
426
 
 
427
//      updateGeometry();
 
428
        adjustSize();
 
429
        KDialog::centerOnScreen( this ); 
 
430
 
 
431
        if (m_loadingProgressDlg)
 
432
                m_loadingProgressDlg->hide();
 
433
        show();
 
434
        m_table->setFocus();
 
435
}
 
436
 
 
437
bool KexiCSVImportDialog::openData()
 
438
{
 
439
        if (m_mode!=File) //data already loaded, no encoding stuff needed
 
440
                return true;
 
441
 
 
442
        delete m_inputStream;
 
443
        m_inputStream = 0;
 
444
        if (m_file) {
 
445
                m_file->close();
 
446
                delete m_file;
 
447
        }
 
448
        m_file = new QFile(m_fname);
 
449
        if (!m_file->open(IO_ReadOnly))
 
450
        {
 
451
                m_file->close();
 
452
                delete m_file;
 
453
                m_file = 0;
 
454
                KMessageBox::sorry( this, i18n("Cannot open input file <nobr>\"%1\"</nobr>.")
 
455
                        .arg(QDir::convertSeparators(m_fname)) );
 
456
                actionButton( Ok )->setEnabled( false );
 
457
                m_cancelled = true;
 
458
                if (parentWidget())
 
459
                        parentWidget()->raise();
 
460
                return false;
 
461
        }
 
462
        return true;
 
463
}
 
464
 
 
465
bool KexiCSVImportDialog::cancelled() const
 
466
{
 
467
        return m_cancelled;
 
468
}
 
469
 
 
470
void KexiCSVImportDialog::fillTable()
 
471
{
 
472
        KexiUtils::WaitCursor wc(true);
 
473
        repaint();
 
474
        m_blockUserEvents = true;
 
475
        QPushButton *pb = actionButton(KDialogBase::Cancel);
 
476
        if (pb)
 
477
                pb->setEnabled(true); //allow to cancel
 
478
        KexiUtils::WaitCursor wait;
 
479
 
 
480
        if (m_table->numRows()>0) //to accept editor
 
481
                m_table->setCurrentCell(0,0);
 
482
 
 
483
        int row, column, maxColumn;
 
484
        QString field = QString::null;
 
485
 
 
486
        for (row = 0; row < m_table->numRows(); ++row)
 
487
                for (column = 0; column < m_table->numCols(); ++column)
 
488
                        m_table->clearCell(row, column);
 
489
 
 
490
        m_detectedTypes.clear();
 
491
        m_detectedTypes.resize(1024, _NO_TYPE_YET);//_TEXT_TYPE);
 
492
        m_uniquenessTest.clear();
 
493
        m_uniquenessTest.resize(1024);
 
494
        m_1stRowForFieldNamesDetected = true;
 
495
 
 
496
        if (true != loadRows(field, row, column, maxColumn, true))
 
497
                return;
 
498
 
 
499
        m_1stRowForFieldNamesDetected = false;
 
500
 
 
501
        // file with only one line without '\n'
 
502
        if (field.length() > 0)
 
503
        {
 
504
                setText(row - m_startline, column, field, true);
 
505
                ++row;
 
506
                field = QString::null;
 
507
        }
 
508
 
 
509
        adjustRows( row - m_startline - (m_1stRowForFieldNames->isChecked()?1:0) );
 
510
 
 
511
        maxColumn = QMAX( maxColumn, column );
 
512
        m_table->setNumCols(maxColumn);
 
513
 
 
514
        for (column = 0; column < m_table->numCols(); ++column)
 
515
        {
 
516
//              QString header = m_table->horizontalHeader()->label(column);
 
517
//              if (header != i18n("Text") && header != i18n("Number") &&
 
518
//                      header != i18n("Date") && header != i18n("Currency"))
 
519
//              const int detectedType = m_detectedTypes[column+1];
 
520
//              m_table->horizontalHeader()->setLabel(column, m_typeNames[ detectedType ]); //i18n("Text"));
 
521
                updateColumnText(column);
 
522
                if (!m_columnsAdjusted)
 
523
                        m_table->adjustColumn(column);
 
524
        }
 
525
        m_columnsAdjusted = true;
 
526
 
 
527
        if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) {
 
528
                if (_NUMBER_TYPE != m_detectedTypes[ m_primaryKeyColumn ]) {
 
529
                        m_primaryKeyColumn = -1;
 
530
                }
 
531
        }
 
532
 
 
533
        m_prevSelectedCol = -1;
 
534
        m_table->setCurrentCell(0,0);
 
535
        currentCellChanged(0, 0);
 
536
        if (m_primaryKeyColumn != -1)
 
537
                m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon);
 
538
 
 
539
        const int count = QMAX(0, m_table->numRows()-1+m_startline);
 
540
        const bool allRowsLoaded = count < m_maximumRowsForPreview;
 
541
        if (allRowsLoaded) {
 
542
                m_startAtLineSpinBox->setMaxValue(count);
 
543
                m_startAtLineSpinBox->setValue(m_startline+1);
 
544
        }
 
545
        m_startAtLineLabel->setText(i18n( "Start at line%1:").arg(
 
546
                        allRowsLoaded ? QString(" (1-%1)").arg(count)
 
547
                        : QString::null //we do not know what's real count
 
548
        ));
 
549
 
 
550
        m_blockUserEvents = false;
 
551
        repaint();
 
552
        m_table->verticalScrollBar()->repaint();//avoid missing repaint
 
553
        m_table->horizontalScrollBar()->repaint();//avoid missing repaint
 
554
}
 
555
 
 
556
tristate KexiCSVImportDialog::loadRows(QString &field, int &row, int &column, int &maxColumn, 
 
557
        bool inGUI)
 
558
{
 
559
        enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
 
560
                 S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;
 
561
        const QChar delimiter(m_delimiterWidget->delimiter()[0]);
 
562
        field = QString::null;
 
563
        const bool ignoreDups = m_ignoreDuplicates->isChecked();
 
564
        bool lastCharDelimiter = false;
 
565
        bool nextRow = false;
 
566
        row = column = 1;
 
567
        maxColumn = 0;
 
568
        QChar x;
 
569
        delete m_inputStream;
 
570
        if ( m_mode == Clipboard ) {
 
571
                m_inputStream = new QTextStream(m_data, IO_ReadOnly);
 
572
        }
 
573
        else {
 
574
                m_file->at(0); //always seek at 0 because loadRows() is called many times
 
575
                m_inputStream = new QTextStream(m_file);
 
576
                if (m_encoding != QString::fromLatin1(KGlobal::locale()->encoding())) {
 
577
                        QTextCodec *codec = KGlobal::charsets()->codecForName(m_encoding);
 
578
                        if (codec)
 
579
                                m_inputStream->setCodec(codec); //QTextCodec::codecForName("CP1250"));
 
580
                }
 
581
        }
 
582
        int progressStep = 0;
 
583
        if (m_importingProgressDlg)
 
584
                progressStep = QMAX( 1, m_importingProgressDlg->progressBar()->totalSteps()/200 );
 
585
        int offset = 0;
 
586
        for (;!m_inputStream->atEnd(); offset++)
 
587
        {
 
588
                if (column >= m_maximumRowsForPreview)
 
589
                        return true;
 
590
 
 
591
                if (m_importingProgressDlg && ((offset % progressStep) < 5)) {
 
592
                        //update progr. bar dlg on final exporting
 
593
                        m_importingProgressDlg->progressBar()->setValue(offset);
 
594
                        qApp->processEvents();
 
595
                        if (m_importingProgressDlg->wasCancelled()) {
 
596
                                delete m_importingProgressDlg;
 
597
                                m_importingProgressDlg = 0;
 
598
                                return ::cancelled;
 
599
                        }
 
600
                }
 
601
 
 
602
                (*m_inputStream) >> x; // read one char
 
603
 
 
604
                if (x == '\r') {
 
605
                        continue; // eat '\r', to handle RFC-compliant files
 
606
                }
 
607
 
 
608
                switch (state)
 
609
                {
 
610
                case S_START :
 
611
                        if (x == m_textquote)
 
612
                        {
 
613
                                state = S_QUOTED_FIELD;
 
614
                        }
 
615
                        else if (x == delimiter)
 
616
                        {
 
617
                                setText(row - m_startline, column, field, inGUI);
 
618
                                field = QString::null;
 
619
                                if ((ignoreDups == false) || (lastCharDelimiter == false))
 
620
                                        ++column;
 
621
                                lastCharDelimiter = true;
 
622
                        }
 
623
                        else if (x == '\n')
 
624
                        {
 
625
                                if (!inGUI) {
 
626
                                        //fill remaining empty fields (database wants them explicity)
 
627
                                        for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) {
 
628
                                                setText(row - m_startline, additionalColumn, QString::null, inGUI);
 
629
                                        }
 
630
                                }
 
631
                                nextRow = true;
 
632
                                maxColumn = QMAX( maxColumn, column );
 
633
                                column = 1;
 
634
                        }
 
635
                        else
 
636
                        {
 
637
                                field += x;
 
638
                                state = S_MAYBE_NORMAL_FIELD;
 
639
                        }
 
640
                        break;
 
641
                case S_QUOTED_FIELD :
 
642
                        if (x == m_textquote)
 
643
                        {
 
644
                                state = S_MAYBE_END_OF_QUOTED_FIELD;
 
645
                        }
 
646
/*allow \n inside quoted fields
 
647
                        else if (x == '\n')
 
648
                        {
 
649
                                setText(row - m_startline, column, field, inGUI);
 
650
                                field = "";
 
651
                                if (x == '\n')
 
652
                                {
 
653
                                        nextRow = true;
 
654
                                        maxColumn = QMAX( maxColumn, column );
 
655
                                        column = 1;
 
656
                                }
 
657
                                else
 
658
                                {
 
659
                                        if ((ignoreDups == false) || (lastCharDelimiter == false))
 
660
                                                ++column;
 
661
                                        lastCharDelimiter = true;
 
662
                                }
 
663
                                state = S_START;
 
664
                        }*/
 
665
                        else
 
666
                        {
 
667
                                field += x;
 
668
                        }
 
669
                        break;
 
670
                case S_MAYBE_END_OF_QUOTED_FIELD :
 
671
                        if (x == m_textquote)
 
672
                        {
 
673
                                field += x; //no, this was just escaped quote character
 
674
                                state = S_QUOTED_FIELD;
 
675
                        }
 
676
                        else if (x == delimiter || x == '\n')
 
677
                        {
 
678
                                setText(row - m_startline, column, field, inGUI);
 
679
                                field = QString::null;
 
680
                                if (x == '\n')
 
681
                                {
 
682
                                        nextRow = true;
 
683
                                        maxColumn = QMAX( maxColumn, column );
 
684
                                        column = 1;
 
685
                                }
 
686
                                else
 
687
                                {
 
688
                                        if ((ignoreDups == false) || (lastCharDelimiter == false))
 
689
                                                ++column;
 
690
                                        lastCharDelimiter = true;
 
691
                                }
 
692
                                state = S_START;
 
693
                        }
 
694
                        else
 
695
                        {
 
696
                                state = S_END_OF_QUOTED_FIELD;
 
697
                        }
 
698
                        break;
 
699
                case S_END_OF_QUOTED_FIELD :
 
700
                        if (x == delimiter || x == '\n')
 
701
                        {
 
702
                                setText(row - m_startline, column, field, inGUI);
 
703
                                field = QString::null;
 
704
                                if (x == '\n')
 
705
                                {
 
706
                                        nextRow = true;
 
707
                                        maxColumn = QMAX( maxColumn, column );
 
708
                                        column = 1;
 
709
                                }
 
710
                                else
 
711
                                {
 
712
                                        if ((ignoreDups == false) || (lastCharDelimiter == false))
 
713
                                                ++column;
 
714
                                        lastCharDelimiter = true;
 
715
                                }
 
716
                                state = S_START;
 
717
                        }
 
718
                        else
 
719
                        {
 
720
                                state = S_END_OF_QUOTED_FIELD;
 
721
                        }
 
722
                        break;
 
723
                case S_MAYBE_NORMAL_FIELD :
 
724
                        if (x == m_textquote)
 
725
                        {
 
726
                                field = QString::null;
 
727
                                state = S_QUOTED_FIELD;
 
728
                                break;
 
729
                        }
 
730
                case S_NORMAL_FIELD :
 
731
                        if (x == delimiter || x == '\n')
 
732
                        {
 
733
                                setText(row - m_startline, column, field, inGUI);
 
734
                                field = QString::null;
 
735
                                if (x == '\n')
 
736
                                {
 
737
                                        nextRow = true;
 
738
                                        maxColumn = QMAX( maxColumn, column );
 
739
                                        column = 1;
 
740
                                }
 
741
                                else
 
742
                                {
 
743
                                        if ((ignoreDups == false) || (lastCharDelimiter == false))
 
744
                                                ++column;
 
745
                                        lastCharDelimiter = true;
 
746
                                }
 
747
                                state = S_START;
 
748
                        }
 
749
                        else
 
750
                        {
 
751
                                field += x;
 
752
                        }
 
753
                }
 
754
                if (x != delimiter)
 
755
                        lastCharDelimiter = false;
 
756
 
 
757
                if (nextRow) {
 
758
                        nextRow = false;
 
759
                        if (!inGUI && row==1 && m_1stRowForFieldNames->isChecked()) {
 
760
                                // do not save to the database 1st row if it contains column names
 
761
                                m_importingStatement->clearArguments();
 
762
                        }
 
763
                        else if (!saveRow(inGUI))
 
764
                                return false;
 
765
                        ++row;
 
766
                }
 
767
 
 
768
                if (m_firstFillTableCall && row==2 
 
769
                        && !m_1stRowForFieldNames->isChecked() && m_1stRowForFieldNamesDetected) 
 
770
                {
 
771
                        //'1st row for field name' flag detected: reload table
 
772
                        m_1stRowForFieldNamesDetected = false;
 
773
                        m_table->setNumRows( 0 );
 
774
                        m_firstFillTableCall = false; //this trick is allowed only once, on startup
 
775
                        m_1stRowForFieldNames->setChecked(true); //this will reload table
 
776
                        //slot1stRowForFieldNamesChanged(1);
 
777
                        m_blockUserEvents = false;
 
778
                        repaint();
 
779
                        return false;
 
780
                }
 
781
 
 
782
                if (!m_importingProgressDlg && row % 20 == 0) {
 
783
                        qApp->processEvents();
 
784
                        //only for GUI mode:
 
785
                        if (!m_firstFillTableCall && m_loadingProgressDlg && m_loadingProgressDlg->wasCancelled()) {
 
786
                                delete m_loadingProgressDlg;
 
787
                                m_loadingProgressDlg = 0;
 
788
                                m_dialogCancelled = true;
 
789
                                reject();
 
790
                                return false;
 
791
                        }
 
792
                }
 
793
 
 
794
                if (!m_firstFillTableCall && m_loadingProgressDlg) {
 
795
                        m_loadingProgressDlg->progressBar()->setValue(QMIN(m_maximumRowsForPreview, row));
 
796
                }
 
797
 
 
798
                if ( inGUI && row > (m_maximumRowsForPreview + (m_1stRowForFieldNamesDetected?1:0)) ) {
 
799
                        kexipluginsdbg << "KexiCSVImportDialog::fillTable() loading stopped at row #" 
 
800
                                << m_maximumRowsForPreview << endl;
 
801
                        break;
 
802
                }
 
803
        }
 
804
        return true;
 
805
}
 
806
 
 
807
void KexiCSVImportDialog::updateColumnText(int col)
 
808
{
 
809
        QString colName;
 
810
        if (col<(int)m_columnNames.count() && (m_1stRowForFieldNames->isChecked() || m_changedColumnNames[col]))
 
811
                colName = m_columnNames[ col ];
 
812
        if (colName.isEmpty()) {
 
813
                colName = i18n("Column %1").arg(col+1); //will be changed to a valid identifier on import
 
814
                m_changedColumnNames[ col ] = false;
 
815
        }
 
816
        int detectedType = m_detectedTypes[col];
 
817
        if (detectedType==_FP_NUMBER_TYPE)
 
818
                detectedType=_NUMBER_TYPE; //we're simplifying that for now
 
819
        else if (detectedType==_NO_TYPE_YET) {
 
820
                m_detectedTypes[col]=_TEXT_TYPE; //entirely empty column
 
821
                detectedType=_TEXT_TYPE;
 
822
        }
 
823
        m_table->horizontalHeader()->setLabel(col, 
 
824
                i18n("Column %1").arg(col+1) + "  \n(" + m_typeNames[ detectedType ] + ")  ");
 
825
        m_table->setText(0, col, colName);
 
826
        m_table->horizontalHeader()->adjustHeaderSize();
 
827
 
 
828
        //check uniqueness
 
829
        QValueList<int> *list = m_uniquenessTest[col];
 
830
        if (m_primaryKeyColumn==-1 && list && !list->isEmpty()) {
 
831
                qHeapSort(*list);
 
832
                QValueList<int>::ConstIterator it=list->constBegin();
 
833
                int prevValue = *it;
 
834
                ++it;
 
835
                for(; it!=list->constEnd() && prevValue!=(*it); ++it)
 
836
                        prevValue=(*it);
 
837
                if (it!=list->constEnd()) {
 
838
                        //duplicates:
 
839
                        list->clear();
 
840
                }
 
841
                else {
 
842
                        //a candidate for PK (autodetected)!
 
843
                        if (-1==m_primaryKeyColumn) {
 
844
                                m_primaryKeyColumn=col;
 
845
                        }
 
846
                }
 
847
        }
 
848
        if (list) //not needed now: conserve memory
 
849
                list->clear();
 
850
}
 
851
 
 
852
void KexiCSVImportDialog::detectTypeAndUniqueness(int row, int col, const QString& text)
 
853
{
 
854
        int intValue;
 
855
        const int type = m_detectedTypes[col];
 
856
        if (row==1 || type!=_TEXT_TYPE) {
 
857
                bool found = false;
 
858
                if (text.isEmpty() && type==_NO_TYPE_YET)
 
859
                        found = true; //real type should be found later
 
860
                //detect type because it's 1st row or all prev. rows were not text
 
861
                //-number?
 
862
                if (!found && (row==1 || type==_NUMBER_TYPE || type==_NO_TYPE_YET)) {
 
863
                        bool ok = text.isEmpty();//empty values allowed
 
864
                        if (!ok)
 
865
                                intValue = text.toInt(&ok);
 
866
                        if (ok && (row==1 || type==_NO_TYPE_YET)) {
 
867
                                m_detectedTypes[col]=_NUMBER_TYPE;
 
868
                                found = true; //yes
 
869
                        }
 
870
                }
 
871
                //-FP number?
 
872
                if (!found && (row==1 || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) {
 
873
                        bool ok = text.isEmpty() || m_fpNumberRegExp.exactMatch(text);
 
874
                        if (!ok)
 
875
                                text.toInt(&ok);
 
876
                        if (ok && (row==1 || type==_NO_TYPE_YET)) {
 
877
                                m_detectedTypes[col]=_FP_NUMBER_TYPE;
 
878
                                found = true; //yes
 
879
                        }
 
880
                }
 
881
                //-date?
 
882
                if (!found && (row==1 || type==_DATE_TYPE || type==_NO_TYPE_YET)) {
 
883
                        if ((row==1 || type==_NO_TYPE_YET)
 
884
                                && (text.isEmpty() || m_dateRegExp1.exactMatch(text) || m_dateRegExp2.exactMatch(text)))
 
885
                        {
 
886
                                m_detectedTypes[col]=_DATE_TYPE;
 
887
                                found = true; //yes
 
888
                        }
 
889
                }
 
890
                //-time?
 
891
                if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) {
 
892
                        if ((row==1 || type==_NO_TYPE_YET)
 
893
                                && (text.isEmpty() || m_timeRegExp1.exactMatch(text) || m_timeRegExp2.exactMatch(text)))
 
894
                        {
 
895
                                m_detectedTypes[col]=_TIME_TYPE;
 
896
                                found = true; //yes
 
897
                        }
 
898
                }
 
899
                //-date/time?
 
900
                if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) {
 
901
                        if (row==1 || type==_NO_TYPE_YET) {
 
902
                                bool detected = text.isEmpty();
 
903
                                if (!detected) {
 
904
                                        const QStringList dateTimeList( QStringList::split(" ", text) );
 
905
                                        bool ok = dateTimeList.count()>=2;
 
906
//! @todo also support ISODateTime's "T" separator?
 
907
//! @todo also support timezones?
 
908
                                        if (ok) {
 
909
                                                //try all combinations
 
910
                                                QString datePart( dateTimeList[0].stripWhiteSpace() );
 
911
                                                QString timePart( dateTimeList[1].stripWhiteSpace() );
 
912
                                                ok = (m_dateRegExp1.exactMatch(datePart) || m_dateRegExp2.exactMatch(datePart))
 
913
                                                        && (m_timeRegExp1.exactMatch(timePart) || m_timeRegExp2.exactMatch(timePart));
 
914
                                        }
 
915
                                        detected = ok;
 
916
                                }
 
917
                                if (detected) {
 
918
                                        m_detectedTypes[col]=_DATETIME_TYPE;
 
919
                                        found = true; //yes
 
920
                                }
 
921
                        }
 
922
                }
 
923
                if (!found && type==_NO_TYPE_YET && !text.isEmpty()) {
 
924
                        //eventually, a non-emptytext after a while
 
925
                        m_detectedTypes[col]=_TEXT_TYPE;
 
926
                        found = true; //yes
 
927
                }
 
928
                //default: text type (already set)
 
929
        }
 
930
        //check uniqueness for this value
 
931
        QValueList<int> *list = m_uniquenessTest[col];
 
932
        if (row==1 && (!list || !list->isEmpty()) && !text.isEmpty() && _NUMBER_TYPE == m_detectedTypes[col]) {
 
933
                if (!list) {
 
934
                        list = new QValueList<int>();
 
935
                        m_uniquenessTest.insert(col, list);
 
936
                }
 
937
                list->append( intValue );
 
938
        }
 
939
        else {
 
940
                //the value is empty or uniqueness test failed in the past
 
941
                if (list && !list->isEmpty())
 
942
                        list->clear(); //indicate that uniqueness test failed
 
943
        }
 
944
}
 
945
 
 
946
void KexiCSVImportDialog::setText(int row, int col, const QString& text, bool inGUI)
 
947
{
 
948
        if (!inGUI) {
 
949
                //save text directly to database buffer
 
950
                if (col==1) { //1st col
 
951
                        m_importingStatement->clearArguments();
 
952
                        if (m_implicitPrimaryKeyAdded)
 
953
                                *m_importingStatement << QVariant(); //id will be autogenerated here
 
954
                }
 
955
                const int detectedType = m_detectedTypes[col-1];
 
956
                if (detectedType==_NUMBER_TYPE) {
 
957
                        *m_importingStatement << ( text.isEmpty() ? QVariant() : text.toInt() );
 
958
//! @todo what about time and float/double types and different integer subtypes?
 
959
                }
 
960
                else if (detectedType==_FP_NUMBER_TYPE) {
 
961
                        *m_importingStatement << ( text.isEmpty() ? QVariant() : text.toDouble() );
 
962
                }
 
963
                else if (detectedType==_DATE_TYPE) {
 
964
                        QDate date( QDate::fromString(text, Qt::ISODate) ); //same as m_dateRegExp1
 
965
                        if (!date.isValid() && m_dateRegExp2.exactMatch(text)) //dd-mm-yyyy
 
966
                                date = QDate(text.mid(6,4).toInt(), text.mid(3,2).toInt(), text.left(2).toInt());
 
967
                        *m_importingStatement << date;
 
968
                }
 
969
                else if (detectedType==_TIME_TYPE) {
 
970
                        QTime time( QTime::fromString(text, Qt::ISODate) ); //same as m_timeRegExp1
 
971
                        if (!time.isValid() && m_timeRegExp2.exactMatch(text)) //hh:mm:ss
 
972
                                time = QTime(text.left(2).toInt(), text.mid(3,2).toInt(), text.mid(6,2).toInt());
 
973
                        *m_importingStatement << time;
 
974
                }
 
975
                else if (detectedType==_DATETIME_TYPE) {
 
976
                        const QStringList dateTimeList( QStringList::split(" ", text) );
 
977
//! @todo also support ISODateTime's "T" separator?
 
978
//! @todo also support timezones?
 
979
                        if (dateTimeList.count()>=2) {
 
980
                                //try all combinations
 
981
                                QString datePart( dateTimeList[0].stripWhiteSpace() );
 
982
                                QString timePart( dateTimeList[1].stripWhiteSpace() );
 
983
                                QDateTime dateTime;
 
984
                                dateTime.setDate( QDate::fromString(datePart, Qt::ISODate) ); //same as m_dateRegExp1
 
985
                                if (!dateTime.date().isValid() && m_dateRegExp2.exactMatch(datePart)) //dd-mm-yyyy
 
986
                                        dateTime.setDate( QDate(datePart.mid(6,4).toInt(), 
 
987
                                                datePart.mid(3,2).toInt(), datePart.left(2).toInt()) );
 
988
                                dateTime.setTime( QTime::fromString(timePart, Qt::ISODate) ); //same as m_timeRegExp1
 
989
                                if (!dateTime.time().isValid() && m_timeRegExp2.exactMatch(timePart)) //hh:mm:ss
 
990
                                        dateTime.setTime( QTime(timePart.left(2).toInt(), 
 
991
                                                timePart.mid(3,2).toInt(), timePart.mid(6,2).toInt()) );
 
992
                                *m_importingStatement << dateTime;
 
993
                        }
 
994
                }
 
995
                else //_TEXT_TYPE and the rest
 
996
                        *m_importingStatement << text;
 
997
                return;
 
998
        }
 
999
        //save text to GUI (table view)
 
1000
        if (m_table->numCols() < col) {
 
1001
                m_table->setNumCols(col);
 
1002
                if ((int)m_columnNames.size() < m_table->numCols()) {
 
1003
                        m_columnNames.resize(m_table->numCols()+10);
 
1004
                        m_changedColumnNames.resize(m_table->numCols()+10);
 
1005
                }
 
1006
        }
 
1007
 
 
1008
        if (m_1stRowForFieldNames->isChecked()) {
 
1009
                if ((row+m_startline)==1) {//this is for column name
 
1010
                        if ((col-1) < (int)m_changedColumnNames.size() && false==m_changedColumnNames[col-1]) {
 
1011
                                //this column has no custom name entered by a user
 
1012
                                //-get the name from the data cell
 
1013
                                QString colName(text.simplifyWhiteSpace());
 
1014
                                if (!colName.isEmpty()) {
 
1015
                                        if (colName.left(1)>="0" && colName.left(1)<="9")
 
1016
                                                colName.prepend(i18n("Column")+" ");
 
1017
                                        m_columnNames[ col-1 ] = colName;
 
1018
                                }
 
1019
                        }
 
1020
                        return;
 
1021
                }
 
1022
        }
 
1023
        else {
 
1024
                if ((row+m_startline)==1) {//this row is for column name
 
1025
                        if (m_1stRowForFieldNamesDetected && !m_1stRowForFieldNames->isChecked()) {
 
1026
                                QString f( text.simplifyWhiteSpace() );
 
1027
                                if (f.isEmpty() || !f[0].isLetter())
 
1028
                                        m_1stRowForFieldNamesDetected = false; //this couldn't be a column name
 
1029
                        }
 
1030
                }
 
1031
                row++; //1st row was for column names
 
1032
        }
 
1033
 
 
1034
        if (row < 2) // skipped by the user
 
1035
                return;
 
1036
 
 
1037
        if (m_table->numRows() < row) {
 
1038
//              if (m_maximumRowsForPreview >= row+100)
 
1039
                m_table->setNumRows(row+100); /* We add more rows at a time to limit recalculations */
 
1040
                //else
 
1041
//                      m_table->setNumRows(m_maximumRowsForPreview);
 
1042
                m_table->verticalHeader()->setLabel(0, i18n("Column name")+"   ");
 
1043
                m_adjustRows=1;
 
1044
        }
 
1045
 
 
1046
        m_table->setText(row - 1, col - 1, text);
 
1047
        m_table->verticalHeader()->setLabel(row-1, QString::number(row-1));
 
1048
 
 
1049
        detectTypeAndUniqueness(row-1, col-1, text);
 
1050
}
 
1051
 
 
1052
bool KexiCSVImportDialog::saveRow(bool inGUI)
 
1053
{
 
1054
        if (inGUI) {
 
1055
                //nothing to do
 
1056
                return true;
 
1057
        }
 
1058
        //save db buffer
 
1059
        bool res = m_importingStatement->execute();
 
1060
//todo: move
 
1061
        m_importingStatement->clearArguments();
 
1062
        return res;
 
1063
//      return m_conn->insertRecord(*m_destinationTableSchema, m_dbRowBuffer);
 
1064
}
 
1065
 
 
1066
void KexiCSVImportDialog::adjustRows(int iRows)
 
1067
{
 
1068
        if (m_adjustRows)
 
1069
        {
 
1070
                m_table->setNumRows( iRows );
 
1071
                m_adjustRows=0;
 
1072
                for (int i = 0; i<iRows; i++)
 
1073
                        m_table->adjustRow(i);
 
1074
        }
 
1075
}
 
1076
 
 
1077
void KexiCSVImportDialog::formatChanged(int id)
 
1078
{
 
1079
        if (id==_PK_FLAG) {
 
1080
                if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) {
 
1081
                        m_table->setPixmap(0, m_primaryKeyColumn, QPixmap());
 
1082
                }
 
1083
                if (m_primaryKeyField->isChecked()) {
 
1084
                        m_primaryKeyColumn = m_table->currentColumn();
 
1085
                        m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon);
 
1086
                }
 
1087
                else
 
1088
                        m_primaryKeyColumn = -1;
 
1089
                return;
 
1090
        }
 
1091
        else {
 
1092
                m_detectedTypes[m_table->currentColumn()]=id;
 
1093
                m_primaryKeyField->setEnabled( _NUMBER_TYPE == id );
 
1094
                m_primaryKeyField->setChecked( m_primaryKeyColumn == m_table->currentColumn() && m_primaryKeyField->isEnabled() );
 
1095
        }
 
1096
        updateColumnText(m_table->currentColumn());
 
1097
}
 
1098
 
 
1099
void KexiCSVImportDialog::delimiterChanged(const QString& delimiter)
 
1100
{
 
1101
        Q_UNUSED(delimiter);
 
1102
        m_columnsAdjusted = false;
 
1103
        //delayed, otherwise combobox won't be repainted
 
1104
        QTimer::singleShot(10, this, SLOT(fillTable()));
 
1105
}
 
1106
 
 
1107
void KexiCSVImportDialog::textquoteSelected(int)
 
1108
{
 
1109
        const QString tq(m_comboQuote->textQuote());
 
1110
        if (tq.isEmpty())
 
1111
                m_textquote = 0;
 
1112
        else
 
1113
                m_textquote = tq[0];
 
1114
 
 
1115
        //delayed, otherwise combobox won't be repainted
 
1116
        QTimer::singleShot(10, this, SLOT(fillTable()));
 
1117
}
 
1118
 
 
1119
void KexiCSVImportDialog::startlineSelected(int startline)
 
1120
{
 
1121
//      const int startline = line.toInt() - 1;
 
1122
        if (m_startline == (startline-1))
 
1123
                return;
 
1124
        m_startline = startline-1;
 
1125
        m_adjustRows=1;
 
1126
        fillTable();
 
1127
        m_table->setFocus();
 
1128
}
 
1129
 
 
1130
void KexiCSVImportDialog::currentCellChanged(int, int col)
 
1131
{
 
1132
        if (m_prevSelectedCol==col)
 
1133
                return;
 
1134
        m_prevSelectedCol = col;
 
1135
        int type = m_detectedTypes[col];
 
1136
        if (type==_FP_NUMBER_TYPE)
 
1137
                type=_NUMBER_TYPE; //we're simplifying that for now
 
1138
 
 
1139
        m_formatCombo->setCurrentItem( type );
 
1140
        m_formatLabel->setText( m_formatComboText.arg(col+1) );
 
1141
        m_primaryKeyField->setEnabled( _NUMBER_TYPE == m_detectedTypes[col]);
 
1142
        m_primaryKeyField->blockSignals(true); //block to disable executing slotPrimaryKeyFieldToggled()
 
1143
         m_primaryKeyField->setChecked( m_primaryKeyColumn == col );
 
1144
        m_primaryKeyField->blockSignals(false);
 
1145
}
 
1146
 
 
1147
void KexiCSVImportDialog::cellValueChanged(int row,int col)
 
1148
{
 
1149
        if (row==0) {//column name has changed
 
1150
                m_columnNames[ col ] = m_table->text(row, col);
 
1151
                m_changedColumnNames.setBit( col );
 
1152
        }
 
1153
}
 
1154
 
 
1155
void KexiCSVImportDialog::accept()
 
1156
{
 
1157
//! @todo MOVE MOST OF THIS TO CORE/ (KexiProject?) after KexiDialogBase code is moved to non-gui place
 
1158
 
 
1159
        KexiGUIMessageHandler msg; //! @todo make it better integrated with main window
 
1160
 
 
1161
        const uint numRows( m_table->numRows() );
 
1162
        if (numRows == 0)
 
1163
                return; //impossible
 
1164
 
 
1165
        if (numRows == 1) {
 
1166
                if (KMessageBox::No == KMessageBox::questionYesNo(this, 
 
1167
                        i18n("Data set contains no rows. Do you want to import empty table?")))
 
1168
                        return;
 
1169
        }
 
1170
 
 
1171
        KexiProject* project = m_mainWin->project();
 
1172
        if (!project) {
 
1173
                msg.showErrorMessage(i18n("No project available."));
 
1174
                return;
 
1175
        }
 
1176
        m_conn = project->dbConnection(); //cache this pointer
 
1177
        if (!m_conn) {
 
1178
                msg.showErrorMessage(i18n("No database connection available."));
 
1179
                return;
 
1180
        }
 
1181
        KexiPart::Part *part = Kexi::partManager().partForMimeType("kexi/table");
 
1182
        if (!part) {
 
1183
                msg.showErrorMessage(&Kexi::partManager());
 
1184
                return;
 
1185
        }
 
1186
 
 
1187
        //get suggested name based on the file name
 
1188
        QString suggestedName;
 
1189
        if (m_mode==File) {
 
1190
                suggestedName = KURL::fromPathOrURL(m_fname).fileName();
 
1191
                //remove extension
 
1192
                if (!suggestedName.isEmpty()) {
 
1193
                        const int idx = suggestedName.findRev(".");
 
1194
                        if (idx!=-1)
 
1195
                                suggestedName = suggestedName.mid(0, idx ).simplifyWhiteSpace();
 
1196
                }
 
1197
        }
 
1198
 
 
1199
        //-new part item
 
1200
        KexiPart::Item* partItemForSavedTable = project->createPartItem(part->info(), suggestedName);
 
1201
        if (!partItemForSavedTable) {
 
1202
        //              msg.showErrorMessage(project);
 
1203
                return;
 
1204
        }
 
1205
 
 
1206
#define _ERR \
 
1207
        { project->deleteUnstoredItem(partItemForSavedTable); \
 
1208
          m_conn = 0; \
 
1209
          delete m_destinationTableSchema; \
 
1210
          m_destinationTableSchema = 0; \
 
1211
        return; }
 
1212
 
 
1213
        //-ask for table name/title
 
1214
        // (THIS IS FROM KexiMainWindowImpl::saveObject())
 
1215
        bool allowOverwriting = true;
 
1216
        tristate res = m_mainWin->getNewObjectInfo( partItemForSavedTable, part, allowOverwriting );
 
1217
        if (~res || !res) {
 
1218
                //! @todo: err
 
1219
                _ERR;
 
1220
        }
 
1221
        //(allowOverwriting is now set to true, if user accepts overwriting, 
 
1222
        // and overwriting will be needed)
 
1223
 
 
1224
//      KexiDB::SchemaData sdata(part->info()->projectPartID());
 
1225
//      sdata.setName( partItem->name() );
 
1226
 
 
1227
        //-create table schema (and thus schema object)
 
1228
        //-assign information (THIS IS FROM KexiDialogBase::storeNewData())
 
1229
        m_destinationTableSchema = new KexiDB::TableSchema(partItemForSavedTable->name());
 
1230
        m_destinationTableSchema->setCaption( partItemForSavedTable->caption() );
 
1231
        m_destinationTableSchema->setDescription( partItemForSavedTable->description() );
 
1232
        const uint numCols( m_table->numCols() );
 
1233
 
 
1234
        m_implicitPrimaryKeyAdded = false;
 
1235
        //add PK if user wanted it
 
1236
        int msgboxResult;
 
1237
        if (m_primaryKeyColumn==-1
 
1238
                && KMessageBox::No != (msgboxResult = KMessageBox::questionYesNoCancel(this, 
 
1239
                        i18n("No Primary Key (autonumber) has been defined.\n"
 
1240
                        "Should it be automatically defined on import (recommended)?\n\n"
 
1241
                        "Note: An imported table without a Primary Key may not be editable (depending on database type)."),
 
1242
                        QString::null, KGuiItem(i18n("Add Database Primary Key to a Table", "Add Primary Key"), "key"),
 
1243
                        KGuiItem(i18n("Do Not Add Database Primary Key to a Table", "Do Not Add")))))
 
1244
        {
 
1245
                if (msgboxResult == KMessageBox::Cancel)
 
1246
                        _ERR; //cancel accepting
 
1247
 
 
1248
                //add implicit PK field
 
1249
//! @todo make this field hidden (what about e.g. pgsql?)
 
1250
                m_implicitPrimaryKeyAdded = true;
 
1251
 
 
1252
                QString fieldName("id");
 
1253
                QString fieldCaption("Id");
 
1254
 
 
1255
                QStringList colnames;
 
1256
                for (uint col = 0; col < numCols; col++)
 
1257
                        colnames.append( m_table->text(0, col).lower().simplifyWhiteSpace() );
 
1258
 
 
1259
                if (colnames.find(fieldName)!=colnames.end()) {
 
1260
                        int num = 1;
 
1261
                        while (colnames.find(fieldName+QString::number(num))!=colnames.end())
 
1262
                                num++;
 
1263
                        fieldName += QString::number(num);
 
1264
                        fieldCaption += QString::number(num);
 
1265
                }
 
1266
                KexiDB::Field *field = new KexiDB::Field(
 
1267
                        fieldName,
 
1268
                        KexiDB::Field::Integer,
 
1269
                        KexiDB::Field::NoConstraints,
 
1270
                        KexiDB::Field::NoOptions,
 
1271
                        0,0, //uint length=0, uint precision=0,
 
1272
                        QVariant(), //QVariant defaultValue=QVariant(),
 
1273
                        fieldCaption
 
1274
                ); //no description and width for now
 
1275
                field->setPrimaryKey(true);
 
1276
                field->setAutoIncrement(true);
 
1277
                m_destinationTableSchema->addField( field );
 
1278
        }
 
1279
 
 
1280
        for (uint col = 0; col < numCols; col++) {
 
1281
                QString fieldCaption( m_table->text(0, col).simplifyWhiteSpace() );
 
1282
                QString fieldName( KexiUtils::string2Identifier( fieldCaption ) );
 
1283
                if (m_destinationTableSchema->field(fieldName)) {
 
1284
                        QString fixedFieldName;
 
1285
                        uint i = 2; //"apple 2, apple 3, etc. if there're many "apple" names
 
1286
                        do {
 
1287
                                fixedFieldName = fieldName + "_" + QString::number(i);
 
1288
                                if (!m_destinationTableSchema->field(fixedFieldName))
 
1289
                                        break;
 
1290
                                i++;
 
1291
                        } while (true);
 
1292
                        fieldName = fixedFieldName;
 
1293
                        fieldCaption += (" " + QString::number(i));
 
1294
                }
 
1295
                const int detectedType = m_detectedTypes[col];
 
1296
                KexiDB::Field::Type fieldType;
 
1297
                if (detectedType==_DATE_TYPE)
 
1298
                        fieldType = KexiDB::Field::Date;
 
1299
                if (detectedType==_TIME_TYPE)
 
1300
                        fieldType = KexiDB::Field::Time;
 
1301
                if (detectedType==_DATETIME_TYPE)
 
1302
                        fieldType = KexiDB::Field::DateTime;
 
1303
                else if (detectedType==_NUMBER_TYPE)
 
1304
                        fieldType = KexiDB::Field::Integer;
 
1305
                else if (detectedType==_FP_NUMBER_TYPE)
 
1306
                        fieldType = KexiDB::Field::Double;
 
1307
//! @todo what about time and float/double types and different integer subtypes?
 
1308
                else //_TEXT_TYPE and the rest
 
1309
                        fieldType = KexiDB::Field::Text;
 
1310
//! @todo what about long text?
 
1311
 
 
1312
                KexiDB::Field *field = new KexiDB::Field(
 
1313
                        fieldName,
 
1314
                        fieldType,
 
1315
                        KexiDB::Field::NoConstraints,
 
1316
                        KexiDB::Field::NoOptions,
 
1317
                        0,0, //uint length=0, uint precision=0,
 
1318
                        QVariant(), //QVariant defaultValue=QVariant(),
 
1319
                        fieldCaption
 
1320
                ); //no description and width for now
 
1321
 
 
1322
                if ((int)col == m_primaryKeyColumn) {
 
1323
                        field->setPrimaryKey(true);
 
1324
                        field->setAutoIncrement(true);
 
1325
                }
 
1326
                m_destinationTableSchema->addField( field );
 
1327
        }
 
1328
 
 
1329
        KexiDB::Transaction transaction = m_conn->beginTransaction();
 
1330
        if (transaction.isNull()) {
 
1331
                msg.showErrorMessage(m_conn);
 
1332
                _ERR;
 
1333
        }
 
1334
        KexiDB::TransactionGuard tg(transaction);
 
1335
 
 
1336
        //-create physical table
 
1337
        if (!m_conn->createTable(m_destinationTableSchema, allowOverwriting)) {
 
1338
                        msg.showErrorMessage(m_conn);
 
1339
                _ERR;
 
1340
        }
 
1341
 
 
1342
#define _DROP_DEST_TABLE_AND_RETURN \
 
1343
        { \
 
1344
        if (m_importingProgressDlg) \
 
1345
                m_importingProgressDlg->hide(); \
 
1346
        project->deleteUnstoredItem(partItemForSavedTable); \
 
1347
        m_conn->dropTable(m_destinationTableSchema); /*alsoRemoveSchema*/ \
 
1348
        m_destinationTableSchema = 0; \
 
1349
        m_conn = 0; \
 
1350
        return; \
 
1351
        }
 
1352
 
 
1353
        m_importingStatement = m_conn->prepareStatement(
 
1354
                KexiDB::PreparedStatement::InsertStatement, *m_destinationTableSchema);
 
1355
        if (!m_importingStatement) {
 
1356
                msg.showErrorMessage(m_conn);
 
1357
                _DROP_DEST_TABLE_AND_RETURN;
 
1358
        }
 
1359
 
 
1360
        if (m_file) {
 
1361
                if (!m_importingProgressDlg) {
 
1362
                        m_importingProgressDlg = new KProgressDialog( this, "m_importingProgressDlg", 
 
1363
                                i18n("Importing CSV Data"), QString::null, true );
 
1364
                }
 
1365
                m_importingProgressDlg->setLabel(
 
1366
                        i18n("Importing CSV Data from <nobr>\"%1\"</nobr> into \"%2\" table...")
 
1367
                        .arg(QDir::convertSeparators(m_fname)).arg(m_destinationTableSchema->name()) );
 
1368
                m_importingProgressDlg->progressBar()->setTotalSteps( QFileInfo(*m_file).size() );
 
1369
                m_importingProgressDlg->show();
 
1370
        }
 
1371
 
 
1372
        int row, column, maxColumn;
 
1373
        QString field = QString::null;
 
1374
 
 
1375
        // main job
 
1376
        res = loadRows(field, row, column, maxColumn, false /*!gui*/ );
 
1377
 
 
1378
        delete m_importingProgressDlg;
 
1379
  m_importingProgressDlg = 0;
 
1380
        if (true != res) {
 
1381
                //importing cancelled or failed
 
1382
                if (!res) //do not display err msg when res == cancelled
 
1383
                        msg.showErrorMessage(m_conn);
 
1384
                _DROP_DEST_TABLE_AND_RETURN;
 
1385
        }
 
1386
 
 
1387
        // file with only one line without '\n'
 
1388
        if (field.length() > 0)
 
1389
        {
 
1390
                setText(row - m_startline, column, field, false /*!gui*/);
 
1391
                //fill remaining empty fields (database wants them explicity)
 
1392
                for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) {
 
1393
                        setText(row - m_startline, additionalColumn, QString::null, false /*!gui*/);
 
1394
                }
 
1395
                if (!saveRow(false /*!gui*/)) {
 
1396
                        msg.showErrorMessage(m_conn);
 
1397
                        _DROP_DEST_TABLE_AND_RETURN;
 
1398
                }
 
1399
                ++row;
 
1400
                field = QString::null;
 
1401
        }
 
1402
 
 
1403
        if (!tg.commit()) {
 
1404
                msg.showErrorMessage(m_conn);
 
1405
                _DROP_DEST_TABLE_AND_RETURN;
 
1406
        }
 
1407
 
 
1408
        //-now we can store the item
 
1409
        partItemForSavedTable->setIdentifier( m_destinationTableSchema->id() );
 
1410
        project->addStoredItem( part->info(), partItemForSavedTable );
 
1411
 
 
1412
        QDialog::accept();
 
1413
        KMessageBox::information(this, i18n("Data has been successfully imported to table \"%1\".")
 
1414
                .arg(m_destinationTableSchema->name()));
 
1415
        parentWidget()->raise();
 
1416
        m_conn = 0;
 
1417
}
 
1418
 
 
1419
int KexiCSVImportDialog::getHeader(int col)
 
1420
{
 
1421
        QString header = m_table->horizontalHeader()->label(col);
 
1422
 
 
1423
        if (header == i18n("Text type for column", "Text"))
 
1424
                return TEXT;
 
1425
        else if (header == i18n("Numeric type for column", "Number"))
 
1426
                return NUMBER;
 
1427
        else if (header == i18n("Currency type for column", "Currency"))
 
1428
                return CURRENCY;
 
1429
        else
 
1430
                return DATE;
 
1431
}
 
1432
 
 
1433
QString KexiCSVImportDialog::getText(int row, int col)
 
1434
{
 
1435
        return m_table->text(row, col);
 
1436
}
 
1437
 
 
1438
void KexiCSVImportDialog::ignoreDuplicatesChanged(int)
 
1439
{
 
1440
        fillTable();
 
1441
}
 
1442
 
 
1443
void KexiCSVImportDialog::slot1stRowForFieldNamesChanged(int)
 
1444
{
 
1445
        m_adjustRows=1;
 
1446
        if (m_1stRowForFieldNames->isChecked() && m_startline>0 && m_startline>=(m_startAtLineSpinBox->maxValue()-1))
 
1447
                m_startline--;
 
1448
        fillTable();
 
1449
}
 
1450
 
 
1451
void KexiCSVImportDialog::optionsButtonClicked()
 
1452
{
 
1453
        KexiCSVImportOptionsDialog dlg(m_encoding, this);
 
1454
        if (QDialog::Accepted != dlg.exec())
 
1455
                return;
 
1456
 
 
1457
        if (m_encoding != dlg.encodingComboBox()->selectedEncoding()) {
 
1458
                m_encoding = dlg.encodingComboBox()->selectedEncoding();
 
1459
                if (!openData())
 
1460
                        return;
 
1461
                fillTable();
 
1462
        }
 
1463
}
 
1464
 
 
1465
bool KexiCSVImportDialog::eventFilter ( QObject * watched, QEvent * e )
 
1466
{
 
1467
        QEvent::Type t = e->type();
 
1468
        // temporary disable keyboard and mouse events for time-consuming tasks
 
1469
        if (m_blockUserEvents && (t==QEvent::KeyPress || t==QEvent::KeyRelease 
 
1470
                || t==QEvent::MouseButtonPress || t==QEvent::MouseButtonDblClick
 
1471
                || t==QEvent::Paint ))
 
1472
                return true;
 
1473
 
 
1474
        if (watched == m_startAtLineSpinBox && t==QEvent::KeyPress) {
 
1475
                QKeyEvent *ke = static_cast<QKeyEvent*>(e);
 
1476
                if (ke->key()==Key_Enter || ke->key()==Key_Return) {
 
1477
                        m_table->setFocus();
 
1478
                        return true;
 
1479
                }
 
1480
        }
 
1481
        return QDialog::eventFilter( watched, e );
 
1482
}
 
1483
 
 
1484
void KexiCSVImportDialog::slotPrimaryKeyFieldToggled(bool on)
 
1485
{
 
1486
        Q_UNUSED(on);
 
1487
        formatChanged(_PK_FLAG);
 
1488
}
 
1489
 
 
1490
void KexiCSVImportDialog::updateRowCountInfo()
 
1491
{
 
1492
//! @todo infoLbl->setFileName( m_fname + " " + i18n("row count", "(rows: %1)").arg(10);
 
1493
        m_infoLbl->setFileName( m_fname );
 
1494
}
 
1495
 
 
1496
#include "kexicsvimportdialog.moc"