diff --git a/core/form.h b/core/form.h index e3637f50f..a234fdcc5 100644 --- a/core/form.h +++ b/core/form.h @@ -1,434 +1,434 @@ /*************************************************************************** * 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_FORM_H_ #define _OKULAR_FORM_H_ #include "okularcore_export.h" #include "area.h" #include "annotations.h" #include "signatureutils.h" #include namespace Okular { class Action; class Page; class PagePrivate; class FormFieldPrivate; class FormFieldButtonPrivate; class FormFieldTextPrivate; class FormFieldChoicePrivate; class FormFieldSignaturePrivate; /** * @short The base interface of a form field. * * This is the very basic interface to represent a field in a form. * * This is not meant to be used as a direct base for the form fields in a * document, but its abstract subclasses are. */ class OKULARCORE_EXPORT FormField { /// @cond PRIVATE friend class Page; friend class PagePrivate; /// @endcond public: /** * The types of form field. */ enum FieldType { FormButton, ///< A "button". See @ref FormFieldButton::ButtonType. FormText, ///< A field of variable text. See @ref FormFieldText::TextType. FormChoice, ///< A choice field. See @ref FormFieldChoice::ChoiceType. FormSignature ///< A signature. }; virtual ~FormField(); /** * The type of the field. */ FieldType type() const; /** * The bouding rect of the field, in normalized coordinates. */ virtual NormalizedRect rect() const = 0; /** * The ID of the field. */ virtual int id() const = 0; /** * The internal name of the field, to be used when referring to the * field in eg scripts. */ virtual QString name() const = 0; /** * The visible name of the field, to be used in the user interface * (eg in error messages, etc). */ virtual QString uiName() const = 0; /** * Whether the field is read-only. */ virtual bool isReadOnly() const; /** * Whether the field is read-only. * * @since 1.4 */ virtual void setReadOnly( bool value ); /** * Whether this form field is visible. */ virtual bool isVisible() const; /** * Whether the field is visible. * * @since 1.5 */ virtual void setVisible( bool value ); Action* activationAction() const; /** * Describes the type of form additional action. * * @since 1.1 */ enum AdditionalActionType { FieldModified, ///< An action to be performed when the user modifies the field FormatField, ///< An action to be performed before the field is formatted to display its value ValidateField, ///< An action to be performed when the field value changes CalculateField, ///< An action to be performed when the field needs to be recalculated }; /** * Returns the additional action of the given @p type or @c nullptr if no action has been defined. * * @since 1.1 */ Action* additionalAction( AdditionalActionType type ) const; /* Returns the additional action of the given @p type or @c nullptr if no action has been defined. * * This is for actions of annotation widgets associated with the FormField * * @since 1.5 */ Action* additionalAction( Annotation::AdditionalActionType type ) const; protected: /// @cond PRIVATE FormField( FormFieldPrivate &dd ); Q_DECLARE_PRIVATE( FormField ) FormFieldPrivate *d_ptr; /// @endcond void setActivationAction( Action *action ); void setAdditionalAction( AdditionalActionType type, Action *action ); void setAdditionalAction( Annotation::AdditionalActionType type, Action *action ); private: Q_DISABLE_COPY( FormField ) }; /** * @short Interface of a button form field. * * This is the base interface to reimplement to represent a button field, like * a push button, a check box or a radio button. * * @since 0.7 (KDE 4.1) */ class OKULARCORE_EXPORT FormFieldButton : public FormField { public: /** * The types of button field. */ enum ButtonType { Push, ///< A simple push button. CheckBox, ///< A check box. Radio ///< A radio button. }; virtual ~FormFieldButton(); /** The particular type of the button field. */ virtual ButtonType buttonType() const = 0; /** * The caption to be used for the button. */ virtual QString caption() const = 0; /** * The state of the button. */ virtual bool state() const = 0; /** * Sets the state of the button to the new \p state . */ virtual void setState( bool state ); /** * The list with the IDs of siblings (ie, buttons belonging to the same * group as the current one. * * Valid only for \ref Radio buttons, an empty list otherwise. */ virtual QList< int > siblings() const = 0; protected: FormFieldButton(); private: Q_DECLARE_PRIVATE( FormFieldButton ) Q_DISABLE_COPY( FormFieldButton ) }; /** * @short Interface of a text form field. * * This is the base interface to reimplement to represent a text field, ie a * field where the user insert text. */ class OKULARCORE_EXPORT FormFieldText : public FormField { public: /** * The types of text field. */ enum TextType { Normal, ///< A simple singleline text field. Multiline, ///< A multiline text field. FileSelect ///< An input field to select the path of a file on disk. }; virtual ~FormFieldText(); /** * The particular type of the text field. */ virtual TextType textType() const = 0; /** * The text of text field. */ virtual QString text() const = 0; /** * Sets the new @p text in the text field. * * The default implementation does nothing. * * Reimplemented only if the setting of new text is supported. */ virtual void setText( const QString& text ); /** * Whether this text field is a password input, eg its text @b must be * replaced with asterisks. * * Always false for @ref FileSelect text fields. */ virtual bool isPassword() const; /** * Whether this text field should allow rich text. */ virtual bool isRichText() const; /** * The maximum length allowed for the text of text field, or -1 if * there is no limitation for the text. */ virtual int maximumLength() const; /** * The alignment of the text within the field. */ virtual Qt::Alignment textAlignment() const; /** * Whether the text inserted manually in the field (where possible) * can be spell-checked. * * @note meaningful only if the field is editable. */ virtual bool canBeSpellChecked() const; protected: FormFieldText(); private: Q_DECLARE_PRIVATE( FormFieldText ) Q_DISABLE_COPY( FormFieldText ) }; /** * @short Interface of a choice form field. * * This is the base interface to reimplement to represent a choice field, ie a * field where the user can select one (of more) element(s) among a set of * choices. */ class OKULARCORE_EXPORT FormFieldChoice : public FormField { public: /** * The types of choice field. */ enum ChoiceType { ComboBox, ///< A combo box choice field. ListBox ///< A list box choice field. }; virtual ~FormFieldChoice(); /** * The particular type of the choice field. */ virtual ChoiceType choiceType() const = 0; /** * The possible choices of the choice field. */ virtual QStringList choices() const = 0; /** * Whether this ComboBox is editable, ie the user can type in a custom * value. * * Always false for the other types of choices. */ virtual bool isEditable() const; /** * Whether more than one choice of this ListBox can be selected at the * same time. * * Always false for the other types of choices. */ virtual bool multiSelect() const; /** * The currently selected choices. * * Always one element in the list in case of single choice elements. */ virtual QList< int > currentChoices() const = 0; /** * Sets the selected choices to @p choices . */ virtual void setCurrentChoices( const QList< int >& choices ); /** The text entered into an editable combo box choice field @since 0.16 (KDE 4.10) */ virtual QString editChoice() const; /** Sets the text entered into an editable combo box choice field @since 0.16 (KDE 4.10) */ virtual void setEditChoice( const QString& text ); /** * The alignment of the text within the field. */ virtual Qt::Alignment textAlignment() const; /** * Whether the text inserted manually in the field (where possible) * can be spell-checked. * * @note meaningful only if the field is editable. */ virtual bool canBeSpellChecked() const; protected: FormFieldChoice(); private: Q_DECLARE_PRIVATE( FormFieldChoice ) Q_DISABLE_COPY( FormFieldChoice ) }; /** * @short Interface of a signature form field. * * This is the base interface to reimplement to represent a signature field. */ class OKULARCORE_EXPORT FormFieldSignature : public FormField { public: /** * The types of signature. */ enum SignatureType { AdbePkcs7sha1, AdbePkcs7detached, EtsiCAdESdetached, UnknownType }; ~FormFieldSignature(); /** * The signature type */ virtual SignatureType signatureType() const = 0; /** * Validate the signature with 'now' as validation time. * */ - virtual SignatureInfo validate() const = 0; + virtual SignatureInfo *validate() const = 0; protected: FormFieldSignature(); private: Q_DECLARE_PRIVATE( FormFieldSignature ) Q_DISABLE_COPY( FormFieldSignature ) }; } #endif diff --git a/core/signatureutils.cpp b/core/signatureutils.cpp index 476eceb95..087be7a32 100644 --- a/core/signatureutils.cpp +++ b/core/signatureutils.cpp @@ -1,386 +1,266 @@ /*************************************************************************** * Copyright (C) 2018 by Chinmoy Ranjan Pradhan * * * * 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 "signatureutils.h".h" using namespace Okular; class Okular::CertificateInfoPrivate { public: CertificateInfoPrivate() : pkType( CertificateInfo::OtherKey ), keyUsages( CertificateInfo::KuNone ), certificateDer( QByteArray() ), serialNumber( QByteArray() ), publicKey( QByteArray() ), version( QByteArray() ), validityStart( QDateTime() ), validityEnd( QDateTime() ), issuerName( QString() ), issuerDN( QString() ), pkStrength( -1 ) { } CertificateInfo::PublicKeyType pkType; CertificateInfo::KeyUsages keyUsages; QByteArray certificateDer; QByteArray serialNumber; QByteArray publicKey; QByteArray version; QDateTime validityStart; QDateTime validityEnd; QString issuerName; QString issuerDN; int pkStrength; }; CertificateInfo::CertificateInfo( CertificateInfoPrivate *priv ) : d_ptr( priv ) { } CertificateInfo::CertificateInfo( const CertificateInfo &other ) : d_ptr( other.d_ptr ) { } CertificateInfo &CertificateInfo::operator=( const CertificateInfo &other ) { if ( this != &other ) d_ptr = other.d_ptr; return *this; } CertificateInfo::~CertificateInfo() { } Q_DECLARE_OPERATORS_FOR_FLAGS( CertificateInfo::KeyUsages ) bool CertificateInfo::isValid() const { return d_ptr.data() != nullptr; } QByteArray CertificateInfo::version() const { Q_D( const CertificateInfo ); return d->version; } QString CertificateInfo::issuerName() const { Q_D( const CertificateInfo ); return d->issuerName; } QString CertificateInfo::issuerDN() const { Q_D( const CertificateInfo ); return d->issuerDN; } QByteArray CertificateInfo::serialNumber() const { Q_D( const CertificateInfo ); return d->serialNumber; } QDateTime CertificateInfo::validityStart() const { Q_D( const CertificateInfo ); return d->validityStart; } QDateTime CertificateInfo::validityEnd() const { Q_D( const CertificateInfo ); return d->validityEnd; } CertificateInfo::KeyUsages CertificateInfo::keyUsages() const { Q_D( const CertificateInfo ); return d->keyUsages; } QByteArray CertificateInfo::publicKey() const { Q_D( const CertificateInfo ); return d->publicKey; } CertificateInfo::PublicKeyType CertificateInfo::publicKeyType() const { Q_D( const CertificateInfo ); return d->pkType; } int CertificateInfo::publicKeyStrength() const { Q_D( const CertificateInfo ); return d->pkStrength; } QByteArray CertificateInfo::certificateData() const { Q_D( const CertificateInfo ); return d->certificateDer; } void CertificateInfo::initPrivate() { if ( d_ptr.isNull() ) d_ptr = QSharedPointer( new CertificateInfoPrivate ); } void CertificateInfo::setVersion( const QByteArray &version ) { Q_D( CertificateInfo ); d->version = version; } void CertificateInfo::setIssuerName( const QString &issuerName ) { Q_D( CertificateInfo ); d->issuerName = issuerName; } void CertificateInfo::setIssuerDN( const QString &issuerDN ) { Q_D( CertificateInfo ); d->issuerDN = issuerDN; } void CertificateInfo::setSerialNumber( const QByteArray &serialNumber ) { Q_D( CertificateInfo ); d->serialNumber = serialNumber; } void CertificateInfo::setValidityStart( const QDateTime &validityStart ) { Q_D( CertificateInfo ); d->validityStart = validityStart; } void CertificateInfo::setValidityEnd( const QDateTime & validityEnd ) { Q_D( CertificateInfo ); d->validityEnd = validityEnd; } void CertificateInfo::setKeyUsages( KeyUsages ku ) { Q_D( CertificateInfo ); d->keyUsages = ku; } void CertificateInfo::setPublicKey( const QByteArray &publicKey ) { Q_D( CertificateInfo ); d->publicKey = publicKey; } void CertificateInfo::setPublicKeyType( PublicKeyType pkType ) { Q_D( CertificateInfo ); d->pkType = pkType; } void CertificateInfo::setPublicKeyStrength( int pkStrength ) { Q_D( CertificateInfo ); d->pkStrength = pkStrength; } void CertificateInfo::setCertificateData( const QByteArray &data ) { Q_D( CertificateInfo ); d->certificateDer = data; } -class Okular::SignatureInfoPrivate -{ - public: - SignatureInfoPrivate() - : signatureStatus( SignatureInfo::SignatureStatusUnknown ), - certificateStatus( SignatureInfo::CertificateStatusUnknown ), - hashAlgorithm( SignatureInfo::HashAlgorithmUnknown ), - rangeBounds( QList() ), signingTime( QDateTime() ), signature( QByteArray() ), - subjectName( QString() ), subjectDN( QString() ), signsTotalDoc( false ) - { - } - - SignatureInfo::SignatureStatus signatureStatus; - SignatureInfo::CertificateStatus certificateStatus; - SignatureInfo::HashAlgorithm hashAlgorithm; - - CertificateInfo certInfo; - QList rangeBounds; - QDateTime signingTime; - QByteArray signature; - QString subjectName; - QString subjectDN; - bool signsTotalDoc; -}; - -SignatureInfo::SignatureInfo( SignatureInfoPrivate *priv ) - : d_ptr( priv ) +SignatureInfo::SignatureInfo() { } -SignatureInfo::SignatureInfo( const SignatureInfo &other ) - : d_ptr( other.d_ptr ) -{ -} - -SignatureInfo &SignatureInfo::operator=( const SignatureInfo &other ) -{ - if ( this != &other ) - d_ptr = other.d_ptr; - - return *this; -} - SignatureInfo::~SignatureInfo() { } -bool SignatureInfo::isValid() const -{ - return d_ptr.data() != nullptr; -} - SignatureInfo::SignatureStatus SignatureInfo::signatureStatus() const { - Q_D( const SignatureInfo ); - return d->signatureStatus; + return SignatureStatusUnknown; } SignatureInfo::CertificateStatus SignatureInfo::certificateStatus() const { - Q_D( const SignatureInfo); - return d->certificateStatus; + return CertificateStatusUnknown; } SignatureInfo::HashAlgorithm SignatureInfo::hashAlgorithm() const { - Q_D( const SignatureInfo ); - return d->hashAlgorithm; + return HashAlgorithmUnknown; } QString SignatureInfo::subjectName() const { - Q_D( const SignatureInfo ); - return d->subjectName; + return QString(); } QString SignatureInfo::subjectDN() const { - Q_D( const SignatureInfo ); - return d->subjectDN; + return QString(); } QDateTime SignatureInfo::signingTime() const { - Q_D( const SignatureInfo ); - return d->signingTime; + return QDateTime(); } QByteArray SignatureInfo::signature() const { - Q_D( const SignatureInfo ); - return d->signature; + return QByteArray(); } QList SignatureInfo::signedRangeBounds() const { - Q_D( const SignatureInfo ); - return d->rangeBounds; + return QList(); } bool SignatureInfo::signsTotalDocument() const { - Q_D( const SignatureInfo ); - return d->signsTotalDoc; + return false; } CertificateInfo SignatureInfo::certificateInfo() const { - Q_D( const SignatureInfo ); - return d->certInfo; -} - -void SignatureInfo::initPrivate() -{ - if ( d_ptr.isNull() ) - d_ptr = QSharedPointer( new SignatureInfoPrivate ); -} - -void SignatureInfo::setSignatureStatus( SignatureStatus sigStatus ) -{ - Q_D( SignatureInfo ); - d->signatureStatus = sigStatus; -} - -void SignatureInfo::setCertificateStatus( CertificateStatus certStatus ) -{ - Q_D( SignatureInfo ); - d->certificateStatus = certStatus; -} - -void SignatureInfo::setSubjectName( const QString &subjectName ) -{ - Q_D( SignatureInfo ); - d->subjectName = subjectName; -} - -void SignatureInfo::setSubjectDN( const QString &subjectDN ) -{ - Q_D( SignatureInfo ); - d->subjectDN = subjectDN; -} - -void SignatureInfo::setHashAlgorithm( HashAlgorithm hashAlgorithm ) -{ - Q_D( SignatureInfo ); - d->hashAlgorithm = hashAlgorithm; -} - -void SignatureInfo::setSigningTime( const QDateTime & signingTime) -{ - Q_D( SignatureInfo ); - d->signingTime = signingTime; -} - -void SignatureInfo::setSignature( const QByteArray &data ) -{ - Q_D( SignatureInfo ); - d->signature = data; -} - -void SignatureInfo::setSignedRangeBounds( const QList &rangeBounds ) -{ - Q_D( SignatureInfo ); - d->rangeBounds = rangeBounds; -} - -void SignatureInfo::setSignsTotalDocument( bool signsTotalDoc ) -{ - Q_D( SignatureInfo ); - d->signsTotalDoc = signsTotalDoc; -} - -void SignatureInfo::setCertificateInfo( CertificateInfo certInfo ) -{ - Q_D( SignatureInfo ); - d->certInfo = certInfo; + return CertificateInfo(); } diff --git a/core/signatureutils.h b/core/signatureutils.h index 881aaae3c..300504ff4 100644 --- a/core/signatureutils.h +++ b/core/signatureutils.h @@ -1,297 +1,272 @@ /*************************************************************************** * Copyright (C) 2018 by Chinmoy Ranjan Pradhan * * * * 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_SIGNATUREINFO_H #define OKULAR_SIGNATUREINFO_H #include "okularcore_export.h" #include #include #include #include #include namespace Okular { class CertificateInfoPrivate; class SignatureInfoPrivate; /** * @short A helper class to store information x509 digital certificate */ class OKULARCORE_EXPORT CertificateInfo { public: /** * The algorithm of public key. */ enum PublicKeyType { RsaKey, DsaKey, EcKey, OtherKey }; /** * Certificate key usage. */ enum KeyUsage { KuNone = 0, KuDigitalSignature = 1, KuNonRepudiation = 2, KuKeyEncipherment = 4, KuDataEncipherment = 8, KuKeyAgreement = 16, KuKeyCertSign = 32, KuClrSign = 64, KuEncipherOnly = 128 }; Q_DECLARE_FLAGS( KeyUsages, KeyUsage ) /** * Conclassor. */ CertificateInfo( CertificateInfoPrivate *priv = nullptr ); /** * Declassor */ virtual ~CertificateInfo(); /** * Returns true if d_ptr is not null. */ bool isValid() const; /** * The certificate version string in hex encoding. */ virtual QByteArray version() const; /** * The common name of certificate issuer. */ virtual QString issuerName() const; /** * The distinguished name of certificate issuer. */ virtual QString issuerDN() const; /** The hex encoded certificate serial number. */ virtual QByteArray serialNumber() const; /** The date-time when certificate becomes valid. */ virtual QDateTime validityStart() const; /** * The date-time when certificate expires. */ virtual QDateTime validityEnd() const; /** * The key usages of certificate. */ virtual KeyUsages keyUsages() const; /** * The public key value. */ virtual QByteArray publicKey() const; /** * The public key type. */ virtual PublicKeyType publicKeyType() const; /** * The strength of public key in bits or -1 in case * key type is 'OtherKey'. */ virtual int publicKeyStrength() const; /** * The DER encoded certificate. */ virtual QByteArray certificateData() const; CertificateInfo( const CertificateInfo &other ); virtual CertificateInfo &operator=( const CertificateInfo &other ); protected: void initPrivate(); void setVersion( const QByteArray & ); void setIssuerName( const QString & ); void setIssuerDN( const QString & ); void setSerialNumber( const QByteArray & ); void setValidityStart( const QDateTime & ); void setValidityEnd( const QDateTime & ); void setKeyUsages( KeyUsages ); void setPublicKey( const QByteArray & ); void setPublicKeyType( PublicKeyType ); void setPublicKeyStrength( int ); void setCertificateData( const QByteArray & ); private: Q_DECLARE_PRIVATE( CertificateInfo ) QSharedPointer d_ptr; }; /** * @short A helper class to store information about digital signature */ class OKULARCORE_EXPORT SignatureInfo { public: /** * The verfication result of the signature. */ enum SignatureStatus { SignatureStatusUnknown, ///< The signature status is unknown for some reason. SignatureValid, ///< The signature is cryptographically valid. SignatureInvalid, ///< The signature is cryptographically invalid. SignatureDigestMismatch, ///< The document content was changed after the signature was applied. SignatureDecodingError, ///< The signature CMS/PKCS7 classure is malformed. SignatureGenericError, ///< The signature could not be verified. SignatureNotFound, ///< The requested signature is not present in the document. SignatureNotVerified ///< The signature is not yet verified. }; /** * The verification result of the certificate. */ enum CertificateStatus { CertificateStatusUnknown, ///< The certificate status is unknown for some reason. CertificateTrusted, ///< The certificate is considered trusted. CertificateUntrustedIssuer, ///< The issuer of this certificate has been marked as untrusted by the user. CertificateUnknownIssuer, ///< The certificate trust chain has not finished in a trusted root certificate. CertificateRevoked, ///< The certificate was revoked by the issuing certificate authority. CertificateExpired, ///< The signing time is outside the validity bounds of this certificate. CertificateGenericError, ///< The certificate could not be verified. CertificateNotVerified ///< The certificate is not yet verified. }; /** * The hash algorithm of the signature */ enum HashAlgorithm { HashAlgorithmUnknown, HashAlgorithmMd2, HashAlgorithmMd5, HashAlgorithmSha1, HashAlgorithmSha256, HashAlgorithmSha384, HashAlgorithmSha512, HashAlgorithmSha224 }; /** - * Conclassor. - */ - SignatureInfo( SignatureInfoPrivate *priv = nullptr ); - - /** - * Declassor. + * Destructor. */ virtual ~SignatureInfo(); - /** - * Returns true if d_ptr is not null. - */ - bool isValid() const; - /** * The signature status of the signature. */ virtual SignatureStatus signatureStatus() const; /** * The certificate status of the signature. */ virtual CertificateStatus certificateStatus() const; /** * The signer subject common name associated with the signature. */ virtual QString subjectName() const; /** * The signer subject distinguished name associated with the signature. */ virtual QString subjectDN() const; /** * The the hash algorithm used for the signature. */ virtual HashAlgorithm hashAlgorithm() const; /** * The signing time associated with the signature. */ virtual QDateTime signingTime() const; /** * Get the signature binary data. */ virtual QByteArray signature() const; /** * Get the bounds of the ranges of the document which are signed. */ virtual QList signedRangeBounds() const; /** * Checks whether the signature authenticates the total document * except for the signature itself. */ virtual bool signsTotalDocument() const; /** * Get certificate details. */ virtual CertificateInfo certificateInfo() const; - SignatureInfo( const SignatureInfo &other ); - SignatureInfo &operator=( const SignatureInfo &other ); - protected: - void initPrivate(); - void setSignatureStatus( SignatureStatus ); - void setCertificateStatus( CertificateStatus ); - void setSubjectName( const QString & ); - void setSubjectDN( const QString & ); - void setHashAlgorithm( HashAlgorithm ); - void setSigningTime( const QDateTime & ); - void setSignature( const QByteArray & ); - void setSignedRangeBounds( const QList & ); - void setSignsTotalDocument( bool ); - void setCertificateInfo( CertificateInfo ); + SignatureInfo(); private: - Q_DECLARE_PRIVATE( SignatureInfo ) - - QSharedPointer d_ptr; + Q_DISABLE_COPY( SignatureInfo ) }; } #endif diff --git a/generators/poppler/formfields.cpp b/generators/poppler/formfields.cpp index 019ce9290..c805f9e87 100644 --- a/generators/poppler/formfields.cpp +++ b/generators/poppler/formfields.cpp @@ -1,434 +1,434 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * Copyright (C) 2018 by Intevation GmbH * * * * 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 "formfields.h" #include "core/action.h" #include "pdfsignatureutils.h" #include #include extern Okular::Action* createLinkFromPopplerLink(const Poppler::Link *popplerLink, bool deletePopplerLink = true); #ifdef HAVE_POPPLER_0_65 # define SET_ANNOT_ACTIONS \ setAdditionalAction( Okular::Annotation::CursorEntering, createLinkFromPopplerLink( field->additionalAction( Poppler::Annotation::CursorEnteringAction ) ) ); \ setAdditionalAction( Okular::Annotation::CursorLeaving, createLinkFromPopplerLink( field->additionalAction( Poppler::Annotation::CursorLeavingAction ) ) ); \ setAdditionalAction( Okular::Annotation::MousePressed, createLinkFromPopplerLink( field->additionalAction( Poppler::Annotation::MousePressedAction ) ) ); \ setAdditionalAction( Okular::Annotation::MouseReleased, createLinkFromPopplerLink( field->additionalAction( Poppler::Annotation::MouseReleasedAction ) ) ); \ setAdditionalAction( Okular::Annotation::FocusIn, createLinkFromPopplerLink( field->additionalAction( Poppler::Annotation::FocusInAction ) ) ); \ setAdditionalAction( Okular::Annotation::FocusOut, createLinkFromPopplerLink( field->additionalAction( Poppler::Annotation::FocusOutAction ) ) ); #else # define SET_ANNOT_ACTIONS #endif #ifdef HAVE_POPPLER_0_53 #define SET_ACTIONS \ setActivationAction( createLinkFromPopplerLink( field->activationAction() ) ); \ setAdditionalAction( Okular::FormField::FieldModified, createLinkFromPopplerLink( field->additionalAction( Poppler::FormField::FieldModified ) ) ); \ setAdditionalAction( Okular::FormField::FormatField, createLinkFromPopplerLink( field->additionalAction( Poppler::FormField::FormatField ) ) ); \ setAdditionalAction( Okular::FormField::ValidateField, createLinkFromPopplerLink( field->additionalAction( Poppler::FormField::ValidateField ) ) ); \ setAdditionalAction( Okular::FormField::CalculateField, createLinkFromPopplerLink( field->additionalAction( Poppler::FormField::CalculateField ) ) ); \ SET_ANNOT_ACTIONS #else #define SET_ACTIONS \ setActivationAction( createLinkFromPopplerLink( field->activationAction() ) ); #endif PopplerFormFieldButton::PopplerFormFieldButton( Poppler::FormFieldButton * field ) : Okular::FormFieldButton(), m_field( field ) { m_rect = Okular::NormalizedRect::fromQRectF( m_field->rect() ); m_id = m_field->id(); SET_ACTIONS } PopplerFormFieldButton::~PopplerFormFieldButton() { delete m_field; } Okular::NormalizedRect PopplerFormFieldButton::rect() const { return m_rect; } int PopplerFormFieldButton::id() const { return m_id; } QString PopplerFormFieldButton::name() const { return m_field->name(); } QString PopplerFormFieldButton::uiName() const { return m_field->uiName(); } bool PopplerFormFieldButton::isReadOnly() const { return m_field->isReadOnly(); } void PopplerFormFieldButton::setReadOnly( bool value ) { #ifdef HAVE_POPPLER_0_64 m_field->setReadOnly( value ); #else Q_UNUSED( value ); #endif } bool PopplerFormFieldButton::isVisible() const { return m_field->isVisible(); } void PopplerFormFieldButton::setVisible( bool value ) { #ifdef HAVE_POPPLER_0_64 m_field->setVisible( value ); #else Q_UNUSED( value ); #endif } Okular::FormFieldButton::ButtonType PopplerFormFieldButton::buttonType() const { switch ( m_field->buttonType() ) { case Poppler::FormFieldButton::Push: return Okular::FormFieldButton::Push; case Poppler::FormFieldButton::CheckBox: return Okular::FormFieldButton::CheckBox; case Poppler::FormFieldButton::Radio: return Okular::FormFieldButton::Radio; } return Okular::FormFieldButton::Push; } QString PopplerFormFieldButton::caption() const { return m_field->caption(); } bool PopplerFormFieldButton::state() const { return m_field->state(); } void PopplerFormFieldButton::setState( bool state ) { m_field->setState( state ); } QList< int > PopplerFormFieldButton::siblings() const { return m_field->siblings(); } PopplerFormFieldText::PopplerFormFieldText( Poppler::FormFieldText * field ) : Okular::FormFieldText(), m_field( field ) { m_rect = Okular::NormalizedRect::fromQRectF( m_field->rect() ); m_id = m_field->id(); SET_ACTIONS } PopplerFormFieldText::~PopplerFormFieldText() { delete m_field; } Okular::NormalizedRect PopplerFormFieldText::rect() const { return m_rect; } int PopplerFormFieldText::id() const { return m_id; } QString PopplerFormFieldText::name() const { return m_field->name(); } QString PopplerFormFieldText::uiName() const { return m_field->uiName(); } bool PopplerFormFieldText::isReadOnly() const { return m_field->isReadOnly(); } void PopplerFormFieldText::setReadOnly( bool value ) { #ifdef HAVE_POPPLER_0_64 m_field->setReadOnly( value ); #else Q_UNUSED( value ); #endif } bool PopplerFormFieldText::isVisible() const { return m_field->isVisible(); } void PopplerFormFieldText::setVisible( bool value ) { #ifdef HAVE_POPPLER_0_64 m_field->setVisible( value ); #else Q_UNUSED( value ); #endif } Okular::FormFieldText::TextType PopplerFormFieldText::textType() const { switch ( m_field->textType() ) { case Poppler::FormFieldText::Normal: return Okular::FormFieldText::Normal; case Poppler::FormFieldText::Multiline: return Okular::FormFieldText::Multiline; case Poppler::FormFieldText::FileSelect: return Okular::FormFieldText::FileSelect; } return Okular::FormFieldText::Normal; } QString PopplerFormFieldText::text() const { return m_field->text(); } void PopplerFormFieldText::setText( const QString& text ) { m_field->setText( text ); } bool PopplerFormFieldText::isPassword() const { return m_field->isPassword(); } bool PopplerFormFieldText::isRichText() const { return m_field->isRichText(); } int PopplerFormFieldText::maximumLength() const { return m_field->maximumLength(); } Qt::Alignment PopplerFormFieldText::textAlignment() const { return Qt::AlignTop | m_field->textAlignment(); } bool PopplerFormFieldText::canBeSpellChecked() const { return m_field->canBeSpellChecked(); } PopplerFormFieldChoice::PopplerFormFieldChoice( Poppler::FormFieldChoice * field ) : Okular::FormFieldChoice(), m_field( field ) { m_rect = Okular::NormalizedRect::fromQRectF( m_field->rect() ); m_id = m_field->id(); SET_ACTIONS } PopplerFormFieldChoice::~PopplerFormFieldChoice() { delete m_field; } Okular::NormalizedRect PopplerFormFieldChoice::rect() const { return m_rect; } int PopplerFormFieldChoice::id() const { return m_id; } QString PopplerFormFieldChoice::name() const { return m_field->name(); } QString PopplerFormFieldChoice::uiName() const { return m_field->uiName(); } bool PopplerFormFieldChoice::isReadOnly() const { return m_field->isReadOnly(); } void PopplerFormFieldChoice::setReadOnly( bool value ) { #ifdef HAVE_POPPLER_0_64 m_field->setReadOnly( value ); #else Q_UNUSED( value ); #endif } bool PopplerFormFieldChoice::isVisible() const { return m_field->isVisible(); } void PopplerFormFieldChoice::setVisible( bool value ) { #ifdef HAVE_POPPLER_0_64 m_field->setVisible( value ); #else Q_UNUSED( value ); #endif } Okular::FormFieldChoice::ChoiceType PopplerFormFieldChoice::choiceType() const { switch ( m_field->choiceType() ) { case Poppler::FormFieldChoice::ComboBox: return Okular::FormFieldChoice::ComboBox; case Poppler::FormFieldChoice::ListBox: return Okular::FormFieldChoice::ListBox; } return Okular::FormFieldChoice::ListBox; } QStringList PopplerFormFieldChoice::choices() const { return m_field->choices(); } bool PopplerFormFieldChoice::isEditable() const { return m_field->isEditable(); } bool PopplerFormFieldChoice::multiSelect() const { return m_field->multiSelect(); } QList PopplerFormFieldChoice::currentChoices() const { return m_field->currentChoices(); } void PopplerFormFieldChoice::setCurrentChoices( const QList& choices ) { m_field->setCurrentChoices( choices ); } QString PopplerFormFieldChoice::editChoice() const { return m_field->editChoice(); } void PopplerFormFieldChoice::setEditChoice( const QString& text ) { m_field->setEditChoice( text ); } Qt::Alignment PopplerFormFieldChoice::textAlignment() const { return Qt::AlignTop | m_field->textAlignment(); } bool PopplerFormFieldChoice::canBeSpellChecked() const { return m_field->canBeSpellChecked(); } PopplerFormFieldSignature::PopplerFormFieldSignature( Poppler::FormFieldSignature * field ) : Okular::FormFieldSignature(), m_field( field ) { m_rect = Okular::NormalizedRect::fromQRectF( m_field->rect() ); m_id = m_field->id(); SET_ACTIONS } PopplerFormFieldSignature::~PopplerFormFieldSignature() { delete m_field; } Okular::NormalizedRect PopplerFormFieldSignature::rect() const { return m_rect; } int PopplerFormFieldSignature::id() const { return m_id; } QString PopplerFormFieldSignature::name() const { return m_field->name(); } QString PopplerFormFieldSignature::uiName() const { return m_field->uiName(); } bool PopplerFormFieldSignature::isReadOnly() const { return m_field->isReadOnly(); } bool PopplerFormFieldSignature::isVisible() const { return m_field->isVisible(); } PopplerFormFieldSignature::SignatureType PopplerFormFieldSignature::signatureType() const { switch ( m_field->signatureType() ) { case Poppler::FormFieldSignature::AdbePkcs7sha1: return Okular::FormFieldSignature::AdbePkcs7sha1; case Poppler::FormFieldSignature::AdbePkcs7detached: return Okular::FormFieldSignature::AdbePkcs7detached; case Poppler::FormFieldSignature::EtsiCAdESdetached: return Okular::FormFieldSignature::EtsiCAdESdetached; default: return Okular::FormFieldSignature::UnknownType; } } -Okular::SignatureInfo PopplerFormFieldSignature::validate() const +Okular::SignatureInfo *PopplerFormFieldSignature::validate() const { auto sigInfo = m_field->validate( Poppler::FormFieldSignature::ValidateVerifyCertificate ); - return PopplerSignatureInfo( sigInfo ); + return ( new PopplerSignatureInfo( sigInfo ) ); } diff --git a/generators/poppler/formfields.h b/generators/poppler/formfields.h index a34a607e8..929c99aed 100644 --- a/generators/poppler/formfields.h +++ b/generators/poppler/formfields.h @@ -1,139 +1,139 @@ /*************************************************************************** * 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_GENERATOR_PDF_FORMFIELDS_H_ #define _OKULAR_GENERATOR_PDF_FORMFIELDS_H_ #include #include "core/form.h" #include "core/signatureutils.h" class PopplerFormFieldButton : public Okular::FormFieldButton { public: PopplerFormFieldButton( Poppler::FormFieldButton * field ); virtual ~PopplerFormFieldButton(); // inherited from Okular::FormField Okular::NormalizedRect rect() const override; int id() const override; QString name() const override; QString uiName() const override; bool isReadOnly() const override; void setReadOnly( bool value ) override; bool isVisible() const override; void setVisible( bool value ) override; // inherited from Okular::FormFieldButton ButtonType buttonType() const override; QString caption() const override; bool state() const override; void setState( bool state ) override; QList< int > siblings() const override; private: Poppler::FormFieldButton * m_field; Okular::NormalizedRect m_rect; int m_id; }; class PopplerFormFieldText : public Okular::FormFieldText { public: PopplerFormFieldText( Poppler::FormFieldText * field ); virtual ~PopplerFormFieldText(); // inherited from Okular::FormField Okular::NormalizedRect rect() const override; int id() const override; QString name() const override; QString uiName() const override; bool isReadOnly() const override; void setReadOnly( bool value ) override; bool isVisible() const override; void setVisible( bool value ) override; // inherited from Okular::FormFieldText Okular::FormFieldText::TextType textType() const override; QString text() const override; void setText( const QString& text ) override; bool isPassword() const override; bool isRichText() const override; int maximumLength() const override; Qt::Alignment textAlignment() const override; bool canBeSpellChecked() const override; private: Poppler::FormFieldText * m_field; Okular::NormalizedRect m_rect; int m_id; }; class PopplerFormFieldChoice : public Okular::FormFieldChoice { public: PopplerFormFieldChoice( Poppler::FormFieldChoice * field ); virtual ~PopplerFormFieldChoice(); // inherited from Okular::FormField Okular::NormalizedRect rect() const override; int id() const override; QString name() const override; QString uiName() const override; bool isReadOnly() const override; void setReadOnly( bool value ) override; bool isVisible() const override; void setVisible( bool value ) override; // inherited from Okular::FormFieldChoice ChoiceType choiceType() const override; QStringList choices() const override; bool isEditable() const override; bool multiSelect() const override; QList currentChoices() const override; void setCurrentChoices( const QList& choices ) override; QString editChoice() const override; void setEditChoice( const QString& text ) override; Qt::Alignment textAlignment() const override; bool canBeSpellChecked() const override; private: Poppler::FormFieldChoice * m_field; Okular::NormalizedRect m_rect; int m_id; }; class PopplerFormFieldSignature : public Okular::FormFieldSignature { public: PopplerFormFieldSignature( Poppler::FormFieldSignature * field ); virtual ~PopplerFormFieldSignature(); // inherited from Okular::FormField Okular::NormalizedRect rect() const override; int id() const override; QString name() const override; QString uiName() const override; bool isReadOnly() const override; bool isVisible() const override; // inherited from Okular::FormFieldSignature SignatureType signatureType() const override; - Okular::SignatureInfo validate() const override; + Okular::SignatureInfo *validate() const override; private: Poppler::FormFieldSignature * m_field; Okular::NormalizedRect m_rect; int m_id; }; #endif diff --git a/generators/poppler/pdfsignatureutils.cpp b/generators/poppler/pdfsignatureutils.cpp index 12e4fcc9a..8eeecd59c 100644 --- a/generators/poppler/pdfsignatureutils.cpp +++ b/generators/poppler/pdfsignatureutils.cpp @@ -1,160 +1,215 @@ /*************************************************************************** * Copyright (C) 2018 by Chinmoy Ranjan Pradhan * * * * 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 "pdfsignatureutils.h" PopplerCertificateInfo::PopplerCertificateInfo( const Poppler::CertificateInfo &info ) : Okular::CertificateInfo() { initPrivate(); setVersion( info.version() ); setIssuerName( info.issuerName() ); setIssuerDN( info.issuerDN() ); setSerialNumber( info.serialNumber() ); setValidityStart( info.validityStart() ); setValidityEnd( info.validityEnd() ); setKeyUsages( convertToOkularKeyUsages( info.keyUsages() ) ); setPublicKey( info.publicKey() ); setPublicKeyStrength( info.publicKeyStrength() ); setPublicKeyType( convertToOkularPublicKeyType( info.publicKeyType() ) ); setCertificateData( info.certificateData() ); } PopplerCertificateInfo::~PopplerCertificateInfo() { } PopplerCertificateInfo::KeyUsages PopplerCertificateInfo::convertToOkularKeyUsages( Poppler::CertificateInfo::KeyUsages popplerKu ) { KeyUsages ku = KuNone; if ( popplerKu.testFlag( Poppler::CertificateInfo::KuDigitalSignature ) ) ku |= KuDigitalSignature; if ( popplerKu.testFlag( Poppler::CertificateInfo::KuNonRepudiation ) ) ku |= KuNonRepudiation; if ( popplerKu.testFlag( Poppler::CertificateInfo::KuKeyEncipherment ) ) ku |= KuKeyEncipherment; if ( popplerKu.testFlag( Poppler::CertificateInfo::KuDataEncipherment ) ) ku |= KuDataEncipherment; if ( popplerKu.testFlag( Poppler::CertificateInfo::KuKeyAgreement ) ) ku |= KuKeyAgreement; if ( popplerKu.testFlag( Poppler::CertificateInfo::KuKeyCertSign ) ) ku |= KuKeyCertSign; if ( popplerKu.testFlag( Poppler::CertificateInfo::KuClrSign ) ) ku |= KuClrSign; if ( popplerKu.testFlag( Poppler::CertificateInfo::KuEncipherOnly ) ) ku |= KuEncipherOnly; return ku; } PopplerCertificateInfo::PublicKeyType PopplerCertificateInfo::convertToOkularPublicKeyType( Poppler::CertificateInfo::PublicKeyType popplerPkType ) { switch ( popplerPkType ) { case Poppler::CertificateInfo::RsaKey: return RsaKey; case Poppler::CertificateInfo::DsaKey: return DsaKey; case Poppler::CertificateInfo::EcKey: return EcKey; case Poppler::CertificateInfo::OtherKey: return OtherKey; } } -PopplerSignatureInfo::PopplerSignatureInfo(const Poppler::SignatureValidationInfo &info) - : Okular::SignatureInfo() +class PopplerSignatureInfoPrivate { - initPrivate(); + public: + QList rangeBounds; + QDateTime signingTime; + QByteArray signature; + QString subjectCN; + QString subjectDN; + int signatureStatus; + int certficateStatus; + int hashAlgorithm; + bool signsTotalDoc; +}; - setSignatureStatus( convertToOkularSigStatus( info.signatureStatus() ) ); - setCertificateStatus( convertToOkularCertStatus( info.certificateStatus() ) ); - setSubjectName( info.signerName() ); - setSubjectDN( info.signerSubjectDN() ); - setHashAlgorithm( convertToOkularHashAlg( info.hashAlgorithm() ) ); - setSigningTime( QDateTime::fromTime_t( info.signingTime() ) ); - setSignature( info.signature() ); - setSignedRangeBounds( info.signedRangeBounds() ); - setSignsTotalDocument( info.signsTotalDocument() ); - setCertificateInfo( PopplerCertificateInfo( info.certificateInfo() ) ); +PopplerSignatureInfo::PopplerSignatureInfo( const Poppler::SignatureValidationInfo &info ) + : d_ptr( new PopplerSignatureInfoPrivate() ) +{ + Q_D( PopplerSignatureInfo ); + d->signatureStatus = info.signatureStatus(); + d->certficateStatus = info.certificateStatus(); + d->subjectCN = info.signerName(); + d->subjectDN = info.signerSubjectDN(); + d->hashAlgorithm = info.hashAlgorithm(); + d->signingTime = QDateTime::fromTime_t( info.signingTime() ); + d->signature = info.signature(); + d->rangeBounds = info.signedRangeBounds(); + d->signsTotalDoc = info.signsTotalDocument(); } PopplerSignatureInfo::~PopplerSignatureInfo() { } -PopplerSignatureInfo::SignatureStatus PopplerSignatureInfo::convertToOkularSigStatus( Poppler::SignatureValidationInfo::SignatureStatus popplerSigStatus) +PopplerSignatureInfo::SignatureStatus PopplerSignatureInfo::signatureStatus() const { - switch( popplerSigStatus ) + Q_D( const PopplerSignatureInfo ); + switch ( d->signatureStatus ) { case Poppler::SignatureValidationInfo::SignatureValid: return SignatureValid; case Poppler::SignatureValidationInfo::SignatureInvalid: return SignatureInvalid; case Poppler::SignatureValidationInfo::SignatureDigestMismatch: return SignatureDigestMismatch; case Poppler::SignatureValidationInfo::SignatureDecodingError: return SignatureDecodingError; case Poppler::SignatureValidationInfo::SignatureGenericError: return SignatureGenericError; case Poppler::SignatureValidationInfo::SignatureNotFound: return SignatureNotFound; case Poppler::SignatureValidationInfo::SignatureNotVerified: return SignatureNotVerified; default: return SignatureStatusUnknown; } } -PopplerSignatureInfo::CertificateStatus PopplerSignatureInfo::convertToOkularCertStatus( Poppler::SignatureValidationInfo::CertificateStatus popplerCertStatus ) +PopplerSignatureInfo::CertificateStatus PopplerSignatureInfo::certificateStatus() const { - switch( popplerCertStatus ) + Q_D( const PopplerSignatureInfo ); + switch ( d->certficateStatus ) { case Poppler::SignatureValidationInfo::CertificateTrusted: return CertificateTrusted; case Poppler::SignatureValidationInfo::CertificateUntrustedIssuer: return CertificateUntrustedIssuer; case Poppler::SignatureValidationInfo::CertificateUnknownIssuer: return CertificateUnknownIssuer; case Poppler::SignatureValidationInfo::CertificateRevoked: return CertificateRevoked; case Poppler::SignatureValidationInfo::CertificateExpired: return CertificateExpired; case Poppler::SignatureValidationInfo::CertificateGenericError: return CertificateGenericError; case Poppler::SignatureValidationInfo::CertificateNotVerified: return CertificateNotVerified; default: return CertificateStatusUnknown; } } -PopplerSignatureInfo::HashAlgorithm PopplerSignatureInfo::convertToOkularHashAlg( Poppler::SignatureValidationInfo::HashAlgorithm popplerHashAlg ) +PopplerSignatureInfo::HashAlgorithm PopplerSignatureInfo::hashAlgorithm() const { - switch( popplerHashAlg ) + Q_D( const PopplerSignatureInfo ); + switch ( d->hashAlgorithm ) { case Poppler::SignatureValidationInfo::HashAlgorithmMd2: return HashAlgorithmMd2; case Poppler::SignatureValidationInfo::HashAlgorithmMd5: return HashAlgorithmMd5; case Poppler::SignatureValidationInfo::HashAlgorithmSha1: return HashAlgorithmSha1; case Poppler::SignatureValidationInfo::HashAlgorithmSha256: return HashAlgorithmSha256; case Poppler::SignatureValidationInfo::HashAlgorithmSha384: return HashAlgorithmSha384; case Poppler::SignatureValidationInfo::HashAlgorithmSha512: return HashAlgorithmSha512; case Poppler::SignatureValidationInfo::HashAlgorithmSha224: return HashAlgorithmSha224; - case Poppler::SignatureValidationInfo::HashAlgorithmUnknown: + default: return HashAlgorithmUnknown; } } + +QString PopplerSignatureInfo::subjectName() const +{ + Q_D( const PopplerSignatureInfo ); + return d->subjectCN; +} +QString PopplerSignatureInfo::subjectDN() const +{ + Q_D( const PopplerSignatureInfo ); + return d->subjectDN; +} + +QDateTime PopplerSignatureInfo::signingTime() const +{ + Q_D( const PopplerSignatureInfo ); + return d->signingTime; +} + +QByteArray PopplerSignatureInfo::signature() const +{ + Q_D( const PopplerSignatureInfo ); + return d->signature; +} + +QList PopplerSignatureInfo::signedRangeBounds() const +{ + Q_D( const PopplerSignatureInfo ); + return d->rangeBounds; +} + +bool PopplerSignatureInfo::signsTotalDocument() const +{ + Q_D( const PopplerSignatureInfo ); + return d->signsTotalDoc; +} + +Okular::CertificateInfo PopplerSignatureInfo::certificateInfo() const +{ + return Okular::CertificateInfo(); +} diff --git a/generators/poppler/pdfsignatureutils.h b/generators/poppler/pdfsignatureutils.h index 4d1f4c985..877410d00 100644 --- a/generators/poppler/pdfsignatureutils.h +++ b/generators/poppler/pdfsignatureutils.h @@ -1,43 +1,52 @@ /*************************************************************************** * Copyright (C) 2018 by Chinmoy Ranjan Pradhan * * * * 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_SIGNATUREINFO_H_ #define _OKULAR_GENERATOR_PDF_SIGNATUREINFO_H_ #include #include "core/signatureutils.h" class PopplerCertificateInfo : public Okular::CertificateInfo { public: PopplerCertificateInfo(const Poppler::CertificateInfo &info); ~PopplerCertificateInfo(); private: KeyUsages convertToOkularKeyUsages( Poppler::CertificateInfo::KeyUsages ); PublicKeyType convertToOkularPublicKeyType( Poppler::CertificateInfo::PublicKeyType ); - - Poppler::CertificateInfo *info; }; +class PopplerSignatureInfoPrivate; + class PopplerSignatureInfo : public Okular::SignatureInfo { public: - PopplerSignatureInfo(const Poppler::SignatureValidationInfo &info); - ~PopplerSignatureInfo(); + PopplerSignatureInfo( const Poppler::SignatureValidationInfo &info ); + virtual ~PopplerSignatureInfo(); + + SignatureStatus signatureStatus() const override; + CertificateStatus certificateStatus() const override; + QString subjectName() const override; + QString subjectDN() const override; + HashAlgorithm hashAlgorithm() const override; + QDateTime signingTime() const override; + QByteArray signature() const override; + QList signedRangeBounds() const override; + bool signsTotalDocument() const override; + Okular::CertificateInfo certificateInfo() const override; private: - SignatureStatus convertToOkularSigStatus( Poppler::SignatureValidationInfo::SignatureStatus ); - CertificateStatus convertToOkularCertStatus( Poppler::SignatureValidationInfo::CertificateStatus ); - HashAlgorithm convertToOkularHashAlg( Poppler::SignatureValidationInfo::HashAlgorithm ); - + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE( PopplerSignatureInfo ) }; #endif diff --git a/ui/formwidgets.cpp b/ui/formwidgets.cpp index ef16253a7..02acd2428 100644 --- a/ui/formwidgets.cpp +++ b/ui/formwidgets.cpp @@ -1,1243 +1,1246 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * 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 * * Copyright (C) 2018 Intevation GmbH * * * * 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 "formwidgets.h" #include "pageviewutils.h" #include "signaturewidgets.h" #include #include #include #include #include #include #include #include #include #include // local includes #include "core/form.h" #include "core/document.h" +#include "core/signatureutils.h" #include "debug_ui.h" FormWidgetsController::FormWidgetsController( Okular::Document *doc ) : QObject( doc ), m_doc( doc ) { // emit changed signal when a form has changed connect( this, &FormWidgetsController::formTextChangedByUndoRedo, this, &FormWidgetsController::changed ); connect( this, &FormWidgetsController::formListChangedByUndoRedo, this, &FormWidgetsController::changed ); connect( this, &FormWidgetsController::formComboChangedByUndoRedo, this, &FormWidgetsController::changed ); // connect form modification signals to and from document connect( this, &FormWidgetsController::formTextChangedByWidget, doc, &Okular::Document::editFormText ); connect( doc, &Okular::Document::formTextChangedByUndoRedo, this, &FormWidgetsController::formTextChangedByUndoRedo ); connect( this, &FormWidgetsController::formListChangedByWidget, doc, &Okular::Document::editFormList ); connect( doc, &Okular::Document::formListChangedByUndoRedo, this, &FormWidgetsController::formListChangedByUndoRedo ); connect( this, &FormWidgetsController::formComboChangedByWidget, doc, &Okular::Document::editFormCombo ); connect( doc, &Okular::Document::formComboChangedByUndoRedo, this, &FormWidgetsController::formComboChangedByUndoRedo ); connect( this, &FormWidgetsController::formButtonsChangedByWidget, doc, &Okular::Document::editFormButtons ); connect( doc, &Okular::Document::formButtonsChangedByUndoRedo, this, &FormWidgetsController::slotFormButtonsChangedByUndoRedo ); // Connect undo/redo signals connect( this, &FormWidgetsController::requestUndo, doc, &Okular::Document::undo ); connect( this, &FormWidgetsController::requestRedo, doc, &Okular::Document::redo ); connect( doc, &Okular::Document::canUndoChanged, this, &FormWidgetsController::canUndoChanged ); connect( doc, &Okular::Document::canRedoChanged, this, &FormWidgetsController::canRedoChanged ); // Connect the generic formWidget refresh signal connect( doc, &Okular::Document::refreshFormWidget, this, &FormWidgetsController::refreshFormWidget ); } FormWidgetsController::~FormWidgetsController() { } void FormWidgetsController::signalAction( Okular::Action *a ) { emit action( a ); } void FormWidgetsController::registerRadioButton( FormWidgetIface *fwButton, Okular::FormFieldButton *formButton ) { if ( !fwButton ) return; QAbstractButton *button = dynamic_cast(fwButton); if ( !button ) { qWarning() << "fwButton is not a QAbstractButton" << fwButton; return; } QList< RadioData >::iterator it = m_radios.begin(), itEnd = m_radios.end(); const int id = formButton->id(); m_buttons.insert( id, button ); for ( ; it != itEnd; ++it ) { const QList< int >::const_iterator idsIt = qFind( (*it).ids, id ); if ( idsIt != (*it).ids.constEnd() ) { qCDebug(OkularUiDebug) << "Adding id" << id << "To group including" << (*it).ids; (*it).group->addButton( button ); (*it).group->setId( button, id ); return; } } const QList< int > siblings = formButton->siblings(); RadioData newdata; newdata.ids = siblings; newdata.ids.append( id ); newdata.group = new QButtonGroup(); newdata.group->addButton( button ); newdata.group->setId( button, id ); // Groups of 1 (like checkboxes) can't be exclusive if (siblings.isEmpty()) newdata.group->setExclusive( false ); connect( newdata.group, SIGNAL( buttonClicked(QAbstractButton* ) ), this, SLOT( slotButtonClicked( QAbstractButton* ) ) ); m_radios.append( newdata ); } void FormWidgetsController::dropRadioButtons() { QList< RadioData >::iterator it = m_radios.begin(), itEnd = m_radios.end(); for ( ; it != itEnd; ++it ) { delete (*it).group; } m_radios.clear(); m_buttons.clear(); } bool FormWidgetsController::canUndo() { return m_doc->canUndo(); } bool FormWidgetsController::canRedo() { return m_doc->canRedo(); } void FormWidgetsController::slotButtonClicked( QAbstractButton *button ) { int pageNumber = -1; CheckBoxEdit *check = qobject_cast< CheckBoxEdit * >( button ); if ( check ) { // Checkboxes need to be uncheckable so if clicking a checked one // disable the exclusive status temporarily and uncheck it Okular::FormFieldButton *formButton = static_cast( check->formField() ); if ( formButton->state() ) { const bool wasExclusive = button->group()->exclusive(); button->group()->setExclusive(false); check->setChecked(false); button->group()->setExclusive(wasExclusive); } pageNumber = check->pageItem()->pageNumber(); } else if ( RadioButtonEdit *radio = qobject_cast< RadioButtonEdit * >( button ) ) { pageNumber = radio->pageItem()->pageNumber(); } const QList< QAbstractButton* > buttons = button->group()->buttons(); QList< bool > checked; QList< bool > prevChecked; QList< Okular::FormFieldButton*> formButtons; foreach ( QAbstractButton* button, buttons ) { checked.append( button->isChecked() ); Okular::FormFieldButton *formButton = static_cast( dynamic_cast(button)->formField() ); formButtons.append( formButton ); prevChecked.append( formButton->state() ); } if (checked != prevChecked) emit formButtonsChangedByWidget( pageNumber, formButtons, checked ); if ( check ) { // The formButtonsChangedByWidget signal changes the value of the underlying // Okular::FormField of the checkbox. We need to execute the activiation // action after this. check->doActivateAction(); } } void FormWidgetsController::slotFormButtonsChangedByUndoRedo( int pageNumber, const QList< Okular::FormFieldButton* > & formButtons) { foreach ( Okular::FormFieldButton* formButton, formButtons ) { int id = formButton->id(); QAbstractButton* button = m_buttons[id]; CheckBoxEdit *check = qobject_cast< CheckBoxEdit * >( button ); if ( check ) { emit refreshFormWidget( check->formField() ); } // temporarily disable exclusiveness of the button group // since it breaks doing/redoing steps into which all the checkboxes // are unchecked const bool wasExclusive = button->group()->exclusive(); button->group()->setExclusive(false); bool checked = formButton->state(); button->setChecked( checked ); button->group()->setExclusive(wasExclusive); button->setFocus(); } emit changed( pageNumber ); } FormWidgetIface * FormWidgetFactory::createWidget( Okular::FormField * ff, QWidget * parent ) { FormWidgetIface * widget = nullptr; switch ( ff->type() ) { case Okular::FormField::FormButton: { Okular::FormFieldButton * ffb = static_cast< Okular::FormFieldButton * >( ff ); switch ( ffb->buttonType() ) { case Okular::FormFieldButton::Push: widget = new PushButtonEdit( ffb, parent ); break; case Okular::FormFieldButton::CheckBox: widget = new CheckBoxEdit( ffb, parent ); break; case Okular::FormFieldButton::Radio: widget = new RadioButtonEdit( ffb, parent ); break; default: ; } break; } case Okular::FormField::FormText: { Okular::FormFieldText * fft = static_cast< Okular::FormFieldText * >( ff ); switch ( fft->textType() ) { case Okular::FormFieldText::Multiline: widget = new TextAreaEdit( fft, parent ); break; case Okular::FormFieldText::Normal: widget = new FormLineEdit( fft, parent ); break; case Okular::FormFieldText::FileSelect: widget = new FileEdit( fft, parent ); break; } break; } case Okular::FormField::FormChoice: { Okular::FormFieldChoice * ffc = static_cast< Okular::FormFieldChoice * >( ff ); switch ( ffc->choiceType() ) { case Okular::FormFieldChoice::ListBox: widget = new ListEdit( ffc, parent ); break; case Okular::FormFieldChoice::ComboBox: widget = new ComboEdit( ffc, parent ); break; } break; } case Okular::FormField::FormSignature: { Okular::FormFieldSignature * ffs = static_cast< Okular::FormFieldSignature * >( ff ); if ( ffs->isVisible() && ffs->signatureType() != Okular::FormFieldSignature::UnknownType ) widget = new SignatureEdit( ffs, parent ); break; } default: ; } if ( ff->isReadOnly() ) widget->setVisibility( false ); return widget; } FormWidgetIface::FormWidgetIface( QWidget * w, Okular::FormField * ff ) : m_controller( nullptr ), m_ff( ff ), m_widget( w ), m_pageItem( nullptr ) { } FormWidgetIface::~FormWidgetIface() { m_ff = nullptr; } Okular::NormalizedRect FormWidgetIface::rect() const { return m_ff->rect(); } void FormWidgetIface::setWidthHeight( int w, int h ) { m_widget->resize( w, h ); } void FormWidgetIface::moveTo( int x, int y ) { m_widget->move( x, y ); } bool FormWidgetIface::setVisibility( bool visible ) { bool hadfocus = m_widget->hasFocus(); if ( hadfocus ) m_widget->clearFocus(); m_widget->setVisible( visible ); return hadfocus; } void FormWidgetIface::setCanBeFilled( bool fill ) { m_widget->setEnabled( fill ); } void FormWidgetIface::setPageItem( PageViewItem *pageItem ) { m_pageItem = pageItem; } void FormWidgetIface::setFormField( Okular::FormField *field ) { m_ff = field; } Okular::FormField* FormWidgetIface::formField() const { return m_ff; } PageViewItem* FormWidgetIface::pageItem() const { return m_pageItem; } void FormWidgetIface::setFormWidgetsController( FormWidgetsController *controller ) { m_controller = controller; QObject *obj = dynamic_cast< QObject * > ( this ); QObject::connect( m_controller, &FormWidgetsController::refreshFormWidget, obj, [this] ( Okular::FormField *form ) { slotRefresh ( form ); }); } void FormWidgetIface::slotRefresh( Okular::FormField * form ) { if ( m_ff != form ) { return; } setVisibility( form->isVisible() && !form->isReadOnly() ); m_widget->setEnabled( !form->isReadOnly() ); } PushButtonEdit::PushButtonEdit( Okular::FormFieldButton * button, QWidget * parent ) : QPushButton( parent ), FormWidgetIface( this, button ) { setText( button->caption() ); setVisible( button->isVisible() ); setCursor( Qt::ArrowCursor ); } CheckBoxEdit::CheckBoxEdit( Okular::FormFieldButton * button, QWidget * parent ) : QCheckBox( parent ), FormWidgetIface( this, button ) { setText( button->caption() ); setVisible( button->isVisible() ); setCursor( Qt::ArrowCursor ); } void CheckBoxEdit::setFormWidgetsController( FormWidgetsController *controller ) { Okular::FormFieldButton *form = static_cast(m_ff); FormWidgetIface::setFormWidgetsController( controller ); m_controller->registerRadioButton( this, form ); setChecked( form->state() ); } void CheckBoxEdit::doActivateAction() { Okular::FormFieldButton *form = static_cast(m_ff); if ( form->activationAction() ) m_controller->signalAction( form->activationAction() ); } void CheckBoxEdit::slotRefresh( Okular::FormField * form ) { if ( form != m_ff ) { return; } FormWidgetIface::slotRefresh( form ); Okular::FormFieldButton *button = static_cast(m_ff); bool oldState = isChecked(); bool newState = button->state(); if ( oldState != newState ) { setChecked( button->state() ); doActivateAction(); } } RadioButtonEdit::RadioButtonEdit( Okular::FormFieldButton * button, QWidget * parent ) : QRadioButton( parent ), FormWidgetIface( this, button ) { setText( button->caption() ); setVisible( button->isVisible() ); setCursor( Qt::ArrowCursor ); } void RadioButtonEdit::setFormWidgetsController( FormWidgetsController *controller ) { Okular::FormFieldButton *form = static_cast(m_ff); FormWidgetIface::setFormWidgetsController( controller ); m_controller->registerRadioButton( this, form ); setChecked( form->state() ); } FormLineEdit::FormLineEdit( Okular::FormFieldText * text, QWidget * parent ) : QLineEdit( parent ), FormWidgetIface( this, text ) { int maxlen = text->maximumLength(); if ( maxlen >= 0 ) setMaxLength( maxlen ); setAlignment( text->textAlignment() ); setText( text->text() ); if ( text->isPassword() ) setEchoMode( QLineEdit::Password ); m_prevCursorPos = cursorPosition(); m_prevAnchorPos = cursorPosition(); connect( this, &QLineEdit::textEdited, this, &FormLineEdit::slotChanged ); connect( this, &QLineEdit::cursorPositionChanged, this, &FormLineEdit::slotChanged ); setVisible( text->isVisible() ); } void FormLineEdit::setFormWidgetsController(FormWidgetsController* controller) { FormWidgetIface::setFormWidgetsController(controller); connect( m_controller, &FormWidgetsController::formTextChangedByUndoRedo, this, &FormLineEdit::slotHandleTextChangedByUndoRedo ); } bool FormLineEdit::event( QEvent* e ) { if ( e->type() == QEvent::KeyPress ) { QKeyEvent *keyEvent = static_cast< QKeyEvent* >( e ); if ( keyEvent == QKeySequence::Undo ) { emit m_controller->requestUndo(); return true; } else if ( keyEvent == QKeySequence::Redo ) { emit m_controller->requestRedo(); return true; } } return QLineEdit::event( e ); } void FormLineEdit::contextMenuEvent( QContextMenuEvent* event ) { QMenu *menu = createStandardContextMenu(); QList actionList = menu->actions(); enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, DeleteAct, SelectAllAct }; QAction *kundo = KStandardAction::create( KStandardAction::Undo, m_controller, SIGNAL( requestUndo() ), menu ); QAction *kredo = KStandardAction::create( KStandardAction::Redo, m_controller, SIGNAL( requestRedo() ), menu ); connect( m_controller, &FormWidgetsController::canUndoChanged, kundo, &QAction::setEnabled ); connect( m_controller, &FormWidgetsController::canRedoChanged, kredo, &QAction::setEnabled ); kundo->setEnabled( m_controller->canUndo() ); kredo->setEnabled( m_controller->canRedo() ); QAction *oldUndo, *oldRedo; oldUndo = actionList[UndoAct]; oldRedo = actionList[RedoAct]; menu->insertAction( oldUndo, kundo ); menu->insertAction( oldRedo, kredo ); menu->removeAction( oldUndo ); menu->removeAction( oldRedo ); menu->exec( event->globalPos() ); delete menu; } void FormLineEdit::slotChanged() { Okular::FormFieldText *form = static_cast(m_ff); QString contents = text(); int cursorPos = cursorPosition(); if ( contents != form->text() ) { m_controller->formTextChangedByWidget( pageItem()->pageNumber(), form, contents, cursorPos, m_prevCursorPos, m_prevAnchorPos ); } m_prevCursorPos = cursorPos; m_prevAnchorPos = cursorPos; if ( hasSelectedText() ) { if ( cursorPos == selectionStart() ) { m_prevAnchorPos = selectionStart() + selectedText().size(); } else { m_prevAnchorPos = selectionStart(); } } } void FormLineEdit::slotHandleTextChangedByUndoRedo( int pageNumber, Okular::FormFieldText* textForm, const QString & contents, int cursorPos, int anchorPos ) { Q_UNUSED(pageNumber); if ( textForm != m_ff || contents == text() ) { return; } disconnect( this, &QLineEdit::cursorPositionChanged, this, &FormLineEdit::slotChanged ); setText(contents); setCursorPosition(anchorPos); cursorForward( true, cursorPos - anchorPos ); connect( this, &QLineEdit::cursorPositionChanged, this, &FormLineEdit::slotChanged ); m_prevCursorPos = cursorPos; m_prevAnchorPos = anchorPos; setFocus(); } void FormLineEdit::slotRefresh( Okular::FormField *form ) { if (form != m_ff) { return; } FormWidgetIface::slotRefresh( form ); Okular::FormFieldText *text = static_cast ( form ); setText( text->text() ); } TextAreaEdit::TextAreaEdit( Okular::FormFieldText * text, QWidget * parent ) : KTextEdit( parent ), FormWidgetIface( this, text ) { setAcceptRichText( text->isRichText() ); setCheckSpellingEnabled( text->canBeSpellChecked() ); setAlignment( text->textAlignment() ); setPlainText( text->text() ); setUndoRedoEnabled( false ); connect( this, &QTextEdit::textChanged, this, &TextAreaEdit::slotChanged ); connect( this, &QTextEdit::cursorPositionChanged, this, &TextAreaEdit::slotChanged ); connect( this, &KTextEdit::aboutToShowContextMenu, this, &TextAreaEdit::slotUpdateUndoAndRedoInContextMenu ); m_prevCursorPos = textCursor().position(); m_prevAnchorPos = textCursor().anchor(); setVisible( text->isVisible() ); } bool TextAreaEdit::event( QEvent* e ) { if ( e->type() == QEvent::KeyPress ) { QKeyEvent *keyEvent = static_cast< QKeyEvent* >(e); if ( keyEvent == QKeySequence::Undo ) { emit m_controller->requestUndo(); return true; } else if ( keyEvent == QKeySequence::Redo ) { emit m_controller->requestRedo(); return true; } } return KTextEdit::event( e ); } void TextAreaEdit::slotUpdateUndoAndRedoInContextMenu( QMenu* menu ) { if ( !menu ) return; QList actionList = menu->actions(); enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs }; QAction *kundo = KStandardAction::create( KStandardAction::Undo, m_controller, SIGNAL( requestUndo() ), menu ); QAction *kredo = KStandardAction::create( KStandardAction::Redo, m_controller, SIGNAL( requestRedo() ), menu ); connect(m_controller, &FormWidgetsController::canUndoChanged, kundo, &QAction::setEnabled ); connect(m_controller, &FormWidgetsController::canRedoChanged, kredo, &QAction::setEnabled ); kundo->setEnabled( m_controller->canUndo() ); kredo->setEnabled( m_controller->canRedo() ); QAction *oldUndo, *oldRedo; oldUndo = actionList[UndoAct]; oldRedo = actionList[RedoAct]; menu->insertAction( oldUndo, kundo ); menu->insertAction( oldRedo, kredo ); menu->removeAction( oldUndo ); menu->removeAction( oldRedo ); } void TextAreaEdit::setFormWidgetsController( FormWidgetsController* controller ) { FormWidgetIface::setFormWidgetsController( controller ); connect( m_controller, &FormWidgetsController::formTextChangedByUndoRedo, this, &TextAreaEdit::slotHandleTextChangedByUndoRedo ); } void TextAreaEdit::slotHandleTextChangedByUndoRedo( int pageNumber, Okular::FormFieldText* textForm, const QString & contents, int cursorPos, int anchorPos ) { Q_UNUSED(pageNumber); if ( textForm != m_ff ) { return; } setPlainText( contents ); QTextCursor c = textCursor(); c.setPosition( anchorPos ); c.setPosition( cursorPos,QTextCursor::KeepAnchor ); m_prevCursorPos = cursorPos; m_prevAnchorPos = anchorPos; setTextCursor( c ); setFocus(); } void TextAreaEdit::slotChanged() { // happens on destruction if (!m_ff) return; Okular::FormFieldText *form = static_cast(m_ff); QString contents = toPlainText(); int cursorPos = textCursor().position(); if (contents != form->text()) { m_controller->formTextChangedByWidget( pageItem()->pageNumber(), form, contents, cursorPos, m_prevCursorPos, m_prevAnchorPos ); } m_prevCursorPos = cursorPos; m_prevAnchorPos = textCursor().anchor(); } void TextAreaEdit::slotRefresh( Okular::FormField *form ) { if (form != m_ff) { return; } FormWidgetIface::slotRefresh( form ); Okular::FormFieldText *text = static_cast ( form ); setPlainText( text->text() ); } FileEdit::FileEdit( Okular::FormFieldText * text, QWidget * parent ) : KUrlRequester( parent ), FormWidgetIface( this, text ) { setMode( KFile::File | KFile::ExistingOnly | KFile::LocalOnly ); setFilter( i18n( "*|All Files" ) ); setUrl( QUrl::fromUserInput( text->text() ) ); lineEdit()->setAlignment( text->textAlignment() ); m_prevCursorPos = lineEdit()->cursorPosition(); m_prevAnchorPos = lineEdit()->cursorPosition(); connect( this, &KUrlRequester::textChanged, this, &FileEdit::slotChanged ); connect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &FileEdit::slotChanged ); setVisible( text->isVisible() ); } void FileEdit::setFormWidgetsController( FormWidgetsController* controller ) { FormWidgetIface::setFormWidgetsController( controller ); connect( m_controller, &FormWidgetsController::formTextChangedByUndoRedo, this, &FileEdit::slotHandleFileChangedByUndoRedo ); } bool FileEdit::eventFilter( QObject* obj, QEvent* event ) { if ( obj == lineEdit() ) { if ( event->type() == QEvent::KeyPress ) { QKeyEvent *keyEvent = static_cast< QKeyEvent* >( event ); if ( keyEvent == QKeySequence::Undo ) { emit m_controller->requestUndo(); return true; } else if ( keyEvent == QKeySequence::Redo ) { emit m_controller->requestRedo(); return true; } } else if( event->type() == QEvent::ContextMenu ) { QContextMenuEvent *contextMenuEvent = static_cast< QContextMenuEvent* >( event ); QMenu *menu = ( (QLineEdit*) lineEdit() )->createStandardContextMenu(); QList< QAction* > actionList = menu->actions(); enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, DeleteAct, SelectAllAct }; QAction *kundo = KStandardAction::create( KStandardAction::Undo, m_controller, SIGNAL( requestUndo() ), menu ); QAction *kredo = KStandardAction::create( KStandardAction::Redo, m_controller, SIGNAL( requestRedo() ), menu ); connect(m_controller, &FormWidgetsController::canUndoChanged, kundo, &QAction::setEnabled ); connect(m_controller, &FormWidgetsController::canRedoChanged, kredo, &QAction::setEnabled ); kundo->setEnabled( m_controller->canUndo() ); kredo->setEnabled( m_controller->canRedo() ); QAction *oldUndo, *oldRedo; oldUndo = actionList[UndoAct]; oldRedo = actionList[RedoAct]; menu->insertAction( oldUndo, kundo ); menu->insertAction( oldRedo, kredo ); menu->removeAction( oldUndo ); menu->removeAction( oldRedo ); menu->exec( contextMenuEvent->globalPos() ); delete menu; return true; } } return KUrlRequester::eventFilter( obj, event ); } void FileEdit::slotChanged() { // Make sure line edit's text matches url expansion if ( text() != url().toLocalFile() ) this->setText( url().toLocalFile() ); Okular::FormFieldText *form = static_cast(m_ff); QString contents = text(); int cursorPos = lineEdit()->cursorPosition(); if (contents != form->text()) { m_controller->formTextChangedByWidget( pageItem()->pageNumber(), form, contents, cursorPos, m_prevCursorPos, m_prevAnchorPos ); } m_prevCursorPos = cursorPos; m_prevAnchorPos = cursorPos; if ( lineEdit()->hasSelectedText() ) { if ( cursorPos == lineEdit()->selectionStart() ) { m_prevAnchorPos = lineEdit()->selectionStart() + lineEdit()->selectedText().size(); } else { m_prevAnchorPos = lineEdit()->selectionStart(); } } } void FileEdit::slotHandleFileChangedByUndoRedo( int pageNumber, Okular::FormFieldText* form, const QString & contents, int cursorPos, int anchorPos ) { Q_UNUSED(pageNumber); if ( form != m_ff || contents == text() ) { return; } disconnect( this, SIGNAL( cursorPositionChanged( int, int ) ), this, SLOT( slotChanged() ) ); setText( contents ); lineEdit()->setCursorPosition( anchorPos ); lineEdit()->cursorForward( true, cursorPos - anchorPos ); connect( this, SIGNAL(cursorPositionChanged( int, int ) ), this, SLOT( slotChanged() ) ); m_prevCursorPos = cursorPos; m_prevAnchorPos = anchorPos; setFocus(); } ListEdit::ListEdit( Okular::FormFieldChoice * choice, QWidget * parent ) : QListWidget( parent ), FormWidgetIface( this, choice ) { addItems( choice->choices() ); setSelectionMode( choice->multiSelect() ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection ); setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); QList< int > selectedItems = choice->currentChoices(); if ( choice->multiSelect() ) { foreach ( int index, selectedItems ) if ( index >= 0 && index < count() ) item( index )->setSelected( true ); } else { if ( selectedItems.count() == 1 && selectedItems.at(0) >= 0 && selectedItems.at(0) < count() ) { setCurrentRow( selectedItems.at(0) ); scrollToItem( item( selectedItems.at(0) ) ); } } connect( this, &QListWidget::itemSelectionChanged, this, &ListEdit::slotSelectionChanged ); setVisible( choice->isVisible() ); setCursor( Qt::ArrowCursor ); } void ListEdit::setFormWidgetsController( FormWidgetsController* controller ) { FormWidgetIface::setFormWidgetsController( controller ); connect( m_controller, &FormWidgetsController::formListChangedByUndoRedo, this, &ListEdit::slotHandleFormListChangedByUndoRedo ); } void ListEdit::slotSelectionChanged() { QList< QListWidgetItem * > selection = selectedItems(); QList< int > rows; foreach( const QListWidgetItem * item, selection ) rows.append( row( item ) ); Okular::FormFieldChoice *form = static_cast(m_ff); if ( rows != form->currentChoices() ) { m_controller->formListChangedByWidget( pageItem()->pageNumber(), form, rows ); } } void ListEdit::slotHandleFormListChangedByUndoRedo( int pageNumber, Okular::FormFieldChoice* listForm, const QList< int > & choices ) { Q_UNUSED(pageNumber); if ( m_ff != listForm ) { return; } disconnect( this, &QListWidget::itemSelectionChanged, this, &ListEdit::slotSelectionChanged ); for(int i=0; i < count(); i++) { item( i )->setSelected( choices.contains(i) ); } connect( this, &QListWidget::itemSelectionChanged, this, &ListEdit::slotSelectionChanged ); setFocus(); } ComboEdit::ComboEdit( Okular::FormFieldChoice * choice, QWidget * parent ) : QComboBox( parent ), FormWidgetIface( this, choice ) { addItems( choice->choices() ); setEditable( true ); setInsertPolicy( NoInsert ); lineEdit()->setReadOnly( !choice->isEditable() ); QList< int > selectedItems = choice->currentChoices(); if ( selectedItems.count() == 1 && selectedItems.at(0) >= 0 && selectedItems.at(0) < count() ) setCurrentIndex( selectedItems.at(0) ); if ( choice->isEditable() && !choice->editChoice().isEmpty() ) lineEdit()->setText( choice->editChoice() ); connect( this, SIGNAL(currentIndexChanged(int)), this, SLOT(slotValueChanged()) ); connect( this, &QComboBox::editTextChanged, this, &ComboEdit::slotValueChanged ); connect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &ComboEdit::slotValueChanged ); setVisible( choice->isVisible() ); setCursor( Qt::ArrowCursor ); m_prevCursorPos = lineEdit()->cursorPosition(); m_prevAnchorPos = lineEdit()->cursorPosition(); } void ComboEdit::setFormWidgetsController(FormWidgetsController* controller) { FormWidgetIface::setFormWidgetsController(controller); connect( m_controller, &FormWidgetsController::formComboChangedByUndoRedo, this, &ComboEdit::slotHandleFormComboChangedByUndoRedo); } void ComboEdit::slotValueChanged() { const QString text = lineEdit()->text(); Okular::FormFieldChoice *form = static_cast(m_ff); QString prevText; if ( form->currentChoices().isEmpty() ) { prevText = form->editChoice(); } else { prevText = form->choices()[form->currentChoices()[0]]; } int cursorPos = lineEdit()->cursorPosition(); if ( text != prevText ) { m_controller->formComboChangedByWidget( pageItem()->pageNumber(), form, currentText(), cursorPos, m_prevCursorPos, m_prevAnchorPos ); } prevText = text; m_prevCursorPos = cursorPos; m_prevAnchorPos = cursorPos; if ( lineEdit()->hasSelectedText() ) { if ( cursorPos == lineEdit()->selectionStart() ) { m_prevAnchorPos = lineEdit()->selectionStart() + lineEdit()->selectedText().size(); } else { m_prevAnchorPos = lineEdit()->selectionStart(); } } } void ComboEdit::slotHandleFormComboChangedByUndoRedo( int pageNumber, Okular::FormFieldChoice* form, const QString & text, int cursorPos, int anchorPos ) { Q_UNUSED(pageNumber); if ( m_ff != form ) { return; } // Determine if text corrisponds to an index choices int index = -1; for ( int i = 0; i < count(); i++ ) { if ( itemText(i) == text ) { index = i; } } m_prevCursorPos = cursorPos; m_prevAnchorPos = anchorPos; disconnect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &ComboEdit::slotValueChanged ); const bool isCustomValue = index == -1; if ( isCustomValue ) { setEditText( text ); } else { setCurrentIndex( index ); } lineEdit()->setCursorPosition( anchorPos ); lineEdit()->cursorForward( true, cursorPos - anchorPos ); connect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &ComboEdit::slotValueChanged ); setFocus(); } void ComboEdit::contextMenuEvent( QContextMenuEvent* event ) { QMenu *menu = lineEdit()->createStandardContextMenu(); QList actionList = menu->actions(); enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, DeleteAct, SelectAllAct }; QAction *kundo = KStandardAction::create( KStandardAction::Undo, m_controller, SIGNAL( requestUndo() ), menu ); QAction *kredo = KStandardAction::create( KStandardAction::Redo, m_controller, SIGNAL( requestRedo() ), menu ); connect( m_controller, &FormWidgetsController::canUndoChanged, kundo, &QAction::setEnabled ); connect( m_controller, &FormWidgetsController::canRedoChanged, kredo, &QAction::setEnabled ); kundo->setEnabled( m_controller->canUndo() ); kredo->setEnabled( m_controller->canRedo() ); QAction *oldUndo, *oldRedo; oldUndo = actionList[UndoAct]; oldRedo = actionList[RedoAct]; menu->insertAction( oldUndo, kundo ); menu->insertAction( oldRedo, kredo ); menu->removeAction( oldUndo ); menu->removeAction( oldRedo ); menu->exec( event->globalPos() ); delete menu; } bool ComboEdit::event( QEvent* e ) { if ( e->type() == QEvent::KeyPress ) { QKeyEvent *keyEvent = static_cast< QKeyEvent* >(e); if ( keyEvent == QKeySequence::Undo ) { emit m_controller->requestUndo(); return true; } else if ( keyEvent == QKeySequence::Redo ) { emit m_controller->requestRedo(); return true; } } return QComboBox::event( e ); } SignatureEdit::SignatureEdit( Okular::FormFieldSignature * signature, QWidget * parent ) : QAbstractButton( parent ), FormWidgetIface( this, signature ), m_leftMouseButtonPressed( false ) { Okular::FormFieldSignature *sigField = static_cast< Okular::FormFieldSignature * >( formField() ); m_sigInfo = sigField->validate(); setCheckable( false ); setCursor( Qt::PointingHandCursor ); connect( this, &SignatureEdit::clicked, this, &SignatureEdit::slotShowSummary ); } bool SignatureEdit::event( QEvent * e ) { switch ( e->type() ) { case QEvent::MouseButtonPress: { QMouseEvent *ev = static_cast< QMouseEvent * >( e ); if ( ev->button() == Qt::LeftButton ) { m_leftMouseButtonPressed = true; update(); } mousePressEvent( ev ); break; } case QEvent::MouseButtonRelease: { QMouseEvent *ev = static_cast< QMouseEvent * >( e ); m_leftMouseButtonPressed = false; if ( ev->button() == Qt::LeftButton) { update(); } mouseReleaseEvent( ev ); break; } default: break; } return QAbstractButton::event( e ); } void SignatureEdit::contextMenuEvent( QContextMenuEvent * event ) { QMenu *menu = new QMenu( this ); QAction *sigVal = new QAction( i18n("Validate Signature"), this ); menu->addAction( sigVal ); connect( sigVal, &QAction::triggered, this, &SignatureEdit::slotShowSummary ); QAction *sigProp = new QAction( i18n("Show Signature Properties"), this ); menu->addAction( sigProp ); connect( sigProp, &QAction::triggered, this, &SignatureEdit::slotShowProperties ); menu->exec( event->globalPos() ); delete menu; } void SignatureEdit::paintEvent( QPaintEvent * ) { QPainter painter( this ); painter.setPen( Qt::black ); if ( m_leftMouseButtonPressed ) { QColor col = palette().color( QPalette::Active, QPalette::Highlight ); col.setAlpha(50); painter.setBrush( col ); } else { painter.setBrush( Qt::transparent ); } painter.drawRect( 0, 0, width()-2, height()-2 ); } -Okular::SignatureInfo SignatureEdit::validate() +Okular::SignatureInfo *SignatureEdit::validate() { + Okular::FormFieldSignature *sigField = static_cast< Okular::FormFieldSignature * >( formField() ); + m_sigInfo = sigField->validate(); m_signatureValidated = true; return m_sigInfo; } void SignatureEdit::slotShowSummary() { SignatureSummaryDialog sigSummaryDlg( m_sigInfo, this ); sigSummaryDlg.exec(); } void SignatureEdit::slotShowProperties() { SignaturePropertiesDialog sigPropDlg( m_sigInfo, this ); sigPropDlg.exec(); } // Code for additional action handling. // Challenge: Change preprocessor magic to C++ magic! // // The mouseRelease event is special because the PDF spec // says that the activation action takes precedence over this. // So the mouse release action is only signaled if no activation // action exists. // // For checkboxes the activation action is not triggered as // they are still triggered from the clicked signal and additionally // when the checked state changes. #define DEFINE_ADDITIONAL_ACTIONS(FormClass, BaseClass) \ void FormClass::mousePressEvent( QMouseEvent *event ) \ { \ Okular::Action *act = m_ff->additionalAction( Okular::Annotation::MousePressed ); \ if ( act ) \ { \ m_controller->signalAction( act ); \ } \ BaseClass::mousePressEvent( event ); \ } \ void FormClass::mouseReleaseEvent( QMouseEvent *event ) \ { \ if ( !QWidget::rect().contains( event->localPos().toPoint() ) ) \ { \ BaseClass::mouseReleaseEvent( event ); \ return; \ } \ Okular::Action *act = m_ff->activationAction(); \ if ( act && !qobject_cast< CheckBoxEdit* > ( this ) ) \ { \ m_controller->signalAction( act ); \ } \ else if ( ( act = m_ff->additionalAction( Okular::Annotation::MouseReleased ) ) ) \ { \ m_controller->signalAction( act ); \ } \ BaseClass::mouseReleaseEvent( event ); \ } \ void FormClass::focusInEvent( QFocusEvent *event ) \ { \ Okular::Action *act = m_ff->additionalAction( Okular::Annotation::FocusIn ); \ if ( act ) \ { \ m_controller->signalAction( act ); \ } \ BaseClass::focusInEvent( event ); \ } \ void FormClass::focusOutEvent( QFocusEvent *event ) \ { \ Okular::Action *act = m_ff->additionalAction( Okular::Annotation::FocusOut ); \ if ( act ) \ { \ m_controller->signalAction( act ); \ } \ BaseClass::focusOutEvent( event ); \ } \ void FormClass::leaveEvent( QEvent *event ) \ { \ Okular::Action *act = m_ff->additionalAction( Okular::Annotation::CursorLeaving ); \ if ( act ) \ { \ m_controller->signalAction( act ); \ } \ BaseClass::leaveEvent( event ); \ } \ void FormClass::enterEvent( QEvent *event ) \ { \ Okular::Action *act = m_ff->additionalAction( Okular::Annotation::CursorEntering ); \ if ( act ) \ { \ m_controller->signalAction( act ); \ } \ BaseClass::enterEvent( event ); \ } DEFINE_ADDITIONAL_ACTIONS( PushButtonEdit, QPushButton ) DEFINE_ADDITIONAL_ACTIONS( CheckBoxEdit, QCheckBox ) DEFINE_ADDITIONAL_ACTIONS( RadioButtonEdit, QRadioButton ) DEFINE_ADDITIONAL_ACTIONS( FormLineEdit, QLineEdit ) DEFINE_ADDITIONAL_ACTIONS( TextAreaEdit, KTextEdit ) DEFINE_ADDITIONAL_ACTIONS( FileEdit, KUrlRequester ) DEFINE_ADDITIONAL_ACTIONS( ListEdit, QListWidget ) DEFINE_ADDITIONAL_ACTIONS( ComboEdit, QComboBox ) DEFINE_ADDITIONAL_ACTIONS( SignatureEdit, QAbstractButton ) #undef DEFINE_ADDITIONAL_ACTIONS #include "moc_formwidgets.cpp" diff --git a/ui/formwidgets.h b/ui/formwidgets.h index 980771d83..2b81a655b 100644 --- a/ui/formwidgets.h +++ b/ui/formwidgets.h @@ -1,378 +1,378 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * 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_FORMWIDGETS_H_ #define _OKULAR_FORMWIDGETS_H_ #include "core/area.h" -#include "core/signatureutils.h" #include #include #include #include #include #include #include #include class ComboEdit; class QMenu; class QButtonGroup; class FormWidgetIface; class PageViewItem; class RadioButtonEdit; class QEvent; namespace Okular { class Action; class FormField; class FormFieldButton; class FormFieldChoice; class FormFieldText; class FormFieldSignature; class Document; +class SignatureInfo; } struct RadioData { RadioData() {} QList< int > ids; QButtonGroup *group; }; class FormWidgetsController : public QObject { Q_OBJECT public: FormWidgetsController( Okular::Document *doc ); virtual ~FormWidgetsController(); void signalAction( Okular::Action *action ); void registerRadioButton( FormWidgetIface *fwButton, Okular::FormFieldButton *formButton ); void dropRadioButtons(); bool canUndo(); bool canRedo(); Q_SIGNALS: void changed( int pageNumber ); void requestUndo(); void requestRedo(); void canUndoChanged( bool undoAvailable ); void canRedoChanged( bool redoAvailable); void formTextChangedByWidget( int pageNumber, Okular::FormFieldText *form, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ); void formTextChangedByUndoRedo( int pageNumber, Okular::FormFieldText *form, const QString & contents, int cursorPos, int anchorPos ); void formListChangedByWidget( int pageNumber, Okular::FormFieldChoice *form, const QList< int > & newChoices ); void formListChangedByUndoRedo( int pageNumber, Okular::FormFieldChoice *form, const QList< int > & choices ); void formComboChangedByWidget( int pageNumber, Okular::FormFieldChoice *form, const QString & newText, int newCursorPos, int prevCursorPos, int prevAnchorPos ); void formComboChangedByUndoRedo( int pageNumber, Okular::FormFieldChoice *form, const QString & text, int cursorPos, int anchorPos ); void formButtonsChangedByWidget( int pageNumber, const QList< Okular::FormFieldButton* > & formButtons, const QList< bool > & newButtonStates ); void action( Okular::Action *action ); void refreshFormWidget( Okular::FormField * form ); private Q_SLOTS: void slotButtonClicked( QAbstractButton *button ); void slotFormButtonsChangedByUndoRedo( int pageNumber, const QList< Okular::FormFieldButton* > & formButtons ); private: friend class TextAreaEdit; friend class FormLineEdit; friend class FileEdit; friend class ListEdit; friend class ComboEdit; QList< RadioData > m_radios; QHash< int, QAbstractButton* > m_buttons; Okular::Document* m_doc; }; class FormWidgetFactory { public: static FormWidgetIface * createWidget( Okular::FormField * ff, QWidget * parent = nullptr ); }; class FormWidgetIface { public: FormWidgetIface( QWidget * w, Okular::FormField * ff ); virtual ~FormWidgetIface(); Okular::NormalizedRect rect() const; void setWidthHeight( int w, int h ); void moveTo( int x, int y ); bool setVisibility( bool visible ); void setCanBeFilled( bool fill ); void setPageItem( PageViewItem *pageItem ); PageViewItem* pageItem() const; void setFormField( Okular::FormField *field ); Okular::FormField* formField() const; virtual void setFormWidgetsController( FormWidgetsController *controller ); protected: virtual void slotRefresh( Okular::FormField *form ); FormWidgetsController * m_controller; Okular::FormField * m_ff; private: QWidget * m_widget; PageViewItem * m_pageItem; }; #define DECLARE_ADDITIONAL_ACTIONS \ protected: \ virtual void mousePressEvent( QMouseEvent *event ) override; \ virtual void mouseReleaseEvent( QMouseEvent *event ) override; \ virtual void focusInEvent( QFocusEvent *event ) override; \ virtual void focusOutEvent( QFocusEvent *event ) override; \ virtual void leaveEvent( QEvent *event ) override; \ virtual void enterEvent( QEvent *event ) override; class PushButtonEdit : public QPushButton, public FormWidgetIface { Q_OBJECT public: explicit PushButtonEdit( Okular::FormFieldButton * button, QWidget * parent = nullptr ); DECLARE_ADDITIONAL_ACTIONS }; class CheckBoxEdit : public QCheckBox, public FormWidgetIface { Q_OBJECT public: explicit CheckBoxEdit( Okular::FormFieldButton * button, QWidget * parent = nullptr ); // reimplemented from FormWidgetIface void setFormWidgetsController( FormWidgetsController *controller ) override; void doActivateAction(); protected: void slotRefresh( Okular::FormField *form ) override; DECLARE_ADDITIONAL_ACTIONS }; class RadioButtonEdit : public QRadioButton, public FormWidgetIface { Q_OBJECT public: explicit RadioButtonEdit( Okular::FormFieldButton * button, QWidget * parent = nullptr ); // reimplemented from FormWidgetIface void setFormWidgetsController( FormWidgetsController *controller ) override; DECLARE_ADDITIONAL_ACTIONS }; class FormLineEdit : public QLineEdit, public FormWidgetIface { Q_OBJECT public: explicit FormLineEdit( Okular::FormFieldText * text, QWidget * parent = nullptr ); void setFormWidgetsController( FormWidgetsController *controller ) override; bool event ( QEvent * e ) override; void contextMenuEvent( QContextMenuEvent* event ) override; public Q_SLOTS: void slotHandleTextChangedByUndoRedo( int pageNumber, Okular::FormFieldText* textForm, const QString & contents, int cursorPos, int anchorPos ); private Q_SLOTS: void slotChanged(); protected: void slotRefresh( Okular::FormField* form ) override; private: int m_prevCursorPos; int m_prevAnchorPos; DECLARE_ADDITIONAL_ACTIONS }; class TextAreaEdit : public KTextEdit, public FormWidgetIface { Q_OBJECT public: explicit TextAreaEdit( Okular::FormFieldText * text, QWidget * parent = nullptr ); void setFormWidgetsController( FormWidgetsController *controller ) override; bool event ( QEvent * e ) override; public Q_SLOTS: void slotHandleTextChangedByUndoRedo( int pageNumber, Okular::FormFieldText * textForm, const QString & contents, int cursorPos, int anchorPos ); void slotUpdateUndoAndRedoInContextMenu( QMenu* menu ); private Q_SLOTS: void slotChanged(); protected: void slotRefresh( Okular::FormField* form ) override; private: int m_prevCursorPos; int m_prevAnchorPos; DECLARE_ADDITIONAL_ACTIONS }; class FileEdit : public KUrlRequester, public FormWidgetIface { Q_OBJECT public: explicit FileEdit( Okular::FormFieldText * text, QWidget * parent = nullptr ); void setFormWidgetsController( FormWidgetsController *controller ) override; protected: bool eventFilter( QObject *obj, QEvent *event ) override; private Q_SLOTS: void slotChanged(); void slotHandleFileChangedByUndoRedo( int pageNumber, Okular::FormFieldText * form, const QString & contents, int cursorPos, int anchorPos ); private: int m_prevCursorPos; int m_prevAnchorPos; DECLARE_ADDITIONAL_ACTIONS }; class ListEdit : public QListWidget, public FormWidgetIface { Q_OBJECT public: explicit ListEdit( Okular::FormFieldChoice * choice, QWidget * parent = nullptr ); void setFormWidgetsController( FormWidgetsController *controller ) override; private Q_SLOTS: void slotSelectionChanged(); void slotHandleFormListChangedByUndoRedo( int pageNumber, Okular::FormFieldChoice * listForm, const QList< int > & choices ); DECLARE_ADDITIONAL_ACTIONS }; class ComboEdit : public QComboBox, public FormWidgetIface { Q_OBJECT public: explicit ComboEdit( Okular::FormFieldChoice * choice, QWidget * parent = nullptr ); void setFormWidgetsController( FormWidgetsController *controller ) override; bool event ( QEvent * e ) override; void contextMenuEvent( QContextMenuEvent* event ) override; private Q_SLOTS: void slotValueChanged(); void slotHandleFormComboChangedByUndoRedo( int pageNumber, Okular::FormFieldChoice * comboForm, const QString & text, int cursorPos, int anchorPos ); private: int m_prevCursorPos; int m_prevAnchorPos; DECLARE_ADDITIONAL_ACTIONS }; class SignatureEdit : public QAbstractButton, public FormWidgetIface { Q_OBJECT public: explicit SignatureEdit( Okular::FormFieldSignature * signature, QWidget * parent = nullptr ); - Okular::SignatureInfo validate(); + Okular::SignatureInfo *validate(); protected: bool event( QEvent * e ) override; void contextMenuEvent( QContextMenuEvent * event ) override; void paintEvent( QPaintEvent * event ) override; private Q_SLOTS: void slotShowSummary(); void slotShowProperties(); private: - Okular::SignatureInfo m_sigInfo; + Okular::SignatureInfo *m_sigInfo; bool m_leftMouseButtonPressed; bool m_signatureValidated; DECLARE_ADDITIONAL_ACTIONS }; #undef DECLARE_ADDITIONAL_ACTIONS #endif diff --git a/ui/pageview.cpp b/ui/pageview.cpp index 568d86e62..10f97f92d 100644 --- a/ui/pageview.cpp +++ b/ui/pageview.cpp @@ -1,5574 +1,5574 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2004-2006 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 * * * * With portions of code from kpdf/kpdf_pagewidget.cc by: * * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2003 by Christophe Devriese * * * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2003 by Dirk Mueller * * Copyright (C) 2004 by James Ots * * Copyright (C) 2011 by Jiri Baum - NICTA * * * * 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 "pageview.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 // system includes #include #include // local includes #include "debug_ui.h" #include "formwidgets.h" #include "pageviewutils.h" #include "pagepainter.h" #include "core/annotations.h" #include "annotwindow.h" #include "guiutils.h" #include "annotationpopup.h" #include "pageviewannotator.h" #include "pageviewmouseannotation.h" #include "priorities.h" #include "toolaction.h" #include "okmenutitle.h" #ifdef HAVE_SPEECH #include "tts.h" #endif #include "videowidget.h" #include "core/action.h" #include "core/area.h" #include "core/document_p.h" #include "core/form.h" #include "core/page.h" #include "core/page_p.h" #include "core/misc.h" #include "core/generator.h" #include "core/movie.h" #include "core/audioplayer.h" #include "core/sourcereference.h" #include "core/tile.h" #include "settings.h" #include "settings_core.h" #include "url_utils.h" #include "magnifierview.h" static const int pageflags = PagePainter::Accessibility | PagePainter::EnhanceLinks | PagePainter::EnhanceImages | PagePainter::Highlights | PagePainter::TextSelection | PagePainter::Annotations; static const float kZoomValues[] = { 0.12, 0.25, 0.33, 0.50, 0.66, 0.75, 1.00, 1.25, 1.50, 2.00, 4.00, 8.00, 16.00 }; static inline double normClamp( double value, double def ) { return ( value < 0.0 || value > 1.0 ) ? def : value; } struct TableSelectionPart { PageViewItem * item; Okular::NormalizedRect rectInItem; Okular::NormalizedRect rectInSelection; TableSelectionPart(PageViewItem * item_p, const Okular::NormalizedRect &rectInItem_p, const Okular::NormalizedRect &rectInSelection_p); }; TableSelectionPart::TableSelectionPart( PageViewItem * item_p, const Okular::NormalizedRect &rectInItem_p, const Okular::NormalizedRect &rectInSelection_p) : item ( item_p ), rectInItem (rectInItem_p), rectInSelection (rectInSelection_p) { } // structure used internally by PageView for data storage class PageViewPrivate { public: PageViewPrivate( PageView *qq ); FormWidgetsController* formWidgetsController(); #ifdef HAVE_SPEECH OkularTTS* tts(); #endif QString selectedText() const; // the document, pageviewItems and the 'visible cache' PageView *q; Okular::Document * document; QVector< PageViewItem * > items; QLinkedList< PageViewItem * > visibleItems; MagnifierView *magnifierView; // view layout (columns and continuous in Settings), zoom and mouse PageView::ZoomMode zoomMode; float zoomFactor; QPoint mouseGrabPos; QPoint mousePressPos; QPoint mouseSelectPos; int mouseMidLastY; bool mouseSelecting; QRect mouseSelectionRect; QColor mouseSelectionColor; bool mouseTextSelecting; QSet< int > pagesWithTextSelection; bool mouseOnRect; int mouseMode; MouseAnnotation * mouseAnnotation; // table selection QList tableSelectionCols; QList tableSelectionRows; QList tableSelectionParts; bool tableDividersGuessed; // viewport move bool viewportMoveActive; QTime viewportMoveTime; QPoint viewportMoveDest; int lastSourceLocationViewportPageNumber; double lastSourceLocationViewportNormalizedX; double lastSourceLocationViewportNormalizedY; QTimer * viewportMoveTimer; int controlWheelAccumulatedDelta; // auto scroll int scrollIncrement; QTimer * autoScrollTimer; // annotations PageViewAnnotator * annotator; //text annotation dialogs list QSet< AnnotWindow * > m_annowindows; // other stuff QTimer * delayResizeEventTimer; bool dirtyLayout; bool blockViewport; // prevents changes to viewport bool blockPixmapsRequest; // prevent pixmap requests PageViewMessage * messageWindow; // in pageviewutils.h bool m_formsVisible; FormWidgetsController *formsWidgetController; #ifdef HAVE_SPEECH OkularTTS * m_tts; #endif QTimer * refreshTimer; QSet refreshPages; // bbox state for Trim to Selection mode Okular::NormalizedRect trimBoundingBox; // infinite resizing loop prevention bool verticalScrollBarVisible; bool horizontalScrollBarVisible; // drag scroll QPoint dragScrollVector; QTimer dragScrollTimer; // left click depress QTimer leftClickTimer; // actions QAction * aRotateClockwise; QAction * aRotateCounterClockwise; QAction * aRotateOriginal; KSelectAction * aPageSizes; KActionMenu * aTrimMode; KToggleAction * aTrimMargins; QAction * aMouseNormal; QAction * aMouseSelect; QAction * aMouseTextSelect; QAction * aMouseTableSelect; QAction * aMouseMagnifier; KToggleAction * aTrimToSelection; KToggleAction * aToggleAnnotator; KSelectAction * aZoom; QAction * aZoomIn; QAction * aZoomOut; KToggleAction * aZoomFitWidth; KToggleAction * aZoomFitPage; KToggleAction * aZoomAutoFit; KActionMenu * aViewMode; KToggleAction * aViewContinuous; QAction * aPrevAction; QAction * aToggleForms; QAction * aSpeakDoc; QAction * aSpeakPage; QAction * aSpeakStop; KActionCollection * actionCollection; QActionGroup * mouseModeActionGroup; QAction * aFitWindowToPage; QAction * aValidateSignatures; int setting_viewCols; bool rtl_Mode; // Keep track of whether tablet pen is currently pressed down bool penDown; // Keep track of mouse over link object const Okular::ObjectRect * mouseOverLinkObject; }; PageViewPrivate::PageViewPrivate( PageView *qq ) : q( qq ) #ifdef HAVE_SPEECH , m_tts( nullptr ) #endif { } FormWidgetsController* PageViewPrivate::formWidgetsController() { if ( !formsWidgetController ) { formsWidgetController = new FormWidgetsController( document ); QObject::connect( formsWidgetController, SIGNAL( changed( int ) ), q, SLOT( slotFormChanged( int ) ) ); QObject::connect( formsWidgetController, SIGNAL( action( Okular::Action* ) ), q, SLOT( slotAction( Okular::Action* ) ) ); } return formsWidgetController; } #ifdef HAVE_SPEECH OkularTTS* PageViewPrivate::tts() { if ( !m_tts ) { m_tts = new OkularTTS( q ); if ( aSpeakStop ) { QObject::connect( m_tts, &OkularTTS::isSpeaking, aSpeakStop, &QAction::setEnabled ); } } return m_tts; } #endif /* PageView. What's in this file? -> quick overview. * Code weight (in rows) and meaning: * 160 - constructor and creating actions plus their connected slots (empty stuff) * 70 - DocumentObserver inherited methodes (important) * 550 - events: mouse, keyboard, drag * 170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes * 100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc.. * other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable, * and many insignificant stuff like this comment :-) */ PageView::PageView( QWidget *parent, Okular::Document *document ) : QAbstractScrollArea( parent ) , Okular::View( QLatin1String( "PageView" ) ) { // create and initialize private storage structure d = new PageViewPrivate( this ); d->document = document; d->aRotateClockwise = nullptr; d->aRotateCounterClockwise = nullptr; d->aRotateOriginal = nullptr; d->aViewMode = nullptr; d->zoomMode = PageView::ZoomFitWidth; d->zoomFactor = 1.0; d->mouseSelecting = false; d->mouseTextSelecting = false; d->mouseOnRect = false; d->mouseMode = Okular::Settings::mouseMode(); d->mouseAnnotation = new MouseAnnotation( this, document ); d->tableDividersGuessed = false; d->viewportMoveActive = false; d->lastSourceLocationViewportPageNumber = -1; d->lastSourceLocationViewportNormalizedX = 0.0; d->lastSourceLocationViewportNormalizedY = 0.0; d->viewportMoveTimer = nullptr; d->controlWheelAccumulatedDelta = 0; d->scrollIncrement = 0; d->autoScrollTimer = nullptr; d->annotator = nullptr; d->dirtyLayout = false; d->blockViewport = false; d->blockPixmapsRequest = false; d->messageWindow = new PageViewMessage(this); d->m_formsVisible = false; d->formsWidgetController = nullptr; #ifdef HAVE_SPEECH d->m_tts = nullptr; #endif d->refreshTimer = nullptr; d->aRotateClockwise = nullptr; d->aRotateCounterClockwise = nullptr; d->aRotateOriginal = nullptr; d->aPageSizes = nullptr; d->aTrimMode = nullptr; d->aTrimMargins = nullptr; d->aTrimToSelection = nullptr; d->aMouseNormal = nullptr; d->aMouseSelect = nullptr; d->aMouseTextSelect = nullptr; d->aToggleAnnotator = nullptr; d->aZoomFitWidth = nullptr; d->aZoomFitPage = nullptr; d->aZoomAutoFit = nullptr; d->aViewMode = nullptr; d->aViewContinuous = nullptr; d->aPrevAction = nullptr; d->aToggleForms = nullptr; d->aSpeakDoc = nullptr; d->aSpeakPage = nullptr; d->aSpeakStop = nullptr; d->actionCollection = nullptr; d->aPageSizes=nullptr; d->setting_viewCols = Okular::Settings::viewColumns(); d->rtl_Mode = Okular::Settings::rtlReadingDirection(); d->mouseModeActionGroup = nullptr; d->penDown = false; d->aMouseMagnifier = nullptr; d->aFitWindowToPage = nullptr; d->trimBoundingBox = Okular::NormalizedRect(); // Null box d->aValidateSignatures = nullptr; switch( Okular::Settings::zoomMode() ) { case 0: { d->zoomFactor = 1; d->zoomMode = PageView::ZoomFixed; break; } case 1: { d->zoomMode = PageView::ZoomFitWidth; break; } case 2: { d->zoomMode = PageView::ZoomFitPage; break; } case 3: { d->zoomMode = PageView::ZoomFitAuto; break; } } d->delayResizeEventTimer = new QTimer( this ); d->delayResizeEventTimer->setSingleShot( true ); connect( d->delayResizeEventTimer, &QTimer::timeout, this, &PageView::delayedResizeEvent ); setFrameStyle(QFrame::NoFrame); setAttribute( Qt::WA_StaticContents ); setObjectName( QStringLiteral( "okular::pageView" ) ); // viewport setup: setup focus, and track mouse viewport()->setFocusProxy( this ); viewport()->setFocusPolicy( Qt::StrongFocus ); viewport()->setAttribute( Qt::WA_OpaquePaintEvent ); viewport()->setAttribute( Qt::WA_NoSystemBackground ); viewport()->setMouseTracking( true ); viewport()->setAutoFillBackground( false ); // the apparently "magic" value of 20 is the same used internally in QScrollArea verticalScrollBar()->setCursor( Qt::ArrowCursor ); verticalScrollBar()->setSingleStep( 20 ); horizontalScrollBar()->setCursor( Qt::ArrowCursor ); horizontalScrollBar()->setSingleStep( 20 ); // conntect the padding of the viewport to pixmaps requests connect(horizontalScrollBar(), &QAbstractSlider::valueChanged, this, &PageView::slotRequestVisiblePixmaps); connect(verticalScrollBar(), &QAbstractSlider::valueChanged, this, &PageView::slotRequestVisiblePixmaps); connect( &d->dragScrollTimer, &QTimer::timeout, this, &PageView::slotDragScroll ); d->leftClickTimer.setSingleShot( true ); connect( &d->leftClickTimer, &QTimer::timeout, this, &PageView::slotShowSizeAllCursor ); // set a corner button to resize the view to the page size // QPushButton * resizeButton = new QPushButton( viewport() ); // resizeButton->setPixmap( SmallIcon("crop") ); // setCornerWidget( resizeButton ); // resizeButton->setEnabled( false ); // connect(...); setAttribute( Qt::WA_InputMethodEnabled, true ); // Grab pinch gestures to zoom and rotate the view grabGesture(Qt::PinchGesture); d->magnifierView = new MagnifierView(document, this); d->magnifierView->hide(); d->magnifierView->setGeometry(0, 0, 351, 201); // TODO: more dynamic? connect(document, &Okular::Document::processMovieAction, this, &PageView::slotProcessMovieAction); connect(document, &Okular::Document::processRenditionAction, this, &PageView::slotProcessRenditionAction); // schedule the welcome message QMetaObject::invokeMethod(this, "slotShowWelcome", Qt::QueuedConnection); } PageView::~PageView() { #ifdef HAVE_SPEECH if ( d->m_tts ) d->m_tts->stopAllSpeechs(); #endif delete d->mouseAnnotation; // delete the local storage structure // We need to assign it to a different list otherwise slotAnnotationWindowDestroyed // will bite us and clear d->m_annowindows QSet< AnnotWindow * > annowindows = d->m_annowindows; d->m_annowindows.clear(); qDeleteAll( annowindows ); // delete all widgets QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); for ( ; dIt != dEnd; ++dIt ) delete *dIt; delete d->formsWidgetController; d->document->removeObserver( this ); delete d; } void PageView::setupBaseActions( KActionCollection * ac ) { d->actionCollection = ac; // Zoom actions ( higher scales takes lots of memory! ) d->aZoom = new KSelectAction(QIcon::fromTheme( QStringLiteral("page-zoom") ), i18n("Zoom"), this); ac->addAction(QStringLiteral("zoom_to"), d->aZoom ); d->aZoom->setEditable( true ); d->aZoom->setMaxComboViewCount( 14 ); connect( d->aZoom, SIGNAL(triggered(QAction*)), this, SLOT(slotZoom()) ); updateZoomText(); d->aZoomIn = KStandardAction::zoomIn( this, SLOT(slotZoomIn()), ac ); d->aZoomOut = KStandardAction::zoomOut( this, SLOT(slotZoomOut()), ac ); } void PageView::setupViewerActions( KActionCollection * ac ) { d->actionCollection = ac; ac->setDefaultShortcut(d->aZoomIn, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Plus)); ac->setDefaultShortcut(d->aZoomOut, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Minus)); // orientation menu actions d->aRotateClockwise = new QAction( QIcon::fromTheme( QStringLiteral("object-rotate-right") ), i18n( "Rotate &Right" ), this ); d->aRotateClockwise->setIconText( i18nc( "Rotate right", "Right" ) ); ac->addAction( QStringLiteral("view_orientation_rotate_cw"), d->aRotateClockwise ); d->aRotateClockwise->setEnabled( false ); connect( d->aRotateClockwise, &QAction::triggered, this, &PageView::slotRotateClockwise ); d->aRotateCounterClockwise = new QAction( QIcon::fromTheme( QStringLiteral("object-rotate-left") ), i18n( "Rotate &Left" ), this ); d->aRotateCounterClockwise->setIconText( i18nc( "Rotate left", "Left" ) ); ac->addAction( QStringLiteral("view_orientation_rotate_ccw"), d->aRotateCounterClockwise ); d->aRotateCounterClockwise->setEnabled( false ); connect( d->aRotateCounterClockwise, &QAction::triggered, this, &PageView::slotRotateCounterClockwise ); d->aRotateOriginal = new QAction( i18n( "Original Orientation" ), this ); ac->addAction( QStringLiteral("view_orientation_original"), d->aRotateOriginal ); d->aRotateOriginal->setEnabled( false ); connect( d->aRotateOriginal, &QAction::triggered, this, &PageView::slotRotateOriginal ); d->aPageSizes = new KSelectAction(i18n("&Page Size"), this); ac->addAction(QStringLiteral("view_pagesizes"), d->aPageSizes); d->aPageSizes->setEnabled( false ); connect( d->aPageSizes , SIGNAL(triggered(int)), this, SLOT(slotPageSizes(int)) ); // Trim View actions d->aTrimMode = new KActionMenu(i18n( "&Trim View" ), this ); d->aTrimMode->setDelayed( false ); ac->addAction(QStringLiteral("view_trim_mode"), d->aTrimMode ); d->aTrimMargins = new KToggleAction( i18n( "&Trim Margins" ), d->aTrimMode->menu() ); d->aTrimMode->addAction( d->aTrimMargins ); ac->addAction( QStringLiteral("view_trim_margins"), d->aTrimMargins ); d->aTrimMargins->setData( qVariantFromValue( (int)Okular::Settings::EnumTrimMode::Margins ) ); connect( d->aTrimMargins, &QAction::toggled, this, &PageView::slotTrimMarginsToggled ); d->aTrimMargins->setChecked( Okular::Settings::trimMargins() ); d->aTrimToSelection = new KToggleAction( i18n( "Trim To &Selection" ), d->aTrimMode->menu() ); d->aTrimMode->addAction( d->aTrimToSelection); ac->addAction( QStringLiteral("view_trim_selection"), d->aTrimToSelection); d->aTrimToSelection->setData( qVariantFromValue( (int)Okular::Settings::EnumTrimMode::Selection ) ); connect( d->aTrimToSelection, &QAction::toggled, this, &PageView::slotTrimToSelectionToggled ); d->aZoomFitWidth = new KToggleAction(QIcon::fromTheme( QStringLiteral("zoom-fit-width") ), i18n("Fit &Width"), this); ac->addAction(QStringLiteral("view_fit_to_width"), d->aZoomFitWidth ); connect( d->aZoomFitWidth, &QAction::toggled, this, &PageView::slotFitToWidthToggled ); d->aZoomFitPage = new KToggleAction(QIcon::fromTheme( QStringLiteral("zoom-fit-best") ), i18n("Fit &Page"), this); ac->addAction(QStringLiteral("view_fit_to_page"), d->aZoomFitPage ); connect( d->aZoomFitPage, &QAction::toggled, this, &PageView::slotFitToPageToggled ); d->aZoomAutoFit = new KToggleAction(QIcon::fromTheme( QStringLiteral("zoom-fit-best") ), i18n("&Auto Fit"), this); ac->addAction(QStringLiteral("view_auto_fit"), d->aZoomAutoFit ); connect( d->aZoomAutoFit, &QAction::toggled, this, &PageView::slotAutoFitToggled ); d->aFitWindowToPage = new QAction(QIcon::fromTheme( QStringLiteral("zoom-fit-width") ), i18n("Fit Wi&ndow to Page"), this); d->aFitWindowToPage->setEnabled( Okular::Settings::viewMode() == (int)Okular::Settings::EnumViewMode::Single ); ac->setDefaultShortcut(d->aFitWindowToPage, QKeySequence(Qt::CTRL + Qt::Key_J) ); ac->addAction( QStringLiteral("fit_window_to_page"), d->aFitWindowToPage ); connect( d->aFitWindowToPage, &QAction::triggered, this, &PageView::slotFitWindowToPage ); // View-Layout actions d->aViewMode = new KActionMenu( QIcon::fromTheme( QStringLiteral("view-split-left-right") ), i18n( "&View Mode" ), this ); d->aViewMode->setDelayed( false ); #define ADD_VIEWMODE_ACTION( text, name, id ) \ do { \ QAction *vm = new QAction( text, this ); \ vm->setCheckable( true ); \ vm->setData( qVariantFromValue( id ) ); \ d->aViewMode->addAction( vm ); \ ac->addAction( QStringLiteral(name), vm ); \ vmGroup->addAction( vm ); \ } while( 0 ) ac->addAction(QStringLiteral("view_render_mode"), d->aViewMode ); QActionGroup *vmGroup = new QActionGroup( this ); //d->aViewMode->menu() ); ADD_VIEWMODE_ACTION( i18n( "Single Page" ), "view_render_mode_single", (int)Okular::Settings::EnumViewMode::Single ); ADD_VIEWMODE_ACTION( i18n( "Facing Pages" ), "view_render_mode_facing", (int)Okular::Settings::EnumViewMode::Facing ); ADD_VIEWMODE_ACTION( i18n( "Facing Pages (Center First Page)" ), "view_render_mode_facing_center_first", (int)Okular::Settings::EnumViewMode::FacingFirstCentered ); ADD_VIEWMODE_ACTION( i18n( "Overview" ), "view_render_mode_overview", (int)Okular::Settings::EnumViewMode::Summary ); const QList viewModeActions = d->aViewMode->menu()->actions(); foreach(QAction *viewModeAction, viewModeActions) { if (viewModeAction->data().toInt() == Okular::Settings::viewMode()) { viewModeAction->setChecked( true ); } } connect( vmGroup, &QActionGroup::triggered, this, &PageView::slotViewMode ); #undef ADD_VIEWMODE_ACTION d->aViewContinuous = new KToggleAction(QIcon::fromTheme( QStringLiteral("view-list-text") ), i18n("&Continuous"), this); ac->addAction(QStringLiteral("view_continuous"), d->aViewContinuous ); connect( d->aViewContinuous, &QAction::toggled, this, &PageView::slotContinuousToggled ); d->aViewContinuous->setChecked( Okular::Settings::viewContinuous() ); // Mouse mode actions for viewer mode d->mouseModeActionGroup = new QActionGroup( this ); d->mouseModeActionGroup->setExclusive( true ); d->aMouseNormal = new QAction( QIcon::fromTheme( QStringLiteral("input-mouse") ), i18n( "&Browse Tool" ), this ); ac->addAction(QStringLiteral("mouse_drag"), d->aMouseNormal ); connect( d->aMouseNormal, &QAction::triggered, this, &PageView::slotSetMouseNormal ); d->aMouseNormal->setIconText( i18nc( "Browse Tool", "Browse" ) ); d->aMouseNormal->setCheckable( true ); ac->setDefaultShortcut(d->aMouseNormal, QKeySequence(Qt::CTRL + Qt::Key_1)); d->aMouseNormal->setActionGroup( d->mouseModeActionGroup ); d->aMouseNormal->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Browse ); QAction * mz = new QAction(QIcon::fromTheme( QStringLiteral("page-zoom") ), i18n("&Zoom Tool"), this); ac->addAction(QStringLiteral("mouse_zoom"), mz ); connect( mz, &QAction::triggered, this, &PageView::slotSetMouseZoom ); mz->setIconText( i18nc( "Zoom Tool", "Zoom" ) ); mz->setCheckable( true ); ac->setDefaultShortcut(mz, QKeySequence(Qt::CTRL + Qt::Key_2)); mz->setActionGroup( d->mouseModeActionGroup ); mz->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Zoom ); QAction * aToggleChangeColors = new QAction(i18n("&Toggle Change Colors"), this); ac->addAction(QStringLiteral("toggle_change_colors"), aToggleChangeColors ); connect( aToggleChangeColors, &QAction::triggered, this, &PageView::slotToggleChangeColors ); } // WARNING: 'setupViewerActions' must have been called before this method void PageView::setupActions( KActionCollection * ac ) { d->actionCollection = ac; ac->setDefaultShortcuts(d->aZoomIn, KStandardShortcut::zoomIn()); ac->setDefaultShortcuts(d->aZoomOut, KStandardShortcut::zoomOut()); // Mouse-Mode actions d->aMouseSelect = new QAction(QIcon::fromTheme( QStringLiteral("select-rectangular") ), i18n("&Selection Tool"), this); ac->addAction(QStringLiteral("mouse_select"), d->aMouseSelect ); connect( d->aMouseSelect, &QAction::triggered, this, &PageView::slotSetMouseSelect ); d->aMouseSelect->setIconText( i18nc( "Select Tool", "Selection" ) ); d->aMouseSelect->setCheckable( true ); ac->setDefaultShortcut(d->aMouseSelect, Qt::CTRL + Qt::Key_3); d->aMouseSelect->setActionGroup( d->mouseModeActionGroup ); d->aMouseSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::RectSelect ); d->aMouseTextSelect = new QAction(QIcon::fromTheme( QStringLiteral("draw-text") ), i18n("&Text Selection Tool"), this); ac->addAction(QStringLiteral("mouse_textselect"), d->aMouseTextSelect ); connect( d->aMouseTextSelect, &QAction::triggered, this, &PageView::slotSetMouseTextSelect ); d->aMouseTextSelect->setIconText( i18nc( "Text Selection Tool", "Text Selection" ) ); d->aMouseTextSelect->setCheckable( true ); ac->setDefaultShortcut(d->aMouseTextSelect, Qt::CTRL + Qt::Key_4); d->aMouseTextSelect->setActionGroup( d->mouseModeActionGroup ); d->aMouseTextSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TextSelect ); d->aMouseTableSelect = new QAction(QIcon::fromTheme( QStringLiteral("table") ), i18n("T&able Selection Tool"), this); ac->addAction(QStringLiteral("mouse_tableselect"), d->aMouseTableSelect ); connect( d->aMouseTableSelect, &QAction::triggered, this, &PageView::slotSetMouseTableSelect ); d->aMouseTableSelect->setIconText( i18nc( "Table Selection Tool", "Table Selection" ) ); d->aMouseTableSelect->setCheckable( true ); ac->setDefaultShortcut(d->aMouseTableSelect, Qt::CTRL + Qt::Key_5); d->aMouseTableSelect->setActionGroup( d->mouseModeActionGroup ); d->aMouseTableSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TableSelect ); d->aMouseMagnifier = new QAction(QIcon::fromTheme( QStringLiteral("document-preview") ), i18n("&Magnifier"), this); ac->addAction(QStringLiteral("mouse_magnifier"), d->aMouseMagnifier ); connect( d->aMouseMagnifier, &QAction::triggered, this, &PageView::slotSetMouseMagnifier ); d->aMouseMagnifier->setIconText( i18nc( "Magnifier Tool", "Magnifier" ) ); d->aMouseMagnifier->setCheckable( true ); ac->setDefaultShortcut(d->aMouseMagnifier, Qt::CTRL + Qt::Key_6); d->aMouseMagnifier->setActionGroup( d->mouseModeActionGroup ); d->aMouseMagnifier->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Magnifier ); d->aToggleAnnotator = new KToggleAction(QIcon::fromTheme( QStringLiteral("draw-freehand") ), i18n("&Review"), this); ac->addAction(QStringLiteral("mouse_toggle_annotate"), d->aToggleAnnotator ); d->aToggleAnnotator->setCheckable( true ); connect( d->aToggleAnnotator, &QAction::toggled, this, &PageView::slotToggleAnnotator ); ac->setDefaultShortcut(d->aToggleAnnotator, Qt::Key_F6); ToolAction *ta = new ToolAction( this ); ac->addAction( QStringLiteral("mouse_selecttools"), ta ); ta->addAction( d->aMouseSelect ); ta->addAction( d->aMouseTextSelect ); ta->addAction( d->aMouseTableSelect ); // speak actions #ifdef HAVE_SPEECH d->aSpeakDoc = new QAction( QIcon::fromTheme( QStringLiteral("text-speak") ), i18n( "Speak Whole Document" ), this ); ac->addAction( QStringLiteral("speak_document"), d->aSpeakDoc ); d->aSpeakDoc->setEnabled( false ); connect( d->aSpeakDoc, &QAction::triggered, this, &PageView::slotSpeakDocument ); d->aSpeakPage = new QAction( QIcon::fromTheme( QStringLiteral("text-speak") ), i18n( "Speak Current Page" ), this ); ac->addAction( QStringLiteral("speak_current_page"), d->aSpeakPage ); d->aSpeakPage->setEnabled( false ); connect( d->aSpeakPage, &QAction::triggered, this, &PageView::slotSpeakCurrentPage ); d->aSpeakStop = new QAction( QIcon::fromTheme( QStringLiteral("media-playback-stop") ), i18n( "Stop Speaking" ), this ); ac->addAction( QStringLiteral("speak_stop_all"), d->aSpeakStop ); d->aSpeakStop->setEnabled( false ); connect( d->aSpeakStop, &QAction::triggered, this, &PageView::slotStopSpeaks ); #else d->aSpeakDoc = 0; d->aSpeakPage = 0; d->aSpeakStop = 0; #endif // Other actions QAction * su = new QAction(i18n("Scroll Up"), this); ac->addAction(QStringLiteral("view_scroll_up"), su ); connect( su, &QAction::triggered, this, &PageView::slotAutoScrollUp ); ac->setDefaultShortcut(su, QKeySequence(Qt::SHIFT + Qt::Key_Up)); addAction(su); QAction * sd = new QAction(i18n("Scroll Down"), this); ac->addAction(QStringLiteral("view_scroll_down"), sd ); connect( sd, &QAction::triggered, this, &PageView::slotAutoScrollDown ); ac->setDefaultShortcut(sd, QKeySequence(Qt::SHIFT + Qt::Key_Down)); addAction(sd); QAction * spu = new QAction(i18n("Scroll Page Up"), this); ac->addAction( QStringLiteral("view_scroll_page_up"), spu ); connect( spu, &QAction::triggered, this, &PageView::slotScrollUp ); ac->setDefaultShortcut(spu, QKeySequence(Qt::SHIFT + Qt::Key_Space)); addAction( spu ); QAction * spd = new QAction(i18n("Scroll Page Down"), this); ac->addAction( QStringLiteral("view_scroll_page_down"), spd ); connect( spd, &QAction::triggered, this, &PageView::slotScrollDown ); ac->setDefaultShortcut(spd, QKeySequence(Qt::Key_Space)); addAction( spd ); d->aToggleForms = new QAction( this ); ac->addAction( QStringLiteral("view_toggle_forms"), d->aToggleForms ); connect( d->aToggleForms, &QAction::triggered, this, &PageView::slotToggleForms ); d->aToggleForms->setEnabled( false ); toggleFormWidgets( false ); d->aValidateSignatures = new QAction( this ); ac->addAction( QStringLiteral("validate_signatures"), d->aValidateSignatures ); connect( d->aValidateSignatures, &QAction::triggered, this, &PageView::slotValidateSignatures ); d->aValidateSignatures->setText( i18n("Validate All signatures")); d->aValidateSignatures->setEnabled( false ); // Setup undo and redo actions QAction *kundo = KStandardAction::create( KStandardAction::Undo, d->document, SLOT(undo()), ac ); QAction *kredo = KStandardAction::create( KStandardAction::Redo, d->document, SLOT(redo()), ac ); connect(d->document, &Okular::Document::canUndoChanged, kundo, &QAction::setEnabled); connect(d->document, &Okular::Document::canRedoChanged, kredo, &QAction::setEnabled); kundo->setEnabled(false); kredo->setEnabled(false); } bool PageView::canFitPageWidth() const { return Okular::Settings::viewMode() != Okular::Settings::EnumViewMode::Single || d->zoomMode != ZoomFitWidth; } void PageView::fitPageWidth( int page ) { // zoom: Fit Width, columns: 1. setActions + relayout + setPage + update d->zoomMode = ZoomFitWidth; Okular::Settings::setViewMode( 0 ); d->aZoomFitWidth->setChecked( true ); d->aZoomFitPage->setChecked( false ); d->aZoomAutoFit->setChecked( false ); d->aViewMode->menu()->actions().at( 0 )->setChecked( true ); viewport()->setUpdatesEnabled( false ); slotRelayoutPages(); viewport()->setUpdatesEnabled( true ); d->document->setViewportPage( page ); updateZoomText(); setFocus(); } void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNumber ) { if ( !annotation ) return; // find the annot window AnnotWindow* existWindow = nullptr; foreach(AnnotWindow *aw, d->m_annowindows) { if ( aw->annotation() == annotation ) { existWindow = aw; break; } } if ( existWindow == nullptr ) { existWindow = new AnnotWindow( this, annotation, d->document, pageNumber ); connect(existWindow, &QObject::destroyed, this, &PageView::slotAnnotationWindowDestroyed); d->m_annowindows << existWindow; } else { existWindow->raise(); existWindow->findChild()->setFocus(); } existWindow->show(); } void PageView::slotAnnotationWindowDestroyed( QObject * window ) { d->m_annowindows.remove( static_cast( window ) ); } void PageView::displayMessage( const QString & message, const QString & details, PageViewMessage::Icon icon, int duration ) { if ( !Okular::Settings::showOSD() ) { if (icon == PageViewMessage::Error) { if ( !details.isEmpty() ) KMessageBox::detailedError( this, message, details ); else KMessageBox::error( this, message ); } return; } // hide messageWindow if string is empty if ( message.isEmpty() ) return d->messageWindow->hide(); // display message (duration is length dependant) if (duration==-1) { duration = 500 + 100 * message.length(); if ( !details.isEmpty() ) duration += 500 + 100 * details.length(); } d->messageWindow->display( message, details, icon, duration ); } void PageView::reparseConfig() { // set the scroll bars policies Qt::ScrollBarPolicy scrollBarMode = Okular::Settings::showScrollBars() ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff; if ( horizontalScrollBarPolicy() != scrollBarMode ) { setHorizontalScrollBarPolicy( scrollBarMode ); setVerticalScrollBarPolicy( scrollBarMode ); } if ( Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Summary && ( (int)Okular::Settings::viewColumns() != d->setting_viewCols ) ) { d->setting_viewCols = Okular::Settings::viewColumns(); slotRelayoutPages(); } if (Okular::Settings::rtlReadingDirection() != d->rtl_Mode ) { d->rtl_Mode = Okular::Settings::rtlReadingDirection(); slotRelayoutPages(); } updatePageStep(); if ( d->annotator ) { d->annotator->setEnabled( false ); d->annotator->reparseConfig(); if ( d->aToggleAnnotator->isChecked() ) slotToggleAnnotator( true ); } // Something like invert colors may have changed // As we don't have a way to find out the old value // We just update the viewport, this shouldn't be that bad // since it's just a repaint of pixmaps we already have viewport()->update(); } KActionCollection *PageView::actionCollection() const { return d->actionCollection; } QAction *PageView::toggleFormsAction() const { return d->aToggleForms; } QAction *PageView::validateSignaturesAction() const { return d->aValidateSignatures; } int PageView::contentAreaWidth() const { return horizontalScrollBar()->maximum() + viewport()->width(); } int PageView::contentAreaHeight() const { return verticalScrollBar()->maximum() + viewport()->height(); } QPoint PageView::contentAreaPosition() const { return QPoint( horizontalScrollBar()->value(), verticalScrollBar()->value() ); } QPoint PageView::contentAreaPoint( const QPoint & pos ) const { return pos + contentAreaPosition(); } QPointF PageView::contentAreaPoint( const QPointF & pos ) const { return pos + contentAreaPosition(); } QString PageViewPrivate::selectedText() const { if ( pagesWithTextSelection.isEmpty() ) return QString(); QString text; QList< int > selpages = pagesWithTextSelection.toList(); qSort( selpages ); const Okular::Page * pg = nullptr; if ( selpages.count() == 1 ) { pg = document->page( selpages.first() ); text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); } else { pg = document->page( selpages.first() ); text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); int end = selpages.count() - 1; for( int i = 1; i < end; ++i ) { pg = document->page( selpages.at( i ) ); text.append( pg->text( nullptr, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); } pg = document->page( selpages.last() ); text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); } return text; } void PageView::copyTextSelection() const { const QString text = d->selectedText(); if ( !text.isEmpty() ) { QClipboard *cb = QApplication::clipboard(); cb->setText( text, QClipboard::Clipboard ); } } void PageView::selectAll() { QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd(); for ( ; it < itEnd; ++it ) { Okular::RegularAreaRect * area = textSelectionForItem( *it ); d->pagesWithTextSelection.insert( (*it)->pageNumber() ); d->document->setPageTextSelection( (*it)->pageNumber(), area, palette().color( QPalette::Active, QPalette::Highlight ) ); } } void PageView::createAnnotationsVideoWidgets(PageViewItem *item, const QLinkedList< Okular::Annotation * > &annotations) { qDeleteAll( item->videoWidgets() ); item->videoWidgets().clear(); QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.constBegin(), aEnd = annotations.constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * a = *aIt; if ( a->subType() == Okular::Annotation::AMovie ) { Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a ); VideoWidget * vw = new VideoWidget( movieAnn, movieAnn->movie(), d->document, viewport() ); item->videoWidgets().insert( movieAnn->movie(), vw ); vw->pageInitialized(); } else if ( a->subType() == Okular::Annotation::ARichMedia ) { Okular::RichMediaAnnotation * richMediaAnn = static_cast< Okular::RichMediaAnnotation * >( a ); VideoWidget * vw = new VideoWidget( richMediaAnn, richMediaAnn->movie(), d->document, viewport() ); item->videoWidgets().insert( richMediaAnn->movie(), vw ); vw->pageInitialized(); } else if ( a->subType() == Okular::Annotation::AScreen ) { const Okular::ScreenAnnotation * screenAnn = static_cast< Okular::ScreenAnnotation * >( a ); Okular::Movie *movie = GuiUtils::renditionMovieFromScreenAnnotation( screenAnn ); if ( movie ) { VideoWidget * vw = new VideoWidget( screenAnn, movie, d->document, viewport() ); item->videoWidgets().insert( movie, vw ); vw->pageInitialized(); } } } } //BEGIN DocumentObserver inherited methods void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags ) { bool documentChanged = setupFlags & Okular::DocumentObserver::DocumentChanged; const bool allownotes = d->document->isAllowed( Okular::AllowNotes ); const bool allowfillforms = d->document->isAllowed( Okular::AllowFillForms ); // allownotes may have changed if ( d->aToggleAnnotator ) d->aToggleAnnotator->setEnabled( allownotes ); // reuse current pages if nothing new if ( ( pageSet.count() == d->items.count() ) && !documentChanged && !( setupFlags & Okular::DocumentObserver::NewLayoutForPages ) ) { int count = pageSet.count(); for ( int i = 0; (i < count) && !documentChanged; i++ ) { if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() ) { documentChanged = true; } else { // even if the document has not changed, allowfillforms may have // changed, so update all fields' "canBeFilled" flag foreach ( FormWidgetIface * w, d->items[i]->formWidgets() ) w->setCanBeFilled( allowfillforms ); } } if ( !documentChanged ) { if ( setupFlags & Okular::DocumentObserver::UrlChanged ) { // Here with UrlChanged and no document changed it means we // need to update all the Annotation* and Form* otherwise // they still point to the old document ones, luckily the old ones are still // around so we can look for the new ones using unique ids, etc d->mouseAnnotation->updateAnnotationPointers(); foreach(AnnotWindow *aw, d->m_annowindows) { Okular::Annotation *newA = d->document->page( aw->pageNumber() )->annotation( aw->annotation()->uniqueName() ); aw->updateAnnotation( newA ); } const QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewport()->width(), viewport()->height() ); for ( int i = 0; i < count; i++ ) { PageViewItem *item = d->items[i]; const QSet fws = item->formWidgets(); foreach ( FormWidgetIface * w, fws ) { Okular::FormField *f = Okular::PagePrivate::findEquivalentForm( d->document->page( i ), w->formField() ); if (f) { w->setFormField( f ); } else { qWarning() << "Lost form field on document save, something is wrong"; item->formWidgets().remove(w); delete w; } } // For the video widgets we don't really care about reusing them since they don't contain much info so just // create them again createAnnotationsVideoWidgets( item, pageSet[i]->annotations() ); Q_FOREACH ( VideoWidget *vw, item->videoWidgets() ) { const Okular::NormalizedRect r = vw->normGeometry(); vw->setGeometry( qRound( item->uncroppedGeometry().left() + item->uncroppedWidth() * r.left ) + 1 - viewportRect.left(), qRound( item->uncroppedGeometry().top() + item->uncroppedHeight() * r.top ) + 1 - viewportRect.top(), qRound( fabs( r.right - r.left ) * item->uncroppedGeometry().width() ), qRound( fabs( r.bottom - r.top ) * item->uncroppedGeometry().height() ) ); // Workaround, otherwise the size somehow gets lost vw->show(); vw->hide(); } } } return; } } // mouseAnnotation must not access our PageViewItem widgets any longer d->mouseAnnotation->reset(); // delete all widgets (one for each page in pageSet) QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); for ( ; dIt != dEnd; ++dIt ) delete *dIt; d->items.clear(); d->visibleItems.clear(); d->pagesWithTextSelection.clear(); toggleFormWidgets( false ); if ( d->formsWidgetController ) d->formsWidgetController->dropRadioButtons(); bool haspages = !pageSet.isEmpty(); bool hasformwidgets = false; bool hassignatureforms = false; // create children widgets QVector< Okular::Page * >::const_iterator setIt = pageSet.constBegin(), setEnd = pageSet.constEnd(); for ( ; setIt != setEnd; ++setIt ) { PageViewItem * item = new PageViewItem( *setIt ); d->items.push_back( item ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug).nospace() << "cropped geom for " << d->items.last()->pageNumber() << " is " << d->items.last()->croppedGeometry(); #endif const QLinkedList< Okular::FormField * > pageFields = (*setIt)->formFields(); QLinkedList< Okular::FormField * >::const_iterator ffIt = pageFields.constBegin(), ffEnd = pageFields.constEnd(); for ( ; ffIt != ffEnd; ++ffIt ) { Okular::FormField * ff = *ffIt; FormWidgetIface * w = FormWidgetFactory::createWidget( ff, viewport() ); if ( w ) { w->setPageItem( item ); w->setFormWidgetsController( d->formWidgetsController() ); w->setVisibility( false ); w->setCanBeFilled( allowfillforms ); item->formWidgets().insert( w ); hasformwidgets = true; if ( w->formField()->type() == Okular::FormField::FormSignature ) hassignatureforms = true; } } createAnnotationsVideoWidgets( item, (*setIt)->annotations() ); } // invalidate layout so relayout/repaint will happen on next viewport change if ( haspages ) { // We do a delayed call to slotRelayoutPages but also set the dirtyLayout // because we might end up in notifyViewportChanged while slotRelayoutPages // has not been done and we don't want that to happen d->dirtyLayout = true; QMetaObject::invokeMethod(this, "slotRelayoutPages", Qt::QueuedConnection); } else { // update the mouse cursor when closing because we may have close through a link and // want the cursor to come back to the normal cursor updateCursor(); // then, make the message window and scrollbars disappear, and trigger a repaint d->messageWindow->hide(); resizeContentArea( QSize( 0,0 ) ); viewport()->update(); // when there is no change to the scrollbars, no repaint would // be done and the old document would still be shown } // OSD to display pages if ( documentChanged && pageSet.count() > 0 && Okular::Settings::showOSD() ) d->messageWindow->display( i18np(" Loaded a one-page document.", " Loaded a %1-page document.", pageSet.count() ), QString(), PageViewMessage::Info, 4000 ); updateActionState( haspages, documentChanged, hasformwidgets, hassignatureforms ); // We need to assign it to a different list otherwise slotAnnotationWindowDestroyed // will bite us and clear d->m_annowindows QSet< AnnotWindow * > annowindows = d->m_annowindows; d->m_annowindows.clear(); qDeleteAll( annowindows ); selectionClear(); } void PageView::updateActionState(bool haspages, bool documentChanged, bool hasformwidgets, bool hassignatureforms ) { if ( d->aPageSizes ) { // may be null if dummy mode is on bool pageSizes = d->document->supportsPageSizes(); d->aPageSizes->setEnabled( pageSizes ); // set the new page sizes: // - if the generator supports them // - if the document changed if ( pageSizes && documentChanged ) { QStringList items; foreach ( const Okular::PageSize &p, d->document->pageSizes() ) items.append( p.name() ); d->aPageSizes->setItems( items ); } } if ( d->aTrimMargins ) d->aTrimMargins->setEnabled( haspages ); if ( d->aTrimToSelection ) d->aTrimToSelection->setEnabled( haspages ); if ( d->aViewMode ) d->aViewMode->setEnabled( haspages ); if ( d->aViewContinuous ) d->aViewContinuous->setEnabled( haspages ); if ( d->aZoomFitWidth ) d->aZoomFitWidth->setEnabled( haspages ); if ( d->aZoomFitPage ) d->aZoomFitPage->setEnabled( haspages ); if ( d->aZoomAutoFit ) d->aZoomAutoFit->setEnabled( haspages ); if ( d->aZoom ) { d->aZoom->selectableActionGroup()->setEnabled( haspages ); d->aZoom->setEnabled( haspages ); } if ( d->aZoomIn ) d->aZoomIn->setEnabled( haspages ); if ( d->aZoomOut ) d->aZoomOut->setEnabled( haspages ); if ( d->mouseModeActionGroup ) d->mouseModeActionGroup->setEnabled( haspages ); if ( d->aRotateClockwise ) d->aRotateClockwise->setEnabled( haspages ); if ( d->aRotateCounterClockwise ) d->aRotateCounterClockwise->setEnabled( haspages ); if ( d->aRotateOriginal ) d->aRotateOriginal->setEnabled( haspages ); if ( d->aToggleForms ) { // may be null if dummy mode is on d->aToggleForms->setEnabled( haspages && hasformwidgets ); } bool allowAnnotations = d->document->isAllowed( Okular::AllowNotes ); if ( d->annotator ) { bool allowTools = haspages && allowAnnotations; d->annotator->setToolsEnabled( allowTools ); d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() ); } if ( d->aToggleAnnotator ) { if ( !allowAnnotations && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); } d->aToggleAnnotator->setEnabled( allowAnnotations ); } #ifdef HAVE_SPEECH if ( d->aSpeakDoc ) { const bool enablettsactions = haspages ? Okular::Settings::useTTS() : false; d->aSpeakDoc->setEnabled( enablettsactions ); d->aSpeakPage->setEnabled( enablettsactions ); } #endif if (d->aMouseMagnifier) d->aMouseMagnifier->setEnabled(d->document->supportsTiles()); if ( d->aFitWindowToPage ) d->aFitWindowToPage->setEnabled( haspages && !Okular::Settings::viewContinuous() ); if ( d->aValidateSignatures ) d->aValidateSignatures->setEnabled( haspages && hasformwidgets && hassignatureforms ); } bool PageView::areSourceLocationsShownGraphically() const { return Okular::Settings::showSourceLocationsGraphically(); } void PageView::setShowSourceLocationsGraphically(bool show) { if( show == Okular::Settings::showSourceLocationsGraphically() ) { return; } Okular::Settings::setShowSourceLocationsGraphically( show ); viewport()->update(); } void PageView::setLastSourceLocationViewport( const Okular::DocumentViewport& vp ) { if( vp.rePos.enabled ) { d->lastSourceLocationViewportNormalizedX = normClamp( vp.rePos.normalizedX, 0.5 ); d->lastSourceLocationViewportNormalizedY = normClamp( vp.rePos.normalizedY, 0.0 ); } else { d->lastSourceLocationViewportNormalizedX = 0.5; d->lastSourceLocationViewportNormalizedY = 0.0; } d->lastSourceLocationViewportPageNumber = vp.pageNumber; viewport()->update(); } void PageView::clearLastSourceLocationViewport() { d->lastSourceLocationViewportPageNumber = -1; d->lastSourceLocationViewportNormalizedX = 0.0; d->lastSourceLocationViewportNormalizedY = 0.0; viewport()->update(); } void PageView::notifyViewportChanged( bool smoothMove ) { QMetaObject::invokeMethod(this, "slotRealNotifyViewportChanged", Qt::QueuedConnection, Q_ARG( bool, smoothMove )); } void PageView::slotRealNotifyViewportChanged( bool smoothMove ) { // if we are the one changing viewport, skip this nofity if ( d->blockViewport ) return; // block setViewport outgoing calls d->blockViewport = true; // find PageViewItem matching the viewport description const Okular::DocumentViewport & vp = d->document->viewport(); PageViewItem * item = nullptr; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) if ( (*iIt)->pageNumber() == vp.pageNumber ) { item = *iIt; break; } if ( !item ) { qCWarning(OkularUiDebug) << "viewport for page" << vp.pageNumber << "has no matching item!"; d->blockViewport = false; return; } #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "document viewport changed"; #endif // relayout in "Single Pages" mode or if a relayout is pending d->blockPixmapsRequest = true; if ( !Okular::Settings::viewContinuous() || d->dirtyLayout ) slotRelayoutPages(); // restore viewport center or use default {x-center,v-top} alignment const QRect & r = item->croppedGeometry(); int newCenterX = r.left(), newCenterY = r.top(); if ( vp.rePos.enabled ) { if ( vp.rePos.pos == Okular::DocumentViewport::Center ) { newCenterX += (int)( normClamp( vp.rePos.normalizedX, 0.5 ) * (double)r.width() ); newCenterY += (int)( normClamp( vp.rePos.normalizedY, 0.0 ) * (double)r.height() ); } else { // TopLeft newCenterX += (int)( normClamp( vp.rePos.normalizedX, 0.0 ) * (double)r.width() + viewport()->width() / 2 ); newCenterY += (int)( normClamp( vp.rePos.normalizedY, 0.0 ) * (double)r.height() + viewport()->height() / 2 ); } } else { newCenterX += r.width() / 2; newCenterY += viewport()->height() / 2 - 10; } // if smooth movement requested, setup parameters and start it if ( smoothMove ) { d->viewportMoveActive = true; d->viewportMoveTime.start(); d->viewportMoveDest.setX( newCenterX ); d->viewportMoveDest.setY( newCenterY ); if ( !d->viewportMoveTimer ) { d->viewportMoveTimer = new QTimer( this ); connect( d->viewportMoveTimer, &QTimer::timeout, this, &PageView::slotMoveViewport ); } d->viewportMoveTimer->start( 25 ); verticalScrollBar()->setEnabled( false ); horizontalScrollBar()->setEnabled( false ); } else center( newCenterX, newCenterY ); d->blockPixmapsRequest = false; // request visible pixmaps in the current viewport and recompute it slotRequestVisiblePixmaps(); // enable setViewport calls d->blockViewport = false; if( viewport() ) { viewport()->update(); } // since the page has moved below cursor, update it updateCursor(); } void PageView::notifyPageChanged( int pageNumber, int changedFlags ) { // only handle pixmap / highlight changes notifies if ( changedFlags & DocumentObserver::Bookmark ) return; if ( changedFlags & DocumentObserver::Annotations ) { const QLinkedList< Okular::Annotation * > annots = d->document->page( pageNumber )->annotations(); const QLinkedList< Okular::Annotation * >::ConstIterator annItEnd = annots.end(); QSet< AnnotWindow * >::Iterator it = d->m_annowindows.begin(); for ( ; it != d->m_annowindows.end(); ) { QLinkedList< Okular::Annotation * >::ConstIterator annIt = qFind( annots, (*it)->annotation() ); if ( annIt != annItEnd ) { (*it)->reloadInfo(); ++it; } else { AnnotWindow *w = *it; it = d->m_annowindows.erase( it ); // Need to delete after removing from the list // otherwise deleting will call slotAnnotationWindowDestroyed which will mess // the list and the iterators delete w; } } d->mouseAnnotation->notifyAnnotationChanged( pageNumber ); } if ( changedFlags & DocumentObserver::BoundingBox ) { #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "BoundingBox change on page" << pageNumber; #endif slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! // Repaint the whole widget since layout may have changed viewport()->update(); return; } // iterate over visible items: if page(pageNumber) is one of them, repaint it QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); for ( ; iIt != iEnd; ++iIt ) if ( (*iIt)->pageNumber() == pageNumber && (*iIt)->isVisible() ) { // update item's rectangle plus the little outline QRect expandedRect = (*iIt)->croppedGeometry(); // a PageViewItem is placed in the global page layout, // while we need to map its position in the viewport coordinates // (to get the correct area to repaint) expandedRect.translate( -contentAreaPosition() ); expandedRect.adjust( -1, -1, 3, 3 ); viewport()->update( expandedRect ); // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor if ( cursor().shape() != Qt::SizeVerCursor ) { // since the page has been regenerated below cursor, update it updateCursor(); } break; } } void PageView::notifyContentsCleared( int changedFlags ) { // if pixmaps were cleared, re-ask them if ( changedFlags & DocumentObserver::Pixmap ) QMetaObject::invokeMethod(this, "slotRequestVisiblePixmaps", Qt::QueuedConnection); } void PageView::notifyZoom( int factor ) { if ( factor > 0 ) updateZoom( ZoomIn ); else updateZoom( ZoomOut ); } bool PageView::canUnloadPixmap( int pageNumber ) const { if ( Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal ) { // if the item is visible, forbid unloading QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd(); for ( ; vIt != vEnd; ++vIt ) if ( (*vIt)->pageNumber() == pageNumber ) return false; } else { // forbid unloading of the visible items, and of the previous and next QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd(); for ( ; vIt != vEnd; ++vIt ) if ( abs( (*vIt)->pageNumber() - pageNumber ) <= 1 ) return false; } // if hidden premit unloading return true; } void PageView::notifyCurrentPageChanged( int previous, int current ) { if ( previous != -1 ) { PageViewItem * item = d->items.at( previous ); if ( item ) { Q_FOREACH ( VideoWidget *videoWidget, item->videoWidgets() ) videoWidget->pageLeft(); } } if ( current != -1 ) { PageViewItem * item = d->items.at( current ); if ( item ) { Q_FOREACH ( VideoWidget *videoWidget, item->videoWidgets() ) videoWidget->pageEntered(); } // update zoom text and factor if in a ZoomFit/* zoom mode if ( d->zoomMode != ZoomFixed ) updateZoomText(); } } //END DocumentObserver inherited methods //BEGIN View inherited methods bool PageView::supportsCapability( ViewCapability capability ) const { switch ( capability ) { case Zoom: case ZoomModality: return true; } return false; } Okular::View::CapabilityFlags PageView::capabilityFlags( ViewCapability capability ) const { switch ( capability ) { case Zoom: case ZoomModality: return CapabilityRead | CapabilityWrite | CapabilitySerializable; } return nullptr; } QVariant PageView::capability( ViewCapability capability ) const { switch ( capability ) { case Zoom: return d->zoomFactor; case ZoomModality: return d->zoomMode; } return QVariant(); } void PageView::setCapability( ViewCapability capability, const QVariant &option ) { switch ( capability ) { case Zoom: { bool ok = true; double factor = option.toDouble( &ok ); if ( ok && factor > 0.0 ) { d->zoomFactor = static_cast< float >( factor ); updateZoom( ZoomRefreshCurrent ); } break; } case ZoomModality: { bool ok = true; int mode = option.toInt( &ok ); if ( ok ) { if ( mode >= 0 && mode < 3 ) updateZoom( (ZoomMode)mode ); } break; } } } //END View inherited methods //BEGIN widget events bool PageView::event( QEvent * event ) { if ( event->type() == QEvent::Gesture ) { return gestureEvent(static_cast( event )); } // do not stop the event return QAbstractScrollArea::event( event ); } bool PageView::gestureEvent( QGestureEvent * event ) { QPinchGesture *pinch = static_cast(event->gesture(Qt::PinchGesture)); if (pinch) { // Viewport zoom level at the moment where the pinch gesture starts. // The viewport zoom level _during_ the gesture will be this value // times the relative zoom reported by QGestureEvent. static qreal vanillaZoom = d->zoomFactor; if (pinch->state() == Qt::GestureStarted) { vanillaZoom = d->zoomFactor; } const QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags(); // Zoom if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged) { d->zoomFactor = vanillaZoom * pinch->totalScaleFactor(); d->blockPixmapsRequest = true; updateZoom( ZoomRefreshCurrent ); d->blockPixmapsRequest = false; viewport()->repaint(); } // Count the number of 90-degree rotations we did since the start of the pinch gesture. // Otherwise a pinch turned to 90 degrees and held there will rotate the page again and again. static int rotations = 0; if (changeFlags & QPinchGesture::RotationAngleChanged) { // Rotation angle relative to the accumulated page rotations triggered by the current pinch // We actually turn at 80 degrees rather than at 90 degrees. That's less strain on the hands. const qreal relativeAngle = pinch->rotationAngle() - rotations*90; if (relativeAngle > 80) { slotRotateClockwise(); rotations++; } if (relativeAngle < -80) { slotRotateCounterClockwise(); rotations--; } } if (pinch->state() == Qt::GestureFinished) { rotations = 0; } return true; } return false; } void PageView::paintEvent(QPaintEvent *pe) { const QPoint areaPos = contentAreaPosition(); // create the rect into contents from the clipped screen rect QRect viewportRect = viewport()->rect(); viewportRect.translate( areaPos ); QRect contentsRect = pe->rect().translated( areaPos ).intersected( viewportRect ); if ( !contentsRect.isValid() ) return; #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "paintevent" << contentsRect; #endif // create the screen painter. a pixel painted at contentsX,contentsY // appears to the top-left corner of the scrollview. QPainter screenPainter( viewport() ); // translate to simulate the scrolled content widget screenPainter.translate( -areaPos ); // selectionRect is the normalized mouse selection rect QRect selectionRect = d->mouseSelectionRect; if ( !selectionRect.isNull() ) selectionRect = selectionRect.normalized(); // selectionRectInternal without the border QRect selectionRectInternal = selectionRect; selectionRectInternal.adjust( 1, 1, -1, -1 ); // color for blending QColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ? d->mouseSelectionColor : Qt::red; // subdivide region into rects const QVector &allRects = pe->region().rects(); uint numRects = allRects.count(); // preprocess rects area to see if it worths or not using subdivision uint summedArea = 0; for ( uint i = 0; i < numRects; i++ ) { const QRect & r = allRects[i]; summedArea += r.width() * r.height(); } // very elementary check: SUMj(Region[j].area) is less than boundingRect.area bool useSubdivision = summedArea < (0.6 * contentsRect.width() * contentsRect.height()); if ( !useSubdivision ) numRects = 1; // iterate over the rects (only one loop if not using subdivision) for ( uint i = 0; i < numRects; i++ ) { if ( useSubdivision ) { // set 'contentsRect' to a part of the sub-divided region contentsRect = allRects[i].translated( areaPos ).intersected( viewportRect ); if ( !contentsRect.isValid() ) continue; } #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << contentsRect; #endif // note: this check will take care of all things requiring alpha blending (not only selection) bool wantCompositing = !selectionRect.isNull() && contentsRect.intersects( selectionRect ); // also alpha-blend when there is a table selection... wantCompositing |= !d->tableSelectionParts.isEmpty(); if ( wantCompositing && Okular::Settings::enableCompositing() ) { // create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0}) QPixmap doubleBuffer( contentsRect.size() * devicePixelRatioF() ); doubleBuffer.setDevicePixelRatio(devicePixelRatioF()); QPainter pixmapPainter( &doubleBuffer ); pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() ); // 1) Layer 0: paint items and clear bg on unpainted rects drawDocumentOnPainter( contentsRect, &pixmapPainter ); // 2a) Layer 1a: paint (blend) transparent selection (rectangle) if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && !selectionRectInternal.contains( contentsRect ) ) { QRect blendRect = selectionRectInternal.intersected( contentsRect ); // skip rectangles covered by the selection's border if ( blendRect.isValid() ) { // grab current pixmap into a new one to colorize contents QPixmap blendedPixmap( blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); blendedPixmap.setDevicePixelRatio(devicePixelRatioF()); QPainter p( &blendedPixmap ); p.drawPixmap( 0, 0, doubleBuffer, (blendRect.left() - contentsRect.left()) * devicePixelRatioF(), (blendRect.top() - contentsRect.top()) * devicePixelRatioF(), blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); QColor blCol = selBlendColor.dark( 140 ); blCol.setAlphaF( 0.2 ); p.fillRect( blendedPixmap.rect(), blCol ); p.end(); // copy the blended pixmap back to its place pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap ); } // draw border (red if the selection is too small) pixmapPainter.setPen( selBlendColor ); pixmapPainter.drawRect( selectionRect.adjusted( 0, 0, -1, -1 ) ); } // 2b) Layer 1b: paint (blend) transparent selection (table) foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); QRect selectionPartRectInternal = selectionPartRect; selectionPartRectInternal.adjust( 1, 1, -1, -1 ); if ( !selectionPartRect.isNull() && selectionPartRect.intersects( contentsRect ) && !selectionPartRectInternal.contains( contentsRect ) ) { QRect blendRect = selectionPartRectInternal.intersected( contentsRect ); // skip rectangles covered by the selection's border if ( blendRect.isValid() ) { // grab current pixmap into a new one to colorize contents QPixmap blendedPixmap( blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); blendedPixmap.setDevicePixelRatio(devicePixelRatioF()); QPainter p( &blendedPixmap ); p.drawPixmap( 0, 0, doubleBuffer, (blendRect.left() - contentsRect.left()) * devicePixelRatioF(), (blendRect.top() - contentsRect.top()) * devicePixelRatioF(), blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); QColor blCol = d->mouseSelectionColor.dark( 140 ); blCol.setAlphaF( 0.2 ); p.fillRect( blendedPixmap.rect(), blCol ); p.end(); // copy the blended pixmap back to its place pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap ); } // draw border (red if the selection is too small) pixmapPainter.setPen( d->mouseSelectionColor ); pixmapPainter.drawRect( selectionPartRect.adjusted( 0, 0, -1, -1 ) ); } } drawTableDividers( &pixmapPainter ); // 3a) Layer 1: give annotator painting control if ( d->annotator && d->annotator->routePaints( contentsRect ) ) d->annotator->routePaint( &pixmapPainter, contentsRect ); // 3b) Layer 1: give mouseAnnotation painting control d->mouseAnnotation->routePaint( &pixmapPainter, contentsRect ); // 4) Layer 2: overlays if ( Okular::Settings::debugDrawBoundaries() ) { pixmapPainter.setPen( Qt::blue ); pixmapPainter.drawRect( contentsRect ); } // finish painting and draw contents pixmapPainter.end(); screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer ); } else { // 1) Layer 0: paint items and clear bg on unpainted rects drawDocumentOnPainter( contentsRect, &screenPainter ); // 2a) Layer 1a: paint opaque selection (rectangle) if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && !selectionRectInternal.contains( contentsRect ) ) { screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) ); screenPainter.drawRect( selectionRect ); } // 2b) Layer 1b: paint opaque selection (table) foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); QRect selectionPartRectInternal = selectionPartRect; selectionPartRectInternal.adjust( 1, 1, -1, -1 ); if ( !selectionPartRect.isNull() && selectionPartRect.intersects( contentsRect ) && !selectionPartRectInternal.contains( contentsRect ) ) { screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) ); screenPainter.drawRect( selectionPartRect ); } } drawTableDividers( &screenPainter ); // 3a) Layer 1: give annotator painting control if ( d->annotator && d->annotator->routePaints( contentsRect ) ) d->annotator->routePaint( &screenPainter, contentsRect ); // 3b) Layer 1: give mouseAnnotation painting control d->mouseAnnotation->routePaint( &screenPainter, contentsRect ); // 4) Layer 2: overlays if ( Okular::Settings::debugDrawBoundaries() ) { screenPainter.setPen( Qt::red ); screenPainter.drawRect( contentsRect ); } } } } void PageView::drawTableDividers(QPainter * screenPainter) { if (!d->tableSelectionParts.isEmpty()) { screenPainter->setPen( d->mouseSelectionColor.dark() ); if (d->tableDividersGuessed) { QPen p = screenPainter->pen(); p.setStyle( Qt::DashLine ); screenPainter->setPen( p ); } foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); QRect selectionPartRectInternal = selectionPartRect; selectionPartRectInternal.adjust( 1, 1, -1, -1 ); foreach(double col, d->tableSelectionCols) { if (col >= tsp.rectInSelection.left && col <= tsp.rectInSelection.right) { col = (col - tsp.rectInSelection.left) / (tsp.rectInSelection.right - tsp.rectInSelection.left); const int x = selectionPartRect.left() + col * selectionPartRect.width() + 0.5; screenPainter->drawLine( x, selectionPartRectInternal.top(), x, selectionPartRectInternal.top() + selectionPartRectInternal.height() ); } } foreach(double row, d->tableSelectionRows) { if (row >= tsp.rectInSelection.top && row <= tsp.rectInSelection.bottom) { row = (row - tsp.rectInSelection.top) / (tsp.rectInSelection.bottom - tsp.rectInSelection.top); const int y = selectionPartRect.top() + row * selectionPartRect.height() + 0.5; screenPainter->drawLine( selectionPartRectInternal.left(), y, selectionPartRectInternal.left() + selectionPartRectInternal.width(), y ); } } } } } void PageView::resizeEvent( QResizeEvent *e ) { if ( d->items.isEmpty() ) { resizeContentArea( e->size() ); return; } if ( ( d->zoomMode == ZoomFitWidth || d->zoomMode == ZoomFitAuto ) && !verticalScrollBar()->isVisible() && qAbs(e->oldSize().height() - e->size().height()) < verticalScrollBar()->width() && d->verticalScrollBarVisible ) { // this saves us from infinite resizing loop because of scrollbars appearing and disappearing // see bug 160628 for more info // TODO looks are still a bit ugly because things are left uncentered // but better a bit ugly than unusable d->verticalScrollBarVisible = false; resizeContentArea( e->size() ); return; } else if ( d->zoomMode == ZoomFitAuto && !horizontalScrollBar()->isVisible() && qAbs(e->oldSize().width() - e->size().width()) < horizontalScrollBar()->height() && d->horizontalScrollBarVisible ) { // this saves us from infinite resizing loop because of scrollbars appearing and disappearing // TODO looks are still a bit ugly because things are left uncentered // but better a bit ugly than unusable d->horizontalScrollBarVisible = false; resizeContentArea( e->size() ); return; } // start a timer that will refresh the pixmap after 0.2s d->delayResizeEventTimer->start( 200 ); d->verticalScrollBarVisible = verticalScrollBar()->isVisible(); d->horizontalScrollBarVisible = horizontalScrollBar()->isVisible(); } void PageView::keyPressEvent( QKeyEvent * e ) { e->accept(); // if performing a selection or dyn zooming, disable keys handling if ( ( d->mouseSelecting && e->key() != Qt::Key_Escape ) || ( QApplication::mouseButtons () & Qt::MidButton ) ) return; // if viewport is moving, disable keys handling if ( d->viewportMoveActive ) return; // move/scroll page by using keys switch ( e->key() ) { case Qt::Key_J: case Qt::Key_K: case Qt::Key_Down: case Qt::Key_PageDown: case Qt::Key_Up: case Qt::Key_PageUp: case Qt::Key_Backspace: if ( e->key() == Qt::Key_Down || e->key() == Qt::Key_PageDown || e->key() == Qt::Key_J ) { bool singleStep = e->key() == Qt::Key_Down || e->key() == Qt::Key_J; slotScrollDown( singleStep ); } else { bool singleStep = e->key() == Qt::Key_Up || e->key() == Qt::Key_K; slotScrollUp( singleStep ); } break; case Qt::Key_Left: case Qt::Key_H: if ( horizontalScrollBar()->maximum() == 0 ) { //if we cannot scroll we go to the previous page vertically int next_page = d->document->currentPage() - viewColumns(); d->document->setViewportPage(next_page); } else horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub ); break; case Qt::Key_Right: case Qt::Key_L: if ( horizontalScrollBar()->maximum() == 0 ) { //if we cannot scroll we advance the page vertically int next_page = d->document->currentPage() + viewColumns(); d->document->setViewportPage(next_page); } else horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd ); break; case Qt::Key_Escape: emit escPressed(); selectionClear( d->tableDividersGuessed ? ClearOnlyDividers : ClearAllSelection ); d->mousePressPos = QPoint(); if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } d->mouseAnnotation->routeKeyPressEvent( e ); break; case Qt::Key_Delete: d->mouseAnnotation->routeKeyPressEvent( e ); break; case Qt::Key_Shift: case Qt::Key_Control: if ( d->autoScrollTimer ) { if ( d->autoScrollTimer->isActive() ) d->autoScrollTimer->stop(); else slotAutoScroll(); return; } // fallthrough default: e->ignore(); return; } // if a known key has been pressed, stop scrolling the page if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::keyReleaseEvent( QKeyEvent * e ) { e->accept(); if ( d->annotator && d->annotator->active() ) { if ( d->annotator->routeKeyEvent( e ) ) return; } if ( e->key() == Qt::Key_Escape && d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::inputMethodEvent( QInputMethodEvent * e ) { Q_UNUSED(e) } void PageView::tabletEvent( QTabletEvent * e ) { // Ignore tablet events that we don't care about if ( !( e->type() == QEvent::TabletPress || e->type() == QEvent::TabletRelease || e->type() == QEvent::TabletMove ) ) { e->ignore(); return; } // Determine pen state bool penReleased = false; if ( e->type() == QEvent::TabletPress ) { d->penDown = true; } if ( e->type() == QEvent::TabletRelease ) { d->penDown = false; penReleased = true; } // If we're editing an annotation and the tablet pen is either down or just released // then dispatch event to annotator if ( d->annotator && d->annotator->active() && ( d->penDown || penReleased ) ) { const QPoint eventPos = contentAreaPoint( e->pos() ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); const QPoint localOriginInGlobal = mapToGlobal( QPoint(0,0) ); // routeTabletEvent will accept or ignore event as appropriate d->annotator->routeTabletEvent( e, pageItem, localOriginInGlobal ); } else { e->ignore(); } } void PageView::mouseMoveEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; // don't perform any mouse action when no document is shown if ( d->items.isEmpty() ) return; // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; // if holding mouse mid button, perform zoom if ( e->buttons() & Qt::MidButton ) { int mouseY = e->globalPos().y(); int deltaY = d->mouseMidLastY - mouseY; // wrap mouse from top to bottom const QRect mouseContainer = QApplication::desktop()->screenGeometry( this ); const int absDeltaY = abs(deltaY); if ( absDeltaY > mouseContainer.height() / 2 ) { deltaY = mouseContainer.height() - absDeltaY; } const float upperZoomLimit = d->document->supportsTiles() ? 15.99 : 3.99; if ( mouseY <= mouseContainer.top() + 4 && d->zoomFactor < upperZoomLimit ) { mouseY = mouseContainer.bottom() - 5; QCursor::setPos( e->globalPos().x(), mouseY ); } // wrap mouse from bottom to top else if ( mouseY >= mouseContainer.bottom() - 4 && d->zoomFactor > 0.101 ) { mouseY = mouseContainer.top() + 5; QCursor::setPos( e->globalPos().x(), mouseY ); } // remember last position d->mouseMidLastY = mouseY; // update zoom level, perform zoom and redraw if ( deltaY ) { d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) ); d->blockPixmapsRequest = true; updateZoom( ZoomRefreshCurrent ); d->blockPixmapsRequest = false; viewport()->repaint(); } return; } const QPoint eventPos = contentAreaPoint( e->pos() ); // if we're editing an annotation, dispatch event to it if ( d->annotator && d->annotator->active() ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); updateCursor( eventPos ); d->annotator->routeMouseEvent( e, pageItem ); return; } bool leftButton = (e->buttons() == Qt::LeftButton); bool rightButton = (e->buttons() == Qt::RightButton); switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::Browse: { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); if ( leftButton ) { d->leftClickTimer.stop(); if ( pageItem && d->mouseAnnotation->isActive() ) { // if left button pressed and annotation is focused, forward move event d->mouseAnnotation->routeMouseMoveEvent( pageItem, eventPos, leftButton ); } // drag page else if ( !d->mouseGrabPos.isNull() ) { setCursor( Qt::ClosedHandCursor ); QPoint mousePos = e->globalPos(); QPoint delta = d->mouseGrabPos - mousePos; // wrap mouse from top to bottom const QRect mouseContainer = QApplication::desktop()->screenGeometry( this ); // If the delta is huge it probably means we just wrapped in that direction const QPoint absDelta(abs(delta.x()), abs(delta.y())); if ( absDelta.y() > mouseContainer.height() / 2 ) { delta.setY(mouseContainer.height() - absDelta.y()); } if ( absDelta.x() > mouseContainer.width() / 2 ) { delta.setX(mouseContainer.width() - absDelta.x()); } if ( mousePos.y() <= mouseContainer.top() + 4 && verticalScrollBar()->value() < verticalScrollBar()->maximum() - 10 ) { mousePos.setY( mouseContainer.bottom() - 5 ); QCursor::setPos( mousePos ); } // wrap mouse from bottom to top else if ( mousePos.y() >= mouseContainer.bottom() - 4 && verticalScrollBar()->value() > 10 ) { mousePos.setY( mouseContainer.top() + 5 ); QCursor::setPos( mousePos ); } // remember last position d->mouseGrabPos = mousePos; // scroll page by position increment scrollTo( horizontalScrollBar()->value() + delta.x(), verticalScrollBar()->value() + delta.y() ); } } else if ( rightButton && !d->mousePressPos.isNull() && d->aMouseSelect ) { // if mouse moves 5 px away from the press point, switch to 'selection' int deltaX = d->mousePressPos.x() - e->globalPos().x(), deltaY = d->mousePressPos.y() - e->globalPos().y(); if ( deltaX > 5 || deltaX < -5 || deltaY > 5 || deltaY < -5 ) { d->aPrevAction = d->aMouseNormal; d->aMouseSelect->trigger(); QPoint newPos = eventPos + QPoint( deltaX, deltaY ); selectionStart( newPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); updateSelection( eventPos ); break; } } else { /* Forward move events which are still not yet consumed by "mouse grab" or aMouseSelect */ d->mouseAnnotation->routeMouseMoveEvent( pageItem, eventPos, leftButton ); updateCursor(); } } break; case Okular::Settings::EnumMouseMode::Zoom: case Okular::Settings::EnumMouseMode::RectSelect: case Okular::Settings::EnumMouseMode::TableSelect: case Okular::Settings::EnumMouseMode::TrimSelect: // set second corner of selection if ( d->mouseSelecting ) { updateSelection( eventPos ); d->mouseOverLinkObject = nullptr; } updateCursor(); break; case Okular::Settings::EnumMouseMode::Magnifier: if ( e->buttons() ) // if any button is pressed at all { moveMagnifier( e->pos() ); updateMagnifier( eventPos ); } break; case Okular::Settings::EnumMouseMode::TextSelect: // if mouse moves 5 px away from the press point and the document soupports text extraction, do 'textselection' if ( !d->mouseTextSelecting && !d->mousePressPos.isNull() && d->document->supportsSearching() && ( ( eventPos - d->mouseSelectPos ).manhattanLength() > 5 ) ) { d->mouseTextSelecting = true; } updateSelection( eventPos ); updateCursor(); break; } } void PageView::mousePressEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; // don't perform any mouse action when no document is shown if ( d->items.isEmpty() ) return; // if performing a selection or dyn zooming, disable mouse press if ( d->mouseSelecting || ( e->button() != Qt::MidButton && ( e->buttons() & Qt::MidButton) ) || d->viewportMoveActive ) return; // if the page is scrolling, stop it if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } // if pressing mid mouse button while not doing other things, begin 'continuous zoom' mode if ( e->button() == Qt::MidButton ) { d->mouseMidLastY = e->globalPos().y(); setCursor( Qt::SizeVerCursor ); return; } const QPoint eventPos = contentAreaPoint( e->pos() ); // if we're editing an annotation, dispatch event to it if ( d->annotator && d->annotator->active() ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); d->annotator->routeMouseEvent( e, pageItem ); return; } // trigger history navigation for additional mouse buttons if ( e->button() == Qt::XButton1 ) { emit mouseBackButtonClick(); return; } if ( e->button() == Qt::XButton2 ) { emit mouseForwardButtonClick(); return; } // update press / 'start drag' mouse position d->mousePressPos = e->globalPos(); // handle mode dependant mouse press actions bool leftButton = e->button() == Qt::LeftButton, rightButton = e->button() == Qt::RightButton; // Not sure we should erase the selection when clicking with left. if ( d->mouseMode != Okular::Settings::EnumMouseMode::TextSelect ) textSelectionClear(); switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::Browse: // drag start / click / link following { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); if ( leftButton ) { if ( pageItem ) { d->mouseAnnotation->routeMousePressEvent( pageItem, eventPos ); } d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mousePressPos; if ( !d->mouseOnRect ) d->leftClickTimer.start( QApplication::doubleClickInterval() + 10 ); } else if ( rightButton ) { if ( pageItem ) { // find out normalized mouse coords inside current item const QRect & itemRect = pageItem->uncroppedGeometry(); double nX = pageItem->absToPageX(eventPos.x()); double nY = pageItem->absToPageY(eventPos.y()); const QLinkedList< const Okular::ObjectRect *> orects = pageItem->page()->objectRects( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() ); if ( !orects.isEmpty() ) { AnnotationPopup popup( d->document, AnnotationPopup::MultiAnnotationMode, this ); foreach ( const Okular::ObjectRect * orect, orects ) { Okular::Annotation * ann = ( (Okular::AnnotationObjectRect *)orect )->annotation(); if ( ann && (ann->subType() != Okular::Annotation::AWidget) ) popup.addAnnotation( ann, pageItem->pageNumber() ); } connect( &popup, &AnnotationPopup::openAnnotationWindow, this, &PageView::openAnnotationWindow ); popup.exec( e->globalPos() ); // Since ↑ spins its own event loop we won't get the mouse release event // so reset mousePressPos here d->mousePressPos = QPoint(); } } } } break; case Okular::Settings::EnumMouseMode::Zoom: // set first corner of the zoom rect if ( leftButton ) selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ), false ); else if ( rightButton ) updateZoom( ZoomOut ); break; case Okular::Settings::EnumMouseMode::Magnifier: moveMagnifier( e->pos() ); d->magnifierView->show(); updateMagnifier( eventPos ); break; case Okular::Settings::EnumMouseMode::RectSelect: // set first corner of the selection rect case Okular::Settings::EnumMouseMode::TrimSelect: if ( leftButton ) { selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); } break; case Okular::Settings::EnumMouseMode::TableSelect: if ( leftButton ) { if (d->tableSelectionParts.isEmpty()) { selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); } else { QRect updatedRect; foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); // This will update the whole table rather than just the added/removed divider // (which can span more than one part). updatedRect = updatedRect.united(selectionPartRect); if (!selectionPartRect.contains(eventPos)) continue; // At this point it's clear we're either adding or removing a divider manually, so obviously the user is happy with the guess (if any). d->tableDividersGuessed = false; // There's probably a neat trick to finding which edge it's closest to, // but this way has the advantage of simplicity. const int fromLeft = abs(selectionPartRect.left() - eventPos.x()); const int fromRight = abs(selectionPartRect.left() + selectionPartRect.width() - eventPos.x()); const int fromTop = abs(selectionPartRect.top() - eventPos.y()); const int fromBottom = abs(selectionPartRect.top() + selectionPartRect.height() - eventPos.y()); const int colScore = fromToptableSelectionCols.length(); i++) { const double col = (d->tableSelectionCols[i] - tsp.rectInSelection.left) / (tsp.rectInSelection.right - tsp.rectInSelection.left); const int colX = selectionPartRect.left() + col * selectionPartRect.width() + 0.5; if (abs(colX - eventPos.x())<=3) { d->tableSelectionCols.removeAt(i); deleted=true; break; } } if (!deleted) { double col = eventPos.x() - selectionPartRect.left(); col /= selectionPartRect.width(); // at this point, it's normalised within the part col *= (tsp.rectInSelection.right - tsp.rectInSelection.left); col += tsp.rectInSelection.left; // at this point, it's normalised within the whole table d->tableSelectionCols.append(col); qSort(d->tableSelectionCols); } } else { bool deleted=false; for(int i=0; itableSelectionRows.length(); i++) { const double row = (d->tableSelectionRows[i] - tsp.rectInSelection.top) / (tsp.rectInSelection.bottom - tsp.rectInSelection.top); const int rowY = selectionPartRect.top() + row * selectionPartRect.height() + 0.5; if (abs(rowY - eventPos.y())<=3) { d->tableSelectionRows.removeAt(i); deleted=true; break; } } if (!deleted) { double row = eventPos.y() - selectionPartRect.top(); row /= selectionPartRect.height(); // at this point, it's normalised within the part row *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); row += tsp.rectInSelection.top; // at this point, it's normalised within the whole table d->tableSelectionRows.append(row); qSort(d->tableSelectionRows); } } } updatedRect.translate( -contentAreaPosition() ); viewport()->update( updatedRect ); } } break; case Okular::Settings::EnumMouseMode::TextSelect: d->mouseSelectPos = eventPos; if ( !rightButton ) { textSelectionClear(); } break; } } void PageView::mouseReleaseEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; // stop the drag scrolling d->dragScrollTimer.stop(); d->leftClickTimer.stop(); const bool leftButton = e->button() == Qt::LeftButton; const bool rightButton = e->button() == Qt::RightButton; if ( d->mouseAnnotation->isActive() && leftButton ) { // Just finished to move the annotation d->mouseAnnotation->routeMouseReleaseEvent(); } // don't perform any mouse action when no document is shown.. if ( d->items.isEmpty() ) { // ..except for right Clicks (emitted even it viewport is empty) if ( e->button() == Qt::RightButton ) emit rightClick( nullptr, e->globalPos() ); return; } // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; const QPoint eventPos = contentAreaPoint( e->pos() ); // handle mode indepent mid buttom zoom if ( e->button() == Qt::MidButton ) { // request pixmaps since it was disabled during drag slotRequestVisiblePixmaps(); // the cursor may now be over a link.. update it updateCursor( eventPos ); return; } // if we're editing an annotation, dispatch event to it if ( d->annotator && d->annotator->active() ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); d->annotator->routeMouseEvent( e, pageItem ); return; } switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::Browse:{ // return the cursor to its normal state after dragging if ( cursor().shape() == Qt::ClosedHandCursor ) updateCursor( eventPos ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); const QPoint pressPos = contentAreaPoint( mapFromGlobal( d->mousePressPos ) ); const PageViewItem * pageItemPressPos = pickItemOnPoint( pressPos.x(), pressPos.y() ); // if the mouse has not moved since the press, that's a -click- if ( leftButton && pageItem && pageItem == pageItemPressPos && ( (d->mousePressPos - e->globalPos()).manhattanLength() < QApplication::startDragDistance() ) ) { if ( !mouseReleaseOverLink( d->mouseOverLinkObject ) && ( e->modifiers() == Qt::ShiftModifier ) ) { const double nX = pageItem->absToPageX(eventPos.x()); const double nY = pageItem->absToPageY(eventPos.y()); const Okular::ObjectRect * rect; // TODO: find a better way to activate the source reference "links" // for the moment they are activated with Shift + left click // Search the nearest source reference. rect = pageItem->page()->objectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( !rect ) { static const double s_minDistance = 0.025; // FIXME?: empirical value? double distance = 0.0; rect = pageItem->page()->nearestObjectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight(), &distance ); // distance is distanceSqr, adapt it to a normalized value distance = distance / (pow( pageItem->uncroppedWidth(), 2 ) + pow( pageItem->uncroppedHeight(), 2 )); if ( rect && ( distance > s_minDistance ) ) rect = nullptr; } if ( rect ) { const Okular::SourceReference * ref = static_cast< const Okular::SourceReference * >( rect->object() ); d->document->processSourceReference( ref ); } else { const Okular::SourceReference * ref = d->document->dynamicSourceReference( pageItem-> pageNumber(), nX * pageItem->page()->width(), nY * pageItem->page()->height() ); if ( ref ) { d->document->processSourceReference( ref ); delete ref; } } } #if 0 else { // a link can move us to another page or even to another document, there's no point in trying to // process the click on the image once we have processes the click on the link rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->width(), pageItem->height() ); if ( rect ) { // handle click over a image } /* Enrico and me have decided this is not worth the trouble it generates else { // if not on a rect, the click selects the page // if ( pageItem->pageNumber() != (int)d->document->currentPage() ) d->document->setViewportPage( pageItem->pageNumber(), this ); }*/ } #endif } else if ( rightButton && !d->mouseAnnotation->isModified() ) { if ( pageItem && pageItem == pageItemPressPos && ( (d->mousePressPos - e->globalPos()).manhattanLength() < QApplication::startDragDistance() ) ) { QMenu * menu = createProcessLinkMenu(pageItem, eventPos ); if ( menu ) { menu->exec( e->globalPos() ); menu->deleteLater(); } else { const double nX = pageItem->absToPageX(eventPos.x()); const double nY = pageItem->absToPageY(eventPos.y()); // a link can move us to another page or even to another document, there's no point in trying to // process the click on the image once we have processes the click on the link const Okular::ObjectRect * rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( rect ) { // handle right click over a image } else { // right click (if not within 5 px of the press point, the mode // had been already changed to 'Selection' instead of 'Normal') emit rightClick( pageItem->page(), e->globalPos() ); } } } else { // right click (if not within 5 px of the press point, the mode // had been already changed to 'Selection' instead of 'Normal') emit rightClick( pageItem ? pageItem->page() : nullptr, e->globalPos() ); } } }break; case Okular::Settings::EnumMouseMode::Zoom: // if a selection rect has been defined, zoom into it if ( leftButton && d->mouseSelecting ) { QRect selRect = d->mouseSelectionRect.normalized(); if ( selRect.width() <= 8 && selRect.height() <= 8 ) { selectionClear(); break; } // find out new zoom ratio and normalized view center (relative to the contentsRect) double zoom = qMin( (double)viewport()->width() / (double)selRect.width(), (double)viewport()->height() / (double)selRect.height() ); double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)contentAreaWidth()); double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)contentAreaHeight()); const float upperZoomLimit = d->document->supportsTiles() ? 16.0 : 4.0; if ( d->zoomFactor <= upperZoomLimit || zoom <= 1.0 ) { d->zoomFactor *= zoom; viewport()->setUpdatesEnabled( false ); updateZoom( ZoomRefreshCurrent ); viewport()->setUpdatesEnabled( true ); } // recenter view and update the viewport center( (int)(nX * contentAreaWidth()), (int)(nY * contentAreaHeight()) ); viewport()->update(); // hide message box and delete overlay window selectionClear(); } break; case Okular::Settings::EnumMouseMode::Magnifier: d->magnifierView->hide(); break; case Okular::Settings::EnumMouseMode::TrimSelect: { // if it is a left release checks if is over a previous link press if ( leftButton && mouseReleaseOverLink ( d->mouseOverLinkObject ) ) { selectionClear(); break; } // if mouse is released and selection is null this is a rightClick if ( rightButton && !d->mouseSelecting ) { break; } PageViewItem * pageItem = pickItemOnPoint(eventPos.x(), eventPos.y()); // ensure end point rests within a page, or ignore if (!pageItem) { break; } QRect selectionRect = d->mouseSelectionRect.normalized(); double nLeft = pageItem->absToPageX(selectionRect.left()); double nRight = pageItem->absToPageX(selectionRect.right()); double nTop = pageItem->absToPageY(selectionRect.top()); double nBottom = pageItem->absToPageY(selectionRect.bottom()); if ( nLeft < 0 ) nLeft = 0; if ( nTop < 0 ) nTop = 0; if ( nRight > 1 ) nRight = 1; if ( nBottom > 1 ) nBottom = 1; d->trimBoundingBox = Okular::NormalizedRect(nLeft, nTop, nRight, nBottom); // Trim Selection successfully done, hide prompt d->messageWindow->hide(); // clear widget selection and invalidate rect selectionClear(); // When Trim selection bbox interaction is over, we should switch to another mousemode. if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } else { d->aMouseNormal->trigger(); } // with d->trimBoundingBox defined, redraw for trim to take visual effect if ( d->document->pages() > 0 ) { slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! } break; } case Okular::Settings::EnumMouseMode::RectSelect: { // if it is a left release checks if is over a previous link press if ( leftButton && mouseReleaseOverLink ( d->mouseOverLinkObject ) ) { selectionClear(); break; } // if mouse is released and selection is null this is a rightClick if ( rightButton && !d->mouseSelecting ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); emit rightClick( pageItem ? pageItem->page() : nullptr, e->globalPos() ); break; } // if a selection is defined, display a popup if ( (!leftButton && !d->aPrevAction) || (!rightButton && d->aPrevAction) || !d->mouseSelecting ) break; QRect selectionRect = d->mouseSelectionRect.normalized(); if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 ) { selectionClear(); if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } break; } // if we support text generation QString selectedText; if (d->document->supportsSearching()) { // grab text in selection by extracting it from all intersected pages const Okular::Page * okularPage=nullptr; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; if ( !item->isVisible() ) continue; const QRect & itemRect = item->croppedGeometry(); if ( selectionRect.intersects( itemRect ) ) { // request the textpage if there isn't one okularPage= item->page(); qCDebug(OkularUiDebug) << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage(); if ( !okularPage->hasTextPage() ) d->document->requestTextPage( okularPage->number() ); // grab text in the rect that intersects itemRect QRect relativeRect = selectionRect.intersected( itemRect ); relativeRect.translate( -item->uncroppedGeometry().topLeft() ); Okular::RegularAreaRect rects; rects.append( Okular::NormalizedRect( relativeRect, item->uncroppedWidth(), item->uncroppedHeight() ) ); selectedText += okularPage->text( &rects ); } } } // popup that ask to copy:text and copy/save:image QMenu menu( this ); menu.setObjectName("PopupMenu"); QAction *textToClipboard = nullptr; #ifdef HAVE_SPEECH QAction *speakText = nullptr; #endif QAction *imageToClipboard = nullptr; QAction *imageToFile = nullptr; if ( d->document->supportsSearching() && !selectedText.isEmpty() ) { menu.addAction( new OKMenuTitle( &menu, i18np( "Text (1 character)", "Text (%1 characters)", selectedText.length() ) ) ); textToClipboard = menu.addAction( QIcon::fromTheme(QStringLiteral("edit-copy")), i18n( "Copy to Clipboard" ) ); textToClipboard->setObjectName("CopyTextToClipboard"); bool copyAllowed = d->document->isAllowed( Okular::AllowCopy ); if ( !copyAllowed ) { textToClipboard->setEnabled( false ); textToClipboard->setText( i18n("Copy forbidden by DRM") ); } #ifdef HAVE_SPEECH if ( Okular::Settings::useTTS() ) speakText = menu.addAction( QIcon::fromTheme(QStringLiteral("text-speak")), i18n( "Speak Text" ) ); #endif if ( copyAllowed ) { addWebShortcutsMenu( &menu, selectedText ); } } menu.addAction( new OKMenuTitle( &menu, i18n( "Image (%1 by %2 pixels)", selectionRect.width(), selectionRect.height() ) ) ); imageToClipboard = menu.addAction( QIcon::fromTheme(QStringLiteral("image-x-generic")), i18n( "Copy to Clipboard" ) ); imageToFile = menu.addAction( QIcon::fromTheme(QStringLiteral("document-save")), i18n( "Save to File..." ) ); QAction *choice = menu.exec( e->globalPos() ); // check if the user really selected an action if ( choice ) { // IMAGE operation chosen if ( choice == imageToClipboard || choice == imageToFile ) { // renders page into a pixmap QPixmap copyPix( selectionRect.width(), selectionRect.height() ); QPainter copyPainter( ©Pix ); copyPainter.translate( -selectionRect.left(), -selectionRect.top() ); drawDocumentOnPainter( selectionRect, ©Painter ); copyPainter.end(); if ( choice == imageToClipboard ) { // [2] copy pixmap to clipboard QClipboard *cb = QApplication::clipboard(); cb->setPixmap( copyPix, QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setPixmap( copyPix, QClipboard::Selection ); d->messageWindow->display( i18n( "Image [%1x%2] copied to clipboard.", copyPix.width(), copyPix.height() ) ); } else if ( choice == imageToFile ) { // [3] save pixmap to file QString fileName = QFileDialog::getSaveFileName(this, i18n("Save file"), QString(), i18n("Images (*.png *.jpeg)")); if ( fileName.isEmpty() ) d->messageWindow->display( i18n( "File not saved." ), QString(), PageViewMessage::Warning ); else { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl( QUrl::fromLocalFile(fileName) ); QString type; if ( !mime.isDefault() ) type = QStringLiteral("PNG"); else type = mime.name().section( QLatin1Char('/'), -1 ).toUpper(); copyPix.save( fileName, qPrintable( type ) ); d->messageWindow->display( i18n( "Image [%1x%2] saved to %3 file.", copyPix.width(), copyPix.height(), type ) ); } } } // TEXT operation chosen else { if ( choice == textToClipboard ) { // [1] copy text to clipboard QClipboard *cb = QApplication::clipboard(); cb->setText( selectedText, QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setText( selectedText, QClipboard::Selection ); } #ifdef HAVE_SPEECH else if ( choice == speakText ) { // [2] speech selection using TTS d->tts()->say( selectedText ); } #endif } } // clear widget selection and invalidate rect selectionClear(); // restore previous action if came from it using right button if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } }break; case Okular::Settings::EnumMouseMode::TableSelect: { // if it is a left release checks if is over a previous link press if ( leftButton && mouseReleaseOverLink ( d->mouseOverLinkObject ) ) { selectionClear(); break; } // if mouse is released and selection is null this is a rightClick if ( rightButton && !d->mouseSelecting ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); emit rightClick( pageItem ? pageItem->page() : nullptr, e->globalPos() ); break; } QRect selectionRect = d->mouseSelectionRect.normalized(); if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 && d->tableSelectionParts.isEmpty() ) { selectionClear(); if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } break; } if (d->mouseSelecting) { // break up the selection into page-relative pieces d->tableSelectionParts.clear(); const Okular::Page * okularPage=nullptr; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; if ( !item->isVisible() ) continue; const QRect & itemRect = item->croppedGeometry(); if ( selectionRect.intersects( itemRect ) ) { // request the textpage if there isn't one okularPage= item->page(); qCDebug(OkularUiDebug) << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage(); if ( !okularPage->hasTextPage() ) d->document->requestTextPage( okularPage->number() ); // grab text in the rect that intersects itemRect QRect rectInItem = selectionRect.intersected( itemRect ); rectInItem.translate( -item->uncroppedGeometry().topLeft() ); QRect rectInSelection = selectionRect.intersected( itemRect ); rectInSelection.translate( -selectionRect.topLeft() ); d->tableSelectionParts.append( TableSelectionPart( item, Okular::NormalizedRect( rectInItem, item->uncroppedWidth(), item->uncroppedHeight() ), Okular::NormalizedRect( rectInSelection, selectionRect.width(), selectionRect.height() ) ) ); } } QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( 0, 0, 1, 1 ); updatedRect.translate( -contentAreaPosition() ); d->mouseSelecting = false; d->mouseSelectionRect.setCoords( 0, 0, 0, 0 ); d->tableSelectionCols.clear(); d->tableSelectionRows.clear(); guessTableDividers(); viewport()->update( updatedRect ); } if ( !d->document->isAllowed( Okular::AllowCopy ) ) { d->messageWindow->display( i18n("Copy forbidden by DRM"), QString(), PageViewMessage::Info, -1 ); break; } QString selText; QString selHtml; QList xs = d->tableSelectionCols; QList ys = d->tableSelectionRows; xs.prepend(0.0); xs.append(1.0); ys.prepend(0.0); ys.append(1.0); selHtml = QString::fromLatin1("" "" ""); for (int r=0; r+1"); for (int c=0; c+1tableSelectionParts) { // first, crop the cell to this part if (!tsp.rectInSelection.intersects(cell)) continue; Okular::NormalizedRect cellPart = tsp.rectInSelection & cell; // intersection // second, convert it from table coordinates to part coordinates cellPart.left -= tsp.rectInSelection.left; cellPart.left /= (tsp.rectInSelection.right - tsp.rectInSelection.left); cellPart.right -= tsp.rectInSelection.left; cellPart.right /= (tsp.rectInSelection.right - tsp.rectInSelection.left); cellPart.top -= tsp.rectInSelection.top; cellPart.top /= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); cellPart.bottom -= tsp.rectInSelection.top; cellPart.bottom /= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); // third, convert from part coordinates to item coordinates cellPart.left *= (tsp.rectInItem.right - tsp.rectInItem.left); cellPart.left += tsp.rectInItem.left; cellPart.right *= (tsp.rectInItem.right - tsp.rectInItem.left); cellPart.right += tsp.rectInItem.left; cellPart.top *= (tsp.rectInItem.bottom - tsp.rectInItem.top); cellPart.top += tsp.rectInItem.top; cellPart.bottom *= (tsp.rectInItem.bottom - tsp.rectInItem.top); cellPart.bottom += tsp.rectInItem.top; // now get the text Okular::RegularAreaRect rects; rects.append( cellPart ); txt += tsp.item->page()->text( &rects, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ); } QString html = txt; selText += txt.replace(QLatin1Char('\n'), QLatin1Char(' ')); html.replace(QLatin1Char('&'), QLatin1String("&")).replace(QLatin1Char('<'), QLatin1String("<")).replace(QLatin1Char('>'), QLatin1String(">")); // Remove newlines, do not turn them into
, because // Excel interprets
within cell as new cell... html.replace(QLatin1Char('\n'), QLatin1String(" ")); selHtml += QStringLiteral("
"); } selText += QLatin1Char('\n'); selHtml += QLatin1String("\n"); } selHtml += QLatin1String("
") + html + QStringLiteral("
\n"); QClipboard *cb = QApplication::clipboard(); QMimeData *md = new QMimeData(); md->setText(selText); md->setHtml(selHtml); cb->setMimeData( md, QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setMimeData( md, QClipboard::Selection ); }break; case Okular::Settings::EnumMouseMode::TextSelect: // if it is a left release checks if is over a previous link press if ( leftButton && mouseReleaseOverLink ( d->mouseOverLinkObject ) ) { selectionClear(); break; } if ( d->mouseTextSelecting ) { d->mouseTextSelecting = false; // textSelectionClear(); if ( d->document->isAllowed( Okular::AllowCopy ) ) { const QString text = d->selectedText(); if ( !text.isEmpty() ) { QClipboard *cb = QApplication::clipboard(); if ( cb->supportsSelection() ) cb->setText( text, QClipboard::Selection ); } } } else if ( !d->mousePressPos.isNull() && rightButton ) { PageViewItem* item = pickItemOnPoint(eventPos.x(),eventPos.y()); const Okular::Page *page; //if there is text selected in the page if (item) { QAction * httpLink = nullptr; QAction * textToClipboard = nullptr; QString url; QMenu * menu = createProcessLinkMenu( item, eventPos ); const bool mouseClickOverLink = (menu != nullptr); #ifdef HAVE_SPEECH QAction *speakText = nullptr; #endif if ( (page = item->page())->textSelection() ) { if ( !menu ) { menu = new QMenu(this); } textToClipboard = menu->addAction( QIcon::fromTheme( QStringLiteral("edit-copy") ), i18n( "Copy Text" ) ); #ifdef HAVE_SPEECH if ( Okular::Settings::useTTS() ) speakText = menu->addAction( QIcon::fromTheme( QStringLiteral("text-speak") ), i18n( "Speak Text" ) ); #endif if ( !d->document->isAllowed( Okular::AllowCopy ) ) { textToClipboard->setEnabled( false ); textToClipboard->setText( i18n("Copy forbidden by DRM") ); } else { addWebShortcutsMenu( menu, d->selectedText() ); } // if the right-click was over a link add "Follow This link" instead of "Go to" if (!mouseClickOverLink) { url = UrlUtils::getUrl( d->selectedText() ); if ( !url.isEmpty() ) { const QString squeezedText = KStringHandler::rsqueeze( url, 30 ); httpLink = menu->addAction( i18n( "Go to '%1'", squeezedText ) ); httpLink->setObjectName("GoToAction"); } } } if ( menu ) { menu->setObjectName("PopupMenu"); QAction *choice = menu->exec( e->globalPos() ); // check if the user really selected an action if ( choice ) { if ( choice == textToClipboard ) copyTextSelection(); #ifdef HAVE_SPEECH else if ( choice == speakText ) { const QString text = d->selectedText(); d->tts()->say( text ); } #endif else if ( choice == httpLink ) { new KRun( QUrl( url ), this ); } } menu->deleteLater(); } } } break; } // reset mouse press / 'drag start' position d->mousePressPos = QPoint(); } void PageView::guessTableDividers() { QList< QPair > colTicks, rowTicks, colSelectionTicks, rowSelectionTicks; foreach ( const TableSelectionPart& tsp, d->tableSelectionParts ) { // add ticks for the edges of this area... colSelectionTicks.append( qMakePair( tsp.rectInSelection.left, +1 ) ); colSelectionTicks.append( qMakePair( tsp.rectInSelection.right, -1 ) ); rowSelectionTicks.append( qMakePair( tsp.rectInSelection.top, +1 ) ); rowSelectionTicks.append( qMakePair( tsp.rectInSelection.bottom, -1 ) ); // get the words in this part Okular::RegularAreaRect rects; rects.append( tsp.rectInItem ); const Okular::TextEntity::List words = tsp.item->page()->words( &rects, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ); foreach (Okular::TextEntity *te, words) { if (te->text().isEmpty()) { delete te; continue; } Okular::NormalizedRect wordArea = *te->area(); // convert it from item coordinates to part coordinates wordArea.left -= tsp.rectInItem.left; wordArea.left /= (tsp.rectInItem.right - tsp.rectInItem.left); wordArea.right -= tsp.rectInItem.left; wordArea.right /= (tsp.rectInItem.right - tsp.rectInItem.left); wordArea.top -= tsp.rectInItem.top; wordArea.top /= (tsp.rectInItem.bottom - tsp.rectInItem.top); wordArea.bottom -= tsp.rectInItem.top; wordArea.bottom /= (tsp.rectInItem.bottom - tsp.rectInItem.top); // convert from part coordinates to table coordinates wordArea.left *= (tsp.rectInSelection.right - tsp.rectInSelection.left); wordArea.left += tsp.rectInSelection.left; wordArea.right *= (tsp.rectInSelection.right - tsp.rectInSelection.left); wordArea.right += tsp.rectInSelection.left; wordArea.top *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); wordArea.top += tsp.rectInSelection.top; wordArea.bottom *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); wordArea.bottom += tsp.rectInSelection.top; // add to the ticks arrays... colTicks.append( qMakePair( wordArea.left, +1) ); colTicks.append( qMakePair( wordArea.right, -1) ); rowTicks.append( qMakePair( wordArea.top, +1) ); rowTicks.append( qMakePair( wordArea.bottom, -1) ); delete te; } } int tally = 0; qSort( colSelectionTicks ); qSort( rowSelectionTicks ); for (int i = 0; i < colSelectionTicks.length(); ++i) { tally += colSelectionTicks[i].second; if ( tally == 0 && i + 1 < colSelectionTicks.length() && colSelectionTicks[i+1].first != colSelectionTicks[i].first) { colTicks.append( qMakePair( colSelectionTicks[i].first, +1 ) ); colTicks.append( qMakePair( colSelectionTicks[i+1].first, -1 ) ); } } Q_ASSERT( tally == 0 ); for (int i = 0; i < rowSelectionTicks.length(); ++i) { tally += rowSelectionTicks[i].second; if ( tally == 0 && i + 1 < rowSelectionTicks.length() && rowSelectionTicks[i+1].first != rowSelectionTicks[i].first) { rowTicks.append( qMakePair( rowSelectionTicks[i].first, +1 ) ); rowTicks.append( qMakePair( rowSelectionTicks[i+1].first, -1 ) ); } } Q_ASSERT( tally == 0 ); qSort( colTicks ); qSort( rowTicks ); for (int i = 0; i < colTicks.length(); ++i) { tally += colTicks[i].second; if ( tally == 0 && i + 1 < colTicks.length() && colTicks[i+1].first != colTicks[i].first) { d->tableSelectionCols.append( (colTicks[i].first+colTicks[i+1].first) / 2 ); d->tableDividersGuessed = true; } } Q_ASSERT( tally == 0 ); for (int i = 0; i < rowTicks.length(); ++i) { tally += rowTicks[i].second; if ( tally == 0 && i + 1 < rowTicks.length() && rowTicks[i+1].first != rowTicks[i].first) { d->tableSelectionRows.append( (rowTicks[i].first+rowTicks[i+1].first) / 2 ); d->tableDividersGuessed = true; } } Q_ASSERT( tally == 0 ); } void PageView::mouseDoubleClickEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; if ( e->button() == Qt::LeftButton ) { const QPoint eventPos = contentAreaPoint( e->pos() ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); if ( pageItem ) { // find out normalized mouse coords inside current item double nX = pageItem->absToPageX(eventPos.x()); double nY = pageItem->absToPageY(eventPos.y()); if ( d->mouseMode == Okular::Settings::EnumMouseMode::TextSelect ) { textSelectionClear(); Okular::RegularAreaRect *wordRect = pageItem->page()->wordAt( Okular::NormalizedPoint( nX, nY ) ); if ( wordRect ) { // TODO words with hyphens across pages d->document->setPageTextSelection( pageItem->pageNumber(), wordRect, palette().color( QPalette::Active, QPalette::Highlight ) ); d->pagesWithTextSelection << pageItem->pageNumber(); if ( d->document->isAllowed( Okular::AllowCopy ) ) { const QString text = d->selectedText(); if ( !text.isEmpty() ) { QClipboard *cb = QApplication::clipboard(); if ( cb->supportsSelection() ) cb->setText( text, QClipboard::Selection ); } } return; } } const QRect & itemRect = pageItem->uncroppedGeometry(); Okular::Annotation * ann = nullptr; const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() ); if ( orect ) ann = ( (Okular::AnnotationObjectRect *)orect )->annotation(); if ( ann && ann->subType() != Okular::Annotation::AWidget ) { openAnnotationWindow( ann, pageItem->pageNumber() ); } } } } void PageView::wheelEvent( QWheelEvent *e ) { // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; if ( !d->document->isOpened() ) { QAbstractScrollArea::wheelEvent( e ); return; } int delta = e->delta(), vScroll = verticalScrollBar()->value(); e->accept(); if ( (e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) { d->controlWheelAccumulatedDelta += delta; if ( d->controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep ) { slotZoomOut(); d->controlWheelAccumulatedDelta = 0; } else if ( d->controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep ) { slotZoomIn(); d->controlWheelAccumulatedDelta = 0; } } else { d->controlWheelAccumulatedDelta = 0; if ( delta <= -QWheelEvent::DefaultDeltasPerStep && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->maximum() ) { // go to next page if ( (int)d->document->currentPage() < d->items.count() - 1 ) { // more optimized than document->setNextPage and then move view to top Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber += viewColumns(); if ( newViewport.pageNumber >= (int)d->items.count() ) newViewport.pageNumber = d->items.count() - 1; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 0.0; d->document->setViewport( newViewport ); } } else if ( delta >= QWheelEvent::DefaultDeltasPerStep && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->minimum() ) { // go to prev page if ( d->document->currentPage() > 0 ) { // more optimized than document->setPrevPage and then move view to bottom Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber -= viewColumns(); if ( newViewport.pageNumber < 0 ) newViewport.pageNumber = 0; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 1.0; d->document->setViewport( newViewport ); } } else QAbstractScrollArea::wheelEvent( e ); } updateCursor(); } bool PageView::viewportEvent( QEvent * e ) { if ( e->type() == QEvent::ToolTip && d->mouseMode == Okular::Settings::EnumMouseMode::Browse ) { QHelpEvent * he = static_cast< QHelpEvent* >( e ); if ( d->mouseAnnotation->isMouseOver() ) { d->mouseAnnotation->routeTooltipEvent( he ); } else { const QPoint eventPos = contentAreaPoint( he->pos() ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); const Okular::ObjectRect * rect = nullptr; const Okular::Action * link = nullptr; if ( pageItem ) { double nX = pageItem->absToPageX( eventPos.x() ); double nY = pageItem->absToPageY( eventPos.y() ); rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( rect ) link = static_cast< const Okular::Action * >( rect->object() ); } if ( link ) { QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); r.translate( pageItem->uncroppedGeometry().topLeft() ); r.translate( -contentAreaPosition() ); QString tip = link->actionTip(); if ( !tip.isEmpty() ) QToolTip::showText( he->globalPos(), tip, viewport(), r ); } } e->accept(); return true; } else // do not stop the event return QAbstractScrollArea::viewportEvent( e ); } void PageView::scrollContentsBy( int dx, int dy ) { const QRect r = viewport()->rect(); viewport()->scroll( dx, dy, r ); // HACK manually repaint the damaged regions, as it seems some updates are missed // thus leaving artifacts around QRegion rgn( r ); rgn -= rgn & r.translated( dx, dy ); foreach ( const QRect &rect, rgn.rects() ) viewport()->repaint( rect ); } //END widget events QList< Okular::RegularAreaRect * > PageView::textSelections( const QPoint& start, const QPoint& end, int& firstpage ) { firstpage = -1; QList< Okular::RegularAreaRect * > ret; QSet< int > affectedItemsSet; QRect selectionRect = QRect( start, end ).normalized(); foreach( PageViewItem * item, d->items ) { if ( item->isVisible() && selectionRect.intersects( item->croppedGeometry() ) ) affectedItemsSet.insert( item->pageNumber() ); } #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << ">>>> item selected by mouse:" << affectedItemsSet.count(); #endif if ( !affectedItemsSet.isEmpty() ) { // is the mouse drag line the ne-sw diagonal of the selection rect? bool direction_ne_sw = start == selectionRect.topRight() || start == selectionRect.bottomLeft(); int tmpmin = d->document->pages(); int tmpmax = 0; foreach( int p, affectedItemsSet ) { if ( p < tmpmin ) tmpmin = p; if ( p > tmpmax ) tmpmax = p; } PageViewItem * a = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.right() : selectionRect.left() ), (int)selectionRect.top() ); int min = a && ( a->pageNumber() != tmpmax ) ? a->pageNumber() : tmpmin; PageViewItem * b = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.left() : selectionRect.right() ), (int)selectionRect.bottom() ); int max = b && ( b->pageNumber() != tmpmin ) ? b->pageNumber() : tmpmax; QList< int > affectedItemsIds; for ( int i = min; i <= max; ++i ) affectedItemsIds.append( i ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << ">>>> pages:" << affectedItemsIds; #endif firstpage = affectedItemsIds.first(); if ( affectedItemsIds.count() == 1 ) { PageViewItem * item = d->items[ affectedItemsIds.first() ]; selectionRect.translate( -item->uncroppedGeometry().topLeft() ); ret.append( textSelectionForItem( item, direction_ne_sw ? selectionRect.topRight() : selectionRect.topLeft(), direction_ne_sw ? selectionRect.bottomLeft() : selectionRect.bottomRight() ) ); } else if ( affectedItemsIds.count() > 1 ) { // first item PageViewItem * first = d->items[ affectedItemsIds.first() ]; QRect geom = first->croppedGeometry().intersected( selectionRect ).translated( -first->uncroppedGeometry().topLeft() ); ret.append( textSelectionForItem( first, selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.topRight() : geom.topLeft() ) : ( direction_ne_sw ? geom.bottomRight() : geom.bottomLeft() ), QPoint() ) ); // last item PageViewItem * last = d->items[ affectedItemsIds.last() ]; geom = last->croppedGeometry().intersected( selectionRect ).translated( -last->uncroppedGeometry().topLeft() ); // the last item needs to appended at last... Okular::RegularAreaRect * lastArea = textSelectionForItem( last, QPoint(), selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.bottomLeft() : geom.bottomRight() ) : ( direction_ne_sw ? geom.topLeft() : geom.topRight() ) ); affectedItemsIds.removeFirst(); affectedItemsIds.removeLast(); // item between the two above foreach( int page, affectedItemsIds ) { ret.append( textSelectionForItem( d->items[ page ] ) ); } ret.append( lastArea ); } } return ret; } void PageView::drawDocumentOnPainter( const QRect & contentsRect, QPainter * p ) { QColor backColor; if ( Okular::Settings::useCustomBackgroundColor() ) backColor = Okular::Settings::backgroundColor(); else backColor = viewport()->palette().color( QPalette::Dark ); // when checking if an Item is contained in contentsRect, instead of // growing PageViewItems rects (for keeping outline into account), we // grow the contentsRect QRect checkRect = contentsRect; checkRect.adjust( -3, -3, 1, 1 ); // create a region from which we'll subtract painted rects QRegion remainingArea( contentsRect ); // iterate over all items painting the ones intersecting contentsRect QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { // check if a piece of the page intersects the contents rect if ( !(*iIt)->isVisible() || !(*iIt)->croppedGeometry().intersects( checkRect ) ) continue; // get item and item's outline geometries PageViewItem * item = *iIt; QRect itemGeometry = item->croppedGeometry(), outlineGeometry = itemGeometry; outlineGeometry.adjust( -1, -1, 3, 3 ); // move the painter to the top-left corner of the real page p->save(); p->translate( itemGeometry.left(), itemGeometry.top() ); // draw the page outline (black border and 2px bottom-right shadow) if ( !itemGeometry.contains( contentsRect ) ) { int itemWidth = itemGeometry.width(), itemHeight = itemGeometry.height(); // draw simple outline p->setPen( Qt::black ); p->drawRect( -1, -1, itemWidth + 1, itemHeight + 1 ); // draw bottom/right gradient static const int levels = 2; int r = backColor.red() / (levels + 2) + 6, g = backColor.green() / (levels + 2) + 6, b = backColor.blue() / (levels + 2) + 6; for ( int i = 0; i < levels; i++ ) { p->setPen( QColor( r * (i+2), g * (i+2), b * (i+2) ) ); p->drawLine( i, i + itemHeight + 1, i + itemWidth + 1, i + itemHeight + 1 ); p->drawLine( i + itemWidth + 1, i, i + itemWidth + 1, i + itemHeight ); p->setPen( backColor ); p->drawLine( -1, i + itemHeight + 1, i - 1, i + itemHeight + 1 ); p->drawLine( i + itemWidth + 1, -1, i + itemWidth + 1, i - 1 ); } } // draw the page using the PagePainter with all flags active if ( contentsRect.intersects( itemGeometry ) ) { Okular::NormalizedPoint *viewPortPoint = nullptr; Okular::NormalizedPoint point( d->lastSourceLocationViewportNormalizedX, d->lastSourceLocationViewportNormalizedY ); if( Okular::Settings::showSourceLocationsGraphically() && item->pageNumber() == d->lastSourceLocationViewportPageNumber ) { viewPortPoint = &point; } QRect pixmapRect = contentsRect.intersected( itemGeometry ); pixmapRect.translate( -item->croppedGeometry().topLeft() ); PagePainter::paintCroppedPageOnPainter( p, item->page(), this, pageflags, item->uncroppedWidth(), item->uncroppedHeight(), pixmapRect, item->crop(), viewPortPoint ); } // remove painted area from 'remainingArea' and restore painter remainingArea -= outlineGeometry.intersected( contentsRect ); p->restore(); } // fill with background color the unpainted area const QVector &backRects = remainingArea.rects(); int backRectsNumber = backRects.count(); for ( int jr = 0; jr < backRectsNumber; jr++ ) p->fillRect( backRects[ jr ], backColor ); } void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight ) { const Okular::Page * okularPage = item->page(); double width = okularPage->width(), height = okularPage->height(), zoom = d->zoomFactor; Okular::NormalizedRect crop( 0., 0., 1., 1. ); // Handle cropping, due to either "Trim Margin" or "Trim to Selection" cases if (( Okular::Settings::trimMargins() && okularPage->isBoundingBoxKnown() && !okularPage->boundingBox().isNull() ) || ( d->aTrimToSelection && d->aTrimToSelection->isChecked() && !d->trimBoundingBox.isNull())) { crop = Okular::Settings::trimMargins() ? okularPage->boundingBox() : d->trimBoundingBox; // Rotate the bounding box for ( int i = okularPage->rotation(); i > 0; --i ) { Okular::NormalizedRect rot = crop; crop.left = 1 - rot.bottom; crop.top = rot.left; crop.right = 1 - rot.top; crop.bottom = rot.right; } // Expand the crop slightly beyond the bounding box (for Trim Margins only) if (Okular::Settings::trimMargins()) { static const double cropExpandRatio = 0.04; const double cropExpand = cropExpandRatio * ( (crop.right-crop.left) + (crop.bottom-crop.top) ) / 2; crop = Okular::NormalizedRect( crop.left - cropExpand, crop.top - cropExpand, crop.right + cropExpand, crop.bottom + cropExpand ) & Okular::NormalizedRect( 0, 0, 1, 1 ); } // We currently generate a larger image and then crop it, so if the // crop rect is very small the generated image is huge. Hence, we shouldn't // let the crop rect become too small. static double minCropRatio; if (Okular::Settings::trimMargins()) { // Make sure we crop by at most 50% in either dimension: minCropRatio = 0.5; } else { // Looser Constraint for "Trim Selection" minCropRatio = 0.20; } if ( ( crop.right - crop.left ) < minCropRatio ) { const double newLeft = ( crop.left + crop.right ) / 2 - minCropRatio/2; crop.left = qMax( 0.0, qMin( 1.0 - minCropRatio, newLeft ) ); crop.right = crop.left + minCropRatio; } if ( ( crop.bottom - crop.top ) < minCropRatio ) { const double newTop = ( crop.top + crop.bottom ) / 2 - minCropRatio/2; crop.top = qMax( 0.0, qMin( 1.0 - minCropRatio, newTop ) ); crop.bottom = crop.top + minCropRatio; } width *= ( crop.right - crop.left ); height *= ( crop.bottom - crop.top ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "Cropped page" << okularPage->number() << "to" << crop << "width" << width << "height" << height << "by bbox" << okularPage->boundingBox(); #endif } if ( d->zoomMode == ZoomFixed ) { width *= zoom; height *= zoom; item->setWHZC( (int)width, (int)height, d->zoomFactor, crop ); } else if ( d->zoomMode == ZoomFitWidth ) { height = ( height / width ) * colWidth; zoom = (double)colWidth / width; item->setWHZC( colWidth, (int)height, zoom, crop ); if ((uint)item->pageNumber() == d->document->currentPage()) d->zoomFactor = zoom; } else if ( d->zoomMode == ZoomFitPage ) { const double scaleW = (double)colWidth / (double)width; const double scaleH = (double)rowHeight / (double)height; zoom = qMin( scaleW, scaleH ); item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop ); if ((uint)item->pageNumber() == d->document->currentPage()) d->zoomFactor = zoom; } else if ( d->zoomMode == ZoomFitAuto ) { const double aspectRatioRelation = 1.25; // relation between aspect ratios for "auto fit" const double uiAspect = (double)rowHeight / (double)colWidth; const double pageAspect = (double)height / (double)width; const double rel = uiAspect / pageAspect; const bool isContinuous = Okular::Settings::viewContinuous(); if ( !isContinuous && rel > aspectRatioRelation ) { // UI space is relatively much higher than the page zoom = (double)rowHeight / (double)height; } else if ( rel < 1.0 / aspectRatioRelation ) { // UI space is relatively much wider than the page in relation zoom = (double)colWidth / (double)width; } else { // aspect ratios of page and UI space are very similar const double scaleW = (double)colWidth / (double)width; const double scaleH = (double)rowHeight / (double)height; zoom = qMin( scaleW, scaleH ); } item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop ); if ((uint)item->pageNumber() == d->document->currentPage()) d->zoomFactor = zoom; } #ifndef NDEBUG else qCDebug(OkularUiDebug) << "calling updateItemSize with unrecognized d->zoomMode!"; #endif } PageViewItem * PageView::pickItemOnPoint( int x, int y ) { PageViewItem * item = nullptr; QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * i = *iIt; const QRect & r = i->croppedGeometry(); if ( x < r.right() && x > r.left() && y < r.bottom() ) { if ( y > r.top() ) item = i; break; } } return item; } void PageView::textSelectionClear() { // something to clear if ( !d->pagesWithTextSelection.isEmpty() ) { QSet< int >::ConstIterator it = d->pagesWithTextSelection.constBegin(), itEnd = d->pagesWithTextSelection.constEnd(); for ( ; it != itEnd; ++it ) d->document->setPageTextSelection( *it, nullptr, QColor() ); d->pagesWithTextSelection.clear(); } } void PageView::selectionStart( const QPoint & pos, const QColor & color, bool /*aboveAll*/ ) { selectionClear(); d->mouseSelecting = true; d->mouseSelectionRect.setRect( pos.x(), pos.y(), 1, 1 ); d->mouseSelectionColor = color; // ensures page doesn't scroll if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::scrollPosIntoView( const QPoint & pos ) { if (pos.x() < horizontalScrollBar()->value()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value()); else if (horizontalScrollBar()->value() + viewport()->width() < pos.x()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value() - viewport()->width()); else d->dragScrollVector.setX(0); if (pos.y() < verticalScrollBar()->value()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value()); else if (verticalScrollBar()->value() + viewport()->height() < pos.y()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value() - viewport()->height()); else d->dragScrollVector.setY(0); if (d->dragScrollVector != QPoint(0, 0)) { if (!d->dragScrollTimer.isActive()) d->dragScrollTimer.start(100); } else d->dragScrollTimer.stop(); } void PageView::updateSelection( const QPoint & pos ) { if ( d->mouseSelecting ) { scrollPosIntoView( pos ); // update the selection rect QRect updateRect = d->mouseSelectionRect; d->mouseSelectionRect.setBottomLeft( pos ); updateRect |= d->mouseSelectionRect; updateRect.translate( -contentAreaPosition() ); viewport()->update( updateRect.adjusted( -1, -2, 2, 1 ) ); } else if ( d->mouseTextSelecting) { scrollPosIntoView( pos ); int first = -1; const QList< Okular::RegularAreaRect * > selections = textSelections( pos, d->mouseSelectPos, first ); QSet< int > pagesWithSelectionSet; for ( int i = 0; i < selections.count(); ++i ) pagesWithSelectionSet.insert( i + first ); const QSet< int > noMoreSelectedPages = d->pagesWithTextSelection - pagesWithSelectionSet; // clear the selection from pages not selected anymore foreach( int p, noMoreSelectedPages ) { d->document->setPageTextSelection( p, nullptr, QColor() ); } // set the new selection for the selected pages foreach( int p, pagesWithSelectionSet ) { d->document->setPageTextSelection( p, selections[ p - first ], palette().color( QPalette::Active, QPalette::Highlight ) ); } d->pagesWithTextSelection = pagesWithSelectionSet; } } static Okular::NormalizedPoint rotateInNormRect( const QPoint &rotated, const QRect &rect, Okular::Rotation rotation ) { Okular::NormalizedPoint ret; switch ( rotation ) { case Okular::Rotation0: ret = Okular::NormalizedPoint( rotated.x(), rotated.y(), rect.width(), rect.height() ); break; case Okular::Rotation90: ret = Okular::NormalizedPoint( rotated.y(), rect.width() - rotated.x(), rect.height(), rect.width() ); break; case Okular::Rotation180: ret = Okular::NormalizedPoint( rect.width() - rotated.x(), rect.height() - rotated.y(), rect.width(), rect.height() ); break; case Okular::Rotation270: ret = Okular::NormalizedPoint( rect.height() - rotated.y(), rotated.x(), rect.height(), rect.width() ); break; } return ret; } Okular::RegularAreaRect * PageView::textSelectionForItem( PageViewItem * item, const QPoint & startPoint, const QPoint & endPoint ) { const QRect & geometry = item->uncroppedGeometry(); Okular::NormalizedPoint startCursor( 0.0, 0.0 ); if ( !startPoint.isNull() ) { startCursor = rotateInNormRect( startPoint, geometry, item->page()->rotation() ); } Okular::NormalizedPoint endCursor( 1.0, 1.0 ); if ( !endPoint.isNull() ) { endCursor = rotateInNormRect( endPoint, geometry, item->page()->rotation() ); } Okular::TextSelection mouseTextSelectionInfo( startCursor, endCursor ); const Okular::Page * okularPage = item->page(); if ( !okularPage->hasTextPage() ) d->document->requestTextPage( okularPage->number() ); Okular::RegularAreaRect * selectionArea = okularPage->textArea( &mouseTextSelectionInfo ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug).nospace() << "text areas (" << okularPage->number() << "): " << ( selectionArea ? QString::number( selectionArea->count() ) : "(none)" ); #endif return selectionArea; } void PageView::selectionClear(const ClearMode mode) { QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( -2, -2, 2, 2 ); d->mouseSelecting = false; d->mouseSelectionRect.setCoords( 0, 0, 0, 0 ); d->tableSelectionCols.clear(); d->tableSelectionRows.clear(); d->tableDividersGuessed = false; foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); // should check whether this is on-screen here? updatedRect = updatedRect.united(selectionPartRect); } if ( mode != ClearOnlyDividers ) { d->tableSelectionParts.clear(); } d->tableSelectionParts.clear(); updatedRect.translate( -contentAreaPosition() ); viewport()->update( updatedRect ); } // const to be used for both zoomFactorFitMode function and slotRelayoutPages. static const int kcolWidthMargin = 6; static const int krowHeightMargin = 12; double PageView::zoomFactorFitMode( ZoomMode mode ) { const int pageCount = d->items.count(); if ( pageCount == 0 ) return 0; const bool facingCentered = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered || (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount == 1); const bool overrideCentering = facingCentered && pageCount < 3; const int nCols = overrideCentering ? 1 : viewColumns(); const double colWidth = viewport()->width() / nCols - kcolWidthMargin; const double rowHeight = viewport()->height() - krowHeightMargin; const PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage()) ]; // prevent segmentation fault when openning a new document; if ( !currentItem ) return 0; const Okular::Page * okularPage = currentItem->page(); const double width = okularPage->width(), height = okularPage->height(); if ( mode == ZoomFitWidth ) return (double) colWidth / width; if ( mode == ZoomFitPage ) { const double scaleW = (double) colWidth / (double)width; const double scaleH = (double) rowHeight / (double)height; return qMin(scaleW, scaleH); } return 0; } void PageView::updateZoom( ZoomMode newZoomMode ) { if ( newZoomMode == ZoomFixed ) { if ( d->aZoom->currentItem() == 0 ) newZoomMode = ZoomFitWidth; else if ( d->aZoom->currentItem() == 1 ) newZoomMode = ZoomFitPage; else if ( d->aZoom->currentItem() == 2 ) newZoomMode = ZoomFitAuto; } float newFactor = d->zoomFactor; QAction * checkedZoomAction = nullptr; switch ( newZoomMode ) { case ZoomFixed:{ //ZoomFixed case QString z = d->aZoom->currentText(); // kdelibs4 sometimes adds accelerators to actions' text directly :( z.remove (QLatin1Char('&')); z.remove (QLatin1Char('%')); newFactor = QLocale().toDouble( z ) / 100.0; }break; case ZoomIn: case ZoomOut:{ const float zoomFactorFitWidth = zoomFactorFitMode(ZoomFitWidth); const float zoomFactorFitPage = zoomFactorFitMode(ZoomFitPage); QVector zoomValue(15); qCopy(kZoomValues, kZoomValues + 13, zoomValue.begin()); zoomValue[13] = zoomFactorFitWidth; zoomValue[14] = zoomFactorFitPage; qSort(zoomValue.begin(), zoomValue.end()); QVector::iterator i; if ( newZoomMode == ZoomOut ) { if (newFactor <= zoomValue.first()) return; i = qLowerBound(zoomValue.begin(), zoomValue.end(), newFactor) - 1; } else { if (newFactor >= zoomValue.last()) return; i = qUpperBound(zoomValue.begin(), zoomValue.end(), newFactor); } const float tmpFactor = *i; if ( tmpFactor == zoomFactorFitWidth ) { newZoomMode = ZoomFitWidth; checkedZoomAction = d->aZoomFitWidth; } else if ( tmpFactor == zoomFactorFitPage ) { newZoomMode = ZoomFitPage; checkedZoomAction = d->aZoomFitPage; } else { newFactor = tmpFactor; newZoomMode = ZoomFixed; } } break; case ZoomFitWidth: checkedZoomAction = d->aZoomFitWidth; break; case ZoomFitPage: checkedZoomAction = d->aZoomFitPage; break; case ZoomFitAuto: checkedZoomAction = d->aZoomAutoFit; break; case ZoomRefreshCurrent: newZoomMode = ZoomFixed; d->zoomFactor = -1; break; } const float upperZoomLimit = d->document->supportsTiles() ? 16.0 : 4.0; if ( newFactor > upperZoomLimit ) newFactor = upperZoomLimit; if ( newFactor < 0.1 ) newFactor = 0.1; if ( newZoomMode != d->zoomMode || (newZoomMode == ZoomFixed && newFactor != d->zoomFactor ) ) { // rebuild layout and update the whole viewport d->zoomMode = newZoomMode; d->zoomFactor = newFactor; // be sure to block updates to document's viewport bool prevState = d->blockViewport; d->blockViewport = true; slotRelayoutPages(); d->blockViewport = prevState; // request pixmaps slotRequestVisiblePixmaps(); // update zoom text updateZoomText(); // update actions checked state if ( d->aZoomFitWidth ) { d->aZoomFitWidth->setChecked( checkedZoomAction == d->aZoomFitWidth ); d->aZoomFitPage->setChecked( checkedZoomAction == d->aZoomFitPage ); d->aZoomAutoFit->setChecked( checkedZoomAction == d->aZoomAutoFit ); } } else if ( newZoomMode == ZoomFixed && newFactor == d->zoomFactor ) updateZoomText(); d->aZoomIn->setEnabled( d->zoomFactor < upperZoomLimit-0.001 ); d->aZoomOut->setEnabled( d->zoomFactor > 0.101 ); } void PageView::updateZoomText() { // use current page zoom as zoomFactor if in ZoomFit/* mode if ( d->zoomMode != ZoomFixed && d->items.count() > 0 ) d->zoomFactor = d->items[ qMax( 0, (int)d->document->currentPage() ) ]->zoomFactor(); float newFactor = d->zoomFactor; d->aZoom->removeAllActions(); // add items that describe fit actions QStringList translated; translated << i18n("Fit Width") << i18n("Fit Page") << i18n("Auto Fit"); // add percent items int idx = 0, selIdx = 3; bool inserted = false; //use: "d->zoomMode != ZoomFixed" to hide Fit/* zoom ratio int zoomValueCount = 11; if ( d->document->supportsTiles() ) zoomValueCount = 13; while ( idx < zoomValueCount || !inserted ) { float value = idx < zoomValueCount ? kZoomValues[ idx ] : newFactor; if ( !inserted && newFactor < (value - 0.0001) ) value = newFactor; else idx ++; if ( value > (newFactor - 0.0001) && value < (newFactor + 0.0001) ) inserted = true; if ( !inserted ) selIdx++; // we do not need to display 2-digit precision QString localValue( QLocale().toString( value * 100.0, 'f', 1 ) ); localValue.remove( QLocale().decimalPoint() + QLatin1Char('0') ); // remove a trailing zero in numbers like 66.70 if ( localValue.right( 1 ) == QLatin1String( "0" ) && localValue.indexOf( QLocale().decimalPoint() ) > -1 ) localValue.chop( 1 ); translated << QStringLiteral( "%1%" ).arg( localValue ); } d->aZoom->setItems( translated ); // select current item in list if ( d->zoomMode == ZoomFitWidth ) selIdx = 0; else if ( d->zoomMode == ZoomFitPage ) selIdx = 1; else if ( d->zoomMode == ZoomFitAuto ) selIdx = 2; // we have to temporarily enable the actions as otherwise we can't set a new current item d->aZoom->setEnabled( true ); d->aZoom->selectableActionGroup()->setEnabled( true ); d->aZoom->setCurrentItem( selIdx ); d->aZoom->setEnabled( d->items.size() > 0 ); d->aZoom->selectableActionGroup()->setEnabled( d->items.size() > 0 ); } void PageView::updateCursor() { const QPoint p = contentAreaPosition() + viewport()->mapFromGlobal( QCursor::pos() ); updateCursor( p ); } void PageView::updateCursor( const QPoint &p ) { // reset mouse over link it will be re-set if that still valid d->mouseOverLinkObject = nullptr; // detect the underlaying page (if present) PageViewItem * pageItem = pickItemOnPoint( p.x(), p.y() ); if ( d->annotator && d->annotator->active() ) { if ( pageItem || d->annotator->annotating() ) setCursor( d->annotator->cursor() ); else setCursor( Qt::ForbiddenCursor ); } else if ( pageItem ) { double nX = pageItem->absToPageX(p.x()); double nY = pageItem->absToPageY(p.y()); Qt::CursorShape cursorShapeFallback; // if over a ObjectRect (of type Link) change cursor to hand switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::TextSelect: if (d->mouseTextSelecting) { setCursor( Qt::IBeamCursor ); return; } cursorShapeFallback = Qt::IBeamCursor; break; case Okular::Settings::EnumMouseMode::Magnifier: setCursor( Qt::CrossCursor ); return; case Okular::Settings::EnumMouseMode::RectSelect: case Okular::Settings::EnumMouseMode::TrimSelect: if (d->mouseSelecting) { setCursor( Qt::CrossCursor ); return; } cursorShapeFallback = Qt::CrossCursor; break; case Okular::Settings::EnumMouseMode::Browse: d->mouseOnRect = false; if ( d->mouseAnnotation->isMouseOver() ) { d->mouseOnRect = true; setCursor( d->mouseAnnotation->cursor() ); return; } else { cursorShapeFallback = Qt::OpenHandCursor; } break; default: setCursor( Qt::ArrowCursor ); return; } const Okular::ObjectRect * linkobj = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( linkobj ) { d->mouseOverLinkObject = linkobj; d->mouseOnRect = true; setCursor( Qt::PointingHandCursor ); } else { setCursor(cursorShapeFallback); } } else { // if there's no page over the cursor and we were showing the pointingHandCursor // go back to the normal one d->mouseOnRect = false; setCursor( Qt::ArrowCursor ); } } void PageView::reloadForms() { QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); if( d->m_formsVisible ) { for ( ; iIt != iEnd; ++iIt ) { (*iIt)->reloadFormWidgetsState(); } } } void PageView::moveMagnifier( const QPoint& p ) // non scaled point { const int w = d->magnifierView->width() * 0.5; const int h = d->magnifierView->height() * 0.5; int x = p.x() - w; int y = p.y() - h; const int max_x = viewport()->width(); const int max_y = viewport()->height(); QPoint scroll(0,0); if (x < 0) { if (horizontalScrollBar()->value() > 0) scroll.setX(x - w); x = 0; } if (y < 0) { if (verticalScrollBar()->value() > 0) scroll.setY(y - h); y = 0; } if (p.x() + w > max_x) { if (horizontalScrollBar()->value() < horizontalScrollBar()->maximum()) scroll.setX(p.x() + 2 * w - max_x); x = max_x - d->magnifierView->width() - 1; } if (p.y() + h > max_y) { if (verticalScrollBar()->value() < verticalScrollBar()->maximum()) scroll.setY(p.y() + 2 * h - max_y); y = max_y - d->magnifierView->height() - 1; } if (!scroll.isNull()) scrollPosIntoView(contentAreaPoint(p + scroll)); d->magnifierView->move(x, y); } void PageView::updateMagnifier( const QPoint& p ) // scaled point { /* translate mouse coordinates to page coordinates and inform the magnifier of the situation */ PageViewItem *item = pickItemOnPoint(p.x(), p.y()); if (item) { Okular::NormalizedPoint np(item->absToPageX(p.x()), item->absToPageY(p.y())); d->magnifierView->updateView( np, item->page() ); } } int PageView::viewColumns() const { int vm = Okular::Settings::viewMode(); if (vm == Okular::Settings::EnumViewMode::Single) return 1; else if (vm == Okular::Settings::EnumViewMode::Facing || vm == Okular::Settings::EnumViewMode::FacingFirstCentered) return 2; else if (vm == Okular::Settings::EnumViewMode::Summary && d->document->pages() < Okular::Settings::viewColumns() ) return d->document->pages(); else return Okular::Settings::viewColumns(); } void PageView::center(int cx, int cy) { scrollTo( cx - viewport()->width() / 2, cy - viewport()->height() / 2 ); } void PageView::scrollTo( int x, int y ) { bool prevState = d->blockPixmapsRequest; int newValue = -1; if ( x != horizontalScrollBar()->value() || y != verticalScrollBar()->value() ) newValue = 1; // Pretend this call is the result of a scrollbar event d->blockPixmapsRequest = true; horizontalScrollBar()->setValue( x ); verticalScrollBar()->setValue( y ); d->blockPixmapsRequest = prevState; slotRequestVisiblePixmaps( newValue ); } void PageView::toggleFormWidgets( bool on ) { bool somehadfocus = false; QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); for ( ; dIt != dEnd; ++dIt ) { bool hadfocus = (*dIt)->setFormWidgetsVisible( on ); somehadfocus = somehadfocus || hadfocus; } if ( somehadfocus ) setFocus(); d->m_formsVisible = on; if ( d->aToggleForms ) // it may not exist if we are on dummy mode { if ( d->m_formsVisible ) { d->aToggleForms->setText( i18n( "Hide Forms" ) ); } else { d->aToggleForms->setText( i18n( "Show Forms" ) ); } } } void PageView::resizeContentArea( const QSize & newSize ) { const QSize vs = viewport()->size(); int hRange = newSize.width() - vs.width(); int vRange = newSize.height() - vs.height(); if ( horizontalScrollBar()->isVisible() && hRange == verticalScrollBar()->width() && verticalScrollBar()->isVisible() && vRange == horizontalScrollBar()->height() && Okular::Settings::showScrollBars() ) { hRange = 0; vRange = 0; } horizontalScrollBar()->setRange( 0, hRange ); verticalScrollBar()->setRange( 0, vRange ); updatePageStep(); } void PageView::updatePageStep() { const QSize vs = viewport()->size(); horizontalScrollBar()->setPageStep( vs.width() ); verticalScrollBar()->setPageStep( vs.height() * (100 - Okular::Settings::scrollOverlap()) / 100 ); } void PageView::addWebShortcutsMenu( QMenu * menu, const QString & text ) { if ( text.isEmpty() ) { return; } QString searchText = text; searchText = searchText.replace( QLatin1Char('\n'), QLatin1Char(' ') ).replace(QLatin1Char( '\r'), QLatin1Char(' ') ).simplified(); if ( searchText.isEmpty() ) { return; } KUriFilterData filterData( searchText ); filterData.setSearchFilteringOptions( KUriFilterData::RetrievePreferredSearchProvidersOnly ); if ( KUriFilter::self()->filterSearchUri( filterData, KUriFilter::NormalTextFilter ) ) { const QStringList searchProviders = filterData.preferredSearchProviders(); if ( !searchProviders.isEmpty() ) { QMenu *webShortcutsMenu = new QMenu( menu ); webShortcutsMenu->setIcon( QIcon::fromTheme( QStringLiteral("preferences-web-browser-shortcuts") ) ); const QString squeezedText = KStringHandler::rsqueeze( searchText, 21 ); webShortcutsMenu->setTitle( i18n( "Search for '%1' with", squeezedText ) ); QAction *action = nullptr; foreach( const QString &searchProvider, searchProviders ) { action = new QAction( searchProvider, webShortcutsMenu ); action->setIcon( QIcon::fromTheme( filterData.iconNameForPreferredSearchProvider( searchProvider ) ) ); action->setData( filterData.queryForPreferredSearchProvider( searchProvider ) ); connect( action, &QAction::triggered, this, &PageView::slotHandleWebShortcutAction ); webShortcutsMenu->addAction( action ); } webShortcutsMenu->addSeparator(); action = new QAction( i18n( "Configure Web Shortcuts..." ), webShortcutsMenu ); action->setIcon( QIcon::fromTheme( QStringLiteral("configure") ) ); connect( action, &QAction::triggered, this, &PageView::slotConfigureWebShortcuts ); webShortcutsMenu->addAction( action ); menu->addMenu(webShortcutsMenu); } } } QMenu* PageView::createProcessLinkMenu(PageViewItem *item, const QPoint &eventPos) { // check if the right-click was over a link const double nX = item->absToPageX(eventPos.x()); const double nY = item->absToPageY(eventPos.y()); const Okular::ObjectRect * rect = item->page()->objectRect( Okular::ObjectRect::Action, nX, nY, item->uncroppedWidth(), item->uncroppedHeight() ); if ( rect ) { QMenu *menu = new QMenu(this); const Okular::Action * link = static_cast< const Okular::Action * >( rect->object() ); // creating the menu and its actions QAction * processLink = menu->addAction( i18n( "Follow This Link" ) ); processLink->setObjectName("ProcessLinkAction"); if ( link->actionType() == Okular::Action::Sound ) { processLink->setText( i18n( "Play this Sound" ) ); if ( Okular::AudioPlayer::instance()->state() == Okular::AudioPlayer::PlayingState ) { QAction * actStopSound = menu->addAction( i18n( "Stop Sound" ) ); connect( actStopSound, &QAction::triggered, []() { Okular::AudioPlayer::instance()->stopPlaybacks(); }); } } if ( dynamic_cast< const Okular::BrowseAction * >( link ) ) { QAction * actCopyLinkLocation = menu->addAction( QIcon::fromTheme( QStringLiteral("edit-copy") ), i18n( "Copy Link Address" ) ); actCopyLinkLocation->setObjectName("CopyLinkLocationAction"); connect( actCopyLinkLocation, &QAction::triggered, [ link ]() { const Okular::BrowseAction * browseLink = static_cast< const Okular::BrowseAction * >( link ); QClipboard *cb = QApplication::clipboard(); cb->setText( browseLink->url().toDisplayString(), QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setText( browseLink->url().toDisplayString(), QClipboard::Selection ); } ); } connect( processLink, &QAction::triggered, [this, link]() { d->document->processAction( link ); }); return menu; } return nullptr; } //BEGIN private SLOTS void PageView::slotRelayoutPages() // called by: notifySetup, viewportResizeEvent, slotViewMode, slotContinuousToggled, updateZoom { // set an empty container if we have no pages const int pageCount = d->items.count(); if ( pageCount < 1 ) { return; } // if viewport was auto-moving, stop it if ( d->viewportMoveActive ) { center( d->viewportMoveDest.x(), d->viewportMoveDest.y() ); d->viewportMoveActive = false; d->viewportMoveTimer->stop(); verticalScrollBar()->setEnabled( true ); horizontalScrollBar()->setEnabled( true ); } // common iterator used in this method and viewport parameters QVector< PageViewItem * >::const_iterator iIt, iEnd = d->items.constEnd(); int viewportWidth = viewport()->width(), viewportHeight = viewport()->height(), fullWidth = 0, fullHeight = 0; QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewportWidth, viewportHeight ); // handle the 'center first page in row' stuff const bool facing = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount > 1; const bool facingCentered = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered || (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount == 1); const bool overrideCentering = facingCentered && pageCount < 3; const bool centerFirstPage = facingCentered && !overrideCentering; const bool facingPages = facing || centerFirstPage; const bool centerLastPage = centerFirstPage && pageCount % 2 == 0; const bool continuousView = Okular::Settings::viewContinuous(); const int nCols = overrideCentering ? 1 : viewColumns(); const bool singlePageViewMode = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Single; if ( d->aFitWindowToPage ) d->aFitWindowToPage->setEnabled( !continuousView && singlePageViewMode ); // set all items geometry and resize contents. handle 'continuous' and 'single' modes separately PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage() ) ]; // Here we find out column's width and row's height to compute a table // so we can place widgets 'centered in virtual cells'. const int nRows = (int)ceil( (float)(centerFirstPage ? (pageCount + nCols - 1) : pageCount) / (float)nCols ); int * colWidth = new int[ nCols ], * rowHeight = new int[ nRows ], cIdx = 0, rIdx = 0; for ( int i = 0; i < nCols; i++ ) colWidth[ i ] = viewportWidth / nCols; for ( int i = 0; i < nRows; i++ ) rowHeight[ i ] = 0; // handle the 'centering on first row' stuff if ( centerFirstPage ) cIdx += nCols - 1; // 1) find the maximum columns width and rows height for a grid in // which each page must well-fit inside a cell for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; // update internal page size (leaving a little margin in case of Fit* modes) updateItemSize( item, colWidth[ cIdx ] - kcolWidthMargin, viewportHeight - krowHeightMargin ); // find row's maximum height and column's max width if ( item->croppedWidth() + kcolWidthMargin > colWidth[ cIdx ] ) colWidth[ cIdx ] = item->croppedWidth() + kcolWidthMargin; if ( item->croppedHeight() + krowHeightMargin > rowHeight[ rIdx ] ) rowHeight[ rIdx ] = item->croppedHeight() + krowHeightMargin; // handle the 'centering on first row' stuff // update col/row indices if ( ++cIdx == nCols ) { cIdx = 0; rIdx++; } } const int pageRowIdx = ( ( centerFirstPage ? nCols - 1 : 0 ) + currentItem->pageNumber() ) / nCols; // 2) compute full size for ( int i = 0; i < nCols; i++ ) fullWidth += colWidth[ i ]; if ( continuousView ) { for ( int i = 0; i < nRows; i++ ) fullHeight += rowHeight[ i ]; } else fullHeight = rowHeight[ pageRowIdx ]; // 3) arrange widgets inside cells (and refine fullHeight if needed) int insertX = 0, insertY = fullHeight < viewportHeight ? ( viewportHeight - fullHeight ) / 2 : 0; const int origInsertY = insertY; cIdx = 0; rIdx = 0; if ( centerFirstPage ) { cIdx += nCols - 1; for ( int i = 0; i < cIdx; ++i ) insertX += colWidth[ i ]; } for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; int cWidth = colWidth[ cIdx ], rHeight = rowHeight[ rIdx ]; if ( continuousView || rIdx == pageRowIdx ) { const bool reallyDoCenterFirst = item->pageNumber() == 0 && centerFirstPage; const bool reallyDoCenterLast = item->pageNumber() == pageCount - 1 && centerLastPage; int actualX = 0; if ( reallyDoCenterFirst || reallyDoCenterLast ) { // page is centered across entire viewport actualX = (fullWidth - item->croppedWidth()) / 2; } else if ( facingPages ) { if (Okular::Settings::rtlReadingDirection()){ // RTL reading mode actualX = ( (centerFirstPage && item->pageNumber() % 2 == 0) || (!centerFirstPage && item->pageNumber() % 2 == 1) ) ? (fullWidth / 2) - item->croppedWidth() - 1 : (fullWidth / 2) + 1; } else { // page edges 'touch' the center of the viewport actualX = ( (centerFirstPage && item->pageNumber() % 2 == 1) || (!centerFirstPage && item->pageNumber() % 2 == 0) ) ? (fullWidth / 2) - item->croppedWidth() - 1 : (fullWidth / 2) + 1; } } else { // page is centered within its virtual column //actualX = insertX + (cWidth - item->croppedWidth()) / 2; if (Okular::Settings::rtlReadingDirection()){ actualX = fullWidth - insertX - cWidth +( (cWidth - item->croppedWidth()) / 2); } else { actualX = insertX + (cWidth - item->croppedWidth()) / 2; } } item->moveTo( actualX, (continuousView ? insertY : origInsertY) + (rHeight - item->croppedHeight()) / 2 ); item->setVisible( true ); } else { item->moveTo( 0, 0 ); item->setVisible( false ); } item->setFormWidgetsVisible( d->m_formsVisible ); // advance col/row index insertX += cWidth; if ( ++cIdx == nCols ) { cIdx = 0; rIdx++; insertX = 0; insertY += rHeight; } #ifdef PAGEVIEW_DEBUG kWarning() << "updating size for pageno" << item->pageNumber() << "cropped" << item->croppedGeometry() << "uncropped" << item->uncroppedGeometry(); #endif } delete [] colWidth; delete [] rowHeight; // 3) reset dirty state d->dirtyLayout = false; // 4) update scrollview's contents size and recenter view bool wasUpdatesEnabled = viewport()->updatesEnabled(); if ( fullWidth != contentAreaWidth() || fullHeight != contentAreaHeight() ) { const Okular::DocumentViewport vp = d->document->viewport(); // disable updates and resize the viewportContents if ( wasUpdatesEnabled ) viewport()->setUpdatesEnabled( false ); resizeContentArea( QSize( fullWidth, fullHeight ) ); // restore previous viewport if defined and updates enabled if ( wasUpdatesEnabled ) { if ( vp.pageNumber >= 0 ) { int prevX = horizontalScrollBar()->value(), prevY = verticalScrollBar()->value(); const QRect & geometry = d->items[ vp.pageNumber ]->croppedGeometry(); double nX = vp.rePos.enabled ? normClamp( vp.rePos.normalizedX, 0.5 ) : 0.5, nY = vp.rePos.enabled ? normClamp( vp.rePos.normalizedY, 0.0 ) : 0.0; center( geometry.left() + qRound( nX * (double)geometry.width() ), geometry.top() + qRound( nY * (double)geometry.height() ) ); // center() usually moves the viewport, that requests pixmaps too. // if that doesn't happen we have to request them by hand if ( prevX == horizontalScrollBar()->value() && prevY == verticalScrollBar()->value() ) slotRequestVisiblePixmaps(); } // or else go to center page else center( fullWidth / 2, 0 ); viewport()->setUpdatesEnabled( true ); } } // 5) update the whole viewport if updated enabled if ( wasUpdatesEnabled ) viewport()->update(); } void PageView::delayedResizeEvent() { // If we already got here we don't need to execute the timer slot again d->delayResizeEventTimer->stop(); slotRelayoutPages(); slotRequestVisiblePixmaps(); } static void slotRequestPreloadPixmap( Okular::DocumentObserver * observer, const PageViewItem * i, const QRect &expandedViewportRect, QLinkedList< Okular::PixmapRequest * > *requestedPixmaps ) { Okular::NormalizedRect preRenderRegion; const QRect intersectionRect = expandedViewportRect.intersected( i->croppedGeometry() ); if ( !intersectionRect.isEmpty() ) preRenderRegion = Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ); // request the pixmap if not already present if ( !i->page()->hasPixmap( observer, i->uncroppedWidth(), i->uncroppedHeight(), preRenderRegion ) && i->uncroppedWidth() > 0 ) { Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload; requestFeatures |= Okular::PixmapRequest::Asynchronous; const bool pageHasTilesManager = i->page()->hasTilesManager( observer ); if ( pageHasTilesManager && !preRenderRegion.isNull() ) { Okular::PixmapRequest * p = new Okular::PixmapRequest( observer, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, requestFeatures ); requestedPixmaps->push_back( p ); p->setNormalizedRect( preRenderRegion ); p->setTile( true ); } else if ( !pageHasTilesManager ) { Okular::PixmapRequest * p = new Okular::PixmapRequest( observer, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, requestFeatures ); requestedPixmaps->push_back( p ); p->setNormalizedRect( preRenderRegion ); } } } void PageView::slotRequestVisiblePixmaps( int newValue ) { // if requests are blocked (because raised by an unwanted event), exit if ( d->blockPixmapsRequest || d->viewportMoveActive ) return; // precalc view limits for intersecting with page coords inside the loop const bool isEvent = newValue != -1 && !d->blockViewport; const QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewport()->width(), viewport()->height() ); const QRect viewportRectAtZeroZero( 0, 0, viewport()->width(), viewport()->height() ); // some variables used to determine the viewport int nearPageNumber = -1; const double viewportCenterX = (viewportRect.left() + viewportRect.right()) / 2.0; const double viewportCenterY = (viewportRect.top() + viewportRect.bottom()) / 2.0; double focusedX = 0.5, focusedY = 0.0, minDistance = -1.0; // Margin (in pixels) around the viewport to preload const int pixelsToExpand = 512; // iterate over all items d->visibleItems.clear(); QLinkedList< Okular::PixmapRequest * > requestedPixmaps; QVector< Okular::VisiblePageRect * > visibleRects; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * i = *iIt; foreach( FormWidgetIface *fwi, i->formWidgets() ) { Okular::NormalizedRect r = fwi->rect(); fwi->moveTo( qRound( i->uncroppedGeometry().left() + i->uncroppedWidth() * r.left ) + 1 - viewportRect.left(), qRound( i->uncroppedGeometry().top() + i->uncroppedHeight() * r.top ) + 1 - viewportRect.top() ); } Q_FOREACH ( VideoWidget *vw, i->videoWidgets() ) { const Okular::NormalizedRect r = vw->normGeometry(); vw->move( qRound( i->uncroppedGeometry().left() + i->uncroppedWidth() * r.left ) + 1 - viewportRect.left(), qRound( i->uncroppedGeometry().top() + i->uncroppedHeight() * r.top ) + 1 - viewportRect.top() ); if ( vw->isPlaying() && viewportRectAtZeroZero.intersected( vw->geometry() ).isEmpty() ) { vw->stop(); vw->pageLeft(); } } if ( !i->isVisible() ) continue; #ifdef PAGEVIEW_DEBUG kWarning() << "checking page" << i->pageNumber(); kWarning().nospace() << "viewportRect is " << viewportRect << ", page item is " << i->croppedGeometry() << " intersect : " << viewportRect.intersects( i->croppedGeometry() ); #endif // if the item doesn't intersect the viewport, skip it QRect intersectionRect = viewportRect.intersected( i->croppedGeometry() ); if ( intersectionRect.isEmpty() ) { continue; } // add the item to the 'visible list' d->visibleItems.push_back( i ); Okular::VisiblePageRect * vItem = new Okular::VisiblePageRect( i->pageNumber(), Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ) ); visibleRects.push_back( vItem ); #ifdef PAGEVIEW_DEBUG kWarning() << "checking for pixmap for page" << i->pageNumber() << "=" << i->page()->hasPixmap( this, i->uncroppedWidth(), i->uncroppedHeight() ); kWarning() << "checking for text for page" << i->pageNumber() << "=" << i->page()->hasTextPage(); #endif Okular::NormalizedRect expandedVisibleRect = vItem->rect; if ( i->page()->hasTilesManager( this ) && Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Low ) { double rectMargin = pixelsToExpand/(double)i->uncroppedHeight(); expandedVisibleRect.left = qMax( 0.0, vItem->rect.left - rectMargin ); expandedVisibleRect.top = qMax( 0.0, vItem->rect.top - rectMargin ); expandedVisibleRect.right = qMin( 1.0, vItem->rect.right + rectMargin ); expandedVisibleRect.bottom = qMin( 1.0, vItem->rect.bottom + rectMargin ); } // if the item has not the right pixmap, add a request for it if ( !i->page()->hasPixmap( this, i->uncroppedWidth(), i->uncroppedHeight(), expandedVisibleRect ) ) { #ifdef PAGEVIEW_DEBUG kWarning() << "rerequesting visible pixmaps for page" << i->pageNumber() << "!"; #endif Okular::PixmapRequest * p = new Okular::PixmapRequest( this, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRIO, Okular::PixmapRequest::Asynchronous ); requestedPixmaps.push_back( p ); if ( i->page()->hasTilesManager( this ) ) { p->setNormalizedRect( expandedVisibleRect ); p->setTile( true ); } else p->setNormalizedRect( vItem->rect ); } // look for the item closest to viewport center and the relative // position between the item and the viewport center if ( isEvent ) { const QRect & geometry = i->croppedGeometry(); // compute distance between item center and viewport center (slightly moved left) double distance = hypot( (geometry.left() + geometry.right()) / 2 - (viewportCenterX - 4), (geometry.top() + geometry.bottom()) / 2 - viewportCenterY ); if ( distance >= minDistance && nearPageNumber != -1 ) continue; nearPageNumber = i->pageNumber(); minDistance = distance; if ( geometry.height() > 0 && geometry.width() > 0 ) { focusedX = ( viewportCenterX - (double)geometry.left() ) / (double)geometry.width(); focusedY = ( viewportCenterY - (double)geometry.top() ) / (double)geometry.height(); } } } // if preloading is enabled, add the pages before and after in preloading if ( !d->visibleItems.isEmpty() && Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low ) { // as the requests are done in the order as they appear in the list, // request first the next page and then the previous int pagesToPreload = viewColumns(); // if the greedy option is set, preload all pages if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy) pagesToPreload = d->items.count(); const QRect expandedViewportRect = viewportRect.adjusted( 0, -pixelsToExpand, 0, pixelsToExpand ); for( int j = 1; j <= pagesToPreload; j++ ) { // add the page after the 'visible series' in preload const int tailRequest = d->visibleItems.last()->pageNumber() + j; if ( tailRequest < (int)d->items.count() ) { slotRequestPreloadPixmap( this, d->items[ tailRequest ], expandedViewportRect, &requestedPixmaps ); } // add the page before the 'visible series' in preload const int headRequest = d->visibleItems.first()->pageNumber() - j; if ( headRequest >= 0 ) { slotRequestPreloadPixmap( this, d->items[ headRequest ], expandedViewportRect, &requestedPixmaps ); } // stop if we've already reached both ends of the document if ( headRequest < 0 && tailRequest >= (int)d->items.count() ) break; } } // send requests to the document if ( !requestedPixmaps.isEmpty() ) { d->document->requestPixmaps( requestedPixmaps ); } // if this functions was invoked by viewport events, send update to document if ( isEvent && nearPageNumber != -1 ) { // determine the document viewport Okular::DocumentViewport newViewport( nearPageNumber ); newViewport.rePos.enabled = true; newViewport.rePos.normalizedX = focusedX; newViewport.rePos.normalizedY = focusedY; // set the viewport to other observers d->document->setViewport( newViewport , this ); } d->document->setVisiblePageRects( visibleRects, this ); } void PageView::slotMoveViewport() { // converge to viewportMoveDest in 1 second int diffTime = d->viewportMoveTime.elapsed(); if ( diffTime >= 667 || !d->viewportMoveActive ) { center( d->viewportMoveDest.x(), d->viewportMoveDest.y() ); d->viewportMoveTimer->stop(); d->viewportMoveActive = false; slotRequestVisiblePixmaps(); verticalScrollBar()->setEnabled( true ); horizontalScrollBar()->setEnabled( true ); return; } // move the viewport smoothly (kmplot: p(x)=1+0.47*(x-1)^3-0.25*(x-1)^4) float convergeSpeed = (float)diffTime / 667.0, x = ((float)viewport()->width() / 2.0) + horizontalScrollBar()->value(), y = ((float)viewport()->height() / 2.0) + verticalScrollBar()->value(), diffX = (float)d->viewportMoveDest.x() - x, diffY = (float)d->viewportMoveDest.y() - y; convergeSpeed *= convergeSpeed * (1.4 - convergeSpeed); center( (int)(x + diffX * convergeSpeed), (int)(y + diffY * convergeSpeed ) ); } void PageView::slotAutoScroll() { // the first time create the timer if ( !d->autoScrollTimer ) { d->autoScrollTimer = new QTimer( this ); d->autoScrollTimer->setSingleShot( true ); connect( d->autoScrollTimer, &QTimer::timeout, this, &PageView::slotAutoScroll ); } // if scrollIncrement is zero, stop the timer if ( !d->scrollIncrement ) { d->autoScrollTimer->stop(); return; } // compute delay between timer ticks and scroll amount per tick int index = abs( d->scrollIncrement ) - 1; // 0..9 const int scrollDelay[10] = { 200, 100, 50, 30, 20, 30, 25, 20, 30, 20 }; const int scrollOffset[10] = { 1, 1, 1, 1, 1, 2, 2, 2, 4, 4 }; d->autoScrollTimer->start( scrollDelay[ index ] ); int delta = d->scrollIncrement > 0 ? scrollOffset[ index ] : -scrollOffset[ index ]; verticalScrollBar()->setValue(verticalScrollBar()->value() + delta); } void PageView::slotDragScroll() { scrollTo( horizontalScrollBar()->value() + d->dragScrollVector.x(), verticalScrollBar()->value() + d->dragScrollVector.y() ); QPoint p = contentAreaPosition() + viewport()->mapFromGlobal( QCursor::pos() ); updateSelection( p ); } void PageView::slotShowWelcome() { // show initial welcome text d->messageWindow->display( i18n( "Welcome" ), QString(), PageViewMessage::Info, 2000 ); } void PageView::slotShowSizeAllCursor() { setCursor( Qt::SizeAllCursor ); } void PageView::slotHandleWebShortcutAction() { QAction *action = qobject_cast( sender() ); if (action) { KUriFilterData filterData( action->data().toString() ); if ( KUriFilter::self()->filterSearchUri( filterData, KUriFilter::WebShortcutFilter ) ) { QDesktopServices::openUrl( filterData.uri() ); } } } void PageView::slotConfigureWebShortcuts() { KToolInvocation::kdeinitExec( QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts") ); } void PageView::slotZoom() { if ( !d->aZoom->selectableActionGroup()->isEnabled() ) return; setFocus(); updateZoom( ZoomFixed ); } void PageView::slotZoomIn() { updateZoom( ZoomIn ); } void PageView::slotZoomOut() { updateZoom( ZoomOut ); } void PageView::slotFitToWidthToggled( bool on ) { if ( on ) updateZoom( ZoomFitWidth ); } void PageView::slotFitToPageToggled( bool on ) { if ( on ) updateZoom( ZoomFitPage ); } void PageView::slotAutoFitToggled( bool on ) { if ( on ) updateZoom( ZoomFitAuto ); } void PageView::slotViewMode( QAction *action ) { const int nr = action->data().toInt(); if ( (int)Okular::Settings::viewMode() != nr ) { Okular::Settings::setViewMode( nr ); Okular::Settings::self()->save(); if ( d->document->pages() > 0 ) slotRelayoutPages(); } } void PageView::slotContinuousToggled( bool on ) { if ( Okular::Settings::viewContinuous() != on ) { Okular::Settings::setViewContinuous( on ); Okular::Settings::self()->save(); if ( d->document->pages() > 0 ) slotRelayoutPages(); } } void PageView::slotSetMouseNormal() { d->mouseMode = Okular::Settings::EnumMouseMode::Browse; Okular::Settings::setMouseMode( d->mouseMode ); // hide the messageWindow d->messageWindow->hide(); // reshow the annotator toolbar if hiding was forced (and if it is not already visible) if ( d->annotator && d->annotator->hidingWasForced() && d->aToggleAnnotator && !d->aToggleAnnotator->isChecked() ) d->aToggleAnnotator->trigger(); // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseZoom() { d->mouseMode = Okular::Settings::EnumMouseMode::Zoom; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Select zooming area. Right-click to zoom out." ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseMagnifier() { d->mouseMode = Okular::Settings::EnumMouseMode::Magnifier; Okular::Settings::setMouseMode( d->mouseMode ); d->messageWindow->display( i18n( "Click to see the magnified view." ), QString() ); // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseSelect() { d->mouseMode = Okular::Settings::EnumMouseMode::RectSelect; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Draw a rectangle around the text/graphics to copy." ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseTextSelect() { d->mouseMode = Okular::Settings::EnumMouseMode::TextSelect; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Select text" ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseTableSelect() { d->mouseMode = Okular::Settings::EnumMouseMode::TableSelect; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Draw a rectangle around the table, then click near edges to divide up; press Esc to clear." ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotToggleAnnotator( bool on ) { // the 'inHere' trick is needed as the slotSetMouseZoom() calls this static bool inHere = false; if ( inHere ) return; inHere = true; // the annotator can be used in normal mouse mode only, so if asked for it, // switch to normal mode if ( on && d->mouseMode != Okular::Settings::EnumMouseMode::Browse ) d->aMouseNormal->trigger(); // ask for Author's name if not already set if ( Okular::Settings::identityAuthor().isEmpty() ) { // get default username from the kdelibs/kdecore/KUser KUser currentUser; QString userName = currentUser.property( KUser::FullName ).toString(); // ask the user for confirmation/change if ( userName.isEmpty() ) { bool ok = false; userName = QInputDialog::getText(nullptr, i18n( "Annotations author" ), i18n( "Please insert your name or initials:" ), QLineEdit::Normal, QString(), &ok ); if ( !ok ) { d->aToggleAnnotator->trigger(); inHere = false; return; } } // save the name Okular::Settings::setIdentityAuthor( userName ); Okular::Settings::self()->save(); } // create the annotator object if not present if ( !d->annotator ) { d->annotator = new PageViewAnnotator( this, d->document ); bool allowTools = d->document->pages() > 0 && d->document->isAllowed( Okular::AllowNotes ); d->annotator->setToolsEnabled( allowTools ); d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() ); } // initialize/reset annotator (and show/hide toolbar) d->annotator->setEnabled( on ); d->annotator->setHidingForced( false ); inHere = false; } void PageView::slotAutoScrollUp() { if ( d->scrollIncrement < -9 ) return; d->scrollIncrement--; slotAutoScroll(); setFocus(); } void PageView::slotAutoScrollDown() { if ( d->scrollIncrement > 9 ) return; d->scrollIncrement++; slotAutoScroll(); setFocus(); } void PageView::slotScrollUp( bool singleStep ) { // if in single page mode and at the top of the screen, go to \ page if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() > verticalScrollBar()->minimum() ) { if ( singleStep ) verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub ); else verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepSub ); } else if ( d->document->currentPage() > 0 ) { // more optimized than document->setPrevPage and then move view to bottom Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber -= viewColumns(); if ( newViewport.pageNumber < 0 ) newViewport.pageNumber = 0; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 1.0; d->document->setViewport( newViewport ); } } void PageView::slotScrollDown( bool singleStep ) { // if in single page mode and at the bottom of the screen, go to next page if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() < verticalScrollBar()->maximum() ) { if ( singleStep ) verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd ); else verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepAdd ); } else if ( (int)d->document->currentPage() < d->items.count() - 1 ) { // more optimized than document->setNextPage and then move view to top Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber += viewColumns(); if ( newViewport.pageNumber >= (int)d->items.count() ) newViewport.pageNumber = d->items.count() - 1; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 0.0; d->document->setViewport( newViewport ); } } void PageView::slotRotateClockwise() { int id = ( (int)d->document->rotation() + 1 ) % 4; d->document->setRotation( id ); } void PageView::slotRotateCounterClockwise() { int id = ( (int)d->document->rotation() + 3 ) % 4; d->document->setRotation( id ); } void PageView::slotRotateOriginal() { d->document->setRotation( 0 ); } void PageView::slotPageSizes( int newsize ) { if ( newsize < 0 || newsize >= d->document->pageSizes().count() ) return; d->document->setPageSize( d->document->pageSizes().at( newsize ) ); } // Enforce mutual-exclusion between trim modes // Each mode is uniquely identified by a single value // From Okular::Settings::EnumTrimMode void PageView::updateTrimMode( int except_id ) { const QList trimModeActions = d->aTrimMode->menu()->actions(); foreach(QAction *trimModeAction, trimModeActions) { if (trimModeAction->data().toInt() != except_id) trimModeAction->setChecked( false ); } } bool PageView::mouseReleaseOverLink( const Okular::ObjectRect * rect ) const { if ( rect ) { // handle click over a link const Okular::Action * action = static_cast< const Okular::Action * >( rect->object() ); d->document->processAction( action ); return true; } return false; } void PageView::slotTrimMarginsToggled( bool on ) { if (on) { // Turn off any other Trim modes updateTrimMode(d->aTrimMargins->data().toInt()); } if ( Okular::Settings::trimMargins() != on ) { Okular::Settings::setTrimMargins( on ); Okular::Settings::self()->save(); if ( d->document->pages() > 0 ) { slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! } } } void PageView::slotTrimToSelectionToggled( bool on ) { if ( on ) { // Turn off any other Trim modes updateTrimMode(d->aTrimToSelection->data().toInt()); d->mouseMode = Okular::Settings::EnumMouseMode::TrimSelect; // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Draw a rectangle around the page area you wish to keep visible" ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); } else { // toggled off while making selection if ( Okular::Settings::EnumMouseMode::TrimSelect == d->mouseMode ) { // clear widget selection and invalidate rect selectionClear(); // When Trim selection bbox interaction is over, we should switch to another mousemode. if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } else { d->aMouseNormal->trigger(); } } d->trimBoundingBox = Okular::NormalizedRect(); // invalidate box if ( d->document->pages() > 0 ) { slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! } } } void PageView::slotToggleForms() { toggleFormWidgets( !d->m_formsVisible ); } void PageView::slotFormChanged( int pageNumber ) { if ( !d->refreshTimer ) { d->refreshTimer = new QTimer( this ); d->refreshTimer->setSingleShot( true ); connect( d->refreshTimer, &QTimer::timeout, this, &PageView::slotRefreshPage ); } d->refreshPages << pageNumber; int delay = 0; if ( d->m_formsVisible ) { delay = 1000; } d->refreshTimer->start( delay ); } void PageView::slotRefreshPage() { foreach(int req, d->refreshPages) { QMetaObject::invokeMethod( d->document, "refreshPixmaps", Qt::QueuedConnection, Q_ARG( int, req ) ); } d->refreshPages.clear(); } #ifdef HAVE_SPEECH void PageView::slotSpeakDocument() { QString text; QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd(); for ( ; it < itEnd; ++it ) { Okular::RegularAreaRect * area = textSelectionForItem( *it ); text.append( (*it)->page()->text( area ) ); text.append( '\n' ); delete area; } d->tts()->say( text ); } void PageView::slotSpeakCurrentPage() { const int currentPage = d->document->viewport().pageNumber; PageViewItem *item = d->items.at( currentPage ); Okular::RegularAreaRect * area = textSelectionForItem( item ); const QString text = item->page()->text( area ); delete area; d->tts()->say( text ); } void PageView::slotStopSpeaks() { if ( !d->m_tts ) return; d->m_tts->stopAllSpeechs(); } #endif void PageView::slotAction( Okular::Action *action ) { d->document->processAction( action ); } void PageView::externalKeyPressEvent( QKeyEvent *e ) { keyPressEvent( e ); } void PageView::slotProcessMovieAction( const Okular::MovieAction *action ) { const Okular::MovieAnnotation *movieAnnotation = action->annotation(); if ( !movieAnnotation ) return; Okular::Movie *movie = movieAnnotation->movie(); if ( !movie ) return; const int currentPage = d->document->viewport().pageNumber; PageViewItem *item = d->items.at( currentPage ); if ( !item ) return; VideoWidget *vw = item->videoWidgets().value( movie ); if ( !vw ) return; vw->show(); switch ( action->operation() ) { case Okular::MovieAction::Play: vw->stop(); vw->play(); break; case Okular::MovieAction::Stop: vw->stop(); break; case Okular::MovieAction::Pause: vw->pause(); break; case Okular::MovieAction::Resume: vw->play(); break; }; } void PageView::slotProcessRenditionAction( const Okular::RenditionAction *action ) { Okular::Movie *movie = action->movie(); if ( !movie ) return; const int currentPage = d->document->viewport().pageNumber; PageViewItem *item = d->items.at( currentPage ); if ( !item ) return; VideoWidget *vw = item->videoWidgets().value( movie ); if ( !vw ) return; if ( action->operation() == Okular::RenditionAction::None ) return; vw->show(); switch ( action->operation() ) { case Okular::RenditionAction::Play: vw->stop(); vw->play(); break; case Okular::RenditionAction::Stop: vw->stop(); break; case Okular::RenditionAction::Pause: vw->pause(); break; case Okular::RenditionAction::Resume: vw->play(); break; default: return; }; } void PageView::slotSetChangeColors(bool active) { Okular::SettingsCore::setChangeColors(active); Okular::Settings::self()->save(); viewport()->update(); } void PageView::slotToggleChangeColors() { slotSetChangeColors( !Okular::SettingsCore::changeColors() ); } void PageView::slotFitWindowToPage() { PageViewItem currentPageItem = nullptr; QSize viewportSize = viewport()->size(); foreach ( const PageViewItem * pageItem, d->items ) { if ( pageItem->isVisible() ) { currentPageItem = *pageItem; break; } } const QSize pageSize = QSize( currentPageItem.uncroppedWidth() + kcolWidthMargin, currentPageItem.uncroppedHeight() + krowHeightMargin ); if ( verticalScrollBar()->isVisible() ) viewportSize.setWidth( viewportSize.width() + verticalScrollBar()->width() ); if ( horizontalScrollBar()->isVisible() ) viewportSize.setHeight( viewportSize.height() + horizontalScrollBar()->height() ); emit fitWindowToPage( viewportSize, pageSize ); } void PageView::slotValidateSignatures() { bool allSignaturesValid = true; foreach ( PageViewItem * item, d->items ) { foreach ( FormWidgetIface * w, item->formWidgets() ) { if ( w->formField()->type() == Okular::FormField::FormSignature ) { - Okular::SignatureInfo sigInfo = static_cast< SignatureEdit * >( w )->validate(); - if ( sigInfo.signatureStatus() != Okular::SignatureInfo::SignatureValid ) + Okular::SignatureInfo *sigInfo = static_cast< SignatureEdit * >( w )->validate(); + if ( sigInfo->signatureStatus() != Okular::SignatureInfo::SignatureValid ) allSignaturesValid = false; } } } d->aValidateSignatures->setEnabled( false ); emit signatureValidationComplete( allSignaturesValid ); } //END private SLOTS #include "moc_pageview.cpp" /* kate: replace-tabs on; indent-width 4; */ diff --git a/ui/signaturewidgets.cpp b/ui/signaturewidgets.cpp index 700ba65fd..997e8ccff 100644 --- a/ui/signaturewidgets.cpp +++ b/ui/signaturewidgets.cpp @@ -1,228 +1,228 @@ /*************************************************************************** * Copyright (C) 2018 by Chinmoy Ranjan Pradhan * * * * 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 "signaturewidgets.h" #include #include #include #include #include #include #include #include static QString getReadableSigState( Okular::SignatureInfo::SignatureStatus sigStatus ) { switch ( sigStatus ) { case Okular::SignatureInfo::SignatureValid: return i18n("Signature is Valid."); case Okular::SignatureInfo::SignatureInvalid: return i18n("Signature is Invalid."); case Okular::SignatureInfo::SignatureDigestMismatch: return i18n("Digest Mismatch."); case Okular::SignatureInfo::SignatureDecodingError: return i18n("Document isn't signed or corrupted data."); case Okular::SignatureInfo::SignatureNotVerified: return i18n("Signature has not yet been verified."); default: return i18n("Unknown Validation Failure."); } } static QString getReadableCertState( Okular::SignatureInfo::CertificateStatus certStatus ) { switch ( certStatus ) { case Okular::SignatureInfo::CertificateTrusted: return i18n("Certificate is Trusted."); case Okular::SignatureInfo::CertificateUntrustedIssuer: return i18n("Certificate issuer isn't Trusted."); case Okular::SignatureInfo::CertificateUnknownIssuer: return i18n("Certificate issuer is unknown."); case Okular::SignatureInfo::CertificateRevoked: return i18n("Certificate has been Revoked."); case Okular::SignatureInfo::CertificateExpired: return i18n("Certificate has Expired."); case Okular::SignatureInfo::CertificateNotVerified: return i18n("Certificate has not yet been verified."); default: return i18n("Unknown issue with Certificate or corrupted data."); } } static QString getReadableHashAlgorithm( Okular::SignatureInfo::HashAlgorithm hashAlg ) { switch ( hashAlg ) { case Okular::SignatureInfo::HashAlgorithmMd2: return i18n("MD2"); case Okular::SignatureInfo::HashAlgorithmMd5: return i18n("MD5"); case Okular::SignatureInfo::HashAlgorithmSha1: return i18n("SHA1"); case Okular::SignatureInfo::HashAlgorithmSha256: return i18n("SHA256"); case Okular::SignatureInfo::HashAlgorithmSha384: return i18n("SHA384"); case Okular::SignatureInfo::HashAlgorithmSha512: return i18n("SHA512"); case Okular::SignatureInfo::HashAlgorithmSha224: return i18n("SHA224"); default: return i18n("Unknown"); } } -SignaturePropertiesModel::SignaturePropertiesModel( const Okular::SignatureInfo sigInfo, QObject * parent ) +SignaturePropertiesModel::SignaturePropertiesModel( Okular::SignatureInfo *sigInfo, QObject * parent ) : QAbstractTableModel( parent ) { - m_sigProperties.append( qMakePair( i18n("Subject Name"), sigInfo.subjectName() ) ); - m_sigProperties.append( qMakePair( i18n("Subject Distinguished Name"), sigInfo.subjectDN() ) ); - m_sigProperties.append( qMakePair( i18n("Signing Time"), sigInfo.signingTime().toString( QStringLiteral("MMM dd yyyy hh:mm:ss") ) ) ); - m_sigProperties.append( qMakePair( i18n("Hash Algorithm"), getReadableHashAlgorithm( sigInfo.hashAlgorithm() ) ) ); - m_sigProperties.append( qMakePair( i18n("Signature Status"), getReadableSigState( sigInfo.signatureStatus() ) ) ); - m_sigProperties.append( qMakePair( i18n("Certificate Status"), getReadableCertState( sigInfo.certificateStatus() ) ) ); - m_sigProperties.append( qMakePair( i18n("Signature Data"), QString::fromUtf8( sigInfo.signature().toHex(' ') ) ) ); + m_sigProperties.append( qMakePair( i18n("Subject Name"), sigInfo->subjectName() ) ); + m_sigProperties.append( qMakePair( i18n("Subject Distinguished Name"), sigInfo->subjectDN() ) ); + m_sigProperties.append( qMakePair( i18n("Signing Time"), sigInfo->signingTime().toString( QStringLiteral("MMM dd yyyy hh:mm:ss") ) ) ); + m_sigProperties.append( qMakePair( i18n("Hash Algorithm"), getReadableHashAlgorithm( sigInfo->hashAlgorithm() ) ) ); + m_sigProperties.append( qMakePair( i18n("Signature Status"), getReadableSigState( sigInfo->signatureStatus() ) ) ); + m_sigProperties.append( qMakePair( i18n("Certificate Status"), getReadableCertState( sigInfo->certificateStatus() ) ) ); + m_sigProperties.append( qMakePair( i18n("Signature Data"), QString::fromUtf8( sigInfo->signature().toHex(' ') ) ) ); m_sigProperties.append( qMakePair( QStringLiteral("----------"), QString("------Certificate Properties--------") ) ); - Okular::CertificateInfo certInfo = sigInfo.certificateInfo(); + Okular::CertificateInfo certInfo = sigInfo->certificateInfo(); m_sigProperties.append( qMakePair( i18n("Version"), certInfo.version() ) ); m_sigProperties.append( qMakePair( i18n("Issuer Name"), certInfo.issuerName() ) ); m_sigProperties.append( qMakePair( i18n("Issuer Distinguished Name"), certInfo.issuerDN() ) ); m_sigProperties.append( qMakePair( i18n("Serial Number"), certInfo.serialNumber() ) ); m_sigProperties.append( qMakePair( i18n("Validity Start"), certInfo.validityStart().toString( QStringLiteral("MMM dd yyyy hh:mm:ss") ) ) ); m_sigProperties.append( qMakePair( i18n("Validity End"), certInfo.validityEnd().toString( QStringLiteral("MMM dd yyyy hh:mm:ss") ) ) ); m_sigProperties.append( qMakePair( i18n("Public Key"), certInfo.publicKey() ) ); } int SignaturePropertiesModel::columnCount( const QModelIndex &parent ) const { return parent.isValid() ? 0 : 2; } int SignaturePropertiesModel::rowCount( const QModelIndex &parent ) const { return parent.isValid() ? 0 : m_sigProperties.size(); } QVariant SignaturePropertiesModel::data( const QModelIndex &index, int role ) const { int row = index.row(); if ( !index.isValid() || row < 0 || row >= m_sigProperties.count() ) return QVariant(); switch ( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: switch ( index.column() ) { case 0: return m_sigProperties[row].first; case 1: return m_sigProperties[row].second; default: return QString(); } case PropertyValueRole: return m_sigProperties[row].second; } return QVariant(); } QVariant SignaturePropertiesModel::headerData( int section, Qt::Orientation orientation, int role ) const { if ( role == Qt::TextAlignmentRole ) return QVariant( Qt::AlignLeft ); if ( orientation != Qt::Horizontal || role != Qt::DisplayRole) return QVariant(); switch ( section ) { case 0: return i18n("Property"); case 1: return i18n("Value"); default: return QVariant(); } } -SignaturePropertiesDialog::SignaturePropertiesDialog( const Okular::SignatureInfo &sigInfo, QWidget *parent ) +SignaturePropertiesDialog::SignaturePropertiesDialog( Okular::SignatureInfo *sigInfo, QWidget *parent ) : QDialog( parent ), m_sigInfo( sigInfo ) { setModal( true ); setFixedSize( QSize( 450, 500 )); setWindowTitle( i18n("Signature Properties") ); auto sigPropLabel = new QLabel( this ); sigPropLabel->setText( i18n("Signature Properties:") ); auto sigPropTree = new QTreeView( this ); sigPropTree->setIndentation( 0 ); m_sigPropModel = new SignaturePropertiesModel( m_sigInfo, this ); sigPropTree->setModel( m_sigPropModel ); connect( sigPropTree, &QTreeView::clicked, this, &SignaturePropertiesDialog::updateText ); m_sigPropText = new QTextEdit( this ); m_sigPropText->setReadOnly( true ); auto btnBox = new QDialogButtonBox( QDialogButtonBox::Close, this ); btnBox->button( QDialogButtonBox::Close )->setDefault( true ); connect( btnBox, &QDialogButtonBox::rejected, this, &SignatureSummaryDialog::reject ); auto mainLayout = new QVBoxLayout( this ); mainLayout->addWidget( sigPropLabel ); mainLayout->addWidget( sigPropTree ); mainLayout->addWidget( m_sigPropText ); mainLayout->addWidget( btnBox ); setLayout( mainLayout ); } void SignaturePropertiesDialog::updateText( const QModelIndex &index ) { m_sigPropText->setText( m_sigPropModel->data( index, SignaturePropertiesModel::PropertyValueRole ).toString() ); } -SignatureSummaryDialog::SignatureSummaryDialog( const Okular::SignatureInfo &sigInfo, QWidget *parent ) +SignatureSummaryDialog::SignatureSummaryDialog( Okular::SignatureInfo *sigInfo, QWidget *parent ) : QDialog( parent ), m_sigInfo( sigInfo ) { setModal( true ); setFixedSize( QSize(250, 100) ); setWindowTitle( i18n("Signature Validation Status") ); auto sigStatusLabel = new QLabel( this ); - sigStatusLabel->setText( getReadableSigState( m_sigInfo.signatureStatus() ) ); + sigStatusLabel->setText( getReadableSigState( m_sigInfo->signatureStatus() ) ); auto btnBox = new QDialogButtonBox( QDialogButtonBox::Close, this ); auto sigPropBtn = new QPushButton( i18n( "Signature Properties"), this ); btnBox->button( QDialogButtonBox::Close )->setDefault( true ); btnBox->addButton( sigPropBtn, QDialogButtonBox::ActionRole ); connect( btnBox, &QDialogButtonBox::rejected, this, &SignatureSummaryDialog::reject ); connect( sigPropBtn, &QPushButton::clicked, this, &SignatureSummaryDialog::showSignatureProperties ); auto mainLayout = new QVBoxLayout( this ); mainLayout->addWidget( sigStatusLabel ); mainLayout->addWidget( btnBox ); setLayout( mainLayout ); } void SignatureSummaryDialog::showSignatureProperties() { reject(); SignaturePropertiesDialog sigPropDlg( m_sigInfo, this ); sigPropDlg.exec(); } #include "moc_signaturewidgets.cpp" diff --git a/ui/signaturewidgets.h b/ui/signaturewidgets.h index 995eb76f6..0a2ae10c4 100644 --- a/ui/signaturewidgets.h +++ b/ui/signaturewidgets.h @@ -1,75 +1,75 @@ /*************************************************************************** * Copyright (C) 2018 by Chinmoy Ranjan Pradhan * * * * 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_SIGNATUREWIDGETS_H #define OKULAR_SIGNATUREWIDGETS_H #include #include #include #include "core/signatureutils.h" class QTextEdit; namespace Okular { class SignatureInfo; } class SignaturePropertiesModel : public QAbstractTableModel { Q_OBJECT public: - explicit SignaturePropertiesModel( const Okular::SignatureInfo sigInfo, QObject * parent = nullptr ); + explicit SignaturePropertiesModel( Okular::SignatureInfo *sigInfo, QObject * parent = nullptr ); enum { PropertyValueRole = Qt::UserRole }; int columnCount( const QModelIndex &parent = QModelIndex() ) const override; int rowCount( const QModelIndex &parent = QModelIndex() ) const override; QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override; QVariant headerData( int section, Qt::Orientation orientation, int role ) const override; private: QVector< QPair > m_sigProperties; }; class SignaturePropertiesDialog : public QDialog { Q_OBJECT public: - SignaturePropertiesDialog( const Okular::SignatureInfo &sigInfo, QWidget *parent ); + SignaturePropertiesDialog( Okular::SignatureInfo *sigInfo, QWidget *parent ); private Q_SLOTS: void updateText( const QModelIndex &index ); private: SignaturePropertiesModel *m_sigPropModel; QTextEdit *m_sigPropText; - Okular::SignatureInfo m_sigInfo; + Okular::SignatureInfo *m_sigInfo; }; class SignatureSummaryDialog : public QDialog { Q_OBJECT public: - SignatureSummaryDialog( const Okular::SignatureInfo &sigInfo, QWidget *parent ); + SignatureSummaryDialog( Okular::SignatureInfo *sigInfo, QWidget *parent ); private Q_SLOTS: void showSignatureProperties(); private: - Okular::SignatureInfo m_sigInfo; + Okular::SignatureInfo *m_sigInfo; }; #endif