/* $Id: board.cc,v 1.94 2003/08/17 23:10:09 bergo Exp $ */

/*

    eboard - chess client
    http://eboard.sourceforge.net
    Copyright (C) 2000-2003 Felipe Paulo Guazzi Bergo
    bergo@seul.org

    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; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "eboard.h"
#include "board.h"
#include "chess.h"
#include "global.h"
#include "text.h"
#include "protocol.h"

#include "mainwindow.h"

Board * Board::PopupOwner = 0;

// --- Board --------------------------

Board::Board() : WidgetProxy() {
  int i;

  global.debug("Board","Board");

  global.addPieceClient(this);

  cur=0;
  clock.setHost(this);

  premoveq=0;
  mygame=0;
  clock_ch=1;
  hilite_ch=1;
  allowMove=true;
  gotAnimationLoop=0;
  update_queue=false;
  LockAnimate=0;
  FreeMove=false;
  dr_active=false;
  FlipInvert=false;
  EditMode=false;

  AnimationTimeout = -1;

  for(i=0;i<6;i++)
    info[i].erase();

  currently_rendered.setPiece(0,0,EMPTY);

  repaint_due=false;
  frozen_lvl=0;

  f1=global.loadFont(EF_PlayerFont);
  f2=global.loadFont(EF_ClockFont);
  f3=global.loadFont(EF_InfoFont);

  if (global.ShowCoordinates) { borx=20; bory=20; } else { borx=bory=0; }
  morey=0;

  if ((!f1)||(!f2)||(!f3)) {
    cerr << _("<Board::Board> ** failed to load one or more board fonts - install X11 75 dpi and 100 dpi fonts, restart X, and try again.\n");
    exit(2);
  }

  global.BoardList.push_back(this);

  canselect=true;
  sel_color=0xffff00;
  sp=0;
  flipped=false;

  widget=yidget=gtk_drawing_area_new();
  gtk_widget_set_events(yidget,GDK_EXPOSURE_MASK|GDK_BUTTON_PRESS_MASK|
			GDK_BUTTON_RELEASE_MASK|GDK_BUTTON1_MOTION_MASK|
			GDK_POINTER_MOTION_HINT_MASK);

  sqside=59;
  sqw = 2*borx + 8*sqside + 1;
  sqh = 2*bory + morey + 8*sqside + 1;

  buffer=gdk_pixmap_new(MainWindow::RefWindow,sqw,sqh,-1);
  clkbar=gdk_pixmap_new(MainWindow::RefWindow,250,sqh,-1);

  clkgc=gdk_gc_new(clkbar);
  wgc=0;
  
  scrap=(rgbptr)g_malloc(sqw*sqh*3);
  memset(scrap,0,sqw*sqh*3);

  gtk_signal_connect (GTK_OBJECT (yidget), "expose_event",
                      (GtkSignalFunc) board_expose_event, (gpointer) this);
  gtk_signal_connect (GTK_OBJECT (yidget), "configure_event",
                      (GtkSignalFunc) board_configure_event, (gpointer) this);
  gtk_signal_connect (GTK_OBJECT (yidget), "button_press_event",
                      (GtkSignalFunc) board_button_press_event,
		      (gpointer) this);
  gtk_signal_connect (GTK_OBJECT (yidget), "button_release_event",
                      (GtkSignalFunc) board_button_release_event,
		      (gpointer) this);
  gtk_signal_connect (GTK_OBJECT (yidget), "motion_notify_event",
                      (GtkSignalFunc) board_motion_event,
		      (gpointer) this);

  gshow(widget);
}

bool Board::hasGame() {
  return(mygame!=0);
}

Board::~Board() {
  unsigned int i;
  global.debug("Board","~Board");
  global.removeBoard(this);
  global.removePieceClient(this);
  if (f1)
    gdk_font_unref(f1);
  if (f2)
    gdk_font_unref(f2);
  if (f3)
    gdk_font_unref(f3);
  for(i=0;i<LastMove.size();i++)
    delete(LastMove[i]);
  LastMove.clear();
  if (cur)
    delete cur;
  if (wgc)
    gdk_gc_destroy(wgc);
  gdk_gc_destroy(clkgc);
  gdk_pixmap_unref(clkbar);
  gdk_pixmap_unref(buffer);
  if (AnimationTimeout >= 0)
    gtk_timeout_remove(AnimationTimeout);
}

void Board::reloadFonts() {
  if (f1) gdk_font_unref(f1);
  if (f2) gdk_font_unref(f2);
  if (f3) gdk_font_unref(f3);

  f1=global.loadFont(EF_PlayerFont);
  f2=global.loadFont(EF_ClockFont);
  f3=global.loadFont(EF_InfoFont);

  if ((!f1)||(!f2)||(!f3)) {
    cerr << _("<Board::Board> ** failed to load one or more board fonts\n");
    exit(2);
  }
  clock_ch=1;
  queueRepaint();
}

/*
  Resets the board state regarding any previous game played
  here.

  This method is called by the protocol classes when a new
  game is being attached to the board (e.g. when a engine is
  run, when a new game starts on FICS, etc.
*/

void Board::reset() {
  unsigned int i;
  global.debug("Board","reset");
  while(gotAnimationLoop)
    global.WrappedMainIteration();

  if (mygame) {
    if ( (mygame->getBoard() == this) && (mygame->isOver()) )
      mygame->setBoard(0);
  }

  update_queue=false;

  while(!UpdateStack.empty())
    UpdateStack.pop();

  sp=0;
  mygame=0;
  premoveq=0;
  flipped=false;
  clock_ch=1;
  hilite_ch=1;
  allowMove=true;
  dr_active=false;
  LockAnimate=0;
  FlipInvert=false;
  for(i=0;i<6;i++)
    info[i].erase();
  clock.setMirror(0);
  clock.setClock(0,0,0,1);
  currently_rendered.invalidate();
  position.setStartPos();
  for(i=0;i<LastMove.size();i++)
    delete(LastMove[i]);
  LastMove.clear();
  cleanTargets();
  queueRepaint();
}

void Board::setFlipInversion(bool v) {
  FlipInvert=v;
  currently_rendered.invalidate();
  clock_ch=1;
  hilite_ch=1;
  queueRepaint();
}

bool Board::getFlipInversion() {
  return(FlipInvert);
}

void Board::pieceSetChanged() {
 if (cur)
    delete cur;
  cur=new PieceSet(global.pieceset);
  currently_rendered.invalidate();
  if (global.ShowCoordinates) { borx=20; bory=20; } else { borx=bory=0; }
  morey=0;
  queueRepaint();
}

ChessGame * Board::getGame() {
  return(mygame);
}

void Board::setGame(ChessGame *game) {
  global.debug("Board","setGame");
  mygame=game;
  clock_ch=1;
  queueRepaint();

  // update button enabling/disabling
  global.ebook->pretendPaneChanged();
}

void Board::show() {
  // already called on creation
}

void Board::setSensitive(bool state) {
  canselect=state;
  if ((!canselect)&&(sp)) {
    sp=0;
    hilite_ch=1;
    queueRepaint();
  }
}

void Board::clearSel() {
  if (sp) {
    sp=0;
    hilite_ch=1;
    queueRepaint();
  }
}

void Board::setSelColor(int color) {
  sel_color=color;
  if (sp) {
    hilite_ch=1;
    queueRepaint();
  }
}

void Board::setFlipped(bool flip) {
  bool ov;
  ov=flipped;
  flipped=flip;
  if (flipped!=ov) {
    currently_rendered.invalidate();
    clock_ch=1;
    queueRepaint();
  }
}

void Board::supressNextAnimation() {
  ++LockAnimate;
}

void Board::updateClock() {
  if (yidget->window) {
    clock_ch=1;  
    renderClock();
    gdk_draw_pixmap(yidget->window,
		    yidget->style->fg_gc[GTK_WIDGET_STATE (yidget)],
		    clkbar,
		    0, 0,
		    Width(), 0,250, Height());
  }
}

void RootBoard::clockString(int val,char *z) {
  int cv,neg,H,M,S;
  cv=val;
  if (cv<0) {
    cv=-cv;
    neg=1;
  } else
    neg=0;
  
  H=cv/3600;
  M=(cv-3600*H)/60;
  S=(cv-3600*H)%60;

  if (H) {
    if (neg)
      sprintf(z,"-%d:%.2d:%.2d",H,M,S);
    else
      sprintf(z,"%d:%.2d:%.2d",H,M,S);
  } else {
    if (neg)
      sprintf(z,"-%.2d:%.2d",M,S);
    else
      sprintf(z,"%.2d:%.2d",M,S);
  }
}

void Board::repaint() {
  global.debug("Board","repaint");
  WidgetProxy::repaint();
}

