/* 
 * XEvil(TM) Copyright (C) 1994,2000 Steve Hardt and Michael Judge
 * http://www.xevil.com
 * satan@xevil.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program, the file "gpl.txt"; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA, or visit http://www.gnu.org.
 */

// "viewport.cpp"

#ifndef NO_PRAGMAS
#pragma implementation "viewport.h"
#endif


#include "utils.h"
#include "xdata.h"
#include "coord.h"
#include "viewport.h"
#include "game_style.h"
#include "physical.h"
#include "locator.h"
#include "role.h"


extern "C" {
#include <X11/Xutil.h>
#ifdef OPENWOUND_XOS_STRLEN_HACK
#define _strings_h
#endif
#include <X11/Xos.h>
#include <X11/keysym.h>
}
#include <strstream.h>


#define TICK_BORDER_WIDTH 5 // border width big enough for a tick mark.
#define TICK_WIDTH 3
#define ARENA_BORDER_COLOR "black"
#define SMOOTH_BORDER_WIDTH 2  // Just for decoration.


#define MENU_BG_COLOR "gray80" 

// For viewports with no object.
#define ROW_SHIFT 5
#define COL_SHIFT 4

// Number of characters across for each menu button.
#define QUIT_LINE_LENGTH 5
#define NEW_GAME_LINE_LENGTH 9
#define HUMANS_NUM_LINE_LENGTH 9
#define ENEMIES_NUM_LINE_LENGTH 11
#define ENEMIES_REFILL_LINE_LENGTH 13
#define CONTROLS_LINE_LENGTH 13
#define LEARN_CONTROLS_LINE_LENGTH 12
#define STYLE_LINE_LENGTH 11
#define SCENARIOS_LINE_LENGTH 9
#define LEVELS_LINE_LENGTH 6
#define KILL_LINE_LENGTH 16
#define DUEL_LINE_LENGTH 4
#define EXTENDED_LINE_LENGTH 13
#define TRAINING_LINE_LENGTH 8
#define QUANTA_LINE_LENGTH 13
#define COOPERATIVE_LINE_LENGTH 11
#define HELP_LINE_LENGTH 4


// How far on top and sides viewport extends around the room.
#define SM_EXTRA_COL 2
#define SM_EXTRA_ROW 0
#define LG_EXTRA_COL 2
#define LG_EXTRA_ROW 0

// The inner dim, add LG_EXTRA_COL and LG_EXTRA_ROW to get the
// actual size of the Viewport.
#define LG_COL_MAX 26
#define LG_ROW_MAX 16
#define SM_COL_MAX 30
#define SM_ROW_MAX 16

#define SM_STATUS_LINE_LENGTH 25
#define LG_INTELS_LINE_LENGTH 11
#define LG_LEVEL_LINE_LENGTH SM_STATUS_LINE_LENGTH

// Arena message will be displayed this distance above
// the center of the screen.  This is so the text is not
// right on top of the player.
#define ARENA_MESSAGE_OFF_CENTER 25 // 40



enum {statusName,statusClassName,statusHealth,statusMass,
      statusWeapon,statusItem,statusLivesHKills,statusKillsMKills};



struct PanelClosure {
  Boolean radio;
  Viewport* viewport;
  // Callback passed into Viewport constructor. 
  ViewportCallback callback;
};



KeyState::KeyState() {
  for (int n = 0; n < UI_KEYS_MAX; n++) {
    isDown[n] = False;
  }
}



class VInfoProvider: public IViewportInfo {
public:
  VInfoProvider() {vInfo = NULL;}
  ~VInfoProvider() {delete vInfo;}

  void set_viewport_info(const ViewportInfo& v);
  /* EFFECTS: When the information is available, use this. */

  virtual ViewportInfo get_info();


private:
  ViewportInfo* vInfo;
};



void VInfoProvider::set_viewport_info(const ViewportInfo& v) {
  vInfo = new ViewportInfo(v); 
  assert(vInfo);
}



ViewportInfo VInfoProvider::get_info() {
  // If this assert happens, it means that Viewport::init_viewport_info()
  // was not called early enough.
  assert(vInfo);

  return *vInfo;
}



class BorderTickRenderer: public ITickRenderer {
public:
  BorderTickRenderer(Drawable outer,Xvars&,int dpyNum,
                     const Size& outerSize);

  ~BorderTickRenderer();

  virtual void begin_draw(int dpyNum);
  /* EFFECTS: Clear out the border buffers. */

  virtual void end_draw(int dpyNum);
  /* EFFECTS: Draw the border buffers to window. */

  virtual Boolean draw_tick(TickType tt,CMN_DRAWABLE window,
                            Xvars &xvars,int dpyNum,
                            const Size &arenaSize,
                            Dir tickDir,int offset);
  /* EFFECTS: Record one tick mark. */


private:
  static void init_x(Xvars&,int dpyNum,const Size& outerSize);
  /* EFFECTS: Initialize the static graphics data for one display.
     Can only do one at a time since we need outerSize to init
     borderBuffers. */

  Boolean dir_to_border_or_corner(int &val,Dir);
  /* MODIFIES: val */
  /* REQUIRES: Dir is one of the 8 cardinal directions. */
  /* EFFECTS: Convert dir to an enumerated border or corner.  Return True
     if border, False if corner. */

  enum {BORDER_R,BORDER_DN,BORDER_L,BORDER_UP,BORDER_MAX};
  enum {CORNER_DN_R,CORNER_DN_L,CORNER_UP_L,CORNER_UP_R,CORNER_MAX};

  Drawable outer;
  Xvars& xvars;
  int dpyNum;
  Size outerSize;

  // corners and borderBuffers should more appropriately be non-static,
  // since they are really specific to a single Renderer.  But, we share
  // the buffers for all the renderers on a display as an optimization.
  // This means that we can't have draw_tick() calls interleaved for
  // different renderers on the same display.  Never happens anyway.

  // Static data.
  static XvarsValid xValid[Xvars::DISPLAYS_MAX];
  // The rectangular areas corresponding to each border/corner.
  static Area borderAreas[Xvars::DISPLAYS_MAX][BORDER_MAX];
  static Area cornerAreas[Xvars::DISPLAYS_MAX][CORNER_MAX];
  // Rectangles of width ARENA_BORDER_COLOR.  Small back-buffers for
  // draw_tick().
  static Drawable borderBuffers[Xvars::DISPLAYS_MAX][BORDER_MAX];
  // Most recently drawn color for corner.
  static Pixel corners[Xvars::DISPLAYS_MAX][CORNER_MAX];
  static Pixel tickColors[Xvars::DISPLAYS_MAX][TICK_MAX];
  static Pixel background[Xvars::DISPLAYS_MAX];
  static const char* tickColorNames[TICK_MAX];
};



BorderTickRenderer::BorderTickRenderer(Drawable oter,Xvars& x_vars,
                                       int dpy_num,
                                       const Size& outer_size)
: outer(oter), 
  xvars(x_vars),
  dpyNum(dpy_num),
  outerSize(outer_size) {

  if (!xvars.is_valid(xValid[dpyNum])) {
    init_x(xvars,dpyNum,outerSize);
  }
}



BorderTickRenderer::~BorderTickRenderer() {
}



void BorderTickRenderer::begin_draw(int dpyNum) {
  // Erase the corners.
  for (int corner = 0; corner < CORNER_MAX; corner++) {
    corners[dpyNum][corner] = background[dpyNum];
  }

  // Erase the borderBuffers.
  XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],background[dpyNum]);
  for (int border = 0; border < BORDER_MAX; border++) {
    Size borderSize = borderAreas[dpyNum][border].get_size();
    XFillRectangle(xvars.dpy[dpyNum],borderBuffers[dpyNum][border],
                   xvars.gc[dpyNum],
                   0,0,borderSize.width,borderSize.height);
  }
  XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],xvars.black[dpyNum]);
}



void BorderTickRenderer::end_draw(int dpyNum) {
  // Whack corners onto window.
  for (int corner = 0; corner < CORNER_MAX; corner++) {
    XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],
                   corners[dpyNum][corner]);
    Pos pos = cornerAreas[dpyNum][corner].get_pos();
    Size size = cornerAreas[dpyNum][corner].get_size();
    XFillRectangle(xvars.dpy[dpyNum],outer,xvars.gc[dpyNum],
                   pos.x,pos.y,size.width,size.height);
    // Revert GC to former state.
    XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],xvars.black[dpyNum]);
  }

  // Whack borderBuffers onto window.
  for (int border = 0; border < BORDER_MAX; border++) {
    Pos pos = borderAreas[dpyNum][border].get_pos();
    Size size = borderAreas[dpyNum][border].get_size();
    XCopyArea(xvars.dpy[dpyNum],borderBuffers[dpyNum][border],outer,
              xvars.gc[dpyNum],
              0,0,size.width,size.height,
              pos.x,pos.y);
  }
}



Boolean BorderTickRenderer::draw_tick(TickType tt,CMN_DRAWABLE window,
                                      Xvars &,int dpyNum,
                                      const Size &arenaSize,
                                      Dir tickDir,int offset) {
  // Sanity check on the sizes passed around.
  assert((arenaSize.width == outerSize.width - 2 * TICK_BORDER_WIDTH) &&
         (arenaSize.height == outerSize.height - 2 * TICK_BORDER_WIDTH));
  assert(window == outer);

  int val;
  Boolean isBorder = dir_to_border_or_corner(val,tickDir);
  if (!isBorder) {
    // Set the value for the given corner.
    corners[dpyNum][val] = tickColors[dpyNum][tt];
  }
  else {
    // Fill in a small line on the apropriate borderBuffer.

    int offset2 = offset - TICK_BORDER_WIDTH - (TICK_WIDTH >> 1);
    XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],
                   tickColors[dpyNum][tt]);
    switch (val) {
    case BORDER_R:
    case BORDER_L:
      XFillRectangle(xvars.dpy[dpyNum],borderBuffers[dpyNum][val],
                     xvars.gc[dpyNum],
                     0,offset2,TICK_BORDER_WIDTH,TICK_WIDTH);
      break;
    case BORDER_DN:
    case BORDER_UP:
      XFillRectangle(xvars.dpy[dpyNum],borderBuffers[dpyNum][val],
                     xvars.gc[dpyNum],
                     offset2,0,TICK_WIDTH,TICK_BORDER_WIDTH);
      break;
    default:
      assert(0);
    }
    XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],
                   xvars.black[dpyNum]);
  }
  return True;
}



