kchart

csvimportdialog.cc

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