void Board::renderClock() {
  GdkGC *gc=clkgc;
  int i,j,k,sq,bh,ch,th;
  char z[64];
  bool ef, lowtime=false;

  if (!clock_ch)
    return;

  if (mygame)
    if (mygame->AmPlaying)
      lowtime=clock.lowTimeWarning(mygame->MyColor);

  sq=sqside;

  C.prepare(clkbar,gc,0,0,250,sqh);
  C.setColor(0);
  C.drawRect(0,0,C.W,C.H,true);
  C.H = Height();

  bh=56; ch=20; th=36; // font heights
  C.setFont(0,f1);
  C.setFont(1,f2);
  C.setFont(2,f3);

  if (sq*3 < bh) {
    bh=28; ch=14; th=14;
    C.setFont(0,f3);
    C.setFont(1,f3);
    C.setFont(2,f3);
  }

  // draw the clock time and White/Black labels

  ef=effectiveFlip();
  if (ef) i= C.H - (th+10); else i = ch+10;
  j=16;

  C.setColor(0xffffff);
  if (clock.getActive()>0) {
    if (lowtime) C.setColor(0xff8080);
    C.drawRect(15, i - (ch+2), 200-30, bh+4, true);
    C.setColor(0);
  }

  C.drawString(j,i,0,_("Black"));
  clockString(clock.getValue(1),z);
  C.drawString(j,i+th,1,z);

  if (ef) i=ch+10; else i= C.H - (th+10);

  C.setColor(0xffffff);
  if (clock.getActive()<0) {
    if (lowtime) C.setColor(0xff8080);
    C.drawRect(15, i - (ch+2), 200-30, bh+4, true);
    C.setColor(0);
  }
  C.drawString(j,i,0,_("White"));
  clockString(clock.getValue(0),z);
  C.drawString(j,i+th,1,z);

  C.consumeTop(bh+20);
  C.consumeBottom(bh+20);

  // if we have enough space, render player names too
  if ( (mygame) && (C.H > 60) ) {
    C.setColor(0xffffff);
    C.drawString(j,C.Y + ch,0, mygame->getPlayerString(ef?0:1) );
    C.drawString(j,C.Y + C.H,0, mygame->getPlayerString(ef?1:0) );
    C.consumeTop(ch+8);
    C.consumeBottom(ch+8);
  }

  cleanTargets();

  // now render the information lines

  if (mygame) {
    C.setColor(0xffffff);

    for(k=0;k<5;k++) {
      if (C.H < 16) break;
      C.drawString(j, C.Y + 16, 2, info[k] );
      C.consumeTop(16);
    }
    
    // house string is info[5]

    if (!info[5].empty()) {      
      if ((!global.DrawHouseStock)||(C.H < 96)) {
	if (C.H >= 16) {
	  C.drawString(j,C.Y+16,2,info[5]);
	  C.consumeTop(16);
	}
      } else {
	// draw pieces in stock
	piece pk[5]={PAWN,ROOK,KNIGHT,BISHOP,QUEEN};
	int st[5][2];
	int si,sj,we,be;

	// build stock info from string
	for(si=0;si<5;si++) { st[si][0]=st[si][1]=0; }
	sj=0;
	k=info[5].length();
	for(i=0;i<k;i++) {
	  switch(info[5][i]) {
	  case 'P': st[0][sj]++; break;
	  case 'R': st[1][sj]++; break;
	  case 'N': st[2][sj]++; break;
	  case 'B': st[3][sj]++; break;
	  case 'Q': st[4][sj]++; break;
	  case ']': sj=1; break;
	  }
	}

	// draw stock pieces
	int eph, my;

	if (global.UseVectorPieces)
	  eph=40+8;
	else
	  eph=cur->side + 8;

	if (ef) { we=0; be=eph; } else { we=eph; be=0; }

	my=2*eph;
	C.setColor(0x505050);
	for(si=2;si<my+2;si+=2)
	  C.drawLine(5,C.Y+si,235,C.Y+si);


	for(si=3;si<my+2;si+=2) {
	  int lv;
	  lv=my/2 - si;
	  if (lv<0) lv=-lv;
	  lv*=2;
	  if (lv>0xc0) lv=0xc0;
	  lv=0xc0-lv;
	  lv=(lv<<16)|(lv<<8)|lv;
	  C.setColor(lv);

	  C.drawLine(5,C.Y+si,235,C.Y+si);
	}

	C.setColor(0x555555);
	for(si=0;si<5;si++)
	  C.drawLine(40*(si+1),C.Y+2,40*(si+1),C.Y+my+1);

	for(si=0;si<5;si++) {
	  if (st[si][0]) {

	    if (global.UseVectorPieces)
	      global.vpieces.drawPiece(clkbar,gc,40,40*si,C.Y+we,pk[si]|WHITE);
	    else
	      cur->drawPiece(pk[si]|WHITE,clkbar,gc,40*si, C.Y+we);

	    sprintf(z,"%d",st[si][0]);

	    shadyText(40*si+20+1, C.Y+eph+we, 2, z);

	    /*
	    C.setColor(0);
	    C.drawString(40*si+20+1, C.Y+eph+we, 2, z);
	    C.setColor(0xffffff);
	    C.drawString(40*si+20, C.Y+eph+we, 2, z);
	    */

	    if (mygame->MyColor==WHITE)
	      addTarget(new DropSource(pk[si]|WHITE,Width()+40*si,C.Y+we,40,eph));
	  }
	  if (st[si][1]) {

	    if (global.UseVectorPieces)
	      global.vpieces.drawPiece(clkbar,gc,40,40*si,C.Y+be,pk[si]|BLACK);
	    else
	      cur->drawPiece(pk[si]|BLACK,clkbar,gc,40*si, C.Y+be);

	    sprintf(z,"%d",st[si][1]);

	    shadyText(40*si+20+1, C.Y+eph+be, 2, z);

	    /*
	    C.setColor(0);
	    C.drawString(40*si+20+1,C.Y+eph+be, 2, z);
	    C.setColor(0xffffff);
	    C.drawString(40*si+20,C.Y+eph+be, 2, z);
	    */

	    if (mygame->MyColor==BLACK)
	      addTarget(new DropSource(pk[si]|BLACK,Width()+40*si,C.Y+be,40,eph));
	  }
	} // for

	C.consumeTop(2*eph+8);
      } // text | graphic stock
    } // if !info[5].empty

    // draw mouse if protocol menu available
    if (global.protocol)
      if (global.protocol->getPlayerActions()) {
	C.setColor(0xffffff);
	C.drawRect(170,C.Y+8,16,18,true);
	C.drawLine(177,C.Y+8,177,C.Y+4);
	C.setColor(0xffcc00);
	C.drawRect(180,C.Y+8,6,8,true);
	C.setColor(0);
	C.drawLine(170,C.Y+16,186,C.Y+16);
	C.drawLine(175,C.Y+8,175,C.Y+16);
	C.drawLine(180,C.Y+8,180,C.Y+16);
	C.consumeTop(20);
      }	
  }

  if (C.H > 32) {
    if (position.getAnnotation()) {
      C.consumeTop(16);
      j=16;

      C.setColor(0x252535);
      C.drawRect(j-5,C.Y,170,C.H,true);
      C.setColor(0x4545ff);
      C.drawRect(j-5,C.Y,170,C.H,false);

      C.setColor(0xffffff);
      C.drawBoxedString(j,C.Y,160,C.H,2, position.getAnnotation(), " ");
    }
  }

  clock_ch=0;
}

ChessClock * Board::getClock() {
  return(&clock);
}

int Board::getClockValue(bool white) {
  return(clock.getValue(white?0:1));
}

void Board::setClock(int wsecs,int bsecs,int actv,int cdown) {
  global.debug("Board","setClock");
  clock_ch=1;
  clock.setClock(wsecs,bsecs,actv,cdown);
  queueRepaint();
}

void Board::shadyText(int x,int y, int f, char *z) {
  int i,j;
  
  C.setColor(0);
  for(i=-2;i<=3;i++)
    for(j=-2;j<=2;j++)
      C.drawString(x+i,y+j,f,z);

  C.setColor(0x808080);
  C.drawString(x,y,f,z);
  C.setColor(0xffffff);
  C.drawString(x+1,y,f,z);
}

void Board::freeze() {
  frozen_lvl++;
}

void Board::thaw() {
  frozen_lvl--;
  if (frozen_lvl < 0) frozen_lvl=0;
  if ((!frozen_lvl)&&(repaint_due)) {
    repaint();
    repaint_due=false;
  }
}

void Board::invalidate() {
  currently_rendered.invalidate();
  queueRepaint();
}

void Board::queueRepaint() {
  global.debug("Board","queueRepaint");
  if (frozen_lvl)
    repaint_due=true;
  else
    repaint();
}

void Board::setInfo(int idx,char *s) {
  global.debug("Board","setInfo",s);
  info[idx%6]=s;
  clock_ch=1;
  queueRepaint();
}

void Board::setInfo(int idx,string &s) {
  global.debug("Board","setInfo/2");
  info[idx%6] = s;
  clock_ch=1;
  queueRepaint();
}

void Board::update(bool sndflag) {
  unsigned int i,rate;
  AnimatedPiece *ap;  
  Position rpv;
  bool ef;
  bool sok=false;

  global.debug("Board","update");

  if (!mygame)
    return;

  if ((global.AnimateMoves)&&(gotAnimationLoop)) {
    update_queue=true;
    UpdateStack.push(sndflag);
    return;
  }  

  position = mygame->getCurrentPosition();
  rpv = mygame->getPreviousPosition();
  ef=effectiveFlip();

  if ((global.HilightLastMove)||(global.AnimateMoves)) {
    rpv.diff(position,LastMove);
    if (LockAnimate)
      --LockAnimate;
    else
      if (currently_rendered != position)
	if ((global.AnimateMoves)&&(gdk_window_is_viewable(yidget->window))) {
	  sok=true; // sound ok
	  fake=position;
	  fake.intersection(rpv);
	  rate = global.SmootherAnimation ? 5 : 3;
	  for(i=0;i<LastMove.size();i++) {
	    if (ef)
	      ap=new FlatAnimatedPiece(LastMove[i]->Piece,
				       borx+sqside*(7-LastMove[i]->SX),
				       morey+bory+sqside*(LastMove[i]->SY),
				       borx+sqside*(7-LastMove[i]->DX),
				       morey+bory+sqside*(LastMove[i]->DY),
				       rate*LastMove[i]->distance(),sndflag);
	    else
	      ap=new FlatAnimatedPiece(LastMove[i]->Piece,
				       borx+sqside*(LastMove[i]->SX),
				       morey+bory+sqside*(7-LastMove[i]->SY),
				       borx+sqside*(LastMove[i]->DX),
				       morey+bory+sqside*(7-LastMove[i]->DY),
				       rate*LastMove[i]->distance(),sndflag);
	    ap->create(yidget->window,cur,sqside);
	    animes.push_back(ap);
	  }
	  if ((!LastMove.size())&&(sndflag))
	    global.flushSound();
	  if ((!animes.empty())&&(!gotAnimationLoop)) {
	    int tag;
	    gotAnimationLoop=1;
	    currently_rendered.invalidate();
	    board_configure_event(yidget,0,(gpointer)this);
	    if (rate==3)
	      tag=gtk_timeout_add(global.UseVectorPieces?40:70,
				  board_animate,this);
	    else
	      tag=gtk_timeout_add(global.UseVectorPieces?30:40,
				  board_animate,this);
	    AnimationTimeout = tag;
	  }
	}
  }
  if (!global.AnimateMoves)
    queueRepaint();

  /* force redraw of background of dragging */
  if (dr_active)
    dr_step=0;

  update_queue=false;

  if ((!sok)&&(global.BeepWhenOppMoves)&&(sndflag))
    global.flushSound();
}

