1 /*
2     dspdfviewer - Dual Screen PDF Viewer for LaTeX-Beamer
3     Copyright (C) 2012  Danny Edel <mail@danny-edel.de>
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 
20 
21 #include "dspdfviewer.h"
22 #include "renderutils.h"
23 #include "renderingidentifier.h"
24 #include "sconnect.h"
25 
26 #include <QLabel>
27 #include <QMenu>
28 #include <QMenuBar>
29 #include <QAction>
30 #include <QApplication>
31 #include <QDesktopWidget>
32 #include <qlayout.h>
33 
34 #include "debug.h"
35 #include <stdexcept>
36 #include <boost/numeric/conversion/cast.hpp>
37 
38 using boost::numeric_cast;
39 
DSPDFViewer(const RuntimeConfiguration & r)40 DSPDFViewer::DSPDFViewer(const RuntimeConfiguration& r):
41 	runtimeConfiguration(r),
42 	clockDisplayTimer(),
43 	slideStart(),
44 	presentationStart(),
45 	presentationClockRunning(false),
46 	documentFileWatcher(),
47  renderFactory(r),
48  m_pagenumber(0),
49  audienceWindow(1,   r.useFullPage()                 ? PagePart::FullPage : PagePart::LeftHalf , false, r, WindowRole::AudienceWindow),
50  secondaryWindow(0, (r.useFullPage() | r.duplicate())? PagePart::FullPage : PagePart::RightHalf, true , r, WindowRole::PresenterWindow, r.useSecondScreen())
51 {
52   DEBUGOUT << tr("Starting constructor") ;
53 
54   if ( ! r.useSecondScreen() ) {
55     secondaryWindow.hide();
56   }
57 
58 
59   audienceWindow.showLoadingScreen(0);
60   secondaryWindow.showLoadingScreen(0);
61 
62 #if 0 // FIXME Make sure exceptions on startup get handled correctly
63   if ( ! pdfDocument  || pdfDocument->isLocked() )
64   {
65     /// FIXME: Error message
66     throw std::runtime_error( tr("I was not able to open the PDF document. Sorry.").toLocal8Bit().constData() );
67   }
68 #endif
69   DEBUGOUT << tr("Connecting audience window");
70 
71   audienceWindow.setPageNumberLimits(0, numberOfPages()-1);
72 
73   sconnect( &renderFactory, SIGNAL(pageRendered(QSharedPointer<RenderedPage>)), &audienceWindow, SLOT(renderedPageIncoming(QSharedPointer<RenderedPage>)));
74   sconnect( &renderFactory, SIGNAL(pdfFileRereadSuccesfully()), this, SLOT(renderPage()));
75 
76   sconnect( &audienceWindow, SIGNAL(nextPageRequested()), this, SLOT(goForward()));
77   sconnect( &audienceWindow, SIGNAL(previousPageRequested()), this, SLOT(goBackward()));
78   sconnect( &audienceWindow, SIGNAL(pageRequested(uint)), this, SLOT(gotoPage(uint)));
79 
80   sconnect( &audienceWindow, SIGNAL(quitRequested()), this, SLOT(exit()));
81   sconnect( &audienceWindow, SIGNAL(rerenderRequested()), this, SLOT(renderPage()));
82   sconnect( &audienceWindow, SIGNAL(restartRequested()), this, SLOT(goToStartAndResetClocks()));
83 
84   sconnect( &audienceWindow, SIGNAL(screenSwapRequested()), this, SLOT(swapScreens()) );
85 
86   sconnect( &audienceWindow, SIGNAL(blankToggleRequested()), this, SLOT(toggleAudienceScreenBlank()));
87   sconnect( &audienceWindow, SIGNAL(secondScreenFunctionToggleRequested()), this, SLOT(toggleSecondaryScreenFunction()));
88   sconnect( &audienceWindow, SIGNAL(secondScreenDuplicateRequested()), this, SLOT(toggleSecondaryScreenDuplication()));
89 
90   if ( r.useSecondScreen() )
91   {
92     DEBUGOUT << tr("Connecting secondary window");
93 
94     secondaryWindow.setPageNumberLimits(0, numberOfPages()-1);
95 
96     sconnect( &renderFactory, SIGNAL(pageRendered(QSharedPointer<RenderedPage>)), &secondaryWindow, SLOT(renderedPageIncoming(QSharedPointer<RenderedPage>)));
97 
98     sconnect( &secondaryWindow, SIGNAL(nextPageRequested()), this, SLOT(goForward()));
99     sconnect( &secondaryWindow, SIGNAL(previousPageRequested()), this, SLOT(goBackward()));
100     sconnect( &secondaryWindow, SIGNAL(pageRequested(uint)), this, SLOT(gotoPage(uint)));
101 
102     sconnect( &secondaryWindow, SIGNAL(quitRequested()), this, SLOT(exit()));
103     sconnect( &secondaryWindow, SIGNAL(rerenderRequested()), this, SLOT(renderPage()));
104     sconnect( &secondaryWindow, SIGNAL(restartRequested()), this, SLOT(goToStartAndResetClocks()));
105 
106     sconnect( &secondaryWindow, SIGNAL(screenSwapRequested()), this, SLOT(swapScreens()) );
107 
108     sconnect( &secondaryWindow, SIGNAL(blankToggleRequested()), this, SLOT(toggleAudienceScreenBlank()));
109     sconnect( &secondaryWindow, SIGNAL(secondScreenFunctionToggleRequested()), this, SLOT(toggleSecondaryScreenFunction()));
110 	sconnect( &secondaryWindow, SIGNAL(secondScreenDuplicateRequested()), this, SLOT(toggleSecondaryScreenDuplication()));
111 
112 
113     sconnect( this, SIGNAL(presentationClockUpdate(QTime)), &secondaryWindow, SLOT(updatePresentationClock(QTime)));
114     sconnect( this, SIGNAL(slideClockUpdate(QTime)), &secondaryWindow, SLOT(updateSlideClock(QTime)));
115     sconnect( this, SIGNAL(wallClockUpdate(QTime)), &secondaryWindow, SLOT(updateWallClock(QTime)));
116 
117 
118   }
119 
120   renderPage();
121 
122   clockDisplayTimer.setInterval(TIMER_UPDATE_INTERVAL);
123   clockDisplayTimer.start();
124   sconnect( &clockDisplayTimer, SIGNAL(timeout()), this, SLOT(sendAllClockSignals()));
125   sendAllClockSignals();
126 }
127 
128 
129 
~DSPDFViewer()130 DSPDFViewer::~DSPDFViewer()
131 {}
132 
goBackward()133 void DSPDFViewer::goBackward()
134 {
135   resetSlideClock();
136   if ( pageNumber() > 0 )
137     gotoPage(pageNumber()-1);
138 }
139 
goForward()140 void DSPDFViewer::goForward()
141 {
142   resetSlideClock();
143   if ( pageNumber() < numberOfPages()-1 )
144     gotoPage(pageNumber()+1);
145 }
146 
pageNumber() const147 unsigned int DSPDFViewer::pageNumber() const
148 {
149   return m_pagenumber;
150 }
151 
152 
renderPage()153 void DSPDFViewer::renderPage()
154 {
155   DEBUGOUT << tr("Requesting rendering of page %1").arg(m_pagenumber);
156   if ( m_pagenumber >= numberOfPages() ) {
157     DEBUGOUT << "Page number out of range, clamping to " << numberOfPages()-1;
158     m_pagenumber = numberOfPages()-1;
159   }
160   audienceWindow.showLoadingScreen(m_pagenumber);
161   secondaryWindow.showLoadingScreen(m_pagenumber);
162   if ( runtimeConfiguration.showThumbnails() ) {
163     theFactory()->requestPageRendering( toThumbnailRenderIdent(m_pagenumber, secondaryWindow), QThread::LowPriority);
164   }
165   theFactory()->requestPageRendering( toRenderIdent(m_pagenumber, audienceWindow), QThread::HighestPriority);
166 
167   if ( runtimeConfiguration.useSecondScreen() ) {
168     theFactory()->requestPageRendering( toRenderIdent(m_pagenumber, secondaryWindow), QThread::HighPriority);
169   }
170 
171   /** Pre-Render next pages **/
172   for ( unsigned i=m_pagenumber; i<m_pagenumber+runtimeConfiguration.prerenderNextPages() && i < numberOfPages() ; i++) {
173     if ( runtimeConfiguration.showThumbnails() ) {
174       theFactory()->requestPageRendering( toThumbnailRenderIdent(i, secondaryWindow), QThread::LowPriority);
175     }
176     theFactory()->requestPageRendering( toRenderIdent(i, audienceWindow));
177     if ( runtimeConfiguration.useSecondScreen() ) {
178       theFactory()->requestPageRendering( toRenderIdent(i, secondaryWindow));
179     }
180   }
181 
182   /** Pre-Render previous pages **/
183 
184   for ( unsigned i= std::max(m_pagenumber,runtimeConfiguration.prerenderPreviousPages())-runtimeConfiguration.prerenderPreviousPages();
185        i<m_pagenumber; i++) {
186     if ( runtimeConfiguration.showThumbnails() ) {
187       theFactory()->requestPageRendering( toThumbnailRenderIdent(i, secondaryWindow), QThread::LowPriority);
188     }
189     theFactory()->requestPageRendering(toRenderIdent(i, audienceWindow));
190     if ( runtimeConfiguration.useSecondScreen() ) {
191       theFactory()->requestPageRendering(toRenderIdent(i, secondaryWindow));
192     }
193   }
194 
195 }
196 
197 
gotoPage(unsigned int pageNumber)198 void DSPDFViewer::gotoPage(unsigned int pageNumber)
199 {
200   if ( m_pagenumber != pageNumber
201       && numberOfPages() > pageNumber )
202   {
203     m_pagenumber = pageNumber;
204     renderPage();
205   } else {
206     WARNINGOUT << tr("Requested page number %1 which is out of range! Ignoring request.").arg(pageNumber);
207   }
208 }
209 
swapScreens()210 void DSPDFViewer::swapScreens()
211 {
212   if ( audienceWindow.getMonitor() == 0 )
213   {
214     audienceWindow.setMonitor(1);
215     secondaryWindow.setMonitor(0);
216     renderPage();
217   }
218   else
219   {
220     audienceWindow.setMonitor(0);
221     secondaryWindow.setMonitor(1);
222     renderPage();
223   }
224 }
225 
226 
227 
exit()228 void DSPDFViewer::exit()
229 {
230   audienceWindow.close();
231   secondaryWindow.close();
232 }
233 
theFactory()234 PdfRenderFactory* DSPDFViewer::theFactory()
235 {
236   return &renderFactory;
237 }
238 
numberOfPages() const239 unsigned int DSPDFViewer::numberOfPages() const {
240   int pages = renderFactory.numberOfPages();
241   // Numeric cast includes error handling.
242   return numeric_cast<uint>(pages);
243 }
244 
goToStartAndResetClocks()245 void DSPDFViewer::goToStartAndResetClocks()
246 {
247   presentationClockRunning=false;
248   sendAllClockSignals();
249   gotoPage(0);
250 }
251 
presentationClock() const252 QTime DSPDFViewer::presentationClock() const
253 {
254   if ( ! presentationClockRunning )
255     return QTime(0,0);
256   return timeSince(presentationStart);
257 }
258 
wallClock() const259 QTime DSPDFViewer::wallClock() const
260 {
261   return QTime::currentTime();
262 }
263 
slideClock() const264 QTime DSPDFViewer::slideClock() const
265 {
266   if ( ! presentationClockRunning )
267     return QTime(0,0);
268   return timeSince( slideStart );
269 }
270 
resetSlideClock()271 void DSPDFViewer::resetSlideClock()
272 {
273   /* Always resets the slide clock. */
274   slideStart.start();
275   if ( ! presentationClockRunning ) {
276     /* If this starts a presentation, also reset the presentation clock. */
277     presentationStart.start();
278   }
279   /* and make sure they'll get refreshed a second later aswell. */
280   slideStart.start();
281 
282   presentationClockRunning=true;
283 
284   /* Refresh display times immediately */
285   sendAllClockSignals();
286 }
287 
sendAllClockSignals() const288 void DSPDFViewer::sendAllClockSignals() const
289 {
290   emit wallClockUpdate(wallClock());
291   emit slideClockUpdate(slideClock());
292   emit presentationClockUpdate(presentationClock());
293 }
294 
timeSince(const QElapsedTimer & startPoint) const295 QTime DSPDFViewer::timeSince(const QElapsedTimer& startPoint) const
296 {
297   QTime result(0,0);
298   result = result.addMSecs(static_cast<int>(startPoint.elapsed()));
299   return result;
300 }
301 
toRenderIdent(unsigned int pageNumber,const PDFViewerWindow & window)302 RenderingIdentifier DSPDFViewer::toRenderIdent(unsigned int pageNumber, const PDFViewerWindow& window)
303 {
304   return
305   RenderingIdentifier ( pageNumber, window.getMyPagePart(), window.getTargetImageSize());
306 
307 }
308 
toThumbnailRenderIdent(unsigned int pageNumber,PDFViewerWindow & window)309 RenderingIdentifier DSPDFViewer::toThumbnailRenderIdent(unsigned int pageNumber, PDFViewerWindow& window)
310 {
311   QSize newSize = window.getPreviewImageSize();
312   static QSize thumbnailSize;
313   if ( thumbnailSize != newSize ) {
314     DEBUGOUT << "Thumbnail size changed from" << thumbnailSize << "to" << newSize;
315 	thumbnailSize=newSize;
316 	renderPage();
317   }
318   return RenderingIdentifier( pageNumber, runtimeConfiguration.thumbnailPagePart(), thumbnailSize);
319 }
320 
321 
isAudienceScreenBlank() const322 bool DSPDFViewer::isAudienceScreenBlank() const
323 {
324   return audienceWindow.isBlank();
325 }
326 
setAudienceScreenBlank()327 void DSPDFViewer::setAudienceScreenBlank()
328 {
329   audienceWindow.setBlank(true);
330 }
331 
setAudienceScreenVisible()332 void DSPDFViewer::setAudienceScreenVisible()
333 {
334   audienceWindow.setBlank(false);
335 }
336 
toggleAudienceScreenBlank()337 void DSPDFViewer::toggleAudienceScreenBlank()
338 {
339   if ( isAudienceScreenBlank() ) {
340     setAudienceScreenVisible();
341   } else {
342     setAudienceScreenBlank();
343   }
344 }
345 
toggleSecondaryScreenFunction()346 void DSPDFViewer::toggleSecondaryScreenFunction()
347 {
348   DEBUGOUT << "Swapping second screen";
349   switch ( secondaryWindow.getMyPagePart() ) {
350     case PagePart::FullPage:
351       // Nothing to do
352       break;
353     case PagePart::LeftHalf:
354       secondaryWindow.setMyPagePart(PagePart::RightHalf);
355       break;
356     case PagePart::RightHalf:
357       secondaryWindow.setMyPagePart(PagePart::LeftHalf);
358       break;
359   }
360   emit renderPage();
361 }
362 
toggleSecondaryScreenDuplication()363 void DSPDFViewer::toggleSecondaryScreenDuplication()
364 {
365   DEBUGOUT << "Swapping second screen duplication";
366   switch ( secondaryWindow.getMyPagePart() ) {
367     case PagePart::FullPage:
368       if (!runtimeConfiguration.useFullPage()) {
369         secondaryWindow.setMyPagePart(PagePart::RightHalf);
370       }
371       break;
372     case PagePart::LeftHalf:
373     case PagePart::RightHalf:
374       secondaryWindow.setMyPagePart(PagePart::FullPage);
375       break;
376   }
377   emit renderPage();
378 }
379 
audienceGeometry() const380 const QRect DSPDFViewer::audienceGeometry() const {
381 	return audienceWindow.geometry();
382 }
383 
secondGeometry() const384 const QRect DSPDFViewer::secondGeometry() const {
385 	return secondaryWindow.geometry();
386 }
387