-
Notifications
You must be signed in to change notification settings - Fork 29
/
pdfviewerwindow.cpp
536 lines (468 loc) · 14.5 KB
/
pdfviewerwindow.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
/*
dspdfviewer - Dual Screen PDF Viewer for LaTeX-Beamer
Copyright (C) 2012 Danny Edel <mail@danny-edel.de>
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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "pdfviewerwindow.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QHBoxLayout>
#include <QLabel>
#include <QMouseEvent>
#include <QWindow>
#include <QScreen>
#include "debug.h"
#include <QInputDialog>
#include <QMessageBox>
#include "sconnect.h"
#include <cstdlib>
#include <boost/numeric/conversion/cast.hpp>
#include "ui_keybindings.h"
using boost::numeric_cast;
void PDFViewerWindow::setMonitor(const unsigned int monitor)
{
if ( m_monitor != monitor )
{
m_monitor = monitor;
reposition();
}
}
unsigned int PDFViewerWindow::getMonitor() const
{
return m_monitor;
}
PDFViewerWindow::PDFViewerWindow(unsigned int monitor, PagePart pagePart, bool showInformationLine, const RuntimeConfiguration& r, const WindowRole& wr, bool enabled):
QWidget(),
ui(),
m_enabled(enabled),
m_monitor(monitor),
currentImage(),
blank(false),
informationLineVisible(false),
currentPageNumber(0),
minimumPageNumber(0),
maximumPageNumber(65535),
correctImageRendered(false),
myPart(pagePart),
windowRole(wr),
runtimeConfiguration(r),
linkAreas()
{
if ( ! enabled )
return;
ui.setupUi(this);
unsigned mainImageHeight=100-r.bottomPaneHeight();
ui.verticalLayout->setStretch(0, numeric_cast<int>(mainImageHeight) );
ui.verticalLayout->setStretch(1, numeric_cast<int>(r.bottomPaneHeight()) );
setWindowRole(to_QString(wr));
/*: User visible Window Title Line */
if ( windowRole == WindowRole::AudienceWindow ) {
setWindowTitle(tr("DS PDF Viewer - Audience Window"));
} else {
setWindowTitle(tr("DS PDF Viewer - Secondary Window"));
}
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.
*/
hideInformationLine();
}
else {
/* Enable the information line, but control visibility of the components as requested by the user.
*/
this->showInformationLine();
ui.wallClock->setVisible(r.showWallClock());
ui.thumbnailArea->setVisible(r.showThumbnails());
ui.slideClock->setVisible(r.showSlideClock());
ui.presentationClock->setVisible(r.showPresentationClock());
}
reposition(); // This will fullscreen on its own
}
void PDFViewerWindow::reposition()
{
if ( ! m_enabled )
return;
this->setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);
this->showNormal();
/* This works on Windows */
#ifdef _WIN32
QList<QScreen *> screens = QApplication::screens();
if ( m_monitor < numeric_cast<unsigned>(screens.count()) )
this->windowHandle()->setScreen(screens[m_monitor]);
else
this->windowHandle()->setScreen(0);
this->showFullScreen();
#else
const int screenNum=numeric_cast<int>(m_monitor);
QRect rect = QGuiApplication::screens().at(screenNum)->geometry();
move( rect.topLeft() );
resize( rect.size() );
this->showFullScreen();
#endif
/* Note: The focus should be on the primary window, because at least
* Gnome draws the primary window's border onto the secondary.
*
* I dont mind the border on my helper screen, but the
* audience shouldnt see it.
*/
if ( !informationLineVisible )
this->activateWindow();
// this->resize( 100, 100 );
// this->move(rect.topLeft());
//this->showFullScreen();
}
void PDFViewerWindow::displayImage(QImage image)
{
ui.imageLabel->setText( QString() );
ui.imageLabel->resize( image.size() );
if ( blank ) {
// If we're supposed to display a blank image, leave it at this state.
return;
}
currentImage= image;
ui.imageLabel->setPixmap(QPixmap::fromImage(image));
//imageArea->setWidgetResizable(true);
/*
if ( geometry().size() != getTargetImageSize() )
reposition();
*/
}
void PDFViewerWindow::wheelEvent(QWheelEvent* e)
{
// QWidget::wheelEvent(e);
if ( e->angleDelta().y() > 0 )
{
DEBUGOUT << "Back";
emit previousPageRequested();
}
else{
DEBUGOUT << "Next";
emit nextPageRequested();
}
e->accept();
}
void PDFViewerWindow::keyPressEvent(QKeyEvent* e)
{
QWidget::keyPressEvent(e);
switch( e->key() )
{
case Qt::Key_F1:
case Qt::Key_Question: // Help
keybindingsPopup();
break;
case Qt::Key_G:
changePageNumberDialog();
break;
case Qt::Key_F12:
case Qt::Key_S: //Swap
emit screenSwapRequested();
break;
case Qt::Key_Escape:
case Qt::Key_Q: //quit
emit quitRequested();
break;
case Qt::Key_T:
emit secondScreenFunctionToggleRequested();
break;
case Qt::Key_D:
emit secondScreenDuplicateRequested();
break;
case Qt::Key_Space:
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_PageDown:
case Qt::Key_Down:
case Qt::Key_Right:
case Qt::Key_F: // Forward
case Qt::Key_N: // Next
emit nextPageRequested();
break;
case Qt::Key_PageUp:
case Qt::Key_Up:
case Qt::Key_Left:
case Qt::Key_Backspace:
case Qt::Key_P: //Previous
emit previousPageRequested();
break;
case Qt::Key_B:
case Qt::Key_Period:
emit blankToggleRequested();
break;
case Qt::Key_Home:
case Qt::Key_H: //Home
emit restartRequested();
break;
}
}
QSize PDFViewerWindow::getTargetImageSize() const
{
return ui.imageArea->geometry().size();
}
QSize PDFViewerWindow::getPreviewImageSize()
{
QSize completeThumbnailArea = ui.thumbnailArea->frameRect().size();
DEBUGOUT << "Space for all thumbnails:" << completeThumbnailArea;
/** FIXME Work needed:
* since this space must fit three images, we divide horizontal size by three
*/
QSize thirdThumbnailArea ( completeThumbnailArea.width()/3, completeThumbnailArea.height());
static QSize lastThumbnailSize = thirdThumbnailArea;
if ( lastThumbnailSize != thirdThumbnailArea ) {
lastThumbnailSize=thirdThumbnailArea;
emit rerenderRequested();
}
DEBUGOUT << "Space for one thumbnail:" << thirdThumbnailArea;
return thirdThumbnailArea;
}
void PDFViewerWindow::mousePressEvent(QMouseEvent* e)
{
// QWidget::mousePressEvent(e);
if ( e->button() == Qt::LeftButton ) {
emit nextPageRequested();
} else if ( e->button() == Qt::RightButton ) {
emit previousPageRequested();
}
// Ignore other buttons.
}
void PDFViewerWindow::hideInformationLine()
{
if ( ! m_enabled )
return;
informationLineVisible=false;
this->ui.bottomArea->hide();
}
bool PDFViewerWindow::isInformationLineVisible() const
{
return informationLineVisible;
}
void PDFViewerWindow::showInformationLine()
{
if ( ! m_enabled )
return;
informationLineVisible=true;
this->ui.bottomArea->show();
}
void PDFViewerWindow::addThumbnail(uint pageNumber, QImage thumbnail)
{
if ( pageNumber == currentPageNumber-1)
ui.previousThumbnail->setPixmap(QPixmap::fromImage(thumbnail));
else if ( pageNumber == currentPageNumber )
ui.currentThumbnail -> setPixmap(QPixmap::fromImage(thumbnail));
else if ( pageNumber == currentPageNumber+1 )
ui.nextThumbnail->setPixmap(QPixmap::fromImage(thumbnail));
}
void PDFViewerWindow::renderedPageIncoming(QSharedPointer< RenderedPage > renderedPage)
{
if ( ! m_enabled )
return;
// If we're blank, don't do anything with incoming renders.
// Un-blanking will request a rerender.
if ( blank )
return;
// It might be a thumbnail. If we're waiting for one, check if it would fit.
if ( isInformationLineVisible()
&& renderedPage->getPart() == runtimeConfiguration.thumbnailPagePart()
&& renderedPage->getIdentifier().requestedPageSize() == this->getPreviewImageSize() ) {
this->addThumbnail(renderedPage->getPageNumber(), renderedPage->getImage());
}
// If we are not waiting for an image, ignore incoming answers.
if ( correctImageRendered )
return;
if ( renderedPage->getPageNumber() != this->currentPageNumber )
return; // This page is not for us. Ignore it.
if ( renderedPage->getPart() != this->myPart )
return; // This is not our part
// There is an image incoming that might fit.
displayImage(renderedPage->getImage());
// It was even the right size! Yeah!
if ( renderedPage->getIdentifier().requestedPageSize() == getTargetImageSize() ) {
if ( this->runtimeConfiguration.hyperlinkSupport() ) {
this->parseLinks(renderedPage->getLinks());
}
this->correctImageRendered= true;
}
}
void PDFViewerWindow::showLoadingScreen(uint pageNumberToWaitFor)
{
if ( !m_enabled )
return;
// If we're blanked, don't render anything.
if ( blank )
return;
/// FIXME Loading image
this->currentPageNumber = pageNumberToWaitFor;
this->correctImageRendered = false;
this->currentImage = QImage();
ui.imageLabel->setPixmap(QPixmap());
ui.imageLabel->setText(tr("Loading page number %1").arg(pageNumberToWaitFor) );
/** Clear Thumbnails, they will come back in soon */
ui.previousThumbnail->setPixmap( QPixmap() );
ui.currentThumbnail->setPixmap( QPixmap() );
ui.nextThumbnail->setPixmap( QPixmap() );
}
PagePart PDFViewerWindow::getMyPagePart() const
{
return myPart;
}
void PDFViewerWindow::resizeEvent(QResizeEvent* resizeEvent)
{
if ( !m_enabled )
return;
QWidget::resizeEvent(resizeEvent);
DEBUGOUT << "Resize event" << resizeEvent;
DEBUGOUT << "Resized from" << resizeEvent->oldSize() << "to" << resizeEvent->size() << ", requesting re-render.";
static bool i3shellcode_executed = false;
if (
windowRole == WindowRole::AudienceWindow &&
runtimeConfiguration.i3workaround() &&
resizeEvent->spontaneous() &&
// i3 generates a spontaneous resize.
! i3shellcode_executed
// Make sure to do this only once
) {
/* FIXME this is deprecated */
// QCoreApplication::flush(); // Make sure the window has been painted
// This is the second screen. It has now been created.
// so we should call the i3 shellcode now
const std::string shellcode = runtimeConfiguration.i3workaround_shellcode();
DEBUGOUT << "Running i3 workaround shellcode" << shellcode.c_str();
int rc = std::system( shellcode.c_str() );
DEBUGOUT << "Return code of i3-workaround was" << rc ;
i3shellcode_executed=true;
}
emit rerenderRequested();
}
QString PDFViewerWindow::timeToString(const QTime & time) const
{
return time.toString( tr("HH:mm:ss", "This is used by QTime::toString. See its documentation before changing this.") );
}
QString PDFViewerWindow::timeToString(int milliseconds) const
{
return timeToString(QTime(0,0).addMSecs(milliseconds));
}
void PDFViewerWindow::updatePresentationClock(const QTime& presentationClock)
{
ui.presentationClock->setText( QCoreApplication::translate("Form", "Total\n%1").arg(timeToString(presentationClock)));
}
void PDFViewerWindow::updateSlideClock(const QTime& slideClock)
{
ui.slideClock->setText(timeToString(slideClock) );
}
void PDFViewerWindow::updateWallClock(const QTime& wallClock)
{
ui.wallClock->setText(timeToString(wallClock));
}
void PDFViewerWindow::keybindingsPopup()
{
Ui::KeybindingsDialog keybindUi;
QDialog popup;
keybindUi.setupUi(&popup);
keybindUi.label_versionstring->setText(
keybindUi.label_versionstring->text().arg(
QString::fromUtf8(DSPDFVIEWER_VERSION )
)
);
popup.exec();
}
void PDFViewerWindow::changePageNumberDialog()
{
bool ok;
/* While PDF counts zero-based, users probably think that the first
* page is called "1".
*/
uint displayMinNumber = minimumPageNumber+1;
uint displayMaxNumber = maximumPageNumber+1;
uint displayCurNumber = currentPageNumber+1;
int targetPageNumber = QInputDialog::getInt(this,
/* Window Caption */ tr("Select page"),
/* Input field caption */
tr("Jump to page number (%1-%2):").arg(displayMinNumber).arg(displayMaxNumber),
/* Starting number. */
numeric_cast<int>(displayCurNumber),
/* minimum value */
numeric_cast<int>(displayMinNumber),
/* maximum value */
numeric_cast<int>(displayMaxNumber),
/* Step */
1,
/* Did the user accept? */
&ok);
targetPageNumber-=1; // Convert back to zero-based numbering scheme
if ( ok )
{
emit pageRequested(numeric_cast<uint>(targetPageNumber));
}
}
void PDFViewerWindow::setPageNumberLimits(uint minPageNumber, uint maxPageNumber)
{
this->minimumPageNumber = minPageNumber;
this->maximumPageNumber = maxPageNumber;
}
void PDFViewerWindow::setBlank(const bool newBlank)
{
if ( this->blank == newBlank)
return;
/* State changes. request re-render */
this->blank = newBlank;
DEBUGOUT << "Changing blank state to" << blank;
if ( blank ) {
ui.imageLabel->clear();
} else {
emit rerenderRequested();
}
}
bool PDFViewerWindow::isBlank() const
{
return blank;
}
void PDFViewerWindow::setMyPagePart(const PagePart& newPagePart)
{
this->myPart = newPagePart;
}
void PDFViewerWindow::parseLinks(QList< AdjustedLink > links)
{
QList< HyperlinkArea* > newLinkAreas;
for( AdjustedLink const & link: links ) {
const QRectF& rect = link.linkArea();
if ( rect.isNull() ) {
WARNINGOUT << "Null Link Area not supported yet.";
continue;
}
const Poppler::Link::LinkType& type = link.link()->linkType();
if ( type == Poppler::Link::LinkType::Goto ) {
// type is Goto. Bind it to imageLabel
const Poppler::LinkGoto& linkGoto = dynamic_cast<const Poppler::LinkGoto&>( * link.link() );
if( linkGoto.isExternal() ) {
WARNINGOUT << "External links are not supported yet.";
continue;
}
HyperlinkArea* linkArea = new HyperlinkArea(ui.imageLabel, link);
sconnect( linkArea, SIGNAL(gotoPageRequested(uint)), this, SLOT(linkClicked(uint)) );
newLinkAreas.append(linkArea);
}
else {
WARNINGOUT << "Types other than Goto are not supported yet.";
continue;
}
}
// Schedule all old links for deletion
for( HyperlinkArea* hla: this->linkAreas)
hla->deleteLater();
// Add the new list
this->linkAreas = newLinkAreas;
}
void PDFViewerWindow::linkClicked(uint targetNumber)
{
DEBUGOUT << "Hyperlink detected";
emit pageRequested(targetNumber);
}