2020-01-03 18:11:03 +01:00
// Copyright (C) 2020 Jakub Melka
//
// 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 "pdfadvancedfindwidget.h"
# include "ui_pdfadvancedfindwidget.h"
# include "pdfcompiler.h"
# include "pdfdocument.h"
# include "pdfdrawspacecontroller.h"
# include <QMessageBox>
namespace pdfviewer
{
PDFAdvancedFindWidget : : PDFAdvancedFindWidget ( pdf : : PDFDrawWidgetProxy * proxy , QWidget * parent ) :
QWidget ( parent ) ,
ui ( new Ui : : PDFAdvancedFindWidget ) ,
m_proxy ( proxy ) ,
m_document ( nullptr )
{
ui - > setupUi ( this ) ;
2020-01-04 17:58:55 +01:00
ui - > resultsTableWidget - > setHorizontalHeaderLabels ( { tr ( " Page No. " ) , tr ( " Phrase " ) , tr ( " Context " ) } ) ;
2020-01-03 18:11:03 +01:00
connect ( ui - > regularExpressionsCheckbox , & QCheckBox : : clicked , this , & PDFAdvancedFindWidget : : updateUI ) ;
connect ( m_proxy , & pdf : : PDFDrawWidgetProxy : : textLayoutChanged , this , & PDFAdvancedFindWidget : : performSearch ) ;
2020-01-05 18:13:43 +01:00
connect ( ui - > resultsTableWidget , & QTableWidget : : cellDoubleClicked , this , & PDFAdvancedFindWidget : : onResultItemDoubleClicked ) ;
2020-01-11 16:14:38 +01:00
connect ( ui - > resultsTableWidget , & QTableWidget : : itemSelectionChanged , this , & PDFAdvancedFindWidget : : onSelectionChanged ) ;
2020-01-03 18:11:03 +01:00
updateUI ( ) ;
}
PDFAdvancedFindWidget : : ~ PDFAdvancedFindWidget ( )
{
delete ui ;
}
2020-04-25 14:21:06 +02:00
void PDFAdvancedFindWidget : : setDocument ( const pdf : : PDFModifiedDocument & document )
2020-01-03 18:11:03 +01:00
{
if ( m_document ! = document )
{
m_document = document ;
2020-04-25 14:21:06 +02:00
// If document is not being reset, then page text should remain the same,
// so, there is no need to clear the results.
if ( document . hasReset ( ) )
{
m_findResults . clear ( ) ;
updateUI ( ) ;
updateResultsUI ( ) ;
}
2020-01-03 18:11:03 +01:00
}
}
void PDFAdvancedFindWidget : : on_searchButton_clicked ( )
{
m_parameters . phrase = ui - > searchPhraseEdit - > text ( ) ;
m_parameters . isCaseSensitive = ui - > caseSensitiveCheckBox - > isChecked ( ) ;
m_parameters . isWholeWordsOnly = ui - > wholeWordsOnlyCheckBox - > isChecked ( ) ;
m_parameters . isRegularExpression = ui - > regularExpressionsCheckbox - > isChecked ( ) ;
m_parameters . isDotMatchingEverything = ui - > dotMatchesEverythingCheckBox - > isChecked ( ) ;
m_parameters . isMultiline = ui - > multilineMatchingCheckBox - > isChecked ( ) ;
2020-01-04 17:58:55 +01:00
m_parameters . isSoftHyphenRemoved = ui - > removeSoftHyphenCheckBox - > isChecked ( ) ;
2020-01-03 18:11:03 +01:00
m_parameters . isSearchFinished = m_parameters . phrase . isEmpty ( ) ;
if ( m_parameters . isSearchFinished )
{
// We have nothing to search for
return ;
}
// Validate regular expression
if ( m_parameters . isRegularExpression )
{
QRegularExpression expression ( m_parameters . phrase ) ;
if ( ! expression . isValid ( ) )
{
m_parameters . isSearchFinished = true ;
const int patternErrorOffset = expression . patternErrorOffset ( ) ;
QMessageBox : : critical ( this , tr ( " Search error " ) , tr ( " Search phrase regular expression has error '%1' near symbol %2. " ) . arg ( expression . errorString ( ) ) . arg ( patternErrorOffset ) ) ;
ui - > searchPhraseEdit - > setFocus ( ) ;
ui - > searchPhraseEdit - > setSelection ( patternErrorOffset , 1 ) ;
return ;
}
}
2020-01-04 17:58:55 +01:00
m_findResults . clear ( ) ;
2020-01-12 18:46:59 +01:00
m_textSelection . dirty ( ) ;
2020-01-04 17:58:55 +01:00
updateResultsUI ( ) ;
2020-01-03 18:11:03 +01:00
pdf : : PDFAsynchronousTextLayoutCompiler * compiler = m_proxy - > getTextLayoutCompiler ( ) ;
if ( compiler - > isTextLayoutReady ( ) )
{
performSearch ( ) ;
}
else
{
compiler - > makeTextLayout ( ) ;
}
}
2020-01-11 16:14:38 +01:00
void PDFAdvancedFindWidget : : on_clearButton_clicked ( )
{
m_parameters = SearchParameters ( ) ;
m_findResults . clear ( ) ;
updateResultsUI ( ) ;
}
void PDFAdvancedFindWidget : : onSelectionChanged ( )
{
m_textSelection . dirty ( ) ;
m_proxy - > repaintNeeded ( ) ;
}
2020-01-05 18:13:43 +01:00
void PDFAdvancedFindWidget : : onResultItemDoubleClicked ( int row , int column )
{
Q_UNUSED ( column ) ;
if ( row > = 0 & & row < m_findResults . size ( ) )
{
const pdf : : PDFFindResult & findResult = m_findResults [ row ] ;
const pdf : : PDFInteger pageIndex = findResult . textSelectionItems . front ( ) . first . pageIndex ;
m_proxy - > goToPage ( pageIndex ) ;
}
}
2020-01-03 18:11:03 +01:00
void PDFAdvancedFindWidget : : updateUI ( )
{
const bool enableUI = m_document & & m_document - > getCatalog ( ) - > getPageCount ( ) > 0 ;
const bool enableRegularExpressionUI = enableUI & & ui - > regularExpressionsCheckbox - > isChecked ( ) ;
ui - > searchForGroupBox - > setEnabled ( enableUI ) ;
ui - > regularExpressionSettingsGroupBox - > setEnabled ( enableRegularExpressionUI ) ;
}
2020-01-04 17:58:55 +01:00
void PDFAdvancedFindWidget : : updateResultsUI ( )
{
ui - > tabWidget - > setTabText ( ui - > tabWidget - > indexOf ( ui - > resultsTab ) , ! m_findResults . empty ( ) ? tr ( " Results (%1) " ) . arg ( m_findResults . size ( ) ) : tr ( " Results " ) ) ;
ui - > resultsTableWidget - > setRowCount ( static_cast < int > ( m_findResults . size ( ) ) ) ;
for ( int i = 0 , rowCount = int ( m_findResults . size ( ) ) ; i < rowCount ; + + i )
{
const pdf : : PDFFindResult & findResult = m_findResults [ i ] ;
ui - > resultsTableWidget - > setItem ( i , 0 , new QTableWidgetItem ( QString : : number ( findResult . textSelectionItems . front ( ) . first . pageIndex + 1 ) ) ) ;
ui - > resultsTableWidget - > setItem ( i , 1 , new QTableWidgetItem ( findResult . matched ) ) ;
ui - > resultsTableWidget - > setItem ( i , 2 , new QTableWidgetItem ( findResult . context ) ) ;
}
if ( ! m_findResults . empty ( ) )
{
ui - > tabWidget - > setCurrentWidget ( ui - > resultsTab ) ;
}
}
2020-01-11 16:14:38 +01:00
void PDFAdvancedFindWidget : : drawPage ( QPainter * painter ,
pdf : : PDFInteger pageIndex ,
const pdf : : PDFPrecompiledPage * compiledPage ,
pdf : : PDFTextLayoutGetter & layoutGetter ,
2020-04-18 19:01:49 +02:00
const QMatrix & pagePointToDevicePointMatrix ,
QList < pdf : : PDFRenderError > & errors ) const
2020-01-11 16:14:38 +01:00
{
Q_UNUSED ( compiledPage ) ;
2020-04-18 19:01:49 +02:00
Q_UNUSED ( errors ) ;
2020-01-11 16:14:38 +01:00
const pdf : : PDFTextSelection & textSelection = getTextSelection ( ) ;
pdf : : PDFTextSelectionPainter textSelectionPainter ( & textSelection ) ;
textSelectionPainter . draw ( painter , pageIndex , layoutGetter , pagePointToDevicePointMatrix ) ;
}
2020-01-03 18:11:03 +01:00
void PDFAdvancedFindWidget : : performSearch ( )
{
2020-01-12 18:46:59 +01:00
if ( m_parameters . isSearchFinished | | m_parameters . phrase . isEmpty ( ) )
2020-01-03 18:11:03 +01:00
{
return ;
}
m_parameters . isSearchFinished = true ;
pdf : : PDFAsynchronousTextLayoutCompiler * compiler = m_proxy - > getTextLayoutCompiler ( ) ;
if ( ! compiler - > isTextLayoutReady ( ) )
{
// Text layout is not ready yet
return ;
}
2020-01-04 17:58:55 +01:00
// Prepare string to search
bool useRegularExpression = m_parameters . isRegularExpression ;
QString expression = m_parameters . phrase ;
if ( m_parameters . isWholeWordsOnly )
{
if ( useRegularExpression )
{
expression = QString ( " \\ b%1 \\ b " ) . arg ( expression ) ;
}
else
{
expression = QString ( " \\ b%1 \\ b " ) . arg ( QRegularExpression : : escape ( expression ) ) ;
}
useRegularExpression = true ;
}
pdf : : PDFTextFlow : : FlowFlags flowFlags = pdf : : PDFTextFlow : : SeparateBlocks ;
if ( m_parameters . isSoftHyphenRemoved )
{
flowFlags | = pdf : : PDFTextFlow : : RemoveSoftHyphen ;
}
if ( m_parameters . isRegularExpression )
{
flowFlags | = pdf : : PDFTextFlow : : AddLineBreaks ;
}
const pdf : : PDFTextLayoutStorage * textLayoutStorage = compiler - > getTextLayoutStorage ( ) ;
if ( ! useRegularExpression )
{
// Use simple text search
Qt : : CaseSensitivity caseSensitivity = m_parameters . isCaseSensitive ? Qt : : CaseSensitive : Qt : : CaseInsensitive ;
m_findResults = textLayoutStorage - > find ( expression , caseSensitivity , flowFlags ) ;
}
else
{
// Use regular expression search
QRegularExpression : : PatternOptions patternOptions = QRegularExpression : : UseUnicodePropertiesOption | QRegularExpression : : OptimizeOnFirstUsageOption ;
if ( ! m_parameters . isCaseSensitive )
{
patternOptions | = QRegularExpression : : CaseInsensitiveOption ;
}
if ( m_parameters . isDotMatchingEverything )
{
patternOptions | = QRegularExpression : : DotMatchesEverythingOption ;
}
if ( m_parameters . isMultiline )
{
patternOptions | = QRegularExpression : : MultilineOption ;
}
QRegularExpression regularExpression ( expression , patternOptions ) ;
m_findResults = textLayoutStorage - > find ( regularExpression , flowFlags ) ;
}
2020-01-03 18:11:03 +01:00
2020-01-11 16:14:38 +01:00
m_textSelection . dirty ( ) ;
m_proxy - > repaintNeeded ( ) ;
2020-01-04 17:58:55 +01:00
updateResultsUI ( ) ;
2020-01-03 18:11:03 +01:00
}
2020-01-11 16:14:38 +01:00
pdf : : PDFTextSelection PDFAdvancedFindWidget : : getTextSelectionImpl ( ) const
{
pdf : : PDFTextSelection result ;
std : : vector < size_t > selectedRowIndices ;
QModelIndexList selectedRows = ui - > resultsTableWidget - > selectionModel ( ) - > selectedRows ( ) ;
std : : transform ( selectedRows . cbegin ( ) , selectedRows . cend ( ) , std : : back_inserter ( selectedRowIndices ) , [ ] ( const QModelIndex & index ) { return index . row ( ) ; } ) ;
std : : sort ( selectedRowIndices . begin ( ) , selectedRowIndices . end ( ) ) ;
for ( size_t i = 0 ; i < m_findResults . size ( ) ; + + i )
{
const pdf : : PDFFindResult & findResult = m_findResults [ i ] ;
QColor color ( Qt : : blue ) ;
if ( std : : binary_search ( selectedRowIndices . cbegin ( ) , selectedRowIndices . cend ( ) , i ) )
{
color = QColor ( Qt : : yellow ) ;
}
result . addItems ( findResult . textSelectionItems , color ) ;
}
2020-01-12 18:46:59 +01:00
result . build ( ) ;
2020-01-11 16:14:38 +01:00
return result ;
}
void PDFAdvancedFindWidget : : showEvent ( QShowEvent * event )
{
BaseClass : : showEvent ( event ) ;
m_proxy - > registerDrawInterface ( this ) ;
}
void PDFAdvancedFindWidget : : hideEvent ( QHideEvent * event )
{
m_proxy - > unregisterDrawInterface ( this ) ;
BaseClass : : hideEvent ( event ) ;
}
2020-01-03 18:11:03 +01:00
} // namespace pdfviewer