#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>

#include <xclass/OXMsgBox.h>
#include <xclass/OMimeTypes.h>
#include <xclass/OXFileDialog.h>

#include "OXGhostView.h"
#include "OXDialogs.h"
#include "OXPageLBEntry.h"
#include "main.h"

#include "gtb-open.xpm"
#include "gtb-save.xpm"
#include "gtb-print.xpm"
#include "gtb-zoomin.xpm"
#include "gtb-zoomout.xpm"
#include "gtb-prev.xpm"
#include "gtb-next.xpm"
#include "pbg.xpm"


#include "ps.h"


FILE   *psfile = NULL;
struct document *doc = NULL;
int    current_page = -1;
int    current_pagemedia = 0;
XCPageOrientation current_orientation = PageOrientationPortrait;
int    swap_landscape = False;
int    has_toc = False;
int    mag_step = 0;
int    base_papersize = 0;
int    default_document_pagemedia = True;
int    default_document_orientation = True;
int    last_pagemedia_entry = M_PAGEMEDIA;
char   *filename = NULL;
char   *full_filename = NULL;
char   *printerName, *printProg;
time_t mtime;

OXOutput *output_dlg = NULL;

// we will keep our own list of listbox entries,
// as that makes operations with marked pages easier
OXPageLBEntry **toc = NULL;


struct _popup file_popup = {
  { NULL }, {
  { "&Open...",               M_FILE_OPEN,       0,             NULL },
  { "&Reopen",                M_FILE_REOPEN,     MENU_DISABLED, NULL },
  { "&Save marked pages...",  M_FILE_SAVEMARKED, MENU_DISABLED, NULL },
  { "",                       -1,                0,             NULL },
  { "&Print...",              M_FILE_PRINT,      MENU_DISABLED, NULL },
  { "P&rint setup...",        M_FILE_PRINTSETUP, MENU_DISABLED, NULL },
  { "",                       -1,                0,             NULL },
  { "E&xit",                  M_FILE_EXIT,       0,             NULL },
  { NULL,                     -1,                0,             NULL } } };

struct _popup media_popup = {
  { NULL }, {
  { NULL,              -1,                0,             NULL } } };

struct _popup magstep_popup = {
  { NULL }, {
  { "-5",              M_MAGSTEP_M5,      0,             NULL },
  { "-4",              M_MAGSTEP_M4,      0,             NULL },
  { "-3",              M_MAGSTEP_M3,      0,             NULL },
  { "-2",              M_MAGSTEP_M2,      0,             NULL },
  { "-1",              M_MAGSTEP_M1,      0,             NULL },
  { "0",               M_MAGSTEP_0,       MENU_RCHECKED, NULL },
  { "1",               M_MAGSTEP_P1,      0,             NULL },
  { "2",               M_MAGSTEP_P2,      0,             NULL },
  { "3",               M_MAGSTEP_P3,      0,             NULL },
  { "4",               M_MAGSTEP_P4,      0,             NULL },
  { "5",               M_MAGSTEP_P5,      0,             NULL },
  { NULL,              -1,                0,             NULL } } };

struct _popup orien_popup = {
  { NULL }, {
  { "&Document Default", M_ORIEN_DEFAULT, MENU_CHECKED,  NULL },
  { "",                -1,                0,             NULL },
  { "&Portrait",       M_ORIEN_PORTRAIT,  MENU_RCHECKED, NULL },
  { "&Landscape",      M_ORIEN_LANDSCAPE, 0,             NULL },
  { "&Upside-down",    M_ORIEN_UPDOWN,    0,             NULL },
  { "&Seascape",       M_ORIEN_SEASCAPE,  0,             NULL },
  { "",                -1,                0,             NULL },
  { "S&wap Landscape", M_ORIEN_SWAPLANDSCAPE, 0,         NULL },
  { NULL,              -1,                0,             NULL } } };

struct _popup view_popup = {
  { NULL }, {
  { "&Toolbar",        M_VIEW_TOOLBAR,     MENU_CHECKED,  NULL },
  { "Status &Bar",     M_VIEW_STATUSBAR,   MENU_CHECKED,  NULL },
  { "&Page Index",     M_VIEW_PAGEINDEX,   MENU_CHECKED | MENU_DISABLED,  NULL },
  { "",                -1,                 0,             NULL },
  { "Mag&step",        M_VIEW_MAGSTEP,     0,             &magstep_popup },
  { "&Orientation",    M_VIEW_ORIENTATION, 0,             &orien_popup },
  { "&Media",          M_VIEW_MEDIA,       0,             &media_popup },
  { "",                -1,                 0,             NULL },
  { "&Ghostscript output...", M_VIEW_OUTPUT,   0,             NULL },
  { NULL,              -1,                 0,             NULL } } };

struct _popup page_popup = {
  { NULL }, {
  { "&Next",           M_PAGE_NEXT,       MENU_DISABLED, NULL },
  { "&Previous",       M_PAGE_PREVIOUS,   MENU_DISABLED, NULL },
  { "&Redisplay",      M_PAGE_REDISPLAY,  MENU_DISABLED, NULL },
  { "",                -1,                0,             NULL },
  { "&Mark",           M_PAGE_MARK,       MENU_DISABLED, NULL },
  { "&Unmark",         M_PAGE_UNMARK,     MENU_DISABLED, NULL },
  { NULL,              -1,                0,             NULL } } };

struct _popup help_popup = {
  { NULL }, {
  { "&Contents...",    M_HELP_CONTENTS,   MENU_DISABLED, NULL },
  { "&Search...",      M_HELP_SEARCH,     MENU_DISABLED, NULL },
  { "",                -1,                0,             NULL },
  { "&About...",       M_HELP_ABOUT,      0,             NULL },
  { NULL,              -1,                0,             NULL } } };


