diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/CMakeLists.txt dspdfviewer-1.11~ubuntu13.10.1/CMakeLists.txt --- dspdfviewer-1.10.1~ubuntu13.10.1/CMakeLists.txt 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/CMakeLists.txt 2014-07-12 10:11:37.000000000 +0000 @@ -77,11 +77,15 @@ message(STATUS "Using version number ${GIT_DESCRIBE_VERSION} as defined by git-describe.") set(DSPDFVIEWER_VERSION ${GIT_DESCRIBE_VERSION}) else() - # get the first changelog line (containing a version number in parentheses) into DEBIANVERSION - file(STRINGS debian/changelog DEBIANVERSION LIMIT_COUNT 1 REGEX "\\(([0-9.])*\\)") - # reduce the variable to only the first "version-number-ish" thing - string(REGEX MATCH "([0-9.])+" DEBIANVERSION ${DEBIANVERSION} ) - if ( NOT ${DEBIANVERSION} MATCHES "^$" ) + # use the standard utility dpkg-parsechangelog to get the version number + execute_process(COMMAND dpkg-parsechangelog + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE DEBIANCHANGELOG OUTPUT_STRIP_TRAILING_WHITESPACE) + # grep for the line with "Version: " + string(REGEX MATCH "Version: [^ \r\n\t]*" DEBIANVERSION "${DEBIANCHANGELOG}") + # strip "Version: " + string(REPLACE "Version: " "" DEBIANVERSION "${DEBIANVERSION}") + if ( NOT "${DEBIANVERSION}" MATCHES "^$" ) message(STATUS "Using version number ${DEBIANVERSION} as parsed from debian/changelog's first entry.") set(DSPDFVIEWER_VERSION ${DEBIANVERSION}) else() @@ -96,7 +100,7 @@ endif() -set(dspdfviewer_SRCS runtimeconfiguration.cpp renderutils.cpp renderthread.cpp renderingidentifier.cpp pagepart.cpp renderedpage.cpp pdfrenderfactory.cpp pdfviewerwindow.cpp dspdfviewer.cpp main.cpp) +set(dspdfviewer_SRCS pdfpagereference.cpp pdfdocumentreference.cpp runtimeconfiguration.cpp renderutils.cpp renderthread.cpp renderingidentifier.cpp pagepart.cpp renderedpage.cpp pdfrenderfactory.cpp pdfviewerwindow.cpp dspdfviewer.cpp main.cpp) qt4_wrap_ui(dspdfviewer_UIS_H pdfviewerwindow.ui) include_directories(${CMAKE_CURRENT_BINARY_DIR}) qt4_automoc(${dspdfviewer_SRCS}) diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/debian/bzr-builder.manifest dspdfviewer-1.11~ubuntu13.10.1/debian/bzr-builder.manifest --- dspdfviewer-1.10.1~ubuntu13.10.1/debian/bzr-builder.manifest 2013-10-22 16:31:45.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/debian/bzr-builder.manifest 2014-07-12 10:11:37.000000000 +0000 @@ -1,2 +1,2 @@ # bzr-builder format 0.3 deb-version {debupstream} -lp:dspdfviewer revid:git-v1:01881a27a6ff45abc9e68dbea5d9522ec207956c +lp:dspdfviewer revid:git-v1:fdc9884a0223857286ff58b5a743e6da426f19fe diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/debian/changelog dspdfviewer-1.11~ubuntu13.10.1/debian/changelog --- dspdfviewer-1.10.1~ubuntu13.10.1/debian/changelog 2013-10-22 16:31:45.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/debian/changelog 2014-07-12 10:11:37.000000000 +0000 @@ -1,8 +1,20 @@ -dspdfviewer (1.10.1~ubuntu13.10.1) saucy; urgency=low +dspdfviewer (1.11~ubuntu13.10.1) saucy; urgency=low * Auto build. - -- Danny Edel Tue, 22 Oct 2013 16:31:45 +0000 + -- Danny Edel Sat, 12 Jul 2014 10:11:37 +0000 + +dspdfviewer (1.11) stable; urgency=low + + * Spawn the audience window on "secondary" screen + * Different window titles for the two windows + * New feature: Blanking (hotkey b) + * Correctly initialize presentationClockRunning + * Manpage: Added a short section about booleans + * Automatically re-read PDF file on changes + * Add thread priorities, boosting the current page + + -- Danny Edel Sat, 12 Jul 2014 10:52:36 +0200 dspdfviewer (1.10.1) stable; urgency=low diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/docs/dspdfviewer.1 dspdfviewer-1.11~ubuntu13.10.1/docs/dspdfviewer.1 --- dspdfviewer-1.10.1~ubuntu13.10.1/docs/dspdfviewer.1 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/docs/dspdfviewer.1 2014-07-12 10:11:37.000000000 +0000 @@ -58,6 +58,9 @@ options starting with two dashes ('-'). A summary of options is included below. +Note for options of type : They take true, false, 0 or 1 as arguments. +For example, \-\-use-second-screen false can be shortened to \-u0. + .TP .B \-h, \-\-help Show summary of options. @@ -123,9 +126,12 @@ .B Left mouse button, Wheel down, Down Arrow, Right Arrow, Page Down, Return, N, F Go to next page .TP -.B Right mouse button, Wheel up, Up Arrow, Left Arrow, Page Up, Backspace, P, B +.B Right mouse button, Wheel up, Up Arrow, Left Arrow, Page Up, Backspace, P Go to previous page .TP +.B B +Toggle blanking of the audience screen +.TP .B G Go to specific page (a number entry window will pop up) .TP diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/dspdfviewer.cpp dspdfviewer-1.11~ubuntu13.10.1/dspdfviewer.cpp --- dspdfviewer-1.10.1~ubuntu13.10.1/dspdfviewer.cpp 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/dspdfviewer.cpp 2014-07-12 10:11:37.000000000 +0000 @@ -35,12 +35,11 @@ DSPDFViewer::DSPDFViewer(const RuntimeConfiguration& r): runtimeConfiguration(r), - pdfDocument(Poppler::Document::load(r.filePathQString())) - , - renderFactory(r.filePathQString()), + presentationClockRunning(false), + renderFactory(r.filePathQString(), r.cachePDFToMemory()?PDFCacheOption::keepPDFinMemory:PDFCacheOption::rereadFromDisk ), m_pagenumber(0), - audienceWindow(0, r.useFullPage()? PagePart::FullPage : PagePart::LeftHalf , false, r), - secondaryWindow(1, r.useFullPage()? PagePart::FullPage : PagePart::RightHalf, true, r, r.useSecondScreen() ) + audienceWindow(1, r.useFullPage()? PagePart::FullPage : PagePart::LeftHalf , false, r, "Audience_Window"), + secondaryWindow(0, r.useFullPage()? PagePart::FullPage : PagePart::RightHalf, true, r, "Secondary_Window", r.useSecondScreen() ) { qDebug() << "Starting constructor" ; @@ -48,22 +47,24 @@ secondaryWindow.hide(); } + audienceWindow.showLoadingScreen(0); secondaryWindow.showLoadingScreen(0); - + +#if 0 // FIXME Make sure exceptions on startup get handled correctly if ( ! pdfDocument || pdfDocument->isLocked() ) { /// FIXME: Error message throw std::runtime_error("I was not able to open the PDF document. Sorry."); } - setHighQuality(true); - +#endif qDebug() << "Connecting audience window"; audienceWindow.setPageNumberLimits(0, numberOfPages()-1); connect( &renderFactory, SIGNAL(pageRendered(QSharedPointer)), &audienceWindow, SLOT(renderedPageIncoming(QSharedPointer))); connect( &renderFactory, SIGNAL(thumbnailRendered(QSharedPointer)), &audienceWindow, SLOT(renderedThumbnailIncoming(QSharedPointer))); + connect( &renderFactory, SIGNAL(pdfFileRereadSuccesfully()), this, SLOT(renderPage())); connect( &audienceWindow, SIGNAL(nextPageRequested()), this, SLOT(goForward())); connect( &audienceWindow, SIGNAL(previousPageRequested()), this, SLOT(goBackward())); @@ -75,6 +76,8 @@ connect( &audienceWindow, SIGNAL(screenSwapRequested()), this, SLOT(swapScreens()) ); + connect( &audienceWindow, SIGNAL(blankToggleRequested()), this, SLOT(toggleAudienceScreenBlank())); + if ( r.useSecondScreen() ) { qDebug() << "Connecting secondary window"; @@ -93,6 +96,8 @@ connect( &secondaryWindow, SIGNAL(restartRequested()), this, SLOT(goToStartAndResetClocks())); connect( &secondaryWindow, SIGNAL(screenSwapRequested()), this, SLOT(swapScreens()) ); + + connect( &secondaryWindow, SIGNAL(blankToggleRequested()), this, SLOT(toggleAudienceScreenBlank())); connect( this, SIGNAL(presentationClockUpdate(QTime)), &secondaryWindow, SLOT(updatePresentationClock(QTime))); connect( this, SIGNAL(slideClockUpdate(QTime)), &secondaryWindow, SLOT(updateSlideClock(QTime))); @@ -147,15 +152,19 @@ void DSPDFViewer::renderPage() { qDebug() << "Requesting rendering of page " << m_pagenumber; + if ( m_pagenumber >= numberOfPages() ) { + qDebug() << "Page number out of range, clamping to " << numberOfPages()-1; + m_pagenumber = numberOfPages()-1; + } audienceWindow.showLoadingScreen(m_pagenumber); secondaryWindow.showLoadingScreen(m_pagenumber); if ( runtimeConfiguration.showThumbnails() ) { theFactory()->requestThumbnailRendering(m_pagenumber); } - theFactory()->requestPageRendering( toRenderIdent(m_pagenumber, audienceWindow)); + theFactory()->requestPageRendering( toRenderIdent(m_pagenumber, audienceWindow), QThread::HighestPriority); if ( runtimeConfiguration.useSecondScreen() ) { - theFactory()->requestPageRendering( toRenderIdent(m_pagenumber, secondaryWindow)); + theFactory()->requestPageRendering( toRenderIdent(m_pagenumber, secondaryWindow), QThread::HighPriority); } /** Pre-Render next pages **/ @@ -221,13 +230,6 @@ secondaryWindow.close(); } -void DSPDFViewer::setHighQuality(bool hq) -{ - pdfDocument->setRenderHint(Poppler::Document::Antialiasing, hq); - pdfDocument->setRenderHint(Poppler::Document::TextAntialiasing, hq); - pdfDocument->setRenderHint(Poppler::Document::TextHinting, hq); -} - const QSize DSPDFViewer::thumbnailSize = QSize(200, 100); PdfRenderFactory* DSPDFViewer::theFactory() @@ -236,7 +238,8 @@ } unsigned int DSPDFViewer::numberOfPages() const { - if ( pdfDocument->numPages() < 0 ) + int pages = renderFactory.numberOfPages(); + if ( pages < 0 ) { /* What the... ?! * @@ -246,7 +249,7 @@ return 0; } /* numPages is non-negative and therefore safe to use. */ - return pdfDocument->numPages() ; + return pages; } void DSPDFViewer::goToStartAndResetClocks() @@ -313,5 +316,30 @@ } +bool DSPDFViewer::isAudienceScreenBlank() const +{ + return audienceWindow.isBlank(); +} + +void DSPDFViewer::setAudienceScreenBlank() +{ + audienceWindow.setBlank(true); +} + +void DSPDFViewer::setAudienceScreenVisible() +{ + audienceWindow.setBlank(false); +} + +void DSPDFViewer::toggleAudienceScreenBlank() +{ + if ( isAudienceScreenBlank() ) { + setAudienceScreenVisible(); + } else { + setAudienceScreenBlank(); + } +} + + #include "dspdfviewer.moc" diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/dspdfviewer.h dspdfviewer-1.11~ubuntu13.10.1/dspdfviewer.h --- dspdfviewer-1.10.1~ubuntu13.10.1/dspdfviewer.h 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/dspdfviewer.h 2014-07-12 10:11:37.000000000 +0000 @@ -23,6 +23,7 @@ #include #include +#include #include #include "pdfviewerwindow.h" @@ -46,7 +47,7 @@ bool presentationClockRunning; private: - QSharedPointer< Poppler::Document > pdfDocument; + QFileSystemWatcher documentFileWatcher; PdfRenderFactory renderFactory; unsigned int m_pagenumber; PDFViewerWindow audienceWindow; @@ -93,6 +94,8 @@ QTime presentationClock() const; QTime timeSince( const QTime& startPoint) const; + + bool isAudienceScreenBlank() const; signals: void wallClockUpdate(const QTime& wallClock) const; @@ -119,6 +122,10 @@ void swapScreens(); + void toggleAudienceScreenBlank(); + void setAudienceScreenBlank(); + void setAudienceScreenVisible(); + void exit(); }; diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/main.cpp dspdfviewer-1.11~ubuntu13.10.1/main.cpp --- dspdfviewer-1.10.1~ubuntu13.10.1/main.cpp 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/main.cpp 2014-07-12 10:11:37.000000000 +0000 @@ -28,6 +28,8 @@ int main(int argc, char** argv) { QApplication app(argc, argv); + app.setApplicationName( "dspdfviewer" ); + app.setApplicationVersion( DSPDFVIEWER_VERSION ); /* If anything goes wrong, try to display the exception to the user. * Its the least i can do. */ diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/pdfcacheoption.h dspdfviewer-1.11~ubuntu13.10.1/pdfcacheoption.h --- dspdfviewer-1.10.1~ubuntu13.10.1/pdfcacheoption.h 1970-01-01 00:00:00.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/pdfcacheoption.h 2014-07-12 10:11:37.000000000 +0000 @@ -0,0 +1,9 @@ +#ifndef PDFCACHEOPTION_H +#define PDFCACHEOPTION_H + +enum class PDFCacheOption { + keepPDFinMemory, + rereadFromDisk +}; + +#endif // PDFCACHEOPTION_H diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/pdfdocumentreference.cpp dspdfviewer-1.11~ubuntu13.10.1/pdfdocumentreference.cpp --- dspdfviewer-1.10.1~ubuntu13.10.1/pdfdocumentreference.cpp 1970-01-01 00:00:00.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/pdfdocumentreference.cpp 2014-07-12 10:11:37.000000000 +0000 @@ -0,0 +1,82 @@ +#include "pdfdocumentreference.h" +#include "pdfpagereference.h" + +#include +#include +#include + +QSharedPointer< Poppler::Document > PDFDocumentReference::popplerDocument() const +{ + QSharedPointer m_document; + + if ( cacheOption() == PDFCacheOption::rereadFromDisk ) { + qDebug() << "Trying to build a Poppler::Document from file" << filename(); + QSharedPointer diskDocument( Poppler::Document::load(filename()) ); + m_document.swap(diskDocument); + } + else if ( cacheOption() == PDFCacheOption::keepPDFinMemory ) { + qDebug() << "Trying to build a document from" << fileContents_.size() << "byte memory cache"; + QSharedPointer memoryDocument( Poppler::Document::loadFromData(fileContents_) ); + m_document.swap(memoryDocument); + } + if ( !m_document || m_document->isLocked() ) + throw std::runtime_error("Document not readable"); + m_document->setRenderHint(Poppler::Document::Antialiasing, true); + m_document->setRenderHint(Poppler::Document::TextAntialiasing, true); + m_document->setRenderHint(Poppler::Document::TextHinting, true); + return m_document; +} + +PDFPageReference PDFDocumentReference::page(unsigned int pageNumber) const +{ + PDFPageReference ppr(*this, pageNumber); + return ppr; +} + +PDFDocumentReference::PDFDocumentReference(const QString& theFilename, const PDFCacheOption& theCacheOption): +filename_(theFilename), +cacheOption_(theCacheOption) +{ + if ( cacheOption() == PDFCacheOption::keepPDFinMemory ) { + qDebug() << "Reading file into memory"; + QFile file(filename()); + file.open(QIODevice::ReadOnly); + fileContents_ = file.readAll(); + qDebug() << fileContents_.size() << "bytes read"; + } +} +const PDFCacheOption& PDFDocumentReference::cacheOption() const +{ + return cacheOption_; +} + +const QString& PDFDocumentReference::filename() const +{ + return filename_; +} + +PDFDocumentReference& PDFDocumentReference::operator=(const PDFDocumentReference& rhs) +{ + if ( rhs.filename() != filename() ) { + throw std::runtime_error("This PDFDocumentReference has a different filename"); + } + if ( rhs.cacheOption() != cacheOption() ) { + throw std::runtime_error("This PDFDocumentReference has a different cache setting"); + } + fileContents_=rhs.fileContents_; + return *this; +} + +bool operator==(const PDFDocumentReference& lhs, const PDFDocumentReference& rhs) +{ + if ( lhs.cacheOption() != rhs.cacheOption() ) { + return false; + } + else if ( lhs.cacheOption() == PDFCacheOption::keepPDFinMemory ) { + return lhs.fileContents_ == rhs.fileContents_; + } + else { + return lhs.filename() == rhs.filename(); + } +} + diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/pdfdocumentreference.h dspdfviewer-1.11~ubuntu13.10.1/pdfdocumentreference.h --- dspdfviewer-1.10.1~ubuntu13.10.1/pdfdocumentreference.h 1970-01-01 00:00:00.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/pdfdocumentreference.h 2014-07-12 10:11:37.000000000 +0000 @@ -0,0 +1,68 @@ +#ifndef PDFDOCUMENTREFERENCE_H +#define PDFDOCUMENTREFERENCE_H +#include +#include +#include "pdfcacheoption.h" +#include + +// forward-declare +struct PDFPageReference; + +/** Holds a reference to a PDF document. + * + * What exactly this means depends on the cache option: + * If the cache option is hold in memory, this class wild have a + * complete in-memory copy of the PDF file. + * + * If the cache option is reread from disk, the reference is + * just the file name. + * + */ +class PDFDocumentReference +{ +private: + const QString filename_; + QByteArray fileContents_; + const PDFCacheOption cacheOption_; + +public: + /** Create the document reference. + * + * If the cache option is keep in memory, this will read the entire file into memory. + */ + PDFDocumentReference( const QString& filename, const PDFCacheOption& cacheOption); + + /** Returns a Poppler::Page reference to the requested page number + * NOTE: Until poppler supports multi-threading, this will + * create a new Poppler::Document for each request. + */ + PDFPageReference page(unsigned pageNumber) const; + + /** Create a Poppler::Document from this reference. + * If you did not cache the file to memory, this step will try to + * read the file. + */ + QSharedPointer popplerDocument() const; + + /** get filename */ + const QString& filename() const; + + /** get cache setting */ + const PDFCacheOption& cacheOption() const; + + /** Update the document reference. + * This only works if rhs has the same filename and cache options, + * effectively only updating the file contents buffer. + */ + PDFDocumentReference& operator = (const PDFDocumentReference& rhs); + + /** Compares the references. Exact behaviour depends on the cache option: + * If we are memory-caching, this compares the ByteArrays (i.e. checks for + * identical files). + * + * If we are not caching, it just checks for the same filename. + */ + friend bool operator == (const PDFDocumentReference& lhs, const PDFDocumentReference& rhs); +}; + +#endif // PDFDOCUMENTREFERENCE_H diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/pdfpagereference.cpp dspdfviewer-1.11~ubuntu13.10.1/pdfpagereference.cpp --- dspdfviewer-1.10.1~ubuntu13.10.1/pdfpagereference.cpp 1970-01-01 00:00:00.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/pdfpagereference.cpp 2014-07-12 10:11:37.000000000 +0000 @@ -0,0 +1,7 @@ +#include "pdfpagereference.h" + +PDFPageReference::PDFPageReference(const PDFDocumentReference& documentReference, const unsigned int& pageNumber): + document( documentReference.popplerDocument() ), + page( document->page(pageNumber) ) +{ +} diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/pdfpagereference.h dspdfviewer-1.11~ubuntu13.10.1/pdfpagereference.h --- dspdfviewer-1.10.1~ubuntu13.10.1/pdfpagereference.h 1970-01-01 00:00:00.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/pdfpagereference.h 2014-07-12 10:11:37.000000000 +0000 @@ -0,0 +1,22 @@ +#ifndef PDFPAGEREFERENCE_H +#define PDFPAGEREFERENCE_H + +#include +#include +#include "pdfdocumentreference.h" + +/** This holds a reference to a page in a certain document + * WARNING: This also contains a shared pointer to the Poppler::Document, + * so if you delete this object, using the returned page() is undefined + * behaviour. + */ +struct PDFPageReference +{ + const QSharedPointer document; + const QSharedPointer page; + + + PDFPageReference( const PDFDocumentReference& documentReference, const unsigned int& pageNumber ); +}; + +#endif // PDFPAGEREFERENCE_H diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/pdfrenderfactory.cpp dspdfviewer-1.11~ubuntu13.10.1/pdfrenderfactory.cpp --- dspdfviewer-1.10.1~ubuntu13.10.1/pdfrenderfactory.cpp 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/pdfrenderfactory.cpp 2014-07-12 10:11:37.000000000 +0000 @@ -24,6 +24,7 @@ #include #include #include +#include static const QSize ThumbnailSize(200,100); @@ -31,8 +32,12 @@ void PdfRenderFactory::pageThreadFinishedRendering(QSharedPointer renderedPage) { { - QMutexLocker lock(&mutex); + QMutexLocker lock(&mutex); const RenderingIdentifier ident( renderedPage->getIdentifier() ); + // Ignore this incoming rendering if it was from an old version + if ( ident.theVersion != currentVersion ) + return; + renderedPages.insert(ident, new RenderedPage(*renderedPage)); currentlyRenderingPages.remove(ident); } @@ -44,6 +49,10 @@ { { QMutexLocker lock(&mutex); + // Ignore this incoming rendering if it was from an old version + if (renderedPage->getIdentifier().theVersion != currentVersion ) + return; + int pageNumber = renderedPage->getPageNumber(); renderedThumbnails.insert(pageNumber, new RenderedPage(*renderedPage)); currentlyRenderingThumbnails.remove(pageNumber); @@ -52,27 +61,47 @@ emit thumbnailRendered(renderedPage); } -QSharedPointer PdfRenderFactory::fetchDocument() +void PdfRenderFactory::rewatchFile() { - QSharedPointer m_document( Poppler::Document::load(documentFilename) ); - if ( !m_document || m_document->isLocked() ) - throw std::runtime_error("Document not readable"); - m_document->setRenderHint(Poppler::Document::Antialiasing, true); - m_document->setRenderHint(Poppler::Document::TextAntialiasing, true); - m_document->setRenderHint(Poppler::Document::TextHinting, true); - return m_document; + if ( ! fileWatcher.files().contains( documentReference.filename() ) ) { + fileWatcher.addPath( documentReference.filename() ); + // Check if it has been added (i.e. if it exists) + if ( fileWatcher.files().contains( documentReference.filename() ) ) { + // The file was created in the meantime. FileWatcher does not report this as a "change", + // So we have to check it manually. + fileOnDiskChanged(documentReference.filename()); + } + } } -PdfRenderFactory::PdfRenderFactory(QString filename): QObject(), documentFilename(filename) + +PdfRenderFactory::PdfRenderFactory(const QString& filename, const PDFCacheOption& cacheSetting): QObject(), documentReference(filename, cacheSetting), currentVersion(0), + + // Attempt to read the document to get the number of pages within. + // This will throw an error if the document is unreadable. + numberOfPages_(documentReference.popplerDocument()->numPages()) { - fetchDocument(); + + rewatchFile(); + + // register the on-change function + connect(&fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(fileOnDiskChanged(QString))); + + // Make sure it re-watches the file + fileWatcherRewatchTimer.setInterval(1000); + connect(&fileWatcherRewatchTimer, SIGNAL(timeout()), this, SLOT(rewatchFile())); + fileWatcherRewatchTimer.start(); } -void PdfRenderFactory::requestPageRendering(const RenderingIdentifier& renderingIdentifier) +void PdfRenderFactory::requestPageRendering(const RenderingIdentifier& originalIdentifier, QThread::Priority priority) { QMutexLocker lock(&mutex); + RenderingIdentifier renderingIdentifier(originalIdentifier); + + renderingIdentifier.theVersion = currentVersion; + if ( renderedPages.contains(renderingIdentifier) ) { /* Page is ready. Take a copy and lets go. */ @@ -88,10 +117,10 @@ } /* Nobody is working on the page right now. Lets create it. */ - RenderThread* t = new RenderThread( fetchDocument(), renderingIdentifier ); + RenderThread* t = new RenderThread( documentReference, renderingIdentifier ); connect(t, SIGNAL(renderingFinished(QSharedPointer)), this, SLOT(pageThreadFinishedRendering(QSharedPointer))); currentlyRenderingPages.insert(renderingIdentifier); - QThreadPool::globalInstance()->start(t); + QThreadPool::globalInstance()->start(t, priority); } @@ -115,11 +144,90 @@ /* We have to render it */ RenderingIdentifier r(pageNumber, PagePart::FullPage, ThumbnailSize); + r.theVersion = currentVersion; - RenderThread* t = new RenderThread(fetchDocument(), r); + RenderThread* t = new RenderThread(documentReference, r); connect( t, SIGNAL(renderingFinished(QSharedPointer)), this, SLOT(thumbnailThreadFinishedRendering(QSharedPointer))); currentlyRenderingThumbnails.insert(pageNumber); - QThreadPool::globalInstance()->start(t); + QThreadPool::globalInstance()->start(t, QThread::Priority::LowestPriority); +} + +void PdfRenderFactory::fileOnDiskChanged(const QString& filename) +{ + qDebug() << "File" << filename << "has changed on disk"; + + if ( filename != documentReference.filename() ) { + qDebug() << "Ignoring that file."; + return; + } + + // Add path back in case it was modified via "move temporary onto filename", + // which filewatcher treats as a remove and stops watching + + try { + emit pdfFileChanged(); + + { + + // Lock mutex + QMutexLocker locker(&mutex); + + // Create a new File Reference + PDFDocumentReference newDoc(filename, documentReference.cacheOption()); + + if ( documentReference.cacheOption() == PDFCacheOption::keepPDFinMemory ) { + // If we keep them in memory, a byte-by-byte compare should be resonably fast. + // If they are *identical*, we can skip the reloading. + + if( documentReference == newDoc ) { + qDebug() << "The new document compares identical to the old one, not doing anything."; + return; + } + } + + // Verify poppler can read this + newDoc.popplerDocument(); + + // replace the current reference with the new one + documentReference = newDoc; + + numberOfPages_ = documentReference.popplerDocument()->numPages(); + + // clear the page cache + clearAllCaches(); + } + + emit pdfFileRereadSuccesfully(); + } catch( std::runtime_error& e) { + qDebug() << "Unable to read the new reference. keeping the old one."; + emit pdfFileRereadFailed(); + } +} + +void PdfRenderFactory::clearAllCaches() +{ + + // Increment version, so that incoming "old" renders will get ignored + /// TODO: Send a termination signal to these lingering threads + + ++currentVersion; + + // No renders of the current version are taking place, incoming old renders + // will be ignored. + currentlyRenderingPages.clear(); + currentlyRenderingThumbnails.clear(); + + // Remove the caches. Since we use explicit copy semantics, its safe to empty + // these. + renderedPages.clear(); + renderedThumbnails.clear(); + +} + +int PdfRenderFactory::numberOfPages() const +{ + QMutexLocker lock(&mutex); + return numberOfPages_; } diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/pdfrenderfactory.h dspdfviewer-1.11~ubuntu13.10.1/pdfrenderfactory.h --- dspdfviewer-1.10.1~ubuntu13.10.1/pdfrenderfactory.h 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/pdfrenderfactory.h 2014-07-12 10:11:37.000000000 +0000 @@ -24,16 +24,55 @@ #include #include #include +#include +#include +#include #include #include #include "renderedpage.h" +#include "pdfcacheoption.h" +#include "pdfdocumentreference.h" + +/** Factory for rendered pages + * + * This class is responsible for rendering the PDF to images. + * + * You create a factory using a filename (if the file cannot be opened this will throw), + * then you can simply request renderings by supplying the desired size and page part + * to requestPageRendering. + * + * The class uses the global instance of QThreadPool and renders in paralell, meaning it + * can take advantage of SMP or Multi-Core Systems. + * + * NOTE for updating the external file: + * If you plan on rewriting the displayed file while this application is running, + * you can cache the entire file to memory. This can be done via constructor parameter. + * + * That way, if you create an invalid PDF file (for example you make an error in your latex + * document), it still has a copy of the old file present. + * The tradeoff is that this will cost you additional memory for *the entire PDF file*, which + * can be a lot if you have an image-heavy PDF file. + * + * The class will notify you via signals when it detects a file change. Then it will try to re-read + * the file, and it will notify you if that has succeeded or failed. + * + * If the re-reading failed, you can still get pages from cache, and you can keep rendering new ones + * if you have used the memory cache. However, if you try to render new pages, you will probably + * experience strange behaviour or the program will crash. + * + * If the re-reading went well, the cache will be cleared and new page renders will use the new pdf + * file. + * + */ class PdfRenderFactory : public QObject { Q_OBJECT private: - QString documentFilename; + PDFDocumentReference documentReference; + QFileSystemWatcher fileWatcher; + QTimer fileWatcherRewatchTimer; QSet< RenderingIdentifier > currentlyRenderingPages; QSet < int > currentlyRenderingThumbnails; @@ -41,18 +80,32 @@ QCache< RenderingIdentifier, RenderedPage> renderedPages; QCache< int, RenderedPage > renderedThumbnails; - QMutex mutex; + mutable QMutex mutex; + + /** This is a little helper for the cache-clear-function to detect + * renderings that have been started before, but finished after + * a cache clearing. + */ + quint64 currentVersion; + + int numberOfPages_; private: - QSharedPointer fetchDocument(); + void clearAllCaches(); public: - PdfRenderFactory( QString filename ); + PdfRenderFactory( const QString& filename, const PDFCacheOption& cacheSetting ); - void requestPageRendering( const RenderingIdentifier& renderingIdentifier); + /** Request a page rendering. Defaults to low priority (i.e. background rendering), please set High priority manually + * on the current page. + */ + void requestPageRendering( const RenderingIdentifier& originalIdentifier, QThread::Priority priority = QThread::LowPriority); void requestThumbnailRendering( int pageNumber); + int numberOfPages() const; + private slots: + void fileOnDiskChanged(const QString& filename); void pageThreadFinishedRendering( QSharedPointer renderedPage ); void thumbnailThreadFinishedRendering( QSharedPointer renderedPage ); @@ -60,6 +113,12 @@ void pageRendered( QSharedPointer renderedPage); void thumbnailRendered( QSharedPointer renderedThumbnail); + void pdfFileChanged(); + void pdfFileRereadSuccesfully(); + void pdfFileRereadFailed(); + + public slots: + void rewatchFile(); }; #endif // PDFRENDERFACTORY_H diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/pdfviewerwindow.cpp dspdfviewer-1.11~ubuntu13.10.1/pdfviewerwindow.cpp --- dspdfviewer-1.10.1~ubuntu13.10.1/pdfviewerwindow.cpp 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/pdfviewerwindow.cpp 2014-07-12 10:11:37.000000000 +0000 @@ -41,10 +41,11 @@ return m_monitor; } -PDFViewerWindow::PDFViewerWindow(unsigned int monitor, PagePart myPart, bool showInformationLine, const RuntimeConfiguration& r, bool enabled): +PDFViewerWindow::PDFViewerWindow(unsigned int monitor, PagePart myPart, bool showInformationLine, const RuntimeConfiguration& r, const QString& windowRole, bool enabled): QWidget(), m_enabled(enabled), m_monitor(monitor), + blank(false), minimumPageNumber(0), maximumPageNumber(65535), myPart(myPart) @@ -52,6 +53,8 @@ if ( ! enabled ) return; setupUi(this); + setWindowRole(windowRole); + setWindowTitle(QString("DS PDF Viewer - %1").arg(windowRole).replace('_', ' ') ); if ( !showInformationLine || ! r.showPresenterArea()) { /* If the information line is disabled because we're the primary screen, * or the user explicitly said so, disable it completely. @@ -98,9 +101,13 @@ void PDFViewerWindow::displayImage(QImage image) { - currentImage= image; imageLabel->setText(""); imageLabel->resize( image.size() ); + if ( blank ) { + // If we're supposed to display a blank image, leave it at this state. + return; + } + currentImage= image; imageLabel->setPixmap(QPixmap::fromImage(image)); //imageArea->setWidgetResizable(true); @@ -157,10 +164,12 @@ case Qt::Key_Up: case Qt::Key_Left: case Qt::Key_Backspace: - case Qt::Key_B: // Back case Qt::Key_P: //Previous emit previousPageRequested(); break; + case Qt::Key_B: + emit blankToggleRequested(); + break; case Qt::Key_Home: case Qt::Key_H: //Home emit restartRequested(); @@ -312,7 +321,7 @@ emit rerenderRequested(); } -QString PDFViewerWindow::timeToString(QTime time) const +QString PDFViewerWindow::timeToString(const QTime & time) const { return time.toString("HH:mm:ss"); } @@ -376,6 +385,25 @@ this->maximumPageNumber = maximumPageNumber; } +void PDFViewerWindow::setBlank(const bool blank) +{ + if ( this->blank == blank) + return; + /* State changes. request re-render */ + this->blank = blank; + qDebug() << "Changing blank state to" << blank; + if ( blank ) { + imageLabel->clear(); + } else { + emit rerenderRequested(); + } +} + +bool PDFViewerWindow::isBlank() const +{ + return blank; +} + #include "pdfviewerwindow.moc" diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/pdfviewerwindow.h dspdfviewer-1.11~ubuntu13.10.1/pdfviewerwindow.h --- dspdfviewer-1.10.1~ubuntu13.10.1/pdfviewerwindow.h 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/pdfviewerwindow.h 2014-07-12 10:11:37.000000000 +0000 @@ -37,6 +37,7 @@ bool m_enabled; unsigned int m_monitor; QImage currentImage; + bool blank; bool informationLineVisible; @@ -59,7 +60,7 @@ void addThumbnail(uint pageNumber, QImage thumbnail); - QString timeToString(QTime time) const; + QString timeToString(const QTime& time) const; QString timeToString(int milliseconds) const; void changePageNumberDialog(); @@ -69,7 +70,7 @@ * @param monitor monitor to start on (usually 0 for primary) * @param dspdfviewer Pointer to the application (to handle next/prev commands) */ - explicit PDFViewerWindow(unsigned int monitor, PagePart myPart, bool showInformationLine, const RuntimeConfiguration& r, bool enabled=true); + explicit PDFViewerWindow(unsigned int monitor, PagePart myPart, bool showInformationLine, const RuntimeConfiguration& r, const QString& windowRole, bool enabled=true); /** Sets the monitor to display this window on * Automatically calls reposition @@ -94,6 +95,8 @@ bool isInformationLineVisible() const; + bool isBlank() const; + void showLoadingScreen(int pageNumberToWaitFor); public slots: @@ -108,6 +111,8 @@ void setPageNumberLimits(uint minimumPageNumber, uint maximumPageNumber); + void setBlank(const bool blank); + signals: void nextPageRequested(); void previousPageRequested(); @@ -119,6 +124,8 @@ void rerenderRequested(); void quitRequested(); + + void blankToggleRequested(); }; #endif // PDFVIEWERWINDOW_H diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/README.md dspdfviewer-1.11~ubuntu13.10.1/README.md --- dspdfviewer-1.10.1~ubuntu13.10.1/README.md 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/README.md 2014-07-12 10:11:37.000000000 +0000 @@ -19,7 +19,9 @@ Left/Right, Mouse Buttons or Mouse Wheel: Back/Forward -S/F12: Swap screens (if you see the wall clock on the projector) +S or F12: Swap screens (if you see the wall clock on the projector) + +B: blank/unblank audience screen Q/Esc: Quit diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/renderingidentifier.h dspdfviewer-1.11~ubuntu13.10.1/renderingidentifier.h --- dspdfviewer-1.10.1~ubuntu13.10.1/renderingidentifier.h 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/renderingidentifier.h 2014-07-12 10:11:37.000000000 +0000 @@ -33,6 +33,13 @@ QSize theRequestedPageSize; public: + // This field needs to be changed after construction, therefore public + /** The "version" of the file this render started at + * + * This will be set and checked by PDFRenderFactory + */ + quint64 theVersion; + RenderingIdentifier(int pagenum, PagePart pagepart, QSize requestedPageSize); int pageNumber() const; PagePart pagePart() const; diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/renderthread.cpp dspdfviewer-1.11~ubuntu13.10.1/renderthread.cpp --- dspdfviewer-1.10.1~ubuntu13.10.1/renderthread.cpp 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/renderthread.cpp 2014-07-12 10:11:37.000000000 +0000 @@ -22,18 +22,18 @@ #include "renderutils.h" #include -RenderThread::RenderThread(QSharedPointer< Poppler::Document > theDocument, RenderingIdentifier renderIdent): +RenderThread::RenderThread(PDFDocumentReference theDocument, RenderingIdentifier renderIdent): QObject(), QRunnable(), - m_document(theDocument), renderMe(renderIdent) + m_page( theDocument.page( renderIdent.pageNumber() ) ), + renderMe(renderIdent) { - m_page = QSharedPointer( m_document->page(renderIdent.pageNumber()) ); } void RenderThread::run() { qDebug() << "RenderThread for " << renderMe << " started"; - QImage renderImage = RenderUtils::renderPagePart(m_page, renderMe.requestedPageSize(), renderMe.pagePart()); + QImage renderImage = RenderUtils::renderPagePart(m_page.page, renderMe.requestedPageSize(), renderMe.pagePart()); if ( renderImage.isNull() ) { qDebug() << "RenderThread for " << renderMe << " failed"; @@ -44,7 +44,7 @@ QList< QSharedPointer > links; - for( Poppler::Link* link: m_page->links() ) + for( Poppler::Link* link: m_page.page->links() ) { QSharedPointer ptrLink(link); links.append(ptrLink); @@ -54,4 +54,4 @@ emit renderingFinished(renderResult); } -#include "renderthread.cpp.moc" \ No newline at end of file +#include "renderthread.cpp.moc" diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/renderthread.h dspdfviewer-1.11~ubuntu13.10.1/renderthread.h --- dspdfviewer-1.10.1~ubuntu13.10.1/renderthread.h 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/renderthread.h 2014-07-12 10:11:37.000000000 +0000 @@ -24,18 +24,18 @@ #include #include #include "renderedpage.h" +#include "pdfpagereference.h" class RenderThread: public QObject, public QRunnable { Q_OBJECT private: - QSharedPointer m_document; - QSharedPointer m_page; + const PDFPageReference m_page; RenderingIdentifier renderMe; public: - RenderThread( QSharedPointer< Poppler::Document > theDocument, RenderingIdentifier renderIdent); + RenderThread( PDFDocumentReference theDocument, RenderingIdentifier renderIdent); void run(); diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/runtimeconfiguration.cpp dspdfviewer-1.11~ubuntu13.10.1/runtimeconfiguration.cpp --- dspdfviewer-1.10.1~ubuntu13.10.1/runtimeconfiguration.cpp 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/runtimeconfiguration.cpp 2014-07-12 10:11:37.000000000 +0000 @@ -54,6 +54,11 @@ "Pre-render the next arg slides\n" "NOTE: If you set this to zero, you might not get a thumbnail for the next slide unless it was loaded already." ) + ("cache-to-memory", + value(&m_cacheToMemory)->default_value(true), + "Cache the PDF file into memory\n" + "Useful if you are editing the PDF file with latex while using the presenter software." + ) ; options_description secondscreen("Options affecting the second screen"); secondscreen.add_options() @@ -193,3 +198,7 @@ return m_useSecondScreen; } +bool RuntimeConfiguration::cachePDFToMemory() const +{ + return m_cacheToMemory; +} diff -Nru dspdfviewer-1.10.1~ubuntu13.10.1/runtimeconfiguration.h dspdfviewer-1.11~ubuntu13.10.1/runtimeconfiguration.h --- dspdfviewer-1.10.1~ubuntu13.10.1/runtimeconfiguration.h 2013-10-22 16:31:44.000000000 +0000 +++ dspdfviewer-1.11~ubuntu13.10.1/runtimeconfiguration.h 2014-07-12 10:11:37.000000000 +0000 @@ -50,6 +50,9 @@ /** complete path to the PDF file */ std::string m_filePath; + /** Shall the complete PDF be read into memory */ + bool m_cacheToMemory; + /** Single-Display mode * * If True, there is only the audience display, the presenter's screen will remain hidden @@ -97,6 +100,8 @@ unsigned prerenderNextPages() const; bool useSecondScreen() const; + + bool cachePDFToMemory() const; }; #endif // RUNTIMECONFIGURATION_H