"Fossies" - the Fresh Open Source Software Archive

Member "xpdf-4.04/xpdf-qt/QtPDFCore.cc" (18 Apr 2022, 30301 Bytes) of package /linux/misc/xpdf-4.04.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "QtPDFCore.cc" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.03_vs_4.04.

    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 
   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 
  114 QtPDFCore::~QtPDFCore() {
  115 }
  116 
  117 //------------------------------------------------------------------------
  118 // loadFile / displayPage / displayDest
  119 //------------------------------------------------------------------------
  120 
  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
  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 
  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 
  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 
  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 
  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 
  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 
  249 void QtPDFCore::endPan(int wx, int wy) {
  250   panning = gFalse;
  251 }
  252 
  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 
  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 
  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 
  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 #ifndef NO_TEXT_SELECT
  393   if (hasSelection()) {
  394     copySelection(gFalse);
  395   }
  396 #endif
  397 }
  398 
  399 void QtPDFCore::selectLine(int wx, int wy) {
  400   int pg, x, y;
  401 
  402   takeFocus();
  403   if (!doc || doc->getNumPages() == 0 || !selectEnabled) {
  404     return;
  405   }
  406   if (getSelectMode() != selectModeLinear) {
  407     return;
  408   }
  409   if (!cvtWindowToDev(wx, wy, &pg, &x, &y)) {
  410     return;
  411   }
  412   PDFCore::selectLine(pg, x, y);
  413 #ifndef NO_TEXT_SELECT
  414   if (hasSelection()) {
  415     copySelection(gFalse);
  416   }
  417 #endif
  418 }
  419 
  420 void QtPDFCore::doLinkCbk(LinkAction *action) {
  421   LinkDest *dest;
  422   GString *namedDest;
  423   Ref pageRef;
  424   int pg;
  425   GString *cmd, *params;
  426   char *s;
  427 
  428   if (!linkCbk) {
  429     return;
  430   }
  431 
  432   switch (action->getKind()) {
  433 
  434   case actionGoTo:
  435     dest = NULL;
  436     if ((dest = ((LinkGoTo *)action)->getDest())) {
  437       dest = dest->copy();
  438     } else if ((namedDest = ((LinkGoTo *)action)->getNamedDest())) {
  439       dest = doc->findDest(namedDest);
  440     }
  441     pg = 0;
  442     if (dest) {
  443       if (dest->isPageRef()) {
  444     pageRef = dest->getPageRef();
  445     pg = doc->findPage(pageRef.num, pageRef.gen);
  446       } else {
  447     pg = dest->getPageNum();
  448       }
  449       delete dest;
  450     }
  451     (*linkCbk)(linkCbkData, "goto", NULL, pg);
  452     break;
  453 
  454   case actionGoToR:
  455     (*linkCbk)(linkCbkData, "pdf",
  456            ((LinkGoToR *)action)->getFileName()->getCString(), 0);
  457     break;
  458 
  459   case actionLaunch:
  460     cmd = ((LinkLaunch *)action)->getFileName()->copy();
  461     s = cmd->getCString();
  462     if (strcmp(s + cmd->getLength() - 4, ".pdf") &&
  463     strcmp(s + cmd->getLength() - 4, ".PDF") &&
  464     (params = ((LinkLaunch *)action)->getParams())) {
  465       cmd->append(' ')->append(params);
  466     }
  467     (*linkCbk)(linkCbkData, "launch", cmd->getCString(), 0);
  468     delete cmd;
  469     break;
  470 
  471   case actionURI:
  472     (*linkCbk)(linkCbkData, "url",
  473            ((LinkURI *)action)->getURI()->getCString(), 0);
  474     break;
  475 
  476   case actionNamed:
  477     (*linkCbk)(linkCbkData, "named",
  478            ((LinkNamed *)action)->getName()->getCString(), 0);
  479     break;
  480 
  481   case actionMovie:
  482   case actionJavaScript:
  483   case actionSubmitForm:
  484   case actionHide:
  485   case actionUnknown:
  486     (*linkCbk)(linkCbkData, "unknown", NULL, 0);
  487     break;
  488   }
  489 }
  490 
  491 QString QtPDFCore::getSelectedTextQString() {
  492   GString *s, *enc;
  493   QString qs;
  494   int i;
  495 
  496   if (!doc->okToCopy()) {
  497     return "";
  498   }
  499   if (!(s = getSelectedText())) {
  500     return "";
  501   }
  502   enc = globalParams->getTextEncodingName();
  503   if (!enc->cmp("UTF-8")) {
  504     qs = QString::fromUtf8(s->getCString());
  505   } else if (!enc->cmp("UCS-2")) {
  506     for (i = 0; i+1 < s->getLength(); i += 2) {
  507       qs.append((QChar)(((s->getChar(i) & 0xff) << 8) +
  508             (s->getChar(i+1) & 0xff)));
  509     }
  510   } else {
  511     qs = QString(s->getCString());
  512   }
  513   delete s;
  514   delete enc;
  515   return qs;
  516 }
  517 
  518 void QtPDFCore::copySelection(GBool toClipboard) {
  519   QString qs;
  520 
  521   // only X11 has the copy-on-select behavior
  522   if (!toClipboard && !QApplication::clipboard()->supportsSelection()) {
  523     return;
  524   }
  525   if (!doc->okToCopy()) {
  526     return;
  527   }
  528   if (hasSelection()) {
  529     QApplication::clipboard()->setText(getSelectedTextQString(),
  530                        toClipboard ? QClipboard::Clipboard
  531                                : QClipboard::Selection);
  532   }
  533 }
  534 
  535 //------------------------------------------------------------------------
  536 // hyperlinks
  537 //------------------------------------------------------------------------
  538 
  539 GBool QtPDFCore::doAction(LinkAction *action) {
  540   LinkActionKind kind;
  541   LinkDest *dest;
  542   GString *namedDest;
  543   char *s;
  544   GString *fileName, *fileName2, *params;
  545   GString *cmd;
  546   GString *actionName;
  547   Object movieAnnot, obj1, obj2;
  548   GString *msg;
  549   int i;
  550 
  551   switch (kind = action->getKind()) {
  552 
  553   // GoTo / GoToR action
  554   case actionGoTo:
  555   case actionGoToR:
  556     if (kind == actionGoTo) {
  557       dest = NULL;
  558       namedDest = NULL;
  559       if ((dest = ((LinkGoTo *)action)->getDest())) {
  560     dest = dest->copy();
  561       } else if ((namedDest = ((LinkGoTo *)action)->getNamedDest())) {
  562     namedDest = namedDest->copy();
  563       }
  564     } else {
  565       if (!externalHyperlinksEnabled) {
  566     return gFalse;
  567       }
  568       dest = NULL;
  569       namedDest = NULL;
  570       if ((dest = ((LinkGoToR *)action)->getDest())) {
  571     dest = dest->copy();
  572       } else if ((namedDest = ((LinkGoToR *)action)->getNamedDest())) {
  573     namedDest = namedDest->copy();
  574       }
  575       s = ((LinkGoToR *)action)->getFileName()->getCString();
  576       if (isAbsolutePath(s)) {
  577     fileName = new GString(s);
  578       } else {
  579     fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s);
  580       }
  581       if (loadFile(fileName) != errNone) {
  582     if (dest) {
  583       delete dest;
  584     }
  585     if (namedDest) {
  586       delete namedDest;
  587     }
  588     delete fileName;
  589     return gFalse;
  590       }
  591       delete fileName;
  592     }
  593     if (namedDest) {
  594       dest = doc->findDest(namedDest);
  595       delete namedDest;
  596     }
  597     if (dest) {
  598       displayDest(dest);
  599       delete dest;
  600     } else {
  601       if (kind == actionGoToR) {
  602     displayPage(1, gFalse, gFalse, gTrue);
  603       }
  604     }
  605     break;
  606 
  607   // Launch action
  608   case actionLaunch:
  609     if (!externalHyperlinksEnabled) {
  610       return gFalse;
  611     }
  612     fileName = ((LinkLaunch *)action)->getFileName();
  613     s = fileName->getCString();
  614     if (fileName->getLength() >= 4 &&
  615     (!strcmp(s + fileName->getLength() - 4, ".pdf") ||
  616      !strcmp(s + fileName->getLength() - 4, ".PDF"))) {
  617       if (isAbsolutePath(s)) {
  618     fileName = fileName->copy();
  619       } else {
  620     fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s);
  621       }
  622       if (loadFile(fileName) != errNone) {
  623     delete fileName;
  624     return gFalse;
  625       }
  626       delete fileName;
  627       displayPage(1, gFalse, gFalse, gTrue);
  628     } else {
  629       cmd = fileName->copy();
  630       if ((params = ((LinkLaunch *)action)->getParams())) {
  631     cmd->append(' ')->append(params);
  632       }
  633       if (globalParams->getLaunchCommand()) {
  634     cmd->insert(0, ' ');
  635     cmd->insert(0, globalParams->getLaunchCommand());
  636 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
  637     QString cmdStr(cmd->getCString());
  638     QStringList tokens = QProcess::splitCommand(cmdStr);
  639     if (!tokens.isEmpty()) {
  640       QString program = tokens[0];
  641       tokens.removeFirst();
  642       QProcess::startDetached(program, tokens);
  643     }
  644 #else
  645     QProcess::startDetached(cmd->getCString());
  646 #endif
  647       } else {
  648     msg = new GString("About to execute the command:\n");
  649     msg->append(cmd);
  650     if (QMessageBox::question(viewport, "PDF Launch Link",
  651                   msg->getCString(),
  652                   QMessageBox::Ok | QMessageBox::Cancel,
  653                   QMessageBox::Ok)
  654         == QMessageBox::Ok) {
  655 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
  656       QString cmdStr(cmd->getCString());
  657       QStringList tokens = QProcess::splitCommand(cmdStr);
  658       if (!tokens.isEmpty()) {
  659         QString program = tokens[0];
  660         tokens.removeFirst();
  661         QProcess::startDetached(program, tokens);
  662       }
  663 #else
  664       QProcess::startDetached(cmd->getCString());
  665 #endif
  666     }
  667     delete msg;
  668       }
  669       delete cmd;
  670     }
  671     break;
  672 
  673   // URI action
  674   case actionURI:
  675     if (!externalHyperlinksEnabled) {
  676       return gFalse;
  677     }
  678     QDesktopServices::openUrl(QUrl(((LinkURI *)action)->getURI()->getCString(),
  679                    QUrl::TolerantMode));
  680     break;
  681 
  682   // Named action
  683   case actionNamed:
  684     actionName = ((LinkNamed *)action)->getName();
  685     if (!actionName->cmp("NextPage")) {
  686       gotoNextPage(1, gTrue);
  687     } else if (!actionName->cmp("PrevPage")) {
  688       gotoPrevPage(1, gTrue, gFalse);
  689     } else if (!actionName->cmp("FirstPage")) {
  690       displayPage(1, gTrue, gFalse, gTrue);
  691     } else if (!actionName->cmp("LastPage")) {
  692       displayPage(doc->getNumPages(), gTrue, gFalse, gTrue);
  693     } else if (!actionName->cmp("GoBack")) {
  694       goBackward();
  695     } else if (!actionName->cmp("GoForward")) {
  696       goForward();
  697     } else if (!actionName->cmp("Quit")) {
  698       if (actionCbk) {
  699     (*actionCbk)(actionCbkData, actionName->getCString());
  700       }
  701     } else {
  702       error(errSyntaxError, -1,
  703         "Unknown named action: '{0:t}'", actionName);
  704       return gFalse;
  705     }
  706     break;
  707 
  708   // Movie action
  709   case actionMovie:
  710     if (!externalHyperlinksEnabled) {
  711       return gFalse;
  712     }
  713     if (!(cmd = globalParams->getMovieCommand())) {
  714       error(errConfig, -1, "No movieCommand defined in config file");
  715       return gFalse;
  716     }
  717     if (((LinkMovie *)action)->hasAnnotRef()) {
  718       doc->getXRef()->fetch(((LinkMovie *)action)->getAnnotRef()->num,
  719                 ((LinkMovie *)action)->getAnnotRef()->gen,
  720                 &movieAnnot);
  721     } else {
  722       //~ need to use the correct page num here
  723       doc->getCatalog()->getPage(tileMap->getFirstPage())->getAnnots(&obj1);
  724       if (obj1.isArray()) {
  725     for (i = 0; i < obj1.arrayGetLength(); ++i) {
  726       if (obj1.arrayGet(i, &movieAnnot)->isDict()) {
  727         if (movieAnnot.dictLookup("Subtype", &obj2)->isName("Movie")) {
  728           obj2.free();
  729           break;
  730         }
  731         obj2.free();
  732       }
  733       movieAnnot.free();
  734     }
  735     obj1.free();
  736       }
  737     }
  738     if (movieAnnot.isDict()) {
  739       if (movieAnnot.dictLookup("Movie", &obj1)->isDict()) {
  740     if (obj1.dictLookup("F", &obj2)) {
  741       if ((fileName = LinkAction::getFileSpecName(&obj2))) {
  742         if (!isAbsolutePath(fileName->getCString())) {
  743           fileName2 = appendToPath(
  744                   grabPath(doc->getFileName()->getCString()),
  745                   fileName->getCString());
  746           delete fileName;
  747           fileName = fileName2;
  748         }
  749         runCommand(cmd, fileName);
  750         delete fileName;
  751       }
  752       obj2.free();
  753     }
  754     obj1.free();
  755       }
  756     }
  757     movieAnnot.free();
  758     break;
  759 
  760   // unimplemented actions
  761   case actionJavaScript:
  762   case actionSubmitForm:
  763   case actionHide:
  764     return gFalse;
  765 
  766   // unknown action type
  767   case actionUnknown:
  768     error(errSyntaxError, -1, "Unknown link action type: '{0:t}'",
  769       ((LinkUnknown *)action)->getAction());
  770     return gFalse;
  771   }
  772 
  773   return gTrue;
  774 }
  775 
  776 QString QtPDFCore::getLinkInfo(LinkAction *action) {
  777   LinkDest *dest;
  778   GString *namedDest;
  779   Ref pageRef;
  780   int pg;
  781   QString info;
  782 
  783   if (action == lastLinkAction && !lastLinkActionInfo.isEmpty()) {
  784     return lastLinkActionInfo;
  785   }
  786 
  787   switch (action->getKind()) {
  788   case actionGoTo:
  789     dest = NULL;
  790     if ((dest = ((LinkGoTo *)action)->getDest())) {
  791       dest = dest->copy();
  792     } else if ((namedDest = ((LinkGoTo *)action)->getNamedDest())) {
  793       dest = doc->findDest(namedDest);
  794     }
  795     pg = 0;
  796     if (dest) {
  797       if (dest->isPageRef()) {
  798     pageRef = dest->getPageRef();
  799     pg = doc->findPage(pageRef.num, pageRef.gen);
  800       } else {
  801     pg = dest->getPageNum();
  802       }
  803       delete dest;
  804     }
  805     if (pg) {
  806       info = QString("[page ") + QString::number(pg) + QString("]");
  807     } else {
  808       info = "[internal]";
  809     }
  810     break;
  811   case actionGoToR:
  812     info = QString(((LinkGoToR *)action)->getFileName()->getCString());
  813     break;
  814   case actionLaunch:
  815     info = QString(((LinkLaunch *)action)->getFileName()->getCString());
  816     break;
  817   case actionURI:
  818     info = QString(((LinkURI *)action)->getURI()->getCString());
  819     break;
  820   case actionNamed:
  821     info = QString(((LinkNamed *)action)->getName()->getCString());
  822     break;
  823   case actionMovie:
  824     info = "[movie]";
  825     break;
  826   case actionJavaScript:
  827   case actionSubmitForm:
  828   case actionHide:
  829   case actionUnknown:
  830   default:
  831     info = "[unknown]";
  832     break;
  833   }
  834 
  835   lastLinkAction = action;
  836   lastLinkActionInfo = info;
  837 
  838   return info;
  839 }
  840 
  841 // Run a command, given a <cmdFmt> string with one '%s' in it, and an
  842 // <arg> string to insert in place of the '%s'.
  843 void QtPDFCore::runCommand(GString *cmdFmt, GString *arg) {
  844   GString *cmd;
  845   char *s;
  846 
  847   if ((s = strstr(cmdFmt->getCString(), "%s"))) {
  848     cmd = mungeURL(arg);
  849     cmd->insert(0, cmdFmt->getCString(),
  850         (int)(s - cmdFmt->getCString()));
  851     cmd->append(s + 2);
  852   } else {
  853     cmd = cmdFmt->copy();
  854   }
  855 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
  856   QString cmdStr(cmd->getCString());
  857   QStringList tokens = QProcess::splitCommand(cmdStr);
  858   if (!tokens.isEmpty()) {
  859     QString program = tokens[0];
  860     tokens.removeFirst();
  861     QProcess::startDetached(program, tokens);
  862   }
  863 #else
  864   QProcess::startDetached(cmd->getCString());
  865 #endif
  866   delete cmd;
  867 }
  868 
  869 // Escape any characters in a URL which might cause problems when
  870 // calling system().
  871 GString *QtPDFCore::mungeURL(GString *url) {
  872   static const char *allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  873                                "abcdefghijklmnopqrstuvwxyz"
  874                                "0123456789"
  875                                "-_.~/?:@&=+,#%";
  876   GString *newURL;
  877   char c;
  878   int i;
  879 
  880   newURL = new GString();
  881   for (i = 0; i < url->getLength(); ++i) {
  882     c = url->getChar(i);
  883     if (strchr(allowed, c)) {
  884       newURL->append(c);
  885     } else {
  886       newURL->appendf("%{0:02x}", c & 0xff);
  887     }
  888   }
  889   return newURL;
  890 }
  891 
  892 //------------------------------------------------------------------------
  893 // find
  894 //------------------------------------------------------------------------
  895 
  896 GBool QtPDFCore::find(char *s, GBool caseSensitive, GBool next,
  897               GBool backward, GBool wholeWord, GBool onePageOnly) {
  898   if (!PDFCore::find(s, caseSensitive, next,
  899              backward, wholeWord, onePageOnly)) {
  900     return gFalse;
  901   }
  902 #ifndef NO_TEXT_SELECT
  903   copySelection(gFalse);
  904 #endif
  905   return gTrue;
  906 }
  907 
  908 GBool QtPDFCore::findU(Unicode *u, int len, GBool caseSensitive,
  909                GBool next, GBool backward,
  910                GBool wholeWord, GBool onePageOnly) {
  911   if (!PDFCore::findU(u, len, caseSensitive, next,
  912               backward, wholeWord, onePageOnly)) {
  913     return gFalse;
  914   }
  915 #ifndef NO_TEXT_SELECT
  916   copySelection(gFalse);
  917 #endif
  918   return gTrue;
  919 }
  920 
  921 //------------------------------------------------------------------------
  922 // misc access
  923 //------------------------------------------------------------------------
  924 
  925 void QtPDFCore::setBusyCursor(GBool busy) {
  926   if (busy) {
  927     doSetCursor(Qt::WaitCursor);
  928   } else {
  929     doUnsetCursor();
  930   }
  931 }
  932 
  933 void QtPDFCore::doSetCursor(const QCursor &cursor) {
  934 #ifndef QT_NO_CURSOR
  935   viewport->setCursor(cursor);
  936 #endif
  937 }
  938 
  939 void QtPDFCore::doUnsetCursor() {
  940 #ifndef QT_NO_CURSOR
  941   viewport->unsetCursor();
  942 #endif
  943 }
  944 
  945 void QtPDFCore::takeFocus() {
  946   viewport->setFocus(Qt::OtherFocusReason);
  947 }
  948 
  949 QSize QtPDFCore::getBestSize() {
  950   DisplayMode mode;
  951   double zoomPercent;
  952   int w, h, pg, rot;
  953 
  954   if (!doc || doc->getNumPages() == 0) {
  955     //~ what should this return?
  956     return QSize(612, 792);
  957   }
  958   mode = state->getDisplayMode();
  959   pg = tileMap->getFirstPage();
  960   rot = (state->getRotate() + doc->getPageRotate(pg)) % 360;
  961   zoomPercent = state->getZoom();
  962   if (zoomPercent < 0) {
  963     zoomPercent = globalParams->getDefaultFitZoom();
  964     if (zoomPercent <= 0) {
  965       zoomPercent = (int)((100 * displayDpi) / 72.0 + 0.5);
  966       if (zoomPercent < 100) {
  967     zoomPercent = 100;
  968       }
  969     }
  970   }
  971   if (rot == 90 || rot == 270) {
  972     w = (int)(doc->getPageCropHeight(pg) * 0.01 * zoomPercent + 0.5);
  973     h = (int)(doc->getPageCropWidth(pg) * 0.01 * zoomPercent + 0.5);
  974   } else {
  975     w = (int)(doc->getPageCropWidth(pg) * 0.01 * zoomPercent + 0.5);
  976     h = (int)(doc->getPageCropHeight(pg) * 0.01 * zoomPercent + 0.5);
  977   }
  978   if (mode == displayContinuous) {
  979     w += QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent);
  980     h += tileMap->getContinuousPageSpacing();
  981   } else if (mode == displaySideBySideContinuous) {
  982     w = w * 2
  983         + tileMap->getHorizContinuousPageSpacing()
  984         + QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent);
  985     h += tileMap->getContinuousPageSpacing();
  986   } else if (mode == displayHorizontalContinuous) {
  987     w += tileMap->getHorizContinuousPageSpacing();
  988     h += QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent);
  989   }  else if (mode == displaySideBySideSingle) {
  990     w = w * 2 + tileMap->getHorizContinuousPageSpacing();
  991   }
  992   //~ these additions are a kludge to make this work -- 2 pixels are
  993   //~   padding in the QAbstractScrollArea; not sure where the rest go
  994 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
  995   w += 6;
  996   h += 2;
  997 #else
  998   w += 10;
  999   h += 4;
 1000 #endif
 1001   return QSize((int)(w / scaleFactor), (int)(h / scaleFactor));
 1002 }
 1003 
 1004 //------------------------------------------------------------------------
 1005 // GUI code
 1006 //------------------------------------------------------------------------
 1007 
 1008 void QtPDFCore::resizeEvent() {
 1009   setWindowSize((int)(viewport->width() * scaleFactor),
 1010         (int)(viewport->height() * scaleFactor));
 1011 }
 1012 
 1013 void QtPDFCore::paintEvent(int x, int y, int w, int h) {
 1014   SplashBitmap *bitmap;
 1015   GBool wholeWindow;
 1016 
 1017   QPainter painter(viewport);
 1018   wholeWindow = x == 0 && y == 0 &&
 1019                 w == viewport->width() && h == viewport->height();
 1020   bitmap = getWindowBitmap(wholeWindow);
 1021   QImage image(bitmap->getDataPtr(), bitmap->getWidth(),
 1022            bitmap->getHeight(), QImage::Format_RGB888);
 1023   if (scaleFactor == 1) {
 1024     painter.drawImage(QRect(x, y, w, h), image, QRect(x, y, w, h));
 1025   } else {
 1026     painter.drawImage(QRectF(x, y, w, h), image,
 1027               QRectF(x * scaleFactor, y * scaleFactor,
 1028                  w * scaleFactor, h * scaleFactor));
 1029   }
 1030   if (paintDoneCbk) {
 1031     (*paintDoneCbk)(paintDoneCbkData, (bool)isBitmapFinished());
 1032   }
 1033 }
 1034 
 1035 void QtPDFCore::scrollEvent() {
 1036   // avoid loops, e.g., scrollTo -> finishUpdate -> updateScrollbars ->
 1037   // hScrollbar.setValue -> scrollContentsBy -> scrollEvent -> scrollTo
 1038   if (inUpdateScrollbars) {
 1039     return;
 1040   }
 1041   scrollTo(hScrollBar->value(), vScrollBar->value());
 1042 }
 1043 
 1044 void QtPDFCore::tick() {
 1045   PDFCore::tick();
 1046 }
 1047 
 1048 void QtPDFCore::invalidate(int x, int y, int w, int h) {
 1049   int xx, yy, ww, hh;
 1050 
 1051   if (scaleFactor == 1) {
 1052     viewport->update(x, y, w, h);
 1053   } else {
 1054     xx = (int)(x / scaleFactor);
 1055     yy = (int)(y / scaleFactor);
 1056     ww = (int)ceil((x + w) / scaleFactor) - xx;
 1057     hh = (int)ceil((y + h) / scaleFactor) - yy;
 1058     viewport->update(xx, yy, ww, hh);
 1059   }
 1060 }
 1061 
 1062 void QtPDFCore::updateScrollbars() {
 1063   int winW, winH, horizLimit, vertLimit, horizMax, vertMax;
 1064   bool vScrollBarVisible, hScrollBarVisible;
 1065 
 1066   inUpdateScrollbars = gTrue;
 1067 
 1068   winW = state->getWinW();
 1069   winH = state->getWinH();
 1070   tileMap->getScrollLimits(&horizLimit, &vertLimit);
 1071 
 1072   if (horizLimit > winW) {
 1073     horizMax = horizLimit - winW;
 1074   } else {
 1075     horizMax = 0;
 1076   }
 1077   if (vertLimit > winH) {
 1078     vertMax = vertLimit - winH;
 1079   } else {
 1080     vertMax = 0;
 1081   }
 1082 
 1083   // Problem case A: in fixed zoom, there is a case where the page
 1084   // just barely fits in the window; if the scrollbars are visible,
 1085   // they reduce the available window size enough that they are
 1086   // necessary, i.e., the scrollbars are only necessary if they're
 1087   // visible -- so check for that situation and force the scrollbars
 1088   // to be hidden.
 1089   // NB: {h,v}ScrollBar->isVisible() are unreliable at startup, so
 1090   //     we compare the viewport size to the ScrollArea size (with
 1091   //     some slack for margins)
 1092   vScrollBarVisible =
 1093       viewport->parentWidget()->width() - viewport->width() > 8;
 1094   hScrollBarVisible =
 1095       viewport->parentWidget()->height() - viewport->height() > 8;
 1096   if (state->getZoom() >= 0 &&
 1097       vScrollBarVisible &&
 1098       hScrollBarVisible &&
 1099       horizMax <= vScrollBar->width() &&
 1100       vertMax <= hScrollBar->height()) {
 1101     horizMax = 0;
 1102     vertMax = 0;
 1103   }
 1104 
 1105   // Problem case B: in fit-to-width mode, with the vertical scrollbar
 1106   // visible, if the window is just tall enough to fit the page, then
 1107   // the vertical scrollbar will be hidden, resulting in a wider
 1108   // window, resulting in a taller page (because of fit-to-width),
 1109   // resulting in the scrollbar being unhidden, in an infinite loop --
 1110   // so always force the vertical scroll bar to be visible in
 1111   // fit-to-width mode (and in fit-to-page cases where the vertical
 1112   // scrollbar is potentially visible).
 1113   if (state->getZoom() == zoomWidth ||
 1114       (state->getZoom() == zoomPage &&
 1115        (state->getDisplayMode() == displayContinuous ||
 1116     state->getDisplayMode() == displaySideBySideContinuous))) {
 1117     if (vertMax == 0) {
 1118       vertMax = 1;
 1119     }
 1120 
 1121   // Problem case C: same as case B, but for fit-to-height mode and
 1122   // the horizontal scrollbar.
 1123   } else if (state->getZoom() == zoomHeight ||
 1124          (state->getZoom() == zoomPage &&
 1125           state->getDisplayMode() == displayHorizontalContinuous)) {
 1126     if (horizMax == 0) {
 1127       horizMax = 1;
 1128     }
 1129   }
 1130 
 1131   hScrollBar->setMaximum(horizMax);
 1132   hScrollBar->setPageStep(winW);
 1133   hScrollBar->setValue(state->getScrollX());
 1134 
 1135   vScrollBar->setMaximum(vertMax);
 1136   vScrollBar->setPageStep(winH);
 1137   vScrollBar->setValue(state->getScrollY());
 1138 
 1139   inUpdateScrollbars = gFalse;
 1140 }
 1141 
 1142 GBool QtPDFCore::checkForNewFile() {
 1143   QDateTime newModTime;
 1144 
 1145   if (doc->getFileName()) {
 1146     newModTime = QFileInfo(doc->getFileName()->getCString()).lastModified();
 1147     if (newModTime != modTime) {
 1148       modTime = newModTime;
 1149       return gTrue;
 1150     }
 1151   }
 1152   return gFalse;
 1153 }
 1154 
 1155 void QtPDFCore::preLoad() {
 1156   if (preLoadCbk) {
 1157     (*preLoadCbk)(preLoadCbkData);
 1158   }
 1159 }
 1160 
 1161 void QtPDFCore::postLoad() {
 1162   if (postLoadCbk) {
 1163     (*postLoadCbk)(postLoadCbkData);
 1164   }
 1165 }
 1166 
 1167 //------------------------------------------------------------------------
 1168 // password dialog
 1169 //------------------------------------------------------------------------
 1170 
 1171 GString *QtPDFCore::getPassword() {
 1172   QString s;
 1173   bool ok;
 1174 
 1175   if (!showPasswordDialog) {
 1176     return NULL;
 1177   }
 1178   s = QInputDialog::getText(viewport, "PDF Password",
 1179                 "This document requires a password",
 1180                 QLineEdit::Password, "", &ok, Qt::Dialog);
 1181   if (ok) {
 1182     return new GString(s.toLocal8Bit().constData());
 1183   } else {
 1184     return NULL;
 1185   }
 1186 }