File indexing completed on 2024-04-28 15:51:54
0001 /* 0002 SPDX-FileCopyrightText: 2002 Wilco Greven <greven@kde.org> 0003 SPDX-FileCopyrightText: 2002 Chris Cheney <ccheney@cheney.cx> 0004 SPDX-FileCopyrightText: 2002 Malcolm Hunter <malcolm.hunter@gmx.co.uk> 0005 SPDX-FileCopyrightText: 2003-2004 Christophe Devriese <Christophe.Devriese@student.kuleuven.ac.be> 0006 SPDX-FileCopyrightText: 2003 Daniel Molkentin <molkentin@kde.org> 0007 SPDX-FileCopyrightText: 2003 Andy Goossens <andygoossens@telenet.be> 0008 SPDX-FileCopyrightText: 2003 Dirk Mueller <mueller@kde.org> 0009 SPDX-FileCopyrightText: 2003 Laurent Montel <montel@kde.org> 0010 SPDX-FileCopyrightText: 2004 Dominique Devriese <devriese@kde.org> 0011 SPDX-FileCopyrightText: 2004 Christoph Cullmann <crossfire@babylon2k.de> 0012 SPDX-FileCopyrightText: 2004 Henrique Pinto <stampede@coltec.ufmg.br> 0013 SPDX-FileCopyrightText: 2004 Waldo Bastian <bastian@kde.org> 0014 SPDX-FileCopyrightText: 2004-2008 Albert Astals Cid <aacid@kde.org> 0015 SPDX-FileCopyrightText: 2004 Antti Markus <antti.markus@starman.ee> 0016 0017 Work sponsored by the LiMux project of the city of Munich: 0018 SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com> 0019 0020 SPDX-License-Identifier: GPL-2.0-or-later 0021 */ 0022 0023 #include "part.h" 0024 0025 #include "config-okular.h" 0026 0027 // qt/kde includes 0028 #include <QApplication> 0029 #include <QContextMenuEvent> 0030 #if HAVE_DBUS 0031 #include <QDBusConnection> 0032 #endif // HAVE_DBUS 0033 #include <QDialog> 0034 #include <QDialogButtonBox> 0035 #include <QFile> 0036 #include <QFileDialog> 0037 #include <QInputDialog> 0038 #include <QJsonArray> 0039 #include <QLabel> 0040 #include <QLayout> 0041 #include <QMenu> 0042 #include <QMenuBar> 0043 #include <QMimeDatabase> 0044 #include <QPrintDialog> 0045 #include <QPrintPreviewDialog> 0046 #include <QPrinter> 0047 #include <QScopedValueRollback> 0048 #include <QScrollBar> 0049 #include <QSlider> 0050 #include <QSpinBox> 0051 #include <QStandardPaths> 0052 #include <QTemporaryFile> 0053 #include <QTimer> 0054 #include <QWidgetAction> 0055 0056 #include <KAboutPluginDialog> 0057 #include <KActionCollection> 0058 #include <KActionMenu> 0059 #include <KBookmarkAction> 0060 #include <KColorSchemeManager> 0061 #include <KColorSchemeMenu> 0062 #include <KCompressionDevice> 0063 #include <KDirWatch> 0064 #include <KFilterBase> 0065 #include <KHamburgerMenu> 0066 #include <KIO/FileCopyJob> 0067 #include <KIO/Global> 0068 #include <KIO/OpenFileManagerWindowJob> 0069 #include <KIO/StatJob> 0070 #include <KJobWidgets> 0071 #include <KMainWindow> 0072 #include <KMessageBox> 0073 #include <KParts/GUIActivateEvent> 0074 #include <KPasswordDialog> 0075 #include <KPluginMetaData> 0076 #include <KSharedDataCache> 0077 #include <KStandardShortcut> 0078 #include <KToggleAction> 0079 #include <KToggleFullScreenAction> 0080 #include <KToolBar> 0081 #if HAVE_KWALLET 0082 #include <KWallet> 0083 #endif 0084 #include <KXMLGUIClient> 0085 #include <KXMLGUIFactory> 0086 0087 #if HAVE_PURPOSE 0088 #include <Purpose/AlternativesModel> 0089 #include <Purpose/Menu> 0090 #include <purpose_version.h> 0091 #endif 0092 0093 // local includes 0094 #include "aboutdata.h" 0095 #include "bookmarklist.h" 0096 #include "core/action.h" 0097 #include "core/annotations.h" 0098 #include "core/bookmarkmanager.h" 0099 #include "core/document_p.h" 0100 #include "core/form.h" 0101 #include "core/generator.h" 0102 #include "core/page.h" 0103 #include "core/printoptionswidget.h" 0104 #include "drawingtoolactions.h" 0105 #include "embeddedfilesdialog.h" 0106 #include "extensions.h" 0107 #include "fileprinterpreview.h" 0108 #include "findbar.h" 0109 #include "gui/signatureguiutils.h" 0110 #include "layers.h" 0111 #include "minibar.h" 0112 #include "okmenutitle.h" 0113 #include "pagesizelabel.h" 0114 #include "pageview.h" 0115 #include "preferencesdialog.h" 0116 #include "presentationwidget.h" 0117 #include "propertiesdialog.h" 0118 #include "searchwidget.h" 0119 #include "settings.h" 0120 #include "side_reviews.h" 0121 #include "sidebar.h" 0122 #include "signaturepanel.h" 0123 #include "thumbnaillist.h" 0124 #include "toc.h" 0125 0126 #include <memory> 0127 #include <type_traits> 0128 0129 #ifdef OKULAR_KEEP_FILE_OPEN 0130 class FileKeeper 0131 { 0132 public: 0133 FileKeeper() 0134 : m_handle(nullptr) 0135 { 0136 } 0137 0138 ~FileKeeper() 0139 { 0140 } 0141 0142 void open(const QString &path) 0143 { 0144 if (!m_handle) 0145 m_handle = std::fopen(QFile::encodeName(path).constData(), "r"); 0146 } 0147 0148 void close() 0149 { 0150 if (m_handle) { 0151 int ret = std::fclose(m_handle); 0152 Q_UNUSED(ret) 0153 m_handle = nullptr; 0154 } 0155 } 0156 0157 QTemporaryFile *copyToTemporary() const 0158 { 0159 if (!m_handle) 0160 return nullptr; 0161 0162 QTemporaryFile *retFile = new QTemporaryFile; 0163 retFile->open(); 0164 0165 std::rewind(m_handle); 0166 int c = -1; 0167 do { 0168 c = std::fgetc(m_handle); 0169 if (c == EOF) 0170 break; 0171 if (!retFile->putChar((char)c)) 0172 break; 0173 } while (!feof(m_handle)); 0174 0175 retFile->flush(); 0176 0177 return retFile; 0178 } 0179 0180 private: 0181 std::FILE *m_handle; 0182 }; 0183 #endif 0184 0185 K_PLUGIN_FACTORY_WITH_JSON(OkularPartFactory, "okular_part.json", registerPlugin<Okular::Part>();) 0186 0187 static QAction *actionForExportFormat(const Okular::ExportFormat &format, QObject *parent = Q_NULLPTR) 0188 { 0189 QAction *act = new QAction(format.description(), parent); 0190 if (!format.icon().isNull()) { 0191 act->setIcon(format.icon()); 0192 } 0193 return act; 0194 } 0195 0196 static KCompressionDevice::CompressionType compressionTypeFor(const QString &mime_to_check) 0197 { 0198 // The compressedMimeMap is here in case you have a very old shared mime database 0199 // that doesn't have inheritance info for things like gzeps, etc 0200 // Otherwise the "is()" calls below are just good enough 0201 static QHash<QString, KCompressionDevice::CompressionType> compressedMimeMap; 0202 static bool supportBzip = false; 0203 static bool supportXz = false; 0204 const QString app_gzip(QStringLiteral("application/x-gzip")); 0205 const QString app_bzip(QStringLiteral("application/x-bzip")); 0206 const QString app_xz(QStringLiteral("application/x-xz")); 0207 if (compressedMimeMap.isEmpty()) { 0208 std::unique_ptr<KFilterBase> f; 0209 compressedMimeMap[QStringLiteral("image/x-gzeps")] = KCompressionDevice::GZip; 0210 // check we can read bzip2-compressed files 0211 f.reset(KCompressionDevice::filterForCompressionType(KCompressionDevice::BZip2)); 0212 if (f.get()) { 0213 supportBzip = true; 0214 compressedMimeMap[QStringLiteral("application/x-bzpdf")] = KCompressionDevice::BZip2; 0215 compressedMimeMap[QStringLiteral("application/x-bzpostscript")] = KCompressionDevice::BZip2; 0216 compressedMimeMap[QStringLiteral("application/x-bzdvi")] = KCompressionDevice::BZip2; 0217 compressedMimeMap[QStringLiteral("image/x-bzeps")] = KCompressionDevice::BZip2; 0218 } 0219 // check if we can read XZ-compressed files 0220 f.reset(KCompressionDevice::filterForCompressionType(KCompressionDevice::Xz)); 0221 if (f.get()) { 0222 supportXz = true; 0223 } 0224 } 0225 QHash<QString, KCompressionDevice::CompressionType>::const_iterator it = compressedMimeMap.constFind(mime_to_check); 0226 if (it != compressedMimeMap.constEnd()) { 0227 return it.value(); 0228 } 0229 0230 QMimeDatabase db; 0231 QMimeType mime = db.mimeTypeForName(mime_to_check); 0232 if (mime.isValid()) { 0233 if (mime.inherits(app_gzip)) { 0234 return KCompressionDevice::GZip; 0235 } else if (supportBzip && mime.inherits(app_bzip)) { 0236 return KCompressionDevice::BZip2; 0237 } else if (supportXz && mime.inherits(app_xz)) { 0238 return KCompressionDevice::Xz; 0239 } 0240 } 0241 0242 return KCompressionDevice::None; 0243 } 0244 0245 static Okular::EmbedMode detectEmbedMode(QWidget *parentWidget, QObject *parent, const QVariantList &args) 0246 { 0247 Q_UNUSED(parentWidget); 0248 0249 if (parent && (parent->objectName().startsWith(QLatin1String("okular::Shell")) || parent->objectName().startsWith(QLatin1String("okular/okular__Shell")))) { 0250 return Okular::NativeShellMode; 0251 } 0252 0253 for (const QVariant &arg : args) { 0254 if (arg.metaType().id() == QMetaType::QString) { 0255 if (arg.toString() == QLatin1String("Print/Preview")) { 0256 return Okular::PrintPreviewMode; 0257 } else if (arg.toString() == QLatin1String("ViewerWidget")) { 0258 return Okular::ViewerWidgetMode; 0259 } 0260 } 0261 } 0262 0263 return Okular::UnknownEmbedMode; 0264 } 0265 0266 static QString detectConfigFileName(const QVariantList &args) 0267 { 0268 for (const QVariant &arg : args) { 0269 if (arg.metaType().id() == QMetaType::QString) { 0270 QString argString = arg.toString(); 0271 int separatorIndex = argString.indexOf(QStringLiteral("=")); 0272 if (separatorIndex >= 0 && argString.left(separatorIndex) == QLatin1String("ConfigFileName")) { 0273 return argString.mid(separatorIndex + 1); 0274 } 0275 } 0276 } 0277 0278 return QString(); 0279 } 0280 0281 #undef OKULAR_KEEP_FILE_OPEN 0282 0283 #ifdef OKULAR_KEEP_FILE_OPEN 0284 static bool keepFileOpen() 0285 { 0286 static bool keep_file_open = !qgetenv("OKULAR_NO_KEEP_FILE_OPEN").toInt(); 0287 return keep_file_open; 0288 } 0289 #endif 0290 0291 int Okular::Part::numberOfParts = 0; 0292 0293 namespace Okular 0294 { 0295 Part::Part(QObject *parent, const QVariantList &args) 0296 : KParts::ReadWritePart(parent) 0297 , m_tempfile(nullptr) 0298 , m_documentOpenWithPassword(false) 0299 , m_swapInsteadOfOpening(false) 0300 , m_tocEnabled(false) 0301 , m_isReloading(false) 0302 , m_fileWasRemoved(false) 0303 , m_showMenuBarAction(nullptr) 0304 , m_showFullScreenAction(nullptr) 0305 , m_cliPresentation(false) 0306 , m_cliPrint(false) 0307 , m_cliPrintAndExit(false) 0308 , m_embedMode(detectEmbedMode(widget(), parent, args)) 0309 , m_generatorGuiClient(nullptr) 0310 , m_keeper(nullptr) 0311 { 0312 // make sure that the component name is okular otherwise the XMLGUI .rc files are not found 0313 // when this part is used in an application other than okular (e.g. unit tests) 0314 setComponentName(QStringLiteral("okular"), QString()); 0315 0316 setupConfigSkeleton(args); 0317 0318 #if HAVE_DBUS 0319 numberOfParts++; 0320 if (numberOfParts == 1) { 0321 m_registerDbusName = QStringLiteral("/okular"); 0322 } else { 0323 m_registerDbusName = QStringLiteral("/okular%1").arg(numberOfParts); 0324 } 0325 QDBusConnection::sessionBus().registerObject(m_registerDbusName, this, QDBusConnection::ExportScriptableSlots); 0326 #endif // HAVE_DBUS 0327 0328 // connect the started signal to tell the job the mimetypes we like, 0329 // and get some more information from it 0330 connect(this, &KParts::ReadOnlyPart::started, this, &Part::slotJobStarted); 0331 0332 // connect the completed signal so we can put the window caption when loading remote files 0333 connect(this, QOverload<>::of(&Part::completed), this, &Part::setWindowTitleFromDocument); 0334 connect(this, &KParts::ReadOnlyPart::canceled, this, &Part::loadCancelled); 0335 0336 // create browser extension (for printing when embedded into browser) 0337 m_bExtension = new BrowserExtension(this); 0338 0339 const QStringList iconDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics"), QStandardPaths::LocateDirectory); 0340 QIcon::setFallbackSearchPaths(QIcon::fallbackSearchPaths() << iconDirs); 0341 0342 m_sidebar = new Sidebar(widget()); 0343 setWidget(m_sidebar); 0344 connect(m_sidebar, &Sidebar::urlsDropped, this, &Part::handleDroppedUrls); 0345 0346 // build the document 0347 m_document = new Okular::Document(widget()); 0348 connect(m_document, &Document::linkFind, this, &Part::slotFind); 0349 connect(m_document, &Document::linkGoToPage, this, &Part::slotGoToPage); 0350 connect(m_document, &Document::linkPresentation, this, &Part::slotShowPresentation); 0351 connect(m_document, &Document::linkEndPresentation, this, &Part::slotHidePresentation); 0352 connect(m_document, &Document::openUrl, this, &Part::openUrlFromDocument); 0353 connect(m_document->bookmarkManager(), &BookmarkManager::openUrl, this, &Part::openUrlFromBookmarks); 0354 connect(m_document, &Document::close, this, &Part::close); 0355 connect(m_document, &Document::requestPrint, this, &Part::slotPrint); 0356 connect(m_document, &Document::requestSaveAs, this, [this] { slotSaveFileAs(); }); 0357 connect(m_document, &Document::undoHistoryCleanChanged, this, [this](bool clean) { 0358 setModified(!clean); 0359 setWindowTitleFromDocument(); 0360 }); 0361 0362 if (parent && parent->metaObject()->indexOfSlot(QMetaObject::normalizedSignature("slotQuit()").constData()) != -1) { 0363 connect(m_document, SIGNAL(quit()), parent, SLOT(slotQuit())); // clazy:exclude=old-style-connect 0364 } else { 0365 connect(m_document, &Document::quit, this, &Part::cannotQuit); 0366 } 0367 // widgets: ^searchbar (toolbar containing label and SearchWidget) 0368 // m_searchToolBar = new KToolBar( parentWidget, "searchBar" ); 0369 // m_searchToolBar->boxLayout()->setSpacing( KDialog::spacingHint() ); 0370 // QLabel * sLabel = new QLabel( i18n( "&Search:" ), m_searchToolBar, "kde toolbar widget" ); 0371 // m_searchWidget = new SearchWidget( m_searchToolBar, m_document ); 0372 // sLabel->setBuddy( m_searchWidget ); 0373 // m_searchToolBar->setStretchableWidget( m_searchWidget ); 0374 0375 // [left toolbox optional item: Table of Contents] | [] 0376 m_toc = new TOC(nullptr, m_document); 0377 connect(m_toc.data(), &TOC::hasTOC, this, &Part::enableTOC); 0378 connect(m_toc.data(), &TOC::rightClick, this, &Part::slotShowTOCMenu); 0379 0380 // [left toolbox optional item: Layers] | [] 0381 m_layers = new Layers(nullptr, m_document); 0382 connect(m_layers.data(), &Layers::hasLayers, this, &Part::enableLayers); 0383 0384 // [left toolbox: Thumbnails and Bookmarks] | [] 0385 QWidget *thumbsBox = new ThumbnailsBox(nullptr); 0386 thumbsBox->layout()->setSpacing(6); 0387 m_searchWidget = new SearchWidget(thumbsBox, m_document); 0388 thumbsBox->layout()->addWidget(m_searchWidget); 0389 m_thumbnailList = new ThumbnailList(thumbsBox, m_document); 0390 thumbsBox->layout()->addWidget(m_thumbnailList); 0391 // ThumbnailController * m_tc = new ThumbnailController( thumbsBox, m_thumbnailList ); 0392 connect(m_thumbnailList.data(), &ThumbnailList::rightClick, this, &Part::slotShowMenu); 0393 m_sidebar->addItem(thumbsBox, QIcon::fromTheme(QStringLiteral("view-preview")), i18n("Thumbnails")); 0394 0395 m_sidebar->setCurrentItem(thumbsBox); 0396 0397 // [left toolbox: Reviews] | [] 0398 m_reviewsWidget = new Reviews(nullptr, m_document); 0399 m_sidebar->addItem(m_reviewsWidget, QIcon::fromTheme(QStringLiteral("draw-freehand")), i18n("Annotations")); 0400 0401 // [left toolbox: Bookmarks] | [] 0402 m_bookmarkList = new BookmarkList(m_document, nullptr); 0403 m_sidebar->addItem(m_bookmarkList, QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks")); 0404 0405 // [left toolbox optional item: Signature Panel] | [] 0406 m_signaturePanel = new SignaturePanel(m_document, nullptr); 0407 connect(m_signaturePanel.data(), &SignaturePanel::documentHasSignatures, this, &Part::enableSidebarSignaturesItem); 0408 0409 // widgets: [../miniBarContainer] | [] 0410 #ifdef OKULAR_ENABLE_MINIBAR 0411 QWidget *miniBarContainer = new QWidget(0); 0412 m_sidebar->setBottomWidget(miniBarContainer); 0413 QVBoxLayout *miniBarLayout = new QVBoxLayout(miniBarContainer); 0414 miniBarLayout->setContentsMargins(0, 0, 0, 0); 0415 // widgets: [../[spacer/..]] | [] 0416 miniBarLayout->addItem(new QSpacerItem(6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed)); 0417 // widgets: [../[../MiniBar]] | [] 0418 QFrame *bevelContainer = new QFrame(miniBarContainer); 0419 bevelContainer->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); 0420 QVBoxLayout *bevelContainerLayout = new QVBoxLayout(bevelContainer); 0421 bevelContainerLayout->setContentsMargins(4, 4, 4, 4); 0422 m_progressWidget = new ProgressWidget(bevelContainer, m_document); 0423 bevelContainerLayout->addWidget(m_progressWidget); 0424 miniBarLayout->addWidget(bevelContainer); 0425 miniBarLayout->addItem(new QSpacerItem(6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed)); 0426 #endif 0427 0428 // widgets: [] | [right 'pageView'] 0429 QWidget *rightContainer = new QWidget(nullptr); 0430 m_sidebar->setMainWidget(rightContainer); 0431 QVBoxLayout *rightLayout = new QVBoxLayout(rightContainer); 0432 rightLayout->setContentsMargins(0, 0, 0, 0); 0433 rightLayout->setSpacing(0); 0434 // KToolBar * rtb = new KToolBar( rightContainer, "mainToolBarSS" ); 0435 // rightLayout->addWidget( rtb ); 0436 m_migrationMessage = new KMessageWidget(rightContainer); 0437 m_migrationMessage->setVisible(false); 0438 m_migrationMessage->setWordWrap(true); 0439 m_migrationMessage->setMessageType(KMessageWidget::Warning); 0440 m_migrationMessage->setText( 0441 i18n("This document contains annotations or form data that were saved internally by a previous Okular version. Internal storage is <b>no longer supported</b>.<br/>Please save to a file in order to move them if you want to continue " 0442 "to edit the document.")); 0443 rightLayout->addWidget(m_migrationMessage); 0444 m_topMessage = new KMessageWidget(rightContainer); 0445 m_topMessage->setVisible(false); 0446 m_topMessage->setWordWrap(true); 0447 m_topMessage->setMessageType(KMessageWidget::Information); 0448 m_topMessage->setText(i18n("This document has embedded files. <a href=\"okular:/embeddedfiles\">Click here to see them</a> or go to File -> Embedded Files.")); 0449 m_topMessage->setIcon(QIcon::fromTheme(QStringLiteral("mail-attachment"))); 0450 connect(m_topMessage, &KMessageWidget::linkActivated, this, &Part::slotShowEmbeddedFiles); 0451 rightLayout->addWidget(m_topMessage); 0452 m_formsMessage = new KMessageWidget(rightContainer); 0453 m_formsMessage->setVisible(false); 0454 m_formsMessage->setWordWrap(true); 0455 m_formsMessage->setMessageType(KMessageWidget::Information); 0456 rightLayout->addWidget(m_formsMessage); 0457 m_infoMessage = new KMessageWidget(rightContainer); 0458 m_infoMessage->setVisible(false); 0459 m_infoMessage->setWordWrap(true); 0460 m_infoMessage->setMessageType(KMessageWidget::Information); 0461 rightLayout->addWidget(m_infoMessage); 0462 m_infoTimer = new QTimer(); 0463 m_infoTimer->setSingleShot(true); 0464 connect(m_infoTimer, &QTimer::timeout, m_infoMessage, &KMessageWidget::animatedHide); 0465 m_signatureMessage = new KMessageWidget(rightContainer); 0466 m_signatureMessage->setVisible(false); 0467 m_signatureMessage->setWordWrap(true); 0468 rightLayout->addWidget(m_signatureMessage); 0469 m_pageView = new PageView(rightContainer, m_document); 0470 rightContainer->setFocusProxy(m_pageView); 0471 QMetaObject::invokeMethod(m_pageView, "setFocus", Qt::QueuedConnection); // usability setting 0472 // m_splitter->setFocusProxy(m_pageView); 0473 connect(m_pageView.data(), &PageView::rightClick, this, &Part::slotShowMenu); 0474 connect(m_pageView, &PageView::triggerSearch, this, [this](const QString &searchText) { 0475 m_findBar->startSearch(searchText); 0476 slotShowFindBar(); 0477 }); 0478 connect(m_document, &Document::error, this, &Part::errorMessage); 0479 connect(m_document, &Document::warning, this, &Part::warningMessage); 0480 connect(m_document, &Document::notice, this, &Part::noticeMessage); 0481 connect(m_document, &Document::sourceReferenceActivated, this, &Part::slotHandleActivatedSourceReference); 0482 connect(m_pageView.data(), &PageView::fitWindowToPage, this, &Part::fitWindowToPage); 0483 rightLayout->addWidget(m_pageView); 0484 m_layers->setPageView(m_pageView); 0485 m_signaturePanel->setPageView(m_pageView); 0486 m_findBar = new FindBar(m_document, rightContainer); 0487 rightLayout->addWidget(m_findBar); 0488 m_bottomBar = new QWidget(rightContainer); 0489 QHBoxLayout *bottomBarLayout = new QHBoxLayout(m_bottomBar); 0490 m_pageSizeLabel = new PageSizeLabel(m_bottomBar, m_document); 0491 bottomBarLayout->setContentsMargins(0, 0, 0, 0); 0492 bottomBarLayout->setSpacing(0); 0493 bottomBarLayout->addItem(new QSpacerItem(5, 5, QSizePolicy::Expanding, QSizePolicy::Minimum)); 0494 m_miniBarLogic = new MiniBarLogic(this, m_document); 0495 m_miniBar = new MiniBar(m_bottomBar, m_miniBarLogic); 0496 bottomBarLayout->addWidget(m_miniBar); 0497 bottomBarLayout->addWidget(m_pageSizeLabel); 0498 rightLayout->addWidget(m_bottomBar); 0499 0500 m_pageNumberTool = new MiniBar(nullptr, m_miniBarLogic); 0501 0502 connect(m_findBar, &FindBar::forwardKeyPressEvent, m_pageView, &PageView::externalKeyPressEvent); 0503 connect(m_findBar, &FindBar::onCloseButtonPressed, m_pageView, QOverload<>::of(&PageView::setFocus)); 0504 connect(m_miniBar, &MiniBar::forwardKeyPressEvent, m_pageView, &PageView::externalKeyPressEvent); 0505 connect(m_pageView.data(), &PageView::escPressed, m_findBar, &FindBar::resetSearch); 0506 connect(m_pageNumberTool, &MiniBar::forwardKeyPressEvent, m_pageView, &PageView::externalKeyPressEvent); 0507 connect(m_pageView.data(), &PageView::requestOpenFile, this, [this](const QString &filePath, int pageNumber) { 0508 // We cheat a bit here reusing the urlsDropped signal, but at the end the output is the same, we want to open some files 0509 // urlsDropped should have just had a different name 0510 QUrl u = QUrl::fromLocalFile(filePath); 0511 u.setFragment(QStringLiteral("page=%1").arg(pageNumber)); 0512 Q_EMIT urlsDropped({u}); 0513 }); 0514 0515 connect(m_reviewsWidget.data(), &Reviews::openAnnotationWindow, m_pageView.data(), &PageView::openAnnotationWindow); 0516 0517 // add document observers 0518 m_document->addObserver(this); 0519 m_document->addObserver(m_thumbnailList); 0520 m_document->addObserver(m_pageView); 0521 m_document->registerView(m_pageView); 0522 m_document->addObserver(m_toc); 0523 m_document->addObserver(m_miniBarLogic); 0524 #ifdef OKULAR_ENABLE_MINIBAR 0525 m_document->addObserver(m_progressWidget); 0526 #endif 0527 m_document->addObserver(m_reviewsWidget); 0528 m_document->addObserver(m_pageSizeLabel); 0529 m_document->addObserver(m_bookmarkList); 0530 m_document->addObserver(m_signaturePanel); 0531 0532 connect(m_document->bookmarkManager(), &BookmarkManager::saved, this, &Part::slotRebuildBookmarkMenu); 0533 0534 setupViewerActions(); 0535 0536 if (m_embedMode != ViewerWidgetMode) { 0537 setupActions(); 0538 } else { 0539 setViewerShortcuts(); 0540 } 0541 0542 // document watcher and reloader 0543 m_watcher = new KDirWatch(this); 0544 connect(m_watcher, &KDirWatch::dirty, this, &Part::slotFileDirty); 0545 connect(m_watcher, &KDirWatch::created, this, &Part::slotFileDirty); 0546 connect(m_watcher, &KDirWatch::deleted, this, &Part::slotFileDirty); 0547 m_dirtyHandler = new QTimer(this); 0548 m_dirtyHandler->setSingleShot(true); 0549 connect(m_dirtyHandler, &QTimer::timeout, this, [this] { slotAttemptReload(); }); 0550 0551 slotNewConfig(); 0552 0553 // keep us informed when the user changes settings 0554 connect(Okular::Settings::self(), &KCoreConfigSkeleton::configChanged, this, &Part::slotNewConfig); 0555 0556 #if HAVE_SPEECH 0557 // [SPEECH] check for TTS presence and usability 0558 Okular::Settings::setUseTTS(true); 0559 Okular::Settings::self()->save(); 0560 #endif 0561 0562 rebuildBookmarkMenu(false); 0563 0564 if (m_embedMode == ViewerWidgetMode) { 0565 // set the XML-UI resource file for the viewer mode 0566 setXMLFile(QStringLiteral("part-viewermode.rc")); 0567 } else { 0568 // set our main XML-UI resource file 0569 setXMLFile(QStringLiteral("part.rc")); 0570 } 0571 0572 m_pageView->setupBaseActions(actionCollection()); 0573 0574 m_sidebar->setSidebarVisibility(false); 0575 if (m_embedMode != PrintPreviewMode) { 0576 // now set up actions that are required for all remaining modes 0577 m_pageView->setupViewerActions(actionCollection()); 0578 // and if we are not in viewer mode, we want the full GUI 0579 if (m_embedMode != ViewerWidgetMode) { 0580 unsetDummyMode(); 0581 } 0582 } 0583 0584 // ensure history actions are in the correct state 0585 updateViewActions(); 0586 0587 // also update the state of the actions in the page view 0588 m_pageView->updateActionState(false, false); 0589 0590 if (m_embedMode == NativeShellMode) { 0591 m_sidebar->setAutoFillBackground(false); 0592 } 0593 0594 #ifdef OKULAR_KEEP_FILE_OPEN 0595 m_keeper = new FileKeeper(); 0596 #endif 0597 } 0598 0599 void Part::setupConfigSkeleton(const QVariantList &args) 0600 { 0601 const QLatin1String configFileName("okularpartrc"); 0602 0603 // first, we check if a config file name has been specified 0604 QString configFilePath = detectConfigFileName(args); 0605 0606 if (configFilePath.isEmpty()) { 0607 configFilePath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1Char('/') + configFileName; 0608 } 0609 0610 KSharedConfigPtr config = KSharedConfig::openConfig(configFilePath); 0611 0612 // Configuration update: SlidesTransitionsEnabled -> SlidesTransition = NoTransitions. 0613 // See https://invent.kde.org/graphics/okular/-/merge_requests/357 0614 KConfigGroup slidesConfigGroup = config.data()->group(QStringLiteral("Dlg Presentation")); 0615 if (slidesConfigGroup.readEntry<bool>("SlidesTransitionsEnabled", true) == false) { 0616 slidesConfigGroup.writeEntry("SlidesTransition", "NoTransitions"); 0617 } 0618 slidesConfigGroup.deleteEntry("SlidesTransitionsEnabled"); 0619 config.data()->sync(); 0620 0621 Okular::Settings::instance(config); 0622 } 0623 0624 void Part::setupViewerActions() 0625 { 0626 // ACTIONS 0627 KActionCollection *ac = actionCollection(); 0628 0629 // Page Traversal actions 0630 m_gotoPage = KStandardAction::gotoPage(this, SLOT(slotGoToPage()), ac); 0631 ac->setDefaultShortcuts(m_gotoPage, KStandardShortcut::gotoLine()); 0632 // dirty way to activate gotopage when pressing miniBar's button 0633 connect(m_miniBar.data(), &MiniBar::gotoPage, m_gotoPage, &QAction::trigger); 0634 connect(m_pageNumberTool.data(), &MiniBar::gotoPage, m_gotoPage, &QAction::trigger); 0635 0636 m_prevPage = KStandardAction::prior(this, SLOT(slotPreviousPage()), ac); 0637 m_prevPage->setIconText(i18nc("Previous page", "Previous")); 0638 m_prevPage->setToolTip(i18n("Go back to the Previous Page")); 0639 m_prevPage->setWhatsThis(i18n("Moves to the previous page of the document")); 0640 ac->setDefaultShortcut(m_prevPage, QKeySequence()); 0641 // dirty way to activate prev page when pressing miniBar's button 0642 connect(m_miniBar.data(), &MiniBar::prevPage, m_prevPage, &QAction::trigger); 0643 connect(m_pageNumberTool.data(), &MiniBar::prevPage, m_prevPage, &QAction::trigger); 0644 #ifdef OKULAR_ENABLE_MINIBAR 0645 connect(m_progressWidget, SIGNAL(prevPage()), m_prevPage, SLOT(trigger())); 0646 #endif 0647 0648 m_nextPage = KStandardAction::next(this, SLOT(slotNextPage()), ac); 0649 m_nextPage->setIconText(i18nc("Next page", "Next")); 0650 m_nextPage->setToolTip(i18n("Advance to the Next Page")); 0651 m_nextPage->setWhatsThis(i18n("Moves to the next page of the document")); 0652 ac->setDefaultShortcut(m_nextPage, QKeySequence()); 0653 // dirty way to activate next page when pressing miniBar's button 0654 connect(m_miniBar.data(), &MiniBar::nextPage, m_nextPage, &QAction::trigger); 0655 connect(m_pageNumberTool.data(), &MiniBar::nextPage, m_nextPage, &QAction::trigger); 0656 #ifdef OKULAR_ENABLE_MINIBAR 0657 connect(m_progressWidget, SIGNAL(nextPage()), m_nextPage, SLOT(trigger())); 0658 #endif 0659 0660 m_beginningOfDocument = KStandardAction::firstPage(this, SLOT(slotGotoFirst()), ac); 0661 ac->addAction(QStringLiteral("first_page"), m_beginningOfDocument); 0662 m_beginningOfDocument->setText(i18n("Beginning of the document")); 0663 m_beginningOfDocument->setWhatsThis(i18n("Moves to the beginning of the document")); 0664 0665 m_endOfDocument = KStandardAction::lastPage(this, SLOT(slotGotoLast()), ac); 0666 ac->addAction(QStringLiteral("last_page"), m_endOfDocument); 0667 m_endOfDocument->setText(i18n("End of the document")); 0668 m_endOfDocument->setWhatsThis(i18n("Moves to the end of the document")); 0669 0670 // we do not want back and next in history in the dummy mode 0671 m_historyBack = nullptr; 0672 m_historyNext = nullptr; 0673 0674 m_addBookmark = KStandardAction::addBookmark(this, SLOT(slotAddBookmark()), ac); 0675 m_addBookmarkText = m_addBookmark->text(); 0676 m_addBookmarkIcon = m_addBookmark->icon(); 0677 m_bookmarkList->setAddBookmarkAction(m_addBookmark); 0678 0679 m_renameBookmark = ac->addAction(QStringLiteral("rename_bookmark")); 0680 m_renameBookmark->setText(i18n("Rename Bookmark")); 0681 m_renameBookmark->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); 0682 m_renameBookmark->setWhatsThis(i18n("Rename the current bookmark")); 0683 connect(m_renameBookmark, &QAction::triggered, this, &Part::slotRenameCurrentViewportBookmark); 0684 0685 m_prevBookmark = ac->addAction(QStringLiteral("previous_bookmark")); 0686 m_prevBookmark->setText(i18n("Previous Bookmark")); 0687 m_prevBookmark->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); 0688 m_prevBookmark->setWhatsThis(i18n("Go to the previous bookmark")); 0689 connect(m_prevBookmark, &QAction::triggered, this, &Part::slotPreviousBookmark); 0690 0691 m_nextBookmark = ac->addAction(QStringLiteral("next_bookmark")); 0692 m_nextBookmark->setText(i18n("Next Bookmark")); 0693 m_nextBookmark->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); 0694 m_nextBookmark->setWhatsThis(i18n("Go to the next bookmark")); 0695 connect(m_nextBookmark, &QAction::triggered, this, &Part::slotNextBookmark); 0696 0697 m_copy = nullptr; 0698 0699 m_selectAll = nullptr; 0700 m_selectCurrentPage = nullptr; 0701 0702 // Find and other actions 0703 m_find = KStandardAction::find(this, SLOT(slotShowFindBar()), ac); 0704 QList<QKeySequence> s = m_find->shortcuts(); 0705 s.append(QKeySequence(Qt::Key_Slash)); 0706 ac->setDefaultShortcuts(m_find, s); 0707 m_find->setEnabled(false); 0708 0709 m_findNext = KStandardAction::findNext(this, SLOT(slotFindNext()), ac); 0710 m_findNext->setEnabled(false); 0711 0712 m_findPrev = KStandardAction::findPrev(this, SLOT(slotFindPrev()), ac); 0713 m_findPrev->setEnabled(false); 0714 0715 m_save = nullptr; 0716 m_saveAs = nullptr; 0717 m_openContainingFolder = nullptr; 0718 0719 m_hamburgerMenuAction = nullptr; 0720 0721 QAction *prefs = KStandardAction::preferences(this, SLOT(slotPreferences()), ac); 0722 if (m_embedMode == NativeShellMode) { 0723 prefs->setText(i18n("Configure Okular...")); 0724 } else { 0725 // TODO: improve this message 0726 prefs->setText(i18n("Configure Viewer...")); 0727 } 0728 0729 QAction *genPrefs = new QAction(ac); 0730 ac->addAction(QStringLiteral("options_configure_generators"), genPrefs); 0731 if (m_embedMode == ViewerWidgetMode) { 0732 genPrefs->setText(i18n("Configure Viewer Backends...")); 0733 } else { 0734 genPrefs->setText(i18n("Configure Backends...")); 0735 } 0736 genPrefs->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); 0737 genPrefs->setEnabled(m_document->configurableGenerators() > 0); 0738 connect(genPrefs, &QAction::triggered, this, &Part::slotGeneratorPreferences); 0739 0740 m_printPreview = KStandardAction::printPreview(this, SLOT(slotPrintPreview()), ac); 0741 m_printPreview->setEnabled(false); 0742 0743 m_showLeftPanel = nullptr; 0744 m_showBottomBar = nullptr; 0745 m_showSignaturePanel = nullptr; 0746 0747 m_showProperties = ac->addAction(QStringLiteral("properties")); 0748 m_showProperties->setText(i18n("&Properties")); 0749 m_showProperties->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); 0750 ac->setDefaultShortcuts(m_showProperties, {QKeySequence(Qt::ALT | Qt::Key_Return)}); 0751 connect(m_showProperties, &QAction::triggered, this, &Part::slotShowProperties); 0752 m_showProperties->setEnabled(false); 0753 0754 m_showEmbeddedFiles = nullptr; 0755 m_showPresentation = nullptr; 0756 0757 m_exportAs = nullptr; 0758 m_exportAsMenu = nullptr; 0759 m_exportAsText = nullptr; 0760 m_exportAsDocArchive = nullptr; 0761 0762 #if HAVE_PURPOSE 0763 m_share = nullptr; 0764 m_shareMenu = nullptr; 0765 #endif 0766 0767 m_presentationDrawingActions = nullptr; 0768 0769 m_aboutBackend = ac->addAction(QStringLiteral("help_about_backend")); 0770 m_aboutBackend->setText(i18n("About Backend")); 0771 m_aboutBackend->setEnabled(false); 0772 connect(m_aboutBackend, &QAction::triggered, this, &Part::slotAboutBackend); 0773 0774 QAction *reload = ac->add<QAction>(QStringLiteral("file_reload")); 0775 reload->setText(i18n("Reloa&d")); 0776 reload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); 0777 reload->setWhatsThis(i18n("Reload the current document from disk.")); 0778 connect(reload, &QAction::triggered, this, &Part::slotReload); 0779 ac->setDefaultShortcuts(reload, KStandardShortcut::reload()); 0780 m_reload = reload; 0781 0782 m_closeFindBar = ac->addAction(QStringLiteral("close_find_bar"), this, SLOT(slotHideFindBar())); 0783 m_closeFindBar->setText(i18n("Close &Find Bar")); 0784 ac->setDefaultShortcut(m_closeFindBar, QKeySequence(Qt::Key_Escape)); 0785 m_closeFindBar->setEnabled(false); 0786 0787 QWidgetAction *pageno = new QWidgetAction(ac); 0788 pageno->setText(i18n("Page Number")); 0789 pageno->setDefaultWidget(m_pageNumberTool); 0790 ac->addAction(QStringLiteral("page_number"), pageno); 0791 0792 QAction *configureColorModes = new QAction(i18nc("@action", "Configure Color Modes..."), ac); 0793 ac->addAction(QStringLiteral("options_configure_color_modes"), configureColorModes); 0794 configureColorModes->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); 0795 connect(configureColorModes, &QAction::triggered, this, &Part::slotAccessibilityPreferences); 0796 } 0797 0798 void Part::setViewerShortcuts() 0799 { 0800 KActionCollection *ac = actionCollection(); 0801 0802 ac->setDefaultShortcut(m_gotoPage, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_G)); 0803 ac->setDefaultShortcut(m_find, QKeySequence()); 0804 0805 ac->setDefaultShortcut(m_findNext, QKeySequence()); 0806 ac->setDefaultShortcut(m_findPrev, QKeySequence()); 0807 0808 ac->setDefaultShortcut(m_addBookmark, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_B)); 0809 0810 ac->setDefaultShortcut(m_beginningOfDocument, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Home)); 0811 ac->setDefaultShortcut(m_endOfDocument, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_End)); 0812 0813 QAction *action = static_cast<QAction *>(ac->action(QStringLiteral("file_reload"))); 0814 if (action) { 0815 ac->setDefaultShortcut(action, QKeySequence(Qt::ALT | Qt::Key_F5)); 0816 } 0817 } 0818 0819 void Part::setupActions() 0820 { 0821 KActionCollection *ac = actionCollection(); 0822 0823 auto manager = new KColorSchemeManager(this); 0824 KActionMenu *schemeMenu = KColorSchemeMenu::createMenu(manager, this); 0825 ac->addAction(QStringLiteral("colorscheme_menu"), schemeMenu->menu()->menuAction()); 0826 0827 m_copy = KStandardAction::create(KStandardAction::Copy, m_pageView, SLOT(copyTextSelection()), ac); 0828 0829 m_selectAll = KStandardAction::selectAll(m_pageView, SLOT(selectAll()), ac); 0830 0831 // Setup select all action for the current page 0832 m_selectCurrentPage = ac->addAction(QStringLiteral("edit_select_all_current_page")); 0833 m_selectCurrentPage->setText(i18n("Select All Text on Current Page")); 0834 connect(m_selectCurrentPage, &QAction::triggered, m_pageView, &PageView::slotSelectPage); 0835 m_selectCurrentPage->setEnabled(false); 0836 0837 m_save = KStandardAction::save( 0838 this, [this] { saveFile(); }, ac); 0839 m_save->setEnabled(false); 0840 0841 m_saveAs = KStandardAction::saveAs(this, SLOT(slotSaveFileAs()), ac); 0842 m_saveAs->setEnabled(false); 0843 m_migrationMessage->addAction(m_saveAs); 0844 0845 m_showLeftPanel = ac->add<KToggleAction>(QStringLiteral("show_leftpanel")); 0846 m_showLeftPanel->setText(i18n("Show S&idebar")); 0847 const QString preferredSidebarIcon = m_sidebar->layoutDirection() == Qt::LeftToRight ? QStringLiteral("sidebar-expand-left") : QStringLiteral("sidebar-expand-right"); 0848 m_showLeftPanel->setIcon(QIcon::fromTheme(preferredSidebarIcon, QIcon::fromTheme(QStringLiteral("view-sidetree")))); 0849 connect(m_showLeftPanel, &QAction::toggled, this, &Part::slotShowLeftPanel); 0850 ac->setDefaultShortcut(m_showLeftPanel, QKeySequence(Qt::Key_F7)); 0851 m_showLeftPanel->setChecked(Okular::Settings::showLeftPanel()); 0852 slotShowLeftPanel(); 0853 0854 m_showBottomBar = ac->add<KToggleAction>(QStringLiteral("show_bottombar")); 0855 m_showBottomBar->setText(i18n("Show &Page Bar")); 0856 connect(m_showBottomBar, &QAction::toggled, this, &Part::slotShowBottomBar); 0857 m_showBottomBar->setChecked(Okular::Settings::showBottomBar()); 0858 slotShowBottomBar(); 0859 0860 m_showSignaturePanel = ac->add<QAction>(QStringLiteral("show_signatures")); 0861 m_showSignaturePanel->setText(i18n("Show &Signatures Panel")); 0862 connect(m_showSignaturePanel, &QAction::triggered, this, [this] { 0863 if (m_sidebar->currentItem() != m_signaturePanel) { 0864 m_sidebar->setCurrentItem(m_signaturePanel); 0865 } 0866 if (!m_showLeftPanel->isChecked()) { 0867 m_showLeftPanel->trigger(); 0868 } 0869 }); 0870 0871 m_showEmbeddedFiles = ac->addAction(QStringLiteral("embedded_files")); 0872 m_showEmbeddedFiles->setText(i18n("&Embedded Files")); 0873 m_showEmbeddedFiles->setIcon(QIcon::fromTheme(QStringLiteral("mail-attachment"))); 0874 connect(m_showEmbeddedFiles, &QAction::triggered, this, &Part::slotShowEmbeddedFiles); 0875 m_showEmbeddedFiles->setEnabled(false); 0876 0877 m_exportAs = ac->addAction(QStringLiteral("file_export_as")); 0878 m_exportAs->setText(i18n("E&xport As")); 0879 m_exportAs->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); 0880 m_exportAsMenu = new QMenu(); 0881 connect(m_exportAsMenu, &QMenu::triggered, this, &Part::slotExportAs); 0882 m_exportAs->setMenu(m_exportAsMenu); 0883 m_exportAsText = actionForExportFormat(Okular::ExportFormat::standardFormat(Okular::ExportFormat::PlainText), m_exportAsMenu); 0884 m_exportAsMenu->addAction(m_exportAsText); 0885 m_exportAs->setEnabled(false); 0886 m_exportAsText->setEnabled(false); 0887 0888 #if HAVE_PURPOSE 0889 m_share = ac->addAction(QStringLiteral("file_share")); 0890 m_share->setText(i18n("S&hare")); 0891 m_share->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); 0892 m_share->setEnabled(false); 0893 m_shareMenu = new Purpose::Menu(); 0894 connect(m_shareMenu, &Purpose::Menu::finished, this, &Part::slotShareActionFinished); 0895 m_share->setMenu(m_shareMenu); 0896 #endif 0897 0898 m_showPresentation = ac->addAction(QStringLiteral("presentation")); 0899 m_showPresentation->setText(i18n("P&resentation")); 0900 m_showPresentation->setIcon(QIcon::fromTheme(QStringLiteral("view-presentation"))); 0901 connect(m_showPresentation, &QAction::triggered, this, &Part::slotShowPresentation); 0902 ac->setDefaultShortcut(m_showPresentation, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_P)); 0903 m_showPresentation->setEnabled(false); 0904 0905 m_openContainingFolder = ac->addAction(QStringLiteral("open_containing_folder")); 0906 m_openContainingFolder->setText(i18n("Open Con&taining Folder")); 0907 m_openContainingFolder->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder"))); 0908 connect(m_openContainingFolder, &QAction::triggered, this, &Part::slotOpenContainingFolder); 0909 m_openContainingFolder->setEnabled(false); 0910 0911 if (m_embedMode == Okular::NativeShellMode) { // This hamburger menu is designed to be quite Okular-specific. 0912 m_hamburgerMenuAction = KStandardAction::hamburgerMenu(nullptr, nullptr, ac); 0913 if (auto *mainWindow = findMainWindow()) { 0914 m_hamburgerMenuAction->setMenuBar(mainWindow->menuBar()); 0915 } 0916 connect(m_hamburgerMenuAction, &KHamburgerMenu::aboutToShowMenu, this, &Part::slotUpdateHamburgerMenu); 0917 } 0918 0919 QAction *importPS = ac->addAction(QStringLiteral("import_ps")); 0920 importPS->setText(i18n("&Import PostScript as PDF...")); 0921 importPS->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); 0922 connect(importPS, &QAction::triggered, this, &Part::slotImportPSFile); 0923 0924 KToggleAction *blackscreenAction = new KToggleAction(i18n("Switch Blackscreen Mode"), ac); 0925 ac->addAction(QStringLiteral("switch_blackscreen_mode"), blackscreenAction); 0926 ac->setDefaultShortcut(blackscreenAction, QKeySequence(Qt::Key_B)); 0927 blackscreenAction->setIcon(QIcon::fromTheme(QStringLiteral("view-presentation"))); 0928 blackscreenAction->setEnabled(false); 0929 0930 m_presentationDrawingActions = new DrawingToolActions(ac); 0931 0932 QAction *eraseDrawingAction = new QAction(i18n("Erase Drawing"), ac); 0933 ac->addAction(QStringLiteral("presentation_erase_drawings"), eraseDrawingAction); 0934 eraseDrawingAction->setIcon(QIcon::fromTheme(QStringLiteral("draw-eraser-delete-objects"))); 0935 eraseDrawingAction->setEnabled(false); 0936 0937 QAction *configureAnnotations = new QAction(i18n("Configure Annotations..."), ac); 0938 ac->addAction(QStringLiteral("options_configure_annotations"), configureAnnotations); 0939 configureAnnotations->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); 0940 connect(configureAnnotations, &QAction::triggered, this, &Part::slotAnnotationPreferences); 0941 0942 QAction *playPauseAction = new QAction(i18n("Play/Pause Presentation"), ac); 0943 ac->addAction(QStringLiteral("presentation_play_pause"), playPauseAction); 0944 playPauseAction->setEnabled(false); 0945 } 0946 0947 Part::~Part() 0948 { 0949 #if HAVE_DBUS 0950 QDBusConnection::sessionBus().unregisterObject(m_registerDbusName); 0951 #endif // HAVE_DBUS 0952 0953 m_document->removeObserver(this); 0954 0955 if (m_document->isOpened()) { 0956 Part::closeUrl(false); 0957 } 0958 0959 delete m_toc; 0960 delete m_layers; 0961 delete m_pageView; 0962 delete m_thumbnailList; 0963 delete m_miniBar; 0964 delete m_pageNumberTool; 0965 delete m_miniBarLogic; 0966 delete m_bottomBar; 0967 #ifdef OKULAR_ENABLE_MINIBAR 0968 delete m_progressWidget; 0969 #endif 0970 delete m_pageSizeLabel; 0971 delete m_reviewsWidget; 0972 delete m_bookmarkList; 0973 delete m_infoTimer; 0974 delete m_signaturePanel; 0975 0976 delete m_document; 0977 0978 delete m_tempfile; 0979 0980 qDeleteAll(m_bookmarkActions); 0981 0982 delete m_exportAsMenu; 0983 #if HAVE_PURPOSE 0984 delete m_shareMenu; 0985 #endif 0986 0987 #ifdef OKULAR_KEEP_FILE_OPEN 0988 delete m_keeper; 0989 #endif 0990 } 0991 0992 bool Part::openDocument(const QUrl &url, uint page) 0993 { 0994 Okular::DocumentViewport vp(page - 1); 0995 vp.rePos.enabled = true; 0996 vp.rePos.normalizedX = 0; 0997 vp.rePos.normalizedY = 0; 0998 vp.rePos.pos = Okular::DocumentViewport::TopLeft; 0999 if (vp.isValid()) { 1000 m_document->setNextDocumentViewport(vp); 1001 } 1002 return openUrl(url); 1003 } 1004 1005 void Part::startPresentation() 1006 { 1007 m_cliPresentation = true; 1008 } 1009 1010 QStringList Part::supportedMimeTypes() const 1011 { 1012 return m_document->supportedMimeTypes(); 1013 } 1014 1015 QUrl Part::realUrl() const 1016 { 1017 if (!m_realUrl.isEmpty()) { 1018 return m_realUrl; 1019 } 1020 1021 return url(); 1022 } 1023 1024 // ViewerInterface 1025 1026 void Part::showSourceLocation(const QString &fileName, int line, int column, bool showGraphically) 1027 { 1028 Q_UNUSED(column); 1029 1030 const QString u = QStringLiteral("src:%1 %2").arg(line + 1).arg(fileName); 1031 GotoAction action(QString(), u); 1032 m_document->processAction(&action); 1033 if (showGraphically) { 1034 m_pageView->setLastSourceLocationViewport(m_document->viewport()); 1035 } 1036 } 1037 1038 void Part::clearLastShownSourceLocation() 1039 { 1040 m_pageView->clearLastSourceLocationViewport(); 1041 } 1042 1043 bool Part::isWatchFileModeEnabled() const 1044 { 1045 return !m_watcher->signalsBlocked(); 1046 } 1047 1048 void Part::setWatchFileModeEnabled(bool enabled) 1049 { 1050 // Don't call 'KDirWatch::stopScan()' in here (as of KDE Frameworks 5.51.0, see bug 400541)! 1051 // 'KDirWatch::stopScan' has a bug that may affect other code paths that make use of KDirWatch 1052 // (other loaded KParts, for example). 1053 if (isWatchFileModeEnabled() == enabled) { 1054 return; 1055 } 1056 1057 m_watcher->blockSignals(!enabled); 1058 1059 if (!enabled) { 1060 m_dirtyHandler->stop(); 1061 } 1062 } 1063 1064 bool Part::areSourceLocationsShownGraphically() const 1065 { 1066 return m_pageView->areSourceLocationsShownGraphically(); 1067 } 1068 1069 void Part::setShowSourceLocationsGraphically(bool show) 1070 { 1071 m_pageView->setShowSourceLocationsGraphically(show); 1072 } 1073 1074 bool Part::openNewFilesInTabs() const 1075 { 1076 return Okular::Settings::self()->shellOpenFileInTabs(); 1077 } 1078 1079 QWidget *Part::getSideContainer() const 1080 { 1081 return m_sidebar->getSideContainer(); 1082 } 1083 1084 bool Part::activateTabIfAlreadyOpenFile() const 1085 { 1086 return Okular::Settings::self()->switchToTabIfOpen(); 1087 } 1088 1089 void Part::setModified(bool modified) 1090 { 1091 KParts::ReadWritePart::setModified(modified); 1092 1093 if (modified && !m_save->isEnabled()) { 1094 if (!m_warnedAboutModifyingUnsaveableDocument) { 1095 m_warnedAboutModifyingUnsaveableDocument = true; 1096 KMessageBox::information(widget(), 1097 i18n("You have just modified the open document, but this kind of document can not be saved.\nAny modification will be lost once Okular is closed."), 1098 i18n("Document can't be saved"), 1099 QStringLiteral("warnAboutUnsaveableDocuments")); 1100 } 1101 } 1102 } 1103 1104 void Part::slotHandleActivatedSourceReference(const QString &absFileName, int line, int col, bool *handled) 1105 { 1106 Q_EMIT openSourceReference(absFileName, line, col); 1107 if (m_embedMode == Okular::ViewerWidgetMode) { 1108 *handled = true; 1109 } 1110 } 1111 1112 void Part::openUrlFromDocument(const QUrl &url) 1113 { 1114 if (m_embedMode == PrintPreviewMode) { 1115 return; 1116 } 1117 1118 if (url.isLocalFile()) { 1119 if (!QFile::exists(url.toLocalFile())) { 1120 KMessageBox::error(widget(), i18n("Could not open '%1'. File does not exist", url.toDisplayString())); 1121 return; 1122 } 1123 } else { 1124 KIO::StatJob *statJob = KIO::stat(url, KIO::StatJob::SourceSide); 1125 KJobWidgets::setWindow(statJob, widget()); 1126 if (!statJob->exec() || statJob->error()) { 1127 KMessageBox::error(widget(), i18n("Could not open '%1' (%2) ", url.toDisplayString(), statJob->errorString())); 1128 return; 1129 } 1130 } 1131 1132 Q_EMIT m_bExtension->openUrlNotify(); 1133 Q_EMIT m_bExtension->setLocationBarUrl(url.toDisplayString()); 1134 openUrl(url); 1135 } 1136 1137 void Part::openUrlFromBookmarks(const QUrl &_url) 1138 { 1139 QUrl url = _url; 1140 Okular::DocumentViewport vp(_url.fragment(QUrl::FullyDecoded)); 1141 if (vp.isValid()) { 1142 m_document->setNextDocumentViewport(vp); 1143 } 1144 url.setFragment(QString()); 1145 if (m_document->currentDocument() == url) { 1146 if (vp.isValid()) { 1147 m_document->setViewport(vp); 1148 } 1149 } else { 1150 openUrl(url); 1151 } 1152 } 1153 1154 void Part::handleDroppedUrls(const QList<QUrl> &urls) 1155 { 1156 if (urls.isEmpty()) { 1157 return; 1158 } 1159 1160 if (m_embedMode != NativeShellMode || !openNewFilesInTabs()) { 1161 openUrlFromDocument(urls.first()); 1162 return; 1163 } 1164 1165 Q_EMIT urlsDropped(urls); 1166 } 1167 1168 void Part::slotJobStarted(KIO::Job *job) 1169 { 1170 if (job) { 1171 QStringList supportedMimeTypes = m_document->supportedMimeTypes(); 1172 job->addMetaData(QStringLiteral("accept"), supportedMimeTypes.join(QStringLiteral(", ")) + QStringLiteral(", */*;q=0.5")); 1173 1174 connect(job, &KJob::result, this, &Part::slotJobFinished); 1175 } 1176 } 1177 1178 void Part::slotJobFinished(KJob *job) 1179 { 1180 if (job->error() == KIO::ERR_USER_CANCELED) { 1181 m_pageView->displayMessage(i18n("The loading of %1 has been canceled.", realUrl().toDisplayString(QUrl::PreferLocalFile))); 1182 } 1183 } 1184 1185 void Part::loadCancelled(const QString &reason) 1186 { 1187 Q_EMIT setWindowCaption(QString()); 1188 resetStartArguments(); 1189 1190 // when m_viewportDirty.pageNumber != -1 we come from slotAttemptReload 1191 // so we don't want to show an ugly messagebox just because the document is 1192 // taking more than usual to be recreated 1193 if (m_viewportDirty.pageNumber == -1) { 1194 if (m_urlWithFragment.isValid() && !m_urlWithFragment.isLocalFile()) { 1195 tryOpeningUrlWithFragmentAsName(); 1196 } else if (!reason.isEmpty()) { 1197 KMessageBox::error(widget(), i18n("Could not open %1. Reason: %2", url().toDisplayString(), reason)); 1198 } 1199 } 1200 } 1201 1202 void Part::setWindowTitleFromDocument() 1203 { 1204 // If 'DocumentTitle' should be used, check if the document has one. If 1205 // either case is false, use the file name. 1206 QString title = Okular::Settings::displayDocumentNameOrPath() == Okular::Settings::EnumDisplayDocumentNameOrPath::Path ? realUrl().toDisplayString(QUrl::PreferLocalFile) : realUrl().fileName(); 1207 1208 if (Okular::Settings::displayDocumentTitle()) { 1209 const QString docTitle = m_document->metaData(QStringLiteral("DocumentTitle")).toString(); 1210 if (!docTitle.isEmpty() && !docTitle.trimmed().isEmpty()) { 1211 title = docTitle; 1212 } 1213 } 1214 1215 Q_EMIT setWindowCaption(title); 1216 } 1217 1218 KConfigDialog *Part::slotGeneratorPreferences() 1219 { 1220 // Create dialog 1221 KConfigDialog *dialog = new Okular::BackendConfigDialog(m_pageView, QStringLiteral("generator_prefs"), Okular::Settings::self()); 1222 dialog->setAttribute(Qt::WA_DeleteOnClose); 1223 1224 if (m_embedMode == ViewerWidgetMode) { 1225 dialog->setWindowTitle(i18n("Configure Viewer Backends")); 1226 } else { 1227 dialog->setWindowTitle(i18n("Configure Backends")); 1228 } 1229 1230 m_document->fillConfigDialog(dialog); 1231 1232 // Show it 1233 dialog->setWindowModality(Qt::ApplicationModal); 1234 dialog->show(); 1235 1236 return dialog; 1237 } 1238 1239 void Part::notifySetup(const QVector<Okular::Page *> & /*pages*/, int setupFlags) 1240 { 1241 // Hide the migration message if the user has just migrated. Otherwise, 1242 // if m_migrationMessage is already hidden, this does nothing. 1243 if (!m_document->isDocdataMigrationNeeded()) { 1244 m_migrationMessage->animatedHide(); 1245 } 1246 1247 if (!(setupFlags & Okular::DocumentObserver::DocumentChanged)) { 1248 return; 1249 } 1250 1251 rebuildBookmarkMenu(); 1252 updateAboutBackendAction(); 1253 m_findBar->resetSearch(); 1254 m_searchWidget->setEnabled(m_document->supportsSearching()); 1255 } 1256 1257 void Part::notifyViewportChanged(bool /*smoothMove*/) 1258 { 1259 updateViewActions(); 1260 } 1261 1262 void Part::notifyPageChanged(int page, int flags) 1263 { 1264 if (!(flags & Okular::DocumentObserver::Bookmark)) { 1265 return; 1266 } 1267 1268 rebuildBookmarkMenu(); 1269 if (page == m_document->viewport().pageNumber) { 1270 updateBookmarksActions(); 1271 } 1272 } 1273 1274 void Part::goToPage(uint page) 1275 { 1276 if (page <= m_document->pages()) { 1277 m_document->setViewportPage(page - 1); 1278 } 1279 } 1280 1281 void Part::openDocument(const QString &doc) 1282 { 1283 openUrl(QUrl::fromUserInput(doc)); 1284 } 1285 1286 uint Part::pages() 1287 { 1288 return m_document->pages(); 1289 } 1290 1291 uint Part::currentPage() 1292 { 1293 return m_document->pages() ? m_document->currentPage() + 1 : 0; 1294 } 1295 1296 QString Part::currentDocument() 1297 { 1298 return m_document->currentDocument().toDisplayString(QUrl::PreferLocalFile); 1299 } 1300 1301 QString Part::documentMetaData(const QString &metaData) const 1302 { 1303 const Okular::DocumentInfo info = m_document->documentInfo(); 1304 return info.get(metaData); 1305 } 1306 1307 bool Part::slotImportPSFile() 1308 { 1309 QString app = QStandardPaths::findExecutable(QStringLiteral("ps2pdf")); 1310 if (app.isEmpty()) { 1311 // TODO point the user to their distro packages? 1312 KMessageBox::error(widget(), i18n("The program \"ps2pdf\" was not found, so Okular can not import PS files using it."), i18n("ps2pdf not found")); 1313 return false; 1314 } 1315 1316 QMimeDatabase mimeDatabase; 1317 QString filter = i18n("PostScript files (%1)", mimeDatabase.mimeTypeForName(QStringLiteral("application/postscript")).globPatterns().join(QLatin1Char(' '))); 1318 1319 QUrl url = QFileDialog::getOpenFileUrl(widget(), QString(), QUrl(), filter); 1320 if (url.isLocalFile()) { 1321 QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.pdf")); 1322 tf.setAutoRemove(false); 1323 if (!tf.open()) { 1324 return false; 1325 } 1326 m_temporaryLocalFile = tf.fileName(); 1327 tf.close(); 1328 1329 setLocalFilePath(url.toLocalFile()); 1330 QStringList args; 1331 QProcess *p = new QProcess(); 1332 args << url.toLocalFile() << m_temporaryLocalFile; 1333 m_pageView->displayMessage(i18n("Importing PS file as PDF (this may take a while)...")); 1334 connect(p, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &Part::psTransformEnded); 1335 p->start(app, args); 1336 return true; 1337 } 1338 1339 m_temporaryLocalFile.clear(); 1340 return false; 1341 } 1342 1343 void Part::setFileToWatch(const QString &filePath) 1344 { 1345 if (!m_watchedFilePath.isEmpty()) { 1346 unsetFileToWatch(); 1347 } 1348 1349 const QFileInfo fi(filePath); 1350 1351 m_watchedFilePath = filePath; 1352 m_watcher->addFile(m_watchedFilePath); 1353 1354 if (fi.isSymLink()) { 1355 m_watchedFileSymlinkTarget = fi.symLinkTarget(); 1356 m_watcher->addFile(m_watchedFileSymlinkTarget); 1357 } else { 1358 m_watchedFileSymlinkTarget.clear(); 1359 } 1360 } 1361 1362 void Part::unsetFileToWatch() 1363 { 1364 if (m_watchedFilePath.isEmpty()) { 1365 return; 1366 } 1367 1368 m_watcher->removeFile(m_watchedFilePath); 1369 1370 if (!m_watchedFileSymlinkTarget.isEmpty()) { 1371 m_watcher->removeFile(m_watchedFileSymlinkTarget); 1372 } 1373 1374 m_watchedFilePath.clear(); 1375 m_watchedFileSymlinkTarget.clear(); 1376 } 1377 1378 Document::OpenResult Part::doOpenFile(const QMimeType &mimeA, const QString &fileNameToOpenA, bool *isCompressedFile) 1379 { 1380 QMimeDatabase db; 1381 Document::OpenResult openResult = Document::OpenError; 1382 bool uncompressOk = true; 1383 QMimeType mime = mimeA; 1384 QString fileNameToOpen = fileNameToOpenA; 1385 KCompressionDevice::CompressionType compressionType = compressionTypeFor(mime.name()); 1386 if (compressionType != KCompressionDevice::None) { 1387 *isCompressedFile = true; 1388 uncompressOk = handleCompressed(fileNameToOpen, localFilePath(), compressionType); 1389 mime = db.mimeTypeForFile(fileNameToOpen); 1390 } else { 1391 *isCompressedFile = false; 1392 } 1393 1394 if (m_swapInsteadOfOpening) { 1395 m_swapInsteadOfOpening = false; 1396 1397 if (!uncompressOk) { 1398 return Document::OpenError; 1399 } 1400 1401 if (mime.inherits(QStringLiteral("application/vnd.kde.okular-archive"))) { 1402 isDocumentArchive = true; 1403 if (!m_document->swapBackingFileArchive(fileNameToOpen, url())) { 1404 return Document::OpenError; 1405 } 1406 } else { 1407 isDocumentArchive = false; 1408 if (!m_document->swapBackingFile(fileNameToOpen, url())) { 1409 return Document::OpenError; 1410 } 1411 } 1412 1413 m_fileLastModified = QFileInfo(localFilePath()).lastModified(); 1414 return Document::OpenSuccess; 1415 } 1416 1417 isDocumentArchive = false; 1418 if (uncompressOk) { 1419 if (mime.inherits(QStringLiteral("application/vnd.kde.okular-archive"))) { 1420 openResult = m_document->openDocumentArchive(fileNameToOpen, url()); 1421 isDocumentArchive = true; 1422 } else { 1423 openResult = m_document->openDocument(fileNameToOpen, url(), mime); 1424 } 1425 m_documentOpenWithPassword = false; 1426 1427 #if HAVE_KWALLET 1428 // if the file didn't open correctly it might be encrypted, so ask for a pass 1429 QString walletName, walletFolder, walletKey; 1430 m_document->walletDataForFile(fileNameToOpen, &walletName, &walletFolder, &walletKey); 1431 bool firstInput = true; 1432 bool triedWallet = false; 1433 KWallet::Wallet *wallet = nullptr; 1434 bool keep = true; 1435 while (openResult == Document::OpenNeedsPassword) { 1436 QString password; 1437 1438 // 1.A. try to retrieve the first password from the kde wallet system 1439 if (!triedWallet && !walletKey.isNull()) { 1440 const WId parentwid = widget()->effectiveWinId(); 1441 wallet = KWallet::Wallet::openWallet(walletName, parentwid); 1442 if (wallet) { 1443 // use the KPdf folder (and create if missing) 1444 if (!wallet->hasFolder(walletFolder)) { 1445 wallet->createFolder(walletFolder); 1446 } 1447 wallet->setFolder(walletFolder); 1448 1449 // look for the pass in that folder 1450 QString retrievedPass; 1451 if (!wallet->readPassword(walletKey, retrievedPass)) { 1452 password = retrievedPass; 1453 } 1454 } 1455 triedWallet = true; 1456 } 1457 1458 // 1.B. if not retrieved, ask the password using the kde password dialog 1459 if (password.isNull()) { 1460 QString prompt; 1461 if (firstInput) { 1462 prompt = i18n("Please enter the password to read the document:"); 1463 } else { 1464 prompt = i18n("Incorrect password. Try again:"); 1465 } 1466 firstInput = false; 1467 1468 // if the user presses cancel, abort opening 1469 KPasswordDialog dlg(widget(), wallet ? KPasswordDialog::ShowKeepPassword : KPasswordDialog::KPasswordDialogFlags()); 1470 dlg.setWindowTitle(i18n("Document Password")); 1471 dlg.setPrompt(prompt); 1472 if (!dlg.exec()) { 1473 break; 1474 } 1475 password = dlg.password(); 1476 if (wallet) { 1477 keep = dlg.keepPassword(); 1478 } 1479 } 1480 1481 // 2. reopen the document using the password 1482 if (mime.inherits(QStringLiteral("application/vnd.kde.okular-archive"))) { 1483 openResult = m_document->openDocumentArchive(fileNameToOpen, url(), password); 1484 isDocumentArchive = true; 1485 } else { 1486 openResult = m_document->openDocument(fileNameToOpen, url(), mime, password); 1487 } 1488 1489 if (openResult == Document::OpenSuccess) { 1490 m_documentOpenWithPassword = true; 1491 1492 // 3. if the password is correct and the user chose to remember it, store it to the wallet 1493 if (wallet && /*safety check*/ wallet->isOpen() && keep) { 1494 wallet->writePassword(walletKey, password); 1495 } 1496 } 1497 } 1498 #endif 1499 } 1500 1501 if (openResult == Document::OpenSuccess) { 1502 m_fileLastModified = QFileInfo(localFilePath()).lastModified(); 1503 m_warnedAboutModifyingUnsaveableDocument = false; 1504 } 1505 return openResult; 1506 } 1507 1508 bool Part::openFile() 1509 { 1510 QList<QMimeType> mimes; 1511 QString fileNameToOpen = localFilePath(); 1512 const bool isstdin = url().isLocalFile() && url().fileName() == QLatin1String("-"); 1513 const QFileInfo fileInfo(fileNameToOpen); 1514 if ((!isstdin) && (!fileInfo.exists())) { 1515 return false; 1516 } 1517 QMimeDatabase db; 1518 QMimeType pathMime = db.mimeTypeForFile(fileNameToOpen); 1519 if (!arguments().mimeType().isEmpty()) { 1520 QMimeType argMime = db.mimeTypeForName(arguments().mimeType()); 1521 1522 // Select the "childmost" mimetype, if none of them 1523 // inherits the other trust more what pathMime says 1524 // but still do a second try if that one fails 1525 if (argMime.inherits(pathMime.name())) { 1526 mimes << argMime; 1527 } else if (pathMime.inherits(argMime.name())) { 1528 mimes << pathMime; 1529 } else { 1530 mimes << pathMime << argMime; 1531 } 1532 1533 // text is super annoying because it always succeeds when opening so try to make sure 1534 // that we don't set it as first mime unless we're really sure it is that. 1535 // If it could be something else based on the content we try that first but only if that content itself 1536 // is not text or if it's a supported text child like markdown 1537 if (mimes[0].inherits(QStringLiteral("text/plain"))) { 1538 const QMimeType contentMime = db.mimeTypeForFile(fileNameToOpen, QMimeDatabase::MatchContent); 1539 if (!contentMime.inherits(QStringLiteral("text/plain"))) { 1540 mimes.prepend(contentMime); 1541 } else if (contentMime.name() != QLatin1String("text/plain")) { 1542 const QStringList supportedMimes = m_document->supportedMimeTypes(); 1543 if (supportedMimes.contains(contentMime.name())) { 1544 mimes.prepend(contentMime); 1545 } 1546 } 1547 } 1548 } else { 1549 mimes << pathMime; 1550 } 1551 1552 QMimeType mime; 1553 Document::OpenResult openResult = Document::OpenError; 1554 bool isCompressedFile = false; 1555 while (!mimes.isEmpty() && openResult == Document::OpenError) { 1556 mime = mimes.takeFirst(); 1557 openResult = doOpenFile(mime, fileNameToOpen, &isCompressedFile); 1558 } 1559 1560 bool canSearch = m_document->supportsSearching(); 1561 Q_EMIT mimeTypeChanged(mime); 1562 1563 // update one-time actions 1564 const bool ok = openResult == Document::OpenSuccess; 1565 Q_EMIT enableCloseAction(ok); 1566 m_find->setEnabled(ok && canSearch); 1567 m_findNext->setEnabled(ok && canSearch); 1568 m_findPrev->setEnabled(ok && canSearch); 1569 if (m_save) { 1570 m_save->setEnabled(ok && !(isstdin || mime.inherits(QStringLiteral("inode/directory")))); 1571 } 1572 if (m_saveAs) { 1573 m_saveAs->setEnabled(ok && !(isstdin || mime.inherits(QStringLiteral("inode/directory")))); 1574 } 1575 Q_EMIT enablePrintAction(ok && m_document->printingSupport() != Okular::Document::NoPrinting); 1576 m_printPreview->setEnabled(ok && m_document->printingSupport() != Okular::Document::NoPrinting); 1577 m_showProperties->setEnabled(ok); 1578 if (m_openContainingFolder) { 1579 m_openContainingFolder->setEnabled(ok); 1580 } 1581 bool hasEmbeddedFiles = ok && m_document->embeddedFiles() && m_document->embeddedFiles()->count() > 0; 1582 if (m_showEmbeddedFiles) { 1583 m_showEmbeddedFiles->setEnabled(hasEmbeddedFiles); 1584 } 1585 m_topMessage->setVisible(hasEmbeddedFiles && Okular::Settings::showEmbeddedContentMessages()); 1586 m_migrationMessage->setVisible(m_document->isDocdataMigrationNeeded()); 1587 1588 // Warn the user that XFA forms are not supported yet (NOTE: poppler generator only) 1589 if (ok && Okular::Settings::showEmbeddedContentMessages() && m_document->metaData(QStringLiteral("HasUnsupportedXfaForm")).toBool() == true) { 1590 m_formsMessage->setText(i18n("This document has XFA forms, which are currently <b>unsupported</b>.")); 1591 m_formsMessage->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); 1592 m_formsMessage->setMessageType(KMessageWidget::Warning); 1593 m_formsMessage->setVisible(true); 1594 } 1595 // m_pageView->toggleFormsAction() may be null on dummy mode 1596 else if (ok && Okular::Settings::showEmbeddedContentMessages() && m_pageView->toggleFormsAction() && m_pageView->toggleFormsAction()->isEnabled()) { 1597 m_formsMessage->setText(i18n("This document has forms. Click on the button to interact with them, or use View -> Show Forms.")); 1598 m_formsMessage->setMessageType(KMessageWidget::Information); 1599 m_formsMessage->setVisible(true); 1600 } else { 1601 m_formsMessage->setVisible(false); 1602 } 1603 1604 if (ok) { 1605 KMessageWidget::MessageType messageType; 1606 QString message; 1607 1608 std::tie(messageType, message) = SignatureGuiUtils::documentSignatureMessageWidgetText(m_document); 1609 1610 if (!message.isEmpty()) { 1611 if (m_embedMode == PrintPreviewMode) { 1612 if (Okular::Settings::showEmbeddedContentMessages()) { 1613 m_signatureMessage->setText(i18n("All editing and interactive features for this document are disabled. Please save a copy and reopen to edit this document.")); 1614 m_signatureMessage->setVisible(true); 1615 } 1616 } else { 1617 if (Okular::Settings::showEmbeddedContentMessages() || messageType > KMessageWidget::Information) { 1618 m_signatureMessage->setMessageType(messageType); 1619 m_signatureMessage->setText(message); 1620 m_signatureMessage->setVisible(true); 1621 } 1622 } 1623 } 1624 } 1625 1626 if (m_showPresentation) { 1627 m_showPresentation->setEnabled(ok); 1628 } 1629 if (ok) { 1630 if (m_exportAs) { 1631 m_exportFormats = m_document->exportFormats(); 1632 QList<Okular::ExportFormat>::ConstIterator it = m_exportFormats.constBegin(); 1633 QList<Okular::ExportFormat>::ConstIterator itEnd = m_exportFormats.constEnd(); 1634 QMenu *menu = m_exportAs->menu(); 1635 for (; it != itEnd; ++it) { 1636 menu->addAction(actionForExportFormat(*it)); 1637 } 1638 } 1639 #if HAVE_PURPOSE 1640 if (m_share) { 1641 m_shareMenu->model()->setInputData(QJsonObject {{QStringLiteral("mimeType"), mime.name()}, {QStringLiteral("urls"), QJsonArray {url().toString()}}}); 1642 m_shareMenu->model()->setPluginType(QStringLiteral("Export")); 1643 m_shareMenu->reload(); 1644 } 1645 #endif 1646 if (isCompressedFile) { 1647 m_realUrl = url(); 1648 } 1649 #ifdef OKULAR_KEEP_FILE_OPEN 1650 if (keepFileOpen()) 1651 m_keeper->open(fileNameToOpen); 1652 #endif 1653 1654 // Tries to find the text passed from terminal after the file is open 1655 if (!m_textToFindOnOpen.isEmpty()) { 1656 m_findBar->startSearch(m_textToFindOnOpen); 1657 m_textToFindOnOpen = QString(); 1658 } 1659 } 1660 if (m_exportAsText) { 1661 m_exportAsText->setEnabled(ok && m_document->canExportToText()); 1662 } 1663 if (m_exportAs) { 1664 m_exportAs->setEnabled(ok); 1665 } 1666 #if HAVE_PURPOSE 1667 if (m_share) { 1668 m_share->setEnabled(ok); 1669 } 1670 #endif 1671 1672 // update viewing actions 1673 updateViewActions(); 1674 1675 m_fileWasRemoved = false; 1676 1677 if (!ok) { 1678 // if can't open document, update windows so they display blank contents 1679 m_pageView->viewport()->update(); 1680 m_thumbnailList->update(); 1681 setUrl(QUrl()); 1682 return false; 1683 } 1684 1685 // set the file to the fileWatcher 1686 if (url().isLocalFile()) { 1687 setFileToWatch(localFilePath()); 1688 } 1689 1690 // if the 'OpenTOC' flag is set, open the TOC 1691 if (m_document->metaData(QStringLiteral("OpenTOC")).toBool() && m_tocEnabled && m_sidebar->currentItem() != m_toc) { 1692 m_sidebar->setCurrentItem(m_toc); 1693 } 1694 // if the 'StartFullScreen' flag is set and we're not in viewer widget mode, or the command line flag was 1695 // specified, start presentation 1696 const bool presentationBecauseOfDocumentMetadata = (m_embedMode != ViewerWidgetMode) && m_document->metaData(QStringLiteral("StartFullScreen")).toBool(); 1697 if ((presentationBecauseOfDocumentMetadata || m_cliPresentation) && !m_isReloading) { 1698 bool goAheadWithPresentationMode = true; 1699 if (!m_cliPresentation) { 1700 const QString text = i18n( 1701 "This document wants to be shown full screen.\n" 1702 "Leave normal mode and enter presentation mode?"); 1703 const QString caption = i18n("Request to Change Viewing Mode"); 1704 const KGuiItem yesItem = KGuiItem(i18n("Enter Presentation Mode"), QStringLiteral("dialog-ok")); 1705 const KGuiItem noItem = KGuiItem(i18n("Deny Request"), QStringLiteral("dialog-cancel")); 1706 const int result = KMessageBox::questionTwoActions(widget(), text, caption, yesItem, noItem); 1707 if (result == KMessageBox::SecondaryAction) { 1708 goAheadWithPresentationMode = false; 1709 } 1710 } 1711 m_cliPresentation = false; 1712 if (goAheadWithPresentationMode) { 1713 QMetaObject::invokeMethod(this, "slotShowPresentation", Qt::QueuedConnection); 1714 } 1715 } 1716 m_generatorGuiClient = factory() ? m_document->guiClient() : nullptr; 1717 if (m_generatorGuiClient) { 1718 factory()->addClient(m_generatorGuiClient); 1719 } 1720 if (m_cliPrint) { 1721 m_cliPrint = false; 1722 slotPrint(); 1723 } else if (m_cliPrintAndExit) { 1724 slotPrint(); 1725 } 1726 return true; 1727 } 1728 1729 bool Part::openUrl(const QUrl &url) 1730 { 1731 return openUrl(url, false /* swapInsteadOfOpening */); 1732 } 1733 1734 bool Part::openUrl(const QUrl &_url, bool swapInsteadOfOpening) 1735 { 1736 /* Store swapInsteadOfOpening, so that closeUrl and openFile will be able 1737 * to read it */ 1738 m_swapInsteadOfOpening = swapInsteadOfOpening; 1739 1740 // The subsequent call to closeUrl clears the arguments. 1741 // We want to save them and restore them later. 1742 const KParts::OpenUrlArguments args = arguments(); 1743 1744 // Close current document if any 1745 if (!closeUrl()) { 1746 return false; 1747 } 1748 1749 setArguments(args); 1750 1751 QUrl url(_url); 1752 if (url.hasFragment()) { 1753 m_urlWithFragment = _url; 1754 const QString dest = url.fragment(QUrl::FullyDecoded); 1755 bool ok = true; 1756 int page = dest.toInt(&ok); 1757 1758 if (!ok) { 1759 const QStringList parameters = dest.split(QLatin1Char('&')); 1760 for (const QString ¶meter : parameters) { 1761 if (parameter.startsWith(QStringLiteral("page="), Qt::CaseInsensitive)) { 1762 page = QStringView {dest}.mid(5).toInt(&ok); 1763 } 1764 } 1765 } 1766 1767 if (ok) { 1768 Okular::DocumentViewport vp(page - 1); 1769 vp.rePos.enabled = true; 1770 vp.rePos.normalizedX = 0; 1771 vp.rePos.normalizedY = 0; 1772 vp.rePos.pos = Okular::DocumentViewport::TopLeft; 1773 m_document->setNextDocumentViewport(vp); 1774 } else { 1775 m_document->setNextDocumentDestination(dest); 1776 } 1777 url.setFragment(QString()); 1778 } else { 1779 m_urlWithFragment.clear(); 1780 } 1781 1782 // this calls in sequence the 'closeUrl' and 'openFile' methods 1783 bool openOk = KParts::ReadWritePart::openUrl(url); 1784 1785 if (openOk) { 1786 m_viewportDirty.pageNumber = -1; 1787 1788 setWindowTitleFromDocument(); 1789 } else { 1790 if (m_urlWithFragment.isValid() && m_urlWithFragment.isLocalFile()) { 1791 openOk = tryOpeningUrlWithFragmentAsName(); 1792 } else { 1793 resetStartArguments(); 1794 /* TRANSLATORS: Adding the reason (%2) why the opening failed (if any). */ 1795 QString errorMessage = i18n("Could not open %1. %2", url.toDisplayString(), QStringLiteral("\n%1").arg(m_document->openError())); 1796 KMessageBox::error(widget(), errorMessage); 1797 } 1798 } 1799 1800 return openOk; 1801 } 1802 1803 bool Part::tryOpeningUrlWithFragmentAsName() 1804 { 1805 QUrl url = m_urlWithFragment; 1806 url.setPath(url.path() + QLatin1Char('#') + url.fragment()); 1807 url.setFragment(QString()); 1808 return openUrl(url); 1809 } 1810 1811 bool Part::queryClose() 1812 { 1813 if (!isReadWrite() || !isModified()) { 1814 return true; 1815 } 1816 1817 // TODO When we get different saving backends we need to query the backend 1818 // as to if it can save changes even if the open file has been modified, 1819 // since we only have poppler as saving backend for now we're skipping that check 1820 if (m_fileLastModified != QFileInfo(localFilePath()).lastModified()) { 1821 int res; 1822 if (m_isReloading) { 1823 res = KMessageBox::warningContinueCancel(widget(), 1824 xi18nc("@info", 1825 "The file <filename>%1</filename> has unsaved changes but has been modified by another program. Reloading it " 1826 "will replace the unsaved changes with the changes made in the other " 1827 "program.<nl/><nl/>Do you want to continue reloading the file?", 1828 url().fileName()), 1829 i18n("File Changed"), 1830 KGuiItem(i18n("Continue Reloading")), // <- KMessageBox::Continue 1831 KGuiItem(i18n("Abort Reloading"))); 1832 } else { 1833 res = KMessageBox::warningContinueCancel(widget(), 1834 xi18nc("@info", 1835 "The file <filename>%1</filename> has unsaved changes but has been modified by another program. Closing it " 1836 "will replace the unsaved changes with the changes made in the other " 1837 "program.<nl/><nl/>Do you want to continue closing the file?", 1838 url().fileName()), 1839 i18n("File Changed"), 1840 KGuiItem(i18n("Continue Closing")), // <- KMessageBox::Continue 1841 KGuiItem(i18n("Abort Closing"))); 1842 } 1843 return res == KMessageBox::Continue; 1844 } 1845 1846 // Not all things are saveable (e.g. files opened from stdin, folders) 1847 if (m_save->isEnabled()) { 1848 const int res = KMessageBox::warningTwoActionsCancel(widget(), i18n("Do you want to save your changes to \"%1\" or discard them?", url().fileName()), i18n("Close Document"), KStandardGuiItem::save(), KStandardGuiItem::discard()); 1849 1850 switch (res) { 1851 case KMessageBox::PrimaryAction: // Save 1852 saveFile(); 1853 return !isModified(); // Only allow closing if file was really saved 1854 case KMessageBox::SecondaryAction: // Discard 1855 return true; 1856 default: // Cancel 1857 return false; 1858 } 1859 } else { 1860 return true; 1861 } 1862 } 1863 1864 bool Part::closeUrl(bool promptToSave) 1865 { 1866 if (promptToSave && !queryClose()) { 1867 return false; 1868 } 1869 1870 if (m_swapInsteadOfOpening) { 1871 // If we're swapping the backing file, we don't want to close the 1872 // current one when openUrl() calls us internally 1873 return true; // pretend it worked 1874 } 1875 1876 m_document->setHistoryClean(true); 1877 1878 if (!m_temporaryLocalFile.isNull() && m_temporaryLocalFile != localFilePath()) { 1879 QFile::remove(m_temporaryLocalFile); 1880 m_temporaryLocalFile.clear(); 1881 } 1882 1883 slotHidePresentation(); 1884 Q_EMIT enableCloseAction(false); 1885 m_find->setEnabled(false); 1886 m_findNext->setEnabled(false); 1887 m_findPrev->setEnabled(false); 1888 if (m_save) { 1889 m_save->setEnabled(false); 1890 } 1891 if (m_saveAs) { 1892 m_saveAs->setEnabled(false); 1893 } 1894 m_printPreview->setEnabled(false); 1895 m_showProperties->setEnabled(false); 1896 if (m_showEmbeddedFiles) { 1897 m_showEmbeddedFiles->setEnabled(false); 1898 } 1899 if (m_exportAs) { 1900 m_exportAs->setEnabled(false); 1901 } 1902 if (m_exportAsText) { 1903 m_exportAsText->setEnabled(false); 1904 } 1905 m_exportFormats.clear(); 1906 if (m_exportAs) { 1907 QMenu *menu = m_exportAs->menu(); 1908 QList<QAction *> acts = menu->actions(); 1909 int num = acts.count(); 1910 for (int i = 1; i < num; ++i) { 1911 menu->removeAction(acts.at(i)); 1912 delete acts.at(i); 1913 } 1914 } 1915 #if HAVE_PURPOSE 1916 if (m_share) { 1917 m_share->setEnabled(false); 1918 m_shareMenu->clear(); 1919 } 1920 #endif 1921 if (m_showPresentation) { 1922 m_showPresentation->setEnabled(false); 1923 } 1924 Q_EMIT setWindowCaption(QLatin1String("")); 1925 Q_EMIT enablePrintAction(false); 1926 m_realUrl = QUrl(); 1927 if (url().isLocalFile()) { 1928 unsetFileToWatch(); 1929 } 1930 m_fileWasRemoved = false; 1931 if (m_generatorGuiClient) { 1932 factory()->removeClient(m_generatorGuiClient); 1933 } 1934 m_generatorGuiClient = nullptr; 1935 m_document->closeDocument(); 1936 m_fileLastModified = QDateTime(); 1937 updateViewActions(); 1938 delete m_tempfile; 1939 m_tempfile = nullptr; 1940 if (widget()) { 1941 m_searchWidget->clearText(); 1942 m_migrationMessage->setVisible(false); 1943 m_topMessage->setVisible(false); 1944 m_formsMessage->setVisible(false); 1945 m_signatureMessage->setVisible(false); 1946 } 1947 #ifdef OKULAR_KEEP_FILE_OPEN 1948 m_keeper->close(); 1949 #endif 1950 bool r = KParts::ReadWritePart::closeUrl(); 1951 setUrl(QUrl()); 1952 1953 return r; 1954 } 1955 1956 bool Part::closeUrl() 1957 { 1958 return closeUrl(true); 1959 } 1960 1961 void Part::guiActivateEvent(KParts::GUIActivateEvent *event) 1962 { 1963 updateViewActions(); 1964 1965 KParts::ReadWritePart::guiActivateEvent(event); 1966 1967 setWindowTitleFromDocument(); 1968 1969 if (event->activated()) { 1970 m_pageView->setupActionsPostGUIActivated(); 1971 rebuildBookmarkMenu(); 1972 } 1973 } 1974 1975 void Part::close() 1976 { 1977 if (m_embedMode == NativeShellMode) { 1978 closeUrl(); 1979 } else { 1980 KMessageBox::information(widget(), i18n("This link points to a close document action that does not work when using the embedded viewer."), QString(), QStringLiteral("warnNoCloseIfNotInOkular")); 1981 } 1982 } 1983 1984 void Part::cannotQuit() 1985 { 1986 KMessageBox::information(widget(), i18n("This link points to a quit application action that does not work when using the embedded viewer."), QString(), QStringLiteral("warnNoQuitIfNotInOkular")); 1987 } 1988 1989 void Part::slotShowLeftPanel() 1990 { 1991 bool showLeft = m_showLeftPanel->isChecked(); 1992 Okular::Settings::setShowLeftPanel(showLeft); 1993 Okular::Settings::self()->save(); 1994 // show/hide left panel 1995 m_sidebar->setSidebarVisibility(showLeft); 1996 } 1997 1998 void Part::slotShowBottomBar() 1999 { 2000 const bool showBottom = m_showBottomBar->isChecked(); 2001 Okular::Settings::setShowBottomBar(showBottom); 2002 Okular::Settings::self()->save(); 2003 // show/hide bottom bar 2004 m_bottomBar->setVisible(showBottom); 2005 } 2006 2007 void Part::slotFileDirty(const QString &path) 2008 { 2009 // The beauty of this is that each start cancels the previous one. 2010 // This means that timeout() is only fired when there have 2011 // no changes to the file for the last 750 millisecs. 2012 // This ensures that we don't update on every other byte that gets 2013 // written to the file. 2014 if (path == localFilePath()) { 2015 // Only start watching the file in case if it wasn't removed 2016 if (QFile::exists(localFilePath())) { 2017 m_dirtyHandler->start(750); 2018 } else { 2019 m_fileWasRemoved = true; 2020 } 2021 } else { 2022 const QFileInfo fi(localFilePath()); 2023 if (fi.absolutePath() == path) { 2024 // Our parent has been dirtified 2025 if (!QFile::exists(localFilePath())) { 2026 m_fileWasRemoved = true; 2027 } else if (m_fileWasRemoved && QFile::exists(localFilePath())) { 2028 // we need to watch the new file 2029 unsetFileToWatch(); 2030 setFileToWatch(localFilePath()); 2031 m_dirtyHandler->start(750); 2032 } 2033 } else if (fi.isSymLink() && fi.symLinkTarget() == path) { 2034 if (QFile::exists(fi.symLinkTarget())) { 2035 m_dirtyHandler->start(750); 2036 } else { 2037 m_fileWasRemoved = true; 2038 } 2039 } 2040 } 2041 } 2042 2043 // Attempt to reload the document, one or more times, optionally from a different URL 2044 bool Part::slotAttemptReload(bool oneShot, const QUrl &newUrl) 2045 { 2046 // Skip reload when another reload is already in progress 2047 if (m_isReloading) { 2048 return false; 2049 } 2050 QScopedValueRollback<bool> rollback(m_isReloading, true); 2051 2052 bool tocReloadPrepared = false; 2053 2054 // do the following the first time the file is reloaded 2055 if (m_viewportDirty.pageNumber == -1) { 2056 // store the url of the current document 2057 m_oldUrl = newUrl.isEmpty() ? url() : newUrl; 2058 2059 // store the current viewport 2060 m_viewportDirty = m_document->viewport(); 2061 2062 // store the current toolbox pane 2063 m_dirtyToolboxItem = m_sidebar->currentItem(); 2064 m_wasSidebarVisible = m_sidebar->isSidebarVisible(); 2065 2066 // store if presentation view was open 2067 m_wasPresentationOpen = (m_presentationWidget != nullptr); 2068 2069 // preserves the toc state after reload 2070 m_toc->prepareForReload(); 2071 tocReloadPrepared = true; 2072 2073 // store the page rotation 2074 m_dirtyPageRotation = m_document->rotation(); 2075 2076 // inform the user about the operation in progress 2077 // TODO: Remove this line and integrate reload info in queryClose 2078 m_pageView->displayMessage(i18n("Reloading the document...")); 2079 } 2080 2081 // close and (try to) reopen the document 2082 if (!closeUrl()) { 2083 m_viewportDirty.pageNumber = -1; 2084 2085 if (tocReloadPrepared) { 2086 m_toc->rollbackReload(); 2087 } 2088 return false; 2089 } 2090 2091 if (tocReloadPrepared) { 2092 m_toc->finishReload(); 2093 } 2094 2095 // inform the user about the operation in progress 2096 m_pageView->displayMessage(i18n("Reloading the document...")); 2097 2098 bool reloadSucceeded = false; 2099 2100 if (KParts::ReadWritePart::openUrl(m_oldUrl)) { 2101 // on successful opening, restore the previous viewport 2102 if (m_viewportDirty.pageNumber >= (int)m_document->pages()) { 2103 m_viewportDirty.pageNumber = (int)m_document->pages() - 1; 2104 } 2105 m_document->setViewport(m_viewportDirty); 2106 m_oldUrl = QUrl(); 2107 m_viewportDirty.pageNumber = -1; 2108 m_document->setRotation(m_dirtyPageRotation); 2109 if (m_sidebar->currentItem() != m_dirtyToolboxItem) { 2110 m_sidebar->setCurrentItem(m_dirtyToolboxItem); 2111 } 2112 if (m_sidebar->isSidebarVisible() != m_wasSidebarVisible) { 2113 m_sidebar->setSidebarVisibility(m_wasSidebarVisible); 2114 } 2115 if (m_wasPresentationOpen) { 2116 slotShowPresentation(); 2117 } 2118 Q_EMIT enablePrintAction(true && m_document->printingSupport() != Okular::Document::NoPrinting); 2119 2120 reloadSucceeded = true; 2121 } else if (!oneShot) { 2122 // start watching the file again (since we dropped it on close) 2123 setFileToWatch(localFilePath()); 2124 m_dirtyHandler->start(750); 2125 } 2126 2127 return reloadSucceeded; 2128 } 2129 2130 void Part::updateViewActions() 2131 { 2132 bool opened = m_document->pages() > 0; 2133 if (opened) { 2134 m_gotoPage->setEnabled(m_document->pages() > 1); 2135 2136 // Check if you are at the beginning or not 2137 if (m_document->currentPage() != 0) { 2138 m_beginningOfDocument->setEnabled(true); 2139 m_prevPage->setEnabled(true); 2140 } else { 2141 if (m_pageView->verticalScrollBar()->value() != 0) { 2142 // The page isn't at the very beginning 2143 m_beginningOfDocument->setEnabled(true); 2144 } else { 2145 // The page is at the very beginning of the document 2146 m_beginningOfDocument->setEnabled(false); 2147 } 2148 // The document is at the first page, you can go to a page before 2149 m_prevPage->setEnabled(false); 2150 } 2151 2152 if (m_document->pages() == m_document->currentPage() + 1) { 2153 // If you are at the end, disable go to next page 2154 m_nextPage->setEnabled(false); 2155 if (m_pageView->verticalScrollBar()->value() == m_pageView->verticalScrollBar()->maximum()) { 2156 // If you are the end of the page of the last document, you can't go to the last page 2157 m_endOfDocument->setEnabled(false); 2158 } else { 2159 // Otherwise you can move to the endif 2160 m_endOfDocument->setEnabled(true); 2161 } 2162 } else { 2163 // If you are not at the end, enable go to next page 2164 m_nextPage->setEnabled(true); 2165 m_endOfDocument->setEnabled(true); 2166 } 2167 2168 if (m_historyBack) { 2169 m_historyBack->setEnabled(!m_document->historyAtBegin()); 2170 } 2171 if (m_historyNext) { 2172 m_historyNext->setEnabled(!m_document->historyAtEnd()); 2173 } 2174 m_reload->setEnabled(true); 2175 if (m_copy) { 2176 m_copy->setEnabled(true); 2177 } 2178 if (m_selectAll) { 2179 m_selectAll->setEnabled(true); 2180 } 2181 if (m_selectCurrentPage) { 2182 m_selectCurrentPage->setEnabled(true); 2183 } 2184 } else { 2185 m_gotoPage->setEnabled(false); 2186 m_beginningOfDocument->setEnabled(false); 2187 m_endOfDocument->setEnabled(false); 2188 m_prevPage->setEnabled(false); 2189 m_nextPage->setEnabled(false); 2190 if (m_historyBack) { 2191 m_historyBack->setEnabled(false); 2192 } 2193 if (m_historyNext) { 2194 m_historyNext->setEnabled(false); 2195 } 2196 m_reload->setEnabled(false); 2197 if (m_copy) { 2198 m_copy->setEnabled(false); 2199 } 2200 if (m_selectAll) { 2201 m_selectAll->setEnabled(false); 2202 } 2203 if (m_selectCurrentPage) { 2204 m_selectCurrentPage->setEnabled(false); 2205 } 2206 } 2207 2208 if (factory()) { 2209 QWidget *menu = factory()->container(QStringLiteral("menu_okular_part_viewer"), this); 2210 if (menu) { 2211 menu->setEnabled(opened); 2212 } 2213 2214 menu = factory()->container(QStringLiteral("view_orientation"), this); 2215 if (menu) { 2216 menu->setEnabled(opened); 2217 } 2218 } 2219 Q_EMIT viewerMenuStateChange(opened); 2220 2221 updateBookmarksActions(); 2222 } 2223 2224 void Part::updateBookmarksActions() 2225 { 2226 bool opened = m_document->pages() > 0; 2227 if (opened) { 2228 m_addBookmark->setEnabled(true); 2229 if (m_document->bookmarkManager()->isBookmarked(m_document->viewport())) { 2230 m_addBookmark->setText(i18n("Remove Bookmark")); 2231 m_addBookmark->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-remove"), QIcon::fromTheme(QStringLiteral("edit-delete-bookmark")))); 2232 m_renameBookmark->setEnabled(true); 2233 } else { 2234 m_addBookmark->setText(m_addBookmarkText); 2235 m_addBookmark->setIcon(m_addBookmarkIcon); 2236 m_renameBookmark->setEnabled(false); 2237 } 2238 } else { 2239 m_addBookmark->setEnabled(false); 2240 m_addBookmark->setText(m_addBookmarkText); 2241 m_addBookmark->setIcon(m_addBookmarkIcon); 2242 m_renameBookmark->setEnabled(false); 2243 } 2244 } 2245 2246 void Part::enableTOC(bool enable) 2247 { 2248 if (!enable) { 2249 m_tocEnabled = false; 2250 return; 2251 } 2252 2253 m_sidebar->addItem(m_toc, QIcon::fromTheme(QApplication::isLeftToRight() ? QStringLiteral("format-justify-left") : QStringLiteral("format-justify-right")), i18n("Contents")); 2254 m_tocEnabled = true; 2255 2256 // If present, show the TOC when a document is opened 2257 if (m_sidebar->currentItem() != m_toc) { 2258 m_sidebar->setCurrentItem(m_toc); 2259 } 2260 } 2261 2262 void Part::slotRebuildBookmarkMenu() 2263 { 2264 rebuildBookmarkMenu(); 2265 } 2266 2267 void Part::enableLayers(bool enable) 2268 { 2269 if (!enable) { 2270 return; 2271 } 2272 2273 m_sidebar->addItem(m_layers, QIcon::fromTheme(QStringLiteral("format-list-unordered")), i18n("Layers")); 2274 } 2275 2276 void Part::enableSidebarSignaturesItem(bool enable) 2277 { 2278 if (!enable) { 2279 return; 2280 } 2281 2282 m_sidebar->addItem(m_signaturePanel, QIcon::fromTheme(QStringLiteral("application-pkcs7-signature")), i18n("Signatures")); 2283 } 2284 2285 void Part::slotShowFindBar() 2286 { 2287 m_findBar->show(); 2288 m_findBar->focusAndSetCursor(); 2289 m_closeFindBar->setEnabled(true); 2290 } 2291 2292 void Part::slotHideFindBar() 2293 { 2294 if (m_findBar->maybeHide()) { 2295 m_pageView->setFocus(); 2296 m_closeFindBar->setEnabled(false); 2297 } 2298 } 2299 2300 // BEGIN go to page dialog 2301 class GotoPageDialog : public QDialog 2302 { 2303 Q_OBJECT 2304 2305 public: 2306 GotoPageDialog(QWidget *p, int current, int max) 2307 : QDialog(p) 2308 { 2309 setWindowTitle(i18n("Go to Page")); 2310 buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 2311 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 2312 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 2313 2314 QVBoxLayout *topLayout = new QVBoxLayout(this); 2315 topLayout->setContentsMargins(6, 6, 6, 6); 2316 QHBoxLayout *midLayout = new QHBoxLayout(); 2317 spinbox = new QSpinBox(this); 2318 spinbox->setRange(1, max); 2319 spinbox->setValue(current); 2320 spinbox->setFocus(); 2321 2322 slider = new QSlider(Qt::Horizontal, this); 2323 slider->setRange(1, max); 2324 slider->setValue(current); 2325 slider->setSingleStep(1); 2326 slider->setTickPosition(QSlider::TicksBelow); 2327 slider->setTickInterval(max / 10); 2328 2329 connect(slider, &QSlider::valueChanged, spinbox, &QSpinBox::setValue); 2330 connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), slider, &QSlider::setValue); 2331 2332 QLabel *label = new QLabel(i18n("&Page:"), this); 2333 label->setBuddy(spinbox); 2334 topLayout->addWidget(label); 2335 topLayout->addLayout(midLayout); 2336 midLayout->addWidget(slider); 2337 midLayout->addWidget(spinbox); 2338 2339 // A little bit extra space 2340 topLayout->addStretch(10); 2341 topLayout->addWidget(buttonBox); 2342 spinbox->setFocus(); 2343 } 2344 2345 int getPage() const 2346 { 2347 return spinbox->value(); 2348 } 2349 2350 protected: 2351 QSpinBox *spinbox; 2352 QSlider *slider; 2353 QDialogButtonBox *buttonBox; 2354 }; 2355 // END go to page dialog 2356 2357 void Part::slotGoToPage() 2358 { 2359 GotoPageDialog pageDialog(m_pageView, m_document->currentPage() + 1, m_document->pages()); 2360 if (pageDialog.exec() == QDialog::Accepted) { 2361 m_document->setViewportPage(pageDialog.getPage() - 1, nullptr, true); 2362 } 2363 } 2364 2365 void Part::slotPreviousPage() 2366 { 2367 if (m_document->isOpened() && !(m_document->currentPage() < 1)) { 2368 m_document->setViewportPage(m_document->currentPage() - 1, nullptr, true); 2369 } 2370 } 2371 2372 void Part::slotNextPage() 2373 { 2374 if (m_document->isOpened() && m_document->currentPage() < (m_document->pages() - 1)) { 2375 m_document->setViewportPage(m_document->currentPage() + 1, nullptr, true); 2376 } 2377 } 2378 2379 void Part::slotGotoFirst() 2380 { 2381 if (m_document->isOpened()) { 2382 m_document->setViewportPage(0, nullptr, true); 2383 m_beginningOfDocument->setEnabled(false); 2384 } 2385 } 2386 2387 void Part::slotGotoLast() 2388 { 2389 if (m_document->isOpened()) { 2390 DocumentViewport endPage(m_document->pages() - 1); 2391 endPage.rePos.enabled = true; 2392 endPage.rePos.normalizedX = 0; 2393 endPage.rePos.normalizedY = 1; 2394 endPage.rePos.pos = Okular::DocumentViewport::TopLeft; 2395 m_document->setViewport(endPage, nullptr, true); 2396 m_endOfDocument->setEnabled(false); 2397 } 2398 } 2399 2400 void Part::slotHistoryBack() 2401 { 2402 m_document->setPrevViewport(); 2403 } 2404 2405 void Part::slotHistoryNext() 2406 { 2407 m_document->setNextViewport(); 2408 } 2409 2410 void Part::slotAddBookmark() 2411 { 2412 DocumentViewport vp = m_document->viewport(); 2413 if (m_document->bookmarkManager()->isBookmarked(vp)) { 2414 m_document->bookmarkManager()->removeBookmark(vp); 2415 } else { 2416 m_document->bookmarkManager()->addBookmark(vp); 2417 } 2418 } 2419 2420 void Part::slotRenameBookmark(const DocumentViewport &viewport) 2421 { 2422 Q_ASSERT(m_document->bookmarkManager()->isBookmarked(viewport)); 2423 if (m_document->bookmarkManager()->isBookmarked(viewport)) { 2424 KBookmark bookmark = m_document->bookmarkManager()->bookmark(viewport); 2425 const QString newName = QInputDialog::getText(widget(), i18n("Rename Bookmark"), i18n("Enter the new name of the bookmark:"), QLineEdit::Normal, bookmark.fullText()); 2426 if (!newName.isEmpty()) { 2427 m_document->bookmarkManager()->renameBookmark(&bookmark, newName); 2428 } 2429 } 2430 } 2431 2432 void Part::slotRenameBookmarkFromMenu() 2433 { 2434 QAction *action = dynamic_cast<QAction *>(sender()); 2435 Q_ASSERT(action); 2436 if (action) { 2437 DocumentViewport vp(action->data().toString()); 2438 slotRenameBookmark(vp); 2439 } 2440 } 2441 2442 void Part::slotRemoveBookmarkFromMenu() 2443 { 2444 QAction *action = dynamic_cast<QAction *>(sender()); 2445 Q_ASSERT(action); 2446 if (action) { 2447 DocumentViewport vp(action->data().toString()); 2448 slotRemoveBookmark(vp); 2449 } 2450 } 2451 2452 void Part::slotRemoveBookmark(const DocumentViewport &viewport) 2453 { 2454 Q_ASSERT(m_document->bookmarkManager()->isBookmarked(viewport)); 2455 if (m_document->bookmarkManager()->isBookmarked(viewport)) { 2456 m_document->bookmarkManager()->removeBookmark(viewport); 2457 } 2458 } 2459 2460 void Part::slotRenameCurrentViewportBookmark() 2461 { 2462 slotRenameBookmark(m_document->viewport()); 2463 } 2464 2465 bool Part::aboutToShowContextMenu(QMenu * /*menu*/, QAction *action, QMenu *contextMenu) 2466 { 2467 KBookmarkAction *ba = dynamic_cast<KBookmarkAction *>(action); 2468 if (ba != nullptr) { 2469 QAction *separatorAction = contextMenu->addSeparator(); 2470 separatorAction->setObjectName(QStringLiteral("OkularPrivateRenameBookmarkActions")); 2471 QAction *renameAction = contextMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename this Bookmark"), this, &Part::slotRenameBookmarkFromMenu); 2472 renameAction->setData(ba->property("htmlRef").toString()); 2473 renameAction->setObjectName(QStringLiteral("OkularPrivateRenameBookmarkActions")); 2474 QAction *deleteAction = contextMenu->addAction(QIcon::fromTheme(QStringLiteral("bookmark-remove"), QIcon::fromTheme(QStringLiteral("edit-delete-bookmark"))), i18n("Remove this Bookmark"), this, &Part::slotRemoveBookmarkFromMenu); 2475 deleteAction->setData(ba->property("htmlRef").toString()); 2476 deleteAction->setObjectName(QStringLiteral("OkularPrivateRenameBookmarkActions")); 2477 } 2478 return ba; 2479 } 2480 2481 void Part::slotPreviousBookmark() 2482 { 2483 const KBookmark bookmark = m_document->bookmarkManager()->previousBookmark(m_document->viewport()); 2484 2485 if (!bookmark.isNull()) { 2486 DocumentViewport vp(bookmark.url().fragment(QUrl::FullyDecoded)); 2487 m_document->setViewport(vp, nullptr, true); 2488 } 2489 } 2490 2491 void Part::slotNextBookmark() 2492 { 2493 const KBookmark bookmark = m_document->bookmarkManager()->nextBookmark(m_document->viewport()); 2494 2495 if (!bookmark.isNull()) { 2496 DocumentViewport vp(bookmark.url().fragment(QUrl::FullyDecoded)); 2497 m_document->setViewport(vp, nullptr, true); 2498 } 2499 } 2500 2501 void Part::slotFind() 2502 { 2503 // when in presentation mode, there's already a search bar, taking care of 2504 // the 'find' requests 2505 if (m_presentationWidget != nullptr) { 2506 m_presentationWidget->slotFind(); 2507 } else { 2508 slotShowFindBar(); 2509 } 2510 } 2511 2512 void Part::slotFindNext() 2513 { 2514 if (m_findBar->isHidden()) { 2515 slotShowFindBar(); 2516 } else { 2517 m_findBar->findNext(); 2518 } 2519 } 2520 2521 void Part::slotFindPrev() 2522 { 2523 if (m_findBar->isHidden()) { 2524 slotShowFindBar(); 2525 } else { 2526 m_findBar->findPrev(); 2527 } 2528 } 2529 2530 bool Part::saveFile() 2531 { 2532 if (!isModified()) { 2533 return true; 2534 } else { 2535 return saveAs(url()); 2536 } 2537 } 2538 2539 bool Part::slotSaveFileAs(bool showOkularArchiveAsDefaultFormat) 2540 { 2541 if (m_embedMode == PrintPreviewMode) { 2542 return false; 2543 } 2544 2545 // Determine the document's mimetype 2546 QMimeDatabase db; 2547 QMimeType originalMimeType; 2548 const QString typeName = m_document->documentInfo().get(DocumentInfo::MimeType); 2549 if (!typeName.isEmpty()) { 2550 originalMimeType = db.mimeTypeForName(typeName); 2551 } 2552 2553 // What data would we lose if we saved natively? 2554 bool wontSaveForms, wontSaveAnnotations; 2555 checkNativeSaveDataLoss(&wontSaveForms, &wontSaveAnnotations); 2556 2557 const QMimeType okularArchiveMimeType = db.mimeTypeForName(QStringLiteral("application/vnd.kde.okular-archive")); 2558 2559 // Prepare "Save As" dialog 2560 const QString originalMimeTypeFilter = i18nc("File type name and pattern", "%1 (%2)", originalMimeType.comment(), originalMimeType.globPatterns().join(QLatin1Char(' '))); 2561 const QString okularArchiveMimeTypeFilter = i18nc("File type name and pattern", "%1 (%2)", okularArchiveMimeType.comment(), okularArchiveMimeType.globPatterns().join(QLatin1Char(' '))); 2562 2563 // What format choice should we show as default? 2564 QString selectedFilter = (isDocumentArchive || showOkularArchiveAsDefaultFormat || wontSaveForms || wontSaveAnnotations) ? okularArchiveMimeTypeFilter : originalMimeTypeFilter; 2565 2566 QString filter = originalMimeTypeFilter + QStringLiteral(";;") + okularArchiveMimeTypeFilter; 2567 2568 const QUrl saveUrl = QFileDialog::getSaveFileUrl(widget(), i18n("Save As"), url(), filter, &selectedFilter); 2569 2570 if (!saveUrl.isValid() || saveUrl.isEmpty()) { 2571 return false; 2572 } 2573 2574 // Has the user chosen to save in .okular archive format? 2575 const bool saveAsOkularArchive = (selectedFilter == okularArchiveMimeTypeFilter); 2576 2577 if (saveAsOkularArchive) { 2578 // Non Plasma file dialogs are terrible and it's very easy to select saving a file as okular archive and call it hello.md 2579 // and that's bad because it is *not* an .md file so tell the user to fix it 2580 Q_ASSERT(okularArchiveMimeType.suffixes().count() == 1); 2581 Q_ASSERT(okularArchiveMimeType.suffixes().at(0) == okularArchiveMimeType.preferredSuffix()); 2582 const QString wantedExtension = QLatin1Char('.') + okularArchiveMimeType.preferredSuffix(); 2583 if (!saveUrl.path().endsWith(wantedExtension)) { 2584 const auto button = KMessageBox::questionTwoActions(widget(), 2585 i18n("You have chosen to save an Okular Archive without the file name ending with the '%1' extension. That is not allowed, do you want to choose a new name?", wantedExtension), 2586 i18n("Unsupported extension"), 2587 KGuiItem(i18nc("@action:button", "Choose New Name"), QStringLiteral("edit-rename")), 2588 KStandardGuiItem::cancel()); 2589 2590 return button == KMessageBox::PrimaryAction ? slotSaveFileAs(showOkularArchiveAsDefaultFormat) : false; 2591 } 2592 } 2593 2594 return saveAs(saveUrl, saveAsOkularArchive ? SaveAsOkularArchive : NoSaveAsFlags); 2595 } 2596 2597 bool Part::saveAs(const QUrl &saveUrl) 2598 { 2599 // Save in the same format (.okular vs native) as the current file 2600 return saveAs(saveUrl, isDocumentArchive ? SaveAsOkularArchive : NoSaveAsFlags); 2601 } 2602 2603 static QUrl resolveSymlinksIfFileExists(const QUrl &saveUrl) 2604 { 2605 if (saveUrl.isLocalFile()) { 2606 const QFileInfo fi(saveUrl.toLocalFile()); 2607 return fi.exists() ? QUrl::fromLocalFile(fi.canonicalFilePath()) : saveUrl; 2608 } else { 2609 return saveUrl; 2610 } 2611 } 2612 2613 bool Part::saveAs(const QUrl &saveUrl, SaveAsFlags flags) 2614 { 2615 // TODO When we get different saving backends we need to query the backend 2616 // as to if it can save changes even if the open file has been modified, 2617 // since we only have poppler as saving backend for now we're skipping that check 2618 2619 // Don't warn the user about external changes if what actually happened was that 2620 // the file on disk was deleted for some reason; in this case just go on normally 2621 // to avoid confusion or data loss. 2622 // Also don't warn if the file was modified on disk but the user is doing a Save As 2623 // with a different URL, since the original changed document is safe so there's 2624 // nothing to warn about. 2625 const QFileInfo fi(localFilePath()); 2626 if (fi.exists() && m_fileLastModified != fi.lastModified() && saveUrl == realUrl()) { 2627 const int res = KMessageBox::warningTwoActionsCancel(widget(), 2628 xi18nc("@info", 2629 "The file <filename>%1</filename> has been modified by another program. If you save now, any " 2630 "changes made in the other program will be lost. Are you sure you want to continue?", 2631 realUrl().fileName()), 2632 i18n("Save - Warning"), 2633 KStandardGuiItem::cont(), // <- KMessageBox::PrimaryAction 2634 KGuiItem(i18n("Save a Copy Elsewhere")), // <- KMessageBox::SecondaryAction 2635 KStandardGuiItem::cancel()); // <- KMessageBox::Cancel 2636 2637 if (res == KMessageBox::SecondaryAction) { 2638 slotSaveFileAs(false); 2639 } 2640 if (res != KMessageBox::PrimaryAction) { 2641 return false; 2642 } 2643 } 2644 2645 bool hasUserAcceptedReload = false; 2646 if (m_documentOpenWithPassword) { 2647 const int res = KMessageBox::warningContinueCancel( 2648 widget(), 2649 i18n("The current document is protected with a password.<br />In order to save, the file needs to be reloaded. You will be asked for the password again and your undo/redo history will be lost.<br />Do you want to continue?"), 2650 i18n("Save - Warning"), 2651 KStandardGuiItem::cont(), 2652 KStandardGuiItem::cancel()); 2653 2654 switch (res) { 2655 case KMessageBox::Continue: 2656 hasUserAcceptedReload = true; 2657 // do nothing 2658 break; 2659 case KMessageBox::Cancel: // User said no to continue, so return true even if save didn't happen otherwise we will get an error 2660 return true; 2661 } 2662 } 2663 2664 bool setModifiedAfterSave = false; 2665 2666 QString tmpFileName; 2667 // don't turn the file_copy that use this to a file_move 2668 // doesn't work on Windows 2669 bool deleteTmpFileName = false; 2670 { 2671 // Own scope for the QTemporaryFile since we only care about the random name 2672 // we need to destroy it so the file gets deleted otherwise windows will complain 2673 // when trying to save over it because the file is still open 2674 QTemporaryFile tf; 2675 if (!tf.open()) { 2676 KMessageBox::information(widget(), i18n("Could not open the temporary file for saving.")); 2677 return false; 2678 } 2679 tmpFileName = tf.fileName(); 2680 } 2681 2682 // Figure out the real save url, for symlinks we don't want to copy over the symlink but over the target file 2683 const QUrl realSaveUrl = resolveSymlinksIfFileExists(saveUrl); 2684 2685 // Due to the way we write we can overwrite readonly files so check if it's one and just bail out early 2686 if (realSaveUrl.isLocalFile()) { 2687 const QFileInfo fi(realSaveUrl.toLocalFile()); 2688 if (fi.exists() && !fi.isWritable()) { 2689 KMessageBox::information(widget(), xi18nc("@info", "Could not overwrite <filename>%1</filename> because that file is read-only. Try saving to another location or changing that file's permissions.", realSaveUrl.toLocalFile())); 2690 return false; 2691 } 2692 } 2693 2694 QScopedPointer<QTemporaryFile> tempFile; 2695 KIO::Job *copyJob = nullptr; // this will be filled with the job that writes to saveUrl 2696 2697 // Does the user want a .okular archive? 2698 if (flags & SaveAsOkularArchive) { 2699 if (!hasUserAcceptedReload && !m_document->canSwapBackingFile()) { 2700 const int res = KMessageBox::warningContinueCancel(widget(), 2701 i18n("After saving, the current document format requires the file to be reloaded. Your undo/redo history will be lost.<br />Do you want to continue?"), 2702 i18n("Save - Warning"), 2703 KStandardGuiItem::cont(), 2704 KStandardGuiItem::cancel()); 2705 2706 switch (res) { 2707 case KMessageBox::Continue: 2708 // do nothing 2709 break; 2710 case KMessageBox::Cancel: // User said no to continue, so return true even if save didn't happen otherwise we will get an error 2711 return true; 2712 } 2713 } 2714 2715 if (!m_document->saveDocumentArchive(tmpFileName)) { 2716 KMessageBox::information(widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", tmpFileName)); 2717 return false; 2718 } 2719 2720 copyJob = KIO::file_copy(QUrl::fromLocalFile(tmpFileName), realSaveUrl, -1, KIO::Overwrite); 2721 deleteTmpFileName = true; 2722 } else { 2723 bool wontSaveForms, wontSaveAnnotations; 2724 checkNativeSaveDataLoss(&wontSaveForms, &wontSaveAnnotations); 2725 2726 // If something can't be saved in this format, ask for confirmation 2727 QStringList listOfwontSaves; 2728 if (wontSaveForms) { 2729 listOfwontSaves << i18n("Filled form contents"); 2730 } 2731 if (wontSaveAnnotations) { 2732 listOfwontSaves << i18n("User annotations"); 2733 } 2734 if (!listOfwontSaves.isEmpty()) { 2735 if (saveUrl == url()) { 2736 // Save 2737 const QString warningMessage = i18n("You are about to save changes, but the current file format does not support saving the following elements. Please use the <i>Okular document archive</i> format to preserve them."); 2738 const int result = KMessageBox::warningContinueCancelList(widget(), 2739 warningMessage, 2740 listOfwontSaves, 2741 i18n("Warning"), 2742 KGuiItem(i18n("Save as Okular document archive..."), QStringLiteral("document-save-as")), // <- KMessageBox::Continue 2743 KStandardGuiItem::cancel()); 2744 2745 switch (result) { 2746 case KMessageBox::Continue: // -> Save as Okular document archive 2747 return slotSaveFileAs(true /* showOkularArchiveAsDefaultFormat */); 2748 default: 2749 return false; 2750 } 2751 } else { 2752 // Save as 2753 const QString warningMessage = m_document->canSwapBackingFile() ? i18n( 2754 "You are about to save changes, but the current file format does not support saving the following elements. Please use the <i>Okular document " 2755 "archive</i> format to preserve them. Click <i>Continue</i> to save the document and discard these elements.") 2756 : i18n( 2757 "You are about to save changes, but the current file format does not support saving the following elements. Please use the <i>Okular document " 2758 "archive</i> format to preserve them. Click <i>Continue</i> to save, but you will lose these elements as well as the undo/redo history."); 2759 const QString continueMessage = m_document->canSwapBackingFile() ? i18n("Continue") : i18n("Continue losing changes"); 2760 const int result = KMessageBox::warningTwoActionsCancelList(widget(), 2761 warningMessage, 2762 listOfwontSaves, 2763 i18n("Warning"), 2764 KGuiItem(i18n("Save as Okular document archive..."), QStringLiteral("document-save-as")), // <- KMessageBox::PrimaryAction 2765 KGuiItem(continueMessage, QStringLiteral("arrow-right"))); // <- KMessageBox::SecondaryAction 2766 2767 switch (result) { 2768 case KMessageBox::PrimaryAction: // -> Save as Okular document archive 2769 return slotSaveFileAs(true /* showOkularArchiveAsDefaultFormat */); 2770 case KMessageBox::SecondaryAction: // -> Continue 2771 setModifiedAfterSave = m_document->canSwapBackingFile(); 2772 break; 2773 case KMessageBox::Cancel: 2774 return false; 2775 } 2776 } 2777 } 2778 2779 if (m_document->canSaveChanges()) { 2780 // If the generator supports saving changes, save them 2781 2782 QString errorText; 2783 if (!m_document->saveChanges(tmpFileName, &errorText)) { 2784 if (errorText.isEmpty()) { 2785 KMessageBox::information(widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", tmpFileName)); 2786 } else { 2787 KMessageBox::information(widget(), i18n("File could not be saved in '%1'. %2", tmpFileName, errorText)); 2788 } 2789 2790 return false; 2791 } 2792 2793 copyJob = KIO::file_copy(QUrl::fromLocalFile(tmpFileName), realSaveUrl, -1, KIO::Overwrite); 2794 deleteTmpFileName = true; 2795 } else { 2796 // If the generators doesn't support saving changes, we will 2797 // just copy the original file. 2798 2799 if (isDocumentArchive) { 2800 // Special case: if the user is extracting the contents of a 2801 // .okular archive back to the native format, we can't just copy 2802 // the open file (which is a .okular). So let's ask to core to 2803 // extract and give us the real file 2804 2805 if (!m_document->extractArchivedFile(tmpFileName)) { 2806 KMessageBox::information(widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", tmpFileName)); 2807 return false; 2808 } 2809 2810 copyJob = KIO::file_copy(QUrl::fromLocalFile(tmpFileName), realSaveUrl, -1, KIO::Overwrite); 2811 deleteTmpFileName = true; 2812 } else { 2813 // Otherwise just copy the open file. 2814 // make use of the already downloaded (in case of remote URLs) file, 2815 // no point in downloading that again 2816 QUrl srcUrl = QUrl::fromLocalFile(localFilePath()); 2817 // duh, our local file disappeared... 2818 if (!QFile::exists(localFilePath())) { 2819 if (url().isLocalFile()) { 2820 #ifdef OKULAR_KEEP_FILE_OPEN 2821 // local file: try to get it back from the open handle on it 2822 tempFile.reset(m_keeper->copyToTemporary()); 2823 if (tempFile) 2824 srcUrl = KUrl::fromPath(tempFile->fileName()); 2825 #else 2826 const QString msg = i18n("Okular cannot copy %1 to the specified location.\n\nThe document does not exist anymore.", localFilePath()); 2827 KMessageBox::error(widget(), msg); 2828 return false; 2829 #endif 2830 } else { 2831 // we still have the original remote URL of the document, 2832 // so copy the document from there 2833 srcUrl = url(); 2834 } 2835 } 2836 2837 if (srcUrl != saveUrl) { 2838 copyJob = KIO::file_copy(srcUrl, realSaveUrl, -1, KIO::Overwrite); 2839 } else { 2840 // Don't do a real copy in this case, just update the timestamps 2841 copyJob = KIO::setModificationTime(realSaveUrl, QDateTime::currentDateTime()); 2842 } 2843 } 2844 } 2845 } 2846 2847 // Stop watching for changes while we write the new file (useful when 2848 // overwriting) 2849 if (url().isLocalFile()) { 2850 unsetFileToWatch(); 2851 } 2852 2853 const auto deleteTmpFileFunction = [deleteTmpFileName, tmpFileName] { 2854 Q_ASSERT(deleteTmpFileName == QFile::exists(tmpFileName)); 2855 if (deleteTmpFileName) { 2856 QFile::remove(tmpFileName); 2857 } 2858 }; 2859 2860 KJobWidgets::setWindow(copyJob, widget()); 2861 if (!copyJob->exec()) { 2862 KMessageBox::information(widget(), i18n("File could not be saved in '%1'. Error: '%2'. Try to save it to another location.", saveUrl.toDisplayString(), copyJob->errorString())); 2863 2864 // Restore watcher 2865 if (url().isLocalFile()) { 2866 setFileToWatch(localFilePath()); 2867 } 2868 2869 deleteTmpFileFunction(); 2870 2871 return false; 2872 } 2873 deleteTmpFileFunction(); 2874 2875 m_document->setHistoryClean(true); 2876 2877 if (m_document->isDocdataMigrationNeeded()) { 2878 m_document->docdataMigrationDone(); 2879 } 2880 2881 bool reloadedCorrectly = true; 2882 2883 // Make the generator use the new file instead of the old one 2884 if (m_document->canSwapBackingFile() && !m_documentOpenWithPassword) { 2885 QWidget *currentSidebarItem = m_sidebar->currentItem(); 2886 // this calls openFile internally, which in turn actually calls 2887 // m_document->swapBackingFile() instead of the regular loadDocument 2888 if (openUrl(saveUrl, true /* swapInsteadOfOpening */)) { 2889 if (setModifiedAfterSave) { 2890 m_document->setHistoryClean(false); 2891 } 2892 } else { 2893 reloadedCorrectly = false; 2894 } 2895 2896 if (m_sidebar->currentItem() != currentSidebarItem) { 2897 m_sidebar->setCurrentItem(currentSidebarItem); 2898 } 2899 } else { 2900 // If the generator doesn't support swapping file, then just reload 2901 // the document from the new location 2902 if (!slotAttemptReload(true, saveUrl)) { 2903 reloadedCorrectly = false; 2904 } 2905 } 2906 2907 // In case of file swapping errors, close the document to avoid inconsistencies 2908 if (!reloadedCorrectly) { 2909 qWarning() << "The document hasn't been reloaded/swapped correctly"; 2910 closeUrl(); 2911 } 2912 2913 // Restore watcher 2914 if (url().isLocalFile()) { 2915 setFileToWatch(localFilePath()); 2916 } 2917 2918 // Set correct permission taking into account the umask value 2919 #ifndef Q_OS_WIN 2920 const QString saveFilePath = saveUrl.toLocalFile(); 2921 if (QFile::exists(saveFilePath)) { 2922 const mode_t mask = umask(0); 2923 umask(mask); 2924 const mode_t fileMode = 0666 & ~mask; 2925 chmod(QFile::encodeName(saveFilePath).constData(), fileMode); 2926 } 2927 #endif 2928 2929 return true; 2930 } 2931 2932 // If the user wants to save in the original file's format, some features might 2933 // not be available. Find out what cannot be saved in this format 2934 void Part::checkNativeSaveDataLoss(bool *out_wontSaveForms, bool *out_wontSaveAnnotations) const 2935 { 2936 bool wontSaveForms = false; 2937 bool wontSaveAnnotations = false; 2938 2939 if (!m_document->canSaveChanges(Document::SaveFormsCapability)) { 2940 /* Set wontSaveForms only if there are forms */ 2941 const int pagecount = m_document->pages(); 2942 2943 for (int pageno = 0; pageno < pagecount; ++pageno) { 2944 const Okular::Page *page = m_document->page(pageno); 2945 if (!page->formFields().empty()) { 2946 wontSaveForms = true; 2947 break; 2948 } 2949 } 2950 } 2951 2952 if (!m_document->canSaveChanges(Document::SaveAnnotationsCapability)) { 2953 /* Set wontSaveAnnotations only if there are local annotations */ 2954 const int pagecount = m_document->pages(); 2955 2956 for (int pageno = 0; pageno < pagecount; ++pageno) { 2957 const QList<Okular::Annotation *> annotations = m_document->page(pageno)->annotations(); 2958 for (const Okular::Annotation *ann : annotations) { 2959 if (!(ann->flags() & Okular::Annotation::External)) { 2960 wontSaveAnnotations = true; 2961 break; 2962 } 2963 } 2964 if (wontSaveAnnotations) { 2965 break; 2966 } 2967 } 2968 } 2969 2970 *out_wontSaveForms = wontSaveForms; 2971 *out_wontSaveAnnotations = wontSaveAnnotations; 2972 } 2973 2974 void Part::slotPreferences() 2975 { 2976 // Create dialog 2977 PreferencesDialog *dialog = new PreferencesDialog(m_pageView, Okular::Settings::self(), m_embedMode, m_document->editorCommandOverride()); 2978 dialog->setAttribute(Qt::WA_DeleteOnClose); 2979 2980 // Show it 2981 dialog->show(); 2982 } 2983 2984 void Part::slotToggleChangeColors() 2985 { 2986 slotSetChangeColors(!Okular::SettingsCore::changeColors()); 2987 } 2988 2989 void Part::slotSetChangeColors(bool active) 2990 { 2991 Okular::SettingsCore::setChangeColors(active); 2992 Okular::Settings::self()->save(); 2993 } 2994 2995 void Part::slotAccessibilityPreferences() 2996 { 2997 // Create dialog 2998 PreferencesDialog *dialog = new PreferencesDialog(m_pageView, Okular::Settings::self(), m_embedMode, m_document->editorCommandOverride()); 2999 dialog->setAttribute(Qt::WA_DeleteOnClose); 3000 3001 // Show it 3002 dialog->switchToAccessibilityPage(); 3003 dialog->show(); 3004 } 3005 3006 void Part::slotAnnotationPreferences() 3007 { 3008 // Create dialog 3009 PreferencesDialog *dialog = new PreferencesDialog(m_pageView, Okular::Settings::self(), m_embedMode, m_document->editorCommandOverride()); 3010 dialog->setAttribute(Qt::WA_DeleteOnClose); 3011 3012 // Show it 3013 dialog->switchToAnnotationsPage(); 3014 dialog->show(); 3015 } 3016 3017 void Part::slotNewConfig() 3018 { 3019 // Apply settings here. A good policy is to check whether the setting has 3020 // changed before applying changes. 3021 3022 // Watch File 3023 setWatchFileModeEnabled(Okular::Settings::watchFile()); 3024 3025 // Main View (pageView) 3026 m_pageView->reparseConfig(); 3027 3028 // update document settings 3029 m_document->reparseConfig(); 3030 3031 // update TOC settings 3032 if (m_tocEnabled) { 3033 m_toc->reparseConfig(); 3034 } 3035 3036 // update ThumbnailList contents 3037 if (Okular::Settings::showLeftPanel() && !m_thumbnailList->isHidden()) { 3038 m_thumbnailList->updateWidgets(); 3039 } 3040 3041 // update Reviews settings 3042 m_reviewsWidget->reparseConfig(); 3043 3044 setWindowTitleFromDocument(); 3045 3046 if (m_presentationDrawingActions) { 3047 m_presentationDrawingActions->reparseConfig(); 3048 if (factory()) { 3049 factory()->refreshActionProperties(); 3050 } 3051 } 3052 } 3053 3054 void Part::slotPrintPreview() 3055 { 3056 if (m_document->pages() == 0) { 3057 return; 3058 } 3059 3060 QPrinter printer; 3061 QString tempFilePattern; 3062 3063 if (m_document->printingSupport() == Okular::Document::PostscriptPrinting) { 3064 tempFilePattern = (QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps")); 3065 } else if (m_document->printingSupport() == Okular::Document::NativePrinting) { 3066 tempFilePattern = (QDir::tempPath() + QLatin1String("/okular_XXXXXX.pdf")); 3067 } else { 3068 return; 3069 } 3070 3071 // Generate a temp filename for Print to File, then release the file so generator can write to it 3072 QTemporaryFile tf(tempFilePattern); 3073 tf.setAutoRemove(true); 3074 tf.open(); 3075 printer.setOutputFileName(tf.fileName()); 3076 tf.close(); 3077 setupPrint(printer); 3078 doPrint(printer); 3079 if (QFile::exists(printer.outputFileName())) { 3080 Okular::FilePrinterPreview previewdlg(printer.outputFileName(), widget()); 3081 previewdlg.exec(); 3082 } 3083 } 3084 3085 void Part::slotShowTOCMenu(const Okular::DocumentViewport &vp, const QPoint point, const QString &title) 3086 { 3087 showMenu(m_document->page(vp.pageNumber), point, title, vp, true); 3088 } 3089 3090 void Part::slotShowMenu(const Okular::Page *page, const QPoint point) 3091 { 3092 showMenu(page, point); 3093 } 3094 3095 void Part::showMenu(const Okular::Page *page, const QPoint point, const QString &bookmarkTitle, const Okular::DocumentViewport &vp, bool showTOCActions) 3096 { 3097 if (m_embedMode == PrintPreviewMode) { 3098 return; 3099 } 3100 3101 bool reallyShow = false; 3102 const bool isCurrentPage = page && page->number() == m_document->viewport().pageNumber; 3103 3104 if (!m_showMenuBarAction) { 3105 m_showMenuBarAction = findActionInKPartHierarchy<KToggleAction>(KStandardAction::name(KStandardAction::ShowMenubar)); 3106 } 3107 if (!m_showFullScreenAction) { 3108 m_showFullScreenAction = findActionInKPartHierarchy<KToggleFullScreenAction>(KStandardAction::name(KStandardAction::FullScreen)); 3109 } 3110 3111 QMenu popup; 3112 if (showTOCActions) { 3113 popup.addAction(i18n("Expand whole section"), m_toc.data(), &TOC::expandRecursively); 3114 popup.addAction(i18n("Collapse whole section"), m_toc.data(), &TOC::collapseRecursively); 3115 popup.addAction(i18n("Expand all"), m_toc.data(), &TOC::expandAll); 3116 popup.addAction(i18n("Collapse all"), m_toc.data(), &TOC::collapseAll); 3117 reallyShow = true; 3118 } 3119 3120 QAction *addBookmark = nullptr; 3121 QAction *removeBookmark = nullptr; 3122 QAction *fitPageWidth = nullptr; 3123 if (page) { 3124 popup.addAction(new OKMenuTitle(&popup, i18n("Page %1", page->number() + 1))); 3125 if (m_thumbnailList->isVisible() && !Okular::Settings::syncThumbnailsViewport()) { 3126 const QIcon &syncIcon = QIcon::fromTheme(QStringLiteral("emblem-synchronizing"), QIcon::fromTheme(QStringLiteral("view-refresh"))); 3127 popup.addAction(syncIcon, i18n("Sync Thumbnail with Page"), m_thumbnailList.data(), &ThumbnailList::syncThumbnail); 3128 } 3129 if ((!isCurrentPage && m_document->bookmarkManager()->isBookmarked(page->number())) || (isCurrentPage && m_document->bookmarkManager()->isBookmarked(m_document->viewport()))) { 3130 removeBookmark = popup.addAction(QIcon::fromTheme(QStringLiteral("bookmark-remove"), QIcon::fromTheme(QStringLiteral("edit-delete-bookmark"))), i18n("Remove Bookmark")); 3131 } else { 3132 addBookmark = popup.addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("Add Bookmark")); 3133 } 3134 if (m_pageView->canFitPageWidth()) { 3135 fitPageWidth = popup.addAction(QIcon::fromTheme(QStringLiteral("zoom-fit-best")), i18n("Fit Width")); 3136 } 3137 popup.addAction(m_prevBookmark); 3138 popup.addAction(m_nextBookmark); 3139 reallyShow = true; 3140 } 3141 3142 const int amountOfActions = popup.actions().count(); 3143 if (m_showMenuBarAction && !m_showMenuBarAction->isChecked()) { 3144 if (m_hamburgerMenuAction) { 3145 m_hamburgerMenuAction->addToMenu(&popup); 3146 } else { 3147 popup.addAction(m_showMenuBarAction); 3148 } 3149 } 3150 if (m_showFullScreenAction && m_showFullScreenAction->isChecked()) { 3151 popup.addAction(m_showFullScreenAction); 3152 } 3153 if (popup.actions().count() > amountOfActions && popup.actions().constLast()->isVisible()) { 3154 popup.insertAction(popup.actions().at(amountOfActions), new OKMenuTitle(&popup, i18n("Tools"))); 3155 reallyShow = true; 3156 } 3157 3158 if (reallyShow) { 3159 QAction *res = popup.exec(point); 3160 if (res) { 3161 if (res == addBookmark) { 3162 if (isCurrentPage && bookmarkTitle.isEmpty()) { 3163 m_document->bookmarkManager()->addBookmark(m_document->viewport()); 3164 } else if (!bookmarkTitle.isEmpty()) { 3165 m_document->bookmarkManager()->addBookmark(m_document->currentDocument(), vp, bookmarkTitle); 3166 } else { 3167 m_document->bookmarkManager()->addBookmark(page->number()); 3168 } 3169 } else if (res == removeBookmark) { 3170 if (isCurrentPage) { 3171 m_document->bookmarkManager()->removeBookmark(m_document->viewport()); 3172 } else { 3173 m_document->bookmarkManager()->removeBookmark(page->number()); 3174 } 3175 } else if (res == fitPageWidth) { 3176 m_pageView->fitPageWidth(page->number()); 3177 } 3178 } 3179 } 3180 } 3181 3182 template<class Action> Action *Part::findActionInKPartHierarchy(const QString &actionName) 3183 { 3184 static_assert(std::is_base_of<QAction, Action>::value, "Calling this method to find something other than an Action makes no sense."); 3185 if (factory()) { 3186 const QList<KXMLGUIClient *> clients(factory()->clients()); 3187 for (auto client : clients) { 3188 if (QAction *act = client->actionCollection()->action(actionName)) { 3189 if (Action *castedAction = qobject_cast<Action *>(act)) { 3190 return castedAction; 3191 } 3192 } 3193 } 3194 } 3195 return nullptr; 3196 } 3197 3198 KMainWindow *Part::findMainWindow() 3199 { 3200 auto *potentialMainWindow = parent(); 3201 while (potentialMainWindow) { 3202 if (auto *mainWindow = qobject_cast<KMainWindow *>(potentialMainWindow)) { 3203 return mainWindow; 3204 } 3205 potentialMainWindow = potentialMainWindow->parent(); 3206 } 3207 return nullptr; 3208 } 3209 3210 void Part::slotShowProperties() 3211 { 3212 PropertiesDialog *d = new PropertiesDialog(widget(), m_document); 3213 connect(d, &QDialog::finished, d, &QObject::deleteLater); 3214 d->open(); 3215 } 3216 3217 void Part::slotShowEmbeddedFiles() 3218 { 3219 EmbeddedFilesDialog *d = new EmbeddedFilesDialog(widget(), m_document); 3220 connect(d, &QDialog::finished, d, &QObject::deleteLater); 3221 d->open(); 3222 } 3223 3224 void Part::slotShowPresentation() 3225 { 3226 if (!m_presentationWidget) { 3227 m_presentationWidget = new PresentationWidget(widget(), m_document, m_presentationDrawingActions, actionCollection()); 3228 } 3229 } 3230 3231 void Part::slotHidePresentation() 3232 { 3233 if (m_presentationWidget) { 3234 delete m_presentationWidget.data(); 3235 } 3236 } 3237 3238 void Part::slotUpdateHamburgerMenu() 3239 { 3240 auto ac = actionCollection(); 3241 3242 auto menu = m_hamburgerMenuAction->menu(); 3243 if (!menu) { 3244 menu = new QMenu(widget()); 3245 m_hamburgerMenuAction->setMenu(menu); 3246 if (!m_showMenuBarAction) { 3247 m_showMenuBarAction = findActionInKPartHierarchy<KToggleAction>(KStandardAction::name(KStandardAction::ShowMenubar)); 3248 } 3249 m_hamburgerMenuAction->setShowMenuBarAction(m_showMenuBarAction); 3250 } else { 3251 menu->clear(); 3252 } 3253 3254 QToolBar *visibleMainToolbar = nullptr; 3255 if (auto *mainWindow = findMainWindow()) { 3256 visibleMainToolbar = mainWindow->toolBar(); 3257 if (!visibleMainToolbar->isVisible()) { 3258 visibleMainToolbar = nullptr; 3259 } 3260 const auto toolbars = mainWindow->toolBars(); 3261 for (const auto &toolbar : toolbars) { 3262 m_hamburgerMenuAction->hideActionsOf(toolbar); 3263 } 3264 3265 bool menuAvailable = false; // We already know the menu bar is hidden when this menu is opened. 3266 // If no menu is available, we want to add actions to the hamburger menu to show them again. 3267 // The hamburger menu serves as the fallback that is available through the right-click context menu. 3268 if (visibleMainToolbar && visibleMainToolbar->actions().contains(m_hamburgerMenuAction)) { 3269 menuAvailable = true; 3270 } 3271 if (!menuAvailable) { 3272 menu->addAction(m_showMenuBarAction); 3273 if (!visibleMainToolbar) { 3274 menu->addAction(findActionInKPartHierarchy(QStringLiteral("mainToolBar"))); 3275 } 3276 menu->addSeparator(); 3277 } 3278 } 3279 3280 // When changing actions, keep "Simple by default, powerful when needed" in mind. 3281 // To retrieve an action, it is fastest to use a direct pointer if available (m_action), otherwise use 3282 // ac->action(actionName) and if the action isn't in the actionCollection() of this part, 3283 // use findActionInKPartHierarchy(actionName). 3284 menu->addAction(findActionInKPartHierarchy(KStandardAction::name(KStandardAction::Open))); 3285 menu->addAction(findActionInKPartHierarchy(KStandardAction::name(KStandardAction::OpenRecent))); 3286 menu->addAction(m_save); 3287 menu->addAction(m_saveAs); 3288 menu->addSeparator(); 3289 menu->addAction(ac->action(QStringLiteral("mouse_drag"))); 3290 if (!visibleMainToolbar || !visibleMainToolbar->actions().contains(ac->action(QStringLiteral("mouse_selecttools")))) { 3291 menu->addAction(ac->action(QStringLiteral("mouse_select"))); 3292 } 3293 menu->addAction(m_copy); 3294 menu->addAction(m_find); 3295 menu->addAction(m_showLeftPanel); 3296 if (!visibleMainToolbar || visibleMainToolbar->actions().contains(ac->action(QStringLiteral("annotation_favorites")))) { 3297 menu->addAction(ac->action(QStringLiteral("mouse_toggle_annotate"))); 3298 } 3299 menu->addAction(ac->action(KStandardAction::name(KStandardAction::Undo))); 3300 menu->addAction(ac->action(KStandardAction::name(KStandardAction::Redo))); 3301 menu->addSeparator(); 3302 3303 menu->addAction(findActionInKPartHierarchy(KStandardAction::name(KStandardAction::Print))); 3304 menu->addAction(m_printPreview); 3305 menu->addSeparator(); 3306 menu->addAction(ac->action(QStringLiteral("add_digital_signature"))); 3307 menu->addAction(m_showProperties); 3308 menu->addAction(m_openContainingFolder); 3309 #if HAVE_PURPOSE 3310 menu->addAction(m_share); 3311 #endif 3312 menu->addSeparator(); 3313 3314 menu->addAction(ac->action(QStringLiteral("zoom_to"))); 3315 const QMenuBar *menuBar = m_hamburgerMenuAction->menuBar(); 3316 if (menuBar && menuBar->actions().count() < 3) { // 3 to make sure none of the code below can crash. 3317 menuBar = nullptr; 3318 } 3319 auto curatedViewMenu = menu->addMenu(QIcon::fromTheme(QStringLiteral("page-2sides")), menuBar ? menuBar->actions().at(1)->text() : QStringLiteral("View")); 3320 if (!m_showFullScreenAction) { 3321 m_showFullScreenAction = findActionInKPartHierarchy<KToggleFullScreenAction>(KStandardAction::name(KStandardAction::FullScreen)); 3322 } 3323 curatedViewMenu->addAction(m_showFullScreenAction); 3324 curatedViewMenu->addAction(m_showPresentation); 3325 curatedViewMenu->addSeparator(); 3326 curatedViewMenu->addAction(findActionInKPartHierarchy(QStringLiteral("view_render_mode"))); 3327 if (auto *viewOrientationMenu = qobject_cast<QMenu *>(factory()->container(QStringLiteral("view_orientation"), this))) { 3328 curatedViewMenu->addAction(viewOrientationMenu->menuAction()); 3329 } 3330 curatedViewMenu->addAction(findActionInKPartHierarchy(QStringLiteral("view_trim_mode"))); 3331 curatedViewMenu->addSeparator(); 3332 curatedViewMenu->addAction(ac->action(QStringLiteral("view_toggle_forms"))); 3333 m_hamburgerMenuAction->hideActionsOf(curatedViewMenu); 3334 3335 #if HAVE_SPEECH 3336 auto speakMenu = menu->addMenu(QIcon::fromTheme(QStringLiteral("text-speak")), i18nc("@action:inmenu menu that contains actions to control text to speach", "Speak")); 3337 speakMenu->addAction(ac->action(QStringLiteral("speak_document"))); 3338 speakMenu->addAction(ac->action(QStringLiteral("speak_current_page"))); 3339 speakMenu->addAction(ac->action(QStringLiteral("speak_stop_all"))); 3340 speakMenu->addAction(ac->action(QStringLiteral("speak_pause_resume"))); 3341 m_hamburgerMenuAction->hideActionsOf(speakMenu); 3342 #endif 3343 3344 // Add the "Settings" menu from the menu bar. 3345 if (menuBar) { 3346 menu->addAction(menuBar->actions().at(menuBar->actions().count() - 3)); 3347 } 3348 } 3349 3350 void Part::slotTogglePresentation() 3351 { 3352 if (m_document->isOpened()) { 3353 if (!m_presentationWidget) { 3354 m_presentationWidget = new PresentationWidget(widget(), m_document, m_presentationDrawingActions, actionCollection()); 3355 } else { 3356 delete m_presentationWidget.data(); 3357 } 3358 } 3359 } 3360 3361 void Part::reload() 3362 { 3363 if (m_document->isOpened()) { 3364 slotReload(); 3365 } 3366 } 3367 3368 void Part::enableStartWithPrint() 3369 { 3370 m_cliPrint = true; 3371 } 3372 3373 void Part::enableExitAfterPrint() 3374 { 3375 m_cliPrintAndExit = true; 3376 } 3377 3378 #define kKPlugin QStringLiteral("KPlugin") 3379 3380 void Part::slotAboutBackend() 3381 { 3382 const KPluginMetaData data = m_document->generatorInfo(); 3383 if (!data.isValid()) { 3384 return; 3385 } 3386 3387 // Here we do a bit of magic because KPluginMetaData doesn't have setters 3388 // so we get the json info from it, modify it and use that for the KAboutPluginDialog 3389 // in case the internals of KPluginMetaData change it won't be too bad, at most we're 3390 // missing the icon or the generator extra description 3391 QJsonObject rawData = data.rawData(); 3392 const QIcon icon = QIcon::fromTheme(data.iconName()); 3393 3394 // fall back to mime type icon 3395 if (icon.isNull()) { 3396 const Okular::DocumentInfo documentInfo = m_document->documentInfo(QSet<DocumentInfo::Key>() << DocumentInfo::MimeType); 3397 const QString mimeTypeName = documentInfo.get(DocumentInfo::MimeType); 3398 if (!mimeTypeName.isEmpty()) { 3399 QMimeDatabase db; 3400 QMimeType type = db.mimeTypeForName(mimeTypeName); 3401 if (type.isValid()) { 3402 QJsonObject kplugin = rawData[kKPlugin].toObject(); 3403 kplugin[QStringLiteral("Icon")] = type.iconName(); 3404 rawData[kKPlugin] = kplugin; 3405 } 3406 } 3407 } 3408 3409 const QString extraDescription = m_document->metaData(QStringLiteral("GeneratorExtraDescription")).toString(); 3410 3411 if (!extraDescription.isEmpty()) { 3412 const QString descriptionAndLang = QStringLiteral("Description[%1]").arg(QLocale().name()); 3413 QJsonObject kplugin = rawData[kKPlugin].toObject(); 3414 kplugin[descriptionAndLang] = QStringLiteral("%1\n\n%2").arg(data.description(), extraDescription); 3415 rawData[kKPlugin] = kplugin; 3416 } 3417 3418 KAboutPluginDialog dlg(KPluginMetaData(rawData, data.fileName()), widget()); 3419 dlg.exec(); 3420 } 3421 3422 void Part::slotExportAs(QAction *act) 3423 { 3424 QList<QAction *> acts = m_exportAs->menu() ? m_exportAs->menu()->actions() : QList<QAction *>(); 3425 int id = acts.indexOf(act); 3426 if ((id < 0) || (id >= acts.count())) { 3427 return; 3428 } 3429 3430 QMimeDatabase mimeDatabase; 3431 QMimeType mimeType; 3432 switch (id) { 3433 case 0: 3434 mimeType = mimeDatabase.mimeTypeForName(QStringLiteral("text/plain")); 3435 break; 3436 default: 3437 mimeType = m_exportFormats.at(id - 1).mimeType(); 3438 break; 3439 } 3440 QString filter = i18nc("File type name and pattern", "%1 (%2)", mimeType.comment(), mimeType.globPatterns().join(QLatin1Char(' '))); 3441 3442 QString fileName = QFileDialog::getSaveFileName(widget(), QString(), QString(), filter); 3443 3444 if (!fileName.isEmpty()) { 3445 bool saved = false; 3446 switch (id) { 3447 case 0: 3448 saved = m_document->exportToText(fileName); 3449 break; 3450 default: 3451 saved = m_document->exportTo(fileName, m_exportFormats.at(id - 1)); 3452 break; 3453 } 3454 if (!saved) { 3455 KMessageBox::information(widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", fileName)); 3456 } 3457 } 3458 } 3459 3460 void Part::slotReload() 3461 { 3462 // stop the dirty handler timer, otherwise we may conflict with the 3463 // auto-refresh system 3464 m_dirtyHandler->stop(); 3465 3466 slotAttemptReload(); 3467 } 3468 3469 void Part::slotPrint() 3470 { 3471 if (m_document->pages() == 0) { 3472 return; 3473 } 3474 3475 #ifdef Q_OS_WIN 3476 QPrinter printer(QPrinter::HighResolution); 3477 #else 3478 QPrinter printer; 3479 #endif 3480 QWidget *printConfigWidget = nullptr; 3481 3482 // Must do certain QPrinter setup before creating QPrintDialog 3483 setupPrint(printer); 3484 3485 // Create the Print Dialog with extra config widgets if required 3486 if (m_document->canConfigurePrinter()) { 3487 printConfigWidget = m_document->printConfigurationWidget(); 3488 } else { 3489 printConfigWidget = new DefaultPrintOptionsWidget(); 3490 } 3491 3492 QPrintDialog printDialog(&printer, widget()); 3493 printDialog.setWindowTitle(i18nc("@title:window", "Print")); 3494 QList<QWidget *> options; 3495 if (printConfigWidget) { 3496 options << printConfigWidget; 3497 } 3498 printDialog.setOptionTabs(options); 3499 3500 // Set the available Print Range 3501 printDialog.setMinMax(1, m_document->pages()); 3502 printDialog.setFromTo(1, m_document->pages()); 3503 3504 // If the user has bookmarked pages for printing, then enable Selection 3505 if (!m_document->bookmarkedPageRange().isEmpty()) { 3506 printDialog.setOption(QAbstractPrintDialog::PrintSelection); 3507 } 3508 3509 // If the Document type doesn't support print to both PS & PDF then disable the Print Dialog option 3510 if (printDialog.testOption(QAbstractPrintDialog::PrintToFile) && !m_document->supportsPrintToFile()) { 3511 printDialog.setOption(QAbstractPrintDialog::PrintToFile, false); 3512 } 3513 3514 // Enable the Current Page option in the dialog. 3515 if (m_document->pages() > 1 && currentPage() > 0) { 3516 printDialog.setOption(QAbstractPrintDialog::PrintCurrentPage); 3517 } 3518 3519 bool success = true; 3520 if (printDialog.exec()) { 3521 // set option for margins if widget is of corresponding type that holds this information 3522 PrintOptionsWidget *optionWidget = dynamic_cast<PrintOptionsWidget *>(printConfigWidget); 3523 if (optionWidget != nullptr) { 3524 printer.setFullPage(optionWidget->ignorePrintMargins()); 3525 } else { 3526 // printConfigurationWidget() method should always return an object of type Okular::PrintOptionsWidget, 3527 // (signature does not (yet) require it for ABI stability reasons), so Q_EMIT a warning if the object is of another type 3528 qWarning() << "printConfigurationWidget() method did not return an Okular::PrintOptionsWidget. This is strongly discouraged!"; 3529 } 3530 3531 success = doPrint(printer); 3532 } 3533 3534 if (m_cliPrintAndExit) { 3535 exit(success ? EXIT_SUCCESS : EXIT_FAILURE); 3536 } 3537 } 3538 3539 void Part::setupPrint(QPrinter &printer) 3540 { 3541 printer.setPageOrientation(m_document->orientation()); 3542 3543 // title 3544 QString title = m_document->metaData(QStringLiteral("DocumentTitle")).toString(); 3545 if (title.isEmpty()) { 3546 title = m_document->currentDocument().fileName(); 3547 } 3548 if (!title.isEmpty()) { 3549 printer.setDocName(title); 3550 } 3551 } 3552 3553 bool Part::doPrint(QPrinter &printer) 3554 { 3555 if (!m_document->isAllowed(Okular::AllowPrint)) { 3556 KMessageBox::error(widget(), i18n("Printing this document is not allowed.")); 3557 return false; 3558 } 3559 3560 const Document::PrintError printError = m_document->print(printer); 3561 if (printError != Document::NoPrintError) { 3562 const QString error = Okular::Document::printErrorString(printError); 3563 if (error.isEmpty()) { 3564 KMessageBox::error(widget(), i18n("Could not print the document. Unknown error. Please report to bugs.kde.org")); 3565 } else { 3566 KMessageBox::error(widget(), i18n("Could not print the document. Detailed error is \"%1\". Please report to bugs.kde.org", error)); 3567 } 3568 return false; 3569 } 3570 return true; 3571 } 3572 3573 void Part::psTransformEnded(int exit, QProcess::ExitStatus status) 3574 { 3575 Q_UNUSED(exit) 3576 if (status != QProcess::NormalExit) { 3577 return; 3578 } 3579 3580 QProcess *senderobj = sender() ? qobject_cast<QProcess *>(sender()) : nullptr; 3581 if (senderobj) { 3582 senderobj->close(); 3583 senderobj->deleteLater(); 3584 } 3585 3586 setLocalFilePath(m_temporaryLocalFile); 3587 openUrl(QUrl::fromLocalFile(m_temporaryLocalFile)); 3588 m_temporaryLocalFile.clear(); 3589 } 3590 3591 void Part::displayInfoMessage(const QString &message, KMessageWidget::MessageType messageType, int duration) 3592 { 3593 if (!Okular::Settings::showOSD()) { 3594 if (messageType == KMessageWidget::Error) { 3595 KMessageBox::error(widget(), message); 3596 } 3597 return; 3598 } 3599 3600 // hide messageWindow if string is empty 3601 if (message.isEmpty()) { 3602 m_infoMessage->animatedHide(); 3603 } 3604 3605 // display message (duration is length dependent) 3606 if (duration < 0) { 3607 duration = 500 + 100 * message.length(); 3608 } 3609 m_infoTimer->start(duration); 3610 m_infoMessage->setText(message); 3611 m_infoMessage->setMessageType(messageType); 3612 m_infoMessage->setVisible(true); 3613 } 3614 3615 void Part::errorMessage(const QString &message, int duration) 3616 { 3617 displayInfoMessage(message, KMessageWidget::Error, duration); 3618 } 3619 3620 void Part::warningMessage(const QString &message, int duration) 3621 { 3622 displayInfoMessage(message, KMessageWidget::Warning, duration); 3623 } 3624 3625 void Part::noticeMessage(const QString &message, int duration) 3626 { 3627 // less important message -> simpler display widget in the PageView 3628 m_pageView->displayMessage(message, QString(), PageViewMessage::Info, duration); 3629 } 3630 3631 void Part::moveSplitter(int sideWidgetSize) 3632 { 3633 m_sidebar->moveSplitter(sideWidgetSize); 3634 } 3635 3636 void Part::unsetDummyMode() 3637 { 3638 if (m_embedMode == PrintPreviewMode) { 3639 return; 3640 } 3641 3642 m_sidebar->setSidebarVisibility(Okular::Settings::showLeftPanel()); 3643 3644 // add back and next in history 3645 m_historyBack = KStandardAction::documentBack(this, SLOT(slotHistoryBack()), actionCollection()); 3646 m_historyBack->setWhatsThis(i18n("Go to the place you were before")); 3647 connect(m_pageView.data(), &PageView::mouseBackButtonClick, m_historyBack, &QAction::trigger); 3648 3649 m_historyNext = KStandardAction::documentForward(this, SLOT(slotHistoryNext()), actionCollection()); 3650 m_historyNext->setWhatsThis(i18n("Go to the place you were after")); 3651 connect(m_pageView.data(), &PageView::mouseForwardButtonClick, m_historyNext, &QAction::trigger); 3652 3653 m_pageView->setupActions(actionCollection()); 3654 3655 // attach the actions of the children widgets too 3656 m_formsMessage->addAction(m_pageView->toggleFormsAction()); 3657 3658 m_signatureMessage->addAction(m_showSignaturePanel); 3659 3660 // ensure history actions are in the correct state 3661 updateViewActions(); 3662 } 3663 3664 bool Part::handleCompressed(QString &destpath, const QString &path, KCompressionDevice::CompressionType compressionType) 3665 { 3666 m_tempfile = nullptr; 3667 3668 // we are working with a compressed file, decompressing 3669 // temporary file for decompressing 3670 QTemporaryFile *newtempfile = new QTemporaryFile(); 3671 newtempfile->setAutoRemove(true); 3672 3673 if (!newtempfile->open()) { 3674 KMessageBox::error(widget(), 3675 i18n("<qt><strong>File Error!</strong> Could not create temporary file " 3676 "<nobr><strong>%1</strong></nobr>.</qt>", 3677 newtempfile->errorString())); 3678 delete newtempfile; 3679 return false; 3680 } 3681 3682 // decompression filer 3683 KCompressionDevice dev(path, compressionType); 3684 3685 if (!dev.open(QIODevice::ReadOnly)) { 3686 KMessageBox::detailedError(widget(), 3687 i18n("<qt><strong>File Error!</strong> Could not open the file " 3688 "<nobr><strong>%1</strong></nobr> for uncompression. " 3689 "The file will not be loaded.</qt>", 3690 path), 3691 i18n("<qt>This error typically occurs if you do " 3692 "not have enough permissions to read the file. " 3693 "You can check ownership and permissions if you " 3694 "right-click on the file in the Dolphin " 3695 "file manager, then choose the 'Properties' option, " 3696 "and select 'Permissions' tab in the opened window.</qt>")); 3697 3698 delete newtempfile; 3699 return false; 3700 } 3701 3702 char buf[65536]; 3703 int read = 0; 3704 3705 while ((read = dev.read(buf, sizeof(buf))) > 0) { 3706 int wrtn = newtempfile->write(buf, read); 3707 if (read != wrtn) { 3708 break; 3709 } 3710 } 3711 if ((read != 0) || (newtempfile->size() == 0)) { 3712 KMessageBox::detailedError(widget(), 3713 i18n("<qt><strong>File Error!</strong> Could not uncompress " 3714 "the file <nobr><strong>%1</strong></nobr>. " 3715 "The file will not be loaded.</qt>", 3716 path), 3717 i18n("<qt>This error typically occurs if the file is corrupt. " 3718 "If you want to be sure, try to decompress the file manually " 3719 "using command-line tools.</qt>")); 3720 delete newtempfile; 3721 return false; 3722 } 3723 m_tempfile = newtempfile; 3724 destpath = m_tempfile->fileName(); 3725 return true; 3726 } 3727 3728 void Part::rebuildBookmarkMenu(bool unplugActions) 3729 { 3730 if (unplugActions) { 3731 unplugActionList(QStringLiteral("bookmarks_currentdocument")); 3732 qDeleteAll(m_bookmarkActions); 3733 m_bookmarkActions.clear(); 3734 } 3735 QUrl u = m_document->currentDocument(); 3736 if (u.isValid()) { 3737 m_bookmarkActions = m_document->bookmarkManager()->actionsForUrl(u); 3738 } 3739 bool havebookmarks = true; 3740 if (m_bookmarkActions.isEmpty()) { 3741 havebookmarks = false; 3742 QAction *a = new QAction(nullptr); 3743 a->setText(i18n("No Bookmarks")); 3744 a->setEnabled(false); 3745 m_bookmarkActions.append(a); 3746 } 3747 plugActionList(QStringLiteral("bookmarks_currentdocument"), m_bookmarkActions); 3748 3749 if (factory()) { 3750 const QList<KXMLGUIClient *> clients(factory()->clients()); 3751 bool containerFound = false; 3752 for (int i = 0; !containerFound && i < clients.size(); ++i) { 3753 QMenu *container = dynamic_cast<QMenu *>(factory()->container(QStringLiteral("bookmarks"), clients[i])); 3754 if (container && container->actions().contains(m_bookmarkActions.first())) { 3755 container->installEventFilter(this); 3756 containerFound = true; 3757 } 3758 } 3759 } 3760 3761 m_prevBookmark->setEnabled(havebookmarks); 3762 m_nextBookmark->setEnabled(havebookmarks); 3763 } 3764 3765 bool Part::eventFilter(QObject *watched, QEvent *event) 3766 { 3767 switch (event->type()) { 3768 case QEvent::ContextMenu: { 3769 QContextMenuEvent *e = static_cast<QContextMenuEvent *>(event); 3770 QMenu *menu = static_cast<QMenu *>(watched); 3771 3772 QScopedPointer<QMenu> ctxMenu(new QMenu); 3773 3774 QPoint pos; 3775 bool ret = false; 3776 if (e->reason() == QContextMenuEvent::Mouse) { 3777 pos = e->pos(); 3778 ret = aboutToShowContextMenu(menu, menu->actionAt(e->pos()), ctxMenu.data()); 3779 } else if (menu->activeAction()) { 3780 pos = menu->actionGeometry(menu->activeAction()).center(); 3781 ret = aboutToShowContextMenu(menu, menu->activeAction(), ctxMenu.data()); 3782 } 3783 ctxMenu->exec(menu->mapToGlobal(pos)); 3784 3785 if (ret) { 3786 event->accept(); 3787 } 3788 return ret; 3789 } 3790 3791 default: 3792 break; 3793 } 3794 3795 return KParts::ReadWritePart::eventFilter(watched, event); 3796 } 3797 3798 void Part::updateAboutBackendAction() 3799 { 3800 const KPluginMetaData data = m_document->generatorInfo(); 3801 m_aboutBackend->setEnabled(data.isValid()); 3802 } 3803 3804 void Part::resetStartArguments() 3805 { 3806 m_cliPrint = false; 3807 m_cliPrintAndExit = false; 3808 } 3809 3810 #if HAVE_PURPOSE 3811 void Part::slotShareActionFinished(const QJsonObject &output, int error, const QString &message) 3812 { 3813 if (error) { 3814 KMessageBox::error(widget(), i18n("There was a problem sharing the document: %1", message), i18n("Share")); 3815 } else { 3816 const QString url = output[QStringLiteral("url")].toString(); 3817 if (url.isEmpty()) { 3818 m_pageView->displayMessage(i18n("Document shared successfully")); 3819 } else { 3820 KMessageBox::information(widget(), i18n("You can find the shared document at: <a href=\"%1\">%1</a>", url), i18n("Share"), QString(), KMessageBox::Notify | KMessageBox::AllowLink); 3821 } 3822 } 3823 } 3824 #endif 3825 3826 void Part::setReadWrite(bool readwrite) 3827 { 3828 m_document->setAnnotationEditingEnabled(readwrite); 3829 ReadWritePart::setReadWrite(readwrite); 3830 } 3831 3832 void Part::enableStartWithFind(const QString &text) 3833 { 3834 m_textToFindOnOpen = QString(text); 3835 } 3836 3837 void Part::setEditorCmd(const QString &editorCmd) 3838 { 3839 m_document->setEditorCommandOverride(editorCmd); 3840 } 3841 3842 void Part::slotOpenContainingFolder() 3843 { 3844 KIO::highlightInFileManager({QUrl(localFilePath())}); 3845 } 3846 3847 } // namespace Okular 3848 3849 #include "part.moc" 3850 3851 /* kate: replace-tabs on; indent-width 4; */