void BorderTickRenderer::init_x(Xvars& xvars,int dpyNum,
                                const Size& outerSize) {
  assert(!xvars.is_valid(xValid[dpyNum]));
  // Static initialization of xValid requires this to be true.
  //  assert(XVARS_VALID_INIT == 0);


  // Allocate colors.
  background[dpyNum] = xvars.alloc_named_color(dpyNum,ARENA_BORDER_COLOR);
  for (TickType tt = 0; tt < TICK_MAX; tt++) {
    tickColors[dpyNum][tt] = 
      xvars.alloc_named_color(dpyNum,tickColorNames[tt]);
  }


  // Compute areas of each border/corner.
  // Should have some helper in area to create areas from internal
  // touching dirs, or something like that.
  {
    Pos pos(outerSize.width - TICK_BORDER_WIDTH,
            TICK_BORDER_WIDTH);
    Size size;
    size.set(TICK_BORDER_WIDTH,
             outerSize.height - 2 * TICK_BORDER_WIDTH);
    Area area(pos,size);
    borderAreas[dpyNum][BORDER_R] = area;
  }
  {
    Pos pos(TICK_BORDER_WIDTH,
            outerSize.height - TICK_BORDER_WIDTH);
    Size size;
    size.set(outerSize.width - 2 * TICK_BORDER_WIDTH,
             TICK_BORDER_WIDTH);
    Area area(pos,size);
    borderAreas[dpyNum][BORDER_DN] = area;
  }
  {
    Pos pos(0,
            TICK_BORDER_WIDTH);
    Size size;
    size.set(TICK_BORDER_WIDTH,
             outerSize.height - 2 * TICK_BORDER_WIDTH);
    Area area(pos,size);
    borderAreas[dpyNum][BORDER_L] = area;
  }
  {
    Pos pos(TICK_BORDER_WIDTH,
            0);
    Size size;
    size.set(outerSize.width - 2 * TICK_BORDER_WIDTH,
             TICK_BORDER_WIDTH);
    Area area(pos,size);
    borderAreas[dpyNum][BORDER_UP] = area;
  }
  // Now corners.
  Size cornerSize;
  cornerSize.set(TICK_BORDER_WIDTH,TICK_BORDER_WIDTH);
  {
    Pos pos(outerSize.width - TICK_BORDER_WIDTH,
            outerSize.height - TICK_BORDER_WIDTH);
    Area area(pos,cornerSize);
    cornerAreas[dpyNum][CORNER_DN_R] = area;
  }
  {
    Pos pos(0,
            outerSize.height - TICK_BORDER_WIDTH);
    Area area(pos,cornerSize);
    cornerAreas[dpyNum][CORNER_DN_L] = area;
  }
  {
    Pos pos(0,
            0);
    Area area(pos,cornerSize);
    cornerAreas[dpyNum][CORNER_UP_L] = area;
  }
  {
    Pos pos(outerSize.width - TICK_BORDER_WIDTH,
            0);
    Area area(pos,cornerSize);
    cornerAreas[dpyNum][CORNER_UP_R] = area;
  }


  // Create borderBuffers.
  for (int border = 0; border < BORDER_MAX; border++) {
    Size size = borderAreas[dpyNum][border].get_size();
    borderBuffers[dpyNum][border] = 
      XCreatePixmap(xvars.dpy[dpyNum],xvars.root[dpyNum],
                    size.width,size.height,
                    xvars.depth[dpyNum]);
  }


  xvars.mark_valid(xValid[dpyNum]);
}



Boolean BorderTickRenderer::dir_to_border_or_corner(int& val,Dir dir) {
  switch (dir) {
  case CO_R:
    val = BORDER_R;
    return True;
  case CO_DN_R:
    val = CORNER_DN_R;
    return False;
  case CO_DN:
    val = BORDER_DN;
    return True;
  case CO_DN_L:
    val = CORNER_DN_L;
    return False;
  case CO_L:
    val = BORDER_L;
    return True;
  case CO_UP_L:
    val = CORNER_UP_L;
    return False;
  case CO_UP:
    val = BORDER_UP;
    return True;
  case CO_UP_R:
    val = CORNER_UP_R;
    return False;
  default:
    assert(0);
  }
  return False;;
}



XvarsValid BorderTickRenderer::xValid[Xvars::DISPLAYS_MAX] = {
  0,
  //  XVARS_VALID_INIT,
  // Assuming that XVARS_VALID_INIT == 0, this will automatically
  // initialize the rest of the values.
};



Area BorderTickRenderer::borderAreas[Xvars::DISPLAYS_MAX][BORDER_MAX];



Area BorderTickRenderer::cornerAreas[Xvars::DISPLAYS_MAX][CORNER_MAX];



Drawable BorderTickRenderer::borderBuffers[Xvars::DISPLAYS_MAX][BORDER_MAX];



Pixel BorderTickRenderer::corners[Xvars::DISPLAYS_MAX][CORNER_MAX];



Pixel BorderTickRenderer::tickColors[Xvars::DISPLAYS_MAX][TICK_MAX];



Pixel BorderTickRenderer::background[Xvars::DISPLAYS_MAX];



const char* BorderTickRenderer::tickColorNames[TICK_MAX] = {
  "yellow",
  "green",
  "red",
  "blue",
};



// Would be better to put this and draw_string() inside of Xvars.
// Size is window coordinates.
void Viewport_draw_string_center(Xvars &xvars,int dpyNum,
                                 Drawable drawable,const Size& size,
                                 const char *msg) {
  assert(msg);  

  Size textSize;
  textSize.height = xvars.bigFont[dpyNum]->max_bounds.ascent 
    + xvars.bigFont[dpyNum]->max_bounds.descent;
  // get width
  if (xvars.bigFont[dpyNum]->per_char) {
    // variable width, go over all chars.
    textSize.width = 0;
    const char *p = msg;
    while (*p) {
      int index = *p -  xvars.bigFont[dpyNum]->min_char_or_byte2;
      textSize.width +=  xvars.bigFont[dpyNum]->per_char[index].width;
      p++;
    }
  }
  else {
    // Fixed witdth font, just multiply
    textSize.width = xvars.bigFont[dpyNum]->max_bounds.width * strlen(msg);
  }
  
  // Draw in center of the arena.
  Pos pos((size.width - textSize.width) / 2,
          (size.height  - textSize.height) / 2
          - xvars.stretch_y(ARENA_MESSAGE_OFF_CENTER));

  // Draw on black background in big font.
  XSetFont(xvars.dpy[dpyNum],xvars.gc[dpyNum],
           xvars.bigFont[dpyNum]->fid);
  XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],
                 xvars.black[dpyNum]);
  XDrawString(xvars.dpy[dpyNum],drawable,
              xvars.gc[dpyNum],
              pos.x,pos.y + xvars.bigFont[dpyNum]->max_bounds.ascent,
              msg,strlen(msg));
  XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],
                 xvars.arenaTextColor[dpyNum]);
  pos.x++;
  pos.y++;  
  XDrawString(xvars.dpy[dpyNum],drawable,
              xvars.gc[dpyNum],
              pos.x,pos.y + xvars.bigFont[dpyNum]->max_bounds.ascent,
              msg,strlen(msg));

  // Set back to default
  XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],xvars.black[dpyNum]);
  XSetFont(xvars.dpy[dpyNum],xvars.gc[dpyNum],
           xvars.font[dpyNum]->fid);
}



class IScroller {
public:
  virtual ~IScroller();
  
  virtual void init(Xvars&,int dpyNum,Drawable arenaBG,
                    const Size& viewportSize) = 0;
  /* EFFECTS: Initialize any graphics needs of the IScroller. 
     viewportSize is in unstretched coordinates.  arenaBG is the 
     background window behind the arena, used for drawing tricks on its 
     border. */

  virtual Boolean move_to_pos(Area& viewportArea,const Pos& pos,
                              const Dim& innerDim,const Dim& extraDim) = 0;
  /* MODIFIES: vieportArea.set_pos() */
  /* REQUIRES: pos is a location in the world. */
  /* EFFECTS: Set the pos of viewportArea as necessary to follow an object
     centered at pos.  Return whether the viewport moved. */

  virtual void draw_arena(Window arena,Window arenaBG,WorldP,LocatorP,
                          const Area& viewportArea,IntelP intel,
                          Boolean forceDraw,Boolean reduceDraw,
                          const char* arenaMessage) = 0;
  /* REQUIRES: xvars and dpyNum are the same as the ones given to init. */
  /* EFFECTS: Draw the world and locator onto the window arena which 
     represents the area specified by viewportArea.  If forceDraw is true,
     must redraw everything.  If arenaMessage is non-NULL, draw it in the
     center of the arena. */

  virtual int get_border_width() = 0;
  /* NOTE: The Scroller owns this because it may or may want to draw tick
     marks in the arena border. */

  virtual void reset() = 0;
};



IScroller::~IScroller() {
}



class DiscreteScroller: public IScroller {
public:
  DiscreteScroller();

  virtual ~DiscreteScroller();
  
  virtual void init(Xvars&,int dpyNum,Drawable,const Size&);

  virtual Boolean move_to_pos(Area& viewportArea,const Pos& pos,
                              const Dim& innerDim,const Dim& extraDim);

  virtual void draw_arena(Window arena,Window arenaBG,WorldP,LocatorP,
                          const Area& viewportArea,IntelP intel,
                          Boolean forceDraw,Boolean reduceDraw,
                          const char* arenaMessage);
  /* EFFECTS: Just draw what has changed.  Don't scroll the background
     at a different rate than the foreground.  Draws tick marks in the 
     border. */

  virtual int get_border_width() {return TICK_BORDER_WIDTH;}

  virtual void reset();


private:
  enum {FLASH_TIME = 3};
  ITickRenderer* borderRenderer;
  Xvars* xvars;
  int dpyNum;
  Timer flashTimer;
};



DiscreteScroller::DiscreteScroller() {
  borderRenderer = NULL;
  flashTimer.set_max(FLASH_TIME);
  flashTimer.set();
}



DiscreteScroller::~DiscreteScroller() {
  delete borderRenderer;
}



void DiscreteScroller::init(Xvars& x_vars,int dpy_num,Drawable arenaBG,
                            const Size& viewportSize) {
  xvars = &x_vars;
  dpyNum = dpy_num;

  Size outerSize = xvars->stretch_size(viewportSize);
  outerSize.width += 2 * TICK_BORDER_WIDTH;
  outerSize.height += 2 * TICK_BORDER_WIDTH;

  borderRenderer = new BorderTickRenderer(arenaBG,*xvars,dpyNum,outerSize);
  assert(borderRenderer);
}