void Board::sendMove() {
  global.debug("Board","sendMove");
  if ((sp<2)||(!mygame)) return;
  if ((allowMove)||(FreeMove)) {
    if ((global.CheckLegality)&&(!FreeMove))
      if (!position.isMoveLegalCartesian(ax[0],ay[0],ax[1],ay[1],
					 mygame->MyColor,mygame->Variant)) {
	char z[128];
	sprintf(z,_("Illegal Move %c%d%c%d (Legality Checking On)"),
		'a'+ax[0],ay[0]+1,'a'+ax[1],ay[1]+1);
	global.status->setText(z,15);
	goto sendmove_cleanup;	
      }      
    position.moveCartesian(ax[0],ay[0],ax[1],ay[1],REGULAR,true);
    mygame->sendMove(ax[0],ay[0],ax[1],ay[1]);
    allowMove=false;
  } else if (global.Premove) {
    premoveq=1;
    pmx[0]=ax[0];
    pmy[0]=ay[0];
    pmx[1]=ax[1];
    pmy[1]=ay[1];
    pmp=EMPTY;
  }
 sendmove_cleanup:
  sp=0;
  hilite_ch=1;
  repaint();
}

void Board::setCanMove(bool value) {
  bool oldv;
  global.debug("Board","setCanMove");

  oldv=allowMove;
  allowMove=value;

  // commit premove if any
  if ((mygame)&&(global.Premove)&&(!oldv)&&(value)&&(premoveq)) {
    repaint();

    if (pmp==EMPTY) {
      if (position.isMoveLegalCartesian(pmx[0],pmy[0],pmx[1],pmy[1],
					mygame->MyColor,mygame->Variant)) {
	position.moveCartesian(pmx[0],pmy[0],pmx[1],pmy[1],REGULAR,true);
	mygame->sendMove(pmx[0],pmy[0],pmx[1],pmy[1]);
      }
    } else {
      if (position.isDropLegal(pmp,pmx[1],pmy[1],
			       mygame->MyColor,mygame->Variant)) {
	if (pmp&COLOR_MASK == 0) pmp|=mygame->MyColor;
	position.moveDrop(pmp, pmx[1], pmy[1]);
	mygame->sendDrop(pmp, pmx[1], pmy[1]);
      }
    }
    premoveq=0;
    sp=0;
    hilite_ch=1;
    repaint();
  }
}

void Board::stopClock() {
  clock_ch=1;
  clock.setClock(clock.getValue(0),clock.getValue(1),0,1);
  queueRepaint();
}

void Board::openMovelist() {
  global.debug("Board","openMovelist");
  if (!mygame)
    return;
  mygame->openMoveList();
}

void Board::walkBack1() {
  if (!mygame)
    return;
  freeze();
  mygame->goBack1();
  position= mygame->getCurrentPosition();
  setInfo(2,position.getLastMove());
  setInfo(4,position.getMaterialString(mygame->Variant));
  setInfo(5,position.getHouseString());
  queueRepaint();
  thaw();
}

void Board::walkBackAll() {
  if (!mygame)
    return;
  freeze();
  mygame->goBackAll();
  position= mygame->getCurrentPosition();
  setInfo(2,position.getLastMove());
  setInfo(4,position.getMaterialString(mygame->Variant));
  setInfo(5,position.getHouseString());
  queueRepaint();
  thaw();
}

void Board::walkForward1() {
  if (!mygame)
    return;
  freeze();
  mygame->goForward1();
  position= mygame->getCurrentPosition();
  setInfo(2,position.getLastMove());
  setInfo(4,position.getMaterialString(mygame->Variant));
  setInfo(5,position.getHouseString());
  queueRepaint();
  thaw();
}

void Board::walkForwardAll() {
  if (!mygame)
    return;
  freeze();
  mygame->goForwardAll();
  position=mygame->getCurrentPosition();
  setInfo(2,position.getLastMove());
  setInfo(4,position.getMaterialString(mygame->Variant));
  setInfo(5,position.getHouseString());
  queueRepaint();
  thaw();
}

void Board::outlineRectangle(GdkGC *gc, int x,int y,int color,int pen) {
  int i,j,k,X,Y,S;
  gdk_rgb_gc_set_foreground(gc,color);
  j=x; k=y; if (effectiveFlip()) j=7-j; else k=7-k;
  if ((global.UseVectorPieces)||(!cur->extruded))
    for(i=0;i<pen;i++)
      gdk_draw_rectangle(yidget->window,gc,FALSE,borx+j*sqside+i,
			 morey+bory+k*sqside+i,
			 sqside-2*i,sqside-2*i);
  else {
    X=borx+j*sqside; Y=morey+bory+k*sqside; S=sqside;
    gdk_draw_rectangle(yidget->window,gc,TRUE,X,Y,pen,sqside);
    gdk_draw_rectangle(yidget->window,gc,TRUE,X+S-pen,Y,pen,sqside);
    gdk_draw_rectangle(yidget->window,gc,TRUE,X,Y,3*pen,pen);
    gdk_draw_rectangle(yidget->window,gc,TRUE,X+S-3*pen,Y,3*pen,pen);
    gdk_draw_rectangle(yidget->window,gc,TRUE,X,Y+S-pen,3*pen,pen);
    gdk_draw_rectangle(yidget->window,gc,TRUE,X+S-3*pen,Y+S-pen,3*pen,pen);
  }
      
}

void Board::drawBall(GdkGC *gc, int x,int y,int color,int radius) {
  int j,k;
  j=x; k=y; if (effectiveFlip()) j=7-j; else k=7-k;
  gdk_rgb_gc_set_foreground(gc,color);
  gdk_draw_arc(yidget->window,gc,FALSE,
	       borx+j*sqside+sqside/2-radius,
	       morey+bory+k*sqside+sqside/2-radius,
	       radius*2, radius*2, 0, 360*64);
}

void Board::drop(piece p) {
  global.debug("Board","drop");
  if (!mygame) return;
  if (p&COLOR_MASK == 0) p|=mygame->MyColor;
  if ((!allowMove)&&(!FreeMove)) {
    if (global.Premove) {
      premoveq=1;
      pmx[0]=-1;
      pmx[1]=dropsq[0];
      pmy[1]=dropsq[1];
      pmp=p;
    } else
      return;
  } else {
    if ((global.CheckLegality)&&(!FreeMove))
      if (!position.isDropLegal(p&PIECE_MASK, dropsq[0], dropsq[1],
				mygame->MyColor,mygame->Variant))
	{
	  char z[128];
	  sprintf(z,_("Illegal Drop on %c%d (Legality Checking On)"),
		  'a'+dropsq[0],dropsq[1]+1);
	  global.status->setText(z,15);
	  goto drop_cleanup;
	}
    position.moveDrop(p, dropsq[0], dropsq[1]);
    mygame->sendDrop(p, dropsq[0], dropsq[1]);
  }

 drop_cleanup:
  sp=0;
  hilite_ch=1;
  repaint();
}

// for crazyhouse/bughouse
void Board::popupDropMenu(int col,int row,int sx,int sy,guint32 etime) {
  GtkWidget *popmenu;
  GtkWidget *mi[7];
  Position pos;
  int i;
  char z[64];

  // sanity checks
  if (!allowMove)       return;
  if (!mygame)          return;
  if (mygame->isOver()) return;
  if ( (mygame->Variant != CRAZYHOUSE)&&(mygame->Variant != BUGHOUSE) )
    return;

  dropsq[0]=col;
  dropsq[1]=row;

  popmenu=gtk_menu_new();

  pos=mygame->getLastPosition();
  
  mi[0]=gtk_menu_item_new_with_label(_("Drop Piece:"));
  gtk_widget_set_sensitive(mi[0],FALSE);
  mi[1]=gtk_menu_item_new();

  sprintf(z,_("Pawn    %d"),i=pos.getStockCount(PAWN|mygame->MyColor));
  mi[2]=gtk_menu_item_new_with_label(z);
  if (!i) gtk_widget_set_sensitive(mi[2],FALSE);

  sprintf(z,_("Rook    %d"),i=pos.getStockCount(ROOK|mygame->MyColor));
  mi[3]=gtk_menu_item_new_with_label(z);
  if (!i) gtk_widget_set_sensitive(mi[3],FALSE);

  sprintf(z,_("Knight  %d"),i=pos.getStockCount(KNIGHT|mygame->MyColor));
  mi[4]=gtk_menu_item_new_with_label(z);
  if (!i) gtk_widget_set_sensitive(mi[4],FALSE);

  sprintf(z,_("Bishop  %d"),i=pos.getStockCount(BISHOP|mygame->MyColor));
  mi[5]=gtk_menu_item_new_with_label(z);
  if (!i) gtk_widget_set_sensitive(mi[5],FALSE);

  sprintf(z,_("Queen   %d"),i=pos.getStockCount(QUEEN|mygame->MyColor));
  mi[6]=gtk_menu_item_new_with_label(z);
  if (!i) gtk_widget_set_sensitive(mi[6],FALSE);

  for(i=0;i<7;i++) {
    gshow(mi[i]);
    gtk_menu_append(GTK_MENU(popmenu),mi[i]);
  }

  gtk_signal_connect(GTK_OBJECT(mi[2]),"activate",
		     GTK_SIGNAL_FUNC(drop_callbackP),
		     (gpointer)this);
  gtk_signal_connect(GTK_OBJECT(mi[3]),"activate",
		     GTK_SIGNAL_FUNC(drop_callbackR),
		     (gpointer)this);
  gtk_signal_connect(GTK_OBJECT(mi[4]),"activate",
		     GTK_SIGNAL_FUNC(drop_callbackN),
		     (gpointer)this);
  gtk_signal_connect(GTK_OBJECT(mi[5]),"activate",
		     GTK_SIGNAL_FUNC(drop_callbackB),
		     (gpointer)this);
  gtk_signal_connect(GTK_OBJECT(mi[6]),"activate",
		     GTK_SIGNAL_FUNC(drop_callbackQ),
		     (gpointer)this);

  gtk_menu_popup(GTK_MENU(popmenu),0,0,0,0,3,etime);
}

