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"