Boolean DiscreteScroller::move_to_pos(Area& viewportArea,const Pos& pos,
                                      const Dim& innerDim,
                                      const Dim& extraDim) {
  // Center the viewport on a chunk of this size.
  // The entire viewport has size 
  // innerDim + 2 * get_extra_dim()

  // The Loc containing pos.
  Loc l;
  l.set(pos.y / WSQUARE_HEIGHT,pos.x / WSQUARE_WIDTH);

  Pos oldPos  = viewportArea.get_pos();  

  // Scroll by rooms.
  Loc vLoc;
  vLoc.r = 
    innerDim.rowMax * (l.r / innerDim.rowMax) - extraDim.rowMax;
  vLoc.c = 
    innerDim.colMax * (l.c / innerDim.colMax) - extraDim.colMax;
  Pos newPos(vLoc.c * WSQUARE_WIDTH,vLoc.r * WSQUARE_HEIGHT);

  // No change.
  if (oldPos == newPos) {
    return False;
  }
  // Move to new position.
  else {
    viewportArea.set_pos(newPos);
    return True;
  }
}



void DiscreteScroller::draw_arena(Window arena,Window arenaBG,
                                  WorldP world,LocatorP locator,
                                  const Area& viewportArea,IntelP intel,
                                  Boolean forceDraw,
                                  Boolean,
                                  const char* arenaMessage) {
  // Redraw the entire visible portion of the World only if necessary.
  if (forceDraw) {
    // Don't use reduceDraw with Discrete scrolling.  If we wanted it to
    // work, we would have to make locator->draw_buffered take the 
    // reduceDraw argument.
    world->draw(arena,*xvars,dpyNum,viewportArea,False,False); 
  }

  // Get the viewportArea as a Box.
  Pos vPos = viewportArea.get_pos();
  Size vSize = viewportArea.get_size();
  // SmallViewport should never be scrolled in increments other than 
  // wsquares.
  assert(vPos.x % WSQUARE_WIDTH == 0 
         && vPos.y % WSQUARE_HEIGHT == 0);
  assert(vSize.width % WSQUARE_WIDTH == 0 
         && vSize.height % WSQUARE_HEIGHT == 0);
  Loc vLoc;
  vLoc.set(vPos.y / WSQUARE_HEIGHT,vPos.x / WSQUARE_WIDTH);
  Dim vDim(vSize.height / WSQUARE_HEIGHT,vSize.width / WSQUARE_WIDTH);
  Box viewportBox(vLoc,vDim);

  // Draw all visible objects.
  locator->draw_buffered(arena,*xvars,dpyNum,viewportBox);  

  flashTimer.clock();

  // Draw tick marks on the border around the arena.
  if (intel) {
    // Every few turns, don't draw the tick marks.
    if (flashTimer.ready()) {
      // Begin and end draw to erase border.
      borderRenderer->begin_draw(dpyNum);
      borderRenderer->end_draw(dpyNum);
      flashTimer.set();
    }
    else {
      locator->draw_ticks(arenaBG,*xvars,dpyNum,
                          viewportArea,intel->get_id(),
                          borderRenderer);
    }
  }

  if (arenaMessage) {
    Viewport_draw_string_center(*xvars,dpyNum,arena,
                                xvars->stretch_size(vSize),
                                arenaMessage);
  }
}



void DiscreteScroller::reset() {
  // This will clear out any tick marks.
  borderRenderer->begin_draw(dpyNum);
  borderRenderer->end_draw(dpyNum);
}



class SmoothScroller: public IScroller {
public:
  SmoothScroller();

  virtual ~SmoothScroller();

  virtual void init(Xvars&,int dpyNum,Drawable,const Size& viewportSize);

  virtual Boolean move_to_pos(Area& viewportArea,const Pos& pos,
                              const Dim& innerDim,const Dim& extraDim);
  /* EFFECTS: Smooth scrolling. */

  virtual void draw_arena(Window arena,Window arenaBG,WorldP,LocatorP,
                          const Area& viewportArea,IntelP intel,
                          Boolean forceDraw,Boolean reduceDraw,
                          const char* arenaMessage);
  /* EFFECTS: Redraw entire screen.  Scroll the background at a different
     rate than the foreground. */

  virtual int get_border_width() {return SMOOTH_BORDER_WIDTH;}

  virtual void reset() {}


private:
  Xvars* xvars;
  int dpyNum;

  // The double-buffer.
  Pixmap buffer;
};



SmoothScroller::SmoothScroller() {
}



void SmoothScroller::init(Xvars& x_vars,int dpy_num,Drawable,
                          const Size& viewportSize) {
  xvars = &x_vars;
  dpyNum = dpy_num;

  // In window coordinates, does not include border.
  Size arenaSize = xvars->stretch_size(viewportSize);
  
  if (Viewport::get_use_buffer()) {
    // Create back-buffer the size of the arena, without the border.
    buffer = XCreatePixmap(xvars->dpy[dpyNum],
                           xvars->root[dpyNum],
                           arenaSize.width,
                           arenaSize.height,
                           xvars->depth[dpyNum]);
  }
}



SmoothScroller::~SmoothScroller() {
  if (Viewport::get_use_buffer()) {
    XFreePixmap(xvars->dpy[dpyNum],buffer);
  }
}



Boolean SmoothScroller::move_to_pos(Area& viewportArea,const Pos& pos,
                                    const Dim&,const Dim&) {
  Pos posOld = viewportArea.get_pos();
  Pos posNew = pos - 0.5f * viewportArea.get_size();
  viewportArea.set_pos(posNew);

  return !(posOld == posNew);
}



void SmoothScroller::draw_arena(Window arena,Window,
                                WorldP world,LocatorP locator,
                                const Area& viewportArea,IntelP intel,
                                Boolean,Boolean reduceDraw,
                                const char* arenaMessage) {
  Boolean useBuffer = Viewport::get_use_buffer();
  Pixmap dest = (useBuffer ? buffer : arena);

  // Window coordinates.
  Size arenaInner = xvars->stretch_size(viewportArea.get_size());
    
  // Only need to fill with black if not drawing the background.
  if (reduceDraw) {
    XFillRectangle(xvars->dpy[dpyNum],dest,xvars->gc[dpyNum],
                   0,0,arenaInner.width,arenaInner.height);
  }

  // Draw world on the back-buffer.  Will redraw entire world each turn.
  world->draw(dest,*xvars,dpyNum,viewportArea,reduceDraw,True);

  // Draw all objects directly to dest.
  locator->draw_directly(dest,*xvars,dpyNum,viewportArea);

  // Draw tick marks on margins of screen.
  if (intel) {
    // Use locator as the implementation of ITickRenderer.  This uses 
    // the XPM tick mark images.
    locator->draw_ticks(dest,*xvars,dpyNum,
                        viewportArea,intel->get_id(),
                        locator);
  }

  if (arenaMessage) {
    Viewport_draw_string_center(*xvars,dpyNum,dest,
                                arenaInner,
                                arenaMessage);
  }

  // Whack back-buffer onto window.
  if (useBuffer) {
    XCopyArea(xvars->dpy[dpyNum],buffer,arena,xvars->gc[dpyNum],
              0,0,
              arenaInner.width,
              arenaInner.height,
              0,0);
  }
}



Viewport::Viewport(int arg_c,char** arg_v,
                   Xvars& x_vars,int dNum,
                   WorldP wrld,LocatorP lctr,
                   Boolean smoothScroll,int mNum,
                   IDifficultyCallback* diffCB,
                   IStyleInfo* styInfo,
                   IKeyObserver* kObserver,
                   //                   IChatObserver* cObserver,
                   const DifficultyLevel dLevels[DIFFICULTY_LEVELS_NUM],
                   RoleType rType,
                   ViewportCallback callbacks[VIEWPORT_CB_NUM],
                   void* clos)
: argc(arg_c),
  argv(arg_v),
  xvars(x_vars), 
  dpyNum(dNum), 
  world(wrld), 
  locator(lctr), 
  menusNum(mNum) {
  intel = NULL;
  
  roleType = rType;

  input = UI_INPUT_NONE;

  cursorDefined = False;
  arenaMessage = NULL;
  redrawArena = True;
  promptDifficulty = False;

  keyState = new KeyState();
  keyDispatcher = new KeyDispatcher();
  assert(keyDispatcher);  

  diffCallback = diffCB;
  styleInfo = styInfo;
  keyObserver = kObserver;
  //  chatObserver = cObserver;

  difficultyLevels = dLevels;

  // Create PanelClosures
  // NOTE: Creating one for menuStyle even though it is not used.
  for (int n = 0; n < VIEWPORT_CB_NUM; n++) {
    PanelClosure* p = new PanelClosure;
    assert(p);
    // Certain menu items must enforce radio-button behavior.
    if (n == menuScenarios ||
        n == menuLevels || 
        n == menuKill ||
        n == menuDuel ||
        n == menuExtended ||
        n == menuTraining) {
      p->radio = True;
    }
    else {
      p->radio = False;
    }
    p->viewport = this;
    // The viewport callback.
    p->callback = callbacks[n];
    panelClosures.add((void*)p);
  }
  // The one closure for all ViewportCallbacks.
  closure = clos;


  if (smoothScroll) {
    scroller = new SmoothScroller();
  }
  else {
    scroller = new DiscreteScroller();
  }
  assert(scroller);
}
 


void Viewport::init() {
  // Call all the virtual functions here that we aren't allowed to call
  // in the constructor.  Is there a hard-core C++ guru out there who 
  // knows a better way of doing this?

  // Dimensions of viewport are slightly larger than the innerDim.
  Dim viewportDim = get_inner_dim();
  Dim extraDim = get_extra_dim();
  viewportDim.colMax += 2 * extraDim.colMax;
  viewportDim.rowMax += 2 * extraDim.rowMax;

  // Start alligned with upper-left of world.
  Pos pos(0,0);
  Size size;
  size.set(viewportDim);
  Area vArea(pos,size);
  viewportArea = vArea;
  
  if (!sizeValid[dpyNum]) {
    init_sizes(xvars,dpyNum,world);
  }
  
  create_toplevel();

  scroller->init(xvars,dpyNum,arenaBG,viewportArea.get_size());
}



