1 /*
2 This is part of TeXworks, an environment for working with TeX documents
3 Copyright (C) 2007-2016 Jonathan Kew, Stefan Löffler, Charlie Sharpsteen
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 authors,
19 see <http://www.tug.org/texworks/>.
20 */
21
22 #include "PDFDocument.h"
23 #include "TeXDocument.h"
24 #include "TWApp.h"
25 #include "TWUtils.h"
26 #include "FindDialog.h"
27 #include "ClickableLabel.h"
28
29 #include <QDockWidget>
30 #include <QCloseEvent>
31 #include <QFileDialog>
32 #include <QMessageBox>
33 #include <QStatusBar>
34 #include <QPainter>
35 #include <QPaintEngine>
36 #include <QLabel>
37 #include <QScrollArea>
38 #include <QStyle>
39 #include <QDesktopWidget>
40 #include <QScrollBar>
41 #include <QRegion>
42 #include <QVector>
43 #include <QList>
44 #include <QStack>
45 #include <QInputDialog>
46 #include <QDesktopServices>
47 #include <QUrl>
48 #include <QShortcut>
49 #include <QToolTip>
50 #include <QSignalMapper>
51
52 #include <math.h>
53
54 #define SYNCTEX_GZ_EXT ".synctex.gz"
55 #define SYNCTEX_EXT ".synctex"
56
57 #define ROUND(x) floor((x)+0.5)
58
59 // Possible sizes of the magnifying glass (in pixel)
60 const int magSizes[] = { 200, 300, 400 };
61
62 // duration of highlighting in PDF view (might make configurable?)
63 const int kPDFHighlightDuration = 2000;
64
65
66
67 #pragma mark === PDFDocument ===
68
69 // TODO: This is seemingly unused---verify && remove
70 QList<PDFDocument*> PDFDocument::docList;
71
PDFDocument(const QString & fileName,TeXDocument * texDoc)72 PDFDocument::PDFDocument(const QString &fileName, TeXDocument *texDoc)
73 : _syncHighlight(NULL), _synchronizer(NULL), openedManually(false)
74 {
75 init();
76
77 if (texDoc == NULL)
78 openedManually = true;
79
80 loadFile(fileName);
81
82 QMap<QString,QVariant> properties = TWApp::instance()->getFileProperties(curFile);
83 if (properties.contains("geometry"))
84 restoreGeometry(properties.value("geometry").toByteArray());
85 else
86 TWUtils::zoomToHalfScreen(this, true);
87
88 if (properties.contains("state"))
89 restoreState(properties.value("state").toByteArray(), kPDFWindowStateVersion);
90
91 if (properties.contains("pdfPageMode"))
92 setPageMode(properties.value("pdfPageMode", -1).toInt());
93
94 QTimer::singleShot(100, this, SLOT(setDefaultScale()));
95
96 if (texDoc != NULL) {
97 stackUnder((QWidget*)texDoc);
98 actionSide_by_Side->setEnabled(true);
99 actionGo_to_Source->setEnabled(true);
100 sourceDocList.append(texDoc);
101 }
102 }
103
~PDFDocument()104 PDFDocument::~PDFDocument()
105 {
106 docList.removeAll(this);
107 }
108
init()109 void PDFDocument::init()
110 {
111 docList.append(this);
112
113 setupUi(this);
114 #if defined(Q_OS_WIN)
115 TWApp::instance()->createMessageTarget(this);
116 #endif
117
118 setAttribute(Qt::WA_DeleteOnClose, true);
119 setAttribute(Qt::WA_MacNoClickThrough, true);
120
121 QIcon winIcon;
122 #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
123 // The Compiz window manager doesn't seem to support icons larger than
124 // 128x128, so we add a suitable one first
125 winIcon.addFile(":/images/images/TeXworks-doc-128.png");
126 #endif
127 winIcon.addFile(":/images/images/TeXworks-doc.png");
128 setWindowIcon(winIcon);
129
130 pdfWidget = new QtPDF::PDFDocumentWidget(this);
131 pdfWidget->setSearchResultHighlightBrush(QBrush(Qt::transparent));
132 pdfWidget->setCurrentSearchResultHighlightBrush(QBrush(Qt::transparent));
133 pdfWidget->setAcceptDrops(false);
134 _searchResultHighlightBrush = QColor(255, 255, 0, 63);
135 setCentralWidget(pdfWidget);
136
137 connect(pdfWidget, SIGNAL(changedPage(int)), this, SLOT(updateStatusBar()));
138 connect(pdfWidget, SIGNAL(changedZoom(qreal)), this, SLOT(updateStatusBar()));
139 connect(pdfWidget, SIGNAL(changedDocument(const QWeakPointer<QtPDF::Backend::Document>)), this, SLOT(changedDocument(const QWeakPointer<QtPDF::Backend::Document>)));
140 connect(pdfWidget, SIGNAL(searchResultHighlighted(const int, const QList<QPolygonF>)), this, SLOT(searchResultHighlighted(const int, const QList<QPolygonF>)));
141 connect(pdfWidget, SIGNAL(changedPageMode(QtPDF::PDFDocumentView::PageMode)), this, SLOT(updatePageMode(QtPDF::PDFDocumentView::PageMode)));
142 connect(pdfWidget, SIGNAL(requestOpenPdf(QString,QtPDF::PDFDestination,bool)), this, SLOT(maybeOpenPdf(QString,QtPDF::PDFDestination,bool)));
143 connect(pdfWidget, SIGNAL(requestOpenUrl(QUrl)), this, SLOT(maybeOpenUrl(QUrl)));
144
145 toolButtonGroup = new QButtonGroup(toolBar);
146 toolButtonGroup->addButton(qobject_cast<QAbstractButton*>(toolBar->widgetForAction(actionMagnify)), QtPDF::PDFDocumentView::MouseMode_MagnifyingGlass);
147 toolButtonGroup->addButton(qobject_cast<QAbstractButton*>(toolBar->widgetForAction(actionScroll)), QtPDF::PDFDocumentView::MouseMode_Move);
148 toolButtonGroup->addButton(qobject_cast<QAbstractButton*>(toolBar->widgetForAction(actionSelect_Text)), QtPDF::PDFDocumentView::MouseMode_Select);
149 // toolButtonGroup->addButton(qobject_cast<QAbstractButton*>(toolBar->widgetForAction(actionSelect_Image)), kSelectImage);
150 connect(toolButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(setMouseMode(int)));
151 pdfWidget->setMouseModeMagnifyingGlass();
152
153 scaleLabel = new ClickableLabel();
154 statusBar()->addPermanentWidget(scaleLabel);
155 scaleLabel->setFrameStyle(QFrame::StyledPanel);
156 scaleLabel->setFont(statusBar()->font());
157 connect(scaleLabel, SIGNAL(mouseLeftClick(QMouseEvent*)), this, SLOT(scaleLabelClick(QMouseEvent*)));
158
159 pageLabel = new ClickableLabel();
160 statusBar()->addPermanentWidget(pageLabel);
161 pageLabel->setFrameStyle(QFrame::StyledPanel);
162 pageLabel->setFont(statusBar()->font());
163 connect(pageLabel, SIGNAL(mouseLeftClick(QMouseEvent*)), this, SLOT(doPageDialog()));
164
165 connect(actionAbout_TW, SIGNAL(triggered()), qApp, SLOT(about()));
166 connect(actionSettings_and_Resources, SIGNAL(triggered()), qApp, SLOT(doResourcesDialog()));
167 connect(actionGoToHomePage, SIGNAL(triggered()), qApp, SLOT(goToHomePage()));
168 connect(actionWriteToMailingList, SIGNAL(triggered()), qApp, SLOT(writeToMailingList()));
169
170 connect(actionNew, SIGNAL(triggered()), qApp, SLOT(newFile()));
171 connect(actionNew_from_Template, SIGNAL(triggered()), qApp, SLOT(newFromTemplate()));
172 connect(actionOpen, SIGNAL(triggered()), qApp, SLOT(open()));
173 connect(actionPrintPdf, SIGNAL(triggered()), this, SLOT(print()));
174
175 connect(actionQuit_TeXworks, SIGNAL(triggered()), TWApp::instance(), SLOT(maybeQuit()));
176
177 connect(actionFind, SIGNAL(triggered()), this, SLOT(doFindDialog()));
178
179 connect(actionFirst_Page, SIGNAL(triggered()), pdfWidget, SLOT(goFirst()));
180 connect(actionPrevious_Page, SIGNAL(triggered()), pdfWidget, SLOT(goPrev()));
181 connect(actionNext_Page, SIGNAL(triggered()), pdfWidget, SLOT(goNext()));
182 connect(actionLast_Page, SIGNAL(triggered()), pdfWidget, SLOT(goLast()));
183 connect(actionGo_to_Page, SIGNAL(triggered()), this, SLOT(doPageDialog()));
184 addAction(actionPrevious_ViewRect);
185 connect(actionPrevious_ViewRect, SIGNAL(triggered()), pdfWidget, SLOT(goPrevViewRect()));
186 connect(pdfWidget, SIGNAL(changedPage(int)), this, SLOT(enablePageActions(int)));
187
188 connect(actionActual_Size, SIGNAL(triggered()), pdfWidget, SLOT(zoom100()));
189 connect(actionFit_to_Width, SIGNAL(triggered()), pdfWidget, SLOT(zoomFitWidth()));
190 connect(actionFit_to_Window, SIGNAL(triggered()), pdfWidget, SLOT(zoomFitWindow()));
191 connect(actionZoom_In, SIGNAL(triggered()), pdfWidget, SLOT(zoomIn()));
192 connect(actionZoom_Out, SIGNAL(triggered()), pdfWidget, SLOT(zoomOut()));
193 connect(actionFull_Screen, SIGNAL(triggered()), this, SLOT(toggleFullScreen()));
194 connect(pdfWidget, SIGNAL(contextClick(int, const QPointF&)), this, SLOT(syncClick(int, const QPointF&)));
195 pageModeSignalMapper.setMapping(actionPageMode_Single, QtPDF::PDFDocumentView::PageMode_SinglePage);
196 pageModeSignalMapper.setMapping(actionPageMode_Continuous, QtPDF::PDFDocumentView::PageMode_OneColumnContinuous);
197 pageModeSignalMapper.setMapping(actionPageMode_TwoPagesContinuous, QtPDF::PDFDocumentView::PageMode_TwoColumnContinuous);
198 connect(actionPageMode_Single, SIGNAL(triggered()), &pageModeSignalMapper, SLOT(map()));
199 connect(actionPageMode_Continuous, SIGNAL(triggered()), &pageModeSignalMapper, SLOT(map()));
200 connect(actionPageMode_TwoPagesContinuous, SIGNAL(triggered()), &pageModeSignalMapper, SLOT(map()));
201 connect(&pageModeSignalMapper, SIGNAL(mapped(int)), this, SLOT(setPageMode(int)));
202
203 if (actionZoom_In->shortcut() == QKeySequence("Ctrl++"))
204 new QShortcut(QKeySequence("Ctrl+="), pdfWidget, SLOT(zoomIn()));
205
206 connect(actionTypeset, SIGNAL(triggered()), this, SLOT(retypeset()));
207
208 connect(actionStack, SIGNAL(triggered()), qApp, SLOT(stackWindows()));
209 connect(actionTile, SIGNAL(triggered()), qApp, SLOT(tileWindows()));
210 connect(actionSide_by_Side, SIGNAL(triggered()), this, SLOT(sideBySide()));
211 connect(actionPlace_on_Left, SIGNAL(triggered()), this, SLOT(placeOnLeft()));
212 connect(actionPlace_on_Right, SIGNAL(triggered()), this, SLOT(placeOnRight()));
213 connect(actionGo_to_Source, SIGNAL(triggered()), this, SLOT(goToSource()));
214
215 connect(actionFind_Again, SIGNAL(triggered()), this, SLOT(doFindAgain()));
216
217 updateRecentFileActions();
218 connect(qApp, SIGNAL(recentFileActionsChanged()), this, SLOT(updateRecentFileActions()));
219 connect(qApp, SIGNAL(windowListChanged()), this, SLOT(updateWindowMenu()));
220 connect(actionClear_Recent_Files, SIGNAL(triggered()), TWApp::instance(), SLOT(clearRecentFiles()));
221
222 connect(qApp, SIGNAL(hideFloatersExcept(QWidget*)), this, SLOT(hideFloatersUnlessThis(QWidget*)));
223 connect(this, SIGNAL(activatedWindow(QWidget*)), qApp, SLOT(activatedWindow(QWidget*)));
224
225 connect(actionPreferences, SIGNAL(triggered()), qApp, SLOT(preferences()));
226
227 connect(this, SIGNAL(destroyed()), qApp, SLOT(updateWindowMenus()));
228
229 connect(qApp, SIGNAL(syncPdf(const QString&, int, int, bool)), this, SLOT(syncFromSource(const QString&, int, int, bool)));
230
231 _syncHighlightRemover.setSingleShot(true);
232 connect(&_syncHighlightRemover, SIGNAL(timeout()), this, SLOT(clearSyncHighlight()));
233
234 _searchResultHighlightRemover.setSingleShot(true);
235 connect(&_searchResultHighlightRemover, SIGNAL(timeout()), this, SLOT(clearSearchResultHighlight()));
236
237 menuShow->addAction(toolBar->toggleViewAction());
238 menuShow->addSeparator();
239
240 QDockWidget * dw = pdfWidget->dockWidget(QtPDF::PDFDocumentView::Dock_TableOfContents, this);
241 dw->hide();
242 addDockWidget(Qt::LeftDockWidgetArea, dw);
243 menuShow->addAction(dw->toggleViewAction());
244
245 dw = pdfWidget->dockWidget(QtPDF::PDFDocumentView::Dock_MetaData, this);
246 dw->hide();
247 addDockWidget(Qt::LeftDockWidgetArea, dw);
248 menuShow->addAction(dw->toggleViewAction());
249
250 dw = pdfWidget->dockWidget(QtPDF::PDFDocumentView::Dock_Fonts, this);
251 dw->hide();
252 addDockWidget(Qt::BottomDockWidgetArea, dw);
253 menuShow->addAction(dw->toggleViewAction());
254
255 dw = pdfWidget->dockWidget(QtPDF::PDFDocumentView::Dock_Permissions, this);
256 dw->hide();
257 addDockWidget(Qt::LeftDockWidgetArea, dw);
258 menuShow->addAction(dw->toggleViewAction());
259
260 dw = pdfWidget->dockWidget(QtPDF::PDFDocumentView::Dock_Annotations, this);
261 dw->hide();
262 addDockWidget(Qt::LeftDockWidgetArea, dw);
263 menuShow->addAction(dw->toggleViewAction());
264
265 exitFullscreen = NULL;
266
267 QSETTINGS_OBJECT(settings);
268 switch(settings.value("pdfPageMode", kDefault_PDFPageMode).toInt()) {
269 case 0:
270 setPageMode(QtPDF::PDFDocumentView::PageMode_SinglePage);
271 break;
272 case 1:
273 setPageMode(QtPDF::PDFDocumentView::PageMode_OneColumnContinuous);
274 break;
275 case 2:
276 setPageMode(QtPDF::PDFDocumentView::PageMode_TwoColumnContinuous);
277 break;
278 default:
279 setPageMode(kDefault_PDFPageMode);
280 break;
281 }
282 resetMagnifier();
283
284 if (settings.contains("previewResolution"))
285 pdfWidget->setResolution(settings.value("previewResolution", QApplication::desktop()->logicalDpiX()).toInt());
286
287 TWUtils::applyToolbarOptions(this, settings.value("toolBarIconSize", 2).toInt(), settings.value("toolBarShowText", false).toBool());
288
289 TWApp::instance()->updateWindowMenus();
290
291 initScriptable(menuScripts, actionAbout_Scripts, actionManage_Scripts,
292 actionUpdate_Scripts, actionShow_Scripts_Folder);
293
294 TWUtils::insertHelpMenuItems(menuHelp);
295 TWUtils::installCustomShortcuts(this);
296 }
297
changeEvent(QEvent * event)298 void PDFDocument::changeEvent(QEvent *event)
299 {
300 if (event->type() == QEvent::LanguageChange) {
301 QString title = windowTitle();
302 retranslateUi(this);
303 TWUtils::insertHelpMenuItems(menuHelp);
304 setWindowTitle(title);
305 updateStatusBar();
306 }
307 QMainWindow::changeEvent(event);
308 }
309
linkToSource(TeXDocument * texDoc)310 void PDFDocument::linkToSource(TeXDocument *texDoc)
311 {
312 if (texDoc != NULL) {
313 if (!sourceDocList.contains(texDoc))
314 sourceDocList.append(texDoc);
315 actionGo_to_Source->setEnabled(true);
316 }
317 }
318
texClosed(QObject * obj)319 void PDFDocument::texClosed(QObject *obj)
320 {
321 TeXDocument *texDoc = reinterpret_cast<TeXDocument*>(obj);
322 // can't use qobject_cast here as the object's metadata is already gone!
323 if (texDoc != 0) {
324 sourceDocList.removeAll(texDoc);
325 if (sourceDocList.count() == 0)
326 close();
327 }
328 }
329
texActivated(TeXDocument * texDoc)330 void PDFDocument::texActivated(TeXDocument * texDoc)
331 {
332 // A source file was activated. Make sure it is the first in the list of
333 // source docs so that future "Goto Source" actions point there.
334 if (sourceDocList.first() != texDoc) {
335 sourceDocList.removeAll(texDoc);
336 sourceDocList.prepend(texDoc);
337 }
338 }
339
updateRecentFileActions()340 void PDFDocument::updateRecentFileActions()
341 {
342 TWUtils::updateRecentFileActions(this, recentFileActions, menuOpen_Recent, actionClear_Recent_Files);
343 }
344
updateWindowMenu()345 void PDFDocument::updateWindowMenu()
346 {
347 TWUtils::updateWindowMenu(this, menuWindow);
348 }
349
sideBySide()350 void PDFDocument::sideBySide()
351 {
352 if (sourceDocList.count() > 0) {
353 TWUtils::sideBySide(sourceDocList.first(), this);
354 sourceDocList.first()->selectWindow(false);
355 selectWindow();
356 }
357 else
358 placeOnRight();
359 }
360
event(QEvent * event)361 bool PDFDocument::event(QEvent *event)
362 {
363 switch (event->type()) {
364 case QEvent::WindowActivate:
365 showFloaters();
366 emit activatedWindow(this);
367 break;
368 default:
369 break;
370 }
371 return QMainWindow::event(event);
372 }
373
closeEvent(QCloseEvent * event)374 void PDFDocument::closeEvent(QCloseEvent *event)
375 {
376 event->accept();
377 if (openedManually) {
378 saveRecentFileInfo();
379 }
380 deleteLater();
381 }
382
saveRecentFileInfo()383 void PDFDocument::saveRecentFileInfo()
384 {
385 QMap<QString,QVariant> fileProperties;
386 fileProperties.insert("path", curFile);
387 fileProperties.insert("geometry", saveGeometry());
388 fileProperties.insert("state", saveState(kPDFWindowStateVersion));
389 fileProperties.insert("pdfPageMode", pdfWidget->pageMode());
390 TWApp::instance()->addToRecentFiles(fileProperties);
391 }
392
loadFile(const QString & fileName)393 void PDFDocument::loadFile(const QString &fileName)
394 {
395 setCurrentFile(fileName);
396 QSETTINGS_OBJECT(settings);
397 QFileInfo info(fileName);
398 settings.setValue("openDialogDir", info.canonicalPath());
399
400 reload();
401 }
402
reload()403 void PDFDocument::reload()
404 {
405 QApplication::setOverrideCursor(Qt::WaitCursor);
406
407 clearSyncHighlight();
408 if (pdfWidget->load(curFile)) {
409 loadSyncData();
410 emit reloaded();
411 }
412 else {
413 statusBar()->showMessage(tr("Failed to load file \"%1\"; perhaps it is not a valid PDF document.").arg(TWUtils::strippedName(curFile)));
414 }
415 QApplication::restoreOverrideCursor();
416 }
417
loadSyncData()418 void PDFDocument::loadSyncData()
419 {
420 if (_synchronizer) {
421 delete _synchronizer;
422 _synchronizer = NULL;
423 }
424 _synchronizer = new TWSyncTeXSynchronizer(curFile);
425 if (!_synchronizer)
426 statusBar()->showMessage(tr("Error initializing SyncTeX"), kStatusMessageDuration);
427 else if (!_synchronizer->isValid())
428 statusBar()->showMessage(tr("No SyncTeX data available"), kStatusMessageDuration);
429 else
430 statusBar()->showMessage(tr("SyncTeX: \"%1\"").arg(_synchronizer->syncTeXFilename()), kStatusMessageDuration);
431 }
432
syncClick(int pageIndex,const QPointF & pos)433 void PDFDocument::syncClick(int pageIndex, const QPointF& pos)
434 {
435 if (!_synchronizer)
436 return;
437
438 clearSyncHighlight();
439
440 /* NOTE: PDF coordinates are upside down (i.e., (0,0) is in the lower left),
441 * whereas SyncTeX expects TeX coordinates (i.e., (0,0) in the upper
442 * left). Hence we need to convert the coordinates
443 */
444 QSharedPointer<QtPDF::Backend::Document> doc = pdfWidget->document().toStrongRef();
445 if (!doc)
446 return;
447 QSharedPointer<QtPDF::Backend::Page> page = doc->page(pageIndex).toStrongRef();
448 if (!page)
449 return;
450
451 TWSynchronizer::PDFSyncPoint src;
452 src.filename = curFile;
453 src.page = pageIndex + 1;
454 src.rects.append(QRectF(pos.x(), page->pageSizeF().height() - pos.y(), 0, 0));
455
456 // Get target point
457 TWSynchronizer::TeXSyncPoint dest = _synchronizer->syncFromPDF(src);
458
459 // Check target point
460 if (dest.filename.isEmpty() || dest.line < 0)
461 return;
462
463 // Display the result
464 QDir curDir(QFileInfo(curFile).canonicalPath());
465 if (dest.col >= 0)
466 TeXDocument::openDocument(QFileInfo(curDir, dest.filename).canonicalFilePath(), true, true, dest.line, dest.col, dest.col + 1);
467 else
468 TeXDocument::openDocument(QFileInfo(curDir, dest.filename).canonicalFilePath(), true, true, dest.line, -1, -1);
469 }
470
syncFromSource(const QString & sourceFile,int lineNo,int col,bool activatePreview)471 void PDFDocument::syncFromSource(const QString& sourceFile, int lineNo, int col, bool activatePreview)
472 {
473 if (!_synchronizer)
474 return;
475
476 TWSynchronizer::TeXSyncPoint src;
477 src.filename = sourceFile;
478 src.line = lineNo;
479 src.col = col;
480
481 // Get target point
482 TWSynchronizer::PDFSyncPoint dest = _synchronizer->syncFromTeX(src);
483
484 // Check target point
485 if (dest.page < 1 || QFileInfo(curFile) != QFileInfo(dest.filename))
486 return;
487
488 // Display the result
489 pdfWidget->goToPage(dest.page - 1);
490 QPainterPath path;
491 path.setFillRule(Qt::WindingFill);
492 foreach(QRectF r, dest.rects)
493 path.addRect(r);
494
495 clearSyncHighlight();
496 _syncHighlight = pdfWidget->addHighlightPath(dest.page - 1, path, QColor(255, 255, 0, 63));
497
498 // Ensure that the synhronization point is displayed (in the center)
499 pdfWidget->centerOn(_syncHighlight->mapToScene(_syncHighlight->boundingRect().center()));
500
501 // Start the highlight removal timer (if applicable)
502 if (kPDFHighlightDuration > 0)
503 _syncHighlightRemover.start(kPDFHighlightDuration);
504
505 // Update the view (and possibly bring it to the front)
506 pdfWidget->update();
507 if (activatePreview)
508 selectWindow();
509 }
510
invalidateSyncHighlight()511 void PDFDocument::invalidateSyncHighlight()
512 {
513 // This slot should be called when the graphics item pointed to by
514 // _syncHighlight goes out of scope (e.g., because the PDF changed, all pages
515 // were deleted, and, in the process, all subordinate graphics items as well).
516 _syncHighlight = NULL;
517 _syncHighlightRemover.stop();
518 }
519
clearSyncHighlight()520 void PDFDocument::clearSyncHighlight()
521 {
522 if (_syncHighlight) {
523 delete _syncHighlight;
524 _syncHighlight = NULL;
525 }
526 _syncHighlightRemover.stop();
527 }
528
clearSearchResultHighlight()529 void PDFDocument::clearSearchResultHighlight()
530 {
531 if (!widget())
532 return;
533 widget()->setCurrentSearchResultHighlightBrush(QBrush(Qt::transparent));
534 }
535
setCurrentFile(const QString & fileName)536 void PDFDocument::setCurrentFile(const QString &fileName)
537 {
538 curFile = QFileInfo(fileName).canonicalFilePath();
539 setWindowTitle(tr("%1[*] - %2").arg(TWUtils::strippedName(curFile)).arg(tr(TEXWORKS_NAME)));
540 TWApp::instance()->updateWindowMenus();
541 }
542
findDocument(const QString & fileName)543 PDFDocument *PDFDocument::findDocument(const QString &fileName)
544 {
545 QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();
546
547 foreach (QWidget *widget, qApp->topLevelWidgets()) {
548 PDFDocument *theDoc = qobject_cast<PDFDocument*>(widget);
549 if (theDoc && theDoc->curFile == canonicalFilePath)
550 return theDoc;
551 }
552 return NULL;
553 }
554
zoomToRight(QWidget * otherWindow)555 void PDFDocument::zoomToRight(QWidget *otherWindow)
556 {
557 QDesktopWidget *desktop = QApplication::desktop();
558 QRect screenRect = desktop->availableGeometry(otherWindow == NULL ? this : otherWindow);
559 screenRect.setTop(screenRect.top() + 22);
560 screenRect.setLeft((screenRect.left() + screenRect.right()) / 2 + 1);
561 screenRect.setBottom(screenRect.bottom() - 1);
562 screenRect.setRight(screenRect.right() - 1);
563 setGeometry(screenRect);
564 }
565
showPage(int page)566 void PDFDocument::showPage(int page)
567 {
568 pageLabel->setText(tr("page %1 of %2").arg(page).arg(pdfWidget->lastPage()));
569 }
570
showScale(qreal scale)571 void PDFDocument::showScale(qreal scale)
572 {
573 scaleLabel->setText(tr("%1%").arg(ROUND(scale * 10000.0) / 100.0));
574 }
575
retypeset()576 void PDFDocument::retypeset()
577 {
578 if (sourceDocList.count() > 0)
579 sourceDocList.first()->typeset();
580 }
581
interrupt()582 void PDFDocument::interrupt()
583 {
584 if (sourceDocList.count() > 0)
585 sourceDocList.first()->interrupt();
586 }
587
goToSource()588 void PDFDocument::goToSource()
589 {
590 if (sourceDocList.count() > 0)
591 sourceDocList.first()->selectWindow();
592 else
593 // should not occur, the action is supposed to be disabled
594 actionGo_to_Source->setEnabled(false);
595 }
596
changedDocument(const QWeakPointer<QtPDF::Backend::Document> newDoc)597 void PDFDocument::changedDocument(const QWeakPointer<QtPDF::Backend::Document> newDoc) {
598 updateStatusBar();
599 invalidateSyncHighlight();
600 enablePageActions(pdfWidget->currentPage());
601 }
602
enablePageActions(int pageIndex)603 void PDFDocument::enablePageActions(int pageIndex)
604 {
605 //#if !defined(Q_OS_DARWIN)
606 // On Mac OS X, disabling these leads to a crash if we hit the end of document while auto-repeating a key
607 // (seems like a Qt bug, but needs further investigation)
608 // 2008-09-07: seems to no longer be a problem, probably thanks to Qt 4.4 update
609 actionFirst_Page->setEnabled(pageIndex > 0);
610 actionPrevious_Page->setEnabled(pageIndex > 0);
611 actionNext_Page->setEnabled(pageIndex < pdfWidget->lastPage() - 1);
612 actionLast_Page->setEnabled(pageIndex < pdfWidget->lastPage() - 1);
613 //#endif
614 }
615
toggleFullScreen()616 void PDFDocument::toggleFullScreen()
617 {
618 if (windowState() & Qt::WindowFullScreen) {
619 // exiting full-screen mode
620 statusBar()->show();
621 toolBar->show();
622 showNormal();
623 actionFull_Screen->setChecked(false);
624 delete exitFullscreen;
625 }
626 else {
627 // entering full-screen mode
628 statusBar()->hide();
629 toolBar->hide();
630 showFullScreen();
631 pdfWidget->zoomFitWindow();
632 actionFull_Screen->setChecked(true);
633 exitFullscreen = new QShortcut(Qt::Key_Escape, this, SLOT(toggleFullScreen()));
634 }
635 }
636
setPageMode(const int newMode)637 void PDFDocument::setPageMode(const int newMode)
638 {
639 if (!pdfWidget)
640 return;
641
642 switch (newMode) {
643 case QtPDF::PDFDocumentView::PageMode_SinglePage:
644 case QtPDF::PDFDocumentView::PageMode_OneColumnContinuous:
645 case QtPDF::PDFDocumentView::PageMode_TwoColumnContinuous:
646 pdfWidget->setPageMode((QtPDF::PDFDocumentView::PageMode)newMode);
647 break;
648 default:
649 return;
650 }
651 }
652
updatePageMode(const QtPDF::PDFDocumentView::PageMode newMode)653 void PDFDocument::updatePageMode(const QtPDF::PDFDocumentView::PageMode newMode)
654 {
655 // Mark proper menu item
656 actionPageMode_Single->setChecked(newMode == QtPDF::PDFDocumentView::PageMode_SinglePage);
657 actionPageMode_Continuous->setChecked(newMode== QtPDF::PDFDocumentView::PageMode_OneColumnContinuous);
658 actionPageMode_TwoPagesContinuous->setChecked(newMode == QtPDF::PDFDocumentView::PageMode_TwoColumnContinuous);
659 }
660
resetMagnifier()661 void PDFDocument::resetMagnifier()
662 {
663 Q_ASSERT(pdfWidget != NULL);
664 QSETTINGS_OBJECT(settings);
665
666 if (settings.value("circularMagnifier", kDefault_CircularMagnifier).toBool())
667 pdfWidget->setMagnifierShape(QtPDF::DocumentTool::MagnifyingGlass::Magnifier_Circle);
668 else
669 pdfWidget->setMagnifierShape(QtPDF::DocumentTool::MagnifyingGlass::Magnifier_Rectangle);
670
671 pdfWidget->setMagnifierSize(magSizes[qBound(0, settings.value("magnifierSize", kDefault_MagnifierSize).toInt() - 1, 2)]);
672 }
673
setResolution(const double res)674 void PDFDocument::setResolution(const double res)
675 {
676 Q_ASSERT(pdfWidget != NULL);
677 if (res > 0)
678 pdfWidget->setResolution(res);
679 }
680
enableTypesetAction(bool enabled)681 void PDFDocument::enableTypesetAction(bool enabled)
682 {
683 actionTypeset->setEnabled(enabled);
684 }
685
updateTypesettingAction(bool processRunning)686 void PDFDocument::updateTypesettingAction(bool processRunning)
687 {
688 if (processRunning) {
689 disconnect(actionTypeset, SIGNAL(triggered()), this, SLOT(retypeset()));
690 actionTypeset->setIcon(QIcon(":/images/tango/process-stop.png"));
691 actionTypeset->setText(tr("Abort typesetting"));
692 connect(actionTypeset, SIGNAL(triggered()), this, SLOT(interrupt()));
693 enableTypesetAction(true);
694 }
695 else {
696 disconnect(actionTypeset, SIGNAL(triggered()), this, SLOT(interrupt()));
697 actionTypeset->setIcon(QIcon(":/images/images/runtool.png"));
698 actionTypeset->setText(tr("Typeset"));
699 connect(actionTypeset, SIGNAL(triggered()), this, SLOT(retypeset()));
700 }
701 }
702
dragEnterEvent(QDragEnterEvent * event)703 void PDFDocument::dragEnterEvent(QDragEnterEvent *event)
704 {
705 // Only accept files for now
706 event->ignore();
707 if (event->mimeData()->hasUrls()) {
708 const QList<QUrl> urls = event->mimeData()->urls();
709 foreach (const QUrl& url, urls) {
710 if (url.scheme() == "file") {
711 event->acceptProposedAction();
712 break;
713 }
714 }
715 }
716 }
717
dropEvent(QDropEvent * event)718 void PDFDocument::dropEvent(QDropEvent *event)
719 {
720 event->ignore();
721 if (event->mimeData()->hasUrls()) {
722 const QList<QUrl> urls = event->mimeData()->urls();
723 foreach (const QUrl& url, urls)
724 if (url.scheme() == "file")
725 TWApp::instance()->openFile(url.toLocalFile());
726 event->acceptProposedAction();
727 }
728 }
729
contextMenuEvent(QContextMenuEvent * event)730 void PDFDocument::contextMenuEvent(QContextMenuEvent *event)
731 {
732 Q_ASSERT(pdfWidget != NULL);
733 QMenu menu(this);
734
735 if (_synchronizer && _synchronizer->isValid()) {
736 QAction *act = new QAction(tr("Jump to Source"), &menu);
737 act->setData(QVariant(event->pos()));
738 connect(act, SIGNAL(triggered()), this, SLOT(jumpToSource()));
739 menu.addAction(act);
740 menu.addSeparator();
741 }
742
743 menu.addAction(tr("Zoom In"), pdfWidget, SLOT(zoomIn()));
744 menu.addAction(tr("Zoom Out"), pdfWidget, SLOT(zoomOut()));
745 menu.addAction(tr("Actual Size"), pdfWidget, SLOT(zoom100()));
746 menu.addAction(tr("Fit to Width"), pdfWidget, SLOT(zoomFitWidth()));
747 menu.addAction(tr("Fit to Window"), pdfWidget, SLOT(zoomFitWindow()));
748
749 menu.exec(event->globalPos());
750 }
751
jumpToSource()752 void PDFDocument::jumpToSource()
753 {
754 QAction *act = qobject_cast<QAction*>(sender());
755 if (!act || !pdfWidget)
756 return;
757
758 QPoint eventPos = act->data().toPoint();
759 QPointF scenePos = pdfWidget->mapToScene(pdfWidget->mapFrom(this, eventPos));
760
761 // Map to scene, then map to page
762 QtPDF::PDFDocumentScene * scene = qobject_cast<QtPDF::PDFDocumentScene*>(pdfWidget->scene());
763 if (!scene)
764 return;
765 QtPDF::PDFPageGraphicsItem * page = (QtPDF::PDFPageGraphicsItem*)(scene->pageAt(scenePos));
766 if (!page)
767 return;
768
769 syncClick(scene->pageNumFor(page), page->mapToPage(page->mapFromScene(scenePos)));
770 }
771
doFindDialog()772 void PDFDocument::doFindDialog()
773 {
774 if (PDFFindDialog::doFindDialog(this) == QDialog::Accepted)
775 doFindAgain(true);
776 }
777
doFindAgain(bool newSearch)778 void PDFDocument::doFindAgain(bool newSearch /* = false */)
779 {
780 QSETTINGS_OBJECT(settings);
781
782 QString searchText = settings.value("searchText").toString();
783 if (searchText.isEmpty())
784 return;
785
786 QtPDF::Backend::SearchFlags searchFlags;
787 QTextDocument::FindFlags flags = (QTextDocument::FindFlags)settings.value("searchFlags").toInt();
788
789 if ((flags & QTextDocument::FindCaseSensitively) == 0)
790 searchFlags |= QtPDF::Backend::Search_CaseInsensitive;
791 if ((flags & QTextDocument::FindBackward) != 0)
792 searchFlags |= QtPDF::Backend::Search_Backwards;
793
794 widget()->search(searchText, searchFlags);
795 }
796
searchResultHighlighted(const int pageNum,const QList<QPolygonF> region)797 void PDFDocument::searchResultHighlighted(const int pageNum, const QList<QPolygonF> region)
798 {
799 QSETTINGS_OBJECT(settings);
800
801 widget()->setCurrentSearchResultHighlightBrush(_searchResultHighlightBrush);
802 if (kPDFHighlightDuration > 0)
803 _searchResultHighlightRemover.start(kPDFHighlightDuration);
804
805 if (hasSyncData() && settings.value("searchPdfSync").toBool() && !region.isEmpty()) {
806 // emit a syncClick message at the center of the left edge of the (bounding)
807 // rect. To ensure hit-testing succeeds later on, we add an offset of 1e-5
808 // (for rectangles of finite width)
809 QRectF r = region[0].boundingRect();
810 QPointF pt(r.left() + 1e-5 * qMin(r.width(), 1.), r.center().y());
811 emit syncClick(pageNum, pt);
812 }
813 }
814
setDefaultScale()815 void PDFDocument::setDefaultScale() {
816 QSETTINGS_OBJECT(settings);
817 switch (settings.value("scaleOption", kDefault_PreviewScaleOption).toInt()) {
818 case 2:
819 pdfWidget->zoomFitWidth();
820 break;
821 case 3:
822 pdfWidget->zoomFitWindow();
823 break;
824 case 4:
825 pdfWidget->setZoomLevel(settings.value("previewScale", kDefault_PreviewScale).toFloat() / 100.);
826 break;
827 default:
828 pdfWidget->zoom100();
829 break;
830 }
831 }
832
maybeOpenUrl(const QUrl url)833 void PDFDocument::maybeOpenUrl(const QUrl url)
834 {
835 // Opening URLs could be a security risk, so ask the user (but make "yes,
836 // proceed the default option - after all the user typically clicked on the
837 // link deliberately)
838 if (QMessageBox::question(this, tr("Open URL"), tr("You are in the process of opening the URL %1. Opening unknown or untrusted web adresses can be a security risk.\nDo you want to continue?").arg(url.toString()),
839 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes)
840 QDesktopServices::openUrl(url);
841 }
842
maybeOpenPdf(QString filename,QtPDF::PDFDestination destination,bool newWindow)843 void PDFDocument::maybeOpenPdf(QString filename, QtPDF::PDFDestination destination, bool newWindow)
844 {
845 // Unlike in maybeOpenUrl, this function only works on local PDF files which
846 // we assume are safe.
847 // TODO: We currently ignore the value of newWindow and always open a new
848 // window. This avoids the need to update/invalidate all pointers to this
849 // PDFDocument (e.g., in the TeXDocument associated with it) to notify the
850 // other parts of the code that a completely new and unrelated document is
851 // loaded here now.
852 PDFDocument * pdf = qobject_cast<PDFDocument*>(TWApp::instance()->openFile(filename));
853 if (!pdf || !pdf->widget())
854 return;
855 pdf->widget()->goToPDFDestination(destination, false);
856 }
857
858
print()859 void PDFDocument::print()
860 {
861 // Currently, printing is not supported in a reliable, cross-platform way
862 // Instead, offer to open the document in the system's default viewer
863
864 QString msg = tr("Unfortunately, this version of %1 is unable to print Pdf documents due to various technical reasons.\n").arg(TEXWORKS_NAME);
865 msg += tr("Do you want to open the file in the default viewer for printing instead?");
866 msg += tr(" (remember to close it again to avoid access problems)");
867
868 if(QMessageBox::information(this,
869 tr("Print Pdf..."), msg,
870 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes
871 ) {
872 QDesktopServices::openUrl(QUrl::fromLocalFile(curFile));
873 }
874 }
875
showScaleContextMenu(const QPoint pos)876 void PDFDocument::showScaleContextMenu(const QPoint pos)
877 {
878 static QMenu * contextMenu = NULL;
879 static QSignalMapper * contextMenuMapper = NULL;
880 QAction * a;
881
882 if (contextMenu == NULL) {
883 contextMenu = new QMenu(this);
884 contextMenuMapper = new QSignalMapper(this);
885
886 contextMenu->addAction(actionFit_to_Width);
887 contextMenu->addAction(actionFit_to_Window);
888 contextMenu->addSeparator();
889
890 a = contextMenu->addAction(tr("Custom..."));
891 connect(a, SIGNAL(triggered()), this, SLOT(doScaleDialog()));
892
893 a = contextMenu->addAction("200%");
894 connect(a, SIGNAL(triggered()), contextMenuMapper, SLOT(map()));
895 contextMenuMapper->setMapping(a, "2");
896 a = contextMenu->addAction("150%");
897 connect(a, SIGNAL(triggered()), contextMenuMapper, SLOT(map()));
898 contextMenuMapper->setMapping(a, "1.5");
899 // "100%" corresponds to "Actual Size", but we keep the numeric value
900 // here for consistency
901 a = contextMenu->addAction("100%");
902 a->setShortcut(actionActual_Size->shortcut());
903 connect(a, SIGNAL(triggered()), contextMenuMapper, SLOT(map()));
904 contextMenuMapper->setMapping(a, "1");
905 a = contextMenu->addAction("75%");
906 connect(a, SIGNAL(triggered()), contextMenuMapper, SLOT(map()));
907 contextMenuMapper->setMapping(a, ".75");
908 a = contextMenu->addAction("50%");
909 connect(a, SIGNAL(triggered()), contextMenuMapper, SLOT(map()));
910 contextMenuMapper->setMapping(a, ".5");
911
912 connect(contextMenuMapper, SIGNAL(mapped(const QString&)), this, SLOT(setScaleFromContextMenu(const QString&)));
913 }
914
915 contextMenu->popup(scaleLabel->mapToGlobal(pos));
916 }
917
setScaleFromContextMenu(const QString & strZoom)918 void PDFDocument::setScaleFromContextMenu(const QString & strZoom)
919 {
920 bool conversionOK = false;
921 float zoom = strZoom.toFloat(&conversionOK);
922 // FIXME: This should actually use the point the context menu was opened at as
923 // anchor for zooming. Currently, arbitrary coordinates are not supported yet
924 // (and using QGraphicsView::AnchorUnderMouse would use the position of the
925 // mouse cursor when the user clicks on the respective menu item - which will
926 // be somewhere else
927 if (pdfWidget && conversionOK)
928 pdfWidget->setZoomLevel(zoom);
929 }
930
updateStatusBar()931 void PDFDocument::updateStatusBar()
932 {
933 Q_ASSERT(pdfWidget != NULL);
934 showPage(pdfWidget->currentPage() + 1);
935 showScale(pdfWidget->zoomLevel());
936 }
937
setMouseMode(const int newMode)938 void PDFDocument::setMouseMode(const int newMode)
939 {
940 Q_ASSERT(pdfWidget != NULL);
941 pdfWidget->setMouseMode((QtPDF::PDFDocumentView::MouseMode)newMode);
942 }
943
doPageDialog()944 void PDFDocument::doPageDialog()
945 {
946 bool ok;
947 Q_ASSERT(pdfWidget != NULL);
948
949 #if QT_VERSION < 0x050000
950 int pageNo = QInputDialog::getInteger(this, tr("Go to Page"),
951 tr("Page number:"), pdfWidget->currentPage() + 1,
952 1, pdfWidget->lastPage(), 1, &ok);
953 #else
954 int pageNo = QInputDialog::getInt(this, tr("Go to Page"),
955 tr("Page number:"), pdfWidget->currentPage() + 1,
956 1, pdfWidget->lastPage(), 1, &ok);
957 #endif
958 if (ok)
959 pdfWidget->goToPage(pageNo - 1);
960 }
961
doScaleDialog()962 void PDFDocument::doScaleDialog()
963 {
964 bool ok;
965 Q_ASSERT(pdfWidget != NULL);
966
967 double newScale = QInputDialog::getDouble(this, tr("Set Zoom"), tr("Zoom level:"), 100 * pdfWidget->zoomLevel(), 0, 2147483647, 0, &ok);
968 if (ok)
969 pdfWidget->setZoomLevel(newScale / 100);
970 }
971