2020-01-18 11:38:54 +01:00
// Copyright (C) 2019-2020 Jakub Melka
2019-11-28 18:20:32 +01:00
//
// This file is part of PdfForQt.
//
// PdfForQt is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PdfForQt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
# include "pdfsidebarwidget.h"
# include "ui_pdfsidebarwidget.h"
2019-12-07 17:59:03 +01:00
# include "pdfwidgetutils.h"
2020-02-18 20:21:18 +01:00
# include "pdftexttospeech.h"
2019-12-07 17:59:03 +01:00
2019-11-28 18:20:32 +01:00
# include "pdfdocument.h"
# include "pdfitemmodels.h"
2019-12-03 19:24:43 +01:00
# include "pdfexception.h"
2019-12-08 19:20:36 +01:00
# include "pdfdrawspacecontroller.h"
2019-12-03 19:24:43 +01:00
# include <QMenu>
# include <QAction>
# include <QFileDialog>
# include <QStandardPaths>
# include <QMessageBox>
2019-12-04 19:18:40 +01:00
# include <QPainter>
2019-11-28 18:20:32 +01:00
namespace pdfviewer
{
2019-12-04 19:18:40 +01:00
constexpr const char * STYLESHEET =
" QPushButton { background-color: #404040; color: #FFFFFF; } "
" QPushButton:disabled { background-color: #404040; color: #000000; } "
" QPushButton:checked { background-color: #808080; color: #FFFFFF; } "
2019-12-07 17:59:03 +01:00
" QWidget#thumbnailsToolbarWidget { background-color: #F0F0F0 } "
2020-02-18 20:21:18 +01:00
" QWidget#speechPage { background-color: #F0F0F0 } "
2019-12-04 19:18:40 +01:00
" QWidget#PDFSidebarWidget { background-color: #404040; background: green;} " ;
2020-02-18 20:21:18 +01:00
PDFSidebarWidget : : PDFSidebarWidget ( pdf : : PDFDrawWidgetProxy * proxy , PDFTextToSpeech * textToSpeech , QWidget * parent ) :
2019-11-28 18:20:32 +01:00
QWidget ( parent ) ,
ui ( new Ui : : PDFSidebarWidget ) ,
2019-12-08 19:20:36 +01:00
m_proxy ( proxy ) ,
2020-02-18 20:21:18 +01:00
m_textToSpeech ( textToSpeech ) ,
2019-11-28 18:20:32 +01:00
m_outlineTreeModel ( nullptr ) ,
2019-12-07 17:59:03 +01:00
m_thumbnailsModel ( nullptr ) ,
2019-11-28 18:20:32 +01:00
m_optionalContentTreeModel ( nullptr ) ,
m_document ( nullptr ) ,
2019-12-01 18:10:11 +01:00
m_optionalContentActivity ( nullptr ) ,
m_attachmentsTreeModel ( nullptr )
2019-11-28 18:20:32 +01:00
{
ui - > setupUi ( this ) ;
2019-12-04 19:18:40 +01:00
setStyleSheet ( STYLESHEET ) ;
2019-11-28 18:20:32 +01:00
// Outline
2019-11-29 19:10:29 +01:00
QIcon bookmarkIcon ( " :/resources/bookmark.svg " ) ;
m_outlineTreeModel = new pdf : : PDFOutlineTreeItemModel ( qMove ( bookmarkIcon ) , this ) ;
2019-11-28 18:20:32 +01:00
ui - > bookmarksTreeView - > setModel ( m_outlineTreeModel ) ;
ui - > bookmarksTreeView - > header ( ) - > hide ( ) ;
2019-11-29 19:10:29 +01:00
connect ( ui - > bookmarksTreeView , & QTreeView : : clicked , this , & PDFSidebarWidget : : onOutlineItemClicked ) ;
2019-11-28 18:20:32 +01:00
2019-12-07 17:59:03 +01:00
// Thumbnails
m_thumbnailsModel = new pdf : : PDFThumbnailsItemModel ( proxy , this ) ;
int thumbnailsMargin = ui - > thumbnailsListView - > style ( ) - > pixelMetric ( QStyle : : PM_FocusFrameHMargin , nullptr , ui - > thumbnailsListView ) + 1 ;
int thumbnailsFontSize = QFontMetrics ( ui - > thumbnailsListView - > font ( ) ) . lineSpacing ( ) ;
m_thumbnailsModel - > setExtraItemSizeHint ( 2 * thumbnailsMargin , thumbnailsMargin + thumbnailsFontSize ) ;
ui - > thumbnailsListView - > setModel ( m_thumbnailsModel ) ;
connect ( ui - > thumbnailsSizeSlider , & QSlider : : valueChanged , this , & PDFSidebarWidget : : onThumbnailsSizeChanged ) ;
2019-12-08 19:20:36 +01:00
connect ( ui - > thumbnailsListView , & QListView : : clicked , this , & PDFSidebarWidget : : onThumbnailClicked ) ;
2019-12-07 17:59:03 +01:00
onThumbnailsSizeChanged ( ui - > thumbnailsSizeSlider - > value ( ) ) ;
2019-11-28 18:20:32 +01:00
// Optional content
ui - > optionalContentTreeView - > header ( ) - > hide ( ) ;
m_optionalContentTreeModel = new pdf : : PDFOptionalContentTreeItemModel ( this ) ;
ui - > optionalContentTreeView - > setModel ( m_optionalContentTreeModel ) ;
2019-12-01 18:10:11 +01:00
// Attachments
ui - > attachmentsTreeView - > header ( ) - > hide ( ) ;
m_attachmentsTreeModel = new pdf : : PDFAttachmentsTreeItemModel ( this ) ;
ui - > attachmentsTreeView - > setModel ( m_attachmentsTreeModel ) ;
ui - > attachmentsTreeView - > setSelectionMode ( QAbstractItemView : : SingleSelection ) ;
2019-12-03 19:24:43 +01:00
ui - > attachmentsTreeView - > setSelectionBehavior ( QAbstractItemView : : SelectItems ) ;
ui - > attachmentsTreeView - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
connect ( ui - > attachmentsTreeView , & QTreeView : : customContextMenuRequested , this , & PDFSidebarWidget : : onAttachmentCustomContextMenuRequested ) ;
2019-12-01 18:10:11 +01:00
2019-11-28 18:20:32 +01:00
m_pageInfo [ Invalid ] = { nullptr , ui - > emptyPage } ;
m_pageInfo [ OptionalContent ] = { ui - > optionalContentButton , ui - > optionalContentPage } ;
m_pageInfo [ Bookmarks ] = { ui - > bookmarksButton , ui - > bookmarksPage } ;
m_pageInfo [ Thumbnails ] = { ui - > thumbnailsButton , ui - > thumbnailsPage } ;
2019-12-01 18:10:11 +01:00
m_pageInfo [ Attachments ] = { ui - > attachmentsButton , ui - > attachmentsPage } ;
2020-02-18 20:21:18 +01:00
m_pageInfo [ Speech ] = { ui - > speechButton , ui - > speechPage } ;
2019-11-28 18:20:32 +01:00
2019-12-07 17:59:03 +01:00
for ( const auto & pageInfo : m_pageInfo )
{
if ( pageInfo . second . button )
{
connect ( pageInfo . second . button , & QPushButton : : clicked , this , & PDFSidebarWidget : : onPageButtonClicked ) ;
}
}
2020-02-18 20:21:18 +01:00
m_textToSpeech - > initializeUI ( ui - > speechLocaleComboBox , ui - > speechVoiceComboBox ,
ui - > speechRateEdit , ui - > speechPitchEdit , ui - > speechVolumeEdit ,
2020-02-20 19:25:32 +01:00
ui - > speechPlayButton , ui - > speechPauseButton , ui - > speechStopButton , ui - > speechSynchronizeButton ,
ui - > speechRateValueLabel , ui - > speechPitchValueLabel , ui - > speechVolumeValueLabel ,
ui - > speechActualTextEdit ) ;
2020-02-18 20:21:18 +01:00
2019-11-28 18:20:32 +01:00
selectPage ( Invalid ) ;
updateButtons ( ) ;
}
PDFSidebarWidget : : ~ PDFSidebarWidget ( )
{
delete ui ;
}
void PDFSidebarWidget : : setDocument ( const pdf : : PDFDocument * document , pdf : : PDFOptionalContentActivity * optionalContentActivity )
{
m_document = document ;
m_optionalContentActivity = optionalContentActivity ;
// Update outline
m_outlineTreeModel - > setDocument ( document ) ;
2019-12-07 17:59:03 +01:00
// Thumbnails
m_thumbnailsModel - > setDocument ( document ) ;
2019-11-28 18:20:32 +01:00
// Update optional content
m_optionalContentTreeModel - > setDocument ( document ) ;
m_optionalContentTreeModel - > setActivity ( m_optionalContentActivity ) ;
ui - > optionalContentTreeView - > expandAll ( ) ;
2019-12-01 18:10:11 +01:00
// Update attachments
m_attachmentsTreeModel - > setDocument ( document ) ;
ui - > attachmentsTreeView - > expandAll ( ) ;
ui - > attachmentsTreeView - > resizeColumnToContents ( 0 ) ;
2019-11-28 18:20:32 +01:00
Page preferred = Invalid ;
if ( m_document )
{
2019-11-29 19:10:29 +01:00
const pdf : : PageMode pageMode = m_document - > getCatalog ( ) - > getPageMode ( ) ;
2019-11-28 18:20:32 +01:00
switch ( pageMode )
{
2019-11-29 19:10:29 +01:00
case pdf : : PageMode : : UseOutlines :
2019-11-28 18:20:32 +01:00
preferred = Bookmarks ;
break ;
2019-11-29 19:10:29 +01:00
case pdf : : PageMode : : UseThumbnails :
2019-11-28 18:20:32 +01:00
preferred = Thumbnails ;
break ;
2019-11-29 19:10:29 +01:00
case pdf : : PageMode : : UseOptionalContent :
2019-11-28 18:20:32 +01:00
preferred = OptionalContent ;
break ;
2019-12-01 18:10:11 +01:00
case pdf : : PageMode : : UseAttachments :
preferred = Attachments ;
break ;
2019-12-07 17:59:03 +01:00
case pdf : : PageMode : : Fullscreen :
{
pdf : : PDFViewerPreferences : : NonFullScreenPageMode nonFullscreenPageMode = m_document - > getCatalog ( ) - > getViewerPreferences ( ) - > getNonFullScreenPageMode ( ) ;
switch ( nonFullscreenPageMode )
{
case pdf : : PDFViewerPreferences : : NonFullScreenPageMode : : UseOutline :
preferred = Bookmarks ;
break ;
case pdf : : PDFViewerPreferences : : NonFullScreenPageMode : : UseThumbnails :
preferred = Thumbnails ;
break ;
case pdf : : PDFViewerPreferences : : NonFullScreenPageMode : : UseOptionalContent :
preferred = OptionalContent ;
break ;
default :
break ;
}
break ;
}
2019-11-28 18:20:32 +01:00
default :
break ;
}
}
// Update GUI
updateGUI ( preferred ) ;
updateButtons ( ) ;
}
bool PDFSidebarWidget : : isEmpty ( ) const
{
for ( int i = _BEGIN ; i < _END ; + + i )
{
if ( ! isEmpty ( static_cast < Page > ( i ) ) )
{
return false ;
}
}
return true ;
}
bool PDFSidebarWidget : : isEmpty ( Page page ) const
{
switch ( page )
{
case Invalid :
return true ;
case Bookmarks :
return m_outlineTreeModel - > isEmpty ( ) ;
case Thumbnails :
2019-12-07 17:59:03 +01:00
return m_thumbnailsModel - > isEmpty ( ) ;
2019-11-28 18:20:32 +01:00
2019-12-01 18:10:11 +01:00
case OptionalContent :
return m_optionalContentTreeModel - > isEmpty ( ) ;
case Attachments :
return m_attachmentsTreeModel - > isEmpty ( ) ;
2020-02-18 20:21:18 +01:00
case Speech :
return ! m_textToSpeech - > isValid ( ) ;
2019-11-28 18:20:32 +01:00
default :
Q_ASSERT ( false ) ;
break ;
}
return true ;
}
void PDFSidebarWidget : : selectPage ( Page page )
{
// Switch state of the buttons and select the page
for ( const auto & pageInfo : m_pageInfo )
{
if ( pageInfo . second . button )
{
pageInfo . second . button - > setChecked ( pageInfo . first = = page ) ;
}
if ( pageInfo . first = = page )
{
ui - > stackedWidget - > setCurrentWidget ( pageInfo . second . page ) ;
}
}
}
std : : vector < PDFSidebarWidget : : Page > PDFSidebarWidget : : getValidPages ( ) const
{
std : : vector < PDFSidebarWidget : : Page > result ;
result . reserve ( _END - _BEGIN + 1 ) ;
for ( int i = _BEGIN ; i < _END ; + + i )
{
if ( ! isEmpty ( static_cast < Page > ( i ) ) )
{
result . push_back ( static_cast < Page > ( i ) ) ;
}
}
return result ;
}
2019-12-08 19:20:36 +01:00
void PDFSidebarWidget : : setCurrentPages ( const std : : vector < pdf : : PDFInteger > & currentPages )
{
if ( ! currentPages . empty ( ) & & ui - > synchronizeThumbnailsButton - > isChecked ( ) )
{
QModelIndex index = m_thumbnailsModel - > index ( currentPages . front ( ) , 0 , QModelIndex ( ) ) ;
if ( index . isValid ( ) )
{
ui - > thumbnailsListView - > scrollTo ( index , QListView : : EnsureVisible ) ;
// Try to examine, if we have to switch the current index
QModelIndex currentIndex = ui - > thumbnailsListView - > currentIndex ( ) ;
if ( currentIndex . isValid ( ) )
{
const pdf : : PDFInteger currentPageIndex = m_thumbnailsModel - > getPageIndex ( currentIndex ) ;
Q_ASSERT ( std : : is_sorted ( currentPages . cbegin ( ) , currentPages . cend ( ) ) ) ;
if ( std : : binary_search ( currentPages . cbegin ( ) , currentPages . cend ( ) , currentPageIndex ) )
{
return ;
}
}
ui - > thumbnailsListView - > setCurrentIndex ( index ) ;
}
}
}
2019-11-28 18:20:32 +01:00
void PDFSidebarWidget : : updateGUI ( Page preferredPage )
{
if ( preferredPage ! = Invalid & & ! isEmpty ( preferredPage ) )
{
selectPage ( preferredPage ) ;
}
else
{
// Select first nonempty page
std : : vector < Page > validPages = getValidPages ( ) ;
if ( ! validPages . empty ( ) )
{
selectPage ( validPages . front ( ) ) ;
}
else
{
selectPage ( Invalid ) ;
}
}
}
void PDFSidebarWidget : : updateButtons ( )
{
for ( const auto & pageInfo : m_pageInfo )
{
if ( pageInfo . second . button )
{
pageInfo . second . button - > setEnabled ( ! isEmpty ( pageInfo . first ) ) ;
}
}
}
2019-12-07 17:59:03 +01:00
void PDFSidebarWidget : : onPageButtonClicked ( )
{
QObject * pushButton = sender ( ) ;
for ( const auto & pageInfo : m_pageInfo )
{
if ( pageInfo . second . button = = pushButton )
{
Q_ASSERT ( ! isEmpty ( pageInfo . first ) ) ;
selectPage ( pageInfo . first ) ;
break ;
}
}
}
2019-11-29 19:10:29 +01:00
void PDFSidebarWidget : : onOutlineItemClicked ( const QModelIndex & index )
{
if ( const pdf : : PDFAction * action = m_outlineTreeModel - > getAction ( index ) )
{
emit actionTriggered ( action ) ;
}
}
2019-12-07 17:59:03 +01:00
void PDFSidebarWidget : : onThumbnailsSizeChanged ( int size )
{
2020-02-23 18:59:54 +01:00
const int thumbnailsSize = pdf : : PDFWidgetUtils : : getPixelSize ( this , size * 10.0 ) ;
2019-12-07 17:59:03 +01:00
Q_ASSERT ( thumbnailsSize > 0 ) ;
m_thumbnailsModel - > setThumbnailsSize ( thumbnailsSize ) ;
}
2019-12-03 19:24:43 +01:00
void PDFSidebarWidget : : onAttachmentCustomContextMenuRequested ( const QPoint & pos )
{
if ( const pdf : : PDFFileSpecification * fileSpecification = m_attachmentsTreeModel - > getFileSpecification ( ui - > attachmentsTreeView - > indexAt ( pos ) ) )
{
QMenu menu ( this ) ;
QAction * action = new QAction ( QApplication : : style ( ) - > standardIcon ( QStyle : : SP_DialogSaveButton , nullptr , ui - > attachmentsTreeView ) , tr ( " Save to File... " ) , & menu ) ;
auto onSaveTriggered = [ this , fileSpecification ] ( )
{
const pdf : : PDFEmbeddedFile * platformFile = fileSpecification - > getPlatformFile ( ) ;
if ( platformFile & & platformFile - > isValid ( ) )
{
QString defaultFileName = QStandardPaths : : writableLocation ( QStandardPaths : : DocumentsLocation ) + QDir : : separator ( ) + fileSpecification - > getPlatformFileName ( ) ;
QString saveFileName = QFileDialog : : getSaveFileName ( this , tr ( " Save attachment " ) , defaultFileName ) ;
if ( ! saveFileName . isEmpty ( ) )
{
try
{
QByteArray data = m_document - > getDecodedStream ( platformFile - > getStream ( ) ) ;
QFile file ( saveFileName ) ;
if ( file . open ( QFile : : WriteOnly | QFile : : Truncate ) )
{
file . write ( data ) ;
file . close ( ) ;
}
else
{
QMessageBox : : critical ( this , tr ( " Error " ) , tr ( " Failed to save attachment to file. %1 " ) . arg ( file . errorString ( ) ) ) ;
}
}
catch ( pdf : : PDFException e )
{
QMessageBox : : critical ( this , tr ( " Error " ) , tr ( " Failed to save attachment to file. %1 " ) . arg ( e . getMessage ( ) ) ) ;
}
}
}
else
{
QMessageBox : : critical ( this , tr ( " Error " ) , tr ( " Failed to save attachment to file. Attachment is corrupted. " ) ) ;
}
} ;
connect ( action , & QAction : : triggered , this , onSaveTriggered ) ;
menu . addAction ( action ) ;
menu . exec ( ui - > attachmentsTreeView - > viewport ( ) - > mapToGlobal ( pos ) ) ;
}
}
2019-12-08 19:20:36 +01:00
void PDFSidebarWidget : : onThumbnailClicked ( const QModelIndex & index )
{
if ( index . isValid ( ) )
{
m_proxy - > goToPage ( m_thumbnailsModel - > getPageIndex ( index ) ) ;
}
}
2019-12-04 19:18:40 +01:00
void PDFSidebarWidget : : paintEvent ( QPaintEvent * event )
{
Q_UNUSED ( event ) ;
QPainter painter ( this ) ;
painter . fillRect ( rect ( ) , QColor ( 64 , 64 , 64 ) ) ;
}
2019-11-28 18:20:32 +01:00
} // namespace pdfviewer