File indexing completed on 2024-05-12 16:46:29

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