1 /***************************************************************************
2 * Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
3 * Copyright (C) 2004-2008 by Albert Astals Cid <aacid@kde.org> *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 ***************************************************************************/
12 #include "document_p.h"
15 #define _WIN32_WINNT 0x0500
19 // qt/kde/system includes
20 #include <QtCore/QtAlgorithms>
21 #include <QtCore/QDir>
22 #include <QtCore/QFile>
23 #include <QtCore/QFileInfo>
24 #include <QtCore/QMap>
25 #include <QtCore/QProcess>
26 #include <QtCore/QTextStream>
27 #include <QtCore/QTimer>
28 #include <QtGui/QApplication>
29 #include <QtGui/QLabel>
30 #include <QtGui/QPrinter>
31 #include <QtGui/QPrintDialog>
33 #include <kaboutdata.h>
34 #include <kauthorized.h>
35 #include <kcomponentdata.h>
36 #include <kconfigdialog.h>
38 #include <klibloader.h>
40 #include <kmessagebox.h>
41 #include <kmimetypetrader.h>
43 #include <kstandarddirs.h>
44 #include <ktemporaryfile.h>
45 #include <ktoolinvocation.h>
49 #include "annotations.h"
50 #include "annotations_p.h"
51 #include "audioplayer.h"
52 #include "audioplayer_p.h"
53 #include "bookmarkmanager.h"
54 #include "chooseenginedialog_p.h"
56 #include "generator_p.h"
57 #include "interfaces/configinterface.h"
58 #include "interfaces/guiinterface.h"
59 #include "interfaces/printinterface.h"
60 #include "interfaces/saveinterface.h"
64 #include "pagecontroller_p.h"
67 #include "sourcereference.h"
68 #include "sourcereference_p.h"
72 #include <config-okular.h>
74 using namespace Okular
;
76 struct AllocatedPixmap
82 // public constructor: initialize data
83 AllocatedPixmap( int i
, int p
, qulonglong m
) : id( i
), page( p
), memory( m
) {}
88 // store search properties
90 RegularAreaRect continueOnMatch
;
91 QSet
< int > highlightedPages
;
93 // fields related to previous searches (used for 'continueSearch')
95 Document::SearchType cachedType
;
96 Qt::CaseSensitivity cachedCaseSensitivity
;
97 bool cachedViewportMove
: 1;
98 bool cachedNoDialogs
: 1;
102 #define foreachObserver( cmd ) {\
103 QMap< int, DocumentObserver * >::const_iterator it=d->m_observers.constBegin(), end=d->m_observers.constEnd();\
104 for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }
106 #define foreachObserverD( cmd ) {\
107 QMap< int, DocumentObserver * >::const_iterator it = m_observers.constBegin(), end = m_observers.constEnd();\
108 for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }
110 #define OKULAR_HISTORY_MAXSTEPS 100
111 #define OKULAR_HISTORY_SAVEDSTEPS 10
113 /***** Document ******/
115 QString
DocumentPrivate::pagesSizeString() const
119 if (m_generator
->pagesSizeMetric() != Generator::None
)
121 QSizeF size
= m_parent
->allPagesSize();
122 if (size
.isValid()) return localizedSize(size
);
123 else return QString();
125 else return QString();
127 else return QString();
130 QString
DocumentPrivate::localizedSize(const QSizeF
&size
) const
132 double inchesWidth
= 0, inchesHeight
= 0;
133 switch (m_generator
->pagesSizeMetric())
135 case Generator::Points
:
136 inchesWidth
= size
.width() / 72.0;
137 inchesHeight
= size
.height() / 72.0;
140 case Generator::None
:
143 if (KGlobal::locale()->measureSystem() == KLocale::Imperial
)
145 return i18n("%1 x %2 in", inchesWidth
, inchesHeight
);
149 return i18n("%1 x %2 mm", inchesWidth
* 25.4, inchesHeight
* 25.4);
153 void DocumentPrivate::cleanupPixmapMemory( qulonglong
/*sure? bytesOffset*/ )
155 // [MEM] choose memory parameters based on configuration profile
156 qulonglong clipValue
= 0;
157 qulonglong memoryToFree
= 0;
158 switch ( Settings::memoryLevel() )
160 case Settings::EnumMemoryLevel::Low
:
161 memoryToFree
= m_allocatedPixmapsTotalMemory
;
164 case Settings::EnumMemoryLevel::Normal
:
166 qulonglong thirdTotalMemory
= getTotalMemory() / 3;
167 qulonglong freeMemory
= getFreeMemory();
168 if (m_allocatedPixmapsTotalMemory
> thirdTotalMemory
) memoryToFree
= m_allocatedPixmapsTotalMemory
- thirdTotalMemory
;
169 if (m_allocatedPixmapsTotalMemory
> freeMemory
) clipValue
= (m_allocatedPixmapsTotalMemory
- freeMemory
) / 2;
173 case Settings::EnumMemoryLevel::Aggressive
:
175 qulonglong freeMemory
= getFreeMemory();
176 if (m_allocatedPixmapsTotalMemory
> freeMemory
) clipValue
= (m_allocatedPixmapsTotalMemory
- freeMemory
) / 2;
181 if ( clipValue
> memoryToFree
)
182 memoryToFree
= clipValue
;
184 if ( memoryToFree
> 0 )
186 // [MEM] free memory starting from older pixmaps
188 QLinkedList
< AllocatedPixmap
* >::iterator pIt
= m_allocatedPixmapsFifo
.begin();
189 QLinkedList
< AllocatedPixmap
* >::iterator pEnd
= m_allocatedPixmapsFifo
.end();
190 while ( (pIt
!= pEnd
) && (memoryToFree
> 0) )
192 AllocatedPixmap
* p
= *pIt
;
193 if ( m_observers
.value( p
->id
)->canUnloadPixmap( p
->page
) )
195 // update internal variables
196 pIt
= m_allocatedPixmapsFifo
.erase( pIt
);
197 m_allocatedPixmapsTotalMemory
-= p
->memory
;
198 memoryToFree
-= p
->memory
;
201 m_pagesVector
.at( p
->page
)->deletePixmap( p
->id
);
202 // delete allocation descriptor
207 //p--rintf("freeMemory A:[%d -%d = %d] \n", m_allocatedPixmapsFifo.count() + pagesFreed, pagesFreed, m_allocatedPixmapsFifo.count() );
211 qulonglong
DocumentPrivate::getTotalMemory()
213 static qulonglong cachedValue
= 0;
217 #if defined(Q_OS_LINUX)
218 // if /proc/meminfo doesn't exist, return 128MB
219 QFile
memFile( "/proc/meminfo" );
220 if ( !memFile
.open( QIODevice::ReadOnly
) )
221 return (cachedValue
= 134217728);
223 // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
224 // and 'Cached' fields. consider swapped memory as used memory.
225 QTextStream
readStream( &memFile
);
228 QString entry
= readStream
.readLine();
229 if ( entry
.isNull() ) break;
230 if ( entry
.startsWith( "MemTotal:" ) )
231 return (cachedValue
= (1024 * entry
.section( ' ', -2, -2 ).toInt()));
233 #elif defined(Q_OS_WIN)
236 GlobalMemoryStatusEx (&stat
);
238 return ( cachedValue
= stat
.ullTotalPhys
);
240 return (cachedValue
= 134217728);
243 qulonglong
DocumentPrivate::getFreeMemory()
245 static QTime lastUpdate
= QTime::currentTime();
246 static qulonglong cachedValue
= 0;
248 if ( lastUpdate
.secsTo( QTime::currentTime() ) <= 2 )
251 #if defined(Q_OS_LINUX)
252 // if /proc/meminfo doesn't exist, return MEMORY FULL
253 QFile
memFile( "/proc/meminfo" );
254 if ( !memFile
.open( QIODevice::ReadOnly
) )
257 // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
258 // and 'Cached' fields. consider swapped memory as used memory.
259 qulonglong memoryFree
= 0;
261 QTextStream
readStream( &memFile
);
264 entry
= readStream
.readLine();
265 if ( entry
.isNull() ) break;
266 if ( entry
.startsWith( "MemFree:" ) ||
267 entry
.startsWith( "Buffers:" ) ||
268 entry
.startsWith( "Cached:" ) ||
269 entry
.startsWith( "SwapFree:" ) )
270 memoryFree
+= entry
.section( ' ', -2, -2 ).toInt();
271 if ( entry
.startsWith( "SwapTotal:" ) )
272 memoryFree
-= entry
.section( ' ', -2, -2 ).toInt();
276 lastUpdate
= QTime::currentTime();
278 return ( cachedValue
= (1024 * memoryFree
) );
279 #elif defined(Q_OS_WIN)
282 GlobalMemoryStatusEx (&stat
);
284 lastUpdate
= QTime::currentTime();
286 return ( cachedValue
= stat
.ullAvailPhys
);
288 // tell the memory is full.. will act as in LOW profile
293 void DocumentPrivate::loadDocumentInfo()
294 // note: load data and stores it internally (document or pages). observers
295 // are still uninitialized at this point so don't access them
297 //kDebug(OkularDebug).nospace() << "Using '" << d->m_xmlFileName << "' as document info file.";
298 if ( m_xmlFileName
.isEmpty() )
301 QFile
infoFile( m_xmlFileName
);
302 if ( !infoFile
.exists() || !infoFile
.open( QIODevice::ReadOnly
) )
305 // Load DOM from XML file
306 QDomDocument
doc( "documentInfo" );
307 if ( !doc
.setContent( &infoFile
) )
309 kDebug(OkularDebug
) << "Can't load XML pair! Check for broken xml.";
315 QDomElement root
= doc
.documentElement();
316 if ( root
.tagName() != "documentInfo" )
319 KUrl
documentUrl( root
.attribute( "url" ) );
321 // Parse the DOM tree
322 QDomNode topLevelNode
= root
.firstChild();
323 while ( topLevelNode
.isElement() )
325 QString catName
= topLevelNode
.toElement().tagName();
327 // Restore page attributes (bookmark, annotations, ...) from the DOM
328 if ( catName
== "pageList" )
330 QDomNode pageNode
= topLevelNode
.firstChild();
331 while ( pageNode
.isElement() )
333 QDomElement pageElement
= pageNode
.toElement();
334 if ( pageElement
.hasAttribute( "number" ) )
336 // get page number (node's attribute)
338 int pageNumber
= pageElement
.attribute( "number" ).toInt( &ok
);
340 // pass the domElement to the right page, to read config data from
341 if ( ok
&& pageNumber
>= 0 && pageNumber
< (int)m_pagesVector
.count() )
342 m_pagesVector
[ pageNumber
]->d
->restoreLocalContents( pageElement
);
344 pageNode
= pageNode
.nextSibling();
348 // Restore 'general info' from the DOM
349 else if ( catName
== "generalInfo" )
351 QDomNode infoNode
= topLevelNode
.firstChild();
352 while ( infoNode
.isElement() )
354 QDomElement infoElement
= infoNode
.toElement();
356 // restore viewports history
357 if ( infoElement
.tagName() == "history" )
360 m_viewportHistory
.clear();
361 // append old viewports
362 QDomNode historyNode
= infoNode
.firstChild();
363 while ( historyNode
.isElement() )
365 QDomElement historyElement
= historyNode
.toElement();
366 if ( historyElement
.hasAttribute( "viewport" ) )
368 QString vpString
= historyElement
.attribute( "viewport" );
369 m_viewportIterator
= m_viewportHistory
.insert( m_viewportHistory
.end(),
370 DocumentViewport( vpString
) );
372 historyNode
= historyNode
.nextSibling();
375 if ( m_viewportHistory
.isEmpty() )
376 m_viewportIterator
= m_viewportHistory
.insert( m_viewportHistory
.end(), DocumentViewport() );
378 else if ( infoElement
.tagName() == "rotation" )
380 QString str
= infoElement
.text();
382 int newrotation
= !str
.isEmpty() ? ( str
.toInt( &ok
) % 4 ) : 0;
383 if ( ok
&& newrotation
!= 0 )
385 setRotationInternal( newrotation
, false );
388 else if ( infoElement
.tagName() == "views" )
390 QDomNode viewNode
= infoNode
.firstChild();
391 while ( viewNode
.isElement() )
393 QDomElement viewElement
= viewNode
.toElement();
394 if ( viewElement
.tagName() == "view" )
396 const QString viewName
= viewElement
.attribute( "name" );
397 Q_FOREACH ( View
* view
, m_views
)
399 if ( view
->name() == viewName
)
401 loadViewsInfo( view
, viewElement
);
406 viewNode
= viewNode
.nextSibling();
409 infoNode
= infoNode
.nextSibling();
413 topLevelNode
= topLevelNode
.nextSibling();
417 void DocumentPrivate::loadViewsInfo( View
*view
, const QDomElement
&e
)
419 QDomNode viewNode
= e
.firstChild();
420 while ( viewNode
.isElement() )
422 QDomElement viewElement
= viewNode
.toElement();
424 if ( viewElement
.tagName() == "zoom" )
426 const QString valueString
= viewElement
.attribute( "value" );
427 bool newzoom_ok
= true;
428 const double newzoom
= !valueString
.isEmpty() ? valueString
.toDouble( &newzoom_ok
) : 1.0;
429 if ( newzoom_ok
&& newzoom
!= 0
430 && view
->supportsCapability( View::Zoom
)
431 && ( view
->capabilityFlags( View::Zoom
) & ( View::CapabilityRead
| View::CapabilitySerializable
) ) )
433 view
->setCapability( View::Zoom
, newzoom
);
435 const QString modeString
= viewElement
.attribute( "mode" );
436 bool newmode_ok
= true;
437 const int newmode
= !modeString
.isEmpty() ? modeString
.toInt( &newmode_ok
) : 2;
439 && view
->supportsCapability( View::ZoomModality
)
440 && ( view
->capabilityFlags( View::ZoomModality
) & ( View::CapabilityRead
| View::CapabilitySerializable
) ) )
442 view
->setCapability( View::ZoomModality
, newmode
);
446 viewNode
= viewNode
.nextSibling();
450 void DocumentPrivate::saveViewsInfo( View
*view
, QDomElement
&e
) const
452 if ( view
->supportsCapability( View::Zoom
)
453 && ( view
->capabilityFlags( View::Zoom
) & ( View::CapabilityRead
| View::CapabilitySerializable
) )
454 && view
->supportsCapability( View::ZoomModality
)
455 && ( view
->capabilityFlags( View::ZoomModality
) & ( View::CapabilityRead
| View::CapabilitySerializable
) ) )
457 QDomElement zoomEl
= e
.ownerDocument().createElement( "zoom" );
458 e
.appendChild( zoomEl
);
460 const double zoom
= view
->capability( View::Zoom
).toDouble( &ok
);
461 if ( ok
&& zoom
!= 0 )
463 zoomEl
.setAttribute( "value", zoom
);
465 const int mode
= view
->capability( View::ZoomModality
).toInt( &ok
);
468 zoomEl
.setAttribute( "mode", mode
);
473 QString
DocumentPrivate::giveAbsolutePath( const QString
& fileName
) const
475 if ( !QDir::isRelativePath( fileName
) )
478 if ( !m_url
.isValid() )
481 return m_url
.upUrl().url() + fileName
;
484 bool DocumentPrivate::openRelativeFile( const QString
& fileName
)
486 QString absFileName
= giveAbsolutePath( fileName
);
487 if ( absFileName
.isEmpty() )
490 kDebug(OkularDebug
).nospace() << "openDocument: '" << absFileName
<< "'";
492 emit m_parent
->openUrl( absFileName
);
496 Generator
* DocumentPrivate::loadGeneratorLibrary( const KService::Ptr
&service
)
498 KPluginFactory
*factory
= KPluginLoader( service
->library() ).factory();
501 kWarning(OkularDebug
).nospace() << "Invalid plugin factory for " << service
->library() << "!";
504 Generator
* generator
= factory
->create
< Okular::Generator
>( 0 );
505 GeneratorInfo
info( factory
->componentData() );
506 info
.generator
= generator
;
507 if ( info
.data
.isValid() && info
.data
.aboutData() )
508 info
.catalogName
= info
.data
.aboutData()->catalogName();
509 m_loadedGenerators
.insert( service
->name(), info
);
513 void DocumentPrivate::loadAllGeneratorLibraries()
515 if ( m_generatorsLoaded
)
518 m_generatorsLoaded
= true;
520 QString
constraint("([X-KDE-Priority] > 0) and (exist Library)") ;
521 KService::List offers
= KServiceTypeTrader::self()->query( "okular/Generator", constraint
);
522 loadServiceList( offers
);
525 void DocumentPrivate::loadServiceList( const KService::List
& offers
)
527 int count
= offers
.count();
531 for ( int i
= 0; i
< count
; ++i
)
533 QString propName
= offers
.at(i
)->name();
534 // don't load already loaded generators
535 QHash
< QString
, GeneratorInfo
>::const_iterator genIt
= m_loadedGenerators
.constFind( propName
);
536 if ( !m_loadedGenerators
.isEmpty() && genIt
!= m_loadedGenerators
.end() )
539 Generator
* g
= loadGeneratorLibrary( offers
.at(i
) );
544 void DocumentPrivate::unloadGenerator( const GeneratorInfo
& info
)
546 delete info
.generator
;
549 void DocumentPrivate::cacheExportFormats()
551 if ( m_exportCached
)
554 const ExportFormat::List formats
= m_generator
->exportFormats();
555 for ( int i
= 0; i
< formats
.count(); ++i
)
557 if ( formats
.at( i
).mimeType()->name() == QLatin1String( "text/plain" ) )
558 m_exportToText
= formats
.at( i
);
560 m_exportFormats
.append( formats
.at( i
) );
563 m_exportCached
= true;
566 ConfigInterface
* DocumentPrivate::generatorConfig( GeneratorInfo
& info
)
568 if ( info
.configChecked
)
571 info
.config
= qobject_cast
< Okular::ConfigInterface
* >( info
.generator
);
572 info
.configChecked
= true;
576 SaveInterface
* DocumentPrivate::generatorSave( GeneratorInfo
& info
)
578 if ( info
.saveChecked
)
581 info
.save
= qobject_cast
< Okular::SaveInterface
* >( info
.generator
);
582 info
.saveChecked
= true;
586 void DocumentPrivate::saveDocumentInfo() const
588 if ( m_xmlFileName
.isEmpty() )
591 QFile
infoFile( m_xmlFileName
);
592 if (infoFile
.open( QIODevice::WriteOnly
| QIODevice::Truncate
) )
595 QDomDocument
doc( "documentInfo" );
596 QDomProcessingInstruction xmlPi
= doc
.createProcessingInstruction(
597 QString::fromLatin1( "xml" ), QString::fromLatin1( "version=\"1.0\" encoding=\"utf-8\"" ) );
598 doc
.appendChild( xmlPi
);
599 QDomElement root
= doc
.createElement( "documentInfo" );
600 root
.setAttribute( "url", m_url
.pathOrUrl() );
601 doc
.appendChild( root
);
603 // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM
604 QDomElement pageList
= doc
.createElement( "pageList" );
605 root
.appendChild( pageList
);
606 // <page list><page number='x'>.... </page> save pages that hold data
607 QVector
< Page
* >::const_iterator pIt
= m_pagesVector
.begin(), pEnd
= m_pagesVector
.end();
608 for ( ; pIt
!= pEnd
; ++pIt
)
609 (*pIt
)->d
->saveLocalContents( pageList
, doc
);
611 // 2.2. Save document info (current viewport, history, ... ) to DOM
612 QDomElement generalInfo
= doc
.createElement( "generalInfo" );
613 root
.appendChild( generalInfo
);
614 // create rotation node
615 if ( m_rotation
!= Rotation0
)
617 QDomElement rotationNode
= doc
.createElement( "rotation" );
618 generalInfo
.appendChild( rotationNode
);
619 rotationNode
.appendChild( doc
.createTextNode( QString::number( (int)m_rotation
) ) );
621 // <general info><history> ... </history> save history up to OKULAR_HISTORY_SAVEDSTEPS viewports
622 QLinkedList
< DocumentViewport
>::const_iterator backIterator
= m_viewportIterator
;
623 if ( backIterator
!= m_viewportHistory
.end() )
625 // go back up to OKULAR_HISTORY_SAVEDSTEPS steps from the current viewportIterator
626 int backSteps
= OKULAR_HISTORY_SAVEDSTEPS
;
627 while ( backSteps
-- && backIterator
!= m_viewportHistory
.begin() )
630 // create history root node
631 QDomElement historyNode
= doc
.createElement( "history" );
632 generalInfo
.appendChild( historyNode
);
634 // add old[backIterator] and present[viewportIterator] items
635 QLinkedList
< DocumentViewport
>::const_iterator endIt
= m_viewportIterator
;
637 while ( backIterator
!= endIt
)
639 QString name
= (backIterator
== m_viewportIterator
) ? "current" : "oldPage";
640 QDomElement historyEntry
= doc
.createElement( name
);
641 historyEntry
.setAttribute( "viewport", (*backIterator
).toString() );
642 historyNode
.appendChild( historyEntry
);
646 // create views root node
647 QDomElement viewsNode
= doc
.createElement( "views" );
648 generalInfo
.appendChild( viewsNode
);
649 Q_FOREACH ( View
* view
, m_views
)
651 QDomElement viewEntry
= doc
.createElement( "view" );
652 viewEntry
.setAttribute( "name", view
->name() );
653 viewsNode
.appendChild( viewEntry
);
654 saveViewsInfo( view
, viewEntry
);
657 // 3. Save DOM to XML file
658 QString xml
= doc
.toString();
659 QTextStream
os( &infoFile
);
660 os
.setCodec( "UTF-8" );
666 void DocumentPrivate::slotTimedMemoryCheck()
668 // [MEM] clean memory (for 'free mem dependant' profiles only)
669 if ( Settings::memoryLevel() != Settings::EnumMemoryLevel::Low
&&
670 m_allocatedPixmapsTotalMemory
> 1024*1024 )
671 cleanupPixmapMemory();
674 void DocumentPrivate::sendGeneratorRequest()
677 PixmapRequest
* request
= 0;
678 m_pixmapRequestsMutex
.lock();
679 while ( !m_pixmapRequestsStack
.isEmpty() && !request
)
681 PixmapRequest
* r
= m_pixmapRequestsStack
.last();
683 m_pixmapRequestsStack
.pop_back();
685 // request only if page isn't already present or request has invalid id
686 else if ( ( !r
->d
->mForce
&& r
->page()->hasPixmap( r
->id(), r
->width(), r
->height() ) ) || r
->id() <= 0 || r
->id() >= MAX_OBSERVER_ID
)
688 m_pixmapRequestsStack
.pop_back();
691 else if ( (long)r
->width() * (long)r
->height() > 20000000L )
693 m_pixmapRequestsStack
.pop_back();
694 if ( !m_warnedOutOfMemory
)
696 kWarning(OkularDebug
).nospace() << "Running out of memory on page " << r
->pageNumber()
697 << " (" << r
->width() << "x" << r
->height() << " px);";
698 kWarning(OkularDebug
) << "this message will be reported only once.";
699 m_warnedOutOfMemory
= true;
707 // if no request found (or already generated), return
710 m_pixmapRequestsMutex
.unlock();
714 // [MEM] preventive memory freeing
715 qulonglong pixmapBytes
= 4 * request
->width() * request
->height();
716 if ( pixmapBytes
> (1024 * 1024) )
717 cleanupPixmapMemory( pixmapBytes
);
719 // submit the request to the generator
720 if ( m_generator
->canGeneratePixmap() )
722 kDebug(OkularDebug
).nospace() << "sending request id=" << request
->id() << " " <<request
->width() << "x" << request
->height() << "@" << request
->pageNumber() << " async == " << request
->asynchronous();
723 m_pixmapRequestsStack
.removeAll ( request
);
725 if ( (int)m_rotation
% 2 )
728 // we always have to unlock _before_ the generatePixmap() because
729 // a sync generation would end with requestDone() -> deadlock, and
730 // we can not really know if the generator can do async requests
731 m_executingPixmapRequests
.push_back( request
);
732 m_pixmapRequestsMutex
.unlock();
733 m_generator
->generatePixmap( request
);
737 m_pixmapRequestsMutex
.unlock();
738 // pino (7/4/2006): set the polling interval from 10 to 30
739 QTimer::singleShot( 30, m_parent
, SLOT(sendGeneratorRequest()) );
743 void DocumentPrivate::rotationFinished( int page
)
745 QMap
< int, DocumentObserver
* >::const_iterator it
= m_observers
.begin(), end
= m_observers
.end();
746 for ( ; it
!= end
; ++ it
) {
747 (*it
)->notifyPageChanged( page
, DocumentObserver::Pixmap
| DocumentObserver::Annotations
);
751 void DocumentPrivate::fontReadingProgress( int page
)
753 emit m_parent
->fontReadingProgress( page
);
755 if ( page
>= (int)m_parent
->pages() - 1 )
757 emit m_parent
->fontReadingEnded();
759 m_fontsCached
= true;
763 void DocumentPrivate::fontReadingGotFont( const Okular::FontInfo
& font
)
765 // TODO try to avoid duplicate fonts
766 m_fontsCache
.append( font
);
768 emit m_parent
->gotFont( font
);
771 void DocumentPrivate::slotGeneratorConfigChanged( const QString
& )
776 // reparse generator config and if something changed clear Pages
777 bool configchanged
= false;
778 QHash
< QString
, GeneratorInfo
>::iterator it
= m_loadedGenerators
.begin(), itEnd
= m_loadedGenerators
.end();
779 for ( ; it
!= itEnd
; ++it
)
781 Okular::ConfigInterface
* iface
= generatorConfig( it
.value() );
784 bool it_changed
= iface
->reparseConfig();
785 if ( it_changed
&& ( m_generator
== it
.value().generator
) )
786 configchanged
= true;
791 // invalidate pixmaps
792 QVector
<Page
*>::const_iterator it
= m_pagesVector
.begin(), end
= m_pagesVector
.end();
793 for ( ; it
!= end
; ++it
) {
794 (*it
)->deletePixmaps();
797 // [MEM] remove allocation descriptors
798 QLinkedList
< AllocatedPixmap
* >::const_iterator aIt
= m_allocatedPixmapsFifo
.begin();
799 QLinkedList
< AllocatedPixmap
* >::const_iterator aEnd
= m_allocatedPixmapsFifo
.end();
800 for ( ; aIt
!= aEnd
; ++aIt
)
802 m_allocatedPixmapsFifo
.clear();
803 m_allocatedPixmapsTotalMemory
= 0;
805 // send reload signals to observers
806 foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap
) );
809 // free memory if in 'low' profile
810 if ( Settings::memoryLevel() == Settings::EnumMemoryLevel::Low
&&
811 !m_allocatedPixmapsFifo
.isEmpty() && !m_pagesVector
.isEmpty() )
812 cleanupPixmapMemory();
815 void DocumentPrivate::refreshPixmaps( int pageNumber
)
817 Page
* page
= m_pagesVector
.value( pageNumber
, 0 );
821 QLinkedList
< Okular::PixmapRequest
* > requestedPixmaps
;
822 QMap
< int, PagePrivate::PixmapObject
>::ConstIterator it
= page
->d
->m_pixmaps
.begin(), itEnd
= page
->d
->m_pixmaps
.end();
823 for ( ; it
!= itEnd
; ++it
)
825 QSize size
= (*it
).m_pixmap
->size();
826 if ( (*it
).m_rotation
% 2 )
828 PixmapRequest
* p
= new PixmapRequest( it
.key(), pageNumber
, size
.width(), size
.height(), 1, true );
830 requestedPixmaps
.push_back( p
);
832 if ( !requestedPixmaps
.isEmpty() )
833 m_parent
->requestPixmaps( requestedPixmaps
, Okular::Document::NoOption
);
836 void DocumentPrivate::_o_configChanged()
838 // free text pages if needed
839 calculateMaxTextPages();
840 while (m_allocatedTextPagesFifo
.count() > m_maxAllocatedTextPages
)
842 int pageToKick
= m_allocatedTextPagesFifo
.takeFirst();
843 m_pagesVector
.at(pageToKick
)->setTextPage( 0 ); // deletes the textpage
847 void DocumentPrivate::doContinueNextMatchSearch(void *pagesToNotifySet
, void * theMatch
, int currentPage
, int searchID
, const QString
& text
, int theCaseSensitivity
, bool moveViewport
, const QColor
& color
, bool noDialogs
, int donePages
)
849 RegularAreaRect
* match
= static_cast<RegularAreaRect
*>(theMatch
);
850 Qt::CaseSensitivity caseSensitivity
= static_cast<Qt::CaseSensitivity
>(theCaseSensitivity
);
851 QSet
< int > *pagesToNotify
= static_cast< QSet
< int > * >( pagesToNotifySet
);
853 if (m_searchCancelled
&& !match
)
855 // if the user cancelled but he just got a match, give him the match!
856 QApplication::restoreOverrideCursor();
857 emit m_parent
->searchFinished( searchID
, Document::SearchCancelled
);
858 delete pagesToNotify
;
862 // if no match found, loop through the whole doc, starting from currentPage
865 int pageCount
= m_pagesVector
.count();
866 if (donePages
< pageCount
)
868 bool doContinue
= true;
869 if ( currentPage
>= pageCount
)
871 if ( noDialogs
|| KMessageBox::questionYesNo(m_parent
->widget(), i18n("End of document reached.\nContinue from the beginning?"), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel()) == KMessageBox::Yes
)
879 Page
* page
= m_pagesVector
[ currentPage
];
880 // request search page if needed
881 if ( !page
->hasTextPage() )
882 m_parent
->requestTextPage( page
->number() );
883 // if found a match on the current page, end the loop
884 match
= page
->findText( searchID
, text
, FromTop
, caseSensitivity
);
896 QMetaObject::invokeMethod(m_parent
, "doContinueNextMatchSearch", Qt::QueuedConnection
, Q_ARG(void *, pagesToNotifySet
), Q_ARG(void *, match
), Q_ARG(int, currentPage
), Q_ARG(int, searchID
), Q_ARG(QString
, text
), Q_ARG(int, caseSensitivity
), Q_ARG(bool, moveViewport
), Q_ARG(QColor
, color
), Q_ARG(bool, noDialogs
), Q_ARG(int, donePages
));
902 // reset cursor to previous shape
903 QApplication::restoreOverrideCursor();
905 bool foundAMatch
= false;
907 // if a match has been found..
910 // update the RunningSearch structure adding this match..
911 RunningSearch
* s
= m_searches
[searchID
];
913 s
->continueOnPage
= currentPage
;
914 s
->continueOnMatch
= *match
;
915 s
->highlightedPages
.insert( currentPage
);
916 // ..add highlight to the page..
917 m_pagesVector
[ currentPage
]->d
->setHighlight( searchID
, match
, color
);
919 // ..queue page for notifying changes..
920 pagesToNotify
->insert( currentPage
);
922 // ..move the viewport to show the first of the searched word sequence centered
925 DocumentViewport
searchViewport( currentPage
);
926 searchViewport
.rePos
.enabled
= true;
927 searchViewport
.rePos
.normalizedX
= (match
->first().left
+ match
->first().right
) / 2.0;
928 searchViewport
.rePos
.normalizedY
= (match
->first().top
+ match
->first().bottom
) / 2.0;
929 m_parent
->setViewport( searchViewport
, -1, true );
933 else if ( !noDialogs
)
934 KMessageBox::information( m_parent
->widget(), i18n( "No matches found for '%1'.", text
) );
936 // notify observers about highlights changes
937 foreach(int pageNumber
, *pagesToNotify
)
938 foreach(DocumentObserver
*observer
, m_observers
)
939 observer
->notifyPageChanged( pageNumber
, DocumentObserver::Highlights
);
941 if (foundAMatch
) emit m_parent
->searchFinished( searchID
, Document::MatchFound
);
942 else emit m_parent
->searchFinished( searchID
, Document::NoMatchFound
);
944 delete pagesToNotify
;
947 void DocumentPrivate::doContinuePrevMatchSearch(void *pagesToNotifySet
, void * theMatch
, int currentPage
, int searchID
, const QString
& text
, int theCaseSensitivity
, bool moveViewport
, const QColor
& color
, bool noDialogs
, int donePages
)
949 RegularAreaRect
* match
= static_cast<RegularAreaRect
*>(theMatch
);
950 Qt::CaseSensitivity caseSensitivity
= static_cast<Qt::CaseSensitivity
>(theCaseSensitivity
);
951 QSet
< int > *pagesToNotify
= static_cast< QSet
< int > * >( pagesToNotifySet
);
953 if (m_searchCancelled
&& !match
)
955 // if the user cancelled but he just got a match, give him the match!
956 QApplication::restoreOverrideCursor();
957 emit m_parent
->searchFinished( searchID
, Document::SearchCancelled
);
958 delete pagesToNotify
;
963 // if no match found, loop through the whole doc, starting from currentPage
966 int pageCount
= m_pagesVector
.count();
967 if (donePages
< pageCount
)
969 bool doContinue
= true;
970 if ( currentPage
< 0 )
972 if ( noDialogs
|| KMessageBox::questionYesNo(m_parent
->widget(), i18n("Beginning of document reached.\nContinue from the bottom?"), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel()) == KMessageBox::Yes
)
973 currentPage
= pageCount
- 1;
980 Page
* page
= m_pagesVector
[ currentPage
];
981 // request search page if needed
982 if ( !page
->hasTextPage() )
983 m_parent
->requestTextPage( page
->number() );
984 // if found a match on the current page, end the loop
985 match
= page
->findText( searchID
, text
, FromBottom
, caseSensitivity
);
997 QMetaObject::invokeMethod(m_parent
, "doContinuePrevMatchSearch", Qt::QueuedConnection
, Q_ARG(void *, pagesToNotifySet
), Q_ARG(void *, match
), Q_ARG(int, currentPage
), Q_ARG(int, searchID
), Q_ARG(QString
, text
), Q_ARG(int, caseSensitivity
), Q_ARG(bool, moveViewport
), Q_ARG(QColor
, color
), Q_ARG(bool, noDialogs
), Q_ARG(int, donePages
));
1003 // reset cursor to previous shape
1004 QApplication::restoreOverrideCursor();
1006 bool foundAMatch
= false;
1008 // if a match has been found..
1011 // update the RunningSearch structure adding this match..
1012 RunningSearch
* s
= m_searches
[searchID
];
1014 s
->continueOnPage
= currentPage
;
1015 s
->continueOnMatch
= *match
;
1016 s
->highlightedPages
.insert( currentPage
);
1017 // ..add highlight to the page..
1018 m_pagesVector
[ currentPage
]->d
->setHighlight( searchID
, match
, color
);
1020 // ..queue page for notifying changes..
1021 pagesToNotify
->insert( currentPage
);
1023 // ..move the viewport to show the first of the searched word sequence centered
1026 DocumentViewport
searchViewport( currentPage
);
1027 searchViewport
.rePos
.enabled
= true;
1028 searchViewport
.rePos
.normalizedX
= (match
->first().left
+ match
->first().right
) / 2.0;
1029 searchViewport
.rePos
.normalizedY
= (match
->first().top
+ match
->first().bottom
) / 2.0;
1030 m_parent
->setViewport( searchViewport
, -1, true );
1034 else if ( !noDialogs
)
1035 KMessageBox::information( m_parent
->widget(), i18n( "No matches found for '%1'.", text
) );
1037 // notify observers about highlights changes
1038 foreach(int pageNumber
, *pagesToNotify
)
1039 foreach(DocumentObserver
*observer
, m_observers
)
1040 observer
->notifyPageChanged( pageNumber
, DocumentObserver::Highlights
);
1042 if (foundAMatch
) emit m_parent
->searchFinished( searchID
, Document::MatchFound
);
1043 else emit m_parent
->searchFinished( searchID
, Document::NoMatchFound
);
1045 delete pagesToNotify
;
1048 void DocumentPrivate::doContinueAllDocumentSearch(void *pagesToNotifySet
, void *pageMatchesMap
, int currentPage
, int searchID
, const QString
& text
, int theCaseSensitivity
, const QColor
& color
)
1050 QMap
< Page
*, QVector
<RegularAreaRect
*> > *pageMatches
= static_cast< QMap
< Page
*, QVector
<RegularAreaRect
*> > * >(pageMatchesMap
);
1051 Qt::CaseSensitivity caseSensitivity
= static_cast<Qt::CaseSensitivity
>(theCaseSensitivity
);
1052 QSet
< int > *pagesToNotify
= static_cast< QSet
< int > * >( pagesToNotifySet
);
1054 if (m_searchCancelled
)
1056 typedef QVector
<RegularAreaRect
*> MatchesVector
;
1058 QApplication::restoreOverrideCursor();
1059 emit m_parent
->searchFinished( searchID
, Document::SearchCancelled
);
1060 foreach(const MatchesVector
&mv
, *pageMatches
) qDeleteAll(mv
);
1062 delete pagesToNotify
;
1066 if (currentPage
< m_pagesVector
.count())
1068 // get page (from the first to the last)
1069 Page
*page
= m_pagesVector
.at(currentPage
);
1070 int pageNumber
= page
->number(); // redundant? is it == currentPage ?
1072 // request search page if needed
1073 if ( !page
->hasTextPage() )
1074 m_parent
->requestTextPage( pageNumber
);
1076 // loop on a page adding highlights for all found items
1077 RegularAreaRect
* lastMatch
= 0;
1081 lastMatch
= page
->findText( searchID
, text
, NextResult
, caseSensitivity
, lastMatch
);
1083 lastMatch
= page
->findText( searchID
, text
, FromTop
, caseSensitivity
);
1088 // add highligh rect to the matches map
1089 (*pageMatches
)[page
].append(lastMatch
);
1093 QMetaObject::invokeMethod(m_parent
, "doContinueAllDocumentSearch", Qt::QueuedConnection
, Q_ARG(void *, pagesToNotifySet
), Q_ARG(void *, pageMatches
), Q_ARG(int, currentPage
+ 1), Q_ARG(int, searchID
), Q_ARG(QString
, text
), Q_ARG(int, caseSensitivity
), Q_ARG(QColor
, color
));
1097 // reset cursor to previous shape
1098 QApplication::restoreOverrideCursor();
1100 RunningSearch
* s
= m_searches
[searchID
];
1101 bool foundAMatch
= pageMatches
->count() != 0;
1102 QMap
< Page
*, QVector
<RegularAreaRect
*> >::const_iterator it
, itEnd
;
1103 it
= pageMatches
->begin();
1104 itEnd
= pageMatches
->end();
1105 for ( ; it
!= itEnd
; ++it
)
1107 foreach(RegularAreaRect
*match
, it
.value())
1109 it
.key()->d
->setHighlight( searchID
, match
, color
);
1112 s
->highlightedPages
.insert( it
.key()->number() );
1113 pagesToNotify
->insert( it
.key()->number() );
1116 foreach(DocumentObserver
*observer
, m_observers
)
1117 observer
->notifySetup( m_pagesVector
, 0 );
1119 // notify observers about highlights changes
1120 foreach(int pageNumber
, *pagesToNotify
)
1121 foreach(DocumentObserver
*observer
, m_observers
)
1122 observer
->notifyPageChanged( pageNumber
, DocumentObserver::Highlights
);
1124 if (foundAMatch
) emit m_parent
->searchFinished(searchID
, Document::MatchFound
);
1125 else emit m_parent
->searchFinished( searchID
, Document::NoMatchFound
);
1128 delete pagesToNotify
;
1132 void DocumentPrivate::doContinueGooglesDocumentSearch(void *pagesToNotifySet
, void *pageMatchesMap
, int currentPage
, int searchID
, const QString
& text
, int theCaseSensitivity
, const QColor
& color
, bool matchAll
)
1134 typedef QPair
<RegularAreaRect
*, QColor
> MatchColor
;
1135 QMap
< Page
*, QVector
<MatchColor
> > *pageMatches
= static_cast< QMap
< Page
*, QVector
<MatchColor
> > * >(pageMatchesMap
);
1136 Qt::CaseSensitivity caseSensitivity
= static_cast<Qt::CaseSensitivity
>(theCaseSensitivity
);
1137 QSet
< int > *pagesToNotify
= static_cast< QSet
< int > * >( pagesToNotifySet
);
1139 if (m_searchCancelled
)
1141 typedef QVector
<MatchColor
> MatchesVector
;
1143 QApplication::restoreOverrideCursor();
1144 emit m_parent
->searchFinished( searchID
, Document::SearchCancelled
);
1146 foreach(const MatchesVector
&mv
, *pageMatches
)
1148 foreach(const MatchColor
&mc
, mv
) delete mc
.first
;
1151 delete pagesToNotify
;
1155 QStringList words
= text
.split( " ", QString::SkipEmptyParts
);
1156 const int wordCount
= words
.count();
1157 const int hueStep
= (wordCount
> 1) ? (60 / (wordCount
- 1)) : 60;
1158 int baseHue
, baseSat
, baseVal
;
1159 color
.getHsv( &baseHue
, &baseSat
, &baseVal
);
1161 if (currentPage
< m_pagesVector
.count())
1163 // get page (from the first to the last)
1164 Page
*page
= m_pagesVector
.at(currentPage
);
1165 int pageNumber
= page
->number(); // redundant? is it == currentPage ?
1167 // request search page if needed
1168 if ( !page
->hasTextPage() )
1169 m_parent
->requestTextPage( pageNumber
);
1171 // loop on a page adding highlights for all found items
1172 bool allMatched
= wordCount
> 0,
1174 for ( int w
= 0; w
< wordCount
; w
++ )
1176 const QString
&word
= words
[ w
];
1177 int newHue
= baseHue
- w
* hueStep
;
1180 QColor wordColor
= QColor::fromHsv( newHue
, baseSat
, baseVal
);
1181 RegularAreaRect
* lastMatch
= 0;
1182 // add all highlights for current word
1183 bool wordMatched
= false;
1187 lastMatch
= page
->findText( searchID
, word
, NextResult
, caseSensitivity
, lastMatch
);
1189 lastMatch
= page
->findText( searchID
, word
, FromTop
, caseSensitivity
);
1194 // add highligh rect to the matches map
1195 (*pageMatches
)[page
].append(MatchColor(lastMatch
, wordColor
));
1198 allMatched
= allMatched
&& wordMatched
;
1199 anyMatched
= anyMatched
|| wordMatched
;
1202 // if not all words are present in page, remove partial highlights
1203 if ( !allMatched
&& matchAll
)
1205 QVector
<MatchColor
> &matches
= (*pageMatches
)[page
];
1206 foreach(const MatchColor
&mc
, matches
) delete mc
.first
;
1207 pageMatches
->remove(page
);
1210 QMetaObject::invokeMethod(m_parent
, "doContinueGooglesDocumentSearch", Qt::QueuedConnection
, Q_ARG(void *, pagesToNotifySet
), Q_ARG(void *, pageMatches
), Q_ARG(int, currentPage
+ 1), Q_ARG(int, searchID
), Q_ARG(QString
, text
), Q_ARG(int, caseSensitivity
), Q_ARG(QColor
, color
), Q_ARG(bool, matchAll
));
1214 // reset cursor to previous shape
1215 QApplication::restoreOverrideCursor();
1217 RunningSearch
* s
= m_searches
[searchID
];
1218 bool foundAMatch
= pageMatches
->count() != 0;
1219 QMap
< Page
*, QVector
<MatchColor
> >::const_iterator it
, itEnd
;
1220 it
= pageMatches
->begin();
1221 itEnd
= pageMatches
->end();
1222 for ( ; it
!= itEnd
; ++it
)
1224 foreach(const MatchColor
&mc
, it
.value())
1226 it
.key()->d
->setHighlight( searchID
, mc
.first
, mc
.second
);
1229 s
->highlightedPages
.insert( it
.key()->number() );
1230 pagesToNotify
->insert( it
.key()->number() );
1233 // send page lists to update observers (since some filter on bookmarks)
1234 foreach(DocumentObserver
*observer
, m_observers
)
1235 observer
->notifySetup( m_pagesVector
, 0 );
1237 // notify observers about highlights changes
1238 foreach(int pageNumber
, *pagesToNotify
)
1239 foreach(DocumentObserver
*observer
, m_observers
)
1240 observer
->notifyPageChanged( pageNumber
, DocumentObserver::Highlights
);
1242 if (foundAMatch
) emit m_parent
->searchFinished( searchID
, Document::MatchFound
);
1243 else emit m_parent
->searchFinished( searchID
, Document::NoMatchFound
);
1246 delete pagesToNotify
;
1250 QVariant
DocumentPrivate::documentMetaData( const QString
&key
, const QVariant
&option
) const
1252 if ( key
== QLatin1String( "PaperColor" ) )
1254 bool giveDefault
= option
.toBool();
1255 // load paper color from Settings, or use the default color (white)
1256 // if we were told to do so
1258 if ( ( Settings::renderMode() == Settings::EnumRenderMode::Paper
)
1259 && Settings::changeColors() )
1261 color
= Settings::paperColor();
1263 else if ( giveDefault
)
1269 else if ( key
== QLatin1String( "ZoomFactor" ) )
1271 return Settings::zoomFactor();
1273 else if ( key
== QLatin1String( "TextAntialias" ) )
1275 switch ( Settings::textAntialias() )
1277 case Settings::EnumTextAntialias::Enabled
:
1281 case Settings::EnumTextAntialias::UseKDESettings
:
1282 // TODO: read the KDE configuration
1286 case Settings::EnumTextAntialias::Disabled
:
1291 else if ( key
== QLatin1String( "GraphicsAntialias" ) )
1293 switch ( Settings::graphicsAntialias() )
1295 case Settings::EnumGraphicsAntialias::Enabled
:
1298 case Settings::EnumGraphicsAntialias::Disabled
:
1307 Document::Document( QWidget
*widget
)
1308 : QObject( 0 ), d( new DocumentPrivate( this ) )
1310 d
->m_widget
= widget
;
1311 d
->m_bookmarkManager
= new BookmarkManager( d
);
1312 d
->m_viewportIterator
= d
->m_viewportHistory
.insert( d
->m_viewportHistory
.end(), DocumentViewport() );
1314 connect( PageController::self(), SIGNAL( rotationFinished( int ) ),
1315 this, SLOT( rotationFinished( int ) ) );
1316 connect( Settings::self(), SIGNAL( configChanged() ), this, SLOT( _o_configChanged() ) );
1318 qRegisterMetaType
<Okular::FontInfo
>();
1321 Document::~Document()
1323 // delete generator, pages, and related stuff
1326 QSet
< View
* >::const_iterator viewIt
= d
->m_views
.begin(), viewEnd
= d
->m_views
.end();
1327 for ( ; viewIt
!= viewEnd
; ++viewIt
)
1330 v
->d_func()->document
= 0;
1333 // delete the bookmark manager
1334 delete d
->m_bookmarkManager
;
1336 // delete the loaded generators
1337 QHash
< QString
, GeneratorInfo
>::const_iterator it
= d
->m_loadedGenerators
.constBegin(), itEnd
= d
->m_loadedGenerators
.constEnd();
1338 for ( ; it
!= itEnd
; ++it
)
1339 d
->unloadGenerator( it
.value() );
1340 d
->m_loadedGenerators
.clear();
1342 // delete the private structure
1346 static bool kserviceMoreThan( const KService::Ptr
&s1
, const KService::Ptr
&s2
)
1348 return s1
->property( "X-KDE-Priority" ).toInt() > s2
->property( "X-KDE-Priority" ).toInt();
1351 bool Document::openDocument( const QString
& docFile
, const KUrl
& url
, const KMimeType::Ptr
&_mime
)
1353 KMimeType::Ptr mime
= _mime
;
1354 QByteArray filedata
;
1355 qint64 document_size
= -1;
1356 bool isstdin
= url
.fileName( KUrl::ObeyTrailingSlash
) == QLatin1String( "-" );
1359 if ( mime
.count() <= 0 )
1362 // docFile is always local so we can use QFile on it
1363 QFile
fileReadTest( docFile
);
1364 if ( !fileReadTest
.open( QIODevice::ReadOnly
) )
1366 d
->m_docFileName
.clear();
1369 // determine the related "xml document-info" filename
1371 d
->m_docFileName
= docFile
;
1372 if ( url
.isLocalFile() )
1374 QString fn
= url
.fileName();
1375 document_size
= fileReadTest
.size();
1376 fn
= QString::number( document_size
) + '.' + fn
+ ".xml";
1377 fileReadTest
.close();
1378 QString newokular
= "okular/docdata/" + fn
;
1379 QString newokularfile
= KStandardDirs::locateLocal( "data", newokular
);
1380 if ( !QFile::exists( newokularfile
) )
1382 QString oldkpdf
= "kpdf/" + fn
;
1383 QString oldkpdffile
= KStandardDirs::locateLocal( "data", oldkpdf
);
1384 if ( QFile::exists( oldkpdffile
) )
1386 // ### copy or move?
1387 if ( !QFile::copy( oldkpdffile
, newokularfile
) )
1391 d
->m_xmlFileName
= newokularfile
;
1397 qstdin
.open( stdin
, QIODevice::ReadOnly
);
1398 filedata
= qstdin
.readAll();
1399 mime
= KMimeType::findByContent( filedata
);
1400 if ( !mime
|| mime
->name() == QLatin1String( "application/octet-stream" ) )
1402 document_size
= filedata
.size();
1405 // 0. load Generator
1406 // request only valid non-disabled plugins suitable for the mimetype
1407 QString
constraint("([X-KDE-Priority] > 0) and (exist Library)") ;
1408 KService::List offers
= KMimeTypeTrader::self()->query(mime
->name(),"okular/Generator",constraint
);
1409 if (offers
.isEmpty())
1411 emit
error( i18n( "Can not find a plugin which is able to handle the document being passed." ), -1 );
1412 kWarning(OkularDebug
).nospace() << "No plugin for mimetype '" << mime
->name() << "'.";
1416 // best ranked offer search
1417 int offercount
= offers
.count();
1418 if ( offercount
> 1 )
1420 // sort the offers: the offers with an higher priority come before
1421 qStableSort( offers
.begin(), offers
.end(), kserviceMoreThan
);
1423 if ( Settings::chooseGenerators() )
1426 for ( int i
= 0; i
< offercount
; ++i
)
1428 list
<< offers
.at(i
)->name();
1431 ChooseEngineDialog
choose( list
, mime
, widget() );
1433 if ( choose
.exec() == QDialog::Rejected
)
1436 hRank
= choose
.selectedGenerator();
1440 QString propName
= offers
.at(hRank
)->name();
1441 QHash
< QString
, GeneratorInfo
>::const_iterator genIt
= d
->m_loadedGenerators
.constFind( propName
);
1442 QString catalogName
;
1443 if ( genIt
!= d
->m_loadedGenerators
.constEnd() )
1445 d
->m_generator
= genIt
.value().generator
;
1446 catalogName
= genIt
.value().catalogName
;
1450 d
->m_generator
= d
->loadGeneratorLibrary( offers
.at(hRank
) );
1451 if ( !d
->m_generator
)
1453 genIt
= d
->m_loadedGenerators
.constFind( propName
);
1454 Q_ASSERT( genIt
!= d
->m_loadedGenerators
.constEnd() );
1455 catalogName
= genIt
.value().catalogName
;
1457 Q_ASSERT_X( d
->m_generator
, "Document::load()", "null generator?!" );
1459 if ( !catalogName
.isEmpty() )
1460 KGlobal::locale()->insertCatalog( catalogName
);
1462 d
->m_generator
->d_func()->m_document
= d
;
1464 // connect error reporting signals
1465 connect( d
->m_generator
, SIGNAL( error( const QString
&, int ) ), this, SIGNAL( error( const QString
&, int ) ) );
1466 connect( d
->m_generator
, SIGNAL( warning( const QString
&, int ) ), this, SIGNAL( warning( const QString
&, int ) ) );
1467 connect( d
->m_generator
, SIGNAL( notice( const QString
&, int ) ), this, SIGNAL( notice( const QString
&, int ) ) );
1469 // 1. load Document (and set busy cursor while loading)
1470 QApplication::setOverrideCursor( Qt::WaitCursor
);
1471 bool openOk
= false;
1474 openOk
= d
->m_generator
->loadDocument( docFile
, d
->m_pagesVector
);
1476 else if ( !filedata
.isEmpty() )
1478 if ( d
->m_generator
->hasFeature( Generator::ReadRawData
) )
1480 openOk
= d
->m_generator
->loadDocumentFromData( filedata
, d
->m_pagesVector
);
1484 d
->m_tempFile
= new KTemporaryFile();
1485 if ( !d
->m_tempFile
->open() )
1487 delete d
->m_tempFile
;
1492 d
->m_tempFile
->write( filedata
);
1493 QString tmpFileName
= d
->m_tempFile
->fileName();
1494 d
->m_tempFile
->close();
1495 openOk
= d
->m_generator
->loadDocument( tmpFileName
, d
->m_pagesVector
);
1500 QApplication::restoreOverrideCursor();
1501 if ( !openOk
|| d
->m_pagesVector
.size() <= 0 )
1503 if ( !catalogName
.isEmpty() )
1504 KGlobal::locale()->removeCatalog( catalogName
);
1510 d
->m_generatorName
= propName
;
1512 // 2. load Additional Data (our bookmarks and metadata) about the document
1513 d
->loadDocumentInfo();
1514 d
->m_bookmarkManager
->setUrl( d
->m_url
);
1516 // 3. setup observers inernal lists and data
1517 foreachObserver( notifySetup( d
->m_pagesVector
, DocumentObserver::DocumentChanged
) );
1519 // 4. set initial page (restoring the page saved in xml if loaded)
1520 DocumentViewport loadedViewport
= (*d
->m_viewportIterator
);
1521 if ( loadedViewport
.isValid() )
1523 (*d
->m_viewportIterator
) = DocumentViewport();
1524 if ( loadedViewport
.pageNumber
>= (int)d
->m_pagesVector
.size() )
1525 loadedViewport
.pageNumber
= d
->m_pagesVector
.size() - 1;
1528 loadedViewport
.pageNumber
= 0;
1529 setViewport( loadedViewport
);
1531 // start bookmark saver timer
1532 if ( !d
->m_saveBookmarksTimer
)
1534 d
->m_saveBookmarksTimer
= new QTimer( this );
1535 connect( d
->m_saveBookmarksTimer
, SIGNAL( timeout() ), this, SLOT( saveDocumentInfo() ) );
1537 d
->m_saveBookmarksTimer
->start( 5 * 60 * 1000 );
1539 // start memory check timer
1540 if ( !d
->m_memCheckTimer
)
1542 d
->m_memCheckTimer
= new QTimer( this );
1543 connect( d
->m_memCheckTimer
, SIGNAL( timeout() ), this, SLOT( slotTimedMemoryCheck() ) );
1545 d
->m_memCheckTimer
->start( 2000 );
1547 if (d
->m_nextDocumentViewport
.isValid())
1549 setViewport(d
->m_nextDocumentViewport
);
1550 d
->m_nextDocumentViewport
= DocumentViewport();
1553 AudioPlayer::instance()->d
->m_currentDocument
= isstdin
? KUrl() : d
->m_url
;
1554 d
->m_docSize
= document_size
;
1556 const QStringList docScripts
= d
->m_generator
->metaData( "DocumentScripts", "JavaScript" ).toStringList();
1557 if ( !docScripts
.isEmpty() )
1559 d
->m_scripter
= new Scripter( d
);
1560 Q_FOREACH ( const QString
&docscript
, docScripts
)
1562 d
->m_scripter
->execute( JavaScript
, docscript
);
1570 KXMLGUIClient
* Document::guiClient()
1572 if ( d
->m_generator
)
1574 Okular::GuiInterface
* iface
= qobject_cast
< Okular::GuiInterface
* >( d
->m_generator
);
1576 return iface
->guiClient();
1581 void Document::closeDocument()
1583 // check if there's anything to close...
1584 if ( !d
->m_generator
)
1587 delete d
->m_scripter
;
1591 bool startEventLoop
= false;
1594 d
->m_pixmapRequestsMutex
.lock();
1595 startEventLoop
= !d
->m_executingPixmapRequests
.isEmpty();
1596 d
->m_pixmapRequestsMutex
.unlock();
1597 if ( startEventLoop
)
1599 d
->m_closingLoop
= &loop
;
1601 d
->m_closingLoop
= 0;
1604 while ( startEventLoop
);
1606 if ( d
->m_fontThread
)
1608 disconnect( d
->m_fontThread
, 0, this, 0 );
1609 d
->m_fontThread
->stopExtraction();
1610 d
->m_fontThread
->wait();
1611 d
->m_fontThread
= 0;
1614 // stop any audio playback
1615 AudioPlayer::instance()->stopPlaybacks();
1617 // close the current document and save document info if a document is still opened
1618 if ( d
->m_generator
&& d
->m_pagesVector
.size() > 0 )
1620 d
->saveDocumentInfo();
1621 d
->m_generator
->closeDocument();
1625 if ( d
->m_memCheckTimer
)
1626 d
->m_memCheckTimer
->stop();
1627 if ( d
->m_saveBookmarksTimer
)
1628 d
->m_saveBookmarksTimer
->stop();
1630 if ( d
->m_generator
)
1632 // disconnect the generator from this document ...
1633 d
->m_generator
->d_func()->m_document
= 0;
1634 // .. and this document from the generator signals
1635 disconnect( d
->m_generator
, 0, this, 0 );
1637 QHash
< QString
, GeneratorInfo
>::const_iterator genIt
= d
->m_loadedGenerators
.constFind( d
->m_generatorName
);
1638 Q_ASSERT( genIt
!= d
->m_loadedGenerators
.constEnd() );
1639 if ( !genIt
.value().catalogName
.isEmpty() && !genIt
.value().config
)
1640 KGlobal::locale()->removeCatalog( genIt
.value().catalogName
);
1643 d
->m_generatorName
= QString();
1645 d
->m_docFileName
= QString();
1646 d
->m_xmlFileName
= QString();
1647 delete d
->m_tempFile
;
1650 d
->m_exportCached
= false;
1651 d
->m_exportFormats
.clear();
1652 d
->m_exportToText
= ExportFormat();
1653 d
->m_fontsCached
= false;
1654 d
->m_fontsCache
.clear();
1655 d
->m_rotation
= Rotation0
;
1656 // remove requests left in queue
1657 d
->m_pixmapRequestsMutex
.lock();
1658 QLinkedList
< PixmapRequest
* >::const_iterator sIt
= d
->m_pixmapRequestsStack
.begin();
1659 QLinkedList
< PixmapRequest
* >::const_iterator sEnd
= d
->m_pixmapRequestsStack
.end();
1660 for ( ; sIt
!= sEnd
; ++sIt
)
1662 d
->m_pixmapRequestsStack
.clear();
1663 d
->m_pixmapRequestsMutex
.unlock();
1665 // send an empty list to observers (to free their data)
1666 foreachObserver( notifySetup( QVector
< Page
* >(), DocumentObserver::DocumentChanged
) );
1668 // delete pages and clear 'd->m_pagesVector' container
1669 QVector
< Page
* >::const_iterator pIt
= d
->m_pagesVector
.begin();
1670 QVector
< Page
* >::const_iterator pEnd
= d
->m_pagesVector
.end();
1671 for ( ; pIt
!= pEnd
; ++pIt
)
1673 d
->m_pagesVector
.clear();
1675 // clear 'memory allocation' descriptors
1676 QLinkedList
< AllocatedPixmap
* >::const_iterator aIt
= d
->m_allocatedPixmapsFifo
.begin();
1677 QLinkedList
< AllocatedPixmap
* >::const_iterator aEnd
= d
->m_allocatedPixmapsFifo
.end();
1678 for ( ; aIt
!= aEnd
; ++aIt
)
1680 d
->m_allocatedPixmapsFifo
.clear();
1682 // clear 'running searches' descriptors
1683 QMap
< int, RunningSearch
* >::const_iterator rIt
= d
->m_searches
.begin();
1684 QMap
< int, RunningSearch
* >::const_iterator rEnd
= d
->m_searches
.end();
1685 for ( ; rIt
!= rEnd
; ++rIt
)
1687 d
->m_searches
.clear();
1689 // clear the visible areas and notify the observers
1690 QVector
< VisiblePageRect
* >::const_iterator vIt
= d
->m_pageRects
.begin();
1691 QVector
< VisiblePageRect
* >::const_iterator vEnd
= d
->m_pageRects
.end();
1692 for ( ; vIt
!= vEnd
; ++vIt
)
1694 d
->m_pageRects
.clear();
1695 foreachObserver( notifyVisibleRectsChanged() );
1697 // reset internal variables
1699 d
->m_viewportHistory
.clear();
1700 d
->m_viewportHistory
.append( DocumentViewport() );
1701 d
->m_viewportIterator
= d
->m_viewportHistory
.begin();
1702 d
->m_allocatedPixmapsTotalMemory
= 0;
1703 d
->m_pageSize
= PageSize();
1704 d
->m_pageSizes
.clear();
1705 AudioPlayer::instance()->d
->m_currentDocument
= KUrl();
1708 void Document::addObserver( DocumentObserver
* pObserver
)
1710 // keep the pointer to the observer in a map
1711 d
->m_observers
.insert( pObserver
->observerId(), pObserver
);
1713 // if the observer is added while a document is already opened, tell it
1714 if ( !d
->m_pagesVector
.isEmpty() )
1716 pObserver
->notifySetup( d
->m_pagesVector
, DocumentObserver::DocumentChanged
);
1717 pObserver
->notifyViewportChanged( false /*disables smoothMove*/ );
1721 void Document::removeObserver( DocumentObserver
* pObserver
)
1723 // remove observer from the map. it won't receive notifications anymore
1724 if ( d
->m_observers
.contains( pObserver
->observerId() ) )
1726 // free observer's pixmap data
1727 int observerId
= pObserver
->observerId();
1728 QVector
<Page
*>::const_iterator it
= d
->m_pagesVector
.begin(), end
= d
->m_pagesVector
.end();
1729 for ( ; it
!= end
; ++it
)
1730 (*it
)->deletePixmap( observerId
);
1732 // [MEM] free observer's allocation descriptors
1733 QLinkedList
< AllocatedPixmap
* >::iterator aIt
= d
->m_allocatedPixmapsFifo
.begin();
1734 QLinkedList
< AllocatedPixmap
* >::iterator aEnd
= d
->m_allocatedPixmapsFifo
.end();
1735 while ( aIt
!= aEnd
)
1737 AllocatedPixmap
* p
= *aIt
;
1738 if ( p
->id
== observerId
)
1740 aIt
= d
->m_allocatedPixmapsFifo
.erase( aIt
);
1747 // delete observer entry from the map
1748 d
->m_observers
.remove( observerId
);
1752 void Document::reparseConfig()
1754 // reparse generator config and if something changed clear Pages
1755 bool configchanged
= false;
1756 if ( d
->m_generator
)
1758 Okular::ConfigInterface
* iface
= qobject_cast
< Okular::ConfigInterface
* >( d
->m_generator
);
1760 configchanged
= iface
->reparseConfig();
1762 if ( configchanged
)
1764 // invalidate pixmaps
1765 QVector
<Page
*>::const_iterator it
= d
->m_pagesVector
.begin(), end
= d
->m_pagesVector
.end();
1766 for ( ; it
!= end
; ++it
) {
1767 (*it
)->deletePixmaps();
1770 // [MEM] remove allocation descriptors
1771 QLinkedList
< AllocatedPixmap
* >::const_iterator aIt
= d
->m_allocatedPixmapsFifo
.begin();
1772 QLinkedList
< AllocatedPixmap
* >::const_iterator aEnd
= d
->m_allocatedPixmapsFifo
.end();
1773 for ( ; aIt
!= aEnd
; ++aIt
)
1775 d
->m_allocatedPixmapsFifo
.clear();
1776 d
->m_allocatedPixmapsTotalMemory
= 0;
1778 // send reload signals to observers
1779 foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap
) );
1782 // free memory if in 'low' profile
1783 if ( Settings::memoryLevel() == Settings::EnumMemoryLevel::Low
&&
1784 !d
->m_allocatedPixmapsFifo
.isEmpty() && !d
->m_pagesVector
.isEmpty() )
1785 d
->cleanupPixmapMemory();
1789 QWidget
*Document::widget() const
1794 bool Document::isOpened() const
1796 return d
->m_generator
;
1799 bool Document::canConfigurePrinter( ) const
1801 if ( d
->m_generator
)
1803 Okular::PrintInterface
* iface
= qobject_cast
< Okular::PrintInterface
* >( d
->m_generator
);
1804 return iface
? true : false;
1810 const DocumentInfo
* Document::documentInfo() const
1812 if ( d
->m_generator
)
1814 const DocumentInfo
*infoConst
= d
->m_generator
->generateDocumentInfo();
1818 DocumentInfo
*info
= const_cast< DocumentInfo
* >( infoConst
);
1819 QString pagesSize
= d
->pagesSizeString();
1820 if ( d
->m_docSize
!= -1 )
1822 QString sizeString
= KGlobal::locale()->formatByteSize( d
->m_docSize
);
1823 info
->set( "documentSize", sizeString
, i18n( "File Size" ) );
1825 if (!pagesSize
.isEmpty())
1827 info
->set( "pagesSize", pagesSize
, i18n("Page Size") );
1834 const DocumentSynopsis
* Document::documentSynopsis() const
1836 return d
->m_generator
? d
->m_generator
->generateDocumentSynopsis() : NULL
;
1839 void Document::startFontReading()
1841 if ( !d
->m_generator
|| !d
->m_generator
->hasFeature( Generator::FontInfo
) || d
->m_fontThread
)
1844 if ( d
->m_fontsCached
)
1846 // in case we have cached fonts, simulate a reading
1847 // this way the API is the same, and users no need to care about the
1849 for ( int i
= 0; i
< d
->m_fontsCache
.count(); ++i
)
1851 emit
gotFont( d
->m_fontsCache
.at( i
) );
1852 emit
fontReadingProgress( i
/ pages() );
1854 emit
fontReadingEnded();
1858 d
->m_fontThread
= new FontExtractionThread( d
->m_generator
, pages() );
1859 connect( d
->m_fontThread
, SIGNAL( gotFont( const Okular::FontInfo
& ) ), this, SLOT( fontReadingGotFont( const Okular::FontInfo
& ) ) );
1860 connect( d
->m_fontThread
, SIGNAL( progress( int ) ), this, SLOT( fontReadingProgress( int ) ) );
1862 d
->m_fontThread
->startExtraction( /*d->m_generator->hasFeature( Generator::Threaded )*/true );
1865 void Document::stopFontReading()
1867 if ( !d
->m_fontThread
)
1870 disconnect( d
->m_fontThread
, 0, this, 0 );
1871 d
->m_fontThread
->stopExtraction();
1872 d
->m_fontThread
= 0;
1873 d
->m_fontsCache
.clear();
1876 bool Document::canProvideFontInformation() const
1878 return d
->m_generator
? d
->m_generator
->hasFeature( Generator::FontInfo
) : false;
1881 const QList
<EmbeddedFile
*> *Document::embeddedFiles() const
1883 return d
->m_generator
? d
->m_generator
->embeddedFiles() : NULL
;
1886 const Page
* Document::page( int n
) const
1888 return ( n
< d
->m_pagesVector
.count() ) ? d
->m_pagesVector
.at(n
) : 0;
1891 const DocumentViewport
& Document::viewport() const
1893 return (*d
->m_viewportIterator
);
1896 const QVector
< VisiblePageRect
* > & Document::visiblePageRects() const
1898 return d
->m_pageRects
;
1901 void Document::setVisiblePageRects( const QVector
< VisiblePageRect
* > & visiblePageRects
, int excludeId
)
1903 QVector
< VisiblePageRect
* >::const_iterator vIt
= d
->m_pageRects
.begin();
1904 QVector
< VisiblePageRect
* >::const_iterator vEnd
= d
->m_pageRects
.end();
1905 for ( ; vIt
!= vEnd
; ++vIt
)
1907 d
->m_pageRects
= visiblePageRects
;
1908 // notify change to all other (different from id) observers
1909 QMap
< int, DocumentObserver
* >::const_iterator it
= d
->m_observers
.begin(), end
= d
->m_observers
.end();
1910 for ( ; it
!= end
; ++ it
)
1911 if ( it
.key() != excludeId
)
1912 (*it
)->notifyVisibleRectsChanged();
1915 uint
Document::currentPage() const
1917 return (*d
->m_viewportIterator
).pageNumber
;
1920 uint
Document::pages() const
1922 return d
->m_pagesVector
.size();
1925 KUrl
Document::currentDocument() const
1930 bool Document::isAllowed( Permission action
) const
1932 #if !OKULAR_FORCE_DRM
1933 if ( KAuthorized::authorize( "skip_drm" ) && !Okular::Settings::obeyDRM() )
1937 return d
->m_generator
? d
->m_generator
->isAllowed( action
) : false;
1940 bool Document::supportsSearching() const
1942 return d
->m_generator
? d
->m_generator
->hasFeature( Generator::TextExtraction
) : false;
1945 bool Document::supportsPageSizes() const
1947 return d
->m_generator
? d
->m_generator
->hasFeature( Generator::PageSizes
) : false;
1950 PageSize::List
Document::pageSizes() const
1952 if ( d
->m_generator
)
1954 if ( d
->m_pageSizes
.isEmpty() )
1955 d
->m_pageSizes
= d
->m_generator
->pageSizes();
1956 return d
->m_pageSizes
;
1958 return PageSize::List();
1961 bool Document::canExportToText() const
1963 if ( !d
->m_generator
)
1966 d
->cacheExportFormats();
1967 return !d
->m_exportToText
.isNull();
1970 bool Document::exportToText( const QString
& fileName
) const
1972 if ( !d
->m_generator
)
1975 d
->cacheExportFormats();
1976 if ( d
->m_exportToText
.isNull() )
1979 return d
->m_generator
->exportTo( fileName
, d
->m_exportToText
);
1982 ExportFormat::List
Document::exportFormats() const
1984 if ( !d
->m_generator
)
1985 return ExportFormat::List();
1987 d
->cacheExportFormats();
1988 return d
->m_exportFormats
;
1991 bool Document::exportTo( const QString
& fileName
, const ExportFormat
& format
) const
1993 return d
->m_generator
? d
->m_generator
->exportTo( fileName
, format
) : false;
1996 bool Document::historyAtBegin() const
1998 return d
->m_viewportIterator
== d
->m_viewportHistory
.begin();
2001 bool Document::historyAtEnd() const
2003 return d
->m_viewportIterator
== --(d
->m_viewportHistory
.end());
2006 QVariant
Document::metaData( const QString
& key
, const QVariant
& option
) const
2008 return d
->m_generator
? d
->m_generator
->metaData( key
, option
) : QVariant();
2011 Rotation
Document::rotation() const
2013 return d
->m_rotation
;
2016 QSizeF
Document::allPagesSize() const
2018 bool allPagesSameSize
= true;
2020 for (int i
= 0; allPagesSameSize
&& i
< d
->m_pagesVector
.count(); ++i
)
2022 const Page
*p
= d
->m_pagesVector
.at(i
);
2023 if (i
== 0) size
= QSizeF(p
->width(), p
->height());
2026 allPagesSameSize
= (size
== QSizeF(p
->width(), p
->height()));
2029 if (allPagesSameSize
) return size
;
2030 else return QSizeF();
2033 QString
Document::pageSizeString(int page
) const
2037 if (d
->m_generator
->pagesSizeMetric() != Generator::None
)
2039 const Page
*p
= d
->m_pagesVector
.at( page
);
2040 return d
->localizedSize(QSizeF(p
->width(), p
->height()));
2046 void Document::requestPixmaps( const QLinkedList
< PixmapRequest
* > & requests
)
2048 requestPixmaps( requests
, RemoveAllPrevious
);
2051 void Document::requestPixmaps( const QLinkedList
< PixmapRequest
* > & requests
, PixmapRequestFlags reqOptions
)
2053 if ( requests
.isEmpty() )
2056 if ( !d
->m_generator
)
2058 // delete requests..
2059 QLinkedList
< PixmapRequest
* >::const_iterator rIt
= requests
.begin(), rEnd
= requests
.end();
2060 for ( ; rIt
!= rEnd
; ++rIt
)
2066 // 1. [CLEAN STACK] remove previous requests of requesterID
2067 int requesterID
= requests
.first()->id();
2068 QSet
< int > requestedPages
;
2070 QLinkedList
< PixmapRequest
* >::const_iterator rIt
= requests
.begin(), rEnd
= requests
.end();
2071 for ( ; rIt
!= rEnd
; ++rIt
)
2072 requestedPages
.insert( (*rIt
)->pageNumber() );
2074 const bool removeAllPrevious
= reqOptions
& RemoveAllPrevious
;
2075 d
->m_pixmapRequestsMutex
.lock();
2076 QLinkedList
< PixmapRequest
* >::iterator sIt
= d
->m_pixmapRequestsStack
.begin(), sEnd
= d
->m_pixmapRequestsStack
.end();
2077 while ( sIt
!= sEnd
)
2079 if ( (*sIt
)->id() == requesterID
2080 && ( removeAllPrevious
|| requestedPages
.contains( (*sIt
)->pageNumber() ) ) )
2082 // delete request and remove it from stack
2084 sIt
= d
->m_pixmapRequestsStack
.erase( sIt
);
2090 // 2. [ADD TO STACK] add requests to stack
2091 bool threadingDisabled
= !Settings::enableThreading();
2092 QLinkedList
< PixmapRequest
* >::const_iterator rIt
= requests
.begin(), rEnd
= requests
.end();
2093 for ( ; rIt
!= rEnd
; ++rIt
)
2095 // set the 'page field' (see PixmapRequest) and check if it is valid
2096 PixmapRequest
* request
= *rIt
;
2097 kDebug(OkularDebug
).nospace() << "request id=" << request
->id() << " " <<request
->width() << "x" << request
->height() << "@" << request
->pageNumber();
2098 if ( d
->m_pagesVector
.value( request
->pageNumber() ) == 0 )
2100 // skip requests referencing an invalid page (must not happen)
2105 request
->d
->mPage
= d
->m_pagesVector
.value( request
->pageNumber() );
2107 if ( !request
->asynchronous() )
2108 request
->d
->mPriority
= 0;
2110 if ( request
->asynchronous() && threadingDisabled
)
2111 request
->d
->mAsynchronous
= false;
2113 // add request to the 'stack' at the right place
2114 if ( !request
->priority() )
2115 // add priority zero requests to the top of the stack
2116 d
->m_pixmapRequestsStack
.append( request
);
2119 // insert in stack sorted by priority
2120 sIt
= d
->m_pixmapRequestsStack
.begin();
2121 sEnd
= d
->m_pixmapRequestsStack
.end();
2122 while ( sIt
!= sEnd
&& (*sIt
)->priority() > request
->priority() )
2124 d
->m_pixmapRequestsStack
.insert( sIt
, request
);
2127 d
->m_pixmapRequestsMutex
.unlock();
2129 // 3. [START FIRST GENERATION] if <NO>generator is ready, start a new generation,
2130 // or else (if gen is running) it will be started when the new contents will
2131 //come from generator (in requestDone())</NO>
2132 // all handling of requests put into sendGeneratorRequest
2133 // if ( generator->canRequestPixmap() )
2134 d
->sendGeneratorRequest();
2137 void Document::requestTextPage( uint page
)
2139 Page
* kp
= d
->m_pagesVector
[ page
];
2140 if ( !d
->m_generator
|| !kp
)
2143 // Memory management for TextPages
2145 d
->m_generator
->generateTextPage( kp
);
2148 void Document::addPageAnnotation( int page
, Annotation
* annotation
)
2150 // find out the page to attach annotation
2151 Page
* kp
= d
->m_pagesVector
[ page
];
2152 if ( !d
->m_generator
|| !kp
)
2155 // the annotation belongs already to a page
2156 if ( annotation
->d_ptr
->m_page
)
2159 // add annotation to the page
2160 kp
->addAnnotation( annotation
);
2162 // notify observers about the change
2163 foreachObserver( notifyPageChanged( page
, DocumentObserver::Annotations
) );
2166 void Document::modifyPageAnnotation( int page
, Annotation
* newannotation
)
2168 //TODO: modify annotations
2170 // find out the page
2171 Page
* kp
= d
->m_pagesVector
[ page
];
2172 if ( !d
->m_generator
|| !kp
)
2175 kp
->d
->modifyAnnotation( newannotation
);
2176 // notify observers about the change
2177 foreachObserver( notifyPageChanged( page
, DocumentObserver::Annotations
) );
2181 void Document::removePageAnnotation( int page
, Annotation
* annotation
)
2183 // find out the page
2184 Page
* kp
= d
->m_pagesVector
[ page
];
2185 if ( !d
->m_generator
|| !kp
)
2188 // try to remove the annotation
2189 if ( kp
->removeAnnotation( annotation
) )
2191 // in case of success, notify observers about the change
2192 foreachObserver( notifyPageChanged( page
, DocumentObserver::Annotations
) );
2196 void Document::removePageAnnotations( int page
, const QList
< Annotation
* > &annotations
)
2198 // find out the page
2199 Page
* kp
= d
->m_pagesVector
[ page
];
2200 if ( !d
->m_generator
|| !kp
)
2203 bool changed
= false;
2204 foreach ( Annotation
* annotation
, annotations
)
2206 // try to remove the annotation
2207 if ( kp
->removeAnnotation( annotation
) )
2214 // in case we removed even only one annotation, notify observers about the change
2215 foreachObserver( notifyPageChanged( page
, DocumentObserver::Annotations
) );
2219 void Document::setPageTextSelection( int page
, RegularAreaRect
* rect
, const QColor
& color
)
2221 Page
* kp
= d
->m_pagesVector
[ page
];
2222 if ( !d
->m_generator
|| !kp
)
2225 // add or remove the selection basing whether rect is null or not
2227 kp
->d
->setTextSelections( rect
, color
);
2229 kp
->d
->deleteTextSelections();
2231 // notify observers about the change
2232 foreachObserver( notifyPageChanged( page
, DocumentObserver::TextSelection
) );
2235 /* REFERENCE IMPLEMENTATION: better calling setViewport from other code
2236 void Document::setNextPage()
2238 // advance page and set viewport on observers
2239 if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 )
2240 setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber + 1 ) );
2243 void Document::setPrevPage()
2245 // go to previous page and set viewport on observers
2246 if ( (*d->m_viewportIterator).pageNumber > 0 )
2247 setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber - 1 ) );
2250 void Document::setViewportPage( int page
, int excludeId
, bool smoothMove
)
2252 // clamp page in range [0 ... numPages-1]
2255 else if ( page
> (int)d
->m_pagesVector
.count() )
2256 page
= d
->m_pagesVector
.count() - 1;
2258 // make a viewport from the page and broadcast it
2259 setViewport( DocumentViewport( page
), excludeId
, smoothMove
);
2262 void Document::setViewport( const DocumentViewport
& viewport
, int excludeId
, bool smoothMove
)
2264 // if already broadcasted, don't redo it
2265 DocumentViewport
& oldViewport
= *d
->m_viewportIterator
;
2266 // disabled by enrico on 2005-03-18 (less debug output)
2267 //if ( viewport == oldViewport )
2268 // kDebug(OkularDebug) << "setViewport with the same viewport.";
2270 // set internal viewport taking care of history
2271 if ( oldViewport
.pageNumber
== viewport
.pageNumber
|| !oldViewport
.isValid() )
2273 // if page is unchanged save the viewport at current position in queue
2274 oldViewport
= viewport
;
2278 // remove elements after viewportIterator in queue
2279 d
->m_viewportHistory
.erase( ++d
->m_viewportIterator
, d
->m_viewportHistory
.end() );
2281 // keep the list to a reasonable size by removing head when needed
2282 if ( d
->m_viewportHistory
.count() >= OKULAR_HISTORY_MAXSTEPS
)
2283 d
->m_viewportHistory
.pop_front();
2285 // add the item at the end of the queue
2286 d
->m_viewportIterator
= d
->m_viewportHistory
.insert( d
->m_viewportHistory
.end(), viewport
);
2289 // notify change to all other (different from id) observers
2290 QMap
< int, DocumentObserver
* >::const_iterator it
= d
->m_observers
.begin(), end
= d
->m_observers
.end();
2291 for ( ; it
!= end
; ++ it
)
2292 if ( it
.key() != excludeId
)
2293 (*it
)->notifyViewportChanged( smoothMove
);
2295 // [MEM] raise position of currently viewed page in allocation queue
2296 if ( d
->m_allocatedPixmapsFifo
.count() > 1 )
2298 const int page
= viewport
.pageNumber
;
2299 QLinkedList
< AllocatedPixmap
* > viewportPixmaps
;
2300 QLinkedList
< AllocatedPixmap
* >::iterator aIt
= d
->m_allocatedPixmapsFifo
.begin();
2301 QLinkedList
< AllocatedPixmap
* >::iterator aEnd
= d
->m_allocatedPixmapsFifo
.end();
2302 while ( aIt
!= aEnd
)
2304 if ( (*aIt
)->page
== page
)
2306 viewportPixmaps
.append( *aIt
);
2307 aIt
= d
->m_allocatedPixmapsFifo
.erase( aIt
);
2312 if ( !viewportPixmaps
.isEmpty() )
2313 d
->m_allocatedPixmapsFifo
+= viewportPixmaps
;
2317 void Document::setZoom(int factor
, int excludeId
)
2319 // notify change to all other (different from id) observers
2320 QMap
< int, DocumentObserver
* >::const_iterator it
= d
->m_observers
.begin(), end
= d
->m_observers
.end();
2321 for ( ; it
!= end
; ++ it
)
2322 if ( it
.key() != excludeId
)
2323 (*it
)->notifyZoom( factor
);
2326 void Document::setPrevViewport()
2327 // restore viewport from the history
2329 if ( d
->m_viewportIterator
!= d
->m_viewportHistory
.begin() )
2331 // restore previous viewport and notify it to observers
2332 --d
->m_viewportIterator
;
2333 foreachObserver( notifyViewportChanged( true ) );
2337 void Document::setNextViewport()
2338 // restore next viewport from the history
2340 QLinkedList
< DocumentViewport
>::const_iterator nextIterator
= d
->m_viewportIterator
;
2342 if ( nextIterator
!= d
->m_viewportHistory
.end() )
2344 // restore next viewport and notify it to observers
2345 ++d
->m_viewportIterator
;
2346 foreachObserver( notifyViewportChanged( true ) );
2350 void Document::setNextDocumentViewport( const DocumentViewport
& viewport
)
2352 d
->m_nextDocumentViewport
= viewport
;
2355 void Document::searchText( int searchID
, const QString
& text
, bool fromStart
, Qt::CaseSensitivity caseSensitivity
,
2356 SearchType type
, bool moveViewport
, const QColor
& color
, bool noDialogs
)
2358 d
->m_searchCancelled
= false;
2360 // safety checks: don't perform searches on empty or unsearchable docs
2361 if ( !d
->m_generator
|| !d
->m_generator
->hasFeature( Generator::TextExtraction
) || d
->m_pagesVector
.isEmpty() )
2363 emit
searchFinished( searchID
, NoMatchFound
);
2369 KDialog
*searchDialog
= new KDialog(widget());
2370 searchDialog
->setCaption( i18n("Search in progress...") );
2371 searchDialog
->setButtons( KDialog::Cancel
);
2372 QLabel
*searchLabel
= new QLabel(i18n("Searching for %1", text
), searchDialog
);
2373 searchDialog
->setMainWidget( searchLabel
);
2375 QTimer::singleShot(500, searchDialog
, SLOT(show()));
2376 connect(this, SIGNAL( searchFinished(int, Okular::Document::SearchStatus
) ), searchDialog
, SLOT(deleteLater()));
2377 connect(searchDialog
, SIGNAL( finished() ), this, SLOT(cancelSearch()));
2380 // if searchID search not recorded, create new descriptor and init params
2381 QMap
< int, RunningSearch
* >::iterator searchIt
= d
->m_searches
.find( searchID
);
2382 if ( searchIt
== d
->m_searches
.end() )
2384 RunningSearch
* search
= new RunningSearch();
2385 search
->continueOnPage
= -1;
2386 searchIt
= d
->m_searches
.insert( searchID
, search
);
2388 if (d
->m_lastSearchID
!= searchID
)
2390 resetSearch(d
->m_lastSearchID
);
2392 d
->m_lastSearchID
= searchID
;
2393 RunningSearch
* s
= *searchIt
;
2395 // update search stucture
2396 bool newText
= text
!= s
->cachedString
;
2397 s
->cachedString
= text
;
2398 s
->cachedType
= type
;
2399 s
->cachedCaseSensitivity
= caseSensitivity
;
2400 s
->cachedViewportMove
= moveViewport
;
2401 s
->cachedNoDialogs
= noDialogs
;
2402 s
->cachedColor
= color
;
2404 // global data for search
2405 QSet
< int > *pagesToNotify
= new QSet
< int >;
2407 // remove highlights from pages and queue them for notifying changes
2408 *pagesToNotify
+= s
->highlightedPages
;
2409 foreach(int pageNumber
, s
->highlightedPages
)
2410 d
->m_pagesVector
.at(pageNumber
)->d
->deleteHighlights( searchID
);
2411 s
->highlightedPages
.clear();
2413 // set hourglass cursor
2414 QApplication::setOverrideCursor( Qt::WaitCursor
);
2416 // 1. ALLDOC - proces all document marking pages
2417 if ( type
== AllDocument
)
2419 QMap
< Page
*, QVector
<RegularAreaRect
*> > *pageMatches
= new QMap
< Page
*, QVector
<RegularAreaRect
*> >;
2421 // search and highlight 'text' (as a solid phrase) on all pages
2422 QMetaObject::invokeMethod(this, "doContinueAllDocumentSearch", Qt::QueuedConnection
, Q_ARG(void *, pagesToNotify
), Q_ARG(void *, pageMatches
), Q_ARG(int, 0), Q_ARG(int, searchID
), Q_ARG(QString
, text
), Q_ARG(int, caseSensitivity
), Q_ARG(QColor
, color
));
2424 // 2. NEXTMATCH - find next matching item (or start from top)
2425 else if ( type
== NextMatch
)
2427 // find out from where to start/resume search from
2428 int viewportPage
= (*d
->m_viewportIterator
).pageNumber
;
2429 int currentPage
= fromStart
? 0 : ((s
->continueOnPage
!= -1) ? s
->continueOnPage
: viewportPage
);
2430 Page
* lastPage
= fromStart
? 0 : d
->m_pagesVector
[ currentPage
];
2432 // continue checking last TextPage first (if it is the current page)
2433 RegularAreaRect
* match
= 0;
2434 if ( lastPage
&& lastPage
->number() == s
->continueOnPage
)
2437 match
= lastPage
->findText( searchID
, text
, FromTop
, caseSensitivity
);
2439 match
= lastPage
->findText( searchID
, text
, NextResult
, caseSensitivity
, &s
->continueOnMatch
);
2444 QMetaObject::invokeMethod(this, "doContinueNextMatchSearch", Qt::QueuedConnection
, Q_ARG(void *, pagesToNotify
), Q_ARG(void *, match
), Q_ARG(int, currentPage
), Q_ARG(int, searchID
), Q_ARG(QString
, text
), Q_ARG(int, caseSensitivity
), Q_ARG(bool, moveViewport
), Q_ARG(QColor
, color
), Q_ARG(bool, noDialogs
), Q_ARG(int, 1));
2446 // 3. PREVMATCH - find previous matching item (or start from bottom)
2447 else if ( type
== PreviousMatch
)
2449 // find out from where to start/resume search from
2450 int viewportPage
= (*d
->m_viewportIterator
).pageNumber
;
2451 int currentPage
= fromStart
? d
->m_pagesVector
.count() - 1 : ((s
->continueOnPage
!= -1) ? s
->continueOnPage
: viewportPage
);
2452 Page
* lastPage
= fromStart
? 0 : d
->m_pagesVector
[ currentPage
];
2454 // continue checking last TextPage first (if it is the current page)
2455 RegularAreaRect
* match
= 0;
2456 if ( lastPage
&& lastPage
->number() == s
->continueOnPage
)
2459 match
= lastPage
->findText( searchID
, text
, FromBottom
, caseSensitivity
);
2461 match
= lastPage
->findText( searchID
, text
, PreviousResult
, caseSensitivity
, &s
->continueOnMatch
);
2466 QMetaObject::invokeMethod(this, "doContinuePrevMatchSearch", Qt::QueuedConnection
, Q_ARG(void *, pagesToNotify
), Q_ARG(void *, match
), Q_ARG(int, currentPage
), Q_ARG(int, searchID
), Q_ARG(QString
, text
), Q_ARG(int, caseSensitivity
), Q_ARG(bool, moveViewport
), Q_ARG(QColor
, color
), Q_ARG(bool, noDialogs
), Q_ARG(int, 1));
2468 // 4. GOOGLE* - process all document marking pages
2469 else if ( type
== GoogleAll
|| type
== GoogleAny
)
2471 bool matchAll
= type
== GoogleAll
;
2473 QMap
< Page
*, QVector
< QPair
<RegularAreaRect
*, QColor
> > > *pageMatches
= new QMap
< Page
*, QVector
<QPair
<RegularAreaRect
*, QColor
> > >;
2475 // search and highlight every word in 'text' on all pages
2476 QMetaObject::invokeMethod(this, "doContinueGooglesDocumentSearch", Qt::QueuedConnection
, Q_ARG(void *, pagesToNotify
), Q_ARG(void *, pageMatches
), Q_ARG(int, 0), Q_ARG(int, searchID
), Q_ARG(QString
, text
), Q_ARG(int, caseSensitivity
), Q_ARG(QColor
, color
), Q_ARG(bool, matchAll
));
2480 void Document::continueSearch( int searchID
)
2482 // check if searchID is present in runningSearches
2483 QMap
< int, RunningSearch
* >::const_iterator it
= d
->m_searches
.constFind( searchID
);
2484 if ( it
== d
->m_searches
.constEnd() )
2486 emit
searchFinished( searchID
, NoMatchFound
);
2490 // start search with cached parameters from last search by searchID
2491 RunningSearch
* p
= *it
;
2492 searchText( searchID
, p
->cachedString
, false, p
->cachedCaseSensitivity
,
2493 p
->cachedType
, p
->cachedViewportMove
, p
->cachedColor
,
2494 p
->cachedNoDialogs
);
2497 void Document::continueSearch( int searchID
, SearchType type
)
2499 // check if searchID is present in runningSearches
2500 QMap
< int, RunningSearch
* >::const_iterator it
= d
->m_searches
.constFind( searchID
);
2501 if ( it
== d
->m_searches
.constEnd() )
2503 emit
searchFinished( searchID
, NoMatchFound
);
2507 // start search with cached parameters from last search by searchID
2508 RunningSearch
* p
= *it
;
2509 searchText( searchID
, p
->cachedString
, false, p
->cachedCaseSensitivity
,
2510 type
, p
->cachedViewportMove
, p
->cachedColor
,
2511 p
->cachedNoDialogs
);
2514 void Document::resetSearch( int searchID
)
2516 // check if searchID is present in runningSearches
2517 QMap
< int, RunningSearch
* >::iterator searchIt
= d
->m_searches
.find( searchID
);
2518 if ( searchIt
== d
->m_searches
.end() )
2521 // get previous parameters for search
2522 RunningSearch
* s
= *searchIt
;
2524 // unhighlight pages and inform observers about that
2525 foreach(int pageNumber
, s
->highlightedPages
)
2527 d
->m_pagesVector
.at(pageNumber
)->d
->deleteHighlights( searchID
);
2528 foreachObserver( notifyPageChanged( pageNumber
, DocumentObserver::Highlights
) );
2531 // send the setup signal too (to update views that filter on matches)
2532 foreachObserver( notifySetup( d
->m_pagesVector
, 0 ) );
2534 // remove serch from the runningSearches list and delete it
2535 d
->m_searches
.erase( searchIt
);
2539 void Document::cancelSearch()
2541 d
->m_searchCancelled
= true;
2544 BookmarkManager
* Document::bookmarkManager() const
2546 return d
->m_bookmarkManager
;
2549 QList
<int> Document::bookmarkedPageList() const
2552 uint docPages
= pages();
2554 //pages are 0-indexed internally, but 1-indexed externally
2555 for ( uint i
= 0; i
< docPages
; i
++ )
2557 if ( bookmarkManager()->isBookmarked( i
) )
2565 QString
Document::bookmarkedPageRange() const
2567 // Code formerly in Part::slotPrint()
2570 uint docPages
= pages();
2574 for ( uint i
= 0; i
< docPages
; ++i
)
2576 if ( bookmarkManager()->isBookmarked( i
) )
2585 else if ( startId
>= 0 && endId
>= 0 )
2587 if ( !range
.isEmpty() )
2590 if ( endId
- startId
> 0 )
2591 range
+= QString( "%1-%2" ).arg( startId
+ 1 ).arg( endId
+ 1 );
2593 range
+= QString::number( startId
+ 1 );
2598 if ( startId
>= 0 && endId
>= 0 )
2600 if ( !range
.isEmpty() )
2603 if ( endId
- startId
> 0 )
2604 range
+= QString( "%1-%2" ).arg( startId
+ 1 ).arg( endId
+ 1 );
2606 range
+= QString::number( startId
+ 1 );
2611 void Document::processAction( const Action
* action
)
2616 switch( action
->actionType() )
2618 case Action::Goto
: {
2619 const GotoAction
* go
= static_cast< const GotoAction
* >( action
);
2620 d
->m_nextDocumentViewport
= go
->destViewport();
2622 // Explanation of why d->m_nextDocumentViewport is needed:
2623 // all openRelativeFile does is launch a signal telling we
2624 // want to open another URL, the problem is that when the file is
2625 // non local, the loading is done assynchronously so you can't
2626 // do a setViewport after the if as it was because you are doing the setViewport
2627 // on the old file and when the new arrives there is no setViewport for it and
2628 // it does not show anything
2630 // first open filename if link is pointing outside this document
2631 if ( go
->isExternal() && !d
->openRelativeFile( go
->fileName() ) )
2633 kWarning(OkularDebug
).nospace() << "Action: Error opening '" << go
->fileName() << "'.";
2638 // skip local links that point to nowhere (broken ones)
2639 if (!d
->m_nextDocumentViewport
.isValid())
2642 setViewport( d
->m_nextDocumentViewport
, -1, true );
2643 d
->m_nextDocumentViewport
= DocumentViewport();
2648 case Action::Execute
: {
2649 const ExecuteAction
* exe
= static_cast< const ExecuteAction
* >( action
);
2650 QString fileName
= exe
->fileName();
2651 if ( fileName
.endsWith( ".pdf" ) || fileName
.endsWith( ".PDF" ) )
2653 d
->openRelativeFile( fileName
);
2657 // Albert: the only pdf i have that has that kind of link don't define
2658 // an application and use the fileName as the file to open
2659 fileName
= d
->giveAbsolutePath( fileName
);
2660 KMimeType::Ptr mime
= KMimeType::findByPath( fileName
);
2661 // Check executables
2662 if ( KRun::isExecutableFile( fileName
, mime
->name() ) )
2664 // Don't have any pdf that uses this code path, just a guess on how it should work
2665 if ( !exe
->parameters().isEmpty() )
2667 fileName
= d
->giveAbsolutePath( exe
->parameters() );
2668 mime
= KMimeType::findByPath( fileName
);
2669 if ( KRun::isExecutableFile( fileName
, mime
->name() ) )
2671 // this case is a link pointing to an executable with a parameter
2672 // that also is an executable, possibly a hand-crafted pdf
2673 KMessageBox::information( widget(), i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that.") );
2679 // this case is a link pointing to an executable with no parameters
2680 // core developers find unacceptable executing it even after asking the user
2681 KMessageBox::information( widget(), i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that.") );
2686 KService::Ptr ptr
= KMimeTypeTrader::self()->preferredService( mime
->name(), "Application" );
2690 lst
.append( fileName
);
2691 KRun::run( *ptr
, lst
, 0 );
2694 KMessageBox::information( widget(), i18n( "No application found for opening file of mimetype %1.", mime
->name() ) );
2697 case Action::DocAction
: {
2698 const DocumentAction
* docaction
= static_cast< const DocumentAction
* >( action
);
2699 switch( docaction
->documentActionType() )
2701 case DocumentAction::PageFirst
:
2702 setViewportPage( 0 );
2704 case DocumentAction::PagePrev
:
2705 if ( (*d
->m_viewportIterator
).pageNumber
> 0 )
2706 setViewportPage( (*d
->m_viewportIterator
).pageNumber
- 1 );
2708 case DocumentAction::PageNext
:
2709 if ( (*d
->m_viewportIterator
).pageNumber
< (int)d
->m_pagesVector
.count() - 1 )
2710 setViewportPage( (*d
->m_viewportIterator
).pageNumber
+ 1 );
2712 case DocumentAction::PageLast
:
2713 setViewportPage( d
->m_pagesVector
.count() - 1 );
2715 case DocumentAction::HistoryBack
:
2718 case DocumentAction::HistoryForward
:
2721 case DocumentAction::Quit
:
2724 case DocumentAction::Presentation
:
2725 emit
linkPresentation();
2727 case DocumentAction::EndPresentation
:
2728 emit
linkEndPresentation();
2730 case DocumentAction::Find
:
2733 case DocumentAction::GoToPage
:
2734 emit
linkGoToPage();
2736 case DocumentAction::Close
:
2742 case Action::Browse
: {
2743 const BrowseAction
* browse
= static_cast< const BrowseAction
* >( action
);
2745 int lilyRow
= 0, lilyCol
= 0;
2746 // if the url is a mailto one, invoke mailer
2747 if ( browse
->url().startsWith( "mailto:", Qt::CaseInsensitive
) )
2748 KToolInvocation::invokeMailer( browse
->url() );
2749 else if ( extractLilyPondSourceReference( browse
->url(), &lilySource
, &lilyRow
, &lilyCol
) )
2751 const SourceReference
ref( lilySource
, lilyRow
, lilyCol
);
2752 processSourceReference( &ref
);
2756 QString url
= browse
->url();
2758 // fix for #100366, documents with relative links that are the form of http:foo.pdf
2759 if (url
.indexOf("http:") == 0 && url
.indexOf("http://") == -1 && url
.right(4) == ".pdf")
2761 d
->openRelativeFile(url
.mid(5));
2765 KUrl realUrl
= KUrl( url
);
2767 // handle documents with relative path
2768 if ( d
->m_url
.isValid() )
2770 realUrl
= KUrl( d
->m_url
.upUrl(), url
);
2773 // Albert: this is not a leak!
2774 new KRun( realUrl
, widget() );
2778 case Action::Sound
: {
2779 const SoundAction
* linksound
= static_cast< const SoundAction
* >( action
);
2780 AudioPlayer::instance()->playSound( linksound
->sound(), linksound
);
2783 case Action::Script
: {
2784 const ScriptAction
* linkscript
= static_cast< const ScriptAction
* >( action
);
2785 if ( !d
->m_scripter
)
2786 d
->m_scripter
= new Scripter( d
);
2787 d
->m_scripter
->execute( linkscript
->scriptType(), linkscript
->script() );
2791 //const MovieAction * movie = static_cast< const MovieAction * >( action );
2792 // TODO this (Movie action)
2797 void Document::processSourceReference( const SourceReference
* ref
)
2802 if ( !QFile::exists( ref
->fileName() ) )
2804 kDebug(OkularDebug
).nospace() << "No such file: '" << ref
->fileName() << "'";
2808 static QHash
< int, QString
> editors
;
2809 // init the editors table if empty (on first run, usually)
2810 if ( editors
.isEmpty() )
2812 editors
[ Settings::EnumExternalEditor::Kate
] =
2813 QLatin1String( "kate --use --line %l --column %c" );
2814 editors
[ Settings::EnumExternalEditor::Scite
] =
2815 QLatin1String( "scite %f \"-goto:%l,%c\"" );
2818 QHash
< int, QString
>::const_iterator it
= editors
.constFind( Settings::externalEditor() );
2820 if ( it
!= editors
.end() )
2823 p
= Settings::externalEditorCommand();
2824 // custom editor not yet configured
2828 // replacing the placeholders
2829 p
.replace( QLatin1String( "%l" ), QString::number( ref
->row() ) );
2830 p
.replace( QLatin1String( "%c" ), QString::number( ref
->column() ) );
2831 if ( p
.indexOf( QLatin1String( "%f" ) ) > -1 )
2832 p
.replace( QLatin1String( "%f" ), ref
->fileName() );
2834 p
.append( QLatin1String( " " ) + ref
->fileName() );
2837 if ( p
.isEmpty() || p
.trimmed() == ref
->fileName() )
2840 QProcess::startDetached( p
);
2843 Document::PrintingType
Document::printingSupport() const
2845 if ( d
->m_generator
)
2848 if ( d
->m_generator
->hasFeature( Generator::PrintNative
) )
2850 return NativePrinting
;
2854 if ( d
->m_generator
->hasFeature( Generator::PrintPostscript
) )
2856 return PostscriptPrinting
;
2865 bool Document::supportsPrintToFile() const
2867 return d
->m_generator
? d
->m_generator
->hasFeature( Generator::PrintToFile
) : false;
2870 bool Document::print( QPrinter
&printer
)
2872 return d
->m_generator
? d
->m_generator
->print( printer
) : false;
2875 QWidget
* Document::printConfigurationWidget() const
2877 if ( d
->m_generator
)
2879 PrintInterface
* iface
= qobject_cast
< Okular::PrintInterface
* >( d
->m_generator
);
2880 return iface
? iface
->printConfigurationWidget() : 0;
2886 void Document::fillConfigDialog( KConfigDialog
* dialog
)
2891 // ensure that we have all the generators with settings loaded
2892 QString
constraint( "([X-KDE-Priority] > 0) and (exist Library) and ([X-KDE-okularHasInternalSettings])" );
2893 KService::List offers
= KServiceTypeTrader::self()->query( "okular/Generator", constraint
);
2894 d
->loadServiceList( offers
);
2896 bool pagesAdded
= false;
2897 QHash
< QString
, GeneratorInfo
>::iterator it
= d
->m_loadedGenerators
.begin();
2898 QHash
< QString
, GeneratorInfo
>::iterator itEnd
= d
->m_loadedGenerators
.end();
2899 for ( ; it
!= itEnd
; ++it
)
2901 Okular::ConfigInterface
* iface
= d
->generatorConfig( it
.value() );
2904 iface
->addPages( dialog
);
2906 if ( !it
.value().catalogName
.isEmpty() )
2907 KGlobal::locale()->insertCatalog( it
.value().catalogName
);
2912 connect( dialog
, SIGNAL( settingsChanged( const QString
& ) ),
2913 this, SLOT( slotGeneratorConfigChanged( const QString
& ) ) );
2917 int Document::configurableGenerators() const
2919 QString
constraint( "([X-KDE-Priority] > 0) and (exist Library) and ([X-KDE-okularHasInternalSettings])" );
2920 KService::List offers
= KServiceTypeTrader::self()->query( "okular/Generator", constraint
);
2921 return offers
.count();
2924 QStringList
Document::supportedMimeTypes() const
2926 if ( !d
->m_supportedMimeTypes
.isEmpty() )
2927 return d
->m_supportedMimeTypes
;
2929 QString
constraint( "(Library == 'okularpart')" );
2930 QLatin1String
basePartService( "KParts/ReadOnlyPart" );
2931 KService::List offers
= KServiceTypeTrader::self()->query( basePartService
, constraint
);
2932 KService::List::ConstIterator it
= offers
.begin(), itEnd
= offers
.end();
2933 for ( ; it
!= itEnd
; ++it
)
2935 KService::Ptr service
= *it
;
2936 QStringList mimeTypes
= service
->serviceTypes();
2937 foreach ( const QString
& mimeType
, mimeTypes
)
2938 if ( mimeType
!= basePartService
)
2939 d
->m_supportedMimeTypes
.append( mimeType
);
2942 return d
->m_supportedMimeTypes
;
2945 const KComponentData
* Document::componentData() const
2947 if ( !d
->m_generator
)
2950 QHash
< QString
, GeneratorInfo
>::const_iterator genIt
= d
->m_loadedGenerators
.constFind( d
->m_generatorName
);
2951 Q_ASSERT( genIt
!= d
->m_loadedGenerators
.constEnd() );
2952 const KComponentData
* kcd
= &genIt
.value().data
;
2955 if ( kcd
->isValid() && kcd
->aboutData() && kcd
->aboutData()->programName().isEmpty() )
2961 bool Document::canSaveChanges() const
2963 if ( !d
->m_generator
)
2965 Q_ASSERT( !d
->m_generatorName
.isEmpty() );
2967 QHash
< QString
, GeneratorInfo
>::iterator genIt
= d
->m_loadedGenerators
.find( d
->m_generatorName
);
2968 Q_ASSERT( genIt
!= d
->m_loadedGenerators
.end() );
2969 SaveInterface
* saveIface
= d
->generatorSave( genIt
.value() );
2973 return saveIface
->supportsOption( SaveInterface::SaveChanges
);
2976 bool Document::saveChanges( const QString
&fileName
)
2978 if ( !d
->m_generator
|| fileName
.isEmpty() )
2980 Q_ASSERT( !d
->m_generatorName
.isEmpty() );
2982 QHash
< QString
, GeneratorInfo
>::iterator genIt
= d
->m_loadedGenerators
.find( d
->m_generatorName
);
2983 Q_ASSERT( genIt
!= d
->m_loadedGenerators
.end() );
2984 SaveInterface
* saveIface
= d
->generatorSave( genIt
.value() );
2985 if ( !saveIface
|| !saveIface
->supportsOption( SaveInterface::SaveChanges
) )
2988 return saveIface
->save( fileName
, SaveInterface::SaveChanges
);
2991 void Document::registerView( View
*view
)
2996 Document
*viewDoc
= view
->viewDocument();
2999 // check if already registered for this document
3000 if ( viewDoc
== this )
3003 viewDoc
->unregisterView( view
);
3006 d
->m_views
.insert( view
);
3007 view
->d_func()->document
= d
;
3010 void Document::unregisterView( View
*view
)
3015 Document
*viewDoc
= view
->viewDocument();
3016 if ( !viewDoc
|| viewDoc
!= this )
3019 view
->d_func()->document
= 0;
3020 d
->m_views
.remove( view
);
3023 QByteArray
Document::fontData(const FontInfo
&font
) const
3029 QMetaObject::invokeMethod(d
->m_generator
, "requestFontData", Qt::DirectConnection
, Q_ARG(Okular::FontInfo
, font
), Q_ARG(QByteArray
*, &result
));
3035 void DocumentPrivate::requestDone( PixmapRequest
* req
)
3040 if ( !m_generator
|| m_closingLoop
)
3042 m_pixmapRequestsMutex
.lock();
3043 m_executingPixmapRequests
.removeAll( req
);
3044 m_pixmapRequestsMutex
.unlock();
3046 if ( m_closingLoop
)
3047 m_closingLoop
->exit();
3052 if ( !m_generator
->canGeneratePixmap() )
3053 kDebug(OkularDebug
) << "requestDone with generator not in READY state.";
3056 // [MEM] 1.1 find and remove a previous entry for the same page and id
3057 QLinkedList
< AllocatedPixmap
* >::iterator aIt
= m_allocatedPixmapsFifo
.begin();
3058 QLinkedList
< AllocatedPixmap
* >::iterator aEnd
= m_allocatedPixmapsFifo
.end();
3059 for ( ; aIt
!= aEnd
; ++aIt
)
3060 if ( (*aIt
)->page
== req
->pageNumber() && (*aIt
)->id
== req
->id() )
3062 AllocatedPixmap
* p
= *aIt
;
3063 m_allocatedPixmapsFifo
.erase( aIt
);
3064 m_allocatedPixmapsTotalMemory
-= p
->memory
;
3069 QMap
< int, DocumentObserver
* >::const_iterator itObserver
= m_observers
.constFind( req
->id() );
3070 if ( itObserver
!= m_observers
.constEnd() )
3072 // [MEM] 1.2 append memory allocation descriptor to the FIFO
3073 qulonglong memoryBytes
= 4 * req
->width() * req
->height();
3074 AllocatedPixmap
* memoryPage
= new AllocatedPixmap( req
->id(), req
->pageNumber(), memoryBytes
);
3075 m_allocatedPixmapsFifo
.append( memoryPage
);
3076 m_allocatedPixmapsTotalMemory
+= memoryBytes
;
3078 // 2. notify an observer that its pixmap changed
3079 itObserver
.value()->notifyPageChanged( req
->pageNumber(), DocumentObserver::Pixmap
);
3083 kWarning(OkularDebug
) << "Receiving a done request for the defunct observer" << req
->id();
3086 // 3. delete request
3087 m_pixmapRequestsMutex
.lock();
3088 m_executingPixmapRequests
.removeAll( req
);
3089 m_pixmapRequestsMutex
.unlock();
3092 // 4. start a new generation if some is pending
3093 m_pixmapRequestsMutex
.lock();
3094 bool hasPixmaps
= !m_pixmapRequestsStack
.isEmpty();
3095 m_pixmapRequestsMutex
.unlock();
3097 sendGeneratorRequest();
3100 void DocumentPrivate::setPageBoundingBox( int page
, const NormalizedRect
& boundingBox
)
3102 Page
* kp
= m_pagesVector
[ page
];
3103 if ( !m_generator
|| !kp
)
3106 if ( kp
->boundingBox() == boundingBox
)
3108 kp
->setBoundingBox( boundingBox
);
3110 // notify observers about the change
3111 foreachObserverD( notifyPageChanged( page
, DocumentObserver::BoundingBox
) );
3113 // TODO: For generators that generate the bbox by pixmap scanning, if the first generated pixmap is very small, the bounding box will forever be inaccurate.
3114 // TODO: Crop computation should also consider annotations, actions, etc. to make sure they're not cropped away.
3115 // TODO: Help compute bounding box for generators that create a QPixmap without a QImage, like text and plucker.
3116 // TODO: Don't compute the bounding box if no one needs it (e.g., Trim Borders is off).
3120 void DocumentPrivate::calculateMaxTextPages()
3122 int multipliers
= qMax(1, qRound(getTotalMemory() / 536870912.0)); // 512 MB
3123 switch (Settings::memoryLevel())
3125 case Settings::EnumMemoryLevel::Low
:
3126 m_maxAllocatedTextPages
= multipliers
* 2;
3129 case Settings::EnumMemoryLevel::Normal
:
3130 m_maxAllocatedTextPages
= multipliers
* 50;
3133 case Settings::EnumMemoryLevel::Aggressive
:
3134 m_maxAllocatedTextPages
= multipliers
* 250;
3139 void DocumentPrivate::textGenerationDone( Page
*page
)
3141 if ( !m_generator
|| m_closingLoop
) return;
3143 // 1. If we reached the cache limit, delete the first text page from the fifo
3144 if (m_allocatedTextPagesFifo
.size() == m_maxAllocatedTextPages
)
3146 int pageToKick
= m_allocatedTextPagesFifo
.takeFirst();
3147 if (pageToKick
!= page
->number()) // this should never happen but better be safe than sorry
3149 m_pagesVector
.at(pageToKick
)->setTextPage( 0 ); // deletes the textpage
3153 // 2. Add the page to the fifo of generated text pages
3154 m_allocatedTextPagesFifo
.append( page
->number() );
3157 void Document::setRotation( int r
)
3159 d
->setRotationInternal( r
, true );
3162 void DocumentPrivate::setRotationInternal( int r
, bool notify
)
3164 Rotation rotation
= (Rotation
)r
;
3165 if ( !m_generator
|| ( m_rotation
== rotation
) )
3168 // tell the pages to rotate
3169 QVector
< Okular::Page
* >::const_iterator pIt
= m_pagesVector
.begin();
3170 QVector
< Okular::Page
* >::const_iterator pEnd
= m_pagesVector
.end();
3171 for ( ; pIt
!= pEnd
; ++pIt
)
3172 (*pIt
)->d
->rotateAt( rotation
);
3175 // notify the generator that the current rotation has changed
3176 m_generator
->rotationChanged( rotation
, m_rotation
);
3178 // set the new rotation
3179 m_rotation
= rotation
;
3183 foreachObserverD( notifySetup( m_pagesVector
, DocumentObserver::NewLayoutForPages
) );
3184 foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap
| DocumentObserver::Highlights
| DocumentObserver::Annotations
) );
3186 kDebug(OkularDebug
) << "Rotated:" << r
;
3189 void Document::setPageSize( const PageSize
&size
)
3191 if ( !d
->m_generator
|| !d
->m_generator
->hasFeature( Generator::PageSizes
) )
3194 if ( d
->m_pageSizes
.isEmpty() )
3195 d
->m_pageSizes
= d
->m_generator
->pageSizes();
3196 int sizeid
= d
->m_pageSizes
.indexOf( size
);
3200 // tell the pages to change size
3201 QVector
< Okular::Page
* >::const_iterator pIt
= d
->m_pagesVector
.begin();
3202 QVector
< Okular::Page
* >::const_iterator pEnd
= d
->m_pagesVector
.end();
3203 for ( ; pIt
!= pEnd
; ++pIt
)
3204 (*pIt
)->d
->changeSize( size
);
3205 // clear 'memory allocation' descriptors
3206 QLinkedList
< AllocatedPixmap
* >::const_iterator aIt
= d
->m_allocatedPixmapsFifo
.begin();
3207 QLinkedList
< AllocatedPixmap
* >::const_iterator aEnd
= d
->m_allocatedPixmapsFifo
.end();
3208 for ( ; aIt
!= aEnd
; ++aIt
)
3210 d
->m_allocatedPixmapsFifo
.clear();
3211 d
->m_allocatedPixmapsTotalMemory
= 0;
3212 // notify the generator that the current page size has changed
3213 d
->m_generator
->pageSizeChanged( size
, d
->m_pageSize
);
3214 // set the new page size
3215 d
->m_pageSize
= size
;
3217 foreachObserver( notifySetup( d
->m_pagesVector
, DocumentObserver::NewLayoutForPages
) );
3218 foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap
| DocumentObserver::Highlights
) );
3219 kDebug(OkularDebug
) << "New PageSize id:" << sizeid
;
3223 /** DocumentViewport **/
3225 DocumentViewport::DocumentViewport( int n
)
3229 rePos
.enabled
= false;
3230 rePos
.normalizedX
= 0.5;
3231 rePos
.normalizedY
= 0.0;
3233 autoFit
.enabled
= false;
3234 autoFit
.width
= false;
3235 autoFit
.height
= false;
3238 DocumentViewport::DocumentViewport( const QString
& xmlDesc
)
3241 // default settings (maybe overridden below)
3242 rePos
.enabled
= false;
3243 rePos
.normalizedX
= 0.5;
3244 rePos
.normalizedY
= 0.0;
3246 autoFit
.enabled
= false;
3247 autoFit
.width
= false;
3248 autoFit
.height
= false;
3250 // check for string presence
3251 if ( xmlDesc
.isEmpty() )
3254 // decode the string
3257 QString token
= xmlDesc
.section( ';', field
, field
);
3258 while ( !token
.isEmpty() )
3260 // decode the current token
3263 pageNumber
= token
.toInt( &ok
);
3267 else if ( token
.startsWith( "C1" ) )
3269 rePos
.enabled
= true;
3270 rePos
.normalizedX
= token
.section( ':', 1, 1 ).toDouble();
3271 rePos
.normalizedY
= token
.section( ':', 2, 2 ).toDouble();
3274 else if ( token
.startsWith( "C2" ) )
3276 rePos
.enabled
= true;
3277 rePos
.normalizedX
= token
.section( ':', 1, 1 ).toDouble();
3278 rePos
.normalizedY
= token
.section( ':', 2, 2 ).toDouble();
3279 if (token
.section( ':', 3, 3 ).toInt() == 1) rePos
.pos
= Center
;
3280 else rePos
.pos
= TopLeft
;
3282 else if ( token
.startsWith( "AF1" ) )
3284 autoFit
.enabled
= true;
3285 autoFit
.width
= token
.section( ':', 1, 1 ) == "T";
3286 autoFit
.height
= token
.section( ':', 2, 2 ) == "T";
3288 // proceed tokenizing string
3290 token
= xmlDesc
.section( ';', field
, field
);
3294 QString
DocumentViewport::toString() const
3296 // start string with page number
3297 QString s
= QString::number( pageNumber
);
3298 // if has center coordinates, save them on string
3299 if ( rePos
.enabled
)
3300 s
+= QString( ";C2:" ) + QString::number( rePos
.normalizedX
) +
3301 ':' + QString::number( rePos
.normalizedY
) +
3302 ':' + QString::number( rePos
.pos
);
3303 // if has autofit enabled, save its state on string
3304 if ( autoFit
.enabled
)
3305 s
+= QString( ";AF1:" ) + (autoFit
.width
? "T" : "F") +
3306 ':' + (autoFit
.height
? "T" : "F");
3310 bool DocumentViewport::isValid() const
3312 return pageNumber
>= 0;
3315 bool DocumentViewport::operator==( const DocumentViewport
& vp
) const
3317 bool equal
= ( pageNumber
== vp
.pageNumber
) &&
3318 ( rePos
.enabled
== vp
.rePos
.enabled
) &&
3319 ( autoFit
.enabled
== vp
.autoFit
.enabled
);
3322 if ( rePos
.enabled
&&
3323 (( rePos
.normalizedX
!= vp
.rePos
.normalizedX
) ||
3324 ( rePos
.normalizedY
!= vp
.rePos
.normalizedY
) || rePos
.pos
!= vp
.rePos
.pos
) )
3326 if ( autoFit
.enabled
&&
3327 (( autoFit
.width
!= vp
.autoFit
.width
) ||
3328 ( autoFit
.height
!= vp
.autoFit
.height
)) )
3334 /** DocumentInfo **/
3336 DocumentInfo::DocumentInfo()
3337 : QDomDocument( "DocumentInformation" )
3339 QDomElement docElement
= createElement( "DocumentInfo" );
3340 appendChild( docElement
);
3343 void DocumentInfo::set( const QString
&key
, const QString
&value
,
3344 const QString
&title
)
3346 QDomElement docElement
= documentElement();
3347 QDomElement element
;
3349 // check whether key already exists
3350 QDomNodeList list
= docElement
.elementsByTagName( key
);
3351 if ( list
.count() > 0 )
3352 element
= list
.item( 0 ).toElement();
3354 element
= createElement( key
);
3356 element
.setAttribute( "value", value
);
3357 element
.setAttribute( "title", title
);
3359 if ( list
.count() == 0 )
3360 docElement
.appendChild( element
);
3363 void DocumentInfo::set( enum Key key
, const QString
&value
)
3367 set( "title", value
, i18n( "Title" ) );
3370 set( "subject", value
, i18n( "Subject" ) );
3373 set( "description", value
, i18n( "Description" ) );
3376 set( "author", value
, i18n( "Author" ) );
3379 set( "creator", value
, i18n( "Creator" ) );
3382 set( "producer", value
, i18n( "Producer" ) );
3385 set( "copyright", value
, i18n( "Copyright" ) );
3388 set( "pages", value
, i18n( "Pages" ) );
3391 set( "creationDate", value
, i18n( "Created" ) );
3393 case ModificationDate
:
3394 set( "modificationDate", value
, i18n( "Modified" ) );
3397 set( "mimeType", value
, i18n( "Mime Type" ) );
3400 set( "category", value
, i18n( "Category" ) );
3403 set( "keywords", value
, i18n( "Keywords" ) );
3406 kWarning(OkularDebug
) << "Invalid key passed";
3411 QString
DocumentInfo::get( const QString
&key
) const
3413 QDomElement docElement
= documentElement();
3414 QDomElement element
;
3416 // check whether key already exists
3417 QDomNodeList list
= docElement
.elementsByTagName( key
);
3418 if ( list
.count() > 0 )
3419 return list
.item( 0 ).toElement().attribute( "value" );
3425 /** DocumentSynopsis **/
3427 DocumentSynopsis::DocumentSynopsis()
3428 : QDomDocument( "DocumentSynopsis" )
3430 // void implementation, only subclassed for naming
3433 DocumentSynopsis::DocumentSynopsis( const QDomDocument
&document
)
3434 : QDomDocument( document
)
3438 /** EmbeddedFile **/
3440 EmbeddedFile::EmbeddedFile()
3444 EmbeddedFile::~EmbeddedFile()
3448 VisiblePageRect::VisiblePageRect( int page
, const NormalizedRect
&rectangle
)
3449 : pageNumber( page
), rect( rectangle
)
3453 #include "document.moc"
3455 /* kate: replace-tabs on; indent-width 4; */