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

0001 /*
0002     SPDX-FileCopyrightText: 2004 Albert Astals Cid <aacid@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "propertiesdialog.h"
0008 
0009 // qt/kde includes
0010 #include <QApplication>
0011 #include <QFile>
0012 #include <QFileDialog>
0013 #include <QFormLayout>
0014 #include <QHeaderView>
0015 #include <QIcon>
0016 #include <QLatin1Char>
0017 #include <QLayout>
0018 #include <QMenu>
0019 #include <QProgressBar>
0020 #include <QPushButton>
0021 #include <QSortFilterProxyModel>
0022 #include <QTableView>
0023 #include <QTimer>
0024 #include <QTreeView>
0025 
0026 #include <KIconLoader>
0027 #include <KLocalizedString>
0028 #include <KMessageBox>
0029 #include <KSqueezedTextLabel>
0030 #include <QMimeDatabase>
0031 
0032 // local includes
0033 #include "core/document.h"
0034 
0035 static const int IsExtractableRole = Qt::UserRole;
0036 static const int FontInfoRole = Qt::UserRole + 1;
0037 
0038 PropertiesDialog::PropertiesDialog(QWidget *parent, Okular::Document *doc)
0039     : KPageDialog(parent)
0040     , m_document(doc)
0041     , m_fontPage(nullptr)
0042     , m_fontModel(nullptr)
0043     , m_pageSizesModel(nullptr)
0044     , m_fontInfo(nullptr)
0045     , m_fontProgressBar(nullptr)
0046     , m_fontScanStarted(false)
0047 {
0048     setFaceType(Tabbed);
0049     setWindowTitle(i18n("Unknown File"));
0050     setStandardButtons(QDialogButtonBox::Ok);
0051 
0052     // PROPERTIES
0053     QFrame *page = new QFrame();
0054     KPageWidgetItem *item = addPage(page, i18n("&Properties"));
0055     item->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
0056 
0057     // get document info
0058     const Okular::DocumentInfo info = doc->documentInfo();
0059     QFormLayout *layout = new QFormLayout(page);
0060 
0061     // mime name based on mimetype id
0062     QString mimeName = info.get(Okular::DocumentInfo::MimeType).section(QLatin1Char('/'), -1).toUpper();
0063     setWindowTitle(i18n("%1 Properties", mimeName));
0064 
0065     /* obtains the properties list, conveniently ordered */
0066     QStringList orderedProperties;
0067     orderedProperties << Okular::DocumentInfo::getKeyString(Okular::DocumentInfo::FilePath) << Okular::DocumentInfo::getKeyString(Okular::DocumentInfo::PagesSize) << Okular::DocumentInfo::getKeyString(Okular::DocumentInfo::DocumentSize);
0068     for (Okular::DocumentInfo::Key ks = Okular::DocumentInfo::Title; ks <= Okular::DocumentInfo::Keywords; ks = Okular::DocumentInfo::Key(ks + 1)) {
0069         orderedProperties << Okular::DocumentInfo::getKeyString(ks);
0070     }
0071     const QStringList infoKeys = info.keys();
0072     for (const QString &ks : infoKeys) {
0073         if (!orderedProperties.contains(ks)) {
0074             orderedProperties << ks;
0075         }
0076     }
0077 
0078     for (const QString &key : std::as_const(orderedProperties)) {
0079         const QString titleString = info.getKeyTitle(key);
0080         const QString valueString = info.get(key);
0081         if (titleString.isNull() || valueString.isNull()) {
0082             continue;
0083         }
0084 
0085         // create labels and layout them
0086         QWidget *value = nullptr;
0087         if (key == Okular::DocumentInfo::getKeyString(Okular::DocumentInfo::MimeType)) {
0088             /// for mime type fields, show icon as well
0089             value = new QWidget(page);
0090             /// place icon left of mime type's name
0091             QHBoxLayout *hboxLayout = new QHBoxLayout(value);
0092             hboxLayout->setContentsMargins(0, 0, 0, 0);
0093             /// retrieve icon and place it in a QLabel
0094             QMimeDatabase db;
0095             QMimeType mimeType = db.mimeTypeForName(valueString);
0096             KSqueezedTextLabel *squeezed;
0097             if (mimeType.isValid()) {
0098                 /// retrieve icon and place it in a QLabel
0099                 QLabel *pixmapLabel = new QLabel(value);
0100                 hboxLayout->addWidget(pixmapLabel, 0);
0101                 const QIcon icon = QIcon::fromTheme(mimeType.iconName(), QIcon::fromTheme(QStringLiteral("application-octet-stream")));
0102                 pixmapLabel->setPixmap(icon.pixmap(KIconLoader::SizeSmall));
0103                 /// mime type's name and label
0104                 squeezed = new KSqueezedTextLabel(i18nc("mimetype information, example: \"PDF Document (application/pdf)\"", "%1 (%2)", mimeType.comment(), valueString), value);
0105             } else {
0106                 /// only mime type name
0107                 squeezed = new KSqueezedTextLabel(valueString, value);
0108             }
0109             squeezed->setTextInteractionFlags(Qt::TextSelectableByMouse);
0110             hboxLayout->addWidget(squeezed, 1);
0111         } else {
0112             /// default for any other document information
0113             KSqueezedTextLabel *label = new KSqueezedTextLabel(valueString, page);
0114             label->setTextInteractionFlags(Qt::TextSelectableByMouse);
0115             value = label;
0116         }
0117         layout->addRow(new QLabel(i18n("%1:", titleString)), value);
0118     }
0119 
0120     // FONTS
0121     if (doc->canProvideFontInformation()) {
0122         // create fonts tab and layout it
0123         QFrame *page2 = new QFrame();
0124         m_fontPage = addPage(page2, i18n("&Fonts"));
0125         m_fontPage->setIcon(QIcon::fromTheme(QStringLiteral("dialog-text-and-font")));
0126         QVBoxLayout *page2Layout = new QVBoxLayout(page2);
0127         // add a tree view
0128         QTreeView *view = new QTreeView(page2);
0129         view->setContextMenuPolicy(Qt::CustomContextMenu);
0130         connect(view, &QTreeView::customContextMenuRequested, this, &PropertiesDialog::showFontsMenu);
0131         page2Layout->addWidget(view);
0132         view->setRootIsDecorated(false);
0133         view->setAlternatingRowColors(true);
0134         view->setSortingEnabled(true);
0135         // creating a proxy model so we can sort the data
0136         QSortFilterProxyModel *proxymodel = new QSortFilterProxyModel(view);
0137         proxymodel->setDynamicSortFilter(true);
0138         proxymodel->setSortCaseSensitivity(Qt::CaseInsensitive);
0139         m_fontModel = new FontsListModel(view);
0140         proxymodel->setSourceModel(m_fontModel);
0141         view->setModel(proxymodel);
0142         view->sortByColumn(0, Qt::AscendingOrder);
0143         m_fontInfo = new QLabel(this);
0144         page2Layout->addWidget(m_fontInfo);
0145         m_fontInfo->setText(i18n("Reading font information..."));
0146         m_fontInfo->hide();
0147         m_fontProgressBar = new QProgressBar(this);
0148         page2Layout->addWidget(m_fontProgressBar);
0149         m_fontProgressBar->setRange(0, 100);
0150         m_fontProgressBar->setValue(0);
0151         m_fontProgressBar->hide();
0152     }
0153 
0154     // PAGE SIZES
0155     if (!m_document->allPagesSize().isValid()) {
0156         // create page sizes tab tab and layout it when there are multiple page sizes
0157         QFrame *page3 = new QFrame();
0158         KPageWidgetItem *pageSizesPage = addPage(page3, i18n("&Page Sizes"));
0159         pageSizesPage->setIcon(QIcon::fromTheme(QStringLiteral("view-pages-overview")));
0160         QVBoxLayout *page3Layout = new QVBoxLayout(page3);
0161 
0162         // Add a table view
0163         QTableView *view = new QTableView(page3);
0164         m_pageSizesModel = new PageSizesModel(view, m_document);
0165         page3Layout->addWidget(view);
0166         view->setModel(m_pageSizesModel);
0167         view->setAlternatingRowColors(true);
0168         view->setCornerButtonEnabled(false);
0169         view->resizeColumnsToContents();
0170         view->verticalHeader()->hide();
0171 
0172         // Stretch the last column, which is the widest
0173         QHeaderView *headerView = view->horizontalHeader();
0174         headerView->setSectionResizeMode(0, QHeaderView::Interactive);
0175         headerView->setSectionResizeMode(1, QHeaderView::Stretch);
0176     }
0177 
0178     // KPageDialog is a bit buggy, it doesn't fix its own sizeHint, so we have to manually resize
0179     resize(layout->sizeHint());
0180 
0181     connect(pageWidget(), QOverload<KPageWidgetItem *, KPageWidgetItem *>::of(&KPageWidget::currentPageChanged), this, &PropertiesDialog::pageChanged);
0182 }
0183 
0184 PropertiesDialog::~PropertiesDialog()
0185 {
0186     m_document->stopFontReading();
0187 }
0188 
0189 void PropertiesDialog::pageChanged(KPageWidgetItem *current, KPageWidgetItem *)
0190 {
0191     if (current == m_fontPage && !m_fontScanStarted) {
0192         connect(m_document, &Okular::Document::gotFont, m_fontModel, &FontsListModel::addFont);
0193         connect(m_document, &Okular::Document::fontReadingProgress, this, &PropertiesDialog::slotFontReadingProgress);
0194         connect(m_document, &Okular::Document::fontReadingEnded, this, &PropertiesDialog::slotFontReadingEnded);
0195         QTimer::singleShot(0, this, &PropertiesDialog::reallyStartFontReading);
0196 
0197         m_fontScanStarted = true;
0198     }
0199 }
0200 
0201 void PropertiesDialog::slotFontReadingProgress(int page)
0202 {
0203     m_fontProgressBar->setValue(m_fontProgressBar->maximum() * (page + 1) / m_document->pages());
0204 }
0205 
0206 void PropertiesDialog::slotFontReadingEnded()
0207 {
0208     m_fontInfo->hide();
0209     m_fontProgressBar->hide();
0210 }
0211 
0212 void PropertiesDialog::reallyStartFontReading()
0213 {
0214     m_fontInfo->show();
0215     m_fontProgressBar->show();
0216     m_document->startFontReading();
0217 }
0218 
0219 void PropertiesDialog::showFontsMenu(const QPoint pos)
0220 {
0221     QTreeView *view = static_cast<QTreeView *>(sender());
0222     QModelIndex index = view->indexAt(pos);
0223     if (index.data(IsExtractableRole).toBool()) {
0224         QMenu *menu = new QMenu(this);
0225         menu->addAction(i18nc("@action:inmenu", "&Extract Font"));
0226         const QAction *result = menu->exec(view->viewport()->mapToGlobal(pos));
0227         if (result) {
0228             Okular::FontInfo fi = index.data(FontInfoRole).value<Okular::FontInfo>();
0229             const QString caption = i18n("Where do you want to save %1?", fi.name());
0230             const QString path = QFileDialog::getSaveFileName(this, caption, fi.name());
0231             if (path.isEmpty()) {
0232                 return;
0233             }
0234 
0235             QFile f(path);
0236             if (f.open(QIODevice::WriteOnly)) {
0237                 f.write(m_document->fontData(fi));
0238                 f.close();
0239             } else {
0240                 KMessageBox::error(this, i18n("Could not open \"%1\" for writing. File was not saved.", path));
0241             }
0242         }
0243     }
0244 }
0245 
0246 FontsListModel::FontsListModel(QObject *parent)
0247     : QAbstractTableModel(parent)
0248 {
0249 }
0250 
0251 FontsListModel::~FontsListModel()
0252 {
0253 }
0254 
0255 void FontsListModel::addFont(const Okular::FontInfo &fi)
0256 {
0257     beginInsertRows(QModelIndex(), m_fonts.size(), m_fonts.size());
0258 
0259     m_fonts << fi;
0260 
0261     endInsertRows();
0262 }
0263 
0264 int FontsListModel::columnCount(const QModelIndex &parent) const
0265 {
0266     return parent.isValid() ? 0 : 3;
0267 }
0268 
0269 static QString descriptionForFontType(Okular::FontInfo::FontType type)
0270 {
0271     switch (type) {
0272     case Okular::FontInfo::Type1:
0273         return i18n("Type 1");
0274         break;
0275     case Okular::FontInfo::Type1C:
0276         return i18n("Type 1C");
0277         break;
0278     case Okular::FontInfo::Type1COT:
0279         return i18nc("OT means OpenType", "Type 1C (OT)");
0280         break;
0281     case Okular::FontInfo::Type3:
0282         return i18n("Type 3");
0283         break;
0284     case Okular::FontInfo::TrueType:
0285         return i18n("TrueType");
0286         break;
0287     case Okular::FontInfo::TrueTypeOT:
0288         return i18nc("OT means OpenType", "TrueType (OT)");
0289         break;
0290     case Okular::FontInfo::CIDType0:
0291         return i18n("CID Type 0");
0292         break;
0293     case Okular::FontInfo::CIDType0C:
0294         return i18n("CID Type 0C");
0295         break;
0296     case Okular::FontInfo::CIDType0COT:
0297         return i18nc("OT means OpenType", "CID Type 0C (OT)");
0298         break;
0299     case Okular::FontInfo::CIDTrueType:
0300         return i18n("CID TrueType");
0301         break;
0302     case Okular::FontInfo::CIDTrueTypeOT:
0303         return i18nc("OT means OpenType", "CID TrueType (OT)");
0304         break;
0305     case Okular::FontInfo::TeXPK:
0306         return i18n("TeX PK");
0307         break;
0308     case Okular::FontInfo::TeXVirtual:
0309         return i18n("TeX virtual");
0310         break;
0311     case Okular::FontInfo::TeXFontMetric:
0312         return i18n("TeX Font Metric");
0313         break;
0314     case Okular::FontInfo::TeXFreeTypeHandled:
0315         return i18n("TeX FreeType-handled");
0316         break;
0317     case Okular::FontInfo::Unknown:
0318         return i18nc("Unknown font type", "Unknown");
0319         break;
0320     }
0321     return QString();
0322 }
0323 
0324 static QString pathOrDescription(const Okular::FontInfo &font)
0325 {
0326     switch (font.embedType()) {
0327     case Okular::FontInfo::NotEmbedded:
0328         return font.file();
0329         break;
0330     case Okular::FontInfo::EmbeddedSubset:
0331         return i18n("Embedded (subset)");
0332         break;
0333     case Okular::FontInfo::FullyEmbedded:
0334         return i18n("Fully embedded");
0335         break;
0336     }
0337     return QString();
0338 }
0339 
0340 static QString descriptionForEmbedType(Okular::FontInfo::EmbedType type)
0341 {
0342     switch (type) {
0343     case Okular::FontInfo::NotEmbedded:
0344         return i18n("No");
0345         break;
0346     case Okular::FontInfo::EmbeddedSubset:
0347         return i18n("Yes (subset)");
0348         break;
0349     case Okular::FontInfo::FullyEmbedded:
0350         return i18n("Yes");
0351         break;
0352     }
0353     return QString();
0354 }
0355 
0356 QVariant FontsListModel::data(const QModelIndex &index, int role) const
0357 {
0358     if (!index.isValid() || index.row() < 0 || index.row() >= m_fonts.count()) {
0359         return QVariant();
0360     }
0361 
0362     switch (role) {
0363     case Qt::DisplayRole:
0364         switch (index.column()) {
0365         case 0: {
0366             const Okular::FontInfo &fi = m_fonts.at(index.row());
0367             const QString fontname = fi.name();
0368             const QString substituteName = fi.substituteName();
0369             if (fi.embedType() == Okular::FontInfo::NotEmbedded && !substituteName.isEmpty() && !fontname.isEmpty() && substituteName != fontname) {
0370                 return i18nc("Replacing missing font with another one", "%1 (substituting with %2)", fontname, substituteName);
0371             }
0372             return fontname.isEmpty() ? i18nc("font name not available (empty)", "[n/a]") : fontname;
0373         }
0374         case 1:
0375             return descriptionForFontType(m_fonts.at(index.row()).type());
0376             break;
0377         case 2:
0378             return pathOrDescription(m_fonts.at(index.row()));
0379             break;
0380         }
0381         break;
0382     case Qt::ToolTipRole: {
0383         QString fontname = m_fonts.at(index.row()).name();
0384         if (fontname.isEmpty()) {
0385             fontname = i18n("Unknown font");
0386         }
0387         QString tooltip = QLatin1String("<html><b>") + fontname + QLatin1String("</b>");
0388         if (m_fonts.at(index.row()).embedType() == Okular::FontInfo::NotEmbedded) {
0389             tooltip += QStringLiteral(" (<span style=\"font-family: '%1'\">%2</span>)").arg(fontname, fontname);
0390         }
0391         tooltip += QLatin1String("<br />") + i18n("Embedded: %1", descriptionForEmbedType(m_fonts.at(index.row()).embedType()));
0392         tooltip += QLatin1String("</html>");
0393         return tooltip;
0394     }
0395     case IsExtractableRole: {
0396         return m_fonts.at(index.row()).canBeExtracted();
0397     }
0398     case FontInfoRole: {
0399         QVariant v;
0400         v.setValue(m_fonts.at(index.row()));
0401         return v;
0402     }
0403     }
0404 
0405     return QVariant();
0406 }
0407 
0408 QVariant FontsListModel::headerData(int section, Qt::Orientation orientation, int role) const
0409 {
0410     if (orientation != Qt::Horizontal) {
0411         return QVariant();
0412     }
0413 
0414     if (role == Qt::TextAlignmentRole) {
0415         return QVariant(Qt::AlignLeft);
0416     }
0417 
0418     if (role != Qt::DisplayRole) {
0419         return QVariant();
0420     }
0421 
0422     switch (section) {
0423     case 0:
0424         return i18n("Name");
0425         break;
0426     case 1:
0427         return i18n("Type");
0428         break;
0429     case 2:
0430         return i18n("File");
0431         break;
0432     default:
0433         return QVariant();
0434     }
0435 }
0436 
0437 int FontsListModel::rowCount(const QModelIndex &parent) const
0438 {
0439     return parent.isValid() ? 0 : m_fonts.size();
0440 }
0441 
0442 PageSizesModel::PageSizesModel(QObject *parent, Okular::Document *doc)
0443     : QAbstractTableModel(parent)
0444     , m_document(doc)
0445 {
0446 }
0447 
0448 PageSizesModel::~PageSizesModel()
0449 {
0450 }
0451 
0452 int PageSizesModel::columnCount(const QModelIndex &parent) const
0453 {
0454     return parent.isValid() ? 0 : 2;
0455 }
0456 
0457 QVariant PageSizesModel::data(const QModelIndex &index, int role) const
0458 {
0459     if (!index.isValid() || index.row() < 0 || index.row() >= (int)m_document->pages()) {
0460         return QVariant();
0461     }
0462 
0463     switch (index.column()) {
0464     case 0: {
0465         if (role == Qt::DisplayRole) {
0466             return index.row() + 1; // Page zero doesn't make sense to the user
0467         } else if (role == Qt::TextAlignmentRole) {
0468             return Qt::AlignCenter;
0469         }
0470         break;
0471     }
0472     case 1:
0473         if (role == Qt::DisplayRole) {
0474             return m_document->pageSizeString(index.row());
0475         }
0476         break;
0477     }
0478 
0479     return QVariant();
0480 }
0481 
0482 QVariant PageSizesModel::headerData(int section, Qt::Orientation orientation, int role) const
0483 {
0484     if (orientation != Qt::Horizontal) {
0485         return QVariant();
0486     }
0487 
0488     if (role == Qt::TextAlignmentRole) {
0489         return QVariant(Qt::AlignLeft);
0490     }
0491 
0492     if (role != Qt::DisplayRole) {
0493         return QVariant();
0494     }
0495 
0496     switch (section) {
0497     case 0:
0498         return i18n("Page");
0499         break;
0500     case 1:
0501         return i18n("Size");
0502         break;
0503     default:
0504         return QVariant();
0505     }
0506 }
0507 
0508 int PageSizesModel::rowCount(const QModelIndex &parent) const
0509 {
0510     return parent.isValid() ? 0 : m_document->pages();
0511 }
0512 
0513 #include "moc_propertiesdialog.cpp"
0514 
0515 /* kate: replace-tabs on; indent-width 4; */