void Board::popupProtocolMenu(int x,int y, guint32 etime) {
  GtkWidget *mi, *hs, *popmenu;
  vector<string *> *v;
  char z[64];
  unsigned int i;
  
  if (!global.protocol) return;
  if (!mygame) return;
  if (global.protocol->getPlayerActions() == NULL) return;

  popmenu=gtk_menu_new();
  PopupOwner = this;

  // players
  v=global.protocol->getPlayerActions();

  for(i=0;i<v->size();i++) {
    sprintf(z,"%s: %s",mygame->PlayerName[0], ((*v)[i])->c_str() );
    mi=gtk_menu_item_new_with_label(z);
    gshow(mi);
    gtk_menu_append(GTK_MENU(popmenu),mi);

    gtk_signal_connect(GTK_OBJECT(mi),"activate",
		       GTK_SIGNAL_FUNC(menu_whitep),
		       (gpointer) ( (*v)[i] ) );    
  }

  mi=gtk_menu_item_new();
  hs=gtk_hseparator_new();
  gtk_container_add(GTK_CONTAINER(mi),hs);
  Gtk::show(hs,mi,0);
  gtk_menu_append(GTK_MENU(popmenu),mi);

  for(i=0;i<v->size();i++) {
    sprintf(z,"%s: %s",mygame->PlayerName[1], ((*v)[i])->c_str() );
    mi=gtk_menu_item_new_with_label(z);
    gshow(mi);
    gtk_menu_append(GTK_MENU(popmenu),mi);

    gtk_signal_connect(GTK_OBJECT(mi),"activate",
		       GTK_SIGNAL_FUNC(menu_blackp),
		       (gpointer) ( (*v)[i] ) );
  }

  if (global.protocol->getGameActions() != 0) {
    mi=gtk_menu_item_new();
    hs=gtk_hseparator_new();
    gtk_container_add(GTK_CONTAINER(mi),hs);
    Gtk::show(hs,mi,0);
    gtk_menu_append(GTK_MENU(popmenu),mi);

    v=global.protocol->getGameActions();

    for(i=0;i<v->size();i++) {
      sprintf(z,_("Game #%d: %s"),mygame->GameNumber, ((*v)[i])->c_str() );
      mi=gtk_menu_item_new_with_label(z);
      gshow(mi);
      gtk_menu_append(GTK_MENU(popmenu),mi);
      
      gtk_signal_connect(GTK_OBJECT(mi),"activate",
			 GTK_SIGNAL_FUNC(menu_gamep),
			 (gpointer) ( (*v)[i] ) );
    }
  }

  gtk_menu_popup(GTK_MENU(popmenu),0,0,0,0,3,etime);
}

void Board::dump() {
  cerr.setf(ios::hex,ios::basefield);
  cerr.setf(ios::showbase);

  cerr << "[board " << ((unsigned int)this) << "] ";
  cerr << "game=[" << ((unsigned int)mygame) << "] ";

  cerr.setf(ios::dec,ios::basefield);
  cerr << "paneid=" << getPageId() << endl;
}

/* callbacks */

void drop_callbackP(GtkMenuItem *item,gpointer data) {
  Board *b; b=(Board *)data; b->drop(PAWN);
}

void drop_callbackR(GtkMenuItem *item,gpointer data) {
  Board *b; b=(Board *)data; b->drop(ROOK);
}

void drop_callbackN(GtkMenuItem *item,gpointer data) {
  Board *b; b=(Board *)data; b->drop(KNIGHT);
}

void drop_callbackB(GtkMenuItem *item,gpointer data) {
  Board *b; b=(Board *)data; b->drop(BISHOP);
}

void drop_callbackQ(GtkMenuItem *item,gpointer data) {
  Board *b; b=(Board *)data; b->drop(QUEEN);
}

void menu_whitep(GtkMenuItem *item, gpointer data) {
  Board *me;
  me=Board::PopupOwner;
  Board::PopupOwner = 0;
  if (!me) return;
  if (!me->mygame) return;
  if (!global.protocol) return;  
  global.protocol->callPlayerAction(me->mygame->PlayerName[0],(string *) data);
}

void menu_blackp(GtkMenuItem *item, gpointer data) {
  Board *me;
  me=Board::PopupOwner;
  Board::PopupOwner = 0;
  if (!me) return;
  if (!me->mygame) return;
  if (!global.protocol) return;  
  global.protocol->callPlayerAction(me->mygame->PlayerName[1],(string *) data);
}

void menu_gamep(GtkMenuItem *item, gpointer data) {
  Board *me;
  me=Board::PopupOwner;
  Board::PopupOwner = 0;
  if (!me) return;
  if (!me->mygame) return;
  if (!global.protocol) return;  
  global.protocol->callGameAction(me->mygame->GameNumber,(string *) data);
}

void Board::drawCoordinates(GdkPixmap *dest,GdkGC *gc,int side) {
  bool ef;
  int i;
  char z[2];
  z[1]=0;
  ef=effectiveFlip();

  if ((borx)||(bory)) {
    gdk_rgb_gc_set_foreground(gc,0);
    gdk_draw_rectangle(dest,gc,TRUE,0,0,Width(),bory);
    gdk_draw_rectangle(dest,gc,TRUE,0,Height()-bory,Width(),bory);
    gdk_draw_rectangle(dest,gc,TRUE,0,0,borx,Height());
    gdk_draw_rectangle(dest,gc,TRUE,Width()-borx,0,borx,Height());
  }

  gdk_rgb_gc_set_foreground(gc,0x404040);
  gdk_draw_rectangle(dest,gc,TRUE,borx,2,side*8,bory-4);
  gdk_draw_rectangle(dest,gc,TRUE,borx,2+side*8+bory+morey,side*8,bory-4);
  gdk_draw_rectangle(dest,gc,TRUE,2,bory+1,borx-4,morey+side*8-1);
  gdk_draw_rectangle(dest,gc,TRUE,2+side*8+borx,bory+1,borx-4,morey+side*8-1);

  gdk_rgb_gc_set_foreground(gc,0xffffff);

  for(i=0;i<8;i++) {
    z[0]=ef?'h'-i:'a'+i;
    gdk_draw_string(dest,f3,gc, borx+side*i+side/2, bory-4, z);
    gdk_draw_string(dest,f3,gc, borx+side*i+side/2, morey+8*side+2*bory-4, z);
    z[0]=ef?'1'+i:'8'-i;
    gdk_draw_string(dest,f3,gc, 6, bory+morey+side*i+side/2, z);
    gdk_draw_string(dest,f3,gc, side*8+borx+6, bory+morey+side*i+side/2, z);
  }


}

void Board::composeVecBoard(GdkGC *gc) {
  Position *joker;
  int i,j;
  global.vpieces.drawSquares(buffer,gc,sqside, borx, bory);

  if (gotAnimationLoop) joker=&fake; else joker=&position;
  if (effectiveFlip())
    for(i=0;i<8;i++)
      for(j=0;j<8;j++)
	global.vpieces.drawPiece(buffer,gc,sqside,borx+i*sqside,bory+j*sqside,
				 joker->getPiece(7-i,j));
  else
    for(i=0;i<8;i++)
      for(j=0;j<8;j++)
	global.vpieces.drawPiece(buffer,gc,sqside,borx+i*sqside,bory+j*sqside,
				 joker->getPiece(i,7-j));

  if (global.ShowCoordinates)
    drawCoordinates(buffer,gc,sqside);

  i=Width();
  gdk_rgb_gc_set_foreground(gc,0);
  gdk_draw_rectangle(buffer,gc,TRUE,0,i,i,sqh-Height());

  currently_rendered=*(joker);
}

void Board::pasteVecBoard() {
  gdk_draw_pixmap(yidget->window,
		  yidget->style->fg_gc[GTK_WIDGET_STATE (yidget)],
		  buffer,
		  0, 0,
		  0, 0,Width(),Height());
}

static GdkEventMotion LastMotionEvent;

