26 #include <KApplication>
29 #include <KFileDialog>
30 #include <KInputDialog>
33 #include <KMessageBox>
34 #include <KProgressDialog>
35 #include <KStandardDirs>
36 #include <KUrlRequester>
38 #include <QtCore/QPointer>
39 #include <QtCore/QTextCodec>
40 #include <QtCore/QThread>
41 #include <QtCore/QUuid>
42 #include <QButtonGroup>
44 #include <QGridLayout>
46 #include <QHBoxLayout>
47 #include <QHeaderView>
49 #include <QPushButton>
50 #include <QRadioButton>
51 #include <QStyledItemDelegate>
62 class ContactFieldComboBox :
public KComboBox
66 ContactFieldComboBox( QWidget *parent = 0 )
73 QMapIterator<QString, ContactFields::Field> it( mFieldMap );
74 while ( it.hasNext() ) {
77 addItem( it.key(), QVariant( it.value() ) );
81 for (
int i = 0; i < count(); ++i ) {
82 maxLength = qMax( maxLength, itemText( i ).length() );
85 setMinimumContentsLength( maxLength );
86 setSizeAdjustPolicy( AdjustToMinimumContentsLength );
87 setFixedSize( sizeHint() );
92 setCurrentIndex( findData( (uint)field ) );
101 static void fillFieldMap()
103 if ( !mFieldMap.isEmpty() ) {
110 for (
int i = 0; i < fields.count(); ++i ) {
115 static QMap<QString, ContactFields::Field> mFieldMap;
118 QMap<QString, ContactFields::Field> ContactFieldComboBox::mFieldMap;
120 class ContactFieldDelegate :
public QStyledItemDelegate
123 ContactFieldDelegate(
QObject *parent = 0 )
124 : QStyledItemDelegate( parent )
128 QString displayText(
const QVariant &value,
const QLocale & )
const
133 QWidget *createEditor( QWidget *parent,
const QStyleOptionViewItem &,
134 const QModelIndex & )
const
136 ContactFieldComboBox *editor =
new ContactFieldComboBox( parent );
141 void setEditorData( QWidget *editor,
const QModelIndex &index )
const
143 const unsigned int value = index.model()->data( index, Qt::EditRole ).toUInt();
145 ContactFieldComboBox *fieldCombo =
static_cast<ContactFieldComboBox*
>( editor );
149 void setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
151 ContactFieldComboBox *fieldCombo =
static_cast<ContactFieldComboBox*
>( editor );
153 model->setData( index, fieldCombo->currentField(), Qt::EditRole );
156 void updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
157 const QModelIndex & )
const
159 editor->setGeometry( option.rect );
162 void paint( QPainter *painter,
const QStyleOptionViewItem &option,
163 const QModelIndex &index )
const
165 if ( index.row() == 0 ) {
166 QStyleOptionViewItem headerOption( option );
167 headerOption.font.setBold(
true );
169 QStyledItemDelegate::paint( painter, headerOption, index );
171 QStyledItemDelegate::paint( painter, option, index );
177 :
KDialog( parent ), mDevice( 0 )
179 setCaption( i18nc(
"@title:window",
"CSV Import Dialog" ) );
180 setButtons( Ok | Cancel | User1 | User2 );
181 setDefaultButton( Ok );
183 showButtonSeparator(
true );
191 connect( mUrlRequester, SIGNAL(returnPressed(QString)),
192 this, SLOT(setFile(QString)) );
193 connect( mUrlRequester, SIGNAL(urlSelected(KUrl)),
194 this, SLOT(setFile(KUrl)) );
195 connect( mUrlRequester->lineEdit(), SIGNAL(textChanged(QString)),
196 this, SLOT(urlChanged(QString)) );
197 connect( mDelimiterGroup, SIGNAL(buttonClicked(
int)),
198 this, SLOT(delimiterClicked(
int)) );
199 connect( mDelimiterEdit, SIGNAL(returnPressed()),
200 this, SLOT(customDelimiterChanged()) );
201 connect( mDelimiterEdit, SIGNAL(textChanged(QString)),
202 this, SLOT(customDelimiterChanged(QString)) );
203 connect( mComboQuote, SIGNAL(activated(QString)),
204 this, SLOT(textQuoteChanged(QString)) );
205 connect( mCodecCombo, SIGNAL(activated(QString)),
206 this, SLOT(codecChanged()) );
207 connect( mSkipFirstRow, SIGNAL(toggled(
bool)),
208 this, SLOT(skipFirstRowChanged(
bool)) );
210 connect( mModel, SIGNAL(finishedLoading()),
this, SLOT(modelFinishedLoading()) );
212 delimiterClicked( 0 );
213 textQuoteChanged( QLatin1String(
"\"") );
214 skipFirstRowChanged(
false );
225 DateParser dateParser( mDatePatternEdit->text() );
227 KProgressDialog progressDialog( const_cast<CSVImportDialog*>(
this )->mainWidget() );
228 progressDialog.setAutoClose(
true );
229 progressDialog.progressBar()->setMaximum( mModel->
rowCount() );
230 progressDialog.setLabelText( i18nc(
"@label",
"Importing contacts" ) );
231 progressDialog.show();
233 kapp->processEvents();
235 for (
int row = 1; row < mModel->
rowCount(); ++row ) {
236 KABC::Addressee contact;
237 bool emptyRow =
true;
239 for (
int column = 0; column < mModel->
columnCount(); ++column ) {
240 QString value = mModel->
data( mModel->index( row, column ), Qt::DisplayRole ).toString();
242 if ( !value.isEmpty() ) {
250 value = dateParser.parse( value ).toString( Qt::ISODate );
253 value.replace( QLatin1String(
"\\n"), QLatin1String(
"\n") );
259 kapp->processEvents();
261 if ( progressDialog.wasCancelled() ) {
262 return KABC::AddresseeList();
265 progressDialog.progressBar()->setValue( progressDialog.progressBar()->value() + 1 );
267 if ( !emptyRow && !contact.isEmpty() ) {
268 contacts.append( contact );
275 void CSVImportDialog::initGUI()
277 QWidget *page =
new QWidget(
this );
278 setMainWidget( page );
280 QGridLayout *layout =
new QGridLayout( page );
281 layout->setSpacing( spacingHint() );
282 layout->setMargin( 0 );
284 QHBoxLayout *hbox =
new QHBoxLayout();
285 hbox->setSpacing( spacingHint() );
287 QLabel *label =
new QLabel( i18nc(
"@label",
"File to import:" ), page );
288 hbox->addWidget( label );
290 mUrlRequester =
new KUrlRequester( page );
291 mUrlRequester->setFilter( QLatin1String(
"*.csv") );
292 mUrlRequester->lineEdit()->setTrapReturnKey(
true );
293 mUrlRequester->setToolTip(
294 i18nc(
"@info:tooltip",
"Select a csv file to import" ) );
295 mUrlRequester->setWhatsThis(
296 i18nc(
"@info:whatsthis",
297 "Click this button to start a file chooser that will allow you to "
298 "select a csv file to import." ) );
299 hbox->addWidget( mUrlRequester );
301 layout->addLayout( hbox, 0, 0, 1, 5 );
304 QGroupBox *group =
new QGroupBox( i18nc(
"@title:group",
"Delimiter" ), page );
305 QGridLayout *delimiterLayout =
new QGridLayout;
306 delimiterLayout->setMargin( marginHint() );
307 delimiterLayout->setSpacing( spacingHint() );
308 group->setLayout( delimiterLayout );
309 delimiterLayout->setAlignment( Qt::AlignTop );
310 layout->addWidget( group, 1, 0, 4, 1 );
312 mDelimiterGroup =
new QButtonGroup(
this );
313 mDelimiterGroup->setExclusive(
true );
315 QRadioButton *button =
new QRadioButton( i18nc(
"@option:radio Field separator",
"Comma" ) );
317 i18nc(
"@info:tooltip",
"Set the field separator to a comma" ) );
318 button->setWhatsThis(
319 i18nc(
"@info:whatsthis",
320 "Select this option if your csv file uses the comma as a field separator." ) );
321 button->setChecked(
true );
322 mDelimiterGroup->addButton( button, 0 );
323 delimiterLayout->addWidget( button, 0, 0 );
325 button =
new QRadioButton( i18nc(
"@option:radio Field separator",
"Semicolon" ) );
327 i18nc(
"@info:tooltip",
"Set the field separator to a semicolon" ) );
328 button->setWhatsThis(
329 i18nc(
"@info:whatsthis",
330 "Select this option if your csv file uses the semicolon as a field separator." ) );
331 mDelimiterGroup->addButton( button, 1 );
332 delimiterLayout->addWidget( button, 0, 1 );
334 button =
new QRadioButton( i18nc(
"@option:radio Field separator",
"Tabulator" ) );
336 i18nc(
"@info:tooltip",
"Set the field separator to a tab character" ) );
337 button->setWhatsThis(
338 i18nc(
"@info:whatsthis",
339 "Select this option if your csv file uses the tab character as a field separator." ) );
340 mDelimiterGroup->addButton( button, 2 );
341 delimiterLayout->addWidget( button, 1, 0 );
343 button =
new QRadioButton( i18nc(
"@option:radio Field separator",
"Space" ) );
345 i18nc(
"@info:tooltip",
"Set the field separator to a space character" ) );
346 button->setWhatsThis(
347 i18nc(
"@info:whatsthis",
348 "Select this option if your csv file uses the space character as a field separator." ) );
349 mDelimiterGroup->addButton( button, 3 );
350 delimiterLayout->addWidget( button, 1, 1 );
352 button =
new QRadioButton( i18nc(
"@option:radio Custum field separator",
"Other" ) );
354 i18nc(
"@info:tooltip",
"Set the field separator to a custom character" ) );
355 button->setWhatsThis(
356 i18nc(
"@info:whatsthis",
357 "Select this option if to use some other character as the field delimiter "
358 "for the data in your csv file." ) );
359 mDelimiterGroup->addButton( button, 4 );
360 delimiterLayout->addWidget( button, 0, 2 );
362 mDelimiterEdit =
new KLineEdit( group );
363 mDelimiterEdit->setToolTip(
364 i18nc(
"@info:tooltip",
365 "Set the custom delimiter character" ) );
366 mDelimiterEdit->setWhatsThis(
367 i18nc(
"@info:whatsthis",
368 "Enter a custom character to use as the delimiter character. "
369 "If you enter more than 1 character, only the first will be used and "
370 "the remaining characters will be ignored." ) );
371 delimiterLayout->addWidget( mDelimiterEdit, 1, 2 );
374 label =
new QLabel( i18nc(
"@label:listbox",
"Text quote:" ), page );
375 layout->addWidget( label, 1, 2 );
377 mComboQuote =
new KComboBox( page );
378 mComboQuote->setToolTip(
379 i18nc(
"@info:tooltip",
"Select the quote character" ) );
380 mComboQuote->setWhatsThis(
381 i18nc(
"@info:whatsthis",
382 "Choose the character that your csv data uses to \"quote\" the field delimiter "
383 "if that character happens to occur within the data. For example, if the "
384 "comma is the field delimiter, then any comma occurring with the data "
385 "will be \"quoted\" by the character specified here." ) );
386 mComboQuote->setEditable(
false );
387 mComboQuote->addItem( i18nc(
"@item:inlistbox Qoute character option",
"\"" ), 0 );
388 mComboQuote->addItem( i18nc(
"@item:inlistbox Quote character option",
"'" ), 1 );
389 mComboQuote->addItem( i18nc(
"@item:inlistbox Quote character option",
"None" ), 2 );
390 layout->addWidget( mComboQuote, 1, 3 );
393 label =
new QLabel( i18nc(
"@label:listbox",
"Date format:" ), page );
394 layout->addWidget( label, 2, 2 );
396 mDatePatternEdit =
new KLineEdit( page );
397 mDatePatternEdit->setText( QLatin1String(
"Y-M-D") );
398 mDatePatternEdit->setToolTip(
399 i18nc(
"@info:tooltip",
400 "<para><list><item>y: year with 2 digits</item>"
401 "<item>Y: year with 4 digits</item>"
402 "<item>m: month with 1 or 2 digits</item>"
403 "<item>M: month with 2 digits</item>"
404 "<item>d: day with 1 or 2 digits</item>"
405 "<item>D: day with 2 digits</item>"
406 "<item>H: hours with 2 digits</item>"
407 "<item>I: minutes with 2 digits</item>"
408 "<item>S: seconds with 2 digits</item>"
409 "</list></para>" ) );
410 mDatePatternEdit->setWhatsThis(
411 i18nc(
"@info:whatsthis",
412 "<para>Specify a format to use for dates included in your csv data. "
413 "Use the following sequences to help you define the format:</para>"
414 "<para><list><item>y: year with 2 digits</item>"
415 "<item>Y: year with 4 digits</item>"
416 "<item>m: month with 1 or 2 digits</item>"
417 "<item>M: month with 2 digits</item>"
418 "<item>d: day with 1 or 2 digits</item>"
419 "<item>D: day with 2 digits</item>"
420 "<item>H: hours with 2 digits</item>"
421 "<item>I: minutes with 2 digits</item>"
422 "<item>S: seconds with 2 digits</item>"
424 "<para>Example: \"Y-M-D\" corresponds to a date like \"2012-01-04\"</para>" ) );
425 layout->addWidget( mDatePatternEdit, 2, 3 );
428 label =
new QLabel( i18nc(
"@label:listbox",
"Text codec:" ), page );
429 layout->addWidget( label, 3, 2 );
431 mCodecCombo =
new KComboBox( page );
432 mCodecCombo->setToolTip(
433 i18nc(
"@info:tooltip",
"Select the text codec" ) );
434 mCodecCombo->setWhatsThis(
435 i18nc(
"@info:whatsthis",
436 "Choose the character encoding of the data in your csv file." ) );
437 layout->addWidget( mCodecCombo, 3, 3 );
440 mSkipFirstRow =
new QCheckBox( i18nc(
"@option:check",
"Skip first row of file" ), page );
441 mSkipFirstRow->setToolTip(
442 i18nc(
"@info:tooltip",
"Skip first row of csv file when importing" ) );
443 mSkipFirstRow->setWhatsThis(
444 i18nc(
"@info:whatsthis",
445 "Check this box if you want the import to skip over the first row "
446 "of the csv data. In many cases, the first line of a csv file will be a "
447 "comment line describing the order of the data fields included in the file." ) );
448 layout->addWidget( mSkipFirstRow, 4, 2, 1, 2 );
451 mTable =
new QTableView( page );
452 mTable->setModel( mModel );
453 mTable->setItemDelegateForRow( 0,
new ContactFieldDelegate(
this ) );
454 mTable->horizontalHeader()->hide();
455 mTable->verticalHeader()->hide();
456 mTable->setEditTriggers( QAbstractItemView::CurrentChanged );
457 mTable->setHorizontalScrollMode( QAbstractItemView::ScrollPerPixel );
458 layout->addWidget( mTable, 5, 0, 1, 5 );
460 setButtonText( User1, i18nc(
"@action:button",
"Apply Template..." ) );
461 setButtonText( User2, i18nc(
"@action:button",
"Save Template..." ) );
463 enableButton( Ok,
false );
464 enableButton( User1,
false );
465 enableButton( User2,
false );
470 void CSVImportDialog::reloadCodecs()
472 mCodecCombo->clear();
476 Q_FOREACH (
const QByteArray &name, QTextCodec::availableCodecs() ) {
477 mCodecs.append( QTextCodec::codecForName( name ) );
480 mCodecCombo->addItem( i18nc(
"@item:inlistbox Codec setting",
"Local (%1)",
481 QLatin1String( QTextCodec::codecForLocale()->name() ) ),
Local );
482 mCodecCombo->addItem( i18nc(
"@item:inlistbox Codec setting",
"Latin1" ),
Latin1 );
483 mCodecCombo->addItem( i18nc(
"@item:inlistbox Codec setting",
"Unicode" ),
Uni );
484 mCodecCombo->addItem( i18nc(
"@item:inlistbox Codec setting",
"Microsoft Unicode" ),
MSBug );
486 for (
int i = 0; i < mCodecs.count(); ++i ) {
487 mCodecCombo->addItem( QLatin1String(mCodecs.at( i )->name()),
Codec + i );
491 void CSVImportDialog::customDelimiterChanged()
493 if ( mDelimiterGroup->checkedId() == 4 ) {
494 delimiterClicked( 4 );
498 void CSVImportDialog::customDelimiterChanged(
const QString &,
bool reload )
500 mDelimiterGroup->button( 4 )->setChecked (
true );
501 delimiterClicked( 4, reload );
504 void CSVImportDialog::delimiterClicked(
int id,
bool reload )
511 mDelimiterEdit->setFocus( Qt::OtherFocusReason );
512 if ( !mDelimiterEdit->text().isEmpty() ) {
527 if ( mDevice && reload ) {
528 mModel->
load( mDevice );
532 void CSVImportDialog::textQuoteChanged(
const QString &mark,
bool reload )
534 if ( mComboQuote->currentIndex() == 2 ) {
540 if ( mDevice && reload ) {
541 mModel->
load( mDevice );
545 void CSVImportDialog::skipFirstRowChanged(
bool checked,
bool reload )
547 mFieldSelection.clear();
548 for (
int column = 0; column < mModel->
columnCount(); ++column ) {
549 mFieldSelection.append(
559 if ( mDevice && reload ) {
560 mModel->
load( mDevice );
566 if ( button == KDialog::Ok ) {
567 bool assigned =
false;
569 for (
int column = 0; column < mModel->
columnCount(); ++column ) {
570 if ( mModel->
data( mModel->index( 0, column ),
580 i18nc(
"@info:status",
"You must assign at least one column." ) );
584 }
else if ( button == User1 ) {
586 }
else if ( button == User2 ) {
588 }
else if ( button == KDialog::Cancel ) {
593 void CSVImportDialog::applyTemplate()
596 if ( !dlg->templatesAvailable() ) {
599 i18nc(
"@label",
"There are no templates available yet." ),
600 i18nc(
"@title:window",
"No templates available" ) );
605 if ( !dlg->exec() || !dlg ) {
610 const QString templateFileName = dlg->selectedTemplate();
613 KConfig config( templateFileName, KConfig::SimpleConfig );
615 const KConfigGroup generalGroup( &config,
"General" );
616 mDatePatternEdit->setText( generalGroup.readEntry(
"DatePattern",
"Y-M-D" ) );
617 mDelimiterEdit->setText( generalGroup.readEntry(
"DelimiterOther" ) );
619 const int delimiterButton = generalGroup.readEntry(
"DelimiterType", 0 );
620 const int quoteType = generalGroup.readEntry(
"QuoteType", 0 );
621 const bool skipFirstRow = generalGroup.readEntry(
"SkipFirstRow",
false );
623 mDelimiterGroup->button( delimiterButton )->setChecked(
true );
624 delimiterClicked( delimiterButton,
false );
626 mComboQuote->setCurrentIndex( quoteType );
627 textQuoteChanged( mComboQuote->currentText(), false );
631 mSkipFirstRow->blockSignals(
true );
632 mSkipFirstRow->setChecked( skipFirstRow );
633 mSkipFirstRow->blockSignals(
false );
635 skipFirstRowChanged( skipFirstRow,
false );
638 mModel->
load( mDevice );
641 setProperty(
"TemplateFileName", templateFileName );
642 connect( mModel, SIGNAL(finishedLoading()),
this, SLOT(finalizeApplyTemplate()) );
645 void CSVImportDialog::finalizeApplyTemplate()
647 const QString templateFileName = property(
"TemplateFileName" ).toString();
649 KConfig config( templateFileName, KConfig::SimpleConfig );
651 const KConfigGroup generalGroup( &config,
"General" );
652 const uint columns = generalGroup.readEntry(
"Columns", 0 );
655 const KConfigGroup columnMapGroup( &config,
"csv column map" );
657 for ( uint i = 0; i < columns; ++i ) {
658 const uint assignedField = columnMapGroup.readEntry( QString::number( i ), 0 );
659 mModel->
setData( mModel->index( 0, i ), assignedField, Qt::EditRole );
663 void CSVImportDialog::saveTemplate()
666 KInputDialog::getText( i18nc(
"@title:window",
"Template Name" ),
667 i18nc(
"@info",
"Please enter a name for the template:" ) );
669 if ( name.isEmpty() ) {
673 const QString fileName =
674 KStandardDirs::locateLocal(
"data", QLatin1String(
"kaddressbook/csv-templates/") +
675 QUuid::createUuid().toString() +
676 QLatin1String(
".desktop" ));
678 KConfig config( fileName );
679 KConfigGroup generalGroup( &config,
"General" );
680 generalGroup.writeEntry(
"DatePattern", mDatePatternEdit->text() );
681 generalGroup.writeEntry(
"Columns", mModel->
columnCount() );
682 generalGroup.writeEntry(
"DelimiterType", mDelimiterGroup->checkedId() );
683 generalGroup.writeEntry(
"DelimiterOther", mDelimiterEdit->text() );
684 generalGroup.writeEntry(
"SkipFirstRow", mSkipFirstRow->isChecked() );
685 generalGroup.writeEntry(
"QuoteType", mComboQuote->currentIndex() );
687 KConfigGroup miscGroup( &config,
"Misc" );
688 miscGroup.writeEntry(
"Name", name );
690 KConfigGroup columnMapGroup( &config,
"csv column map" );
691 for (
int column = 0; column < mModel->
columnCount(); ++column ) {
692 columnMapGroup.writeEntry( QString::number( column ),
693 mModel->
data( mModel->index( 0, column ),
694 Qt::DisplayRole ).toUInt() );
700 void CSVImportDialog::setFile(
const KUrl &fileName )
702 setFile( fileName.toLocalFile() );
705 void CSVImportDialog::setFile(
const QString &fileName )
707 if ( fileName.isEmpty() ) {
711 QFile *file =
new QFile( fileName );
712 if ( !file->open( QIODevice::ReadOnly ) ) {
713 KMessageBox::sorry(
this, i18nc(
"@info:status",
"Cannot open input file." ) );
722 mModel->
load( mDevice );
725 void CSVImportDialog::urlChanged(
const QString &file )
727 bool state = !file.isEmpty();
729 enableButton( Ok, state );
730 enableButton( User1, state );
731 enableButton( User2, state );
734 void CSVImportDialog::codecChanged(
bool reload )
736 const int code = mCodecCombo->currentIndex();
738 if ( code ==
Local ) {
740 }
else if ( code >=
Codec ) {
742 }
else if ( code ==
Uni ) {
743 mModel->
setTextCodec( QTextCodec::codecForName(
"UTF-16" ) );
744 }
else if ( code ==
MSBug ) {
745 mModel->
setTextCodec( QTextCodec::codecForName(
"UTF-16LE" ) );
746 }
else if ( code ==
Latin1 ) {
747 mModel->
setTextCodec( QTextCodec::codecForName(
"ISO 8859-1" ) );
749 mModel->
setTextCodec( QTextCodec::codecForName(
"UTF-8" ) );
752 if ( mDevice && reload ) {
753 mModel->
load( mDevice );
757 void CSVImportDialog::modelFinishedLoading()
759 ContactFieldComboBox *box =
new ContactFieldComboBox();
760 int preferredWidth = box->sizeHint().width();
763 for (
int i = 0; i < mModel->
columnCount(); ++i ) {
764 mTable->setColumnWidth( i, preferredWidth );
767 for (
int column = 0; column < mFieldSelection.count(); ++column ) {
768 mModel->
setData( mModel->index( 0, column ), mFieldSelection.at( column ), Qt::EditRole );
770 mFieldSelection.clear();
773 #include <csvimportdialog.moc>
void setTextCodec(QTextCodec *textCodec)
Sets the text codec that shall be used for parsing the csv list.
KABC::AddresseeList contacts() const
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const
Inherited from QAbstractTableModel.
void setStartRow(uint startRow)
Sets the row from where the parsing shall be started.
void setDelimiter(const QChar &delimiter)
Sets the character that is used as delimiter for fields.
virtual void slotButtonClicked(int)
virtual QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
Inherited from QAbstractTableModel.
This class parses the datetime out of a given string with the help of a pattern.
bool load(QIODevice *device)
Loads the data from the device into the model.
CSVImportDialog(QWidget *parent=0)
virtual int columnCount(const QModelIndex &parent=QModelIndex()) const
Inherited from QAbstractTableModel.
void setTextQuote(const QChar &textQuote)
Sets the character that is used for quoting.
virtual bool setData(const QModelIndex &index, const QVariant &data, int role=Qt::EditRole)
Inherited from QAbstractTableModel.