Viewport::~Viewport() {
  assert (panelClosures.length() == VIEWPORT_CB_NUM);
  int n;
  for (n = 0; n < panelClosures.length(); n++) {
    PanelClosure* p = (PanelClosure*)panelClosures.get(n);
    delete p;
  }
  
  Utils::freeif(arenaMessage);
  delete keyDispatcher;
  delete keyState;
  
  for (n = 0; n < menusNum; n++) {
    delete menus[n];
  }
  for (n = 0; n < VW_STATUSES_NUM; n++) {
    delete statuses[n];
  }
  delete humansPlaying;
  delete enemiesPlaying;
  delete messageBar;
  delete level;

  delete scroller;
  
  XDestroyWindow(xvars.dpy[dpyNum],toplevel);
}
 
 

void Viewport::set_menu_controls(Boolean val) {
  assert(menusNum > menuControls);
  ((TogglePanel*)menus[menuControls])->set_value(val);
}



Boolean Viewport::get_menu_learn_controls() {
  assert(menusNum > menuLearnControls);
  return ((TogglePanel*)menus[menuLearnControls])->get_value();  
}



void Viewport::set_menu_learn_controls(Boolean val) {
  assert(menusNum > menuLearnControls);
  ((TogglePanel*)menus[menuLearnControls])->set_value(val);
}
 


void Viewport::set_menu_humans_num(int val) {
  assert(menusNum > menuHumansNum);
  ostrstream str;
  str << val << ends;
  ((WritePanel *)menus[menuHumansNum])->set_value(str.str());
  delete str.str();
}
 


void Viewport::set_menu_enemies_num(int val) {
  assert(menusNum > menuEnemiesNum);
  ostrstream str;
  str << val << ends;
  ((WritePanel *)menus[menuEnemiesNum])->set_value(str.str());
  delete str.str();
}



void Viewport::set_menu_help(Boolean val) {
  assert(menusNum > menuHelp);
  ((TogglePanel*)menus[menuHelp])->set_value(val);
}



void Viewport::set_enemies_refill(Boolean val) {
  ((TogglePanel *)menus[menuEnemiesRefill])->set_value(val);
}



void Viewport::set_style_and_role_type(GameStyleType style,RoleType roleType) {
  assert(menusNum == VW_MENUS_PRIMARY_NUM);

  // Set value of radio buttons.
  ((TogglePanel *)menus[menuScenarios])->set_value(style == SCENARIOS);
  ((TogglePanel *)menus[menuLevels])->set_value(style == LEVELS);
  ((TogglePanel *)menus[menuKill])->set_value(style == KILL);
  ((TogglePanel *)menus[menuDuel])->set_value(style == DUEL);
  ((TogglePanel *)menus[menuExtended])->set_value(style == EXTENDED);
  ((TogglePanel *)menus[menuTraining])->set_value(style == TRAINING);

  // EnemiesNum
  menus[menuEnemiesNum]->
    set_sensitive(Role::uses_enemies_num(roleType) && 
                  GameStyle::uses_enemies_num(style));

  // EnemiesRefill
  menus[menuEnemiesRefill]->
    set_sensitive(Role::uses_enemies_refill(roleType) && 
                  GameStyle::uses_enemies_refill(style));

  // GameStyle
  Boolean enabled = Role::uses_game_style(roleType);
  menus[menuLevels]->set_sensitive(enabled);
  menus[menuScenarios]->set_sensitive(enabled);
  menus[menuKill]->set_sensitive(enabled);
  menus[menuDuel]->set_sensitive(enabled);
  menus[menuExtended]->set_sensitive(enabled);
  menus[menuTraining]->set_sensitive(enabled);
  
  // HumansNum
  menus[menuHumansNum]->set_sensitive(Role::uses_humans_num(roleType));

  // Cooperative
  menus[menuCooperative]->set_sensitive(Role::uses_cooperative(roleType));
}



void Viewport::set_quanta(Quanta quanta) {
  assert(menusNum > menuQuanta);
  ostrstream str;
  str << quanta << ends;
  ((WritePanel *)menus[menuQuanta])->set_value(str.str());
  delete str.str();  
} 



void Viewport::set_cooperative(Boolean val) {
  assert(menusNum > menuCooperative);
  ((TogglePanel *)menus[menuCooperative])->set_value(val);
}



void Viewport::set_humans_playing(int val) {  
  ostrstream msg;
  if (val == 1) {
    msg << "1 Human" << ends;
  }
  else {
    msg << val << " Humans" << ends;
  }
  char *msg_str = msg.str();
  
  humansPlaying->set_message(msg_str);
  delete msg_str;
}



void Viewport::set_enemies_playing(int val) {  
  ostrstream msg;
  if (val == 1) {
    msg << "1 Enemy" << ends; 
  }
  else {
    msg << val << " Enemies" << ends; 
  }
  char *msg_str = msg.str();
  
  enemiesPlaying->set_message(msg_str);
  delete msg_str;
}



const char* Viewport::get_level() {
  return level->get_message();
}



void Viewport::set_level(const char* val) {  
  level->set_message(val);
}



void Viewport::set_input(UIinput inpt) {
  assert(inpt != UI_INPUT_NONE);
  input = inpt; 
}



void Viewport::set_message(const char* message) {
  messageBar->set_message(message);
}



void Viewport::set_arena_message(const char* msg,Quanta time) {
  Utils::freeif(arenaMessage);
  arenaMessage = Utils::strdup(msg);
  arenaMessageTimer.set(time);
  redrawArena = True;
}



void Viewport::register_intel(int humanColorNum,IntelP intl) {  
  assert(intl);  
  intel = intl;

  // Set colors of status panels to be those of the human.
  if (intel->is_human()) {
    Pixel pixel = xvars.humanColors[dpyNum][humanColorNum];
    for (int num = 0; num < VW_STATUSES_NUM; num++) {
      statuses[num]->set_foreground(pixel);
    }
  }

}



void Viewport::clear_all() {
  XClearWindow(xvars.dpy[dpyNum],arena);
  for (int mm = 0; mm < menusNum; mm++) {
    menus[mm]->clear();
  }
  for (int s = 0; s < VW_STATUSES_NUM; s++) {
    statuses[s]->clear();
  }
  humansPlaying->clear();
  enemiesPlaying->clear();
  messageBar->clear();
  level->clear();
}



void Viewport::unclear_all() {
  redrawArena = True;
  for (int mm = 0; mm < menusNum; mm++) {
    menus[mm]->redraw();
  }
  for (int s = 0; s < VW_STATUSES_NUM; s++) {
    statuses[s]->redraw();
  }
  humansPlaying->redraw();
  enemiesPlaying->redraw();
  messageBar->redraw();
  level->redraw();
}



void Viewport::set_prompt_difficulty(Boolean val) {
  promptDifficulty = val;
  redrawArena = True;
}



Boolean Viewport::has_window(Window w) {
  // Check toplevel and arena.
  if (toplevel == w || arena == w) {
    return True;
  }

  // Check menus.
  int m;
  for (m = 0; m < menusNum; m++) {
    if (menus[m]->get_window() == w) {
      return True;
    }
  }
    
  // Check statuses.
  for (m = 0; m < VW_STATUSES_NUM; m++) {
    if (statuses[m]->get_window() == w) {
      return True;
    }
  }
  
  if (humansPlaying->get_window() == w) {
    return True;
  }
  
  if (enemiesPlaying->get_window() == w) {
    return True;
  }
  
  if (messageBar->get_window() == w) {
    return True;
  }
    
  if (level->get_window() == w) {
    return True;
  }
  
  return False;
}



void Viewport::reset() {
  intel = NULL;
  redrawArena = True;
  promptDifficulty = False;
  Utils::freeif(arenaMessage);  // Actual message being displayed.
  for (int m = 0; m < VW_STATUSES_NUM; m++) {
    statuses[m]->set_message("");
    statuses[m]->set_foreground(xvars.black[dpyNum]);
  }
  Dim extraDim = get_extra_dim();

  messageBar->set_chat(False);

  Pos zeroPos;
  viewportArea.set_pos(zeroPos);

  scroller->reset();
}



void Viewport::process_event(XEvent* event) {
  assert(has_window(event->xany.window));
  
  // Put this first becase messageBar wants to capture all key events
  // for chat mode.
  if (messageBar->process_event(dpyNum,event)) {
    return;
  }
  
  // Events on the toplevel or arena.
  if (event->xany.window == toplevel ||
      event->xany.window == arena) {
    switch(event->type) {
    case Expose:
      expose(event);
      break;
    case KeyPress:
      key_press(event);
      break;
    case KeyRelease:
      key_release(event);
      break;
    case ButtonPress:
      button_press(event);
      break;
    default:
      cerr << "Warning: Unexpected event of type " << event->type 
           << endl;
      break;
    }
    return;
  }


  // Send to all menus/statuses/etc.
  int m;
  for (m = 0; m < menusNum; m++) {
    if (menus[m]->process_event(dpyNum,event)) {
      return;
    }
  }
  
  for (m = 0; m < VW_STATUSES_NUM; m++) {
    if (statuses[m]->process_event(dpyNum,event)) {
      return;
    }
  }
  
  if (humansPlaying->process_event(dpyNum,event)) {
    return;
  }
    
  if (enemiesPlaying->process_event(dpyNum,event)) {
    return;
  }
    
  if (level->process_event(dpyNum,event)) {
    return;
  }
}



IViewportInfo* Viewport::get_info() {
  if (!vInfoProvider) {
    vInfoProvider = new VInfoProvider();
    assert(vInfoProvider);
  }
  return vInfoProvider;
}



void Viewport::init_viewport_info(Boolean isStretched,Boolean smoothScroll) {
  // Make sure provider has been created.
  VInfoProvider* provider = (VInfoProvider*)get_info();

  if (isStretched) {
    provider->set_viewport_info(LargeViewport::get_info(smoothScroll));
  }
  else {
    provider->set_viewport_info(SmallViewport::get_info(smoothScroll));
  }
}