struct _tb_data tb_data[] = {
  { "gtb-open.xpm",   gtb_open_xpm,   "Open File",         BUTTON_NORMAL, M_FILE_OPEN,  NULL },
  { "gtb-save.xpm",   gtb_save_xpm,   "Save Marked Pages", BUTTON_NORMAL, M_FILE_SAVEMARKED, NULL },
  { "gtb-print.xpm",  gtb_print_xpm,  "Print",             BUTTON_NORMAL, M_FILE_PRINT, NULL },
  { "",               NULL,           0,                   0,             -1,           NULL },
  { "gtb-prev.xpm",   gtb_prev_xpm,   "Previous Page",     BUTTON_NORMAL, M_PAGE_PREVIOUS, NULL },
  { "gtb-next.xpm",   gtb_next_xpm,   "Next Page",         BUTTON_NORMAL, M_PAGE_NEXT,  NULL },
  { "",               NULL,           0,                   0,             -1,           NULL },
  { "gtb-zoomin.xpm", gtb_zoomin_xpm, "Zoom In",           BUTTON_NORMAL, M_ZOOM_IN,    NULL },
  { "gtb-zoomout.xpm",gtb_zoomout_xpm,"Zoom Out",          BUTTON_NORMAL, M_ZOOM_OUT,   NULL },
  { NULL,             NULL,           NULL,                0,             0,            NULL }
};


OMimeTypes *MimeTypeList;  // currently we need this for OXFileDialog

char *filetypes[] = { "All files",        "*",
                      "Postscript files", "*.ps",
                      NULL,               NULL };

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

int main(int argc, char **argv) {
  char mimerc[PATH_MAX];

  OXClient clientX;

  sprintf(mimerc, "%s/.mime.types", getenv("HOME"));
  MimeTypeList = new OMimeTypes(&clientX, mimerc);

  OXMain mainWindow(clientX.GetRoot(), 10, 10);
  mainWindow.Resize(600, 400);

  if (argc > 1) mainWindow.OpenFile(argv[1]);

  mainWindow.MapWindow();

  clientX.Run();
}

OXMain::OXMain(const OXWindow *p, int w, int h) :
  OXMainFrame(p, w, h) {
  char tmp[BUFSIZ];
  const OPicture *cbgnd;

  _menuBarLayout = new OLayoutHints(LHINTS_TOP | LHINTS_LEFT | LHINTS_EXPAND_X, 
                                    0, 0, 1, 1);
  _menuBarItemLayout = new OLayoutHints(LHINTS_TOP | LHINTS_LEFT,
                                    0, 4, 0, 0);

  _magstepPopup = _MakePopup(&magstep_popup);
  _orienPopup   = _MakePopup(&orien_popup);
  _mediaPopup   = _MakePopup(&media_popup);

  _menuFile = _MakePopup(&file_popup);
  _menuView = _MakePopup(&view_popup);
  _menuPage = _MakePopup(&page_popup);
  _menuHelp = _MakePopup(&help_popup);

  _menuFile->Associate(this);
  _menuView->Associate(this);
  _menuPage->Associate(this);
  _menuHelp->Associate(this);
  _magstepPopup->Associate(this);
  _orienPopup->Associate(this);
  _mediaPopup->Associate(this);

  __build_pagemedia_menu();

  //------ menu bar

  _menuBar = new OXMenuBar(this, 1, 1, HORIZONTAL_FRAME);
  _menuBar->AddPopup(new OHotString("&File"), _menuFile, _menuBarItemLayout);
  _menuBar->AddPopup(new OHotString("&View"), _menuView, _menuBarItemLayout);
  _menuBar->AddPopup(new OHotString("&Page"), _menuPage, _menuBarItemLayout);
  _menuBar->AddPopup(new OHotString("&Help"), _menuHelp, _menuBarItemLayout);

  AddFrame(_menuBar, _menuBarLayout);

  //---- toolbar

  _toolBarSep = new OXHorizontal3dLine(this);

  _toolBar = new OXCompositeFrame(this, 60, 20, HORIZONTAL_FRAME);
  _InitToolBar();

  AddFrame(_toolBarSep, new OLayoutHints(LHINTS_TOP | LHINTS_EXPAND_X));
  AddFrame(_toolBar, new OLayoutHints(LHINTS_TOP | LHINTS_EXPAND_X, 
                                      0, 0, 3, 3));

  //------ horizontal frame containing _lb and _canvas

  _hf = new OXCompositeFrame(this, 10, 10, HORIZONTAL_FRAME);

  //------ page index (table of contents)

  _lb = new OXListBox(_hf, LB_PAGE, SUNKEN_FRAME | DOUBLE_BORDER | FIXED_WIDTH);
  _lb->Resize(54, 50);
  _lb->IntegralHeight(False);
  _lb->SetMultipleSelect(True);
  _lb->Associate(this);

  _hf->AddFrame(_lb, new OLayoutHints(LHINTS_LEFT | LHINTS_EXPAND_Y,
                     0, 3, 0, 0));

  _hf->HideFrame(_lb);

  //------ Canvas frame and ghostview widget

  _canvas = new OXCanvas(_hf, 10, 10, SUNKEN_FRAME | DOUBLE_BORDER);
  _hf->AddFrame(_canvas, new OLayoutHints(LHINTS_EXPAND_X | LHINTS_EXPAND_Y));

  _container = new OXCompositeFrame(_canvas->GetViewPort(), 640, 480,
                                    OWN_BKGND);
  _canvas->SetContainer(_container);

  cbgnd = _client->GetPicture("pbg.xpm", pbg_xpm);
  if (!cbgnd) FatalError("Bad or missing internal pixmap");
  _container->SetBackgroundPixmap(cbgnd->GetPicture());

  _gv = new OXGhostView(_container, 10, 10, GV_MAIN,
                        OWN_BKGND, _client->GetColorByName("white"));
  _container->AddFrame(_gv, new OLayoutHints(LHINTS_CENTER_X | LHINTS_CENTER_Y,
                                             5, 5, 5, 5));

  _gv->DisableInterpreter();
  _gv->Associate(this);

  AddFrame(_hf, new OLayoutHints(LHINTS_EXPAND_X | LHINTS_EXPAND_Y));

  //------ status bar

  _statusBar = new OXCompositeFrame(this, 60, 20, HORIZONTAL_FRAME | SUNKEN_FRAME);
  AddFrame(_statusBar, new OLayoutHints(LHINTS_BOTTOM | LHINTS_EXPAND_X,
                                     0, 0, 3, 0));

  _lstatus = new OXLabel(_statusBar, new OString(""));
  _statusBar->AddFrame(_lstatus, new OLayoutHints(LHINTS_TOP | LHINTS_LEFT,
                                               2, 0, 1, 1));

  SetWindowTitle(NULL);
  SetClassHints("XCGhostView", "XCGhostView");

  printerName = new char[strlen("lp0")+1];
  strcpy(printerName, "lp");
  printProg = new char[strlen("lpr")+1];
  strcpy(printProg, "lpr");

  MapSubwindows();
}

