csvdialog.cpp
1/* This file is part of the KDE project
2 Copyright (C) 1999 David Faure <faure@kde.org>
3 Copyright (C) 2004 Nicolas GOUTTE <goutte@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19*/
20
21#include <dialogui.h>
22#include <csvdialog.h>
23
24#include <tqtable.h>
25#include <tqcheckbox.h>
26#include <tqcursor.h>
27#include <tqlineedit.h>
28#include <tqcombobox.h>
29#include <tqspinbox.h>
30#include <tqtextstream.h>
31#include <tqbuttongroup.h>
32#include <tqpushbutton.h>
33#include <tqradiobutton.h>
34#include <tqtextcodec.h>
35
36#include <tdeapplication.h>
37#include <tdeconfig.h>
38#include <kdebug.h>
39#include <tdelocale.h>
40#include <kcombobox.h>
41#include <tdemessagebox.h>
42#include <kcharsets.h>
43
44CSVDialog::CSVDialog(TQWidget* parent, TQByteArray& fileArray, const TQString /*seperator*/)
45 : KDialogBase(parent, 0, true, TQString(), Ok|Cancel, No, true),
46 m_adjustRows(false),
47 m_adjustCols(false),
48 m_startRow(0),
49 m_startCol(0),
50 m_endRow(-1),
51 m_endCol(-1),
52 m_textquote('"'),
53 m_delimiter(","),
54 m_ignoreDups(false),
55 m_fileArray(fileArray),
56 m_dialog(new DialogUI(this)),
57 m_codec( TQTextCodec::codecForName( "UTF-8" ) )
58{
59 setCaption( i18n( "Import" ) );
60 kapp->restoreOverrideCursor();
61
62 TQStringList encodings;
63 encodings << i18n( "Descriptive encoding name", "Recommended ( %1 )" ).arg( "UTF-8" );
64 encodings << i18n( "Descriptive encoding name", "Locale ( %1 )" ).arg( TQTextCodec::codecForLocale()->name() );
65 encodings += TDEGlobal::charsets()->descriptiveEncodingNames();
66 // Add a few non-standard encodings, which might be useful for text files
67 const TQString description(i18n("Descriptive encoding name","Other ( %1 )"));
68 encodings << description.arg("Apple Roman"); // Apple
69 encodings << description.arg("IBM 850") << description.arg("IBM 866"); // MS DOS
70 encodings << description.arg("CP 1258"); // Windows
71 m_dialog->comboBoxEncoding->insertStringList(encodings);
72
73 m_formatList << i18n( "Text" );
74 m_formatList << i18n( "Number" );
75 m_formatList << i18n( "Currency" );
76 m_formatList << i18n( "Date" );
77 m_formatList << i18n( "Decimal Comma Number" );
78 m_formatList << i18n( "Decimal Point Number" );
79 m_dialog->m_formatComboBox->insertStringList( m_formatList );
80
81 m_dialog->m_sheet->setReadOnly( true );
82
83 loadSettings();
84
85 fillTable();
86
87 //resize(sizeHint());
88 resize( 600, 400 ); // Try to show as much as possible of the table view
89 setMainWidget(m_dialog);
90
91 m_dialog->m_sheet->setSelectionMode( TQTable::Multi );
92
93 connect(m_dialog->m_formatComboBox, TQ_SIGNAL(activated( const TQString& )),
94 this, TQ_SLOT(formatChanged( const TQString& )));
95 connect(m_dialog->m_delimiterBox, TQ_SIGNAL(clicked(int)),
96 this, TQ_SLOT(delimiterClicked(int)));
97 connect(m_dialog->m_delimiterEdit, TQ_SIGNAL(returnPressed()),
98 this, TQ_SLOT(returnPressed()));
99 connect(m_dialog->m_delimiterEdit, TQ_SIGNAL(textChanged ( const TQString & )),
100 this, TQ_SLOT(formatChanged ( const TQString & ) ));
101 connect(m_dialog->m_comboQuote, TQ_SIGNAL(activated(const TQString &)),
102 this, TQ_SLOT(textquoteSelected(const TQString &)));
103 connect(m_dialog->m_sheet, TQ_SIGNAL(currentChanged(int, int)),
104 this, TQ_SLOT(currentCellChanged(int, int)));
105 connect(m_dialog->m_ignoreDuplicates, TQ_SIGNAL(stateChanged(int)),
106 this, TQ_SLOT(ignoreDuplicatesChanged(int)));
107 connect(m_dialog->m_updateButton, TQ_SIGNAL(clicked()),
108 this, TQ_SLOT(updateClicked()));
109 connect(m_dialog->comboBoxEncoding, TQ_SIGNAL(textChanged ( const TQString & )),
110 this, TQ_SLOT(encodingChanged ( const TQString & ) ));
111}
112
113CSVDialog::~CSVDialog()
114{
115 saveSettings();
116 kapp->setOverrideCursor(TQt::waitCursor);
117}
118
119void CSVDialog::loadSettings()
120{
121 TDEConfig *config = kapp->config();
122 config->setGroup("CSVDialog Settings");
123 m_textquote = config->readEntry("textquote", "\"")[0];
124 m_delimiter = config->readEntry("delimiter", ",");
125 m_ignoreDups = config->readBoolEntry("ignoreDups", false);
126 const TQString codecText = config->readEntry("codec", "");
127
128 // update widgets
129 if (!codecText.isEmpty()) {
130 m_dialog->comboBoxEncoding->setCurrentText(codecText);
131 m_codec = getCodec();
132 }
133 if (m_delimiter == ",") m_dialog->m_radioComma->setChecked(true);
134 else if (m_delimiter == "\t") m_dialog->m_radioTab->setChecked(true);
135 else if (m_delimiter == " ") m_dialog->m_radioSpace->setChecked(true);
136 else if (m_delimiter == ";") m_dialog->m_radioSemicolon->setChecked(true);
137 else {
138 m_dialog->m_radioOther->setChecked(true);
139 m_dialog->m_delimiterEdit->setText(m_delimiter);
140 }
141 m_dialog->m_ignoreDuplicates->setChecked(m_ignoreDups);
142 m_dialog->m_comboQuote->setCurrentItem(m_textquote == '\'' ? 1
143 : m_textquote == '"' ? 0 : 2);
144}
145
146void CSVDialog::saveSettings()
147{
148 TDEConfig *config = kapp->config();
149 config->setGroup("CSVDialog Settings");
150 TQString q = m_textquote;
151 config->writeEntry("textquote", q);
152 config->writeEntry("delimiter", m_delimiter);
153 config->writeEntry("ignoreDups", m_ignoreDups);
154 config->writeEntry("codec", m_dialog->comboBoxEncoding->currentText());
155 config->sync();
156}
157
158void CSVDialog::fillTable( )
159{
160 int row, column;
161 bool lastCharDelimiter = false;
162 enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
163 S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;
164
165 TQChar x;
166 TQString field;
167
168 kapp->setOverrideCursor(TQt::waitCursor);
169
170 for (row = 0; row < m_dialog->m_sheet->numRows(); ++row)
171 for (column = 0; column < m_dialog->m_sheet->numCols(); ++column)
172 m_dialog->m_sheet->clearCell(row, column);
173
174 int maxColumn = 1;
175 row = column = 1;
176 TQTextStream inputStream(m_fileArray, IO_ReadOnly);
177 kdDebug(30501) << "Encoding: " << m_codec->name() << endl;
178 inputStream.setCodec( m_codec );
179
180 bool lastCharWasCr = false; // Last character was a Carriage Return
181 while (!inputStream.atEnd())
182 {
183 inputStream >> x; // read one char
184
185 // ### TODO: we should perhaps skip all other control characters
186 if ( x == '\r' )
187 {
188 // We have a Carriage Return, assume that its role is the one of a LineFeed
189 lastCharWasCr = true;
190 x = '\n'; // Replace by Line Feed
191 }
192 else if ( x == '\n' && lastCharWasCr )
193 {
194 // The end of line was already handled by the Carriage Return, so do nothing for this character
195 lastCharWasCr = false;
196 continue;
197 }
198 else if ( x == TQChar( 0xc ) )
199 {
200 // We have a FormFeed, skip it
201 lastCharWasCr = false;
202 continue;
203 }
204 else
205 {
206 lastCharWasCr = false;
207 }
208
209 if ( column > maxColumn )
210 maxColumn = column;
211
212 switch (state)
213 {
214 case S_START :
215 if (x == m_textquote)
216 {
217 state = S_QUOTED_FIELD;
218 }
219 else if (x == m_delimiter)
220 {
221 if ((m_ignoreDups == false) || (lastCharDelimiter == false))
222 ++column;
223 lastCharDelimiter = true;
224 }
225 else if (x == '\n')
226 {
227 ++row;
228 column = 1;
229 if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
230 break;
231 }
232 else
233 {
234 field += x;
235 state = S_MAYBE_NORMAL_FIELD;
236 }
237 break;
238 case S_QUOTED_FIELD :
239 if (x == m_textquote)
240 {
241 state = S_MAYBE_END_OF_QUOTED_FIELD;
242 }
243 else
244 {
245 field += x;
246 }
247 break;
248 case S_MAYBE_END_OF_QUOTED_FIELD :
249 if (x == m_textquote)
250 {
251 field += x;
252 state = S_QUOTED_FIELD;
253 }
254 else if (x == m_delimiter || x == '\n')
255 {
256 setText(row - m_startRow, column - m_startCol, field);
257 field = TQString();
258 if (x == '\n')
259 {
260 ++row;
261 column = 1;
262 if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
263 break;
264 }
265 else
266 {
267 if ((m_ignoreDups == false) || (lastCharDelimiter == false))
268 ++column;
269 lastCharDelimiter = true;
270 }
271 state = S_START;
272 }
273 else
274 {
275 state = S_END_OF_QUOTED_FIELD;
276 }
277 break;
278 case S_END_OF_QUOTED_FIELD :
279 if (x == m_delimiter || x == '\n')
280 {
281 setText(row - m_startRow, column - m_startCol, field);
282 field = TQString();
283 if (x == '\n')
284 {
285 ++row;
286 column = 1;
287 if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
288 break;
289 }
290 else
291 {
292 if ((m_ignoreDups == false) || (lastCharDelimiter == false))
293 ++column;
294 lastCharDelimiter = true;
295 }
296 state = S_START;
297 }
298 break;
299 case S_MAYBE_NORMAL_FIELD :
300 if (x == m_textquote)
301 {
302 field = TQString();
303 state = S_QUOTED_FIELD;
304 break;
305 }
306 case S_NORMAL_FIELD :
307 if (x == m_delimiter || x == '\n')
308 {
309 setText(row - m_startRow, column - m_startCol, field);
310 field = TQString();
311 if (x == '\n')
312 {
313 ++row;
314 column = 1;
315 if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
316 break;
317 }
318 else
319 {
320 if ((m_ignoreDups == false) || (lastCharDelimiter == false))
321 ++column;
322 lastCharDelimiter = true;
323 }
324 state = S_START;
325 }
326 else
327 {
328 field += x;
329 }
330 }
331 if (x != m_delimiter)
332 lastCharDelimiter = false;
333 }
334
335 if ( !field.isEmpty() )
336 {
337 // the last line of the file had not any line end
338 setText(row - m_startRow, column - m_startCol, field);
339 ++row;
340 field = TQString();
341 }
342
343 m_adjustCols = true;
344 adjustRows( row - m_startRow );
345 adjustCols( maxColumn - m_startCol );
346 m_dialog->m_colEnd->setMaxValue( maxColumn );
347 if ( m_endCol == -1 )
348 m_dialog->m_colEnd->setValue( maxColumn );
349
350
351 for (column = 0; column < m_dialog->m_sheet->numCols(); ++column)
352 {
353 const TQString header = m_dialog->m_sheet->horizontalHeader()->label(column);
354 if ( m_formatList.find( header ) == m_formatList.end() )
355 m_dialog->m_sheet->horizontalHeader()->setLabel(column, i18n("Text"));
356
357 m_dialog->m_sheet->adjustColumn(column);
358 }
359 fillComboBox();
360
361 kapp->restoreOverrideCursor();
362}
363
364void CSVDialog::fillComboBox()
365{
366 if ( m_endRow == -1 )
367 m_dialog->m_rowEnd->setValue( m_dialog->m_sheet->numRows() );
368 else
369 m_dialog->m_rowEnd->setValue( m_endRow );
370
371 if ( m_endCol == -1 )
372 m_dialog->m_colEnd->setValue( m_dialog->m_sheet->numCols() );
373 else
374 m_dialog->m_colEnd->setValue( m_endCol );
375
376 m_dialog->m_rowEnd->setMinValue( 1 );
377 m_dialog->m_colEnd->setMinValue( 1 );
378 m_dialog->m_rowEnd->setMaxValue( m_dialog->m_sheet->numRows() );
379 m_dialog->m_colEnd->setMaxValue( m_dialog->m_sheet->numCols() );
380
381 m_dialog->m_rowStart->setMinValue( 1 );
382 m_dialog->m_colStart->setMinValue( 1 );
383 m_dialog->m_rowStart->setMaxValue( m_dialog->m_sheet->numRows() );
384 m_dialog->m_colStart->setMaxValue( m_dialog->m_sheet->numCols() );
385}
386
387int CSVDialog::getRows()
388{
389 int rows = m_dialog->m_sheet->numRows();
390 if ( m_endRow >= 0 )
391 {
392 if ( rows > ( m_startRow + m_endRow ) )
393 rows = m_startRow + m_endRow;
394 }
395
396 return rows;
397}
398
399int CSVDialog::getCols()
400{
401 int cols = m_dialog->m_sheet->numCols();
402 if ( m_endCol >= 0 )
403 {
404 if ( cols > ( m_startCol + m_endCol ) )
405 cols = m_startCol + m_endCol;
406 }
407
408 return cols;
409}
410
411int CSVDialog::getHeader(int col)
412{
413 TQString header = m_dialog->m_sheet->horizontalHeader()->label(col);
414
415 if (header == i18n("Text"))
416 return TEXT;
417 else if (header == i18n("Number"))
418 return NUMBER;
419 else if (header == i18n("Currency"))
420 return CURRENCY;
421 else if ( header == i18n( "Date" ) )
422 return DATE;
423 else if ( header == i18n( "Decimal Comma Number" ) )
424 return COMMANUMBER;
425 else if ( header == i18n( "Decimal Point Number" ) )
426 return POINTNUMBER;
427 else
428 return TEXT; // Should not happen
429}
430
431TQString CSVDialog::getText(int row, int col)
432{
433 return m_dialog->m_sheet->text( row, col );
434}
435
436void CSVDialog::setText(int row, int col, const TQString& text)
437{
438 if ( row < 1 || col < 1 ) // skipped by the user
439 return;
440
441 if ( ( row > ( m_endRow - m_startRow ) && m_endRow > 0 ) || ( col > ( m_endCol - m_startCol ) && m_endCol > 0 ) )
442 return;
443
444 if ( m_dialog->m_sheet->numRows() < row )
445 {
446 m_dialog->m_sheet->setNumRows( row + 5000 ); /* We add 5000 at a time to limit recalculations */
447 m_adjustRows = true;
448 }
449
450 if ( m_dialog->m_sheet->numCols() < col )
451 {
452 m_dialog->m_sheet->setNumCols( col );
453 m_adjustCols = true;
454 }
455
456 m_dialog->m_sheet->setText( row - 1, col - 1, text );
457}
458
459/*
460 * Called after the first fillTable() when number of rows are unknown.
461 */
462void CSVDialog::adjustRows(int iRows)
463{
464 if (m_adjustRows)
465 {
466 m_dialog->m_sheet->setNumRows( iRows );
467 m_adjustRows = false;
468 }
469}
470
471void CSVDialog::adjustCols(int iCols)
472{
473 if (m_adjustCols)
474 {
475 m_dialog->m_sheet->setNumCols( iCols );
476 m_adjustCols = false;
477
478 if ( m_endCol == -1 )
479 {
480 if ( iCols > ( m_endCol - m_startCol ) )
481 iCols = m_endCol - m_startCol;
482
483 m_dialog->m_sheet->setNumCols( iCols );
484 }
485 }
486}
487
488void CSVDialog::returnPressed()
489{
490 if (m_dialog->m_delimiterBox->id(m_dialog->m_delimiterBox->selected()) != 4)
491 return;
492
493 m_delimiter = m_dialog->m_delimiterEdit->text();
494 fillTable();
495}
496
497void CSVDialog::textChanged ( const TQString & )
498{
499 m_dialog->m_radioOther->setChecked ( true );
500 delimiterClicked(4); // other
501}
502
503void CSVDialog::formatChanged( const TQString& newValue )
504{
505 //kdDebug(30501) << "CSVDialog::formatChanged:" << newValue << endl;
506 for ( int i = 0; i < m_dialog->m_sheet->numSelections(); ++i )
507 {
508 TQTableSelection select ( m_dialog->m_sheet->selection( i ) );
509 for ( int j = select.leftCol(); j <= select.rightCol() ; ++j )
510 {
511 m_dialog->m_sheet->horizontalHeader()->setLabel( j, newValue );
512
513 }
514 }
515}
516
517void CSVDialog::delimiterClicked(int id)
518{
519 switch (id)
520 {
521 case 0: // comma
522 m_delimiter = ",";
523 break;
524 case 4: // other
525 m_delimiter = m_dialog->m_delimiterEdit->text();
526 break;
527 case 2: // tab
528 m_delimiter = "\t";
529 break;
530 case 3: // space
531 m_delimiter = " ";
532 break;
533 case 1: // semicolon
534 m_delimiter = ";";
535 break;
536 }
537
538 fillTable();
539}
540
541void CSVDialog::textquoteSelected(const TQString& mark)
542{
543 if (mark == i18n("None"))
544 m_textquote = 0;
545 else
546 m_textquote = mark[0];
547
548 fillTable();
549}
550
551void CSVDialog::updateClicked()
552{
553 if ( !checkUpdateRange() )
554 return;
555
556 m_startRow = m_dialog->m_rowStart->value() - 1;
557 m_endRow = m_dialog->m_rowEnd->value();
558
559 m_startCol = m_dialog->m_colStart->value() - 1;
560 m_endCol = m_dialog->m_colEnd->value();
561
562 fillTable();
563}
564
565bool CSVDialog::checkUpdateRange()
566{
567 if ( ( m_dialog->m_rowStart->value() > m_dialog->m_rowEnd->value() )
568 || ( m_dialog->m_colStart->value() > m_dialog->m_colEnd->value() ) )
569 {
570 KMessageBox::error( this, i18n( "Please check the ranges you specified. The start value must be lower than the end value." ) );
571 return false;
572 }
573
574 return true;
575}
576
577void CSVDialog::currentCellChanged(int, int col)
578{
579 const TQString header = m_dialog->m_sheet->horizontalHeader()->label(col);
580 m_dialog->m_formatComboBox->setCurrentText( header );
581}
582
583void CSVDialog::ignoreDuplicatesChanged(int)
584{
585 if (m_dialog->m_ignoreDuplicates->isChecked())
586 m_ignoreDups = true;
587 else
588 m_ignoreDups = false;
589 fillTable();
590}
591
592TQTextCodec* CSVDialog::getCodec(void) const
593{
594 const TQString strCodec( TDEGlobal::charsets()->encodingForName( m_dialog->comboBoxEncoding->currentText() ) );
595 kdDebug(30502) << "Encoding: " << strCodec << endl;
596
597 bool ok = false;
598 TQTextCodec* codec = TQTextCodec::codecForName( strCodec.utf8() );
599
600 // If TQTextCodec has not found a valid encoding, so try with KCharsets.
601 if ( codec )
602 {
603 ok = true;
604 }
605 else
606 {
607 codec = TDEGlobal::charsets()->codecForName( strCodec, ok );
608 }
609
610 // Still nothing?
611 if ( !codec || !ok )
612 {
613 // Default: UTF-8
614 kdWarning(30502) << "Cannot find encoding:" << strCodec << endl;
615 // ### TODO: what parent to use?
616 KMessageBox::error( 0, i18n("Cannot find encoding: %1").arg( strCodec ) );
617 return 0;
618 }
619
620 return codec;
621}
622
623void CSVDialog::encodingChanged ( const TQString & )
624{
625 TQTextCodec* codec = getCodec();
626
627 if ( codec )
628 {
629 m_codec = codec;
630 fillTable();
631 }
632}
633
634
635#include <csvdialog.moc>