// Implement IDispatcher.
void Viewport::dispatch(ITcommand command,void*) {
  if (!acceptInput) {
    return;
  }
  
  // Can chat even if no associated human.  For Server in observer mode.
  if (command == IT_CHAT) {
    // Only enable Chat mode if the current Role supports it.
    if (Role::uses_chat(styleInfo->get_role_type())) {
      messageBar->set_chat(True);
    }
    return;
  }

  // If there is an intel associated with the viewport, give command to it.
  if (intel && intel->is_playing()) {  
    // Only give the command to a human.
    if (intel->is_human()) {
      ((HumanP)intel)->set_command(command);
    }
    return;
  }


  // No associated intel, so scroll with the keyset. 
  Boolean changed;
  switch (command) {
  // Right.
  case IT_R:
    changed = shift_viewport(COL_SHIFT,0);
    break;
  // Down right.
  case IT_DN_R:
    changed = shift_viewport(COL_SHIFT,ROW_SHIFT);
    break;
  // Down.
  case IT_DN:
    changed = shift_viewport(0,ROW_SHIFT);
    break;
  // Down left.
  case IT_DN_L:
    changed = shift_viewport(-COL_SHIFT,ROW_SHIFT);
    break;
  // Left.
  case IT_L:
    changed = shift_viewport(-COL_SHIFT,0);
    break;
  // Up left.
  case IT_UP_L:
    changed = shift_viewport(-COL_SHIFT,-ROW_SHIFT);
    break;
  // Up.
  case IT_UP:
    changed = shift_viewport(0,-ROW_SHIFT);
    break;
  // Up right.
  case IT_UP_R:
    changed = shift_viewport(COL_SHIFT,-ROW_SHIFT);
    break;
  default:
    changed = False;
  }
  
  if (changed) {
    redrawArena = True;
  }
}



void Viewport::pre_clock() {
  // Before check for death in follow_intel().
  update_statuses();
  
  follow_intel();

  draw();
      
  arenaMessageTimer.clock();

  // Clear arenaMessage being displayed if time is up.
  if (arenaMessage && arenaMessageTimer.ready()) {
    Utils::freeif(arenaMessage);
    // Clean up the message next time.
    redrawArena = True;
  }
}



void Viewport::post_clock() {
  // Want this immediately after the events have been pulled from the 
  // queue and before Locator::clock().
  if (input != UI_INPUT_NONE) {
    keyDispatcher->clock(keyState,this,NULL);
  }
}



void Viewport::expose(XEvent*) {  
  if (!cursorDefined) {
    XDefineCursor(xvars.dpy[dpyNum],arena,xvars.arenaCursor[dpyNum]);
    cursorDefined = True;
  }
  // Mark dirty to force a redraw.
  redrawArena = True;
  draw();
}



