File indexing completed on 2025-01-19 04:46:30

0001 /*
0002   This file is part of KAddressBook.
0003   SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
0004 
0005   SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "csvimportdialog.h"
0009 
0010 #include "dateparser.h"
0011 #include "qcsvmodel.h"
0012 #include "templateselectiondialog.h"
0013 
0014 #include <KConfig>
0015 #include <KLineEdit>
0016 #include <KLocalizedString>
0017 #include <KMessageBox>
0018 #include <KUrlRequester>
0019 #include <QComboBox>
0020 #include <QInputDialog>
0021 #include <QLineEdit>
0022 #include <QProgressDialog>
0023 
0024 #include <KConfigGroup>
0025 #include <QApplication>
0026 #include <QButtonGroup>
0027 #include <QCheckBox>
0028 #include <QDialogButtonBox>
0029 #include <QGridLayout>
0030 #include <QGroupBox>
0031 #include <QHBoxLayout>
0032 #include <QHeaderView>
0033 #include <QLabel>
0034 #include <QPointer>
0035 #include <QPushButton>
0036 #include <QRadioButton>
0037 #include <QStandardPaths>
0038 #include <QStyledItemDelegate>
0039 #include <QTableView>
0040 #include <QTextCodec>
0041 #include <QThread>
0042 #include <QUuid>
0043 #include <QVBoxLayout>
0044 
0045 enum {
0046     Local = 0,
0047     Latin1 = 1,
0048     Uni = 2,
0049     MSBug = 3,
0050     Codec = 4,
0051 };
0052 
0053 class ContactFieldComboBox : public QComboBox
0054 {
0055     Q_OBJECT
0056 public:
0057     ContactFieldComboBox(QWidget *parent = nullptr)
0058         : QComboBox(parent)
0059     {
0060         fillFieldMap();
0061 
0062         addItem(KAddressBookImportExport::ContactFields::label(KAddressBookImportExport::ContactFields::Undefined),
0063                 KAddressBookImportExport::ContactFields::Undefined);
0064 
0065         QMapIterator<QString, KAddressBookImportExport::ContactFields::Field> it(mFieldMap);
0066         while (it.hasNext()) {
0067             it.next();
0068 
0069             addItem(it.key(), QVariant(it.value()));
0070         }
0071 
0072         int maxLength = 0;
0073         for (int i = 0; i < count(); ++i) {
0074             maxLength = qMax(maxLength, itemText(i).length());
0075         }
0076 
0077         setMinimumContentsLength(maxLength);
0078         setSizeAdjustPolicy(AdjustToContents);
0079         setFixedSize(sizeHint());
0080     }
0081 
0082     void setCurrentField(KAddressBookImportExport::ContactFields::Field field)
0083     {
0084         setCurrentIndex(findData((uint)field));
0085     }
0086 
0087     [[nodiscard]] KAddressBookImportExport::ContactFields::Field currentField() const
0088     {
0089         return (KAddressBookImportExport::ContactFields::Field)itemData(currentIndex()).toUInt();
0090     }
0091 
0092 private:
0093     static void fillFieldMap()
0094     {
0095         if (!mFieldMap.isEmpty()) {
0096             return;
0097         }
0098 
0099         KAddressBookImportExport::ContactFields::Fields fields = KAddressBookImportExport::ContactFields::allFields();
0100         fields.remove(KAddressBookImportExport::ContactFields::Undefined);
0101 
0102         for (int i = 0, total = fields.count(); i < total; ++i) {
0103             mFieldMap.insert(KAddressBookImportExport::ContactFields::label(fields.at(i)), fields.at(i));
0104         }
0105     }
0106 
0107     static QMap<QString, KAddressBookImportExport::ContactFields::Field> mFieldMap;
0108 };
0109 
0110 QMap<QString, KAddressBookImportExport::ContactFields::Field> ContactFieldComboBox::mFieldMap;
0111 
0112 class ContactFieldDelegate : public QStyledItemDelegate
0113 {
0114 public:
0115     ContactFieldDelegate(QObject *parent = nullptr)
0116         : QStyledItemDelegate(parent)
0117     {
0118     }
0119 
0120     [[nodiscard]] QString displayText(const QVariant &value, const QLocale &) const override
0121     {
0122         return KAddressBookImportExport::ContactFields::label((KAddressBookImportExport::ContactFields::Field)value.toUInt());
0123     }
0124 
0125     QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const override
0126     {
0127         auto editor = new ContactFieldComboBox(parent);
0128 
0129         return editor;
0130     }
0131 
0132     void setEditorData(QWidget *editor, const QModelIndex &index) const override
0133     {
0134         const unsigned int value = index.model()->data(index, Qt::EditRole).toUInt();
0135 
0136         auto fieldCombo = static_cast<ContactFieldComboBox *>(editor);
0137         fieldCombo->setCurrentField((KAddressBookImportExport::ContactFields::Field)value);
0138     }
0139 
0140     void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
0141     {
0142         auto fieldCombo = static_cast<ContactFieldComboBox *>(editor);
0143 
0144         model->setData(index, fieldCombo->currentField(), Qt::EditRole);
0145     }
0146 
0147     void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const override
0148     {
0149         editor->setGeometry(option.rect);
0150     }
0151 
0152     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
0153     {
0154         if (index.row() == 0) {
0155             QStyleOptionViewItem headerOption(option);
0156             headerOption.font.setBold(true);
0157 
0158             QStyledItemDelegate::paint(painter, headerOption, index);
0159         } else {
0160             QStyledItemDelegate::paint(painter, option, index);
0161         }
0162     }
0163 };
0164 
0165 CSVImportDialog::CSVImportDialog(QWidget *parent)
0166     : QDialog(parent)
0167 {
0168     setWindowTitle(i18nc("@title:window", "CSV Import Dialog"));
0169     setModal(true);
0170 
0171     mModel = new QCsvModel(this);
0172 
0173     initGUI();
0174 
0175     reloadCodecs();
0176 
0177     connect(mUrlRequester, &KUrlRequester::returnPressed, this, &CSVImportDialog::setFile);
0178     connect(mUrlRequester, &KUrlRequester::urlSelected, this, &CSVImportDialog::setUrl);
0179     connect(mUrlRequester->lineEdit(), &QLineEdit::textChanged, this, &CSVImportDialog::urlChanged);
0180     connect(mDelimiterGroup, &QButtonGroup::buttonClicked, this, [this](QAbstractButton *button) {
0181         if (button) {
0182             const int val = mDelimiterGroup->id(button);
0183             delimiterClicked(val);
0184         }
0185     });
0186     connect(mDelimiterEdit, &QLineEdit::returnPressed, this, [this]() {
0187         customDelimiterChanged();
0188     });
0189     connect(mDelimiterEdit, &QLineEdit::textChanged, this, [this](const QString &str) {
0190         customDelimiterChanged(str);
0191     });
0192     connect(mComboQuote, &QComboBox::textActivated, this, [this](const QString &str) {
0193         textQuoteChanged(str);
0194     });
0195     connect(mCodecCombo, &QComboBox::textActivated, this, [this]() {
0196         codecChanged();
0197     });
0198 
0199     connect(mSkipFirstRow, &QCheckBox::toggled, this, [this](bool b) {
0200         skipFirstRowChanged(b);
0201     });
0202 
0203     connect(mModel, &QCsvModel::finishedLoading, this, &CSVImportDialog::modelFinishedLoading);
0204 
0205     delimiterClicked(0);
0206     textQuoteChanged(QStringLiteral("\""));
0207     skipFirstRowChanged(false);
0208 }
0209 
0210 CSVImportDialog::~CSVImportDialog()
0211 {
0212     delete mDevice;
0213 }
0214 
0215 KContacts::AddresseeList CSVImportDialog::contacts() const
0216 {
0217     KContacts::AddresseeList contacts;
0218     DateParser dateParser(mDatePatternEdit->text());
0219 
0220     QProgressDialog progressDialog(const_cast<CSVImportDialog *>(this));
0221     progressDialog.setAutoClose(true);
0222     progressDialog.setMaximum(mModel->rowCount());
0223     progressDialog.setLabelText(i18nc("@label", "Importing contacts"));
0224     progressDialog.show();
0225 
0226     qApp->processEvents();
0227 
0228     for (int row = 1; row < mModel->rowCount(); ++row) {
0229         KContacts::Addressee contact;
0230         bool emptyRow = true;
0231 
0232         for (int column = 0; column < mModel->columnCount(); ++column) {
0233             QString value = mModel->data(mModel->index(row, column), Qt::DisplayRole).toString();
0234 
0235             if (!value.isEmpty()) {
0236                 emptyRow = false;
0237 
0238                 const KAddressBookImportExport::ContactFields::Field field =
0239                     (KAddressBookImportExport::ContactFields::Field)mModel->data(mModel->index(0, column)).toUInt();
0240 
0241                 // convert the custom date format to ISO format
0242                 if (field == KAddressBookImportExport::ContactFields::Birthday || field == KAddressBookImportExport::ContactFields::Anniversary) {
0243                     value = dateParser.parse(value).toString(Qt::ISODate);
0244                 }
0245 
0246                 value.replace(QLatin1StringView("\\n"), QStringLiteral("\n"));
0247 
0248                 KAddressBookImportExport::ContactFields::setValue(field, value, contact);
0249             }
0250         }
0251 
0252         qApp->processEvents();
0253 
0254         if (progressDialog.wasCanceled()) {
0255             return {};
0256         }
0257 
0258         progressDialog.setValue(progressDialog.value() + 1);
0259 
0260         if (!emptyRow && !contact.isEmpty()) {
0261             contacts.append(contact);
0262         }
0263     }
0264 
0265     return contacts;
0266 }
0267 
0268 void CSVImportDialog::initGUI()
0269 {
0270     auto page = new QWidget(this);
0271 
0272     auto mainLayout = new QVBoxLayout(this);
0273     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
0274     mainLayout->addWidget(page);
0275 
0276     mOkButton = buttonBox->button(QDialogButtonBox::Ok);
0277     mOkButton->setDefault(true);
0278     mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0279     mUser1Button = new QPushButton;
0280     buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole);
0281     connect(mUser1Button, &QAbstractButton::clicked, this, &CSVImportDialog::slotApplyTemplate);
0282     mUser2Button = new QPushButton;
0283     connect(mUser2Button, &QAbstractButton::clicked, this, &CSVImportDialog::slotSaveTemplate);
0284     buttonBox->addButton(mUser2Button, QDialogButtonBox::ActionRole);
0285     connect(buttonBox, &QDialogButtonBox::accepted, this, &CSVImportDialog::slotOk);
0286     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0287 
0288     auto layout = new QGridLayout;
0289     mainLayout->addLayout(layout);
0290     layout->setContentsMargins({});
0291 
0292     auto hbox = new QHBoxLayout;
0293 
0294     auto label = new QLabel(i18nc("@label", "File to import:"), page);
0295     mainLayout->addWidget(label);
0296     hbox->addWidget(label);
0297 
0298     mUrlRequester = new KUrlRequester(page);
0299     mainLayout->addWidget(mUrlRequester);
0300     mUrlRequester->setMimeTypeFilters({QStringLiteral("text/csv")});
0301     mUrlRequester->lineEdit()->setTrapReturnKey(true);
0302     mUrlRequester->setToolTip(i18nc("@info:tooltip", "Select a csv file to import"));
0303     mUrlRequester->setWhatsThis(i18nc("@info:whatsthis",
0304                                       "Click this button to start a file chooser that will allow you to "
0305                                       "select a csv file to import."));
0306     hbox->addWidget(mUrlRequester);
0307 
0308     layout->addLayout(hbox, 0, 0, 1, 5);
0309 
0310     // Delimiter: comma, semicolon, tab, space, other
0311     auto group = new QGroupBox(i18nc("@title:group", "Delimiter"), page);
0312     mainLayout->addWidget(group);
0313     auto delimiterLayout = new QGridLayout;
0314     group->setLayout(delimiterLayout);
0315     delimiterLayout->setAlignment(Qt::AlignTop);
0316     layout->addWidget(group, 1, 0, 4, 1);
0317 
0318     mDelimiterGroup = new QButtonGroup(this);
0319     mDelimiterGroup->setExclusive(true);
0320 
0321     auto button = new QRadioButton(i18nc("@option:radio Field separator", "Comma"));
0322     button->setToolTip(i18nc("@info:tooltip", "Set the field separator to a comma"));
0323     button->setWhatsThis(i18nc("@info:whatsthis", "Select this option if your csv file uses the comma as a field separator."));
0324     button->setChecked(true);
0325     mDelimiterGroup->addButton(button, 0);
0326     delimiterLayout->addWidget(button, 0, 0);
0327 
0328     button = new QRadioButton(i18nc("@option:radio Field separator", "Semicolon"));
0329     button->setToolTip(i18nc("@info:tooltip", "Set the field separator to a semicolon"));
0330     button->setWhatsThis(i18nc("@info:whatsthis", "Select this option if your csv file uses the semicolon as a field separator."));
0331     mDelimiterGroup->addButton(button, 1);
0332     delimiterLayout->addWidget(button, 0, 1);
0333 
0334     button = new QRadioButton(i18nc("@option:radio Field separator", "Tabulator"));
0335     button->setToolTip(i18nc("@info:tooltip", "Set the field separator to a tab character"));
0336     button->setWhatsThis(i18nc("@info:whatsthis", "Select this option if your csv file uses the tab character as a field separator."));
0337     mDelimiterGroup->addButton(button, 2);
0338     delimiterLayout->addWidget(button, 1, 0);
0339 
0340     button = new QRadioButton(i18nc("@option:radio Field separator", "Space"));
0341     button->setToolTip(i18nc("@info:tooltip", "Set the field separator to a space character"));
0342     button->setWhatsThis(i18nc("@info:whatsthis", "Select this option if your csv file uses the space character as a field separator."));
0343     mDelimiterGroup->addButton(button, 3);
0344     delimiterLayout->addWidget(button, 1, 1);
0345 
0346     button = new QRadioButton(i18nc("@option:radio Custom field separator", "Other"));
0347     button->setToolTip(i18nc("@info:tooltip", "Set the field separator to a custom character"));
0348     button->setWhatsThis(i18nc("@info:whatsthis",
0349                                "Select this option if to use some other character as the field delimiter "
0350                                "for the data in your csv file."));
0351     mDelimiterGroup->addButton(button, 4);
0352     delimiterLayout->addWidget(button, 0, 2);
0353 
0354     mDelimiterEdit = new QLineEdit(group);
0355     mDelimiterEdit->setToolTip(i18nc("@info:tooltip", "Set the custom delimiter character"));
0356     mDelimiterEdit->setWhatsThis(i18nc("@info:whatsthis",
0357                                        "Enter a custom character to use as the delimiter character. "
0358                                        "If you enter more than 1 character, only the first will be used and "
0359                                        "the remaining characters will be ignored."));
0360     delimiterLayout->addWidget(mDelimiterEdit, 1, 2);
0361 
0362     // text quote
0363     label = new QLabel(i18nc("@label:listbox", "Text quote:"), page);
0364     mainLayout->addWidget(label);
0365     layout->addWidget(label, 1, 2);
0366 
0367     mComboQuote = new QComboBox(page);
0368     mainLayout->addWidget(mComboQuote);
0369     mComboQuote->setToolTip(i18nc("@info:tooltip", "Select the quote character"));
0370     mComboQuote->setWhatsThis(i18nc("@info:whatsthis",
0371                                     "Choose the character that your csv data uses to \"quote\" the field delimiter "
0372                                     "if that character happens to occur within the data.  For example, if the "
0373                                     "comma is the field delimiter, then any comma occurring with the data "
0374                                     "will be \"quoted\" by the character specified here."));
0375     mComboQuote->setEditable(false);
0376     mComboQuote->addItem(i18nc("@item:inlistbox Quote character option", "\""), 0);
0377     mComboQuote->addItem(i18nc("@item:inlistbox Quote character option", "'"), 1);
0378     mComboQuote->addItem(i18nc("@item:inlistbox Quote character option", "None"), 2);
0379     layout->addWidget(mComboQuote, 1, 3);
0380 
0381     // date format
0382     label = new QLabel(i18nc("@label:listbox", "Date format:"), page);
0383     mainLayout->addWidget(label);
0384     layout->addWidget(label, 2, 2);
0385 
0386     mDatePatternEdit = new QLineEdit(page);
0387     mainLayout->addWidget(mDatePatternEdit);
0388     mDatePatternEdit->setText(QStringLiteral("Y-M-D")); // ISO 8601 date format as default
0389     mDatePatternEdit->setToolTip(xi18nc("@info:tooltip",
0390                                         "<para><list><item>y: year with 2 digits</item>"
0391                                         "<item>Y: year with 4 digits</item>"
0392                                         "<item>m: month with 1 or 2 digits</item>"
0393                                         "<item>M: month with 2 digits</item>"
0394                                         "<item>d: day with 1 or 2 digits</item>"
0395                                         "<item>D: day with 2 digits</item>"
0396                                         "<item>H: hours with 2 digits</item>"
0397                                         "<item>I: minutes with 2 digits</item>"
0398                                         "<item>S: seconds with 2 digits</item>"
0399                                         "</list></para>"));
0400     mDatePatternEdit->setWhatsThis(xi18nc("@info:whatsthis",
0401                                           "<para>Specify a format to use for dates included in your csv data. "
0402                                           "Use the following sequences to help you define the format:</para>"
0403                                           "<para><list><item>y: year with 2 digits</item>"
0404                                           "<item>Y: year with 4 digits</item>"
0405                                           "<item>m: month with 1 or 2 digits</item>"
0406                                           "<item>M: month with 2 digits</item>"
0407                                           "<item>d: day with 1 or 2 digits</item>"
0408                                           "<item>D: day with 2 digits</item>"
0409                                           "<item>H: hours with 2 digits</item>"
0410                                           "<item>I: minutes with 2 digits</item>"
0411                                           "<item>S: seconds with 2 digits</item>"
0412                                           "</list></para>"
0413                                           "<para>Example: \"Y-M-D\" corresponds to a date like \"2012-01-04\"</para>"));
0414     layout->addWidget(mDatePatternEdit, 2, 3);
0415 
0416     // text codec
0417     label = new QLabel(i18nc("@label:listbox", "Text codec:"), page);
0418     mainLayout->addWidget(label);
0419     layout->addWidget(label, 3, 2);
0420 
0421     mCodecCombo = new QComboBox(page);
0422     mainLayout->addWidget(mCodecCombo);
0423     mCodecCombo->setToolTip(i18nc("@info:tooltip", "Select the text codec"));
0424     mCodecCombo->setWhatsThis(i18nc("@info:whatsthis", "Choose the character encoding of the data in your csv file."));
0425     layout->addWidget(mCodecCombo, 3, 3);
0426 
0427     // skip first line
0428     mSkipFirstRow = new QCheckBox(i18nc("@option:check", "Skip first row of file"), page);
0429     mainLayout->addWidget(mSkipFirstRow);
0430     mSkipFirstRow->setToolTip(i18nc("@info:tooltip", "Skip first row of csv file when importing"));
0431     mSkipFirstRow->setWhatsThis(i18nc("@info:whatsthis",
0432                                       "Check this box if you want the import to skip over the first row "
0433                                       "of the csv data. In many cases, the first line of a csv file will be a "
0434                                       "comment line describing the order of the data fields included in the file."));
0435     layout->addWidget(mSkipFirstRow, 4, 2, 1, 2);
0436 
0437     // csv view
0438     mTable = new QTableView(page);
0439     mainLayout->addWidget(mTable);
0440     mTable->setModel(mModel);
0441     mTable->setItemDelegateForRow(0, new ContactFieldDelegate(this));
0442     mTable->horizontalHeader()->hide();
0443     mTable->verticalHeader()->hide();
0444     mTable->setEditTriggers(QAbstractItemView::CurrentChanged);
0445     mTable->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
0446     layout->addWidget(mTable, 5, 0, 1, 5);
0447 
0448     mUser1Button->setText(i18nc("@action:button", "Apply Template..."));
0449     mUser2Button->setText(i18nc("@action:button", "Save Template..."));
0450 
0451     mOkButton->setEnabled(false);
0452     mUser1Button->setEnabled(false);
0453     mUser2Button->setEnabled(false);
0454     mainLayout->addWidget(buttonBox);
0455 
0456     resize(500, 400);
0457 }
0458 
0459 void CSVImportDialog::reloadCodecs()
0460 {
0461     mCodecCombo->clear();
0462 
0463     mCodecs.clear();
0464 
0465     const QList<QByteArray> lstCodec = QTextCodec::availableCodecs();
0466     for (const QByteArray &name : lstCodec) {
0467         mCodecs.append(QTextCodec::codecForName(name));
0468     }
0469 
0470     mCodecCombo->addItem(i18nc("@item:inlistbox Codec setting", "Local (%1)", QLatin1StringView(QTextCodec::codecForLocale()->name())), Local);
0471     mCodecCombo->addItem(i18nc("@item:inlistbox Codec setting", "Latin1"), Latin1);
0472     mCodecCombo->addItem(i18nc("@item:inlistbox Codec setting", "Unicode"), Uni);
0473     mCodecCombo->addItem(i18nc("@item:inlistbox Codec setting", "Microsoft Unicode"), MSBug);
0474 
0475     for (int i = 0, total = mCodecs.count(); i < total; ++i) {
0476         mCodecCombo->addItem(QLatin1StringView(mCodecs.at(i)->name()), Codec + i);
0477     }
0478 }
0479 
0480 void CSVImportDialog::customDelimiterChanged()
0481 {
0482     if (mDelimiterGroup->checkedId() == 4) {
0483         delimiterClicked(4);
0484     }
0485 }
0486 
0487 void CSVImportDialog::customDelimiterChanged(const QString &, bool reload)
0488 {
0489     mDelimiterGroup->button(4)->setChecked(true);
0490     delimiterClicked(4, reload); // other
0491 }
0492 
0493 void CSVImportDialog::delimiterClicked(int id, bool reload)
0494 {
0495     switch (id) {
0496     case 0: // comma
0497         mModel->setDelimiter(QLatin1Char(','));
0498         break;
0499     case 4: // other
0500         mDelimiterEdit->setFocus(Qt::OtherFocusReason);
0501         if (!mDelimiterEdit->text().isEmpty()) {
0502             mModel->setDelimiter(mDelimiterEdit->text().at(0));
0503         }
0504         break;
0505     case 2: // tab
0506         mModel->setDelimiter(QLatin1Char('\t'));
0507         break;
0508     case 3: // space
0509         mModel->setDelimiter(QLatin1Char(' '));
0510         break;
0511     case 1: // semicolon
0512         mModel->setDelimiter(QLatin1Char(';'));
0513         break;
0514     }
0515 
0516     if (mDevice && reload) {
0517         mModel->load(mDevice);
0518     }
0519 }
0520 
0521 void CSVImportDialog::textQuoteChanged(const QString &mark, bool reload)
0522 {
0523     if (mComboQuote->currentIndex() == 2) {
0524         mModel->setTextQuote(QChar());
0525     } else {
0526         mModel->setTextQuote(mark.at(0));
0527     }
0528 
0529     if (mDevice && reload) {
0530         mModel->load(mDevice);
0531     }
0532 }
0533 
0534 void CSVImportDialog::skipFirstRowChanged(bool checked, bool reload)
0535 {
0536     mFieldSelection.clear();
0537     for (int column = 0; column < mModel->columnCount(); ++column) {
0538         mFieldSelection.append((KAddressBookImportExport::ContactFields::Field)mModel->data(mModel->index(0, column)).toInt());
0539     }
0540 
0541     if (checked) {
0542         mModel->setStartRow(1);
0543     } else {
0544         mModel->setStartRow(0);
0545     }
0546 
0547     if (mDevice && reload) {
0548         mModel->load(mDevice);
0549     }
0550 }
0551 
0552 void CSVImportDialog::slotApplyTemplate()
0553 {
0554     applyTemplate();
0555 }
0556 
0557 void CSVImportDialog::slotSaveTemplate()
0558 {
0559     saveTemplate();
0560 }
0561 
0562 void CSVImportDialog::slotOk()
0563 {
0564     bool assigned = false;
0565 
0566     for (int column = 0; column < mModel->columnCount(); ++column) {
0567         if (mModel->data(mModel->index(0, column), Qt::DisplayRole).toUInt() != KAddressBookImportExport::ContactFields::Undefined) {
0568             assigned = true;
0569             break;
0570         }
0571     }
0572 
0573     if (!assigned) {
0574         KMessageBox::error(this, i18nc("@info:status", "You must assign at least one column."));
0575     } else {
0576         accept();
0577     }
0578 }
0579 
0580 void CSVImportDialog::applyTemplate()
0581 {
0582     QPointer<TemplateSelectionDialog> dlg = new TemplateSelectionDialog(this);
0583     if (!dlg->templatesAvailable()) {
0584         KMessageBox::error(this, i18nc("@label", "There are no templates available yet."), i18nc("@title:window", "No templates available"));
0585         delete dlg;
0586         return;
0587     }
0588 
0589     if (!dlg->exec()) {
0590         delete dlg;
0591         return;
0592     }
0593 
0594     const QString templateFileName = dlg->selectedTemplate();
0595     delete dlg;
0596 
0597     KConfig config(templateFileName, KConfig::SimpleConfig);
0598 
0599     const KConfigGroup generalGroup(&config, "General");
0600     mDatePatternEdit->setText(generalGroup.readEntry("DatePattern", "Y-M-D"));
0601     mDelimiterEdit->setText(generalGroup.readEntry("DelimiterOther"));
0602 
0603     const int delimiterButton = generalGroup.readEntry("DelimiterType", 0);
0604     const int quoteType = generalGroup.readEntry("QuoteType", 0);
0605     const bool skipFirstRow = generalGroup.readEntry("SkipFirstRow", false);
0606 
0607     mDelimiterGroup->button(delimiterButton)->setChecked(true);
0608     delimiterClicked(delimiterButton, false);
0609 
0610     mComboQuote->setCurrentIndex(quoteType);
0611     textQuoteChanged(mComboQuote->currentText(), false);
0612 
0613     // do block signals here, otherwise it will trigger a reload of the model and
0614     // the following skipFirstRowChanged call end up with an empty model
0615     mSkipFirstRow->blockSignals(true);
0616     mSkipFirstRow->setChecked(skipFirstRow);
0617     mSkipFirstRow->blockSignals(false);
0618 
0619     skipFirstRowChanged(skipFirstRow, false);
0620 
0621     if (mDevice) {
0622         mModel->load(mDevice);
0623     }
0624 
0625     setProperty("TemplateFileName", templateFileName);
0626     connect(mModel, &QCsvModel::finishedLoading, this, &CSVImportDialog::finalizeApplyTemplate);
0627 }
0628 
0629 void CSVImportDialog::finalizeApplyTemplate()
0630 {
0631     const QString templateFileName = property("TemplateFileName").toString();
0632 
0633     KConfig config(templateFileName, KConfig::SimpleConfig);
0634 
0635     const KConfigGroup generalGroup(&config, "General");
0636     const uint columns = generalGroup.readEntry("Columns", 0);
0637 
0638     // create the column map
0639     const KConfigGroup columnMapGroup(&config, "csv column map");
0640 
0641     for (uint i = 0; i < columns; ++i) {
0642         const uint assignedField = columnMapGroup.readEntry(QString::number(i), 0);
0643         mModel->setData(mModel->index(0, i), assignedField, Qt::EditRole);
0644     }
0645 }
0646 
0647 void CSVImportDialog::saveTemplate()
0648 {
0649     const QString name = QInputDialog::getText(this, i18nc("@title:window", "Template Name"), i18nc("@info", "Please enter a name for the template:"));
0650 
0651     if (name.isEmpty()) {
0652         return;
0653     }
0654 
0655     const int numberOfColumn(mModel->columnCount());
0656     if (numberOfColumn == 0) {
0657         return;
0658     }
0659 
0660     const QString fileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kaddressbook/csv-templates/")
0661         + QUuid::createUuid().toString() + QStringLiteral(".desktop");
0662 
0663     QFileInfo fileInfo(fileName);
0664     QDir().mkpath(fileInfo.absolutePath());
0665 
0666     KConfig config(fileName);
0667     KConfigGroup generalGroup(&config, QStringLiteral("General"));
0668     generalGroup.writeEntry("DatePattern", mDatePatternEdit->text());
0669     generalGroup.writeEntry("Columns", mModel->columnCount());
0670     generalGroup.writeEntry("DelimiterType", mDelimiterGroup->checkedId());
0671     generalGroup.writeEntry("DelimiterOther", mDelimiterEdit->text());
0672     generalGroup.writeEntry("SkipFirstRow", mSkipFirstRow->isChecked());
0673     generalGroup.writeEntry("QuoteType", mComboQuote->currentIndex());
0674 
0675     KConfigGroup miscGroup(&config, QStringLiteral("Misc"));
0676     miscGroup.writeEntry("Name", name);
0677 
0678     KConfigGroup columnMapGroup(&config, QStringLiteral("csv column map"));
0679     for (int column = 0; column < numberOfColumn; ++column) {
0680         columnMapGroup.writeEntry(QString::number(column), mModel->data(mModel->index(0, column), Qt::DisplayRole).toUInt());
0681     }
0682 
0683     config.sync();
0684 }
0685 
0686 void CSVImportDialog::setUrl(const QUrl &fileName)
0687 {
0688     setFile(fileName.toLocalFile());
0689 }
0690 
0691 void CSVImportDialog::setFile(const QString &fileName)
0692 {
0693     if (fileName.isEmpty()) {
0694         return;
0695     }
0696 
0697     auto file = new QFile(fileName);
0698     if (!file->open(QIODevice::ReadOnly)) {
0699         KMessageBox::error(this, i18nc("@info:status", "Cannot open input file."));
0700         delete file;
0701         return;
0702     }
0703 
0704     delete mDevice;
0705 
0706     mDevice = file;
0707 
0708     mModel->load(mDevice);
0709 }
0710 
0711 void CSVImportDialog::urlChanged(const QString &file)
0712 {
0713     bool state = !file.isEmpty();
0714 
0715     mOkButton->setEnabled(state);
0716     mUser1Button->setEnabled(state);
0717     mUser2Button->setEnabled(state);
0718 }
0719 
0720 void CSVImportDialog::codecChanged(bool reload)
0721 {
0722     const int code = mCodecCombo->currentIndex();
0723 
0724     if (code == Local) {
0725         mModel->setTextCodec(QTextCodec::codecForLocale());
0726     } else if (code >= Codec) {
0727         mModel->setTextCodec(mCodecs.at(code - Codec));
0728     } else if (code == Uni) {
0729         mModel->setTextCodec(QTextCodec::codecForName("UTF-16"));
0730     } else if (code == MSBug) {
0731         mModel->setTextCodec(QTextCodec::codecForName("UTF-16LE"));
0732     } else if (code == Latin1) {
0733         mModel->setTextCodec(QTextCodec::codecForName("ISO 8859-1"));
0734     } else {
0735         mModel->setTextCodec(QTextCodec::codecForName("UTF-8"));
0736     }
0737 
0738     if (mDevice && reload) {
0739         mModel->load(mDevice);
0740     }
0741 }
0742 
0743 void CSVImportDialog::modelFinishedLoading()
0744 {
0745     auto box = new ContactFieldComboBox();
0746     int preferredWidth = box->sizeHint().width();
0747     delete box;
0748 
0749     for (int i = 0; i < mModel->columnCount(); ++i) {
0750         mTable->setColumnWidth(i, preferredWidth);
0751     }
0752 
0753     for (int column = 0; column < mFieldSelection.count(); ++column) {
0754         mModel->setData(mModel->index(0, column), mFieldSelection.at(column), Qt::EditRole);
0755     }
0756     mFieldSelection.clear();
0757 }
0758 
0759 #include "csvimportdialog.moc"
0760 
0761 #include "moc_csvimportdialog.cpp"