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