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 }