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"