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