summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMontel Laurent <montel@kde.org>2016-10-11 05:31:09 (GMT)
committerMontel Laurent <montel@kde.org>2016-10-11 05:31:09 (GMT)
commit42ba70ef6b69bf4152f1174c163186223951429a (patch)
tree3db2c801b9e9ce9265920952bc6d3b5c95db5501
parentd978e647edb38a699e73de85f5a1cbd8a3f76a01 (diff)
Add import dialog for csv
-rw-r--r--kaddressbook/importexportplugins/csv/CMakeLists.txt5
-rw-r--r--kaddressbook/importexportplugins/csv/import/csvimportdialog.cpp798
-rw-r--r--kaddressbook/importexportplugins/csv/import/csvimportdialog.h99
-rw-r--r--kaddressbook/importexportplugins/csv/import/dateparser.cpp131
-rw-r--r--kaddressbook/importexportplugins/csv/import/dateparser.h53
-rw-r--r--kaddressbook/importexportplugins/csv/import/qcsvmodel.cpp337
-rw-r--r--kaddressbook/importexportplugins/csv/import/qcsvmodel.h136
-rw-r--r--kaddressbook/importexportplugins/csv/import/qcsvmodel_p.h66
-rw-r--r--kaddressbook/importexportplugins/csv/import/qcsvreader.cpp407
-rw-r--r--kaddressbook/importexportplugins/csv/import/qcsvreader.h237
-rw-r--r--kaddressbook/importexportplugins/csv/import/templateselectiondialog.cpp253
-rw-r--r--kaddressbook/importexportplugins/csv/import/templateselectiondialog.h46
12 files changed, 2568 insertions, 0 deletions
diff --git a/kaddressbook/importexportplugins/csv/CMakeLists.txt b/kaddressbook/importexportplugins/csv/CMakeLists.txt
index f191ff0..e1f11e7 100644
--- a/kaddressbook/importexportplugins/csv/CMakeLists.txt
+++ b/kaddressbook/importexportplugins/csv/CMakeLists.txt
@@ -1,6 +1,11 @@
set(kaddressbook_importexport_csv_SRCS
csvimportexportplugin.cpp
csvimportexportplugininterface.cpp
+ #import/csvimportdialog.cpp
+ import/dateparser.cpp
+ import/qcsvmodel.cpp
+ import/qcsvreader.cpp
+ import/templateselectiondialog.cpp
)
diff --git a/kaddressbook/importexportplugins/csv/import/csvimportdialog.cpp b/kaddressbook/importexportplugins/csv/import/csvimportdialog.cpp
new file mode 100644
index 0000000..3ad90dd
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/csvimportdialog.cpp
@@ -0,0 +1,798 @@
+/*
+ This file is part of KAddressBook.
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include "csvimportdialog.h"
+
+#include "dateparser.h"
+#include "qcsvmodel.h"
+#include "templateselectiondialog.h"
+
+#include <KConfig>
+#include <KComboBox>
+#include <QInputDialog>
+#include <QLineEdit>
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <QProgressDialog>
+#include <KUrlRequester>
+#include <KLineEdit>
+
+#include <QApplication>
+#include <QPointer>
+#include <QTextCodec>
+#include <QThread>
+#include <QUuid>
+#include <QButtonGroup>
+#include <QCheckBox>
+#include <QGridLayout>
+#include <QGroupBox>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QPushButton>
+#include <QRadioButton>
+#include <QStyledItemDelegate>
+#include <QTableView>
+#include <QHeaderView>
+#include <QStandardPaths>
+#include <KConfigGroup>
+#include <QDialogButtonBox>
+#include <QVBoxLayout>
+
+enum {
+ Local = 0,
+ Latin1 = 1,
+ Uni = 2,
+ MSBug = 3,
+ Codec = 4
+};
+
+class ContactFieldComboBox : public KComboBox
+{
+public:
+
+ ContactFieldComboBox(QWidget *parent = Q_NULLPTR)
+ : KComboBox(parent)
+ {
+ fillFieldMap();
+
+ addItem(ContactFields::label(ContactFields::Undefined), ContactFields::Undefined);
+
+ QMapIterator<QString, ContactFields::Field> it(mFieldMap);
+ while (it.hasNext()) {
+ it.next();
+
+ addItem(it.key(), QVariant(it.value()));
+ }
+
+ int maxLength = 0;
+ for (int i = 0; i < count(); ++i) {
+ maxLength = qMax(maxLength, itemText(i).length());
+ }
+
+ setMinimumContentsLength(maxLength);
+ setSizeAdjustPolicy(AdjustToMinimumContentsLength);
+ setFixedSize(sizeHint());
+ }
+
+ void setCurrentField(ContactFields::Field field)
+ {
+ setCurrentIndex(findData((uint)field));
+ }
+
+ ContactFields::Field currentField() const
+ {
+ return (ContactFields::Field)itemData(currentIndex()).toUInt();
+ }
+
+private:
+ static void fillFieldMap()
+ {
+ if (!mFieldMap.isEmpty()) {
+ return;
+ }
+
+ ContactFields::Fields fields = ContactFields::allFields();
+ fields.remove(ContactFields::Undefined);
+
+ for (int i = 0; i < fields.count(); ++i) {
+ mFieldMap.insert(ContactFields::label(fields.at(i)), fields.at(i));
+ }
+ }
+
+ static QMap<QString, ContactFields::Field> mFieldMap;
+};
+
+QMap<QString, ContactFields::Field> ContactFieldComboBox::mFieldMap;
+
+class ContactFieldDelegate : public QStyledItemDelegate
+{
+public:
+ ContactFieldDelegate(QObject *parent = Q_NULLPTR)
+ : QStyledItemDelegate(parent)
+ {
+ }
+
+ QString displayText(const QVariant &value, const QLocale &) const Q_DECL_OVERRIDE
+ {
+ return ContactFields::label((ContactFields::Field)value.toUInt());
+ }
+
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
+ const QModelIndex &) const Q_DECL_OVERRIDE
+ {
+ ContactFieldComboBox *editor = new ContactFieldComboBox(parent);
+
+ return editor;
+ }
+
+ void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE
+ {
+ const unsigned int value = index.model()->data(index, Qt::EditRole).toUInt();
+
+ ContactFieldComboBox *fieldCombo = static_cast<ContactFieldComboBox *>(editor);
+ fieldCombo->setCurrentField((ContactFields::Field)value);
+ }
+
+ void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const Q_DECL_OVERRIDE
+ {
+ ContactFieldComboBox *fieldCombo = static_cast<ContactFieldComboBox *>(editor);
+
+ model->setData(index, fieldCombo->currentField(), Qt::EditRole);
+ }
+
+ void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
+ const QModelIndex &) const Q_DECL_OVERRIDE
+ {
+ editor->setGeometry(option.rect);
+ }
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const Q_DECL_OVERRIDE
+ {
+ if (index.row() == 0) {
+ QStyleOptionViewItem headerOption(option);
+ headerOption.font.setBold(true);
+
+ QStyledItemDelegate::paint(painter, headerOption, index);
+ } else {
+ QStyledItemDelegate::paint(painter, option, index);
+ }
+ }
+};
+
+CSVImportDialog::CSVImportDialog(QWidget *parent)
+ : QDialog(parent), mDevice(Q_NULLPTR)
+{
+ setWindowTitle(i18nc("@title:window", "CSV Import Dialog"));
+ setModal(true);
+
+ mModel = new QCsvModel(this);
+
+ initGUI();
+
+ reloadCodecs();
+
+ connect(mUrlRequester, SIGNAL(returnPressed(QString)), this, SLOT(setFile(QString)));
+ connect(mUrlRequester, &KUrlRequester::urlSelected, this, &CSVImportDialog::setUrl);
+ connect(mUrlRequester->lineEdit(), &QLineEdit::textChanged, this, &CSVImportDialog::urlChanged);
+ connect(mDelimiterGroup, SIGNAL(buttonClicked(int)), this, SLOT(delimiterClicked(int)));
+ connect(mDelimiterEdit, SIGNAL(returnPressed()), this, SLOT(customDelimiterChanged()));
+ connect(mDelimiterEdit, SIGNAL(textChanged(QString)), this, SLOT(customDelimiterChanged(QString)));
+ connect(mComboQuote, SIGNAL(activated(QString)), this, SLOT(textQuoteChanged(QString)));
+ connect(mCodecCombo, SIGNAL(activated(QString)), this, SLOT(codecChanged()));
+ connect(mSkipFirstRow, SIGNAL(toggled(bool)), this, SLOT(skipFirstRowChanged(bool)));
+
+ connect(mModel, &QCsvModel::finishedLoading, this, &CSVImportDialog::modelFinishedLoading);
+
+ delimiterClicked(0);
+ textQuoteChanged(QStringLiteral("\""));
+ skipFirstRowChanged(false);
+}
+
+CSVImportDialog::~CSVImportDialog()
+{
+ delete mDevice;
+}
+
+KContacts::AddresseeList CSVImportDialog::contacts() const
+{
+ KContacts::AddresseeList contacts;
+ DateParser dateParser(mDatePatternEdit->text());
+
+ QProgressDialog progressDialog(const_cast<CSVImportDialog *>(this));
+ progressDialog.setAutoClose(true);
+ progressDialog.setMaximum(mModel->rowCount());
+ progressDialog.setLabelText(i18nc("@label", "Importing contacts"));
+ progressDialog.show();
+
+ qApp->processEvents();
+
+ for (int row = 1; row < mModel->rowCount(); ++row) {
+ KContacts::Addressee contact;
+ bool emptyRow = true;
+
+ for (int column = 0; column < mModel->columnCount(); ++column) {
+ QString value = mModel->data(mModel->index(row, column), Qt::DisplayRole).toString();
+
+ if (!value.isEmpty()) {
+ emptyRow = false;
+
+ const ContactFields::Field field =
+ (ContactFields::Field)mModel->data(mModel->index(0, column)).toUInt();
+
+ // convert the custom date format to ISO format
+ if (field == ContactFields::Birthday || field == ContactFields::Anniversary) {
+ value = dateParser.parse(value).toString(Qt::ISODate);
+ }
+
+ value.replace(QLatin1String("\\n"), QStringLiteral("\n"));
+
+ ContactFields::setValue(field, value, contact);
+ }
+ }
+
+ qApp->processEvents();
+
+ if (progressDialog.wasCanceled()) {
+ return KContacts::AddresseeList();
+ }
+
+ progressDialog.setValue(progressDialog.value() + 1);
+
+ if (!emptyRow && !contact.isEmpty()) {
+ contacts.append(contact);
+ }
+ }
+
+ return contacts;
+}
+
+void CSVImportDialog::initGUI()
+{
+
+ QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ QWidget *page = new QWidget(this);
+
+ QVBoxLayout *mainLayout = new QVBoxLayout(this);
+ mainLayout->addWidget(page);
+
+ mOkButton = buttonBox->button(QDialogButtonBox::Ok);
+ mOkButton->setDefault(true);
+ mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return);
+ mUser1Button = new QPushButton;
+ buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole);
+ connect(mUser1Button, &QAbstractButton::clicked, this, &CSVImportDialog::slotApplyTemplate);
+ mUser2Button = new QPushButton;
+ connect(mUser2Button, &QAbstractButton::clicked, this, &CSVImportDialog::slotSaveTemplate);
+ buttonBox->addButton(mUser2Button, QDialogButtonBox::ActionRole);
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &CSVImportDialog::slotOk);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ QGridLayout *layout = new QGridLayout;
+ mainLayout->addLayout(layout);
+ layout->setMargin(0);
+
+ QHBoxLayout *hbox = new QHBoxLayout;
+
+ QLabel *label = new QLabel(i18nc("@label", "File to import:"), page);
+ mainLayout->addWidget(label);
+ hbox->addWidget(label);
+
+ mUrlRequester = new KUrlRequester(page);
+ mainLayout->addWidget(mUrlRequester);
+ mUrlRequester->setFilter(QStringLiteral("*.csv"));
+ mUrlRequester->lineEdit()->setTrapReturnKey(true);
+ mUrlRequester->setToolTip(
+ i18nc("@info:tooltip", "Select a csv file to import"));
+ mUrlRequester->setWhatsThis(
+ i18nc("@info:whatsthis",
+ "Click this button to start a file chooser that will allow you to "
+ "select a csv file to import."));
+ hbox->addWidget(mUrlRequester);
+
+ layout->addLayout(hbox, 0, 0, 1, 5);
+
+ // Delimiter: comma, semicolon, tab, space, other
+ QGroupBox *group = new QGroupBox(i18nc("@title:group", "Delimiter"), page);
+ mainLayout->addWidget(group);
+ QGridLayout *delimiterLayout = new QGridLayout;
+ group->setLayout(delimiterLayout);
+ delimiterLayout->setAlignment(Qt::AlignTop);
+ layout->addWidget(group, 1, 0, 4, 1);
+
+ mDelimiterGroup = new QButtonGroup(this);
+ mDelimiterGroup->setExclusive(true);
+
+ QRadioButton *button = new QRadioButton(i18nc("@option:radio Field separator", "Comma"));
+ button->setToolTip(
+ i18nc("@info:tooltip", "Set the field separator to a comma"));
+ button->setWhatsThis(
+ i18nc("@info:whatsthis",
+ "Select this option if your csv file uses the comma as a field separator."));
+ button->setChecked(true);
+ mDelimiterGroup->addButton(button, 0);
+ delimiterLayout->addWidget(button, 0, 0);
+
+ button = new QRadioButton(i18nc("@option:radio Field separator", "Semicolon"));
+ button->setToolTip(
+ i18nc("@info:tooltip", "Set the field separator to a semicolon"));
+ button->setWhatsThis(
+ i18nc("@info:whatsthis",
+ "Select this option if your csv file uses the semicolon as a field separator."));
+ mDelimiterGroup->addButton(button, 1);
+ delimiterLayout->addWidget(button, 0, 1);
+
+ button = new QRadioButton(i18nc("@option:radio Field separator", "Tabulator"));
+ button->setToolTip(
+ i18nc("@info:tooltip", "Set the field separator to a tab character"));
+ button->setWhatsThis(
+ i18nc("@info:whatsthis",
+ "Select this option if your csv file uses the tab character as a field separator."));
+ mDelimiterGroup->addButton(button, 2);
+ delimiterLayout->addWidget(button, 1, 0);
+
+ button = new QRadioButton(i18nc("@option:radio Field separator", "Space"));
+ button->setToolTip(
+ i18nc("@info:tooltip", "Set the field separator to a space character"));
+ button->setWhatsThis(
+ i18nc("@info:whatsthis",
+ "Select this option if your csv file uses the space character as a field separator."));
+ mDelimiterGroup->addButton(button, 3);
+ delimiterLayout->addWidget(button, 1, 1);
+
+ button = new QRadioButton(i18nc("@option:radio Custum field separator", "Other"));
+ button->setToolTip(
+ i18nc("@info:tooltip", "Set the field separator to a custom character"));
+ button->setWhatsThis(
+ i18nc("@info:whatsthis",
+ "Select this option if to use some other character as the field delimiter "
+ "for the data in your csv file."));
+ mDelimiterGroup->addButton(button, 4);
+ delimiterLayout->addWidget(button, 0, 2);
+
+ mDelimiterEdit = new QLineEdit(group);
+ mDelimiterEdit->setToolTip(
+ i18nc("@info:tooltip",
+ "Set the custom delimiter character"));
+ mDelimiterEdit->setWhatsThis(
+ i18nc("@info:whatsthis",
+ "Enter a custom character to use as the delimiter character. "
+ "If you enter more than 1 character, only the first will be used and "
+ "the remaining characters will be ignored."));
+ delimiterLayout->addWidget(mDelimiterEdit, 1, 2);
+
+ // text quote
+ label = new QLabel(i18nc("@label:listbox", "Text quote:"), page);
+ mainLayout->addWidget(label);
+ layout->addWidget(label, 1, 2);
+
+ mComboQuote = new KComboBox(page);
+ mainLayout->addWidget(mComboQuote);
+ mComboQuote->setToolTip(
+ i18nc("@info:tooltip", "Select the quote character"));
+ mComboQuote->setWhatsThis(
+ i18nc("@info:whatsthis",
+ "Choose the character that your csv data uses to \"quote\" the field delimiter "
+ "if that character happens to occur within the data. For example, if the "
+ "comma is the field delimiter, then any comma occurring with the data "
+ "will be \"quoted\" by the character specified here."));
+ mComboQuote->setEditable(false);
+ mComboQuote->addItem(i18nc("@item:inlistbox Qoute character option", "\""), 0);
+ mComboQuote->addItem(i18nc("@item:inlistbox Quote character option", "'"), 1);
+ mComboQuote->addItem(i18nc("@item:inlistbox Quote character option", "None"), 2);
+ layout->addWidget(mComboQuote, 1, 3);
+
+ // date format
+ label = new QLabel(i18nc("@label:listbox", "Date format:"), page);
+ mainLayout->addWidget(label);
+ layout->addWidget(label, 2, 2);
+
+ mDatePatternEdit = new QLineEdit(page);
+ mainLayout->addWidget(mDatePatternEdit);
+ mDatePatternEdit->setText(QStringLiteral("Y-M-D")); // ISO 8601 date format as default
+ mDatePatternEdit->setToolTip(
+ xi18nc("@info:tooltip",
+ "<para><list><item>y: year with 2 digits</item>"
+ "<item>Y: year with 4 digits</item>"
+ "<item>m: month with 1 or 2 digits</item>"
+ "<item>M: month with 2 digits</item>"
+ "<item>d: day with 1 or 2 digits</item>"
+ "<item>D: day with 2 digits</item>"
+ "<item>H: hours with 2 digits</item>"
+ "<item>I: minutes with 2 digits</item>"
+ "<item>S: seconds with 2 digits</item>"
+ "</list></para>"));
+ mDatePatternEdit->setWhatsThis(
+ xi18nc("@info:whatsthis",
+ "<para>Specify a format to use for dates included in your csv data. "
+ "Use the following sequences to help you define the format:</para>"
+ "<para><list><item>y: year with 2 digits</item>"
+ "<item>Y: year with 4 digits</item>"
+ "<item>m: month with 1 or 2 digits</item>"
+ "<item>M: month with 2 digits</item>"
+ "<item>d: day with 1 or 2 digits</item>"
+ "<item>D: day with 2 digits</item>"
+ "<item>H: hours with 2 digits</item>"
+ "<item>I: minutes with 2 digits</item>"
+ "<item>S: seconds with 2 digits</item>"
+ "</list></para>"
+ "<para>Example: \"Y-M-D\" corresponds to a date like \"2012-01-04\"</para>"));
+ layout->addWidget(mDatePatternEdit, 2, 3);
+
+ // text codec
+ label = new QLabel(i18nc("@label:listbox", "Text codec:"), page);
+ mainLayout->addWidget(label);
+ layout->addWidget(label, 3, 2);
+
+ mCodecCombo = new KComboBox(page);
+ mainLayout->addWidget(mCodecCombo);
+ mCodecCombo->setToolTip(
+ i18nc("@info:tooltip", "Select the text codec"));
+ mCodecCombo->setWhatsThis(
+ i18nc("@info:whatsthis",
+ "Choose the character encoding of the data in your csv file."));
+ layout->addWidget(mCodecCombo, 3, 3);
+
+ // skip first line
+ mSkipFirstRow = new QCheckBox(i18nc("@option:check", "Skip first row of file"), page);
+ mainLayout->addWidget(mSkipFirstRow);
+ mSkipFirstRow->setToolTip(
+ i18nc("@info:tooltip", "Skip first row of csv file when importing"));
+ mSkipFirstRow->setWhatsThis(
+ i18nc("@info:whatsthis",
+ "Check this box if you want the import to skip over the first row "
+ "of the csv data. In many cases, the first line of a csv file will be a "
+ "comment line describing the order of the data fields included in the file."));
+ layout->addWidget(mSkipFirstRow, 4, 2, 1, 2);
+
+ // csv view
+ mTable = new QTableView(page);
+ mainLayout->addWidget(mTable);
+ mTable->setModel(mModel);
+ mTable->setItemDelegateForRow(0, new ContactFieldDelegate(this));
+ mTable->horizontalHeader()->hide();
+ mTable->verticalHeader()->hide();
+ mTable->setEditTriggers(QAbstractItemView::CurrentChanged);
+ mTable->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
+ layout->addWidget(mTable, 5, 0, 1, 5);
+
+ mUser1Button->setText(i18nc("@action:button", "Apply Template..."));
+ mUser2Button->setText(i18nc("@action:button", "Save Template..."));
+
+ mOkButton->setEnabled(false);
+ mUser1Button->setEnabled(false);
+ mUser2Button->setEnabled(false);
+ mainLayout->addWidget(buttonBox);
+
+ resize(500, 400);
+}
+
+void CSVImportDialog::reloadCodecs()
+{
+ mCodecCombo->clear();
+
+ mCodecs.clear();
+
+ Q_FOREACH (const QByteArray &name, QTextCodec::availableCodecs()) {
+ mCodecs.append(QTextCodec::codecForName(name));
+ }
+
+ mCodecCombo->addItem(i18nc("@item:inlistbox Codec setting", "Local (%1)",
+ QLatin1String(QTextCodec::codecForLocale()->name())), Local);
+ mCodecCombo->addItem(i18nc("@item:inlistbox Codec setting", "Latin1"), Latin1);
+ mCodecCombo->addItem(i18nc("@item:inlistbox Codec setting", "Unicode"), Uni);
+ mCodecCombo->addItem(i18nc("@item:inlistbox Codec setting", "Microsoft Unicode"), MSBug);
+
+ for (int i = 0; i < mCodecs.count(); ++i) {
+ mCodecCombo->addItem(QLatin1String(mCodecs.at(i)->name()), Codec + i);
+ }
+}
+
+void CSVImportDialog::customDelimiterChanged()
+{
+ if (mDelimiterGroup->checkedId() == 4) {
+ delimiterClicked(4);
+ }
+}
+
+void CSVImportDialog::customDelimiterChanged(const QString &, bool reload)
+{
+ mDelimiterGroup->button(4)->setChecked(true);
+ delimiterClicked(4, reload); // other
+}
+
+void CSVImportDialog::delimiterClicked(int id, bool reload)
+{
+ switch (id) {
+ case 0: // comma
+ mModel->setDelimiter(QLatin1Char(','));
+ break;
+ case 4: // other
+ mDelimiterEdit->setFocus(Qt::OtherFocusReason);
+ if (!mDelimiterEdit->text().isEmpty()) {
+ mModel->setDelimiter(mDelimiterEdit->text().at(0));
+ }
+ break;
+ case 2: // tab
+ mModel->setDelimiter(QLatin1Char('\t'));
+ break;
+ case 3: // space
+ mModel->setDelimiter(QLatin1Char(' '));
+ break;
+ case 1: // semicolon
+ mModel->setDelimiter(QLatin1Char(';'));
+ break;
+ }
+
+ if (mDevice && reload) {
+ mModel->load(mDevice);
+ }
+}
+
+void CSVImportDialog::textQuoteChanged(const QString &mark, bool reload)
+{
+ if (mComboQuote->currentIndex() == 2) {
+ mModel->setTextQuote(QChar());
+ } else {
+ mModel->setTextQuote(mark.at(0));
+ }
+
+ if (mDevice && reload) {
+ mModel->load(mDevice);
+ }
+}
+
+void CSVImportDialog::skipFirstRowChanged(bool checked, bool reload)
+{
+ mFieldSelection.clear();
+ for (int column = 0; column < mModel->columnCount(); ++column) {
+ mFieldSelection.append(
+ (ContactFields::Field)mModel->data(mModel->index(0, column)).toInt());
+ }
+
+ if (checked) {
+ mModel->setStartRow(1);
+ } else {
+ mModel->setStartRow(0);
+ }
+
+ if (mDevice && reload) {
+ mModel->load(mDevice);
+ }
+}
+
+void CSVImportDialog::slotApplyTemplate()
+{
+ applyTemplate();
+}
+
+void CSVImportDialog::slotSaveTemplate()
+{
+ saveTemplate();
+}
+
+void CSVImportDialog::slotOk()
+{
+ bool assigned = false;
+
+ for (int column = 0; column < mModel->columnCount(); ++column) {
+ if (mModel->data(mModel->index(0, column),
+ Qt::DisplayRole).toUInt() != ContactFields::Undefined) {
+ assigned = true;
+ break;
+ }
+ }
+
+ if (!assigned) {
+ KMessageBox::sorry(
+ this,
+ i18nc("@info:status", "You must assign at least one column."));
+ } else {
+ accept();
+ }
+}
+
+void CSVImportDialog::applyTemplate()
+{
+ QPointer<TemplateSelectionDialog> dlg = new TemplateSelectionDialog(this);
+ if (!dlg->templatesAvailable()) {
+ KMessageBox::sorry(
+ this,
+ i18nc("@label", "There are no templates available yet."),
+ i18nc("@title:window", "No templates available"));
+ delete dlg;
+ return;
+ }
+
+ if (!dlg->exec() || !dlg) {
+ delete dlg;
+ return;
+ }
+
+ const QString templateFileName = dlg->selectedTemplate();
+ delete dlg;
+
+ KConfig config(templateFileName, KConfig::SimpleConfig);
+
+ const KConfigGroup generalGroup(&config, "General");
+ mDatePatternEdit->setText(generalGroup.readEntry("DatePattern", "Y-M-D"));
+ mDelimiterEdit->setText(generalGroup.readEntry("DelimiterOther"));
+
+ const int delimiterButton = generalGroup.readEntry("DelimiterType", 0);
+ const int quoteType = generalGroup.readEntry("QuoteType", 0);
+ const bool skipFirstRow = generalGroup.readEntry("SkipFirstRow", false);
+
+ mDelimiterGroup->button(delimiterButton)->setChecked(true);
+ delimiterClicked(delimiterButton, false);
+
+ mComboQuote->setCurrentIndex(quoteType);
+ textQuoteChanged(mComboQuote->currentText(), false);
+
+ // do block signals here, otherwise it will trigger a reload of the model and
+ // the following skipFirstRowChanged call end up with an empty model
+ mSkipFirstRow->blockSignals(true);
+ mSkipFirstRow->setChecked(skipFirstRow);
+ mSkipFirstRow->blockSignals(false);
+
+ skipFirstRowChanged(skipFirstRow, false);
+
+ if (mDevice) {
+ mModel->load(mDevice);
+ }
+
+ setProperty("TemplateFileName", templateFileName);
+ connect(mModel, &QCsvModel::finishedLoading, this, &CSVImportDialog::finalizeApplyTemplate);
+}
+
+void CSVImportDialog::finalizeApplyTemplate()
+{
+ const QString templateFileName = property("TemplateFileName").toString();
+
+ KConfig config(templateFileName, KConfig::SimpleConfig);
+
+ const KConfigGroup generalGroup(&config, "General");
+ const uint columns = generalGroup.readEntry("Columns", 0);
+
+ // create the column map
+ const KConfigGroup columnMapGroup(&config, "csv column map");
+
+ for (uint i = 0; i < columns; ++i) {
+ const uint assignedField = columnMapGroup.readEntry(QString::number(i), 0);
+ mModel->setData(mModel->index(0, i), assignedField, Qt::EditRole);
+ }
+}
+
+void CSVImportDialog::saveTemplate()
+{
+ const QString name =
+ QInputDialog::getText(this, i18nc("@title:window", "Template Name"),
+ i18nc("@info", "Please enter a name for the template:"));
+
+ if (name.isEmpty()) {
+ return;
+ }
+
+ const int numberOfColumn(mModel->columnCount());
+ if (numberOfColumn == 0) {
+ return;
+ }
+
+ const QString fileName =
+ QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kaddressbook/csv-templates/") +
+ QUuid::createUuid().toString() +
+ QLatin1String(".desktop");
+
+ QFileInfo fileInfo(fileName);
+ QDir().mkpath(fileInfo.absolutePath());
+
+ KConfig config(fileName);
+ KConfigGroup generalGroup(&config, "General");
+ generalGroup.writeEntry("DatePattern", mDatePatternEdit->text());
+ generalGroup.writeEntry("Columns", mModel->columnCount());
+ generalGroup.writeEntry("DelimiterType", mDelimiterGroup->checkedId());
+ generalGroup.writeEntry("DelimiterOther", mDelimiterEdit->text());
+ generalGroup.writeEntry("SkipFirstRow", mSkipFirstRow->isChecked());
+ generalGroup.writeEntry("QuoteType", mComboQuote->currentIndex());
+
+ KConfigGroup miscGroup(&config, "Misc");
+ miscGroup.writeEntry("Name", name);
+
+ KConfigGroup columnMapGroup(&config, "csv column map");
+ for (int column = 0; column < numberOfColumn; ++column) {
+ columnMapGroup.writeEntry(QString::number(column),
+ mModel->data(mModel->index(0, column),
+ Qt::DisplayRole).toUInt());
+ }
+
+ config.sync();
+}
+
+void CSVImportDialog::setUrl(const QUrl &fileName)
+{
+ setFile(fileName.toLocalFile());
+}
+
+void CSVImportDialog::setFile(const QString &fileName)
+{
+ if (fileName.isEmpty()) {
+ return;
+ }
+
+ QFile *file = new QFile(fileName);
+ if (!file->open(QIODevice::ReadOnly)) {
+ KMessageBox::sorry(this, i18nc("@info:status", "Cannot open input file."));
+ delete file;
+ return;
+ }
+
+ delete mDevice;
+
+ mDevice = file;
+
+ mModel->load(mDevice);
+}
+
+void CSVImportDialog::urlChanged(const QString &file)
+{
+ bool state = !file.isEmpty();
+
+ mOkButton->setEnabled(state);
+ mUser1Button->setEnabled(state);
+ mUser2Button->setEnabled(state);
+}
+
+void CSVImportDialog::codecChanged(bool reload)
+{
+ const int code = mCodecCombo->currentIndex();
+
+ if (code == Local) {
+ mModel->setTextCodec(QTextCodec::codecForLocale());
+ } else if (code >= Codec) {
+ mModel->setTextCodec(mCodecs.at(code - Codec));
+ } else if (code == Uni) {
+ mModel->setTextCodec(QTextCodec::codecForName("UTF-16"));
+ } else if (code == MSBug) {
+ mModel->setTextCodec(QTextCodec::codecForName("UTF-16LE"));
+ } else if (code == Latin1) {
+ mModel->setTextCodec(QTextCodec::codecForName("ISO 8859-1"));
+ } else {
+ mModel->setTextCodec(QTextCodec::codecForName("UTF-8"));
+ }
+
+ if (mDevice && reload) {
+ mModel->load(mDevice);
+ }
+}
+
+void CSVImportDialog::modelFinishedLoading()
+{
+ ContactFieldComboBox *box = new ContactFieldComboBox();
+ int preferredWidth = box->sizeHint().width();
+ delete box;
+
+ for (int i = 0; i < mModel->columnCount(); ++i) {
+ mTable->setColumnWidth(i, preferredWidth);
+ }
+
+ for (int column = 0; column < mFieldSelection.count(); ++column) {
+ mModel->setData(mModel->index(0, column), mFieldSelection.at(column), Qt::EditRole);
+ }
+ mFieldSelection.clear();
+}
+
diff --git a/kaddressbook/importexportplugins/csv/import/csvimportdialog.h b/kaddressbook/importexportplugins/csv/import/csvimportdialog.h
new file mode 100644
index 0000000..b1e1cf5
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/csvimportdialog.h
@@ -0,0 +1,99 @@
+/*
+ This file is part of KAddressBook.
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#ifndef CSVIMPORTDIALOG_H
+#define CSVIMPORTDIALOG_H
+
+#include "contactfields.h"
+
+#include <KContacts/Addressee>
+
+#include <QDialog>
+
+#include <QList>
+
+class KComboBox;
+class QLineEdit;
+class KUrlRequester;
+
+class QButtonGroup;
+class QCheckBox;
+class QCsvModel;
+class QTableView;
+
+class CSVImportDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit CSVImportDialog(QWidget *parent = Q_NULLPTR);
+ ~CSVImportDialog();
+
+ KContacts::AddresseeList contacts() const;
+
+private Q_SLOTS:
+ void setFile(const QString &);
+ void setUrl(const QUrl &);
+ void urlChanged(const QString &);
+
+ void customDelimiterChanged();
+ void customDelimiterChanged(const QString &, bool reload = true);
+ void delimiterClicked(int, bool reload = true);
+ void textQuoteChanged(const QString &, bool reload = true);
+ void skipFirstRowChanged(bool, bool reload = true);
+ void codecChanged(bool reload = true);
+
+ void modelFinishedLoading();
+ void finalizeApplyTemplate();
+
+ void slotSaveTemplate();
+ void slotApplyTemplate();
+ void slotOk();
+private:
+ void applyTemplate();
+ void saveTemplate();
+
+ QTableView *mTable;
+ QButtonGroup *mDelimiterGroup;
+ QLineEdit *mDelimiterEdit;
+ QLineEdit *mDatePatternEdit;
+ KComboBox *mComboQuote;
+ KComboBox *mCodecCombo;
+ QCheckBox *mSkipFirstRow;
+ KUrlRequester *mUrlRequester;
+ QCsvModel *mModel;
+
+ void initGUI();
+
+ void reloadCodecs();
+ QTextCodec *currentCodec();
+ QList<QTextCodec *> mCodecs;
+
+ QChar mTextQuote;
+ QString mDelimiter;
+ QMap<QString, uint> mTypeMap;
+ QIODevice *mDevice;
+ ContactFields::Fields mFieldSelection;
+ QPushButton *mUser1Button;
+ QPushButton *mUser2Button;
+ QPushButton *mOkButton;
+
+};
+
+#endif
diff --git a/kaddressbook/importexportplugins/csv/import/dateparser.cpp b/kaddressbook/importexportplugins/csv/import/dateparser.cpp
new file mode 100644
index 0000000..33052b8
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/dateparser.cpp
@@ -0,0 +1,131 @@
+/*
+ This file is part of KAddressBook.
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include "dateparser.h"
+
+DateParser::DateParser(const QString &pattern)
+ : mPattern(pattern)
+{
+}
+
+DateParser::~DateParser()
+{
+}
+
+QDateTime DateParser::parse(const QString &dateStr) const
+{
+ int year, month, day, hour, minute, second;
+ year = month = day = hour = minute = second = 0;
+
+ int currPos = 0;
+ for (int i = 0; i < mPattern.length(); ++i) {
+ if (mPattern[ i ] == QLatin1Char('y')) { // 19YY
+ if (currPos + 1 < dateStr.length()) {
+ year = 1900 + dateStr.midRef(currPos, 2).toInt();
+ currPos += 2;
+ } else {
+ return QDateTime();
+ }
+ } else if (mPattern[ i ] == QLatin1Char('Y')) { // YYYY
+ if (currPos + 3 < dateStr.length()) {
+ year = dateStr.midRef(currPos, 4).toInt();
+ currPos += 4;
+ } else {
+ return QDateTime();
+ }
+ } else if (mPattern[ i ] == QLatin1Char('m')) { // M or MM
+ if (currPos + 1 < dateStr.length()) {
+ if (dateStr[ currPos ].isDigit()) {
+ if (dateStr[ currPos + 1 ].isDigit()) {
+ month = dateStr.midRef(currPos, 2).toInt();
+ currPos += 2;
+ continue;
+ }
+ }
+ }
+ if (currPos < dateStr.length()) {
+ if (dateStr[ currPos ].isDigit()) {
+ month = dateStr.midRef(currPos, 1).toInt();
+ currPos++;
+ continue;
+ }
+ }
+
+ return QDateTime();
+ } else if (mPattern[ i ] == QLatin1Char('M')) { // 0M or MM
+ if (currPos + 1 < dateStr.length()) {
+ month = dateStr.midRef(currPos, 2).toInt();
+ currPos += 2;
+ } else {
+ return QDateTime();
+ }
+ } else if (mPattern[ i ] == QLatin1Char('d')) { // D or DD
+ if (currPos + 1 < dateStr.length()) {
+ if (dateStr[ currPos ].isDigit()) {
+ if (dateStr[ currPos + 1 ].isDigit()) {
+ day = dateStr.midRef(currPos, 2).toInt();
+ currPos += 2;
+ continue;
+ }
+ }
+ }
+ if (currPos < dateStr.length()) {
+ if (dateStr[ currPos ].isDigit()) {
+ day = dateStr.midRef(currPos, 1).toInt();
+ currPos++;
+ continue;
+ }
+ }
+
+ return QDateTime();
+ } else if (mPattern[ i ] == QLatin1Char('D')) { // 0D or DD
+ if (currPos + 1 < dateStr.length()) {
+ day = dateStr.midRef(currPos, 2).toInt();
+ currPos += 2;
+ } else {
+ return QDateTime();
+ }
+ } else if (mPattern[ i ] == QLatin1Char('H')) { // 0H or HH
+ if (currPos + 1 < dateStr.length()) {
+ hour = dateStr.midRef(currPos, 2).toInt();
+ currPos += 2;
+ } else {
+ return QDateTime();
+ }
+ } else if (mPattern[ i ] == QLatin1Char('I')) { // 0I or II
+ if (currPos + 1 < dateStr.length()) {
+ minute = dateStr.midRef(currPos, 2).toInt();
+ currPos += 2;
+ } else {
+ return QDateTime();
+ }
+ } else if (mPattern[ i ] == QLatin1Char('S')) { // 0S or SS
+ if (currPos + 1 < dateStr.length()) {
+ second = dateStr.midRef(currPos, 2).toInt();
+ currPos += 2;
+ } else {
+ return QDateTime();
+ }
+ } else {
+ currPos++;
+ }
+ }
+
+ return QDateTime(QDate(year, month, day), QTime(hour, minute, second));
+}
diff --git a/kaddressbook/importexportplugins/csv/import/dateparser.h b/kaddressbook/importexportplugins/csv/import/dateparser.h
new file mode 100644
index 0000000..b688a5c
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/dateparser.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of KAddressBook.
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#ifndef DATEPARSER_H
+#define DATEPARSER_H
+
+#include <QDateTime>
+#include <QString>
+
+/**
+ This class parses the datetime out of a given string with the
+ help of a pattern.
+
+ The pattern can contain the following place holders:
+ y = year (e.g. 82)
+ Y = year (e.g. 1982)
+ m = month (e.g. 7, 07 or 12)
+ M = month (e.g. 07 or 12)
+ d = day (e.g. 3, 03 or 17)
+ D = day (e.g. 03 or 17)
+ H = hour (e.g. 12)
+ I = minute (e.g. 56)
+ S = second (e.g. 30)
+ */
+class DateParser
+{
+public:
+ explicit DateParser(const QString &pattern);
+ ~DateParser();
+
+ QDateTime parse(const QString &dateStr) const;
+
+private:
+ QString mPattern;
+};
+
+#endif
diff --git a/kaddressbook/importexportplugins/csv/import/qcsvmodel.cpp b/kaddressbook/importexportplugins/csv/import/qcsvmodel.cpp
new file mode 100644
index 0000000..f09593e
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/qcsvmodel.cpp
@@ -0,0 +1,337 @@
+/*
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "qcsvmodel.h"
+#include "qcsvmodel_p.h"
+#include "qcsvreader.h"
+
+#include <QMap>
+#include <QPair>
+#include <QStringList>
+#include <QVector>
+
+CsvParser::CsvParser(QObject *parent)
+ : QThread(parent), mDevice(Q_NULLPTR), mRowCount(0), mColumnCount(0), mCacheCounter(0)
+{
+ mReader = new QCsvReader(this);
+}
+
+CsvParser::~CsvParser()
+{
+ delete mReader;
+}
+
+void CsvParser::load(QIODevice *device)
+{
+ mDevice = device;
+
+ start();
+}
+
+void CsvParser::begin()
+{
+ mCacheCounter = 0;
+ mRowCount = 0;
+ mColumnCount = 0;
+}
+
+void CsvParser::beginLine()
+{
+ mRowCount++;
+}
+
+void CsvParser::field(const QString &data, uint row, uint column)
+{
+ const int tmp = qMax(mColumnCount, (int)column + 1);
+ if (tmp != mColumnCount) {
+ mColumnCount = tmp;
+ Q_EMIT columnCountChanged(tmp);
+ }
+
+ Q_EMIT dataChanged(data, row, column);
+}
+
+void CsvParser::endLine()
+{
+ mCacheCounter++;
+ if (mCacheCounter == 50) {
+ Q_EMIT rowCountChanged(mRowCount);
+ mCacheCounter = 0;
+ }
+}
+
+void CsvParser::end()
+{
+ Q_EMIT rowCountChanged(mRowCount);
+ Q_EMIT ended();
+}
+
+void CsvParser::error(const QString &)
+{
+}
+
+void CsvParser::run()
+{
+ if (!mDevice->isOpen()) {
+ mDevice->open(QIODevice::ReadOnly);
+ }
+
+ mDevice->reset();
+ mReader->read(mDevice);
+}
+
+class QCsvModel::Private
+{
+public:
+ Private(QCsvModel *model)
+ : mParent(model), mParser(Q_NULLPTR),
+ mDevice(Q_NULLPTR), mRowCount(0), mColumnCount(0)
+ {
+ }
+
+ void columnCountChanged(int columns);
+ void rowCountChanged(int rows);
+ void fieldChanged(const QString &data, int row, int column);
+ void finishedLoading();
+
+ QCsvModel *mParent;
+ CsvParser *mParser;
+ QVector<QString> mFieldIdentifiers;
+ QMap< QPair<int, int>, QString> mFields;
+ QIODevice *mDevice;
+
+ int mRowCount;
+ int mColumnCount;
+};
+
+void QCsvModel::Private::columnCountChanged(int columns)
+{
+ mColumnCount = columns;
+ mFieldIdentifiers.resize(columns);
+ mFieldIdentifiers[ columns - 1 ] = QStringLiteral("0");
+ Q_EMIT mParent->layoutChanged();
+}
+
+void QCsvModel::Private::rowCountChanged(int rows)
+{
+ mRowCount = rows;
+ Q_EMIT mParent->layoutChanged();
+}
+
+void QCsvModel::Private::fieldChanged(const QString &data, int row, int column)
+{
+ mFields.insert(QPair<int, int>(row, column), data);
+}
+
+void QCsvModel::Private::finishedLoading()
+{
+ Q_EMIT mParent->finishedLoading();
+}
+
+QCsvModel::QCsvModel(QObject *parent)
+ : QAbstractTableModel(parent), d(new Private(this))
+{
+ d->mParser = new CsvParser(this);
+
+ connect(d->mParser, SIGNAL(columnCountChanged(int)),
+ this, SLOT(columnCountChanged(int)), Qt::QueuedConnection);
+ connect(d->mParser, SIGNAL(rowCountChanged(int)),
+ this, SLOT(rowCountChanged(int)), Qt::QueuedConnection);
+ connect(d->mParser, SIGNAL(dataChanged(QString,int,int)),
+ this, SLOT(fieldChanged(QString,int,int)), Qt::QueuedConnection);
+ connect(d->mParser, &CsvParser::ended, this, &QCsvModel::finishedLoading);
+}
+
+QCsvModel::~QCsvModel()
+{
+ delete d;
+}
+
+bool QCsvModel::load(QIODevice *device)
+{
+ d->mDevice = device;
+ d->mRowCount = 0;
+ d->mColumnCount = 0;
+
+ Q_EMIT layoutChanged();
+
+ d->mParser->load(device);
+
+ return true;
+}
+
+void QCsvModel::setTextQuote(const QChar &textQuote)
+{
+ const bool isRunning = d->mParser->isRunning();
+
+ if (isRunning) {
+ d->mParser->reader()->terminate();
+ d->mParser->wait();
+ }
+
+ d->mParser->reader()->setTextQuote(textQuote);
+
+ if (isRunning) {
+ load(d->mDevice);
+ }
+}
+
+QChar QCsvModel::textQuote() const
+{
+ return d->mParser->reader()->textQuote();
+}
+
+void QCsvModel::setDelimiter(const QChar &delimiter)
+{
+ const bool isRunning = d->mParser->isRunning();
+
+ if (isRunning) {
+ d->mParser->reader()->terminate();
+ d->mParser->wait();
+ }
+
+ d->mParser->reader()->setDelimiter(delimiter);
+
+ if (isRunning) {
+ load(d->mDevice);
+ }
+}
+
+QChar QCsvModel::delimiter() const
+{
+ return d->mParser->reader()->delimiter();
+}
+
+void QCsvModel::setStartRow(uint startRow)
+{
+ const bool isRunning = d->mParser->isRunning();
+
+ if (isRunning) {
+ d->mParser->reader()->terminate();
+ d->mParser->wait();
+ }
+
+ d->mParser->reader()->setStartRow(startRow);
+
+ if (isRunning) {
+ load(d->mDevice);
+ }
+}
+
+uint QCsvModel::startRow() const
+{
+ return d->mParser->reader()->startRow();
+}
+
+void QCsvModel::setTextCodec(QTextCodec *textCodec)
+{
+ const bool isRunning = d->mParser->isRunning();
+
+ if (isRunning) {
+ d->mParser->reader()->terminate();
+ d->mParser->wait();
+ }
+
+ d->mParser->reader()->setTextCodec(textCodec);
+
+ if (isRunning) {
+ load(d->mDevice);
+ }
+}
+
+QTextCodec *QCsvModel::textCodec() const
+{
+ return d->mParser->reader()->textCodec();
+}
+
+int QCsvModel::columnCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid()) {
+ return d->mColumnCount;
+ } else {
+ return 0;
+ }
+}
+
+int QCsvModel::rowCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid()) {
+ return d->mRowCount + 1; // +1 for the header row
+ } else {
+ return 0;
+ }
+}
+
+QVariant QCsvModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid()) {
+ return QVariant();
+ }
+
+ if (index.row() == 0) {
+ if (index.column() >= d->mFieldIdentifiers.count()) {
+ return QVariant();
+ }
+
+ if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ return d->mFieldIdentifiers.at(index.column());
+ }
+
+ return QVariant();
+ }
+
+ const QPair<int, int> pair(index.row() - 1, index.column());
+ if (!d->mFields.contains(pair)) {
+ return QVariant();
+ }
+
+ const QString data = d->mFields.value(pair);
+
+ if (role == Qt::DisplayRole) {
+ return data;
+ } else {
+ return QVariant();
+ }
+}
+
+bool QCsvModel::setData(const QModelIndex &index, const QVariant &data, int role)
+{
+ if (role == Qt::EditRole && index.row() == 0 &&
+ index.column() <= d->mFieldIdentifiers.count()) {
+ d->mFieldIdentifiers[ index.column() ] = data.toString();
+
+ Q_EMIT dataChanged(index, index);
+ return true;
+ }
+
+ return false;
+}
+
+Qt::ItemFlags QCsvModel::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+ if (index.row() == 0) {
+ flags |= Qt::ItemIsEditable;
+ }
+
+ return flags;
+}
+
+#include "moc_qcsvmodel.cpp"
+#include "moc_qcsvmodel_p.cpp"
diff --git a/kaddressbook/importexportplugins/csv/import/qcsvmodel.h b/kaddressbook/importexportplugins/csv/import/qcsvmodel.h
new file mode 100644
index 0000000..a4a3b31
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/qcsvmodel.h
@@ -0,0 +1,136 @@
+/*
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef QCSVMODEL_H
+#define QCSVMODEL_H
+
+#include <QAbstractTableModel>
+#include <QIODevice>
+
+class QCsvModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Creates a new csv model.
+ */
+ explicit QCsvModel(QObject *parent);
+
+ /**
+ * Destroys the csv model.
+ */
+ ~QCsvModel();
+
+ /**
+ * Loads the data from the @p device into the model.
+ */
+ bool load(QIODevice *device);
+
+ /**
+ * Sets the character that is used for quoting. The default is '"'.
+ */
+ void setTextQuote(const QChar &textQuote);
+
+ /**
+ * Returns the character that is used for quoting.
+ */
+ QChar textQuote() const;
+
+ /**
+ * Sets the character that is used as delimiter for fields.
+ * The default is ' '.
+ */
+ void setDelimiter(const QChar &delimiter);
+
+ /**
+ * Returns the delimiter that is used as delimiter for fields.
+ */
+ QChar delimiter() const;
+
+ /**
+ * Sets the row from where the parsing shall be started.
+ *
+ * Some csv files have some kind of header in the first line with
+ * the column titles. To retrieve only the real data, set the start row
+ * to '1' in this case.
+ *
+ * The default start row is 0.
+ */
+ void setStartRow(uint startRow);
+
+ /**
+ * Returns the start row.
+ */
+ uint startRow() const;
+
+ /**
+ * Sets the text codec that shall be used for parsing the csv list.
+ *
+ * The default is the system locale.
+ */
+ void setTextCodec(QTextCodec *textCodec);
+
+ /**
+ * Returns the text codec that is used for parsing the csv list.
+ */
+ QTextCodec *textCodec() const;
+
+ /**
+ * Inherited from QAbstractTableModel.
+ */
+ int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+
+ /**
+ * Inherited from QAbstractTableModel.
+ */
+ int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+
+ /**
+ * Inherited from QAbstractTableModel.
+ */
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+
+ /**
+ * Inherited from QAbstractTableModel.
+ */
+ bool setData(const QModelIndex &index, const QVariant &data, int role = Qt::EditRole) Q_DECL_OVERRIDE;
+
+ /**
+ * Inherited from QAbstractTableModel.
+ */
+ Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
+
+Q_SIGNALS:
+ /**
+ * This signal is emitted whenever the model has loaded all data.
+ */
+ void finishedLoading();
+
+private:
+ class Private;
+ Private *const d;
+
+ Q_PRIVATE_SLOT(d, void columnCountChanged(int))
+ Q_PRIVATE_SLOT(d, void rowCountChanged(int))
+ Q_PRIVATE_SLOT(d, void fieldChanged(const QString &, int, int))
+ Q_PRIVATE_SLOT(d, void finishedLoading())
+};
+
+#endif
diff --git a/kaddressbook/importexportplugins/csv/import/qcsvmodel_p.h b/kaddressbook/importexportplugins/csv/import/qcsvmodel_p.h
new file mode 100644
index 0000000..3efd7d9
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/qcsvmodel_p.h
@@ -0,0 +1,66 @@
+/*
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef QCSVMODEL_P_H
+#define QCSVMODEL_P_H
+
+#include "qcsvreader.h"
+
+#include <QtCore/QThread>
+
+class CsvParser : public QThread, public QCsvBuilderInterface
+{
+ Q_OBJECT
+
+public:
+ explicit CsvParser(QObject *parent);
+ ~CsvParser();
+
+ void load(QIODevice *device);
+
+ void begin() Q_DECL_OVERRIDE;
+ void beginLine() Q_DECL_OVERRIDE;
+ void field(const QString &data, uint row, uint column) Q_DECL_OVERRIDE;
+ void endLine() Q_DECL_OVERRIDE;
+ void end() Q_DECL_OVERRIDE;
+ void error(const QString &errorMsg) Q_DECL_OVERRIDE;
+
+ QCsvReader *reader()
+ {
+ return mReader;
+ }
+
+Q_SIGNALS:
+ void columnCountChanged(int columns);
+ void rowCountChanged(int rows);
+ void dataChanged(const QString &data, int row, int column);
+ void ended();
+
+protected:
+ void run() Q_DECL_OVERRIDE;
+
+private:
+ QCsvReader *mReader;
+ QIODevice *mDevice;
+ int mRowCount;
+ int mColumnCount;
+ int mCacheCounter;
+};
+
+#endif
diff --git a/kaddressbook/importexportplugins/csv/import/qcsvreader.cpp b/kaddressbook/importexportplugins/csv/import/qcsvreader.cpp
new file mode 100644
index 0000000..6745867
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/qcsvreader.cpp
@@ -0,0 +1,407 @@
+/*
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "qcsvreader.h"
+
+#include <QStringList>
+#include <QTextCodec>
+#include <QTextStream>
+
+#include <KLocalizedString>
+
+QCsvBuilderInterface::~QCsvBuilderInterface()
+{
+}
+
+class QCsvReader::Private
+{
+public:
+ Private(QCsvBuilderInterface *builder)
+ : mBuilder(builder), mNotTerminated(true)
+ {
+ mTextQuote = QLatin1Char('"');
+ mDelimiter = QLatin1Char(' ');
+ mStartRow = 0;
+ mCodec = QTextCodec::codecForLocale();
+ }
+
+ void emitBeginLine(uint row);
+ void emitEndLine(uint row);
+ void emitField(const QString &data, int row, int column);
+
+ QCsvBuilderInterface *mBuilder;
+ QTextCodec *mCodec;
+ QChar mTextQuote;
+ QChar mDelimiter;
+
+ uint mStartRow;
+ bool mNotTerminated;
+};
+
+void QCsvReader::Private::emitBeginLine(uint row)
+{
+ if ((row - mStartRow) > 0) {
+ mBuilder->beginLine();
+ }
+}
+
+void QCsvReader::Private::emitEndLine(uint row)
+{
+ if ((row - mStartRow) > 0) {
+ mBuilder->endLine();
+ }
+}
+
+void QCsvReader::Private::emitField(const QString &data, int row, int column)
+{
+ if ((row - mStartRow) > 0) {
+ mBuilder->field(data, row - mStartRow - 1, column - 1);
+ }
+}
+
+QCsvReader::QCsvReader(QCsvBuilderInterface *builder)
+ : d(new Private(builder))
+{
+ Q_ASSERT(builder);
+}
+
+QCsvReader::~QCsvReader()
+{
+ delete d;
+}
+
+bool QCsvReader::read(QIODevice *device)
+{
+ enum State {
+ StartLine,
+ QuotedField,
+ QuotedFieldEnd,
+ NormalField,
+ EmptyField
+ };
+
+ int row, column;
+
+ QString field;
+ QChar input;
+ State currentState = StartLine;
+
+ row = column = 1;
+
+ d->mBuilder->begin();
+
+ if (!device->isOpen()) {
+ d->emitBeginLine(row);
+ d->mBuilder->error(i18n("Device is not open"));
+ d->emitEndLine(row);
+ d->mBuilder->end();
+ return false;
+ }
+
+ QTextStream inputStream(device);
+ inputStream.setCodec(d->mCodec);
+
+ /**
+ * We use the following state machine to parse CSV:
+ *
+ * digraph {
+ * StartLine -> StartLine [label="\\r\\n"]
+ * StartLine -> QuotedField [label="Quote"]
+ * StartLine -> EmptyField [label="Delimiter"]
+ * StartLine -> NormalField [label="Other Char"]
+ *
+ * QuotedField -> QuotedField [label="\\r\\n"]
+ * QuotedField -> QuotedFieldEnd [label="Quote"]
+ * QuotedField -> QuotedField [label="Delimiter"]
+ * QuotedField -> QuotedField [label="Other Char"]
+ *
+ * QuotedFieldEnd -> StartLine [label="\\r\\n"]
+ * QuotedFieldEnd -> QuotedField [label="Quote"]
+ * QuotedFieldEnd -> EmptyField [label="Delimiter"]
+ * QuotedFieldEnd -> EmptyField [label="Other Char"]
+ *
+ * EmptyField -> StartLine [label="\\r\\n"]
+ * EmptyField -> QuotedField [label="Quote"]
+ * EmptyField -> EmptyField [label="Delimiter"]
+ * EmptyField -> NormalField [label="Other Char"]
+ *
+ * NormalField -> StartLine [label="\\r\\n"]
+ * NormalField -> NormalField [label="Quote"]
+ * NormalField -> EmptyField [label="Delimiter"]
+ * NormalField -> NormalField [label="Other Char"]
+ * }
+ */
+
+ while (!inputStream.atEnd() && d->mNotTerminated) {
+ inputStream >> input;
+
+ switch (currentState) {
+ case StartLine:
+ if (input == QLatin1Char('\r') || input == QLatin1Char('\n')) {
+ currentState = StartLine;
+ } else if (input == d->mTextQuote) {
+ d->emitBeginLine(row);
+ currentState = QuotedField;
+ } else if (input == d->mDelimiter) {
+ d->emitBeginLine(row);
+ d->emitField(field, row, column);
+ column++;
+ currentState = EmptyField;
+ } else {
+ d->emitBeginLine(row);
+ field.append(input);
+ currentState = NormalField;
+ }
+ break;
+ case QuotedField:
+ if (input == QLatin1Char('\r') || input == QLatin1Char('\n')) {
+ field.append(input);
+ currentState = QuotedField;
+ } else if (input == d->mTextQuote) {
+ currentState = QuotedFieldEnd;
+ } else if (input == d->mDelimiter) {
+ field.append(input);
+ currentState = QuotedField;
+ } else {
+ field.append(input);
+ currentState = QuotedField;
+ }
+ break;
+ case QuotedFieldEnd:
+ if (input == QLatin1Char('\r') || input == QLatin1Char('\n')) {
+ d->emitField(field, row, column);
+ field.clear();
+ d->emitEndLine(row);
+ column = 1;
+ row++;
+ currentState = StartLine;
+ } else if (input == d->mTextQuote) {
+ field.append(input);
+ currentState = QuotedField;
+ } else if (input == d->mDelimiter) {
+ d->emitField(field, row, column);
+ field.clear();
+ column++;
+ currentState = EmptyField;
+ } else {
+ d->emitField(field, row, column);
+ field.clear();
+ column++;
+ field.append(input);
+ currentState = EmptyField;
+ }
+ break;
+ case NormalField:
+ if (input == QLatin1Char('\r') || input == QLatin1Char('\n')) {
+ d->emitField(field, row, column);
+ field.clear();
+ d->emitEndLine(row);
+ row++;
+ column = 1;
+ currentState = StartLine;
+ } else if (input == d->mTextQuote) {
+ field.append(input);
+ currentState = NormalField;
+ } else if (input == d->mDelimiter) {
+ d->emitField(field, row, column);
+ field.clear();
+ column++;
+ currentState = EmptyField;
+ } else {
+ field.append(input);
+ currentState = NormalField;
+ }
+ break;
+ case EmptyField:
+ if (input == QLatin1Char('\r') || input == QLatin1Char('\n')) {
+ d->emitField(QString(), row, column);
+ field.clear();
+ d->emitEndLine(row);
+ column = 1;
+ row++;
+ currentState = StartLine;
+ } else if (input == d->mTextQuote) {
+ currentState = QuotedField;
+ } else if (input == d->mDelimiter) {
+ d->emitField(QString(), row, column);
+ column++;
+ currentState = EmptyField;
+ } else {
+ field.append(input);
+ currentState = NormalField;
+ }
+ break;
+ }
+ }
+
+ if (currentState != StartLine) {
+ if (field.length() > 0) {
+ d->emitField(field, row, column);
+ ++row;
+ field.clear();
+ }
+ d->emitEndLine(row);
+ }
+
+ d->mBuilder->end();
+
+ return true;
+}
+
+void QCsvReader::setTextQuote(const QChar &textQuote)
+{
+ d->mTextQuote = textQuote;
+}
+
+QChar QCsvReader::textQuote() const
+{
+ return d->mTextQuote;
+}
+
+void QCsvReader::setDelimiter(const QChar &delimiter)
+{
+ d->mDelimiter = delimiter;
+}
+
+QChar QCsvReader::delimiter() const
+{
+ return d->mDelimiter;
+}
+
+void QCsvReader::setStartRow(uint startRow)
+{
+ d->mStartRow = startRow;
+}
+
+uint QCsvReader::startRow() const
+{
+ return d->mStartRow;
+}
+
+void QCsvReader::setTextCodec(QTextCodec *textCodec)
+{
+ d->mCodec = textCodec;
+}
+
+QTextCodec *QCsvReader::textCodec() const
+{
+ return d->mCodec;
+}
+
+void QCsvReader::terminate()
+{
+ d->mNotTerminated = false;
+}
+
+class QCsvStandardBuilder::Private
+{
+public:
+ Private()
+ {
+ init();
+ }
+
+ void init();
+
+ QString mLastErrorString;
+ uint mRowCount;
+ uint mColumnCount;
+ QList<QStringList> mRows;
+};
+
+void QCsvStandardBuilder::Private::init()
+{
+ mRows.clear();
+ mRowCount = 0;
+ mColumnCount = 0;
+ mLastErrorString.clear();
+}
+
+QCsvStandardBuilder::QCsvStandardBuilder()
+ : d(new Private)
+{
+}
+
+QCsvStandardBuilder::~QCsvStandardBuilder()
+{
+ delete d;
+}
+
+QString QCsvStandardBuilder::lastErrorString() const
+{
+ return d->mLastErrorString;
+}
+
+uint QCsvStandardBuilder::rowCount() const
+{
+ return d->mRowCount;
+}
+
+uint QCsvStandardBuilder::columnCount() const
+{
+ return d->mColumnCount;
+}
+
+QString QCsvStandardBuilder::data(uint row, uint column) const
+{
+ if (row > d->mRowCount || column > d->mColumnCount || column >= (uint)d->mRows[ row ].count()) {
+ return QString();
+ }
+
+ return d->mRows[ row ][ column ];
+}
+
+void QCsvStandardBuilder::begin()
+{
+ d->init();
+}
+
+void QCsvStandardBuilder::beginLine()
+{
+ d->mRows.append(QStringList());
+ d->mRowCount++;
+}
+
+void QCsvStandardBuilder::field(const QString &data, uint row, uint column)
+{
+ const uint size = d->mRows[ row ].size();
+ if (column >= size) {
+ for (uint i = column; i < size + 1; ++i) {
+ d->mRows[ row ].append(QString());
+ }
+ }
+
+ d->mRows[ row ][ column ] = data;
+
+ d->mColumnCount = qMax(d->mColumnCount, column + 1);
+}
+
+void QCsvStandardBuilder::endLine()
+{
+}
+
+void QCsvStandardBuilder::end()
+{
+}
+
+void QCsvStandardBuilder::error(const QString &errorMsg)
+{
+ d->mLastErrorString = errorMsg;
+}
+
diff --git a/kaddressbook/importexportplugins/csv/import/qcsvreader.h b/kaddressbook/importexportplugins/csv/import/qcsvreader.h
new file mode 100644
index 0000000..18ee74e
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/qcsvreader.h
@@ -0,0 +1,237 @@
+/*
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef QCSVREADER_H
+#define QCSVREADER_H
+
+#include <QObject>
+
+class QIODevice;
+
+/**
+ * @short An interface to build data structures from a CSV file.
+ *
+ * This class provides an abstract interface that can be used
+ * to build up data structures from a comma separated value file
+ * that is parsed with QCsvReader.
+ *
+ * @author Tobias Koenig <tokoe@kde.org>
+ */
+class QCsvBuilderInterface
+{
+public:
+ /**
+ * This method is called on the destruction of the interface.
+ */
+ virtual ~QCsvBuilderInterface();
+
+ /**
+ * This method is called on start of the parsing.
+ */
+ virtual void begin() = 0;
+
+ /**
+ * This method is called whenever a new line starts.
+ */
+ virtual void beginLine() = 0;
+
+ /**
+ * This method is called for every parsed field.
+ *
+ * @param data The data of the field.
+ * @param row The row of the field.
+ * @param column The column of the field.
+ */
+ virtual void field(const QString &data, uint row, uint column) = 0;
+
+ /**
+ * This method is called whenever a line ends.
+ */
+ virtual void endLine() = 0;
+
+ /**
+ * This method is called at the end of parsing.
+ */
+ virtual void end() = 0;
+
+ /**
+ * This method is called whenever an error occurs during parsing.
+ *
+ * @param errorMsg The error message.
+ */
+ virtual void error(const QString &errorMsg) = 0;
+};
+
+/**
+ * @short A parser for comma separated value data.
+ *
+ * QCsvReader is a class that reads a comma separated value list (csv)
+ * from a device and parses it into its fields. The parsed data are
+ * passed to a QCsvBuilderInterface instance, which can build up
+ * arbitrary data structures from it.
+ *
+ * @author Tobias Koenig <tokoe@kde.org>
+ */
+class QCsvReader : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QChar textQuote READ textQuote WRITE setTextQuote)
+ Q_PROPERTY(QChar delimiter READ delimiter WRITE setDelimiter)
+ Q_PROPERTY(uint startRow READ startRow WRITE setStartRow)
+
+public:
+ /**
+ * Creates a new csv reader.
+ *
+ * @param builder The builder to use.
+ */
+ explicit QCsvReader(QCsvBuilderInterface *builder);
+
+ /**
+ * Destroys the csv reader.
+ */
+ ~QCsvReader();
+
+ /**
+ * Parses the csv data from @p device.
+ *
+ * @return true on success, false otherwise.
+ */
+ bool read(QIODevice *device);
+
+ /**
+ * Sets the character that is used for quoting. The default is '"'.
+ */
+ void setTextQuote(const QChar &textQuote);
+
+ /**
+ * Returns the character that is used for quoting.
+ */
+ QChar textQuote() const;
+
+ /**
+ * Sets the character that is used as delimiter for fields.
+ * The default is ' '.
+ */
+ void setDelimiter(const QChar &delimiter);
+
+ /**
+ * Returns the delimiter that is used as delimiter for fields.
+ */
+ QChar delimiter() const;
+
+ /**
+ * Sets the row from where the parsing shall be started.
+ *
+ * Some csv files have some kind of header in the first line with
+ * the column titles. To retrieve only the real data, set the start row
+ * to '1' in this case.
+ *
+ * The default start row is 0.
+ */
+ void setStartRow(uint startRow);
+
+ /**
+ * Returns the start row.
+ */
+ uint startRow() const;
+
+ /**
+ * Sets the text codec that shall be used for parsing the csv list.
+ *
+ * The default is the system locale.
+ */
+ void setTextCodec(QTextCodec *textCodec);
+
+ /**
+ * Returns the text codec that is used for parsing the csv list.
+ */
+ QTextCodec *textCodec() const;
+
+ /**
+ * Terminates the parsing of the csv data.
+ */
+ void terminate();
+
+private:
+ class Private;
+ Private *const d;
+};
+
+/**
+ * @short A convenience class that implements QCsvBuilderInterface.
+ *
+ * QCsvStandardBuilder is a convenience class which stores
+ * the parsed data from a csv list.
+ *
+ * @author Tobias Koenig <tokoe@kde.org>
+ */
+class QCsvStandardBuilder : public QCsvBuilderInterface
+{
+public:
+ /**
+ * Creates a new csv standard builder.
+ */
+ QCsvStandardBuilder();
+
+ /**
+ * Destroys the csv standard builder.
+ */
+ ~QCsvStandardBuilder();
+
+ /**
+ * Returns the error message of the last error.
+ */
+ QString lastErrorString() const;
+
+ /**
+ * Returns the number of rows.
+ */
+ uint rowCount() const;
+
+ /**
+ * Returns the number of columns.
+ */
+ uint columnCount() const;
+
+ /**
+ * Returns the data of the field at the given
+ * @p row and @p column.
+ */
+ QString data(uint row, uint column) const;
+
+ /**
+ * @internal
+ */
+ void begin() Q_DECL_OVERRIDE;
+ void beginLine() Q_DECL_OVERRIDE;
+ void field(const QString &data, uint row, uint column) Q_DECL_OVERRIDE;
+ void endLine() Q_DECL_OVERRIDE;
+ void end() Q_DECL_OVERRIDE;
+ void error(const QString &errorMsg) Q_DECL_OVERRIDE;
+
+private:
+ class Private;
+ Private *const d;
+
+ Q_DISABLE_COPY(QCsvStandardBuilder)
+};
+
+#endif
diff --git a/kaddressbook/importexportplugins/csv/import/templateselectiondialog.cpp b/kaddressbook/importexportplugins/csv/import/templateselectiondialog.cpp
new file mode 100644
index 0000000..cea86c5
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/templateselectiondialog.cpp
@@ -0,0 +1,253 @@
+/*
+ This file is part of KAddressBook.
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include "templateselectiondialog.h"
+
+#include <KConfig>
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <QStandardPaths>
+#include <QVBoxLayout>
+#include <QIcon>
+#include <QDir>
+
+#include <QPushButton>
+#include <QAbstractTableModel>
+#include <QFile>
+#include <QFileInfo>
+#include <QLabel>
+#include <QListView>
+#include <QMouseEvent>
+#include <QStyledItemDelegate>
+#include <KConfigGroup>
+#include <QDialogButtonBox>
+
+typedef struct {
+ QString displayName;
+ QString fileName;
+ bool isDeletable;
+} TemplateInfo;
+
+class TemplatesModel : public QAbstractTableModel
+{
+public:
+ TemplatesModel(QObject *parent = Q_NULLPTR)
+ : QAbstractTableModel(parent)
+ {
+ update();
+ }
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
+ {
+ if (!parent.isValid()) {
+ return mTemplates.count();
+ } else {
+ return 0;
+ }
+ }
+
+ int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
+ {
+ if (!parent.isValid()) {
+ return 2;
+ } else {
+ return 0;
+ }
+ }
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE
+ {
+ if (!index.isValid() || index.row() >= mTemplates.count() || index.column() >= 2) {
+ return QVariant();
+ }
+
+ if (role == Qt::DisplayRole) {
+ if (index.column() == 0) {
+ return mTemplates[ index.row() ].displayName;
+ } else {
+ return mTemplates[ index.row() ].fileName;
+ }
+ }
+
+ if (role == Qt::UserRole) {
+ return mTemplates[ index.row() ].isDeletable;
+ }
+
+ return QVariant();
+ }
+
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE {
+ if (parent.isValid() || row < 0 || row >= mTemplates.count())
+ {
+ return false;
+ }
+
+ beginRemoveRows(parent, row, row + count - 1);
+ for (int i = 0; i < count; ++i)
+ {
+ if (!QFile::remove(mTemplates[ row ].fileName)) {
+ return false;
+ }
+ mTemplates.removeAt(row);
+ }
+
+ endRemoveRows();
+ return true;
+ }
+
+ void update()
+ {
+ beginResetModel();
+ mTemplates.clear();
+ const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("/kaddressbook/csv-templates/"), QStandardPaths::LocateDirectory);
+ Q_FOREACH (const QString &dir, dirs) {
+ const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.desktop"));
+ Q_FOREACH (const QString &file, fileNames) {
+ const QString fileName = dir + QLatin1Char('/') + file;
+
+ KConfig config(fileName, KConfig::SimpleConfig);
+
+ if (!config.hasGroup("csv column map")) {
+ continue;
+ }
+
+ KConfigGroup group(&config, "Misc");
+ TemplateInfo info;
+ info.displayName = group.readEntry("Name");
+ info.fileName = fileName;
+
+ const QFileInfo fileInfo(info.fileName);
+ info.isDeletable = QFileInfo(fileInfo.absolutePath()).isWritable();
+
+ mTemplates.append(info);
+ }
+ }
+ endResetModel();
+ }
+
+ bool templatesAvailable() const
+ {
+ return !mTemplates.isEmpty();
+ }
+
+private:
+ QList<TemplateInfo> mTemplates;
+};
+
+class TemplateSelectionDelegate : public QStyledItemDelegate
+{
+public:
+ explicit TemplateSelectionDelegate(QObject *parent = Q_NULLPTR)
+ : QStyledItemDelegate(parent), mIcon(QIcon::fromTheme(QStringLiteral("list-remove")))
+ {
+ }
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const Q_DECL_OVERRIDE
+ {
+ QStyledItemDelegate::paint(painter, option, index);
+
+ if (index.data(Qt::UserRole).toBool()) {
+ mIcon.paint(painter, option.rect, Qt::AlignRight);
+ }
+ }
+
+ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE
+ {
+ QSize hint = QStyledItemDelegate::sizeHint(option, index);
+
+ if (index.data(Qt::UserRole).toBool()) {
+ hint.setWidth(hint.width() + 16);
+ }
+
+ return hint;
+ }
+
+ bool editorEvent(QEvent *event, QAbstractItemModel *model,
+ const QStyleOptionViewItem &option, const QModelIndex &index) Q_DECL_OVERRIDE {
+ if (event->type() == QEvent::MouseButtonRelease && index.data(Qt::UserRole).toBool())
+ {
+ const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
+ QRect buttonRect = option.rect;
+ buttonRect.setLeft(buttonRect.right() - 16);
+
+ if (buttonRect.contains(mouseEvent->pos())) {
+ const QString templateName = index.data(Qt::DisplayRole).toString();
+ if (KMessageBox::questionYesNo(
+ Q_NULLPTR,
+ i18nc("@label", "Do you really want to delete template '%1'?",
+ templateName)) == KMessageBox::Yes) {
+ model->removeRows(index.row(), 1);
+ return true;
+ }
+ }
+ }
+
+ return QStyledItemDelegate::editorEvent(event, model, option, index);
+ }
+
+private:
+ QIcon mIcon;
+};
+
+TemplateSelectionDialog::TemplateSelectionDialog(QWidget *parent)
+ : QDialog(parent)
+{
+ setWindowTitle(i18nc("@title:window", "Template Selection"));
+ QVBoxLayout *mainLayout = new QVBoxLayout(this);
+
+ QLabel *lab = new QLabel(i18nc("@info", "Please select a template, that matches the CSV file:"), this);
+ mainLayout->addWidget(lab);
+
+ mView = new QListView(this);
+ mainLayout->addWidget(mView);
+
+ mView->setModel(new TemplatesModel(this));
+ mView->setItemDelegate(new TemplateSelectionDelegate(this));
+
+ connect(mView->selectionModel(), &QItemSelectionModel::selectionChanged,
+ this, &TemplateSelectionDialog::updateButtons);
+ QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
+ mOkButton = buttonBox->button(QDialogButtonBox::Ok);
+ mOkButton->setDefault(true);
+ mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return);
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &TemplateSelectionDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &TemplateSelectionDialog::reject);
+ mOkButton->setEnabled(false);
+ mainLayout->addWidget(buttonBox);
+}
+
+bool TemplateSelectionDialog::templatesAvailable() const
+{
+ return static_cast<TemplatesModel *>(mView->model())->templatesAvailable();
+}
+
+QString TemplateSelectionDialog::selectedTemplate() const
+{
+ const QModelIndex rowIndex = mView->currentIndex();
+ const QModelIndex index = mView->model()->index(rowIndex.row(), 1);
+
+ return index.data(Qt::DisplayRole).toString();
+}
+
+void TemplateSelectionDialog::updateButtons()
+{
+ mOkButton->setEnabled(mView->currentIndex().isValid());
+}
+
diff --git a/kaddressbook/importexportplugins/csv/import/templateselectiondialog.h b/kaddressbook/importexportplugins/csv/import/templateselectiondialog.h
new file mode 100644
index 0000000..2f398d6
--- /dev/null
+++ b/kaddressbook/importexportplugins/csv/import/templateselectiondialog.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of KAddressBook.
+ Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#ifndef TEMPLATESELECTIONDIALOG_H
+#define TEMPLATESELECTIONDIALOG_H
+
+#include <QDialog>
+
+class QListView;
+class QPushButton;
+class TemplateSelectionDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit TemplateSelectionDialog(QWidget *parent = Q_NULLPTR);
+
+ bool templatesAvailable() const;
+
+ QString selectedTemplate() const;
+
+private Q_SLOTS:
+ void updateButtons();
+
+private:
+ QListView *mView;
+ QPushButton *mOkButton;
+};
+
+#endif