kexicsvimportdialog.cpp
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.cpp
5 and will be merged back with KOffice libraries.
6
7 Copyright (C) 2002-2003 Norbert Andres <nandres@web.de>
8 Copyright (C) 2002-2003 Ariya Hidayat <ariya@kde.org>
9 Copyright (C) 2002 Laurent Montel <montel@kde.org>
10 Copyright (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 <tqbuttongroup.h>
29#include <tqcheckbox.h>
30#include <tqclipboard.h>
31#include <tqlabel.h>
32#include <tqlineedit.h>
33#include <tqmime.h>
34#include <tqpushbutton.h>
35#include <tqradiobutton.h>
36#include <tqtable.h>
37#include <tqlayout.h>
38#include <tqfiledialog.h>
39#include <tqpainter.h>
40#include <tqtextcodec.h>
41#include <tqtimer.h>
42#include <tqfontmetrics.h>
43#include <tqtooltip.h>
44
45#include <tdeapplication.h>
46#include <kdebug.h>
47#include <kdialogbase.h>
48#include <tdefiledialog.h>
49#include <tdelocale.h>
50#include <tdemessagebox.h>
51#include <tdeglobalsettings.h>
52#include <kiconloader.h>
53#include <kcharsets.h>
54#include <knuminput.h>
55#include <kprogress.h>
56#include <kactivelabel.h>
57
58#include <kexiutils/identifier.h>
59#include <kexiutils/utils.h>
60#include <core/kexi.h>
61#include <core/kexiproject.h>
62#include <core/kexipart.h>
63#include <core/kexipartinfo.h>
64#include <core/keximainwindow.h>
65#include <core/kexiguimsghandler.h>
66#include <kexidb/connection.h>
67#include <kexidb/tableschema.h>
68#include <kexidb/transaction.h>
69#include <widget/kexicharencodingcombobox.h>
70
71#include "kexicsvimportdialog.h"
72#include "kexicsvwidgets.h"
73
74#ifdef TQ_WS_WIN
75#include <tderecentdirs.h>
76#include <windows.h>
77#endif
78
79#if 0
80#include <kspread_cell.h>
81#include <kspread_doc.h>
82#include <kspread_sheet.h>
83#include <kspread_undo.h>
84#include <kspread_view.h>
85#endif
86
87#define _IMPORT_ICON "table" /*todo: change to "file_import" or so*/
88#define _TEXT_TYPE 0
89#define _NUMBER_TYPE 1
90#define _DATE_TYPE 2
91#define _TIME_TYPE 3
92#define _DATETIME_TYPE 4
93#define _PK_FLAG 5
94
95//extra:
96#define _NO_TYPE_YET -1 //allows to accept a number of empty cells, before something non-empty
97#define _FP_NUMBER_TYPE 255 //_NUMBER_TYPE variant
98#define MAX_ROWS_TO_PREVIEW 100 //max 100 rows is reasonable
99#define MAX_BYTES_TO_PREVIEW 10240 //max 10KB is reasonable
100#define MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER 4096
101
102class KexiCSVImportDialogTable : public TQTable
103{
104public:
105 KexiCSVImportDialogTable( TQWidget * parent = 0, const char * name = 0 )
106 : TQTable(parent, name) {
107 f = font();
108 f.setBold(true);
109 }
110 virtual void paintCell( TQPainter * p, int row, int col, const TQRect & cr, bool selected, const TQColorGroup & cg ) {
111 if (row==0)
112 p->setFont(f);
113 else
114 p->setFont(font());
115 TQTable::paintCell(p, row, col, cr, selected, cg);
116 }
117 virtual void setColumnWidth( int col, int w ) {
118 //make columns a bit wider
119 TQTable::setColumnWidth( col, w + 16 );
120 }
121 TQFont f;
122};
123
125void installRecursiveEventFilter(TQObject *filter, TQObject *object)
126{
127 object->installEventFilter(filter);
128
129 TQObjectList clo = object->childrenListObject();
130
131 if (clo.isEmpty())
132 return;
133
134 TQObjectList list = clo;
135 for(TQObject *obj = list.first(); obj; obj = list.next())
136 installRecursiveEventFilter(filter, obj);
137}
138
140 TQWidget * parent, const char * name
141)
142 : KDialogBase(
143 KDialogBase::Plain,
144 i18n( "Import CSV Data File" )
146 ,
147 (mode==File ? User1 : (ButtonCode)0) |Ok|Cancel,
148 Ok,
149 parent,
150 name ? name : "KexiCSVImportDialog",
151 true,
152 false,
153 KGuiItem( i18n("&Options"))
154 ),
155 m_mainWin(mainWin),
156 m_cancelled( false ),
157 m_adjustRows( true ),
158 m_startline( 0 ),
159 m_textquote( TQString(KEXICSV_DEFAULT_FILE_TEXT_QUOTE)[0] ),
160 m_mode(mode),
161 m_prevSelectedCol(-1),
162 m_columnsAdjusted(false),
163 m_1stRowForFieldNamesDetected(false),
164 m_firstFillTableCall(true),
165 m_blockUserEvents(false),
166 m_primaryKeyColumn(-1),
167 m_dialogCancelled(false),
168 m_conn(0),
169 m_destinationTableSchema(0),
170 m_allRowsLoadedInPreview(false),
171 m_stoppedAt_MAX_BYTES_TO_PREVIEW(false)
172{
173 setWFlags(getWFlags() | TQt::WStyle_Maximize | TQt::WStyle_SysMenu);
174 hide();
175 setButtonOK(KGuiItem( i18n("&Import..."), _IMPORT_ICON));
176
177 m_typeNames.resize(5);
178 m_typeNames[0] = i18n("text");
179 m_typeNames[1] = i18n("number");
180 m_typeNames[2] = i18n("date");
181 m_typeNames[3] = i18n("time");
182 m_typeNames[4] = i18n("date/time");
183
184 kapp->config()->setGroup("ImportExport");
185 m_maximumRowsForPreview = kapp->config()->readNumEntry("MaximumRowsForPreviewInImportDialog", MAX_ROWS_TO_PREVIEW);
186 m_maximumBytesForPreview = kapp->config()->readNumEntry("MaximumBytesForPreviewInImportDialog", MAX_BYTES_TO_PREVIEW);
187
188 m_pkIcon = SmallIcon("key");
189
190 m_uniquenessTest.setAutoDelete(true);
191
192 setIcon(DesktopIcon(_IMPORT_ICON));
193 setSizeGripEnabled( true );
194
195// m_encoding = TQString::fromLatin1(TDEGlobal::locale()->encoding());
196// m_stripWhiteSpaceInTextValuesChecked = true;
197 m_file = 0;
198 m_inputStream = 0;
199
200 TQVBoxLayout *lyr = new TQVBoxLayout(plainPage(), 0, KDialogBase::spacingHint(), "lyr");
201
202 m_infoLbl = new KexiCSVInfoLabel(
203 m_mode==File ? i18n("Preview of data from file:")
204 : i18n("Preview of data from clipboard:"),
205 plainPage()
206 );
207 lyr->addWidget( m_infoLbl );
208
209 TQWidget* page = new TQFrame( plainPage(), "page" );
210 TQGridLayout *glyr= new TQGridLayout( page, 4, 5, 0, KDialogBase::spacingHint(), "glyr");
211 lyr->addWidget( page );
212
213 // Delimiter: comma, semicolon, tab, space, other
214 m_delimiterWidget = new KexiCSVDelimiterWidget(true /*lineEditOnBottom*/, page);
215 m_detectDelimiter = true;
216 glyr->addMultiCellWidget( m_delimiterWidget, 1, 2, 0, 0 );
217
218 TQLabel *delimiterLabel = new TQLabel(m_delimiterWidget, i18n("Delimiter:"), page);
219 delimiterLabel->setAlignment(TQt::AlignAuto | TQt::AlignBottom);
220 glyr->addMultiCellWidget( delimiterLabel, 0, 0, 0, 0 );
221
222 // Format: number, text, currency,
223 m_formatComboText = i18n( "Format for column %1:" );
224 m_formatCombo = new KComboBox(page, "m_formatCombo");
225 m_formatCombo->insertItem(i18n("Text"));
226 m_formatCombo->insertItem(i18n("Number"));
227 m_formatCombo->insertItem(i18n("Date"));
228 m_formatCombo->insertItem(i18n("Time"));
229 m_formatCombo->insertItem(i18n("Date/Time"));
230 glyr->addMultiCellWidget( m_formatCombo, 1, 1, 1, 1 );
231
232 m_formatLabel = new TQLabel(m_formatCombo, "", page);
233 m_formatLabel->setAlignment(TQt::AlignAuto | TQt::AlignBottom);
234 glyr->addWidget( m_formatLabel, 0, 1 );
235
236 m_primaryKeyField = new TQCheckBox( i18n( "Primary key" ), page, "m_primaryKeyField" );
237 glyr->addWidget( m_primaryKeyField, 2, 1 );
238 connect(m_primaryKeyField, TQ_SIGNAL(toggled(bool)), this, TQ_SLOT(slotPrimaryKeyFieldToggled(bool)));
239
240 m_comboQuote = new KexiCSVTextQuoteComboBox( page );
241 glyr->addWidget( m_comboQuote, 1, 2 );
242
243 TextLabel2 = new TQLabel( m_comboQuote, i18n( "Text quote:" ), page, "TextLabel2" );
244 TextLabel2->setSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Preferred );
245 TextLabel2->setAlignment(TQt::AlignAuto | TQt::AlignBottom);
246 glyr->addWidget( TextLabel2, 0, 2 );
247
248 m_startAtLineSpinBox = new KIntSpinBox( page, "m_startAtLineSpinBox" );
249 m_startAtLineSpinBox->setMinValue(1);
250 m_startAtLineSpinBox->setSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Fixed );
251 m_startAtLineSpinBox->setMinimumWidth(TQFontMetrics(m_startAtLineSpinBox->font()).width("8888888"));
252 glyr->addWidget( m_startAtLineSpinBox, 1, 3 );
253
254 m_startAtLineLabel = new TQLabel( m_startAtLineSpinBox, "",
255 page, "TextLabel3" );
256 m_startAtLineLabel->setSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Preferred );
257 m_startAtLineLabel->setAlignment(TQt::AlignAuto | TQt::AlignBottom);
258 glyr->addWidget( m_startAtLineLabel, 0, 3 );
259
260 TQSpacerItem* spacer_2 = new TQSpacerItem( 0, 0, TQSizePolicy::Minimum, TQSizePolicy::Preferred );
261 glyr->addItem( spacer_2, 0, 4 );
262
263 m_ignoreDuplicates = new TQCheckBox( page, "m_ignoreDuplicates" );
264 m_ignoreDuplicates->setText( i18n( "Ignore duplicated delimiters" ) );
265 glyr->addMultiCellWidget( m_ignoreDuplicates, 2, 2, 2, 4 );
266
267 m_1stRowForFieldNames = new TQCheckBox( page, "m_1stRowForFieldNames" );
268 m_1stRowForFieldNames->setText( i18n( "First row contains column names" ) );
269 glyr->addMultiCellWidget( m_1stRowForFieldNames, 3, 3, 2, 4 );
270
271 m_table = new KexiCSVImportDialogTable( plainPage(), "m_table" );
272 lyr->addWidget( m_table );
273
274 m_table->setSizePolicy( TQSizePolicy(TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding, 1, 1) );
275 m_table->setNumRows( 0 );
276 m_table->setNumCols( 0 );
277
279/*
280if ( m_mode == Clipboard )
281 {
282 setCaption( i18n( "Inserting From Clipboard" ) );
283 TQMimeSource * mime = TQApplication::clipboard()->data();
284 if ( !mime )
285 {
286 KMessageBox::information( this, i18n("There is no data in the clipboard.") );
287 m_cancelled = true;
288 return;
289 }
290
291 if ( !mime->provides( "text/plain" ) )
292 {
293 KMessageBox::information( this, i18n("There is no usable data in the clipboard.") );
294 m_cancelled = true;
295 return;
296 }
297 m_fileArray = TQByteArray(mime->encodedData( "text/plain" ) );
298 }
299 else if ( mode == File )
300 {*/
301 m_dateRegExp = TQRegExp("(\\d{1,4})([/\\-\\.])(\\d{1,2})([/\\-\\.])(\\d{1,4})");
302 m_timeRegExp1 = TQRegExp("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})");
303 m_timeRegExp2 = TQRegExp("(\\d{1,2}):(\\d{1,2})");
304 m_fpNumberRegExp = TQRegExp("[\\-]{0,1}\\d*[,\\.]\\d+");
305 TQString caption( i18n("Open CSV Data File") );
306
307 if (m_mode == File) {
308 TQStringList mimetypes( csvMimeTypes() );
309#ifdef TQ_WS_WIN
311 TQString recentDir = TDEGlobalSettings::documentPath();
312 m_fname = TQFileDialog::getOpenFileName(
313 KFileDialog::getStartURL(":CSVImportExport", recentDir).path(),
314 KexiUtils::fileDialogFilterStrings(mimetypes, false),
315 page, "KexiCSVImportDialog", caption);
316 if ( !m_fname.isEmpty() ) {
317 //save last visited path
318 KURL url;
319 url.setPath( m_fname );
320 if (url.isLocalFile())
321 TDERecentDirs::add(":CSVImportExport", url.directory());
322 }
323#else
324 m_fname = KFileDialog::getOpenFileName(":CSVImportExport", mimetypes.join(" "),
325 this, caption);
326#endif
327 //cancel action !
328 if ( m_fname.isEmpty() )
329 {
330 actionButton( Ok )->setEnabled( false );
331 m_cancelled = true;
332 if (parentWidget())
333 parentWidget()->raise();
334 return;
335 }
336 }
337 else if (m_mode == Clipboard) {
338 TQCString subtype("plain");
339 m_clipboardData = TQApplication::clipboard()->text(subtype, TQClipboard::Clipboard);
340/* debug
341 for (int i=0;TQApplication::clipboard()->data(TQClipboard::Clipboard)->format(i);i++)
342 kdDebug() << i << ": "
343 << TQApplication::clipboard()->data(TQClipboard::Clipboard)->format(i) << endl;
344*/
345 }
346 else {
347 return;
348 }
349
350 m_loadingProgressDlg = 0;
351 m_importingProgressDlg = 0;
352 if (m_mode == File) {
353 m_loadingProgressDlg = new KProgressDialog(
354 this, "m_loadingProgressDlg", i18n("Loading CSV Data"), i18n("Loading CSV Data from \"%1\"...")
355 .arg(TQDir::convertSeparators(m_fname)), true);
356 m_loadingProgressDlg->progressBar()->setTotalSteps( m_maximumRowsForPreview+1 );
357 m_loadingProgressDlg->show();
358 }
359
360 if (m_mode==Clipboard) {
361 m_infoLbl->setIcon("edit-paste");
362 }
363 //updateRowCountInfo();
364
365 m_table->setSelectionMode(TQTable::NoSelection);
366
367 connect(m_formatCombo, TQ_SIGNAL(activated(int)),
368 this, TQ_SLOT(formatChanged(int)));
369 connect(m_delimiterWidget, TQ_SIGNAL(delimiterChanged(const TQString&)),
370 this, TQ_SLOT(delimiterChanged(const TQString&)));
371 connect(m_startAtLineSpinBox, TQ_SIGNAL(valueChanged ( int )),
372 this, TQ_SLOT(startlineSelected(int)));
373 connect(m_comboQuote, TQ_SIGNAL(activated(int)),
374 this, TQ_SLOT(textquoteSelected(int)));
375 connect(m_table, TQ_SIGNAL(currentChanged(int, int)),
376 this, TQ_SLOT(currentCellChanged(int, int)));
377 connect(m_table, TQ_SIGNAL(valueChanged(int,int)),
378 this, TQ_SLOT(cellValueChanged(int,int)));
379 connect(m_ignoreDuplicates, TQ_SIGNAL(stateChanged(int)),
380 this, TQ_SLOT(ignoreDuplicatesChanged(int)));
381 connect(m_1stRowForFieldNames, TQ_SIGNAL(stateChanged(int)),
382 this, TQ_SLOT(slot1stRowForFieldNamesChanged(int)));
383
384 connect(this, TQ_SIGNAL(user1Clicked()), this, TQ_SLOT(optionsButtonClicked()));
385
386 installRecursiveEventFilter(this, this);
387
388 initLater();
389}
390
391KexiCSVImportDialog::~KexiCSVImportDialog()
392{
393 delete m_file;
394}
395
396void KexiCSVImportDialog::initLater()
397{
398 if (!openData())
399 return;
400
401// delimiterChanged(detectedDelimiter); // this will cause fillTable()
402 m_columnsAdjusted = false;
403 fillTable();
404 delete m_loadingProgressDlg;
405 m_loadingProgressDlg = 0;
406 if (m_dialogCancelled) {
407// m_loadingProgressDlg->hide();
408 // m_loadingProgressDlg->close();
409 TQTimer::singleShot(0, this, TQ_SLOT(reject()));
410 return;
411 }
412
413 currentCellChanged(0, 0);
414
415// updateGeometry();
416 adjustSize();
417 KDialog::centerOnScreen( this );
418
419 if (m_loadingProgressDlg)
420 m_loadingProgressDlg->hide();
421 show();
422 m_table->setFocus();
423}
424
425bool KexiCSVImportDialog::openData()
426{
427 if (m_mode!=File) //data already loaded, no encoding stuff needed
428 return true;
429
430 delete m_inputStream;
431 m_inputStream = 0;
432 if (m_file) {
433 m_file->close();
434 delete m_file;
435 }
436 m_file = new TQFile(m_fname);
437 if (!m_file->open(IO_ReadOnly))
438 {
439 m_file->close();
440 delete m_file;
441 m_file = 0;
442 KMessageBox::sorry( this, i18n("Cannot open input file <nobr>\"%1\"</nobr>.")
443 .arg(TQDir::convertSeparators(m_fname)) );
444 actionButton( Ok )->setEnabled( false );
445 m_cancelled = true;
446 if (parentWidget())
447 parentWidget()->raise();
448 return false;
449 }
450 return true;
451}
452
453bool KexiCSVImportDialog::cancelled() const
454{
455 return m_cancelled;
456}
457
458void KexiCSVImportDialog::fillTable()
459{
460 KexiUtils::WaitCursor wc(true);
461 repaint();
462 m_blockUserEvents = true;
463 TQPushButton *pb = actionButton(KDialogBase::Cancel);
464 if (pb)
465 pb->setEnabled(true); //allow to cancel
467
468 if (m_table->numRows()>0) //to accept editor
469 m_table->setCurrentCell(0,0);
470
471 int row, column, maxColumn;
472 TQString field = TQString();
473
474 for (row = 0; row < m_table->numRows(); ++row)
475 for (column = 0; column < m_table->numCols(); ++column)
476 m_table->clearCell(row, column);
477
478 m_detectedTypes.clear();
479 m_detectedTypes.resize(1024, _NO_TYPE_YET);//_TEXT_TYPE);
480 m_uniquenessTest.clear();
481 m_uniquenessTest.resize(1024);
482 m_1stRowForFieldNamesDetected = true;
483
484 if (true != loadRows(field, row, column, maxColumn, true))
485 return;
486
487 m_1stRowForFieldNamesDetected = false;
488
489 // file with only one line without '\n'
490 if (field.length() > 0)
491 {
492 setText(row - m_startline, column, field, true);
493 ++row;
494 field = TQString();
495 }
496
497 adjustRows( row - m_startline - (m_1stRowForFieldNames->isChecked()?1:0) );
498
499 maxColumn = TQMAX( maxColumn, column );
500 m_table->setNumCols(maxColumn);
501
502 for (column = 0; column < m_table->numCols(); ++column)
503 {
504// TQString header = m_table->horizontalHeader()->label(column);
505// if (header != i18n("Text") && header != i18n("Number") &&
506// header != i18n("Date") && header != i18n("Currency"))
507// const int detectedType = m_detectedTypes[column+1];
508// m_table->horizontalHeader()->setLabel(column, m_typeNames[ detectedType ]); //i18n("Text"));
509 updateColumnText(column);
510 if (!m_columnsAdjusted)
511 m_table->adjustColumn(column);
512 }
513 m_columnsAdjusted = true;
514
515 if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) {
516 if (_NUMBER_TYPE != m_detectedTypes[ m_primaryKeyColumn ]) {
517 m_primaryKeyColumn = -1;
518 }
519 }
520
521 m_prevSelectedCol = -1;
522 m_table->setCurrentCell(0,0);
523 currentCellChanged(0, 0);
524 if (m_primaryKeyColumn != -1)
525 m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon);
526
527 const int count = TQMAX(0, m_table->numRows()-1+m_startline);
528 m_allRowsLoadedInPreview = count < m_maximumRowsForPreview && !m_stoppedAt_MAX_BYTES_TO_PREVIEW;
529 if (m_allRowsLoadedInPreview) {
530 m_startAtLineSpinBox->setMaxValue(count);
531 m_startAtLineSpinBox->setValue(m_startline+1);
532 }
533 m_startAtLineLabel->setText(i18n( "Start at line%1:").arg(
534 m_allRowsLoadedInPreview ? TQString(" (1-%1)").arg(count)
535 : TQString() //we do not know what's real count
536 ));
537 updateRowCountInfo();
538
539 m_blockUserEvents = false;
540 repaint();
541 m_table->verticalScrollBar()->repaint();//avoid missing repaint
542 m_table->horizontalScrollBar()->repaint();//avoid missing repaint
543}
544
545TQString KexiCSVImportDialog::detectDelimiterByLookingAtFirstBytesOfFile(TQTextStream& inputStream)
546{
547 m_file->at(0);
548
549 // try to detect delimiter
550 // \t has priority, then ; then ,
551 const TQIODevice::Offset origOffset = inputStream.device()->at();
552 TQChar c, prevChar=0;
553 int detectedDelimiter = 0;
554 bool insideQuote = false;
555
556 //characters by priority
557 const int CH_TAB_AFTER_QUOTE = 500;
558 const int CH_SEMICOLON_AFTER_QUOTE = 499;
559 const int CH_COMMA_AFTER_QUOTE = 498;
560 const int CH_TAB = 200; // \t
561 const int CH_SEMICOLON = 199; // ;
562 const int CH_COMMA = 198; // ,
563
564 TQValueList<int> tabsPerLine, semicolonsPerLine, commasPerLine;
565 int tabs = 0, semicolons = 0, commas = 0;
566 int line = 0;
567 for (uint i=0; !inputStream.atEnd() && i < MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER; i++) {
568 (*m_inputStream) >> c; // read one char
569 if (prevChar=='"') {
570 if (c!='"') //real quote (not double "")
571 insideQuote = !insideQuote;
572 }
573 if (insideQuote) {
574 prevChar = c;
575 continue;
576 }
577 if (c==' ')
578 continue;
579 if (c=='\n') {//end of line
580 //remember # of tabs/semicolons/commas in this line
581 tabsPerLine += tabs;
582 tabs = 0;
583 semicolonsPerLine += semicolons;
584 semicolons = 0;
585 commasPerLine += commas;
586 commas = 0;
587 line++;
588 }
589 else if (c=='\t') {
590 tabs++;
591 detectedDelimiter = TQMAX( prevChar=='"' ? CH_TAB_AFTER_QUOTE : CH_TAB, detectedDelimiter );
592 }
593 else if (c==';') {
594 semicolons++;
595 detectedDelimiter = TQMAX( prevChar=='"' ? CH_SEMICOLON_AFTER_QUOTE : CH_SEMICOLON, detectedDelimiter );
596 }
597 else if (c==',') {
598 commas++;
599 detectedDelimiter = TQMAX( prevChar=='"' ? CH_COMMA_AFTER_QUOTE : CH_COMMA, detectedDelimiter );
600 }
601 prevChar = c;
602 }
603
604 inputStream.device()->at(origOffset); //restore orig. offset
605
606 //now, try to find a delimiter character that exists the same number of times in all the checked lines
607 //this detection method has priority over others
608 TQValueList<int>::ConstIterator it;
609 if (tabsPerLine.count()>1) {
610 tabs = tabsPerLine.isEmpty() ? 0 : tabsPerLine.first();
611 for (it=tabsPerLine.constBegin(); it!=tabsPerLine.constEnd(); ++it) {
612 if (tabs != *it)
613 break;
614 }
615 if (tabs>0 && it==tabsPerLine.constEnd())
616 return "\t";
617 }
618 if (semicolonsPerLine.count()>1) {
619 semicolons = semicolonsPerLine.isEmpty() ? 0 : semicolonsPerLine.first();
620 for (it=semicolonsPerLine.constBegin(); it!=semicolonsPerLine.constEnd(); ++it) {
621 if (semicolons != *it)
622 break;
623 }
624 if (semicolons > 0 && it==semicolonsPerLine.constEnd())
625 return ";";
626 }
627 if (commasPerLine.count()>1) {
628 commas = commasPerLine.first();
629 for (it=commasPerLine.constBegin(); it!=commasPerLine.constEnd(); ++it) {
630 if (commas != *it)
631 break;
632 }
633 if (commas > 0 && it==commasPerLine.constEnd())
634 return ",";
635 }
636 //now return the winning character by looking at CH_* symbol
637 if (detectedDelimiter == CH_TAB_AFTER_QUOTE || detectedDelimiter == CH_TAB)
638 return "\t";
639 if (detectedDelimiter == CH_SEMICOLON_AFTER_QUOTE || detectedDelimiter == CH_SEMICOLON)
640 return ";";
641 if (detectedDelimiter == CH_COMMA_AFTER_QUOTE || detectedDelimiter == CH_COMMA)
642 return ",";
643
644 return KEXICSV_DEFAULT_FILE_DELIMITER; //<-- default
645}
646
647tristate KexiCSVImportDialog::loadRows(TQString &field, int &row, int &column, int &maxColumn,
648 bool inGUI)
649{
650 enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
651 S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;
652 field = TQString();
653 const bool ignoreDups = m_ignoreDuplicates->isChecked();
654 bool lastCharDelimiter = false;
655 bool nextRow = false;
656 row = column = 1;
657 maxColumn = 0;
658 TQChar x;
659 const bool hadInputStream = m_inputStream!=0;
660 delete m_inputStream;
661 if ( m_mode == Clipboard ) {
662 m_inputStream = new TQTextStream(m_clipboardData, IO_ReadOnly);
663 if (!hadInputStream)
664 m_delimiterWidget->setDelimiter(KEXICSV_DEFAULT_CLIPBOARD_DELIMITER);
665 }
666 else {
667 m_file->at(0); //always seek at 0 because loadRows() is called many times
668 m_inputStream = new TQTextStream(m_file);
669 if (m_options.defaultEncodingExplicitySet) {
670 TQTextCodec *codec = TDEGlobal::charsets()->codecForName(m_options.encoding);
671 if (codec)
672 m_inputStream->setCodec(codec); //TQTextCodec::codecForName("CP1250"));
673 }
674 if (m_detectDelimiter) {
675 const TQString delimiter( detectDelimiterByLookingAtFirstBytesOfFile(*m_inputStream) );
676 if (m_delimiterWidget->delimiter() != delimiter)
677 m_delimiterWidget->setDelimiter( delimiter );
678 }
679 }
680 const TQChar delimiter(m_delimiterWidget->delimiter()[0]);
681 m_stoppedAt_MAX_BYTES_TO_PREVIEW = false;
682 int progressStep = 0;
683 if (m_importingProgressDlg)
684 progressStep = TQMAX( 1, m_importingProgressDlg->progressBar()->totalSteps()/200 );
685 int offset = 0;
686 for (;!m_inputStream->atEnd(); offset++)
687 {
688//disabled: this breaks wide spreadsheets
689// if (column >= m_maximumRowsForPreview)
690// return true;
691
692 if (m_importingProgressDlg && ((offset % progressStep) < 5)) {
693 //update progr. bar dlg on final exporting
694 m_importingProgressDlg->progressBar()->setValue(offset);
695 tqApp->processEvents();
696 if (m_importingProgressDlg->wasCancelled()) {
697 delete m_importingProgressDlg;
698 m_importingProgressDlg = 0;
699 return ::cancelled;
700 }
701 }
702
703 (*m_inputStream) >> x; // read one char
704
705 if (x == '\r') {
706 continue; // eat '\r', to handle RFC-compliant files
707 }
708 if (offset==0 && x.unicode()==0xfeff) {
709 // Ignore BOM, the "Byte Order Mark"
710 // (http://en.wikipedia.org/wiki/Byte_Order_Mark, // http://www.unicode.org/charts/PDF/UFFF0.pdf)
711 continue;
712 }
713
714 switch (state)
715 {
716 case S_START :
717 if (x == m_textquote)
718 {
719 state = S_QUOTED_FIELD;
720 }
721 else if (x == delimiter)
722 {
723 setText(row - m_startline, column, field, inGUI);
724 field = TQString();
725 if ((ignoreDups == false) || (lastCharDelimiter == false))
726 ++column;
727 lastCharDelimiter = true;
728 }
729 else if (x == '\n')
730 {
731 if (!inGUI) {
732 //fill remaining empty fields (database wants them explicitly)
733 for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) {
734 setText(row - m_startline, additionalColumn, TQString(), inGUI);
735 }
736 }
737 nextRow = true;
738 maxColumn = TQMAX( maxColumn, column );
739 column = 1;
740 }
741 else
742 {
743 field += x;
744 state = S_MAYBE_NORMAL_FIELD;
745 }
746 break;
747 case S_QUOTED_FIELD :
748 if (x == m_textquote)
749 {
750 state = S_MAYBE_END_OF_QUOTED_FIELD;
751 }
752/*allow \n inside quoted fields
753 else if (x == '\n')
754 {
755 setText(row - m_startline, column, field, inGUI);
756 field = "";
757 if (x == '\n')
758 {
759 nextRow = true;
760 maxColumn = TQMAX( maxColumn, column );
761 column = 1;
762 }
763 else
764 {
765 if ((ignoreDups == false) || (lastCharDelimiter == false))
766 ++column;
767 lastCharDelimiter = true;
768 }
769 state = S_START;
770 }*/
771 else
772 {
773 field += x;
774 }
775 break;
776 case S_MAYBE_END_OF_QUOTED_FIELD :
777 if (x == m_textquote)
778 {
779 field += x; //no, this was just escaped quote character
780 state = S_QUOTED_FIELD;
781 }
782 else if (x == delimiter || x == '\n')
783 {
784 setText(row - m_startline, column, field, inGUI);
785 field = TQString();
786 if (x == '\n')
787 {
788 nextRow = true;
789 maxColumn = TQMAX( maxColumn, column );
790 column = 1;
791 }
792 else
793 {
794 if ((ignoreDups == false) || (lastCharDelimiter == false))
795 ++column;
796 lastCharDelimiter = true;
797 }
798 state = S_START;
799 }
800 else
801 {
802 state = S_END_OF_QUOTED_FIELD;
803 }
804 break;
805 case S_END_OF_QUOTED_FIELD :
806 if (x == delimiter || x == '\n')
807 {
808 setText(row - m_startline, column, field, inGUI);
809 field = TQString();
810 if (x == '\n')
811 {
812 nextRow = true;
813 maxColumn = TQMAX( maxColumn, column );
814 column = 1;
815 }
816 else
817 {
818 if ((ignoreDups == false) || (lastCharDelimiter == false))
819 ++column;
820 lastCharDelimiter = true;
821 }
822 state = S_START;
823 }
824 else
825 {
826 state = S_END_OF_QUOTED_FIELD;
827 }
828 break;
829 case S_MAYBE_NORMAL_FIELD :
830 if (x == m_textquote)
831 {
832 field = TQString();
833 state = S_QUOTED_FIELD;
834 break;
835 }
836 case S_NORMAL_FIELD :
837 if (x == delimiter || x == '\n')
838 {
839 setText(row - m_startline, column, field, inGUI);
840 field = TQString();
841 if (x == '\n')
842 {
843 nextRow = true;
844 maxColumn = TQMAX( maxColumn, column );
845 column = 1;
846 }
847 else
848 {
849 if ((ignoreDups == false) || (lastCharDelimiter == false))
850 ++column;
851 lastCharDelimiter = true;
852 }
853 state = S_START;
854 }
855 else
856 {
857 field += x;
858 }
859 }
860 if (x != delimiter)
861 lastCharDelimiter = false;
862
863 if (nextRow) {
864 if (!inGUI && row==1 && m_1stRowForFieldNames->isChecked()) {
865 // do not save to the database 1st row if it contains column names
866 m_importingStatement->clearArguments();
867 }
868 else if (!saveRow(inGUI))
869 return false;
870
871 ++row;
872 }
873
874 if (m_firstFillTableCall && row==2
875 && !m_1stRowForFieldNames->isChecked() && m_1stRowForFieldNamesDetected)
876 {
877 //'1st row for field name' flag detected: reload table
878 m_1stRowForFieldNamesDetected = false;
879 m_table->setNumRows( 0 );
880 m_firstFillTableCall = false; //this trick is allowed only once, on startup
881 m_1stRowForFieldNames->setChecked(true); //this will reload table
882 //slot1stRowForFieldNamesChanged(1);
883 m_blockUserEvents = false;
884 repaint();
885 return false;
886 }
887
888 if (!m_importingProgressDlg && row % 20 == 0) {
889 tqApp->processEvents();
890 //only for GUI mode:
891 if (!m_firstFillTableCall && m_loadingProgressDlg && m_loadingProgressDlg->wasCancelled()) {
892 delete m_loadingProgressDlg;
893 m_loadingProgressDlg = 0;
894 m_dialogCancelled = true;
895 reject();
896 return false;
897 }
898 }
899
900 if (!m_firstFillTableCall && m_loadingProgressDlg) {
901 m_loadingProgressDlg->progressBar()->setValue(TQMIN(m_maximumRowsForPreview, row));
902 }
903
904 if ( inGUI && row > (m_maximumRowsForPreview + (m_1stRowForFieldNamesDetected?1:0)) ) {
905 kexipluginsdbg << "KexiCSVImportDialog::fillTable() loading stopped at row #"
906 << m_maximumRowsForPreview << endl;
907 break;
908 }
909 if (nextRow) {
910 nextRow = false;
911 //additional speedup: stop processing now if too many bytes were loaded for preview
912 kexipluginsdbg << offset << endl;
913 if (inGUI && offset >= m_maximumBytesForPreview && row >= 2) {
914 m_stoppedAt_MAX_BYTES_TO_PREVIEW = true;
915 return true;
916 }
917 }
918 }
919 return true;
920}
921
922void KexiCSVImportDialog::updateColumnText(int col)
923{
924 TQString colName;
925 if (col<(int)m_columnNames.count() && (m_1stRowForFieldNames->isChecked() || m_changedColumnNames[col]))
926 colName = m_columnNames[ col ];
927 if (colName.isEmpty()) {
928 colName = i18n("Column %1").arg(col+1); //will be changed to a valid identifier on import
929 m_changedColumnNames[ col ] = false;
930 }
931 int detectedType = m_detectedTypes[col];
932 if (detectedType==_FP_NUMBER_TYPE)
933 detectedType=_NUMBER_TYPE; //we're simplifying that for now
934 else if (detectedType==_NO_TYPE_YET) {
935 m_detectedTypes[col]=_TEXT_TYPE; //entirely empty column
936 detectedType=_TEXT_TYPE;
937 }
938 m_table->horizontalHeader()->setLabel(col,
939 i18n("Column %1").arg(col+1) + " \n(" + m_typeNames[ detectedType ] + ") ");
940 m_table->setText(0, col, colName);
941 m_table->horizontalHeader()->adjustHeaderSize();
942
943 //check uniqueness
944 TQValueList<int> *list = m_uniquenessTest[col];
945 if (m_primaryKeyColumn==-1 && list && !list->isEmpty()) {
946 qHeapSort(*list);
947 TQValueList<int>::ConstIterator it=list->constBegin();
948 int prevValue = *it;
949 ++it;
950 for(; it!=list->constEnd() && prevValue!=(*it); ++it)
951 prevValue=(*it);
952 if (it!=list->constEnd()) {
953 //duplicates:
954 list->clear();
955 }
956 else {
957 //a candidate for PK (autodetected)!
958 if (-1==m_primaryKeyColumn) {
959 m_primaryKeyColumn=col;
960 }
961 }
962 }
963 if (list) //not needed now: conserve memory
964 list->clear();
965}
966
967void KexiCSVImportDialog::detectTypeAndUniqueness(int row, int col, const TQString& text)
968{
969 int intValue;
970 const int type = m_detectedTypes[col];
971 if (row==1 || type!=_TEXT_TYPE) {
972 bool found = false;
973 if (text.isEmpty() && type==_NO_TYPE_YET)
974 found = true; //real type should be found later
975 //detect type because it's 1st row or all prev. rows were not text
976 //-FP number? (trying before "number" type is a must)
977 if (!found && (row==1 || type==_NUMBER_TYPE || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) {
978 bool ok = text.isEmpty() || m_fpNumberRegExp.exactMatch(text);
979 //if (!ok)
980 // text.toDouble(&ok);
981 if (ok && (row==1 || type==_NUMBER_TYPE || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) {
982 m_detectedTypes[col]=_FP_NUMBER_TYPE;
983 found = true; //yes
984 }
985 }
986 //-number?
987 if (!found && (row==1 || type==_NUMBER_TYPE || type==_NO_TYPE_YET)) {
988 bool ok = text.isEmpty();//empty values allowed
989 if (!ok)
990 intValue = text.toInt(&ok);
991 if (ok && (row==1 || type==_NO_TYPE_YET)) {
992 m_detectedTypes[col]=_NUMBER_TYPE;
993 found = true; //yes
994 }
995 }
996 //-date?
997 if (!found && (row==1 || type==_DATE_TYPE || type==_NO_TYPE_YET)) {
998 if ((row==1 || type==_NO_TYPE_YET)
999 && (text.isEmpty() || m_dateRegExp.exactMatch(text)))
1000 {
1001 m_detectedTypes[col]=_DATE_TYPE;
1002 found = true; //yes
1003 }
1004 }
1005 //-time?
1006 if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) {
1007 if ((row==1 || type==_NO_TYPE_YET)
1008 && (text.isEmpty() || m_timeRegExp1.exactMatch(text) || m_timeRegExp2.exactMatch(text)))
1009 {
1010 m_detectedTypes[col]=_TIME_TYPE;
1011 found = true; //yes
1012 }
1013 }
1014 //-date/time?
1015 if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) {
1016 if (row==1 || type==_NO_TYPE_YET) {
1017 bool detected = text.isEmpty();
1018 if (!detected) {
1019 const TQStringList dateTimeList( TQStringList::split(" ", text) );
1020 bool ok = dateTimeList.count()>=2;
1023 if (ok) {
1024 //try all combinations
1025 TQString datePart( dateTimeList[0].stripWhiteSpace() );
1026 TQString timePart( dateTimeList[1].stripWhiteSpace() );
1027 ok = m_dateRegExp.exactMatch(datePart)
1028 && (m_timeRegExp1.exactMatch(timePart) || m_timeRegExp2.exactMatch(timePart));
1029 }
1030 detected = ok;
1031 }
1032 if (detected) {
1033 m_detectedTypes[col]=_DATETIME_TYPE;
1034 found = true; //yes
1035 }
1036 }
1037 }
1038 if (!found && type==_NO_TYPE_YET && !text.isEmpty()) {
1039 //eventually, a non-emptytext after a while
1040 m_detectedTypes[col]=_TEXT_TYPE;
1041 found = true; //yes
1042 }
1043 //default: text type (already set)
1044 }
1045 //check uniqueness for this value
1046 TQValueList<int> *list = m_uniquenessTest[col];
1047 if (row==1 && (!list || !list->isEmpty()) && !text.isEmpty() && _NUMBER_TYPE == m_detectedTypes[col]) {
1048 if (!list) {
1049 list = new TQValueList<int>();
1050 m_uniquenessTest.insert(col, list);
1051 }
1052 list->append( intValue );
1053 }
1054 else {
1055 //the value is empty or uniqueness test failed in the past
1056 if (list && !list->isEmpty())
1057 list->clear(); //indicate that uniqueness test failed
1058 }
1059}
1060
1061bool KexiCSVImportDialog::parseDate(const TQString& text, TQDate& date)
1062{
1063 if (!m_dateRegExp.exactMatch(text))
1064 return false;
1065 //dddd - dd - dddd
1066 //1 2 3 4 5 <- pos
1067 const int d1 = m_dateRegExp.cap(1).toInt(), d3 = m_dateRegExp.cap(3).toInt(), d5 = m_dateRegExp.cap(5).toInt();
1068 if (m_dateRegExp.cap(2)=="/") //probably separator for american format mm/dd/yyyy
1069 date = TQDate(d5, d1, d3);
1070 else {
1071 if (d5 > 31) //d5 == year
1072 date = TQDate(d5, d3, d1);
1073 else //d1 == year
1074 date = TQDate(d1, d3, d5);
1075 }
1076 return date.isValid();
1077}
1078
1079bool KexiCSVImportDialog::parseTime(const TQString& text, TQTime& time)
1080{
1081 time = TQTime::fromString(text, TQt::ISODate); //same as m_timeRegExp1
1082 if (time.isValid())
1083 return true;
1084 if (m_timeRegExp2.exactMatch(text)) { //hh:mm:ss
1085 time = TQTime(m_timeRegExp2.cap(1).toInt(), m_timeRegExp2.cap(3).toInt(), m_timeRegExp2.cap(5).toInt());
1086 return true;
1087 }
1088 return false;
1089}
1090
1091void KexiCSVImportDialog::setText(int row, int col, const TQString& text, bool inGUI)
1092{
1093 if (!inGUI) {
1094 //save text directly to database buffer
1095 if (col==1) { //1st col
1096 m_importingStatement->clearArguments();
1097 if (m_implicitPrimaryKeyAdded)
1098 *m_importingStatement << TQVariant(); //id will be autogenerated here
1099 }
1100 const int detectedType = m_detectedTypes[col-1];
1101 if (detectedType==_NUMBER_TYPE) {
1102 *m_importingStatement << ( text.isEmpty() ? TQVariant() : text.toInt() );
1104 }
1105 else if (detectedType==_FP_NUMBER_TYPE) {
1106 //replace ',' with '.'
1107 TQCString t(text.latin1());
1108 const int textLen = t.length();
1109 for (int i=0; i<textLen; i++) {
1110 if (t.at(i)==',') {
1111 t.at(i) = '.';
1112 break;
1113 }
1114 }
1115 *m_importingStatement << ( t.isEmpty() ? TQVariant() : t.toDouble() );
1116 }
1117 else if (detectedType==_DATE_TYPE) {
1118 TQDate date;
1119 if (parseDate(text, date))
1120 *m_importingStatement << date;
1121 }
1122 else if (detectedType==_TIME_TYPE) {
1123 TQTime time;
1124 if (parseTime(text, time))
1125 *m_importingStatement << time;
1126 }
1127 else if (detectedType==_DATETIME_TYPE) {
1128 TQStringList dateTimeList( TQStringList::split(" ", text) );
1129 if (dateTimeList.count()<2)
1130 dateTimeList = TQStringList::split("T", text); //also support ISODateTime's "T" separator
1132 if (dateTimeList.count()>=2) {
1133 TQString datePart( dateTimeList[0].stripWhiteSpace() );
1134 TQDate date;
1135 if (parseDate(datePart, date)) {
1136 TQString timePart( dateTimeList[1].stripWhiteSpace() );
1137 TQTime time;
1138 if (parseTime(timePart, time))
1139 *m_importingStatement << TQDateTime(date, time);
1140 }
1141 }
1142 }
1143 else //_TEXT_TYPE and the rest
1144 *m_importingStatement << (m_options.stripWhiteSpaceInTextValuesChecked ? text.stripWhiteSpace() : text);
1145 return;
1146 }
1147 //save text to GUI (table view)
1148 if (m_table->numCols() < col) {
1149 m_table->setNumCols(col);
1150 if ((int)m_columnNames.size() < m_table->numCols()) {
1151 m_columnNames.resize(m_table->numCols()+10);
1152 m_changedColumnNames.resize(m_table->numCols()+10);
1153 }
1154 }
1155
1156 if (m_1stRowForFieldNames->isChecked()) {
1157 if ((row+m_startline)==1) {//this is for column name
1158 if ((col-1) < (int)m_changedColumnNames.size() && (int)false==(int)m_changedColumnNames[col-1]) {
1159 //this column has no custom name entered by a user
1160 //-get the name from the data cell
1161 TQString colName(text.simplifyWhiteSpace());
1162 if (!colName.isEmpty()) {
1163 if (colName.left(1)>="0" && colName.left(1)<="9")
1164 colName.prepend(i18n("Column")+" ");
1165 m_columnNames[ col-1 ] = colName;
1166 }
1167 }
1168 return;
1169 }
1170 }
1171 else {
1172 if ((row+m_startline)==1) {//this row is for column name
1173 if (m_1stRowForFieldNamesDetected && !m_1stRowForFieldNames->isChecked()) {
1174 TQString f( text.simplifyWhiteSpace() );
1175 if (f.isEmpty() || !f[0].isLetter())
1176 m_1stRowForFieldNamesDetected = false; //this couldn't be a column name
1177 }
1178 }
1179 row++; //1st row was for column names
1180 }
1181
1182 if (row < 2) // skipped by the user
1183 return;
1184
1185 if (m_table->numRows() < row) {
1186// if (m_maximumRowsForPreview >= row+100)
1187 m_table->setNumRows(row+100); /* We add more rows at a time to limit recalculations */
1188 //else
1189// m_table->setNumRows(m_maximumRowsForPreview);
1190 m_table->verticalHeader()->setLabel(0, i18n("Column name")+" ");
1191 m_adjustRows=true;
1192 }
1193
1194 m_table->setText(row - 1, col - 1, (m_options.stripWhiteSpaceInTextValuesChecked ? text.stripWhiteSpace() : text));
1195 m_table->verticalHeader()->setLabel(row-1, TQString::number(row-1));
1196
1197 detectTypeAndUniqueness(row-1, col-1, text);
1198}
1199
1200bool KexiCSVImportDialog::saveRow(bool inGUI)
1201{
1202 if (inGUI) {
1203 //nothing to do
1204 return true;
1205 }
1206 //save db buffer
1207 bool res = m_importingStatement->execute();
1208//todo: move
1209 m_importingStatement->clearArguments();
1210 return res;
1211// return m_conn->insertRecord(*m_destinationTableSchema, m_dbRowBuffer);
1212}
1213
1214void KexiCSVImportDialog::adjustRows(int iRows)
1215{
1216 if (m_adjustRows)
1217 {
1218 m_table->setNumRows( iRows );
1219 m_adjustRows=false;
1220 for (int i = 0; i<iRows; i++)
1221 m_table->adjustRow(i);
1222 }
1223}
1224
1225void KexiCSVImportDialog::formatChanged(int id)
1226{
1227 if (id==_PK_FLAG) {
1228 if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) {
1229 m_table->setPixmap(0, m_primaryKeyColumn, TQPixmap());
1230 }
1231 if (m_primaryKeyField->isChecked()) {
1232 m_primaryKeyColumn = m_table->currentColumn();
1233 m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon);
1234 }
1235 else
1236 m_primaryKeyColumn = -1;
1237 return;
1238 }
1239 else {
1240 m_detectedTypes[m_table->currentColumn()]=id;
1241 m_primaryKeyField->setEnabled( _NUMBER_TYPE == id );
1242 m_primaryKeyField->setChecked( m_primaryKeyColumn == m_table->currentColumn() && m_primaryKeyField->isEnabled() );
1243 }
1244 updateColumnText(m_table->currentColumn());
1245}
1246
1247void KexiCSVImportDialog::delimiterChanged(const TQString& delimiter)
1248{
1249 Q_UNUSED(delimiter);
1250 m_columnsAdjusted = false;
1251 m_detectDelimiter = false; //selected by hand: do not detect in the future
1252 //delayed, otherwise combobox won't be repainted
1253 fillTableLater();
1254}
1255
1256void KexiCSVImportDialog::textquoteSelected(int)
1257{
1258 const TQString tq(m_comboQuote->textQuote());
1259 if (tq.isEmpty())
1260 m_textquote = 0;
1261 else
1262 m_textquote = tq[0];
1263
1264 kexipluginsdbg << "KexiCSVImportDialog::textquoteSelected(): " << m_textquote << endl;
1265
1266 //delayed, otherwise combobox won't be repainted
1267 fillTableLater();
1268}
1269
1270void KexiCSVImportDialog::fillTableLater()
1271{
1272 m_table->setNumRows( 0 );
1273 TQTimer::singleShot(10, this, TQ_SLOT(fillTable()));
1274}
1275
1276void KexiCSVImportDialog::startlineSelected(int startline)
1277{
1278// const int startline = line.toInt() - 1;
1279 if (m_startline == (startline-1))
1280 return;
1281 m_startline = startline-1;
1282 m_adjustRows=true;
1283 fillTable();
1284 m_table->setFocus();
1285}
1286
1287void KexiCSVImportDialog::currentCellChanged(int, int col)
1288{
1289 if (m_prevSelectedCol==col)
1290 return;
1291 m_prevSelectedCol = col;
1292 int type = m_detectedTypes[col];
1293 if (type==_FP_NUMBER_TYPE)
1294 type=_NUMBER_TYPE; //we're simplifying that for now
1295
1296 m_formatCombo->setCurrentItem( type );
1297 m_formatLabel->setText( m_formatComboText.arg(col+1) );
1298 m_primaryKeyField->setEnabled( _NUMBER_TYPE == m_detectedTypes[col]);
1299 m_primaryKeyField->blockSignals(true); //block to disable executing slotPrimaryKeyFieldToggled()
1300 m_primaryKeyField->setChecked( m_primaryKeyColumn == col );
1301 m_primaryKeyField->blockSignals(false);
1302}
1303
1304void KexiCSVImportDialog::cellValueChanged(int row,int col)
1305{
1306 if (row==0) {//column name has changed
1307 m_columnNames[ col ] = m_table->text(row, col);
1308 m_changedColumnNames.setBit( col );
1309 }
1310}
1311
1313{
1315
1316 KexiGUIMessageHandler msg;
1317
1318 const uint numRows( m_table->numRows() );
1319 if (numRows == 0)
1320 return; //impossible
1321
1322 if (numRows == 1) {
1323 if (KMessageBox::No == KMessageBox::questionYesNo(this,
1324 i18n("Data set contains no rows. Do you want to import empty table?")))
1325 return;
1326 }
1327
1328 KexiProject* project = m_mainWin->project();
1329 if (!project) {
1330 msg.showErrorMessage(i18n("No project available."));
1331 return;
1332 }
1333 m_conn = project->dbConnection(); //cache this pointer
1334 if (!m_conn) {
1335 msg.showErrorMessage(i18n("No database connection available."));
1336 return;
1337 }
1338 KexiPart::Part *part = Kexi::partManager().partForMimeType("kexi/table");
1339 if (!part) {
1340 msg.showErrorMessage(&Kexi::partManager());
1341 return;
1342 }
1343
1344 //get suggested name based on the file name
1345 TQString suggestedName;
1346 if (m_mode==File) {
1347 suggestedName = KURL::fromPathOrURL(m_fname).fileName();
1348 //remove extension
1349 if (!suggestedName.isEmpty()) {
1350 const int idx = suggestedName.findRev(".");
1351 if (idx!=-1)
1352 suggestedName = suggestedName.mid(0, idx ).simplifyWhiteSpace();
1353 }
1354 }
1355
1356 //-new part item
1357 KexiPart::Item* partItemForSavedTable = project->createPartItem(part->info(), suggestedName);
1358 if (!partItemForSavedTable) {
1359 // msg.showErrorMessage(project);
1360 return;
1361 }
1362
1363#define _ERR \
1364 { project->deleteUnstoredItem(partItemForSavedTable); \
1365 m_conn = 0; \
1366 delete m_destinationTableSchema; \
1367 m_destinationTableSchema = 0; \
1368 return; }
1369
1370 //-ask for table name/title
1371 // (THIS IS FROM KexiMainWindowImpl::saveObject())
1372 bool allowOverwriting = true;
1373 tristate res = m_mainWin->getNewObjectInfo( partItemForSavedTable, part, allowOverwriting );
1374 if (~res || !res) {
1376 _ERR;
1377 }
1378 //(allowOverwriting is now set to true, if user accepts overwriting,
1379 // and overwriting will be needed)
1380
1381// KexiDB::SchemaData sdata(part->info()->projectPartID());
1382// sdata.setName( partItem->name() );
1383
1384 //-create table schema (and thus schema object)
1385 //-assign information (THIS IS FROM KexiDialogBase::storeNewData())
1386 m_destinationTableSchema = new KexiDB::TableSchema(partItemForSavedTable->name());
1387 m_destinationTableSchema->setCaption( partItemForSavedTable->caption() );
1388 m_destinationTableSchema->setDescription( partItemForSavedTable->description() );
1389 const uint numCols( m_table->numCols() );
1390
1391 m_implicitPrimaryKeyAdded = false;
1392 //add PK if user wanted it
1393 int msgboxResult;
1394 if (m_primaryKeyColumn==-1
1395 && KMessageBox::No != (msgboxResult = KMessageBox::questionYesNoCancel(this,
1396 i18n("No Primary Key (autonumber) has been defined.\n"
1397 "Should it be automatically defined on import (recommended)?\n\n"
1398 "Note: An imported table without a Primary Key may not be editable (depending on database type)."),
1399 TQString(), KGuiItem(i18n("Add Database Primary Key to a Table", "Add Primary Key"), "key"),
1400 KGuiItem(i18n("Do Not Add Database Primary Key to a Table", "Do Not Add")))))
1401 {
1402 if (msgboxResult == KMessageBox::Cancel)
1403 _ERR; //cancel accepting
1404
1405 //add implicit PK field
1407 m_implicitPrimaryKeyAdded = true;
1408
1409 TQString fieldName("id");
1410 TQString fieldCaption("Id");
1411
1412 TQStringList colnames;
1413 for (uint col = 0; col < numCols; col++)
1414 colnames.append( m_table->text(0, col).lower().simplifyWhiteSpace() );
1415
1416 if (colnames.find(fieldName)!=colnames.end()) {
1417 int num = 1;
1418 while (colnames.find(fieldName+TQString::number(num))!=colnames.end())
1419 num++;
1420 fieldName += TQString::number(num);
1421 fieldCaption += TQString::number(num);
1422 }
1423 KexiDB::Field *field = new KexiDB::Field(
1424 fieldName,
1426 KexiDB::Field::NoConstraints,
1427 KexiDB::Field::NoOptions,
1428 0,0, //uint length=0, uint precision=0,
1429 TQVariant(), //TQVariant defaultValue=TQVariant(),
1430 fieldCaption
1431 ); //no description and width for now
1432 field->setPrimaryKey(true);
1433 field->setAutoIncrement(true);
1434 m_destinationTableSchema->addField( field );
1435 }
1436
1437 for (uint col = 0; col < numCols; col++) {
1438 TQString fieldCaption( m_table->text(0, col).simplifyWhiteSpace() );
1439 TQString fieldName( KexiUtils::string2Identifier( fieldCaption ) );
1440 if (m_destinationTableSchema->field(fieldName)) {
1441 TQString fixedFieldName;
1442 uint i = 2; //"apple 2, apple 3, etc. if there're many "apple" names
1443 do {
1444 fixedFieldName = fieldName + "_" + TQString::number(i);
1445 if (!m_destinationTableSchema->field(fixedFieldName))
1446 break;
1447 i++;
1448 } while (true);
1449 fieldName = fixedFieldName;
1450 fieldCaption += (" " + TQString::number(i));
1451 }
1452 const int detectedType = m_detectedTypes[col];
1453 KexiDB::Field::Type fieldType;
1454 if (detectedType==_DATE_TYPE)
1455 fieldType = KexiDB::Field::Date;
1456 if (detectedType==_TIME_TYPE)
1457 fieldType = KexiDB::Field::Time;
1458 if (detectedType==_DATETIME_TYPE)
1459 fieldType = KexiDB::Field::DateTime;
1460 else if (detectedType==_NUMBER_TYPE)
1461 fieldType = KexiDB::Field::Integer;
1462 else if (detectedType==_FP_NUMBER_TYPE)
1463 fieldType = KexiDB::Field::Double;
1465 else //_TEXT_TYPE and the rest
1466 fieldType = KexiDB::Field::Text;
1468
1469 KexiDB::Field *field = new KexiDB::Field(
1470 fieldName,
1471 fieldType,
1472 KexiDB::Field::NoConstraints,
1473 KexiDB::Field::NoOptions,
1474 0,0, //uint length=0, uint precision=0,
1475 TQVariant(), //TQVariant defaultValue=TQVariant(),
1476 fieldCaption
1477 ); //no description and width for now
1478
1479 if ((int)col == m_primaryKeyColumn) {
1480 field->setPrimaryKey(true);
1481 field->setAutoIncrement(true);
1482 }
1483 m_destinationTableSchema->addField( field );
1484 }
1485
1486 KexiDB::Transaction transaction = m_conn->beginTransaction();
1487 if (transaction.isNull()) {
1488 msg.showErrorMessage(m_conn);
1489 _ERR;
1490 }
1491 KexiDB::TransactionGuard tg(transaction);
1492
1493 //-create physical table
1494 if (!m_conn->createTable(m_destinationTableSchema, allowOverwriting)) {
1495 msg.showErrorMessage(m_conn);
1496 _ERR;
1497 }
1498
1499#define _DROP_DEST_TABLE_AND_RETURN \
1500 { \
1501 if (m_importingProgressDlg) \
1502 m_importingProgressDlg->hide(); \
1503 project->deleteUnstoredItem(partItemForSavedTable); \
1504 m_conn->dropTable(m_destinationTableSchema); /*alsoRemoveSchema*/ \
1505 m_destinationTableSchema = 0; \
1506 m_conn = 0; \
1507 return; \
1508 }
1509
1510 m_importingStatement = m_conn->prepareStatement(
1511 KexiDB::PreparedStatement::InsertStatement, *m_destinationTableSchema);
1512 if (!m_importingStatement) {
1513 msg.showErrorMessage(m_conn);
1514 _DROP_DEST_TABLE_AND_RETURN;
1515 }
1516
1517 if (m_file) {
1518 if (!m_importingProgressDlg) {
1519 m_importingProgressDlg = new KProgressDialog( this, "m_importingProgressDlg",
1520 i18n("Importing CSV Data"), TQString(), true );
1521 }
1522 m_importingProgressDlg->setLabel(
1523 i18n("Importing CSV Data from <nobr>\"%1\"</nobr> into \"%2\" table...")
1524 .arg(TQDir::convertSeparators(m_fname)).arg(m_destinationTableSchema->name()) );
1525 m_importingProgressDlg->progressBar()->setTotalSteps( TQFileInfo(*m_file).size() );
1526 m_importingProgressDlg->show();
1527 }
1528
1529 int row, column, maxColumn;
1530 TQString field = TQString();
1531
1532 // main job
1533 res = loadRows(field, row, column, maxColumn, false );
1534
1535 delete m_importingProgressDlg;
1536 m_importingProgressDlg = 0;
1537 if (true != res) {
1538 //importing cancelled or failed
1539 if (!res) //do not display err msg when res == cancelled
1540 msg.showErrorMessage(m_conn);
1541 _DROP_DEST_TABLE_AND_RETURN;
1542 }
1543
1544 // file with only one line without '\n'
1545 if (field.length() > 0)
1546 {
1547 setText(row - m_startline, column, field, false );
1548 //fill remaining empty fields (database wants them explicitly)
1549 for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) {
1550 setText(row - m_startline, additionalColumn, TQString(), false );
1551 }
1552 if (!saveRow(false )) {
1553 msg.showErrorMessage(m_conn);
1554 _DROP_DEST_TABLE_AND_RETURN;
1555 }
1556 ++row;
1557 field = TQString();
1558 }
1559
1560 if (!tg.commit()) {
1561 msg.showErrorMessage(m_conn);
1562 _DROP_DEST_TABLE_AND_RETURN;
1563 }
1564
1565 //-now we can store the item
1566 partItemForSavedTable->setIdentifier( m_destinationTableSchema->id() );
1567 project->addStoredItem( part->info(), partItemForSavedTable );
1568
1569 TQDialog::accept();
1570 KMessageBox::information(this, i18n("Data has been successfully imported to table \"%1\".")
1571 .arg(m_destinationTableSchema->name()));
1572 parentWidget()->raise();
1573 m_conn = 0;
1574}
1575
1576int KexiCSVImportDialog::getHeader(int col)
1577{
1578 TQString header = m_table->horizontalHeader()->label(col);
1579
1580 if (header == i18n("Text type for column", "Text"))
1581 return TEXT;
1582 else if (header == i18n("Numeric type for column", "Number"))
1583 return NUMBER;
1584 else if (header == i18n("Currency type for column", "Currency"))
1585 return CURRENCY;
1586 else
1587 return DATE;
1588}
1589
1590TQString KexiCSVImportDialog::getText(int row, int col)
1591{
1592 return m_table->text(row, col);
1593}
1594
1595void KexiCSVImportDialog::ignoreDuplicatesChanged(int)
1596{
1597 fillTable();
1598}
1599
1600void KexiCSVImportDialog::slot1stRowForFieldNamesChanged(int)
1601{
1602 m_adjustRows=true;
1603 if (m_1stRowForFieldNames->isChecked() && m_startline>0 && m_startline>=(m_startAtLineSpinBox->maxValue()-1))
1604 m_startline--;
1605 fillTable();
1606}
1607
1608void KexiCSVImportDialog::optionsButtonClicked()
1609{
1610 KexiCSVImportOptionsDialog dlg(m_options, this);
1611 if (TQDialog::Accepted != dlg.exec())
1612 return;
1613
1614 KexiCSVImportOptions newOptions( dlg.options() );
1615 if (m_options != newOptions) {
1616 m_options = newOptions;
1617 if (!openData())
1618 return;
1619 fillTable();
1620 }
1621}
1622
1623bool KexiCSVImportDialog::eventFilter ( TQObject * watched, TQEvent * e )
1624{
1625 TQEvent::Type t = e->type();
1626 // temporary disable keyboard and mouse events for time-consuming tasks
1627 if (m_blockUserEvents && (t==TQEvent::KeyPress || t==TQEvent::KeyRelease
1628 || t==TQEvent::MouseButtonPress || t==TQEvent::MouseButtonDblClick
1629 || t==TQEvent::Paint ))
1630 return true;
1631
1632 if (watched == m_startAtLineSpinBox && t==TQEvent::KeyPress) {
1633 TQKeyEvent *ke = static_cast<TQKeyEvent*>(e);
1634 if (ke->key()==TQt::Key_Enter || ke->key()==TQt::Key_Return) {
1635 m_table->setFocus();
1636 return true;
1637 }
1638 }
1639 return TQDialog::eventFilter( watched, e );
1640}
1641
1642void KexiCSVImportDialog::slotPrimaryKeyFieldToggled(bool on)
1643{
1644 Q_UNUSED(on);
1645 formatChanged(_PK_FLAG);
1646}
1647
1648void KexiCSVImportDialog::updateRowCountInfo()
1649{
1650 m_infoLbl->setFileName( m_fname );
1651 if (m_allRowsLoadedInPreview) {
1652 m_infoLbl->setCommentText(
1653 i18n("row count", "(rows: %1)").arg( m_table->numRows()-1+m_startline ) );
1654 TQToolTip::remove( m_infoLbl );
1655 }
1656 else {
1657 m_infoLbl->setCommentText(
1658 i18n("row count", "(rows: more than %1)").arg( m_table->numRows()-1+m_startline ) );
1659 TQToolTip::add( m_infoLbl->commentLabel(), i18n("Not all rows are visible on this preview") );
1660 }
1661}
1662
1663#include "kexicsvimportdialog.moc"
A combo box widget providing a list of possible delimiters Used by CSV import and export dialogs.
KexiCSVImportDialog(Mode mode, KexiMainWindow *mainWin, TQWidget *parent, const char *name=0)
A helper widget showing a short text information with an icon. See ctor for details....
void setIcon(const TQString &iconName)
sets icon pixmap to iconName. Used wher setIconForFilename was false in ctor.
A combo box widget providing a list of possible quote characters Used by CSV import and export dialog...
bool createTable(TableSchema *tableSchema, bool replaceExisting=false)
Creates a table according to the given schema.
virtual PreparedStatement::Ptr prepareStatement(PreparedStatement::StatementType type, FieldList &fields)=0
Transaction beginTransaction()
Creates new transaction handle and starts a new transaction.
FieldList & addField(Field *field)
Field * field(uint id)
Definition fieldlist.h:76
Meta-data for a field.
Definition field.h:73
@ InsertStatement
INSERT statement will be prepared end executed.
Helper class for using inside methods for given connection.
This class encapsulates transaction handle.
Definition transaction.h:60
Kexi's main window interface This interface is implemented by KexiMainWindowImpl class.
virtual KexiProject * project()=0
Project data of currently opened project or NULL if no project here yet.
virtual tristate getNewObjectInfo(KexiPart::Item *partItem, KexiPart::Part *part, bool &allowOverwriting, const TQString &messageWhenAskingForName=TQString())=0
Information about a single object that can be instantiated using Kexi Part.
Part * partForMimeType(const TQString &mimeTypt)
The main class for kexi frontend parts like tables, queries, forms and reports.
Definition kexipart.h:66
A project's main controller.
Definition kexiproject.h:68
void addStoredItem(KexiPart::Info *info, KexiPart::Item *item)
KexiDB::Connection * dbConnection() const
KexiPart::Item * createPartItem(KexiPart::Info *info, const TQString &suggestedCaption=TQString())
3-state logical type with three values: true, false and cancelled and convenient operators.
Definition tristate.h:100
KFORMEDITOR_EXPORT void installRecursiveEventFilter(TQObject *object, TQObject *container)
KEXIUTILS_EXPORT TQString string2Identifier(const TQString &s)
KEXIUTILS_EXPORT TQString fileDialogFilterStrings(const TQStringList &mimeStrings, bool kdeFormat)
KEXICORE_EXPORT KexiPart::Manager & partManager()
shared part manager
Definition kexi.cpp:97