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 }