"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.

    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 }