/* ******************************************************************
   board_expose_event

   called on: repaints (both generated by the program and by the
                        windowing system).

   description: 
   paints straight on window. paints external borders
   black if window size requires it.

   Board:
   If using vectorized pieces, calls [composeVecBoard] to update
   the [buffer] variable.
   Paints [buffer] (board) on window.

   Clock:
   If [clock_ch] is set, call renderClock.
   Draws [clkbar] on window.

   Highlights:
   Paints highlights, selection, premove indicators straight
   to window.

   Dragging:
   If dragging, call [board_motion_event] to update the
   dragging buffers.

   ********************************************************** */
gboolean board_expose_event(GtkWidget *widget,GdkEventExpose *ee,
                            gpointer data) {
  Board *me;
  GdkGC *gc;
  unsigned int i;
  int w,h,sq,pw,fw,fh;

  global.debug("Board","board_expose_event");

  me=(Board *)data;
  w=ee->area.width;
  h=ee->area.height;
  fw=ee->area.x+ee->area.width;
  fh=ee->area.y+ee->area.height;

  sq=me->sqside;

  if (!me->wgc)
    me->wgc=gdk_gc_new(widget->window);
  gc=me->wgc;

  if (fw> ( me->Width() + 250) )
    gdk_draw_rectangle(widget->window,widget->style->black_gc,TRUE,
		       me->Width()+250,0,fw-me->Width()-250,fh);

  if (fh > me->sqh) {
    gdk_draw_rectangle(widget->window,widget->style->black_gc,TRUE,
		       0,me->sqh,fw,fh-me->sqh);
    fh=me->sqh;
  }

  if (fw > me->sqw)
    fw=me->sqw;
  
  pw=fw;
  if (fw> me->Width() ) pw=me->Width();

  // draw board+pieces

  if (global.UseVectorPieces)
    me->composeVecBoard(gc);

  gdk_draw_pixmap(widget->window,
		  widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
		  me->buffer,
		  0, 0,
		  0, 0,pw,fh);

  // draw clock

  if (me->clock_ch)
    me->renderClock();
  gdk_draw_pixmap(widget->window,
                  widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
                  me->clkbar,
                  0, 0,
                  me->Width(), 0,250,fh);

  // draw highlighted squares

  if (!me->gotAnimationLoop)
    if ((me->sp)||(!me->LastMove.empty())||(me->premoveq)) {
    
      if (!me->EditMode)
	if ((global.HilightLastMove)&&(!me->LastMove.empty()))
	  for(i=0;i<me->LastMove.size();i++) {
	    me->outlineRectangle(gc,me->LastMove[i]->SX,me->LastMove[i]->SY,
				 0x0000ff,3);
	    me->outlineRectangle(gc,me->LastMove[i]->DX,me->LastMove[i]->DY,
				 0x0000ff,3);
	}
      
      if (me->sp)
	for(i=0;i < (unsigned int) (me->sp);i++)
	  me->outlineRectangle(gc,me->ax[i],me->ay[i],me->sel_color,4);
      
      if (me->premoveq) {
	me->outlineRectangle(gc,me->pmx[1],me->pmy[1],0xff0000,3);
	me->drawBall(gc,me->pmx[1],me->pmy[1],0xff0000,5);
	if (me->pmx[0]>=0) {
	  me->outlineRectangle(gc,me->pmx[0],me->pmy[0],0xff0000,3);
	  me->drawBall(gc,me->pmx[0],me->pmy[0],0xff0000,5);
	}
      }
    }
  
  /* if dragging... */
  if (me->dr_active) {
    me->dr_step=0;
    board_motion_event(widget,&LastMotionEvent,data);
  }

  return 1;
}

/* ******************************************************************
   board_configure_event

   called on: resizes, piece changes, program-generated repaints.

   Updates the [buffer] pixmap.
   
   Description:

   Square size/Pieces:
   Recalculates square size. Resizes piece set [Board::cur] if needed.

   Return immediately if dealing with vectorized pieces.

   Compose [buffer]:
   Render position to [scrap]. If animating a move, renders
   [Board::fake], else renders [Board::position].

   Copy [scrap] to [buffer]. (X programming gizmo, scrap is an image,
   buffer is a pixmap, and bitmap pieces can't be rendered on
   pixmaps directly)

   Coordinates:
   Draw coordinates to [buffer] if global.ShowCoordinates set.

   updates [currently_rendered] to reflect the current situation.
   
   ************************************************************* */
gboolean board_configure_event(GtkWidget *widget,GdkEventConfigure *ce,
			       gpointer data) {
  Board *me;
  int optimal_square,sq;
  int ww,wh,i,j;
  int oldsqw, oldsqh;
  Position *joker;

  me=(Board *)data;

  global.debug("Board","board_configure_event");

  gdk_window_get_size(widget->window,&ww,&wh);
  ww-=200;

  if ( (ww-2*me->borx) > (wh-2*me->bory) ) {
    if ( (global.UseVectorPieces) || (! global.pieceset->extruded) ) {
      optimal_square=(wh - 2 * me->bory)/8;
      me->morey=0;
    } else {
      optimal_square=(int) ((wh - 2 * me->bory)/8.50);
      me->morey = optimal_square / 2;
    }
  } else {
    optimal_square=(ww - 2 * me->borx)/8;
    if ( (global.UseVectorPieces) || (! global.pieceset->extruded) )
      me->morey = 0;
    else
      me->morey = optimal_square / 2;
  }

  if (optimal_square < 10) return FALSE;

  if (me->cur == 0)
    me->cur=new PieceSet(global.pieceset);
  if (me->cur->side != optimal_square) {
    if (me->cur->side != global.pieceset->side) {
      delete me->cur;
      me->cur=new PieceSet(global.pieceset);
    }      
    me->cur->scale(optimal_square);
    me->currently_rendered.invalidate();
  }
  sq=optimal_square;
  if (sq!=me->sqside) {
    me->currently_rendered.invalidate();
    me->clock_ch=1;
  }

  oldsqw=me->sqw;
  oldsqh=me->sqh;
  me->sqside=sq;
  me->sqw = 2*me->borx + 8 * me->sqside + 1;
  me->sqh = 2*me->bory + me->morey + 8 * me->sqside + 1;



  if ((me->currently_rendered == me->position)&&(!me->update_queue)) {
    return 1;
  }

  if (oldsqw!= me->sqw || oldsqh != me->sqh) {
    gdk_pixmap_unref(me->clkbar);
    gdk_pixmap_unref(me->buffer);
    if (me->clkgc) { gdk_gc_destroy(me->clkgc); me->clkgc=0; }
    if (me->scrap) { g_free(me->scrap); me->scrap=0; }

    me->buffer=gdk_pixmap_new(MainWindow::RefWindow,
			      me->sqw,me->sqh,-1);
    me->clkbar=gdk_pixmap_new(MainWindow::RefWindow,
			      250,me->sqh,-1);
    me->clkgc=gdk_gc_new(me->clkbar);
    me->scrap=(rgbptr)g_malloc(me->sqw * me->sqh * 3);
  }

  memset(me->scrap,0,me->sqw * me->sqh * 3);

  if (global.UseVectorPieces)
    return 1;

  if (me->gotAnimationLoop)
    joker=&(me->fake);
  else
    joker=&(me->position);

  me->cur->beginQueueing();

  if (me->effectiveFlip())
    for(i=0;i<8;i++)
      for(j=0;j<8;j++)
	me->cur->drawPieceAndSquare(joker->getPiece(7-i,j),
				    me->scrap,me->borx+i*sq,
				    me->morey+me->bory+j*sq,me->sqw,
				    (i+j)%2);
  else
    for(i=0;i<8;i++)
      for(j=0;j<8;j++)
	me->cur->drawPieceAndSquare(joker->getPiece(i,7-j),
				    me->scrap,me->borx+i*sq,
				    me->morey+me->bory+j*sq,me->sqw,
				    (i+j)%2);

  me->cur->endQueueing();
  // and doing without curly braces just adds to the general perplexity

  // copy scrap to pixmap buffer
  gdk_draw_rgb_image(me->buffer,widget->style->black_gc,0,0,
		     me->sqw, me->sqh,
		     GDK_RGB_DITHER_NORMAL,me->scrap,me->sqw*3);

  if (global.ShowCoordinates) {
    if (!me->wgc)
      me->wgc=gdk_gc_new(widget->window);
    me->drawCoordinates(me->buffer,me->wgc,sq);
  }

  me->currently_rendered=*(joker);

  return 1;
}

/*
static bool not_disjoint_intervals(int a, int b, int c, int d) {
  return( (c<=b) && (d>=a) );
}

static bool rects_overlap(int x1,int y1,int w1,int h1,
			  int x2,int y2,int w2,int h2) {
  return( not_disjoint_intervals(x1,x1+w1, x2, x2+w2) &&
	  not_disjoint_intervals(y1,y1+h1, y2, y2+h2) );
}
*/

static GdkPixmap *GCPbuffer=0;
static int gcpw=0,gcph=0;

/* ***************************************************************
   board_motion_event

   called on: mouse moved during drag, also called by 
              [board_expose_event]

   depends on all Board::dr_* fields.
   uses [GCPbuffer] (static to this module) as drawing buffer.

   Startup Sanity Checking:
   Return immediately if the after-click timeout hasn't
   expired [dr_fto], not dragging a piece [dr_active],
   no event [em], or dragging not with left mouse button.

   GCPbuffer allocation:
   If current [GCPbuffer] is smaller than needed, dump the
   current one and allocate another.

   Step 0: Prepare background
   If first call on this drag ([dr_step] = 0), copy [buffer] to
   [GCPbuffer]

   Step 1: Erase previous dragging frame
   Only if not first call on this drag ([dr_step] != 0).
   copy square from [buffer] to [GCPbuffer],
   copy clock from [clkbar] to [GCPbuffer] if needed.
   Extruded sets: be sure to erase enough to cover [PieceSet::height]

   Step 2: Erase source square
   Only if dragging from the board (instead of zhouse stock,
   [dr_fromstock]).
   Vectorized pieces: draw square and coordinates directly to
    [GCPbuffer].
   Bitmapped pieces: allocate new image (M[0]), draw empty square on
    it, copy [M[0]] to [GCPbuffer].
   Extruded Bitmapped pieces: redraw piece on square right above the
    source square.

   Step 3: Draw dragged piece
   Vectorized pieces: just draw it on [GCPbuffer]
   Bitmap pieces: paint alpha'ed piece (more comments to come, experimental
    code)

   Step 3 1/2: Draw coordinates (bitmap-mode only)
   Redraw the coordinates (if global.ShowCoordinates set) on
   [GCPbuffer]

   Step 4: Commit
   Draw [GCPbuffer] on window, update Board::dr_* about what has
   been done, deallocate all local storage as needed.

   *************************************************************** */
