diff --git a/autotests/searchtest.cpp b/autotests/searchtest.cpp --- a/autotests/searchtest.cpp +++ b/autotests/searchtest.cpp @@ -80,7 +80,7 @@ #define TEST_NEXT_PREV(searchType, expectedStatus) \ { \ - Okular::RegularAreaRect* result = tp->findText(0, searchString, searchType, Qt::CaseSensitive, NULL); \ + Okular::RegularAreaRect* result = tp->findText(0, searchString, searchType, Qt::CaseSensitive, NULL, false); \ QCOMPARE(!!result, expectedStatus); \ delete result; \ } @@ -178,7 +178,7 @@ d.openDocument(testFile, QUrl(), mime); const int searchId = 0; - d.searchText(searchId, QStringLiteral(" i "), true, Qt::CaseSensitive, Okular::Document::NextMatch, false, QColor()); + d.searchText(searchId, QStringLiteral(" i "), true, Qt::CaseSensitive, Okular::Document::NextMatch, false, QColor(), false); QTRY_COMPARE(spy.count(), 1); QCOMPARE(receiver.m_id, searchId); QCOMPARE(receiver.m_status, Okular::Document::MatchFound); @@ -200,7 +200,7 @@ CREATE_PAGE; - Okular::RegularAreaRect* result = tp->findText(0, QStringLiteral("a"), Okular::FromBottom, Qt::CaseSensitive, nullptr); + Okular::RegularAreaRect* result = tp->findText(0, QStringLiteral("a"), Okular::FromBottom, Qt::CaseSensitive, nullptr, false); QVERIFY(result); delete result; @@ -219,7 +219,7 @@ CREATE_PAGE; - Okular::RegularAreaRect* result = tp->findText(0, QStringLiteral("ab"), Okular::FromTop, Qt::CaseSensitive, nullptr); + Okular::RegularAreaRect* result = tp->findText(0, QStringLiteral("ab"), Okular::FromTop, Qt::CaseSensitive, nullptr, false); QVERIFY(result); Okular::RegularAreaRect expected; expected.append(rect[1]); @@ -250,7 +250,7 @@ CREATE_PAGE; - Okular::RegularAreaRect* result = tp->findText(0, QString::fromUtf8("İ"), Okular::FromTop, Qt::CaseInsensitive, nullptr); + Okular::RegularAreaRect* result = tp->findText(0, QString::fromUtf8("İ"), Okular::FromTop, Qt::CaseInsensitive, nullptr, false); QVERIFY(result); delete result; @@ -276,7 +276,7 @@ CREATE_PAGE; Okular::RegularAreaRect* result = tp->findText(0, QStringLiteral("supercalifragilisticexpialidocious"), - Okular::FromTop, Qt::CaseSensitive, nullptr); + Okular::FromTop, Qt::CaseSensitive, nullptr, false); QVERIFY(result); Okular::RegularAreaRect expected; for (int i = 0; i < text.size(); i++) { @@ -294,7 +294,7 @@ CREATE_PAGE; \ \ Okular::RegularAreaRect* result = tp->findText(0, QStringLiteral(searchString), \ - Okular::FromTop, Qt::CaseSensitive, NULL); \ + Okular::FromTop, Qt::CaseSensitive, NULL, false); \ \ QCOMPARE(!!result, matchExpected); \ \ @@ -349,14 +349,14 @@ { Okular::RegularAreaRect* result = tp->findText(0, QStringLiteral("a"), - Okular::FromTop, Qt::CaseSensitive, nullptr); + Okular::FromTop, Qt::CaseSensitive, nullptr, false); QVERIFY(result); delete result; } { Okular::RegularAreaRect* result = tp->findText(0, QStringLiteral("a"), - Okular::FromBottom, Qt::CaseSensitive, nullptr); + Okular::FromBottom, Qt::CaseSensitive, nullptr, false); QVERIFY(result); delete result; } @@ -388,7 +388,7 @@ CREATE_PAGE; Okular::RegularAreaRect* result = tp->findText(0, QStringLiteral("Only one column"), - Okular::FromTop, Qt::CaseSensitive, nullptr); + Okular::FromTop, Qt::CaseSensitive, nullptr, false); QVERIFY(result); delete result; @@ -416,7 +416,7 @@ CREATE_PAGE; Okular::RegularAreaRect* result = tp->findText(0, QStringLiteral("This text in"), - Okular::FromTop, Qt::CaseSensitive, nullptr); + Okular::FromTop, Qt::CaseSensitive, nullptr, false); QVERIFY(!result); delete result; diff --git a/conf/okular.kcfg b/conf/okular.kcfg --- a/conf/okular.kcfg +++ b/conf/okular.kcfg @@ -306,6 +306,9 @@ true + + false + diff --git a/core/document.h b/core/document.h --- a/core/document.h +++ b/core/document.h @@ -636,9 +636,10 @@ * @param type The type of the search. @ref SearchType * @param moveViewport Whether the viewport shall be moved to the position of the matches. * @param color The highlighting color of the matches. + * @param wholeWords Whether the search should only find whole words or not. */ void searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity, - SearchType type, bool moveViewport, const QColor & color ); + SearchType type, bool moveViewport, const QColor & color, bool wholeWords ); /** * Continues the search for the given @p searchID. diff --git a/core/document.cpp b/core/document.cpp --- a/core/document.cpp +++ b/core/document.cpp @@ -135,6 +135,7 @@ Document::SearchType cachedType; Qt::CaseSensitivity cachedCaseSensitivity; bool cachedViewportMove : 1; + bool cachedWholeWords; bool isCurrentlySearching : 1; QColor cachedColor; int pagesDone; @@ -1669,7 +1670,7 @@ m_parent->requestTextPage( page->number() ); // if found a match on the current page, end the loop - searchStruct->match = page->findText( searchStruct->searchID, search->cachedString, forward ? FromTop : FromBottom, search->cachedCaseSensitivity ); + searchStruct->match = page->findText( searchStruct->searchID, search->cachedString, forward ? FromTop : FromBottom, search->cachedCaseSensitivity, nullptr, search->cachedWholeWords ); if ( !searchStruct->match ) { if (forward) searchStruct->currentPage++; @@ -3770,7 +3771,7 @@ } void Document::searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity, - SearchType type, bool moveViewport, const QColor & color ) + SearchType type, bool moveViewport, const QColor & color, bool wholeWords ) { d->m_searchCancelled = false; @@ -3798,6 +3799,7 @@ s->cachedCaseSensitivity = caseSensitivity; s->cachedViewportMove = moveViewport; s->cachedColor = color; + s->cachedWholeWords = wholeWords; s->isCurrentlySearching = true; // global data for search @@ -3837,9 +3839,9 @@ if ( lastPage && lastPage->number() == s->continueOnPage ) { if ( newText ) - match = lastPage->findText( searchID, text, forward ? FromTop : FromBottom, caseSensitivity ); + match = lastPage->findText( searchID, text, forward ? FromTop : FromBottom, caseSensitivity, nullptr, wholeWords ); else - match = lastPage->findText( searchID, text, forward ? NextResult : PreviousResult, caseSensitivity, &s->continueOnMatch ); + match = lastPage->findText( searchID, text, forward ? NextResult : PreviousResult, caseSensitivity, &s->continueOnMatch, wholeWords ); if ( !match ) { if (forward) currentPage++; @@ -3883,7 +3885,7 @@ RunningSearch * p = *it; if ( !p->isCurrentlySearching ) searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity, - p->cachedType, p->cachedViewportMove, p->cachedColor ); + p->cachedType, p->cachedViewportMove, p->cachedColor, p->cachedWholeWords ); } void Document::continueSearch( int searchID, SearchType type ) @@ -3900,7 +3902,7 @@ RunningSearch * p = *it; if ( !p->isCurrentlySearching ) searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity, - type, p->cachedViewportMove, p->cachedColor ); + type, p->cachedViewportMove, p->cachedColor, p->cachedWholeWords ); } void Document::resetSearch( int searchID ) diff --git a/core/page.h b/core/page.h --- a/core/page.h +++ b/core/page.h @@ -186,7 +186,8 @@ * right/below the coordinates of the given rect. */ RegularAreaRect* findText( int id, const QString & text, SearchDirection direction, - Qt::CaseSensitivity caseSensitivity, const RegularAreaRect * lastRect=nullptr) const; + Qt::CaseSensitivity caseSensitivity, const RegularAreaRect * lastRect=nullptr, + const bool wholeWords=0) const; /** * Returns the page text (or part of it). diff --git a/core/page.cpp b/core/page.cpp --- a/core/page.cpp +++ b/core/page.cpp @@ -301,13 +301,13 @@ } RegularAreaRect * Page::findText( int id, const QString & text, SearchDirection direction, - Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect ) const + Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect, + const bool wholeWords ) const { RegularAreaRect* rect = nullptr; if ( text.isEmpty() || !d->m_text ) return rect; - - rect = d->m_text->findText( id, text, direction, caseSensitivity, lastRect ); + rect = d->m_text->findText( id, text, direction, caseSensitivity, lastRect, wholeWords ); return rect; } diff --git a/core/textpage.h b/core/textpage.h --- a/core/textpage.h +++ b/core/textpage.h @@ -137,9 +137,11 @@ * the search is case insensitive. * @param lastRect If 0 the search starts at the beginning of the page, otherwise * right/below the coordinates of the given rect. + * @param wholeWords If true, it will search for whole words only */ RegularAreaRect* findText( int id, const QString &text, SearchDirection direction, - Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect ); + Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect, + const bool wholeWords ); /** * Text extraction function. diff --git a/core/textpage.cpp b/core/textpage.cpp --- a/core/textpage.cpp +++ b/core/textpage.cpp @@ -714,7 +714,8 @@ RegularAreaRect* TextPage::findText( int searchID, const QString &query, SearchDirection direct, - Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *area ) + Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *area, + const bool wholeWords ) { SearchDirection dir=direct; // invalid search request @@ -764,11 +765,11 @@ ? CaseSensitiveCmpFn : CaseInsensitiveCmpFn; if ( forward ) { - ret = d->findTextInternalForward( searchID, query, cmpFn, start, start_offset, end ); + ret = d->findTextInternalForward( searchID, query, cmpFn, start, start_offset, end, wholeWords ); } else { - ret = d->findTextInternalBackward( searchID, query, cmpFn, start, start_offset, end ); + ret = d->findTextInternalBackward( searchID, query, cmpFn, start, start_offset, end, wholeWords ); } return ret; } @@ -839,11 +840,64 @@ return ret; } +// Checks if the word searched is whole + +bool TextPagePrivate::isWholeWord( const TextList::ConstIterator &start, + const TextList::ConstIterator &end) +{ + + // Divided in two parts, after the word and before + bool part1 = false, part2 = false; + + // If I'm at the beggining/end of the text, I don't need to check after/before + if( start == m_words.constBegin() ) + part1 = true; + else + { + // Else I check if the text before is a number or letter, if it isn't, a word begins here + TextList::ConstIterator before = start; + before--; + const TinyTextEntity* curEntity = *before; + const QString& str = curEntity->text(); + int len = stringLengthAdaptedWithHyphen( str, before, m_words.constEnd() ); + if( !str[len-1].isLetterOrNumber() ) part1 = true; + } + + if( end == m_words.constEnd() ) + part2 = true; + else + { + + // For the end is a bit more complicated, some text entities can end at a \n, + // and checking the next would get the first char in the next phrase. + // So I'm checking this right here. + TextList::ConstIterator after = end; + const TinyTextEntity* endEntity = *after; + const QString& endStr = endEntity->text(); + + if( endStr.endsWith( QStringLiteral("\n") )) + part2 = true; + + // If it doesn't end in \n, I check the next char, as done in before. + else + { + after++; + const TinyTextEntity* curEntity = *after; + const QString& str = curEntity->text(); + int len = stringLengthAdaptedWithHyphen( str, after, m_words.constEnd() ); + if( !str[0].isLetterOrNumber() ) part2 = true; + } + } + + return part1 && part2; +} + RegularAreaRect* TextPagePrivate::findTextInternalForward( int searchID, const QString &_query, TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, - const TextList::ConstIterator &end) + const TextList::ConstIterator &end, + bool wholeWords ) { // normalize query search all unicode (including glyphs) const QString query = _query.normalized(QString::NormalizationForm_KC); @@ -917,18 +971,32 @@ if (queryLeft==0) { - // save or update the search point for the current searchID - QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); - if ( sIt == m_searchPoints.end() ) + //If it's looking for whole words and this is not a whole word + if(wholeWords && !isWholeWord(it_begin, it)) { - sIt = m_searchPoints.insert( searchID, new SearchPoint ); + j = 0; + queryLeft=query.length(); + it = it_begin; + offset = offset_begin+1; + it_begin = TextList::ConstIterator(); } - SearchPoint* sp = *sIt; - sp->it_begin = it_begin; - sp->it_end = it; - sp->offset_begin = offset_begin; - sp->offset_end = offset + min; - return searchPointToArea(sp); + + else + { + // save or update the search point for the current searchID + QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); + if ( sIt == m_searchPoints.end() ) + { + sIt = m_searchPoints.insert( searchID, new SearchPoint ); + } + SearchPoint* sp = *sIt; + sp->it_begin = it_begin; + sp->it_end = it; + sp->offset_begin = offset_begin; + sp->offset_end = offset + min; + return searchPointToArea(sp); + } + } it++; @@ -952,7 +1020,8 @@ TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, - const TextList::ConstIterator &end) + const TextList::ConstIterator &end, + bool wholeWords ) { // normalize query to search all unicode (including glyphs) const QString query = _query.normalized(QString::NormalizationForm_KC); @@ -1035,18 +1104,31 @@ if ( queryLeft == 0 ) { - // save or update the search point for the current searchID - QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); - if ( sIt == m_searchPoints.end() ) + + //If it's looking for whole words and this is not a whole word + if(wholeWords && !isWholeWord(it, it_begin)) + { + j = 0; + queryLeft=query.length(); + it = it_begin; + offset = offset_begin+1; + it_begin = TextList::ConstIterator(); + } + else { - sIt = m_searchPoints.insert( searchID, new SearchPoint ); + // save or update the search point for the current searchID + QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); + if ( sIt == m_searchPoints.end() ) + { + sIt = m_searchPoints.insert( searchID, new SearchPoint ); + } + SearchPoint* sp = *sIt; + sp->it_begin = it; + sp->it_end = it_begin; + sp->offset_begin = offset - min; + sp->offset_end = offset_begin; + return searchPointToArea(sp); } - SearchPoint* sp = *sIt; - sp->it_begin = it; - sp->it_end = it_begin; - sp->offset_begin = offset - min; - sp->offset_end = offset_begin; - return searchPointToArea(sp); } offset = 0; diff --git a/core/textpage_p.h b/core/textpage_p.h --- a/core/textpage_p.h +++ b/core/textpage_p.h @@ -47,18 +47,24 @@ TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, - const TextList::ConstIterator &end); + const TextList::ConstIterator &end, + bool wholeWords ); RegularAreaRect * findTextInternalBackward( int searchID, const QString &query, TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, - const TextList::ConstIterator &end ); + const TextList::ConstIterator &end, + bool wholeWords ); /** * Copy a TextList to m_words, the pointers of list are adopted */ void setWordList(const TextList &list); + + bool isWholeWord( const TextList::ConstIterator &start, + const TextList::ConstIterator &end); + /** * Make necessary modifications in the TextList to make the text order correct, so * that textselection works fine diff --git a/mobile/components/documentitem.cpp b/mobile/components/documentitem.cpp --- a/mobile/components/documentitem.cpp +++ b/mobile/components/documentitem.cpp @@ -181,7 +181,7 @@ m_document->cancelSearch(); m_document->resetSearch(PAGEVIEW_SEARCH_ID); m_document->searchText(PAGEVIEW_SEARCH_ID, text, 1, Qt::CaseInsensitive, - Okular::Document::AllDocument, true, QColor(100,100,200,40)); + Okular::Document::AllDocument, true, QColor(100,100,200,40), false); if (!m_searchInProgress) { m_searchInProgress = true; diff --git a/ui/findbar.h b/ui/findbar.h --- a/ui/findbar.h +++ b/ui/findbar.h @@ -46,6 +46,7 @@ private Q_SLOTS: void caseSensitivityChanged(); + void findWholeWordsChanged(); void fromCurrentPageChanged(); void findAsYouTypeChanged(); void closeAndStopSearch(); @@ -55,6 +56,7 @@ QAction * m_caseSensitiveAct; QAction * m_fromCurrentPageAct; QAction * m_findAsYouTypeAct; + QAction * m_findWholeWords; bool eventFilter( QObject *target, QEvent *event ) override; bool m_active; }; diff --git a/ui/findbar.cpp b/ui/findbar.cpp --- a/ui/findbar.cpp +++ b/ui/findbar.cpp @@ -71,6 +71,9 @@ m_fromCurrentPageAct->setCheckable( true ); m_findAsYouTypeAct = optionsMenu->addAction( i18n( "Find as you type" ) ); m_findAsYouTypeAct->setCheckable( true ); + m_findWholeWords = optionsMenu->addAction( i18n( "Find whole words only" ) ); + m_findWholeWords->setCheckable( true ); + optionsBtn->setMenu( optionsMenu ); lay->addWidget( optionsBtn ); @@ -80,10 +83,12 @@ connect( m_caseSensitiveAct, &QAction::toggled, this, &FindBar::caseSensitivityChanged ); connect( m_fromCurrentPageAct, &QAction::toggled, this, &FindBar::fromCurrentPageChanged ); connect( m_findAsYouTypeAct, &QAction::toggled, this, &FindBar::findAsYouTypeChanged ); + connect( m_findWholeWords, &QAction::toggled, this, &FindBar::findWholeWordsChanged ); m_caseSensitiveAct->setChecked( Okular::Settings::searchCaseSensitive() ); m_fromCurrentPageAct->setChecked( Okular::Settings::searchFromCurrentPage() ); m_findAsYouTypeAct->setChecked( Okular::Settings::findAsYouType() ); + m_findWholeWords->setChecked( Okular::Settings::findWholeWords() ); hide(); @@ -169,6 +174,16 @@ m_search->lineEdit()->restartSearch(); } +void FindBar::findWholeWordsChanged() +{ + m_search->lineEdit()->setWholeWords( m_findWholeWords->isChecked() ); + if( !m_active ) + return; + Okular::Settings::setFindWholeWords( m_findWholeWords->isChecked() ); + Okular::Settings::self()->save(); + m_search->lineEdit()->restartSearch(); +} + void FindBar::fromCurrentPageChanged() { m_search->lineEdit()->setSearchFromStart( !m_fromCurrentPageAct->isChecked() ); diff --git a/ui/searchlineedit.h b/ui/searchlineedit.h --- a/ui/searchlineedit.h +++ b/ui/searchlineedit.h @@ -30,6 +30,7 @@ void clearText(); void setSearchCaseSensitivity( Qt::CaseSensitivity cs ); + void setWholeWords( bool wholeWords ); void setSearchMinimumLength( int length ); void setSearchType( Okular::Document::SearchType type ); void setSearchId( int id ); @@ -62,6 +63,7 @@ int m_id; QColor m_color; bool m_moveViewport; + bool m_wholeWords; bool m_changed; bool m_fromStart; bool m_findAsYouType; diff --git a/ui/searchlineedit.cpp b/ui/searchlineedit.cpp --- a/ui/searchlineedit.cpp +++ b/ui/searchlineedit.cpp @@ -54,6 +54,13 @@ m_changed = true; } + +void SearchLineEdit::setWholeWords( bool wholeWords ) +{ + m_wholeWords = wholeWords; + m_changed = true; +} + void SearchLineEdit::setSearchMinimumLength( int length ) { m_minLength = length; @@ -242,7 +249,7 @@ emit searchStarted(); m_searchRunning = true; m_document->searchText( m_id, thistext, m_fromStart, m_caseSensitivity, - m_searchType, m_moveViewport, m_color ); + m_searchType, m_moveViewport, m_color, m_wholeWords ); } else m_document->resetSearch( m_id );