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