Commit 9e73b423 authored by Enrico Ros's avatar Enrico Ros

Fixed type-ahead searches. Reworked search code. Found alternative way to

setRasterOp. Cleaned up RenningSearch class. Performance fix on actions.

svn path=/trunk/kdegraphics/kpdf/; revision=385447
parent 6fb78405
......@@ -6,22 +6,20 @@ Legend:
MRG - MeRGed (code from a branch or a patch)
Status:
-> 2005-02-02: Features for 3.4 done. Ready for bugfixes.
-> 2005-01-27: Stable and leakchecked. Usability: needs testing.
-> 2005-01-20: Stable. Apart from a bad (but still safe) memory deallocation
mechanism the core is ready for a public release.
In progress 3.4 features (deadline is 2k5-Feb-2):
-> new word highlighting for searches / other highlights
More items (first items will enter 'In progress list' first):
-> go to next/previous bookmark actions (showing in thumbnailslist rmb popup too)
-> viewport restoring: sometimes it seems to restore the viewport a bit under where it was
-> viewport restoring: save the page width setting between runs (save/restore zoom factor)
-> presentation: provide a pageX/totalPages indicator in addition to the circle one
-> add scrollbar marks for bookmarks (like kate)
-> google search in the page
-> search: google search in the page
-> cleanup code and update README.png
-> find-as-you-type: use shortcut for 'find next' action (not the default one)
-> search: use shortcut for 'find next' action (not the default one) in find-ahead
-> show Viewport in ThumbnailsList (blended/contour)
-> Delay TOC (DocumentSynapsis) generation (and move it on thread)
-> refactor ThumbnailsList to do internal rendering as pageview does (way faster
......@@ -79,6 +77,7 @@ More items (first items will enter 'In progress list' first):
-> export: export to other formats keeping formatting (a dream.. except for PNG :-) (PS is easy, we just have PSOutputDev that does it :-D)
Done (newest features come first):
-> FIX: memleaks, bugfixes (1,..)
-> CHG: changes and cleanups in pageView's mouse handling functions
-> ADD: KTTSD simple support: speech selection using kspeech api via pure dcop (don't break compatibiltiy)
-> CHG: right click and drag while in 'normal' mode changes to 'selection' mode and selects
......
......@@ -35,11 +35,13 @@
#include "conf/settings.h"
// structures used internally by KPDFDocument for local variables storage
class AllocatedPixmap;
class RunningSearch;
class KPDFDocumentPrivate
{
public:
// find descriptors (can handle multiple searches)
QMap< int, class RunningSearch * > searches;
// find descriptors, mapped by ID (we handle multiple searches)
QMap< int, RunningSearch * > searches;
// cached stuff
QString docFileName;
......@@ -52,7 +54,7 @@ class KPDFDocumentPrivate
// observers / requests / allocator stuff
QMap< int, DocumentObserver * > observers;
QValueList< PixmapRequest * > pixmapRequestsStack;
QValueList< class AllocatedPixmap * > allocatedPixmapsFifo;
QValueList< AllocatedPixmap * > allocatedPixmapsFifo;
int allocatedPixmapsTotalMemory;
// timers (memory checking / info saver)
......@@ -72,19 +74,18 @@ struct AllocatedPixmap
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
// store search properties
int continueOnPage;
HighlightRect continueOnMatch;
QValueList< int > highlightedPages;
// fields related to previous searches (used for 'continueSearch')
QString cachedString;
KPDFDocument::SearchType cachedType;
bool cachedCaseSensitive;
bool cachedViewportMove;
bool cachedNoDialogs;
QColor cachedColor;
};
#define foreachObserver( cmd ) {\
......@@ -415,7 +416,7 @@ void KPDFDocument::requestPixmaps( const QValueList< PixmapRequest * > & request
// add request to the 'stack' at the right place
if ( !request->priority )
// add priority zero requests to the top of the stack
d->pixmapRequestsStack.push_back( request );
d->pixmapRequestsStack.append( request );
else
{
// insert in stack sorted by priority
......@@ -550,57 +551,58 @@ void KPDFDocument::setNextViewport()
}
bool KPDFDocument::searchText( int searchID, const QString & text, bool fromStart,
bool caseSensitive, SearchType type, bool moveViewport, const QColor & color )
bool KPDFDocument::searchText( int searchID, const QString & text, bool fromStart, bool caseSensitive,
SearchType type, bool moveViewport, const QColor & color, bool noDialogs )
{
// add a new search descriptor to runningSearches
// if searchID search not recorded, create new descriptor and init params
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;
search->continueOnPage = -1;
d->searches[ searchID ] = search;
}
// 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 );
// update search stucture
bool newText = text != s->cachedString;
s->cachedString = text;
s->cachedType = type;
s->cachedCaseSensitive = caseSensitive;
s->cachedViewportMove = moveViewport;
s->cachedNoDialogs = noDialogs;
s->cachedColor = color;
// unhighlight pages and inform observers about that
QValueList< int >::iterator it = s->matchedPages.begin(), end = s->matchedPages.end();
// global data for search
bool foundAMatch = false;
QValueList< int > pagesToNotify;
// remove highlights from pages and queue them for notifying changes
pagesToNotify += s->highlightedPages;
QValueList< int >::iterator it = s->highlightedPages.begin(), end = s->highlightedPages.end();
for ( ; it != end; ++it )
{
int pageNumber = *it;
pages_vector[ pageNumber ]->deleteHighlights( searchID );
foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) );
}
s->matchedPages.clear();
pages_vector[ *it ]->deleteHighlights( searchID );
s->highlightedPages.clear();
//
bool foundAMatch = false;
// set hourglass cursor
QApplication::setOverrideCursor( waitCursor );
// [1] ALLDOC - proces all document marking pages
// 1. ALLDOC - proces all document marking pages
if ( type == AllDoc )
{
// perform a linear search/mark
// search and highlight text on all pages
QValueVector< KPDFPage * >::iterator it = pages_vector.begin(), end = pages_vector.end();
for ( ; it != end; ++it )
{
// get page (from the first to the last)
KPDFPage * page = *it;
int pageNumber = page->number();
// request search page if needed
if ( !page->hasSearchPage() )
requestTextPage( page->number() );
requestTextPage( pageNumber );
bool found = false;
// loop on a page adding highlights for all found items
bool addedHighlights = false;
HighlightRect * lastMatch = 0;
while ( 1 )
{
......@@ -612,66 +614,64 @@ bool KPDFDocument::searchText( int searchID, const QString & text, bool fromStar
if ( !lastMatch )
break;
found = true;
// add highligh rect to the page
lastMatch->id = searchID;
lastMatch->color = color;
page->setHighlight( lastMatch );
s->lastMatch = *lastMatch;
addedHighlights = true;
}
if ( found )
// if added highlights, udpate internals and queue page for notify
if ( addedHighlights )
{
foundAMatch = true;
s->matchedPages.append( page->number() );
s->highlightedPages.append( pageNumber );
if ( !pagesToNotify.contains( pageNumber ) )
pagesToNotify.append( pageNumber );
}
}
// send page lists
// reset cursor to previous shape
QApplication::restoreOverrideCursor();
// send page lists if found anything new
//if ( foundAMatch ) ?maybe?
foreachObserver( notifySetup( pages_vector, false ) );
}
// [2] NEXTMATCH - find next matching item (or start from top)
else if ( s->type == NextMatch )
// 2. NEXTMATCH - find next matching item (or start from top)
else if ( 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
{
s->lastPage = 0;
}
HighlightRect * r = 0;
int currentPage = fromStart ? 0 : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage);
KPDFPage * lastPage = fromStart ? 0 : pages_vector[ currentPage ];
// continue checking last SearchPage first (if it is the current page)
if ( lastPage && lastPage->number() == s->lastPage )
{
r = lastPage->searchText( text, caseSensitive, &s->lastMatch );
if ( !r )
HighlightRect * r = 0;
if ( lastPage && lastPage->number() == s->continueOnPage )
{
lastPage->deleteHighlights( searchID );
currentPage++;
}
if ( newText )
r = lastPage->searchText( text, caseSensitive );
else
r = lastPage->searchText( text, caseSensitive, &s->continueOnMatch );
if ( r )
{
s->lastMatch = *r;
s->matchedPages.push_back( currentPage );
s->continueOnMatch = *r;
s->highlightedPages.append( currentPage );
}
else
currentPage++;
}
// if no match found, loop through the whole doc, starting from currentPage
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 )
if ( !noDialogs && KMessageBox::questionYesNo(0, i18n("End of document reached.\nContinue from the beginning?")) == KMessageBox::Yes )
currentPage = 0;
else
break;
......@@ -681,62 +681,75 @@ bool KPDFDocument::searchText( int searchID, const QString & text, bool fromStar
requestTextPage( page->number() );
if ( (r = page->searchText( text, caseSensitive )) )
{
s->lastPage = currentPage;
s->lastMatch = *r;
s->matchedPages.push_back( currentPage );
s->continueOnPage = currentPage;
s->continueOnMatch = *r;
s->highlightedPages.append( currentPage );
break;
}
currentPage++;
}
}
// reset cursor to previous shape
QApplication::restoreOverrideCursor();
// if a match has been found..
if ( r )
{
// add highlight to the page
foundAMatch = true;
// ..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
// ..queue page for notifying changes..
if ( !pagesToNotify.contains( pageNumber ) )
pagesToNotify.append( pageNumber );
// ..move the viewport to show the searched word centered
if ( moveViewport )
{
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*/ )
}
else if ( !noDialogs )
KMessageBox::information( 0, i18n("No matches found for '%1'.").arg( text ) );
}
// 3. PREVMATCH //TODO
else if ( type == PrevMatch )
{
//TODO
}
// 4. GOOGLELIKE //TODO
else if ( type == GoogleLike )
{
//TODO
}
// reset cursor to previous shape
QApplication::restoreOverrideCursor();
// notify observers about highlights changes
QValueList< int >::iterator nIt = pagesToNotify.begin(), nEnd = pagesToNotify.end();
for ( ; nIt != nEnd; ++nIt )
foreachObserver( notifyPageChanged( *nIt, DocumentObserver::Highlights ) );
// return if search has found one or more matches
return foundAMatch;
}
bool KPDFDocument::continueSearch( int searchID )
{
// check if searchID is present in runningSearches
if ( !d->searches.contains( searchID ) )
return false;
// get previous parameters for search
// start search with cached parameters from last search by searchID
RunningSearch * p = d->searches[ searchID ];
return searchText( searchID, p->text, false, p->caseSensitive, p->type, p->moveViewport, p->highlightColor );
return searchText( searchID, p->cachedString, false, p->cachedCaseSensitive,
p->cachedType, p->cachedViewportMove, p->cachedColor,
p->cachedNoDialogs );
}
void KPDFDocument::resetSearch( int searchID )
......@@ -749,14 +762,16 @@ void KPDFDocument::resetSearch( int searchID )
RunningSearch * s = d->searches[ searchID ];
// unhighlight pages and inform observers about that
QValueList< int >::iterator it = s->matchedPages.begin(), end = s->matchedPages.end();
QValueList< int >::iterator it = s->highlightedPages.begin(), end = s->highlightedPages.end();
for ( ; it != end; ++it )
{
int pageNumber = *it;
pages_vector[ pageNumber ]->deleteHighlights( searchID );
foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) );
}
s->matchedPages.clear();
// send the setup signal too (to update views that filter on matches)
foreachObserver( notifySetup( pages_vector, false ) );
// remove serch from the runningSearches list and delete it
d->searches.remove( searchID );
......
......@@ -82,7 +82,7 @@ class KPDFDocument : public QObject
enum SearchType { NextMatch, PrevMatch, AllDoc, GoogleLike };
bool searchText( int searchID, const QString & text, bool fromStart, bool caseSensitive,
SearchType type, bool moveViewport, const QColor & color );
SearchType type, bool moveViewport, const QColor & color, bool noDialogs = false );
bool continueSearch( int searchID );
void resetSearch( int searchID );
......
......@@ -65,6 +65,9 @@
#include "core/document.h"
#include "core/page.h"
// definition of searchID for this class
#define PART_SEARCH_ID 1
typedef KParts::GenericFactory<KPDF::Part> KPDFPartFactory;
K_EXPORT_COMPONENT_FACTORY(libkpdfpart, KPDFPartFactory)
......@@ -75,7 +78,8 @@ unsigned int Part::m_count = 0;
Part::Part(QWidget *parentWidget, const char *widgetName,
QObject *parent, const char *name,
const QStringList & /*args*/ )
: DCOPObject("kpdf"), KParts::ReadOnlyPart(parent, name), m_showMenuBarAction(0), m_actionsSearched(false)
: DCOPObject("kpdf"), KParts::ReadOnlyPart(parent, name), m_showMenuBarAction(0),
m_actionsSearched(false), m_searchStarted(false)
{
// load catalog for translation
KGlobal::locale()->insertCatalogue("kpdf");
......@@ -255,7 +259,7 @@ Part::Part(QWidget *parentWidget, const char *widgetName,
// set our XML-UI resource file
setXMLFile("part.rc");
updateActions();
updateViewActions();
slotWatchFile();
}
......@@ -278,7 +282,7 @@ void Part::notifyViewportChanged( bool /*smoothMove*/ )
int viewportPage = m_document->viewport().pageNumber;
if ( viewportPage != lastPage )
{
updateActions();
updateViewActions();
lastPage = viewportPage;
}
}
......@@ -315,11 +319,15 @@ bool Part::openFile()
bool ok = m_document->openDocument( m_file );
// update one-time actions
m_find->setEnabled( ok );
m_findNext->setEnabled( ok );
m_saveAs->setEnabled( ok );
m_printPreview->setEnabled( ok );
m_showProperties->setEnabled( ok );
m_showPresentation->setEnabled( ok );
// update viewing actions
updateActions();
updateViewActions();
if ( !ok )
{
......@@ -353,6 +361,21 @@ bool Part::openURL(const KURL &url)
return b;
}
bool Part::closeURL()
{
m_find->setEnabled( false );
m_findNext->setEnabled( false );
m_saveAs->setEnabled( false );
m_printPreview->setEnabled( false );
m_showProperties->setEnabled( false );
m_showPresentation->setEnabled( false );
updateViewActions();
m_searchStarted = false;
if (!m_file.isEmpty()) m_watcher->removeFile(m_file);
m_document->closeDocument();
return KParts::ReadOnlyPart::closeURL();
}
void Part::slotWatchFile()
{
Settings::setWatchFile(m_watchFile->isChecked());
......@@ -388,19 +411,10 @@ void Part::slotDoFileDirty()
}
}
bool Part::closeURL()
void Part::updateViewActions()
{
m_saveAs->setEnabled( false );
m_printPreview->setEnabled( false );
if (!m_file.isEmpty()) m_watcher->removeFile(m_file);
m_document->closeDocument();
return KParts::ReadOnlyPart::closeURL();
}
void Part::updateActions()
{
bool ok = m_document->pages() > 0;
if ( ok )
bool opened = m_document->pages() > 0;
if ( opened )
{
bool atBegin = m_document->currentPage() < 1;
bool atEnd = m_document->currentPage() >= (m_document->pages() - 1);
......@@ -422,9 +436,6 @@ void Part::updateActions()
m_historyBack->setEnabled( false );
m_historyNext->setEnabled( false );
}
m_find->setEnabled( ok );
m_showProperties->setEnabled( ok );
m_showPresentation->setEnabled( ok );
}
void Part::enableTOC(bool enable)
......@@ -512,15 +523,18 @@ void Part::slotFind()
#endif
if ( dlg.exec() == QDialog::Accepted )
{
m_findNext->setEnabled( true );
m_document->searchText( 10, dlg.pattern(), false, dlg.options() & KFindDialog::CaseSensitive,
m_searchStarted = true;
m_document->searchText( PART_SEARCH_ID, dlg.pattern(), false, dlg.options() & KFindDialog::CaseSensitive,
KPDFDocument::NextMatch, true, Qt::red );
}
}
void Part::slotFindNext()
{
m_document->continueSearch( 10 );
if ( m_searchStarted )
m_document->continueSearch( PART_SEARCH_ID );
else
slotFind();
}
void Part::slotSaveFileAs()
......
......@@ -101,7 +101,7 @@ protected slots:
void slotShowProperties();
void slotShowPresentation();
// can be connected to widget elements
void updateActions();
void updateViewActions();
void enableTOC(bool enable);
public slots:
......@@ -151,6 +151,7 @@ private:
KToggleAction* m_showMenuBarAction;
KToggleAction* m_showFullScreenAction;
bool m_actionsSearched;
bool m_searchStarted;
};
......
......@@ -46,6 +46,7 @@ void PagePainter::paintPageOnPainter( const KPDFPage * page, int id, int flags,
}
}
// if have no pixmap, draw blank page with gray cross and exit
if ( !pixmap )
{
if ( Settings::changeColors() &&
......@@ -62,11 +63,34 @@ void PagePainter::paintPageOnPainter( const KPDFPage * page, int id, int flags,
return;
}
// clear accessibility flag if not really needed
if ( !Settings::changeColors() || Settings::renderMode() == Settings::EnumRenderMode::Paper )
flags &= ~Accessibility;
// use backBuffer if accessiblity (page color processing) is needed
bool backBuffer = flags & Accessibility;
// use backBuffer even if we have to render highlights
if ( !backBuffer && (flags & Highlights) && !page->m_highlights.isEmpty() )
{
// precalc normalized limits rect for intersection
double nXMin = (double)limits.left() / (double)width,
nXMax = (double)(limits.right() + 1) / (double)width,
nYMin = (double)limits.top() / (double)height,
nYMax = (double)(limits.bottom() + 1) / (double)height;
// if an highlightRect intersects limits, use backBuffer for painting
QValueList< HighlightRect * >::const_iterator hIt = page->m_highlights.begin(), hEnd = page->m_highlights.end();
for ( ; hIt != hEnd; ++hIt )
{
HighlightRect * r = *hIt;
// intersection: A) highlightRect, B) normalized limits
if ( (r->left < nXMax) && (r->right > nXMin) && (r->top < nYMax) && (r->bottom > nYMin) )
{
backBuffer = true;
break;
}
}
}
// we have a pixmap to paint, now let's paint it using a direct or buffered painter
bool backBuffer = Settings::changeColors() &&
Settings::renderMode() != Settings::EnumRenderMode::Paper;
// if PagePainter::Accessibility is not in 'flags', disable backBuffer
backBuffer = backBuffer && (flags & Accessibility);
QPixmap * backPixmap = 0;
QPainter * p = destPainter;
if ( backBuffer )
......@@ -171,26 +195,53 @@ void PagePainter::paintPageOnPainter( const KPDFPage * page, int id, int flags,
}
// draw selection (note: it is rescaled since the text page is at 100% scale)
if ( ( flags & Highlights ) && !page->m_highlights.isEmpty() )
if ( backBuffer && ( flags & Highlights ) && !page->m_highlights.isEmpty() )
{
QImage backImage = backPixmap->convertToImage();
// draw highlights that are inside the 'limits' paint region
QValueList< HighlightRect * >::const_iterator hIt = page->m_highlights.begin(), hEnd = page->m_highlights.end();
for ( ; hIt != hEnd; ++hIt )
{
HighlightRect * r = *hIt;
int x = (int)( r->left * width ),
y = (int)( r->top * height ),
w = (int)( r->right * width ) - x,
h = (int)( r->bottom * height ) - y;
if ( w > 0 && h > 0 )
QRect highlightRect( (int)(r->left * width), (int)(r->top * height),
(int)((r->right - r->left) * width) + 1, (int)((r->bottom - r->top) * height) + 1 );
if ( highlightRect.isValid() && highlightRect.intersects( limits ) )
{
#if 0
// TODO setRasterOp is no more on Qt4 find an alternative way of doing this
p->setBrush( Qt::SolidPattern );
p->setPen( QPen( Qt::black, 1 ) ); // should not be necessary bug a Qt bug makes it necessary
p->setRasterOp( Qt::NotROP );
p->drawRect( x, y, w, h );
p->drawRect( highlightRect );
#else
// TODO this is the other way
// find out the rect to highlight on pixmap
highlightRect = highlightRect.intersect( limits );
highlightRect.moveBy( -limits.left(), -limits.top() );
// Manual yellow highlight
unsigned int * data = (unsigned int *)backImage.bits();
int val,
rh = r->color.red(),
gh = r->color.green(),
bh = r->color.blue();
for( int y = highlightRect.top(); y < highlightRect.bottom(); ++y )
{
int offset = y * backImage.width();
for( int x = highlightRect.left(); x < highlightRect.right(); ++x )
{
val = data[x + offset];
int newR = (qRed(val) * rh) / 255,
newG = (qGreen(val) * gh) / 255,
newB = (qBlue(val) * bh) / 255;
data[x + offset] = qRgba( newR, newG, newB, 255 );