// Helper macros for Viewport::key_press().
#define NUM_KEY_EQ(keySym) \
  ((event->xkey.keycode == XKeysymToKeycode(xvars.dpy[dpyNum],XK_ ## keySym)) || \
   (event->xkey.keycode == XKeysymToKeycode(xvars.dpy[dpyNum],XK_KP_ ## keySym)))

void Viewport::key_press(XEvent *event) {
  // If waiting for user to enter difficulty, don't accept anything else.
  if (promptDifficulty) {
    // Should be DIFFICULTY_LEVELS_NUM of these.  It'd be nice to 
    // abstract this more.
    if (NUM_KEY_EQ(0)) {
      diffCallback->change_difficulty(0);
    }
    else if (NUM_KEY_EQ(1)) {
      diffCallback->change_difficulty(1);
    }
    else if (NUM_KEY_EQ(2)) {
      diffCallback->change_difficulty(2);
    }
    else if (NUM_KEY_EQ(3)) {
      diffCallback->change_difficulty(3);
    }
    
    if (event->xkey.keycode == 
        XKeysymToKeycode(xvars.dpy[dpyNum],XK_space)) {
      diffCallback->change_difficulty(DIFF_NORMAL);
    }
    return;
  }

  // The observer will translate the keycode into a key and send it
  // back to Viewport::receive_key.  Note that it may send it to 
  // receive_key on a different viewport.
  keyObserver->key_event(dpyNum,event->xkey.keycode,True);
}



void Viewport::key_release(XEvent *event) {     
  if (promptDifficulty) {
    return;
  }
  keyObserver->key_event(dpyNum,event->xkey.keycode,False);
}



void Viewport::receive_key(int key,Boolean down) {
  keyState->set(key,down);
}



void Viewport::button_press(XEvent *event) {
  if (promptDifficulty) {
    diffCallback->change_difficulty(DIFF_NORMAL);
    return;
  }
  
  // Button1 moves your character.
  if (event->xbutton.button == Button1) {
    dispatch(Intel::dir_to_command(button_press_to_dir(event)),NULL);
  }
  // Button2 fires weapon.
  else if (event->xbutton.button == Button2) {
    dispatch(Intel::dir_to_command_weapon(button_press_to_dir(event)),NULL);
  }
  // Button3 changes weapon.
  else if (event->xbutton.button == Button3) {
    dispatch(IT_WEAPON_CHANGE,NULL);
  }
}



void Viewport::init_sizes(Xvars& xvars,int dpyNum,WorldP) {
  Dim innerDim = get_inner_dim();
  Dim extraDim = get_extra_dim();
  int border = scroller->get_border_width();

  innerSize[dpyNum].set(xvars.stretch_x(innerDim.colMax * WSQUARE_WIDTH),
                        xvars.stretch_y(innerDim.rowMax * WSQUARE_HEIGHT));

  arenaSize[dpyNum].width = innerSize[dpyNum].width 
    + xvars.stretch_x(2 * WSQUARE_WIDTH * extraDim.colMax)
    + 2 * border;
  arenaSize[dpyNum].height = innerSize[dpyNum].height 
    + xvars.stretch_y(2 * WSQUARE_HEIGHT * extraDim.rowMax)
    + 2 * border;
}



void Viewport::panel_callback(Panel* panel,void* value,void* closure) {
  PanelClosure* pClosure = (PanelClosure*)closure;
  assert(pClosure);

  // Enforce radio button behavior for certain panels.
  // Make sure you can't uncheck the active button.
  // The logic to unselect the active button when another one is set is
  // in Viewport::set_style().
  //
  // Would be much better to put the radio-button logic in the Panel classes.
  if (pClosure->radio) {
    // SGI compiler says you can't cast void* to Boolean.
    Boolean bValue = (Boolean)(int)value;
    if (!bValue) {
      ((TogglePanel*)panel)->set_value(True);
    }
  }

  // Call the appropriate callback registered in the Viewport constructor
  // with the closure given to the Viewport constructor.
  pClosure->callback(value,pClosure->viewport,
                     pClosure->viewport->closure);
}



Dir Viewport::button_press_to_dir(XEvent *event) {
  assert(event->type == ButtonPress);
  
  if (event->xbutton.x < arenaSize[dpyNum].width * 0.3333) {
    if (event->xbutton.y < arenaSize[dpyNum].height * 0.3333) {
      return CO_UP_L;
    }
    else if (event->xbutton.y < arenaSize[dpyNum].height * 0.6666) {
      return CO_L;
    }
    else {
      return CO_DN_L;
    }
  }
  else if (event->xbutton.x < arenaSize[dpyNum].width * .6666) {
    if (event->xbutton.y < arenaSize[dpyNum].height * 0.3333) {
      return CO_UP;
    }
    else if (event->xbutton.y < arenaSize[dpyNum].height * 0.6666) {
      return CO_air;
    }
    else {
      return CO_DN;
    }
  }
  else {
    if (event->xbutton.y < arenaSize[dpyNum].height * 0.3333) {
      return CO_UP_R;
    }
    else if (event->xbutton.y < arenaSize[dpyNum].height * 0.6666) {
      return CO_R;
    }
    else {
      return CO_DN_R;
    }
  }
}


 
void Viewport::draw() {
  // Just draw some text in the middle of the arena.
  if (promptDifficulty) {
    if (redrawArena) {
      XClearWindow(xvars.dpy[dpyNum],arena);
      
      // prompt user for level of difficulty.
      XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],
                     xvars.red[dpyNum]);
      
      Pos pos(xvars.fontSize[dpyNum].width,xvars.fontSize[dpyNum].height);
      draw_string(pos,"Enter level of difficulty:");
      pos.y += xvars.fontSize[dpyNum].height;
      
      for (int n = 0; n < DIFFICULTY_LEVELS_NUM; n++) {
        ostrstream str;
        str << "[" << n;
        if (n == DIFF_NORMAL) {
          str << ",space]  ";
        }
        else {
          str << "]        ";
        }
        str << difficultyLevels[n].name << ends;
        pos.y += xvars.fontSize[dpyNum].height;
        draw_string(pos,str.str());
        delete str.str();
      }
      
      XSetForeground(xvars.dpy[dpyNum],xvars.gc[dpyNum],
                     xvars.black[dpyNum]);
    }
  }

  // Normal drawing.
  else {
    // Draw world and all objects.
    scroller->draw_arena(arena,arenaBG,world,locator,viewportArea,intel,
                         redrawArena,reduceDraw,arenaMessage);
  }  
  
  // Mark as not dirty.  We just brought everything up to date.
  redrawArena = False;
}



void Viewport::draw_string(const Pos &pos,char *msg) {
  XDrawString(xvars.dpy[dpyNum],arena,
              xvars.gc[dpyNum],
              pos.x,pos.y + xvars.font[dpyNum]->max_bounds.ascent,
              msg,strlen(msg));
}



Boolean Viewport::shift_viewport(int cols,int rows) {
  Pos shift(cols * WSQUARE_WIDTH,rows * WSQUARE_HEIGHT);

  const Dim worldDim = world->get_dim();

  Pos pos = viewportArea.get_pos();
  Size size = viewportArea.get_size();
  if (shift.x > 0) {
    if (pos.x + shift.x < 
        worldDim.colMax * WSQUARE_WIDTH) {
      pos.x += shift.x;
    }
  }
  if (shift.x < 0) {
    if (pos.x + size.width + shift.x > 0) {
      pos.x += shift.x;
    }
  }
  if (shift.y > 0) {
    if (pos.y + shift.y < 
        worldDim.rowMax * WSQUARE_HEIGHT) {
      pos.y += shift.y;
    }
  }
  if (shift.y < 0) {
    if (pos.y + size.height + shift.y > 0) {
      pos.y += shift.y;
    }
  }

  // See if we actually changed pos.
  if (!(pos == viewportArea.get_pos())) {
    viewportArea.set_pos(pos);
    return True;
  }

  return False;
}



void Viewport::create_toplevel() {
  const char* title = NULL;
  switch (roleType) {
  case R_STAND_ALONE:
    title = "XEvil";
    break;
  case R_CLIENT:
    title = "XEvil [Client]";
    break;
  case R_SERVER:
    title = "XEvil [Server]";
    break;
  default:
    assert(0);
  }
  
  toplevel = 
    xvars.create_toplevel_window(argc,argv,dpyNum,viewportSize[dpyNum],
                                 title,KeyPressMask | KeyReleaseMask);

#if 0
  toplevel = 
    XCreateSimpleWindow(xvars.dpy[dpyNum],xvars.root[dpyNum],0,0,
                        viewportSize[dpyNum].width,
                        viewportSize[dpyNum].height,
                        0,xvars.windowBorder[dpyNum],
                        xvars.windowBg[dpyNum]);

  XSizeHints size_hints;
  size_hints.flags = PPosition | PSize | PMinSize | PMaxSize;
  size_hints.min_width = viewportSize[dpyNum].width;
  size_hints.min_height = viewportSize[dpyNum].height;
  size_hints.max_width = viewportSize[dpyNum].width;
  size_hints.max_height = viewportSize[dpyNum].height;

  XTextProperty windowName, iconName;
  char *window_name = "XEvil";  // Will appear on window.
  char *icon_name = "XEvil";
  Status stat = XStringListToTextProperty(&window_name,1,&windowName);
  assert(stat);
  stat = XStringListToTextProperty(&icon_name,1,&iconName);
  assert(stat);
  
  XWMHints wm_hints;
  wm_hints.initial_state = NormalState;
  wm_hints.input = True;
  wm_hints.icon_pixmap = xvars.icon[dpyNum];
  wm_hints.flags = StateHint | IconPixmapHint | InputHint;

  XClassHint class_hints;
  class_hints.res_name = argv[0];
  class_hints.res_class = "XEvil";

  XSetWMProperties(xvars.dpy[dpyNum],toplevel,
                   &windowName,&iconName,argv,argc,
                   &size_hints,&wm_hints,&class_hints);

  XSelectInput(xvars.dpy[dpyNum],toplevel,
               KeyPressMask | KeyReleaseMask);
  
  // Add WM_DELETE_WINDOW protocol
  XChangeProperty(xvars.dpy[dpyNum],toplevel,
                  xvars.wmProtocols[dpyNum],XA_ATOM,
                  32,PropModePrepend,
                  (unsigned char *)&xvars.wmDeleteWindow[dpyNum],
                  1);
#endif

  create_menus();
  create_arena();
  create_statuses();
  create_intels_playing();
  create_message_bar();
  create_level();

  XMapWindow(xvars.dpy[dpyNum],arena);
  XMapWindow(xvars.dpy[dpyNum],arenaBG);
  XMapWindow(xvars.dpy[dpyNum],toplevel);
}



void Viewport::create_menus() {
  if (!menusNum) {
    return;
  }
  Pixel menuBg = xvars.alloc_named_color(dpyNum,MENU_BG_COLOR);
  Panel *p = NULL;

  
  // Compute sizes for all menus.
  Size quitUnit = ButtonPanel::get_unit(xvars.font[dpyNum],QUIT_LINE_LENGTH);
  Size newGameUnit = 
    ButtonPanel::get_unit(xvars.font[dpyNum],NEW_GAME_LINE_LENGTH);
  Size humansNumUnit = 
    WritePanel::get_unit(xvars.font[dpyNum],HUMANS_NUM_LINE_LENGTH);
  Size enemiesNumUnit = 
    WritePanel::get_unit(xvars.font[dpyNum],ENEMIES_NUM_LINE_LENGTH);
  Size enemiesRefillUnit = 
    TogglePanel::get_unit(xvars.font[dpyNum],ENEMIES_REFILL_LINE_LENGTH);
  Size controlsUnit =
    TogglePanel::get_unit(xvars.font[dpyNum],CONTROLS_LINE_LENGTH);
  Size learnControlsUnit =
    TogglePanel::get_unit(xvars.font[dpyNum],LEARN_CONTROLS_LINE_LENGTH);

  Size styleUnit = 
    TogglePanel::get_unit(xvars.font[dpyNum],STYLE_LINE_LENGTH);
  Size scenariosUnit = 
    TogglePanel::get_unit(xvars.font[dpyNum],SCENARIOS_LINE_LENGTH);
  Size levelsUnit = 
    TogglePanel::get_unit(xvars.font[dpyNum],LEVELS_LINE_LENGTH);
  Size killUnit = TogglePanel::get_unit(xvars.font[dpyNum],KILL_LINE_LENGTH);
  Size duelUnit = TogglePanel::get_unit(xvars.font[dpyNum],DUEL_LINE_LENGTH);
  Size extendedUnit = 
    TogglePanel::get_unit(xvars.font[dpyNum],EXTENDED_LINE_LENGTH);
  Size trainingUnit = 
    TogglePanel::get_unit(xvars.font[dpyNum],TRAINING_LINE_LENGTH);
  Size quantaUnit = 
    WritePanel::get_unit(xvars.font[dpyNum],QUANTA_LINE_LENGTH);
  Size cooperativeUnit = 
    WritePanel::get_unit(xvars.font[dpyNum],COOPERATIVE_LINE_LENGTH);
  Size helpUnit = 
    TextPanel::get_unit(xvars.font[dpyNum],HELP_LINE_LENGTH);


  // Create actual menu panels.
  // Menus created at (0,0) for Large and Small Viewports.
  Pos pos(0,0);

  if (menusNum == VW_MENUS_PRIMARY_NUM) {
    assert(dpyNum == 0);
    
    // Quit button.
    p = menus[menuQuit] = 
      new ButtonPanel(dpyNum,xvars,toplevel,
                      pos,quitUnit,
                      Viewport::panel_callback,panelClosures.get(menuQuit),
                      "Quit");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += quitUnit.width;
    
    // New Game button.
    p = menus[menuNewGame] = 
      new ButtonPanel(dpyNum,xvars,toplevel,
                      pos,newGameUnit,
                      Viewport::panel_callback,panelClosures.get(menuNewGame),
                      "New Game");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += newGameUnit.width;
    
    // Humans WritePanel
    p = menus[menuHumansNum] = 
      new WritePanel(dpyNum,xvars,toplevel,
                     pos,humansNumUnit,
                     Viewport::panel_callback,panelClosures.get(menuHumansNum),
                     "Humans:");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += humansNumUnit.width;
    
    // Enemies WritePanel
    p = menus[menuEnemiesNum] = 
      new WritePanel(dpyNum,xvars,toplevel,
                     pos,enemiesNumUnit,
                     Viewport::panel_callback,panelClosures.get(menuEnemiesNum),
                     "Enemies:");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += enemiesNumUnit.width;
    
    // Enemies Refill TogglePanel
    p = menus[menuEnemiesRefill] = 
      new TogglePanel(dpyNum,xvars,toplevel,
                      pos,enemiesRefillUnit,
                      Viewport::panel_callback,
                      panelClosures.get(menuEnemiesRefill),
                      "Regen Enemies");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += enemiesRefillUnit.width;
  }
  else {
    assert(menusNum == VW_MENUS_SECONDARY_NUM);
  }
  
  // Learn Controls ButtonPanel
  p = menus[menuLearnControls] = 
    new TogglePanel(dpyNum,xvars,toplevel,
                    pos,learnControlsUnit,
                    Viewport::panel_callback,
                    panelClosures.get(menuLearnControls),
                    "Set Controls");
  assert(p);
  p->set_background(menuBg,False);
  pos.x += learnControlsUnit.width;
  
  // Controls TogglePanel
  p = menus[menuControls] = 
    new TogglePanel(dpyNum,xvars,toplevel,
                    pos,controlsUnit,
                    Viewport::panel_callback,
                    panelClosures.get(menuControls),
                    "Show Controls");
  assert(p);
  p->set_background(menuBg,False);
  pos.x += controlsUnit.width;
  
  
  // speed now in first row.
  if (menusNum == VW_MENUS_PRIMARY_NUM) {
    p = menus[menuQuanta] = 
      new WritePanel(dpyNum,xvars,toplevel,
                     pos,quantaUnit,
                     Viewport::panel_callback,panelClosures.get(menuQuanta),
                     "Speed(ms):");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += quantaUnit.width;
  }

  
  // Second row.
  pos.x = 0;
  pos.y += quitUnit.height;
  
  if (menusNum == VW_MENUS_PRIMARY_NUM) {
    // Game stlye header.
    p = menus[menuStyle] = 
      new TextPanel(dpyNum,xvars,toplevel,
                    pos,styleUnit,
                    NULL,NULL,
                    "Game style:");
    assert(p);
    //      p->set_background(menuBg,False);
    pos.x += styleUnit.width;
      
    // Levels game style.
    p = menus[menuLevels] = 
      new TogglePanel(dpyNum,xvars,toplevel,
                      pos,levelsUnit,
                      Viewport::panel_callback,panelClosures.get(menuLevels),
                      "Levels");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += levelsUnit.width;
    
    // Scenarios game style.
    p = menus[menuScenarios] = 
      new TogglePanel(dpyNum,xvars,toplevel,
                      pos,scenariosUnit,
                      Viewport::panel_callback,
                      panelClosures.get(menuScenarios),
                      "Scenarios");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += scenariosUnit.width;
    
    // Kill, Kill, Kill game style.
    p = menus[menuKill] = 
      new TogglePanel(dpyNum,xvars,toplevel,
                      pos,killUnit,
                      Viewport::panel_callback,panelClosures.get(menuKill),
                      "Kill, Kill, Kill");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += killUnit.width;
    
    // Duel game style.
    p = menus[menuDuel] = 
      new TogglePanel(dpyNum,xvars,toplevel,
                      pos,duelUnit,
                      Viewport::panel_callback,panelClosures.get(menuDuel),
                      "Duel");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += duelUnit.width;
    
    // Extended duel game style.
    p = menus[menuExtended] = 
      new TogglePanel(dpyNum,xvars,toplevel,
                      pos,extendedUnit,
                      Viewport::panel_callback,panelClosures.get(menuExtended),
                      "Extended Duel");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += extendedUnit.width;
    
    // Training game style.
    p = menus[menuTraining] = 
      new TogglePanel(dpyNum,xvars,toplevel,
                      pos,trainingUnit,
                      Viewport::panel_callback,
                      panelClosures.get(menuTraining),
                      "Training");
    assert(p);
    p->set_background(menuBg,False);
    pos.x += trainingUnit.width;
    
    // Cooperative mode.
    pos.x = viewportSize[dpyNum].width - cooperativeUnit.width 
      - helpUnit.width;
    p = menus[menuCooperative] = 
      new TogglePanel(dpyNum,xvars,toplevel,
                      pos,cooperativeUnit,
                      Viewport::panel_callback,
                      panelClosures.get(menuCooperative),
                      "Cooperative");
    assert(p);
    p->set_background(menuBg,False);

    // Help button.
    pos.x = viewportSize[dpyNum].width - helpUnit.width;
    p = menus[menuHelp] = 
      new TogglePanel(dpyNum,xvars,toplevel,
                      pos,helpUnit,
                      Viewport::panel_callback,
                      panelClosures.get(menuHelp),
                      "Help");
    assert(p);
    p->set_background(menuBg,False);
  }
}



void Viewport::create_arena() {
  // The background window behind arena, used for drawing tick marks.
  arenaBG =
    XCreateSimpleWindow(xvars.dpy[dpyNum],
                        toplevel,
                        0,menusSize[dpyNum].height,
                        arenaSize[dpyNum].width,
                        arenaSize[dpyNum].height,
                        0,
                        xvars.alloc_named_color(dpyNum,
                                                ARENA_BORDER_COLOR,
                                                xvars.black[dpyNum]),
                        xvars.alloc_named_color(dpyNum,
                                                ARENA_BORDER_COLOR,
                                                xvars.black[dpyNum]));
  // arenaBG doesn't really need any events.


  int border = scroller->get_border_width();

  // Create arena just inside arenaBG.
  arena =
    XCreateSimpleWindow(xvars.dpy[dpyNum],
                        arenaBG,
                        border,border,
                        arenaSize[dpyNum].width - 2 * border,
                        arenaSize[dpyNum].height - 2 * border,
                        0,
                        xvars.alloc_named_color(dpyNum,
                                                Xvars_BACKGROUND),
                        xvars.alloc_named_color(dpyNum,
                                                Xvars_BACKGROUND));
  XSelectInput(xvars.dpy[dpyNum],arena,
               ExposureMask | ButtonPressMask);
}



void Viewport::create_intels_playing() {
  Pos pos = intelsPlayingPos[dpyNum];
  Size oneIntelPlaying = intelsPlayingSize[dpyNum];
  oneIntelPlaying.height /= 2;

  humansPlaying = 
    new TextPanel(dpyNum,xvars,toplevel,
                  pos,oneIntelPlaying);
  assert(humansPlaying);

  pos.y += oneIntelPlaying.height;
  enemiesPlaying = 
    new TextPanel(dpyNum,xvars,toplevel,
                  pos,oneIntelPlaying);
  assert(enemiesPlaying);
}



void Viewport::create_message_bar() {
  messageBar = new ChatPanel(dpyNum,xvars,toplevel,
                             messageBarPos[dpyNum],messageBarSize[dpyNum],
                             Viewport::panel_callback,
                             panelClosures.get(stChat));
  assert(messageBar);
}



// Need better name.  This refers to the status item that displays 
// level-specific information.
void Viewport::create_level() {
  level = new TextPanel(dpyNum,xvars,toplevel,
                        levelPos[dpyNum],levelSize[dpyNum]);
  assert(level);
}



void Viewport::update_statuses() {
  // See if we actually need to do anything.
  if (!intel || !intel->intel_status_changed()) {
    return;
  }

  const IntelStatus *status = intel->get_intel_status();
  
  ostrstream name;
  name << status->name << ends;
  statuses[statusName]->set_message(name.str());
  delete name.str();

  ostrstream className;
  className << status->className << ends;
  statuses[statusClassName]->set_message(className.str());
  delete className.str();
  
  ostrstream health;
  if (status->health == -1) {
    health << "Dead" << ends;
  }
  else {
    health << status->health << " Health" << ends; 
  }
  statuses[statusHealth]->set_message(health.str());
  delete health.str();
  
  ostrstream mass;
  mass << status->mass << " Mass" << ends;
  statuses[statusMass]->set_message(mass.str());
  delete mass.str();
  
  ostrstream weapon;
  if (status->weaponClassId == A_None) {
    weapon << "No Weapon";
  }
  else {
    weapon << status->weapon;
  }
  if (status->ammo != PH_AMMO_UNLIMITED) {
    weapon << " (" << status->ammo << ")";
  }
  weapon << ends;
  statuses[statusWeapon]->
    set_foreground(status->weaponReady ? 
                   xvars.green[dpyNum] : xvars.red[dpyNum],False);
  statuses[statusWeapon]->set_message(weapon.str());
  delete weapon.str();
  
  ostrstream item;
  if (status->itemClassId == A_None) {
    item << "No Item";
  }
  else {
    item << status->item << " (" << status->itemCount << ")";
  }
  item << ends;
  statuses[statusItem]->set_message(item.str());
  delete item.str();
  
  ostrstream livesHKills;
  if (styleInfo->get_game_style_type() == EXTENDED) {
    // Takes soups into account.
    livesHKills << (status->humanKills - status->soups) 
                << " Human Kills" << ends;
  }
  else {
    if (status->lives == IT_INFINITE_LIVES) {
      // Does this case ever happen?  I don't think so.
      livesHKills << "Unlimited Lives" << ends;
    }
    else {
      if (status->lives == 1) {
        livesHKills << "1 Life" << ends;
      }
      else {
        livesHKills << status->lives << " Lives" << ends;
      }
    }
  }
  statuses[statusLivesHKills]->set_message(livesHKills.str());
  delete livesHKills.str();
  
  ostrstream killsMKills;
  if (styleInfo->get_game_style_type() == EXTENDED) {
    killsMKills << status->enemyKills << " Machine Kills" << ends;
  }
  else {
    int kills = status->humanKills + status->enemyKills;
    if (kills == 1) {
      killsMKills << "1 Kill" << ends;
    }
    else {
      killsMKills << kills << " Kills" << ends;
    }
  }
  statuses[statusKillsMKills]->set_message(killsMKills.str());
  delete killsMKills.str();
}



void Viewport::follow_intel() {
  if (intel) {
    // Only follow if playing.
    if (intel->is_playing()) {
      PhysicalP p;
      if (p = locator->lookup(intel->get_id())) {
        const Area &a = p->get_area();
        if (scroller->move_to_pos(viewportArea,a.get_middle(),
                                  get_inner_dim(),get_extra_dim())) {
          redrawArena = True;
        }
      }
    }
#if 0
    // As good a place as any to check if the intel is dead.
    else {
      // Unregister the Intel.
      intel = NULL;
    }
#endif
  }
}



Boolean Viewport::acceptInput = False;



VInfoProvider* Viewport::vInfoProvider = NULL;



Boolean Viewport::reduceDraw = False;



Boolean Viewport::useBuffer = True;



Boolean Viewport::sizeValid[Xvars::DISPLAYS_MAX] = {
  0,
  // Compiler will initialize the rest of the values to zero.
};



Size Viewport::menusSize[Xvars::DISPLAYS_MAX];



Size Viewport::innerSize[Xvars::DISPLAYS_MAX];



Size Viewport::arenaSize[Xvars::DISPLAYS_MAX];



Size Viewport::statusesSize[Xvars::DISPLAYS_MAX];



Pos Viewport::intelsPlayingPos[Xvars::DISPLAYS_MAX];



Size Viewport::intelsPlayingSize[Xvars::DISPLAYS_MAX];



Pos Viewport::messageBarPos[Xvars::DISPLAYS_MAX];



Size Viewport::messageBarSize[Xvars::DISPLAYS_MAX];



Pos Viewport::levelPos[Xvars::DISPLAYS_MAX];



Size Viewport::levelSize[Xvars::DISPLAYS_MAX];



Size Viewport::viewportSize[Xvars::DISPLAYS_MAX];



SmallViewport::SmallViewport(int argc,char** argv,
                             Xvars& xvars,int dpyNum,
                             WorldP w,LocatorP l,
                             Boolean smoothScroll,int menusNum,
                             IDifficultyCallback* diffCB,
                             IStyleInfo* styleInfo,
                             IKeyObserver* keyObserver,
                             //                             IChatObserver* chatObserver,
                             const DifficultyLevel 
                             dLevels[DIFFICULTY_LEVELS_NUM],
                             RoleType roleType,
                             ViewportCallback callbacks[VIEWPORT_CB_NUM],
                             void* closure) 
: Viewport(argc,argv,xvars,dpyNum,w,l,
           smoothScroll,menusNum,diffCB,styleInfo,
           keyObserver,dLevels,roleType,callbacks,closure) {
}



Dim SmallViewport::get_inner_dim() {
  Dim ret(SM_ROW_MAX,SM_COL_MAX);
  return ret;
}



Dim SmallViewport::get_extra_dim() {
  Dim ret(SM_EXTRA_ROW,SM_EXTRA_COL);
  return ret;
}



void SmallViewport::init_sizes(Xvars& xvars,int dpyNum,WorldP world) {
  Viewport::init_sizes(xvars,dpyNum,world);

  // Statuses first to get width of viewport.
  statusesSize[dpyNum] = 
    TextPanel::get_unit(xvars.font[dpyNum],SM_STATUS_LINE_LENGTH);
  // Minus 1 because health and mass on the same line and plus .5 to
  // leave a little space.
  statusesSize[dpyNum].height = 
    (int) (statusesSize[dpyNum].height * (VW_STATUSES_NUM - .75));

  // Menus next, needed for intelsPlayingPos.
  Size oneMenuUnit = 
    ButtonPanel::get_unit(xvars.font[dpyNum],1);
  menusSize[dpyNum].height = 2 * oneMenuUnit.height;
  menusSize[dpyNum].width = arenaSize[dpyNum].width
    + statusesSize[dpyNum].width;
  
  // Intels Playing, two of them.
  intelsPlayingPos[dpyNum].x = arenaSize[dpyNum].width;
  intelsPlayingPos[dpyNum].y = 
    menusSize[dpyNum].height + statusesSize[dpyNum].height;
  intelsPlayingSize[dpyNum] = 
    TextPanel::get_unit(xvars.font[dpyNum],SM_STATUS_LINE_LENGTH);
  intelsPlayingSize[dpyNum].height *= 2;

  // Level-specific info, flush with bottom of arena.
  levelSize[dpyNum] = 
    TextPanel::get_unit(xvars.font[dpyNum],SM_STATUS_LINE_LENGTH,2);
  levelPos[dpyNum].x = arenaSize[dpyNum].width;
  levelPos[dpyNum].y = 
    menusSize[dpyNum].height + arenaSize[dpyNum].height 
    - levelSize[dpyNum].height;
    
  // Message bar at bottom.
  messageBarPos[dpyNum].x = 0;
  messageBarPos[dpyNum].y = 
    menusSize[dpyNum].height + arenaSize[dpyNum].height;
  messageBarSize[dpyNum] = TextPanel::get_unit(xvars.font[dpyNum],1,2);
  messageBarSize[dpyNum].width = arenaSize[dpyNum].width 
    + statusesSize[dpyNum].width;

  // Entire size of viewport.
  viewportSize[dpyNum].width = arenaSize[dpyNum].width
    + statusesSize[dpyNum].width;
  viewportSize[dpyNum].height = menusSize[dpyNum].height 
    + arenaSize[dpyNum].height + messageBarSize[dpyNum].height;
}



void SmallViewport::create_statuses() {
  Pos pos(arenaSize[dpyNum].width,menusSize[dpyNum].height);
  Size statusUnit;

  for (int n = 0; n < VW_STATUSES_NUM; n++) {
    if (n == statusWeapon) {
	    statusUnit = 
	      TextPanel::get_unit(xvars.font[dpyNum],SM_STATUS_LINE_LENGTH);
	    statuses[n] = 
	      new ButtonPanel(dpyNum,xvars,toplevel,
                          pos,statusUnit,
                          Viewport::panel_callback,
                          panelClosures.get(stWeapon));
	  }
    else if (n == statusItem) {
	    statusUnit = 
	      TextPanel::get_unit(xvars.font[dpyNum],SM_STATUS_LINE_LENGTH);
	    statuses[n] = 
	      new ButtonPanel(dpyNum,xvars,toplevel,
                          pos,statusUnit,
                          Viewport::panel_callback,
                          panelClosures.get(stItem));
	  }
    else if (n == statusHealth) {
	    statusUnit = 
	      TextPanel::get_unit(xvars.font[dpyNum],
                              SM_STATUS_LINE_LENGTH / 2);
	    statuses[n] = 
	      new TextPanel(dpyNum,xvars,
                        toplevel,
                        pos,statusUnit);
	  }
    else if (n == statusMass) {
	    statusUnit = 
	      TextPanel::get_unit(xvars.font[dpyNum],SM_STATUS_LINE_LENGTH);
	    Size otherStatusUnit = 
	      TextPanel::get_unit(xvars.font[dpyNum],SM_STATUS_LINE_LENGTH / 2);
	    Pos p(pos.x + otherStatusUnit.width,pos.y);
	    Size s;
	    s.width = statusUnit.width - otherStatusUnit.width;
	    s.height = statusUnit.height;
	    statuses[n] = 
	      new TextPanel(dpyNum,xvars,
                        toplevel,p,s);
	  }
    else {
	    statusUnit = 
	      TextPanel::get_unit(xvars.font[dpyNum],SM_STATUS_LINE_LENGTH);
	    statuses[n] = 
	      new TextPanel(dpyNum,xvars,toplevel,
                        pos,statusUnit);
	  }
    
    assert(statuses[n]);
    if (n != statusHealth) {
      pos.y += statusUnit.height;
    }
  }
}


 
ViewportInfo SmallViewport::get_info(Boolean smoothScroll) {
  if (smoothScroll) {
    Size visible;
    visible.set((SM_COL_MAX + 2 * SM_EXTRA_COL) * WSQUARE_WIDTH,
                (SM_ROW_MAX + 2 * SM_EXTRA_ROW) * WSQUARE_WIDTH);
    ViewportInfo ret(IT_VISION_RANGE,visible);
    return ret;
  }
  else {
    Size logicalSize;
    logicalSize.set(SM_COL_MAX * WSQUARE_WIDTH,
                    SM_ROW_MAX * WSQUARE_HEIGHT);
    Size extraSize;
    extraSize.set(SM_EXTRA_COL * WSQUARE_WIDTH,
                  SM_EXTRA_ROW * WSQUARE_HEIGHT);
    
    // Non-smooth scrolling.
    ViewportInfo ret(IT_VISION_RANGE,logicalSize,extraSize);
    return ret;
  }
}



LargeViewport::LargeViewport(int argc,char** argv,
                             Xvars& xvars,int dpyNum,
                             WorldP w,LocatorP l,
                             Boolean smoothScroll,int menusNum,
                             IDifficultyCallback* diffCB,
                             IStyleInfo* styleInfo,
                             IKeyObserver* keyObserver,
                             //                             IChatObserver* chatObserver,
                             const DifficultyLevel 
                             dLevels[DIFFICULTY_LEVELS_NUM],
                             RoleType roleType,
                             ViewportCallback callbacks[VIEWPORT_CB_NUM],
                             void* closure) 
: Viewport(argc,argv,xvars,dpyNum,w,l,smoothScroll,
           menusNum,diffCB,styleInfo,
           keyObserver,dLevels,roleType,callbacks,closure) {
}



Dim LargeViewport::get_inner_dim() {
  Dim ret(LG_ROW_MAX,LG_COL_MAX);
  return ret;
}



Dim LargeViewport::get_extra_dim() {
  Dim ret(LG_EXTRA_ROW,LG_EXTRA_COL);
  return ret;
}



void LargeViewport::init_sizes(Xvars& xvars,int dpyNum,WorldP world) {
  Viewport::init_sizes(xvars,dpyNum,world);

  // Menus.
  Size oneMenuUnit = 
    ButtonPanel::get_unit(xvars.font[dpyNum],1);
  menusSize[dpyNum].width = arenaSize[dpyNum].width;
  menusSize[dpyNum].height = 2 * oneMenuUnit.height;

  // Get height of 2 statuses.
  Size statusUnit = 
    TextPanel::get_unit(xvars.font[dpyNum],1,1);
  statusUnit.height *= 2;

  // Level-specific info.
  levelSize[dpyNum] = 
    TextPanel::get_unit(xvars.font[dpyNum],LG_LEVEL_LINE_LENGTH,2);
  levelSize[dpyNum].height = statusUnit.height;
  levelPos[dpyNum].x = arenaSize[dpyNum].width - levelSize[dpyNum].width;
  levelPos[dpyNum].y = menusSize[dpyNum].height + arenaSize[dpyNum].height;

  // Status rectangles.
  statusesSize[dpyNum].height = statusUnit.height;
  statusesSize[dpyNum].width = arenaSize[dpyNum].width 
    - levelSize[dpyNum].width;

  // Intels playing.
  intelsPlayingSize[dpyNum] = 
    TextPanel::get_unit(xvars.font[dpyNum],LG_INTELS_LINE_LENGTH);
  intelsPlayingSize[dpyNum].height *= 2;
  intelsPlayingPos[dpyNum].x = 
    arenaSize[dpyNum].width - intelsPlayingSize[dpyNum].width;
  intelsPlayingPos[dpyNum].y = levelPos[dpyNum].y + levelSize[dpyNum].height;

  // Message bar at bottom.
  messageBarPos[dpyNum].x = 0;
  messageBarPos[dpyNum].y = intelsPlayingPos[dpyNum].y;
  messageBarSize[dpyNum].width = intelsPlayingPos[dpyNum].x;
  messageBarSize[dpyNum].height = intelsPlayingSize[dpyNum].height;

  viewportSize[dpyNum].width = arenaSize[dpyNum].width;
  viewportSize[dpyNum].height = 
    menusSize[dpyNum].height 
    + arenaSize[dpyNum].height 
    + levelSize[dpyNum].height
    + intelsPlayingSize[dpyNum].height;
}



void LargeViewport::create_statuses() {
  Pos pos(0,menusSize[dpyNum].height + arenaSize[dpyNum].height);  
  Size statusUnit[4];
  int n;
  for (n = 0; n < 4; n++) {
    statusUnit[n].height = statusesSize[dpyNum].height / 2;
  }
  statusUnit[0].width = (int)(0.29 * statusesSize[dpyNum].width);
  statusUnit[1].width = (int)(0.29 * statusesSize[dpyNum].width);
  statusUnit[2].width = (int)(0.18 * statusesSize[dpyNum].width);
  statusUnit[3].width = statusesSize[dpyNum].width - statusUnit[0].width
    - statusUnit[1].width - statusUnit[2].width;
                 

  // First row.
  n = 0;
  statuses[statusWeapon] = 
    new ButtonPanel(dpyNum,xvars,toplevel,
                    pos,statusUnit[n],
                    Viewport::panel_callback,
                    panelClosures.get(stWeapon));
  pos.x += statusUnit[n++].width;

  statuses[statusName] = 
    new TextPanel(dpyNum,xvars,toplevel,
                  pos,statusUnit[n]);
  pos.x += statusUnit[n++].width;

  statuses[statusHealth] = 
    new TextPanel(dpyNum,xvars,toplevel,
                  pos,statusUnit[n]);
  pos.x += statusUnit[n++].width;

  statuses[statusLivesHKills] = 
    new TextPanel(dpyNum,xvars,toplevel,
                  pos,statusUnit[n]);
  pos.x = 0;
  n = 0;
  pos.y += statusUnit[n].height;


  // Second row.
  statuses[statusItem] = 
    new ButtonPanel(dpyNum,xvars,toplevel,
                    pos,statusUnit[n],
                    Viewport::panel_callback,
                    panelClosures.get(stItem));
  pos.x += statusUnit[n++].width;
  
  statuses[statusClassName] = 
    new TextPanel(dpyNum,xvars,toplevel,
                  pos,statusUnit[n]);
  pos.x += statusUnit[n++].width;

  statuses[statusMass] = 
    new TextPanel(dpyNum,xvars,toplevel,
                  pos,statusUnit[n]);
  pos.x += statusUnit[n++].width;

  statuses[statusKillsMKills] = 
    new TextPanel(dpyNum,xvars,toplevel,
                  pos,statusUnit[n]);
}



ViewportInfo LargeViewport::get_info(Boolean smoothScroll) {
  if (smoothScroll) {
    Size visible;
    visible.set((LG_COL_MAX + 2 * LG_EXTRA_COL) * WSQUARE_WIDTH,
                (LG_ROW_MAX + 2 * LG_EXTRA_ROW) * WSQUARE_WIDTH);
    ViewportInfo ret(IT_VISION_RANGE,visible);
    return ret;
  }
  else {
    Size logicalSize;
    logicalSize.set(LG_COL_MAX * WSQUARE_WIDTH,
                    LG_ROW_MAX * WSQUARE_HEIGHT);
    Size extraSize;
    extraSize.set(LG_EXTRA_COL * WSQUARE_WIDTH,
                  LG_EXTRA_ROW * WSQUARE_HEIGHT);
    
    // Non-smooth scrolling.
    ViewportInfo ret(IT_VISION_RANGE,logicalSize,extraSize);
    return ret;
  }
}


