kaddressbook

csvimportdialog.cpp
1 /*
2  This file is part of KAddressBook.
3  Copyright (C) 2003 Tobias Koenig <tokoe@kde.org>
4  based on the code of KSpread's CSV Import Dialog
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License as published by the Free Software Foundation; either
9  version 2 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Library General Public License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to
18  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  Boston, MA 02110-1301, USA.
20 */
21 
22 
23 #include <tqbuttongroup.h>
24 #include <tqcheckbox.h>
25 #include <tqcombobox.h>
26 #include <tqlabel.h>
27 #include <tqlayout.h>
28 #include <tqlineedit.h>
29 #include <tqpushbutton.h>
30 #include <tqradiobutton.h>
31 #include <tqtable.h>
32 #include <tqtextcodec.h>
33 #include <tqtooltip.h>
34 
35 #include <tdeapplication.h>
36 #include <kdebug.h>
37 #include <kdialogbase.h>
38 #include <tdefiledialog.h>
39 #include <klineedit.h>
40 #include <tdelocale.h>
41 #include <kinputdialog.h>
42 #include <tdemessagebox.h>
43 #include <kprogress.h>
44 #include <kstandarddirs.h>
45 #include <kurlrequester.h>
46 
47 #include "dateparser.h"
48 
49 #include "csvimportdialog.h"
50 
51 enum { Local = 0, Guess = 1, Latin1 = 2, Uni = 3, MSBug = 4, Codec = 5 };
52 
53 CSVImportDialog::CSVImportDialog( TDEABC::AddressBook *ab, TQWidget *parent,
54  const char * name )
55  : KDialogBase( Plain, i18n ( "CSV Import Dialog" ), Ok | Cancel | User1 |
56  User2, Ok, parent, name, true, true ),
57  mAdjustRows( false ),
58  mStartLine( 0 ),
59  mTextQuote( '"' ),
60  mDelimiter( "," ),
61  mAddressBook( ab )
62 {
63  initGUI();
64 
65  mTypeMap.insert( i18n( "Undefined" ), Undefined );
66  mTypeMap.insert( TDEABC::Addressee::formattedNameLabel(), FormattedName );
67  mTypeMap.insert( TDEABC::Addressee::familyNameLabel(), FamilyName );
68  mTypeMap.insert( TDEABC::Addressee::givenNameLabel(), GivenName );
69  mTypeMap.insert( TDEABC::Addressee::additionalNameLabel(), AdditionalName );
70  mTypeMap.insert( TDEABC::Addressee::prefixLabel(), Prefix );
71  mTypeMap.insert( TDEABC::Addressee::suffixLabel(), Suffix );
72  mTypeMap.insert( TDEABC::Addressee::nickNameLabel(), NickName );
73  mTypeMap.insert( TDEABC::Addressee::birthdayLabel(), Birthday );
74 
75  mTypeMap.insert( TDEABC::Addressee::homeAddressStreetLabel(), HomeAddressStreet );
76  mTypeMap.insert( TDEABC::Addressee::homeAddressLocalityLabel(),
77  HomeAddressLocality );
78  mTypeMap.insert( TDEABC::Addressee::homeAddressRegionLabel(), HomeAddressRegion );
79  mTypeMap.insert( TDEABC::Addressee::homeAddressPostalCodeLabel(),
80  HomeAddressPostalCode );
81  mTypeMap.insert( TDEABC::Addressee::homeAddressCountryLabel(),
82  HomeAddressCountry );
83  mTypeMap.insert( TDEABC::Addressee::homeAddressLabelLabel(), HomeAddressLabel );
84 
85  mTypeMap.insert( TDEABC::Addressee::businessAddressStreetLabel(),
86  BusinessAddressStreet );
87  mTypeMap.insert( TDEABC::Addressee::businessAddressLocalityLabel(),
88  BusinessAddressLocality );
89  mTypeMap.insert( TDEABC::Addressee::businessAddressRegionLabel(),
90  BusinessAddressRegion );
91  mTypeMap.insert( TDEABC::Addressee::businessAddressPostalCodeLabel(),
92  BusinessAddressPostalCode );
93  mTypeMap.insert( TDEABC::Addressee::businessAddressCountryLabel(),
94  BusinessAddressCountry );
95  mTypeMap.insert( TDEABC::Addressee::businessAddressLabelLabel(),
96  BusinessAddressLabel );
97 
98  mTypeMap.insert( TDEABC::Addressee::homePhoneLabel(), HomePhone );
99  mTypeMap.insert( TDEABC::Addressee::businessPhoneLabel(), BusinessPhone );
100  mTypeMap.insert( TDEABC::Addressee::mobilePhoneLabel(), MobilePhone );
101  mTypeMap.insert( TDEABC::Addressee::homeFaxLabel(), HomeFax );
102  mTypeMap.insert( TDEABC::Addressee::businessFaxLabel(), BusinessFax );
103  mTypeMap.insert( TDEABC::Addressee::carPhoneLabel(), CarPhone );
104  mTypeMap.insert( TDEABC::Addressee::isdnLabel(), Isdn );
105  mTypeMap.insert( TDEABC::Addressee::pagerLabel(), Pager );
106  mTypeMap.insert( TDEABC::Addressee::emailLabel(), Email );
107  mTypeMap.insert( TDEABC::Addressee::mailerLabel(), Mailer );
108  mTypeMap.insert( TDEABC::Addressee::titleLabel(), Title );
109  mTypeMap.insert( TDEABC::Addressee::roleLabel(), Role );
110  mTypeMap.insert( TDEABC::Addressee::organizationLabel(), Organization );
111  mTypeMap.insert( TDEABC::Addressee::noteLabel(), Note );
112  mTypeMap.insert( TDEABC::Addressee::urlLabel(), URL );
113 
114  mCustomCounter = mTypeMap.count();
115  int count = mCustomCounter;
116 
117  TDEABC::Field::List fields = mAddressBook->fields( TDEABC::Field::CustomCategory );
118  TDEABC::Field::List::Iterator it;
119  for ( it = fields.begin(); it != fields.end(); ++it, ++count )
120  mTypeMap.insert( (*it)->label(), count );
121 
122  reloadCodecs();
123 
124  connect( mDelimiterBox, TQ_SIGNAL( clicked( int ) ),
125  this, TQ_SLOT( delimiterClicked( int ) ) );
126  connect( mDelimiterEdit, TQ_SIGNAL( returnPressed() ),
127  this, TQ_SLOT( returnPressed() ) );
128  connect( mDelimiterEdit, TQ_SIGNAL( textChanged ( const TQString& ) ),
129  this, TQ_SLOT( textChanged ( const TQString& ) ) );
130  connect( mComboLine, TQ_SIGNAL( activated( const TQString& ) ),
131  this, TQ_SLOT( lineSelected( const TQString& ) ) );
132  connect( mComboQuote, TQ_SIGNAL( activated( const TQString& ) ),
133  this, TQ_SLOT( textquoteSelected( const TQString& ) ) );
134  connect( mIgnoreDuplicates, TQ_SIGNAL( stateChanged( int ) ),
135  this, TQ_SLOT( ignoreDuplicatesChanged( int ) ) );
136  connect( mCodecCombo, TQ_SIGNAL( activated( const TQString& ) ),
137  this, TQ_SLOT( codecChanged() ) );
138 
139  connect( mUrlRequester, TQ_SIGNAL( returnPressed( const TQString& ) ),
140  this, TQ_SLOT( setFile( const TQString& ) ) );
141  connect( mUrlRequester, TQ_SIGNAL( urlSelected( const TQString& ) ),
142  this, TQ_SLOT( setFile( const TQString& ) ) );
143  connect( mUrlRequester->lineEdit(), TQ_SIGNAL( textChanged ( const TQString& ) ),
144  this, TQ_SLOT( urlChanged( const TQString& ) ) );
145 
146  connect( this, TQ_SIGNAL( user1Clicked() ),
147  this, TQ_SLOT( applyTemplate() ) );
148 
149  connect( this, TQ_SIGNAL( user2Clicked() ),
150  this, TQ_SLOT( saveTemplate() ) );
151 }
152 
153 CSVImportDialog::~CSVImportDialog()
154 {
155  mCodecs.clear();
156 }
157 
158 TDEABC::AddresseeList CSVImportDialog::contacts() const
159 {
160  DateParser dateParser( mDatePatternEdit->text() );
161  TDEABC::AddresseeList contacts;
162 
163  KProgressDialog progressDialog( mPage );
164  progressDialog.setAutoClose( true );
165  progressDialog.progressBar()->setTotalSteps( mTable->numRows() );
166  progressDialog.setLabel( i18n( "Importing contacts" ) );
167  progressDialog.show();
168 
169  kapp->processEvents();
170 
171  for ( int row = 1; row < mTable->numRows(); ++row ) {
172  TDEABC::Addressee a;
173  bool emptyRow = true;
174  TDEABC::Address addrHome( TDEABC::Address::Home );
175  TDEABC::Address addrWork( TDEABC::Address::Work );
176  for ( int col = 0; col < mTable->numCols(); ++col ) {
177  TQComboTableItem *item = static_cast<TQComboTableItem*>( mTable->item( 0,
178  col ) );
179  if ( !item ) {
180  kdError() << "ERROR: item cast failed" << endl;
181  continue;
182  }
183 
184  TQString value = mTable->text( row, col );
185  if ( 1 == row && static_cast<TQTableItem *>(item)->text() == value )
186  // we are looking at a header row, stop now
187  break;
188 
189  if ( !value.isEmpty() )
190  emptyRow = false;
191 
192  switch ( posToType( item->currentItem() ) ) {
193  case Undefined:
194  continue;
195  break;
196  case FormattedName:
197  a.setFormattedName( value );
198  break;
199  case GivenName:
200  a.setGivenName( value );
201  break;
202  case FamilyName:
203  a.setFamilyName( value );
204  break;
205  case AdditionalName:
206  a.setAdditionalName( value );
207  break;
208  case Prefix:
209  a.setPrefix( value );
210  break;
211  case Suffix:
212  a.setSuffix( value );
213  break;
214  case NickName:
215  a.setNickName( value );
216  break;
217  case Birthday:
218  a.setBirthday( dateParser.parse( value ) );
219  break;
220  case Email:
221  if ( !value.isEmpty() )
222  a.insertEmail( value, true );
223  break;
224  case Role:
225  a.setRole( value );
226  break;
227  case Title:
228  a.setTitle( value );
229  break;
230  case Mailer:
231  a.setMailer( value );
232  break;
233  case URL:
234  a.setUrl( KURL( value ) );
235  break;
236  case Organization:
237  a.setOrganization( value );
238  break;
239  case Note:
240  a.setNote( a.note() + value + "\n" );
241  break;
242 
243  case HomePhone:
244  if ( !value.isEmpty() ) {
245  TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Home );
246  a.insertPhoneNumber( number );
247  }
248  break;
249  case BusinessPhone:
250  if ( !value.isEmpty() ) {
251  TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Work );
252  a.insertPhoneNumber( number );
253  }
254  break;
255  case MobilePhone:
256  if ( !value.isEmpty() ) {
257  TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Cell );
258  a.insertPhoneNumber( number );
259  }
260  break;
261  case HomeFax:
262  if ( !value.isEmpty() ) {
263  TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Home |
264  TDEABC::PhoneNumber::Fax );
265  a.insertPhoneNumber( number );
266  }
267  break;
268  case BusinessFax:
269  if ( !value.isEmpty() ) {
270  TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Work |
271  TDEABC::PhoneNumber::Fax );
272  a.insertPhoneNumber( number );
273  }
274  break;
275  case CarPhone:
276  if ( !value.isEmpty() ) {
277  TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Car );
278  a.insertPhoneNumber( number );
279  }
280  break;
281  case Isdn:
282  if ( !value.isEmpty() ) {
283  TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Isdn );
284  a.insertPhoneNumber( number );
285  }
286  break;
287  case Pager:
288  if ( !value.isEmpty() ) {
289  TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Pager );
290  a.insertPhoneNumber( number );
291  }
292  break;
293 
294  case HomeAddressStreet:
295  addrHome.setStreet( value );
296  break;
297  case HomeAddressLocality:
298  addrHome.setLocality( value );
299  break;
300  case HomeAddressRegion:
301  addrHome.setRegion( value );
302  break;
303  case HomeAddressPostalCode:
304  addrHome.setPostalCode( value );
305  break;
306  case HomeAddressCountry:
307  addrHome.setCountry( value );
308  break;
309  case HomeAddressLabel:
310  addrHome.setLabel( value );
311  break;
312 
313  case BusinessAddressStreet:
314  addrWork.setStreet( value );
315  break;
316  case BusinessAddressLocality:
317  addrWork.setLocality( value );
318  break;
319  case BusinessAddressRegion:
320  addrWork.setRegion( value );
321  break;
322  case BusinessAddressPostalCode:
323  addrWork.setPostalCode( value );
324  break;
325  case BusinessAddressCountry:
326  addrWork.setCountry( value );
327  break;
328  case BusinessAddressLabel:
329  addrWork.setLabel( value );
330  break;
331  default:
332  TDEABC::Field::List fields = mAddressBook->fields( TDEABC::Field::CustomCategory );
333  TDEABC::Field::List::Iterator it;
334 
335  int counter = 0;
336  for ( it = fields.begin(); it != fields.end(); ++it ) {
337  if ( counter == (int)( posToType( item->currentItem() ) - mCustomCounter ) ) {
338  (*it)->setValue( a, value );
339  break;
340  }
341  ++counter;
342  }
343  break;
344  }
345  }
346 
347  kapp->processEvents();
348 
349  if ( progressDialog.wasCancelled() )
350  return TDEABC::AddresseeList();
351 
352  progressDialog.progressBar()->advance( 1 );
353 
354  if ( !addrHome.isEmpty() )
355  a.insertAddress( addrHome );
356  if ( !addrWork.isEmpty() )
357  a.insertAddress( addrWork );
358 
359  if ( !emptyRow && !a.isEmpty() )
360  contacts.append( a );
361  }
362 
363  return contacts;
364 }
365 
366 void CSVImportDialog::initGUI()
367 {
368  mPage = plainPage();
369 
370  TQGridLayout *layout = new TQGridLayout( mPage, 1, 1, marginHint(),
371  spacingHint() );
372  TQHBoxLayout *hbox = new TQHBoxLayout();
373  hbox->setSpacing( spacingHint() );
374 
375  TQLabel *label = new TQLabel( i18n( "File to import:" ), mPage );
376  hbox->addWidget( label );
377 
378  mUrlRequester = new KURLRequester( mPage );
379  mUrlRequester->setFilter( "*.csv" );
380  hbox->addWidget( mUrlRequester );
381 
382  layout->addMultiCellLayout( hbox, 0, 0, 0, 4 );
383 
384  // Delimiter: comma, semicolon, tab, space, other
385  mDelimiterBox = new TQButtonGroup( i18n( "Delimiter" ), mPage );
386  mDelimiterBox->setColumnLayout( 0, TQt::Vertical );
387  mDelimiterBox->layout()->setSpacing( spacingHint() );
388  mDelimiterBox->layout()->setMargin( marginHint() );
389  TQGridLayout *delimiterLayout = new TQGridLayout( mDelimiterBox->layout() );
390  delimiterLayout->setAlignment( TQt::AlignTop );
391  layout->addMultiCellWidget( mDelimiterBox, 1, 4, 0, 0 );
392 
393  mRadioComma = new TQRadioButton( i18n( "Comma" ), mDelimiterBox );
394  mRadioComma->setChecked( true );
395  delimiterLayout->addWidget( mRadioComma, 0, 0 );
396 
397  mRadioSemicolon = new TQRadioButton( i18n( "Semicolon" ), mDelimiterBox );
398  delimiterLayout->addWidget( mRadioSemicolon, 0, 1 );
399 
400  mRadioTab = new TQRadioButton( i18n( "Tabulator" ), mDelimiterBox );
401  delimiterLayout->addWidget( mRadioTab, 1, 0 );
402 
403  mRadioSpace = new TQRadioButton( i18n( "Space" ), mDelimiterBox );
404  delimiterLayout->addWidget( mRadioSpace, 1, 1 );
405 
406  mRadioOther = new TQRadioButton( i18n( "Other" ), mDelimiterBox );
407  delimiterLayout->addWidget( mRadioOther, 0, 2 );
408 
409  mDelimiterEdit = new TQLineEdit( mDelimiterBox );
410  delimiterLayout->addWidget( mDelimiterEdit, 1, 2 );
411 
412  mComboLine = new TQComboBox( false, mPage );
413  mComboLine->insertItem( i18n( "1" ) );
414  layout->addWidget( mComboLine, 2, 3 );
415 
416  mComboQuote = new TQComboBox( false, mPage );
417  mComboQuote->insertItem( i18n( "\"" ), 0 );
418  mComboQuote->insertItem( i18n( "'" ), 1 );
419  mComboQuote->insertItem( i18n( "None" ), 2 );
420  layout->addWidget( mComboQuote, 2, 2 );
421 
422  mDatePatternEdit = new TQLineEdit( mPage );
423  mDatePatternEdit->setText( "Y-M-D" ); // ISO 8601 format as default
424  TQToolTip::add( mDatePatternEdit, i18n( "<ul><li>y: year with 2 digits</li>"
425  "<li>Y: year with 4 digits</li>"
426  "<li>m: month with 1 or 2 digits</li>"
427  "<li>M: month with 2 digits</li>"
428  "<li>d: day with 1 or 2 digits</li>"
429  "<li>D: day with 2 digits</li></ul>" ) );
430  layout->addWidget( mDatePatternEdit, 2, 4 );
431 
432  label = new TQLabel( i18n( "Start at line:" ), mPage );
433  layout->addWidget( label, 1, 3 );
434 
435  label = new TQLabel( i18n( "Textquote:" ), mPage );
436  layout->addWidget( label, 1, 2 );
437 
438  label = new TQLabel( i18n( "Date format:" ), mPage );
439  layout->addWidget( label, 1, 4 );
440 
441  mIgnoreDuplicates = new TQCheckBox( mPage );
442  mIgnoreDuplicates->setText( i18n( "Ignore duplicate delimiters" ) );
443  layout->addMultiCellWidget( mIgnoreDuplicates, 3, 3, 2, 4 );
444 
445  mCodecCombo = new TQComboBox( mPage );
446  layout->addMultiCellWidget( mCodecCombo, 4, 4, 2, 4 );
447 
448  mTable = new TQTable( 0, 0, mPage );
449  mTable->setSelectionMode( TQTable::NoSelection );
450  mTable->horizontalHeader()->hide();
451  layout->addMultiCellWidget( mTable, 5, 5, 0, 4 );
452 
453  setButtonText( User1, i18n( "Apply Template..." ) );
454  setButtonText( User2, i18n( "Save Template..." ) );
455 
456  enableButtonOK( false );
457  actionButton( User1 )->setEnabled( false );
458  actionButton( User2 )->setEnabled( false );
459 
460  resize( 400, 300 );
461 }
462 
463 void CSVImportDialog::fillTable()
464 {
465  int row, column;
466  bool lastCharDelimiter = false;
467  bool ignoreDups = mIgnoreDuplicates->isChecked();
468  enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
469  S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;
470 
471  TQChar x;
472  TQString field;
473 
474  // store previous assignment
475  mTypeStore.clear();
476  for ( column = 0; column < mTable->numCols(); ++column ) {
477  TQComboTableItem *item = static_cast<TQComboTableItem*>( mTable->item( 0,
478  column ) );
479  if ( !item || mClearTypeStore )
480  mTypeStore.append( typeToPos( Undefined ) );
481  else if ( item )
482  mTypeStore.append( item->currentItem() );
483  }
484 
485  clearTable();
486 
487  row = column = 1;
488 
489  TQTextStream inputStream( mFileArray, IO_ReadOnly );
490 
491  // find the current codec
492  int code = mCodecCombo->currentItem();
493  if ( code == Local )
494  inputStream.setEncoding( TQTextStream::Locale );
495  else if ( code >= Codec )
496  inputStream.setCodec( mCodecs.at( code - Codec ) );
497  else if ( code == Uni )
498  inputStream.setEncoding( TQTextStream::Unicode );
499  else if ( code == MSBug )
500  inputStream.setEncoding( TQTextStream::UnicodeReverse );
501  else if ( code == Latin1 )
502  inputStream.setEncoding( TQTextStream::Latin1 );
503  else if ( code == Guess ) {
504  TQTextCodec* codec = TQTextCodec::codecForContent( mFileArray.data(), mFileArray.size() );
505  if ( codec ) {
506  KMessageBox::information( this, i18n( "Using codec '%1'" ).arg( codec->name() ), i18n( "Encoding" ) );
507  inputStream.setCodec( codec );
508  }
509  }
510 
511  int maxColumn = 0;
512  while ( !inputStream.atEnd() ) {
513  inputStream >> x; // read one char
514 
515  if ( x == '\r' ) inputStream >> x; // eat '\r', to handle DOS/LOSEDOWS files correctly
516 
517  switch ( state ) {
518  case S_START :
519  if ( x == mTextQuote ) {
520  state = S_QUOTED_FIELD;
521  } else if ( x == mDelimiter ) {
522  if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
523  ++column;
524  lastCharDelimiter = true;
525  } else if ( x == '\n' ) {
526  ++row;
527  column = 1;
528  } else {
529  field += x;
530  state = S_MAYBE_NORMAL_FIELD;
531  }
532  break;
533  case S_QUOTED_FIELD :
534  if ( x == mTextQuote ) {
535  state = S_MAYBE_END_OF_QUOTED_FIELD;
536  } else if ( x == '\n' && mTextQuote.isNull() ) {
537  setText( row - mStartLine + 1, column, field );
538  field = "";
539  if ( x == '\n' ) {
540  ++row;
541  column = 1;
542  } else {
543  if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
544  ++column;
545  lastCharDelimiter = true;
546  }
547  state = S_START;
548  } else {
549  field += x;
550  }
551  break;
552  case S_MAYBE_END_OF_QUOTED_FIELD :
553  if ( x == mTextQuote ) {
554  field += x;
555  state = S_QUOTED_FIELD;
556  } else if ( x == mDelimiter || x == '\n' ) {
557  setText( row - mStartLine + 1, column, field );
558  field = "";
559  if ( x == '\n' ) {
560  ++row;
561  column = 1;
562  } else {
563  if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
564  ++column;
565  lastCharDelimiter = true;
566  }
567  state = S_START;
568  } else {
569  state = S_END_OF_QUOTED_FIELD;
570  }
571  break;
572  case S_END_OF_QUOTED_FIELD :
573  if ( x == mDelimiter || x == '\n' ) {
574  setText( row - mStartLine + 1, column, field );
575  field = "";
576  if ( x == '\n' ) {
577  ++row;
578  column = 1;
579  } else {
580  if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
581  ++column;
582  lastCharDelimiter = true;
583  }
584  state = S_START;
585  } else {
586  state = S_END_OF_QUOTED_FIELD;
587  }
588  break;
589  case S_MAYBE_NORMAL_FIELD :
590  if ( x == mTextQuote ) {
591  field = "";
592  state = S_QUOTED_FIELD;
593  break;
594  }
595  case S_NORMAL_FIELD :
596  if ( x == mDelimiter || x == '\n' ) {
597  setText( row - mStartLine + 1, column, field );
598  field = "";
599  if ( x == '\n' ) {
600  ++row;
601  column = 1;
602  } else {
603  if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
604  ++column;
605  lastCharDelimiter = true;
606  }
607  state = S_START;
608  } else {
609  field += x;
610  }
611  }
612  if ( x != mDelimiter )
613  lastCharDelimiter = false;
614 
615  if ( column > maxColumn )
616  maxColumn = column;
617  }
618 
619  // file with only one line without '\n'
620  if ( field.length() > 0 ) {
621  setText( row - mStartLine + 1, column, field );
622  ++row;
623  field = "";
624  }
625 
626  adjustRows( row - mStartLine );
627  mTable->setNumCols( maxColumn );
628 
629  for ( column = 0; column < mTable->numCols(); ++column ) {
630  TQComboTableItem *item = new TQComboTableItem( mTable, mTypeMap.keys() );
631  mTable->setItem( 0, column, item );
632  if ( column < (int)mTypeStore.count() )
633  item->setCurrentItem( mTypeStore[ column ] );
634  else
635  item->setCurrentItem( typeToPos( Undefined ) );
636  mTable->adjustColumn( column );
637  }
638 
639  resizeColumns();
640 }
641 
642 void CSVImportDialog::clearTable()
643 {
644  for ( int row = 0; row < mTable->numRows(); ++row )
645  for ( int column = 0; column < mTable->numCols(); ++column )
646  mTable->clearCell( row, column );
647 }
648 
649 void CSVImportDialog::fillComboBox()
650 {
651  mComboLine->clear();
652  for ( int row = 1; row < mTable->numRows() + 1; ++row )
653  mComboLine->insertItem( TQString::number( row ), row - 1 );
654 }
655 
656 void CSVImportDialog::reloadCodecs()
657 {
658  mCodecCombo->clear();
659 
660  mCodecs.clear();
661 
662  TQTextCodec *codec;
663  for ( int i = 0; ( codec = TQTextCodec::codecForIndex( i ) ); i++ )
664  mCodecs.append( codec );
665 
666  mCodecCombo->insertItem( i18n( "Local (%1)" ).arg( TQTextCodec::codecForLocale()->name() ), Local );
667  mCodecCombo->insertItem( i18n( "[guess]" ), Guess );
668  mCodecCombo->insertItem( i18n( "Latin1" ), Latin1 );
669  mCodecCombo->insertItem( i18n( "Unicode" ), Uni );
670  mCodecCombo->insertItem( i18n( "Microsoft Unicode" ), MSBug );
671 
672  for ( uint i = 0; i < mCodecs.count(); i++ )
673  mCodecCombo->insertItem( mCodecs.at( i )->name(), Codec + i );
674 }
675 
676 void CSVImportDialog::setText( int row, int col, const TQString& text )
677 {
678  if ( row < 1 ) // skipped by the user
679  return;
680 
681  if ( mTable->numRows() < row ) {
682  mTable->setNumRows( row + 5000 ); // We add 5000 at a time to limit recalculations
683  mAdjustRows = true;
684  }
685 
686  if ( mTable->numCols() < col )
687  mTable->setNumCols( col + 50 ); // We add 50 at a time to limit recalculation
688 
689  mTable->setText( row - 1, col - 1, text );
690 }
691 
692 /*
693  * Called after the first fillTable() when number of rows are unknown.
694  */
695 void CSVImportDialog::adjustRows( int rows )
696 {
697  if ( mAdjustRows ) {
698  mTable->setNumRows( rows );
699  mAdjustRows = false;
700  }
701 }
702 
703 void CSVImportDialog::resizeColumns()
704 {
705  TQFontMetrics fm = fontMetrics();
706  int width = 0;
707 
708  TQMap<TQString, uint>::ConstIterator it;
709  for ( it = mTypeMap.begin(); it != mTypeMap.end(); ++it ) {
710  width = TQMAX( width, fm.width( it.key() ) );
711  }
712 
713  for ( int i = 0; i < mTable->numCols(); ++i )
714  mTable->setColumnWidth( i, TQMAX( width + 15, mTable->columnWidth( i ) ) );
715 }
716 
717 void CSVImportDialog::returnPressed()
718 {
719  if ( mDelimiterBox->id( mDelimiterBox->selected() ) != 4 )
720  return;
721 
722  mDelimiter = mDelimiterEdit->text();
723  fillTable();
724 }
725 
726 void CSVImportDialog::textChanged ( const TQString& )
727 {
728  mRadioOther->setChecked ( true );
729  delimiterClicked( 4 ); // other
730 }
731 
732 void CSVImportDialog::delimiterClicked( int id )
733 {
734  switch ( id ) {
735  case 0: // comma
736  mDelimiter = ",";
737  break;
738  case 4: // other
739  mDelimiter = mDelimiterEdit->text();
740  break;
741  case 2: // tab
742  mDelimiter = "\t";
743  break;
744  case 3: // space
745  mDelimiter = " ";
746  break;
747  case 1: // semicolon
748  mDelimiter = ";";
749  break;
750  }
751 
752  fillTable();
753 }
754 
755 void CSVImportDialog::textquoteSelected( const TQString& mark )
756 {
757  if ( mComboQuote->currentItem() == 2 )
758  mTextQuote = 0;
759  else
760  mTextQuote = mark[ 0 ];
761 
762  fillTable();
763 }
764 
765 void CSVImportDialog::lineSelected( const TQString& line )
766 {
767  mStartLine = line.toInt() - 1;
768  fillTable();
769 }
770 
771 void CSVImportDialog::slotOk()
772 {
773  bool assigned = false;
774 
775  for ( int column = 0; column < mTable->numCols(); ++column ) {
776  TQComboTableItem *item = static_cast<TQComboTableItem*>( mTable->item( 0,
777  column ) );
778  if ( item && posToType( item->currentItem() ) != Undefined )
779  assigned = true;
780  }
781 
782  if ( assigned )
783  KDialogBase::slotOk();
784  else
785  KMessageBox::sorry( this, i18n( "You have to assign at least one column." ) );
786 }
787 
788 void CSVImportDialog::applyTemplate()
789 {
790  TQMap<uint,int> columnMap;
791  TQMap<TQString, TQString> fileMap;
792  TQStringList templates;
793 
794  // load all template files
795  TQStringList list = TDEGlobal::dirs()->findAllResources( "data" , TQString( kapp->name() ) +
796  "/csv-templates/*.desktop", true, true );
797 
798  for ( TQStringList::iterator it = list.begin(); it != list.end(); ++it )
799  {
800  KSimpleConfig config( *it, true );
801 
802  if ( !config.hasGroup( "csv column map" ) )
803  continue;
804 
805  config.setGroup( "Misc" );
806  templates.append( config.readEntry( "Name" ) );
807  fileMap.insert( config.readEntry( "Name" ), *it );
808  }
809 
810  // let the user chose, what to take
811  bool ok = false;
812  TQString tmp;
813  tmp = KInputDialog::getItem( i18n( "Template Selection" ),
814  i18n( "Please select a template, that matches the CSV file:" ),
815  templates, 0, false, &ok, this );
816 
817  if ( !ok )
818  return;
819 
820  KSimpleConfig config( fileMap[ tmp ], true );
821  config.setGroup( "General" );
822  mDatePatternEdit->setText( config.readEntry( "DatePattern", "Y-M-D" ) );
823  uint numColumns = config.readUnsignedNumEntry( "Columns" );
824  mDelimiterEdit->setText( config.readEntry( "DelimiterOther" ) );
825  mDelimiterBox->setButton( config.readNumEntry( "DelimiterType" ) );
826  delimiterClicked( config.readNumEntry( "DelimiterType" ) );
827  int quoteType = config.readNumEntry( "QuoteType" );
828  mComboQuote->setCurrentItem( quoteType );
829  textquoteSelected( mComboQuote->currentText() );
830 
831  // create the column map
832  config.setGroup( "csv column map" );
833  for ( uint i = 0; i < numColumns; ++i ) {
834  int col = config.readNumEntry( TQString::number( i ) );
835  columnMap.insert( i, col );
836  }
837 
838  // apply the column map
839  for ( uint column = 0; column < columnMap.count(); ++column ) {
840  int type = columnMap[ column ];
841  TQComboTableItem *item = static_cast<TQComboTableItem*>( mTable->item( 0,
842  column ) );
843  if ( item )
844  item->setCurrentItem( typeToPos( type ) );
845  }
846 }
847 
848 void CSVImportDialog::saveTemplate()
849 {
850  TQString fileName = KFileDialog::getSaveFileName(
851  locateLocal( "data", TQString( kapp->name() ) + "/csv-templates/" ),
852  "*.desktop", this );
853 
854  if ( fileName.isEmpty() )
855  return;
856 
857  if ( !fileName.contains( ".desktop" ) )
858  fileName += ".desktop";
859 
860  if( TQFileInfo(fileName).exists() ) {
861  if(KMessageBox::questionYesNo( this, i18n("Do you want to overwrite file \"%1\"").arg(fileName) ) == KMessageBox::No)
862  return;
863  }
864  TQString name = KInputDialog::getText( i18n( "Template Name" ), i18n( "Please enter a name for the template:" ) );
865 
866  if ( name.isEmpty() )
867  return;
868 
869  TDEConfig config( fileName );
870  config.setGroup( "General" );
871  config.writeEntry( "DatePattern", mDatePatternEdit->text() );
872  config.writeEntry( "Columns", mTable->numCols() );
873  config.writeEntry( "DelimiterType", mDelimiterBox->id( mDelimiterBox->selected() ) );
874  config.writeEntry( "DelimiterOther", mDelimiterEdit->text() );
875  config.writeEntry( "QuoteType", mComboQuote->currentItem() );
876 
877  config.setGroup( "Misc" );
878  config.writeEntry( "Name", name );
879 
880  config.setGroup( "csv column map" );
881 
882  for ( int column = 0; column < mTable->numCols(); ++column ) {
883  TQComboTableItem *item = static_cast<TQComboTableItem*>( mTable->item( 0,
884  column ) );
885  if ( item )
886  config.writeEntry( TQString::number( column ), posToType(
887  item->currentItem() ) );
888  else
889  config.writeEntry( TQString::number( column ), 0 );
890  }
891 
892  config.sync();
893 }
894 
895 TQString CSVImportDialog::getText( int row, int col )
896 {
897  return mTable->text( row, col );
898 }
899 
900 uint CSVImportDialog::posToType( int pos ) const
901 {
902  uint counter = 0;
903  TQMap<TQString, uint>::ConstIterator it;
904  for ( it = mTypeMap.begin(); it != mTypeMap.end(); ++it, ++counter )
905  if ( counter == (uint)pos )
906  return it.data();
907 
908  return 0;
909 }
910 
911 int CSVImportDialog::typeToPos( uint type ) const
912 {
913  uint counter = 0;
914  TQMap<TQString, uint>::ConstIterator it;
915  for ( it = mTypeMap.begin(); it != mTypeMap.end(); ++it, ++counter )
916  if ( it.data() == type )
917  return counter;
918 
919  return -1;
920 }
921 
922 void CSVImportDialog::ignoreDuplicatesChanged( int )
923 {
924  fillTable();
925 }
926 
927 void CSVImportDialog::setFile( const TQString &fileName )
928 {
929  if ( fileName.isEmpty() )
930  return;
931 
932  TQFile file( fileName );
933  if ( !file.open( IO_ReadOnly ) ) {
934  KMessageBox::sorry( this, i18n( "Cannot open input file." ) );
935  file.close();
936  return;
937  }
938 
939  mFileArray = file.readAll();
940  file.close();
941 
942  mClearTypeStore = true;
943  clearTable();
944  mTable->setNumCols( 0 );
945  mTable->setNumRows( 0 );
946  fillTable();
947  mClearTypeStore = false;
948 
949  fillComboBox();
950 }
951 
952 void CSVImportDialog::urlChanged( const TQString &file )
953 {
954  bool state = !file.isEmpty();
955 
956  enableButtonOK( state );
957  actionButton( User1 )->setEnabled( state );
958  actionButton( User2 )->setEnabled( state );
959 }
960 
961 void CSVImportDialog::codecChanged()
962 {
963  fillTable();
964 }
965 
966 #include <csvimportdialog.moc>
This class parses the date out of a given string with the help of a pattern.
Definition: dateparser.h:41