diff --git a/CMakeLists.txt b/CMakeLists.txt
index ae4fa6af0..fe2525329 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,478 +1,480 @@
cmake_minimum_required(VERSION 3.0)
# KDE Application Version, managed by release script
set (KDE_APPLICATIONS_VERSION_MAJOR "19")
set (KDE_APPLICATIONS_VERSION_MINOR "07")
set (KDE_APPLICATIONS_VERSION_MICRO "70")
set (KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}")
project(okular VERSION 1.7.${KDE_APPLICATIONS_VERSION_MICRO})
set(QT_REQUIRED_VERSION "5.8.0")
set(KF5_REQUIRED_VERSION "5.44.0")
find_package(ECM 5.33.0 CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
include(ECMInstallIcons)
include(ECMSetupVersion)
include(ECMOptionalAddSubdirectory)
include(GenerateExportHeader)
include(FeatureSummary)
include(ECMAddAppIcon)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(ECMAddTests)
include(ECMAddAppIcon)
include(CMakePackageConfigHelpers)
ecm_setup_version(${PROJECT_VERSION}
VARIABLE_PREFIX OKULAR
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/core/version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/Okular5ConfigVersion.cmake")
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED COMPONENTS Core DBus Test Widgets PrintSupport Svg Qml Quick)
find_package(Qt5 ${QT_REQUIRED_VERSION} OPTIONAL_COMPONENTS TextToSpeech)
if (NOT Qt5TextToSpeech_FOUND)
message(STATUS "Qt5TextToSpeech not found, speech features will be disabled")
else()
add_definitions(-DHAVE_SPEECH)
endif()
if(NOT CMAKE_VERSION VERSION_LESS "3.10.0")
# CMake 3.9+ warns about automoc on files without Q_OBJECT, and doesn't know about other macros.
# 3.10+ lets us provide more macro names that require automoc.
list(APPEND CMAKE_AUTOMOC_MACRO_NAMES "OKULAR_EXPORT_PLUGIN")
endif()
set(optionalComponents)
if (ANDROID)
# we want to make sure that generally all components are found
set(optionalComponents "OPTIONAL_COMPONENTS")
endif()
find_package(KF5 ${KF5_REQUIRED_VERSION} REQUIRED COMPONENTS
Archive
Bookmarks
Completion
Config
ConfigWidgets
CoreAddons
Crash
IconThemes
KIO
Parts
ThreadWeaver
WindowSystem
${optionalComponents}
DocTools
JS
Wallet
)
if(KF5Wallet_FOUND)
add_definitions(-DWITH_KWALLET=1)
endif()
if(KF5JS_FOUND)
add_definitions(-DWITH_KJS=1)
endif()
if(NOT WIN32 AND NOT ANDROID)
find_package(KF5 ${KF5_REQUIRED_VERSION} REQUIRED COMPONENTS
Activities
)
set_package_properties("KF5Activities" PROPERTIES
DESCRIPTION "Activities interface library"
URL "https://api.kde.org/frameworks/kactivities/html/"
TYPE RECOMMENDED
PURPOSE "Required for Activities integration.")
endif()
find_package(KF5Kirigami2)
set_package_properties(KF5Kirigami2 PROPERTIES
DESCRIPTION "A QtQuick based components set"
PURPOSE "Required at runtime by the mobile app"
TYPE RUNTIME
)
find_package(Phonon4Qt5 CONFIG REQUIRED)
find_package(KF5Purpose)
set_package_properties(KF5Purpose PROPERTIES
DESCRIPTION "A framework for services and actions integration"
PURPOSE "Required for enabling the share menu in Okular"
TYPE OPTIONAL
)
if (KF5Purpose_FOUND)
set(PURPOSE_FOUND 1)
else()
set(PURPOSE_FOUND 0)
endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules)
find_package(ZLIB REQUIRED)
# This is here instead of in generators since we use if(Poppler_Qt5_FOUND) in autotests/
find_package(Poppler "0.12.1" COMPONENTS Qt5)
set_package_properties("Poppler" PROPERTIES
TYPE RECOMMENDED
PURPOSE "Support for PDF files in okular.")
add_definitions(-DQT_USE_FAST_OPERATOR_PLUS)
add_definitions(-DTRANSLATION_DOMAIN="okular")
add_definitions(-DQT_NO_URL_CAST_FROM_STRING)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${PHONON_INCLUDES} core/synctex ${ZLIB_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/core)
option(BUILD_OKULARKIRIGAMI "Builds the touch-friendly frontend" ON)
if (BUILD_OKULARKIRIGAMI)
add_subdirectory( mobile )
endif()
option(BUILD_COVERAGE "Build the project with gcov support" OFF)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.0.0")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override" )
endif()
endif()
if(BUILD_COVERAGE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov")
endif()
add_subdirectory( ui )
add_subdirectory( shell )
add_subdirectory( generators )
if(BUILD_TESTING)
add_subdirectory( autotests )
add_subdirectory( conf/autotests )
endif()
if(KF5DocTools_FOUND)
add_subdirectory(doc)
endif()
include(OkularConfigureChecks.cmake)
if(NOT WIN32)
set(MATH_LIB m)
else(NOT WIN32)
set(MATH_LIB)
endif(NOT WIN32)
# okularcore
set(okularcore_SRCS
core/action.cpp
core/annotations.cpp
core/area.cpp
core/audioplayer.cpp
core/bookmarkmanager.cpp
core/chooseenginedialog.cpp
core/document.cpp
core/documentcommands.cpp
core/fontinfo.cpp
core/form.cpp
core/generator.cpp
core/generator_p.cpp
core/misc.cpp
core/movie.cpp
core/observer.cpp
core/debug.cpp
core/page.cpp
core/pagecontroller.cpp
core/pagesize.cpp
core/pagetransition.cpp
core/rotationjob.cpp
core/scripter.cpp
core/sound.cpp
core/sourcereference.cpp
core/textdocumentgenerator.cpp
core/textdocumentsettings.cpp
core/textpage.cpp
core/tilesmanager.cpp
core/utils.cpp
core/view.cpp
core/fileprinter.cpp
+ core/printoptionswidget.cpp
core/signatureutils.cpp
core/script/event.cpp
core/synctex/synctex_parser.c
core/synctex/synctex_parser_utils.c
)
qt5_add_resources(okularcore_SRCS
core/script/builtin.qrc
)
ki18n_wrap_ui(okularcore_SRCS
conf/textdocumentsettings.ui
)
install( FILES
core/action.h
core/annotations.h
core/area.h
core/document.h
core/fontinfo.h
core/form.h
core/generator.h
core/global.h
core/page.h
core/pagesize.h
core/pagetransition.h
core/signatureutils.h
core/sound.h
core/sourcereference.h
core/textdocumentgenerator.h
core/textdocumentsettings.h
core/textpage.h
core/tile.h
core/utils.h
core/fileprinter.h
+ core/printoptionswidget.h
core/observer.h
${CMAKE_CURRENT_BINARY_DIR}/core/version.h
${CMAKE_CURRENT_BINARY_DIR}/core/okularcore_export.h
${CMAKE_CURRENT_BINARY_DIR}/settings_core.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/okular/core COMPONENT Devel)
install( FILES
interfaces/configinterface.h
interfaces/guiinterface.h
interfaces/printinterface.h
interfaces/saveinterface.h
interfaces/viewerinterface.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/okular/interfaces COMPONENT Devel)
ki18n_wrap_ui(okularcore_SRCS
core/chooseenginewidget.ui
)
kconfig_add_kcfg_files(okularcore_SRCS conf/settings_core.kcfgc)
add_library(okularcore SHARED ${okularcore_SRCS})
generate_export_header(okularcore BASE_NAME okularcore EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/core/okularcore_export.h")
if (ANDROID)
set(fileName ${CMAKE_BINARY_DIR}/Okular5Core-android-dependencies.xml)
file(WRITE "${fileName}" "\n"
"\n"
"\n")
install(FILES ${fileName} DESTINATION ${KDE_INSTALL_LIBDIR})
endif()
# Special handling for linking okularcore on OSX/Apple
IF(APPLE)
SET(OKULAR_IOKIT "-framework IOKit" CACHE STRING "Apple IOKit framework")
ENDIF(APPLE)
# Extra library needed by imported synctex code on Windows
if(WIN32)
set(SHLWAPI shlwapi)
endif(WIN32)
target_link_libraries(okularcore
PRIVATE
${OKULAR_IOKIT}
${SHLWAPI}
KF5::Archive
KF5::KIOCore
KF5::KIOWidgets
KF5::I18n
KF5::ThreadWeaver
KF5::Bookmarks
Phonon::phonon4qt5
${MATH_LIB}
${ZLIB_LIBRARIES}
PUBLIC # these are included from the installed headers
KF5::CoreAddons
KF5::XmlGui
KF5::ConfigGui
Qt5::PrintSupport
Qt5::Widgets
)
if (KF5Wallet_FOUND)
target_link_libraries(okularcore PRIVATE KF5::Wallet)
endif()
if (KF5JS_FOUND)
target_sources(okularcore PRIVATE
core/script/executor_kjs.cpp
core/script/kjs_app.cpp
core/script/kjs_console.cpp
core/script/kjs_data.cpp
core/script/kjs_document.cpp
core/script/kjs_field.cpp
core/script/kjs_fullscreen.cpp
core/script/kjs_field.cpp
core/script/kjs_spell.cpp
core/script/kjs_util.cpp
core/script/kjs_event.cpp
)
target_link_libraries(okularcore PRIVATE KF5::JS KF5::JSApi)
endif()
set_target_properties(okularcore PROPERTIES VERSION 9.0.0 SOVERSION 9 OUTPUT_NAME Okular5Core EXPORT_NAME Core)
install(TARGETS okularcore EXPORT Okular5Targets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES conf/okular.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
install(FILES conf/okular_core.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
install(FILES core/okularGenerator.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR})
# okularpart
set(okularpart_conf_SRCS
conf/preferencesdialog.cpp
conf/dlgaccessibility.cpp
conf/dlgdebug.cpp
conf/dlgeditor.cpp
conf/dlggeneral.cpp
conf/dlgannotations.cpp
conf/dlgperformance.cpp
conf/dlgpresentation.cpp
conf/editannottooldialog.cpp
conf/editdrawingtooldialog.cpp
conf/widgetannottools.cpp
conf/widgetconfigurationtoolsbase.cpp
conf/widgetdrawingtools.cpp
)
set(okularpart_SRCS
${okularpart_conf_SRCS}
part.cpp
extensions.cpp
ui/embeddedfilesdialog.cpp
ui/annotwindow.cpp
ui/annotationmodel.cpp
ui/annotationpopup.cpp
ui/annotationpropertiesdialog.cpp
ui/annotationproxymodels.cpp
ui/annotationtools.cpp
ui/annotationwidgets.cpp
ui/bookmarklist.cpp
ui/certificateviewer.cpp
ui/debug_ui.cpp
ui/drawingtoolactions.cpp
ui/fileprinterpreview.cpp
ui/findbar.cpp
ui/formwidgets.cpp
ui/guiutils.cpp
ui/ktreeviewsearchline.cpp
ui/latexrenderer.cpp
ui/minibar.cpp
ui/okmenutitle.cpp
ui/pageitemdelegate.cpp
ui/pagepainter.cpp
ui/pagesizelabel.cpp
ui/pageviewannotator.cpp
ui/pageviewmouseannotation.cpp
ui/pageview.cpp
ui/magnifierview.cpp
ui/pageviewutils.cpp
ui/presentationsearchbar.cpp
ui/presentationwidget.cpp
ui/propertiesdialog.cpp
ui/revisionviewer.cpp
ui/searchlineedit.cpp
ui/searchwidget.cpp
ui/sidebar.cpp
ui/side_reviews.cpp
ui/snapshottaker.cpp
ui/thumbnaillist.cpp
ui/toc.cpp
ui/tocmodel.cpp
ui/toolaction.cpp
ui/videowidget.cpp
ui/layers.cpp
ui/signatureguiutils.cpp
ui/signaturepropertiesdialog.cpp
ui/signaturemodel.cpp
ui/signaturepanel.cpp
)
if (Qt5TextToSpeech_FOUND)
set(okularpart_SRCS ${okularpart_SRCS}
ui/tts.cpp)
endif()
ki18n_wrap_ui(okularpart_SRCS
conf/dlgaccessibilitybase.ui
conf/dlgeditorbase.ui
conf/dlggeneralbase.ui
conf/dlgannotationsbase.ui
conf/dlgperformancebase.ui
conf/dlgpresentationbase.ui
)
kconfig_add_kcfg_files(okularpart_SRCS conf/settings.kcfgc)
add_library(okularpart SHARED ${okularpart_SRCS})
generate_export_header(okularpart BASE_NAME okularpart)
target_link_libraries(okularpart okularcore
${MATH_LIB}
Qt5::Svg
Phonon::phonon4qt5
KF5::Archive
KF5::Bookmarks
KF5::I18n
KF5::IconThemes
KF5::ItemViews
KF5::KIOCore
KF5::KIOFileWidgets
KF5::KIOWidgets
KF5::Parts
KF5::Solid
KF5::WindowSystem
)
if(KF5Wallet_FOUND)
target_link_libraries(okularpart KF5::Wallet)
endif()
if (KF5Purpose_FOUND)
target_link_libraries(okularpart KF5::PurposeWidgets)
endif()
set_target_properties(okularpart PROPERTIES PREFIX "")
if (Qt5TextToSpeech_FOUND)
target_link_libraries(okularpart Qt5::TextToSpeech)
endif()
install(TARGETS okularpart DESTINATION ${KDE_INSTALL_PLUGINDIR})
########### install files ###############
install(FILES okular.upd DESTINATION ${KDE_INSTALL_DATADIR}/kconf_update)
install( FILES okular_part.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} )
install( FILES part.rc part-viewermode.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/okular )
install( FILES okular.categories DESTINATION ${KDE_INSTALL_CONFDIR} )
########### cmake files #################
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/Okular5")
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/Okular5Config.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/Okular5Config.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
PATH_VARS INCLUDE_INSTALL_DIR CMAKE_INSTALL_PREFIX
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/Okular5Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/Okular5ConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
install(EXPORT Okular5Targets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE Okular5Targets.cmake NAMESPACE Okular::)
########### summary #################
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/core/document.h b/core/document.h
index cf98aacbe..8485befc4 100644
--- a/core/document.h
+++ b/core/document.h
@@ -1,1452 +1,1456 @@
/***************************************************************************
* Copyright (C) 2004-2005 by Enrico Ros *
* Copyright (C) 2004-2008 by Albert Astals Cid *
* Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group *
* company, info@kdab.com. Work sponsored by the *
* LiMux project of the city of Munich *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#ifndef _OKULAR_DOCUMENT_H_
#define _OKULAR_DOCUMENT_H_
#include "okularcore_export.h"
#include "area.h"
#include "global.h"
#include "pagesize.h"
#include
#include
#include
#include
#include
#include
#include
class QPrintDialog;
class KBookmark;
class KConfigDialog;
class KPluginMetaData;
class KXMLGUIClient;
class DocumentItem;
class QAbstractItemModel;
namespace Okular {
class Annotation;
class BookmarkManager;
class DocumentInfoPrivate;
class DocumentObserver;
class DocumentPrivate;
class DocumentSynopsis;
class DocumentViewport;
class EmbeddedFile;
class ExportFormat;
class FontInfo;
class FormField;
class FormFieldText;
class FormFieldButton;
class FormFieldChoice;
class Generator;
class Action;
class MovieAction;
class Page;
class PixmapRequest;
class RenditionAction;
class SourceReference;
class View;
class VisiblePageRect;
class SignatureInfo;
/** IDs for seaches. Globally defined here. **/
#define PART_SEARCH_ID 1
#define PAGEVIEW_SEARCH_ID 2
#define SW_SEARCH_ID 3
#define PRESENTATION_SEARCH_ID 4
/**
* The DocumentInfo structure can be filled in by generators to display
* metadata about the currently opened file.
*/
class OKULARCORE_EXPORT DocumentInfo
{
friend class Document;
public:
/**
* The list of predefined keys.
*/
enum Key {
Title, ///< The title of the document
Subject, ///< The subject of the document
Description, ///< The description of the document
Author, ///< The author of the document
Creator, ///< The creator of the document (this can be different from the author)
Producer, ///< The producer of the document (e.g. some software)
Copyright, ///< The copyright of the document
Pages, ///< The number of pages of the document
CreationDate, ///< The date of creation of the document
ModificationDate, ///< The date of last modification of the document
MimeType, ///< The mime type of the document
Category, ///< The category of the document
Keywords, ///< The keywords which describe the content of the document
FilePath, ///< The path of the file @since 0.10 (KDE 4.4)
DocumentSize, ///< The size of the document @since 0.10 (KDE 4.4)
PagesSize, ///< The size of the pages (if all pages have the same size) @since 0.10 (KDE 4.4)
CustomKeys, ///< All the custom keys the generator supports @since 0.21
Invalid ///< An invalid key @since 0.21. It will always be the last element in the enum
};
/**
* Creates a new document info.
*/
DocumentInfo();
DocumentInfo(const DocumentInfo &info);
DocumentInfo& operator=( const DocumentInfo& );
~DocumentInfo();
/**
* Returns all the keys present in this DocumentInfo
*
* @since 0.21
*/
QStringList keys() const;
/**
* Returns the value for a given key or an null string when the
* key doesn't exist.
*/
QString get( Key key ) const;
/**
* Returns the value for a given key or an null string when the
* key doesn't exist.
*/
QString get( const QString &key ) const;
/**
* Sets a value for a custom key. The title should be an i18n'ed
* string, since it's used in the document information dialog.
*/
void set( const QString &key, const QString &value,
const QString &title = QString() );
/**
* Sets a value for a special key. The title should be an i18n'ed
* string, since it's used in the document information dialog.
*/
void set( Key key, const QString &value );
/**
* Returns the user visible string for the given key
* Takes into account keys added by the set() that takes a QString
*
* @since 0.21
*/
QString getKeyTitle( const QString &key ) const;
/**
* Returns the internal string for the given key
* @since 0.10 (KDE 4.4)
*/
static QString getKeyString( Key key );
/**
* Returns the user visible string for the given key
* @since 0.10 (KDE 4.4)
*/
static QString getKeyTitle( Key key );
/**
* Returns the Key from a string key
* @since 0.21
*/
static Key getKeyFromString( const QString &key );
private:
DocumentInfoPrivate *d;
};
/**
* @short The Document. Heart of everything. Actions take place here.
*
* The Document is the main object in Okular. All views query the Document to
* get data/properties or even for accessing pages (in a 'const' way).
*
* It is designed to keep it detached from the document type (pdf, ps, you
* name it..) so whenever you want to get some data, it asks its internal
* generators to do the job and return results in a format-independent way.
*
* Apart from the generator (the currently running one) the document stores
* all the Pages ('Page' class) of the current document in a vector and
* notifies all the registered DocumentObservers when some content changes.
*
* For a better understanding of hierarchies @see README.internals.png
* @see DocumentObserver, Page
*/
class OKULARCORE_EXPORT Document : public QObject
{
Q_OBJECT
public:
/**
* Creates a new document with the given @p widget as widget to relay GUI things (messageboxes, ...).
*/
explicit Document( QWidget *widget );
/**
* Destroys the document.
*/
~Document();
/**
* Describes the result of an open document operation.
* @since 0.20 (KDE 4.14)
*/
enum OpenResult
{
OpenSuccess, //< The document was opened successfully
OpenError, //< The document failed to open
OpenNeedsPassword //< The document needs a password to be opened or the one provided is not the correct
};
/**
* Opens the document.
* @since 0.20 (KDE 4.14)
*/
OpenResult openDocument( const QString & docFile, const QUrl & url, const QMimeType &mime, const QString &password = QString() );
/**
* Closes the document.
*/
void closeDocument();
/**
* Registers a new @p observer for the document.
*/
void addObserver( DocumentObserver *observer );
/**
* Unregisters the given @p observer for the document.
*/
void removeObserver( DocumentObserver *observer );
/**
* Reparses and applies the configuration.
*/
void reparseConfig();
/**
* Returns whether the document is currently opened.
*/
bool isOpened() const;
/**
* Returns the meta data of the document.
*/
DocumentInfo documentInfo() const;
/**
* Returns the asked set of meta data of the document. The result may contain more
* metadata than the one asked for.
*/
DocumentInfo documentInfo( const QSet &keys ) const;
/**
* Returns the table of content of the document or 0 if no
* table of content is available.
*/
const DocumentSynopsis * documentSynopsis() const;
/**
* Starts the reading of the information about the fonts in the
* document, if available.
*
* The results as well the end of the reading is notified using the
* signals gotFont(), fontReadingProgress() and fontReadingEnded()
*/
void startFontReading();
/**
* Force the termination of the reading of the information about the
* fonts in the document, if running.
*/
void stopFontReading();
/**
* Whether the current document can provide information about the
* fonts used in it.
*/
bool canProvideFontInformation() const;
/**
* Returns the list of embedded files or 0 if no embedded files
* are available.
*/
const QList *embeddedFiles() const;
/**
* Returns the page object for the given page @p number or 0
* if the number is out of range.
*/
const Page * page( int number ) const;
/**
* Returns the current viewport of the document.
*/
const DocumentViewport & viewport() const;
/**
* Sets the list of visible page rectangles.
* @see VisiblePageRect
*/
void setVisiblePageRects( const QVector< VisiblePageRect * > & visiblePageRects, DocumentObserver *excludeObserver = nullptr );
/**
* Returns the list of visible page rectangles.
*/
const QVector< VisiblePageRect * > & visiblePageRects() const;
/**
* Returns the number of the current page.
*/
uint currentPage() const;
/**
* Returns the number of pages of the document.
*/
uint pages() const;
/**
* Returns the url of the currently opened document.
*/
QUrl currentDocument() const;
/**
* Returns whether the given @p action is allowed in the document.
* @see @ref Permission
*/
bool isAllowed( Permission action ) const;
/**
* Returns whether the document supports searching.
*/
bool supportsSearching() const;
/**
* Returns whether the document supports the listing of page sizes.
*/
bool supportsPageSizes() const;
/**
* Returns whether the current document supports tiles
*
* @since 0.16 (KDE 4.10)
*/
bool supportsTiles() const;
/**
* Returns the list of supported page sizes or an empty list if this
* feature is not available.
* @see supportsPageSizes()
*/
PageSize::List pageSizes() const;
/**
* Returns whether the document supports the export to ASCII text.
*/
bool canExportToText() const;
/**
* Exports the document as ASCII text and saves it under @p fileName.
*/
bool exportToText( const QString& fileName ) const;
/**
* Returns the list of supported export formats.
* @see ExportFormat
*/
QList exportFormats() const;
/**
* Exports the document in the given @p format and saves it under @p fileName.
*/
bool exportTo( const QString& fileName, const ExportFormat& format ) const;
/**
* Returns whether the document history is at the begin.
*/
bool historyAtBegin() const;
/**
* Returns whether the document history is at the end.
*/
bool historyAtEnd() const;
/**
* Returns the meta data for the given @p key and @p option or an empty variant
* if the key doesn't exists.
*/
QVariant metaData( const QString & key, const QVariant & option = QVariant() ) const;
/**
* Returns the current rotation of the document.
*/
Rotation rotation() const;
/**
* If all pages have the same size this method returns it, if the page sizes
* differ an empty size object is returned.
*/
QSizeF allPagesSize() const;
/**
* Returns the size string for the given @p page or an empty string
* if the page is out of range.
*/
QString pageSizeString( int page ) const;
/**
* Returns the gui client of the generator, if it provides one.
*/
KXMLGUIClient* guiClient();
/**
* Sets the current document viewport to the given @p page.
*
* @param page The number of the page.
* @param excludeObserver The observer ids which shouldn't be effected by this change.
* @param smoothMove Whether the move shall be animated smoothly.
*/
void setViewportPage( int page, DocumentObserver *excludeObserver = nullptr, bool smoothMove = false );
/**
* Sets the current document viewport to the given @p viewport.
*
* @param viewport The document viewport.
* @param excludeObserver The observer which shouldn't be effected by this change.
* @param smoothMove Whether the move shall be animated smoothly.
*/
void setViewport( const DocumentViewport &viewport, DocumentObserver *excludeObserver = nullptr, bool smoothMove = false );
/**
* Sets the current document viewport to the next viewport in the
* viewport history.
*/
void setPrevViewport();
/**
* Sets the current document viewport to the previous viewport in the
* viewport history.
*/
void setNextViewport();
/**
* Sets the next @p viewport in the viewport history.
*/
void setNextDocumentViewport( const DocumentViewport &viewport );
/**
* Sets the next @p namedDestination in the viewport history.
*
* @since 0.9 (KDE 4.3)
*/
void setNextDocumentDestination( const QString &namedDestination );
/**
* Sets the zoom for the current document.
*/
void setZoom( int factor, DocumentObserver *excludeObserver = nullptr );
/**
* Describes the possible options for the pixmap requests.
*/
enum PixmapRequestFlag
{
NoOption = 0, ///< No options
RemoveAllPrevious = 1 ///< Remove all the previous requests, even for non requested page pixmaps
};
Q_DECLARE_FLAGS( PixmapRequestFlags, PixmapRequestFlag )
/**
* Sends @p requests for pixmap generation.
*
* The same as requestPixmaps( requests, RemoveAllPrevious );
*/
void requestPixmaps( const QLinkedList &requests );
/**
* Sends @p requests for pixmap generation.
*
* @param requests the linked list of requests
* @param reqOptions the options for the request
*
* @since 0.7 (KDE 4.1)
*/
void requestPixmaps( const QLinkedList &requests, PixmapRequestFlags reqOptions );
/**
* Sends a request for text page generation for the given page @p number.
*/
void requestTextPage( uint number );
/**
* Adds a new @p annotation to the given @p page.
*/
void addPageAnnotation( int page, Annotation *annotation );
/**
* Tests if the @p annotation can be modified
*
* @since 0.15 (KDE 4.9)
*/
bool canModifyPageAnnotation( const Annotation * annotation ) const;
/**
* Prepares to modify the properties of the given @p annotation.
* Must be called before the annotation's properties are modified
*
* @since 0.17 (KDE 4.11)
*/
void prepareToModifyAnnotationProperties( Annotation * annotation );
/**
* Modifies the given @p annotation on the given @p page.
* Must be preceded by a call to prepareToModifyAnnotationProperties before
* the annotation's properties are modified
*
* @since 0.17 (KDE 4.11)
*/
void modifyPageAnnotationProperties( int page, Annotation * annotation );
/**
* Translates the position of the given @p annotation on the given @p page by a distance @p delta in normalized coordinates.
*
* Consecutive translations applied to the same @p annotation are merged together on the undo stack if the
* BeingMoved flag is set on the @p annotation.
*
* @since 0.17 (KDE 4.11)
*/
void translatePageAnnotation( int page, Annotation *annotation, const Okular::NormalizedPoint & delta );
/**
* Adjusts the position of the top-left and bottom-right corners of given @p annotation on the given @p page.
*
* Can be used to implement resize functionality.
* @p delta1 in normalized coordinates is added to top-left.
* @p delta2 in normalized coordinates is added to bottom-right.
*
* Consecutive adjustments applied to the same @p annotation are merged together on the undo stack if the
* BeingResized flag is set on the @p annotation.
*
* @since 1.1.0
*/
void adjustPageAnnotation( int page, Annotation * annotation, const Okular::NormalizedPoint & delta1, const Okular::NormalizedPoint & delta2 );
/**
* Edits the plain text contents of the given @p annotation on the given @p page.
*
* The contents are set to @p newContents with cursor position @p newCursorPos.
* The previous cursor position @p prevCursorPos and previous anchor position @p prevAnchorPos
* must also be supplied so that they can be restored if the edit action is undone.
*
* The Annotation's internal contents should not be modified prior to calling this method.
*
* @since 0.17 (KDE 4.11)
*/
void editPageAnnotationContents( int page, Annotation* annotation, const QString & newContents,
int newCursorPos, int prevCursorPos, int prevAnchorPos );
/**
* Tests if the @p annotation can be removed
*
* @since 0.15 (KDE 4.9)
*/
bool canRemovePageAnnotation( const Annotation * annotation ) const;
/**
* Removes the given @p annotation from the given @p page.
*/
void removePageAnnotation( int page, Annotation *annotation );
/**
* Removes the given @p annotations from the given @p page.
*/
void removePageAnnotations( int page, const QList &annotations );
/**
* Sets the text selection for the given @p page.
*
* @param page The number of the page.
* @param rect The rectangle of the selection.
* @param color The color of the selection.
*/
void setPageTextSelection( int page, RegularAreaRect * rect, const QColor & color );
/**
* Returns true if there is an undo command available; otherwise returns false.
* @since 0.17 (KDE 4.11)
*/
bool canUndo() const;
/**
* Returns true if there is a redo command available; otherwise returns false.
* @since 0.17 (KDE 4.11)
*/
bool canRedo() const;
/**
* Describes the possible search types.
*/
enum SearchType
{
NextMatch, ///< Search next match
PreviousMatch, ///< Search previous match
AllDocument, ///< Search complete document
GoogleAll, ///< Search complete document (all words in google style)
GoogleAny ///< Search complete document (any words in google style)
};
/**
* Describes how search ended
*/
// TODO remove EndOfDocumentReached when we break API
enum SearchStatus
{
MatchFound, ///< Any match was found
NoMatchFound, ///< No match was found
SearchCancelled, ///< The search was cancelled
EndOfDocumentReached ///< This is not ever emitted since 1.3. The end of document was reached without any match @since 0.20 (KDE 4.14)
};
/**
* Searches the given @p text in the document.
*
* @param searchID The unique id for this search request.
* @param text The text to be searched.
* @param fromStart Whether the search should be started at begin of the document.
* @param caseSensitivity Whether the search is case sensitive.
* @param type The type of the search. @ref SearchType
* @param moveViewport Whether the viewport shall be moved to the position of the matches.
* @param color The highlighting color of the matches.
*/
void searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity,
SearchType type, bool moveViewport, const QColor & color );
/**
* Continues the search for the given @p searchID.
*/
void continueSearch( int searchID );
/**
* Continues the search for the given @p searchID, optionally specifying
* a new type for the search.
*
* @since 0.7 (KDE 4.1)
*/
void continueSearch( int searchID, SearchType type );
/**
* Resets the search for the given @p searchID.
*/
void resetSearch( int searchID );
/**
* Returns the bookmark manager of the document.
*/
BookmarkManager * bookmarkManager() const;
/**
* Processes the given @p action.
*/
void processAction( const Action *action );
/**
* Returns a list of the bookmarked.pages
*/
QList bookmarkedPageList() const;
/**
* Returns the range of the bookmarked.pages
*/
QString bookmarkedPageRange() const;
/**
* Processes/Executes the given source @p reference.
*/
void processSourceReference( const SourceReference *reference );
/**
* Returns whether the document can configure the printer itself.
*/
bool canConfigurePrinter() const;
/**
* What type of printing a document supports
*/
enum PrintingType
{
NoPrinting, ///< Printing Not Supported
NativePrinting, ///< Native Cross-Platform Printing
PostscriptPrinting ///< Postscript file printing
};
/**
* Returns what sort of printing the document supports:
* Native, Postscript, None
*/
PrintingType printingSupport() const;
/**
* Returns whether the document supports printing to both PDF and PS files.
*/
bool supportsPrintToFile() const;
/**
* Prints the document to the given @p printer.
*/
bool print( QPrinter &printer );
/**
* Returns the last print error in case print() failed
* @since 0.11 (KDE 4.5)
*/
QString printError() const;
/**
* Returns a custom printer configuration page or 0 if no
* custom printer configuration page is available.
+ *
+ * The returned object should be of a PrintOptionsWidget subclass
+ * (which is not officially enforced by the signature for binary
+ * compatibility reasons).
*/
QWidget* printConfigurationWidget() const;
/**
* Fill the KConfigDialog @p dialog with the setting pages of the
* generators.
*/
void fillConfigDialog( KConfigDialog * dialog );
/**
* Returns the number of generators that have a configuration widget.
*/
int configurableGenerators() const;
/**
* Returns the list with the supported MIME types.
*/
QStringList supportedMimeTypes() const;
/**
* Returns the metadata associated with the generator. May be invalid.
*/
KPluginMetaData generatorInfo() const;
/**
* Returns whether the generator supports hot-swapping the current file
* with another identical file
*
* @since 1.3
*/
bool canSwapBackingFile() const;
/**
* Reload the document from a new location, without any visible effect
* to the user.
*
* The new file must be identical to the current one or, if the document
* has been modified (eg the user edited forms and annotations), the new
* document must have these changes too. For example, you can call
* saveChanges first to write changes to a file and then swapBackingFile
* to switch to the new location.
*
* @since 1.3
*/
bool swapBackingFile( const QString &newFileName, const QUrl &url );
/**
* Same as swapBackingFile, but newFileName must be a .okular file.
*
* The new file must be identical to the current one or, if the document
* has been modified (eg the user edited forms and annotations), the new
* document must have these changes too. For example, you can call
* saveDocumentArchive first to write changes to a file and then
* swapBackingFileArchive to switch to the new location.
*
* @since 1.3
*/
bool swapBackingFileArchive( const QString &newFileName, const QUrl &url );
/**
* Sets the history to be clean
*
* @since 1.3
*/
void setHistoryClean( bool clean );
/**
* Saving capabilities. Their availability varies according to the
* underlying generator and/or the document type.
*
* @see canSaveChanges (SaveCapability)
* @since 0.15 (KDE 4.9)
*/
enum SaveCapability
{
SaveFormsCapability = 1, ///< Can save form changes
SaveAnnotationsCapability = 2 ///< Can save annotation changes
};
/**
* Returns whether it's possible to save a given category of changes to
* another document.
*
* @since 0.15 (KDE 4.9)
*/
bool canSaveChanges( SaveCapability cap ) const;
/**
* Returns whether the changes to the document (modified annotations,
* values in form fields, etc) can be saved to another document.
*
* Equivalent to the logical OR of canSaveChanges(SaveCapability) for
* each capability.
*
* @since 0.7 (KDE 4.1)
*/
bool canSaveChanges() const;
/**
* Save the document and the optional changes to it to the specified
* @p fileName.
*
* @since 0.7 (KDE 4.1)
*/
bool saveChanges( const QString &fileName );
/**
* Save the document and the optional changes to it to the specified
* @p fileName and returns a @p errorText if fails.
*
* @since 0.10 (KDE 4.4)
*/
bool saveChanges( const QString &fileName, QString *errorText );
/**
* Register the specified @p view for the current document.
*
* It is unregistered from the previous document, if any.
*
* @since 0.7 (KDE 4.1)
*/
void registerView( View *view );
/**
* Unregister the specified @p view from the current document.
*
* @since 0.7 (KDE 4.1)
*/
void unregisterView( View *view );
/**
* Gets the font data for the given font
*
* @since 0.8 (KDE 4.2)
*/
QByteArray fontData(const FontInfo &font) const;
/**
* Opens a document archive.
*
* @since 0.20 (KDE 4.14)
*/
OpenResult openDocumentArchive( const QString & docFile, const QUrl & url, const QString &password = QString() );
/**
* Saves a document archive.
*
* @since 0.8 (KDE 4.2)
*/
bool saveDocumentArchive( const QString &fileName );
/**
* Extract the document file from the current archive.
*
* @warning This function only works if the current file is a document archive
*
* @since 1.3
*/
bool extractArchivedFile( const QString &destFileName );
/**
* Asks the generator to dynamically generate a SourceReference for a given
* page number and absolute X and Y position on this page.
*
* @attention Ownership of the returned SourceReference is transferred to the caller.
* @note This method does not call processSourceReference( const SourceReference * )
*
* @since 0.10 (KDE 4.4)
*/
const SourceReference * dynamicSourceReference( int pageNr, double absX, double absY );
/**
* Returns the orientation of the document (for printing purposes). This
* is used in the KPart to initialize the print dialog and in the
* generators to check whether the document needs to be rotated or not.
*
* @since 0.14 (KDE 4.8)
*/
QPrinter::Orientation orientation() const;
/**
* Control annotation editing (creation, modification and removal),
* which is enabled by default.
*
* @since 0.15 (KDE 4.9)
*/
void setAnnotationEditingEnabled( bool enable );
/**
* Returns which wallet data to use to read/write the password for the given fileName
*
* @since 0.20 (KDE 4.14)
*/
void walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const;
/**
* Since version 0.21, okular does not allow editing annotations and
* form data if they are stored in the docdata directory (like older
* okular versions did by default).
* If this flag is set, then annotations and forms cannot be edited.
*
* @since 1.3
*/
bool isDocdataMigrationNeeded() const;
/**
* Delete annotations and form data from the docdata folder. Call it if
* isDocdataMigrationNeeded() was true and you've just saved them to an
* external file.
*
* @since 1.3
*/
void docdataMigrationDone();
/**
* Returns the model for rendering layers (NULL if the document has no layers)
*
* @since 0.24
*/
QAbstractItemModel * layersModel() const;
public Q_SLOTS:
/**
* This slot is called whenever the user changes the @p rotation of
* the document.
*/
void setRotation( int rotation );
/**
* This slot is called whenever the user changes the page @p size
* of the document.
*/
void setPageSize( const PageSize &size );
/**
* Cancels the current search
*/
void cancelSearch();
/**
* Undo last edit command
* @since 0.17 (KDE 4.11)
*/
void undo();
/**
* Redo last undone edit command
* @since 0.17 (KDE 4.11)
*/
void redo();
/**
* Edit the text contents of the specified @p form on page @p page to be @p newContents.
* The new text cursor position (@p newCursorPos), previous text cursor position (@p prevCursorPos),
* and previous cursor anchor position will be restored by the undo / redo commands.
* @since 0.17 (KDE 4.11)
*/
void editFormText( int pageNumber,
Okular::FormFieldText* form,
const QString & newContents,
int newCursorPos,
int prevCursorPos,
int prevAnchorPos );
/**
* Edit the selected list entries in @p form on page @p page to be @p newChoices.
* @since 0.17 (KDE 4.11)
*/
void editFormList( int pageNumber,
Okular::FormFieldChoice* form,
const QList & newChoices );
/**
* Set the active choice in the combo box @p form on page @p page to @p newText
* The new cursor position (@p newCursorPos), previous cursor position
* (@p prevCursorPos), and previous anchor position (@p prevAnchorPos)
* will be restored by the undo / redo commands.
*
* @since 0.17 (KDE 4.11)
*/
void editFormCombo( int pageNumber,
Okular::FormFieldChoice *form,
const QString & newText,
int newCursorPos,
int prevCursorPos,
int prevAnchorPos );
/**
* Set the states of the group of form buttons @p formButtons on page @p page to @p newButtonStates.
* The lists @p formButtons and @p newButtonStates should be the same length and true values
* in @p newButtonStates indicate that the corresponding entry in @p formButtons should be enabled.
*/
void editFormButtons( int pageNumber,
const QList< Okular::FormFieldButton* > & formButtons,
const QList< bool > & newButtonStates );
/**
* Reloads the pixmaps for whole document
*
* @since 0.24
*/
void reloadDocument() const;
/**
* Returns the part of document covered by the given signature @p info.
*
* @since 1.7
*/
QByteArray requestSignedRevisionData( const Okular::SignatureInfo &info );
Q_SIGNALS:
/**
* This signal is emitted whenever the document is about to close.
* @since 1.5.3
*/
void aboutToClose();
/**
* This signal is emitted whenever an action requests a
* document close operation.
*/
void close();
/**
* This signal is emitted whenever an action requests an
* application quit operation.
*/
void quit();
/**
* This signal is emitted whenever an action requests a
* find operation.
*/
void linkFind();
/**
* This signal is emitted whenever an action requests a
* goto operation.
*/
void linkGoToPage();
/**
* This signal is emitted whenever an action requests a
* start presentation operation.
*/
void linkPresentation();
/**
* This signal is emitted whenever an action requests an
* end presentation operation.
*/
void linkEndPresentation();
/**
* This signal is emitted whenever an action requests an
* open url operation for the given document @p url.
*/
void openUrl( const QUrl &url );
/**
* This signal is emitted whenever an error occurred.
*
* @param text The description of the error.
* @param duration The time in milliseconds the message should be shown to the user.
*/
void error( const QString &text, int duration );
/**
* This signal is emitted to signal a warning.
*
* @param text The description of the warning.
* @param duration The time in milliseconds the message should be shown to the user.
*/
void warning( const QString &text, int duration );
/**
* This signal is emitted to signal a notice.
*
* @param text The description of the notice.
* @param duration The time in milliseconds the message should be shown to the user.
*/
void notice( const QString &text, int duration );
/**
* Emitted when a new font is found during the reading of the fonts of
* the document.
*/
void gotFont( const Okular::FontInfo& font );
/**
* Reports the progress when reading the fonts in the document.
*
* \param page is the page that was just finished to scan for fonts
*/
void fontReadingProgress( int page );
/**
* Reports that the reading of the fonts in the document is finished.
*/
void fontReadingEnded();
/**
* Reports that the current search finished
*/
void searchFinished( int searchID, Okular::Document::SearchStatus endStatus );
/**
* This signal is emitted whenever a source reference with the given parameters has been
* activated.
*
* \param absFileName absolute name of the file.
* \param line line number.
* \param col column number.
* \param handled should be set to 'true' if a slot handles this source reference; the
* default action to launch the configured editor will then not be performed
* by the document
*
* @since 0.14 (KDE 4.8)
*/
void sourceReferenceActivated(const QString& absFileName, int line, int col, bool *handled);
/**
* This signal is emitted whenever an movie action is triggered and the UI should process it.
*/
void processMovieAction( const Okular::MovieAction *action );
/**
* This signal is emitted whenever the availability of the undo function changes
* @since 0.17 (KDE 4.11)
*/
void canUndoChanged( bool undoAvailable );
/**
* This signal is emitted whenever the availability of the redo function changes
* @since 0.17 (KDE 4.11)
*/
void canRedoChanged( bool redoAvailable );
/**
* This signal is emitted whenever the undo history is clean (i.e. the same status the last time it was saved)
* @since 1.3
*/
void undoHistoryCleanChanged( bool clean );
/**
* This signal is emitted whenever an rendition action is triggered and the UI should process it.
*
* @since 0.16 (KDE 4.10)
*/
void processRenditionAction( const Okular::RenditionAction *action );
/**
* This signal is emitted whenever the contents of the given @p annotation are changed by an undo
* or redo action.
*
* The new contents (@p contents), cursor position (@p cursorPos), and anchor position (@p anchorPos) are
* included
* @since 0.17 (KDE 4.11)
*/
void annotationContentsChangedByUndoRedo( Okular::Annotation* annotation, const QString & contents, int cursorPos, int anchorPos );
/**
* This signal is emitted whenever the text contents of the given text @p form on the given @p page
* are changed by an undo or redo action.
*
* The new text contents (@p contents), cursor position (@p cursorPos), and anchor position (@p anchorPos) are
* included
* @since 0.17 (KDE 4.11)
*/
void formTextChangedByUndoRedo( int page, Okular::FormFieldText* form, const QString & contents, int cursorPos, int anchorPos );
/**
* This signal is emitted whenever the selected @p choices for the given list @p form on the
* given @p page are changed by an undo or redo action.
* @since 0.17 (KDE 4.11)
*/
void formListChangedByUndoRedo( int page, Okular::FormFieldChoice* form, const QList< int > & choices );
/**
* This signal is emitted whenever the active @p text for the given combo @p form on the
* given @p page is changed by an undo or redo action.
* @since 0.17 (KDE 4.11)
*/
void formComboChangedByUndoRedo( int page, Okular::FormFieldChoice* form, const QString & text, int cursorPos, int anchorPos );
/**
* This signal is emitted whenever the state of the specified group of form buttons (@p formButtons) on the
* given @p page is changed by an undo or redo action.
* @since 0.17 (KDE 4.11)
*/
void formButtonsChangedByUndoRedo( int page, const QList< Okular::FormFieldButton* > & formButtons );
/**
* This signal is emitted whenever a FormField was changed programmatically and the
* according widget should be updated.
* @since 1.4
*/
void refreshFormWidget( Okular::FormField *field );
private:
/// @cond PRIVATE
friend class DocumentPrivate;
friend class ::DocumentItem;
friend class EditAnnotationContentsCommand;
friend class EditFormTextCommand;
friend class EditFormListCommand;
friend class EditFormComboCommand;
friend class EditFormButtonsCommand;
/// @endcond
DocumentPrivate *const d;
Q_DISABLE_COPY( Document )
Q_PRIVATE_SLOT( d, void saveDocumentInfo() const )
Q_PRIVATE_SLOT( d, void slotTimedMemoryCheck() )
Q_PRIVATE_SLOT( d, void sendGeneratorPixmapRequest() )
Q_PRIVATE_SLOT( d, void rotationFinished( int page, Okular::Page *okularPage ) )
Q_PRIVATE_SLOT( d, void slotFontReadingProgress( int page ) )
Q_PRIVATE_SLOT( d, void fontReadingGotFont( const Okular::FontInfo& font ) )
Q_PRIVATE_SLOT( d, void slotGeneratorConfigChanged( const QString& ) )
Q_PRIVATE_SLOT( d, void refreshPixmaps( int ) )
Q_PRIVATE_SLOT( d, void _o_configChanged() )
// search thread simulators
Q_PRIVATE_SLOT( d, void doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct) )
Q_PRIVATE_SLOT( d, void doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID) )
Q_PRIVATE_SLOT( d, void doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words) )
};
/**
* @short A view on the document.
*
* The Viewport structure is the 'current view' over the document. Contained
* data is broadcasted between observers to synchronize their viewports to get
* the 'I scroll one view and others scroll too' views.
*/
class OKULARCORE_EXPORT DocumentViewport
{
public:
/**
* Creates a new viewport for the given page @p number.
*/
DocumentViewport( int number = -1 );
/**
* Creates a new viewport from the given xml @p description.
*/
DocumentViewport( const QString &description );
/**
* Returns the viewport as xml description.
*/
QString toString() const;
/**
* Returns whether the viewport is valid.
*/
bool isValid() const;
/**
* @internal
*/
bool operator==( const DocumentViewport &other ) const;
bool operator<( const DocumentViewport &other ) const;
/**
* The number of the page nearest the center of the viewport.
*/
int pageNumber;
/**
* Describes the relative position of the viewport.
*/
enum Position
{
Center = 1, ///< Relative to the center of the page.
TopLeft = 2 ///< Relative to the top left corner of the page.
};
/**
* If 'rePos.enabled == true' then this structure contains the
* viewport center or top left depending on the value of pos.
*/
struct {
bool enabled;
double normalizedX;
double normalizedY;
Position pos;
} rePos;
/**
* If 'autoFit.enabled == true' then the page must be autofit in the viewport.
*/
struct {
bool enabled;
bool width;
bool height;
} autoFit;
};
/**
* @short A DOM tree that describes the Table of Contents.
*
* The Synopsis (TOC or Table Of Contents for friends) is represented via
* a dom tree where each node has an internal name (displayed in the TOC)
* and one or more attributes.
*
* In the tree the tag name is the 'screen' name of the entry. A tag can have
* attributes. Here follows the list of tag attributes with meaning:
* - Destination: A string description of the referred viewport
* - DestinationName: A 'named reference' to the viewport that must be converted
* using metaData( "NamedViewport", viewport_name )
* - ExternalFileName: A document to be opened, whose destination is specified
* with Destination or DestinationName
* - Open: a boolean saying whether its TOC branch is open or not (default: false)
* - URL: a URL to be open as destination; if set, no other Destination* or
* ExternalFileName entry is used
*/
class OKULARCORE_EXPORT DocumentSynopsis : public QDomDocument
{
public:
/**
* Creates a new document synopsis object.
*/
DocumentSynopsis();
/**
* Creates a new document synopsis object with the given
* @p document as parent node.
*/
DocumentSynopsis( const QDomDocument &document );
};
/**
* @short An embedded file into the document.
*
* This class represents a sort of interface of an embedded file in a document.
*
* Generators \b must re-implement its members to give the all the information
* about an embedded file, like its name, its description, the date of creation
* and modification, and the real data of the file.
*/
class OKULARCORE_EXPORT EmbeddedFile
{
public:
/**
* Creates a new embedded file.
*/
EmbeddedFile();
/**
* Destroys the embedded file.
*/
virtual ~EmbeddedFile();
/**
* Returns the name of the file
*/
virtual QString name() const = 0;
/**
* Returns the description of the file, or an empty string if not
* available
*/
virtual QString description() const = 0;
/**
* Returns the real data representing the file contents
*/
virtual QByteArray data() const = 0;
/**
* Returns the size (in bytes) of the file, if available, or -1 otherwise.
*
* @note this method should be a fast way to know the size of the file
* with no need to extract all the data from it
*/
virtual int size() const = 0;
/**
* Returns the modification date of the file, or an invalid date
* if not available
*/
virtual QDateTime modificationDate() const = 0;
/**
* Returns the creation date of the file, or an invalid date
* if not available
*/
virtual QDateTime creationDate() const = 0;
};
/**
* @short An area of a specified page
*/
class OKULARCORE_EXPORT VisiblePageRect
{
public:
/**
* Creates a new visible page rectangle.
*
* @param pageNumber The page number where the rectangle is located.
* @param rectangle The rectangle in normalized coordinates.
*/
explicit VisiblePageRect( int pageNumber = -1, const NormalizedRect &rectangle = NormalizedRect() );
/**
* The page number where the rectangle is located.
*/
int pageNumber;
/**
* The rectangle in normalized coordinates.
*/
NormalizedRect rect;
};
}
Q_DECLARE_METATYPE( Okular::DocumentInfo::Key )
Q_DECLARE_OPERATORS_FOR_FLAGS( Okular::Document::PixmapRequestFlags )
#endif
/* kate: replace-tabs on; indent-width 4; */
diff --git a/core/fileprinter.cpp b/core/fileprinter.cpp
index cc476a2f2..5c9c57200 100644
--- a/core/fileprinter.cpp
+++ b/core/fileprinter.cpp
@@ -1,643 +1,645 @@
/***************************************************************************
* Copyright (C) 2007,2010 by John Layt *
* *
* FilePrinterPreview based on KPrintPreview (originally LGPL) *
* Copyright (c) 2007 Alex Merry *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "fileprinter.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "debug_p.h"
using namespace Okular;
int FilePrinter::printFile( QPrinter &printer, const QString file,
QPrinter::Orientation documentOrientation, FileDeletePolicy fileDeletePolicy,
PageSelectPolicy pageSelectPolicy, const QString &pageRange )
{
FilePrinter fp;
return fp.doPrintFiles( printer, QStringList( file ), fileDeletePolicy, pageSelectPolicy, pageRange,
documentOrientation );
}
int FilePrinter::doPrintFiles( QPrinter &printer, QStringList fileList, FileDeletePolicy fileDeletePolicy,
PageSelectPolicy pageSelectPolicy, const QString &pageRange,
QPrinter::Orientation documentOrientation )
{
if ( fileList.size() < 1 ) {
return -8;
}
for (QStringList::ConstIterator it = fileList.constBegin(); it != fileList.constEnd(); ++it) {
if (!QFile::exists(*it)) {
return -7;
}
}
if ( printer.printerState() == QPrinter::Aborted || printer.printerState() == QPrinter::Error ) {
return -6;
}
QString exe;
QStringList argList;
int ret;
// Print to File if a filename set, assumes there must be only 1 file
if ( !printer.outputFileName().isEmpty() ) {
if ( QFile::exists( printer.outputFileName() ) ) {
QFile::remove( printer.outputFileName() );
}
QFileInfo inputFileInfo = QFileInfo( fileList[0] );
QFileInfo outputFileInfo = QFileInfo( printer.outputFileName() );
bool doDeleteFile = (fileDeletePolicy == FilePrinter::SystemDeletesFiles);
if ( inputFileInfo.suffix() == outputFileInfo.suffix() ) {
if ( doDeleteFile ) {
bool res = QFile::rename( fileList[0], printer.outputFileName() );
if ( res ) {
doDeleteFile = false;
ret = 0;
} else {
ret = -5;
}
} else {
bool res = QFile::copy( fileList[0], printer.outputFileName() );
if ( res ) {
ret = 0;
} else {
ret = -5;
}
}
} else if ( inputFileInfo.suffix() == QLatin1String("ps") && printer.outputFormat() == QPrinter::PdfFormat && ps2pdfAvailable() ) {
exe = QStringLiteral("ps2pdf");
argList << fileList[0] << printer.outputFileName();
qCDebug(OkularCoreDebug) << "Executing" << exe << "with arguments" << argList;
ret = KProcess::execute( exe, argList );
} else if ( inputFileInfo.suffix() == "pdf" && printer.outputFormat() == QPrinter::NativeFormat && pdf2psAvailable() ) {
exe = "pdf2ps";
argList << fileList[0] << printer.outputFileName();
qCDebug(OkularCoreDebug) << "Executing" << exe << "with arguments" << argList;
ret = KProcess::execute( exe, argList );
} else {
ret = -5;
}
if ( doDeleteFile ) {
QFile::remove( fileList[0] );
}
} else { // Print to a printer via lpr command
//Decide what executable to use to print with, need the CUPS version of lpr if available
//Some distros name the CUPS version of lpr as lpr-cups or lpr.cups so try those first
//before default to lpr, or failing that to lp
if ( !QStandardPaths::findExecutable(QStringLiteral("lpr-cups")).isEmpty() ) {
exe = QStringLiteral("lpr-cups");
} else if ( !QStandardPaths::findExecutable(QStringLiteral("lpr.cups")).isEmpty() ) {
exe = QStringLiteral("lpr.cups");
} else if ( !QStandardPaths::findExecutable(QStringLiteral("lpr")).isEmpty() ) {
exe = QStringLiteral("lpr");
} else if ( !QStandardPaths::findExecutable(QStringLiteral("lp")).isEmpty() ) {
exe = QStringLiteral("lp");
} else {
return -9;
}
bool useCupsOptions = cupsAvailable();
argList = printArguments( printer, fileDeletePolicy, pageSelectPolicy,
useCupsOptions, pageRange, exe, documentOrientation ) << fileList;
qCDebug(OkularCoreDebug) << "Executing" << exe << "with arguments" << argList;
ret = KProcess::execute( exe, argList );
}
return ret;
}
QList FilePrinter::pageList( QPrinter &printer, int lastPage, const QList &selectedPageList )
{
return pageList( printer, lastPage, 0, selectedPageList );
}
QList FilePrinter::pageList( QPrinter &printer, int lastPage,
int currentPage, const QList &selectedPageList )
{
if ( printer.printRange() == QPrinter::Selection) {
return selectedPageList;
}
int startPage, endPage;
QList list;
if ( printer.printRange() == QPrinter::PageRange ) {
startPage = printer.fromPage();
endPage = printer.toPage();
} else if ( printer.printRange() == QPrinter::CurrentPage) {
startPage = currentPage;
endPage = currentPage;
} else { //AllPages
startPage = 1;
endPage = lastPage;
}
for (int i = startPage; i <= endPage; i++ ) {
list << i;
}
return list;
}
QString FilePrinter::pageRange( QPrinter &printer, int lastPage, const QList &selectedPageList )
{
if ( printer.printRange() == QPrinter::Selection) {
return pageListToPageRange( selectedPageList );
}
if ( printer.printRange() == QPrinter::PageRange ) {
return QStringLiteral("%1-%2").arg(printer.fromPage()).arg(printer.toPage());
}
return QStringLiteral("1-%2").arg( lastPage );
}
QString FilePrinter::pageListToPageRange( const QList &pageList )
{
QString pageRange;
int count = pageList.count();
int i = 0;
int seqStart = i;
int seqEnd;
while ( i != count ) {
if ( i + 1 == count || pageList[i] + 1 != pageList[i+1] ) {
seqEnd = i;
if ( !pageRange.isEmpty() ) {
pageRange.append(QLatin1Char(','));
}
if ( seqStart == seqEnd ) {
pageRange.append(pageList[i]);
} else {
pageRange.append(QStringLiteral("%1-%2").arg(seqStart).arg(seqEnd));
}
seqStart = i + 1;
}
i++;
}
return pageRange;
}
bool FilePrinter::ps2pdfAvailable()
{
return ( !QStandardPaths::findExecutable(QStringLiteral("ps2pdf")).isEmpty() );
}
bool FilePrinter::pdf2psAvailable()
{
return ( !QStandardPaths::findExecutable(QStringLiteral("pdf2ps")).isEmpty() );
}
bool FilePrinter::cupsAvailable()
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
// Ideally we would have access to the private Qt method
// QCUPSSupport::cupsAvailable() to do this as it is very complex routine.
// However, if CUPS is available then QPrinter::numCopies() will always return 1
// whereas if CUPS is not available it will return the real number of copies.
// This behaviour is guaranteed never to change, so we can use it as a reliable substitute.
QPrinter testPrinter;
testPrinter.setNumCopies( 2 );
return ( testPrinter.numCopies() == 1 );
#else
return false;
#endif
}
bool FilePrinter::detectCupsService()
{
QTcpSocket qsock;
qsock.connectToHost(QStringLiteral("localhost"), 631);
bool rtn = qsock.waitForConnected() && qsock.isValid();
qsock.abort();
return rtn;
}
bool FilePrinter::detectCupsConfig()
{
if ( QFile::exists(QStringLiteral("/etc/cups/cupsd.conf")) ) return true;
if ( QFile::exists(QStringLiteral("/usr/etc/cups/cupsd.conf")) ) return true;
if ( QFile::exists(QStringLiteral("/usr/local/etc/cups/cupsd.conf")) ) return true;
if ( QFile::exists(QStringLiteral("/opt/etc/cups/cupsd.conf")) ) return true;
if ( QFile::exists(QStringLiteral("/opt/local/etc/cups/cupsd.conf")) ) return true;
return false;
}
QSize FilePrinter::psPaperSize( QPrinter &printer )
{
QSize size = printer.pageLayout().pageSize().sizePoints();
if ( printer.pageSize() == QPrinter::Custom )
{
return QSize( (int) printer.widthMM() * ( 25.4 / 72 ),
(int) printer.heightMM() * ( 25.4 / 72 ) );
}
if ( printer.orientation() == QPrinter::Landscape ) {
size.transpose();
}
return size;
}
Generator::PrintError FilePrinter::printError( int c )
{
Generator::PrintError pe;
if ( c >= 0 )
{
pe = Generator::NoPrintError;
}
else {
switch ( c )
{
case -1:
pe = Generator::PrintingProcessCrashPrintError;
break;
case -2:
pe = Generator::PrintingProcessStartPrintError;
break;
case -5:
pe = Generator::PrintToFilePrintError;
break;
case -6:
pe = Generator::InvalidPrinterStatePrintError;
break;
case -7:
pe = Generator::UnableToFindFilePrintError;
break;
case -8:
pe = Generator::NoFileToPrintError;
break;
case -9:
pe = Generator::NoBinaryToPrintError;
break;
default:
pe = Generator::UnknownPrintError;
}
}
return pe;
}
QStringList FilePrinter::printArguments( QPrinter &printer, FileDeletePolicy fileDeletePolicy,
PageSelectPolicy pageSelectPolicy, bool useCupsOptions,
const QString &pageRange, const QString &version,
QPrinter::Orientation documentOrientation )
{
QStringList argList;
if ( ! destination( printer, version ).isEmpty() ) {
argList << destination( printer, version );
}
if ( ! copies( printer, version ).isEmpty() ) {
argList << copies( printer, version );
}
if ( ! jobname( printer, version ).isEmpty() ) {
argList << jobname( printer, version );
}
if ( ! pages( printer, pageSelectPolicy, pageRange, useCupsOptions, version ).isEmpty() ) {
argList << pages( printer, pageSelectPolicy, pageRange, useCupsOptions, version );
}
if ( useCupsOptions && ! cupsOptions( printer, documentOrientation ).isEmpty() ) {
argList << cupsOptions( printer, documentOrientation);
}
if ( ! deleteFile( printer, fileDeletePolicy, version ).isEmpty() ) {
argList << deleteFile( printer, fileDeletePolicy, version );
}
if ( version == QLatin1String("lp") ) {
argList << QStringLiteral("--");
}
return argList;
}
QStringList FilePrinter::destination( QPrinter &printer, const QString &version )
{
if ( version == QLatin1String("lp") ) {
return QStringList(QStringLiteral("-d")) << printer.printerName();
}
if ( version.startsWith( QLatin1String("lpr") ) ) {
return QStringList(QStringLiteral("-P")) << printer.printerName();
}
return QStringList();
}
QStringList FilePrinter::copies( QPrinter &printer, const QString &version )
{
int cp = printer.actualNumCopies();
if ( version == QLatin1String("lp") ) {
return QStringList(QStringLiteral("-n")) << QStringLiteral("%1").arg( cp );
}
if ( version.startsWith( QLatin1String("lpr") ) ) {
return QStringList() << QStringLiteral("-#%1").arg( cp );
}
return QStringList();
}
QStringList FilePrinter::jobname( QPrinter &printer, const QString &version )
{
if ( ! printer.docName().isEmpty() ) {
if ( version == QLatin1String("lp") ) {
return QStringList(QStringLiteral("-t")) << printer.docName();
}
if ( version.startsWith( QLatin1String("lpr") ) ) {
const QString shortenedDocName = QString::fromUtf8(printer.docName().toUtf8().left(255));
return QStringList(QStringLiteral("-J")) << shortenedDocName;
}
}
return QStringList();
}
QStringList FilePrinter::deleteFile( QPrinter &, FileDeletePolicy fileDeletePolicy, const QString &version )
{
if ( fileDeletePolicy == FilePrinter::SystemDeletesFiles && version.startsWith( QLatin1String("lpr") ) ) {
return QStringList(QStringLiteral("-r"));
}
return QStringList();
}
QStringList FilePrinter::pages( QPrinter &printer, PageSelectPolicy pageSelectPolicy, const QString &pageRange,
bool useCupsOptions, const QString &version )
{
if ( pageSelectPolicy == FilePrinter::SystemSelectsPages ) {
if ( printer.printRange() == QPrinter::Selection && ! pageRange.isEmpty() ) {
if ( version == QLatin1String("lp") ) {
return QStringList(QStringLiteral("-P")) << pageRange ;
}
if ( version.startsWith( QLatin1String("lpr") ) && useCupsOptions ) {
return QStringList(QStringLiteral("-o")) << QStringLiteral("page-ranges=%1").arg( pageRange );
}
}
if ( printer.printRange() == QPrinter::PageRange ) {
if ( version == QLatin1String("lp") ) {
return QStringList(QStringLiteral("-P")) << QStringLiteral("%1-%2").arg( printer.fromPage() )
.arg( printer.toPage() );
}
if ( version.startsWith( QLatin1String("lpr") ) && useCupsOptions ) {
return QStringList(QStringLiteral("-o")) << QStringLiteral("page-ranges=%1-%2").arg( printer.fromPage() )
.arg( printer.toPage() );
}
}
}
return QStringList(); // AllPages
}
QStringList FilePrinter::cupsOptions( QPrinter &printer, QPrinter::Orientation documentOrientation )
{
QStringList optionList;
if ( ! optionMedia( printer ).isEmpty() ) {
optionList << optionMedia( printer );
}
if ( ! optionOrientation( printer, documentOrientation ).isEmpty() ) {
optionList << optionOrientation( printer, documentOrientation );
}
if ( ! optionDoubleSidedPrinting( printer ).isEmpty() ) {
optionList << optionDoubleSidedPrinting( printer );
}
if ( ! optionPageOrder( printer ).isEmpty() ) {
optionList << optionPageOrder( printer );
}
if ( ! optionCollateCopies( printer ).isEmpty() ) {
optionList << optionCollateCopies( printer );
}
if ( ! optionPageMargins( printer ).isEmpty() ) {
optionList << optionPageMargins( printer );
}
optionList << optionCupsProperties( printer );
return optionList;
}
QStringList FilePrinter::optionMedia( QPrinter &printer )
{
if ( ! mediaPageSize( printer ).isEmpty() &&
! mediaPaperSource( printer ).isEmpty() ) {
return QStringList(QStringLiteral("-o")) <<
QStringLiteral("media=%1,%2").arg( mediaPageSize( printer ), mediaPaperSource( printer ) );
}
if ( ! mediaPageSize( printer ).isEmpty() ) {
return QStringList(QStringLiteral("-o")) <<
QStringLiteral("media=%1").arg( mediaPageSize( printer ) );
}
if ( ! mediaPaperSource( printer ).isEmpty() ) {
return QStringList(QStringLiteral("-o")) <<
QStringLiteral("media=%1").arg( mediaPaperSource( printer ) );
}
return QStringList();
}
QString FilePrinter::mediaPageSize( QPrinter &printer )
{
switch ( printer.pageSize() ) {
case QPrinter::A0: return QStringLiteral("A0");
case QPrinter::A1: return QStringLiteral("A1");
case QPrinter::A2: return QStringLiteral("A2");
case QPrinter::A3: return QStringLiteral("A3");
case QPrinter::A4: return QStringLiteral("A4");
case QPrinter::A5: return QStringLiteral("A5");
case QPrinter::A6: return QStringLiteral("A6");
case QPrinter::A7: return QStringLiteral("A7");
case QPrinter::A8: return QStringLiteral("A8");
case QPrinter::A9: return QStringLiteral("A9");
case QPrinter::B0: return QStringLiteral("B0");
case QPrinter::B1: return QStringLiteral("B1");
case QPrinter::B10: return QStringLiteral("B10");
case QPrinter::B2: return QStringLiteral("B2");
case QPrinter::B3: return QStringLiteral("B3");
case QPrinter::B4: return QStringLiteral("B4");
case QPrinter::B5: return QStringLiteral("B5");
case QPrinter::B6: return QStringLiteral("B6");
case QPrinter::B7: return QStringLiteral("B7");
case QPrinter::B8: return QStringLiteral("B8");
case QPrinter::B9: return QStringLiteral("B9");
case QPrinter::C5E: return QStringLiteral("C5"); //Correct Translation?
case QPrinter::Comm10E: return QStringLiteral("Comm10"); //Correct Translation?
case QPrinter::DLE: return QStringLiteral("DL"); //Correct Translation?
case QPrinter::Executive: return QStringLiteral("Executive");
case QPrinter::Folio: return QStringLiteral("Folio");
case QPrinter::Ledger: return QStringLiteral("Ledger");
case QPrinter::Legal: return QStringLiteral("Legal");
case QPrinter::Letter: return QStringLiteral("Letter");
case QPrinter::Tabloid: return QStringLiteral("Tabloid");
case QPrinter::Custom: return QStringLiteral("Custom.%1x%2mm")
.arg( printer.widthMM() )
.arg( printer.heightMM() );
default: return QString();
}
}
// What about Upper and MultiPurpose? And others in PPD???
QString FilePrinter::mediaPaperSource( QPrinter &printer )
{
switch ( printer.paperSource() ) {
case QPrinter::Auto: return QString();
case QPrinter::Cassette: return QStringLiteral("Cassette");
case QPrinter::Envelope: return QStringLiteral("Envelope");
case QPrinter::EnvelopeManual: return QStringLiteral("EnvelopeManual");
case QPrinter::FormSource: return QStringLiteral("FormSource");
case QPrinter::LargeCapacity: return QStringLiteral("LargeCapacity");
case QPrinter::LargeFormat: return QStringLiteral("LargeFormat");
case QPrinter::Lower: return QStringLiteral("Lower");
case QPrinter::MaxPageSource: return QStringLiteral("MaxPageSource");
case QPrinter::Middle: return QStringLiteral("Middle");
case QPrinter::Manual: return QStringLiteral("Manual");
case QPrinter::OnlyOne: return QStringLiteral("OnlyOne");
case QPrinter::Tractor: return QStringLiteral("Tractor");
case QPrinter::SmallFormat: return QStringLiteral("SmallFormat");
default: return QString();
}
}
QStringList FilePrinter::optionOrientation( QPrinter &printer, QPrinter::Orientation documentOrientation )
{
// portrait and landscape options rotate the document according to the document orientation
// If we want to print a landscape document as one would expect it, we have to pass the
// portrait option so that the document is not rotated additionally
if ( printer.orientation() == documentOrientation ) {
// the user wants the document printed as is
return QStringList(QStringLiteral("-o")) << QStringLiteral("portrait");
} else {
// the user expects the document being rotated by 90 degrees
return QStringList(QStringLiteral("-o")) << QStringLiteral("landscape");
}
}
QStringList FilePrinter::optionDoubleSidedPrinting( QPrinter &printer )
{
switch ( printer.duplex() ) {
case QPrinter::DuplexNone: return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=one-sided");
case QPrinter::DuplexAuto: if ( printer.orientation() == QPrinter::Landscape ) {
return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-short-edge");
} else {
return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-long-edge");
}
case QPrinter::DuplexLongSide: return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-long-edge");
case QPrinter::DuplexShortSide: return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-short-edge");
default: return QStringList(); //Use printer default
}
}
QStringList FilePrinter::optionPageOrder( QPrinter &printer )
{
if ( printer.pageOrder() == QPrinter::LastPageFirst ) {
return QStringList(QStringLiteral("-o")) << QStringLiteral("outputorder=reverse");
}
return QStringList(QStringLiteral("-o")) << QStringLiteral("outputorder=normal");
}
QStringList FilePrinter::optionCollateCopies( QPrinter &printer )
{
if ( printer.collateCopies() ) {
return QStringList(QStringLiteral("-o")) << QStringLiteral("Collate=True");
}
return QStringList(QStringLiteral("-o")) << QStringLiteral("Collate=False");
}
QStringList FilePrinter::optionPageMargins( QPrinter &printer )
{
if (printer.printEngine()->property(QPrintEngine::PPK_PageMargins).isNull()) {
return QStringList();
} else {
- qreal l, t, r, b;
- printer.getPageMargins( &l, &t, &r, &b, QPrinter::Point );
+ qreal l(0), t(0), r(0), b(0);
+ if (!printer.fullPage()) {
+ printer.getPageMargins( &l, &t, &r, &b, QPrinter::Point );
+ }
return QStringList(QStringLiteral("-o")) << QStringLiteral("page-left=%1").arg(l)
<< QStringLiteral("-o") << QStringLiteral("page-top=%1").arg(t)
<< QStringLiteral("-o") << QStringLiteral("page-right=%1").arg(r)
<< QStringLiteral("-o") << QStringLiteral("page-bottom=%1").arg(b) << QStringLiteral("-o") << QStringLiteral("fit-to-page");
}
}
QStringList FilePrinter::optionCupsProperties( QPrinter &printer )
{
QStringList dialogOptions = printer.printEngine()->property(QPrintEngine::PrintEnginePropertyKey(0xfe00)).toStringList();
QStringList cupsOptions;
for ( int i = 0; i < dialogOptions.count(); i = i + 2 ) {
if ( dialogOptions[i+1].isEmpty() ) {
cupsOptions << QStringLiteral("-o") << dialogOptions[i];
} else {
cupsOptions << QStringLiteral("-o") << dialogOptions[i] + QLatin1Char('=') + dialogOptions[i+1];
}
}
return cupsOptions;
}
/* kate: replace-tabs on; indent-width 4; */
diff --git a/core/printoptionswidget.cpp b/core/printoptionswidget.cpp
new file mode 100644
index 000000000..989674b7d
--- /dev/null
+++ b/core/printoptionswidget.cpp
@@ -0,0 +1,36 @@
+/***************************************************************************
+ * Copyright (C) 2019 Michael Weghorn *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ ***************************************************************************/
+
+#include "printoptionswidget.h"
+
+#include
+#include
+
+#include
+
+namespace Okular {
+
+DefaultPrintOptionsWidget::DefaultPrintOptionsWidget(QWidget *parent)
+ : PrintOptionsWidget(parent)
+{
+ setWindowTitle( i18n( "Print Options" ) );
+ QFormLayout *layout = new QFormLayout(this);
+ m_ignorePrintMargins = new QComboBox;
+ // value indicates whether full page is enabled (i.e. print margins ignored)
+ m_ignorePrintMargins->insertItem(0, i18n("Fit to printable area"), false);
+ m_ignorePrintMargins->insertItem(1, i18n("Fit to full page"), true);
+ layout->addRow(i18n("Scale mode:"), m_ignorePrintMargins);
+}
+
+bool DefaultPrintOptionsWidget::ignorePrintMargins() const
+{
+ return m_ignorePrintMargins->currentData().value();
+}
+
+}
diff --git a/core/printoptionswidget.h b/core/printoptionswidget.h
new file mode 100644
index 000000000..d1a0deeb5
--- /dev/null
+++ b/core/printoptionswidget.h
@@ -0,0 +1,54 @@
+/***************************************************************************
+ * Copyright (C) 2019 Michael Weghorn *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ ***************************************************************************/
+
+#ifndef PRINTOPTIONSWIDGET_H
+#define PRINTOPTIONSWIDGET_H
+
+#include
+
+#include "okularcore_export.h"
+
+class QComboBox;
+class QFormLayout;
+
+namespace Okular {
+
+/**
+ * @short Abstract base class for an extra print options widget in the print dialog.
+ */
+class OKULARCORE_EXPORT PrintOptionsWidget : public QWidget
+{
+ public:
+ explicit PrintOptionsWidget(QWidget * parent = nullptr)
+ : QWidget(parent) {}
+ virtual bool ignorePrintMargins() const = 0;
+};
+
+/**
+ * @short The default okular extra print options widget.
+ *
+ * It just implements the required method 'ignorePrintMargins()' from
+ * the base class 'PrintOptionsWidget'.
+ */
+class OKULARCORE_EXPORT DefaultPrintOptionsWidget : public PrintOptionsWidget
+{
+ Q_OBJECT
+
+ public:
+ explicit DefaultPrintOptionsWidget(QWidget *parent = nullptr);
+
+ bool ignorePrintMargins() const override;
+
+ private:
+ QComboBox *m_ignorePrintMargins;
+};
+
+}
+
+#endif
diff --git a/generators/poppler/generator_pdf.cpp b/generators/poppler/generator_pdf.cpp
index 6bce88c8c..0cfd86d4d 100644
--- a/generators/poppler/generator_pdf.cpp
+++ b/generators/poppler/generator_pdf.cpp
@@ -1,2075 +1,2079 @@
/***************************************************************************
* Copyright (C) 2004-2008 by Albert Astals Cid *
* Copyright (C) 2004 by Enrico Ros *
* Copyright (C) 2012 by Guillermo A. Amaral B. *
* Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group *
* company, info@kdab.com. Work sponsored by the *
* LiMux project of the city of Munich *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include
#include "generator_pdf.h"
// qt/kde includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
#include
#include
#include "ui_pdfsettingswidget.h"
#include "pdfsettings.h"
#include
#include
#ifdef HAVE_POPPLER_0_73
#include
#endif
#include "debug_pdf.h"
#include "annots.h"
#include "formfields.h"
#include "popplerembeddedfile.h"
Q_DECLARE_METATYPE(Poppler::Annotation*)
Q_DECLARE_METATYPE(Poppler::FontInfo)
Q_DECLARE_METATYPE(const Poppler::LinkMovie*)
Q_DECLARE_METATYPE(const Poppler::LinkRendition*)
#ifdef HAVE_POPPLER_0_50
Q_DECLARE_METATYPE(const Poppler::LinkOCGState*)
#endif
static const int defaultPageWidth = 595;
static const int defaultPageHeight = 842;
-class PDFOptionsPage : public QWidget
+class PDFOptionsPage : public Okular::PrintOptionsWidget
{
Q_OBJECT
public slots:
void enableOrDisableScaleMode()
{
m_scaleMode->setEnabled ( m_forceRaster->isChecked() );
if ( m_forceRaster->isChecked() ) {
m_scaleMode->setToolTip( i18n( "Scaling mode for the printed pages" ) );
} else {
m_scaleMode->setToolTip( i18n( "Select rasterization to enable this!" ) );
}
}
public:
enum ScaleMode {
FitToPrintableArea,
FitToPage,
None
};
Q_ENUM(ScaleMode)
PDFOptionsPage()
{
setWindowTitle( i18n( "PDF Options" ) );
QVBoxLayout *layout = new QVBoxLayout(this);
m_printAnnots = new QCheckBox(i18n("Print annotations"), this);
m_printAnnots->setToolTip(i18n("Include annotations in the printed document"));
m_printAnnots->setWhatsThis(i18n("Includes annotations in the printed document. You can disable this if you want to print the original unannotated document."));
layout->addWidget(m_printAnnots);
m_forceRaster = new QCheckBox(i18n("Force rasterization"), this);
m_forceRaster->setToolTip(i18n("Rasterize into an image before printing"));
m_forceRaster->setWhatsThis(i18n("Forces the rasterization of each page into an image before printing it. This usually gives somewhat worse results, but is useful when printing documents that appear to print incorrectly."));
layout->addWidget(m_forceRaster);
QWidget* formWidget = new QWidget(this);
QFormLayout* printBackendLayout = new QFormLayout(formWidget);
m_scaleMode = new QComboBox;
m_scaleMode->insertItem(FitToPrintableArea, i18n("Fit to printable area"), FitToPrintableArea);
m_scaleMode->insertItem(FitToPage, i18n("Fit to full page"), FitToPage);
m_scaleMode->insertItem(None, i18n("None; print original size"), None);
m_scaleMode->setToolTip(i18n("Select rasterization to enable this!"));
printBackendLayout->addRow(i18n("Scale mode:"), m_scaleMode);
layout->addWidget(formWidget);
layout->addStretch(1);
// Enable scaleMode only if the file is to be rasterized before printing
m_scaleMode->setEnabled( false );
connect( m_forceRaster, &QCheckBox::stateChanged, this, &PDFOptionsPage::enableOrDisableScaleMode );
#if defined(Q_OS_WIN) && !defined HAVE_POPPLER_0_60
m_printAnnots->setVisible( false );
#endif
setPrintAnnots( true ); // Default value
}
+ bool ignorePrintMargins() const override {
+ return scaleMode() == FitToPage;
+ }
+
bool printAnnots()
{
return m_printAnnots->isChecked();
}
void setPrintAnnots( bool printAnnots )
{
m_printAnnots->setChecked( printAnnots );
}
bool printForceRaster()
{
return m_forceRaster->isChecked();
}
void setPrintForceRaster( bool forceRaster )
{
m_forceRaster->setChecked( forceRaster );
}
ScaleMode scaleMode() const
{
return m_scaleMode->currentData().value();
}
private:
QCheckBox *m_printAnnots;
QCheckBox *m_forceRaster;
QComboBox *m_scaleMode;
};
static void fillViewportFromLinkDestination( Okular::DocumentViewport &viewport, const Poppler::LinkDestination &destination )
{
viewport.pageNumber = destination.pageNumber() - 1;
if (!viewport.isValid()) return;
// get destination position
// TODO add other attributes to the viewport (taken from link)
// switch ( destination->getKind() )
// {
// case destXYZ:
if (destination.isChangeLeft() || destination.isChangeTop())
{
// TODO remember to change this if we implement DPI and/or rotation
double left, top;
left = destination.left();
top = destination.top();
viewport.rePos.normalizedX = left;
viewport.rePos.normalizedY = top;
viewport.rePos.enabled = true;
viewport.rePos.pos = Okular::DocumentViewport::TopLeft;
}
/* TODO
if ( dest->getChangeZoom() )
make zoom change*/
/* break;
default:
// implement the others cases
break;*/
// }
}
Okular::Sound* createSoundFromPopplerSound( const Poppler::SoundObject *popplerSound )
{
Okular::Sound *sound = popplerSound->soundType() == Poppler::SoundObject::Embedded ? new Okular::Sound( popplerSound->data() ) : new Okular::Sound( popplerSound->url() );
sound->setSamplingRate( popplerSound->samplingRate() );
sound->setChannels( popplerSound->channels() );
sound->setBitsPerSample( popplerSound->bitsPerSample() );
switch ( popplerSound->soundEncoding() )
{
case Poppler::SoundObject::Raw:
sound->setSoundEncoding( Okular::Sound::Raw );
break;
case Poppler::SoundObject::Signed:
sound->setSoundEncoding( Okular::Sound::Signed );
break;
case Poppler::SoundObject::muLaw:
sound->setSoundEncoding( Okular::Sound::muLaw );
break;
case Poppler::SoundObject::ALaw:
sound->setSoundEncoding( Okular::Sound::ALaw );
break;
}
return sound;
}
Okular::Movie* createMovieFromPopplerMovie( const Poppler::MovieObject *popplerMovie )
{
Okular::Movie *movie = new Okular::Movie( popplerMovie->url() );
movie->setSize( popplerMovie->size() );
movie->setRotation( (Okular::Rotation)( popplerMovie->rotation() / 90 ) );
movie->setShowControls( popplerMovie->showControls() );
movie->setPlayMode( (Okular::Movie::PlayMode)popplerMovie->playMode() );
movie->setAutoPlay( false ); // will be triggered by external MovieAnnotation
movie->setShowPosterImage( popplerMovie->showPosterImage() );
movie->setPosterImage( popplerMovie->posterImage() );
return movie;
}
Okular::Movie* createMovieFromPopplerScreen( const Poppler::LinkRendition *popplerScreen )
{
Poppler::MediaRendition *rendition = popplerScreen->rendition();
Okular::Movie *movie = 0;
if ( rendition->isEmbedded() )
movie = new Okular::Movie( rendition->fileName(), rendition->data() );
else
movie = new Okular::Movie( rendition->fileName() );
movie->setSize( rendition->size() );
movie->setShowControls( rendition->showControls() );
if ( rendition->repeatCount() == 0 ) {
movie->setPlayMode( Okular::Movie::PlayRepeat );
} else {
movie->setPlayMode( Okular::Movie::PlayLimited );
movie->setPlayRepetitions( rendition->repeatCount() );
}
movie->setAutoPlay( rendition->autoPlay() );
return movie;
}
#ifdef HAVE_POPPLER_0_36
QPair createMovieFromPopplerRichMedia( const Poppler::RichMediaAnnotation *popplerRichMedia )
{
const QPair emptyResult(0, 0);
/**
* To convert a Flash/Video based RichMedia annotation to a movie, we search for the first
* Flash/Video richmedia instance and parse the flashVars parameter for the 'source' identifier.
* That identifier is then used to find the associated embedded file through the assets
* mapping.
*/
const Poppler::RichMediaAnnotation::Content *content = popplerRichMedia->content();
if ( !content )
return emptyResult;
const QList configurations = content->configurations();
if ( configurations.isEmpty() )
return emptyResult;
const Poppler::RichMediaAnnotation::Configuration *configuration = configurations[0];
const QList instances = configuration->instances();
if ( instances.isEmpty() )
return emptyResult;
const Poppler::RichMediaAnnotation::Instance *instance = instances[0];
if ( ( instance->type() != Poppler::RichMediaAnnotation::Instance::TypeFlash ) &&
( instance->type() != Poppler::RichMediaAnnotation::Instance::TypeVideo ) )
return emptyResult;
const Poppler::RichMediaAnnotation::Params *params = instance->params();
if ( !params )
return emptyResult;
QString sourceId;
bool playbackLoops = false;
const QStringList flashVars = params->flashVars().split( QLatin1Char( '&' ) );
foreach ( const QString & flashVar, flashVars ) {
const int pos = flashVar.indexOf( QLatin1Char( '=' ) );
if ( pos == -1 )
continue;
const QString key = flashVar.left( pos );
const QString value = flashVar.mid( pos + 1 );
if ( key == QLatin1String( "source" ) )
sourceId = value;
else if ( key == QLatin1String( "loop" ) )
playbackLoops = ( value == QLatin1String( "true" ) ? true : false );
}
if ( sourceId.isEmpty() )
return emptyResult;
const QList assets = content->assets();
if ( assets.isEmpty() )
return emptyResult;
Poppler::RichMediaAnnotation::Asset *matchingAsset = 0;
foreach ( Poppler::RichMediaAnnotation::Asset *asset, assets ) {
if ( asset->name() == sourceId ) {
matchingAsset = asset;
break;
}
}
if ( !matchingAsset )
return emptyResult;
Poppler::EmbeddedFile *embeddedFile = matchingAsset->embeddedFile();
if ( !embeddedFile )
return emptyResult;
Okular::EmbeddedFile *pdfEmbeddedFile = new PDFEmbeddedFile( embeddedFile );
Okular::Movie *movie = new Okular::Movie( embeddedFile->name(), embeddedFile->data() );
movie->setPlayMode( playbackLoops ? Okular::Movie::PlayRepeat : Okular::Movie::PlayLimited );
if ( popplerRichMedia && popplerRichMedia->settings() && popplerRichMedia->settings()->activation() ) {
if ( popplerRichMedia->settings()->activation()->condition() == Poppler::RichMediaAnnotation::Activation::PageOpened ||
popplerRichMedia->settings()->activation()->condition() == Poppler::RichMediaAnnotation::Activation::PageVisible ) {
movie->setAutoPlay( true );
} else {
movie->setAutoPlay( false );
}
} else {
movie->setAutoPlay( false );
}
return qMakePair(movie, pdfEmbeddedFile);
}
#endif
/**
* Note: the function will take ownership of the popplerLink object.
*/
Okular::Action* createLinkFromPopplerLink(const Poppler::Link *popplerLink, bool deletePopplerLink = true)
{
if (!popplerLink)
return nullptr;
Okular::Action *link = 0;
const Poppler::LinkGoto *popplerLinkGoto;
const Poppler::LinkExecute *popplerLinkExecute;
const Poppler::LinkBrowse *popplerLinkBrowse;
const Poppler::LinkAction *popplerLinkAction;
const Poppler::LinkSound *popplerLinkSound;
const Poppler::LinkJavaScript *popplerLinkJS;
const Poppler::LinkMovie *popplerLinkMovie;
const Poppler::LinkRendition *popplerLinkRendition;
Okular::DocumentViewport viewport;
switch(popplerLink->linkType())
{
case Poppler::Link::None:
break;
case Poppler::Link::Goto:
{
popplerLinkGoto = static_cast(popplerLink);
const Poppler::LinkDestination dest = popplerLinkGoto->destination();
const QString destName = dest.destinationName();
if (destName.isEmpty())
{
fillViewportFromLinkDestination( viewport, dest );
link = new Okular::GotoAction(popplerLinkGoto->fileName(), viewport);
}
else
{
link = new Okular::GotoAction(popplerLinkGoto->fileName(), destName);
}
}
break;
case Poppler::Link::Execute:
popplerLinkExecute = static_cast(popplerLink);
link = new Okular::ExecuteAction( popplerLinkExecute->fileName(), popplerLinkExecute->parameters() );
break;
case Poppler::Link::Browse:
popplerLinkBrowse = static_cast(popplerLink);
link = new Okular::BrowseAction( QUrl(popplerLinkBrowse->url()) );
break;
case Poppler::Link::Action:
popplerLinkAction = static_cast(popplerLink);
link = new Okular::DocumentAction( (Okular::DocumentAction::DocumentActionType)popplerLinkAction->actionType() );
break;
case Poppler::Link::Sound:
{
popplerLinkSound = static_cast(popplerLink);
Poppler::SoundObject *popplerSound = popplerLinkSound->sound();
Okular::Sound *sound = createSoundFromPopplerSound( popplerSound );
link = new Okular::SoundAction( popplerLinkSound->volume(), popplerLinkSound->synchronous(), popplerLinkSound->repeat(), popplerLinkSound->mix(), sound );
}
break;
case Poppler::Link::JavaScript:
{
popplerLinkJS = static_cast(popplerLink);
link = new Okular::ScriptAction( Okular::JavaScript, popplerLinkJS->script() );
}
break;
case Poppler::Link::Rendition:
{
if (!deletePopplerLink)
{
// If links should not be deleted it probably means that they
// are part of a nextActions chain. There is no support
// to resolveMediaLinkReferences on nextActions. It would also
// be necessary to ensure that resolveMediaLinkReferences does
// not delete the Links which are part of a nextActions list
// to avoid a double deletion.
qCDebug(OkularPdfDebug) << "parsing rendition link without deletion is not supported. Action chain might be broken.";
break;
}
deletePopplerLink = false; // we'll delete it inside resolveMediaLinkReferences() after we have resolved all references
popplerLinkRendition = static_cast( popplerLink );
Okular::RenditionAction::OperationType operation = Okular::RenditionAction::None;
switch ( popplerLinkRendition->action() )
{
case Poppler::LinkRendition::NoRendition:
operation = Okular::RenditionAction::None;
break;
case Poppler::LinkRendition::PlayRendition:
operation = Okular::RenditionAction::Play;
break;
case Poppler::LinkRendition::StopRendition:
operation = Okular::RenditionAction::Stop;
break;
case Poppler::LinkRendition::PauseRendition:
operation = Okular::RenditionAction::Pause;
break;
case Poppler::LinkRendition::ResumeRendition:
operation = Okular::RenditionAction::Resume;
break;
};
Okular::Movie *movie = 0;
if ( popplerLinkRendition->rendition() )
movie = createMovieFromPopplerScreen( popplerLinkRendition );
Okular::RenditionAction *renditionAction = new Okular::RenditionAction( operation, movie, Okular::JavaScript, popplerLinkRendition->script() );
renditionAction->setNativeId( QVariant::fromValue( popplerLinkRendition ) );
link = renditionAction;
}
break;
case Poppler::Link::Movie:
{
if (!deletePopplerLink)
{
// See comment above in Link::Rendition
qCDebug(OkularPdfDebug) << "parsing movie link without deletion is not supported. Action chain might be broken.";
break;
}
deletePopplerLink = false; // we'll delete it inside resolveMediaLinkReferences() after we have resolved all references
popplerLinkMovie = static_cast( popplerLink );
Okular::MovieAction::OperationType operation = Okular::MovieAction::Play;
switch ( popplerLinkMovie->operation() )
{
case Poppler::LinkMovie::Play:
operation = Okular::MovieAction::Play;
break;
case Poppler::LinkMovie::Stop:
operation = Okular::MovieAction::Stop;
break;
case Poppler::LinkMovie::Pause:
operation = Okular::MovieAction::Pause;
break;
case Poppler::LinkMovie::Resume:
operation = Okular::MovieAction::Resume;
break;
};
Okular::MovieAction *movieAction = new Okular::MovieAction( operation );
movieAction->setNativeId( QVariant::fromValue( popplerLinkMovie ) );
link = movieAction;
}
break;
#ifdef HAVE_POPPLER_0_64
case Poppler::Link::Hide:
{
const Poppler::LinkHide * l = static_cast( popplerLink );
QStringList scripts;
for ( const QString &target: l->targets() )
{
scripts << QStringLiteral( "getField(\"%1\").hidden = %2;" ).arg( target ).arg( l->isShowAction() ? QLatin1String( "false" ) : QLatin1String( "true" ) );
}
link = new Okular::ScriptAction( Okular::JavaScript, scripts.join( QLatin1Char( '\n' ) ) );
}
break;
#endif
case Poppler::Link::OCGState:
Q_UNREACHABLE();
}
#ifdef HAVE_POPPLER_0_64
if (link)
{
QVector< Okular::Action * > nextActions;
for ( const Poppler::Link *nl : popplerLink->nextLinks() )
{
nextActions << createLinkFromPopplerLink( nl, false );
}
link->setNextActions( nextActions );
}
#endif
if ( deletePopplerLink )
delete popplerLink;
return link;
}
/**
* Note: the function will take ownership of the popplerLink objects.
*/
static QLinkedList generateLinks( const QList &popplerLinks )
{
QLinkedList links;
foreach(const Poppler::Link *popplerLink, popplerLinks)
{
QRectF linkArea = popplerLink->linkArea();
double nl = linkArea.left(),
nt = linkArea.top(),
nr = linkArea.right(),
nb = linkArea.bottom();
// create the rect using normalized coords and attach the Okular::Link to it
Okular::ObjectRect * rect = new Okular::ObjectRect( nl, nt, nr, nb, false, Okular::ObjectRect::Action, createLinkFromPopplerLink(popplerLink) );
// add the ObjectRect to the container
links.push_front( rect );
}
return links;
}
/** NOTES on threading:
* internal: thread race prevention is done via the 'docLock' mutex. the
* mutex is needed only because we have the asynchronous thread; else
* the operations are all within the 'gui' thread, scheduled by the
* Qt scheduler and no mutex is needed.
* external: dangerous operations are all locked via mutex internally, and the
* only needed external thing is the 'canGeneratePixmap' method
* that tells if the generator is free (since we don't want an
* internal queue to store PixmapRequests). A generatedPixmap call
* without the 'ready' flag set, results in undefined behavior.
* So, as example, printing while generating a pixmap asynchronously is safe,
* it might only block the gui thread by 1) waiting for the mutex to unlock
* in async thread and 2) doing the 'heavy' print operation.
*/
OKULAR_EXPORT_PLUGIN(PDFGenerator, "libokularGenerator_poppler.json")
static void PDFGeneratorPopplerDebugFunction(const QString &message, const QVariant &closure)
{
Q_UNUSED(closure);
qCDebug(OkularPdfDebug) << "[Poppler]" << message;
}
PDFGenerator::PDFGenerator( QObject *parent, const QVariantList &args )
: Generator( parent, args ), pdfdoc( 0 ),
docSynopsisDirty( true ),
docEmbeddedFilesDirty( true ), nextFontPage( 0 ),
annotProxy( 0 )
{
setFeature( Threaded );
setFeature( TextExtraction );
setFeature( FontInfo );
#ifdef Q_OS_WIN32
setFeature( PrintNative );
#else
setFeature( PrintPostscript );
#endif
if ( Okular::FilePrinter::ps2pdfAvailable() )
setFeature( PrintToFile );
setFeature( ReadRawData );
setFeature( TiledRendering );
setFeature( SwapBackingFile );
#ifdef HAVE_POPPLER_0_63
setFeature( SupportsCancelling );
#endif
// You only need to do it once not for each of the documents but it is cheap enough
// so doing it all the time won't hurt either
Poppler::setDebugErrorFunction(PDFGeneratorPopplerDebugFunction, QVariant());
}
PDFGenerator::~PDFGenerator()
{
delete pdfOptionsPage;
}
//BEGIN Generator inherited functions
Okular::Document::OpenResult PDFGenerator::loadDocumentWithPassword( const QString & filePath, QVector & pagesVector, const QString &password )
{
#ifndef NDEBUG
if ( pdfdoc )
{
qCDebug(OkularPdfDebug) << "PDFGenerator: multiple calls to loadDocument. Check it.";
return Okular::Document::OpenError;
}
#endif
// create PDFDoc for the given file
pdfdoc = Poppler::Document::load( filePath, 0, 0 );
return init(pagesVector, password);
}
Okular::Document::OpenResult PDFGenerator::loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector & pagesVector, const QString &password )
{
#ifndef NDEBUG
if ( pdfdoc )
{
qCDebug(OkularPdfDebug) << "PDFGenerator: multiple calls to loadDocument. Check it.";
return Okular::Document::OpenError;
}
#endif
// create PDFDoc for the given file
pdfdoc = Poppler::Document::loadFromData( fileData, 0, 0 );
return init(pagesVector, password);
}
Okular::Document::OpenResult PDFGenerator::init(QVector & pagesVector, const QString &password)
{
if ( !pdfdoc )
return Okular::Document::OpenError;
if ( pdfdoc->isLocked() )
{
pdfdoc->unlock( password.toLatin1(), password.toLatin1() );
if ( pdfdoc->isLocked() ) {
delete pdfdoc;
pdfdoc = 0;
return Okular::Document::OpenNeedsPassword;
}
}
// build Pages (currentPage was set -1 by deletePages)
int pageCount = pdfdoc->numPages();
if (pageCount < 0) {
delete pdfdoc;
pdfdoc = 0;
return Okular::Document::OpenError;
}
pagesVector.resize(pageCount);
rectsGenerated.fill(false, pageCount);
annotationsOnOpenHash.clear();
loadPages(pagesVector, 0, false);
// update the configuration
reparseConfig();
// create annotation proxy
annotProxy = new PopplerAnnotationProxy( pdfdoc, userMutex(), &annotationsOnOpenHash );
// the file has been loaded correctly
return Okular::Document::OpenSuccess;
}
PDFGenerator::SwapBackingFileResult PDFGenerator::swapBackingFile( QString const &newFileName, QVector & newPagesVector )
{
const QBitArray oldRectsGenerated = rectsGenerated;
doCloseDocument();
auto openResult = loadDocumentWithPassword(newFileName, newPagesVector, QString());
if (openResult != Okular::Document::OpenSuccess)
return SwapBackingFileError;
// Recreate links if needed since they are done on image() and image() is not called when swapping the file
// since the page is already rendered
if (oldRectsGenerated.count() == rectsGenerated.count()) {
for (int i = 0; i < oldRectsGenerated.count(); ++i) {
if (oldRectsGenerated[i]) {
Okular::Page *page = newPagesVector[i];
Poppler::Page *pp = pdfdoc->page( i );
if (pp) {
page->setObjectRects(generateLinks(pp->links()));
rectsGenerated[i] = true;
resolveMediaLinkReferences(page);
delete pp;
}
}
}
}
return SwapBackingFileReloadInternalData;
}
bool PDFGenerator::doCloseDocument()
{
// remove internal objects
userMutex()->lock();
delete annotProxy;
annotProxy = 0;
delete pdfdoc;
pdfdoc = 0;
userMutex()->unlock();
docSynopsisDirty = true;
docSyn.clear();
docEmbeddedFilesDirty = true;
qDeleteAll(docEmbeddedFiles);
docEmbeddedFiles.clear();
nextFontPage = 0;
rectsGenerated.clear();
return true;
}
void PDFGenerator::loadPages(QVector &pagesVector, int rotation, bool clear)
{
// TODO XPDF 3.01 check
const int count = pagesVector.count();
double w = 0, h = 0;
for ( int i = 0; i < count ; i++ )
{
// get xpdf page
Poppler::Page * p = pdfdoc->page( i );
Okular::Page * page;
if (p)
{
const QSizeF pSize = p->pageSizeF();
w = pSize.width() / 72.0 * dpi().width();
h = pSize.height() / 72.0 * dpi().height();
Okular::Rotation orientation = Okular::Rotation0;
switch (p->orientation())
{
case Poppler::Page::Landscape: orientation = Okular::Rotation90; break;
case Poppler::Page::UpsideDown: orientation = Okular::Rotation180; break;
case Poppler::Page::Seascape: orientation = Okular::Rotation270; break;
case Poppler::Page::Portrait: orientation = Okular::Rotation0; break;
}
if (rotation % 2 == 1)
qSwap(w,h);
// init a Okular::page, add transition and annotation information
page = new Okular::Page( i, w, h, orientation );
addTransition( p, page );
if ( true ) //TODO real check
addAnnotations( p, page );
Poppler::Link * tmplink = p->action( Poppler::Page::Opening );
if ( tmplink )
{
page->setPageAction( Okular::Page::Opening, createLinkFromPopplerLink( tmplink ) );
}
tmplink = p->action( Poppler::Page::Closing );
if ( tmplink )
{
page->setPageAction( Okular::Page::Closing, createLinkFromPopplerLink( tmplink ) );
}
page->setDuration( p->duration() );
page->setLabel( p->label() );
addFormFields( p, page );
// kWarning(PDFDebug).nospace() << page->width() << "x" << page->height();
#ifdef PDFGENERATOR_DEBUG
qCDebug(OkularPdfDebug) << "load page" << i << "with rotation" << rotation << "and orientation" << orientation;
#endif
delete p;
if (clear && pagesVector[i])
delete pagesVector[i];
}
else
{
page = new Okular::Page( i, defaultPageWidth, defaultPageHeight, Okular::Rotation0 );
}
// set the Okular::page at the right position in document's pages vector
pagesVector[i] = page;
}
}
Okular::DocumentInfo PDFGenerator::generateDocumentInfo( const QSet &keys ) const
{
Okular::DocumentInfo docInfo;
docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("application/pdf") );
userMutex()->lock();
if ( pdfdoc )
{
// compile internal structure reading properties from PDFDoc
if ( keys.contains( Okular::DocumentInfo::Title ) )
docInfo.set( Okular::DocumentInfo::Title, pdfdoc->info(QStringLiteral("Title")) );
if ( keys.contains( Okular::DocumentInfo::Subject ) )
docInfo.set( Okular::DocumentInfo::Subject, pdfdoc->info(QStringLiteral("Subject")) );
if ( keys.contains( Okular::DocumentInfo::Author ) )
docInfo.set( Okular::DocumentInfo::Author, pdfdoc->info(QStringLiteral("Author")) );
if ( keys.contains( Okular::DocumentInfo::Keywords ) )
docInfo.set( Okular::DocumentInfo::Keywords, pdfdoc->info(QStringLiteral("Keywords")) );
if ( keys.contains( Okular::DocumentInfo::Creator ) )
docInfo.set( Okular::DocumentInfo::Creator, pdfdoc->info(QStringLiteral("Creator")) );
if ( keys.contains( Okular::DocumentInfo::Producer ) )
docInfo.set( Okular::DocumentInfo::Producer, pdfdoc->info(QStringLiteral("Producer")) );
if ( keys.contains( Okular::DocumentInfo::CreationDate ) )
docInfo.set( Okular::DocumentInfo::CreationDate, QLocale().toString( pdfdoc->date(QStringLiteral("CreationDate")), QLocale::LongFormat ) );
if ( keys.contains( Okular::DocumentInfo::ModificationDate ) )
docInfo.set( Okular::DocumentInfo::ModificationDate, QLocale().toString( pdfdoc->date(QStringLiteral("ModDate")), QLocale::LongFormat ) );
if ( keys.contains( Okular::DocumentInfo::CustomKeys ) )
{
int major, minor;
pdfdoc->getPdfVersion(&major, &minor);
docInfo.set( QStringLiteral("format"), i18nc( "PDF v. ", "PDF v. %1.%2", major, minor ), i18n( "Format" ) );
docInfo.set( QStringLiteral("encryption"), pdfdoc->isEncrypted() ? i18n( "Encrypted" ) : i18n( "Unencrypted" ), i18n("Security") );
docInfo.set( QStringLiteral("optimization"), pdfdoc->isLinearized() ? i18n( "Yes" ) : i18n( "No" ), i18n("Optimized") );
}
docInfo.set( Okular::DocumentInfo::Pages, QString::number( pdfdoc->numPages() ) );
}
userMutex()->unlock();
return docInfo;
}
const Okular::DocumentSynopsis * PDFGenerator::generateDocumentSynopsis()
{
if ( !docSynopsisDirty )
return &docSyn;
if ( !pdfdoc )
return NULL;
userMutex()->lock();
QDomDocument *toc = pdfdoc->toc();
userMutex()->unlock();
if ( !toc )
return NULL;
addSynopsisChildren(toc, &docSyn);
delete toc;
docSynopsisDirty = false;
return &docSyn;
}
static Okular::FontInfo::FontType convertPopplerFontInfoTypeToOkularFontInfoType( Poppler::FontInfo::Type type )
{
switch ( type )
{
case Poppler::FontInfo::Type1:
return Okular::FontInfo::Type1;
break;
case Poppler::FontInfo::Type1C:
return Okular::FontInfo::Type1C;
break;
case Poppler::FontInfo::Type3:
return Okular::FontInfo::Type3;
break;
case Poppler::FontInfo::TrueType:
return Okular::FontInfo::TrueType;
break;
case Poppler::FontInfo::CIDType0:
return Okular::FontInfo::CIDType0;
break;
case Poppler::FontInfo::CIDType0C:
return Okular::FontInfo::CIDType0C;
break;
case Poppler::FontInfo::CIDTrueType:
return Okular::FontInfo::CIDTrueType;
break;
case Poppler::FontInfo::Type1COT:
return Okular::FontInfo::Type1COT;
break;
case Poppler::FontInfo::TrueTypeOT:
return Okular::FontInfo::TrueTypeOT;
break;
case Poppler::FontInfo::CIDType0COT:
return Okular::FontInfo::CIDType0COT;
break;
case Poppler::FontInfo::CIDTrueTypeOT:
return Okular::FontInfo::CIDTrueTypeOT;
break;
case Poppler::FontInfo::unknown:
default: ;
}
return Okular::FontInfo::Unknown;
}
static Okular::FontInfo::EmbedType embedTypeForPopplerFontInfo( const Poppler::FontInfo &fi )
{
Okular::FontInfo::EmbedType ret = Okular::FontInfo::NotEmbedded;
if ( fi.isEmbedded() )
{
if ( fi.isSubset() )
{
ret = Okular::FontInfo::EmbeddedSubset;
}
else
{
ret = Okular::FontInfo::FullyEmbedded;
}
}
return ret;
}
Okular::FontInfo::List PDFGenerator::fontsForPage( int page )
{
Okular::FontInfo::List list;
if ( page != nextFontPage )
return list;
QList fonts;
userMutex()->lock();
Poppler::FontIterator* it = pdfdoc->newFontIterator(page);
if (it->hasNext()) {
fonts = it->next();
}
userMutex()->unlock();
foreach (const Poppler::FontInfo &font, fonts)
{
Okular::FontInfo of;
of.setName( font.name() );
of.setType( convertPopplerFontInfoTypeToOkularFontInfoType( font.type() ) );
of.setEmbedType( embedTypeForPopplerFontInfo( font) );
of.setFile( font.file() );
of.setCanBeExtracted( of.embedType() != Okular::FontInfo::NotEmbedded );
QVariant nativeId;
nativeId.setValue( font );
of.setNativeId( nativeId );
list.append( of );
}
++nextFontPage;
return list;
}
const QList *PDFGenerator::embeddedFiles() const
{
if (docEmbeddedFilesDirty)
{
userMutex()->lock();
const QList &popplerFiles = pdfdoc->embeddedFiles();
foreach(Poppler::EmbeddedFile* pef, popplerFiles)
{
docEmbeddedFiles.append(new PDFEmbeddedFile(pef));
}
userMutex()->unlock();
docEmbeddedFilesDirty = false;
}
return &docEmbeddedFiles;
}
QAbstractItemModel* PDFGenerator::layersModel() const
{
return pdfdoc->hasOptionalContent() ? pdfdoc->optionalContentModel() : NULL;
}
void PDFGenerator::opaqueAction( const Okular::BackendOpaqueAction *action )
{
#ifdef HAVE_POPPLER_0_50
const Poppler::LinkOCGState *popplerLink = action->nativeId().value();
pdfdoc->optionalContentModel()->applyLink( const_cast< Poppler::LinkOCGState* >( popplerLink ) );
#else
(void)action;
#endif
}
bool PDFGenerator::isAllowed( Okular::Permission permission ) const
{
bool b = true;
switch ( permission )
{
case Okular::AllowModify:
b = pdfdoc->okToChange();
break;
case Okular::AllowCopy:
b = pdfdoc->okToCopy();
break;
case Okular::AllowPrint:
b = pdfdoc->okToPrint();
break;
case Okular::AllowNotes:
b = pdfdoc->okToAddNotes();
break;
case Okular::AllowFillForms:
b = pdfdoc->okToFillForm();
break;
default: ;
}
return b;
}
#ifdef HAVE_POPPLER_0_62
struct RenderImagePayload
{
RenderImagePayload(PDFGenerator *g, Okular::PixmapRequest *r) :
generator(g), request(r)
{
// Don't report partial updates for the first 500 ms
timer.setInterval(500);
timer.setSingleShot(true);
timer.start();
}
PDFGenerator *generator;
Okular::PixmapRequest *request;
QTimer timer;
};
Q_DECLARE_METATYPE(RenderImagePayload*)
static bool shouldDoPartialUpdateCallback(const QVariant &vPayload)
{
auto payload = vPayload.value();
// Since the timer lives in a thread without an event loop we need to stop it ourselves
// when the remaining time has reached 0
if (payload->timer.isActive() && payload->timer.remainingTime() == 0) {
payload->timer.stop();
}
return !payload->timer.isActive();
}
static void partialUpdateCallback(const QImage &image, const QVariant &vPayload)
{
auto payload = vPayload.value();
QMetaObject::invokeMethod(payload->generator, "signalPartialPixmapRequest", Qt::QueuedConnection, Q_ARG(Okular::PixmapRequest*, payload->request), Q_ARG(QImage, image));
}
#endif
#ifdef HAVE_POPPLER_0_63
static bool shouldAbortRenderCallback(const QVariant &vPayload)
{
auto payload = vPayload.value();
return payload->request->shouldAbortRender();
}
#endif
QImage PDFGenerator::image( Okular::PixmapRequest * request )
{
// debug requests to this (xpdf) generator
//qCDebug(OkularPdfDebug) << "id: " << request->id << " is requesting " << (request->async ? "ASYNC" : "sync") << " pixmap for page " << request->page->number() << " [" << request->width << " x " << request->height << "].";
// compute dpi used to get an image with desired width and height
Okular::Page * page = request->page();
double pageWidth = page->width(),
pageHeight = page->height();
if ( page->rotation() % 2 )
qSwap( pageWidth, pageHeight );
qreal fakeDpiX = request->width() / pageWidth * dpi().width();
qreal fakeDpiY = request->height() / pageHeight * dpi().height();
// generate links rects only the first time
bool genObjectRects = !rectsGenerated.at( page->number() );
// 0. LOCK [waits for the thread end]
userMutex()->lock();
if ( request->shouldAbortRender() )
{
userMutex()->unlock();
return QImage();
}
// 1. Set OutputDev parameters and Generate contents
// note: thread safety is set on 'false' for the GUI (this) thread
Poppler::Page *p = pdfdoc->page(page->number());
// 2. Take data from outputdev and attach it to the Page
QImage img;
if (p)
{
#ifdef HAVE_POPPLER_0_63
if ( request->isTile() )
{
const QRect rect = request->normalizedRect().geometry( request->width(), request->height() );
if ( request->partialUpdatesWanted() )
{
RenderImagePayload payload( this, request );
img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0,
partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, QVariant::fromValue( &payload ) );
}
else
{
RenderImagePayload payload( this, request );
img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0,
nullptr, nullptr, shouldAbortRenderCallback, QVariant::fromValue( &payload ) );
}
}
else
{
if ( request->partialUpdatesWanted() )
{
RenderImagePayload payload( this, request );
img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0,
partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, QVariant::fromValue( &payload ) );
}
else
{
RenderImagePayload payload( this, request );
img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0,
nullptr, nullptr, shouldAbortRenderCallback, QVariant::fromValue( &payload ) );
}
}
#elif defined(HAVE_POPPLER_0_62)
if ( request->isTile() )
{
const QRect rect = request->normalizedRect().geometry( request->width(), request->height() );
if ( request->partialUpdatesWanted() )
{
RenderImagePayload payload( this, request );
img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0,
partialUpdateCallback, shouldDoPartialUpdateCallback, QVariant::fromValue( &payload ) );
}
else
{
img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0 );
}
}
else
{
if ( request->partialUpdatesWanted() )
{
RenderImagePayload payload( this, request );
img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0,
partialUpdateCallback, shouldDoPartialUpdateCallback, QVariant::fromValue( &payload ) );
}
else
{
img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 );
}
}
#else
if ( request->isTile() )
{
const QRect rect = request->normalizedRect().geometry( request->width(), request->height() );
img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0 );
}
else
{
img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 );
}
#endif
}
else
{
img = QImage( request->width(), request->height(), QImage::Format_Mono );
img.fill( Qt::white );
}
if ( p && genObjectRects )
{
// TODO previously we extracted Image type rects too, but that needed porting to poppler
// and as we are not doing anything with Image type rects i did not port it, have a look at
// dead gp_outputdev.cpp on image extraction
page->setObjectRects( generateLinks(p->links()) );
rectsGenerated[ request->page()->number() ] = true;
resolveMediaLinkReferences( page );
}
// 3. UNLOCK [re-enables shared access]
userMutex()->unlock();
delete p;
return img;
}
template
void resolveMediaLinks( Okular::Action *action, enum Okular::Annotation::SubType subType, QHash &annotationsHash )
{
OkularLinkType *okularAction = static_cast( action );
const PopplerLinkType *popplerLink = action->nativeId().value();
QHashIterator it( annotationsHash );
while ( it.hasNext() )
{
it.next();
if ( it.key()->subType() == subType )
{
const PopplerAnnotationType *popplerAnnotation = static_cast( it.value() );
if ( popplerLink->isReferencedAnnotation( popplerAnnotation ) )
{
okularAction->setAnnotation( static_cast( it.key() ) );
okularAction->setNativeId( QVariant() );
delete popplerLink; // delete the associated Poppler::LinkMovie object, it's not needed anymore
break;
}
}
}
}
void PDFGenerator::resolveMediaLinkReference( Okular::Action *action )
{
if ( !action )
return;
if ( (action->actionType() != Okular::Action::Movie) && (action->actionType() != Okular::Action::Rendition) )
return;
resolveMediaLinks( action, Okular::Annotation::AMovie, annotationsOnOpenHash );
resolveMediaLinks( action, Okular::Annotation::AScreen, annotationsOnOpenHash );
}
void PDFGenerator::resolveMediaLinkReferences( Okular::Page *page )
{
resolveMediaLinkReference( const_cast( page->pageAction( Okular::Page::Opening ) ) );
resolveMediaLinkReference( const_cast( page->pageAction( Okular::Page::Closing ) ) );
foreach ( Okular::Annotation *annotation, page->annotations() )
{
if ( annotation->subType() == Okular::Annotation::AScreen )
{
Okular::ScreenAnnotation *screenAnnotation = static_cast( annotation );
resolveMediaLinkReference( screenAnnotation->additionalAction( Okular::Annotation::PageOpening ) );
resolveMediaLinkReference( screenAnnotation->additionalAction( Okular::Annotation::PageClosing ) );
}
if ( annotation->subType() == Okular::Annotation::AWidget )
{
Okular::WidgetAnnotation *widgetAnnotation = static_cast( annotation );
resolveMediaLinkReference( widgetAnnotation->additionalAction( Okular::Annotation::PageOpening ) );
resolveMediaLinkReference( widgetAnnotation->additionalAction( Okular::Annotation::PageClosing ) );
}
}
foreach ( Okular::FormField *field, page->formFields() )
resolveMediaLinkReference( field->activationAction() );
}
#ifdef HAVE_POPPLER_0_63
struct TextExtractionPayload
{
TextExtractionPayload(Okular::TextRequest *r) :
request(r)
{
}
Okular::TextRequest *request;
};
Q_DECLARE_METATYPE(TextExtractionPayload*)
static bool shouldAbortTextExtractionCallback(const QVariant &vPayload)
{
auto payload = vPayload.value();
return payload->request->shouldAbortExtraction();
}
#endif
Okular::TextPage* PDFGenerator::textPage( Okular::TextRequest *request )
{
const Okular::Page *page = request->page();
#ifdef PDFGENERATOR_DEBUG
qCDebug(OkularPdfDebug) << "page" << page->number();
#endif
// build a TextList...
QList textList;
double pageWidth, pageHeight;
userMutex()->lock();
Poppler::Page *pp = pdfdoc->page( page->number() );
if (pp)
{
#ifdef HAVE_POPPLER_0_63
TextExtractionPayload payload(request);
textList = pp->textList( Poppler::Page::Rotate0, shouldAbortTextExtractionCallback, QVariant::fromValue( &payload ) );
#else
textList = pp->textList();
#endif
const QSizeF s = pp->pageSizeF();
pageWidth = s.width();
pageHeight = s.height();
}
else
{
pageWidth = defaultPageWidth;
pageHeight = defaultPageHeight;
}
delete pp;
userMutex()->unlock();
if ( textList.isEmpty() && request->shouldAbortExtraction() )
return nullptr;
Okular::TextPage *tp = abstractTextPage(textList, pageHeight, pageWidth, (Poppler::Page::Rotation)page->orientation());
qDeleteAll(textList);
return tp;
}
void PDFGenerator::requestFontData(const Okular::FontInfo &font, QByteArray *data)
{
Poppler::FontInfo fi = font.nativeId().value();
*data = pdfdoc->fontData(fi);
}
#define DUMMY_QPRINTER_COPY
bool PDFGenerator::print( QPrinter& printer )
{
bool printAnnots = true;
bool forceRasterize = false;
PDFOptionsPage::ScaleMode scaleMode = PDFOptionsPage::FitToPrintableArea;
if ( pdfOptionsPage )
{
printAnnots = pdfOptionsPage->printAnnots();
forceRasterize = pdfOptionsPage->printForceRaster();
scaleMode = pdfOptionsPage->scaleMode();
}
#ifdef Q_OS_WIN
// Windows can only print by rasterization, because that is
// currently the only way Okular implements printing without using UNIX-specific
// tools like 'lpr'.
forceRasterize = true;
#ifndef HAVE_POPPLER_0_60
// The Document::HideAnnotations flags was introduced in poppler 0.60
printAnnots = true;
#endif
#endif
#ifdef HAVE_POPPLER_0_60
if ( forceRasterize )
{
pdfdoc->setRenderHint(Poppler::Document::HideAnnotations, !printAnnots);
#else
if ( forceRasterize && printAnnots)
{
#endif
// If requested, scale to full page instead of the printable area
- if ( scaleMode == PDFOptionsPage::FitToPage )
- printer.setFullPage( true );
+ printer.setFullPage( pdfOptionsPage->ignorePrintMargins() );
QPainter painter;
painter.begin(&printer);
QList pageList = Okular::FilePrinter::pageList( printer, pdfdoc->numPages(),
document()->currentPage() + 1,
document()->bookmarkedPageList() );
for ( int i = 0; i < pageList.count(); ++i )
{
if ( i != 0 )
printer.newPage();
const int page = pageList.at( i ) - 1;
userMutex()->lock();
std::unique_ptr pp( pdfdoc->page( page ) );
if (pp)
{
QSizeF pageSize = pp->pageSizeF(); // Unit is 'points' (i.e., 1/72th of an inch)
QRect painterWindow = painter.window(); // Unit is 'QPrinter::DevicePixel'
// Default: no scaling at all, but we need to go from DevicePixel units to 'points'
// Warning: We compute the horizontal scaling, and later assume that the vertical scaling will be the same.
double scaling = printer.paperRect(QPrinter::DevicePixel).width() / printer.paperRect(QPrinter::Point).width();
if ( scaleMode != PDFOptionsPage::None )
{
// Get the two scaling factors needed to fit the page onto paper horizontally or vertically
auto horizontalScaling = painterWindow.width() / pageSize.width();
auto verticalScaling = painterWindow.height() / pageSize.height();
// We use the smaller of the two for both directions, to keep the aspect ratio
scaling = std::min(horizontalScaling, verticalScaling);
}
#ifdef Q_OS_WIN
QImage img = pp->renderToImage( printer.physicalDpiX(), printer.physicalDpiY() );
#else
// UNIX: Same resolution as the postscript rasterizer; see discussion at https://git.reviewboard.kde.org/r/130218/
QImage img = pp->renderToImage( 300, 300 );
#endif
painter.drawImage( QRectF( QPointF( 0, 0 ), scaling * pp->pageSizeF() ), img );
}
userMutex()->unlock();
}
painter.end();
return true;
}
#ifdef DUMMY_QPRINTER_COPY
// Get the real page size to pass to the ps generator
QPrinter dummy( QPrinter::PrinterResolution );
dummy.setFullPage( true );
dummy.setOrientation( printer.orientation() );
dummy.setPageSize( printer.pageSize() );
dummy.setPaperSize( printer.paperSize( QPrinter::Millimeter ), QPrinter::Millimeter );
int width = dummy.width();
int height = dummy.height();
#else
int width = printer.width();
int height = printer.height();
#endif
if (width <= 0 || height <= 0)
{
lastPrintError = InvalidPageSizePrintError;
return false;
}
// Create the tempfile to send to FilePrinter, which will manage the deletion
QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps"));
if ( !tf.open() )
{
lastPrintError = TemporaryFileOpenPrintError;
return false;
}
QString tempfilename = tf.fileName();
// Generate the list of pages to be printed as selected in the print dialog
QList pageList = Okular::FilePrinter::pageList( printer, pdfdoc->numPages(),
document()->currentPage() + 1,
document()->bookmarkedPageList() );
// TODO rotation
tf.setAutoRemove(false);
QString pstitle = metaData(QStringLiteral("Title"), QVariant()).toString();
if ( pstitle.trimmed().isEmpty() )
{
pstitle = document()->currentDocument().fileName();
}
Poppler::PSConverter *psConverter = pdfdoc->psConverter();
psConverter->setOutputDevice(&tf);
psConverter->setPageList(pageList);
psConverter->setPaperWidth(width);
psConverter->setPaperHeight(height);
psConverter->setRightMargin(0);
psConverter->setBottomMargin(0);
psConverter->setLeftMargin(0);
psConverter->setTopMargin(0);
psConverter->setStrictMargins(false);
psConverter->setForceRasterize(forceRasterize);
psConverter->setTitle(pstitle);
if (!printAnnots)
psConverter->setPSOptions(psConverter->psOptions() | Poppler::PSConverter::HideAnnotations );
userMutex()->lock();
if (psConverter->convert())
{
userMutex()->unlock();
delete psConverter;
tf.close();
int ret = Okular::FilePrinter::printFile( printer, tempfilename,
document()->orientation(),
Okular::FilePrinter::SystemDeletesFiles,
Okular::FilePrinter::ApplicationSelectsPages,
document()->bookmarkedPageRange() );
lastPrintError = Okular::FilePrinter::printError( ret );
return (lastPrintError == NoPrintError);
}
else
{
lastPrintError = FileConversionPrintError;
delete psConverter;
userMutex()->unlock();
}
tf.close();
return false;
}
QVariant PDFGenerator::metaData( const QString & key, const QVariant & option ) const
{
if ( key == QLatin1String("StartFullScreen") )
{
QMutexLocker ml(userMutex());
// asking for the 'start in fullscreen mode' (pdf property)
if ( pdfdoc->pageMode() == Poppler::Document::FullScreen )
return true;
}
else if ( key == QLatin1String("NamedViewport") && !option.toString().isEmpty() )
{
Okular::DocumentViewport viewport;
QString optionString = option.toString();
// asking for the page related to a 'named link destination'. the
// option is the link name. @see addSynopsisChildren.
userMutex()->lock();
Poppler::LinkDestination *ld = pdfdoc->linkDestination( optionString );
userMutex()->unlock();
if ( ld )
{
fillViewportFromLinkDestination( viewport, *ld );
}
delete ld;
if ( viewport.pageNumber >= 0 )
return viewport.toString();
}
else if ( key == QLatin1String("DocumentTitle") )
{
userMutex()->lock();
QString title = pdfdoc->info( QStringLiteral("Title") );
userMutex()->unlock();
return title;
}
else if ( key == QLatin1String("OpenTOC") )
{
QMutexLocker ml(userMutex());
if ( pdfdoc->pageMode() == Poppler::Document::UseOutlines )
return true;
}
else if ( key == QLatin1String("DocumentScripts") && option.toString() == QLatin1String("JavaScript") )
{
QMutexLocker ml(userMutex());
return pdfdoc->scripts();
}
else if ( key == QLatin1String("HasUnsupportedXfaForm") )
{
QMutexLocker ml(userMutex());
return pdfdoc->formType() == Poppler::Document::XfaForm;
}
else if ( key == QLatin1String("FormCalculateOrder") )
{
#ifdef HAVE_POPPLER_0_53
QMutexLocker ml(userMutex());
return QVariant::fromValue>(pdfdoc->formCalculateOrder());
#endif
}
else if ( key == QLatin1String("GeneratorExtraDescription") )
{
#ifdef HAVE_POPPLER_0_73
if (Poppler::Version::string() == POPPLER_VERSION) {
return i18n("Using Poppler %1", Poppler::Version::string());
} else {
return i18n("Using Poppler %1\n\nBuilt against Poppler %2", Poppler::Version::string(), POPPLER_VERSION);
}
#endif
}
else if ( key == QLatin1String("IsDigitallySigned") )
{
const Okular::Document *doc = document();
uint numPages = doc->pages();
for ( uint i = 0; i < numPages; i++ )
{
foreach ( Okular::FormField *f, doc->page( i )->formFields() )
{
if ( f->type() == Okular::FormField::FormSignature )
return true;
}
}
return false;
}
return QVariant();
}
bool PDFGenerator::reparseConfig()
{
if ( !pdfdoc )
return false;
bool somethingchanged = false;
// load paper color
QColor color = documentMetaData( PaperColorMetaData, true ).value< QColor >();
// if paper color is changed we have to rebuild every visible pixmap in addition
// to the outputDevice. it's the 'heaviest' case, other effect are just recoloring
// over the page rendered on 'standard' white background.
if ( color != pdfdoc->paperColor() )
{
userMutex()->lock();
pdfdoc->setPaperColor(color);
userMutex()->unlock();
somethingchanged = true;
}
bool aaChanged = setDocumentRenderHints();
somethingchanged = somethingchanged || aaChanged;
return somethingchanged;
}
void PDFGenerator::addPages( KConfigDialog *dlg )
{
#ifdef HAVE_POPPLER_0_24
Ui_PDFSettingsWidget pdfsw;
QWidget* w = new QWidget(dlg);
pdfsw.setupUi(w);
dlg->addPage(w, PDFSettings::self(), i18n("PDF"), QStringLiteral("application-pdf"), i18n("PDF Backend Configuration") );
#endif
}
bool PDFGenerator::setDocumentRenderHints()
{
bool changed = false;
const Poppler::Document::RenderHints oldhints = pdfdoc->renderHints();
#define SET_HINT(hintname, hintdefvalue, hintflag) \
{ \
bool newhint = documentMetaData(hintname, hintdefvalue).toBool(); \
if (newhint != oldhints.testFlag(hintflag)) \
{ \
pdfdoc->setRenderHint(hintflag, newhint); \
changed = true; \
} \
}
SET_HINT(GraphicsAntialiasMetaData, true, Poppler::Document::Antialiasing)
SET_HINT(TextAntialiasMetaData, true, Poppler::Document::TextAntialiasing)
SET_HINT(TextHintingMetaData, false, Poppler::Document::TextHinting)
#undef SET_HINT
#ifdef HAVE_POPPLER_0_24
// load thin line mode
const int thinLineMode = PDFSettings::enhanceThinLines();
const bool enableThinLineSolid = thinLineMode == PDFSettings::EnumEnhanceThinLines::Solid;
const bool enableShapeLineSolid = thinLineMode == PDFSettings::EnumEnhanceThinLines::Shape;
const bool thinLineSolidWasEnabled = (oldhints & Poppler::Document::ThinLineSolid) == Poppler::Document::ThinLineSolid;
const bool thinLineShapeWasEnabled = (oldhints & Poppler::Document::ThinLineShape) == Poppler::Document::ThinLineShape;
if (enableThinLineSolid != thinLineSolidWasEnabled) {
pdfdoc->setRenderHint(Poppler::Document::ThinLineSolid, enableThinLineSolid);
changed = true;
}
if (enableShapeLineSolid != thinLineShapeWasEnabled) {
pdfdoc->setRenderHint(Poppler::Document::ThinLineShape, enableShapeLineSolid);
changed = true;
}
#endif
return changed;
}
Okular::ExportFormat::List PDFGenerator::exportFormats() const
{
static Okular::ExportFormat::List formats;
if ( formats.isEmpty() ) {
formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) );
}
return formats;
}
bool PDFGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format )
{
if ( format.mimeType().inherits( QStringLiteral( "text/plain" ) ) ) {
QFile f( fileName );
if ( !f.open( QIODevice::WriteOnly ) )
return false;
QTextStream ts( &f );
int num = document()->pages();
for ( int i = 0; i < num; ++i )
{
QString text;
userMutex()->lock();
Poppler::Page *pp = pdfdoc->page(i);
if (pp)
{
text = pp->text(QRect()).normalized(QString::NormalizationForm_KC);
}
userMutex()->unlock();
ts << text;
delete pp;
}
f.close();
return true;
}
return false;
}
//END Generator inherited functions
inline void append (Okular::TextPage* ktp,
const QString &s, double l, double b, double r, double t)
{
// kWarning(PDFDebug).nospace() << "text: " << s << " at (" << l << "," << t << ")x(" << r <<","<append(s, new Okular::NormalizedRect(l, t, r, b));
}
Okular::TextPage * PDFGenerator::abstractTextPage(const QList &text, double height, double width,int rot)
{
Q_UNUSED(rot);
Okular::TextPage* ktp=new Okular::TextPage;
Poppler::TextBox *next;
#ifdef PDFGENERATOR_DEBUG
qCDebug(OkularPdfDebug) << "getting text page in generator pdf - rotation:" << rot;
#endif
QString s;
bool addChar;
foreach (Poppler::TextBox *word, text)
{
const int qstringCharCount = word->text().length();
next=word->nextWord();
int textBoxChar = 0;
for (int j = 0; j < qstringCharCount; j++)
{
const QChar c = word->text().at(j);
if (c.isHighSurrogate())
{
s = c;
addChar = false;
}
else if (c.isLowSurrogate())
{
s += c;
addChar = true;
}
else
{
s = c;
addChar = true;
}
if (addChar)
{
QRectF charBBox = word->charBoundingBox(textBoxChar);
append(ktp, (j==qstringCharCount-1 && !next) ? (s + QLatin1Char('\n')) : s,
charBBox.left()/width,
charBBox.bottom()/height,
charBBox.right()/width,
charBBox.top()/height);
textBoxChar++;
}
}
if ( word->hasSpaceAfter() && next )
{
// TODO Check with a document with vertical text
// probably won't work and we will need to do comparisons
// between wordBBox and nextWordBBox to see if they are
// vertically or horizontally aligned
QRectF wordBBox = word->boundingBox();
QRectF nextWordBBox = next->boundingBox();
append(ktp, QStringLiteral(" "),
wordBBox.right()/width,
wordBBox.bottom()/height,
nextWordBBox.left()/width,
wordBBox.top()/height);
}
}
return ktp;
}
void PDFGenerator::addSynopsisChildren( QDomNode * parent, QDomNode * parentDestination )
{
// keep track of the current listViewItem
QDomNode n = parent->firstChild();
while( !n.isNull() )
{
// convert the node to an element (sure it is)
QDomElement e = n.toElement();
// The name is the same
QDomElement item = docSyn.createElement( e.tagName() );
parentDestination->appendChild(item);
if (!e.attribute(QStringLiteral("ExternalFileName")).isNull()) item.setAttribute(QStringLiteral("ExternalFileName"), e.attribute(QStringLiteral("ExternalFileName")));
if (!e.attribute(QStringLiteral("DestinationName")).isNull()) item.setAttribute(QStringLiteral("ViewportName"), e.attribute(QStringLiteral("DestinationName")));
if (!e.attribute(QStringLiteral("Destination")).isNull())
{
Okular::DocumentViewport vp;
fillViewportFromLinkDestination( vp, Poppler::LinkDestination(e.attribute(QStringLiteral("Destination"))) );
item.setAttribute( QStringLiteral("Viewport"), vp.toString() );
}
if (!e.attribute(QStringLiteral("Open")).isNull()) item.setAttribute(QStringLiteral("Open"), e.attribute(QStringLiteral("Open")));
if (!e.attribute(QStringLiteral("DestinationURI")).isNull()) item.setAttribute(QStringLiteral("URL"), e.attribute(QStringLiteral("DestinationURI")));
// descend recursively and advance to the next node
if ( e.hasChildNodes() ) addSynopsisChildren( &n, & item );
n = n.nextSibling();
}
}
void PDFGenerator::addAnnotations( Poppler::Page * popplerPage, Okular::Page * page )
{
#ifdef HAVE_POPPLER_0_28
QSet subtypes;
subtypes << Poppler::Annotation::AFileAttachment
<< Poppler::Annotation::ASound
<< Poppler::Annotation::AMovie
<< Poppler::Annotation::AWidget
<< Poppler::Annotation::AScreen
<< Poppler::Annotation::AText
<< Poppler::Annotation::ALine
<< Poppler::Annotation::AGeom
<< Poppler::Annotation::AHighlight
<< Poppler::Annotation::AInk
<< Poppler::Annotation::AStamp
<< Poppler::Annotation::ACaret;
QList popplerAnnotations = popplerPage->annotations( subtypes );
#else
QList popplerAnnotations = popplerPage->annotations();
#endif
foreach(Poppler::Annotation *a, popplerAnnotations)
{
bool doDelete = true;
Okular::Annotation * newann = createAnnotationFromPopplerAnnotation( a, &doDelete );
if (newann)
{
page->addAnnotation(newann);
if ( a->subType() == Poppler::Annotation::AScreen )
{
Poppler::ScreenAnnotation *annotScreen = static_cast( a );
Okular::ScreenAnnotation *screenAnnotation = static_cast( newann );
// The activation action
const Poppler::Link *actionLink = annotScreen->action();
if ( actionLink )
screenAnnotation->setAction( createLinkFromPopplerLink( actionLink ) );
// The additional actions
const Poppler::Link *pageOpeningLink = annotScreen->additionalAction( Poppler::Annotation::PageOpeningAction );
if ( pageOpeningLink )
screenAnnotation->setAdditionalAction( Okular::Annotation::PageOpening, createLinkFromPopplerLink( pageOpeningLink ) );
const Poppler::Link *pageClosingLink = annotScreen->additionalAction( Poppler::Annotation::PageClosingAction );
if ( pageClosingLink )
screenAnnotation->setAdditionalAction( Okular::Annotation::PageClosing, createLinkFromPopplerLink( pageClosingLink ) );
}
if ( a->subType() == Poppler::Annotation::AWidget )
{
Poppler::WidgetAnnotation *annotWidget = static_cast( a );
Okular::WidgetAnnotation *widgetAnnotation = static_cast( newann );
// The additional actions
const Poppler::Link *pageOpeningLink = annotWidget->additionalAction( Poppler::Annotation::PageOpeningAction );
if ( pageOpeningLink )
widgetAnnotation->setAdditionalAction( Okular::Annotation::PageOpening, createLinkFromPopplerLink( pageOpeningLink ) );
const Poppler::Link *pageClosingLink = annotWidget->additionalAction( Poppler::Annotation::PageClosingAction );
if ( pageClosingLink )
widgetAnnotation->setAdditionalAction( Okular::Annotation::PageClosing, createLinkFromPopplerLink( pageClosingLink ) );
}
if ( !doDelete )
annotationsOnOpenHash.insert( newann, a );
}
if ( doDelete )
delete a;
}
}
void PDFGenerator::addTransition( Poppler::Page * pdfPage, Okular::Page * page )
// called on opening when MUTEX is not used
{
Poppler::PageTransition *pdfTransition = pdfPage->transition();
if ( !pdfTransition || pdfTransition->type() == Poppler::PageTransition::Replace )
return;
Okular::PageTransition *transition = new Okular::PageTransition();
switch ( pdfTransition->type() ) {
case Poppler::PageTransition::Replace:
// won't get here, added to avoid warning
break;
case Poppler::PageTransition::Split:
transition->setType( Okular::PageTransition::Split );
break;
case Poppler::PageTransition::Blinds:
transition->setType( Okular::PageTransition::Blinds );
break;
case Poppler::PageTransition::Box:
transition->setType( Okular::PageTransition::Box );
break;
case Poppler::PageTransition::Wipe:
transition->setType( Okular::PageTransition::Wipe );
break;
case Poppler::PageTransition::Dissolve:
transition->setType( Okular::PageTransition::Dissolve );
break;
case Poppler::PageTransition::Glitter:
transition->setType( Okular::PageTransition::Glitter );
break;
case Poppler::PageTransition::Fly:
transition->setType( Okular::PageTransition::Fly );
break;
case Poppler::PageTransition::Push:
transition->setType( Okular::PageTransition::Push );
break;
case Poppler::PageTransition::Cover:
transition->setType( Okular::PageTransition::Cover );
break;
case Poppler::PageTransition::Uncover:
transition->setType( Okular::PageTransition::Uncover );
break;
case Poppler::PageTransition::Fade:
transition->setType( Okular::PageTransition::Fade );
break;
}
#ifdef HAVE_POPPLER_0_37
transition->setDuration( pdfTransition->durationReal() );
#else
transition->setDuration( pdfTransition->duration() );
#endif
switch ( pdfTransition->alignment() ) {
case Poppler::PageTransition::Horizontal:
transition->setAlignment( Okular::PageTransition::Horizontal );
break;
case Poppler::PageTransition::Vertical:
transition->setAlignment( Okular::PageTransition::Vertical );
break;
}
switch ( pdfTransition->direction() ) {
case Poppler::PageTransition::Inward:
transition->setDirection( Okular::PageTransition::Inward );
break;
case Poppler::PageTransition::Outward:
transition->setDirection( Okular::PageTransition::Outward );
break;
}
transition->setAngle( pdfTransition->angle() );
transition->setScale( pdfTransition->scale() );
transition->setIsRectangular( pdfTransition->isRectangular() );
page->setTransition( transition );
}
void PDFGenerator::addFormFields( Poppler::Page * popplerPage, Okular::Page * page )
{
QList popplerFormFields = popplerPage->formFields();
QLinkedList okularFormFields;
foreach( Poppler::FormField *f, popplerFormFields )
{
Okular::FormField * of = 0;
switch ( f->type() )
{
case Poppler::FormField::FormButton:
of = new PopplerFormFieldButton( static_cast( f ) );
break;
case Poppler::FormField::FormText:
of = new PopplerFormFieldText( static_cast( f ) );
break;
case Poppler::FormField::FormChoice:
of = new PopplerFormFieldChoice( static_cast( f ) );
break;
case Poppler::FormField::FormSignature: {
of = new PopplerFormFieldSignature( static_cast( f ) );
break;
}
default: ;
}
if ( of )
// form field created, good - it will take care of the Poppler::FormField
okularFormFields.append( of );
else
// no form field available - delete the Poppler::FormField
delete f;
}
if ( !okularFormFields.isEmpty() )
page->setFormFields( okularFormFields );
}
PDFGenerator::PrintError PDFGenerator::printError() const
{
return lastPrintError;
}
-QWidget* PDFGenerator::printConfigurationWidget() const
+Okular::PrintOptionsWidget* PDFGenerator::printConfigurationWidget() const
{
if ( !pdfOptionsPage )
{
const_cast(this)->pdfOptionsPage = new PDFOptionsPage();
}
return pdfOptionsPage;
}
bool PDFGenerator::supportsOption( SaveOption option ) const
{
switch ( option )
{
case SaveChanges:
{
return true;
}
default: ;
}
return false;
}
bool PDFGenerator::save( const QString &fileName, SaveOptions options, QString *errorText )
{
Q_UNUSED(errorText);
Poppler::PDFConverter *pdfConv = pdfdoc->pdfConverter();
pdfConv->setOutputFileName( fileName );
if ( options & SaveChanges )
pdfConv->setPDFOptions( pdfConv->pdfOptions() | Poppler::PDFConverter::WithChanges );
QMutexLocker locker( userMutex() );
QHashIterator it( annotationsOnOpenHash );
while ( it.hasNext() )
{
it.next();
if ( it.value()->uniqueName().isEmpty() )
{
it.value()->setUniqueName( it.key()->uniqueName() );
}
}
bool success = pdfConv->convert();
if (!success)
{
switch (pdfConv->lastError())
{
case Poppler::BaseConverter::NotSupportedInputFileError:
// This can only happen with Poppler before 0.22 which did not have qt5 version
break;
case Poppler::BaseConverter::NoError:
case Poppler::BaseConverter::FileLockedError:
// we can't get here
break;
case Poppler::BaseConverter::OpenOutputError:
// the default text message is good for this case
break;
}
}
delete pdfConv;
return success;
}
Okular::AnnotationProxy* PDFGenerator::annotationProxy() const
{
return annotProxy;
}
#include "generator_pdf.moc"
Q_LOGGING_CATEGORY(OkularPdfDebug, "org.kde.okular.generators.pdf", QtWarningMsg)
/* kate: replace-tabs on; indent-width 4; */
diff --git a/generators/poppler/generator_pdf.h b/generators/poppler/generator_pdf.h
index b168d045a..cc679d108 100644
--- a/generators/poppler/generator_pdf.h
+++ b/generators/poppler/generator_pdf.h
@@ -1,157 +1,158 @@
/***************************************************************************
* Copyright (C) 2004-2008 by Albert Astals Cid *
* Copyright (C) 2004 by Enrico Ros *
* Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group *
* company, info@kdab.com. Work sponsored by the *
* LiMux project of the city of Munich *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#ifndef _OKULAR_GENERATOR_PDF_H_
#define _OKULAR_GENERATOR_PDF_H_
//#include "synctex/synctex_parser.h"
#include
#include
#include
#include
#include
+#include
#include
#include
#include
namespace Okular {
class ObjectRect;
class SourceReference;
class SignatureInfo;
}
class PDFOptionsPage;
class PopplerAnnotationProxy;
/**
* @short A generator that builds contents from a PDF document.
*
* All Generator features are supported and implemented by this one.
* Internally this holds a reference to xpdf's core objects and provides
* contents generation using the PDFDoc object and a couple of OutputDevices
* called Okular::OutputDev and Okular::TextDev (both defined in gp_outputdev.h).
*
* For generating page contents we tell PDFDoc to render a page and grab
* contents from out OutputDevs when rendering finishes.
*
*/
class PDFGenerator : public Okular::Generator, public Okular::ConfigInterface, public Okular::PrintInterface, public Okular::SaveInterface
{
Q_OBJECT
Q_INTERFACES( Okular::Generator )
Q_INTERFACES( Okular::ConfigInterface )
Q_INTERFACES( Okular::PrintInterface )
Q_INTERFACES( Okular::SaveInterface )
public:
PDFGenerator( QObject *parent, const QVariantList &args );
virtual ~PDFGenerator();
// [INHERITED] load a document and fill up the pagesVector
Okular::Document::OpenResult loadDocumentWithPassword( const QString & fileName, QVector & pagesVector, const QString & password ) override;
Okular::Document::OpenResult loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector & pagesVector, const QString & password ) override;
void loadPages(QVector &pagesVector, int rotation=-1, bool clear=false);
// [INHERITED] document information
Okular::DocumentInfo generateDocumentInfo( const QSet &keys ) const override;
const Okular::DocumentSynopsis * generateDocumentSynopsis() override;
Okular::FontInfo::List fontsForPage( int page ) override;
const QList * embeddedFiles() const override;
PageSizeMetric pagesSizeMetric() const override{ return Pixels; }
QAbstractItemModel * layersModel() const override;
void opaqueAction( const Okular::BackendOpaqueAction *action ) override;
// [INHERITED] document information
bool isAllowed( Okular::Permission permission ) const override;
// [INHERITED] perform actions on document / pages
QImage image( Okular::PixmapRequest *page ) override;
// [INHERITED] print page using an already configured kprinter
bool print( QPrinter& printer ) override;
// [INHERITED] reply to some metadata requests
QVariant metaData( const QString & key, const QVariant & option ) const override;
// [INHERITED] reparse configuration
bool reparseConfig() override;
void addPages( KConfigDialog * ) override;
// [INHERITED] text exporting
Okular::ExportFormat::List exportFormats() const override;
bool exportTo( const QString &fileName, const Okular::ExportFormat &format ) override;
// [INHERITED] print interface
- QWidget* printConfigurationWidget() const override;
+ Okular::PrintOptionsWidget* printConfigurationWidget() const override;
// [INHERITED] save interface
bool supportsOption( SaveOption ) const override;
bool save( const QString &fileName, SaveOptions options, QString *errorText ) override;
Okular::AnnotationProxy* annotationProxy() const override;
protected:
SwapBackingFileResult swapBackingFile( QString const &newFileName, QVector & newPagesVector ) override;
bool doCloseDocument() override;
Okular::TextPage* textPage( Okular::TextRequest *request ) override;
protected Q_SLOTS:
void requestFontData(const Okular::FontInfo &font, QByteArray *data);
Okular::Generator::PrintError printError() const;
private:
Okular::Document::OpenResult init(QVector & pagesVector, const QString &password);
// create the document synopsis hierarchy
void addSynopsisChildren( QDomNode * parentSource, QDomNode * parentDestination );
// fetch annotations from the pdf file and add they to the page
void addAnnotations( Poppler::Page * popplerPage, Okular::Page * page );
// fetch the transition information and add it to the page
void addTransition( Poppler::Page * popplerPage, Okular::Page * page );
// fetch the form fields and add them to the page
void addFormFields( Poppler::Page * popplerPage, Okular::Page * page );
Okular::TextPage * abstractTextPage(const QList &text, double height, double width, int rot);
void resolveMediaLinkReferences( Okular::Page *page );
void resolveMediaLinkReference( Okular::Action *action );
bool setDocumentRenderHints();
// poppler dependent stuff
Poppler::Document *pdfdoc;
// misc variables for document info and synopsis caching
bool docSynopsisDirty;
Okular::DocumentSynopsis docSyn;
mutable bool docEmbeddedFilesDirty;
mutable QList docEmbeddedFiles;
int nextFontPage;
PopplerAnnotationProxy *annotProxy;
// the hash below only contains annotations that were present on the file at open time
// this is enough for what we use it for
QHash annotationsOnOpenHash;
QBitArray rectsGenerated;
QPointer pdfOptionsPage;
PrintError lastPrintError;
};
#endif
/* kate: replace-tabs on; indent-width 4; */
diff --git a/interfaces/printinterface.h b/interfaces/printinterface.h
index ac51272f8..334deda6e 100644
--- a/interfaces/printinterface.h
+++ b/interfaces/printinterface.h
@@ -1,60 +1,64 @@
/***************************************************************************
* Copyright (C) 2007 by Pino Toscano *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#ifndef _OKULAR_PRINTINTERFACE_H_
#define _OKULAR_PRINTINTERFACE_H_
#include "../core/okularcore_export.h"
#include
class QWidget;
namespace Okular {
/**
* @short Abstract interface for advanced printing control
*
* This interface defines an advanced way of interfacing with the print
* process.
*
* How to use it in a custom Generator:
* @code
class MyGenerator : public Okular::Generator, public Okular::PrintInterface
{
Q_OBJECT
Q_INTERFACES( Okular::PrintInterface )
...
};
* @endcode
* and - of course - implementing its methods.
*/
class OKULARCORE_EXPORT PrintInterface
{
public:
/**
* Destroys the printer interface.
*/
virtual ~PrintInterface() {}
/**
* Builds and returns a new printing configuration widget.
*
* @note don't keep a pointer to the new constructed widget, as it
* will be handled elsewhere (in the Okular KPart)
+ *
+ * @note The returned object should be of a PrintOptionsWidget subclass
+ * (which is not officially enforced by the signature for binary
+ * compatibility reasons).
*/
virtual QWidget* printConfigurationWidget() const = 0;
};
}
Q_DECLARE_INTERFACE( Okular::PrintInterface, "org.kde.okular.PrintInterface/0.1" )
#endif
diff --git a/part.cpp b/part.cpp
index 4ef3d0f43..869ee8a40 100644
--- a/part.cpp
+++ b/part.cpp
@@ -1,3678 +1,3696 @@
/***************************************************************************
* Copyright (C) 2002 by Wilco Greven *
* Copyright (C) 2002 by Chris Cheney *
* Copyright (C) 2002 by Malcolm Hunter *
* Copyright (C) 2003-2004 by Christophe Devriese *
* *
* Copyright (C) 2003 by Daniel Molkentin *
* Copyright (C) 2003 by Andy Goossens *
* Copyright (C) 2003 by Dirk Mueller *
* Copyright (C) 2003 by Laurent Montel *
* Copyright (C) 2004 by Dominique Devriese *
* Copyright (C) 2004 by Christoph Cullmann *
* Copyright (C) 2004 by Henrique Pinto *
* Copyright (C) 2004 by Waldo Bastian *
* Copyright (C) 2004-2008 by Albert Astals Cid *
* Copyright (C) 2004 by Antti Markus *
* Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group *
* company, info@kdab.com. Work sponsored by the *
* LiMux project of the city of Munich *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "part.h"
// qt/kde includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef WITH_KWALLET
#include
#endif
#include
#include
#if PURPOSE_FOUND
#include
#include
#endif
#if 0
#include
#endif
// local includes
#include "aboutdata.h"
#include "extensions.h"
#include "ui/debug_ui.h"
#include "ui/drawingtoolactions.h"
#include "ui/pageview.h"
#include "ui/toc.h"
#include "ui/searchwidget.h"
#include "ui/thumbnaillist.h"
#include "ui/side_reviews.h"
#include "ui/minibar.h"
#include "ui/embeddedfilesdialog.h"
#include "ui/propertiesdialog.h"
#include "ui/presentationwidget.h"
#include "ui/pagesizelabel.h"
#include "ui/bookmarklist.h"
#include "ui/findbar.h"
#include "ui/sidebar.h"
#include "ui/fileprinterpreview.h"
#include "ui/guiutils.h"
#include "ui/layers.h"
#include "ui/okmenutitle.h"
#include "ui/signaturepanel.h"
#include "conf/preferencesdialog.h"
#include "settings.h"
#include "core/action.h"
#include "core/annotations.h"
#include "core/bookmarkmanager.h"
#include "core/document.h"
#include "core/document_p.h"
#include "core/generator.h"
#include "core/page.h"
#include "core/fileprinter.h"
+#include "core/printoptionswidget.h"
#include
#ifdef OKULAR_KEEP_FILE_OPEN
class FileKeeper
{
public:
FileKeeper()
: m_handle( nullptr )
{
}
~FileKeeper()
{
}
void open( const QString & path )
{
if ( !m_handle )
m_handle = std::fopen( QFile::encodeName( path ).constData(), "r" );
}
void close()
{
if ( m_handle )
{
int ret = std::fclose( m_handle );
Q_UNUSED( ret )
m_handle = nullptr;
}
}
QTemporaryFile* copyToTemporary() const
{
if ( !m_handle )
return nullptr;
QTemporaryFile * retFile = new QTemporaryFile;
retFile->open();
std::rewind( m_handle );
int c = -1;
do
{
c = std::fgetc( m_handle );
if ( c == EOF )
break;
if ( !retFile->putChar( (char)c ) )
break;
} while ( !feof( m_handle ) );
retFile->flush();
return retFile;
}
private:
std::FILE * m_handle;
};
#endif
K_PLUGIN_FACTORY(OkularPartFactory, registerPlugin();)
static QAction* actionForExportFormat( const Okular::ExportFormat& format, QObject *parent = Q_NULLPTR )
{
QAction *act = new QAction( format.description(), parent );
if ( !format.icon().isNull() )
{
act->setIcon( format.icon() );
}
return act;
}
static KFilterDev::CompressionType compressionTypeFor( const QString& mime_to_check )
{
// The compressedMimeMap is here in case you have a very old shared mime database
// that doesn't have inheritance info for things like gzeps, etc
// Otherwise the "is()" calls below are just good enough
static QHash< QString, KFilterDev::CompressionType > compressedMimeMap;
static bool supportBzip = false;
static bool supportXz = false;
const QString app_gzip( QStringLiteral( "application/x-gzip" ) );
const QString app_bzip( QStringLiteral( "application/x-bzip" ) );
const QString app_xz( QStringLiteral( "application/x-xz" ) );
if ( compressedMimeMap.isEmpty() )
{
std::unique_ptr< KFilterBase > f;
compressedMimeMap[ QLatin1String( "image/x-gzeps" ) ] = KFilterDev::GZip;
// check we can read bzip2-compressed files
f.reset( KCompressionDevice::filterForCompressionType( KCompressionDevice::BZip2 ) );
if ( f.get() )
{
supportBzip = true;
compressedMimeMap[ QLatin1String( "application/x-bzpdf" ) ] = KFilterDev::BZip2;
compressedMimeMap[ QLatin1String( "application/x-bzpostscript" ) ] = KFilterDev::BZip2;
compressedMimeMap[ QLatin1String( "application/x-bzdvi" ) ] = KFilterDev::BZip2;
compressedMimeMap[ QLatin1String( "image/x-bzeps" ) ] = KFilterDev::BZip2;
}
// check if we can read XZ-compressed files
f.reset( KCompressionDevice::filterForCompressionType( KCompressionDevice::Xz ) );
if ( f.get() )
{
supportXz = true;
}
}
QHash< QString, KFilterDev::CompressionType >::const_iterator it = compressedMimeMap.constFind( mime_to_check );
if ( it != compressedMimeMap.constEnd() )
return it.value();
QMimeDatabase db;
QMimeType mime = db.mimeTypeForName( mime_to_check );
if ( mime.isValid() )
{
if ( mime.inherits( app_gzip ) )
return KFilterDev::GZip;
else if ( supportBzip && mime.inherits( app_bzip ) )
return KFilterDev::BZip2;
else if ( supportXz && mime.inherits( app_xz ) )
return KFilterDev::Xz;
}
return KFilterDev::None;
}
static Okular::EmbedMode detectEmbedMode( QWidget *parentWidget, QObject *parent, const QVariantList &args )
{
Q_UNUSED( parentWidget );
if ( parent
&& ( parent->objectName().startsWith( QLatin1String( "okular::Shell" ) )
|| parent->objectName().startsWith( QLatin1String( "okular/okular__Shell" ) ) ) )
return Okular::NativeShellMode;
if ( parent
&& ( QByteArray( "KHTMLPart" ) == parent->metaObject()->className() ) )
return Okular::KHTMLPartMode;
Q_FOREACH ( const QVariant &arg, args )
{
if ( arg.type() == QVariant::String )
{
if ( arg.toString() == QLatin1String( "Print/Preview" ) )
{
return Okular::PrintPreviewMode;
}
else if ( arg.toString() == QLatin1String( "ViewerWidget" ) )
{
return Okular::ViewerWidgetMode;
}
}
}
return Okular::UnknownEmbedMode;
}
static QString detectConfigFileName( const QVariantList &args )
{
Q_FOREACH ( const QVariant &arg, args )
{
if ( arg.type() == QVariant::String )
{
QString argString = arg.toString();
int separatorIndex = argString.indexOf( QStringLiteral("=") );
if ( separatorIndex >= 0 && argString.left( separatorIndex ) == QLatin1String( "ConfigFileName" ) )
{
return argString.mid( separatorIndex + 1 );
}
}
}
return QString();
}
#undef OKULAR_KEEP_FILE_OPEN
#ifdef OKULAR_KEEP_FILE_OPEN
static bool keepFileOpen()
{
static bool keep_file_open = !qgetenv("OKULAR_NO_KEEP_FILE_OPEN").toInt();
return keep_file_open;
}
#endif
int Okular::Part::numberOfParts = 0;
namespace Okular
{
Part::Part(QWidget *parentWidget,
QObject *parent,
const QVariantList &args)
: KParts::ReadWritePart(parent),
m_tempfile( nullptr ), m_documentOpenWithPassword( false ), m_swapInsteadOfOpening( false ), m_isReloading( false ), m_fileWasRemoved( false ), m_showMenuBarAction( nullptr ), m_showFullScreenAction( nullptr ), m_actionsSearched( false ),
m_cliPresentation(false), m_cliPrint(false), m_cliPrintAndExit(false), m_embedMode(detectEmbedMode(parentWidget, parent, args)), m_generatorGuiClient(nullptr), m_keeper( nullptr )
{
// make sure that the component name is okular otherwise the XMLGUI .rc files are not found
// when this part is used in an application other than okular (e.g. unit tests)
setComponentName(QStringLiteral("okular"), QString());
const QLatin1String configFileName("okularpartrc");
// first, we check if a config file name has been specified
QString configFilePath = detectConfigFileName( args );
if ( configFilePath.isEmpty() )
{
configFilePath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1Char('/') + configFileName;
}
// Migrate old config
if ( !QFile::exists( configFilePath ) ) {
qCDebug(OkularUiDebug) << "Did not find a config file, attempting to look for old config";
// Migrate old config + UI
Kdelibs4ConfigMigrator configMigrator( componentName() );
// UI file is handled automatically, we only need to specify config name because we're a part
configMigrator.setConfigFiles( QStringList( configFileName ) );
// If there's no old okular config to migrate, look for kpdf
if ( !configMigrator.migrate() ) {
qCDebug(OkularUiDebug) << "Did not find an old okular config file, attempting to look for kpdf config";
// First try the automatic detection, using $KDEHOME etc.
Kdelibs4Migration migration;
QString kpdfConfig = migration.locateLocal( "config", QStringLiteral("kpdfpartrc") );
// Fallback just in case it tried e. g. ~/.kde4
if ( kpdfConfig.isEmpty() ) {
kpdfConfig = QDir::homePath() + QStringLiteral("/.kde/share/config/kpdfpartrc");
}
if ( QFile::exists( kpdfConfig ) ) {
qCDebug(OkularUiDebug) << "Found old kpdf config" << kpdfConfig << "copying to" << configFilePath;
QFile::copy( kpdfConfig, configFilePath );
} else {
qCDebug(OkularUiDebug) << "Did not find an old kpdf config file";
}
} else {
qCDebug(OkularUiDebug) << "Migrated old okular config";
}
}
Okular::Settings::instance( configFilePath );
numberOfParts++;
if (numberOfParts == 1) {
m_registerDbusName = QStringLiteral("/okular");
} else {
m_registerDbusName = QStringLiteral("/okular%1").arg(numberOfParts);
}
QDBusConnection::sessionBus().registerObject(m_registerDbusName, this, QDBusConnection::ExportScriptableSlots);
// connect the started signal to tell the job the mimetypes we like,
// and get some more information from it
connect(this, &KParts::ReadOnlyPart::started, this, &Part::slotJobStarted);
// connect the completed signal so we can put the window caption when loading remote files
connect(this, SIGNAL(completed()), this, SLOT(setWindowTitleFromDocument()));
connect(this, &KParts::ReadOnlyPart::canceled, this, &Part::loadCancelled);
// create browser extension (for printing when embedded into browser)
m_bExtension = new BrowserExtension(this);
// create live connect extension (for integrating with browser scripting)
new OkularLiveConnectExtension( this );
GuiUtils::addIconLoader( iconLoader() );
m_sidebar = new Sidebar( parentWidget );
setWidget( m_sidebar );
connect( m_sidebar, &Sidebar::urlsDropped, this, &Part::handleDroppedUrls );
// build the document
m_document = new Okular::Document(widget());
connect( m_document, &Document::linkFind, this, &Part::slotFind );
connect( m_document, &Document::linkGoToPage, this, &Part::slotGoToPage );
connect( m_document, &Document::linkPresentation, this, &Part::slotShowPresentation );
connect( m_document, &Document::linkEndPresentation, this, &Part::slotHidePresentation );
connect( m_document, &Document::openUrl, this, &Part::openUrlFromDocument );
connect( m_document->bookmarkManager(), &BookmarkManager::openUrl, this, &Part::openUrlFromBookmarks );
connect( m_document, &Document::close, this, &Part::close );
connect( m_document, &Document::undoHistoryCleanChanged, this,
[this](bool clean)
{
setModified( !clean );
setWindowTitleFromDocument();
}
);
if ( parent && parent->metaObject()->indexOfSlot( QMetaObject::normalizedSignature( "slotQuit()" ).constData() ) != -1 )
connect( m_document, SIGNAL(quit()), parent, SLOT(slotQuit()) );
else
connect( m_document, &Document::quit, this, &Part::cannotQuit );
// widgets: ^searchbar (toolbar containing label and SearchWidget)
// m_searchToolBar = new KToolBar( parentWidget, "searchBar" );
// m_searchToolBar->boxLayout()->setSpacing( KDialog::spacingHint() );
// QLabel * sLabel = new QLabel( i18n( "&Search:" ), m_searchToolBar, "kde toolbar widget" );
// m_searchWidget = new SearchWidget( m_searchToolBar, m_document );
// sLabel->setBuddy( m_searchWidget );
// m_searchToolBar->setStretchableWidget( m_searchWidget );
// [left toolbox: Table of Contents] | []
m_toc = new TOC( nullptr, m_document );
connect( m_toc.data(), &TOC::hasTOC, this, &Part::enableTOC );
connect( m_toc.data(), &TOC::rightClick, this, &Part::slotShowTOCMenu );
m_sidebar->addItem( m_toc, QIcon::fromTheme(QApplication::isLeftToRight() ? QStringLiteral("format-justify-left") : QStringLiteral("format-justify-right")), i18n("Contents") );
enableTOC( false );
// [left toolbox: Layers] | []
m_layers = new Layers( nullptr, m_document );
connect( m_layers.data(), &Layers::hasLayers, this, &Part::enableLayers );
m_sidebar->addItem( m_layers, QIcon::fromTheme( QStringLiteral("format-list-unordered") ), i18n( "Layers" ) );
enableLayers( false );
// [left toolbox: Thumbnails and Bookmarks] | []
QWidget * thumbsBox = new ThumbnailsBox( nullptr );
thumbsBox->layout()->setSpacing( 6 );
m_searchWidget = new SearchWidget( thumbsBox, m_document );
thumbsBox->layout()->addWidget(m_searchWidget);
m_thumbnailList = new ThumbnailList( thumbsBox, m_document );
thumbsBox->layout()->addWidget(m_thumbnailList);
// ThumbnailController * m_tc = new ThumbnailController( thumbsBox, m_thumbnailList );
connect( m_thumbnailList.data(), &ThumbnailList::rightClick, this, &Part::slotShowMenu );
m_sidebar->addItem( thumbsBox, QIcon::fromTheme( QStringLiteral("view-preview") ), i18n("Thumbnails") );
m_sidebar->setCurrentItem( thumbsBox );
// [left toolbox: Reviews] | []
m_reviewsWidget = new Reviews( nullptr, m_document );
m_sidebar->addItem( m_reviewsWidget, QIcon::fromTheme(QStringLiteral("draw-freehand")), i18n("Reviews") );
m_sidebar->setItemEnabled( m_reviewsWidget, false );
// [left toolbox: Bookmarks] | []
m_bookmarkList = new BookmarkList( m_document, nullptr );
m_sidebar->addItem( m_bookmarkList, QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks") );
m_sidebar->setItemEnabled( m_bookmarkList, false );
// [left toolbox: Signature Panel] | []
m_signaturePanel = new SignaturePanel( m_document, nullptr );
connect( m_signaturePanel.data(), &SignaturePanel::documentHasSignatures, this, &Part::showSidebarSignaturesItem );
m_sidebar->addItem( m_signaturePanel, QIcon::fromTheme(QStringLiteral("application-pkcs7-signature")), i18n("Signatures") );
showSidebarSignaturesItem( false );
// widgets: [../miniBarContainer] | []
#ifdef OKULAR_ENABLE_MINIBAR
QWidget * miniBarContainer = new QWidget( 0 );
m_sidebar->setBottomWidget( miniBarContainer );
QVBoxLayout * miniBarLayout = new QVBoxLayout( miniBarContainer );
miniBarLayout->setMargin( 0 );
// widgets: [../[spacer/..]] | []
miniBarLayout->addItem( new QSpacerItem( 6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed ) );
// widgets: [../[../MiniBar]] | []
QFrame * bevelContainer = new QFrame( miniBarContainer );
bevelContainer->setFrameStyle( QFrame::StyledPanel | QFrame::Sunken );
QVBoxLayout * bevelContainerLayout = new QVBoxLayout( bevelContainer );
bevelContainerLayout->setMargin( 4 );
m_progressWidget = new ProgressWidget( bevelContainer, m_document );
bevelContainerLayout->addWidget( m_progressWidget );
miniBarLayout->addWidget( bevelContainer );
miniBarLayout->addItem( new QSpacerItem( 6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed ) );
#endif
// widgets: [] | [right 'pageView']
QWidget * rightContainer = new QWidget( nullptr );
m_sidebar->setMainWidget( rightContainer );
QVBoxLayout * rightLayout = new QVBoxLayout( rightContainer );
rightLayout->setMargin( 0 );
rightLayout->setSpacing( 0 );
// KToolBar * rtb = new KToolBar( rightContainer, "mainToolBarSS" );
// rightLayout->addWidget( rtb );
m_migrationMessage = new KMessageWidget( rightContainer );
m_migrationMessage->setVisible( false );
m_migrationMessage->setWordWrap( true );
m_migrationMessage->setMessageType( KMessageWidget::Warning );
m_migrationMessage->setText( i18n( "This document contains annotations or form data that were saved internally by a previous Okular version. Internal storage is no longer supported.
Please save to a file in order to move them if you want to continue to edit the document." ) );
rightLayout->addWidget( m_migrationMessage );
m_topMessage = new KMessageWidget( rightContainer );
m_topMessage->setVisible( false );
m_topMessage->setWordWrap( true );
m_topMessage->setMessageType( KMessageWidget::Information );
m_topMessage->setText( i18n( "This document has embedded files. Click here to see them or go to File -> Embedded Files." ) );
m_topMessage->setIcon( QIcon::fromTheme( QStringLiteral("mail-attachment") ) );
connect( m_topMessage, &KMessageWidget::linkActivated, this, &Part::slotShowEmbeddedFiles );
rightLayout->addWidget( m_topMessage );
m_formsMessage = new KMessageWidget( rightContainer );
m_formsMessage->setVisible( false );
m_formsMessage->setWordWrap( true );
m_formsMessage->setMessageType( KMessageWidget::Information );
rightLayout->addWidget( m_formsMessage );
m_infoMessage = new KMessageWidget( rightContainer );
m_infoMessage->setVisible( false );
m_infoMessage->setWordWrap( true );
m_infoMessage->setMessageType( KMessageWidget::Information );
rightLayout->addWidget( m_infoMessage );
m_infoTimer = new QTimer();
m_infoTimer->setSingleShot( true );
connect( m_infoTimer, &QTimer::timeout, m_infoMessage, &KMessageWidget::animatedHide );
m_signatureMessage = new KMessageWidget( rightContainer );
m_signatureMessage->setVisible( false );
m_signatureMessage->setWordWrap( true );
m_signatureMessage->setMessageType( KMessageWidget::Information );
rightLayout->addWidget( m_signatureMessage );
m_pageView = new PageView( rightContainer, m_document );
QMetaObject::invokeMethod( m_pageView, "setFocus", Qt::QueuedConnection ); //usability setting
// m_splitter->setFocusProxy(m_pageView);
connect( m_pageView.data(), &PageView::rightClick, this, &Part::slotShowMenu );
connect( m_document, &Document::error, this, &Part::errorMessage );
connect( m_document, &Document::warning, this, &Part::warningMessage );
connect( m_document, &Document::notice, this, &Part::noticeMessage );
connect( m_document, &Document::sourceReferenceActivated, this, &Part::slotHandleActivatedSourceReference );
connect( m_pageView.data(), &PageView::fitWindowToPage, this, &Part::fitWindowToPage );
rightLayout->addWidget( m_pageView );
m_layers->setPageView( m_pageView );
m_signaturePanel->setPageView( m_pageView );
m_findBar = new FindBar( m_document, rightContainer );
rightLayout->addWidget( m_findBar );
m_bottomBar = new QWidget( rightContainer );
QHBoxLayout * bottomBarLayout = new QHBoxLayout( m_bottomBar );
m_pageSizeLabel = new PageSizeLabel( m_bottomBar, m_document );
bottomBarLayout->setMargin( 0 );
bottomBarLayout->setSpacing( 0 );
bottomBarLayout->addItem( new QSpacerItem( 5, 5, QSizePolicy::Expanding, QSizePolicy::Minimum ) );
m_miniBarLogic = new MiniBarLogic( this, m_document );
m_miniBar = new MiniBar( m_bottomBar, m_miniBarLogic );
bottomBarLayout->addWidget( m_miniBar );
bottomBarLayout->addWidget( m_pageSizeLabel );
rightLayout->addWidget( m_bottomBar );
m_pageNumberTool = new MiniBar( nullptr, m_miniBarLogic );
connect( m_findBar, SIGNAL(forwardKeyPressEvent(QKeyEvent*)), m_pageView, SLOT(externalKeyPressEvent(QKeyEvent*)));
connect( m_findBar, SIGNAL(onCloseButtonPressed()), m_pageView, SLOT(setFocus()));
connect( m_miniBar, SIGNAL(forwardKeyPressEvent(QKeyEvent*)), m_pageView, SLOT(externalKeyPressEvent(QKeyEvent*)));
connect( m_pageView.data(), &PageView::escPressed, m_findBar, &FindBar::resetSearch );
connect( m_pageNumberTool, SIGNAL(forwardKeyPressEvent(QKeyEvent*)), m_pageView, SLOT(externalKeyPressEvent(QKeyEvent*)));
connect( m_reviewsWidget.data(), &Reviews::openAnnotationWindow,
m_pageView.data(), &PageView::openAnnotationWindow );
// add document observers
m_document->addObserver( this );
m_document->addObserver( m_thumbnailList );
m_document->addObserver( m_pageView );
m_document->registerView( m_pageView );
m_document->addObserver( m_toc );
m_document->addObserver( m_miniBarLogic );
#ifdef OKULAR_ENABLE_MINIBAR
m_document->addObserver( m_progressWidget );
#endif
m_document->addObserver( m_reviewsWidget );
m_document->addObserver( m_pageSizeLabel );
m_document->addObserver( m_bookmarkList );
m_document->addObserver( m_signaturePanel );
connect( m_document->bookmarkManager(), &BookmarkManager::saved,
this, &Part::slotRebuildBookmarkMenu );
setupViewerActions();
if ( m_embedMode != ViewerWidgetMode )
{
setupActions();
}
else
{
setViewerShortcuts();
}
// document watcher and reloader
m_watcher = new KDirWatch( this );
connect( m_watcher, &KDirWatch::dirty, this, &Part::slotFileDirty );
connect( m_watcher, &KDirWatch::created, this, &Part::slotFileDirty );
connect( m_watcher, &KDirWatch::deleted, this, &Part::slotFileDirty );
m_dirtyHandler = new QTimer( this );
m_dirtyHandler->setSingleShot( true );
connect( m_dirtyHandler, &QTimer::timeout, this, [this] { slotAttemptReload(); } );
slotNewConfig();
// keep us informed when the user changes settings
connect( Okular::Settings::self(), &KCoreConfigSkeleton::configChanged, this, &Part::slotNewConfig );
#ifdef HAVE_SPEECH
// [SPEECH] check for TTS presence and usability
Okular::Settings::setUseTTS( true );
Okular::Settings::self()->save();
#endif
rebuildBookmarkMenu( false );
if ( m_embedMode == ViewerWidgetMode ) {
// set the XML-UI resource file for the viewer mode
setXMLFile(QStringLiteral("part-viewermode.rc"));
}
else
{
// set our main XML-UI resource file
setXMLFile(QStringLiteral("part.rc"));
}
m_pageView->setupBaseActions( actionCollection() );
m_sidebar->setSidebarVisibility( false );
if ( m_embedMode != PrintPreviewMode )
{
// now set up actions that are required for all remaining modes
m_pageView->setupViewerActions( actionCollection() );
// and if we are not in viewer mode, we want the full GUI
if ( m_embedMode != ViewerWidgetMode )
{
unsetDummyMode();
}
}
// ensure history actions are in the correct state
updateViewActions();
// also update the state of the actions in the page view
m_pageView->updateActionState( false, false, false );
if ( m_embedMode == NativeShellMode )
m_sidebar->setAutoFillBackground( false );
#ifdef OKULAR_KEEP_FILE_OPEN
m_keeper = new FileKeeper();
#endif
}
void Part::setupViewerActions()
{
// ACTIONS
KActionCollection * ac = actionCollection();
// Page Traversal actions
m_gotoPage = KStandardAction::gotoPage( this, SLOT(slotGoToPage()), ac );
ac->setDefaultShortcuts(m_gotoPage, KStandardShortcut::gotoLine());
// dirty way to activate gotopage when pressing miniBar's button
connect( m_miniBar.data(), &MiniBar::gotoPage, m_gotoPage, &QAction::trigger );
connect( m_pageNumberTool.data(), &MiniBar::gotoPage, m_gotoPage, &QAction::trigger );
m_prevPage = KStandardAction::prior(this, SLOT(slotPreviousPage()), ac);
m_prevPage->setIconText( i18nc( "Previous page", "Previous" ) );
m_prevPage->setToolTip( i18n( "Go back to the Previous Page" ) );
m_prevPage->setWhatsThis( i18n( "Moves to the previous page of the document" ) );
ac->setDefaultShortcut(m_prevPage, QKeySequence());
// dirty way to activate prev page when pressing miniBar's button
connect( m_miniBar.data(), &MiniBar::prevPage, m_prevPage, &QAction::trigger );
connect( m_pageNumberTool.data(), &MiniBar::prevPage, m_prevPage, &QAction::trigger );
#ifdef OKULAR_ENABLE_MINIBAR
connect( m_progressWidget, SIGNAL(prevPage()), m_prevPage, SLOT(trigger()) );
#endif
m_nextPage = KStandardAction::next(this, SLOT(slotNextPage()), ac );
m_nextPage->setIconText( i18nc( "Next page", "Next" ) );
m_nextPage->setToolTip( i18n( "Advance to the Next Page" ) );
m_nextPage->setWhatsThis( i18n( "Moves to the next page of the document" ) );
ac->setDefaultShortcut(m_nextPage, QKeySequence());
// dirty way to activate next page when pressing miniBar's button
connect( m_miniBar.data(), &MiniBar::nextPage, m_nextPage, &QAction::trigger );
connect( m_pageNumberTool.data(), &MiniBar::nextPage, m_nextPage, &QAction::trigger );
#ifdef OKULAR_ENABLE_MINIBAR
connect( m_progressWidget, SIGNAL(nextPage()), m_nextPage, SLOT(trigger()) );
#endif
m_beginningOfDocument = KStandardAction::firstPage( this, SLOT(slotGotoFirst()), ac );
ac->addAction(QStringLiteral("first_page"), m_beginningOfDocument);
m_beginningOfDocument->setText(i18n( "Beginning of the document"));
m_beginningOfDocument->setWhatsThis( i18n( "Moves to the beginning of the document" ) );
m_endOfDocument = KStandardAction::lastPage( this, SLOT(slotGotoLast()), ac );
ac->addAction(QStringLiteral("last_page"),m_endOfDocument);
m_endOfDocument->setText(i18n( "End of the document"));
m_endOfDocument->setWhatsThis( i18n( "Moves to the end of the document" ) );
// we do not want back and next in history in the dummy mode
m_historyBack = nullptr;
m_historyNext = nullptr;
m_addBookmark = KStandardAction::addBookmark( this, SLOT(slotAddBookmark()), ac );
m_addBookmarkText = m_addBookmark->text();
m_addBookmarkIcon = m_addBookmark->icon();
m_renameBookmark = ac->addAction(QStringLiteral("rename_bookmark"));
m_renameBookmark->setText(i18n( "Rename Bookmark" ));
m_renameBookmark->setIcon(QIcon::fromTheme( QStringLiteral("edit-rename") ));
m_renameBookmark->setWhatsThis( i18n( "Rename the current bookmark" ) );
connect( m_renameBookmark, &QAction::triggered, this, &Part::slotRenameCurrentViewportBookmark );
m_prevBookmark = ac->addAction(QStringLiteral("previous_bookmark"));
m_prevBookmark->setText(i18n( "Previous Bookmark" ));
m_prevBookmark->setIcon(QIcon::fromTheme( QStringLiteral("go-up-search") ));
m_prevBookmark->setWhatsThis( i18n( "Go to the previous bookmark" ) );
connect( m_prevBookmark, &QAction::triggered, this, &Part::slotPreviousBookmark );
m_nextBookmark = ac->addAction(QStringLiteral("next_bookmark"));
m_nextBookmark->setText(i18n( "Next Bookmark" ));
m_nextBookmark->setIcon(QIcon::fromTheme( QStringLiteral("go-down-search") ));
m_nextBookmark->setWhatsThis( i18n( "Go to the next bookmark" ) );
connect( m_nextBookmark, &QAction::triggered, this, &Part::slotNextBookmark );
m_copy = nullptr;
m_selectAll = nullptr;
// Find and other actions
m_find = KStandardAction::find( this, SLOT(slotShowFindBar()), ac );
QList s = m_find->shortcuts();
s.append( QKeySequence( Qt::Key_Slash ) );
ac->setDefaultShortcuts(m_find, s);
m_find->setEnabled( false );
m_findNext = KStandardAction::findNext( this, SLOT(slotFindNext()), ac);
m_findNext->setEnabled( false );
m_findPrev = KStandardAction::findPrev( this, SLOT(slotFindPrev()), ac );
m_findPrev->setEnabled( false );
m_save = nullptr;
m_saveAs = nullptr;
QAction * prefs = KStandardAction::preferences( this, SLOT(slotPreferences()), ac);
if ( m_embedMode == NativeShellMode )
{
prefs->setText( i18n( "Configure Okular..." ) );
}
else
{
// TODO: improve this message
prefs->setText( i18n( "Configure Viewer..." ) );
}
QAction * genPrefs = new QAction( ac );
ac->addAction(QStringLiteral("options_configure_generators"), genPrefs);
if ( m_embedMode == ViewerWidgetMode )
{
genPrefs->setText( i18n( "Configure Viewer Backends..." ) );
}
else
{
genPrefs->setText( i18n( "Configure Backends..." ) );
}
genPrefs->setIcon( QIcon::fromTheme( QStringLiteral("configure") ) );
genPrefs->setEnabled( m_document->configurableGenerators() > 0 );
connect( genPrefs, &QAction::triggered, this, &Part::slotGeneratorPreferences );
m_printPreview = KStandardAction::printPreview( this, SLOT(slotPrintPreview()), ac );
m_printPreview->setEnabled( false );
m_showLeftPanel = nullptr;
m_showBottomBar = nullptr;
m_showSignaturePanel = nullptr;
m_showProperties = ac->addAction(QStringLiteral("properties"));
m_showProperties->setText(i18n("&Properties"));
m_showProperties->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
connect(m_showProperties, &QAction::triggered, this, &Part::slotShowProperties);
m_showProperties->setEnabled( false );
m_showEmbeddedFiles = nullptr;
m_showPresentation = nullptr;
m_exportAs = nullptr;
m_exportAsMenu = nullptr;
m_exportAsText = nullptr;
m_exportAsDocArchive = nullptr;
#if PURPOSE_FOUND
m_share = nullptr;
m_shareMenu = nullptr;
#endif
m_presentationDrawingActions = nullptr;
m_aboutBackend = ac->addAction(QStringLiteral("help_about_backend"));
m_aboutBackend->setText(i18n("About Backend"));
m_aboutBackend->setEnabled( false );
connect(m_aboutBackend, &QAction::triggered, this, &Part::slotAboutBackend);
QAction *reload = ac->add( QStringLiteral("file_reload") );
reload->setText( i18n( "Reloa&d" ) );
reload->setIcon( QIcon::fromTheme( QStringLiteral("view-refresh") ) );
reload->setWhatsThis( i18n( "Reload the current document from disk." ) );
connect( reload, &QAction::triggered, this, &Part::slotReload );
ac->setDefaultShortcuts(reload, KStandardShortcut::reload());
m_reload = reload;
m_closeFindBar = ac->addAction( QStringLiteral("close_find_bar"), this, SLOT(slotHideFindBar()) );
m_closeFindBar->setText( i18n("Close &Find Bar") );
ac->setDefaultShortcut(m_closeFindBar, QKeySequence(Qt::Key_Escape));
m_closeFindBar->setEnabled( false );
QWidgetAction *pageno = new QWidgetAction( ac );
pageno->setText( i18n( "Page Number" ) );
pageno->setDefaultWidget( m_pageNumberTool );
ac->addAction( QStringLiteral("page_number"), pageno );
}
void Part::setViewerShortcuts()
{
KActionCollection * ac = actionCollection();
ac->setDefaultShortcut(m_gotoPage, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_G));
ac->setDefaultShortcut(m_find, QKeySequence());
ac->setDefaultShortcut(m_findNext, QKeySequence());
ac->setDefaultShortcut(m_findPrev, QKeySequence());
ac->setDefaultShortcut(m_addBookmark, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_B));
ac->setDefaultShortcut(m_beginningOfDocument, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Home));
ac->setDefaultShortcut(m_endOfDocument, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_End));
QAction *action = static_cast( ac->action( QStringLiteral("file_reload") ) );
if (action) {
ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_F5));
}
}
void Part::setupActions()
{
KActionCollection * ac = actionCollection();
m_copy = KStandardAction::create( KStandardAction::Copy, m_pageView, SLOT(copyTextSelection()), ac );
m_selectAll = KStandardAction::selectAll( m_pageView, SLOT(selectAll()), ac );
m_save = KStandardAction::save( this, [this] { saveFile(); }, ac );
m_save->setEnabled( false );
m_saveAs = KStandardAction::saveAs( this, SLOT(slotSaveFileAs()), ac );
m_saveAs->setEnabled( false );
m_migrationMessage->addAction( m_saveAs );
m_showLeftPanel = ac->add(QStringLiteral("show_leftpanel"));
m_showLeftPanel->setText(i18n( "Show &Navigation Panel"));
m_showLeftPanel->setIcon(QIcon::fromTheme( QStringLiteral("view-sidetree") ));
connect( m_showLeftPanel, &QAction::toggled, this, &Part::slotShowLeftPanel );
ac->setDefaultShortcut(m_showLeftPanel, QKeySequence(Qt::Key_F7));
m_showLeftPanel->setChecked( Okular::Settings::showLeftPanel() );
slotShowLeftPanel();
m_showBottomBar = ac->add(QStringLiteral("show_bottombar"));
m_showBottomBar->setText(i18n( "Show &Page Bar"));
connect( m_showBottomBar, &QAction::toggled, this, &Part::slotShowBottomBar );
m_showBottomBar->setChecked( Okular::Settings::showBottomBar() );
slotShowBottomBar();
m_showSignaturePanel = ac->add(QStringLiteral("show_signatures"));
m_showSignaturePanel->setText(i18n("Show &Signatures Panel"));
connect( m_showSignaturePanel, &QAction::triggered, this, [this] {
if ( m_sidebar->currentItem() != m_signaturePanel) {
m_sidebar->setCurrentItem( m_signaturePanel );
}
});
m_showEmbeddedFiles = ac->addAction(QStringLiteral("embedded_files"));
m_showEmbeddedFiles->setText(i18n("&Embedded Files"));
m_showEmbeddedFiles->setIcon( QIcon::fromTheme( QStringLiteral("mail-attachment") ) );
connect(m_showEmbeddedFiles, &QAction::triggered, this, &Part::slotShowEmbeddedFiles);
m_showEmbeddedFiles->setEnabled( false );
m_exportAs = ac->addAction(QStringLiteral("file_export_as"));
m_exportAs->setText(i18n("E&xport As"));
m_exportAs->setIcon( QIcon::fromTheme( QStringLiteral("document-export") ) );
m_exportAsMenu = new QMenu();
connect(m_exportAsMenu, &QMenu::triggered, this, &Part::slotExportAs);
m_exportAs->setMenu( m_exportAsMenu );
m_exportAsText = actionForExportFormat( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ), m_exportAsMenu );
m_exportAsMenu->addAction( m_exportAsText );
m_exportAs->setEnabled( false );
m_exportAsText->setEnabled( false );
#if PURPOSE_FOUND
m_share = ac->addAction( QStringLiteral("file_share") );
m_share->setText( i18n("S&hare") );
m_share->setIcon( QIcon::fromTheme( QStringLiteral("document-share") ) );
m_share->setEnabled( false );
m_shareMenu = new Purpose::Menu();
connect(m_shareMenu, &Purpose::Menu::finished, this, &Part::slotShareActionFinished);
m_share->setMenu( m_shareMenu );
#endif
m_showPresentation = ac->addAction(QStringLiteral("presentation"));
m_showPresentation->setText(i18n("P&resentation"));
m_showPresentation->setIcon( QIcon::fromTheme( QStringLiteral("view-presentation") ) );
connect(m_showPresentation, &QAction::triggered, this, &Part::slotShowPresentation);
ac->setDefaultShortcut(m_showPresentation, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_P));
m_showPresentation->setEnabled( false );
QAction * importPS = ac->addAction(QStringLiteral("import_ps"));
importPS->setText(i18n("&Import PostScript as PDF..."));
importPS->setIcon(QIcon::fromTheme(QStringLiteral("document-import")));
connect(importPS, &QAction::triggered, this, &Part::slotImportPSFile);
#if 0
QAction * ghns = ac->addAction("get_new_stuff");
ghns->setText(i18n("&Get Books From Internet..."));
ghns->setIcon(QIcon::fromTheme("get-hot-new-stuff"));
connect(ghns, SIGNAL(triggered()), this, SLOT(slotGetNewStuff()));
#endif
KToggleAction *blackscreenAction = new KToggleAction( i18n( "Switch Blackscreen Mode" ), ac );
ac->addAction( QStringLiteral("switch_blackscreen_mode"), blackscreenAction );
ac->setDefaultShortcut(blackscreenAction, QKeySequence(Qt::Key_B));
blackscreenAction->setIcon( QIcon::fromTheme( QStringLiteral("view-presentation") ) );
blackscreenAction->setEnabled( false );
m_presentationDrawingActions = new DrawingToolActions( ac );
QAction *eraseDrawingAction = new QAction( i18n( "Erase Drawing" ), ac );
ac->addAction( QStringLiteral("presentation_erase_drawings"), eraseDrawingAction );
eraseDrawingAction->setIcon( QIcon::fromTheme( QStringLiteral("draw-eraser-delete-objects") ) );
eraseDrawingAction->setEnabled( false );
QAction *configureAnnotations = new QAction( i18n( "Configure Annotations..." ), ac );
ac->addAction( QStringLiteral("options_configure_annotations"), configureAnnotations );
configureAnnotations->setIcon( QIcon::fromTheme( QStringLiteral("configure") ) );
connect(configureAnnotations, &QAction::triggered, this, &Part::slotAnnotationPreferences);
QAction *playPauseAction = new QAction( i18n( "Play/Pause Presentation" ), ac );
ac->addAction( QStringLiteral("presentation_play_pause"), playPauseAction );
playPauseAction->setEnabled( false );
}
Part::~Part()
{
QDBusConnection::sessionBus().unregisterObject(m_registerDbusName);
GuiUtils::removeIconLoader( iconLoader() );
m_document->removeObserver( this );
if ( m_document->isOpened() )
Part::closeUrl( false );
delete m_toc;
delete m_layers;
delete m_pageView;
delete m_thumbnailList;
delete m_miniBar;
delete m_pageNumberTool;
delete m_miniBarLogic;
delete m_bottomBar;
#ifdef OKULAR_ENABLE_MINIBAR
delete m_progressWidget;
#endif
delete m_pageSizeLabel;
delete m_reviewsWidget;
delete m_bookmarkList;
delete m_infoTimer;
delete m_signaturePanel;
delete m_document;
delete m_tempfile;
qDeleteAll( m_bookmarkActions );
delete m_exportAsMenu;
#if PURPOSE_FOUND
delete m_shareMenu;
#endif
#ifdef OKULAR_KEEP_FILE_OPEN
delete m_keeper;
#endif
}
bool Part::openDocument(const QUrl& url, uint page)
{
Okular::DocumentViewport vp( page - 1 );
vp.rePos.enabled = true;
vp.rePos.normalizedX = 0;
vp.rePos.normalizedY = 0;
vp.rePos.pos = Okular::DocumentViewport::TopLeft;
if ( vp.isValid() )
m_document->setNextDocumentViewport( vp );
return openUrl( url );
}
void Part::startPresentation()
{
m_cliPresentation = true;
}
QStringList Part::supportedMimeTypes() const
{
return m_document->supportedMimeTypes();
}
QUrl Part::realUrl() const
{
if ( !m_realUrl.isEmpty() )
return m_realUrl;
return url();
}
// ViewerInterface
void Part::showSourceLocation(const QString& fileName, int line, int column, bool showGraphically)
{
Q_UNUSED(column);
const QString u = QStringLiteral( "src:%1 %2" ).arg( line + 1 ).arg( fileName );
GotoAction action( QString(), u );
m_document->processAction( &action );
if( showGraphically )
{
m_pageView->setLastSourceLocationViewport( m_document->viewport() );
}
}
void Part::clearLastShownSourceLocation()
{
m_pageView->clearLastSourceLocationViewport();
}
bool Part::isWatchFileModeEnabled() const
{
return !m_watcher->signalsBlocked();
}
void Part::setWatchFileModeEnabled(bool enabled)
{
// Don't call 'KDirWatch::stopScan()' in here (as of KDE Frameworks 5.51.0, see bug 400541)!
// 'KDirWatch::stopScan' has a bug that may affect other code paths that make use of KDirWatch
// (other loaded KParts, for example).
if( isWatchFileModeEnabled() == enabled )
{
return;
}
m_watcher->blockSignals(!enabled);
if( !enabled )
{
m_dirtyHandler->stop();
}
}
bool Part::areSourceLocationsShownGraphically() const
{
return m_pageView->areSourceLocationsShownGraphically();
}
void Part::setShowSourceLocationsGraphically(bool show)
{
m_pageView->setShowSourceLocationsGraphically(show);
}
bool Part::openNewFilesInTabs() const
{
return Okular::Settings::self()->shellOpenFileInTabs();
}
void Part::slotHandleActivatedSourceReference(const QString& absFileName, int line, int col, bool *handled)
{
emit openSourceReference( absFileName, line, col );
if ( m_embedMode == Okular::ViewerWidgetMode )
{
*handled = true;
}
}
void Part::openUrlFromDocument(const QUrl &url)
{
if ( m_embedMode == PrintPreviewMode )
return;
if (url.isLocalFile()) {
if (!QFile::exists(url.toLocalFile())) {
KMessageBox::error( widget(), i18n("Could not open '%1'. File does not exist", url.toDisplayString() ) );
return;
}
} else {
KIO::StatJob *statJob = KIO::stat(url, KIO::StatJob::SourceSide, 0);
KJobWidgets::setWindow(statJob, widget());
if (!statJob->exec() || statJob->error()) {
KMessageBox::error( widget(), i18n("Could not open '%1' (%2) ", url.toDisplayString(), statJob->errorString() ) );
return;
}
}
m_bExtension->openUrlNotify();
m_bExtension->setLocationBarUrl(url.toDisplayString());
openUrl(url);
}
void Part::openUrlFromBookmarks(const QUrl &_url)
{
QUrl url = _url;
Okular::DocumentViewport vp( _url.fragment(QUrl::FullyDecoded) );
if ( vp.isValid() )
m_document->setNextDocumentViewport( vp );
url.setFragment( QString() );
if ( m_document->currentDocument() == url )
{
if ( vp.isValid() )
m_document->setViewport( vp );
}
else
openUrl( url );
}
void Part::handleDroppedUrls( const QList& urls )
{
if ( urls.isEmpty() )
return;
if ( m_embedMode != NativeShellMode || !openNewFilesInTabs() )
{
openUrlFromDocument( urls.first() );
return;
}
emit urlsDropped( urls );
}
void Part::slotJobStarted(KIO::Job *job)
{
if (job)
{
QStringList supportedMimeTypes = m_document->supportedMimeTypes();
job->addMetaData(QStringLiteral("accept"), supportedMimeTypes.join(QStringLiteral(", ")) + QStringLiteral(", */*;q=0.5"));
connect(job, &KJob::result, this, &Part::slotJobFinished);
}
}
void Part::slotJobFinished(KJob *job)
{
if ( job->error() == KIO::ERR_USER_CANCELED )
{
m_pageView->displayMessage( i18n( "The loading of %1 has been canceled.", realUrl().toDisplayString(QUrl::PreferLocalFile) ) );
}
}
void Part::loadCancelled(const QString &reason)
{
emit setWindowCaption( QString() );
resetStartArguments();
// when m_viewportDirty.pageNumber != -1 we come from slotAttemptReload
// so we don't want to show an ugly messagebox just because the document is
// taking more than usual to be recreated
if (m_viewportDirty.pageNumber == -1)
{
if (!reason.isEmpty())
{
KMessageBox::error( widget(), i18n("Could not open %1. Reason: %2", url().toDisplayString(), reason ) );
}
}
}
void Part::setWindowTitleFromDocument()
{
// If 'DocumentTitle' should be used, check if the document has one. If
// either case is false, use the file name.
QString title = Okular::Settings::displayDocumentNameOrPath() == Okular::Settings::EnumDisplayDocumentNameOrPath::Path ? realUrl().toDisplayString(QUrl::PreferLocalFile)
: realUrl().fileName();
if ( Okular::Settings::displayDocumentTitle() )
{
const QString docTitle = m_document->metaData( QStringLiteral("DocumentTitle") ).toString();
if ( !docTitle.isEmpty() && !docTitle.trimmed().isEmpty() )
{
title = docTitle;
}
}
emit setWindowCaption( title );
}
KConfigDialog * Part::slotGeneratorPreferences( )
{
// Create dialog
KConfigDialog * dialog = new Okular::BackendConfigDialog( m_pageView, QStringLiteral("generator_prefs"), Okular::Settings::self() );
dialog->setAttribute( Qt::WA_DeleteOnClose );
if( m_embedMode == ViewerWidgetMode )
{
dialog->setWindowTitle( i18n( "Configure Viewer Backends" ) );
}
else
{
dialog->setWindowTitle( i18n( "Configure Backends" ) );
}
m_document->fillConfigDialog( dialog );
// Show it
dialog->setWindowModality( Qt::ApplicationModal );
dialog->show();
return dialog;
}
void Part::notifySetup( const QVector< Okular::Page * > & /*pages*/, int setupFlags )
{
// Hide the migration message if the user has just migrated. Otherwise,
// if m_migrationMessage is already hidden, this does nothing.
if ( !m_document->isDocdataMigrationNeeded() )
m_migrationMessage->animatedHide();
if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) )
return;
rebuildBookmarkMenu();
updateAboutBackendAction();
m_findBar->resetSearch();
m_searchWidget->setEnabled( m_document->supportsSearching() );
}
void Part::notifyViewportChanged( bool /*smoothMove*/ )
{
updateViewActions();
}
void Part::notifyPageChanged( int page, int flags )
{
if ( !(flags & Okular::DocumentObserver::Bookmark ) )
return;
rebuildBookmarkMenu();
if ( page == m_document->viewport().pageNumber )
updateBookmarksActions();
}
void Part::goToPage(uint i)
{
if ( i <= m_document->pages() )
m_document->setViewportPage( i - 1 );
}
void Part::openDocument( const QString &doc )
{
openUrl( QUrl::fromUserInput( doc ) );
}
uint Part::pages()
{
return m_document->pages();
}
uint Part::currentPage()
{
return m_document->pages() ? m_document->currentPage() + 1 : 0;
}
QString Part::currentDocument()
{
return m_document->currentDocument().toDisplayString(QUrl::PreferLocalFile);
}
QString Part::documentMetaData( const QString &metaData ) const
{
const Okular::DocumentInfo info = m_document->documentInfo();
return info.get( metaData );
}
bool Part::slotImportPSFile()
{
QString app = QStandardPaths::findExecutable(QStringLiteral("ps2pdf") );
if ( app.isEmpty() )
{
// TODO point the user to their distro packages?
KMessageBox::error( widget(), i18n( "The program \"ps2pdf\" was not found, so Okular can not import PS files using it." ), i18n("ps2pdf not found") );
return false;
}
QMimeDatabase mimeDatabase;
QString filter = i18n("PostScript files (%1)", mimeDatabase.mimeTypeForName(QStringLiteral("application/postscript")).globPatterns().join(QLatin1Char(' ')));
QUrl url = QFileDialog::getOpenFileUrl( widget(), QString(), QUrl(), filter );
if ( url.isLocalFile() )
{
QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.pdf"));
tf.setAutoRemove( false );
if ( !tf.open() )
return false;
m_temporaryLocalFile = tf.fileName();
tf.close();
setLocalFilePath( url.toLocalFile() );
QStringList args;
QProcess *p = new QProcess();
args << url.toLocalFile() << m_temporaryLocalFile;
m_pageView->displayMessage(i18n("Importing PS file as PDF (this may take a while)..."));
connect(p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(psTransformEnded(int,QProcess::ExitStatus)));
p->start(app, args);
return true;
}
m_temporaryLocalFile.clear();
return false;
}
void Part::setFileToWatch( const QString &filePath )
{
if ( !m_watchedFilePath.isEmpty() )
unsetFileToWatch();
const QFileInfo fi(filePath);
m_watchedFilePath = filePath;
m_watcher->addFile( m_watchedFilePath );
if ( fi.isSymLink() )
{
m_watchedFileSymlinkTarget = fi.symLinkTarget();
m_watcher->addFile( m_watchedFileSymlinkTarget );
}
else
{
m_watchedFileSymlinkTarget.clear();
}
}
void Part::unsetFileToWatch()
{
if ( m_watchedFilePath.isEmpty() )
return;
m_watcher->removeFile( m_watchedFilePath );
if ( !m_watchedFileSymlinkTarget.isEmpty() )
m_watcher->removeFile( m_watchedFileSymlinkTarget );
m_watchedFilePath.clear();
m_watchedFileSymlinkTarget.clear();
}
Document::OpenResult Part::doOpenFile( const QMimeType &mimeA, const QString &fileNameToOpenA, bool *isCompressedFile )
{
QMimeDatabase db;
Document::OpenResult openResult = Document::OpenError;
bool uncompressOk = true;
QMimeType mime = mimeA;
QString fileNameToOpen = fileNameToOpenA;
KFilterDev::CompressionType compressionType = compressionTypeFor( mime.name() );
if ( compressionType != KFilterDev::None )
{
*isCompressedFile = true;
uncompressOk = handleCompressed( fileNameToOpen, localFilePath(), compressionType );
mime = db.mimeTypeForFile( fileNameToOpen );
}
else
{
*isCompressedFile = false;
}
if ( m_swapInsteadOfOpening )
{
m_swapInsteadOfOpening = false;
if ( !uncompressOk )
return Document::OpenError;
if ( mime.inherits( QStringLiteral("application/vnd.kde.okular-archive") ) )
{
isDocumentArchive = true;
if (!m_document->swapBackingFileArchive( fileNameToOpen, url() ))
return Document::OpenError;
}
else
{
isDocumentArchive = false;
if (!m_document->swapBackingFile( fileNameToOpen, url() ))
return Document::OpenError;
}
m_fileLastModified = QFileInfo( localFilePath() ).lastModified();
return Document::OpenSuccess;
}
isDocumentArchive = false;
if ( uncompressOk )
{
if ( mime.inherits( QStringLiteral("application/vnd.kde.okular-archive") ) )
{
openResult = m_document->openDocumentArchive( fileNameToOpen, url() );
isDocumentArchive = true;
}
else
{
openResult = m_document->openDocument( fileNameToOpen, url(), mime );
}
m_documentOpenWithPassword = false;
#ifdef WITH_KWALLET
// if the file didn't open correctly it might be encrypted, so ask for a pass
QString walletName, walletFolder, walletKey;
m_document->walletDataForFile(fileNameToOpen, &walletName, &walletFolder, &walletKey);
bool firstInput = true;
bool triedWallet = false;
KWallet::Wallet * wallet = nullptr;
bool keep = true;
while ( openResult == Document::OpenNeedsPassword )
{
QString password;
// 1.A. try to retrieve the first password from the kde wallet system
if ( !triedWallet && !walletKey.isNull() )
{
const WId parentwid = widget()->effectiveWinId();
wallet = KWallet::Wallet::openWallet( walletName, parentwid );
if ( wallet )
{
// use the KPdf folder (and create if missing)
if ( !wallet->hasFolder( walletFolder ) )
wallet->createFolder( walletFolder );
wallet->setFolder( walletFolder );
// look for the pass in that folder
QString retrievedPass;
if ( !wallet->readPassword( walletKey, retrievedPass ) )
password = retrievedPass;
}
triedWallet = true;
}
// 1.B. if not retrieved, ask the password using the kde password dialog
if ( password.isNull() )
{
QString prompt;
if ( firstInput )
prompt = i18n( "Please enter the password to read the document:" );
else
prompt = i18n( "Incorrect password. Try again:" );
firstInput = false;
// if the user presses cancel, abort opening
KPasswordDialog dlg( widget(), wallet ? KPasswordDialog::ShowKeepPassword : KPasswordDialog::KPasswordDialogFlags() );
dlg.setWindowTitle( i18n( "Document Password" ) );
dlg.setPrompt( prompt );
if( !dlg.exec() )
break;
password = dlg.password();
if ( wallet )
keep = dlg.keepPassword();
}
// 2. reopen the document using the password
if ( mime.inherits( QStringLiteral("application/vnd.kde.okular-archive") ) )
{
openResult = m_document->openDocumentArchive( fileNameToOpen, url(), password );
isDocumentArchive = true;
}
else
{
openResult = m_document->openDocument( fileNameToOpen, url(), mime, password );
}
if ( openResult == Document::OpenSuccess )
{
m_documentOpenWithPassword = true;
// 3. if the password is correct and the user chose to remember it, store it to the wallet
if (wallet && /*safety check*/ wallet->isOpen() && keep )
{
wallet->writePassword( walletKey, password );
}
}
}
#endif
}
if ( openResult == Document::OpenSuccess )
{
m_fileLastModified = QFileInfo( localFilePath() ).lastModified();
}
return openResult;
}
bool Part::openFile()
{
QList mimes;
QString fileNameToOpen = localFilePath();
const bool isstdin = url().isLocalFile() && url().fileName() == QLatin1String( "-" );
const QFileInfo fileInfo( fileNameToOpen );
if ( (!isstdin) && (!fileInfo.exists()) )
return false;
QMimeDatabase db;
QMimeType pathMime = db.mimeTypeForFile( fileNameToOpen );
if ( !arguments().mimeType().isEmpty() )
{
QMimeType argMime = db.mimeTypeForName( arguments().mimeType() );
// Select the "childmost" mimetype, if none of them
// inherits the other trust more what pathMime says
// but still do a second try if that one fails
if ( argMime.inherits( pathMime.name() ) )
{
mimes << argMime;
}
else if ( pathMime.inherits( argMime.name() ) )
{
mimes << pathMime;
}
else
{
mimes << pathMime << argMime;
}
if (mimes[0].name() == QLatin1String("text/plain")) {
QMimeType contentMime = db.mimeTypeForFile(fileNameToOpen, QMimeDatabase::MatchContent);
mimes.prepend( contentMime );
}
}
else
{
mimes << pathMime;
}
QMimeType mime;
Document::OpenResult openResult = Document::OpenError;
bool isCompressedFile = false;
while ( !mimes.isEmpty() && openResult == Document::OpenError ) {
mime = mimes.takeFirst();
openResult = doOpenFile( mime, fileNameToOpen, &isCompressedFile );
}
bool canSearch = m_document->supportsSearching();
emit mimeTypeChanged( mime );
// update one-time actions
const bool ok = openResult == Document::OpenSuccess;
emit enableCloseAction( ok );
m_find->setEnabled( ok && canSearch );
m_findNext->setEnabled( ok && canSearch );
m_findPrev->setEnabled( ok && canSearch );
if( m_save ) m_save->setEnabled( ok && !( isstdin || mime.inherits( "inode/directory" ) ) );
if( m_saveAs ) m_saveAs->setEnabled( ok && !( isstdin || mime.inherits( "inode/directory" ) ) );
emit enablePrintAction( ok && m_document->printingSupport() != Okular::Document::NoPrinting );
m_printPreview->setEnabled( ok && m_document->printingSupport() != Okular::Document::NoPrinting );
m_showProperties->setEnabled( ok );
bool hasEmbeddedFiles = ok && m_document->embeddedFiles() && m_document->embeddedFiles()->count() > 0;
if ( m_showEmbeddedFiles ) m_showEmbeddedFiles->setEnabled( hasEmbeddedFiles );
m_topMessage->setVisible( hasEmbeddedFiles && Okular::Settings::showOSD() );
m_migrationMessage->setVisible( m_document->isDocdataMigrationNeeded() );
// Warn the user that XFA forms are not supported yet (NOTE: poppler generator only)
if ( ok && m_document->metaData( QStringLiteral("HasUnsupportedXfaForm") ).toBool() == true )
{
m_formsMessage->setText( i18n( "This document has XFA forms, which are currently unsupported." ) );
m_formsMessage->setIcon( QIcon::fromTheme( QStringLiteral("dialog-warning") ) );
m_formsMessage->setMessageType( KMessageWidget::Warning );
m_formsMessage->setVisible( true );
}
// m_pageView->toggleFormsAction() may be null on dummy mode
else if ( ok && m_pageView->toggleFormsAction() && m_pageView->toggleFormsAction()->isEnabled() )
{
m_formsMessage->setText( i18n( "This document has forms. Click on the button to interact with them, or use View -> Show Forms." ) );
m_formsMessage->setMessageType( KMessageWidget::Information );
m_formsMessage->setVisible( true );
}
else
{
m_formsMessage->setVisible( false );
}
if ( ok && m_document->metaData( QStringLiteral("IsDigitallySigned") ).toBool() )
{
if ( m_embedMode == PrintPreviewMode )
{
m_signatureMessage->setText( i18n( "All editing and interactive features for this document are disabled. Please save a copy and reopen to edit this document." ) );
}
else
{
m_signatureMessage->setText( i18n( "This document is digitally signed." ) );
}
m_signatureMessage->setVisible( true );
}
if ( m_showPresentation ) m_showPresentation->setEnabled( ok );
if ( ok )
{
if ( m_exportAs )
{
m_exportFormats = m_document->exportFormats();
QList::ConstIterator it = m_exportFormats.constBegin();
QList::ConstIterator itEnd = m_exportFormats.constEnd();
QMenu *menu = m_exportAs->menu();
for ( ; it != itEnd; ++it )
{
menu->addAction( actionForExportFormat( *it ) );
}
}
#if PURPOSE_FOUND
if ( m_share )
{
m_shareMenu->model()->setInputData(QJsonObject{
{ QStringLiteral("mimeType"), mime.name() },
{ QStringLiteral("urls"), QJsonArray{ url().toString() } }
});
m_shareMenu->model()->setPluginType( QStringLiteral("Export") );
m_shareMenu->reload();
}
#endif
if ( isCompressedFile )
{
m_realUrl = url();
}
#ifdef OKULAR_KEEP_FILE_OPEN
if ( keepFileOpen() )
m_keeper->open( fileNameToOpen );
#endif
// Tries to find the text passed from terminal after the file is open
if(!m_textToFindOnOpen.isEmpty())
{
m_findBar->startSearch(m_textToFindOnOpen);
m_textToFindOnOpen = QString();
}
}
if ( m_exportAsText ) m_exportAsText->setEnabled( ok && m_document->canExportToText() );
if ( m_exportAs ) m_exportAs->setEnabled( ok );
#if PURPOSE_FOUND
if ( m_share ) m_share->setEnabled( ok );
#endif
// update viewing actions
updateViewActions();
m_fileWasRemoved = false;
if ( !ok )
{
// if can't open document, update windows so they display blank contents
m_pageView->viewport()->update();
m_thumbnailList->update();
setUrl( QUrl() );
return false;
}
// set the file to the fileWatcher
if ( url().isLocalFile() )
setFileToWatch( localFilePath() );
// if the 'OpenTOC' flag is set, open the TOC
if ( m_document->metaData( QStringLiteral("OpenTOC") ).toBool() && m_sidebar->isItemEnabled( m_toc ) && !m_sidebar->isCollapsed() && m_sidebar->currentItem() != m_toc )
{
m_sidebar->setCurrentItem( m_toc, Sidebar::DoNotUncollapseIfCollapsed );
}
// if the 'StartFullScreen' flag is set and we're not in viewer widget mode, or the command line flag was
// specified, start presentation
const bool presentationBecauseOfDocumentMetadata = ( m_embedMode != ViewerWidgetMode ) && m_document->metaData( QStringLiteral("StartFullScreen") ).toBool();
if ( presentationBecauseOfDocumentMetadata || m_cliPresentation )
{
bool goAheadWithPresentationMode = true;
if ( !m_cliPresentation )
{
const QString text = i18n( "This document wants to be shown full screen.\n"
"Leave normal mode and enter presentation mode?" );
const QString caption = i18n( "Request to Change Viewing Mode" );
const KGuiItem yesItem = KGuiItem( i18n( "Enter Presentation Mode" ), QStringLiteral("dialog-ok") );
const KGuiItem noItem = KGuiItem( i18n( "Deny Request" ), QStringLiteral("dialog-cancel") );
const int result = KMessageBox::questionYesNo( widget(), text, caption, yesItem, noItem );
if ( result == KMessageBox::No )
goAheadWithPresentationMode = false;
}
m_cliPresentation = false;
if ( goAheadWithPresentationMode )
QMetaObject::invokeMethod( this, "slotShowPresentation", Qt::QueuedConnection );
}
m_generatorGuiClient = factory() ? m_document->guiClient() : nullptr;
if ( m_generatorGuiClient )
factory()->addClient( m_generatorGuiClient );
if ( m_cliPrint )
{
m_cliPrint = false;
slotPrint();
}
else if ( m_cliPrintAndExit )
{
slotPrint();
}
return true;
}
bool Part::openUrl( const QUrl &url )
{
return openUrl( url, false /* swapInsteadOfOpening */ );
}
bool Part::openUrl( const QUrl &_url, bool swapInsteadOfOpening )
{
/* Store swapInsteadOfOpening, so that closeUrl and openFile will be able
* to read it */
m_swapInsteadOfOpening = swapInsteadOfOpening;
// The subsequent call to closeUrl clears the arguments.
// We want to save them and restore them later.
const KParts::OpenUrlArguments args = arguments();
// Close current document if any
if ( !closeUrl() )
return false;
setArguments(args);
QUrl url( _url );
if ( url.hasFragment() )
{
const QString dest = url.fragment(QUrl::FullyDecoded);
bool ok = true;
const int page = dest.toInt( &ok );
if ( ok )
{
Okular::DocumentViewport vp( page - 1 );
vp.rePos.enabled = true;
vp.rePos.normalizedX = 0;
vp.rePos.normalizedY = 0;
vp.rePos.pos = Okular::DocumentViewport::TopLeft;
m_document->setNextDocumentViewport( vp );
}
else
{
m_document->setNextDocumentDestination( dest );
}
url.setFragment( QString() );
}
// this calls in sequence the 'closeUrl' and 'openFile' methods
bool openOk = KParts::ReadWritePart::openUrl( url );
if ( openOk )
{
m_viewportDirty.pageNumber = -1;
setWindowTitleFromDocument();
}
else
{
resetStartArguments();
KMessageBox::error( widget(), i18n("Could not open %1", url.toDisplayString() ) );
}
return openOk;
}
bool Part::queryClose()
{
if ( !isReadWrite() || !isModified() )
return true;
// TODO When we get different saving backends we need to query the backend
// as to if it can save changes even if the open file has been modified,
// since we only have poppler as saving backend for now we're skipping that check
if ( m_fileLastModified != QFileInfo( localFilePath() ).lastModified() )
{
int res;
if ( m_isReloading )
{
res = KMessageBox::warningYesNo( widget(),
i18n( "There are unsaved changes, and the file '%1' has been modified by another program. Your changes will be lost, because the file can no longer be saved.
Do you want to continue reloading the file?", url().fileName() ),
i18n( "File Changed" ),
KGuiItem( i18n( "Continue Reloading" ) ), // <- KMessageBox::Yes
KGuiItem( i18n( "Abort Reloading" ) ));
}
else
{
res = KMessageBox::warningYesNo( widget(),
i18n( "There are unsaved changes, and the file '%1' has been modified by another program. Your changes will be lost, because the file can no longer be saved.
Do you want to continue closing the file?", url().fileName() ),
i18n( "File Changed" ),
KGuiItem( i18n( "Continue Closing" ) ), // <- KMessageBox::Yes
KGuiItem( i18n( "Abort Closing" ) ));
}
return res == KMessageBox::Yes;
}
const int res = KMessageBox::warningYesNoCancel( widget(),
i18n( "Do you want to save your changes to \"%1\" or discard them?", url().fileName() ),
i18n( "Close Document" ),
KStandardGuiItem::save(),
KStandardGuiItem::discard() );
switch ( res )
{
case KMessageBox::Yes: // Save
saveFile();
return !isModified(); // Only allow closing if file was really saved
case KMessageBox::No: // Discard
return true;
default: // Cancel
return false;
}
}
bool Part::closeUrl(bool promptToSave)
{
if ( promptToSave && !queryClose() )
return false;
if ( m_swapInsteadOfOpening )
{
// If we're swapping the backing file, we don't want to close the
// current one when openUrl() calls us internally
return true; // pretend it worked
}
m_document->setHistoryClean( true );
if (!m_temporaryLocalFile.isNull() && m_temporaryLocalFile != localFilePath())
{
QFile::remove( m_temporaryLocalFile );
m_temporaryLocalFile.clear();
}
slotHidePresentation();
emit enableCloseAction( false );
m_find->setEnabled( false );
m_findNext->setEnabled( false );
m_findPrev->setEnabled( false );
if( m_save ) m_save->setEnabled( false );
if( m_saveAs ) m_saveAs->setEnabled( false );
m_printPreview->setEnabled( false );
m_showProperties->setEnabled( false );
if ( m_showEmbeddedFiles ) m_showEmbeddedFiles->setEnabled( false );
if ( m_exportAs ) m_exportAs->setEnabled( false );
if ( m_exportAsText ) m_exportAsText->setEnabled( false );
m_exportFormats.clear();
if ( m_exportAs )
{
QMenu *menu = m_exportAs->menu();
QList acts = menu->actions();
int num = acts.count();
for ( int i = 1; i < num; ++i )
{
menu->removeAction( acts.at(i) );
delete acts.at(i);
}
}
#if PURPOSE_FOUND
if ( m_share )
{
m_share->setEnabled(false);
m_shareMenu->clear();
}
#endif
if ( m_showPresentation ) m_showPresentation->setEnabled( false );
emit setWindowCaption(QLatin1String(""));
emit enablePrintAction(false);
m_realUrl = QUrl();
if ( url().isLocalFile() )
unsetFileToWatch();
m_fileWasRemoved = false;
if ( m_generatorGuiClient )
factory()->removeClient( m_generatorGuiClient );
m_generatorGuiClient = nullptr;
m_document->closeDocument();
m_fileLastModified = QDateTime();
updateViewActions();
delete m_tempfile;
m_tempfile = nullptr;
if ( widget() )
{
m_searchWidget->clearText();
m_migrationMessage->setVisible( false );
m_topMessage->setVisible( false );
m_formsMessage->setVisible( false );
m_signatureMessage->setVisible( false );
}
#ifdef OKULAR_KEEP_FILE_OPEN
m_keeper->close();
#endif
bool r = KParts::ReadWritePart::closeUrl();
setUrl(QUrl());
return r;
}
bool Part::closeUrl()
{
return closeUrl( true );
}
void Part::guiActivateEvent(KParts::GUIActivateEvent *event)
{
updateViewActions();
KParts::ReadWritePart::guiActivateEvent(event);
setWindowTitleFromDocument();
}
void Part::close()
{
if ( m_embedMode == NativeShellMode )
{
closeUrl();
}
else KMessageBox::information( widget(), i18n( "This link points to a close document action that does not work when using the embedded viewer." ), QString(), QStringLiteral("warnNoCloseIfNotInOkular") );
}
void Part::cannotQuit()
{
KMessageBox::information( widget(), i18n( "This link points to a quit application action that does not work when using the embedded viewer." ), QString(), QStringLiteral("warnNoQuitIfNotInOkular") );
}
void Part::slotShowLeftPanel()
{
bool showLeft = m_showLeftPanel->isChecked();
Okular::Settings::setShowLeftPanel( showLeft );
Okular::Settings::self()->save();
// show/hide left panel
m_sidebar->setSidebarVisibility( showLeft );
}
void Part::slotShowBottomBar()
{
const bool showBottom = m_showBottomBar->isChecked();
Okular::Settings::setShowBottomBar( showBottom );
Okular::Settings::self()->save();
// show/hide bottom bar
m_bottomBar->setVisible( showBottom );
}
void Part::slotFileDirty( const QString& path )
{
// The beauty of this is that each start cancels the previous one.
// This means that timeout() is only fired when there have
// no changes to the file for the last 750 millisecs.
// This ensures that we don't update on every other byte that gets
// written to the file.
if ( path == localFilePath() )
{
// Only start watching the file in case if it wasn't removed
if (QFile::exists(localFilePath()))
m_dirtyHandler->start( 750 );
else
m_fileWasRemoved = true;
}
else
{
const QFileInfo fi(localFilePath());
if ( fi.absolutePath() == path )
{
// Our parent has been dirtified
if (!QFile::exists(localFilePath()))
{
m_fileWasRemoved = true;
}
else if (m_fileWasRemoved && QFile::exists(localFilePath()))
{
// we need to watch the new file
unsetFileToWatch();
setFileToWatch( localFilePath() );
m_dirtyHandler->start( 750 );
}
}
else if ( fi.isSymLink() && fi.symLinkTarget() == path )
{
if ( QFile::exists( fi.symLinkTarget() ))
m_dirtyHandler->start( 750 );
else
m_fileWasRemoved = true;
}
}
}
// Attempt to reload the document, one or more times, optionally from a different URL
bool Part::slotAttemptReload( bool oneShot, const QUrl &newUrl )
{
// Skip reload when another reload is already in progress
if ( m_isReloading ) {
return false;
}
QScopedValueRollback rollback(m_isReloading, true);
bool tocReloadPrepared = false;
// do the following the first time the file is reloaded
if ( m_viewportDirty.pageNumber == -1 )
{
// store the url of the current document
m_oldUrl = newUrl.isEmpty() ? url() : newUrl;
// store the current viewport
m_viewportDirty = m_document->viewport();
// store the current toolbox pane
m_dirtyToolboxItem = m_sidebar->currentItem();
m_wasSidebarVisible = m_sidebar->isSidebarVisible();
m_wasSidebarCollapsed = m_sidebar->isCollapsed();
// store if presentation view was open
m_wasPresentationOpen = ((PresentationWidget*)m_presentationWidget != nullptr);
// preserves the toc state after reload
m_toc->prepareForReload();
tocReloadPrepared = true;
// store the page rotation
m_dirtyPageRotation = m_document->rotation();
// inform the user about the operation in progress
// TODO: Remove this line and integrate reload info in queryClose
m_pageView->displayMessage( i18n("Reloading the document...") );
}
// close and (try to) reopen the document
if ( !closeUrl() )
{
m_viewportDirty.pageNumber = -1;
if ( tocReloadPrepared )
{
m_toc->rollbackReload();
}
return false;
}
if ( tocReloadPrepared )
m_toc->finishReload();
// inform the user about the operation in progress
m_pageView->displayMessage( i18n("Reloading the document...") );
bool reloadSucceeded = false;
if ( KParts::ReadWritePart::openUrl( m_oldUrl ) )
{
// on successful opening, restore the previous viewport
if ( m_viewportDirty.pageNumber >= (int) m_document->pages() )
m_viewportDirty.pageNumber = (int) m_document->pages() - 1;
m_document->setViewport( m_viewportDirty );
m_oldUrl = QUrl();
m_viewportDirty.pageNumber = -1;
m_document->setRotation( m_dirtyPageRotation );
if ( m_sidebar->currentItem() != m_dirtyToolboxItem && m_sidebar->isItemEnabled( m_dirtyToolboxItem )
&& !m_sidebar->isCollapsed() )
{
m_sidebar->setCurrentItem( m_dirtyToolboxItem );
}
if ( m_sidebar->isSidebarVisible() != m_wasSidebarVisible )
{
m_sidebar->setSidebarVisibility( m_wasSidebarVisible );
}
if ( m_sidebar->isCollapsed() != m_wasSidebarCollapsed )
{
m_sidebar->setCollapsed( m_wasSidebarCollapsed );
}
if (m_wasPresentationOpen) slotShowPresentation();
emit enablePrintAction(true && m_document->printingSupport() != Okular::Document::NoPrinting);
reloadSucceeded = true;
}
else if ( !oneShot )
{
// start watching the file again (since we dropped it on close)
setFileToWatch( localFilePath() );
m_dirtyHandler->start( 750 );
}
return reloadSucceeded;
}
void Part::updateViewActions()
{
bool opened = m_document->pages() > 0;
if ( opened )
{
m_gotoPage->setEnabled( m_document->pages() > 1 );
// Check if you are at the beginning or not
if (m_document->currentPage() != 0)
{
m_beginningOfDocument->setEnabled( true );
m_prevPage->setEnabled( true );
}
else
{
if (m_pageView->verticalScrollBar()->value() != 0)
{
// The page isn't at the very beginning
m_beginningOfDocument->setEnabled( true );
}
else
{
// The page is at the very beginning of the document
m_beginningOfDocument->setEnabled( false );
}
// The document is at the first page, you can go to a page before
m_prevPage->setEnabled( false );
}
if (m_document->pages() == m_document->currentPage() + 1 )
{
// If you are at the end, disable go to next page
m_nextPage->setEnabled( false );
if (m_pageView->verticalScrollBar()->value() == m_pageView->verticalScrollBar()->maximum())
{
// If you are the end of the page of the last document, you can't go to the last page
m_endOfDocument->setEnabled( false );
}
else
{
// Otherwise you can move to the endif
m_endOfDocument->setEnabled( true );
}
}
else
{
// If you are not at the end, enable go to next page
m_nextPage->setEnabled( true );
m_endOfDocument->setEnabled( true );
}
if (m_historyBack) m_historyBack->setEnabled( !m_document->historyAtBegin() );
if (m_historyNext) m_historyNext->setEnabled( !m_document->historyAtEnd() );
m_reload->setEnabled( true );
if (m_copy) m_copy->setEnabled( true );
if (m_selectAll) m_selectAll->setEnabled( true );
}
else
{
m_gotoPage->setEnabled( false );
m_beginningOfDocument->setEnabled( false );
m_endOfDocument->setEnabled( false );
m_prevPage->setEnabled( false );
m_nextPage->setEnabled( false );
if (m_historyBack) m_historyBack->setEnabled( false );
if (m_historyNext) m_historyNext->setEnabled( false );
m_reload->setEnabled( false );
if (m_copy) m_copy->setEnabled( false );
if (m_selectAll) m_selectAll->setEnabled( false );
}
if ( factory() )
{
QWidget *menu = factory()->container(QStringLiteral("menu_okular_part_viewer"), this);
if (menu) menu->setEnabled( opened );
menu = factory()->container(QStringLiteral("view_orientation"), this);
if (menu) menu->setEnabled( opened );
}
emit viewerMenuStateChange( opened );
updateBookmarksActions();
}
void Part::updateBookmarksActions()
{
bool opened = m_document->pages() > 0;
if ( opened )
{
m_addBookmark->setEnabled( true );
if ( m_document->bookmarkManager()->isBookmarked( m_document->viewport() ) )
{
m_addBookmark->setText( i18n( "Remove Bookmark" ) );
m_addBookmark->setIcon( QIcon::fromTheme( QStringLiteral("edit-delete-bookmark") ) );
m_renameBookmark->setEnabled( true );
}
else
{
m_addBookmark->setText( m_addBookmarkText );
m_addBookmark->setIcon( m_addBookmarkIcon );
m_renameBookmark->setEnabled( false );
}
}
else
{
m_addBookmark->setEnabled( false );
m_addBookmark->setText( m_addBookmarkText );
m_addBookmark->setIcon( m_addBookmarkIcon );
m_renameBookmark->setEnabled( false );
}
}
void Part::enableTOC(bool enable)
{
m_sidebar->setItemEnabled(m_toc, enable);
// If present, show the TOC when a document is opened
if ( enable && m_sidebar->currentItem() != m_toc )
{
m_sidebar->setCurrentItem( m_toc, Sidebar::DoNotUncollapseIfCollapsed );
}
}
void Part::slotRebuildBookmarkMenu()
{
rebuildBookmarkMenu();
}
void Part::enableLayers(bool enable)
{
m_sidebar->setItemVisible( m_layers, enable );
}
void Part::showSidebarSignaturesItem( bool show )
{
m_sidebar->setItemVisible( m_signaturePanel, show );
}
void Part::slotShowFindBar()
{
m_findBar->show();
m_findBar->focusAndSetCursor();
m_closeFindBar->setEnabled( true );
}
void Part::slotHideFindBar()
{
if ( m_findBar->maybeHide() )
{
m_pageView->setFocus();
m_closeFindBar->setEnabled( false );
}
}
//BEGIN go to page dialog
class GotoPageDialog : public QDialog
{
Q_OBJECT
public:
GotoPageDialog(QWidget *p, int current, int max) : QDialog(p)
{
setWindowTitle(i18n("Go to Page"));
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QVBoxLayout *topLayout = new QVBoxLayout(this);
topLayout->setMargin(6);
QHBoxLayout *midLayout = new QHBoxLayout();
spinbox = new QSpinBox(this);
spinbox->setRange(1, max);
spinbox->setValue(current);
spinbox->setFocus();
slider = new QSlider(Qt::Horizontal, this);
slider->setRange(1, max);
slider->setValue(current);
slider->setSingleStep(1);
slider->setTickPosition(QSlider::TicksBelow);
slider->setTickInterval(max/10);
connect(slider, &QSlider::valueChanged, spinbox, &QSpinBox::setValue);
connect(spinbox, static_cast(&QSpinBox::valueChanged), slider, &QSlider::setValue);
QLabel *label = new QLabel(i18n("&Page:"), this);
label->setBuddy(spinbox);
topLayout->addWidget(label);
topLayout->addLayout(midLayout);
midLayout->addWidget(slider);
midLayout->addWidget(spinbox);
// A little bit extra space
topLayout->addStretch(10);
topLayout->addWidget(buttonBox);
spinbox->setFocus();
}
int getPage() const
{
return spinbox->value();
}
protected:
QSpinBox *spinbox;
QSlider *slider;
QDialogButtonBox *buttonBox;
};
//END go to page dialog
void Part::slotGoToPage()
{
GotoPageDialog pageDialog( m_pageView, m_document->currentPage() + 1, m_document->pages() );
if ( pageDialog.exec() == QDialog::Accepted )
m_document->setViewportPage( pageDialog.getPage() - 1 );
}
void Part::slotPreviousPage()
{
if ( m_document->isOpened() && !(m_document->currentPage() < 1) )
m_document->setViewportPage( m_document->currentPage() - 1 );
}
void Part::slotNextPage()
{
if ( m_document->isOpened() && m_document->currentPage() < (m_document->pages() - 1) )
m_document->setViewportPage( m_document->currentPage() + 1 );
}
void Part::slotGotoFirst()
{
if ( m_document->isOpened() ) {
m_document->setViewportPage( 0 );
m_beginningOfDocument->setEnabled( false );
}
}
void Part::slotGotoLast()
{
if ( m_document->isOpened() )
{
DocumentViewport endPage(m_document->pages() -1 );
endPage.rePos.enabled = true;
endPage.rePos.normalizedX = 0;
endPage.rePos.normalizedY = 1;
endPage.rePos.pos = Okular::DocumentViewport::TopLeft;
m_document->setViewport(endPage);
m_endOfDocument->setEnabled(false);
}
}
void Part::slotHistoryBack()
{
m_document->setPrevViewport();
}
void Part::slotHistoryNext()
{
m_document->setNextViewport();
}
void Part::slotAddBookmark()
{
DocumentViewport vp = m_document->viewport();
if ( m_document->bookmarkManager()->isBookmarked( vp ) )
{
m_document->bookmarkManager()->removeBookmark( vp );
}
else
{
m_document->bookmarkManager()->addBookmark( vp );
}
}
void Part::slotRenameBookmark( const DocumentViewport &viewport )
{
Q_ASSERT(m_document->bookmarkManager()->isBookmarked( viewport ));
if ( m_document->bookmarkManager()->isBookmarked( viewport ) )
{
KBookmark bookmark = m_document->bookmarkManager()->bookmark( viewport );
const QString newName = QInputDialog::getText(widget(), i18n( "Rename Bookmark" ), i18n( "Enter the new name of the bookmark:" ), QLineEdit::Normal, bookmark.fullText());
if (!newName.isEmpty())
{
m_document->bookmarkManager()->renameBookmark(&bookmark, newName);
}
}
}
void Part::slotRenameBookmarkFromMenu()
{
QAction *action = dynamic_cast(sender());
Q_ASSERT( action );
if ( action )
{
DocumentViewport vp( action->data().toString() );
slotRenameBookmark( vp );
}
}
void Part::slotRemoveBookmarkFromMenu()
{
QAction *action = dynamic_cast(sender());
Q_ASSERT( action );
if ( action )
{
DocumentViewport vp ( action->data().toString() );
slotRemoveBookmark( vp );
}
}
void Part::slotRemoveBookmark(const DocumentViewport &viewport)
{
Q_ASSERT(m_document->bookmarkManager()->isBookmarked( viewport ));
if ( m_document->bookmarkManager()->isBookmarked( viewport ) )
{
m_document->bookmarkManager()->removeBookmark( viewport );
}
}
void Part::slotRenameCurrentViewportBookmark()
{
slotRenameBookmark( m_document->viewport() );
}
bool Part::aboutToShowContextMenu(QMenu * /*menu*/, QAction *action, QMenu *contextMenu)
{
KBookmarkAction *ba = dynamic_cast