diff --git a/src/gui/element/associatedfilesui.cpp b/src/gui/element/associatedfilesui.cpp index bbe81952..e591e360 100644 --- a/src/gui/element/associatedfilesui.cpp +++ b/src/gui/element/associatedfilesui.cpp @@ -1,305 +1,306 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "associatedfilesui.h" #include #include #include #include #include #include #include #include #include +#include +#include -#include #include class AssociatedFilesUI::Private { private: AssociatedFilesUI *p; public: QLabel *labelGreeting; - KLineEdit *lineEditSourceUrl; + QLineEdit *lineEditSourceUrl; QRadioButton *radioNoCopyMove, *radioCopyFile, *radioMoveFile; QLabel *labelMoveCopyLocation; - KLineEdit *lineMoveCopyLocation; + QLineEdit *lineMoveCopyLocation; QGroupBox *groupBoxRename; QRadioButton *radioKeepFilename, *radioRenameToEntryId, *radioUserDefinedName; - KLineEdit *lineEditUserDefinedName; + QLineEdit *lineEditUserDefinedName; QGroupBox *groupBoxPathType; QRadioButton *radioRelativePath, *radioAbsolutePath; - KLineEdit *linePreview; + QLineEdit *linePreview; QUrl sourceUrl; QSharedPointer entry; QString entryId; const File *bibTeXfile; Private(AssociatedFilesUI *parent) : p(parent), entry(QSharedPointer()), bibTeXfile(nullptr) { setupGUI(); } void setupGUI() { QBoxLayout *layout = new QVBoxLayout(p); labelGreeting = new QLabel(p); layout->addWidget(labelGreeting); labelGreeting->setWordWrap(true); - lineEditSourceUrl = new KLineEdit(p); + lineEditSourceUrl = new QLineEdit(p); layout->addWidget(lineEditSourceUrl); lineEditSourceUrl->setReadOnly(true); layout->addSpacing(8); QLabel *label = new QLabel(i18n("The following operations can be performed when associating the document with the entry:"), p); layout->addWidget(label); label->setWordWrap(true); QGroupBox *groupBox = new QGroupBox(i18n("File operation"), p); layout->addWidget(groupBox); QBoxLayout *groupBoxLayout = new QVBoxLayout(groupBox); QButtonGroup *buttonGroup = new QButtonGroup(groupBox); radioNoCopyMove = new QRadioButton(i18n("Do not copy or move document, only insert reference to it"), groupBox); groupBoxLayout->addWidget(radioNoCopyMove); buttonGroup->addButton(radioNoCopyMove); radioCopyFile = new QRadioButton(i18n("Copy document next to bibliography file"), groupBox); groupBoxLayout->addWidget(radioCopyFile); buttonGroup->addButton(radioCopyFile); radioMoveFile = new QRadioButton(i18n("Move document next to bibliography file"), groupBox); groupBoxLayout->addWidget(radioMoveFile); buttonGroup->addButton(radioMoveFile); connect(buttonGroup, static_cast(&QButtonGroup::buttonClicked), p, &AssociatedFilesUI::updateUIandPreview); radioNoCopyMove->setChecked(true); /// by default groupBoxLayout->addSpacing(4); labelMoveCopyLocation = new QLabel(i18n("Path and filename of bibliography file:"), groupBox); groupBoxLayout->addWidget(labelMoveCopyLocation, 1); - lineMoveCopyLocation = new KLineEdit(groupBox); + lineMoveCopyLocation = new QLineEdit(groupBox); lineMoveCopyLocation->setReadOnly(true); groupBoxLayout->addWidget(lineMoveCopyLocation, 1); groupBoxRename = new QGroupBox(i18n("Rename Document?"), p); layout->addWidget(groupBoxRename); QGridLayout *gridLayout = new QGridLayout(groupBoxRename); gridLayout->setColumnMinimumWidth(0, 16); gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 1); buttonGroup = new QButtonGroup(groupBoxRename); radioKeepFilename = new QRadioButton(i18n("Keep document's original filename"), groupBoxRename); gridLayout->addWidget(radioKeepFilename, 0, 0, 1, 2); buttonGroup->addButton(radioKeepFilename); radioRenameToEntryId = new QRadioButton(groupBoxRename); gridLayout->addWidget(radioRenameToEntryId, 1, 0, 1, 2); buttonGroup->addButton(radioRenameToEntryId); radioUserDefinedName = new QRadioButton(i18n("User-defined name:"), groupBoxRename); gridLayout->addWidget(radioUserDefinedName, 2, 0, 1, 2); buttonGroup->addButton(radioUserDefinedName); - lineEditUserDefinedName = new KLineEdit(groupBoxRename); + lineEditUserDefinedName = new QLineEdit(groupBoxRename); gridLayout->addWidget(lineEditUserDefinedName, 3, 1, 1, 1); - connect(lineEditUserDefinedName, &KLineEdit::textEdited, p, &AssociatedFilesUI::updateUIandPreview); + connect(lineEditUserDefinedName, &QLineEdit::textEdited, p, &AssociatedFilesUI::updateUIandPreview); connect(buttonGroup, static_cast(&QButtonGroup::buttonClicked), p, &AssociatedFilesUI::updateUIandPreview); radioRenameToEntryId->setChecked(true); /// by default groupBoxPathType = new QGroupBox(i18n("Path as Inserted into Entry"), p); buttonGroup = new QButtonGroup(groupBoxPathType); layout->addWidget(groupBoxPathType); groupBoxLayout = new QVBoxLayout(groupBoxPathType); radioRelativePath = new QRadioButton(i18n("Relative Path"), groupBoxPathType); groupBoxLayout->addWidget(radioRelativePath); buttonGroup->addButton(radioRelativePath); radioAbsolutePath = new QRadioButton(i18n("Absolute Path"), groupBoxPathType); groupBoxLayout->addWidget(radioAbsolutePath); buttonGroup->addButton(radioAbsolutePath); connect(buttonGroup, static_cast(&QButtonGroup::buttonClicked), p, &AssociatedFilesUI::updateUIandPreview); radioRelativePath->setChecked(true); /// by default layout->addSpacing(8); label = new QLabel(i18n("Preview of reference to be inserted:"), p); layout->addWidget(label); - linePreview = new KLineEdit(p); + linePreview = new QLineEdit(p); layout->addWidget(linePreview); linePreview->setReadOnly(true); layout->addStretch(10); } }; bool AssociatedFilesUI::associateUrl(const QUrl &url, QSharedPointer &entry, const File *bibTeXfile, QWidget *parent) { QPointer dlg = new QDialog(parent); QBoxLayout *layout = new QVBoxLayout(dlg); QPointer ui = new AssociatedFilesUI(entry->id(), bibTeXfile, dlg); layout->addWidget(ui); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, dlg); layout->addWidget(buttonBox); dlg->setLayout(layout); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, dlg.data(), &QDialog::accept); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, dlg.data(), &QDialog::reject); if (AssociatedFiles::urlIsLocal(url)) ui->setupForLocalFile(url, entry->id()); else ui->setupForRemoteUrl(url, entry->id()); const bool accepted = dlg->exec() == QDialog::Accepted; bool success = true; if (accepted) { const QUrl newUrl = AssociatedFiles::copyDocument(url, entry->id(), bibTeXfile, ui->renameOperation(), ui->moveCopyOperation(), dlg, ui->userDefinedFilename()); success &= !newUrl.isEmpty(); if (success) { const QString referenceString = AssociatedFiles::associateDocumentURL(newUrl, entry, bibTeXfile, ui->pathType()); success &= !referenceString.isEmpty(); } } delete dlg; return accepted && success; } QString AssociatedFilesUI::associateUrl(const QUrl &url, const QString &entryId, const File *bibTeXfile, QWidget *parent) { QPointer dlg = new QDialog(parent); QBoxLayout *layout = new QVBoxLayout(dlg); QPointer ui = new AssociatedFilesUI(entryId, bibTeXfile, dlg); layout->addWidget(ui); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, dlg); layout->addWidget(buttonBox); dlg->setLayout(layout); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, dlg.data(), &QDialog::accept); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, dlg.data(), &QDialog::reject); if (AssociatedFiles::urlIsLocal(url)) ui->setupForLocalFile(url, entryId); else ui->setupForRemoteUrl(url, entryId); const bool accepted = dlg->exec() == QDialog::Accepted; bool success = true; QString referenceString; if (accepted) { const QUrl newUrl = AssociatedFiles::copyDocument(url, entryId, bibTeXfile, ui->renameOperation(), ui->moveCopyOperation(), dlg, ui->userDefinedFilename()); success &= !newUrl.isEmpty(); if (success) { referenceString = AssociatedFiles::associateDocumentURL(newUrl, bibTeXfile, ui->pathType()); success &= !referenceString.isEmpty(); } } delete dlg; return accepted && success ? referenceString : QString(); } AssociatedFilesUI::AssociatedFilesUI(const QString &entryId, const File *bibTeXfile, QWidget *parent) : QWidget(parent), d(new AssociatedFilesUI::Private(this)) { d->entryId = entryId; d->bibTeXfile = bibTeXfile; } AssociatedFilesUI::~AssociatedFilesUI() { delete d; } AssociatedFiles::RenameOperation AssociatedFilesUI::renameOperation() const { if (d->radioRenameToEntryId->isChecked()) return AssociatedFiles::roEntryId; else if (d->radioKeepFilename->isChecked() || d->lineEditUserDefinedName->text().isEmpty()) return AssociatedFiles::roKeepName; else return AssociatedFiles::roUserDefined; } AssociatedFiles::MoveCopyOperation AssociatedFilesUI::moveCopyOperation() const { if (d->radioNoCopyMove->isChecked()) return AssociatedFiles::mcoNoCopyMove; else if (d->radioMoveFile->isChecked()) return AssociatedFiles::mcoMove; else return AssociatedFiles::mcoCopy; } AssociatedFiles::PathType AssociatedFilesUI::pathType() const { return d->radioAbsolutePath->isChecked() ? AssociatedFiles::ptAbsolute : AssociatedFiles::ptRelative; } QString AssociatedFilesUI::userDefinedFilename() const { QString text = d->lineEditUserDefinedName->text(); const int p = qMax(text.lastIndexOf(QLatin1Char('/')), text.lastIndexOf(QDir::separator())); if (p > 0) text = text.mid(p + 1); return text; } void AssociatedFilesUI::updateUIandPreview() { QString preview = i18n("No preview available"); const QString entryId = d->entryId.isEmpty() && !d->entry.isNull() ? d->entry->id() : d->entryId; if (entryId.isEmpty()) { d->radioRenameToEntryId->setEnabled(false); d->radioKeepFilename->setChecked(true); } else d->radioRenameToEntryId->setEnabled(true); if (d->bibTeXfile == nullptr || !d->bibTeXfile->hasProperty(File::Url)) { d->radioRelativePath->setEnabled(false); d->radioAbsolutePath->setChecked(true); d->labelMoveCopyLocation->hide(); d->lineMoveCopyLocation->hide(); } else { d->radioRelativePath->setEnabled(true); d->labelMoveCopyLocation->show(); d->lineMoveCopyLocation->show(); d->lineMoveCopyLocation->setText(d->bibTeXfile->property(File::Url).toUrl().path()); } if (d->bibTeXfile != nullptr && !d->sourceUrl.isEmpty() && !entryId.isEmpty()) { const QUrl newUrl = AssociatedFiles::copyDocument(d->sourceUrl, entryId, d->bibTeXfile, renameOperation(), moveCopyOperation(), nullptr, d->lineEditUserDefinedName->text(), true); if (!newUrl.isEmpty()) preview = AssociatedFiles::associateDocumentURL(newUrl, d->bibTeXfile, pathType()); } d->linePreview->setText(preview); d->groupBoxRename->setEnabled(!d->radioNoCopyMove->isChecked()); } void AssociatedFilesUI::setupForRemoteUrl(const QUrl &url, const QString &entryId) { d->sourceUrl = url; d->lineEditSourceUrl->setText(url.toDisplayString()); if (entryId.isEmpty()) { d->labelGreeting->setText(i18n("The following remote document is about to be associated with the current entry:")); d->radioRenameToEntryId->setText(i18n("Rename after entry's id")); } else { d->labelGreeting->setText(i18n("The following remote document is about to be associated with entry '%1':", entryId)); d->radioRenameToEntryId->setText(i18n("Rename after entry's id: '%1'", entryId)); } updateUIandPreview(); } void AssociatedFilesUI::setupForLocalFile(const QUrl &url, const QString &entryId) { d->sourceUrl = url; d->lineEditSourceUrl->setText(url.path()); if (entryId.isEmpty()) { d->labelGreeting->setText(i18n("The following local document is about to be associated with the current entry:")); d->radioRenameToEntryId->setText(i18n("Rename after entry's id")); } else { d->labelGreeting->setText(i18n("The following local document is about to be associated with entry '%1':", entryId)); d->radioRenameToEntryId->setText(i18n("Rename after entry's id: '%1'", entryId)); } updateUIandPreview(); } diff --git a/src/gui/element/elementwidgets.cpp b/src/gui/element/elementwidgets.cpp index a8cccfc7..006e0ade 100644 --- a/src/gui/element/elementwidgets.cpp +++ b/src/gui/element/elementwidgets.cpp @@ -1,1380 +1,1380 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "elementwidgets.h" #include #include #include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include -#include #include #include #include #include #include #include "preferences.h" #include "idsuggestions.h" #include "fileinfo.h" #include "kbibtex.h" #include "bibtexentries.h" #include "bibtexfields.h" #include "fileimporterbibtex.h" #include "fileexporterbibtex.h" #include "file.h" #include "fieldinput.h" #include "entry.h" #include "macro.h" #include "preamble.h" #include "fieldlineedit.h" #include "delayedexecutiontimer.h" #include "logging_gui.h" static const unsigned int interColumnSpace = 16; static const char *PropertyIdSuggestion = "PropertyIdSuggestion"; ElementWidget::ElementWidget(QWidget *parent) : QWidget(parent), isReadOnly(false), m_file(nullptr), m_isModified(false) { /// nothing } bool ElementWidget::isModified() const { return m_isModified; } void ElementWidget::setModified(bool newIsModified) { m_isModified = newIsModified; emit modified(newIsModified); } void ElementWidget::gotModified() { setModified(true); } EntryConfiguredWidget::EntryConfiguredWidget(const QSharedPointer &entryTabLayout, QWidget *parent) : ElementWidget(parent), fieldInputCount(entryTabLayout->singleFieldLayouts.size()), numCols(entryTabLayout->columns), etl(entryTabLayout) { gridLayout = new QGridLayout(this); /// Initialize list of field input widgets plus labels listOfLabeledFieldInput = new LabeledFieldInput*[fieldInputCount]; createGUI(); } EntryConfiguredWidget::~EntryConfiguredWidget() { delete[] listOfLabeledFieldInput; } bool EntryConfiguredWidget::apply(QSharedPointer element) const { if (isReadOnly) return false; /// never save data if in read-only mode QSharedPointer entry = element.dynamicCast(); if (entry.isNull()) return false; for (QMap::ConstIterator it = bibtexKeyToWidget.constBegin(); it != bibtexKeyToWidget.constEnd(); ++it) { Value value; it.value()->apply(value); entry->remove(it.key()); if (!value.isEmpty()) entry->insert(it.key(), value); } return true; } bool EntryConfiguredWidget::reset(QSharedPointer element) { QSharedPointer entry = element.dynamicCast(); if (entry.isNull()) return false; /// clear all widgets for (QMap::Iterator it = bibtexKeyToWidget.begin(); it != bibtexKeyToWidget.end(); ++it) { it.value()->setFile(m_file); it.value()->clear(); } for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it) { const QString key = it.key().toLower(); if (bibtexKeyToWidget.contains(key)) { FieldInput *fieldInput = bibtexKeyToWidget[key]; fieldInput->setElement(element.data()); fieldInput->reset(it.value()); } } return true; } bool EntryConfiguredWidget::validate(QWidget **widgetWithIssue, QString &message) const { for (int i = fieldInputCount - 1; i >= 0; --i) { const bool v = listOfLabeledFieldInput[i]->fieldInput->validate(widgetWithIssue, message); if (!v) return false; } return true; } void EntryConfiguredWidget::showReqOptWidgets(bool forceVisible, const QString &entryType) { layoutGUI(forceVisible, entryType); } void EntryConfiguredWidget::setReadOnly(bool isReadOnly) { ElementWidget::setReadOnly(isReadOnly); for (QMap::Iterator it = bibtexKeyToWidget.begin(); it != bibtexKeyToWidget.end(); ++it) it.value()->setReadOnly(isReadOnly); } QString EntryConfiguredWidget::label() { return etl->uiCaption; } QIcon EntryConfiguredWidget::icon() { return QIcon::fromTheme(etl->iconName); } void EntryConfiguredWidget::setFile(const File *file) { for (QMap::Iterator it = bibtexKeyToWidget.begin(); it != bibtexKeyToWidget.end(); ++it) { it.value()->setFile(file); if (file != nullptr) { /// list of unique values for same field QStringList list = file->uniqueEntryValuesList(it.key()); /// for crossref fields, add all entries' ids if (it.key().toLower() == Entry::ftCrossRef) list.append(file->allKeys(File::etEntry)); /// add macro keys list.append(file->allKeys(File::etMacro)); it.value()->setCompletionItems(list); } } ElementWidget::setFile(file); } bool EntryConfiguredWidget::canEdit(const Element *element) { return Entry::isEntry(*element); } void EntryConfiguredWidget::createGUI() { int i = 0; for (const SingleFieldLayout &sfl : const_cast &>(etl->singleFieldLayouts)) { LabeledFieldInput *labeledFieldInput = new LabeledFieldInput; /// create an editing widget for this field const FieldDescription &fd = BibTeXFields::instance().find(sfl.bibtexLabel); labeledFieldInput->fieldInput = new FieldInput(sfl.fieldInputLayout, fd.preferredTypeFlag, fd.typeFlags, this); labeledFieldInput->fieldInput->setFieldKey(sfl.bibtexLabel); bibtexKeyToWidget.insert(sfl.bibtexLabel, labeledFieldInput->fieldInput); connect(labeledFieldInput->fieldInput, &FieldInput::modified, this, &EntryConfiguredWidget::gotModified); /// memorize if field input should grow vertically (e.g. is a list) labeledFieldInput->isVerticallyMinimumExpaning = sfl.fieldInputLayout == KBibTeX::MultiLine || sfl.fieldInputLayout == KBibTeX::List || sfl.fieldInputLayout == KBibTeX::PersonList || sfl.fieldInputLayout == KBibTeX::KeywordList; /// create a label next to the editing widget labeledFieldInput->label = new QLabel(QString(QStringLiteral("%1:")).arg(sfl.uiLabel), this); labeledFieldInput->label->setBuddy(labeledFieldInput->fieldInput->buddy()); /// align label's text vertically to match field input const Qt::Alignment horizontalAlignment = static_cast(labeledFieldInput->label->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment)) & Qt::AlignHorizontal_Mask; labeledFieldInput->label->setAlignment(horizontalAlignment | (labeledFieldInput->isVerticallyMinimumExpaning ? Qt::AlignTop : Qt::AlignVCenter)); listOfLabeledFieldInput[i] = labeledFieldInput; ++i; } layoutGUI(true); } void EntryConfiguredWidget::layoutGUI(bool forceVisible, const QString &entryType) { QStringList visibleItems; if (!forceVisible && !entryType.isEmpty()) { const QString entryTypeLc = entryType.toLower(); for (const auto &ed : BibTeXEntries::instance()) { if (entryTypeLc == ed.upperCamelCase.toLower() || entryTypeLc == ed.upperCamelCaseAlt.toLower()) { /// this ugly conversion is necessary because we have a "^" (xor) and "|" (and/or) /// syntax to differentiate required items (not used yet, but will be used /// later if missing required items are marked). QString visible = ed.requiredItems.join(QStringLiteral(",")); visible += QLatin1Char(',') + ed.optionalItems.join(QStringLiteral(",")); visible = visible.replace(QLatin1Char('|'), QLatin1Char(',')).replace(QLatin1Char('^'), QLatin1Char(',')); visibleItems = visible.split(QStringLiteral(",")); break; } } } else if (!forceVisible) { // TODO is this an error condition? } /// variables to keep track which and how many field inputs will be visible int countVisible = 0; QScopedArrayPointer visible(new bool[fieldInputCount]); /// ... and if any field input is vertically expaning /// (e.g. a list, important for layout) bool anyoneVerticallyExpanding = false; for (int i = fieldInputCount - 1; i >= 0; --i) { listOfLabeledFieldInput[i]->label->setVisible(false); listOfLabeledFieldInput[i]->fieldInput->setVisible(false); gridLayout->removeWidget(listOfLabeledFieldInput[i]->label); gridLayout->removeWidget(listOfLabeledFieldInput[i]->fieldInput); const QString key = bibtexKeyToWidget.key(listOfLabeledFieldInput[i]->fieldInput).toLower(); const FieldDescription &fd = BibTeXFields::instance().find(key); Value value; listOfLabeledFieldInput[i]->fieldInput->apply(value); /// Hide non-required and non-optional type-dependent fields, /// except if the field has content visible[i] = forceVisible || fd.typeIndependent || !value.isEmpty() || visibleItems.contains(key); if (visible[i]) { ++countVisible; anyoneVerticallyExpanding |= listOfLabeledFieldInput[i]->isVerticallyMinimumExpaning; } } int numRows = countVisible / numCols; if (countVisible % numCols > 0) ++numRows; gridLayout->setRowStretch(numRows, anyoneVerticallyExpanding ? 0 : 1000); int col = 0, row = 0; for (int i = 0; i < fieldInputCount; ++i) if (visible[i]) { /// add label and field input to new position in grid layout gridLayout->addWidget(listOfLabeledFieldInput[i]->label, row, col * 3); gridLayout->addWidget(listOfLabeledFieldInput[i]->fieldInput, row, col * 3 + 1); /// set row stretch gridLayout->setRowStretch(row, listOfLabeledFieldInput[i]->isVerticallyMinimumExpaning ? 1000 : 0); /// set column stretch and spacing gridLayout->setColumnStretch(col * 3, 1); gridLayout->setColumnStretch(col * 3 + 1, 1000); if (col > 0) gridLayout->setColumnMinimumWidth(col * 3 - 1, interColumnSpace); /// count rows and columns correctly ++row; if (row >= numRows) { row = 0; ++col; } /// finally, set label and field input visible again listOfLabeledFieldInput[i]->label->setVisible(true); listOfLabeledFieldInput[i]->fieldInput->setVisible(true); // FIXME expensive! } if (countVisible > 0) { /// fix row stretch for (int i = numRows + 1; i < 100; ++i) gridLayout->setRowStretch(i, 0); /// hide unused columns for (int i = (col + (row == 0 ? 0 : 1)) * 3 - 1; i < 100; ++i) { gridLayout->setColumnMinimumWidth(i, 0); gridLayout->setColumnStretch(i, 0); } } } ReferenceWidget::ReferenceWidget(QWidget *parent) : ElementWidget(parent), m_applyElement(nullptr), m_entryIdManuallySet(false), m_element(QSharedPointer()) { createGUI(); } bool ReferenceWidget::apply(QSharedPointer element) const { if (isReadOnly) return false; /// never save data if in read-only mode bool result = false; QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) { entry->setType(computeType()); entry->setId(entryId->text()); result = true; } else { QSharedPointer macro = element.dynamicCast(); if (!macro.isNull()) { macro->setKey(entryId->text()); result = true; } } return result; } bool ReferenceWidget::reset(QSharedPointer element) { /// if signals are not deactivated, the "modified" signal would be emitted when /// resetting the widgets' values - disconnect(entryType->lineEdit(), &KLineEdit::textChanged, this, &ReferenceWidget::gotModified); - disconnect(entryId, &KLineEdit::textEdited, this, &ReferenceWidget::entryIdManuallyChanged); + disconnect(entryType->lineEdit(), &QLineEdit::textChanged, this, &ReferenceWidget::gotModified); + disconnect(entryId, &QLineEdit::textEdited, this, &ReferenceWidget::entryIdManuallyChanged); bool result = false; QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) { entryType->setEnabled(!isReadOnly); buttonSuggestId->setEnabled(!isReadOnly); QString type = BibTeXEntries::instance().format(entry->type(), KBibTeX::cUpperCamelCase); int index = entryType->findData(type); if (index == -1) { const QString typeLower(type.toLower()); for (const auto &ed : BibTeXEntries::instance()) if (typeLower == ed.upperCamelCaseAlt.toLower()) { index = entryType->findData(ed.upperCamelCase); break; } } entryType->setCurrentIndex(index); if (index == -1) { /// A customized value not known to KBibTeX entryType->lineEdit()->setText(type); } entryId->setText(entry->id()); /// New entries have no values. Use this fact /// to recognize new entries, for which it is /// allowed to automatic set their ids /// if a default id suggestion had been specified. m_entryIdManuallySet = entry->count() > 0; result = true; } else { entryType->setEnabled(false); buttonSuggestId->setEnabled(false); QSharedPointer macro = element.dynamicCast(); if (!macro.isNull()) { entryType->lineEdit()->setText(i18n("Macro")); entryId->setText(macro->key()); result = true; } } - connect(entryId, &KLineEdit::textEdited, this, &ReferenceWidget::entryIdManuallyChanged); - connect(entryType->lineEdit(), &KLineEdit::textChanged, this, &ReferenceWidget::gotModified); + connect(entryId, &QLineEdit::textEdited, this, &ReferenceWidget::entryIdManuallyChanged); + connect(entryType->lineEdit(), &QLineEdit::textChanged, this, &ReferenceWidget::gotModified); return result; } bool ReferenceWidget::validate(QWidget **widgetWithIssue, QString &message) const { message.clear(); static const QRegularExpression validTypeRegExp(QStringLiteral("^[a-z]+$"), QRegularExpression::CaseInsensitiveOption); const QString type = computeType(); const QRegularExpressionMatch validTypeMatch = validTypeRegExp.match(type); if (!validTypeMatch.hasMatch() || validTypeMatch.capturedLength() != type.length()) { if (widgetWithIssue != nullptr) *widgetWithIssue = entryType; message = i18n("Element type '%1' is invalid.", type); return false; } static const QRegularExpression validIdRegExp(QStringLiteral("^[a-z0-9][a-z0-9_:.+/$\\\"&-]*$"), QRegularExpression::CaseInsensitiveOption); const QString id = entryId->text(); const QRegularExpressionMatch validIdMatch = validIdRegExp.match(id); if (!validIdMatch.hasMatch() || validIdMatch.capturedLength() != id.length()) { if (widgetWithIssue != nullptr) *widgetWithIssue = entryId; message = i18n("Id '%1' is invalid", id); return false; } return true; } void ReferenceWidget::setReadOnly(bool isReadOnly) { ElementWidget::setReadOnly(isReadOnly); entryId->setReadOnly(isReadOnly); entryType->setEnabled(!isReadOnly); } QString ReferenceWidget::label() { return QString(); } QIcon ReferenceWidget::icon() { return QIcon(); } bool ReferenceWidget::canEdit(const Element *element) { return Entry::isEntry(*element) || Macro::isMacro(*element); } void ReferenceWidget::setOriginalElement(const QSharedPointer &orig) { m_element = orig; } QString ReferenceWidget::currentId() const { return entryId->text(); } void ReferenceWidget::setCurrentId(const QString &newId) { entryId->setText(newId); } void ReferenceWidget::createGUI() { QHBoxLayout *layout = new QHBoxLayout(this); - entryType = new KComboBox(this); + entryType = new QComboBox(this); entryType->setEditable(true); entryType->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); QLabel *label = new QLabel(i18n("Type:"), this); label->setBuddy(entryType); layout->addWidget(label); layout->addWidget(entryType); layout->addSpacing(interColumnSpace); - entryId = new KLineEdit(this); + entryId = new QLineEdit(this); entryId->setClearButtonEnabled(true); label = new QLabel(i18n("Id:"), this); label->setBuddy(entryId); layout->addWidget(label); layout->addWidget(entryId); for (const auto &ed : BibTeXEntries::instance()) entryType->addItem(ed.label, ed.upperCamelCase); /// Sort the combo box locale-aware. Thus we need a SortFilterProxyModel QSortFilterProxyModel *proxy = new QSortFilterProxyModel(entryType); proxy->setSortLocaleAware(true); proxy->setSourceModel(entryType->model()); entryType->model()->setParent(proxy); entryType->setModel(proxy); entryType->model()->sort(0); /// Button with a menu listing a set of preconfigured id suggestions buttonSuggestId = new QPushButton(QIcon::fromTheme(QStringLiteral("view-filter")), QString(), this); buttonSuggestId->setToolTip(i18n("Select a suggested id for this entry")); layout->addWidget(buttonSuggestId); QMenu *suggestionsMenu = new QMenu(buttonSuggestId); buttonSuggestId->setMenu(suggestionsMenu); - connect(entryType->lineEdit(), &KLineEdit::textChanged, this, &ReferenceWidget::gotModified); - connect(entryId, &KLineEdit::textEdited, this, &ReferenceWidget::entryIdManuallyChanged); - connect(entryType->lineEdit(), &KLineEdit::textChanged, this, &ReferenceWidget::entryTypeChanged); + connect(entryType->lineEdit(), &QLineEdit::textChanged, this, &ReferenceWidget::gotModified); + connect(entryId, &QLineEdit::textEdited, this, &ReferenceWidget::entryIdManuallyChanged); + connect(entryType->lineEdit(), &QLineEdit::textChanged, this, &ReferenceWidget::entryTypeChanged); connect(suggestionsMenu, &QMenu::aboutToShow, this, &ReferenceWidget::prepareSuggestionsMenu); } void ReferenceWidget::prepareSuggestionsMenu() { /// Collect information on the current entry as it is edited QSharedPointer guiDataEntry(new Entry()); m_applyElement->apply(guiDataEntry); QSharedPointer crossrefResolvedEntry(guiDataEntry->resolveCrossref(m_file)); static const IdSuggestions *idSuggestions = new IdSuggestions(); QMenu *suggestionsMenu = buttonSuggestId->menu(); suggestionsMenu->clear(); /// Keep track of shown suggestions to avoid duplicates QSet knownIdSuggestion; const QString defaultSuggestion = idSuggestions->defaultFormatId(*crossrefResolvedEntry.data()); const auto formatIdList = idSuggestions->formatIdList(*crossrefResolvedEntry.data()); for (const QString &suggestionBase : formatIdList) { bool isDefault = suggestionBase == defaultSuggestion; QString suggestion = suggestionBase; /// Test for duplicate ids, use fallback ids with numeric suffix if (m_file != nullptr && m_file->containsKey(suggestion)) { int suffix = 2; while (m_file->containsKey(suggestion = suggestionBase + QChar('_') + QString::number(suffix))) ++suffix; } /// Keep track of shown suggestions to avoid duplicates if (knownIdSuggestion.contains(suggestion)) continue; else knownIdSuggestion.insert(suggestion); /// Create action for suggestion, use icon depending if default or not QAction *suggestionAction = new QAction(suggestion, suggestionsMenu); suggestionAction->setIcon(QIcon::fromTheme(isDefault ? QStringLiteral("favorites") : QStringLiteral("view-filter"))); /// Mesh action into GUI suggestionsMenu->addAction(suggestionAction); connect(suggestionAction, &QAction::triggered, this, &ReferenceWidget::insertSuggestionFromAction); /// Remember suggestion string for time when action gets triggered suggestionAction->setProperty(PropertyIdSuggestion, suggestion); } } void ReferenceWidget::insertSuggestionFromAction() { QAction *action = qobject_cast(sender()); if (action != nullptr) { const QString suggestion = action->property(PropertyIdSuggestion).toString(); entryId->setText(suggestion); } } void ReferenceWidget::entryIdManuallyChanged() { m_entryIdManuallySet = true; gotModified(); } void ReferenceWidget::setEntryIdByDefault() { if (isReadOnly) { /// Never set the suggestion automatically if in read-only mode return; } if (m_entryIdManuallySet) { /// If user changed entry id manually, /// do not overwrite it by a default value return; } static const IdSuggestions *idSuggestions = new IdSuggestions(); /// If there is a default suggestion format set ... if (idSuggestions->hasDefaultFormat()) { /// Collect information on the current entry as it is edited QSharedPointer guiDataEntry(new Entry()); m_applyElement->apply(guiDataEntry); QSharedPointer crossrefResolvedEntry(guiDataEntry->resolveCrossref(m_file)); /// Determine default suggestion based on current data const QString defaultSuggestion = idSuggestions->defaultFormatId(*crossrefResolvedEntry.data()); if (!defaultSuggestion.isEmpty()) { - disconnect(entryId, &KLineEdit::textEdited, this, &ReferenceWidget::entryIdManuallyChanged); + disconnect(entryId, &QLineEdit::textEdited, this, &ReferenceWidget::entryIdManuallyChanged); /// Apply default suggestion to widget entryId->setText(defaultSuggestion); - connect(entryId, &KLineEdit::textEdited, this, &ReferenceWidget::entryIdManuallyChanged); + connect(entryId, &QLineEdit::textEdited, this, &ReferenceWidget::entryIdManuallyChanged); } } } QString ReferenceWidget::computeType() const { if (entryType->currentIndex() < 0 || entryType->lineEdit()->isModified()) return BibTeXEntries::instance().format(entryType->lineEdit()->text(), KBibTeX::cUpperCamelCase); else return entryType->itemData(entryType->currentIndex()).toString(); } FilesWidget::FilesWidget(QWidget *parent) : ElementWidget(parent) { QVBoxLayout *layout = new QVBoxLayout(this); fileList = new FieldInput(KBibTeX::UrlList, KBibTeX::tfVerbatim /* eventually ignored, see constructor of UrlListEdit */, KBibTeX::tfVerbatim /* eventually ignored, see constructor of UrlListEdit */, this); fileList->setFieldKey(QStringLiteral("^external")); layout->addWidget(fileList); connect(fileList, &FieldInput::modified, this, &FilesWidget::gotModified); } bool FilesWidget::apply(QSharedPointer element) const { if (isReadOnly) return false; /// never save data if in read-only mode QSharedPointer entry = element.dynamicCast(); if (entry.isNull()) return false; for (const QString &keyStem : keyStart) for (int i = 1; i < 32; ++i) { /// FIXME replace number by constant const QString key = i > 1 ? keyStem + QString::number(i) : keyStem; entry->remove(key); } Value combinedValue; fileList->apply(combinedValue); Value urlValue, doiValue, localFileValue; urlValue.reserve(combinedValue.size()); doiValue.reserve(combinedValue.size()); localFileValue.reserve(combinedValue.size()); for (const auto &valueItem : const_cast(combinedValue)) { const QSharedPointer verbatimText = valueItem.dynamicCast(); if (!verbatimText.isNull()) { const QString text = verbatimText->text(); QRegularExpressionMatch match; if ((match = KBibTeX::urlRegExp.match(text)).hasMatch()) { /// add full URL VerbatimText *newVT = new VerbatimText(match.captured(0)); /// test for duplicates if (urlValue.contains(*newVT)) delete newVT; else urlValue.append(QSharedPointer(newVT)); } else if ((match = KBibTeX::doiRegExp.match(text)).hasMatch()) { /// add DOI VerbatimText *newVT = new VerbatimText(match.captured(0)); /// test for duplicates if (doiValue.contains(*newVT)) delete newVT; else doiValue.append(QSharedPointer(newVT)); } else { /// add anything else (e.g. local file) VerbatimText *newVT = new VerbatimText(*verbatimText); /// test for duplicates if (localFileValue.contains(*newVT)) delete newVT; else localFileValue.append(QSharedPointer(newVT)); } } } if (urlValue.isEmpty()) entry->remove(Entry::ftUrl); else entry->insert(Entry::ftUrl, urlValue); if (localFileValue.isEmpty()) { entry->remove(Entry::ftFile); entry->remove(Entry::ftLocalFile); } else if (Preferences::instance().bibliographySystem() == Preferences::BibLaTeX) { entry->remove(Entry::ftLocalFile); entry->insert(Entry::ftFile, localFileValue); } else if (Preferences::instance().bibliographySystem() == Preferences::BibTeX) { entry->remove(Entry::ftFile); entry->insert(Entry::ftLocalFile, localFileValue); } if (doiValue.isEmpty()) entry->remove(Entry::ftDOI); else entry->insert(Entry::ftDOI, doiValue); return true; } bool FilesWidget::reset(QSharedPointer element) { QSharedPointer entry = element.dynamicCast(); if (entry.isNull()) return false; Value combinedValue; for (const QString &keyStem : keyStart) for (int i = 1; i < 32; ++i) { /// FIXME replace number by constant const QString key = i > 1 ? keyStem + QString::number(i) : keyStem; const Value &value = entry->operator [](key); for (const auto &valueItem : const_cast(value)) combinedValue.append(valueItem); } fileList->setElement(element.data()); fileList->reset(combinedValue); return true; } bool FilesWidget::validate(QWidget **widgetWithIssue, QString &message) const { return fileList->validate(widgetWithIssue, message); } void FilesWidget::setReadOnly(bool isReadOnly) { ElementWidget::setReadOnly(isReadOnly); fileList->setReadOnly(isReadOnly); } QString FilesWidget::label() { return i18n("External"); } QIcon FilesWidget::icon() { return QIcon::fromTheme(QStringLiteral("emblem-symbolic-link")); } void FilesWidget::setFile(const File *file) { ElementWidget::setFile(file); fileList->setFile(file); } bool FilesWidget::canEdit(const Element *element) { return Entry::isEntry(*element); } const QStringList FilesWidget::keyStart {Entry::ftUrl, QStringLiteral("postscript"), Entry::ftLocalFile, Entry::ftDOI, Entry::ftFile, QStringLiteral("ee"), QStringLiteral("biburl")}; OtherFieldsWidget::OtherFieldsWidget(const QStringList &blacklistedFields, QWidget *parent) : ElementWidget(parent), blackListed(blacklistedFields) { internalEntry = QSharedPointer(new Entry()); createGUI(); } OtherFieldsWidget::~OtherFieldsWidget() { delete fieldContent; } bool OtherFieldsWidget::apply(QSharedPointer element) const { if (isReadOnly) return false; /// never save data if in read-only mode QSharedPointer entry = element.dynamicCast(); if (entry.isNull()) return false; for (const QString &key : const_cast(deletedKeys)) entry->remove(key); for (const QString &key : const_cast(modifiedKeys)) { entry->remove(key); entry->insert(key, internalEntry->value(key)); } return true; } bool OtherFieldsWidget::reset(QSharedPointer element) { QSharedPointer entry = element.dynamicCast(); if (entry.isNull()) return false; internalEntry = QSharedPointer(new Entry(*entry.data())); deletedKeys.clear(); // FIXME clearing list may be premature here... modifiedKeys.clear(); // FIXME clearing list may be premature here... updateList(); updateGUI(); return true; } bool OtherFieldsWidget::validate(QWidget **, QString &) const { /// No checks to make here; all actual check will be conducted in actionAddApply(..) return true; } void OtherFieldsWidget::setReadOnly(bool isReadOnly) { ElementWidget::setReadOnly(isReadOnly); fieldName->setReadOnly(isReadOnly); fieldContent->setReadOnly(isReadOnly); /// will take care of enabled/disabling buttons updateGUI(); updateList(); } QString OtherFieldsWidget::label() { return i18n("Other Fields"); } QIcon OtherFieldsWidget::icon() { return QIcon::fromTheme(QStringLiteral("other")); } bool OtherFieldsWidget::canEdit(const Element *element) { return Entry::isEntry(*element); } void OtherFieldsWidget::listElementExecuted(QTreeWidgetItem *item, int column) { Q_UNUSED(column) /// we do not care which column got clicked QString key = item->text(0); fieldName->setText(key); fieldContent->reset(internalEntry->value(key)); } void OtherFieldsWidget::listCurrentChanged(QTreeWidgetItem *item, QTreeWidgetItem *previous) { Q_UNUSED(previous) bool validUrl = false; bool somethingSelected = item != nullptr; buttonDelete->setEnabled(somethingSelected && !isReadOnly); if (somethingSelected) { currentUrl = QUrl(item->text(1)); validUrl = currentUrl.isValid() && currentUrl.isLocalFile() & QFileInfo::exists(currentUrl.toLocalFile()); if (!validUrl) { const QRegularExpressionMatch urlRegExpMatch = KBibTeX::urlRegExp.match(item->text(1)); if (urlRegExpMatch.hasMatch()) { currentUrl = QUrl(urlRegExpMatch.captured(0)); validUrl = currentUrl.isValid(); buttonOpen->setEnabled(validUrl); } } } if (!validUrl) currentUrl = QUrl(); buttonOpen->setEnabled(validUrl); } void OtherFieldsWidget::actionAddApply() { if (isReadOnly) return; /// never modify anything if in read-only mode QString key = fieldName->text(), message; Value value; if (!fieldContent->validate(nullptr, message)) return; ///< invalid values should not get applied if (!fieldContent->apply(value)) return; if (internalEntry->contains(key)) internalEntry->remove(key); internalEntry->insert(key, value); if (!modifiedKeys.contains(key)) modifiedKeys << key; updateList(); updateGUI(); gotModified(); } void OtherFieldsWidget::actionDelete() { if (isReadOnly) return; /// never modify anything if in read-only mode Q_ASSERT_X(otherFieldsList->currentItem() != nullptr, "OtherFieldsWidget::actionDelete", "otherFieldsList->currentItem() is NULL"); QString key = otherFieldsList->currentItem()->text(0); if (!deletedKeys.contains(key)) deletedKeys << key; internalEntry->remove(key); updateList(); updateGUI(); listCurrentChanged(otherFieldsList->currentItem(), nullptr); gotModified(); } void OtherFieldsWidget::actionOpen() { if (currentUrl.isValid()) { /// Guess mime type for url to open QMimeType mimeType = FileInfo::mimeTypeForUrl(currentUrl); const QString mimeTypeName = mimeType.name(); /// Ask KDE subsystem to open url in viewer matching mime type #if KIO_VERSION < 0x051f00 // < 5.31.0 KRun::runUrl(currentUrl, mimeTypeName, this, false, false); #else // KIO_VERSION < 0x051f00 // >= 5.31.0 KRun::runUrl(currentUrl, mimeTypeName, this, KRun::RunFlags()); #endif // KIO_VERSION < 0x051f00 } } void OtherFieldsWidget::createGUI() { QGridLayout *layout = new QGridLayout(this); /// set row and column stretches based on chosen layout layout->setColumnStretch(0, 0); layout->setColumnStretch(1, 1); layout->setColumnStretch(2, 0); layout->setRowStretch(0, 0); layout->setRowStretch(1, 1); layout->setRowStretch(2, 0); layout->setRowStretch(3, 0); layout->setRowStretch(4, 1); QLabel *label = new QLabel(i18n("Name:"), this); layout->addWidget(label, 0, 0, 1, 1); label->setAlignment(static_cast(label->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment))); - fieldName = new KLineEdit(this); + fieldName = new QLineEdit(this); layout->addWidget(fieldName, 0, 1, 1, 1); label->setBuddy(fieldName); buttonAddApply = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add"), this); buttonAddApply->setEnabled(false); layout->addWidget(buttonAddApply, 0, 2, 1, 1); label = new QLabel(i18n("Content:"), this); layout->addWidget(label, 1, 0, 1, 1); label->setAlignment(static_cast(label->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment))); fieldContent = new FieldInput(KBibTeX::MultiLine, KBibTeX::tfSource, KBibTeX::tfSource, this); layout->addWidget(fieldContent, 1, 1, 1, 2); label->setBuddy(fieldContent->buddy()); label = new QLabel(i18n("List:"), this); layout->addWidget(label, 2, 0, 1, 1); label->setAlignment(static_cast(label->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment))); otherFieldsList = new QTreeWidget(this); otherFieldsList->setHeaderLabels(QStringList {i18n("Key"), i18n("Value")}); otherFieldsList->setRootIsDecorated(false); layout->addWidget(otherFieldsList, 2, 1, 3, 1); label->setBuddy(otherFieldsList); buttonDelete = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Delete"), this); buttonDelete->setEnabled(false); layout->addWidget(buttonDelete, 2, 2, 1, 1); buttonOpen = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open"), this); buttonOpen->setEnabled(false); layout->addWidget(buttonOpen, 3, 2, 1, 1); connect(otherFieldsList, &QTreeWidget::itemActivated, this, &OtherFieldsWidget::listElementExecuted); connect(otherFieldsList, &QTreeWidget::currentItemChanged, this, &OtherFieldsWidget::listCurrentChanged); connect(otherFieldsList, &QTreeWidget::itemSelectionChanged, this, &OtherFieldsWidget::updateGUI); - connect(fieldName, &KLineEdit::textEdited, this, &OtherFieldsWidget::updateGUI); + connect(fieldName, &QLineEdit::textEdited, this, &OtherFieldsWidget::updateGUI); connect(buttonAddApply, &QPushButton::clicked, this, &OtherFieldsWidget::actionAddApply); connect(buttonDelete, &QPushButton::clicked, this, &OtherFieldsWidget::actionDelete); connect(buttonOpen, &QPushButton::clicked, this, &OtherFieldsWidget::actionOpen); } void OtherFieldsWidget::updateList() { const QString selText = otherFieldsList->selectedItems().isEmpty() ? QString() : otherFieldsList->selectedItems().first()->text(0); const QString curText = otherFieldsList->currentItem() == nullptr ? QString() : otherFieldsList->currentItem()->text(0); otherFieldsList->clear(); for (Entry::ConstIterator it = internalEntry->constBegin(); it != internalEntry->constEnd(); ++it) if (!blackListed.contains(it.key().toLower())) { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, it.key()); item->setText(1, PlainTextValue::text(it.value())); item->setIcon(0, QIcon::fromTheme(QStringLiteral("entry"))); // FIXME otherFieldsList->addTopLevelItem(item); item->setSelected(selText == it.key()); if (it.key() == curText) otherFieldsList->setCurrentItem(item); } } void OtherFieldsWidget::updateGUI() { QString key = fieldName->text(); if (key.isEmpty() || blackListed.contains(key, Qt::CaseInsensitive)) // TODO check for more (e.g. spaces) buttonAddApply->setEnabled(false); else { buttonAddApply->setEnabled(!isReadOnly); buttonAddApply->setText(internalEntry->contains(key) ? i18n("Apply") : i18n("Add")); buttonAddApply->setIcon(internalEntry->contains(key) ? QIcon::fromTheme(QStringLiteral("document-edit")) : QIcon::fromTheme(QStringLiteral("list-add"))); } } MacroWidget::MacroWidget(QWidget *parent) : ElementWidget(parent) { createGUI(); } MacroWidget::~MacroWidget() { delete fieldInputValue; } bool MacroWidget::apply(QSharedPointer element) const { if (isReadOnly) return false; /// never save data if in read-only mode QSharedPointer macro = element.dynamicCast(); if (macro.isNull()) return false; Value value; bool result = fieldInputValue->apply(value); macro->setValue(value); return result; } bool MacroWidget::reset(QSharedPointer element) { QSharedPointer macro = element.dynamicCast(); if (macro.isNull()) return false; return fieldInputValue->reset(macro->value()); } bool MacroWidget::validate(QWidget **widgetWithIssue, QString &message) const { return fieldInputValue->validate(widgetWithIssue, message); } void MacroWidget::setReadOnly(bool isReadOnly) { ElementWidget::setReadOnly(isReadOnly); fieldInputValue->setReadOnly(isReadOnly); } QString MacroWidget::label() { return i18n("Macro"); } QIcon MacroWidget::icon() { return QIcon::fromTheme(QStringLiteral("macro")); } bool MacroWidget::canEdit(const Element *element) { return Macro::isMacro(*element); } void MacroWidget::createGUI() { QBoxLayout *layout = new QHBoxLayout(this); QLabel *label = new QLabel(i18n("Value:"), this); layout->addWidget(label, 0); label->setAlignment(static_cast(label->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment))); fieldInputValue = new FieldInput(KBibTeX::MultiLine, KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfSource, this); layout->addWidget(fieldInputValue, 1); label->setBuddy(fieldInputValue->buddy()); connect(fieldInputValue, &FieldInput::modified, this, &MacroWidget::gotModified); } PreambleWidget::PreambleWidget(QWidget *parent) : ElementWidget(parent) { createGUI(); } bool PreambleWidget::apply(QSharedPointer element) const { if (isReadOnly) return false; /// never save data if in read-only mode QSharedPointer preamble = element.dynamicCast(); if (preamble.isNull()) return false; Value value; bool result = fieldInputValue->apply(value); preamble->setValue(value); return result; } bool PreambleWidget::reset(QSharedPointer element) { QSharedPointer preamble = element.dynamicCast(); if (preamble.isNull()) return false; return fieldInputValue->reset(preamble->value()); } bool PreambleWidget::validate(QWidget **widgetWithIssue, QString &message) const { return fieldInputValue->validate(widgetWithIssue, message); } void PreambleWidget::setReadOnly(bool isReadOnly) { ElementWidget::setReadOnly(isReadOnly); fieldInputValue->setReadOnly(isReadOnly); } QString PreambleWidget::label() { return i18n("Preamble"); } QIcon PreambleWidget::icon() { return QIcon::fromTheme(QStringLiteral("preamble")); } bool PreambleWidget::canEdit(const Element *element) { return Preamble::isPreamble(*element); } void PreambleWidget::createGUI() { QBoxLayout *layout = new QHBoxLayout(this); QLabel *label = new QLabel(i18n("Value:"), this); layout->addWidget(label, 0); label->setAlignment(static_cast(label->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment))); fieldInputValue = new FieldInput(KBibTeX::MultiLine, KBibTeX::tfSource, KBibTeX::tfSource, this); // FIXME: other editing modes beyond Source applicable? layout->addWidget(fieldInputValue, 1); label->setBuddy(fieldInputValue->buddy()); connect(fieldInputValue, &FieldInput::modified, this, &PreambleWidget::gotModified); } class SourceWidget::Private { public: - KComboBox *messages; + QComboBox *messages; QPushButton *buttonRestore; FileImporterBibTeX *importerBibTeX; DelayedExecutionTimer *delayedExecutionTimer; Private(SourceWidget *parent) : messages(nullptr), buttonRestore(nullptr), importerBibTeX(new FileImporterBibTeX(parent)), delayedExecutionTimer(new DelayedExecutionTimer(1500, 500, parent)) { /// nothing } void addMessage(const FileImporter::MessageSeverity severity, const QString &messageText) { const QIcon icon = severity == FileImporter::SeverityInfo ? QIcon::fromTheme(QStringLiteral("dialog-information")) : (severity == FileImporter::SeverityWarning ? QIcon::fromTheme(QStringLiteral("dialog-warning")) : (severity == FileImporter::SeverityError ? QIcon::fromTheme(QStringLiteral("dialog-error")) : QIcon::fromTheme(QStringLiteral("dialog-question")))); messages->addItem(icon, messageText); } }; SourceWidget::SourceWidget(QWidget *parent) : ElementWidget(parent), elementClass(elementInvalid), d(new SourceWidget::Private(this)) { createGUI(); connect(document, &KTextEditor::Document::textChanged, d->delayedExecutionTimer, &DelayedExecutionTimer::trigger); - connect(document, &KTextEditor::Document::textChanged, d->messages, &KComboBox::clear); + connect(document, &KTextEditor::Document::textChanged, d->messages, &QComboBox::clear); connect(d->delayedExecutionTimer, &DelayedExecutionTimer::triggered, this, &SourceWidget::updateMessage); } SourceWidget::~SourceWidget() { delete document; delete d; } void SourceWidget::setElementClass(ElementClass elementClass) { this->elementClass = elementClass; updateMessage(); } bool SourceWidget::apply(QSharedPointer element) const { if (isReadOnly) return false; ///< never save data if in read-only mode const QString text = document->text(); const QScopedPointer file(d->importerBibTeX->fromString(text)); if (file.isNull() || file->count() != 1) return false; QSharedPointer entry = element.dynamicCast(); QSharedPointer readEntry = file->first().dynamicCast(); if (!readEntry.isNull() && !entry.isNull()) { if (elementClass != elementEntry) return false; ///< Source widget should only edit Entry objects entry->operator =(*readEntry.data()); //entry = readEntry; return true; } else { QSharedPointer macro = element.dynamicCast(); QSharedPointer readMacro = file->first().dynamicCast(); if (!readMacro.isNull() && !macro.isNull()) { if (elementClass != elementMacro) return false; ///< Source widget should only edit Macro objects macro->operator =(*readMacro.data()); return true; } else { QSharedPointer preamble = element.dynamicCast(); QSharedPointer readPreamble = file->first().dynamicCast(); if (!readPreamble.isNull() && !preamble.isNull()) { if (elementClass != elementPreamble) return false; ///< Source widget should only edit Preamble objects preamble->operator =(*readPreamble.data()); return true; } else { qCWarning(LOG_KBIBTEX_GUI) << "Do not know how to apply source code"; return false; } } } } bool SourceWidget::reset(QSharedPointer element) { /// if signals are not deactivated, the "modified" signal would be emitted when /// resetting the widget's value disconnect(document, &KTextEditor::Document::textChanged, this, &SourceWidget::gotModified); FileExporterBibTeX exporter(this); exporter.setEncoding(QStringLiteral("utf-8")); const QString exportedText = exporter.toString(element, m_file); if (!exportedText.isEmpty()) { originalText = exportedText; /// Limitation of KTextEditor: If editor is read-only, no text can be set /// Therefore, temporarily lift read-only status const bool originalReadWriteStatus = document->isReadWrite(); document->setReadWrite(true); const bool settingTextSuccessful = document->setText(originalText); if (!settingTextSuccessful) qCWarning(LOG_KBIBTEX_GUI) << "Could not set BibTeX source code to source editor"; document->setReadWrite(originalReadWriteStatus); } else qCWarning(LOG_KBIBTEX_GUI) << "Converting entry to BibTeX source resulting in empty text"; connect(document, &KTextEditor::Document::textChanged, this, &SourceWidget::gotModified); return !exportedText.isEmpty(); } bool SourceWidget::validate(QWidget **widgetWithIssue, QString &message) const { message.clear(); d->messages->clear(); const QString text = document->text(); connect(d->importerBibTeX, &FileImporterBibTeX::message, this, &SourceWidget::addMessage); const QScopedPointer file(d->importerBibTeX->fromString(text)); disconnect(d->importerBibTeX, &FileImporterBibTeX::message, this, &SourceWidget::addMessage); if (file.isNull() || file->count() != 1) { if (widgetWithIssue != nullptr) *widgetWithIssue = document->views().at(0); ///< We create one view initially, so this should never fail message = i18n("Given source code does not parse as one single BibTeX element."); return false; } bool result = false; switch (elementClass) { case elementEntry: { QSharedPointer entry = file->first().dynamicCast(); result = !entry.isNull(); if (!result) message = i18n("Given source code does not parse as one single BibTeX entry."); } break; case elementMacro: { QSharedPointer macro = file->first().dynamicCast(); result = !macro.isNull(); if (!result) message = i18n("Given source code does not parse as one single BibTeX macro."); } break; case elementPreamble: { QSharedPointer preamble = file->first().dynamicCast(); result = !preamble.isNull(); if (!result) message = i18n("Given source code does not parse as one single BibTeX preamble."); } break; // case elementComment // TODO? default: message = QString(QStringLiteral("elementClass is unknown: %1")).arg(elementClass); result = false; } if (!result && widgetWithIssue != nullptr) *widgetWithIssue = document->views().at(0); ///< We create one view initially, so this should never fail if (message.isEmpty() && d->messages->count() == 0) d->addMessage(FileImporter::SeverityInfo, i18n("No issues detected")); return result; } void SourceWidget::setReadOnly(bool isReadOnly) { ElementWidget::setReadOnly(isReadOnly); d->buttonRestore->setEnabled(!isReadOnly); document->setReadWrite(!isReadOnly); } QString SourceWidget::label() { return i18n("Source"); } QIcon SourceWidget::icon() { return QIcon::fromTheme(QStringLiteral("code-context")); } bool SourceWidget::canEdit(const Element *element) { Q_UNUSED(element) return true; /// source widget should be able to edit any element } void SourceWidget::createGUI() { QGridLayout *layout = new QGridLayout(this); layout->setColumnStretch(0, 1); layout->setColumnStretch(1, 0); layout->setRowStretch(0, 1); layout->setRowStretch(1, 0); KTextEditor::Editor *editor = KTextEditor::Editor::instance(); document = editor->createDocument(this); document->setHighlightingMode(QStringLiteral("BibTeX")); KTextEditor::View *view = document->createView(this); layout->addWidget(view, 0, 0, 1, 2); - d->messages = new KComboBox(this); + d->messages = new QComboBox(this); layout->addWidget(d->messages, 1, 0, 1, 1); d->buttonRestore = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Restore"), this); layout->addWidget(d->buttonRestore, 1, 1, 1, 1); connect(d->buttonRestore, &QPushButton::clicked, this, static_cast(&SourceWidget::reset)); connect(document, &KTextEditor::Document::textChanged, this, &SourceWidget::gotModified); } void SourceWidget::reset() { /// if signals are not deactivated, the "modified" signal would be emitted when /// resetting the widget's value disconnect(document, &KTextEditor::Document::textChanged, this, &SourceWidget::gotModified); document->setText(originalText); setModified(false); connect(document, &KTextEditor::Document::textChanged, this, &SourceWidget::gotModified); } void SourceWidget::addMessage(const FileImporter::MessageSeverity severity, const QString &messageText) { d->addMessage(severity, messageText); } void SourceWidget::updateMessage() { QString message; const bool validationResult = validate(nullptr, message); if (!message.isEmpty()) { if (validationResult) addMessage(FileImporter::SeverityInfo, message); else addMessage(FileImporter::SeverityError, message); } } #include "elementwidgets.moc" diff --git a/src/gui/element/elementwidgets.h b/src/gui/element/elementwidgets.h index 2c6478c9..4517003e 100644 --- a/src/gui/element/elementwidgets.h +++ b/src/gui/element/elementwidgets.h @@ -1,337 +1,336 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #ifndef KBIBTEX_GUI_ELEMENTWIDGETS_H #define KBIBTEX_GUI_ELEMENTWIDGETS_H #include "kbibtexgui_export.h" #include #include #include #include #include "elementeditor.h" #include "entrylayout.h" #include "fileimporter.h" class QTreeWidget; class QTreeWidgetItem; class QGridLayout; class QPushButton; - -class KLineEdit; -class KComboBox; +class QLineEdit; +class QComboBox; namespace KTextEditor { class Document; } class File; class Entry; class Element; class FieldInput; class ElementWidget : public QWidget { Q_OBJECT public: explicit ElementWidget(QWidget *parent); virtual bool apply(QSharedPointer element) const = 0; virtual bool reset(QSharedPointer element) = 0; virtual bool validate(QWidget **widgetWithIssue, QString &message) const = 0; virtual void setReadOnly(bool isReadOnly) { this->isReadOnly = isReadOnly; } virtual void showReqOptWidgets(bool, const QString &) = 0; virtual QString label() = 0; virtual QIcon icon() = 0; bool isModified() const; void setModified(bool); virtual void setFile(const File *file) { m_file = file; } virtual bool canEdit(const Element *) = 0; protected: bool isReadOnly; const File *m_file; protected slots: void gotModified(); private: bool m_isModified; signals: void modified(bool); }; class EntryConfiguredWidget : public ElementWidget { Q_OBJECT private: typedef struct { QLabel *label; FieldInput *fieldInput; bool isVerticallyMinimumExpaning; } LabeledFieldInput; LabeledFieldInput **listOfLabeledFieldInput; const int fieldInputCount, numCols; QGridLayout *gridLayout; const QSharedPointer etl; QMap bibtexKeyToWidget; void createGUI(); void layoutGUI(bool forceVisible, const QString &entryType = QString()); public: EntryConfiguredWidget(const QSharedPointer &entryTabLayout, QWidget *parent); ~EntryConfiguredWidget() override; bool apply(QSharedPointer element) const override; bool reset(QSharedPointer element) override; bool validate(QWidget **widgetWithIssue, QString &message) const override; void setReadOnly(bool isReadOnly) override; void showReqOptWidgets(bool forceVisible, const QString &entryType) override; QString label() override; QIcon icon() override; void setFile(const File *file) override; bool canEdit(const Element *element) override; }; class ReferenceWidget : public ElementWidget { Q_OBJECT private: - KComboBox *entryType; - KLineEdit *entryId; + QComboBox *entryType; + QLineEdit *entryId; QPushButton *buttonSuggestId; void createGUI(); public: explicit ReferenceWidget(QWidget *parent); bool apply(QSharedPointer element) const override; bool reset(QSharedPointer element) override; bool validate(QWidget **widgetWithIssue, QString &message) const override; void setReadOnly(bool isReadOnly) override; void showReqOptWidgets(bool, const QString &) override {} void setApplyElementInterface(ElementEditor::ApplyElementInterface *applyElement) { m_applyElement = applyElement; } void setOriginalElement(const QSharedPointer &orig); /** * Return the current value of the entry id/macro key editing widget. * * @return Current value of entry id/macro key if any, otherwise empty string */ QString currentId() const; void setCurrentId(const QString &newId); QString label() override; QIcon icon() override; bool canEdit(const Element *element) override; public slots: void setEntryIdByDefault(); private: ElementEditor::ApplyElementInterface *m_applyElement; bool m_entryIdManuallySet; QSharedPointer m_element; QString computeType() const; private slots: void prepareSuggestionsMenu(); void insertSuggestionFromAction(); void entryIdManuallyChanged(); signals: void entryTypeChanged(); }; class FilesWidget : public ElementWidget { Q_OBJECT private: FieldInput *fileList; public: explicit FilesWidget(QWidget *parent); bool apply(QSharedPointer element) const override; bool reset(QSharedPointer element) override; bool validate(QWidget **widgetWithIssue, QString &message) const override; void setReadOnly(bool isReadOnly) override; void showReqOptWidgets(bool, const QString &) override {} QString label() override; QIcon icon() override; void setFile(const File *file) override; bool canEdit(const Element *element) override; private: static const QStringList keyStart; }; class OtherFieldsWidget : public ElementWidget { Q_OBJECT private: - KLineEdit *fieldName; + QLineEdit *fieldName; FieldInput *fieldContent; QTreeWidget *otherFieldsList; QPushButton *buttonDelete; QPushButton *buttonOpen; QPushButton *buttonAddApply; QUrl currentUrl; const QStringList blackListed; QSharedPointer internalEntry; QStringList deletedKeys, modifiedKeys; bool m_isReadOnly; void createGUI(); void updateList(); public: OtherFieldsWidget(const QStringList &blacklistedFields, QWidget *parent); ~OtherFieldsWidget() override; bool apply(QSharedPointer element) const override; bool reset(QSharedPointer element) override; bool validate(QWidget **widgetWithIssue, QString &message) const override; void setReadOnly(bool isReadOnly) override; void showReqOptWidgets(bool, const QString &) override {} QString label() override; QIcon icon() override; bool canEdit(const Element *element) override; private slots: void listElementExecuted(QTreeWidgetItem *item, int column); void listCurrentChanged(QTreeWidgetItem *item, QTreeWidgetItem *previous); void actionAddApply(); void actionDelete(); void actionOpen(); void updateGUI(); }; class MacroWidget : public ElementWidget { Q_OBJECT private: FieldInput *fieldInputValue; void createGUI(); public: explicit MacroWidget(QWidget *parent); ~MacroWidget() override; bool apply(QSharedPointer element) const override; bool reset(QSharedPointer element) override; bool validate(QWidget **widgetWithIssue, QString &message) const override; void setReadOnly(bool isReadOnly) override; void showReqOptWidgets(bool, const QString &) override {} QString label() override; QIcon icon() override; bool canEdit(const Element *element) override; }; class PreambleWidget : public ElementWidget { Q_OBJECT private: FieldInput *fieldInputValue; void createGUI(); public: explicit PreambleWidget(QWidget *parent); bool apply(QSharedPointer element) const override; bool reset(QSharedPointer element) override; bool validate(QWidget **widgetWithIssue, QString &message) const override; void setReadOnly(bool isReadOnly) override; void showReqOptWidgets(bool, const QString &) override {} QString label() override; QIcon icon() override; bool canEdit(const Element *element) override; }; class SourceWidget : public ElementWidget { Q_OBJECT public: enum ElementClass { elementInvalid = -1, elementEntry = 0, elementMacro, elementPreamble, elementComment }; private: KTextEditor::Document *document; QString originalText; ElementClass elementClass; void createGUI(); public: explicit SourceWidget(QWidget *parent); ~SourceWidget() override; void setElementClass(ElementClass elementClass); bool apply(QSharedPointer element) const override; bool reset(QSharedPointer element) override; bool validate(QWidget **widgetWithIssue, QString &message) const override; void setReadOnly(bool isReadOnly) override; void showReqOptWidgets(bool, const QString &) override {} QString label() override; QIcon icon() override; bool canEdit(const Element *element) override; private slots: void reset(); void addMessage(const FileImporter::MessageSeverity severity, const QString &messageText); void updateMessage(); private: class Private; Private *const d; }; #endif // KBIBTEX_GUI_ELEMENTWIDGETS_H diff --git a/src/gui/field/colorlabelwidget.cpp b/src/gui/field/colorlabelwidget.cpp index a82315f7..33640ab5 100644 --- a/src/gui/field/colorlabelwidget.cpp +++ b/src/gui/field/colorlabelwidget.cpp @@ -1,215 +1,215 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "colorlabelwidget.h" #include #include #include #include #include #include "notificationhub.h" #include "preferences.h" class ColorLabelComboBoxModel : public QAbstractItemModel, private NotificationListener { Q_OBJECT public: enum ColorLabelComboBoxModelRole { /// Color of a color-label pair ColorRole = Qt::UserRole + 1721 }; struct ColorLabelPair { QColor color; QString label; }; QList colorLabelPairs; QColor userColor; ColorLabelComboBoxModel(QObject *p = nullptr) : QAbstractItemModel(p), userColor(Qt::black) { NotificationHub::registerNotificationListener(this, NotificationHub::EventConfigurationChanged); } void notificationEvent(int eventId) override { if (eventId == NotificationHub::EventConfigurationChanged) { beginResetModel(); /// New data will be pulled automatically via Preferences::instance().colorCodes() endResetModel(); } } QModelIndex index(int row, int column, const QModelIndex &) const override { return createIndex(row, column); } QModelIndex parent(const QModelIndex & = QModelIndex()) const override { return QModelIndex(); } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return parent == QModelIndex() ? 2 + Preferences::instance().colorCodes().count() : 0; } int columnCount(const QModelIndex & = QModelIndex()) const override { return 1; } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (role == ColorRole) { if (index.row() == 0) return QColor(Qt::black); else if (index.row() == rowCount() - 1) return userColor; else return Preferences::instance().colorCodes().at(index.row() - 1).first; } else if (role == Qt::FontRole && (index.row() == 0 || index.row() == rowCount() - 1)) { QFont font; font.setItalic(true); return font; } else if (role == Qt::DecorationRole && index.row() > 0 && (index.row() < rowCount() - 1 || userColor != Qt::black)) { QColor color = data(index, ColorRole).value(); return ColorLabelWidget::createSolidIcon(color); } else if (role == Qt::DisplayRole) if (index.row() == 0) return i18n("No color"); else if (index.row() == rowCount() - 1) return i18n("User-defined color"); else return Preferences::instance().colorCodes().at(index.row() - 1).second; else return QVariant(); } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { if (section != 0 || orientation != Qt::Horizontal || role != Qt::DisplayRole) return QVariant(); return i18n("Color & Label"); } void setColor(const QColor &newColor) { userColor = newColor; const QModelIndex idx = index(rowCount() - 1, 0, QModelIndex()); emit dataChanged(idx, idx); } }; class ColorLabelWidget::ColorLabelWidgetPrivate { public: ColorLabelComboBoxModel *model; ColorLabelWidgetPrivate(ColorLabelWidget *parent, ColorLabelComboBoxModel *m) : model(m) { Q_UNUSED(parent) } }; ColorLabelWidget::ColorLabelWidget(QWidget *parent) - : KComboBox(false, parent), d(new ColorLabelWidgetPrivate(this, new ColorLabelComboBoxModel(this))) + : QComboBox(parent), d(new ColorLabelWidgetPrivate(this, new ColorLabelComboBoxModel(this))) { setModel(d->model); connect(this, static_cast(&QComboBox::currentIndexChanged), this, &ColorLabelWidget::slotCurrentIndexChanged); } ColorLabelWidget::~ColorLabelWidget() { delete d; } void ColorLabelWidget::clear() { setCurrentIndex(0); } bool ColorLabelWidget::reset(const Value &value) { /// Avoid triggering signal when current index is set by the program disconnect(this, static_cast(&QComboBox::currentIndexChanged), this, &ColorLabelWidget::slotCurrentIndexChanged); QSharedPointer verbatimText; if (value.count() == 1 && !(verbatimText = value.first().dynamicCast()).isNull()) { int i = 0; const QColor color = QColor(verbatimText->text()); for (; i < d->model->rowCount(); ++i) if (d->model->data(d->model->index(i, 0, QModelIndex()), ColorLabelComboBoxModel::ColorRole).value() == color) break; if (i >= d->model->rowCount()) { d->model->userColor = color; i = d->model->rowCount() - 1; } setCurrentIndex(i); } else setCurrentIndex(0); /// Re-enable triggering signal after setting current index connect(this, static_cast(&QComboBox::currentIndexChanged), this, &ColorLabelWidget::slotCurrentIndexChanged); return true; } bool ColorLabelWidget::apply(Value &value) const { QColor color = d->model->data(d->model->index(currentIndex(), 0, QModelIndex()), ColorLabelComboBoxModel::ColorRole).value(); value.clear(); if (color != Qt::black) value.append(QSharedPointer(new VerbatimText(color.name()))); return true; } bool ColorLabelWidget::validate(QWidget **, QString &) const { return true; } void ColorLabelWidget::setReadOnly(bool isReadOnly) { setEnabled(!isReadOnly); } void ColorLabelWidget::slotCurrentIndexChanged(int index) { if (index == count() - 1) { QColor dialogColor = d->model->userColor; if (QColorDialog::getColor(dialogColor, this) == QColorDialog::Accepted) d->model->setColor(dialogColor); } emit modified(); } QPixmap ColorLabelWidget::createSolidIcon(const QColor &color) { QFontMetrics fm = QFontMetrics(QFont()); int h = fm.height() - 4; QPixmap pm(h, h); QPainter painter(&pm); painter.setPen(color); painter.setBrush(QBrush(color)); painter.drawRect(0, 0, h, h); return pm; } #include "colorlabelwidget.moc" diff --git a/src/gui/field/colorlabelwidget.h b/src/gui/field/colorlabelwidget.h index 53500053..193b094e 100644 --- a/src/gui/field/colorlabelwidget.h +++ b/src/gui/field/colorlabelwidget.h @@ -1,57 +1,57 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #ifndef KBIBTEX_GUI_COLORLABELWIDGET_H #define KBIBTEX_GUI_COLORLABELWIDGET_H -#include +#include #include "kbibtexgui_export.h" #include "value.h" /** * @author Thomas Fischer */ -class KBIBTEXGUI_EXPORT ColorLabelWidget : public KComboBox +class KBIBTEXGUI_EXPORT ColorLabelWidget : public QComboBox { Q_OBJECT public: explicit ColorLabelWidget(QWidget *parent = nullptr); ~ColorLabelWidget() override; void clear(); bool reset(const Value &value); bool apply(Value &value) const; bool validate(QWidget **widgetWithIssue, QString &message) const; void setReadOnly(bool); static QPixmap createSolidIcon(const QColor &color); signals: void modified(); private slots: void slotCurrentIndexChanged(int); private: class ColorLabelWidgetPrivate; ColorLabelWidget::ColorLabelWidgetPrivate *d; }; #endif // KBIBTEX_GUI_COLORLABELWIDGET_H diff --git a/src/gui/file/findduplicatesui.cpp b/src/gui/file/findduplicatesui.cpp index 9dda80af..7520ca92 100644 --- a/src/gui/file/findduplicatesui.cpp +++ b/src/gui/file/findduplicatesui.cpp @@ -1,740 +1,740 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "findduplicatesui.h" #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include "fileimporterbibtex.h" #include "bibtexentries.h" #include "radiobuttontreeview.h" #include "fileview.h" #include "filedelegate.h" #include "models/filemodel.h" #include "findduplicates.h" #include "logging_gui.h" /** * Model to hold alternative values as visualized in the RadioTreeView */ class AlternativesItemModel : public QAbstractItemModel { Q_OBJECT private: /// marker to memorize in an index's internal id that it is a top-level index static const quintptr noParentInternalId; /// parent widget, needed to get font from (for text in italics) QTreeView *p; EntryClique *currentClique; public: enum SelectionType {SelectionTypeNone, SelectionTypeRadio, SelectionTypeCheck}; enum AlternativesItemModelRole { /// Raw, all-lowercase field name FieldNameRole = Qt::UserRole + 101, /// Text used for inline editing of values UserInputRole = Qt::UserRole + 103 }; AlternativesItemModel(QTreeView *parent) : QAbstractItemModel(parent), p(parent), currentClique(nullptr) { /// nothing } static SelectionType selectionType(const QString &fieldName) { if (fieldName.isEmpty()) return SelectionTypeNone; if (fieldName == Entry::ftKeywords || fieldName == Entry::ftUrl) return SelectionTypeCheck; return SelectionTypeRadio; } void setCurrentClique(EntryClique *currentClique) { this->currentClique = currentClique; } QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { if (parent == QModelIndex()) return createIndex(row, column, noParentInternalId); else if (parent.parent() == QModelIndex()) return createIndex(row, column, static_cast(parent.row())); return QModelIndex(); } QModelIndex parent(const QModelIndex &index) const override { if (index.internalId() >= noParentInternalId) return QModelIndex(); else return createIndex(static_cast(index.internalId()), 0, noParentInternalId); } int rowCount(const QModelIndex &parent = QModelIndex()) const override { if (currentClique == nullptr) return 0; if (parent == QModelIndex()) { /// top-level index, check how many lists of lists of alternatives exist return currentClique->fieldCount(); } else if (parent.parent() == QModelIndex()) { /// first, find the map of alternatives for this chosen field name (see parent) QString fieldName = parent.data(FieldNameRole).toString(); const auto alt = currentClique->values(fieldName); /// second, return number of alternatives for list of alternatives /// plus one for an "else" option return alt.count() + (fieldName.startsWith('^') || fieldName == Entry::ftKeywords || fieldName == Entry::ftUrl ? 0 : 1); } return 0; } int columnCount(const QModelIndex &) const override { /// only one column in use return 1; } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { Q_UNUSED(section) Q_UNUSED(orientation) if (role == Qt::DisplayRole) return i18n("Alternatives"); return QVariant(); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (index.parent() == QModelIndex()) { /// top-level elements showing field names like "Title", "Authors", etc const QString fieldName = currentClique->fieldList().at(index.row()).toLower(); switch (role) { case AlternativesItemModelRole::FieldNameRole: /// plain-and-simple field name (all lower case) return fieldName; case Qt::ToolTipRole: case Qt::DisplayRole: /// nicely formatted field names for visual representation if (fieldName == QStringLiteral("^id")) return i18n("Identifier"); else if (fieldName == QStringLiteral("^type")) return i18n("Type"); else return BibTeXEntries::instance().format(fieldName, KBibTeX::cUpperCamelCase); case RadioButtonTreeView::IsRadioRole: /// this is not to be a radio widget return QVariant::fromValue(false); case Qt::FontRole: if (fieldName.startsWith('^')) { QFont f = p->font(); f.setItalic(true); return f; } else return p->font(); } } else if (index.parent().parent() == QModelIndex()) { /// second-level entries for alternatives /// start with determining which list of alternatives actually to use QString fieldName = index.parent().data(FieldNameRole).toString(); const auto &values = currentClique->values(fieldName); switch (role) { case Qt::EditRole: case Qt::ToolTipRole: case Qt::DisplayRole: if (index.row() < values.count()) { QString text = PlainTextValue::text(values.at(index.row())); if (fieldName == QStringLiteral("^type")) text = BibTeXEntries::instance().format(text, KBibTeX::cUpperCamelCase); /// textual representation of the alternative's value return text; } else /// add an "else" option return i18n("None of the above"); case Qt::FontRole: /// for the "else" option, make font italic if (index.row() >= values.count()) { QFont f = p->font(); f.setItalic(true); return f; } else return p->font(); case Qt::CheckStateRole: { if (selectionType(fieldName) != SelectionTypeCheck) return QVariant(); const auto chosenValues = currentClique->chosenValues(fieldName); QString text = PlainTextValue::text(values.at(index.row())); for (const Value &value : chosenValues) { if (PlainTextValue::text(value) == text) return Qt::Checked; } return Qt::Unchecked; } case RadioButtonTreeView::RadioSelectedRole: { if (selectionType(fieldName) != SelectionTypeRadio) return QVariant::fromValue(false); /// return selection status (true or false) for this alternative Value chosen = currentClique->chosenValue(fieldName); if (chosen.isEmpty()) return QVariant::fromValue(index.row() >= values.count()); else if (index.row() < values.count()) { QString chosenPlainText = PlainTextValue::text(chosen); QString rowPlainText = PlainTextValue::text(values[index.row()]); return QVariant::fromValue(chosenPlainText == rowPlainText); } return QVariant::fromValue(false); } case RadioButtonTreeView::IsRadioRole: /// this is to be a radio widget return QVariant::fromValue(selectionType(fieldName) == SelectionTypeRadio); } } return QVariant(); } bool setData(const QModelIndex &index, const QVariant &value, int role = RadioButtonTreeView::RadioSelectedRole) override { if (index.parent() != QModelIndex()) { bool isInt; int checkState = value.toInt(&isInt); const QString fieldName = index.parent().data(FieldNameRole).toString(); auto values = currentClique->values(fieldName); if (role == RadioButtonTreeView::RadioSelectedRole && value.canConvert() && value.toBool() == true && selectionType(fieldName) == SelectionTypeRadio) { /// start with determining which list of alternatives actually to use /// store which alternative was selected if (index.row() < values.count()) currentClique->setChosenValue(fieldName, values[index.row()]); else { Value v; currentClique->setChosenValue(fieldName, v); } /// update view on neighbouring alternatives emit dataChanged(index.sibling(0, 0), index.sibling(rowCount(index.parent()), 0)); return true; } else if (role == Qt::CheckStateRole && isInt && selectionType(fieldName) == SelectionTypeCheck) { if (checkState == Qt::Checked) currentClique->setChosenValue(fieldName, values[index.row()], EntryClique::AddValue); else if (checkState == Qt::Unchecked) currentClique->setChosenValue(fieldName, values[index.row()], EntryClique::RemoveValue); else return false; ///< tertium non datur emit dataChanged(index, index); return true; } else if (role == UserInputRole) { const QString text = value.toString(); if (text.isEmpty()) return false; const Value old = values.at(index.row()); if (old.isEmpty()) return false; Value v; QSharedPointer pt = old.first().dynamicCast<PlainText>(); if (!pt.isNull()) v.append(QSharedPointer<PlainText>(new PlainText(text))); else { QSharedPointer<VerbatimText> vt = old.first().dynamicCast<VerbatimText>(); if (!vt.isNull()) v.append(QSharedPointer<VerbatimText>(new VerbatimText(text))); else { QSharedPointer<MacroKey> mk = old.first().dynamicCast<MacroKey>(); if (!mk.isNull()) v.append(QSharedPointer<MacroKey>(new MacroKey(text))); else { QSharedPointer<Person> ps = old.first().dynamicCast<Person>(); if (!ps.isNull()) FileImporterBibTeX::parsePersonList(text, v); else { QSharedPointer<Keyword> kw = old.first().dynamicCast<Keyword>(); if (!kw.isNull()) { const QList<QSharedPointer<Keyword> > keywordList = FileImporterBibTeX::splitKeywords(text); v.reserve(keywordList.size()); for (const auto &keyword : keywordList) v.append(keyword); } else { qCDebug(LOG_KBIBTEX_GUI) << "Not know how to set this text:" << text; } } } } } if (!v.isEmpty()) { values.removeAt(index.row()); values.insert(index.row(), v); emit dataChanged(index, index); return true; } else return false; } } return false; } bool hasChildren(const QModelIndex &parent = QModelIndex()) const override { /// depth-two tree return parent == QModelIndex() || parent.parent() == QModelIndex(); } Qt::ItemFlags flags(const QModelIndex &index) const override { Qt::ItemFlags f = QAbstractItemModel::flags(index); if (index.parent() != QModelIndex()) { QString fieldName = index.parent().data(FieldNameRole).toString(); if (selectionType(fieldName) == SelectionTypeCheck) f |= Qt::ItemIsUserCheckable; const auto values = currentClique->values(fieldName); if (index.row() < values.count()) f |= Qt::ItemIsEditable; } return f; } }; const quintptr AlternativesItemModel::noParentInternalId = std::numeric_limits<quintptr>::max(); /** * Specialization of RadioButtonItemDelegate which allows to edit * values in a AlternativesItemModel. */ class AlternativesItemDelegate: public RadioButtonItemDelegate { Q_OBJECT public: AlternativesItemDelegate(QObject *p) : RadioButtonItemDelegate(p) { /// nothing } QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const override { if (index.parent() != QModelIndex()) { /// Only second-level indices in the model can be edited /// (those are the actual values). - /// Use a plain, border-less KLineEdit. - KLineEdit *lineEdit = new KLineEdit(parent); + /// Use a plain, border-less QLineEdit. + QLineEdit *lineEdit = new QLineEdit(parent); lineEdit->setStyleSheet(QStringLiteral("border: none;")); return lineEdit; } return nullptr; } void setEditorData(QWidget *editor, const QModelIndex &index) const override { - if (KLineEdit *lineEdit = qobject_cast<KLineEdit *>(editor)) { + if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor)) { /// Set line edit's default value to string fetched from model lineEdit->setText(index.data(Qt::EditRole).toString()); } } void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override { - if (KLineEdit *lineEdit = qobject_cast<KLineEdit *>(editor)) { + if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor)) { /// Set user-entered text to model (and underlying value) model->setData(index, lineEdit->text(), AlternativesItemModel::UserInputRole); /// Ensure that the edited value is checked if it is /// a checkbox-checkable value, or gets a "dot" in its /// radio button if it is radio-checkable. const QString fieldName = index.parent().data(AlternativesItemModel::FieldNameRole).toString(); if (AlternativesItemModel::selectionType(fieldName) == AlternativesItemModel::SelectionTypeCheck) model->setData(index, Qt::Checked, Qt::CheckStateRole); else if (AlternativesItemModel::selectionType(fieldName) == AlternativesItemModel::SelectionTypeRadio) model->setData(index, true, RadioButtonTreeView::RadioSelectedRole); } } void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const override { QRect rect = option.rect; // TODO better placement of editing widget? //int radioButtonWidth = QApplication::style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth, &option); //int spacing = QApplication::style()->pixelMetric(QStyle::PM_RadioButtonLabelSpacing, &option); //rect.setLeft(rect.left() +spacing*3/2 + radioButtonWidth); editor->setGeometry(rect); } }; class CheckableFileModel : public FileModel { Q_OBJECT private: QVector<EntryClique *> cl; int currentClique; QTreeView *tv; public: CheckableFileModel(QVector<EntryClique *> &cliqueList, QTreeView *treeView, QObject *parent = nullptr) : FileModel(parent), cl(cliqueList), currentClique(0), tv(treeView) { /// nothing } void setCurrentClique(EntryClique *currentClique) { this->currentClique = cl.indexOf(currentClique); } QVariant data(const QModelIndex &index, int role) const override { if (role == Qt::CheckStateRole && index.column() == 1) { QSharedPointer<Entry> entry = element(index.row()).dynamicCast<Entry>(); Q_ASSERT_X(!entry.isNull(), "CheckableFileModel::data", "entry is NULL"); if (!entry.isNull()) { QList<QSharedPointer<Entry> > entryList = cl[currentClique]->entryList(); if (entryList.contains(QSharedPointer<Entry>(entry))) // TODO does this work? return cl[currentClique]->isEntryChecked(QSharedPointer<Entry>(entry)) ? Qt::Checked : Qt::Unchecked; } } return FileModel::data(index, role); } bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override { bool ok; int checkState = value.toInt(&ok); Q_ASSERT_X(ok, "CheckableFileModel::setData", QString("Could not convert value " + value.toString()).toLatin1()); if (ok && role == Qt::CheckStateRole && index.column() == 1) { QSharedPointer<Entry> entry = element(index.row()).dynamicCast<Entry>(); if (!entry.isNull()) { QList<QSharedPointer<Entry> > entryList = cl[currentClique]->entryList(); if (entryList.contains(QSharedPointer<Entry>(entry))) { // TODO does this work? EntryClique *ec = cl[currentClique]; ec->setEntryChecked(QSharedPointer<Entry>(entry), checkState == Qt::Checked); cl[currentClique] = ec; emit dataChanged(index, index); tv->reset(); tv->expandAll(); return true; } } } return false; } Qt::ItemFlags flags(const QModelIndex &index) const override { Qt::ItemFlags f = FileModel::flags(index); if (index.column() == 1) f |= Qt::ItemIsUserCheckable; return f; } }; class FilterIdFileModel : public QSortFilterProxyModel { Q_OBJECT private: CheckableFileModel *internalModel; EntryClique *currentClique; public: FilterIdFileModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent), internalModel(nullptr), currentClique(nullptr) { /// nothing } void setCurrentClique(EntryClique *currentClique) { Q_ASSERT_X(internalModel != nullptr, "FilterIdFileModel::setCurrentClique(EntryClique *currentClique)", "internalModel is NULL"); internalModel->setCurrentClique(currentClique); this->currentClique = currentClique; invalidate(); } void setSourceModel(QAbstractItemModel *model) override { QSortFilterProxyModel::setSourceModel(model); internalModel = dynamic_cast<CheckableFileModel *>(model); Q_ASSERT_X(internalModel != nullptr, "FilterIdFileModel::setSourceModel(QAbstractItemModel *model)", "internalModel is NULL"); } bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { Q_UNUSED(source_parent) if (internalModel != nullptr && currentClique != nullptr) { QSharedPointer<Entry> entry = internalModel->element(source_row).dynamicCast<Entry>(); if (!entry.isNull()) { QList<QSharedPointer<Entry> > entryList = currentClique->entryList(); if (entryList.contains(QSharedPointer<Entry>(entry))) return true; // TODO does this work? } } return false; } }; class MergeWidget::MergeWidgetPrivate { private: MergeWidget *p; public: File *file; FileView *editor; QPushButton *buttonNext, *buttonPrev; QLabel *labelWhichClique; static const char *whichCliqueText; CheckableFileModel *model; FilterIdFileModel *filterModel; RadioButtonTreeView *alternativesView; AlternativesItemModel *alternativesItemModel; AlternativesItemDelegate *alternativesItemDelegate; int currentClique; QVector<EntryClique *> &cl; MergeWidgetPrivate(MergeWidget *parent, File *bibTeXFile, QVector<EntryClique *> &cliqueList) : p(parent), file(bibTeXFile), currentClique(0), cl(cliqueList) { setupGUI(); } void setupGUI() { p->setMinimumSize(p->fontMetrics().xHeight() * 96, p->fontMetrics().xHeight() * 64); p->setBaseSize(p->fontMetrics().xHeight() * 128, p->fontMetrics().xHeight() * 96); QBoxLayout *layout = new QVBoxLayout(p); QLabel *label = new QLabel(i18n("Select your duplicates"), p); layout->addWidget(label); QSplitter *splitter = new QSplitter(Qt::Vertical, p); layout->addWidget(splitter); editor = new FileView(QStringLiteral("MergeWidget"), splitter); editor->setItemDelegate(new FileDelegate(editor)); editor->setReadOnly(true); alternativesView = new RadioButtonTreeView(splitter); model = new CheckableFileModel(cl, alternativesView, p); model->setBibliographyFile(file); QBoxLayout *containerLayout = new QHBoxLayout(); layout->addLayout(containerLayout); containerLayout->addStretch(10); labelWhichClique = new QLabel(p); containerLayout->addWidget(labelWhichClique); buttonPrev = new QPushButton(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Previous Clique"), p); containerLayout->addWidget(buttonPrev, 1); buttonNext = new QPushButton(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Next Clique"), p); containerLayout->addWidget(buttonNext, 1); filterModel = new FilterIdFileModel(p); filterModel->setSourceModel(model); alternativesItemModel = new AlternativesItemModel(alternativesView); alternativesItemDelegate = new AlternativesItemDelegate(alternativesView); showCurrentClique(); connect(buttonPrev, &QPushButton::clicked, p, &MergeWidget::previousClique); connect(buttonNext, &QPushButton::clicked, p, &MergeWidget::nextClique); connect(editor, &FileView::doubleClicked, editor, &FileView::viewCurrentElement); } void showCurrentClique() { EntryClique *ec = cl[currentClique]; filterModel->setCurrentClique(ec); alternativesItemModel->setCurrentClique(ec); editor->setModel(filterModel); alternativesView->setModel(alternativesItemModel); alternativesView->setItemDelegate(alternativesItemDelegate); editor->reset(); alternativesView->reset(); alternativesView->expandAll(); buttonNext->setEnabled(currentClique >= 0 && currentClique < cl.count() - 1); buttonPrev->setEnabled(currentClique > 0); labelWhichClique->setText(i18n(whichCliqueText, currentClique + 1, cl.count())); } }; const char *MergeWidget::MergeWidgetPrivate::whichCliqueText = "Showing clique %1 of %2."; MergeWidget::MergeWidget(File *file, QVector<EntryClique *> &cliqueList, QWidget *parent) : QWidget(parent), d(new MergeWidgetPrivate(this, file, cliqueList)) { /// nothing } MergeWidget::~MergeWidget() { delete d; } void MergeWidget::previousClique() { if (d->currentClique > 0) { --d->currentClique; d->showCurrentClique(); } } void MergeWidget::nextClique() { if (d->currentClique >= 0 && d->currentClique < d->cl.count() - 1) { ++d->currentClique; d->showCurrentClique(); } } class FindDuplicatesUI::FindDuplicatesUIPrivate { public: KParts::Part *part; FileView *view; FindDuplicatesUIPrivate(FindDuplicatesUI *parent, KParts::Part *kpart, FileView *fileView) : part(kpart), view(fileView) { Q_UNUSED(parent) } }; FindDuplicatesUI::FindDuplicatesUI(KParts::Part *part, FileView *fileView) : QObject(), d(new FindDuplicatesUIPrivate(this, part, fileView)) { QAction *newAction = new QAction(QIcon::fromTheme(QStringLiteral("tab-duplicate")), i18n("Find Duplicates"), this); part->actionCollection()->addAction(QStringLiteral("findduplicates"), newAction); connect(newAction, &QAction::triggered, this, &FindDuplicatesUI::startDuplicatesSearch); } FindDuplicatesUI::~FindDuplicatesUI() { delete d; } void FindDuplicatesUI::startDuplicatesSearch() { // FIXME move to settings //bool ok = false; //int sensitivity = KInputDialog::getInteger(i18n("Sensitivity"), i18n("Enter a value for sensitivity.\n\nLow values (close to 0) require very similar entries for duplicate detection, larger values (close to 10000) are more likely to count entries as duplicates.\n\nPlease provide feedback to the developers if you have a suggestion for a better default value than 4000."), 4000, 0, 10000, 10, &ok, d->part->widget()); //if (!ok) sensitivity = 4000; int sensitivity = 4000; FileModel *model = d->view->fileModel(); if (model == nullptr) return; /// Full file, used to remove merged elements from /// Stays the same even when merging is restricted to selected elements File *originalFile = model->bibliographyFile(); /// File to be used to find duplicate in, /// may be only a subset of the original one if selection is used File *workingSetFile = originalFile; /// If more than one element but not all are selected in the main list view, /// ask the user if duplicates are to be searched only within the selection const int selectedRowsCount = d->view->selectedElements().count(); if (selectedRowsCount > 1 && selectedRowsCount < d->view->sortFilterProxyModel()->sourceModel()->rowCount() && KMessageBox::questionYesNo(d->part->widget(), i18n("Multiple elements are selected. Do you want to search for duplicates only within the selection or in the whole document?"), i18n("Search only in selection?"), KGuiItem(i18n("Only in selection")), KGuiItem(i18n("Whole document"))) == KMessageBox::Yes) { /// Yes, do only search for duplicates within selection, so copy all /// selected elements to a temporary new File object workingSetFile = new File(); const QModelIndexList mil = d->view->selectionModel()->selectedRows(); for (const QModelIndex &index : mil) workingSetFile->append(model->element(d->view->sortFilterProxyModel()->mapToSource(index).row())); } /// Actual duplicate finder, can be given a widget that will be the /// parent of a progress bar window and sensitivity value when to /// recognize two entries as being duplicates of each other FindDuplicates fd(d->part->widget(), sensitivity); QVector<EntryClique *> cliques; bool gotCanceled = fd.findDuplicateEntries(workingSetFile, cliques); if (gotCanceled) { /// Duplicate search was cancelled, e.g. by pressing the Cancel /// button on the progress bar window if (workingSetFile != originalFile) delete workingSetFile; return; } if (cliques.isEmpty()) KMessageBox::information(d->part->widget(), i18n("No duplicates were found."), i18n("No duplicates found")); else { /// Duplicates have been found, so let user choose how to handle duplicate fields /// Why is a QPointer used here you may wonder? Check here in case the link still works: /// https://blogs.kde.org/2009/03/26/how-crash-almost-every-qtkde-application-and-how-fix-it-0 QPointer<QDialog> dlg = new QDialog(d->part->widget()); dlg->setWindowTitle(i18n("Merge Duplicates")); MergeWidget *mw = new MergeWidget(workingSetFile, cliques, dlg); mw->layout()->setMargin(0); QBoxLayout *layout = new QVBoxLayout(dlg); layout->addWidget(mw); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, dlg); layout->addWidget(buttonBox); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, dlg.data(), &QDialog::accept); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, dlg.data(), &QDialog::reject); if (dlg->exec() == QDialog::Accepted && MergeDuplicates::mergeDuplicateEntries(cliques, d->view->fileModel())) { /// If the user made selections on what to merge how /// AND the requested changes could be applied on the /// 'original' file (not the working set file), then /// notify the world that things have changed d->view->externalModification(); } /// Clean memory while (!cliques.isEmpty()) { EntryClique *ec = cliques.first(); cliques.removeFirst(); delete ec; } delete dlg; } if (workingSetFile != originalFile) delete workingSetFile; } #include "findduplicatesui.moc" diff --git a/src/gui/preferences/kbibtexpreferencesdialog.cpp b/src/gui/preferences/kbibtexpreferencesdialog.cpp index 0f18fd54..fdfa840f 100644 --- a/src/gui/preferences/kbibtexpreferencesdialog.cpp +++ b/src/gui/preferences/kbibtexpreferencesdialog.cpp @@ -1,207 +1,207 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "kbibtexpreferencesdialog.h" #include <QSet> #include <QFileInfo> #include <QDialogButtonBox> #include <QPushButton> +#include <QComboBox> #include <KLocalizedString> -#include <KComboBox> #include <KMessageBox> #include <KGuiItem> #include <QStandardPaths> #include "notificationhub.h" #include "settingsgeneralwidget.h" #include "settingsglobalkeywordswidget.h" #include "settingsfileexporterpdfpswidget.h" #include "settingsfileexporterwidget.h" #include "settingscolorlabelwidget.h" #include "settingsuserinterfacewidget.h" #include "settingsidsuggestionswidget.h" #include "logging_gui.h" class KBibTeXPreferencesDialog::KBibTeXPreferencesDialogPrivate { private: KBibTeXPreferencesDialog *p; QSet<SettingsAbstractWidget *> settingWidgets; public: bool notifyOfChanges; KBibTeXPreferencesDialogPrivate(KBibTeXPreferencesDialog *parent) : p(parent) { notifyOfChanges = false; } void addPages() { SettingsAbstractWidget *settingsWidget = new SettingsGeneralWidget(p); settingWidgets.insert(settingsWidget); KPageWidgetItem *pageGlobal = p->addPage(settingsWidget, settingsWidget->label()); pageGlobal->setIcon(settingsWidget->icon()); connect(settingsWidget, &SettingsAbstractWidget::changed, p, &KBibTeXPreferencesDialog::gotChanged); settingsWidget = new SettingsGlobalKeywordsWidget(p); settingWidgets.insert(settingsWidget); KPageWidgetItem *page = p->addSubPage(pageGlobal, settingsWidget, settingsWidget->label()); page->setIcon(settingsWidget->icon()); connect(settingsWidget, &SettingsAbstractWidget::changed, p, &KBibTeXPreferencesDialog::gotChanged); settingsWidget = new SettingsColorLabelWidget(p); settingWidgets.insert(settingsWidget); page = p->addSubPage(pageGlobal, settingsWidget, settingsWidget->label()); page->setIcon(settingsWidget->icon()); connect(settingsWidget, &SettingsAbstractWidget::changed, p, &KBibTeXPreferencesDialog::gotChanged); settingsWidget = new SettingsIdSuggestionsWidget(p); settingWidgets.insert(settingsWidget); page = p->addSubPage(pageGlobal, settingsWidget, settingsWidget->label()); page->setIcon(settingsWidget->icon()); connect(settingsWidget, &SettingsAbstractWidget::changed, p, &KBibTeXPreferencesDialog::gotChanged); settingsWidget = new SettingsUserInterfaceWidget(p); settingWidgets.insert(settingsWidget); page = p->addPage(settingsWidget, settingsWidget->label()); page->setIcon(settingsWidget->icon()); connect(settingsWidget, &SettingsAbstractWidget::changed, p, &KBibTeXPreferencesDialog::gotChanged); settingsWidget = new SettingsFileExporterWidget(p); settingWidgets.insert(settingsWidget); KPageWidgetItem *pageSaving = p->addPage(settingsWidget, settingsWidget->label()); pageSaving->setIcon(settingsWidget->icon()); connect(settingsWidget, &SettingsAbstractWidget::changed, p, &KBibTeXPreferencesDialog::gotChanged); settingsWidget = new SettingsFileExporterPDFPSWidget(p); settingWidgets.insert(settingsWidget); page = p->addSubPage(pageSaving, settingsWidget, settingsWidget->label()); page->setIcon(settingsWidget->icon()); connect(settingsWidget, &SettingsAbstractWidget::changed, p, &KBibTeXPreferencesDialog::gotChanged); } void loadState() { for (SettingsAbstractWidget *settingsWidget : const_cast<const QSet<SettingsAbstractWidget *> &>(settingWidgets)) { settingsWidget->loadState(); } } void saveState() { for (SettingsAbstractWidget *settingsWidget : const_cast<const QSet<SettingsAbstractWidget *> &>(settingWidgets)) { settingsWidget->saveState(); } } void restoreDefaults() { notifyOfChanges = true; switch (KMessageBox::warningYesNoCancel(p, i18n("This will reset the settings to factory defaults. Should this affect only the current page or all settings?"), i18n("Reset to Defaults"), KGuiItem(i18n("All settings"), QStringLiteral("edit-undo")), KGuiItem(i18n("Only current page"), QStringLiteral("document-revert")))) { case KMessageBox::Yes: { for (SettingsAbstractWidget *settingsWidget : const_cast<const QSet<SettingsAbstractWidget *> &>(settingWidgets)) { settingsWidget->resetToDefaults(); } break; } case KMessageBox::No: { SettingsAbstractWidget *widget = qobject_cast<SettingsAbstractWidget *>(p->currentPage()->widget()); if (widget != nullptr) widget->resetToDefaults(); break; } case KMessageBox::Cancel: break; /// nothing to do here default: qCWarning(LOG_KBIBTEX_GUI) << "There should be no use for a default case here!"; } } void apply() { p->buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); saveState(); notifyOfChanges = true; } void reset() { p->buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); loadState(); } void ok() { notifyOfChanges = true; if (p->buttonBox()->button(QDialogButtonBox::Apply)->isEnabled()) { /// Apply settings only if changes have made /// (state stored by Apply button being enabled or disabled) apply(); } p->accept(); } }; KBibTeXPreferencesDialog::KBibTeXPreferencesDialog(QWidget *parent, Qt::WindowFlags flags) : KPageDialog(parent, flags), d(new KBibTeXPreferencesDialogPrivate(this)) { setFaceType(KPageDialog::Tree); setWindowTitle(i18n("Preferences")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Reset | QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel, Qt::Horizontal, this); buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); connect(buttonBox, &QDialogButtonBox::clicked, this, &KBibTeXPreferencesDialog::buttonClicked); setButtonBox(buttonBox); setModal(true); d->addPages(); } KBibTeXPreferencesDialog::~KBibTeXPreferencesDialog() { delete d; } void KBibTeXPreferencesDialog::hideEvent(QHideEvent *) { // TODO missing documentation: what triggers 'hideEvent' and why is 'notifyOfChanges' necessary? if (d->notifyOfChanges) NotificationHub::publishEvent(NotificationHub::EventConfigurationChanged); } void KBibTeXPreferencesDialog::buttonClicked(QAbstractButton *button) { switch (buttonBox()->standardButton(button)) { case QDialogButtonBox::Apply: d->apply(); break; case QDialogButtonBox::Ok: d->ok(); break; case QDialogButtonBox::RestoreDefaults: d->restoreDefaults(); break; case QDialogButtonBox::Reset: d->reset(); break; default: qCWarning(LOG_KBIBTEX_GUI) << "There should be no use for a default case here!"; } } void KBibTeXPreferencesDialog::gotChanged() { buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(true); } diff --git a/src/gui/preferences/settingsabstractwidget.cpp b/src/gui/preferences/settingsabstractwidget.cpp index 9ee2a39f..53690fa8 100644 --- a/src/gui/preferences/settingsabstractwidget.cpp +++ b/src/gui/preferences/settingsabstractwidget.cpp @@ -1,26 +1,24 @@ /*************************************************************************** * Copyright (C) 2004-2014 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "settingsabstractwidget.h" -#include <KComboBox> - SettingsAbstractWidget::SettingsAbstractWidget(QWidget *parent) : QWidget(parent) { /// nothing } diff --git a/src/gui/preferences/settingscolorlabelwidget.cpp b/src/gui/preferences/settingscolorlabelwidget.cpp index 665b6230..3814cc49 100644 --- a/src/gui/preferences/settingscolorlabelwidget.cpp +++ b/src/gui/preferences/settingscolorlabelwidget.cpp @@ -1,519 +1,519 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "settingscolorlabelwidget.h" #include "settingscolorlabelwidget_p.h" #include <ctime> #include <QLayout> #include <QStyledItemDelegate> #include <QSignalMapper> #include <QPushButton> +#include <QLineEdit> #include <QMenu> #include <KColorButton> -#include <KLineEdit> #include <KActionMenu> #include <KLocalizedString> #include "file.h" #include "fileview.h" #include "colorlabelwidget.h" #include "models/filemodel.h" #include "preferences.h" class ColorLabelSettingsDelegate : public QStyledItemDelegate { Q_OBJECT public: ColorLabelSettingsDelegate(QWidget *parent = nullptr) : QStyledItemDelegate(parent) { /// nothing } QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const override { if (index.column() == 0) /// Colors are to be edited in a color button return new KColorButton(parent); else /// Text strings are to be edited in a line edit - return new KLineEdit(parent); + return new QLineEdit(parent); } void setEditorData(QWidget *editor, const QModelIndex &index) const override { if (index.column() == 0) { KColorButton *colorButton = qobject_cast<KColorButton *>(editor); /// Initialized color button with row's current color colorButton->setColor(index.model()->data(index, Qt::EditRole).value<QColor>()); } else { - KLineEdit *lineEdit = qobject_cast<KLineEdit *>(editor); + QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor); /// Initialized line edit with row's current color's label lineEdit->setText(index.model()->data(index, Qt::EditRole).toString()); } } void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override { if (index.column() == 0) { KColorButton *colorButton = qobject_cast<KColorButton *>(editor); if (colorButton->color() != Qt::black) /// Assign color button's color back to model model->setData(index, colorButton->color(), Qt::EditRole); } else if (index.column() == 1) { - KLineEdit *lineEdit = qobject_cast<KLineEdit *>(editor); + QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor); if (!lineEdit->text().isEmpty()) /// Assign line edit's text back to model model->setData(index, lineEdit->text(), Qt::EditRole); } } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { QSize hint = QStyledItemDelegate::sizeHint(option, index); QFontMetrics fm = QFontMetrics(QFont()); /// Enforce minimum height of 4 times x-height hint.setHeight(qMax(hint.height(), fm.xHeight() * 4)); return hint; } }; ColorLabelSettingsModel::ColorLabelSettingsModel(QObject *parent) : QAbstractItemModel(parent) { /// Load stored color-label pairs loadState(); } int ColorLabelSettingsModel::rowCount(const QModelIndex &parent) const { /// Single-level list of color-label pairs has as many rows as pairs return parent == QModelIndex() ? colorLabelPairs.size() : 0; } int ColorLabelSettingsModel::columnCount(const QModelIndex &parent) const { /// Single-level list of color-label pairs has as 2 columns /// (one of color, one for label) return parent == QModelIndex() ? 2 : 0; } QModelIndex ColorLabelSettingsModel::index(int row, int column, const QModelIndex &parent) const { if (row >= 0 && row <= colorLabelPairs.size() - 1 && column >= 0 && column <= 1 && parent == QModelIndex()) /// Create index for valid combinations of row, column, and parent return createIndex(row, column, row); else return QModelIndex(); } QModelIndex ColorLabelSettingsModel::parent(const QModelIndex &) const { /// Single-level list's indices have no other index as parent return QModelIndex(); } QVariant ColorLabelSettingsModel::data(const QModelIndex &index, int role) const { /// Skip invalid model indices if (index == QModelIndex() || index.row() < 0 || index.row() >= colorLabelPairs.size()) return QVariant(); if ((role == Qt::DecorationRole || role == Qt::EditRole) && index.column() == 0) /// First column has colors only (no text) return colorLabelPairs[index.row()].first; else if ((role == Qt::DisplayRole || role == Qt::EditRole) && index.column() == 1) /// Second column has colors' labels return colorLabelPairs[index.row()].second; return QVariant(); } bool ColorLabelSettingsModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole) { const QModelIndex left = index.sibling(index.row(), 0); const QModelIndex right = index.sibling(index.row(), 1); if (index.column() == 0 && value.canConvert<QColor>()) { /// For first column if a valid color is to be set ... const QColor color = value.value<QColor>(); if (color != Qt::black && (color.red() > 0 || color.green() > 0 || color.blue() > 0)) { /// ... store this color in the data structure colorLabelPairs[index.row()].first = color; /// Notify everyone about the changes emit dataChanged(left, right); emit modified(); return true; } } else if (index.column() == 1 && value.canConvert<QString>()) { /// For second column if a label text is to be set ... const QString text = value.toString(); if (!text.isEmpty()) { /// ... store this text in the data structure colorLabelPairs[index.row()].second = text; /// Notify everyone about the changes emit dataChanged(left, right); emit modified(); return true; } } } return false; } Qt::ItemFlags ColorLabelSettingsModel::flags(const QModelIndex &index) const { Qt::ItemFlags result = QAbstractItemModel::flags(index); result |= Qt::ItemIsEditable; ///< all cells can be edited (color or text label) return result; } QVariant ColorLabelSettingsModel::headerData(int section, Qt::Orientation orientation, int role) const { /// Only vertical lists supported if (orientation != Qt::Horizontal || role != Qt::DisplayRole) return QVariant(); switch (section) { case 0: return i18n("Color"); case 1: return i18n("Label"); default: return QVariant(); } } /** * Load list of color-label pairs from user's configuration file. * Fall back on pre-defined colors and labels if no user settings exist. */ void ColorLabelSettingsModel::loadState() { colorLabelPairs = Preferences::instance().colorCodes(); } /** * Save list of color-label pairs in user's configuration file. */ void ColorLabelSettingsModel::saveState() { Preferences::instance().setColorCodes(colorLabelPairs); } /** * Revert in-memory data structure (list of color-label pairs) to defaults. * Does not touch user's configuration file (would require an Apply operation). */ void ColorLabelSettingsModel::resetToDefaults() { colorLabelPairs = Preferences::instance().defaultColorCodes; emit modified(); } /** * Add a new color-label pair to this model. * The pair will be appended to the list's end. * No check is performed if a similar color or label is already in use. * * @param color Color of the color-label pair * @param label Label of the color-label pair */ void ColorLabelSettingsModel::addColorLabel(const QColor &color, const QString &label) { const int newRow = colorLabelPairs.size(); beginInsertRows(QModelIndex(), newRow, newRow); colorLabelPairs.append(qMakePair(color, label)); endInsertRows(); emit modified(); } /** * Remove a color-label pair from this model. * The pair is identified by the row number. * * @param row Row number of the pair to be removed */ void ColorLabelSettingsModel::removeColorLabel(int row) { if (row >= 0 && row < colorLabelPairs.size()) { beginRemoveRows(QModelIndex(), row, row); colorLabelPairs.removeAt(row); endRemoveRows(); emit modified(); } } class SettingsColorLabelWidget::Private { private: SettingsColorLabelWidget *p; ColorLabelSettingsDelegate *delegate; public: ColorLabelSettingsModel *model; QPushButton *buttonRemove; QTreeView *view; Private(SettingsColorLabelWidget *parent) : p(parent), delegate(nullptr), model(nullptr), buttonRemove(nullptr), view(nullptr) { /// nothing } void loadState() { /// Delegate state maintenance to model if (model != nullptr) model->loadState(); } void saveState() { /// Delegate state maintenance to model if (model != nullptr) model->saveState(); } void resetToDefaults() { /// Delegate state maintenance to model if (model != nullptr) model->resetToDefaults(); } void setupGUI() { QGridLayout *layout = new QGridLayout(p); layout->setMargin(0); /// Central element in the main widget /// is a tree view for color-label pairs view = new QTreeView(p); layout->addWidget(view, 0, 0, 3, 1); view->setRootIsDecorated(false); /// Tree view's model maintains color-label pairs model = new ColorLabelSettingsModel(view); view->setModel(model); /// Changes in the model (e.g. through setData(..)) /// get propagated as this widget's changed() signal connect(model, &ColorLabelSettingsModel::modified, p, &SettingsColorLabelWidget::changed); /// Delegate to handle changes of color (through KColorButton) - /// and label (throuh KLineEdit) + /// and label (throuh QLineEdit) delegate = new ColorLabelSettingsDelegate(view); view->setItemDelegate(delegate); /// Button to add a new randomized color QPushButton *buttonAdd = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add..."), p); layout->addWidget(buttonAdd, 0, 1, 1, 1); connect(buttonAdd, &QPushButton::clicked, p, &SettingsColorLabelWidget::addColor); /// Remove selected color-label pair; button is disabled /// if no row is selected in tree view buttonRemove = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Remove"), p); layout->addWidget(buttonRemove, 1, 1, 1, 1); buttonRemove->setEnabled(false); connect(buttonRemove, &QPushButton::clicked, p, &SettingsColorLabelWidget::removeColor); } }; SettingsColorLabelWidget::SettingsColorLabelWidget(QWidget *parent) : SettingsAbstractWidget(parent), d(new Private(this)) { /// Seed random number generator qsrand(time(nullptr)); /// Setup GUI elements d->setupGUI(); /// Connect signals connect(d->view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SettingsColorLabelWidget::updateRemoveButtonStatus); } SettingsColorLabelWidget::~SettingsColorLabelWidget() { delete d; } QString SettingsColorLabelWidget::label() const { return i18n("Color & Labels"); } QIcon SettingsColorLabelWidget::icon() const { return QIcon::fromTheme(QStringLiteral("preferences-desktop-color")); } void SettingsColorLabelWidget::loadState() { d->loadState(); } void SettingsColorLabelWidget::saveState() { d->saveState(); } void SettingsColorLabelWidget::resetToDefaults() { d->resetToDefaults(); } void SettingsColorLabelWidget::addColor() { /// Create a randomized color, but guarantee /// some minimum value for each color component const QColor newColor((qrand() & 0xff) | 0x30, (qrand() & 0xff) | 0x30, (qrand() & 0xff) | 0x30); /// Set the new label to be the color's hex string const QString newColorName(newColor.name().remove(QLatin1Char('#'))); /// Add new color-label pair to model's data d->model->addColorLabel(newColor, i18nc("Label for a new color; placeholder is for a 6-digit hex string", "NewColor%1", newColorName)); } void SettingsColorLabelWidget::removeColor() { if (!d->view->selectionModel()->selectedIndexes().isEmpty()) { /// Determine which row is selected const int row = d->view->selectionModel()->selectedIndexes().first().row(); /// Remove row from model d->model->removeColorLabel(row); updateRemoveButtonStatus(); } } void SettingsColorLabelWidget::updateRemoveButtonStatus() { /// Enable remove button iff tree view's selection is not empty d->buttonRemove->setEnabled(!d->view->selectionModel()->selectedIndexes().isEmpty()); } class ColorLabelContextMenu::Private { private: // UNUSED ColorLabelContextMenu *p; public: /// Tree view to show this context menu in FileView *fileView; /// Actual menu to show KActionMenu *menu; /// Signal handle to react to calls to items in menu QSignalMapper *sm; Private(FileView *fv, ColorLabelContextMenu *parent) : /* UNUSED p(parent),*/ fileView(fv) { sm = new QSignalMapper(parent); menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("preferences-desktop-color")), i18n("Color"), fileView); /// Let menu be a sub menu to the tree view's context menu fileView->addAction(menu); } void rebuildMenu() { menu->menu()->clear(); /// Add color-label pairs to menu as stored /// in the user's configuration file for (QVector<QPair<QColor, QString>>::ConstIterator it = Preferences::instance().colorCodes().constBegin(); it != Preferences::instance().colorCodes().constEnd(); ++it) { QAction *action = new QAction(QIcon(ColorLabelWidget::createSolidIcon(it->first)), it->second, menu); menu->addAction(action); sm->setMapping(action, it->first.name()); connect(action, &QAction::triggered, sm, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); } QAction *action = new QAction(menu); action->setSeparator(true); menu->addAction(action); /// Special action that removes any color /// from a BibTeX entry by setting the color to black action = new QAction(i18n("No color"), menu); menu->addAction(action); sm->setMapping(action, QStringLiteral("#000000")); connect(action, &QAction::triggered, sm, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); } }; ColorLabelContextMenu::ColorLabelContextMenu(FileView *widget) : QObject(widget), d(new Private(widget, this)) { connect(d->sm, static_cast<void(QSignalMapper::*)(const QString &)>(&QSignalMapper::mapped), this, &ColorLabelContextMenu::colorActivated); /// Listen to changes in the configuration files NotificationHub::registerNotificationListener(this, NotificationHub::EventConfigurationChanged); d->rebuildMenu(); } ColorLabelContextMenu::~ColorLabelContextMenu() { delete d; } KActionMenu *ColorLabelContextMenu::menuAction() { return d->menu; } void ColorLabelContextMenu::setEnabled(bool enabled) { d->menu->setEnabled(enabled); } void ColorLabelContextMenu::notificationEvent(int eventId) { if (eventId == NotificationHub::EventConfigurationChanged) d->rebuildMenu(); } void ColorLabelContextMenu::colorActivated(const QString &colorString) { /// User activated some color from the menu, /// so apply this color code to the currently /// selected item in the tree view SortFilterFileModel *sfbfm = qobject_cast<SortFilterFileModel *>(d->fileView->model()); Q_ASSERT_X(sfbfm != nullptr, "ColorLabelContextMenu::colorActivated(const QString &colorString)", "SortFilterFileModel *sfbfm is NULL"); FileModel *model = sfbfm->fileSourceModel(); Q_ASSERT_X(model != nullptr, "ColorLabelContextMenu::colorActivated(const QString &colorString)", "FileModel *model is NULL"); /// Apply color change to all selected rows const QModelIndexList list = d->fileView->selectionModel()->selectedIndexes(); for (const QModelIndex &index : list) { const QModelIndex mappedIndex = sfbfm->mapToSource(index); /// Selection may span over multiple columns; /// to avoid duplicate assignments, consider only column 1 if (mappedIndex.column() == 1) { const int row = mappedIndex.row(); QSharedPointer<Entry> entry = model->element(row).dynamicCast<Entry>(); if (!entry.isNull()) { /// Clear old color entry bool modifying = entry->remove(Entry::ftColor) > 0; if (colorString != QStringLiteral("#000000")) { ///< black is a special color that means "no color" /// Only if valid color was selected, set this color Value v; v.append(QSharedPointer<VerbatimText>(new VerbatimText(colorString))); entry->insert(Entry::ftColor, v); modifying = true; } if (modifying) model->elementChanged(row); } } } } #include "settingscolorlabelwidget.moc" diff --git a/src/gui/preferences/settingsfileexporterpdfpswidget.cpp b/src/gui/preferences/settingsfileexporterpdfpswidget.cpp index 7a46cc8e..56330dda 100644 --- a/src/gui/preferences/settingsfileexporterpdfpswidget.cpp +++ b/src/gui/preferences/settingsfileexporterpdfpswidget.cpp @@ -1,140 +1,142 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "settingsfileexporterpdfpswidget.h" #include <QFormLayout> #include <QPageSize> -#include <KLineEdit> +#include <QLineEdit> +#include <QComboBox> -#include <KComboBox> #include <KLocalizedString> #include "guihelper.h" #include "fileexportertoolchain.h" #include "preferences.h" class SettingsFileExporterPDFPSWidget::SettingsFileExporterPDFPSWidgetPrivate { private: SettingsFileExporterPDFPSWidget *p; - KComboBox *comboBoxPaperSize; + QComboBox *comboBoxPaperSize; - KComboBox *comboBoxBabelLanguage; - KComboBox *comboBoxBibliographyStyle; + QComboBox *comboBoxBabelLanguage; + QComboBox *comboBoxBibliographyStyle; public: SettingsFileExporterPDFPSWidgetPrivate(SettingsFileExporterPDFPSWidget *parent) : p(parent) { setupGUI(); } void loadState() { int row = qMax(0, GUIHelper::selectValue(comboBoxPaperSize->model(), static_cast<int>(Preferences::instance().pageSize()), Qt::UserRole)); comboBoxPaperSize->setCurrentIndex(row); const QString babelLanguage = Preferences::instance().laTeXBabelLanguage(); row = qMax(0, GUIHelper::selectValue(comboBoxBabelLanguage->model(), babelLanguage)); comboBoxBabelLanguage->setCurrentIndex(row); const QString bibliographyStyle = Preferences::instance().bibTeXBibliographyStyle(); row = qMax(0, GUIHelper::selectValue(comboBoxBibliographyStyle->model(), bibliographyStyle)); comboBoxBibliographyStyle->setCurrentIndex(row); } void saveState() { Preferences::instance().setPageSize(static_cast<QPageSize::PageSizeId>(comboBoxPaperSize->currentData().toInt())); Preferences::instance().setLaTeXBabelLanguage(comboBoxBabelLanguage->lineEdit()->text()); Preferences::instance().setBibTeXBibliographyStyle(comboBoxBibliographyStyle->lineEdit()->text()); } void resetToDefaults() { int row = qMax(0, GUIHelper::selectValue(comboBoxPaperSize->model(), static_cast<int>(Preferences::defaultPageSize), Qt::UserRole)); comboBoxPaperSize->setCurrentIndex(row); row = qMax(0, GUIHelper::selectValue(comboBoxBabelLanguage->model(), Preferences::defaultLaTeXBabelLanguage)); comboBoxBabelLanguage->setCurrentIndex(row); row = qMax(0, GUIHelper::selectValue(comboBoxBibliographyStyle->model(), Preferences::defaultBibTeXBibliographyStyle)); comboBoxBibliographyStyle->setCurrentIndex(row); } void setupGUI() { QFormLayout *layout = new QFormLayout(p); - comboBoxPaperSize = new KComboBox(false, p); + comboBoxPaperSize = new QComboBox(p); comboBoxPaperSize->setObjectName(QStringLiteral("comboBoxPaperSize")); layout->addRow(i18n("Paper Size:"), comboBoxPaperSize); for (const auto &dbItem : Preferences::availablePageSizes) comboBoxPaperSize->addItem(QPageSize::name(dbItem.first), dbItem.second); connect(comboBoxPaperSize, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), p, &SettingsAbstractWidget::changed); - comboBoxBabelLanguage = new KComboBox(true, p); + comboBoxBabelLanguage = new QComboBox(p); comboBoxBabelLanguage->setObjectName(QStringLiteral("comboBoxBabelLanguage")); + comboBoxBabelLanguage->setEditable(true); layout->addRow(i18n("Language for 'babel':"), comboBoxBabelLanguage); comboBoxBabelLanguage->addItem(QStringLiteral("english")); comboBoxBabelLanguage->addItem(QStringLiteral("ngerman")); comboBoxBabelLanguage->addItem(QStringLiteral("swedish")); connect(comboBoxBabelLanguage->lineEdit(), &QLineEdit::textChanged, p, &SettingsFileExporterPDFPSWidget::changed); - comboBoxBibliographyStyle = new KComboBox(true, p); + comboBoxBibliographyStyle = new QComboBox(p); comboBoxBibliographyStyle->setObjectName(QStringLiteral("comboBoxBibliographyStyle")); + comboBoxBibliographyStyle->setEditable(true); layout->addRow(i18n("Bibliography style:"), comboBoxBibliographyStyle); static const QStringList styles {QString(QStringLiteral("abbrv")), QString(QStringLiteral("alpha")), QString(QStringLiteral("plain")), QString(QStringLiteral("agsm")), QString(QStringLiteral("dcu")), QString(QStringLiteral("jmr")), QString(QStringLiteral("jphysicsB")), QString(QStringLiteral("kluwer")), QString(QStringLiteral("nederlands"))}; for (const QString &style : styles) { comboBoxBibliographyStyle->addItem(style); } connect(comboBoxBibliographyStyle->lineEdit(), &QLineEdit::textChanged, p, &SettingsFileExporterPDFPSWidget::changed); } }; SettingsFileExporterPDFPSWidget::SettingsFileExporterPDFPSWidget(QWidget *parent) : SettingsAbstractWidget(parent), d(new SettingsFileExporterPDFPSWidgetPrivate(this)) { d->loadState(); } SettingsFileExporterPDFPSWidget::~SettingsFileExporterPDFPSWidget() { delete d; } QString SettingsFileExporterPDFPSWidget::label() const { return i18n("PDF & Postscript"); } QIcon SettingsFileExporterPDFPSWidget::icon() const { return QIcon::fromTheme(QStringLiteral("application-pdf")); } void SettingsFileExporterPDFPSWidget::loadState() { d->loadState(); } void SettingsFileExporterPDFPSWidget::saveState() { d->saveState(); } void SettingsFileExporterPDFPSWidget::resetToDefaults() { d->resetToDefaults(); } diff --git a/src/gui/preferences/settingsfileexporterwidget.cpp b/src/gui/preferences/settingsfileexporterwidget.cpp index 360d0212..255aa707 100644 --- a/src/gui/preferences/settingsfileexporterwidget.cpp +++ b/src/gui/preferences/settingsfileexporterwidget.cpp @@ -1,207 +1,208 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "settingsfileexporterwidget.h" #include <QFormLayout> #include <QCheckBox> #include <QSpinBox> +#include <QLineEdit> +#include <QComboBox> #include <QPushButton> #include <qplatformdefs.h> #include <KLocalizedString> -#include <KComboBox> -#include <KLineEdit> #include <KUrlRequester> +#include <KLineEdit> /// required as KUrlRequester returns it #include "guihelper.h" #include "italictextitemmodel.h" #include "fileexporter.h" #include "clipboard.h" #include "preferences.h" #include "lyx.h" class SettingsFileExporterWidget::SettingsFileExporterWidgetPrivate { private: SettingsFileExporterWidget *p; - KComboBox *comboBoxCopyReferenceCmd; + QComboBox *comboBoxCopyReferenceCmd; static const QString citeCmdToLabel; public: #ifdef QT_LSTAT QCheckBox *checkboxUseAutomaticLyXPipeDetection; #endif // QT_LSTAT - KComboBox *comboBoxBackupScope; + QComboBox *comboBoxBackupScope; QSpinBox *spinboxNumberOfBackups; KUrlRequester *lineeditLyXPipePath; #ifdef QT_LSTAT QString lastUserInputLyXPipePath; #endif // QT_LSTAT SettingsFileExporterWidgetPrivate(SettingsFileExporterWidget *parent) : p(parent) { setupGUI(); } void loadState() { int row = GUIHelper::selectValue(comboBoxCopyReferenceCmd->model(), Preferences::instance().copyReferenceCommand(), ItalicTextItemModel::IdentifierRole); comboBoxCopyReferenceCmd->setCurrentIndex(row); const int index = qMax(0, comboBoxBackupScope->findData(static_cast<int>(Preferences::instance().backupScope()))); comboBoxBackupScope->setCurrentIndex(index); spinboxNumberOfBackups->setValue(qMax(0, qMin(spinboxNumberOfBackups->maximum(), Preferences::instance().numberOfBackups()))); #ifndef QT_LSTAT lineeditLyXPipePath->setText(Preferences::instance().lyXPipePath()); #else // QT_LSTAT checkboxUseAutomaticLyXPipeDetection->setChecked(Preferences::instance().lyXUseAutomaticPipeDetection()); lastUserInputLyXPipePath = Preferences::instance().lyXPipePath(); p->automaticLyXDetectionToggled(checkboxUseAutomaticLyXPipeDetection->isChecked()); #endif // QT_LSTAT } void saveState() { Preferences::instance().setCopyReferenceCommand(comboBoxCopyReferenceCmd->itemData(comboBoxCopyReferenceCmd->currentIndex(), ItalicTextItemModel::IdentifierRole).toString()); Preferences::instance().setBackupScope(static_cast<Preferences::BackupScope>(comboBoxBackupScope->itemData(comboBoxBackupScope->currentIndex()).toInt())); Preferences::instance().setNumberOfBackups(spinboxNumberOfBackups->value()); #ifndef QT_LSTAT Preferences::instance().setLyXPipePath(lineeditLyXPipePath->text()); #else // QT_LSTAT Preferences::instance().setLyXUseAutomaticPipeDetection(checkboxUseAutomaticLyXPipeDetection->isChecked()); Preferences::instance().setLyXPipePath(checkboxUseAutomaticLyXPipeDetection->isChecked() ? lastUserInputLyXPipePath : lineeditLyXPipePath->text()); #endif // QT_LSTAT } void resetToDefaults() { int row = GUIHelper::selectValue(comboBoxCopyReferenceCmd->model(), QString(), Qt::UserRole); comboBoxCopyReferenceCmd->setCurrentIndex(row); const int index = qMax(0, comboBoxBackupScope->findData(Preferences::defaultBackupScope)); comboBoxBackupScope->setCurrentIndex(index); spinboxNumberOfBackups->setValue(qMax(0, qMin(spinboxNumberOfBackups->maximum(), Preferences::defaultNumberOfBackups))); #ifndef QT_LSTAT const QString pipe = Preferences::defaultLyXPipePath; #else // QT_LSTAT checkboxUseAutomaticLyXPipeDetection->setChecked(Preferences::defaultLyXUseAutomaticPipeDetection); QString pipe = LyX::guessLyXPipeLocation(); if (pipe.isEmpty()) pipe = Preferences::defaultLyXPipePath; #endif // QT_LSTAT lineeditLyXPipePath->setText(pipe); } void setupGUI() { QFormLayout *layout = new QFormLayout(p); - comboBoxCopyReferenceCmd = new KComboBox(false, p); + comboBoxCopyReferenceCmd = new QComboBox(p); comboBoxCopyReferenceCmd->setObjectName(QStringLiteral("comboBoxCopyReferenceCmd")); layout->addRow(i18n("Command for 'Copy Reference':"), comboBoxCopyReferenceCmd); ItalicTextItemModel *itim = new ItalicTextItemModel(); itim->addItem(i18n("No command"), QString()); for (const QString &citeCommand : Preferences::availableCopyReferenceCommands) itim->addItem(citeCmdToLabel.arg(citeCommand), citeCommand); comboBoxCopyReferenceCmd->setModel(itim); connect(comboBoxCopyReferenceCmd, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), p, &SettingsFileExporterWidget::changed); #ifdef QT_LSTAT checkboxUseAutomaticLyXPipeDetection = new QCheckBox(QString(), p); layout->addRow(i18n("Detect LyX pipe automatically:"), checkboxUseAutomaticLyXPipeDetection); connect(checkboxUseAutomaticLyXPipeDetection, &QCheckBox::toggled, p, &SettingsFileExporterWidget::changed); connect(checkboxUseAutomaticLyXPipeDetection, &QCheckBox::toggled, p, &SettingsFileExporterWidget::automaticLyXDetectionToggled); #endif // QT_LSTAT lineeditLyXPipePath = new KUrlRequester(p); layout->addRow(i18n("Manually specified LyX pipe:"), lineeditLyXPipePath); - connect(lineeditLyXPipePath->lineEdit(), &KLineEdit::textEdited, p, &SettingsFileExporterWidget::changed); + connect(qobject_cast<QLineEdit *>(lineeditLyXPipePath->lineEdit()), &QLineEdit::textEdited, p, &SettingsFileExporterWidget::changed); lineeditLyXPipePath->setMinimumWidth(lineeditLyXPipePath->fontMetrics().width(QChar('W')) * 20); lineeditLyXPipePath->setFilter(QStringLiteral("inode/fifo")); lineeditLyXPipePath->setMode(KFile::ExistingOnly | KFile::LocalOnly); - comboBoxBackupScope = new KComboBox(false, p); + comboBoxBackupScope = new QComboBox(p); for (const auto &pair : Preferences::availableBackupScopes) comboBoxBackupScope->addItem(pair.second, static_cast<int>(pair.first)); layout->addRow(i18n("Backups when saving:"), comboBoxBackupScope); connect(comboBoxBackupScope, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), p, &SettingsFileExporterWidget::changed); spinboxNumberOfBackups = new QSpinBox(p); spinboxNumberOfBackups->setMinimum(1); spinboxNumberOfBackups->setMaximum(16); layout->addRow(i18n("Number of Backups:"), spinboxNumberOfBackups); connect(spinboxNumberOfBackups, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), p, &SettingsFileExporterWidget::changed); connect(comboBoxBackupScope, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), p, &SettingsFileExporterWidget::updateGUI); } }; const QString SettingsFileExporterWidget::SettingsFileExporterWidgetPrivate::citeCmdToLabel = QStringLiteral("\\%1{") + QChar(0x2026) + QChar('}'); SettingsFileExporterWidget::SettingsFileExporterWidget(QWidget *parent) : SettingsAbstractWidget(parent), d(new SettingsFileExporterWidgetPrivate(this)) { d->loadState(); } SettingsFileExporterWidget::~SettingsFileExporterWidget() { delete d; } QString SettingsFileExporterWidget::label() const { return i18n("Saving and Exporting"); } QIcon SettingsFileExporterWidget::icon() const { return QIcon::fromTheme(QStringLiteral("document-save")); } void SettingsFileExporterWidget::loadState() { d->loadState(); } void SettingsFileExporterWidget::saveState() { d->saveState(); } void SettingsFileExporterWidget::resetToDefaults() { d->resetToDefaults(); } #ifdef QT_LSTAT void SettingsFileExporterWidget::automaticLyXDetectionToggled(bool isChecked) { d->lineeditLyXPipePath->setEnabled(!isChecked); if (isChecked) { d->lastUserInputLyXPipePath = d->lineeditLyXPipePath->text(); d->lineeditLyXPipePath->setText(LyX::guessLyXPipeLocation()); } else d->lineeditLyXPipePath->setText(d->lastUserInputLyXPipePath); } #endif // QT_LSTAT void SettingsFileExporterWidget::updateGUI() { d->spinboxNumberOfBackups->setEnabled(d->comboBoxBackupScope->itemData(d->comboBoxBackupScope->currentIndex()).toInt() != static_cast<int>(Preferences::NoBackup)); } diff --git a/src/gui/preferences/settingsgeneralwidget.cpp b/src/gui/preferences/settingsgeneralwidget.cpp index 56f3be0c..2156e10b 100644 --- a/src/gui/preferences/settingsgeneralwidget.cpp +++ b/src/gui/preferences/settingsgeneralwidget.cpp @@ -1,119 +1,119 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "settingsgeneralwidget.h" #include <QFormLayout> +#include <QComboBox> #include <KLocalizedString> -#include <KComboBox> -#include "guihelper.h" -#include "value.h" #include "preferences.h" +#include "value.h" +#include "guihelper.h" class SettingsGeneralWidget::SettingsGeneralWidgetPrivate { private: SettingsGeneralWidget *p; - KComboBox *comboBoxBibliographySystem; - KComboBox *comboBoxPersonNameFormatting; + QComboBox *comboBoxBibliographySystem; + QComboBox *comboBoxPersonNameFormatting; const Person dummyPerson; public: SettingsGeneralWidgetPrivate(SettingsGeneralWidget *parent) : p(parent), dummyPerson(Person(i18n("John"), i18n("Doe"), i18n("Jr."))) { setupGUI(); } void loadState() { comboBoxBibliographySystem->setCurrentIndex(comboBoxBibliographySystem->findData(QVariant::fromValue<int>(static_cast<int>(Preferences::instance().bibliographySystem())))); int row = GUIHelper::selectValue(comboBoxPersonNameFormatting->model(), Person::transcribePersonName(&dummyPerson, Preferences::instance().personNameFormat())); comboBoxPersonNameFormatting->setCurrentIndex(row); } void saveState() { Preferences::instance().setBibliographySystem(static_cast<Preferences::BibliographySystem>(comboBoxBibliographySystem->currentData().toInt())); Preferences::instance().setPersonNameFormat(comboBoxPersonNameFormatting->itemData(comboBoxPersonNameFormatting->currentIndex()).toString()); } void resetToDefaults() { comboBoxBibliographySystem->setCurrentIndex(static_cast<int>(Preferences::defaultBibliographySystem)); int row = GUIHelper::selectValue(comboBoxPersonNameFormatting->model(), Person::transcribePersonName(&dummyPerson, Preferences::defaultPersonNameFormat)); comboBoxPersonNameFormatting->setCurrentIndex(row); } void setupGUI() { QFormLayout *layout = new QFormLayout(p); - comboBoxBibliographySystem = new KComboBox(false, p); + comboBoxBibliographySystem = new QComboBox(p); comboBoxBibliographySystem->setObjectName(QStringLiteral("comboBoxBibliographySystem")); for (QVector<QPair<Preferences::BibliographySystem, QString>>::ConstIterator it = Preferences::availableBibliographySystems.constBegin(); it != Preferences::availableBibliographySystems.constEnd(); ++it) comboBoxBibliographySystem->addItem(it->second, QVariant::fromValue<int>(static_cast<int>(it->first))); layout->addRow(i18n("Bibliography System:"), comboBoxBibliographySystem); connect(comboBoxBibliographySystem, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), p, &SettingsGeneralWidget::changed); - comboBoxPersonNameFormatting = new KComboBox(false, p); + comboBoxPersonNameFormatting = new QComboBox(p); layout->addRow(i18n("Person Names Formatting:"), comboBoxPersonNameFormatting); static const QStringList formattingOptions {Preferences::personNameFormatFirstLast, Preferences::personNameFormatLastFirst}; for (const QString &formattingOption : formattingOptions) comboBoxPersonNameFormatting->addItem(Person::transcribePersonName(&dummyPerson, formattingOption), formattingOption); connect(comboBoxPersonNameFormatting, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), p, &SettingsGeneralWidget::changed); } }; SettingsGeneralWidget::SettingsGeneralWidget(QWidget *parent) : SettingsAbstractWidget(parent), d(new SettingsGeneralWidgetPrivate(this)) { d->loadState(); } SettingsGeneralWidget::~SettingsGeneralWidget() { delete d; } QString SettingsGeneralWidget::label() const { return i18n("General"); } QIcon SettingsGeneralWidget::icon() const { return QIcon::fromTheme(QStringLiteral("kbibtex")); } void SettingsGeneralWidget::loadState() { d->loadState(); } void SettingsGeneralWidget::saveState() { d->saveState(); } void SettingsGeneralWidget::resetToDefaults() { d->resetToDefaults(); } diff --git a/src/gui/preferences/settingsidsuggestionseditor.cpp b/src/gui/preferences/settingsidsuggestionseditor.cpp index 210aa417..c98acc85 100644 --- a/src/gui/preferences/settingsidsuggestionseditor.cpp +++ b/src/gui/preferences/settingsidsuggestionseditor.cpp @@ -1,929 +1,929 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "settingsidsuggestionseditor.h" #include <QGridLayout> #include <QFormLayout> #include <QScrollArea> #include <QLabel> +#include <QLineEdit> +#include <QComboBox> #include <QSpinBox> #include <QCheckBox> #include <QSignalMapper> #include <QMenu> #include <QPointer> #include <QPushButton> #include <QAction> #include <QDialogButtonBox> -#include <KLineEdit> -#include <KComboBox> #include <KLocalizedString> #include <KIconLoader> #include "rangewidget.h" /** * @author Thomas Fischer */ class TokenWidget : public QGroupBox { Q_OBJECT protected: QGridLayout *gridLayout; QFormLayout *formLayout; public: explicit TokenWidget(QWidget *parent) : QGroupBox(parent) { gridLayout = new QGridLayout(this); formLayout = new QFormLayout(); gridLayout->addLayout(formLayout, 0, 0, 4, 1); gridLayout->setColumnStretch(0, 100); } void addButtons(QPushButton *buttonUp, QPushButton *buttonDown, QPushButton *buttonRemove) { gridLayout->setColumnMinimumWidth(1, 32); gridLayout->setColumnStretch(1, 1); gridLayout->setColumnStretch(2, 1); gridLayout->addWidget(buttonUp, 0, 2, 1, 1); buttonUp->setParent(this); gridLayout->addWidget(buttonDown, 1, 2, 1, 1); buttonDown->setParent(this); gridLayout->addWidget(buttonRemove, 2, 2, 1, 1); buttonRemove->setParent(this); } virtual QString toString() const = 0; }; /** * @author Thomas Fischer */ class AuthorWidget : public TokenWidget { Q_OBJECT private: RangeWidget *rangeWidgetAuthor; QCheckBox *checkBoxLastAuthor; QLabel *labelAuthorRange; - KComboBox *comboBoxChangeCase; - KLineEdit *lineEditTextInBetween; + QComboBox *comboBoxChangeCase; + QLineEdit *lineEditTextInBetween; QSpinBox *spinBoxLength; private slots: void updateRangeLabel() { const int lower = rangeWidgetAuthor->lowerValue(); const int upper = rangeWidgetAuthor->upperValue(); const int max = rangeWidgetAuthor->maximum(); labelAuthorRange->setText(IdSuggestions::formatAuthorRange(lower, upper == max ? 0x00ffffff : upper, checkBoxLastAuthor->isChecked())); } public: AuthorWidget(const struct IdSuggestions::IdSuggestionTokenInfo &info, IdSuggestionsEditWidget *isew, QWidget *parent) : TokenWidget(parent) { setTitle(i18n("Authors")); QBoxLayout *boxLayout = new QVBoxLayout(); boxLayout->setMargin(0); formLayout->addRow(i18n("Author Range:"), boxLayout); static const QStringList authorRange {i18n("First author"), i18n("Second author"), i18n("Third author"), i18n("Fourth author"), i18n("Fifth author"), i18n("Sixth author"), i18n("Seventh author"), i18n("Eighth author"), i18n("Ninth author"), i18n("Tenth author"), i18n("|Last author")}; rangeWidgetAuthor = new RangeWidget(authorRange, this); boxLayout->addWidget(rangeWidgetAuthor); rangeWidgetAuthor->setLowerValue(info.startWord); rangeWidgetAuthor->setUpperValue(qMin(authorRange.size() - 1, info.endWord)); checkBoxLastAuthor = new QCheckBox(i18n("... and last author"), this); boxLayout->addWidget(checkBoxLastAuthor); labelAuthorRange = new QLabel(this); boxLayout->addWidget(labelAuthorRange); const int maxWidth = qMax(labelAuthorRange->fontMetrics().width(i18n("From first author to author %1 and last author", 88)), labelAuthorRange->fontMetrics().width(i18n("From author %1 to author %2 and last author", 88, 88))); labelAuthorRange->setMinimumWidth(maxWidth); - comboBoxChangeCase = new KComboBox(false, this); + comboBoxChangeCase = new QComboBox(this); comboBoxChangeCase->addItem(i18n("No change"), IdSuggestions::ccNoChange); comboBoxChangeCase->addItem(i18n("To upper case"), IdSuggestions::ccToUpper); comboBoxChangeCase->addItem(i18n("To lower case"), IdSuggestions::ccToLower); comboBoxChangeCase->addItem(i18n("To CamelCase"), IdSuggestions::ccToCamelCase); formLayout->addRow(i18n("Change casing:"), comboBoxChangeCase); comboBoxChangeCase->setCurrentIndex(static_cast<int>(info.caseChange)); /// enum has numbers assigned to cases and combo box has same indices - lineEditTextInBetween = new KLineEdit(this); + lineEditTextInBetween = new QLineEdit(this); formLayout->addRow(i18n("Text in between:"), lineEditTextInBetween); lineEditTextInBetween->setText(info.inBetween); spinBoxLength = new QSpinBox(this); formLayout->addRow(i18n("Only first characters:"), spinBoxLength); spinBoxLength->setSpecialValueText(i18n("No limitation")); spinBoxLength->setMinimum(0); spinBoxLength->setMaximum(9); spinBoxLength->setValue(info.len == 0 || info.len > 9 ? 0 : info.len); connect(rangeWidgetAuthor, &RangeWidget::lowerValueChanged, isew, &IdSuggestionsEditWidget::updatePreview); connect(rangeWidgetAuthor, &RangeWidget::upperValueChanged, isew, &IdSuggestionsEditWidget::updatePreview); connect(rangeWidgetAuthor, &RangeWidget::lowerValueChanged, this, &AuthorWidget::updateRangeLabel); connect(rangeWidgetAuthor, &RangeWidget::upperValueChanged, this, &AuthorWidget::updateRangeLabel); connect(checkBoxLastAuthor, &QCheckBox::toggled, isew, &IdSuggestionsEditWidget::updatePreview); connect(checkBoxLastAuthor, &QCheckBox::toggled, this, &AuthorWidget::updateRangeLabel); connect(comboBoxChangeCase, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), isew, &IdSuggestionsEditWidget::updatePreview); - connect(lineEditTextInBetween, &KLineEdit::textEdited, isew, &IdSuggestionsEditWidget::updatePreview); + connect(lineEditTextInBetween, &QLineEdit::textEdited, isew, &IdSuggestionsEditWidget::updatePreview); connect(spinBoxLength, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), isew, &IdSuggestionsEditWidget::updatePreview); updateRangeLabel(); } QString toString() const override { QString result = QStringLiteral("A"); if (spinBoxLength->value() > 0) result.append(QString::number(spinBoxLength->value())); const IdSuggestions::CaseChange caseChange = static_cast<IdSuggestions::CaseChange>(comboBoxChangeCase->currentIndex()); if (caseChange == IdSuggestions::ccToLower) result.append(QStringLiteral("l")); else if (caseChange == IdSuggestions::ccToUpper) result.append(QStringLiteral("u")); else if (caseChange == IdSuggestions::ccToCamelCase) result.append(QStringLiteral("c")); if (rangeWidgetAuthor->lowerValue() > 0 || rangeWidgetAuthor->upperValue() < rangeWidgetAuthor->maximum()) result.append(QString(QStringLiteral("w%1%2")).arg(rangeWidgetAuthor->lowerValue()).arg(rangeWidgetAuthor->upperValue() < rangeWidgetAuthor->maximum() ? QString::number(rangeWidgetAuthor->upperValue()) : QStringLiteral("I"))); if (checkBoxLastAuthor->isChecked()) result.append(QStringLiteral("L")); const QString text = lineEditTextInBetween->text(); if (!text.isEmpty()) result.append(QStringLiteral("\"")).append(text); return result; } }; /** * @author Thomas Fischer */ class YearWidget : public TokenWidget { Q_OBJECT private: - KComboBox *comboBoxDigits; + QComboBox *comboBoxDigits; public: YearWidget(int digits, IdSuggestionsEditWidget *isew, QWidget *parent) : TokenWidget(parent) { setTitle(i18n("Year")); - comboBoxDigits = new KComboBox(false, this); + comboBoxDigits = new QComboBox(this); comboBoxDigits->addItem(i18n("2 digits"), 2); comboBoxDigits->addItem(i18n("4 digits"), 4); formLayout->addRow(i18n("Digits:"), comboBoxDigits); comboBoxDigits->setCurrentIndex(comboBoxDigits->findData(digits)); connect(comboBoxDigits, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), isew, &IdSuggestionsEditWidget::updatePreview); } QString toString() const override { const int year = comboBoxDigits->itemData(comboBoxDigits->currentIndex()).toInt(); QString result = year == 4 ? QStringLiteral("Y") : QStringLiteral("y"); return result; } }; /** * @author Thomas Fischer */ class VolumeWidget : public TokenWidget { Q_OBJECT private: QLabel *labelCheckmark; public: VolumeWidget(IdSuggestionsEditWidget *, QWidget *parent) : TokenWidget(parent) { setTitle(i18n("Volume")); labelCheckmark = new QLabel(this); labelCheckmark->setPixmap(KIconLoader::global()->loadMimeTypeIcon(QStringLiteral("dialog-ok-apply"), KIconLoader::Small)); formLayout->addRow(i18n("Volume:"), labelCheckmark); } QString toString() const override { return QStringLiteral("v"); } }; /** * @author Thomas Fischer */ class PageNumberWidget : public TokenWidget { Q_OBJECT private: QLabel *labelCheckmark; public: PageNumberWidget(IdSuggestionsEditWidget *, QWidget *parent) : TokenWidget(parent) { setTitle(i18n("Page Number")); labelCheckmark = new QLabel(this); labelCheckmark->setPixmap(KIconLoader::global()->loadMimeTypeIcon(QStringLiteral("dialog-ok-apply"), KIconLoader::Small)); formLayout->addRow(i18n("First page's number:"), labelCheckmark); } QString toString() const override { return QStringLiteral("p"); } }; /** * @author Thomas Fischer */ class TitleWidget : public TokenWidget { Q_OBJECT private: RangeWidget *rangeWidgetAuthor; QLabel *labelWordsRange; QCheckBox *checkBoxRemoveSmallWords; - KComboBox *comboBoxChangeCase; - KLineEdit *lineEditTextInBetween; + QComboBox *comboBoxChangeCase; + QLineEdit *lineEditTextInBetween; QSpinBox *spinBoxLength; private slots: void updateRangeLabel() { const int lower = rangeWidgetAuthor->lowerValue(); const int upper = rangeWidgetAuthor->upperValue(); const int max = rangeWidgetAuthor->maximum(); if (lower == 0 && upper == 0) labelWordsRange->setText(i18n("First word only")); else if (lower == 1 && upper == max) labelWordsRange->setText(i18n("All but first word")); else if (lower == 0 && upper == max) labelWordsRange->setText(i18n("From first to last word")); else if (lower > 0 && upper == max) labelWordsRange->setText(i18n("From word %1 to last word", lower + 1)); else if (lower == 0 && upper < max) labelWordsRange->setText(i18n("From first word to word %1", upper + 1)); else labelWordsRange->setText(i18n("From word %1 to word %2", lower + 1, upper + 1)); } public: TitleWidget(const struct IdSuggestions::IdSuggestionTokenInfo &info, bool removeSmallWords, IdSuggestionsEditWidget *isew, QWidget *parent) : TokenWidget(parent) { setTitle(i18n("Title")); QBoxLayout *boxLayout = new QVBoxLayout(); boxLayout->setMargin(0); formLayout->addRow(i18n("Word Range:"), boxLayout); static const QStringList wordRange {i18n("First word"), i18n("Second word"), i18n("Third word"), i18n("Fourth word"), i18n("Fifth word"), i18n("Sixth word"), i18n("Seventh word"), i18n("Eighth word"), i18n("Ninth word"), i18n("Tenth word"), i18n("|Last word")}; rangeWidgetAuthor = new RangeWidget(wordRange, this); boxLayout->addWidget(rangeWidgetAuthor); if (info.startWord > 0 || info.endWord < 0xffff) { rangeWidgetAuthor->setLowerValue(info.startWord); rangeWidgetAuthor->setUpperValue(qMin(rangeWidgetAuthor->maximum(), info.endWord)); } else { rangeWidgetAuthor->setLowerValue(0); rangeWidgetAuthor->setUpperValue(rangeWidgetAuthor->maximum()); } labelWordsRange = new QLabel(this); boxLayout->addWidget(labelWordsRange); const int a = qMax(labelWordsRange->fontMetrics().width(i18n("From first to last word")), labelWordsRange->fontMetrics().width(i18n("From word %1 to last word", 88))); const int b = qMax(labelWordsRange->fontMetrics().width(i18n("From first word to word %1", 88)), labelWordsRange->fontMetrics().width(i18n("From word %1 to word %2", 88, 88))); labelWordsRange->setMinimumWidth(qMax(a, b)); checkBoxRemoveSmallWords = new QCheckBox(i18n("Remove"), this); formLayout->addRow(i18n("Small words:"), checkBoxRemoveSmallWords); checkBoxRemoveSmallWords->setChecked(removeSmallWords); - comboBoxChangeCase = new KComboBox(false, this); + comboBoxChangeCase = new QComboBox(this); comboBoxChangeCase->addItem(i18n("No change"), IdSuggestions::ccNoChange); comboBoxChangeCase->addItem(i18n("To upper case"), IdSuggestions::ccToUpper); comboBoxChangeCase->addItem(i18n("To lower case"), IdSuggestions::ccToLower); comboBoxChangeCase->addItem(i18n("To CamelCase"), IdSuggestions::ccToCamelCase); formLayout->addRow(i18n("Change casing:"), comboBoxChangeCase); comboBoxChangeCase->setCurrentIndex(static_cast<int>(info.caseChange)); /// enum has numbers assigned to cases and combo box has same indices - lineEditTextInBetween = new KLineEdit(this); + lineEditTextInBetween = new QLineEdit(this); formLayout->addRow(i18n("Text in between:"), lineEditTextInBetween); lineEditTextInBetween->setText(info.inBetween); spinBoxLength = new QSpinBox(this); formLayout->addRow(i18n("Only first characters:"), spinBoxLength); spinBoxLength->setSpecialValueText(i18n("No limitation")); spinBoxLength->setMinimum(0); spinBoxLength->setMaximum(9); spinBoxLength->setValue(info.len == 0 || info.len > 9 ? 0 : info.len); connect(rangeWidgetAuthor, &RangeWidget::lowerValueChanged, isew, &IdSuggestionsEditWidget::updatePreview); connect(rangeWidgetAuthor, &RangeWidget::upperValueChanged, isew, &IdSuggestionsEditWidget::updatePreview); connect(rangeWidgetAuthor, &RangeWidget::lowerValueChanged, this, &TitleWidget::updateRangeLabel); connect(rangeWidgetAuthor, &RangeWidget::upperValueChanged, this, &TitleWidget::updateRangeLabel); connect(checkBoxRemoveSmallWords, &QCheckBox::toggled, isew, &IdSuggestionsEditWidget::updatePreview); connect(comboBoxChangeCase, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), isew, &IdSuggestionsEditWidget::updatePreview); - connect(lineEditTextInBetween, &KLineEdit::textEdited, isew, &IdSuggestionsEditWidget::updatePreview); + connect(lineEditTextInBetween, &QLineEdit::textEdited, isew, &IdSuggestionsEditWidget::updatePreview); connect(spinBoxLength, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), isew, &IdSuggestionsEditWidget::updatePreview); updateRangeLabel(); } QString toString() const override { QString result = checkBoxRemoveSmallWords->isChecked() ? QStringLiteral("T") : QStringLiteral("t"); if (spinBoxLength->value() > 0) result.append(QString::number(spinBoxLength->value())); const IdSuggestions::CaseChange caseChange = static_cast<IdSuggestions::CaseChange>(comboBoxChangeCase->currentIndex()); if (caseChange == IdSuggestions::ccToLower) result.append(QStringLiteral("l")); else if (caseChange == IdSuggestions::ccToUpper) result.append(QStringLiteral("u")); else if (caseChange == IdSuggestions::ccToCamelCase) result.append(QStringLiteral("c")); if (rangeWidgetAuthor->lowerValue() > 0 || rangeWidgetAuthor->upperValue() < rangeWidgetAuthor->maximum()) result.append(QString(QStringLiteral("w%1%2")).arg(rangeWidgetAuthor->lowerValue()).arg(rangeWidgetAuthor->upperValue() < rangeWidgetAuthor->maximum() ? QString::number(rangeWidgetAuthor->upperValue()) : QStringLiteral("I"))); const QString text = lineEditTextInBetween->text(); if (!text.isEmpty()) result.append(QStringLiteral("\"")).append(text); return result; } }; /** * @author Thomas Fischer */ class JournalWidget : public TokenWidget { Q_OBJECT private: QCheckBox *checkBoxRemoveSmallWords; - KComboBox *comboBoxChangeCase; - KLineEdit *lineEditTextInBetween; + QComboBox *comboBoxChangeCase; + QLineEdit *lineEditTextInBetween; QSpinBox *spinBoxLength; public: JournalWidget(const struct IdSuggestions::IdSuggestionTokenInfo &info, bool removeSmallWords, IdSuggestionsEditWidget *isew, QWidget *parent) : TokenWidget(parent) { setTitle(i18n("Journal")); QBoxLayout *boxLayout = new QVBoxLayout(); boxLayout->setMargin(0); checkBoxRemoveSmallWords = new QCheckBox(i18n("Remove"), this); formLayout->addRow(i18n("Small words:"), checkBoxRemoveSmallWords); checkBoxRemoveSmallWords->setChecked(removeSmallWords); - comboBoxChangeCase = new KComboBox(false, this); + comboBoxChangeCase = new QComboBox(this); comboBoxChangeCase->addItem(i18n("No change"), IdSuggestions::ccNoChange); comboBoxChangeCase->addItem(i18n("To upper case"), IdSuggestions::ccToUpper); comboBoxChangeCase->addItem(i18n("To lower case"), IdSuggestions::ccToLower); comboBoxChangeCase->addItem(i18n("To CamelCase"), IdSuggestions::ccToCamelCase); formLayout->addRow(i18n("Change casing:"), comboBoxChangeCase); comboBoxChangeCase->setCurrentIndex(static_cast<int>(info.caseChange)); /// enum has numbers assigned to cases and combo box has same indices - lineEditTextInBetween = new KLineEdit(this); + lineEditTextInBetween = new QLineEdit(this); formLayout->addRow(i18n("Text in between:"), lineEditTextInBetween); lineEditTextInBetween->setText(info.inBetween); spinBoxLength = new QSpinBox(this); formLayout->addRow(i18n("Only first characters:"), spinBoxLength); spinBoxLength->setSpecialValueText(i18n("No limitation")); spinBoxLength->setMinimum(0); spinBoxLength->setMaximum(9); spinBoxLength->setValue(info.len == 0 || info.len > 9 ? 0 : info.len); connect(checkBoxRemoveSmallWords, &QCheckBox::toggled, isew, &IdSuggestionsEditWidget::updatePreview); connect(comboBoxChangeCase, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), isew, &IdSuggestionsEditWidget::updatePreview); - connect(lineEditTextInBetween, &KLineEdit::textEdited, isew, &IdSuggestionsEditWidget::updatePreview); + connect(lineEditTextInBetween, &QLineEdit::textEdited, isew, &IdSuggestionsEditWidget::updatePreview); connect(spinBoxLength, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), isew, &IdSuggestionsEditWidget::updatePreview); } QString toString() const override { QString result = checkBoxRemoveSmallWords->isChecked() ? QStringLiteral("J") : QStringLiteral("j"); if (spinBoxLength->value() > 0) result.append(QString::number(spinBoxLength->value())); const IdSuggestions::CaseChange caseChange = static_cast<IdSuggestions::CaseChange>(comboBoxChangeCase->currentIndex()); if (caseChange == IdSuggestions::ccToLower) result.append(QStringLiteral("l")); else if (caseChange == IdSuggestions::ccToUpper) result.append(QStringLiteral("u")); else if (caseChange == IdSuggestions::ccToCamelCase) result.append(QStringLiteral("c")); const QString text = lineEditTextInBetween->text(); if (!text.isEmpty()) result.append(QStringLiteral("\"")).append(text); return result; } }; /** * @author Erik Quaeghebeur */ class TypeWidget : public TokenWidget { Q_OBJECT private: - KComboBox *comboBoxChangeCase; + QComboBox *comboBoxChangeCase; QSpinBox *spinBoxLength; public: TypeWidget(const struct IdSuggestions::IdSuggestionTokenInfo &info, IdSuggestionsEditWidget *isew, QWidget *parent) : TokenWidget(parent) { setTitle(i18n("Type")); QBoxLayout *boxLayout = new QVBoxLayout(); boxLayout->setMargin(0); - comboBoxChangeCase = new KComboBox(false, this); + comboBoxChangeCase = new QComboBox(this); comboBoxChangeCase->addItem(i18n("No change"), IdSuggestions::ccNoChange); comboBoxChangeCase->addItem(i18n("To upper case"), IdSuggestions::ccToUpper); comboBoxChangeCase->addItem(i18n("To lower case"), IdSuggestions::ccToLower); comboBoxChangeCase->addItem(i18n("To CamelCase"), IdSuggestions::ccToCamelCase); formLayout->addRow(i18n("Change casing:"), comboBoxChangeCase); comboBoxChangeCase->setCurrentIndex(static_cast<int>(info.caseChange)); /// enum has numbers assigned to cases and combo box has same indices spinBoxLength = new QSpinBox(this); formLayout->addRow(i18n("Only first characters:"), spinBoxLength); spinBoxLength->setSpecialValueText(i18n("No limitation")); spinBoxLength->setMinimum(0); spinBoxLength->setMaximum(9); spinBoxLength->setValue(info.len == 0 || info.len > 9 ? 0 : info.len); connect(comboBoxChangeCase, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), isew, &IdSuggestionsEditWidget::updatePreview); connect(spinBoxLength, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), isew, &IdSuggestionsEditWidget::updatePreview); } QString toString() const override { QString result = QStringLiteral("e"); if (spinBoxLength->value() > 0) result.append(QString::number(spinBoxLength->value())); const IdSuggestions::CaseChange caseChange = static_cast<IdSuggestions::CaseChange>(comboBoxChangeCase->currentIndex()); if (caseChange == IdSuggestions::ccToLower) result.append(QStringLiteral("l")); else if (caseChange == IdSuggestions::ccToUpper) result.append(QStringLiteral("u")); else if (caseChange == IdSuggestions::ccToCamelCase) result.append(QStringLiteral("c")); return result; } }; /** * @author Thomas Fischer */ class TextWidget : public TokenWidget { Q_OBJECT private: - KLineEdit *lineEditText; + QLineEdit *lineEditText; public: TextWidget(const QString &text, IdSuggestionsEditWidget *isew, QWidget *parent) : TokenWidget(parent) { setTitle(i18n("Text")); - lineEditText = new KLineEdit(this); + lineEditText = new QLineEdit(this); formLayout->addRow(i18n("Text:"), lineEditText); lineEditText->setText(text); - connect(lineEditText, &KLineEdit::textEdited, isew, &IdSuggestionsEditWidget::updatePreview); + connect(lineEditText, &QLineEdit::textEdited, isew, &IdSuggestionsEditWidget::updatePreview); } QString toString() const override { QString result = QStringLiteral("\"") + lineEditText->text(); return result; } }; class IdSuggestionsEditWidget::IdSuggestionsEditWidgetPrivate { private: IdSuggestionsEditWidget *p; public: enum TokenType {ttTitle, ttAuthor, ttYear, ttJournal, ttType, ttText, ttVolume, ttPageNumber}; QWidget *container; QBoxLayout *containerLayout; QList<TokenWidget *> widgetList; QLabel *labelPreview; QPushButton *buttonAddTokenAtTop, *buttonAddTokenAtBottom; const Entry *previewEntry; QSignalMapper *signalMapperRemove, *signalMapperMoveUp, *signalMapperMoveDown; QScrollArea *area; IdSuggestionsEditWidgetPrivate(const Entry *pe, IdSuggestionsEditWidget *parent) : p(parent), previewEntry(pe) { setupGUI(); } void setupGUI() { QGridLayout *layout = new QGridLayout(p); labelPreview = new QLabel(p); layout->addWidget(labelPreview, 0, 0, 1, 1); layout->setColumnStretch(0, 100); area = new QScrollArea(p); layout->addWidget(area, 1, 0, 1, 1); area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); container = new QWidget(area); area->setWidget(container); area->setWidgetResizable(true); containerLayout = new QVBoxLayout(container); area->setMinimumSize(384, 256); buttonAddTokenAtTop = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add at top"), container); containerLayout->addWidget(buttonAddTokenAtTop, 0); containerLayout->addStretch(1); buttonAddTokenAtBottom = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add at bottom"), container); containerLayout->addWidget(buttonAddTokenAtBottom, 0); QMenu *menuAddToken = new QMenu(p); QSignalMapper *signalMapperAddMenu = new QSignalMapper(p); buttonAddTokenAtTop->setMenu(menuAddToken); QAction *action = menuAddToken->addAction(i18n("Title"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, -ttTitle); action = menuAddToken->addAction(i18n("Author"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, -ttAuthor); action = menuAddToken->addAction(i18n("Year"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, -ttYear); action = menuAddToken->addAction(i18n("Journal"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, -ttJournal); action = menuAddToken->addAction(i18n("Type"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, -ttType); action = menuAddToken->addAction(i18n("Volume"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, -ttVolume); action = menuAddToken->addAction(i18n("Page Number"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, -ttPageNumber); action = menuAddToken->addAction(i18n("Text"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, -ttText); connect(signalMapperAddMenu, static_cast<void(QSignalMapper::*)(int)>(&QSignalMapper::mapped), p, &IdSuggestionsEditWidget::addToken); menuAddToken = new QMenu(p); signalMapperAddMenu = new QSignalMapper(p); buttonAddTokenAtBottom->setMenu(menuAddToken); action = menuAddToken->addAction(i18n("Title"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, ttTitle); action = menuAddToken->addAction(i18n("Author"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, ttAuthor); action = menuAddToken->addAction(i18n("Year"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, ttYear); action = menuAddToken->addAction(i18n("Journal"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, ttJournal); action = menuAddToken->addAction(i18n("Type"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, ttType); action = menuAddToken->addAction(i18n("Volume"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, ttVolume); action = menuAddToken->addAction(i18n("Page Number"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, ttPageNumber); action = menuAddToken->addAction(i18n("Text"), signalMapperAddMenu, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperAddMenu->setMapping(action, ttText); connect(signalMapperAddMenu, static_cast<void(QSignalMapper::*)(int)>(&QSignalMapper::mapped), p, &IdSuggestionsEditWidget::addToken); signalMapperMoveUp = new QSignalMapper(p); connect(signalMapperMoveUp, static_cast<void(QSignalMapper::*)(QWidget *)>(&QSignalMapper::mapped), p, &IdSuggestionsEditWidget::moveUpToken); signalMapperMoveDown = new QSignalMapper(p); connect(signalMapperMoveDown, static_cast<void(QSignalMapper::*)(QWidget *)>(&QSignalMapper::mapped), p, &IdSuggestionsEditWidget::moveDownToken); signalMapperRemove = new QSignalMapper(p); connect(signalMapperRemove, static_cast<void(QSignalMapper::*)(QWidget *)>(&QSignalMapper::mapped), p, &IdSuggestionsEditWidget::removeToken); } void addManagementButtons(TokenWidget *tokenWidget) { if (tokenWidget != nullptr) { QPushButton *buttonUp = new QPushButton(QIcon::fromTheme(QStringLiteral("go-up")), QString(), tokenWidget); QPushButton *buttonDown = new QPushButton(QIcon::fromTheme(QStringLiteral("go-down")), QString(), tokenWidget); QPushButton *buttonRemove = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove")), QString(), tokenWidget); tokenWidget->addButtons(buttonUp, buttonDown, buttonRemove); connect(buttonUp, &QPushButton::clicked, signalMapperMoveUp, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperMoveUp->setMapping(buttonUp, tokenWidget); connect(buttonDown, &QPushButton::clicked, signalMapperMoveDown, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperMoveDown->setMapping(buttonDown, tokenWidget); connect(buttonRemove, &QPushButton::clicked, signalMapperRemove, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); signalMapperRemove->setMapping(buttonRemove, tokenWidget); } } void add(TokenType tokenType, bool atTop) { TokenWidget *tokenWidget = nullptr; switch (tokenType) { case ttTitle: { struct IdSuggestions::IdSuggestionTokenInfo info; info.inBetween = QString(); info.len = -1; info.startWord = 0; info.endWord = 0x00ffffff; info.lastWord = false; info.caseChange = IdSuggestions::ccNoChange; tokenWidget = new TitleWidget(info, true, p, container); } break; case ttAuthor: { struct IdSuggestions::IdSuggestionTokenInfo info; info.inBetween = QString(); info.len = -1; info.startWord = 0; info.endWord = 0x00ffffff; info.lastWord = false; info.caseChange = IdSuggestions::ccNoChange; tokenWidget = new AuthorWidget(info, p, container); } break; case ttYear: tokenWidget = new YearWidget(4, p, container); break; case ttJournal: { struct IdSuggestions::IdSuggestionTokenInfo info; info.inBetween = QString(); info.len = 1; info.startWord = 0; info.endWord = 0x00ffffff; info.lastWord = false; info.caseChange = IdSuggestions::ccNoChange; tokenWidget = new JournalWidget(info, true, p, container); } break; case ttType: { struct IdSuggestions::IdSuggestionTokenInfo info; info.inBetween = QString(); info.len = -1; info.startWord = 0; info.endWord = 0x00ffffff; info.lastWord = false; info.caseChange = IdSuggestions::ccNoChange; tokenWidget = new TypeWidget(info, p, container); } break; case ttText: tokenWidget = new TextWidget(QString(), p, container); break; case ttVolume: tokenWidget = new VolumeWidget(p, container); break; case ttPageNumber: tokenWidget = new PageNumberWidget(p, container); break; } if (tokenWidget != nullptr) { const int pos = atTop ? 1 : containerLayout->count() - 2; atTop ? widgetList.prepend(tokenWidget) : widgetList.append(tokenWidget); containerLayout->insertWidget(pos, tokenWidget, 1); addManagementButtons(tokenWidget); } } void reset(const QString &formatString) { while (!widgetList.isEmpty()) delete widgetList.takeFirst(); const QStringList tokenList = formatString.split(QStringLiteral("|"), QString::SkipEmptyParts); for (const QString &token : tokenList) { TokenWidget *tokenWidget = nullptr; if (token[0] == 'a' || token[0] == 'A' || token[0] == 'z') { struct IdSuggestions::IdSuggestionTokenInfo info = p->evalToken(token.mid(1)); /// Support deprecated 'a' and 'z' cases if (token[0] == 'a') info.startWord = info.endWord = 0; else if (token[0] == 'z') { info.startWord = 1; info.endWord = 0x00ffffff; } tokenWidget = new AuthorWidget(info, p, container); widgetList << tokenWidget; containerLayout->insertWidget(containerLayout->count() - 2, tokenWidget, 1); } else if (token[0] == 'y') { tokenWidget = new YearWidget(2, p, container); widgetList << tokenWidget; containerLayout->insertWidget(containerLayout->count() - 2, tokenWidget, 1); } else if (token[0] == 'Y') { tokenWidget = new YearWidget(4, p, container); widgetList << tokenWidget; containerLayout->insertWidget(containerLayout->count() - 2, tokenWidget, 1); } else if (token[0] == 't' || token[0] == 'T') { struct IdSuggestions::IdSuggestionTokenInfo info = p->evalToken(token.mid(1)); tokenWidget = new TitleWidget(info, token[0].isUpper(), p, container); widgetList << tokenWidget; containerLayout->insertWidget(containerLayout->count() - 2, tokenWidget, 1); } else if (token[0] == 'j' || token[0] == 'J') { struct IdSuggestions::IdSuggestionTokenInfo info = p->evalToken(token.mid(1)); tokenWidget = new JournalWidget(info, token[0].isUpper(), p, container); widgetList << tokenWidget; containerLayout->insertWidget(containerLayout->count() - 2, tokenWidget, 1); } else if (token[0] == 'e') { struct IdSuggestions::IdSuggestionTokenInfo info = p->evalToken(token.mid(1)); tokenWidget = new TypeWidget(info, p, container); widgetList << tokenWidget; containerLayout->insertWidget(containerLayout->count() - 2, tokenWidget, 1); } else if (token[0] == 'v') { tokenWidget = new VolumeWidget(p, container); widgetList << tokenWidget; containerLayout->insertWidget(containerLayout->count() - 2, tokenWidget, 1); } else if (token[0] == 'p') { tokenWidget = new PageNumberWidget(p, container); widgetList << tokenWidget; containerLayout->insertWidget(containerLayout->count() - 2, tokenWidget, 1); } else if (token[0] == '"') { tokenWidget = new TextWidget(token.mid(1), p, container); widgetList << tokenWidget; containerLayout->insertWidget(containerLayout->count() - 2, tokenWidget, 1); } if (tokenWidget != nullptr) addManagementButtons(tokenWidget); } p->updatePreview(); } QString apply() { QStringList result; result.reserve(widgetList.size()); for (TokenWidget *widget : const_cast<const QList<TokenWidget *> &>(widgetList)) result << widget->toString(); return result.join(QStringLiteral("|")); } }; IdSuggestionsEditWidget::IdSuggestionsEditWidget(const Entry *previewEntry, QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), IdSuggestions(), d(new IdSuggestionsEditWidgetPrivate(previewEntry, this)) { /// nothing } IdSuggestionsEditWidget::~IdSuggestionsEditWidget() { // TODO } void IdSuggestionsEditWidget::setFormatString(const QString &formatString) { d->reset(formatString); } QString IdSuggestionsEditWidget::formatString() const { return d->apply(); } void IdSuggestionsEditWidget::updatePreview() { const QString formatString = d->apply(); d->labelPreview->setText(formatId(*d->previewEntry, formatString)); d->labelPreview->setToolTip(i18n("<qt>Structure:<ul><li>%1</li></ul>Example: %2</qt>", formatStrToHuman(formatString).join(QStringLiteral("</li><li>")), formatId(*d->previewEntry, formatString))); } void IdSuggestionsEditWidget::moveUpToken(QWidget *widget) { TokenWidget *tokenWidget = static_cast<TokenWidget *>(widget); int curPos = d->widgetList.indexOf(tokenWidget); if (curPos > 0) { d->widgetList.removeAt(curPos); const int layoutPos = d->containerLayout->indexOf(tokenWidget); d->containerLayout->removeWidget(tokenWidget); d->widgetList.insert(curPos - 1, tokenWidget); d->containerLayout->insertWidget(layoutPos - 1, tokenWidget, 1); updatePreview(); } } void IdSuggestionsEditWidget::moveDownToken(QWidget *widget) { TokenWidget *tokenWidget = static_cast<TokenWidget *>(widget); int curPos = d->widgetList.indexOf(tokenWidget); if (curPos < d->widgetList.size() - 1) { d->widgetList.removeAt(curPos); const int layoutPos = d->containerLayout->indexOf(tokenWidget); d->containerLayout->removeWidget(tokenWidget); d->widgetList.insert(curPos + 1, tokenWidget); d->containerLayout->insertWidget(layoutPos + 1, tokenWidget, 1); updatePreview(); } } void IdSuggestionsEditWidget::removeToken(QWidget *widget) { TokenWidget *tokenWidget = static_cast<TokenWidget *>(widget); d->widgetList.removeOne(tokenWidget); d->containerLayout->removeWidget(tokenWidget); tokenWidget->deleteLater(); updatePreview(); } void IdSuggestionsEditWidget::addToken(int cmd) { if (cmd < 0) { d->add(static_cast<IdSuggestionsEditWidgetPrivate::TokenType>(-cmd), true); d->area->ensureWidgetVisible(d->buttonAddTokenAtTop); // FIXME does not work as intended } else { d->add(static_cast<IdSuggestionsEditWidgetPrivate::TokenType>(cmd), false); d->area->ensureWidgetVisible(d->buttonAddTokenAtBottom); // FIXME does not work as intended } updatePreview(); } IdSuggestionsEditDialog::IdSuggestionsEditDialog(QWidget *parent, Qt::WindowFlags flags) : QDialog(parent, flags) { setWindowTitle(i18n("Edit Id Suggestion")); } IdSuggestionsEditDialog::~IdSuggestionsEditDialog() { /// nothing } QString IdSuggestionsEditDialog::editSuggestion(const Entry *previewEntry, const QString &suggestion, QWidget *parent) { QPointer<IdSuggestionsEditDialog> dlg = new IdSuggestionsEditDialog(parent); QBoxLayout *boxLayout = new QVBoxLayout(dlg); IdSuggestionsEditWidget *widget = new IdSuggestionsEditWidget(previewEntry, dlg); boxLayout->addWidget(widget); QDialogButtonBox *dbb = new QDialogButtonBox(dlg); dbb->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); boxLayout->addWidget(dbb); connect(dbb->button(QDialogButtonBox::Ok), &QPushButton::clicked, dlg.data(), &QDialog::accept); connect(dbb->button(QDialogButtonBox::Cancel), &QPushButton::clicked, dlg.data(), &QDialog::reject); widget->setFormatString(suggestion); if (dlg->exec() == Accepted) { const QString formatString = widget->formatString(); delete dlg; return formatString; } delete dlg; /// Return unmodified original suggestion return suggestion; } #include "settingsidsuggestionseditor.moc" diff --git a/src/gui/preferences/settingsuserinterfacewidget.cpp b/src/gui/preferences/settingsuserinterfacewidget.cpp index 15c45115..7ba3d2bf 100644 --- a/src/gui/preferences/settingsuserinterfacewidget.cpp +++ b/src/gui/preferences/settingsuserinterfacewidget.cpp @@ -1,106 +1,106 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "settingsuserinterfacewidget.h" #include <QFormLayout> #include <QCheckBox> +#include <QComboBox> #include <QBoxLayout> -#include <KComboBox> #include <KLocalizedString> #include "preferences.h" #include "elementwidgets.h" #include "guihelper.h" #include "models/filemodel.h" class SettingsUserInterfaceWidget::SettingsUserInterfaceWidgetPrivate { private: SettingsUserInterfaceWidget *p; - KComboBox *comboBoxElementDoubleClickAction; + QComboBox *comboBoxElementDoubleClickAction; public: SettingsUserInterfaceWidgetPrivate(SettingsUserInterfaceWidget *parent) : p(parent) { setupGUI(); } void loadState() { const int row = qMax(0, GUIHelper::selectValue(comboBoxElementDoubleClickAction->model(), static_cast<int>(Preferences::instance().fileViewDoubleClickAction()), Qt::UserRole)); comboBoxElementDoubleClickAction->setCurrentIndex(row); } void saveState() { Preferences::instance().setFileViewDoubleClickAction(static_cast<Preferences::FileViewDoubleClickAction>(comboBoxElementDoubleClickAction->currentData().toInt())); } void resetToDefaults() { const int row = qMax(0, GUIHelper::selectValue(comboBoxElementDoubleClickAction->model(), static_cast<int>(Preferences::defaultFileViewDoubleClickAction), Qt::UserRole)); comboBoxElementDoubleClickAction->setCurrentIndex(row); } void setupGUI() { QFormLayout *layout = new QFormLayout(p); - comboBoxElementDoubleClickAction = new KComboBox(p); + comboBoxElementDoubleClickAction = new QComboBox(p); comboBoxElementDoubleClickAction->setObjectName(QStringLiteral("comboBoxElementDoubleClickAction")); for (QVector<QPair<Preferences::FileViewDoubleClickAction, QString>>::ConstIterator it = Preferences::availableFileViewDoubleClickActions.constBegin(); it != Preferences::availableFileViewDoubleClickActions.constEnd(); ++it) comboBoxElementDoubleClickAction->addItem(it->second, static_cast<int>(it->first)); layout->addRow(i18n("When double-clicking an element:"), comboBoxElementDoubleClickAction); connect(comboBoxElementDoubleClickAction, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), p, &SettingsUserInterfaceWidget::changed); } }; SettingsUserInterfaceWidget::SettingsUserInterfaceWidget(QWidget *parent) : SettingsAbstractWidget(parent), d(new SettingsUserInterfaceWidgetPrivate(this)) { d->loadState(); } QString SettingsUserInterfaceWidget::label() const { return i18n("User Interface"); } QIcon SettingsUserInterfaceWidget::icon() const { return QIcon::fromTheme(QStringLiteral("user-identity")); } SettingsUserInterfaceWidget::~SettingsUserInterfaceWidget() { delete d; } void SettingsUserInterfaceWidget::loadState() { d->loadState(); } void SettingsUserInterfaceWidget::saveState() { d->saveState(); } void SettingsUserInterfaceWidget::resetToDefaults() { d->resetToDefaults(); } diff --git a/src/gui/valuelistmodel.cpp b/src/gui/valuelistmodel.cpp index b48a7de8..d4861d84 100644 --- a/src/gui/valuelistmodel.cpp +++ b/src/gui/valuelistmodel.cpp @@ -1,572 +1,572 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "valuelistmodel.h" #include <typeinfo> #include <QApplication> #include <QTextDocument> #include <QAbstractTextDocumentLayout> #include <QListView> +#include <QLineEdit> #include <QGridLayout> #include <QStringListModel> #include <QPainter> #include <QFrame> #include <QLayout> #include <QHeaderView> +#include <QComboBox> -#include <KComboBox> #include <KLocalizedString> #include <KColorScheme> -#include <KLineEdit> #include "fieldlineedit.h" #include "bibtexfields.h" #include "entry.h" #include "preferences.h" #include "models/filemodel.h" #include "logging_gui.h" QWidget *ValueListDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &sovi, const QModelIndex &index) const { if (index.column() == 0) { const FieldDescription &fd = BibTeXFields::instance().find(m_fieldName); FieldLineEdit *fieldLineEdit = new FieldLineEdit(fd.preferredTypeFlag, fd.typeFlags, false, parent); fieldLineEdit->setAutoFillBackground(true); return fieldLineEdit; } else return QStyledItemDelegate::createEditor(parent, sovi, index); } void ValueListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { if (index.column() == 0) { FieldLineEdit *fieldLineEdit = qobject_cast<FieldLineEdit *>(editor); if (fieldLineEdit != nullptr) fieldLineEdit->reset(index.model()->data(index, Qt::EditRole).value<Value>()); } } void ValueListDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { FieldLineEdit *fieldLineEdit = qobject_cast<FieldLineEdit *>(editor); if (fieldLineEdit != nullptr) { Value v; fieldLineEdit->apply(v); if (v.count() == 1) /// field should contain exactly one value item (no zero, not two or more) model->setData(index, QVariant::fromValue(v)); } } QSize ValueListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size = QStyledItemDelegate::sizeHint(option, index); size.setHeight(qMax(size.height(), option.fontMetrics.height() * 3 / 2)); // TODO calculate height better return size; } void ValueListDelegate::commitAndCloseEditor() { - KLineEdit *editor = qobject_cast<KLineEdit *>(sender()); + QLineEdit *editor = qobject_cast<QLineEdit *>(sender()); emit commitData(editor); emit closeEditor(editor); } void ValueListDelegate::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const { QStyledItemDelegate::initStyleOption(option, index); if (option->decorationPosition != QStyleOptionViewItem::Top) { /// remove text from style (do not draw text) option->text.clear(); } } void ValueListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &_option, const QModelIndex &index) const { QStyleOptionViewItem option = _option; /// code heavily inspired by kdepimlibs-4.6.3/akonadi/collectionstatisticsdelegate.cpp /// save painter's state, restored before leaving this function painter->save(); /// first, paint the basic, but without the text. We remove the text /// in initStyleOption(), which gets called by QStyledItemDelegate::paint(). QStyledItemDelegate::paint(painter, option, index); /// now, we retrieve the correct style option by calling intiStyleOption from /// the superclass. QStyledItemDelegate::initStyleOption(&option, index); QString field = option.text; /// now calculate the rectangle for the text QStyle *s = m_parent->style(); const QWidget *widget = option.widget; const QRect textRect = s->subElementRect(QStyle::SE_ItemViewItemText, &option, widget); if (option.state & QStyle::State_Selected) { /// selected lines are drawn with different color painter->setPen(option.palette.highlightedText().color()); } /// count will be empty unless only one column is shown const QString count = index.column() == 0 && index.model()->columnCount() == 1 ? QString(QStringLiteral(" (%1)")).arg(index.data(ValueListModel::CountRole).toInt()) : QString(); /// squeeze the folder text if it is to big and calculate the rectangles /// where the folder text and the unread count will be drawn to QFontMetrics fm(painter->fontMetrics()); int countWidth = fm.width(count); int fieldWidth = fm.width(field); if (countWidth + fieldWidth > textRect.width()) { /// text plus count is too wide for column, cut text and insert "..." field = fm.elidedText(field, Qt::ElideRight, textRect.width() - countWidth - 8); fieldWidth = textRect.width() - countWidth - 12; } /// determine rects to draw field int top = textRect.top() + (textRect.height() - fm.height()) / 2; QRect fieldRect = textRect; QRect countRect = textRect; fieldRect.setTop(top); fieldRect.setHeight(fm.height()); if (m_parent->header()->visualIndex(index.column()) == 0) { /// left-align text fieldRect.setLeft(fieldRect.left() + 4); ///< hm, indent necessary? fieldRect.setRight(fieldRect.left() + fieldWidth); } else { /// right-align text fieldRect.setRight(fieldRect.right() - 4); ///< hm, indent necessary? fieldRect.setLeft(fieldRect.right() - fieldWidth); ///< hm, indent necessary? } /// draw field name painter->drawText(fieldRect, Qt::AlignLeft, field); if (!count.isEmpty()) { /// determine rects to draw count countRect.setTop(top); countRect.setHeight(fm.height()); countRect.setLeft(fieldRect.right()); /// use bold font QFont font = painter->font(); font.setBold(true); painter->setFont(font); /// determine color for count number const QColor countColor = (option.state & QStyle::State_Selected) ? KColorScheme(QPalette::Active, KColorScheme::Selection).foreground(KColorScheme::LinkText).color() : KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color(); painter->setPen(countColor); /// draw count painter->drawText(countRect, Qt::AlignLeft, count); } /// restore painter's state painter->restore(); } ValueListModel::ValueListModel(const File *bibtexFile, const QString &fieldName, QObject *parent) : QAbstractTableModel(parent), file(bibtexFile), fName(fieldName.toLower()), showCountColumn(true), sortBy(SortByText) { readConfiguration(); updateValues(); NotificationHub::registerNotificationListener(this, NotificationHub::EventConfigurationChanged); } int ValueListModel::rowCount(const QModelIndex &parent) const { return parent == QModelIndex() ? values.count() : 0; } int ValueListModel::columnCount(const QModelIndex &parent) const { return parent == QModelIndex() ? (showCountColumn ? 2 : 1) : 0; } QVariant ValueListModel::data(const QModelIndex &index, int role) const { if (index.row() >= values.count() || index.column() >= 2) return QVariant(); if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { if (index.column() == 0) { if (fName == Entry::ftColor) { QString text = values[index.row()].text; if (text.isEmpty()) return QVariant(); QString colorText = colorToLabel[text]; if (colorText.isEmpty()) return QVariant(text); return QVariant(colorText); } else return QVariant(values[index.row()].text); } else return QVariant(values[index.row()].count); } else if (role == SortRole) { static const QRegularExpression ignoredInSorting(QStringLiteral("[{}\\\\]+")); QString buffer = values[index.row()].sortBy.isEmpty() ? values[index.row()].text : values[index.row()].sortBy; buffer = buffer.remove(ignoredInSorting).toLower(); if ((showCountColumn && index.column() == 1) || (!showCountColumn && sortBy == SortByCount)) { /// Sort by string consisting of a zero-padded count and the lower-case text, /// for example "0000000051keyword" /// Used if (a) two columns are shown (showCountColumn is true) and column 1 /// (the count column) is to be sorted or (b) if only one column is shown /// (showCountColumn is false) and this single column is to be sorted by count. return QString(QStringLiteral("%1%2")).arg(values[index.row()].count, 10, 10, QLatin1Char('0')).arg(buffer); } else { /// Otherwise use lower-case text for sorting return QVariant(buffer); } } else if (role == SearchTextRole) { return QVariant(values[index.row()].text); } else if (role == Qt::EditRole) return QVariant::fromValue(values[index.row()].value); else if (role == CountRole) return QVariant(values[index.row()].count); else return QVariant(); } bool ValueListModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_ASSERT_X(file != nullptr, "ValueListModel::setData", "You cannot set data if there is no BibTeX file associated with this value list."); /// Continue only if in edit role and first column is to be changed if (role == Qt::EditRole && index.column() == 0) { /// Fetch the string as it was shown before the editing started QString origText = data(index, Qt::DisplayRole).toString(); /// Special treatment for colors if (fName == Entry::ftColor) { /// for colors, convert color (RGB) to the associated label QString color = colorToLabel.key(origText); if (!color.isEmpty()) origText = color; } /// Retrieve the Value object containing the user-entered data Value newValue = value.value<Value>(); /// nice variable names ... ;-) if (newValue.isEmpty()) { qCWarning(LOG_KBIBTEX_GUI) << "Cannot replace with empty value"; return false; } /// Fetch the string representing the new, user-entered value const QString newText = PlainTextValue::text(newValue); if (newText == origText) { qCWarning(LOG_KBIBTEX_GUI) << "Skipping to replace value with itself"; return false; } bool success = searchAndReplaceValueInEntries(index, newValue) && searchAndReplaceValueInModel(index, newValue); return success; } return false; } Qt::ItemFlags ValueListModel::flags(const QModelIndex &index) const { Qt::ItemFlags result = QAbstractTableModel::flags(index); /// make first column editable if (index.column() == 0) result |= Qt::ItemIsEditable; return result; } QVariant ValueListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (section >= 2 || orientation != Qt::Horizontal || role != Qt::DisplayRole) return QVariant(); else if ((section == 0 && columnCount() == 2) || (columnCount() == 1 && sortBy == SortByText)) return QVariant(i18n("Value")); else return QVariant(i18n("Count")); } void ValueListModel::removeValue(const QModelIndex &index) { removeValueFromEntries(index); removeValueFromModel(index); } void ValueListModel::setShowCountColumn(bool showCountColumn) { beginResetModel(); this->showCountColumn = showCountColumn; endResetModel(); } void ValueListModel::setSortBy(SortBy sortBy) { beginResetModel(); this->sortBy = sortBy; endResetModel(); } void ValueListModel::notificationEvent(int eventId) { if (eventId == NotificationHub::EventConfigurationChanged) { beginResetModel(); readConfiguration(); endResetModel(); } } void ValueListModel::readConfiguration() { /// load mapping from color value to label colorToLabel.clear(); for (QVector<QPair<QColor, QString>>::ConstIterator it = Preferences::instance().colorCodes().constBegin(); it != Preferences::instance().colorCodes().constEnd(); ++it) colorToLabel.insert(it->first.name(), it->second); } void ValueListModel::updateValues() { values.clear(); if (file == nullptr) return; for (const auto &element : const_cast<const File &>(*file)) { QSharedPointer<const Entry> entry = element.dynamicCast<const Entry>(); if (!entry.isNull()) { for (Entry::ConstIterator eit = entry->constBegin(); eit != entry->constEnd(); ++eit) { QString key = eit.key().toLower(); if (key == fName) { insertValue(eit.value()); break; } if (eit.value().isEmpty()) qCWarning(LOG_KBIBTEX_GUI) << "value for key" << key << "in entry" << entry->id() << "is empty"; } } } } void ValueListModel::insertValue(const Value &value) { for (const QSharedPointer<ValueItem> &item : value) { const QString text = PlainTextValue::text(*item); if (text.isEmpty()) continue; ///< skip empty values int index = indexOf(text); if (index < 0) { /// previously unknown text ValueLine newValueLine; newValueLine.text = text; newValueLine.count = 1; newValueLine.value.append(item); /// memorize sorting criterium: /// * for persons, use last name first /// * in any case, use lower case const QSharedPointer<Person> person = item.dynamicCast<Person>(); newValueLine.sortBy = person.isNull() ? text.toLower() : person->lastName().toLower() + QStringLiteral(" ") + person->firstName().toLower(); values << newValueLine; } else { ++values[index].count; } } } int ValueListModel::indexOf(const QString &text) { QString color; QString cmpText = text; if (fName == Entry::ftColor && !(color = colorToLabel.key(text, QString())).isEmpty()) cmpText = color; if (cmpText.isEmpty()) qCWarning(LOG_KBIBTEX_GUI) << "Should never happen"; int i = 0; /// this is really slow for large data sets: O(n^2) /// maybe use a hash table instead? for (const ValueLine &valueLine : const_cast<const ValueLineList &>(values)) { if (valueLine.text == cmpText) return i; ++i; } return -1; } bool ValueListModel::searchAndReplaceValueInEntries(const QModelIndex &index, const Value &newValue) { /// Fetch the string representing the new, user-entered value const QString newText = PlainTextValue::text(newValue); if (newText.isEmpty()) return false; /// Fetch the string as it was shown before the editing started QString origText = data(index, Qt::DisplayRole).toString(); /// Special treatment for colors if (fName == Entry::ftColor) { /// for colors, convert color (RGB) to the associated label QString color = colorToLabel.key(origText); if (!color.isEmpty()) origText = color; } /// Go through all elements in the current file for (const QSharedPointer<Element> &element : const_cast<const File &>(*file)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); /// Process only Entry objects if (!entry.isNull()) { /// Go through every key-value pair in entry (author, title, ...) for (Entry::Iterator eit = entry->begin(); eit != entry->end(); ++eit) { /// Fetch key-value pair's key const QString key = eit.key().toLower(); /// Process only key-value pairs that are filtered for (e.g. only keywords) if (key == fName) { eit.value().replace(origText, newValue.first()); break; } } } } return true; } bool ValueListModel::searchAndReplaceValueInModel(const QModelIndex &index, const Value &newValue) { /// Fetch the string representing the new, user-entered value const QString newText = PlainTextValue::text(newValue); if (newText.isEmpty()) return false; const int row = index.row(); /// Test if user-entered text exists already in model's data /// newTextAlreadyInListIndex will be row of duplicate or /// -1 if new text is unique int newTextAlreadyInListIndex = -1; for (int r = values.count() - 1; newTextAlreadyInListIndex < 0 && r >= 0; --r) { if (row != r && values[r].text == newText) newTextAlreadyInListIndex = r; } if (newTextAlreadyInListIndex < 0) { /// User-entered text is unique, so simply replace /// old text with new text values[row].text = newText; values[row].value = newValue; const QSharedPointer<Person> person = newValue.first().dynamicCast<Person>(); values[row].sortBy = person.isNull() ? QString() : person->lastName() + QStringLiteral(" ") + person->firstName(); } else { /// The user-entered text existed before const int lastRow = values.count() - 1; if (row != lastRow) { /// Unless duplicate is last one in list, /// overwrite edited row with last row's value values[row].text = values[lastRow].text; values[row].value = values[lastRow].value; values[row].sortBy = values[lastRow].sortBy; } /// Remove last row, which is no longer used beginRemoveRows(QModelIndex(), lastRow, lastRow); values.remove(lastRow); endRemoveRows(); } /// Notify Qt about data changed emit dataChanged(index, index); return true; } void ValueListModel::removeValueFromEntries(const QModelIndex &index) { /// Retrieve the Value object containing the user-entered data const Value toBeDeletedValue = values[index.row()].value; if (toBeDeletedValue.isEmpty()) { return; } const QString toBeDeletedText = PlainTextValue::text(toBeDeletedValue); if (toBeDeletedText.isEmpty()) { return; } /// Go through all elements in the current file for (const QSharedPointer<Element> &element : const_cast<const File &>(*file)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); /// Process only Entry objects if (!entry.isNull()) { /// Go through every key-value pair in entry (author, title, ...) for (Entry::Iterator eit = entry->begin(); eit != entry->end(); ++eit) { /// Fetch key-value pair's key const QString key = eit.key().toLower(); /// Process only key-value pairs that are filtered for (e.g. only keywords) if (key == fName) { /// Fetch the key-value pair's value's textual representation const QString valueFullText = PlainTextValue::text(eit.value()); if (valueFullText == toBeDeletedText) { /// If the key-value pair's value's textual representation is the same /// as the value to be delted, remove this key-value pair /// This test is usually true for keys like title, year, or edition. entry->remove(key); /// This would break the Iterator, but code "breakes" from loop anyways } else { /// The test above failed, but the delete operation may have /// to be applied to a ValueItem inside the value. /// Possible keys for such a case include author, editor, or keywords. /// Process each ValueItem inside this Value for (Value::Iterator vit = eit.value().begin(); vit != eit.value().end();) { /// Similar procedure as for full values above: /// If a ValueItem's textual representation is the same /// as the shown string which has be deleted, remove the /// ValueItem from this Value. If the Value becomes empty, /// remove Value as well. const QString valueItemText = PlainTextValue::text(* (*vit)); if (valueItemText == toBeDeletedText) { /// Erase old ValueItem from this Value vit = eit.value().erase(vit); } else ++vit; } if (eit.value().isEmpty()) { /// This value does no longer contain any ValueItems. entry->remove(key); /// This would break the Iterator, but code "breakes" from loop anyways } } break; } } } } } void ValueListModel::removeValueFromModel(const QModelIndex &index) { const int row = index.row(); const int lastRow = values.count() - 1; if (row != lastRow) { /// Unless duplicate is last one in list, /// overwrite edited row with last row's value values[row].text = values[lastRow].text; values[row].value = values[lastRow].value; values[row].sortBy = values[lastRow].sortBy; emit dataChanged(index, index); } /// Remove last row, which is no longer used beginRemoveRows(QModelIndex(), lastRow, lastRow); values.remove(lastRow); endRemoveRows(); } diff --git a/src/gui/widgets/filesettingswidget.cpp b/src/gui/widgets/filesettingswidget.cpp index d2317517..161dd3e2 100644 --- a/src/gui/widgets/filesettingswidget.cpp +++ b/src/gui/widgets/filesettingswidget.cpp @@ -1,180 +1,180 @@ /***************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * *****************************************************************************/ #include "filesettingswidget.h" #include <QCheckBox> #include <QFormLayout> +#include <QComboBox> -#include <KComboBox> #include <KLocalizedString> #include "preferences.h" #include "italictextitemmodel.h" #include "file.h" #include "guihelper.h" #define createDelimiterString(a, b) (QString(QStringLiteral("%1%2%3")).arg(a).arg(QChar(8230)).arg(b)) FileSettingsWidget::FileSettingsWidget(QWidget *parent) : QWidget(parent), dummyPerson(Person(i18n("John"), i18n("Doe"), i18n("Jr."))), m_file(nullptr) { setupGUI(); } void FileSettingsWidget::resetToLoadedProperties() { loadProperties(m_file); } void FileSettingsWidget::loadProperties(File *file) { m_file = file; if (m_file == nullptr) return; /// Nothing to do if (file->hasProperty(File::Encoding)) { m_comboBoxEncodings->blockSignals(true); const QString encoding = file->property(File::Encoding).toString(); const int row = GUIHelper::selectValue(m_comboBoxEncodings->model(), encoding); m_comboBoxEncodings->setCurrentIndex(row); m_comboBoxEncodings->blockSignals(false); } if (file->hasProperty(File::StringDelimiter)) { m_comboBoxStringDelimiters->blockSignals(true); const QString stringDelimiter = file->property(File::StringDelimiter).toString(); const int row = GUIHelper::selectValue(m_comboBoxStringDelimiters->model(), createDelimiterString(stringDelimiter[0], stringDelimiter[1])); m_comboBoxStringDelimiters->setCurrentIndex(row); m_comboBoxStringDelimiters->blockSignals(false); } if (file->hasProperty(File::QuoteComment)) { m_comboBoxQuoteComment->blockSignals(true); const Preferences::QuoteComment quoteComment = static_cast<Preferences::QuoteComment>(file->property(File::QuoteComment).toInt()); const int row = qMax(0, GUIHelper::selectValue(m_comboBoxQuoteComment->model(), static_cast<int>(quoteComment), Qt::UserRole)); m_comboBoxQuoteComment->setCurrentIndex(row); m_comboBoxQuoteComment->blockSignals(false); } if (file->hasProperty(File::KeywordCasing)) { m_comboBoxKeywordCasing->blockSignals(true); const KBibTeX::Casing keywordCasing = static_cast<KBibTeX::Casing>(file->property(File::KeywordCasing).toInt()); m_comboBoxKeywordCasing->setCurrentIndex(static_cast<int>(keywordCasing)); m_comboBoxKeywordCasing->blockSignals(false); } if (file->hasProperty(File::ProtectCasing)) { m_checkBoxProtectCasing->blockSignals(true); m_checkBoxProtectCasing->setCheckState(static_cast<Qt::CheckState>(file->property(File::ProtectCasing).toInt())); m_checkBoxProtectCasing->blockSignals(false); } if (file->hasProperty(File::NameFormatting)) { m_comboBoxPersonNameFormatting->blockSignals(true); const int row = GUIHelper::selectValue(m_comboBoxPersonNameFormatting->model(), file->property(File::NameFormatting).toString(), ItalicTextItemModel::IdentifierRole); m_comboBoxPersonNameFormatting->setCurrentIndex(row); m_comboBoxPersonNameFormatting->blockSignals(false); } if (file->hasProperty(File::ListSeparator)) { m_comboBoxListSeparator->blockSignals(true); m_comboBoxListSeparator->setCurrentIndex(m_comboBoxListSeparator->findData(file->property(File::ListSeparator))); m_comboBoxListSeparator->blockSignals(false); } } void FileSettingsWidget::applyProperties() { saveProperties(m_file); } void FileSettingsWidget::saveProperties(File *file) { m_file = file; if (m_file == nullptr) return; file->setProperty(File::Encoding, m_comboBoxEncodings->currentText()); const QString stringDelimiter = m_comboBoxStringDelimiters->currentText(); file->setProperty(File::StringDelimiter, QString(stringDelimiter[0]) + stringDelimiter[stringDelimiter.length() - 1]); bool ok = false; const Preferences::QuoteComment quoteComment = static_cast<Preferences::QuoteComment>(m_comboBoxQuoteComment->currentData().toInt(&ok)); if (ok) file->setProperty(File::QuoteComment, static_cast<int>(quoteComment)); const KBibTeX::Casing keywordCasing = static_cast<KBibTeX::Casing>(m_comboBoxKeywordCasing->currentIndex()); file->setProperty(File::KeywordCasing, static_cast<int>(keywordCasing)); file->setProperty(File::ProtectCasing, static_cast<int>(m_checkBoxProtectCasing->checkState())); file->setProperty(File::NameFormatting, m_comboBoxPersonNameFormatting->itemData(m_comboBoxPersonNameFormatting->currentIndex(), ItalicTextItemModel::IdentifierRole)); file->setProperty(File::ListSeparator, m_comboBoxListSeparator->itemData(m_comboBoxListSeparator->currentIndex()).toString()); } void FileSettingsWidget::resetToDefaults() { if (m_file != nullptr) { m_file->setPropertiesToDefault(); loadProperties(m_file); } } void FileSettingsWidget::setupGUI() { QFormLayout *layout = new QFormLayout(this); - m_comboBoxEncodings = new KComboBox(false, this); + m_comboBoxEncodings = new QComboBox(this); m_comboBoxEncodings->setObjectName(QStringLiteral("comboBoxEncodings")); layout->addRow(i18n("Encoding:"), m_comboBoxEncodings); m_comboBoxEncodings->addItems(Preferences::availableBibTeXEncodings); connect(m_comboBoxEncodings, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &FileSettingsWidget::widgetsChanged); - m_comboBoxStringDelimiters = new KComboBox(false, this); + m_comboBoxStringDelimiters = new QComboBox(this); m_comboBoxStringDelimiters->setObjectName(QStringLiteral("comboBoxStringDelimiters")); layout->addRow(i18n("String Delimiters:"), m_comboBoxStringDelimiters); m_comboBoxStringDelimiters->addItem(createDelimiterString('"', '"')); m_comboBoxStringDelimiters->addItem(createDelimiterString('{', '}')); m_comboBoxStringDelimiters->addItem(createDelimiterString('(', ')')); connect(m_comboBoxStringDelimiters, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &FileSettingsWidget::widgetsChanged); - m_comboBoxQuoteComment = new KComboBox(false, this); + m_comboBoxQuoteComment = new QComboBox(this); layout->addRow(i18n("Comment Quoting:"), m_comboBoxQuoteComment); for (QVector<QPair<Preferences::QuoteComment, QString>>::ConstIterator it = Preferences::availableBibTeXQuoteComments.constBegin(); it != Preferences::availableBibTeXQuoteComments.constEnd(); ++it) m_comboBoxQuoteComment->addItem(it->second, static_cast<int>(it->first)); connect(m_comboBoxQuoteComment, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &FileSettingsWidget::widgetsChanged); - m_comboBoxKeywordCasing = new KComboBox(false, this); + m_comboBoxKeywordCasing = new QComboBox(this); layout->addRow(i18n("Keyword Casing:"), m_comboBoxKeywordCasing); m_comboBoxKeywordCasing->addItem(i18nc("Keyword Casing", "lowercase")); m_comboBoxKeywordCasing->addItem(i18nc("Keyword Casing", "Initial capital")); m_comboBoxKeywordCasing->addItem(i18nc("Keyword Casing", "UpperCamelCase")); m_comboBoxKeywordCasing->addItem(i18nc("Keyword Casing", "lowerCamelCase")); m_comboBoxKeywordCasing->addItem(i18nc("Keyword Casing", "UPPERCASE")); connect(m_comboBoxKeywordCasing, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &FileSettingsWidget::widgetsChanged); m_checkBoxProtectCasing = new QCheckBox(i18n("Protect Titles"), this); m_checkBoxProtectCasing->setTristate(true); layout->addRow(i18n("Protect Casing?"), m_checkBoxProtectCasing); connect(m_checkBoxProtectCasing, &QCheckBox::stateChanged, this, &FileSettingsWidget::widgetsChanged); - m_comboBoxPersonNameFormatting = new KComboBox(false, this); + m_comboBoxPersonNameFormatting = new QComboBox(this); m_comboBoxPersonNameFormatting->setObjectName(QStringLiteral("comboBoxPersonNameFormatting")); layout->addRow(i18n("Person Names Formatting:"), m_comboBoxPersonNameFormatting); connect(m_comboBoxPersonNameFormatting, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &FileSettingsWidget::widgetsChanged); ItalicTextItemModel *itim = new ItalicTextItemModel(this); itim->addItem(i18n("Use global settings"), QString(QString())); itim->addItem(Person::transcribePersonName(&dummyPerson, Preferences::personNameFormatFirstLast), Preferences::personNameFormatFirstLast); itim->addItem(Person::transcribePersonName(&dummyPerson, Preferences::personNameFormatLastFirst), Preferences::personNameFormatLastFirst); m_comboBoxPersonNameFormatting->setModel(itim); - m_comboBoxListSeparator = new KComboBox(false, this); + m_comboBoxListSeparator = new QComboBox(this); layout->addRow(i18n("List Separator"), m_comboBoxListSeparator); connect(m_comboBoxListSeparator, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &FileSettingsWidget::widgetsChanged); m_comboBoxListSeparator->addItem(QStringLiteral(";"), QVariant::fromValue<QString>(QStringLiteral("; "))); m_comboBoxListSeparator->addItem(QStringLiteral(","), QVariant::fromValue<QString>(QStringLiteral(", "))); } diff --git a/src/gui/widgets/filesettingswidget.h b/src/gui/widgets/filesettingswidget.h index 268ce375..79c4ce07 100644 --- a/src/gui/widgets/filesettingswidget.h +++ b/src/gui/widgets/filesettingswidget.h @@ -1,70 +1,69 @@ /***************************************************************************** * Copyright (C) 2004-2014 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * *****************************************************************************/ #ifndef KBIBTEX_GUI_FILESETTINGSWIDGET_H #define KBIBTEX_GUI_FILESETTINGSWIDGET_H #include "kbibtexgui_export.h" #include <QWidget> #include "value.h" class QCheckBox; - -class KComboBox; +class QComboBox; class File; /** * @author Thomas Fischer */ class KBIBTEXGUI_EXPORT FileSettingsWidget : public QWidget { Q_OBJECT public: explicit FileSettingsWidget(QWidget *parent); void loadProperties(File *file); void saveProperties(File *file); public slots: void resetToLoadedProperties(); void applyProperties(); void resetToDefaults(); signals: void widgetsChanged(); private: - KComboBox *m_comboBoxEncodings; - KComboBox *m_comboBoxStringDelimiters; - KComboBox *m_comboBoxQuoteComment; - KComboBox *m_comboBoxKeywordCasing; + QComboBox *m_comboBoxEncodings; + QComboBox *m_comboBoxStringDelimiters; + QComboBox *m_comboBoxQuoteComment; + QComboBox *m_comboBoxKeywordCasing; QCheckBox *m_checkBoxProtectCasing; - KComboBox *m_comboBoxPersonNameFormatting; - KComboBox *m_comboBoxListSeparator; + QComboBox *m_comboBoxPersonNameFormatting; + QComboBox *m_comboBoxListSeparator; const Person dummyPerson; File *m_file; void setupGUI(); }; #endif // KBIBTEX_GUI_FILESETTINGSWIDGET_H diff --git a/src/gui/widgets/filterbar.cpp b/src/gui/widgets/filterbar.cpp index fe1b8807..56353616 100644 --- a/src/gui/widgets/filterbar.cpp +++ b/src/gui/widgets/filterbar.cpp @@ -1,317 +1,324 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "filterbar.h" #include <algorithm> #include <QLayout> #include <QLabel> #include <QTimer> #include <QIcon> #include <QPushButton> +#include <QLineEdit> +#include <QComboBox> -#include <KComboBox> #include <KLocalizedString> -#include <KLineEdit> #include <KConfigGroup> #include <KSharedConfig> #include "bibtexfields.h" #include "delayedexecutiontimer.h" static bool sortStringsLocaleAware(const QString &s1, const QString &s2) { return QString::localeAwareCompare(s1, s2) < 0; } class FilterBar::FilterBarPrivate { private: FilterBar *p; public: KSharedConfigPtr config; const QString configGroupName; - KComboBox *comboBoxFilterText; + QComboBox *comboBoxFilterText; const int maxNumStoredFilterTexts; - KComboBox *comboBoxCombination; - KComboBox *comboBoxField; + QComboBox *comboBoxCombination; + QComboBox *comboBoxField; QPushButton *buttonSearchPDFfiles; QPushButton *buttonClearAll; DelayedExecutionTimer *delayedTimer; FilterBarPrivate(FilterBar *parent) : p(parent), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), configGroupName(QStringLiteral("Filter Bar")), maxNumStoredFilterTexts(12) { delayedTimer = new DelayedExecutionTimer(p); setupGUI(); connect(delayedTimer, &DelayedExecutionTimer::triggered, p, &FilterBar::publishFilter); } ~FilterBarPrivate() { delete delayedTimer; } void setupGUI() { QBoxLayout *layout = new QHBoxLayout(p); layout->setMargin(0); QLabel *label = new QLabel(i18n("Filter:"), p); layout->addWidget(label, 0); - comboBoxFilterText = new KComboBox(true, p); + comboBoxFilterText = new QComboBox(p); label->setBuddy(comboBoxFilterText); layout->addWidget(comboBoxFilterText, 5); comboBoxFilterText->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); comboBoxFilterText->setEditable(true); QFontMetrics metrics(comboBoxFilterText->font()); comboBoxFilterText->setMinimumWidth(metrics.width(QStringLiteral("AIWaiw")) * 7); - KLineEdit *lineEdit = static_cast<KLineEdit *>(comboBoxFilterText->lineEdit()); + QLineEdit *lineEdit = static_cast<QLineEdit *>(comboBoxFilterText->lineEdit()); lineEdit->setClearButtonEnabled(true); lineEdit->setPlaceholderText(i18n("Filter bibliographic entries")); - comboBoxCombination = new KComboBox(false, p); + comboBoxCombination = new QComboBox(p); layout->addWidget(comboBoxCombination, 1); comboBoxCombination->addItem(i18n("any word")); /// AnyWord=0 comboBoxCombination->addItem(i18n("every word")); /// EveryWord=1 comboBoxCombination->addItem(i18n("exact phrase")); /// ExactPhrase=2 comboBoxCombination->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - comboBoxField = new KComboBox(false, p); + comboBoxField = new QComboBox(p); layout->addWidget(comboBoxField, 1); comboBoxField->addItem(i18n("any field"), QVariant()); comboBoxField->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); /// Use a hash map to get an alphabetically sorted list QHash<QString, QString> fielddescs; for (const auto &fd : const_cast<const BibTeXFields &>(BibTeXFields::instance())) if (fd.upperCamelCaseAlt.isEmpty()) fielddescs.insert(fd.label, fd.upperCamelCase); /// Sort locale-aware QList<QString> keys = fielddescs.keys(); std::sort(keys.begin(), keys.end(), sortStringsLocaleAware); for (const QString &key : const_cast<const QList<QString> &>(keys)) { const QString &value = fielddescs[key]; comboBoxField->addItem(key, value); } buttonSearchPDFfiles = new QPushButton(p); buttonSearchPDFfiles->setIcon(QIcon::fromTheme(QStringLiteral("application-pdf"))); buttonSearchPDFfiles->setToolTip(i18n("Include PDF files in full-text search")); buttonSearchPDFfiles->setCheckable(true); layout->addWidget(buttonSearchPDFfiles, 0); buttonClearAll = new QPushButton(p); buttonClearAll->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-rtl"))); buttonClearAll->setToolTip(i18n("Reset filter criteria")); layout->addWidget(buttonClearAll, 0); /// restore history on filter texts /// see addCompletionString for more detailed explanation KConfigGroup configGroup(config, configGroupName); QStringList completionListDate = configGroup.readEntry(QStringLiteral("PreviousSearches"), QStringList()); for (QStringList::Iterator it = completionListDate.begin(); it != completionListDate.end(); ++it) comboBoxFilterText->addItem((*it).mid(12)); comboBoxFilterText->lineEdit()->setText(QString()); comboBoxCombination->setCurrentIndex(configGroup.readEntry("CurrentCombination", 0)); comboBoxField->setCurrentIndex(configGroup.readEntry("CurrentField", 0)); connect(comboBoxFilterText->lineEdit(), &QLineEdit::textChanged, delayedTimer, &DelayedExecutionTimer::trigger); connect(comboBoxFilterText->lineEdit(), &QLineEdit::returnPressed, p, &FilterBar::userPressedEnter); connect(comboBoxCombination, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), p, &FilterBar::comboboxStatusChanged); connect(comboBoxField, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), p, &FilterBar::comboboxStatusChanged); connect(buttonSearchPDFfiles, &QPushButton::toggled, p, &FilterBar::comboboxStatusChanged); connect(comboBoxCombination, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), delayedTimer, &DelayedExecutionTimer::trigger); connect(comboBoxField, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), delayedTimer, &DelayedExecutionTimer::trigger); connect(buttonSearchPDFfiles, &QPushButton::toggled, delayedTimer, &DelayedExecutionTimer::trigger); connect(buttonClearAll, &QPushButton::clicked, p, &FilterBar::resetState); } SortFilterFileModel::FilterQuery filter() { SortFilterFileModel::FilterQuery result; result.combination = comboBoxCombination->currentIndex() == 0 ? SortFilterFileModel::AnyTerm : SortFilterFileModel::EveryTerm; result.terms.clear(); if (comboBoxCombination->currentIndex() == 2) /// exact phrase result.terms << comboBoxFilterText->lineEdit()->text(); else { /// any or every word static const QRegularExpression sequenceOfSpacesRegExp(QStringLiteral("\\s+")); result.terms = comboBoxFilterText->lineEdit()->text().split(sequenceOfSpacesRegExp, QString::SkipEmptyParts); } result.field = comboBoxField->currentIndex() == 0 ? QString() : comboBoxField->itemData(comboBoxField->currentIndex(), Qt::UserRole).toString(); result.searchPDFfiles = buttonSearchPDFfiles->isChecked(); return result; } void setFilter(const SortFilterFileModel::FilterQuery &fq) { /// Avoid triggering loops of activation comboBoxCombination->blockSignals(true); /// Set check state for action for either "any word", /// "every word", or "exact phrase", respectively const int combinationIndex = fq.combination == SortFilterFileModel::AnyTerm ? 0 : (fq.terms.count() < 2 ? 2 : 1); comboBoxCombination->setCurrentIndex(combinationIndex); /// Reset activation block comboBoxCombination->blockSignals(false); /// Avoid triggering loops of activation comboBoxField->blockSignals(true); /// Find and check action that corresponds to field name ("author", ...) const QString lower = fq.field.toLower(); for (int idx = comboBoxField->count() - 1; idx >= 0; --idx) { if (comboBoxField->itemData(idx, Qt::UserRole).toString().toLower() == lower) { comboBoxField->setCurrentIndex(idx); break; } } /// Reset activation block comboBoxField->blockSignals(false); /// Avoid triggering loops of activation buttonSearchPDFfiles->blockSignals(true); /// Set flag if associated PDF files have to be searched buttonSearchPDFfiles->setChecked(fq.searchPDFfiles); /// Reset activation block buttonSearchPDFfiles->blockSignals(false); /// Avoid triggering loops of activation comboBoxFilterText->lineEdit()->blockSignals(true); /// Set filter text widget's content comboBoxFilterText->lineEdit()->setText(fq.terms.join(QStringLiteral(" "))); /// Reset activation block comboBoxFilterText->lineEdit()->blockSignals(false); } + bool modelContainsText(QAbstractItemModel *model, const QString &text) { + for (int row = 0; row < model->rowCount(); ++row) + if (model->index(row, 0, QModelIndex()).data().toString().contains(text)) + return true; + return false; + } + void addCompletionString(const QString &text) { KConfigGroup configGroup(config, configGroupName); /// Previous searches are stored as a string list, where each individual /// string starts with 12 characters for the date and time when this /// search was used. Starting from the 13th character (12th, if you /// start counting from 0) the user's input is stored. /// This approach has several advantages: It does not require a more /// complex data structure, can easily read and written using /// KConfigGroup's functions, and can be sorted lexicographically/ /// chronologically using QStringList's sort. /// Disadvantage is that string fragments have to be managed manually. QStringList completionListDate = configGroup.readEntry(QStringLiteral("PreviousSearches"), QStringList()); for (QStringList::Iterator it = completionListDate.begin(); it != completionListDate.end();) if ((*it).mid(12) == text) it = completionListDate.erase(it); else ++it; completionListDate << (QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMddhhmm")) + text); /// after sorting, discard all but the maxNumStoredFilterTexts most /// recent user-entered filter texts completionListDate.sort(); while (completionListDate.count() > maxNumStoredFilterTexts) completionListDate.removeFirst(); configGroup.writeEntry(QStringLiteral("PreviousSearches"), completionListDate); config->sync(); /// add user-entered filter text to combobox's drop-down list - if (!comboBoxFilterText->contains(text)) + if (!text.isEmpty() && !modelContainsText(comboBoxFilterText->model(), text)) comboBoxFilterText->addItem(text); } void storeComboBoxStatus() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("CurrentCombination"), comboBoxCombination->currentIndex()); configGroup.writeEntry(QStringLiteral("CurrentField"), comboBoxField->currentIndex()); configGroup.writeEntry(QStringLiteral("SearchPDFFiles"), buttonSearchPDFfiles->isChecked()); config->sync(); } void restoreState() { KConfigGroup configGroup(config, configGroupName); comboBoxCombination->setCurrentIndex(configGroup.readEntry(QStringLiteral("CurrentCombination"), 0)); comboBoxField->setCurrentIndex(configGroup.readEntry(QStringLiteral("CurrentField"), 0)); buttonSearchPDFfiles->setChecked(configGroup.readEntry(QStringLiteral("SearchPDFFiles"), false)); } void resetState() { comboBoxFilterText->lineEdit()->clear(); comboBoxCombination->setCurrentIndex(0); comboBoxField->setCurrentIndex(0); buttonSearchPDFfiles->setChecked(false); } }; FilterBar::FilterBar(QWidget *parent) : QWidget(parent), d(new FilterBarPrivate(this)) { d->restoreState(); setFocusProxy(d->comboBoxFilterText); QTimer::singleShot(250, this, &FilterBar::buttonHeight); } FilterBar::~FilterBar() { delete d; } void FilterBar::setFilter(const SortFilterFileModel::FilterQuery &fq) { d->setFilter(fq); emit filterChanged(fq); } SortFilterFileModel::FilterQuery FilterBar::filter() { return d->filter(); } void FilterBar::setPlaceholderText(const QString &msg) { - KLineEdit *lineEdit = static_cast<KLineEdit *>(d->comboBoxFilterText->lineEdit()); + QLineEdit *lineEdit = static_cast<QLineEdit *>(d->comboBoxFilterText->lineEdit()); lineEdit->setPlaceholderText(msg); } void FilterBar::comboboxStatusChanged() { d->buttonSearchPDFfiles->setEnabled(d->comboBoxField->currentIndex() == 0); d->storeComboBoxStatus(); } void FilterBar::resetState() { d->resetState(); emit filterChanged(d->filter()); } void FilterBar::userPressedEnter() { /// only store text in auto-completion if user pressed enter d->addCompletionString(d->comboBoxFilterText->lineEdit()->text()); publishFilter(); } void FilterBar::publishFilter() { emit filterChanged(d->filter()); } void FilterBar::buttonHeight() { QSizePolicy sp = d->buttonSearchPDFfiles->sizePolicy(); d->buttonSearchPDFfiles->setSizePolicy(sp.horizontalPolicy(), QSizePolicy::MinimumExpanding); d->buttonClearAll->setSizePolicy(sp.horizontalPolicy(), QSizePolicy::MinimumExpanding); } diff --git a/src/gui/widgets/menulineedit.cpp b/src/gui/widgets/menulineedit.cpp index 806c5059..b7ff1af9 100644 --- a/src/gui/widgets/menulineedit.cpp +++ b/src/gui/widgets/menulineedit.cpp @@ -1,321 +1,321 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "menulineedit.h" #include <QLayout> #include <QApplication> #include <QMenu> #include <QTimer> #include <QPushButton> #include <KLineEdit> #include <KTextEdit> #include <KConfigGroup> #include <KSharedConfig> #include "notificationhub.h" const int MenuLineEdit::MenuLineConfigurationChangedEvent = NotificationHub::EventUserDefined + 1861; const QString MenuLineEdit::keyLimitKeyboardTabStops = QStringLiteral("LimitKeyboardTabStops"); class MenuLineEdit::MenuLineEditPrivate : public NotificationListener { private: MenuLineEdit *p; bool isMultiLine; bool m_isReadOnly; QHBoxLayout *hLayout; static const QString transparentStyleSheet, normalStyleSheet; bool makeInnerWidgetsTransparent; public: QPushButton *m_pushButtonType; KLineEdit *m_singleLineEditText; KTextEdit *m_multiLineEditText; MenuLineEditPrivate(bool isMultiLine, MenuLineEdit *parent) : p(parent), m_isReadOnly(false), makeInnerWidgetsTransparent(false), m_singleLineEditText(nullptr), m_multiLineEditText(nullptr) { this->isMultiLine = isMultiLine; /// listen to configuration change events specifically concerning a MenuLineEdit widget NotificationHub::registerNotificationListener(this, MenuLineEdit::MenuLineConfigurationChangedEvent); setupUI(); } ~MenuLineEditPrivate() override { for (int i = hLayout->count() - 1; i >= 0; --i) { QWidget *w = hLayout->itemAt(i)->widget(); w->deleteLater(); } } void notificationEvent(int eventId) override { if (eventId == MenuLineEdit::MenuLineConfigurationChangedEvent) { /// load setting limitKeyboardTabStops KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))); static QString const configGroupName = QStringLiteral("User Interface"); KConfigGroup configGroup(config, configGroupName); const bool limitKeyboardTabStops = configGroup.readEntry(MenuLineEdit::keyLimitKeyboardTabStops, false); /// check each widget inside MenuLineEdit for (int i = hLayout->count() - 1; i >= 0; --i) { QWidget *w = hLayout->itemAt(i)->widget(); if (w != nullptr && w != m_singleLineEditText && w != m_multiLineEditText) { /// for all widgets except the main editing widget: change tab focus policy w->setFocusPolicy(limitKeyboardTabStops ? Qt::ClickFocus : Qt::StrongFocus); } } } } void setupUI() { p->setObjectName(QStringLiteral("FieldLineEdit")); hLayout = new QHBoxLayout(p); hLayout->setMargin(0); hLayout->setSpacing(2); m_pushButtonType = new QPushButton(p); appendWidget(m_pushButtonType); hLayout->setStretchFactor(m_pushButtonType, 0); m_pushButtonType->setObjectName(QStringLiteral("FieldLineEditButton")); if (isMultiLine) { m_multiLineEditText = new KTextEdit(p); appendWidget(m_multiLineEditText); connect(m_multiLineEditText, &KTextEdit::textChanged, p, &MenuLineEdit::slotTextChanged); m_multiLineEditText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); p->setFocusProxy(m_multiLineEditText); m_multiLineEditText->setAcceptRichText(false); } else { m_singleLineEditText = new KLineEdit(p); appendWidget(m_singleLineEditText); hLayout->setStretchFactor(m_singleLineEditText, 100); m_singleLineEditText->setClearButtonEnabled(true); m_singleLineEditText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); m_singleLineEditText->setCompletionMode(KCompletion::CompletionPopup); m_singleLineEditText->completionObject()->setIgnoreCase(true); p->setFocusProxy(m_singleLineEditText); - connect(m_singleLineEditText, &KLineEdit::textEdited, p, &MenuLineEdit::textChanged); + connect(m_singleLineEditText, &QLineEdit::textEdited, p, &MenuLineEdit::textChanged); } p->setFocusPolicy(Qt::StrongFocus); // FIXME improve focus handling p->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); } void prependWidget(QWidget *widget) { widget->setParent(p); hLayout->insertWidget(0, widget); widget->setStyleSheet(makeInnerWidgetsTransparent ? transparentStyleSheet : normalStyleSheet); setWidgetReadOnly(widget, m_isReadOnly); fixTabOrder(); } void appendWidget(QWidget *widget) { widget->setParent(p); hLayout->addWidget(widget); widget->setStyleSheet(makeInnerWidgetsTransparent ? transparentStyleSheet : normalStyleSheet); setWidgetReadOnly(widget, m_isReadOnly); fixTabOrder(); } void fixTabOrder() { QWidget *cur = nullptr; if (hLayout->count() > 0) p->setTabOrder(p, (cur = hLayout->itemAt(0)->widget())); for (int i = 1; i < hLayout->count(); ++i) { QWidget *next = hLayout->itemAt(i)->widget(); p->setTabOrder(cur, next); cur = next; } } void verticallyStretchButtons() { /// do not vertically stretch if using transparent style sheet if (makeInnerWidgetsTransparent) return; /// check each widget inside MenuLineEdit for (int i = hLayout->count() - 1; i >= 0; --i) { QWidget *w = hLayout->itemAt(i)->widget(); if (w != nullptr && w != m_singleLineEditText && w != m_multiLineEditText) { /// for all widgets except the main editing widget: change tab focus policy QSizePolicy sp = w->sizePolicy(); w->setSizePolicy(sp.horizontalPolicy(), QSizePolicy::MinimumExpanding); } } } void setStyleSheet(bool makeInnerWidgetsTransparent) { this->makeInnerWidgetsTransparent = makeInnerWidgetsTransparent; for (int i = hLayout->count() - 1; i >= 0; --i) { QWidget *w = hLayout->itemAt(i)->widget(); if (w != nullptr) w->setStyleSheet(makeInnerWidgetsTransparent ? transparentStyleSheet : normalStyleSheet); } } void setWidgetReadOnly(QWidget *w, bool isReadOnly) { if (m_singleLineEditText == w) m_singleLineEditText->setReadOnly(isReadOnly); else if (m_multiLineEditText == w) m_multiLineEditText->setReadOnly(isReadOnly); else if (!w->property("isConst").isValid() && !w->property("isConst").toBool()) w->setEnabled(!isReadOnly); } void setReadOnly(bool isReadOnly) { m_isReadOnly = isReadOnly; for (int i = hLayout->count() - 1; i >= 0; --i) { QWidget *w = hLayout->itemAt(i)->widget(); setWidgetReadOnly(w, isReadOnly); } } }; -const QString MenuLineEdit::MenuLineEditPrivate::transparentStyleSheet = QStringLiteral("KTextEdit { border-style: none; background-color: transparent; }\nKLineEdit { border-style: none; background-color: transparent; }\nKPushButton { border-style: none; background-color: transparent; padding: 0px; margin-left: 2px; margin-right:2px; text-align: left; }"); +const QString MenuLineEdit::MenuLineEditPrivate::transparentStyleSheet = QStringLiteral("KTextEdit { border-style: none; background-color: transparent; }\nQLineEdit { border-style: none; background-color: transparent; }\nKPushButton { border-style: none; background-color: transparent; padding: 0px; margin-left: 2px; margin-right:2px; text-align: left; }"); const QString MenuLineEdit::MenuLineEditPrivate::normalStyleSheet = QStringLiteral("QPushButton { padding:4px; margin:0px; text-align: left; }\nQPushButton::menu-indicator {subcontrol-position: right center; subcontrol-origin: content;}"); MenuLineEdit::MenuLineEdit(bool isMultiLine, QWidget *parent) : QFrame(parent), d(new MenuLineEditPrivate(isMultiLine, this)) { if (d->m_singleLineEditText != nullptr) { /// Only for single-line variants stretch buttons vertically QTimer::singleShot(250, this, &MenuLineEdit::slotVerticallyStretchButtons); } } MenuLineEdit::~MenuLineEdit() { delete d; } void MenuLineEdit::setMenu(QMenu *menu) { d->m_pushButtonType->setMenu(menu); } void MenuLineEdit::setReadOnly(bool readOnly) { d->setReadOnly(readOnly); } QString MenuLineEdit::text() const { if (d->m_singleLineEditText != nullptr) return d->m_singleLineEditText->text(); if (d->m_multiLineEditText != nullptr) return d->m_multiLineEditText->document()->toPlainText(); return QString(); } void MenuLineEdit::setText(const QString &text) { if (d->m_singleLineEditText != nullptr) { d->m_singleLineEditText->setText(text); d->m_singleLineEditText->setCursorPosition(0); } else if (d->m_multiLineEditText != nullptr) { d->m_multiLineEditText->document()->setPlainText(text); QTextCursor tc = d->m_multiLineEditText->textCursor(); tc.setPosition(0); d->m_multiLineEditText->setTextCursor(tc); } } void MenuLineEdit::setIcon(const QIcon &icon) { d->m_pushButtonType->setIcon(icon); } void MenuLineEdit::setFont(const QFont &font) { if (d->m_singleLineEditText != nullptr) d->m_singleLineEditText->setFont(font); if (d->m_multiLineEditText != nullptr) d->m_multiLineEditText->document()->setDefaultFont(font); } void MenuLineEdit::setButtonToolTip(const QString &text) { d->m_pushButtonType->setToolTip(text); } void MenuLineEdit::setChildAcceptDrops(bool acceptDrops) { if (d->m_singleLineEditText != nullptr) d->m_singleLineEditText->setAcceptDrops(acceptDrops); if (d->m_multiLineEditText != nullptr) d->m_multiLineEditText->setAcceptDrops(acceptDrops); } QWidget *MenuLineEdit::buddy() { if (d->m_singleLineEditText != nullptr) return d->m_singleLineEditText; if (d->m_multiLineEditText != nullptr) return d->m_multiLineEditText; return nullptr; } void MenuLineEdit::prependWidget(QWidget *widget) { d->prependWidget(widget); } void MenuLineEdit::appendWidget(QWidget *widget) { d->appendWidget(widget); } void MenuLineEdit::setInnerWidgetsTransparency(bool makeInnerWidgetsTransparent) { d->setStyleSheet(makeInnerWidgetsTransparent); } bool MenuLineEdit::isModified() const { if (d->m_singleLineEditText != nullptr) return d->m_singleLineEditText->isModified(); if (d->m_multiLineEditText != nullptr) return d->m_multiLineEditText->document()->isModified(); return false; } void MenuLineEdit::setCompletionItems(const QStringList &items) { if (d->m_singleLineEditText != nullptr) d->m_singleLineEditText->completionObject()->setItems(items); } void MenuLineEdit::focusInEvent(QFocusEvent *) { if (d->m_singleLineEditText != nullptr) d->m_singleLineEditText->setFocus(); else if (d->m_multiLineEditText != nullptr) d->m_multiLineEditText->setFocus(); } void MenuLineEdit::slotTextChanged() { Q_ASSERT_X(d->m_multiLineEditText != nullptr, "MenuLineEdit::slotTextChanged", "d->m_multiLineEditText is NULL"); emit textChanged(d->m_multiLineEditText->toPlainText()); } void MenuLineEdit::slotVerticallyStretchButtons() { d->verticallyStretchButtons(); } diff --git a/src/networking/onlinesearch/onlinesearcharxiv.cpp b/src/networking/onlinesearch/onlinesearcharxiv.cpp index 537a53d8..c85ee3ce 100644 --- a/src/networking/onlinesearch/onlinesearcharxiv.cpp +++ b/src/networking/onlinesearch/onlinesearcharxiv.cpp @@ -1,745 +1,745 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "onlinesearcharxiv.h" #include <QNetworkReply> #include <QRegularExpression> #ifdef HAVE_QTWIDGETS #include <QGridLayout> #include <QLabel> #include <QSpinBox> +#include <QLineEdit> #include <QTextStream> #endif // HAVE_QTWIDGETS #ifdef HAVE_KF5 -#include <KLineEdit> #include <KMessageBox> #include <KConfigGroup> #include <KLocalizedString> #else // HAVE_KF5 #define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "fileimporterbibtex.h" #include "xsltransform.h" #include "encoderxml.h" #include "internalnetworkaccessmanager.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS class OnlineSearchArXiv::OnlineSearchQueryFormArXiv : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); lineEditFreeText->setText(configGroup.readEntry(QStringLiteral("freeText"), QString())); numResultsField->setValue(configGroup.readEntry(QStringLiteral("numResults"), 10)); } public: - KLineEdit *lineEditFreeText; + QLineEdit *lineEditFreeText; QSpinBox *numResultsField; OnlineSearchQueryFormArXiv(QWidget *parent) : OnlineSearchQueryFormAbstract(parent), configGroupName(QStringLiteral("Search Engine arXiv.org")) { QGridLayout *layout = new QGridLayout(this); layout->setMargin(0); QLabel *label = new QLabel(i18n("Free text:"), this); layout->addWidget(label, 0, 0, 1, 1); - lineEditFreeText = new KLineEdit(this); + lineEditFreeText = new QLineEdit(this); lineEditFreeText->setClearButtonEnabled(true); lineEditFreeText->setFocus(Qt::TabFocusReason); layout->addWidget(lineEditFreeText, 0, 1, 1, 1); label->setBuddy(lineEditFreeText); - connect(lineEditFreeText, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormArXiv::returnPressed); + connect(lineEditFreeText, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormArXiv::returnPressed); label = new QLabel(i18n("Number of Results:"), this); layout->addWidget(label, 1, 0, 1, 1); numResultsField = new QSpinBox(this); numResultsField->setMinimum(3); numResultsField->setMaximum(100); numResultsField->setValue(20); layout->addWidget(numResultsField, 1, 1, 1, 1); label->setBuddy(numResultsField); layout->setRowStretch(2, 100); loadState(); } bool readyToStart() const override { return !lineEditFreeText->text().isEmpty(); } void copyFromEntry(const Entry &entry) override { lineEditFreeText->setText(authorLastNames(entry).join(QStringLiteral(" ")) + QLatin1Char(' ') + PlainTextValue::text(entry[Entry::ftTitle])); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("freeText"), lineEditFreeText->text()); configGroup.writeEntry(QStringLiteral("numResults"), numResultsField->value()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchArXiv::OnlineSearchArXivPrivate { private: static const QString xsltFilenameBase; public: const XSLTransform xslt; #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormArXiv *form; #endif // HAVE_QTWIDGETS const QString arXivQueryBaseUrl; OnlineSearchArXivPrivate(OnlineSearchArXiv *) : xslt(XSLTransform::locateXSLTfile(xsltFilenameBase)), #ifdef HAVE_QTWIDGETS form(nullptr), #endif // HAVE_QTWIDGETS arXivQueryBaseUrl(QStringLiteral("https://export.arxiv.org/api/query?")) { if (!xslt.isValid()) qCWarning(LOG_KBIBTEX_NETWORKING) << "Failed to initialize XSL transformation based on file '" << xsltFilenameBase << "'"; } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { /// format search terms const auto respectingQuotationMarks = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditFreeText->text()); QStringList queryFragments; queryFragments.reserve(respectingQuotationMarks.size()); for (const QString &queryFragment : respectingQuotationMarks) queryFragments.append(OnlineSearchAbstract::encodeURL(queryFragment)); return QUrl(QString(QStringLiteral("%1search_query=all:\"%3\"&start=0&max_results=%2")).arg(arXivQueryBaseUrl).arg(form->numResultsField->value()).arg(queryFragments.join(QStringLiteral("\"+AND+all:\"")))); ///< join search terms with an AND operation } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { /// format search terms QStringList queryFragments; for (QMap<QString, QString>::ConstIterator it = query.constBegin(); it != query.constEnd(); ++it) { const auto respectingQuotationMarks = OnlineSearchAbstract::splitRespectingQuotationMarks(it.value()); for (const auto &queryFragment : respectingQuotationMarks) queryFragments.append(OnlineSearchAbstract::encodeURL(queryFragment)); } return QUrl(QString(QStringLiteral("%1search_query=all:\"%3\"&start=0&max_results=%2")).arg(arXivQueryBaseUrl).arg(numResults).arg(queryFragments.join(QStringLiteral("\"+AND+all:\"")))); ///< join search terms with an AND operation } void interpreteJournal(Entry &entry) { /** * TODO examples * - https://arxiv.org/abs/1111.5338 * Eur. Phys. J. H 36, 183-201 (2011) * - https://arxiv.org/abs/1111.5348 * IJCSI International Journal of Computer Science Issues, Vol. 8, Issue 3, No. 1, 2011, 224-229 * - https://arxiv.org/abs/1110.3379 * IJCSI International Journal of Computer Science Issues, Vol. 8, Issue 5, No 3, September 2011 ISSN (Online): 1694-0814 * - https://arxiv.org/abs/1102.5769 * The International Journal of Multimedia & Its Applications, 3(1), 2011 * - https://arxiv.org/abs/1003.3022 * American Journal of Physics -- April 2010 -- Volume 78, Issue 4, pp. 377-383 */ static const QRegularExpression /** * Structure: * - journal title: letters, space, dot * - optional: spaces * - volume: number * - volume: number * Examples: * Journal of Inefficient Algorithms 5 (2003) 35-39 * New J. Phys. 10 (2008) 033023 * Physics Letters A 297 (2002) 4-8 * Appl.Phys. B75 (2002) 655-665 * JHEP 0611 (2006) 045 * - https://arxiv.org/abs/1105.4915 * Astrophys. J. 736 (2011) 7 * - https://arxiv.org/abs/astro-ph/0209123 * Astrophys.J. 578 (2002) L103-L106 * - https://arxiv.org/abs/quant-ph/0611139 * Journal of Physics: Conference Series 70 (2007) 012003 * Captures: * 1: journal title * 2: volume * 3: year * 4: page start * 6: page end */ journalRef1(QStringLiteral("^([a-z. ]+[a-z.])\\s*(\\d+)\\s+\\((\\d{4})\\)\\s+([0-9A-Z]+)(-([0-9A-Z]+))?$"), QRegularExpression::CaseInsensitiveOption), /** * Examples: * Journal of Inefficient Algorithms, Vol. 93, No. 2 (2009), pp. 42-51 * Stud. Hist. Phil. Mod. Phys., Vol 33 no 3 (2003), pp. 441-468 * - https://arxiv.org/abs/quant-ph/0311028 * International Journal of Quantum Information, Vol. 1, No. 4 (2003) 427-441 * - https://arxiv.org/abs/1003.3521 * Int. J. Geometric Methods in Modern Physics Vol. 8, No. 1 (2011) 155-165 * Captures: * 1: journal title * 2: volume * 3: number * 4: year * 5: page start * 7: page end */ journalRef2(QStringLiteral("^([a-zA-Z. ]+[a-zA-Z.]),\\s+Vol\\.?\\s+(\\d+)[,]?\\s+No\\.?\\s+(\\d+)\\s+\\((\\d{4})\\)[,]?\\s+(pp\\.\\s+)?(\\d+)(-(\\d+))?$"), QRegularExpression::CaseInsensitiveOption), /** * Examples: * Journal of Inefficient Algorithms, volume 4, number 1, pp. 12-21, 2008 * - https://arxiv.org/abs/cs/0601030 * Scientometrics, volume 69, number 3, pp. 669-687, 2006 * Captures: * 1: journal title * 2: volume * 3: number * 4: page start * 6: page end * 7: year */ journalRef3(QStringLiteral("^([a-zA-Z. ]+),\\s+volume\\s+(\\d+),\\s+number\\s+(\\d+),\\s+pp\\.\\s+(\\d+)(-(\\d+))?,\\s+(\\d{4})$"), QRegularExpression::CaseInsensitiveOption), /** * Examples: * Journal of Inefficient Algorithms 4(1): 101-122, 2010 * JHEP0809:131,2008 * Phys.Rev.D78:013004,2008 * Lect.NotesPhys.690:107-127,2006 * Europhys. Letters 70:1-7 (2005) * - https://arxiv.org/abs/quant-ph/0608209 * J.Phys.A40:9025-9032,2007 * - https://arxiv.org/abs/hep-th/9907001 * Phys.Rev.Lett.85:5042-5045,2000 * - https://arxiv.org/abs/physics/0606007 * Journal of Conflict Resolution 51(1): 58 - 88 (2007) * - https://arxiv.org/abs/arxiv:cs/0609126 * Learn.Publ.20:16-22,2007 * - https://arxiv.org/abs/cs/9901005 * Journal of Artificial Intelligence Research (JAIR), 9:247-293 * - https://arxiv.org/abs/hep-ph/0403113 * Nucl.Instrum.Meth.A534:250-259,2004 * Captures: * 1: journal title * 2: volume * 4: number * 5: page start * 7: page end * 9 or 10: year */ journalRef4(QStringLiteral("^([a-z. ()]+)[,]?\\s*(\\d+)(\\((\\d+)\\))?:\\s*(\\d+)(\\s*-\\s*(\\d+))?(,\\s*(\\d{4})|\\s+\\((\\d{4})\\))?$"), QRegularExpression::CaseInsensitiveOption), /** * Examples: * Journal of Inefficient Algorithms vol. 31, 4 2000 * Phys. Rev. A 71, 032339 (2005) * Phys. Rev. Lett. 91, 027901 (2003) * Phys. Rev. A 78, 013620 (2008) * Phys. Rev. E 62, 1842 (2000) * J. Math. Phys. 49, 032105 (2008) * Phys. Rev. Lett. 91, 217905 (2003). * Physical Review B vol. 66, 161320(R) (2002) * - https://arxiv.org/abs/1003.1050 * Phys. Rev. A 82, 012304 (2010) * - https://arxiv.org/abs/quant-ph/0607107 * J. Mod. Opt., 54, 2211 (2007) * - https://arxiv.org/abs/quant-ph/0602069 * New J. Phys. 8, 58 (2006) * - https://arxiv.org/abs/quant-ph/0610030 * Rev. Mod. Phys. 79, 555 (2007) * - https://arxiv.org/abs/1007.2292 * J. Phys. A: Math. Theor. 44, 145304 (2011) * Captures: * 1: journal title * 3: volume * 4: page start * 6: year */ journalRef5(QStringLiteral("^([a-zA-Z. ]+)\\s+(vol\\.\\s+)?(\\d+),\\s+(\\d+)(\\([A-Z]+\\))?\\s+\\((\\d{4})\\)[.]?$")), /** * Examples: * Journal of Inefficient Algorithms, 11(2) (1999) 42-55 * Learned Publishing, 20(1) (January 2007) 16-22 * Captures: * 1: journal title * 2: volume * 3: number * 4: year * 5: page start * 7: page end */ journalRef6(QStringLiteral("^([a-zA-Z. ]+),\\s+(\\d+)\\((\\d+)\\)\\s+(\\(([A-Za-z]+\\s+)?(\\d{4})\\))?\\s+(\\d+)(-(\\d+))?$")), /** * Examples: * Pacific J. Math. 231 (2007), no. 2, 279–291 * Captures: * 1: journal title * 2: volume * 3: year * 4: number * 5: page start * 6: page end */ journalRef7(QStringLiteral("^([a-zA-Z. ]+[a-zA-Z.])\\s+(\\d+)\\s+\\((\\d+)\\), no\\.\\s+(\\d+),\\s+(\\d)[^ ]+(\\d+)$")), generalJour(QStringLiteral("^[a-z0-9]{,3}[a-z. ]+"), QRegularExpression::CaseInsensitiveOption), generalYear(QStringLiteral("\\b(18|19|20)\\d{2}\\b")), generalPages(QStringLiteral("\\b([1-9]\\d{0,2})\\s*[-]+\\s*([1-9]\\d{0,2})\\b")); const QString journalText = PlainTextValue::text(entry.value(Entry::ftJournal)); /// nothing to do on empty journal text if (journalText.isEmpty()) return; else entry.remove(Entry::ftJournal); QRegularExpressionMatch match; if ((match = journalRef1.match(journalText)).hasMatch()) { /** * Captures: * 1: journal title * 2: volume * 3: year * 4: page start * 6: page end */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(3)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } if (!(text = match.captured(4)).isEmpty()) { const QString endPage = match.captured(6); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1--%2")).arg(text, endPage)))); entry.insert(Entry::ftPages, v); } } } else if ((match = journalRef2.match(journalText)).hasMatch()) { /** * Captures: * 1: journal title * 2: volume * 3: number * 4: year * 5: page start * 7: page end */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(3)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftNumber, v); } if (!(text = match.captured(4)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } if (!(text = match.captured(5)).isEmpty()) { const QString endPage = match.captured(7); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1%3%2")).arg(text, endPage, QChar(0x2013))))); entry.insert(Entry::ftPages, v); } } } else if ((match = journalRef3.match(journalText)).hasMatch()) { /** * Captures: * 1: journal title * 2: volume * 4: number * 5: page start * 7: page end * 9 or 10: year */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(4)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftNumber, v); } if (!(text = match.captured(9)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); if (!(text = match.captured(10)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } if (!(text = match.captured(5)).isEmpty()) { const QString endPage = match.captured(7); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1%3%2")).arg(text, endPage, QChar(0x2013))))); entry.insert(Entry::ftPages, v); } } } } else if ((match = journalRef4.match(journalText)).hasMatch()) { /** * Captures: * 1: journal title * 2: volume * 4: number * 5: page start * 7: page end * 9 or 10: year */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(5)).isEmpty()) { const QString endPage = match.captured(7); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text.append(QChar(0x2013)).append(endPage)))); entry.insert(Entry::ftPages, v); } } if (!(text = match.captured(9)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } } else if ((match = journalRef5.match(journalText)).hasMatch()) { /** Captures: * 1: journal title * 3: volume * 4: page start * 6: year */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(3)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(4)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } if (!(text = match.captured(6)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } } else if ((match = journalRef6.match(journalText)).hasMatch()) { /** Captures: * 1: journal title * 2: volume * 3: number * 4: year * 5: page start * 7: page end */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(3)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftNumber, v); } if (!(text = match.captured(4)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } if (!(text = match.captured(5)).isEmpty()) { const QString endPage = match.captured(7); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1%3%2")).arg(text, endPage, QChar(0x2013))))); entry.insert(Entry::ftPages, v); } } } else if ((match = journalRef7.match(journalText)).hasMatch()) { /** Captures: * 1: journal title * 2: volume * 3: year * 4: number * 5: page start * 6: page end */ QString text; if (!(text = match.captured(1)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftJournal, v); } if (!(text = match.captured(2)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftVolume, v); } if (!(text = match.captured(3)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftYear, v); } if (!(text = match.captured(4)).isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftNumber, v); } if (!(text = match.captured(5)).isEmpty()) { const QString endPage = match.captured(6); if (endPage.isEmpty()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(text))); entry.insert(Entry::ftPages, v); } else { Value v; v.append(QSharedPointer<PlainText>(new PlainText(QString(QStringLiteral("%1%3%2")).arg(text, endPage, QChar(0x2013))))); entry.insert(Entry::ftPages, v); } } } else { if ((match = generalJour.match(journalText)).hasMatch()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(match.captured(0)))); entry.insert(Entry::ftPages, v); } if ((match = generalYear.match(journalText)).hasMatch()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(match.captured(0)))); entry.insert(Entry::ftYear, v); } if ((match = generalPages.match(journalText)).hasMatch()) { Value v; v.append(QSharedPointer<PlainText>(new PlainText(match.captured(0)))); entry.insert(Entry::ftPages, v); } } } }; const QString OnlineSearchArXiv::OnlineSearchArXivPrivate::xsltFilenameBase = QStringLiteral("arxiv2bibtex.xsl"); OnlineSearchArXiv::OnlineSearchArXiv(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchArXiv::OnlineSearchArXivPrivate(this)) { /// nothing } OnlineSearchArXiv::~OnlineSearchArXiv() { delete d; } #ifdef HAVE_QTWIDGETS void OnlineSearchArXiv::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl()); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchArXiv::downloadDone); d->form->saveState(); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS void OnlineSearchArXiv::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl(query, numResults)); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchArXiv::downloadDone); refreshBusyProperty(); } QString OnlineSearchArXiv::label() const { #ifdef HAVE_KF5 return i18n("arXiv.org"); #else // HAVE_KF5 //= onlinesearch-arxiv-label return QObject::tr("arXiv.org"); #endif // HAVE_KF5 } QString OnlineSearchArXiv::favIconUrl() const { return QStringLiteral("https://arxiv.org/favicon.ico"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchArXiv::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchArXiv::OnlineSearchQueryFormArXiv(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchArXiv::homepage() const { return QUrl(QStringLiteral("https://arxiv.org/")); } void OnlineSearchArXiv::downloadDone() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters QString result = QString::fromUtf8(reply->readAll().constData()); result = result.remove(QStringLiteral("xmlns=\"http://www.w3.org/2005/Atom\"")); // FIXME fix arxiv2bibtex.xsl to handle namespace /// use XSL transformation to get BibTeX document from XML result const QString bibTeXcode = EncoderXML::instance().decode(d->xslt.transform(result)); if (bibTeXcode.isEmpty()) { qCWarning(LOG_KBIBTEX_NETWORKING) << "XSL tranformation failed for data from " << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultInvalidArguments); } else { FileImporterBibTeX importer(this); File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } } refreshBusyProperty(); } void OnlineSearchArXiv::sanitizeEntry(QSharedPointer<Entry> entry) { OnlineSearchAbstract::sanitizeEntry(entry); d->interpreteJournal(*entry); } #include "onlinesearcharxiv.moc" diff --git a/src/networking/onlinesearch/onlinesearchbibsonomy.cpp b/src/networking/onlinesearch/onlinesearchbibsonomy.cpp index 1d79be95..cba6eb50 100644 --- a/src/networking/onlinesearch/onlinesearchbibsonomy.cpp +++ b/src/networking/onlinesearch/onlinesearchbibsonomy.cpp @@ -1,290 +1,291 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "onlinesearchbibsonomy.h" #include <QBuffer> #ifdef HAVE_QTWIDGETS #include <QLayout> #include <QSpinBox> +#include <QComboBox> +#include <QLineEdit> #include <QLabel> #include <QIcon> #endif // HAVE_QTWIDGETS #include <QNetworkReply> #ifdef HAVE_KF5 #include <KLocalizedString> #include <KConfigGroup> -#include <KComboBox> #include <KMessageBox> -#include <KLineEdit> #else // HAVE_KF5 #define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "fileimporterbibtex.h" #include "file.h" #include "entry.h" #include "internalnetworkaccessmanager.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS class OnlineSearchBibsonomy::OnlineSearchQueryFormBibsonomy : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); comboBoxSearchWhere->setCurrentIndex(configGroup.readEntry(QStringLiteral("searchWhere"), 0)); lineEditSearchTerm->setText(configGroup.readEntry(QStringLiteral("searchTerm"), QString())); numResultsField->setValue(configGroup.readEntry(QStringLiteral("numResults"), 10)); } public: - KComboBox *comboBoxSearchWhere; - KLineEdit *lineEditSearchTerm; + QComboBox *comboBoxSearchWhere; + QLineEdit *lineEditSearchTerm; QSpinBox *numResultsField; OnlineSearchQueryFormBibsonomy(QWidget *widget) : OnlineSearchQueryFormAbstract(widget), configGroupName(QStringLiteral("Search Engine Bibsonomy")) { QGridLayout *layout = new QGridLayout(this); layout->setMargin(0); - comboBoxSearchWhere = new KComboBox(false, this); + comboBoxSearchWhere = new QComboBox(this); layout->addWidget(comboBoxSearchWhere, 0, 0, 1, 1); + comboBoxSearchWhere->setEditable(true); comboBoxSearchWhere->addItem(i18n("Tag"), "tag"); comboBoxSearchWhere->addItem(i18n("User"), "user"); comboBoxSearchWhere->addItem(i18n("Group"), "group"); comboBoxSearchWhere->addItem(i18n("Author"), "author"); comboBoxSearchWhere->addItem(i18n("Concept"), "concept/tag"); comboBoxSearchWhere->addItem(i18n("BibTeX Key"), "bibtexkey"); comboBoxSearchWhere->addItem(i18n("Everywhere"), "search"); comboBoxSearchWhere->setCurrentIndex(comboBoxSearchWhere->count() - 1); - lineEditSearchTerm = new KLineEdit(this); + lineEditSearchTerm = new QLineEdit(this); layout->addWidget(lineEditSearchTerm, 0, 1, 1, 1); lineEditSearchTerm->setClearButtonEnabled(true); - connect(lineEditSearchTerm, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormBibsonomy::returnPressed); + connect(lineEditSearchTerm, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormBibsonomy::returnPressed); QLabel *label = new QLabel(i18n("Number of Results:"), this); layout->addWidget(label, 1, 0, 1, 1); numResultsField = new QSpinBox(this); numResultsField->setMinimum(3); numResultsField->setMaximum(100); numResultsField->setValue(20); layout->addWidget(numResultsField, 1, 1, 1, 1); label->setBuddy(numResultsField); layout->setRowStretch(2, 100); lineEditSearchTerm->setFocus(Qt::TabFocusReason); loadState(); } bool readyToStart() const override { return !lineEditSearchTerm->text().isEmpty(); } void copyFromEntry(const Entry &entry) override { comboBoxSearchWhere->setCurrentIndex(comboBoxSearchWhere->count() - 1); lineEditSearchTerm->setText(authorLastNames(entry).join(QStringLiteral(" ")) + QLatin1Char(' ') + PlainTextValue::text(entry[Entry::ftTitle])); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("searchWhere"), comboBoxSearchWhere->currentIndex()); configGroup.writeEntry(QStringLiteral("searchTerm"), lineEditSearchTerm->text()); configGroup.writeEntry(QStringLiteral("numResults"), numResultsField->value()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchBibsonomy::OnlineSearchBibsonomyPrivate { public: #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormBibsonomy *form; #endif // HAVE_QTWIDGETS OnlineSearchBibsonomyPrivate(OnlineSearchBibsonomy *) #ifdef HAVE_QTWIDGETS : form(nullptr) #endif // HAVE_QTWIDGETS { /// nothing } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { if (form == nullptr) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Cannot build query url if no form is specified"; return QUrl(); } QString queryString = OnlineSearchAbstract::encodeURL(form->lineEditSearchTerm->text()); return QUrl(QStringLiteral("https://www.bibsonomy.org/bib/") + form->comboBoxSearchWhere->itemData(form->comboBoxSearchWhere->currentIndex()).toString() + QStringLiteral("/") + queryString + QString(QStringLiteral("?items=%1")).arg(form->numResultsField->value())); } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { QString url = QStringLiteral("https://www.bibsonomy.org/bib/"); const bool hasFreeText = !query[queryKeyFreeText].isEmpty(); const bool hasTitle = !query[queryKeyTitle].isEmpty(); const bool hasAuthor = !query[queryKeyAuthor].isEmpty(); const bool hasYear = !query[queryKeyYear].isEmpty(); QString searchType = QStringLiteral("search"); if (hasAuthor && !hasFreeText && !hasTitle && !hasYear) { /// if only the author field is used, a special author search /// on BibSonomy can be used searchType = QStringLiteral("author"); } QStringList queryFragments; for (QMap<QString, QString>::ConstIterator it = query.constBegin(); it != query.constEnd(); ++it) { queryFragments << OnlineSearchAbstract::encodeURL(it.value()); } QString queryString = queryFragments.join(QStringLiteral("%20")); url.append(searchType + QLatin1Char('/') + queryString + QString(QStringLiteral("?items=%1")).arg(numResults)); return QUrl(url); } void sanitizeEntry(QSharedPointer<Entry> entry) { /// if entry contains a description field but no abstract, /// rename description field to abstract const QString ftDescription = QStringLiteral("description"); if (!entry->contains(Entry::ftAbstract) && entry->contains(ftDescription)) { Value v = entry->value(QStringLiteral("description")); entry->insert(Entry::ftAbstract, v); entry->remove(ftDescription); } } }; OnlineSearchBibsonomy::OnlineSearchBibsonomy(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchBibsonomyPrivate(this)) { /// nothing } OnlineSearchBibsonomy::~OnlineSearchBibsonomy() { delete d; } void OnlineSearchBibsonomy::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl(query, numResults)); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchBibsonomy::downloadDone); refreshBusyProperty(); } #ifdef HAVE_QTWIDGETS void OnlineSearchBibsonomy::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl()); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchBibsonomy::downloadDone); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS QString OnlineSearchBibsonomy::label() const { #ifdef HAVE_KF5 return i18n("Bibsonomy"); #else // HAVE_KF5 //= onlinesearch-bibsonomy-label return QObject::tr("Bibsonomy"); #endif // HAVE_KF5 } QString OnlineSearchBibsonomy::favIconUrl() const { return QStringLiteral("https://www.bibsonomy.org/resources/image/favicon.png"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchBibsonomy::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchBibsonomy::OnlineSearchQueryFormBibsonomy(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchBibsonomy::homepage() const { return QUrl(QStringLiteral("https://www.bibsonomy.org/")); } void OnlineSearchBibsonomy::downloadDone() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters QString bibTeXcode = QString::fromUtf8(reply->readAll().constData()); if (!bibTeXcode.isEmpty()) { FileImporterBibTeX importer(this); const File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } else { /// returned file is empty stopSearch(resultNoError); } } refreshBusyProperty(); } #include "onlinesearchbibsonomy.moc" diff --git a/src/networking/onlinesearch/onlinesearchdoi.cpp b/src/networking/onlinesearch/onlinesearchdoi.cpp index 371009a2..5e9ac87b 100644 --- a/src/networking/onlinesearch/onlinesearchdoi.cpp +++ b/src/networking/onlinesearch/onlinesearchdoi.cpp @@ -1,251 +1,251 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "onlinesearchdoi.h" #ifdef HAVE_QTWIDGETS #include <QLabel> +#include <QLineEdit> #include <QGridLayout> #endif // HAVE_QTWIDGETS #include <QNetworkRequest> #include <QNetworkReply> #include <QRegularExpression> #ifdef HAVE_KF5 -#include <KLineEdit> #include <KConfigGroup> #include <KLocalizedString> #endif // HAVE_KF5 #include "kbibtex.h" #include "internalnetworkaccessmanager.h" #include "fileimporterbibtex.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS class OnlineSearchDOI::OnlineSearchQueryFormDOI : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); lineEditDoiNumber->setText(configGroup.readEntry(QStringLiteral("doiNumber"), QString())); } public: - KLineEdit *lineEditDoiNumber; + QLineEdit *lineEditDoiNumber; OnlineSearchQueryFormDOI(QWidget *widget) : OnlineSearchQueryFormAbstract(widget), configGroupName(QStringLiteral("Search Engine DOI")) { QGridLayout *layout = new QGridLayout(this); layout->setMargin(0); QLabel *label = new QLabel(i18n("DOI:"), this); layout->addWidget(label, 0, 0, 1, 1); - lineEditDoiNumber = new KLineEdit(this); + lineEditDoiNumber = new QLineEdit(this); layout->addWidget(lineEditDoiNumber, 0, 1, 1, 1); lineEditDoiNumber->setClearButtonEnabled(true); - connect(lineEditDoiNumber, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormDOI::returnPressed); + connect(lineEditDoiNumber, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormDOI::returnPressed); layout->setRowStretch(1, 100); lineEditDoiNumber->setFocus(Qt::TabFocusReason); loadState(); } bool readyToStart() const override { return !lineEditDoiNumber->text().isEmpty(); } void copyFromEntry(const Entry &entry) override { lineEditDoiNumber->setText(PlainTextValue::text(entry[Entry::ftDOI])); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("doiNumber"), lineEditDoiNumber->text()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchDOI::OnlineSearchDOIPrivate { public: #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormDOI *form; #endif // HAVE_QTWIDGETS OnlineSearchDOIPrivate(OnlineSearchDOI *parent) #ifdef HAVE_QTWIDGETS : form(nullptr) #endif // HAVE_QTWIDGETS { Q_UNUSED(parent) } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { if (form == nullptr) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Cannot build query url if no form is specified"; return QUrl(); } return QUrl(QStringLiteral("https://dx.doi.org/") + form->lineEditDoiNumber->text()); } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { Q_UNUSED(numResults) const QRegularExpressionMatch doiRegExpMatch = KBibTeX::doiRegExp.match(query[queryKeyFreeText]); if (doiRegExpMatch.hasMatch()) { return QUrl(QStringLiteral("https://dx.doi.org/") + doiRegExpMatch.captured(0)); } return QUrl(); } }; OnlineSearchDOI::OnlineSearchDOI(QWidget *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchDOIPrivate(this)) { // TODO } OnlineSearchDOI::~OnlineSearchDOI() { delete d; } #ifdef HAVE_QTWIDGETS void OnlineSearchDOI::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); const QUrl url = d->buildQueryUrl(); if (url.isValid()) { QNetworkRequest request(url); request.setRawHeader(QByteArray("Accept"), QByteArray("text/bibliography; style=bibtex")); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchDOI::downloadDone); d->form->saveState(); } else delayedStoppedSearch(resultNoError); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS void OnlineSearchDOI::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); const QUrl url = d->buildQueryUrl(query, numResults); if (url.isValid()) { QNetworkRequest request(url); request.setRawHeader(QByteArray("Accept"), QByteArray("text/bibliography; style=bibtex")); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchDOI::downloadDone); refreshBusyProperty(); } else delayedStoppedSearch(resultNoError); } QString OnlineSearchDOI::label() const { return i18n("DOI"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchDOI::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchDOI::OnlineSearchQueryFormDOI(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchDOI::homepage() const { return QUrl(QStringLiteral("https://dx.doi.org/")); } QString OnlineSearchDOI::favIconUrl() const { return QStringLiteral("https://dx.doi.org/favicon.ico"); } void OnlineSearchDOI::downloadDone() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QUrl redirUrl; if (handleErrors(reply, redirUrl)) { if (redirUrl.isValid()) { /// redirection to another url ++numSteps; QNetworkRequest request(redirUrl); request.setRawHeader(QByteArray("Accept"), QByteArray("text/bibliography; style=bibtex")); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchDOI::downloadDone); } else { /// ensure proper treatment of UTF-8 characters const QString bibTeXcode = QString::fromUtf8(reply->readAll().constData()); if (!bibTeXcode.isEmpty()) { FileImporterBibTeX importer(this); File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } else { /// returned file is empty stopSearch(resultNoError); } } } refreshBusyProperty(); } #include "onlinesearchdoi.moc" diff --git a/src/networking/onlinesearch/onlinesearchgeneral.cpp b/src/networking/onlinesearch/onlinesearchgeneral.cpp index 5b3a2be3..7e555abe 100644 --- a/src/networking/onlinesearch/onlinesearchgeneral.cpp +++ b/src/networking/onlinesearch/onlinesearchgeneral.cpp @@ -1,136 +1,136 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "onlinesearchgeneral.h" #ifdef HAVE_QTWIDGETS #include <QFormLayout> #include <QLabel> #include <QSpinBox> +#include <QLineEdit> -#include <KLineEdit> #include <KLocalizedString> #include <KConfigGroup> #include "kbibtex.h" #include "entry.h" OnlineSearchQueryFormGeneral::OnlineSearchQueryFormGeneral(QWidget *parent) : OnlineSearchQueryFormAbstract(parent), configGroupName(QStringLiteral("Search Engine General")) { QFormLayout *layout = new QFormLayout(this); layout->setMargin(0); QLabel *label = new QLabel(i18n("Free text:"), this); - KLineEdit *lineEdit = new KLineEdit(this); + QLineEdit *lineEdit = new QLineEdit(this); layout->addRow(label, lineEdit); lineEdit->setClearButtonEnabled(true); lineEdit->setFocus(Qt::TabFocusReason); queryFields.insert(OnlineSearchAbstract::queryKeyFreeText, lineEdit); label->setBuddy(lineEdit); - connect(lineEdit, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormGeneral::returnPressed); + connect(lineEdit, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormGeneral::returnPressed); label = new QLabel(i18n("Title:"), this); - lineEdit = new KLineEdit(this); + lineEdit = new QLineEdit(this); layout->addRow(label, lineEdit); lineEdit->setClearButtonEnabled(true); queryFields.insert(OnlineSearchAbstract::queryKeyTitle, lineEdit); label->setBuddy(lineEdit); - connect(lineEdit, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormGeneral::returnPressed); + connect(lineEdit, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormGeneral::returnPressed); label = new QLabel(i18n("Author:"), this); - lineEdit = new KLineEdit(this); + lineEdit = new QLineEdit(this); layout->addRow(label, lineEdit); lineEdit->setClearButtonEnabled(true); queryFields.insert(OnlineSearchAbstract::queryKeyAuthor, lineEdit); label->setBuddy(lineEdit); - connect(lineEdit, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormGeneral::returnPressed); + connect(lineEdit, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormGeneral::returnPressed); label = new QLabel(i18n("Year:"), this); - lineEdit = new KLineEdit(this); + lineEdit = new QLineEdit(this); layout->addRow(label, lineEdit); lineEdit->setClearButtonEnabled(true); queryFields.insert(OnlineSearchAbstract::queryKeyYear, lineEdit); label->setBuddy(lineEdit); - connect(lineEdit, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormGeneral::returnPressed); + connect(lineEdit, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormGeneral::returnPressed); label = new QLabel(i18n("Number of Results:"), this); numResultsField = new QSpinBox(this); layout->addRow(label, numResultsField); numResultsField->setMinimum(3); numResultsField->setMaximum(100); numResultsField->setValue(20); label->setBuddy(numResultsField); loadState(); } bool OnlineSearchQueryFormGeneral::readyToStart() const { - for (QMap<QString, KLineEdit *>::ConstIterator it = queryFields.constBegin(); it != queryFields.constEnd(); ++it) + for (QMap<QString, QLineEdit *>::ConstIterator it = queryFields.constBegin(); it != queryFields.constEnd(); ++it) if (!it.value()->text().isEmpty()) return true; return false; } void OnlineSearchQueryFormGeneral::copyFromEntry(const Entry &entry) { queryFields[OnlineSearchAbstract::queryKeyFreeText]->setText(guessFreeText(entry)); queryFields[OnlineSearchAbstract::queryKeyTitle]->setText(PlainTextValue::text(entry[Entry::ftTitle])); queryFields[OnlineSearchAbstract::queryKeyAuthor]->setText(authorLastNames(entry).join(QStringLiteral(" "))); queryFields[OnlineSearchAbstract::queryKeyYear]->setText(PlainTextValue::text(entry[Entry::ftYear])); } QMap<QString, QString> OnlineSearchQueryFormGeneral::getQueryTerms() { QMap<QString, QString> result; - for (QMap<QString, KLineEdit *>::ConstIterator it = queryFields.constBegin(); it != queryFields.constEnd(); ++it) { + for (QMap<QString, QLineEdit *>::ConstIterator it = queryFields.constBegin(); it != queryFields.constEnd(); ++it) { if (!it.value()->text().isEmpty()) result.insert(it.key(), it.value()->text()); } saveState(); return result; } int OnlineSearchQueryFormGeneral::getNumResults() { return numResultsField->value(); } void OnlineSearchQueryFormGeneral::loadState() { KConfigGroup configGroup(config, configGroupName); - for (QMap<QString, KLineEdit *>::ConstIterator it = queryFields.constBegin(); it != queryFields.constEnd(); ++it) { + for (QMap<QString, QLineEdit *>::ConstIterator it = queryFields.constBegin(); it != queryFields.constEnd(); ++it) { it.value()->setText(configGroup.readEntry(it.key(), QString())); } numResultsField->setValue(configGroup.readEntry(QStringLiteral("numResults"), 10)); } void OnlineSearchQueryFormGeneral::saveState() { KConfigGroup configGroup(config, configGroupName); - for (QMap<QString, KLineEdit *>::ConstIterator it = queryFields.constBegin(); it != queryFields.constEnd(); ++it) { + for (QMap<QString, QLineEdit *>::ConstIterator it = queryFields.constBegin(); it != queryFields.constEnd(); ++it) { configGroup.writeEntry(it.key(), it.value()->text()); } configGroup.writeEntry(QStringLiteral("numResults"), numResultsField->value()); config->sync(); } #endif // HAVE_QTWIDGETS diff --git a/src/networking/onlinesearch/onlinesearchgeneral.h b/src/networking/onlinesearch/onlinesearchgeneral.h index 6ba389fa..406d1a16 100644 --- a/src/networking/onlinesearch/onlinesearchgeneral.h +++ b/src/networking/onlinesearch/onlinesearchgeneral.h @@ -1,53 +1,52 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #ifndef KBIBTEX_NETWORKING_ONLINESEARCHGENERAL_H #define KBIBTEX_NETWORKING_ONLINESEARCHGENERAL_H #include <KSharedConfig> #include "onlinesearchabstract.h" #ifdef HAVE_QTWIDGETS class QSpinBox; - -class KLineEdit; +class QLineEdit; class KBIBTEXNETWORKING_EXPORT OnlineSearchQueryFormGeneral : public OnlineSearchQueryFormAbstract { Q_OBJECT public: explicit OnlineSearchQueryFormGeneral(QWidget *parent); bool readyToStart() const override; void copyFromEntry(const Entry &) override; QMap<QString, QString> getQueryTerms(); int getNumResults(); private: - QMap<QString, KLineEdit *> queryFields; + QMap<QString, QLineEdit *> queryFields; QSpinBox *numResultsField; const QString configGroupName; void loadState(); void saveState(); }; #endif // HAVE_QTWIDGETS #endif // KBIBTEX_NETWORKING_ONLINESEARCHGENERAL_H diff --git a/src/networking/onlinesearch/onlinesearchingentaconnect.cpp b/src/networking/onlinesearch/onlinesearchingentaconnect.cpp index 0c320ded..b79a8801 100644 --- a/src/networking/onlinesearch/onlinesearchingentaconnect.cpp +++ b/src/networking/onlinesearch/onlinesearchingentaconnect.cpp @@ -1,424 +1,424 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "onlinesearchingentaconnect.h" #include <QBuffer> #ifdef HAVE_QTWIDGETS #include <QLabel> #include <QFormLayout> #include <QSpinBox> +#include <QLineEdit> #include <QIcon> #endif // HAVE_QTWIDGETS #include <QNetworkReply> #include <QUrlQuery> #ifdef HAVE_KF5 #include <KLocalizedString> #include <KConfigGroup> -#include <KLineEdit> #else // HAVE_KF5 #define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "file.h" #include "entry.h" #include "fileimporterbibtex.h" #include "internalnetworkaccessmanager.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS class OnlineSearchIngentaConnect::OnlineSearchQueryFormIngentaConnect : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); lineEditFullText->setText(configGroup.readEntry(QStringLiteral("fullText"), QString())); lineEditTitle->setText(configGroup.readEntry(QStringLiteral("title"), QString())); lineEditAuthor->setText(configGroup.readEntry(QStringLiteral("author"), QString())); lineEditAbstractKeywords->setText(configGroup.readEntry(QStringLiteral("abstractKeywords"), QString())); lineEditPublication->setText(configGroup.readEntry(QStringLiteral("publication"), QString())); lineEditISSNDOIISBN->setText(configGroup.readEntry(QStringLiteral("ISSNDOIISBN"), QString())); lineEditVolume->setText(configGroup.readEntry(QStringLiteral("volume"), QString())); lineEditIssue->setText(configGroup.readEntry(QStringLiteral("issue"), QString())); numResultsField->setValue(configGroup.readEntry(QStringLiteral("numResults"), 10)); } public: - KLineEdit *lineEditFullText; - KLineEdit *lineEditTitle; - KLineEdit *lineEditAuthor; - KLineEdit *lineEditAbstractKeywords; - KLineEdit *lineEditPublication; - KLineEdit *lineEditISSNDOIISBN; - KLineEdit *lineEditVolume; - KLineEdit *lineEditIssue; + QLineEdit *lineEditFullText; + QLineEdit *lineEditTitle; + QLineEdit *lineEditAuthor; + QLineEdit *lineEditAbstractKeywords; + QLineEdit *lineEditPublication; + QLineEdit *lineEditISSNDOIISBN; + QLineEdit *lineEditVolume; + QLineEdit *lineEditIssue; QSpinBox *numResultsField; OnlineSearchQueryFormIngentaConnect(QWidget *widget) : OnlineSearchQueryFormAbstract(widget), configGroupName(QStringLiteral("Search Engine IngentaConnect")) { QFormLayout *layout = new QFormLayout(this); layout->setMargin(0); - lineEditFullText = new KLineEdit(this); + lineEditFullText = new QLineEdit(this); lineEditFullText->setClearButtonEnabled(true); layout->addRow(i18n("Full text:"), lineEditFullText); - connect(lineEditFullText, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); + connect(lineEditFullText, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); - lineEditTitle = new KLineEdit(this); + lineEditTitle = new QLineEdit(this); lineEditTitle->setClearButtonEnabled(true); layout->addRow(i18n("Title:"), lineEditTitle); - connect(lineEditTitle, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); + connect(lineEditTitle, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); - lineEditAuthor = new KLineEdit(this); + lineEditAuthor = new QLineEdit(this); lineEditAuthor->setClearButtonEnabled(true); layout->addRow(i18n("Author:"), lineEditAuthor); - connect(lineEditAuthor, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); + connect(lineEditAuthor, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); - lineEditAbstractKeywords = new KLineEdit(this); + lineEditAbstractKeywords = new QLineEdit(this); lineEditAbstractKeywords->setClearButtonEnabled(true); layout->addRow(i18n("Abstract/Keywords:"), lineEditAbstractKeywords); - connect(lineEditAbstractKeywords, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); + connect(lineEditAbstractKeywords, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); - lineEditPublication = new KLineEdit(this); + lineEditPublication = new QLineEdit(this); lineEditPublication->setClearButtonEnabled(true); layout->addRow(i18n("Publication:"), lineEditPublication); - connect(lineEditPublication, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); + connect(lineEditPublication, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); - lineEditISSNDOIISBN = new KLineEdit(this); + lineEditISSNDOIISBN = new QLineEdit(this); lineEditISSNDOIISBN->setClearButtonEnabled(true); layout->addRow(i18n("ISSN/ISBN/DOI:"), lineEditISSNDOIISBN); - connect(lineEditISSNDOIISBN, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); + connect(lineEditISSNDOIISBN, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); - lineEditVolume = new KLineEdit(this); + lineEditVolume = new QLineEdit(this); lineEditVolume->setClearButtonEnabled(true); layout->addRow(i18n("Volume:"), lineEditVolume); - connect(lineEditVolume, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); + connect(lineEditVolume, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); - lineEditIssue = new KLineEdit(this); + lineEditIssue = new QLineEdit(this); lineEditIssue->setClearButtonEnabled(true); layout->addRow(i18n("Issue/Number:"), lineEditIssue); - connect(lineEditIssue, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); + connect(lineEditIssue, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormIngentaConnect::returnPressed); numResultsField = new QSpinBox(this); layout->addRow(i18n("Number of Results:"), numResultsField); numResultsField->setMinimum(3); numResultsField->setMaximum(100); numResultsField->setValue(10); } bool readyToStart() const override { return !(lineEditFullText->text().isEmpty() && lineEditTitle->text().isEmpty() && lineEditAuthor->text().isEmpty() && lineEditAbstractKeywords->text().isEmpty() && lineEditPublication->text().isEmpty() && lineEditISSNDOIISBN->text().isEmpty() && lineEditVolume->text().isEmpty() && lineEditIssue->text().isEmpty()); } void copyFromEntry(const Entry &entry) override { lineEditTitle->setText(PlainTextValue::text(entry[Entry::ftTitle])); lineEditAuthor->setText(authorLastNames(entry).join(QStringLiteral(" "))); lineEditVolume->setText(PlainTextValue::text(entry[Entry::ftVolume])); lineEditIssue->setText(PlainTextValue::text(entry[Entry::ftNumber])); QString issnDoiIsbn = PlainTextValue::text(entry[Entry::ftDOI]); if (issnDoiIsbn.isEmpty()) issnDoiIsbn = PlainTextValue::text(entry[Entry::ftISBN]); if (issnDoiIsbn.isEmpty()) issnDoiIsbn = PlainTextValue::text(entry[Entry::ftISSN]); lineEditISSNDOIISBN->setText(issnDoiIsbn); QString publication = PlainTextValue::text(entry[Entry::ftJournal]); if (publication.isEmpty()) publication = PlainTextValue::text(entry[Entry::ftBookTitle]); lineEditPublication->setText(publication); // TODO lineEditAbstractKeywords->setText(QString()); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("fullText"), lineEditFullText->text()); configGroup.writeEntry(QStringLiteral("title"), lineEditTitle->text()); configGroup.writeEntry(QStringLiteral("author"), lineEditAuthor->text()); configGroup.writeEntry(QStringLiteral("abstractKeywords"), lineEditAbstractKeywords->text()); configGroup.writeEntry(QStringLiteral("publication"), lineEditPublication->text()); configGroup.writeEntry(QStringLiteral("ISSNDOIISBN"), lineEditISSNDOIISBN->text()); configGroup.writeEntry(QStringLiteral("volume"), lineEditVolume->text()); configGroup.writeEntry(QStringLiteral("issue"), lineEditIssue->text()); configGroup.writeEntry(QStringLiteral("numResults"), numResultsField->value()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchIngentaConnect::OnlineSearchIngentaConnectPrivate { private: const QString ingentaConnectBaseUrl; public: #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormIngentaConnect *form; #endif // HAVE_QTWIDGETS OnlineSearchIngentaConnectPrivate(OnlineSearchIngentaConnect *) : ingentaConnectBaseUrl(QStringLiteral("https://www.ingentaconnect.com/search?format=bib")) #ifdef HAVE_QTWIDGETS , form(nullptr) #endif // HAVE_QTWIDGETS { /// nothing } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { if (form == nullptr) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Cannot build query url if no form is specified"; return QUrl(); } QUrl queryUrl(ingentaConnectBaseUrl); QUrlQuery query(queryUrl); int index = 1; const QStringList chunksFullText = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditFullText->text()); for (const QString &chunk : chunksFullText) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); ///< join search terms with an AND operation query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("fulltext")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksAuthor = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditAuthor->text()); for (const QString &chunk : chunksAuthor) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("author")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksTitle = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditTitle->text()); for (const QString &chunk : chunksTitle) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("title")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksPublication = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditPublication->text()); for (const QString &chunk : chunksPublication) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("journalbooktitle")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksIssue = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditIssue->text()); for (const QString &chunk : chunksIssue) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("issue")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksVolume = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditVolume->text()); for (const QString &chunk : chunksVolume) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("volume")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksKeywords = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditAbstractKeywords->text()); for (const QString &chunk : chunksKeywords) { if (index > 1) query.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); query.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("tka")); query.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } query.addQueryItem(QStringLiteral("pageSize"), QString::number(form->numResultsField->value())); query.addQueryItem(QStringLiteral("sortDescending"), QStringLiteral("true")); query.addQueryItem(QStringLiteral("subscribed"), QStringLiteral("false")); query.addQueryItem(QStringLiteral("sortField"), QStringLiteral("default")); queryUrl.setQuery(query); return queryUrl; } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { QUrl queryUrl(ingentaConnectBaseUrl); QUrlQuery q(queryUrl); int index = 1; const QStringList chunksFreeText = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyFreeText]); for (const QString &chunk : chunksFreeText) { if (index > 1) q.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); q.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("fulltext")); q.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksAuthor = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyAuthor]); for (const QString &chunk : chunksAuthor) { if (index > 1) q.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); q.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("author")); q.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } const QStringList chunksTitle = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyTitle]); for (const QString &chunk : chunksTitle) { if (index > 1) q.addQueryItem(QString(QStringLiteral("operator%1")).arg(index), QStringLiteral("AND")); q.addQueryItem(QString(QStringLiteral("option%1")).arg(index), QStringLiteral("title")); q.addQueryItem(QString(QStringLiteral("value%1")).arg(index), chunk); ++index; } /// Field "year" not supported in IngentaConnect's search q.addQueryItem(QStringLiteral("pageSize"), QString::number(numResults)); q.addQueryItem(QStringLiteral("sortDescending"), QStringLiteral("true")); q.addQueryItem(QStringLiteral("subscribed"), QStringLiteral("false")); q.addQueryItem(QStringLiteral("sortField"), QStringLiteral("default")); queryUrl.setQuery(q); return queryUrl; } }; OnlineSearchIngentaConnect::OnlineSearchIngentaConnect(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchIngentaConnectPrivate(this)) { /// nothing } OnlineSearchIngentaConnect::~OnlineSearchIngentaConnect() { delete d; } void OnlineSearchIngentaConnect::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl(query, numResults)); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchIngentaConnect::downloadDone); refreshBusyProperty(); } #ifdef HAVE_QTWIDGETS void OnlineSearchIngentaConnect::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(d->buildQueryUrl()); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchIngentaConnect::downloadDone); d->form->saveState(); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS QString OnlineSearchIngentaConnect::label() const { #ifdef HAVE_KF5 return i18n("IngentaConnect"); #else // HAVE_KF5 //= onlinesearch-ingentaconnect-label return QObject::tr("IngentaConnect"); #endif // HAVE_KF5 } QString OnlineSearchIngentaConnect::favIconUrl() const { return QStringLiteral("http://www.ingentaconnect.com/favicon.ico"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchIngentaConnect::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchIngentaConnect::OnlineSearchQueryFormIngentaConnect(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchIngentaConnect::homepage() const { return QUrl(QStringLiteral("https://www.ingentaconnect.com/")); } void OnlineSearchIngentaConnect::downloadDone() { emit progress(curStep = numSteps, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters QString bibTeXcode = QString::fromUtf8(reply->readAll().constData()); if (!bibTeXcode.isEmpty()) { FileImporterBibTeX importer(this); File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const auto &element : const_cast<const File &>(*bibtexFile)) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } else { /// returned file is empty stopSearch(resultNoError); } } refreshBusyProperty(); } #include "onlinesearchingentaconnect.moc" diff --git a/src/networking/onlinesearch/onlinesearchsemanticscholar.cpp b/src/networking/onlinesearch/onlinesearchsemanticscholar.cpp index 63ecddd2..ed9a58de 100644 --- a/src/networking/onlinesearch/onlinesearchsemanticscholar.cpp +++ b/src/networking/onlinesearch/onlinesearchsemanticscholar.cpp @@ -1,296 +1,296 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "onlinesearchsemanticscholar.h" #include <QJsonObject> #include <QJsonArray> #include <QJsonDocument> #include <QNetworkRequest> #include <QNetworkReply> #include <QRegularExpression> #ifdef HAVE_QTWIDGETS +#include <QLineEdit> #include <QFormLayout> #endif // HAVE_QTWIDGETS #ifdef HAVE_KF5 -#include <KLineEdit> #include <KConfigGroup> #include <KLocalizedString> #endif // HAVE_KF5 #include "kbibtex.h" #include "fileimporterbibtex.h" #include "internalnetworkaccessmanager.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS class OnlineSearchSemanticScholar::OnlineSearchQueryFormSemanticScholar : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); lineEditPaperReference->setText(configGroup.readEntry(QStringLiteral("paperReference"), QString())); } public: - KLineEdit *lineEditPaperReference; + QLineEdit *lineEditPaperReference; OnlineSearchQueryFormSemanticScholar(QWidget *widget) : OnlineSearchQueryFormAbstract(widget), configGroupName(QStringLiteral("Search Engine Semantic Scholar")) { QFormLayout *layout = new QFormLayout(this); layout->setMargin(0); - lineEditPaperReference = new KLineEdit(this); + lineEditPaperReference = new QLineEdit(this); lineEditPaperReference->setClearButtonEnabled(true); lineEditPaperReference->setFocus(Qt::TabFocusReason); layout->addRow(i18n("Paper Reference:"), lineEditPaperReference); - connect(lineEditPaperReference, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSemanticScholar::returnPressed); + connect(lineEditPaperReference, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormSemanticScholar::returnPressed); loadState(); } bool readyToStart() const override { return !lineEditPaperReference->text().isEmpty(); } void copyFromEntry(const Entry &entry) override { lineEditPaperReference->setText(guessFreeText(entry)); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("paperReference"), lineEditPaperReference->text()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchSemanticScholar::OnlineSearchSemanticScholarPrivate { public: #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormSemanticScholar *form; #endif // HAVE_QTWIDGETS OnlineSearchSemanticScholarPrivate(OnlineSearchSemanticScholar *parent) #ifdef HAVE_QTWIDGETS : form(nullptr) #endif // HAVE_QTWIDGETS { Q_UNUSED(parent) } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { if (form == nullptr) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Cannot build query url if no form is specified"; return QUrl(); } const QRegularExpressionMatch doiRegExpMatch = KBibTeX::doiRegExp.match(form->lineEditPaperReference->text()); if (doiRegExpMatch.hasMatch()) return QUrl(QStringLiteral("https://api.semanticscholar.org/v1/paper/") + doiRegExpMatch.captured(0)); else { const QRegularExpressionMatch arXivRegExpMatch = KBibTeX::arXivRegExpWithoutPrefix.match(form->lineEditPaperReference->text()); if (arXivRegExpMatch.hasMatch()) return QUrl(QStringLiteral("https://api.semanticscholar.org/v1/paper/") + arXivRegExpMatch.captured(0)); } return QUrl(); } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query, int numResults) { Q_UNUSED(numResults) const QRegularExpressionMatch doiRegExpMatch = KBibTeX::doiRegExp.match(query[queryKeyFreeText]); if (doiRegExpMatch.hasMatch()) return QUrl(QStringLiteral("https://api.semanticscholar.org/v1/paper/") + doiRegExpMatch.captured(0)); else { const QRegularExpressionMatch arXivRegExpMatch = KBibTeX::arXivRegExpWithoutPrefix.match(query[queryKeyFreeText]); if (arXivRegExpMatch.hasMatch()) return QUrl(QStringLiteral("https://api.semanticscholar.org/v1/paper/") + arXivRegExpMatch.captured(0)); } return QUrl(); } Entry *entryFromJsonObject(const QJsonObject &object) const { const QString title = object.value(QStringLiteral("title")).toString(); const QString paperId = object.value(QStringLiteral("paperId")).toString(); const int year = object.value(QStringLiteral("year")).toInt(-1); /// Basic sanity check if (title.isEmpty() || paperId.isEmpty() || year < 1700) return nullptr; Entry *entry = new Entry(Entry::etMisc, QStringLiteral("SemanticScholar:") + paperId); entry->insert(Entry::ftTitle, Value() << QSharedPointer<PlainText>(new PlainText(title))); entry->insert(QStringLiteral("x-paperId"), Value() << QSharedPointer<VerbatimText>(new VerbatimText(paperId))); entry->insert(Entry::ftYear, Value() << QSharedPointer<PlainText>(new PlainText(QString::number(year)))); const QString doi = object.value(QStringLiteral("doi")).toString(); const QRegularExpressionMatch doiRegExpMatch = KBibTeX::doiRegExp.match(doi); if (doiRegExpMatch.hasMatch()) entry->insert(Entry::ftDOI, Value() << QSharedPointer<VerbatimText>(new VerbatimText(doiRegExpMatch.captured()))); const QString arxivId = object.value(QStringLiteral("arxivId")).toString(); const QRegularExpressionMatch arXivRegExpMatch = KBibTeX::arXivRegExpWithoutPrefix.match(arxivId); if (arXivRegExpMatch.hasMatch()) entry->insert(QStringLiteral("eprint"), Value() << QSharedPointer<VerbatimText>(new VerbatimText(arXivRegExpMatch.captured()))); const QJsonArray authorArray = object.value(QStringLiteral("authors")).toArray(); Value authors; for (const QJsonValue &author : authorArray) { const QString name = author.toObject().value(QStringLiteral("name")).toString(); if (!name.isEmpty()) { QSharedPointer<Person> person = FileImporterBibTeX::personFromString(name); if (!person.isNull()) authors.append(person); } } if (!authors.isEmpty()) entry->insert(Entry::ftAuthor, authors); return entry; } }; OnlineSearchSemanticScholar::OnlineSearchSemanticScholar(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchSemanticScholarPrivate(this)) { /// nothing } OnlineSearchSemanticScholar::~OnlineSearchSemanticScholar() { delete d; } #ifdef HAVE_QTWIDGETS void OnlineSearchSemanticScholar::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); const QUrl url = d->buildQueryUrl(); if (url.isValid()) { QNetworkRequest request(url); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchSemanticScholar::downloadDone); d->form->saveState(); } else delayedStoppedSearch(resultNoError); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS void OnlineSearchSemanticScholar::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); const QUrl url = d->buildQueryUrl(query, numResults); if (url.isValid()) { QNetworkRequest request(url); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchSemanticScholar::downloadDone); } else delayedStoppedSearch(resultNoError); refreshBusyProperty(); } QString OnlineSearchSemanticScholar::label() const { return i18n("Semantic Scholar"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchSemanticScholar::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchSemanticScholar::OnlineSearchQueryFormSemanticScholar(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchSemanticScholar::homepage() const { return QUrl(QStringLiteral("https://www.semanticscholar.org/")); } QString OnlineSearchSemanticScholar::favIconUrl() const { return QStringLiteral("https://www.semanticscholar.org/img/favicon.png"); } void OnlineSearchSemanticScholar::downloadDone() { emit progress(++curStep, numSteps); QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); QUrl redirUrl; if (handleErrors(reply, redirUrl)) { if (redirUrl.isValid()) { /// redirection to another url ++numSteps; QNetworkRequest request(redirUrl); QNetworkReply *newReply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(newReply); connect(newReply, &QNetworkReply::finished, this, &OnlineSearchSemanticScholar::downloadDone); } else { QJsonParseError parseError; const auto buffer = reply->readAll(); const QString jsonText = QString::fromUtf8(buffer); dumpToFile(QStringLiteral("semanticscholar.json"), jsonText); const QJsonDocument document = QJsonDocument::fromJson(buffer, &parseError); if (parseError.error == QJsonParseError::NoError) { if (document.isObject()) { Entry *entry = d->entryFromJsonObject(document.object()); if (entry != nullptr) { publishEntry(QSharedPointer<Entry>(entry)); stopSearch(resultNoError); } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "Problem with JSON data from Semantic Scholar: Data could not be interpreted as a bibliographic entry"; stopSearch(resultUnspecifiedError); } } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "Problem with JSON data from Semantic Scholar: Document is not an object"; stopSearch(resultUnspecifiedError); } } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "Problem with JSON data from Semantic Scholar: " << parseError.errorString(); stopSearch(resultUnspecifiedError); } } } refreshBusyProperty(); } #include "onlinesearchsemanticscholar.moc" diff --git a/src/networking/onlinesearch/onlinesearchspringerlink.cpp b/src/networking/onlinesearch/onlinesearchspringerlink.cpp index a6e402d1..0612d628 100644 --- a/src/networking/onlinesearch/onlinesearchspringerlink.cpp +++ b/src/networking/onlinesearch/onlinesearchspringerlink.cpp @@ -1,357 +1,357 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "onlinesearchspringerlink.h" #ifdef HAVE_QTWIDGETS #include <QFormLayout> #include <QSpinBox> +#include <QLineEdit> #include <QLabel> #endif // HAVE_QTWIDGETS #include <QRegularExpression> #include <QNetworkRequest> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QUrlQuery> #ifdef HAVE_KF5 #include <KLocalizedString> -#include <KLineEdit> #include <KConfigGroup> #else // HAVE_KF5 #define i18n(text) QObject::tr(text) #endif // HAVE_KF5 #include "internalnetworkaccessmanager.h" #include "encoder.h" #include "encoderxml.h" #include "fileimporterbibtex.h" #include "xsltransform.h" #include "logging_networking.h" #ifdef HAVE_QTWIDGETS /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class OnlineSearchSpringerLink::OnlineSearchQueryFormSpringerLink : public OnlineSearchQueryFormAbstract { Q_OBJECT private: QString configGroupName; void loadState() { KConfigGroup configGroup(config, configGroupName); lineEditFreeText->setText(configGroup.readEntry(QStringLiteral("free"), QString())); lineEditTitle->setText(configGroup.readEntry(QStringLiteral("title"), QString())); lineEditBookTitle->setText(configGroup.readEntry(QStringLiteral("bookTitle"), QString())); lineEditAuthorEditor->setText(configGroup.readEntry(QStringLiteral("authorEditor"), QString())); lineEditYear->setText(configGroup.readEntry(QStringLiteral("year"), QString())); numResultsField->setValue(configGroup.readEntry(QStringLiteral("numResults"), 10)); } public: - KLineEdit *lineEditFreeText, *lineEditTitle, *lineEditBookTitle, *lineEditAuthorEditor, *lineEditYear; + QLineEdit *lineEditFreeText, *lineEditTitle, *lineEditBookTitle, *lineEditAuthorEditor, *lineEditYear; QSpinBox *numResultsField; OnlineSearchQueryFormSpringerLink(QWidget *parent) : OnlineSearchQueryFormAbstract(parent), configGroupName(QStringLiteral("Search Engine SpringerLink")) { QFormLayout *layout = new QFormLayout(this); layout->setMargin(0); - lineEditFreeText = new KLineEdit(this); + lineEditFreeText = new QLineEdit(this); lineEditFreeText->setClearButtonEnabled(true); QLabel *label = new QLabel(i18n("Free Text:"), this); label->setBuddy(lineEditFreeText); layout->addRow(label, lineEditFreeText); - connect(lineEditFreeText, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); + connect(lineEditFreeText, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); - lineEditTitle = new KLineEdit(this); + lineEditTitle = new QLineEdit(this); lineEditTitle->setClearButtonEnabled(true); label = new QLabel(i18n("Title:"), this); label->setBuddy(lineEditTitle); layout->addRow(label, lineEditTitle); - connect(lineEditTitle, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); + connect(lineEditTitle, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); - lineEditBookTitle = new KLineEdit(this); + lineEditBookTitle = new QLineEdit(this); lineEditBookTitle->setClearButtonEnabled(true); label = new QLabel(i18n("Book/Journal title:"), this); label->setBuddy(lineEditBookTitle); layout->addRow(label, lineEditBookTitle); - connect(lineEditBookTitle, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); + connect(lineEditBookTitle, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); - lineEditAuthorEditor = new KLineEdit(this); + lineEditAuthorEditor = new QLineEdit(this); lineEditAuthorEditor->setClearButtonEnabled(true); label = new QLabel(i18n("Author or Editor:"), this); label->setBuddy(lineEditAuthorEditor); layout->addRow(label, lineEditAuthorEditor); - connect(lineEditAuthorEditor, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); + connect(lineEditAuthorEditor, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); - lineEditYear = new KLineEdit(this); + lineEditYear = new QLineEdit(this); lineEditYear->setClearButtonEnabled(true); label = new QLabel(i18n("Year:"), this); label->setBuddy(lineEditYear); layout->addRow(label, lineEditYear); - connect(lineEditYear, &KLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); + connect(lineEditYear, &QLineEdit::returnPressed, this, &OnlineSearchQueryFormSpringerLink::returnPressed); numResultsField = new QSpinBox(this); label = new QLabel(i18n("Number of Results:"), this); label->setBuddy(numResultsField); layout->addRow(label, numResultsField); numResultsField->setMinimum(3); numResultsField->setMaximum(100); lineEditFreeText->setFocus(Qt::TabFocusReason); loadState(); } bool readyToStart() const override { return !(lineEditFreeText->text().isEmpty() && lineEditTitle->text().isEmpty() && lineEditBookTitle->text().isEmpty() && lineEditAuthorEditor->text().isEmpty()); } void copyFromEntry(const Entry &entry) override { lineEditTitle->setText(PlainTextValue::text(entry[Entry::ftTitle])); QString bookTitle = PlainTextValue::text(entry[Entry::ftBookTitle]); if (bookTitle.isEmpty()) bookTitle = PlainTextValue::text(entry[Entry::ftJournal]); lineEditBookTitle->setText(bookTitle); lineEditAuthorEditor->setText(authorLastNames(entry).join(QStringLiteral(" "))); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(QStringLiteral("free"), lineEditFreeText->text()); configGroup.writeEntry(QStringLiteral("title"), lineEditTitle->text()); configGroup.writeEntry(QStringLiteral("bookTitle"), lineEditBookTitle->text()); configGroup.writeEntry(QStringLiteral("authorEditor"), lineEditAuthorEditor->text()); configGroup.writeEntry(QStringLiteral("year"), lineEditYear->text()); configGroup.writeEntry(QStringLiteral("numResults"), numResultsField->value()); config->sync(); } }; #endif // HAVE_QTWIDGETS class OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate { private: static const QString xsltFilenameBase; public: static const QString springerMetadataKey; const XSLTransform xslt; #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormSpringerLink *form; #endif // HAVE_QTWIDGETS OnlineSearchSpringerLinkPrivate(OnlineSearchSpringerLink *) : xslt(XSLTransform::locateXSLTfile(xsltFilenameBase)) #ifdef HAVE_QTWIDGETS , form(nullptr) #endif // HAVE_QTWIDGETS { if (!xslt.isValid()) qCWarning(LOG_KBIBTEX_NETWORKING) << "Failed to initialize XSL transformation based on file '" << xsltFilenameBase << "'"; } #ifdef HAVE_QTWIDGETS QUrl buildQueryUrl() { if (form == nullptr) return QUrl(); QUrl queryUrl = QUrl(QString(QStringLiteral("http://api.springer.com/metadata/pam/?api_key=")).append(springerMetadataKey)); QString queryString = form->lineEditFreeText->text(); const QStringList titleChunks = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditTitle->text()); for (const QString &titleChunk : titleChunks) { queryString += QString(QStringLiteral(" title:%1")).arg(Encoder::instance().convertToPlainAscii(titleChunk)); } const QStringList bookTitleChunks = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditBookTitle->text()); for (const QString &titleChunk : bookTitleChunks) { queryString += QString(QStringLiteral(" ( journal:%1 OR book:%1 )")).arg(Encoder::instance().convertToPlainAscii(titleChunk)); } const QStringList authors = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditAuthorEditor->text()); for (const QString &author : authors) { queryString += QString(QStringLiteral(" name:%1")).arg(Encoder::instance().convertToPlainAscii(author)); } const QString year = form->lineEditYear->text(); if (!year.isEmpty()) queryString += QString(QStringLiteral(" year:%1")).arg(year); queryString = queryString.simplified(); QUrlQuery query(queryUrl); query.addQueryItem(QStringLiteral("q"), queryString); queryUrl.setQuery(query); return queryUrl; } #endif // HAVE_QTWIDGETS QUrl buildQueryUrl(const QMap<QString, QString> &query) { QUrl queryUrl = QUrl(QString(QStringLiteral("http://api.springer.com/metadata/pam/?api_key=")).append(springerMetadataKey)); QString queryString = query[queryKeyFreeText]; const QStringList titleChunks = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyTitle]); for (const QString &titleChunk : titleChunks) { queryString += QString(QStringLiteral(" title:%1")).arg(Encoder::instance().convertToPlainAscii(titleChunk)); } const QStringList authors = OnlineSearchAbstract::splitRespectingQuotationMarks(query[queryKeyAuthor]); for (const QString &author : authors) { queryString += QString(QStringLiteral(" name:%1")).arg(Encoder::instance().convertToPlainAscii(author)); } QString year = query[queryKeyYear]; if (!year.isEmpty()) { static const QRegularExpression yearRegExp("\\b(18|19|20)[0-9]{2}\\b"); const QRegularExpressionMatch yearRegExpMatch = yearRegExp.match(year); if (yearRegExpMatch.hasMatch()) { year = yearRegExpMatch.captured(0); queryString += QString(QStringLiteral(" year:%1")).arg(year); } } queryString = queryString.simplified(); QUrlQuery q(queryUrl); q.addQueryItem(QStringLiteral("q"), queryString); queryUrl.setQuery(q); return queryUrl; } }; const QString OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate::xsltFilenameBase = QStringLiteral("pam2bibtex.xsl"); const QString OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate::springerMetadataKey(InternalNetworkAccessManager::reverseObfuscate("\xce\xb8\x4d\x2c\x8d\xba\xa9\xc4\x61\x9\x58\x6c\xbb\xde\x86\xb5\xb1\xc6\x15\x71\x76\x45\xd\x79\x12\x65\x95\xe1\x5d\x2f\x1d\x24\x10\x72\x2a\x5e\x69\x4\xdc\xba\xab\xc3\x28\x58\x8a\xfa\x5e\x69")); OnlineSearchSpringerLink::OnlineSearchSpringerLink(QObject *parent) : OnlineSearchAbstract(parent), d(new OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate(this)) { /// nothing } OnlineSearchSpringerLink::~OnlineSearchSpringerLink() { delete d; } #ifdef HAVE_QTWIDGETS void OnlineSearchSpringerLink::startSearchFromForm() { m_hasBeenCanceled = false; emit progress(curStep = 0, numSteps = 1); QUrl springerLinkSearchUrl = d->buildQueryUrl(); QNetworkRequest request(springerLinkSearchUrl); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchSpringerLink::doneFetchingPAM); if (d->form != nullptr) d->form->saveState(); refreshBusyProperty(); } #endif // HAVE_QTWIDGETS void OnlineSearchSpringerLink::startSearch(const QMap<QString, QString> &query, int numResults) { m_hasBeenCanceled = false; QUrl springerLinkSearchUrl = d->buildQueryUrl(query); QUrlQuery q(springerLinkSearchUrl); q.addQueryItem(QStringLiteral("p"), QString::number(numResults)); springerLinkSearchUrl.setQuery(q); emit progress(curStep = 0, numSteps = 1); QNetworkRequest request(springerLinkSearchUrl); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); connect(reply, &QNetworkReply::finished, this, &OnlineSearchSpringerLink::doneFetchingPAM); refreshBusyProperty(); } QString OnlineSearchSpringerLink::label() const { #ifdef HAVE_KF5 return i18n("SpringerLink"); #else // HAVE_KF5 //= onlinesearch-springerlink-label return QObject::tr("SpringerLink"); #endif // HAVE_KF5 } QString OnlineSearchSpringerLink::favIconUrl() const { return QStringLiteral("http://link.springer.com/static/0.6623/sites/link/images/favicon.ico"); } #ifdef HAVE_QTWIDGETS OnlineSearchQueryFormAbstract *OnlineSearchSpringerLink::customWidget(QWidget *parent) { if (d->form == nullptr) d->form = new OnlineSearchQueryFormSpringerLink(parent); return d->form; } #endif // HAVE_QTWIDGETS QUrl OnlineSearchSpringerLink::homepage() const { return QUrl(QStringLiteral("http://www.springerlink.com/")); } void OnlineSearchSpringerLink::doneFetchingPAM() { QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); if (handleErrors(reply)) { /// ensure proper treatment of UTF-8 characters const QString xmlSource = QString::fromUtf8(reply->readAll().constData()); const QString bibTeXcode = EncoderXML::instance().decode(d->xslt.transform(xmlSource).remove(QStringLiteral("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"))); if (bibTeXcode.isEmpty()) { qCWarning(LOG_KBIBTEX_NETWORKING) << "XSL tranformation failed for data from " << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultInvalidArguments); } else { FileImporterBibTeX importer(this); const File *bibtexFile = importer.fromString(bibTeXcode); bool hasEntries = false; if (bibtexFile != nullptr) { for (const QSharedPointer<Element> &element : *bibtexFile) { QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); hasEntries |= publishEntry(entry); } stopSearch(resultNoError); delete bibtexFile; } else { qCWarning(LOG_KBIBTEX_NETWORKING) << "No valid BibTeX file results returned on request on" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); stopSearch(resultUnspecifiedError); } } } refreshBusyProperty(); } #include "onlinesearchspringerlink.moc" diff --git a/src/networking/zotero/oauthwizard.cpp b/src/networking/zotero/oauthwizard.cpp index 75a197f4..3f9f122d 100644 --- a/src/networking/zotero/oauthwizard.cpp +++ b/src/networking/zotero/oauthwizard.cpp @@ -1,194 +1,194 @@ /*************************************************************************** - * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * + * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "oauthwizard.h" #include <QLabel> #include <QLayout> #include <QDialogButtonBox> #include <QGridLayout> #include <QClipboard> #include <QApplication> #include <QPushButton> +#include <QLineEdit> #include <QUrl> #include <QUrlQuery> #include <QRegularExpression> #include <QtNetworkAuth> #include <KLocalizedString> -#include <KLineEdit> #include <KRun> #include <kio_version.h> #include "internalnetworkaccessmanager.h" #include "logging_networking.h" using namespace Zotero; class Zotero::OAuthWizard::Private { private: Zotero::OAuthWizard *p; QOAuth1 *qOAuth; - KLineEdit *lineEditAuthorizationUrl; + QLineEdit *lineEditAuthorizationUrl; QPushButton *buttonCopyAuthorizationUrl, *buttonOpenAuthorizationUrl; public: int userId; QString apiKey; Private(Zotero::OAuthWizard *parent) : p(parent), userId(-1) { /// Configure the OAuth 1 object, including KBibTeX's app-specific credentials /// and various URLs required for the OAuth handshakes qOAuth = new QOAuth1(&InternalNetworkAccessManager::instance(), parent); qOAuth->setClientCredentials(InternalNetworkAccessManager::reverseObfuscate("\xb7\xd4\x7d\x48\xc0\xf9\x3f\x9\x3c\x5d\x10\x26\x36\x53\xf6\xcf\x54\x65\xee\xd6\x35\x50\xdb\xea\xb8\x8e\xf4\xcd\x14\x75\xbe\x8a\x55\x33\x4d\x28\xbe\xdb\x60\x51"), InternalNetworkAccessManager::reverseObfuscate("\x95\xa1\xa9\xcb\xc8\xfd\xd8\xbc\x43\x70\x54\x64\x5f\x68\x2b\x1e\xb3\xd1\x29\x1b\x39\xf\x89\xbb\x55\x66\x20\x42\xa7\xc5\x1a\x28\x4e\x7c\xf9\x9b\x9\x6b\x75\x4d")); qOAuth->setSignatureMethod(QOAuth1::SignatureMethod::Hmac_Sha1); qOAuth->setTemporaryCredentialsUrl(QUrl(QStringLiteral("https://www.zotero.org/oauth/request"))); qOAuth->setAuthorizationUrl(QUrl(QStringLiteral("https://www.zotero.org/oauth/authorize"))); qOAuth->setTokenCredentialsUrl(QUrl(QStringLiteral("https://www.zotero.org/oauth/access"))); /// The QOAuthHttpServerReplyHandler will start a small webserver to which the /// user's webbrowser will be redirected to in case that KBibTeX got the /// requested permissions granted by the user when being logged in in Zotero. /// If the correct URL with an authorization token is requested from this /// local webserver, the credentials will be stored and this wizard dialog /// be closed. const int port = qrand() % 50000 + 15000; QOAuthHttpServerReplyHandler *replyHandler = new QOAuthHttpServerReplyHandler(port, parent); replyHandler->setCallbackPath("kbibtex-zotero-oauth"); replyHandler->setCallbackText(i18n("<html><head><title>KBibTeX authorized to use Zotero</title></head><body><p>KBibTeX got successfully authorized to read your Zotero database.</p></body></html>")); qOAuth->setReplyHandler(replyHandler); setupGUI(); /// Disable various controls until delayed initialization is complete lineEditAuthorizationUrl->setEnabled(false); buttonCopyAuthorizationUrl->setEnabled(false); buttonOpenAuthorizationUrl->setEnabled(false); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); connect(qOAuth, &QAbstractOAuth::authorizeWithBrowser, parent, [this, parent](QUrl url) { QUrlQuery query(url); query.addQueryItem(QStringLiteral("name"), QStringLiteral("KBibTeX")); query.addQueryItem(QStringLiteral("library_access"), QStringLiteral("1")); query.addQueryItem(QStringLiteral("notes_access"), QStringLiteral("0")); query.addQueryItem(QStringLiteral("write_access"), QStringLiteral("0")); query.addQueryItem(QStringLiteral("all_groups"), QStringLiteral("read")); url.setQuery(query); /// Received a URL from Zotero which the user can open in a web browser. /// To do that, re-enable various controls. lineEditAuthorizationUrl->setText(url.toDisplayString()); lineEditAuthorizationUrl->setEnabled(true); buttonCopyAuthorizationUrl->setEnabled(true); buttonOpenAuthorizationUrl->setEnabled(true); QApplication::restoreOverrideCursor(); }); connect(replyHandler, &QOAuthHttpServerReplyHandler::tokensReceived, parent, [this, parent](const QVariantMap &tokens) { /// Upon successful authorization, when the web browser is redirected /// to the local webserver run by the QOAuthHttpServerReplyHandler instance, /// extract the relevant parameters passed along as part of the GET /// request sent to this webserver. static const QString oauthTokenKey = QStringLiteral("oauth_token"); static const QString userIdKey = QStringLiteral("userID"); if (tokens.contains(oauthTokenKey) && tokens.contains(userIdKey)) { apiKey = tokens[oauthTokenKey].toString(); bool ok = false; userId = tokens[userIdKey].toString().toInt(&ok); if (!ok) { userId = -1; apiKey.clear(); parent->reject(); } else parent->accept(); } else { apiKey.clear(); userId = -1; } }); qOAuth->grant(); } void setupGUI() { QGridLayout *gridLayout = new QGridLayout(p); gridLayout->setColumnStretch(0, 1); gridLayout->setColumnStretch(1, 0); gridLayout->setColumnStretch(2, 0); QLabel *labelExplanation = new QLabel(i18n("<qt><p>To allow <strong>KBibTeX</strong> access your <strong>Zotero bibliography</strong>, this KBibTeX instance has to be authorized.</p><p>The process of authorization involves multiple steps:</p><ol><li>Open the URL as shown below in a web browser.</li><li>Log in at Zotero and approve the permissions for KBibTeX.</li><li>If successful, you will be redirected to a web page telling you that KBibTeX got authorized.<br/>This window will be closed automatically.</li></ol></qt>"), p); gridLayout->addWidget(labelExplanation, 0, 0, 1, 3); gridLayout->setRowMinimumHeight(1, p->fontMetrics().xHeight() * 2); - lineEditAuthorizationUrl = new KLineEdit(p); + lineEditAuthorizationUrl = new QLineEdit(p); lineEditAuthorizationUrl->setReadOnly(true); gridLayout->addWidget(lineEditAuthorizationUrl, 2, 0, 1, 3); buttonCopyAuthorizationUrl = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy URL"), p); gridLayout->addWidget(buttonCopyAuthorizationUrl, 3, 1, 1, 1); connect(buttonCopyAuthorizationUrl, &QPushButton::clicked, p, [this]() { QApplication::clipboard()->setText(lineEditAuthorizationUrl->text()); }); buttonOpenAuthorizationUrl = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open-remote")), i18n("Open URL"), p); gridLayout->addWidget(buttonOpenAuthorizationUrl, 3, 2, 1, 1); connect(buttonOpenAuthorizationUrl, &QPushButton::clicked, p, [this]() { KRun::runUrl(QUrl(lineEditAuthorizationUrl->text()), QStringLiteral("text/html"), p, KRun::RunFlags()); }); gridLayout->setRowMinimumHeight(4, p->fontMetrics().xHeight() * 2); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, Qt::Horizontal, p); gridLayout->addWidget(buttonBox, 5, 0, 1, 3); connect(buttonBox, &QDialogButtonBox::clicked, [this, buttonBox](QAbstractButton *button) { if (button == buttonBox->button(QDialogButtonBox::Close)) p->reject(); }); p->setWindowTitle(i18n("Zotero OAuth Key Exchange")); } }; OAuthWizard::OAuthWizard(QWidget *parent) : QDialog(parent), d(new OAuthWizard::Private(this)) { /// nothing } OAuthWizard::~OAuthWizard() { delete d; } int OAuthWizard::exec() { const int result = QDialog::exec(); if (result != Accepted || d->userId < 0) { d->userId = -1; d->apiKey.clear(); return Rejected; } else return Accepted; } int OAuthWizard::userId() const { return d->userId; } QString OAuthWizard::apiKey() const { return d->apiKey; } #include "oauthwizard.moc" diff --git a/src/networking/zotero/oauthwizard.h b/src/networking/zotero/oauthwizard.h index 79aee61d..37a8b89c 100644 --- a/src/networking/zotero/oauthwizard.h +++ b/src/networking/zotero/oauthwizard.h @@ -1,51 +1,51 @@ /*************************************************************************** - * Copyright (C) 2004-2017 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * + * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #ifndef KBIBTEX_NETWORKING_ZOTERO_OAUTHWIZARD_H #define KBIBTEX_NETWORKING_ZOTERO_OAUTHWIZARD_H #include <QDialog> #include "kbibtexnetworking_export.h" namespace Zotero { /** * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> */ class KBIBTEXNETWORKING_EXPORT OAuthWizard : public QDialog { Q_OBJECT public: explicit OAuthWizard(QWidget *parent); ~OAuthWizard() override; int exec() override; int userId() const; QString apiKey() const; private: class Private; Private *const d; }; } // end of namespace Zotero #endif // KBIBTEX_NETWORKING_ZOTERO_OAUTHWIZARD_H diff --git a/src/program/docklets/documentpreview.cpp b/src/program/docklets/documentpreview.cpp index dd315a49..62f980b2 100644 --- a/src/program/docklets/documentpreview.cpp +++ b/src/program/docklets/documentpreview.cpp @@ -1,717 +1,717 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "documentpreview.h" #include <typeinfo> #include <QDomDocument> #include <QDomElement> #include <QList> #include <QLayout> #include <QMap> #include <QFileInfo> #include <QResizeEvent> #include <QCheckBox> #include <QMenuBar> #include <QStackedWidget> #include <QDockWidget> #include <QDebug> #include <QPushButton> +#include <QComboBox> #include <QMutex> #include <QMimeDatabase> #include <QMimeType> #include <QIcon> #ifdef HAVE_WEBENGINEWIDGETS #include <QWebEngineView> #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS #include <QWebView> #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS #include <KLocalizedString> -#include <KComboBox> #include <KJobWidgets> #include <KRun> #include <KMimeTypeTrader> #include <KService> #include <KParts/Part> #include <KParts/ReadOnlyPart> #include <kio/jobclasses.h> #include <kio/job.h> #include <kio/jobuidelegate.h> #include <KToolBar> #include <KActionCollection> #include <KSharedConfig> #include <KConfigGroup> #include <kio_version.h> #include "kbibtex.h" #include "element.h" #include "entry.h" #include "file.h" #include "fileinfo.h" #include "logging_program.h" ImageLabel::ImageLabel(const QString &text, QWidget *parent, Qt::WindowFlags f) : QLabel(text, parent, f) { /// nothing } void ImageLabel::setPixmap(const QPixmap &pixmap) { m_pixmap = pixmap; if (!m_pixmap.isNull()) { setCursor(Qt::WaitCursor); QPixmap scaledPixmap = m_pixmap.width() <= width() && m_pixmap.height() <= height() ? m_pixmap : pixmap.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QLabel::setPixmap(scaledPixmap); setMinimumSize(100, 100); unsetCursor(); } else QLabel::setPixmap(m_pixmap); } void ImageLabel::resizeEvent(QResizeEvent *event) { QLabel::resizeEvent(event); if (!m_pixmap.isNull()) { setCursor(Qt::WaitCursor); QPixmap scaledPixmap = m_pixmap.width() <= event->size().width() && m_pixmap.height() <= event->size().height() ? m_pixmap : m_pixmap.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QLabel::setPixmap(scaledPixmap); setMinimumSize(100, 100); unsetCursor(); } } class DocumentPreview::DocumentPreviewPrivate { public: struct UrlInfo { QUrl url; QString mimeType; QIcon icon; }; private: DocumentPreview *p; KSharedConfigPtr config; static const QString configGroupName; static const QString onlyLocalFilesCheckConfig; QPushButton *externalViewerButton; QStackedWidget *stackedWidget; ImageLabel *message; QMap<int, struct UrlInfo> cbxEntryToUrlInfo; QMutex addingUrlMutex; static const QString arXivPDFUrlStart; bool anyLocal; QMenuBar *menuBar; KToolBar *toolBar; KParts::ReadOnlyPart *okularPart; #ifdef HAVE_WEBENGINEWIDGETS QWebEngineView *htmlWidget; #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS QWebView *htmlWidget; #else // HAVE_WEBKITWIDGETS KParts::ReadOnlyPart *htmlPart; #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS int swpMessage, swpOkular, swpHTML; public: - KComboBox *urlComboBox; + QComboBox *urlComboBox; QPushButton *onlyLocalFilesButton; QList<KIO::StatJob *> runningJobs; QSharedPointer<const Entry> entry; QUrl baseUrl; bool anyRemote; KParts::ReadOnlyPart *locatePart(const QString &mimeType, QWidget *parentWidget) { KService::Ptr service = KMimeTypeTrader::self()->preferredService(mimeType, QStringLiteral("KParts/ReadOnlyPart")); if (service) { KParts::ReadOnlyPart *part = service->createInstance<KParts::ReadOnlyPart>(parentWidget, p); connect(part, static_cast<void(KParts::ReadOnlyPart::*)()>(&KParts::ReadOnlyPart::completed), p, &DocumentPreview::loadingFinished); return part; } else return nullptr; } DocumentPreviewPrivate(DocumentPreview *parent) : p(parent), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), anyLocal(false), entry(nullptr), anyRemote(false) { setupGUI(); } /** * Create user interface for this widget. * It consists of some controlling widget on the top, * but the most space is consumed by KPart widgets * inside a QStackedWidget to show the external content * (PDF file, web page, ...). */ void setupGUI() { QVBoxLayout *layout = new QVBoxLayout(p); layout->setMargin(0); /// some widgets on the top to control the view QHBoxLayout *innerLayout = new QHBoxLayout(); layout->addLayout(innerLayout, 0); onlyLocalFilesButton = new QPushButton(QIcon::fromTheme(QStringLiteral("applications-internet")), QString(), p); onlyLocalFilesButton->setToolTip(i18n("Toggle between local files only and all documents including remote ones")); innerLayout->addWidget(onlyLocalFilesButton, 0); onlyLocalFilesButton->setCheckable(true); QSizePolicy sp = onlyLocalFilesButton->sizePolicy(); sp.setVerticalPolicy(QSizePolicy::MinimumExpanding); onlyLocalFilesButton->setSizePolicy(sp); - urlComboBox = new KComboBox(false, p); + urlComboBox = new QComboBox(p); innerLayout->addWidget(urlComboBox, 1); externalViewerButton = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open")), QString(), p); externalViewerButton->setToolTip(i18n("Open in external program")); innerLayout->addWidget(externalViewerButton, 0); sp = externalViewerButton->sizePolicy(); sp.setVerticalPolicy(QSizePolicy::MinimumExpanding); externalViewerButton->setSizePolicy(sp); menuBar = new QMenuBar(p); menuBar->setBackgroundRole(QPalette::Window); menuBar->setVisible(false); layout->addWidget(menuBar, 0); toolBar = new KToolBar(p); toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); toolBar->setBackgroundRole(QPalette::Window); toolBar->setVisible(false); layout->addWidget(toolBar, 0); /// main part of the widget stackedWidget = new QStackedWidget(p); layout->addWidget(stackedWidget, 1); /// default widget if no preview is available message = new ImageLabel(i18n("No preview available"), stackedWidget); message->setAlignment(Qt::AlignCenter); message->setWordWrap(true); swpMessage = stackedWidget->addWidget(message); connect(message, &QLabel::linkActivated, p, &DocumentPreview::linkActivated); /// add parts to stackedWidget okularPart = locatePart(QStringLiteral("application/pdf"), stackedWidget); swpOkular = (okularPart == nullptr) ? -1 : stackedWidget->addWidget(okularPart->widget()); if (okularPart == nullptr || swpOkular < 0) { qCWarning(LOG_KBIBTEX_PROGRAM) << "No 'KDE Framworks 5'-based Okular part for PDF or PostScript document preview available."; } #ifdef HAVE_WEBENGINEWIDGETS qCDebug(LOG_KBIBTEX_PROGRAM) << "WebEngine is available, using it instead of WebKit or HTML KPart (both neither considered nor tested for) for HTML/Web preview."; /// To make DrKonqi handle crashes in Chromium-based QtWebEngine, /// set a certain environment variable. For details, see here: /// https://www.dvratil.cz/2018/10/drkonqi-and-qtwebengine/ /// https://phabricator.kde.org/D16004 const auto chromiumFlags = qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"); if (!chromiumFlags.contains("disable-in-process-stack-traces")) { qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags + " --disable-in-process-stack-traces"); } htmlWidget = new QWebEngineView(stackedWidget); swpHTML = stackedWidget->addWidget(htmlWidget); connect(htmlWidget, &QWebEngineView::loadFinished, p, &DocumentPreview::loadingFinished); #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS qCDebug(LOG_KBIBTEX_PROGRAM) << "WebKit is available, using it instead of WebEngine (missing) or HTML KPart (not considered) for HTML/Web preview."; htmlWidget = new QWebView(stackedWidget); swpHTML = stackedWidget->addWidget(htmlWidget); connect(htmlWidget, &QWebView::loadFinished, p, &DocumentPreview::loadingFinished); #else // HAVE_WEBKITWIDGETS htmlPart = locatePart(QStringLiteral("text/html"), stackedWidget); if (htmlPart != nullptr) { qCDebug(LOG_KBIBTEX_PROGRAM) << "HTML KPart is available, using it instead of WebEngine or WebKit (neither available) for HTML/Web preview."; swpHTML = stackedWidget->addWidget(htmlPart->widget()); } else { qCDebug(LOG_KBIBTEX_PROGRAM) << "No HTML viewing component is available, disabling HTML/Web preview."; swpHTML = -1; } #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS loadState(); connect(externalViewerButton, &QPushButton::clicked, p, &DocumentPreview::openExternally); connect(urlComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), p, &DocumentPreview::comboBoxChanged); connect(onlyLocalFilesButton, &QPushButton::toggled, p, &DocumentPreview::onlyLocalFilesChanged); } bool addUrl(const struct UrlInfo &urlInfo) { bool isLocal = KBibTeX::isLocalOrRelative(urlInfo.url); anyLocal |= isLocal; if (!onlyLocalFilesButton->isChecked() && !isLocal) return true; ///< ignore URL if only local files are allowed if (isLocal) { /// create a drop-down list entry if file is a local file /// (based on patch by Luis Silva) QString fn = urlInfo.url.fileName(); QString full = urlInfo.url.url(QUrl::PreferLocalFile); QString dir = urlInfo.url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); QString text = fn.isEmpty() ? full : (dir.isEmpty() ? fn : QString(QStringLiteral("%1 [%2]")).arg(fn, dir)); urlComboBox->addItem(urlInfo.icon, text); } else { /// create a drop-down list entry if file is a remote file urlComboBox->addItem(urlInfo.icon, urlInfo.url.toDisplayString()); } urlComboBox->setEnabled(true); cbxEntryToUrlInfo.insert(urlComboBox->count() - 1, urlInfo); externalViewerButton->setEnabled(true); if (urlComboBox->count() == 1 || ///< first entry in combobox isLocal || ///< local files always preferred over URLs /// prefer arXiv summary URLs over other URLs (!anyLocal && urlInfo.url.host().contains(QStringLiteral("arxiv.org/abs")))) { showUrl(urlInfo); } return true; } void update() { p->setCursor(Qt::WaitCursor); /// reset and clear all controls if (swpOkular >= 0 && okularPart != nullptr) okularPart->closeUrl(); #ifdef HAVE_WEBENGINEWIDGETS htmlWidget->stop(); #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS htmlWidget->stop(); #else // HAVE_WEBKITWIDGETS if (swpHTML >= 0 && htmlPart != nullptr) htmlPart->closeUrl(); #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS urlComboBox->setEnabled(false); urlComboBox->clear(); cbxEntryToUrlInfo.clear(); externalViewerButton->setEnabled(false); showMessage(i18n("Refreshing...")); // krazy:exclude=qmethods /// cancel/kill all running jobs auto it = runningJobs.begin(); while (it != runningJobs.end()) { (*it)->kill(); it = runningJobs.erase(it); } /// clear flag that memorizes if any local file was referenced anyLocal = false; anyRemote = false; /// do not load external reference if widget is hidden if (isVisible()) { const auto urlList = FileInfo::entryUrls(entry, baseUrl, FileInfo::TestExistenceYes); for (const QUrl &url : urlList) { bool isLocal = KBibTeX::isLocalOrRelative(url); anyRemote |= !isLocal; if (!onlyLocalFilesButton->isChecked() && !isLocal) continue; KIO::StatJob *job = KIO::stat(url, KIO::StatJob::SourceSide, 3, KIO::HideProgressInfo); runningJobs << job; KJobWidgets::setWindow(job, p); connect(job, &KIO::StatJob::result, p, &DocumentPreview::statFinished); } if (urlList.isEmpty()) { /// Case no URLs associated with this entry. /// For-loop above was never executed. showMessage(i18n("No documents to show.")); // krazy:exclude=qmethods p->setCursor(Qt::ArrowCursor); } else if (runningJobs.isEmpty()) { /// Case no stat jobs are running. As there were URLs (tested in /// previous condition), this implies that there were remote /// references that were ignored by executing "continue" above. /// Give user hint that by enabling remote files, more can be shown. showMessage(i18n("<qt>No documents to show.<br/><a href=\"disableonlylocalfiles\">Disable the restriction</a> to local files to see remote documents.</qt>")); // krazy:exclude=qmethods p->setCursor(Qt::ArrowCursor); } } else p->setCursor(Qt::ArrowCursor); } void showMessage(const QString &msgText) { stackedWidget->setCurrentIndex(swpMessage); message->setPixmap(QPixmap()); message->setText(msgText); if (swpOkular >= 0) stackedWidget->widget(swpOkular)->setEnabled(false); if (swpHTML >= 0) stackedWidget->widget(swpHTML)->setEnabled(false); menuBar->setVisible(false); toolBar->setVisible(true); menuBar->clear(); toolBar->clear(); } void setupToolMenuBarForPart(const KParts::ReadOnlyPart *part) { /* KAction *printAction = KStandardAction::print(part, SLOT(slotPrint()), part->actionCollection()); printAction->setEnabled(false); connect(part, SIGNAL(enablePrintAction(bool)), printAction, SLOT(setEnabled(bool))); */ QDomDocument doc = part->domDocument(); QDomElement docElem = doc.documentElement(); QDomNodeList toolbarNodes = docElem.elementsByTagName(QStringLiteral("ToolBar")); for (int i = 0; i < toolbarNodes.count(); ++i) { QDomNodeList toolbarItems = toolbarNodes.at(i).childNodes(); for (int j = 0; j < toolbarItems.count(); ++j) { QDomNode toolbarItem = toolbarItems.at(j); if (toolbarItem.nodeName() == QStringLiteral("Action")) { QString actionName = toolbarItem.attributes().namedItem(QStringLiteral("name")).nodeValue(); toolBar->addAction(part->actionCollection()->action(actionName)); } else if (toolbarItem.nodeName() == QStringLiteral("Separator")) { toolBar->addSeparator(); } } } QDomNodeList menubarNodes = docElem.elementsByTagName(QStringLiteral("MenuBar")); for (int i = 0; i < menubarNodes.count(); ++i) { QDomNodeList menubarNode = menubarNodes.at(i).childNodes(); for (int j = 0; j < menubarNode.count(); ++j) { QDomNode menubarItem = menubarNode.at(j); if (menubarItem.nodeName() == QStringLiteral("Menu")) { QDomNodeList menuNode = menubarItem.childNodes(); QString text; for (int k = 0; k < menuNode.count(); ++k) { QDomNode menuItem = menuNode.at(k); if (menuItem.nodeName() == QStringLiteral("text")) { text = menuItem.firstChild().toText().data(); break; } } QMenu *menu = menuBar->addMenu(text); for (int k = 0; k < menuNode.count(); ++k) { QDomNode menuItem = menuNode.at(k); if (menuItem.nodeName() == QStringLiteral("Action")) { QString actionName = menuItem.attributes().namedItem(QStringLiteral("name")).nodeValue(); menu->addAction(part->actionCollection()->action(actionName)); } else if (menuItem.nodeName() == QStringLiteral("Separator")) { menu->addSeparator(); } } } } } QDomNodeList actionPropertiesList = docElem.elementsByTagName(QStringLiteral("ActionProperties")); for (int i = 0; i < actionPropertiesList.count(); ++i) { QDomNodeList actionProperties = actionPropertiesList.at(i).childNodes(); for (int j = 0; j < actionProperties.count(); ++j) { QDomNode actionNode = actionProperties.at(j); if (actionNode.nodeName() == QStringLiteral("Action")) { const QString actionName = actionNode.attributes().namedItem(QStringLiteral("name")).toAttr().nodeValue(); const QString actionShortcut = actionNode.attributes().namedItem(QStringLiteral("shortcut")).toAttr().value(); QAction *action = part->actionCollection()->action(actionName); if (action != nullptr) { action->setShortcut(QKeySequence(actionShortcut)); } } } } menuBar->setVisible(true); toolBar->setVisible(true); } void showPart(const KParts::ReadOnlyPart *part, QWidget *widget) { menuBar->setVisible(false); toolBar->setVisible(false); menuBar->clear(); toolBar->clear(); if (okularPart != nullptr && part == okularPart && swpOkular >= 0) { stackedWidget->setCurrentIndex(swpOkular); stackedWidget->widget(swpOkular)->setEnabled(true); setupToolMenuBarForPart(okularPart); #ifdef HAVE_WEBENGINEWIDGETS } else if (widget == htmlWidget) { stackedWidget->setCurrentIndex(swpHTML); stackedWidget->widget(swpHTML)->setEnabled(true); #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS } else if (widget == htmlWidget) { stackedWidget->setCurrentIndex(swpHTML); stackedWidget->widget(swpHTML)->setEnabled(true); #else // HAVE_WEBKITWIDGETS } else if (htmlPart != nullptr && part == htmlPart && swpHTML >= 0) { stackedWidget->setCurrentIndex(swpHTML); stackedWidget->widget(swpHTML)->setEnabled(true); setupToolMenuBarForPart(htmlPart); #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS } else if (widget == message) { stackedWidget->setCurrentIndex(swpMessage); } else showMessage(i18n("Cannot show requested part")); // krazy:exclude=qmethods } bool showUrl(const struct UrlInfo &urlInfo) { static const QStringList okularMimetypes {QStringLiteral("application/x-pdf"), QStringLiteral("application/pdf"), QStringLiteral("application/x-gzpdf"), QStringLiteral("application/x-bzpdf"), QStringLiteral("application/x-wwf"), QStringLiteral("image/vnd.djvu"), QStringLiteral("image/vnd.djvu+multipage"), QStringLiteral("application/postscript"), QStringLiteral("image/x-eps"), QStringLiteral("application/x-gzpostscript"), QStringLiteral("application/x-bzpostscript"), QStringLiteral("image/x-gzeps"), QStringLiteral("image/x-bzeps")}; static const QStringList htmlMimetypes {QStringLiteral("text/html"), QStringLiteral("application/xml"), QStringLiteral("application/xhtml+xml")}; static const QStringList imageMimetypes {QStringLiteral("image/jpeg"), QStringLiteral("image/png"), QStringLiteral("image/gif"), QStringLiteral("image/tiff")}; if (swpHTML >= 0) stackedWidget->widget(swpHTML)->setEnabled(false); if (swpOkular >= 0 && okularPart != nullptr) { stackedWidget->widget(swpOkular)->setEnabled(false); okularPart->closeUrl(); } #ifdef HAVE_WEBENGINEWIDGETS htmlWidget->stop(); #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS htmlWidget->stop(); #else // HAVE_WEBKITWIDGETS if (swpHTML >= 0 && htmlPart != nullptr) htmlPart->closeUrl(); #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS if (swpOkular >= 0 && okularPart != nullptr && okularMimetypes.contains(urlInfo.mimeType)) { p->setCursor(Qt::BusyCursor); showMessage(i18n("Loading...")); // krazy:exclude=qmethods return okularPart->openUrl(urlInfo.url); } else if (htmlMimetypes.contains(urlInfo.mimeType)) { p->setCursor(Qt::BusyCursor); showMessage(i18n("Loading...")); // krazy:exclude=qmethods #ifdef HAVE_WEBENGINEWIDGETS htmlWidget->load(urlInfo.url); return true; #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS htmlWidget->load(urlInfo.url); return true; #else // HAVE_WEBKITWIDGETS return (swpHTML >= 0 && htmlPart != nullptr) ? htmlPart->openUrl(urlInfo.url) : false; #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS } else if (imageMimetypes.contains(urlInfo.mimeType)) { p->setCursor(Qt::BusyCursor); message->setPixmap(QPixmap(urlInfo.url.url(QUrl::PreferLocalFile))); showPart(nullptr, message); p->unsetCursor(); return true; } else { QString additionalInformation; if (urlInfo.mimeType == QStringLiteral("application/pdf")) additionalInformation = i18nc("Additional information in case there is not KPart available for mime type 'application/pdf'", "<br/><br/>Please install <a href=\"https://userbase.kde.org/Okular\">Okular</a> for KDE Frameworks&nbsp;5 to make use of its PDF viewing component.<br/>Okular for KDE&nbsp;4 will not work."); showMessage(i18nc("First parameter is mime type, second parameter is optional information (may be empty)", "<qt>Don't know how to show mimetype '%1'.%2</qt>", urlInfo.mimeType, additionalInformation)); // krazy:exclude=qmethods } return false; } void openExternally() { QUrl url(cbxEntryToUrlInfo[urlComboBox->currentIndex()].url); /// Guess mime type for url to open QMimeType mimeType = FileInfo::mimeTypeForUrl(url); const QString mimeTypeName = mimeType.name(); /// Ask KDE subsystem to open url in viewer matching mime type #if KIO_VERSION < 0x051f00 // < 5.31.0 KRun::runUrl(url, mimeTypeName, p, false, false); #else // KIO_VERSION < 0x051f00 // >= 5.31.0 KRun::runUrl(url, mimeTypeName, p, KRun::RunFlags()); #endif // KIO_VERSION < 0x051f00 } UrlInfo urlMetaInfo(const QUrl &url) { UrlInfo result; result.url = url; if (!KBibTeX::isLocalOrRelative(url) && url.fileName().isEmpty()) { /// URLs not pointing to a specific file should be opened with a web browser component result.icon = QIcon::fromTheme(QStringLiteral("text-html")); result.mimeType = QStringLiteral("text/html"); return result; } QMimeType mimeType = FileInfo::mimeTypeForUrl(url); // FIXME accuracy, necessary: /* if (accuracy < 50) { QMimeDatabase db; mimeType = db.mimeTypeForFile(url.fileName()); } */ result.mimeType = mimeType.name(); result.icon = QIcon::fromTheme(mimeType.iconName()); if (result.mimeType == QStringLiteral("application/octet-stream")) { /// application/octet-stream is a fall-back if KDE did not know better result.icon = QIcon::fromTheme(QStringLiteral("text-html")); result.mimeType = QStringLiteral("text/html"); } else if ((result.mimeType.isEmpty() || result.mimeType == QStringLiteral("inode/directory")) && (result.url.scheme() == QStringLiteral("http") || result.url.scheme() == QStringLiteral("https"))) { /// directory via http means normal webpage (not browsable directory) result.icon = QIcon::fromTheme(QStringLiteral("text-html")); result.mimeType = QStringLiteral("text/html"); } if (url.url(QUrl::PreferLocalFile).startsWith(arXivPDFUrlStart)) { result.icon = QIcon::fromTheme(QStringLiteral("application-pdf")); result.mimeType = QStringLiteral("application/pdf"); } return result; } void comboBoxChanged(int index) { showUrl(cbxEntryToUrlInfo[index]); } bool isVisible() { /// get dock where this widget is inside /// static cast is save as constructor requires parent to be QDockWidget QDockWidget *pp = static_cast<QDockWidget *>(p->parent()); return pp != nullptr && !pp->isHidden(); } void loadState() { KConfigGroup configGroup(config, configGroupName); onlyLocalFilesButton->setChecked(!configGroup.readEntry(onlyLocalFilesCheckConfig, true)); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(onlyLocalFilesCheckConfig, !onlyLocalFilesButton->isChecked()); config->sync(); } }; const QString DocumentPreview::DocumentPreviewPrivate::arXivPDFUrlStart = QStringLiteral("http://arxiv.org/pdf/"); const QString DocumentPreview::DocumentPreviewPrivate::configGroupName = QStringLiteral("URL Preview"); const QString DocumentPreview::DocumentPreviewPrivate::onlyLocalFilesCheckConfig = QStringLiteral("OnlyLocalFiles"); DocumentPreview::DocumentPreview(QDockWidget *parent) : QWidget(parent), d(new DocumentPreviewPrivate(this)) { connect(parent, &QDockWidget::visibilityChanged, this, &DocumentPreview::visibilityChanged); } DocumentPreview::~DocumentPreview() { delete d; } void DocumentPreview::setElement(QSharedPointer<Element> element, const File *) { d->entry = element.dynamicCast<const Entry>(); d->update(); } void DocumentPreview::openExternally() { d->openExternally(); } void DocumentPreview::setBibTeXUrl(const QUrl &url) { d->baseUrl = url; } void DocumentPreview::onlyLocalFilesChanged() { d->saveState(); d->update(); } void DocumentPreview::visibilityChanged(bool) { d->update(); } void DocumentPreview::comboBoxChanged(int index) { d->comboBoxChanged(index); } void DocumentPreview::statFinished(KJob *kjob) { KIO::StatJob *job = static_cast<KIO::StatJob *>(kjob); d->runningJobs.removeOne(job); if (!job->error()) { const QUrl url = job->mostLocalUrl(); DocumentPreviewPrivate::UrlInfo urlInfo = d->urlMetaInfo(url); setCursor(d->runningJobs.isEmpty() ? Qt::ArrowCursor : Qt::BusyCursor); d->addUrl(urlInfo); } else { qCWarning(LOG_KBIBTEX_PROGRAM) << job->error() << job->errorString(); } if (d->runningJobs.isEmpty()) { /// If this was the last background stat job ... setCursor(Qt::ArrowCursor); if (d->urlComboBox->count() < 1) { /// In case that no valid references were found by the stat jobs ... if (d->anyRemote && !d->onlyLocalFilesButton->isChecked()) { /// There are some remote URLs to probe, /// but user was only looking for local files d->showMessage(i18n("<qt>No documents to show.<br/><a href=\"disableonlylocalfiles\">Disable the restriction</a> to local files to see remote documents.</qt>")); // krazy:exclude=qmethods } else { /// No stat job at all succeeded. Show message to user. d->showMessage(i18n("No documents to show.\nSome URLs or files could not be retrieved.")); // krazy:exclude=qmethods } } } } void DocumentPreview::loadingFinished() { setCursor(Qt::ArrowCursor); d->showPart(qobject_cast<KParts::ReadOnlyPart *>(sender()), qobject_cast<QWidget *>(sender())); } void DocumentPreview::linkActivated(const QString &link) { if (link == QStringLiteral("disableonlylocalfiles")) d->onlyLocalFilesButton->setChecked(true); else if (link.startsWith(QStringLiteral("http://")) || link.startsWith(QStringLiteral("https://"))) { const QUrl urlToOpen = QUrl::fromUserInput(link); if (urlToOpen.isValid()) { /// Guess mime type for url to open QMimeType mimeType = FileInfo::mimeTypeForUrl(urlToOpen); const QString mimeTypeName = mimeType.name(); /// Ask KDE subsystem to open url in viewer matching mime type #if KIO_VERSION < 0x051f00 // < 5.31.0 KRun::runUrl(urlToOpen, mimeTypeName, this, false, false); #else // KIO_VERSION < 0x051f00 // >= 5.31.0 KRun::runUrl(urlToOpen, mimeTypeName, this, KRun::RunFlags()); #endif // KIO_VERSION < 0x051f00 } } } diff --git a/src/program/docklets/filesettings.cpp b/src/program/docklets/filesettings.cpp index f594f92c..a398a610 100644 --- a/src/program/docklets/filesettings.cpp +++ b/src/program/docklets/filesettings.cpp @@ -1,77 +1,77 @@ /***************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * *****************************************************************************/ #include "filesettings.h" #include <QFormLayout> #include <QCheckBox> +#include <QComboBox> -#include <KComboBox> #include <KLocalizedString> #include "preferences.h" #include "guihelper.h" #include "italictextitemmodel.h" #include "fileview.h" #include "models/filemodel.h" #include "value.h" #include "file.h" FileSettings::FileSettings(QWidget *parent) : FileSettingsWidget(parent), m_fileView(nullptr) { setEnabled(false); connect(this, &FileSettings::widgetsChanged, this, &FileSettings::widgetsChangedSlot); connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::currentChanged, this, &FileSettings::currentFileChangedSlot); /// Monitoring file flag changes to get notified of /// "Save As" operations where the file settings /// may get changed (requires a reload of properties) connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::flagsChanged, this, &FileSettings::flagsChangedSlot); } void FileSettings::setFileView(FileView *fileView) { m_fileView = fileView; currentFileChangedSlot(); } void FileSettings::widgetsChangedSlot() { File *file = m_fileView != nullptr && m_fileView->fileModel() != nullptr ? m_fileView->fileModel()->bibliographyFile() : nullptr; if (file != nullptr) { saveProperties(file); /// Notify main view about change it its data m_fileView->externalModification(); } } void FileSettings::currentFileChangedSlot() { File *file = m_fileView != nullptr && m_fileView->fileModel() != nullptr ? m_fileView->fileModel()->bibliographyFile() : nullptr; loadProperties(file); setEnabled(file != nullptr); } void FileSettings::flagsChangedSlot(const OpenFileInfo::StatusFlags statusFlags) { if (statusFlags.testFlag(OpenFileInfo::Open)) { File *file = m_fileView != nullptr && m_fileView->fileModel() != nullptr ? m_fileView->fileModel()->bibliographyFile() : nullptr; loadProperties(file); } } diff --git a/src/program/docklets/referencepreview.cpp b/src/program/docklets/referencepreview.cpp index 33282925..81b99111 100644 --- a/src/program/docklets/referencepreview.cpp +++ b/src/program/docklets/referencepreview.cpp @@ -1,419 +1,419 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * and contributors * * * * Contributions to this file were made by * * - Jurgen Spitzmuller <juergen@spitzmueller.org> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "referencepreview.h" #include <QFrame> #include <QBuffer> #include <QTextDocument> #include <QLayout> #include <QApplication> #include <QTextStream> #include <QTemporaryFile> #include <QPalette> #include <QMimeType> #include <QDebug> #include <QFileDialog> #include <QPushButton> #include <QFontDatabase> +#include <QComboBox> #include <KLocalizedString> -#include <KComboBox> #include <KRun> #include <KIO/CopyJob> #include <KJobWidgets> #include <KSharedConfig> #include <KConfigGroup> #include <KTextEdit> #include <kio_version.h> #include "xsltransform.h" #include "fileexporterbibtex.h" #include "fileexporterbibtex2html.h" #include "fileexporterris.h" #include "fileexporterxslt.h" #include "element.h" #include "file.h" #include "entry.h" #include "fileview.h" #include "logging_program.h" static const struct PreviewStyles { QString label, style, type; } previewStyles[] = { {i18n("Source (BibTeX)"), QStringLiteral("bibtex"), QStringLiteral("exporter")}, {i18n("Source (RIS)"), QStringLiteral("ris"), QStringLiteral("exporter")}, {QStringLiteral("abbrv"), QStringLiteral("abbrv"), QStringLiteral("bibtex2html")}, {QStringLiteral("acm"), QStringLiteral("acm"), QStringLiteral("bibtex2html")}, {QStringLiteral("alpha"), QStringLiteral("alpha"), QStringLiteral("bibtex2html")}, {QStringLiteral("apalike"), QStringLiteral("apalike"), QStringLiteral("bibtex2html")}, {QStringLiteral("ieeetr"), QStringLiteral("ieeetr"), QStringLiteral("bibtex2html")}, {QStringLiteral("plain"), QStringLiteral("plain"), QStringLiteral("bibtex2html")}, {QStringLiteral("siam"), QStringLiteral("siam"), QStringLiteral("bibtex2html")}, {QStringLiteral("unsrt"), QStringLiteral("unsrt"), QStringLiteral("bibtex2html")}, {i18n("Standard"), QStringLiteral("standard"), QStringLiteral("xml")}, {i18n("Fancy"), QStringLiteral("fancy"), QStringLiteral("xml")}, {i18n("Wikipedia Citation"), QStringLiteral("wikipedia-cite"), QStringLiteral("plain_xml")}, {i18n("Abstract-only"), QStringLiteral("abstractonly"), QStringLiteral("xml")} }; Q_DECLARE_METATYPE(PreviewStyles) class ReferencePreview::ReferencePreviewPrivate { private: ReferencePreview *p; public: KSharedConfigPtr config; const QString configGroupName; const QString configKeyName; QPushButton *buttonOpen, *buttonSaveAsHTML; QString htmlText; QUrl baseUrl; QTextDocument *htmlDocument; KTextEdit *htmlView; - KComboBox *comboBox; + QComboBox *comboBox; QSharedPointer<const Element> element; const File *file; FileView *fileView; const QColor textColor; const int defaultFontSize; const QString htmlStart; const QString notAvailableMessage; ReferencePreviewPrivate(ReferencePreview *parent) : p(parent), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), configGroupName(QStringLiteral("Reference Preview Docklet")), configKeyName(QStringLiteral("Style")), file(nullptr), fileView(nullptr), textColor(QApplication::palette().text().color()), defaultFontSize(QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize()), htmlStart(QStringLiteral("<html>\n<head>\n<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n<style type=\"text/css\">\npre {\n white-space: pre-wrap;\n white-space: -moz-pre-wrap;\n white-space: -pre-wrap;\n white-space: -o-pre-wrap;\n word-wrap: break-word;\n}\n</style>\n</head>\n<body style=\"color: ") + textColor.name() + QStringLiteral("; font-size: ") + QString::number(defaultFontSize) + QStringLiteral("pt; font-family: '") + QFontDatabase::systemFont(QFontDatabase::GeneralFont).family() + QStringLiteral("'; background-color: '") + QApplication::palette().base().color().name(QColor::HexRgb) + QStringLiteral("'\">")), notAvailableMessage(htmlStart + QStringLiteral("<p style=\"font-style: italic;\">") + i18n("No preview available") + QStringLiteral("</p><p style=\"font-size: 90%;\">") + i18n("Reason:") + QStringLiteral(" %1</p></body></html>")) { QGridLayout *gridLayout = new QGridLayout(p); gridLayout->setMargin(0); gridLayout->setColumnStretch(0, 1); gridLayout->setColumnStretch(1, 0); gridLayout->setColumnStretch(2, 0); - comboBox = new KComboBox(p); + comboBox = new QComboBox(p); gridLayout->addWidget(comboBox, 0, 0, 1, 3); QFrame *frame = new QFrame(p); gridLayout->addWidget(frame, 1, 0, 1, 3); frame->setFrameShadow(QFrame::Sunken); frame->setFrameShape(QFrame::StyledPanel); QVBoxLayout *layout = new QVBoxLayout(frame); layout->setMargin(0); htmlView = new KTextEdit(frame); htmlView->setReadOnly(true); htmlDocument = new QTextDocument(htmlView); htmlView->setDocument(htmlDocument); layout->addWidget(htmlView); buttonOpen = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open"), p); buttonOpen->setToolTip(i18n("Open reference in web browser.")); gridLayout->addWidget(buttonOpen, 2, 1, 1, 1); buttonSaveAsHTML = new QPushButton(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as HTML"), p); buttonSaveAsHTML->setToolTip(i18n("Save reference as HTML fragment.")); gridLayout->addWidget(buttonSaveAsHTML, 2, 2, 1, 1); } bool saveHTML(const QUrl &url) const { QTemporaryFile tempFile; tempFile.setAutoRemove(true); bool result = saveHTML(tempFile); if (result) { KIO::CopyJob *copyJob = KIO::copy(QUrl::fromLocalFile(tempFile.fileName()), url, KIO::Overwrite); KJobWidgets::setWindow(copyJob, p); result = copyJob->exec(); } return result; } bool saveHTML(QTemporaryFile &tempFile) const { if (tempFile.open()) { QTextStream ts(&tempFile); ts.setCodec("utf-8"); static const QRegularExpression kbibtexHrefRegExp(QStringLiteral("<a[^>]+href=\"kbibtex:[^>]+>(.+?)</a>")); QString modifiedHtmlText = htmlText; modifiedHtmlText = modifiedHtmlText.replace(kbibtexHrefRegExp, QStringLiteral("\\1")); ts << modifiedHtmlText; tempFile.close(); return true; } return false; } void loadState() { static bool hasBibTeX2HTML = !QStandardPaths::findExecutable(QStringLiteral("bibtex2html")).isEmpty(); KConfigGroup configGroup(config, configGroupName); const QString previousStyle = configGroup.readEntry(configKeyName, QString()); comboBox->clear(); int styleIndex = 0, c = 0; for (const PreviewStyles &previewStyle : previewStyles) { if (!hasBibTeX2HTML && previewStyle.type.contains(QStringLiteral("bibtex2html"))) continue; comboBox->addItem(previewStyle.label, QVariant::fromValue(previewStyle)); if (previousStyle == previewStyle.style) styleIndex = c; ++c; } comboBox->setCurrentIndex(styleIndex); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(configKeyName, comboBox->itemData(comboBox->currentIndex()).value<PreviewStyles>().style); config->sync(); } }; ReferencePreview::ReferencePreview(QWidget *parent) : QWidget(parent), d(new ReferencePreviewPrivate(this)) { d->loadState(); connect(d->buttonOpen, &QPushButton::clicked, this, &ReferencePreview::openAsHTML); connect(d->buttonSaveAsHTML, &QPushButton::clicked, this, &ReferencePreview::saveAsHTML); connect(d->comboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ReferencePreview::renderHTML); setEnabled(false); } ReferencePreview::~ReferencePreview() { delete d; } void ReferencePreview::setHtml(const QString &html, bool buttonsEnabled) { d->htmlText = QString(html).remove(QStringLiteral("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")); d->htmlDocument->setHtml(d->htmlText); d->buttonOpen->setEnabled(buttonsEnabled); d->buttonSaveAsHTML->setEnabled(buttonsEnabled); } void ReferencePreview::setEnabled(bool enabled) { if (enabled) setHtml(d->htmlText, true); else setHtml(d->notAvailableMessage.arg(i18n("Preview disabled")), false); d->htmlView->setEnabled(enabled); d->comboBox->setEnabled(enabled); } void ReferencePreview::setElement(QSharedPointer<Element> element, const File *file) { d->element = element; d->file = file; renderHTML(); } void ReferencePreview::renderHTML() { enum { ignore, /// do not include crossref'ed entry's values (one entry) /// NOT USED: add, /// feed both the current entry as well as the crossref'ed entry into the exporter (two entries) merge /// merge the crossref'ed entry's values into the current entry (one entry) } crossRefHandling = ignore; if (d->element.isNull()) { setHtml(d->notAvailableMessage.arg(i18n("No element selected")), false); return; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); FileExporter *exporter = nullptr; const PreviewStyles previewStyle = d->comboBox->itemData(d->comboBox->currentIndex()).value<PreviewStyles>(); if (previewStyle.type == QStringLiteral("exporter")) { if (previewStyle.style == QStringLiteral("bibtex")) { FileExporterBibTeX *exporterBibTeX = new FileExporterBibTeX(this); exporterBibTeX->setEncoding(QStringLiteral("utf-8")); exporter = exporterBibTeX; } else if (previewStyle.style == QStringLiteral("ris")) exporter = new FileExporterRIS(this); else qCWarning(LOG_KBIBTEX_PROGRAM) << "Don't know how to handle output style " << previewStyle.style << " for type " << previewStyle.type; } else if (previewStyle.type == QStringLiteral("bibtex2html")) { crossRefHandling = merge; FileExporterBibTeX2HTML *exporterHTML = new FileExporterBibTeX2HTML(this); exporterHTML->setLaTeXBibliographyStyle(previewStyle.style); exporter = exporterHTML; } else if (previewStyle.type == QStringLiteral("xml") || previewStyle.type.endsWith(QStringLiteral("_xml"))) { crossRefHandling = merge; const QString filename = previewStyle.style + QStringLiteral(".xsl"); exporter = new FileExporterXSLT(XSLTransform::locateXSLTfile(filename), this); } else qCWarning(LOG_KBIBTEX_PROGRAM) << "Don't know how to handle output type " << previewStyle.type; if (exporter != nullptr) { QBuffer buffer(this); buffer.open(QBuffer::WriteOnly); bool exporterResult = false; QStringList errorLog; QSharedPointer<const Entry> entry = d->element.dynamicCast<const Entry>(); /** NOT USED if (crossRefHandling == add && !entry.isNull()) { QString crossRef = PlainTextValue::text(entry->value(QStringLiteral("crossref"))); QSharedPointer<const Entry> crossRefEntry = d->file == NULL ? QSharedPointer<const Entry>() : d->file->containsKey(crossRef) .dynamicCast<const Entry>(); if (!crossRefEntry.isNull()) { File file; file.append(QSharedPointer<Entry>(new Entry(*entry))); file.append(QSharedPointer<Entry>(new Entry(*crossRefEntry))); exporterResult = exporter->save(&buffer, &file, &errorLog); } else exporterResult = exporter->save(&buffer, d->element, d->file, &errorLog); } else */ if (crossRefHandling == merge && !entry.isNull()) { QSharedPointer<Entry> merged = QSharedPointer<Entry>(entry->resolveCrossref(d->file)); exporterResult = exporter->save(&buffer, merged, d->file, &errorLog); } else exporterResult = exporter->save(&buffer, d->element, d->file, &errorLog); buffer.close(); delete exporter; buffer.open(QBuffer::ReadOnly); QString text = QString::fromUtf8(buffer.readAll().constData()); buffer.close(); bool buttonsEnabled = true; if (!exporterResult || text.isEmpty()) { /// something went wrong, no output ... text = d->notAvailableMessage.arg(i18n("No output generated")); buttonsEnabled = false; qCDebug(LOG_KBIBTEX_PROGRAM) << errorLog.join(QStringLiteral("\n")); } else { /// beautify text text.replace(QStringLiteral("``"), QStringLiteral("&ldquo;")); text.replace(QStringLiteral("''"), QStringLiteral("&rdquo;")); static const QRegularExpression openingSingleQuotationRegExp(QStringLiteral("(^|[> ,.;:!?])`(\\S)")); static const QRegularExpression closingSingleQuotationRegExp(QStringLiteral("(\\S)'([ ,.;:!?<]|$)")); text.replace(openingSingleQuotationRegExp, QStringLiteral("\\1&lsquo;\\2")); text.replace(closingSingleQuotationRegExp, QStringLiteral("\\1&rsquo;\\2")); if (previewStyle.style == QStringLiteral("wikipedia-cite")) text.remove(QStringLiteral("\n")); if (text.contains(QStringLiteral("{{cite FIXME"))) { /// Wikipedia {{cite ...}} command had problems (e.g. unknown entry type) text = d->notAvailableMessage.arg(i18n("This type of element is not supported by Wikipedia's <tt>{{cite}}</tt> command.")); } else if (previewStyle.type == QStringLiteral("exporter") || previewStyle.type.startsWith(QStringLiteral("plain_"))) { /// source text.prepend(QStringLiteral("';\">")); text.prepend(QFontDatabase::systemFont(QFontDatabase::FixedFont).family()); text.prepend(QStringLiteral("<pre style=\"font-family: '")); text.prepend(d->htmlStart); text.append(QStringLiteral("</pre></body></html>")); } else if (previewStyle.type == QStringLiteral("bibtex2html")) { /// bibtex2html /// remove "generated by" line from HTML code if BibTeX2HTML was used text.remove(QRegularExpression(QStringLiteral("<hr><p><em>.*</p>"))); text.remove(QRegularExpression(QStringLiteral("<[/]?(font)[^>]*>"))); text.remove(QRegularExpression(QStringLiteral("^.*?<td.*?</td.*?<td>"))); text.remove(QRegularExpression(QStringLiteral("</td>.*$"))); text.remove(QRegularExpression(QStringLiteral("\\[ <a.*?</a> \\]"))); /// replace ASCII art with Unicode characters text.replace(QStringLiteral("---"), QString(QChar(0x2014))); text.replace(QStringLiteral("--"), QString(QChar(0x2013))); text.prepend(d->htmlStart); text.append("</body></html>"); } else if (previewStyle.type == QStringLiteral("xml")) { /// XML/XSLT text.prepend(d->htmlStart); text.append("</body></html>"); } /// adopt current color scheme text.replace(QStringLiteral("color: black;"), QString(QStringLiteral("color: %1;")).arg(d->textColor.name())); } setHtml(text, buttonsEnabled); d->saveState(); } else { /// something went wrong, no exporter ... setHtml(d->notAvailableMessage.arg(i18n("No output generated")), false); } QApplication::restoreOverrideCursor(); } void ReferencePreview::openAsHTML() { QTemporaryFile file(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QDir::separator() + QStringLiteral("referencePreview-openAsHTML-XXXXXX.html")); file.setAutoRemove(false); /// let file stay alive for browser d->saveHTML(file); /// Ask KDE subsystem to open url in viewer matching mime type QUrl url(file.fileName()); #if KIO_VERSION < 0x051f00 // < 5.31.0 KRun::runUrl(url, QStringLiteral("text/html"), this, false, false); #else // KIO_VERSION < 0x051f00 // >= 5.31.0 KRun::runUrl(url, QStringLiteral("text/html"), this, KRun::RunFlags()); #endif // KIO_VERSION < 0x051f00 } void ReferencePreview::saveAsHTML() { QUrl url = QFileDialog::getSaveFileUrl(this, i18n("Save as HTML"), QUrl(), QStringLiteral("text/html")); if (url.isValid()) d->saveHTML(url); } void ReferencePreview::linkClicked(const QUrl &url) { QString text = url.toDisplayString(); if (text.startsWith(QStringLiteral("kbibtex:filter:"))) { text = text.mid(15); if (d->fileView != nullptr) { int p = text.indexOf(QStringLiteral("=")); SortFilterFileModel::FilterQuery fq; fq.terms << text.mid(p + 1); fq.combination = SortFilterFileModel::EveryTerm; fq.field = text.left(p); fq.searchPDFfiles = false; d->fileView->setFilterBarFilter(fq); } } } void ReferencePreview::setFileView(FileView *fileView) { d->fileView = fileView; } diff --git a/src/program/docklets/searchform.cpp b/src/program/docklets/searchform.cpp index c8b4f935..0ea574ba 100644 --- a/src/program/docklets/searchform.cpp +++ b/src/program/docklets/searchform.cpp @@ -1,515 +1,515 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "searchform.h" #include <QLayout> #include <QMap> #include <QLabel> #include <QListWidget> +#include <QLineEdit> #include <QSpinBox> #include <QStackedWidget> #include <QTabWidget> #include <QProgressBar> #include <QMimeDatabase> #include <QMimeType> #include <QTimer> #include <QSet> #include <QAction> #include <QScrollArea> #include <QIcon> #include <QPushButton> #include <QDebug> -#include <KLineEdit> #include <KLocalizedString> #include <KRun> #include <KMessageBox> #include <KParts/Part> #include <KParts/ReadOnlyPart> #include <KConfigGroup> #include <KSharedConfig> #include <kio_version.h> #include "element.h" #include "file.h" #include "comment.h" #include "fileexporterbibtex.h" #include "onlinesearchabstract.h" #include "onlinesearchgeneral.h" #include "onlinesearchbibsonomy.h" #include "onlinesearchgooglescholar.h" #include "onlinesearchpubmed.h" #include "onlinesearchieeexplore.h" #include "onlinesearchacmportal.h" #include "onlinesearchsciencedirect.h" #include "onlinesearchspringerlink.h" #include "onlinesearcharxiv.h" #include "onlinesearchjstor.h" #include "onlinesearchmathscinet.h" #include "onlinesearchmrlookup.h" #include "onlinesearchinspirehep.h" #include "onlinesearchcernds.h" #include "onlinesearchingentaconnect.h" #include "onlinesearchsoanasaads.h" #include "onlinesearchisbndb.h" #include "onlinesearchideasrepec.h" #include "onlinesearchdoi.h" #include "onlinesearchbiorxiv.h" #include "onlinesearchsemanticscholar.h" #include "openfileinfo.h" #include "fileview.h" #include "models/filemodel.h" #include "searchresults.h" #include "logging_program.h" class SearchForm::SearchFormPrivate { private: SearchForm *p; QStackedWidget *queryTermsStack; QWidget *listContainer; QListWidget *enginesList; QLabel *whichEnginesLabel; QAction *actionOpenHomepage; public: KSharedConfigPtr config; const QString configGroupName; SearchResults *sr; QMap<QListWidgetItem *, OnlineSearchAbstract *> itemToOnlineSearch; QSet<OnlineSearchAbstract *> runningSearches; QPushButton *searchButton; QPushButton *useEntryButton; OnlineSearchQueryFormGeneral *generalQueryTermsForm; QTabWidget *tabWidget; QSharedPointer<const Entry> currentEntry; QProgressBar *progressBar; QMap<OnlineSearchAbstract *, int> progressMap; QMap<OnlineSearchQueryFormAbstract *, QScrollArea *> formToScrollArea; enum SearchFormPrivateRole { /// Homepage of a search engine HomepageRole = Qt::UserRole + 5, /// Special widget for a search engine WidgetRole = Qt::UserRole + 6, /// Name of a search engine NameRole = Qt::UserRole + 7 }; SearchFormPrivate(SearchResults *searchResults, SearchForm *parent) : p(parent), whichEnginesLabel(nullptr), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), configGroupName(QStringLiteral("Search Engines Docklet")), sr(searchResults), searchButton(nullptr), useEntryButton(nullptr), currentEntry(nullptr) { createGUI(); } OnlineSearchQueryFormAbstract *currentQueryForm() { QScrollArea *area = qobject_cast<QScrollArea *>(queryTermsStack->currentWidget()); return formToScrollArea.key(area, nullptr); } QScrollArea *wrapInScrollArea(OnlineSearchQueryFormAbstract *form, QWidget *parent) { QScrollArea *scrollArea = new QScrollArea(parent); form->setParent(scrollArea); scrollArea->setWidget(form); scrollArea->setWidgetResizable(true); scrollArea->setFrameShape(QFrame::NoFrame); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); formToScrollArea.insert(form, scrollArea); return scrollArea; } QWidget *createQueryTermsStack(QWidget *parent) { QWidget *container = new QWidget(parent); QVBoxLayout *vLayout = new QVBoxLayout(container); whichEnginesLabel = new QLabel(container); whichEnginesLabel->setWordWrap(true); vLayout->addWidget(whichEnginesLabel); vLayout->setStretchFactor(whichEnginesLabel, 0); connect(whichEnginesLabel, &QLabel::linkActivated, p, &SearchForm::switchToEngines); vLayout->addSpacing(8); queryTermsStack = new QStackedWidget(container); vLayout->addWidget(queryTermsStack); vLayout->setStretchFactor(queryTermsStack, 5); QScrollArea *scrollArea = wrapInScrollArea(createGeneralQueryTermsForm(queryTermsStack), queryTermsStack); queryTermsStack->addWidget(scrollArea); return container; } OnlineSearchQueryFormAbstract *createGeneralQueryTermsForm(QWidget *parent = nullptr) { generalQueryTermsForm = new OnlineSearchQueryFormGeneral(parent); return generalQueryTermsForm; } QWidget *createEnginesGUI(QWidget *parent) { listContainer = new QWidget(parent); QGridLayout *layout = new QGridLayout(listContainer); layout->setRowStretch(0, 1); layout->setRowStretch(1, 0); enginesList = new QListWidget(listContainer); layout->addWidget(enginesList, 0, 0, 1, 1); connect(enginesList, &QListWidget::itemChanged, p, &SearchForm::itemCheckChanged); connect(enginesList, &QListWidget::currentItemChanged, p, &SearchForm::enginesListCurrentChanged); enginesList->setSelectionMode(QAbstractItemView::NoSelection); actionOpenHomepage = new QAction(QIcon::fromTheme(QStringLiteral("internet-web-browser")), i18n("Go to Homepage"), p); connect(actionOpenHomepage, &QAction::triggered, p, &SearchForm::openHomepage); enginesList->addAction(actionOpenHomepage); enginesList->setContextMenuPolicy(Qt::ActionsContextMenu); return listContainer; } void createGUI() { QGridLayout *layout = new QGridLayout(p); layout->setMargin(0); layout->setRowStretch(0, 1); layout->setRowStretch(1, 0); layout->setColumnStretch(0, 0); layout->setColumnStretch(1, 1); layout->setColumnStretch(2, 0); tabWidget = new QTabWidget(p); tabWidget->setDocumentMode(true); layout->addWidget(tabWidget, 0, 0, 1, 3); QWidget *widget = createQueryTermsStack(tabWidget); tabWidget->addTab(widget, QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Query Terms")); QWidget *listContainer = createEnginesGUI(tabWidget); tabWidget->addTab(listContainer, QIcon::fromTheme(QStringLiteral("applications-engineering")), i18n("Engines")); connect(tabWidget, &QTabWidget::currentChanged, p, &SearchForm::tabSwitched); useEntryButton = new QPushButton(QIcon::fromTheme(QStringLiteral("go-up")), i18n("Use Entry"), p); layout->addWidget(useEntryButton, 1, 0, 1, 1); useEntryButton->setEnabled(false); connect(useEntryButton, &QPushButton::clicked, p, &SearchForm::copyFromEntry); progressBar = new QProgressBar(p); layout->addWidget(progressBar, 1, 1, 1, 1); progressBar->setMaximum(1000); progressBar->hide(); searchButton = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Search"), p); layout->addWidget(searchButton, 1, 2, 1, 1); connect(generalQueryTermsForm, &OnlineSearchQueryFormGeneral::returnPressed, searchButton, &QPushButton::click); updateGUI(); } void loadEngines() { enginesList->clear(); addEngine(new OnlineSearchAcmPortal(p)); addEngine(new OnlineSearchArXiv(p)); addEngine(new OnlineSearchBioRxiv(p)); addEngine(new OnlineSearchBibsonomy(p)); addEngine(new OnlineSearchGoogleScholar(p)); addEngine(new OnlineSearchIEEEXplore(p)); addEngine(new OnlineSearchIngentaConnect(p)); addEngine(new OnlineSearchJStor(p)); addEngine(new OnlineSearchMathSciNet(p)); addEngine(new OnlineSearchMRLookup(p)); addEngine(new OnlineSearchInspireHep(p)); addEngine(new OnlineSearchCERNDS(p)); addEngine(new OnlineSearchPubMed(p)); addEngine(new OnlineSearchScienceDirect(p)); addEngine(new OnlineSearchSpringerLink(p)); addEngine(new OnlineSearchSOANASAADS(p)); /// addEngine(new OnlineSearchIsbnDB(p)); /// disabled as provider switched to a paid model on 2017-12-26 addEngine(new OnlineSearchIDEASRePEc(p)); addEngine(new OnlineSearchDOI(p)); addEngine(new OnlineSearchSemanticScholar(p)); p->itemCheckChanged(nullptr); updateGUI(); } void addEngine(OnlineSearchAbstract *engine) { KConfigGroup configGroup(config, configGroupName); /// Disable signals while updating the widget and its items enginesList->blockSignals(true); QListWidgetItem *item = new QListWidgetItem(engine->label(), enginesList); static const QSet<QString> enginesEnabledByDefault {QStringLiteral("GoogleScholar"), QStringLiteral("Bibsonomy")}; item->setCheckState(configGroup.readEntry(engine->name(), enginesEnabledByDefault.contains(engine->name())) ? Qt::Checked : Qt::Unchecked); item->setIcon(engine->icon(item)); item->setToolTip(engine->label()); item->setData(HomepageRole, engine->homepage()); item->setData(NameRole, engine->name()); OnlineSearchQueryFormAbstract *widget = engine->customWidget(queryTermsStack); item->setData(WidgetRole, QVariant::fromValue<OnlineSearchQueryFormAbstract *>(widget)); if (widget != nullptr) { connect(widget, &OnlineSearchQueryFormAbstract::returnPressed, searchButton, &QPushButton::click); QScrollArea *scrollArea = wrapInScrollArea(widget, queryTermsStack); queryTermsStack->addWidget(scrollArea); } itemToOnlineSearch.insert(item, engine); connect(engine, &OnlineSearchAbstract::foundEntry, p, &SearchForm::foundEntry); connect(engine, &OnlineSearchAbstract::stoppedSearch, p, &SearchForm::stoppedSearch); connect(engine, &OnlineSearchAbstract::progress, p, &SearchForm::updateProgress); /// Re-enable signals after updating the widget and its items enginesList->blockSignals(false); } void switchToSearch() { for (QMap<QListWidgetItem *, OnlineSearchAbstract *>::ConstIterator it = itemToOnlineSearch.constBegin(); it != itemToOnlineSearch.constEnd(); ++it) disconnect(searchButton, &QPushButton::clicked, it.value(), &OnlineSearchAbstract::cancel); connect(searchButton, &QPushButton::clicked, p, &SearchForm::startSearch); searchButton->setText(i18n("Search")); searchButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); for (int i = tabWidget->count() - 1; i >= 0; --i) tabWidget->widget(i)->setEnabled(true); tabWidget->unsetCursor(); } void switchToCancel() { disconnect(searchButton, &QPushButton::clicked, p, &SearchForm::startSearch); for (QMap<QListWidgetItem *, OnlineSearchAbstract *>::ConstIterator it = itemToOnlineSearch.constBegin(); it != itemToOnlineSearch.constEnd(); ++it) connect(searchButton, &QPushButton::clicked, it.value(), &OnlineSearchAbstract::cancel); searchButton->setText(i18n("Stop")); searchButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); for (int i = tabWidget->count() - 1; i >= 0; --i) tabWidget->widget(i)->setEnabled(false); tabWidget->setCursor(Qt::WaitCursor); } void switchToEngines() { tabWidget->setCurrentWidget(listContainer); } void updateGUI() { if (whichEnginesLabel == nullptr) return; QStringList checkedEngines; QListWidgetItem *cursor = nullptr; for (QMap<QListWidgetItem *, OnlineSearchAbstract *>::ConstIterator it = itemToOnlineSearch.constBegin(); it != itemToOnlineSearch.constEnd(); ++it) if (it.key()->checkState() == Qt::Checked) { checkedEngines << it.key()->text(); cursor = it.key(); } switch (checkedEngines.size()) { case 0: whichEnginesLabel->setText(i18n("No search engine selected (<a href=\"changeEngine\">change</a>).")); break; case 1: whichEnginesLabel->setText(i18n("Search engine <b>%1</b> is selected (<a href=\"changeEngine\">change</a>).", checkedEngines.first())); break; case 2: whichEnginesLabel->setText(i18n("Search engines <b>%1</b> and <b>%2</b> are selected (<a href=\"changeEngine\">change</a>).", checkedEngines.first(), checkedEngines.at(1))); break; case 3: whichEnginesLabel->setText(i18n("Search engines <b>%1</b>, <b>%2</b>, and <b>%3</b> are selected (<a href=\"changeEngine\">change</a>).", checkedEngines.first(), checkedEngines.at(1), checkedEngines.at(2))); break; default: whichEnginesLabel->setText(i18n("Search engines <b>%1</b>, <b>%2</b>, and more are selected (<a href=\"changeEngine\">change</a>).", checkedEngines.first(), checkedEngines.at(1))); break; } OnlineSearchQueryFormAbstract *currentQueryWidget = nullptr; if (cursor != nullptr && checkedEngines.size() == 1) currentQueryWidget = cursor->data(WidgetRole).value<OnlineSearchQueryFormAbstract *>(); if (currentQueryWidget == nullptr) currentQueryWidget = generalQueryTermsForm; QScrollArea *area = formToScrollArea.value(currentQueryWidget, nullptr); if (area != nullptr) queryTermsStack->setCurrentWidget(area); if (useEntryButton != nullptr) useEntryButton->setEnabled(!currentEntry.isNull() && tabWidget->currentIndex() == 0); } void openHomepage() { QListWidgetItem *item = enginesList->currentItem(); if (item != nullptr) { QUrl url = item->data(HomepageRole).toUrl(); /// Guess mime type for url to open QMimeType mimeType = FileInfo::mimeTypeForUrl(url); const QString mimeTypeName = mimeType.name(); /// Ask KDE subsystem to open url in viewer matching mime type #if KIO_VERSION < 0x051f00 // < 5.31.0 KRun::runUrl(url, mimeTypeName, p, false, false); #else // KIO_VERSION < 0x051f00 // >= 5.31.0 KRun::runUrl(url, mimeTypeName, p, KRun::RunFlags()); #endif // KIO_VERSION < 0x051f00 } } void enginesListCurrentChanged(QListWidgetItem *current) { actionOpenHomepage->setEnabled(current != nullptr); } }; SearchForm::SearchForm(SearchResults *searchResults, QWidget *parent) : QWidget(parent), d(new SearchFormPrivate(searchResults, this)) { d->loadEngines(); d->switchToSearch(); } SearchForm::~SearchForm() { delete d; } void SearchForm::updatedConfiguration() { d->loadEngines(); } void SearchForm::setElement(QSharedPointer<Element> element, const File *) { d->currentEntry = element.dynamicCast<const Entry>(); d->useEntryButton->setEnabled(!d->currentEntry.isNull() && d->tabWidget->currentIndex() == 0); } void SearchForm::switchToEngines() { d->switchToEngines(); } void SearchForm::startSearch() { OnlineSearchQueryFormAbstract *currentForm = d->currentQueryForm(); if (!currentForm->readyToStart()) { KMessageBox::sorry(this, i18n("Could not start searching the Internet:\nThe search terms are not complete or invalid."), i18n("Searching the Internet")); return; } d->runningSearches.clear(); d->sr->clear(); d->progressBar->setValue(0); d->progressMap.clear(); d->useEntryButton->hide(); d->progressBar->show(); if (currentForm == d->generalQueryTermsForm) { /// start search using the general-purpose form's values QMap<QString, QString> queryTerms = d->generalQueryTermsForm->getQueryTerms(); int numResults = d->generalQueryTermsForm->getNumResults(); for (QMap<QListWidgetItem *, OnlineSearchAbstract *>::ConstIterator it = d->itemToOnlineSearch.constBegin(); it != d->itemToOnlineSearch.constEnd(); ++it) if (it.key()->checkState() == Qt::Checked) { it.value()->startSearch(queryTerms, numResults); d->runningSearches.insert(it.value()); } if (d->runningSearches.isEmpty()) { /// if no search engine has been checked (selected), something went wrong return; } } else { /// use the single selected search engine's specific form for (QMap<QListWidgetItem *, OnlineSearchAbstract *>::ConstIterator it = d->itemToOnlineSearch.constBegin(); it != d->itemToOnlineSearch.constEnd(); ++it) if (it.key()->checkState() == Qt::Checked) { it.value()->startSearchFromForm(); d->runningSearches.insert(it.value()); } if (d->runningSearches.isEmpty()) { /// if no search engine has been checked (selected), something went wrong return; } } d->switchToCancel(); } void SearchForm::foundEntry(QSharedPointer<Entry> entry) { d->sr->insertElement(entry); } void SearchForm::stoppedSearch(int) { OnlineSearchAbstract *engine = static_cast<OnlineSearchAbstract *>(sender()); if (d->runningSearches.remove(engine)) { if (d->runningSearches.isEmpty()) { /// last search engine stopped d->switchToSearch(); emit doneSearching(); QTimer::singleShot(1000, d->progressBar, &QProgressBar::hide); QTimer::singleShot(1100, d->useEntryButton, &QPushButton::show); } else { QStringList remainingEngines; remainingEngines.reserve(d->runningSearches.size()); for (OnlineSearchAbstract *running : const_cast<const QSet<OnlineSearchAbstract *> &>(d->runningSearches)) { remainingEngines.append(running->label()); } if (!remainingEngines.isEmpty()) qCDebug(LOG_KBIBTEX_PROGRAM) << "Remaining running engines:" << remainingEngines.join(QStringLiteral(", ")); } } } void SearchForm::tabSwitched(int newTab) { Q_UNUSED(newTab); d->updateGUI(); } void SearchForm::itemCheckChanged(QListWidgetItem *item) { int numCheckedEngines = 0; for (QMap<QListWidgetItem *, OnlineSearchAbstract *>::ConstIterator it = d->itemToOnlineSearch.constBegin(); it != d->itemToOnlineSearch.constEnd(); ++it) if (it.key()->checkState() == Qt::Checked) ++numCheckedEngines; d->searchButton->setEnabled(numCheckedEngines > 0); if (item != nullptr) { KConfigGroup configGroup(d->config, d->configGroupName); QString name = item->data(SearchForm::SearchFormPrivate::NameRole).toString(); configGroup.writeEntry(name, item->checkState() == Qt::Checked); d->config->sync(); } } void SearchForm::openHomepage() { d->openHomepage(); } void SearchForm::enginesListCurrentChanged(QListWidgetItem *current, QListWidgetItem *) { d->enginesListCurrentChanged(current); } void SearchForm::copyFromEntry() { Q_ASSERT_X(!d->currentEntry.isNull(), "SearchForm::copyFromEntry", "d->currentEntry is NULL"); d->currentQueryForm()->copyFromEntry(*(d->currentEntry)); } void SearchForm::updateProgress(int cur, int total) { OnlineSearchAbstract *ws = static_cast<OnlineSearchAbstract *>(sender()); d->progressMap[ws] = total > 0 ? cur * 1000 / total : 0; int progress = 0, count = 0; for (QMap<OnlineSearchAbstract *, int>::ConstIterator it = d->progressMap.constBegin(); it != d->progressMap.constEnd(); ++it, ++count) progress += it.value(); d->progressBar->setValue(count >= 1 ? progress / count : 0); } diff --git a/src/program/docklets/valuelist.cpp b/src/program/docklets/valuelist.cpp index 7d5f7a0f..cd464851 100644 --- a/src/program/docklets/valuelist.cpp +++ b/src/program/docklets/valuelist.cpp @@ -1,484 +1,485 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "valuelist.h" #include <typeinfo> #include <QTreeView> #include <QHeaderView> #include <QGridLayout> #include <QStringListModel> #include <QScrollBar> +#include <QLineEdit> +#include <QComboBox> #include <QTimer> #include <QSortFilterProxyModel> #include <QAction> -#include <KLineEdit> -#include <KComboBox> #include <KConfigGroup> #include <KLocalizedString> #include <KToggleAction> #include <KSharedConfig> #include "bibtexfields.h" #include "entry.h" #include "fileview.h" #include "valuelistmodel.h" #include "models/filemodel.h" class ValueList::ValueListPrivate { private: ValueList *p; ValueListDelegate *delegate; public: KSharedConfigPtr config; const QString configGroupName; const QString configKeyFieldName, configKeyShowCountColumn, configKeySortByCountAction, configKeyHeaderState; FileView *fileView; QTreeView *treeviewFieldValues; ValueListModel *model; QSortFilterProxyModel *sortingModel; - KComboBox *comboboxFieldNames; - KLineEdit *lineeditFilter; + QComboBox *comboboxFieldNames; + QLineEdit *lineeditFilter; const int countWidth; QAction *assignSelectionAction; QAction *removeSelectionAction; KToggleAction *showCountColumnAction; KToggleAction *sortByCountAction; ValueListPrivate(ValueList *parent) : p(parent), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), configGroupName(QStringLiteral("Value List Docklet")), configKeyFieldName(QStringLiteral("FieldName")), configKeyShowCountColumn(QStringLiteral("ShowCountColumn")), configKeySortByCountAction(QStringLiteral("SortByCountAction")), configKeyHeaderState(QStringLiteral("HeaderState")), fileView(nullptr), model(nullptr), sortingModel(nullptr), countWidth(8 + parent->fontMetrics().width(i18n("Count"))) { setupGUI(); initialize(); } void setupGUI() { QBoxLayout *layout = new QVBoxLayout(p); layout->setMargin(0); - comboboxFieldNames = new KComboBox(true, p); + comboboxFieldNames = new QComboBox(p); + comboboxFieldNames->setEditable(true); layout->addWidget(comboboxFieldNames); - lineeditFilter = new KLineEdit(p); + lineeditFilter = new QLineEdit(p); layout->addWidget(lineeditFilter); lineeditFilter->setClearButtonEnabled(true); lineeditFilter->setPlaceholderText(i18n("Filter value list")); treeviewFieldValues = new QTreeView(p); layout->addWidget(treeviewFieldValues); treeviewFieldValues->setEditTriggers(QAbstractItemView::EditKeyPressed); treeviewFieldValues->setSortingEnabled(true); treeviewFieldValues->sortByColumn(0, Qt::AscendingOrder); delegate = new ValueListDelegate(treeviewFieldValues); treeviewFieldValues->setItemDelegate(delegate); treeviewFieldValues->setRootIsDecorated(false); treeviewFieldValues->setSelectionMode(QTreeView::ExtendedSelection); treeviewFieldValues->setAlternatingRowColors(true); treeviewFieldValues->header()->setSectionResizeMode(QHeaderView::Fixed); treeviewFieldValues->setContextMenuPolicy(Qt::ActionsContextMenu); /// create context menu item to start renaming QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Replace all occurrences"), p); connect(action, &QAction::triggered, p, &ValueList::startItemRenaming); treeviewFieldValues->addAction(action); /// create context menu item to delete value action = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-delete-row")), i18n("Delete all occurrences"), p); connect(action, &QAction::triggered, p, &ValueList::deleteAllOccurrences); treeviewFieldValues->addAction(action); /// create context menu item to search for multiple selections action = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Search for selected values"), p); connect(action, &QAction::triggered, p, &ValueList::searchSelection); treeviewFieldValues->addAction(action); /// create context menu item to assign value to selected bibliography elements assignSelectionAction = new QAction(QIcon::fromTheme(QStringLiteral("emblem-new")), i18n("Add value to selected entries"), p); connect(assignSelectionAction, &QAction::triggered, p, &ValueList::assignSelection); treeviewFieldValues->addAction(assignSelectionAction); /// create context menu item to remove value from selected bibliography elements removeSelectionAction = new QAction(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Remove value from selected entries"), p); connect(removeSelectionAction, &QAction::triggered, p, &ValueList::removeSelection); treeviewFieldValues->addAction(removeSelectionAction); p->setEnabled(false); connect(comboboxFieldNames, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), p, &ValueList::fieldNamesChanged); - connect(comboboxFieldNames, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), lineeditFilter, &KLineEdit::clear); + connect(comboboxFieldNames, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), lineeditFilter, &QLineEdit::clear); connect(treeviewFieldValues, &QTreeView::activated, p, &ValueList::listItemActivated); connect(delegate, &ValueListDelegate::closeEditor, treeviewFieldValues, &QTreeView::reset); /// add context menu to header treeviewFieldValues->header()->setContextMenuPolicy(Qt::ActionsContextMenu); showCountColumnAction = new KToggleAction(i18n("Show Count Column"), treeviewFieldValues); connect(showCountColumnAction, &QAction::triggered, p, &ValueList::showCountColumnToggled); treeviewFieldValues->header()->addAction(showCountColumnAction); sortByCountAction = new KToggleAction(i18n("Sort by Count"), treeviewFieldValues); connect(sortByCountAction, &QAction::triggered, p, &ValueList::sortByCountToggled); treeviewFieldValues->header()->addAction(sortByCountAction); } void setComboboxFieldNamesCurrentItem(const QString &text) { int index = comboboxFieldNames->findData(text, Qt::UserRole, Qt::MatchExactly); if (index < 0) index = comboboxFieldNames->findData(text, Qt::UserRole, Qt::MatchStartsWith); if (index < 0) index = comboboxFieldNames->findData(text, Qt::UserRole, Qt::MatchContains); if (index >= 0) comboboxFieldNames->setCurrentIndex(index); } void initialize() { lineeditFilter->clear(); comboboxFieldNames->clear(); for (const auto &fd : const_cast<const BibTeXFields &>(BibTeXFields::instance())) { if (!fd.upperCamelCaseAlt.isEmpty()) continue; /// keep only "single" fields and not combined ones like "Author or Editor" if (fd.upperCamelCase.startsWith('^')) continue; /// skip "type" and "id" comboboxFieldNames->addItem(fd.label, fd.upperCamelCase); } /// Sort the combo box locale-aware. Thus we need a SortFilterProxyModel QSortFilterProxyModel *proxy = new QSortFilterProxyModel(comboboxFieldNames); proxy->setSortLocaleAware(true); proxy->setSourceModel(comboboxFieldNames->model()); comboboxFieldNames->model()->setParent(proxy); comboboxFieldNames->setModel(proxy); comboboxFieldNames->model()->sort(0); KConfigGroup configGroup(config, configGroupName); QString fieldName = configGroup.readEntry(configKeyFieldName, QString(Entry::ftAuthor)); setComboboxFieldNamesCurrentItem(fieldName); if (allowsMultipleValues(fieldName)) assignSelectionAction->setText(i18n("Add value to selected entries")); else assignSelectionAction->setText(i18n("Replace value of selected entries")); showCountColumnAction->setChecked(configGroup.readEntry(configKeyShowCountColumn, true)); sortByCountAction->setChecked(configGroup.readEntry(configKeySortByCountAction, false)); sortByCountAction->setEnabled(!showCountColumnAction->isChecked()); QByteArray headerState = configGroup.readEntry(configKeyHeaderState, QByteArray()); treeviewFieldValues->header()->restoreState(headerState); connect(treeviewFieldValues->header(), &QHeaderView::sortIndicatorChanged, p, &ValueList::columnsChanged); } void update() { QString text = comboboxFieldNames->itemData(comboboxFieldNames->currentIndex()).toString(); if (text.isEmpty()) text = comboboxFieldNames->currentText(); delegate->setFieldName(text); model = fileView == nullptr ? nullptr : fileView->valueListModel(text); QAbstractItemModel *usedModel = model; if (usedModel != nullptr) { model->setShowCountColumn(showCountColumnAction->isChecked()); model->setSortBy(sortByCountAction->isChecked() ? ValueListModel::SortByCount : ValueListModel::SortByText); if (sortingModel != nullptr) delete sortingModel; sortingModel = new QSortFilterProxyModel(p); sortingModel->setSourceModel(model); if (treeviewFieldValues->header()->isSortIndicatorShown()) sortingModel->sort(treeviewFieldValues->header()->sortIndicatorSection(), treeviewFieldValues->header()->sortIndicatorOrder()); else sortingModel->sort(1, Qt::DescendingOrder); sortingModel->setSortRole(ValueListModel::SortRole); sortingModel->setFilterKeyColumn(0); sortingModel->setFilterCaseSensitivity(Qt::CaseInsensitive); sortingModel->setFilterRole(ValueListModel::SearchTextRole); - connect(lineeditFilter, &KLineEdit::textEdited, sortingModel, &QSortFilterProxyModel::setFilterFixedString); + connect(lineeditFilter, &QLineEdit::textEdited, sortingModel, &QSortFilterProxyModel::setFilterFixedString); sortingModel->setSortLocaleAware(true); usedModel = sortingModel; } treeviewFieldValues->setModel(usedModel); KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(configKeyFieldName, text); config->sync(); } bool allowsMultipleValues(const QString &field) const { return (field.compare(Entry::ftAuthor, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftEditor, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftUrl, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftFile, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftLocalFile, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftDOI, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftKeywords, Qt::CaseInsensitive) == 0); } }; ValueList::ValueList(QWidget *parent) : QWidget(parent), d(new ValueListPrivate(this)) { QTimer::singleShot(500, this, &ValueList::delayedResize); } ValueList::~ValueList() { delete d; } void ValueList::setFileView(FileView *fileView) { if (d->fileView != nullptr) disconnect(d->fileView, &FileView::selectedElementsChanged, this, &ValueList::editorSelectionChanged); d->fileView = fileView; if (d->fileView != nullptr) { connect(d->fileView, &FileView::selectedElementsChanged, this, &ValueList::editorSelectionChanged); connect(d->fileView, &FileView::destroyed, this, &ValueList::editorDestroyed); } editorSelectionChanged(); update(); resizeEvent(nullptr); } void ValueList::update() { d->update(); setEnabled(d->fileView != nullptr); } void ValueList::resizeEvent(QResizeEvent *) { int widgetWidth = d->treeviewFieldValues->size().width() - d->treeviewFieldValues->verticalScrollBar()->size().width() - 8; d->treeviewFieldValues->setColumnWidth(0, widgetWidth - d->countWidth); d->treeviewFieldValues->setColumnWidth(1, d->countWidth); } void ValueList::listItemActivated(const QModelIndex &index) { setEnabled(false); QString itemText = d->sortingModel->mapToSource(index).data(ValueListModel::SearchTextRole).toString(); QVariant fieldVar = d->comboboxFieldNames->itemData(d->comboboxFieldNames->currentIndex()); QString fieldText = fieldVar.toString(); if (fieldText.isEmpty()) fieldText = d->comboboxFieldNames->currentText(); SortFilterFileModel::FilterQuery fq; fq.terms << itemText; fq.combination = SortFilterFileModel::EveryTerm; fq.field = fieldText; fq.searchPDFfiles = false; d->fileView->setFilterBarFilter(fq); setEnabled(true); } void ValueList::searchSelection() { QVariant fieldVar = d->comboboxFieldNames->itemData(d->comboboxFieldNames->currentIndex()); QString fieldText = fieldVar.toString(); if (fieldText.isEmpty()) fieldText = d->comboboxFieldNames->currentText(); SortFilterFileModel::FilterQuery fq; fq.combination = SortFilterFileModel::EveryTerm; fq.field = fieldText; const auto selectedIndexes = d->treeviewFieldValues->selectionModel()->selectedIndexes(); for (const QModelIndex &index : selectedIndexes) { if (index.column() == 0) { QString itemText = index.data(ValueListModel::SearchTextRole).toString(); fq.terms << itemText; } } fq.searchPDFfiles = false; if (!fq.terms.isEmpty()) d->fileView->setFilterBarFilter(fq); } void ValueList::assignSelection() { QString field = d->comboboxFieldNames->itemData(d->comboboxFieldNames->currentIndex()).toString(); if (field.isEmpty()) field = d->comboboxFieldNames->currentText(); if (field.isEmpty()) return; ///< empty field is invalid; quit const Value toBeAssignedValue = d->sortingModel->mapToSource(d->treeviewFieldValues->currentIndex()).data(Qt::EditRole).value<Value>(); if (toBeAssignedValue.isEmpty()) return; ///< empty value is invalid; quit const QString toBeAssignedValueText = PlainTextValue::text(toBeAssignedValue); /// Keep track if any modifications were made to the bibliography file bool madeModification = false; /// Go through all selected elements in current editor const QList<QSharedPointer<Element> > &selection = d->fileView->selectedElements(); for (const auto &element : selection) { /// Only entries (not macros or comments) are of interest QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); if (!entry.isNull()) { /// Fields are separated into two categories: /// 1. Where more values can be appended, like authors or URLs /// 2. Where values should be replaced, like title, year, or journal if (d->allowsMultipleValues(field)) { /// Fields for which multiple values are valid bool valueItemAlreadyContained = false; ///< add only if to-be-assigned value is not yet contained Value entrysValueForField = entry->value(field); for (const auto &containedValueItem : const_cast<const Value &>(entrysValueForField)) { valueItemAlreadyContained |= PlainTextValue::text(containedValueItem) == toBeAssignedValueText; if (valueItemAlreadyContained) break; } if (!valueItemAlreadyContained) { /// Add each ValueItem from the to-be-assigned value to the entry's value for this field entrysValueForField.reserve(toBeAssignedValue.size()); for (const auto &newValueItem : toBeAssignedValue) { entrysValueForField.append(newValueItem); } /// "Write back" value to field in entry entry->remove(field); entry->insert(field, entrysValueForField); /// Keep track that bibliography file has been modified madeModification = true; } } else { /// Fields for which only value is valid, thus the old value will be replaced entry->remove(field); entry->insert(field, toBeAssignedValue); /// Keep track that bibliography file has been modified madeModification = true; } } } if (madeModification) { /// Notify main editor about change it its data d->fileView->externalModification(); } } void ValueList::removeSelection() { QString field = d->comboboxFieldNames->itemData(d->comboboxFieldNames->currentIndex()).toString(); if (field.isEmpty()) field = d->comboboxFieldNames->currentText(); if (field.isEmpty()) return; ///< empty field is invalid; quit const Value toBeRemovedValue = d->sortingModel->mapToSource(d->treeviewFieldValues->currentIndex()).data(Qt::EditRole).value<Value>(); if (toBeRemovedValue.isEmpty()) return; ///< empty value is invalid; quit const QString toBeRemovedValueText = PlainTextValue::text(toBeRemovedValue); /// Keep track if any modifications were made to the bibliography file bool madeModification = false; /// Go through all selected elements in current editor const QList<QSharedPointer<Element> > &selection = d->fileView->selectedElements(); for (const auto &element : selection) { /// Only entries (not macros or comments) are of interest QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); if (!entry.isNull()) { Value entrysValueForField = entry->value(field); bool valueModified = false; for (int i = 0; i < entrysValueForField.count(); ++i) { const QString valueItemText = PlainTextValue::text(entrysValueForField[i]); if (valueItemText == toBeRemovedValueText) { valueModified = true; entrysValueForField.remove(i); break; } } if (valueModified) { entry->remove(field); entry->insert(field, entrysValueForField); madeModification = true; } } } if (madeModification) { update(); /// Notify main editor about change it its data d->fileView->externalModification(); } } void ValueList::startItemRenaming() { /// Get current index from sorted model QModelIndex sortedIndex = d->treeviewFieldValues->currentIndex(); /// Make the tree view start and editing delegate on the index d->treeviewFieldValues->edit(sortedIndex); } void ValueList::deleteAllOccurrences() { /// Get current index from sorted model QModelIndex sortedIndex = d->treeviewFieldValues->currentIndex(); /// Get "real" index from original model, but resort to sibling in first column QModelIndex realIndex = d->sortingModel->mapToSource(sortedIndex); realIndex = realIndex.sibling(realIndex.row(), 0); /// Remove current index from data model d->model->removeValue(realIndex); /// Notify main editor about change it its data d->fileView->externalModification(); } void ValueList::showCountColumnToggled() { if (d->model != nullptr) d->model->setShowCountColumn(d->showCountColumnAction->isChecked()); if (d->showCountColumnAction->isChecked()) resizeEvent(nullptr); d->sortByCountAction->setEnabled(!d->showCountColumnAction->isChecked()); KConfigGroup configGroup(d->config, d->configGroupName); configGroup.writeEntry(d->configKeyShowCountColumn, d->showCountColumnAction->isChecked()); d->config->sync(); } void ValueList::sortByCountToggled() { if (d->model != nullptr) d->model->setSortBy(d->sortByCountAction->isChecked() ? ValueListModel::SortByCount : ValueListModel::SortByText); KConfigGroup configGroup(d->config, d->configGroupName); configGroup.writeEntry(d->configKeySortByCountAction, d->sortByCountAction->isChecked()); d->config->sync(); } void ValueList::delayedResize() { resizeEvent(nullptr); } void ValueList::columnsChanged() { QByteArray headerState = d->treeviewFieldValues->header()->saveState(); KConfigGroup configGroup(d->config, d->configGroupName); configGroup.writeEntry(d->configKeyHeaderState, headerState); d->config->sync(); resizeEvent(nullptr); } void ValueList::editorSelectionChanged() { const bool selectedElements = d->fileView == nullptr ? false : d->fileView->selectedElements().count() > 0; d->assignSelectionAction->setEnabled(selectedElements); d->removeSelectionAction->setEnabled(selectedElements); } void ValueList::editorDestroyed() { /// Reset internal variable to NULL to avoid /// accessing invalid pointer/data later d->fileView = nullptr; editorSelectionChanged(); } void ValueList::fieldNamesChanged(int i) { const QString field = d->comboboxFieldNames->itemData(i).toString(); if (d->allowsMultipleValues(field)) d->assignSelectionAction->setText(i18n("Add value to selected entries")); else d->assignSelectionAction->setText(i18n("Replace value of selected entries")); update(); } diff --git a/src/program/docklets/zoterobrowser.cpp b/src/program/docklets/zoterobrowser.cpp index bb393438..e9f50c6b 100644 --- a/src/program/docklets/zoterobrowser.cpp +++ b/src/program/docklets/zoterobrowser.cpp @@ -1,440 +1,441 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see <https://www.gnu.org/licenses/>. * ***************************************************************************/ #include "zoterobrowser.h" #include <QTreeView> #include <QTabWidget> #include <QListView> #include <QLayout> #include <QFormLayout> #include <QAbstractItemModel> #include <QRadioButton> #include <QPushButton> #include <QDebug> -#include <KLineEdit> +#include <QPointer> +#include <QLineEdit> +#include <QComboBox> #include <KLocalizedString> -#include <KComboBox> #include <KWallet/KWallet> #include <KMessageBox> #include "element.h" #include "searchresults.h" #include "zotero/collectionmodel.h" #include "zotero/collection.h" #include "zotero/items.h" #include "zotero/groups.h" #include "zotero/tags.h" #include "zotero/tagmodel.h" #include "zotero/api.h" #include "zotero/oauthwizard.h" using KWallet::Wallet; class ZoteroBrowser::Private { private: ZoteroBrowser *p; public: Zotero::Items *items; Zotero::Groups *groups; Zotero::Tags *tags; Zotero::TagModel *tagModel; Zotero::Collection *collection; Zotero::CollectionModel *collectionModel; QSharedPointer<Zotero::API> api; bool needToApplyCredentials; SearchResults *searchResults; QTabWidget *tabWidget; QTreeView *collectionBrowser; QListView *tagBrowser; - KLineEdit *lineEditNumericUserId; - KLineEdit *lineEditApiKey; + QLineEdit *lineEditNumericUserId; + QLineEdit *lineEditApiKey; QRadioButton *radioPersonalLibrary; QRadioButton *radioGroupLibrary; bool comboBoxGroupListInitialized; - KComboBox *comboBoxGroupList; + QComboBox *comboBoxGroupList; QCursor nonBusyCursor; Wallet *wallet; static const QString walletFolderOAuth, walletEntryKBibTeXZotero, walletKeyZoteroId, walletKeyZoteroApiKey; Private(SearchResults *sr, ZoteroBrowser *parent) : p(parent), items(nullptr), groups(nullptr), tags(nullptr), tagModel(nullptr), collection(nullptr), collectionModel(nullptr), needToApplyCredentials(true), searchResults(sr), comboBoxGroupListInitialized(false), nonBusyCursor(p->cursor()), wallet(nullptr) { setupGUI(); } ~Private() { if (wallet != nullptr) delete wallet; if (items != nullptr) delete items; if (groups != nullptr) delete groups; if (tags != nullptr) delete tags; if (tagModel != nullptr) delete tagModel; if (collection != nullptr) delete collection; if (collectionModel != nullptr) delete collectionModel; api.clear(); } void setupGUI() { QBoxLayout *layout = new QVBoxLayout(p); tabWidget = new QTabWidget(p); layout->addWidget(tabWidget); QWidget *container = new QWidget(tabWidget); tabWidget->addTab(container, QIcon::fromTheme(QStringLiteral("preferences-web-browser-identification")), i18n("Library")); connect(tabWidget, &QTabWidget::currentChanged, p, &ZoteroBrowser::tabChanged); QBoxLayout *containerLayout = new QVBoxLayout(container); /// Personal or Group Library QGridLayout *gridLayout = new QGridLayout(); containerLayout->addLayout(gridLayout); gridLayout->setMargin(0); gridLayout->setColumnMinimumWidth(0, 16); // TODO determine size of a radio button radioPersonalLibrary = new QRadioButton(i18n("Personal library"), container); gridLayout->addWidget(radioPersonalLibrary, 0, 0, 1, 2); radioGroupLibrary = new QRadioButton(i18n("Group library"), container); gridLayout->addWidget(radioGroupLibrary, 1, 0, 1, 2); - comboBoxGroupList = new KComboBox(false, container); + comboBoxGroupList = new QComboBox(container); gridLayout->addWidget(comboBoxGroupList, 2, 1, 1, 1); QSizePolicy sizePolicy = comboBoxGroupList->sizePolicy(); sizePolicy.setHorizontalPolicy(QSizePolicy::MinimumExpanding); comboBoxGroupList->setSizePolicy(sizePolicy); radioPersonalLibrary->setChecked(true); comboBoxGroupList->setEnabled(false); comboBoxGroupList->addItem(i18n("No groups available")); connect(radioGroupLibrary, &QRadioButton::toggled, p, &ZoteroBrowser::radioButtonsToggled); connect(radioPersonalLibrary, &QRadioButton::toggled, p, &ZoteroBrowser::radioButtonsToggled); connect(comboBoxGroupList, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), p, &ZoteroBrowser::groupListChanged); containerLayout->addStretch(10); /// Credentials QFormLayout *containerForm = new QFormLayout(); containerLayout->addLayout(containerForm, 1); containerForm->setMargin(0); - lineEditNumericUserId = new KLineEdit(container); + lineEditNumericUserId = new QLineEdit(container); lineEditNumericUserId->setSizePolicy(sizePolicy); lineEditNumericUserId->setReadOnly(true); containerForm->addRow(i18n("Numeric user id:"), lineEditNumericUserId); - connect(lineEditNumericUserId, &KLineEdit::textChanged, p, &ZoteroBrowser::invalidateGroupList); + connect(lineEditNumericUserId, &QLineEdit::textChanged, p, &ZoteroBrowser::invalidateGroupList); - lineEditApiKey = new KLineEdit(container); + lineEditApiKey = new QLineEdit(container); lineEditApiKey->setSizePolicy(sizePolicy); lineEditApiKey->setReadOnly(true); containerForm->addRow(i18n("API key:"), lineEditApiKey); - connect(lineEditApiKey, &KLineEdit::textChanged, p, &ZoteroBrowser::invalidateGroupList); + connect(lineEditApiKey, &QLineEdit::textChanged, p, &ZoteroBrowser::invalidateGroupList); QBoxLayout *containerButtonLayout = new QHBoxLayout(); containerLayout->addLayout(containerButtonLayout, 0); containerButtonLayout->setMargin(0); QPushButton *buttonGetOAuthCredentials = new QPushButton(QIcon::fromTheme(QStringLiteral("preferences-web-browser-identification")), i18n("Get New Credentials"), container); containerButtonLayout->addWidget(buttonGetOAuthCredentials, 0); connect(buttonGetOAuthCredentials, &QPushButton::clicked, p, &ZoteroBrowser::getOAuthCredentials); containerButtonLayout->addStretch(1); /// Collection browser collectionBrowser = new QTreeView(tabWidget); tabWidget->addTab(collectionBrowser, QIcon::fromTheme(QStringLiteral("folder-yellow")), i18n("Collections")); collectionBrowser->setHeaderHidden(true); collectionBrowser->setExpandsOnDoubleClick(false); connect(collectionBrowser, &QTreeView::doubleClicked, p, &ZoteroBrowser::collectionDoubleClicked); /// Tag browser tagBrowser = new QListView(tabWidget); tabWidget->addTab(tagBrowser, QIcon::fromTheme(QStringLiteral("mail-tagged")), i18n("Tags")); connect(tagBrowser, &QListView::doubleClicked, p, &ZoteroBrowser::tagDoubleClicked); } void queueReadOAuthCredentials() { if (wallet != nullptr && wallet->isOpen()) p->readOAuthCredentials(true); else { /// Wallet is closed or not initialized if (wallet != nullptr) /// Delete existing but closed wallet, will be replaced by new, open wallet soon delete wallet; p->setEnabled(false); p->setCursor(Qt::WaitCursor); wallet = Wallet::openWallet(Wallet::NetworkWallet(), p->winId(), Wallet::Asynchronous); connect(wallet, &Wallet::walletOpened, p, &ZoteroBrowser::readOAuthCredentials); } } void queueWriteOAuthCredentials() { if (wallet != nullptr && wallet->isOpen()) p->writeOAuthCredentials(true); else { /// Wallet is closed or not initialized if (wallet != nullptr) /// Delete existing but closed wallet, will be replaced by new, open wallet soon delete wallet; p->setEnabled(false); p->setCursor(Qt::WaitCursor); wallet = Wallet::openWallet(Wallet::NetworkWallet(), p->winId(), Wallet::Asynchronous); connect(wallet, &Wallet::walletOpened, p, &ZoteroBrowser::writeOAuthCredentials); } } }; const QString ZoteroBrowser::Private::walletFolderOAuth = QStringLiteral("OAuth"); const QString ZoteroBrowser::Private::walletEntryKBibTeXZotero = QStringLiteral("KBibTeX/Zotero"); const QString ZoteroBrowser::Private::walletKeyZoteroId = QStringLiteral("UserId"); const QString ZoteroBrowser::Private::walletKeyZoteroApiKey = QStringLiteral("ApiKey"); ZoteroBrowser::ZoteroBrowser(SearchResults *searchResults, QWidget *parent) : QWidget(parent), d(new ZoteroBrowser::Private(searchResults, this)) { /// Forece GUI update updateButtons(); radioButtonsToggled(); } ZoteroBrowser::~ZoteroBrowser() { delete d; } void ZoteroBrowser::visibiltyChanged(bool v) { if (v && d->lineEditApiKey->text().isEmpty()) /// If Zotero dock became visible and no API key is set, check KWallet for credentials d->queueReadOAuthCredentials(); } void ZoteroBrowser::modelReset() { if (!d->collection->busy() && !d->tags->busy()) { setCursor(d->nonBusyCursor); setEnabled(true); } else { setCursor(Qt::WaitCursor); setEnabled(false); } if (!d->tags->busy() && !d->collection->busy() && !(d->collection->initialized() && d->tags->initialized())) KMessageBox::information(this, i18n("KBibTeX failed to retrieve the bibliography from Zotero. Please check that the provided user id and API key are valid."), i18n("Failed to retrieve data from Zotero")); } void ZoteroBrowser::collectionDoubleClicked(const QModelIndex &index) { setCursor(Qt::WaitCursor); setEnabled(false); ///< will be re-enabled when item retrieve got finished (slot reenableWidget) const QString collectionId = index.data(Zotero::CollectionModel::CollectionIdRole).toString(); d->searchResults->clear(); d->items->retrieveItemsByCollection(collectionId); } void ZoteroBrowser::tagDoubleClicked(const QModelIndex &index) { setCursor(Qt::WaitCursor); setEnabled(false); ///< will be re-enabled when item retrieve got finished (slot reenableWidget) const QString tag = index.data(Zotero::TagModel::TagRole).toString(); d->searchResults->clear(); d->items->retrieveItemsByTag(tag); } void ZoteroBrowser::showItem(QSharedPointer<Element> e) { d->searchResults->insertElement(e); emit itemToShow(); } void ZoteroBrowser::reenableWidget() { setCursor(d->nonBusyCursor); setEnabled(true); } void ZoteroBrowser::updateButtons() { const bool validNumericIdAndApiKey = !d->lineEditNumericUserId->text().isEmpty() && !d->lineEditApiKey->text().isEmpty(); d->radioGroupLibrary->setEnabled(validNumericIdAndApiKey); d->radioPersonalLibrary->setEnabled(validNumericIdAndApiKey); d->needToApplyCredentials = true; } bool ZoteroBrowser::applyCredentials() { bool ok = false; const int userId = d->lineEditNumericUserId->text().toInt(&ok); const QString apiKey = d->lineEditApiKey->text(); if (ok && !apiKey.isEmpty()) { setCursor(Qt::WaitCursor); setEnabled(false); ok = false; int groupId = d->comboBoxGroupList->itemData(d->comboBoxGroupList->currentIndex()).toInt(&ok); if (!ok) groupId = -1; disconnect(d->tags, &Zotero::Tags::finishedLoading, this, &ZoteroBrowser::reenableWidget); disconnect(d->items, &Zotero::Items::stoppedSearch, this, &ZoteroBrowser::reenableWidget); disconnect(d->items, &Zotero::Items::foundElement, this, &ZoteroBrowser::showItem); disconnect(d->tagModel, &Zotero::TagModel::modelReset, this, &ZoteroBrowser::modelReset); disconnect(d->collectionModel, &Zotero::CollectionModel::modelReset, this, &ZoteroBrowser::modelReset); d->collection->deleteLater(); d->items->deleteLater(); d->tags->deleteLater(); d->collectionModel->deleteLater(); d->tagModel->deleteLater(); d->api.clear(); const bool makeGroupRequest = d->radioGroupLibrary->isChecked() && groupId > 0; d->api = QSharedPointer<Zotero::API>(new Zotero::API(makeGroupRequest ? Zotero::API::GroupRequest : Zotero::API::UserRequest, makeGroupRequest ? groupId : userId, d->lineEditApiKey->text(), this)); d->items = new Zotero::Items(d->api, this); d->tags = new Zotero::Tags(d->api, this); d->tagModel = new Zotero::TagModel(d->tags, this); d->tagBrowser->setModel(d->tagModel); d->collection = new Zotero::Collection(d->api, this); d->collectionModel = new Zotero::CollectionModel(d->collection, this); d->collectionBrowser->setModel(d->collectionModel); connect(d->collectionModel, &Zotero::CollectionModel::modelReset, this, &ZoteroBrowser::modelReset); connect(d->tagModel, &Zotero::TagModel::modelReset, this, &ZoteroBrowser::modelReset); connect(d->items, &Zotero::Items::foundElement, this, &ZoteroBrowser::showItem); connect(d->items, &Zotero::Items::stoppedSearch, this, &ZoteroBrowser::reenableWidget); connect(d->tags, &Zotero::Tags::finishedLoading, this, &ZoteroBrowser::reenableWidget); d->needToApplyCredentials = false; return true; } else return false; } void ZoteroBrowser::radioButtonsToggled() { d->comboBoxGroupList->setEnabled(d->comboBoxGroupListInitialized && d->comboBoxGroupList->count() > 0 && d->radioGroupLibrary->isChecked()); if (!d->comboBoxGroupListInitialized && d->radioGroupLibrary->isChecked()) retrieveGroupList(); d->needToApplyCredentials = true; } void ZoteroBrowser::groupListChanged() { d->needToApplyCredentials = true; } void ZoteroBrowser::retrieveGroupList() { bool ok = false; const int userId = d->lineEditNumericUserId->text().toInt(&ok); if (ok) { setCursor(Qt::WaitCursor); setEnabled(false); d->comboBoxGroupList->clear(); d->comboBoxGroupListInitialized = false; disconnect(d->groups, &Zotero::Groups::finishedLoading, this, &ZoteroBrowser::gotGroupList); d->groups->deleteLater(); d->api.clear(); d->api = QSharedPointer<Zotero::API>(new Zotero::API(Zotero::API::UserRequest, userId, d->lineEditApiKey->text(), this)); d->groups = new Zotero::Groups(d->api, this); connect(d->groups, &Zotero::Groups::finishedLoading, this, &ZoteroBrowser::gotGroupList); } } void ZoteroBrowser::invalidateGroupList() { d->comboBoxGroupList->clear(); d->comboBoxGroupListInitialized = false; d->comboBoxGroupList->addItem(i18n("No groups available or no permissions")); d->comboBoxGroupList->setEnabled(false); d->radioPersonalLibrary->setChecked(true); } void ZoteroBrowser::gotGroupList() { const QMap<int, QString> groupMap = d->groups->groups(); for (QMap<int, QString>::ConstIterator it = groupMap.constBegin(); it != groupMap.constEnd(); ++it) { d->comboBoxGroupList->addItem(it.value(), QVariant::fromValue<int>(it.key())); } if (groupMap.isEmpty()) { invalidateGroupList(); } else { d->comboBoxGroupListInitialized = true; d->comboBoxGroupList->setEnabled(true); d->needToApplyCredentials = true; } reenableWidget(); } void ZoteroBrowser::getOAuthCredentials() { QPointer<Zotero::OAuthWizard> wizard = new Zotero::OAuthWizard(this); if (wizard->exec() && !wizard->apiKey().isEmpty() && wizard->userId() >= 0) { d->lineEditApiKey->setText(wizard->apiKey()); d->lineEditNumericUserId->setText(QString::number(wizard->userId())); d->queueWriteOAuthCredentials(); updateButtons(); retrieveGroupList(); } delete wizard; } void ZoteroBrowser::readOAuthCredentials(bool ok) { /// Do not call this slot a second time disconnect(d->wallet, &Wallet::walletOpened, this, &ZoteroBrowser::readOAuthCredentials); if (ok && (d->wallet->hasFolder(ZoteroBrowser::Private::walletFolderOAuth) || d->wallet->createFolder(ZoteroBrowser::Private::walletFolderOAuth)) && d->wallet->setFolder(ZoteroBrowser::Private::walletFolderOAuth)) { if (d->wallet->hasEntry(ZoteroBrowser::Private::walletEntryKBibTeXZotero)) { QMap<QString, QString> map; if (d->wallet->readMap(ZoteroBrowser::Private::walletEntryKBibTeXZotero, map) == 0) { if (map.contains(ZoteroBrowser::Private::walletKeyZoteroId) && map.contains(ZoteroBrowser::Private::walletKeyZoteroApiKey)) { d->lineEditNumericUserId->setText(map.value(ZoteroBrowser::Private::walletKeyZoteroId, QString())); d->lineEditApiKey->setText(map.value(ZoteroBrowser::Private::walletKeyZoteroApiKey, QString())); updateButtons(); retrieveGroupList(); } else qWarning() << "Failed to locate Zotero Id and/or API key in KWallet"; } else qWarning() << "Failed to access Zotero data in KWallet"; } else qDebug() << "No Zotero credentials stored in KWallet"; } else qWarning() << "Accessing KWallet to sync API key did not succeed"; reenableWidget(); } void ZoteroBrowser::writeOAuthCredentials(bool ok) { disconnect(d->wallet, &Wallet::walletOpened, this, &ZoteroBrowser::writeOAuthCredentials); if (ok && (d->wallet->hasFolder(ZoteroBrowser::Private::walletFolderOAuth) || d->wallet->createFolder(ZoteroBrowser::Private::walletFolderOAuth)) && d->wallet->setFolder(ZoteroBrowser::Private::walletFolderOAuth)) { QMap<QString, QString> map; map.insert(ZoteroBrowser::Private::walletKeyZoteroId, d->lineEditNumericUserId->text()); map.insert(ZoteroBrowser::Private::walletKeyZoteroApiKey, d->lineEditApiKey->text()); if (d->wallet->writeMap(ZoteroBrowser::Private::walletEntryKBibTeXZotero, map) != 0) qWarning() << "Writing API key to KWallet failed"; } else qWarning() << "Accessing KWallet to sync API key did not succeed"; reenableWidget(); } void ZoteroBrowser::tabChanged(int newTabIndex) { if (newTabIndex > 0 /** tabs after credential tab*/ && d->needToApplyCredentials) { const bool success = applyCredentials(); for (int i = 1; i < d->tabWidget->count(); ++i) d->tabWidget->widget(i)->setEnabled(success); } }