filters

csvdialog.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 1999 David Faure <faure@kde.org>
00003    Copyright (C) 2004 Nicolas GOUTTE <goutte@kde.org>
00004    
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019 */
00020 
00021 #include <dialogui.h>
00022 #include <csvdialog.h>
00023 
00024 #include <qtable.h>
00025 #include <qcheckbox.h>
00026 #include <qcursor.h>
00027 #include <qlineedit.h>
00028 #include <qcombobox.h>
00029 #include <qspinbox.h>
00030 #include <qtextstream.h>
00031 #include <qbuttongroup.h>
00032 #include <qpushbutton.h>
00033 #include <qradiobutton.h>
00034 #include <qtextcodec.h>
00035 
00036 #include <kapplication.h>
00037 #include <kdebug.h>
00038 #include <klocale.h>
00039 #include <kcombobox.h>
00040 #include <kmessagebox.h>
00041 #include <kcharsets.h>
00042 
00043 CSVDialog::CSVDialog(QWidget* parent, QByteArray& fileArray, const QString /*seperator*/)
00044     : KDialogBase(parent, 0, true, QString::null, Ok|Cancel, No, true),
00045       m_adjustRows(false),
00046       m_adjustCols(false),
00047       m_startRow(0),
00048       m_startCol(0),
00049       m_endRow(-1),
00050       m_endCol(-1),
00051       m_textquote('"'),
00052       m_delimiter(","),
00053       m_ignoreDups(false),
00054       m_fileArray(fileArray),
00055       m_dialog(new DialogUI(this)),
00056       m_codec( QTextCodec::codecForName( "UTF-8" ) )
00057 {
00058     setCaption( i18n( "Import" ) );
00059     kapp->restoreOverrideCursor();
00060 
00061     QStringList encodings;
00062     encodings << i18n( "Descriptive encoding name", "Recommended ( %1 )" ).arg( "UTF-8" );
00063     encodings << i18n( "Descriptive encoding name", "Locale ( %1 )" ).arg( QTextCodec::codecForLocale()->name() );
00064     encodings += KGlobal::charsets()->descriptiveEncodingNames();
00065     // Add a few non-standard encodings, which might be useful for text files
00066     const QString description(i18n("Descriptive encoding name","Other ( %1 )"));
00067     encodings << description.arg("Apple Roman"); // Apple
00068     encodings << description.arg("IBM 850") << description.arg("IBM 866"); // MS DOS
00069     encodings << description.arg("CP 1258"); // Windows
00070     m_dialog->comboBoxEncoding->insertStringList(encodings);
00071 
00072     m_formatList << i18n( "Text" );
00073     m_formatList << i18n( "Number" );
00074     m_formatList << i18n( "Currency" );
00075     m_formatList << i18n( "Date" );
00076     m_formatList << i18n( "Decimal Comma Number" );
00077     m_formatList << i18n( "Decimal Point Number" );
00078     m_dialog->m_formatComboBox->insertStringList( m_formatList );
00079 
00080     m_dialog->m_sheet->setReadOnly( true );
00081 
00082     fillTable();
00083 
00084     //resize(sizeHint());
00085     resize( 600, 400 ); // Try to show as much as possible of the table view
00086     setMainWidget(m_dialog);
00087 
00088     m_dialog->m_sheet->setSelectionMode( QTable::Multi );
00089 
00090     connect(m_dialog->m_formatComboBox, SIGNAL(activated( const QString& )),
00091             this, SLOT(formatChanged( const QString& )));
00092     connect(m_dialog->m_delimiterBox, SIGNAL(clicked(int)),
00093             this, SLOT(delimiterClicked(int)));
00094     connect(m_dialog->m_delimiterEdit, SIGNAL(returnPressed()),
00095             this, SLOT(returnPressed()));
00096     connect(m_dialog->m_delimiterEdit, SIGNAL(textChanged ( const QString & )),
00097             this, SLOT(formatChanged ( const QString & ) ));
00098     connect(m_dialog->m_comboQuote, SIGNAL(activated(const QString &)),
00099             this, SLOT(textquoteSelected(const QString &)));
00100     connect(m_dialog->m_sheet, SIGNAL(currentChanged(int, int)),
00101             this, SLOT(currentCellChanged(int, int)));
00102     connect(m_dialog->m_ignoreDuplicates, SIGNAL(stateChanged(int)),
00103             this, SLOT(ignoreDuplicatesChanged(int)));
00104     connect(m_dialog->m_updateButton, SIGNAL(clicked()),
00105             this, SLOT(updateClicked()));
00106     connect(m_dialog->comboBoxEncoding, SIGNAL(textChanged ( const QString & )),
00107             this, SLOT(encodingChanged ( const QString & ) ));
00108 }
00109 
00110 CSVDialog::~CSVDialog()
00111 {
00112     kapp->setOverrideCursor(Qt::waitCursor);
00113 }
00114 
00115 void CSVDialog::fillTable( )
00116 {
00117     int row, column;
00118     bool lastCharDelimiter = false;
00119     enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
00120            S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;
00121 
00122     QChar x;
00123     QString field;
00124 
00125     kapp->setOverrideCursor(Qt::waitCursor);
00126 
00127     for (row = 0; row < m_dialog->m_sheet->numRows(); ++row)
00128         for (column = 0; column < m_dialog->m_sheet->numCols(); ++column)
00129             m_dialog->m_sheet->clearCell(row, column);
00130 
00131     int maxColumn = 1;
00132     row = column = 1;
00133     QTextStream inputStream(m_fileArray, IO_ReadOnly);
00134     kdDebug(30501) << "Encoding: " << m_codec->name() << endl;
00135     inputStream.setCodec( m_codec );
00136 
00137     bool lastCharWasCr = false; // Last character was a Carriage Return
00138     while (!inputStream.atEnd()) 
00139     {
00140         inputStream >> x; // read one char
00141 
00142         // ### TODO: we should perhaps skip all other control characters
00143         if ( x == '\r' )
00144         {
00145             // We have a Carriage Return, assume that its role is the one of a LineFeed
00146             lastCharWasCr = true;
00147             x = '\n'; // Replace by Line Feed
00148         }
00149         else if ( x == '\n' && lastCharWasCr )
00150         {
00151             // The end of line was already handled by the Carriage Return, so do nothing for this character
00152             lastCharWasCr = false;
00153             continue;
00154         }
00155         else if ( x == QChar( 0xc ) )
00156         {
00157             // We have a FormFeed, skip it
00158             lastCharWasCr = false;
00159             continue;
00160         }
00161         else
00162         {
00163             lastCharWasCr = false;
00164         }
00165 
00166         if ( column > maxColumn )
00167           maxColumn = column;
00168 
00169         switch (state)
00170         {
00171          case S_START :
00172             if (x == m_textquote)
00173             {
00174                 state = S_QUOTED_FIELD;
00175             }
00176             else if (x == m_delimiter)
00177             {
00178                 if ((m_ignoreDups == false) || (lastCharDelimiter == false))
00179                     ++column;
00180                 lastCharDelimiter = true;
00181             }
00182             else if (x == '\n')
00183             {
00184                 ++row;
00185                 column = 1;
00186                 if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
00187                   break;
00188             }
00189             else
00190             {
00191                 field += x;
00192                 state = S_MAYBE_NORMAL_FIELD;
00193             }
00194             break;
00195          case S_QUOTED_FIELD :
00196             if (x == m_textquote)
00197             {
00198                 state = S_MAYBE_END_OF_QUOTED_FIELD;
00199             }
00200             else
00201             {
00202                 field += x;
00203             }
00204             break;
00205          case S_MAYBE_END_OF_QUOTED_FIELD :
00206             if (x == m_textquote)
00207             {
00208                 field += x;
00209                 state = S_QUOTED_FIELD;
00210             }
00211             else if (x == m_delimiter || x == '\n')
00212             {
00213                 setText(row - m_startRow, column - m_startCol, field);
00214                 field = QString::null;
00215                 if (x == '\n')
00216                 {
00217                     ++row;
00218                     column = 1;
00219                     if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
00220                       break;
00221                 }
00222                 else
00223                 {
00224                     if ((m_ignoreDups == false) || (lastCharDelimiter == false))
00225                         ++column;
00226                     lastCharDelimiter = true;
00227                 }
00228                 state = S_START;
00229             }
00230             else
00231             {
00232                 state = S_END_OF_QUOTED_FIELD;
00233             }
00234             break;
00235          case S_END_OF_QUOTED_FIELD :
00236             if (x == m_delimiter || x == '\n')
00237             {
00238                 setText(row - m_startRow, column - m_startCol, field);
00239                 field = QString::null;
00240                 if (x == '\n')
00241                 {
00242                     ++row;
00243                     column = 1;
00244                     if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
00245                       break;
00246                 }
00247                 else
00248                 {
00249                     if ((m_ignoreDups == false) || (lastCharDelimiter == false))
00250                         ++column;
00251                     lastCharDelimiter = true;
00252                 }
00253                 state = S_START;
00254             }
00255             break;
00256          case S_MAYBE_NORMAL_FIELD :
00257             if (x == m_textquote)
00258             {
00259                 field = QString::null;
00260                 state = S_QUOTED_FIELD;
00261                 break;
00262             }
00263          case S_NORMAL_FIELD :
00264             if (x == m_delimiter || x == '\n')
00265             {
00266                 setText(row - m_startRow, column - m_startCol, field);
00267                 field = QString::null;
00268                 if (x == '\n')
00269                 {
00270                     ++row;
00271                     column = 1;
00272                     if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
00273                       break;
00274                 }
00275                 else
00276                 {
00277                     if ((m_ignoreDups == false) || (lastCharDelimiter == false))
00278                         ++column;
00279                     lastCharDelimiter = true;
00280                 }
00281                 state = S_START;
00282             }
00283             else
00284             {
00285                 field += x;
00286             }
00287         }
00288         if (x != m_delimiter)
00289           lastCharDelimiter = false;
00290     }
00291 
00292     if ( !field.isEmpty() )
00293     {
00294       // the last line of the file had not any line end
00295       setText(row - m_startRow, column - m_startCol, field);
00296       ++row;
00297       field = QString::null;
00298     }
00299     
00300     m_adjustCols = true;
00301     adjustRows( row - m_startRow );
00302     adjustCols( maxColumn - m_startCol );
00303     m_dialog->m_colEnd->setMaxValue( maxColumn );
00304     if ( m_endCol == -1 )
00305       m_dialog->m_colEnd->setValue( maxColumn );
00306     
00307 
00308     for (column = 0; column < m_dialog->m_sheet->numCols(); ++column)
00309     {
00310         const QString header = m_dialog->m_sheet->horizontalHeader()->label(column);
00311         if ( m_formatList.find( header ) == m_formatList.end() )
00312             m_dialog->m_sheet->horizontalHeader()->setLabel(column, i18n("Text"));
00313 
00314         m_dialog->m_sheet->adjustColumn(column);
00315     }
00316     fillComboBox();
00317 
00318     kapp->restoreOverrideCursor();
00319 }
00320 
00321 void CSVDialog::fillComboBox()
00322 {
00323   if ( m_endRow == -1 )
00324     m_dialog->m_rowEnd->setValue( m_dialog->m_sheet->numRows() );  
00325   else
00326     m_dialog->m_rowEnd->setValue( m_endRow );
00327 
00328   if ( m_endCol == -1 )
00329     m_dialog->m_colEnd->setValue( m_dialog->m_sheet->numCols() );
00330   else
00331     m_dialog->m_colEnd->setValue( m_endCol );  
00332 
00333   m_dialog->m_rowEnd->setMinValue( 1 );
00334   m_dialog->m_colEnd->setMinValue( 1 );
00335   m_dialog->m_rowEnd->setMaxValue( m_dialog->m_sheet->numRows() );
00336   m_dialog->m_colEnd->setMaxValue( m_dialog->m_sheet->numCols() );
00337 
00338   m_dialog->m_rowStart->setMinValue( 1 );
00339   m_dialog->m_colStart->setMinValue( 1 );
00340   m_dialog->m_rowStart->setMaxValue( m_dialog->m_sheet->numRows() );
00341   m_dialog->m_colStart->setMaxValue( m_dialog->m_sheet->numCols() );
00342 }
00343 
00344 int CSVDialog::getRows()
00345 {
00346   int rows = m_dialog->m_sheet->numRows();
00347   if ( m_endRow >= 0 )
00348   {
00349     if ( rows > ( m_startRow + m_endRow ) )
00350       rows = m_startRow + m_endRow;
00351   }
00352 
00353   return rows;
00354 }
00355 
00356 int CSVDialog::getCols()
00357 {
00358   int cols = m_dialog->m_sheet->numCols();
00359   if ( m_endCol >= 0 )
00360   {
00361     if ( cols > ( m_startCol + m_endCol ) )
00362       cols = m_startCol + m_endCol;
00363   }
00364 
00365   return cols;
00366 }
00367 
00368 int CSVDialog::getHeader(int col)
00369 {
00370     QString header = m_dialog->m_sheet->horizontalHeader()->label(col);
00371     
00372     if (header == i18n("Text"))
00373         return TEXT;
00374     else if (header == i18n("Number"))
00375         return NUMBER;
00376     else if (header == i18n("Currency"))
00377         return CURRENCY;
00378     else if ( header == i18n( "Date" ) )
00379         return DATE;
00380     else if ( header == i18n( "Decimal Comma Number" ) )
00381         return COMMANUMBER;
00382     else if ( header == i18n( "Decimal Point Number" ) )
00383         return POINTNUMBER;
00384     else
00385         return TEXT; // Should not happen
00386 }
00387 
00388 QString CSVDialog::getText(int row, int col)
00389 {
00390     return m_dialog->m_sheet->text( row, col );
00391 }
00392 
00393 void CSVDialog::setText(int row, int col, const QString& text)
00394 {
00395     if ( row < 1 || col < 1 ) // skipped by the user
00396         return;
00397 
00398     if ( ( row > ( m_endRow - m_startRow ) && m_endRow > 0 ) || ( col > ( m_endCol - m_startCol ) && m_endCol > 0 ) )
00399       return;
00400 
00401     if ( m_dialog->m_sheet->numRows() < row ) 
00402     {
00403         m_dialog->m_sheet->setNumRows( row + 5000 ); /* We add 5000 at a time to limit recalculations */
00404         m_adjustRows = true;
00405     }
00406 
00407     if ( m_dialog->m_sheet->numCols() < col )
00408     {
00409         m_dialog->m_sheet->setNumCols( col );
00410         m_adjustCols = true;
00411     }
00412 
00413     m_dialog->m_sheet->setText( row - 1, col - 1, text );
00414 }
00415 
00416 /*
00417  * Called after the first fillTable() when number of rows are unknown.
00418  */
00419 void CSVDialog::adjustRows(int iRows)
00420 {
00421     if (m_adjustRows) 
00422     {
00423         m_dialog->m_sheet->setNumRows( iRows );
00424         m_adjustRows = false;
00425     }
00426 }
00427 
00428 void CSVDialog::adjustCols(int iCols)
00429 {
00430     if (m_adjustCols) 
00431     {  
00432         m_dialog->m_sheet->setNumCols( iCols );
00433         m_adjustCols = false;
00434 
00435         if ( m_endCol == -1 )
00436         {
00437           if ( iCols > ( m_endCol - m_startCol ) )
00438             iCols = m_endCol - m_startCol;
00439 
00440           m_dialog->m_sheet->setNumCols( iCols );
00441         }
00442     }
00443 }
00444 
00445 void CSVDialog::returnPressed()
00446 {
00447     if (m_dialog->m_delimiterBox->id(m_dialog->m_delimiterBox->selected()) != 4)
00448         return;
00449 
00450     m_delimiter = m_dialog->m_delimiterEdit->text();
00451     fillTable();
00452 }
00453 
00454 void CSVDialog::textChanged ( const QString & )
00455 {
00456     m_dialog->m_radioOther->setChecked ( true );
00457     delimiterClicked(4); // other
00458 }
00459 
00460 void CSVDialog::formatChanged( const QString& newValue )
00461 {
00462     //kdDebug(30501) << "CSVDialog::formatChanged:" << newValue << endl;
00463     for ( int i = 0; i < m_dialog->m_sheet->numSelections(); ++i )
00464     {
00465         QTableSelection select ( m_dialog->m_sheet->selection( i ) );
00466         for ( int j = select.leftCol(); j <= select.rightCol() ; ++j )
00467         {
00468             m_dialog->m_sheet->horizontalHeader()->setLabel( j, newValue );
00469             
00470         }
00471     }
00472 }
00473 
00474 void CSVDialog::delimiterClicked(int id)
00475 {
00476     switch (id)
00477     {
00478     case 0: // comma
00479         m_delimiter = ",";
00480         break;
00481     case 4: // other
00482         m_delimiter = m_dialog->m_delimiterEdit->text();
00483         break;
00484     case 2: // tab
00485         m_delimiter = "\t";
00486         break;
00487     case 3: // space
00488         m_delimiter = " ";
00489         break;
00490     case 1: // semicolon
00491         m_delimiter = ";";
00492         break;
00493     }
00494 
00495     fillTable();
00496 }
00497 
00498 void CSVDialog::textquoteSelected(const QString& mark)
00499 {
00500     if (mark == i18n("None"))
00501         m_textquote = 0;
00502     else
00503         m_textquote = mark[0];
00504 
00505     fillTable();
00506 }
00507 
00508 void CSVDialog::updateClicked()
00509 {
00510   if ( !checkUpdateRange() )
00511     return;
00512 
00513   m_startRow = m_dialog->m_rowStart->value() - 1;
00514   m_endRow   = m_dialog->m_rowEnd->value();
00515 
00516   m_startCol  = m_dialog->m_colStart->value() - 1;
00517   m_endCol    = m_dialog->m_colEnd->value();
00518 
00519   fillTable();
00520 }
00521 
00522 bool CSVDialog::checkUpdateRange()
00523 {
00524   if ( ( m_dialog->m_rowStart->value() > m_dialog->m_rowEnd->value() ) 
00525        || ( m_dialog->m_colStart->value() > m_dialog->m_colEnd->value() ) )
00526   {
00527     KMessageBox::error( this, i18n( "Please check the ranges you specified. The start value must be lower than the end value." ) );
00528     return false;
00529   }
00530 
00531   return true;
00532 }
00533 
00534 void CSVDialog::currentCellChanged(int, int col)
00535 {
00536     const QString header = m_dialog->m_sheet->horizontalHeader()->label(col);
00537     m_dialog->m_formatComboBox->setCurrentText( header );
00538 }
00539 
00540 void CSVDialog::ignoreDuplicatesChanged(int)
00541 {
00542   if (m_dialog->m_ignoreDuplicates->isChecked())
00543     m_ignoreDups = true;
00544   else
00545     m_ignoreDups = false;
00546   fillTable();
00547 }
00548 
00549 QTextCodec* CSVDialog::getCodec(void) const
00550 {
00551     const QString strCodec( KGlobal::charsets()->encodingForName( m_dialog->comboBoxEncoding->currentText() ) );
00552     kdDebug(30502) << "Encoding: " << strCodec << endl;
00553 
00554     bool ok = false;
00555     QTextCodec* codec = QTextCodec::codecForName( strCodec.utf8() );
00556 
00557     // If QTextCodec has not found a valid encoding, so try with KCharsets.
00558     if ( codec )
00559     {
00560         ok = true;
00561     }
00562     else
00563     {
00564         codec = KGlobal::charsets()->codecForName( strCodec, ok );
00565     }
00566 
00567     // Still nothing?
00568     if ( !codec || !ok )
00569     {
00570         // Default: UTF-8
00571         kdWarning(30502) << "Cannot find encoding:" << strCodec << endl;
00572         // ### TODO: what parent to use?
00573         KMessageBox::error( 0, i18n("Cannot find encoding: %1").arg( strCodec ) );
00574         return 0;
00575     }
00576 
00577     return codec;
00578 }
00579 
00580 void CSVDialog::encodingChanged ( const QString & )
00581 {
00582     QTextCodec* codec = getCodec();
00583 
00584     if ( codec )
00585     {
00586         m_codec = codec;
00587         fillTable();
00588     }
00589 }
00590 
00591 
00592 #include <csvdialog.moc>
KDE Home | KDE Accessibility Home | Description of Access Keys