diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -386,6 +386,8 @@ ui/layers.cpp ui/signatureguiutils.cpp ui/signaturepropertiesdialog.cpp + ui/signaturemodel.cpp + ui/signaturepanel.cpp ) if (Qt5TextToSpeech_FOUND) diff --git a/part.h b/part.h --- a/part.h +++ b/part.h @@ -74,6 +74,7 @@ class BookmarkList; class DrawingToolActions; class Layers; +class SignaturePanel; #if PURPOSE_FOUND namespace Purpose { class Menu; } @@ -323,6 +324,7 @@ QPointer m_reviewsWidget; QPointer m_bookmarkList; QPointer m_layers; + QPointer m_panel; // document watcher (and reloader) variables KDirWatch *m_watcher; diff --git a/part.cpp b/part.cpp --- a/part.cpp +++ b/part.cpp @@ -106,6 +106,7 @@ #include "ui/guiutils.h" #include "ui/layers.h" #include "ui/okmenutitle.h" +#include "ui/signaturepanel.h" #include "conf/preferencesdialog.h" #include "settings.h" #include "core/action.h" @@ -451,6 +452,11 @@ m_sidebar->addItem( m_bookmarkList, QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks") ); m_sidebar->setItemEnabled( m_bookmarkList, false ); + // [left toolbox: Signature Panel] | [] + m_panel = new SignaturePanel( nullptr, m_document ); + m_sidebar->addItem( m_panel, QIcon::fromTheme(QStringLiteral("application-pkcs7-signature")), i18n("Signatures") ); + m_sidebar->setItemEnabled( m_panel, false ); + // widgets: [../miniBarContainer] | [] #ifdef OKULAR_ENABLE_MINIBAR QWidget * miniBarContainer = new QWidget( 0 ); @@ -516,6 +522,7 @@ connect( m_pageView.data(), &PageView::fitWindowToPage, this, &Part::fitWindowToPage ); rightLayout->addWidget( m_pageView ); m_layers->setPageView( m_pageView ); + m_panel->setPageView( m_pageView ); m_findBar = new FindBar( m_document, rightContainer ); rightLayout->addWidget( m_findBar ); m_bottomBar = new QWidget( rightContainer ); @@ -554,6 +561,7 @@ m_document->addObserver( m_reviewsWidget ); m_document->addObserver( m_pageSizeLabel ); m_document->addObserver( m_bookmarkList ); + m_document->addObserver( m_panel ); connect( m_document->bookmarkManager(), &BookmarkManager::saved, this, &Part::slotRebuildBookmarkMenu ); @@ -941,6 +949,7 @@ delete m_reviewsWidget; delete m_bookmarkList; delete m_infoTimer; + delete m_panel; delete m_document; @@ -3374,6 +3383,7 @@ m_sidebar->setItemEnabled( m_reviewsWidget, true ); m_sidebar->setItemEnabled( m_bookmarkList, true ); + m_sidebar->setItemEnabled( m_panel, true ); m_sidebar->setSidebarVisibility( Okular::Settings::showLeftPanel() ); // add back and next in history diff --git a/ui/formwidgets.h b/ui/formwidgets.h --- a/ui/formwidgets.h +++ b/ui/formwidgets.h @@ -355,6 +355,12 @@ public: explicit SignatureEdit( Okular::FormFieldSignature * signature, QWidget * parent = nullptr ); + // This will be called when an item in signature panel is clicked. Calling it will change this + // widget's state for sometime. If this widget was visible prior to calling this then background + // color will change and borders will remain otherwise visibility of this widget will change. + // During the change all interactions will be disabled. + void setDummyMode( bool set ); + protected: bool event( QEvent * e ) override; void contextMenuEvent( QContextMenuEvent * event ) override; @@ -365,7 +371,9 @@ void slotViewProperties(); private: - bool m_lefMouseButtonPressed; + bool m_widgetPressed; + bool m_dummyMode; + bool m_wasVisible; // this will help in deciding whether or not to paint border for this widget DECLARE_ADDITIONAL_ACTIONS }; diff --git a/ui/formwidgets.cpp b/ui/formwidgets.cpp --- a/ui/formwidgets.cpp +++ b/ui/formwidgets.cpp @@ -1060,32 +1060,60 @@ } SignatureEdit::SignatureEdit( Okular::FormFieldSignature * signature, QWidget * parent ) - : QAbstractButton( parent ), FormWidgetIface( this, signature ), m_lefMouseButtonPressed( false ) + : QAbstractButton( parent ), FormWidgetIface( this, signature ), + m_widgetPressed( false ), m_dummyMode( false ), m_wasVisible( false ) { setCheckable( false ); setCursor( Qt::PointingHandCursor ); connect( this, &SignatureEdit::clicked, this, &SignatureEdit::slotViewProperties ); } +void SignatureEdit::setDummyMode( bool set ) +{ + m_dummyMode = set; + if ( m_dummyMode ) + { + m_wasVisible = isVisible(); + //if widget was hidden then show it. + //even if it wasn't hidden calling this will still update the background. + setVisibility( true ); + } + else + { + //forms were not visible before this call so hide this widget. + if ( !m_wasVisible ) + setVisibility( false ); + //forms were visible even before this call so only update the background color. + else + update(); + } +} + bool SignatureEdit::event( QEvent * e ) { + if ( m_dummyMode && e->type() != QEvent::Paint ) + { + e->accept(); + return true; + } + switch ( e->type() ) { case QEvent::MouseButtonPress: { QMouseEvent *ev = static_cast< QMouseEvent * >( e ); if ( ev->button() == Qt::LeftButton ) { - m_lefMouseButtonPressed = true; + m_widgetPressed = true; update(); } mousePressEvent( ev ); break; } case QEvent::MouseButtonRelease: { QMouseEvent *ev = static_cast< QMouseEvent * >( e ); - m_lefMouseButtonPressed = false; + m_widgetPressed = false; if ( ev->button() == Qt::LeftButton) { update(); @@ -1116,8 +1144,17 @@ void SignatureEdit::paintEvent( QPaintEvent * ) { QPainter painter( this ); - painter.setPen( Qt::black ); - if ( m_lefMouseButtonPressed ) + //no borders when user hasn't allowed the forms to be shown + if ( m_dummyMode && !m_wasVisible ) + { + painter.setPen( Qt::transparent ); + } + else + { + painter.setPen( Qt::black ); + } + + if ( m_widgetPressed || m_dummyMode ) { QColor col = palette().color( QPalette::Active, QPalette::Highlight ); col.setAlpha(50); @@ -1132,6 +1169,9 @@ void SignatureEdit::slotViewRevision() { + if ( m_dummyMode ) + return; + QByteArray revisionData; Okular::FormFieldSignature *formSignature = static_cast< Okular::FormFieldSignature * >( formField() ); m_controller->m_doc->requestSignedRevisionData( formSignature->validate(), &revisionData ); @@ -1141,6 +1181,9 @@ void SignatureEdit::slotViewProperties() { + if ( m_dummyMode ) + return; + Okular::FormFieldSignature *formSignature = static_cast< Okular::FormFieldSignature * >( formField() ); SignaturePropertiesDialog propDlg( m_controller->m_doc, formSignature, this ); propDlg.exec(); diff --git a/ui/pageview.h b/ui/pageview.h --- a/ui/pageview.h +++ b/ui/pageview.h @@ -117,6 +117,8 @@ void updateCursor(); + void highlightSignatureFormWidget( int formId ); + public Q_SLOTS: void copyTextSelection() const; void selectAll(); diff --git a/ui/pageview.cpp b/ui/pageview.cpp --- a/ui/pageview.cpp +++ b/ui/pageview.cpp @@ -5530,6 +5530,33 @@ emit fitWindowToPage( viewportSize, pageSize ); } +void PageView::highlightSignatureFormWidget( int formId ) +{ + QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); + for ( ; dIt != dEnd; ++dIt ) + { + foreach ( auto fw, (*dIt)->formWidgets() ) + { + if ( fw->formField()->id() == formId ) + { + SignatureEdit *widget = static_cast< SignatureEdit * >( fw ); + if ( !widget ) + return; + widget->setDummyMode( true ); + QTimer *timer = new QTimer( this ); + timer->setSingleShot( true ); + timer->start( 250 ); + connect(timer, &QTimer::timeout, this, [=]{ + widget->setDummyMode( false ); + timer->stop(); + delete timer; + }); + return; + } + } + } +} + //END private SLOTS #include "moc_pageview.cpp" diff --git a/ui/signaturemodel.h b/ui/signaturemodel.h new file mode 100644 --- /dev/null +++ b/ui/signaturemodel.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * Copyright (C) 2018 by Chinmoy Ranjan Pradhan + +namespace Okular { +class Document; +} + +class SignatureModelPrivate; + +class SignatureModel : public QAbstractItemModel +{ + Q_OBJECT + + public: + enum { + FormIdRole = Qt::UserRole + 1000, + PageRole + }; + + explicit SignatureModel( Okular::Document *doc, QObject *parent = nullptr ); + virtual ~SignatureModel(); + + int columnCount( const QModelIndex &parent = QModelIndex() ) const override; + QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override; + bool hasChildren( const QModelIndex &parent = QModelIndex() ) const override; + QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const override; + QModelIndex parent( const QModelIndex &index ) const override; + int rowCount( const QModelIndex &parent = QModelIndex() ) const override; + + private: + Q_DECLARE_PRIVATE( SignatureModel ) + QScopedPointer d_ptr; +}; + + + +#endif diff --git a/ui/signaturemodel.cpp b/ui/signaturemodel.cpp new file mode 100644 --- /dev/null +++ b/ui/signaturemodel.cpp @@ -0,0 +1,286 @@ +/*************************************************************************** + * Copyright (C) 2018 by Chinmoy Ranjan Pradhan + +#include +#include +#include + +#include "core/document.h" +#include "core/observer.h" +#include "core/page.h" +#include "core/form.h" +#include "core/signatureutils.h" + +struct SignatureItem +{ + enum DataType + { + Null, + RevisionInfo, + ValidityStatus, + SigningTime, + Reason, + FieldInfo + }; + + SignatureItem(); + SignatureItem( SignatureItem *parent, Okular::FormFieldSignature *form, DataType type, int page ); + ~SignatureItem(); + + QVector children; + SignatureItem *parent; + Okular::FormFieldSignature *form; + QString displayString; + DataType type; + int page; +}; + +SignatureItem::SignatureItem() + : parent( nullptr ), form( nullptr ), type( Null ), page( -1 ) +{ +} + +SignatureItem::SignatureItem( SignatureItem *_parent, Okular::FormFieldSignature *_form , DataType _type, int _page ) + : parent( _parent ), form( _form ), type( _type ), page( _page ) +{ + Q_ASSERT( parent ); + parent->children.append( this ); +} + +SignatureItem::~SignatureItem() +{ + qDeleteAll( children ); +} + +class SignatureModelPrivate : public Okular::DocumentObserver +{ +public: + SignatureModelPrivate( SignatureModel *qq ); + ~SignatureModelPrivate() override; + + void notifySetup( const QVector &pages, int setupFlags ) override; + + QModelIndex indexForItem( SignatureItem *item ) const; + void rebuildTree( const QVector &pages ); + + SignatureModel *q; + SignatureItem *root; + QPointer document; +}; + +SignatureModelPrivate::SignatureModelPrivate( SignatureModel *qq ) + : q( qq ), root( new SignatureItem ) +{ +} + +SignatureModelPrivate::~SignatureModelPrivate() +{ + delete root; +} + +static void updateFormFieldSignaturePointer( SignatureItem *item, const QVector &pages ) +{ + if ( item->form ) + { + foreach ( Okular::FormField *f, pages[item->page]->formFields() ) + { + if ( item->form->id() == f->id() ) + { + item->form = static_cast( f ); + break; + } + } + if ( !item->form ) + qWarning() << "Lost signature form field, something went wrong"; + } + + foreach ( SignatureItem *child, item->children ) + updateFormFieldSignaturePointer( child, pages ); +} + +void SignatureModelPrivate::notifySetup( const QVector &pages, int setupFlags ) +{ + if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) ) + { + if ( setupFlags & Okular::DocumentObserver::UrlChanged ) + { + updateFormFieldSignaturePointer( root, pages ); + } + return; + } + + q->beginResetModel(); + qDeleteAll( root->children ); + root->children.clear(); + rebuildTree( pages ); + q->endResetModel(); +} + +QModelIndex SignatureModelPrivate::indexForItem( SignatureItem *item ) const +{ + if ( item->parent ) + { + int index = item->parent->children.indexOf( item ); + if ( index >= 0 && index < item->parent->children.count() ) + return q->createIndex( index, 0, item ); + } + return QModelIndex(); +} + +void SignatureModelPrivate::rebuildTree( const QVector &pages ) +{ + if ( pages.isEmpty() ) + return; + + emit q->layoutAboutToBeChanged(); + foreach ( auto page, pages ) + { + const int currentPage = page->number(); + // get form fields page by page so that page number and index of the form can be determined. + QVector signatureFormFields = SignatureGuiUtils::getSignatureFormFields( document, false, currentPage ); + if ( signatureFormFields.isEmpty() ) + continue; + + for ( int i = 0; i < signatureFormFields.count(); i++ ) + { + Okular::FormFieldSignature *sf = signatureFormFields[i]; + Okular::SignatureInfo *info = sf->validate(); + + // based on whether or not signature form is a nullptr it is decided if clicking on an item should change the viewport. + auto *parentItem = new SignatureItem( root, sf, SignatureItem::RevisionInfo, currentPage ); + parentItem->displayString = i18n( "Rev. %1: Signed By %2", i+1, info->signerName() ); + + auto childItem1 = new SignatureItem( parentItem, nullptr, SignatureItem::ValidityStatus, currentPage ); + childItem1->displayString = SignatureGuiUtils::getReadableSignatureStatus( info->signatureStatus() ); + + auto childItem2 = new SignatureItem( parentItem, nullptr, SignatureItem::SigningTime, currentPage ); + childItem2->displayString = i18n("Signing Time: %1", info->signingTime().toString( QStringLiteral("MMM dd yyyy hh:mm:ss") ) ); + + auto childItem3 = new SignatureItem( parentItem, nullptr, SignatureItem::Reason, currentPage ); + childItem3->displayString = i18n("Reason: %1", !info->reason().isEmpty() ? info->reason() : i18n("Not Available") ); + + auto childItem4 = new SignatureItem( parentItem, sf, SignatureItem::FieldInfo, currentPage ); + childItem4->displayString = i18n("Field: %1 on page %2", sf->name(), currentPage+1 ); + + } + } + emit q->layoutChanged(); +} + +SignatureModel::SignatureModel( Okular::Document *doc, QObject *parent ) + : QAbstractItemModel( parent ), d_ptr( new SignatureModelPrivate( this ) ) +{ + Q_D( SignatureModel ); + d->document = doc; + d->document->addObserver( d ); +} + +SignatureModel::~SignatureModel() +{ + Q_D( SignatureModel ); + d->document->removeObserver( d ); +} + +int SignatureModel::columnCount( const QModelIndex & ) const +{ + return 1; +} + +QVariant SignatureModel::data( const QModelIndex &index, int role ) const +{ + Q_D( const SignatureModel ); + + if ( !index.isValid() ) + return QVariant(); + + SignatureItem *item = static_cast( index.internalPointer() ); + if ( item == d->root ) + return QVariant(); + + switch ( role ) + { + case Qt::DisplayRole: + case Qt::ToolTipRole: + return item->displayString; + case Qt::DecorationRole: + if ( item->type == SignatureItem::RevisionInfo ) + { + Okular::SignatureInfo *info = item->form->validate(); + Okular::SignatureInfo::SignatureStatus signatureStatus = info->signatureStatus(); + switch ( signatureStatus ) + { + case Okular::SignatureInfo::SignatureValid: + return QIcon::fromTheme( QStringLiteral("dialog-ok") ); + case Okular::SignatureInfo::SignatureInvalid: + return QIcon::fromTheme( QStringLiteral("dialog-close") ); + case Okular::SignatureInfo::SignatureDigestMismatch: + return QIcon::fromTheme( QStringLiteral("dialog-warning") ); + default: + return QIcon::fromTheme( QStringLiteral("dialog-question") ); + + } + } + return QIcon(); + case FormIdRole: + return item->form ? item->form->id() : -1; + case PageRole: + return item->page; + } + + return QVariant(); +} + +bool SignatureModel::hasChildren( const QModelIndex &parent ) const +{ + if ( !parent.isValid() ) + return true; + + SignatureItem *item = static_cast( parent.internalPointer() ); + return !item->children.isEmpty(); +} + +QModelIndex SignatureModel::index( int row, int column, const QModelIndex &parent ) const +{ + Q_D( const SignatureModel ); + + if ( row < 0 || column != 0 ) + return QModelIndex(); + + SignatureItem *item = parent.isValid() ? static_cast( parent.internalPointer() ) : d->root; + if ( row < item->children.count() ) + return createIndex( row, column, item->children.at( row ) ); + + return QModelIndex(); +} + +QModelIndex SignatureModel::parent( const QModelIndex &index ) const +{ + Q_D( const SignatureModel ); + + if ( !index.isValid() ) + return QModelIndex(); + + SignatureItem *item = static_cast( index.internalPointer() ); + return d->indexForItem( item->parent ); +} + +int SignatureModel::rowCount( const QModelIndex &parent ) const +{ + Q_D( const SignatureModel ); + + SignatureItem *item = parent.isValid() ? static_cast( parent.internalPointer() ) : d->root; + return item->children.count(); +} + +#include "moc_signaturemodel.cpp" diff --git a/ui/signaturepanel.h b/ui/signaturepanel.h new file mode 100644 --- /dev/null +++ b/ui/signaturepanel.h @@ -0,0 +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_SIGNATUREPANEL_H +#define OKULAR_SIGNATUREPANEL_H + +#include +#include + +#include "core/observer.h" + +namespace Okular { +class Document; +class FormFieldSignature; +} + +class QTreeView; +class SignatureModel; +class PageView; + +class SignaturePanelPrivate; + +class SignaturePanel : public QWidget, public Okular::DocumentObserver +{ + Q_OBJECT + public: + SignaturePanel( QWidget *parent, Okular::Document *document ); + ~SignaturePanel(); + + // inherited from DocumentObserver + void notifySetup( const QVector &pages, int setupFlags ) override; + + void setPageView( PageView *pv ); + + private Q_SLOTS: + void activated( const QModelIndex& ); + void slotShowContextMenu(); + void slotViewRevision(); + void slotViewProperties(); + + private: + Q_DECLARE_PRIVATE( SignaturePanel ) + QScopedPointer d_ptr; +}; + +#endif diff --git a/ui/signaturepanel.cpp b/ui/signaturepanel.cpp new file mode 100644 --- /dev/null +++ b/ui/signaturepanel.cpp @@ -0,0 +1,152 @@ +/*************************************************************************** + * 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 "signaturepanel.h" + +#include "pageview.h" +#include "signatureguiutils.h" +#include "signaturemodel.h" +#include "revisionviewer.h" +#include "signaturepropertiesdialog.h" + +#include + +#include +#include +#include +#include +#include + +#include "core/form.h" +#include "core/document.h" + +class SignaturePanelPrivate +{ + public: + Okular::FormFieldSignature *getSignatureFromId( int formId ); + + QHash m_signatureForms; + Okular::Document *m_document; + Okular::FormFieldSignature *m_currentForm; + QTreeView *m_view; + SignatureModel *m_model; + PageView *m_pageView; +}; + +SignaturePanel::SignaturePanel( QWidget *parent, Okular::Document *document ) + : QWidget( parent ), d_ptr( new SignaturePanelPrivate ) +{ + Q_D( SignaturePanel ); + d->m_view = new QTreeView( this ); + d->m_view->setAlternatingRowColors( true ); + d->m_view->setSelectionMode( QAbstractItemView::ExtendedSelection ); + d->m_view->header()->hide(); + + d->m_document = document; + d->m_model = new SignatureModel( d->m_document, this ); + + d->m_view->setModel( d->m_model ); + connect( d->m_view->selectionModel(), &QItemSelectionModel::currentChanged, this, &SignaturePanel::activated ); + connect( d->m_view, &QTreeView::pressed, this, &SignaturePanel::slotShowContextMenu ); + + auto vLayout = new QVBoxLayout( this ); + vLayout->setMargin( 0 ); + vLayout->setSpacing( 6 ); + vLayout->addWidget( d->m_view ); +} + +Okular::FormFieldSignature *SignaturePanelPrivate::getSignatureFromId( int formId ) +{ + if ( formId == -1 ) + return nullptr; + return m_signatureForms[formId]; +} + +void SignaturePanel::activated( const QModelIndex &index ) +{ + Q_D( SignaturePanel ); + int formId = d->m_model->data( index, SignatureModel::FormIdRole ).toInt(); + d->m_currentForm = d->getSignatureFromId( formId ); + if ( !d->m_currentForm ) + return; + Okular::NormalizedRect nr = d->m_currentForm->rect(); + Okular::DocumentViewport vp; + vp.pageNumber = d->m_model->data( index, SignatureModel::PageRole ).toInt(); + vp.rePos.enabled = true; + vp.rePos.pos = Okular::DocumentViewport::Center; + vp.rePos.normalizedX = ( nr.right + nr.left ) / 2.0; + vp.rePos.normalizedY = ( nr.bottom + nr.top ) / 2.0; + d->m_document->setViewport( vp, nullptr ); + d->m_pageView->highlightSignatureFormWidget( formId ); +} + +void SignaturePanel::slotShowContextMenu() +{ + Q_D( SignaturePanel ); + if ( !d->m_currentForm ) + return; + + Qt::MouseButtons buttons = QApplication::mouseButtons(); + if ( buttons == Qt::RightButton ) + { + QMenu *menu = new QMenu( this ); + QAction *sigRev = new QAction( i18n("View Revision"), menu ); + QAction *sigProp = new QAction( i18n("View Properties"), menu ); + connect( sigRev, &QAction::triggered, this, &SignaturePanel::slotViewRevision ); + connect( sigProp, &QAction::triggered, this, &SignaturePanel::slotViewProperties ); + menu->addAction( sigRev ); + menu->addAction( sigProp ); + menu->exec( QCursor::pos() ); + delete menu; + } +} + +void SignaturePanel::slotViewRevision() +{ + Q_D( SignaturePanel ); + QByteArray data; + d->m_document->requestSignedRevisionData( d->m_currentForm->validate(), &data ); + RevisionViewer viewer( data, this ); + viewer.viewRevision(); +} + +void SignaturePanel::slotViewProperties() +{ + Q_D( SignaturePanel ); + SignaturePropertiesDialog propDlg( d->m_document, d->m_currentForm, this ); + propDlg.exec(); +} + +void SignaturePanel::notifySetup( const QVector &/*pages*/, int setupFlags ) +{ + if ( !( setupFlags & Okular::DocumentObserver::UrlChanged ) ) + return; + + Q_D( SignaturePanel ); + QVector signatureForms = SignatureGuiUtils::getSignatureFormFields( d->m_document, true, -1 ); + foreach ( auto sf, signatureForms ) + { + d->m_signatureForms[sf->id()] = sf; + } +} + +SignaturePanel::~SignaturePanel() +{ + Q_D( SignaturePanel ); + d->m_document->removeObserver( this ); + delete d->m_model; +} + +void SignaturePanel::setPageView( PageView *pv ) +{ + Q_D( SignaturePanel ); + d->m_pageView = pv; +} + +#include "moc_signaturepanel.cpp"