]> www.fi.muni.cz Git - evince.git/blob - pdf/xpdf/XPDFCore.cc
Import of Xpdf 2.01 for merge
[evince.git] / pdf / xpdf / XPDFCore.cc
1 //========================================================================
2 //
3 // XPDFCore.cc
4 //
5 // Copyright 2002 Glyph & Cog, LLC
6 //
7 //========================================================================
8
9 #include <aconf.h>
10
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
13 #endif
14
15 #include <X11/keysym.h>
16 #include <X11/cursorfont.h>
17 #include "gmem.h"
18 #include "GString.h"
19 #include "GList.h"
20 #include "Error.h"
21 #include "GlobalParams.h"
22 #include "PDFDoc.h"
23 #include "ErrorCodes.h"
24 #include "GfxState.h"
25 #include "PSOutputDev.h"
26 #include "TextOutputDev.h"
27 #include "XPixmapOutputDev.h"
28 #include "XPDFCore.h"
29
30 // these macro defns conflict with xpdf's Object class
31 #ifdef LESSTIF_VERSION
32 #undef XtDisplay
33 #undef XtScreen
34 #undef XtWindow
35 #undef XtParent
36 #undef XtIsRealized
37 #endif
38
39 // hack around old X includes which are missing these symbols
40 #ifndef XK_Page_Up
41 #define XK_Page_Up              0xFF55
42 #endif
43 #ifndef XK_Page_Down
44 #define XK_Page_Down            0xFF56
45 #endif
46 #ifndef XK_KP_Home
47 #define XK_KP_Home              0xFF95
48 #endif
49 #ifndef XK_KP_Left
50 #define XK_KP_Left              0xFF96
51 #endif
52 #ifndef XK_KP_Up
53 #define XK_KP_Up                0xFF97
54 #endif
55 #ifndef XK_KP_Right
56 #define XK_KP_Right             0xFF98
57 #endif
58 #ifndef XK_KP_Down
59 #define XK_KP_Down              0xFF99
60 #endif
61 #ifndef XK_KP_Prior
62 #define XK_KP_Prior             0xFF9A
63 #endif
64 #ifndef XK_KP_Page_Up
65 #define XK_KP_Page_Up           0xFF9A
66 #endif
67 #ifndef XK_KP_Next
68 #define XK_KP_Next              0xFF9B
69 #endif
70 #ifndef XK_KP_Page_Down
71 #define XK_KP_Page_Down         0xFF9B
72 #endif
73 #ifndef XK_KP_End
74 #define XK_KP_End               0xFF9C
75 #endif
76 #ifndef XK_KP_Begin
77 #define XK_KP_Begin             0xFF9D
78 #endif
79 #ifndef XK_KP_Insert
80 #define XK_KP_Insert            0xFF9E
81 #endif
82 #ifndef XK_KP_Delete
83 #define XK_KP_Delete            0xFF9F
84 #endif
85
86 //------------------------------------------------------------------------
87
88 #define highlightNone     0
89 #define highlightNormal   1
90 #define highlightSelected 2
91
92 //------------------------------------------------------------------------
93
94 static int zoomDPI[maxZoom - minZoom + 1] = {
95   29, 35, 42, 50, 60,
96   72,
97   86, 104, 124, 149, 179
98 };
99
100 //------------------------------------------------------------------------
101
102 GString *XPDFCore::currentSelection = NULL;
103 XPDFCore *XPDFCore::currentSelectionOwner = NULL;
104
105 //------------------------------------------------------------------------
106 // XPDFCore
107 //------------------------------------------------------------------------
108
109 XPDFCore::XPDFCore(Widget shellA, Widget parentWidgetA,
110                    Gulong paperColorA, GBool fullScreenA, GBool reverseVideo,
111                    GBool installCmap, int rgbCubeSize) {
112   GString *initialZoom;
113   int i;
114
115   shell = shellA;
116   parentWidget = parentWidgetA;
117   display = XtDisplay(parentWidget);
118   screenNum = XScreenNumberOfScreen(XtScreen(parentWidget));
119
120   paperColor = paperColorA;
121   fullScreen = fullScreenA;
122
123   // for some reason, querying XmNvisual doesn't work (even if done
124   // after the window is mapped)
125   visual = DefaultVisual(display, screenNum);
126   XtVaGetValues(shell, XmNcolormap, &colormap, NULL);
127
128   scrolledWin = NULL;
129   hScrollBar = NULL;
130   vScrollBar = NULL;
131   drawAreaFrame = NULL;
132   drawArea = NULL;
133   out = NULL;
134
135   doc = NULL;
136   page = 0;
137   rotate = 0;
138
139   // get the initial zoom value
140   initialZoom = globalParams->getInitialZoom();
141   if (!initialZoom->cmp("page")) {
142     zoom = zoomPage;
143   } else if (!initialZoom->cmp("width")) {
144     zoom = zoomWidth;
145   } else {
146     zoom = atoi(initialZoom->getCString());
147     if (zoom < minZoom) {
148       zoom = minZoom;
149     } else if (zoom > maxZoom) {
150       zoom = maxZoom;
151     }
152   }
153   delete initialZoom;
154
155   scrollX = 0;
156   scrollY = 0;
157   linkAction = NULL;
158   selectXMin = selectXMax = 0;
159   selectYMin = selectYMax = 0;
160   dragging = gFalse;
161   lastDragLeft = lastDragTop = gTrue;
162
163   panning = gFalse;
164
165
166   updateCbk = NULL;
167   actionCbk = NULL;
168   keyPressCbk = NULL;
169   mouseCbk = NULL;
170   reqPasswordCbk = NULL;
171
172   // no history yet
173   historyCur = xpdfHistorySize - 1;
174   historyBLen = historyFLen = 0;
175   for (i = 0; i < xpdfHistorySize; ++i) {
176     history[i].fileName = NULL;
177   }
178
179   // optional features default to on
180   hyperlinksEnabled = gTrue;
181   selectEnabled = gTrue;
182
183   // do X-specific initialization and create the widgets
184   initWindow();
185
186   // create the OutputDev
187   out = new XPixmapOutputDev(display, screenNum, visual, colormap,
188                              reverseVideo, paperColor,
189                              installCmap, rgbCubeSize, gTrue,
190                              &outputDevRedrawCbk, this);
191   out->startDoc(NULL);
192 }
193
194 XPDFCore::~XPDFCore() {
195   int i;
196
197   if (out) {
198     delete out;
199   }
200   if (doc) {
201     delete doc;
202   }
203   if (currentSelectionOwner == this && currentSelection) {
204     delete currentSelection;
205     currentSelection = NULL;
206     currentSelectionOwner = NULL;
207   }
208   for (i = 0; i < xpdfHistorySize; ++i) {
209     if (history[i].fileName) {
210       delete history[i].fileName;
211     }
212   }
213   if (selectGC) {
214     XFreeGC(display, selectGC);
215     XFreeGC(display, highlightGC);
216   }
217   if (drawAreaGC) {
218     XFreeGC(display, drawAreaGC);
219   }
220   if (drawArea) {
221     XtDestroyWidget(drawArea);
222   }
223   if (drawAreaFrame) {
224     XtDestroyWidget(drawAreaFrame);
225   }
226   if (vScrollBar) {
227     XtDestroyWidget(vScrollBar);
228   }
229   if (hScrollBar) {
230     XtDestroyWidget(hScrollBar);
231   }
232   if (scrolledWin) {
233     XtDestroyWidget(scrolledWin);
234   }
235   if (busyCursor) {
236     XFreeCursor(display, busyCursor);
237   }
238   if (linkCursor) {
239     XFreeCursor(display, linkCursor);
240   }
241   if (selectCursor) {
242     XFreeCursor(display, selectCursor);
243   }
244 }
245
246 //------------------------------------------------------------------------
247 // loadFile / displayPage / displayDest
248 //------------------------------------------------------------------------
249
250 int XPDFCore::loadFile(GString *fileName, GString *ownerPassword,
251                        GString *userPassword) {
252   PDFDoc *newDoc;
253   GString *password;
254   GBool again;
255   int err;
256
257   // busy cursor
258   setCursor(busyCursor);
259
260   // open the PDF file
261   newDoc = new PDFDoc(fileName->copy(), ownerPassword, userPassword);
262   if (!newDoc->isOk()) {
263     err = newDoc->getErrorCode();
264     delete newDoc;
265     if (err != errEncrypted || !reqPasswordCbk) {
266       setCursor(None);
267       return err;
268     }
269
270     // try requesting a password
271     again = ownerPassword != NULL || userPassword != NULL;
272     while (1) {
273       if (!(password = (*reqPasswordCbk)(reqPasswordCbkData, again))) {
274         setCursor(None);
275         return errEncrypted;
276       }
277       newDoc = new PDFDoc(fileName->copy(), password, password);
278       if (newDoc->isOk()) {
279         break;
280       }
281       err = newDoc->getErrorCode();
282       delete newDoc;
283       if (err != errEncrypted) {
284         setCursor(None);
285         return err;
286       }
287       again = gTrue;
288     }
289   }
290
291   // replace old document
292   if (doc) {
293     delete doc;
294   }
295   doc = newDoc;
296   if (out) {
297     out->startDoc(doc->getXRef());
298   }
299
300   // nothing displayed yet
301   page = -99;
302
303   // save the modification time
304   modTime = getModTime(doc->getFileName()->getCString());
305
306   // update the parent window
307   if (updateCbk) {
308     (*updateCbk)(updateCbkData, doc->getFileName(), -1,
309                  doc->getNumPages(), NULL);
310   }
311
312   // back to regular cursor
313   setCursor(None);
314
315   return errNone;
316 }
317
318 void XPDFCore::resizeToPage(int pg) {
319   Dimension width, height;
320   double width1, height1;
321   Dimension topW, topH, topBorder, daW, daH;
322   Dimension displayW, displayH;
323
324   displayW = DisplayWidth(display, screenNum);
325   displayH = DisplayHeight(display, screenNum);
326   if (fullScreen) {
327     width = displayW;
328     height = displayH;
329   } else {
330     if (pg < 0 || pg > doc->getNumPages()) {
331       width1 = 612;
332       height1 = 792;
333     } else if (doc->getPageRotate(pg) == 90 ||
334                doc->getPageRotate(pg) == 270) {
335       width1 = doc->getPageHeight(pg);
336       height1 = doc->getPageWidth(pg);
337     } else {
338       width1 = doc->getPageWidth(pg);
339       height1 = doc->getPageHeight(pg);
340     }
341     if (zoom == zoomPage || zoom == zoomWidth) {
342       width = (Dimension)((width1 * zoomDPI[defZoom - minZoom]) / 72 + 0.5);
343       height = (Dimension)((height1 * zoomDPI[defZoom - minZoom]) / 72 + 0.5);
344     } else {
345       width = (Dimension)((width1 * zoomDPI[zoom - minZoom]) / 72 + 0.5);
346       height = (Dimension)((height1 * zoomDPI[zoom - minZoom]) / 72 + 0.5);
347     }
348     if (width > displayW - 100) {
349       width = displayW - 100;
350     }
351     if (height > displayH - 150) {
352       height = displayH - 150;
353     }
354   }
355
356   if (XtIsRealized(shell)) {
357     XtVaGetValues(shell, XmNwidth, &topW, XmNheight, &topH,
358                   XmNborderWidth, &topBorder, NULL);
359     XtVaGetValues(drawArea, XmNwidth, &daW, XmNheight, &daH, NULL);
360     XtVaSetValues(shell, XmNwidth, width + (topW - daW),
361                   XmNheight, height + (topH - daH), NULL);
362   } else {
363     XtVaSetValues(drawArea, XmNwidth, width, XmNheight, height, NULL);
364   }
365 }
366
367 void XPDFCore::clear() {
368   if (!doc) {
369     return;
370   }
371
372   // no document
373   delete doc;
374   doc = NULL;
375   out->clear();
376
377   // no page displayed
378   page = -99;
379
380   // redraw
381   scrollX = scrollY = 0;
382   updateScrollBars();
383   redrawRectangle(scrollX, scrollY, drawAreaWidth, drawAreaHeight);
384 }
385
386 void XPDFCore::displayPage(int pageA, int zoomA, int rotateA,
387                            GBool scrollToTop, GBool addToHist) {
388   double hDPI, vDPI;
389   int rot;
390   XPDFHistory *h;
391   GBool newZoom;
392   XGCValues gcValues;
393   time_t newModTime;
394   int oldScrollX, oldScrollY;
395
396   // update the zoom and rotate values
397   newZoom = zoomA != zoom;
398   zoom = zoomA;
399   rotate = rotateA;
400
401   // check for document and valid page number
402   if (!doc || pageA <= 0 || pageA > doc->getNumPages()) {
403     return;
404   }
405
406   // busy cursor
407   setCursor(busyCursor);
408
409
410   // check for changes to the file
411   newModTime = getModTime(doc->getFileName()->getCString());
412   if (newModTime != modTime) {
413     if (loadFile(doc->getFileName()) == errNone) {
414       if (pageA > doc->getNumPages()) {
415         pageA = doc->getNumPages();
416       }
417     }
418     modTime = newModTime;
419   }
420
421   // free the old GCs
422   if (selectGC) {
423     XFreeGC(display, selectGC);
424     XFreeGC(display, highlightGC);
425   }
426
427   // new page number
428   page = pageA;
429
430   // scroll to top
431   if (scrollToTop) {
432     scrollY = 0;
433   }
434
435   // if zoom level changed, scroll to the top-left corner
436   if (newZoom) {
437     scrollX = scrollY = 0;
438   }
439
440   // initialize mouse-related stuff
441   linkAction = NULL;
442   selectXMin = selectXMax = 0;
443   selectYMin = selectYMax = 0;
444   dragging = gFalse;
445   lastDragLeft = lastDragTop = gTrue;
446
447   // draw the page
448   rot = rotate + doc->getPageRotate(page);
449   if (rot >= 360) {
450     rot -= 360;
451   } else if (rotate < 0) {
452     rot += 360;
453   }
454   if (zoom == zoomPage) {
455     if (rot == 90 || rot == 270) {
456       hDPI = (drawAreaWidth / doc->getPageHeight(page)) * 72;
457       vDPI = (drawAreaHeight / doc->getPageWidth(page)) * 72;
458     } else {
459       hDPI = (drawAreaWidth / doc->getPageWidth(page)) * 72;
460       vDPI = (drawAreaHeight / doc->getPageHeight(page)) * 72;
461     }
462     dpi = (hDPI < vDPI) ? hDPI : vDPI;
463   } else if (zoom == zoomWidth) {
464     if (rot == 90 || rot == 270) {
465       dpi = (drawAreaWidth / doc->getPageHeight(page)) * 72;
466     } else {
467       dpi = (drawAreaWidth / doc->getPageWidth(page)) * 72;
468     }
469   } else {
470     dpi = zoomDPI[zoom - minZoom];
471   }
472   out->setWindow(XtWindow(drawArea));
473   doc->displayPage(out, page, dpi, rotate, gTrue);
474   oldScrollX = scrollX;
475   oldScrollY = scrollY;
476   updateScrollBars();
477   if (scrollX != oldScrollX || scrollY != oldScrollY) {
478     redrawRectangle(scrollX, scrollY, drawAreaWidth, drawAreaHeight);
479   }
480
481
482   // add to history
483   if (addToHist) {
484     if (++historyCur == xpdfHistorySize) {
485       historyCur = 0;
486     }
487     h = &history[historyCur];
488     if (h->fileName) {
489       delete h->fileName;
490     }
491     h->fileName = doc->getFileName()->copy();
492     h->page = page;
493     if (historyBLen < xpdfHistorySize) {
494       ++historyBLen;
495     }
496     historyFLen = 0;
497   }
498
499   // update the parent window
500   if (updateCbk) {
501     (*updateCbk)(updateCbkData, NULL, page, -1, "");
502   }
503
504   // allocate new GCs
505   gcValues.foreground = BlackPixel(display, screenNum) ^
506                         WhitePixel(display, screenNum);
507   gcValues.function = GXxor;
508   selectGC = XCreateGC(display, out->getPixmap(),
509                        GCForeground | GCFunction, &gcValues);
510   highlightGC = XCreateGC(display, out->getPixmap(),
511                        GCForeground | GCFunction, &gcValues);
512
513   // back to regular cursor
514   setCursor(None);
515 }
516
517 void XPDFCore::displayDest(LinkDest *dest, int zoomA, int rotateA,
518                            GBool addToHist) {
519   Ref pageRef;
520   int pg;
521   int dx, dy;
522
523   if (dest->isPageRef()) {
524     pageRef = dest->getPageRef();
525     pg = doc->findPage(pageRef.num, pageRef.gen);
526   } else {
527     pg = dest->getPageNum();
528   }
529   if (pg <= 0 || pg > doc->getNumPages()) {
530     pg = 1;
531   }
532   if (pg != page) {
533     displayPage(pg, zoomA, rotateA, gTrue, addToHist);
534   }
535
536   if (fullScreen) {
537     return;
538   }
539   switch (dest->getKind()) {
540   case destXYZ:
541     out->cvtUserToDev(dest->getLeft(), dest->getTop(), &dx, &dy);
542     if (dest->getChangeLeft() || dest->getChangeTop()) {
543       scrollTo(dest->getChangeLeft() ? dx : scrollX,
544                dest->getChangeTop() ? dy : scrollY);
545     }
546     //~ what is the zoom parameter?
547     break;
548   case destFit:
549   case destFitB:
550     //~ do fit
551     scrollTo(0, 0);
552     break;
553   case destFitH:
554   case destFitBH:
555     //~ do fit
556     out->cvtUserToDev(0, dest->getTop(), &dx, &dy);
557     scrollTo(0, dy);
558     break;
559   case destFitV:
560   case destFitBV:
561     //~ do fit
562     out->cvtUserToDev(dest->getLeft(), 0, &dx, &dy);
563     scrollTo(dx, 0);
564     break;
565   case destFitR:
566     //~ do fit
567     out->cvtUserToDev(dest->getLeft(), dest->getTop(), &dx, &dy);
568     scrollTo(dx, dy);
569     break;
570   }
571 }
572
573 //------------------------------------------------------------------------
574 // page/position changes
575 //------------------------------------------------------------------------
576
577 void XPDFCore::gotoNextPage(int inc, GBool top) {
578   int pg;
579
580   if (!doc || doc->getNumPages() == 0) {
581     return;
582   }
583   if (page < doc->getNumPages()) {
584     if ((pg = page + inc) > doc->getNumPages()) {
585       pg = doc->getNumPages();
586     }
587     displayPage(pg, zoom, rotate, top, gTrue);
588   } else {
589     XBell(display, 0);
590   }
591 }
592
593 void XPDFCore::gotoPrevPage(int dec, GBool top, GBool bottom) {
594   int pg;
595
596   if (!doc || doc->getNumPages() == 0) {
597     return;
598   }
599   if (page > 1) {
600     if (!fullScreen && bottom) {
601       scrollY = out->getPixmapHeight() - drawAreaHeight;
602       if (scrollY < 0) {
603         scrollY = 0;
604       }
605       // displayPage will call updateScrollBars()
606     }
607     if ((pg = page - dec) < 1) {
608       pg = 1;
609     }
610     displayPage(pg, zoom, rotate, top, gTrue);
611   } else {
612     XBell(display, 0);
613   }
614 }
615
616 void XPDFCore::goForward() {
617   if (historyFLen == 0) {
618     XBell(display, 0);
619     return;
620   }
621   if (++historyCur == xpdfHistorySize) {
622     historyCur = 0;
623   }
624   --historyFLen;
625   ++historyBLen;
626   if (history[historyCur].fileName->cmp(doc->getFileName()) != 0) {
627     if (loadFile(history[historyCur].fileName) != errNone) {
628       XBell(display, 0);
629       return;
630     }
631   }
632   displayPage(history[historyCur].page, zoom, rotate, gFalse, gFalse);
633 }
634
635 void XPDFCore::goBackward() {
636   if (historyBLen <= 1) {
637     XBell(display, 0);
638     return;
639   }
640   if (--historyCur < 0) {
641     historyCur = xpdfHistorySize - 1;
642   }
643   --historyBLen;
644   ++historyFLen;
645   if (history[historyCur].fileName->cmp(doc->getFileName()) != 0) {
646     if (loadFile(history[historyCur].fileName) != errNone) {
647       XBell(display, 0);
648       return;
649     }
650   }
651   displayPage(history[historyCur].page, zoom, rotate, gFalse, gFalse);
652 }
653
654 void XPDFCore::scrollLeft(int nCols) {
655   scrollTo(scrollX - nCols * 16, scrollY);
656 }
657
658 void XPDFCore::scrollRight(int nCols) {
659   scrollTo(scrollX + nCols * 16, scrollY);
660 }
661
662 void XPDFCore::scrollUp(int nLines) {
663   scrollTo(scrollX, scrollY - nLines * 16);
664 }
665
666 void XPDFCore::scrollDown(int nLines) {
667   scrollTo(scrollX, scrollY + nLines * 16);
668 }
669
670 void XPDFCore::scrollPageUp() {
671   if (scrollY == 0) {
672     gotoPrevPage(1, gFalse, gTrue);
673   } else {
674     scrollTo(scrollX, scrollY - drawAreaHeight);
675   }
676 }
677
678 void XPDFCore::scrollPageDown() {
679   if (scrollY >= out->getPixmapHeight() - drawAreaHeight) {
680     gotoNextPage(1, gTrue);
681   } else {
682     scrollTo(scrollX, scrollY + drawAreaHeight);
683   }
684 }
685
686 void XPDFCore::scrollTo(int x, int y) {
687   GBool needRedraw;
688   int maxPos, pos;
689
690   needRedraw = gFalse;
691
692   maxPos = out ? out->getPixmapWidth() : 1;
693   if (maxPos < drawAreaWidth) {
694     maxPos = drawAreaWidth;
695   }
696   if (x < 0) {
697     pos = 0;
698   } else if (x > maxPos - drawAreaWidth) {
699     pos = maxPos - drawAreaWidth;
700   } else {
701     pos = x;
702   }
703   if (scrollX != pos) {
704     scrollX = pos;
705     XmScrollBarSetValues(hScrollBar, scrollX, drawAreaWidth, 16,
706                          drawAreaWidth, False);
707     needRedraw = gTrue;
708   }
709
710   maxPos = out ? out->getPixmapHeight() : 1;
711   if (maxPos < drawAreaHeight) {
712     maxPos = drawAreaHeight;
713   }
714   if (y < 0) {
715     pos = 0;
716   } else if (y > maxPos - drawAreaHeight) {
717     pos = maxPos - drawAreaHeight;
718   } else {
719     pos = y;
720   }
721   if (scrollY != pos) {
722     scrollY = pos;
723     XmScrollBarSetValues(vScrollBar, scrollY, drawAreaHeight, 16,
724                          drawAreaHeight, False);
725     needRedraw = gTrue;
726   }
727
728   if (needRedraw) {
729     redrawRectangle(scrollX, scrollY, drawAreaWidth, drawAreaHeight);
730   }
731 }
732
733 //------------------------------------------------------------------------
734 // selection
735 //------------------------------------------------------------------------
736
737 void XPDFCore::setSelection(int newXMin, int newYMin,
738                             int newXMax, int newYMax) {
739   Pixmap pixmap;
740   int x, y;
741   GBool needRedraw, needScroll;
742   GBool moveLeft, moveRight, moveTop, moveBottom;
743
744   pixmap = out->getPixmap();
745
746
747   // erase old selection on off-screen bitmap
748   needRedraw = gFalse;
749   if (selectXMin < selectXMax && selectYMin < selectYMax) {
750     XFillRectangle(display, pixmap,
751                    selectGC, selectXMin, selectYMin,
752                    selectXMax - selectXMin, selectYMax - selectYMin);
753     needRedraw = gTrue;
754   }
755
756   // draw new selection on off-screen bitmap
757   if (newXMin < newXMax && newYMin < newYMax) {
758     XFillRectangle(display, pixmap,
759                    selectGC, newXMin, newYMin,
760                    newXMax - newXMin, newYMax - newYMin);
761     needRedraw = gTrue;
762   }
763
764   // check which edges moved
765   moveLeft = newXMin != selectXMin;
766   moveTop = newYMin != selectYMin;
767   moveRight = newXMax != selectXMax;
768   moveBottom = newYMax != selectYMax;
769
770   // redraw currently visible part of bitmap
771   if (needRedraw) {
772     if (moveLeft) {
773       redrawRectangle((newXMin < selectXMin) ? newXMin : selectXMin,
774                       (newYMin < selectYMin) ? newYMin : selectYMin,
775                       (newXMin > selectXMin) ? newXMin : selectXMin,
776                       (newYMax > selectYMax) ? newYMax : selectYMax);
777     }
778     if (moveRight) {
779       redrawRectangle((newXMax < selectXMax) ? newXMax : selectXMax,
780                       (newYMin < selectYMin) ? newYMin : selectYMin,
781                       (newXMax > selectXMax) ? newXMax : selectXMax,
782                       (newYMax > selectYMax) ? newYMax : selectYMax);
783     }
784     if (moveTop) {
785       redrawRectangle((newXMin < selectXMin) ? newXMin : selectXMin,
786                       (newYMin < selectYMin) ? newYMin : selectYMin,
787                       (newXMax > selectXMax) ? newXMax : selectXMax,
788                       (newYMin > selectYMin) ? newYMin : selectYMin);
789     }
790     if (moveBottom) {
791       redrawRectangle((newXMin < selectXMin) ? newXMin : selectXMin,
792                       (newYMax < selectYMax) ? newYMax : selectYMax,
793                       (newXMax > selectXMax) ? newXMax : selectXMax,
794                       (newYMax > selectYMax) ? newYMax : selectYMax);
795     }
796   }
797
798   // switch to new selection coords
799   selectXMin = newXMin;
800   selectXMax = newXMax;
801   selectYMin = newYMin;
802   selectYMax = newYMax;
803
804   // scroll if necessary
805   if (fullScreen) {
806     return;
807   }
808   needScroll = gFalse;
809   x = scrollX;
810   y = scrollY;
811   if (moveLeft && selectXMin < x) {
812     x = selectXMin;
813     needScroll = gTrue;
814   } else if (moveRight && selectXMax >= x + drawAreaWidth) {
815     x = selectXMax - drawAreaWidth;
816     needScroll = gTrue;
817   } else if (moveLeft && selectXMin >= x + drawAreaWidth) {
818     x = selectXMin - drawAreaWidth;
819     needScroll = gTrue;
820   } else if (moveRight && selectXMax < x) {
821     x = selectXMax;
822     needScroll = gTrue;
823   }
824   if (moveTop && selectYMin < y) {
825     y = selectYMin;
826     needScroll = gTrue;
827   } else if (moveBottom && selectYMax >= y + drawAreaHeight) {
828     y = selectYMax - drawAreaHeight;
829     needScroll = gTrue;
830   } else if (moveTop && selectYMin >= y + drawAreaHeight) {
831     y = selectYMin - drawAreaHeight;
832     needScroll = gTrue;
833   } else if (moveBottom && selectYMax < y) {
834     y = selectYMax;
835     needScroll = gTrue;
836   }
837   if (needScroll) {
838     scrollTo(x, y);
839   }
840 }
841
842 void XPDFCore::moveSelection(int mx, int my) {
843   int xMin, yMin, xMax, yMax;
844
845   // clip mouse coords
846   if (mx < 0) {
847     mx = 0;
848   } else if (mx >= out->getPixmapWidth()) {
849     mx = out->getPixmapWidth() - 1;
850   }
851   if (my < 0) {
852     my = 0;
853   } else if (my >= out->getPixmapHeight()) {
854     my = out->getPixmapHeight() - 1;
855   }
856
857   // move appropriate edges of selection
858   if (lastDragLeft) {
859     if (mx < selectXMax) {
860       xMin = mx;
861       xMax = selectXMax;
862     } else {
863       xMin = selectXMax;
864       xMax = mx;
865       lastDragLeft = gFalse;
866     }
867   } else {
868     if (mx > selectXMin) {
869       xMin = selectXMin;
870       xMax = mx;
871     } else {
872       xMin = mx;
873       xMax = selectXMin;
874       lastDragLeft = gTrue;
875     }
876   }
877   if (lastDragTop) {
878     if (my < selectYMax) {
879       yMin = my;
880       yMax = selectYMax;
881     } else {
882       yMin = selectYMax;
883       yMax = my;
884       lastDragTop = gFalse;
885     }
886   } else {
887     if (my > selectYMin) {
888       yMin = selectYMin;
889       yMax = my;
890     } else {
891       yMin = my;
892       yMax = selectYMin;
893       lastDragTop = gTrue;
894     }
895   }
896
897   // redraw the selection
898   setSelection(xMin, yMin, xMax, yMax);
899 }
900
901 // X's copy-and-paste mechanism is brain damaged.  Xt doesn't help
902 // any, but doesn't make it too much worse, either.  Motif, on the
903 // other hand, adds significant complexity to the mess.  So here we
904 // blow off the Motif junk and stick to plain old Xt.  The next two
905 // functions (copySelection and convertSelectionCbk) implement the
906 // magic needed to deal with Xt's mechanism.  Note that this requires
907 // global variables (currentSelection and currentSelectionOwner).
908
909 void XPDFCore::copySelection() {
910   if (!doc->okToCopy()) {
911     return;
912   }
913   if (currentSelection) {
914     delete currentSelection;
915   }
916   //~ for multithreading: need a mutex here
917   currentSelection = out->getText(selectXMin, selectYMin,
918                                   selectXMax, selectYMax);
919   currentSelectionOwner = this;
920   XtOwnSelection(drawArea, XA_PRIMARY, XtLastTimestampProcessed(display),
921                  &convertSelectionCbk, NULL, NULL);
922 }
923
924 Boolean XPDFCore::convertSelectionCbk(Widget widget, Atom *selection,
925                                       Atom *target, Atom *type,
926                                       XtPointer *value, unsigned long *length,
927                                       int *format) {
928   if (*target != XA_STRING) {
929     return False;
930   }
931   //~ for multithreading: need a mutex here
932   *value = XtNewString(currentSelection->getCString());
933   *length = currentSelection->getLength();
934   *type = XA_STRING;
935   *format = 8; // 8-bit elements
936   return True;
937 }
938
939 GBool XPDFCore::getSelection(int *xMin, int *yMin, int *xMax, int *yMax) {
940   if (selectXMin >= selectXMax || selectYMin >= selectYMax) {
941     return gFalse;
942   }
943   *xMin = selectXMin;
944   *yMin = selectYMin;
945   *xMax = selectXMax;
946   *yMax = selectYMax;
947   return gTrue;
948 }
949
950 GString *XPDFCore::extractText(int xMin, int yMin, int xMax, int yMax) {
951   if (!doc->okToCopy()) {
952     return NULL;
953   }
954   return out->getText(xMin, yMin, xMax, yMax);
955 }
956
957 GString *XPDFCore::extractText(int pageNum,
958                                int xMin, int yMin, int xMax, int yMax) {
959   TextOutputDev *textOut;
960   GString *s;
961
962   if (!doc->okToCopy()) {
963     return NULL;
964   }
965   textOut = new TextOutputDev(NULL, gFalse, gFalse, gFalse);
966   if (!textOut->isOk()) {
967     delete textOut;
968     return NULL;
969   }
970   doc->displayPage(textOut, pageNum, dpi, rotate, gFalse);
971   s = textOut->getText(xMin, yMin, xMax, yMax);
972   delete textOut;
973   return s;
974 }
975
976 //------------------------------------------------------------------------
977 // hyperlinks
978 //------------------------------------------------------------------------
979
980 void XPDFCore::doLink(int mx, int my) {
981   double x, y;
982   LinkAction *action;
983
984   // look for a link
985   out->cvtDevToUser(mx, my, &x, &y);
986   if ((action = doc->findLink(x, y))) {
987     doAction(action);
988   }
989 }
990
991 void XPDFCore::doAction(LinkAction *action) {
992   LinkActionKind kind;
993   LinkDest *dest;
994   GString *namedDest;
995   char *s;
996   GString *fileName, *fileName2;
997   GString *cmd;
998   GString *actionName;
999   Object movieAnnot, obj1, obj2;
1000   GString *msg;
1001   int i;
1002
1003   switch (kind = action->getKind()) {
1004
1005   // GoTo / GoToR action
1006   case actionGoTo:
1007   case actionGoToR:
1008     if (kind == actionGoTo) {
1009       dest = NULL;
1010       namedDest = NULL;
1011       if ((dest = ((LinkGoTo *)action)->getDest())) {
1012         dest = dest->copy();
1013       } else if ((namedDest = ((LinkGoTo *)action)->getNamedDest())) {
1014         namedDest = namedDest->copy();
1015       }
1016     } else {
1017       dest = NULL;
1018       namedDest = NULL;
1019       if ((dest = ((LinkGoToR *)action)->getDest())) {
1020         dest = dest->copy();
1021       } else if ((namedDest = ((LinkGoToR *)action)->getNamedDest())) {
1022         namedDest = namedDest->copy();
1023       }
1024       s = ((LinkGoToR *)action)->getFileName()->getCString();
1025       //~ translate path name for VMS (deal with '/')
1026       if (isAbsolutePath(s)) {
1027         fileName = new GString(s);
1028       } else {
1029         fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s);
1030       }
1031       if (loadFile(fileName) != errNone) {
1032         if (dest) {
1033           delete dest;
1034         }
1035         if (namedDest) {
1036           delete namedDest;
1037         }
1038         delete fileName;
1039         return;
1040       }
1041       delete fileName;
1042     }
1043     if (namedDest) {
1044       dest = doc->findDest(namedDest);
1045       delete namedDest;
1046     }
1047     if (dest) {
1048       displayDest(dest, zoom, rotate, gTrue);
1049       delete dest;
1050     } else {
1051       if (kind == actionGoToR) {
1052         displayPage(1, zoom, 0, gFalse, gTrue);
1053       }
1054     }
1055     break;
1056
1057   // Launch action
1058   case actionLaunch:
1059     fileName = ((LinkLaunch *)action)->getFileName();
1060     s = fileName->getCString();
1061     if (!strcmp(s + fileName->getLength() - 4, ".pdf") ||
1062         !strcmp(s + fileName->getLength() - 4, ".PDF")) {
1063       //~ translate path name for VMS (deal with '/')
1064       if (isAbsolutePath(s)) {
1065         fileName = fileName->copy();
1066       } else {
1067         fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s);
1068       }
1069       if (loadFile(fileName) != errNone) {
1070         delete fileName;
1071         return;
1072       }
1073       delete fileName;
1074       displayPage(1, zoom, rotate, gFalse, gTrue);
1075     } else {
1076       fileName = fileName->copy();
1077       if (((LinkLaunch *)action)->getParams()) {
1078         fileName->append(' ');
1079         fileName->append(((LinkLaunch *)action)->getParams());
1080       }
1081 #ifdef VMS
1082       fileName->insert(0, "spawn/nowait ");
1083 #elif defined(__EMX__)
1084       fileName->insert(0, "start /min /n ");
1085 #else
1086       fileName->append(" &");
1087 #endif
1088       msg = new GString("About to execute the command:\n");
1089       msg->append(fileName);
1090       if (doQuestionDialog("Launching external application", msg)) {
1091         system(fileName->getCString());
1092       }
1093       delete fileName;
1094       delete msg;
1095     }
1096     break;
1097
1098   // URI action
1099   case actionURI:
1100     if (!(cmd = globalParams->getURLCommand())) {
1101       error(-1, "No urlCommand defined in config file");
1102       break;
1103     }
1104     runCommand(cmd, ((LinkURI *)action)->getURI());
1105     break;
1106
1107   // Named action
1108   case actionNamed:
1109     actionName = ((LinkNamed *)action)->getName();
1110     if (!actionName->cmp("NextPage")) {
1111       gotoNextPage(1, gTrue);
1112     } else if (!actionName->cmp("PrevPage")) {
1113       gotoPrevPage(1, gTrue, gFalse);
1114     } else if (!actionName->cmp("FirstPage")) {
1115       if (page != 1) {
1116         displayPage(1, zoom, rotate, gTrue, gTrue);
1117       }
1118     } else if (!actionName->cmp("LastPage")) {
1119       if (page != doc->getNumPages()) {
1120         displayPage(doc->getNumPages(), zoom, rotate, gTrue, gTrue);
1121       }
1122     } else if (!actionName->cmp("GoBack")) {
1123       goBackward();
1124     } else if (!actionName->cmp("GoForward")) {
1125       goForward();
1126     } else if (!actionName->cmp("Quit")) {
1127       if (actionCbk) {
1128         (*actionCbk)(actionCbkData, "Quit");
1129       }
1130     } else {
1131       error(-1, "Unknown named action: '%s'", actionName->getCString());
1132     }
1133     break;
1134
1135   // Movie action
1136   case actionMovie:
1137     if (!(cmd = globalParams->getMovieCommand())) {
1138       error(-1, "No movieCommand defined in config file");
1139       break;
1140     }
1141     if (((LinkMovie *)action)->hasAnnotRef()) {
1142       doc->getXRef()->fetch(((LinkMovie *)action)->getAnnotRef()->num,
1143                             ((LinkMovie *)action)->getAnnotRef()->gen,
1144                             &movieAnnot);
1145     } else {
1146       doc->getCatalog()->getPage(page)->getAnnots(&obj1);
1147       if (obj1.isArray()) {
1148         for (i = 0; i < obj1.arrayGetLength(); ++i) {
1149           if (obj1.arrayGet(i, &movieAnnot)->isDict()) {
1150             if (movieAnnot.dictLookup("Subtype", &obj2)->isName("Movie")) {
1151               obj2.free();
1152               break;
1153             }
1154             obj2.free();
1155           }
1156           movieAnnot.free();
1157         }
1158         obj1.free();
1159       }
1160     }
1161     if (movieAnnot.isDict()) {
1162       if (movieAnnot.dictLookup("Movie", &obj1)->isDict()) {
1163         if (obj1.dictLookup("F", &obj2)) {
1164           if ((fileName = LinkAction::getFileSpecName(&obj2))) {
1165             if (!isAbsolutePath(fileName->getCString())) {
1166               fileName2 = appendToPath(
1167                               grabPath(doc->getFileName()->getCString()),
1168                               fileName->getCString());
1169               delete fileName;
1170               fileName = fileName2;
1171             }
1172             runCommand(cmd, fileName);
1173             delete fileName;
1174           }
1175           obj2.free();
1176         }
1177         obj1.free();
1178       }
1179     }
1180     movieAnnot.free();
1181     break;
1182
1183   // unknown action type
1184   case actionUnknown:
1185     error(-1, "Unknown link action type: '%s'",
1186           ((LinkUnknown *)action)->getAction()->getCString());
1187     break;
1188   }
1189 }
1190
1191 // Run a command, given a <cmdFmt> string with one '%s' in it, and an
1192 // <arg> string to insert in place of the '%s'.
1193 void XPDFCore::runCommand(GString *cmdFmt, GString *arg) {
1194   GString *cmd;
1195   char *s;
1196   int i;
1197
1198   if ((s = strstr(cmdFmt->getCString(), "%s"))) {
1199     cmd = arg->copy();
1200     // filter out any quote marks (' or ") to avoid a potential
1201     // security hole
1202     i = 0;
1203     while (i < cmd->getLength()) {
1204       if (cmd->getChar(i) == '"') {
1205         cmd->del(i);
1206         cmd->insert(i, "%22");
1207         i += 3;
1208       } else if (cmd->getChar(i) == '\'') {
1209         cmd->del(i);
1210         cmd->insert(i, "%27");
1211         i += 3;
1212       } else {
1213         ++i;
1214       }
1215     }
1216     cmd->insert(0, cmdFmt->getCString(),
1217                 s - cmdFmt->getCString());
1218     cmd->append(s + 2);
1219   } else {
1220     cmd = cmdFmt->copy();
1221   }
1222 #ifdef VMS
1223   cmd->insert(0, "spawn/nowait ");
1224 #elif defined(__EMX__)
1225   cmd->insert(0, "start /min /n ");
1226 #else
1227   cmd->append(" &");
1228 #endif
1229   system(cmd->getCString());
1230   delete cmd;
1231 }
1232
1233
1234 //------------------------------------------------------------------------
1235 // find
1236 //------------------------------------------------------------------------
1237
1238 void XPDFCore::find(char *s) {
1239   Unicode *u;
1240   TextOutputDev *textOut;
1241   int xMin, yMin, xMax, yMax;
1242   double xMin1, yMin1, xMax1, yMax1;
1243   int pg;
1244   GBool top;
1245   int len, i;
1246
1247   // check for zero-length string
1248   if (!s[0]) {
1249     XBell(display, 0);
1250     return;
1251   }
1252
1253   // set cursor to watch
1254   setCursor(busyCursor);
1255
1256   // convert to Unicode
1257 #if 1 //~ should do something more intelligent here
1258   len = strlen(s);
1259   u = (Unicode *)gmalloc(len * sizeof(Unicode));
1260   for (i = 0; i < len; ++i) {
1261     u[i] = (Unicode)(s[i] & 0xff);
1262   }
1263 #endif
1264
1265   // search current page starting at current selection or top of page
1266   xMin = yMin = xMax = yMax = 0;
1267   if (selectXMin < selectXMax && selectYMin < selectYMax) {
1268     xMin = selectXMax;
1269     yMin = (selectYMin + selectYMax) / 2;
1270     top = gFalse;
1271   } else {
1272     top = gTrue;
1273   }
1274   if (out->findText(u, len, top, gTrue, &xMin, &yMin, &xMax, &yMax)) {
1275     goto found;
1276   }
1277
1278   // search following pages
1279   textOut = new TextOutputDev(NULL, gFalse, gFalse, gFalse);
1280   if (!textOut->isOk()) {
1281     delete textOut;
1282     goto done;
1283   }
1284   for (pg = page+1; pg <= doc->getNumPages(); ++pg) {
1285     doc->displayPage(textOut, pg, 72, 0, gFalse);
1286     if (textOut->findText(u, len, gTrue, gTrue,
1287                           &xMin1, &yMin1, &xMax1, &yMax1)) {
1288       goto foundPage;
1289     }
1290   }
1291
1292   // search previous pages
1293   for (pg = 1; pg < page; ++pg) {
1294     doc->displayPage(textOut, pg, 72, 0, gFalse);
1295     if (textOut->findText(u, len, gTrue, gTrue,
1296                           &xMin1, &yMin1, &xMax1, &yMax1)) {
1297       goto foundPage;
1298     }
1299   }
1300   delete textOut;
1301
1302   // search current page ending at current selection
1303   if (selectXMin < selectXMax && selectYMin < selectYMax) {
1304     xMax = selectXMin;
1305     yMax = (selectYMin + selectYMax) / 2;
1306     if (out->findText(u, len, gTrue, gFalse, &xMin, &yMin, &xMax, &yMax)) {
1307       goto found;
1308     }
1309   }
1310
1311   // not found
1312   XBell(display, 0);
1313   goto done;
1314
1315   // found on a different page
1316  foundPage:
1317   delete textOut;
1318   displayPage(pg, zoom, rotate, gTrue, gTrue);
1319   if (!out->findText(u, len, gTrue, gTrue, &xMin, &yMin, &xMax, &yMax)) {
1320     // this can happen if coalescing is bad
1321     goto done;
1322   }
1323
1324   // found: change the selection
1325  found:
1326   setSelection(xMin, yMin, xMax, yMax);
1327 #ifndef NO_TEXT_SELECT
1328   copySelection();
1329 #endif
1330
1331  done:
1332   gfree(u);
1333
1334   // reset cursors to normal
1335   setCursor(None);
1336 }
1337
1338 //------------------------------------------------------------------------
1339 // misc access
1340 //------------------------------------------------------------------------
1341
1342 void XPDFCore::setBusyCursor(GBool busy) {
1343   setCursor(busy ? busyCursor : None);
1344 }
1345
1346 void XPDFCore::takeFocus() {
1347   XmProcessTraversal(drawArea, XmTRAVERSE_CURRENT);
1348 }
1349
1350 //------------------------------------------------------------------------
1351 // GUI code
1352 //------------------------------------------------------------------------
1353
1354 void XPDFCore::initWindow() {
1355   Arg args[20];
1356   int n;
1357
1358   // create the cursors
1359   busyCursor = XCreateFontCursor(display, XC_watch);
1360   linkCursor = XCreateFontCursor(display, XC_hand2);
1361   selectCursor = XCreateFontCursor(display, XC_cross);
1362   currentCursor = 0;
1363
1364   // create the scrolled window and scrollbars
1365   n = 0;
1366   XtSetArg(args[n], XmNscrollingPolicy, XmAPPLICATION_DEFINED); ++n;
1367   XtSetArg(args[n], XmNvisualPolicy, XmVARIABLE); ++n;
1368   scrolledWin = XmCreateScrolledWindow(parentWidget, "scroll", args, n);
1369   XtManageChild(scrolledWin);
1370   n = 0;
1371   XtSetArg(args[n], XmNorientation, XmHORIZONTAL); ++n;
1372   XtSetArg(args[n], XmNminimum, 0); ++n;
1373   XtSetArg(args[n], XmNmaximum, 1); ++n;
1374   XtSetArg(args[n], XmNsliderSize, 1); ++n;
1375   XtSetArg(args[n], XmNvalue, 0); ++n;
1376   XtSetArg(args[n], XmNincrement, 1); ++n;
1377   XtSetArg(args[n], XmNpageIncrement, 1); ++n;
1378   hScrollBar = XmCreateScrollBar(scrolledWin, "hScrollBar", args, n);
1379   XtManageChild(hScrollBar);
1380   XtAddCallback(hScrollBar, XmNvalueChangedCallback,
1381                 &hScrollChangeCbk, (XtPointer)this);
1382 #ifndef DISABLE_SMOOTH_SCROLL
1383   XtAddCallback(hScrollBar, XmNdragCallback,
1384                 &hScrollDragCbk, (XtPointer)this);
1385 #endif
1386   n = 0;
1387   XtSetArg(args[n], XmNorientation, XmVERTICAL); ++n;
1388   XtSetArg(args[n], XmNminimum, 0); ++n;
1389   XtSetArg(args[n], XmNmaximum, 1); ++n;
1390   XtSetArg(args[n], XmNsliderSize, 1); ++n;
1391   XtSetArg(args[n], XmNvalue, 0); ++n;
1392   XtSetArg(args[n], XmNincrement, 1); ++n;
1393   XtSetArg(args[n], XmNpageIncrement, 1); ++n;
1394   vScrollBar = XmCreateScrollBar(scrolledWin, "vScrollBar", args, n);
1395   XtManageChild(vScrollBar);
1396   XtAddCallback(vScrollBar, XmNvalueChangedCallback,
1397                 &vScrollChangeCbk, (XtPointer)this);
1398 #ifndef DISABLE_SMOOTH_SCROLL
1399   XtAddCallback(vScrollBar, XmNdragCallback,
1400                 &vScrollDragCbk, (XtPointer)this);
1401 #endif
1402
1403   // create the drawing area
1404   n = 0;
1405   XtSetArg(args[n], XmNshadowType, XmSHADOW_IN); ++n;
1406   XtSetArg(args[n], XmNmarginWidth, 0); ++n;
1407   XtSetArg(args[n], XmNmarginHeight, 0); ++n;
1408   if (fullScreen) {
1409     XtSetArg(args[n], XmNshadowThickness, 0); ++n;
1410   }
1411   drawAreaFrame = XmCreateFrame(scrolledWin, "drawAreaFrame", args, n);
1412   XtManageChild(drawAreaFrame);
1413   n = 0;
1414   XtSetArg(args[n], XmNresizePolicy, XmRESIZE_ANY); ++n;
1415   XtSetArg(args[n], XmNbackground, paperColor); ++n;
1416   XtSetArg(args[n], XmNwidth, 700); ++n;
1417   XtSetArg(args[n], XmNheight, 500); ++n;
1418   drawArea = XmCreateDrawingArea(drawAreaFrame, "drawArea", args, n);
1419   XtManageChild(drawArea);
1420   XtAddCallback(drawArea, XmNresizeCallback, &resizeCbk, (XtPointer)this);
1421   XtAddCallback(drawArea, XmNexposeCallback, &redrawCbk, (XtPointer)this);
1422   XtAddCallback(drawArea, XmNinputCallback, &inputCbk, (XtPointer)this);
1423   resizeCbk(drawArea, this, NULL);
1424
1425   // set up mouse motion translations
1426   XtOverrideTranslations(drawArea, XtParseTranslationTable(
1427       "<Btn1Down>:DrawingAreaInput()\n"
1428       "<Btn1Up>:DrawingAreaInput()\n"
1429       "<Btn1Motion>:DrawingAreaInput()\n"
1430       "<Motion>:DrawingAreaInput()"));
1431
1432   // can't create a GC until the window gets mapped
1433   drawAreaGC = NULL;
1434   selectGC = NULL;
1435   highlightGC = NULL;
1436 }
1437
1438 void XPDFCore::hScrollChangeCbk(Widget widget, XtPointer ptr,
1439                              XtPointer callData) {
1440   XPDFCore *core = (XPDFCore *)ptr;
1441   XmScrollBarCallbackStruct *data = (XmScrollBarCallbackStruct *)callData;
1442
1443   core->scrollTo(data->value, core->scrollY);
1444 }
1445
1446 void XPDFCore::hScrollDragCbk(Widget widget, XtPointer ptr,
1447                               XtPointer callData) {
1448   XPDFCore *core = (XPDFCore *)ptr;
1449   XmScrollBarCallbackStruct *data = (XmScrollBarCallbackStruct *)callData;
1450
1451   core->scrollTo(data->value, core->scrollY);
1452 }
1453
1454 void XPDFCore::vScrollChangeCbk(Widget widget, XtPointer ptr,
1455                              XtPointer callData) {
1456   XPDFCore *core = (XPDFCore *)ptr;
1457   XmScrollBarCallbackStruct *data = (XmScrollBarCallbackStruct *)callData;
1458
1459   core->scrollTo(core->scrollX, data->value);
1460 }
1461
1462 void XPDFCore::vScrollDragCbk(Widget widget, XtPointer ptr,
1463                               XtPointer callData) {
1464   XPDFCore *core = (XPDFCore *)ptr;
1465   XmScrollBarCallbackStruct *data = (XmScrollBarCallbackStruct *)callData;
1466
1467   core->scrollTo(core->scrollX, data->value);
1468 }
1469
1470 void XPDFCore::resizeCbk(Widget widget, XtPointer ptr, XtPointer callData) {
1471   XPDFCore *core = (XPDFCore *)ptr;
1472   Arg args[2];
1473   int n;
1474   Dimension w, h;
1475
1476   n = 0;
1477   XtSetArg(args[n], XmNwidth, &w); ++n;
1478   XtSetArg(args[n], XmNheight, &h); ++n;
1479   XtGetValues(core->drawArea, args, n);
1480   core->drawAreaWidth = (int)w;
1481   core->drawAreaHeight = (int)h;
1482   if (core->page >= 0 &&
1483       (core->zoom == zoomPage || core->zoom == zoomWidth)) {
1484     core->displayPage(core->page, core->zoom, core->rotate,
1485                       gFalse, gFalse);
1486   } else {
1487     core->updateScrollBars();
1488   }
1489 }
1490
1491 void XPDFCore::redrawCbk(Widget widget, XtPointer ptr, XtPointer callData) {
1492   XPDFCore *core = (XPDFCore *)ptr;
1493   XmDrawingAreaCallbackStruct *data = (XmDrawingAreaCallbackStruct *)callData;
1494   int x, y, w, h;
1495
1496   if (data->reason == XmCR_EXPOSE) {
1497     x = core->scrollX + data->event->xexpose.x;
1498     y = core->scrollY + data->event->xexpose.y;
1499     w = data->event->xexpose.width;
1500     h = data->event->xexpose.height;
1501   } else {
1502     x = core->scrollX;
1503     y = core->scrollY;
1504     w = core->drawAreaWidth;
1505     h = core->drawAreaHeight;
1506   }
1507   core->redrawRectangle(x, y, w, h);
1508 }
1509
1510 void XPDFCore::outputDevRedrawCbk(void *data) {
1511   XPDFCore *core = (XPDFCore *)data;
1512
1513   core->redrawRectangle(core->scrollX, core->scrollY,
1514                         core->drawAreaWidth, core->drawAreaHeight);
1515 }
1516
1517 void XPDFCore::inputCbk(Widget widget, XtPointer ptr, XtPointer callData) {
1518   XPDFCore *core = (XPDFCore *)ptr;
1519   XmDrawingAreaCallbackStruct *data = (XmDrawingAreaCallbackStruct *)callData;
1520   LinkAction *action;
1521   int mx, my;
1522   double x, y;
1523   char *s;
1524   KeySym key;
1525   char buf[20];
1526   int n;
1527
1528   switch (data->event->type) {
1529   case ButtonPress:
1530     if (data->event->xbutton.button == 1) {
1531       core->takeFocus();
1532       if (core->doc && core->doc->getNumPages() > 0) {
1533         if (core->selectEnabled) {
1534           mx = core->scrollX + data->event->xbutton.x;
1535           my = core->scrollY + data->event->xbutton.y;
1536           core->setSelection(mx, my, mx, my);
1537           core->setCursor(core->selectCursor);
1538           core->dragging = gTrue;
1539         }
1540       }
1541     } else if (data->event->xbutton.button == 2) {
1542       if (!core->fullScreen) {
1543         core->panning = gTrue;
1544         core->panMX = data->event->xbutton.x;
1545         core->panMY = data->event->xbutton.y;
1546       }
1547     } else if (data->event->xbutton.button == 4) { // mouse wheel up
1548       if (core->fullScreen) {
1549         core->gotoPrevPage(1, gTrue, gFalse);
1550       } else if (core->scrollY == 0) {
1551         core->gotoPrevPage(1, gFalse, gTrue);
1552       } else {
1553         core->scrollUp(1);
1554       }
1555     } else if (data->event->xbutton.button == 5) { // mouse wheel down
1556       if (core->fullScreen ||
1557           core->scrollY >=
1558             core->out->getPixmapHeight() - core->drawAreaHeight) {
1559         core->gotoNextPage(1, gTrue);
1560       } else {
1561         core->scrollDown(1);
1562       }
1563     } else if (data->event->xbutton.button == 6) { // second mouse wheel right
1564       if (!core->fullScreen) {
1565         core->scrollRight(1);
1566       }
1567     } else if (data->event->xbutton.button == 7) { // second mouse wheel left
1568       if (!core->fullScreen) {
1569         core->scrollLeft(1);
1570       }
1571     } else {
1572       if (*core->mouseCbk) {
1573         (*core->mouseCbk)(core->mouseCbkData, data->event);
1574       }
1575     }
1576     break;
1577   case ButtonRelease:
1578     if (data->event->xbutton.button == 1) {
1579       if (core->doc && core->doc->getNumPages() > 0) {
1580         mx = core->scrollX + data->event->xbutton.x;
1581         my = core->scrollY + data->event->xbutton.y;
1582         if (core->dragging) {
1583           core->dragging = gFalse;
1584           core->setCursor(None);
1585           core->moveSelection(mx, my);
1586 #ifndef NO_TEXT_SELECT
1587           if (core->selectXMin != core->selectXMax &&
1588               core->selectYMin != core->selectYMax) {
1589             if (core->doc->okToCopy()) {
1590               core->copySelection();
1591             } else {
1592               error(-1, "Copying of text from this document is not allowed.");
1593             }
1594           }
1595 #endif
1596         }
1597         if (core->hyperlinksEnabled) {
1598           if (core->selectXMin == core->selectXMax ||
1599             core->selectYMin == core->selectYMax) {
1600             core->doLink(mx, my);
1601           }
1602         }
1603       }
1604     } else if (data->event->xbutton.button == 2) {
1605       core->panning = gFalse;
1606     } else {
1607       if (*core->mouseCbk) {
1608         (*core->mouseCbk)(core->mouseCbkData, data->event);
1609       }
1610     }
1611     break;
1612   case MotionNotify:
1613     if (core->doc && core->doc->getNumPages() > 0) {
1614       mx = core->scrollX + data->event->xbutton.x;
1615       my = core->scrollY + data->event->xbutton.y;
1616       if (core->dragging) {
1617         core->moveSelection(mx, my);
1618       } else if (core->hyperlinksEnabled) {
1619         core->out->cvtDevToUser(mx, my, &x, &y);
1620         if ((action = core->doc->findLink(x, y))) {
1621           core->setCursor(core->linkCursor);
1622           if (action != core->linkAction) {
1623             core->linkAction = action;
1624             if (core->updateCbk) {
1625               s = "";
1626               switch (action->getKind()) {
1627               case actionGoTo:
1628                 s = "[internal link]";
1629                 break;
1630               case actionGoToR:
1631                 s = ((LinkGoToR *)action)->getFileName()->getCString();
1632                 break;
1633               case actionLaunch:
1634                 s = ((LinkLaunch *)action)->getFileName()->getCString();
1635                 break;
1636               case actionURI:
1637                 s = ((LinkURI *)action)->getURI()->getCString();
1638                 break;
1639               case actionNamed:
1640                 s = ((LinkNamed *)action)->getName()->getCString();
1641                 break;
1642               case actionMovie:
1643                 s = "[movie]";
1644                 break;
1645               case actionUnknown:
1646                 s = "[unknown link]";
1647                 break;
1648               }
1649               (*core->updateCbk)(core->updateCbkData, NULL, -1, -1, s);
1650             }
1651           }
1652         } else {
1653           core->setCursor(None);
1654           if (core->linkAction) {
1655             core->linkAction = NULL;
1656             if (core->updateCbk) {
1657               (*core->updateCbk)(core->updateCbkData, NULL, -1, -1, "");
1658             }
1659           }
1660         }
1661       }
1662     }
1663     if (core->panning) {
1664       core->scrollTo(core->scrollX - (data->event->xbutton.x - core->panMX),
1665                      core->scrollY - (data->event->xbutton.y - core->panMY));
1666       core->panMX = data->event->xbutton.x;
1667       core->panMY = data->event->xbutton.y;
1668     }
1669     break;
1670   case KeyPress:
1671     n = XLookupString(&data->event->xkey, buf, sizeof(buf) - 1,
1672                       &key, NULL);
1673     core->keyPress(buf, key, data->event->xkey.state);
1674     break;
1675   }
1676 }
1677
1678 void XPDFCore::keyPress(char *s, KeySym key, Guint modifiers) {
1679   switch (key) {
1680   case XK_Home:
1681   case XK_KP_Home:
1682     if (modifiers & ControlMask) {
1683       displayPage(1, zoom, rotate, gTrue, gTrue);
1684     } else if (!fullScreen) {
1685       scrollTo(0, 0);
1686     }
1687     return;
1688   case XK_End:
1689   case XK_KP_End:
1690     if (modifiers & ControlMask) {
1691       displayPage(doc->getNumPages(), zoom, rotate, gTrue, gTrue);
1692     } else if (!fullScreen) {
1693       scrollTo(out->getPixmapWidth() - drawAreaWidth,
1694                out->getPixmapHeight() - drawAreaHeight);
1695     }
1696     return;
1697   case XK_Page_Up:
1698   case XK_KP_Page_Up:
1699     if (fullScreen) {
1700       gotoPrevPage(1, gTrue, gFalse);
1701     } else {
1702       scrollPageUp();
1703     }
1704     return;
1705   case XK_Page_Down:
1706   case XK_KP_Page_Down:
1707     if (fullScreen) {
1708       gotoNextPage(1, gTrue);
1709     } else {
1710       scrollPageDown();
1711     }
1712     return;
1713   case XK_Left:
1714   case XK_KP_Left:
1715     if (!fullScreen) {
1716       scrollLeft();
1717     }
1718     return;
1719   case XK_Right:
1720   case XK_KP_Right:
1721     if (!fullScreen) {
1722       scrollRight();
1723     }
1724     return;
1725   case XK_Up:
1726   case XK_KP_Up:
1727     if (!fullScreen) {
1728       scrollUp();
1729     }
1730     return;
1731   case XK_Down:
1732   case XK_KP_Down:
1733     if (!fullScreen) {
1734       scrollDown();
1735     }
1736     return;
1737   }
1738
1739   if (*keyPressCbk) {
1740     (*keyPressCbk)(keyPressCbkData, s, key, modifiers);
1741   }
1742 }
1743
1744 void XPDFCore::redrawRectangle(int x, int y, int w, int h) {
1745   XGCValues gcValues;
1746   Window drawAreaWin;
1747
1748   // clip to window
1749   if (x < scrollX) {
1750     w -= scrollX - x;
1751     x = scrollX;
1752   }
1753   if (x + w > scrollX + drawAreaWidth) {
1754     w = scrollX + drawAreaWidth - x;
1755   }
1756   if (y < scrollY) {
1757     h -= scrollY - y;
1758     y = scrollY;
1759   }
1760   if (y + h > scrollY + drawAreaHeight) {
1761     h = scrollY + drawAreaHeight - y;
1762   }
1763
1764   // create a GC for the drawing area
1765   drawAreaWin = XtWindow(drawArea);
1766   if (!drawAreaGC) {
1767     gcValues.foreground = paperColor;
1768     drawAreaGC = XCreateGC(display, drawAreaWin, GCForeground, &gcValues);
1769   }
1770
1771   // draw white background past the edges of the document
1772   if (x + w > out->getPixmapWidth()) {
1773     XFillRectangle(display, drawAreaWin, drawAreaGC,
1774                    out->getPixmapWidth() - scrollX, y - scrollY,
1775                    x + w - out->getPixmapWidth(), h);
1776     w = out->getPixmapWidth() - x;
1777   }
1778   if (y + h > out->getPixmapHeight()) {
1779     XFillRectangle(display, drawAreaWin, drawAreaGC,
1780                    x - scrollX, out->getPixmapHeight() - scrollY,
1781                    w, y + h - out->getPixmapHeight());
1782     h = out->getPixmapHeight() - y;
1783   }
1784
1785   // redraw (checking to see if pixmap has been allocated yet)
1786   if (out->getPixmapWidth() > 0) {
1787     XCopyArea(display, out->getPixmap(), drawAreaWin, drawAreaGC,
1788               x, y, w, h, x - scrollX, y - scrollY);
1789   }
1790 }
1791
1792 void XPDFCore::updateScrollBars() {
1793   Arg args[20];
1794   int n;
1795   int maxPos;
1796
1797   maxPos = out ? out->getPixmapWidth() : 1;
1798   if (maxPos < drawAreaWidth) {
1799     maxPos = drawAreaWidth;
1800   }
1801   if (scrollX > maxPos - drawAreaWidth) {
1802     scrollX = maxPos - drawAreaWidth;
1803   }
1804   n = 0;
1805   XtSetArg(args[n], XmNvalue, scrollX); ++n;
1806   XtSetArg(args[n], XmNmaximum, maxPos); ++n;
1807   XtSetArg(args[n], XmNsliderSize, drawAreaWidth); ++n;
1808   XtSetArg(args[n], XmNincrement, 16); ++n;
1809   XtSetArg(args[n], XmNpageIncrement, drawAreaWidth); ++n;
1810   XtSetValues(hScrollBar, args, n);
1811
1812   maxPos = out ? out->getPixmapHeight() : 1;
1813   if (maxPos < drawAreaHeight) {
1814     maxPos = drawAreaHeight;
1815   }
1816   if (scrollY > maxPos - drawAreaHeight) {
1817     scrollY = maxPos - drawAreaHeight;
1818   }
1819   n = 0;
1820   XtSetArg(args[n], XmNvalue, scrollY); ++n;
1821   XtSetArg(args[n], XmNmaximum, maxPos); ++n;
1822   XtSetArg(args[n], XmNsliderSize, drawAreaHeight); ++n;
1823   XtSetArg(args[n], XmNincrement, 16); ++n;
1824   XtSetArg(args[n], XmNpageIncrement, drawAreaHeight); ++n;
1825   XtSetValues(vScrollBar, args, n);
1826 }
1827
1828 void XPDFCore::setCursor(Cursor cursor) {
1829   Window topWin;
1830
1831   if (cursor == currentCursor) {
1832     return;
1833   }
1834   if (!(topWin = XtWindow(shell))) {
1835     return;
1836   }
1837   if (cursor == None) {
1838     XUndefineCursor(display, topWin);
1839   } else {
1840     XDefineCursor(display, topWin, cursor);
1841   }
1842   XFlush(display);
1843   currentCursor = cursor;
1844 }
1845
1846 GBool XPDFCore::doQuestionDialog(char *title, GString *msg) {
1847   return doDialog(XmDIALOG_QUESTION, gTrue, title, msg);
1848 }
1849
1850 void XPDFCore::doInfoDialog(char *title, GString *msg) {
1851   doDialog(XmDIALOG_INFORMATION, gFalse, title, msg);
1852 }
1853
1854 void XPDFCore::doErrorDialog(char *title, GString *msg) {
1855   doDialog(XmDIALOG_ERROR, gFalse, title, msg);
1856 }
1857
1858 GBool XPDFCore::doDialog(int type, GBool hasCancel,
1859                          char *title, GString *msg) {
1860   Widget dialog;
1861   XtAppContext appContext;
1862   Arg args[20];
1863   int n;
1864   XmString s1, s2;
1865   XEvent event;
1866
1867   n = 0;
1868   XtSetArg(args[n], XmNdialogType, type); ++n;
1869   XtSetArg(args[n], XmNdialogStyle, XmDIALOG_PRIMARY_APPLICATION_MODAL); ++n;
1870   s1 = XmStringCreateLocalized(title);
1871   XtSetArg(args[n], XmNdialogTitle, s1); ++n;
1872   s2 = XmStringCreateLocalized(msg->getCString());
1873   XtSetArg(args[n], XmNmessageString, s2); ++n;
1874   dialog = XmCreateMessageDialog(drawArea, "questionDialog", args, n);
1875   XmStringFree(s1);
1876   XmStringFree(s2);
1877   XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
1878   XtAddCallback(dialog, XmNokCallback,
1879                 &dialogOkCbk, (XtPointer)this);
1880   if (hasCancel) {
1881     XtAddCallback(dialog, XmNcancelCallback,
1882                   &dialogCancelCbk, (XtPointer)this);
1883   } else {
1884     XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
1885   }
1886
1887   XtManageChild(dialog);
1888
1889   appContext = XtWidgetToApplicationContext(dialog);
1890   dialogDone = 0;
1891   do {
1892     XtAppNextEvent(appContext, &event);
1893     XtDispatchEvent(&event);
1894   } while (!dialogDone);
1895
1896   XtUnmanageChild(dialog);
1897   XtDestroyWidget(dialog);
1898
1899   return dialogDone > 0;
1900 }
1901
1902 void XPDFCore::dialogOkCbk(Widget widget, XtPointer ptr,
1903                            XtPointer callData) {
1904   XPDFCore *core = (XPDFCore *)ptr;
1905
1906   core->dialogDone = 1;
1907 }
1908
1909 void XPDFCore::dialogCancelCbk(Widget widget, XtPointer ptr,
1910                                XtPointer callData) {
1911   XPDFCore *core = (XPDFCore *)ptr;
1912
1913   core->dialogDone = -1;
1914 }