gboolean board_motion_event(GtkWidget *widget,
			    GdkEventMotion *em,
			    gpointer data) {
  Board *me;
  GdkGC *gcpgc;
  int rw,rh;
  bool clock_too=false;
  int ww,wh,sw,sh;
  int sq,he,xc,xr;
  int bw,bh;

  memcpy(&LastMotionEvent,em,sizeof(GdkEventMotion));

  me=(Board *)data;

  if ((!me->dr_fto)||(!me->dr_active)||(!em)||(!(em->state & GDK_BUTTON1_MASK)))
    return FALSE;

  sq=he=me->sqside;
  if ((me->cur->extruded)&&(!global.UseVectorPieces)) he=me->cur->height;

  // STEP 0: prepare double buffer for drawing

  gdk_window_get_size(widget->window,&ww,&wh);

  if (!me->wgc)
    me->wgc=gdk_gc_new(widget->window);
  gcpgc=me->wgc;
  gdk_rgb_gc_set_foreground(gcpgc,0);

  sw=ww; sh=wh;
  if (sw> me->sqw) sw=me->sqw;
  if (sh> me->sqh) sh=me->sqh;

  if ( (gcpw < ww)|| (gcph < wh) ) {
    gcpw=ww; gcph=wh;
    if (GCPbuffer) gdk_pixmap_unref(GCPbuffer);
    GCPbuffer=gdk_pixmap_new(widget->window, gcpw, gcph, -1);
    gdk_draw_rectangle(GCPbuffer,gcpgc,TRUE,0,0,gcpw,gcph);
    gdk_draw_pixmap(GCPbuffer,gcpgc,me->buffer,0,0,0,0,sw,sh);
    clock_too=true;
  } else
    if (! me->dr_step) {
      clock_too=true;
      gdk_draw_rectangle(GCPbuffer,gcpgc,TRUE,0,0,gcpw,gcph);
      gdk_draw_pixmap(GCPbuffer,gcpgc,me->buffer,0,0,0,0,sw,sh);
    }

  bw=me->Width();
  bh=me->Height();

  // STEP 1: erase dirty animation square

  if (me->dr_step) {
    rw= bw - me->dr_dirty[0];
    rh= bh - me->dr_dirty[1];
    if (rw > sq) { rw = sq; clock_too=true; }
    if (rh > he) { rh = he; clock_too=true; }
 
    if ((rw>0)&&(rh>0))
      gdk_draw_pixmap(GCPbuffer,gcpgc,
		      me->buffer,
		      me->dr_dirty[0],me->dr_dirty[1],
		      me->dr_dirty[0],me->dr_dirty[1],
		      rw,rh);
  }

  if (clock_too) {

    gdk_draw_pixmap(GCPbuffer,
		    widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
		    me->clkbar,
		    0, 0,
		    bw, 0,250, bh );
    
  }

  // STEP 2: erase source square (if not dragging from zhouse stock)

  if (! me->dr_fromstock) {
    if (global.UseVectorPieces) {

      gdk_rgb_gc_set_foreground(gcpgc,((me->dr_c+me->dr_r)&0x01)?global.DarkSqColor:global.LightSqColor);
      gdk_draw_rectangle(GCPbuffer,gcpgc,TRUE,
			 me->borx + me->dr_c*sq, 
			 me->morey + me->bory + me->dr_r*sq,
			 sq,sq);
      gdk_rgb_gc_set_foreground(gcpgc,0);
      gdk_draw_rectangle(GCPbuffer,gcpgc,FALSE,
			 me->borx + me->dr_c*sq, 
			 me->morey + me->bory + me->dr_r*sq,
			 sq,sq);
    } else {
      
      // I only need *3, but glibc has some weird glitches when it
      // comes to allocating not padded to 32-bit words

      me->M[0].ensure(sq*he*4);
      me->cur->drawSquare( (me->dr_c+me->dr_r)&0x01, me->M[0].data , 0, 0, sq );
      gdk_draw_rgb_image(GCPbuffer,widget->style->black_gc,
			 me->borx + me->dr_c * sq, 
			 me->morey + me->bory + me->dr_r * sq,
			 sq, sq,
			 GDK_RGB_DITHER_NORMAL, me->M[0].data ,3 * sq);

      // erase upper portion of extruded piece
      if (me->cur->extruded) {
	if (me->dr_r > 0) { // there's a square above it
	  me->M[1].ensure(sq*sq*4);
	  xc = me->effectiveFlip() ? 7 - me->dr_c : me->dr_c;
	  xr = me->effectiveFlip() ? me->dr_r - 1 : 8 - me->dr_r ;
	  me->cur->drawPieceAndSquare( 
				      me->position.getPiece(xc, xr) ,
				      me->M[0].data, 0, 0, sq, ((xc+xr)%2 == 0) );
	  memcpy(me->M[1].data , me->M[0].data, sq*sq*3);

	  gdk_draw_rgb_image(GCPbuffer,widget->style->black_gc,
			     me->borx + me->dr_c * sq, 
			     me->morey + me->bory + (me->dr_r - 1) * sq,
			     sq, sq,
			     GDK_RGB_DITHER_NORMAL,me->M[0].data,3 * sq);
	} else { // there is nothing above it, just paint it black
	  gdk_draw_rectangle(GCPbuffer,widget->style->black_gc,TRUE,
			     me->borx + me->dr_c * sq,
			     me->bory, sq, me->morey);
	}
      }

    }
  }

  // STEP 3: draw dragged piece

  int dx,dy;
  int x,y;
  GdkModifierType state;

  if (em->is_hint){
    gdk_window_get_pointer(em->window, &x, &y, &state);
  } else {
    x=(int)(em->x);
    y=(int)(em->y);
  }

  // prevent segfaults (bug sf#459164)
  if (x < 0) x=0;
  if (y < 0) y=0;
  if (x > ww) x=ww;
  if (y > wh) y=wh;

  dx = x - me->dr_ox;
  dy = y - me->dr_oy;

  // yet more segfault prevention
  if (dx < 0) { x-=dx; dx=0; }
  if (dy < 0) { y-=dy; dy=0; }

  if (global.UseVectorPieces) {
    global.vpieces.drawPiece((GdkPixmap *)(GCPbuffer),
			     gcpgc,sq,dx,dy,me->dr_p);    
    xr=dy;
    //  } else if (dx+sq > me->Width() ) {
  } else {
    // segment-by-segment masked render, piece overlaps clock area

    if (me->cur->extruded) {
      xr=dy-he+sq; xc=he-sq;
      if (xr < 0) { xc+=xr; xr=0; }
      if (xc > me->morey) { --xc; ++xr; }
    } else { xc=0; xr=dy; }
    
    me->cur->drawPiece(me->dr_p,GCPbuffer,gcpgc,dx,dy);
  }

  // STEP 3 1/2: draw coordinates if not in vector mode
  if ((!global.UseVectorPieces)&&(global.ShowCoordinates))
    me->drawCoordinates(GCPbuffer,gcpgc,sq);

  // STEP 4: commit double buffer to window

  gdk_draw_pixmap(widget->window,gcpgc,GCPbuffer,0,0,0,0,ww,wh);

  me->dr_dirty[0]=dx;
  me->dr_dirty[1]=xr;
  me->dr_step++;
  
  return TRUE;
}

gboolean board_button_release_event(GtkWidget *widget,
				    GdkEventButton *be,
				    gpointer data) {
  Board *me;
  int x,y;
  bool must_repaint=false;
  bool drop_from_drag=false;

  if (be==0) return 0;
  me=(Board *)data;

  if ((be->button==1)&&(me->dr_active)) {
    if (me->dr_step)
      must_repaint=true;
    me->dr_active=false;

    if (me->dr_fromstock)
      drop_from_drag=true;
  }

  if ( (be->x < me->borx) || (be->y < me->morey + me->bory ) )
    goto b_rele_nothing;

  x=((int)(be->x)-me->borx)/me->sqside;
  y=((int)(be->y)-me->bory-me->morey)/me->sqside;  

  if ((x>7)||(y>7)) goto b_rele_nothing;
  if (me->effectiveFlip()) x=7-x; else y=7-y;
  //
  if (be->button==1) {

    // piece dragged from stock
    if (drop_from_drag) {
      me->dropsq[0]=x;
      me->dropsq[1]=y;
      if (me->mygame) {
	me->drop(me->dr_p);
	return 1;
      }
    }

    if ( (me->sp==1) && ( (x!=me->ax[0]) || (y!=me->ay[0]) ) ) {
      me->ax[me->sp]=x; me->ay[me->sp]=y;
      me->sp=2;      
      if (me->mygame)
	me->sendMove();
      else {
	me->sp=0;
	me->hilite_ch=1;
	me->repaint();
      }
      return 1;
    }
  }

 b_rele_nothing:
  if (must_repaint) {
    me->clock_ch=1;
    me->hilite_ch=1;
    me->repaint();
  }
  return 1;
}