OXMain::~OXMain() {
  delete _gv;

  delete _statusBar;
  delete _lstatus;
  delete _canvas;
  delete _container;
  delete _toolBar;
  delete _toolBarSep;
  delete _hf;
 
  delete _menuBarLayout;
  delete _menuBarItemLayout;

  delete _menuFile;
  delete _menuView;
  delete _menuPage;
  delete _menuHelp;
  delete _mediaPopup;
  delete _orienPopup;
  delete _magstepPopup;
}

void OXMain::CloseWindow() {
  delete _gv;
  OXMainFrame::CloseWindow();
}

OXPopupMenu *OXMain::_MakePopup(struct _popup *p) {

  OXPopupMenu *popup = new OXPopupMenu(_client->GetRoot());

  for (int i=0; p->popup[i].name != NULL; ++i) {
    if (strlen(p->popup[i].name) == 0) {
      popup->AddSeparator();
    } else {
      if (p->popup[i].popup_ref == NULL) {
        popup->AddEntry(new OHotString(p->popup[i].name), p->popup[i].id);
      } else {
        struct _popup *p1 = p->popup[i].popup_ref;
        popup->AddPopup(new OHotString(p->popup[i].name), p1->ptr);
      }
      if (p->popup[i].state & MENU_DISABLED) popup->DisableEntry(p->popup[i].id);
      if (p->popup[i].state & MENU_CHECKED) popup->CheckEntry(p->popup[i].id);
      if (p->popup[i].state & MENU_RCHECKED) popup->RCheckEntry(p->popup[i].id,
                                                                p->popup[i].id,
                                                                p->popup[i].id);
    }
  }
  p->ptr = popup;

  return popup;
}

void OXMain::_InitToolBar() {
  int i, spacing = 0;
  OXPictureButton *button;
  const OPicture *buttonpic;    

  for (i=0; tb_data[i].pixmap_name != NULL; ++i) {
    if (strlen(tb_data[i].pixmap_name) == 0) {
      spacing = 8;
      continue;
    }

    buttonpic = _client->GetPicture(tb_data[i].pixmap_name,
                                    tb_data[i].pixmap_data);
    if (!buttonpic)
      FatalError("Bad or missing inlined pixmap: %s", tb_data[i].pixmap_name);

    button = new OXPictureButton(_toolBar, buttonpic, tb_data[i].id);
    _toolBar->AddFrame(button, new OLayoutHints(LHINTS_TOP | LHINTS_LEFT,
                                                spacing, 0, 0, 0));
    button->Associate(this);

    button->SetType(tb_data[i].type);
    button->SetTip(tb_data[i].tip_text);
    tb_data[i].button = button;
    spacing = 0;
  }
}

