File indexing completed on 2024-04-28 05:47:44

0001 /*
0002     ark: A program for modifying archives via a GUI.
0003 
0004     SPDX-FileCopyrightText: 2004-2008 Henrique Pinto <henrique.pinto@kdemail.net>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "arkviewer.h"
0010 #include "ark_debug.h"
0011 
0012 #include <KAboutPluginDialog>
0013 #include <KActionCollection>
0014 #include <KApplicationTrader>
0015 #include <KIO/ApplicationLauncherJob>
0016 #include <KIO/JobUiDelegate>
0017 #include <KIO/JobUiDelegateFactory>
0018 #include <KLocalizedString>
0019 #include <KMessageBox>
0020 #include <KParts/OpenUrlArguments>
0021 #include <KParts/PartLoader>
0022 #include <KPluginMetaData>
0023 #include <KXMLGUIFactory>
0024 
0025 #include <QAction>
0026 #include <QFile>
0027 #include <QMimeDatabase>
0028 #include <QProgressDialog>
0029 #include <QPushButton>
0030 #include <QStyle>
0031 
0032 #include <algorithm>
0033 
0034 ArkViewer::ArkViewer()
0035     : KParts::MainWindow()
0036 {
0037     setupUi(this);
0038 
0039     KStandardAction::close(this, &QMainWindow::close, actionCollection());
0040 
0041     QAction *closeAction = actionCollection()->addAction(QStringLiteral("close"), this, &ArkViewer::close);
0042     closeAction->setShortcut(Qt::Key_Escape);
0043 
0044     setXMLFile(QStringLiteral("ark_viewer.rc"));
0045     setupGUI(ToolBar);
0046 }
0047 
0048 ArkViewer::~ArkViewer()
0049 {
0050     if (m_part) {
0051         QProgressDialog progressDialog(this);
0052         progressDialog.setWindowTitle(i18n("Closing preview"));
0053         progressDialog.setLabelText(i18n("Please wait while the preview is being closed..."));
0054 
0055         progressDialog.setMinimumDuration(500);
0056         progressDialog.setModal(true);
0057         progressDialog.setCancelButton(nullptr);
0058         progressDialog.setRange(0, 0);
0059 
0060         // #261785: this preview dialog is not modal, so we need to delete
0061         //          the previewed file ourselves when the dialog is closed;
0062 
0063         m_part.data()->closeUrl();
0064 
0065         if (!m_fileName.isEmpty()) {
0066             QFile::remove(m_fileName);
0067         }
0068     }
0069 
0070     guiFactory()->removeClient(m_part);
0071     delete m_part;
0072 }
0073 
0074 void ArkViewer::openExternalViewer(const KService::Ptr viewer, const QString &fileName)
0075 {
0076     qCDebug(ARK) << "Using external viewer";
0077 
0078     const QList<QUrl> fileUrlList = {QUrl::fromLocalFile(fileName)};
0079     KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(viewer);
0080     job->setUrls(fileUrlList);
0081     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
0082     // The temporary file will be removed when the viewer application exits.
0083     job->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles);
0084     job->start();
0085 }
0086 
0087 void ArkViewer::openInternalViewer(const KPluginMetaData &viewer, const QString &fileName, const QString &entryPath, const QMimeType &mimeType)
0088 {
0089     qCDebug(ARK) << "Opening internal viewer";
0090 
0091     ArkViewer *internalViewer = new ArkViewer();
0092     internalViewer->show();
0093     if (internalViewer->viewInInternalViewer(viewer, fileName, entryPath, mimeType)) {
0094         // The internal viewer is showing the file, and will
0095         // remove the temporary file in its destructor.  So there
0096         // is no more to do here.
0097         return;
0098     } else {
0099         KMessageBox::error(nullptr, i18n("The internal viewer cannot preview this file."));
0100         delete internalViewer;
0101 
0102         qCDebug(ARK) << "Removing temporary file:" << fileName;
0103         QFile::remove(fileName);
0104     }
0105 }
0106 
0107 bool ArkViewer::askViewAsPlainText(const QMimeType &mimeType)
0108 {
0109     int response;
0110     if (!mimeType.isDefault()) {
0111         // File has a defined MIME type, and not the default
0112         // application/octet-stream.  So it could be viewable as
0113         // plain text, ask the user.
0114         response = KMessageBox::warningContinueCancel(
0115             nullptr,
0116             xi18n("The internal viewer cannot preview this type of file<nl/>(%1).<nl/><nl/>Do you want to try to view it as plain text?", mimeType.name()),
0117             i18nc("@title:window", "Cannot Preview File"),
0118             KGuiItem(i18nc("@action:button", "Preview as Text"), QIcon::fromTheme(QStringLiteral("text-plain"))),
0119             KStandardGuiItem::cancel(),
0120             QStringLiteral("PreviewAsText_%1").arg(mimeType.name()));
0121     } else {
0122         // No defined MIME type, or the default application/octet-stream.
0123         // There is still a possibility that it could be viewable as plain
0124         // text, so ask the user.  Not the same as the message/question
0125         // above, because the wording and default are different.
0126         response = KMessageBox::warningContinueCancel(
0127             nullptr,
0128             xi18n("The internal viewer cannot preview this unknown type of file.<nl/><nl/>Do you want to try to view it as plain text?"),
0129             i18nc("@title:window", "Cannot Preview File"),
0130             KGuiItem(i18nc("@action:button", "Preview as Text"), QIcon::fromTheme(QStringLiteral("text-plain"))),
0131             KStandardGuiItem::cancel(),
0132             QString(),
0133             KMessageBox::Dangerous);
0134     }
0135 
0136     return response != KMessageBox::Cancel;
0137 }
0138 
0139 void ArkViewer::view(const QString &fileName, const QString &entryPath, const QMimeType &mimeType)
0140 {
0141     QMimeDatabase db;
0142     qCDebug(ARK) << "viewing" << fileName << "from" << entryPath << "with mime type:" << mimeType.name();
0143 
0144     const std::optional<KPluginMetaData> internalViewer = ArkViewer::getInternalViewer(mimeType.name());
0145 
0146     if (internalViewer) {
0147         openInternalViewer(*internalViewer, fileName, entryPath, mimeType);
0148         return;
0149     }
0150 
0151     const KService::Ptr externalViewer = ArkViewer::getExternalViewer(mimeType.name());
0152 
0153     if (externalViewer) {
0154         openExternalViewer(externalViewer, fileName);
0155         return;
0156     }
0157 
0158     // No internal or external viewer available for the file.  Ask the user if it
0159     // should be previewed as text/plain.
0160     if (askViewAsPlainText(mimeType)) {
0161         const std::optional<KPluginMetaData> textViewer = ArkViewer::getInternalViewer(QStringLiteral("text/plain"));
0162 
0163         if (textViewer) {
0164             openInternalViewer(*textViewer, fileName, entryPath, db.mimeTypeForName(QStringLiteral("text/plain")));
0165             return;
0166         }
0167     }
0168     qCDebug(ARK) << "Removing temporary file:" << fileName;
0169     QFile::remove(fileName);
0170 }
0171 
0172 bool ArkViewer::viewInInternalViewer(const KPluginMetaData &viewer, const QString &fileName, const QString &entryPath, const QMimeType &mimeType)
0173 {
0174     // Set icon and comment for the mimetype.
0175     m_iconLabel->setPixmap(QIcon::fromTheme(mimeType.iconName()).pixmap(style()->pixelMetric(QStyle::PixelMetric::PM_SmallIconSize)));
0176     m_commentLabel->setText(mimeType.comment());
0177 
0178     // Create the ReadOnlyPart instance.
0179     const auto result = KParts::PartLoader::instantiatePart<KParts::ReadOnlyPart>(viewer, this, this);
0180 
0181     if (!result) {
0182         qCDebug(ARK) << "Failed to create internal viewer" << result.errorString;
0183         return false;
0184     }
0185 
0186     m_part = result.plugin;
0187 
0188     if (!m_part.data()) {
0189         return false;
0190     }
0191 
0192     // Insert the KPart into its placeholder.
0193     mainLayout->insertWidget(0, m_part->widget());
0194 
0195     QAction *action = actionCollection()->addAction(QStringLiteral("help_about_kpart"));
0196     const KPluginMetaData partMetaData = m_part->metaData();
0197     const QString iconName = partMetaData.iconName();
0198     if (!iconName.isEmpty()) {
0199         action->setIcon(QIcon::fromTheme(iconName));
0200     }
0201     action->setText(i18nc("@action", "About Viewer Component"));
0202     connect(action, &QAction::triggered, this, &ArkViewer::aboutKPart);
0203 
0204     createGUI(m_part.data());
0205     setAutoSaveSettings(QStringLiteral("Viewer"), true);
0206 
0207     m_part.data()->openUrl(QUrl::fromLocalFile(fileName));
0208     m_part.data()->widget()->setFocus();
0209     m_fileName = fileName;
0210 
0211     // Needs to come after openUrl to override the part-provided caption
0212     setWindowTitle(entryPath);
0213     setWindowFilePath(fileName);
0214 
0215     return true;
0216 }
0217 
0218 KService::Ptr ArkViewer::getExternalViewer(const QString &mimeType)
0219 {
0220     return KApplicationTrader::preferredService(mimeType);
0221 }
0222 
0223 std::optional<KPluginMetaData> ArkViewer::getInternalViewer(const QString &mimeType)
0224 {
0225     // No point in even trying to find anything for application/octet-stream
0226     if (mimeType == QLatin1String("application/octet-stream")) {
0227         return {};
0228     }
0229 
0230     // Try to get a read-only kpart for the internal viewer
0231     QVector<KPluginMetaData> offers = KParts::PartLoader::partsForMimeType(mimeType);
0232 
0233     auto arkPartIt = std::find_if(offers.begin(), offers.end(), [](const KPluginMetaData &service) {
0234         return service.pluginId() == QLatin1String("arkpart");
0235     });
0236 
0237     // Use the Ark part only when the mime type matches an archive type directly.
0238     // Many file types (e.g. Open Document) are technically just archives
0239     // but browsing their internals is typically not what the user wants.
0240     if (arkPartIt != offers.end()) {
0241         // Not using hasMimeType() as we're explicitly not interested in inheritance.
0242         if (!arkPartIt->mimeTypes().contains(mimeType)) {
0243             offers.erase(arkPartIt);
0244         }
0245     }
0246 
0247     // Skip the KHTML part
0248     auto khtmlPart = std::find_if(offers.begin(), offers.end(), [](const KPluginMetaData &part) {
0249         return part.pluginId() == QLatin1String("khtmlpart");
0250     });
0251 
0252     if (khtmlPart != offers.end()) {
0253         offers.erase(khtmlPart);
0254     }
0255 
0256     // The oktetapart can open any file, but a hex viewer isn't really useful here
0257     // Skip it so we prefer an external viewer instead
0258     auto oktetaPart = std::find_if(offers.begin(), offers.end(), [](const KPluginMetaData &part) {
0259         return part.pluginId() == QLatin1String("oktetapart");
0260     });
0261 
0262     if (oktetaPart != offers.end()) {
0263         offers.erase(oktetaPart);
0264     }
0265 
0266     if (!offers.isEmpty()) {
0267         return offers.at(0);
0268     } else {
0269         return {};
0270     }
0271 }
0272 
0273 void ArkViewer::aboutKPart()
0274 {
0275     if (!m_part) {
0276         return;
0277     }
0278 
0279     auto *dialog = new KAboutPluginDialog(m_part->metaData(), this);
0280     dialog->setAttribute(Qt::WA_DeleteOnClose);
0281     dialog->show();
0282 }
0283 
0284 #include "moc_arkviewer.cpp"