/* This file is part of the KDE project Copyright (C) 2002-2003 Norbert Andres (C) 2002-2003 Ariya Hidayat (C) 2002 Laurent Montel (C) 1999 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kspread_dlg_csv.h" using namespace KSpread; CSVDialog::CSVDialog( View * parent, const char * name, TQRect const & rect, Mode mode) : KDialogBase( parent, name, true, TQString(), Ok|Cancel ), m_pView( parent ), m_cancelled( false ), m_adjustRows( 0 ), m_startline( 0 ), m_textquote( '"' ), m_delimiter( "," ), m_targetRect( rect ), m_mode( mode ) { if ( !name ) setName( "CSV" ); setSizeGripEnabled( TRUE ); TQWidget* page = new TQWidget( this ); setMainWidget( page ); // MyDialogLayout = new TQGridLayout( page, 4, 4, marginHint(), spacingHint(), "MyDialogLayout"); MyDialogLayout = new TQGridLayout( page, 1, 1, 11, 6, "MyDialogLayout"); // Limit the range int column = m_targetRect.left(); Cell* lastCell = m_pView->activeSheet()->getLastCellColumn( column ); if( lastCell ) if( m_targetRect.bottom() > lastCell->row() ) m_targetRect.setBottom( lastCell->row() ); m_sheet = new TQTable( page, "m_table" ); //m_sheet->setSizePolicy( TQSizePolicy( (TQSizePolicy::SizeType)5, (TQSizePolicy::SizeType)7, 0, 0, m_sheet->sizePolicy().hasHeightForWidth() ) ); m_sheet->setNumRows( 0 ); m_sheet->setNumCols( 0 ); MyDialogLayout->addMultiCellWidget( m_sheet, 3, 3, 0, 3 ); // Delimiter: comma, semicolon, tab, space, other m_delimiterBox = new TQButtonGroup( page, "m_delimiterBox" ); m_delimiterBox->setSizePolicy( TQSizePolicy( (TQSizePolicy::SizeType)1, (TQSizePolicy::SizeType)1, 0, 0, m_delimiterBox->sizePolicy().hasHeightForWidth() ) ); m_delimiterBox->setTitle( i18n( "Delimiter" ) ); m_delimiterBox->setColumnLayout(0, TQt::Vertical ); m_delimiterBox->layout()->setSpacing( KDialog::spacingHint() ); m_delimiterBox->layout()->setMargin( KDialog::marginHint() ); m_delimiterBoxLayout = new TQGridLayout( m_delimiterBox->layout() ); m_delimiterBoxLayout->setAlignment( TQt::AlignTop ); MyDialogLayout->addMultiCellWidget( m_delimiterBox, 0, 2, 0, 0 ); m_ignoreDuplicates = new TQCheckBox( page, "m_ignoreDuplicates" ); m_ignoreDuplicates->setText( i18n( "Ignore duplicate delimiters" ) ); MyDialogLayout->addMultiCellWidget( m_ignoreDuplicates, 2, 2, 2, 3 ); m_radioComma = new TQRadioButton( m_delimiterBox, "m_radioComma" ); m_radioComma->setText( i18n( "Comma" ) ); m_radioComma->setChecked( TRUE ); m_delimiterBoxLayout->addWidget( m_radioComma, 0, 0 ); m_radioSemicolon = new TQRadioButton( m_delimiterBox, "m_radioSemicolon" ); m_radioSemicolon->setText( i18n( "Semicolon" ) ); m_delimiterBoxLayout->addWidget( m_radioSemicolon, 0, 1 ); m_radioTab = new TQRadioButton( m_delimiterBox, "m_radioTab" ); m_radioTab->setText( i18n( "Tabulator" ) ); m_delimiterBoxLayout->addWidget( m_radioTab, 1, 0 ); m_radioSpace = new TQRadioButton( m_delimiterBox, "m_radioSpace" ); m_radioSpace->setText( i18n( "Space" ) ); m_delimiterBoxLayout->addWidget( m_radioSpace, 1, 1 ); m_radioOther = new TQRadioButton( m_delimiterBox, "m_radioOther" ); m_radioOther->setText( i18n( "Other" ) ); m_delimiterBoxLayout->addWidget( m_radioOther, 0, 2 ); m_delimiterEdit = new TQLineEdit( m_delimiterBox, "m_delimiterEdit" ); m_delimiterEdit->setSizePolicy( TQSizePolicy( (TQSizePolicy::SizeType)0, (TQSizePolicy::SizeType)0, 0, 0, m_delimiterEdit->sizePolicy().hasHeightForWidth() ) ); m_delimiterEdit->setMaximumSize( TQSize( 30, 32767 ) ); m_delimiterBoxLayout->addWidget( m_delimiterEdit, 1, 2 ); // Format: number, text, currency, m_formatBox = new TQButtonGroup( page, "m_formatBox" ); m_formatBox->setSizePolicy( TQSizePolicy( (TQSizePolicy::SizeType)1, (TQSizePolicy::SizeType)1, 0, 0, m_formatBox->sizePolicy().hasHeightForWidth() ) ); m_formatBox->setTitle( i18n( "Format" ) ); m_formatBox->setColumnLayout(0, TQt::Vertical ); m_formatBox->layout()->setSpacing( KDialog::spacingHint() ); m_formatBox->layout()->setMargin( KDialog::marginHint() ); m_formatBoxLayout = new TQGridLayout( m_formatBox->layout() ); m_formatBoxLayout->setAlignment( TQt::AlignTop ); MyDialogLayout->addMultiCellWidget( m_formatBox, 0, 2, 1, 1 ); m_radioNumber = new TQRadioButton( m_formatBox, "m_radioNumber" ); m_radioNumber->setText( i18n( "Number" ) ); m_formatBoxLayout->addMultiCellWidget( m_radioNumber, 1, 1, 0, 1 ); m_radioText = new TQRadioButton( m_formatBox, "m_radioText" ); m_radioText->setText( i18n( "Text" ) ); m_radioText->setChecked( TRUE ); m_formatBoxLayout->addWidget( m_radioText, 0, 0 ); m_radioCurrency = new TQRadioButton( m_formatBox, "m_radioCurrency" ); m_radioCurrency->setText( i18n( "Currency" ) ); m_formatBoxLayout->addMultiCellWidget( m_radioCurrency, 0, 0, 1, 2 ); m_radioDate = new TQRadioButton( m_formatBox, "m_radioDate" ); m_radioDate->setText( i18n( "Date" ) ); m_formatBoxLayout->addWidget( m_radioDate, 1, 2 ); m_comboLine = new TQComboBox( FALSE, page, "m_comboLine" ); m_comboLine->insertItem( i18n( "1" ) ); m_comboLine->setSizePolicy( TQSizePolicy( (TQSizePolicy::SizeType)1, (TQSizePolicy::SizeType)0, 0, 0, m_comboLine->sizePolicy().hasHeightForWidth() ) ); MyDialogLayout->addWidget( m_comboLine, 1, 3 ); m_comboQuote = new TQComboBox( FALSE, page, "m_comboQuote" ); m_comboQuote->insertItem( i18n( "\"" ) ); m_comboQuote->insertItem( i18n( "'" ) ); m_comboQuote->insertItem( i18n( "None" ) ); m_comboQuote->setSizePolicy( TQSizePolicy( (TQSizePolicy::SizeType)1, (TQSizePolicy::SizeType)0, 0, 0, m_comboQuote->sizePolicy().hasHeightForWidth() ) ); MyDialogLayout->addWidget( m_comboQuote, 1, 2 ); TQSpacerItem* spacer_2 = new TQSpacerItem( 0, 0, TQSizePolicy::Minimum, TQSizePolicy::Preferred ); MyDialogLayout->addItem( spacer_2, 2, 3 ); TextLabel3 = new TQLabel( page, "TextLabel3" ); TextLabel3->setSizePolicy( TQSizePolicy( (TQSizePolicy::SizeType)1, (TQSizePolicy::SizeType)0, 0, 0, TextLabel3->sizePolicy().hasHeightForWidth() ) ); TextLabel3->setText( i18n( "Start at line:" ) ); MyDialogLayout->addWidget( TextLabel3, 0, 3 ); TextLabel2 = new TQLabel( page, "TextLabel2" ); TextLabel2->setSizePolicy( TQSizePolicy( (TQSizePolicy::SizeType)1, (TQSizePolicy::SizeType)0, 0, 0, TextLabel2->sizePolicy().hasHeightForWidth() ) ); TextLabel2->setText( i18n( "Textquote:" ) ); MyDialogLayout->addWidget( TextLabel2, 0, 2 ); if ( m_mode == Clipboard ) { setCaption( i18n( "Inserting From Clipboard" ) ); TQMimeSource * mime = TQApplication::clipboard()->data(); if ( !mime ) { KMessageBox::information( this, i18n("There is no data in the clipboard.") ); m_cancelled = true; return; } if ( !mime->provides( "text/plain" ) ) { KMessageBox::information( this, i18n("There is no usable data in the clipboard.") ); m_cancelled = true; return; } m_fileArray = TQByteArray(mime->encodedData( "text/plain" ) ); } else if ( mode == File ) { setCaption( i18n( "Inserting Text File" ) ); TQString file = KFileDialog::getOpenFileName(":", "text/plain", this); //cancel action ! if ( file.isEmpty() ) { actionButton( Ok )->setEnabled( false ); m_cancelled = true; return; } TQFile in(file); if (!in.open(IO_ReadOnly)) { KMessageBox::sorry( this, i18n("Cannot open input file.") ); in.close(); actionButton( Ok )->setEnabled( false ); m_cancelled = true; return; } m_fileArray = TQByteArray(in.size()); in.readBlock(m_fileArray.data(), in.size()); in.close(); } else { setCaption( i18n( "Text to Columns" ) ); m_data = ""; Cell * cell; Sheet * sheet = m_pView->activeSheet(); int col = m_targetRect.left(); for (int i = m_targetRect.top(); i <= m_targetRect.bottom(); ++i) { cell = sheet->cellAt( col, i ); if ( !cell->isEmpty() && !cell->isDefault() ) { m_data += cell->strOutText(); } m_data += "\n"; } } fillSheet(); fillComboBox(); resize(sizeHint()); m_sheet->setSelectionMode(TQTable::NoSelection); connect(m_formatBox, TQT_SIGNAL(clicked(int)), this, TQT_SLOT(formatClicked(int))); connect(m_delimiterBox, TQT_SIGNAL(clicked(int)), this, TQT_SLOT(delimiterClicked(int))); connect(m_delimiterEdit, TQT_SIGNAL(returnPressed()), this, TQT_SLOT(returnPressed())); connect(m_delimiterEdit, TQT_SIGNAL(textChanged ( const TQString & )), this, TQT_SLOT(textChanged ( const TQString & ) )); connect(m_comboLine, TQT_SIGNAL(activated(const TQString&)), this, TQT_SLOT(lineSelected(const TQString&))); connect(m_comboQuote, TQT_SIGNAL(activated(const TQString&)), this, TQT_SLOT(textquoteSelected(const TQString&))); connect(m_sheet, TQT_SIGNAL(currentChanged(int, int)), this, TQT_SLOT(currentCellChanged(int, int))); connect(m_ignoreDuplicates, TQT_SIGNAL(stateChanged(int)), this, TQT_SLOT(ignoreDuplicatesChanged(int))); } CSVDialog::~CSVDialog() { // no need to delete child widgets, TQt does it all for us } bool CSVDialog::cancelled() { return m_cancelled; } void CSVDialog::fillSheet() { int row, column; bool lastCharDelimiter = false; bool ignoreDups = m_ignoreDuplicates->isChecked(); enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD, S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START; TQChar x; TQString field = ""; for (row = 0; row < m_sheet->numRows(); ++row) for (column = 0; column < m_sheet->numCols(); ++column) m_sheet->clearCell(row, column); row = column = 1; if (m_mode != Column) { m_mode = Column; m_data = TQString(m_fileArray); m_fileArray.resize(0); } TQTextStream inputStream(m_data, IO_ReadOnly); inputStream.setEncoding(TQTextStream::Locale); while (!inputStream.atEnd()) { inputStream >> x; // read one char if (x == '\r') inputStream >> x; // eat '\r', to handle DOS/LOSEDOWS files correctly switch (state) { case S_START : if (x == m_textquote) { state = S_QUOTED_FIELD; } else if (x == m_delimiter) { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } else if (x == '\n') { ++row; column = 1; } else { field += x; state = S_MAYBE_NORMAL_FIELD; } break; case S_QUOTED_FIELD : if (x == m_textquote) { state = S_MAYBE_END_OF_QUOTED_FIELD; } else if (x == '\n') { setText(row - m_startline, column, field); field = ""; if (x == '\n') { ++row; column = 1; } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { field += x; } break; case S_MAYBE_END_OF_QUOTED_FIELD : if (x == m_textquote) { field += x; state = S_QUOTED_FIELD; } else if (x == m_delimiter || x == '\n') { setText(row - m_startline, column, field); field = ""; if (x == '\n') { ++row; column = 1; } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { state = S_END_OF_QUOTED_FIELD; } break; case S_END_OF_QUOTED_FIELD : if (x == m_delimiter || x == '\n') { setText(row - m_startline, column, field); field = ""; if (x == '\n') { ++row; column = 1; } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { state = S_END_OF_QUOTED_FIELD; } break; case S_MAYBE_NORMAL_FIELD : if (x == m_textquote) { field = ""; state = S_QUOTED_FIELD; break; } case S_NORMAL_FIELD : if (x == m_delimiter || x == '\n') { setText(row - m_startline, column, field); field = ""; if (x == '\n') { ++row; column = 1; } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { field += x; } } if (x != m_delimiter) lastCharDelimiter = false; } // file with only one line without '\n' if (field.length() > 0) { setText(row - m_startline, column, field); ++row; field = ""; } adjustRows( row - m_startline ); for (column = 0; column < m_sheet->numCols(); ++column) { TQString header = m_sheet->horizontalHeader()->label(column); if (header != i18n("Text") && header != i18n("Number") && header != i18n("Date") && header != i18n("Currency")) m_sheet->horizontalHeader()->setLabel(column, i18n("Text")); m_sheet->adjustColumn(column); } } void CSVDialog::fillComboBox() { m_comboLine->clear(); for (int row = 0; row < m_sheet->numRows(); ++row) m_comboLine->insertItem(TQString::number(row + 1), row); } void CSVDialog::setText(int row, int col, const TQString& text) { if (row < 1) // skipped by the user return; if (m_sheet->numRows() < row) { m_sheet->setNumRows(row+5000); /* We add 5000 at a time to limit recalculations */ m_adjustRows=1; } if (m_sheet->numCols() < col) m_sheet->setNumCols(col); m_sheet->setText(row - 1, col - 1, text); } /* * Called after the first fillSheet() when number of rows are unknown. */ void CSVDialog::adjustRows(int iRows) { if (m_adjustRows) { m_sheet->setNumRows( iRows ); m_adjustRows=0; } } void CSVDialog::returnPressed() { if (m_delimiterBox->id(m_delimiterBox->selected()) != 4) return; m_delimiter = m_delimiterEdit->text(); fillSheet(); } void CSVDialog::textChanged ( const TQString & ) { m_radioOther->setChecked ( true ); delimiterClicked(4); // other } void CSVDialog::formatClicked(int id) { TQString header; switch (id) { case 1: // text header = i18n("Text"); break; case 0: // number header = i18n("Number"); break; case 3: // date header = i18n("Date"); break; case 2: // currency header = i18n("Currency"); break; } m_sheet->horizontalHeader()->setLabel(m_sheet->currentColumn(), header); } void CSVDialog::delimiterClicked(int id) { switch (id) { case 0: // comma m_delimiter = ","; break; case 4: // other m_delimiter = m_delimiterEdit->text(); break; case 2: // tab m_delimiter = "\t"; break; case 3: // space m_delimiter = " "; break; case 1: // semicolon m_delimiter = ";"; break; } fillSheet(); } void CSVDialog::textquoteSelected(const TQString& mark) { if (mark == i18n("none")) m_textquote = 0; else m_textquote = mark[0]; fillSheet(); } void CSVDialog::lineSelected(const TQString& line) { m_startline = line.toInt() - 1; fillSheet(); } void CSVDialog::currentCellChanged(int, int col) { int id; TQString header = m_sheet->horizontalHeader()->label(col); if (header == i18n("Text")) id = 1; else if (header == i18n("Number")) id = 0; else if (header == i18n("Date")) id = 3; else id = 2; m_formatBox->setButton(id); } void CSVDialog::accept() { Sheet * sheet = m_pView->activeSheet(); TQString csv_delimiter = TQString(); Cell * cell; int numRows = m_sheet->numRows(); int numCols = m_sheet->numCols(); if (numRows == 0) ++numRows; if ( (numCols > m_targetRect.width()) && (m_targetRect.width() > 1) ) { numCols = m_targetRect.width(); } else m_targetRect.setRight( m_targetRect.left() + numCols ); if ( (numRows > m_targetRect.height()) && (m_targetRect.height() > 1) ) numRows = m_targetRect.height(); else m_targetRect.setBottom( m_targetRect.top() + numRows ); if ( numRows == 1 && numCols == 1) { Doc * doc = m_pView->doc(); cell = sheet->nonDefaultCell( m_targetRect.left(), m_targetRect.top() ); if ( !doc->undoLocked() ) { UndoSetText * undo = new UndoSetText( doc, sheet , cell->text(), m_targetRect.left(), m_targetRect.top(), cell->formatType() ); doc->addCommand( undo ); } } else { UndoChangeAreaTextCell * undo = new UndoChangeAreaTextCell( m_pView->doc(), sheet , m_targetRect ); m_pView->doc()->addCommand( undo ); } m_pView->doc()->emitBeginOperation(); int i; int left = m_targetRect.left(); int top = m_targetRect.top(); TQMemArray widths( numCols ); for ( i = 0; i < numCols; ++i ) { ColumnFormat * c = sheet->nonDefaultColumnFormat( left + i ); widths[i] = c->dblWidth(); } for (int row = 0; row < numRows; ++row) { for (int col = 0; col < numCols; ++col) { cell = sheet->nonDefaultCell( left + col, top + row ); cell->setCellText( getText( row, col ) ); TQFontMetrics fm = sheet->painter().fontMetrics(); double w = fm.width( cell->strOutText() ); if ( w == 0.0 ) { TQFontMetrics fm( cell->format()->textFont( left + col, top + row ) ); w = fm.width('x') * (double) getText( row, col ).length(); } if ( w > widths[col] ) widths[col] = w; cell->format()->setFormatType(Generic_format); /* Disabling this code for now, everything will use Generic formatting, hoping for the best (Tomas) //### FIXME: long term solution is to allow to select Generic format ("autodetect") in the dialog and make it the default switch (getHeader(col)) { case TEXT: break; case NUMBER: cell->format()->setFormatType(Number_format); cell->setPrecision(2); break; case DATE: cell->format()->setFormatType(ShortDate_format); break; case CURRENCY: cell->format()->setFormatType(Money_format); break; } */ } } for ( i = 0; i < numCols; ++i ) { ColumnFormat * c = sheet->nonDefaultColumnFormat( left + i ); c->setDblWidth( widths[i] ); sheet->emit_updateColumn( c, left + i ); } m_pView->slotUpdateView( sheet ); TQDialog::accept(); } int CSVDialog::getHeader(int col) { TQString header = m_sheet->horizontalHeader()->label(col); if (header == i18n("Text")) return TEXT; else if (header == i18n("Number")) return NUMBER; else if (header == i18n("Currency")) return CURRENCY; else return DATE; } TQString CSVDialog::getText(int row, int col) { return m_sheet->text(row, col); } void CSVDialog::ignoreDuplicatesChanged(int) { fillSheet(); } #include "kspread_dlg_csv.moc"