int OXMain::ProcessMessage(long msg, long parm1, long parm2) {

  switch (GET_MSG(msg)) {

    case XC_COMMAND:
      switch(GET_SUBMSG(msg)) {

        case CM_BUTTON:
        case CM_MENU:
          if (parm1 >= M_PAGEMEDIA) DoSetPageMedia(parm1);

          switch(parm1) {

            //--------------------------------------- File

            case M_FILE_OPEN:
              DoOpen();
              break;

            case M_FILE_REOPEN:
              DoReopen();
              break;

            case M_FILE_SAVEMARKED:
              DoSaveMarked();
              break;

            case M_FILE_PRINT:
              DoPrint();
              break;

            case M_FILE_EXIT:
              CloseWindow();
              break;

            //--------------------------------------- View

            case M_VIEW_OUTPUT:
              DoViewOutput();
              break;

            case M_VIEW_TOOLBAR:
              DoToggleToolBar();
              break;

            case M_VIEW_PAGEINDEX:
              DoTogglePageIndex();
              break;

            case M_VIEW_STATUSBAR:
              DoToggleStatusBar();
              break;

            //--------------------------------------- Page

            case M_PAGE_NEXT:
              DoPageNext();
              break;

            case M_PAGE_PREVIOUS:
              DoPagePrev();
              break;

            case M_PAGE_MARK:
              DoMark();
              break;

            case M_PAGE_UNMARK:
              DoUnmark();
              break;

            case M_ORIEN_DEFAULT:
              DoToggleDefaultOrientation();
              break;

            case M_ORIEN_PORTRAIT:
            case M_ORIEN_LANDSCAPE:
            case M_ORIEN_UPDOWN:
            case M_ORIEN_SEASCAPE:
              DoSetOrientation(parm1);
              break;

            case M_ORIEN_SWAPLANDSCAPE:
              DoSwapLandscape();
              break;

            case M_MAGSTEP_M5:
            case M_MAGSTEP_M4:
            case M_MAGSTEP_M3:
            case M_MAGSTEP_M2:
            case M_MAGSTEP_M1:
            case M_MAGSTEP_0:
            case M_MAGSTEP_P1:
            case M_MAGSTEP_P2:
            case M_MAGSTEP_P3:
            case M_MAGSTEP_P4:
            case M_MAGSTEP_P5:
              DoSetMagstep(parm1 - M_MAGSTEP_0);
              break;

            case M_ZOOM_IN:
              DoSetMagstep(mag_step+1);
              break;

            case M_ZOOM_OUT:
              DoSetMagstep(mag_step-1);
              break;

            default:
              break;

          } // switch(parm1)
          break;

        case CM_LISTBOX:
          switch(parm1) {
            case LB_PAGE:
              if (parm2 != current_page)
                __show_page(parm2); ///////
              break;

            default:
              break;
          }
          break;

        default:
          break;

      } // switch(GET_SUBMSG(msg))
      break;

    case XC_GHOSTVIEW:
      switch(GET_SUBMSG(msg)) {
        case GV_PAGE:
          break;

        case GV_DONE:
          break;

        case GV_REFRESH:
          DoRefresh();
          break;

        case GV_BADALLOC:
          ErrorMsg(MB_ICONASTERISK,
                   "Could not allocate backing pixmap in main window.");
          break;

        case GV_FAILED:
          ErrorMsg(MB_ICONSTOP,
                   "PostScript interpreter failed in main window");
          break;

        case GV_OUTPUT:
          DoViewOutput();
          if (output_dlg) {
            output_dlg->AddText(_gv->GetOutputBuffer());
            _gv->ClearOutputBuffer();
          }
          break;

        default:
          break;
      }
      break;

    default:
      break;

  } // switch(GET_MSG(msg))

  return True;
}

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

void OXMain::SetWindowTitle(char *title) {
  static char *pname = "fOX Ghostview";

  if (title) {
    char *wname = new char[strlen(title) + 20];
    sprintf(wname, "%s - %s", pname, title);
    SetWindowName(wname);
    delete wname;
  } else {
    SetWindowName(pname);
  }
}

void OXMain::UpdateStatus() {
  char tmp[1024];

  strcpy(tmp, (doc && doc->title) ? doc->title : "");
  _lstatus->SetText(new OString(tmp));
  _statusBar->Layout();
}

void OXMain::ErrorMsg(int icon_type, char *msg) {
  OString stitle((icon_type == MB_ICONSTOP) ? "Error" : "Warning");
  OString smsg(msg);
  new OXMsgBox(_client->GetRoot(), this, &stitle, new OString(&smsg),
               icon_type, ID_OK);
}

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

void OXMain::OpenFile(char *fname) {
  struct stat sbuf;
  char buf[BUFSIZ];
  FILE *fp;

  if (!fname) return;

  if ((fp = fopen(fname, "r")) == NULL) {
    fprintf(stderr, "Could not open input file: \"%s\"\n", fname);
    exit(1);
  } else {
    filename = new char[strlen(fname)+1];
    strcpy(filename, fname);
    if (*fname == '/') {
      full_filename = new char[strlen(fname)+1];
      strcpy(full_filename, fname);
    } else {
      getcwd(buf, BUFSIZ);
      full_filename = new char[strlen(buf)+strlen(fname)+2];
      sprintf(full_filename, "%s/%s", buf, fname);
    }
    //if (psfile) fclose(psfile);
    psfile = fp;
    stat(full_filename, &sbuf);
    mtime = sbuf.st_mtime;
    __new_file(0);
    SetWindowTitle(filename);
    UpdateStatus();
    __show_page(0);
  }
}

void OXMain::DoOpen() {
  OFileInfo fi;
  struct stat sbuf;
  FILE *fp;

  fi.MimeTypesList = MimeTypeList;
  fi.file_types = filetypes;
  new OXFileDialog(_client->GetRoot(), this, FDLG_OPEN, &fi);
  if (fi.filename) {
    if ((fp = fopen(fi.filename, "r")) == NULL) {
      ErrorMsg(MB_ICONSTOP, "Could not open file.");
    } else {
      if (filename) delete[] filename;
      if (full_filename) delete[] full_filename;
      filename = new char[strlen(fi.filename)+1];
      strcpy(filename, fi.filename);
      full_filename = new char[strlen(fi.ini_dir)+strlen(fi.filename)+2];
      sprintf(full_filename, "%s/%s", fi.ini_dir, fi.filename);
      if (psfile) fclose(psfile);
      psfile = fp;
      stat(full_filename, &sbuf);
      mtime = sbuf.st_mtime;
      __new_file(0);
      SetWindowTitle(filename);
      UpdateStatus();
      __show_page(0);
    }
  }
}

//--- Explicitly reopen the file.

