File indexing completed on 2024-05-12 05:10:09

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "csvimporter.h"
0026 #include "csvparser.h"
0027 #include "translators.h" // needed for ImportAction
0028 #include "../collectionfieldsdialog.h"
0029 #include "../collection.h"
0030 #include "../tellico_debug.h"
0031 #include "../collectionfactory.h"
0032 #include "../gui/collectiontypecombo.h"
0033 #include "../utils/stringset.h"
0034 
0035 #include <KComboBox>
0036 #include <KSharedConfig>
0037 #include <KConfigGroup>
0038 #include <KMessageBox>
0039 #include <KLocalizedString>
0040 
0041 #include <QSpinBox>
0042 #include <QLineEdit>
0043 #include <QPushButton>
0044 #include <QGroupBox>
0045 #include <QLabel>
0046 #include <QCheckBox>
0047 #include <QRadioButton>
0048 #include <QTableWidget>
0049 #include <QHeaderView>
0050 #include <QGridLayout>
0051 #include <QByteArray>
0052 #include <QVBoxLayout>
0053 #include <QHBoxLayout>
0054 #include <QButtonGroup>
0055 #include <QApplication>
0056 
0057 using Tellico::Import::CSVImporter;
0058 
0059 CSVImporter::CSVImporter(const QUrl& url_) : Tellico::Import::TextImporter(url_),
0060     m_collType(-1),
0061     m_existingCollection(nullptr),
0062     m_firstRowHeader(false),
0063     m_delimiter(QStringLiteral(",")),
0064     m_cancelled(false),
0065     m_widget(nullptr),
0066     m_comboColl(nullptr),
0067     m_checkFirstRowHeader(nullptr),
0068     m_radioComma(nullptr),
0069     m_radioSemicolon(nullptr),
0070     m_radioTab(nullptr),
0071     m_radioOther(nullptr),
0072     m_editOther(nullptr),
0073     m_editColDelimiter(nullptr),
0074     m_editRowDelimiter(nullptr),
0075     m_table(nullptr),
0076     m_colSpinBox(nullptr),
0077     m_comboField(nullptr),
0078     m_setColumnBtn(nullptr),
0079     m_hasAssignedFields(false),
0080     m_isLibraryThing(false),
0081     m_parser(new CSVParser(text())) {
0082   m_parser->setDelimiter(m_delimiter);
0083 }
0084 
0085 CSVImporter::~CSVImporter() {
0086   delete m_parser;
0087   m_parser = nullptr;
0088 }
0089 
0090 Tellico::Data::CollPtr CSVImporter::collection() {
0091   // don't just check if m_coll is non-null since the collection can be created elsewhere
0092   if(m_coll && m_coll->entryCount() > 0) {
0093     return m_coll;
0094   }
0095 
0096   if(!m_coll) {
0097     createCollection();
0098   }
0099 
0100   const QStringList existingNames = m_coll->fieldNames();
0101 
0102   if(m_fieldsToImport.isEmpty() && m_table) {
0103     for(int col = 0; col < m_table->columnCount(); ++col) {
0104       QString t = m_table->horizontalHeaderItem(col)->text();
0105       if(m_coll->fieldByTitle(t)) {
0106         m_columnsToImport << col;
0107         m_fieldsToImport << m_coll->fieldNameByTitle(t);
0108       }
0109     }
0110   }
0111 
0112   if(m_fieldsToImport.isEmpty()) {
0113     myDebug() << "no fields assigned";
0114     return Data::CollPtr();
0115   }
0116 
0117   m_parser->reset(text());
0118 
0119   // if the first row are headers, skip it
0120   if(m_firstRowHeader) {
0121     m_parser->skipLine();
0122   }
0123 
0124   const uint numChars = text().size();
0125   const uint stepSize = qMax(s_stepSize, numChars/100);
0126   const bool showProgress = options() & ImportProgress;
0127 
0128   // do we need to replace column or row delimiters
0129   const bool replaceColDelimiter = (!m_colDelimiter.isEmpty() && m_colDelimiter != FieldFormat::columnDelimiterString());
0130   const bool replaceRowDelimiter = (!m_rowDelimiter.isEmpty() && m_rowDelimiter != FieldFormat::rowDelimiterString());
0131 
0132   uint j = 0;
0133   while(!m_cancelled && m_parser->hasNext()) {
0134     bool empty = true;
0135     Data::EntryPtr entry(new Data::Entry(m_coll));
0136     QStringList values = m_parser->nextTokens();
0137     for(int i = 0; i < m_fieldsToImport.size(); ++i) {
0138       if(m_columnsToImport.at(i) >= values.size()) {
0139         break;
0140       }
0141       const QString currentFieldName = m_fieldsToImport.at(i);
0142       if(!m_coll->hasField(currentFieldName)) {
0143         myDebug() << "No field in collection named" << currentFieldName;
0144         continue;
0145       }
0146       QString value = values[m_columnsToImport.at(i)].trimmed();
0147       const auto fieldType = m_coll->fieldByName(currentFieldName)->type();
0148       // only replace delimiters for tables
0149       // see https://forum.kde.org/viewtopic.php?f=200&t=142712
0150       if(replaceColDelimiter && fieldType == Data::Field::Table) {
0151         value.replace(m_colDelimiter, FieldFormat::columnDelimiterString());
0152       }
0153       if(replaceRowDelimiter && fieldType == Data::Field::Table) {
0154         value.replace(m_rowDelimiter, FieldFormat::rowDelimiterString());
0155       }
0156       if(m_isLibraryThing) {
0157         // special cases for LibraryThing import
0158         if(currentFieldName == QLatin1String("isbn")) {
0159           // ISBN values are enclosed by brackets
0160           value.remove(QLatin1Char('[')).remove(QLatin1Char(']'));
0161         } else if(currentFieldName == QLatin1String("keyword")) {
0162           // LT values are comma-separated
0163           value.replace(QLatin1String(","), FieldFormat::delimiterString());
0164         } else if(currentFieldName == QLatin1String("cdate")) {
0165           // only want date, not time. 10 characters since it's zero-padded
0166           value.truncate(10);
0167         }
0168       }
0169       bool success = entry->setField(currentFieldName, value);
0170       // we might need to add a new allowed value
0171       // assume that if the user is importing the value, it should be allowed
0172       if(!success && fieldType == Data::Field::Choice) {
0173         Data::FieldPtr f = m_coll->fieldByName(currentFieldName);
0174         StringSet allow;
0175         allow.add(f->allowed());
0176         allow.add(value);
0177         f->setAllowed(allow.values());
0178         m_coll->modifyField(f);
0179         success = entry->setField(currentFieldName, value);
0180       }
0181       if(empty && success) {
0182         empty = false;
0183       }
0184       j += value.size();
0185     }
0186     if(!empty) {
0187       m_coll->addEntries(entry);
0188     }
0189 
0190     if(showProgress && j%stepSize == 0) {
0191       emit signalProgress(this, 100*j/numChars);
0192       qApp->processEvents();
0193     }
0194   }
0195 
0196   {
0197     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("ImportOptions - CSV"));
0198     config.writeEntry("Delimiter", m_delimiter);
0199     config.writeEntry("ColumnDelimiter", m_colDelimiter);
0200     config.writeEntry("RowDelimiter", m_rowDelimiter);
0201     config.writeEntry("First Row Titles", m_firstRowHeader);
0202   }
0203 
0204   return m_coll;
0205 }
0206 
0207 QWidget* CSVImporter::widget(QWidget* parent_) {
0208   if(m_widget && m_widget->parent() == parent_) {
0209     return m_widget;
0210   }
0211 
0212   m_widget = new QWidget(parent_);
0213   QVBoxLayout* l = new QVBoxLayout(m_widget);
0214 
0215   QGroupBox* groupBox = new QGroupBox(i18n("CSV Options"), m_widget);
0216   QVBoxLayout* vlay = new QVBoxLayout(groupBox);
0217 
0218   QHBoxLayout* hlay = new QHBoxLayout();
0219   vlay->addLayout(hlay);
0220   QLabel* lab = new QLabel(i18n("Collection &type:"), groupBox);
0221   hlay->addWidget(lab);
0222   m_comboColl = new GUI::CollectionTypeCombo(groupBox);
0223   hlay->addWidget(m_comboColl);
0224   lab->setBuddy(m_comboColl);
0225   m_comboColl->setWhatsThis(i18n("Select the type of collection being imported."));
0226   void (QComboBox::* activatedInt)(int) = &QComboBox::activated;
0227   connect(m_comboColl, activatedInt, this, &CSVImporter::slotTypeChanged);
0228 
0229   m_checkFirstRowHeader = new QCheckBox(i18n("&First row contains field titles"), groupBox);
0230   m_checkFirstRowHeader->setWhatsThis(i18n("If checked, the first row is used as field titles."));
0231   connect(m_checkFirstRowHeader, &QAbstractButton::toggled, this, &CSVImporter::slotFirstRowHeader);
0232   hlay->addWidget(m_checkFirstRowHeader);
0233 
0234   hlay->addStretch(10);
0235 
0236   // use a constant width for the edit boxes. They're 1 or 2 characters long.
0237 #if (QT_VERSION < QT_VERSION_CHECK(5, 11, 0))
0238   const int editWidth = 4 * m_widget->fontMetrics().width(QLatin1Char('X'));
0239 #else
0240   const int editWidth = 4 * m_widget->fontMetrics().horizontalAdvance(QLatin1Char('X'));
0241 #endif
0242 
0243   QHBoxLayout* delimiterLayout = new QHBoxLayout();
0244   vlay->addLayout(delimiterLayout);
0245 
0246   lab = new QLabel(i18n("Delimiter:"), groupBox);
0247   lab->setWhatsThis(i18n("In addition to a comma, other characters may be used as "
0248                          "a delimiter, separating each value in the file."));
0249   delimiterLayout->addWidget(lab);
0250 
0251   m_radioComma = new QRadioButton(groupBox);
0252   m_radioComma->setText(i18n("&Comma"));
0253   m_radioComma->setChecked(true);
0254   m_radioComma->setWhatsThis(i18n("Use a comma as the delimiter."));
0255   delimiterLayout->addWidget(m_radioComma);
0256 
0257   m_radioSemicolon = new QRadioButton( groupBox);
0258   m_radioSemicolon->setText(i18n("&Semicolon"));
0259   m_radioSemicolon->setWhatsThis(i18n("Use a semi-colon as the delimiter."));
0260   delimiterLayout->addWidget(m_radioSemicolon);
0261 
0262   m_radioTab = new QRadioButton(groupBox);
0263   m_radioTab->setText(i18n("Ta&b"));
0264   m_radioTab->setWhatsThis(i18n("Use a tab as the delimiter."));
0265   delimiterLayout->addWidget(m_radioTab);
0266 
0267   m_radioOther = new QRadioButton(groupBox);
0268   m_radioOther->setText(i18n("Ot&her:"));
0269   m_radioOther->setWhatsThis(i18n("Use a custom string as the delimiter."));
0270   delimiterLayout->addWidget(m_radioOther);
0271 
0272   m_editOther = new QLineEdit(groupBox);
0273   m_editOther->setEnabled(false);
0274   m_editOther->setFixedWidth(editWidth);
0275   m_editOther->setMaxLength(1);
0276   m_editOther->setWhatsThis(i18n("A custom string, such as a colon, may be used as a delimiter."));
0277   m_editOther->setEnabled(false);
0278   delimiterLayout->addWidget(m_editOther);
0279   connect(m_radioOther, &QAbstractButton::toggled,
0280           m_editOther, &QWidget::setEnabled);
0281   connect(m_editOther, &QLineEdit::textChanged, this, &CSVImporter::slotDelimiter);
0282   delimiterLayout->addStretch(10);
0283 
0284   QButtonGroup* buttonGroup = new QButtonGroup(groupBox);
0285   buttonGroup->addButton(m_radioComma);
0286   buttonGroup->addButton(m_radioSemicolon);
0287   buttonGroup->addButton(m_radioTab);
0288   buttonGroup->addButton(m_radioOther);
0289 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
0290   void (QButtonGroup::* buttonClickedInt)(int) = &QButtonGroup::buttonClicked;
0291   connect(buttonGroup, buttonClickedInt, this, &CSVImporter::slotDelimiter);
0292 #else
0293   connect(buttonGroup, &QButtonGroup::idClicked, this, &CSVImporter::slotDelimiter);
0294 #endif
0295 
0296   QHBoxLayout* delimiterLayout2 = new QHBoxLayout();
0297   vlay->addLayout(delimiterLayout2);
0298 
0299   QString w = i18n("The column delimiter separates values in each column of a <i>Table</i> field.");
0300   lab = new QLabel(i18n("Table column delimiter:"), groupBox);
0301   lab->setWhatsThis(w);
0302   delimiterLayout2->addWidget(lab);
0303   m_editColDelimiter = new QLineEdit(groupBox);
0304   m_editColDelimiter->setWhatsThis(w);
0305   m_editColDelimiter->setFixedWidth(editWidth);
0306   m_editColDelimiter->setMaxLength(1);
0307   delimiterLayout2->addWidget(m_editColDelimiter);
0308   connect(m_editColDelimiter, &QLineEdit::textChanged, this, &CSVImporter::slotDelimiter);
0309 
0310   w = i18n("The row delimiter separates values in each row of a <i>Table</i> field.");
0311   lab = new QLabel(i18n("Table row delimiter:"), groupBox);
0312   lab->setWhatsThis(w);
0313   delimiterLayout2->addWidget(lab);
0314   m_editRowDelimiter = new QLineEdit(groupBox);
0315   m_editRowDelimiter->setWhatsThis(w);
0316   m_editRowDelimiter->setFixedWidth(editWidth);
0317   m_editRowDelimiter->setMaxLength(1);
0318   delimiterLayout2->addWidget(m_editRowDelimiter);
0319   connect(m_editRowDelimiter, &QLineEdit::textChanged, this, &CSVImporter::slotDelimiter);
0320 
0321   delimiterLayout2->addStretch(10);
0322 
0323   m_table = new QTableWidget(5, 0, groupBox);
0324   vlay->addWidget(m_table);
0325   m_table->setSelectionMode(QAbstractItemView::SingleSelection);
0326   m_table->setSelectionBehavior(QAbstractItemView::SelectColumns);
0327   m_table->verticalHeader()->hide();
0328   m_table->horizontalHeader()->setSectionsClickable(true);
0329   m_table->setMinimumHeight(m_widget->fontMetrics().lineSpacing() * 8);
0330   m_table->setWhatsThis(i18n("The table shows up to the first five lines of the CSV file."));
0331   connect(m_table, &QTableWidget::currentCellChanged, this, &CSVImporter::slotCurrentChanged);
0332   connect(m_table->horizontalHeader(), &QHeaderView::sectionClicked, this, &CSVImporter::slotHeaderClicked);
0333 
0334   QHBoxLayout* hlay3 = new QHBoxLayout();
0335   vlay->addLayout(hlay3);
0336 
0337   QString what = i18n("<qt>Set each column to correspond to a field in the collection by choosing "
0338                       "a column, selecting the field, then clicking the <i>Assign Field</i> button.</qt>");
0339   lab = new QLabel(i18n("Co&lumn:"), groupBox);
0340   hlay3->addWidget(lab);
0341   lab->setWhatsThis(what);
0342   m_colSpinBox = new QSpinBox(groupBox);
0343   hlay3->addWidget(m_colSpinBox);
0344   m_colSpinBox->setWhatsThis(what);
0345   m_colSpinBox->setMinimum(1);
0346   void (QSpinBox::* valueChangedInt)(int) = &QSpinBox::valueChanged;
0347   connect(m_colSpinBox, valueChangedInt, this, &CSVImporter::slotSelectColumn);
0348   lab->setBuddy(m_colSpinBox);
0349 
0350   hlay3->addSpacing(10);
0351 
0352   lab = new QLabel(i18n("&Data field in this column:"), groupBox);
0353   hlay3->addWidget(lab);
0354   lab->setWhatsThis(what);
0355   m_comboField = new KComboBox(groupBox);
0356   hlay3->addWidget(m_comboField);
0357   m_comboField->setWhatsThis(what);
0358   // roughly 5 times the width of the edit box
0359   m_comboField->setFixedWidth(5 * editWidth);
0360 //  m_comboField->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0361   connect(m_comboField, activatedInt, this, &CSVImporter::slotFieldChanged);
0362   lab->setBuddy(m_comboField);
0363 
0364   hlay3->addSpacing(10);
0365 
0366   m_setColumnBtn = new QPushButton(i18n("&Assign Field"), groupBox);
0367   hlay3->addWidget(m_setColumnBtn);
0368   m_setColumnBtn->setWhatsThis(what);
0369   m_setColumnBtn->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")));
0370   connect(m_setColumnBtn, &QAbstractButton::clicked, this, &CSVImporter::slotSetColumnTitle);
0371 //  hlay3->addStretch(10);
0372 
0373   l->addWidget(groupBox);
0374   l->addStretch(1);
0375 
0376   KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("ImportOptions - CSV"));
0377   m_delimiter = config.readEntry("Delimiter", m_delimiter);
0378   m_colDelimiter = config.readEntry("ColumnDelimiter", m_colDelimiter);
0379   m_rowDelimiter = config.readEntry("RowDelimiter", m_rowDelimiter);
0380   m_firstRowHeader = config.readEntry("First Row Titles", m_firstRowHeader);
0381 
0382   m_checkFirstRowHeader->setChecked(m_firstRowHeader);
0383   if(m_delimiter == QLatin1String(",")) {
0384     m_radioComma->setChecked(true);
0385   } else if(m_delimiter == QLatin1String(";")) {
0386     m_radioSemicolon->setChecked(true);
0387   } else if(m_delimiter == QLatin1String("\t")) {
0388     m_radioTab->setChecked(true);
0389   } else if(!m_delimiter.isEmpty()) {
0390     m_radioOther->setChecked(true);
0391     m_editOther->setEnabled(true);
0392     m_editOther->setText(m_delimiter);
0393   }
0394   m_editColDelimiter->setText(m_colDelimiter);
0395   m_editRowDelimiter->setText(m_rowDelimiter);
0396 
0397   slotDelimiter(); // initialize the parser and then load the text
0398 
0399   return m_widget;
0400 }
0401 
0402 bool CSVImporter::validImport() const {
0403   // at least one column has to be defined
0404   if(!m_hasAssignedFields) {
0405     KMessageBox::error(m_widget, i18n("At least one column must be assigned to a field. "
0406                                       "Only assigned columns will be imported."));
0407   }
0408   return m_hasAssignedFields;
0409 }
0410 
0411 void CSVImporter::setCollectionType(int collType_) {
0412   m_collType = collType_;
0413 }
0414 
0415 void CSVImporter::setImportColumns(const QList<int>& columns_, const QStringList& fieldNames_) {
0416   Q_ASSERT(columns_.size() == fieldNames_.size());
0417   m_columnsToImport = columns_;
0418   m_fieldsToImport = fieldNames_;
0419 }
0420 
0421 void CSVImporter::setDelimiter(const QString& delimiter_) {
0422   m_delimiter = delimiter_;
0423 }
0424 
0425 void CSVImporter::setColumnDelimiter(const QString& delimiter_) {
0426   m_colDelimiter = delimiter_;
0427 }
0428 
0429 void CSVImporter::setRowDelimiter(const QString& delimiter_) {
0430   m_rowDelimiter = delimiter_;
0431 }
0432 
0433 void CSVImporter::fillTable() {
0434   if(!m_table) {
0435     return;
0436   }
0437 
0438   m_parser->reset(text());
0439   // not skipping first row since the updateHeader() call depends on it
0440 
0441   int maxCols = 0;
0442   int row = 0;
0443   for( ; m_parser->hasNext() && row < m_table->rowCount(); ++row) {
0444     QStringList values = m_parser->nextTokens();
0445     if(static_cast<int>(values.count()) > m_table->columnCount()) {
0446       m_table->setColumnCount(values.count());
0447       m_colSpinBox->setMaximum(values.count());
0448     }
0449     int col = 0;
0450     foreach(const QString& value, values) {
0451       m_table->setItem(row, col, new QTableWidgetItem(value));
0452       m_table->resizeColumnToContents(col);
0453       ++col;
0454     }
0455     if(col > maxCols) {
0456       maxCols = col;
0457     }
0458     // special case, check if the header row matches LibraryThing CSV export
0459     // assume LT export always uses identical header row and verify against first 7 columns
0460     if(row == 0 && values.count() > 7) {
0461       m_isLibraryThing = (values.at(0) == QLatin1String("'TITLE'") &&
0462                           values.at(1) == QLatin1String("'AUTHOR (first, last)'") &&
0463                           values.at(2) == QLatin1String("'AUTHOR (last, first)'") &&
0464                           values.at(3) == QLatin1String("'DATE'") &&
0465                           values.at(4) == QLatin1String("'LCC'") &&
0466                           values.at(5) == QLatin1String("'DDC'") &&
0467                           values.at(6) == QLatin1String("'ISBNs'"));
0468     }
0469   }
0470   for( ; row < m_table->rowCount(); ++row) {
0471     for(int col = 0; col < m_table->columnCount(); ++col) {
0472       delete m_table->takeItem(row, col);
0473     }
0474   }
0475 
0476   m_table->setColumnCount(maxCols);
0477 
0478   if(m_isLibraryThing) {
0479     // do not call slotFirstRowHeader since it will loop
0480     m_firstRowHeader = true;
0481     updateHeader();
0482   }
0483 }
0484 
0485 void CSVImporter::slotTypeChanged() {
0486   createCollection();
0487 
0488   updateHeader();
0489   updateFieldCombo();
0490 
0491   // hack to force a resize
0492   m_comboField->setFont(m_comboField->font());
0493   m_comboField->updateGeometry();
0494 }
0495 
0496 void CSVImporter::slotFirstRowHeader(bool b_) {
0497   m_firstRowHeader = b_;
0498   updateHeader();
0499   fillTable();
0500 }
0501 
0502 void CSVImporter::slotDelimiter() {
0503   if(m_radioComma->isChecked()) {
0504     m_delimiter = QStringLiteral(",");
0505   } else if(m_radioSemicolon->isChecked()) {
0506     m_delimiter = QStringLiteral(";");
0507   } else if(m_radioTab->isChecked()) {
0508     m_delimiter = QStringLiteral("\t");
0509   } else {
0510     m_editOther->setFocus();
0511     m_delimiter = m_editOther->text();
0512   }
0513   m_colDelimiter = m_editColDelimiter->text();
0514   m_rowDelimiter = m_editRowDelimiter->text();
0515   if(!m_delimiter.isEmpty()) {
0516     m_parser->setDelimiter(m_delimiter);
0517     fillTable();
0518     updateHeader();
0519   }
0520 }
0521 
0522 void CSVImporter::slotCurrentChanged(int, int col_) {
0523   const int pos = col_+1;
0524   m_colSpinBox->setValue(pos); //slotSelectColumn() gets called because of the signal
0525 }
0526 
0527 void CSVImporter::slotHeaderClicked(int col_) {
0528   const int pos = col_+1;
0529   m_colSpinBox->setValue(pos); //slotSelectColumn() gets called because of the signal
0530 }
0531 
0532 void CSVImporter::slotSelectColumn(int pos_) {
0533   // pos is really the number of the position of the column
0534   const int col = pos_ - 1;
0535   m_table->scrollToItem(m_table->item(0, col));
0536   m_table->selectColumn(col);
0537   m_comboField->setCurrentItem(m_table->horizontalHeaderItem(col)->text());
0538 }
0539 
0540 void CSVImporter::slotSetColumnTitle() {
0541   int col = m_colSpinBox->value()-1;
0542   const QString title = m_comboField->currentText();
0543   m_table->horizontalHeaderItem(col)->setText(title);
0544   m_hasAssignedFields = true;
0545   // make sure none of the other columns have this title
0546   bool found = false;
0547   for(int i = 0; i < col; ++i) {
0548     if(m_table->horizontalHeaderItem(i)->text() == title) {
0549       m_table->horizontalHeaderItem(i)->setText(QString::number(i+1));
0550       found = true;
0551       break;
0552     }
0553   }
0554   // if found, then we're done
0555   if(found) {
0556     return;
0557   }
0558   for(int i = col+1; i < m_table->columnCount(); ++i) {
0559     if(m_table->horizontalHeaderItem(i)->text() == title) {
0560       m_table->horizontalHeaderItem(i)->setText(QString::number(i+1));
0561       break;
0562     }
0563   }
0564 }
0565 
0566 void CSVImporter::updateHeader() {
0567   if(!m_table) {
0568     return;
0569   }
0570 
0571   for(int col = 0; col < m_table->columnCount(); ++col) {
0572     QTableWidgetItem* headerItem = m_table->horizontalHeaderItem(col);
0573     if(!headerItem) {
0574       headerItem = new QTableWidgetItem();
0575       m_table->setHorizontalHeaderItem(col, headerItem);
0576     }
0577 
0578     QTableWidgetItem* item = m_table->item(0, col);
0579     Data::FieldPtr field;
0580     if(item && m_coll) {
0581       QString itemValue = item->text();
0582       // check against LibraryThing import
0583       if(m_isLibraryThing && m_coll->type() == Data::Collection::Book) {
0584         static QHash<QString, QString> ltFields;
0585         if(ltFields.isEmpty()) {
0586           ltFields[QStringLiteral("TITLE")]                = QStringLiteral("title");
0587           ltFields[QStringLiteral("AUTHOR (first, last)")] = QStringLiteral("author");
0588           ltFields[QStringLiteral("DATE")]                 = QStringLiteral("pub_year");
0589           ltFields[QStringLiteral("ISBNs")]                = QStringLiteral("isbn");
0590           ltFields[QStringLiteral("RATINGS")]              = QStringLiteral("rating");
0591           ltFields[QStringLiteral("ENTRY DATE")]           = QStringLiteral("cdate");
0592           ltFields[QStringLiteral("TAGS")]                 = QStringLiteral("keyword");
0593           ltFields[QStringLiteral("COMMENT")]              = QStringLiteral("comments");
0594           ltFields[QStringLiteral("REVIEWS")]              = QStringLiteral("review");
0595         }
0596         // strip leading and trailing single quotes
0597         itemValue.remove(0,1).chop(1);
0598         itemValue = ltFields.value(itemValue);
0599 
0600         // review is a new field, we're going to add it by default
0601         if(itemValue == QLatin1String("review") && !m_coll->hasField(itemValue)) {
0602           Data::FieldPtr field(new Data::Field(QStringLiteral("review"), i18n("Review"), Data::Field::Para));
0603           m_coll->addField(field);
0604           updateFieldCombo();
0605           m_comboField->setCurrentIndex(m_comboField->count()-2);
0606         }
0607       }
0608       field = m_coll->fieldByTitle(itemValue);
0609       if(!field) {
0610         field = m_coll->fieldByName(itemValue);
0611       }
0612     }
0613     if(m_firstRowHeader && field) {
0614       headerItem->setText(field->title());
0615       m_hasAssignedFields = true;
0616     } else {
0617       headerItem->setText(QString::number(col+1));
0618     }
0619   }
0620 }
0621 
0622 void CSVImporter::slotFieldChanged(int idx_) {
0623   // only care if it's the last item -> add new field
0624   if(idx_ < m_comboField->count()-1) {
0625     return;
0626   }
0627 
0628   CollectionFieldsDialog dlg(m_coll, m_widget);
0629   dlg.setNotifyKernel(false);
0630 
0631   if(dlg.exec() == QDialog::Accepted) {
0632     updateFieldCombo();
0633     fillTable();
0634   }
0635 
0636   // set the combo to the item before last
0637   m_comboField->setCurrentIndex(m_comboField->count()-2);
0638 }
0639 
0640 void CSVImporter::slotActionChanged(int action_) {
0641   Data::CollPtr currColl = currentCollection();
0642   if(!currColl) {
0643     m_existingCollection = nullptr;
0644     return;
0645   }
0646 
0647   switch(action_) {
0648     case Import::Replace:
0649       {
0650         int currType = m_comboColl->currentType();
0651         m_comboColl->reset();
0652         m_comboColl->setCurrentType(currType);
0653         m_existingCollection = nullptr;
0654       }
0655       break;
0656 
0657     case Import::Append:
0658     case Import::Merge:
0659      {
0660         m_comboColl->clear();
0661         QString name = CollectionFactory::nameHash().value(currColl->type());
0662         m_comboColl->addItem(name, currColl->type());
0663         m_existingCollection = currColl;
0664      }
0665      break;
0666   }
0667   slotTypeChanged();
0668 }
0669 
0670 void CSVImporter::slotCancel() {
0671   m_cancelled = true;
0672 }
0673 
0674 void CSVImporter::createCollection() {
0675   Q_ASSERT(m_collType > -1 || m_comboColl);
0676   Data::Collection::Type type = static_cast<Data::Collection::Type>(m_collType > -1 ? m_collType : m_comboColl->currentType());
0677   m_coll = CollectionFactory::collection(type, true);
0678   if(m_existingCollection) {
0679     // if we're using the existing collection, then we
0680     // want the newly created collection to have the same fields
0681     foreach(Data::FieldPtr field, m_coll->fields()) {
0682       m_coll->removeField(field, true /* force */);
0683     }
0684     foreach(Data::FieldPtr field, m_existingCollection->fields()) {
0685       m_coll->addField(Data::FieldPtr(new Data::Field(*field)));
0686     }
0687   }
0688 }
0689 
0690 void CSVImporter::updateFieldCombo() {
0691   m_comboField->clear();
0692   foreach(Data::FieldPtr field, m_coll->fields()) {
0693     m_comboField->addItem(field->title());
0694   }
0695   m_comboField->addItem(i18n("<New Field>"));
0696 }