static gboolean board_drag_start_timeout(gpointer data) {
  bool *b;
  b=(bool *)data;
  *b=true;
  return FALSE;
}

gboolean board_button_press_event(GtkWidget *widget,GdkEventButton *be,
			    gpointer data) {
  Board *me;
  int x,y;
  me=(Board *)data;
  if (be==0) return 0;
  if ((!me->canselect)&&(be->button!=3)) return 1;

  if ( (be->x < me->borx) || (be->y < me->bory + me->morey ) ) return 1;

  x=((int)(be->x)- me->borx)/me->sqside;
  y=((int)(be->y)- me->bory - me->morey )/me->sqside;

  if ((x<8)&&(y<8)) {
    me->dr_ox= ((int)(be->x) - me->borx ) % me->sqside;
    me->dr_oy= ((int)(be->y) - me->bory - me->morey ) % me->sqside;
    me->dr_c=x;
    me->dr_r=y;
  }

  if ((x>7)||(y>7)) {
    DropSource *ds;
    if (be->button == 1) {
      ds=me->hitTarget((int)(be->x),(int)(be->y));      
      if (ds) {
	if (ds->dragged) {
	  me->dr_active=true;
	  me->dr_fto=false;
	  me->dr_fromstock=true;
	  me->dr_step=0;
	  me->dr_p=ds->P;
	  me->dr_ox = me->sqside / 2;
	  me->dr_oy = me->sqside / 2;
	  me->dr_c=0; // non-sense
	  me->dr_r=0;
	  gtk_timeout_add(90,board_drag_start_timeout,
			  (gpointer)(&(me->dr_fto)));
	} else {
	  EditBoard *eme;
	  eme=(EditBoard *)data;

	  // empty / start pos buttons
	  if ((me->EditMode)&&(me->mygame)) {
	    switch(ds->P) {
	    case EMPTY: me->mygame->editEmpty(); break;
	    case WHITE|BLACK: me->mygame->editStartPos(); break;
	    case WHITE: eme->popRunEngine(ds->X,ds->Y2); break;
	    case BLACK: eme->flipSide(); break;
	    case WASPAWN: eme->getFEN(); break;
	    }
	  }
	}
      }
    }
    if (be->button == 3) {
      me->popupProtocolMenu( (int) be->x, (int) be->y, be->time );
    }
    return 1;
  }
  
  if (me->effectiveFlip()) x=7-x; else y=7-y;  

  if (be->button == 1) {

    me->dr_active=true;
    me->dr_fto=false;
    me->dr_fromstock=false;
    me->dr_step=0;
    me->dr_p=me->position.getPiece(x,y);
    if (me->dr_p == EMPTY)
      me->dr_active=false;
    else
      gtk_timeout_add(90,board_drag_start_timeout,
		      (gpointer) (&(me->dr_fto)));

    if (me->sp > 1) me->sp=0;
    if ( (me->sp == 1) && (x==me->ax[0]) && (y==me->ay[0]) )
      me->sp=0;
    else {

      if ((me->sp == 1) &&
	  ( (me->position.getPiece(me->ax[0],me->ay[0])&COLOR_MASK) ==
	    (me->position.getPiece(x,y)&COLOR_MASK)) &&
	  (me->position.getPiece(x,y)!=EMPTY) && (me->allowMove) )
	me->sp=0;
      
      me->ax[me->sp]=x; me->ay[me->sp]=y;
      ++(me->sp);
    }

    if ((me->sp==2)&&(me->mygame)) {
      me->sendMove();
    } else {
      me->premoveq=0;
      me->hilite_ch=1;
      me->repaint();
    }
  }
  if ((be->button == 3)&&(me->canselect)) {
    me->popupDropMenu(x,y,0,0,be->time);
  }

  return 1;
}

gboolean vec_board_animate(gpointer data) {
  list<AnimatedPiece *>::iterator li;
  int not_over=0, nonover=0;
  Board *me;
  GdkGC *gc;
  GdkEventExpose ee;
  int w,h;
  int sh0=0,sh1=0;

  me=(Board *)data;

  for(li=me->animes.begin();li!=me->animes.end();li++) {
    if (! (*li)->over() )
      nonover++; // to paint overs when nonovers are in the queue
    if ( (*li)->getSoundHold() )
      sh0++;
  }

  gc=gdk_gc_new(me->yidget->window);
  gdk_window_get_size(me->yidget->window,&w,&h);
  ee.area.width=w;
  ee.area.height=h;
  ee.area.x=0;
  ee.area.y=0;
  me->composeVecBoard(gc);
  for(li=me->animes.begin();li!=me->animes.end();li++)
    if (nonover) {
      (*li)->step();
      global.vpieces.drawPiece(me->buffer,gc,me->sqside,
			       (*li)->getX(),(*li)->getY(),(*li)->getPiece());
    }
  me->pasteVecBoard();
  gdk_gc_destroy(gc);

  // count remaining
  for(li=me->animes.begin();li!=me->animes.end();li++) {
    if (! (*li)->over())
      not_over++;
    if ( (*li)->getSoundHold() )
      sh1++;
  }

  // if none remain, finish them all
  if (!not_over) {
    for(li=me->animes.begin();li!=me->animes.end();li++)
      (*li)->lemming();
    me->animes.clear();
  }

  // if there was at least one animation waiting to release the sound
  // and now there is none, flush the sound stack
  if ((sh0)&&(!sh1))
    global.flushSound();

  if (not_over)
    return TRUE;
  else {
    me->gotAnimationLoop=0;
    me->AnimationTimeout=-1;
    if (me->update_queue) {
      me->update(me->UpdateStack.top());
      me->UpdateStack.pop();
    }
    me->queueRepaint();
    return FALSE;
  }
}

gboolean board_animate(gpointer data) {
  list<AnimatedPiece *>::iterator li;
  Board *me;
  Rect *r;
  int dy,nonover=0;
  int sh0=0,sh1=0;

  global.debug("Board","board_animate");
  if (global.UseVectorPieces)
    return(vec_board_animate(data));

  int not_over=0;
  
  me=(Board *)data;

  dy=me->cur->side - me->cur->height;

  for(li=me->animes.begin();li!=me->animes.end();li++) {
    if (! (*li)->over() )
      nonover++; // to paint overs when nonovers are in the queue
    if ( (*li)->getSoundHold() )
      sh0++;
  }

  // clear previous frames

  while(! me->adirty.empty() ) {
    r=me->adirty.front();

    gdk_draw_pixmap(me->yidget->window,
		    me->yidget->style->black_gc,
		    me->buffer,
		    r->x, r->y,
		    r->x, r->y,
		    r->w, r->h);

    delete r;
    me->adirty.pop_front();
  }

  // draw current frames

  for(li=me->animes.begin();li!=me->animes.end();li++)
    if (nonover) {

      (*li)->step();

      r=new Rect((*li)->getX(), (*li)->getY(),
		 me->cur->side, me->cur->height);

      me->cur->drawPiece((*li)->getPiece(),
			 me->yidget->window,
			 me->yidget->style->black_gc,
			 r->x, r->y);
      r->translate(0,dy);
      me->adirty.push_back(r);
    } 

  // count remaining
  for(li=me->animes.begin();li!=me->animes.end();li++) {
    if (! (*li)->over())
      not_over++;
    if ( (*li)->getSoundHold() )
      sh1++;
  }

  // if none remain, finish them all
  if (!not_over) {
    for(li=me->animes.begin();li!=me->animes.end();li++)
      (*li)->lemming();
    me->animes.clear();
  }

  // if there was at least one animation waiting to release the sound
  // and now there is none, flush the sound stack
  if ((sh0)&&(!sh1))
    global.flushSound();
  
  if (not_over)
    return TRUE;
  else {
    me->gotAnimationLoop=0;
    me->AnimationTimeout=-1;
    if (me->update_queue) {
      me->update(me->UpdateStack.top());
      me->UpdateStack.pop();
    }
    me->queueRepaint();
    return FALSE;
  }
}

// ------------- targets

DropSource::DropSource(piece p, int x1,int y1,int w,int h,bool d) {
  X=x1; Y=y1;
  X2=X+w; Y2=Y+h;
  P=p;
  dragged = d;
}

bool DropSource::hit(int x,int y) {
  return((x>=X)&&(x<=X2)&&(y>Y)&&(y<Y2));
}

void TargetManager::cleanTargets() {
  int i,j;
  j=targets.size();
  for(i=0;i<j;i++)
    delete(targets[i]);
  targets.clear();
}

void TargetManager::addTarget(DropSource *ds) {
  targets.push_back(ds);
}

DropSource * TargetManager::hitTarget(int x,int y) {
  int i,j;
  j=targets.size();
  for(i=0;i<j;i++)
    if (targets[i]->hit(x,y))
      return(targets[i]);
  return 0;
}

EditBoard::EditBoard(ChessGame *cg) : Board() {
  EditMode = true;
  FreeMove = true;
  setGame(cg);
}

static int popx=0, popy=0;

void poprun_cb(GtkMenu *m,gint *x,gint *y,gpointer data) {
  *x=popx;
  *y=popy;
}

static EditBoard *eb_last=0;

void eb_runengine_nobm(GtkMenuItem *item,gpointer data) {
  int *i;
  EngineProtocol *ep;
  Position p;

  if (!eb_last) return;
  if (!eb_last->mygame) return;
  
  i=(int *)data;
  
  switch(*i) {
  case 0: ep=new GnuChess4Protocol(); break;
  case 1: ep=new CraftyProtocol();    break;
  case 2: ep=new SjengProtocol();     break;
  case 3: ep=new XBoardProtocol();    break;
  default: return;
  }

  p=eb_last->mygame->getCurrentPosition();
  ep->setInitialPosition(&p);
  global.chandler->openEngine(ep,0);
}

