diff --git a/core/document.cpp b/core/document.cpp index d458a4c2c..c41d047e1 100644 --- a/core/document.cpp +++ b/core/document.cpp @@ -1,4851 +1,4921 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2004-2008 by Albert Astals Cid * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "document.h" #include "document_p.h" #include "documentcommands_p.h" #include #ifdef Q_OS_WIN #define _WIN32_WINNT 0x0500 #include #elif defined(Q_OS_FREEBSD) #include #include #include #endif // qt/kde/system includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // local includes #include "action.h" #include "annotations.h" #include "annotations_p.h" #include "audioplayer.h" #include "audioplayer_p.h" #include "bookmarkmanager.h" #include "chooseenginedialog_p.h" #include "debug_p.h" #include "generator_p.h" #include "interfaces/configinterface.h" #include "interfaces/guiinterface.h" #include "interfaces/printinterface.h" #include "interfaces/saveinterface.h" #include "observer.h" #include "misc.h" #include "page.h" #include "page_p.h" #include "pagecontroller_p.h" #include "scripter.h" #include "settings_core.h" #include "sourcereference.h" #include "sourcereference_p.h" #include "texteditors_p.h" #include "tile.h" #include "tilesmanager_p.h" #include "utils_p.h" #include "view.h" #include "view_p.h" #include "form.h" #include "utils.h" #include #include using namespace Okular; struct AllocatedPixmap { // owner of the page DocumentObserver *observer; int page; qulonglong memory; // public constructor: initialize data AllocatedPixmap( DocumentObserver *o, int p, qulonglong m ) : observer( o ), page( p ), memory( m ) {} }; struct ArchiveData { ArchiveData() { } QString originalFileName; KTemporaryFile document; KTemporaryFile metadataFile; }; struct RunningSearch { // store search properties int continueOnPage; RegularAreaRect continueOnMatch; QSet< int > highlightedPages; // fields related to previous searches (used for 'continueSearch') QString cachedString; Document::SearchType cachedType; Qt::CaseSensitivity cachedCaseSensitivity; bool cachedViewportMove : 1; bool isCurrentlySearching : 1; QColor cachedColor; int pagesDone; }; #define foreachObserver( cmd ) {\ QSet< DocumentObserver * >::const_iterator it=d->m_observers.constBegin(), end=d->m_observers.constEnd();\ for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } #define foreachObserverD( cmd ) {\ QSet< DocumentObserver * >::const_iterator it = m_observers.constBegin(), end = m_observers.constEnd();\ for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } #define OKULAR_HISTORY_MAXSTEPS 100 #define OKULAR_HISTORY_SAVEDSTEPS 10 /***** Document ******/ QString DocumentPrivate::pagesSizeString() const { if (m_generator) { if (m_generator->pagesSizeMetric() != Generator::None) { QSizeF size = m_parent->allPagesSize(); if (size.isValid()) return localizedSize(size); else return QString(); } else return QString(); } else return QString(); } QString DocumentPrivate::namePaperSize(double inchesWidth, double inchesHeight) const { // Account for small deviations in paper sizes static const double marginFactor = 0.03; static const double lowerBoundFactor = 1.0 - marginFactor; static const double upperBoundFactor = 1.0 + marginFactor; const QPrinter::Orientation orientation = inchesWidth > inchesHeight ? QPrinter::Landscape : QPrinter::Portrait; // enforce portrait mode for further tests if (inchesWidth > inchesHeight) qSwap(inchesWidth, inchesHeight); // Use QPrinter to find which of the predefined paper sizes // matches best the given paper width and height QPrinter dummyPrinter; QPrinter::PaperSize paperSize = QPrinter::Custom; for (int i = 0; i < (int)QPrinter::NPaperSize; ++i) { const QPrinter::PaperSize ps = (QPrinter::PaperSize)i; dummyPrinter.setPaperSize(ps); const QSizeF definedPaperSize = dummyPrinter.paperSize(QPrinter::Inch); if (inchesWidth > definedPaperSize.width() * lowerBoundFactor && inchesWidth < definedPaperSize.width() * upperBoundFactor && inchesHeight > definedPaperSize.height() * lowerBoundFactor && inchesHeight < definedPaperSize.height() * upperBoundFactor) { paperSize = ps; break; } } // Handle all paper sizes defined in QPrinter, // return string depending if paper's orientation is landscape or portrait switch (paperSize) { case QPrinter::A0: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A0") : i18nc("paper size", "portrait DIN/ISO A0"); case QPrinter::A1: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A1") : i18nc("paper size", "portrait DIN/ISO A1"); case QPrinter::A2: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A2") : i18nc("paper size", "portrait DIN/ISO A2"); case QPrinter::A3: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A3") : i18nc("paper size", "portrait DIN/ISO A3"); case QPrinter::A4: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A4") : i18nc("paper size", "portrait DIN/ISO A4"); case QPrinter::A5: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A5") : i18nc("paper size", "portrait DIN/ISO A5"); case QPrinter::A6: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A6") : i18nc("paper size", "portrait DIN/ISO A6"); case QPrinter::A7: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A7") : i18nc("paper size", "portrait DIN/ISO A7"); case QPrinter::A8: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A8") : i18nc("paper size", "portrait DIN/ISO A8"); case QPrinter::A9: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO A9") : i18nc("paper size", "portrait DIN/ISO A9"); case QPrinter::B0: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B0") : i18nc("paper size", "portrait DIN/ISO B0"); case QPrinter::B1: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B1") : i18nc("paper size", "portrait DIN/ISO B1"); case QPrinter::B2: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B2") : i18nc("paper size", "portrait DIN/ISO B2"); case QPrinter::B3: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B3") : i18nc("paper size", "portrait DIN/ISO B3"); case QPrinter::B4: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B4") : i18nc("paper size", "portrait DIN/ISO B4"); case QPrinter::B5: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B5") : i18nc("paper size", "portrait DIN/ISO B5"); case QPrinter::B6: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B6") : i18nc("paper size", "portrait DIN/ISO B6"); case QPrinter::B7: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B7") : i18nc("paper size", "portrait DIN/ISO B7"); case QPrinter::B8: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B8") : i18nc("paper size", "portrait DIN/ISO B8"); case QPrinter::B9: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B9") : i18nc("paper size", "portrait DIN/ISO B9"); case QPrinter::B10: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DIN/ISO B10") : i18nc("paper size", "portrait DIN/ISO B10"); case QPrinter::Letter: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape letter") : i18nc("paper size", "portrait letter"); case QPrinter::Legal: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape legal") : i18nc("paper size", "portrait legal"); case QPrinter::Executive: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape executive") : i18nc("paper size", "portrait executive"); case QPrinter::C5E: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape C5E") : i18nc("paper size", "portrait C5E"); case QPrinter::Comm10E: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape Comm10E") : i18nc("paper size", "portrait Comm10E"); case QPrinter::DLE: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape DLE") : i18nc("paper size", "portrait DLE"); case QPrinter::Folio: return orientation == QPrinter::Landscape ? i18nc("paper size", "landscape folio") : i18nc("paper size", "portrait folio"); case QPrinter::Tabloid: case QPrinter::Ledger: /// Ledger and Tabloid are the same, just rotated by 90 degrees return orientation == QPrinter::Landscape ? i18nc("paper size", "ledger") : i18nc("paper size", "tabloid"); case QPrinter::Custom: return orientation == QPrinter::Landscape ? i18nc("paper size", "unknown landscape paper size") : i18nc("paper size", "unknown portrait paper size"); } kWarning() << "PaperSize" << paperSize << "has not been covered"; return QString(); } QString DocumentPrivate::localizedSize(const QSizeF &size) const { double inchesWidth = 0, inchesHeight = 0; switch (m_generator->pagesSizeMetric()) { case Generator::Points: inchesWidth = size.width() / 72.0; inchesHeight = size.height() / 72.0; break; case Generator::Pixels: { const QSizeF dpi = m_generator->dpi(); inchesWidth = size.width() / dpi.width(); inchesHeight = size.height() / dpi.height(); } break; case Generator::None: break; } if (KGlobal::locale()->measureSystem() == KLocale::Imperial) { return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 x %2 in (%3)", inchesWidth, inchesHeight, namePaperSize(inchesWidth, inchesHeight)); } else { return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 x %2 mm (%3)", QString::number(inchesWidth * 25.4, 'd', 0), QString::number(inchesHeight * 25.4, 'd', 0), namePaperSize(inchesWidth, inchesHeight)); } } qulonglong DocumentPrivate::calculateMemoryToFree() { // [MEM] choose memory parameters based on configuration profile qulonglong clipValue = 0; qulonglong memoryToFree = 0; switch ( SettingsCore::memoryLevel() ) { case SettingsCore::EnumMemoryLevel::Low: memoryToFree = m_allocatedPixmapsTotalMemory; break; case SettingsCore::EnumMemoryLevel::Normal: { qulonglong thirdTotalMemory = getTotalMemory() / 3; qulonglong freeMemory = getFreeMemory(); if (m_allocatedPixmapsTotalMemory > thirdTotalMemory) memoryToFree = m_allocatedPixmapsTotalMemory - thirdTotalMemory; if (m_allocatedPixmapsTotalMemory > freeMemory) clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2; } break; case SettingsCore::EnumMemoryLevel::Aggressive: { qulonglong freeMemory = getFreeMemory(); if (m_allocatedPixmapsTotalMemory > freeMemory) clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2; } break; case SettingsCore::EnumMemoryLevel::Greedy: { qulonglong freeSwap; qulonglong freeMemory = getFreeMemory( &freeSwap ); const qulonglong memoryLimit = qMin( qMax( freeMemory, getTotalMemory()/2 ), freeMemory+freeSwap ); if (m_allocatedPixmapsTotalMemory > memoryLimit) clipValue = (m_allocatedPixmapsTotalMemory - memoryLimit) / 2; } break; } if ( clipValue > memoryToFree ) memoryToFree = clipValue; return memoryToFree; } void DocumentPrivate::cleanupPixmapMemory() { cleanupPixmapMemory( calculateMemoryToFree() ); } void DocumentPrivate::cleanupPixmapMemory( qulonglong memoryToFree ) { if ( memoryToFree < 1 ) return; const int currentViewportPage = (*m_viewportIterator).pageNumber; // Create a QMap of visible rects, indexed by page number QMap< int, VisiblePageRect * > visibleRects; QVector< Okular::VisiblePageRect * >::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) visibleRects.insert( (*vIt)->pageNumber, (*vIt) ); // Free memory starting from pages that are farthest from the current one int pagesFreed = 0; while ( memoryToFree > 0 ) { AllocatedPixmap * p = searchLowestPriorityPixmap( true, true ); if ( !p ) // No pixmap to remove break; kDebug().nospace() << "Evicting cache pixmap observer=" << p->observer << " page=" << p->page; // m_allocatedPixmapsTotalMemory can't underflow because we always add or remove // the memory used by the AllocatedPixmap so at most it can reach zero m_allocatedPixmapsTotalMemory -= p->memory; // Make sure memoryToFree does not underflow if ( p->memory > memoryToFree ) memoryToFree = 0; else memoryToFree -= p->memory; pagesFreed++; // delete pixmap m_pagesVector.at( p->page )->deletePixmap( p->observer ); // delete allocation descriptor delete p; } // If we're still on low memory, try to free individual tiles // Store pages that weren't completely removed QLinkedList< AllocatedPixmap * > pixmapsToKeep; while (memoryToFree > 0) { int clean_hits = 0; foreach (DocumentObserver *observer, m_observers) { AllocatedPixmap * p = searchLowestPriorityPixmap( false, true, observer ); if ( !p ) // No pixmap to remove continue; clean_hits++; TilesManager *tilesManager = m_pagesVector.at( p->page )->d->tilesManager( observer ); if ( tilesManager && tilesManager->totalMemory() > 0 ) { qulonglong memoryDiff = p->memory; NormalizedRect visibleRect; if ( visibleRects.contains( p->page ) ) visibleRect = visibleRects[ p->page ]->rect; // Free non visible tiles tilesManager->cleanupPixmapMemory( memoryToFree, visibleRect, currentViewportPage ); p->memory = tilesManager->totalMemory(); memoryDiff -= p->memory; memoryToFree = (memoryDiff < memoryToFree) ? (memoryToFree - memoryDiff) : 0; m_allocatedPixmapsTotalMemory -= memoryDiff; if ( p->memory > 0 ) pixmapsToKeep.append( p ); else delete p; } else pixmapsToKeep.append( p ); } if (clean_hits == 0) break; } m_allocatedPixmaps += pixmapsToKeep; //p--rintf("freeMemory A:[%d -%d = %d] \n", m_allocatedPixmaps.count() + pagesFreed, pagesFreed, m_allocatedPixmaps.count() ); } /* Returns the next pixmap to evict from cache, or NULL if no suitable pixmap * if found. If unloadableOnly is set, only unloadable pixmaps are returned. If * thenRemoveIt is set, the pixmap is removed from m_allocatedPixmaps before * returning it */ AllocatedPixmap * DocumentPrivate::searchLowestPriorityPixmap( bool unloadableOnly, bool thenRemoveIt, DocumentObserver *observer ) { QLinkedList< AllocatedPixmap * >::iterator pIt = m_allocatedPixmaps.begin(); QLinkedList< AllocatedPixmap * >::iterator pEnd = m_allocatedPixmaps.end(); QLinkedList< AllocatedPixmap * >::iterator farthestPixmap = pEnd; const int currentViewportPage = (*m_viewportIterator).pageNumber; /* Find the pixmap that is farthest from the current viewport */ int maxDistance = -1; while ( pIt != pEnd ) { const AllocatedPixmap * p = *pIt; // Filter by observer if ( observer == 0 || p->observer == observer ) { const int distance = qAbs( p->page - currentViewportPage ); if ( maxDistance < distance && ( !unloadableOnly || p->observer->canUnloadPixmap( p->page ) ) ) { maxDistance = distance; farthestPixmap = pIt; } } ++pIt; } /* No pixmap to remove */ if ( farthestPixmap == pEnd ) return 0; AllocatedPixmap * selectedPixmap = *farthestPixmap; if ( thenRemoveIt ) m_allocatedPixmaps.erase( farthestPixmap ); return selectedPixmap; } qulonglong DocumentPrivate::getTotalMemory() { static qulonglong cachedValue = 0; if ( cachedValue ) return cachedValue; #if defined(Q_OS_LINUX) // if /proc/meminfo doesn't exist, return 128MB QFile memFile( "/proc/meminfo" ); if ( !memFile.open( QIODevice::ReadOnly ) ) return (cachedValue = 134217728); QTextStream readStream( &memFile ); while ( true ) { QString entry = readStream.readLine(); if ( entry.isNull() ) break; if ( entry.startsWith( "MemTotal:" ) ) return (cachedValue = (Q_UINT64_C(1024) * entry.section( ' ', -2, -2 ).toULongLong())); } #elif defined(Q_OS_FREEBSD) qulonglong physmem; int mib[] = {CTL_HW, HW_PHYSMEM}; size_t len = sizeof( physmem ); if ( sysctl( mib, 2, &physmem, &len, NULL, 0 ) == 0 ) return (cachedValue = physmem); #elif defined(Q_OS_WIN) MEMORYSTATUSEX stat; stat.dwLength = sizeof(stat); GlobalMemoryStatusEx (&stat); return ( cachedValue = stat.ullTotalPhys ); #endif return (cachedValue = 134217728); } qulonglong DocumentPrivate::getFreeMemory( qulonglong *freeSwap ) { static QTime lastUpdate = QTime::currentTime().addSecs(-3); static qulonglong cachedValue = 0; static qulonglong cachedFreeSwap = 0; if ( qAbs( lastUpdate.secsTo( QTime::currentTime() ) ) <= 2 ) { if (freeSwap) *freeSwap = cachedFreeSwap; return cachedValue; } /* Initialize the returned free swap value to 0. It is overwritten if the * actual value is available */ if (freeSwap) *freeSwap = 0; #if defined(Q_OS_LINUX) // if /proc/meminfo doesn't exist, return MEMORY FULL QFile memFile( "/proc/meminfo" ); if ( !memFile.open( QIODevice::ReadOnly ) ) return 0; // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers' // and 'Cached' fields. consider swapped memory as used memory. qulonglong memoryFree = 0; QString entry; QTextStream readStream( &memFile ); static const int nElems = 5; QString names[nElems] = { "MemFree:", "Buffers:", "Cached:", "SwapFree:", "SwapTotal:" }; qulonglong values[nElems] = { 0, 0, 0, 0, 0 }; bool foundValues[nElems] = { false, false, false, false, false }; while ( true ) { entry = readStream.readLine(); if ( entry.isNull() ) break; for ( int i = 0; i < nElems; ++i ) { if ( entry.startsWith( names[i] ) ) { values[i] = entry.section( ' ', -2, -2 ).toULongLong( &foundValues[i] ); } } } memFile.close(); bool found = true; for ( int i = 0; found && i < nElems; ++i ) found = found && foundValues[i]; if ( found ) { /* MemFree + Buffers + Cached - SwapUsed = * = MemFree + Buffers + Cached - (SwapTotal - SwapFree) = * = MemFree + Buffers + Cached + SwapFree - SwapTotal */ memoryFree = values[0] + values[1] + values[2] + values[3]; if ( values[4] > memoryFree ) memoryFree = 0; else memoryFree -= values[4]; } else { return 0; } lastUpdate = QTime::currentTime(); if (freeSwap) *freeSwap = ( cachedFreeSwap = (Q_UINT64_C(1024) * values[3]) ); return ( cachedValue = (Q_UINT64_C(1024) * memoryFree) ); #elif defined(Q_OS_FREEBSD) qulonglong cache, inact, free, psize; size_t cachelen, inactlen, freelen, psizelen; cachelen = sizeof( cache ); inactlen = sizeof( inact ); freelen = sizeof( free ); psizelen = sizeof( psize ); // sum up inactive, cached and free memory if ( sysctlbyname( "vm.stats.vm.v_cache_count", &cache, &cachelen, NULL, 0 ) == 0 && sysctlbyname( "vm.stats.vm.v_inactive_count", &inact, &inactlen, NULL, 0 ) == 0 && sysctlbyname( "vm.stats.vm.v_free_count", &free, &freelen, NULL, 0 ) == 0 && sysctlbyname( "vm.stats.vm.v_page_size", &psize, &psizelen, NULL, 0 ) == 0 ) { lastUpdate = QTime::currentTime(); return (cachedValue = (cache + inact + free) * psize); } else { return 0; } #elif defined(Q_OS_WIN) MEMORYSTATUSEX stat; stat.dwLength = sizeof(stat); GlobalMemoryStatusEx (&stat); lastUpdate = QTime::currentTime(); if (freeSwap) *freeSwap = ( cachedFreeSwap = stat.ullAvailPageFile ); return ( cachedValue = stat.ullAvailPhys ); #else // tell the memory is full.. will act as in LOW profile return 0; #endif } void DocumentPrivate::loadDocumentInfo() // note: load data and stores it internally (document or pages). observers // are still uninitialized at this point so don't access them { //kDebug(OkularDebug).nospace() << "Using '" << d->m_xmlFileName << "' as document info file."; if ( m_xmlFileName.isEmpty() ) return; QFile infoFile( m_xmlFileName ); loadDocumentInfo( infoFile ); } void DocumentPrivate::loadDocumentInfo( QFile &infoFile ) { if ( !infoFile.exists() || !infoFile.open( QIODevice::ReadOnly ) ) return; // Load DOM from XML file QDomDocument doc( "documentInfo" ); if ( !doc.setContent( &infoFile ) ) { kDebug(OkularDebug) << "Can't load XML pair! Check for broken xml."; infoFile.close(); return; } infoFile.close(); QDomElement root = doc.documentElement(); if ( root.tagName() != "documentInfo" ) return; KUrl documentUrl( root.attribute( "url" ) ); // Parse the DOM tree QDomNode topLevelNode = root.firstChild(); while ( topLevelNode.isElement() ) { QString catName = topLevelNode.toElement().tagName(); // Restore page attributes (bookmark, annotations, ...) from the DOM if ( catName == "pageList" ) { QDomNode pageNode = topLevelNode.firstChild(); while ( pageNode.isElement() ) { QDomElement pageElement = pageNode.toElement(); if ( pageElement.hasAttribute( "number" ) ) { // get page number (node's attribute) bool ok; int pageNumber = pageElement.attribute( "number" ).toInt( &ok ); // pass the domElement to the right page, to read config data from if ( ok && pageNumber >= 0 && pageNumber < (int)m_pagesVector.count() ) m_pagesVector[ pageNumber ]->d->restoreLocalContents( pageElement ); } pageNode = pageNode.nextSibling(); } } // Restore 'general info' from the DOM else if ( catName == "generalInfo" ) { QDomNode infoNode = topLevelNode.firstChild(); while ( infoNode.isElement() ) { QDomElement infoElement = infoNode.toElement(); // restore viewports history if ( infoElement.tagName() == "history" ) { // clear history m_viewportHistory.clear(); // append old viewports QDomNode historyNode = infoNode.firstChild(); while ( historyNode.isElement() ) { QDomElement historyElement = historyNode.toElement(); if ( historyElement.hasAttribute( "viewport" ) ) { QString vpString = historyElement.attribute( "viewport" ); m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), DocumentViewport( vpString ) ); } historyNode = historyNode.nextSibling(); } // consistancy check if ( m_viewportHistory.isEmpty() ) m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), DocumentViewport() ); } else if ( infoElement.tagName() == "rotation" ) { QString str = infoElement.text(); bool ok = true; int newrotation = !str.isEmpty() ? ( str.toInt( &ok ) % 4 ) : 0; if ( ok && newrotation != 0 ) { setRotationInternal( newrotation, false ); } } else if ( infoElement.tagName() == "views" ) { QDomNode viewNode = infoNode.firstChild(); while ( viewNode.isElement() ) { QDomElement viewElement = viewNode.toElement(); if ( viewElement.tagName() == "view" ) { const QString viewName = viewElement.attribute( "name" ); Q_FOREACH ( View * view, m_views ) { if ( view->name() == viewName ) { loadViewsInfo( view, viewElement ); break; } } } viewNode = viewNode.nextSibling(); } } infoNode = infoNode.nextSibling(); } } topLevelNode = topLevelNode.nextSibling(); } // } void DocumentPrivate::loadViewsInfo( View *view, const QDomElement &e ) { QDomNode viewNode = e.firstChild(); while ( viewNode.isElement() ) { QDomElement viewElement = viewNode.toElement(); if ( viewElement.tagName() == "zoom" ) { const QString valueString = viewElement.attribute( "value" ); bool newzoom_ok = true; const double newzoom = !valueString.isEmpty() ? valueString.toDouble( &newzoom_ok ) : 1.0; if ( newzoom_ok && newzoom != 0 && view->supportsCapability( View::Zoom ) && ( view->capabilityFlags( View::Zoom ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) { view->setCapability( View::Zoom, newzoom ); } const QString modeString = viewElement.attribute( "mode" ); bool newmode_ok = true; const int newmode = !modeString.isEmpty() ? modeString.toInt( &newmode_ok ) : 2; if ( newmode_ok && view->supportsCapability( View::ZoomModality ) && ( view->capabilityFlags( View::ZoomModality ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) { view->setCapability( View::ZoomModality, newmode ); } } viewNode = viewNode.nextSibling(); } } void DocumentPrivate::saveViewsInfo( View *view, QDomElement &e ) const { if ( view->supportsCapability( View::Zoom ) && ( view->capabilityFlags( View::Zoom ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) && view->supportsCapability( View::ZoomModality ) && ( view->capabilityFlags( View::ZoomModality ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) { QDomElement zoomEl = e.ownerDocument().createElement( "zoom" ); e.appendChild( zoomEl ); bool ok = true; const double zoom = view->capability( View::Zoom ).toDouble( &ok ); if ( ok && zoom != 0 ) { zoomEl.setAttribute( "value", QString::number(zoom) ); } const int mode = view->capability( View::ZoomModality ).toInt( &ok ); if ( ok ) { zoomEl.setAttribute( "mode", mode ); } } } QString DocumentPrivate::giveAbsolutePath( const QString & fileName ) const { if ( !QDir::isRelativePath( fileName ) ) return fileName; if ( !m_url.isValid() ) return QString(); return m_url.upUrl().url() + fileName; } bool DocumentPrivate::openRelativeFile( const QString & fileName ) { QString absFileName = giveAbsolutePath( fileName ); if ( absFileName.isEmpty() ) return false; kDebug(OkularDebug).nospace() << "openDocument: '" << absFileName << "'"; emit m_parent->openUrl( absFileName ); return true; } Generator * DocumentPrivate::loadGeneratorLibrary( const KService::Ptr &service ) { KPluginFactory *factory = KPluginLoader( service->library() ).factory(); if ( !factory ) { kWarning(OkularDebug).nospace() << "Invalid plugin factory for " << service->library() << "!"; return 0; } Generator * generator = factory->create< Okular::Generator >( service->pluginKeyword(), 0 ); GeneratorInfo info( factory->componentData() ); info.generator = generator; if ( info.data.isValid() && info.data.aboutData() ) info.catalogName = info.data.aboutData()->catalogName(); m_loadedGenerators.insert( service->name(), info ); return generator; } void DocumentPrivate::loadAllGeneratorLibraries() { if ( m_generatorsLoaded ) return; m_generatorsLoaded = true; QString constraint("([X-KDE-Priority] > 0) and (exist Library)") ; KService::List offers = KServiceTypeTrader::self()->query( "okular/Generator", constraint ); loadServiceList( offers ); } void DocumentPrivate::loadServiceList( const KService::List& offers ) { int count = offers.count(); if ( count <= 0 ) return; for ( int i = 0; i < count; ++i ) { QString propName = offers.at(i)->name(); // don't load already loaded generators QHash< QString, GeneratorInfo >::const_iterator genIt = m_loadedGenerators.constFind( propName ); if ( !m_loadedGenerators.isEmpty() && genIt != m_loadedGenerators.constEnd() ) continue; Generator * g = loadGeneratorLibrary( offers.at(i) ); (void)g; } } void DocumentPrivate::unloadGenerator( const GeneratorInfo& info ) { delete info.generator; } void DocumentPrivate::cacheExportFormats() { if ( m_exportCached ) return; const ExportFormat::List formats = m_generator->exportFormats(); for ( int i = 0; i < formats.count(); ++i ) { if ( formats.at( i ).mimeType()->name() == QLatin1String( "text/plain" ) ) m_exportToText = formats.at( i ); else m_exportFormats.append( formats.at( i ) ); } m_exportCached = true; } ConfigInterface* DocumentPrivate::generatorConfig( GeneratorInfo& info ) { if ( info.configChecked ) return info.config; info.config = qobject_cast< Okular::ConfigInterface * >( info.generator ); info.configChecked = true; return info.config; } SaveInterface* DocumentPrivate::generatorSave( GeneratorInfo& info ) { if ( info.saveChecked ) return info.save; info.save = qobject_cast< Okular::SaveInterface * >( info.generator ); info.saveChecked = true; return info.save; } Document::OpenResult DocumentPrivate::openDocumentInternal( const KService::Ptr& offer, bool isstdin, const QString& docFile, const QByteArray& filedata, const QString& password ) { QString propName = offer->name(); QHash< QString, GeneratorInfo >::const_iterator genIt = m_loadedGenerators.constFind( propName ); QString catalogName; if ( genIt != m_loadedGenerators.constEnd() ) { m_generator = genIt.value().generator; catalogName = genIt.value().catalogName; } else { m_generator = loadGeneratorLibrary( offer ); if ( !m_generator ) return Document::OpenError; genIt = m_loadedGenerators.constFind( propName ); Q_ASSERT( genIt != m_loadedGenerators.constEnd() ); catalogName = genIt.value().catalogName; } Q_ASSERT_X( m_generator, "Document::load()", "null generator?!" ); if ( !catalogName.isEmpty() ) KGlobal::locale()->insertCatalog( catalogName ); m_generator->d_func()->m_document = this; // connect error reporting signals QObject::connect( m_generator, SIGNAL(error(QString,int)), m_parent, SIGNAL(error(QString,int)) ); QObject::connect( m_generator, SIGNAL(warning(QString,int)), m_parent, SIGNAL(warning(QString,int)) ); QObject::connect( m_generator, SIGNAL(notice(QString,int)), m_parent, SIGNAL(notice(QString,int)) ); QApplication::setOverrideCursor( Qt::WaitCursor ); m_generator->setDPI(Utils::realDpi(m_widget)); Document::OpenResult openResult = Document::OpenError; if ( !isstdin ) { openResult = m_generator->loadDocumentWithPassword( docFile, m_pagesVector, password ); } else if ( !filedata.isEmpty() ) { if ( m_generator->hasFeature( Generator::ReadRawData ) ) { openResult = m_generator->loadDocumentFromDataWithPassword( filedata, m_pagesVector, password ); } else { m_tempFile = new KTemporaryFile(); if ( !m_tempFile->open() ) { delete m_tempFile; m_tempFile = 0; } else { m_tempFile->write( filedata ); QString tmpFileName = m_tempFile->fileName(); m_tempFile->close(); openResult = m_generator->loadDocumentWithPassword( tmpFileName, m_pagesVector, password ); } } } QApplication::restoreOverrideCursor(); if ( openResult != Document::OpenSuccess || m_pagesVector.size() <= 0 ) { if ( !catalogName.isEmpty() ) KGlobal::locale()->removeCatalog( catalogName ); m_generator->d_func()->m_document = 0; QObject::disconnect( m_generator, 0, m_parent, 0 ); m_generator = 0; qDeleteAll( m_pagesVector ); m_pagesVector.clear(); delete m_tempFile; m_tempFile = 0; // TODO: emit a message telling the document is empty if ( openResult == Document::OpenSuccess ) openResult = Document::OpenError; } return openResult; } bool DocumentPrivate::savePageDocumentInfo( KTemporaryFile *infoFile, int what ) const { if ( infoFile->open() ) { // 1. Create DOM QDomDocument doc( "documentInfo" ); QDomProcessingInstruction xmlPi = doc.createProcessingInstruction( QString::fromLatin1( "xml" ), QString::fromLatin1( "version=\"1.0\" encoding=\"utf-8\"" ) ); doc.appendChild( xmlPi ); QDomElement root = doc.createElement( "documentInfo" ); doc.appendChild( root ); // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM QDomElement pageList = doc.createElement( "pageList" ); root.appendChild( pageList ); // .... save pages that hold data QVector< Page * >::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->saveLocalContents( pageList, doc, PageItems( what ) ); // 3. Save DOM to XML file QString xml = doc.toString(); QTextStream os( infoFile ); os.setCodec( "UTF-8" ); os << xml; return true; } return false; } DocumentViewport DocumentPrivate::nextDocumentViewport() const { DocumentViewport ret = m_nextDocumentViewport; if ( !m_nextDocumentDestination.isEmpty() && m_generator ) { DocumentViewport vp( m_generator->metaData( "NamedViewport", m_nextDocumentDestination ).toString() ); if ( vp.isValid() ) { ret = vp; } } return ret; } void DocumentPrivate::warnLimitedAnnotSupport() { if ( !m_showWarningLimitedAnnotSupport ) return; m_showWarningLimitedAnnotSupport = false; // Show the warning once if ( m_annotationsNeedSaveAs ) { // Shown if the user is editing annotations in a file whose metadata is // not stored locally (.okular archives belong to this category) KMessageBox::information( m_widget, i18n("Your annotation changes will not be saved automatically. Use File -> Save As...\nor your changes will be lost once the document is closed"), QString(), "annotNeedSaveAs" ); } else if ( !canAddAnnotationsNatively() ) { // If the generator doesn't support native annotations KMessageBox::information( m_widget, i18n("Your annotations are saved internally by Okular.\nYou can export the annotated document using File -> Export As -> Document Archive"), QString(), "annotExportAsArchive" ); } } void DocumentPrivate::performAddPageAnnotation( int page, Annotation * annotation ) { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); AnnotationProxy *proxy = iface ? iface->annotationProxy() : 0; // find out the page to attach annotation Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; // the annotation belongs already to a page if ( annotation->d_ptr->m_page ) return; // add annotation to the page kp->addAnnotation( annotation ); // tell the annotation proxy if ( proxy && proxy->supports(AnnotationProxy::Addition) ) proxy->notifyAddition( annotation, page ); // notify observers about the change notifyAnnotationChanges( page ); if ( annotation->flags() & Annotation::ExternallyDrawn ) { // Redraw everything, including ExternallyDrawn annotations refreshPixmaps( page ); } warnLimitedAnnotSupport(); } void DocumentPrivate::performRemovePageAnnotation( int page, Annotation * annotation ) { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); AnnotationProxy *proxy = iface ? iface->annotationProxy() : 0; bool isExternallyDrawn; // find out the page Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; if ( annotation->flags() & Annotation::ExternallyDrawn ) isExternallyDrawn = true; else isExternallyDrawn = false; // try to remove the annotation if ( m_parent->canRemovePageAnnotation( annotation ) ) { // tell the annotation proxy if ( proxy && proxy->supports(AnnotationProxy::Removal) ) proxy->notifyRemoval( annotation, page ); kp->removeAnnotation( annotation ); // Also destroys the object // in case of success, notify observers about the change notifyAnnotationChanges( page ); if ( isExternallyDrawn ) { // Redraw everything, including ExternallyDrawn annotations refreshPixmaps( page ); } } warnLimitedAnnotSupport(); } void DocumentPrivate::performModifyPageAnnotation( int page, Annotation * annotation, bool appearanceChanged ) { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); AnnotationProxy *proxy = iface ? iface->annotationProxy() : 0; // find out the page Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; // tell the annotation proxy if ( proxy && proxy->supports(AnnotationProxy::Modification) ) { proxy->notifyModification( annotation, page, appearanceChanged ); } // notify observers about the change notifyAnnotationChanges( page ); if ( appearanceChanged && (annotation->flags() & Annotation::ExternallyDrawn) ) { /* When an annotation is being moved, the generator will not render it. * Therefore there's no need to refresh pixmaps after the first time */ if ( annotation->flags() & Annotation::BeingMoved ) { if ( m_annotationBeingMoved ) return; else // First time: take note m_annotationBeingMoved = true; } else { m_annotationBeingMoved = false; } // Redraw everything, including ExternallyDrawn annotations kDebug(OkularDebug) << "Refreshing Pixmaps"; refreshPixmaps( page ); } // If the user is moving the annotation, don't steal the focus if ( (annotation->flags() & Annotation::BeingMoved) == 0 ) warnLimitedAnnotSupport(); } void DocumentPrivate::performSetAnnotationContents( const QString & newContents, Annotation *annot, int pageNumber ) { bool appearanceChanged = false; // Check if appearanceChanged should be true switch ( annot->subType() ) { // If it's an in-place TextAnnotation, set the inplace text case Okular::Annotation::AText: { Okular::TextAnnotation * txtann = static_cast< Okular::TextAnnotation * >( annot ); if ( txtann->textType() == Okular::TextAnnotation::InPlace ) { appearanceChanged = true; } break; } // If it's a LineAnnotation, check if caption text is visible case Okular::Annotation::ALine: { Okular::LineAnnotation * lineann = static_cast< Okular::LineAnnotation * >( annot ); if ( lineann->showCaption() ) appearanceChanged = true; break; } default: break; } // Set contents annot->setContents( newContents ); // Tell the document the annotation has been modified performModifyPageAnnotation( pageNumber, annot, appearanceChanged ); } void DocumentPrivate::saveDocumentInfo() const { if ( m_xmlFileName.isEmpty() ) return; QFile infoFile( m_xmlFileName ); if (infoFile.open( QIODevice::WriteOnly | QIODevice::Truncate) ) { // 1. Create DOM QDomDocument doc( "documentInfo" ); QDomProcessingInstruction xmlPi = doc.createProcessingInstruction( QString::fromLatin1( "xml" ), QString::fromLatin1( "version=\"1.0\" encoding=\"utf-8\"" ) ); doc.appendChild( xmlPi ); QDomElement root = doc.createElement( "documentInfo" ); root.setAttribute( "url", m_url.pathOrUrl() ); doc.appendChild( root ); // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM QDomElement pageList = doc.createElement( "pageList" ); root.appendChild( pageList ); PageItems saveWhat = AllPageItems; if ( m_annotationsNeedSaveAs ) { /* In this case, if the user makes a modification, he's requested to * save to a new document. Therefore, if there are existing local * annotations, we save them back unmodified in the original * document's metadata, so that it appears that it was not changed */ saveWhat |= OriginalAnnotationPageItems; } // .... save pages that hold data QVector< Page * >::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->saveLocalContents( pageList, doc, saveWhat ); // 2.2. Save document info (current viewport, history, ... ) to DOM QDomElement generalInfo = doc.createElement( "generalInfo" ); root.appendChild( generalInfo ); // create rotation node if ( m_rotation != Rotation0 ) { QDomElement rotationNode = doc.createElement( "rotation" ); generalInfo.appendChild( rotationNode ); rotationNode.appendChild( doc.createTextNode( QString::number( (int)m_rotation ) ) ); } // ... save history up to OKULAR_HISTORY_SAVEDSTEPS viewports QLinkedList< DocumentViewport >::const_iterator backIterator = m_viewportIterator; if ( backIterator != m_viewportHistory.constEnd() ) { // go back up to OKULAR_HISTORY_SAVEDSTEPS steps from the current viewportIterator int backSteps = OKULAR_HISTORY_SAVEDSTEPS; while ( backSteps-- && backIterator != m_viewportHistory.constBegin() ) --backIterator; // create history root node QDomElement historyNode = doc.createElement( "history" ); generalInfo.appendChild( historyNode ); // add old[backIterator] and present[viewportIterator] items QLinkedList< DocumentViewport >::const_iterator endIt = m_viewportIterator; ++endIt; while ( backIterator != endIt ) { QString name = (backIterator == m_viewportIterator) ? "current" : "oldPage"; QDomElement historyEntry = doc.createElement( name ); historyEntry.setAttribute( "viewport", (*backIterator).toString() ); historyNode.appendChild( historyEntry ); ++backIterator; } } // create views root node QDomElement viewsNode = doc.createElement( "views" ); generalInfo.appendChild( viewsNode ); Q_FOREACH ( View * view, m_views ) { QDomElement viewEntry = doc.createElement( "view" ); viewEntry.setAttribute( "name", view->name() ); viewsNode.appendChild( viewEntry ); saveViewsInfo( view, viewEntry ); } // 3. Save DOM to XML file QString xml = doc.toString(); QTextStream os( &infoFile ); os.setCodec( "UTF-8" ); os << xml; } infoFile.close(); } void DocumentPrivate::slotTimedMemoryCheck() { // [MEM] clean memory (for 'free mem dependant' profiles only) if ( SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Low && m_allocatedPixmapsTotalMemory > 1024*1024 ) cleanupPixmapMemory(); } void DocumentPrivate::sendGeneratorPixmapRequest() { /* If the pixmap cache will have to be cleaned in order to make room for the * next request, get the distance from the current viewport of the page * whose pixmap will be removed. We will ignore preload requests for pages * that are at the same distance or farther */ const qulonglong memoryToFree = calculateMemoryToFree(); const int currentViewportPage = (*m_viewportIterator).pageNumber; int maxDistance = INT_MAX; // Default: No maximum if ( memoryToFree ) { AllocatedPixmap *pixmapToReplace = searchLowestPriorityPixmap( true ); if ( pixmapToReplace ) maxDistance = qAbs( pixmapToReplace->page - currentViewportPage ); } // find a request PixmapRequest * request = 0; m_pixmapRequestsMutex.lock(); while ( !m_pixmapRequestsStack.isEmpty() && !request ) { PixmapRequest * r = m_pixmapRequestsStack.last(); if (!r) { m_pixmapRequestsStack.pop_back(); continue; } QRect requestRect = r->isTile() ? r->normalizedRect().geometry( r->width(), r->height() ) : QRect( 0, 0, r->width(), r->height() ); TilesManager *tilesManager = r->d->tilesManager(); // If it's a preload but the generator is not threaded no point in trying to preload if ( r->preload() && !m_generator->hasFeature( Generator::Threaded ) ) { m_pixmapRequestsStack.pop_back(); delete r; } // request only if page isn't already present and request has valid id // request only if page isn't already present and request has valid id else if ( ( !r->d->mForce && r->page()->hasPixmap( r->observer(), r->width(), r->height(), r->normalizedRect() ) ) || !m_observers.contains(r->observer()) ) { m_pixmapRequestsStack.pop_back(); delete r; } else if ( !r->d->mForce && r->preload() && qAbs( r->pageNumber() - currentViewportPage ) >= maxDistance ) { m_pixmapRequestsStack.pop_back(); //kDebug() << "Ignoring request that doesn't fit in cache"; delete r; } // Ignore requests for pixmaps that are already being generated else if ( tilesManager && tilesManager->isRequesting( r->normalizedRect(), r->width(), r->height() ) ) { m_pixmapRequestsStack.pop_back(); delete r; } // If the requested area is above 8000000 pixels, switch on the tile manager else if ( !tilesManager && m_generator->hasFeature( Generator::TiledRendering ) && (long)r->width() * (long)r->height() > 8000000L ) { // if the image is too big. start using tiles kDebug(OkularDebug).nospace() << "Start using tiles on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);"; // fill the tiles manager with the last rendered pixmap const QPixmap *pixmap = r->page()->_o_nearestPixmap( r->observer(), r->width(), r->height() ); if ( pixmap ) { tilesManager = new TilesManager( r->pageNumber(), pixmap->width(), pixmap->height(), r->page()->rotation() ); tilesManager->setPixmap( pixmap, NormalizedRect( 0, 0, 1, 1 ) ); tilesManager->setSize( r->width(), r->height() ); } else { // create new tiles manager tilesManager = new TilesManager( r->pageNumber(), r->width(), r->height(), r->page()->rotation() ); } tilesManager->setRequest( r->normalizedRect(), r->width(), r->height() ); r->page()->deletePixmap( r->observer() ); r->page()->d->setTilesManager( r->observer(), tilesManager ); r->setTile( true ); // Change normalizedRect to the smallest rect that contains all // visible tiles. if ( !r->normalizedRect().isNull() ) { NormalizedRect tilesRect; const QList tiles = tilesManager->tilesAt( r->normalizedRect(), TilesManager::TerminalTile ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { Tile tile = *tIt; if ( tilesRect.isNull() ) tilesRect = tile.rect(); else tilesRect |= tile.rect(); ++tIt; } r->setNormalizedRect( tilesRect ); request = r; } else { // Discard request if normalizedRect is null. This happens in // preload requests issued by PageView if the requested page is // not visible and the user has just switched from a non-tiled // zoom level to a tiled one m_pixmapRequestsStack.pop_back(); delete r; } } // If the requested area is below 6000000 pixels, switch off the tile manager else if ( tilesManager && (long)r->width() * (long)r->height() < 6000000L ) { kDebug(OkularDebug).nospace() << "Stop using tiles on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);"; // page is too small. stop using tiles. r->page()->deletePixmap( r->observer() ); r->setTile( false ); request = r; } else if ( (long)requestRect.width() * (long)requestRect.height() > 20000000L ) { m_pixmapRequestsStack.pop_back(); if ( !m_warnedOutOfMemory ) { kWarning(OkularDebug).nospace() << "Running out of memory on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);"; kWarning(OkularDebug) << "this message will be reported only once."; m_warnedOutOfMemory = true; } delete r; } else { request = r; } } // if no request found (or already generated), return if ( !request ) { m_pixmapRequestsMutex.unlock(); return; } // [MEM] preventive memory freeing qulonglong pixmapBytes = 0; TilesManager * tm = request->d->tilesManager(); if ( tm ) pixmapBytes = tm->totalMemory(); else pixmapBytes = 4 * request->width() * request->height(); if ( pixmapBytes > (1024 * 1024) ) cleanupPixmapMemory( memoryToFree /* previously calculated value */ ); // submit the request to the generator if ( m_generator->canGeneratePixmap() ) { QRect requestRect = !request->isTile() ? QRect(0, 0, request->width(), request->height() ) : request->normalizedRect().geometry( request->width(), request->height() ); kDebug(OkularDebug).nospace() << "sending request observer=" << request->observer() << " " <pageNumber() << " async == " << request->asynchronous() << " isTile == " << request->isTile(); m_pixmapRequestsStack.removeAll ( request ); if ( tm ) tm->setRequest( request->normalizedRect(), request->width(), request->height() ); if ( (int)m_rotation % 2 ) request->d->swap(); if ( m_rotation != Rotation0 && !request->normalizedRect().isNull() ) request->setNormalizedRect( TilesManager::fromRotatedRect( request->normalizedRect(), m_rotation ) ); // we always have to unlock _before_ the generatePixmap() because // a sync generation would end with requestDone() -> deadlock, and // we can not really know if the generator can do async requests m_executingPixmapRequests.push_back( request ); m_pixmapRequestsMutex.unlock(); m_generator->generatePixmap( request ); } else { m_pixmapRequestsMutex.unlock(); // pino (7/4/2006): set the polling interval from 10 to 30 QTimer::singleShot( 30, m_parent, SLOT(sendGeneratorPixmapRequest()) ); } } void DocumentPrivate::rotationFinished( int page, Okular::Page *okularPage ) { Okular::Page *wantedPage = m_pagesVector.value( page, 0 ); if ( !wantedPage || wantedPage != okularPage ) return; foreach(DocumentObserver *o, m_observers) o->notifyPageChanged( page, DocumentObserver::Pixmap | DocumentObserver::Annotations ); } void DocumentPrivate::fontReadingProgress( int page ) { emit m_parent->fontReadingProgress( page ); if ( page >= (int)m_parent->pages() - 1 ) { emit m_parent->fontReadingEnded(); m_fontThread = 0; m_fontsCached = true; } } void DocumentPrivate::fontReadingGotFont( const Okular::FontInfo& font ) { // TODO try to avoid duplicate fonts m_fontsCache.append( font ); emit m_parent->gotFont( font ); } void DocumentPrivate::slotGeneratorConfigChanged( const QString& ) { if ( !m_generator ) return; // reparse generator config and if something changed clear Pages bool configchanged = false; QHash< QString, GeneratorInfo >::iterator it = m_loadedGenerators.begin(), itEnd = m_loadedGenerators.end(); for ( ; it != itEnd; ++it ) { Okular::ConfigInterface * iface = generatorConfig( it.value() ); if ( iface ) { bool it_changed = iface->reparseConfig(); if ( it_changed && ( m_generator == it.value().generator ) ) configchanged = true; } } if ( configchanged ) { // invalidate pixmaps QVector::const_iterator it = m_pagesVector.constBegin(), end = m_pagesVector.constEnd(); for ( ; it != end; ++it ) { (*it)->deletePixmaps(); } // [MEM] remove allocation descriptors qDeleteAll( m_allocatedPixmaps ); m_allocatedPixmaps.clear(); m_allocatedPixmapsTotalMemory = 0; // send reload signals to observers foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap ) ); } // free memory if in 'low' profile if ( SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !m_allocatedPixmaps.isEmpty() && !m_pagesVector.isEmpty() ) cleanupPixmapMemory(); } void DocumentPrivate::refreshPixmaps( int pageNumber ) { Page* page = m_pagesVector.value( pageNumber, 0 ); if ( !page ) return; QLinkedList< Okular::PixmapRequest * > requestedPixmaps; QMap< DocumentObserver*, PagePrivate::PixmapObject >::ConstIterator it = page->d->m_pixmaps.constBegin(), itEnd = page->d->m_pixmaps.constEnd(); for ( ; it != itEnd; ++it ) { QSize size = (*it).m_pixmap->size(); PixmapRequest * p = new PixmapRequest( it.key(), pageNumber, size.width(), size.height(), 1, PixmapRequest::Asynchronous ); p->d->mForce = true; requestedPixmaps.push_back( p ); } foreach (DocumentObserver *observer, m_observers) { TilesManager *tilesManager = page->d->tilesManager( observer ); if ( tilesManager ) { tilesManager->markDirty(); PixmapRequest * p = new PixmapRequest( observer, pageNumber, tilesManager->width(), tilesManager->height(), 1, PixmapRequest::Asynchronous ); NormalizedRect tilesRect; // Get the visible page rect NormalizedRect visibleRect; QVector< Okular::VisiblePageRect * >::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) { if ( (*vIt)->pageNumber == pageNumber ) { visibleRect = (*vIt)->rect; break; } } if ( !visibleRect.isNull() ) { p->setNormalizedRect( visibleRect ); p->setTile( true ); p->d->mForce = true; requestedPixmaps.push_back( p ); } else { delete p; } } } if ( !requestedPixmaps.isEmpty() ) m_parent->requestPixmaps( requestedPixmaps, Okular::Document::NoOption ); } void DocumentPrivate::_o_configChanged() { // free text pages if needed calculateMaxTextPages(); while (m_allocatedTextPagesFifo.count() > m_maxAllocatedTextPages) { int pageToKick = m_allocatedTextPagesFifo.takeFirst(); m_pagesVector.at(pageToKick)->setTextPage( 0 ); // deletes the textpage } } void DocumentPrivate::doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct) { DoContinueDirectionMatchSearchStruct *searchStruct = static_cast(doContinueDirectionMatchSearchStruct); RunningSearch *search = m_searches.value(searchStruct->searchID); if ((m_searchCancelled && !searchStruct->match) || !search) { // if the user cancelled but he just got a match, give him the match! QApplication::restoreOverrideCursor(); if (search) search->isCurrentlySearching = false; emit m_parent->searchFinished( searchStruct->searchID, Document::SearchCancelled ); delete searchStruct->pagesToNotify; delete searchStruct; return; } const bool forward = search->cachedType == Document::NextMatch; bool doContinue = false; // if no match found, loop through the whole doc, starting from currentPage if ( !searchStruct->match ) { const int pageCount = m_pagesVector.count(); if (search->pagesDone < pageCount) { doContinue = true; if ( searchStruct->currentPage >= pageCount || searchStruct->currentPage < 0 ) { doContinue = false; search->isCurrentlySearching = false; search->continueOnPage = forward ? 0 : pageCount - 1; search->continueOnMatch = RegularAreaRect(); emit m_parent->searchFinished ( searchStruct->searchID, Document::EndOfDocumentReached ); } } } if (doContinue) { // get page Page * page = m_pagesVector[ searchStruct->currentPage ]; // request search page if needed if ( !page->hasTextPage() ) m_parent->requestTextPage( page->number() ); // if found a match on the current page, end the loop searchStruct->match = page->findText( searchStruct->searchID, search->cachedString, forward ? FromTop : FromBottom, search->cachedCaseSensitivity ); if ( !searchStruct->match ) { if (forward) searchStruct->currentPage++; else searchStruct->currentPage--; search->pagesDone++; } else { search->pagesDone = 1; } // Both of the previous if branches need to call doContinueDirectionMatchSearch QMetaObject::invokeMethod(m_parent, "doContinueDirectionMatchSearch", Qt::QueuedConnection, Q_ARG(void *, searchStruct)); } else { doProcessSearchMatch( searchStruct->match, search, searchStruct->pagesToNotify, searchStruct->currentPage, searchStruct->searchID, search->cachedViewportMove, search->cachedColor ); delete searchStruct; } } void DocumentPrivate::doProcessSearchMatch( RegularAreaRect *match, RunningSearch *search, QSet< int > *pagesToNotify, int currentPage, int searchID, bool moveViewport, const QColor & color ) { // reset cursor to previous shape QApplication::restoreOverrideCursor(); bool foundAMatch = false; search->isCurrentlySearching = false; // if a match has been found.. if ( match ) { // update the RunningSearch structure adding this match.. foundAMatch = true; search->continueOnPage = currentPage; search->continueOnMatch = *match; search->highlightedPages.insert( currentPage ); // ..add highlight to the page.. m_pagesVector[ currentPage ]->d->setHighlight( searchID, match, color ); // ..queue page for notifying changes.. pagesToNotify->insert( currentPage ); // Create a normalized rectangle around the search match that includes a 5% buffer on all sides. const Okular::NormalizedRect matchRectWithBuffer = Okular::NormalizedRect( match->first().left - 0.05, match->first().top - 0.05, match->first().right + 0.05, match->first().bottom + 0.05 ); const bool matchRectFullyVisible = isNormalizedRectangleFullyVisible( matchRectWithBuffer, currentPage ); // ..move the viewport to show the first of the searched word sequence centered if ( moveViewport && !matchRectFullyVisible ) { DocumentViewport searchViewport( currentPage ); searchViewport.rePos.enabled = true; searchViewport.rePos.normalizedX = (match->first().left + match->first().right) / 2.0; searchViewport.rePos.normalizedY = (match->first().top + match->first().bottom) / 2.0; m_parent->setViewport( searchViewport, 0, true ); } delete match; } // notify observers about highlights changes foreach(int pageNumber, *pagesToNotify) foreach(DocumentObserver *observer, m_observers) observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); if (foundAMatch) emit m_parent->searchFinished( searchID, Document::MatchFound ); else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); delete pagesToNotify; } void DocumentPrivate::doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID) { QMap< Page *, QVector > *pageMatches = static_cast< QMap< Page *, QVector > * >(pageMatchesMap); QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet ); RunningSearch *search = m_searches.value(searchID); if (m_searchCancelled || !search) { typedef QVector MatchesVector; QApplication::restoreOverrideCursor(); if (search) search->isCurrentlySearching = false; emit m_parent->searchFinished( searchID, Document::SearchCancelled ); foreach(const MatchesVector &mv, *pageMatches) qDeleteAll(mv); delete pageMatches; delete pagesToNotify; return; } if (currentPage < m_pagesVector.count()) { // get page (from the first to the last) Page *page = m_pagesVector.at(currentPage); int pageNumber = page->number(); // redundant? is it == currentPage ? // request search page if needed if ( !page->hasTextPage() ) m_parent->requestTextPage( pageNumber ); // loop on a page adding highlights for all found items RegularAreaRect * lastMatch = 0; while ( 1 ) { if ( lastMatch ) lastMatch = page->findText( searchID, search->cachedString, NextResult, search->cachedCaseSensitivity, lastMatch ); else lastMatch = page->findText( searchID, search->cachedString, FromTop, search->cachedCaseSensitivity ); if ( !lastMatch ) break; // add highligh rect to the matches map (*pageMatches)[page].append(lastMatch); } delete lastMatch; QMetaObject::invokeMethod(m_parent, "doContinueAllDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotifySet), Q_ARG(void *, pageMatches), Q_ARG(int, currentPage + 1), Q_ARG(int, searchID)); } else { // reset cursor to previous shape QApplication::restoreOverrideCursor(); search->isCurrentlySearching = false; bool foundAMatch = pageMatches->count() != 0; QMap< Page *, QVector >::const_iterator it, itEnd; it = pageMatches->constBegin(); itEnd = pageMatches->constEnd(); for ( ; it != itEnd; ++it) { foreach(RegularAreaRect *match, it.value()) { it.key()->d->setHighlight( searchID, match, search->cachedColor ); delete match; } search->highlightedPages.insert( it.key()->number() ); pagesToNotify->insert( it.key()->number() ); } foreach(DocumentObserver *observer, m_observers) observer->notifySetup( m_pagesVector, 0 ); // notify observers about highlights changes foreach(int pageNumber, *pagesToNotify) foreach(DocumentObserver *observer, m_observers) observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); if (foundAMatch) emit m_parent->searchFinished(searchID, Document::MatchFound ); else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); delete pageMatches; delete pagesToNotify; } } void DocumentPrivate::doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words) { typedef QPair MatchColor; QMap< Page *, QVector > *pageMatches = static_cast< QMap< Page *, QVector > * >(pageMatchesMap); QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet ); RunningSearch *search = m_searches.value(searchID); if (m_searchCancelled || !search) { typedef QVector MatchesVector; QApplication::restoreOverrideCursor(); if (search) search->isCurrentlySearching = false; emit m_parent->searchFinished( searchID, Document::SearchCancelled ); foreach(const MatchesVector &mv, *pageMatches) { foreach(const MatchColor &mc, mv) delete mc.first; } delete pageMatches; delete pagesToNotify; return; } const int wordCount = words.count(); const int hueStep = (wordCount > 1) ? (60 / (wordCount - 1)) : 60; int baseHue, baseSat, baseVal; search->cachedColor.getHsv( &baseHue, &baseSat, &baseVal ); if (currentPage < m_pagesVector.count()) { // get page (from the first to the last) Page *page = m_pagesVector.at(currentPage); int pageNumber = page->number(); // redundant? is it == currentPage ? // request search page if needed if ( !page->hasTextPage() ) m_parent->requestTextPage( pageNumber ); // loop on a page adding highlights for all found items bool allMatched = wordCount > 0, anyMatched = false; for ( int w = 0; w < wordCount; w++ ) { const QString &word = words[ w ]; int newHue = baseHue - w * hueStep; if ( newHue < 0 ) newHue += 360; QColor wordColor = QColor::fromHsv( newHue, baseSat, baseVal ); RegularAreaRect * lastMatch = 0; // add all highlights for current word bool wordMatched = false; while ( 1 ) { if ( lastMatch ) lastMatch = page->findText( searchID, word, NextResult, search->cachedCaseSensitivity, lastMatch ); else lastMatch = page->findText( searchID, word, FromTop, search->cachedCaseSensitivity); if ( !lastMatch ) break; // add highligh rect to the matches map (*pageMatches)[page].append(MatchColor(lastMatch, wordColor)); wordMatched = true; } allMatched = allMatched && wordMatched; anyMatched = anyMatched || wordMatched; } // if not all words are present in page, remove partial highlights const bool matchAll = search->cachedType == Document::GoogleAll; if ( !allMatched && matchAll ) { QVector &matches = (*pageMatches)[page]; foreach(const MatchColor &mc, matches) delete mc.first; pageMatches->remove(page); } 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(QStringList, words)); } else { // reset cursor to previous shape QApplication::restoreOverrideCursor(); search->isCurrentlySearching = false; bool foundAMatch = pageMatches->count() != 0; QMap< Page *, QVector >::const_iterator it, itEnd; it = pageMatches->constBegin(); itEnd = pageMatches->constEnd(); for ( ; it != itEnd; ++it) { foreach(const MatchColor &mc, it.value()) { it.key()->d->setHighlight( searchID, mc.first, mc.second ); delete mc.first; } search->highlightedPages.insert( it.key()->number() ); pagesToNotify->insert( it.key()->number() ); } // send page lists to update observers (since some filter on bookmarks) foreach(DocumentObserver *observer, m_observers) observer->notifySetup( m_pagesVector, 0 ); // notify observers about highlights changes foreach(int pageNumber, *pagesToNotify) foreach(DocumentObserver *observer, m_observers) observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); if (foundAMatch) emit m_parent->searchFinished( searchID, Document::MatchFound ); else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); delete pageMatches; delete pagesToNotify; } } QVariant DocumentPrivate::documentMetaData( const QString &key, const QVariant &option ) const { if ( key == QLatin1String( "PaperColor" ) ) { bool giveDefault = option.toBool(); // load paper color from Settings, or use the default color (white) // if we were told to do so QColor color; if ( ( SettingsCore::renderMode() == SettingsCore::EnumRenderMode::Paper ) && SettingsCore::changeColors() ) { color = SettingsCore::paperColor(); } else if ( giveDefault ) { color = Qt::white; } return color; } else if ( key == QLatin1String( "TextAntialias" ) ) { switch ( SettingsCore::textAntialias() ) { case SettingsCore::EnumTextAntialias::Enabled: return true; break; #if 0 case Settings::EnumTextAntialias::UseKDESettings: // TODO: read the KDE configuration return true; break; #endif case SettingsCore::EnumTextAntialias::Disabled: return false; break; } } else if ( key == QLatin1String( "GraphicsAntialias" ) ) { switch ( SettingsCore::graphicsAntialias() ) { case SettingsCore::EnumGraphicsAntialias::Enabled: return true; break; case SettingsCore::EnumGraphicsAntialias::Disabled: return false; break; } } else if ( key == QLatin1String( "TextHinting" ) ) { switch ( SettingsCore::textHinting() ) { case SettingsCore::EnumTextHinting::Enabled: return true; break; case SettingsCore::EnumTextHinting::Disabled: return false; break; } } return QVariant(); } bool DocumentPrivate::isNormalizedRectangleFullyVisible( const Okular::NormalizedRect & rectOfInterest, int rectPage ) { bool rectFullyVisible = false; const QVector & visibleRects = m_parent->visiblePageRects(); QVector::const_iterator vEnd = visibleRects.end(); QVector::const_iterator vIt = visibleRects.begin(); for ( ; ( vIt != vEnd ) && !rectFullyVisible; ++vIt ) { if ( (*vIt)->pageNumber == rectPage && (*vIt)->rect.contains( rectOfInterest.left, rectOfInterest.top ) && (*vIt)->rect.contains( rectOfInterest.right, rectOfInterest.bottom ) ) { rectFullyVisible = true; } } return rectFullyVisible; } Document::Document( QWidget *widget ) : QObject( 0 ), d( new DocumentPrivate( this ) ) { d->m_widget = widget; d->m_bookmarkManager = new BookmarkManager( d ); d->m_viewportIterator = d->m_viewportHistory.insert( d->m_viewportHistory.end(), DocumentViewport() ); d->m_undoStack = new QUndoStack(this); connect( SettingsCore::self(), SIGNAL(configChanged()), this, SLOT(_o_configChanged()) ); connect( d->m_undoStack, SIGNAL( canUndoChanged(bool) ), this, SIGNAL( canUndoChanged(bool))); connect( d->m_undoStack, SIGNAL( canRedoChanged(bool) ), this, SIGNAL( canRedoChanged(bool) ) ); qRegisterMetaType(); } Document::~Document() { // delete generator, pages, and related stuff closeDocument(); QSet< View * >::const_iterator viewIt = d->m_views.constBegin(), viewEnd = d->m_views.constEnd(); for ( ; viewIt != viewEnd; ++viewIt ) { View *v = *viewIt; v->d_func()->document = 0; } // delete the bookmark manager delete d->m_bookmarkManager; // delete the loaded generators QHash< QString, GeneratorInfo >::const_iterator it = d->m_loadedGenerators.constBegin(), itEnd = d->m_loadedGenerators.constEnd(); for ( ; it != itEnd; ++it ) d->unloadGenerator( it.value() ); d->m_loadedGenerators.clear(); // delete the private structure delete d; } class kMimeTypeMoreThan { public: kMimeTypeMoreThan( const KMimeType::Ptr &mime ) : _mime( mime ) {} bool operator()( const KService::Ptr &s1, const KService::Ptr &s2 ) { const QString mimeName = _mime->name(); if (s1->mimeTypes().contains( mimeName ) && !s2->mimeTypes().contains( mimeName )) return true; else if (s2->mimeTypes().contains( mimeName ) && !s1->mimeTypes().contains( mimeName )) return false; return s1->property( "X-KDE-Priority" ).toInt() > s2->property( "X-KDE-Priority" ).toInt(); } private: const KMimeType::Ptr &_mime; }; Document::OpenResult Document::openDocument( const QString & docFile, const KUrl& url, const KMimeType::Ptr &_mime, const QString & password ) { KMimeType::Ptr mime = _mime; QByteArray filedata; qint64 document_size = -1; bool isstdin = url.fileName( KUrl::ObeyTrailingSlash ) == QLatin1String( "-" ); bool triedMimeFromFileContent = false; if ( !isstdin ) { if ( mime.count() <= 0 ) return OpenError; // docFile is always local so we can use QFileInfo on it QFileInfo fileReadTest( docFile ); if ( fileReadTest.isFile() && !fileReadTest.isReadable() ) { d->m_docFileName.clear(); return OpenError; } // determine the related "xml document-info" filename d->m_url = url; d->m_docFileName = docFile; if ( url.isLocalFile() && !d->m_archiveData ) { QString fn = url.fileName(); document_size = fileReadTest.size(); fn = QString::number( document_size ) + '.' + fn + ".xml"; QString newokular = "okular/docdata/" + fn; QString newokularfile = KStandardDirs::locateLocal( "data", newokular ); if ( !QFile::exists( newokularfile ) ) { QString oldkpdf = "kpdf/" + fn; QString oldkpdffile = KStandardDirs::locateLocal( "data", oldkpdf ); if ( QFile::exists( oldkpdffile ) ) { // ### copy or move? if ( !QFile::copy( oldkpdffile, newokularfile ) ) return OpenError; } } d->m_xmlFileName = newokularfile; } } else { QFile qstdin; qstdin.open( stdin, QIODevice::ReadOnly ); filedata = qstdin.readAll(); mime = KMimeType::findByContent( filedata ); if ( !mime || mime->name() == QLatin1String( "application/octet-stream" ) ) return OpenError; document_size = filedata.size(); triedMimeFromFileContent = true; } // 0. load Generator // request only valid non-disabled plugins suitable for the mimetype QString constraint("([X-KDE-Priority] > 0) and (exist Library)") ; KService::List offers = KMimeTypeTrader::self()->query(mime->name(),"okular/Generator",constraint); if ( offers.isEmpty() && !triedMimeFromFileContent ) { KMimeType::Ptr newmime = KMimeType::findByFileContent( docFile ); triedMimeFromFileContent = true; if ( newmime->name() != mime->name() ) { mime = newmime; offers = KMimeTypeTrader::self()->query( mime->name(), "okular/Generator", constraint ); } if ( offers.isEmpty() ) { // There's still no offers, do a final mime search based on the filename // We need this because sometimes (e.g. when downloading from a webserver) the mimetype we // use is the one fed by the server, that may be wrong newmime = KMimeType::findByUrl( docFile ); if ( newmime->name() != mime->name() ) { mime = newmime; offers = KMimeTypeTrader::self()->query( mime->name(), "okular/Generator", constraint ); } } } if (offers.isEmpty()) { emit error( i18n( "Can not find a plugin which is able to handle the document being passed." ), -1 ); kWarning(OkularDebug).nospace() << "No plugin for mimetype '" << mime->name() << "'."; return OpenError; } int hRank=0; // best ranked offer search int offercount = offers.count(); if ( offercount > 1 ) { // sort the offers: the offers with an higher priority come before qStableSort( offers.begin(), offers.end(), kMimeTypeMoreThan( mime ) ); if ( SettingsCore::chooseGenerators() ) { QStringList list; for ( int i = 0; i < offercount; ++i ) { list << offers.at(i)->name(); } ChooseEngineDialog choose( list, mime, d->m_widget ); if ( choose.exec() == QDialog::Rejected ) return OpenError; hRank = choose.selectedGenerator(); } } KService::Ptr offer = offers.at( hRank ); // 1. load Document OpenResult openResult = d->openDocumentInternal( offer, isstdin, docFile, filedata, password ); if ( openResult == OpenError && !triedMimeFromFileContent ) { KMimeType::Ptr newmime = KMimeType::findByFileContent( docFile ); triedMimeFromFileContent = true; if ( newmime->name() != mime->name() ) { mime = newmime; offers = KMimeTypeTrader::self()->query( mime->name(), "okular/Generator", constraint ); if ( !offers.isEmpty() ) { offer = offers.first(); openResult = d->openDocumentInternal( offer, isstdin, docFile, filedata, password ); } } } if ( openResult != OpenSuccess ) { return openResult; } d->m_generatorName = offer->name(); d->m_pageController = new PageController(); connect( d->m_pageController, SIGNAL(rotationFinished(int,Okular::Page*)), this, SLOT(rotationFinished(int,Okular::Page*)) ); bool containsExternalAnnotations = false; foreach ( Page * p, d->m_pagesVector ) { p->d->m_doc = d; if ( !p->annotations().empty() ) containsExternalAnnotations = true; } // Be quiet while restoring local annotations d->m_showWarningLimitedAnnotSupport = false; d->m_annotationsNeedSaveAs = false; // 2. load Additional Data (bookmarks, local annotations and metadata) about the document if ( d->m_archiveData ) { d->loadDocumentInfo( d->m_archiveData->metadataFile ); d->m_annotationsNeedSaveAs = true; } else { d->loadDocumentInfo(); d->m_annotationsNeedSaveAs = ( d->canAddAnnotationsNatively() && containsExternalAnnotations ); } d->m_showWarningLimitedAnnotSupport = true; d->m_bookmarkManager->setUrl( d->m_url ); // 3. setup observers inernal lists and data foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged ) ); // 4. set initial page (restoring the page saved in xml if loaded) DocumentViewport loadedViewport = (*d->m_viewportIterator); if ( loadedViewport.isValid() ) { (*d->m_viewportIterator) = DocumentViewport(); if ( loadedViewport.pageNumber >= (int)d->m_pagesVector.size() ) loadedViewport.pageNumber = d->m_pagesVector.size() - 1; } else loadedViewport.pageNumber = 0; setViewport( loadedViewport ); // start bookmark saver timer if ( !d->m_saveBookmarksTimer ) { d->m_saveBookmarksTimer = new QTimer( this ); connect( d->m_saveBookmarksTimer, SIGNAL(timeout()), this, SLOT(saveDocumentInfo()) ); } d->m_saveBookmarksTimer->start( 5 * 60 * 1000 ); // start memory check timer if ( !d->m_memCheckTimer ) { d->m_memCheckTimer = new QTimer( this ); connect( d->m_memCheckTimer, SIGNAL(timeout()), this, SLOT(slotTimedMemoryCheck()) ); } d->m_memCheckTimer->start( 2000 ); const DocumentViewport nextViewport = d->nextDocumentViewport(); if ( nextViewport.isValid() ) { setViewport( nextViewport ); d->m_nextDocumentViewport = DocumentViewport(); d->m_nextDocumentDestination = QString(); } AudioPlayer::instance()->d->m_currentDocument = isstdin ? KUrl() : d->m_url; d->m_docSize = document_size; const QStringList docScripts = d->m_generator->metaData( "DocumentScripts", "JavaScript" ).toStringList(); if ( !docScripts.isEmpty() ) { d->m_scripter = new Scripter( d ); Q_FOREACH ( const QString &docscript, docScripts ) { d->m_scripter->execute( JavaScript, docscript ); } } return OpenSuccess; } KXMLGUIClient* Document::guiClient() { if ( d->m_generator ) { Okular::GuiInterface * iface = qobject_cast< Okular::GuiInterface * >( d->m_generator ); if ( iface ) return iface->guiClient(); } return 0; } void Document::closeDocument() { // check if there's anything to close... if ( !d->m_generator ) return; delete d->m_pageController; d->m_pageController = 0; delete d->m_scripter; d->m_scripter = 0; // remove requests left in queue d->m_pixmapRequestsMutex.lock(); QLinkedList< PixmapRequest * >::const_iterator sIt = d->m_pixmapRequestsStack.constBegin(); QLinkedList< PixmapRequest * >::const_iterator sEnd = d->m_pixmapRequestsStack.constEnd(); for ( ; sIt != sEnd; ++sIt ) delete *sIt; d->m_pixmapRequestsStack.clear(); d->m_pixmapRequestsMutex.unlock(); QEventLoop loop; bool startEventLoop = false; do { d->m_pixmapRequestsMutex.lock(); startEventLoop = !d->m_executingPixmapRequests.isEmpty(); d->m_pixmapRequestsMutex.unlock(); if ( startEventLoop ) { d->m_closingLoop = &loop; loop.exec(); d->m_closingLoop = 0; } } while ( startEventLoop ); if ( d->m_fontThread ) { disconnect( d->m_fontThread, 0, this, 0 ); d->m_fontThread->stopExtraction(); d->m_fontThread->wait(); d->m_fontThread = 0; } // stop any audio playback AudioPlayer::instance()->stopPlaybacks(); // close the current document and save document info if a document is still opened if ( d->m_generator && d->m_pagesVector.size() > 0 ) { d->saveDocumentInfo(); d->m_generator->closeDocument(); } // stop timers if ( d->m_memCheckTimer ) d->m_memCheckTimer->stop(); if ( d->m_saveBookmarksTimer ) d->m_saveBookmarksTimer->stop(); if ( d->m_generator ) { // disconnect the generator from this document ... d->m_generator->d_func()->m_document = 0; // .. and this document from the generator signals disconnect( d->m_generator, 0, this, 0 ); QHash< QString, GeneratorInfo >::const_iterator genIt = d->m_loadedGenerators.constFind( d->m_generatorName ); Q_ASSERT( genIt != d->m_loadedGenerators.constEnd() ); if ( !genIt.value().catalogName.isEmpty() && !genIt.value().config ) KGlobal::locale()->removeCatalog( genIt.value().catalogName ); } d->m_generator = 0; d->m_generatorName = QString(); d->m_url = KUrl(); d->m_docFileName = QString(); d->m_xmlFileName = QString(); delete d->m_tempFile; d->m_tempFile = 0; delete d->m_archiveData; d->m_archiveData = 0; d->m_docSize = -1; d->m_exportCached = false; d->m_exportFormats.clear(); d->m_exportToText = ExportFormat(); d->m_fontsCached = false; d->m_fontsCache.clear(); d->m_rotation = Rotation0; // send an empty list to observers (to free their data) foreachObserver( notifySetup( QVector< Page * >(), DocumentObserver::DocumentChanged ) ); // delete pages and clear 'd->m_pagesVector' container QVector< Page * >::const_iterator pIt = d->m_pagesVector.constBegin(); QVector< Page * >::const_iterator pEnd = d->m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) delete *pIt; d->m_pagesVector.clear(); // clear 'memory allocation' descriptors qDeleteAll( d->m_allocatedPixmaps ); d->m_allocatedPixmaps.clear(); // clear 'running searches' descriptors QMap< int, RunningSearch * >::const_iterator rIt = d->m_searches.constBegin(); QMap< int, RunningSearch * >::const_iterator rEnd = d->m_searches.constEnd(); for ( ; rIt != rEnd; ++rIt ) delete *rIt; d->m_searches.clear(); // clear the visible areas and notify the observers QVector< VisiblePageRect * >::const_iterator vIt = d->m_pageRects.constBegin(); QVector< VisiblePageRect * >::const_iterator vEnd = d->m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) delete *vIt; d->m_pageRects.clear(); foreachObserver( notifyVisibleRectsChanged() ); // reset internal variables d->m_viewportHistory.clear(); d->m_viewportHistory.append( DocumentViewport() ); d->m_viewportIterator = d->m_viewportHistory.begin(); d->m_allocatedPixmapsTotalMemory = 0; d->m_allocatedTextPagesFifo.clear(); d->m_pageSize = PageSize(); d->m_pageSizes.clear(); delete d->m_documentInfo; d->m_documentInfo = 0; AudioPlayer::instance()->d->m_currentDocument = KUrl(); d->m_undoStack->clear(); } void Document::addObserver( DocumentObserver * pObserver ) { Q_ASSERT( !d->m_observers.contains( pObserver ) ); d->m_observers << pObserver; // if the observer is added while a document is already opened, tell it if ( !d->m_pagesVector.isEmpty() ) { pObserver->notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged ); pObserver->notifyViewportChanged( false /*disables smoothMove*/ ); } } void Document::removeObserver( DocumentObserver * pObserver ) { // remove observer from the map. it won't receive notifications anymore if ( d->m_observers.contains( pObserver ) ) { // free observer's pixmap data QVector::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd(); for ( ; it != end; ++it ) (*it)->deletePixmap( pObserver ); // [MEM] free observer's allocation descriptors QLinkedList< AllocatedPixmap * >::iterator aIt = d->m_allocatedPixmaps.begin(); QLinkedList< AllocatedPixmap * >::iterator aEnd = d->m_allocatedPixmaps.end(); while ( aIt != aEnd ) { AllocatedPixmap * p = *aIt; if ( p->observer == pObserver ) { aIt = d->m_allocatedPixmaps.erase( aIt ); delete p; } else ++aIt; } // delete observer entry from the map d->m_observers.remove( pObserver ); } } void Document::reparseConfig() { // reparse generator config and if something changed clear Pages bool configchanged = false; if ( d->m_generator ) { Okular::ConfigInterface * iface = qobject_cast< Okular::ConfigInterface * >( d->m_generator ); if ( iface ) configchanged = iface->reparseConfig(); } if ( configchanged ) { // invalidate pixmaps QVector::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd(); for ( ; it != end; ++it ) { (*it)->deletePixmaps(); } // [MEM] remove allocation descriptors qDeleteAll( d->m_allocatedPixmaps ); d->m_allocatedPixmaps.clear(); d->m_allocatedPixmapsTotalMemory = 0; // send reload signals to observers foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap ) ); } // free memory if in 'low' profile if ( SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !d->m_allocatedPixmaps.isEmpty() && !d->m_pagesVector.isEmpty() ) d->cleanupPixmapMemory(); } bool Document::isOpened() const { return d->m_generator; } bool Document::canConfigurePrinter( ) const { if ( d->m_generator ) { Okular::PrintInterface * iface = qobject_cast< Okular::PrintInterface * >( d->m_generator ); return iface ? true : false; } else return 0; } const DocumentInfo * Document::documentInfo() const { if ( d->m_documentInfo ) return d->m_documentInfo; if ( d->m_generator ) { DocumentInfo *info = new DocumentInfo(); const DocumentInfo *tmp = d->m_generator->generateDocumentInfo(); if ( tmp ) *info = *tmp; info->set( DocumentInfo::FilePath, currentDocument().prettyUrl() ); const QString pagesSize = d->pagesSizeString(); if ( d->m_docSize != -1 ) { const QString sizeString = KGlobal::locale()->formatByteSize( d->m_docSize ); info->set( DocumentInfo::DocumentSize, sizeString ); } if (!pagesSize.isEmpty()) { info->set( DocumentInfo::PagesSize, pagesSize ); } const DocumentInfo::Key keyPages = DocumentInfo::Pages; const QString keyString = DocumentInfo::getKeyString( keyPages ); if ( info->get( keyString ).isEmpty() ) { info->set( keyString, QString::number( this->pages() ), DocumentInfo::getKeyTitle( keyPages ) ); } d->m_documentInfo = info; return info; } else return NULL; } const DocumentSynopsis * Document::documentSynopsis() const { return d->m_generator ? d->m_generator->generateDocumentSynopsis() : NULL; } void Document::startFontReading() { if ( !d->m_generator || !d->m_generator->hasFeature( Generator::FontInfo ) || d->m_fontThread ) return; if ( d->m_fontsCached ) { // in case we have cached fonts, simulate a reading // this way the API is the same, and users no need to care about the // internal caching for ( int i = 0; i < d->m_fontsCache.count(); ++i ) { emit gotFont( d->m_fontsCache.at( i ) ); emit fontReadingProgress( i / pages() ); } emit fontReadingEnded(); return; } d->m_fontThread = new FontExtractionThread( d->m_generator, pages() ); connect( d->m_fontThread, SIGNAL(gotFont(Okular::FontInfo)), this, SLOT(fontReadingGotFont(Okular::FontInfo)) ); connect( d->m_fontThread, SIGNAL(progress(int)), this, SLOT(fontReadingProgress(int)) ); d->m_fontThread->startExtraction( /*d->m_generator->hasFeature( Generator::Threaded )*/true ); } void Document::stopFontReading() { if ( !d->m_fontThread ) return; disconnect( d->m_fontThread, 0, this, 0 ); d->m_fontThread->stopExtraction(); d->m_fontThread = 0; d->m_fontsCache.clear(); } bool Document::canProvideFontInformation() const { return d->m_generator ? d->m_generator->hasFeature( Generator::FontInfo ) : false; } const QList *Document::embeddedFiles() const { return d->m_generator ? d->m_generator->embeddedFiles() : NULL; } const Page * Document::page( int n ) const { return ( n < d->m_pagesVector.count() ) ? d->m_pagesVector.at(n) : 0; } const DocumentViewport & Document::viewport() const { return (*d->m_viewportIterator); } const QVector< VisiblePageRect * > & Document::visiblePageRects() const { return d->m_pageRects; } void Document::setVisiblePageRects( const QVector< VisiblePageRect * > & visiblePageRects, DocumentObserver *excludeObserver ) { QVector< VisiblePageRect * >::const_iterator vIt = d->m_pageRects.constBegin(); QVector< VisiblePageRect * >::const_iterator vEnd = d->m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) delete *vIt; d->m_pageRects = visiblePageRects; // notify change to all other (different from id) observers foreach(DocumentObserver *o, d->m_observers) if ( o != excludeObserver ) o->notifyVisibleRectsChanged(); } uint Document::currentPage() const { return (*d->m_viewportIterator).pageNumber; } uint Document::pages() const { return d->m_pagesVector.size(); } KUrl Document::currentDocument() const { return d->m_url; } bool Document::isAllowed( Permission action ) const { if ( action == Okular::AllowNotes && !d->m_annotationEditingEnabled ) return false; #if !OKULAR_FORCE_DRM if ( KAuthorized::authorize( "skip_drm" ) && !Okular::SettingsCore::obeyDRM() ) return true; #endif return d->m_generator ? d->m_generator->isAllowed( action ) : false; } bool Document::supportsSearching() const { return d->m_generator ? d->m_generator->hasFeature( Generator::TextExtraction ) : false; } bool Document::supportsPageSizes() const { return d->m_generator ? d->m_generator->hasFeature( Generator::PageSizes ) : false; } bool Document::supportsTiles() const { return d->m_generator ? d->m_generator->hasFeature( Generator::TiledRendering ) : false; } PageSize::List Document::pageSizes() const { if ( d->m_generator ) { if ( d->m_pageSizes.isEmpty() ) d->m_pageSizes = d->m_generator->pageSizes(); return d->m_pageSizes; } return PageSize::List(); } bool Document::canExportToText() const { if ( !d->m_generator ) return false; d->cacheExportFormats(); return !d->m_exportToText.isNull(); } bool Document::exportToText( const QString& fileName ) const { if ( !d->m_generator ) return false; d->cacheExportFormats(); if ( d->m_exportToText.isNull() ) return false; return d->m_generator->exportTo( fileName, d->m_exportToText ); } ExportFormat::List Document::exportFormats() const { if ( !d->m_generator ) return ExportFormat::List(); d->cacheExportFormats(); return d->m_exportFormats; } bool Document::exportTo( const QString& fileName, const ExportFormat& format ) const { return d->m_generator ? d->m_generator->exportTo( fileName, format ) : false; } bool Document::historyAtBegin() const { return d->m_viewportIterator == d->m_viewportHistory.begin(); } bool Document::historyAtEnd() const { return d->m_viewportIterator == --(d->m_viewportHistory.end()); } QVariant Document::metaData( const QString & key, const QVariant & option ) const { return d->m_generator ? d->m_generator->metaData( key, option ) : QVariant(); } Rotation Document::rotation() const { return d->m_rotation; } QSizeF Document::allPagesSize() const { bool allPagesSameSize = true; QSizeF size; for (int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i) { const Page *p = d->m_pagesVector.at(i); if (i == 0) size = QSizeF(p->width(), p->height()); else { allPagesSameSize = (size == QSizeF(p->width(), p->height())); } } if (allPagesSameSize) return size; else return QSizeF(); } QString Document::pageSizeString(int page) const { if (d->m_generator) { if (d->m_generator->pagesSizeMetric() != Generator::None) { const Page *p = d->m_pagesVector.at( page ); return d->localizedSize(QSizeF(p->width(), p->height())); } } return QString(); } void Document::requestPixmaps( const QLinkedList< PixmapRequest * > & requests ) { requestPixmaps( requests, RemoveAllPrevious ); } void Document::requestPixmaps( const QLinkedList< PixmapRequest * > & requests, PixmapRequestFlags reqOptions ) { if ( requests.isEmpty() ) return; if ( !d->m_generator || d->m_closingLoop ) { // delete requests.. QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); for ( ; rIt != rEnd; ++rIt ) delete *rIt; // ..and return return; } // 1. [CLEAN STACK] remove previous requests of requesterID // FIXME This assumes all requests come from the same observer, that is true atm but not enforced anywhere DocumentObserver *requesterObserver = requests.first()->observer(); QSet< int > requestedPages; { QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); for ( ; rIt != rEnd; ++rIt ) requestedPages.insert( (*rIt)->pageNumber() ); } const bool removeAllPrevious = reqOptions & RemoveAllPrevious; d->m_pixmapRequestsMutex.lock(); QLinkedList< PixmapRequest * >::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end(); while ( sIt != sEnd ) { if ( (*sIt)->observer() == requesterObserver && ( removeAllPrevious || requestedPages.contains( (*sIt)->pageNumber() ) ) ) { // delete request and remove it from stack delete *sIt; sIt = d->m_pixmapRequestsStack.erase( sIt ); } else ++sIt; } // 2. [ADD TO STACK] add requests to stack QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); for ( ; rIt != rEnd; ++rIt ) { // set the 'page field' (see PixmapRequest) and check if it is valid PixmapRequest * request = *rIt; kDebug(OkularDebug).nospace() << "request observer=" << request->observer() << " " <width() << "x" << request->height() << "@" << request->pageNumber(); if ( d->m_pagesVector.value( request->pageNumber() ) == 0 ) { // skip requests referencing an invalid page (must not happen) delete request; continue; } request->d->mPage = d->m_pagesVector.value( request->pageNumber() ); if ( request->isTile() ) { // Change the current request rect so that only invalid tiles are // requested. Also make sure the rect is tile-aligned. NormalizedRect tilesRect; const QList tiles = request->d->tilesManager()->tilesAt( request->normalizedRect(), TilesManager::TerminalTile ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { const Tile &tile = *tIt; if ( !tile.isValid() ) { if ( tilesRect.isNull() ) tilesRect = tile.rect(); else tilesRect |= tile.rect(); } tIt++; } request->setNormalizedRect( tilesRect ); } if ( !request->asynchronous() ) request->d->mPriority = 0; // add request to the 'stack' at the right place if ( !request->priority() ) // add priority zero requests to the top of the stack d->m_pixmapRequestsStack.append( request ); else { // insert in stack sorted by priority sIt = d->m_pixmapRequestsStack.begin(); sEnd = d->m_pixmapRequestsStack.end(); while ( sIt != sEnd && (*sIt)->priority() > request->priority() ) ++sIt; d->m_pixmapRequestsStack.insert( sIt, request ); } } d->m_pixmapRequestsMutex.unlock(); // 3. [START FIRST GENERATION] if generator is ready, start a new generation, // or else (if gen is running) it will be started when the new contents will //come from generator (in requestDone()) // all handling of requests put into sendGeneratorPixmapRequest // if ( generator->canRequestPixmap() ) d->sendGeneratorPixmapRequest(); } void Document::requestTextPage( uint page ) { Page * kp = d->m_pagesVector[ page ]; if ( !d->m_generator || !kp ) return; // Memory management for TextPages d->m_generator->generateTextPage( kp ); } void DocumentPrivate::notifyAnnotationChanges( int page ) { int flags = DocumentObserver::Annotations; if ( m_annotationsNeedSaveAs ) flags |= DocumentObserver::NeedSaveAs; foreachObserverD( notifyPageChanged( page, flags ) ); } void Document::addPageAnnotation( int page, Annotation * annotation ) { // Transform annotation's base boundary rectangle into unrotated coordinates Page *p = d->m_pagesVector[page]; QTransform t = p->d->rotationMatrix(); annotation->d_ptr->baseTransform(t.inverted()); QUndoCommand *uc = new AddAnnotationCommand(this->d, annotation, page); d->m_undoStack->push(uc); } bool Document::canModifyPageAnnotation( const Annotation * annotation ) const { if ( !annotation || ( annotation->flags() & Annotation::DenyWrite ) ) return false; if ( !isAllowed(Okular::AllowNotes) ) return false; if ( ( annotation->flags() & Annotation::External ) && !d->canModifyExternalAnnotations() ) return false; switch ( annotation->subType() ) { case Annotation::AText: case Annotation::ALine: case Annotation::AGeom: case Annotation::AHighlight: case Annotation::AStamp: case Annotation::AInk: return true; default: return false; } } void Document::prepareToModifyAnnotationProperties( Annotation * annotation ) { Q_ASSERT(d->m_prevPropsOfAnnotBeingModified.isNull()); if (!d->m_prevPropsOfAnnotBeingModified.isNull()) { kError(OkularDebug) << "Error: Document::prepareToModifyAnnotationProperties has already been called since last call to Document::modifyPageAnnotationProperties"; return; } d->m_prevPropsOfAnnotBeingModified = annotation->getAnnotationPropertiesDomNode(); } void Document::modifyPageAnnotationProperties( int page, Annotation * annotation ) { Q_ASSERT(!d->m_prevPropsOfAnnotBeingModified.isNull()); if (d->m_prevPropsOfAnnotBeingModified.isNull()) { kError(OkularDebug) << "Error: Document::prepareToModifyAnnotationProperties must be called before Annotation is modified"; return; } QDomNode prevProps = d->m_prevPropsOfAnnotBeingModified; QUndoCommand *uc = new Okular::ModifyAnnotationPropertiesCommand( d, annotation, page, prevProps, annotation->getAnnotationPropertiesDomNode() ); d->m_undoStack->push( uc ); d->m_prevPropsOfAnnotBeingModified.clear(); } void Document::translatePageAnnotation(int page, Annotation* annotation, const NormalizedPoint & delta ) { int complete = (annotation->flags() & Okular::Annotation::BeingMoved) == 0; QUndoCommand *uc = new Okular::TranslateAnnotationCommand( d, annotation, page, delta, complete ); d->m_undoStack->push(uc); } void Document::editPageAnnotationContents( int page, Annotation* annotation, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ) { QString prevContents = annotation->contents(); QUndoCommand *uc = new EditAnnotationContentsCommand( d, annotation, page, newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos ); d->m_undoStack->push( uc ); } bool Document::canRemovePageAnnotation( const Annotation * annotation ) const { if ( !annotation || ( annotation->flags() & Annotation::DenyDelete ) ) return false; if ( ( annotation->flags() & Annotation::External ) && !d->canRemoveExternalAnnotations() ) return false; switch ( annotation->subType() ) { case Annotation::AText: case Annotation::ALine: case Annotation::AGeom: case Annotation::AHighlight: case Annotation::AStamp: case Annotation::AInk: return true; default: return false; } } void Document::removePageAnnotation( int page, Annotation * annotation ) { QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page); d->m_undoStack->push(uc); } void Document::removePageAnnotations( int page, const QList &annotations ) { d->m_undoStack->beginMacro(i18nc("remove a collection of annotations from the page", "remove annotations")); foreach(Annotation* annotation, annotations) { QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page); d->m_undoStack->push(uc); } d->m_undoStack->endMacro(); } bool DocumentPrivate::canAddAnnotationsNatively() const { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Addition) ) return true; return false; } bool DocumentPrivate::canModifyExternalAnnotations() const { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Modification) ) return true; return false; } bool DocumentPrivate::canRemoveExternalAnnotations() const { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Removal) ) return true; return false; } void Document::setPageTextSelection( int page, RegularAreaRect * rect, const QColor & color ) { Page * kp = d->m_pagesVector[ page ]; if ( !d->m_generator || !kp ) return; // add or remove the selection basing whether rect is null or not if ( rect ) kp->d->setTextSelections( rect, color ); else kp->d->deleteTextSelections(); // notify observers about the change foreachObserver( notifyPageChanged( page, DocumentObserver::TextSelection ) ); } bool Document::canUndo() const { return d->m_undoStack->canUndo(); } bool Document::canRedo() const { return d->m_undoStack->canRedo(); } /* REFERENCE IMPLEMENTATION: better calling setViewport from other code void Document::setNextPage() { // advance page and set viewport on observers if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 ) setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber + 1 ) ); } void Document::setPrevPage() { // go to previous page and set viewport on observers if ( (*d->m_viewportIterator).pageNumber > 0 ) setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber - 1 ) ); } */ void Document::setViewportPage( int page, DocumentObserver *excludeObserver, bool smoothMove ) { // clamp page in range [0 ... numPages-1] if ( page < 0 ) page = 0; else if ( page > (int)d->m_pagesVector.count() ) page = d->m_pagesVector.count() - 1; // make a viewport from the page and broadcast it setViewport( DocumentViewport( page ), excludeObserver, smoothMove ); } void Document::setViewport( const DocumentViewport & viewport, DocumentObserver *excludeObserver, bool smoothMove ) { if ( !viewport.isValid() ) { kDebug(OkularDebug) << "invalid viewport:" << viewport.toString(); return; } if ( viewport.pageNumber >= int(d->m_pagesVector.count()) ) { //kDebug(OkularDebug) << "viewport out of document:" << viewport.toString(); return; } // if already broadcasted, don't redo it DocumentViewport & oldViewport = *d->m_viewportIterator; // disabled by enrico on 2005-03-18 (less debug output) //if ( viewport == oldViewport ) // kDebug(OkularDebug) << "setViewport with the same viewport."; const int oldPageNumber = oldViewport.pageNumber; // set internal viewport taking care of history if ( oldViewport.pageNumber == viewport.pageNumber || !oldViewport.isValid() ) { // if page is unchanged save the viewport at current position in queue oldViewport = viewport; } else { // remove elements after viewportIterator in queue d->m_viewportHistory.erase( ++d->m_viewportIterator, d->m_viewportHistory.end() ); // keep the list to a reasonable size by removing head when needed if ( d->m_viewportHistory.count() >= OKULAR_HISTORY_MAXSTEPS ) d->m_viewportHistory.pop_front(); // add the item at the end of the queue d->m_viewportIterator = d->m_viewportHistory.insert( d->m_viewportHistory.end(), viewport ); } const int currentViewportPage = (*d->m_viewportIterator).pageNumber; const bool currentPageChanged = (oldPageNumber != currentViewportPage); // notify change to all other (different from id) observers foreach(DocumentObserver *o, d->m_observers) { if ( o != excludeObserver ) o->notifyViewportChanged( smoothMove ); if ( currentPageChanged ) o->notifyCurrentPageChanged( oldPageNumber, currentViewportPage ); } } void Document::setZoom(int factor, DocumentObserver *excludeObserver) { // notify change to all other (different from id) observers foreach(DocumentObserver *o, d->m_observers) if (o != excludeObserver) o->notifyZoom( factor ); } void Document::setPrevViewport() // restore viewport from the history { if ( d->m_viewportIterator != d->m_viewportHistory.begin() ) { const int oldViewportPage = (*d->m_viewportIterator).pageNumber; // restore previous viewport and notify it to observers --d->m_viewportIterator; foreachObserver( notifyViewportChanged( true ) ); const int currentViewportPage = (*d->m_viewportIterator).pageNumber; if (oldViewportPage != currentViewportPage) foreachObserver( notifyCurrentPageChanged( oldViewportPage, currentViewportPage ) ); } } void Document::setNextViewport() // restore next viewport from the history { QLinkedList< DocumentViewport >::const_iterator nextIterator = d->m_viewportIterator; ++nextIterator; if ( nextIterator != d->m_viewportHistory.end() ) { const int oldViewportPage = (*d->m_viewportIterator).pageNumber; // restore next viewport and notify it to observers ++d->m_viewportIterator; foreachObserver( notifyViewportChanged( true ) ); const int currentViewportPage = (*d->m_viewportIterator).pageNumber; if (oldViewportPage != currentViewportPage) foreachObserver( notifyCurrentPageChanged( oldViewportPage, currentViewportPage ) ); } } void Document::setNextDocumentViewport( const DocumentViewport & viewport ) { d->m_nextDocumentViewport = viewport; } void Document::setNextDocumentDestination( const QString &namedDestination ) { d->m_nextDocumentDestination = namedDestination; } void Document::searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity, SearchType type, bool moveViewport, const QColor & color ) { d->m_searchCancelled = false; // safety checks: don't perform searches on empty or unsearchable docs if ( !d->m_generator || !d->m_generator->hasFeature( Generator::TextExtraction ) || d->m_pagesVector.isEmpty() ) { emit searchFinished( searchID, NoMatchFound ); return; } // if searchID search not recorded, create new descriptor and init params QMap< int, RunningSearch * >::iterator searchIt = d->m_searches.find( searchID ); if ( searchIt == d->m_searches.end() ) { RunningSearch * search = new RunningSearch(); search->continueOnPage = -1; searchIt = d->m_searches.insert( searchID, search ); } RunningSearch * s = *searchIt; // update search structure bool newText = text != s->cachedString; s->cachedString = text; s->cachedType = type; s->cachedCaseSensitivity = caseSensitivity; s->cachedViewportMove = moveViewport; s->cachedColor = color; s->isCurrentlySearching = true; // global data for search QSet< int > *pagesToNotify = new QSet< int >; // remove highlights from pages and queue them for notifying changes *pagesToNotify += s->highlightedPages; foreach(int pageNumber, s->highlightedPages) d->m_pagesVector.at(pageNumber)->d->deleteHighlights( searchID ); s->highlightedPages.clear(); // set hourglass cursor QApplication::setOverrideCursor( Qt::WaitCursor ); // 1. ALLDOC - proces all document marking pages if ( type == AllDocument ) { QMap< Page *, QVector > *pageMatches = new QMap< Page *, QVector >; // search and highlight 'text' (as a solid phrase) on all pages QMetaObject::invokeMethod(this, "doContinueAllDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotify), Q_ARG(void *, pageMatches), Q_ARG(int, 0), Q_ARG(int, searchID)); } // 2. NEXTMATCH - find next matching item (or start from top) // 3. PREVMATCH - find previous matching item (or start from bottom) else if ( type == NextMatch || type == PreviousMatch ) { // find out from where to start/resume search from const bool forward = type == NextMatch; const int viewportPage = (*d->m_viewportIterator).pageNumber; const int fromStartSearchPage = forward ? 0 : d->m_pagesVector.count() - 1; int currentPage = fromStart ? fromStartSearchPage : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage); Page * lastPage = fromStart ? 0 : d->m_pagesVector[ currentPage ]; int pagesDone = 0; // continue checking last TextPage first (if it is the current page) RegularAreaRect * match = 0; if ( lastPage && lastPage->number() == s->continueOnPage ) { if ( newText ) match = lastPage->findText( searchID, text, forward ? FromTop : FromBottom, caseSensitivity ); else match = lastPage->findText( searchID, text, forward ? NextResult : PreviousResult, caseSensitivity, &s->continueOnMatch ); if ( !match ) { if (forward) currentPage++; else currentPage--; pagesDone++; } } s->pagesDone = pagesDone; DoContinueDirectionMatchSearchStruct *searchStruct = new DoContinueDirectionMatchSearchStruct(); searchStruct->pagesToNotify = pagesToNotify; searchStruct->match = match; searchStruct->currentPage = currentPage; searchStruct->searchID = searchID; QMetaObject::invokeMethod(this, "doContinueDirectionMatchSearch", Qt::QueuedConnection, Q_ARG(void *, searchStruct)); } // 4. GOOGLE* - process all document marking pages else if ( type == GoogleAll || type == GoogleAny ) { QMap< Page *, QVector< QPair > > *pageMatches = new QMap< Page *, QVector > >; const QStringList words = text.split( ' ', QString::SkipEmptyParts ); // search and highlight every word in 'text' on all pages QMetaObject::invokeMethod(this, "doContinueGooglesDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotify), Q_ARG(void *, pageMatches), Q_ARG(int, 0), Q_ARG(int, searchID), Q_ARG(QStringList, words)); } } void Document::continueSearch( int searchID ) { // check if searchID is present in runningSearches QMap< int, RunningSearch * >::const_iterator it = d->m_searches.constFind( searchID ); if ( it == d->m_searches.constEnd() ) { emit searchFinished( searchID, NoMatchFound ); return; } // start search with cached parameters from last search by searchID RunningSearch * p = *it; if ( !p->isCurrentlySearching ) searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity, p->cachedType, p->cachedViewportMove, p->cachedColor ); } void Document::continueSearch( int searchID, SearchType type ) { // check if searchID is present in runningSearches QMap< int, RunningSearch * >::const_iterator it = d->m_searches.constFind( searchID ); if ( it == d->m_searches.constEnd() ) { emit searchFinished( searchID, NoMatchFound ); return; } // start search with cached parameters from last search by searchID RunningSearch * p = *it; if ( !p->isCurrentlySearching ) searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity, type, p->cachedViewportMove, p->cachedColor ); } void Document::resetSearch( int searchID ) { // if we are closing down, don't bother doing anything if ( !d->m_generator ) return; // check if searchID is present in runningSearches QMap< int, RunningSearch * >::iterator searchIt = d->m_searches.find( searchID ); if ( searchIt == d->m_searches.end() ) return; // get previous parameters for search RunningSearch * s = *searchIt; // unhighlight pages and inform observers about that foreach(int pageNumber, s->highlightedPages) { d->m_pagesVector.at(pageNumber)->d->deleteHighlights( searchID ); foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) ); } // send the setup signal too (to update views that filter on matches) foreachObserver( notifySetup( d->m_pagesVector, 0 ) ); // remove serch from the runningSearches list and delete it d->m_searches.erase( searchIt ); delete s; } void Document::cancelSearch() { d->m_searchCancelled = true; } void Document::undo() { d->m_undoStack->undo(); } void Document::redo() { d->m_undoStack->redo(); } void Document::editFormText( int pageNumber, Okular::FormFieldText* form, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ) { QUndoCommand *uc = new EditFormTextCommand( this->d, form, pageNumber, newContents, newCursorPos, form->text(), prevCursorPos, prevAnchorPos ); d->m_undoStack->push( uc ); } void Document::editFormList( int pageNumber, FormFieldChoice* form, const QList< int > & newChoices ) { const QList< int > prevChoices = form->currentChoices(); QUndoCommand *uc = new EditFormListCommand( this->d, form, pageNumber, newChoices, prevChoices ); d->m_undoStack->push( uc ); } void Document::editFormCombo( int pageNumber, FormFieldChoice* form, const QString & newText, int newCursorPos, int prevCursorPos, int prevAnchorPos ) { QString prevText; if ( form->currentChoices().isEmpty() ) { prevText = form->editChoice(); } else { prevText = form->choices()[form->currentChoices()[0]]; } QUndoCommand *uc = new EditFormComboCommand( this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos ); d->m_undoStack->push( uc ); } void Document::editFormButtons( int pageNumber, const QList< FormFieldButton* >& formButtons, const QList< bool >& newButtonStates ) { QUndoCommand *uc = new EditFormButtonsCommand( this->d, pageNumber, formButtons, newButtonStates ); d->m_undoStack->push( uc ); } BookmarkManager * Document::bookmarkManager() const { return d->m_bookmarkManager; } QList Document::bookmarkedPageList() const { QList list; uint docPages = pages(); //pages are 0-indexed internally, but 1-indexed externally for ( uint i = 0; i < docPages; i++ ) { if ( bookmarkManager()->isBookmarked( i ) ) { list << i + 1; } } return list; } QString Document::bookmarkedPageRange() const { // Code formerly in Part::slotPrint() // range detecting QString range; uint docPages = pages(); int startId = -1; int endId = -1; for ( uint i = 0; i < docPages; ++i ) { if ( bookmarkManager()->isBookmarked( i ) ) { if ( startId < 0 ) startId = i; if ( endId < 0 ) endId = startId; else ++endId; } else if ( startId >= 0 && endId >= 0 ) { if ( !range.isEmpty() ) range += ','; if ( endId - startId > 0 ) range += QString( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 ); else range += QString::number( startId + 1 ); startId = -1; endId = -1; } } if ( startId >= 0 && endId >= 0 ) { if ( !range.isEmpty() ) range += ','; if ( endId - startId > 0 ) range += QString( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 ); else range += QString::number( startId + 1 ); } return range; } void Document::processAction( const Action * action ) { if ( !action ) return; switch( action->actionType() ) { case Action::Goto: { const GotoAction * go = static_cast< const GotoAction * >( action ); d->m_nextDocumentViewport = go->destViewport(); d->m_nextDocumentDestination = go->destinationName(); // Explanation of why d->m_nextDocumentViewport is needed: // all openRelativeFile does is launch a signal telling we // want to open another URL, the problem is that when the file is // non local, the loading is done assynchronously so you can't // do a setViewport after the if as it was because you are doing the setViewport // on the old file and when the new arrives there is no setViewport for it and // it does not show anything // first open filename if link is pointing outside this document if ( go->isExternal() && !d->openRelativeFile( go->fileName() ) ) { kWarning(OkularDebug).nospace() << "Action: Error opening '" << go->fileName() << "'."; return; } else { const DocumentViewport nextViewport = d->nextDocumentViewport(); // skip local links that point to nowhere (broken ones) if ( !nextViewport.isValid() ) return; setViewport( nextViewport, 0, true ); d->m_nextDocumentViewport = DocumentViewport(); d->m_nextDocumentDestination = QString(); } } break; case Action::Execute: { const ExecuteAction * exe = static_cast< const ExecuteAction * >( action ); QString fileName = exe->fileName(); if ( fileName.endsWith( ".pdf" ) || fileName.endsWith( ".PDF" ) ) { d->openRelativeFile( fileName ); return; } // Albert: the only pdf i have that has that kind of link don't define // an application and use the fileName as the file to open fileName = d->giveAbsolutePath( fileName ); KMimeType::Ptr mime = KMimeType::findByPath( fileName ); // Check executables if ( KRun::isExecutableFile( fileName, mime->name() ) ) { // Don't have any pdf that uses this code path, just a guess on how it should work if ( !exe->parameters().isEmpty() ) { fileName = d->giveAbsolutePath( exe->parameters() ); mime = KMimeType::findByPath( fileName ); if ( KRun::isExecutableFile( fileName, mime->name() ) ) { // this case is a link pointing to an executable with a parameter // that also is an executable, possibly a hand-crafted pdf KMessageBox::information( d->m_widget, i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that.") ); return; } } else { // this case is a link pointing to an executable with no parameters // core developers find unacceptable executing it even after asking the user KMessageBox::information( d->m_widget, i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that.") ); return; } } KService::Ptr ptr = KMimeTypeTrader::self()->preferredService( mime->name(), "Application" ); if ( ptr ) { KUrl::List lst; lst.append( fileName ); KRun::run( *ptr, lst, 0 ); } else KMessageBox::information( d->m_widget, i18n( "No application found for opening file of mimetype %1.", mime->name() ) ); } break; case Action::DocAction: { const DocumentAction * docaction = static_cast< const DocumentAction * >( action ); switch( docaction->documentActionType() ) { case DocumentAction::PageFirst: setViewportPage( 0 ); break; case DocumentAction::PagePrev: if ( (*d->m_viewportIterator).pageNumber > 0 ) setViewportPage( (*d->m_viewportIterator).pageNumber - 1 ); break; case DocumentAction::PageNext: if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 ) setViewportPage( (*d->m_viewportIterator).pageNumber + 1 ); break; case DocumentAction::PageLast: setViewportPage( d->m_pagesVector.count() - 1 ); break; case DocumentAction::HistoryBack: setPrevViewport(); break; case DocumentAction::HistoryForward: setNextViewport(); break; case DocumentAction::Quit: emit quit(); break; case DocumentAction::Presentation: emit linkPresentation(); break; case DocumentAction::EndPresentation: emit linkEndPresentation(); break; case DocumentAction::Find: emit linkFind(); break; case DocumentAction::GoToPage: emit linkGoToPage(); break; case DocumentAction::Close: emit close(); break; } } break; case Action::Browse: { const BrowseAction * browse = static_cast< const BrowseAction * >( action ); QString lilySource; int lilyRow = 0, lilyCol = 0; // if the url is a mailto one, invoke mailer if ( browse->url().startsWith( "mailto:", Qt::CaseInsensitive ) ) KToolInvocation::invokeMailer( browse->url() ); else if ( extractLilyPondSourceReference( browse->url(), &lilySource, &lilyRow, &lilyCol ) ) { const SourceReference ref( lilySource, lilyRow, lilyCol ); processSourceReference( &ref ); } else { QString url = browse->url(); // fix for #100366, documents with relative links that are the form of http:foo.pdf if (url.indexOf("http:") == 0 && url.indexOf("http://") == -1 && url.right(4) == ".pdf") { d->openRelativeFile(url.mid(5)); return; } KUrl realUrl = KUrl( url ); // handle documents with relative path if ( d->m_url.isValid() ) { realUrl = KUrl( d->m_url.upUrl(), url ); } // Albert: this is not a leak! new KRun( realUrl, d->m_widget ); } } break; case Action::Sound: { const SoundAction * linksound = static_cast< const SoundAction * >( action ); AudioPlayer::instance()->playSound( linksound->sound(), linksound ); } break; case Action::Script: { const ScriptAction * linkscript = static_cast< const ScriptAction * >( action ); if ( !d->m_scripter ) d->m_scripter = new Scripter( d ); d->m_scripter->execute( linkscript->scriptType(), linkscript->script() ); } break; case Action::Movie: emit processMovieAction( static_cast< const MovieAction * >( action ) ); break; case Action::Rendition: { const RenditionAction * linkrendition = static_cast< const RenditionAction * >( action ); if ( !linkrendition->script().isEmpty() ) { if ( !d->m_scripter ) d->m_scripter = new Scripter( d ); d->m_scripter->execute( linkrendition->scriptType(), linkrendition->script() ); } else { emit processRenditionAction( static_cast< const RenditionAction * >( action ) ); } } break; } } void Document::processSourceReference( const SourceReference * ref ) { if ( !ref ) return; const KUrl url( d->giveAbsolutePath( ref->fileName() ) ); if ( !url.isLocalFile() ) { kDebug(OkularDebug) << url.url() << "is not a local file."; return; } const QString absFileName = url.toLocalFile(); if ( !QFile::exists( absFileName ) ) { kDebug(OkularDebug) << "No such file:" << absFileName; return; } bool handled = false; emit sourceReferenceActivated(absFileName, ref->row(), ref->column(), &handled); if(handled) { return; } static QHash< int, QString > editors; // init the editors table if empty (on first run, usually) if ( editors.isEmpty() ) { editors = buildEditorsMap(); } QHash< int, QString >::const_iterator it = editors.constFind( SettingsCore::externalEditor() ); QString p; if ( it != editors.constEnd() ) p = *it; else p = SettingsCore::externalEditorCommand(); // custom editor not yet configured if ( p.isEmpty() ) return; // manually append the %f placeholder if not specified if ( p.indexOf( QLatin1String( "%f" ) ) == -1 ) p.append( QLatin1String( " %f" ) ); // replacing the placeholders QHash< QChar, QString > map; map.insert( 'f', absFileName ); map.insert( 'c', QString::number( ref->column() ) ); map.insert( 'l', QString::number( ref->row() ) ); const QString cmd = KMacroExpander::expandMacrosShellQuote( p, map ); if ( cmd.isEmpty() ) return; const QStringList args = KShell::splitArgs( cmd ); if ( args.isEmpty() ) return; KProcess::startDetached( args ); } const SourceReference * Document::dynamicSourceReference( int pageNr, double absX, double absY ) { const SourceReference * ref = 0; if ( d->m_generator ) { QMetaObject::invokeMethod( d->m_generator, "dynamicSourceReference", Qt::DirectConnection, Q_RETURN_ARG(const Okular::SourceReference*, ref), Q_ARG(int, pageNr), Q_ARG(double, absX), Q_ARG(double, absY) ); } return ref; } Document::PrintingType Document::printingSupport() const { if ( d->m_generator ) { if ( d->m_generator->hasFeature( Generator::PrintNative ) ) { return NativePrinting; } #ifndef Q_OS_WIN if ( d->m_generator->hasFeature( Generator::PrintPostscript ) ) { return PostscriptPrinting; } #endif } return NoPrinting; } bool Document::supportsPrintToFile() const { return d->m_generator ? d->m_generator->hasFeature( Generator::PrintToFile ) : false; } bool Document::print( QPrinter &printer ) { return d->m_generator ? d->m_generator->print( printer ) : false; } QString Document::printError() const { Okular::Generator::PrintError err = Generator::UnknownPrintError; if ( d->m_generator ) { QMetaObject::invokeMethod( d->m_generator, "printError", Qt::DirectConnection, Q_RETURN_ARG(Okular::Generator::PrintError, err) ); } Q_ASSERT( err != Generator::NoPrintError ); switch ( err ) { case Generator::TemporaryFileOpenPrintError: return i18n( "Could not open a temporary file" ); case Generator::FileConversionPrintError: return i18n( "Print conversion failed" ); case Generator::PrintingProcessCrashPrintError: return i18n( "Printing process crashed" ); case Generator::PrintingProcessStartPrintError: return i18n( "Printing process could not start" ); case Generator::PrintToFilePrintError: return i18n( "Printing to file failed" ); case Generator::InvalidPrinterStatePrintError: return i18n( "Printer was in invalid state" ); case Generator::UnableToFindFilePrintError: return i18n( "Unable to find file to print" ); case Generator::NoFileToPrintError: return i18n( "There was no file to print" ); case Generator::NoBinaryToPrintError: return i18n( "Could not find a suitable binary for printing. Make sure CUPS lpr binary is available" ); case Generator::InvalidPageSizePrintError: return i18n( "The page print size is invalid" ); case Generator::NoPrintError: return QString(); case Generator::UnknownPrintError: return QString(); } return QString(); } QWidget* Document::printConfigurationWidget() const { if ( d->m_generator ) { PrintInterface * iface = qobject_cast< Okular::PrintInterface * >( d->m_generator ); return iface ? iface->printConfigurationWidget() : 0; } else return 0; } void Document::fillConfigDialog( KConfigDialog * dialog ) { if ( !dialog ) return; // ensure that we have all the generators with settings loaded QString constraint( "([X-KDE-Priority] > 0) and (exist Library) and ([X-KDE-okularHasInternalSettings])" ); KService::List offers = KServiceTypeTrader::self()->query( "okular/Generator", constraint ); d->loadServiceList( offers ); bool pagesAdded = false; QHash< QString, GeneratorInfo >::iterator it = d->m_loadedGenerators.begin(); QHash< QString, GeneratorInfo >::iterator itEnd = d->m_loadedGenerators.end(); for ( ; it != itEnd; ++it ) { Okular::ConfigInterface * iface = d->generatorConfig( it.value() ); if ( iface ) { iface->addPages( dialog ); pagesAdded = true; if ( !it.value().catalogName.isEmpty() ) KGlobal::locale()->insertCatalog( it.value().catalogName ); } } if ( pagesAdded ) { connect( dialog, SIGNAL(settingsChanged(QString)), this, SLOT(slotGeneratorConfigChanged(QString)) ); } } int Document::configurableGenerators() const { QString constraint( "([X-KDE-Priority] > 0) and (exist Library) and ([X-KDE-okularHasInternalSettings])" ); KService::List offers = KServiceTypeTrader::self()->query( "okular/Generator", constraint ); return offers.count(); } QStringList Document::supportedMimeTypes() const { if ( !d->m_supportedMimeTypes.isEmpty() ) return d->m_supportedMimeTypes; QString constraint( "(Library == 'okularpart')" ); QLatin1String basePartService( "KParts/ReadOnlyPart" ); KService::List offers = KServiceTypeTrader::self()->query( basePartService, constraint ); KService::List::ConstIterator it = offers.constBegin(), itEnd = offers.constEnd(); for ( ; it != itEnd; ++it ) { KService::Ptr service = *it; QStringList mimeTypes = service->serviceTypes(); foreach ( const QString& mimeType, mimeTypes ) if ( mimeType != basePartService ) d->m_supportedMimeTypes.append( mimeType ); } return d->m_supportedMimeTypes; } const KComponentData* Document::componentData() const { if ( !d->m_generator ) return 0; QHash< QString, GeneratorInfo >::const_iterator genIt = d->m_loadedGenerators.constFind( d->m_generatorName ); Q_ASSERT( genIt != d->m_loadedGenerators.constEnd() ); const KComponentData* kcd = &genIt.value().data; // empty about data if ( kcd->isValid() && kcd->aboutData() && kcd->aboutData()->programName().isEmpty() ) return 0; return kcd; } +bool Document::canSwapBackingFile() const +{ + if ( !d->m_generator ) + return false; + Q_ASSERT( !d->m_generatorName.isEmpty() ); + + QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); + Q_ASSERT( genIt != d->m_loadedGenerators.end() ); + + return genIt->generator->hasFeature( Generator::SwapBackingFile ); +} + +bool Document::swapBackingFile( const QString &newFileName, const KUrl & url ) +{ + if ( !d->m_generator ) + return false; + Q_ASSERT( !d->m_generatorName.isEmpty() ); + + QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); + Q_ASSERT( genIt != d->m_loadedGenerators.end() ); + + if ( !genIt->generator->hasFeature( Generator::SwapBackingFile ) ) + return false; + + kDebug(OkularDebug) << "Swapping backing file to" << newFileName; + if (genIt->generator->swapBackingFile( newFileName )) + { + d->m_url = url; + return true; + } + else + { + return false; + } +} + +bool Document::swapBackingFileArchive( const QString &newFileName, const KUrl & url ) +{ + if ( !d->m_generator ) + return false; + Q_ASSERT( !d->m_generatorName.isEmpty() ); + + QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); + Q_ASSERT( genIt != d->m_loadedGenerators.end() ); + + if ( !genIt->generator->hasFeature( Generator::SwapBackingFile ) ) + return false; + + kDebug(OkularDebug) << "Swapping backing archive to" << newFileName; + + ArchiveData *newArchive = DocumentPrivate::unpackDocumentArchive( newFileName ); + if ( !newArchive ) + return false; + + const QString tempFileName = newArchive->document.fileName(); + + kDebug(OkularDebug) << "Swapping backing file to" << tempFileName; + if (genIt->generator->swapBackingFile( tempFileName )) + { + delete d->m_archiveData; + d->m_archiveData = newArchive; + d->m_url = url; + return true; + } + else + { + return false; + } +} + bool Document::canSaveChanges() const { if ( !d->m_generator ) return false; Q_ASSERT( !d->m_generatorName.isEmpty() ); QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); Q_ASSERT( genIt != d->m_loadedGenerators.end() ); SaveInterface* saveIface = d->generatorSave( genIt.value() ); if ( !saveIface ) return false; return saveIface->supportsOption( SaveInterface::SaveChanges ); } bool Document::canSaveChanges( SaveCapability cap ) const { switch ( cap ) { case SaveFormsCapability: /* Assume that if the generator supports saving, forms can be saved. * We have no means to actually query the generator at the moment * TODO: Add some method to query the generator in SaveInterface */ return canSaveChanges(); case SaveAnnotationsCapability: return d->canAddAnnotationsNatively(); } return false; } bool Document::saveChanges( const QString &fileName ) { QString errorText; return saveChanges( fileName, &errorText ); } bool Document::saveChanges( const QString &fileName, QString *errorText ) { if ( !d->m_generator || fileName.isEmpty() ) return false; Q_ASSERT( !d->m_generatorName.isEmpty() ); QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); Q_ASSERT( genIt != d->m_loadedGenerators.end() ); SaveInterface* saveIface = d->generatorSave( genIt.value() ); if ( !saveIface || !saveIface->supportsOption( SaveInterface::SaveChanges ) ) return false; return saveIface->save( fileName, SaveInterface::SaveChanges, errorText ); } void Document::registerView( View *view ) { if ( !view ) return; Document *viewDoc = view->viewDocument(); if ( viewDoc ) { // check if already registered for this document if ( viewDoc == this ) return; viewDoc->unregisterView( view ); } d->m_views.insert( view ); view->d_func()->document = d; } void Document::unregisterView( View *view ) { if ( !view ) return; Document *viewDoc = view->viewDocument(); if ( !viewDoc || viewDoc != this ) return; view->d_func()->document = 0; d->m_views.remove( view ); } QByteArray Document::fontData(const FontInfo &font) const { QByteArray result; if (d->m_generator) { QMetaObject::invokeMethod(d->m_generator, "requestFontData", Qt::DirectConnection, Q_ARG(Okular::FontInfo, font), Q_ARG(QByteArray *, &result)); } return result; } ArchiveData *DocumentPrivate::unpackDocumentArchive( const QString &archivePath ) { const KMimeType::Ptr mime = KMimeType::findByPath( archivePath, 0, false /* content too */ ); if ( !mime->is( "application/vnd.kde.okular-archive" ) ) return 0; KZip okularArchive( archivePath ); if ( !okularArchive.open( QIODevice::ReadOnly ) ) return 0; const KArchiveDirectory * mainDir = okularArchive.directory(); const KArchiveEntry * mainEntry = mainDir->entry( "content.xml" ); if ( !mainEntry || !mainEntry->isFile() ) return 0; std::auto_ptr< QIODevice > mainEntryDevice( static_cast< const KZipFileEntry * >( mainEntry )->createDevice() ); QDomDocument doc; if ( !doc.setContent( mainEntryDevice.get() ) ) return 0; mainEntryDevice.reset(); QDomElement root = doc.documentElement(); if ( root.tagName() != "OkularArchive" ) return 0; QString documentFileName; QString metadataFileName; QDomElement el = root.firstChild().toElement(); for ( ; !el.isNull(); el = el.nextSibling().toElement() ) { if ( el.tagName() == "Files" ) { QDomElement fileEl = el.firstChild().toElement(); for ( ; !fileEl.isNull(); fileEl = fileEl.nextSibling().toElement() ) { if ( fileEl.tagName() == "DocumentFileName" ) documentFileName = fileEl.text(); else if ( fileEl.tagName() == "MetadataFileName" ) metadataFileName = fileEl.text(); } } } if ( documentFileName.isEmpty() ) return 0; const KArchiveEntry * docEntry = mainDir->entry( documentFileName ); if ( !docEntry || !docEntry->isFile() ) return 0; std::auto_ptr< ArchiveData > archiveData( new ArchiveData() ); const int dotPos = documentFileName.indexOf( '.' ); if ( dotPos != -1 ) archiveData->document.setSuffix( documentFileName.mid( dotPos ) ); if ( !archiveData->document.open() ) return 0; archiveData->originalFileName = documentFileName; { std::auto_ptr< QIODevice > docEntryDevice( static_cast< const KZipFileEntry * >( docEntry )->createDevice() ); copyQIODevice( docEntryDevice.get(), &archiveData->document ); archiveData->document.close(); } const KArchiveEntry * metadataEntry = mainDir->entry( metadataFileName ); if ( metadataEntry && metadataEntry->isFile() ) { std::auto_ptr< QIODevice > metadataEntryDevice( static_cast< const KZipFileEntry * >( metadataEntry )->createDevice() ); archiveData->metadataFile.setSuffix( ".xml" ); if ( archiveData->metadataFile.open() ) { copyQIODevice( metadataEntryDevice.get(), &archiveData->metadataFile ); archiveData->metadataFile.close(); } } return archiveData.release(); } Document::OpenResult Document::openDocumentArchive( const QString & docFile, const KUrl & url, const QString & password ) { d->m_archiveData = DocumentPrivate::unpackDocumentArchive( docFile ); if ( !d->m_archiveData ) return OpenError; const QString tempFileName = d->m_archiveData->document.fileName(); const KMimeType::Ptr docMime = KMimeType::findByPath( tempFileName, 0, true /* local file */ ); const OpenResult ret = openDocument( tempFileName, url, docMime, password ); if ( ret != OpenSuccess ) { delete d->m_archiveData; d->m_archiveData = 0; } return ret; } bool Document::saveDocumentArchive( const QString &fileName ) { if ( !d->m_generator ) return false; /* If we opened an archive, use the name of original file (eg foo.pdf) * instead of the archive's one (eg foo.okular) */ QString docFileName = d->m_archiveData ? d->m_archiveData->originalFileName : d->m_url.fileName(); if ( docFileName == QLatin1String( "-" ) ) return false; QString docPath = d->m_docFileName; const QFileInfo fi( docPath ); if ( fi.isSymLink() ) docPath = fi.symLinkTarget(); KZip okularArchive( fileName ); if ( !okularArchive.open( QIODevice::WriteOnly ) ) return false; const KUser user; #ifndef Q_OS_WIN const KUserGroup userGroup( user.gid() ); #else const KUserGroup userGroup( QString( "" ) ); #endif QDomDocument contentDoc( "OkularArchive" ); QDomProcessingInstruction xmlPi = contentDoc.createProcessingInstruction( QString::fromLatin1( "xml" ), QString::fromLatin1( "version=\"1.0\" encoding=\"utf-8\"" ) ); contentDoc.appendChild( xmlPi ); QDomElement root = contentDoc.createElement( "OkularArchive" ); contentDoc.appendChild( root ); QDomElement filesNode = contentDoc.createElement( "Files" ); root.appendChild( filesNode ); QDomElement fileNameNode = contentDoc.createElement( "DocumentFileName" ); filesNode.appendChild( fileNameNode ); fileNameNode.appendChild( contentDoc.createTextNode( docFileName ) ); QDomElement metadataFileNameNode = contentDoc.createElement( "MetadataFileName" ); filesNode.appendChild( metadataFileNameNode ); metadataFileNameNode.appendChild( contentDoc.createTextNode( "metadata.xml" ) ); // If the generator can save annotations natively, do it KTemporaryFile modifiedFile; bool annotationsSavedNatively = false; if ( d->canAddAnnotationsNatively() ) { if ( !modifiedFile.open() ) return false; modifiedFile.close(); // We're only interested in the file name QString errorText; if ( saveChanges( modifiedFile.fileName(), &errorText ) ) { docPath = modifiedFile.fileName(); // Save this instead of the original file annotationsSavedNatively = true; } else { kWarning(OkularDebug) << "saveChanges failed: " << errorText; kDebug(OkularDebug) << "Falling back to saving a copy of the original file"; } } KTemporaryFile metadataFile; PageItems saveWhat = annotationsSavedNatively ? None : AnnotationPageItems; if ( !d->savePageDocumentInfo( &metadataFile, saveWhat ) ) return false; const QByteArray contentDocXml = contentDoc.toByteArray(); okularArchive.writeFile( "content.xml", user.loginName(), userGroup.name(), contentDocXml.constData(), contentDocXml.length() ); okularArchive.addLocalFile( docPath, docFileName ); okularArchive.addLocalFile( metadataFile.fileName(), "metadata.xml" ); if ( !okularArchive.close() ) return false; return true; } bool Document::extractArchivedFile( const QString &destFileName ) { if ( !d->m_archiveData ) return false; // Remove existing file, if present (QFile::copy doesn't overwrite by itself) QFile::remove( destFileName ); return d->m_archiveData->document.copy( destFileName ); } QPrinter::Orientation Document::orientation() const { double width, height; int landscape, portrait; const Okular::Page *currentPage; // if some pages are landscape and others are not, the most common wins, as // QPrinter does not accept a per-page setting landscape = 0; portrait = 0; for (uint i = 0; i < pages(); i++) { currentPage = page(i); width = currentPage->width(); height = currentPage->height(); if (currentPage->orientation() == Okular::Rotation90 || currentPage->orientation() == Okular::Rotation270) qSwap(width, height); if (width > height) landscape++; else portrait++; } return (landscape > portrait) ? QPrinter::Landscape : QPrinter::Portrait; } void Document::setAnnotationEditingEnabled( bool enable ) { d->m_annotationEditingEnabled = enable; foreachObserver( notifySetup( d->m_pagesVector, 0 ) ); } void Document::walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const { if (d->m_generator) { d->m_generator->walletDataForFile( fileName, walletName, walletFolder, walletKey ); } } void DocumentPrivate::requestDone( PixmapRequest * req ) { if ( !req ) return; if ( !m_generator || m_closingLoop ) { m_pixmapRequestsMutex.lock(); m_executingPixmapRequests.removeAll( req ); m_pixmapRequestsMutex.unlock(); delete req; if ( m_closingLoop ) m_closingLoop->exit(); return; } #ifndef NDEBUG if ( !m_generator->canGeneratePixmap() ) kDebug(OkularDebug) << "requestDone with generator not in READY state."; #endif // [MEM] 1.1 find and remove a previous entry for the same page and id QLinkedList< AllocatedPixmap * >::iterator aIt = m_allocatedPixmaps.begin(); QLinkedList< AllocatedPixmap * >::iterator aEnd = m_allocatedPixmaps.end(); for ( ; aIt != aEnd; ++aIt ) if ( (*aIt)->page == req->pageNumber() && (*aIt)->observer == req->observer() ) { AllocatedPixmap * p = *aIt; m_allocatedPixmaps.erase( aIt ); m_allocatedPixmapsTotalMemory -= p->memory; delete p; break; } DocumentObserver *observer = req->observer(); if ( m_observers.contains(observer) ) { // [MEM] 1.2 append memory allocation descriptor to the FIFO qulonglong memoryBytes = 0; const TilesManager *tm = req->d->tilesManager(); if ( tm ) memoryBytes = tm->totalMemory(); else memoryBytes = 4 * req->width() * req->height(); AllocatedPixmap * memoryPage = new AllocatedPixmap( req->observer(), req->pageNumber(), memoryBytes ); m_allocatedPixmaps.append( memoryPage ); m_allocatedPixmapsTotalMemory += memoryBytes; // 2. notify an observer that its pixmap changed observer->notifyPageChanged( req->pageNumber(), DocumentObserver::Pixmap ); } #ifndef NDEBUG else kWarning(OkularDebug) << "Receiving a done request for the defunct observer" << observer; #endif // 3. delete request m_pixmapRequestsMutex.lock(); m_executingPixmapRequests.removeAll( req ); m_pixmapRequestsMutex.unlock(); delete req; // 4. start a new generation if some is pending m_pixmapRequestsMutex.lock(); bool hasPixmaps = !m_pixmapRequestsStack.isEmpty(); m_pixmapRequestsMutex.unlock(); if ( hasPixmaps ) sendGeneratorPixmapRequest(); } void DocumentPrivate::setPageBoundingBox( int page, const NormalizedRect& boundingBox ) { Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; if ( kp->boundingBox() == boundingBox ) return; kp->setBoundingBox( boundingBox ); // notify observers about the change foreachObserverD( notifyPageChanged( page, DocumentObserver::BoundingBox ) ); // TODO: For generators that generate the bbox by pixmap scanning, if the first generated pixmap is very small, the bounding box will forever be inaccurate. // TODO: Crop computation should also consider annotations, actions, etc. to make sure they're not cropped away. // TODO: Help compute bounding box for generators that create a QPixmap without a QImage, like text and plucker. // TODO: Don't compute the bounding box if no one needs it (e.g., Trim Borders is off). } void DocumentPrivate::calculateMaxTextPages() { int multipliers = qMax(1, qRound(getTotalMemory() / 536870912.0)); // 512 MB switch (SettingsCore::memoryLevel()) { case SettingsCore::EnumMemoryLevel::Low: m_maxAllocatedTextPages = multipliers * 2; break; case SettingsCore::EnumMemoryLevel::Normal: m_maxAllocatedTextPages = multipliers * 50; break; case SettingsCore::EnumMemoryLevel::Aggressive: m_maxAllocatedTextPages = multipliers * 250; break; case SettingsCore::EnumMemoryLevel::Greedy: m_maxAllocatedTextPages = multipliers * 1250; break; } } void DocumentPrivate::textGenerationDone( Page *page ) { if ( !m_generator || m_closingLoop ) return; // 1. If we reached the cache limit, delete the first text page from the fifo if (m_allocatedTextPagesFifo.size() == m_maxAllocatedTextPages) { int pageToKick = m_allocatedTextPagesFifo.takeFirst(); if (pageToKick != page->number()) // this should never happen but better be safe than sorry { m_pagesVector.at(pageToKick)->setTextPage( 0 ); // deletes the textpage } } // 2. Add the page to the fifo of generated text pages m_allocatedTextPagesFifo.append( page->number() ); } void Document::setRotation( int r ) { d->setRotationInternal( r, true ); } void DocumentPrivate::setRotationInternal( int r, bool notify ) { Rotation rotation = (Rotation)r; if ( !m_generator || ( m_rotation == rotation ) ) return; // tell the pages to rotate QVector< Okular::Page * >::const_iterator pIt = m_pagesVector.constBegin(); QVector< Okular::Page * >::const_iterator pEnd = m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->rotateAt( rotation ); if ( notify ) { // notify the generator that the current rotation has changed m_generator->rotationChanged( rotation, m_rotation ); } // set the new rotation m_rotation = rotation; if ( notify ) { foreachObserverD( notifySetup( m_pagesVector, DocumentObserver::NewLayoutForPages ) ); foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap | DocumentObserver::Highlights | DocumentObserver::Annotations ) ); } kDebug(OkularDebug) << "Rotated:" << r; } void Document::setPageSize( const PageSize &size ) { if ( !d->m_generator || !d->m_generator->hasFeature( Generator::PageSizes ) ) return; if ( d->m_pageSizes.isEmpty() ) d->m_pageSizes = d->m_generator->pageSizes(); int sizeid = d->m_pageSizes.indexOf( size ); if ( sizeid == -1 ) return; // tell the pages to change size QVector< Okular::Page * >::const_iterator pIt = d->m_pagesVector.constBegin(); QVector< Okular::Page * >::const_iterator pEnd = d->m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->changeSize( size ); // clear 'memory allocation' descriptors qDeleteAll( d->m_allocatedPixmaps ); d->m_allocatedPixmaps.clear(); d->m_allocatedPixmapsTotalMemory = 0; // notify the generator that the current page size has changed d->m_generator->pageSizeChanged( size, d->m_pageSize ); // set the new page size d->m_pageSize = size; foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::NewLayoutForPages ) ); foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap | DocumentObserver::Highlights ) ); kDebug(OkularDebug) << "New PageSize id:" << sizeid; } /** DocumentViewport **/ DocumentViewport::DocumentViewport( int n ) : pageNumber( n ) { // default settings rePos.enabled = false; rePos.normalizedX = 0.5; rePos.normalizedY = 0.0; rePos.pos = Center; autoFit.enabled = false; autoFit.width = false; autoFit.height = false; } DocumentViewport::DocumentViewport( const QString & xmlDesc ) : pageNumber( -1 ) { // default settings (maybe overridden below) rePos.enabled = false; rePos.normalizedX = 0.5; rePos.normalizedY = 0.0; rePos.pos = Center; autoFit.enabled = false; autoFit.width = false; autoFit.height = false; // check for string presence if ( xmlDesc.isEmpty() ) return; // decode the string bool ok; int field = 0; QString token = xmlDesc.section( ';', field, field ); while ( !token.isEmpty() ) { // decode the current token if ( field == 0 ) { pageNumber = token.toInt( &ok ); if ( !ok ) return; } else if ( token.startsWith( "C1" ) ) { rePos.enabled = true; rePos.normalizedX = token.section( ':', 1, 1 ).toDouble(); rePos.normalizedY = token.section( ':', 2, 2 ).toDouble(); rePos.pos = Center; } else if ( token.startsWith( "C2" ) ) { rePos.enabled = true; rePos.normalizedX = token.section( ':', 1, 1 ).toDouble(); rePos.normalizedY = token.section( ':', 2, 2 ).toDouble(); if (token.section( ':', 3, 3 ).toInt() == 1) rePos.pos = Center; else rePos.pos = TopLeft; } else if ( token.startsWith( "AF1" ) ) { autoFit.enabled = true; autoFit.width = token.section( ':', 1, 1 ) == "T"; autoFit.height = token.section( ':', 2, 2 ) == "T"; } // proceed tokenizing string field++; token = xmlDesc.section( ';', field, field ); } } QString DocumentViewport::toString() const { // start string with page number QString s = QString::number( pageNumber ); // if has center coordinates, save them on string if ( rePos.enabled ) s += QString( ";C2:" ) + QString::number( rePos.normalizedX ) + ':' + QString::number( rePos.normalizedY ) + ':' + QString::number( rePos.pos ); // if has autofit enabled, save its state on string if ( autoFit.enabled ) s += QString( ";AF1:" ) + (autoFit.width ? "T" : "F") + ':' + (autoFit.height ? "T" : "F"); return s; } bool DocumentViewport::isValid() const { return pageNumber >= 0; } bool DocumentViewport::operator==( const DocumentViewport & vp ) const { bool equal = ( pageNumber == vp.pageNumber ) && ( rePos.enabled == vp.rePos.enabled ) && ( autoFit.enabled == vp.autoFit.enabled ); if ( !equal ) return false; if ( rePos.enabled && (( rePos.normalizedX != vp.rePos.normalizedX) || ( rePos.normalizedY != vp.rePos.normalizedY ) || rePos.pos != vp.rePos.pos) ) return false; if ( autoFit.enabled && (( autoFit.width != vp.autoFit.width ) || ( autoFit.height != vp.autoFit.height )) ) return false; return true; } bool DocumentViewport::operator<( const DocumentViewport & vp ) const { // TODO: Check autoFit and Position if ( pageNumber != vp.pageNumber ) return pageNumber < vp.pageNumber; if ( !rePos.enabled && vp.rePos.enabled ) return true; if ( !vp.rePos.enabled ) return false; if ( rePos.normalizedY != vp.rePos.normalizedY ) return rePos.normalizedY < vp.rePos.normalizedY; return rePos.normalizedX < vp.rePos.normalizedX; } /** DocumentInfo **/ DocumentInfo::DocumentInfo() : QDomDocument( "DocumentInformation" ) { QDomElement docElement = createElement( "DocumentInfo" ); appendChild( docElement ); } void DocumentInfo::set( const QString &key, const QString &value, const QString &title ) { QDomElement docElement = documentElement(); QDomElement element; // check whether key already exists QDomNodeList list = docElement.elementsByTagName( key ); if ( list.count() > 0 ) element = list.item( 0 ).toElement(); else element = createElement( key ); element.setAttribute( "value", value ); element.setAttribute( "title", title ); if ( list.count() == 0 ) docElement.appendChild( element ); } void DocumentInfo::set( Key key, const QString &value ) { const QString keyString = getKeyString( key ); if ( !keyString.isEmpty() ) set( keyString, value, getKeyTitle( key ) ); else kWarning(OkularDebug) << "Invalid key passed"; } QString DocumentInfo::get( const QString &key ) const { const QDomElement docElement = documentElement(); // check whether key already exists const QDomNodeList list = docElement.elementsByTagName( key ); if ( list.count() > 0 ) return list.item( 0 ).toElement().attribute( "value" ); else return QString(); } QString DocumentInfo::getKeyString( Key key ) //const { switch ( key ) { case Title: return "title"; break; case Subject: return "subject"; break; case Description: return "description"; break; case Author: return "author"; break; case Creator: return "creator"; break; case Producer: return "producer"; break; case Copyright: return "copyright"; break; case Pages: return "pages"; break; case CreationDate: return "creationDate"; break; case ModificationDate: return "modificationDate"; break; case MimeType: return "mimeType"; break; case Category: return "category"; break; case Keywords: return "keywords"; break; case FilePath: return "filePath"; break; case DocumentSize: return "documentSize"; break; case PagesSize: return "pageSize"; break; default: return QString(); break; } } QString DocumentInfo::getKeyTitle( Key key ) //const { switch ( key ) { case Title: return i18n( "Title" ); break; case Subject: return i18n( "Subject" ); break; case Description: return i18n( "Description" ); break; case Author: return i18n( "Author" ); break; case Creator: return i18n( "Creator" ); break; case Producer: return i18n( "Producer" ); break; case Copyright: return i18n( "Copyright" ); break; case Pages: return i18n( "Pages" ); break; case CreationDate: return i18n( "Created" ); break; case ModificationDate: return i18n( "Modified" ); break; case MimeType: return i18n( "Mime Type" ); break; case Category: return i18n( "Category" ); break; case Keywords: return i18n( "Keywords" ); break; case FilePath: return i18n( "File Path" ); break; case DocumentSize: return i18n( "File Size" ); break; case PagesSize: return i18n("Page Size"); break; default: return QString(); break; } } /** DocumentSynopsis **/ DocumentSynopsis::DocumentSynopsis() : QDomDocument( "DocumentSynopsis" ) { // void implementation, only subclassed for naming } DocumentSynopsis::DocumentSynopsis( const QDomDocument &document ) : QDomDocument( document ) { } /** EmbeddedFile **/ EmbeddedFile::EmbeddedFile() { } EmbeddedFile::~EmbeddedFile() { } VisiblePageRect::VisiblePageRect( int page, const NormalizedRect &rectangle ) : pageNumber( page ), rect( rectangle ) { } #undef foreachObserver #undef foreachObserverD #include "document.moc" /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/document.h b/core/document.h index 585004960..58c2c4b45 100644 --- a/core/document.h +++ b/core/document.h @@ -1,1283 +1,1318 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2004-2008 by Albert Astals Cid * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef _OKULAR_DOCUMENT_H_ #define _OKULAR_DOCUMENT_H_ #include "okular_export.h" #include "area.h" #include "global.h" #include "pagesize.h" #include #include #include #include #include #include class QPrintDialog; class KComponentData; class KBookmark; class KConfigDialog; class KXMLGUIClient; class KUrl; class DocumentItem; namespace Okular { class Annotation; class BookmarkManager; class DocumentInfo; class DocumentObserver; class DocumentPrivate; class DocumentSynopsis; class DocumentViewport; class EmbeddedFile; class ExportFormat; class FontInfo; class FormFieldText; class FormFieldButton; class FormFieldChoice; class Generator; class Action; class MovieAction; class Page; class PixmapRequest; class RenditionAction; class SourceReference; class View; class VisiblePageRect; /** IDs for seaches. Globally defined here. **/ #define PART_SEARCH_ID 1 #define PAGEVIEW_SEARCH_ID 2 #define SW_SEARCH_ID 3 #define PRESENTATION_SEARCH_ID 4 /** * @short The Document. Heart of everything. Actions take place here. * * The Document is the main object in Okular. All views query the Document to * get data/properties or even for accessing pages (in a 'const' way). * * It is designed to keep it detached from the document type (pdf, ps, you * name it..) so whenever you want to get some data, it asks its internals * generator to do the job and return results in a format-indepedent way. * * Apart from the generator (the currently running one) the document stores * all the Pages ('Page' class) of the current document in a vector and * notifies all the registered DocumentObservers when some content changes. * * For a better understanding of hieracies @see README.internals.png * @see DocumentObserver, Page */ class OKULAR_EXPORT Document : public QObject { Q_OBJECT public: /** * Creates a new document with the given @p widget as widget to relay GUI things (messageboxes, ...). */ explicit Document( QWidget *widget ); /** * Destroys the document. */ ~Document(); /** * Describes the result of an open document operation. * @since 0.20 (KDE 4.14) */ enum OpenResult { OpenSuccess, //< The document was opened successfully OpenError, //< The document failed to open OpenNeedsPassword //< The document needs a password to be opened or the one provided is not the correct }; /** * Opens the document. * @since 0.20 (KDE 4.14) */ OpenResult openDocument( const QString & docFile, const KUrl & url, const KMimeType::Ptr &mime, const QString &password = QString() ); /** * Closes the document. */ void closeDocument(); /** * Registers a new @p observer for the document. */ void addObserver( DocumentObserver *observer ); /** * Unregisters the given @p observer for the document. */ void removeObserver( DocumentObserver *observer ); /** * Reparses and applies the configuration. */ void reparseConfig(); /** * Returns whether the document is currently opened. */ bool isOpened() const; /** * Returns the meta data of the document or 0 if no meta data * are available. */ const DocumentInfo * documentInfo() const; /** * Returns the table of content of the document or 0 if no * table of content is available. */ const DocumentSynopsis * documentSynopsis() const; /** * Starts the reading of the information about the fonts in the * document, if available. * * The results as well the end of the reading is notified using the * signals gotFont(), fontReadingProgress() and fontReadingEnded() */ void startFontReading(); /** * Force the termination of the reading of the information about the * fonts in the document, if running. */ void stopFontReading(); /** * Whether the current document can provide information about the * fonts used in it. */ bool canProvideFontInformation() const; /** * Returns the list of embedded files or 0 if no embedded files * are available. */ const QList *embeddedFiles() const; /** * Returns the page object for the given page @p number or 0 * if the number is out of range. */ const Page * page( int number ) const; /** * Returns the current viewport of the document. */ const DocumentViewport & viewport() const; /** * Sets the list of visible page rectangles. * @see VisiblePageRect */ void setVisiblePageRects( const QVector< VisiblePageRect * > & visiblePageRects, DocumentObserver *excludeObserver = 0 ); /** * Returns the list of visible page rectangles. */ const QVector< VisiblePageRect * > & visiblePageRects() const; /** * Returns the number of the current page. */ uint currentPage() const; /** * Returns the number of pages of the document. */ uint pages() const; /** * Returns the url of the currently opened document. */ KUrl currentDocument() const; /** * Returns whether the given @p action is allowed in the document. * @see @ref Permission */ bool isAllowed( Permission action ) const; /** * Returns whether the document supports searching. */ bool supportsSearching() const; /** * Returns whether the document supports the listing of page sizes. */ bool supportsPageSizes() const; /** * Returns whether the current document supports tiles * * @since 0.16 (KDE 4.10) */ bool supportsTiles() const; /** * Returns the list of supported page sizes or an empty list if this * feature is not available. * @see supportsPageSizes() */ PageSize::List pageSizes() const; /** * Returns whether the document supports the export to ASCII text. */ bool canExportToText() const; /** * Exports the document as ASCII text and saves it under @p fileName. */ bool exportToText( const QString& fileName ) const; /** * Returns the list of supported export formats. * @see ExportFormat */ QList exportFormats() const; /** * Exports the document in the given @p format and saves it under @p fileName. */ bool exportTo( const QString& fileName, const ExportFormat& format ) const; /** * Returns whether the document history is at the begin. */ bool historyAtBegin() const; /** * Returns whether the document history is at the end. */ bool historyAtEnd() const; /** * Returns the meta data for the given @p key and @p option or an empty variant * if the key doesn't exists. */ QVariant metaData( const QString & key, const QVariant & option = QVariant() ) const; /** * Returns the current rotation of the document. */ Rotation rotation() const; /** * If all pages have the same size this method returns it, if the page sizes * differ an empty size object is returned. */ QSizeF allPagesSize() const; /** * Returns the size string for the given @p page or an empty string * if the page is out of range. */ QString pageSizeString( int page ) const; /** * Returns the gui client of the generator, if it provides one. */ KXMLGUIClient* guiClient(); /** * Sets the current document viewport to the given @p page. * * @param excludeObserver The observer ids which shouldn't be effected by this change. * @param smoothMove Whether the move shall be animated smoothly. */ void setViewportPage( int page, DocumentObserver *excludeObserver = 0, bool smoothMove = false ); /** * Sets the current document viewport to the given @p viewport. * * @param excludeObserver The observer which shouldn't be effected by this change. * @param smoothMove Whether the move shall be animated smoothly. */ void setViewport( const DocumentViewport &viewport, DocumentObserver *excludeObserver = 0, bool smoothMove = false ); /** * Sets the current document viewport to the next viewport in the * viewport history. */ void setPrevViewport(); /** * Sets the current document viewport to the previous viewport in the * viewport history. */ void setNextViewport(); /** * Sets the next @p viewport in the viewport history. */ void setNextDocumentViewport( const DocumentViewport &viewport ); /** * Sets the next @p namedDestination in the viewport history. * * @since 0.9 (KDE 4.3) */ void setNextDocumentDestination( const QString &namedDestination ); /** * Sets the zoom for the current document. */ void setZoom( int factor, DocumentObserver *excludeObserver = 0 ); /** * Describes the possible options for the pixmap requests. */ enum PixmapRequestFlag { NoOption = 0, ///< No options RemoveAllPrevious = 1 ///< Remove all the previous requests, even for non requested page pixmaps }; Q_DECLARE_FLAGS( PixmapRequestFlags, PixmapRequestFlag ) /** * Sends @p requests for pixmap generation. * * The same as requestPixmaps( requests, RemoveAllPrevious ); */ void requestPixmaps( const QLinkedList &requests ); /** * Sends @p requests for pixmap generation. * * @param reqOptions the options for the request * * @since 0.7 (KDE 4.1) */ void requestPixmaps( const QLinkedList &requests, PixmapRequestFlags reqOptions ); /** * Sends a request for text page generation for the given page @p number. */ void requestTextPage( uint number ); /** * Adds a new @p annotation to the given @p page. */ void addPageAnnotation( int page, Annotation *annotation ); /** * Tests if the @p annotation can be modified * * @since 0.15 (KDE 4.9) */ bool canModifyPageAnnotation( const Annotation * annotation ) const; /** * Prepares to modify the properties of the given @p annotation. * Must be called before the annotation's properties are modified * * @since 0.17 (KDE 4.11) */ void prepareToModifyAnnotationProperties( Annotation * annotation ); /** * Modifies the given @p annotation on the given @p page. * Must be preceded by a call to prepareToModifyAnnotationProperties before * the annotation's properties are modified * * @since 0.17 (KDE 4.11) */ void modifyPageAnnotationProperties( int page, Annotation * annotation ); /** * Translates the position of the given @p annotation on the given @p page by a distance @p delta in normalized coordinates. * * Consecutive translations applied to the same @p annotation are merged together on the undo stack if the * BeingMoved flag is set on the @P annotation * * @since 0.17 (KDE 4.11) */ void translatePageAnnotation( int page, Annotation *annotation, const Okular::NormalizedPoint & delta ); /** * Edits the plain text contents of the given @p annotation on the given @p page. * * The contents are set to @p newContents with cursor position @p newCursorPos. * The previous cursor position @p prevCursorPos and previous anchor position @p prevAnchorPos * must also be supplied so that they can be restored if the edit action is undone. * * The Annotation's internal contents should not be modified prior to calling this method. * * @since 0.17 (KDE 4.11) */ void editPageAnnotationContents( int page, Annotation* annotation, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ); /** * Tests if the @p annotation can be removed * * @since 0.15 (KDE 4.9) */ bool canRemovePageAnnotation( const Annotation * annotation ) const; /** * Removes the given @p annotation from the given @p page. */ void removePageAnnotation( int page, Annotation *annotation ); /** * Removes the given @p annotations from the given @p page. */ void removePageAnnotations( int page, const QList &annotations ); /** * Sets the text selection for the given @p page. * * @param rect The rectangle of the selection. * @param color The color of the selection. */ void setPageTextSelection( int page, RegularAreaRect * rect, const QColor & color ); /** * Returns true if there is an undo command available; otherwise returns false. * @since 0.17 (KDE 4.11) */ bool canUndo() const; /** * Returns true if there is a redo command available; otherwise returns false. * @since 0.17 (KDE 4.11) */ bool canRedo() const; /** * Describes the possible search types. */ enum SearchType { NextMatch, ///< Search next match PreviousMatch, ///< Search previous match AllDocument, ///< Search complete document GoogleAll, ///< Search all words in google style GoogleAny ///< Search any words in google style }; /** * Describes how search ended */ enum SearchStatus { MatchFound, ///< Any match was found NoMatchFound, ///< No match was found SearchCancelled, ///< The search was cancelled EndOfDocumentReached ///< The end of document was reached without any match @since 0.20 (KDE 4.14) }; /** * Searches the given @p text in the document. * * @param searchID The unique id for this search request. * @param fromStart Whether the search should be started at begin of the document. * @param caseSensitivity Whether the search is case sensitive. * @param type The type of the search. @ref SearchType * @param moveViewport Whether the viewport shall be moved to the position of the matches. * @param color The highlighting color of the matches. */ void searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity, SearchType type, bool moveViewport, const QColor & color ); /** * Continues the search for the given @p searchID. */ void continueSearch( int searchID ); /** * Continues the search for the given @p searchID, optionally specifying * a new type for the search. * * @since 0.7 (KDE 4.1) */ void continueSearch( int searchID, SearchType type ); /** * Resets the search for the given @p searchID. */ void resetSearch( int searchID ); /** * Returns the bookmark manager of the document. */ BookmarkManager * bookmarkManager() const; /** * Processes the given @p action. */ void processAction( const Action *action ); /** * Returns a list of the bookmarked.pages */ QList bookmarkedPageList() const; /** * Returns the range of the bookmarked.pages */ QString bookmarkedPageRange() const; /** * Processes/Executes the given source @p reference. */ void processSourceReference( const SourceReference *reference ); /** * Returns whether the document can configure the printer itself. */ bool canConfigurePrinter() const; /** * What type of printing a document supports */ enum PrintingType { NoPrinting, ///< Printing Not Supported NativePrinting, ///< Native Cross-Platform Printing PostscriptPrinting ///< Postscript file printing }; /** * Returns what sort of printing the document supports: * Native, Postscript, None */ PrintingType printingSupport() const; /** * Returns whether the document supports printing to both PDF and PS files. */ bool supportsPrintToFile() const; /** * Prints the document to the given @p printer. */ bool print( QPrinter &printer ); /** * Returns the last print error in case print() failed * @since 0.11 (KDE 4.5) */ QString printError() const; /** * Returns a custom printer configuration page or 0 if no * custom printer configuration page is available. */ QWidget* printConfigurationWidget() const; /** * Fill the KConfigDialog @p dialog with the setting pages of the * generators. */ void fillConfigDialog( KConfigDialog * dialog ); /** * Returns the number of generators that have a configuration widget. */ int configurableGenerators() const; /** * Returns the list with the supported MIME types. */ QStringList supportedMimeTypes() const; /** * Returns the component data associated with the generator. May be null. */ const KComponentData* componentData() const; + /** + * Returns whether the generator supports hot-swapping the current file + * with another identical file + * + * @since 0.20 (KDE 4.14) + */ + bool canSwapBackingFile() const; + + /** + * Reload the document from a new location, without any visible effect + * to the user. + * + * The new file must be identical to the current one or, if the document + * has been modified (eg the user edited forms and annotations), the new + * document must have these changes too. For example, you can call + * saveChanges first to write changes to a file and then swapBackingFile + * to switch to the new location. + * + * @since 0.20 (KDE 4.14) + */ + bool swapBackingFile( const QString &newFileName, const KUrl & url ); + + /** + * Same as swapBackingFile, but newFileName must be a .okular file. + * + * The new file must be identical to the current one or, if the document + * has been modified (eg the user edited forms and annotations), the new + * document must have these changes too. For example, you can call + * saveDocumentArchive first to write changes to a file and then + * swapBackingFileArchive to switch to the new location. + * + * @since 0.20 (KDE 4.14) + */ + bool swapBackingFileArchive( const QString &newFileName, const KUrl & url ); + /** * Saving capabilities. Their availability varies according to the * underlying generator and/or the document type. * * @see canSaveChanges (SaveCapability) * @since 0.15 (KDE 4.9) */ enum SaveCapability { SaveFormsCapability = 1, ///< Can save form changes SaveAnnotationsCapability = 2 ///< Can save annotation changes }; /** * Returns whether it's possible to save a given category of changes to * another document. * * @since 0.15 (KDE 4.9) */ bool canSaveChanges( SaveCapability cap ) const; /** * Returns whether the changes to the document (modified annotations, * values in form fields, etc) can be saved to another document. * * Equivalent to the logical OR of canSaveChanges(SaveCapability) for * each capability. * * @since 0.7 (KDE 4.1) */ bool canSaveChanges() const; /** * Save the document and the optional changes to it to the specified * @p fileName. * * @since 0.7 (KDE 4.1) */ bool saveChanges( const QString &fileName ); /** * Save the document and the optional changes to it to the specified * @p fileName and returns a @p errorText if fails. * * @since 0.10 (KDE 4.4) */ bool saveChanges( const QString &fileName, QString *errorText ); /** * Register the specified @p view for the current document. * * It is unregistered from the previous document, if any. * * @since 0.7 (KDE 4.1) */ void registerView( View *view ); /** * Unregister the specified @p view from the current document. * * @since 0.7 (KDE 4.1) */ void unregisterView( View *view ); /** * Gets the font data for the given font * * @since 0.8 (KDE 4.2) */ QByteArray fontData(const FontInfo &font) const; /** * Opens a document archive. * * @since 0.20 (KDE 4.14) */ OpenResult openDocumentArchive( const QString & docFile, const KUrl & url, const QString &password = QString() ); /** * Saves a document archive. * * @since 0.8 (KDE 4.2) */ bool saveDocumentArchive( const QString &fileName ); /** * Extract the document file from the current archive. * * @warning This function only works if the current file is a document archive * * @since 0.14 (KDE 4.20) */ bool extractArchivedFile( const QString &destFileName ); /** * Asks the generator to dynamically generate a SourceReference for a given * page number and absolute X and Y position on this page. * * @attention Ownership of the returned SourceReference is transferred to the caller. * @note This method does not call processSourceReference( const SourceReference * ) * * @since 0.10 (KDE 4.4) */ const SourceReference * dynamicSourceReference( int pageNr, double absX, double absY ); /** * Returns the orientation of the document (for printing purposes). This * is used in the KPart to initialize the print dialog and in the * generators to check whether the document needs to be rotated or not. * * @since 0.14 (KDE 4.8) */ QPrinter::Orientation orientation() const; /** * Control annotation editing (creation, modification and removal), * which is enabled by default. * * @since 0.15 (KDE 4.9) */ void setAnnotationEditingEnabled( bool enable ); /** * Returns which wallet data to use to read/write the password for the given fileName * * @since 0.20 (KDE 4.14) */ void walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const; public Q_SLOTS: /** * This slot is called whenever the user changes the @p rotation of * the document. */ void setRotation( int rotation ); /** * This slot is called whenever the user changes the page @p size * of the document. */ void setPageSize( const PageSize &size ); /** * Cancels the current search */ void cancelSearch(); /** * Undo last edit command * @since 0.17 (KDE 4.11) */ void undo(); /** * Redo last undone edit command * @since 0.17 (KDE 4.11) */ void redo(); /** * Edit the text contents of the specified @p form on page @p page to be @p newContents. * The new text cursor position (@p newCursorPos), previous text cursor position (@p prevCursorPos), * and previous cursor anchor position will be restored by the undo / redo commands. * @since 0.17 (KDE 4.11) */ void editFormText( int pageNumber, Okular::FormFieldText* form, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ); /** * Edit the selected list entries in @p form on page @p page to be @p newChoices. * @since 0.17 (KDE 4.11) */ void editFormList( int pageNumber, Okular::FormFieldChoice* form, const QList & newChoices ); /** * Set the active choice in the combo box @p form on page @p page to @p newText * The new cursor position (@p newCursorPos), previous cursor position * (@p prevCursorPos), and previous anchor position (@p prevAnchorPos) * will be restored by the undo / redo commands. * * @since 0.17 (KDE 4.11) */ void editFormCombo( int pageNumber, Okular::FormFieldChoice *form, const QString & newText, int newCursorPos, int prevCursorPos, int prevAnchorPos ); /** * Set the states of the group of form buttons @p formButtons on page @p page to @p newButtonStates. * The lists @p formButtons and @p newButtonStates should be the same length and true values * in @p newButtonStates indicate that the corresponding entry in @p formButtons should be enabled. */ void editFormButtons( int pageNumber, const QList< Okular::FormFieldButton* > & formButtons, const QList< bool > & newButtonStates ); Q_SIGNALS: /** * This signal is emitted whenever an action requests a * document close operation. */ void close(); /** * This signal is emitted whenever an action requests an * application quit operation. */ void quit(); /** * This signal is emitted whenever an action requests a * find operation. */ void linkFind(); /** * This signal is emitted whenever an action requests a * goto operation. */ void linkGoToPage(); /** * This signal is emitted whenever an action requests a * start presentation operation. */ void linkPresentation(); /** * This signal is emitted whenever an action requests an * end presentation operation. */ void linkEndPresentation(); /** * This signal is emitted whenever an action requests an * open url operation for the given document @p url. */ void openUrl( const KUrl &url ); /** * This signal is emitted whenever an error occurred. * * @param text The description of the error. * @param duration The time in seconds the message should be shown to the user. */ void error( const QString &text, int duration ); /** * This signal is emitted to signal a warning. * * @param text The description of the warning. * @param duration The time in seconds the message should be shown to the user. */ void warning( const QString &text, int duration ); /** * This signal is emitted to signal a notice. * * @param text The description of the notice. * @param duration The time in seconds the message should be shown to the user. */ void notice( const QString &text, int duration ); /** * Emitted when a new font is found during the reading of the fonts of * the document. */ void gotFont( const Okular::FontInfo& font ); /** * Reports the progress when reading the fonts in the document. * * \param page is the page that was just finished to scan for fonts */ void fontReadingProgress( int page ); /** * Reports that the reading of the fonts in the document is finished. */ void fontReadingEnded(); /** * Reports that the current search finished */ void searchFinished( int searchID, Okular::Document::SearchStatus endStatus ); /** * This signal is emitted whenever a source reference with the given parameters has been * activated. * * \param handled should be set to 'true' if a slot handles this source reference; the * default action to launch the configured editor will then not be performed * by the document * * @since 0.14 (KDE 4.8) */ void sourceReferenceActivated(const QString& absFileName, int line, int col, bool *handled); /** * This signal is emitted whenever an movie action is triggered and the UI should process it. */ void processMovieAction( const Okular::MovieAction *action ); /** * This signal is emmitted whenever the availability of the undo function changes * @since 0.17 (KDE 4.11) */ void canUndoChanged( bool undoAvailable ); /** * This signal is emmitted whenever the availability of the redo function changes * @since 0.17 (KDE 4.11) */ void canRedoChanged( bool redoAvailable ); /** * This signal is emitted whenever an rendition action is triggered and the UI should process it. * * @since 0.16 (KDE 4.10) */ void processRenditionAction( const Okular::RenditionAction *action ); /** * This signal is emmitted whenever the contents of the given @p annotation are changed by an undo * or redo action. * * The new contents (@p contents), cursor position (@p cursorPos), and anchor position (@p anchorPos) are * included * @since 0.17 (KDE 4.11) */ void annotationContentsChangedByUndoRedo( Okular::Annotation* annotation, const QString & contents, int cursorPos, int anchorPos ); /** * This signal is emmitted whenever the text contents of the given text @p form on the given @p page * are changed by an undo or redo action. * * The new text contents (@p contents), cursor position (@p cursorPos), and anchor position (@p anchorPos) are * included * @since 0.17 (KDE 4.11) */ void formTextChangedByUndoRedo( int page, Okular::FormFieldText* form, const QString & contents, int cursorPos, int anchorPos ); /** * This signal is emmitted whenever the selected @p choices for the given list @p form on the * given @p page are changed by an undo or redo action. * @since 0.17 (KDE 4.11) */ void formListChangedByUndoRedo( int page, Okular::FormFieldChoice* form, const QList< int > & choices ); /** * This signal is emmitted whenever the active @p text for the given combo @p form on the * given @p page is changed by an undo or redo action. * @since 0.17 (KDE 4.11) */ void formComboChangedByUndoRedo( int page, Okular::FormFieldChoice* form, const QString & text, int cursorPos, int anchorPos ); /** * This signal is emmitted whenever the state of the specified group of form buttons (@p formButtons) on the * given @p page is changed by an undo or redo action. * @since 0.17 (KDE 4.11) */ void formButtonsChangedByUndoRedo( int page, const QList< Okular::FormFieldButton* > & formButtons ); private: /// @cond PRIVATE friend class DocumentPrivate; friend class ::DocumentItem; friend class EditAnnotationContentsCommand; friend class EditFormTextCommand; friend class EditFormListCommand; friend class EditFormComboCommand; friend class EditFormButtonsCommand; /// @endcond DocumentPrivate *const d; Q_DISABLE_COPY( Document ) Q_PRIVATE_SLOT( d, void saveDocumentInfo() const ) Q_PRIVATE_SLOT( d, void slotTimedMemoryCheck() ) Q_PRIVATE_SLOT( d, void sendGeneratorPixmapRequest() ) Q_PRIVATE_SLOT( d, void rotationFinished( int page, Okular::Page *okularPage ) ) Q_PRIVATE_SLOT( d, void fontReadingProgress( int page ) ) Q_PRIVATE_SLOT( d, void fontReadingGotFont( const Okular::FontInfo& font ) ) Q_PRIVATE_SLOT( d, void slotGeneratorConfigChanged( const QString& ) ) Q_PRIVATE_SLOT( d, void refreshPixmaps( int ) ) Q_PRIVATE_SLOT( d, void _o_configChanged() ) // search thread simulators Q_PRIVATE_SLOT( d, void doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct) ) Q_PRIVATE_SLOT( d, void doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID) ) Q_PRIVATE_SLOT( d, void doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words) ) }; /** * @short A view on the document. * * The Viewport structure is the 'current view' over the document. Contained * data is broadcasted between observers to synchronize their viewports to get * the 'I scroll one view and others scroll too' views. */ class OKULAR_EXPORT DocumentViewport { public: /** * Creates a new viewport for the given page @p number. */ DocumentViewport( int number = -1 ); /** * Creates a new viewport from the given xml @p description. */ DocumentViewport( const QString &description ); /** * Returns the viewport as xml description. */ QString toString() const; /** * Returns whether the viewport is valid. */ bool isValid() const; /** * @internal */ bool operator==( const DocumentViewport &other ) const; bool operator<( const DocumentViewport &other ) const; /** * The number of the page nearest the center of the viewport. */ int pageNumber; /** * Describes the relative position of the viewport. */ enum Position { Center = 1, ///< Relative to the center of the page. TopLeft = 2 ///< Relative to the top left corner of the page. }; /** * If 'rePos.enabled == true' then this structure contains the * viewport center or top left depending on the value of pos. */ struct { bool enabled; double normalizedX; double normalizedY; Position pos; } rePos; /** * If 'autoFit.enabled == true' then the page must be autofitted in the viewport. */ struct { bool enabled; bool width; bool height; } autoFit; }; /** * @short A DOM tree containing information about the document. * * The DocumentInfo structure can be filled in by generators to display * metadata about the currently opened file. */ class OKULAR_EXPORT DocumentInfo : public QDomDocument { public: /** * The list of predefined keys. */ enum Key { Title, ///< The title of the document Subject, ///< The subject of the document Description, ///< The description of the document Author, ///< The author of the document Creator, ///< The creator of the document (this can be different from the author) Producer, ///< The producer of the document (e.g. some software) Copyright, ///< The copyright of the document Pages, ///< The number of pages of the document CreationDate, ///< The date of creation of the document ModificationDate, ///< The date of last modification of the document MimeType, ///< The mime type of the document Category, ///< The category of the document Keywords, ///< The keywords which describe the content of the document FilePath, ///< The path of the file @since 0.10 (KDE 4.4) DocumentSize, ///< The size of the document @since 0.10 (KDE 4.4) PagesSize ///< The size of the pages (if all pages have the same size) @since 0.10 (KDE 4.4) }; /** * Creates a new document info. */ DocumentInfo(); /** * Sets a value for a special key. The title should be an i18n'ed * string, since it's used in the document information dialog. */ void set( const QString &key, const QString &value, const QString &title = QString() ); /** * Sets the value for a predefined key. You should use this method * whenever a predefined key exists for your value. */ void set( Key key, const QString &value ); /** * Returns the value for a given key or an empty string when the * key doesn't exist. */ QString get( const QString &key ) const; /** * Returns the internal string for the given key * @since 0.10 (KDE 4.4) */ static QString getKeyString( Key key ); /** * Returns the user visible string for the given key * @since 0.10 (KDE 4.4) */ static QString getKeyTitle( Key key ); }; /** * @short A DOM tree that describes the Table of Contents. * * The Synopsis (TOC or Table Of Contents for friends) is represented via * a dom tree where each node has an internal name (displayed in the TOC) * and one or more attributes. * * In the tree the tag name is the 'screen' name of the entry. A tag can have * attributes. Here follows the list of tag attributes with meaning: * - Destination: A string description of the referred viewport * - DestinationName: A 'named reference' to the viewport that must be converted * using metaData( "NamedViewport", viewport_name ) * - ExternalFileName: A document to be opened, whose destination is specified * with Destination or DestinationName * - Open: a boolean saying whether its TOC branch is open or not (default: false) * - URL: a URL to be open as destination; if set, no other Destination* or * ExternalFileName entry is used */ class OKULAR_EXPORT DocumentSynopsis : public QDomDocument { public: /** * Creates a new document synopsis object. */ DocumentSynopsis(); /** * Creates a new document synopsis object with the given * @p document as parent node. */ DocumentSynopsis( const QDomDocument &document ); }; /** * @short An embedded file into the document. * * This class represents a sort of interface of an embedded file in a document. * * Generators \b must re-implement its members to give the all the information * about an embedded file, like its name, its description, the date of creation * and modification, and the real data of the file. */ class OKULAR_EXPORT EmbeddedFile { public: /** * Creates a new embedded file. */ EmbeddedFile(); /** * Destroys the embedded file. */ virtual ~EmbeddedFile(); /** * Returns the name of the file */ virtual QString name() const = 0; /** * Returns the description of the file, or an empty string if not * available */ virtual QString description() const = 0; /** * Returns the real data representing the file contents */ virtual QByteArray data() const = 0; /** * Returns the size (in bytes) of the file, if available, or -1 otherwise. * * @note this method should be a fast way to know the size of the file * with no need to extract all the data from it */ virtual int size() const = 0; /** * Returns the modification date of the file, or an invalid date * if not available */ virtual QDateTime modificationDate() const = 0; /** * Returns the creation date of the file, or an invalid date * if not available */ virtual QDateTime creationDate() const = 0; }; /** * @short An area of a specified page */ class OKULAR_EXPORT VisiblePageRect { public: /** * Creates a new visible page rectangle. * * @param pageNumber The page number where the rectangle is located. * @param rectangle The rectangle in normalized coordinates. */ explicit VisiblePageRect( int pageNumber = -1, const NormalizedRect &rectangle = NormalizedRect() ); /** * The page number where the rectangle is located. */ int pageNumber; /** * The rectangle in normalized coordinates. */ NormalizedRect rect; }; } Q_DECLARE_METATYPE( Okular::DocumentInfo::Key ) Q_DECLARE_OPERATORS_FOR_FLAGS( Okular::Document::PixmapRequestFlags ) #endif /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/generator.cpp b/core/generator.cpp index ec1d1f76d..704bf4d66 100644 --- a/core/generator.cpp +++ b/core/generator.cpp @@ -1,672 +1,677 @@ /*************************************************************************** * Copyright (C) 2005 by Piotr Szymanski * * Copyright (C) 2008 by Albert Astals Cid * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "generator.h" #include "generator_p.h" #include "observer.h" #include #include #include #include #include #include #include "document.h" #include "document_p.h" #include "page.h" #include "page_p.h" #include "textpage.h" #include "utils.h" using namespace Okular; GeneratorPrivate::GeneratorPrivate() : m_document( 0 ), mPixmapGenerationThread( 0 ), mTextPageGenerationThread( 0 ), m_mutex( 0 ), m_threadsMutex( 0 ), mPixmapReady( true ), mTextPageReady( true ), m_closing( false ), m_closingLoop( 0 ), m_dpi(72.0, 72.0) { } GeneratorPrivate::~GeneratorPrivate() { if ( mPixmapGenerationThread ) mPixmapGenerationThread->wait(); delete mPixmapGenerationThread; if ( mTextPageGenerationThread ) mTextPageGenerationThread->wait(); delete mTextPageGenerationThread; delete m_mutex; delete m_threadsMutex; } PixmapGenerationThread* GeneratorPrivate::pixmapGenerationThread() { if ( mPixmapGenerationThread ) return mPixmapGenerationThread; Q_Q( Generator ); mPixmapGenerationThread = new PixmapGenerationThread( q ); QObject::connect( mPixmapGenerationThread, SIGNAL(finished()), q, SLOT(pixmapGenerationFinished()), Qt::QueuedConnection ); return mPixmapGenerationThread; } TextPageGenerationThread* GeneratorPrivate::textPageGenerationThread() { if ( mTextPageGenerationThread ) return mTextPageGenerationThread; Q_Q( Generator ); mTextPageGenerationThread = new TextPageGenerationThread( q ); QObject::connect( mTextPageGenerationThread, SIGNAL(finished()), q, SLOT(textpageGenerationFinished()), Qt::QueuedConnection ); return mTextPageGenerationThread; } void GeneratorPrivate::pixmapGenerationFinished() { Q_Q( Generator ); PixmapRequest *request = mPixmapGenerationThread->request(); mPixmapGenerationThread->endGeneration(); QMutexLocker locker( threadsLock() ); mPixmapReady = true; if ( m_closing ) { delete request; if ( mTextPageReady ) { locker.unlock(); m_closingLoop->quit(); } return; } const QImage& img = mPixmapGenerationThread->image(); request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( img ) ), request->normalizedRect() ); const int pageNumber = request->page()->number(); if ( mPixmapGenerationThread->calcBoundingBox() ) q->updatePageBoundingBox( pageNumber, mPixmapGenerationThread->boundingBox() ); q->signalPixmapRequestDone( request ); } void GeneratorPrivate::textpageGenerationFinished() { Q_Q( Generator ); Page *page = mTextPageGenerationThread->page(); mTextPageGenerationThread->endGeneration(); QMutexLocker locker( threadsLock() ); mTextPageReady = true; if ( m_closing ) { delete mTextPageGenerationThread->textPage(); if ( mPixmapReady ) { locker.unlock(); m_closingLoop->quit(); } return; } if ( mTextPageGenerationThread->textPage() ) { TextPage *tp = mTextPageGenerationThread->textPage(); page->setTextPage( tp ); q->signalTextGenerationDone( page, tp ); } } QMutex* GeneratorPrivate::threadsLock() { if ( !m_threadsMutex ) m_threadsMutex = new QMutex(); return m_threadsMutex; } QVariant GeneratorPrivate::metaData( const QString &, const QVariant & ) const { return QVariant(); } QImage GeneratorPrivate::image( PixmapRequest * ) { return QImage(); } Generator::Generator( QObject *parent, const QVariantList &args ) : QObject( parent ), d_ptr( new GeneratorPrivate() ) { d_ptr->q_ptr = this; Q_UNUSED( args ) } Generator::Generator( GeneratorPrivate &dd, QObject *parent, const QVariantList &args ) : QObject( parent ), d_ptr( &dd ) { d_ptr->q_ptr = this; Q_UNUSED( args ) } Generator::~Generator() { delete d_ptr; } bool Generator::loadDocument( const QString & fileName, QVector< Page * > & pagesVector ) { return false; } bool Generator::loadDocumentFromData( const QByteArray &, QVector< Page * > & ) { return false; } Document::OpenResult Generator::loadDocumentWithPassword( const QString & fileName, QVector< Page * > & pagesVector, const QString & ) { return loadDocument( fileName, pagesVector ) ? Document::OpenSuccess : Document::OpenError; } Document::OpenResult Generator::loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector< Page * > & pagesVector, const QString & ) { return loadDocumentFromData( fileData, pagesVector ) ? Document::OpenSuccess : Document::OpenError; } +bool Generator::swapBackingFile( QString const &newFileName ) +{ + return false; +} + bool Generator::closeDocument() { Q_D( Generator ); d->m_closing = true; d->threadsLock()->lock(); if ( !( d->mPixmapReady && d->mTextPageReady ) ) { QEventLoop loop; d->m_closingLoop = &loop; d->threadsLock()->unlock(); loop.exec(); d->m_closingLoop = 0; } else { d->threadsLock()->unlock(); } bool ret = doCloseDocument(); d->m_closing = false; return ret; } bool Generator::canGeneratePixmap() const { Q_D( const Generator ); return d->mPixmapReady; } void Generator::generatePixmap( PixmapRequest *request ) { Q_D( Generator ); d->mPixmapReady = false; const bool calcBoundingBox = !request->isTile() && !request->page()->isBoundingBoxKnown(); if ( request->asynchronous() && hasFeature( Threaded ) ) { d->pixmapGenerationThread()->startGeneration( request, calcBoundingBox ); /** * We create the text page for every page that is visible to the * user, so he can use the text extraction tools without a delay. */ if ( hasFeature( TextExtraction ) && !request->page()->hasTextPage() && canGenerateTextPage() && !d->m_closing ) { d->mTextPageReady = false; d->textPageGenerationThread()->startGeneration( request->page() ); } return; } const QImage& img = image( request ); request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( img ) ), request->normalizedRect() ); const int pageNumber = request->page()->number(); d->mPixmapReady = true; signalPixmapRequestDone( request ); if ( calcBoundingBox ) updatePageBoundingBox( pageNumber, Utils::imageBoundingBox( &img ) ); } bool Generator::canGenerateTextPage() const { Q_D( const Generator ); return d->mTextPageReady; } void Generator::generateTextPage( Page *page ) { TextPage *tp = textPage( page ); page->setTextPage( tp ); signalTextGenerationDone( page, tp ); } QImage Generator::image( PixmapRequest *request ) { Q_D( Generator ); return d->image( request ); } TextPage* Generator::textPage( Page* ) { return 0; } const DocumentInfo * Generator::generateDocumentInfo() { return 0; } const DocumentSynopsis * Generator::generateDocumentSynopsis() { return 0; } FontInfo::List Generator::fontsForPage( int ) { return FontInfo::List(); } const QList * Generator::embeddedFiles() const { return 0; } Generator::PageSizeMetric Generator::pagesSizeMetric() const { return None; } bool Generator::isAllowed( Permission ) const { return true; } void Generator::rotationChanged( Rotation, Rotation ) { } PageSize::List Generator::pageSizes() const { return PageSize::List(); } void Generator::pageSizeChanged( const PageSize &, const PageSize & ) { } bool Generator::print( QPrinter& ) { return false; } Generator::PrintError Generator::printError() const { return UnknownPrintError; } QVariant Generator::metaData( const QString &key, const QVariant &option ) const { Q_D( const Generator ); return d->metaData( key, option ); } ExportFormat::List Generator::exportFormats() const { return ExportFormat::List(); } bool Generator::exportTo( const QString&, const ExportFormat& ) { return false; } void Generator::walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const { *walletKey = fileName.section('/', -1, -1); *walletName = KWallet::Wallet::NetworkWallet(); *walletFolder = "KPdf"; } bool Generator::hasFeature( GeneratorFeature feature ) const { Q_D( const Generator ); return d->m_features.contains( feature ); } void Generator::signalPixmapRequestDone( PixmapRequest * request ) { Q_D( Generator ); if ( d->m_document ) d->m_document->requestDone( request ); else { delete request; } } void Generator::signalTextGenerationDone( Page *page, TextPage *textPage ) { Q_D( Generator ); if ( d->m_document ) d->m_document->textGenerationDone( page ); else delete textPage; } const Document * Generator::document() const { Q_D( const Generator ); if ( d->m_document ) { return d->m_document->m_parent; } return 0; } void Generator::setFeature( GeneratorFeature feature, bool on ) { Q_D( Generator ); if ( on ) d->m_features.insert( feature ); else d->m_features.remove( feature ); } QVariant Generator::documentMetaData( const QString &key, const QVariant &option ) const { Q_D( const Generator ); if ( !d->m_document ) return QVariant(); return d->m_document->documentMetaData( key, option ); } QMutex* Generator::userMutex() const { Q_D( const Generator ); if ( !d->m_mutex ) { d->m_mutex = new QMutex(); } return d->m_mutex; } void Generator::updatePageBoundingBox( int page, const NormalizedRect & boundingBox ) { Q_D( Generator ); if ( d->m_document ) // still connected to document? d->m_document->setPageBoundingBox( page, boundingBox ); } void Generator::requestFontData(const Okular::FontInfo & /*font*/, QByteArray * /*data*/) { } const SourceReference * Generator::dynamicSourceReference( int /*pageNr*/, double /*absX*/, double /*absY*/) { return 0; } void Generator::setDPI(const QSizeF & dpi) { Q_D( Generator ); d->m_dpi = dpi; } QSizeF Generator::dpi() const { Q_D( const Generator ); return d->m_dpi; } PixmapRequest::PixmapRequest( DocumentObserver *observer, int pageNumber, int width, int height, int priority, PixmapRequestFeatures features ) : d( new PixmapRequestPrivate ) { d->mObserver = observer; d->mPageNumber = pageNumber; d->mWidth = width; d->mHeight = height; d->mPriority = priority; d->mFeatures = features; d->mForce = false; d->mTile = false; d->mNormalizedRect = NormalizedRect(); } PixmapRequest::~PixmapRequest() { delete d; } DocumentObserver *PixmapRequest::observer() const { return d->mObserver; } int PixmapRequest::pageNumber() const { return d->mPageNumber; } int PixmapRequest::width() const { return d->mWidth; } int PixmapRequest::height() const { return d->mHeight; } int PixmapRequest::priority() const { return d->mPriority; } bool PixmapRequest::asynchronous() const { return d->mFeatures & Asynchronous; } bool PixmapRequest::preload() const { return d->mFeatures & Preload; } Page* PixmapRequest::page() const { return d->mPage; } void PixmapRequest::setTile( bool tile ) { d->mTile = tile; } bool PixmapRequest::isTile() const { return d->mTile; } void PixmapRequest::setNormalizedRect( const NormalizedRect &rect ) { if ( d->mNormalizedRect == rect ) return; d->mNormalizedRect = rect; } const NormalizedRect& PixmapRequest::normalizedRect() const { return d->mNormalizedRect; } Okular::TilesManager* PixmapRequestPrivate::tilesManager() const { return mPage->d->tilesManager(mObserver); } void PixmapRequestPrivate::swap() { qSwap( mWidth, mHeight ); } class Okular::ExportFormatPrivate : public QSharedData { public: ExportFormatPrivate( const QString &description, const KMimeType::Ptr &mimeType, const KIcon &icon = KIcon() ) : QSharedData(), mDescription( description ), mMimeType( mimeType ), mIcon( icon ) { } ~ExportFormatPrivate() { } QString mDescription; KMimeType::Ptr mMimeType; KIcon mIcon; }; ExportFormat::ExportFormat() : d( new ExportFormatPrivate( QString(), KMimeType::Ptr() ) ) { } ExportFormat::ExportFormat( const QString &description, const KMimeType::Ptr &mimeType ) : d( new ExportFormatPrivate( description, mimeType ) ) { } ExportFormat::ExportFormat( const KIcon &icon, const QString &description, const KMimeType::Ptr &mimeType ) : d( new ExportFormatPrivate( description, mimeType, icon ) ) { } ExportFormat::~ExportFormat() { } ExportFormat::ExportFormat( const ExportFormat &other ) : d( other.d ) { } ExportFormat& ExportFormat::operator=( const ExportFormat &other ) { if ( this == &other ) return *this; d = other.d; return *this; } QString ExportFormat::description() const { return d->mDescription; } KMimeType::Ptr ExportFormat::mimeType() const { return d->mMimeType; } KIcon ExportFormat::icon() const { return d->mIcon; } bool ExportFormat::isNull() const { return d->mMimeType.isNull() || d->mDescription.isNull(); } ExportFormat ExportFormat::standardFormat( StandardExportFormat type ) { switch ( type ) { case PlainText: return ExportFormat( KIcon( "text-x-generic" ), i18n( "Plain &Text..." ), KMimeType::mimeType( "text/plain" ) ); break; case PDF: return ExportFormat( KIcon( "application-pdf" ), i18n( "PDF" ), KMimeType::mimeType( "application/pdf" ) ); break; case OpenDocumentText: return ExportFormat( KIcon( "application-vnd.oasis.opendocument.text" ), i18nc( "This is the document format", "OpenDocument Text" ), KMimeType::mimeType( "application/vnd.oasis.opendocument.text" ) ); break; case HTML: return ExportFormat( KIcon( "text-html" ), i18nc( "This is the document format", "HTML" ), KMimeType::mimeType( "text/html" ) ); break; } return ExportFormat(); } bool ExportFormat::operator==( const ExportFormat &other ) const { return d == other.d; } bool ExportFormat::operator!=( const ExportFormat &other ) const { return d != other.d; } QDebug operator<<( QDebug str, const Okular::PixmapRequest &req ) { QString s = QString( "PixmapRequest(#%2, %1, %3x%4, page %6, prio %5)" ) .arg( QString( req.asynchronous() ? "async" : "sync" ) ) .arg( (qulonglong)req.observer() ) .arg( req.width() ) .arg( req.height() ) .arg( req.priority() ) .arg( req.pageNumber() ); str << qPrintable( s ); return str; } #include "generator.moc" /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/generator.h b/core/generator.h index 506f8a826..9784dadb0 100644 --- a/core/generator.h +++ b/core/generator.h @@ -1,699 +1,711 @@ /*************************************************************************** * Copyright (C) 2004-5 by Enrico Ros * * Copyright (C) 2005 by Piotr Szymanski * * Copyright (C) 2008 by Albert Astals Cid * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef _OKULAR_GENERATOR_H_ #define _OKULAR_GENERATOR_H_ #include "okular_export.h" #include "document.h" #include "fontinfo.h" #include "global.h" #include "pagesize.h" #include #include #include #include #include #include #include #include #include #define OKULAR_EXPORT_PLUGIN( classname, aboutdata ) \ K_PLUGIN_FACTORY( classname ## Factory, registerPlugin< classname >(); ) \ K_EXPORT_PLUGIN( classname ## Factory( aboutdata ) ) class QByteArray; class QMutex; class QPrinter; class QPrintDialog; class KIcon; namespace Okular { class DocumentFonts; class DocumentInfo; class DocumentObserver; class DocumentSynopsis; class EmbeddedFile; class ExportFormatPrivate; class FontInfo; class GeneratorPrivate; class Page; class PixmapRequest; class PixmapRequestPrivate; class TextPage; class NormalizedRect; class SourceReference; /* Note: on contents generation and asynchronous queries. * Many observers may want to request data syncronously or asynchronously. * - Sync requests. These should be done in-place. * - Async request must be done in real background. That usually means a * thread, such as QThread derived classes. * Once contents are available, they must be immediately stored in the * Page they refer to, and a signal is emitted as soon as storing * (even for sync or async queries) has been done. */ /** * @short Defines an entry for the export menu * * This class encapsulates information about an export format. * Every Generator can support 0 or more export formats which can be * queried with @ref Generator::exportFormats(). */ class OKULAR_EXPORT ExportFormat { public: typedef QList List; /** * Creates an empty export format. * * @see isNull() */ ExportFormat(); /** * Creates a new export format. * * @param description The i18n'ed description of the format. * @param mimeType The supported mime type of the format. */ ExportFormat( const QString &description, const KMimeType::Ptr &mimeType ); /** * Creates a new export format. * * @param icon The icon used in the GUI for this format. * @param description The i18n'ed description of the format. * @param mimeType The supported mime type of the format. */ ExportFormat( const KIcon &icon, const QString &description, const KMimeType::Ptr &mimeType ); /** * Destroys the export format. */ ~ExportFormat(); /** * @internal */ ExportFormat( const ExportFormat &other ); /** * @internal */ ExportFormat& operator=( const ExportFormat &other ); /** * Returns the description of the format. */ QString description() const; /** * Returns the mime type of the format. */ KMimeType::Ptr mimeType() const; /** * Returns the icon for GUI representations of the format. */ KIcon icon() const; /** * Returns whether the export format is null/valid. * * An ExportFormat is null if the mimetype is not valid or the * description is empty, or both. */ bool isNull() const; /** * Type of standard export format. */ enum StandardExportFormat { PlainText, ///< Plain text PDF, ///< PDF, aka Portable Document Format OpenDocumentText, ///< OpenDocument Text format @since 0.8 (KDE 4.2) HTML ///< OpenDocument Text format @since 0.8 (KDE 4.2) }; /** * Builds a standard format for the specified @p type . */ static ExportFormat standardFormat( StandardExportFormat type ); bool operator==( const ExportFormat &other ) const; bool operator!=( const ExportFormat &other ) const; private: /// @cond PRIVATE friend class ExportFormatPrivate; /// @endcond QSharedDataPointer d; }; /** * @short [Abstract Class] The information generator. * * Most of class members are virtuals and some of them pure virtual. The pure * virtuals provide the minimal functionalities for a Generator, that is being * able to generate QPixmap for the Page 's of the Document. * * Implementing the other functions will make the Generator able to provide * more contents and/or functionalities (like text extraction). * * Generation/query is requested by the Document class only, and that * class stores the resulting data into Page s. The data will then be * displayed by the GUI components (PageView, ThumbnailList, etc..). * * @see PrintInterface, ConfigInterface, GuiInterface */ class OKULAR_EXPORT Generator : public QObject { /// @cond PRIVATE friend class PixmapGenerationThread; friend class TextPageGenerationThread; /// @endcond Q_OBJECT public: /** * Describe the possible optional features that a Generator can * provide. */ enum GeneratorFeature { Threaded, TextExtraction, ///< Whether the Generator can extract text from the document in the form of TextPage's ReadRawData, ///< Whether the Generator can read a document directly from its raw data. FontInfo, ///< Whether the Generator can provide information about the fonts used in the document PageSizes, ///< Whether the Generator can change the size of the document pages. PrintNative, ///< Whether the Generator supports native cross-platform printing (QPainter-based). PrintPostscript, ///< Whether the Generator supports postscript-based file printing. PrintToFile, ///< Whether the Generator supports export to PDF & PS through the Print Dialog - TiledRendering ///< Whether the Generator can render tiles @since 0.16 (KDE 4.10) + TiledRendering, ///< Whether the Generator can render tiles @since 0.16 (KDE 4.10) + SwapBackingFile ///< Whether the Generator can hot-swap the file it's reading from @since 0.20 (KDE 4.14) }; /** * Creates a new generator. */ Generator( QObject *parent, const QVariantList &args ); /** * Destroys the generator. */ virtual ~Generator(); /** * Loads the document with the given @p fileName and fills the * @p pagesVector with the parsed pages. * * @note If you implement the WithPassword variants you don't need to implement this one * * @returns true on success, false otherwise. */ virtual bool loadDocument( const QString & fileName, QVector< Page * > & pagesVector ); /** * Loads the document from the raw data @p fileData and fills the * @p pagesVector with the parsed pages. * * @note If you implement the WithPassword variants you don't need to implement this one * * @note the Generator has to have the feature @ref ReadRawData enabled * * @returns true on success, false otherwise. */ virtual bool loadDocumentFromData( const QByteArray & fileData, QVector< Page * > & pagesVector ); /** * Loads the document with the given @p fileName and @p password and fills the * @p pagesVector with the parsed pages. * * @note Do not implement this if your format doesn't support passwords, it'll cleanly call loadDocument() * * @since 0.20 (KDE 4.14) * * @returns a LoadResult defining the result of the operation */ virtual Document::OpenResult loadDocumentWithPassword( const QString & fileName, QVector< Page * > & pagesVector, const QString &password ); /** * Loads the document from the raw data @p fileData and @p password and fills the * @p pagesVector with the parsed pages. * * @note Do not implement this if your format doesn't support passwords, it'll cleanly call loadDocumentFromData() * * @note the Generator has to have the feature @ref ReadRawData enabled * * @since 0.20 (KDE 4.14) * * @returns a LoadResult defining the result of the operation */ virtual Document::OpenResult loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector< Page * > & pagesVector, const QString &password ); + /** + * Changes the path of the file we are reading from. The new path must + * point to a copy of the same document. + * + * @note the Generator has to have the feature @ref SwapBackingFile enabled + * + * @since 0.20 (KDE 4.14) + * + * @returns true on success, false otherwise. + */ + virtual bool swapBackingFile( const QString &newFileName ); /** * This method is called when the document is closed and not used * any longer. * * @returns true on success, false otherwise. */ bool closeDocument(); /** * This method returns whether the generator is ready to * handle a new pixmap request. */ virtual bool canGeneratePixmap() const; /** * This method can be called to trigger the generation of * a new pixmap as described by @p request. */ virtual void generatePixmap( PixmapRequest * request ); /** * This method returns whether the generator is ready to * handle a new text page request. */ virtual bool canGenerateTextPage() const; /** * This method can be called to trigger the generation of * a text page for the given @p page. * * The generation is done synchronous or asynchronous, depending * on the @p type parameter and the capabilities of the * generator (e.g. multithreading). * * @see TextPage */ virtual void generateTextPage( Page * page ); /** * Returns the general information object of the document or 0 if * no information are available. */ virtual const DocumentInfo * generateDocumentInfo(); /** * Returns the 'table of content' object of the document or 0 if * no table of content is available. */ virtual const DocumentSynopsis * generateDocumentSynopsis(); /** * Returns the 'list of embedded fonts' object of the specified \page * of the document. * * \param page a page of the document, starting from 0 - -1 indicates all * the other fonts */ virtual FontInfo::List fontsForPage( int page ); /** * Returns the 'list of embedded files' object of the document or 0 if * no list of embedded files is available. */ virtual const QList * embeddedFiles() const; /** * This enum identifies the metric of the page size. */ enum PageSizeMetric { None, ///< The page size is not defined in a physical metric. Points, ///< The page size is given in 1/72 inches. Pixels ///< The page size is given in screen pixels @since 0.19 (KDE 4.13) }; /** * This method returns the metric of the page size. Default is @ref None. */ virtual PageSizeMetric pagesSizeMetric() const; /** * This method returns whether given @p action (@ref Permission) is * allowed in this document. */ virtual bool isAllowed( Permission action ) const; /** * This method is called when the orientation has been changed by the user. */ virtual void rotationChanged( Rotation orientation, Rotation oldOrientation ); /** * Returns the list of supported page sizes. */ virtual PageSize::List pageSizes() const; /** * This method is called when the page size has been changed by the user. */ virtual void pageSizeChanged( const PageSize &pageSize, const PageSize &oldPageSize ); /** * This method is called to print the document to the given @p printer. */ virtual bool print( QPrinter &printer ); /** * Possible print errors * @since 0.11 (KDE 4.5) */ enum PrintError { NoPrintError, ///< There was no print error UnknownPrintError, TemporaryFileOpenPrintError, FileConversionPrintError, PrintingProcessCrashPrintError, PrintingProcessStartPrintError, PrintToFilePrintError, InvalidPrinterStatePrintError, UnableToFindFilePrintError, NoFileToPrintError, NoBinaryToPrintError, InvalidPageSizePrintError ///< @since 0.18.2 (KDE 4.12.2) }; /** * This method returns the meta data of the given @p key with the given @p option * of the document. */ virtual QVariant metaData( const QString &key, const QVariant &option ) const; /** * Returns the list of additional supported export formats. */ virtual ExportFormat::List exportFormats() const; /** * This method is called to export the document in the given @p format and save it * under the given @p fileName. The format must be one of the supported export formats. */ virtual bool exportTo( const QString &fileName, const ExportFormat &format ); /** * This method is called to know which wallet data should be used for the given file name. * Unless you have very special requirements to where wallet data should be stored you * don't need to reimplement this method. */ virtual void walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const; /** * Query for the specified @p feature. */ bool hasFeature( GeneratorFeature feature ) const; /** * Update DPI of the generator * * @since 0.19 (KDE 4.13) */ void setDPI(const QSizeF &dpi); Q_SIGNALS: /** * This signal should be emitted whenever an error occurred in the generator. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void error( const QString &message, int duration ); /** * This signal should be emitted whenever the user should be warned. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void warning( const QString &message, int duration ); /** * This signal should be emitted whenever the user should be noticed. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void notice( const QString &message, int duration ); protected: /** * This method must be called when the pixmap request triggered by generatePixmap() * has been finished. */ void signalPixmapRequestDone( PixmapRequest * request ); /** * This method must be called when a text generation has been finished. */ void signalTextGenerationDone( Page *page, TextPage *textPage ); /** * This method is called when the document is closed and not used * any longer. * * @returns true on success, false otherwise. */ virtual bool doCloseDocument() = 0; /** * Returns the image of the page as specified in * the passed pixmap @p request. * * @warning this method may be executed in its own separated thread if the * @ref Threaded is enabled! */ virtual QImage image( PixmapRequest *page ); /** * Returns the text page for the given @p page. * * @warning this method may be executed in its own separated thread if the * @ref Threaded is enabled! */ virtual TextPage* textPage( Page *page ); /** * Returns a pointer to the document. */ const Document * document() const; /** * Toggle the @p feature . */ void setFeature( GeneratorFeature feature, bool on = true ); /** * Request a meta data of the Document, if available, like an internal * setting. */ QVariant documentMetaData( const QString &key, const QVariant &option = QVariant() ) const; /** * Return the pointer to a mutex the generator can use freely. */ QMutex* userMutex() const; /** * Set the bounding box of a page after the page has already been handed * to the Document. Call this instead of Page::setBoundingBox() to ensure * that all observers are notified. * * @since 0.7 (KDE 4.1) */ void updatePageBoundingBox( int page, const NormalizedRect & boundingBox ); /** * Returns DPI, previously set via setDPI() * @since 0.19 (KDE 4.13) */ QSizeF dpi() const; protected Q_SLOTS: /** * Gets the font data for the given font * * @since 0.8 (KDE 4.1) */ void requestFontData(const Okular::FontInfo &font, QByteArray *data); /** * Asks the generator to dynamically generate a SourceReference for a given * page number and absolute X and Y position on this page. * * @attention Ownership of the returned SourceReference is transferred to the caller. * @since 0.10 (KDE 4.4) */ const SourceReference * dynamicSourceReference( int pageNr, double absX, double absY ); /** * Returns the last print error in case print() failed * @since 0.11 (KDE 4.5) */ Okular::Generator::PrintError printError() const; protected: /// @cond PRIVATE Generator( GeneratorPrivate &dd, QObject *parent, const QVariantList &args ); Q_DECLARE_PRIVATE( Generator ) GeneratorPrivate *d_ptr; friend class Document; friend class DocumentPrivate; /// @endcond PRIVATE private: Q_DISABLE_COPY( Generator ) Q_PRIVATE_SLOT( d_func(), void pixmapGenerationFinished() ) Q_PRIVATE_SLOT( d_func(), void textpageGenerationFinished() ) }; /** * @short Describes a pixmap type request. */ class OKULAR_EXPORT PixmapRequest { friend class Document; friend class DocumentPrivate; public: enum PixmapRequestFeature { NoFeature = 0, Asynchronous = 1, Preload = 2 }; Q_DECLARE_FLAGS( PixmapRequestFeatures, PixmapRequestFeature ) /** * Creates a new pixmap request. * * @param observer The observer. * @param pageNumber The page number. * @param width The width of the page. * @param height The height of the page. * @param priority The priority of the request. * @param features The features of generation. */ PixmapRequest( DocumentObserver *observer, int pageNumber, int width, int height, int priority, PixmapRequestFeatures features ); /** * Destroys the pixmap request. */ ~PixmapRequest(); /** * Returns the observer of the request. */ DocumentObserver *observer() const; /** * Returns the page number of the request. */ int pageNumber() const; /** * Returns the page width of the requested pixmap. */ int width() const; /** * Returns the page height of the requested pixmap. */ int height() const; /** * Returns the priority (less it better, 0 is maximum) of the * request. */ int priority() const; /** * Returns whether the generation should be done synchronous or * asynchronous. * * If asynchronous, the pixmap is created in a thread and the observer * is notified when the job is done. */ bool asynchronous() const; /** * Returns whether the generation request is for a page that is not important * i.e. it's just for speeding up future rendering */ bool preload() const; /** * Returns a pointer to the page where the pixmap shall be generated for. */ Page *page() const; /** * Sets whether the generator should render only the given normalized * rect or the entire page * * @since 0.16 (KDE 4.10) */ void setTile( bool tile ); /** * Returns whether the generator should render just the region given by * normalizedRect() or the entire page. * * @since 0.16 (KDE 4.10) */ bool isTile() const; /** * Sets the region of the page to request. * * @since 0.16 (KDE 4.10) */ void setNormalizedRect( const NormalizedRect &rect ); /** * Returns the normalized region of the page to request. * * @since 0.16 (KDE 4.10) */ const NormalizedRect& normalizedRect() const; private: Q_DISABLE_COPY( PixmapRequest ) friend class PixmapRequestPrivate; PixmapRequestPrivate* const d; }; } Q_DECLARE_METATYPE(Okular::Generator::PrintError) #ifndef QT_NO_DEBUG_STREAM OKULAR_EXPORT QDebug operator<<( QDebug str, const Okular::PixmapRequest &req ); #endif #endif /* kate: replace-tabs on; indent-width 4; */ diff --git a/generators/kimgio/generator_kimgio.cpp b/generators/kimgio/generator_kimgio.cpp index f856434fe..c6822415a 100644 --- a/generators/kimgio/generator_kimgio.cpp +++ b/generators/kimgio/generator_kimgio.cpp @@ -1,195 +1,203 @@ /*************************************************************************** * Copyright (C) 2005 by Albert Astals Cid * * Copyright (C) 2006-2007 by Pino Toscano * * Copyright (C) 2006-2007 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "generator_kimgio.h" #include #include #include #include #include #include #include #include #include #include #include #include static KAboutData createAboutData() { KAboutData aboutData( "okular_kimgio", "okular_kimgio", ki18n( "Image Backend" ), "0.1.2", ki18n( "A simple image backend" ), KAboutData::License_GPL, ki18n( "© 2005, 2009 Albert Astals Cid\n" "© 2006-2007 Pino Toscano\n" "© 2006-2007 Tobias Koenig" ) ); aboutData.addAuthor( ki18n( "Albert Astals Cid" ), KLocalizedString(), "aacid@kde.org" ); aboutData.addAuthor( ki18n( "Pino Toscano" ), KLocalizedString(), "pino@kde.org" ); aboutData.addAuthor( ki18n( "Tobias Koenig" ), KLocalizedString(), "tokoe@kde.org" ); return aboutData; } OKULAR_EXPORT_PLUGIN( KIMGIOGenerator, createAboutData() ) KIMGIOGenerator::KIMGIOGenerator( QObject *parent, const QVariantList &args ) : Generator( parent, args ) { setFeature( ReadRawData ); setFeature( Threaded ); setFeature( TiledRendering ); setFeature( PrintNative ); setFeature( PrintToFile ); + setFeature( SwapBackingFile ); /* setComponentData( *ownComponentData() ); setXMLFile( "gui.rc" ); KAction * kimgio_test = new KAction( this ); kimgio_test->setText( "Image test" ); kimgio_test->setIcon( KIcon( "smiley" ) ); connect( kimgio_test, SIGNAL(triggered(bool)), this, SLOT(slotTest()) ); actionCollection()->addAction( "kimgio_test", kimgio_test ); */ } KIMGIOGenerator::~KIMGIOGenerator() { } bool KIMGIOGenerator::loadDocument( const QString & fileName, QVector & pagesVector ) { const QString mime = KMimeType::findByFileContent(fileName)->name(); const QStringList types = KImageIO::typeForMime(mime); const QByteArray type = !types.isEmpty() ? types[0].toAscii() : QByteArray(); QImageReader reader( fileName, type ); if ( !reader.read( &m_img ) ) { emit error( i18n( "Unable to load document: %1", reader.errorString() ), -1 ); return false; } docInfo.set( Okular::DocumentInfo::MimeType, mime ); // Apply transformations dictated by Exif metadata KExiv2Iface::KExiv2 exifMetadata; if ( exifMetadata.load( fileName ) ) { exifMetadata.rotateExifQImage( m_img, exifMetadata.getImageOrientation() ); } pagesVector.resize( 1 ); Okular::Page * page = new Okular::Page( 0, m_img.width(), m_img.height(), Okular::Rotation0 ); pagesVector[0] = page; return true; } bool KIMGIOGenerator::loadDocumentFromData( const QByteArray & fileData, QVector & pagesVector ) { const QString mime = KMimeType::findByContent(fileData)->name(); const QStringList types = KImageIO::typeForMime(mime); const QByteArray type = !types.isEmpty() ? types[0].toAscii() : QByteArray(); QBuffer buffer; buffer.setData( fileData ); buffer.open( QIODevice::ReadOnly ); QImageReader reader( &buffer, type ); if ( !reader.read( &m_img ) ) { emit error( i18n( "Unable to load document: %1", reader.errorString() ), -1 ); return false; } docInfo.set( Okular::DocumentInfo::MimeType, mime ); // Apply transformations dictated by Exif metadata KExiv2Iface::KExiv2 exifMetadata; if ( exifMetadata.loadFromData( fileData ) ) { exifMetadata.rotateExifQImage( m_img, exifMetadata.getImageOrientation() ); } pagesVector.resize( 1 ); Okular::Page * page = new Okular::Page( 0, m_img.width(), m_img.height(), Okular::Rotation0 ); pagesVector[0] = page; return true; } +bool KIMGIOGenerator::swapBackingFile( QString const &newFileName ) +{ + // NOP: We don't actually need to do anything because all data has already + // been loaded in RAM + return true; +} + bool KIMGIOGenerator::doCloseDocument() { m_img = QImage(); return true; } QImage KIMGIOGenerator::image( Okular::PixmapRequest * request ) { // perform a smooth scaled generation if ( request->isTile() ) { const QRect srcRect = request->normalizedRect().geometry( m_img.width(), m_img.height() ); const QRect destRect = request->normalizedRect().geometry( request->width(), request->height() ); QImage destImg( destRect.size(), QImage::Format_RGB32 ); destImg.fill( Qt::white ); QPainter p( &destImg ); p.setRenderHint( QPainter::SmoothPixmapTransform ); p.drawImage( destImg.rect(), m_img, srcRect ); return destImg; } else { int width = request->width(); int height = request->height(); if ( request->page()->rotation() % 2 == 1 ) qSwap( width, height ); return m_img.scaled( width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); } } bool KIMGIOGenerator::print( QPrinter& printer ) { QPainter p( &printer ); QImage image( m_img ); if ( ( image.width() > printer.width() ) || ( image.height() > printer.height() ) ) image = image.scaled( printer.width(), printer.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); p.drawImage( 0, 0, image ); return true; } void KIMGIOGenerator::slotTest() { kDebug() << "Test"; } const Okular::DocumentInfo * KIMGIOGenerator::generateDocumentInfo() { return &docInfo; } #include "generator_kimgio.moc" diff --git a/generators/kimgio/generator_kimgio.h b/generators/kimgio/generator_kimgio.h index faebd8572..a60c36e72 100644 --- a/generators/kimgio/generator_kimgio.h +++ b/generators/kimgio/generator_kimgio.h @@ -1,47 +1,48 @@ /*************************************************************************** * Copyright (C) 2005 by Albert Astals Cid * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef _OKULAR_GENERATOR_KIMGIO_H_ #define _OKULAR_GENERATOR_KIMGIO_H_ #include #include #include class KIMGIOGenerator : public Okular::Generator { Q_OBJECT public: KIMGIOGenerator( QObject *parent, const QVariantList &args ); virtual ~KIMGIOGenerator(); // [INHERITED] load a document and fill up the pagesVector bool loadDocument( const QString & fileName, QVector & pagesVector ); bool loadDocumentFromData( const QByteArray & fileData, QVector & pagesVector ); + bool swapBackingFile( QString const &newFileName ); // [INHERITED] print document using already configured kprinter bool print( QPrinter& printer ); // [INHERITED] document information const Okular::DocumentInfo * generateDocumentInfo(); protected: bool doCloseDocument(); QImage image( Okular::PixmapRequest * request ); private slots: void slotTest(); private: QImage m_img; Okular::DocumentInfo docInfo; }; #endif diff --git a/part.cpp b/part.cpp index 620530cdc..3ab912317 100644 --- a/part.cpp +++ b/part.cpp @@ -1,3093 +1,3147 @@ /*************************************************************************** * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2002 by Chris Cheney * * Copyright (C) 2002 by Malcolm Hunter * * Copyright (C) 2003-2004 by Christophe Devriese * * * * Copyright (C) 2003 by Daniel Molkentin * * Copyright (C) 2003 by Andy Goossens * * Copyright (C) 2003 by Dirk Mueller * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2004 by Dominique Devriese * * Copyright (C) 2004 by Christoph Cullmann * * Copyright (C) 2004 by Henrique Pinto * * Copyright (C) 2004 by Waldo Bastian * * Copyright (C) 2004-2008 by Albert Astals Cid * * Copyright (C) 2004 by Antti Markus * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "part.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 #include #endif #include #include #include #include #include // local includes #include "aboutdata.h" #include "extensions.h" #include "ui/pageview.h" #include "ui/toc.h" #include "ui/searchwidget.h" #include "ui/thumbnaillist.h" #include "ui/side_reviews.h" #include "ui/minibar.h" #include "ui/embeddedfilesdialog.h" #include "ui/propertiesdialog.h" #include "ui/presentationwidget.h" #include "ui/pagesizelabel.h" #include "ui/bookmarklist.h" #include "ui/findbar.h" #include "ui/sidebar.h" #include "ui/fileprinterpreview.h" #include "ui/guiutils.h" #include "conf/preferencesdialog.h" #include "settings.h" #include "core/action.h" #include "core/annotations.h" #include "core/bookmarkmanager.h" #include "core/document.h" #include "core/generator.h" #include "core/page.h" #include "core/fileprinter.h" #include #include class FileKeeper { public: FileKeeper() : m_handle( NULL ) { } ~FileKeeper() { } void open( const QString & path ) { if ( !m_handle ) m_handle = std::fopen( QFile::encodeName( path ), "r" ); } void close() { if ( m_handle ) { int ret = std::fclose( m_handle ); Q_UNUSED( ret ) m_handle = NULL; } } KTemporaryFile* copyToTemporary() const { if ( !m_handle ) return 0; KTemporaryFile * retFile = new KTemporaryFile; retFile->open(); std::rewind( m_handle ); int c = -1; do { c = std::fgetc( m_handle ); if ( c == EOF ) break; if ( !retFile->putChar( (char)c ) ) break; } while ( !feof( m_handle ) ); retFile->flush(); return retFile; } private: std::FILE * m_handle; }; Okular::PartFactory::PartFactory() : KPluginFactory(okularAboutData( "okular", I18N_NOOP( "Okular" ) )) { } Okular::PartFactory::~PartFactory() { } QObject *Okular::PartFactory::create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword) { Q_UNUSED ( keyword ); Okular::Part *object = new Okular::Part( parentWidget, parent, args, componentData() ); object->setReadWrite( QLatin1String(iface) == QLatin1String("KParts::ReadWritePart") ); return object; } K_EXPORT_PLUGIN( Okular::PartFactory() ) static QAction* actionForExportFormat( const Okular::ExportFormat& format, QObject *parent = 0 ) { QAction *act = new QAction( format.description(), parent ); if ( !format.icon().isNull() ) { act->setIcon( format.icon() ); } return act; } static QString compressedMimeFor( const QString& mime_to_check ) { // The compressedMimeMap is here in case you have a very old shared mime database // that doesn't have inheritance info for things like gzeps, etc // Otherwise the "is()" calls below are just good enough static QHash< QString, QString > compressedMimeMap; static bool supportBzip = false; static bool supportXz = false; const QString app_gzip( QString::fromLatin1( "application/x-gzip" ) ); const QString app_bzip( QString::fromLatin1( "application/x-bzip" ) ); const QString app_xz( QString::fromLatin1( "application/x-xz" ) ); if ( compressedMimeMap.isEmpty() ) { std::auto_ptr< KFilterBase > f; compressedMimeMap[ QString::fromLatin1( "image/x-gzeps" ) ] = app_gzip; // check we can read bzip2-compressed files f.reset( KFilterBase::findFilterByMimeType( app_bzip ) ); if ( f.get() ) { supportBzip = true; compressedMimeMap[ QString::fromLatin1( "application/x-bzpdf" ) ] = app_bzip; compressedMimeMap[ QString::fromLatin1( "application/x-bzpostscript" ) ] = app_bzip; compressedMimeMap[ QString::fromLatin1( "application/x-bzdvi" ) ] = app_bzip; compressedMimeMap[ QString::fromLatin1( "image/x-bzeps" ) ] = app_bzip; } // check we can read XZ-compressed files f.reset( KFilterBase::findFilterByMimeType( app_xz ) ); if ( f.get() ) { supportXz = true; } } QHash< QString, QString >::const_iterator it = compressedMimeMap.constFind( mime_to_check ); if ( it != compressedMimeMap.constEnd() ) return it.value(); KMimeType::Ptr mime = KMimeType::mimeType( mime_to_check ); if ( mime ) { if ( mime->is( app_gzip ) ) return app_gzip; else if ( supportBzip && mime->is( app_bzip ) ) return app_bzip; else if ( supportXz && mime->is( app_xz ) ) return app_xz; } return QString(); } static Okular::EmbedMode detectEmbedMode( QWidget *parentWidget, QObject *parent, const QVariantList &args ) { Q_UNUSED( parentWidget ); if ( parent && ( parent->objectName() == QLatin1String( "okular::Shell" ) || parent->objectName() == QLatin1String( "okular/okular__Shell" ) ) ) return Okular::NativeShellMode; if ( parent && ( QByteArray( "KHTMLPart" ) == parent->metaObject()->className() ) ) return Okular::KHTMLPartMode; Q_FOREACH ( const QVariant &arg, args ) { if ( arg.type() == QVariant::String ) { if ( arg.toString() == QLatin1String( "Print/Preview" ) ) { return Okular::PrintPreviewMode; } else if ( arg.toString() == QLatin1String( "ViewerWidget" ) ) { return Okular::ViewerWidgetMode; } } } return Okular::UnknownEmbedMode; } static QString detectConfigFileName( const QVariantList &args ) { Q_FOREACH ( const QVariant &arg, args ) { if ( arg.type() == QVariant::String ) { QString argString = arg.toString(); int separatorIndex = argString.indexOf( "=" ); if ( separatorIndex >= 0 && argString.left( separatorIndex ) == QLatin1String( "ConfigFileName" ) ) { return argString.mid( separatorIndex + 1 ); } } } return QString(); } #undef OKULAR_KEEP_FILE_OPEN #ifdef OKULAR_KEEP_FILE_OPEN static bool keepFileOpen() { static bool keep_file_open = !qgetenv("OKULAR_NO_KEEP_FILE_OPEN").toInt(); return keep_file_open; } #endif int Okular::Part::numberOfParts = 0; namespace Okular { Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList &args, KComponentData componentData ) : KParts::ReadWritePart(parent), -m_tempfile( 0 ), m_fileWasRemoved( false ), m_showMenuBarAction( 0 ), m_showFullScreenAction( 0 ), m_actionsSearched( false ), +m_tempfile( 0 ), m_swapInsteadOfOpening( false ), m_fileWasRemoved( false ), m_showMenuBarAction( 0 ), m_showFullScreenAction( 0 ), m_actionsSearched( false ), m_cliPresentation(false), m_cliPrint(false), m_embedMode(detectEmbedMode(parentWidget, parent, args)), m_generatorGuiClient(0), m_keeper( 0 ) { // first, we check if a config file name has been specified QString configFileName = detectConfigFileName( args ); if ( configFileName.isEmpty() ) { configFileName = KStandardDirs::locateLocal( "config", "okularpartrc" ); // first necessary step: copy the configuration from kpdf, if available if ( !QFile::exists( configFileName ) ) { QString oldkpdfconffile = KStandardDirs::locateLocal( "config", "kpdfpartrc" ); if ( QFile::exists( oldkpdfconffile ) ) QFile::copy( oldkpdfconffile, configFileName ); } } Okular::Settings::instance( configFileName ); numberOfParts++; if (numberOfParts == 1) { QDBusConnection::sessionBus().registerObject("/okular", this, QDBusConnection::ExportScriptableSlots); } else { QDBusConnection::sessionBus().registerObject(QString("/okular%1").arg(numberOfParts), this, QDBusConnection::ExportScriptableSlots); } // connect the started signal to tell the job the mimetypes we like, // and get some more information from it connect(this, SIGNAL(started(KIO::Job*)), this, SLOT(slotJobStarted(KIO::Job*))); // connect the completed signal so we can put the window caption when loading remote files connect(this, SIGNAL(completed()), this, SLOT(setWindowTitleFromDocument())); connect(this, SIGNAL(canceled(QString)), this, SLOT(loadCancelled(QString))); // create browser extension (for printing when embedded into browser) m_bExtension = new BrowserExtension(this); // create live connect extension (for integrating with browser scripting) new OkularLiveConnectExtension( this ); // we need an instance setComponentData( componentData ); GuiUtils::addIconLoader( iconLoader() ); m_sidebar = new Sidebar( parentWidget ); setWidget( m_sidebar ); connect( m_sidebar, SIGNAL(urlsDropped(KUrl::List)), SLOT(handleDroppedUrls(KUrl::List)) ); // build the document m_document = new Okular::Document(widget()); connect( m_document, SIGNAL(linkFind()), this, SLOT(slotFind()) ); connect( m_document, SIGNAL(linkGoToPage()), this, SLOT(slotGoToPage()) ); connect( m_document, SIGNAL(linkPresentation()), this, SLOT(slotShowPresentation()) ); connect( m_document, SIGNAL(linkEndPresentation()), this, SLOT(slotHidePresentation()) ); connect( m_document, SIGNAL(openUrl(KUrl)), this, SLOT(openUrlFromDocument(KUrl)) ); connect( m_document->bookmarkManager(), SIGNAL(openUrl(KUrl)), this, SLOT(openUrlFromBookmarks(KUrl)) ); connect( m_document, SIGNAL(close()), this, SLOT(close()) ); if ( parent && parent->metaObject()->indexOfSlot( QMetaObject::normalizedSignature( "slotQuit()" ) ) != -1 ) connect( m_document, SIGNAL(quit()), parent, SLOT(slotQuit()) ); else connect( m_document, SIGNAL(quit()), this, SLOT(cannotQuit()) ); // widgets: ^searchbar (toolbar containing label and SearchWidget) // m_searchToolBar = new KToolBar( parentWidget, "searchBar" ); // m_searchToolBar->boxLayout()->setSpacing( KDialog::spacingHint() ); // QLabel * sLabel = new QLabel( i18n( "&Search:" ), m_searchToolBar, "kde toolbar widget" ); // m_searchWidget = new SearchWidget( m_searchToolBar, m_document ); // sLabel->setBuddy( m_searchWidget ); // m_searchToolBar->setStretchableWidget( m_searchWidget ); int tbIndex; // [left toolbox: Table of Contents] | [] m_toc = new TOC( 0, m_document ); connect( m_toc, SIGNAL(hasTOC(bool)), this, SLOT(enableTOC(bool)) ); tbIndex = m_sidebar->addItem( m_toc, KIcon(QApplication::isLeftToRight() ? "format-justify-left" : "format-justify-right"), i18n("Contents") ); enableTOC( false ); // [left toolbox: Thumbnails and Bookmarks] | [] KVBox * thumbsBox = new ThumbnailsBox( 0 ); thumbsBox->setSpacing( 6 ); m_searchWidget = new SearchWidget( thumbsBox, m_document ); m_thumbnailList = new ThumbnailList( thumbsBox, m_document ); // ThumbnailController * m_tc = new ThumbnailController( thumbsBox, m_thumbnailList ); connect( m_thumbnailList, SIGNAL(rightClick(const Okular::Page*,QPoint)), this, SLOT(slotShowMenu(const Okular::Page*,QPoint)) ); tbIndex = m_sidebar->addItem( thumbsBox, KIcon( "view-preview" ), i18n("Thumbnails") ); m_sidebar->setCurrentIndex( tbIndex ); // [left toolbox: Reviews] | [] m_reviewsWidget = new Reviews( 0, m_document ); m_sidebar->addItem( m_reviewsWidget, KIcon("draw-freehand"), i18n("Reviews") ); m_sidebar->setItemEnabled( 2, false ); // [left toolbox: Bookmarks] | [] m_bookmarkList = new BookmarkList( m_document, 0 ); m_sidebar->addItem( m_bookmarkList, KIcon("bookmarks"), i18n("Bookmarks") ); m_sidebar->setItemEnabled( 3, false ); // widgets: [../miniBarContainer] | [] #ifdef OKULAR_ENABLE_MINIBAR QWidget * miniBarContainer = new QWidget( 0 ); m_sidebar->setBottomWidget( miniBarContainer ); QVBoxLayout * miniBarLayout = new QVBoxLayout( miniBarContainer ); miniBarLayout->setMargin( 0 ); // widgets: [../[spacer/..]] | [] miniBarLayout->addItem( new QSpacerItem( 6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed ) ); // widgets: [../[../MiniBar]] | [] QFrame * bevelContainer = new QFrame( miniBarContainer ); bevelContainer->setFrameStyle( QFrame::StyledPanel | QFrame::Sunken ); QVBoxLayout * bevelContainerLayout = new QVBoxLayout( bevelContainer ); bevelContainerLayout->setMargin( 4 ); m_progressWidget = new ProgressWidget( bevelContainer, m_document ); bevelContainerLayout->addWidget( m_progressWidget ); miniBarLayout->addWidget( bevelContainer ); miniBarLayout->addItem( new QSpacerItem( 6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed ) ); #endif // widgets: [] | [right 'pageView'] QWidget * rightContainer = new QWidget( 0 ); m_sidebar->setMainWidget( rightContainer ); QVBoxLayout * rightLayout = new QVBoxLayout( rightContainer ); rightLayout->setMargin( 0 ); rightLayout->setSpacing( 0 ); // KToolBar * rtb = new KToolBar( rightContainer, "mainToolBarSS" ); // rightLayout->addWidget( rtb ); m_topMessage = new KMessageWidget( rightContainer ); m_topMessage->setVisible( false ); m_topMessage->setWordWrap( true ); m_topMessage->setMessageType( KMessageWidget::Information ); m_topMessage->setText( i18n( "This document has embedded files. Click here to see them or go to File -> Embedded Files." ) ); m_topMessage->setIcon( KIcon( "mail-attachment" ) ); connect( m_topMessage, SIGNAL(linkActivated(QString)), this, SLOT(slotShowEmbeddedFiles()) ); rightLayout->addWidget( m_topMessage ); m_formsMessage = new KMessageWidget( rightContainer ); m_formsMessage->setVisible( false ); m_formsMessage->setWordWrap( true ); m_formsMessage->setMessageType( KMessageWidget::Information ); rightLayout->addWidget( m_formsMessage ); m_infoMessage = new KMessageWidget( rightContainer ); m_infoMessage->setVisible( false ); m_infoMessage->setWordWrap( true ); m_infoMessage->setMessageType( KMessageWidget::Information ); rightLayout->addWidget( m_infoMessage ); m_infoTimer = new QTimer(); m_infoTimer->setSingleShot( true ); connect( m_infoTimer, SIGNAL(timeout()), m_infoMessage, SLOT(animatedHide()) ); m_pageView = new PageView( rightContainer, m_document ); QMetaObject::invokeMethod( m_pageView, "setFocus", Qt::QueuedConnection ); //usability setting // m_splitter->setFocusProxy(m_pageView); connect( m_pageView, SIGNAL(rightClick(const Okular::Page*,QPoint)), this, SLOT(slotShowMenu(const Okular::Page*,QPoint)) ); connect( m_document, SIGNAL(error(QString,int)), this, SLOT(errorMessage(QString,int)) ); connect( m_document, SIGNAL(warning(QString,int)), this, SLOT(warningMessage(QString,int)) ); connect( m_document, SIGNAL(notice(QString,int)), this, SLOT(noticeMessage(QString,int)) ); connect( m_document, SIGNAL(sourceReferenceActivated(const QString&,int,int,bool*)), this, SLOT(slotHandleActivatedSourceReference(const QString&,int,int,bool*)) ); rightLayout->addWidget( m_pageView ); m_findBar = new FindBar( m_document, rightContainer ); rightLayout->addWidget( m_findBar ); m_bottomBar = new QWidget( rightContainer ); QHBoxLayout * bottomBarLayout = new QHBoxLayout( m_bottomBar ); m_pageSizeLabel = new PageSizeLabel( m_bottomBar, m_document ); bottomBarLayout->setMargin( 0 ); bottomBarLayout->setSpacing( 0 ); bottomBarLayout->addItem( new QSpacerItem( 5, 5, QSizePolicy::Expanding, QSizePolicy::Minimum ) ); m_miniBarLogic = new MiniBarLogic( this, m_document ); m_miniBar = new MiniBar( m_bottomBar, m_miniBarLogic ); bottomBarLayout->addWidget( m_miniBar ); bottomBarLayout->addWidget( m_pageSizeLabel ); rightLayout->addWidget( m_bottomBar ); m_pageNumberTool = new MiniBar( 0, m_miniBarLogic ); connect( m_findBar, SIGNAL(forwardKeyPressEvent(QKeyEvent*)), m_pageView, SLOT(externalKeyPressEvent(QKeyEvent*))); connect( m_miniBar, SIGNAL(forwardKeyPressEvent(QKeyEvent*)), m_pageView, SLOT(externalKeyPressEvent(QKeyEvent*))); connect( m_pageView, SIGNAL(escPressed()), m_findBar, SLOT(resetSearch()) ); connect( m_pageNumberTool, SIGNAL(forwardKeyPressEvent(QKeyEvent*)), m_pageView, SLOT(externalKeyPressEvent(QKeyEvent*))); connect( m_reviewsWidget, SIGNAL(openAnnotationWindow(Okular::Annotation*,int)), m_pageView, SLOT(openAnnotationWindow(Okular::Annotation*,int)) ); // add document observers m_document->addObserver( this ); m_document->addObserver( m_thumbnailList ); m_document->addObserver( m_pageView ); m_document->registerView( m_pageView ); m_document->addObserver( m_toc ); m_document->addObserver( m_miniBarLogic ); #ifdef OKULAR_ENABLE_MINIBAR m_document->addObserver( m_progressWidget ); #endif m_document->addObserver( m_reviewsWidget ); m_document->addObserver( m_pageSizeLabel ); m_document->addObserver( m_bookmarkList ); connect( m_document->bookmarkManager(), SIGNAL(saved()), this, SLOT(slotRebuildBookmarkMenu()) ); setupViewerActions(); if ( m_embedMode != ViewerWidgetMode ) { setupActions(); } else { setViewerShortcuts(); } // document watcher and reloader m_watcher = new KDirWatch( this ); connect( m_watcher, SIGNAL(created(QString)), this, SLOT(slotFileDirty(QString)) ); connect( m_watcher, SIGNAL(deleted(QString)), this, SLOT(slotFileDirty(QString)) ); connect( m_watcher, SIGNAL(dirty(QString)), this, SLOT(slotFileDirty(QString)) ); m_dirtyHandler = new QTimer( this ); m_dirtyHandler->setSingleShot( true ); connect( m_dirtyHandler, SIGNAL(timeout()),this, SLOT(slotAttemptReload()) ); slotNewConfig(); // keep us informed when the user changes settings connect( Okular::Settings::self(), SIGNAL(configChanged()), this, SLOT(slotNewConfig()) ); // [SPEECH] check for KTTSD presence and usability const KService::Ptr kttsd = KService::serviceByDesktopName("kttsd"); Okular::Settings::setUseKTTSD( kttsd ); Okular::Settings::self()->writeConfig(); rebuildBookmarkMenu( false ); if ( m_embedMode == ViewerWidgetMode ) { // set the XML-UI resource file for the viewer mode setXMLFile("part-viewermode.rc"); } else { // set our main XML-UI resource file setXMLFile("part.rc"); } m_pageView->setupBaseActions( actionCollection() ); m_sidebar->setSidebarVisibility( false ); if ( m_embedMode != PrintPreviewMode ) { // now set up actions that are required for all remaining modes m_pageView->setupViewerActions( actionCollection() ); // and if we are not in viewer mode, we want the full GUI if ( m_embedMode != ViewerWidgetMode ) { unsetDummyMode(); } } // ensure history actions are in the correct state updateViewActions(); // also update the state of the actions in the page view m_pageView->updateActionState( false, false, false ); if ( m_embedMode == NativeShellMode ) m_sidebar->setAutoFillBackground( false ); #ifdef OKULAR_KEEP_FILE_OPEN m_keeper = new FileKeeper(); #endif } void Part::setupViewerActions() { // ACTIONS KActionCollection * ac = actionCollection(); // Page Traversal actions m_gotoPage = KStandardAction::gotoPage( this, SLOT(slotGoToPage()), ac ); m_gotoPage->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_G) ); // dirty way to activate gotopage when pressing miniBar's button connect( m_miniBar, SIGNAL(gotoPage()), m_gotoPage, SLOT(trigger()) ); connect( m_pageNumberTool, SIGNAL(gotoPage()), m_gotoPage, SLOT(trigger()) ); m_prevPage = KStandardAction::prior(this, SLOT(slotPreviousPage()), ac); m_prevPage->setIconText( i18nc( "Previous page", "Previous" ) ); m_prevPage->setToolTip( i18n( "Go back to the Previous Page" ) ); m_prevPage->setWhatsThis( i18n( "Moves to the previous page of the document" ) ); m_prevPage->setShortcut( 0 ); // dirty way to activate prev page when pressing miniBar's button connect( m_miniBar, SIGNAL(prevPage()), m_prevPage, SLOT(trigger()) ); connect( m_pageNumberTool, SIGNAL(prevPage()), m_prevPage, SLOT(trigger()) ); #ifdef OKULAR_ENABLE_MINIBAR connect( m_progressWidget, SIGNAL(prevPage()), m_prevPage, SLOT(trigger()) ); #endif m_nextPage = KStandardAction::next(this, SLOT(slotNextPage()), ac ); m_nextPage->setIconText( i18nc( "Next page", "Next" ) ); m_nextPage->setToolTip( i18n( "Advance to the Next Page" ) ); m_nextPage->setWhatsThis( i18n( "Moves to the next page of the document" ) ); m_nextPage->setShortcut( 0 ); // dirty way to activate next page when pressing miniBar's button connect( m_miniBar, SIGNAL(nextPage()), m_nextPage, SLOT(trigger()) ); connect( m_pageNumberTool, SIGNAL(nextPage()), m_nextPage, SLOT(trigger()) ); #ifdef OKULAR_ENABLE_MINIBAR connect( m_progressWidget, SIGNAL(nextPage()), m_nextPage, SLOT(trigger()) ); #endif m_beginningOfDocument = KStandardAction::firstPage( this, SLOT(slotGotoFirst()), ac ); ac->addAction("first_page", m_beginningOfDocument); m_beginningOfDocument->setText(i18n( "Beginning of the document")); m_beginningOfDocument->setWhatsThis( i18n( "Moves to the beginning of the document" ) ); m_endOfDocument = KStandardAction::lastPage( this, SLOT(slotGotoLast()), ac ); ac->addAction("last_page",m_endOfDocument); m_endOfDocument->setText(i18n( "End of the document")); m_endOfDocument->setWhatsThis( i18n( "Moves to the end of the document" ) ); // we do not want back and next in history in the dummy mode m_historyBack = 0; m_historyNext = 0; m_addBookmark = KStandardAction::addBookmark( this, SLOT(slotAddBookmark()), ac ); m_addBookmarkText = m_addBookmark->text(); m_addBookmarkIcon = m_addBookmark->icon(); m_renameBookmark = ac->addAction("rename_bookmark"); m_renameBookmark->setText(i18n( "Rename Bookmark" )); m_renameBookmark->setIcon(KIcon( "edit-rename" )); m_renameBookmark->setWhatsThis( i18n( "Rename the current bookmark" ) ); connect( m_renameBookmark, SIGNAL(triggered()), this, SLOT(slotRenameCurrentViewportBookmark()) ); m_prevBookmark = ac->addAction("previous_bookmark"); m_prevBookmark->setText(i18n( "Previous Bookmark" )); m_prevBookmark->setIcon(KIcon( "go-up-search" )); m_prevBookmark->setWhatsThis( i18n( "Go to the previous bookmark" ) ); connect( m_prevBookmark, SIGNAL(triggered()), this, SLOT(slotPreviousBookmark()) ); m_nextBookmark = ac->addAction("next_bookmark"); m_nextBookmark->setText(i18n( "Next Bookmark" )); m_nextBookmark->setIcon(KIcon( "go-down-search" )); m_nextBookmark->setWhatsThis( i18n( "Go to the next bookmark" ) ); connect( m_nextBookmark, SIGNAL(triggered()), this, SLOT(slotNextBookmark()) ); m_copy = 0; m_selectAll = 0; // Find and other actions m_find = KStandardAction::find( this, SLOT(slotShowFindBar()), ac ); QList s = m_find->shortcuts(); s.append( QKeySequence( Qt::Key_Slash ) ); m_find->setShortcuts( s ); m_find->setEnabled( false ); m_findNext = KStandardAction::findNext( this, SLOT(slotFindNext()), ac); m_findNext->setEnabled( false ); m_findPrev = KStandardAction::findPrev( this, SLOT(slotFindPrev()), ac ); m_findPrev->setEnabled( false ); m_save = 0; m_saveAs = 0; QAction * prefs = KStandardAction::preferences( this, SLOT(slotPreferences()), ac); if ( m_embedMode == NativeShellMode ) { prefs->setText( i18n( "Configure Okular..." ) ); } else { // TODO: improve this message prefs->setText( i18n( "Configure Viewer..." ) ); } KAction * genPrefs = new KAction( ac ); ac->addAction("options_configure_generators", genPrefs); if ( m_embedMode == ViewerWidgetMode ) { genPrefs->setText( i18n( "Configure Viewer Backends..." ) ); } else { genPrefs->setText( i18n( "Configure Backends..." ) ); } genPrefs->setIcon( KIcon( "configure" ) ); genPrefs->setEnabled( m_document->configurableGenerators() > 0 ); connect( genPrefs, SIGNAL(triggered(bool)), this, SLOT(slotGeneratorPreferences()) ); m_printPreview = KStandardAction::printPreview( this, SLOT(slotPrintPreview()), ac ); m_printPreview->setEnabled( false ); m_showLeftPanel = 0; m_showBottomBar = 0; m_showProperties = ac->addAction("properties"); m_showProperties->setText(i18n("&Properties")); m_showProperties->setIcon(KIcon("document-properties")); connect(m_showProperties, SIGNAL(triggered()), this, SLOT(slotShowProperties())); m_showProperties->setEnabled( false ); m_showEmbeddedFiles = 0; m_showPresentation = 0; m_exportAs = 0; m_exportAsMenu = 0; m_exportAsText = 0; m_aboutBackend = ac->addAction("help_about_backend"); m_aboutBackend->setText(i18n("About Backend")); m_aboutBackend->setEnabled( false ); connect(m_aboutBackend, SIGNAL(triggered()), this, SLOT(slotAboutBackend())); KAction *reload = ac->add( "file_reload" ); reload->setText( i18n( "Reloa&d" ) ); reload->setIcon( KIcon( "view-refresh" ) ); reload->setWhatsThis( i18n( "Reload the current document from disk." ) ); connect( reload, SIGNAL(triggered()), this, SLOT(slotReload()) ); reload->setShortcut( KStandardShortcut::reload() ); m_reload = reload; m_closeFindBar = ac->addAction( "close_find_bar", this, SLOT(slotHideFindBar()) ); m_closeFindBar->setText( i18n("Close &Find Bar") ); m_closeFindBar->setShortcut( QKeySequence(Qt::Key_Escape) ); m_closeFindBar->setEnabled( false ); KAction *pageno = new KAction( i18n( "Page Number" ), ac ); pageno->setDefaultWidget( m_pageNumberTool ); ac->addAction( "page_number", pageno ); } void Part::setViewerShortcuts() { KActionCollection * ac = actionCollection(); m_gotoPage->setShortcut( QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_G) ); m_find->setShortcuts( QList() ); m_findNext->setShortcut( QKeySequence() ); m_findPrev->setShortcut( QKeySequence() ); m_addBookmark->setShortcut( QKeySequence( Qt::CTRL + Qt::ALT + Qt::Key_B ) ); m_beginningOfDocument->setShortcut( QKeySequence( Qt::CTRL + Qt::ALT + Qt::Key_Home ) ); m_endOfDocument->setShortcut( QKeySequence( Qt::CTRL + Qt::ALT + Qt::Key_End ) ); KAction *action = static_cast( ac->action( "file_reload" ) ); if( action ) action->setShortcuts( QList() << QKeySequence( Qt::ALT + Qt::Key_F5 ) ); } void Part::setupActions() { KActionCollection * ac = actionCollection(); m_copy = KStandardAction::create( KStandardAction::Copy, m_pageView, SLOT(copyTextSelection()), ac ); m_selectAll = KStandardAction::selectAll( m_pageView, SLOT(selectAll()), ac ); m_save = KStandardAction::save( this, SLOT(saveFile()), ac ); m_save->setEnabled( false ); m_saveAs = KStandardAction::saveAs( this, SLOT(slotSaveFileAs()), ac ); m_saveAs->setEnabled( false ); m_showLeftPanel = ac->add("show_leftpanel"); m_showLeftPanel->setText(i18n( "Show &Navigation Panel")); m_showLeftPanel->setIcon(KIcon( "view-sidetree" )); connect( m_showLeftPanel, SIGNAL(toggled(bool)), this, SLOT(slotShowLeftPanel()) ); m_showLeftPanel->setShortcut( Qt::Key_F7 ); m_showLeftPanel->setChecked( Okular::Settings::showLeftPanel() ); slotShowLeftPanel(); m_showBottomBar = ac->add("show_bottombar"); m_showBottomBar->setText(i18n( "Show &Page Bar")); connect( m_showBottomBar, SIGNAL(toggled(bool)), this, SLOT(slotShowBottomBar()) ); m_showBottomBar->setChecked( Okular::Settings::showBottomBar() ); slotShowBottomBar(); m_showEmbeddedFiles = ac->addAction("embedded_files"); m_showEmbeddedFiles->setText(i18n("&Embedded Files")); m_showEmbeddedFiles->setIcon( KIcon( "mail-attachment" ) ); connect(m_showEmbeddedFiles, SIGNAL(triggered()), this, SLOT(slotShowEmbeddedFiles())); m_showEmbeddedFiles->setEnabled( false ); m_exportAs = ac->addAction("file_export_as"); m_exportAs->setText(i18n("E&xport As")); m_exportAs->setIcon( KIcon( "document-export" ) ); m_exportAsMenu = new QMenu(); connect(m_exportAsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotExportAs(QAction*))); m_exportAs->setMenu( m_exportAsMenu ); m_exportAsText = actionForExportFormat( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ), m_exportAsMenu ); m_exportAsMenu->addAction( m_exportAsText ); m_exportAs->setEnabled( false ); m_exportAsText->setEnabled( false ); m_showPresentation = ac->addAction("presentation"); m_showPresentation->setText(i18n("P&resentation")); m_showPresentation->setIcon( KIcon( "view-presentation" ) ); connect(m_showPresentation, SIGNAL(triggered()), this, SLOT(slotShowPresentation())); m_showPresentation->setShortcut( QKeySequence( Qt::CTRL + Qt::SHIFT + Qt::Key_P ) ); m_showPresentation->setEnabled( false ); QAction * importPS = ac->addAction("import_ps"); importPS->setText(i18n("&Import PostScript as PDF...")); importPS->setIcon(KIcon("document-import")); connect(importPS, SIGNAL(triggered()), this, SLOT(slotImportPSFile())); #if 0 QAction * ghns = ac->addAction("get_new_stuff"); ghns->setText(i18n("&Get Books From Internet...")); ghns->setIcon(KIcon("get-hot-new-stuff")); connect(ghns, SIGNAL(triggered()), this, SLOT(slotGetNewStuff())); // TEMP, REMOVE ME! ghns->setShortcut( Qt::Key_G ); #endif KToggleAction *blackscreenAction = new KToggleAction( i18n( "Switch Blackscreen Mode" ), ac ); ac->addAction( "switch_blackscreen_mode", blackscreenAction ); blackscreenAction->setShortcut( QKeySequence( Qt::Key_B ) ); blackscreenAction->setIcon( KIcon( "view-presentation" ) ); blackscreenAction->setEnabled( false ); KToggleAction *drawingAction = new KToggleAction( i18n( "Toggle Drawing Mode" ), ac ); ac->addAction( "presentation_drawing_mode", drawingAction ); drawingAction->setIcon( KIcon( "draw-freehand" ) ); drawingAction->setEnabled( false ); KAction *eraseDrawingAction = new KAction( i18n( "Erase Drawings" ), ac ); ac->addAction( "presentation_erase_drawings", eraseDrawingAction ); eraseDrawingAction->setIcon( KIcon( "draw-eraser" ) ); eraseDrawingAction->setEnabled( false ); KAction *configureAnnotations = new KAction( i18n( "Configure Annotations..." ), ac ); ac->addAction( "options_configure_annotations", configureAnnotations ); configureAnnotations->setIcon( KIcon( "configure" ) ); connect(configureAnnotations, SIGNAL(triggered()), this, SLOT(slotAnnotationPreferences())); KAction *playPauseAction = new KAction( i18n( "Play/Pause Presentation" ), ac ); ac->addAction( "presentation_play_pause", playPauseAction ); playPauseAction->setEnabled( false ); } Part::~Part() { GuiUtils::removeIconLoader( iconLoader() ); m_document->removeObserver( this ); if ( m_document->isOpened() ) Part::closeUrl( false ); delete m_toc; delete m_pageView; delete m_thumbnailList; delete m_miniBar; delete m_pageNumberTool; delete m_miniBarLogic; delete m_bottomBar; #ifdef OKULAR_ENABLE_MINIBAR delete m_progressWidget; #endif delete m_pageSizeLabel; delete m_reviewsWidget; delete m_bookmarkList; delete m_infoTimer; delete m_document; delete m_tempfile; qDeleteAll( m_bookmarkActions ); delete m_exportAsMenu; #ifdef OKULAR_KEEP_FILE_OPEN delete m_keeper; #endif } bool Part::openDocument(const KUrl& url, uint page) { Okular::DocumentViewport vp( page - 1 ); vp.rePos.enabled = true; vp.rePos.normalizedX = 0; vp.rePos.normalizedY = 0; vp.rePos.pos = Okular::DocumentViewport::TopLeft; if ( vp.isValid() ) m_document->setNextDocumentViewport( vp ); return openUrl( url ); } void Part::startPresentation() { m_cliPresentation = true; } QStringList Part::supportedMimeTypes() const { return m_document->supportedMimeTypes(); } KUrl Part::realUrl() const { if ( !m_realUrl.isEmpty() ) return m_realUrl; return url(); } // ViewerInterface void Part::showSourceLocation(const QString& fileName, int line, int column, bool showGraphically) { const QString u = QString( "src:%1 %2" ).arg( line + 1 ).arg( fileName ); GotoAction action( QString(), u ); m_document->processAction( &action ); if( showGraphically ) { m_pageView->setLastSourceLocationViewport( m_document->viewport() ); } } void Part::clearLastShownSourceLocation() { m_pageView->clearLastSourceLocationViewport(); } bool Part::isWatchFileModeEnabled() const { return !m_watcher->isStopped(); } void Part::setWatchFileModeEnabled(bool enabled) { if ( enabled && m_watcher->isStopped() ) { m_watcher->startScan(); } else if( !enabled && !m_watcher->isStopped() ) { m_dirtyHandler->stop(); m_watcher->stopScan(); } } bool Part::areSourceLocationsShownGraphically() const { return m_pageView->areSourceLocationsShownGraphically(); } void Part::setShowSourceLocationsGraphically(bool show) { m_pageView->setShowSourceLocationsGraphically(show); } bool Part::openNewFilesInTabs() const { return Okular::Settings::self()->shellOpenFileInTabs(); } void Part::slotHandleActivatedSourceReference(const QString& absFileName, int line, int col, bool *handled) { emit openSourceReference( absFileName, line, col ); if ( m_embedMode == Okular::ViewerWidgetMode ) { *handled = true; } } void Part::openUrlFromDocument(const KUrl &url) { if ( m_embedMode == PrintPreviewMode ) return; if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, widget())) { m_bExtension->openUrlNotify(); m_bExtension->setLocationBarUrl(url.prettyUrl()); openUrl(url); } else { KMessageBox::error( widget(), i18n("Could not open '%1'. File does not exist", url.pathOrUrl() ) ); } } void Part::openUrlFromBookmarks(const KUrl &_url) { KUrl url = _url; Okular::DocumentViewport vp( _url.htmlRef() ); if ( vp.isValid() ) m_document->setNextDocumentViewport( vp ); url.setHTMLRef( QString() ); if ( m_document->currentDocument() == url ) { if ( vp.isValid() ) m_document->setViewport( vp ); } else openUrl( url ); } void Part::handleDroppedUrls( const KUrl::List& urls ) { if ( urls.isEmpty() ) return; if ( m_embedMode != NativeShellMode || !openNewFilesInTabs() ) { openUrlFromDocument( urls.first() ); return; } emit urlsDropped( urls ); } void Part::slotJobStarted(KIO::Job *job) { if (job) { QStringList supportedMimeTypes = m_document->supportedMimeTypes(); job->addMetaData("accept", supportedMimeTypes.join(", ") + ", */*;q=0.5"); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotJobFinished(KJob*))); } } void Part::slotJobFinished(KJob *job) { if ( job->error() == KIO::ERR_USER_CANCELED ) { m_pageView->displayMessage( i18n( "The loading of %1 has been canceled.", realUrl().pathOrUrl() ) ); } } void Part::loadCancelled(const QString &reason) { emit setWindowCaption( QString() ); resetStartArguments(); // when m_viewportDirty.pageNumber != -1 we come from slotAttemptReload // so we don't want to show an ugly messagebox just because the document is // taking more than usual to be recreated if (m_viewportDirty.pageNumber == -1) { if (!reason.isEmpty()) { KMessageBox::error( widget(), i18n("Could not open %1. Reason: %2", url().prettyUrl(), reason ) ); } } } void Part::setWindowTitleFromDocument() { // If 'DocumentTitle' should be used, check if the document has one. If // either case is false, use the file name. QString title = Okular::Settings::displayDocumentNameOrPath() == Okular::Settings::EnumDisplayDocumentNameOrPath::Path ? realUrl().pathOrUrl() : realUrl().fileName(); if ( Okular::Settings::displayDocumentTitle() ) { const QString docTitle = m_document->metaData( "DocumentTitle" ).toString(); if ( !docTitle.isEmpty() && !docTitle.trimmed().isEmpty() ) { title = docTitle; } } emit setWindowCaption( title ); } KConfigDialog * Part::slotGeneratorPreferences( ) { // Create dialog KConfigDialog * dialog = new KConfigDialog( m_pageView, "generator_prefs", Okular::Settings::self() ); dialog->setAttribute( Qt::WA_DeleteOnClose ); if( m_embedMode == ViewerWidgetMode ) { dialog->setCaption( i18n( "Configure Viewer Backends" ) ); } else { dialog->setCaption( i18n( "Configure Backends" ) ); } m_document->fillConfigDialog( dialog ); // Show it dialog->setWindowModality( Qt::ApplicationModal ); dialog->show(); return dialog; } void Part::notifySetup( const QVector< Okular::Page * > & /*pages*/, int setupFlags ) { if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) ) return; rebuildBookmarkMenu(); updateAboutBackendAction(); m_findBar->resetSearch(); m_searchWidget->setEnabled( m_document->supportsSearching() ); } void Part::notifyViewportChanged( bool /*smoothMove*/ ) { updateViewActions(); } void Part::notifyPageChanged( int page, int flags ) { if ( flags & Okular::DocumentObserver::NeedSaveAs ) setModified(); if ( !(flags & Okular::DocumentObserver::Bookmark ) ) return; rebuildBookmarkMenu(); if ( page == m_document->viewport().pageNumber ) updateBookmarksActions(); } void Part::goToPage(uint i) { if ( i <= m_document->pages() ) m_document->setViewportPage( i - 1 ); } void Part::openDocument( const QString &doc ) { openUrl( KUrl( doc ) ); } uint Part::pages() { return m_document->pages(); } uint Part::currentPage() { return m_document->pages() ? m_document->currentPage() + 1 : 0; } QString Part::currentDocument() { return m_document->currentDocument().pathOrUrl(); } QString Part::documentMetaData( const QString &metaData ) const { const Okular::DocumentInfo * info = m_document->documentInfo(); if ( info ) { QDomElement docElement = info->documentElement(); for ( QDomNode node = docElement.firstChild(); !node.isNull(); node = node.nextSibling() ) { const QDomElement element = node.toElement(); if ( metaData.compare( element.tagName(), Qt::CaseInsensitive ) == 0 ) return element.attribute( "value" ); } } return QString(); } bool Part::slotImportPSFile() { QString app = KStandardDirs::findExe( "ps2pdf" ); if ( app.isEmpty() ) { // TODO point the user to their distro packages? KMessageBox::error( widget(), i18n( "The program \"ps2pdf\" was not found, so Okular can not import PS files using it." ), i18n("ps2pdf not found") ); return false; } KUrl url = KFileDialog::getOpenUrl( KUrl(), "application/postscript", this->widget() ); if ( url.isLocalFile() ) { KTemporaryFile tf; tf.setSuffix( ".pdf" ); tf.setAutoRemove( false ); if ( !tf.open() ) return false; m_temporaryLocalFile = tf.fileName(); tf.close(); setLocalFilePath( url.toLocalFile() ); QStringList args; QProcess *p = new QProcess(); args << url.toLocalFile() << m_temporaryLocalFile; m_pageView->displayMessage(i18n("Importing PS file as PDF (this may take a while)...")); connect(p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(psTransformEnded(int,QProcess::ExitStatus))); p->start(app, args); return true; } m_temporaryLocalFile.clear(); return false; } void Part::setFileToWatch( const QString &filePath ) { if ( !m_watchedFilePath.isEmpty() ) unsetFileToWatch(); const QFileInfo fi(filePath); m_watchedFilePath = filePath; m_watcher->addFile( m_watchedFilePath ); if ( fi.isSymLink() ) { m_watchedFileSymlinkTarget = fi.readLink(); m_watcher->addFile( m_watchedFileSymlinkTarget ); } else { m_watchedFileSymlinkTarget.clear(); } } void Part::unsetFileToWatch() { if ( m_watchedFilePath.isEmpty() ) return; m_watcher->removeFile( m_watchedFilePath ); if ( !m_watchedFileSymlinkTarget.isEmpty() ) m_watcher->removeFile( m_watchedFileSymlinkTarget ); m_watchedFilePath.clear(); m_watchedFileSymlinkTarget.clear(); } bool Part::openFile() { KMimeType::Ptr mime; QString fileNameToOpen = localFilePath(); const bool isstdin = url().isLocalFile() && url().fileName( KUrl::ObeyTrailingSlash ) == QLatin1String( "-" ); const QFileInfo fileInfo( fileNameToOpen ); if ( !isstdin && !fileInfo.exists() ) return false; if ( !arguments().mimeType().isEmpty() ) { mime = KMimeType::mimeType( arguments().mimeType() ); } if ( !mime ) { mime = KMimeType::findByPath( fileNameToOpen ); } bool isCompressedFile = false; bool uncompressOk = true; QString compressedMime = compressedMimeFor( mime->name() ); if ( compressedMime.isEmpty() ) compressedMime = compressedMimeFor( mime->parentMimeType() ); if ( !compressedMime.isEmpty() ) { isCompressedFile = true; uncompressOk = handleCompressed( fileNameToOpen, localFilePath(), compressedMime ); mime = KMimeType::findByPath( fileNameToOpen ); } + + if ( m_swapInsteadOfOpening ) + { + m_swapInsteadOfOpening = false; + + if ( !uncompressOk ) + return false; + + if ( mime->is( "application/vnd.kde.okular-archive" ) ) + { + isDocumentArchive = true; + return m_document->swapBackingFileArchive( fileNameToOpen, url() ); + } + else + { + isDocumentArchive = false; + return m_document->swapBackingFile( fileNameToOpen, url() ); + } + } + Document::OpenResult openResult = Document::OpenError; isDocumentArchive = false; if ( uncompressOk ) { if ( mime->is( "application/vnd.kde.okular-archive" ) ) { openResult = m_document->openDocumentArchive( fileNameToOpen, url() ); isDocumentArchive = true; } else { openResult = m_document->openDocument( fileNameToOpen, url(), mime ); } // if the file didn't open correctly it might be encrypted, so ask for a pass QString walletName, walletFolder, walletKey; m_document->walletDataForFile(fileNameToOpen, &walletName, &walletFolder, &walletKey); bool firstInput = true; bool triedWallet = false; KWallet::Wallet * wallet = 0; bool keep = true; while ( openResult == Document::OpenNeedsPassword ) { QString password; // 1.A. try to retrieve the first password from the kde wallet system if ( !triedWallet && !walletKey.isNull() ) { const WId parentwid = widget()->effectiveWinId(); wallet = KWallet::Wallet::openWallet( walletName, parentwid ); if ( wallet ) { // use the KPdf folder (and create if missing) if ( !wallet->hasFolder( walletFolder ) ) wallet->createFolder( walletFolder ); wallet->setFolder( walletFolder ); // look for the pass in that folder QString retrievedPass; if ( !wallet->readPassword( walletKey, retrievedPass ) ) password = retrievedPass; } triedWallet = true; } // 1.B. if not retrieved, ask the password using the kde password dialog if ( password.isNull() ) { QString prompt; if ( firstInput ) prompt = i18n( "Please enter the password to read the document:" ); else prompt = i18n( "Incorrect password. Try again:" ); firstInput = false; // if the user presses cancel, abort opening KPasswordDialog dlg( widget(), wallet ? KPasswordDialog::ShowKeepPassword : KPasswordDialog::KPasswordDialogFlags() ); dlg.setCaption( i18n( "Document Password" ) ); dlg.setPrompt( prompt ); if( !dlg.exec() ) break; password = dlg.password(); if ( wallet ) keep = dlg.keepPassword(); } // 2. reopen the document using the password if ( mime->is( "application/vnd.kde.okular-archive" ) ) { openResult = m_document->openDocumentArchive( fileNameToOpen, url(), password ); isDocumentArchive = true; } else { openResult = m_document->openDocument( fileNameToOpen, url(), mime, password ); } // 3. if the password is correct and the user chose to remember it, store it to the wallet if ( openResult == Document::OpenSuccess && wallet && /*safety check*/ wallet->isOpen() && keep ) { wallet->writePassword( walletKey, password ); } } } bool canSearch = m_document->supportsSearching(); emit mimeTypeChanged( mime ); // update one-time actions const bool ok = openResult == Document::OpenSuccess; emit enableCloseAction( ok ); m_find->setEnabled( ok && canSearch ); m_findNext->setEnabled( ok && canSearch ); m_findPrev->setEnabled( ok && canSearch ); if( m_save ) m_save->setEnabled( ok && !( isstdin || mime->is( "inode/directory" ) ) ); if( m_saveAs ) m_saveAs->setEnabled( ok && !( isstdin || mime->is( "inode/directory" ) ) ); emit enablePrintAction( ok && m_document->printingSupport() != Okular::Document::NoPrinting ); m_printPreview->setEnabled( ok && m_document->printingSupport() != Okular::Document::NoPrinting ); m_showProperties->setEnabled( ok ); bool hasEmbeddedFiles = ok && m_document->embeddedFiles() && m_document->embeddedFiles()->count() > 0; if ( m_showEmbeddedFiles ) m_showEmbeddedFiles->setEnabled( hasEmbeddedFiles ); m_topMessage->setVisible( hasEmbeddedFiles && Okular::Settings::showOSD() ); // Warn the user that XFA forms are not supported yet (NOTE: poppler generator only) if ( ok && m_document->metaData( "HasUnsupportedXfaForm" ).toBool() == true ) { m_formsMessage->setText( i18n( "This document has XFA forms, which are currently unsupported." ) ); m_formsMessage->setIcon( KIcon( "dialog-warning" ) ); m_formsMessage->setMessageType( KMessageWidget::Warning ); m_formsMessage->setVisible( true ); } // m_pageView->toggleFormsAction() may be null on dummy mode else if ( ok && m_pageView->toggleFormsAction() && m_pageView->toggleFormsAction()->isEnabled() ) { m_formsMessage->setText( i18n( "This document has forms. Click on the button to interact with them, or use View -> Show Forms." ) ); m_formsMessage->setMessageType( KMessageWidget::Information ); m_formsMessage->setVisible( true ); } else { m_formsMessage->setVisible( false ); } if ( m_showPresentation ) m_showPresentation->setEnabled( ok ); if ( ok ) { if ( m_exportAs ) { m_exportFormats = m_document->exportFormats(); QList::ConstIterator it = m_exportFormats.constBegin(); QList::ConstIterator itEnd = m_exportFormats.constEnd(); QMenu *menu = m_exportAs->menu(); for ( ; it != itEnd; ++it ) { menu->addAction( actionForExportFormat( *it ) ); } } if ( isCompressedFile ) { m_realUrl = url(); } #ifdef OKULAR_KEEP_FILE_OPEN if ( keepFileOpen() ) m_keeper->open( fileNameToOpen ); #endif } if ( m_exportAsText ) m_exportAsText->setEnabled( ok && m_document->canExportToText() ); if ( m_exportAs ) m_exportAs->setEnabled( ok ); // update viewing actions updateViewActions(); m_fileWasRemoved = false; if ( !ok ) { // if can't open document, update windows so they display blank contents m_pageView->viewport()->update(); m_thumbnailList->update(); setUrl( KUrl() ); return false; } // set the file to the fileWatcher if ( url().isLocalFile() ) setFileToWatch( localFilePath() ); // if the 'OpenTOC' flag is set, open the TOC if ( m_document->metaData( "OpenTOC" ).toBool() && m_sidebar->isItemEnabled( 0 ) && !m_sidebar->isCollapsed() ) { m_sidebar->setCurrentIndex( 0, Sidebar::DoNotUncollapseIfCollapsed ); } // if the 'StartFullScreen' flag is set, or the command line flag was // specified, start presentation if ( m_document->metaData( "StartFullScreen" ).toBool() || m_cliPresentation ) { bool goAheadWithPresentationMode = true; if ( !m_cliPresentation ) { const QString text = i18n( "The document requested to be launched in presentation mode.\n" "Do you want to allow it?" ); const QString caption = i18n( "Presentation Mode" ); const KGuiItem yesItem = KGuiItem( i18n( "Allow" ), "dialog-ok", i18n( "Allow the presentation mode" ) ); const KGuiItem noItem = KGuiItem( i18n( "Do Not Allow" ), "process-stop", i18n( "Do not allow the presentation mode" ) ); const int result = KMessageBox::questionYesNo( widget(), text, caption, yesItem, noItem ); if ( result == KMessageBox::No ) goAheadWithPresentationMode = false; } m_cliPresentation = false; if ( goAheadWithPresentationMode ) QMetaObject::invokeMethod( this, "slotShowPresentation", Qt::QueuedConnection ); } m_generatorGuiClient = factory() ? m_document->guiClient() : 0; if ( m_generatorGuiClient ) factory()->addClient( m_generatorGuiClient ); if ( m_cliPrint ) { m_cliPrint = false; slotPrint(); } return true; } bool Part::openUrl(const KUrl &_url) { // Close current document if any if ( !closeUrl() ) return false; KUrl url( _url ); if ( url.hasHTMLRef() ) { const QString dest = url.htmlRef(); bool ok = true; const int page = dest.toInt( &ok ); if ( ok ) { Okular::DocumentViewport vp( page - 1 ); vp.rePos.enabled = true; vp.rePos.normalizedX = 0; vp.rePos.normalizedY = 0; vp.rePos.pos = Okular::DocumentViewport::TopLeft; m_document->setNextDocumentViewport( vp ); } else { m_document->setNextDocumentDestination( dest ); } url.setHTMLRef( QString() ); } // this calls in sequence the 'closeUrl' and 'openFile' methods bool openOk = KParts::ReadWritePart::openUrl( url ); if ( openOk ) { m_viewportDirty.pageNumber = -1; setWindowTitleFromDocument(); } else { resetStartArguments(); KMessageBox::error( widget(), i18n("Could not open %1", url.pathOrUrl() ) ); } return openOk; } bool Part::queryClose() { if ( !isReadWrite() || !isModified() ) return true; const int res = KMessageBox::warningYesNoCancel( widget(), i18n( "Do you want to save your annotation changes or discard them?" ), i18n( "Close Document" ), KStandardGuiItem::save(), KStandardGuiItem::discard() ); switch ( res ) { case KMessageBox::Yes: // Save as saveFile(); return !isModified(); // Only allow closing if file was really saved case KMessageBox::No: // Discard return true; default: // Cancel return false; } } bool Part::closeUrl(bool promptToSave) { if ( promptToSave && !queryClose() ) return false; + if ( m_swapInsteadOfOpening ) + { + // If we're swapping the backing file, we don't want to close the + // current one when openUrl() calls us internally + return true; // pretend it worked + } + setModified( false ); if (!m_temporaryLocalFile.isNull() && m_temporaryLocalFile != localFilePath()) { QFile::remove( m_temporaryLocalFile ); m_temporaryLocalFile.clear(); } slotHidePresentation(); emit enableCloseAction( false ); m_find->setEnabled( false ); m_findNext->setEnabled( false ); m_findPrev->setEnabled( false ); if( m_save ) m_save->setEnabled( false ); if( m_saveAs ) m_saveAs->setEnabled( false ); m_printPreview->setEnabled( false ); m_showProperties->setEnabled( false ); if ( m_showEmbeddedFiles ) m_showEmbeddedFiles->setEnabled( false ); if ( m_exportAs ) m_exportAs->setEnabled( false ); if ( m_exportAsText ) m_exportAsText->setEnabled( false ); m_exportFormats.clear(); if ( m_exportAs ) { QMenu *menu = m_exportAs->menu(); QList acts = menu->actions(); int num = acts.count(); for ( int i = 1; i < num; ++i ) { menu->removeAction( acts.at(i) ); delete acts.at(i); } } if ( m_showPresentation ) m_showPresentation->setEnabled( false ); emit setWindowCaption(""); emit enablePrintAction(false); m_realUrl = KUrl(); if ( url().isLocalFile() ) unsetFileToWatch(); m_fileWasRemoved = false; if ( m_generatorGuiClient ) factory()->removeClient( m_generatorGuiClient ); m_generatorGuiClient = 0; m_document->closeDocument(); updateViewActions(); delete m_tempfile; m_tempfile = 0; if ( widget() ) { m_searchWidget->clearText(); m_topMessage->setVisible( false ); m_formsMessage->setVisible( false ); } #ifdef OKULAR_KEEP_FILE_OPEN m_keeper->close(); #endif bool r = KParts::ReadWritePart::closeUrl(); setUrl(KUrl()); return r; } bool Part::closeUrl() { return closeUrl( true ); } void Part::guiActivateEvent(KParts::GUIActivateEvent *event) { updateViewActions(); KParts::ReadWritePart::guiActivateEvent(event); } void Part::close() { if ( m_embedMode == NativeShellMode ) { closeUrl(); } else KMessageBox::information( widget(), i18n( "This link points to a close document action that does not work when using the embedded viewer." ), QString(), "warnNoCloseIfNotInOkular" ); } void Part::cannotQuit() { KMessageBox::information( widget(), i18n( "This link points to a quit application action that does not work when using the embedded viewer." ), QString(), "warnNoQuitIfNotInOkular" ); } void Part::slotShowLeftPanel() { bool showLeft = m_showLeftPanel->isChecked(); Okular::Settings::setShowLeftPanel( showLeft ); Okular::Settings::self()->writeConfig(); // show/hide left panel m_sidebar->setSidebarVisibility( showLeft ); } void Part::slotShowBottomBar() { const bool showBottom = m_showBottomBar->isChecked(); Okular::Settings::setShowBottomBar( showBottom ); Okular::Settings::self()->writeConfig(); // show/hide bottom bar m_bottomBar->setVisible( showBottom ); } void Part::slotFileDirty( const QString& path ) { kDebug() << path; // The beauty of this is that each start cancels the previous one. // This means that timeout() is only fired when there have // no changes to the file for the last 750 milisecs. // This ensures that we don't update on every other byte that gets // written to the file. if ( path == localFilePath() ) { // Only start watching the file in case if it wasn't removed if (QFile::exists(localFilePath())) m_dirtyHandler->start( 750 ); else m_fileWasRemoved = true; } else { const QFileInfo fi(localFilePath()); if ( fi.absolutePath() == path ) { // Our parent has been dirtified if (!QFile::exists(localFilePath())) { m_fileWasRemoved = true; } else if (m_fileWasRemoved && QFile::exists(localFilePath())) { // we need to watch the new file unsetFileToWatch(); setFileToWatch( localFilePath() ); m_dirtyHandler->start( 750 ); } } else if ( fi.isSymLink() && fi.readLink() == path ) { if ( QFile::exists( fi.readLink() )) m_dirtyHandler->start( 750 ); else m_fileWasRemoved = true; } } } // Attempt to reload the document, one or more times, optionally from a different URL void Part::slotAttemptReload( bool oneShot, const KUrl &newUrl ) { bool tocReloadPrepared = false; // do the following the first time the file is reloaded if ( m_viewportDirty.pageNumber == -1 ) { // store the url of the current document m_oldUrl = newUrl.isEmpty() ? url() : newUrl; // store the current viewport m_viewportDirty = m_document->viewport(); // store the current toolbox pane m_dirtyToolboxIndex = m_sidebar->currentIndex(); m_wasSidebarVisible = m_sidebar->isSidebarVisible(); m_wasSidebarCollapsed = m_sidebar->isCollapsed(); // store if presentation view was open m_wasPresentationOpen = ((PresentationWidget*)m_presentationWidget != 0); // preserves the toc state after reload m_toc->prepareForReload(); tocReloadPrepared = true; // store the page rotation m_dirtyPageRotation = m_document->rotation(); // inform the user about the operation in progress // TODO: Remove this line and integrate reload info in queryClose m_pageView->displayMessage( i18n("Reloading the document...") ); } // close and (try to) reopen the document if ( !closeUrl() ) { m_viewportDirty.pageNumber = -1; if ( tocReloadPrepared ) { m_toc->rollbackReload(); } return; } if ( tocReloadPrepared ) m_toc->finishReload(); // inform the user about the operation in progress m_pageView->displayMessage( i18n("Reloading the document...") ); if ( KParts::ReadWritePart::openUrl( m_oldUrl ) ) { // on successful opening, restore the previous viewport if ( m_viewportDirty.pageNumber >= (int) m_document->pages() ) m_viewportDirty.pageNumber = (int) m_document->pages() - 1; m_document->setViewport( m_viewportDirty ); m_oldUrl = KUrl(); m_viewportDirty.pageNumber = -1; m_document->setRotation( m_dirtyPageRotation ); if ( m_sidebar->currentIndex() != m_dirtyToolboxIndex && m_sidebar->isItemEnabled( m_dirtyToolboxIndex ) && !m_sidebar->isCollapsed() ) { m_sidebar->setCurrentIndex( m_dirtyToolboxIndex ); } if ( m_sidebar->isSidebarVisible() != m_wasSidebarVisible ) { m_sidebar->setSidebarVisibility( m_wasSidebarVisible ); } if ( m_sidebar->isCollapsed() != m_wasSidebarCollapsed ) { m_sidebar->setCollapsed( m_wasSidebarCollapsed ); } if (m_wasPresentationOpen) slotShowPresentation(); emit enablePrintAction(true && m_document->printingSupport() != Okular::Document::NoPrinting); } else if ( !oneShot ) { // start watching the file again (since we dropped it on close) setFileToWatch( localFilePath() ); m_dirtyHandler->start( 750 ); } } void Part::updateViewActions() { bool opened = m_document->pages() > 0; if ( opened ) { m_gotoPage->setEnabled( m_document->pages() > 1 ); // Check if you are at the beginning or not if (m_document->currentPage() != 0) { m_beginningOfDocument->setEnabled( true ); m_prevPage->setEnabled( true ); } else { if (m_pageView->verticalScrollBar()->value() != 0) { // The page isn't at the very beginning m_beginningOfDocument->setEnabled( true ); } else { // The page is at the very beginning of the document m_beginningOfDocument->setEnabled( false ); } // The document is at the first page, you can go to a page before m_prevPage->setEnabled( false ); } if (m_document->pages() == m_document->currentPage() + 1 ) { // If you are at the end, disable go to next page m_nextPage->setEnabled( false ); if (m_pageView->verticalScrollBar()->value() == m_pageView->verticalScrollBar()->maximum()) { // If you are the end of the page of the last document, you can't go to the last page m_endOfDocument->setEnabled( false ); } else { // Otherwise you can move to the endif m_endOfDocument->setEnabled( true ); } } else { // If you are not at the end, enable go to next page m_nextPage->setEnabled( true ); m_endOfDocument->setEnabled( true ); } if (m_historyBack) m_historyBack->setEnabled( !m_document->historyAtBegin() ); if (m_historyNext) m_historyNext->setEnabled( !m_document->historyAtEnd() ); m_reload->setEnabled( true ); if (m_copy) m_copy->setEnabled( true ); if (m_selectAll) m_selectAll->setEnabled( true ); } else { m_gotoPage->setEnabled( false ); m_beginningOfDocument->setEnabled( false ); m_endOfDocument->setEnabled( false ); m_prevPage->setEnabled( false ); m_nextPage->setEnabled( false ); if (m_historyBack) m_historyBack->setEnabled( false ); if (m_historyNext) m_historyNext->setEnabled( false ); m_reload->setEnabled( false ); if (m_copy) m_copy->setEnabled( false ); if (m_selectAll) m_selectAll->setEnabled( false ); } if ( factory() ) { QWidget *menu = factory()->container("menu_okular_part_viewer", this); if (menu) menu->setEnabled( opened ); menu = factory()->container("view_orientation", this); if (menu) menu->setEnabled( opened ); } emit viewerMenuStateChange( opened ); updateBookmarksActions(); } void Part::updateBookmarksActions() { bool opened = m_document->pages() > 0; if ( opened ) { m_addBookmark->setEnabled( true ); if ( m_document->bookmarkManager()->isBookmarked( m_document->viewport() ) ) { m_addBookmark->setText( i18n( "Remove Bookmark" ) ); m_addBookmark->setIcon( KIcon( "edit-delete-bookmark" ) ); m_renameBookmark->setEnabled( true ); } else { m_addBookmark->setText( m_addBookmarkText ); m_addBookmark->setIcon( m_addBookmarkIcon ); m_renameBookmark->setEnabled( false ); } } else { m_addBookmark->setEnabled( false ); m_addBookmark->setText( m_addBookmarkText ); m_addBookmark->setIcon( m_addBookmarkIcon ); m_renameBookmark->setEnabled( false ); } } void Part::enableTOC(bool enable) { m_sidebar->setItemEnabled(0, enable); // If present, show the TOC when a document is opened if ( enable ) { m_sidebar->setCurrentIndex( 0, Sidebar::DoNotUncollapseIfCollapsed ); } } void Part::slotRebuildBookmarkMenu() { rebuildBookmarkMenu(); } void Part::slotShowFindBar() { m_findBar->show(); m_findBar->focusAndSetCursor(); m_closeFindBar->setEnabled( true ); } void Part::slotHideFindBar() { if ( m_findBar->maybeHide() ) { m_pageView->setFocus(); m_closeFindBar->setEnabled( false ); } } //BEGIN go to page dialog class GotoPageDialog : public KDialog { public: GotoPageDialog(QWidget *p, int current, int max) : KDialog(p) { setCaption(i18n("Go to Page")); setButtons(Ok | Cancel); setDefaultButton(Ok); QWidget *w = new QWidget(this); setMainWidget(w); QVBoxLayout *topLayout = new QVBoxLayout(w); topLayout->setMargin(0); topLayout->setSpacing(spacingHint()); e1 = new KIntNumInput(current, w); e1->setRange(1, max); e1->setEditFocus(true); e1->setSliderEnabled(true); QLabel *label = new QLabel(i18n("&Page:"), w); label->setBuddy(e1); topLayout->addWidget(label); topLayout->addWidget(e1); // A little bit extra space topLayout->addSpacing(spacingHint()); topLayout->addStretch(10); e1->setFocus(); } int getPage() const { return e1->value(); } protected: KIntNumInput *e1; }; //END go to page dialog void Part::slotGoToPage() { GotoPageDialog pageDialog( m_pageView, m_document->currentPage() + 1, m_document->pages() ); if ( pageDialog.exec() == QDialog::Accepted ) m_document->setViewportPage( pageDialog.getPage() - 1 ); } void Part::slotPreviousPage() { if ( m_document->isOpened() && !(m_document->currentPage() < 1) ) m_document->setViewportPage( m_document->currentPage() - 1 ); } void Part::slotNextPage() { if ( m_document->isOpened() && m_document->currentPage() < (m_document->pages() - 1) ) m_document->setViewportPage( m_document->currentPage() + 1 ); } void Part::slotGotoFirst() { if ( m_document->isOpened() ) { m_document->setViewportPage( 0 ); m_beginningOfDocument->setEnabled( false ); } } void Part::slotGotoLast() { if ( m_document->isOpened() ) { DocumentViewport endPage(m_document->pages() -1 ); endPage.rePos.enabled = true; endPage.rePos.normalizedX = 0; endPage.rePos.normalizedY = 1; endPage.rePos.pos = Okular::DocumentViewport::TopLeft; m_document->setViewport(endPage); m_endOfDocument->setEnabled(false); } } void Part::slotHistoryBack() { m_document->setPrevViewport(); } void Part::slotHistoryNext() { m_document->setNextViewport(); } void Part::slotAddBookmark() { DocumentViewport vp = m_document->viewport(); if ( m_document->bookmarkManager()->isBookmarked( vp ) ) { m_document->bookmarkManager()->removeBookmark( vp ); } else { m_document->bookmarkManager()->addBookmark( vp ); } } void Part::slotRenameBookmark( const DocumentViewport &viewport ) { Q_ASSERT(m_document->bookmarkManager()->isBookmarked( viewport )); if ( m_document->bookmarkManager()->isBookmarked( viewport ) ) { KBookmark bookmark = m_document->bookmarkManager()->bookmark( viewport ); const QString newName = KInputDialog::getText( i18n( "Rename Bookmark" ), i18n( "Enter the new name of the bookmark:" ), bookmark.fullText(), 0, widget()); if (!newName.isEmpty()) { m_document->bookmarkManager()->renameBookmark(&bookmark, newName); } } } void Part::slotRenameBookmarkFromMenu() { QAction *action = dynamic_cast(sender()); Q_ASSERT( action ); if ( action ) { DocumentViewport vp( action->data().toString() ); slotRenameBookmark( vp ); } } void Part::slotRenameCurrentViewportBookmark() { slotRenameBookmark( m_document->viewport() ); } void Part::slotAboutToShowContextMenu(KMenu * /*menu*/, QAction *action, QMenu *contextMenu) { const QList actions = contextMenu->findChildren("OkularPrivateRenameBookmarkActions"); foreach(QAction *a, actions) { contextMenu->removeAction(a); delete a; } KBookmarkAction *ba = dynamic_cast(action); if (ba != NULL) { QAction *separatorAction = contextMenu->addSeparator(); separatorAction->setObjectName("OkularPrivateRenameBookmarkActions"); QAction *renameAction = contextMenu->addAction( KIcon( "edit-rename" ), i18n( "Rename this Bookmark" ), this, SLOT(slotRenameBookmarkFromMenu()) ); renameAction->setData(ba->property("htmlRef").toString()); renameAction->setObjectName("OkularPrivateRenameBookmarkActions"); } } void Part::slotPreviousBookmark() { const KBookmark bookmark = m_document->bookmarkManager()->previousBookmark( m_document->viewport() ); if ( !bookmark.isNull() ) { DocumentViewport vp( bookmark.url().htmlRef() ); m_document->setViewport( vp ); } } void Part::slotNextBookmark() { const KBookmark bookmark = m_document->bookmarkManager()->nextBookmark( m_document->viewport() ); if ( !bookmark.isNull() ) { DocumentViewport vp( bookmark.url().htmlRef() ); m_document->setViewport( vp ); } } void Part::slotFind() { // when in presentation mode, there's already a search bar, taking care of // the 'find' requests if ( (PresentationWidget*)m_presentationWidget != 0 ) { m_presentationWidget->slotFind(); } else { slotShowFindBar(); } } void Part::slotFindNext() { if (m_findBar->isHidden()) slotShowFindBar(); else m_findBar->findNext(); } void Part::slotFindPrev() { if (m_findBar->isHidden()) slotShowFindBar(); else m_findBar->findPrev(); } bool Part::saveFile() { if ( !isModified() ) return true; else return saveAs( url() ); } bool Part::slotSaveFileAs( bool showOkularArchiveAsDefaultFormat ) { if ( m_embedMode == PrintPreviewMode ) return false; // Determine the document's mimetype KMimeType::Ptr originalMimeType; if ( const Okular::DocumentInfo *documentInfo = m_document->documentInfo() ) { QString typeName = documentInfo->get("mimeType"); if ( !typeName.isEmpty() ) originalMimeType = KMimeType::mimeType( typeName ); } // What format choice should we show as default? const QString defaultMimeType = (isDocumentArchive || showOkularArchiveAsDefaultFormat) ? "application/vnd.kde.okular-archive" : originalMimeType->name(); // Prepare "Save As" dialog KFileDialog fd( url(), QString(), widget() ); fd.setWindowTitle( i18n("Save As") ); fd.setOperationMode( KFileDialog::Saving ); fd.setMode( KFile::File ); fd.setConfirmOverwrite( true ); fd.setMimeFilter( QStringList() << originalMimeType->name() << "application/vnd.kde.okular-archive", defaultMimeType ); // Show it if ( fd.exec() == QDialog::Accepted ) { const KUrl saveUrl = fd.selectedUrl(); if ( !saveUrl.isValid() || saveUrl.isEmpty() ) return false; // Has the user chosen to save in .okular archive format? const bool saveAsOkularArchive = ( fd.currentMimeFilter() == "application/vnd.kde.okular-archive" ); return saveAs( saveUrl, saveAsOkularArchive ); } else { return false; } } bool Part::saveAs(const KUrl & saveUrl) { // Save in the same format (.okular vs native) as the current file return saveAs( saveUrl, isDocumentArchive /* saveAsOkularArchive */ ); } bool Part::saveAs( const KUrl & saveUrl, bool saveAsOkularArchive ) { KTemporaryFile tf; QString fileName; if ( !tf.open() ) { KMessageBox::information( widget(), i18n("Could not open the temporary file for saving." ) ); return false; } fileName = tf.fileName(); tf.close(); QScopedPointer tempFile; KIO::Job *copyJob; // this will be filled with the job that writes to saveUrl // Does the user want a .okular archive? if ( saveAsOkularArchive ) { if ( !m_document->saveDocumentArchive( fileName ) ) { KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", fileName ) ); return false; } copyJob = KIO::file_copy( fileName, saveUrl, -1, KIO::Overwrite ); } else { // If the user wants to save in the original file's format, some features // might not be available. Find out what can't be saved in this format bool wontSaveAnnotations = false; bool wontSaveForms = false; if ( !m_document->canSaveChanges( Document::SaveFormsCapability ) ) { /* Set wontSaveForms only if there are forms */ const int pagecount = m_document->pages(); for ( int pageno = 0; pageno < pagecount; ++pageno ) { const Okular::Page *page = m_document->page( pageno ); if ( !page->formFields().empty() ) { wontSaveForms = true; break; } } } if ( !m_document->canSaveChanges( Document::SaveAnnotationsCapability ) ) { /* Set wontSaveAnnotations only if there are local annotations */ const int pagecount = m_document->pages(); for ( int pageno = 0; pageno < pagecount; ++pageno ) { const Okular::Page *page = m_document->page( pageno ); foreach ( const Okular::Annotation *ann, page->annotations() ) { if ( !(ann->flags() & Okular::Annotation::External) ) { wontSaveAnnotations = true; break; } } if ( wontSaveAnnotations ) break; } } // If something can't be saved in this format, ask for confirmation QStringList listOfwontSaves; if ( wontSaveForms ) listOfwontSaves << i18n( "Filled form contents" ); if ( wontSaveAnnotations ) listOfwontSaves << i18n( "User annotations" ); if ( !listOfwontSaves.isEmpty() ) { int result = KMessageBox::warningYesNoCancelList( widget(), i18n( "The following elements cannot be saved in this format and will be lost.
If you want to preserve them, please use the Okular document archive format." ), listOfwontSaves, i18n( "Warning" ), KGuiItem( i18n( "Save as Okular document archive..." ), "document-save-as" ), // <- KMessageBox::Yes KStandardGuiItem::cont() ); // <- KMessageBox::NO switch (result) { case KMessageBox::Yes: // -> Save as Okular document archive return slotSaveFileAs( true /* showOkularArchiveAsDefaultFormat */ ); case KMessageBox::No: // -> Continue break; case KMessageBox::Cancel: return false; } } if ( m_document->canSaveChanges() ) { // If the generator supports saving changes, save them QString errorText; if ( !m_document->saveChanges( fileName, &errorText ) ) { if (errorText.isEmpty()) KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", fileName ) ); else KMessageBox::information( widget(), i18n("File could not be saved in '%1'. %2", fileName, errorText ) ); return false; } copyJob = KIO::file_copy( fileName, saveUrl, -1, KIO::Overwrite ); } else { // If the generators doesn't support saving changes, we will // just copy the original file. if ( isDocumentArchive ) { // Special case: if the user is extracting the contents of a // .okular archive back to the native format, we can't just copy // the open file (which is a .okular). So let's ask to core to // extract and give us the real file if ( !m_document->extractArchivedFile( fileName ) ) { KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", fileName ) ); return false; } copyJob = KIO::file_copy( fileName, saveUrl, -1, KIO::Overwrite ); } else { // Otherwise just copy the open file. // make use of the already downloaded (in case of remote URLs) file, // no point in downloading that again KUrl srcUrl = KUrl::fromPath( localFilePath() ); // duh, our local file disappeared... if ( !QFile::exists( localFilePath() ) ) { if ( url().isLocalFile() ) { #ifdef OKULAR_KEEP_FILE_OPEN // local file: try to get it back from the open handle on it tempFile.reset( m_keeper->copyToTemporary() ); if ( tempFile ) srcUrl = KUrl::fromPath( tempFile->fileName() ); #else const QString msg = i18n( "Okular cannot copy %1 to the specified location.\n\nThe document does not exist anymore.", localFilePath() ); KMessageBox::sorry( widget(), msg ); return false; #endif } else { // we still have the original remote URL of the document, // so copy the document from there srcUrl = url(); } } if ( srcUrl != saveUrl ) { copyJob = KIO::file_copy( srcUrl, saveUrl, -1, KIO::Overwrite ); } else { // Don't do a real copy in this case, just update the timestamps copyJob = KIO::setModificationTime( saveUrl, QDateTime::currentDateTime() ); } } } } // Stop watching for changes while we write the new file (useful when // overwriting) if ( url().isLocalFile() ) unsetFileToWatch(); if ( !KIO::NetAccess::synchronousRun( copyJob, widget() ) ) { KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", saveUrl.prettyUrl() ) ); // Restore watcher if ( url().isLocalFile() ) setFileToWatch( localFilePath() ); return false; } setModified( false ); // Load new file instead of the old one if ( url() != saveUrl ) - slotAttemptReload( true, saveUrl ); + { + if ( m_document->canSwapBackingFile() ) + { + // If the generator supports hot-swapping of the backing file + // tell openFile to swap the backing file instead of opening a new one + m_swapInsteadOfOpening = true; + + // this calls openFile internally, which in turn actually calls + // m_document->swapBackingFile() instead of the regular loadDocument + const bool success = openUrl( saveUrl ); + + // restore it back to false -- this has already been done by + // openFile, but let's do it again for extra safety + m_swapInsteadOfOpening = false; + + // In case of file swapping errors, close everything to avoid inconsistencies + if ( !success ) + { + closeUrl(); + } + } + else + { + // If the generator doesn't support swapping file, then just reload + // the document from the new location + slotAttemptReload( true, saveUrl ); + } + } // Restore watcher if ( url().isLocalFile() ) setFileToWatch( localFilePath() ); return true; } void Part::slotGetNewStuff() { #if 0 KNS::Engine engine(widget()); engine.init( "okular.knsrc" ); // show the modal dialog over pageview and execute it KNS::Entry::List entries = engine.downloadDialogModal( m_pageView ); Q_UNUSED( entries ) #endif } void Part::slotPreferences() { // Create dialog PreferencesDialog * dialog = new PreferencesDialog( m_pageView, Okular::Settings::self(), m_embedMode ); dialog->setAttribute( Qt::WA_DeleteOnClose ); // Show it dialog->show(); } void Part::slotAnnotationPreferences() { // Create dialog PreferencesDialog * dialog = new PreferencesDialog( m_pageView, Okular::Settings::self(), m_embedMode ); dialog->setAttribute( Qt::WA_DeleteOnClose ); // Show it dialog->switchToAnnotationsPage(); dialog->show(); } void Part::slotNewConfig() { // Apply settings here. A good policy is to check whether the setting has // changed before applying changes. // Watch File setWatchFileModeEnabled(Okular::Settings::watchFile()); // Main View (pageView) m_pageView->reparseConfig(); // update document settings m_document->reparseConfig(); // update TOC settings if ( m_sidebar->isItemEnabled(0) ) m_toc->reparseConfig(); // update ThumbnailList contents if ( Okular::Settings::showLeftPanel() && !m_thumbnailList->isHidden() ) m_thumbnailList->updateWidgets(); // update Reviews settings if ( m_sidebar->isItemEnabled(2) ) m_reviewsWidget->reparseConfig(); setWindowTitleFromDocument (); } void Part::slotPrintPreview() { if (m_document->pages() == 0) return; QPrinter printer; // Native printing supports KPrintPreview, Postscript needs to use FilePrinterPreview if ( m_document->printingSupport() == Okular::Document::NativePrinting ) { KPrintPreview previewdlg( &printer, widget() ); setupPrint( printer ); doPrint( printer ); previewdlg.exec(); } else { // Generate a temp filename for Print to File, then release the file so generator can write to it KTemporaryFile tf; tf.setAutoRemove( true ); tf.setSuffix( ".ps" ); tf.open(); printer.setOutputFileName( tf.fileName() ); tf.close(); setupPrint( printer ); doPrint( printer ); if ( QFile::exists( printer.outputFileName() ) ) { Okular::FilePrinterPreview previewdlg( printer.outputFileName(), widget() ); previewdlg.exec(); } } } void Part::slotShowMenu(const Okular::Page *page, const QPoint &point) { if ( m_embedMode == PrintPreviewMode ) return; bool reallyShow = false; const bool currentPage = page && page->number() == m_document->viewport().pageNumber; if (!m_actionsSearched) { // the quest for options_show_menubar KActionCollection *ac; QAction *act; if (factory()) { const QList clients(factory()->clients()); for(int i = 0 ; (!m_showMenuBarAction || !m_showFullScreenAction) && i < clients.size(); ++i) { ac = clients.at(i)->actionCollection(); // show_menubar act = ac->action("options_show_menubar"); if (act && qobject_cast(act)) m_showMenuBarAction = qobject_cast(act); // fullscreen act = ac->action("fullscreen"); if (act && qobject_cast(act)) m_showFullScreenAction = qobject_cast(act); } } m_actionsSearched = true; } KMenu *popup = new KMenu( widget() ); QAction *addBookmark = 0; QAction *removeBookmark = 0; QAction *fitPageWidth = 0; if (page) { popup->addTitle( i18n( "Page %1", page->number() + 1 ) ); if ( ( !currentPage && m_document->bookmarkManager()->isBookmarked( page->number() ) ) || ( currentPage && m_document->bookmarkManager()->isBookmarked( m_document->viewport() ) ) ) removeBookmark = popup->addAction( KIcon("edit-delete-bookmark"), i18n("Remove Bookmark") ); else addBookmark = popup->addAction( KIcon("bookmark-new"), i18n("Add Bookmark") ); if ( m_pageView->canFitPageWidth() ) fitPageWidth = popup->addAction( KIcon("zoom-fit-best"), i18n("Fit Width") ); popup->addAction( m_prevBookmark ); popup->addAction( m_nextBookmark ); reallyShow = true; } /* //Albert says: I have not ported this as i don't see it does anything if ( d->mouseOnRect ) // and rect->objectType() == ObjectRect::Image ... { m_popup->insertItem( SmallIcon("document-save"), i18n("Save Image..."), 4 ); m_popup->setItemEnabled( 4, false ); }*/ if ((m_showMenuBarAction && !m_showMenuBarAction->isChecked()) || (m_showFullScreenAction && m_showFullScreenAction->isChecked())) { popup->addTitle( i18n( "Tools" ) ); if (m_showMenuBarAction && !m_showMenuBarAction->isChecked()) popup->addAction(m_showMenuBarAction); if (m_showFullScreenAction && m_showFullScreenAction->isChecked()) popup->addAction(m_showFullScreenAction); reallyShow = true; } if (reallyShow) { QAction *res = popup->exec(point); if (res) { if (res == addBookmark) { if (currentPage) m_document->bookmarkManager()->addBookmark( m_document->viewport() ); else m_document->bookmarkManager()->addBookmark( page->number() ); } else if (res == removeBookmark) { if (currentPage) m_document->bookmarkManager()->removeBookmark( m_document->viewport() ); else m_document->bookmarkManager()->removeBookmark( page->number() ); } else if (res == fitPageWidth) { m_pageView->fitPageWidth( page->number() ); } } } delete popup; } void Part::slotShowProperties() { PropertiesDialog *d = new PropertiesDialog(widget(), m_document); d->exec(); delete d; } void Part::slotShowEmbeddedFiles() { EmbeddedFilesDialog *d = new EmbeddedFilesDialog(widget(), m_document); d->exec(); delete d; } void Part::slotShowPresentation() { if ( !m_presentationWidget ) { m_presentationWidget = new PresentationWidget( widget(), m_document, actionCollection() ); } } void Part::slotHidePresentation() { if ( m_presentationWidget ) delete (PresentationWidget*) m_presentationWidget; } void Part::slotTogglePresentation() { if ( m_document->isOpened() ) { if ( !m_presentationWidget ) m_presentationWidget = new PresentationWidget( widget(), m_document, actionCollection() ); else delete (PresentationWidget*) m_presentationWidget; } } void Part::reload() { if ( m_document->isOpened() ) { slotReload(); } } void Part::enableStartWithPrint() { m_cliPrint = true; } void Part::slotAboutBackend() { const KComponentData *data = m_document->componentData(); if ( !data ) return; KAboutData aboutData( *data->aboutData() ); if ( aboutData.programIconName().isEmpty() || aboutData.programIconName() == aboutData.appName() ) { if ( const Okular::DocumentInfo *documentInfo = m_document->documentInfo() ) { const QString mimeTypeName = documentInfo->get("mimeType"); if ( !mimeTypeName.isEmpty() ) { if ( KMimeType::Ptr type = KMimeType::mimeType( mimeTypeName ) ) aboutData.setProgramIconName( type->iconName() ); } } } KAboutApplicationDialog dlg( &aboutData, widget() ); dlg.exec(); } void Part::slotExportAs(QAction * act) { QList acts = m_exportAs->menu() ? m_exportAs->menu()->actions() : QList(); int id = acts.indexOf( act ); if ( ( id < 0 ) || ( id >= acts.count() ) ) return; QString filter; switch ( id ) { case 0: filter = "text/plain"; break; default: filter = m_exportFormats.at( id - 1 ).mimeType()->name(); break; } QString fileName = KFileDialog::getSaveFileName( url().isLocalFile() ? url().directory() : QString(), filter, widget(), QString(), KFileDialog::ConfirmOverwrite ); if ( !fileName.isEmpty() ) { bool saved = false; switch ( id ) { case 0: saved = m_document->exportToText( fileName ); break; default: saved = m_document->exportTo( fileName, m_exportFormats.at( id - 1 ) ); break; } if ( !saved ) KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", fileName ) ); } } void Part::slotReload() { // stop the dirty handler timer, otherwise we may conflict with the // auto-refresh system m_dirtyHandler->stop(); slotAttemptReload(); } void Part::slotPrint() { if (m_document->pages() == 0) return; #ifdef Q_WS_WIN QPrinter printer(QPrinter::HighResolution); #else QPrinter printer; #endif QPrintDialog *printDialog = 0; QWidget *printConfigWidget = 0; // Must do certain QPrinter setup before creating QPrintDialog setupPrint( printer ); // Create the Print Dialog with extra config widgets if required if ( m_document->canConfigurePrinter() ) { printConfigWidget = m_document->printConfigurationWidget(); } if ( printConfigWidget ) { printDialog = KdePrint::createPrintDialog( &printer, QList() << printConfigWidget, widget() ); } else { printDialog = KdePrint::createPrintDialog( &printer, widget() ); } if ( printDialog ) { // Set the available Print Range printDialog->setMinMax( 1, m_document->pages() ); printDialog->setFromTo( 1, m_document->pages() ); // If the user has bookmarked pages for printing, then enable Selection if ( !m_document->bookmarkedPageRange().isEmpty() ) { printDialog->addEnabledOption( QAbstractPrintDialog::PrintSelection ); } // If the Document type doesn't support print to both PS & PDF then disable the Print Dialog option if ( printDialog->isOptionEnabled( QAbstractPrintDialog::PrintToFile ) && !m_document->supportsPrintToFile() ) { printDialog->setEnabledOptions( printDialog->enabledOptions() ^ QAbstractPrintDialog::PrintToFile ); } #if QT_VERSION >= KDE_MAKE_VERSION(4,7,0) // Enable the Current Page option in the dialog. if ( m_document->pages() > 1 && currentPage() > 0 ) { printDialog->setOption( QAbstractPrintDialog::PrintCurrentPage ); } #endif if ( printDialog->exec() ) doPrint( printer ); delete printDialog; } } void Part::setupPrint( QPrinter &printer ) { printer.setOrientation(m_document->orientation()); // title QString title = m_document->metaData( "DocumentTitle" ).toString(); if ( title.isEmpty() ) { title = m_document->currentDocument().fileName(); } if ( !title.isEmpty() ) { printer.setDocName( title ); } } void Part::doPrint(QPrinter &printer) { if (!m_document->isAllowed(Okular::AllowPrint)) { KMessageBox::error(widget(), i18n("Printing this document is not allowed.")); return; } if (!m_document->print(printer)) { const QString error = m_document->printError(); if (error.isEmpty()) { KMessageBox::error(widget(), i18n("Could not print the document. Unknown error. Please report to bugs.kde.org")); } else { KMessageBox::error(widget(), i18n("Could not print the document. Detailed error is \"%1\". Please report to bugs.kde.org", error)); } } } void Part::restoreDocument(const KConfigGroup &group) { KUrl url ( group.readPathEntry( "URL", QString() ) ); if ( url.isValid() ) { QString viewport = group.readEntry( "Viewport" ); if (!viewport.isEmpty()) m_document->setNextDocumentViewport( Okular::DocumentViewport( viewport ) ); openUrl( url ); } } void Part::saveDocumentRestoreInfo(KConfigGroup &group) { group.writePathEntry( "URL", url().url() ); group.writeEntry( "Viewport", m_document->viewport().toString() ); } void Part::psTransformEnded(int exit, QProcess::ExitStatus status) { Q_UNUSED( exit ) if ( status != QProcess::NormalExit ) return; QProcess *senderobj = sender() ? qobject_cast< QProcess * >( sender() ) : 0; if ( senderobj ) { senderobj->close(); senderobj->deleteLater(); } setLocalFilePath( m_temporaryLocalFile ); openUrl( m_temporaryLocalFile ); m_temporaryLocalFile.clear(); } void Part::displayInfoMessage( const QString &message, KMessageWidget::MessageType messageType, int duration ) { if ( !Okular::Settings::showOSD() ) { if (messageType == KMessageWidget::Error) { KMessageBox::error( widget(), message ); } return; } // hide messageWindow if string is empty if ( message.isEmpty() ) m_infoMessage->animatedHide(); // display message (duration is length dependant) if ( duration < 0 ) { duration = 500 + 100 * message.length(); } m_infoTimer->start( duration ); m_infoMessage->setText( message ); m_infoMessage->setMessageType( messageType ); m_infoMessage->setVisible( true ); } void Part::errorMessage( const QString &message, int duration ) { displayInfoMessage( message, KMessageWidget::Error, duration ); } void Part::warningMessage( const QString &message, int duration ) { displayInfoMessage( message, KMessageWidget::Warning, duration ); } void Part::noticeMessage( const QString &message, int duration ) { // less important message -> simpleer display widget in the PageView m_pageView->displayMessage( message, QString(), PageViewMessage::Info, duration ); } void Part::unsetDummyMode() { if ( m_embedMode == PrintPreviewMode ) return; m_sidebar->setItemEnabled( 2, true ); m_sidebar->setItemEnabled( 3, true ); m_sidebar->setSidebarVisibility( Okular::Settings::showLeftPanel() ); // add back and next in history m_historyBack = KStandardAction::documentBack( this, SLOT(slotHistoryBack()), actionCollection() ); m_historyBack->setWhatsThis( i18n( "Go to the place you were before" ) ); connect(m_pageView, SIGNAL(mouseBackButtonClick()), m_historyBack, SLOT(trigger())); m_historyNext = KStandardAction::documentForward( this, SLOT(slotHistoryNext()), actionCollection()); m_historyNext->setWhatsThis( i18n( "Go to the place you were after" ) ); connect(m_pageView, SIGNAL(mouseForwardButtonClick()), m_historyNext, SLOT(trigger())); m_pageView->setupActions( actionCollection() ); // attach the actions of the children widgets too m_formsMessage->addAction( m_pageView->toggleFormsAction() ); m_formsMessage->setVisible( m_pageView->toggleFormsAction() != 0 ); // ensure history actions are in the correct state updateViewActions(); } bool Part::handleCompressed( QString &destpath, const QString &path, const QString &compressedMimetype ) { m_tempfile = 0; // we are working with a compressed file, decompressing // temporary file for decompressing KTemporaryFile *newtempfile = new KTemporaryFile(); newtempfile->setAutoRemove(true); if ( !newtempfile->open() ) { KMessageBox::error( widget(), i18n("File Error! Could not create temporary file " "%1.", strerror(newtempfile->error()))); delete newtempfile; return false; } // decompression filer QIODevice* filterDev = KFilterDev::deviceForFile( path, compressedMimetype ); if (!filterDev) { delete newtempfile; return false; } if ( !filterDev->open(QIODevice::ReadOnly) ) { KMessageBox::detailedError( widget(), i18n("File Error! Could not open the file " "%1 for uncompression. " "The file will not be loaded.", path), i18n("This error typically occurs if you do " "not have enough permissions to read the file. " "You can check ownership and permissions if you " "right-click on the file in the Dolphin " "file manager and then choose the 'Properties' tab.")); delete filterDev; delete newtempfile; return false; } char buf[65536]; int read = 0, wrtn = 0; while ((read = filterDev->read(buf, sizeof(buf))) > 0) { wrtn = newtempfile->write(buf, read); if ( read != wrtn ) break; } delete filterDev; if ((read != 0) || (newtempfile->size() == 0)) { KMessageBox::detailedError(widget(), i18n("File Error! Could not uncompress " "the file %1. " "The file will not be loaded.", path ), i18n("This error typically occurs if the file is corrupt. " "If you want to be sure, try to decompress the file manually " "using command-line tools.")); delete newtempfile; return false; } m_tempfile = newtempfile; destpath = m_tempfile->fileName(); return true; } void Part::rebuildBookmarkMenu( bool unplugActions ) { if ( unplugActions ) { unplugActionList( "bookmarks_currentdocument" ); qDeleteAll( m_bookmarkActions ); m_bookmarkActions.clear(); } KUrl u = m_document->currentDocument(); if ( u.isValid() ) { m_bookmarkActions = m_document->bookmarkManager()->actionsForUrl( u ); } bool havebookmarks = true; if ( m_bookmarkActions.isEmpty() ) { havebookmarks = false; QAction * a = new KAction( 0 ); a->setText( i18n( "No Bookmarks" ) ); a->setEnabled( false ); m_bookmarkActions.append( a ); } plugActionList( "bookmarks_currentdocument", m_bookmarkActions ); if (factory()) { const QList clients(factory()->clients()); bool containerFound = false; for (int i = 0; !containerFound && i < clients.size(); ++i) { QWidget *container = factory()->container("bookmarks", clients[i]); if (container && container->actions().contains(m_bookmarkActions.first())) { Q_ASSERT(dynamic_cast(container)); disconnect(container, 0, this, 0); connect(container, SIGNAL(aboutToShowContextMenu(KMenu*,QAction*,QMenu*)), this, SLOT(slotAboutToShowContextMenu(KMenu*,QAction*,QMenu*))); containerFound = true; } } } m_prevBookmark->setEnabled( havebookmarks ); m_nextBookmark->setEnabled( havebookmarks ); } void Part::updateAboutBackendAction() { const KComponentData *data = m_document->componentData(); if ( data ) { m_aboutBackend->setEnabled( true ); } else { m_aboutBackend->setEnabled( false ); } } void Part::resetStartArguments() { m_cliPrint = false; } void Part::setReadWrite(bool readwrite) { m_document->setAnnotationEditingEnabled( readwrite ); ReadWritePart::setReadWrite( readwrite ); } } // namespace Okular #include "part.moc" /* kate: replace-tabs on; indent-width 4; */ diff --git a/part.h b/part.h index 19b432031..4facd1f29 100644 --- a/part.h +++ b/part.h @@ -1,376 +1,377 @@ /*************************************************************************** * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2003-2004 by Christophe Devriese * * * * Copyright (C) 2003 by Andy Goossens * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2004 by Dominique Devriese * * Copyright (C) 2004-2007 by Albert Astals Cid * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef _PART_H_ #define _PART_H_ #include #include #include #include #include #include #include #include "core/observer.h" #include "core/document.h" #include "kdocumentviewer.h" #include "interfaces/viewerinterface.h" #include "okular_part_export.h" #include class QAction; class QWidget; class QPrinter; class QMenu; class KUrl; class KConfigDialog; class KConfigGroup; class KDirWatch; class KToggleAction; class KToggleFullScreenAction; class KSelectAction; class KAboutData; class KTemporaryFile; class KAction; class KMenu; namespace KParts { class GUIActivateEvent; } class FindBar; class ThumbnailList; class PageSizeLabel; class PageView; class PresentationWidget; class ProgressWidget; class SearchWidget; class Sidebar; class TOC; class MiniBar; class MiniBarLogic; class FileKeeper; class Reviews; class BookmarkList; namespace Okular { class BrowserExtension; class ExportFormat; /** * Describes the possible embedding modes of the part * * @since 0.14 (KDE 4.8) */ enum EmbedMode { UnknownEmbedMode, NativeShellMode, // embedded in the native Okular' shell PrintPreviewMode, // embedded to show the print preview of a document KHTMLPartMode, // embedded in KHTML ViewerWidgetMode // the part acts as a widget that can display all kinds of documents }; /** * This is a "Part". It that does all the real work in a KPart * application. * * @short Main Part * @author Wilco Greven * @version 0.2 */ class OKULAR_PART_EXPORT Part : public KParts::ReadWritePart, public Okular::DocumentObserver, public KDocumentViewer, public Okular::ViewerInterface { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.okular") Q_INTERFACES(KDocumentViewer) Q_INTERFACES(Okular::ViewerInterface) friend class PartTest; public: // Default constructor /** * If one element of 'args' contains one of the strings "Print/Preview" or "ViewerWidget", * the part will be set up in the corresponding mode. Additionally, it is possible to specify * which config file should be used by adding a string containing "ConfigFileName=" * to 'args'. **/ Part(QWidget* parentWidget, QObject* parent, const QVariantList& args, KComponentData componentData); // Destructor ~Part(); // inherited from DocumentObserver void notifySetup( const QVector< Okular::Page * > &pages, int setupFlags ); void notifyViewportChanged( bool smoothMove ); void notifyPageChanged( int page, int flags ); bool openDocument(const KUrl& url, uint page); void startPresentation(); QStringList supportedMimeTypes() const; KUrl realUrl() const; void showSourceLocation(const QString& fileName, int line, int column, bool showGraphically = true); void clearLastShownSourceLocation(); bool isWatchFileModeEnabled() const; void setWatchFileModeEnabled(bool enable); bool areSourceLocationsShownGraphically() const; void setShowSourceLocationsGraphically(bool show); bool openNewFilesInTabs() const; public slots: // dbus Q_SCRIPTABLE Q_NOREPLY void goToPage(uint page); Q_SCRIPTABLE Q_NOREPLY void openDocument( const QString &doc ); Q_SCRIPTABLE uint pages(); Q_SCRIPTABLE uint currentPage(); Q_SCRIPTABLE QString currentDocument(); Q_SCRIPTABLE QString documentMetaData( const QString &metaData ) const; Q_SCRIPTABLE void slotPreferences(); Q_SCRIPTABLE void slotFind(); Q_SCRIPTABLE void slotPrintPreview(); Q_SCRIPTABLE void slotPreviousPage(); Q_SCRIPTABLE void slotNextPage(); Q_SCRIPTABLE void slotGotoFirst(); Q_SCRIPTABLE void slotGotoLast(); Q_SCRIPTABLE void slotTogglePresentation(); Q_SCRIPTABLE Q_NOREPLY void reload(); Q_SCRIPTABLE Q_NOREPLY void enableStartWithPrint(); signals: void enablePrintAction(bool enable); void openSourceReference(const QString& absFileName, int line, int column); void viewerMenuStateChange(bool enabled); void enableCloseAction(bool enable); void mimeTypeChanged(KMimeType::Ptr mimeType); void urlsDropped( const KUrl::List& urls ); protected: // reimplemented from KParts::ReadWritePart bool openFile(); bool openUrl(const KUrl &url); void guiActivateEvent(KParts::GUIActivateEvent *event); void displayInfoMessage( const QString &message, KMessageWidget::MessageType messageType = KMessageWidget::Information, int duration = -1 );; public: bool queryClose(); bool closeUrl(); bool closeUrl(bool promptToSave); void setReadWrite(bool readwrite); bool saveAs(const KUrl & saveUrl); protected slots: // connected to actions void openUrlFromDocument(const KUrl &url); void openUrlFromBookmarks(const KUrl &url); void handleDroppedUrls( const KUrl::List& urls ); void slotGoToPage(); void slotHistoryBack(); void slotHistoryNext(); void slotAddBookmark(); void slotRenameBookmarkFromMenu(); void slotRenameCurrentViewportBookmark(); void slotAboutToShowContextMenu(KMenu *menu, QAction *action, QMenu *contextMenu); void slotPreviousBookmark(); void slotNextBookmark(); void slotFindNext(); void slotFindPrev(); bool slotSaveFileAs(bool showOkularArchiveAsDefaultFormat = false); void slotGetNewStuff(); void slotNewConfig(); void slotShowMenu(const Okular::Page *page, const QPoint &point); void slotShowProperties(); void slotShowEmbeddedFiles(); void slotShowLeftPanel(); void slotShowBottomBar(); void slotShowPresentation(); void slotHidePresentation(); void slotExportAs(QAction *); bool slotImportPSFile(); void slotAboutBackend(); void slotReload(); void close(); void cannotQuit(); void slotShowFindBar(); void slotHideFindBar(); void slotJobStarted(KIO::Job *job); void slotJobFinished(KJob *job); void loadCancelled(const QString &reason); void setWindowTitleFromDocument(); // can be connected to widget elements void updateViewActions(); void updateBookmarksActions(); void enableTOC(bool enable); void slotRebuildBookmarkMenu(); public slots: bool saveFile(); // connected to Shell action (and browserExtension), not local one void slotPrint(); void restoreDocument(const KConfigGroup &group); void saveDocumentRestoreInfo(KConfigGroup &group); void slotFileDirty( const QString& ); void slotAttemptReload( bool oneShot = false, const KUrl &newUrl = KUrl() ); void psTransformEnded(int, QProcess::ExitStatus); KConfigDialog * slotGeneratorPreferences(); void errorMessage( const QString &message, int duration = 0 ); void warningMessage( const QString &message, int duration = -1 ); void noticeMessage( const QString &message, int duration = -1 ); private: void setupViewerActions(); void setViewerShortcuts(); void setupActions(); void setupPrint( QPrinter &printer ); void doPrint( QPrinter &printer ); bool handleCompressed( QString &destpath, const QString &path, const QString &compressedMimetype ); void rebuildBookmarkMenu( bool unplugActions = true ); void updateAboutBackendAction(); void unsetDummyMode(); void slotRenameBookmark( const DocumentViewport &viewport ); void resetStartArguments(); bool saveAs( const KUrl & saveUrl, bool saveAsOkularArchive ); void setFileToWatch( const QString &filePath ); void unsetFileToWatch(); static int numberOfParts; KTemporaryFile *m_tempfile; // the document Okular::Document * m_document; QString m_temporaryLocalFile; bool isDocumentArchive; + bool m_swapInsteadOfOpening; // if set, the next open operation will replace the backing file (used when reloading just saved files) // main widgets Sidebar *m_sidebar; SearchWidget *m_searchWidget; FindBar * m_findBar; KMessageWidget * m_topMessage; KMessageWidget * m_formsMessage; KMessageWidget * m_infoMessage; QPointer m_thumbnailList; QPointer m_pageView; QPointer m_toc; QPointer m_miniBarLogic; QPointer m_miniBar; QPointer m_pageNumberTool; QPointer m_bottomBar; QPointer m_presentationWidget; QPointer m_progressWidget; QPointer m_pageSizeLabel; QPointer m_reviewsWidget; QPointer m_bookmarkList; // document watcher (and reloader) variables KDirWatch *m_watcher; QString m_watchedFilePath, m_watchedFileSymlinkTarget; QTimer *m_dirtyHandler; KUrl m_oldUrl; Okular::DocumentViewport m_viewportDirty; bool m_wasPresentationOpen; int m_dirtyToolboxIndex; bool m_wasSidebarVisible; bool m_wasSidebarCollapsed; bool m_fileWasRemoved; Rotation m_dirtyPageRotation; // Remember the search history QStringList m_searchHistory; // actions KAction *m_gotoPage; KAction *m_prevPage; KAction *m_nextPage; KAction *m_beginningOfDocument; KAction *m_endOfDocument; KAction *m_historyBack; KAction *m_historyNext; KAction *m_addBookmark; KAction *m_renameBookmark; KAction *m_prevBookmark; KAction *m_nextBookmark; KAction *m_copy; KAction *m_selectAll; KAction *m_find; KAction *m_findNext; KAction *m_findPrev; KAction *m_save; KAction *m_saveAs; KAction *m_saveCopyAs; KAction *m_printPreview; KAction *m_showProperties; KAction *m_showEmbeddedFiles; KAction *m_exportAs; QAction *m_exportAsText; QAction *m_exportAsDocArchive; KAction *m_showPresentation; KToggleAction* m_showMenuBarAction; KToggleAction* m_showLeftPanel; KToggleAction* m_showBottomBar; KToggleFullScreenAction* m_showFullScreenAction; KAction *m_aboutBackend; KAction *m_reload; QMenu *m_exportAsMenu; KAction *m_closeFindBar; bool m_actionsSearched; BrowserExtension *m_bExtension; QList m_exportFormats; QList m_bookmarkActions; bool m_cliPresentation; bool m_cliPrint; QString m_addBookmarkText; QIcon m_addBookmarkIcon; EmbedMode m_embedMode; KUrl m_realUrl; KXMLGUIClient *m_generatorGuiClient; FileKeeper *m_keeper; // Timer for m_infoMessage QTimer *m_infoTimer; private slots: void slotAnnotationPreferences(); void slotHandleActivatedSourceReference(const QString& absFileName, int line, int col, bool *handled); }; class PartFactory : public KPluginFactory { Q_OBJECT public: PartFactory(); virtual ~PartFactory(); protected: virtual QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword); }; } #endif /* kate: replace-tabs on; indent-width 4; */