kchart

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