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