Commit f4b94c01 authored by Enrico Ros's avatar Enrico Ros

SEARCH CHANGES: absolutely want to get in those fixes before freeze.

fixed multiple selections on page bugs.
next commits will come tomorrow.

svn path=/trunk/kdegraphics/kpdf/; revision=385045
parent 3e1834c2
......@@ -38,13 +38,8 @@
class KPDFDocumentPrivate
{
public:
// find related
QString searchText;
bool searchCase;
int searchPage;
// filtering related
QString filterText;
bool filterCase;
// find descriptors (can handle multiple searches)
QMap< int, class RunningSearch * > searches;
// cached stuff
QString docFileName;
......@@ -75,6 +70,23 @@ struct AllocatedPixmap
AllocatedPixmap( int i, int p, int m ) : id( i ), page( p ), memory( m ) {};
};
struct RunningSearch
{
// publicly accessible fields
QString text;
bool caseSensitive;
bool moveViewport;
KPDFDocument::SearchType type;
QColor highlightColor;
int lastPage;
HighlightRect lastMatch;
QValueList< int > matchedPages;
// public constructor: initialize data
};
#define foreachObserver( cmd ) {\
QMap< int, DocumentObserver * >::iterator it=d->observers.begin(), end=d->observers.end();\
for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }
......@@ -85,7 +97,6 @@ struct AllocatedPixmap
KPDFDocument::KPDFDocument()
: generator( 0 ), d( new KPDFDocumentPrivate )
{
d->searchPage = -1;
d->allocatedPixmapsTotalMemory = 0;
d->memCheckTimer = 0;
d->saveBookmarksTimer = 0;
......@@ -214,11 +225,17 @@ void KPDFDocument::closeDocument()
delete *aIt;
d->allocatedPixmapsFifo.clear();
// clear 'running searches' descriptors
QMap< int, RunningSearch * >::iterator rIt = d->searches.begin();
QMap< int, RunningSearch * >::iterator rEnd = d->searches.end();
for ( ; rIt != rEnd; ++rIt )
delete *rIt;
d->searches.clear();
// reset internal variables
d->viewportHistory.clear();
d->viewportHistory.append( DocumentViewport() );
d->viewportIterator = d->viewportHistory.begin();
d->searchPage = -1;
d->allocatedPixmapsTotalMemory = 0;
}
......@@ -532,98 +549,228 @@ void KPDFDocument::setNextViewport()
}
}
bool KPDFDocument::findText( const QString & string, bool keepCase, bool findAhead )
bool KPDFDocument::searchText( int searchID, const QString & text, bool fromStart,
bool caseSensitive, SearchType type, bool moveViewport, const QColor & color )
{
// turn selection drawing off on filtered pages
if ( !d->filterText.isEmpty() )
unHilightPages();
// add a new search descriptor to runningSearches
if ( !d->searches.contains( searchID ) )
{
RunningSearch * search = new RunningSearch();
search->text = text;
search->caseSensitive = caseSensitive;
search->moveViewport = moveViewport;
search->type = type;
search->highlightColor = color;
search->lastPage = -1;
d->searches[ searchID ] = search;
}
// save params for the 'find next' case
if ( !string.isEmpty() )
// get search descriptor and update stucture
RunningSearch * s = d->searches[ searchID ];
if ( text != s->text )
s->text = text;
if ( type != s->type )
s->type = type;
// set hourglass cursor
QApplication::setOverrideCursor( waitCursor );
// unhighlight pages and inform observers about that
QValueList< int >::iterator it = s->matchedPages.begin(), end = s->matchedPages.end();
for ( ; it != end; ++it )
{
d->searchText = string;
d->searchCase = keepCase;
int pageNumber = *it;
pages_vector[ pageNumber ]->deleteHighlights( searchID );
foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) );
}
s->matchedPages.clear();
//
bool foundAMatch = false;
// [1] ALLDOC - proces all document marking pages
if ( type == AllDoc )
{
// perform a linear search/mark
QValueVector< KPDFPage * >::iterator it = pages_vector.begin(), end = pages_vector.end();
for ( ; it != end; ++it )
{
KPDFPage * page = *it;
if ( !page->hasSearchPage() )
requestTextPage( page->number() );
// continue checking last SearchPage first (if it is the current page)
int currentPage = (*d->viewportIterator).pageNumber;
int pageCount = pages_vector.count();
KPDFPage * foundPage = 0,
* lastPage = (d->searchPage > -1) ? pages_vector[ d->searchPage ] : 0;
if ( lastPage && d->searchPage == currentPage )
if ( lastPage->hasText( d->searchText, d->searchCase, findAhead ) )
foundPage = lastPage;
bool found = false;
HighlightRect * lastMatch = 0;
while ( 1 )
{
if ( lastMatch )
lastMatch = page->searchText( text, caseSensitive, lastMatch );
else
lastMatch = page->searchText( text, caseSensitive );
if ( !lastMatch )
break;
found = true;
lastMatch->id = searchID;
lastMatch->color = color;
page->setHighlight( lastMatch );
s->lastMatch = *lastMatch;
}
if ( found )
{
foundAMatch = true;
s->matchedPages.append( page->number() );
}
}
// send page lists
foreachObserver( notifySetup( pages_vector, false ) );
}
// [2] NEXTMATCH - find next matching item (or start from top)
else if ( s->type == NextMatch )
{
// find out from where to start/resume search from
int viewportPage = (*d->viewportIterator).pageNumber;
int currentPage = 0;
KPDFPage * lastPage = 0;
if ( !fromStart )
{
currentPage = s->lastPage != -1 ? s->lastPage : viewportPage;
lastPage = pages_vector[ currentPage ];
}
else
{
lastPage->clearAttribute( KPDFPage::Highlight );
currentPage++;
s->lastPage = 0;
}
if ( !foundPage )
// loop through the whole document
for ( int i = 0; i < pageCount; i++ )
HighlightRect * r = 0;
// continue checking last SearchPage first (if it is the current page)
if ( lastPage && lastPage->number() == s->lastPage )
{
if ( currentPage >= pageCount )
r = lastPage->searchText( text, caseSensitive, &s->lastMatch );
if ( !r )
{
if ( !findAhead && KMessageBox::questionYesNo(0, i18n("End of document reached.\nContinue from the beginning?")) == KMessageBox::Yes )
currentPage = 0;
else
break;
lastPage->deleteHighlights( searchID );
currentPage++;
}
KPDFPage * page = pages_vector[ currentPage ];
if ( !page->hasSearchPage() )
requestTextPage( page->number() );
if ( page->hasText( d->searchText, d->searchCase, true ) )
else
{
foundPage = page;
break;
s->lastMatch = *r;
s->matchedPages.push_back( currentPage );
}
currentPage++;
}
if ( foundPage )
if ( !r )
{
// loop through the whole document
const int pageCount = pages_vector.count();
for ( int i = 0; i < pageCount; i++ )
{
if ( currentPage >= pageCount )
{
if ( /*FIXME !findAhead &&*/ KMessageBox::questionYesNo(0, i18n("End of document reached.\nContinue from the beginning?")) == KMessageBox::Yes )
currentPage = 0;
else
break;
}
KPDFPage * page = pages_vector[ currentPage ];
if ( !page->hasSearchPage() )
requestTextPage( page->number() );
if ( (r = page->searchText( text, caseSensitive )) )
{
s->lastPage = currentPage;
s->lastMatch = *r;
s->matchedPages.push_back( currentPage );
break;
}
currentPage++;
}
}
if ( r )
{
// add highlight to the page
int pageNumber = currentPage;
KPDFPage * foundPage = pages_vector[ currentPage ];
r->id = searchID;
r->color = color;
foundPage->setHighlight( r );
// move the viewport to show the searched word centered
DocumentViewport searchViewport( pageNumber );
searchViewport.reCenter.enabled = true;
searchViewport.reCenter.normalizedCenterX = (r->left + r->right) / 2.0;
searchViewport.reCenter.normalizedCenterY = (r->top + r->bottom) / 2.0;
setViewport( searchViewport, -1, true );
// notify all observers about hilighting chages
foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) );
}
else if ( true /*FIXME !findAhead*/ )
KMessageBox::information( 0, i18n("No matches found for '%1'.").arg( text ) );
}
else if ( type == PrevMatch )
{
int pageNumber = foundPage->number();
d->searchPage = pageNumber;
foundPage->setAttribute( KPDFPage::Highlight );
// move the viewport to show the searched word centered
DocumentViewport searchViewport( pageNumber );
const QPoint & center = foundPage->getLastSearchCenter();
searchViewport.reCenter.enabled = true;
searchViewport.reCenter.normalizedCenterX = (double)center.x() / foundPage->width();
searchViewport.reCenter.normalizedCenterY = (double)center.y() / foundPage->height();
setViewport( searchViewport, -1, true );
// notify all observers about hilighting chages
foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) );
//TODO
}
else if ( type == GoogleLike )
{
//TODO
}
else if ( !findAhead )
KMessageBox::information( 0, i18n("No matches found for '%1'.").arg(d->searchText) );
return foundPage;
// reset cursor to previous shape
QApplication::restoreOverrideCursor();
return foundAMatch;
}
void KPDFDocument::findTextAll( const QString & pattern, bool keepCase )
bool KPDFDocument::continueSearch( int searchID )
{
// if pattern is null, clear 'hilighted' attribute in all pages
if ( pattern.isEmpty() )
unHilightPages();
// check if searchID is present in runningSearches
if ( !d->searches.contains( searchID ) )
return false;
// cache search pattern
d->filterText = pattern;
d->filterCase = keepCase;
// set the busy cursor globally on the application
QApplication::setOverrideCursor( waitCursor );
// perform a linear search/mark
processPageList( false );
// reset cursor to previous shape
QApplication::restoreOverrideCursor();
// get previous parameters for search
RunningSearch * p = d->searches[ searchID ];
return searchText( searchID, p->text, false, p->caseSensitive, p->type, p->moveViewport, p->highlightColor );
}
void KPDFDocument::resetSearch( int searchID )
{
// check if searchID is present in runningSearches
if ( !d->searches.contains( searchID ) )
return;
// get previous parameters for search
RunningSearch * s = d->searches[ searchID ];
// unhighlight pages and inform observers about that
QValueList< int >::iterator it = s->matchedPages.begin(), end = s->matchedPages.end();
for ( ; it != end; ++it )
{
int pageNumber = *it;
pages_vector[ pageNumber ]->deleteHighlights( searchID );
foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) );
}
s->matchedPages.clear();
// remove serch from the runningSearches list and delete it
d->searches.remove( searchID );
delete s;
}
void KPDFDocument::toggleBookmark( int n )
{
KPDFPage * page = ( n < (int)pages_vector.count() ) ? pages_vector[ n ] : 0;
if ( page )
{
page->toggleAttribute( KPDFPage::Bookmark );
page->setBookmark( !page->hasBookmark() );
foreachObserver( notifyPageChanged( n, DocumentObserver::Bookmark ) );
}
}
......@@ -980,7 +1127,7 @@ void KPDFDocument::loadDocumentInfo()
{
pageNumber = e.text().toInt(&ok);
if ( ok && pageNumber >= 0 && pageNumber < (int)pages_vector.count() )
pages_vector[ pageNumber ]->setAttribute( KPDFPage::Bookmark );
pages_vector[ pageNumber ]->setBookmark( true );
}
n = n.nextSibling();
}
......@@ -1053,6 +1200,7 @@ bool KPDFDocument::openRelativeFile( const QString & fileName )
void KPDFDocument::processPageList( bool documentChanged )
{
/* FIXME
if ( d->filterText.length() < 3 )
unHilightPages();
else
......@@ -1061,7 +1209,7 @@ void KPDFDocument::processPageList( bool documentChanged )
for ( uint i = 0; i < pageCount ; i++ )
{
KPDFPage * page = pages_vector[ i ];
page->clearAttribute( KPDFPage::Highlight );
//page->clearAttribute( KPDFPage::Highlight );
if ( d->filterText.length() > 2 )
{
if ( !page->hasSearchPage() )
......@@ -1071,29 +1219,11 @@ void KPDFDocument::processPageList( bool documentChanged )
}
}
}
*/
// send the list to observers
foreachObserver( notifySetup( pages_vector, documentChanged ) );
}
void KPDFDocument::unHilightPages(bool filteredOnly)
{
if ( filteredOnly && d->filterText.isEmpty() )
return;
d->filterText = QString::null;
QValueVector<KPDFPage*>::iterator it = pages_vector.begin(), end = pages_vector.end();
for ( ; it != end; ++it )
{
KPDFPage * page = *it;
if ( page->attributes() & KPDFPage::Highlight )
{
page->clearAttribute( KPDFPage::Highlight );
foreachObserver( notifyPageChanged( page->number(), DocumentObserver::Highlights ) );
}
}
}
void KPDFDocument::saveDocumentInfo() const
{
......@@ -1114,7 +1244,7 @@ void KPDFDocument::saveDocumentInfo() const
for ( uint i = 0; i < pages_vector.count() ; i++ )
{
if (pages_vector[i]->attributes() & KPDFPage::Bookmark)
if ( pages_vector[i]->hasBookmark() )
{
QDomElement page = doc.createElement( "page" );
page.appendChild( doc.createTextNode( QString::number(i) ) );
......
......@@ -79,12 +79,16 @@ class KPDFDocument : public QObject
void setNextViewport();
void requestPixmaps( const QValueList< PixmapRequest * > & requests );
void requestTextPage( uint page );
bool findText( const QString & text = "", bool caseSensitive = false, bool findAhead = false );
void findTextAll( const QString & pattern, bool caseSensitive );
enum SearchType { NextMatch, PrevMatch, AllDoc, GoogleLike };
bool searchText( int searchID, const QString & text, bool fromStart, bool caseSensitive,
SearchType type, bool moveViewport, const QColor & color );
bool continueSearch( int searchID );
void resetSearch( int searchID );
void toggleBookmark( int page );
void processLink( const KPDFLink * link );
bool print( KPrinter &printer );
void unHilightPages(bool filteredOnly = true);
// notifications sent by generator
void requestDone( PixmapRequest * request );
......
......@@ -24,9 +24,8 @@
/** class KPDFPage **/
KPDFPage::KPDFPage( uint page, float w, float h, int r )
: m_number( page ), m_rotation( r ), m_attributes( 0 ),
m_width( w ), m_height( h ), m_sLeft( 0 ), m_sTop( 0 ),
m_sRight( 0 ), m_sBottom( 0 ), m_text( 0 ), m_transition( 0 )
: m_number( page ), m_rotation( r ), m_width( w ), m_height( h ),
m_bookmarked( false ), m_text( 0 ), m_transition( 0 )
{
// if landscape swap width <-> height (rotate 90deg CCW)
if ( r == 90 || r == 270 )
......@@ -44,6 +43,7 @@ KPDFPage::KPDFPage( uint page, float w, float h, int r )
KPDFPage::~KPDFPage()
{
deletePixmapsAndRects();
deleteHighlights();
delete m_text;
delete m_transition;
}
......@@ -96,13 +96,6 @@ const KPDFPageTransition * KPDFPage::getTransition() const
return m_transition;
}
const QPoint KPDFPage::getLastSearchCenter() const
{
int centerX = (int)((m_sRight + m_sLeft) / 2),
centerY = (int)((m_sTop + m_sBottom) / 2);
return QPoint( centerX, centerY );
}
const QString KPDFPage::getTextInRect( const QRect & rect, double zoom ) const
{
if ( !m_text )
......@@ -118,34 +111,61 @@ const QString KPDFPage::getTextInRect( const QRect & rect, double zoom ) const
}
bool KPDFPage::hasText( const QString & text, bool strictCase, bool fromTop )
HighlightRect * KPDFPage::searchText( const QString & text, bool strictCase, HighlightRect * lastRect )
{
if ( !m_text )
return false;
if ( text.isEmpty() )
return 0;
const QChar* str = text.unicode();
// create a xpf's Unicode (unsigned int) array for the given text
const QChar * str = text.unicode();
int len = text.length();
Unicode *u = (Unicode *)gmalloc(len * sizeof(Unicode));
Unicode u[ len ];
for (int i = 0; i < len; ++i)
u[i] = str[i].unicode();
// find out the direction of search
enum SearchDir { FromTop, NextMatch, PrevMatch } dir = lastRect ? NextMatch : FromTop;
double sLeft, sTop, sRight, sBottom;
if ( dir == NextMatch )
{
sLeft = lastRect->left * m_width;
sTop = lastRect->top * m_height;
sRight = lastRect->right * m_width;
sBottom = lastRect->bottom * m_height;
}
// this loop is only for 'bad case' matches
bool found = false;
while ( !found )
{
found = m_text->findText( u, len, fromTop ? gTrue : gFalse, gTrue, fromTop ? gFalse : gTrue, gFalse, &m_sLeft, &m_sTop, &m_sRight, &m_sBottom );
if ( dir == FromTop )
found = m_text->findText( u, len, gTrue, gTrue, gFalse, gFalse, &sLeft, &sTop, &sRight, &sBottom );
else if ( dir == NextMatch )
found = m_text->findText( u, len, gFalse, gTrue, gTrue, gFalse, &sLeft, &sTop, &sRight, &sBottom );
else if ( dir == PrevMatch )
// FIXME: this doesn't work as expected
found = m_text->findText( u, len, gTrue, gFalse, gFalse, gTrue, &sLeft, &sTop, &sRight, &sBottom );
// if not found (even in case unsensitive search), terminate
if ( !found )
break;
if( strictCase )
// check for case sensitivity
if ( strictCase )
{
// since we're in 'Case sensitive' mode, check if words are identical
GString * orig = m_text->getText( m_sLeft, m_sTop, m_sRight, m_sBottom );
found = QString::fromUtf8( orig->getCString() ) == text;
if ( !found && fromTop )
fromTop = false;
GString * realText = m_text->getText( sLeft, sTop, sRight, sBottom );
found = QString::fromUtf8( realText->getCString() ) == text;
if ( !found && dir == FromTop )
dir = NextMatch;
delete realText;
}
}
gfree(u);
return found;
// if the page was found, return a new normalized HighlightRect
if ( found )
return new HighlightRect( sLeft / m_width, sTop / m_height, sRight / m_width, sBottom / m_height );
return 0;
}
void KPDFPage::setPixmap( int id, QPixmap * pixmap )
......@@ -169,6 +189,13 @@ void KPDFPage::setRects( const QValueList< KPDFPageRect * > rects )
m_rects = rects;
}
void KPDFPage::setHighlight( HighlightRect * hr, bool add )
{
if ( !add )
deleteHighlights( hr->id );
m_highlights.append( hr );
}
void KPDFPage::setTransition( const KPDFPageTransition * transition )
{
delete m_transition;
......@@ -198,6 +225,23 @@ void KPDFPage::deletePixmapsAndRects()
m_rects.clear();
}
void KPDFPage::deleteHighlights( int id )
{
// delete highlights by ID
QValueList< HighlightRect * >::iterator it = m_highlights.begin(), end = m_highlights.end();
while ( it != end )
{
HighlightRect * highlight = *it;
if ( id == -1 || highlight->id == id )
{
it = m_highlights.remove( it );
delete highlight;
}
else
++it;
}
}
/** class KPDFPageRect **/
......@@ -254,3 +298,16 @@ void KPDFPageRect::deletePointer()
kdDebug() << "Object deletion not implemented for type '"
<< m_pointerType << "' ." << endl;
}
/** class HighlightRect **/
HighlightRect::HighlightRect()
: id( -1 ), left( 0.0 ), top( 0.0 ), right( 0.0 ), bottom( 0.0 )
{
}
HighlightRect::HighlightRect( double l, double t, double r, double b )
: id( -1 ), left( l ), top( t ), right( r ), bottom( b )
{
}
......@@ -18,6 +18,7 @@ class QRect;
class TextPage;
class KPDFPageRect;
class KPDFPageTransition;
class HighlightRect;
/**
* @short Collector for all the data belonging to a page.
......@@ -34,48 +35,53 @@ class KPDFPage
KPDFPage( uint number, float width, float height, int rotation );
~KPDFPage();
enum KPDFPageAttributes { Highlight = 1, Bookmark = 2 };