libtdepim

kimportdialog.cpp
1/*
2 This file is part of libtdepim.
3
4 Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (c) 2002 Tobias Koenig <tokoe@kde.org>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22
23// Generic CSV import. Please do not add application specific code to this
24// class. Application specific code should go to a subclass provided by the
25// application using this dialog.
26
27#include <tqbuttongroup.h>
28#include <tqfile.h>
29#include <tqlabel.h>
30#include <tqlayout.h>
31#include <tqlineedit.h>
32#include <tqlistview.h>
33#include <tqradiobutton.h>
34#include <tqregexp.h>
35#include <tqtable.h>
36#include <tqtextstream.h>
37#include <tqvbox.h>
38
39#include <tdeapplication.h>
40#include <kdebug.h>
41#include <kcombobox.h>
42#include <kinputdialog.h>
43#include <klineedit.h>
44#include <tdelocale.h>
45#include <kprogress.h>
46#include <ksimpleconfig.h>
47#include <tdestandarddirs.h>
48#include <kurlrequester.h>
49#include <tdefiledialog.h>
50
51#include "kimportdialog.h"
52#include "kimportdialog.moc"
53
54KImportColumn::KImportColumn(KImportDialog *dlg,const TQString &header, int count)
55 : m_maxCount(count),
56 m_refCount(0),
57 m_header(header),
58 mDialog(dlg)
59{
60 mFormats.append(FormatPlain);
61 mFormats.append(FormatUnquoted);
62// mFormats.append(FormatBracketed);
63
64 mDefaultFormat = FormatUnquoted;
65
66 mDialog->addColumn(this);
67}
68
69TQValueList<int> KImportColumn::formats()
70{
71 return mFormats;
72}
73
74TQString KImportColumn::formatName(int format)
75{
76 switch (format) {
77 case FormatPlain:
78 return i18n("Plain");
79 case FormatUnquoted:
80 return i18n("Unquoted");
81 case FormatBracketed:
82 return i18n("Bracketed");
83 default:
84 return i18n("Undefined");
85 }
86}
87
88int KImportColumn::defaultFormat()
89{
90 return mDefaultFormat;
91}
92
93TQString KImportColumn::preview(const TQString &value, int format)
94{
95 if (format == FormatBracketed) {
96 return "(" + value + ")";
97 } else if (format == FormatUnquoted) {
98 if (value.left(1) == "\"" && value.right(1) == "\"") {
99 return value.mid(1,value.length()-2);
100 } else {
101 return value;
102 }
103 } else {
104 return value;
105 }
106}
107
108void KImportColumn::addColId(int id)
109{
110 mColIds.append(id);
111}
112
113void KImportColumn::removeColId(int id)
114{
115 mColIds.remove(id);
116}
117
118TQValueList<int> KImportColumn::colIdList()
119{
120 return mColIds;
121}
122
123TQString KImportColumn::convert()
124{
125 TQValueList<int>::ConstIterator it = mColIds.begin();
126 if (it == mColIds.end()) return "";
127 else return mDialog->cell(*it);
128}
129
130
131class ColumnItem : public TQListViewItem {
132 public:
133 ColumnItem(KImportColumn *col,TQListView *parent) : TQListViewItem(parent), mColumn(col)
134 {
135 setText(0,mColumn->header());
136 }
137
138 KImportColumn *column() { return mColumn; }
139
140 private:
141 KImportColumn *mColumn;
142};
143
151KImportDialog::KImportDialog(TQWidget* parent)
152 : KDialogBase(parent,"importdialog",true,i18n("Import Text File"),Ok|Cancel),
153 mSeparator(","),
154 mCurrentRow(0)
155{
156 mData.setAutoDelete( true );
157
158 TQVBox *topBox = new TQVBox(this);
159 setMainWidget(topBox);
160 topBox->setSpacing(spacingHint());
161
162 TQHBox *fileBox = new TQHBox(topBox);
163 fileBox->setSpacing(spacingHint());
164 new TQLabel(i18n("File to import:"),fileBox);
165 KURLRequester *urlRequester = new KURLRequester(fileBox);
166 urlRequester->setFilter( "*.csv" );
167 connect(urlRequester,TQ_SIGNAL(returnPressed(const TQString &)),
168 TQ_SLOT(setFile(const TQString &)));
169 connect(urlRequester,TQ_SIGNAL(urlSelected(const TQString &)),
170 TQ_SLOT(setFile(const TQString &)));
171 connect(urlRequester->lineEdit(),TQ_SIGNAL(textChanged ( const TQString & )),
172 TQ_SLOT(slotUrlChanged(const TQString & )));
173 mTable = new TQTable(5,5,topBox);
174 mTable->setMinimumHeight( 150 );
175 connect(mTable,TQ_SIGNAL(selectionChanged()),TQ_SLOT(tableSelected()));
176
177 TQHBox *separatorBox = new TQHBox( topBox );
178 separatorBox->setSpacing( spacingHint() );
179
180 new TQLabel( i18n( "Separator:" ), separatorBox );
181
182 mSeparatorCombo = new KComboBox( separatorBox );
183 mSeparatorCombo->insertItem( "," );
184 mSeparatorCombo->insertItem( i18n( "Tab" ) );
185 mSeparatorCombo->insertItem( i18n( "Space" ) );
186 mSeparatorCombo->insertItem( "=" );
187 mSeparatorCombo->insertItem( ";" );
188 connect(mSeparatorCombo, TQ_SIGNAL( activated(int) ),
189 this, TQ_SLOT( separatorClicked(int) ) );
190 mSeparatorCombo->setCurrentItem( 0 );
191
192 TQHBox *rowsBox = new TQHBox( topBox );
193 rowsBox->setSpacing( spacingHint() );
194
195 new TQLabel( i18n( "Import starts at row:" ), rowsBox );
196 mStartRow = new TQSpinBox( rowsBox );
197 mStartRow->setMinValue( 1 );
198/*
199 new TQLabel( i18n( "And ends at row:" ), rowsBox );
200 mEndRow = new TQSpinBox( rowsBox );
201 mEndRow->setMinValue( 1 );
202*/
203 TQVBox *assignBox = new TQVBox(topBox);
204 assignBox->setSpacing(spacingHint());
205
206 TQHBox *listsBox = new TQHBox(assignBox);
207 listsBox->setSpacing(spacingHint());
208
209 mHeaderList = new TQListView(listsBox);
210 mHeaderList->addColumn(i18n("Header"));
211 connect(mHeaderList, TQ_SIGNAL(selectionChanged(TQListViewItem*)),
212 this, TQ_SLOT(headerSelected(TQListViewItem*)));
213 connect(mHeaderList,TQ_SIGNAL(doubleClicked(TQListViewItem*)),
214 TQ_SLOT(assignColumn(TQListViewItem *)));
215
216 mFormatCombo = new KComboBox( listsBox );
217 mFormatCombo->setDuplicatesEnabled( false );
218
219 TQPushButton *assignButton = new TQPushButton(i18n("Assign to Selected Column"),
220 assignBox);
221 connect(assignButton,TQ_SIGNAL(clicked()),TQ_SLOT(assignColumn()));
222
223 TQPushButton *removeButton = new TQPushButton(i18n("Remove Assignment From Selected Column"),
224 assignBox);
225 connect(removeButton,TQ_SIGNAL(clicked()),TQ_SLOT(removeColumn()));
226
227 TQPushButton *assignTemplateButton = new TQPushButton(i18n("Assign with Template..."),
228 assignBox);
229 connect(assignTemplateButton,TQ_SIGNAL(clicked()),TQ_SLOT(assignTemplate()));
230
231 TQPushButton *saveTemplateButton = new TQPushButton(i18n("Save Current Template"),
232 assignBox);
233 connect(saveTemplateButton,TQ_SIGNAL(clicked()),TQ_SLOT(saveTemplate()));
234
235 resize(500,300);
236
237 connect(this,TQ_SIGNAL(okClicked()),TQ_SLOT(applyConverter()));
238 connect(this,TQ_SIGNAL(applyClicked()),TQ_SLOT(applyConverter()));
239 enableButtonOK(!urlRequester->lineEdit()->text().isEmpty());
240}
241
242void KImportDialog::slotUrlChanged(const TQString & text)
243{
244 enableButtonOK(!text.isEmpty());
245}
246
247bool KImportDialog::setFile(const TQString& file)
248{
249 enableButtonOK(!file.isEmpty());
250 kdDebug(5300) << "KImportDialog::setFile(): " << file << endl;
251
252 TQFile f(file);
253
254 if (f.open(IO_ReadOnly)) {
255 mFile = "";
256 TQTextStream t(&f);
257 mFile = t.read();
258// while (!t.eof()) mFile.append(t.readLine());
259 f.close();
260
261 readFile();
262
263// mEndRow->setValue( mData.count() );
264
265 return true;
266 } else {
267 kdDebug(5300) << " Open failed" << endl;
268 return false;
269 }
270}
271
272void KImportDialog::registerColumns()
273{
274 TQPtrListIterator<KImportColumn> colIt(mColumns);
275 for (; colIt.current(); ++colIt) {
276 new ColumnItem(*colIt,mHeaderList);
277 }
278 mHeaderList->setSelected(mHeaderList->firstChild(),true);
279}
280
281void KImportDialog::fillTable()
282{
283// kdDebug(5300) << "KImportDialog::fillTable()" << endl;
284
285 int row, column;
286
287 for (row = 0; row < mTable->numRows(); ++row)
288 for (column = 0; column < mTable->numCols(); ++column)
289 mTable->clearCell(row, column);
290
291 for ( row = 0; row < int(mData.count()); ++row ) {
292 TQValueVector<TQString> *rowVector = mData[ row ];
293 for( column = 0; column < int(rowVector->size()); ++column ) {
294 setCellText( row, column, rowVector->at( column ) );
295 }
296 }
297}
298
299void KImportDialog::readFile( int rows )
300{
301 kdDebug(5300) << "KImportDialog::readFile(): " << rows << endl;
302
303 mData.clear();
304
305 int row, column;
306 enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
307 S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;
308
309 TQChar m_textquote = '"';
310 int m_startline = 0;
311
312 TQChar x;
313 TQString field = "";
314
315 row = column = 0;
316 TQTextStream inputStream(mFile, IO_ReadOnly);
317 inputStream.setEncoding(TQTextStream::Locale);
318
319 KProgressDialog pDialog(this, 0, i18n("Loading Progress"),
320 i18n("Please wait while the file is loaded."), true);
321 pDialog.setAllowCancel(true);
322 pDialog.showCancelButton(true);
323 pDialog.setAutoClose(true);
324
325 KProgress *progress = pDialog.progressBar();
326 progress->setTotalSteps( mFile.contains(mSeparator, false) );
327 progress->setValue(0);
328 int progressValue = 0;
329
330 if (progress->totalSteps() > 0) // We have data
331 pDialog.show();
332
333 while (!inputStream.atEnd() && !pDialog.wasCancelled()) {
334 inputStream >> x; // read one char
335
336 // update the dialog if needed
337 if (x == mSeparator)
338 {
339 progress->setValue(progressValue++);
340 if (progressValue % 15 == 0) // try not to constantly repaint
341 tdeApp->processEvents();
342 }
343
344 if (x == '\r') inputStream >> x; // eat '\r', to handle DOS/LOSEDOWS files correctly
345
346 switch (state) {
347 case S_START :
348 if (x == m_textquote) {
349 field += x;
350 state = S_QUOTED_FIELD;
351 } else if (x == mSeparator) {
352 ++column;
353 } else if (x == '\n') {
354 ++row;
355 column = 0;
356 } else {
357 field += x;
358 state = S_MAYBE_NORMAL_FIELD;
359 }
360 break;
361 case S_QUOTED_FIELD :
362 if (x == m_textquote) {
363 field += x;
364 state = S_MAYBE_END_OF_QUOTED_FIELD;
365 } else if (x == '\n') {
366 setData(row - m_startline, column, field);
367 field = "";
368 if (x == '\n') {
369 ++row;
370 column = 0;
371 } else {
372 ++column;
373 }
374 state = S_START;
375 } else {
376 field += x;
377 }
378 break;
379 case S_MAYBE_END_OF_QUOTED_FIELD :
380 if (x == m_textquote) {
381 field += x;
382 state = S_QUOTED_FIELD;
383 } else if (x == mSeparator || x == '\n') {
384 setData(row - m_startline, column, field);
385 field = "";
386 if (x == '\n') {
387 ++row;
388 column = 0;
389 } else {
390 ++column;
391 }
392 state = S_START;
393 } else {
394 state = S_END_OF_QUOTED_FIELD;
395 }
396 break;
397 case S_END_OF_QUOTED_FIELD :
398 if (x == mSeparator || x == '\n') {
399 setData(row - m_startline, column, field);
400 field = "";
401 if (x == '\n') {
402 ++row;
403 column = 0;
404 } else {
405 ++column;
406 }
407 state = S_START;
408 } else {
409 state = S_END_OF_QUOTED_FIELD;
410 }
411 break;
412 case S_MAYBE_NORMAL_FIELD :
413 if (x == m_textquote) {
414 field = "";
415 state = S_QUOTED_FIELD;
416 }
417 case S_NORMAL_FIELD :
418 if (x == mSeparator || x == '\n') {
419 setData(row - m_startline, column, field);
420 field = "";
421 if (x == '\n') {
422 ++row;
423 column = 0;
424 } else {
425 ++column;
426 }
427 state = S_START;
428 } else {
429 field += x;
430 }
431 }
432
433 if ( rows > 0 && row > rows ) break;
434 }
435
436 fillTable();
437}
438
439void KImportDialog::setCellText(int row, int col, const TQString& text)
440{
441 if (row < 0) return;
442
443 if ((mTable->numRows() - 1) < row) mTable->setNumRows(row + 1);
444 if ((mTable->numCols() - 1) < col) mTable->setNumCols(col + 1);
445
446 KImportColumn *c = mColumnDict.find(col);
447 TQString formattedText;
448 if (c) formattedText = c->preview(text,findFormat(col));
449 else formattedText = text;
450 mTable->setText(row, col, formattedText);
451}
452
453void KImportDialog::formatSelected(TQListViewItem*)
454{
455// kdDebug(5300) << "KImportDialog::formatSelected()" << endl;
456}
457
458void KImportDialog::headerSelected(TQListViewItem* item)
459{
460 KImportColumn *col = ((ColumnItem *)item)->column();
461
462 if (!col) return;
463
464 mFormatCombo->clear();
465
466 TQValueList<int> formats = col->formats();
467
468 TQValueList<int>::ConstIterator it = formats.begin();
469 TQValueList<int>::ConstIterator end = formats.end();
470 while(it != end) {
471 mFormatCombo->insertItem( col->formatName(*it), *it - 1 );
472 ++it;
473 }
474
475 TQTableSelection selection = mTable->selection(mTable->currentSelection());
476
477 updateFormatSelection(selection.leftCol());
478}
479
480void KImportDialog::updateFormatSelection(int column)
481{
482 int format = findFormat(column);
483
484 if ( format == KImportColumn::FormatUndefined )
485 mFormatCombo->setCurrentItem( 0 );
486 else
487 mFormatCombo->setCurrentItem( format - 1 );
488}
489
490void KImportDialog::tableSelected()
491{
492 TQTableSelection selection = mTable->selection(mTable->currentSelection());
493
494 TQListViewItem *item = mHeaderList->firstChild();
495 KImportColumn *col = mColumnDict.find(selection.leftCol());
496 if (col) {
497 while(item) {
498 if (item->text(0) == col->header()) {
499 break;
500 }
501 item = item->nextSibling();
502 }
503 }
504 if (item) {
505 mHeaderList->setSelected(item,true);
506 }
507
508 updateFormatSelection(selection.leftCol());
509}
510
511void KImportDialog::separatorClicked(int id)
512{
513 switch(id) {
514 case 0:
515 mSeparator = ',';
516 break;
517 case 1:
518 mSeparator = '\t';
519 break;
520 case 2:
521 mSeparator = ' ';
522 break;
523 case 3:
524 mSeparator = '=';
525 break;
526 case 4:
527 mSeparator = ';';
528 break;
529 default:
530 mSeparator = ',';
531 break;
532 }
533
534 readFile();
535}
536
537void KImportDialog::assignColumn(TQListViewItem *item)
538{
539 if (!item) return;
540
541// kdDebug(5300) << "KImportDialog::assignColumn(): current Col: " << mTable->currentColumn()
542// << endl;
543
544 ColumnItem *colItem = (ColumnItem *)item;
545
546 TQTableSelection selection = mTable->selection(mTable->currentSelection());
547
548// kdDebug(5300) << " l: " << selection.leftCol() << " r: " << selection.rightCol() << endl;
549
550 for(int i=selection.leftCol();i<=selection.rightCol();++i) {
551 if (i >= 0) {
552 mTable->horizontalHeader()->setLabel(i,colItem->text(0));
553 mColumnDict.replace(i,colItem->column());
554 int format = mFormatCombo->currentItem() + 1;
555 mFormats.replace(i,format);
556 colItem->column()->addColId(i);
557 }
558 }
559
560 readFile();
561}
562
563void KImportDialog::assignColumn()
564{
565 assignColumn(mHeaderList->currentItem());
566}
567
568void KImportDialog::assignTemplate()
569{
570 TQMap<uint,int> columnMap;
571 TQMap<TQString, TQString> fileMap;
572 TQStringList templates;
573
574 // load all template files
575 TQStringList list = TDEGlobal::dirs()->findAllResources( "data" , TQString( tdeApp->name() ) +
576 "/csv-templates/*.desktop", true, true );
577
578 for ( TQStringList::iterator it = list.begin(); it != list.end(); ++it )
579 {
580 KSimpleConfig config( *it, true );
581
582 if ( !config.hasGroup( "csv column map" ) )
583 continue;
584
585 config.setGroup( "Misc" );
586 templates.append( config.readEntry( "Name" ) );
587 fileMap.insert( config.readEntry( "Name" ), *it );
588 }
589
590 // let the user chose, what to take
591 bool ok = false;
592 TQString tmp;
593 tmp = KInputDialog::getItem( i18n( "Template Selection" ),
594 i18n( "Please select a template, that matches the CSV file:" ),
595 templates, 0, false, &ok, this );
596
597 if ( !ok )
598 return;
599
600 KSimpleConfig config( fileMap[ tmp ], true );
601 config.setGroup( "General" );
602 uint numColumns = config.readUnsignedNumEntry( "Columns" );
603 int format = config.readNumEntry( "Format" );
604
605 // create the column map
606 config.setGroup( "csv column map" );
607 for ( uint i = 0; i < numColumns; ++i ) {
608 int col = config.readNumEntry( TQString::number( i ) );
609 columnMap.insert( i, col );
610 }
611
612 // apply the column map
613 for ( uint i = 0; i < columnMap.count(); ++i ) {
614 int tableColumn = columnMap[i];
615 if ( tableColumn == -1 )
616 continue;
617 KImportColumn *col = mColumns.at(i);
618 mTable->horizontalHeader()->setLabel( tableColumn, col->header() );
619 mColumnDict.replace( tableColumn, col );
620 mFormats.replace( tableColumn, format );
621 col->addColId( tableColumn );
622 }
623
624 readFile();
625}
626
627void KImportDialog::removeColumn()
628{
629 TQTableSelection selection = mTable->selection(mTable->currentSelection());
630
631// kdDebug(5300) << " l: " << selection.leftCol() << " r: " << selection.rightCol() << endl;
632
633 for(int i=selection.leftCol();i<=selection.rightCol();++i) {
634 if (i >= 0) {
635 mTable->horizontalHeader()->setLabel(i,TQString::number(i+1));
636 KImportColumn *col = mColumnDict.find(i);
637 if (col) {
638 mColumnDict.remove(i);
639 mFormats.remove(i);
640 col->removeColId(i);
641 }
642 }
643 }
644
645 readFile();
646}
647
648void KImportDialog::applyConverter()
649{
650 kdDebug(5300) << "KImportDialog::applyConverter" << endl;
651
652 KProgressDialog pDialog(this, 0, i18n("Importing Progress"),
653 i18n("Please wait while the data is imported."), true);
654 pDialog.setAllowCancel(true);
655 pDialog.showCancelButton(true);
656 pDialog.setAutoClose(true);
657
658 KProgress *progress = pDialog.progressBar();
659 progress->setTotalSteps( mTable->numRows()-1 );
660 progress->setValue(0);
661
662 readFile( 0 );
663
664 pDialog.show();
665 for( uint i = mStartRow->value() - 1; i < mData.count() && !pDialog.wasCancelled(); ++i ) {
666 mCurrentRow = i;
667 progress->setValue(i);
668 if (i % 5 == 0) // try to avoid constantly processing events
669 tdeApp->processEvents();
670
671 convertRow();
672 }
673}
674
675int KImportDialog::findFormat(int column)
676{
677 TQMap<int,int>::ConstIterator formatIt = mFormats.find(column);
678 int format;
679 if (formatIt == mFormats.end()) format = KImportColumn::FormatUndefined;
680 else format = *formatIt;
681
682// kdDebug(5300) << "KImportDialog::findformat(): " << column << ": " << format << endl;
683
684 return format;
685}
686
687TQString KImportDialog::cell(uint col)
688{
689 if ( col >= mData[ mCurrentRow ]->size() ) return "";
690 else return data( mCurrentRow, col );
691}
692
693void KImportDialog::addColumn(KImportColumn *col)
694{
695 mColumns.append(col);
696}
697
698void KImportDialog::setData( uint row, uint col, const TQString &value )
699{
700 TQString val = value;
701 val.replace( "\\n", "\n" );
702
703 if ( row >= mData.count() ) {
704 mData.resize( row + 1 );
705 }
706
707 TQValueVector<TQString> *rowVector = mData[ row ];
708 if ( !rowVector ) {
709 rowVector = new TQValueVector<TQString>;
710 mData.insert( row, rowVector );
711 }
712 if ( col >= rowVector->size() ) {
713 rowVector->resize( col + 1 );
714 }
715
716 KImportColumn *c = mColumnDict.find( col );
717 if ( c )
718 rowVector->at( col ) = c->preview( val, findFormat(col) );
719 else
720 rowVector->at( col ) = val;
721}
722
723TQString KImportDialog::data( uint row, uint col )
724{
725 return mData[ row ]->at( col );
726}
727
728void KImportDialog::saveTemplate()
729{
730 TQString fileName = KFileDialog::getSaveFileName(
731 locateLocal( "data", TQString( tdeApp->name() ) + "/csv-templates/" ),
732 "*.desktop", this );
733
734 if ( fileName.isEmpty() )
735 return;
736
737 if ( !fileName.contains( ".desktop" ) )
738 fileName += ".desktop";
739
740 TQString name = KInputDialog::getText( i18n( "Template Name" ), i18n( "Please enter a name for the template:" ) );
741
742 if ( name.isEmpty() )
743 return;
744
745 TDEConfig config( fileName );
746 config.setGroup( "General" );
747 config.writeEntry( "Columns", mColumns.count() );
748 config.writeEntry( "Format", mFormatCombo->currentItem() + 1 );
749
750 config.setGroup( "Misc" );
751 config.writeEntry( "Name", name );
752
753 config.setGroup( "csv column map" );
754
755 KImportColumn *column;
756 uint counter = 0;
757 for ( column = mColumns.first(); column; column = mColumns.next() ) {
758 TQValueList<int> list = column->colIdList();
759 if ( list.count() > 0 )
760 config.writeEntry( TQString::number( counter ), list[ 0 ] );
761 else
762 config.writeEntry( TQString::number( counter ), -1 );
763 counter++;
764 }
765
766 config.sync();
767}