void OXMain::DoReopen() {
  struct stat sbuf;
  int page = current_page;

  if (psfile) fclose(psfile);
  psfile = fopen(full_filename, "r");
  if (!psfile) {
    ErrorMsg(MB_ICONSTOP, "Could not reopen file.");
    return;
  }
  stat(full_filename, &sbuf);
  mtime = sbuf.st_mtime;
  __new_file(page);
  __show_page(page);
}


void OXMain::DoSaveMarked() {
  int retc;
  OFileInfo fi;
  FILE *pswrite;

  if (!__marked_pages()) {
    XBell(GetDisplay(), 0);
    return;
  }

  fi.MimeTypesList = MimeTypeList;
  fi.file_types = filetypes;
  new OXFileDialog(_client->GetRoot(), this, FDLG_SAVE, &fi);
  if (fi.filename) {

    // check whether the file already exists, and ask
    // permission to overwrite if so.

    if (access(fi.filename, F_OK) == 0) {
      new OXMsgBox(_client->GetRoot(), this,
            new OString("Save"),
            new OString("A file with the same name already exists. Overwrite?"),
            MB_ICONQUESTION, ID_YES | ID_NO, &retc);
      if (retc == ID_NO) return;
    }

    pswrite = fopen(fi.filename, "w");
    if (pswrite == NULL) {
      OString stitle("Write Error");
      ErrorMsg(MB_ICONSTOP, "Can't write postscript file.");
    } else {
      __write_doc(pswrite);
      fclose(pswrite);
    }

  }

}

void OXMain::DoPrint() {
  int  retc = PB_PRINTALL;
  char tmp[1024];
  int  whole_mode = False;

  if (!_menuFile->IsEntryEnabled(M_FILE_PRINT)) {
    XBell(GetDisplay(), 0);
    return;
  }

  if (doc && toc) {
    if (__marked_pages()) {
      whole_mode = True;
      retc |= PB_PRINTMARKED;
    }
  }

  new OXPrintBox(_client->GetRoot(), this, 400, 150, 
                 &printerName, &printProg, &retc);

  if (retc & PB_OK) {

    if (retc & PB_PRINTALL) whole_mode = True;

    sprintf(tmp, "%s -P%s", printProg, printerName);
    FILE *p = popen(tmp, "w");
    if (p == NULL) {
      ErrorMsg(MB_ICONSTOP, "Print command failed.");
      return;
    }

    if (toc && !whole_mode) {
      __write_doc(p);
    } else {
      FILE *psf = fopen(full_filename, "r");
      if (psf) {
        int  bytes;
        char buf[BUFSIZ];

        while (bytes = read(fileno(psf), buf, BUFSIZ))
          bytes = write(fileno(p), buf, bytes);
        fclose(psf);
      } else {
        ErrorMsg(MB_ICONSTOP, "Could not open source file.");
      }
    }

    if (pclose(p) != 0)
      ErrorMsg(MB_ICONSTOP, "Print command failed.");      

  }      

}

void OXMain::DoPageNext() {
  int new_page;

  if (has_toc && doc) {
    new_page = current_page + 1;
    if (new_page < doc->numpages)
      __show_page(new_page);
    else
      XBell(GetDisplay(), 0);
  } else {
    __show_page(0);
  }
}

void OXMain::DoPagePrev() {
  int new_page;

  if (has_toc) {
    new_page = current_page - 1;
    if (new_page >= 0)
      __show_page(new_page);
    else
      XBell(GetDisplay(), 0);
  }
}

void OXMain::DoToggleDefaultOrientation() {
  if (default_document_orientation) {
    default_document_orientation = False;
    _orienPopup->UnCheckEntry(M_ORIEN_DEFAULT);
  } else {
    default_document_orientation = True;
    _orienPopup->CheckEntry(M_ORIEN_DEFAULT);
  }
  __set_orientation(current_page);
}

void OXMain::DoSwapLandscape() {
  if (swap_landscape) {
    swap_landscape = False;
    _orienPopup->UnCheckEntry(M_ORIEN_SWAPLANDSCAPE);
  } else {
    swap_landscape = True;
    _orienPopup->CheckEntry(M_ORIEN_SWAPLANDSCAPE);
  }
  __show_page(current_page);
}

void OXMain::DoMark() {
  if (toc) {
    int i;

    for (i=0; i<doc->numpages; ++i)
      if (toc[i]->IsActive()) 
        toc[i]->SetMark(PLBE_CHECKMARK, True);
  }
  if (__marked_pages())
    _menuFile->EnableEntry(M_FILE_SAVEMARKED);
  else
    _menuFile->DisableEntry(M_FILE_SAVEMARKED);
}

void OXMain::DoUnmark() {
  if (toc) {
    int i;

    for (i=0; i<doc->numpages; ++i)
      if (toc[i]->IsActive())
        toc[i]->SetMark(PLBE_CHECKMARK, False);
  }
  if (__marked_pages())
    _menuFile->EnableEntry(M_FILE_SAVEMARKED);
  else
    _menuFile->DisableEntry(M_FILE_SAVEMARKED);
}

void OXMain::DoSetMagstep(int mstep) {
  float xdpi, ydpi;

  if (mstep == mag_step) return;

  if (mstep < -5 || mstep > 5) {
    XBell(GetDisplay(), 0);
    return;
  }

  mag_step = mstep;

  xdpi = _gv->ComputeXdpi();
  ydpi = _gv->ComputeYdpi();

  if (mstep < 0) {
    while (mstep++) xdpi /= 1.2, ydpi /= 1.2;
  } else {
    while (mstep--) xdpi *= 1.2, ydpi *= 1.2;
  }

  _magstepPopup->RCheckEntry(mag_step+M_MAGSTEP_0, 
                             M_MAGSTEP_M5, M_MAGSTEP_P5);
  if (_gv->SetDpi(xdpi, ydpi)) {
    _canvas->Layout();
  }
}

