diff --git a/src/data/file.cpp b/src/data/file.cpp index a65d9c3d..6a15e4f4 100644 --- a/src/data/file.cpp +++ b/src/data/file.cpp @@ -1,338 +1,342 @@ /*************************************************************************** - * Copyright (C) 2004-2019 by Thomas Fischer * + * Copyright (C) 2004-2020 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 "file.h" #include #include #include #include #include #include "entry.h" #include "element.h" #include "macro.h" #include "comment.h" #include "preamble.h" #include "logging_data.h" const QString File::Url = QStringLiteral("Url"); const QString File::Encoding = QStringLiteral("Encoding"); const QString File::StringDelimiter = QStringLiteral("StringDelimiter"); const QString File::QuoteComment = QStringLiteral("QuoteComment"); const QString File::KeywordCasing = QStringLiteral("KeywordCasing"); const QString File::ProtectCasing = QStringLiteral("ProtectCasing"); const QString File::NameFormatting = QStringLiteral("NameFormatting"); const QString File::ListSeparator = QStringLiteral("ListSeparator"); const quint64 valid = 0x08090a0b0c0d0e0f; const quint64 invalid = 0x0102030405060708; class File::FilePrivate { private: quint64 validInvalidField; static const quint64 initialInternalIdCounter; static quint64 internalIdCounter; public: const quint64 internalId; QHash properties; explicit FilePrivate(File *parent) : validInvalidField(valid), internalId(++internalIdCounter) { Q_UNUSED(parent) const bool isValid = checkValidity(); if (!isValid) qCDebug(LOG_KBIBTEX_DATA) << "Creating File instance" << internalId << " Valid?" << isValid; loadConfiguration(); } ~FilePrivate() { const bool isValid = checkValidity(); if (!isValid) qCDebug(LOG_KBIBTEX_DATA) << "Deleting File instance" << internalId << " Valid?" << isValid; validInvalidField = invalid; } /// Copy-assignment operator FilePrivate &operator= (const FilePrivate &other) { if (this != &other) { validInvalidField = other.validInvalidField; properties = other.properties; const bool isValid = checkValidity(); if (!isValid) qCDebug(LOG_KBIBTEX_DATA) << "Assigning File instance" << other.internalId << "to" << internalId << " Is other valid?" << other.checkValidity() << " Self valid?" << isValid; } return *this; } /// Move-assignment operator FilePrivate &operator= (FilePrivate &&other) { if (this != &other) { validInvalidField = std::move(other.validInvalidField); properties = std::move(other.properties); const bool isValid = checkValidity(); if (!isValid) qCDebug(LOG_KBIBTEX_DATA) << "Assigning File instance" << other.internalId << "to" << internalId << " Is other valid?" << other.checkValidity() << " Self valid?" << isValid; } return *this; } void loadConfiguration() { /// Load and set configuration as stored in settings properties.insert(File::Encoding, Preferences::instance().bibTeXEncoding()); properties.insert(File::StringDelimiter, Preferences::instance().bibTeXStringDelimiter()); properties.insert(File::QuoteComment, static_cast(Preferences::instance().bibTeXQuoteComment())); properties.insert(File::KeywordCasing, static_cast(Preferences::instance().bibTeXKeywordCasing())); properties.insert(File::NameFormatting, Preferences::instance().personNameFormat()); properties.insert(File::ProtectCasing, static_cast(Preferences::instance().bibTeXProtectCasing() ? Qt::Checked : Qt::Unchecked)); properties.insert(File::ListSeparator, Preferences::instance().bibTeXListSeparator()); } bool checkValidity() const { if (validInvalidField != valid) { /// 'validInvalidField' must equal to the know 'valid' value qCWarning(LOG_KBIBTEX_DATA) << "Failed validity check: " << validInvalidField << "!=" << valid; return false; } else if (internalId <= initialInternalIdCounter) { /// Internal id counter starts at initialInternalIdCounter+1 qCWarning(LOG_KBIBTEX_DATA) << "Failed validity check: " << internalId << "< " << (initialInternalIdCounter + 1); return false; } else if (internalId > 600000) { /// Reasonable assumption: not more that 500000 ids used qCWarning(LOG_KBIBTEX_DATA) << "Failed validity check: " << internalId << "> 600000"; return false; } return true; } }; const quint64 File::FilePrivate::initialInternalIdCounter = 99999; quint64 File::FilePrivate::internalIdCounter = File::FilePrivate::initialInternalIdCounter; File::File() : QList >(), d(new FilePrivate(this)) { /// nothing } File::File(const File &other) : QList >(other), d(new FilePrivate(this)) { d->operator =(*other.d); } File::File(File &&other) : QList >(std::move(other)), d(new FilePrivate(this)) { d->operator =(std::move(*other.d)); } File::~File() { Q_ASSERT_X(d->checkValidity(), "File::~File()", "This File object is not valid"); delete d; } File &File::operator= (const File &other) { if (this != &other) d->operator =(*other.d); return *this; } File &File::operator= (File &&other) { if (this != &other) d->operator =(std::move(*other.d)); return *this; } bool File::operator==(const File &other) const { if (size() != other.size()) return false; for (File::ConstIterator myIt = constBegin(), otherIt = other.constBegin(); myIt != constEnd() && otherIt != constEnd(); ++myIt, ++otherIt) { QSharedPointer myEntry = myIt->dynamicCast(); QSharedPointer otherEntry = otherIt->dynamicCast(); if ((myEntry.isNull() && !otherEntry.isNull()) || (!myEntry.isNull() && otherEntry.isNull())) return false; if (!myEntry.isNull() && !otherEntry.isNull()) { if (myEntry->operator !=(*otherEntry.data())) return false; } else { QSharedPointer myMacro = myIt->dynamicCast(); QSharedPointer otherMacro = otherIt->dynamicCast(); if ((myMacro.isNull() && !otherMacro.isNull()) || (!myMacro.isNull() && otherMacro.isNull())) return false; if (!myMacro.isNull() && !otherMacro.isNull()) { if (myMacro->operator !=(*otherMacro.data())) return false; } else { QSharedPointer myPreamble = myIt->dynamicCast(); QSharedPointer otherPreamble = otherIt->dynamicCast(); if ((myPreamble.isNull() && !otherPreamble.isNull()) || (!myPreamble.isNull() && otherPreamble.isNull())) return false; if (!myPreamble.isNull() && !otherPreamble.isNull()) { if (myPreamble->operator !=(*otherPreamble.data())) return false; } else { QSharedPointer myComment = myIt->dynamicCast(); QSharedPointer otherComment = otherIt->dynamicCast(); if ((myComment.isNull() && !otherComment.isNull()) || (!myComment.isNull() && otherComment.isNull())) return false; if (!myComment.isNull() && !otherComment.isNull()) { // TODO right now, don't care if comments are equal qCDebug(LOG_KBIBTEX_DATA) << "File objects being compared contain comments, ignoring those"; } else { /// This case should never be reached qCWarning(LOG_KBIBTEX_DATA) << "Met unhandled case while comparing two File objects"; return false; } } } } } return true; } bool File::operator!=(const File &other) const { return !operator ==(other); } const QSharedPointer File::containsKey(const QString &key, ElementTypes elementTypes) const { if (!d->checkValidity()) qCCritical(LOG_KBIBTEX_DATA) << "const QSharedPointer File::containsKey(const QString &key, ElementTypes elementTypes) const" << "This File object is not valid"; for (const auto &element : const_cast(*this)) { const QSharedPointer entry = elementTypes.testFlag(ElementType::Entry) ? element.dynamicCast() : QSharedPointer(); if (!entry.isNull()) { if (entry->id() == key) return entry; } else { const QSharedPointer macro = elementTypes.testFlag(ElementType::Macro) ? element.dynamicCast() : QSharedPointer(); if (!macro.isNull()) { if (macro->key() == key) return macro; } } } return QSharedPointer(); } QStringList File::allKeys(ElementTypes elementTypes) const { if (!d->checkValidity()) qCCritical(LOG_KBIBTEX_DATA) << "QStringList File::allKeys(ElementTypes elementTypes) const" << "This File object is not valid"; QStringList result; result.reserve(size()); for (const auto &element : const_cast(*this)) { const QSharedPointer entry = elementTypes.testFlag(ElementType::Entry) ? element.dynamicCast() : QSharedPointer(); if (!entry.isNull()) result.append(entry->id()); else { const QSharedPointer macro = elementTypes.testFlag(ElementType::Macro) ? element.dynamicCast() : QSharedPointer(); if (!macro.isNull()) result.append(macro->key()); } } return result; } QSet File::uniqueEntryValuesSet(const QString &fieldName) const { if (!d->checkValidity()) qCCritical(LOG_KBIBTEX_DATA) << "QSet File::uniqueEntryValuesSet(const QString &fieldName) const" << "This File object is not valid"; QSet valueSet; const QString lcFieldName = fieldName.toLower(); for (const auto &element : const_cast(*this)) { const QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it) if (it.key().toLower() == lcFieldName) { const auto itValue = it.value(); for (const QSharedPointer &valueItem : itValue) { /// Check if ValueItem to process points to a person const QSharedPointer person = valueItem.dynamicCast(); if (!person.isNull()) { QSet personNameFormattingSet {Preferences::personNameFormatLastFirst, Preferences::personNameFormatFirstLast}; personNameFormattingSet.insert(Preferences::instance().personNameFormat()); /// Add person's name formatted using each of the templates assembled above for (const QString &personNameFormatting : const_cast &>(personNameFormattingSet)) valueSet.insert(Person::transcribePersonName(person.data(), personNameFormatting)); } else { /// Default case: use PlainTextValue::text to translate ValueItem /// to a human-readable text valueSet.insert(PlainTextValue::text(*valueItem)); } } } } return valueSet; } QStringList File::uniqueEntryValuesList(const QString &fieldName) const { if (!d->checkValidity()) qCCritical(LOG_KBIBTEX_DATA) << "QStringList File::uniqueEntryValuesList(const QString &fieldName) const" << "This File object is not valid"; QSet valueSet = uniqueEntryValuesSet(fieldName); +#if QT_VERSION >= 0x050e00 + QStringList list(valueSet.constBegin(), valueSet.constEnd()); ///< This function was introduced in Qt 5.14 +#else // QT_VERSION < 0x050e00 QStringList list = valueSet.toList(); +#endif // QT_VERSION >= 0x050e00 list.sort(); return list; } void File::setProperty(const QString &key, const QVariant &value) { if (!d->checkValidity()) qCCritical(LOG_KBIBTEX_DATA) << "void File::setProperty(const QString &key, const QVariant &value)" << "This File object is not valid"; d->properties.insert(key, value); } QVariant File::property(const QString &key) const { if (!d->checkValidity()) qCCritical(LOG_KBIBTEX_DATA) << "QVariant File::property(const QString &key) const" << "This File object is not valid"; return d->properties.contains(key) ? d->properties.value(key) : QVariant(); } QVariant File::property(const QString &key, const QVariant &defaultValue) const { if (!d->checkValidity()) qCCritical(LOG_KBIBTEX_DATA) << "QVariant File::property(const QString &key, const QVariant &defaultValue) const" << "This File object is not valid"; return d->properties.value(key, defaultValue); } bool File::hasProperty(const QString &key) const { if (!d->checkValidity()) qCCritical(LOG_KBIBTEX_DATA) << "bool File::hasProperty(const QString &key) const" << "This File object is not valid"; return d->properties.contains(key); } void File::setPropertiesToDefault() { if (!d->checkValidity()) qCCritical(LOG_KBIBTEX_DATA) << "void File::setPropertiesToDefault()" << "This File object is not valid"; d->loadConfiguration(); } bool File::checkValidity() const { return d->checkValidity(); } QDebug operator<<(QDebug dbg, const File &file) { dbg.nospace() << "File is " << (file.checkValidity() ? "" : "NOT ") << "valid and has " << file.count() << " members"; return dbg; } diff --git a/src/networking/urlchecker.cpp b/src/networking/urlchecker.cpp index 61ec634d..ea741f83 100644 --- a/src/networking/urlchecker.cpp +++ b/src/networking/urlchecker.cpp @@ -1,184 +1,208 @@ /*************************************************************************** - * Copyright (C) 2004-2019 by Thomas Fischer * + * Copyright (C) 2004-2020 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 "urlchecker.h" #include #include #include #include #include #include #include #include "internalnetworkaccessmanager.h" #include "logging_networking.h" class UrlChecker::Private { private: UrlChecker *p; public: QAtomicInteger busyCounter; QSet urlsToCheck; Private(UrlChecker *parent) : p(parent) { /// nothing } void queueMoreOrFinish() { - if (busyCounter.load() <= 0 && urlsToCheck.isEmpty()) { + if ( +#if QT_VERSION >= 0x050e00 + busyCounter.loadRelaxed() <= 0 ///< This function was introduced in Qt 5.14. +#else // QT_VERSION < 0x050e00 + busyCounter.load() <= 0 +#endif // QT_VERSION >= 0x050e00 + && urlsToCheck.isEmpty()) { /// In case there are no running checks and the queue of URLs to check is empty, /// wait for a brief moment of time, then fire a 'finished' signal. QTimer::singleShot(100, p, [this]() { - if (busyCounter.load() <= 0 && urlsToCheck.isEmpty()) + if ( +#if QT_VERSION >= 0x050e00 + busyCounter.loadRelaxed() <= 0 ///< This function was introduced in Qt 5.14. +#else // QT_VERSION < 0x050e00 + busyCounter.load() <= 0 +#endif // QT_VERSION >= 0x050e00 + && urlsToCheck.isEmpty()) QMetaObject::invokeMethod(p, "finished", Qt::DirectConnection, QGenericReturnArgument()); else /// It should not happen that when this timer is triggered the original condition is violated - qCCritical(LOG_KBIBTEX_NETWORKING) << "This cannot happen:" << busyCounter.load() << urlsToCheck.count(); + qCCritical(LOG_KBIBTEX_NETWORKING) << "This cannot happen:" << +#if QT_VERSION >= 0x050e00 + busyCounter.loadRelaxed() +#else // QT_VERSION < 0x050e00 + busyCounter.load() +#endif // QT_VERSION >= 0x050e00 + << urlsToCheck.count(); }); } else { /// Initiate as many checks as possible - while (!urlsToCheck.isEmpty() && busyCounter.load() <= 4) + while (!urlsToCheck.isEmpty() && +#if QT_VERSION >= 0x050e00 + busyCounter.loadRelaxed() <= 4 ///< This function was introduced in Qt 5.14. +#else // QT_VERSION < 0x050e00 + busyCounter.load() <= 4 +#endif // QT_VERSION >= 0x050e00 + ) checkNextUrl(); } } void checkNextUrl() { /// Immediately return if there are no URLs to check if (urlsToCheck.isEmpty()) return; /// Pop one URL from set of URLS to check auto firstUrlIt = urlsToCheck.begin(); const QUrl url = *firstUrlIt; urlsToCheck.erase(firstUrlIt); QNetworkRequest request(url); request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); busyCounter.ref(); QObject::connect(reply, &QNetworkReply::finished, p, [this, reply]() { const QUrl url = reply->url(); if (reply->error() != QNetworkReply::NoError) { /// Instead of an 'emit' ... QMetaObject::invokeMethod(p, "urlChecked", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(QUrl, url), Q_ARG(UrlChecker::Status, UrlChecker::Status::NetworkError), Q_ARG(QString, reply->errorString())); qCWarning(LOG_KBIBTEX_NETWORKING) << "NetworkError:" << reply->errorString() << url.toDisplayString(); } else { const QByteArray data = reply->read(1024); if (data.isEmpty()) { /// Instead of an 'emit' ... QMetaObject::invokeMethod(p, "urlChecked", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(QUrl, url), Q_ARG(UrlChecker::Status, UrlChecker::Status::UnknownError), Q_ARG(QString, QStringLiteral("No data received"))); qCWarning(LOG_KBIBTEX_NETWORKING) << "UnknownError: No data received" << url.toDisplayString(); } else { const QString filename = url.fileName().toLower(); const bool filenameSuggestsHTML = filename.isEmpty() || filename.endsWith(QStringLiteral(".html")) || filename.endsWith(QStringLiteral(".htm")); const bool filenameSuggestsPDF = filename.endsWith(QStringLiteral(".pdf")); const bool filenameSuggestsPostScript = filename.endsWith(QStringLiteral(".ps")); const bool containsHTML = data.contains(" element : bibtexFile) { /// Process only entries, not comments, preambles or macros QSharedPointer entry = element.dynamicCast(); if (entry.isNull()) continue; /// Retrieve set of URLs per entry and add to set of URLS to be checked const QSet thisEntryUrls = FileInfo::entryUrls(entry, bibtexFile.property(File::Url).toUrl(), FileInfo::TestExistence::No); for (const QUrl &u : thisEntryUrls) d->urlsToCheck.insert(u); ///< better? } if (d->urlsToCheck.isEmpty()) { /// No URLs identified in bibliography, so nothing to do QTimer::singleShot(100, this, [this]() { emit finished(); }); return; } d->queueMoreOrFinish(); } diff --git a/src/program/documentlist.cpp b/src/program/documentlist.cpp index b4c71f3b..50e04830 100644 --- a/src/program/documentlist.cpp +++ b/src/program/documentlist.cpp @@ -1,420 +1,420 @@ /*************************************************************************** - * Copyright (C) 2004-2019 by Thomas Fischer * + * Copyright (C) 2004-2020 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 "documentlist.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class DirOperatorWidget : public QWidget { Q_OBJECT public: KDirOperator *dirOperator; DirOperatorWidget(QWidget *parent) : QWidget(parent) { QGridLayout *layout = new QGridLayout(this); layout->setMargin(0); layout->setColumnStretch(0, 0); layout->setColumnStretch(1, 0); layout->setColumnStretch(2, 1); QPushButton *buttonUp = new QPushButton(QIcon::fromTheme(QStringLiteral("go-up")), QString(), this); buttonUp->setToolTip(i18n("One level up")); layout->addWidget(buttonUp, 0, 0, 1, 1); QPushButton *buttonHome = new QPushButton(QIcon::fromTheme(QStringLiteral("user-home")), QString(), this); buttonHome->setToolTip(i18n("Go to Home folder")); layout->addWidget(buttonHome, 0, 1, 1, 1); dirOperator = new KDirOperator(QUrl(QStringLiteral("file:") + QDir::homePath()), this); layout->addWidget(dirOperator, 1, 0, 1, 3); dirOperator->setView(KFile::Detail); connect(buttonUp, &QPushButton::clicked, dirOperator, &KDirOperator::cdUp); connect(buttonHome, &QPushButton::clicked, dirOperator, &KDirOperator::home); } }; DocumentListDelegate::DocumentListDelegate(QObject *parent) : QStyledItemDelegate(parent) { /// nothing } void DocumentListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { int height = option.rect.height(); QStyle *style = QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); painter->save(); if (option.state & QStyle::State_Selected) { painter->setPen(QPen(option.palette.highlightedText().color())); } else { painter->setPen(QPen(option.palette.text().color())); } OpenFileInfo *ofi = qvariant_cast(index.data(Qt::UserRole)); if (OpenFileInfoManager::instance().currentFile() == ofi) { /// for the currently open file, use a bold font to write file name QFont font = painter->font(); font.setBold(true); painter->setFont(font); } QRect textRect = option.rect; textRect.setLeft(textRect.left() + height + 4); textRect.setHeight(height / 2); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, index.data(Qt::DisplayRole).toString()); textRect = option.rect; textRect.setLeft(textRect.left() + height + 4); textRect.setTop(textRect.top() + height / 2); textRect.setHeight(height * 3 / 8); QFont font = painter->font(); font.setPointSize(font.pointSize() * 7 / 8); painter->setFont(font); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, ofi->fullCaption()); QIcon icon = qvariant_cast(index.data(Qt::DecorationRole)); painter->drawPixmap(option.rect.left() + 1, option.rect.top() + 1, height - 2, height - 2, icon.pixmap(height - 2, height - 2)); painter->restore(); } QSize DocumentListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size = QStyledItemDelegate::sizeHint(option, index); size.setHeight(size.height() * 9 / 4); return size; } class DocumentListModel::DocumentListModelPrivate { public: OpenFileInfo::StatusFlag sf; OpenFileInfoManager::OpenFileInfoList ofiList; public: DocumentListModelPrivate(OpenFileInfo::StatusFlag statusFlag, DocumentListModel *parent) : sf(statusFlag) { Q_UNUSED(parent) } }; DocumentListModel::DocumentListModel(OpenFileInfo::StatusFlag statusFlag, QObject *parent) : QAbstractListModel(parent), d(new DocumentListModel::DocumentListModelPrivate(statusFlag, this)) { listsChanged(d->sf); connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::flagsChanged, this, &DocumentListModel::listsChanged); } DocumentListModel::~DocumentListModel() { delete d; } int DocumentListModel::rowCount(const QModelIndex &parent) const { if (parent != QModelIndex()) return 0; return d->ofiList.count(); } QVariant DocumentListModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= rowCount()) return QVariant(); OpenFileInfo *openFileInfo = d->ofiList[index.row()]; const QString iconName = openFileInfo->mimeType().replace(QLatin1Char('/'), QLatin1Char('-')); switch (role) { case Qt::DisplayRole: return openFileInfo->shortCaption(); case Qt::DecorationRole: { /// determine mime type-based icon and overlays (e.g. for modified files) QStringList overlays; if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::Favorite)) overlays << QStringLiteral("favorites"); else overlays << QString(); if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::RecentlyUsed)) overlays << QStringLiteral("document-open-recent"); else overlays << QString(); if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::Open)) overlays << QStringLiteral("folder-open"); else overlays << QString(); if (openFileInfo->isModified()) overlays << QStringLiteral("document-save"); else overlays << QString(); return KDE::icon(iconName, overlays, nullptr); } case Qt::ToolTipRole: { QString htmlText(QString(QStringLiteral(" %2")).arg(KIconLoader::global()->iconPath(iconName, KIconLoader::Small), openFileInfo->shortCaption())); const QUrl url = openFileInfo->url(); if (url.isValid()) { QString path(QFileInfo(url.path()).path()); if (!path.endsWith(QLatin1Char('/'))) path.append(QLatin1Char('/')); htmlText.append(i18n("
located in %1", path)); } QStringList flagListItems; if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::Favorite)) flagListItems << i18n("Favorite"); if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::RecentlyUsed)) flagListItems << i18n("Recently Used"); if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::Open)) flagListItems << i18n("Open"); if (openFileInfo->isModified()) flagListItems << i18n("Modified"); if (!flagListItems.empty()) { htmlText.append(QStringLiteral("
    ")); for (const QString &flagListItem : const_cast(flagListItems)) { htmlText.append(QString(QStringLiteral("
  • %1
  • ")).arg(flagListItem)); } htmlText.append(QStringLiteral("
")); } htmlText.append(QStringLiteral("
")); return htmlText; } - case Qt::UserRole: return qVariantFromValue(openFileInfo); + case Qt::UserRole: return QVariant::fromValue(openFileInfo); default: return QVariant(); } } QVariant DocumentListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal || section != 0 || role != Qt::DisplayRole) return QVariant(); return QVariant("List of Files"); } void DocumentListModel::listsChanged(OpenFileInfo::StatusFlags statusFlags) { if (statusFlags.testFlag(d->sf)) { beginResetModel(); d->ofiList = OpenFileInfoManager::instance().filteredItems(d->sf); endResetModel(); } } class DocumentListView::DocumentListViewPrivate { private: DocumentListView *p; public: QAction *actionAddToFav, *actionRemFromFav; QAction *actionCloseFile, *actionOpenFile; KActionMenu *actionOpenMenu; QList openMenuActions; DocumentListViewPrivate(DocumentListView *parent) : p(parent), actionAddToFav(nullptr), actionRemFromFav(nullptr), actionCloseFile(nullptr), actionOpenFile(nullptr), actionOpenMenu(nullptr) { /// nothing } void openFileWithService(KService::Ptr service) { const QModelIndex modelIndex = p->currentIndex(); if (modelIndex != QModelIndex()) { OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); if (!ofi->isModified() || (KMessageBox::questionYesNo(p, i18n("The current document has to be saved before switching the viewer/editor component."), i18n("Save before switching?"), KGuiItem(i18n("Save document"), QIcon::fromTheme(QStringLiteral("document-save"))), KGuiItem(i18n("Do not switch"), QIcon::fromTheme(QStringLiteral("dialog-cancel")))) == KMessageBox::Yes && ofi->save())) OpenFileInfoManager::instance().setCurrentFile(ofi, service); } } }; DocumentListView::DocumentListView(OpenFileInfo::StatusFlag statusFlag, QWidget *parent) : QListView(parent), d(new DocumentListViewPrivate(this)) { setContextMenuPolicy(Qt::ActionsContextMenu); setItemDelegate(new DocumentListDelegate(this)); if (statusFlag == OpenFileInfo::StatusFlag::Open) { d->actionCloseFile = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close File"), this); connect(d->actionCloseFile, &QAction::triggered, this, &DocumentListView::closeFile); addAction(d->actionCloseFile); } else { d->actionOpenFile = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open File"), this); connect(d->actionOpenFile, &QAction::triggered, this, &DocumentListView::openFile); addAction(d->actionOpenFile); } d->actionOpenMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open with"), this); addAction(d->actionOpenMenu); if (statusFlag == OpenFileInfo::StatusFlag::Favorite) { d->actionRemFromFav = new QAction(QIcon::fromTheme(QStringLiteral("favorites")), i18n("Remove from Favorites"), this); connect(d->actionRemFromFav, &QAction::triggered, this, &DocumentListView::removeFromFavorites); addAction(d->actionRemFromFav); } else { d->actionAddToFav = new QAction(QIcon::fromTheme(QStringLiteral("favorites")), i18n("Add to Favorites"), this); connect(d->actionAddToFav, &QAction::triggered, this, &DocumentListView::addToFavorites); addAction(d->actionAddToFav); } connect(this, &DocumentListView::activated, this, &DocumentListView::openFile); currentChanged(QModelIndex(), QModelIndex()); } DocumentListView::~DocumentListView() { delete d; } void DocumentListView::addToFavorites() { QModelIndex modelIndex = currentIndex(); if (modelIndex != QModelIndex()) { OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); ofi->addFlags(OpenFileInfo::StatusFlag::Favorite); } } void DocumentListView::removeFromFavorites() { QModelIndex modelIndex = currentIndex(); if (modelIndex != QModelIndex()) { OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); ofi->removeFlags(OpenFileInfo::StatusFlag::Favorite); } } void DocumentListView::openFile() { QModelIndex modelIndex = currentIndex(); if (modelIndex != QModelIndex()) { OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); OpenFileInfoManager::instance().setCurrentFile(ofi); } } void DocumentListView::closeFile() { QModelIndex modelIndex = currentIndex(); if (modelIndex != QModelIndex()) { OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); OpenFileInfoManager::instance().close(ofi); } } void DocumentListView::currentChanged(const QModelIndex ¤t, const QModelIndex &) { bool hasCurrent = current != QModelIndex(); OpenFileInfo *ofi = hasCurrent ? qvariant_cast(current.data(Qt::UserRole)) : nullptr; bool isOpen = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::StatusFlag::Open) : false; bool isFavorite = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::StatusFlag::Favorite) : false; bool hasName = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::StatusFlag::HasName) : false; if (d->actionOpenFile != nullptr) d->actionOpenFile->setEnabled(hasCurrent && !isOpen); if (d->actionCloseFile != nullptr) d->actionCloseFile->setEnabled(hasCurrent && isOpen); if (d->actionAddToFav != nullptr) d->actionAddToFav->setEnabled(hasCurrent && !isFavorite && hasName); if (d->actionRemFromFav != nullptr) d->actionRemFromFav->setEnabled(hasCurrent && isFavorite); for (QAction *action : const_cast &>(d->openMenuActions)) { d->actionOpenMenu->removeAction(action); } if (ofi != nullptr) { const KService::List services = ofi->listOfServices(); for (KService::Ptr servicePtr : services) { QAction *menuItem = new QAction(QIcon::fromTheme(servicePtr->icon()), servicePtr->name(), this); d->actionOpenMenu->addAction(menuItem); d->openMenuActions << menuItem; connect(menuItem, &QAction::triggered, this, [this, servicePtr]() { d->openFileWithService(servicePtr); }); } } d->actionOpenMenu->setEnabled(!d->openMenuActions.isEmpty()); } class DocumentList::DocumentListPrivate { public: DocumentListView *listOpenFiles; DocumentListView *listRecentFiles; DocumentListView *listFavorites; DirOperatorWidget *dirOperator; DocumentListPrivate(DocumentList *p) { listOpenFiles = new DocumentListView(OpenFileInfo::StatusFlag::Open, p); DocumentListModel *model = new DocumentListModel(OpenFileInfo::StatusFlag::Open, listOpenFiles); listOpenFiles->setModel(model); p->addTab(listOpenFiles, QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open Files")); listRecentFiles = new DocumentListView(OpenFileInfo::StatusFlag::RecentlyUsed, p); model = new DocumentListModel(OpenFileInfo::StatusFlag::RecentlyUsed, listRecentFiles); listRecentFiles->setModel(model); p->addTab(listRecentFiles, QIcon::fromTheme(QStringLiteral("document-open-recent")), i18n("Recently Used")); listFavorites = new DocumentListView(OpenFileInfo::StatusFlag::Favorite, p); model = new DocumentListModel(OpenFileInfo::StatusFlag::Favorite, listFavorites); listFavorites->setModel(model); p->addTab(listFavorites, QIcon::fromTheme(QStringLiteral("favorites")), i18n("Favorites")); dirOperator = new DirOperatorWidget(p); p->addTab(dirOperator, QIcon::fromTheme(QStringLiteral("system-file-manager")), i18n("Filesystem Browser")); connect(dirOperator->dirOperator, &KDirOperator::fileSelected, p, &DocumentList::fileSelected); } }; DocumentList::DocumentList(QWidget *parent) : QTabWidget(parent), d(new DocumentListPrivate(this)) { setDocumentMode(true); } void DocumentList::fileSelected(const KFileItem &item) { if (item.isFile() && item.isReadable()) emit openFile(item.url()); } #include "documentlist.moc"