compile
[kdegraphics.git] / okular / core / document.cpp
blob40e8ff8d350d44f63e28889491070a147ff8a9d7
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> *
4 * *
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 ***************************************************************************/
11 #include "document.h"
12 #include "document_p.h"
14 #ifdef Q_OS_WIN
15 #define _WIN32_WINNT 0x0500
16 #include <windows.h>
17 #endif
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>
37 #include <kdebug.h>
38 #include <klibloader.h>
39 #include <klocale.h>
40 #include <kmessagebox.h>
41 #include <kmimetypetrader.h>
42 #include <krun.h>
43 #include <kstandarddirs.h>
44 #include <ktemporaryfile.h>
45 #include <ktoolinvocation.h>
47 // local includes
48 #include "action.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"
55 #include "debug_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"
61 #include "observer.h"
62 #include "page.h"
63 #include "page_p.h"
64 #include "pagecontroller_p.h"
65 #include "scripter.h"
66 #include "settings.h"
67 #include "sourcereference.h"
68 #include "sourcereference_p.h"
69 #include "view.h"
70 #include "view_p.h"
72 #include <config-okular.h>
74 using namespace Okular;
76 struct AllocatedPixmap
78 // owner of the page
79 int id;
80 int page;
81 qulonglong memory;
82 // public constructor: initialize data
83 AllocatedPixmap( int i, int p, qulonglong m ) : id( i ), page( p ), memory( m ) {}
86 struct RunningSearch
88 // store search properties
89 int continueOnPage;
90 RegularAreaRect continueOnMatch;
91 QSet< int > highlightedPages;
93 // fields related to previous searches (used for 'continueSearch')
94 QString cachedString;
95 Document::SearchType cachedType;
96 Qt::CaseSensitivity cachedCaseSensitivity;
97 bool cachedViewportMove : 1;
98 bool cachedNoDialogs : 1;
99 QColor cachedColor;
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
117 if (m_generator)
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;
138 break;
140 case Generator::None:
141 break;
143 if (KGlobal::locale()->measureSystem() == KLocale::Imperial)
145 return i18n("%1 x %2 in", inchesWidth, inchesHeight);
147 else
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;
162 break;
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;
171 break;
173 case Settings::EnumMemoryLevel::Aggressive:
175 qulonglong freeMemory = getFreeMemory();
176 if (m_allocatedPixmapsTotalMemory > freeMemory) clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2;
178 break;
181 if ( clipValue > memoryToFree )
182 memoryToFree = clipValue;
184 if ( memoryToFree > 0 )
186 // [MEM] free memory starting from older pixmaps
187 int pagesFreed = 0;
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;
199 pagesFreed++;
200 // delete pixmap
201 m_pagesVector.at( p->page )->deletePixmap( p->id );
202 // delete allocation descriptor
203 delete p;
204 } else
205 ++pIt;
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;
214 if ( cachedValue )
215 return cachedValue;
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 );
226 while ( true )
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)
234 MEMORYSTATUSEX stat;
236 GlobalMemoryStatusEx (&stat);
238 return ( cachedValue = stat.ullTotalPhys );
239 #endif
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 )
249 return cachedValue;
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 ) )
255 return 0;
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;
260 QString entry;
261 QTextStream readStream( &memFile );
262 while ( true )
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();
274 memFile.close();
276 lastUpdate = QTime::currentTime();
278 return ( cachedValue = (1024 * memoryFree) );
279 #elif defined(Q_OS_WIN)
280 MEMORYSTATUSEX stat;
282 GlobalMemoryStatusEx (&stat);
284 lastUpdate = QTime::currentTime();
286 return ( cachedValue = stat.ullAvailPhys );
287 #else
288 // tell the memory is full.. will act as in LOW profile
289 return 0;
290 #endif
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() )
299 return;
301 QFile infoFile( m_xmlFileName );
302 if ( !infoFile.exists() || !infoFile.open( QIODevice::ReadOnly ) )
303 return;
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.";
310 infoFile.close();
311 return;
313 infoFile.close();
315 QDomElement root = doc.documentElement();
316 if ( root.tagName() != "documentInfo" )
317 return;
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)
337 bool ok;
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" )
359 // clear 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();
374 // consistancy check
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();
381 bool ok = true;
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 );
402 break;
406 viewNode = viewNode.nextSibling();
409 infoNode = infoNode.nextSibling();
413 topLevelNode = topLevelNode.nextSibling();
414 } // </documentInfo>
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;
438 if ( newmode_ok
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 );
459 bool ok = true;
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 );
466 if ( ok )
468 zoomEl.setAttribute( "mode", mode );
473 QString DocumentPrivate::giveAbsolutePath( const QString & fileName ) const
475 if ( !QDir::isRelativePath( fileName ) )
476 return fileName;
478 if ( !m_url.isValid() )
479 return QString();
481 return m_url.upUrl().url() + fileName;
484 bool DocumentPrivate::openRelativeFile( const QString & fileName )
486 QString absFileName = giveAbsolutePath( fileName );
487 if ( absFileName.isEmpty() )
488 return false;
490 kDebug(OkularDebug).nospace() << "openDocument: '" << absFileName << "'";
492 emit m_parent->openUrl( absFileName );
493 return true;
496 Generator * DocumentPrivate::loadGeneratorLibrary( const KService::Ptr &service )
498 KPluginFactory *factory = KPluginLoader( service->library() ).factory();
499 if ( !factory )
501 kWarning(OkularDebug).nospace() << "Invalid plugin factory for " << service->library() << "!";
502 return 0;
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 );
510 return generator;
513 void DocumentPrivate::loadAllGeneratorLibraries()
515 if ( m_generatorsLoaded )
516 return;
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();
528 if ( count <= 0 )
529 return;
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() )
537 continue;
539 Generator * g = loadGeneratorLibrary( offers.at(i) );
540 (void)g;
544 void DocumentPrivate::unloadGenerator( const GeneratorInfo& info )
546 delete info.generator;
549 void DocumentPrivate::cacheExportFormats()
551 if ( m_exportCached )
552 return;
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 );
559 else
560 m_exportFormats.append( formats.at( i ) );
563 m_exportCached = true;
566 ConfigInterface* DocumentPrivate::generatorConfig( GeneratorInfo& info )
568 if ( info.configChecked )
569 return info.config;
571 info.config = qobject_cast< Okular::ConfigInterface * >( info.generator );
572 info.configChecked = true;
573 return info.config;
576 SaveInterface* DocumentPrivate::generatorSave( GeneratorInfo& info )
578 if ( info.saveChecked )
579 return info.save;
581 info.save = qobject_cast< Okular::SaveInterface * >( info.generator );
582 info.saveChecked = true;
583 return info.save;
586 void DocumentPrivate::saveDocumentInfo() const
588 if ( m_xmlFileName.isEmpty() )
589 return;
591 QFile infoFile( m_xmlFileName );
592 if (infoFile.open( QIODevice::WriteOnly | QIODevice::Truncate) )
594 // 1. Create DOM
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() )
628 --backIterator;
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;
636 ++endIt;
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 );
643 ++backIterator;
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" );
661 os << xml;
663 infoFile.close();
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()
676 // find a request
677 PixmapRequest * request = 0;
678 m_pixmapRequestsMutex.lock();
679 while ( !m_pixmapRequestsStack.isEmpty() && !request )
681 PixmapRequest * r = m_pixmapRequestsStack.last();
682 if (!r)
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();
689 delete r;
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;
701 delete r;
703 else
704 request = r;
707 // if no request found (or already generated), return
708 if ( !request )
710 m_pixmapRequestsMutex.unlock();
711 return;
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 )
726 request->d->swap();
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 );
735 else
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();
758 m_fontThread = 0;
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& )
773 if ( !m_generator )
774 return;
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() );
782 if ( iface )
784 bool it_changed = iface->reparseConfig();
785 if ( it_changed && ( m_generator == it.value().generator ) )
786 configchanged = true;
789 if ( configchanged )
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 )
801 delete *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 );
818 if ( !page )
819 return;
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 )
827 size.transpose();
828 PixmapRequest * p = new PixmapRequest( it.key(), pageNumber, size.width(), size.height(), 1, true );
829 p->d->mForce = 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;
859 return;
862 // if no match found, loop through the whole doc, starting from currentPage
863 if ( !match )
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 )
872 currentPage = 0;
873 else
874 doContinue = false;
876 if (doContinue)
878 // get page
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 );
886 if ( !match )
888 currentPage++;
889 donePages++;
891 else
893 donePages = 1;
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));
897 return;
902 // reset cursor to previous shape
903 QApplication::restoreOverrideCursor();
905 bool foundAMatch = false;
907 // if a match has been found..
908 if ( match )
910 // update the RunningSearch structure adding this match..
911 RunningSearch * s = m_searches[searchID];
912 foundAMatch = true;
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
923 if ( moveViewport )
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 );
931 delete match;
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;
959 return;
963 // if no match found, loop through the whole doc, starting from currentPage
964 if ( !match )
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;
974 else
975 doContinue = false;
977 if (doContinue)
979 // get page
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 );
987 if ( !match )
989 currentPage--;
990 donePages++;
992 else
994 donePages = 1;
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));
998 return;
1003 // reset cursor to previous shape
1004 QApplication::restoreOverrideCursor();
1006 bool foundAMatch = false;
1008 // if a match has been found..
1009 if ( match )
1011 // update the RunningSearch structure adding this match..
1012 RunningSearch * s = m_searches[searchID];
1013 foundAMatch = true;
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
1024 if ( moveViewport )
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 );
1032 delete match;
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);
1061 delete pageMatches;
1062 delete pagesToNotify;
1063 return;
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;
1078 while ( 1 )
1080 if ( lastMatch )
1081 lastMatch = page->findText( searchID, text, NextResult, caseSensitivity, lastMatch );
1082 else
1083 lastMatch = page->findText( searchID, text, FromTop, caseSensitivity );
1085 if ( !lastMatch )
1086 break;
1088 // add highligh rect to the matches map
1089 (*pageMatches)[page].append(lastMatch);
1091 delete 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));
1095 else
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 );
1110 delete match;
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 );
1127 delete pageMatches;
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;
1150 delete pageMatches;
1151 delete pagesToNotify;
1152 return;
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,
1173 anyMatched = false;
1174 for ( int w = 0; w < wordCount; w++ )
1176 const QString &word = words[ w ];
1177 int newHue = baseHue - w * hueStep;
1178 if ( newHue < 0 )
1179 newHue += 360;
1180 QColor wordColor = QColor::fromHsv( newHue, baseSat, baseVal );
1181 RegularAreaRect * lastMatch = 0;
1182 // add all highlights for current word
1183 bool wordMatched = false;
1184 while ( 1 )
1186 if ( lastMatch )
1187 lastMatch = page->findText( searchID, word, NextResult, caseSensitivity, lastMatch );
1188 else
1189 lastMatch = page->findText( searchID, word, FromTop, caseSensitivity);
1191 if ( !lastMatch )
1192 break;
1194 // add highligh rect to the matches map
1195 (*pageMatches)[page].append(MatchColor(lastMatch, wordColor));
1196 wordMatched = true;
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));
1212 else
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 );
1227 delete mc.first;
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 );
1245 delete pageMatches;
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
1257 QColor color;
1258 if ( ( Settings::renderMode() == Settings::EnumRenderMode::Paper )
1259 && Settings::changeColors() )
1261 color = Settings::paperColor();
1263 else if ( giveDefault )
1265 color = Qt::white;
1267 return color;
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:
1278 return true;
1279 break;
1280 #if 0
1281 case Settings::EnumTextAntialias::UseKDESettings:
1282 // TODO: read the KDE configuration
1283 return true;
1284 break;
1285 #endif
1286 case Settings::EnumTextAntialias::Disabled:
1287 return false;
1288 break;
1291 else if ( key == QLatin1String( "GraphicsAntialias" ) )
1293 switch ( Settings::graphicsAntialias() )
1295 case Settings::EnumGraphicsAntialias::Enabled:
1296 return true;
1297 break;
1298 case Settings::EnumGraphicsAntialias::Disabled:
1299 return false;
1300 break;
1303 return QVariant();
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
1324 closeDocument();
1326 QSet< View * >::const_iterator viewIt = d->m_views.begin(), viewEnd = d->m_views.end();
1327 for ( ; viewIt != viewEnd; ++viewIt )
1329 View *v = *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
1343 delete d;
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( "-" );
1357 if ( !isstdin )
1359 if ( mime.count() <= 0 )
1360 return false;
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();
1367 return false;
1369 // determine the related "xml document-info" filename
1370 d->m_url = url;
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 ) )
1388 return false;
1391 d->m_xmlFileName = newokularfile;
1394 else
1396 QFile qstdin;
1397 qstdin.open( stdin, QIODevice::ReadOnly );
1398 filedata = qstdin.readAll();
1399 mime = KMimeType::findByContent( filedata );
1400 if ( !mime || mime->name() == QLatin1String( "application/octet-stream" ) )
1401 return false;
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() << "'.";
1413 return false;
1415 int hRank=0;
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() )
1425 QStringList list;
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 )
1434 return false;
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;
1448 else
1450 d->m_generator = d->loadGeneratorLibrary( offers.at(hRank) );
1451 if ( !d->m_generator )
1452 return false;
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;
1472 if ( !isstdin )
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 );
1482 else
1484 d->m_tempFile = new KTemporaryFile();
1485 if ( !d->m_tempFile->open() )
1487 delete d->m_tempFile;
1488 d->m_tempFile = 0;
1490 else
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 );
1506 d->m_generator = 0;
1507 return openOk;
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;
1527 else
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 );
1566 return true;
1570 KXMLGUIClient* Document::guiClient()
1572 if ( d->m_generator )
1574 Okular::GuiInterface * iface = qobject_cast< Okular::GuiInterface * >( d->m_generator );
1575 if ( iface )
1576 return iface->guiClient();
1578 return 0;
1581 void Document::closeDocument()
1583 // check if there's anything to close...
1584 if ( !d->m_generator )
1585 return;
1587 delete d->m_scripter;
1588 d->m_scripter = 0;
1590 QEventLoop loop;
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;
1600 loop.exec();
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();
1624 // stop timers
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 );
1642 d->m_generator = 0;
1643 d->m_generatorName = QString();
1644 d->m_url = KUrl();
1645 d->m_docFileName = QString();
1646 d->m_xmlFileName = QString();
1647 delete d->m_tempFile;
1648 d->m_tempFile = 0;
1649 d->m_docSize = -1;
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 )
1661 delete *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 )
1672 delete *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 )
1679 delete *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 )
1686 delete *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 )
1693 delete *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 );
1741 delete p;
1743 else
1744 ++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 );
1759 if ( iface )
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 )
1774 delete *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
1791 return d->m_widget;
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;
1806 else
1807 return 0;
1810 const DocumentInfo * Document::documentInfo() const
1812 if ( d->m_generator )
1814 const DocumentInfo *infoConst = d->m_generator->generateDocumentInfo();
1815 if ( !infoConst )
1816 return 0;
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") );
1829 return info;
1831 else return NULL;
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 )
1842 return;
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
1848 // internal caching
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();
1855 return;
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 )
1868 return;
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 )
1906 delete *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
1927 return d->m_url;
1930 bool Document::isAllowed( Permission action ) const
1932 #if !OKULAR_FORCE_DRM
1933 if ( KAuthorized::authorize( "skip_drm" ) && !Okular::Settings::obeyDRM() )
1934 return true;
1935 #endif
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 )
1964 return false;
1966 d->cacheExportFormats();
1967 return !d->m_exportToText.isNull();
1970 bool Document::exportToText( const QString& fileName ) const
1972 if ( !d->m_generator )
1973 return false;
1975 d->cacheExportFormats();
1976 if ( d->m_exportToText.isNull() )
1977 return false;
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;
2019 QSizeF size;
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());
2024 else
2026 allPagesSameSize = (size == QSizeF(p->width(), p->height()));
2029 if (allPagesSameSize) return size;
2030 else return QSizeF();
2033 QString Document::pageSizeString(int page) const
2035 if (d->m_generator)
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()));
2043 return QString();
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() )
2054 return;
2056 if ( !d->m_generator )
2058 // delete requests..
2059 QLinkedList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
2060 for ( ; rIt != rEnd; ++rIt )
2061 delete *rIt;
2062 // ..and return
2063 return;
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
2083 delete *sIt;
2084 sIt = d->m_pixmapRequestsStack.erase( sIt );
2086 else
2087 ++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)
2101 delete request;
2102 continue;
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 );
2117 else
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() )
2123 ++sIt;
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 )
2141 return;
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 )
2153 return;
2155 // the annotation belongs already to a page
2156 if ( annotation->d_ptr->m_page )
2157 return;
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 )
2173 return;
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 )
2186 return;
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 )
2201 return;
2203 bool changed = false;
2204 foreach ( Annotation * annotation, annotations )
2206 // try to remove the annotation
2207 if ( kp->removeAnnotation( annotation ) )
2209 changed = true;
2212 if ( changed )
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 )
2223 return;
2225 // add or remove the selection basing whether rect is null or not
2226 if ( rect )
2227 kp->d->setTextSelections( rect, color );
2228 else
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]
2253 if ( page < 0 )
2254 page = 0;
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;
2276 else
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 );
2308 continue;
2310 ++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;
2341 ++nextIterator;
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 );
2364 return;
2367 if ( !noDialogs )
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 )
2436 if ( newText )
2437 match = lastPage->findText( searchID, text, FromTop, caseSensitivity );
2438 else
2439 match = lastPage->findText( searchID, text, NextResult, caseSensitivity, &s->continueOnMatch );
2440 if ( !match )
2441 currentPage++;
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 )
2458 if ( newText )
2459 match = lastPage->findText( searchID, text, FromBottom, caseSensitivity );
2460 else
2461 match = lastPage->findText( searchID, text, PreviousResult, caseSensitivity, &s->continueOnMatch );
2462 if ( !match )
2463 currentPage--;
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 );
2487 return;
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 );
2504 return;
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() )
2519 return;
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 );
2536 delete s;
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
2551 QList<int> list;
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 ) )
2559 list << i + 1;
2562 return list;
2565 QString Document::bookmarkedPageRange() const
2567 // Code formerly in Part::slotPrint()
2568 // range detecting
2569 QString range;
2570 uint docPages = pages();
2571 int startId = -1;
2572 int endId = -1;
2574 for ( uint i = 0; i < docPages; ++i )
2576 if ( bookmarkManager()->isBookmarked( i ) )
2578 if ( startId < 0 )
2579 startId = i;
2580 if ( endId < 0 )
2581 endId = startId;
2582 else
2583 ++endId;
2585 else if ( startId >= 0 && endId >= 0 )
2587 if ( !range.isEmpty() )
2588 range += ',';
2590 if ( endId - startId > 0 )
2591 range += QString( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 );
2592 else
2593 range += QString::number( startId + 1 );
2594 startId = -1;
2595 endId = -1;
2598 if ( startId >= 0 && endId >= 0 )
2600 if ( !range.isEmpty() )
2601 range += ',';
2603 if ( endId - startId > 0 )
2604 range += QString( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 );
2605 else
2606 range += QString::number( startId + 1 );
2608 return range;
2611 void Document::processAction( const Action * action )
2613 if ( !action )
2614 return;
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() << "'.";
2634 return;
2636 else
2638 // skip local links that point to nowhere (broken ones)
2639 if (!d->m_nextDocumentViewport.isValid())
2640 return;
2642 setViewport( d->m_nextDocumentViewport, -1, true );
2643 d->m_nextDocumentViewport = DocumentViewport();
2646 } break;
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 );
2654 return;
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.") );
2674 return;
2677 else
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.") );
2682 return;
2686 KService::Ptr ptr = KMimeTypeTrader::self()->preferredService( mime->name(), "Application" );
2687 if ( ptr )
2689 KUrl::List lst;
2690 lst.append( fileName );
2691 KRun::run( *ptr, lst, 0 );
2693 else
2694 KMessageBox::information( widget(), i18n( "No application found for opening file of mimetype %1.", mime->name() ) );
2695 } break;
2697 case Action::DocAction: {
2698 const DocumentAction * docaction = static_cast< const DocumentAction * >( action );
2699 switch( docaction->documentActionType() )
2701 case DocumentAction::PageFirst:
2702 setViewportPage( 0 );
2703 break;
2704 case DocumentAction::PagePrev:
2705 if ( (*d->m_viewportIterator).pageNumber > 0 )
2706 setViewportPage( (*d->m_viewportIterator).pageNumber - 1 );
2707 break;
2708 case DocumentAction::PageNext:
2709 if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 )
2710 setViewportPage( (*d->m_viewportIterator).pageNumber + 1 );
2711 break;
2712 case DocumentAction::PageLast:
2713 setViewportPage( d->m_pagesVector.count() - 1 );
2714 break;
2715 case DocumentAction::HistoryBack:
2716 setPrevViewport();
2717 break;
2718 case DocumentAction::HistoryForward:
2719 setNextViewport();
2720 break;
2721 case DocumentAction::Quit:
2722 emit quit();
2723 break;
2724 case DocumentAction::Presentation:
2725 emit linkPresentation();
2726 break;
2727 case DocumentAction::EndPresentation:
2728 emit linkEndPresentation();
2729 break;
2730 case DocumentAction::Find:
2731 emit linkFind();
2732 break;
2733 case DocumentAction::GoToPage:
2734 emit linkGoToPage();
2735 break;
2736 case DocumentAction::Close:
2737 emit close();
2738 break;
2740 } break;
2742 case Action::Browse: {
2743 const BrowseAction * browse = static_cast< const BrowseAction * >( action );
2744 QString lilySource;
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 );
2754 else
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));
2762 return;
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() );
2776 } break;
2778 case Action::Sound: {
2779 const SoundAction * linksound = static_cast< const SoundAction * >( action );
2780 AudioPlayer::instance()->playSound( linksound->sound(), linksound );
2781 } break;
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() );
2788 } break;
2790 case Action::Movie:
2791 //const MovieAction * movie = static_cast< const MovieAction * >( action );
2792 // TODO this (Movie action)
2793 break;
2797 void Document::processSourceReference( const SourceReference * ref )
2799 if ( !ref )
2800 return;
2802 if ( !QFile::exists( ref->fileName() ) )
2804 kDebug(OkularDebug).nospace() << "No such file: '" << ref->fileName() << "'";
2805 return;
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() );
2819 QString p;
2820 if ( it != editors.end() )
2821 p = *it;
2822 else
2823 p = Settings::externalEditorCommand();
2824 // custom editor not yet configured
2825 if ( p.isEmpty() )
2826 return;
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() );
2833 else
2834 p.append( QLatin1String( " " ) + ref->fileName() );
2836 // paranoic checks
2837 if ( p.isEmpty() || p.trimmed() == ref->fileName() )
2838 return;
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;
2853 #ifndef Q_OS_WIN
2854 if ( d->m_generator->hasFeature( Generator::PrintPostscript ) )
2856 return PostscriptPrinting;
2858 #endif
2862 return NoPrinting;
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;
2882 else
2883 return 0;
2886 void Document::fillConfigDialog( KConfigDialog * dialog )
2888 if ( !dialog )
2889 return;
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() );
2902 if ( iface )
2904 iface->addPages( dialog );
2905 pagesAdded = true;
2906 if ( !it.value().catalogName.isEmpty() )
2907 KGlobal::locale()->insertCatalog( it.value().catalogName );
2910 if ( pagesAdded )
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 )
2948 return 0;
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;
2954 // empty about data
2955 if ( kcd->isValid() && kcd->aboutData() && kcd->aboutData()->programName().isEmpty() )
2956 return 0;
2958 return kcd;
2961 bool Document::canSaveChanges() const
2963 if ( !d->m_generator )
2964 return false;
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() );
2970 if ( !saveIface )
2971 return false;
2973 return saveIface->supportsOption( SaveInterface::SaveChanges );
2976 bool Document::saveChanges( const QString &fileName )
2978 if ( !d->m_generator || fileName.isEmpty() )
2979 return false;
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 ) )
2986 return false;
2988 return saveIface->save( fileName, SaveInterface::SaveChanges );
2991 void Document::registerView( View *view )
2993 if ( !view )
2994 return;
2996 Document *viewDoc = view->viewDocument();
2997 if ( viewDoc )
2999 // check if already registered for this document
3000 if ( viewDoc == this )
3001 return;
3003 viewDoc->unregisterView( view );
3006 d->m_views.insert( view );
3007 view->d_func()->document = d;
3010 void Document::unregisterView( View *view )
3012 if ( !view )
3013 return;
3015 Document *viewDoc = view->viewDocument();
3016 if ( !viewDoc || viewDoc != this )
3017 return;
3019 view->d_func()->document = 0;
3020 d->m_views.remove( view );
3023 QByteArray Document::fontData(const FontInfo &font) const
3025 QByteArray result;
3027 if (d->m_generator)
3029 QMetaObject::invokeMethod(d->m_generator, "requestFontData", Qt::DirectConnection, Q_ARG(Okular::FontInfo, font), Q_ARG(QByteArray *, &result));
3032 return result;
3035 void DocumentPrivate::requestDone( PixmapRequest * req )
3037 if ( !req )
3038 return;
3040 if ( !m_generator || m_closingLoop )
3042 m_pixmapRequestsMutex.lock();
3043 m_executingPixmapRequests.removeAll( req );
3044 m_pixmapRequestsMutex.unlock();
3045 delete req;
3046 if ( m_closingLoop )
3047 m_closingLoop->exit();
3048 return;
3051 #ifndef NDEBUG
3052 if ( !m_generator->canGeneratePixmap() )
3053 kDebug(OkularDebug) << "requestDone with generator not in READY state.";
3054 #endif
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;
3065 delete p;
3066 break;
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 );
3081 #ifndef NDEBUG
3082 else
3083 kWarning(OkularDebug) << "Receiving a done request for the defunct observer" << req->id();
3084 #endif
3086 // 3. delete request
3087 m_pixmapRequestsMutex.lock();
3088 m_executingPixmapRequests.removeAll( req );
3089 m_pixmapRequestsMutex.unlock();
3090 delete req;
3092 // 4. start a new generation if some is pending
3093 m_pixmapRequestsMutex.lock();
3094 bool hasPixmaps = !m_pixmapRequestsStack.isEmpty();
3095 m_pixmapRequestsMutex.unlock();
3096 if ( hasPixmaps )
3097 sendGeneratorRequest();
3100 void DocumentPrivate::setPageBoundingBox( int page, const NormalizedRect& boundingBox )
3102 Page * kp = m_pagesVector[ page ];
3103 if ( !m_generator || !kp )
3104 return;
3106 if ( kp->boundingBox() == boundingBox )
3107 return;
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;
3127 break;
3129 case Settings::EnumMemoryLevel::Normal:
3130 m_maxAllocatedTextPages = multipliers * 50;
3131 break;
3133 case Settings::EnumMemoryLevel::Aggressive:
3134 m_maxAllocatedTextPages = multipliers * 250;
3135 break;
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 ) )
3166 return;
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 );
3173 if ( notify )
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;
3181 if ( notify )
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 ) )
3192 return;
3194 if ( d->m_pageSizes.isEmpty() )
3195 d->m_pageSizes = d->m_generator->pageSizes();
3196 int sizeid = d->m_pageSizes.indexOf( size );
3197 if ( sizeid == -1 )
3198 return;
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 )
3209 delete *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 )
3226 : pageNumber( n )
3228 // default settings
3229 rePos.enabled = false;
3230 rePos.normalizedX = 0.5;
3231 rePos.normalizedY = 0.0;
3232 rePos.pos = Center;
3233 autoFit.enabled = false;
3234 autoFit.width = false;
3235 autoFit.height = false;
3238 DocumentViewport::DocumentViewport( const QString & xmlDesc )
3239 : pageNumber( -1 )
3241 // default settings (maybe overridden below)
3242 rePos.enabled = false;
3243 rePos.normalizedX = 0.5;
3244 rePos.normalizedY = 0.0;
3245 rePos.pos = Center;
3246 autoFit.enabled = false;
3247 autoFit.width = false;
3248 autoFit.height = false;
3250 // check for string presence
3251 if ( xmlDesc.isEmpty() )
3252 return;
3254 // decode the string
3255 bool ok;
3256 int field = 0;
3257 QString token = xmlDesc.section( ';', field, field );
3258 while ( !token.isEmpty() )
3260 // decode the current token
3261 if ( field == 0 )
3263 pageNumber = token.toInt( &ok );
3264 if ( !ok )
3265 return;
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();
3272 rePos.pos = Center;
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
3289 field++;
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");
3307 return s;
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 );
3320 if ( !equal )
3321 return false;
3322 if ( rePos.enabled &&
3323 (( rePos.normalizedX != vp.rePos.normalizedX) ||
3324 ( rePos.normalizedY != vp.rePos.normalizedY ) || rePos.pos != vp.rePos.pos) )
3325 return false;
3326 if ( autoFit.enabled &&
3327 (( autoFit.width != vp.autoFit.width ) ||
3328 ( autoFit.height != vp.autoFit.height )) )
3329 return false;
3330 return true;
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();
3353 else
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 )
3365 switch ( key ) {
3366 case Title:
3367 set( "title", value, i18n( "Title" ) );
3368 break;
3369 case Subject:
3370 set( "subject", value, i18n( "Subject" ) );
3371 break;
3372 case Description:
3373 set( "description", value, i18n( "Description" ) );
3374 break;
3375 case Author:
3376 set( "author", value, i18n( "Author" ) );
3377 break;
3378 case Creator:
3379 set( "creator", value, i18n( "Creator" ) );
3380 break;
3381 case Producer:
3382 set( "producer", value, i18n( "Producer" ) );
3383 break;
3384 case Copyright:
3385 set( "copyright", value, i18n( "Copyright" ) );
3386 break;
3387 case Pages:
3388 set( "pages", value, i18n( "Pages" ) );
3389 break;
3390 case CreationDate:
3391 set( "creationDate", value, i18n( "Created" ) );
3392 break;
3393 case ModificationDate:
3394 set( "modificationDate", value, i18n( "Modified" ) );
3395 break;
3396 case MimeType:
3397 set( "mimeType", value, i18n( "Mime Type" ) );
3398 break;
3399 case Category:
3400 set( "category", value, i18n( "Category" ) );
3401 break;
3402 case Keywords:
3403 set( "keywords", value, i18n( "Keywords" ) );
3404 break;
3405 default:
3406 kWarning(OkularDebug) << "Invalid key passed";
3407 break;
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" );
3420 else
3421 return QString();
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; */