00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
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 )
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>