kaddressbook

csvimportdialog.cpp

00001 /*
00002    This file is part of KAddressBook.
00003    Copyright (C) 2003 Tobias Koenig <tokoe@kde.org>
00004                  based on the code of KSpread's CSV Import Dialog
00005 
00006    This library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License as published by the Free Software Foundation; either
00009    version 2 of the License, or (at your option) any later version.
00010 
00011    This library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public License
00017    along with this library; see the file COPYING.LIB.  If not, write to
00018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019    Boston, MA 02110-1301, USA.
00020 */
00021 
00022 
00023 #include <qbuttongroup.h>
00024 #include <qcheckbox.h>
00025 #include <qcombobox.h>
00026 #include <qlabel.h>
00027 #include <qlayout.h>
00028 #include <qlineedit.h>
00029 #include <qpushbutton.h>
00030 #include <qradiobutton.h>
00031 #include <qtable.h>
00032 #include <qtextcodec.h>
00033 #include <qtooltip.h>
00034 
00035 #include <kapplication.h>
00036 #include <kdebug.h>
00037 #include <kdialogbase.h>
00038 #include <kfiledialog.h>
00039 #include <klineedit.h>
00040 #include <klocale.h>
00041 #include <kinputdialog.h>
00042 #include <kmessagebox.h>
00043 #include <kprogress.h>
00044 #include <kstandarddirs.h>
00045 #include <kurlrequester.h>
00046 
00047 #include "dateparser.h"
00048 
00049 #include "csvimportdialog.h"
00050 
00051 enum { Local = 0, Guess = 1, Latin1 = 2, Uni = 3, MSBug = 4, Codec = 5 };
00052 
00053 CSVImportDialog::CSVImportDialog( KABC::AddressBook *ab, QWidget *parent,
00054                                   const char * name )
00055   : KDialogBase( Plain, i18n ( "CSV Import Dialog" ), Ok | Cancel | User1 |
00056                  User2, Ok, parent, name, true, true ),
00057     mAdjustRows( false ),
00058     mStartLine( 0 ),
00059     mTextQuote( '"' ),
00060     mDelimiter( "," ),
00061     mAddressBook( ab )
00062 {
00063   initGUI();
00064 
00065   mTypeMap.insert( i18n( "Undefined" ), Undefined );
00066   mTypeMap.insert( KABC::Addressee::formattedNameLabel(), FormattedName );
00067   mTypeMap.insert( KABC::Addressee::familyNameLabel(), FamilyName );
00068   mTypeMap.insert( KABC::Addressee::givenNameLabel(), GivenName );
00069   mTypeMap.insert( KABC::Addressee::additionalNameLabel(), AdditionalName );
00070   mTypeMap.insert( KABC::Addressee::prefixLabel(), Prefix );
00071   mTypeMap.insert( KABC::Addressee::suffixLabel(), Suffix );
00072   mTypeMap.insert( KABC::Addressee::nickNameLabel(), NickName );
00073   mTypeMap.insert( KABC::Addressee::birthdayLabel(), Birthday );
00074 
00075   mTypeMap.insert( KABC::Addressee::homeAddressStreetLabel(), HomeAddressStreet );
00076   mTypeMap.insert( KABC::Addressee::homeAddressLocalityLabel(),
00077                    HomeAddressLocality );
00078   mTypeMap.insert( KABC::Addressee::homeAddressRegionLabel(), HomeAddressRegion );
00079   mTypeMap.insert( KABC::Addressee::homeAddressPostalCodeLabel(),
00080                    HomeAddressPostalCode );
00081   mTypeMap.insert( KABC::Addressee::homeAddressCountryLabel(),
00082                    HomeAddressCountry );
00083   mTypeMap.insert( KABC::Addressee::homeAddressLabelLabel(), HomeAddressLabel );
00084 
00085   mTypeMap.insert( KABC::Addressee::businessAddressStreetLabel(),
00086                    BusinessAddressStreet );
00087   mTypeMap.insert( KABC::Addressee::businessAddressLocalityLabel(),
00088                    BusinessAddressLocality );
00089   mTypeMap.insert( KABC::Addressee::businessAddressRegionLabel(),
00090                    BusinessAddressRegion );
00091   mTypeMap.insert( KABC::Addressee::businessAddressPostalCodeLabel(),
00092                    BusinessAddressPostalCode );
00093   mTypeMap.insert( KABC::Addressee::businessAddressCountryLabel(),
00094                    BusinessAddressCountry );
00095   mTypeMap.insert( KABC::Addressee::businessAddressLabelLabel(),
00096                    BusinessAddressLabel );
00097 
00098   mTypeMap.insert( KABC::Addressee::homePhoneLabel(), HomePhone );
00099   mTypeMap.insert( KABC::Addressee::businessPhoneLabel(), BusinessPhone );
00100   mTypeMap.insert( KABC::Addressee::mobilePhoneLabel(), MobilePhone );
00101   mTypeMap.insert( KABC::Addressee::homeFaxLabel(), HomeFax );
00102   mTypeMap.insert( KABC::Addressee::businessFaxLabel(), BusinessFax );
00103   mTypeMap.insert( KABC::Addressee::carPhoneLabel(), CarPhone );
00104   mTypeMap.insert( KABC::Addressee::isdnLabel(), Isdn );
00105   mTypeMap.insert( KABC::Addressee::pagerLabel(), Pager );
00106   mTypeMap.insert( KABC::Addressee::emailLabel(), Email );
00107   mTypeMap.insert( KABC::Addressee::mailerLabel(), Mailer );
00108   mTypeMap.insert( KABC::Addressee::titleLabel(), Title );
00109   mTypeMap.insert( KABC::Addressee::roleLabel(), Role );
00110   mTypeMap.insert( KABC::Addressee::organizationLabel(), Organization );
00111   mTypeMap.insert( KABC::Addressee::noteLabel(), Note );
00112   mTypeMap.insert( KABC::Addressee::urlLabel(), URL );
00113 
00114   mCustomCounter = mTypeMap.count();
00115   int count = mCustomCounter;
00116 
00117   KABC::Field::List fields = mAddressBook->fields( KABC::Field::CustomCategory );
00118   KABC::Field::List::Iterator it;
00119   for ( it = fields.begin(); it != fields.end(); ++it, ++count )
00120     mTypeMap.insert( (*it)->label(), count );
00121 
00122   reloadCodecs();
00123 
00124   connect( mDelimiterBox, SIGNAL( clicked( int ) ),
00125            this, SLOT( delimiterClicked( int ) ) );
00126   connect( mDelimiterEdit, SIGNAL( returnPressed() ),
00127            this, SLOT( returnPressed() ) );
00128   connect( mDelimiterEdit, SIGNAL( textChanged ( const QString& ) ),
00129            this, SLOT( textChanged ( const QString& ) ) );
00130   connect( mComboLine, SIGNAL( activated( const QString& ) ),
00131            this, SLOT( lineSelected( const QString& ) ) );
00132   connect( mComboQuote, SIGNAL( activated( const QString& ) ),
00133            this, SLOT( textquoteSelected( const QString& ) ) );
00134   connect( mIgnoreDuplicates, SIGNAL( stateChanged( int ) ),
00135            this, SLOT( ignoreDuplicatesChanged( int ) ) );
00136   connect( mCodecCombo, SIGNAL( activated( const QString& ) ),
00137            this, SLOT( codecChanged() ) );
00138 
00139   connect( mUrlRequester, SIGNAL( returnPressed( const QString& ) ),
00140            this, SLOT( setFile( const QString& ) ) );
00141   connect( mUrlRequester, SIGNAL( urlSelected( const QString& ) ),
00142            this, SLOT( setFile( const QString& ) ) );
00143   connect( mUrlRequester->lineEdit(), SIGNAL( textChanged ( const QString& ) ),
00144            this, SLOT( urlChanged( const QString& ) ) );
00145 
00146   connect( this, SIGNAL( user1Clicked() ),
00147            this, SLOT( applyTemplate() ) );
00148 
00149   connect( this, SIGNAL( user2Clicked() ),
00150            this, SLOT( saveTemplate() ) );
00151 }
00152 
00153 CSVImportDialog::~CSVImportDialog()
00154 {
00155   mCodecs.clear();
00156 }
00157 
00158 KABC::AddresseeList CSVImportDialog::contacts() const
00159 {
00160   DateParser dateParser( mDatePatternEdit->text() );
00161   KABC::AddresseeList contacts;
00162 
00163   KProgressDialog progressDialog( mPage );
00164   progressDialog.setAutoClose( true );
00165   progressDialog.progressBar()->setTotalSteps( mTable->numRows() );
00166   progressDialog.setLabel( i18n( "Importing contacts" ) );
00167   progressDialog.show();
00168 
00169   kapp->processEvents();
00170 
00171   for ( int row = 1; row < mTable->numRows(); ++row ) {
00172     KABC::Addressee a;
00173     bool emptyRow = true;
00174     KABC::Address addrHome( KABC::Address::Home );
00175     KABC::Address addrWork( KABC::Address::Work );
00176     for ( int col = 0; col < mTable->numCols(); ++col ) {
00177       QComboTableItem *item = static_cast<QComboTableItem*>( mTable->item( 0,
00178                                                              col ) );
00179       if ( !item ) {
00180         kdError() << "ERROR: item cast failed" << endl;
00181         continue;
00182       }
00183 
00184       QString value = mTable->text( row, col );
00185       if ( 1 == row && static_cast<QTableItem *>(item)->text() == value )
00186         // we are looking at a header row, stop now
00187         break;
00188 
00189       if ( !value.isEmpty() )
00190         emptyRow = false;
00191 
00192       switch ( posToType( item->currentItem() ) ) {
00193         case Undefined:
00194           continue;
00195           break;
00196         case FormattedName:
00197           a.setFormattedName( value );
00198           break;
00199         case GivenName:
00200           a.setGivenName( value );
00201           break;
00202         case FamilyName:
00203           a.setFamilyName( value );
00204           break;
00205         case AdditionalName:
00206           a.setAdditionalName( value );
00207           break;
00208         case Prefix:
00209           a.setPrefix( value );
00210           break;
00211         case Suffix:
00212           a.setSuffix( value );
00213           break;
00214         case NickName:
00215           a.setNickName( value );
00216           break;
00217         case Birthday:
00218           a.setBirthday( dateParser.parse( value ) );
00219           break;
00220         case Email:
00221           if ( !value.isEmpty() )
00222             a.insertEmail( value, true );
00223           break;
00224         case Role:
00225           a.setRole( value );
00226           break;
00227         case Title:
00228           a.setTitle( value );
00229           break;
00230         case Mailer:
00231           a.setMailer( value );
00232           break;
00233         case URL:
00234           a.setUrl( KURL( value ) );
00235           break;
00236         case Organization:
00237           a.setOrganization( value );
00238           break;
00239         case Note:
00240           a.setNote( a.note() + value + "\n" );
00241           break;
00242 
00243         case HomePhone:
00244           if ( !value.isEmpty() ) {
00245             KABC::PhoneNumber number( value, KABC::PhoneNumber::Home );
00246             a.insertPhoneNumber( number );
00247           }
00248           break;
00249         case BusinessPhone:
00250           if ( !value.isEmpty() ) {
00251             KABC::PhoneNumber number( value, KABC::PhoneNumber::Work );
00252             a.insertPhoneNumber( number );
00253           }
00254           break;
00255         case MobilePhone:
00256           if ( !value.isEmpty() ) {
00257             KABC::PhoneNumber number( value, KABC::PhoneNumber::Cell );
00258             a.insertPhoneNumber( number );
00259           }
00260           break;
00261         case HomeFax:
00262           if ( !value.isEmpty() ) {
00263             KABC::PhoneNumber number( value, KABC::PhoneNumber::Home |
00264                                              KABC::PhoneNumber::Fax );
00265             a.insertPhoneNumber( number );
00266           }
00267           break;
00268         case BusinessFax:
00269           if ( !value.isEmpty() ) {
00270             KABC::PhoneNumber number( value, KABC::PhoneNumber::Work |
00271                                              KABC::PhoneNumber::Fax );
00272             a.insertPhoneNumber( number );
00273           }
00274           break;
00275         case CarPhone:
00276           if ( !value.isEmpty() ) {
00277             KABC::PhoneNumber number( value, KABC::PhoneNumber::Car );
00278             a.insertPhoneNumber( number );
00279           }
00280           break;
00281         case Isdn:
00282           if ( !value.isEmpty() ) {
00283             KABC::PhoneNumber number( value, KABC::PhoneNumber::Isdn );
00284             a.insertPhoneNumber( number );
00285           }
00286           break;
00287         case Pager:
00288           if ( !value.isEmpty() ) {
00289             KABC::PhoneNumber number( value, KABC::PhoneNumber::Pager );
00290             a.insertPhoneNumber( number );
00291           }
00292           break;
00293 
00294         case HomeAddressStreet:
00295           addrHome.setStreet( value );
00296           break;
00297         case HomeAddressLocality:
00298           addrHome.setLocality( value );
00299           break;
00300         case HomeAddressRegion:
00301           addrHome.setRegion( value );
00302           break;
00303         case HomeAddressPostalCode:
00304           addrHome.setPostalCode( value );
00305           break;
00306         case HomeAddressCountry:
00307           addrHome.setCountry( value );
00308           break;
00309         case HomeAddressLabel:
00310           addrHome.setLabel( value );
00311           break;
00312 
00313         case BusinessAddressStreet:
00314           addrWork.setStreet( value );
00315           break;
00316         case BusinessAddressLocality:
00317           addrWork.setLocality( value );
00318           break;
00319         case BusinessAddressRegion:
00320           addrWork.setRegion( value );
00321           break;
00322         case BusinessAddressPostalCode:
00323           addrWork.setPostalCode( value );
00324           break;
00325         case BusinessAddressCountry:
00326           addrWork.setCountry( value );
00327           break;
00328         case BusinessAddressLabel:
00329           addrWork.setLabel( value );
00330           break;
00331         default:
00332           KABC::Field::List fields = mAddressBook->fields( KABC::Field::CustomCategory );
00333           KABC::Field::List::Iterator it;
00334 
00335           int counter = 0;
00336           for ( it = fields.begin(); it != fields.end(); ++it ) {
00337             if ( counter == (int)( posToType( item->currentItem() ) - mCustomCounter ) ) {
00338               (*it)->setValue( a, value );
00339               break;
00340             }
00341             ++counter;
00342           }
00343           break;
00344       }
00345     }
00346 
00347     kapp->processEvents();
00348 
00349     if ( progressDialog.wasCancelled() )
00350       return KABC::AddresseeList();
00351 
00352     progressDialog.progressBar()->advance( 1 );
00353 
00354     if ( !addrHome.isEmpty() )
00355       a.insertAddress( addrHome );
00356     if ( !addrWork.isEmpty() )
00357       a.insertAddress( addrWork );
00358 
00359     if ( !emptyRow && !a.isEmpty() )
00360       contacts.append( a );
00361   }
00362 
00363   return contacts;
00364 }
00365 
00366 void CSVImportDialog::initGUI()
00367 {
00368   mPage = plainPage();
00369 
00370   QGridLayout *layout = new QGridLayout( mPage, 1, 1, marginHint(),
00371                                          spacingHint() );
00372   QHBoxLayout *hbox = new QHBoxLayout();
00373   hbox->setSpacing( spacingHint() );
00374 
00375   QLabel *label = new QLabel( i18n( "File to import:" ), mPage );
00376   hbox->addWidget( label );
00377 
00378   mUrlRequester = new KURLRequester( mPage );
00379   mUrlRequester->setFilter( "*.csv" );
00380   hbox->addWidget( mUrlRequester );
00381 
00382   layout->addMultiCellLayout( hbox, 0, 0, 0, 4 );
00383 
00384   // Delimiter: comma, semicolon, tab, space, other
00385   mDelimiterBox = new QButtonGroup( i18n( "Delimiter" ), mPage );
00386   mDelimiterBox->setColumnLayout( 0, Qt::Vertical );
00387   mDelimiterBox->layout()->setSpacing( spacingHint() );
00388   mDelimiterBox->layout()->setMargin( marginHint() );
00389   QGridLayout *delimiterLayout = new QGridLayout( mDelimiterBox->layout() );
00390   delimiterLayout->setAlignment( Qt::AlignTop );
00391   layout->addMultiCellWidget( mDelimiterBox, 1, 4, 0, 0 );
00392 
00393   mRadioComma = new QRadioButton( i18n( "Comma" ), mDelimiterBox );
00394   mRadioComma->setChecked( true );
00395   delimiterLayout->addWidget( mRadioComma, 0, 0 );
00396 
00397   mRadioSemicolon = new QRadioButton( i18n( "Semicolon" ), mDelimiterBox );
00398   delimiterLayout->addWidget( mRadioSemicolon, 0, 1 );
00399 
00400   mRadioTab = new QRadioButton( i18n( "Tabulator" ), mDelimiterBox );
00401   delimiterLayout->addWidget( mRadioTab, 1, 0 );
00402 
00403   mRadioSpace = new QRadioButton( i18n( "Space" ), mDelimiterBox );
00404   delimiterLayout->addWidget( mRadioSpace, 1, 1 );
00405 
00406   mRadioOther = new QRadioButton( i18n( "Other" ), mDelimiterBox );
00407   delimiterLayout->addWidget( mRadioOther, 0, 2 );
00408 
00409   mDelimiterEdit = new QLineEdit( mDelimiterBox );
00410   delimiterLayout->addWidget( mDelimiterEdit, 1, 2 );
00411 
00412   mComboLine = new QComboBox( false, mPage );
00413   mComboLine->insertItem( i18n( "1" ) );
00414   layout->addWidget( mComboLine, 2, 3 );
00415 
00416   mComboQuote = new QComboBox( false, mPage );
00417   mComboQuote->insertItem( i18n( "\"" ), 0 );
00418   mComboQuote->insertItem( i18n( "'" ), 1 );
00419   mComboQuote->insertItem( i18n( "None" ), 2 );
00420   layout->addWidget( mComboQuote, 2, 2 );
00421 
00422   mDatePatternEdit = new QLineEdit( mPage );
00423   mDatePatternEdit->setText( "Y-M-D" ); // ISO 8601 format as default
00424   QToolTip::add( mDatePatternEdit, i18n( "<ul><li>y: year with 2 digits</li>"
00425                                          "<li>Y: year with 4 digits</li>"
00426                                          "<li>m: month with 1 or 2 digits</li>"
00427                                          "<li>M: month with 2 digits</li>"
00428                                          "<li>d: day with 1 or 2 digits</li>"
00429                                          "<li>D: day with 2 digits</li></ul>" ) );
00430   layout->addWidget( mDatePatternEdit, 2, 4 );
00431 
00432   label = new QLabel( i18n( "Start at line:" ), mPage );
00433   layout->addWidget( label, 1, 3 );
00434 
00435   label = new QLabel( i18n( "Textquote:" ), mPage );
00436   layout->addWidget( label, 1, 2 );
00437 
00438   label = new QLabel( i18n( "Date format:" ), mPage );
00439   layout->addWidget( label, 1, 4 );
00440 
00441   mIgnoreDuplicates = new QCheckBox( mPage );
00442   mIgnoreDuplicates->setText( i18n( "Ignore duplicate delimiters" ) );
00443   layout->addMultiCellWidget( mIgnoreDuplicates, 3, 3, 2, 4 );
00444 
00445   mCodecCombo = new QComboBox( mPage );
00446   layout->addMultiCellWidget( mCodecCombo, 4, 4, 2, 4 );
00447 
00448   mTable = new QTable( 0, 0, mPage );
00449   mTable->setSelectionMode( QTable::NoSelection );
00450   mTable->horizontalHeader()->hide();
00451   layout->addMultiCellWidget( mTable, 5, 5, 0, 4 );
00452 
00453   setButtonText( User1, i18n( "Apply Template..." ) );
00454   setButtonText( User2, i18n( "Save Template..." ) );
00455 
00456   enableButtonOK( false );
00457   actionButton( User1 )->setEnabled( false );
00458   actionButton( User2 )->setEnabled( false );
00459 
00460   resize( 400, 300 );
00461 }
00462 
00463 void CSVImportDialog::fillTable()
00464 {
00465   int row, column;
00466   bool lastCharDelimiter = false;
00467   bool ignoreDups = mIgnoreDuplicates->isChecked();
00468   enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
00469          S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;
00470 
00471   QChar x;
00472   QString field;
00473 
00474   // store previous assignment
00475   mTypeStore.clear();
00476   for ( column = 0; column < mTable->numCols(); ++column ) {
00477     QComboTableItem *item = static_cast<QComboTableItem*>( mTable->item( 0,
00478                                                            column ) );
00479     if ( !item || mClearTypeStore )
00480       mTypeStore.append( typeToPos( Undefined ) );
00481     else if ( item )
00482       mTypeStore.append( item->currentItem() );
00483   }
00484 
00485   clearTable();
00486 
00487   row = column = 1;
00488 
00489   QTextStream inputStream( mFileArray, IO_ReadOnly );
00490 
00491   // find the current codec
00492   int code = mCodecCombo->currentItem();
00493   if ( code == Local )
00494     inputStream.setEncoding( QTextStream::Locale );
00495   else if ( code >= Codec )
00496     inputStream.setCodec( mCodecs.at( code - Codec ) );
00497   else if ( code == Uni )
00498     inputStream.setEncoding( QTextStream::Unicode );
00499   else if ( code == MSBug )
00500     inputStream.setEncoding( QTextStream::UnicodeReverse );
00501   else if ( code == Latin1 )
00502     inputStream.setEncoding( QTextStream::Latin1 );
00503   else if ( code == Guess ) {
00504     QTextCodec* codec = QTextCodec::codecForContent( mFileArray.data(), mFileArray.size() );
00505     if ( codec ) {
00506       KMessageBox::information( this, i18n( "Using codec '%1'" ).arg( codec->name() ), i18n( "Encoding" ) );
00507       inputStream.setCodec( codec );
00508     }
00509   }
00510 
00511   int maxColumn = 0;
00512   while ( !inputStream.atEnd() ) {
00513     inputStream >> x; // read one char
00514 
00515     if ( x == '\r' ) inputStream >> x; // eat '\r', to handle DOS/LOSEDOWS files correctly
00516 
00517     switch ( state ) {
00518      case S_START :
00519       if ( x == mTextQuote ) {
00520         state = S_QUOTED_FIELD;
00521       } else if ( x == mDelimiter ) {
00522         if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
00523           ++column;
00524         lastCharDelimiter = true;
00525       } else if ( x == '\n' ) {
00526         ++row;
00527         column = 1;
00528       } else {
00529         field += x;
00530         state = S_MAYBE_NORMAL_FIELD;
00531       }
00532       break;
00533      case S_QUOTED_FIELD :
00534       if ( x == mTextQuote ) {
00535         state = S_MAYBE_END_OF_QUOTED_FIELD;
00536       } else if ( x == '\n' &&  mTextQuote.isNull() ) {
00537         setText( row - mStartLine + 1, column, field );
00538         field = "";
00539         if ( x == '\n' ) {
00540           ++row;
00541           column = 1;
00542         } else {
00543           if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
00544             ++column;
00545           lastCharDelimiter = true;
00546         }
00547         state = S_START;
00548       } else {
00549         field += x;
00550       }
00551       break;
00552      case S_MAYBE_END_OF_QUOTED_FIELD :
00553       if ( x == mTextQuote ) {
00554         field += x;
00555         state = S_QUOTED_FIELD;
00556       } else if ( x == mDelimiter || x == '\n' ) {
00557         setText( row - mStartLine + 1, column, field );
00558         field = "";
00559         if ( x == '\n' ) {
00560           ++row;
00561           column = 1;
00562         } else {
00563           if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
00564             ++column;
00565           lastCharDelimiter = true;
00566         }
00567         state = S_START;
00568       } else {
00569         state = S_END_OF_QUOTED_FIELD;
00570       }
00571       break;
00572      case S_END_OF_QUOTED_FIELD :
00573       if ( x == mDelimiter || x == '\n' ) {
00574         setText( row - mStartLine + 1, column, field );
00575         field = "";
00576         if ( x == '\n' ) {
00577           ++row;
00578           column = 1;
00579         } else {
00580           if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
00581             ++column;
00582           lastCharDelimiter = true;
00583         }
00584         state = S_START;
00585       } else {
00586         state = S_END_OF_QUOTED_FIELD;
00587       }
00588       break;
00589      case S_MAYBE_NORMAL_FIELD :
00590       if ( x == mTextQuote ) {
00591         field = "";
00592         state = S_QUOTED_FIELD;
00593         break;
00594       }
00595      case S_NORMAL_FIELD :
00596       if ( x == mDelimiter || x == '\n' ) {
00597         setText( row - mStartLine + 1, column, field );
00598         field = "";
00599         if ( x == '\n' ) {
00600           ++row;
00601           column = 1;
00602         } else {
00603           if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
00604             ++column;
00605           lastCharDelimiter = true;
00606         }
00607         state = S_START;
00608       } else {
00609         field += x;
00610       }
00611     }
00612     if ( x != mDelimiter )
00613       lastCharDelimiter = false;
00614 
00615     if ( column > maxColumn )
00616       maxColumn = column;
00617   }
00618 
00619   // file with only one line without '\n'
00620   if ( field.length() > 0 ) {
00621     setText( row - mStartLine + 1, column, field );
00622     ++row;
00623     field = "";
00624   }
00625 
00626   adjustRows( row - mStartLine );
00627   mTable->setNumCols( maxColumn );
00628 
00629   for ( column = 0; column < mTable->numCols(); ++column ) {
00630     QComboTableItem *item = new QComboTableItem( mTable, mTypeMap.keys() );
00631     mTable->setItem( 0, column, item );
00632     if ( column < (int)mTypeStore.count() )
00633       item->setCurrentItem( mTypeStore[ column ] );
00634     else
00635       item->setCurrentItem( typeToPos( Undefined ) );
00636     mTable->adjustColumn( column );
00637   }
00638 
00639   resizeColumns();
00640 }
00641 
00642 void CSVImportDialog::clearTable()
00643 {
00644   for ( int row = 0; row < mTable->numRows(); ++row )
00645     for ( int column = 0; column < mTable->numCols(); ++column )
00646       mTable->clearCell( row, column );
00647 }
00648 
00649 void CSVImportDialog::fillComboBox()
00650 {
00651   mComboLine->clear();
00652   for ( int row = 1; row < mTable->numRows() + 1; ++row )
00653     mComboLine->insertItem( QString::number( row ), row - 1 );
00654 }
00655 
00656 void CSVImportDialog::reloadCodecs()
00657 {
00658   mCodecCombo->clear();
00659 
00660   mCodecs.clear();
00661 
00662   QTextCodec *codec;
00663   for ( int i = 0; ( codec = QTextCodec::codecForIndex( i ) ); i++ )
00664     mCodecs.append( codec );
00665 
00666   mCodecCombo->insertItem( i18n( "Local (%1)" ).arg( QTextCodec::codecForLocale()->name() ), Local );
00667   mCodecCombo->insertItem( i18n( "[guess]" ), Guess );
00668   mCodecCombo->insertItem( i18n( "Latin1" ), Latin1 );
00669   mCodecCombo->insertItem( i18n( "Unicode" ), Uni );
00670   mCodecCombo->insertItem( i18n( "Microsoft Unicode" ), MSBug );
00671 
00672     for ( uint i = 0; i < mCodecs.count(); i++ )
00673     mCodecCombo->insertItem( mCodecs.at( i )->name(), Codec + i );
00674 }
00675 
00676 void CSVImportDialog::setText( int row, int col, const QString& text )
00677 {
00678   if ( row < 1 ) // skipped by the user
00679     return;
00680 
00681   if ( mTable->numRows() < row ) {
00682     mTable->setNumRows( row + 5000 ); // We add 5000 at a time to limit recalculations
00683     mAdjustRows = true;
00684   }
00685 
00686   if ( mTable->numCols() < col )
00687     mTable->setNumCols( col + 50 ); // We add 50 at a time to limit recalculation
00688 
00689   mTable->setText( row - 1, col - 1, text );
00690 }
00691 
00692 /*
00693  * Called after the first fillTable() when number of rows are unknown.
00694  */
00695 void CSVImportDialog::adjustRows( int rows )
00696 {
00697   if ( mAdjustRows ) {
00698     mTable->setNumRows( rows );
00699     mAdjustRows = false;
00700   }
00701 }
00702 
00703 void CSVImportDialog::resizeColumns()
00704 {
00705   QFontMetrics fm = fontMetrics();
00706   int width = 0;
00707 
00708   QMap<QString, uint>::ConstIterator it;
00709   for ( it = mTypeMap.begin(); it != mTypeMap.end(); ++it ) {
00710     width = QMAX( width, fm.width( it.key() ) );
00711   }
00712 
00713   for ( int i = 0; i < mTable->numCols(); ++i )
00714     mTable->setColumnWidth( i, QMAX( width + 15, mTable->columnWidth( i ) ) );
00715 }
00716 
00717 void CSVImportDialog::returnPressed()
00718 {
00719   if ( mDelimiterBox->id( mDelimiterBox->selected() ) != 4 )
00720     return;
00721 
00722   mDelimiter = mDelimiterEdit->text();
00723   fillTable();
00724 }
00725 
00726 void CSVImportDialog::textChanged ( const QString& )
00727 {
00728   mRadioOther->setChecked ( true );
00729   delimiterClicked( 4 ); // other
00730 }
00731 
00732 void CSVImportDialog::delimiterClicked( int id )
00733 {
00734   switch ( id ) {
00735    case 0: // comma
00736     mDelimiter = ",";
00737     break;
00738    case 4: // other
00739     mDelimiter = mDelimiterEdit->text();
00740     break;
00741    case 2: // tab
00742     mDelimiter = "\t";
00743     break;
00744    case 3: // space
00745     mDelimiter = " ";
00746     break;
00747    case 1: // semicolon
00748     mDelimiter = ";";
00749     break;
00750   }
00751 
00752   fillTable();
00753 }
00754 
00755 void CSVImportDialog::textquoteSelected( const QString& mark )
00756 {
00757   if ( mComboQuote->currentItem() == 2 )
00758     mTextQuote = 0;
00759   else
00760     mTextQuote = mark[ 0 ];
00761 
00762   fillTable();
00763 }
00764 
00765 void CSVImportDialog::lineSelected( const QString& line )
00766 {
00767   mStartLine = line.toInt() - 1;
00768   fillTable();
00769 }
00770 
00771 void CSVImportDialog::slotOk()
00772 {
00773   bool assigned = false;
00774 
00775   for ( int column = 0; column < mTable->numCols(); ++column ) {
00776     QComboTableItem *item = static_cast<QComboTableItem*>( mTable->item( 0,
00777                                                            column ) );
00778     if ( item && posToType( item->currentItem() ) != Undefined )
00779       assigned = true;
00780   }
00781 
00782   if ( assigned )
00783     KDialogBase::slotOk();
00784   else
00785     KMessageBox::sorry( this, i18n( "You have to assign at least one column." ) );
00786 }
00787 
00788 void CSVImportDialog::applyTemplate()
00789 {
00790   QMap<uint,int> columnMap;
00791   QMap<QString, QString> fileMap;
00792   QStringList templates;
00793 
00794   // load all template files
00795   QStringList list = KGlobal::dirs()->findAllResources( "data" , QString( kapp->name() ) +
00796       "/csv-templates/*.desktop", true, true );
00797 
00798   for ( QStringList::iterator it = list.begin(); it != list.end(); ++it )
00799   {
00800     KSimpleConfig config( *it, true );
00801 
00802     if ( !config.hasGroup( "csv column map" ) )
00803         continue;
00804 
00805     config.setGroup( "Misc" );
00806     templates.append( config.readEntry( "Name" ) );
00807     fileMap.insert( config.readEntry( "Name" ), *it );
00808   }
00809 
00810   // let the user chose, what to take
00811   bool ok = false;
00812   QString tmp;
00813   tmp = KInputDialog::getItem( i18n( "Template Selection" ),
00814                   i18n( "Please select a template, that matches the CSV file:" ),
00815                   templates, 0, false, &ok, this );
00816 
00817   if ( !ok )
00818     return;
00819 
00820   KSimpleConfig config( fileMap[ tmp ], true );
00821   config.setGroup( "General" );
00822   mDatePatternEdit->setText( config.readEntry( "DatePattern", "Y-M-D" ) );
00823   uint numColumns = config.readUnsignedNumEntry( "Columns" );
00824   mDelimiterEdit->setText( config.readEntry( "DelimiterOther" ) );
00825   mDelimiterBox->setButton( config.readNumEntry( "DelimiterType" ) );
00826   delimiterClicked( config.readNumEntry( "DelimiterType" ) );
00827   int quoteType = config.readNumEntry( "QuoteType" );
00828   mComboQuote->setCurrentItem( quoteType );
00829   textquoteSelected( mComboQuote->currentText() );
00830 
00831   // create the column map
00832   config.setGroup( "csv column map" );
00833   for ( uint i = 0; i < numColumns; ++i ) {
00834     int col = config.readNumEntry( QString::number( i ) );
00835     columnMap.insert( i, col );
00836   }
00837 
00838   // apply the column map
00839   for ( uint column = 0; column < columnMap.count(); ++column ) {
00840     int type = columnMap[ column ];
00841     QComboTableItem *item = static_cast<QComboTableItem*>( mTable->item( 0,
00842                                                            column ) );
00843     if ( item )
00844       item->setCurrentItem( typeToPos( type ) );
00845   }
00846 }
00847 
00848 void CSVImportDialog::saveTemplate()
00849 {
00850   QString fileName = KFileDialog::getSaveFileName(
00851                      locateLocal( "data", QString( kapp->name() ) + "/csv-templates/" ),
00852                      "*.desktop", this );
00853 
00854   if ( fileName.isEmpty() )
00855     return;
00856 
00857   if ( !fileName.contains( ".desktop" ) )
00858     fileName += ".desktop";
00859 
00860   QString name = KInputDialog::getText( i18n( "Template Name" ), i18n( "Please enter a name for the template:" ) );
00861 
00862   if ( name.isEmpty() )
00863     return;
00864 
00865   KConfig config( fileName );
00866   config.setGroup( "General" );
00867   config.writeEntry( "DatePattern", mDatePatternEdit->text() );
00868   config.writeEntry( "Columns", mTable->numCols() );
00869   config.writeEntry( "DelimiterType", mDelimiterBox->id( mDelimiterBox->selected() ) );
00870   config.writeEntry( "DelimiterOther", mDelimiterEdit->text() );
00871   config.writeEntry( "QuoteType", mComboQuote->currentItem() );
00872 
00873   config.setGroup( "Misc" );
00874   config.writeEntry( "Name", name );
00875 
00876   config.setGroup( "csv column map" );
00877 
00878   for ( int column = 0; column < mTable->numCols(); ++column ) {
00879     QComboTableItem *item = static_cast<QComboTableItem*>( mTable->item( 0,
00880                                                            column ) );
00881     if ( item )
00882       config.writeEntry( QString::number( column ), posToType(
00883                          item->currentItem() ) );
00884     else
00885       config.writeEntry( QString::number( column ), 0 );
00886   }
00887 
00888   config.sync();
00889 }
00890 
00891 QString CSVImportDialog::getText( int row, int col )
00892 {
00893   return mTable->text( row, col );
00894 }
00895 
00896 uint CSVImportDialog::posToType( int pos ) const
00897 {
00898   uint counter = 0;
00899   QMap<QString, uint>::ConstIterator it;
00900   for ( it = mTypeMap.begin(); it != mTypeMap.end(); ++it, ++counter )
00901     if ( counter == (uint)pos )
00902       return it.data();
00903 
00904   return 0;
00905 }
00906 
00907 int CSVImportDialog::typeToPos( uint type ) const
00908 {
00909   uint counter = 0;
00910   QMap<QString, uint>::ConstIterator it;
00911   for ( it = mTypeMap.begin(); it != mTypeMap.end(); ++it, ++counter )
00912     if ( it.data() == type )
00913       return counter;
00914 
00915   return -1;
00916 }
00917 
00918 void CSVImportDialog::ignoreDuplicatesChanged( int )
00919 {
00920   fillTable();
00921 }
00922 
00923 void CSVImportDialog::setFile( const QString &fileName )
00924 {
00925   if ( fileName.isEmpty() )
00926     return;
00927 
00928   QFile file( fileName );
00929   if ( !file.open( IO_ReadOnly ) ) {
00930     KMessageBox::sorry( this, i18n( "Cannot open input file." ) );
00931     file.close();
00932     return;
00933   }
00934 
00935   mFileArray = file.readAll();
00936   file.close();
00937 
00938   mClearTypeStore = true;
00939   clearTable();
00940   mTable->setNumCols( 0 );
00941   mTable->setNumRows( 0 );
00942   fillTable();
00943   mClearTypeStore = false;
00944 
00945   fillComboBox();
00946 }
00947 
00948 void CSVImportDialog::urlChanged( const QString &file )
00949 {
00950   bool state = !file.isEmpty();
00951 
00952   enableButtonOK( state );
00953   actionButton( User1 )->setEnabled( state );
00954   actionButton( User2 )->setEnabled( state );
00955 }
00956 
00957 void CSVImportDialog::codecChanged()
00958 {
00959   fillTable();
00960 }
00961 
00962 #include <csvimportdialog.moc>
KDE Home | KDE Accessibility Home | Description of Access Keys