• Skip to content
  • Skip to link menu
KDE 4.0 API Reference
  • KDE API Reference
  • kdegraphics
  • Sitemap
  • Contact Us
 

okular

document.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002  *   Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it>             *
00003  *   Copyright (C) 2004-2007 by Albert Astals Cid <aacid@kde.org>          *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU General Public License as published by  *
00007  *   the Free Software Foundation; either version 2 of the License, or     *
00008  *   (at your option) any later version.                                   *
00009  ***************************************************************************/
00010 
00011 #include "document.h"
00012 #include "document_p.h"
00013 
00014 #ifdef Q_OS_WIN
00015 #define _WIN32_WINNT 0x0500
00016 #include <windows.h>
00017 #endif
00018 
00019 // qt/kde/system includes
00020 #include <QtCore/QtAlgorithms>
00021 #include <QtCore/QDir>
00022 #include <QtCore/QFile>
00023 #include <QtCore/QFileInfo>
00024 #include <QtCore/QMap>
00025 #include <QtCore/QProcess>
00026 #include <QtCore/QTextStream>
00027 #include <QtCore/QTimer>
00028 #include <QtGui/QApplication>
00029 #include <QtGui/QLabel>
00030 #include <QtGui/QPrinter>
00031 #include <QtGui/QPrintDialog>
00032 
00033 #include <kaboutdata.h>
00034 #include <kauthorized.h>
00035 #include <kcomponentdata.h>
00036 #include <kconfigdialog.h>
00037 #include <kdebug.h>
00038 #include <klibloader.h>
00039 #include <klocale.h>
00040 #include <kmessagebox.h>
00041 #include <kmimetypetrader.h>
00042 #include <krun.h>
00043 #include <kstandarddirs.h>
00044 #include <ktemporaryfile.h>
00045 #include <ktoolinvocation.h>
00046 
00047 // local includes
00048 #include "action.h"
00049 #include "annotations.h"
00050 #include "annotations_p.h"
00051 #include "audioplayer.h"
00052 #include "audioplayer_p.h"
00053 #include "bookmarkmanager.h"
00054 #include "chooseenginedialog_p.h"
00055 #include "debug_p.h"
00056 #include "generator_p.h"
00057 #include "interfaces/configinterface.h"
00058 #include "interfaces/guiinterface.h"
00059 #include "interfaces/printinterface.h"
00060 #include "observer.h"
00061 #include "page.h"
00062 #include "page_p.h"
00063 #include "pagecontroller_p.h"
00064 #include "settings.h"
00065 #include "sourcereference.h"
00066 
00067 #include <config-okular.h>
00068 
00069 using namespace Okular;
00070 
00071 struct AllocatedPixmap
00072 {
00073     // owner of the page
00074     int id;
00075     int page;
00076     qulonglong memory;
00077     // public constructor: initialize data
00078     AllocatedPixmap( int i, int p, qulonglong m ) : id( i ), page( p ), memory( m ) {}
00079 };
00080 
00081 struct RunningSearch
00082 {
00083     // store search properties
00084     int continueOnPage;
00085     RegularAreaRect continueOnMatch;
00086     QSet< int > highlightedPages;
00087 
00088     // fields related to previous searches (used for 'continueSearch')
00089     QString cachedString;
00090     Document::SearchType cachedType;
00091     Qt::CaseSensitivity cachedCaseSensitivity;
00092     bool cachedViewportMove : 1;
00093     bool cachedNoDialogs : 1;
00094     QColor cachedColor;
00095 };
00096 
00097 #define foreachObserver( cmd ) {\
00098     QMap< int, DocumentObserver * >::const_iterator it=d->m_observers.begin(), end=d->m_observers.end();\
00099     for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }
00100 
00101 #define foreachObserverD( cmd ) {\
00102     QMap< int, DocumentObserver * >::const_iterator it = m_observers.begin(), end = m_observers.end();\
00103     for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }
00104 
00105 #define OKULAR_HISTORY_MAXSTEPS 100
00106 #define OKULAR_HISTORY_SAVEDSTEPS 10
00107 
00108 /***** Document ******/
00109 
00110 QString DocumentPrivate::pagesSizeString() const
00111 {
00112     if (m_generator)
00113     {
00114         if (m_generator->pagesSizeMetric() != Generator::None)
00115         {
00116             QSizeF size = m_parent->allPagesSize();
00117             if (size.isValid()) return localizedSize(size);
00118             else return QString();
00119         }
00120         else return QString();
00121     }
00122     else return QString();
00123 }
00124 
00125 QString DocumentPrivate::localizedSize(const QSizeF &size) const
00126 {
00127     double inchesWidth = 0, inchesHeight = 0;
00128     switch (m_generator->pagesSizeMetric())
00129     {
00130         case Generator::Points:
00131             inchesWidth = size.width() / 72.0;
00132             inchesHeight = size.height() / 72.0;
00133         break;
00134 
00135         case Generator::None:
00136         break;
00137     }
00138     if (KGlobal::locale()->measureSystem() == KLocale::Imperial)
00139     {
00140         return i18n("%1 x %2 in", inchesWidth, inchesHeight);
00141     }
00142     else
00143     {
00144         return i18n("%1 x %2 mm", inchesWidth * 25.4, inchesHeight * 25.4);
00145     }
00146 }
00147 
00148 void DocumentPrivate::cleanupPixmapMemory( qulonglong /*sure? bytesOffset*/ )
00149 {
00150     // [MEM] choose memory parameters based on configuration profile
00151     qulonglong clipValue = ~0U;
00152     qulonglong memoryToFree = ~0U;
00153     switch ( Settings::memoryLevel() )
00154     {
00155         case Settings::EnumMemoryLevel::Low:
00156             memoryToFree = m_allocatedPixmapsTotalMemory;
00157             break;
00158 
00159         case Settings::EnumMemoryLevel::Normal:
00160             memoryToFree = m_allocatedPixmapsTotalMemory - getTotalMemory() / 3;
00161             clipValue = (m_allocatedPixmapsTotalMemory - getFreeMemory()) / 2;
00162             break;
00163 
00164         case Settings::EnumMemoryLevel::Aggressive:
00165             clipValue = (m_allocatedPixmapsTotalMemory - getFreeMemory()) / 2;
00166             break;
00167     }
00168 
00169     if ( clipValue > memoryToFree )
00170         memoryToFree = clipValue;
00171 
00172     if ( memoryToFree > 0 )
00173     {
00174         // [MEM] free memory starting from older pixmaps
00175         int pagesFreed = 0;
00176         QLinkedList< AllocatedPixmap * >::iterator pIt = m_allocatedPixmapsFifo.begin();
00177         QLinkedList< AllocatedPixmap * >::iterator pEnd = m_allocatedPixmapsFifo.end();
00178         while ( (pIt != pEnd) && (memoryToFree > 0) )
00179         {
00180             AllocatedPixmap * p = *pIt;
00181             if ( m_observers.value( p->id )->canUnloadPixmap( p->page ) )
00182             {
00183                 // update internal variables
00184                 pIt = m_allocatedPixmapsFifo.erase( pIt );
00185                 m_allocatedPixmapsTotalMemory -= p->memory;
00186                 memoryToFree -= p->memory;
00187                 pagesFreed++;
00188                 // delete pixmap
00189                 m_pagesVector.at( p->page )->deletePixmap( p->id );
00190                 // delete allocation descriptor
00191                 delete p;
00192             } else
00193                 ++pIt;
00194         }
00195         //p--rintf("freeMemory A:[%d -%d = %d] \n", m_allocatedPixmapsFifo.count() + pagesFreed, pagesFreed, m_allocatedPixmapsFifo.count() );
00196     }
00197 }
00198 
00199 qulonglong DocumentPrivate::getTotalMemory()
00200 {
00201     static qulonglong cachedValue = 0;
00202     if ( cachedValue )
00203         return cachedValue;
00204 
00205 #if defined(Q_OS_LINUX)
00206     // if /proc/meminfo doesn't exist, return 128MB
00207     QFile memFile( "/proc/meminfo" );
00208     if ( !memFile.open( QIODevice::ReadOnly ) )
00209         return (cachedValue = 134217728);
00210 
00211     // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
00212     // and 'Cached' fields. consider swapped memory as used memory.
00213     QTextStream readStream( &memFile );
00214      while ( true )
00215     {
00216         QString entry = readStream.readLine();
00217         if ( entry.isNull() ) break;
00218         if ( entry.startsWith( "MemTotal:" ) )
00219             return (cachedValue = (1024 * entry.section( ' ', -2, -2 ).toInt()));
00220     }
00221 #elif defined(Q_OS_WIN)
00222     MEMORYSTATUSEX stat;
00223 
00224     GlobalMemoryStatusEx (&stat);
00225 
00226     return ( cachedValue = stat.ullTotalPhys );
00227 #endif
00228     return (cachedValue = 134217728);
00229 }
00230 
00231 qulonglong DocumentPrivate::getFreeMemory()
00232 {
00233     static QTime lastUpdate = QTime::currentTime();
00234     static qulonglong cachedValue = 0;
00235 
00236     if ( lastUpdate.secsTo( QTime::currentTime() ) <= 2 )
00237         return cachedValue;
00238 
00239 #if defined(Q_OS_LINUX)
00240     // if /proc/meminfo doesn't exist, return MEMORY FULL
00241     QFile memFile( "/proc/meminfo" );
00242     if ( !memFile.open( QIODevice::ReadOnly ) )
00243         return 0;
00244 
00245     // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
00246     // and 'Cached' fields. consider swapped memory as used memory.
00247     qulonglong memoryFree = 0;
00248     QString entry;
00249     QTextStream readStream( &memFile );
00250     while ( true )
00251     {
00252         entry = readStream.readLine();
00253         if ( entry.isNull() ) break;
00254         if ( entry.startsWith( "MemFree:" ) ||
00255                 entry.startsWith( "Buffers:" ) ||
00256                 entry.startsWith( "Cached:" ) ||
00257                 entry.startsWith( "SwapFree:" ) )
00258             memoryFree += entry.section( ' ', -2, -2 ).toInt();
00259         if ( entry.startsWith( "SwapTotal:" ) )
00260             memoryFree -= entry.section( ' ', -2, -2 ).toInt();
00261     }
00262     memFile.close();
00263 
00264     lastUpdate = QTime::currentTime();
00265 
00266     return ( cachedValue = (1024 * memoryFree) );
00267 #elif defined(Q_OS_WIN)
00268     MEMORYSTATUSEX stat;
00269 
00270     GlobalMemoryStatusEx (&stat);
00271 
00272     lastUpdate = QTime::currentTime();
00273 
00274     return ( cachedValue = stat.ullAvailPhys );
00275 #else
00276     // tell the memory is full.. will act as in LOW profile
00277     return 0;
00278 #endif
00279 }
00280 
00281 void DocumentPrivate::loadDocumentInfo()
00282 // note: load data and stores it internally (document or pages). observers
00283 // are still uninitialized at this point so don't access them
00284 {
00285     //kDebug(OkularDebug).nospace() << "Using '" << d->m_xmlFileName << "' as document info file.";
00286     if ( m_xmlFileName.isEmpty() )
00287         return;
00288 
00289     QFile infoFile( m_xmlFileName );
00290     if ( !infoFile.exists() || !infoFile.open( QIODevice::ReadOnly ) )
00291         return;
00292 
00293     // Load DOM from XML file
00294     QDomDocument doc( "documentInfo" );
00295     if ( !doc.setContent( &infoFile ) )
00296     {
00297         kDebug(OkularDebug) << "Can't load XML pair! Check for broken xml.";
00298         infoFile.close();
00299         return;
00300     }
00301     infoFile.close();
00302 
00303     QDomElement root = doc.documentElement();
00304     if ( root.tagName() != "documentInfo" )
00305         return;
00306 
00307     KUrl documentUrl( root.attribute( "url" ) );
00308 
00309     // Parse the DOM tree
00310     QDomNode topLevelNode = root.firstChild();
00311     while ( topLevelNode.isElement() )
00312     {
00313         QString catName = topLevelNode.toElement().tagName();
00314 
00315         // Restore page attributes (bookmark, annotations, ...) from the DOM
00316         if ( catName == "pageList" )
00317         {
00318             QDomNode pageNode = topLevelNode.firstChild();
00319             while ( pageNode.isElement() )
00320             {
00321                 QDomElement pageElement = pageNode.toElement();
00322                 if ( pageElement.hasAttribute( "number" ) )
00323                 {
00324                     // get page number (node's attribute)
00325                     bool ok;
00326                     int pageNumber = pageElement.attribute( "number" ).toInt( &ok );
00327 
00328                     // pass the domElement to the right page, to read config data from
00329                     if ( ok && pageNumber >= 0 && pageNumber < (int)m_pagesVector.count() )
00330                         m_pagesVector[ pageNumber ]->d->restoreLocalContents( pageElement );
00331                 }
00332                 pageNode = pageNode.nextSibling();
00333             }
00334         }
00335 
00336         // Restore 'general info' from the DOM
00337         else if ( catName == "generalInfo" )
00338         {
00339             QDomNode infoNode = topLevelNode.firstChild();
00340             while ( infoNode.isElement() )
00341             {
00342                 QDomElement infoElement = infoNode.toElement();
00343 
00344                 // restore viewports history
00345                 if ( infoElement.tagName() == "history" )
00346                 {
00347                     // clear history
00348                     m_viewportHistory.clear();
00349                     // append old viewports
00350                     QDomNode historyNode = infoNode.firstChild();
00351                     while ( historyNode.isElement() )
00352                     {
00353                         QDomElement historyElement = historyNode.toElement();
00354                         if ( historyElement.hasAttribute( "viewport" ) )
00355                         {
00356                             QString vpString = historyElement.attribute( "viewport" );
00357                             m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(),
00358                                     DocumentViewport( vpString ) );
00359                         }
00360                         historyNode = historyNode.nextSibling();
00361                     }
00362                     // consistancy check
00363                     if ( m_viewportHistory.isEmpty() )
00364                         m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), DocumentViewport() );
00365                 }
00366                 else if ( infoElement.tagName() == "rotation" )
00367                 {
00368                     QString str = infoElement.text();
00369                     bool ok = true;
00370                     int newrotation = !str.isEmpty() ? ( str.toInt( &ok ) % 4 ) : 0;
00371                     if ( ok && newrotation != 0 )
00372                     {
00373                         setRotationInternal( newrotation, false );
00374                     }
00375                 }
00376                 infoNode = infoNode.nextSibling();
00377             }
00378         }
00379 
00380         topLevelNode = topLevelNode.nextSibling();
00381     } // </documentInfo>
00382 }
00383 
00384 QString DocumentPrivate::giveAbsolutePath( const QString & fileName ) const
00385 {
00386     if ( !m_url.isValid() )
00387         return QString();
00388 
00389     if ( !QDir::isRelativePath( fileName ) )
00390         return fileName;
00391 
00392     return m_url.upUrl().url() + fileName;
00393 }
00394 
00395 bool DocumentPrivate::openRelativeFile( const QString & fileName )
00396 {
00397     QString absFileName = giveAbsolutePath( fileName );
00398     if ( absFileName.isEmpty() )
00399         return false;
00400 
00401     kDebug(OkularDebug).nospace() << "openDocument: '" << absFileName << "'";
00402 
00403     emit m_parent->openUrl( absFileName );
00404     return true;
00405 }
00406 
00407 Generator * DocumentPrivate::loadGeneratorLibrary( const KService::Ptr &service )
00408 {
00409     KPluginFactory *factory = KPluginLoader( service->library() ).factory();
00410     if ( !factory )
00411     {
00412         kWarning(OkularDebug).nospace() << "Invalid plugin factory for " << service->library() << "!";
00413         return 0;
00414     }
00415     Generator * generator = factory->create< Okular::Generator >( 0 );
00416     GeneratorInfo info( factory->componentData() );
00417     info.generator = generator;
00418     if ( info.data.isValid() && info.data.aboutData() )
00419         info.catalogName = info.data.aboutData()->catalogName();
00420     m_loadedGenerators.insert( service->name(), info );
00421     return generator;
00422 }
00423 
00424 void DocumentPrivate::loadAllGeneratorLibraries()
00425 {
00426     if ( m_generatorsLoaded )
00427         return;
00428 
00429     m_generatorsLoaded = true;
00430 
00431     QString constraint("([X-KDE-Priority] > 0) and (exist Library)") ;
00432     KService::List offers = KServiceTypeTrader::self()->query( "okular/Generator", constraint );
00433     loadServiceList( offers );
00434 }
00435 
00436 void DocumentPrivate::loadServiceList( const KService::List& offers )
00437 {
00438     int count = offers.count();
00439     if ( count <= 0 )
00440         return;
00441 
00442     for ( int i = 0; i < count; ++i )
00443     {
00444         QString propName = offers.at(i)->name();
00445         // don't load already loaded generators
00446         QHash< QString, GeneratorInfo >::const_iterator genIt = m_loadedGenerators.constFind( propName );
00447         if ( !m_loadedGenerators.isEmpty() && genIt != m_loadedGenerators.end() )
00448             continue;
00449 
00450         Generator * g = loadGeneratorLibrary( offers.at(i) );
00451         (void)g;
00452     }
00453 }
00454 
00455 void DocumentPrivate::unloadGenerator( const GeneratorInfo& info )
00456 {
00457     delete info.generator;
00458 }
00459 
00460 void DocumentPrivate::cacheExportFormats()
00461 {
00462     if ( m_exportCached )
00463         return;
00464 
00465     const ExportFormat::List formats = m_generator->exportFormats();
00466     for ( int i = 0; i < formats.count(); ++i )
00467     {
00468         if ( formats.at( i ).mimeType()->name() == QLatin1String( "text/plain" ) )
00469             m_exportToText = formats.at( i );
00470         else
00471             m_exportFormats.append( formats.at( i ) );
00472     }
00473 
00474     m_exportCached = true;
00475 }
00476 
00477 ConfigInterface* DocumentPrivate::generatorConfig( GeneratorInfo& info )
00478 {
00479     if ( info.configChecked )
00480         return info.config;
00481 
00482     info.config = qobject_cast< Okular::ConfigInterface * >( info.generator );
00483     info.configChecked = true;
00484     return info.config;
00485 }
00486 
00487 void DocumentPrivate::saveDocumentInfo() const
00488 {
00489     if ( m_xmlFileName.isEmpty() )
00490         return;
00491 
00492     QFile infoFile( m_xmlFileName );
00493     if (infoFile.open( QIODevice::WriteOnly | QIODevice::Truncate) )
00494     {
00495         // 1. Create DOM
00496         QDomDocument doc( "documentInfo" );
00497         QDomElement root = doc.createElement( "documentInfo" );
00498         root.setAttribute( "url", m_url.pathOrUrl() );
00499         doc.appendChild( root );
00500 
00501         // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM
00502         QDomElement pageList = doc.createElement( "pageList" );
00503         root.appendChild( pageList );
00504         // <page list><page number='x'>.... </page> save pages that hold data
00505         QVector< Page * >::const_iterator pIt = m_pagesVector.begin(), pEnd = m_pagesVector.end();
00506         for ( ; pIt != pEnd; ++pIt )
00507             (*pIt)->d->saveLocalContents( pageList, doc );
00508 
00509         // 2.2. Save document info (current viewport, history, ... ) to DOM
00510         QDomElement generalInfo = doc.createElement( "generalInfo" );
00511         root.appendChild( generalInfo );
00512         // create rotation node
00513         if ( m_rotation != Rotation0 )
00514         {
00515             QDomElement rotationNode = doc.createElement( "rotation" );
00516             generalInfo.appendChild( rotationNode );
00517             rotationNode.appendChild( doc.createTextNode( QString::number( (int)m_rotation ) ) );
00518         }
00519         // <general info><history> ... </history> save history up to OKULAR_HISTORY_SAVEDSTEPS viewports
00520         QLinkedList< DocumentViewport >::const_iterator backIterator = m_viewportIterator;
00521         if ( backIterator != m_viewportHistory.end() )
00522         {
00523             // go back up to OKULAR_HISTORY_SAVEDSTEPS steps from the current viewportIterator
00524             int backSteps = OKULAR_HISTORY_SAVEDSTEPS;
00525             while ( backSteps-- && backIterator != m_viewportHistory.begin() )
00526                 --backIterator;
00527 
00528             // create history root node
00529             QDomElement historyNode = doc.createElement( "history" );
00530             generalInfo.appendChild( historyNode );
00531 
00532             // add old[backIterator] and present[viewportIterator] items
00533             QLinkedList< DocumentViewport >::const_iterator endIt = m_viewportIterator;
00534             ++endIt;
00535             while ( backIterator != endIt )
00536             {
00537                 QString name = (backIterator == m_viewportIterator) ? "current" : "oldPage";
00538                 QDomElement historyEntry = doc.createElement( name );
00539                 historyEntry.setAttribute( "viewport", (*backIterator).toString() );
00540                 historyNode.appendChild( historyEntry );
00541                 ++backIterator;
00542             }
00543         }
00544 
00545         // 3. Save DOM to XML file
00546         QString xml = doc.toString();
00547         QTextStream os( &infoFile );
00548         os << xml;
00549     }
00550     infoFile.close();
00551 }
00552 
00553 void DocumentPrivate::slotTimedMemoryCheck()
00554 {
00555     // [MEM] clean memory (for 'free mem dependant' profiles only)
00556     if ( Settings::memoryLevel() != Settings::EnumMemoryLevel::Low &&
00557          m_allocatedPixmapsTotalMemory > 1024*1024 )
00558         cleanupPixmapMemory();
00559 }
00560 
00561 void DocumentPrivate::sendGeneratorRequest()
00562 {
00563     // find a request
00564     PixmapRequest * request = 0;
00565     m_pixmapRequestsMutex.lock();
00566     while ( !m_pixmapRequestsStack.isEmpty() && !request )
00567     {
00568         PixmapRequest * r = m_pixmapRequestsStack.last();
00569         if (!r)
00570             m_pixmapRequestsStack.pop_back();
00571 
00572         // request only if page isn't already present or request has invalid id
00573         else if ( r->page()->hasPixmap( r->id(), r->width(), r->height() ) || r->id() <= 0 || r->id() >= MAX_OBSERVER_ID)
00574         {
00575             m_pixmapRequestsStack.pop_back();
00576             delete r;
00577         }
00578         else if ( (long)r->width() * (long)r->height() > 20000000L )
00579         {
00580             m_pixmapRequestsStack.pop_back();
00581             if ( !m_warnedOutOfMemory )
00582             {
00583                 kWarning(OkularDebug).nospace() << "Running out of memory on page " << r->pageNumber()
00584                     << " (" << r->width() << "x" << r->height() << " px);";
00585                 kWarning(OkularDebug) << "this message will be reported only once.";
00586                 m_warnedOutOfMemory = true;
00587             }
00588             delete r;
00589         }
00590         else
00591             request = r;
00592     }
00593 
00594     // if no request found (or already generated), return
00595     if ( !request )
00596     {
00597         m_pixmapRequestsMutex.unlock();
00598         return;
00599     }
00600 
00601     // [MEM] preventive memory freeing
00602     qulonglong pixmapBytes = 4 * request->width() * request->height();
00603     if ( pixmapBytes > (1024 * 1024) )
00604         cleanupPixmapMemory( pixmapBytes );
00605 
00606     // submit the request to the generator
00607     if ( m_generator->canGeneratePixmap() )
00608     {
00609         kDebug(OkularDebug).nospace() << "sending request id=" << request->id() << " " <<request->width() << "x" << request->height() << "@" << request->pageNumber() << " async == " << request->asynchronous();
00610         m_pixmapRequestsStack.removeAll ( request );
00611 
00612         if ( (int)m_rotation % 2 )
00613             request->d->swap();
00614 
00615         // we always have to unlock _before_ the generatePixmap() because
00616         // a sync generation would end with requestDone() -> deadlock, and
00617         // we can not really know if the generator can do async requests
00618         m_executingPixmapRequests.push_back( request );
00619         m_pixmapRequestsMutex.unlock();
00620         m_generator->generatePixmap( request );
00621     }
00622     else
00623     {
00624         m_pixmapRequestsMutex.unlock();
00625         // pino (7/4/2006): set the polling interval from 10 to 30
00626         QTimer::singleShot( 30, m_parent, SLOT(sendGeneratorRequest()) );
00627     }
00628 }
00629 
00630 void DocumentPrivate::rotationFinished( int page )
00631 {
00632     QMap< int, DocumentObserver * >::const_iterator it = m_observers.begin(), end = m_observers.end();
00633     for ( ; it != end ; ++ it ) {
00634         (*it)->notifyPageChanged( page, DocumentObserver::Pixmap | DocumentObserver::Annotations );
00635     }
00636 }
00637 
00638 void DocumentPrivate::fontReadingProgress( int page )
00639 {
00640     emit m_parent->fontReadingProgress( page );
00641 
00642     if ( page >= (int)m_parent->pages() - 1 )
00643     {
00644         emit m_parent->fontReadingEnded();
00645         m_fontThread = 0;
00646         m_fontsCached = true;
00647     }
00648 }
00649 
00650 void DocumentPrivate::fontReadingGotFont( const Okular::FontInfo& font )
00651 {
00652     // TODO try to avoid duplicate fonts
00653     m_fontsCache.append( font );
00654 
00655     emit m_parent->gotFont( font );
00656 }
00657 
00658 void DocumentPrivate::slotGeneratorConfigChanged( const QString& )
00659 {
00660     if ( !m_generator )
00661         return;
00662 
00663     // reparse generator config and if something changed clear Pages
00664     bool configchanged = false;
00665     QHash< QString, GeneratorInfo >::iterator it = m_loadedGenerators.begin(), itEnd = m_loadedGenerators.end();
00666     for ( ; it != itEnd; ++it )
00667     {
00668         Okular::ConfigInterface * iface = generatorConfig( it.value() );
00669         if ( iface )
00670         {
00671             bool it_changed = iface->reparseConfig();
00672             if ( it_changed && ( m_generator == it.value().generator ) )
00673                 configchanged = true;
00674         }
00675     }
00676     if ( configchanged )
00677     {
00678         // invalidate pixmaps
00679         QVector<Page*>::const_iterator it = m_pagesVector.begin(), end = m_pagesVector.end();
00680         for ( ; it != end; ++it ) {
00681             (*it)->deletePixmaps();
00682         }
00683 
00684         // [MEM] remove allocation descriptors
00685         QLinkedList< AllocatedPixmap * >::const_iterator aIt = m_allocatedPixmapsFifo.begin();
00686         QLinkedList< AllocatedPixmap * >::const_iterator aEnd = m_allocatedPixmapsFifo.end();
00687         for ( ; aIt != aEnd; ++aIt )
00688             delete *aIt;
00689         m_allocatedPixmapsFifo.clear();
00690         m_allocatedPixmapsTotalMemory = 0;
00691 
00692         // send reload signals to observers
00693         foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap ) );
00694     }
00695 
00696     // free memory if in 'low' profile
00697     if ( Settings::memoryLevel() == Settings::EnumMemoryLevel::Low &&
00698          !m_allocatedPixmapsFifo.isEmpty() && !m_pagesVector.isEmpty() )
00699         cleanupPixmapMemory();
00700 }
00701 
00702 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)
00703 {
00704     RegularAreaRect * match = static_cast<RegularAreaRect *>(theMatch);
00705     Qt::CaseSensitivity caseSensitivity = static_cast<Qt::CaseSensitivity>(theCaseSensitivity);
00706     QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet );
00707 
00708     if (m_searchCancelled && !match)
00709     {
00710         // if the user cancelled but he just got a match, give him the match!
00711         QApplication::restoreOverrideCursor();
00712         emit m_parent->searchFinished( searchID, Document::SearchCancelled );
00713         delete pagesToNotify;
00714         return;
00715     }
00716 
00717     // if no match found, loop through the whole doc, starting from currentPage
00718     if ( !match )
00719     {
00720         int pageCount = m_pagesVector.count();
00721         if (donePages < pageCount)
00722         {
00723             bool doContinue = true;
00724             if ( currentPage >= pageCount )
00725             {
00726                 if ( noDialogs || KMessageBox::questionYesNo(m_parent->widget(), i18n("End of document reached.\nContinue from the beginning?"), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel()) == KMessageBox::Yes )
00727                     currentPage = 0;
00728                 else
00729                     doContinue = false;
00730             }
00731             if (doContinue)
00732             {
00733                 // get page
00734                 Page * page = m_pagesVector[ currentPage ];
00735                 // request search page if needed
00736                 if ( !page->hasTextPage() )
00737                     m_parent->requestTextPage( page->number() );
00738                 // if found a match on the current page, end the loop
00739                 match = page->findText( searchID, text, FromTop, caseSensitivity );
00740 
00741                 if ( !match ) currentPage++;
00742 
00743                 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 +1));
00744                 return;
00745             }
00746         }
00747     }
00748 
00749     // reset cursor to previous shape
00750     QApplication::restoreOverrideCursor();
00751 
00752     bool foundAMatch = false;
00753 
00754     // if a match has been found..
00755     if ( match )
00756     {
00757         // update the RunningSearch structure adding this match..
00758         RunningSearch * s = m_searches[searchID];
00759         foundAMatch = true;
00760         s->continueOnPage = currentPage;
00761         s->continueOnMatch = *match;
00762         s->highlightedPages.insert( currentPage );
00763         // ..add highlight to the page..
00764         m_pagesVector[ currentPage ]->d->setHighlight( searchID, match, color );
00765 
00766         // ..queue page for notifying changes..
00767         pagesToNotify->insert( currentPage );
00768 
00769         // ..move the viewport to show the first of the searched word sequence centered
00770         if ( moveViewport )
00771         {
00772             DocumentViewport searchViewport( currentPage );
00773             searchViewport.rePos.enabled = true;
00774             searchViewport.rePos.normalizedX = (match->first().left + match->first().right) / 2.0;
00775             searchViewport.rePos.normalizedY = (match->first().top + match->first().bottom) / 2.0;
00776             m_parent->setViewport( searchViewport, -1, true );
00777         }
00778         delete match;
00779     }
00780     else if ( !noDialogs )
00781         KMessageBox::information( m_parent->widget(), i18n( "No matches found for '%1'.", text ) );
00782 
00783     // notify observers about highlights changes
00784     foreach(int pageNumber, *pagesToNotify)
00785         foreach(DocumentObserver *observer, m_observers)
00786             observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights );
00787 
00788     if (foundAMatch) emit m_parent->searchFinished( searchID, Document::MatchFound );
00789     else emit m_parent->searchFinished( searchID, Document::NoMatchFound );
00790 
00791     delete pagesToNotify;
00792 }
00793 
00794 void DocumentPrivate::doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QString & text, int theCaseSensitivity, const QColor & color)
00795 {
00796     QMap< Page *, QVector<RegularAreaRect *> > *pageMatches = static_cast< QMap< Page *, QVector<RegularAreaRect *> > * >(pageMatchesMap);
00797     Qt::CaseSensitivity caseSensitivity = static_cast<Qt::CaseSensitivity>(theCaseSensitivity);
00798     QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet );
00799 
00800     if (m_searchCancelled)
00801     {
00802         typedef QVector<RegularAreaRect *> MatchesVector;
00803 
00804         QApplication::restoreOverrideCursor();
00805         emit m_parent->searchFinished( searchID, Document::SearchCancelled );
00806         foreach(const MatchesVector &mv, *pageMatches) qDeleteAll(mv);
00807         delete pageMatches;
00808         delete pagesToNotify;
00809         return;
00810     }
00811 
00812     if (currentPage < m_pagesVector.count())
00813     {
00814         // get page (from the first to the last)
00815         Page *page = m_pagesVector.at(currentPage);
00816         int pageNumber = page->number(); // redundant? is it == currentPage ?
00817 
00818         // request search page if needed
00819         if ( !page->hasTextPage() )
00820             m_parent->requestTextPage( pageNumber );
00821 
00822         // loop on a page adding highlights for all found items
00823         RegularAreaRect * lastMatch = 0;
00824         while ( 1 )
00825         {
00826             if ( lastMatch )
00827                 lastMatch = page->findText( searchID, text, NextResult, caseSensitivity, lastMatch );
00828             else
00829                 lastMatch = page->findText( searchID, text, FromTop, caseSensitivity );
00830 
00831             if ( !lastMatch )
00832                 break;
00833 
00834             // add highligh rect to the matches map
00835             (*pageMatches)[page].append(lastMatch);
00836         }
00837         delete lastMatch;
00838 
00839         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));
00840     }
00841     else
00842     {
00843         // reset cursor to previous shape
00844         QApplication::restoreOverrideCursor();
00845 
00846         RunningSearch * s = m_searches[searchID];
00847         bool foundAMatch = pageMatches->count() != 0;
00848         QMap< Page *, QVector<RegularAreaRect *> >::const_iterator it, itEnd;
00849         it = pageMatches->begin();
00850         itEnd = pageMatches->end();
00851         for ( ; it != itEnd; ++it)
00852         {
00853             foreach(RegularAreaRect *match, it.value())
00854             {
00855                 it.key()->d->setHighlight( searchID, match, color );
00856                 delete match;
00857             }
00858             s->highlightedPages.insert( it.key()->number() );
00859             pagesToNotify->insert( it.key()->number() );
00860         }
00861 
00862         foreach(DocumentObserver *observer, m_observers)
00863             observer->notifySetup( m_pagesVector, 0 );
00864 
00865         // notify observers about highlights changes
00866         foreach(int pageNumber, *pagesToNotify)
00867             foreach(DocumentObserver *observer, m_observers)
00868                 observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights );
00869 
00870         if (foundAMatch) emit m_parent->searchFinished(searchID, Document::MatchFound );
00871         else emit m_parent->searchFinished( searchID, Document::NoMatchFound );
00872 
00873         delete pageMatches;
00874         delete pagesToNotify;
00875     }
00876 }
00877 
00878 void DocumentPrivate::doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QString & text, int theCaseSensitivity, const QColor & color, bool matchAll)
00879 {
00880     typedef QPair<RegularAreaRect *, QColor> MatchColor;
00881     QMap< Page *, QVector<MatchColor> > *pageMatches = static_cast< QMap< Page *, QVector<MatchColor> > * >(pageMatchesMap);
00882     Qt::CaseSensitivity caseSensitivity = static_cast<Qt::CaseSensitivity>(theCaseSensitivity);
00883     QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet );
00884 
00885     if (m_searchCancelled)
00886     {
00887         typedef QVector<MatchColor> MatchesVector;
00888 
00889         QApplication::restoreOverrideCursor();
00890         emit m_parent->searchFinished( searchID, Document::SearchCancelled );
00891 
00892         foreach(const MatchesVector &mv, *pageMatches)
00893         {
00894             foreach(const MatchColor &mc, mv) delete mc.first;
00895         }
00896         delete pageMatches;
00897         delete pagesToNotify;
00898         return;
00899     }
00900 
00901     QStringList words = text.split( " ", QString::SkipEmptyParts );
00902     const int wordCount = words.count();
00903     const int hueStep = (wordCount > 1) ? (60 / (wordCount - 1)) : 60;
00904     int baseHue, baseSat, baseVal;
00905     color.getHsv( &baseHue, &baseSat, &baseVal );
00906 
00907     if (currentPage < m_pagesVector.count())
00908     {
00909         // get page (from the first to the last)
00910         Page *page = m_pagesVector.at(currentPage);
00911         int pageNumber = page->number(); // redundant? is it == currentPage ?
00912 
00913         // request search page if needed
00914         if ( !page->hasTextPage() )
00915             m_parent->requestTextPage( pageNumber );
00916 
00917         // loop on a page adding highlights for all found items
00918         bool allMatched = wordCount > 0,
00919              anyMatched = false;
00920         for ( int w = 0; w < wordCount; w++ )
00921         {
00922             const QString &word = words[ w ];
00923             int newHue = baseHue - w * hueStep;
00924             if ( newHue < 0 )
00925                 newHue += 360;
00926             QColor wordColor = QColor::fromHsv( newHue, baseSat, baseVal );
00927             RegularAreaRect * lastMatch = 0;
00928             // add all highlights for current word
00929             bool wordMatched = false;
00930             while ( 1 )
00931             {
00932                 if ( lastMatch )
00933                     lastMatch = page->findText( searchID, word, NextResult, caseSensitivity, lastMatch );
00934                 else
00935                     lastMatch = page->findText( searchID, word, FromTop, caseSensitivity);
00936 
00937                 if ( !lastMatch )
00938                     break;
00939 
00940                 // add highligh rect to the matches map
00941                 (*pageMatches)[page].append(MatchColor(lastMatch, wordColor));
00942                 wordMatched = true;
00943             }
00944             allMatched = allMatched && wordMatched;
00945             anyMatched = anyMatched || wordMatched;
00946         }
00947 
00948         // if not all words are present in page, remove partial highlights
00949         if ( !allMatched && matchAll )
00950         {
00951             QVector<MatchColor> &matches = (*pageMatches)[page];
00952             foreach(const MatchColor &mc, matches) delete mc.first;
00953             pageMatches->remove(page);
00954         }
00955 
00956         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));
00957     }
00958     else
00959     {
00960         // reset cursor to previous shape
00961         QApplication::restoreOverrideCursor();
00962 
00963         RunningSearch * s = m_searches[searchID];
00964         bool foundAMatch = pageMatches->count() != 0;
00965         QMap< Page *, QVector<MatchColor> >::const_iterator it, itEnd;
00966         it = pageMatches->begin();
00967         itEnd = pageMatches->end();
00968         for ( ; it != itEnd; ++it)
00969         {
00970             foreach(const MatchColor &mc, it.value())
00971             {
00972                 it.key()->d->setHighlight( searchID, mc.first, mc.second );
00973                 delete mc.first;
00974             }
00975             s->highlightedPages.insert( it.key()->number() );
00976             pagesToNotify->insert( it.key()->number() );
00977         }
00978 
00979         // send page lists to update observers (since some filter on bookmarks)
00980         foreach(DocumentObserver *observer, m_observers)
00981             observer->notifySetup( m_pagesVector, 0 );
00982 
00983         // notify observers about highlights changes
00984         foreach(int pageNumber, *pagesToNotify)
00985             foreach(DocumentObserver *observer, m_observers)
00986                 observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights );
00987 
00988         if (foundAMatch) emit m_parent->searchFinished( searchID, Document::MatchFound );
00989         else emit m_parent->searchFinished( searchID, Document::NoMatchFound );
00990 
00991         delete pageMatches;
00992         delete pagesToNotify;
00993     }
00994 }
00995 
00996 QVariant DocumentPrivate::documentMetaData( const QString &key, const QVariant &option ) const
00997 {
00998     if ( key == QLatin1String( "PaperColor" ) )
00999     {
01000         bool giveDefault = option.toBool();
01001         // load paper color from Settings, or use the default color (white)
01002         // if we were told to do so
01003         QColor color;
01004         if ( ( Settings::renderMode() == Settings::EnumRenderMode::Paper )
01005              && Settings::changeColors() )
01006         {
01007             color = Settings::paperColor();
01008         }
01009         else if ( giveDefault )
01010         {
01011             color = Qt::white;
01012         }
01013         return color;
01014     }
01015     else if ( key == QLatin1String( "ZoomFactor" ) )
01016     {
01017         return Settings::zoomFactor();
01018     }
01019     else if ( key == QLatin1String( "TextAntialias" ) )
01020     {
01021         // TODO: add a configuration
01022         // TODO: eventually read the KDE configuration
01023         return true;
01024     }
01025     else if ( key == QLatin1String( "GraphicsAntialias" ) )
01026     {
01027         // TODO: add a configuration
01028         return true;
01029     }
01030     return QVariant();
01031 }
01032 
01033 
01034 Document::Document( QWidget *widget )
01035     : QObject( widget ), d( new DocumentPrivate( this ) )
01036 {
01037     d->m_bookmarkManager = new BookmarkManager( d );
01038     d->m_viewportIterator = d->m_viewportHistory.insert( d->m_viewportHistory.end(), DocumentViewport() );
01039 
01040     connect( PageController::self(), SIGNAL( rotationFinished( int ) ),
01041              this, SLOT( rotationFinished( int ) ) );
01042 
01043     qRegisterMetaType<Okular::FontInfo>();
01044 }
01045 
01046 Document::~Document()
01047 {
01048     // stop any audio playback
01049     AudioPlayer::instance()->stopPlaybacks();
01050 
01051     // delete generator, pages, and related stuff
01052     closeDocument();
01053 
01054     // delete the bookmark manager
01055     delete d->m_bookmarkManager;
01056 
01057     // delete the loaded generators
01058     QHash< QString, GeneratorInfo >::const_iterator it = d->m_loadedGenerators.constBegin(), itEnd = d->m_loadedGenerators.constEnd();
01059     for ( ; it != itEnd; ++it )
01060         d->unloadGenerator( it.value() );
01061     d->m_loadedGenerators.clear();
01062 
01063     // delete the private structure
01064     delete d;
01065 }
01066 
01067 static bool kserviceMoreThan( const KService::Ptr &s1, const KService::Ptr &s2 )
01068 {
01069     return s1->property( "X-KDE-Priority" ).toInt() > s2->property( "X-KDE-Priority" ).toInt();
01070 }
01071 
01072 bool Document::openDocument( const QString & docFile, const KUrl& url, const KMimeType::Ptr &_mime )
01073 {
01074     KMimeType::Ptr mime = _mime;
01075     QByteArray filedata;
01076     qint64 document_size = -1;
01077     bool isstdin = url.fileName( KUrl::ObeyTrailingSlash ) == QLatin1String( "-" );
01078     if ( !isstdin )
01079     {
01080         if ( mime.count() <= 0 )
01081             return false;
01082 
01083         // docFile is always local so we can use QFile on it
01084         QFile fileReadTest( docFile );
01085         if ( !fileReadTest.open( QIODevice::ReadOnly ) )
01086         {
01087             d->m_docFileName.clear();
01088             return false;
01089         }
01090         // determine the related "xml document-info" filename
01091         d->m_url = url;
01092         d->m_docFileName = docFile;
01093         if ( url.isLocalFile() )
01094         {
01095         QString fn = docFile.contains('/') ? docFile.section('/', -1, -1) : docFile;
01096         document_size = fileReadTest.size();
01097         fn = QString::number( document_size ) + '.' + fn + ".xml";
01098         fileReadTest.close();
01099         QString newokular = "okular/docdata/" + fn;
01100         QString newokularfile = KStandardDirs::locateLocal( "data", newokular );
01101         if ( !QFile::exists( newokularfile ) )
01102         {
01103             QString oldkpdf = "kpdf/" + fn;
01104             QString oldkpdffile = KStandardDirs::locateLocal( "data", oldkpdf );
01105             if ( QFile::exists( oldkpdffile ) )
01106             {
01107                 // ### copy or move?
01108                 if ( !QFile::copy( oldkpdffile, newokularfile ) )
01109                     return false;
01110             }
01111         }
01112         d->m_xmlFileName = newokularfile;
01113         }
01114     }
01115     else
01116     {
01117         QFile qstdin;
01118         qstdin.open( stdin, QIODevice::ReadOnly );
01119         filedata = qstdin.readAll();
01120         mime = KMimeType::findByContent( filedata );
01121         if ( !mime || mime->name() == QLatin1String( "application/octet-stream" ) )
01122             return false;
01123         document_size = filedata.size();
01124     }
01125 
01126     // 0. load Generator
01127     // request only valid non-disabled plugins suitable for the mimetype
01128     QString constraint("([X-KDE-Priority] > 0) and (exist Library)") ;
01129     KService::List offers = KMimeTypeTrader::self()->query(mime->name(),"okular/Generator",constraint);
01130     if (offers.isEmpty())
01131     {
01132         emit error( i18n( "Can not find a plugin which is able to handle the passed document." ), -1 );
01133         kWarning(OkularDebug).nospace() << "No plugin for mimetype '" << mime->name() << "'.";
01134         return false;
01135     }
01136     int hRank=0;
01137     // best ranked offer search
01138     int offercount = offers.count();
01139     if ( offercount > 1 )
01140     {
01141         // sort the offers: the offers with an higher priority come before
01142         qStableSort( offers.begin(), offers.end(), kserviceMoreThan );
01143 
01144         if ( Settings::chooseGenerators() )
01145         {
01146             QStringList list;
01147             for ( int i = 0; i < offercount; ++i )
01148             {
01149                 list << offers.at(i)->name();
01150             }
01151 
01152             ChooseEngineDialog choose( list, mime, widget() );
01153 
01154             if ( choose.exec() == QDialog::Rejected )
01155                 return false;
01156 
01157             hRank = choose.selectedGenerator();
01158         }
01159     }
01160 
01161     QString propName = offers.at(hRank)->name();
01162     QHash< QString, GeneratorInfo >::const_iterator genIt = d->m_loadedGenerators.constFind( propName );
01163     QString catalogName;
01164     if ( genIt != d->m_loadedGenerators.constEnd() )
01165     {
01166         d->m_generator = genIt.value().generator;
01167         catalogName = genIt.value().catalogName;
01168     }
01169     else
01170     {
01171         d->m_generator = d->loadGeneratorLibrary( offers.at(hRank) );
01172         if ( !d->m_generator )
01173             return false;
01174         genIt = d->m_loadedGenerators.constFind( propName );
01175         Q_ASSERT( genIt != d->m_loadedGenerators.constEnd() );
01176         catalogName = genIt.value().catalogName;
01177     }
01178     Q_ASSERT_X( d->m_generator, "Document::load()", "null generator?!" );
01179 
01180     if ( !catalogName.isEmpty() )
01181         KGlobal::locale()->insertCatalog( catalogName );
01182 
01183     d->m_generator->d_func()->m_document = d;
01184 
01185     // connect error reporting signals
01186     connect( d->m_generator, SIGNAL( error( const QString&, int ) ), this, SIGNAL( error( const QString&, int ) ) );
01187     connect( d->m_generator, SIGNAL( warning( const QString&, int ) ), this, SIGNAL( warning( const QString&, int ) ) );
01188     connect( d->m_generator, SIGNAL( notice( const QString&, int ) ), this, SIGNAL( notice( const QString&, int ) ) );
01189 
01190     // 1. load Document (and set busy cursor while loading)
01191     QApplication::setOverrideCursor( Qt::WaitCursor );
01192     bool openOk = false;
01193     if ( !isstdin )
01194     {
01195         openOk = d->m_generator->loadDocument( docFile, d->m_pagesVector );
01196     }
01197     else if ( !filedata.isEmpty() )
01198     {
01199         if ( d->m_generator->hasFeature( Generator::ReadRawData ) )
01200         {
01201             openOk = d->m_generator->loadDocumentFromData( filedata, d->m_pagesVector );
01202         }
01203         else
01204         {
01205             d->m_tempFile = new KTemporaryFile();
01206             if ( !d->m_tempFile->open() )
01207             {
01208                 delete d->m_tempFile;
01209                 d->m_tempFile = 0;
01210             }
01211             else
01212             {
01213                 d->m_tempFile->write( filedata );
01214                 QString tmpFileName = d->m_tempFile->fileName();
01215                 d->m_tempFile->close();
01216                 openOk = d->m_generator->loadDocument( tmpFileName, d->m_pagesVector );
01217             }
01218         }
01219     }
01220 
01221     QApplication::restoreOverrideCursor();
01222     if ( !openOk || d->m_pagesVector.size() <= 0 )
01223     {
01224         if ( !catalogName.isEmpty() )
01225             KGlobal::locale()->removeCatalog( catalogName );
01226 
01227         d->m_generator = 0;
01228         return openOk;
01229     }
01230 
01231     d->m_generatorName = propName;
01232 
01233     // 2. load Additional Data (our bookmarks and metadata) about the document
01234     d->loadDocumentInfo();
01235     d->m_bookmarkManager->setUrl( d->m_url );
01236 
01237     // 3. setup observers inernal lists and data
01238     foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged ) );
01239 
01240     // 4. set initial page (restoring the page saved in xml if loaded)
01241     DocumentViewport loadedViewport = (*d->m_viewportIterator);
01242     if ( loadedViewport.isValid() )
01243     {
01244         (*d->m_viewportIterator) = DocumentViewport();
01245         if ( loadedViewport.pageNumber >= (int)d->m_pagesVector.size() )
01246             loadedViewport.pageNumber = d->m_pagesVector.size() - 1;
01247     }
01248     else
01249         loadedViewport.pageNumber = 0;
01250     setViewport( loadedViewport );
01251 
01252     // start bookmark saver timer
01253     if ( !d->m_saveBookmarksTimer )
01254     {
01255         d->m_saveBookmarksTimer = new QTimer( this );
01256         connect( d->m_saveBookmarksTimer, SIGNAL( timeout() ), this, SLOT( saveDocumentInfo() ) );
01257     }
01258     d->m_saveBookmarksTimer->start( 5 * 60 * 1000 );
01259 
01260     // start memory check timer
01261     if ( !d->m_memCheckTimer )
01262     {
01263         d->m_memCheckTimer = new QTimer( this );
01264         connect( d->m_memCheckTimer, SIGNAL( timeout() ), this, SLOT( slotTimedMemoryCheck() ) );
01265     }
01266     d->m_memCheckTimer->start( 2000 );
01267 
01268     if (d->m_nextDocumentViewport.isValid())
01269     {
01270         setViewport(d->m_nextDocumentViewport);
01271         d->m_nextDocumentViewport = DocumentViewport();
01272     }
01273 
01274     AudioPlayer::instance()->d->m_currentDocument = isstdin ? KUrl() : d->m_url;
01275     d->m_docSize = document_size;
01276 
01277     return true;
01278 }
01279 
01280 
01281 KXMLGUIClient* Document::guiClient()
01282 {
01283     if ( d->m_generator )
01284     {
01285         Okular::GuiInterface * iface = qobject_cast< Okular::GuiInterface * >( d->m_generator );
01286         if ( iface )
01287             return iface->guiClient();
01288     }
01289     return 0;
01290 }
01291 
01292 void Document::closeDocument()
01293 {
01294     // check if there's anything to close...
01295     if ( !d->m_generator )
01296         return;
01297 
01298     QEventLoop loop;
01299     bool startEventLoop = false;
01300     do
01301     {
01302         d->m_pixmapRequestsMutex.lock();
01303         startEventLoop = !d->m_executingPixmapRequests.isEmpty();
01304         d->m_pixmapRequestsMutex.unlock();
01305         if ( startEventLoop )
01306         {
01307             d->m_closingLoop = &loop;
01308             loop.exec();
01309             d->m_closingLoop = 0;
01310         }
01311     }
01312     while ( startEventLoop );
01313 
01314     if ( d->m_fontThread )
01315     {
01316         disconnect( d->m_fontThread, 0, this, 0 );
01317         d->m_fontThread->stopExtraction();
01318         d->m_fontThread->wait();
01319         d->m_fontThread = 0;
01320     }
01321 
01322     // close the current document and save document info if a document is still opened
01323     if ( d->m_generator && d->m_pagesVector.size() > 0 )
01324     {
01325         d->saveDocumentInfo();
01326         d->m_generator->closeDocument();
01327     }
01328 
01329     // stop timers
01330     if ( d->m_memCheckTimer )
01331         d->m_memCheckTimer->stop();
01332     if ( d->m_saveBookmarksTimer )
01333         d->m_saveBookmarksTimer->stop();
01334 
01335     if ( d->m_generator )
01336     {
01337         // disconnect the generator from this document ...
01338         d->m_generator->d_func()->m_document = 0;
01339         // .. and this document from the generator signals
01340         disconnect( d->m_generator, 0, this, 0 );
01341 
01342         QHash< QString, GeneratorInfo >::const_iterator genIt = d->m_loadedGenerators.constFind( d->m_generatorName );
01343         Q_ASSERT( genIt != d->m_loadedGenerators.constEnd() );
01344         if ( !genIt.value().catalogName.isEmpty() && !genIt.value().config )
01345             KGlobal::locale()->removeCatalog( genIt.value().catalogName );
01346     }
01347     d->m_generator = 0;
01348     d->m_generatorName = QString();
01349     d->m_url = KUrl();
01350     d->m_docFileName = QString();
01351     d->m_xmlFileName = QString();
01352     delete d->m_tempFile;
01353     d->m_tempFile = 0;
01354     d->m_docSize = -1;
01355     d->m_exportCached = false;
01356     d->m_exportFormats.clear();
01357     d->m_exportToText = ExportFormat();
01358     d->m_fontsCached = false;
01359     d->m_fontsCache.clear();
01360     d->m_rotation = Rotation0;
01361     // remove requests left in queue
01362     d->m_pixmapRequestsMutex.lock();
01363     QLinkedList< PixmapRequest * >::const_iterator sIt = d->m_pixmapRequestsStack.begin();
01364     QLinkedList< PixmapRequest * >::const_iterator sEnd = d->m_pixmapRequestsStack.end();
01365     for ( ; sIt != sEnd; ++sIt )
01366         delete *sIt;
01367     d->m_pixmapRequestsStack.clear();
01368     d->m_pixmapRequestsMutex.unlock();
01369 
01370     // send an empty list to observers (to free their data)
01371     foreachObserver( notifySetup( QVector< Page * >(), DocumentObserver::DocumentChanged ) );
01372 
01373     // delete pages and clear 'd->m_pagesVector' container
01374     QVector< Page * >::const_iterator pIt = d->m_pagesVector.begin();
01375     QVector< Page * >::const_iterator pEnd = d->m_pagesVector.end();
01376     for ( ; pIt != pEnd; ++pIt )
01377         delete *pIt;
01378     d->m_pagesVector.clear();
01379 
01380     // clear 'memory allocation' descriptors
01381     QLinkedList< AllocatedPixmap * >::const_iterator aIt = d->m_allocatedPixmapsFifo.begin();
01382     QLinkedList< AllocatedPixmap * >::const_iterator aEnd = d->m_allocatedPixmapsFifo.end();
01383     for ( ; aIt != aEnd; ++aIt )
01384         delete *aIt;
01385     d->m_allocatedPixmapsFifo.clear();
01386 
01387     // clear 'running searches' descriptors
01388     QMap< int, RunningSearch * >::const_iterator rIt = d->m_searches.begin();
01389     QMap< int, RunningSearch * >::const_iterator rEnd = d->m_searches.end();
01390     for ( ; rIt != rEnd; ++rIt )
01391         delete *rIt;
01392     d->m_searches.clear();
01393 
01394     // clear the visible areas and notify the observers
01395     QVector< VisiblePageRect * >::const_iterator vIt = d->m_pageRects.begin();
01396     QVector< VisiblePageRect * >::const_iterator vEnd = d->m_pageRects.end();
01397     for ( ; vIt != vEnd; ++vIt )
01398         delete *vIt;
01399     d->m_pageRects.clear();
01400     foreachObserver( notifyVisibleRectsChanged() );
01401 
01402     // reset internal variables
01403 
01404     d->m_viewportHistory.clear();
01405     d->m_viewportHistory.append( DocumentViewport() );
01406     d->m_viewportIterator = d->m_viewportHistory.begin();
01407     d->m_allocatedPixmapsTotalMemory = 0;
01408     d->m_pageSize = PageSize();
01409     d->m_pageSizes.clear();
01410     AudioPlayer::instance()->d->m_currentDocument = KUrl();
01411 }
01412 
01413 void Document::addObserver( DocumentObserver * pObserver )
01414 {
01415     // keep the pointer to the observer in a map
01416     d->m_observers.insert( pObserver->observerId(), pObserver );
01417 
01418     // if the observer is added while a document is already opened, tell it
01419     if ( !d->m_pagesVector.isEmpty() )
01420     {
01421         pObserver->notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged );
01422         pObserver->notifyViewportChanged( false /*disables smoothMove*/ );
01423     }
01424 }
01425 
01426 void Document::removeObserver( DocumentObserver * pObserver )
01427 {
01428     // remove observer from the map. it won't receive notifications anymore
01429     if ( d->m_observers.contains( pObserver->observerId() ) )
01430     {
01431         // free observer's pixmap data
01432         int observerId = pObserver->observerId();
01433         QVector<Page*>::const_iterator it = d->m_pagesVector.begin(), end = d->m_pagesVector.end();
01434         for ( ; it != end; ++it )
01435             (*it)->deletePixmap( observerId );
01436 
01437         // [MEM] free observer's allocation descriptors
01438         QLinkedList< AllocatedPixmap * >::iterator aIt = d->m_allocatedPixmapsFifo.begin();
01439         QLinkedList< AllocatedPixmap * >::iterator aEnd = d->m_allocatedPixmapsFifo.end();
01440         while ( aIt != aEnd )
01441         {
01442             AllocatedPixmap * p = *aIt;
01443             if ( p->id == observerId )
01444             {
01445                 aIt = d->m_allocatedPixmapsFifo.erase( aIt );
01446                 delete p;
01447             }
01448             else
01449                 ++aIt;
01450         }
01451 
01452         // delete observer entry from the map
01453         d->m_observers.remove( observerId );
01454     }
01455 }
01456 
01457 void Document::reparseConfig()
01458 {
01459     // reparse generator config and if something changed clear Pages
01460     bool configchanged = false;
01461     if ( d->m_generator )
01462     {
01463         Okular::ConfigInterface * iface = qobject_cast< Okular::ConfigInterface * >( d->m_generator );
01464         if ( iface )
01465             configchanged = iface->reparseConfig();
01466     }
01467     if ( configchanged )
01468     {
01469         // invalidate pixmaps
01470         QVector<Page*>::const_iterator it = d->m_pagesVector.begin(), end = d->m_pagesVector.end();
01471         for ( ; it != end; ++it ) {
01472             (*it)->deletePixmaps();
01473         }
01474 
01475         // [MEM] remove allocation descriptors
01476         QLinkedList< AllocatedPixmap * >::const_iterator aIt = d->m_allocatedPixmapsFifo.begin();
01477         QLinkedList< AllocatedPixmap * >::const_iterator aEnd = d->m_allocatedPixmapsFifo.end();
01478         for ( ; aIt != aEnd; ++aIt )
01479             delete *aIt;
01480         d->m_allocatedPixmapsFifo.clear();
01481         d->m_allocatedPixmapsTotalMemory = 0;
01482 
01483         // send reload signals to observers
01484         foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap ) );
01485     }
01486 
01487     // free memory if in 'low' profile
01488     if ( Settings::memoryLevel() == Settings::EnumMemoryLevel::Low &&
01489          !d->m_allocatedPixmapsFifo.isEmpty() && !d->m_pagesVector.isEmpty() )
01490         d->cleanupPixmapMemory();
01491 }
01492 
01493 
01494 QWidget *Document::widget() const
01495 {
01496     return parent() ? static_cast< QWidget * >( parent() ) : 0;
01497 }
01498 
01499 bool Document::isOpened() const
01500 {
01501     return d->m_generator;
01502 }
01503 
01504 bool Document::canConfigurePrinter( ) const
01505 {
01506     if ( d->m_generator )
01507     {
01508         Okular::PrintInterface * iface = qobject_cast< Okular::PrintInterface * >( d->m_generator );
01509         return iface ? true : false;
01510     }
01511     else
01512         return 0;
01513 }
01514 
01515 const DocumentInfo * Document::documentInfo() const
01516 {
01517     if ( d->m_generator )
01518     {
01519         const DocumentInfo *infoConst = d->m_generator->generateDocumentInfo();
01520         if ( !infoConst )
01521             return 0;
01522 
01523         DocumentInfo *info = const_cast< DocumentInfo * >( infoConst );
01524         QString pagesSize = d->pagesSizeString();
01525         if ( d->m_docSize != -1 )
01526         {
01527             QString sizeString = KGlobal::locale()->formatByteSize( d->m_docSize );
01528             info->set( "documentSize", sizeString, i18n( "File Size" ) );
01529         }
01530         if (!pagesSize.isEmpty())
01531         {
01532             info->set( "pagesSize", pagesSize, i18n("Pages Size") );
01533         }
01534         return info;
01535     }
01536     else return NULL;
01537 }
01538 
01539 const DocumentSynopsis * Document::documentSynopsis() const
01540 {
01541     return d->m_generator ? d->m_generator->generateDocumentSynopsis() : NULL;
01542 }
01543 
01544 void Document::startFontReading()
01545 {
01546     if ( !d->m_generator || !d->m_generator->hasFeature( Generator::FontInfo ) || d->m_fontThread )
01547         return;
01548 
01549     if ( d->m_fontsCached )
01550     {
01551         // in case we have cached fonts, simulate a reading
01552         // this way the API is the same, and users no need to care about the
01553         // internal caching
01554         for ( int i = 0; i < d->m_fontsCache.count(); ++i )
01555         {
01556             emit gotFont( d->m_fontsCache.at( i ) );
01557             emit fontReadingProgress( i / pages() );
01558         }
01559         emit fontReadingEnded();
01560         return;
01561     }
01562 
01563     d->m_fontThread = new FontExtractionThread( d->m_generator, pages() );
01564     connect( d->m_fontThread, SIGNAL( gotFont( const Okular::FontInfo& ) ), this, SLOT( fontReadingGotFont( const Okular::FontInfo& ) ) );
01565     connect( d->m_fontThread, SIGNAL( progress( int ) ), this, SLOT( fontReadingProgress( int ) ) );
01566 
01567     d->m_fontThread->startExtraction( /*d->m_generator->hasFeature( Generator::Threaded )*/true );
01568 }
01569 
01570 void Document::stopFontReading()
01571 {
01572     if ( !d->m_fontThread )
01573         return;
01574 
01575     disconnect( d->m_fontThread, 0, this, 0 );
01576     d->m_fontThread->stopExtraction();
01577     d->m_fontThread = 0;
01578     d->m_fontsCache.clear();
01579 }
01580 
01581 bool Document::canProvideFontInformation() const
01582 {
01583     return d->m_generator ? d->m_generator->hasFeature( Generator::FontInfo ) : false;
01584 }
01585 
01586 const QList<EmbeddedFile*> *Document::embeddedFiles() const
01587 {
01588     return d->m_generator ? d->m_generator->embeddedFiles() : NULL;
01589 }
01590 
01591 const Page * Document::page( int n ) const
01592 {
01593     return ( n < d->m_pagesVector.count() ) ? d->m_pagesVector.at(n) : 0;
01594 }
01595 
01596 const DocumentViewport & Document::viewport() const
01597 {
01598     return (*d->m_viewportIterator);
01599 }
01600 
01601 const QVector< VisiblePageRect * > & Document::visiblePageRects() const
01602 {
01603     return d->m_pageRects;
01604 }
01605 
01606 void Document::setVisiblePageRects( const QVector< VisiblePageRect * > & visiblePageRects, int excludeId )
01607 {
01608     QVector< VisiblePageRect * >::const_iterator vIt = d->m_pageRects.begin();
01609     QVector< VisiblePageRect * >::const_iterator vEnd = d->m_pageRects.end();
01610     for ( ; vIt != vEnd; ++vIt )
01611         delete *vIt;
01612     d->m_pageRects = visiblePageRects;
01613     // notify change to all other (different from id) observers
01614     QMap< int, DocumentObserver * >::const_iterator it = d->m_observers.begin(), end = d->m_observers.end();
01615     for ( ; it != end ; ++ it )
01616         if ( it.key() != excludeId )
01617             (*it)->notifyVisibleRectsChanged();
01618 }
01619 
01620 uint Document::currentPage() const
01621 {
01622     return (*d->m_viewportIterator).pageNumber;
01623 }
01624 
01625 uint Document::pages() const
01626 {
01627     return d->m_pagesVector.size();
01628 }
01629 
01630 KUrl Document::currentDocument() const
01631 {
01632     return d->m_url;
01633 }
01634 
01635 bool Document::isAllowed( Permission action ) const
01636 {
01637 #if !OKULAR_FORCE_DRM
01638     if ( KAuthorized::authorize( "skip_drm" ) && !Okular::Settings::obeyDRM() )
01639         return true;
01640 #endif
01641 
01642     return d->m_generator ? d->m_generator->isAllowed( action ) : false;
01643 }
01644 
01645 bool Document::supportsSearching() const
01646 {
01647     return d->m_generator ? d->m_generator->hasFeature( Generator::TextExtraction ) : false;
01648 }
01649 
01650 bool Document::supportsPageSizes() const
01651 {
01652     return d->m_generator ? d->m_generator->hasFeature( Generator::PageSizes ) : false;
01653 }
01654 
01655 PageSize::List Document::pageSizes() const
01656 {
01657     if ( d->m_generator )
01658     {
01659         if ( d->m_pageSizes.isEmpty() )
01660             d->m_pageSizes = d->m_generator->pageSizes();
01661         return d->m_pageSizes;
01662     }
01663     return PageSize::List();
01664 }
01665 
01666 bool Document::canExportToText() const
01667 {
01668     if ( !d->m_generator )
01669         return false;
01670 
01671     d->cacheExportFormats();
01672     return !d->m_exportToText.isNull();
01673 }
01674 
01675 bool Document::exportToText( const QString& fileName ) const
01676 {
01677     if ( !d->m_generator )
01678         return false;
01679 
01680     d->cacheExportFormats();
01681     if ( d->m_exportToText.isNull() )
01682         return false;
01683 
01684     return d->m_generator->exportTo( fileName, d->m_exportToText );
01685 }
01686 
01687 ExportFormat::List Document::exportFormats() const
01688 {
01689     if ( !d->m_generator )
01690         return ExportFormat::List();
01691 
01692     d->cacheExportFormats();
01693     return d->m_exportFormats;
01694 }
01695 
01696 bool Document::exportTo( const QString& fileName, const ExportFormat& format ) const
01697 {
01698     return d->m_generator ? d->m_generator->exportTo( fileName, format ) : false;
01699 }
01700 
01701 bool Document::historyAtBegin() const
01702 {
01703     return d->m_viewportIterator == d->m_viewportHistory.begin();
01704 }
01705 
01706 bool Document::historyAtEnd() const
01707 {
01708     return d->m_viewportIterator == --(d->m_viewportHistory.end());
01709 }
01710 
01711 QVariant Document::metaData( const QString & key, const QVariant & option ) const
01712 {
01713     return d->m_generator ? d->m_generator->metaData( key, option ) : QVariant();
01714 }
01715 
01716 Rotation Document::rotation() const
01717 {
01718     return d->m_rotation;
01719 }
01720 
01721 QSizeF Document::allPagesSize() const
01722 {
01723     bool allPagesSameSize = true;
01724     QSizeF size;
01725     for (int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i)
01726     {
01727         const Page *p = d->m_pagesVector.at(i);
01728         if (i == 0) size = QSizeF(p->width(), p->height());
01729         else
01730         {
01731             allPagesSameSize = (size == QSizeF(p->width(), p->height()));
01732         }
01733     }
01734     if (allPagesSameSize) return size;
01735     else return QSizeF();
01736 }
01737 
01738 QString Document::pageSizeString(int page) const
01739 {
01740     if (d->m_generator)
01741     {
01742         if (d->m_generator->pagesSizeMetric() != Generator::None)
01743         {
01744             const Page *p = d->m_pagesVector.at( page );
01745             return d->localizedSize(QSizeF(p->width(), p->height()));
01746         }
01747     }
01748     return QString();
01749 }
01750 
01751 void Document::requestPixmaps( const QLinkedList< PixmapRequest * > & requests )
01752 {
01753     if ( requests.isEmpty() )
01754         return;
01755 
01756     if ( !d->m_generator )
01757     {
01758         // delete requests..
01759         QLinkedList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
01760         for ( ; rIt != rEnd; ++rIt )
01761             delete *rIt;
01762         // ..and return
01763         return;
01764     }
01765 
01766     // 1. [CLEAN STACK] remove previous requests of requesterID
01767     int requesterID = requests.first()->id();
01768     d->m_pixmapRequestsMutex.lock();
01769     QLinkedList< PixmapRequest * >::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end();
01770     while ( sIt != sEnd )
01771     {
01772         if ( (*sIt)->id() == requesterID )
01773         {
01774             // delete request and remove it from stack
01775             delete *sIt;
01776             sIt = d->m_pixmapRequestsStack.erase( sIt );
01777         }
01778         else
01779             ++sIt;
01780     }
01781 
01782     // 2. [ADD TO STACK] add requests to stack
01783     bool threadingDisabled = !Settings::enableThreading();
01784     QLinkedList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
01785     for ( ; rIt != rEnd; ++rIt )
01786     {
01787         // set the 'page field' (see PixmapRequest) and check if it is valid
01788         PixmapRequest * request = *rIt;
01789         kDebug(OkularDebug).nospace() << "request id=" << request->id() << " " <<request->width() << "x" << request->height() << "@" << request->pageNumber();
01790         if ( d->m_pagesVector.value( request->pageNumber() ) == 0 )
01791         {
01792             // skip requests referencing an invalid page (must not happen)
01793             delete request;
01794             continue;
01795         }
01796 
01797         request->d->mPage = d->m_pagesVector.value( request->pageNumber() );
01798 
01799         if ( !request->asynchronous() )
01800             request->d->mPriority = 0;
01801 
01802         if ( request->asynchronous() && threadingDisabled )
01803             request->d->mAsynchronous = false;
01804 
01805         // add request to the 'stack' at the right place
01806         if ( !request->priority() )
01807             // add priority zero requests to the top of the stack
01808             d->m_pixmapRequestsStack.append( request );
01809         else
01810         {
01811             // insert in stack sorted by priority
01812             sIt = d->m_pixmapRequestsStack.begin();
01813             sEnd = d->m_pixmapRequestsStack.end();
01814             while ( sIt != sEnd && (*sIt)->priority() > request->priority() )
01815                 ++sIt;
01816             d->m_pixmapRequestsStack.insert( sIt, request );
01817         }
01818     }
01819     d->m_pixmapRequestsMutex.unlock();
01820 
01821     // 3. [START FIRST GENERATION] if <NO>generator is ready, start a new generation,
01822     // or else (if gen is running) it will be started when the new contents will
01823     //come from generator (in requestDone())</NO>
01824     // all handling of requests put into sendGeneratorRequest
01825     //    if ( generator->canRequestPixmap() )
01826         d->sendGeneratorRequest();
01827 }
01828 
01829 void Document::requestTextPage( uint page )
01830 {
01831     Page * kp = d->m_pagesVector[ page ];
01832     if ( !d->m_generator || !kp )
01833         return;
01834 
01835     // Memory management for TextPages
01836 
01837     d->m_generator->generateTextPage( kp );
01838 }
01839 
01840 void Document::addPageAnnotation( int page, Annotation * annotation )
01841 {
01842     // find out the page to attach annotation
01843     Page * kp = d->m_pagesVector[ page ];
01844     if ( !d->m_generator || !kp )
01845         return;
01846 
01847     // the annotation belongs already to a page
01848     if ( annotation->d_ptr->m_page )
01849         return;
01850 
01851     // add annotation to the page
01852     kp->addAnnotation( annotation );
01853 
01854     // notify observers about the change
01855     foreachObserver( notifyPageChanged( page, DocumentObserver::Annotations ) );
01856 }
01857 
01858 void Document::modifyPageAnnotation( int page, Annotation * newannotation )
01859 {
01860     //TODO: modify annotations
01861 
01862     // find out the page
01863     Page * kp = d->m_pagesVector[ page ];
01864     if ( !d->m_generator || !kp )
01865         return;
01866 
01867     kp->d->modifyAnnotation( newannotation );
01868     // notify observers about the change
01869     foreachObserver( notifyPageChanged( page, DocumentObserver::Annotations ) );
01870 }
01871 
01872 
01873 void Document::removePageAnnotation( int page, Annotation * annotation )
01874 {
01875     // find out the page
01876     Page * kp = d->m_pagesVector[ page ];
01877     if ( !d->m_generator || !kp )
01878         return;
01879 
01880     // try to remove the annotation
01881     if ( kp->removeAnnotation( annotation ) )
01882     {
01883         // in case of success, notify observers about the change
01884         foreachObserver( notifyPageChanged( page, DocumentObserver::Annotations ) );
01885     }
01886 }
01887 
01888 void Document::removePageAnnotations( int page, const QList< Annotation * > &annotations )
01889 {
01890     // find out the page
01891     Page * kp = d->m_pagesVector[ page ];
01892     if ( !d->m_generator || !kp )
01893         return;
01894 
01895     bool changed = false;
01896     foreach ( Annotation * annotation, annotations )
01897     {
01898         // try to remove the annotation
01899         if ( kp->removeAnnotation( annotation ) )
01900         {
01901             changed = true;
01902         }
01903     }
01904     if ( changed )
01905     {
01906         // in case we removed even only one annotation, notify observers about the change
01907         foreachObserver( notifyPageChanged( page, DocumentObserver::Annotations ) );
01908     }
01909 }
01910 
01911 void Document::setPageTextSelection( int page, RegularAreaRect * rect, const QColor & color )
01912 {
01913     Page * kp = d->m_pagesVector[ page ];
01914     if ( !d->m_generator || !kp )
01915         return;
01916 
01917     // add or remove the selection basing whether rect is null or not
01918     if ( rect )
01919         kp->d->setTextSelections( rect, color );
01920     else
01921         kp->d->deleteTextSelections();
01922 
01923     // notify observers about the change
01924     foreachObserver( notifyPageChanged( page, DocumentObserver::TextSelection ) );
01925 }
01926 
01927 /* REFERENCE IMPLEMENTATION: better calling setViewport from other code
01928 void Document::setNextPage()
01929 {
01930     // advance page and set viewport on observers
01931     if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 )
01932         setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber + 1 ) );
01933 }
01934 
01935 void Document::setPrevPage()
01936 {
01937     // go to previous page and set viewport on observers
01938     if ( (*d->m_viewportIterator).pageNumber > 0 )
01939         setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber - 1 ) );
01940 }
01941 */
01942 void Document::setViewportPage( int page, int excludeId, bool smoothMove )
01943 {
01944     // clamp page in range [0 ... numPages-1]
01945     if ( page < 0 )
01946         page = 0;
01947     else if ( page > (int)d->m_pagesVector.count() )
01948         page = d->m_pagesVector.count() - 1;
01949 
01950     // make a viewport from the page and broadcast it
01951     setViewport( DocumentViewport( page ), excludeId, smoothMove );
01952 }
01953 
01954 void Document::setViewport( const DocumentViewport & viewport, int excludeId, bool smoothMove )
01955 {
01956     // if already broadcasted, don't redo it
01957     DocumentViewport & oldViewport = *d->m_viewportIterator;
01958     // disabled by enrico on 2005-03-18 (less debug output)
01959     //if ( viewport == oldViewport )
01960     //    kDebug(OkularDebug) << "setViewport with the same viewport.";
01961 
01962     // set internal viewport taking care of history
01963     if ( oldViewport.pageNumber == viewport.pageNumber || !oldViewport.isValid() )
01964     {
01965         // if page is unchanged save the viewport at current position in queue
01966         oldViewport = viewport;
01967     }
01968     else
01969     {
01970         // remove elements after viewportIterator in queue
01971         d->m_viewportHistory.erase( ++d->m_viewportIterator, d->m_viewportHistory.end() );
01972 
01973         // keep the list to a reasonable size by removing head when needed
01974         if ( d->m_viewportHistory.count() >= OKULAR_HISTORY_MAXSTEPS )
01975             d->m_viewportHistory.pop_front();
01976 
01977         // add the item at the end of the queue
01978         d->m_viewportIterator = d->m_viewportHistory.insert( d->m_viewportHistory.end(), viewport );
01979     }
01980 
01981     // notify change to all other (different from id) observers
01982     QMap< int, DocumentObserver * >::const_iterator it = d->m_observers.begin(), end = d->m_observers.end();
01983     for ( ; it != end ; ++ it )
01984         if ( it.key() != excludeId )
01985             (*it)->notifyViewportChanged( smoothMove );
01986 
01987     // [MEM] raise position of currently viewed page in allocation queue
01988     if ( d->m_allocatedPixmapsFifo.count() > 1 )
01989     {
01990         const int page = viewport.pageNumber;
01991         QLinkedList< AllocatedPixmap * > viewportPixmaps;
01992         QLinkedList< AllocatedPixmap * >::iterator aIt = d->m_allocatedPixmapsFifo.begin();
01993         QLinkedList< AllocatedPixmap * >::iterator aEnd = d->m_allocatedPixmapsFifo.end();
01994         while ( aIt != aEnd )
01995         {
01996             if ( (*aIt)->page == page )
01997             {
01998                 viewportPixmaps.append( *aIt );
01999                 aIt = d->m_allocatedPixmapsFifo.erase( aIt );
02000                 continue;
02001             }
02002             ++aIt;
02003         }
02004         if ( !viewportPixmaps.isEmpty() )
02005             d->m_allocatedPixmapsFifo += viewportPixmaps;
02006     }
02007 }
02008 
02009 void Document::setZoom(int factor, int excludeId)
02010 {
02011     // notify change to all other (different from id) observers
02012     QMap< int, DocumentObserver * >::const_iterator it = d->m_observers.begin(), end = d->m_observers.end();
02013     for ( ; it != end ; ++ it )
02014         if ( it.key() != excludeId )
02015             (*it)->notifyZoom( factor );
02016 }
02017 
02018 void Document::setPrevViewport()
02019 // restore viewport from the history
02020 {
02021     if ( d->m_viewportIterator != d->m_viewportHistory.begin() )
02022     {
02023         // restore previous viewport and notify it to observers
02024         --d->m_viewportIterator;
02025         foreachObserver( notifyViewportChanged( true ) );
02026     }
02027 }
02028 
02029 void Document::setNextViewport()
02030 // restore next viewport from the history
02031 {
02032     QLinkedList< DocumentViewport >::const_iterator nextIterator = d->m_viewportIterator;
02033     ++nextIterator;
02034     if ( nextIterator != d->m_viewportHistory.end() )
02035     {
02036         // restore next viewport and notify it to observers
02037         ++d->m_viewportIterator;
02038         foreachObserver( notifyViewportChanged( true ) );
02039     }
02040 }
02041 
02042 void Document::setNextDocumentViewport( const DocumentViewport & viewport )
02043 {
02044     d->m_nextDocumentViewport = viewport;
02045 }
02046 
02047 void Document::searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity,
02048                                SearchType type, bool moveViewport, const QColor & color, bool noDialogs )
02049 {
02050     d->m_searchCancelled = false;
02051 
02052     // safety checks: don't perform searches on empty or unsearchable docs
02053     if ( !d->m_generator || !d->m_generator->hasFeature( Generator::TextExtraction ) || d->m_pagesVector.isEmpty() )
02054     {
02055         emit searchFinished( searchID, NoMatchFound );
02056         return;
02057     }
02058 
02059     if ( !noDialogs )
02060     {
02061         KDialog *searchDialog = new KDialog(widget());
02062         searchDialog->setCaption( i18n("Search in progress...") );
02063         searchDialog->setButtons( KDialog::Cancel );
02064         QLabel *searchLabel = new QLabel(i18n("Searching for %1", text), searchDialog);
02065         searchDialog->setMainWidget( searchLabel );
02066 
02067         QTimer::singleShot(500, searchDialog, SLOT(show()));
02068         connect(this, SIGNAL( searchFinished(int, Okular::Document::SearchStatus) ), searchDialog, SLOT(deleteLater()));
02069         connect(searchDialog, SIGNAL( finished() ), this, SLOT(cancelSearch()));
02070     }
02071 
02072     // if searchID search not recorded, create new descriptor and init params
02073     QMap< int, RunningSearch * >::iterator searchIt = d->m_searches.find( searchID );
02074     if ( searchIt == d->m_searches.end() )
02075     {
02076         RunningSearch * search = new RunningSearch();
02077         search->continueOnPage = -1;
02078         searchIt = d->m_searches.insert( searchID, search );
02079     }
02080     if (d->m_lastSearchID != searchID)
02081     {
02082         resetSearch(d->m_lastSearchID);
02083     }
02084     d->m_lastSearchID = searchID;
02085     RunningSearch * s = *searchIt;
02086 
02087     // update search stucture
02088     bool newText = text != s->cachedString;
02089     s->cachedString = text;
02090     s->cachedType = type;
02091     s->cachedCaseSensitivity = caseSensitivity;
02092     s->cachedViewportMove = moveViewport;
02093     s->cachedNoDialogs = noDialogs;
02094     s->cachedColor = color;
02095 
02096     // global data for search
02097     QSet< int > *pagesToNotify = new QSet< int >;
02098 
02099     // remove highlights from pages and queue them for notifying changes
02100     *pagesToNotify += s->highlightedPages;
02101     foreach(int pageNumber, s->highlightedPages)
02102         d->m_pagesVector.at(pageNumber)->d->deleteHighlights( searchID );
02103     s->highlightedPages.clear();
02104 
02105     // set hourglass cursor
02106     QApplication::setOverrideCursor( Qt::WaitCursor );
02107 
02108     // 1. ALLDOC - proces all document marking pages
02109     if ( type == AllDocument )
02110     {
02111         QMap< Page *, QVector<RegularAreaRect *> > *pageMatches = new QMap< Page *, QVector<RegularAreaRect *> >;
02112 
02113         // search and highlight 'text' (as a solid phrase) on all pages
02114         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));
02115     }
02116     // 2. NEXTMATCH - find next matching item (or start from top)
02117     else if ( type == NextMatch )
02118     {
02119         // find out from where to start/resume search from
02120         int viewportPage = (*d->m_viewportIterator).pageNumber;
02121         int currentPage = fromStart ? 0 : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage);
02122         Page * lastPage = fromStart ? 0 : d->m_pagesVector[ currentPage ];
02123 
02124         // continue checking last TextPage first (if it is the current page)
02125         RegularAreaRect * match = 0;
02126         if ( lastPage && lastPage->number() == s->continueOnPage )
02127         {
02128             if ( newText )
02129                 match = lastPage->findText( searchID, text, FromTop, caseSensitivity );
02130             else
02131                 match = lastPage->findText( searchID, text, NextResult, caseSensitivity, &s->continueOnMatch );
02132             if ( !match )
02133                 currentPage++;
02134         }
02135 
02136         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));
02137     }
02138     // 3. PREVMATCH //TODO
02139     else if ( type == PreviousMatch )
02140     {
02141     }
02142     // 4. GOOGLE* - process all document marking pages
02143     else if ( type == GoogleAll || type == GoogleAny )
02144     {
02145         bool matchAll = type == GoogleAll;
02146 
02147         QMap< Page *, QVector< QPair<RegularAreaRect *, QColor> > > *pageMatches = new QMap< Page *, QVector<QPair<RegularAreaRect *, QColor> > >;
02148 
02149         // search and highlight every word in 'text' on all pages
02150         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));
02151     }
02152 }
02153 
02154 void Document::continueSearch( int searchID )
02155 {
02156     // check if searchID is present in runningSearches
02157     QMap< int, RunningSearch * >::const_iterator it = d->m_searches.constFind( searchID );
02158     if ( it == d->m_searches.constEnd() )
02159     {
02160         emit searchFinished( searchID, NoMatchFound );
02161         return;
02162     }
02163 
02164     // start search with cached parameters from last search by searchID
02165     RunningSearch * p = *it;
02166     searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity,
02167                 p->cachedType, p->cachedViewportMove, p->cachedColor,
02168                 p->cachedNoDialogs );
02169 }
02170 
02171 void Document::resetSearch( int searchID )
02172 {
02173     // check if searchID is present in runningSearches
02174     QMap< int, RunningSearch * >::iterator searchIt = d->m_searches.find( searchID );
02175     if ( searchIt == d->m_searches.end() )
02176         return;
02177 
02178     // get previous parameters for search
02179     RunningSearch * s = *searchIt;
02180 
02181     // unhighlight pages and inform observers about that
02182     foreach(int pageNumber, s->highlightedPages)
02183     {
02184         d->m_pagesVector.at(pageNumber)->d->deleteHighlights( searchID );
02185         foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) );
02186     }
02187 
02188     // send the setup signal too (to update views that filter on matches)
02189     foreachObserver( notifySetup( d->m_pagesVector, 0 ) );
02190 
02191     // remove serch from the runningSearches list and delete it
02192     d->m_searches.erase( searchIt );
02193     delete s;
02194 }
02195 
02196 void Document::cancelSearch()
02197 {
02198     d->m_searchCancelled = true;
02199 }
02200 
02201 BookmarkManager * Document::bookmarkManager() const
02202 {
02203     return d->m_bookmarkManager;
02204 }
02205 
02206 QList<int> Document::bookmarkedPageList() const
02207 {
02208     QList<int> list;
02209     uint docPages = pages();
02210 
02211     //pages are 0-indexed internally, but 1-indexed externally
02212     for ( uint i = 0; i < docPages; i++ )
02213     {
02214         if ( bookmarkManager()->isBookmarked( i ) )
02215         {
02216             list << i + 1;
02217         }
02218     }
02219     return list;
02220 }
02221 
02222 QString Document::bookmarkedPageRange() const
02223 {
02224     // Code formerly in Part::slotPrint()
02225     // range detecting
02226     QString range;
02227     uint docPages = pages();
02228     int startId = -1;
02229     int endId = -1;
02230 
02231     for ( uint i = 0; i < docPages; ++i )
02232     {
02233         if ( bookmarkManager()->isBookmarked( i ) )
02234         {
02235             if ( startId < 0 )
02236                 startId = i;
02237             if ( endId < 0 )
02238                 endId = startId;
02239             else
02240                 ++endId;
02241         }
02242         else if ( startId >= 0 && endId >= 0 )
02243         {
02244             if ( !range.isEmpty() )
02245                 range += ',';
02246 
02247             if ( endId - startId > 0 )
02248                 range += QString( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 );
02249             else
02250                 range += QString::number( startId + 1 );
02251             startId = -1;
02252             endId = -1;
02253         }
02254     }
02255     if ( startId >= 0 && endId >= 0 )
02256     {
02257         if ( !range.isEmpty() )
02258             range += ',';
02259 
02260         if ( endId - startId > 0 )
02261             range += QString( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 );
02262         else
02263             range += QString::number( startId + 1 );
02264     }
02265     return range;
02266 }
02267 
02268 void Document::processAction( const Action * action )
02269 {
02270     if ( !action )
02271         return;
02272 
02273     switch( action->actionType() )
02274     {
02275         case Action::Goto: {
02276             const GotoAction * go = static_cast< const GotoAction * >( action );
02277             d->m_nextDocumentViewport = go->destViewport();
02278 
02279             // Explanation of why d->m_nextDocumentViewport is needed:
02280             // all openRelativeFile does is launch a signal telling we
02281             // want to open another URL, the problem is that when the file is
02282             // non local, the loading is done assynchronously so you can't
02283             // do a setViewport after the if as it was because you are doing the setViewport
02284             // on the old file and when the new arrives there is no setViewport for it and
02285             // it does not show anything
02286 
02287             // first open filename if link is pointing outside this document
02288             if ( go->isExternal() && !d->openRelativeFile( go->fileName() ) )
02289             {
02290                 kWarning(OkularDebug).nospace() << "Action: Error opening '" << go->fileName() << "'.";
02291                 return;
02292             }
02293             else
02294             {
02295                 // skip local links that point to nowhere (broken ones)
02296                 if (!d->m_nextDocumentViewport.isValid())
02297                     return;
02298 
02299                 setViewport( d->m_nextDocumentViewport, -1, true );
02300                 d->m_nextDocumentViewport = DocumentViewport();
02301             }
02302 
02303             } break;
02304 
02305         case Action::Execute: {
02306             const ExecuteAction * exe  = static_cast< const ExecuteAction * >( action );
02307             QString fileName = exe->fileName();
02308             if ( fileName.endsWith( ".pdf" ) || fileName.endsWith( ".PDF" ) )
02309             {
02310                 d->openRelativeFile( fileName );
02311                 return;
02312             }
02313 
02314             // Albert: the only pdf i have that has that kind of link don't define
02315             // an application and use the fileName as the file to open
02316             fileName = d->giveAbsolutePath( fileName );
02317             KMimeType::Ptr mime = KMimeType::findByPath( fileName );
02318             // Check executables
02319             if ( KRun::isExecutableFile( fileName, mime->name() ) )
02320             {
02321                 // Don't have any pdf that uses this code path, just a guess on how it should work
02322                 if ( !exe->parameters().isEmpty() )
02323                 {
02324                     fileName = d->giveAbsolutePath( exe->parameters() );
02325                     mime = KMimeType::findByPath( fileName );
02326                     if ( KRun::isExecutableFile( fileName, mime->name() ) )
02327                     {
02328                         // this case is a link pointing to an executable with a parameter
02329                         // that also is an executable, possibly a hand-crafted pdf
02330                         KMessageBox::information( widget(), i18n("The document is trying to execute an external application and for your safety okular does not allow that.") );
02331                         return;
02332                     }
02333                 }
02334                 else
02335                 {
02336                     // this case is a link pointing to an executable with no parameters
02337                     // core developers find unacceptable executing it even after asking the user
02338                     KMessageBox::information( widget(), i18n("The document is trying to execute an external application and for your safety okular does not allow that.") );
02339                     return;
02340                 }
02341             }
02342 
02343             KService::Ptr ptr = KMimeTypeTrader::self()->preferredService( mime->name(), "Application" );
02344             if ( ptr )
02345             {
02346                 KUrl::List lst;
02347                 lst.append( fileName );
02348                 KRun::run( *ptr, lst, 0 );
02349             }
02350             else
02351                 KMessageBox::information( widget(), i18n( "No application found for opening file of mimetype %1.", mime->name() ) );
02352             } break;
02353 
02354         case Action::DocAction: {
02355             const DocumentAction * docaction = static_cast< const DocumentAction * >( action );
02356             switch( docaction->documentActionType() )
02357             {
02358                 case DocumentAction::PageFirst:
02359                     setViewportPage( 0 );
02360                     break;
02361                 case DocumentAction::PagePrev:
02362                     if ( (*d->m_viewportIterator).pageNumber > 0 )
02363                         setViewportPage( (*d->m_viewportIterator).pageNumber - 1 );
02364                     break;
02365                 case DocumentAction::PageNext:
02366                     if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 )
02367                         setViewportPage( (*d->m_viewportIterator).pageNumber + 1 );
02368                     break;
02369                 case DocumentAction::PageLast:
02370                     setViewportPage( d->m_pagesVector.count() - 1 );
02371                     break;
02372                 case DocumentAction::HistoryBack:
02373                     setPrevViewport();
02374                     break;
02375                 case DocumentAction::HistoryForward:
02376                     setNextViewport();
02377                     break;
02378                 case DocumentAction::Quit:
02379                     emit quit();
02380                     break;
02381                 case DocumentAction::Presentation:
02382                     emit linkPresentation();
02383                     break;
02384                 case DocumentAction::EndPresentation:
02385                     emit linkEndPresentation();
02386                     break;
02387                 case DocumentAction::Find:
02388                     emit linkFind();
02389                     break;
02390                 case DocumentAction::GoToPage:
02391                     emit linkGoToPage();
02392                     break;
02393                 case DocumentAction::Close:
02394                     emit close();
02395                     break;
02396             }
02397             } break;
02398 
02399         case Action::Browse: {
02400             const BrowseAction * browse = static_cast< const BrowseAction * >( action );
02401             // if the url is a mailto one, invoke mailer
02402             if ( browse->url().startsWith( "mailto:", Qt::CaseInsensitive ) )
02403                 KToolInvocation::invokeMailer( browse->url() );
02404             else
02405             {
02406                 QString url = browse->url();
02407 
02408                 // fix for #100366, documents with relative links that are the form of http:foo.pdf
02409                 if (url.indexOf("http:") == 0 && url.indexOf("http://") == -1 && url.right(4) == ".pdf")
02410                 {
02411                     d->openRelativeFile(url.mid(5));
02412                     return;
02413                 }
02414 
02415                 // Albert: this is not a leak!
02416                 new KRun( KUrl( url ), widget() );
02417             }
02418             } break;
02419 
02420         case Action::Sound: {
02421             const SoundAction * linksound = static_cast< const SoundAction * >( action );
02422             AudioPlayer::instance()->playSound( linksound->sound(), linksound );
02423             } break;
02424 
02425         case Action::Movie:
02426             //const MovieAction * movie = static_cast< const MovieAction * >( action );
02427             // TODO this (Movie action)
02428             break;
02429     }
02430 }
02431 
02432 void Document::processSourceReference( const SourceReference * ref )
02433 {
02434     if ( !ref )
02435         return;
02436 
02437     if ( !QFile::exists( ref->fileName() ) )
02438     {
02439         kDebug(OkularDebug).nospace() << "No such file: '" << ref->fileName() << "'";
02440         return;
02441     }
02442 
02443     static QHash< int, QString > editors;
02444     // init the editors table if empty (on first run, usually)
02445     if ( editors.isEmpty() )
02446     {
02447         editors[ Settings::EnumExternalEditor::Kate ] =
02448             QLatin1String( "kate --use --line %l --column %c" );
02449         editors[ Settings::EnumExternalEditor::Scite ] =
02450             QLatin1String( "scite %f \"-goto:%l,%c\"" );
02451     }
02452 
02453     QHash< int, QString >::const_iterator it = editors.constFind( Settings::externalEditor() );
02454     QString p;
02455     if ( it != editors.end() )
02456         p = *it;
02457     else
02458         p = Settings::externalEditorCommand();
02459     // custom editor not yet configured
02460     if ( p.isEmpty() )
02461         return;
02462 
02463     // replacing the placeholders
02464     p.replace( QLatin1String( "%l" ), QString::number( ref->row() ) );
02465     p.replace( QLatin1String( "%c" ), QString::number( ref->column() ) );
02466     if ( p.indexOf( QLatin1String( "%f" ) ) > -1 )
02467       p.replace( QLatin1String( "%f" ), ref->fileName() );
02468     else
02469       p.append( QLatin1String( " " ) + ref->fileName() );
02470 
02471     // paranoic checks
02472     if ( p.isEmpty() || p.trimmed() == ref->fileName() )
02473         return;
02474 
02475     QProcess::startDetached( p );
02476 }
02477 
02478 Document::PrintingType Document::printingSupport() const
02479 {
02480     if ( d->m_generator )
02481     {
02482 
02483         if ( d->m_generator->hasFeature( Generator::PrintNative ) )
02484         {
02485             return NativePrinting;
02486         }
02487 
02488 #ifndef Q_OS_WIN
02489         if ( d->m_generator->hasFeature( Generator::PrintPostscript ) )
02490         {
02491             return PostscriptPrinting;
02492         }
02493 #endif
02494 
02495     }
02496 
02497     return NoPrinting;
02498 }
02499 
02500 bool Document::supportsPrintToFile() const
02501 {
02502     return d->m_generator ? d->m_generator->hasFeature( Generator::PrintToFile ) : false;
02503 }
02504 
02505 bool Document::print( QPrinter &printer )
02506 {
02507     return d->m_generator ? d->m_generator->print( printer ) : false;
02508 }
02509 
02510 QWidget* Document::printConfigurationWidget() const
02511 {
02512     if ( d->m_generator )
02513     {
02514         PrintInterface * iface = qobject_cast< Okular::PrintInterface * >( d->m_generator );
02515         return iface ? iface->printConfigurationWidget() : 0;
02516     }
02517     else
02518         return 0;
02519 }
02520 
02521 void Document::fillConfigDialog( KConfigDialog * dialog )
02522 {
02523     if ( !dialog )
02524         return;
02525 
02526     // ensure that we have all the generators with settings loaded
02527     QString constraint( "([X-KDE-Priority] > 0) and (exist Library) and ([X-KDE-okularHasInternalSettings])" );
02528     KService::List offers = KServiceTypeTrader::self()->query( "okular/Generator", constraint );
02529     d->loadServiceList( offers );
02530 
02531     bool pagesAdded = false;
02532     QHash< QString, GeneratorInfo >::iterator it = d->m_loadedGenerators.begin();
02533     QHash< QString, GeneratorInfo >::iterator itEnd = d->m_loadedGenerators.end();
02534     for ( ; it != itEnd; ++it )
02535     {
02536         Okular::ConfigInterface * iface = d->generatorConfig( it.value() );
02537         if ( iface )
02538         {
02539             iface->addPages( dialog );
02540             pagesAdded = true;
02541             if ( !it.value().catalogName.isEmpty() )
02542                 KGlobal::locale()->insertCatalog( it.value().catalogName );
02543         }
02544     }
02545     if ( pagesAdded )
02546     {
02547         connect( dialog, SIGNAL( settingsChanged( const QString& ) ),
02548                  this, SLOT( slotGeneratorConfigChanged( const QString& ) ) );
02549     }
02550 }
02551 
02552 int Document::configurableGenerators() const
02553 {
02554     QString constraint( "([X-KDE-Priority] > 0) and (exist Library) and ([X-KDE-okularHasInternalSettings])" );
02555     KService::List offers = KServiceTypeTrader::self()->query( "okular/Generator", constraint );
02556     return offers.count();
02557 }
02558 
02559 QStringList Document::supportedMimeTypes() const
02560 {
02561     if ( !d->m_supportedMimeTypes.isEmpty() )
02562         return d->m_supportedMimeTypes;
02563 
02564     QString constraint( "(Library == 'okularpart')" );
02565     QLatin1String basePartService( "KParts/ReadOnlyPart" );
02566     KService::List offers = KServiceTypeTrader::self()->query( basePartService, constraint );
02567     KService::List::ConstIterator it = offers.begin(), itEnd = offers.end();
02568     for ( ; it != itEnd; ++it )
02569     {
02570         KService::Ptr service = *it;
02571         QStringList mimeTypes = service->serviceTypes();
02572         foreach ( const QString& mimeType, mimeTypes )
02573             if ( mimeType != basePartService )
02574                 d->m_supportedMimeTypes.append( mimeType );
02575     }
02576 
02577     return d->m_supportedMimeTypes;
02578 }
02579 
02580 const KComponentData* Document::componentData() const
02581 {
02582     if ( !d->m_generator )
02583         return 0;
02584 
02585     QHash< QString, GeneratorInfo >::const_iterator genIt = d->m_loadedGenerators.constFind( d->m_generatorName );
02586     Q_ASSERT( genIt != d->m_loadedGenerators.constEnd() );
02587     const KComponentData* kcd = &genIt.value().data;
02588 
02589     // empty about data
02590     if ( kcd->isValid() && kcd->aboutData() && kcd->aboutData()->programName().isEmpty() )
02591         return 0;
02592 
02593     return kcd;
02594 }
02595 
02596 void DocumentPrivate::requestDone( PixmapRequest * req )
02597 {
02598     if ( !req )
02599         return;
02600 
02601     if ( !m_generator || m_closingLoop )
02602     {
02603         m_pixmapRequestsMutex.lock();
02604         m_executingPixmapRequests.removeAll( req );
02605         m_pixmapRequestsMutex.unlock();
02606         delete req;
02607         if ( m_closingLoop )
02608             m_closingLoop->exit();
02609         return;
02610     }
02611 
02612 #ifndef NDEBUG
02613     if ( !m_generator->canGeneratePixmap() )
02614         kDebug(OkularDebug) << "requestDone with generator not in READY state.";
02615 #endif
02616 
02617     // [MEM] 1.1 find and remove a previous entry for the same page and id
02618     QLinkedList< AllocatedPixmap * >::iterator aIt = m_allocatedPixmapsFifo.begin();
02619     QLinkedList< AllocatedPixmap * >::iterator aEnd = m_allocatedPixmapsFifo.end();
02620     for ( ; aIt != aEnd; ++aIt )
02621         if ( (*aIt)->page == req->pageNumber() && (*aIt)->id == req->id() )
02622         {
02623             AllocatedPixmap * p = *aIt;
02624             m_allocatedPixmapsFifo.erase( aIt );
02625             m_allocatedPixmapsTotalMemory -= p->memory;
02626             delete p;
02627             break;
02628         }
02629 
02630     QMap< int, DocumentObserver * >::const_iterator itObserver = m_observers.constFind( req->id() );
02631     if ( itObserver != m_observers.constEnd() )
02632     {
02633         // [MEM] 1.2 append memory allocation descriptor to the FIFO
02634         qulonglong memoryBytes = 4 * req->width() * req->height();
02635         AllocatedPixmap * memoryPage = new AllocatedPixmap( req->id(), req->pageNumber(), memoryBytes );
02636         m_allocatedPixmapsFifo.append( memoryPage );
02637         m_allocatedPixmapsTotalMemory += memoryBytes;
02638 
02639         // 2. notify an observer that its pixmap changed
02640         itObserver.value()->notifyPageChanged( req->pageNumber(), DocumentObserver::Pixmap );
02641     }
02642 #ifndef NDEBUG
02643     else
02644         kWarning(OkularDebug) << "Receiving a done request for the defunct observer" << req->id();
02645 #endif
02646 
02647     // 3. delete request
02648     m_pixmapRequestsMutex.lock();
02649     m_executingPixmapRequests.removeAll( req );
02650     m_pixmapRequestsMutex.unlock();
02651     delete req;
02652 
02653     // 4. start a new generation if some is pending
02654     m_pixmapRequestsMutex.lock();
02655     bool hasPixmaps = !m_pixmapRequestsStack.isEmpty();
02656     m_pixmapRequestsMutex.unlock();
02657     if ( hasPixmaps )
02658         sendGeneratorRequest();
02659 }
02660 
02661 void Document::setRotation( int r )
02662 {
02663     d->setRotationInternal( r, true );
02664 }
02665 
02666 void DocumentPrivate::setRotationInternal( int r, bool notify )
02667 {
02668     Rotation rotation = (Rotation)r;
02669     if ( !m_generator || ( m_rotation == rotation ) )
02670     return;
02671 
02672     // tell the pages to rotate
02673     QVector< Okular::Page * >::const_iterator pIt = m_pagesVector.begin();
02674     QVector< Okular::Page * >::const_iterator pEnd = m_pagesVector.end();
02675     for ( ; pIt != pEnd; ++pIt )
02676         (*pIt)->d->rotateAt( rotation );
02677     if ( notify )
02678     {
02679         // notify the generator that the current rotation has changed
02680         m_generator->rotationChanged( rotation, m_rotation );
02681     }
02682     // set the new rotation
02683     m_rotation = rotation;
02684 
02685     if ( notify )
02686     {
02687         foreachObserverD( notifySetup( m_pagesVector, DocumentObserver::NewLayoutForPages ) );
02688         foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap | DocumentObserver::Highlights | DocumentObserver::Annotations ) );
02689     }
02690     kDebug(OkularDebug) << "Rotated:" << r;
02691 }
02692 
02693 void Document::setPageSize( const PageSize &size )
02694 {
02695     if ( !d->m_generator || !d->m_generator->hasFeature( Generator::PageSizes ) )
02696         return;
02697 
02698     if ( d->m_pageSizes.isEmpty() )
02699         d->m_pageSizes = d->m_generator->pageSizes();
02700     int sizeid = d->m_pageSizes.indexOf( size );
02701     if ( sizeid == -1 )
02702         return;
02703 
02704     // tell the pages to change size
02705     QVector< Okular::Page * >::const_iterator pIt = d->m_pagesVector.begin();
02706     QVector< Okular::Page * >::const_iterator pEnd = d->m_pagesVector.end();
02707     for ( ; pIt != pEnd; ++pIt )
02708         (*pIt)->d->changeSize( size );
02709     // clear 'memory allocation' descriptors
02710     QLinkedList< AllocatedPixmap * >::const_iterator aIt = d->m_allocatedPixmapsFifo.begin();
02711     QLinkedList< AllocatedPixmap * >::const_iterator aEnd = d->m_allocatedPixmapsFifo.end();
02712     for ( ; aIt != aEnd; ++aIt )
02713         delete *aIt;
02714     d->m_allocatedPixmapsFifo.clear();
02715     d->m_allocatedPixmapsTotalMemory = 0;
02716     // notify the generator that the current page size has changed
02717     d->m_generator->pageSizeChanged( size, d->m_pageSize );
02718     // set the new page size
02719     d->m_pageSize = size;
02720 
02721     foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::NewLayoutForPages ) );
02722     foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap | DocumentObserver::Highlights ) );
02723     kDebug(OkularDebug) << "New PageSize id:" << sizeid;
02724 }
02725 
02726 
02729 DocumentViewport::DocumentViewport( int n )
02730     : pageNumber( n )
02731 {
02732     // default settings
02733     rePos.enabled = false;
02734     rePos.normalizedX = 0.5;
02735     rePos.normalizedY = 0.0;
02736     rePos.pos = Center;
02737     autoFit.enabled = false;
02738     autoFit.width = false;
02739     autoFit.height = false;
02740 }
02741 
02742 DocumentViewport::DocumentViewport( const QString & xmlDesc )
02743     : pageNumber( -1 )
02744 {
02745     // default settings (maybe overridden below)
02746     rePos.enabled = false;
02747     rePos.normalizedX = 0.5;
02748     rePos.normalizedY = 0.0;
02749     rePos.pos = Center;
02750     autoFit.enabled = false;
02751     autoFit.width = false;
02752     autoFit.height = false;
02753 
02754     // check for string presence
02755     if ( xmlDesc.isEmpty() )
02756         return;
02757 
02758     // decode the string
02759     bool ok;
02760     int field = 0;
02761     QString token = xmlDesc.section( ';', field, field );
02762     while ( !token.isEmpty() )
02763     {
02764         // decode the current token
02765         if ( field == 0 )
02766         {
02767             pageNumber = token.toInt( &ok );
02768             if ( !ok )
02769                 return;
02770         }
02771         else if ( token.startsWith( "C1" ) )
02772         {
02773             rePos.enabled = true;
02774             rePos.normalizedX = token.section( ':', 1, 1 ).toDouble();
02775             rePos.normalizedY = token.section( ':', 2, 2 ).toDouble();
02776             rePos.pos = Center;
02777         }
02778         else if ( token.startsWith( "C2" ) )
02779         {
02780             rePos.enabled = true;
02781             rePos.normalizedX = token.section( ':', 1, 1 ).toDouble();
02782             rePos.normalizedY = token.section( ':', 2, 2 ).toDouble();
02783             if (token.section( ':', 3, 3 ).toInt() == 1) rePos.pos = Center;
02784             else rePos.pos = TopLeft;
02785         }
02786         else if ( token.startsWith( "AF1" ) )
02787         {
02788             autoFit.enabled = true;
02789             autoFit.width = token.section( ':', 1, 1 ) == "T";
02790             autoFit.height = token.section( ':', 2, 2 ) == "T";
02791         }
02792         // proceed tokenizing string
02793         field++;
02794         token = xmlDesc.section( ';', field, field );
02795     }
02796 }
02797 
02798 QString DocumentViewport::toString() const
02799 {
02800     // start string with page number
02801     QString s = QString::number( pageNumber );
02802     // if has center coordinates, save them on string
02803     if ( rePos.enabled )
02804         s += QString( ";C2:" ) + QString::number( rePos.normalizedX ) +
02805              ':' + QString::number( rePos.normalizedY ) +
02806              ':' + QString::number( rePos.pos );
02807     // if has autofit enabled, save its state on string
02808     if ( autoFit.enabled )
02809         s += QString( ";AF1:" ) + (autoFit.width ? "T" : "F") +
02810              ':' + (autoFit.height ? "T" : "F");
02811     return s;
02812 }
02813 
02814 bool DocumentViewport::isValid() const
02815 {
02816     return pageNumber >= 0;
02817 }
02818 
02819 bool DocumentViewport::operator==( const DocumentViewport & vp ) const
02820 {
02821     bool equal = ( pageNumber == vp.pageNumber ) &&
02822                  ( rePos.enabled == vp.rePos.enabled ) &&
02823                  ( autoFit.enabled == vp.autoFit.enabled );
02824     if ( !equal )
02825         return false;
02826     if ( rePos.enabled &&
02827          (( rePos.normalizedX != vp.rePos.normalizedX) ||
02828          ( rePos.normalizedY != vp.rePos.normalizedY ) || rePos.pos != vp.rePos.pos) )
02829         return false;
02830     if ( autoFit.enabled &&
02831          (( autoFit.width != vp.autoFit.width ) ||
02832          ( autoFit.height != vp.autoFit.height )) )
02833         return false;
02834     return true;
02835 }
02836 
02837 
02840 DocumentInfo::DocumentInfo()
02841   : QDomDocument( "DocumentInformation" )
02842 {
02843     QDomElement docElement = createElement( "DocumentInfo" );
02844     appendChild( docElement );
02845 }
02846 
02847 void DocumentInfo::set( const QString &key, const QString &value,
02848                         const QString &title )
02849 {
02850     QDomElement docElement = documentElement();
02851     QDomElement element;
02852 
02853     // check whether key already exists
02854     QDomNodeList list = docElement.elementsByTagName( key );
02855     if ( list.count() > 0 )
02856         element = list.item( 0 ).toElement();
02857     else
02858         element = createElement( key );
02859 
02860     element.setAttribute( "value", value );
02861     element.setAttribute( "title", title );
02862 
02863     if ( list.count() == 0 )
02864         docElement.appendChild( element );
02865 }
02866 
02867 void DocumentInfo::set( enum Key key, const QString &value )
02868 {
02869     switch ( key ) {
02870         case Title:
02871             set( "title", value, i18n( "Title" ) );
02872             break;
02873         case Subject:
02874             set( "subject", value, i18n( "Subject" ) );
02875             break;
02876         case Description:
02877             set( "description", value, i18n( "Description" ) );
02878             break;
02879         case Author:
02880             set( "author", value, i18n( "Author" ) );
02881             break;
02882         case Creator:
02883             set( "creator", value, i18n( "Creator" ) );
02884             break;
02885         case Producer:
02886             set( "producer", value, i18n( "Producer" ) );
02887             break;
02888         case Copyright:
02889             set( "copyright", value, i18n( "Copyright" ) );
02890             break;
02891         case Pages:
02892             set( "pages", value, i18n( "Pages" ) );
02893             break;
02894         case CreationDate:
02895             set( "creationDate", value, i18n( "Created" ) );
02896             break;
02897         case ModificationDate:
02898             set( "modificationDate", value, i18n( "Modified" ) );
02899             break;
02900         case MimeType:
02901             set( "mimeType", value, i18n( "Mime Type" ) );
02902             break;
02903         case Category:
02904             set( "category", value, i18n( "Category" ) );
02905             break;
02906         case Keywords:
02907             set( "keywords", value, i18n( "Keywords" ) );
02908             break;
02909         default:
02910             kWarning(OkularDebug) << "Invalid key passed";
02911             break;
02912     }
02913 }
02914 
02915 QString DocumentInfo::get( const QString &key ) const
02916 {
02917     QDomElement docElement = documentElement();
02918     QDomElement element;
02919 
02920     // check whether key already exists
02921     QDomNodeList list = docElement.elementsByTagName( key );
02922     if ( list.count() > 0 )
02923         return list.item( 0 ).toElement().attribute( "value" );
02924     else
02925         return QString();
02926 }
02927 
02928 
02931 DocumentSynopsis::DocumentSynopsis()
02932   : QDomDocument( "DocumentSynopsis" )
02933 {
02934     // void implementation, only subclassed for naming
02935 }
02936 
02937 DocumentSynopsis::DocumentSynopsis( const QDomDocument &document )
02938   : QDomDocument( document )
02939 {
02940 }
02941 
02944 EmbeddedFile::EmbeddedFile()
02945 {
02946 }
02947 
02948 EmbeddedFile::~EmbeddedFile()
02949 {
02950 }
02951 
02952 VisiblePageRect::VisiblePageRect( int page, const NormalizedRect &rectangle )
02953     : pageNumber( page ), rect( rectangle )
02954 {
02955 }
02956 
02957 #include "document.moc"

okular

Skip menu "okular"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdegraphics

Skip menu "kdegraphics"
  • okular
Generated for kdegraphics by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal