//========================================================================
//
// XPDFViewer.cc
//
// Copyright 2002-2003 Glyph & Cog, LLC
//
//========================================================================

#include <aconf.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <X11/keysym.h>
#include <X11/cursorfont.h>
#ifdef HAVE_X11_XPM_H
#include <X11/xpm.h>
#endif
#if defined(__sgi) && (XmVERSION <= 1)
#define Object XtObject
#include <Sgm/HPanedW.h>
#undef Object
#endif
#include "gmem.h"
#include "gfile.h"
#include "GString.h"
#include "GList.h"
#include "Error.h"
#include "GlobalParams.h"
#include "PDFDoc.h"
#include "Link.h"
#include "ErrorCodes.h"
#include "Outline.h"
#include "UnicodeMap.h"
#ifndef DISABLE_OUTLINE
#define Object XtObject
#include "XPDFTree.h"
#undef Object
#endif
#include "XPDFApp.h"
#include "XPDFViewer.h"
#include "PSOutputDev.h"
#include "config.h"

// these macro defns conflict with xpdf's Object class
#ifdef LESSTIF_VERSION
#undef XtDisplay
#undef XtScreen
#undef XtWindow
#undef XtParent
#undef XtIsRealized
#endif

#if XmVERSION <= 1
#define XmSET   True
#define XmUNSET False
#endif

// hack around old X includes which are missing these symbols
#ifndef XK_Page_Up
#define XK_Page_Up              0xFF55
#endif
#ifndef XK_Page_Down
#define XK_Page_Down            0xFF56
#endif
#ifndef XK_KP_Home
#define XK_KP_Home              0xFF95
#endif
#ifndef XK_KP_Left
#define XK_KP_Left              0xFF96
#endif
#ifndef XK_KP_Up
#define XK_KP_Up                0xFF97
#endif
#ifndef XK_KP_Right
#define XK_KP_Right             0xFF98
#endif
#ifndef XK_KP_Down
#define XK_KP_Down              0xFF99
#endif
#ifndef XK_KP_Prior
#define XK_KP_Prior             0xFF9A
#endif
#ifndef XK_KP_Page_Up
#define XK_KP_Page_Up           0xFF9A
#endif
#ifndef XK_KP_Next
#define XK_KP_Next              0xFF9B
#endif
#ifndef XK_KP_Page_Down
#define XK_KP_Page_Down         0xFF9B
#endif
#ifndef XK_KP_End
#define XK_KP_End               0xFF9C
#endif
#ifndef XK_KP_Begin
#define XK_KP_Begin             0xFF9D
#endif
#ifndef XK_KP_Insert
#define XK_KP_Insert            0xFF9E
#endif
#ifndef XK_KP_Delete
#define XK_KP_Delete            0xFF9F
#endif

//------------------------------------------------------------------------
// GUI includes
//------------------------------------------------------------------------

#include "xpdfIcon.xpm"
#include "leftArrow.xbm"
#include "leftArrowDis.xbm"
#include "dblLeftArrow.xbm"
#include "dblLeftArrowDis.xbm"
#include "rightArrow.xbm"
#include "rightArrowDis.xbm"
#include "dblRightArrow.xbm"
#include "dblRightArrowDis.xbm"
#include "backArrow.xbm"
#include "backArrowDis.xbm"
#include "forwardArrow.xbm"
#include "forwardArrowDis.xbm"
#include "find.xbm"
#include "findDis.xbm"
#include "print.xbm"
#include "printDis.xbm"
#include "about.xbm"
#include "about-text.h"

//------------------------------------------------------------------------

struct ZoomMenuInfo {
  const char *label;
  double zoom;
};

static ZoomMenuInfo zoomMenuInfo[nZoomMenuItems] = {
  { "400%",      400 },
  { "200%",      200 },
  { "150%",      150 },
  { "125%",      125 },
  { "100%",      100 },
  { "50%",        50 },
  { "25%",        25 },
  { "12.5%",      12.5 },
  { "fit page",  zoomPage },
  { "fit width", zoomWidth }
};

#define maxZoomIdx   0
#define defZoomIdx   3
#define minZoomIdx   7
#define zoomPageIdx  8
#define zoomWidthIdx 9

//------------------------------------------------------------------------

#define cmdMaxArgs 8

XPDFViewerCmd XPDFViewer::cmdTab[] = {
  { "about",                   0, gFalse, gFalse, &XPDFViewer::cmdAbout },
  { "closeOutline",            0, gFalse, gFalse, &XPDFViewer::cmdCloseOutline },
  { "closeWindow",             0, gFalse, gFalse, &XPDFViewer::cmdCloseWindow },
  { "continuousMode",          0, gFalse, gFalse, &XPDFViewer::cmdContinuousMode },
  { "endPan",                  0, gTrue,  gTrue,  &XPDFViewer::cmdEndPan },
  { "endSelection",            0, gTrue,  gTrue,  &XPDFViewer::cmdEndSelection },
  { "find",                    0, gTrue,  gFalse, &XPDFViewer::cmdFind },
  { "findNext",                0, gTrue,  gFalse, &XPDFViewer::cmdFindNext },
  { "focusToDocWin",           0, gFalse, gFalse, &XPDFViewer::cmdFocusToDocWin },
  { "focusToPageNum",          0, gFalse, gFalse, &XPDFViewer::cmdFocusToPageNum },
  { "followLink",              0, gTrue,  gTrue,  &XPDFViewer::cmdFollowLink },
  { "followLinkInNewWin",      0, gTrue,  gTrue,  &XPDFViewer::cmdFollowLinkInNewWin },
  { "followLinkInNewWinNoSel", 0, gTrue,  gTrue,  &XPDFViewer::cmdFollowLinkInNewWinNoSel },
  { "followLinkNoSel",         0, gTrue,  gTrue,  &XPDFViewer::cmdFollowLinkNoSel },
  { "fullScreenMode",          0, gFalse, gFalse, &XPDFViewer::cmdFullScreenMode },
  { "goBackward",              0, gFalse, gFalse, &XPDFViewer::cmdGoBackward },
  { "goForward",               0, gFalse, gFalse, &XPDFViewer::cmdGoForward },
  { "gotoDest",                1, gTrue,  gFalse, &XPDFViewer::cmdGotoDest },
  { "gotoLastPage",            0, gTrue,  gFalse, &XPDFViewer::cmdGotoLastPage },
  { "gotoLastPageNoScroll",    0, gTrue,  gFalse, &XPDFViewer::cmdGotoLastPageNoScroll },
  { "gotoPage",                1, gTrue,  gFalse, &XPDFViewer::cmdGotoPage },
  { "gotoPageNoScroll",        1, gTrue,  gFalse, &XPDFViewer::cmdGotoPageNoScroll },
  { "nextPage",                0, gTrue,  gFalse, &XPDFViewer::cmdNextPage },
  { "nextPageNoScroll",        0, gTrue,  gFalse, &XPDFViewer::cmdNextPageNoScroll },
  { "open",                    0, gFalse, gFalse, &XPDFViewer::cmdOpen },
  { "openFile",                1, gFalse, gFalse, &XPDFViewer::cmdOpenFile },
  { "openFileAtDest",          2, gFalse, gFalse, &XPDFViewer::cmdOpenFileAtDest },
  { "openFileAtDestInNewWin",  2, gFalse, gFalse, &XPDFViewer::cmdOpenFileAtDestInNewWin },
  { "openFileAtPage",          2, gFalse, gFalse, &XPDFViewer::cmdOpenFileAtPage },
  { "openFileAtPageInNewWin",  2, gFalse, gFalse, &XPDFViewer::cmdOpenFileAtPageInNewWin },
  { "openFileInNewWin",        1, gFalse, gFalse, &XPDFViewer::cmdOpenFileInNewWin },
  { "openInNewWin",            0, gFalse, gFalse, &XPDFViewer::cmdOpenInNewWin },
  { "openOutline",             0, gFalse, gFalse, &XPDFViewer::cmdOpenOutline },
  { "pageDown",                0, gTrue,  gFalse, &XPDFViewer::cmdPageDown },
  { "pageUp",                  0, gTrue,  gFalse, &XPDFViewer::cmdPageUp },
  { "postPopupMenu",           0, gFalse, gTrue,  &XPDFViewer::cmdPostPopupMenu },
  { "prevPage",                0, gTrue,  gFalse, &XPDFViewer::cmdPrevPage },
  { "prevPageNoScroll",        0, gTrue,  gFalse, &XPDFViewer::cmdPrevPageNoScroll },
  { "print",                   0, gTrue,  gFalse, &XPDFViewer::cmdPrint },
  { "quit",                    0, gFalse, gFalse, &XPDFViewer::cmdQuit },
  { "raise",                   0, gFalse, gFalse, &XPDFViewer::cmdRaise },
  { "redraw",                  0, gTrue,  gFalse, &XPDFViewer::cmdRedraw },
  { "reload",                  0, gTrue,  gFalse, &XPDFViewer::cmdReload },
  { "rotateCCW",               0, gTrue,  gFalse, &XPDFViewer::cmdRotateCCW },
  { "rotateCW",                0, gTrue,  gFalse, &XPDFViewer::cmdRotateCW },
  { "run",                     1, gFalse, gFalse, &XPDFViewer::cmdRun },
  { "scrollDown",              1, gTrue,  gFalse, &XPDFViewer::cmdScrollDown },
  { "scrollDownNextPage",      1, gTrue,  gFalse, &XPDFViewer::cmdScrollDownNextPage },
  { "scrollLeft",              1, gTrue,  gFalse, &XPDFViewer::cmdScrollLeft },
  { "scrollOutlineDown",       1, gTrue,  gFalse, &XPDFViewer::cmdScrollOutlineDown },
  { "scrollOutlineUp",         1, gTrue,  gFalse, &XPDFViewer::cmdScrollOutlineUp },
  { "scrollRight",             1, gTrue,  gFalse, &XPDFViewer::cmdScrollRight },
  { "scrollToBottomEdge",      0, gTrue,  gFalse, &XPDFViewer::cmdScrollToBottomEdge },
  { "scrollToBottomRight",     0, gTrue,  gFalse, &XPDFViewer::cmdScrollToBottomRight },
  { "scrollToLeftEdge",        0, gTrue,  gFalse, &XPDFViewer::cmdScrollToLeftEdge },
  { "scrollToRightEdge",       0, gTrue,  gFalse, &XPDFViewer::cmdScrollToRightEdge },
  { "scrollToTopEdge",         0, gTrue,  gFalse, &XPDFViewer::cmdScrollToTopEdge },
  { "scrollToTopLeft",         0, gTrue,  gFalse, &XPDFViewer::cmdScrollToTopLeft },
  { "scrollUp",                1, gTrue,  gFalse, &XPDFViewer::cmdScrollUp },
  { "scrollUpPrevPage",        1, gTrue,  gFalse, &XPDFViewer::cmdScrollUpPrevPage },
  { "setSelection",            5, gTrue,  gFalse, &XPDFViewer::cmdSetSelection },
  { "singlePageMode",          0, gFalse, gFalse, &XPDFViewer::cmdSinglePageMode },
  { "startPan",                0, gTrue,  gTrue,  &XPDFViewer::cmdStartPan },
  { "startSelection",          0, gTrue,  gTrue,  &XPDFViewer::cmdStartSelection },
  { "toggleContinuousMode",    0, gFalse, gFalse, &XPDFViewer::cmdToggleContinuousMode },
  { "toggleFullScreenMode",    0, gFalse, gFalse, &XPDFViewer::cmdToggleFullScreenMode },
  { "toggleOutline",           0, gFalse, gFalse, &XPDFViewer::cmdToggleOutline },
  { "windowMode",              0, gFalse, gFalse, &XPDFViewer::cmdWindowMode },
  { "zoomFitPage",             0, gFalse, gFalse, &XPDFViewer::cmdZoomFitPage },
  { "zoomFitWidth",            0, gFalse, gFalse, &XPDFViewer::cmdZoomFitWidth },
  { "zoomIn",                  0, gFalse, gFalse, &XPDFViewer::cmdZoomIn },
  { "zoomOut",                 0, gFalse, gFalse, &XPDFViewer::cmdZoomOut },
  { "zoomPercent",             1, gFalse, gFalse, &XPDFViewer::cmdZoomPercent },
  { "zoomToSelection",         0, gTrue,  gFalse, &XPDFViewer::cmdZoomToSelection }
};

#define nCmds (sizeof(cmdTab) / sizeof(XPDFViewerCmd))

//------------------------------------------------------------------------

XPDFViewer::XPDFViewer(XPDFApp *appA, GString *fileName,
		       int pageA, GString *destName, GBool fullScreen,
		       GString *ownerPassword, GString *userPassword) {
  LinkDest *dest;
  int pg;
  double z;

  app = appA;
  win = NULL;
  core = NULL;
  ok = gFalse;
#ifndef DISABLE_OUTLINE
  outlineLabels = NULL;
  outlineLabelsLength = outlineLabelsSize = 0;
  outlinePaneWidth = 175;
#endif

  // do Motif-specific initialization and create the window;
  // this also creates the core object
  initWindow(fullScreen);
  initAboutDialog();
  initFindDialog();
  initPrintDialog();
  openDialog = NULL;
  saveAsDialog = NULL;

  dest = NULL; // make gcc happy
  pg = pageA; // make gcc happy

  if (fileName) {
    if (loadFile(fileName, ownerPassword, userPassword)) {
      getPageAndDest(pageA, destName, &pg, &dest);
#ifndef DISABLE_OUTLINE
      if (outlineScroll != None &&
	  core->getDoc()->getOutline()->getItems() &&
	  core->getDoc()->getOutline()->getItems()->getLength() > 0) {
	XtVaSetValues(outlineScroll, XmNwidth, outlinePaneWidth, NULL);
      }
#endif
    } else {
      return;
    }
  }
  core->resizeToPage(pg);

  // map the window -- we do this after calling resizeToPage to avoid
  // an annoying on-screen resize
  mapWindow();

  // display the first page
  z = core->getZoom();
  if (dest) {
    displayDest(dest, z, core->getRotate(), gTrue);
    delete dest;
  } else {
    displayPage(pg, z, core->getRotate(), gTrue, gTrue);
  }

  ok = gTrue;
}

XPDFViewer::XPDFViewer(XPDFApp *appA, PDFDoc *doc, int pageA,
		       GString *destName, GBool fullScreen) {
  LinkDest *dest;
  int pg;
  double z;

  app = appA;
  win = NULL;
  core = NULL;
  ok = gFalse;
#ifndef DISABLE_OUTLINE
  outlineLabels = NULL;
  outlineLabelsLength = outlineLabelsSize = 0;
  outlinePaneWidth = 175;
#endif

  // do Motif-specific initialization and create the window;
  // this also creates the core object
  initWindow(fullScreen);
  initAboutDialog();
  initFindDialog();
  initPrintDialog();
  openDialog = NULL;
  saveAsDialog = NULL;

  dest = NULL; // make gcc happy
  pg = pageA; // make gcc happy

  if (doc) {
    core->loadDoc(doc);
    getPageAndDest(pageA, destName, &pg, &dest);
#ifndef DISABLE_OUTLINE
    if (outlineScroll != None &&
	core->getDoc()->getOutline()->getItems() &&
	core->getDoc()->getOutline()->getItems()->getLength() > 0) {
      XtVaSetValues(outlineScroll, XmNwidth, outlinePaneWidth, NULL);
    }
#endif
  }
  core->resizeToPage(pg);

  // map the window -- we do this after calling resizeToPage to avoid
  // an annoying on-screen resize
  mapWindow();

  // display the first page
  z = core->getZoom();
  if (dest) {
    displayDest(dest, z, core->getRotate(), gTrue);
    delete dest;
  } else {
    displayPage(pg, z, core->getRotate(), gTrue, gTrue);
  }

  ok = gTrue;
}

XPDFViewer::~XPDFViewer() {
  delete core;
  if (aboutBigFont) {
    XmFontListFree(aboutBigFont);
  }
  if (aboutVersionFont) {
    XmFontListFree(aboutVersionFont);
  }
  if (aboutFixedFont) {
    XmFontListFree(aboutFixedFont);
  }
  closeWindow();
#ifndef DISABLE_OUTLINE
  if (outlineLabels) {
    gfree(outlineLabels);
  }
#endif
}

void XPDFViewer::open(GString *fileName, int pageA, GString *destName) {
  LinkDest *dest;
  int pg;
  double z;

  if (!core->getDoc() || fileName->cmp(core->getDoc()->getFileName())) {
    if (!loadFile(fileName, NULL, NULL)) {
      return;
    }
  }
  getPageAndDest(pageA, destName, &pg, &dest);
  z = core->getZoom();
  if (dest) {
    displayDest(dest, z, core->getRotate(), gTrue);
    delete dest;
  } else {
    displayPage(pg, z, core->getRotate(), gTrue, gTrue);
  }
}

void XPDFViewer::clear() {
  char *title;
  XmString s;

  core->clear();

  // set up title
  title = app->getTitle() ? app->getTitle()->getCString()
                          : (char *)xpdfAppName;
  XtVaSetValues(win, XmNtitle, title, XmNiconName, title, NULL);

  if (toolBar != None) {

    // set up number-of-pages display
    s = XmStringCreateLocalized("");
    XtVaSetValues(pageNumText, XmNlabelString, s, NULL);
    XmStringFree(s);
    s = XmStringCreateLocalized(" of 0");
    XtVaSetValues(pageCountLabel, XmNlabelString, s, NULL);
    XmStringFree(s);

    // disable buttons
    XtVaSetValues(prevTenPageBtn, XmNsensitive, False, NULL);
    XtVaSetValues(prevPageBtn, XmNsensitive, False, NULL);
    XtVaSetValues(nextTenPageBtn, XmNsensitive, False, NULL);
    XtVaSetValues(nextPageBtn, XmNsensitive, False, NULL);
  }

  // remove the old outline
#ifndef DISABLE_OUTLINE
  setupOutline();
#endif
}

//------------------------------------------------------------------------
// load / display
//------------------------------------------------------------------------

GBool XPDFViewer::loadFile(GString *fileName, GString *ownerPassword,
			   GString *userPassword) {
  return core->loadFile(fileName, ownerPassword, userPassword) == errNone;
}

void XPDFViewer::reloadFile() {
  int pg;

  if (!core->getDoc()) {
    return;
  }
  pg = core->getPageNum();
  loadFile(core->getDoc()->getFileName());
  if (pg > core->getDoc()->getNumPages()) {
    pg = core->getDoc()->getNumPages();
  }
  displayPage(pg, core->getZoom(), core->getRotate(), gFalse, gFalse);
}

void XPDFViewer::displayPage(int pageA, double zoomA, int rotateA,
			     GBool scrollToTop, GBool addToHist) {
  core->displayPage(pageA, zoomA, rotateA, scrollToTop, addToHist);
}

void XPDFViewer::displayDest(LinkDest *dest, double zoomA, int rotateA,
			     GBool addToHist) {
  core->displayDest(dest, zoomA, rotateA, addToHist);
}

void XPDFViewer::getPageAndDest(int pageA, GString *destName,
				int *pageOut, LinkDest **destOut) {
  Ref pageRef;

  // find the page number for a named destination
  *pageOut = pageA;
  *destOut = NULL;
  if (destName && (*destOut = core->getDoc()->findDest(destName))) {
    if ((*destOut)->isPageRef()) {
      pageRef = (*destOut)->getPageRef();
      *pageOut = core->getDoc()->findPage(pageRef.num, pageRef.gen);
    } else {
      *pageOut = (*destOut)->getPageNum();
    }
  }

  if (*pageOut <= 0) {
    *pageOut = 1;
  }
  if (*pageOut > core->getDoc()->getNumPages()) {
    *pageOut = core->getDoc()->getNumPages();
  }
}

//------------------------------------------------------------------------
// hyperlinks / actions
//------------------------------------------------------------------------

void XPDFViewer::doLink(int wx, int wy, GBool onlyIfNoSelection,
			GBool newWin) {
  XPDFViewer *newViewer;
  LinkAction *action;
  int pg, selPg;
  double xu, yu, selULX, selULY, selLRX, selLRY;

  if (core->getHyperlinksEnabled() &&
      core->cvtWindowToUser(wx, wy, &pg, &xu, &yu) &&
      !(onlyIfNoSelection &&
	core->getSelection(&selPg, &selULX, &selULY, &selLRX, &selLRY))) {
    if ((action = core->findLink(pg, xu, yu))) {
      if (newWin &&
	  core->getDoc()->getFileName() &&
	  (action->getKind() == actionGoTo ||
	   action->getKind() == actionGoToR ||
	   (action->getKind() == actionNamed &&
	    ((LinkNamed *)action)->getName()->cmp("Quit")))) {
	newViewer = app->open(core->getDoc()->getFileName());
	newViewer->core->doAction(action);
      } else {
	core->doAction(action);
      }
    }
  }
}

void XPDFViewer::actionCbk(void *data, char *action) {
  XPDFViewer *viewer = (XPDFViewer *)data;

  if (!strcmp(action, "Quit")) {
    viewer->app->quit();
  }
}

//------------------------------------------------------------------------
// keyboard/mouse input
//------------------------------------------------------------------------

void XPDFViewer::keyPressCbk(void *data, KeySym key, Guint modifiers,
			     XEvent *event) {
  XPDFViewer *viewer = (XPDFViewer *)data;
  int keyCode;
  GList *cmds;
  int i;

  if (key >= 0x20 && key <= 0xfe) {
    keyCode = (int)key;
  } else if (key == XK_Tab ||
	     key == XK_KP_Tab) {
    keyCode = xpdfKeyCodeTab;
  } else if (key == XK_Return) {
    keyCode = xpdfKeyCodeReturn;
  } else if (key == XK_KP_Enter) {
    keyCode = xpdfKeyCodeEnter;
  } else if (key == XK_BackSpace) {
    keyCode = xpdfKeyCodeBackspace;
  } else if (key == XK_Insert ||
	     key == XK_KP_Insert) {
    keyCode = xpdfKeyCodeInsert;
  } else if (key == XK_Delete ||
	     key == XK_KP_Delete) {
    keyCode = xpdfKeyCodeDelete;
  } else if (key == XK_Home ||
	     key == XK_KP_Home) {
    keyCode = xpdfKeyCodeHome;
  } else if (key == XK_End ||
	     key == XK_KP_End) {
    keyCode = xpdfKeyCodeEnd;
  } else if (key == XK_Page_Up ||
	     key == XK_KP_Page_Up) {
    keyCode = xpdfKeyCodePgUp;
  } else if (key == XK_Page_Down ||
	     key == XK_KP_Page_Down) {
    keyCode = xpdfKeyCodePgDn;
  } else if (key == XK_Left ||
	     key == XK_KP_Left) {
    keyCode = xpdfKeyCodeLeft;
  } else if (key == XK_Right ||
	     key == XK_KP_Right) {
    keyCode = xpdfKeyCodeRight;
  } else if (key == XK_Up ||
	     key == XK_KP_Up) {
    keyCode = xpdfKeyCodeUp;
  } else if (key == XK_Down ||
	     key == XK_KP_Down) {
    keyCode = xpdfKeyCodeDown;
  } else if (key >= XK_F1 && key <= XK_F35) {
    keyCode = xpdfKeyCodeF1 + (key - XK_F1);
  } else {
    return;
  }

  if ((cmds = globalParams->getKeyBinding(keyCode,
					  viewer->getModifiers(modifiers),
					  viewer->getContext(modifiers)))) {
    for (i = 0; i < cmds->getLength(); ++i) {
      viewer->execCmd((GString *)cmds->get(i), event);
    }
    deleteGList(cmds, GString);
  }
}

void XPDFViewer::mouseCbk(void *data, XEvent *event) {
  XPDFViewer *viewer = (XPDFViewer *)data;
  int keyCode;
  GList *cmds;
  int i;

  if (event->type == ButtonPress) {
    if (event->xbutton.button >= 1 && event->xbutton.button <= 32) {
      keyCode = xpdfKeyCodeMousePress1 + event->xbutton.button - 1;
    } else {
      return;
    }
  } else if (event->type == ButtonRelease) {
    if (event->xbutton.button >= 1 && event->xbutton.button <= 32) {
      keyCode = xpdfKeyCodeMouseRelease1 + event->xbutton.button - 1;
    } else {
      return;
    }
  } else {
    return;
  }

  if ((cmds = globalParams->getKeyBinding(keyCode,
					  viewer->getModifiers(
						      event->xkey.state),
					  viewer->getContext(
						      event->xkey.state)))) {
    for (i = 0; i < cmds->getLength(); ++i) {
      viewer->execCmd((GString *)cmds->get(i), event);
    }
    deleteGList(cmds, GString);
  }
}

int XPDFViewer::getModifiers(Guint modifiers) {
  int mods;

  mods = 0;
  if (modifiers & ShiftMask) {
    mods |= xpdfKeyModShift;
  }
  if (modifiers & ControlMask) {
    mods |= xpdfKeyModCtrl;
  }
  if (modifiers & Mod1Mask) {
    mods |= xpdfKeyModAlt;
  }
  return mods;
}

int XPDFViewer::getContext(Guint modifiers) {
  int context;

  context = (core->getFullScreen() ? xpdfKeyContextFullScreen
                                   : xpdfKeyContextWindow) |
            (core->getContinuousMode() ? xpdfKeyContextContinuous
                                       : xpdfKeyContextSinglePage) |
            (core->getLinkAction() ? xpdfKeyContextOverLink
                                   : xpdfKeyContextOffLink) |
            ((modifiers & Mod5Mask) ? xpdfKeyContextScrLockOn
	                            : xpdfKeyContextScrLockOff);
  return context;
}

void XPDFViewer::execCmd(GString *cmd, XEvent *event) {
  GString *name;
  GString *args[cmdMaxArgs];
  char *p0, *p1;
  int nArgs, i;
  int a, b, m, cmp;

  //----- parse the command
  name = NULL;
  nArgs = 0;
  for (i = 0; i < cmdMaxArgs; ++i) {
    args[i] = NULL;
  }
  p0 = cmd->getCString();
  for (p1 = p0; *p1 && isalnum(*p1); ++p1) ;
  if (p1 == p0) {
    goto err1;
  }
  name = new GString(p0, p1 - p0);
  if (*p1 == '(') {
    while (nArgs < cmdMaxArgs) {
      p0 = p1 + 1;
      for (p1 = p0; *p1 && *p1 != ',' && *p1 != ')'; ++p1) ;
      args[nArgs++] = new GString(p0, p1 - p0);
      if (*p1 != ',') {
	break;
      }
    }
    if (*p1 != ')') {
      goto err1;
    }
    ++p1;
  }
  if (*p1) {
    goto err1;
  }
  
  //----- find the command
  a = -1;
  b = nCmds;
  // invariant: cmdTab[a].name < name < cmdTab[b].name
  while (b - a > 1) {
    m = (a + b) / 2;
    cmp = strcmp(cmdTab[m].name, name->getCString());
    if (cmp < 0) {
      a = m;
    } else if (cmp > 0) {
      b = m;
    } else {
      a = b = m;
    }
  }
  if (cmp != 0) {
    goto err1;
  }

  //----- execute the command
  if (nArgs != cmdTab[a].nArgs ||
      (cmdTab[a].requiresEvent && !event)) {
    goto err1;
  }
  if (cmdTab[a].requiresDoc && !core->getDoc()) {
    // don't issue an error message for this -- it happens, e.g., when
    // clicking in a window with no open PDF file
    goto err2;
  }
  (this->*cmdTab[a].func)(args, nArgs, event);

  //----- clean up
  delete name;
  for (i = 0; i < nArgs; ++i) {
    if (args[i]) {
      delete args[i];
    }
  }
  return;

 err1:
  error(errConfig, -1, "Invalid command syntax: '{0:t}'", cmd);
 err2:
  if (name) {
    delete name;
  }
  for (i = 0; i < nArgs; ++i) {
    if (args[i]) {
      delete args[i];
    }
  }
}

//------------------------------------------------------------------------
// command functions
//------------------------------------------------------------------------

static int mouseX(XEvent *event) {
  switch (event->type) {
  case ButtonPress:
  case ButtonRelease:
    return event->xbutton.x;
  case KeyPress:
    return event->xkey.x;
  }
  return 0;
}

static int mouseY(XEvent *event) {
  switch (event->type) {
  case ButtonPress:
  case ButtonRelease:
    return event->xbutton.y;
  case KeyPress:
    return event->xkey.y;
  }
  return 0;
}

void XPDFViewer::cmdAbout(GString *args[], int nArgs,
			  XEvent *event) {
  XtManageChild(aboutDialog);
}

void XPDFViewer::cmdCloseOutline(GString *args[], int nArgs,
				 XEvent *event) {
#ifndef DISABLE_OUTLINE
  Dimension w;

  if (outlineScroll == None) {
    return;
  }
  XtVaGetValues(outlineScroll, XmNwidth, &w, NULL);
  if (w > 1) {
    outlinePaneWidth = w;
    // this ugly kludge is apparently the only way to resize the panes
    // within an XmPanedWindow
    XtVaSetValues(outlineScroll, XmNpaneMinimum, 1,
		  XmNpaneMaximum, 1, NULL);
    XtVaSetValues(outlineScroll, XmNpaneMinimum, 1,
		  XmNpaneMaximum, 10000, NULL);
  }
#endif
}

void XPDFViewer::cmdCloseWindow(GString *args[], int nArgs,
				XEvent *event) {
  app->close(this, gFalse);
}

void XPDFViewer::cmdContinuousMode(GString *args[], int nArgs,
				   XEvent *event) {
  Widget btn;

  if (core->getContinuousMode()) {
    return;
  }
  core->setContinuousMode(gTrue);

  btn = XtNameToWidget(popupMenu, "continuousMode");
  XtVaSetValues(btn, XmNset, XmSET, NULL);
}

void XPDFViewer::cmdEndPan(GString *args[], int nArgs,
			   XEvent *event) {
  core->endPan(mouseX(event), mouseY(event));
}

void XPDFViewer::cmdEndSelection(GString *args[], int nArgs,
				 XEvent *event) {
  core->endSelection(mouseX(event), mouseY(event));
}

void XPDFViewer::cmdFind(GString *args[], int nArgs,
			 XEvent *event) {
  mapFindDialog();
}

void XPDFViewer::cmdFindNext(GString *args[], int nArgs,
			     XEvent *event) {
  doFind(gTrue);
}

void XPDFViewer::cmdFocusToDocWin(GString *args[], int nArgs,
				  XEvent *event) {
  core->takeFocus();
}

void XPDFViewer::cmdFocusToPageNum(GString *args[], int nArgs,
				   XEvent *event) {
  if (toolBar != None) {
    XmTextFieldSetSelection(pageNumText, 0,
			    strlen(XmTextFieldGetString(pageNumText)),
			    XtLastTimestampProcessed(display));
    XmProcessTraversal(pageNumText, XmTRAVERSE_CURRENT);
  }
}

void XPDFViewer::cmdFollowLink(GString *args[], int nArgs,
			       XEvent *event) {
  doLink(mouseX(event), mouseY(event), gFalse, gFalse);
}

void XPDFViewer::cmdFollowLinkInNewWin(GString *args[], int nArgs,
				       XEvent *event) {
  doLink(mouseX(event), mouseY(event), gFalse, gTrue);
}

void XPDFViewer::cmdFollowLinkInNewWinNoSel(GString *args[], int nArgs,
					    XEvent *event) {
  doLink(mouseX(event), mouseY(event), gTrue, gTrue);
}

void XPDFViewer::cmdFollowLinkNoSel(GString *args[], int nArgs,
				    XEvent *event) {
  doLink(mouseX(event), mouseY(event), gTrue, gFalse);
}

void XPDFViewer::cmdFullScreenMode(GString *args[], int nArgs,
				   XEvent *event) {
  PDFDoc *doc;
  XPDFViewer *viewer;
  int pg;
  Widget btn;

  if (core->getFullScreen()) {
    return;
  }
  pg = core->getPageNum();
  XtPopdown(win);
  doc = core->takeDoc(gFalse);
  viewer = app->reopen(this, doc, pg, gTrue);

  btn = XtNameToWidget(viewer->popupMenu, "fullScreen");
  XtVaSetValues(btn, XmNset, XmSET, NULL);
}

void XPDFViewer::cmdGoBackward(GString *args[], int nArgs,
			       XEvent *event) {
  core->goBackward();
}

void XPDFViewer::cmdGoForward(GString *args[], int nArgs,
			      XEvent *event) {
  core->goForward();
}

void XPDFViewer::cmdGotoDest(GString *args[], int nArgs,
			     XEvent *event) {
  int pg;
  LinkDest *dest;

  getPageAndDest(1, args[0], &pg, &dest);
  if (dest) {
    displayDest(dest, core->getZoom(), core->getRotate(), gTrue);
    delete dest;
  }
}

void XPDFViewer::cmdGotoLastPage(GString *args[], int nArgs,
				 XEvent *event) {
  displayPage(core->getDoc()->getNumPages(),
	      core->getZoom(), core->getRotate(),
	      gTrue, gTrue);
}

void XPDFViewer::cmdGotoLastPageNoScroll(GString *args[], int nArgs,
					 XEvent *event) {
  displayPage(core->getDoc()->getNumPages(),
	      core->getZoom(), core->getRotate(),
	      gFalse, gTrue);
}

void XPDFViewer::cmdGotoPage(GString *args[], int nArgs,
			     XEvent *event) {
  int pg;

  pg = atoi(args[0]->getCString());
  if (pg < 1 || pg > core->getDoc()->getNumPages()) {
    return;
  }
  displayPage(pg, core->getZoom(), core->getRotate(), gTrue, gTrue);
}

void XPDFViewer::cmdGotoPageNoScroll(GString *args[], int nArgs,
				     XEvent *event) {
  int pg;

  pg = atoi(args[0]->getCString());
  if (pg < 1 || pg > core->getDoc()->getNumPages()) {
    return;
  }
  displayPage(pg, core->getZoom(), core->getRotate(), gFalse, gTrue);
}

void XPDFViewer::cmdNextPage(GString *args[], int nArgs,
			     XEvent *event) {
  core->gotoNextPage(1, gTrue);
}

void XPDFViewer::cmdNextPageNoScroll(GString *args[], int nArgs,
				     XEvent *event) {
  core->gotoNextPage(1, gFalse);
}

void XPDFViewer::cmdOpen(GString *args[], int nArgs,
			 XEvent *event) {
  mapOpenDialog(gFalse);
}

void XPDFViewer::cmdOpenFile(GString *args[], int nArgs,
			     XEvent *event) {
  open(args[0], 1, NULL);
}

void XPDFViewer::cmdOpenFileAtDest(GString *args[], int nArgs,
				   XEvent *event) {
  open(args[0], 1, args[1]);
}

void XPDFViewer::cmdOpenFileAtDestInNewWin(GString *args[], int nArgs,
					   XEvent *event) {
  app->openAtDest(args[0], args[1]);
}

void XPDFViewer::cmdOpenFileAtPage(GString *args[], int nArgs,
				   XEvent *event) {
  open(args[0], atoi(args[1]->getCString()), NULL);
}

void XPDFViewer::cmdOpenFileAtPageInNewWin(GString *args[], int nArgs,
					   XEvent *event) {
  app->open(args[0], atoi(args[1]->getCString()));
}

void XPDFViewer::cmdOpenFileInNewWin(GString *args[], int nArgs,
				     XEvent *event) {
  app->open(args[0]);
}

void XPDFViewer::cmdOpenInNewWin(GString *args[], int nArgs,
				 XEvent *event) {
  mapOpenDialog(gTrue);
}

void XPDFViewer::cmdOpenOutline(GString *args[], int nArgs,
				XEvent *event) {
#ifndef DISABLE_OUTLINE
  Dimension w;

  if (outlineScroll == None) {
    return;
  }
  XtVaGetValues(outlineScroll, XmNwidth, &w, NULL);
  if (w == 1) {
    // this ugly kludge is apparently the only way to resize the panes
    // within an XmPanedWindow
    XtVaSetValues(outlineScroll, XmNpaneMinimum, outlinePaneWidth,
		  XmNpaneMaximum, outlinePaneWidth, NULL);
    XtVaSetValues(outlineScroll, XmNpaneMinimum, 1,
		  XmNpaneMaximum, 10000, NULL);
  }
#endif
}

void XPDFViewer::cmdPageDown(GString *args[], int nArgs,
			     XEvent *event) {
  core->scrollPageDown();
}

void XPDFViewer::cmdPageUp(GString *args[], int nArgs,
			   XEvent *event) {
  core->scrollPageUp();
}

void XPDFViewer::cmdPostPopupMenu(GString *args[], int nArgs,
				  XEvent *event) {
  XmMenuPosition(popupMenu, event->type == ButtonPress ? &event->xbutton
		                                       : (XButtonEvent *)NULL);
  XtManageChild(popupMenu);

  // this is magic (taken from DDD) - weird things happen if this
  // call isn't made (this is done in two different places, in hopes
  // of squashing this stupid bug)
  XtUngrabButton(core->getDrawAreaWidget(), AnyButton, AnyModifier);
}

void XPDFViewer::cmdPrevPage(GString *args[], int nArgs,
			     XEvent *event) {
  core->gotoPrevPage(1, gTrue, gFalse);
}

void XPDFViewer::cmdPrevPageNoScroll(GString *args[], int nArgs,
				     XEvent *event) {
  core->gotoPrevPage(1, gFalse, gFalse);
}

void XPDFViewer::cmdPrint(GString *args[], int nArgs,
			  XEvent *event) {
  XtManageChild(printDialog);
}

void XPDFViewer::cmdQuit(GString *args[], int nArgs,
			 XEvent *event) {
  app->quit();
}

void XPDFViewer::cmdRaise(GString *args[], int nArgs,
			  XEvent *event) {
  XMapRaised(display, XtWindow(win));
  XFlush(display);
}

void XPDFViewer::cmdRedraw(GString *args[], int nArgs,
			   XEvent *event) {
  displayPage(core->getPageNum(), core->getZoom(), core->getRotate(),
	      gFalse, gFalse);
}

void XPDFViewer::cmdReload(GString *args[], int nArgs,
			   XEvent *event) {
  reloadFile();
}

void XPDFViewer::cmdRotateCCW(GString *args[], int nArgs,
			     XEvent *event) {
  int r;

  r = core->getRotate();
  r = (r == 0) ? 270 : r - 90;
  displayPage(core->getPageNum(), core->getZoom(), r, gTrue, gFalse);
}

void XPDFViewer::cmdRotateCW(GString *args[], int nArgs,
			     XEvent *event) {
  int r;

  r = core->getRotate();
  r = (r == 270) ? 0 : r + 90;
  displayPage(core->getPageNum(), core->getZoom(), r, gTrue, gFalse);
}

void XPDFViewer::cmdRun(GString *args[], int nArgs,
			XEvent *event) {
  GString *fmt, *cmd, *s;
  LinkAction *action;
  double selLRX, selLRY, selURX, selURY, mouseX, mouseY;
  int selPage, mousePage;
  GBool gotSel, gotMouse;
  char buf[64];
  char *p;
  char c0, c1;
  int i;

  cmd = new GString();
  fmt = args[0];
  i = 0;
  gotSel = gotMouse = gFalse;
  while (i < fmt->getLength()) {
    c0 = fmt->getChar(i);
    if (c0 == '%' && i+1 < fmt->getLength()) {
      c1 = fmt->getChar(i+1);
      switch (c1) {
      case 'f':
	if (core->getDoc() && (s = core->getDoc()->getFileName())) {
	  cmd->append(s);
	}
	break;
      case 'b':
	if (core->getDoc() && (s = core->getDoc()->getFileName())) {
	  if ((p = strrchr(s->getCString(), '.'))) {
	    cmd->append(s->getCString(), p - s->getCString());
	  } else {
	    cmd->append(s);
	  }
	}
	break;
      case 'u':
	if ((action = core->getLinkAction()) &&
	    action->getKind() == actionURI) {
	  s = core->mungeURL(((LinkURI *)action)->getURI());
	  cmd->append(s);
	  delete s;
	}
	break;
      case 'p':
	if (core->getDoc()) {
	  sprintf(buf, "%d", core->getPageNum());
	  cmd->append(buf);
	}
	break;
      case 'x':
      case 'y':
      case 'X':
      case 'Y':
	if (!gotSel) {
	  if (!core->getSelection(&selPage, &selURX, &selURY,
				  &selLRX, &selLRY)) {
	    selPage = 0;
	    selURX = selURY = selLRX = selLRY = 0;
	  }
	  gotSel = gTrue;
	}
	sprintf(buf, "%g",
		(c1 == 'x') ? selURX :
		(c1 == 'y') ? selURY :
		(c1 == 'X') ? selLRX : selLRY);
	cmd->append(buf);
	break;
      case 'i':
      case 'j':
      case 'k':
	if (!gotMouse) {
	  if (event->type == ButtonPress || event->type == ButtonRelease) {
	    core->cvtWindowToUser(event->xbutton.x, event->xbutton.y,
				  &mousePage, &mouseX, &mouseY);
	  } else if (event->type == KeyPress) {
	    core->cvtWindowToUser(event->xkey.x, event->xkey.y,
				  &mousePage, &mouseX, &mouseY);
	  } else {
	    mousePage = 0;
	    mouseX = mouseY = 0;
	  }
	  gotMouse = gTrue;
	}
	if (c1 == 'i') {
	  sprintf(buf, "%d", mousePage);
	} else {
	  sprintf(buf, "%g", (c1 == 'j') ? mouseX : mouseY);
	}
	cmd->append(buf);
	break;
      default:
	cmd->append(c1);
	break;
      }
      i += 2;
    } else {
      cmd->append(c0);
      ++i;
    }
  }
#ifdef VMS
  cmd->insert(0, "spawn/nowait ");
#elif defined(__EMX__)
  cmd->insert(0, "start /min /n ");
#else
  cmd->append(" &");
#endif
  system(cmd->getCString());
  delete cmd;
}

void XPDFViewer::cmdScrollDown(GString *args[], int nArgs,
			       XEvent *event) {
  core->scrollDown(atoi(args[0]->getCString()));
}

void XPDFViewer::cmdScrollDownNextPage(GString *args[], int nArgs,
				       XEvent *event) {
  core->scrollDownNextPage(atoi(args[0]->getCString()));
}

void XPDFViewer::cmdScrollLeft(GString *args[], int nArgs,
			       XEvent *event) {
  core->scrollLeft(atoi(args[0]->getCString()));
}

void XPDFViewer::cmdScrollOutlineDown(GString *args[], int nArgs,
				      XEvent *event) {
#ifndef DISABLE_OUTLINE
  Widget sb;
  int val, inc, pageInc, m, slider;

  if (outlineScroll == None) {
    return;
  }
  if ((sb = XtNameToWidget(outlineScroll, "VertScrollBar"))) {
    XtVaGetValues(sb, XmNvalue, &val, XmNincrement, &inc,
		  XmNpageIncrement, &pageInc, XmNmaximum, &m,
		  XmNsliderSize, &slider, NULL);
    if ((val += inc * atoi(args[0]->getCString())) > m - slider) {
      val = m - slider;
    }
    XmScrollBarSetValues(sb, val, slider, inc, pageInc, True);
  }
#endif
}

void XPDFViewer::cmdScrollOutlineUp(GString *args[], int nArgs,
				    XEvent *event) {
#ifndef DISABLE_OUTLINE
  Widget sb;
  int val, inc, pageInc, m, slider;

  if (outlineScroll == None) {
    return;
  }
  if ((sb = XtNameToWidget(outlineScroll, "VertScrollBar"))) {
    XtVaGetValues(sb, XmNvalue, &val, XmNincrement, &inc,
		  XmNpageIncrement, &pageInc, XmNminimum, &m,
		  XmNsliderSize, &slider, NULL);
    if ((val -= inc * atoi(args[0]->getCString())) < m) {
      val = m;
    }
    XmScrollBarSetValues(sb, val, slider, inc, pageInc, True);
  }
#endif
}

void XPDFViewer::cmdScrollRight(GString *args[], int nArgs,
				XEvent *event) {
  core->scrollRight(atoi(args[0]->getCString()));
}

void XPDFViewer::cmdScrollToBottomEdge(GString *args[], int nArgs,
				       XEvent *event) {
  core->scrollToBottomEdge();
}

void XPDFViewer::cmdScrollToBottomRight(GString *args[], int nArgs,
					XEvent *event) {
  core->scrollToBottomRight();
}

void XPDFViewer::cmdScrollToLeftEdge(GString *args[], int nArgs,
				     XEvent *event) {
  core->scrollToLeftEdge();
}

void XPDFViewer::cmdScrollToRightEdge(GString *args[], int nArgs,
				      XEvent *event) {
  core->scrollToRightEdge();
}

void XPDFViewer::cmdScrollToTopEdge(GString *args[], int nArgs,
				    XEvent *event) {
  core->scrollToTopEdge();
}

void XPDFViewer::cmdScrollToTopLeft(GString *args[], int nArgs,
				    XEvent *event) {
  core->scrollToTopLeft();
}

void XPDFViewer::cmdScrollUp(GString *args[], int nArgs,
			     XEvent *event) {
  core->scrollUp(atoi(args[0]->getCString()));
}

void XPDFViewer::cmdScrollUpPrevPage(GString *args[], int nArgs,
				     XEvent *event) {
  core->scrollUpPrevPage(atoi(args[0]->getCString()));
}

void XPDFViewer::cmdSetSelection(GString *args[], int nArgs, XEvent *event) {
  int pg, ulx, uly, lrx, lry;

  pg = atoi(args[0]->getCString());
  core->cvtUserToDev(core->getPageNum(),
		     atof(args[1]->getCString()), atof(args[2]->getCString()),
		     &ulx, &uly);
  core->cvtUserToDev(core->getPageNum(),
		     atof(args[3]->getCString()), atof(args[4]->getCString()),
		     &lrx, &lry);
  core->setSelection(pg, ulx, uly, lrx, lry);
}

void XPDFViewer::cmdSinglePageMode(GString *args[], int nArgs,
				   XEvent *event) {
  Widget btn;

  if (!core->getContinuousMode()) {
    return;
  }
  core->setContinuousMode(gFalse);

  btn = XtNameToWidget(popupMenu, "continuousMode");
  XtVaSetValues(btn, XmNset, XmUNSET, NULL);
}

void XPDFViewer::cmdStartPan(GString *args[], int nArgs,
			     XEvent *event) {
  core->startPan(mouseX(event), mouseY(event));
}

void XPDFViewer::cmdStartSelection(GString *args[], int nArgs,
				   XEvent *event) {
  core->startSelection(mouseX(event), mouseY(event));
}

void XPDFViewer::cmdToggleContinuousMode(GString *args[], int nArgs,
					 XEvent *event) {
  if (core->getContinuousMode()) {
    cmdSinglePageMode(NULL, 0, event);
  } else {
    cmdContinuousMode(NULL, 0, event);
  }
}

void XPDFViewer::cmdToggleFullScreenMode(GString *args[], int nArgs,
					 XEvent *event) {
  if (core->getFullScreen()) {
    cmdWindowMode(NULL, 0, event);
  } else {
    cmdFullScreenMode(NULL, 0, event);
  }
}

void XPDFViewer::cmdToggleOutline(GString *args[], int nArgs,
				  XEvent *event) {
#ifndef DISABLE_OUTLINE
  Dimension w;

  if (outlineScroll == None) {
    return;
  }
  XtVaGetValues(outlineScroll, XmNwidth, &w, NULL);
  if (w > 1) {
    cmdCloseOutline(NULL, 0, event);
  } else {
    cmdOpenOutline(NULL, 0, event);
  }
#endif
}

void XPDFViewer::cmdWindowMode(GString *args[], int nArgs,
			       XEvent *event) {
  PDFDoc *doc;
  XPDFViewer *viewer;
  int pg;
  Widget btn;

  if (!core->getFullScreen()) {
    return;
  }
  pg = core->getPageNum();
  XtPopdown(win);
  doc = core->takeDoc(gFalse);
  viewer = app->reopen(this, doc, pg, gFalse);

  btn = XtNameToWidget(viewer->popupMenu, "fullScreen");
  XtVaSetValues(btn, XmNset, XmUNSET, NULL);
}

void XPDFViewer::cmdZoomFitPage(GString *args[], int nArgs,
				XEvent *event) {
  if (core->getZoom() != zoomPage) {
    setZoomIdx(zoomPageIdx);
    displayPage(core->getPageNum(), zoomPage,
		core->getRotate(), gTrue, gFalse);
  }
}

void XPDFViewer::cmdZoomFitWidth(GString *args[], int nArgs,
				 XEvent *event) {
  if (core->getZoom() != zoomWidth) {
    setZoomIdx(zoomWidthIdx);
    displayPage(core->getPageNum(), zoomWidth,
		core->getRotate(), gTrue, gFalse);
  }
}

void XPDFViewer::cmdZoomIn(GString *args[], int nArgs,
			   XEvent *event) {
  int z;

  z = getZoomIdx();
  if (z <= minZoomIdx && z > maxZoomIdx) {
    --z;
    setZoomIdx(z);
    displayPage(core->getPageNum(), zoomMenuInfo[z].zoom,
		core->getRotate(), gTrue, gFalse);
  }
}

void XPDFViewer::cmdZoomOut(GString *args[], int nArgs,
			    XEvent *event) {
  int z;

  z = getZoomIdx();
  if (z < minZoomIdx && z >= maxZoomIdx) {
    ++z;
    setZoomIdx(z);
    displayPage(core->getPageNum(), zoomMenuInfo[z].zoom,
		core->getRotate(), gTrue, gFalse);
  }
}

void XPDFViewer::cmdZoomPercent(GString *args[], int nArgs,
				XEvent *event) {
  double z;

  z = atof(args[0]->getCString());
  setZoomVal(z);
  displayPage(core->getPageNum(), z, core->getRotate(), gTrue, gFalse);
}

void XPDFViewer::cmdZoomToSelection(GString *args[], int nArgs,
				    XEvent *event) {
  int pg;
  double ulx, uly, lrx, lry;

  if (core->getSelection(&pg, &ulx, &uly, &lrx, &lry)) {
    core->zoomToRect(pg, ulx, uly, lrx, lry);
  }
}

//------------------------------------------------------------------------
// GUI code: main window
//------------------------------------------------------------------------

void XPDFViewer::initWindow(GBool fullScreen) {
  Colormap colormap;
  XColor xcol;
  Atom state, val;
  Arg args[20];
  int n;
  char *title;

  display = XtDisplay(app->getAppShell());
  screenNum = XScreenNumberOfScreen(XtScreen(app->getAppShell()));

  toolBar = None;
#ifndef DISABLE_OUTLINE
  outlineScroll = None;
#endif

  // private colormap
  if (app->getInstallCmap()) {
    XtVaGetValues(app->getAppShell(), XmNcolormap, &colormap, NULL);
    // ensure that BlackPixel and WhitePixel are reserved in the
    // new colormap
    xcol.red = xcol.green = xcol.blue = 0;
    XAllocColor(display, colormap, &xcol);
    xcol.red = xcol.green = xcol.blue = 65535;
    XAllocColor(display, colormap, &xcol);
    colormap = XCopyColormapAndFree(display, colormap);
  }

  // top-level window
  n = 0;
  title = app->getTitle() ? app->getTitle()->getCString()
                          : (char *)xpdfAppName;
  XtSetArg(args[n], XmNtitle, title); ++n;
  XtSetArg(args[n], XmNiconName, title); ++n;
  XtSetArg(args[n], XmNminWidth, 100); ++n;
  XtSetArg(args[n], XmNminHeight, 100); ++n;
  XtSetArg(args[n], XmNbaseWidth, 0); ++n;
  XtSetArg(args[n], XmNbaseHeight, 0); ++n;
  XtSetArg(args[n], XmNdeleteResponse, XmDO_NOTHING); ++n;
  win = XtCreatePopupShell("win", topLevelShellWidgetClass,
			   app->getAppShell(), args, n);
  if (app->getInstallCmap()) {
    XtVaSetValues(win, XmNcolormap, colormap, NULL);
  }
  XmAddWMProtocolCallback(win, XInternAtom(display, "WM_DELETE_WINDOW", False),
			  &closeMsgCbk, this);

  // create the full-screen window
  if (fullScreen) {
    initCore(win, gTrue);

  // create the normal (non-full-screen) window
  } else {
    if (app->getGeometry()) {
      n = 0;
      XtSetArg(args[n], XmNgeometry, app->getGeometry()->getCString()); ++n;
      XtSetValues(win, args, n);
    }

    n = 0;
    form = XmCreateForm(win, "form", args, n);
    XtManageChild(form);

#ifdef DISABLE_OUTLINE
    initToolbar(form);
    n = 0;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
    XtSetValues(toolBar, args, n);

    initCore(form, gFalse);
    n = 0;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n;
    XtSetArg(args[n], XmNbottomWidget, toolBar); ++n;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
    XtSetValues(core->getWidget(), args, n);
#else
    initToolbar(form);
    n = 0;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
    XtSetValues(toolBar, args, n);

    initPanedWin(form);
    n = 0;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n;
    XtSetArg(args[n], XmNbottomWidget, toolBar); ++n;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
    XtSetValues(panedWin, args, n);

    initCore(panedWin, fullScreen);
    n = 0;
    XtSetArg(args[n], XmNpositionIndex, 1); ++n;
    XtSetArg(args[n], XmNallowResize, True); ++n;
    XtSetArg(args[n], XmNpaneMinimum, 1); ++n;
    XtSetArg(args[n], XmNpaneMaximum, 10000); ++n;
    XtSetValues(core->getWidget(), args, n);
#endif
  }

  // set the zoom menu to match the initial zoom setting
  setZoomVal(core->getZoom());

  // set traversal order
  XtVaSetValues(core->getDrawAreaWidget(),
		XmNnavigationType, XmEXCLUSIVE_TAB_GROUP, NULL);
  if (toolBar != None) {
    XtVaSetValues(backBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(prevTenPageBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(prevPageBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(nextPageBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(nextTenPageBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(forwardBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(pageNumText, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(zoomWidget, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(findBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(printBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(aboutBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
    XtVaSetValues(quitBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
		  NULL);
  }

  initPopupMenu();

  if (fullScreen) {
    // Set both the old-style Motif decorations hint and the new-style
    // _NET_WM_STATE property.  This is redundant, but might be useful
    // for older window managers.  We also set the geometry to +0+0 to
    // avoid interactive placement.  (Note: we need to realize the
    // shell, so it has a Window on which to set the _NET_WM_STATE
    // property, but we don't want to map it until later, so we set
    // mappedWhenManaged to false.)
    n = 0;
    XtSetArg(args[n], XmNmappedWhenManaged, False); ++n;
    XtSetArg(args[n], XmNmwmDecorations, 0); ++n;
    XtSetArg(args[n], XmNgeometry, "+0+0"); ++n;
    XtSetValues(win, args, n);
    XtRealizeWidget(win);
    state = XInternAtom(display, "_NET_WM_STATE", False);
    val = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", False);
    XChangeProperty(display, XtWindow(win), state, XA_ATOM, 32,
		    PropModeReplace, (Guchar *)&val, 1);
  }
}

void XPDFViewer::initToolbar(Widget parent) {
  Widget label, lastBtn;
#ifndef USE_COMBO_BOX
  Widget btn;
#endif
  Arg args[20];
  int n;
  XmString s, emptyString;
  int i;

  // toolbar
  n = 0;
  toolBar = XmCreateForm(parent, "toolBar", args, n);
  XtManageChild(toolBar);

  // create an empty string -- this is used for buttons that will get
  // pixmaps later
  emptyString = XmStringCreateLocalized("");

  // page movement buttons
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 6); ++n;
  XtSetArg(args[n], XmNsensitive, False); ++n;
  XtSetArg(args[n], XmNlabelString, emptyString); ++n;
  backBtn = XmCreatePushButton(toolBar, "back", args, n);
  addToolTip(backBtn, "Back");
  XtManageChild(backBtn);
  XtAddCallback(backBtn, XmNactivateCallback,
		&backCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, backBtn); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 6); ++n;
  XtSetArg(args[n], XmNsensitive, False); ++n;
  XtSetArg(args[n], XmNlabelString, emptyString); ++n;
  prevTenPageBtn = XmCreatePushButton(toolBar, "prevTenPage", args, n);
  addToolTip(prevTenPageBtn, "-10 pages");
  XtManageChild(prevTenPageBtn);
  XtAddCallback(prevTenPageBtn, XmNactivateCallback,
		&prevTenPageCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, prevTenPageBtn); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 6); ++n;
  XtSetArg(args[n], XmNsensitive, False); ++n;
  XtSetArg(args[n], XmNlabelString, emptyString); ++n;
  prevPageBtn = XmCreatePushButton(toolBar, "prevPage", args, n);
  addToolTip(prevPageBtn, "Previous page");
  XtManageChild(prevPageBtn);
  XtAddCallback(prevPageBtn, XmNactivateCallback,
		&prevPageCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, prevPageBtn); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 6); ++n;
  XtSetArg(args[n], XmNsensitive, False); ++n;
  XtSetArg(args[n], XmNlabelString, emptyString); ++n;
  nextPageBtn = XmCreatePushButton(toolBar, "nextPage", args, n);
  addToolTip(nextPageBtn, "Next page");
  XtManageChild(nextPageBtn);
  XtAddCallback(nextPageBtn, XmNactivateCallback,
		&nextPageCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, nextPageBtn); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 6); ++n;
  XtSetArg(args[n], XmNsensitive, False); ++n;
  XtSetArg(args[n], XmNlabelString, emptyString); ++n;
  nextTenPageBtn = XmCreatePushButton(toolBar, "nextTenPage", args, n);
  addToolTip(nextTenPageBtn, "+10 pages");
  XtManageChild(nextTenPageBtn);
  XtAddCallback(nextTenPageBtn, XmNactivateCallback,
		&nextTenPageCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, nextTenPageBtn); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 6); ++n;
  XtSetArg(args[n], XmNsensitive, False); ++n;
  XtSetArg(args[n], XmNlabelString, emptyString); ++n;
  forwardBtn = XmCreatePushButton(toolBar, "forward", args, n);
  addToolTip(forwardBtn, "Forward");
  XtManageChild(forwardBtn);
  XtAddCallback(forwardBtn, XmNactivateCallback,
		&forwardCbk, (XtPointer)this);

  // page number display
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, forwardBtn); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  s = XmStringCreateLocalized("Page ");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  label = XmCreateLabel(toolBar, "pageLabel", args, n);
  XmStringFree(s);
  XtManageChild(label);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, label); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 3); ++n;
  XtSetArg(args[n], XmNmarginHeight, 3); ++n;
  XtSetArg(args[n], XmNcolumns, 5); ++n;
  pageNumText = XmCreateTextField(toolBar, "pageNum", args, n);
  XtManageChild(pageNumText);
  XtAddCallback(pageNumText, XmNactivateCallback,
		&pageNumCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, pageNumText); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  s = XmStringCreateLocalized(" of 00000");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); ++n;
  XtSetArg(args[n], XmNrecomputeSize, False); ++n;
  pageCountLabel = XmCreateLabel(toolBar, "pageCountLabel", args, n);
  XmStringFree(s);
  XtManageChild(pageCountLabel);
  s = XmStringCreateLocalized(" of 0");
  XtVaSetValues(pageCountLabel, XmNlabelString, s, NULL);
  XmStringFree(s);

  // zoom menu
#if USE_COMBO_BOX
  XmString st[nZoomMenuItems];
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, pageCountLabel); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 0); ++n;
  XtSetArg(args[n], XmNmarginHeight, 0); ++n;
  XtSetArg(args[n], XmNcomboBoxType, XmDROP_DOWN_COMBO_BOX); ++n;
  XtSetArg(args[n], XmNpositionMode, XmONE_BASED); ++n;
  XtSetArg(args[n], XmNcolumns, 7); ++n;
  for (i = 0; i < nZoomMenuItems; ++i) {
    st[i] = XmStringCreateLocalized((char *)zoomMenuInfo[i].label);
  }
  XtSetArg(args[n], XmNitems, st); ++n;
  XtSetArg(args[n], XmNitemCount, nZoomMenuItems); ++n;
  zoomComboBox = XmCreateComboBox(toolBar, "zoomComboBox", args, n);
  for (i = 0; i < nZoomMenuItems; ++i) {
    XmStringFree(st[i]);
  }
  addToolTip(zoomComboBox, "Zoom");
  XtAddCallback(zoomComboBox, XmNselectionCallback,
		&zoomComboBoxCbk, (XtPointer)this);
  XtManageChild(zoomComboBox);
  zoomWidget = zoomComboBox;
#else
  Widget menuPane;
  char buf[16];
  n = 0;
  menuPane = XmCreatePulldownMenu(toolBar, "zoomMenuPane", args, n);
  for (i = 0; i < nZoomMenuItems; ++i) {
    n = 0;
    s = XmStringCreateLocalized((char *)zoomMenuInfo[i].label);
    XtSetArg(args[n], XmNlabelString, s); ++n;
    XtSetArg(args[n], XmNuserData, (XtPointer)i); ++n;
    sprintf(buf, "zoom%d", i);
    btn = XmCreatePushButton(menuPane, buf, args, n);
    XmStringFree(s);
    XtManageChild(btn);
    XtAddCallback(btn, XmNactivateCallback,
		  &zoomMenuCbk, (XtPointer)this);
    zoomMenuBtns[i] = btn;
  }
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, pageCountLabel); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 0); ++n;
  XtSetArg(args[n], XmNmarginHeight, 0); ++n;
  XtSetArg(args[n], XmNsubMenuId, menuPane); ++n;
  zoomMenu = XmCreateOptionMenu(toolBar, "zoomMenu", args, n);
  addToolTip(zoomMenu, "Zoom");
  XtManageChild(zoomMenu);
  zoomWidget = zoomMenu;
#endif

  // find/print/about buttons
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, zoomWidget); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 6); ++n;
  XtSetArg(args[n], XmNlabelString, emptyString); ++n;
  findBtn = XmCreatePushButton(toolBar, "find", args, n);
  addToolTip(findBtn, "Find");
  XtManageChild(findBtn);
  XtAddCallback(findBtn, XmNactivateCallback,
		&findCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, findBtn); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 6); ++n;
  XtSetArg(args[n], XmNlabelString, emptyString); ++n;
  printBtn = XmCreatePushButton(toolBar, "print", args, n);
  addToolTip(printBtn, "Print");
  XtManageChild(printBtn);
  XtAddCallback(printBtn, XmNactivateCallback,
		&printCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, printBtn); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 6); ++n;
  XtSetArg(args[n], XmNlabelString, emptyString); ++n;
  aboutBtn = XmCreatePushButton(toolBar, "about", args, n);
  addToolTip(aboutBtn, "About / help");
  XtManageChild(aboutBtn);
  XtAddCallback(aboutBtn, XmNactivateCallback,
		&aboutCbk, (XtPointer)this);
  lastBtn = aboutBtn;

  // quit button
  n = 0;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNmarginWidth, 6); ++n;
  s = XmStringCreateLocalized("Quit");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  quitBtn = XmCreatePushButton(toolBar, "quit", args, n);
  XmStringFree(s);
  XtManageChild(quitBtn);
  XtAddCallback(quitBtn, XmNactivateCallback,
		&quitCbk, (XtPointer)this);

  // link label
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, lastBtn); ++n;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNrightWidget, quitBtn); ++n;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  s = XmStringCreateLocalized("");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  XtSetArg(args[n], XmNrecomputeSize, True); ++n;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); ++n;
  linkLabel = XmCreateLabel(toolBar, "linkLabel", args, n);
  XmStringFree(s);
  XtManageChild(linkLabel);

  XmStringFree(emptyString);
}

#ifndef DISABLE_OUTLINE
void XPDFViewer::initPanedWin(Widget parent) {
  Widget clipWin;
  Arg args[20];
  int n;

  // paned window
  n = 0;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); ++n;
#if defined(__sgi) && (XmVERSION <= 1)
  panedWin = SgCreateHorzPanedWindow(parent, "panedWin", args, n);
#else
  panedWin = XmCreatePanedWindow(parent, "panedWin", args, n);
#endif
  XtManageChild(panedWin);

  // scrolled window for outline container
  n = 0;
  XtSetArg(args[n], XmNpositionIndex, 0); ++n;
  XtSetArg(args[n], XmNallowResize, True); ++n;
  XtSetArg(args[n], XmNpaneMinimum, 1); ++n;
  XtSetArg(args[n], XmNpaneMaximum, 10000); ++n;
#if !(defined(__sgi) && (XmVERSION <= 1))
  XtSetArg(args[n], XmNwidth, 1); ++n;
#endif
  XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); ++n;
  outlineScroll = XmCreateScrolledWindow(panedWin, "outlineScroll", args, n);
  XtManageChild(outlineScroll);
  XtVaGetValues(outlineScroll, XmNclipWindow, &clipWin, NULL);
  XtVaSetValues(clipWin, XmNbackground, app->getPaperPixel(), NULL);

  // outline tree
  n = 0;
  XtSetArg(args[n], XmNbackground, app->getPaperPixel()); ++n;
  outlineTree = XPDFCreateTree(outlineScroll, "outlineTree", args, n);
  XtManageChild(outlineTree);
  XtAddCallback(outlineTree, XPDFNselectionCallback, &outlineSelectCbk,
		(XtPointer)this);
}
#endif

void XPDFViewer::initCore(Widget parent, GBool fullScreen) {
  core = new XPDFCore(win, parent,
		      app->getPaperRGB(), app->getPaperPixel(),
		      app->getMattePixel(fullScreen),
		      fullScreen, app->getReverseVideo(),
		      app->getInstallCmap(), app->getRGBCubeSize());
  core->setUpdateCbk(&updateCbk, this);
  core->setActionCbk(&actionCbk, this);
  core->setKeyPressCbk(&keyPressCbk, this);
  core->setMouseCbk(&mouseCbk, this);
}

void XPDFViewer::initPopupMenu() {
  Widget btn;
  Arg args[20];
  int n;
  XmString s, s2;

  n = 0;
#if XmVersion < 1002
  // older versions of Motif need this, newer ones choke on it,
  // sometimes not displaying the menu at all, maybe depending on the
  // state of the NumLock key (taken from DDD)
  XtSetArg(args[n], XmNmenuPost, "<Btn3Down>"); ++n;
#endif
  popupMenu = XmCreatePopupMenu(core->getDrawAreaWidget(), "popupMenu",
				args, n);
  n = 0;
  s = XmStringCreateLocalized("Open...");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  s2 = XmStringCreateLocalized("O");
  XtSetArg(args[n], XmNacceleratorText, s2); ++n;
  btn = XmCreatePushButton(popupMenu, "open", args, n);
  XmStringFree(s);
  XmStringFree(s2);
  XtManageChild(btn);
  XtAddCallback(btn, XmNactivateCallback,
		&openCbk, (XtPointer)this);
  n = 0;
  s = XmStringCreateLocalized("Open in new window...");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  btn = XmCreatePushButton(popupMenu, "openInNewWindow", args, n);
  XmStringFree(s);
  XtManageChild(btn);
  XtAddCallback(btn, XmNactivateCallback,
		&openInNewWindowCbk, (XtPointer)this);
  n = 0;
  s = XmStringCreateLocalized("Reload");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  s2 = XmStringCreateLocalized("R");
  XtSetArg(args[n], XmNacceleratorText, s2); ++n;
  btn = XmCreatePushButton(popupMenu, "reload", args, n);
  XmStringFree(s);
  XmStringFree(s2);
  XtManageChild(btn);
  XtAddCallback(btn, XmNactivateCallback,
		&reloadCbk, (XtPointer)this);
  n = 0;
  s = XmStringCreateLocalized("Save as...");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  btn = XmCreatePushButton(popupMenu, "saveAs", args, n);
  XmStringFree(s);
  XtManageChild(btn);
  XtAddCallback(btn, XmNactivateCallback,
		&saveAsCbk, (XtPointer)this);
  n = 0;
  btn = XmCreateSeparator(popupMenu, "sep1", args, n);
  XtManageChild(btn);
  n = 0;
  s = XmStringCreateLocalized("Continuous view");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  XtSetArg(args[n], XmNindicatorType, XmN_OF_MANY); ++n;
  XtSetArg(args[n], XmNvisibleWhenOff, True); ++n;
  XtSetArg(args[n], XmNset, core->getContinuousMode() ? XmSET : XmUNSET); ++n;
  btn = XmCreateToggleButton(popupMenu, "continuousMode", args, n);
  XmStringFree(s);
  XtManageChild(btn);
  XtAddCallback(btn, XmNvalueChangedCallback,
		&continuousModeToggleCbk, (XtPointer)this);
  n = 0;
  s = XmStringCreateLocalized("Full screen");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  XtSetArg(args[n], XmNindicatorType, XmN_OF_MANY); ++n;
  XtSetArg(args[n], XmNvisibleWhenOff, True); ++n;
  XtSetArg(args[n], XmNset, core->getFullScreen() ? XmSET : XmUNSET); ++n;
  btn = XmCreateToggleButton(popupMenu, "fullScreen", args, n);
  XmStringFree(s);
  XtManageChild(btn);
  XtAddCallback(btn, XmNvalueChangedCallback,
		&fullScreenToggleCbk, (XtPointer)this);
  n = 0;
  s = XmStringCreateLocalized("Rotate counterclockwise");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  btn = XmCreatePushButton(popupMenu, "rotateCCW", args, n);
  XmStringFree(s);
  XtManageChild(btn);
  XtAddCallback(btn, XmNactivateCallback,
		&rotateCCWCbk, (XtPointer)this);
  n = 0;
  s = XmStringCreateLocalized("Rotate clockwise");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  btn = XmCreatePushButton(popupMenu, "rotateCW", args, n);
  XmStringFree(s);
  XtManageChild(btn);
  XtAddCallback(btn, XmNactivateCallback,
		&rotateCWCbk, (XtPointer)this);
  n = 0;
  s = XmStringCreateLocalized("Zoom to selection");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  btn = XmCreatePushButton(popupMenu, "zoomToSelection", args, n);
  XmStringFree(s);
  XtManageChild(btn);
  XtAddCallback(btn, XmNactivateCallback,
		&zoomToSelectionCbk, (XtPointer)this);
  n = 0;
  btn = XmCreateSeparator(popupMenu, "sep2", args, n);
  XtManageChild(btn);
  n = 0;
  s = XmStringCreateLocalized("Close");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  s2 = XmStringCreateLocalized("Ctrl+W");
  XtSetArg(args[n], XmNacceleratorText, s2); ++n;
  btn = XmCreatePushButton(popupMenu, "close", args, n);
  XmStringFree(s);
  XmStringFree(s2);
  XtManageChild(btn);
  XtAddCallback(btn, XmNactivateCallback,
		&closeCbk, (XtPointer)this);
  n = 0;
  s = XmStringCreateLocalized("Quit");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  s2 = XmStringCreateLocalized("Q");
  XtSetArg(args[n], XmNacceleratorText, s2); ++n;
  btn = XmCreatePushButton(popupMenu, "quit", args, n);
  XmStringFree(s);
  XmStringFree(s2);
  XtManageChild(btn);
  XtAddCallback(btn, XmNactivateCallback,
		&quitCbk, (XtPointer)this);

  // this is magic (taken from DDD) - weird things happen if this
  // call isn't made
  XtUngrabButton(core->getDrawAreaWidget(), AnyButton, AnyModifier);
}

void XPDFViewer::addToolTip(Widget widget, char *text) {
#ifdef XmNtoolTipString
  XmString s;
  Cardinal n, i;
  WidgetList children;

  if (XtIsComposite(widget)) {
    XtVaGetValues(widget, XmNnumChildren, &n, XmNchildren, &children, NULL);
    for (i = 0; i < n; ++i) {
      addToolTip(children[i], text);
    }
  } else {
    s = XmStringCreateLocalized(text);
    XtVaSetValues(widget, XmNtoolTipString, s, NULL);
    XmStringFree(s);
  }
#endif
}

void XPDFViewer::mapWindow() {
#ifdef HAVE_X11_XPM_H
  Pixmap iconPixmap;
#endif
  int depth;
  Pixel fg, bg, arm;

  // show the window
  XtPopup(win, XtGrabNone);
  core->takeFocus();

  // create the icon
#ifdef HAVE_X11_XPM_H
  if (XpmCreatePixmapFromData(display, XtWindow(win), xpdfIcon,
			      &iconPixmap, NULL, NULL) == XpmSuccess) {
    XtVaSetValues(win, XmNiconPixmap, iconPixmap, NULL);
  }
#endif

  // set button bitmaps (must be done after the window is mapped)
  if (toolBar != None) {
    XtVaGetValues(backBtn, XmNdepth, &depth,
		  XmNforeground, &fg, XmNbackground, &bg,
		  XmNarmColor, &arm, NULL);
    XtVaSetValues(backBtn, XmNlabelType, XmPIXMAP,
		  XmNlabelPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)backArrow_bits,
					      backArrow_width,
					      backArrow_height,
					      fg, bg, depth),
		  XmNarmPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)backArrow_bits,
					      backArrow_width,
					      backArrow_height,
					      fg, arm, depth),
		  XmNlabelInsensitivePixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)backArrowDis_bits,
					      backArrowDis_width,
					      backArrowDis_height,
					      fg, bg, depth),
		  NULL);
    XtVaSetValues(prevTenPageBtn, XmNlabelType, XmPIXMAP,
		  XmNlabelPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)dblLeftArrow_bits,
					      dblLeftArrow_width,
					      dblLeftArrow_height,
					      fg, bg, depth),
		  XmNarmPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)dblLeftArrow_bits,
					      dblLeftArrow_width,
					      dblLeftArrow_height,
					      fg, arm, depth),
		  XmNlabelInsensitivePixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)dblLeftArrowDis_bits,
					      dblLeftArrowDis_width,
					      dblLeftArrowDis_height,
					      fg, bg, depth),
		  NULL);
    XtVaSetValues(prevPageBtn, XmNlabelType, XmPIXMAP,
		  XmNlabelPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)leftArrow_bits,
					      leftArrow_width,
					      leftArrow_height,
					      fg, bg, depth),
		  XmNarmPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)leftArrow_bits,
					      leftArrow_width,
					      leftArrow_height,
					      fg, arm, depth),
		  XmNlabelInsensitivePixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)leftArrowDis_bits,
					      leftArrowDis_width,
					      leftArrowDis_height,
					      fg, bg, depth),
		  NULL);
    XtVaSetValues(nextPageBtn, XmNlabelType, XmPIXMAP,
		  XmNlabelPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)rightArrow_bits,
					      rightArrow_width,
					      rightArrow_height,
					      fg, bg, depth),
		  XmNarmPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)rightArrow_bits,
					      rightArrow_width,
					      rightArrow_height,
					      fg, arm, depth),
		  XmNlabelInsensitivePixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)rightArrowDis_bits,
					      rightArrowDis_width,
					      rightArrowDis_height,
					      fg, bg, depth),
		  NULL);
    XtVaSetValues(nextTenPageBtn, XmNlabelType, XmPIXMAP,
		  XmNlabelPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)dblRightArrow_bits,
					      dblRightArrow_width,
					      dblRightArrow_height,
					      fg, bg, depth),
		  XmNarmPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)dblRightArrow_bits,
					      dblRightArrow_width,
					      dblRightArrow_height,
					      fg, arm, depth),
		  XmNlabelInsensitivePixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)dblRightArrowDis_bits,
					      dblRightArrowDis_width,
					      dblRightArrowDis_height,
					      fg, bg, depth),
		  NULL);
    XtVaSetValues(forwardBtn, XmNlabelType, XmPIXMAP,
		  XmNlabelPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)forwardArrow_bits,
					      forwardArrow_width,
					      forwardArrow_height,
					      fg, bg, depth),
		  XmNarmPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)forwardArrow_bits,
					      forwardArrow_width,
					      forwardArrow_height,
					      fg, arm, depth),
		  XmNlabelInsensitivePixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)forwardArrowDis_bits,
					      forwardArrowDis_width,
					      forwardArrowDis_height,
					      fg, bg, depth),
		  NULL);
    XtVaSetValues(findBtn, XmNlabelType, XmPIXMAP,
		  XmNlabelPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)find_bits,
					      find_width,
					      find_height,
					      fg, bg, depth),
		  XmNarmPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)find_bits,
					      find_width,
					      find_height,
					      fg, arm, depth),
		  XmNlabelInsensitivePixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)findDis_bits,
					      findDis_width,
					      findDis_height,
					      fg, bg, depth),
		  NULL);
    XtVaSetValues(printBtn, XmNlabelType, XmPIXMAP,
		  XmNlabelPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)print_bits,
					      print_width,
					      print_height,
					      fg, bg, depth),
		  XmNarmPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)print_bits,
					      print_width,
					      print_height,
					      fg, arm, depth),
		  XmNlabelInsensitivePixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)printDis_bits,
					      printDis_width,
					      printDis_height,
					      fg, bg, depth),
		  NULL);
    XtVaSetValues(aboutBtn, XmNlabelType, XmPIXMAP,
		  XmNlabelPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)about_bits,
					      about_width,
					      about_height,
					      fg, bg, depth),
		  XmNarmPixmap,
		  XCreatePixmapFromBitmapData(display, XtWindow(toolBar),
					      (char *)about_bits,
					      about_width,
					      about_height,
					      fg, arm, depth),
		  NULL);
  }
}

void XPDFViewer::closeWindow() {
  XtPopdown(win);
  XtDestroyWidget(win);
}

int XPDFViewer::getZoomIdx() {
  int i;

  for (i = 0; i < nZoomMenuItems; ++i) {
    if (core->getZoom() == zoomMenuInfo[i].zoom) {
      return i;
    }
  }
  return -1;
}

void XPDFViewer::setZoomIdx(int idx) {
  if (toolBar == None) {
    return;
  }
#if USE_COMBO_BOX
  XtVaSetValues(zoomComboBox, XmNselectedPosition, idx + 1, NULL);
#else
  XtVaSetValues(zoomMenu, XmNmenuHistory, zoomMenuBtns[idx], NULL);
#endif
}

void XPDFViewer::setZoomVal(double z) {
  if (toolBar == None) {
    return;
  }

#if USE_COMBO_BOX
  char buf[32];
  XmString s;
  int i;

  for (i = 0; i < nZoomMenuItems; ++i) {
    if (z == zoomMenuInfo[i].zoom) {
      XtVaSetValues(zoomComboBox, XmNselectedPosition, i + 1, NULL);
      return;
    }
  }
  sprintf(buf, "%d%%", (int)z);
  s = XmStringCreateLocalized(buf);
  XtVaSetValues(zoomComboBox, XmNselectedItem, s, NULL);
  XmStringFree(s);
#else
  int i;

  for (i = 0; i < nZoomMenuItems; ++i) {
    if (z == zoomMenuInfo[i].zoom) {
      XtVaSetValues(zoomMenu, XmNmenuHistory, zoomMenuBtns[i], NULL);
      return;
    }
  }
  for (i = maxZoomIdx; i < minZoomIdx; ++i) {
    if (z > zoomMenuInfo[i].zoom) {
      break;
    }
  }
  XtVaSetValues(zoomMenu, XmNmenuHistory, zoomMenuBtns[i], NULL);
#endif
}

void XPDFViewer::prevPageCbk(Widget widget, XtPointer ptr,
			     XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->core->gotoPrevPage(1, gTrue, gFalse);
  viewer->core->takeFocus();
}

void XPDFViewer::prevTenPageCbk(Widget widget, XtPointer ptr,
				XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->core->gotoPrevPage(10, gTrue, gFalse);
  viewer->core->takeFocus();
}

void XPDFViewer::nextPageCbk(Widget widget, XtPointer ptr,
			     XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->core->gotoNextPage(1, gTrue);
  viewer->core->takeFocus();
}

void XPDFViewer::nextTenPageCbk(Widget widget, XtPointer ptr,
				XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->core->gotoNextPage(10, gTrue);
  viewer->core->takeFocus();
}

void XPDFViewer::backCbk(Widget widget, XtPointer ptr,
			 XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->core->goBackward();
  viewer->core->takeFocus();
}

void XPDFViewer::forwardCbk(Widget widget, XtPointer ptr,
			    XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->core->goForward();
  viewer->core->takeFocus();
}

#if USE_COMBO_BOX

void XPDFViewer::zoomComboBoxCbk(Widget widget, XtPointer ptr,
				 XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  XmComboBoxCallbackStruct *data = (XmComboBoxCallbackStruct *)callData;
  double z;
  char *s;
  XmStringContext context;
  XmStringCharSet charSet;
  XmStringDirection dir;
  Boolean sep;

  z = viewer->core->getZoom();
  if (data->item_position == 0) {
    XmStringInitContext(&context, data->item_or_text);
    if (XmStringGetNextSegment(context, &s, &charSet, &dir, &sep)) {
      z = atof(s);
      if (z <= 1) {
	z = defZoom;
      }
      XtFree(charSet);
      XtFree(s);
    }
    XmStringFreeContext(context);
  } else {
    z = zoomMenuInfo[data->item_position - 1].zoom;
  }
  // only redraw if this was triggered by an event; otherwise
  // the caller is responsible for doing the redraw
  if (z != viewer->core->getZoom() && data->event) {
    viewer->displayPage(viewer->core->getPageNum(), z,
			viewer->core->getRotate(), gTrue, gFalse);
  }
  viewer->core->takeFocus();
}

#else // USE_COMBO_BOX

void XPDFViewer::zoomMenuCbk(Widget widget, XtPointer ptr,
			     XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  XmPushButtonCallbackStruct *data = (XmPushButtonCallbackStruct *)callData;
  XtPointer userData;
  double z;

  XtVaGetValues(widget, XmNuserData, &userData, NULL);
  z = zoomMenuInfo[(long)userData].zoom;
  // only redraw if this was triggered by an event; otherwise
  // the caller is responsible for doing the redraw
  if (z != viewer->core->getZoom() && data->event) {
    viewer->displayPage(viewer->core->getPageNum(), z,
			viewer->core->getRotate(), gTrue, gFalse);
  }
  viewer->core->takeFocus();
}

#endif // USE_COMBO_BOX

void XPDFViewer::findCbk(Widget widget, XtPointer ptr,
			 XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  if (!viewer->core->getDoc()) {
    return;
  }
  viewer->mapFindDialog();
}

void XPDFViewer::printCbk(Widget widget, XtPointer ptr,
			  XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  if (!viewer->core->getDoc()) {
    return;
  }
  XtManageChild(viewer->printDialog);
}

void XPDFViewer::aboutCbk(Widget widget, XtPointer ptr,
			  XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  XtManageChild(viewer->aboutDialog);
}

void XPDFViewer::quitCbk(Widget widget, XtPointer ptr,
			 XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->app->quit();
}

void XPDFViewer::openCbk(Widget widget, XtPointer ptr,
			 XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->mapOpenDialog(gFalse);
}

void XPDFViewer::openInNewWindowCbk(Widget widget, XtPointer ptr,
				    XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->mapOpenDialog(gTrue);
}

void XPDFViewer::reloadCbk(Widget widget, XtPointer ptr,
			 XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->reloadFile();
}

void XPDFViewer::saveAsCbk(Widget widget, XtPointer ptr,
			   XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  if (!viewer->core->getDoc()) {
    return;
  }
  viewer->mapSaveAsDialog();
}

void XPDFViewer::continuousModeToggleCbk(Widget widget, XtPointer ptr,
					 XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  XmToggleButtonCallbackStruct *data =
      (XmToggleButtonCallbackStruct *)callData;

  viewer->core->setContinuousMode(data->set == XmSET);
}

void XPDFViewer::fullScreenToggleCbk(Widget widget, XtPointer ptr,
				     XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  XmToggleButtonCallbackStruct *data =
      (XmToggleButtonCallbackStruct *)callData;

  if (data->set == XmSET) {
    viewer->cmdFullScreenMode(NULL, 0, NULL);
  } else {
    viewer->cmdWindowMode(NULL, 0, NULL);
  }
}

void XPDFViewer::rotateCCWCbk(Widget widget, XtPointer ptr,
			      XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->cmdRotateCCW(NULL, 0, NULL);
}

void XPDFViewer::rotateCWCbk(Widget widget, XtPointer ptr,
			     XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->cmdRotateCW(NULL, 0, NULL);
}

void XPDFViewer::zoomToSelectionCbk(Widget widget, XtPointer ptr,
				    XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  int pg;
  double ulx, uly, lrx, lry;

  if (viewer->core->getSelection(&pg, &ulx, &uly, &lrx, &lry)) {
    viewer->core->zoomToRect(pg, ulx, uly, lrx, lry);
  }
}

void XPDFViewer::closeCbk(Widget widget, XtPointer ptr,
			  XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->app->close(viewer, gFalse);
}

void XPDFViewer::closeMsgCbk(Widget widget, XtPointer ptr,
			     XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->app->close(viewer, gTrue);
}

void XPDFViewer::pageNumCbk(Widget widget, XtPointer ptr,
			    XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  char *s, *p;
  int pg;
  char buf[20];

  if (!viewer->core->getDoc()) {
    goto err;
  }
  s = XmTextFieldGetString(viewer->pageNumText);
  for (p = s; *p; ++p) {
    if (!isdigit(*p)) {
      goto err;
    }
  }
  pg = atoi(s);
  if (pg < 1 || pg > viewer->core->getDoc()->getNumPages()) {
    goto err;
  }
  viewer->displayPage(pg, viewer->core->getZoom(),
		      viewer->core->getRotate(), gFalse, gTrue);
  viewer->core->takeFocus();
  return;

 err:
  XBell(viewer->display, 0);
  sprintf(buf, "%d", viewer->core->getPageNum());
  XmTextFieldSetString(viewer->pageNumText, buf);
}

void XPDFViewer::updateCbk(void *data, GString *fileName,
			   int pageNum, int numPages, const char *linkString) {
  XPDFViewer *viewer = (XPDFViewer *)data;
  GString *title;
  char buf[20];
  XmString s;

  if (fileName) {
    if (!(title = viewer->app->getTitle())) {
      title = (new GString(xpdfAppName))->append(": ")->append(fileName);
    }
    XtVaSetValues(viewer->win, XmNtitle, title->getCString(),
		  XmNiconName, title->getCString(), NULL);
    if (!viewer->app->getTitle()) {
      delete title;
    }
#ifndef DISABLE_OUTLINE
    viewer->setupOutline();
#endif
    viewer->setupPrintDialog();
  }

  if (viewer->toolBar != None) {
    if (pageNum >= 0) {
      s = XmStringCreateLocalized("");
      XtVaSetValues(viewer->linkLabel, XmNlabelString, s, NULL);
      XmStringFree(s);
      sprintf(buf, "%d", pageNum);
      XmTextFieldSetString(viewer->pageNumText, buf);
      XtVaSetValues(viewer->prevTenPageBtn, XmNsensitive,
		    pageNum > 1, NULL);
      XtVaSetValues(viewer->prevPageBtn, XmNsensitive,
		    pageNum > 1, NULL);
      XtVaSetValues(viewer->nextTenPageBtn, XmNsensitive,
		    pageNum < viewer->core->getDoc()->getNumPages(), NULL);
      XtVaSetValues(viewer->nextPageBtn, XmNsensitive,
		    pageNum < viewer->core->getDoc()->getNumPages(), NULL);
      XtVaSetValues(viewer->backBtn, XmNsensitive,
		    viewer->core->canGoBack(), NULL);
      XtVaSetValues(viewer->forwardBtn, XmNsensitive,
		    viewer->core->canGoForward(), NULL);
    }

    if (numPages >= 0) {
      sprintf(buf, " of %d", numPages);
      s = XmStringCreateLocalized(buf);
      XtVaSetValues(viewer->pageCountLabel, XmNlabelString, s, NULL);
      XmStringFree(s);
    }

    if (linkString) {
      s = XmStringCreateLocalized((char *)linkString);
      XtVaSetValues(viewer->linkLabel, XmNlabelString, s, NULL);
      XmStringFree(s);
    }
  }
}

//------------------------------------------------------------------------
// GUI code: outline
//------------------------------------------------------------------------

#ifndef DISABLE_OUTLINE

void XPDFViewer::setupOutline() {
  GList *items;
  UnicodeMap *uMap;
  GString *enc;
  int i;

  if (outlineScroll == None) {
    return;
  }

  // unmanage and destroy the old labels
  if (outlineLabels) {
    XtUnmanageChildren(outlineLabels, outlineLabelsLength);
    for (i = 0; i < outlineLabelsLength; ++i) {
      XtDestroyWidget(outlineLabels[i]);
    }
    gfree(outlineLabels);
    outlineLabels = NULL;
    outlineLabelsLength = outlineLabelsSize = 0;
  }

  if (core->getDoc()) {

    // create the new labels
    items = core->getDoc()->getOutline()->getItems();
    if (items && items->getLength() > 0) {
      enc = new GString("Latin1");
      uMap = globalParams->getUnicodeMap(enc);
      delete enc;
      setupOutlineItems(items, NULL, uMap);
      uMap->decRefCnt();
    }

    // manage the new labels
    XtManageChildren(outlineLabels, outlineLabelsLength);
  }
}

void XPDFViewer::setupOutlineItems(GList *items, Widget parent,
				   UnicodeMap *uMap) {
  OutlineItem *item;
  GList *kids;
  Widget label;
  Arg args[20];
  GString *title;
  char buf[8];
  XmString s;
  int i, j, n;

  for (i = 0; i < items->getLength(); ++i) {
    item = (OutlineItem *)items->get(i);
    title = new GString();
    for (j = 0; j < item->getTitleLength(); ++j) {
      n = uMap->mapUnicode(item->getTitle()[j], buf, sizeof(buf));
      title->append(buf, n);
    }
    n = 0;
    XtSetArg(args[n], XPDFNentryPosition, i); ++n;
    if (parent) {
      XtSetArg(args[n], XPDFNentryParent, parent); ++n;
    }
    XtSetArg(args[n], XPDFNentryExpanded, item->isOpen()); ++n;
    s = XmStringCreateLocalized(title->getCString());
    delete title;
    XtSetArg(args[n], XmNlabelString, s); ++n;
    XtSetArg(args[n], XmNuserData, item); ++n;
    XtSetArg(args[n], XmNmarginWidth, 0); ++n;
    XtSetArg(args[n], XmNmarginHeight, 2); ++n;
    XtSetArg(args[n], XmNshadowThickness, 0); ++n;
    XtSetArg(args[n], XmNforeground,
	     app->getReverseVideo() ? WhitePixel(display, screenNum)
	                            : BlackPixel(display, screenNum)); ++n;
    XtSetArg(args[n], XmNbackground, app->getPaperPixel()); ++n;
    label = XmCreateLabelGadget(outlineTree, "label", args, n);
    XmStringFree(s);
    if (outlineLabelsLength == outlineLabelsSize) {
      outlineLabelsSize += 64;
      outlineLabels = (Widget *)greallocn(outlineLabels,
					  outlineLabelsSize, sizeof(Widget *));
    }
    outlineLabels[outlineLabelsLength++] = label;
    item->open();
    if ((kids = item->getKids())) {
      setupOutlineItems(kids, label, uMap);
    }
  }
}

void XPDFViewer::outlineSelectCbk(Widget widget, XtPointer ptr,
				  XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  XPDFTreeSelectCallbackStruct *data =
      (XPDFTreeSelectCallbackStruct *)callData;
  OutlineItem *item;

  XtVaGetValues(data->selectedItem, XmNuserData, &item, NULL);
  if (item) {
    if (item->getAction()) {
      viewer->core->doAction(item->getAction());
    }
  }
  viewer->core->takeFocus();
}

#endif // !DISABLE_OUTLINE

//------------------------------------------------------------------------
// GUI code: "about" dialog
//------------------------------------------------------------------------

void XPDFViewer::initAboutDialog() {
  Widget scrolledWin, col, label, sep, closeBtn;
  Arg args[20];
  int n, i;
  XmString s;
  char buf[20];

  //----- dialog
  n = 0;
  s = XmStringCreateLocalized(xpdfAppName ": About");
  XtSetArg(args[n], XmNdialogTitle, s); ++n;
  XtSetArg(args[n], XmNwidth, 450); ++n;
  XtSetArg(args[n], XmNheight, 300); ++n;
  aboutDialog = XmCreateFormDialog(win, "aboutDialog", args, n);
  XmStringFree(s);

  //----- "close" button
  n = 0;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNrightOffset, 4); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomOffset, 4); ++n;
  closeBtn = XmCreatePushButton(aboutDialog, "Close", args, n);
  XtManageChild(closeBtn);
  n = 0;
  XtSetArg(args[n], XmNdefaultButton, closeBtn); ++n;
  XtSetArg(args[n], XmNcancelButton, closeBtn); ++n;
  XtSetValues(aboutDialog, args, n);

  //----- scrolled window and RowColumn
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNbottomWidget, closeBtn); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); ++n;
  scrolledWin = XmCreateScrolledWindow(aboutDialog, "scrolledWin", args, n);
  XtManageChild(scrolledWin);
  n = 0;
  XtSetArg(args[n], XmNorientation, XmVERTICAL); ++n;
  XtSetArg(args[n], XmNpacking, XmPACK_TIGHT); ++n;
  col = XmCreateRowColumn(scrolledWin, "col", args, n);
  XtManageChild(col);

  //----- fonts
  aboutBigFont =
    createFontList("-*-times-bold-i-normal--20-*-*-*-*-*-iso8859-1");
  aboutVersionFont =
    createFontList("-*-times-medium-r-normal--16-*-*-*-*-*-iso8859-1");
  aboutFixedFont =
    createFontList("-*-courier-medium-r-normal--12-*-*-*-*-*-iso8859-1");

  //----- heading
  n = 0;
  s = XmStringCreateLocalized("Xpdf");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  XtSetArg(args[n], XmNfontList, aboutBigFont); ++n;
  label = XmCreateLabel(col, "h0", args, n);
  XmStringFree(s);
  XtManageChild(label);
  n = 0;
  s = XmStringCreateLocalized("Version " xpdfVersion);
  XtSetArg(args[n], XmNlabelString, s); ++n;
  XtSetArg(args[n], XmNfontList, aboutVersionFont); ++n;
  label = XmCreateLabel(col, "h1", args, n);
  XmStringFree(s);
  XtManageChild(label);
  n = 0;
  s = XmStringCreateLocalized(xpdfCopyright);
  XtSetArg(args[n], XmNlabelString, s); ++n;
  XtSetArg(args[n], XmNfontList, aboutVersionFont); ++n;
  label = XmCreateLabel(col, "h2", args, n);
  XmStringFree(s);
  XtManageChild(label);
  n = 0;
  s = XmStringCreateLocalized(" ");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  XtSetArg(args[n], XmNfontList, aboutVersionFont); ++n;
  label = XmCreateLabel(col, "h3", args, n);
  XmStringFree(s);
  XtManageChild(label);
  n = 0;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); ++n;
  sep = XmCreateSeparator(col, "sep", args, n);
  XtManageChild(sep);
  n = 0;
  s = XmStringCreateLocalized(" ");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  XtSetArg(args[n], XmNfontList, aboutVersionFont); ++n;
  label = XmCreateLabel(col, "h4", args, n);
  XmStringFree(s);
  XtManageChild(label);
  n = 0;

  //----- text
  for (i = 0; aboutWinText[i]; ++i) {
    n = 0;
    s = XmStringCreateLocalized((char *)aboutWinText[i]);
    XtSetArg(args[n], XmNlabelString, s); ++n;
    XtSetArg(args[n], XmNfontList, aboutFixedFont); ++n;
    sprintf(buf, "t%d", i);
    label = XmCreateLabel(col, buf, args, n);
    XtManageChild(label);
    XmStringFree(s);
  }
}

//------------------------------------------------------------------------
// GUI code: "open" dialog
//------------------------------------------------------------------------

void XPDFViewer::initOpenDialog() {
  Arg args[20];
  int n;
  XmString s1, s2, s3;
  GString *dir;

  n = 0;
  s1 = XmStringCreateLocalized("Open");
  XtSetArg(args[n], XmNokLabelString, s1); ++n;
  s2 = XmStringCreateLocalized("*.[Pp][Dd][Ff]");
  XtSetArg(args[n], XmNpattern, s2); ++n;
  s3 = XmStringCreateLocalized(xpdfAppName ": Open");
  XtSetArg(args[n], XmNdialogTitle, s3); ++n;
  XtSetArg(args[n], XmNdialogStyle, XmDIALOG_PRIMARY_APPLICATION_MODAL); ++n;
  XtSetArg(args[n], XmNautoUnmanage, True); ++n;
  openDialog = XmCreateFileSelectionDialog(win, "openDialog", args, n);
  XmStringFree(s1);
  XmStringFree(s2);
  XmStringFree(s3);
  XtUnmanageChild(XmFileSelectionBoxGetChild(openDialog,
					     XmDIALOG_HELP_BUTTON));
  XtAddCallback(openDialog, XmNokCallback,
		&openOkCbk, (XtPointer)this);

  if (core->getDoc() && core->getDoc()->getFileName()) {
    dir = makePathAbsolute(grabPath(
	      core->getDoc()->getFileName()->getCString()));
    s1 = XmStringCreateLocalized(dir->getCString());
    XtVaSetValues(openDialog, XmNdirectory, s1, NULL);
    XmStringFree(s1);
    delete dir;
  }
}

void XPDFViewer::mapOpenDialog(GBool openInNewWindowA) {
  if (!openDialog) {
    initOpenDialog();
  }
  openInNewWindow = openInNewWindowA;
  XmFileSelectionDoSearch(openDialog, NULL);
  XtManageChild(openDialog);
}

void XPDFViewer::openOkCbk(Widget widget, XtPointer ptr,
			   XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  XmFileSelectionBoxCallbackStruct *data =
    (XmFileSelectionBoxCallbackStruct *)callData;
  char *fileName;
  XmStringContext context;
  XmStringCharSet charSet;
  XmStringDirection dir;
  Boolean sep;
  GString *fileNameStr;

  XmStringInitContext(&context, data->value);
  if (XmStringGetNextSegment(context, &fileName, &charSet, &dir, &sep)) {
    fileNameStr = new GString(fileName);
    if (viewer->openInNewWindow) {
      viewer->app->open(fileNameStr);
    } else {
      if (viewer->loadFile(fileNameStr)) {
	viewer->displayPage(1, viewer->core->getZoom(),
			    viewer->core->getRotate(), gTrue, gTrue);
      }
    }
    delete fileNameStr;
    XtFree(charSet);
    XtFree(fileName);
  }
  XmStringFreeContext(context);
}

//------------------------------------------------------------------------
// GUI code: "find" dialog
//------------------------------------------------------------------------

void XPDFViewer::initFindDialog() {
  Widget form1, label, okBtn, closeBtn;
  Arg args[20];
  int n;
  XmString s;

  //----- dialog
  n = 0;
  s = XmStringCreateLocalized(xpdfAppName ": Find");
  XtSetArg(args[n], XmNdialogTitle, s); ++n;
  XtSetArg(args[n], XmNnavigationType, XmNONE); ++n;
  XtSetArg(args[n], XmNautoUnmanage, False); ++n;
  findDialog = XmCreateFormDialog(win, "findDialog", args, n);
  XmStringFree(s);

  //----- "find" and "close" buttons
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftOffset, 4); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomOffset, 4); ++n;
  XtSetArg(args[n], XmNnavigationType, XmEXCLUSIVE_TAB_GROUP); ++n;
  okBtn = XmCreatePushButton(findDialog, "Find", args, n);
  XtManageChild(okBtn);
  XtAddCallback(okBtn, XmNactivateCallback,
		&findFindCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNrightOffset, 4); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomOffset, 4); ++n;
  XtSetArg(args[n], XmNnavigationType, XmEXCLUSIVE_TAB_GROUP); ++n;
  closeBtn = XmCreatePushButton(findDialog, "Close", args, n);
  XtManageChild(closeBtn);
  XtAddCallback(closeBtn, XmNactivateCallback,
		&findCloseCbk, (XtPointer)this);

  //----- checkboxes
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftOffset, 4); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNbottomWidget, okBtn); ++n;
  XtSetArg(args[n], XmNindicatorType, XmN_OF_MANY); ++n;
#if XmVERSION <= 1
  XtSetArg(args[n], XmNindicatorOn, True); ++n;
#else
  XtSetArg(args[n], XmNindicatorOn, XmINDICATOR_FILL); ++n;
#endif
  XtSetArg(args[n], XmNset, XmUNSET); ++n;
  s = XmStringCreateLocalized("Search backward");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  findBackwardToggle = XmCreateToggleButton(findDialog, "backward", args, n);
  XmStringFree(s);
  XtManageChild(findBackwardToggle);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, findBackwardToggle); ++n;
  XtSetArg(args[n], XmNleftOffset, 16); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNbottomWidget, okBtn); ++n;
  XtSetArg(args[n], XmNindicatorType, XmN_OF_MANY); ++n;
#if XmVERSION <= 1
  XtSetArg(args[n], XmNindicatorOn, True); ++n;
#else
  XtSetArg(args[n], XmNindicatorOn, XmINDICATOR_FILL); ++n;
#endif
  XtSetArg(args[n], XmNset, XmUNSET); ++n;
  s = XmStringCreateLocalized("Match case");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  findCaseSensitiveToggle =
      XmCreateToggleButton(findDialog, "matchCase", args, n);
  XmStringFree(s);
  XtManageChild(findCaseSensitiveToggle);
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, findCaseSensitiveToggle); ++n;
  XtSetArg(args[n], XmNleftOffset, 16); ++n;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNrightOffset, 4); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNbottomWidget, okBtn); ++n;
  XtSetArg(args[n], XmNindicatorType, XmN_OF_MANY); ++n;
#if XmVERSION <= 1
  XtSetArg(args[n], XmNindicatorOn, True); ++n;
#else
  XtSetArg(args[n], XmNindicatorOn, XmINDICATOR_FILL); ++n;
#endif
  XtSetArg(args[n], XmNset, XmUNSET); ++n;
  s = XmStringCreateLocalized("Whole words only");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  findWholeWordToggle = XmCreateToggleButton(findDialog, "wholeWord", args, n);
  XmStringFree(s);
  XtManageChild(findWholeWordToggle);

  //----- search string entry
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNtopOffset, 4); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNbottomWidget, findBackwardToggle); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftOffset, 2); ++n;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNrightOffset, 2); ++n;
  form1 = XmCreateForm(findDialog, "form", args, n);
  XtManageChild(form1);
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  s = XmStringCreateLocalized("Find text: ");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  label = XmCreateLabel(form1, "label", args, n);
  XmStringFree(s);
  XtManageChild(label);
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNleftWidget, label); ++n;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  findText = XmCreateTextField(form1, "text", args, n);
  XtManageChild(findText);
#ifdef LESSTIF_VERSION
  XtAddCallback(findText, XmNactivateCallback,
		&findFindCbk, (XtPointer)this);
#endif

  //----- dialog parameters
  n = 0;
  XtSetArg(args[n], XmNdefaultButton, okBtn); ++n;
  XtSetArg(args[n], XmNcancelButton, closeBtn); ++n;
#if XmVersion > 1001
  XtSetArg(args[n], XmNinitialFocus, findText); ++n;
#endif
  XtSetValues(findDialog, args, n);
}

void XPDFViewer::findFindCbk(Widget widget, XtPointer ptr,
			     XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  viewer->doFind(gFalse);
}

void XPDFViewer::mapFindDialog() {
  XmTextFieldSetSelection(findText, 0, XmTextFieldGetLastPosition(findText),
			  XtLastTimestampProcessed(display));
  XmTextFieldSetInsertionPosition(findText, 0);
  XtManageChild(findDialog);
}

void XPDFViewer::doFind(GBool next) {
  if (XtWindow(findDialog)) {
    XDefineCursor(display, XtWindow(findDialog), core->getBusyCursor());
  }
  core->find(XmTextFieldGetString(findText),
	     XmToggleButtonGetState(findCaseSensitiveToggle),
	     next,
	     XmToggleButtonGetState(findBackwardToggle),
	     XmToggleButtonGetState(findWholeWordToggle),
	     gFalse);
  if (XtWindow(findDialog)) {
    XUndefineCursor(display, XtWindow(findDialog));
  }
}

void XPDFViewer::findCloseCbk(Widget widget, XtPointer ptr,
			      XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;

  XtUnmanageChild(viewer->findDialog);
}

//------------------------------------------------------------------------
// GUI code: "save as" dialog
//------------------------------------------------------------------------

void XPDFViewer::initSaveAsDialog() {
  Arg args[20];
  int n;
  XmString s1, s2, s3;
  GString *dir;

  n = 0;
  s1 = XmStringCreateLocalized("Save");
  XtSetArg(args[n], XmNokLabelString, s1); ++n;
  s2 = XmStringCreateLocalized("*.[Pp][Dd][Ff]");
  XtSetArg(args[n], XmNpattern, s2); ++n;
  s3 = XmStringCreateLocalized(xpdfAppName ": Save as");
  XtSetArg(args[n], XmNdialogTitle, s3); ++n;
  XtSetArg(args[n], XmNdialogStyle, XmDIALOG_PRIMARY_APPLICATION_MODAL); ++n;
  XtSetArg(args[n], XmNautoUnmanage, True); ++n;
  saveAsDialog = XmCreateFileSelectionDialog(win, "saveAsDialog", args, n);
  XmStringFree(s1);
  XmStringFree(s2);
  XmStringFree(s3);
  XtUnmanageChild(XmFileSelectionBoxGetChild(saveAsDialog,
					     XmDIALOG_HELP_BUTTON));
  XtAddCallback(saveAsDialog, XmNokCallback,
		&saveAsOkCbk, (XtPointer)this);

  if (core->getDoc() && core->getDoc()->getFileName()) {
    dir = makePathAbsolute(grabPath(
	      core->getDoc()->getFileName()->getCString()));
    s1 = XmStringCreateLocalized(dir->getCString());
    XtVaSetValues(saveAsDialog, XmNdirectory, s1, NULL);
    XmStringFree(s1);
    delete dir;
  }
}

void XPDFViewer::mapSaveAsDialog() {
  if (!saveAsDialog) {
    initSaveAsDialog();
  }
  XmFileSelectionDoSearch(saveAsDialog, NULL);
  XtManageChild(saveAsDialog);
}

void XPDFViewer::saveAsOkCbk(Widget widget, XtPointer ptr,
			     XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  XmFileSelectionBoxCallbackStruct *data =
    (XmFileSelectionBoxCallbackStruct *)callData;
  char *fileName;
  GString *fileNameStr;
  XmStringContext context;
  XmStringCharSet charSet;
  XmStringDirection dir;
  Boolean sep;

  XmStringInitContext(&context, data->value);
  if (XmStringGetNextSegment(context, &fileName, &charSet, &dir, &sep)) {
    fileNameStr = new GString(fileName);
    viewer->core->getDoc()->saveAs(fileNameStr);
    delete fileNameStr;
    XtFree(charSet);
    XtFree(fileName);
  }
  XmStringFreeContext(context);
}

//------------------------------------------------------------------------
// GUI code: "print" dialog
//------------------------------------------------------------------------

void XPDFViewer::initPrintDialog() {
  Widget sep1, sep2, row, label1, label2, okBtn, cancelBtn;
  Arg args[20];
  int n;
  XmString s;
  GString *psFileName;

  //----- dialog
  n = 0;
  s = XmStringCreateLocalized(xpdfAppName ": Print");
  XtSetArg(args[n], XmNdialogTitle, s); ++n;
  XtSetArg(args[n], XmNdialogStyle, XmDIALOG_PRIMARY_APPLICATION_MODAL); ++n;
  printDialog = XmCreateFormDialog(win, "printDialog", args, n);
  XmStringFree(s);

  //----- "print with command"
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNtopOffset, 4); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); ++n;
  XtSetArg(args[n], XmNset, XmSET); ++n;
  s = XmStringCreateLocalized("Print with command:");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  printWithCmdBtn = XmCreateToggleButton(printDialog, "printWithCmd", args, n);
  XmStringFree(s);
  XtManageChild(printWithCmdBtn);
  XtAddCallback(printWithCmdBtn, XmNvalueChangedCallback,
		&printWithCmdBtnCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNtopWidget, printWithCmdBtn); ++n;
  XtSetArg(args[n], XmNtopOffset, 2); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftOffset, 16); ++n;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNrightOffset, 4); ++n;
  XtSetArg(args[n], XmNcolumns, 40); ++n;
  printCmdText = XmCreateTextField(printDialog, "printCmd", args, n);
  XtManageChild(printCmdText);

  //----- "print to file"
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNtopWidget, printCmdText); ++n;
  XtSetArg(args[n], XmNtopOffset, 4); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); ++n;
  XtSetArg(args[n], XmNset, XmUNSET); ++n;
  s = XmStringCreateLocalized("Print to file:");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  printToFileBtn = XmCreateToggleButton(printDialog, "printToFile", args, n);
  XmStringFree(s);
  XtManageChild(printToFileBtn);
  XtAddCallback(printToFileBtn, XmNvalueChangedCallback,
		&printToFileBtnCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNtopWidget, printToFileBtn); ++n;
  XtSetArg(args[n], XmNtopOffset, 2); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftOffset, 16); ++n;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNrightOffset, 4); ++n;
  XtSetArg(args[n], XmNcolumns, 40); ++n;
  XtSetArg(args[n], XmNsensitive, False); ++n;
  printFileText = XmCreateTextField(printDialog, "printFile", args, n);
  XtManageChild(printFileText);

  //----- separator
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNtopWidget, printFileText); ++n;
  XtSetArg(args[n], XmNtopOffset, 8); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftOffset, 8); ++n;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNrightOffset, 8); ++n;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); ++n;
  sep1 = XmCreateSeparator(printDialog, "sep1", args, n);
  XtManageChild(sep1);

  //----- page range
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNtopWidget, sep1); ++n;
  XtSetArg(args[n], XmNtopOffset, 8); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftOffset, 4); ++n;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); ++n;
  XtSetArg(args[n], XmNpacking, XmPACK_TIGHT); ++n;
  row = XmCreateRowColumn(printDialog, "row", args, n);
  XtManageChild(row);
  n = 0;
  s = XmStringCreateLocalized("Pages:");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  label1 = XmCreateLabel(row, "label1", args, n);
  XmStringFree(s);
  XtManageChild(label1);
  n = 0;
  XtSetArg(args[n], XmNcolumns, 5); ++n;
  printFirstPage = XmCreateTextField(row, "printFirstPage", args, n);
  XtManageChild(printFirstPage);
  n = 0;
  s = XmStringCreateLocalized("to");
  XtSetArg(args[n], XmNlabelString, s); ++n;
  label2 = XmCreateLabel(row, "label2", args, n);
  XmStringFree(s);
  XtManageChild(label2);
  n = 0;
  XtSetArg(args[n], XmNcolumns, 5); ++n;
  printLastPage = XmCreateTextField(row, "printLastPage", args, n);
  XtManageChild(printLastPage);

  //----- separator
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNtopWidget, row); ++n;
  XtSetArg(args[n], XmNtopOffset, 8); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftOffset, 8); ++n;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNrightOffset, 8); ++n;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); ++n;
  sep2 = XmCreateSeparator(printDialog, "sep2", args, n);
  XtManageChild(sep2);

  //----- "print" and "cancel" buttons
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNtopWidget, sep2); ++n;
  XtSetArg(args[n], XmNtopOffset, 8); ++n;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNleftOffset, 4); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomOffset, 4); ++n;
  okBtn = XmCreatePushButton(printDialog, "Print", args, n);
  XtManageChild(okBtn);
  XtAddCallback(okBtn, XmNactivateCallback,
		&printPrintCbk, (XtPointer)this);
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); ++n;
  XtSetArg(args[n], XmNtopWidget, sep2); ++n;
  XtSetArg(args[n], XmNtopOffset, 8); ++n;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNrightOffset, 4); ++n;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n;
  XtSetArg(args[n], XmNbottomOffset, 4); ++n;
  cancelBtn = XmCreatePushButton(printDialog, "Cancel", args, n);
  XtManageChild(cancelBtn);
  n = 0;
  XtSetArg(args[n], XmNdefaultButton, okBtn); ++n;
  XtSetArg(args[n], XmNcancelButton, cancelBtn); ++n;
  XtSetValues(printDialog, args, n);

  //----- initial values
  if ((psFileName = globalParams->getPSFile())) {
    if (psFileName->getChar(0) == '|') {
      XmTextFieldSetString(printCmdText,
			   psFileName->getCString() + 1);
    } else {
      XmTextFieldSetString(printFileText, psFileName->getCString());
    }
    delete psFileName;
  }
}

void XPDFViewer::setupPrintDialog() {
  PDFDoc *doc;
  char buf[20];
  GString *pdfFileName, *psFileName, *psFileName2;
  char *p;

  doc = core->getDoc();
  psFileName = globalParams->getPSFile();
  if (!psFileName || psFileName->getChar(0) == '|') {
    pdfFileName = doc->getFileName();
    p = pdfFileName->getCString() + pdfFileName->getLength() - 4;
    if (!strcmp(p, ".pdf") || !strcmp(p, ".PDF")) {
      psFileName2 = new GString(pdfFileName->getCString(),
				pdfFileName->getLength() - 4);
    } else {
      psFileName2 = pdfFileName->copy();
    }
    psFileName2->append(".ps");
    XmTextFieldSetString(printFileText, psFileName2->getCString());
    delete psFileName2;
  }
  if (psFileName && psFileName->getChar(0) == '|') {
    XmToggleButtonSetState(printWithCmdBtn, True, False);
    XmToggleButtonSetState(printToFileBtn, False, False);
    XtVaSetValues(printCmdText, XmNsensitive, True, NULL);
    XtVaSetValues(printFileText, XmNsensitive, False, NULL);
  } else {
    XmToggleButtonSetState(printWithCmdBtn, False, False);
    XmToggleButtonSetState(printToFileBtn, True, False);
    XtVaSetValues(printCmdText, XmNsensitive, False, NULL);
    XtVaSetValues(printFileText, XmNsensitive, True, NULL);
  }
  if (psFileName) {
    delete psFileName;
  }

  sprintf(buf, "%d", doc->getNumPages());
  XmTextFieldSetString(printFirstPage, "1");
  XmTextFieldSetString(printLastPage, buf);
}

void XPDFViewer::printWithCmdBtnCbk(Widget widget, XtPointer ptr,
				    XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  XmToggleButtonCallbackStruct *data =
      (XmToggleButtonCallbackStruct *)callData;

  if (data->set != XmSET) {
    XmToggleButtonSetState(viewer->printWithCmdBtn, True, False);
  }
  XmToggleButtonSetState(viewer->printToFileBtn, False, False);
  XtVaSetValues(viewer->printCmdText, XmNsensitive, True, NULL);
  XtVaSetValues(viewer->printFileText, XmNsensitive, False, NULL);
}

void XPDFViewer::printToFileBtnCbk(Widget widget, XtPointer ptr,
				   XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  XmToggleButtonCallbackStruct *data =
      (XmToggleButtonCallbackStruct *)callData;

  if (data->set != XmSET) {
    XmToggleButtonSetState(viewer->printToFileBtn, True, False);
  }
  XmToggleButtonSetState(viewer->printWithCmdBtn, False, False);
  XtVaSetValues(viewer->printFileText, XmNsensitive, True, NULL);
  XtVaSetValues(viewer->printCmdText, XmNsensitive, False, NULL);
}

void XPDFViewer::printPrintCbk(Widget widget, XtPointer ptr,
			       XtPointer callData) {
  XPDFViewer *viewer = (XPDFViewer *)ptr;
  unsigned char withCmd;
  GString *psFileName;
  int firstPage, lastPage;
  PDFDoc *doc;
  PSOutputDev *psOut;

  doc = viewer->core->getDoc();
  if (!doc->okToPrint()) {
    error(errNotAllowed, -1, "Printing this document is not allowed.");
    return;
  }

  viewer->core->setBusyCursor(gTrue);

  XtVaGetValues(viewer->printWithCmdBtn, XmNset, &withCmd, NULL);
  if (withCmd) {
    psFileName = new GString(XmTextFieldGetString(viewer->printCmdText));
    psFileName->insert(0, '|');
  } else {
    psFileName = new GString(XmTextFieldGetString(viewer->printFileText));
  }

  firstPage = atoi(XmTextFieldGetString(viewer->printFirstPage));
  lastPage = atoi(XmTextFieldGetString(viewer->printLastPage));
  if (firstPage < 1) {
    firstPage = 1;
  } else if (firstPage > doc->getNumPages()) {
    firstPage = doc->getNumPages();
  }
  if (lastPage < firstPage) {
    lastPage = firstPage;
  } else if (lastPage > doc->getNumPages()) {
    lastPage = doc->getNumPages();
  }

  psOut = new PSOutputDev(psFileName->getCString(), doc,
			  firstPage, lastPage, psModePS);
  if (psOut->isOk()) {
    doc->displayPages(psOut, firstPage, lastPage, 72, 72,
		      0, gTrue, globalParams->getPSCrop(), gTrue);
  }
  delete psOut;
  delete psFileName;

  viewer->core->setBusyCursor(gFalse);
}

//------------------------------------------------------------------------
// Motif support
//------------------------------------------------------------------------

XmFontList XPDFViewer::createFontList(char *xlfd) {
  XmFontList fontList;

#if XmVersion <= 1001

  XFontStruct *font;
  String params;
  Cardinal nParams;

  font = XLoadQueryFont(display, xlfd);
  if (font) {
    fontList = XmFontListCreate(font, XmSTRING_DEFAULT_CHARSET);
  } else {
    params = (String)xlfd;
    nParams = 1;
    XtAppWarningMsg(app->getAppContext(),
		    "noSuchFont", "CvtStringToXmFontList",
		    "XtToolkitError", "No such font: %s",
		    &params, &nParams);
    fontList = NULL;
  }

#else

  XmFontListEntry entry;

  entry = XmFontListEntryLoad(display, xlfd,
			      XmFONT_IS_FONT, XmFONTLIST_DEFAULT_TAG);
  fontList = XmFontListAppendEntry(NULL, entry);
  XmFontListEntryFree(&entry);

#endif

  return fontList;
}
