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