1 //========================================================================
2 //
3 // QtPDFCore.cc
4 //
5 // Copyright 2009-2014 Glyph & Cog, LLC
6 //
7 //========================================================================
8
9 #include <aconf.h>
10
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
13 #endif
14
15 #include <math.h>
16 #include <string.h>
17 #include <QApplication>
18 #include <QClipboard>
19 #include <QDesktopServices>
20 #include <QFileInfo>
21 #include <QImage>
22 #include <QInputDialog>
23 #include <QMessageBox>
24 #include <QPainter>
25 #include <QProcess>
26 #include <QScrollBar>
27 #include <QStyle>
28 #include <QUrl>
29 #include <QWidget>
30 #include "gmem.h"
31 #include "gmempp.h"
32 #include "gfile.h"
33 #include "GString.h"
34 #include "GList.h"
35 #include "Error.h"
36 #include "GlobalParams.h"
37 #include "PDFDoc.h"
38 #include "Link.h"
39 #include "ErrorCodes.h"
40 #include "GfxState.h"
41 #include "PSOutputDev.h"
42 #include "TextOutputDev.h"
43 #include "SplashBitmap.h"
44 #include "DisplayState.h"
45 #include "TileMap.h"
46 #include "QtPDFCore.h"
47
48 //------------------------------------------------------------------------
49 // QtPDFCore
50 //------------------------------------------------------------------------
51
QtPDFCore(QWidget * viewportA,QScrollBar * hScrollBarA,QScrollBar * vScrollBarA,SplashColorPtr paperColor,SplashColorPtr matteColor,GBool reverseVideo)52 QtPDFCore::QtPDFCore(QWidget *viewportA,
53 QScrollBar *hScrollBarA, QScrollBar *vScrollBarA,
54 SplashColorPtr paperColor, SplashColorPtr matteColor,
55 GBool reverseVideo):
56 PDFCore(splashModeRGB8, 4, reverseVideo, paperColor)
57 {
58 int dpiX, dpiY;
59
60 viewport = viewportA;
61 hScrollBar = hScrollBarA;
62 vScrollBar = vScrollBarA;
63 hScrollBar->setRange(0, 0);
64 hScrollBar->setSingleStep(16);
65 vScrollBar->setRange(0, 0);
66 vScrollBar->setSingleStep(16);
67 viewport->setMouseTracking(true);
68
69 state->setMatteColor(matteColor);
70
71 oldFirstPage = -1;
72 oldMidPage = -1;
73
74 linkAction = NULL;
75 lastLinkAction = NULL;
76
77 dragging = gFalse;
78
79 panning = gFalse;
80
81 inUpdateScrollbars = gFalse;
82
83 updateCbk = NULL;
84 midPageChangedCbk = NULL;
85 preLoadCbk = NULL;
86 postLoadCbk = NULL;
87 actionCbk = NULL;
88 linkCbk = NULL;
89 selectDoneCbk = NULL;
90
91 // optional features default to on
92 hyperlinksEnabled = gTrue;
93 externalHyperlinksEnabled = gTrue;
94 selectEnabled = gTrue;
95 panEnabled = gTrue;
96 showPasswordDialog = gTrue;
97
98 // get Qt's HiDPI scale factor
99 #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
100 scaleFactor = viewport->devicePixelRatioF();
101 #elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
102 scaleFactor = viewport->devicePixelRatio();
103 #else
104 scaleFactor = 1;
105 #endif
106
107 // get the display resolution (used for HiDPI scaling)
108 dpiX = viewport->logicalDpiX();
109 dpiY = viewport->logicalDpiY();
110 displayDpi = dpiX < dpiY ? dpiX : dpiY;
111 displayDpi = (int)(displayDpi * scaleFactor);
112 }
113
~QtPDFCore()114 QtPDFCore::~QtPDFCore() {
115 }
116
117 //------------------------------------------------------------------------
118 // loadFile / displayPage / displayDest
119 //------------------------------------------------------------------------
120
loadFile(GString * fileName,GString * ownerPassword,GString * userPassword)121 int QtPDFCore::loadFile(GString *fileName, GString *ownerPassword,
122 GString *userPassword) {
123 int err;
124
125 err = PDFCore::loadFile(fileName, ownerPassword, userPassword);
126 if (err == errNone) {
127 // save the modification time
128 modTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
129
130 // update the parent window
131 if (updateCbk) {
132 (*updateCbk)(updateCbkData, doc->getFileName(), -1,
133 doc->getNumPages(), NULL);
134 }
135 oldFirstPage = oldMidPage = -1;
136 }
137 return err;
138 }
139
140 #ifdef _WIN32
loadFile(wchar_t * fileName,int fileNameLen,GString * ownerPassword,GString * userPassword)141 int QtPDFCore::loadFile(wchar_t *fileName, int fileNameLen,
142 GString *ownerPassword,
143 GString *userPassword) {
144 int err;
145
146 err = PDFCore::loadFile(fileName, fileNameLen, ownerPassword, userPassword);
147 if (err == errNone) {
148 // save the modification time
149 modTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
150
151 // update the parent window
152 if (updateCbk) {
153 (*updateCbk)(updateCbkData, doc->getFileName(), -1,
154 doc->getNumPages(), NULL);
155 }
156 oldFirstPage = oldMidPage = -1;
157 }
158 return err;
159 }
160 #endif
161
loadFile(BaseStream * stream,GString * ownerPassword,GString * userPassword)162 int QtPDFCore::loadFile(BaseStream *stream, GString *ownerPassword,
163 GString *userPassword) {
164 int err;
165
166 err = PDFCore::loadFile(stream, ownerPassword, userPassword);
167 if (err == errNone) {
168 // no file
169 modTime = QDateTime();
170
171 // update the parent window
172 if (updateCbk) {
173 (*updateCbk)(updateCbkData, doc->getFileName(), -1,
174 doc->getNumPages(), NULL);
175 }
176 oldFirstPage = oldMidPage = -1;
177 }
178 return err;
179 }
180
loadDoc(PDFDoc * docA)181 void QtPDFCore::loadDoc(PDFDoc *docA) {
182 PDFCore::loadDoc(docA);
183
184 // save the modification time
185 if (doc->getFileName()) {
186 modTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
187 } else {
188 modTime = QDateTime();
189 }
190
191 // update the parent window
192 if (updateCbk) {
193 (*updateCbk)(updateCbkData, doc->getFileName(), -1,
194 doc->getNumPages(), NULL);
195 }
196 oldFirstPage = oldMidPage = -1;
197 }
198
reload()199 int QtPDFCore::reload() {
200 int err;
201
202 err = PDFCore::reload();
203 if (err == errNone) {
204 // save the modification time
205 modTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
206
207 // update the parent window
208 if (updateCbk) {
209 (*updateCbk)(updateCbkData, doc->getFileName(), -1,
210 doc->getNumPages(), NULL);
211 }
212 oldFirstPage = oldMidPage = -1;
213 }
214 return err;
215 }
216
finishUpdate(GBool addToHist,GBool checkForChangedFile)217 void QtPDFCore::finishUpdate(GBool addToHist, GBool checkForChangedFile) {
218 int firstPage, midPage;
219
220 PDFCore::finishUpdate(addToHist, checkForChangedFile);
221 firstPage = getPageNum();
222 if (doc && firstPage != oldFirstPage && updateCbk) {
223 (*updateCbk)(updateCbkData, NULL, firstPage, -1, "");
224 }
225 oldFirstPage = firstPage;
226 midPage = getMidPageNum();
227 if (doc && midPage != oldMidPage && midPageChangedCbk) {
228 (*midPageChangedCbk)(midPageChangedCbkData, midPage);
229 }
230 oldMidPage = midPage;
231
232 linkAction = NULL;
233 lastLinkAction = NULL;
234 }
235
236 //------------------------------------------------------------------------
237 // panning and selection
238 //------------------------------------------------------------------------
239
startPan(int wx,int wy)240 void QtPDFCore::startPan(int wx, int wy) {
241 if (!panEnabled) {
242 return;
243 }
244 panning = gTrue;
245 panMX = wx;
246 panMY = wy;
247 }
248
endPan(int wx,int wy)249 void QtPDFCore::endPan(int wx, int wy) {
250 panning = gFalse;
251 }
252
startSelection(int wx,int wy,GBool extend)253 void QtPDFCore::startSelection(int wx, int wy, GBool extend) {
254 int pg, x, y;
255
256 takeFocus();
257 if (!doc || doc->getNumPages() == 0 || !selectEnabled) {
258 return;
259 }
260 if (!cvtWindowToDev(wx, wy, &pg, &x, &y)) {
261 return;
262 }
263 if (extend && hasSelection()) {
264 moveSelectionDrag(pg, x, y);
265 } else {
266 startSelectionDrag(pg, x, y);
267 }
268 if (getSelectMode() == selectModeBlock) {
269 doSetCursor(Qt::CrossCursor);
270 }
271 dragging = gTrue;
272 }
273
endSelection(int wx,int wy)274 void QtPDFCore::endSelection(int wx, int wy) {
275 LinkAction *action;
276 int pg, x, y;
277 double xu, yu;
278 GBool ok;
279
280 if (!doc || doc->getNumPages() == 0) {
281 return;
282 }
283 ok = cvtWindowToDev(wx, wy, &pg, &x, &y);
284 if (dragging) {
285 dragging = gFalse;
286 doUnsetCursor();
287 if (ok) {
288 moveSelectionDrag(pg, x, y);
289 }
290 finishSelectionDrag();
291 if (selectDoneCbk) {
292 (*selectDoneCbk)(selectDoneCbkData);
293 }
294 #ifndef NO_TEXT_SELECT
295 if (hasSelection()) {
296 copySelection(gFalse);
297 }
298 #endif
299 }
300 if (ok) {
301 if (hasSelection()) {
302 action = NULL;
303 } else {
304 cvtDevToUser(pg, x, y, &xu, &yu);
305 action = findLink(pg, xu, yu);
306 }
307 if (linkCbk && action) {
308 doLinkCbk(action);
309 }
310 if (hyperlinksEnabled && action) {
311 doAction(action);
312 }
313 }
314 }
315
mouseMove(int wx,int wy)316 void QtPDFCore::mouseMove(int wx, int wy) {
317 LinkAction *action;
318 int pg, x, y;
319 double xu, yu;
320 const char *s;
321 GBool ok, mouseOverText;
322
323 if (!doc || doc->getNumPages() == 0) {
324 return;
325 }
326 ok = cvtWindowToDev(wx, wy, &pg, &x, &y);
327 if (dragging) {
328 if (ok) {
329 moveSelectionDrag(pg, x, y);
330 }
331 } else {
332 cvtDevToUser(pg, x, y, &xu, &yu);
333
334 // check for a link
335 action = NULL;
336 if (hyperlinksEnabled && ok) {
337 action = findLink(pg, xu, yu);
338 }
339
340 // check for text
341 mouseOverText = gFalse;
342 if (!action && getSelectMode() == selectModeLinear && ok) {
343 mouseOverText = overText(pg, x, y);
344 }
345
346 // update the cursor
347 if (action) {
348 doSetCursor(Qt::PointingHandCursor);
349 } else if (mouseOverText) {
350 doSetCursor(Qt::IBeamCursor);
351 } else {
352 doUnsetCursor();
353 }
354
355 // update the link info
356 if (action != linkAction) {
357 linkAction = action;
358 if (updateCbk) {
359 //~ should pass a QString to updateCbk()
360 if (linkAction) {
361 s = getLinkInfo(linkAction).toLocal8Bit().constData();
362 } else {
363 s = "";
364 }
365 (*updateCbk)(updateCbkData, NULL, -1, -1, s);
366 }
367 }
368 }
369
370 if (panning) {
371 scrollTo(getScrollX() - (wx - panMX),
372 getScrollY() - (wy - panMY));
373 panMX = wx;
374 panMY = wy;
375 }
376 }
377
selectWord(int wx,int wy)378 void QtPDFCore::selectWord(int wx, int wy) {
379 int pg, x, y;
380
381 takeFocus();
382 if (!doc || doc->getNumPages() == 0 || !selectEnabled) {
383 return;
384 }
385 if (getSelectMode() != selectModeLinear) {
386 return;
387 }
388 if (!cvtWindowToDev(wx, wy, &pg, &x, &y)) {
389 return;
390 }
391 PDFCore::selectWord(pg, x, y);
392 }
393
selectLine(int wx,int wy)394 void QtPDFCore::selectLine(int wx, int wy) {
395 int pg, x, y;
396
397 takeFocus();
398 if (!doc || doc->getNumPages() == 0 || !selectEnabled) {
399 return;
400 }
401 if (getSelectMode() != selectModeLinear) {
402 return;
403 }
404 if (!cvtWindowToDev(wx, wy, &pg, &x, &y)) {
405 return;
406 }
407 PDFCore::selectLine(pg, x, y);
408 }
409
doLinkCbk(LinkAction * action)410 void QtPDFCore::doLinkCbk(LinkAction *action) {
411 LinkDest *dest;
412 GString *namedDest;
413 Ref pageRef;
414 int pg;
415 GString *cmd, *params;
416 char *s;
417
418 if (!linkCbk) {
419 return;
420 }
421
422 switch (action->getKind()) {
423
424 case actionGoTo:
425 dest = NULL;
426 if ((dest = ((LinkGoTo *)action)->getDest())) {
427 dest = dest->copy();
428 } else if ((namedDest = ((LinkGoTo *)action)->getNamedDest())) {
429 dest = doc->findDest(namedDest);
430 }
431 pg = 0;
432 if (dest) {
433 if (dest->isPageRef()) {
434 pageRef = dest->getPageRef();
435 pg = doc->findPage(pageRef.num, pageRef.gen);
436 } else {
437 pg = dest->getPageNum();
438 }
439 delete dest;
440 }
441 (*linkCbk)(linkCbkData, "goto", NULL, pg);
442 break;
443
444 case actionGoToR:
445 (*linkCbk)(linkCbkData, "pdf",
446 ((LinkGoToR *)action)->getFileName()->getCString(), 0);
447 break;
448
449 case actionLaunch:
450 cmd = ((LinkLaunch *)action)->getFileName()->copy();
451 s = cmd->getCString();
452 if (strcmp(s + cmd->getLength() - 4, ".pdf") &&
453 strcmp(s + cmd->getLength() - 4, ".PDF") &&
454 (params = ((LinkLaunch *)action)->getParams())) {
455 cmd->append(' ')->append(params);
456 }
457 (*linkCbk)(linkCbkData, "launch", cmd->getCString(), 0);
458 delete cmd;
459 break;
460
461 case actionURI:
462 (*linkCbk)(linkCbkData, "url",
463 ((LinkURI *)action)->getURI()->getCString(), 0);
464 break;
465
466 case actionNamed:
467 (*linkCbk)(linkCbkData, "named",
468 ((LinkNamed *)action)->getName()->getCString(), 0);
469 break;
470
471 case actionMovie:
472 case actionJavaScript:
473 case actionSubmitForm:
474 case actionHide:
475 case actionUnknown:
476 (*linkCbk)(linkCbkData, "unknown", NULL, 0);
477 break;
478 }
479 }
480
getSelectedTextQString()481 QString QtPDFCore::getSelectedTextQString() {
482 GString *s, *enc;
483 QString qs;
484 int i;
485
486 if (!doc->okToCopy()) {
487 return "";
488 }
489 if (!(s = getSelectedText())) {
490 return "";
491 }
492 enc = globalParams->getTextEncodingName();
493 if (!enc->cmp("UTF-8")) {
494 qs = QString::fromUtf8(s->getCString());
495 } else if (!enc->cmp("UCS-2")) {
496 for (i = 0; i+1 < s->getLength(); i += 2) {
497 qs.append((QChar)(((s->getChar(i) & 0xff) << 8) +
498 (s->getChar(i+1) & 0xff)));
499 }
500 } else {
501 qs = QString(s->getCString());
502 }
503 delete s;
504 delete enc;
505 return qs;
506 }
507
copySelection(GBool toClipboard)508 void QtPDFCore::copySelection(GBool toClipboard) {
509 QString qs;
510
511 // only X11 has the copy-on-select behavior
512 if (!toClipboard && !QApplication::clipboard()->supportsSelection()) {
513 return;
514 }
515 if (!doc->okToCopy()) {
516 return;
517 }
518 if (hasSelection()) {
519 QApplication::clipboard()->setText(getSelectedTextQString(),
520 toClipboard ? QClipboard::Clipboard
521 : QClipboard::Selection);
522 }
523 }
524
525 //------------------------------------------------------------------------
526 // hyperlinks
527 //------------------------------------------------------------------------
528
doAction(LinkAction * action)529 GBool QtPDFCore::doAction(LinkAction *action) {
530 LinkActionKind kind;
531 LinkDest *dest;
532 GString *namedDest;
533 char *s;
534 GString *fileName, *fileName2, *params;
535 GString *cmd;
536 GString *actionName;
537 Object movieAnnot, obj1, obj2;
538 GString *msg;
539 int i;
540
541 switch (kind = action->getKind()) {
542
543 // GoTo / GoToR action
544 case actionGoTo:
545 case actionGoToR:
546 if (kind == actionGoTo) {
547 dest = NULL;
548 namedDest = NULL;
549 if ((dest = ((LinkGoTo *)action)->getDest())) {
550 dest = dest->copy();
551 } else if ((namedDest = ((LinkGoTo *)action)->getNamedDest())) {
552 namedDest = namedDest->copy();
553 }
554 } else {
555 if (!externalHyperlinksEnabled) {
556 return gFalse;
557 }
558 dest = NULL;
559 namedDest = NULL;
560 if ((dest = ((LinkGoToR *)action)->getDest())) {
561 dest = dest->copy();
562 } else if ((namedDest = ((LinkGoToR *)action)->getNamedDest())) {
563 namedDest = namedDest->copy();
564 }
565 s = ((LinkGoToR *)action)->getFileName()->getCString();
566 if (isAbsolutePath(s)) {
567 fileName = new GString(s);
568 } else {
569 fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s);
570 }
571 if (loadFile(fileName) != errNone) {
572 if (dest) {
573 delete dest;
574 }
575 if (namedDest) {
576 delete namedDest;
577 }
578 delete fileName;
579 return gFalse;
580 }
581 delete fileName;
582 }
583 if (namedDest) {
584 dest = doc->findDest(namedDest);
585 delete namedDest;
586 }
587 if (dest) {
588 displayDest(dest);
589 delete dest;
590 } else {
591 if (kind == actionGoToR) {
592 displayPage(1, gFalse, gFalse, gTrue);
593 }
594 }
595 break;
596
597 // Launch action
598 case actionLaunch:
599 if (!externalHyperlinksEnabled) {
600 return gFalse;
601 }
602 fileName = ((LinkLaunch *)action)->getFileName();
603 s = fileName->getCString();
604 if (fileName->getLength() >= 4 &&
605 (!strcmp(s + fileName->getLength() - 4, ".pdf") ||
606 !strcmp(s + fileName->getLength() - 4, ".PDF"))) {
607 if (isAbsolutePath(s)) {
608 fileName = fileName->copy();
609 } else {
610 fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s);
611 }
612 if (loadFile(fileName) != errNone) {
613 delete fileName;
614 return gFalse;
615 }
616 delete fileName;
617 displayPage(1, gFalse, gFalse, gTrue);
618 } else {
619 cmd = fileName->copy();
620 if ((params = ((LinkLaunch *)action)->getParams())) {
621 cmd->append(' ')->append(params);
622 }
623 if (globalParams->getLaunchCommand()) {
624 cmd->insert(0, ' ');
625 cmd->insert(0, globalParams->getLaunchCommand());
626 QProcess::startDetached(cmd->getCString());
627 } else {
628 msg = new GString("About to execute the command:\n");
629 msg->append(cmd);
630 if (QMessageBox::question(viewport, "PDF Launch Link",
631 msg->getCString(),
632 QMessageBox::Ok | QMessageBox::Cancel,
633 QMessageBox::Ok)
634 == QMessageBox::Ok) {
635 QProcess::startDetached(cmd->getCString());
636 }
637 delete msg;
638 }
639 delete cmd;
640 }
641 break;
642
643 // URI action
644 case actionURI:
645 if (!externalHyperlinksEnabled) {
646 return gFalse;
647 }
648 QDesktopServices::openUrl(QUrl(((LinkURI *)action)->getURI()->getCString(),
649 QUrl::TolerantMode));
650 break;
651
652 // Named action
653 case actionNamed:
654 actionName = ((LinkNamed *)action)->getName();
655 if (!actionName->cmp("NextPage")) {
656 gotoNextPage(1, gTrue);
657 } else if (!actionName->cmp("PrevPage")) {
658 gotoPrevPage(1, gTrue, gFalse);
659 } else if (!actionName->cmp("FirstPage")) {
660 displayPage(1, gTrue, gFalse, gTrue);
661 } else if (!actionName->cmp("LastPage")) {
662 displayPage(doc->getNumPages(), gTrue, gFalse, gTrue);
663 } else if (!actionName->cmp("GoBack")) {
664 goBackward();
665 } else if (!actionName->cmp("GoForward")) {
666 goForward();
667 } else if (!actionName->cmp("Quit")) {
668 if (actionCbk) {
669 (*actionCbk)(actionCbkData, actionName->getCString());
670 }
671 } else {
672 error(errSyntaxError, -1,
673 "Unknown named action: '{0:t}'", actionName);
674 return gFalse;
675 }
676 break;
677
678 // Movie action
679 case actionMovie:
680 if (!externalHyperlinksEnabled) {
681 return gFalse;
682 }
683 if (!(cmd = globalParams->getMovieCommand())) {
684 error(errConfig, -1, "No movieCommand defined in config file");
685 return gFalse;
686 }
687 if (((LinkMovie *)action)->hasAnnotRef()) {
688 doc->getXRef()->fetch(((LinkMovie *)action)->getAnnotRef()->num,
689 ((LinkMovie *)action)->getAnnotRef()->gen,
690 &movieAnnot);
691 } else {
692 //~ need to use the correct page num here
693 doc->getCatalog()->getPage(tileMap->getFirstPage())->getAnnots(&obj1);
694 if (obj1.isArray()) {
695 for (i = 0; i < obj1.arrayGetLength(); ++i) {
696 if (obj1.arrayGet(i, &movieAnnot)->isDict()) {
697 if (movieAnnot.dictLookup("Subtype", &obj2)->isName("Movie")) {
698 obj2.free();
699 break;
700 }
701 obj2.free();
702 }
703 movieAnnot.free();
704 }
705 obj1.free();
706 }
707 }
708 if (movieAnnot.isDict()) {
709 if (movieAnnot.dictLookup("Movie", &obj1)->isDict()) {
710 if (obj1.dictLookup("F", &obj2)) {
711 if ((fileName = LinkAction::getFileSpecName(&obj2))) {
712 if (!isAbsolutePath(fileName->getCString())) {
713 fileName2 = appendToPath(
714 grabPath(doc->getFileName()->getCString()),
715 fileName->getCString());
716 delete fileName;
717 fileName = fileName2;
718 }
719 runCommand(cmd, fileName);
720 delete fileName;
721 }
722 obj2.free();
723 }
724 obj1.free();
725 }
726 }
727 movieAnnot.free();
728 break;
729
730 // unimplemented actions
731 case actionJavaScript:
732 case actionSubmitForm:
733 case actionHide:
734 return gFalse;
735
736 // unknown action type
737 case actionUnknown:
738 error(errSyntaxError, -1, "Unknown link action type: '{0:t}'",
739 ((LinkUnknown *)action)->getAction());
740 return gFalse;
741 }
742
743 return gTrue;
744 }
745
getLinkInfo(LinkAction * action)746 QString QtPDFCore::getLinkInfo(LinkAction *action) {
747 LinkDest *dest;
748 GString *namedDest;
749 Ref pageRef;
750 int pg;
751 QString info;
752
753 if (action == lastLinkAction && !lastLinkActionInfo.isEmpty()) {
754 return lastLinkActionInfo;
755 }
756
757 switch (action->getKind()) {
758 case actionGoTo:
759 dest = NULL;
760 if ((dest = ((LinkGoTo *)action)->getDest())) {
761 dest = dest->copy();
762 } else if ((namedDest = ((LinkGoTo *)action)->getNamedDest())) {
763 dest = doc->findDest(namedDest);
764 }
765 pg = 0;
766 if (dest) {
767 if (dest->isPageRef()) {
768 pageRef = dest->getPageRef();
769 pg = doc->findPage(pageRef.num, pageRef.gen);
770 } else {
771 pg = dest->getPageNum();
772 }
773 delete dest;
774 }
775 if (pg) {
776 info = QString("[page ") + QString::number(pg) + QString("]");
777 } else {
778 info = "[internal]";
779 }
780 break;
781 case actionGoToR:
782 info = QString(((LinkGoToR *)action)->getFileName()->getCString());
783 break;
784 case actionLaunch:
785 info = QString(((LinkLaunch *)action)->getFileName()->getCString());
786 break;
787 case actionURI:
788 info = QString(((LinkURI *)action)->getURI()->getCString());
789 break;
790 case actionNamed:
791 info = QString(((LinkNamed *)action)->getName()->getCString());
792 break;
793 case actionMovie:
794 info = "[movie]";
795 break;
796 case actionJavaScript:
797 case actionSubmitForm:
798 case actionHide:
799 case actionUnknown:
800 default:
801 info = "[unknown]";
802 break;
803 }
804
805 lastLinkAction = action;
806 lastLinkActionInfo = info;
807
808 return info;
809 }
810
811 // Run a command, given a <cmdFmt> string with one '%s' in it, and an
812 // <arg> string to insert in place of the '%s'.
runCommand(GString * cmdFmt,GString * arg)813 void QtPDFCore::runCommand(GString *cmdFmt, GString *arg) {
814 GString *cmd;
815 char *s;
816
817 if ((s = strstr(cmdFmt->getCString(), "%s"))) {
818 cmd = mungeURL(arg);
819 cmd->insert(0, cmdFmt->getCString(),
820 (int)(s - cmdFmt->getCString()));
821 cmd->append(s + 2);
822 } else {
823 cmd = cmdFmt->copy();
824 }
825 QProcess::startDetached(cmd->getCString());
826 delete cmd;
827 }
828
829 // Escape any characters in a URL which might cause problems when
830 // calling system().
mungeURL(GString * url)831 GString *QtPDFCore::mungeURL(GString *url) {
832 static const char *allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
833 "abcdefghijklmnopqrstuvwxyz"
834 "0123456789"
835 "-_.~/?:@&=+,#%";
836 GString *newURL;
837 char c;
838 int i;
839
840 newURL = new GString();
841 for (i = 0; i < url->getLength(); ++i) {
842 c = url->getChar(i);
843 if (strchr(allowed, c)) {
844 newURL->append(c);
845 } else {
846 newURL->appendf("%{0:02x}", c & 0xff);
847 }
848 }
849 return newURL;
850 }
851
852 //------------------------------------------------------------------------
853 // find
854 //------------------------------------------------------------------------
855
find(char * s,GBool caseSensitive,GBool next,GBool backward,GBool wholeWord,GBool onePageOnly)856 GBool QtPDFCore::find(char *s, GBool caseSensitive, GBool next,
857 GBool backward, GBool wholeWord, GBool onePageOnly) {
858 if (!PDFCore::find(s, caseSensitive, next,
859 backward, wholeWord, onePageOnly)) {
860 return gFalse;
861 }
862 #ifndef NO_TEXT_SELECT
863 copySelection(gFalse);
864 #endif
865 return gTrue;
866 }
867
findU(Unicode * u,int len,GBool caseSensitive,GBool next,GBool backward,GBool wholeWord,GBool onePageOnly)868 GBool QtPDFCore::findU(Unicode *u, int len, GBool caseSensitive,
869 GBool next, GBool backward,
870 GBool wholeWord, GBool onePageOnly) {
871 if (!PDFCore::findU(u, len, caseSensitive, next,
872 backward, wholeWord, onePageOnly)) {
873 return gFalse;
874 }
875 #ifndef NO_TEXT_SELECT
876 copySelection(gFalse);
877 #endif
878 return gTrue;
879 }
880
881 //------------------------------------------------------------------------
882 // misc access
883 //------------------------------------------------------------------------
884
setBusyCursor(GBool busy)885 void QtPDFCore::setBusyCursor(GBool busy) {
886 if (busy) {
887 doSetCursor(Qt::WaitCursor);
888 } else {
889 doUnsetCursor();
890 }
891 }
892
doSetCursor(const QCursor & cursor)893 void QtPDFCore::doSetCursor(const QCursor &cursor) {
894 #ifndef QT_NO_CURSOR
895 viewport->setCursor(cursor);
896 #endif
897 }
898
doUnsetCursor()899 void QtPDFCore::doUnsetCursor() {
900 #ifndef QT_NO_CURSOR
901 viewport->unsetCursor();
902 #endif
903 }
904
takeFocus()905 void QtPDFCore::takeFocus() {
906 viewport->setFocus(Qt::OtherFocusReason);
907 }
908
getBestSize()909 QSize QtPDFCore::getBestSize() {
910 DisplayMode mode;
911 double zoomPercent;
912 int w, h, pg, rot;
913
914 if (!doc || doc->getNumPages() == 0) {
915 //~ what should this return?
916 return QSize(612, 792);
917 }
918 mode = state->getDisplayMode();
919 pg = tileMap->getFirstPage();
920 rot = (state->getRotate() + doc->getPageRotate(pg)) % 360;
921 zoomPercent = state->getZoom();
922 if (zoomPercent < 0) {
923 zoomPercent = globalParams->getDefaultFitZoom();
924 if (zoomPercent <= 0) {
925 zoomPercent = (int)((100 * displayDpi) / 72.0 + 0.5);
926 if (zoomPercent < 100) {
927 zoomPercent = 100;
928 }
929 }
930 }
931 if (rot == 90 || rot == 270) {
932 w = (int)(doc->getPageCropHeight(pg) * 0.01 * zoomPercent + 0.5);
933 h = (int)(doc->getPageCropWidth(pg) * 0.01 * zoomPercent + 0.5);
934 } else {
935 w = (int)(doc->getPageCropWidth(pg) * 0.01 * zoomPercent + 0.5);
936 h = (int)(doc->getPageCropHeight(pg) * 0.01 * zoomPercent + 0.5);
937 }
938 if (mode == displayContinuous) {
939 w += QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent);
940 h += tileMap->getContinuousPageSpacing();
941 } else if (mode == displaySideBySideContinuous) {
942 w = w * 2
943 + tileMap->getHorizContinuousPageSpacing()
944 + QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent);
945 h += tileMap->getContinuousPageSpacing();
946 } else if (mode == displayHorizontalContinuous) {
947 w += tileMap->getHorizContinuousPageSpacing();
948 h += QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent);
949 } else if (mode == displaySideBySideSingle) {
950 w = w * 2 + tileMap->getHorizContinuousPageSpacing();
951 }
952 //~ these additions are a kludge to make this work -- 2 pixels are
953 //~ padding in the QAbstractScrollArea; not sure where the rest go
954 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
955 w += 6;
956 h += 2;
957 #else
958 w += 10;
959 h += 4;
960 #endif
961 return QSize((int)(w / scaleFactor), (int)(h / scaleFactor));
962 }
963
964 //------------------------------------------------------------------------
965 // GUI code
966 //------------------------------------------------------------------------
967
resizeEvent()968 void QtPDFCore::resizeEvent() {
969 setWindowSize((int)(viewport->width() * scaleFactor),
970 (int)(viewport->height() * scaleFactor));
971 }
972
paintEvent(int x,int y,int w,int h)973 void QtPDFCore::paintEvent(int x, int y, int w, int h) {
974 SplashBitmap *bitmap;
975 GBool wholeWindow;
976
977 QPainter painter(viewport);
978 wholeWindow = x == 0 && y == 0 &&
979 w == viewport->width() && h == viewport->height();
980 bitmap = getWindowBitmap(wholeWindow);
981 QImage image(bitmap->getDataPtr(), bitmap->getWidth(),
982 bitmap->getHeight(), QImage::Format_RGB888);
983 if (scaleFactor == 1) {
984 painter.drawImage(QRect(x, y, w, h), image, QRect(x, y, w, h));
985 } else {
986 painter.drawImage(QRectF(x, y, w, h), image,
987 QRectF(x * scaleFactor, y * scaleFactor,
988 w * scaleFactor, h * scaleFactor));
989 }
990 if (paintDoneCbk) {
991 (*paintDoneCbk)(paintDoneCbkData, (bool)isBitmapFinished());
992 }
993 }
994
scrollEvent()995 void QtPDFCore::scrollEvent() {
996 // avoid loops, e.g., scrollTo -> finishUpdate -> updateScrollbars ->
997 // hScrollbar.setValue -> scrollContentsBy -> scrollEvent -> scrollTo
998 if (inUpdateScrollbars) {
999 return;
1000 }
1001 scrollTo(hScrollBar->value(), vScrollBar->value());
1002 }
1003
tick()1004 void QtPDFCore::tick() {
1005 PDFCore::tick();
1006 }
1007
invalidate(int x,int y,int w,int h)1008 void QtPDFCore::invalidate(int x, int y, int w, int h) {
1009 int xx, yy, ww, hh;
1010
1011 if (scaleFactor == 1) {
1012 viewport->update(x, y, w, h);
1013 } else {
1014 xx = (int)(x / scaleFactor);
1015 yy = (int)(y / scaleFactor);
1016 ww = (int)ceil((x + w) / scaleFactor) - xx;
1017 hh = (int)ceil((y + h) / scaleFactor) - yy;
1018 viewport->update(xx, yy, ww, hh);
1019 }
1020 }
1021
updateScrollbars()1022 void QtPDFCore::updateScrollbars() {
1023 int winW, winH, horizLimit, vertLimit, horizMax, vertMax;
1024 bool vScrollBarVisible, hScrollBarVisible;
1025
1026 inUpdateScrollbars = gTrue;
1027
1028 winW = state->getWinW();
1029 winH = state->getWinH();
1030 tileMap->getScrollLimits(&horizLimit, &vertLimit);
1031
1032 if (horizLimit > winW) {
1033 horizMax = horizLimit - winW;
1034 } else {
1035 horizMax = 0;
1036 }
1037 if (vertLimit > winH) {
1038 vertMax = vertLimit - winH;
1039 } else {
1040 vertMax = 0;
1041 }
1042
1043 // Problem case A: in fixed zoom, there is a case where the page
1044 // just barely fits in the window; if the scrollbars are visible,
1045 // they reduce the available window size enough that they are
1046 // necessary, i.e., the scrollbars are only necessary if they're
1047 // visible -- so check for that situation and force the scrollbars
1048 // to be hidden.
1049 // NB: {h,v}ScrollBar->isVisible() are unreliable at startup, so
1050 // we compare the viewport size to the ScrollArea size (with
1051 // some slack for margins)
1052 vScrollBarVisible =
1053 viewport->parentWidget()->width() - viewport->width() > 8;
1054 hScrollBarVisible =
1055 viewport->parentWidget()->height() - viewport->height() > 8;
1056 if (state->getZoom() >= 0 &&
1057 vScrollBarVisible &&
1058 hScrollBarVisible &&
1059 horizMax <= vScrollBar->width() &&
1060 vertMax <= hScrollBar->height()) {
1061 horizMax = 0;
1062 vertMax = 0;
1063 }
1064
1065 // Problem case B: in fit-to-width mode, with the vertical scrollbar
1066 // visible, if the window is just tall enough to fit the page, then
1067 // the vertical scrollbar will be hidden, resulting in a wider
1068 // window, resulting in a taller page (because of fit-to-width),
1069 // resulting in the scrollbar being unhidden, in an infinite loop --
1070 // so always force the vertical scroll bar to be visible in
1071 // fit-to-width mode (and in fit-to-page cases where the vertical
1072 // scrollbar is potentially visible).
1073 if (state->getZoom() == zoomWidth ||
1074 (state->getZoom() == zoomPage &&
1075 (state->getDisplayMode() == displayContinuous ||
1076 state->getDisplayMode() == displaySideBySideContinuous))) {
1077 if (vertMax == 0) {
1078 vertMax = 1;
1079 }
1080
1081 // Problem case C: same as case B, but for fit-to-height mode and
1082 // the horizontal scrollbar.
1083 } else if (state->getZoom() == zoomHeight ||
1084 (state->getZoom() == zoomPage &&
1085 state->getDisplayMode() == displayHorizontalContinuous)) {
1086 if (horizMax == 0) {
1087 horizMax = 1;
1088 }
1089 }
1090
1091 hScrollBar->setMaximum(horizMax);
1092 hScrollBar->setPageStep(winW);
1093 hScrollBar->setValue(state->getScrollX());
1094
1095 vScrollBar->setMaximum(vertMax);
1096 vScrollBar->setPageStep(winH);
1097 vScrollBar->setValue(state->getScrollY());
1098
1099 inUpdateScrollbars = gFalse;
1100 }
1101
checkForNewFile()1102 GBool QtPDFCore::checkForNewFile() {
1103 QDateTime newModTime;
1104
1105 if (doc->getFileName()) {
1106 newModTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
1107 if (newModTime != modTime) {
1108 modTime = newModTime;
1109 return gTrue;
1110 }
1111 }
1112 return gFalse;
1113 }
1114
preLoad()1115 void QtPDFCore::preLoad() {
1116 if (preLoadCbk) {
1117 (*preLoadCbk)(preLoadCbkData);
1118 }
1119 }
1120
postLoad()1121 void QtPDFCore::postLoad() {
1122 if (postLoadCbk) {
1123 (*postLoadCbk)(postLoadCbkData);
1124 }
1125 }
1126
1127 //------------------------------------------------------------------------
1128 // password dialog
1129 //------------------------------------------------------------------------
1130
getPassword()1131 GString *QtPDFCore::getPassword() {
1132 QString s;
1133 bool ok;
1134
1135 if (!showPasswordDialog) {
1136 return NULL;
1137 }
1138 s = QInputDialog::getText(viewport, "PDF Password",
1139 "This document requires a password",
1140 QLineEdit::Password, "", &ok, Qt::Dialog);
1141 if (ok) {
1142 return new GString(s.toLocal8Bit().constData());
1143 } else {
1144 return NULL;
1145 }
1146 }
1147