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