void OXMain::DoSetOrientation(int orien) {
  default_document_orientation = False;
  _orienPopup->UnCheckEntry(M_ORIEN_DEFAULT);
  switch (orien) {
    default:
    case M_ORIEN_PORTRAIT:
      current_orientation = PageOrientationPortrait;
      break;

    case M_ORIEN_LANDSCAPE:
      current_orientation = PageOrientationLandscape;
      break;

    case M_ORIEN_UPDOWN:
      current_orientation = PageOrientationUpsideDown;
      break;

    case M_ORIEN_SEASCAPE:
      current_orientation = PageOrientationSeascape;
      break;
  }
  __set_orientation(current_page);
}

void OXMain::DoSetPageMedia(int media) {
  int n = media - M_PAGEMEDIA;

  default_document_pagemedia = False;
  __set_pagemedia(current_page, n);
}

void OXMain::DoRefresh() {
  int i;

  if (has_toc && doc && doc->pages) {
    _gv->SendPS(psfile, doc->beginprolog,
                        doc->lenprolog, False);
    _gv->SendPS(psfile, doc->beginsetup,
                        doc->lensetup, False);
    if (doc->pageorder == DESCEND)
      i = (doc->numpages - 1) - current_page;
    else
      i = current_page;
    _gv->SendPS(psfile, doc->pages[i].begin,
                        doc->pages[i].len, False);
  }
}

void OXMain::DoToggleToolBar() {
  if (GetState(_toolBar)) {
    HideFrame(_toolBar);
    HideFrame(_toolBarSep);
    _menuView->UnCheckEntry(M_VIEW_TOOLBAR);
  } else {
    ShowFrame(_toolBar);
    ShowFrame(_toolBarSep);
    _menuView->CheckEntry(M_VIEW_TOOLBAR);
  }
}

void OXMain::DoTogglePageIndex() {
  if (_menuView->IsEntryChecked(M_VIEW_PAGEINDEX)) {
    _hf->HideFrame(_lb);
    _menuView->UnCheckEntry(M_VIEW_PAGEINDEX);
  } else {
    _hf->ShowFrame(_lb);
    _menuView->CheckEntry(M_VIEW_PAGEINDEX);
  }
}

void OXMain::DoToggleStatusBar() {
  if (GetState(_statusBar)) {
    HideFrame(_statusBar);
    _menuView->UnCheckEntry(M_VIEW_STATUSBAR);
  } else {
    ShowFrame(_statusBar);
    _menuView->CheckEntry(M_VIEW_STATUSBAR);
  }
}

void OXMain::DoViewOutput() {
  if (output_dlg) {
    output_dlg->RaiseWindow();
  } else {
    output_dlg = new OXOutput(_client->GetRoot(), this, 100, 100);
  }
}

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

//  Setup ghostview. This includes:
//    - scanning the PostScript file,
//    - setting the status bar label,
//    - building the pagemedia menu,
//    - building the table of contents,
//    - sensitizing the appropriate menu buttons,
//    - erasing the stdout popup.

void OXMain::__setup_gv() {

  //--- reset to a known state.

  psfree(doc);
  current_page = -1;
  _gv->DisableInterpreter();
  if (psfile) doc = psscan(psfile);

  default_document_pagemedia = True;
  current_pagemedia = 0;

  __build_pagemedia_menu();

  //--- build table of contents

  _lb->RemoveAllEntries();
  if (toc) delete[] toc;
  toc = NULL;

  if (doc && (!doc->epsf && doc->numpages > 0 ||
               doc->epsf && doc->numpages > 1)) {
    int maxlen = 0;
    int i, j;
    int useful_page_labels = False;
    char tmp[100];

    if (doc->numpages == 1) useful_page_labels = True;

    for (i=1; i<doc->numpages; i++)
      if (useful_page_labels = (useful_page_labels ||
          strcmp(doc->pages[i-1].label, doc->pages[i].label))) break;

    if (useful_page_labels) {
      for (i=0; i<doc->numpages; i++) 
        maxlen = max(maxlen, strlen(doc->pages[i].label));
    } else {
      //double x;
      //x = doc->numpages;
      maxlen = 5; //log10(x) + 1;  // should resize _lb accordingly...
    }

    toc = new OXPageLBEntry*[doc->numpages];

    _lb->AutoUpdate(False);

    for (i=0; i<doc->numpages; i++) {
      if (useful_page_labels) {
        if (doc->pageorder == DESCEND) {
          j = (doc->numpages - 1) - i;
        } else {
          j = i;
        }
        sprintf(tmp, " %*s ", maxlen, doc->pages[j].label);
      } else {
        sprintf(tmp, " %*d ", maxlen, i+1);
      }
      toc[i] = new OXPageLBEntry(_lb->GetContainer(), new OString(tmp), i);
      _lb->AddEntry(toc[i], new OLayoutHints(LHINTS_EXPAND_X | LHINTS_TOP));
    }
    _lb->AutoUpdate(True);

    _gv->SetFilename(NULL);

    if (_menuView->IsEntryChecked(M_VIEW_PAGEINDEX)) _hf->ShowFrame(_lb);
    _menuView->EnableEntry(M_VIEW_PAGEINDEX);
    _menuPage->EnableEntry(M_PAGE_PREVIOUS);
    _menuPage->EnableEntry(M_PAGE_MARK);
    _menuPage->EnableEntry(M_PAGE_UNMARK);
    _menuPage->EnableEntry(M_PAGE_REDISPLAY);

    has_toc = True;

  } else {

    _gv->SetFilename(full_filename);
    _hf->HideFrame(_lb);
    _menuView->DisableEntry(M_VIEW_PAGEINDEX);
    _menuPage->DisableEntry(M_PAGE_PREVIOUS);
    _menuPage->DisableEntry(M_PAGE_MARK);
    _menuPage->DisableEntry(M_PAGE_UNMARK);
    _menuPage->DisableEntry(M_PAGE_REDISPLAY);

    has_toc = False;

  }

  //_lb->MapSubwindows();
  _lb->Update();
  _hf->Layout();

  if (psfile) {
    _menuFile->EnableEntry(M_FILE_REOPEN);
    _menuFile->EnableEntry(M_FILE_PRINT);
    _menuPage->EnableEntry(M_PAGE_NEXT);
  } else {
    _menuFile->DisableEntry(M_FILE_REOPEN);
    _menuFile->DisableEntry(M_FILE_PRINT);
    _menuPage->DisableEntry(M_PAGE_NEXT);
  }
  _menuFile->DisableEntry(M_FILE_SAVEMARKED);

  UpdateStatus();

  if (output_dlg) output_dlg->Clear();
}

