File indexing completed on 2024-05-12 05:54:35

0001 /*
0002     SPDX-FileCopyrightText: 2002 Shie Erlich <erlich@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2002 Rafi Yanai <yanai@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "panelviewer.h"
0010 
0011 // QtCore
0012 #include <QDebug>
0013 #include <QFile>
0014 // QtWidgets
0015 #include <QApplication>
0016 
0017 #include <kservice_version.h>
0018 
0019 #if KSERVICE_VERSION >= QT_VERSION_CHECK(5, 82, 0)
0020 #define CREATE_KPART_5_82 1
0021 #else
0022 #define CREATE_KPART_5_82 0
0023 #endif
0024 
0025 #include <KConfigCore/KSharedConfig>
0026 #include <KI18n/KLocalizedString>
0027 #include <KParts/BrowserExtension>
0028 #include <KParts/ReadWritePart>
0029 
0030 #if CREATE_KPART_5_82
0031 #include <KParts/PartLoader>
0032 #endif
0033 
0034 #include <KIOCore/KFileItem>
0035 #include <KService/KMimeTypeTrader>
0036 #include <KService/KServiceTypeProfile>
0037 #include <KWidgetsAddons/KMessageBox>
0038 
0039 #include "../defaults.h"
0040 #include "lister.h"
0041 
0042 #define DICTSIZE 211
0043 
0044 PanelViewerBase::PanelViewerBase(QWidget *parent, KrViewer::Mode mode)
0045     : QStackedWidget(parent)
0046     , mimes(nullptr)
0047     , cpart(nullptr)
0048     , mode(mode)
0049 {
0050     setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored));
0051 
0052     mimes = new QHash<QString, QPointer<KParts::ReadOnlyPart>>();
0053     cpart = nullptr;
0054     // NOTE: the fallback should be never visible. The viewer is not opened without a file and the
0055     // tab is closed if a file cannot be opened.
0056     fallback = new QLabel(i18n("No file selected or selected file cannot be displayed."), this);
0057     fallback->setAlignment(Qt::Alignment(QFlag(Qt::AlignCenter | Qt::TextExpandTabs)));
0058     fallback->setWordWrap(true);
0059     addWidget(fallback);
0060     setCurrentWidget(fallback);
0061 }
0062 
0063 PanelViewerBase::~PanelViewerBase()
0064 {
0065     // cpart->queryClose();
0066     closeUrl();
0067     QHashIterator<QString, QPointer<KParts::ReadOnlyPart>> lit(*mimes);
0068     while (lit.hasNext()) {
0069         QPointer<KParts::ReadOnlyPart> p = lit.next().value();
0070         if (p)
0071             delete p;
0072     }
0073     mimes->clear();
0074     delete mimes;
0075     delete fallback;
0076 }
0077 
0078 void PanelViewerBase::slotStatResult(KJob *job)
0079 {
0080     if (job->error()) {
0081         KMessageBox::error(this, job->errorString());
0082         emit openUrlFinished(this, false);
0083     } else {
0084         KIO::UDSEntry entry = dynamic_cast<KIO::StatJob *>(job)->statResult();
0085         openFile(KFileItem(entry, curl));
0086     }
0087 }
0088 
0089 KParts::ReadOnlyPart *PanelViewerBase::getPart(const QString &mimetype)
0090 {
0091     KParts::ReadOnlyPart *part = nullptr;
0092 
0093     if (mimes->find(mimetype) == mimes->end()) {
0094         part = createPart(mimetype);
0095         if (part)
0096             mimes->insert(mimetype, part);
0097     } else
0098         part = (*mimes)[mimetype];
0099 
0100     return part;
0101 }
0102 
0103 void PanelViewerBase::openUrl(const QUrl &url)
0104 {
0105     closeUrl();
0106     curl = url;
0107     emit urlChanged(this, url);
0108 
0109     if (url.isLocalFile()) {
0110         if (!QFile::exists(url.path())) {
0111             KMessageBox::error(krMainWindow, i18n("Error at opening %1.", url.path()));
0112             emit openUrlFinished(this, false);
0113             return;
0114         }
0115         openFile(KFileItem(url));
0116     } else {
0117         KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
0118         connect(statJob, &KIO::StatJob::result, this, &PanelViewerBase::slotStatResult);
0119     }
0120 }
0121 
0122 /* ----==={ PanelViewer }===---- */
0123 
0124 PanelViewer::PanelViewer(QWidget *parent, KrViewer::Mode mode)
0125     : PanelViewerBase(parent, mode)
0126 {
0127 }
0128 
0129 PanelViewer::~PanelViewer() = default;
0130 
0131 KParts::ReadOnlyPart *PanelViewer::getListerPart(bool hexMode)
0132 {
0133     KParts::ReadOnlyPart *part = nullptr;
0134 
0135     if (mimes->find(QLatin1String("krusader_lister")) == mimes->end()) {
0136         part = new Lister(this);
0137         mimes->insert(QLatin1String("krusader_lister"), part);
0138     } else
0139         part = (*mimes)[QLatin1String("krusader_lister")];
0140 
0141     if (part) {
0142         auto *lister = qobject_cast<Lister *>((KParts::ReadOnlyPart *)part);
0143         if (lister)
0144             lister->setHexMode(hexMode);
0145     }
0146     return part;
0147 }
0148 
0149 KParts::ReadOnlyPart *PanelViewer::getHexPart()
0150 {
0151     KParts::ReadOnlyPart *part = nullptr;
0152 
0153     if (KConfigGroup(krConfig, "General").readEntry("UseOktetaViewer", _UseOktetaViewer)) {
0154         if (mimes->find("oktetapart") == mimes->end()) {
0155             KPluginFactory *factory = nullptr;
0156             // Okteta >= 0.26 provides a desktop file, prefer that as the binary changes name
0157             KService::Ptr service = KService::serviceByDesktopName("oktetapart");
0158             if (service) {
0159                 factory = KPluginLoader(*service.data()).factory();
0160             } else {
0161                 // fallback to search for desktopfile-less old variant
0162                 factory = KPluginLoader("oktetapart").factory();
0163             }
0164             if (factory) {
0165                 if ((part = factory->create<KParts::ReadOnlyPart>(this, this)))
0166                     mimes->insert("oktetapart", part);
0167             }
0168         } else
0169             part = (*mimes)["oktetapart"];
0170     }
0171 
0172     return part ? part : getListerPart(true);
0173 }
0174 
0175 KParts::ReadOnlyPart *PanelViewer::getTextPart()
0176 {
0177     KParts::ReadOnlyPart *part = getPart("text/plain");
0178     if (!part)
0179         part = getPart("all/allfiles");
0180     return part ? part : getListerPart();
0181 }
0182 
0183 KParts::ReadOnlyPart *PanelViewer::getDefaultPart(const KFileItem &fi)
0184 {
0185     KConfigGroup group(krConfig, "General");
0186     QString modeString = group.readEntry("Default Viewer Mode", QString("generic"));
0187 
0188     KrViewer::Mode mode = KrViewer::Generic;
0189 
0190     if (modeString == "generic")
0191         mode = KrViewer::Generic;
0192     else if (modeString == "text")
0193         mode = KrViewer::Text;
0194     else if (modeString == "hex")
0195         mode = KrViewer::Hex;
0196     else if (modeString == "lister")
0197         mode = KrViewer::Lister;
0198 
0199     QMimeType mimeType = fi.determineMimeType();
0200     bool isBinary = false;
0201     if (mode == KrViewer::Generic || mode == KrViewer::Lister) {
0202         isBinary = !mimeType.inherits(QStringLiteral("text/plain"));
0203     }
0204 
0205     KIO::filesize_t fileSize = fi.size();
0206     KIO::filesize_t limit = (KIO::filesize_t)group.readEntry("Lister Limit", _ListerLimit) * 0x100000;
0207 
0208     QString mimetype = fi.mimetype();
0209 
0210     KParts::ReadOnlyPart *part = nullptr;
0211 
0212     switch (mode) {
0213     case KrViewer::Generic:
0214         if ((mimetype.startsWith(QLatin1String("text/")) || mimetype.startsWith(QLatin1String("all/"))) && fileSize > limit) {
0215             part = getListerPart(isBinary);
0216             break;
0217         } else if ((part = getPart(mimetype))) {
0218             break;
0219         }
0220 #if __GNUC__ >= 7
0221         [[gnu::fallthrough]];
0222 #endif
0223     case KrViewer::Text:
0224         part = fileSize > limit ? getListerPart(false) : getTextPart();
0225         break;
0226     case KrViewer::Lister:
0227         part = getListerPart(isBinary);
0228         break;
0229     case KrViewer::Hex:
0230         part = fileSize > limit ? getListerPart(true) : getHexPart();
0231         break;
0232     default:
0233         abort();
0234     }
0235 
0236     return part;
0237 }
0238 
0239 void PanelViewer::openFile(KFileItem fi)
0240 {
0241     switch (mode) {
0242     case KrViewer::Generic:
0243         cpart = getPart(fi.mimetype());
0244         break;
0245     case KrViewer::Text:
0246         cpart = getTextPart();
0247         break;
0248     case KrViewer::Lister:
0249         cpart = getListerPart();
0250         break;
0251     case KrViewer::Hex:
0252         cpart = getHexPart();
0253         break;
0254     case KrViewer::Default:
0255         cpart = getDefaultPart(fi);
0256         break;
0257     default:
0258         abort();
0259     }
0260 
0261     if (cpart) {
0262         addWidget(cpart->widget());
0263         setCurrentWidget(cpart->widget());
0264 
0265         if (cpart->inherits("KParts::ReadWritePart"))
0266             dynamic_cast<KParts::ReadWritePart *>(cpart.data())->setReadWrite(false);
0267         KParts::OpenUrlArguments args;
0268         args.setReload(true);
0269         cpart->setArguments(args);
0270 
0271         connect(cpart.data(), &KParts::ReadOnlyPart::canceled, this, [=]() {
0272             qDebug() << "openFile canceled: '" << curl << "'";
0273         });
0274 
0275         auto cPartCompleted = [=]() {
0276             connect(cpart.data(), &KParts::ReadOnlyPart::destroyed, this, &PanelViewer::slotCPartDestroyed);
0277             emit openUrlFinished(this, true);
0278             qDebug() << "openFile completed: '" << curl << "'";
0279         };
0280         connect(cpart.data(), QOverload<>::of(&KParts::ReadOnlyPart::completed), this, cPartCompleted);
0281 #if KSERVICE_VERSION >= QT_VERSION_CHECK(5, 81, 0)
0282         connect(cpart.data(), &KParts::ReadOnlyPart::completedWithPendingAction, this, cPartCompleted);
0283 #else
0284         connect(cpart.data(), QOverload<bool>::of(&KParts::ReadOnlyPart::completed), this, cPartCompleted);
0285 #endif
0286 
0287         // Note: Don't rely on return value of openUrl as the call is async in general
0288         cpart->openUrl(curl);
0289 
0290         return;
0291     }
0292 
0293     setCurrentWidget(fallback);
0294     emit openUrlFinished(this, false);
0295 }
0296 
0297 bool PanelViewer::closeUrl()
0298 {
0299     setCurrentWidget(fallback);
0300     if (cpart && cpart->closeUrl()) {
0301         cpart = nullptr;
0302         return true;
0303     }
0304     return false;
0305 }
0306 
0307 #if CREATE_KPART_5_82
0308 
0309 template<typename T>
0310 static T *createKPartForMimeType(QString mimetype, QWidget *parentWidget)
0311 {
0312     auto metaDataList = KParts::PartLoader::partsForMimeType(mimetype);
0313     if (metaDataList.isEmpty())
0314         return nullptr;
0315 
0316     KPluginLoader pluginLoader(metaDataList.first().fileName());
0317     KPluginFactory *pluginFactory = pluginLoader.factory();
0318     if (!pluginFactory)
0319         return nullptr;
0320 
0321     return pluginFactory->create<T>(parentWidget, parentWidget);
0322 }
0323 
0324 #endif // CREATE_KPART_5_82
0325 
0326 KParts::ReadOnlyPart *PanelViewer::createPart(QString mimetype)
0327 {
0328     KParts::ReadOnlyPart *part = nullptr;
0329 
0330 #if CREATE_KPART_5_82
0331     part = createKPartForMimeType<KParts::ReadOnlyPart>(mimetype, this);
0332 #else
0333     KService::Ptr ptr = KMimeTypeTrader::self()->preferredService(mimetype, "KParts/ReadOnlyPart");
0334     if (ptr) {
0335         QVariantList args;
0336         QVariant argsProp = ptr->property("X-KDE-BrowserView-Args");
0337         if (argsProp.isValid())
0338             args << argsProp;
0339         QVariant prop = ptr->property("X-KDE-BrowserView-AllowAsDefault");
0340         if (!prop.isValid() || prop.toBool()) // defaults to true
0341             part = ptr->createInstance<KParts::ReadOnlyPart>(this, this, args);
0342     }
0343 #endif
0344 
0345     if (part) {
0346         KParts::BrowserExtension *ext = KParts::BrowserExtension::childObject(part);
0347         if (ext) {
0348             connect(ext, &KParts::BrowserExtension::openUrlRequestDelayed, this, &PanelViewer::openUrl);
0349             connect(ext, &KParts::BrowserExtension::openUrlRequestDelayed, this, &PanelViewer::openUrlRequest);
0350         }
0351     }
0352     return part;
0353 }
0354 
0355 /* ----==={ PanelEditor }===---- */
0356 
0357 PanelEditor::PanelEditor(QWidget *parent, KrViewer::Mode mode)
0358     : PanelViewerBase(parent, mode)
0359 {
0360 }
0361 
0362 PanelEditor::~PanelEditor() = default;
0363 
0364 void PanelEditor::configureDeps()
0365 {
0366     bool foundPlugin = false;
0367 
0368 #if CREATE_KPART_5_82
0369     foundPlugin = !KParts::PartLoader::partsForMimeType("text/plain").isEmpty();
0370 #else
0371     KService::Ptr ptr = KMimeTypeTrader::self()->preferredService("text/plain", "KParts/ReadWritePart");
0372     if (!ptr)
0373         ptr = KMimeTypeTrader::self()->preferredService("all/allfiles", "KParts/ReadWritePart");
0374     foundPlugin = (ptr != nullptr);
0375 #endif
0376 
0377     if (!foundPlugin)
0378         KMessageBox::error(nullptr, missingKPartMsg(), i18n("Missing Plugin"), KMessageBox::AllowLink);
0379 }
0380 
0381 QString PanelEditor::missingKPartMsg()
0382 {
0383     return i18nc("missing kpart - arg1 is a URL",
0384                  "<b>No text editor plugin available.</b><br/>"
0385                  "Internal editor will not work without this.<br/>"
0386                  "You can fix this by installing Kate:<br/>%1",
0387                  QString("<a href='%1'>%1</a>").arg("https://kde.org/applications/utilities/org.kde.kate"));
0388 }
0389 
0390 void PanelEditor::openFile(KFileItem fi)
0391 {
0392     KIO::filesize_t fileSize = fi.size();
0393     KConfigGroup group(krConfig, "General");
0394     KIO::filesize_t limitMB = (KIO::filesize_t)group.readEntry("Lister Limit", _ListerLimit);
0395     QString mimetype = fi.mimetype();
0396 
0397     if (mode == KrViewer::Generic)
0398         cpart = getPart(mimetype);
0399 
0400     if (fileSize > limitMB * 0x100000) {
0401         if (!cpart || mimetype.startsWith(QLatin1String("text/")) || mimetype.startsWith(QLatin1String("all/"))) {
0402             if (KMessageBox::Cancel
0403                 == KMessageBox::warningContinueCancel(this, i18n("%1 is bigger than %2 MB", curl.toDisplayString(QUrl::PreferLocalFile), limitMB))) {
0404                 setCurrentWidget(fallback);
0405                 emit openUrlFinished(this, false);
0406                 return;
0407             }
0408         }
0409     }
0410 
0411     if (!cpart)
0412         cpart = getPart("text/plain");
0413     if (!cpart)
0414         cpart = getPart("all/allfiles");
0415 
0416     if (cpart) {
0417         addWidget(cpart->widget());
0418         setCurrentWidget(cpart->widget());
0419 
0420         KParts::OpenUrlArguments args;
0421         args.setReload(true);
0422         cpart->setArguments(args);
0423         if (cpart->openUrl(curl)) {
0424             connect(cpart.data(), &KParts::ReadOnlyPart::destroyed, this, &PanelEditor::slotCPartDestroyed);
0425             emit openUrlFinished(this, true);
0426             return;
0427         } // else: don't show error message - assume this has been done by the editor part
0428     } else
0429         KMessageBox::error(this, missingKPartMsg(), i18n("Cannot edit %1", curl.toDisplayString(QUrl::PreferLocalFile)), KMessageBox::AllowLink);
0430 
0431     setCurrentWidget(fallback);
0432     emit openUrlFinished(this, false);
0433 }
0434 
0435 bool PanelEditor::queryClose()
0436 {
0437     if (!cpart)
0438         return true;
0439     return dynamic_cast<KParts::ReadWritePart *>((KParts::ReadOnlyPart *)cpart)->queryClose();
0440 }
0441 
0442 bool PanelEditor::closeUrl()
0443 {
0444     if (!cpart)
0445         return false;
0446 
0447     dynamic_cast<KParts::ReadWritePart *>((KParts::ReadOnlyPart *)cpart)->closeUrl(false);
0448 
0449     setCurrentWidget(fallback);
0450     cpart = nullptr;
0451     return true;
0452 }
0453 
0454 KParts::ReadOnlyPart *PanelEditor::createPart(QString mimetype)
0455 {
0456     KParts::ReadWritePart *part = nullptr;
0457 
0458 #if CREATE_KPART_5_82
0459     part = createKPartForMimeType<KParts::ReadWritePart>(mimetype, this);
0460 #else
0461     KService::Ptr ptr = KMimeTypeTrader::self()->preferredService(mimetype, "KParts/ReadWritePart");
0462     if (ptr) {
0463         QVariantList args;
0464         QVariant argsProp = ptr->property("X-KDE-BrowserView-Args");
0465         if (argsProp.isValid())
0466             args << argsProp;
0467         QVariant prop = ptr->property("X-KDE-BrowserView-AllowAsDefault");
0468         if (!prop.isValid() || prop.toBool()) // defaults to true
0469             part = ptr->createInstance<KParts::ReadWritePart>(this, this, args);
0470     }
0471 #endif
0472 
0473     if (part) {
0474         KParts::BrowserExtension *ext = KParts::BrowserExtension::childObject(part);
0475         if (ext) {
0476             connect(ext, &KParts::BrowserExtension::openUrlRequestDelayed, this, &PanelEditor::openUrl);
0477             connect(ext, &KParts::BrowserExtension::openUrlRequestDelayed, this, &PanelEditor::openUrlRequest);
0478         }
0479     }
0480     return part;
0481 }
0482 
0483 bool PanelEditor::isModified()
0484 {
0485     if (cpart)
0486         return dynamic_cast<KParts::ReadWritePart *>((KParts::ReadOnlyPart *)cpart)->isModified();
0487     else
0488         return false;
0489 }