File indexing completed on 2024-04-28 15:51:44

0001 /*
0002     SPDX-FileCopyrightText: 2006 Albert Astals Cid <aacid@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "embeddedfilesdialog.h"
0008 
0009 #include <QAction>
0010 #include <QCursor>
0011 #include <QDateTime>
0012 #include <QDir>
0013 #include <QFileInfo>
0014 #include <QMenu>
0015 #include <QTemporaryFile>
0016 #include <QTreeWidget>
0017 
0018 #include <KConfigGroup>
0019 #include <KFormat>
0020 #include <KIO/JobUiDelegate>
0021 #include <KIO/JobUiDelegateFactory>
0022 #include <KIO/OpenUrlJob>
0023 #include <KLocalizedString>
0024 #include <KStandardGuiItem>
0025 #include <QDialogButtonBox>
0026 #include <QIcon>
0027 #include <QMimeDatabase>
0028 #include <QMimeType>
0029 #include <QPushButton>
0030 #include <QVBoxLayout>
0031 #include <kio_version.h>
0032 
0033 #include "core/document.h"
0034 #include "gui/guiutils.h"
0035 
0036 Q_DECLARE_METATYPE(Okular::EmbeddedFile *)
0037 
0038 static const int EmbeddedFileRole = Qt::UserRole + 100;
0039 
0040 static QString dateToString(const QDateTime &date)
0041 {
0042     return date.isValid() ? QLocale().toString(date, QLocale::LongFormat) : i18nc("Unknown date", "Unknown");
0043 }
0044 
0045 EmbeddedFilesDialog::EmbeddedFilesDialog(QWidget *parent, const Okular::Document *document)
0046     : QDialog(parent)
0047 {
0048     setWindowTitle(i18nc("@title:window", "Embedded Files"));
0049     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0050     QVBoxLayout *mainLayout = new QVBoxLayout;
0051     setLayout(mainLayout);
0052     mUser1Button = new QPushButton;
0053     buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole);
0054     connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
0055     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0056     buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
0057     KGuiItem::assign(mUser1Button, KStandardGuiItem::save());
0058     mUser1Button->setEnabled(false);
0059 
0060     mUser2Button = new QPushButton;
0061     buttonBox->addButton(mUser2Button, QDialogButtonBox::ActionRole);
0062     KGuiItem::assign(mUser2Button, KGuiItem(i18nc("@action:button", "View"), QStringLiteral("document-open")));
0063     mUser2Button->setEnabled(false);
0064 
0065     m_tw = new QTreeWidget(this);
0066     mainLayout->addWidget(m_tw);
0067     mainLayout->addWidget(buttonBox);
0068 
0069     QStringList header;
0070     header.append(i18nc("@title:column", "Name"));
0071     header.append(i18nc("@title:column", "Description"));
0072     header.append(i18nc("@title:column", "Size"));
0073     header.append(i18nc("@title:column", "Created"));
0074     header.append(i18nc("@title:column", "Modified"));
0075     m_tw->setHeaderLabels(header);
0076     m_tw->setRootIsDecorated(false);
0077     m_tw->setSelectionMode(QAbstractItemView::ExtendedSelection);
0078     m_tw->setContextMenuPolicy(Qt::CustomContextMenu);
0079 
0080     // embeddedFiles() returns a const QList
0081     for (Okular::EmbeddedFile *ef : *document->embeddedFiles()) {
0082         QTreeWidgetItem *twi = new QTreeWidgetItem();
0083         twi->setText(0, ef->name());
0084         QMimeDatabase db;
0085         QMimeType mime = db.mimeTypeForFile(ef->name(), QMimeDatabase::MatchExtension);
0086         if (mime.isValid()) {
0087             twi->setIcon(0, QIcon::fromTheme(mime.iconName()));
0088         }
0089         twi->setText(1, ef->description());
0090         twi->setText(2, ef->size() <= 0 ? i18nc("Not available size", "N/A") : KFormat().formatByteSize(ef->size()));
0091         twi->setText(3, dateToString(ef->creationDate()));
0092         twi->setText(4, dateToString(ef->modificationDate()));
0093         twi->setData(0, EmbeddedFileRole, QVariant::fromValue(ef));
0094         m_tw->addTopLevelItem(twi);
0095     }
0096     // Having filled the columns, it is nice to resize them to be able to read the contents
0097     for (int lv = 0; lv < m_tw->columnCount(); ++lv) {
0098         m_tw->resizeColumnToContents(lv);
0099     }
0100     // This is a bit dubious, but I'm not seeing a nice way to say "expand to fit contents"
0101     m_tw->setMinimumWidth(640);
0102     m_tw->updateGeometry();
0103 
0104     connect(mUser1Button, &QPushButton::clicked, this, &EmbeddedFilesDialog::saveFileFromButton);
0105     connect(mUser2Button, &QPushButton::clicked, this, &EmbeddedFilesDialog::viewFileFromButton);
0106     connect(m_tw, &QWidget::customContextMenuRequested, this, &EmbeddedFilesDialog::attachViewContextMenu);
0107     connect(m_tw, &QTreeWidget::itemSelectionChanged, this, &EmbeddedFilesDialog::updateSaveButton);
0108     connect(m_tw, &QTreeWidget::itemDoubleClicked, this, &EmbeddedFilesDialog::viewFileItem);
0109 }
0110 
0111 void EmbeddedFilesDialog::updateSaveButton()
0112 {
0113     bool enable = (m_tw->selectedItems().count() > 0);
0114     mUser1Button->setEnabled(enable);
0115     mUser2Button->setEnabled(enable);
0116 }
0117 
0118 void EmbeddedFilesDialog::saveFileFromButton()
0119 {
0120     const QList<QTreeWidgetItem *> selected = m_tw->selectedItems();
0121     for (const QTreeWidgetItem *twi : selected) {
0122         Okular::EmbeddedFile *ef = qvariant_cast<Okular::EmbeddedFile *>(twi->data(0, EmbeddedFileRole));
0123         saveFile(ef);
0124     }
0125 }
0126 
0127 void EmbeddedFilesDialog::viewFileFromButton()
0128 {
0129     const QList<QTreeWidgetItem *> selected = m_tw->selectedItems();
0130     for (QTreeWidgetItem *twi : selected) {
0131         Okular::EmbeddedFile *ef = qvariant_cast<Okular::EmbeddedFile *>(twi->data(0, EmbeddedFileRole));
0132         viewFile(ef);
0133     }
0134 }
0135 
0136 void EmbeddedFilesDialog::viewFileItem(QTreeWidgetItem *item, int /*column*/)
0137 {
0138     Okular::EmbeddedFile *ef = qvariant_cast<Okular::EmbeddedFile *>(item->data(0, EmbeddedFileRole));
0139     viewFile(ef);
0140 }
0141 
0142 void EmbeddedFilesDialog::attachViewContextMenu()
0143 {
0144     QList<QTreeWidgetItem *> selected = m_tw->selectedItems();
0145     if (selected.isEmpty()) {
0146         return;
0147     }
0148 
0149     if (selected.size() > 1) {
0150         return;
0151     }
0152 
0153     QMenu menu(this);
0154     const QAction *saveAsAct = menu.addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18nc("@action:inmenu", "&Save As..."));
0155     const QAction *viewAct = menu.addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("@action:inmenu", "&View..."));
0156 
0157     const QAction *act = menu.exec(QCursor::pos());
0158     if (!act) {
0159         return;
0160     }
0161 
0162     Okular::EmbeddedFile *ef = qvariant_cast<Okular::EmbeddedFile *>(selected.at(0)->data(0, EmbeddedFileRole));
0163     if (act == saveAsAct) {
0164         saveFile(ef);
0165     } else if (act == viewAct) {
0166         viewFile(ef);
0167     }
0168 }
0169 
0170 void EmbeddedFilesDialog::viewFile(Okular::EmbeddedFile *ef)
0171 {
0172     // get name and extension
0173     QFileInfo fileInfo(ef->name());
0174 
0175     // save in temporary directory with a unique name resembling the attachment name,
0176     // using QTemporaryFile's XXXXXX placeholder
0177     QTemporaryFile *tmpFile =
0178         new QTemporaryFile(QDir::tempPath() + QLatin1Char('/') + fileInfo.baseName() + QStringLiteral(".XXXXXX") + (fileInfo.completeSuffix().isEmpty() ? QLatin1String("") : QString(QLatin1Char('.') + fileInfo.completeSuffix())));
0179     GuiUtils::writeEmbeddedFile(ef, this, *tmpFile);
0180 
0181     // set readonly to prevent the viewer application from modifying it
0182     tmpFile->setPermissions(QFile::ReadOwner);
0183 
0184     // keep temporary file alive while the dialog is open
0185     m_openedFiles.push_back(QSharedPointer<QTemporaryFile>(tmpFile));
0186 
0187     // view the temporary file with the default application
0188     auto *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(tmpFile->fileName()));
0189     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
0190     job->start();
0191 }
0192 
0193 void EmbeddedFilesDialog::saveFile(Okular::EmbeddedFile *ef)
0194 {
0195     GuiUtils::saveEmbeddedFile(ef, this);
0196 }
0197 
0198 #include "moc_embeddedfilesdialog.cpp"