void OXMain::__new_file(int n) {
  __setup_gv();
}

void OXMain::__show_page(int n) {
  struct stat sbuf;
  int i;

  if (!filename) return;

  // If the file has changed, rescan it so that offsets into the file
  // are still correct. If the file is rescanned, we must setup ghostview
  // again. Also, force a new copy of ghostscript to start.
  if (psfile) {
    if (!stat(full_filename, &sbuf) && mtime != sbuf.st_mtime) {
      fclose(psfile);
      psfile = fopen(full_filename, "r");
      if (!psfile) {
        ErrorMsg(MB_ICONSTOP, "Could not reopen file.");
        return;
      }
      mtime = sbuf.st_mtime;
      __new_file(n);
    }
  }

  // coerce page number to fall in range
  if (has_toc) {
    if (n >= doc->numpages) n = doc->numpages - 1;
    if (n < 0) n = 0;
  }

  __set_orientation(n);
  __set_pagemedia(n);

  if (has_toc && doc && doc->pages) {
    OXPageLBEntry *e;

    e = (OXPageLBEntry *) _lb->FindEntry(current_page);
    if (e) e->SetMark(PLBE_RADIOMARK, False);

    current_page = n;
    _gv->EnableInterpreter();
    _gv->SendPS(psfile, doc->beginprolog, doc->lenprolog, False);
    _gv->SendPS(psfile, doc->beginsetup, doc->lensetup, False);
    if (doc->pageorder == DESCEND) {
      i = (doc->numpages-1)-current_page;
    } else {
      i = current_page;
    }

    _gv->SendPS(psfile, doc->pages[i].begin, doc->pages[i].len, False);
    e = (OXPageLBEntry *) _lb->Select(current_page);
    if (e) e->SetMark(PLBE_RADIOMARK, True);

    _menuPage->EnableEntry(M_PAGE_REDISPLAY);

    if (current_page != 0)
      _menuPage->EnableEntry(M_PAGE_PREVIOUS);
    else
      _menuPage->DisableEntry(M_PAGE_PREVIOUS);

    if (current_page != doc->numpages-1)
      _menuPage->EnableEntry(M_PAGE_NEXT);
    else
      _menuPage->DisableEntry(M_PAGE_NEXT);

  } else {
    if (!_gv->IsInterpreterRunning())
      _gv->EnableInterpreter();
    else if (_gv->IsInterpreterReady())
      _gv->NextPage();
    else
      XBell(GetDisplay(), 0);
  }
}

// Build the Page Media menu, it has two parts:
//  - the document defined page medias
//  - the standard page media defined from Adobe's PDD

void OXMain::__build_pagemedia_menu() {
  int i;

  _mediaPopup->RemoveAllEntries();

  base_papersize = 0;
  if (doc) base_papersize = doc->nummedia;

  if (doc && doc->nummedia) {
    for (i=0; i<doc->nummedia; ++i) {
      _mediaPopup->AddEntry(new OHotString(doc->media[i].name),
                            M_PAGEMEDIA + i);
    }
    _mediaPopup->AddSeparator();
  }
  for (i=0; papersizes[i].name; ++i) {
    if (i > 0) {
      // skip over same paper size with small imageable area
      if ((papersizes[i].width  == papersizes[i-1].width)  &&
          (papersizes[i].height == papersizes[i-1].height))
        continue;
    }
    _mediaPopup->AddEntry(new OHotString(papersizes[i].name),
                          M_PAGEMEDIA + i+base_papersize);
  }
  last_pagemedia_entry =  M_PAGEMEDIA + i+base_papersize -1;
}