void eb_runengine_bm(GtkMenuItem *item,gpointer data) {
  EngineBookmark *ebm;
  EngineProtocol *ep;
  Position p;

  if (!eb_last) return;
  if (!eb_last->mygame) return;

  ebm=(EngineBookmark *)data;

  if (!ebm) return;

  switch(ebm->proto) {
  case 0: ep=new XBoardProtocol();    break;
  case 1: ep=new CraftyProtocol();    break;
  case 2: ep=new SjengProtocol();     break;
  case 3: ep=new GnuChess4Protocol(); break;
  default: return;
  }

  p=eb_last->mygame->getCurrentPosition();
  ep->setInitialPosition(&p);
  global.chandler->openEngine(ep,ebm);
}

void EditBoard::popRunEngine(int x,int y) {
  GtkWidget *popmenu, *note=0, *e;
  vector<GtkWidget *> mi;  
  list<EngineBookmark *>::iterator ei;
  unsigned int i;
  static int sindex[4]={0,1,2,3};

  eb_last=this;

  popmenu=gtk_menu_new();

  mi.push_back(gtk_menu_item_new_with_label("GNU Chess 4..."));
  mi.push_back(gtk_menu_item_new_with_label("Crafty..."));
  mi.push_back(gtk_menu_item_new_with_label("Sjeng..."));
  mi.push_back(gtk_menu_item_new_with_label(_("Generic XBoard Engine...")));
  mi.push_back(gtk_menu_item_new());

  for(i=0;i<4;i++)
    gtk_signal_connect(GTK_OBJECT(mi[i]),"activate",
		       GTK_SIGNAL_FUNC(eb_runengine_nobm),
		       (gpointer) (&sindex[i]) );
  
  if (!global.EnginePresets.empty()) {
    note=gtk_menu_item_new_with_label(_("If you pick a bookmark, the engine\n"\
				      "will play the next move, ignoring\n"\
				      "the side setting in the bookmark."));
    gtk_widget_set_sensitive(note,FALSE);
  }

  for(ei=global.EnginePresets.begin();ei!=global.EnginePresets.end();ei++) {
    e=gtk_menu_item_new_with_label((*ei)->caption.c_str());
    mi.push_back(e);

    gtk_signal_connect(GTK_OBJECT(e),"activate",
		       GTK_SIGNAL_FUNC(eb_runengine_bm),
		       (gpointer)(*ei));
  }

  if (note)
    mi.push_back(gtk_menu_item_new());

  for(i=0;i<mi.size();i++) {
    gshow(mi[i]);
    gtk_menu_append(GTK_MENU(popmenu),mi[i]);
  }

  if (note) {
    gshow(note);
    gtk_menu_append(GTK_MENU(popmenu), note);
  }

  gdk_window_get_origin(widget->window,&popx,&popy);

  popx+=x;
  popy+=y;

  gtk_menu_popup(GTK_MENU(popmenu),0,0,
		 (GtkMenuPositionFunc) poprun_cb,
		 0,1,time(0));
}

void EditBoard::renderClock() {
  GdkGC *gc=clkgc;
  int lch=clock_ch;
  int i,j,k;  

  Board::renderClock();
  if (!lch) return;
  if (!mygame) return;

  // scratch-board pieces and buttons
  C.consumeTop(10);
  i = 32;

  for(k=0;k<6;k++) {
    C.drawButton(10+i*k, C.Y, i, i, NULL, 0, 0xffffff, 0x505070);
    C.drawButton(10+i*k, C.Y+i+8, i, i, NULL, 0, 0xffffff, 0x505070);
    
    global.vpieces.drawPiece(clkbar,gc, i, 10+i*k, C.Y,WHITE|(k+1));
    global.vpieces.drawPiece(clkbar,gc, i, 10+i*k, C.Y+i+8,BLACK|(k+1));
    addTarget(new DropSource(WHITE|(k+1),Width()+10+i*k,C.Y,i,i));
    addTarget(new DropSource(BLACK|(k+1),Width()+10+i*k,C.Y+i+8,i,i));
  }
  
  C.consumeTop(i*2+16);
  
  j=C.drawButton(10,C.Y,-1,20,_("Empty"),2,0xffffff, 0x505070);
  addTarget(new DropSource(EMPTY,Width()+10,C.Y,j,20,false));
  
  k=10+j+10;
  j=C.drawButton(k,C.Y,-1,20,_("Initial Position"),2,0xffffff,0x505070);
  
  addTarget(new DropSource(WHITE|BLACK,Width()+k,C.Y,j,20,false));

  k+=10+j;
  j=C.drawButton(k,C.Y,-1,20,_("From FEN"),2,0xffffff,0x505070);

  addTarget(new DropSource(WASPAWN,Width()+k,C.Y,j,20,false));
  
  C.consumeTop(28);
  
  j=C.drawButton(10,C.Y,-1,20,_("Run Engine..."),
		 2,0xffffff,0xa07050);
  addTarget(new DropSource(WHITE,Width()+10,C.Y,j,20,false));

  k=j+20;
  j+=20+C.stringWidth(2,_("Side to move: "));

  C.drawButton( k,C.Y,j-k+10+24,20,NULL,2,0xffffff,0x505070);
  addTarget(new DropSource(BLACK,Width()+k,C.Y,j-k+10+24,20,false));

  C.drawString( k+10, C.Y+14, 2, _("Side to move: "));

  C.setColor( mygame->getSideHint() ? 0xffffff : 0);
  C.drawEllipse( 10+j+3, C.Y+3, 14, 14, true);
  C.setColor(0xffffff);
  C.drawEllipse( 10+j+3, C.Y+3, 14, 14, false);  

  C.consumeTop(20);
}

void EditBoard::flipSide() {
  if (mygame) {
    mygame->setSideHint( ! mygame->getSideHint() );
    clock_ch=1;
    queueRepaint();
  }
}

void EditBoard::getFEN() {
  (new GetFENDialog(this))->show();
}

void EditBoard::applyFEN(string &s) {
  Position p;

  p.setFEN(s.c_str());

  mygame->editEmpty();
  mygame->updatePosition(p,1,p.sidehint ? 0 : 1,
			 0,0,"<FEN>",false);  
}

GetFENDialog::GetFENDialog(EditBoard *_owner) 
  : ModalDialog(N_("Enter FEN Position"))
{
  GtkWidget *v,*hs,*hb,*ok,*cancel;

  owner=_owner;
  
  gtk_window_set_default_size(GTK_WINDOW(widget), 450, 100);
  
  v=gtk_vbox_new(FALSE,4);

  gtk_container_add(GTK_CONTAINER(widget),v);

  e=gtk_entry_new();
  gtk_box_pack_start(GTK_BOX(v),e,FALSE,TRUE,4);

  hs=gtk_hseparator_new();
  gtk_box_pack_start(GTK_BOX(v),hs,FALSE,TRUE,4);

  hb=gtk_hbutton_box_new();
  gtk_button_box_set_layout(GTK_BUTTON_BOX(hb), GTK_BUTTONBOX_END);
  gtk_button_box_set_spacing(GTK_BUTTON_BOX(hb), 5);
  gtk_box_pack_start(GTK_BOX(v), hb, FALSE, TRUE, 2); 

  ok=gtk_button_new_with_label(_("Ok"));
  GTK_WIDGET_SET_FLAGS(ok,GTK_CAN_DEFAULT);
  cancel=gtk_button_new_with_label(_("Cancel"));
  GTK_WIDGET_SET_FLAGS(cancel,GTK_CAN_DEFAULT);
  gtk_box_pack_start(GTK_BOX(hb),ok,TRUE,TRUE,0);
  gtk_box_pack_start(GTK_BOX(hb),cancel,TRUE,TRUE,0);
  gtk_widget_grab_default(ok);

  Gtk::show(ok,cancel,hb,hs,e,v,0);
  setDismiss(GTK_OBJECT(cancel),"clicked");

  gtk_signal_connect(GTK_OBJECT(ok),"clicked",
		     GTK_SIGNAL_FUNC(getfen_ok),(gpointer)this);

  focused_widget=e;
}

void getfen_ok(GtkWidget *w, gpointer data) {
  string s;
  GetFENDialog *me;
  me=(GetFENDialog *)data;
  s=gtk_entry_get_text(GTK_ENTRY(me->e));
  me->owner->applyFEN(s);
  me->release();
}

// former animate.cc

int AnimatedPiece::over() { return(ower<=0); }

int AnimatedPiece::getX() { return X; }

int AnimatedPiece::getY() { return Y; }

piece AnimatedPiece::getPiece() { return Piece; }

bool AnimatedPiece::getSoundHold() { return((SoundHold)&&(ower>0)); }

// ================================================
// flat implementation
// ================================================

FlatAnimatedPiece::FlatAnimatedPiece(piece p,int x0,int y0,
				     int xf,int yf,int s,bool sndhold) :
  AnimatedPiece()
{
  Piece=p;
  X0=x0; Y0=y0; XF=xf; YF=yf;
  steps=s; X=X0; Y=Y0;
  DX=(XF-X0)/steps;
  DY=(YF-Y0)/steps;
  ower=steps;
  SoundHold=sndhold;
}

void FlatAnimatedPiece::create(GdkWindow *parent,PieceSet *set,int sqside) {
  square=sqside;
}

void FlatAnimatedPiece::lemming() {
  delete this;
}

void FlatAnimatedPiece::destroy() {

}

void FlatAnimatedPiece::step() {
  if (ower > 1) {
    X+=DX;
    Y+=DY;
  }
  if (ower==1) { X=XF; Y=YF; }
  --ower;  
}
