File indexing completed on 2025-01-05 05:14:49

0001 /*
0002 SPDX-FileCopyrightText: 2021 Hamed Masafi <hamed.masfi@gmail.com>
0003 
0004 SPDX-License-Identifier: GPL-3.0-or-later
0005 */
0006 
0007 #include "fileviewerdialog.h"
0008 #include "gitmanager.h"
0009 
0010 #include <KActionCollection>
0011 #include <KLocalizedString>
0012 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0013 #include <KMimeTypeTrader>
0014 #endif
0015 #include <KStandardAction>
0016 #include <KXMLGUIFactory>
0017 
0018 #include "libkommitwidgets_appdebug.h"
0019 #include <QFile>
0020 #include <QMimeData>
0021 #include <QMimeDatabase>
0022 #include <QProgressDialog>
0023 #include <QSettings>
0024 #include <QStandardPaths>
0025 #include <QStyle>
0026 
0027 // TODO: This file need to be refactored
0028 FileViewerDialog::FileViewerDialog(Git::Manager *git, const QString &place, const QString &fileName, QWidget *parent)
0029     : FileViewerDialog(git, Git::File(git, place, fileName), parent)
0030 {
0031 }
0032 
0033 FileViewerDialog::FileViewerDialog(Git::Manager *git, const Git::File &file, QWidget *parent)
0034     : KParts::MainWindow(parent)
0035     , mGit(git)
0036 {
0037     setupUi(this);
0038     showFile(Git::File(file));
0039     QSettings s;
0040     restoreGeometry(s.value(QStringLiteral("FileViewerDialog_Geometry")).toByteArray());
0041     KStandardAction::close(this, &QMainWindow::close, actionCollection());
0042 
0043     setupGUI(ToolBar, QStringLiteral("kommitfileviewerui.rc"));
0044 }
0045 
0046 FileViewerDialog::~FileViewerDialog()
0047 {
0048     QSettings s;
0049     s.setValue(QStringLiteral("FileViewerDialog_Geometry"), saveGeometry());
0050 
0051     if (!mFilePath.isEmpty() && QFile::exists(mFilePath))
0052         QFile::remove(mFilePath);
0053 
0054     if (m_part) {
0055         QProgressDialog progressDialog(this);
0056         progressDialog.setWindowTitle(i18nc("@title:window", "Closing preview"));
0057         progressDialog.setLabelText(i18n("Please wait while the preview is being closed..."));
0058 
0059         progressDialog.setMinimumDuration(500);
0060         progressDialog.setModal(true);
0061         progressDialog.setCancelButton(nullptr);
0062         progressDialog.setRange(0, 0);
0063 
0064         // #261785: this preview dialog is not modal, so we need to delete
0065         //          the previewed file ourselves when the dialog is closed;
0066 
0067         m_part.data()->closeUrl();
0068 
0069         //        if (!m_fileName.isEmpty()) {
0070         //            QFile::remove(m_fileName);
0071         //        }
0072     }
0073 
0074     guiFactory()->removeClient(m_part);
0075     delete m_part;
0076 }
0077 
0078 void FileViewerDialog::showFile(const Git::File &file)
0079 {
0080     QMimeDatabase mimeDatabase;
0081     const auto fn = file.fileName().mid(file.fileName().lastIndexOf(QLatin1Char('/')) + 1);
0082     const auto mime = mimeDatabase.mimeTypeForFile(fn, QMimeDatabase::MatchExtension);
0083     mFilePath = file.fileName();
0084     mFilePath = mFilePath.mid(mFilePath.lastIndexOf(QLatin1Char('/')) + 1);
0085     mFilePath.prepend(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1Char('/'));
0086 
0087     lineEditBranchName->setText(file.place());
0088     lineEditFileName->setText(file.fileName());
0089     plainTextEdit->setReadOnly(true);
0090     setWindowTitle(i18nc("@title:window", "View file: %1", file.fileName()));
0091     setWindowFilePath(file.fileName());
0092     labelFileIcon->setPixmap(QIcon::fromTheme(mime.iconName()).pixmap(style()->pixelMetric(QStyle::PixelMetric::PM_SmallIconSize)));
0093 
0094     auto ptr = getInternalViewer(mime.name());
0095     if (ptr && ptr->isValid()) {
0096         file.save(mFilePath);
0097         if (viewInInternalViewer(ptr, mFilePath, mime))
0098             return;
0099     }
0100 
0101     if (mime.name().startsWith(QStringLiteral("text/")))
0102         showInEditor(file);
0103     else if (mime.name().startsWith(QStringLiteral("image/")))
0104         showAsImage(file);
0105     else {
0106         if (!ptr || !ptr->isValid()) {
0107             showInEditor(file);
0108             qCDebug(KOMMIT_WIDGETS_LOG()) << "fallback to text mode";
0109         } else {
0110             file.save(mFilePath);
0111             if (!viewInInternalViewer(ptr, mFilePath, mime))
0112                 showInEditor(file);
0113         }
0114     }
0115     qCDebug(KOMMIT_WIDGETS_LOG()) << "mime is" << mime.name() << fn << mimeDatabase.suffixForFileName(fn) << stackedWidget->currentIndex();
0116 }
0117 
0118 void FileViewerDialog::showInEditor(const Git::File &file)
0119 {
0120     stackedWidget->setCurrentIndex(0);
0121     plainTextEdit->setPlainText(file.content());
0122     plainTextEdit->setHighlighting(file.fileName());
0123 }
0124 
0125 void FileViewerDialog::showAsImage(const Git::File &file)
0126 {
0127     stackedWidget->setCurrentIndex(1);
0128     const auto p = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QStringLiteral("/klient_img");
0129     file.save(p);
0130     QImage img{p};
0131     labelImage->setPixmap(QPixmap::fromImage(img));
0132 }
0133 
0134 KService::Ptr FileViewerDialog::getInternalViewer(const QString &mimeType)
0135 {
0136 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0137     // No point in even trying to find anything for application/octet-stream
0138     if (mimeType == QLatin1String("application/octet-stream")) {
0139         return {};
0140     }
0141 
0142     // Try to get a read-only kpart for the internal viewer
0143     KService::List offers = KMimeTypeTrader::self()->query(mimeType, QStringLiteral("KParts/ReadOnlyPart"));
0144 
0145     qCDebug(KOMMIT_WIDGETS_LOG()) << offers.size() << "offer(s) found for" << mimeType;
0146     for (const auto &offer : std::as_const(offers))
0147         qCDebug(KOMMIT_WIDGETS_LOG()) << " *" << offer->name() << offer->genericName();
0148     /*auto arkPartIt = std::find_if(offers.begin(), offers.end(), [](KService::Ptr service) {
0149         return service->storageId() == QLatin1String("ark_part.desktop");
0150     });
0151 
0152     // Use the Ark part only when the mime type matches an archive type directly.
0153     // Many file types (e.g. Open Document) are technically just archives
0154     // but browsing their internals is typically not what the user wants.
0155     if (arkPartIt != offers.end()) {
0156         // Not using hasMimeType() as we're explicitly not interested in inheritance.
0157         if (!(*arkPartIt)->mimeTypes().contains(mimeType)) {
0158             offers.erase(arkPartIt);
0159         }
0160     }*/
0161 
0162     // Skip the KHTML part
0163     auto khtmlPart = std::find_if(offers.begin(), offers.end(), [](KService::Ptr service) {
0164         return service->desktopEntryName() == QLatin1String("khtml");
0165     });
0166 
0167     if (khtmlPart != offers.end()) {
0168         offers.erase(khtmlPart);
0169     }
0170 
0171     if (!offers.isEmpty()) {
0172         return offers.first();
0173     } else {
0174         return {};
0175     }
0176 #else
0177     qWarning() << "FileViewerDialog::getInternalViewer need to be port to QT6 ";
0178     return {};
0179 
0180 #endif
0181 }
0182 
0183 void FileViewerDialog::keyPressEvent(QKeyEvent *event)
0184 {
0185     if (event->key() == Qt::Key_Escape)
0186         close();
0187 
0188     KParts::MainWindow::keyPressEvent(event);
0189 }
0190 
0191 KService::Ptr FileViewerDialog::getExternalViewer(const QString &mimeType)
0192 {
0193 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0194     qCDebug(KOMMIT_WIDGETS_LOG()) << mimeType;
0195     const KService::List offers = KMimeTypeTrader::self()->query(mimeType);
0196 
0197     if (!offers.isEmpty()) {
0198         return offers.first();
0199     } else {
0200         return {};
0201     }
0202 #else
0203     qWarning() << "FileViewerDialog::getExternalViewer need to be port to QT6 ";
0204     return {};
0205 #endif
0206 }
0207 
0208 bool FileViewerDialog::viewInInternalViewer(const KService::Ptr &viewer, const QString &fileName, const QMimeType &mimeType)
0209 {
0210 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0211     Q_UNUSED(mimeType)
0212     // Set icon and comment for the mimetype.
0213     //    m_iconLabel->setPixmap(QIcon::fromTheme(mimeType.iconName()).pixmap(style()->pixelMetric(QStyle::PixelMetric::PM_SmallIconSize)));
0214     //    m_commentLabel->setText(mimeType.comment());
0215 
0216     // Create the ReadOnlyPart instance.
0217     QString error;
0218     m_part = viewer->createInstance<KParts::ReadOnlyPart>(widgetContainer, widgetContainer, QVariantList(), &error);
0219 
0220     if (!m_part.data()) {
0221         qCDebug(KOMMIT_WIDGETS_LOG()) << "m_part is null" << error;
0222         return false;
0223     }
0224 
0225     // Insert the KPart into its placeholder.
0226     kPartWidgetLayout->addWidget(m_part.data()->widget());
0227     stackedWidget->setCurrentIndex(2);
0228     //    layout()->replaceWidget(plainTextEdit, m_part.data()->widget());
0229     /*
0230         QAction* action = actionCollection()->addAction(QStringLiteral("help_about_kpart"));
0231         const KPluginMetaData partMetaData = m_part->metaData();
0232         const QString iconName = partMetaData.iconName();
0233         if (!iconName.isEmpty()) {
0234             action->setIcon(QIcon::fromTheme(iconName));
0235         }
0236         action->setText(i18nc("@action", "About Viewer Component"));
0237         connect(action, &QAction::triggered, this, &ArkViewer::aboutKPart);
0238     */
0239     createGUI(m_part.data());
0240     //    setAutoSaveSettings(QStringLiteral("Viewer"), true);
0241 
0242     m_part.data()->openUrl(QUrl::fromLocalFile(fileName));
0243     m_part.data()->widget()->setFocus();
0244     //    m_fileName = fileName;
0245 #else
0246     qWarning() << "FileViewerDialog::viewInInternalViewer need to be port to QT6 ";
0247     return {};
0248 #endif
0249     return true;
0250 }
0251 
0252 #include "moc_fileviewerdialog.cpp"