/* This file is part of libtdepim. Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org> Copyright (c) 2002 Tobias Koenig <tokoe@kde.org> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Generic CSV import. Please do not add application specific code to this // class. Application specific code should go to a subclass provided by the // application using this dialog. #include <tqbuttongroup.h> #include <tqfile.h> #include <tqlabel.h> #include <tqlayout.h> #include <tqlineedit.h> #include <tqlistview.h> #include <tqradiobutton.h> #include <tqregexp.h> #include <tqtable.h> #include <tqtextstream.h> #include <tqvbox.h> #include <kapplication.h> #include <kdebug.h> #include <kcombobox.h> #include <kinputdialog.h> #include <klineedit.h> #include <klocale.h> #include <kprogress.h> #include <ksimpleconfig.h> #include <kstandarddirs.h> #include <kurlrequester.h> #include <kfiledialog.h> #include "kimportdialog.h" #include "kimportdialog.moc" KImportColumn::KImportColumn(KImportDialog *dlg,const TQString &header, int count) : m_maxCount(count), m_refCount(0), m_header(header), mDialog(dlg) { mFormats.append(FormatPlain); mFormats.append(FormatUnquoted); // mFormats.append(FormatBracketed); mDefaultFormat = FormatUnquoted; mDialog->addColumn(this); } TQValueList<int> KImportColumn::formats() { return mFormats; } TQString KImportColumn::formatName(int format) { switch (format) { case FormatPlain: return i18n("Plain"); case FormatUnquoted: return i18n("Unquoted"); case FormatBracketed: return i18n("Bracketed"); default: return i18n("Undefined"); } } int KImportColumn::defaultFormat() { return mDefaultFormat; } TQString KImportColumn::preview(const TQString &value, int format) { if (format == FormatBracketed) { return "(" + value + ")"; } else if (format == FormatUnquoted) { if (value.left(1) == "\"" && value.right(1) == "\"") { return value.mid(1,value.length()-2); } else { return value; } } else { return value; } } void KImportColumn::addColId(int id) { mColIds.append(id); } void KImportColumn::removeColId(int id) { mColIds.remove(id); } TQValueList<int> KImportColumn::colIdList() { return mColIds; } TQString KImportColumn::convert() { TQValueList<int>::ConstIterator it = mColIds.begin(); if (it == mColIds.end()) return ""; else return mDialog->cell(*it); } class ColumnItem : public TQListViewItem { public: ColumnItem(KImportColumn *col,TQListView *parent) : TQListViewItem(parent), mColumn(col) { setText(0,mColumn->header()); } KImportColumn *column() { return mColumn; } private: KImportColumn *mColumn; }; /** This is a generic class for importing line-oriented data from text files. It provides a dialog for file selection, preview, separator selection and column assignment as well as generic conversion routines. For conversion to special data objects, this class has to be inherited by a special class, which reimplements the convertRow() function. */ KImportDialog::KImportDialog(TQWidget* parent) : KDialogBase(parent,"importdialog",true,i18n("Import Text File"),Ok|Cancel), mSeparator(","), mCurrentRow(0) { mData.setAutoDelete( true ); TQVBox *topBox = new TQVBox(this); setMainWidget(topBox); topBox->setSpacing(spacingHint()); TQHBox *fileBox = new TQHBox(topBox); fileBox->setSpacing(spacingHint()); new TQLabel(i18n("File to import:"),fileBox); KURLRequester *urlRequester = new KURLRequester(fileBox); urlRequester->setFilter( "*.csv" ); connect(urlRequester,TQT_SIGNAL(returnPressed(const TQString &)), TQT_SLOT(setFile(const TQString &))); connect(urlRequester,TQT_SIGNAL(urlSelected(const TQString &)), TQT_SLOT(setFile(const TQString &))); connect(urlRequester->lineEdit(),TQT_SIGNAL(textChanged ( const TQString & )), TQT_SLOT(slotUrlChanged(const TQString & ))); mTable = new TQTable(5,5,topBox); mTable->setMinimumHeight( 150 ); connect(mTable,TQT_SIGNAL(selectionChanged()),TQT_SLOT(tableSelected())); TQHBox *separatorBox = new TQHBox( topBox ); separatorBox->setSpacing( spacingHint() ); new TQLabel( i18n( "Separator:" ), separatorBox ); mSeparatorCombo = new KComboBox( separatorBox ); mSeparatorCombo->insertItem( "," ); mSeparatorCombo->insertItem( i18n( "Tab" ) ); mSeparatorCombo->insertItem( i18n( "Space" ) ); mSeparatorCombo->insertItem( "=" ); mSeparatorCombo->insertItem( ";" ); connect(mSeparatorCombo, TQT_SIGNAL( activated(int) ), this, TQT_SLOT( separatorClicked(int) ) ); mSeparatorCombo->setCurrentItem( 0 ); TQHBox *rowsBox = new TQHBox( topBox ); rowsBox->setSpacing( spacingHint() ); new TQLabel( i18n( "Import starts at row:" ), rowsBox ); mStartRow = new TQSpinBox( rowsBox ); mStartRow->setMinValue( 1 ); /* new TQLabel( i18n( "And ends at row:" ), rowsBox ); mEndRow = new TQSpinBox( rowsBox ); mEndRow->setMinValue( 1 ); */ TQVBox *assignBox = new TQVBox(topBox); assignBox->setSpacing(spacingHint()); TQHBox *listsBox = new TQHBox(assignBox); listsBox->setSpacing(spacingHint()); mHeaderList = new TQListView(listsBox); mHeaderList->addColumn(i18n("Header")); connect(mHeaderList, TQT_SIGNAL(selectionChanged(TQListViewItem*)), this, TQT_SLOT(headerSelected(TQListViewItem*))); connect(mHeaderList,TQT_SIGNAL(doubleClicked(TQListViewItem*)), TQT_SLOT(assignColumn(TQListViewItem *))); mFormatCombo = new KComboBox( listsBox ); mFormatCombo->setDuplicatesEnabled( false ); TQPushButton *assignButton = new TQPushButton(i18n("Assign to Selected Column"), assignBox); connect(assignButton,TQT_SIGNAL(clicked()),TQT_SLOT(assignColumn())); TQPushButton *removeButton = new TQPushButton(i18n("Remove Assignment From Selected Column"), assignBox); connect(removeButton,TQT_SIGNAL(clicked()),TQT_SLOT(removeColumn())); TQPushButton *assignTemplateButton = new TQPushButton(i18n("Assign with Template..."), assignBox); connect(assignTemplateButton,TQT_SIGNAL(clicked()),TQT_SLOT(assignTemplate())); TQPushButton *saveTemplateButton = new TQPushButton(i18n("Save Current Template"), assignBox); connect(saveTemplateButton,TQT_SIGNAL(clicked()),TQT_SLOT(saveTemplate())); resize(500,300); connect(this,TQT_SIGNAL(okClicked()),TQT_SLOT(applyConverter())); connect(this,TQT_SIGNAL(applyClicked()),TQT_SLOT(applyConverter())); enableButtonOK(!urlRequester->lineEdit()->text().isEmpty()); } void KImportDialog::slotUrlChanged(const TQString & text) { enableButtonOK(!text.isEmpty()); } bool KImportDialog::setFile(const TQString& file) { enableButtonOK(!file.isEmpty()); kdDebug(5300) << "KImportDialog::setFile(): " << file << endl; TQFile f(file); if (f.open(IO_ReadOnly)) { mFile = ""; TQTextStream t(&f); mFile = t.read(); // while (!t.eof()) mFile.append(t.readLine()); f.close(); readFile(); // mEndRow->setValue( mData.count() ); return true; } else { kdDebug(5300) << " Open failed" << endl; return false; } } void KImportDialog::registerColumns() { TQPtrListIterator<KImportColumn> colIt(mColumns); for (; colIt.current(); ++colIt) { new ColumnItem(*colIt,mHeaderList); } mHeaderList->setSelected(mHeaderList->firstChild(),true); } void KImportDialog::fillTable() { // kdDebug(5300) << "KImportDialog::fillTable()" << endl; int row, column; for (row = 0; row < mTable->numRows(); ++row) for (column = 0; column < mTable->numCols(); ++column) mTable->clearCell(row, column); for ( row = 0; row < int(mData.count()); ++row ) { TQValueVector<TQString> *rowVector = mData[ row ]; for( column = 0; column < int(rowVector->size()); ++column ) { setCellText( row, column, rowVector->at( column ) ); } } } void KImportDialog::readFile( int rows ) { kdDebug(5300) << "KImportDialog::readFile(): " << rows << endl; mData.clear(); int row, column; enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD, S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START; TQChar m_textquote = '"'; int m_startline = 0; TQChar x; TQString field = ""; row = column = 0; TQTextStream inputStream(mFile, IO_ReadOnly); inputStream.setEncoding(TQTextStream::Locale); KProgressDialog pDialog(this, 0, i18n("Loading Progress"), i18n("Please wait while the file is loaded."), true); pDialog.setAllowCancel(true); pDialog.showCancelButton(true); pDialog.setAutoClose(true); KProgress *progress = pDialog.progressBar(); progress->setTotalSteps( mFile.contains(mSeparator, false) ); progress->setValue(0); int progressValue = 0; if (progress->totalSteps() > 0) // We have data pDialog.show(); while (!inputStream.atEnd() && !pDialog.wasCancelled()) { inputStream >> x; // read one char // update the dialog if needed if (x == mSeparator) { progress->setValue(progressValue++); if (progressValue % 15 == 0) // try not to constantly repaint kapp->processEvents(); } if (x == '\r') inputStream >> x; // eat '\r', to handle DOS/LOSEDOWS files correctly switch (state) { case S_START : if (x == m_textquote) { field += x; state = S_QUOTED_FIELD; } else if (x == mSeparator) { ++column; } else if (x == '\n') { ++row; column = 0; } else { field += x; state = S_MAYBE_NORMAL_FIELD; } break; case S_QUOTED_FIELD : if (x == m_textquote) { field += x; state = S_MAYBE_END_OF_QUOTED_FIELD; } else if (x == '\n') { setData(row - m_startline, column, field); field = ""; if (x == '\n') { ++row; column = 0; } else { ++column; } state = S_START; } else { field += x; } break; case S_MAYBE_END_OF_QUOTED_FIELD : if (x == m_textquote) { field += x; state = S_QUOTED_FIELD; } else if (x == mSeparator || x == '\n') { setData(row - m_startline, column, field); field = ""; if (x == '\n') { ++row; column = 0; } else { ++column; } state = S_START; } else { state = S_END_OF_QUOTED_FIELD; } break; case S_END_OF_QUOTED_FIELD : if (x == mSeparator || x == '\n') { setData(row - m_startline, column, field); field = ""; if (x == '\n') { ++row; column = 0; } else { ++column; } state = S_START; } else { state = S_END_OF_QUOTED_FIELD; } break; case S_MAYBE_NORMAL_FIELD : if (x == m_textquote) { field = ""; state = S_QUOTED_FIELD; } case S_NORMAL_FIELD : if (x == mSeparator || x == '\n') { setData(row - m_startline, column, field); field = ""; if (x == '\n') { ++row; column = 0; } else { ++column; } state = S_START; } else { field += x; } } if ( rows > 0 && row > rows ) break; } fillTable(); } void KImportDialog::setCellText(int row, int col, const TQString& text) { if (row < 0) return; if ((mTable->numRows() - 1) < row) mTable->setNumRows(row + 1); if ((mTable->numCols() - 1) < col) mTable->setNumCols(col + 1); KImportColumn *c = mColumnDict.find(col); TQString formattedText; if (c) formattedText = c->preview(text,findFormat(col)); else formattedText = text; mTable->setText(row, col, formattedText); } void KImportDialog::formatSelected(TQListViewItem*) { // kdDebug(5300) << "KImportDialog::formatSelected()" << endl; } void KImportDialog::headerSelected(TQListViewItem* item) { KImportColumn *col = ((ColumnItem *)item)->column(); if (!col) return; mFormatCombo->clear(); TQValueList<int> formats = col->formats(); TQValueList<int>::ConstIterator it = formats.begin(); TQValueList<int>::ConstIterator end = formats.end(); while(it != end) { mFormatCombo->insertItem( col->formatName(*it), *it - 1 ); ++it; } TQTableSelection selection = mTable->selection(mTable->currentSelection()); updateFormatSelection(selection.leftCol()); } void KImportDialog::updateFormatSelection(int column) { int format = findFormat(column); if ( format == KImportColumn::FormatUndefined ) mFormatCombo->setCurrentItem( 0 ); else mFormatCombo->setCurrentItem( format - 1 ); } void KImportDialog::tableSelected() { TQTableSelection selection = mTable->selection(mTable->currentSelection()); TQListViewItem *item = mHeaderList->firstChild(); KImportColumn *col = mColumnDict.find(selection.leftCol()); if (col) { while(item) { if (item->text(0) == col->header()) { break; } item = item->nextSibling(); } } if (item) { mHeaderList->setSelected(item,true); } updateFormatSelection(selection.leftCol()); } void KImportDialog::separatorClicked(int id) { switch(id) { case 0: mSeparator = ','; break; case 1: mSeparator = '\t'; break; case 2: mSeparator = ' '; break; case 3: mSeparator = '='; break; case 4: mSeparator = ';'; break; default: mSeparator = ','; break; } readFile(); } void KImportDialog::assignColumn(TQListViewItem *item) { if (!item) return; // kdDebug(5300) << "KImportDialog::assignColumn(): current Col: " << mTable->currentColumn() // << endl; ColumnItem *colItem = (ColumnItem *)item; TQTableSelection selection = mTable->selection(mTable->currentSelection()); // kdDebug(5300) << " l: " << selection.leftCol() << " r: " << selection.rightCol() << endl; for(int i=selection.leftCol();i<=selection.rightCol();++i) { if (i >= 0) { mTable->horizontalHeader()->setLabel(i,colItem->text(0)); mColumnDict.replace(i,colItem->column()); int format = mFormatCombo->currentItem() + 1; mFormats.replace(i,format); colItem->column()->addColId(i); } } readFile(); } void KImportDialog::assignColumn() { assignColumn(mHeaderList->currentItem()); } void KImportDialog::assignTemplate() { TQMap<uint,int> columnMap; TQMap<TQString, TQString> fileMap; TQStringList templates; // load all template files TQStringList list = KGlobal::dirs()->findAllResources( "data" , TQString( kapp->name() ) + "/csv-templates/*.desktop", true, true ); for ( TQStringList::iterator it = list.begin(); it != list.end(); ++it ) { KSimpleConfig config( *it, true ); if ( !config.hasGroup( "csv column map" ) ) continue; config.setGroup( "Misc" ); templates.append( config.readEntry( "Name" ) ); fileMap.insert( config.readEntry( "Name" ), *it ); } // let the user chose, what to take bool ok = false; TQString tmp; tmp = KInputDialog::getItem( i18n( "Template Selection" ), i18n( "Please select a template, that matches the CSV file:" ), templates, 0, false, &ok, this ); if ( !ok ) return; KSimpleConfig config( fileMap[ tmp ], true ); config.setGroup( "General" ); uint numColumns = config.readUnsignedNumEntry( "Columns" ); int format = config.readNumEntry( "Format" ); // create the column map config.setGroup( "csv column map" ); for ( uint i = 0; i < numColumns; ++i ) { int col = config.readNumEntry( TQString::number( i ) ); columnMap.insert( i, col ); } // apply the column map for ( uint i = 0; i < columnMap.count(); ++i ) { int tableColumn = columnMap[i]; if ( tableColumn == -1 ) continue; KImportColumn *col = mColumns.at(i); mTable->horizontalHeader()->setLabel( tableColumn, col->header() ); mColumnDict.replace( tableColumn, col ); mFormats.replace( tableColumn, format ); col->addColId( tableColumn ); } readFile(); } void KImportDialog::removeColumn() { TQTableSelection selection = mTable->selection(mTable->currentSelection()); // kdDebug(5300) << " l: " << selection.leftCol() << " r: " << selection.rightCol() << endl; for(int i=selection.leftCol();i<=selection.rightCol();++i) { if (i >= 0) { mTable->horizontalHeader()->setLabel(i,TQString::number(i+1)); KImportColumn *col = mColumnDict.find(i); if (col) { mColumnDict.remove(i); mFormats.remove(i); col->removeColId(i); } } } readFile(); } void KImportDialog::applyConverter() { kdDebug(5300) << "KImportDialog::applyConverter" << endl; KProgressDialog pDialog(this, 0, i18n("Importing Progress"), i18n("Please wait while the data is imported."), true); pDialog.setAllowCancel(true); pDialog.showCancelButton(true); pDialog.setAutoClose(true); KProgress *progress = pDialog.progressBar(); progress->setTotalSteps( mTable->numRows()-1 ); progress->setValue(0); readFile( 0 ); pDialog.show(); for( uint i = mStartRow->value() - 1; i < mData.count() && !pDialog.wasCancelled(); ++i ) { mCurrentRow = i; progress->setValue(i); if (i % 5 == 0) // try to avoid constantly processing events kapp->processEvents(); convertRow(); } } int KImportDialog::findFormat(int column) { TQMap<int,int>::ConstIterator formatIt = mFormats.find(column); int format; if (formatIt == mFormats.end()) format = KImportColumn::FormatUndefined; else format = *formatIt; // kdDebug(5300) << "KImportDialog::findformat(): " << column << ": " << format << endl; return format; } TQString KImportDialog::cell(uint col) { if ( col >= mData[ mCurrentRow ]->size() ) return ""; else return data( mCurrentRow, col ); } void KImportDialog::addColumn(KImportColumn *col) { mColumns.append(col); } void KImportDialog::setData( uint row, uint col, const TQString &value ) { TQString val = value; val.replace( "\\n", "\n" ); if ( row >= mData.count() ) { mData.resize( row + 1 ); } TQValueVector<TQString> *rowVector = mData[ row ]; if ( !rowVector ) { rowVector = new TQValueVector<TQString>; mData.insert( row, rowVector ); } if ( col >= rowVector->size() ) { rowVector->resize( col + 1 ); } KImportColumn *c = mColumnDict.find( col ); if ( c ) rowVector->at( col ) = c->preview( val, findFormat(col) ); else rowVector->at( col ) = val; } TQString KImportDialog::data( uint row, uint col ) { return mData[ row ]->at( col ); } void KImportDialog::saveTemplate() { TQString fileName = KFileDialog::getSaveFileName( locateLocal( "data", TQString( kapp->name() ) + "/csv-templates/" ), "*.desktop", this ); if ( fileName.isEmpty() ) return; if ( !fileName.contains( ".desktop" ) ) fileName += ".desktop"; TQString name = KInputDialog::getText( i18n( "Template Name" ), i18n( "Please enter a name for the template:" ) ); if ( name.isEmpty() ) return; KConfig config( fileName ); config.setGroup( "General" ); config.writeEntry( "Columns", mColumns.count() ); config.writeEntry( "Format", mFormatCombo->currentItem() + 1 ); config.setGroup( "Misc" ); config.writeEntry( "Name", name ); config.setGroup( "csv column map" ); KImportColumn *column; uint counter = 0; for ( column = mColumns.first(); column; column = mColumns.next() ) { TQValueList<int> list = column->colIdList(); if ( list.count() > 0 ) config.writeEntry( TQString::number( counter ), list[ 0 ] ); else config.writeEntry( TQString::number( counter ), -1 ); counter++; } config.sync(); }