File indexing completed on 2024-05-19 05:05:52
0001 /*************************************************************************** 0002 * SPDX-License-Identifier: GPL-2.0-or-later 0003 * * 0004 * SPDX-FileCopyrightText: 2004-2023 Thomas Fischer <fischer@unix-ag.uni-kl.de> 0005 * * 0006 * This program is free software; you can redistribute it and/or modify * 0007 * it under the terms of the GNU General Public License as published by * 0008 * the Free Software Foundation; either version 2 of the License, or * 0009 * (at your option) any later version. * 0010 * * 0011 * This program is distributed in the hope that it will be useful, * 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0014 * GNU General Public License for more details. * 0015 * * 0016 * You should have received a copy of the GNU General Public License * 0017 * along with this program; if not, see <https://www.gnu.org/licenses/>. * 0018 ***************************************************************************/ 0019 0020 #include "openfileinfo.h" 0021 0022 #include <QString> 0023 #include <QTimer> 0024 #include <QFileInfo> 0025 #include <QWidget> 0026 #include <QUrl> 0027 #include <QApplication> 0028 0029 #include <KLocalizedString> 0030 #include <KConfig> 0031 #include <KConfigGroup> 0032 #include <KSharedConfig> 0033 #include <KParts/Part> 0034 #include <KParts/ReadOnlyPart> 0035 #include <KParts/ReadWritePart> 0036 #include <KParts/PartLoader> 0037 #include <kparts_version.h> 0038 0039 #ifdef HAVE_POPPLERQT 0040 #include <FileImporterPDF> 0041 #endif // HAVE_POPPLERQT 0042 #include "logging_program.h" 0043 0044 #if KPARTS_VERSION < QT_VERSION_CHECK(5, 100, 0) 0045 // Copied from kparts/partloader.h 0046 namespace KParts::PartLoader { 0047 template<typename T> 0048 static KPluginFactory::Result<T> 0049 instantiatePart(const KPluginMetaData &data, QWidget *parentWidget = nullptr, QObject *parent = nullptr, const QVariantList &args = {}) 0050 { 0051 KPluginFactory::Result<T> result; 0052 KPluginFactory::Result<KPluginFactory> factoryResult = KPluginFactory::loadFactory(data); 0053 if (!factoryResult.plugin) { 0054 result.errorString = factoryResult.errorString; 0055 result.errorReason = factoryResult.errorReason; 0056 return result; 0057 } 0058 T *instance = factoryResult.plugin->create<T>(parentWidget, parent, args); 0059 if (!instance) { 0060 const QString fileName = data.fileName(); 0061 result.errorString = QObject::tr("KPluginFactory could not load the plugin: %1").arg(fileName); 0062 result.errorText = QStringLiteral("KPluginFactory could not load the plugin: %1").arg(fileName); 0063 result.errorReason = KPluginFactory::INVALID_KPLUGINFACTORY_INSTANTIATION; 0064 } else { 0065 result.plugin = instance; 0066 } 0067 return result; 0068 } 0069 } 0070 #endif 0071 0072 class OpenFileInfo::OpenFileInfoPrivate 0073 { 0074 private: 0075 static int globalCounter; 0076 int m_counter; 0077 0078 public: 0079 static const QString keyLastAccess; 0080 static const QString keyURL; 0081 static const QString dateTimeFormat; 0082 0083 OpenFileInfo *p; 0084 0085 KParts::ReadOnlyPart *part; 0086 KPluginMetaData currentPart; 0087 QWidget *internalWidgetParent; 0088 QDateTime lastAccessDateTime; 0089 StatusFlags flags; 0090 OpenFileInfoManager *openFileInfoManager; 0091 QString mimeType; 0092 QUrl url; 0093 0094 OpenFileInfoPrivate(OpenFileInfoManager *openFileInfoManager, const QUrl &url, const QString &mimeType, OpenFileInfo *p) 0095 : m_counter(-1), p(p), part(nullptr), internalWidgetParent(nullptr) { 0096 this->openFileInfoManager = openFileInfoManager; 0097 this->url = url; 0098 if (this->url.isValid() && this->url.scheme().isEmpty()) 0099 qCWarning(LOG_KBIBTEX_PROGRAM) << "No scheme specified for URL" << this->url.toDisplayString(); 0100 this->mimeType = mimeType; 0101 } 0102 0103 ~OpenFileInfoPrivate() { 0104 if (part != nullptr) { 0105 KParts::ReadWritePart *rwp = qobject_cast<KParts::ReadWritePart *>(part); 0106 if (rwp != nullptr) 0107 rwp->closeUrl(true); 0108 delete part; 0109 } 0110 } 0111 0112 KParts::ReadOnlyPart *createPart(QWidget *newWidgetParent, const KPluginMetaData &_partMetaData = {}) { 0113 0114 KPluginMetaData partMetaData = _partMetaData; 0115 0116 if (!p->flags().testFlag(OpenFileInfo::StatusFlag::Open)) { 0117 qCWarning(LOG_KBIBTEX_PROGRAM) << "Cannot create part for a file which is not open"; 0118 return nullptr; 0119 } 0120 0121 Q_ASSERT_X(internalWidgetParent == nullptr || internalWidgetParent == newWidgetParent, "KParts::ReadOnlyPart *OpenFileInfo::OpenFileInfoPrivate::createPart(QWidget *newWidgetParent, const KPluginMetaData & = {})", "internal widget should be either NULL or the same one as supplied as \"newWidgetParent\""); 0122 0123 /** use cached part for this parent if possible */ 0124 if (internalWidgetParent == newWidgetParent && (!partMetaData.isValid() || currentPart == partMetaData)) { 0125 Q_ASSERT_X(part != nullptr, "KParts::ReadOnlyPart *OpenFileInfo::OpenFileInfoPrivate::createPart(QWidget *newWidgetParent, const KPluginMetaData & = {})", "Part is NULL"); 0126 return part; 0127 } else if (part != nullptr) { 0128 KParts::ReadWritePart *rwp = qobject_cast<KParts::ReadWritePart *>(part); 0129 if (rwp != nullptr) 0130 rwp->closeUrl(true); 0131 part->deleteLater(); 0132 part = nullptr; 0133 } 0134 0135 /// reset to invalid values in case something goes wrong 0136 currentPart = {}; 0137 internalWidgetParent = nullptr; 0138 0139 if (!partMetaData.isValid()) { 0140 /// no valid KPartMetaData has been passed 0141 /// try to find a read-write part to open file 0142 partMetaData = p->defaultService(); 0143 } 0144 if (!partMetaData.isValid()) { 0145 qCDebug(LOG_KBIBTEX_PROGRAM) << "PATH=" << getenv("PATH"); 0146 qCDebug(LOG_KBIBTEX_PROGRAM) << "LD_LIBRARY_PATH=" << getenv("LD_LIBRARY_PATH"); 0147 qCDebug(LOG_KBIBTEX_PROGRAM) << "XDG_DATA_DIRS=" << getenv("XDG_DATA_DIRS"); 0148 qCDebug(LOG_KBIBTEX_PROGRAM) << "QT_PLUGIN_PATH=" << getenv("QT_PLUGIN_PATH"); 0149 qCDebug(LOG_KBIBTEX_PROGRAM) << "KDEDIRS=" << getenv("KDEDIRS"); 0150 qCCritical(LOG_KBIBTEX_PROGRAM) << "Cannot find part to handle mimetype " << mimeType; 0151 return nullptr; 0152 } 0153 0154 QString errorString; 0155 0156 const auto loadResult = KParts::PartLoader::instantiatePart<KParts::ReadWritePart>(partMetaData, newWidgetParent, qobject_cast<QObject *>(newWidgetParent)); 0157 0158 errorString = loadResult.errorString; 0159 part = loadResult.plugin; 0160 0161 if (part == nullptr) { 0162 qCDebug(LOG_KBIBTEX_PROGRAM) << "name:" << partMetaData.name(); 0163 qCDebug(LOG_KBIBTEX_PROGRAM) << "version:" << partMetaData.version(); 0164 qCDebug(LOG_KBIBTEX_PROGRAM) << "category:" << partMetaData.category(); 0165 qCDebug(LOG_KBIBTEX_PROGRAM) << "description:" << partMetaData.description(); 0166 qCDebug(LOG_KBIBTEX_PROGRAM) << "fileName:" << partMetaData.fileName(); 0167 qCDebug(LOG_KBIBTEX_PROGRAM) << "PATH=" << getenv("PATH"); 0168 qCDebug(LOG_KBIBTEX_PROGRAM) << "LD_LIBRARY_PATH=" << getenv("LD_LIBRARY_PATH"); 0169 qCDebug(LOG_KBIBTEX_PROGRAM) << "XDG_DATA_DIRS=" << getenv("XDG_DATA_DIRS"); 0170 qCDebug(LOG_KBIBTEX_PROGRAM) << "QT_PLUGIN_PATH=" << getenv("QT_PLUGIN_PATH"); 0171 qCDebug(LOG_KBIBTEX_PROGRAM) << "KDEDIRS=" << getenv("KDEDIRS"); 0172 qCWarning(LOG_KBIBTEX_PROGRAM) << "Could not instantiate read-write part for" << partMetaData.name() << "(mimeType=" << mimeType << ", library=" << partMetaData.fileName() << ", error msg=" << errorString << ")"; 0173 /// creating a read-write part failed, so maybe it is read-only (like Okular's PDF viewer)? 0174 const auto loadResult = KParts::PartLoader::instantiatePart<KParts::ReadOnlyPart>(partMetaData, newWidgetParent, qobject_cast<QObject *>(newWidgetParent)); 0175 errorString = loadResult.errorString; 0176 part = loadResult.plugin; 0177 } 0178 if (part == nullptr) { 0179 /// still cannot create part, must be error 0180 qCCritical(LOG_KBIBTEX_PROGRAM) << "Could not instantiate part for" << partMetaData.name() << "(mimeType=" << mimeType << ", library=" << partMetaData.fileName() << ", error msg=" << errorString << ")"; 0181 return nullptr; 0182 } 0183 0184 if (url.isValid()) { 0185 /// open URL in part 0186 part->openUrl(url); 0187 /// update document list widget accordingly 0188 p->addFlags(OpenFileInfo::StatusFlag::RecentlyUsed); 0189 p->addFlags(OpenFileInfo::StatusFlag::HasName); 0190 } else { 0191 /// initialize part with empty document 0192 part->openUrl(QUrl()); 0193 } 0194 p->addFlags(OpenFileInfo::StatusFlag::Open); 0195 0196 currentPart = partMetaData; 0197 internalWidgetParent = newWidgetParent; 0198 0199 Q_ASSERT_X(part != nullptr, "KParts::ReadOnlyPart *OpenFileInfo::OpenFileInfoPrivate::createPart(QWidget *newWidgetParent, const KPluginMetaData & = {})", "Creation of part failed, is NULL"); /// test should not be necessary, but just to be save ... 0200 return part; 0201 } 0202 0203 int counter() { 0204 if (!url.isValid() && m_counter < 0) 0205 m_counter = ++globalCounter; 0206 else if (url.isValid()) 0207 qCWarning(LOG_KBIBTEX_PROGRAM) << "This function should not be called if URL is valid"; 0208 return m_counter; 0209 } 0210 0211 }; 0212 0213 int OpenFileInfo::OpenFileInfoPrivate::globalCounter = 0; 0214 const QString OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat = QStringLiteral("yyyy-MM-dd-hh-mm-ss-zzz"); 0215 const QString OpenFileInfo::OpenFileInfoPrivate::keyLastAccess = QStringLiteral("LastAccess"); 0216 const QString OpenFileInfo::OpenFileInfoPrivate::keyURL = QStringLiteral("URL"); 0217 0218 OpenFileInfo::OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const QUrl &url) 0219 : d(new OpenFileInfoPrivate(openFileInfoManager, url, FileInfo::mimeTypeForUrl(url).name(), this)) 0220 { 0221 /// nothing 0222 } 0223 0224 OpenFileInfo::OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const QString &mimeType) 0225 : d(new OpenFileInfoPrivate(openFileInfoManager, QUrl(), mimeType, this)) 0226 { 0227 /// nothing 0228 } 0229 0230 OpenFileInfo::~OpenFileInfo() 0231 { 0232 delete d; 0233 } 0234 0235 void OpenFileInfo::setUrl(const QUrl &url) 0236 { 0237 Q_ASSERT_X(url.isValid(), "void OpenFileInfo::setUrl(const QUrl&)", "URL is not valid"); 0238 d->url = url; 0239 if (d->url.scheme().isEmpty()) 0240 qCWarning(LOG_KBIBTEX_PROGRAM) << "No scheme specified for URL" << d->url.toDisplayString(); 0241 d->mimeType = FileInfo::mimeTypeForUrl(url).name(); 0242 addFlags(OpenFileInfo::StatusFlag::HasName); 0243 } 0244 0245 QUrl OpenFileInfo::url() const 0246 { 0247 return d->url; 0248 } 0249 0250 bool OpenFileInfo::isModified() const 0251 { 0252 KParts::ReadWritePart *rwPart = qobject_cast< KParts::ReadWritePart *>(d->part); 0253 if (rwPart == nullptr) 0254 return false; 0255 else 0256 return rwPart->isModified(); 0257 } 0258 0259 bool OpenFileInfo::save() 0260 { 0261 KParts::ReadWritePart *rwPart = qobject_cast< KParts::ReadWritePart *>(d->part); 0262 if (rwPart == nullptr) 0263 return true; 0264 else 0265 return rwPart->save(); 0266 } 0267 0268 bool OpenFileInfo::close() 0269 { 0270 if (d->part == nullptr) { 0271 /// if there is no part, closing always "succeeds" 0272 return true; 0273 } 0274 0275 KParts::ReadWritePart *rwp = qobject_cast<KParts::ReadWritePart *>(d->part); 0276 if (rwp == nullptr || rwp->closeUrl(true)) { 0277 d->part->deleteLater(); 0278 d->part = nullptr; 0279 d->internalWidgetParent = nullptr; 0280 return true; 0281 } 0282 return false; 0283 } 0284 0285 QString OpenFileInfo::mimeType() const 0286 { 0287 return d->mimeType; 0288 } 0289 0290 QString OpenFileInfo::shortCaption() const 0291 { 0292 if (d->url.isValid()) 0293 return d->url.fileName(); 0294 else 0295 return i18n("Unnamed-%1", d->counter()); 0296 } 0297 0298 QString OpenFileInfo::fullCaption() const 0299 { 0300 if (d->url.isValid()) 0301 return d->url.url(QUrl::PreferLocalFile); 0302 else 0303 return shortCaption(); 0304 } 0305 0306 KParts::ReadOnlyPart *OpenFileInfo::part(QWidget *parent, const KPluginMetaData &service) 0307 { 0308 return d->createPart(parent, service); 0309 } 0310 0311 OpenFileInfo::StatusFlags OpenFileInfo::flags() const 0312 { 0313 return d->flags; 0314 } 0315 0316 void OpenFileInfo::setFlags(StatusFlags statusFlags) 0317 { 0318 /// disallow files without name or valid url to become favorites 0319 if (!d->url.isValid() || !d->flags.testFlag(StatusFlag::HasName)) statusFlags &= ~static_cast<int>(StatusFlag::Favorite); 0320 /// files that got opened are by definition recently used files 0321 if (!d->url.isValid() && d->flags.testFlag(StatusFlag::Open)) statusFlags &= StatusFlag::RecentlyUsed; 0322 0323 bool hasChanged = d->flags != statusFlags; 0324 d->flags = statusFlags; 0325 if (hasChanged) 0326 Q_EMIT flagsChanged(statusFlags); 0327 } 0328 0329 void OpenFileInfo::addFlags(StatusFlags statusFlags) 0330 { 0331 /// disallow files without name or valid url to become favorites 0332 if (!d->url.isValid() || !d->flags.testFlag(StatusFlag::HasName)) statusFlags &= ~static_cast<int>(StatusFlag::Favorite); 0333 0334 bool hasChanged = (~d->flags & statusFlags) > 0; 0335 d->flags |= statusFlags; 0336 if (hasChanged) 0337 Q_EMIT flagsChanged(statusFlags); 0338 } 0339 0340 void OpenFileInfo::removeFlags(StatusFlags statusFlags) 0341 { 0342 bool hasChanged = (d->flags & statusFlags) > 0; 0343 d->flags &= ~statusFlags; 0344 if (hasChanged) 0345 Q_EMIT flagsChanged(statusFlags); 0346 } 0347 0348 QDateTime OpenFileInfo::lastAccess() const 0349 { 0350 return d->lastAccessDateTime; 0351 } 0352 0353 void OpenFileInfo::setLastAccess(const QDateTime &dateTime) 0354 { 0355 d->lastAccessDateTime = dateTime; 0356 Q_EMIT flagsChanged(OpenFileInfo::StatusFlag::RecentlyUsed); 0357 } 0358 0359 QVector<KPluginMetaData> OpenFileInfo::listOfServices() 0360 { 0361 const QString mt = mimeType(); 0362 0363 QVector<KPluginMetaData> result = KParts::PartLoader::partsForMimeType(mt); 0364 0365 // Always include KBibTeX's KPart in list of services: 0366 // First, check if KBibTeX's KPart is already in list 0367 auto it = std::find_if(result.cbegin(), result.cend(), [](const KPluginMetaData &md){ 0368 return md.pluginId() == QStringLiteral("kbibtexpart"); 0369 }); 0370 // If not, insert it 0371 if (it == result.cend()) { 0372 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 0373 auto kbibtexpart {KPluginMetaData(QStringLiteral("kbibtexpart"))}; 0374 #else // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0375 auto kbibtexpart {KPluginMetaData::findPluginById(QStringLiteral("kf6/parts"), QStringLiteral("kbibtexpart"))}; 0376 #endif // (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 0377 if (kbibtexpart.isValid()) 0378 result << kbibtexpart; 0379 } 0380 0381 return result; 0382 } 0383 0384 KPluginMetaData OpenFileInfo::defaultService() 0385 { 0386 const QString mt = mimeType(); 0387 0388 KPluginMetaData result; 0389 static const QSet<QString> supportedMimeTypes{ 0390 QStringLiteral("text/x-bibtex"), 0391 #ifdef HAVE_POPPLERQT5 0392 QStringLiteral("application/pdf"), 0393 #endif // HAVE_POPPLERQT5 0394 QStringLiteral("application/x-endnote-refer"), QStringLiteral("application/x-isi-export-format"), QStringLiteral("text/x-research-info-systems"), 0395 QStringLiteral("application/xml") 0396 }; 0397 if (supportedMimeTypes.contains(mt)) { 0398 // If the mime type is natively supported by KBibTeX's part, enforce using this part 0399 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 0400 result = KPluginMetaData(QStringLiteral("kbibtexpart")); 0401 #else // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0402 result = KPluginMetaData::findPluginById(QStringLiteral("kf6/parts"), QStringLiteral("kbibtexpart")); 0403 #endif // (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 0404 } 0405 0406 if (!result.isValid()) { 0407 // If above test for supported mime types failed 0408 // or no valid KPluginMetaData could be instanciated for 'kbibtexpart', 0409 // pick any of the other available parts supporting the requested mime type 0410 0411 const QVector<KPluginMetaData> parts = listOfServices(); 0412 if (!parts.isEmpty()) 0413 result = parts.first(); 0414 } 0415 0416 if (result.isValid()) 0417 qCDebug(LOG_KBIBTEX_PROGRAM) << "Using" << result.name() << "(" << result.description() << ") for mime type" << mt << "through library" << result.fileName(); 0418 else 0419 qCWarning(LOG_KBIBTEX_PROGRAM) << "Could not find part for mime type" << mt; 0420 return result; 0421 } 0422 0423 KPluginMetaData OpenFileInfo::currentService() 0424 { 0425 return d->currentPart; 0426 } 0427 0428 class OpenFileInfoManager::OpenFileInfoManagerPrivate 0429 { 0430 private: 0431 static const QString configGroupNameRecentlyUsed; 0432 static const QString configGroupNameFavorites; 0433 static const QString configGroupNameOpen; 0434 static const int maxNumRecentlyUsedFiles, maxNumFavoriteFiles, maxNumOpenFiles; 0435 0436 public: 0437 OpenFileInfoManager *p; 0438 0439 OpenFileInfoManager::OpenFileInfoList openFileInfoList; 0440 OpenFileInfo *currentFileInfo; 0441 0442 OpenFileInfoManagerPrivate(OpenFileInfoManager *parent) 0443 : p(parent), currentFileInfo(nullptr) { 0444 /// nothing 0445 } 0446 0447 ~OpenFileInfoManagerPrivate() { 0448 for (OpenFileInfoManager::OpenFileInfoList::Iterator it = openFileInfoList.begin(); it != openFileInfoList.end();) { 0449 OpenFileInfo *ofi = *it; 0450 delete ofi; 0451 it = openFileInfoList.erase(it); 0452 } 0453 } 0454 0455 static bool byNameLessThan(const OpenFileInfo *left, const OpenFileInfo *right) { 0456 return left->shortCaption() < right->shortCaption(); 0457 } 0458 0459 static bool byLRULessThan(const OpenFileInfo *left, const OpenFileInfo *right) { 0460 return left->lastAccess() > right->lastAccess(); /// reverse sorting! 0461 } 0462 0463 void readConfig(QUrl &recentlyOpenURL) { 0464 readConfig(OpenFileInfo::StatusFlag::RecentlyUsed, configGroupNameRecentlyUsed, maxNumRecentlyUsedFiles); 0465 readConfig(OpenFileInfo::StatusFlag::Favorite, configGroupNameFavorites, maxNumFavoriteFiles); 0466 readConfig(OpenFileInfo::StatusFlag::Open, configGroupNameOpen, maxNumOpenFiles); 0467 0468 KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kbibtexrc")); 0469 KConfigGroup cg(config, configGroupNameOpen); 0470 static const QString recentlyOpenURLkey = OpenFileInfo::OpenFileInfoPrivate::keyURL + QStringLiteral("-current"); 0471 recentlyOpenURL = cg.hasKey(recentlyOpenURLkey) ? QUrl(cg.readEntry(recentlyOpenURLkey)) : QUrl(); 0472 } 0473 0474 void writeConfig() { 0475 writeConfig(OpenFileInfo::StatusFlag::RecentlyUsed, configGroupNameRecentlyUsed, maxNumRecentlyUsedFiles); 0476 writeConfig(OpenFileInfo::StatusFlag::Favorite, configGroupNameFavorites, maxNumFavoriteFiles); 0477 writeConfig(OpenFileInfo::StatusFlag::Open, configGroupNameOpen, maxNumOpenFiles); 0478 0479 KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kbibtexrc")); 0480 KConfigGroup cg(config, configGroupNameOpen); 0481 static const QString recentlyOpenURLkey = OpenFileInfo::OpenFileInfoPrivate::keyURL + QStringLiteral("-current"); 0482 if (currentFileInfo != nullptr && currentFileInfo->url().isValid()) 0483 /// If there is a currently active and open file, remember that file's URL 0484 cg.writeEntry(recentlyOpenURLkey, currentFileInfo->url().url(QUrl::PreferLocalFile)); 0485 else 0486 cg.deleteEntry(recentlyOpenURLkey); 0487 } 0488 0489 void readConfig(OpenFileInfo::StatusFlag statusFlag, const QString &configGroupName, int maxNumFiles) { 0490 KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kbibtexrc")); 0491 0492 KConfigGroup cg(config, configGroupName); 0493 for (int i = 0; i < maxNumFiles; ++i) { 0494 QUrl fileUrl = QUrl(cg.readEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i), "")); 0495 if (!fileUrl.isValid()) break; 0496 if (fileUrl.scheme().isEmpty()) 0497 fileUrl.setScheme(QStringLiteral("file")); 0498 0499 /// For local files, test if they exist; ignore local files that do not exist 0500 if (fileUrl.isLocalFile()) { 0501 if (!QFileInfo::exists(fileUrl.toLocalFile())) 0502 continue; 0503 } 0504 0505 OpenFileInfo *ofi = p->contains(fileUrl); 0506 if (ofi == nullptr) { 0507 ofi = p->open(fileUrl); 0508 } 0509 ofi->addFlags(statusFlag); 0510 ofi->addFlags(OpenFileInfo::StatusFlag::HasName); 0511 ofi->setLastAccess(QDateTime::fromString(cg.readEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i), ""), OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat)); 0512 } 0513 } 0514 0515 void writeConfig(OpenFileInfo::StatusFlag statusFlag, const QString &configGroupName, int maxNumFiles) { 0516 KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kbibtexrc")); 0517 KConfigGroup cg(config, configGroupName); 0518 OpenFileInfoManager::OpenFileInfoList list = p->filteredItems(statusFlag); 0519 0520 int i = 0; 0521 for (OpenFileInfoManager::OpenFileInfoList::ConstIterator it = list.constBegin(); i < maxNumFiles && it != list.constEnd(); ++it, ++i) { 0522 OpenFileInfo *ofi = *it; 0523 0524 cg.writeEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i), ofi->url().url(QUrl::PreferLocalFile)); 0525 cg.writeEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i), ofi->lastAccess().toString(OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat)); 0526 } 0527 for (; i < maxNumFiles; ++i) { 0528 cg.deleteEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i)); 0529 cg.deleteEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i)); 0530 } 0531 config->sync(); 0532 } 0533 }; 0534 0535 const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameRecentlyUsed = QStringLiteral("DocumentList-RecentlyUsed"); 0536 const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameFavorites = QStringLiteral("DocumentList-Favorites"); 0537 const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameOpen = QStringLiteral("DocumentList-Open"); 0538 const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumFavoriteFiles = 256; 0539 const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumRecentlyUsedFiles = 8; 0540 const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumOpenFiles = 16; 0541 0542 OpenFileInfoManager::OpenFileInfoManager(QObject *parent) 0543 : QObject(parent), d(new OpenFileInfoManagerPrivate(this)) 0544 { 0545 QUrl recentlyOpenURL; 0546 d->readConfig(recentlyOpenURL); 0547 0548 QTimer::singleShot(250, this, [this, recentlyOpenURL]() { 0549 /// In case there is at least one file marked as 'open' but it is not yet actually open, 0550 /// set it as current file now. The file to be opened (identified by URL) should be 0551 /// preferably the file that was actively open at the end of last KBibTeX session. 0552 /// Slightly delaying the actually opening of files is necessary to give precendence 0553 /// to bibliography files passed as command line arguments (see program.cpp) over files 0554 /// that where open when the previous KBibTeX session was quit. 0555 /// See KDE bug 417164. 0556 /// TODO still necessary to refactor the whole 'file opening' control flow to avoid hacks like this one 0557 if (d->currentFileInfo == nullptr) { 0558 OpenFileInfo *ofiToUse = nullptr; 0559 /// Go over the list of files that are flagged as 'open' ... 0560 for (auto *ofi : const_cast<const OpenFileInfoManager::OpenFileInfoList &>(d->openFileInfoList)) 0561 if (ofi->flags().testFlag(OpenFileInfo::StatusFlag::Open) && (ofiToUse == nullptr || (recentlyOpenURL.isValid() && ofi->url() == recentlyOpenURL))) 0562 ofiToUse = ofi; 0563 if (ofiToUse != nullptr) 0564 setCurrentFile(ofiToUse); 0565 } 0566 }); 0567 } 0568 0569 OpenFileInfoManager &OpenFileInfoManager::instance() { 0570 /// Allocate this singleton on heap not stack like most other singletons 0571 /// Supposedly, QCoreApplication will clean this singleton at application's end 0572 static OpenFileInfoManager *singleton = new OpenFileInfoManager(QCoreApplication::instance()); 0573 return *singleton; 0574 } 0575 0576 OpenFileInfoManager::~OpenFileInfoManager() 0577 { 0578 delete d; 0579 } 0580 0581 OpenFileInfo *OpenFileInfoManager::createNew(const QString &mimeType) 0582 { 0583 OpenFileInfo *result = new OpenFileInfo(this, mimeType); 0584 connect(result, &OpenFileInfo::flagsChanged, this, &OpenFileInfoManager::flagsChanged); 0585 d->openFileInfoList << result; 0586 result->setLastAccess(); 0587 return result; 0588 } 0589 0590 OpenFileInfo *OpenFileInfoManager::open(const QUrl &url) 0591 { 0592 Q_ASSERT_X(url.isValid(), "OpenFileInfo *OpenFileInfoManager::open(const QUrl&)", "URL is not valid"); 0593 0594 OpenFileInfo *result = contains(url); 0595 if (result == nullptr) { 0596 /// file not yet open 0597 result = new OpenFileInfo(this, url); 0598 connect(result, &OpenFileInfo::flagsChanged, this, &OpenFileInfoManager::flagsChanged); 0599 d->openFileInfoList << result; 0600 } /// else: file was already open, re-use and return existing OpenFileInfo pointer 0601 result->setLastAccess(); 0602 return result; 0603 } 0604 0605 OpenFileInfo *OpenFileInfoManager::contains(const QUrl &url) const 0606 { 0607 if (!url.isValid()) return nullptr; /// can only be unnamed file 0608 0609 for (auto *ofi : const_cast<const OpenFileInfoManager::OpenFileInfoList &>(d->openFileInfoList)) { 0610 if (ofi->url() == url) 0611 return ofi; 0612 } 0613 return nullptr; 0614 } 0615 0616 bool OpenFileInfoManager::changeUrl(OpenFileInfo *openFileInfo, const QUrl &url) 0617 { 0618 OpenFileInfo *previouslyContained = contains(url); 0619 0620 /// check if old url differs from new url and old url is valid 0621 if (previouslyContained != nullptr && previouslyContained->flags().testFlag(OpenFileInfo::StatusFlag::Open) && previouslyContained != openFileInfo) { 0622 qCWarning(LOG_KBIBTEX_PROGRAM) << "Open file with same URL already exists, forcefully closing it"; 0623 close(previouslyContained); 0624 } 0625 0626 QUrl oldUrl = openFileInfo->url(); 0627 openFileInfo->setUrl(url); 0628 0629 if (url != oldUrl && oldUrl.isValid()) { 0630 /// current document was most probabily renamed (e.g. due to "Save As") 0631 /// add old URL to recently used files, but exclude the open files list 0632 OpenFileInfo *ofi = open(oldUrl); // krazy:exclude=syscalls 0633 OpenFileInfo::StatusFlags statusFlags = (openFileInfo->flags() & ~static_cast<int>(OpenFileInfo::StatusFlag::Open)) | OpenFileInfo::StatusFlag::RecentlyUsed; 0634 ofi->setFlags(statusFlags); 0635 } 0636 if (previouslyContained != nullptr) { 0637 /// keep Favorite flag if set in file that have previously same URL 0638 if (previouslyContained->flags().testFlag(OpenFileInfo::StatusFlag::Favorite)) 0639 openFileInfo->setFlags(openFileInfo->flags() | OpenFileInfo::StatusFlag::Favorite); 0640 0641 /// remove the old entry with the same url has it will be replaced by the new one 0642 d->openFileInfoList.remove(d->openFileInfoList.indexOf(previouslyContained)); 0643 previouslyContained->deleteLater(); 0644 OpenFileInfo::StatusFlags statusFlags = OpenFileInfo::StatusFlag::Open; 0645 statusFlags |= OpenFileInfo::StatusFlag::RecentlyUsed; 0646 statusFlags |= OpenFileInfo::StatusFlag::Favorite; 0647 Q_EMIT flagsChanged(statusFlags); 0648 } 0649 0650 if (openFileInfo == d->currentFileInfo) 0651 Q_EMIT currentChanged(openFileInfo, {}); 0652 Q_EMIT flagsChanged(openFileInfo->flags()); 0653 0654 return true; 0655 } 0656 0657 bool OpenFileInfoManager::close(OpenFileInfo *openFileInfo) 0658 { 0659 if (openFileInfo == nullptr) { 0660 qCWarning(LOG_KBIBTEX_PROGRAM) << "void OpenFileInfoManager::close(OpenFileInfo *openFileInfo): Cannot close openFileInfo which is NULL"; 0661 return false; 0662 } 0663 0664 bool isClosing = false; 0665 openFileInfo->setLastAccess(); 0666 0667 /// remove flag "open" from file to be closed and determine which file to show instead 0668 OpenFileInfo *nextCurrent = (d->currentFileInfo == openFileInfo) ? nullptr : d->currentFileInfo; 0669 for (OpenFileInfo *ofi : const_cast<const OpenFileInfoManager::OpenFileInfoList &>(d->openFileInfoList)) { 0670 if (!isClosing && ofi == openFileInfo && openFileInfo->close()) { 0671 isClosing = true; 0672 /// Mark file as closed (i.e. not open) 0673 openFileInfo->removeFlags(OpenFileInfo::StatusFlag::Open); 0674 /// If file has a filename, remember as recently used 0675 if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::HasName)) 0676 openFileInfo->addFlags(OpenFileInfo::StatusFlag::RecentlyUsed); 0677 } else if (nextCurrent == nullptr && ofi->flags().testFlag(OpenFileInfo::StatusFlag::Open)) 0678 nextCurrent = ofi; 0679 } 0680 0681 /// If the current document is to be closed, 0682 /// switch over to the next available one 0683 if (isClosing) 0684 setCurrentFile(nextCurrent); 0685 0686 return isClosing; 0687 } 0688 0689 bool OpenFileInfoManager::queryCloseAll() 0690 { 0691 /// Assume that all closing operations succeed 0692 bool isClosing = true; 0693 /// For keeping track of files that get closed here 0694 OpenFileInfoList restoreLaterList; 0695 0696 /// For each file known ... 0697 for (OpenFileInfo *openFileInfo : const_cast<const OpenFileInfoManager::OpenFileInfoList &>(d->openFileInfoList)) { 0698 /// Check only open file (ignore recently used, favorites, ...) 0699 if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::Open)) { 0700 if (openFileInfo->close()) { 0701 /// If file could be closed without user canceling the operation ... 0702 /// Mark file as closed (i.e. not open) 0703 openFileInfo->removeFlags(OpenFileInfo::StatusFlag::Open); 0704 /// If file has a filename, remember as recently used 0705 if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::HasName)) 0706 openFileInfo->addFlags(OpenFileInfo::StatusFlag::RecentlyUsed); 0707 /// Remember file as to be marked as open later 0708 restoreLaterList.append(openFileInfo); 0709 } else { 0710 /// User chose to cancel closing operation, 0711 /// stop everything here 0712 isClosing = false; 0713 break; 0714 } 0715 } 0716 } 0717 0718 if (isClosing) { 0719 /// Closing operation was not cancelled, therefore mark 0720 /// all files that were open before as open now. 0721 /// This makes the files to be reopened when KBibTeX is 0722 /// restarted again (assuming that this function was 0723 /// called when KBibTeX is exiting). 0724 for (OpenFileInfo *openFileInfo : const_cast<const OpenFileInfoManager::OpenFileInfoList &>(restoreLaterList)) { 0725 openFileInfo->addFlags(OpenFileInfo::StatusFlag::Open); 0726 } 0727 0728 d->writeConfig(); 0729 } 0730 0731 return isClosing; 0732 } 0733 0734 OpenFileInfo *OpenFileInfoManager::currentFile() const 0735 { 0736 return d->currentFileInfo; 0737 } 0738 0739 void OpenFileInfoManager::setCurrentFile(OpenFileInfo *openFileInfo, const KPluginMetaData &service) 0740 { 0741 bool hasChanged = d->currentFileInfo != openFileInfo; 0742 OpenFileInfo *previous = d->currentFileInfo; 0743 d->currentFileInfo = openFileInfo; 0744 0745 if (d->currentFileInfo != nullptr) { 0746 d->currentFileInfo->addFlags(OpenFileInfo::StatusFlag::Open); 0747 d->currentFileInfo->setLastAccess(); 0748 } 0749 if (hasChanged) { 0750 if (previous != nullptr) previous->setLastAccess(); 0751 Q_EMIT currentChanged(openFileInfo, service); 0752 } else if (openFileInfo != nullptr && service.pluginId() != openFileInfo->currentService().pluginId()) 0753 Q_EMIT currentChanged(openFileInfo, service); 0754 } 0755 0756 OpenFileInfoManager::OpenFileInfoList OpenFileInfoManager::filteredItems(OpenFileInfo::StatusFlag required, OpenFileInfo::StatusFlags forbidden) 0757 { 0758 OpenFileInfoList result; 0759 0760 for (OpenFileInfoList::Iterator it = d->openFileInfoList.begin(); it != d->openFileInfoList.end(); ++it) { 0761 OpenFileInfo *ofi = *it; 0762 if (ofi->flags().testFlag(required) && (ofi->flags() & forbidden) == 0) 0763 result << ofi; 0764 } 0765 0766 if (required == OpenFileInfo::StatusFlag::RecentlyUsed) 0767 std::sort(result.begin(), result.end(), OpenFileInfoManagerPrivate::byLRULessThan); 0768 else if (required == OpenFileInfo::StatusFlag::Favorite || required == OpenFileInfo::StatusFlag::Open) 0769 std::sort(result.begin(), result.end(), OpenFileInfoManagerPrivate::byNameLessThan); 0770 0771 return result; 0772 } 0773 0774 void OpenFileInfoManager::deferredListsChanged() 0775 { 0776 OpenFileInfo::StatusFlags statusFlags = OpenFileInfo::StatusFlag::Open; 0777 statusFlags |= OpenFileInfo::StatusFlag::RecentlyUsed; 0778 statusFlags |= OpenFileInfo::StatusFlag::Favorite; 0779 Q_EMIT flagsChanged(statusFlags); 0780 }