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