1 /*
2 This is part of TeXworks, an environment for working with TeX documents
3 Copyright (C) 2007-2010 Jonathan Kew
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
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 For links to further information, or to contact the author,
19 see <http://texworks.org/>.
20 */
21
22 #ifndef NO_POPPLER_PREVIEW
23
24 #include "PDFDocument.h"
25 #include "PDFDocks.h"
26 //#include "FindDialog.h"
27
28 #include <QPaintEngine>
29 #include <QRegion>
30 #include <QUrl>
31 #include <QShortcut>
32 #include <QtCore/qnumeric.h>
33 #include <QtCore/qmath.h>
34 #if QT_VERSION<QT_VERSION_CHECK(5,15,0)
35 #include <QDesktopWidget>
36 #endif
37
38 #include "universalinputdialog.h"
39
40 #include "configmanager.h"
41 #include "smallUsefulFunctions.h"
42 #include "PDFDocument_config.h"
43 #include "configmanagerinterface.h"
44 #include "pdfannotationdlg.h"
45 #include "pdfannotation.h"
46 #include "minisplitter.h"
47 #include "titledpanel.h"
48
49 #include "pdfsplittool.h"
50 #include "filedialog.h"
51
52 //#include "GlobalParams.h"
53
54 #include "poppler-link.h"
55
56 #define SYNCTEX_GZ_EXT ".synctex.gz"
57 #define SYNCTEX_EXT ".synctex"
58
59 const qreal kMaxScaleFactor = 4.0;
60 const qreal kMinScaleFactor = 0.125;
61
62 // tool codes
63 const int kNone = 0;
64 const int kMagnifier = 1;
65 const int kScroll = 2;
66 const int kSelectText = 3;
67 const int kSelectImage = 4;
68 const int kPresentation = 5; //left-click: next, rclick: prev (for these presentation/mouse-pointer)
69
70 static PDFDocumentConfig *globalConfig = nullptr;
71 bool PDFDocument::isCompiling = false;
72 bool PDFDocument::isMaybeCompiling = false;
73
74 static const int GridBorder = 5;
75
76
convertImage(const QPixmap & pixmap,bool invertColors,bool convertToGray)77 QPixmap convertImage(const QPixmap &pixmap, bool invertColors, bool convertToGray)
78 {
79 if (pixmap.isNull()) return pixmap;
80
81 QImage img = pixmap.toImage();
82 if (invertColors)
83 img.invertPixels();
84 if (convertToGray) {
85 QImage retImg(img.width(), img.height(), QImage::Format_Indexed8);
86 QVector<QRgb> table(256);
87 for ( int i = 0; i < 256; ++i) {
88 table[i] = qRgb(i, i, i);
89 }
90 retImg.setColorTable(table);
91 for (int i = 0; i < img.width(); i++) {
92 for (int j = 0; j < img.height(); j++) {
93 QRgb value = img.pixel(i, j);
94 retImg.setPixel(i, j, static_cast<uint>(qGray(value)));
95 }
96 }
97 return QPixmap::fromImage(retImg);
98 }
99 return QPixmap::fromImage(img);
100 }
101
102 //====================Zoom utils==========================
103
zoomToScreen(QWidget * window)104 void zoomToScreen(QWidget *window)
105 {
106 #if QT_VERSION<QT_VERSION_CHECK(5,15,0)
107 QDesktopWidget *desktop = QApplication::desktop();
108 QRect screenRect = desktop->availableGeometry(window);
109 #else
110 QRect screenRect = window->screen()->availableGeometry();
111 #endif
112 screenRect.setTop(screenRect.top() + window->geometry().y() - window->y());
113 window->setGeometry(screenRect);
114 }
115
zoomToHalfScreen(QWidget * window,bool rhs)116 void zoomToHalfScreen(QWidget *window, bool rhs)
117 {
118 #if QT_VERSION<QT_VERSION_CHECK(5,15,0)
119 QDesktopWidget *desktop = QApplication::desktop();
120 QRect r = desktop->availableGeometry(window);
121 #else
122 QRect r = window->screen()->availableGeometry();
123 #endif
124
125 int wDiff = window->frameGeometry().width() - window->width();
126 int hDiff = window->frameGeometry().height() - window->height();
127
128 if (hDiff == 0 && wDiff == 0) {
129 // window may not be decorated yet, so we don't know how large
130 // the title bar etc. is. Try to extrapolate from other top-level
131 // windows (if some are available). We assume that if either
132 // hDiff or wDiff is non-zero, we have found a decorated window
133 // and can use its values.
134 foreach (QWidget *widget, QApplication::topLevelWidgets()) {
135 if (!qobject_cast<QMainWindow *>(widget))
136 continue;
137 hDiff = widget->frameGeometry().height() - widget->height();
138 wDiff = widget->frameGeometry().width() - widget->width();
139 if (hDiff != 0 || wDiff != 0)
140 break;
141 }
142 // If we still have no valid value for hDiff/wDiff, just guess (on some
143 // platforms)
144 if (hDiff == 0 && wDiff == 0) {
145 // (these values were determined on WinXP with default theme)
146 hDiff = 34;
147 wDiff = 8;
148 }
149 }
150
151 if (rhs) {
152 r.setLeft(r.left() + r.width() / 2);
153 window->move(r.left(), r.top());
154 window->resize(r.width() - wDiff, r.height() - hDiff);
155 } else {
156 r.setWidth(r.width() / 2);
157 window->move(r.left(), r.top());
158 window->resize(r.width() - wDiff, r.height() - hDiff);
159 }
160 }
161
windowsSideBySide(QWidget * window1,QWidget * window2)162 void windowsSideBySide(QWidget *window1, QWidget *window2)
163 {
164 #if QT_VERSION>=QT_VERSION_CHECK(5,15,0)
165 // if the windows reside on the same screen zoom each so that it occupies
166 // half of that screen
167 if (window1->screen() == window2->screen()) {
168 #else
169 QDesktopWidget *desktop = QApplication::desktop();
170
171 // if the windows reside on the same screen zoom each so that it occupies
172 // half of that screen
173 if (desktop->screenNumber(window1) == desktop->screenNumber(window2)) {
174 #endif
175 int window1left = window1->pos().x() <= window2->pos().x();
176 zoomToHalfScreen(window1, !window1left);
177 zoomToHalfScreen(window2, window1left);
178 }
179 // if the windows reside on different screens zoom each so that it uses
180 // its whole screen
181 else {
182 zoomToScreen(window1);
183 zoomToScreen(window2);
184 }
185 }
186
187 void tileWindowsInRect(const QWidgetList &windows, const QRect &bounds)
188 {
189 int numWindows = windows.count();
190 int rows = 1, cols = 1;
191 while (rows * cols < numWindows)
192 if (rows == cols)
193 ++cols;
194 else
195 ++rows;
196 QRect r;
197 r.setWidth(bounds.width() / cols);
198 r.setHeight(bounds.height() / rows);
199 r.moveLeft(bounds.left());
200 r.moveTop(bounds.top());
201 int x = 0, y = 0;
202 foreach (QWidget *window, windows) {
203 int wDiff = window->frameGeometry().width() - window->width();
204 int hDiff = window->frameGeometry().height() - window->height();
205 window->move(r.left(), r.top());
206 window->resize(r.width() - wDiff, r.height() - hDiff);
207 if (window->isMinimized())
208 window->showNormal();
209 if (++x == cols) {
210 x = 0;
211 ++y;
212 r.moveLeft(bounds.left());
213 r.moveTop(bounds.top() + (bounds.height() * y) / rows);
214 } else
215 r.moveLeft(bounds.left() + (bounds.width() * x) / cols);
216 }
217 }
218
219 void stackWindowsInRect(const QWidgetList &windows, const QRect &bounds)
220 {
221 const int kStackingOffset = 20;
222 QRect r(bounds);
223 r.setWidth(r.width() / 2);
224 int index = 0;
225 foreach (QWidget *window, windows) {
226 int wDiff = window->frameGeometry().width() - window->width();
227 int hDiff = window->frameGeometry().height() - window->height();
228 window->move(r.left(), r.top());
229 window->resize(r.width() - wDiff, r.height() - hDiff);
230 if (window->isMinimized())
231 window->showNormal();
232 r.moveLeft(r.left() + kStackingOffset);
233 if (r.right() > bounds.right()) {
234 r = bounds;
235 r.setWidth(r.width() / 2);
236 index = 0;
237 } else if (++index == 10) {
238 r.setTop(bounds.top());
239 index = 0;
240 } else {
241 r.setTop(r.top() + kStackingOffset);
242 if (r.height() < bounds.height() / 2) {
243 r.setTop(bounds.top());
244 index = 0;
245 }
246 }
247 }
248 }
249
250
251 //================== PDFMagnifier ========================
252
253 const int kMagFactor = 2;
254
255 PDFMagnifier::PDFMagnifier(QWidget *parent, qreal inDpi)
256 : QLabel(parent)
257 , oldshape(-2)
258 , page(-1)
259 , overScale(1)
260 , scaleFactor(kMagFactor)
261 , parentDpi(inDpi)
262 , convertedImageIsGrayscale(false)
263 , convertedImageIsColorInverted(false)
264 , imageDpi(0)
265 , imagePage(-1)
266 {
267 }
268
269 void PDFMagnifier::setPage(int pageNr, qreal scale, const QRect &visibleRect)
270 {
271 page = pageNr;
272
273 overScale = this->devicePixelRatio();
274
275 scaleFactor = scale * kMagFactor;
276 if (page < 0) {
277 imagePage = -1;
278 image = QPixmap();
279 convertedImage = QPixmap();
280 convertedImageIsGrayscale = false;
281 convertedImageIsColorInverted = false;
282 } else {
283 PDFWidget *parent = qobject_cast<PDFWidget *>(parentWidget());
284 if (parent != nullptr) {
285 PDFDocument *doc = parent->getPDFDocument();
286 QWidget *viewport = parent->parentWidget();
287 if (viewport != nullptr && viewport->parentWidget() != nullptr) {
288 qreal dpi = parentDpi * scaleFactor;
289 QPoint tl = parent->mapFromParent(viewport->rect().topLeft());
290 QPoint br = parent->mapFromParent(viewport->rect().bottomRight());
291 tl -= visibleRect.topLeft();
292 br -= visibleRect.topLeft();
293 if (tl.x() < 0) tl.setX(0);
294 if (tl.y() < 0) tl.setY(0);
295 if (br.x() > visibleRect.width()) br.setX(visibleRect.width());
296 if (br.y() > visibleRect.height()) br.setY(visibleRect.height());
297 QSize size = QSize(br.x() - tl.x(), br.y() - tl.y()) * kMagFactor;
298 QPoint loc = tl * kMagFactor;
299 if (page != imagePage || qAbs(dpi/imageDpi-1.0)>0.001 || loc != imageLoc || size != imageSize) {
300 //don't cache in rendermanager in order to reduce memory consumption
301 image = doc->renderManager->renderToImage(pageNr, this, "setImage", dpi * overScale , dpi * overScale, loc.x() * overScale, loc.y() * overScale, size.width() * overScale, size.height() * overScale, false, true);
302 }
303 imagePage = page;
304 imageDpi = dpi;
305 imageLoc = loc;
306 imageSize = size;
307
308 mouseTranslate = rect().center() - imageLoc - (visibleRect.topLeft() /*- offset*/) * kMagFactor;
309
310 }
311 }
312 }
313 update();
314 }
315
316 void PDFMagnifier::reshape()
317 {
318 Q_ASSERT(globalConfig);
319 if (!globalConfig || globalConfig->magnifierShape == oldshape) return;
320
321 switch (globalConfig->magnifierShape) {
322 case 2: [[clang::fallthrough]];
323 case 1: { //circular
324 int side = qMin(width(), height());
325 QRegion maskedRegion(width() / 2 - side / 2, height() / 2 - side / 2, side, side, QRegion::Ellipse);
326 setMask(maskedRegion);
327 break;
328 }
329 default:
330 setMask(QRect(0, 0, width(), height())); //rectangular
331 }
332 }
333
334 void PDFMagnifier::setImage(const QPixmap &img, int pageNr)
335 {
336 if (pageNr == page) {
337 image = img;
338 convertedImage = QPixmap();
339 convertedImageIsGrayscale = false;
340 convertedImageIsColorInverted = false;
341 }
342 update();
343 }
344
345 void PDFMagnifier::paintEvent(QPaintEvent *event)
346 {
347 QPainter painter(this);
348 drawFrame(&painter);
349 QRect tmpRect(event->rect().x()*overScale, event->rect().y()*overScale, event->rect().width()*overScale, event->rect().height()*overScale);
350 int side = qMin(width(), height()) ;
351 QRect outline(width() / 2 - side / 2 + 1, height() / 2 - side / 2 + 1, side - 2, side - 2);
352
353 if(globalConfig->magnifierShape==PDFDocumentConfig::CircleWithShadow){
354 // circular magnifier, add transparent shadow
355 const int padding=10;
356
357 QRadialGradient gradient(outline.center(), outline.width() / 2.0 , outline.center());
358 QColor color(Qt::black);
359 color.setAlpha(0);
360 gradient.setColorAt(1.0, color);
361 color.setAlpha(64);
362 gradient.setColorAt(1.0 - padding * 2.0 / (outline.width()), color);
363
364 painter.fillRect(outline, gradient);
365 outline.adjust(padding,padding,-padding,-padding);
366 QRegion maskedRegion(outline, QRegion::Ellipse);
367 painter.setClipRegion(maskedRegion);
368 }
369
370 // draw highlight if necessary
371
372 painter.drawPixmap(event->rect(), getConvertedImage(), tmpRect.translated(kMagFactor * overScale * pos() + mouseTranslate * overScale));
373
374 // draw highlight if necessary
375 {
376 PDFWidget *parent = qobject_cast<PDFWidget *>(parentWidget());
377 if (parent != nullptr) {
378 if(page == parent->highlightPage){
379 if (!parent->highlightPath.isEmpty()) {
380 painter.save();
381 painter.setRenderHint(QPainter::Antialiasing);
382 painter.translate(-kMagFactor * pos()- mouseTranslate -imageLoc );
383 painter.scale(imageDpi/72.0, imageDpi/72.0);
384 painter.setPen(QColor(0, 0, 0, 0));
385 painter.setBrush(UtilsUi::colorFromRGBAstr(globalConfig->highlightColor, QColor(255, 255, 0, 63)));
386 //QPainterPath path=highlightPath;
387 //path.translate(drawTo.left()*72.0/dpi/scaleFactor, drawTo.top()*72.0/dpi/scaleFactor);
388 painter.setCompositionMode(QPainter::CompositionMode_Multiply);
389 painter.drawPath(parent->highlightPath);
390 painter.restore();
391 }
392 }
393 }
394 }
395
396 if (globalConfig->magnifierBorder) {
397 painter.setPen(QPalette().mid().color());
398 switch (globalConfig->magnifierShape) {
399 case PDFDocumentConfig::CircleWithShadow: { //circular
400 //int side = qMin(width(), height()) ;
401 //painter.drawEllipse(width() / 2 - side / 2 + 1, height() / 2 - side / 2 + 1, side - 2, side - 2);
402 painter.drawEllipse(outline);
403 break;
404 }
405 case PDFDocumentConfig::Circle: { //circular without shadow
406 int side = qMin(width(), height()) ;
407 painter.drawEllipse(width() / 2 - side / 2 + 1, height() / 2 - side / 2 + 1, side - 2, side - 2);
408 break;
409 }
410 default:
411 painter.drawRect(0, 0, width() - 1, height() - 1); //rectangular
412 }
413 }
414 }
415
416 /* lazy evaluation of image convertion */
417 QPixmap &PDFMagnifier::getConvertedImage()
418 {
419 if (!globalConfig->invertColors && !globalConfig->grayscale) { // no processing needed
420 return image;
421 }
422 if (globalConfig->invertColors == convertedImageIsColorInverted && globalConfig->grayscale == convertedImageIsGrayscale) { // conversion already done
423 return convertedImage;
424 }
425 convertedImage = convertImage(image, globalConfig->invertColors, globalConfig->grayscale);
426 return convertedImage;
427 }
428
429 #ifdef PHONON
430 PDFMovie::PDFMovie(PDFWidget *parent, QSharedPointer<Poppler::MovieAnnotation> annot, int page): VideoPlayer(parent), page(page)
431 {
432 REQUIRE(parent && annot && parent->getPDFDocument());
433 REQUIRE(annot->subType() == Poppler::Annotation::AMovie);
434 REQUIRE(annot->movie());
435 boundary = annot->boundary();
436 QString url = annot->movie()->url();
437 url = QFileInfo(parent->getPDFDocument()->fileName()).dir().absoluteFilePath(url);
438 if (!QFileInfo(url).exists()) {
439 QMessageBox::warning(this, "", tr("File %1 does not exists").arg(url));
440 return;
441 }
442 this->load(QUrl::fromLocalFile(url));
443
444 popup = new QMenu(this);
445 popup->addAction(tr("&Play"), this, SLOT(realPlay()));
446 popup->addAction(tr("P&ause"), this, SLOT(pause()));
447 popup->addAction(tr("&Stop"), this, SLOT(stop()));
448 popup->addSeparator();
449 popup->addAction(tr("S&eek"), this, SLOT(seekDialog()));
450 popup->addAction(tr("Set &volume"), this, SLOT(setVolumeDialog()));
451
452 setCursor(Qt::PointingHandCursor);
453 }
454
455 void PDFMovie::place()
456 {
457 PDFWidget *pdf = qobject_cast<PDFWidget *>(parent());
458 REQUIRE(pdf);
459 QPointF tl = pdf->mapFromScaledPosition(page, boundary.topLeft());
460 QPointF br = pdf->mapFromScaledPosition(page, boundary.bottomRight());
461 setFixedSize(br.x() - tl.x(), br.y() - tl.y());
462 move(tl.toPoint());
463 }
464
465 void PDFMovie::contextMenuEvent(QContextMenuEvent *e)
466 {
467 popup->popup(e->globalPos());
468 e->accept();
469 }
470
471 void PDFMovie::mouseReleaseEvent(QMouseEvent *e)
472 {
473 //qDebug() << "click: "<<isPaused() << " == !" << isPlaying() << " " << currentTime() << " / " << totalTime();
474 if (isPlaying()) pause();
475 else realPlay();
476 e->accept();
477 }
478
479 void PDFMovie::realPlay()
480 {
481 if (isPlaying()) return;
482 if (isPaused() && currentTime() < totalTime()) play();
483 else {
484 seek(0);
485 QTimer::singleShot(500, this, SLOT(play()));
486 }
487 }
488
489 void PDFMovie::setVolumeDialog()
490 {
491 float vol = volume();
492 UniversalInputDialog uid;
493 uid.addVariable(&vol, tr("Volume:"));
494 if (!uid.exec()) return;
495 setVolume(vol);
496 }
497
498 void PDFMovie::seekDialog()
499 {
500 float pos = currentTime() * 0.001;
501 UniversalInputDialog uid;
502 uid.addVariable(&pos, tr("Time:"));
503 if (!uid.exec()) return;
504 seek(pos * 1000LL);
505 }
506 #endif
507
508 //#pragma mark === PDFWidget ===
509
510 QCursor *PDFWidget::magnifierCursor = nullptr;
511 QCursor *PDFWidget::zoomInCursor = nullptr;
512 QCursor *PDFWidget::zoomOutCursor = nullptr;
513
514 PDFWidget::PDFWidget(bool embedded)
515 : QLabel()
516 , realPageIndex(0), oldRealPageIndex(0)
517 , scaleFactor(1.0)
518 , dpi(72.0)
519 , scaleOption(kFixedMag)
520 , inhibitNextContextMenuEvent(false)
521 , summedWheelDegrees(0)
522 , docPages(0)
523 , saveScaleFactor(1.0)
524 , saveScaleOption(kFitWidth)
525 , ctxZoomInAction(nullptr)
526 , ctxZoomOutAction(nullptr)
527 , shortcutUp(nullptr)
528 , shortcutLeft(nullptr)
529 , shortcutDown(nullptr)
530 , shortcutRight(nullptr)
531
532 , imageDpi(0)
533 , imagePage(-1)
534 , magnifier(nullptr)
535 , currentTool(kNone)
536 , usingTool(kNone)
537 , singlePageStep(true)
538 , gridx(1), gridy(1)
539 , forceUpdate(false)
540 , highlightPage(-1)
541 , pdfdocument(nullptr)
542 {
543 Q_ASSERT(globalConfig);
544 if (!globalConfig) return;
545
546 #ifdef PHONON
547 movie = 0;
548 #endif
549 maxPageSize.setHeight(-1.0);
550 maxPageSize.setWidth(-1.0);
551 horizontalTextRange.setWidth(-1.0);
552
553 dpi = globalConfig->dpi;
554 if (dpi <= 0) dpi = 72; //it crashes if dpi=0
555
556 setBackgroundRole(QPalette::Base);
557 setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
558 setFocusPolicy(embedded ? Qt::NoFocus : Qt::StrongFocus);
559 setScaledContents(true);
560 // Mouse tracking must be always enabled because we use the mouseMoveEvent to update the
561 // widget state (e.g. change the cursor image when hovering over links)
562 setMouseTracking(true);
563 grabGesture(Qt::PinchGesture);
564 grabGesture(Qt::TapGesture);
565
566 switch (globalConfig->scaleOption) {
567 default:
568 fixedScale(1.0);
569 break;
570 case 1:
571 fitWidth(true);
572 break;
573 case 2:
574 fitWindow(true);
575 break;
576 case 3:
577 fixedScale(globalConfig->scale / 100.0);
578 break;
579 case 4:
580 fitTextWidth(true);
581 break;
582 }
583
584 if (magnifierCursor == nullptr) {
585 magnifierCursor = new QCursor(QPixmap(getRealIconFile("magnifier")).scaled(32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation));
586 zoomInCursor = new QCursor(QPixmap(getRealIconFile("zoom-in")).scaled(32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation));
587 zoomOutCursor = new QCursor(QPixmap(getRealIconFile("zoom-out")).scaled(32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation));
588 }
589
590 ctxZoomInAction = new QAction(tr("Zoom In"), this);
591 addAction(ctxZoomInAction);
592 ctxZoomOutAction = new QAction(tr("Zoom Out"), this);
593 addAction(ctxZoomOutAction);
594
595 QAction *action = new QAction(tr("Actual Size"), this);
596 connect(action, SIGNAL(triggered()), this, SLOT(fixedScale()));
597 addAction(action);
598 action = new QAction(tr("Fit to Width"), this);
599 connect(action, SIGNAL(triggered()), this, SLOT(fitWidth()));
600 addAction(action);
601 action = new QAction(tr("Fit to Window"), this);
602 connect(action, SIGNAL(triggered()), this, SLOT(fitWindow()));
603 addAction(action);
604
605 Qt::ShortcutContext context = embedded ? Qt::WidgetWithChildrenShortcut : Qt::WindowShortcut;
606 shortcutUp = new QShortcut(QKeySequence("Up"), this, SLOT(upOrPrev()), nullptr, context);
607 shortcutLeft = new QShortcut(QKeySequence("Left"), this, SLOT(leftOrPrev()), nullptr, context);
608 shortcutDown = new QShortcut(QKeySequence("Down"), this, SLOT(downOrNext()), nullptr, context);
609 shortcutRight = new QShortcut(QKeySequence("Right"), this, SLOT(rightOrNext()), nullptr, context);
610
611 highlightRemover.setSingleShot(true);
612 highlightPage = -1;
613 connect(&highlightRemover, SIGNAL(timeout()), this, SLOT(clearHighlight()));
614 docPages = 0;
615 pageHistoryIndex = -1;
616 }
617
618 PDFWidget::~PDFWidget()
619 {
620 }
621
622 /*
623 * The difference between this and the usual update() is that the usual update() will
624 * first render a blank page or scaled up/down version of the current page and then
625 * redraw later. This is because of how the renderer works: it returns an empty page
626 * if the request page is not in cache, and then call setImage once the page is actually
627 * rendered.
628 *
629 * delayedUpdate(), on the other hand, does not request a repaint by itelf. It just tells
630 * the renderer to render the page. The renderer will then call setImage to render whenever
631 * it is ready.
632 */
633 void PDFWidget::delayedUpdate() {
634
635 qreal overScale = devicePixelRatio();
636
637 qreal newDpi = dpi * scaleFactor;
638 QRect newRect = rect();
639 PDFDocument *doc = getPDFDocument();
640 if (!doc || !doc->renderManager)
641 return;
642
643 // No need to actually call update later since it'll be called by renderManager.
644 if (pages.size() > 0 && (realPageIndex != imagePage || qAbs(newDpi/imageDpi-1.0)>0.001 || newRect != imageRect || forceUpdate)) {
645 if (gridx <= 1 && gridy <= 1)
646 doc->renderManager->renderToImage(pages.first(), this, "setImage",
647 dpi * scaleFactor * overScale, dpi * scaleFactor * overScale, 0, 0,
648 newRect.width() * overScale, newRect.height() * overScale,
649 true, true, 1000);
650 else {
651 QRect visRect = visibleRegion().boundingRect();
652
653 foreach (int pageNr, pages) {
654 QRect drawGrid = pageRect(pageNr);
655 if (!drawGrid.intersects(visRect)) continue;
656
657 doc->renderManager->renderToImage(pageNr, this, "setImage",
658 dpi * scaleFactor * overScale, dpi * scaleFactor * overScale, 0, 0,
659 drawGrid.width() * overScale, drawGrid.height() * overScale,
660 true, true, 1000);
661 }
662 }
663 }
664 }
665
666 void PDFWidget::setPDFDocument(PDFDocument *docu)
667 {
668 pdfdocument = docu;
669 }
670
671 void PDFWidget::setDocument(const QSharedPointer<Poppler::Document> &doc)
672 {
673 pages.clear();
674 document = doc;
675 maxPageSize.setHeight(-1.0);
676 maxPageSize.setWidth(-1.0);
677 horizontalTextRange.setWidth(-1.0);
678
679 if (!document.isNull()) {
680 docPages = document->numPages();
681 setSinglePageStep(globalConfig->singlepagestep);
682 } else
683 docPages = 0;
684 #ifdef PHONON
685 if (movie) {
686 delete movie;
687 movie = 0;
688 }
689 #endif
690 reloadPage();
691 windowResized();
692 }
693
694 void PDFWidget::windowResized()
695 {
696 switch (scaleOption) {
697 case kFixedMag:
698 break;
699 case kFitWidth:
700 fitWidth(true);
701 break;
702 case kFitTextWidth:
703 fitTextWidth(true);
704 break;
705 case kFitWindow:
706 fitWindow(true);
707 break;
708 }
709 delayedUpdate();
710 }
711
712 void fillRectBorder(QPainter &painter, const QRect &inner, const QRect &outer)
713 {
714 painter.drawRect(outer.x(), outer.y(), inner.x() - outer.x(), outer.height());
715 painter.drawRect(inner.right(), outer.y(), outer.right() - inner.right(), outer.height());
716
717 painter.drawRect(inner.x(), outer.y(), inner.width(), inner.y() - outer.y());
718 painter.drawRect(inner.x(), inner.bottom(), inner.width(), outer.bottom() - inner.bottom());
719 }
720
721 void PDFWidget::paintEvent(QPaintEvent *event)
722 {
723 QPainter painter(this);
724 drawFrame(&painter);
725
726 qreal newDpi = dpi * scaleFactor;
727
728 qreal overScale = painter.device()->devicePixelRatio();
729
730 QRect newRect = rect();
731 PDFDocument *doc = getPDFDocument();
732 if (!doc || !doc->renderManager)
733 return;
734 if (pages.size() > 0 && (realPageIndex != imagePage || qAbs(newDpi/imageDpi-1.0)>0.001 || newRect != imageRect || forceUpdate)) {
735 painter.setBrush(QApplication::palette().color(QPalette::Dark));
736 painter.setPen(QApplication::palette().color(QPalette::Dark));
737 if (gridx <= 1 && gridy <= 1) {
738 int pageNr = pages.first();
739 QRect drawTo = pageRect(pageNr);
740 image = doc->renderManager->renderToImage(pageNr, this, "setImage", dpi * scaleFactor * overScale, dpi * scaleFactor * overScale,
741 0, 0, newRect.width() * overScale, newRect.height() * overScale, true, true);
742 if (globalConfig->invertColors || globalConfig->grayscale)
743 image = convertImage(image, globalConfig->invertColors, globalConfig->grayscale);
744 fillRectBorder(painter, drawTo, newRect);
745 QRect source = event->rect().translated(-drawTo.topLeft());
746 painter.drawPixmap(event->rect(), image, QRect(source.left() * overScale, source.top() * overScale, source.width() * overScale, source.height() * overScale));
747 if (pageNr == highlightPage && !highlightPath.isEmpty() ) {
748 painter.setRenderHint(QPainter::Antialiasing);
749 painter.setCompositionMode(QPainter::CompositionMode_Multiply);
750 painter.scale(totalScaleFactor(), totalScaleFactor());
751 painter.setPen(QColor(0, 0, 0, 0));
752 painter.setBrush(UtilsUi::colorFromRGBAstr(globalConfig->highlightColor, QColor(255, 255, 0, 63)));
753 painter.drawPath(highlightPath);
754 }
755 if (currentTool == kPresentation)
756 doc->renderManager->renderToImage(pageNr + 1, this, "", dpi * scaleFactor * overScale, dpi * scaleFactor * overScale, 0, 0, newRect.width() * overScale, newRect.height()*overScale, true, true);
757 } else {
758 QRect visRect = visibleRegion().boundingRect();
759 //image = QPixmap(newRect.width(), newRect.height());
760 //image.fill(QApplication::palette().color(QPalette::Dark).rgb());
761
762 //QPainter p;
763 //p.begin(&image);
764 // paint border betweend pages
765 QSizeF realPageSize = maxPageSizeFDpiAdjusted() * scaleFactor;
766
767 int realPageSizeX = qRound(realPageSize.width());
768 int realPageSizeY = qRound(realPageSize.height());
769
770 //painter.save();
771 for (int i = 1; i < gridx; i++) {
772 QRect rec((realPageSizeX + GridBorder)*i, 0, -GridBorder, newRect.height());
773 if (rec.intersects(visRect))
774 painter.drawRect(rec);
775 }
776 for (int i = 1; i < gridy; i++) {
777 QRect rec(0, (realPageSizeY + GridBorder)*i, newRect.width(), -GridBorder);
778 if (rec.intersects(visRect))
779 painter.drawRect(rec);
780 }
781
782 int curGrid = 0;
783 if (getPageOffset() && realPageIndex == 0) {
784 painter.drawRect(gridPageRect(0));
785 curGrid++;
786 }
787 foreach (int pageNr, pages) {
788 QRect basicGrid = gridPageRect(curGrid++);
789 QRect drawGrid = pageRect(pageNr);
790 if (!drawGrid.intersects(visRect)) { // don't draw invisible pages
791 painter.drawRect(basicGrid);
792 continue;
793 }
794 QPixmap temp = doc->renderManager->renderToImage(
795 pageNr, this, "setImage",
796 dpi * scaleFactor * overScale,
797 dpi * scaleFactor * overScale,
798 0, 0, drawGrid.width() * overScale, drawGrid.height() * overScale, true, true);
799 if (globalConfig->invertColors || globalConfig->grayscale)
800 temp = convertImage(temp, globalConfig->invertColors, globalConfig->grayscale);
801 if (drawGrid != basicGrid)
802 fillRectBorder(painter, drawGrid, basicGrid);
803 painter.drawPixmap(QRect(drawGrid.left(), drawGrid.top(), temp.width() / overScale, temp.height() / overScale), temp);
804 if (pageNr == highlightPage) {
805 if (!highlightPath.isEmpty()) {
806 painter.save();
807 painter.setRenderHint(QPainter::Antialiasing);
808 painter.translate(drawGrid.left(), drawGrid.top());
809 painter.scale(totalScaleFactor(), totalScaleFactor());
810 painter.setPen(QColor(0, 0, 0, 0));
811 painter.setBrush(UtilsUi::colorFromRGBAstr(globalConfig->highlightColor, QColor(255, 255, 0, 63)));
812 //QPainterPath path=highlightPath;
813 //path.translate(drawTo.left()*72.0/dpi/scaleFactor, drawTo.top()*72.0/dpi/scaleFactor);
814 painter.setCompositionMode(QPainter::CompositionMode_Multiply);
815 painter.drawPath(highlightPath);
816 painter.restore();
817 }
818 }
819 }
820 for (; curGrid < gridx * gridy; curGrid++)
821 painter.drawRect(gridPageRect(curGrid));
822 //p.end();
823 //painter.restore();
824 }
825 }
826
827 imagePage = pages.isEmpty() ? -1 : realPageIndex;
828 imageDpi = newDpi;
829 imageRect = newRect;
830 }
831
832 void PDFWidget::setImage(QPixmap, int pageNr)
833 {
834 forceUpdate = true;
835 update(pageRect(pageNr));
836 }
837
838 void PDFWidget::useMagnifier(const QMouseEvent *inEvent)
839 {
840 Q_ASSERT(globalConfig);
841 if (!globalConfig) return;
842 int page = pageFromPos(inEvent->pos());
843 if (page < 0) return;
844 if (!magnifier) magnifier = new PDFMagnifier(this, dpi);
845 magnifier->setFixedSize(globalConfig->magnifierSize * 4 / 3, globalConfig->magnifierSize);
846 magnifier->setPage(page, scaleFactor, pageRect(page));
847 magnifier->reshape();
848 // this was in the hope that if the mouse is released before the image is ready,
849 // the magnifier wouldn't actually get shown. but it doesn't seem to work that way -
850 // the MouseMove event that we're posting must end up ahead of the mouseUp
851 QMouseEvent *event = new QMouseEvent(QEvent::MouseMove, inEvent->pos(), inEvent->globalPos(), inEvent->button(), inEvent->buttons(), inEvent->modifiers());
852 QCoreApplication::postEvent(this, event);
853 usingTool = kMagnifier;
854 }
855
856 // Mouse control for the various tools:
857 // * magnifier
858 // - ctrl-click to sync
859 // - click to use magnifier
860 // - shift-click to zoom in
861 // - shift-click and drag to zoom to selected area
862 // - alt-click to zoom out
863 // * scroll (hand)
864 // - ctrl-click to sync
865 // - click and drag to scroll
866 // - double-click to use magnifier
867 // * select area (crosshair)
868 // - ctrl-click to sync
869 // - click and drag to select area
870 // - double-click to use magnifier
871 // * select text (I-beam)
872 // - ctrl-click to sync
873 // - click and drag to select text
874 // - double-click to use magnifier
875
876 static QPoint scrollClickPos;
877 static Qt::KeyboardModifiers mouseDownModifiers;
878
879 void PDFWidget::mousePressEvent(QMouseEvent *event)
880 {
881 clickedLink.clear();
882 clickedAnnotation.clear();
883
884 switch (event->button()) {
885 case Qt::LeftButton:
886 break; // all cases handled below
887 case Qt::XButton1:
888 goBack();
889 return;
890 case Qt::XButton2:
891 goForward();
892 return;
893 default:
894 return;
895 }
896
897 if (event->button() == Qt::XButton1) {
898 goBack();
899 return;
900 }
901
902 if (event->button() != Qt::LeftButton) {
903 QWidget::mousePressEvent(event);
904 return;
905 }
906
907 mouseDownModifiers = event->modifiers();
908 if (mouseDownModifiers & Qt::ControlModifier) {
909 // ctrl key - this is a sync click, don't handle the mouseDown here
910 } else if (currentTool != kPresentation) {
911 QPointF scaledPos;
912 int pageNr;
913 mapToScaledPosition(event->pos(), pageNr, scaledPos);
914
915 if (pageNr >= 0 && pageNr < realNumPages()) {
916 std::unique_ptr<Poppler::Page> page(document->page(pageNr));
917 if (page) {
918 // check for click in link
919 for(auto &link: page->links()) {
920 if (link->linkArea().contains(scaledPos)) {
921 #if POPPLER_VERSION_MAJOR>=21 && POPPLER_VERSION_MINOR>=6 && QT_VERSION_MAJOR>5
922 clickedLink = QSharedPointer<Poppler::Link>(link.release());
923 #else
924 clickedLink = QSharedPointer<Poppler::Link>(link);
925 #endif
926 continue; // no break because we have to delete all other links
927 }
928 //delete link;
929 }
930 if (!clickedLink) {
931 for (auto &annon: page->annotations()) {
932 if (annon->boundary().contains(scaledPos)) {
933 #if POPPLER_VERSION_MAJOR>=21 && POPPLER_VERSION_MINOR>=6 && QT_VERSION_MAJOR>5
934 clickedAnnotation = QSharedPointer<Poppler::Annotation>(annon.release());
935 #else
936 clickedAnnotation = QSharedPointer<Poppler::Annotation>(annon);
937 #endif
938 continue; // no break because we have to delete all other links
939 }
940 //delete annon;
941 }
942 }
943 if (!clickedLink && !clickedAnnotation) {
944 switch (currentTool) {
945 case kMagnifier:
946 if (mouseDownModifiers & (Qt::ShiftModifier | Qt::AltModifier))
947 ; // do nothing - zoom in or out (on mouseUp)
948 else
949 useMagnifier(event);
950 break;
951
952 case kScroll:
953 setCursor(Qt::ClosedHandCursor);
954 scrollClickPos = event->globalPos();
955 usingTool = kScroll;
956 break;
957 }
958 }
959 }
960 }
961 } else {
962 QPointF scaledPos;
963 int pageNr;
964 mapToScaledPosition(event->pos(), pageNr, scaledPos);
965 if (pageNr >= 0 && pageNr < realNumPages()) {
966 std::unique_ptr<Poppler::Page> page(document->page(pageNr));
967 if (page) {
968 for (auto &annon: page->annotations()) {
969 if (annon->boundary().contains(scaledPos)) {
970 #if POPPLER_VERSION_MAJOR>=21 && POPPLER_VERSION_MINOR>=6 && QT_VERSION_MAJOR>5
971 clickedAnnotation = QSharedPointer<Poppler::Annotation>(annon.release());
972 #else
973 clickedAnnotation = QSharedPointer<Poppler::Annotation>(annon);
974 #endif
975 continue; // no break because we have to delete all other links
976 }
977 }
978 }
979 }
980 }
981 event->accept();
982 }
983
984 void PDFWidget::annotationClicked(QSharedPointer<Poppler::Annotation> annotation, int page)
985 {
986 switch (annotation->subType()) {
987 case Poppler::Annotation::AMovie: {
988 #ifdef PHONON
989 if (movie) delete movie;
990 movie = new PDFMovie(this, qSharedPointerDynamicCast<Poppler::MovieAnnotation>(annotation), page);
991 movie->place();
992 movie->show();
993 movie->play();
994 #else
995 Q_UNUSED(page)
996 UtilsUi::txsWarning("You clicked on a video, but the video playing mode was disabled by you or the package creator.\nRecompile TeXstudio with the option PHONON=true");
997 #endif
998 break;
999 }
1000
1001 case Poppler::Annotation::AText:
1002 case Poppler::Annotation::ACaret:
1003 case Poppler::Annotation::AHighlight: {
1004 PDFAnnotationDlg *dlg = new PDFAnnotationDlg(annotation, this);
1005 dlg->show();
1006 break;
1007 }
1008 default:
1009 ;
1010 }
1011 }
1012
1013 void PDFWidget::openAnnotationDialog(const PDFAnnotation *annon)
1014 {
1015 PDFAnnotationDlg *dlg = new PDFAnnotationDlg(annon->popplerAnnotation(), this);
1016 //qDebug() << annon->popplerAnnotation()->revisionType() << annon->popplerAnnotation()->revisions().count();
1017 dlg->show();
1018 }
1019
1020 void PDFWidget::mouseReleaseEvent(QMouseEvent *event)
1021 {
1022 if (pdfdocument && pdfdocument->embeddedMode)
1023 setFocus();
1024 if (pageHistoryIndex != pageHistory.size() - 1) {
1025 pageHistory.append(PDFPageHistoryItem(realPageIndex, 0, 0));
1026 pageHistoryIndex = pageHistory.size() - 1;
1027 }
1028 updateCurrentPageHistoryOffset();
1029 if (clickedLink) {
1030 int page;
1031 QPointF scaledPos;
1032 mapToScaledPosition(event->pos(), page, scaledPos);
1033 if (page > -1 && clickedLink->linkArea().contains(scaledPos)) {
1034 pageHistoryIndex = pageHistory.size();
1035 doLink(clickedLink);
1036 updateCurrentPageHistoryOffset();
1037 }
1038 } else if (clickedAnnotation) {
1039 int page;
1040 QPointF scaledPos;
1041 mapToScaledPosition(event->pos(), page, scaledPos);
1042 if (page > -1 && clickedAnnotation->boundary().contains(scaledPos)) {
1043 annotationClicked(clickedAnnotation, page);
1044 }
1045 } else if (currentTool == kPresentation) {
1046 if(usingTool== kMagnifier){
1047 usingTool = kNone;
1048 magnifier->close();
1049 }else{
1050 if (event->button() == Qt::LeftButton) goNext();
1051 else if (event->button() == Qt::RightButton) goPrev();
1052 }
1053 } else {
1054 switch (usingTool) {
1055 case kNone:
1056 // Ctrl-click to sync
1057 if (mouseDownModifiers & Qt::ControlModifier) {
1058 if (event->modifiers() & Qt::ControlModifier)
1059 syncWindowClick(event->pos(), true);
1060
1061 break;
1062 }
1063 // check whether to zoom
1064 if (currentTool == kMagnifier) {
1065 Qt::KeyboardModifiers mods = QApplication::keyboardModifiers();
1066 if (mods & Qt::AltModifier)
1067 doZoom(event->pos(), -1);
1068 else if (mods & Qt::ShiftModifier)
1069 doZoom(event->pos(), 1);
1070 }
1071 break;
1072 case kMagnifier:
1073 // Ensure we stop using the tool before hiding the magnifier.
1074 // Otherwise other events in the queue may be processed between
1075 // "close()" and "usingTool=" that could show the magnifier
1076 // again
1077 usingTool = kNone;
1078 magnifier->close();
1079 break;
1080 }
1081 }
1082 clickedLink.clear();
1083 clickedAnnotation.clear();
1084 usingTool = kNone;
1085 updateCursor(event->pos());
1086 event->accept();
1087 }
1088
1089 void PDFWidget::goToDestination(const Poppler::LinkDestination &dest)
1090 {
1091 if (dest.pageNumber() > 0)
1092 goToPageRelativePosition(dest.pageNumber() - 1, dest.isChangeLeft() ? dest.left() : qQNaN(), dest.isChangeTop() ? dest.top() : qQNaN());
1093
1094 /*if (dest.isChangeZoom()) {
1095 // FIXME
1096 }*/
1097 }
1098
1099 void PDFWidget::goToDestination(const QString &destName)
1100 {
1101 if (document.isNull()) return;
1102 #if POPPLER_VERSION_MAJOR>0 || POPPLER_VERSION_MINOR>=74
1103 const Poppler::LinkDestination dest=Poppler::LinkDestination(destName);
1104 goToDestination(dest);
1105 #else
1106 const Poppler::LinkDestination *dest = document->linkDestination(destName);
1107 if (dest){
1108 goToDestination(*dest);
1109 }
1110 #endif
1111
1112
1113 }
1114
1115 void PDFWidget::goToPageRelativePosition(int page, double xinpdf, double yinpdf)
1116 {
1117 PDFScrollArea *scrollArea = getScrollArea();
1118 if (!scrollArea) return;
1119
1120 scrollArea->goToPage(page);
1121
1122 if (qIsNaN(xinpdf)) xinpdf = 0;
1123 xinpdf = qBound<double>(0, xinpdf, 1);
1124 if (qIsNaN(yinpdf)) yinpdf = 0;
1125 yinpdf = qBound<double>(0, yinpdf, 1);
1126
1127 QPoint p = mapFromScaledPosition(page, QPointF( xinpdf, yinpdf));
1128
1129 if (!qIsNaN(xinpdf) && getScaleOption()!=kFitTextWidth)
1130 scrollArea->horizontalScrollBar()->setValue(p.x());
1131
1132 if (!qIsNaN(yinpdf)) {
1133 int val = 0;
1134 if (scrollArea->getContinuous())
1135 val = scrollArea->verticalScrollBar()->value();
1136 scrollArea->verticalScrollBar()->setValue(p.y() + val);
1137 }
1138 }
1139
1140 void PDFWidget::doLink(const QSharedPointer<Poppler::Link> link)
1141 {
1142 switch (link->linkType()) {
1143 case Poppler::Link::None:
1144 break;
1145 case Poppler::Link::Goto: {
1146 const Poppler::LinkGoto *go = dynamic_cast<const Poppler::LinkGoto *>(link.data());
1147 Q_ASSERT(go != nullptr);
1148 if (go->isExternal()) {
1149 QString filename = go->fileName();
1150 if (filename.endsWith(".pdf")) {
1151 QFileInfo fi(filename);
1152 if (fi.isRelative()) {
1153 filename = QDir(QFileInfo(pdfdocument->fileName()).absolutePath()).filePath(filename);
1154 }
1155 pdfdocument->loadFile(filename);
1156 } else {
1157 UtilsUi::txsInformation(tr("Opening external files is currently only supported for PDFs."));
1158 }
1159 } else {
1160 goToDestination(go->destination());
1161 }
1162 }
1163 break;
1164 case Poppler::Link::Browse: {
1165 const Poppler::LinkBrowse *browse = dynamic_cast<const Poppler::LinkBrowse *>(link.data());
1166 Q_ASSERT(browse != nullptr);
1167 QUrl url = QUrl::fromEncoded(browse->url().toLatin1());
1168 if (url.scheme() == "file" || url.scheme().isEmpty() /*i.e. is relative */) {
1169 PDFDocument *doc = getPDFDocument();
1170 if (doc) {
1171 QFileInfo fi(QFileInfo(doc->fileName()).canonicalPath(), url.toLocalFile());
1172 url = QUrl::fromLocalFile(fi.absoluteFilePath());
1173 }
1174 }
1175 if (!QDesktopServices::openUrl(url))
1176 QMessageBox::warning(this, tr("Error"), tr("Could not open link:") + "\n" + url.toString());
1177 }
1178 break;
1179 // unsupported link types:
1180 // case Poppler::Link::Execute:
1181 // break;
1182 // case Poppler::Link::JavaScript:
1183 // break;
1184 // case Poppler::Link::Action:
1185 // break;
1186 // case Poppler::Link::Sound:
1187 // break;
1188 // case Poppler::Link::Movie:
1189 // break;
1190 default:
1191 break;
1192 }
1193 }
1194
1195 void PDFWidget::mouseDoubleClickEvent(QMouseEvent *event)
1196 {
1197 if ((event->button() != Qt::LeftButton) || (currentTool == kPresentation)) {
1198 QWidget::mouseDoubleClickEvent(event);
1199 return;
1200 }
1201 if (!(mouseDownModifiers & Qt::ControlModifier))
1202 useMagnifier(event);
1203 event->accept();
1204 }
1205
1206 void PDFWidget::mouseMoveEvent(QMouseEvent *event)
1207 {
1208 updateCursor(event->pos());
1209 switch (usingTool) {
1210 case kMagnifier: {
1211 QRect viewportClip(mapFromParent(parentWidget()->rect().topLeft()),
1212 mapFromParent(parentWidget()->rect().bottomRight() - QPoint(1, 1)));
1213 QPoint constrainedLoc = event->pos();
1214 if (constrainedLoc.x() < viewportClip.left())
1215 constrainedLoc.setX(viewportClip.left());
1216 else if (constrainedLoc.x() > viewportClip.right())
1217 constrainedLoc.setX(viewportClip.right());
1218 if (constrainedLoc.y() < viewportClip.top())
1219 constrainedLoc.setY(viewportClip.top());
1220 else if (constrainedLoc.y() > viewportClip.bottom())
1221 constrainedLoc.setY(viewportClip.bottom());
1222 magnifier->move(constrainedLoc.x() - magnifier->width() / 2, constrainedLoc.y() - magnifier->height() / 2);
1223 if (magnifier->isHidden()) {
1224 magnifier->show();
1225 setCursor(Qt::BlankCursor);
1226 }
1227 }
1228 event->accept();
1229 break;
1230 case kScroll: {
1231 QPoint delta = event->globalPos() - scrollClickPos;
1232 scrollClickPos = event->globalPos();
1233 QAbstractScrollArea *scrollArea = getScrollArea();
1234 if (scrollArea) {
1235 if (scaleOption != kFitTextWidth || !globalConfig->disableHorizontalScrollingForFitToTextWidth) {
1236 int oldX = scrollArea->horizontalScrollBar()->value();
1237 scrollArea->horizontalScrollBar()->setValue(oldX - delta.x());
1238 }
1239 int oldY = scrollArea->verticalScrollBar()->value();
1240 scrollArea->verticalScrollBar()->setValue(oldY - delta.y());
1241 }
1242 }
1243 event->accept();
1244 break;
1245 default:
1246 event->ignore();
1247 }
1248 }
1249
1250 void PDFWidget::keyPressEvent(QKeyEvent *event)
1251 {
1252 updateCursor(mapFromGlobal(QCursor::pos()));
1253 if (event->key() == Qt::Key_Home)
1254 goFirst();
1255 if (event->key() == Qt::Key_End)
1256 goLast();
1257 if (event->key() == Qt::Key_Space || event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return ) {
1258 if (event->modifiers() & Qt::SHIFT) pageUpOrPrev();
1259 else pageDownOrNext();
1260 }
1261 event->ignore();
1262 }
1263
1264 void PDFWidget::keyReleaseEvent(QKeyEvent *event)
1265 {
1266 updateCursor(mapFromGlobal(QCursor::pos()));
1267 event->ignore();
1268 }
1269
1270 void PDFWidget::focusInEvent(QFocusEvent *event)
1271 {
1272 updateCursor(mapFromGlobal(QCursor::pos()));
1273 event->ignore();
1274 }
1275
1276 void PDFWidget::contextMenuEvent(QContextMenuEvent *event)
1277 {
1278 if (inhibitNextContextMenuEvent) {
1279 inhibitNextContextMenuEvent = false;
1280 return;
1281 }
1282
1283 QMenu menu(this);
1284
1285 PDFDocument *pdfDoc = getPDFDocument();
1286 if (pdfDoc && pdfDoc->hasSyncData()) {
1287 QAction *act = new QAction(tr("Go to Source"), &menu);
1288 act->setData(QVariant(event->pos()));
1289 connect(act, SIGNAL(triggered()), this, SLOT(jumpToSource()));
1290 menu.addAction(act);
1291 menu.addSeparator();
1292 }
1293
1294 menu.addActions(actions());
1295
1296 ctxZoomInAction->setEnabled(scaleFactor < kMaxScaleFactor);
1297 ctxZoomOutAction->setEnabled(scaleFactor > kMinScaleFactor);
1298
1299 if (usingTool == kMagnifier && magnifier) {
1300 magnifier->close();
1301 usingTool = kNone;
1302 }
1303
1304 if (pdfDoc && pdfDoc->menuShow) {
1305 menu.addSeparator();
1306 menu.addMenu(pdfDoc->menuShow);
1307 }
1308
1309 QAction *action = menu.exec(event->globalPos());
1310
1311 if (action == ctxZoomInAction)
1312 doZoom(event->pos(), 1);
1313 else if (action == ctxZoomOutAction)
1314 doZoom(event->pos(), -1);
1315
1316 }
1317
1318 bool PDFWidget::event(QEvent *event)
1319 {
1320 if (event->type() == QEvent::Gesture)
1321 return gestureEvent(static_cast<QGestureEvent *>(event));
1322 return QLabel::event(event);
1323 }
1324
1325 bool PDFWidget::gestureEvent(QGestureEvent *event)
1326 {
1327 if (QGesture *gesture = event->gesture(Qt::PinchGesture))
1328 pinchEvent(static_cast<QPinchGesture *>(gesture));
1329 if (QGesture *gesture = event->gesture(Qt::TapGesture))
1330 tapEvent(static_cast<QTapGesture *>(gesture));
1331 return true;
1332 }
1333
1334 void PDFWidget::pinchEvent(QPinchGesture *gesture)
1335 {
1336 doZoom(mapFromGlobal(gesture->centerPoint().toPoint()), 0, gesture->scaleFactor()*scaleFactor);
1337 }
1338
1339 void PDFWidget::tapEvent(QTapGesture *gesture)
1340 {
1341 if (gesture->state() == Qt::GestureFinished) {
1342 syncWindowClick(gesture->position().toPoint(), true);
1343 }
1344 }
1345
1346 void PDFWidget::jumpToSource()
1347 {
1348 QAction *act = qobject_cast<QAction *>(sender());
1349 if (act != nullptr) {
1350 QPoint eventPos = act->data().toPoint();
1351 syncWindowClick(eventPos, true);
1352 /*
1353 QPointF pagePos(eventPos.x() / scaleFactor * 72.0 / dpi,
1354 eventPos.y() / scaleFactor * 72.0 / dpi);
1355 emit syncClick(pageIndex, pagePos, true);
1356 */
1357 }
1358 }
1359
1360 void PDFWidget::wheelEvent(QWheelEvent *event)
1361 {
1362 if (event->angleDelta().isNull()) return;
1363
1364 if(event->angleDelta().x()!=0){
1365 // horizontal scroll
1366 double numDegrees = event->angleDelta().x() / 8.0;
1367 const int degreesPerStep = 15; // for a typical mouse (some may have finer resolution, but that's k with the co
1368 QScrollBar *scrollBar = getScrollArea()->horizontalScrollBar();
1369 if (scrollBar->minimum() < scrollBar->maximum()) { //if scrollbar visible
1370 scrollBar->setValue(scrollBar->value() - qRound(scrollBar->singleStep() * QApplication::wheelScrollLines() * numDegrees / degreesPerStep));
1371 }
1372 }
1373 if(event->angleDelta().y()!=0){
1374 // vertical scroll
1375 double numDegrees = event->angleDelta().y() / 8.0;
1376 if ((summedWheelDegrees < 0) != (numDegrees < 0)) summedWheelDegrees = 0;
1377 // we may accumulate rotation and handle it in larger chunks
1378 summedWheelDegrees += numDegrees;
1379 const int degreesPerStep = 15; // for a typical mouse (some may have finer resolution, but that's k with the co
1380
1381 if (event->modifiers() == Qt::ControlModifier || event->buttons() == Qt::RightButton) {
1382 if (event->buttons() == Qt::RightButton) {
1383 inhibitNextContextMenuEvent = true;
1384 }
1385 if (qFabs(summedWheelDegrees) >= degreesPerStep ) { //avoid small zoom changes, as they use a lot of memory
1386 #if (QT_VERSION>=QT_VERSION_CHECK(5,15,0))
1387 doZoom(event->position(), (summedWheelDegrees > 0) ? 1 : -1);
1388 #else
1389 doZoom(event->pos(), (summedWheelDegrees > 0) ? 1 : -1);
1390 #endif
1391 summedWheelDegrees = 0;
1392 }
1393 event->accept();
1394 } else {
1395 static QTime lastScrollTime = QTime::currentTime();
1396 QScrollBar *scrollBar = (event->angleDelta().y() == 0) // -> horizontal scroll
1397 ? getScrollArea()->horizontalScrollBar()
1398 : getScrollArea()->verticalScrollBar();
1399 bool mayChangePage = !getScrollArea()->getContinuous();
1400 if (scrollBar->minimum() < scrollBar->maximum()) { //if scrollbar visible
1401 int oldValue = scrollBar->value();
1402 const int scrollPerWheelStep = scrollBar->singleStep() * QApplication::wheelScrollLines();
1403 scrollBar->setValue(scrollBar->value() - qRound(scrollPerWheelStep * summedWheelDegrees / degreesPerStep));
1404 int delta = oldValue - scrollBar->value();
1405 if (delta != 0) {
1406 lastScrollTime = QTime::currentTime();
1407 summedWheelDegrees -= delta * degreesPerStep / scrollPerWheelStep;
1408 mayChangePage = false;
1409 } else
1410 mayChangePage &= (scrollBar->value() == scrollBar->minimum()) || (scrollBar->value() == scrollBar->maximum());
1411 if (QTime::currentTime() < lastScrollTime.addMSecs(500) && qAbs(summedWheelDegrees) < 180)
1412 mayChangePage = false;
1413 }
1414 if (mayChangePage) {
1415 if (event->angleDelta().y() > 0 && realPageIndex > 0) {
1416 goPrev();
1417 scrollBar->triggerAction(QAbstractSlider::SliderToMaximum);
1418 } else if (event->angleDelta().y() < 0 && realPageIndex < realNumPages() - 1) {
1419 goNext();
1420 scrollBar->triggerAction(QAbstractSlider::SliderToMinimum);
1421 }
1422 lastScrollTime = QTime::currentTime();
1423 summedWheelDegrees = 0;
1424 }
1425 event->accept();
1426 }
1427 }
1428 }
1429
1430 void PDFWidget::setTool(int tool)
1431 {
1432 currentTool = tool;
1433 globalConfig->editTool = tool;
1434 PDFScrollArea *scrollArea = getScrollArea();
1435 if (scrollArea) UtilsUi::enableTouchScrolling(scrollArea, tool == kScroll);
1436 updateCursor();
1437 }
1438
1439 void PDFWidget::syncWindowClick(const QPoint &p, bool activate)
1440 {
1441 int page = pageFromPos(p);
1442 if (page < 0) return;
1443 QRect r = pageRect(page);
1444 emit syncClick(page, QPointF(p - r.topLeft()) / totalScaleFactor(), activate);
1445
1446 }
1447
1448 void PDFWidget::syncCurrentPage(bool activate)
1449 {
1450 if (pages.isEmpty()) return;
1451 //single page step mode: jump to center of first page in grid; multi page step: jump to center of grid
1452 int midPage = pageStep() > 1 ? pages[pages.size() / 2] : pages.first();
1453 QSize s = pageRect(midPage).size();
1454 emit syncClick(midPage, QPointF(s.width(), s.height()) / totalScaleFactor(), activate);
1455 }
1456
1457 void PDFWidget::updateCursor()
1458 {
1459 if (usingTool != kNone)
1460 return;
1461
1462 switch (currentTool) {
1463 case kScroll:
1464 setCursor(Qt::OpenHandCursor);
1465 break;
1466 case kMagnifier: {
1467 Qt::KeyboardModifiers mods = QApplication::keyboardModifiers();
1468 if (mods & Qt::AltModifier)
1469 setCursor(*zoomOutCursor);
1470 else if (mods & Qt::ShiftModifier)
1471 setCursor(*zoomInCursor);
1472 else
1473 setCursor(*magnifierCursor);
1474 }
1475 break;
1476 case kSelectText:
1477 setCursor(Qt::IBeamCursor);
1478 break;
1479 case kSelectImage:
1480 setCursor(Qt::CrossCursor);
1481 break;
1482 default:
1483 setCursor(Qt::ArrowCursor);
1484 break;
1485 }
1486 }
1487
1488 QRect PDFWidget::mapPopplerRectToWidget(QRectF r, const QSizeF &pageSize) const
1489 {
1490 r.setWidth(r.width() * scaleFactor * dpi / 72.0 * pageSize.width());
1491 r.setHeight(r.height() * scaleFactor * dpi / 72.0 * pageSize.height());
1492 r.moveLeft(r.left() * scaleFactor * dpi / 72.0 * pageSize.width());
1493 r.moveTop(r.top() * scaleFactor * dpi / 72.0 * pageSize.height());
1494 QRect rr = r.toRect().normalized();
1495 rr.setTopLeft(mapToGlobal(rr.topLeft()));
1496 return rr;
1497 }
1498
1499 #if 0
1500 //only used for testing
1501 void listAnnotationDetails(const Poppler::Annotation *an)
1502 {
1503 qDebug() << "*** Poppler Annotation ***";
1504 qDebug() << "subtype " << an->subType();
1505 qDebug() << "author " << an->author();
1506 qDebug() << "contents " << an->contents();
1507 qDebug() << "uniqueName " << an->uniqueName();
1508 qDebug() << "modifDate " << an->modificationDate();
1509 qDebug() << "creationDate " << an->creationDate();
1510 qDebug() << "flags " << an->flags();
1511 qDebug() << "boundary " << an->boundary();
1512 //qDebug() << "revisions " << an->revisions();
1513 }
1514 #endif
1515
1516 void PDFWidget::updateCursor(const QPoint &pos)
1517 {
1518 if (document.isNull()) return;
1519
1520 QPointF scaledPos;
1521 int pageNr;
1522 mapToScaledPosition(pos, pageNr, scaledPos);
1523 if (pageNr < 0 || pageNr >= realNumPages()) return;
1524 std::unique_ptr<Poppler::Page> page(document->page(pageNr));
1525 if (!page)
1526 return;
1527
1528 // check for link
1529 bool done = false;
1530 for (auto &link: page->links()) {
1531 // poppler's linkArea is relative to the page rect
1532 if (link->linkArea().contains(scaledPos)) {
1533 setCursor(Qt::PointingHandCursor);
1534 QString tooltip;
1535 if (link->linkType() == Poppler::Link::Browse) {
1536 #if POPPLER_VERSION_MAJOR>=21 && POPPLER_VERSION_MINOR>=6 && QT_VERSION_MAJOR>5
1537 const Poppler::LinkBrowse *browse = dynamic_cast<const Poppler::LinkBrowse *>(link.get());
1538 #else
1539 const Poppler::LinkBrowse *browse = dynamic_cast<const Poppler::LinkBrowse *>(link);
1540 #endif
1541 Q_ASSERT(browse != nullptr);
1542 tooltip = browse->url();
1543 } else if (link->linkType() == Poppler::Link::Goto) {
1544 #if POPPLER_VERSION_MAJOR>=21 && POPPLER_VERSION_MINOR>=6 && QT_VERSION_MAJOR>5
1545 const Poppler::LinkGoto *go = dynamic_cast<const Poppler::LinkGoto *>(link.get());
1546 #else
1547 const Poppler::LinkGoto *go = dynamic_cast<const Poppler::LinkGoto *>(link);
1548 #endif
1549 if (go->isExternal()) {
1550 tooltip = go->fileName();
1551 }
1552 }
1553 if (!tooltip.isEmpty()) {
1554 QRect r = mapPopplerRectToWidget(link->linkArea(), page->pageSizeF());
1555 QToolTip::showText(mapToGlobal(pos), tooltip, this, r);
1556 }
1557 done = true;
1558 break;
1559 }
1560 }
1561 if (done) return;
1562
1563 for (auto &annot: page->annotations()) {
1564 if (annot->boundary().contains(scaledPos)) {
1565 switch (annot->subType()) {
1566 case Poppler::Annotation::AMovie:
1567 setCursor(Qt::PointingHandCursor);
1568 break;
1569 case Poppler::Annotation::AText:
1570 case Poppler::Annotation::ACaret:
1571 case Poppler::Annotation::AHighlight: {
1572 setCursor(Qt::PointingHandCursor);
1573 QString text = QString("<b>%1</b><hr>%2").arg(annot->author(), annot->contents());
1574 QRect r = mapPopplerRectToWidget(annot->boundary(), page->pageSizeF());
1575 QToolTip::showText(mapToGlobal(pos), text, this, r);
1576 break;
1577 }
1578 default:
1579 ;
1580 }
1581 done = true;
1582 }
1583 }
1584 if (done) return;
1585
1586 updateCursor();
1587 }
1588
1589 void PDFWidget::adjustSize()
1590 {
1591 if (pages.empty()) return;
1592 QSize pageSize = (maxPageSizeFDpiAdjusted() * scaleFactor).toSize();
1593 pageSize.rwidth() = pageSize.rwidth() * gridx + (gridx - 1) * GridBorder;
1594 pageSize.rheight() = pageSize.rheight() * gridy + (gridy - 1) * GridBorder;
1595 if (pageSize != size()) {
1596 PDFScrollArea *scrollArea = getScrollArea();
1597 if (!scrollArea) return;
1598 qreal jumpTo = -1;
1599
1600 QSize p = scrollArea->viewport()->size();
1601
1602 if (scrollArea->getContinuous() && scrollArea->verticalScrollBar()->maximum() > 0) {
1603 jumpTo = 1.0 * scrollArea->verticalScrollBar()->value() / (scrollArea->verticalScrollBar()->maximum() + p.height());
1604 }
1605 resize(pageSize);
1606 if (jumpTo >= 0) {
1607 scrollArea->verticalScrollBar()->setValue(qRound(jumpTo * (scrollArea->verticalScrollBar()->maximum() + p.height()))); // correct position after resize
1608 }
1609 }
1610 }
1611
1612 void PDFWidget::resetMagnifier()
1613 {
1614 if (magnifier) {
1615 delete magnifier;
1616 magnifier = nullptr;
1617 }
1618 }
1619
1620 void PDFWidget::setResolution(int res)
1621 {
1622 dpi = res;
1623 adjustSize();
1624 resetMagnifier();
1625 }
1626
1627 void PDFWidget::setHighlightPath(const int page, const QPainterPath &path, const bool dontRemove)
1628 {
1629 highlightRemover.stop();
1630
1631 highlightPath = path;
1632 highlightPage = page;
1633 if (path.isEmpty()) return;
1634
1635
1636 PDFScrollArea *scrollArea = getScrollArea();
1637 if (scrollArea)
1638 scrollArea->ensureVisiblePageAbsolutePos(page, highlightPath.boundingRect().center());
1639 if (globalConfig->highlightDuration > 0 && !dontRemove)
1640 highlightRemover.start(globalConfig->highlightDuration);
1641 }
1642
1643 double PDFWidget::totalScaleFactor() const
1644 {
1645 return dpi / 72 * scaleFactor;
1646 }
1647
1648 int PDFWidget::currentPageHistoryIndex() const
1649 {
1650 return pageHistoryIndex;
1651 }
1652
1653 const QList<PDFPageHistoryItem> PDFWidget::currentPageHistory() const
1654 {
1655 return pageHistory;
1656 }
1657
1658 int PDFWidget::getHighlightPage() const
1659 {
1660 return highlightPage;
1661 }
1662
1663 void PDFWidget::clearHighlight()
1664 {
1665 highlightPath = QPainterPath();
1666 highlightPage = -1;
1667 update();
1668 }
1669
1670 int PDFWidget::getPageIndex()
1671 {
1672 return realPageIndex;
1673 }
1674
1675 void PDFWidget::reloadPage(bool sync)
1676 {
1677 //QList<int> oldpages = pages;
1678 pages.clear();
1679 if (magnifier != nullptr)
1680 magnifier->setPage(-1, 0, QRect());
1681 imagePage = -1;
1682 image = QPixmap();
1683 //highlightPath = QPainterPath();
1684 if (!document.isNull()) {
1685 if (realPageIndex >= realNumPages())
1686 realPageIndex = realNumPages() - 1;
1687 if (realPageIndex >= 0) {
1688 int visiblePageCount = qMin(gridx * gridy, realNumPages() - realPageIndex);
1689 for (int i = 0; i < visiblePageCount; i++)
1690 pages << i + realPageIndex;
1691 /*/use old pages if available ([a<=b], [c<=d] find [x<=y] with a <= x, c <= x, y <= b, y <= d)
1692 int firstCommonPage = qMax(pageIndex, oldPageIndex);
1693 int lastCommonPage = qMin(pageIndex + pageCount - 1, oldPageIndex + oldpages.size() - 1);
1694
1695 if (lastCommonPage < firstCommonPage) {
1696 for (int i=0; i < pageCount; i++)
1697 pages.append(pageIndex + i);
1698 } else {
1699 for (int i=pageIndex; i < firstCommonPage; i++) pages.append(i);
1700 for (int i=firstCommonPage; i <= lastCommonPage; i++) pages.append(oldpages[i-oldPageIndex]);
1701 for (int i=lastCommonPage + 1; i < pageIndex + pageCount; i++) pages.append(i);
1702 }*/
1703 //oldPageIndex = pageIndex;
1704 oldRealPageIndex = realPageIndex;
1705 }
1706 }
1707
1708 adjustSize();
1709 delayedUpdate();
1710 updateStatusBar();
1711
1712 if (0 <= pageHistoryIndex && pageHistoryIndex < pageHistory.size() && pageHistory[pageHistoryIndex].page == realPageIndex ) ;
1713 else if (0 <= pageHistoryIndex - 1 && pageHistoryIndex - 1 < pageHistory.size() && pageHistory[pageHistoryIndex - 1].page == realPageIndex ) pageHistoryIndex--;
1714 else if (0 <= pageHistoryIndex + 1 && pageHistoryIndex + 1 < pageHistory.size() && pageHistory[pageHistoryIndex + 1].page == realPageIndex ) pageHistoryIndex++;
1715 else {
1716 while (pageHistory.size() > pageHistoryIndex + 1) pageHistory.removeLast();
1717 pageHistory.append(PDFPageHistoryItem(realPageIndex, 0, 0));
1718 while (pageHistory.size() > 50) pageHistory.removeFirst();
1719 pageHistoryIndex = pageHistory.size() - 1;
1720 }
1721
1722 emit changedPage(realPageIndex, sync);
1723 }
1724
1725 void PDFWidget::updateStatusBar()
1726 {
1727 PDFDocument *doc = getPDFDocument();
1728 if (doc) {
1729 doc->showPage(realPageIndex + 1);
1730 doc->showScale(scaleFactor);
1731 }
1732 #ifdef PHONON
1733 if (movie) movie->place();
1734 #endif
1735 }
1736
1737 void PDFWidget::updateCurrentPageHistoryOffset(){
1738 if (pageHistoryIndex < 0 || pageHistoryIndex >= pageHistory.size()) return;
1739 if (realPageIndex != pageHistory[pageHistoryIndex].page) return;
1740 QPointF out;
1741 int page;
1742 mapToScaledPosition(mapFromParent(QPoint()), page, out);
1743 if (page != realPageIndex) return;
1744 pageHistory[pageHistoryIndex].x = out.x();
1745 pageHistory[pageHistoryIndex].y = out.y();
1746 }
1747
1748 PDFDocument *PDFWidget::getPDFDocument()
1749 {
1750 if (pdfdocument)
1751 return pdfdocument;
1752 QWidget *widget = window();
1753 PDFDocument *doc = qobject_cast<PDFDocument *>(widget);
1754 return doc;
1755 }
1756
1757 int PDFWidget::getPageOffset() const
1758 {
1759 int pageOffset = (!singlePageStep) && (gridCols() == 2) ? 1 : 0;
1760 return pageOffset;
1761 }
1762
1763 void PDFWidget::setGridSize(int gx, int gy, bool setAsDefault)
1764 {
1765 if (gridx == gx && gridy == gy)
1766 return;
1767 gridx = gx;
1768 gridy = gy;
1769 if (setAsDefault)
1770 return;
1771 int pi = realPageIndex;
1772 getScrollArea()->goToPage(realPageIndex);
1773 if (pi == realPageIndex)
1774 reloadPage();
1775 //update();
1776 }
1777
1778 int PDFWidget::visiblePages() const
1779 {
1780 if (pages.isEmpty()) return 0;
1781 int firstPage = pages.first();
1782 int lastPage = pages.last();
1783 int visibleHeight = getScrollArea()->viewport()->height() - this->y();
1784 while (lastPage > firstPage && pageRect(lastPage).top() >= visibleHeight)
1785 lastPage--;
1786 return lastPage - firstPage + 1;
1787 }
1788
1789 int PDFWidget::pseudoNumPages() const
1790 {
1791 if (document.isNull()) return 0;
1792 int pageOffset = getPageOffset();
1793 return docPages + pageOffset;
1794 //return document->numPages();
1795 }
1796
1797 int PDFWidget::realNumPages() const
1798 {
1799 if (document.isNull()) return 0;
1800 return docPages;
1801 }
1802
1803 int PDFWidget::pageStep()
1804 {
1805 bool cont = getScrollArea()->getContinuous();
1806 int result = 1;
1807 if (singlePageStep && !cont) return 1;
1808 if (cont) {
1809 result = gridx;
1810 } else {
1811 if (!singlePageStep)
1812 result = gridx * gridy;
1813 }
1814 return result;
1815 }
1816
1817 int PDFWidget::gridCols() const
1818 {
1819 return gridx;
1820 }
1821
1822 int PDFWidget::gridRowHeight() const
1823 {
1824 double result=maxPageSizeF().height() * scaleFactor * dpi / 72.0 + GridBorder;
1825 return qRound(result)>0 ? qRound(result) : 10; // avoid crashes
1826 }
1827
1828 int PDFWidget::gridBorder() const
1829 {
1830 return GridBorder;
1831 }
1832
1833 void PDFWidget::setSinglePageStep(bool step)
1834 {
1835 if (singlePageStep == step)
1836 return;
1837 singlePageStep = step;
1838 getScrollArea()->goToPage(realPageIndex);
1839 delayedUpdate();
1840 }
1841
1842 void PDFWidget::goFirst()
1843 {
1844 if (document.isNull()) return;
1845 getScrollArea()->goToPage(0);
1846 }
1847
1848 void PDFWidget::goPrev()
1849 {
1850 if (document.isNull()) return;
1851 getScrollArea()->goToPage(realPageIndex + getPageOffset() - pageStep());
1852 }
1853
1854 void PDFWidget::goNext()
1855 {
1856 if (document.isNull()) return;
1857 int pageOffset = getPageOffset();
1858 if (realPageIndex == 0 && pageOffset == 1)
1859 pageOffset = -1;
1860 getScrollArea()->goToPage(realPageIndex + pageOffset + pageStep());
1861 }
1862
1863 void PDFWidget::goLast()
1864 {
1865 if (document.isNull()) return;
1866 getScrollArea()->goToPage(realNumPages() - 1);
1867 }
1868
1869 void PDFWidget::goForward()
1870 {
1871 if (pageHistoryIndex + 1 < pageHistory.size()) {
1872 pageHistoryIndex++;
1873 REQUIRE(!document.isNull() && getScrollArea());
1874 goToPageRelativePosition(pageHistory[pageHistoryIndex].page, pageHistory[pageHistoryIndex].x, pageHistory[pageHistoryIndex].y);
1875 }
1876 }
1877
1878 void PDFWidget::goBack()
1879 {
1880 if (pageHistory.isEmpty()) return;
1881 if (pageHistoryIndex > 0) {
1882 pageHistoryIndex--;
1883 while (pageHistoryIndex >= pageHistory.size()) pageHistoryIndex--;
1884 REQUIRE(!document.isNull() && getScrollArea());
1885 goToPageRelativePosition(pageHistory[pageHistoryIndex].page, pageHistory[pageHistoryIndex].x, pageHistory[pageHistoryIndex].y);
1886 }
1887 }
1888
1889 void PDFWidget::upOrPrev()
1890 {
1891 if (document.isNull()) return;
1892 QScrollBar *scrollBar = getScrollArea()->verticalScrollBar();
1893 if (scrollBar->value() > scrollBar->minimum())
1894 scrollBar->triggerAction(QAbstractSlider::SliderSingleStepSub);
1895 else {
1896 if (realPageIndex > 0) {
1897 goPrev();
1898 scrollBar->triggerAction(QAbstractSlider::SliderToMaximum);
1899 }
1900 }
1901 shortcutUp->setAutoRepeat(scrollBar->value() > scrollBar->minimum());
1902 }
1903
1904 void PDFWidget::leftOrPrev()
1905 {
1906 if (document.isNull()) return;
1907 QScrollBar *scrollBar = getScrollArea()->horizontalScrollBar();
1908 if (scrollBar->value() > scrollBar->minimum())
1909 scrollBar->triggerAction(QAbstractSlider::SliderSingleStepSub);
1910 else {
1911 if (realPageIndex > 0) {
1912 goPrev();
1913 scrollBar->triggerAction(QAbstractSlider::SliderToMaximum);
1914 }
1915 }
1916 shortcutLeft->setAutoRepeat(scrollBar->value() > scrollBar->minimum());
1917 }
1918
1919 void PDFWidget::pageUpOrPrev()
1920 {
1921 if (document.isNull()) return;
1922 QScrollBar *scrollBar = getScrollArea()->verticalScrollBar();
1923 if (scrollBar->value() > scrollBar->minimum())
1924 scrollBar->triggerAction(QAbstractSlider::SliderPageStepSub);
1925 else {
1926 if (realPageIndex > 0) {
1927 goPrev();
1928 scrollBar->triggerAction(QAbstractSlider::SliderToMaximum);
1929 }
1930 }
1931 //shortcutPageUp->setAutoRepeat(scrollBar->value() > scrollBar->minimum());
1932 }
1933
1934 void PDFWidget::downOrNext()
1935 {
1936 if (document.isNull()) return;
1937 QScrollBar *scrollBar = getScrollArea()->verticalScrollBar();
1938 if (scrollBar->value() < scrollBar->maximum())
1939 scrollBar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
1940 else {
1941 if (realPageIndex < realNumPages() - 1) {
1942 goNext();
1943 scrollBar->triggerAction(QAbstractSlider::SliderToMinimum);
1944 }
1945 }
1946 shortcutDown->setAutoRepeat(scrollBar->value() < scrollBar->maximum());
1947 }
1948
1949 void PDFWidget::rightOrNext()
1950 {
1951 if (document.isNull()) return;
1952 QScrollBar *scrollBar = getScrollArea()->horizontalScrollBar();
1953 if (scrollBar->value() < scrollBar->maximum())
1954 scrollBar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
1955 else {
1956 if (realPageIndex < realNumPages() - 1) {
1957 goNext();
1958 scrollBar->triggerAction(QAbstractSlider::SliderToMinimum);
1959 }
1960 }
1961 shortcutRight->setAutoRepeat(scrollBar->value() < scrollBar->maximum());
1962 }
1963
1964 void PDFWidget::pageDownOrNext()
1965 {
1966 if (document.isNull()) return;
1967 QScrollBar *scrollBar = getScrollArea()->verticalScrollBar();
1968 if (scrollBar->value() < scrollBar->maximum())
1969 scrollBar->triggerAction(QAbstractSlider::SliderPageStepAdd);
1970 else {
1971 if (!getScrollArea()->getContinuous() && realPageIndex < realNumPages() - 1) {
1972 goNext();
1973 scrollBar->triggerAction(QAbstractSlider::SliderToMinimum);
1974 }
1975 }
1976 //shortcutPageDown->setAutoRepeat(scrollBar->value() < scrollBar->maximum());
1977 }
1978
1979 void PDFWidget::doPageDialog()
1980 {
1981 if (document.isNull()) return;
1982 bool ok;
1983 setCursor(Qt::ArrowCursor);
1984 int pageNo = QInputDialog::getInt(this, tr("Go to Page"),
1985 tr("Page number:"), realPageIndex + 1,
1986 1, realNumPages(), 1, &ok);
1987 if (ok)
1988 getScrollArea()->goToPage(pageNo - 1);
1989 }
1990
1991 int PDFWidget::normalizedPageIndex(int p)
1992 {
1993 if (p > 0) return p - (p - getPageOffset()) % pageStep();
1994 else return p;
1995 }
1996
1997 void PDFWidget::goToPageDirect(int p, bool sync)
1998 {
1999 if (p < 0) p = 0;
2000 if (p >= realNumPages()) p = realNumPages() - 1;
2001 p = normalizedPageIndex(p);
2002 if (p != realPageIndex && !document.isNull()) { //the first condition is important: it prevents a recursive sync crash
2003 if (p >= 0 && p < realNumPages()) {
2004 realPageIndex = p;
2005 reloadPage(sync);
2006 delayedUpdate();
2007 }
2008 }
2009 }
2010
2011 void PDFWidget::fixedScale(qreal scale)
2012 {
2013 scaleOption = kFixedMag;
2014 if (qAbs(scaleFactor/scale-1.0)>0.001) {
2015 scaleFactor = scale;
2016 adjustSize();
2017 delayedUpdate();
2018 updateStatusBar();
2019 emit changedZoom(scaleFactor);
2020 }
2021 emit changedScaleOption(scaleOption);
2022 }
2023
2024 void PDFWidget::fitWidth(bool checked)
2025 {
2026 if (checked) {
2027 scaleOption = kFitWidth;
2028 QAbstractScrollArea *scrollArea = getScrollArea();
2029 if (scrollArea && !pages.isEmpty()) {
2030 qreal portWidth = scrollArea->viewport()->width() - gridBorder() * (gridx - 1);
2031 QSizeF pageSize = maxPageSizeFDpiAdjusted() * gridx;
2032 scaleFactor = portWidth / pageSize.width();
2033 if (scaleFactor < kMinScaleFactor)
2034 scaleFactor = kMinScaleFactor;
2035 else if (scaleFactor > kMaxScaleFactor)
2036 scaleFactor = kMaxScaleFactor;
2037 adjustSize();
2038 delayedUpdate();
2039 updateStatusBar();
2040 emit changedZoom(scaleFactor);
2041 }
2042 } else
2043 scaleOption = kFixedMag;
2044 emit changedScaleOption(scaleOption);
2045 }
2046
2047 void PDFWidget::fitTextWidth(bool checked)
2048 {
2049 if (checked) {
2050 scaleOption = kFitTextWidth;
2051 QAbstractScrollArea *scrollArea = getScrollArea();
2052 if (scrollArea && !pages.isEmpty()) {
2053 int margin = 8;
2054 qreal portWidth = scrollArea->viewport()->width() - GridBorder * (gridx - 1);
2055
2056 QRectF textRect = horizontalTextRangeF();
2057 if (!textRect.isValid()) return;
2058 qreal targetWidth = maxPageSizeFDpiAdjusted().width() * (gridx - 1) + textRect.width() * dpi / 72.0;
2059 //qreal targetWidth = textRect.width() * dpi / 72.0;
2060 // total with of all pages in the grid - textMargin of a single page
2061 // for a 1x grid, targetWith is the same as textRect.width()
2062 scaleFactor = portWidth / ((targetWidth ) + 2 * margin);
2063 if (scaleFactor < kMinScaleFactor)
2064 scaleFactor = kMinScaleFactor;
2065 else if (scaleFactor > kMaxScaleFactor)
2066 scaleFactor = kMaxScaleFactor;
2067 adjustSize();
2068 scrollArea->horizontalScrollBar()->setValue(qRound(((textRect.left() * dpi / 72.0) - margin) *scaleFactor));
2069 delayedUpdate();
2070 updateStatusBar();
2071 emit changedZoom(scaleFactor);
2072 }
2073 } else
2074 scaleOption = kFixedMag;
2075 emit changedScaleOption(scaleOption);
2076 }
2077
2078 void PDFWidget::fitWindow(bool checked)
2079 {
2080 if (checked) {
2081 scaleOption = kFitWindow;
2082 PDFScrollArea *scrollArea = getScrollArea();
2083 if (scrollArea && !pages.isEmpty()) {
2084 qreal portWidth = scrollArea->viewport()->width() - GridBorder * (gridx - 1);
2085 qreal portHeight = scrollArea->viewport()->height() - GridBorder * (gridy - 1);
2086 QSizeF pageSize = maxPageSizeFDpiAdjusted();
2087 qreal sfh = portWidth / pageSize.width();
2088 qreal sfv = portHeight / pageSize.height();
2089 scaleFactor = sfh < sfv ? sfh : sfv;
2090 if (scaleFactor < kMinScaleFactor)
2091 scaleFactor = kMinScaleFactor;
2092 else if (scaleFactor > kMaxScaleFactor)
2093 scaleFactor = kMaxScaleFactor;
2094 adjustSize();
2095 delayedUpdate();
2096 updateStatusBar();
2097 emit changedZoom(scaleFactor);
2098 }
2099 } else
2100 scaleOption = kFixedMag;
2101 emit changedScaleOption(scaleOption);
2102 }
2103
2104 void PDFWidget::doZoom(const QPoint &clickPos, int dir, qreal newScaleFactor) // dir = 1 for in, -1 for out, 0 to use newScaleFactor
2105 {
2106 QPointF pagePos(clickPos.x() / scaleFactor * 72.0 / dpi,
2107 clickPos.y() / scaleFactor * 72.0 / dpi);
2108 scaleOption = kFixedMag;
2109 emit changedScaleOption(scaleOption);
2110
2111 double zoomStepFactor = globalConfig->zoomStepFactor;
2112 if (zoomStepFactor > 10) zoomStepFactor = 10;
2113 if (zoomStepFactor < 1.001) zoomStepFactor = 1.001;
2114
2115
2116 QPoint globalPos = mapToGlobal(clickPos);
2117 if (dir > 0 && scaleFactor < kMaxScaleFactor) {
2118 scaleFactor *= zoomStepFactor;
2119 if (qFabs(scaleFactor - qRound(scaleFactor)) < 0.01)
2120 scaleFactor = qRound(scaleFactor);
2121 if (scaleFactor > kMaxScaleFactor)
2122 scaleFactor = kMaxScaleFactor;
2123 } else if (dir < 0 && scaleFactor > kMinScaleFactor) {
2124 scaleFactor /= zoomStepFactor;
2125 if (qFabs(scaleFactor - qRound(scaleFactor)) < 0.01)
2126 scaleFactor = qRound(scaleFactor);
2127 if (scaleFactor < kMinScaleFactor)
2128 scaleFactor = kMinScaleFactor;
2129 } else if (dir == 0) {
2130 if (newScaleFactor < kMinScaleFactor) {
2131 newScaleFactor = kMinScaleFactor;
2132 } else if (newScaleFactor > kMaxScaleFactor) {
2133 newScaleFactor = kMaxScaleFactor;
2134 }
2135 if (qAbs(newScaleFactor/scaleFactor-1)<0.001) { // about equal
2136 return;
2137 }
2138 scaleFactor = newScaleFactor;
2139 }
2140
2141 adjustSize();
2142 update();
2143 updateStatusBar();
2144 emit changedZoom(scaleFactor);
2145 QPoint localPos = mapFromGlobal(globalPos);
2146 QPoint pageToLocal(int(pagePos.x() * scaleFactor / 72.0 * dpi),
2147 int(pagePos.y() * scaleFactor / 72.0 * dpi));
2148 QAbstractScrollArea *scrollArea = getScrollArea();
2149 if (scrollArea) {
2150 QScrollBar *hs = scrollArea->horizontalScrollBar();
2151 if (hs != nullptr)
2152 hs->setValue(hs->value() + pageToLocal.x() - localPos.x());
2153 QScrollBar *vs = scrollArea->verticalScrollBar();
2154 if (vs != nullptr)
2155 vs->setValue(vs->value() + pageToLocal.y() - localPos.y());
2156 }
2157 }
2158
2159 void PDFWidget::doZoom(const QPointF &clickPos, int dir, qreal newScaleFactor) // dir = 1 for in, -1 for out, 0 to use newScaleFactor
2160 {
2161 QPointF pagePos(clickPos.x() / scaleFactor * 72.0 / dpi,
2162 clickPos.y() / scaleFactor * 72.0 / dpi);
2163 scaleOption = kFixedMag;
2164 emit changedScaleOption(scaleOption);
2165
2166 double zoomStepFactor = globalConfig->zoomStepFactor;
2167 if (zoomStepFactor > 10) zoomStepFactor = 10;
2168 if (zoomStepFactor < 1.001) zoomStepFactor = 1.001;
2169
2170
2171 QPoint globalPos = mapToGlobal(clickPos.toPoint());
2172 if (dir > 0 && scaleFactor < kMaxScaleFactor) {
2173 scaleFactor *= zoomStepFactor;
2174 if (qFabs(scaleFactor - qRound(scaleFactor)) < 0.01)
2175 scaleFactor = qRound(scaleFactor);
2176 if (scaleFactor > kMaxScaleFactor)
2177 scaleFactor = kMaxScaleFactor;
2178 } else if (dir < 0 && scaleFactor > kMinScaleFactor) {
2179 scaleFactor /= zoomStepFactor;
2180 if (qFabs(scaleFactor - qRound(scaleFactor)) < 0.01)
2181 scaleFactor = qRound(scaleFactor);
2182 if (scaleFactor < kMinScaleFactor)
2183 scaleFactor = kMinScaleFactor;
2184 } else if (dir == 0) {
2185 if (newScaleFactor < kMinScaleFactor) {
2186 newScaleFactor = kMinScaleFactor;
2187 } else if (newScaleFactor > kMaxScaleFactor) {
2188 newScaleFactor = kMaxScaleFactor;
2189 }
2190 if (qAbs(newScaleFactor/scaleFactor-1)<0.001) { // about equal
2191 return;
2192 }
2193 scaleFactor = newScaleFactor;
2194 }
2195
2196 adjustSize();
2197 update();
2198 updateStatusBar();
2199 emit changedZoom(scaleFactor);
2200 QPoint localPos = mapFromGlobal(globalPos);
2201 QPoint pageToLocal(int(pagePos.x() * scaleFactor / 72.0 * dpi),
2202 int(pagePos.y() * scaleFactor / 72.0 * dpi));
2203 QAbstractScrollArea *scrollArea = getScrollArea();
2204 if (scrollArea) {
2205 QScrollBar *hs = scrollArea->horizontalScrollBar();
2206 if (hs != nullptr)
2207 hs->setValue(hs->value() + pageToLocal.x() - localPos.x());
2208 QScrollBar *vs = scrollArea->verticalScrollBar();
2209 if (vs != nullptr)
2210 vs->setValue(vs->value() + pageToLocal.y() - localPos.y());
2211 }
2212 }
2213
2214 void PDFWidget::zoomIn()
2215 {
2216 QWidget *parent = parentWidget();
2217 if (parent != nullptr) {
2218 QPoint ctr = mapFromParent(QPoint(parent->width() / 2, parent->height() / 2));
2219 doZoom(ctr, 1);
2220 }
2221 }
2222
2223 void PDFWidget::zoomOut()
2224 {
2225 QWidget *parent = parentWidget();
2226 if (parent != nullptr) {
2227 QPoint ctr = mapFromParent(QPoint(parent->width() / 2, parent->height() / 2));
2228 doZoom(ctr, -1);
2229 }
2230 }
2231
2232 void PDFWidget::zoom(qreal scale)
2233 {
2234 QWidget *parent = parentWidget();
2235 if (parent != nullptr) {
2236 QPoint ctr = mapFromParent(QPoint(parent->width() / 2, parent->height() / 2));
2237 doZoom(ctr, 0, scale);
2238 }
2239 }
2240
2241 //TODO: optimize?
2242 QRect PDFWidget::gridPageRect(int pageIndex) const
2243 {
2244 if (gridx * gridy <= 1)
2245 return rect();
2246
2247 QSizeF realPageSize = maxPageSizeFDpiAdjusted() * scaleFactor;
2248
2249 int realPageSizeX = qRound(realPageSize.width());
2250 int realPageSizeY = qRound(realPageSize.height());
2251
2252 QPoint p((realPageSizeX + GridBorder) * (pageIndex % gridx), (realPageSizeY + GridBorder) * (pageIndex / gridx));
2253 return QRect(p, QPoint(p.x() + realPageSizeX, p.y() + realPageSizeY));
2254 }
2255
2256 QPoint PDFWidget::gridPagePosition(int pageIndex) const
2257 {
2258 return gridPageRect(pageIndex).topLeft();
2259 }
2260
2261 int PDFWidget::gridPageIndex(const QPoint &position) const
2262 {
2263 if (pages.size() == 0) return -1;
2264 if (gridx * gridy == 1) return 0;
2265 for (int i = 0; i < gridx * gridy; i++)
2266 if (gridPageRect(i).contains(position))
2267 return i;
2268 return -1;
2269 }
2270
2271 void PDFWidget::mapToScaledPosition(const QPoint &position, int &page, QPointF &scaledPos) const
2272 {
2273 if (document.isNull()) return;
2274 page = pageFromPos(position);
2275 QRect r = pageRect(page);
2276 if (r.isNull()) return;
2277 // poppler's pos is relative to the page rect
2278 QPoint temp = position - r.topLeft();
2279 scaledPos.setX(temp.x() * 1.0 / r.width());
2280 scaledPos.setY(temp.y() * 1.0 / r.height());
2281 }
2282
2283 QPoint PDFWidget::mapFromScaledPosition(int page, const QPointF &scaledPos) const
2284 {
2285 if (document.isNull() || pages.size() == 0) return QPoint();
2286 QRect r = pageRect(page);
2287 if (r.isNull()) return QPoint();
2288 return r.topLeft() + QPoint( qRound(scaledPos.x() * r.width()), qRound(scaledPos.y() * r.height() ) );
2289 /* if (rpage < 0 || rpage >= realNumPages()) return QPoint();
2290 QPoint p = pageRect(rpage).topLeft();
2291 Poppler::Page *popplerPage=document->page(rpage);
2292 if(!popplerPage)
2293 return QPoint();
2294 int w=popplerPage->pageSizeF().width();
2295 int h=popplerPage->pageSizeF().height();
2296 delete popplerPage;
2297 return (QPointF(scaledPos.x() * w, scaledPos.y() * h) * scaleFactor * dpi / 72.0 ).toPoint() + p;*/
2298 }
2299
2300 int PDFWidget::pageFromPos(const QPoint &pos) const
2301 {
2302 int page = gridPageIndex(pos) + realPageIndex;
2303 if (realPageIndex == 0) page -= getPageOffset();
2304 if (pageRect(page).contains(pos)) return page;
2305 else return -1;
2306 }
2307
2308 QRect PDFWidget::pageRect(int page) const
2309 {
2310 if (document.isNull())
2311 return QRect();
2312 if (page < pages.first() || page > pages.last())
2313 return QRect();
2314 QRect grect;
2315 if (realPageIndex == 0) grect = gridPageRect(page + getPageOffset());
2316 else grect = gridPageRect(page - realPageIndex);
2317 std::unique_ptr<Poppler::Page> popplerPage(document->page(page));
2318 if (!popplerPage)
2319 return grect;
2320 int realSizeW = qRound(dpi * scaleFactor / 72.0 * popplerPage->pageSizeF().width());
2321 int realSizeH = qRound(dpi * scaleFactor / 72.0 * popplerPage->pageSizeF().height());
2322 int xOffset = (grect.width() - realSizeW) / 2;
2323 int yOffset = (grect.height() - realSizeH) / 2;
2324 if (gridx == 2 && getPageOffset() == 1) {
2325 if (page & 1) xOffset *= 2;
2326 else xOffset = 0;
2327 }
2328 return QRect(grect.left() + xOffset, grect.top() + yOffset, realSizeW, realSizeH);
2329 }
2330
2331 QSizeF PDFWidget::maxPageSizeF() const
2332 {
2333 if (document.isNull()) return QSizeF();
2334 //QSizeF maxPageSize;
2335 //foreach (int page, pages) {
2336 if (!maxPageSize.isValid()) {
2337 for (int page = 0; page < docPages; page++) {
2338 //if (page < 0 || page >= numPages()) continue;
2339 std::unique_ptr<Poppler::Page> popplerPage(document->page(page));
2340 if (!popplerPage) break;
2341 if (popplerPage->pageSizeF().width() > maxPageSize.width()) maxPageSize.setWidth(popplerPage->pageSizeF().width());
2342 if (popplerPage->pageSizeF().height() > maxPageSize.height()) maxPageSize.setHeight(popplerPage->pageSizeF().height());
2343 }
2344 }
2345 return maxPageSize;
2346 }
2347
2348 QSizeF PDFWidget::maxPageSizeFDpiAdjusted() const
2349 {
2350 return maxPageSizeF() * dpi / 72.0;
2351 }
2352
2353 // calculates the maximal horizontal text range (xmin, xmax) over the total document.
2354 // Note: this may be slow on large documents because each TextBox (~word) is analyzed.
2355 // Therefore the value is cached.
2356 // TODO: Replace TextBoxes with the ArtBox of the page once this becomes available via the poppler-qt interface.
2357 // Only the horizontal values of the returned QRectF have meaning.
2358 QRectF PDFWidget::horizontalTextRangeF()
2359 {
2360 REQUIRE_RET(document && pdfdocument, QRectF());
2361
2362 qreal textXmin = +1.e99;
2363 qreal textXmax = -1.e99;
2364 if (!horizontalTextRange.isValid()) {
2365 // horitontalTextRangeF() may be called concurrently, in particular during startup
2366 // see e.g. https://sourceforge.net/p/texstudio/bugs/1292/
2367 // The crash therein is probably caused by a simultaneous access to poppler, so one
2368 // could limit the access there. However, since textWidth calculation is currently
2369 // quite expensive, we do not want to do it multiple times. I assume that the call
2370 // which locks the calculation will finally lead to an update of the displayed width.
2371 // This justifies to discard all width requests in between.
2372 if (!textwidthCalculationMutex.tryLock()) {
2373 return QRectF();
2374 }
2375 QProgressDialog progress(tr("Calculating text width"), tr("Cancel"), 0, docPages);
2376 progress.setWindowModality(Qt::WindowModal);
2377 progress.setMinimumDuration(500);
2378
2379 for (int page = 0; page < docPages; page++) {
2380 progress.setValue(page); //this is like the fire nation
2381 if (horizontalTextRange.isValid()) return horizontalTextRange;
2382 if (progress.wasCanceled() || !document || !pdfdocument) break;
2383 std::unique_ptr<Poppler::Page> popplerPage{ document->page(page) };
2384
2385 if (!popplerPage) break;
2386 for(auto &textbox: popplerPage->textList()) {
2387 QRectF bb = textbox->boundingBox();
2388 if (textXmin > bb.left()) textXmin = bb.left();
2389 if (textXmax < bb.right()) textXmax = bb.right();
2390 }
2391 }
2392 if (textXmax > textXmin) {
2393 horizontalTextRange = QRectF(textXmin, 0, textXmax - textXmin, 1);
2394 }
2395 textwidthCalculationMutex.unlock();
2396 }
2397 return horizontalTextRange;
2398 }
2399
2400
2401 void PDFWidget::saveState()
2402 {
2403 saveScaleFactor = scaleFactor;
2404 saveScaleOption = scaleOption;
2405 }
2406
2407 void PDFWidget::restoreState()
2408 {
2409 if (qAbs(scaleFactor/saveScaleFactor-1.0)>0.001) {
2410 scaleFactor = saveScaleFactor;
2411 adjustSize();
2412 update();
2413 updateStatusBar();
2414 emit changedZoom(scaleFactor);
2415 }
2416 scaleOption = saveScaleOption;
2417 emit changedScaleOption(scaleOption);
2418 }
2419
2420 PDFScrollArea *PDFWidget::getScrollArea() const
2421 {
2422 QWidget *parent = parentWidget();
2423 if (parent != nullptr)
2424 return qobject_cast<PDFScrollArea *>(parent->parentWidget());
2425 else
2426 return nullptr;
2427 }
2428
2429
2430 //#pragma mark === PDFDocument ===
2431
2432 QList<PDFDocument *> PDFDocument::docList;
2433
2434 PDFDocument::PDFDocument(PDFDocumentConfig *const pdfConfig, bool embedded)
2435 : renderManager(nullptr), curFileSize(0), menubar(nullptr), exitFullscreen(nullptr), watcher(nullptr), reloadTimer(nullptr), dwClock(nullptr), dwOutline(nullptr), dwFonts(nullptr), dwInfo(nullptr), dwOverview(nullptr), dwSearch(nullptr),
2436 syncFromSourceBlocked(false), syncToSourceBlocked(false)
2437 {
2438 REQUIRE(pdfConfig);
2439 Q_ASSERT(!globalConfig || (globalConfig == pdfConfig));
2440 globalConfig = pdfConfig;
2441
2442 embeddedMode = embedded;
2443 init(embedded);
2444
2445
2446 watcher = new QFileSystemWatcher(this);
2447 connect(watcher, SIGNAL(fileChanged(const QString&)), this, SLOT(reloadWhenIdle()));
2448
2449 if (!embedded) {
2450 int &x = globalConfig->windowLeft;
2451 int &y = globalConfig->windowTop;
2452 int &w = globalConfig->windowWidth;
2453 int &h = globalConfig->windowHeight;
2454 QRect screen = UtilsUi::getAvailableGeometryAt(QPoint(x, y));
2455 // add some tolerance, as fullscreen seems to have negative coordinate (KDE, Win7 ...)
2456 screen.adjust(-8, -8, +8, +8);
2457 if (!screen.contains(x, y)) {
2458 // top left is not on screen
2459 x = screen.x() + screen.width() * 2 / 3;
2460 y = screen.y() + 10;
2461 if (x + w > screen.right()) w = screen.width() / 3 - 26;
2462 if (y + h > screen.height()) h = screen.height() - 100;
2463 }
2464 if (globalConfig->windowMaximized)
2465 showMaximized();
2466 else
2467 setWindowState(Qt::WindowNoState);
2468
2469 resize(w, h); //important to first resize then move
2470 move(x, y);
2471 if (!globalConfig->windowState.isEmpty()) restoreState(globalConfig->windowState);
2472 toolBar->setVisible(globalConfig->toolbarVisible);
2473 statusbar->setVisible(true);
2474 }
2475 if (embeddedMode && globalConfig->autoHideToolbars) {
2476 setAutoHideToolbars(true);
2477 }
2478
2479 }
2480
2481 PDFDocument::~PDFDocument()
2482 {
2483 globalConfig->windowMaximized = isMaximized();
2484
2485 ConfigManager *configManager=dynamic_cast<ConfigManager *>(ConfigManager::getInstance());
2486
2487 if(configManager){
2488 #if (QT_VERSION > 0x050000) && (QT_VERSION <= 0x050700) && (defined(Q_OS_MAC))
2489 QList<QKeySequence> keys=configManager->specialShortcuts.keys();
2490 foreach(QKeySequence key,keys){
2491 QList<QAction *>acts=configManager->specialShortcuts.values(key);
2492 foreach(QAction *act,acts){
2493 if(act->objectName().startsWith("pdf")){
2494 configManager->specialShortcuts.remove(key,act);
2495 }
2496 }
2497 }
2498 #endif
2499
2500
2501 configManager->menuParents.removeAll(menuroot);
2502 foreach (QMenu *menu, menus) {
2503 configManager->managedMenus.removeAll(menu);
2504 }
2505 }
2506
2507 docList.removeAll(this);
2508 emit documentClosed();
2509 delete renderManager;
2510 renderManager=nullptr;
2511
2512 delete menubar;
2513 menubar=nullptr;
2514 }
2515 /*!
2516 * \brief setup ToolBar
2517 */
2518 void PDFDocument::setupToolBar(){
2519 toolBar = new QToolBar(this);
2520 toolBar->setWindowTitle(tr("Toolbar"));
2521 toolBar->setObjectName(QString("toolBar"));
2522 toolBar->setIconSize(QSize(24, 24));
2523 addToolBar(Qt::TopToolBarArea, toolBar);
2524 toolBarTimer = new QTimer(this);
2525 toolBarTimer->setSingleShot(true);
2526 connect(toolBarTimer, SIGNAL(timeout()), this, SLOT(showToolbars()));
2527
2528 toolBar->addAction(actionTypeset);
2529 toolBar->addSeparator();
2530 toolBar->addAction(actionExternalViewer);
2531 toolBar->addSeparator();
2532 toolBar->addAction(actionMagnify);
2533 toolBar->addAction(actionScroll);
2534 toolBar->addSeparator();
2535 toolBar->addAction(actionBack);
2536 toolBar->addAction(actionForward);
2537 toolBar->addSeparator();
2538 toolBar->addAction(actionFirst_Page);
2539 toolBar->addAction(actionPrevious_Page);
2540 toolBar->addAction(actionNext_Page);
2541 toolBar->addAction(actionLast_Page);
2542 toolBar->addSeparator();
2543 toolBar->addAction(actionActual_Size);
2544 toolBar->addAction(actionFit_to_Width);
2545 toolBar->addAction(actionFit_to_Text_Width);
2546 toolBar->addAction(actionFit_to_Window);
2547 toolBar->addSeparator();
2548 toolBar->addAction(actionAutoHideToolbars);
2549 toolBar->addAction(actionEnlargeViewer);
2550 toolBar->addAction(actionShrinkViewer);
2551 toolBar->addAction(actionToggleEmbedded);
2552 toolBar->addAction(actionClose);
2553
2554 statusbar = new QStatusBar(this);
2555 statusbar->setObjectName(QString("statusbar"));
2556 setStatusBar(statusbar);
2557 }
2558 /*!
2559 * \brief setup menus
2560 * \param embedded adapt to embedded setting
2561 */
2562 void PDFDocument::setupMenus(bool embedded)
2563 {
2564 ConfigManager *configManager=dynamic_cast<ConfigManager *>(ConfigManager::getInstance());
2565 if(!configManager) return;
2566 menuroot=new QMenu(this);
2567
2568 menubar = new QMenuBar(nullptr);
2569 menubar->setObjectName(QString::fromUtf8("menubar"));
2570 menubar->setGeometry(QRect(0, 0, 1197, 21));
2571
2572
2573
2574 menuFile=configManager->newManagedMenu(menuroot,menubar,"pdf/file",QApplication::translate("PDFDocument", "&File"));
2575 menuEdit_2=configManager->newManagedMenu(menuroot,menubar,"pdf/edit",QApplication::translate("PDFDocument", "&Edit"));
2576 menuView=configManager->newManagedMenu(menuroot,menubar,"pdf/view",QApplication::translate("PDFDocument", "&View"));
2577 menuGrid=configManager->newManagedMenu(menuView,nullptr,"pdf/view/grid",QApplication::translate("PDFDocument", "Grid"));
2578 menuWindow=configManager->newManagedMenu(menuroot,menubar,"pdf/window",QApplication::translate("PDFDocument", "&Window"));
2579 menuEdit=configManager->newManagedMenu(menuroot,menubar,"pdf/config",QApplication::translate("PDFDocument", "&Configure"));
2580 menuHelp=configManager->newManagedMenu(menuroot,menubar,"pdf/help",QApplication::translate("PDFDocument", "&Help"));
2581 menus<<menuFile<<menuEdit<<menuEdit_2<<menuGrid<<menuHelp<<menuWindow<<menuView; // housekeeping for later removal
2582
2583 if(!embedded)
2584 setMenuBar(menubar);
2585
2586 actionUserManual=configManager->newManagedAction(menuroot,menuHelp, "help", tr("User &Manual..."), this,SIGNAL(triggeredManual()), QList<QKeySequence>());
2587 menuHelp->addSeparator();
2588
2589 configManager->newManagedAction(menuroot,menuHelp, "about", tr("About"), this,SIGNAL(triggeredAbout()), QList<QKeySequence>() );
2590 configManager->newManagedAction(menuroot,menuFile, "open", tr("&Open..."), this,SLOT(fileOpen()), QList<QKeySequence>(),"document-open" );
2591 configManager->newManagedAction(menuroot,menuFile, "split", tr("Split && Merge..."), this,SLOT(splitMergeTool()), QList<QKeySequence>() );
2592 actionClose=configManager->newManagedAction(menuroot,menuFile, "close", tr("&Close"), this,SLOT(close()), QList<QKeySequence>()<< QKeySequence(Qt::CTRL | Qt::Key_W) ,"close");
2593 menuFile->addSeparator();
2594 configManager->newManagedAction(menuroot,menuFile, "quit", tr("&Quit TeXstudio"), this,SIGNAL(triggeredQuit()), QList<QKeySequence>());
2595 actionPreferences=configManager->newManagedAction(menuroot,menuEdit, "preferences", tr("&Configure TeXstudio"), this, SIGNAL(triggeredConfigure()), QList<QKeySequence>());
2596 menuEdit->addSeparator();
2597 actionScrolling_follows_cursor=configManager->newManagedAction(menuroot,menuEdit, "followCursor", tr("Scrolling follows cursor"), this, "", QList<QKeySequence>());
2598 actionScrolling_follows_cursor->setCheckable(true);
2599 actionCursor_follows_scrolling=configManager->newManagedAction(menuroot,menuEdit, "followScroll", tr("Cursor follows scrolling"), this, "", QList<QKeySequence>());
2600 actionCursor_follows_scrolling->setCheckable(true);
2601 actionSynchronize_multiple_views=configManager->newManagedAction(menuroot,menuEdit, "syncViews", tr("Synchronize multiple views"), this, "", QList<QKeySequence>());
2602 actionSynchronize_multiple_views->setCheckable(true);
2603 actionNoSynchronization=configManager->newManagedAction(menuroot,menuEdit, "noSynchronization", tr("Ignore for synchronization"), this, "", QList<QKeySequence>());
2604 actionNoSynchronization->setCheckable(true);
2605 menuEdit->addSeparator();
2606 actionInvertColors=configManager->newManagedAction(menuroot,menuEdit, "invertColors", tr("Invert Colors"), pdfWidget, SLOT(update()), QList<QKeySequence>());
2607 actionInvertColors->setCheckable(true);
2608 actionGrayscale=configManager->newManagedAction(menuroot,menuEdit, "grayscale", tr("Grayscale"), pdfWidget, SLOT(update()), QList<QKeySequence>());
2609 actionGrayscale->setCheckable(true);
2610
2611 actionMagnify=configManager->newManagedAction(menuroot,menuView, "magnify", tr("&Magnify"), this, "", QList<QKeySequence>(),"magnifier-button");
2612 actionScroll=configManager->newManagedAction(menuroot,menuView, "scroll", tr("&Scroll"), this, "", QList<QKeySequence>(),"hand");
2613 menuView->addSeparator();
2614 actionFirst_Page=configManager->newManagedAction(menuroot,menuView, "firstPage", tr("&First Page"), pdfWidget, SLOT(goFirst()), QList<QKeySequence>()<<Qt::Key_Home<<QKeySequence(Qt::ControlModifier | Qt::Key_Home),"go-first");
2615 actionBack=configManager->newManagedAction(menuroot,menuView, "back", tr("Back"), pdfWidget, SLOT(goBack()), QList<QKeySequence>()<< QKeySequence(Qt::AltModifier | Qt::Key_L),"back");
2616 actionPrevious_Page=configManager->newManagedAction(menuroot,menuView, "previous", tr("&Previous Page"), pdfWidget, SLOT(goPrev()), QList<QKeySequence>(),"go-previous");
2617 actionNext_Page=configManager->newManagedAction(menuroot,menuView, "next", tr("&Next Page"), pdfWidget, SLOT(goNext()), QList<QKeySequence>(),"go-next");
2618 actionForward=configManager->newManagedAction(menuroot,menuView, "forward", tr("Forward"), pdfWidget, SLOT(goForward()), QList<QKeySequence>()<< QKeySequence(Qt::AltModifier | Qt::Key_R),"forward");
2619 actionLast_Page=configManager->newManagedAction(menuroot,menuView, "last", tr("&Last Page"), pdfWidget, SLOT(goLast()), QList<QKeySequence>()<< Qt::Key_End << QKeySequence(Qt::ControlModifier | Qt::Key_End),"go-last");
2620 menuView->addSeparator();
2621 actionGo_to_Page=configManager->newManagedAction(menuroot,menuView, "goto", tr("&Go to Page..."), pdfWidget, SLOT(doPageDialog()), QList<QKeySequence>()<< QKeySequence(Qt::ControlModifier | Qt::Key_J));
2622 menuView->addSeparator();
2623 actionZoom_In=configManager->newManagedAction(menuroot,menuView, "zoomIn", tr("Zoom &In"), pdfWidget, SLOT(zoomIn()), QList<QKeySequence>()<< QKeySequence(Qt::ControlModifier | Qt::Key_Plus),"zoom-in");
2624 actionZoom_Out=configManager->newManagedAction(menuroot,menuView, "zoomOut", tr("Zoom &Out"), pdfWidget, SLOT(zoomOut()), QList<QKeySequence>()<< QKeySequence(Qt::ControlModifier | Qt::Key_Minus),"zoom-out");
2625 actionActual_Size=configManager->newManagedAction(menuroot,menuView, "actualSize", tr("&Actual Size"), pdfWidget, SLOT(fixedScale()), QList<QKeySequence>()<< QKeySequence(Qt::ControlModifier | Qt::Key_1),"zoom-original");
2626 actionFit_to_Width=configManager->newManagedAction(menuroot,menuView, "fitToWidth", tr("Fit to Wi&dth"), this, "", QList<QKeySequence>()<< QKeySequence(Qt::ControlModifier | Qt::Key_2),"zoom-fit-width");
2627 actionFit_to_Width->setCheckable(true);
2628 actionFit_to_Text_Width=configManager->newManagedAction(menuroot,menuView, "fitToTextWidth", tr("Fit to &Text Width"), this, "", QList<QKeySequence>()<< QKeySequence(Qt::ControlModifier | Qt::Key_4),"zoom-fit-text-width");
2629 actionFit_to_Text_Width->setCheckable(true);
2630 actionFit_to_Window=configManager->newManagedAction(menuroot,menuView, "fitToWindow", tr("Fit to &Window"), this, "", QList<QKeySequence>()<< QKeySequence(Qt::ControlModifier | Qt::Key_3),"zoom-fit-best");
2631 actionFit_to_Window->setCheckable(true);
2632 actionContinuous=configManager->newManagedAction(menuroot,menuView, "continuous", tr("&Continuous"), this, "", QList<QKeySequence>());
2633 actionContinuous->setCheckable(true);
2634 actionContinuous->setChecked(true);
2635 menuView->addAction(menuGrid->menuAction());
2636 menuView->addSeparator();
2637 actionFull_Screen=configManager->newManagedAction(menuroot,menuView, "fullscreen", tr("Full &Screen"), this, SLOT(toggleFullScreen(bool)), QList<QKeySequence>()<<QKeySequence(Qt::ControlModifier|Qt::ShiftModifier|Qt::Key_F));
2638 actionPresentation=configManager->newManagedAction(menuroot,menuView, "presentation", tr("Presentation"), this, SLOT(toggleFullScreen(bool)), QList<QKeySequence>()<<Qt::Key_F5);
2639 actionExternalViewer=configManager->newManagedAction(menuroot,menuView, "external", tr("External Viewer"), this, SLOT(runExternalViewer()), QList<QKeySequence>(),"acroread");
2640 actionEnlargeViewer=configManager->newManagedAction(menuroot,menuView, "enlarge", tr("Enlarge Viewer"), this, SLOT(enlarge()), QList<QKeySequence>(),"enlarge-viewer");
2641 actionShrinkViewer=configManager->newManagedAction(menuroot,menuView, "shrink", tr("Shrink Viewer"), this, SLOT(shrink()), QList<QKeySequence>(),"shrink-viewer");
2642 actionToggleEmbedded=configManager->newManagedAction(menuroot,menuView, "toggle", tr("Windowed/Embedded"), this, SLOT(toggleEmbedded()), QList<QKeySequence>());
2643 actionAutoHideToolbars=configManager->newManagedAction(menuroot,menuView, "autoHideToolbar", tr("Auto-hide Toolbar"), this, SLOT(toggleAutoHideToolbars()), QList<QKeySequence>(),"hide-toolbars");
2644 actionAutoHideToolbars->setCheckable(true);
2645 actionAutoHideToolbars->setChecked(globalConfig->autoHideToolbars);
2646
2647 static QStringList sl;
2648 configManager->registerOption("Preview/Possible Grid Sizes", &sl, QStringList() << "1x1" << "2x1" << "1x2" << "2x2" << "3x1" << "3x2" << "3x3");
2649 foreach (const QString &gs, sl) {
2650 QAction *a = configManager->newManagedAction(menuroot,menuGrid, "grid"+gs, gs, this, SLOT(setGrid()), QList<QKeySequence>());
2651 a->setProperty("grid", gs);
2652 }
2653 actionCustom=configManager->newManagedAction(menuroot,menuGrid, "gridCustom", tr("Custom..."), this, SLOT(setGrid()), QList<QKeySequence>());
2654 actionCustom->setProperty("grid","xx");
2655 menuGrid->addSeparator();
2656 actionSinglePageStep=configManager->newManagedAction(menuroot,menuGrid, "singlePageStep", tr("Single Page Step"), pdfWidget, SLOT(setSinglePageStep(bool)), QList<QKeySequence>());
2657 menuWindow->addAction(menuShow->menuAction());
2658 #if (QT_VERSION > 0x050a00) && (defined(Q_OS_MAC))
2659 actionCloseElement=configManager->newManagedAction(menuroot,menuWindow, "closeElement", tr("&Close something"), this, SLOT(closeElement()), QList<QKeySequence>()); // osx work around
2660 #else
2661 actionCloseElement=configManager->newManagedAction(menuroot,menuWindow, "closeElement", tr("&Close something"), this, SLOT(closeElement()), QList<QKeySequence>()<<Qt::Key_Escape);
2662 #endif
2663 menuWindow->addSeparator();
2664 actionSide_by_Side=configManager->newManagedAction(menuroot,menuWindow, "stack", tr("Stac&k"), this, SLOT(stackWindows()), QList<QKeySequence>());
2665 actionTile=configManager->newManagedAction(menuroot,menuWindow, "tile", tr("&Tile"), this, SLOT(tileWindows()), QList<QKeySequence>());
2666 actionSide_by_Side=configManager->newManagedAction(menuroot,menuWindow, "sideBySide", tr("&Side by Side"), this, SLOT(sideBySide()), QList<QKeySequence>());
2667 menuWindow->addSeparator();
2668 actionGo_to_Source=configManager->newManagedAction(menuroot,menuWindow, "gotoSource", tr("&Go to Source"), this, SLOT(goToSource()), QList<QKeySequence>()<<QKeySequence(Qt::ControlModifier|Qt::Key_Apostrophe));
2669 actionFocus_Editor=configManager->newManagedAction(menuroot,menuWindow, "focusEditor", tr("Focus Editor"), this, SIGNAL(focusEditor()), QList<QKeySequence>()<<QKeySequence(Qt::ControlModifier|Qt::AltModifier|Qt::Key_Left));
2670 menuWindow->addSeparator();
2671 actionNew_Window=configManager->newManagedAction(menuroot,menuWindow, "newWindow", tr("New Window"), this, SIGNAL(triggeredClone()), QList<QKeySequence>());
2672 actionFind=configManager->newManagedAction(menuroot,menuEdit_2, "find", tr("&Find"), this, SLOT(doFindDialog()), QList<QKeySequence>()<< QKeySequence(Qt::ControlModifier | Qt::Key_F));
2673 actionFind_Again=configManager->newManagedAction(menuroot,menuEdit_2, "findAgain", tr("Find &again"), this, SLOT(doFindAgain()), QList<QKeySequence>()<< QKeySequence(Qt::ControlModifier | Qt::Key_M)<< Qt::Key_F3);
2674 menuEdit_2->addSeparator();
2675 actionTypeset=configManager->newManagedAction(menuroot,menuEdit_2, "build", tr("Quick Build"), this, SLOT(runQuickBuild()), QList<QKeySequence>()<< QKeySequence(Qt::ControlModifier | Qt::Key_T),"build");
2676
2677 configManager->modifyManagedShortcuts("pdf");
2678 }
2679
2680 /*!
2681 * \brief the shortcuts will only be triggered if this widget has focus (used in embedded mode)
2682 * \param actions
2683 */
2684 void PDFDocument::shortcutOnlyIfFocused(const QList<QAction *> &actions)
2685 {
2686 foreach (QAction *act, actions) {
2687 act->setParent(this);
2688 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
2689 }
2690 }
2691 /*!
2692 * \brief reload settings
2693 */
2694 void PDFDocument::reloadSettings()
2695 {
2696 if (embeddedMode) setAutoHideToolbars(globalConfig->autoHideToolbars);
2697 }
2698 /*!
2699 * \brief initialize PDF window/widget
2700 * \param embedded when used embedded, adapt settings. E.g. no menu , etc.
2701 */
2702 void PDFDocument::init(bool embedded)
2703 {
2704 ConfigManagerInterface *conf = ConfigManagerInterface::getInstance();
2705
2706 docList.append(this);
2707
2708 menuShow = new QMenu(this);
2709 menuShow->setObjectName(QString::fromUtf8("menuShow"));
2710 menuShow->setTitle(QApplication::translate("PDFDocument", "Show"));
2711
2712 pdfWidget = new PDFWidget(embedded); // needs to be initialized before setup menu
2713 pdfWidget->setPDFDocument(this);
2714
2715 //if (!embedded)
2716 setupMenus(embedded);
2717
2718 setupToolBar();
2719
2720
2721 setAttribute(Qt::WA_DeleteOnClose, true);
2722 #if QT_VERSION_MAJOR<6
2723 setAttribute(Qt::WA_MacNoClickThrough, true);
2724 #endif
2725
2726 //load icons
2727 setWindowIcon(QIcon(":/images/previewicon.png"));
2728
2729 QIcon icon = getRealIcon("syncSource-off");
2730 icon.addFile(getRealIconFile("syncSource"), QSize(), QIcon::Normal, QIcon::On);
2731 actionCursor_follows_scrolling->setIcon(icon);
2732 icon = getRealIcon("syncViewer-off");
2733 icon.addFile(getRealIconFile("syncViewer"), QSize(), QIcon::Normal, QIcon::On);
2734 actionScrolling_follows_cursor->setIcon(icon);
2735
2736 if (embedded) {
2737 actionToggleEmbedded->setIcon(getRealIcon("windowed-viewer"));
2738 actionToggleEmbedded->setToolTip(tr("Windowed Viewer"));
2739 } else {
2740 actionToggleEmbedded->setIcon(getRealIcon("embedded-viewer"));
2741 actionToggleEmbedded->setToolTip(tr("Embedded Viewer"));
2742 }
2743
2744 actionExternalViewer->setIcon(getRealIcon("acroread"));
2745 if (embedded) {
2746 actionTypeset->setVisible(false);
2747 actionShrinkViewer->setVisible(false);
2748 } else {
2749 actionClose->setVisible(false);
2750 actionAutoHideToolbars->setVisible(false);
2751 actionEnlargeViewer->setVisible(false);
2752 actionShrinkViewer->setVisible(false);
2753 }
2754
2755 setContextMenuPolicy(Qt::NoContextMenu);
2756
2757 toolButtonGroup = new QButtonGroup(toolBar);
2758 toolButtonGroup->addButton(qobject_cast<QAbstractButton *>(toolBar->widgetForAction(actionMagnify)), kMagnifier);
2759 toolButtonGroup->addButton(qobject_cast<QAbstractButton *>(toolBar->widgetForAction(actionScroll)), kScroll);
2760 // toolButtonGroup->addButton(qobject_cast<QAbstractButton*>(toolBar->widgetForAction(actionSelect_Text)), kSelectText);
2761 // toolButtonGroup->addButton(qobject_cast<QAbstractButton*>(toolBar->widgetForAction(actionSelect_Image)), kSelectImage);
2762 #if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
2763 connect(toolButtonGroup, SIGNAL(idClicked(int)), pdfWidget, SLOT(setTool(int)));
2764 #else
2765 connect(toolButtonGroup, SIGNAL(buttonClicked(int)), pdfWidget, SLOT(setTool(int)));
2766 #endif
2767 conf->registerOption("Preview/EditTool", &globalConfig->editTool, kMagnifier);
2768 QAbstractButton *bt = toolButtonGroup->button(globalConfig->editTool);
2769 if (bt) bt->setChecked(true);
2770 pdfWidget->setTool(globalConfig->editTool);
2771
2772 comboZoom = nullptr;
2773
2774 // adapt icon size to dpi
2775
2776 double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
2777 double scale=dpi/96;
2778
2779 int sz = qRound(qMax(16, ConfigManager::getInstance()->getOption("GUI/SecondaryToobarIconSize").toInt())*scale);
2780
2781 toolBar->setIconSize(QSize(sz, sz));
2782 QWidget *spacer = new QWidget(toolBar);
2783 spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
2784 toolBar->insertWidget(actionAutoHideToolbars, spacer);
2785 addAction(toolBar->toggleViewAction());
2786
2787 leCurrentPage = new QLineEdit(toolBar);
2788 leCurrentPage->setMaxLength(5);
2789 leCurrentPage->setFixedWidth(UtilsUi::getFmWidth(fontMetrics(), "#####"));
2790 leCurrentPageValidator = new QIntValidator(1, 99999, leCurrentPage);
2791 leCurrentPage->setValidator(leCurrentPageValidator);
2792 leCurrentPage->setText("1");
2793 leCurrentPage->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
2794 // TODO: hack to adjust color of the line edit for modern style. Should be done properly in style itself (manhattanstyle.cpp)
2795 int modernStyle = ConfigManager::getInstance()->getOption("GUI/Style").toInt();
2796 if (modernStyle) {
2797 leCurrentPage->setStyleSheet("QLineEdit{ color: white; padding-top: -1px; margin-right: 2px; }");
2798 } else {
2799 leCurrentPage->setStyleSheet("QLineEdit{ padding-top: -1px; margin-right: 2px; }");
2800 }
2801 connect(leCurrentPage, SIGNAL(returnPressed()), this, SLOT(jumpToPage()));
2802 pageCountLabel = new QLabel("1", toolBar);
2803 pageCountLabel->setAlignment(Qt::AlignCenter);
2804 pageCountSeparator = new QLabel(tr("of", "separator for page number: 1 of 3"), toolBar);
2805 pageCountSeparator->setAlignment(Qt::AlignCenter);
2806 toolBar->insertWidget(actionNext_Page, leCurrentPage);
2807 toolBar->insertWidget(actionNext_Page, pageCountSeparator);
2808 toolBar->insertWidget(actionNext_Page, pageCountLabel);
2809 connect(toolBar, SIGNAL(orientationChanged(Qt::Orientation)), this, SLOT(updateToolBarForOrientation(Qt::Orientation)));
2810 updateToolBarForOrientation(toolBar->orientation());
2811
2812 QToolButton *tbCursorFollowsScrolling = UtilsUi::createToolButtonForAction(actionCursor_follows_scrolling);
2813 statusBar()->addPermanentWidget(tbCursorFollowsScrolling);
2814 QToolButton *tbScrollingFollowsCursor = UtilsUi::createToolButtonForAction(actionScrolling_follows_cursor);
2815 statusBar()->addPermanentWidget(tbScrollingFollowsCursor);
2816
2817 QLabel *lbMessage = new QLabel();
2818 lbMessage->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
2819 connect(statusBar(), SIGNAL(messageChanged(QString)), lbMessage, SLOT(setText(QString)));
2820 statusBar()->addPermanentWidget(lbMessage, 1);
2821
2822
2823 pageLabel = new QLabel(statusBar());
2824 statusBar()->addPermanentWidget(pageLabel);
2825
2826 scaleButton = new QToolButton(toolBar);
2827 scaleButton->setToolTip(tr("Scale"));
2828 scaleButton->setPopupMode(QToolButton::InstantPopup);
2829 scaleButton->setAutoRaise(true);
2830 scaleButton->setMinimumWidth(UtilsUi::getFmWidth(statusBar()->fontMetrics(), "OOOOOO"));
2831 scaleButton->setText("100%");
2832 statusBar()->addPermanentWidget(scaleButton);
2833 QList<int> levels = QList<int>() << 25 << 50 << 75 << 100 << 150 << 200 << 300 << 400;
2834 QActionGroup *scaleActions = new QActionGroup(scaleButton);
2835 foreach (int level, levels) {
2836 QAction *act = new QAction(scaleActions);
2837 act->setText(QString("%1%").arg(level));
2838 act->setData(QVariant(level));
2839 connect(act, SIGNAL(triggered()), this, SLOT(zoomFromAction()));
2840 }
2841 scaleButton->addActions(scaleActions->actions());
2842
2843 QToolButton *buttonZoomOut = new QToolButton(statusBar());
2844 buttonZoomOut->setIcon(getRealIcon("zoom-out"));
2845 buttonZoomOut->setToolTip(tr("Zoom Out"));
2846 statusBar()->addPermanentWidget(buttonZoomOut);
2847 connect(buttonZoomOut, SIGNAL(clicked(bool)), actionZoom_Out, SLOT(trigger()));
2848
2849 zoomSlider = new QSlider(toolBar);
2850 zoomSlider->setOrientation(Qt::Horizontal);
2851 zoomSlider->setSingleStep(25);
2852 zoomSlider->setMinimum(-100);
2853 zoomSlider->setMaximum(100);
2854 zoomSlider->setFixedWidth(100);
2855 zoomSlider->setToolTip(tr("Zoom"));
2856 //zoomSlider->setTickInterval(100);
2857 //zoomSlider->setTickPosition(QSlider::TicksBelow);
2858 statusBar()->addPermanentWidget(zoomSlider);
2859
2860 connect(zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(zoomSliderChange(int)));
2861
2862 QToolButton *buttonZoomIn = new QToolButton(statusBar());
2863 buttonZoomIn->setIcon(getRealIcon("zoom-in"));
2864 buttonZoomIn->setToolTip(tr("Zoom In"));
2865 statusBar()->addPermanentWidget(buttonZoomIn);
2866 connect(buttonZoomIn, SIGNAL(clicked()), actionZoom_In, SLOT(trigger()));
2867
2868 QSplitter *vSplitter = new MiniSplitter(Qt::Vertical);
2869
2870 // TODO: Make Frame around Label and Scroll area
2871 scrollArea = new PDFScrollArea;
2872 scrollArea->setBackgroundRole(QPalette::Dark);
2873 //scrollArea->setAlignment(Qt::AlignCenter);
2874 scrollArea->setPDFWidget(pdfWidget);
2875 if (embedded) {
2876 scrollArea->setFrameStyle(QFrame::NoFrame);
2877 }
2878
2879 QWidget *container = new QWidget;
2880 QVBoxLayout *layout = new QVBoxLayout;
2881 container->setLayout(layout);
2882 messageFrame = new MessageFrame;
2883 layout->addWidget(messageFrame);
2884 layout->addWidget(scrollArea);
2885 layout->setContentsMargins(0, 0, 0, 0);
2886 layout->setSpacing(0);
2887
2888 vSplitter->addWidget(container);
2889
2890 annotationPanel = new TitledPanel();
2891 annotationPanel->setFrameShape(QFrame::NoFrame);
2892 annotationPanel->toggleViewAction()->setText(tr("Annotations"));
2893 annotationPanel->setVisible(globalConfig->annotationPanelVisible);
2894 annotationTable = new PDFAnnotationTableView();
2895 TitledPanelPage *annotationPage = new TitledPanelPage(annotationTable, "pdfannotations", tr("Annotations"));
2896 annotationPanel->appendPage(annotationPage);
2897 vSplitter->setStretchFactor(0, 2);
2898 vSplitter->setStretchFactor(1, 1);
2899 vSplitter->addWidget(annotationPanel);
2900 setCentralWidget(vSplitter);
2901
2902 connect(scrollArea, SIGNAL(resized()), pdfWidget, SLOT(windowResized()));
2903
2904 //connect(actionAbout_TW, SIGNAL(triggered()), SIGNAL(triggeredAbout()));
2905 //connect(actionUserManual, SIGNAL(triggered()), SIGNAL(triggeredManual()));
2906 connect(actionEnlargeViewer, SIGNAL(triggered()), this , SLOT(enlarge()));
2907 connect(actionShrinkViewer, SIGNAL(triggered()), this , SLOT(shrink()));
2908
2909
2910 connect(pdfWidget, SIGNAL(changedPage(int,bool)), this, SLOT(enablePageActions(int,bool)));
2911 connect(actionFit_to_Width, SIGNAL(triggered(bool)), pdfWidget, SLOT(fitWidth(bool)));
2912 connect(actionFit_to_Text_Width, SIGNAL(triggered(bool)), pdfWidget, SLOT(fitTextWidth(bool)));
2913 connect(actionFit_to_Window, SIGNAL(triggered(bool)), pdfWidget, SLOT(fitWindow(bool)));
2914
2915
2916 if (!embedded) {
2917 conf->registerOption("Preview/GridX", &globalConfig->gridx, 1);
2918 conf->registerOption("Preview/GridY", &globalConfig->gridy, 1);
2919 pdfWidget->setGridSize(globalConfig->gridx, globalConfig->gridy, true);
2920
2921 //connect(actionSinglePageStep, SIGNAL(toggled(bool)), pdfWidget, SLOT(setSinglePageStep(bool)));
2922 conf->registerOption("Preview/Single Page Step", &globalConfig->singlepagestep, true);
2923 conf->linkOptionToObject(&globalConfig->singlepagestep, actionSinglePageStep, LO_NONE);
2924 connect(actionContinuous, SIGNAL(toggled(bool)), scrollArea, SLOT(setContinuous(bool)));
2925 conf->registerOption("Preview/Continuous", &globalConfig->continuous, true);
2926 conf->linkOptionToObject(&globalConfig->continuous, actionContinuous, LO_NONE);
2927 } else {
2928 pdfWidget->setGridSize(1, 1, true);
2929 pdfWidget->setSinglePageStep(true);
2930 scrollArea->setContinuous(true);
2931 }
2932
2933 //connect(actionZoom_In, SIGNAL(triggered()), pdfWidget, SLOT(zoomIn()));
2934 //connect(actionZoom_Out, SIGNAL(triggered()), pdfWidget, SLOT(zoomOut()));
2935 //connect(actionFull_Screen, SIGNAL(triggered(bool)), this, SLOT(toggleFullScreen(bool)));
2936 //connect(actionPresentation, SIGNAL(triggered(bool)), this, SLOT(toggleFullScreen(bool)));
2937 connect(pdfWidget, SIGNAL(changedZoom(qreal)), this, SLOT(enableZoomActions(qreal)));
2938 connect(pdfWidget, SIGNAL(changedScaleOption(autoScaleOption)), this, SLOT(adjustScaleActions(autoScaleOption)));
2939 connect(pdfWidget, SIGNAL(syncClick(int,const QPointF&,bool)), this, SLOT(syncClick(int,const QPointF&,bool)));
2940
2941 if (actionZoom_In->shortcut() == QKeySequence("Ctrl++"))
2942 new QShortcut(QKeySequence("Ctrl+="), pdfWidget, SLOT(zoomIn()), Q_NULLPTR, Qt::WidgetShortcut);
2943 if (!actionActual_Size->shortcut().isEmpty())
2944 new QShortcut(QKeySequence("Ctrl+0"), pdfWidget, SLOT(fixedScale()), Q_NULLPTR, Qt::WidgetShortcut);
2945
2946
2947 conf->registerOption("Preview/Scrolling Follows Cursor", &globalConfig->followFromCursor, false);
2948 conf->linkOptionToObject(&globalConfig->followFromCursor, actionScrolling_follows_cursor);
2949 conf->registerOption("Preview/Cursor Follows Scrolling", &globalConfig->followFromScroll, false);
2950 conf->linkOptionToObject(&globalConfig->followFromScroll, actionCursor_follows_scrolling);
2951 conf->registerOption("Preview/Sync Multiple Views", &globalConfig->syncViews, true);
2952 conf->linkOptionToObject(&globalConfig->syncViews, actionSynchronize_multiple_views);
2953 conf->registerOption("Preview/Invert Colors", &globalConfig->invertColors, false);
2954 conf->linkOptionToObject(&globalConfig->invertColors, actionInvertColors);
2955 //connect(actionInvertColors, SIGNAL(triggered()), pdfWidget, SLOT(update()));
2956 conf->registerOption("Preview/Grayscale", &globalConfig->grayscale, false);
2957 conf->linkOptionToObject(&globalConfig->grayscale, actionGrayscale);
2958 //connect(actionGrayscale, SIGNAL(triggered()), pdfWidget, SLOT(update()));
2959
2960 if(!embedded){
2961 menuShow->addAction(toolBar->toggleViewAction());
2962 menuShow->addSeparator();
2963 }
2964
2965 menuShow->addAction(annotationPanel->toggleViewAction());
2966
2967 QDockWidget *dw = dwOutline = new PDFOutlineDock(this);
2968 if (embedded)
2969 dw->hide();
2970 addDockWidget(Qt::LeftDockWidgetArea, dw);
2971 menuShow->addAction(dw->toggleViewAction());
2972 connect(this, SIGNAL(documentLoaded()), dw, SLOT(documentLoaded()));
2973 connect(this, SIGNAL(documentClosed()), dw, SLOT(documentClosed()));
2974 connect(pdfWidget, SIGNAL(changedPage(int,bool)), dw, SLOT(pageChanged(int)));
2975
2976 dw = dwInfo = new PDFInfoDock(this);
2977 dw->hide();
2978 addDockWidget(Qt::LeftDockWidgetArea, dw);
2979 menuShow->addAction(dw->toggleViewAction());
2980 connect(this, SIGNAL(documentLoaded()), dw, SLOT(documentLoaded()));
2981 connect(this, SIGNAL(documentClosed()), dw, SLOT(documentClosed()));
2982 connect(pdfWidget, SIGNAL(changedPage(int,bool)), dw, SLOT(pageChanged(int)));
2983
2984 dw = dwSearch = new PDFSearchDock(this);
2985 if (embedded)
2986 dw->hide();
2987 connect(dwSearch, SIGNAL(search(bool,bool)), SLOT(search(bool,bool)));
2988 connect(dwSearch, SIGNAL(visibilityChanged(bool)), SLOT(clearHightlight(bool)));
2989 addDockWidget(Qt::BottomDockWidgetArea, dw);
2990 menuShow->addAction(dw->toggleViewAction());
2991
2992 dw = dwFonts = new PDFFontsDock(this);
2993 dw->hide();
2994 addDockWidget(Qt::BottomDockWidgetArea, dw);
2995 menuShow->addAction(dw->toggleViewAction());
2996 connect(this, SIGNAL(documentLoaded()), dw, SLOT(documentLoaded()));
2997 connect(this, SIGNAL(documentClosed()), dw, SLOT(documentClosed()));
2998 connect(pdfWidget, SIGNAL(changedPage(int, bool)), dw, SLOT(pageChanged(int)));
2999
3000 dw = dwOverview = new PDFOverviewDock(this);
3001 dw->hide();
3002 addDockWidget(Qt::LeftDockWidgetArea, dw);
3003 menuShow->addAction(dw->toggleViewAction());
3004 connect(this, SIGNAL(documentLoaded()), dw, SLOT(documentLoaded()));
3005 connect(this, SIGNAL(documentClosed()), dw, SLOT(documentClosed()));
3006 connect(pdfWidget, SIGNAL(changedPage(int, bool)), dw, SLOT(pageChanged(int)));
3007
3008 dw = dwClock = new PDFClockDock(this);
3009 dw->hide();
3010 addDockWidget(Qt::BottomDockWidgetArea, dw);
3011 menuShow->addAction(dw->toggleViewAction());
3012 connect(pdfWidget, SIGNAL(changedPage(int,bool)), dw, SLOT(pageChanged(int)));
3013 connect(pdfWidget, SIGNAL(changedPage(int,bool)), dw, SLOT(update()));
3014
3015 actionPage_Down = new QAction(tr("Page Down"), this);
3016 actionPage_Down->setShortcut(QKeySequence::MoveToNextPage);
3017 addAction(actionPage_Down);
3018 connect(actionPage_Down, SIGNAL(triggered()), pdfWidget, SLOT(pageDownOrNext()));
3019 actionPage_Up = new QAction(tr("Page Up"), this);
3020 actionPage_Up->setShortcut(QKeySequence::MoveToPreviousPage);
3021 connect(actionPage_Up, SIGNAL(triggered()), pdfWidget, SLOT(pageUpOrPrev()));
3022 addAction(actionPage_Up);
3023 //disable all action shortcuts when embedded
3024 if (embedded) {
3025 shortcutOnlyIfFocused(QList<QAction *>()
3026 << actionNext_Page
3027 << actionPrevious_Page
3028 << actionLast_Page
3029 << actionFirst_Page
3030 << actionForward
3031 << actionBack
3032 << actionPage_Down
3033 << actionPage_Up
3034 << actionGo_to_Page
3035 << actionZoom_In
3036 << actionZoom_Out
3037 << actionFit_to_Window
3038 << actionActual_Size
3039 << actionFit_to_Width
3040 << actionFit_to_Text_Width
3041 << actionClose
3042 << actionGo_to_Source
3043 << actionFind
3044 << actionFind_Again
3045 );
3046 /*actionNew->setShortcut(QKeySequence());
3047 actionOpen->setShortcut(QKeySequence());
3048 actionTypeset->setShortcut(QKeySequence());
3049 actionNew_from_Template->setShortcut(QKeySequence());
3050 actionFull_Screen->setShortcut(QKeySequence());
3051 actionQuit_TeXworks->setShortcut(QKeySequence());
3052 actionCloseElement->setShortcut(QKeySequence());
3053 actionPresentation->setShortcut(QKeySequence());
3054 actionFileOpen->setShortcut(QKeySequence());*/
3055 }
3056 }
3057
3058 bool PDFDocument::followCursor() const
3059 {
3060 Q_ASSERT(globalConfig);
3061 if (!globalConfig) return false;
3062 return globalConfig->followFromCursor;
3063 }
3064
3065 bool PDFDocument::ignoreSynchronization() const
3066 {
3067 return actionNoSynchronization->isChecked();
3068 }
3069
3070 void PDFDocument::changeEvent(QEvent *event)
3071 {
3072 if (event->type() == QEvent::LanguageChange) {
3073 QString title = windowTitle();
3074 //retranslateUi(this);
3075 setWindowTitle(title);
3076 if (pdfWidget && pdfWidget->isVisible())
3077 pdfWidget->updateStatusBar();
3078 } else
3079 QMainWindow::changeEvent(event);
3080 }
3081
3082 void PDFDocument::sideBySide()
3083 {
3084 QWidget *mainWindow = nullptr;
3085 foreach (QWidget *widget, QApplication::topLevelWidgets())
3086 if (!widget->isHidden() && widget != this && qobject_cast<QMainWindow *>(widget) != nullptr) {
3087 mainWindow = widget;
3088 break;
3089 }
3090 if (mainWindow) {
3091 windowsSideBySide(mainWindow, this);
3092 windowsSideBySide(mainWindow, this); //???first call fails on linux with qt4.6.3???? (position is correct, but move doesn't move there, although it works in texworks)
3093 }
3094 }
3095
3096 void PDFDocument::closeEvent(QCloseEvent *event)
3097 {
3098 /*
3099 * Qt is buggy because it only restores the parent cursor shape if PDFWidget is the widget receiving the last mouse event
3100 * (qt_last_mouse_receiver). If we close the PDFWidget by pressing ESC while the zoom tool is the last mouse receiver,
3101 * the shape of the cursor will remain unchanged (magnifying glass, if we just closed the magnifier or blank if we closed
3102 * while using the magnifier). That is why we unset the PDFWidget's cursor, forcing Qt to restore the parent cursor shape.
3103 */
3104 if (pdfWidget) {
3105 pdfWidget->unsetCursor();
3106 }
3107 Q_ASSERT(globalConfig);
3108 if (isVisible() && !embeddedMode) {
3109 saveGeometryToConfig();
3110 }
3111 event->accept();
3112 deleteLater();
3113 }
3114
3115 void PDFDocument::syncFromView(const QString &pdfFile, const QFileInfo &masterFile, int page)
3116 {
3117 if (!actionSynchronize_multiple_views->isChecked() || ignoreSynchronization())
3118 return;
3119 if (pdfFile != curFile || this->masterFile != masterFile)
3120 loadFile(pdfFile, masterFile, NoDisplayFlags);
3121 if (page != widget()->getPageIndex())
3122 scrollArea->goToPage(page, false);
3123 }
3124
3125 /*!
3126 * \brief PDFDocument::loadFile
3127 * \param fileName the file to load
3128 * \param masterFile tex corresponding .tex source file. If not given, assume it's identical to fileName but
3129 * with extension .tex
3130 * \param displayFlags
3131 */
3132 void PDFDocument::loadFile(const QString &fileName, QFileInfo masterFile, PDFDocument::DisplayFlags displayFlags)
3133 {
3134 if (masterFile.fileName().isEmpty()) {
3135 masterFile.setFile(replaceFileExtension(fileName, ".tex"));
3136 }
3137
3138 // check if the file is already loaded
3139 bool fileAlreadyLoaded = (this->masterFile == masterFile);
3140 fileAlreadyLoaded = fileAlreadyLoaded && (curFileUnnormalized == fileName);
3141 if (fileAlreadyLoaded) {
3142 // check size and modifcation date
3143 QFileInfo fi(curFile);
3144 QDateTime lastModified = fi.lastModified();
3145 qint64 filesize = fi.size();
3146 fileAlreadyLoaded = fileAlreadyLoaded && (lastModified == curFileLastModified);
3147 fileAlreadyLoaded = fileAlreadyLoaded && (filesize == curFileSize);
3148 fileAlreadyLoaded = fileAlreadyLoaded && fi.exists();
3149 }
3150 if (!fileAlreadyLoaded) {
3151 this->masterFile = masterFile;
3152 setCurrentFile(fileName);
3153 loadCurrentFile(false);
3154 }
3155
3156 if (watcher) {
3157 const QStringList files = watcher->files();
3158 if (!files.isEmpty())
3159 watcher->removePaths(files); // in case we ever load different files into the same widget
3160 if (curFile != "")
3161 watcher->addPath(curFile);
3162 }
3163 updateDisplayState(displayFlags);
3164 }
3165
3166 void PDFDocument::fillRenderCache(int pg)
3167 {
3168 if (renderManager)
3169 renderManager->fillCache(pg);
3170 }
3171
3172 void PDFDocument::loadCurrentFile(bool fillCache)
3173 {
3174 if (reloadTimer) reloadTimer->stop();
3175 messageFrame->hide();
3176 QAction *act = qobject_cast<QAction *>(sender());
3177 if (act) {
3178 QVariant fc = act->property("fillCache");
3179 if (fc.isValid())
3180 fillCache = fc.toBool();
3181 }
3182
3183 static bool isReloading = false;
3184 if (isReloading) return;
3185 isReloading = true;
3186 QApplication::setOverrideCursor(Qt::WaitCursor);
3187
3188 scanner.clear();
3189
3190 QString password;
3191
3192 retryNow:
3193
3194 if (renderManager) {
3195 renderManager->stopRendering();
3196 renderManager->deleteLater();
3197 renderManager = nullptr;
3198 }
3199
3200 renderManager = new PDFRenderManager(this,globalConfig->limitThreadNumber);
3201 renderManager->setCacheSize(globalConfig->cacheSizeMB);
3202 renderManager->setLoadStrategy(int(globalConfig->loadStrategy));
3203 PDFRenderManager::Error error = PDFRenderManager::NoError;
3204 QFileInfo fi(curFile);
3205 QDateTime lastModified = fi.lastModified();
3206 qint64 filesize = fi.size();
3207 document = renderManager->loadDocument(curFile, error, password);
3208 if (error == PDFRenderManager::FileIncomplete) {
3209 QAction *retryAction = new QAction(tr("Retry"), this);
3210 retryAction->setProperty("fillCache", fillCache);
3211 connect(retryAction, SIGNAL(triggered()), this, SLOT(loadCurrentFile()));
3212 QAction *closeAction = new QAction(tr("Close"), this);
3213 connect(closeAction, SIGNAL(triggered()), this, SLOT(stopReloadTimer()));
3214 connect(closeAction, SIGNAL(triggered()), messageFrame, SLOT(hide()));
3215 messageFrame->showText(tr("%1\ndoes not look like a valid PDF document. Either the file is corrupt or it is in the process of creation. Retrying every two seconds.").arg(curFile),
3216 QList<QAction *>() << retryAction << closeAction);
3217 }
3218
3219 curFileSize = filesize; // store size and modification time to check whether reloaded file has been changed meanwhile
3220 curFileLastModified = lastModified;
3221
3222 if (document.isNull()) {
3223 delete renderManager;
3224 renderManager = nullptr;
3225 switch (error) {
3226 case PDFRenderManager::NoError:
3227 break;
3228 case PDFRenderManager::FileOpenFailed:
3229 statusBar()->showMessage(tr("Failed to find file \"%1\"; perhaps it has been deleted.").arg(curFileUnnormalized));
3230 break;
3231 case PDFRenderManager::PopplerError:
3232 statusBar()->showMessage(tr("Failed to load file \"%1\"; perhaps it is not a valid PDF document.").arg(curFile));
3233 break;
3234 case PDFRenderManager::PopplerErrorBadAlloc:
3235 statusBar()->showMessage(tr("Failed to load file \"%1\" due to a bad alloc; perhaps it is not a valid PDF document.").arg(curFile));
3236 break;
3237 case PDFRenderManager::PopplerErrorException:
3238 statusBar()->showMessage(tr("Failed to load file \"%1\" due to an exception; perhaps it is not a valid PDF document.").arg(curFile));
3239 break;
3240 case PDFRenderManager::FileLocked: {
3241 statusBar()->showMessage(tr("PDF file \"%1\" is locked.").arg(curFile));
3242 bool ok;
3243 password = QInputDialog::getText(nullptr, tr("PDF password"), tr("PDF file \"%1\" is locked.\nYou can now enter the password:").arg(curFile), QLineEdit::Password, password, &ok );
3244 if (ok) goto retryNow;
3245 break;
3246 }
3247 case PDFRenderManager::FileIncomplete:
3248 break; // message is handled via messageFrame
3249 }
3250 pdfWidget->hide();
3251 pdfWidget->setDocument(document);
3252 if (error == PDFRenderManager::FileIncomplete)
3253 reloadWhenIdle();
3254 } else {
3255 pdfWidget->setDocument(document);
3256 pdfWidget->show();
3257
3258 annotations = new PDFAnnotations(this);
3259 annotationTable->setModel(annotations->createModel());
3260 annotationTable->resizeColumnsToContents();
3261 annotationTable->resizeRowsToContents();
3262 connect(annotationTable, SIGNAL(annotationClicked(const PDFAnnotation *)), SLOT(gotoAnnotation(const PDFAnnotation *)));
3263 connect(annotationTable, SIGNAL(annotationDoubleClicked(const PDFAnnotation *)), pdfWidget, SLOT(openAnnotationDialog(const PDFAnnotation *)));
3264
3265 if (!embeddedMode)
3266 pdfWidget->setFocus();
3267
3268 // set page viewer only once
3269 int maxDigits = 1 + qFloor(log10(pdfWidget->realNumPages()));
3270 //if (maxDigits < 2) maxDigits = 2;
3271 leCurrentPage->setMaxLength(maxDigits);
3272 leCurrentPage->setFixedWidth(UtilsUi::getFmWidth(leCurrentPage->fontMetrics(), QString(maxDigits + 1, '#')));
3273 leCurrentPageValidator->setTop(pdfWidget->realNumPages());
3274 //qDebug() << pdfWidget->realNumPages() << maxDigits << UtilsUi::getFmWidth(leCurrentPage->fontMetrics(), QString(maxDigits + 1, '#'))<<QString(maxDigits + 1, '#');
3275
3276 loadSyncData();
3277 if (fillCache) {
3278 renderManager->fillCache();
3279 }
3280 scrollArea->updateScrollBars();
3281
3282 emit documentLoaded();
3283 }
3284 for (int i = 0; i < password.length(); i++)
3285 password[i] = '\0';
3286
3287 QApplication::restoreOverrideCursor();
3288 isReloading = false;
3289 }
3290
3291 void PDFDocument::stopReloadTimer()
3292 {
3293 if (reloadTimer)
3294 reloadTimer->stop();
3295 }
3296
3297 void PDFDocument::reloadWhenIdle()
3298 {
3299 if (reloadTimer)
3300 reloadTimer->stop();
3301 else {
3302 reloadTimer = new QTimer(this);
3303 reloadTimer->setSingleShot(true);
3304 reloadTimer->setInterval(500);
3305 connect(reloadTimer, SIGNAL(timeout()), this, SLOT(idleReload()));
3306 }
3307 reloadTimer->start();
3308 }
3309
3310 void PDFDocument::idleReload()
3311 {
3312 if (isCompiling) reloadWhenIdle();
3313 else loadCurrentFile();
3314 }
3315
3316 void PDFDocument::runExternalViewer()
3317 {
3318 emit runCommand("txs:///view-pdf-external", masterFile, QFileInfo(lastSyncPoint.filename), lastSyncPoint.line);
3319 }
3320
3321 void PDFDocument::runInternalViewer()
3322 {
3323 emit runCommand("txs:///view-pdf-internal --windowed --close-embedded", masterFile, QFileInfo(lastSyncPoint.filename), lastSyncPoint.line);
3324 }
3325
3326 void PDFDocument::toggleEmbedded()
3327 {
3328 if (embeddedMode)
3329 emit runCommand("txs:///view-pdf-internal --windowed --close-embedded", masterFile, QFileInfo(lastSyncPoint.filename), lastSyncPoint.line);
3330 else
3331 emit runCommand("txs:///view-pdf-internal --embedded --close-windowed", masterFile, QFileInfo(lastSyncPoint.filename), lastSyncPoint.line);
3332 }
3333
3334 void PDFDocument::toggleAutoHideToolbars()
3335 {
3336 QAction *act = qobject_cast<QAction *>(sender());
3337 if (act) {
3338 globalConfig->autoHideToolbars = act->isChecked();
3339 reloadSettings();
3340 }
3341 }
3342
3343 void PDFDocument::runQuickBuild()
3344 {
3345 emit runCommand("txs:///quick", masterFile, QFileInfo(lastSyncPoint.filename), lastSyncPoint.line);
3346 }
3347
3348 void PDFDocument::setGrid()
3349 {
3350 REQUIRE(pdfWidget && sender());
3351 QString gs = sender()->property("grid").toString();
3352 if (gs == "xx") {
3353 UniversalInputDialog d;
3354 int x = 1, y = 1;
3355 d.addVariable(&x , "X-Grid:");
3356 d.addVariable(&y , "Y-Grid:");
3357 if (d.exec()) {
3358 pdfWidget->setGridSize(x, y);
3359 globalConfig->gridx = x;
3360 globalConfig->gridy = y;
3361 }
3362 } else {
3363 int p = gs.indexOf("x");
3364 globalConfig->gridx = gs.left(p).toInt();
3365 globalConfig->gridy = gs.mid(p + 1).toInt();
3366 pdfWidget->setGridSize(globalConfig->gridx, globalConfig->gridy);
3367 }
3368 pdfWidget->windowResized();
3369 }
3370
3371 void PDFDocument::jumpToPage()
3372 {
3373 int index = leCurrentPage->text().toInt();
3374 //scrollArea->goToPage(index-1);
3375 goToPage(index - 1);
3376 }
3377
3378 void PDFDocument::shrink()
3379 {
3380 setStateEnlarged(false);
3381 emit triggeredShrink();
3382 }
3383
3384 void PDFDocument::enlarge()
3385 {
3386 setStateEnlarged(true);
3387 emit triggeredEnlarge();
3388 }
3389
3390 void PDFDocument::setStateEnlarged(bool state)
3391 {
3392 actionEnlargeViewer->setVisible(!state);
3393 actionShrinkViewer->setVisible(state);
3394 }
3395
3396 /*!
3397 * \return true if the document itself is closed, otherwise false
3398 */
3399 bool PDFDocument::closeElement()
3400 {
3401 ConfigManager *configManager=dynamic_cast<ConfigManager *>(ConfigManager::getInstance());
3402 if(!configManager) return false;
3403
3404 if (actionPresentation->isChecked()) {
3405 //restore state of docks
3406 if (dwVisSearch)
3407 dwSearch->show();
3408 if (dwVisFonts)
3409 dwFonts->show();
3410 if (dwVisInfo)
3411 dwInfo->show();
3412 if (dwVisOutline)
3413 dwOutline->show();
3414 if (dwVisOverview)
3415 dwOverview->show();
3416 toggleFullScreen(false);
3417 } else if (configManager->useEscForClosingFullscreen && actionFull_Screen->isChecked()) {
3418 toggleFullScreen(false);
3419 } else if (dwFonts && dwFonts->isVisible()) dwFonts->hide();
3420 else if (dwSearch && dwSearch->isVisible()) dwSearch->hide();
3421 else if (dwInfo && dwInfo->isVisible()) dwInfo->hide();
3422 else if (dwClock && dwClock->isVisible()) dwClock->hide();
3423 else if (dwOutline && dwOutline->isVisible()) dwOutline->hide();
3424 else if (dwOverview && dwOverview->isVisible()) dwOverview->hide();
3425 else if (configManager->useEscForClosingEmbeddedViewer && isVisible()) {
3426 // Note: avoid crash on osx where esc key is passed to hidden window
3427 actionClose->trigger();
3428 } else {
3429 return false; // nothing to close
3430 }
3431 return true;
3432 }
3433
3434 void PDFDocument::tileWindows()
3435 {
3436 arrangeWindows(true);
3437 }
3438
3439 void PDFDocument::stackWindows()
3440 {
3441 arrangeWindows(false);
3442 }
3443
3444 void PDFDocument::unminimize()
3445 {
3446 if (isMinimized()) {
3447 if (globalConfig->windowMaximized) {
3448 showMaximized();
3449 } else {
3450 showNormal();
3451 }
3452 } else {
3453 show();
3454 }
3455 }
3456
3457 void PDFDocument::updateDisplayState(PDFDocument::DisplayFlags displayFlags)
3458 {
3459 displayFlags &= (embeddedMode) ? FilterEmbedded : FilterWindowed;
3460 QWidget *activeWindow = QApplication::activeWindow();
3461 unminimize();
3462 if (displayFlags & Raise) {
3463 raise();
3464 } else if (activeWindow) { // unminimize() may have brought the viewer to Front
3465 activeWindow->raise();
3466 }
3467 if (displayFlags & Focus) {
3468 if (embeddedMode) {
3469 setFocus();
3470 } else {
3471 activateWindow();
3472 }
3473 if (scrollArea) scrollArea->setFocus();
3474 } else if (activeWindow) { // unminimize may have made the viewer active
3475 activeWindow->activateWindow();
3476 }
3477
3478 }
3479
3480 void PDFDocument::arrangeWindows(bool tile)
3481 {
3482 foreach (const QScreen *screen, QGuiApplication::screens()) {
3483 QWidgetList windows;
3484 foreach (QWidget *widget, QApplication::topLevelWidgets())
3485 if (!widget->isHidden() && qobject_cast<QMainWindow *>(widget))
3486 windows << widget;
3487 if (windows.size() > 0)
3488 (*(tile ? &tileWindowsInRect : &stackWindowsInRect)) (windows, screen->availableGeometry());
3489 }
3490 }
3491
3492 void PDFDocument::updateToolBarForOrientation(Qt::Orientation orientation)
3493 {
3494 if (orientation == Qt::Horizontal) {
3495 leCurrentPage->setAlignment(Qt::AlignRight);
3496 pageCountSeparator->setText(pageCountSeparator->text() + " ");
3497 } else {
3498 leCurrentPage->setAlignment(Qt::AlignCenter);
3499 pageCountSeparator->setText(pageCountSeparator->text().trimmed());
3500 }
3501 }
3502
3503 void PDFDocument::clearHightlight(bool ){
3504 pdfWidget->setHighlightPath(-1, QPainterPath());
3505 }
3506
3507 void PDFDocument::search(bool backwards, bool incremental)
3508 {
3509 if (!dwSearch) return;
3510 search(dwSearch->getSearchText(), backwards, incremental, dwSearch->hasFlagCaseSensitive(), dwSearch->hasFlagWholeWords(), dwSearch->hasFlagSync());
3511 }
3512 //better use flags for this
3513 void PDFDocument::search(const QString &searchText, bool backwards, bool incremental, bool caseSensitive, bool wholeWords, bool sync)
3514 {
3515 if (document.isNull())
3516 return;
3517
3518 int pageIdx;
3519
3520 Poppler::Page::SearchFlags searchFlags = Poppler::Page::SearchFlags();
3521
3522 Poppler::Page::SearchDirection searchDir; // = Poppler::Page::FromTop;
3523 int deltaPage, firstPage, lastPage;
3524 int run, runs;
3525
3526 if (searchText.isEmpty())
3527 return;
3528
3529 if (!caseSensitive)
3530 searchFlags |= Poppler::Page::IgnoreCase;
3531 if (wholeWords)
3532 searchFlags |= Poppler::Page::WholeWords;
3533
3534 deltaPage = (backwards ? -1 : +1);
3535
3536 // if (!incremental) { //TODO
3537 // lastSearchResult.selRect = QRectF();
3538 // firstSearchPage = pdfWidget->getCurrentPageIndex();
3539 // }
3540 searchDir = (backwards ? Poppler::Page::PreviousResult : Poppler::Page::NextResult);
3541
3542 runs = (/* DISABLES CODE */ (true) ? 2 : 1 ); //true = always wrap around
3543
3544 Q_ASSERT(!backwards || !incremental);
3545 if (incremental) {
3546 //make sure that we find the current match again
3547 lastSearchResult.selRect.setLeft(lastSearchResult.selRect.left() - 0.01);
3548 lastSearchResult.selRect.setRight(lastSearchResult.selRect.left());
3549 }
3550
3551 int startPage = lastSearchResult.pageIdx;
3552 if (lastSearchResult.pageIdx != pdfWidget->getPageIndex()) {
3553 // function to check that lastSearchResult is visible is missing
3554 // quick workaround is that the at least the page is shown, even partially
3555 // visible pages
3556 int visPages=pdfWidget->visiblePages(); // function return too large a number, buggy
3557 if (((pdfWidget->getPageIndex()+visPages-1) < pdfWidget->normalizedPageIndex(lastSearchResult.pageIdx)) || (pdfWidget->getPageIndex() > pdfWidget->normalizedPageIndex(lastSearchResult.pageIdx))) {
3558 startPage = pdfWidget->getPageIndex();
3559 lastSearchResult.selRect = backwards ? QRectF(0, 100000, 1, 1) : QRectF();
3560 }
3561 }
3562
3563 for (run = 0; run < runs; ++run) {
3564 switch (run) {
3565 case 0:
3566 // first run = normal search
3567 lastPage = (backwards ? -1 : pdfWidget->realNumPages());
3568 firstPage = startPage;
3569 break;
3570 case 1:
3571 // second run = after wrap
3572 lastPage = (backwards ? -1 : pdfWidget->realNumPages());
3573 firstPage = (backwards ? pdfWidget->realNumPages() - 1 : 0);
3574 break;
3575 default:
3576 // should not happen
3577 Q_ASSERT(false);
3578 return;
3579 }
3580
3581 for (pageIdx = firstPage; pageIdx != lastPage; pageIdx += deltaPage) {
3582 if (pageIdx < 0 || pageIdx >= pdfWidget->realNumPages())
3583 return;
3584
3585 statusBar()->showMessage(tr("Searching for") + QString(" '%1' (Page %2)").arg(searchText).arg(pageIdx), 1000);
3586
3587 std::unique_ptr<Poppler::Page> page(document->page(pageIdx));
3588 if (!page)
3589 return;
3590
3591 double rectLeft, rectTop, rectRight, rectBottom;
3592 rectLeft = lastSearchResult.selRect.left();
3593 rectTop = lastSearchResult.selRect.top();
3594 rectRight = lastSearchResult.selRect.right();
3595 rectBottom = lastSearchResult.selRect.bottom();
3596 if (page->search(searchText, rectLeft, rectTop, rectRight, rectBottom , searchDir, searchFlags)) {
3597 lastSearchResult.selRect = QRectF(rectLeft, rectTop, rectRight - rectLeft, rectBottom - rectTop);
3598
3599 lastSearchResult.doc = this;
3600 lastSearchResult.pageIdx = pageIdx;
3601 QPainterPath p;
3602 p.addRect(lastSearchResult.selRect);
3603
3604 if (hasSyncData() && sync)
3605 syncClick(pageIdx, lastSearchResult.selRect.center(), false);
3606
3607
3608 pdfWidget->setHighlightPath(lastSearchResult.pageIdx, p,true);
3609 //scroll horizontally
3610 //scrollArea->ensureVisiblePageAbsolutePos(lastSearchResult.pageIdx, lastSearchResult.selRect.topLeft());
3611 pdfWidget->update();
3612 return;
3613 }
3614
3615 lastSearchResult.selRect = backwards ? QRectF(0, 100000, 1, 1) : QRectF();
3616 searchDir = (backwards ? Poppler::Page::PreviousResult : Poppler::Page::NextResult);
3617 }
3618 }
3619 }
3620
3621 void PDFDocument::search()
3622 {
3623 if (!dwSearch) return;
3624 dwSearch->show();
3625 dwSearch->setFocus();
3626 }
3627
3628
3629
3630 QString PDFDocument::debugSyncTeX(const QString &filename)
3631 {
3632 int pos = filename.indexOf(SYNCTEX_EXT);
3633 QString baseName = pos < 0 ? filename : filename.left(pos);
3634 QString pdfFile;
3635 foreach (PDFDocument *pdf, documentList()) {
3636 if (pdf->fileName().startsWith(baseName)) {
3637 pdfFile = pdf->fileName();
3638 break;
3639 }
3640 }
3641 if (pdfFile.isEmpty() && QFile::exists(baseName + ".pdf")) pdfFile = baseName + ".pdf";
3642 else pdfFile = filename;
3643
3644 QSynctex::Scanner sc(pdfFile);
3645 if (!sc.isValid()) return "Failed to load file";
3646
3647 QStringList result;
3648
3649 sc.display();
3650
3651 result.append("Inputs:");
3652 QSynctex::Node node = sc.inputNode();
3653 while (node.isValid()) {
3654 int tag = node.tag();
3655 QString filename = tag >= 0 ? sc.fileName(tag) : "N/A";
3656 result.append(QString("Input:%1:%2").arg(tag).arg(filename));
3657 node = node.sibling();
3658 }
3659
3660 result.append("");
3661 result.append("Sheets:");
3662 node = sc.sheet(1);
3663 while (node.isValid()) {
3664 QSynctex::Node cur = node.child();
3665 int page = cur.page();
3666 QSet<int> inputs;
3667 while (cur.isValid()) {
3668 inputs.insert(cur.tag());
3669 cur = cur.next();
3670 }
3671 node = node.sibling();
3672
3673 QString x = QString("Page:%1:").arg(page);
3674 foreach (int i, inputs)
3675 x.append(QString("%1 ").arg(i));
3676 result.append(x);
3677 }
3678
3679 return result.join("\n");
3680 }
3681
3682 void PDFDocument::gotoAnnotation(const PDFAnnotation *ann)
3683 {
3684 if (!pdfWidget) return;
3685 QPoint topLeft = pdfWidget->mapFromScaledPosition(ann->pageNum(), ann->popplerAnnotation()->boundary().topLeft()) / pdfWidget->totalScaleFactor();
3686 QPoint bottomRight = pdfWidget->mapFromScaledPosition(ann->pageNum(), ann->popplerAnnotation()->boundary().bottomRight() / pdfWidget->totalScaleFactor());
3687 QPainterPath p;
3688 p.addRect(QRect(topLeft, bottomRight));
3689 pdfWidget->setHighlightPath(ann->pageNum(), p);
3690 pdfWidget->update();
3691 }
3692
3693 void PDFDocument::loadSyncData()
3694 {
3695 scanner.load(curFileUnnormalized);
3696 if (!scanner.isValid())
3697 statusBar()->showMessage(tr("No SyncTeX data available"), 3000);
3698 else {
3699 statusBar()->showMessage(tr("SyncTeX: \"%1\"").arg(QDir::toNativeSeparators(scanner.synctexFilename())), 3000);
3700 }
3701 }
3702
3703 void PDFDocument::syncClick(int pageIndex, const QPointF &pos, bool activate)
3704 {
3705 if (!scanner.isValid())
3706 return;
3707 pdfWidget->setHighlightPath(-1, QPainterPath());
3708 pdfWidget->update();
3709 QDir curDir(QFileInfo(curFile).canonicalPath());
3710 QSynctex::NodeIterator iter = scanner.editQuery(pageIndex + 1, static_cast<float>(pos.x()), static_cast<float>(pos.y()));
3711 while (iter.hasNext()) {
3712 QSynctex::Node node = iter.next();
3713 QString fullName = scanner.getNameFileInfo(curDir, node).canonicalFilePath();
3714 if (!globalConfig->syncFileMask.trimmed().isEmpty()) {
3715 bool found = false;
3716 foreach (const QString & s, globalConfig->syncFileMask.split(";"))
3717 if (QRegExp(s.trimmed(), Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(fullName)) {
3718 found = true;
3719 break;
3720 }
3721 if (!found) continue;
3722 }
3723
3724 QString word;
3725 if (!document.isNull() && pageIndex >= 0 && pageIndex < pdfWidget->realNumPages()) {
3726 std::unique_ptr<Poppler::Page> popplerPage(document->page(pageIndex));
3727 if (popplerPage) {
3728 word = popplerPage->text(QRectF(pos, pos).adjusted(-35, -10, 35, 10));
3729 if (word.contains("\n")) word = word.split("\n")[word.split("\n").size() / 2];
3730 // replace ligatures
3731 word.replace(QChar(L'\xFB00'), QString("ff"));
3732 word.replace(QChar(L'\xFB01'), QString("fi"));
3733 word.replace(QChar(L'\xFB02'), QString("fl"));
3734 word.replace(QChar(L'\xFB03'), QString("ffi"));
3735 word.replace(QChar(L'\xFB04'), QString("ffl"));
3736 word.replace(QChar(L'\xFB05'), QString("ft"));
3737 word.replace(QChar(L'\xFB06'), QString("st"));
3738 }
3739 }
3740
3741 syncFromSourceBlocked = true;
3742 emit syncSource(fullName, node.line() - 1, activate, word.trimmed()); //-1 because txs is 0 based, but synctex seems to be 1 based
3743 syncFromSourceBlocked = false;
3744 break; // FIXME: currently we just take the first hit
3745 }
3746 }
3747
3748 /*!
3749 * \brief PDFDocument::syncFromSource
3750 * \param sourceFile
3751 * \param lineNo
3752 * \param column
3753 * \param displayFlags window and widget actions such as changing focus, and raising a window.
3754 * \return 0-based page number or -1 if the syncing was not successful.
3755 */
3756 int PDFDocument::syncFromSource(const QString &sourceFile, int lineNo, int column, PDFDocument::DisplayFlags displayFlags)
3757 {
3758 QSynctex::TeXSyncPoint sourcePoint(sourceFile, lineNo + 1, column + 1); // synctex uses 1-based line and column
3759 lastSyncPoint = sourcePoint;
3760
3761 if (!scanner.isValid() || syncFromSourceBlocked || ignoreSynchronization())
3762 return -1;
3763
3764 QSynctex::PDFSyncPoint pdfPoint = scanner.syncFromTeX(sourcePoint, curFile);
3765 if (pdfPoint.page <= 0)
3766 return -1;
3767
3768 pdfWidget->updateCurrentPageHistoryOffset();
3769
3770 QPainterPath path;
3771 foreach (const QRectF &r, pdfPoint.rects) {
3772 path.addRect(r);
3773 }
3774 syncToSourceBlocked = true;
3775 path.setFillRule(Qt::WindingFill);
3776 if (path.isEmpty()) scrollArea->goToPage(pdfPoint.page - 1, false); // otherwise scrolling is performed in setHighlightPath.
3777 pdfWidget->setHighlightPath(pdfPoint.page - 1, path);
3778 pdfWidget->delayedUpdate();
3779 updateDisplayState(displayFlags);
3780 syncToSourceBlocked = false;
3781 //pdfWidget->repaint();
3782 return pdfPoint.page - 1;
3783 }
3784
3785 void PDFDocument::setCurrentFile(const QString &fileName)
3786 {
3787 curFileUnnormalized = fileName;
3788 curFile = QFileInfo(fileName).canonicalFilePath();
3789 QString niceFile = QFileInfo(curFile).fileName();
3790 setWindowTitle(tr("%1[*] - %2").arg(niceFile,tr(TEXSTUDIO)));
3791 }
3792
3793 PDFDocument *PDFDocument::findDocument(const QString &fileName)
3794 {
3795 QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();
3796
3797 foreach (QWidget *widget, qApp->topLevelWidgets()) {
3798 PDFDocument *theDoc = qobject_cast<PDFDocument *>(widget);
3799 if (theDoc && theDoc->curFile == canonicalFilePath)
3800 return theDoc;
3801 }
3802 return nullptr;
3803 }
3804
3805 void PDFDocument::saveGeometryToConfig()
3806 {
3807 globalConfig->windowLeft = x();
3808 globalConfig->windowTop = y();
3809 globalConfig->windowWidth = width();
3810 globalConfig->windowHeight = height();
3811 globalConfig->windowMaximized = isMaximized();
3812 globalConfig->windowState = saveState();
3813 globalConfig->toolbarVisible = toolBar->isVisible();
3814 globalConfig->annotationPanelVisible = annotationPanel->isVisible();
3815 }
3816
3817 void PDFDocument::zoomToRight(QWidget *otherWindow)
3818 {
3819 #if QT_VERSION>=QT_VERSION_CHECK(5,15,0)
3820 QRect screenRect = otherWindow == nullptr ? this->screen()->availableGeometry() : otherWindow->screen()->availableGeometry();
3821 #else
3822 QDesktopWidget *desktop = QApplication::desktop();
3823 QRect screenRect = desktop->availableGeometry(otherWindow == nullptr ? this : otherWindow);
3824 #endif
3825 screenRect.setTop(screenRect.top() + 22);
3826 screenRect.setLeft((screenRect.left() + screenRect.right()) / 2 + 1);
3827 screenRect.setBottom(screenRect.bottom() - 1);
3828 screenRect.setRight(screenRect.right() - 1);
3829 setGeometry(screenRect);
3830 }
3831
3832 qreal PDFDocument::zoomSliderPosToScale(int pos)
3833 {
3834 if (pos == 0)
3835 return 1.0;
3836 if (pos < 0) {
3837 return (1 - kMinScaleFactor) / abs(zoomSlider->minimum() + 10) * (pos + 10) + 1;
3838 } else {
3839 return (kMaxScaleFactor - 1) / (zoomSlider->maximum() - 10) * (pos - 10) + 1;
3840 }
3841 }
3842
3843 int PDFDocument::scaleToZoomSliderPos(qreal scale)
3844 {
3845 if (scale < 1.01 && scale > 0.99)
3846 return 0;
3847 if (scale < 1) {
3848 return qRound((scale - 1) / (1 - kMinScaleFactor) * abs(zoomSlider->minimum() + 10) - 10);
3849 } else {
3850 return qRound((scale - 1) / (kMaxScaleFactor - 1) * (zoomSlider->maximum() - 10) + 10);
3851 }
3852 }
3853
3854 void PDFDocument::zoomSliderChange(int pos)
3855 {
3856 if (pos > -10 && pos < 10) {
3857 pos = 0;
3858 zoomSlider->setValue(pos);
3859 }
3860 widget()->zoom(zoomSliderPosToScale(pos));
3861 }
3862
3863
3864 void PDFDocument::showPage(int page)
3865 {
3866 //Q_ASSERT(document);
3867 if (document.isNull()) return;
3868 int p = page; //-pdfWidget->getPageOffset();
3869 if (p < 1)
3870 p = 1;
3871 int p2 = page + pdfWidget->visiblePages() - 1;
3872 if (pdfWidget->visiblePages() <= 1) pageLabel->setText(tr("Page %1 of %2").arg(p).arg(pdfWidget->realNumPages()));
3873 else pageLabel->setText(tr("Pages %1 to %2 of %3").arg(p).arg(p2).arg(pdfWidget->realNumPages()));
3874 pageCountLabel->setText(QString("%1").arg(pdfWidget->realNumPages()));
3875
3876 leCurrentPage->setText(QString("%1").arg(p));
3877 }
3878
3879 void PDFDocument::showScale(qreal scale)
3880 {
3881 QString scaleString = QString("%1%").arg(qRound(scale * 100.0));
3882 scaleButton->setText(scaleString);
3883 zoomSlider->blockSignals(true);
3884 // don't emit value changed: This is only used to update the value. It does not initiate changes
3885 zoomSlider->setValue(scaleToZoomSliderPos(scale));
3886 zoomSlider->blockSignals(false);
3887 }
3888
3889 void PDFDocument::goToSource()
3890 {
3891 Q_ASSERT(pdfWidget);
3892 if (!pdfWidget) return;
3893 pdfWidget->syncCurrentPage(true);
3894 }
3895
3896 void PDFDocument::fileOpen()
3897 {
3898 QString newFile = FileDialog::getOpenFileName(this, tr("Open PDF"), curFile, "PDF (*.pdf);;All files (*)");
3899 if (newFile.isEmpty()) return;
3900 loadFile(newFile, QFileInfo(), NoDisplayFlags);
3901 }
3902
3903 void PDFDocument::enablePageActions(int pageIndex, bool sync)
3904 {
3905 //current page has changed
3906 if (document.isNull())
3907 return;
3908
3909 Q_ASSERT(pdfWidget && globalConfig);
3910 if (!pdfWidget || !globalConfig) return;
3911
3912 //#ifndef Q_OS_MAC
3913 // On Mac OS X, disabling these leads to a crash if we hit the end of document while auto-repeating a key
3914 // (seems like a Qt bug, but needs further investigation)
3915 // 2008-09-07: seems to no longer be a problem, probably thanks to Qt 4.4 update
3916 actionFirst_Page->setEnabled(pageIndex > 0);
3917 actionPrevious_Page->setEnabled(pageIndex > 0);
3918 actionNext_Page->setEnabled(pageIndex < pdfWidget->realNumPages() - 1);
3919 actionLast_Page->setEnabled(pageIndex < pdfWidget->realNumPages() - 1);
3920
3921 actionBack->setEnabled(pdfWidget->currentPageHistoryIndex() > 0);
3922 actionForward->setEnabled(pdfWidget->currentPageHistoryIndex() < pdfWidget->currentPageHistory().size() - 1);
3923
3924 //#endif
3925
3926 sync = sync && !syncToSourceBlocked;
3927 if (globalConfig->followFromScroll && sync)
3928 pdfWidget->syncCurrentPage(false);
3929 if (actionSynchronize_multiple_views->isChecked() && sync)
3930 emit syncView(curFile, masterFile, widget()->getPageIndex());
3931 }
3932
3933 void PDFDocument::enableZoomActions(qreal scaleFactor)
3934 {
3935 actionZoom_In->setEnabled(scaleFactor < kMaxScaleFactor);
3936 actionZoom_Out->setEnabled(scaleFactor > kMinScaleFactor);
3937 }
3938
3939 void PDFDocument::adjustScaleActions(autoScaleOption scaleOption)
3940 {
3941 actionFit_to_Window->setChecked(scaleOption == kFitWindow);
3942 actionFit_to_Width->setChecked(scaleOption == kFitWidth);
3943 actionFit_to_Text_Width->setChecked(scaleOption == kFitTextWidth);
3944 Q_ASSERT(scrollArea);
3945 if (scaleOption == kFitWidth) {
3946 if (scrollArea->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff)
3947 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
3948 int minheight = scrollArea->viewport()->height();
3949 int maxheight = scrollArea->viewport()->height();
3950 if (scrollArea->horizontalScrollBar()->isVisible())
3951 maxheight += scrollArea->horizontalScrollBar()->height() + 5;
3952 else
3953 minheight -= scrollArea->horizontalScrollBar()->height() + 5;
3954 if (pdfWidget->height() < minheight) {
3955 if (scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff)
3956 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
3957 } else if (pdfWidget->height() > maxheight) {
3958 if (scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOn)
3959 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
3960 }
3961 } else if (scaleOption == kFitWindow) {
3962 if (scrollArea->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff)
3963 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
3964 if (scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff)
3965 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
3966 } else {
3967 if (scrollArea->horizontalScrollBarPolicy() != Qt::ScrollBarAsNeeded)
3968 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
3969 if (scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAsNeeded)
3970 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
3971 }
3972 }
3973
3974 void PDFDocument::toggleFullScreen(bool fullscreen)
3975 {
3976 bool presentation = false;
3977 if (fullscreen) {
3978 // entering full-screen mode
3979 statusBar()->hide();
3980 toolBar->hide();
3981 globalConfig->windowMaximized = isMaximized();
3982 showFullScreen();
3983 pdfWidget->saveState();
3984 pdfWidget->fitWindow(true);
3985 dwVisOutline = dwOutline->isVisible();
3986 dwVisOverview = dwOverview->isVisible();
3987 dwVisFonts = dwFonts->isVisible();
3988 dwVisSearch = dwSearch->isVisible();
3989 dwVisInfo = dwInfo->isVisible();
3990 if (sender() == actionPresentation) {
3991 menuBar()->hide();
3992 actionFull_Screen->setChecked(false);
3993 actionPresentation->setChecked(true);
3994 exitFullscreen = new QShortcut(Qt::Key_Escape, this, SLOT(closeElement())); //hiding the menubar disables normal shortcut
3995 pdfWidget->setTool(kPresentation);
3996 pdfWidget->setContextMenuPolicy(Qt::NoContextMenu);
3997 dwOutline->hide();
3998 dwFonts->hide();
3999 dwSearch->hide();
4000 dwInfo->hide();
4001 dwOverview->hide();
4002 presentation = true;
4003 if (actionContinuous->isChecked()) {
4004 actionContinuous->setChecked(false);
4005 wasContinuous = true;
4006 } else wasContinuous = false;
4007 } else
4008 actionFull_Screen->setChecked(true);
4009
4010 //actionFull_Screen->setChecked(true);
4011 } else {
4012 // exiting full-screen mode
4013 statusBar()->show();
4014 toolBar->show();
4015 if (globalConfig->windowMaximized)
4016 showMaximized();
4017 else
4018 showNormal();
4019 pdfWidget->restoreState();
4020 actionFull_Screen->setChecked(false);
4021 }
4022 if (!presentation) { //disable presentation things that are neither used in fullscreen nor normal mode
4023 menuBar()->show();
4024 actionPresentation->setChecked(false);
4025 pdfWidget->setTool(toolButtonGroup->checkedId());
4026 pdfWidget->setContextMenuPolicy(Qt::DefaultContextMenu);
4027 if (exitFullscreen) {
4028 delete exitFullscreen;
4029 exitFullscreen = nullptr;
4030 }
4031 if (wasContinuous) actionContinuous->setChecked(true);
4032 }
4033 }
4034
4035 void PDFDocument::resetMagnifier()
4036 {
4037 pdfWidget->resetMagnifier();
4038 }
4039
4040 void PDFDocument::zoomFromAction()
4041 {
4042 QAction *act = qobject_cast<QAction *>(sender());
4043 if (!act) return;
4044
4045 bool ok;
4046 int factor = act->data().toInt(&ok);
4047 if (!ok) { // old combobox
4048 QString text = act->text();
4049 text.chop(1);
4050 factor = text.toInt(&ok);
4051 }
4052 if (ok) {
4053 pdfWidget->fixedScale(0.01 * factor);
4054 }
4055
4056 if (comboZoom)
4057 comboZoom->setDefaultAction(act);
4058 }
4059
4060 void PDFDocument::setResolution(int res)
4061 {
4062 if (res > 0)
4063 pdfWidget->setResolution(res);
4064 }
4065
4066 void PDFDocument::goToDestination(const QString &destName)
4067 {
4068 if (pdfWidget)
4069 pdfWidget->goToDestination(destName);
4070 }
4071
4072 void PDFDocument::goToPage(const int page)
4073 {
4074 if (pdfWidget && scrollArea)
4075 scrollArea->goToPage(page);
4076 }
4077
4078 void PDFDocument::focus()
4079 {
4080 widget()->setFocus();
4081 if (!embeddedMode) {
4082 raise();
4083 activateWindow();
4084 }
4085 }
4086
4087 void PDFDocument::dragEnterEvent(QDragEnterEvent *event)
4088 {
4089 // Only accept files for now
4090 event->ignore();
4091 if (event->mimeData()->hasUrls()) {
4092 const QList<QUrl> urls = event->mimeData()->urls();
4093 foreach (const QUrl &url, urls) {
4094 if (url.scheme() == "file") {
4095 event->acceptProposedAction();
4096 break;
4097 }
4098 }
4099 }
4100 }
4101
4102 void PDFDocument::dropEvent(QDropEvent *event)
4103 {
4104 event->ignore();
4105 if (event->mimeData()->hasUrls()) {
4106 const QList<QUrl> urls = event->mimeData()->urls();
4107 foreach (const QUrl &url, urls)
4108 if (url.scheme() == "file") {
4109 if (url.path().endsWith("pdf")) loadFile(url.toLocalFile());
4110 else emit fileDropped(url);
4111 }
4112 event->acceptProposedAction();
4113 }
4114 }
4115
4116 void PDFDocument::enterEvent(QEvent *event)
4117 {
4118 if (event->type() == QEvent::Enter
4119 && embeddedMode
4120 && globalConfig->autoHideToolbars) {
4121 showToolbarsDelayed();
4122 }
4123 }
4124
4125 void PDFDocument::leaveEvent(QEvent *event)
4126 {
4127 if (event->type() == QEvent::Leave
4128 && embeddedMode
4129 && globalConfig->autoHideToolbars) {
4130 hideToolbars();
4131 }
4132 }
4133
4134 void PDFDocument::mouseMoveEvent(QMouseEvent *event)
4135 {
4136 if (embeddedMode && globalConfig->autoHideToolbars) {
4137 int h = toolBar->height() + 5;
4138 if (event->pos().y() < h || event->pos().y() > this->height() - h) {
4139 showToolbarsDelayed();
4140 } else {
4141 hideToolbars();
4142 }
4143 }
4144 }
4145
4146 void PDFDocument::doFindDialog(const QString command)
4147 {
4148 if (!dwSearch) return;
4149 dwSearch->show();
4150 dwSearch->setFocus();
4151 if (!command.isEmpty())
4152 dwSearch->setSearchText(command);
4153 }
4154
4155 void PDFDocument::doFindAgain()
4156 {
4157 search(false, false);
4158 }
4159
4160 void PDFDocument::printPDF()
4161 {
4162 if (document.isNull())
4163 return;
4164
4165 QString command;
4166 // texmaker 3.0.1 solution
4167 int firstPage, lastPage;
4168 QPrinter printer(QPrinter::HighResolution);
4169 QPrintDialog printDlg(&printer, this);
4170 printer.setDocName(fileName());
4171 printDlg.setMinMax(1, pdfWidget->realNumPages());
4172 printDlg.setFromTo(1, pdfWidget->realNumPages());
4173 printDlg.setOption(QAbstractPrintDialog::PrintToFile, false);
4174 printDlg.setOption(QAbstractPrintDialog::PrintSelection, false);
4175 printDlg.setOption(QAbstractPrintDialog::PrintPageRange, true);
4176 printDlg.setOption(QAbstractPrintDialog::PrintCollateCopies, false);
4177
4178 printDlg.setWindowTitle(tr("Print"));
4179 if (printDlg.exec() != QDialog::Accepted) return;
4180 switch (printDlg.printRange()) {
4181 case QAbstractPrintDialog::PageRange:
4182 firstPage = printDlg.fromPage();
4183 lastPage = printDlg.toPage();
4184 break;
4185 default:
4186 firstPage = 1;
4187 lastPage = pdfWidget->realNumPages();
4188 }
4189
4190 if (!printer.printerName().isEmpty()) {
4191 #if defined(Q_OS_WIN32) && QT_VERSION_MAJOR<6
4192 QString paper;
4193 switch (printer.paperSize()) {
4194 case QPageSize::A0:
4195 paper = "a0";
4196 break;
4197 case QPageSize::A1:
4198 paper = "a1";
4199 break;
4200 case QPageSize::A2:
4201 paper = "a2";
4202 break;
4203 case QPageSize::A3:
4204 paper = "a3";
4205 break;
4206 case QPageSize::A4:
4207 paper = "a4";
4208 break;
4209 case QPageSize::A5:
4210 paper = "a5";
4211 break;
4212 case QPageSize::A6:
4213 paper = "a6";
4214 break;
4215 case QPageSize::B0:
4216 paper = "isob0";
4217 break;
4218 case QPageSize::B1:
4219 paper = "isob1";
4220 break;
4221 case QPageSize::B2:
4222 paper = "isob2";
4223 break;
4224 case QPageSize::B3:
4225 paper = "isob3";
4226 break;
4227 case QPageSize::B4:
4228 paper = "isob4";
4229 break;
4230 case QPageSize::B5:
4231 paper = "isob5";
4232 break;
4233 case QPageSize::B6:
4234 paper = "isob6";
4235 break;
4236 case QPageSize::Letter:
4237 paper = "letter";
4238 break;
4239 case QPageSize::Ledger:
4240 paper = "ledger";
4241 break;
4242 case QPageSize::Legal:
4243 paper = "legal";
4244 break;
4245 default:
4246 paper = "a4";
4247 }
4248
4249 QStringList args;
4250 args << "txs:///gs";
4251 args << "-sDEVICE=mswinpr2";
4252 args << QString("-sOutputFile=\"\%printer\%%1\"").arg(printer.printerName().replace(" ", "_"));
4253 args << "-dBATCH";
4254 args << "-dNOPAUSE";
4255 args << "-dQUIET";
4256 args << "-dNoCancel";
4257 args << "-sPAPERSIZE=" + paper;
4258 args << "-dFirstPage=" + QString::number(firstPage);
4259 args << "-dLastPage=" + QString::number(lastPage);
4260 #else
4261 QStringList args;
4262 args << "lp";
4263 args << QString("-d %1").arg(printer.printerName().replace(" ", "_"));
4264 //args << QString("-n %1").arg(printer.numCopies());
4265 // args << QString("-t \"%1\"").arg(printer.docName());
4266 args << QString("-P %1-%2").arg(firstPage).arg(lastPage);
4267 switch (printer.duplex()) {
4268 case QPrinter::DuplexNone:
4269 args << "-o sides=one-sided";
4270 break;
4271 case QPrinter::DuplexShortSide:
4272 args << "-o sides=two-sided-short-edge";
4273 break;
4274 case QPrinter::DuplexLongSide:
4275 args << "-o sides=two-sided-long-edge";
4276 break;
4277 default:
4278 break;
4279 }
4280 args << "--";
4281 #endif
4282 args << "\"?am.pdf\"";
4283 command = args.join(" ");
4284 } else return;
4285
4286 for (int i = 0; i < printer.copyCount(); i++)
4287 emit runCommand(command, masterFile, masterFile, 0);
4288 }
4289
4290 void PDFDocument::setAutoHideToolbars(bool enabled)
4291 {
4292 setToolbarsVisible(!enabled);
4293 actionAutoHideToolbars->setChecked(enabled);
4294 // When autohiding the toolbars we need the mouseMoveEvent of pdfWidget in order to track user activity.
4295 // We also enable/disable the mouse-tracking flag of all ancestors of the widget because otherwise the may
4296 // swallow the mouseMoveEvent. However we do not change the mouse-tracking flag of the pdfWidget itself
4297 // because it must be always enabled (the widget uses mouseMoveEvent to maintain its internal state).
4298 if (pdfWidget) {
4299 for (QWidget *w = pdfWidget->parentWidget(); w; w = w->parentWidget()) {
4300 w->setMouseTracking(enabled);
4301 }
4302 }
4303 }
4304
4305 // hide toolbars while preserving the position of the PDF content on screen
4306 // we have to compensate the change of scrollArea position by scrolling its content
4307 void PDFDocument::hideToolbars()
4308 {
4309 toolBarTimer->stop();
4310 if (toolBar->isVisible()) {
4311 setToolbarsVisible(false);
4312 // workaround: the method of checking the change in globalPos of the scrollArea (as in enterEvent)
4313 // does not work here (positions are not yet updated after hiding the toolbars)
4314 if (!toolBar->isFloating() && toolBar->orientation() == Qt::Horizontal) {
4315 scrollArea->verticalScrollBar()->setValue(scrollArea->verticalScrollBar()->value() - toolBar->height());
4316 }
4317 }
4318 }
4319
4320 // hide toolbars while preserving the position of the PDF content on screen
4321 // we have to compensate the change of scrollArea position by scrolling its content
4322 void PDFDocument::showToolbars()
4323 {
4324 if (!toolBar->isVisible()) {
4325 QPoint widgetPos = scrollArea->mapToGlobal(QPoint(0, 0));
4326 setToolbarsVisible(true);
4327 QPoint posChange = scrollArea->mapToGlobal(QPoint(0, 0)) - widgetPos;
4328 scrollArea->verticalScrollBar()->setValue(scrollArea->verticalScrollBar()->value() + posChange.y());
4329 }
4330 }
4331
4332 void PDFDocument::showToolbarsDelayed()
4333 {
4334 if (!toolBarTimer->isActive())
4335 toolBarTimer->start(200);
4336 }
4337
4338 void PDFDocument::setToolbarIconSize(int sz)
4339 {
4340 // adapt icon size to dpi
4341 double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
4342 double scale=dpi/96;
4343
4344 toolBar->setIconSize(QSize(qRound(sz*scale), qRound(sz*scale)));
4345 // statusbar
4346 foreach (QObject *c, statusbar->children()) {
4347 QAbstractButton *bt = qobject_cast<QAbstractButton *>(c);
4348 if (bt) {
4349 bt->setIconSize(QSize(qRound(sz*scale), qRound(sz*scale)));
4350 }
4351 }
4352 }
4353
4354 void PDFDocument::showMessage(const QString &text)
4355 {
4356 QAction *closeAction = new QAction(tr("Close"), this);
4357 closeAction->setToolTip(tr("Close Message"));
4358 connect(closeAction, SIGNAL(triggered()), messageFrame, SLOT(hide()));
4359 messageFrame->showText(text, QList<QAction *>() << closeAction);
4360 }
4361
4362 void PDFDocument::setToolbarsVisible(bool visible)
4363 {
4364 toolBar->setVisible(visible);
4365 statusbar->setVisible(visible);
4366 }
4367
4368 void PDFDocument::splitMergeTool()
4369 {
4370 PDFSplitMergeTool *psmt = new PDFSplitMergeTool(nullptr, fileName());
4371 connect(psmt, SIGNAL(runCommand(QString,QFileInfo,QFileInfo,int)), SIGNAL(runCommand(QString,QFileInfo,QFileInfo,int)));
4372 psmt->show();
4373 }
4374
4375 #endif // ndef NO_POPPLER_PREVIEW
4376