void OXMain::__set_orientation(int n) {
  int psorient;
  XCPageOrientation gvorient;

  psorient = NONE;
  if (default_document_orientation) {
    if (doc) {
      if (doc->pages) psorient = doc->pages[n].orientation;
      if (psorient == NONE) psorient = doc->default_page_orientation;
      if (psorient == NONE) psorient = doc->orientation;
    }
  }

  switch (psorient) {
    default:
    case PORTRAIT:
      gvorient = PageOrientationPortrait;
      break;

    case LANDSCAPE:
      gvorient = swap_landscape ? PageOrientationSeascape :
                                  PageOrientationLandscape;
      break;

    case NONE:
      gvorient = current_orientation;
      break;
  }

  current_orientation = gvorient;

  if (_gv->SetOrientation(gvorient)) {
    _canvas->Layout();
  }

  switch (current_orientation) {
    default:
    case PageOrientationPortrait:
      psorient = M_ORIEN_PORTRAIT;
      break;

    case PageOrientationLandscape:
      psorient = M_ORIEN_LANDSCAPE;
      break;

    case PageOrientationUpsideDown:
      psorient = M_ORIEN_UPDOWN;
      break;

    case PageOrientationSeascape:
      psorient = M_ORIEN_SEASCAPE;
      break;
  }
  _orienPopup->RCheckEntry(psorient,
                           M_ORIEN_PORTRAIT,
                           M_ORIEN_SEASCAPE);
}

void OXMain::__set_pagemedia(int n, int media) {
  int llx, lly, urx, ury;
  int new_pagemedia;

  if (doc && doc->epsf &&
      (doc->boundingbox[URX] > doc->boundingbox[LLX]) &&
      (doc->boundingbox[URY] > doc->boundingbox[LLY])) {

    llx = doc->boundingbox[LLX];
    lly = doc->boundingbox[LLY];
    urx = doc->boundingbox[URX];
    ury = doc->boundingbox[URY];

  } else {

    if (media > 0) {
      new_pagemedia = media;
    } else if (default_document_pagemedia &&
               doc && doc->pages && doc->pages[n].media) {
      new_pagemedia = doc->pages[n].media - doc->media;
    } else if (default_document_pagemedia &&
               doc && doc->default_page_media) {
      new_pagemedia = doc->default_page_media - doc->media;
    } else {
      new_pagemedia = current_pagemedia;
    }

    _mediaPopup->RCheckEntry(M_PAGEMEDIA+new_pagemedia,
                             M_PAGEMEDIA,
                             last_pagemedia_entry);

    current_pagemedia = new_pagemedia;

    llx = lly = 0;
    if (doc && doc->media && new_pagemedia < base_papersize) {
      urx = doc->media[new_pagemedia].width;
      ury = doc->media[new_pagemedia].height;
    } else {
      urx = papersizes[new_pagemedia-base_papersize].width;
      ury = papersizes[new_pagemedia-base_papersize].height;
    }
  }

  if (_gv->SetBoundingBox(llx, lly, urx, ury)) {
    _canvas->Layout();
  }
}

//--- Count marked pages

int OXMain::__marked_pages() {
  int i, pages;

  if (!toc || !doc) return 0;

  for (pages=0, i=0; i<doc->numpages; i++)
    if (toc[i]->GetFlags() & PLBE_CHECKMARK) pages++;

  return pages;
}

// length calculates string length at compile time
// can only be used with character constants
#define length(a) (sizeof(a)-1)

//--- Write the headers, marked pages, and trailer to the
//    specified file

void OXMain::__write_doc(FILE *fp) {
  FILE *psfile;
  char text[PSLINELENGTH];
  char *comment;
  int pages_written = False;
  int pages_atend = False;
  int pages = 0;
  int page = 1;
  int i, j;
  long here;

  if (!toc) return;

  pages = __marked_pages();

  if (!pages) return;

  psfile = fopen(full_filename, "r");
  if (!psfile) return;

  here = doc->beginheader;
  while (comment = pscopyuntil(psfile, fp, here,
                               doc->endheader, "%%Pages:")) {
    here = ftell(psfile);
    if (pages_written || pages_atend) {
      free(comment);
      continue;
    }
    sscanf(comment+length("%%Pages:"), "%s", text);
    if (strcmp(text, "(atend)") == 0) {
      fputs(comment, fp);
      pages_atend = True;
    } else {
      switch (sscanf(comment+length("%%Pages:"), "%*d %d", &i)) {
        case 1:
          fprintf(fp, "%%%%Pages: %d %d\n", pages, i);
          break;
        default:
          fprintf(fp, "%%%%Pages: %d\n", pages);
          break;
      }
      pages_written = True;
    }
    free(comment);
  }
  pscopy(psfile, fp, doc->beginpreview, doc->endpreview);
  pscopy(psfile, fp, doc->begindefaults, doc->enddefaults);
  pscopy(psfile, fp, doc->beginprolog, doc->endprolog);
  pscopy(psfile, fp, doc->beginsetup, doc->endsetup);

  for (i=0; i<doc->numpages; i++) {
    if (doc->pageorder == DESCEND) 
      j = (doc->numpages - 1) - i;
    else
      j = i;
    if (toc[j]->GetFlags() & PLBE_CHECKMARK) {
      comment = pscopyuntil(psfile, fp, doc->pages[i].begin,
                            doc->pages[i].end, "%%Page:");
      fprintf(fp, "%%%%Page: %s %d\n",
                  doc->pages[i].label, page++);
      free(comment);
      pscopy(psfile, fp, -1, doc->pages[i].end);
    }
  }

  here = doc->begintrailer;
  while (comment = pscopyuntil(psfile, fp, here,
                               doc->endtrailer, "%%Pages:")) {
    here = ftell(psfile);
    if (pages_written) {
      free(comment);
      continue;
    }
    switch (sscanf(comment+length("%%Pages:"), "%*d %d", &i)) {
      case 1:
        fprintf(fp, "%%%%Pages: %d %d\n", pages, i);
        break;
      default:
        fprintf(fp, "%%%%Pages: %d\n", pages);
        break;
    }
    pages_written = True;
    free(comment);
  }

  fclose(psfile);
}
