
/****************************************************************************
    Copyright (C) 1987-2005 by Jeffery P. Hansen

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

    Last edit by hansen on Thu Sep  2 08:48:26 2004
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include "tkgate.h"

extern int sync_Xserver;
extern int regionUpdate;
extern int smoothScroll;

void doScroll(int dx,int dy);

int gat_scope(ClientData d,Tcl_Interp *tcl,int argc,const char *argv[]);

static Tk_ConfigSpec configSpecs[] = {
  {TK_CONFIG_COLOR, "-background", "background", "Background",     		"white", Tk_Offset(TkgGateWin,bgColor), 0, 0},
  {TK_CONFIG_SYNONYM,"-bg", "background", 0, 0, 0, 0, 0},
  {TK_CONFIG_SYNONYM,"-fg", "foreground", 0, 0, 0, 0, 0},
  {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",     		"black", Tk_Offset(TkgGateWin,fgColor), 0, 0},
  {TK_CONFIG_INT,    "-height", "height", "Height",		     		STR(TKG_GATEWIN_HEIGHT), Tk_Offset(TkgGateWin,height), 0, 0},
  {TK_CONFIG_INT,    "-width", "width", "Width",		     		STR(TKG_GATEWIN_WIDTH), Tk_Offset(TkgGateWin,width), 0, 0},
  {TK_CONFIG_STRING, "-xscrollcommand", "xscrollcommand",  "Xscrollcommand",    "", Tk_Offset(TkgGateWin,xscroll), 0, 0},
  {TK_CONFIG_STRING, "-yscrollcommand", "yscrollcommand",  "Yscrollcommand",	"", Tk_Offset(TkgGateWin,yscroll), 0, 0},
  {TK_CONFIG_STRING, "-takefocus", "takefocus",  "Takefocus", 			"", Tk_Offset(TkgGateWin,takefocus), 0, 0},
  {TK_CONFIG_END, 0, 0, 0, 0, 0, 0, 0}
};
int numConfigSpecs = sizeof(configSpecs)/sizeof(configSpecs[0]);

XGateParams XGate;		/* Parameters used by original gate functions */ 

static int did_doubleclick = 0;

char *font_encoding = "iso8859-1";

/*
 * Return the code for the current major mode
 */
MajorMode tkgate_currentMode()
{
  return XGate.ed->major_mode;
}

/*
 * Changes the major mode opening/closing any windows or connections necessary
 * to do so.
 */
void tkgate_setMajorMode(MajorMode mm)
{
  MajorMode cm = tkgate_currentMode();

#if 0
  {
    static char *modeName[] = {"edit","simulate","analyze"};
    printf("tkgate_setMajorMode: %s --> %s\n",
	   modeName[cm],modeName[mm]);
  }
#endif

  /* 
   * No change in mode, but we might want to issue a message.
   */
  if (mm == cm)

    /* Gen. error message if simulator is already running */
    if (cm == MM_SIMULATE) {
      message(0,msgLookup("err.sim.isrun"));
    return;
  }

  /*
   * Transition to MM_EDIT before changing to new mode. 
   */
  switch (cm) {
  case MM_EDIT :
    break;
  case MM_SIMULATE :
    if (XGate.circuit->simulator.active) {
      SimInterface_end(&XGate.circuit->simulator);
      FlagRedraw();
    }
    break;
  case MM_ANALYZE :
    cpath_close();
    break;
  }

  /* Always reset edit mode when changing major modes */
  setEditMode(XGate.circuit->es,MODE_MOVE);
#if TOUCH_XGATE_ED
  ob_touch(XGate.ed);
#endif
  XGate.ed->major_mode = mm;

  switch (mm) {
  case MM_EDIT :
    DoTcl("tkg_editLogo");
    DoTcl("tkg_enableEditModes");
    ob_mode(OM_ENABLED);
    break;
  case MM_SIMULATE :
    ob_mode(OM_DISABLED);
    ob_clear();
    XGate.circuit->simulator.no_scope = 0;
    SimInterface_begin(&XGate.circuit->simulator);
    DoTcl("tkg_resetLogo");
    DoTcl("tkg_disableEditModes");
    break;
  case MM_ANALYZE :
    ob_mode(OM_DISABLED);
    ob_clear();
    cpath_open();
    DoTcl("tkg_analysisLogo");
    DoTcl("tkg_disableEditModes");
    break;
  }
}

void showSelectedName()
{
  if (XGate.circuit->nsel) {
    message(0,msgLookup("msg.selwire"),XGate.circuit->nsel->signame);		/* Selected wire named '%s'. */
#if 0
    printf("showSelectedName: wire end\n");
#endif
  } else if (XGate.circuit->select) {
    if (XGate.circuit->select->typeinfo->Code != BLOCK) {
      if (XGate.japaneseMode)
	message(0,msgLookup("msg.selgate"), 				/* Selected %s named '%s'. */
		XGate.circuit->select->ename,
		XGate.circuit->select->typeinfo->englishName);
      else
	message(0,msgLookup("msg.selgate"), 				/* Selected %s named '%s'. */
		XGate.circuit->select->typeinfo->englishName,
		XGate.circuit->select->ename);
    } else {
      if (XGate.japaneseMode)
	message(0,msgLookup("msg.selblock"), 				/* "Selected %s block named '%s'." */
		XGate.circuit->select->ename,
		XGate.circuit->select->u.block.BlockFunction);
      else {
	message(0,msgLookup("msg.selblock"), 				/* "Selected %s block named '%s'." */
		XGate.circuit->select->u.block.BlockFunction,
		XGate.circuit->select->ename);
      }
    }
  }
}

/*
   Find a font from the specified variable name.  Issue error message and
   exit if not found.
*/
static Tk_Font GrabFont(char *name)
{
  Tk_Window root = Tk_MainWindow(XGate.tcl);
  Tk_Font F;

  if (!(F = Tk_GetFont(XGate.tcl,root,name))) {
    fprintf(stderr,"tkgate: Can't find the font '%s'\n",name);
    exit(1);
  }

  return F;
}

static int Tkg_GetColor(const char *name)
{
  XColor XC,eXC;

  XAllocNamedColor(XGate.D,XGate.CM,name,&XC,&eXC);
  return XC.pixel;
}

GC Tkg_createGC(int func,char *colorName,Tk_Font font)
{
  unsigned mask;
  XGCValues values;
  int color, white;

  white = XWhitePixelOfScreen(XGate.S);
  color  = Tkg_GetColor(colorName);

  mask = GCForeground | GCBackground | GCFunction | GCGraphicsExposures;

  if (func == GXxor) {
    mask |= GCPlaneMask;
    values.function = GXxor;
    values.foreground = color ^ white;
    values.background = 0;
    values.plane_mask = color ^ white;
  } else {
    values.function = func;
    values.foreground = color;
    values.background = white;
  }

  values.graphics_exposures = False;

  if (font) {
    values.font = Tk_FontId(font);
    mask |= GCFont;
  }

  return XCreateGC(XGate.D,XGate.root,mask,&values);
}

/*
 * Set initial color for a GC
 */
static void Tkg_setColor(GC gc,int func, const char *colorName)
{
  unsigned mask;
  XGCValues values;
  int color, white;

  if (!colorName) return;

  white = XWhitePixelOfScreen(XGate.S);
  color  = Tkg_GetColor(colorName);

  mask = GCForeground | GCBackground;

  if (func == GXxor) {
    mask |= GCPlaneMask;
    values.foreground = color ^ white;
    values.background = 0;
    values.plane_mask = color ^ white;
  } else {
    values.foreground = color;
    values.background = white;
  }

  XChangeGC(XGate.D,gc,mask,&values);
}

void Tkg_changeColor(GC gc,int func,int pixel)
{
  XGCValues values;
  unsigned mask;
  int white;

  white = XWhitePixelOfScreen(XGate.S);
  mask = GCForeground | GCBackground;

  if (func == GXxor) {
    mask |= GCPlaneMask;
    values.foreground = pixel ^ white;
    values.background = 0;
    values.plane_mask = pixel ^ white;
  } else {
    values.foreground = pixel;
    values.background = white;
  }

  XChangeGC(XGate.D,gc,mask,&values);
}

void setGCcolors()
{
  Tkg_setColor(XGate.instGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_instColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.moduleGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_moduleColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.modportGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_modulePortColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.frameGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_frameColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.commentGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_commentColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.hyperlinkGC,	GXxor, Tcl_GetVar(XGate.tcl,"tkg_hyperlinkColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.wireGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_wireColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.busGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_busColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.selWireGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_wireColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.selBusGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_busColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.toolGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_toolColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.cpathGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_cpathColor",TCL_GLOBAL_ONLY));
#if TKGATE_JSUPPORT
  Tkg_setColor(XGate.kanjiGC,		GXxor, Tcl_GetVar(XGate.tcl,"tkg_commentColor",TCL_GLOBAL_ONLY));
#endif
  /*  Tkg_setColor(XGate.scopeSelectGC,	GXxor,  Tcl_GetVar(XGate.tcl,"tkg_gridColor",TCL_GLOBAL_ONLY));*/
  Tkg_setColor(XGate.scopeXHairGC,	GXxor,  Tcl_GetVar(XGate.tcl,"tkg_gridColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.scopeGridGC,	GXcopy, Tcl_GetVar(XGate.tcl,"tkg_gridColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.scopeOneGC,	GXcopy, Tcl_GetVar(XGate.tcl,"tkg_oneColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.scopeZeroGC,	GXcopy, Tcl_GetVar(XGate.tcl,"tkg_zeroColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.scopeFloatGC,	GXcopy, Tcl_GetVar(XGate.tcl,"tkg_floatColor",TCL_GLOBAL_ONLY));
  Tkg_setColor(XGate.scopeUnknownGC,	GXcopy, Tcl_GetVar(XGate.tcl,"tkg_unknownColor",TCL_GLOBAL_ONLY));

  XGate.inst_pixel       = Tkg_GetColor(Tcl_GetVar(XGate.tcl,"tkg_instColor",TCL_GLOBAL_ONLY));
  XGate.ledoff_pixel     = Tkg_GetColor(Tcl_GetVar(XGate.tcl,"tkg_offLedColor",TCL_GLOBAL_ONLY));
  XGate.ledon_pixel      = Tkg_GetColor(Tcl_GetVar(XGate.tcl,"tkg_onLedColor",TCL_GLOBAL_ONLY));
  XGate.ledunknown_pixel = Tkg_GetColor(Tcl_GetVar(XGate.tcl,"tkg_zLedColor",TCL_GLOBAL_ONLY));

  XGate.comment_pixel = Tkg_GetColor(Tcl_GetVar(XGate.tcl,"tkg_commentColor",TCL_GLOBAL_ONLY));
  XGate.hyperlink_pixel = Tkg_GetColor(Tcl_GetVar(XGate.tcl,"tkg_hyperlinkColor",TCL_GLOBAL_ONLY));
}

/*
   Allocate GCs for main window
*/
void initGCs()
{
  unsigned mask;
  XGCValues values;
  Pixmap bitPM;
  char fontName[STRMAX];

  /*
     Do font set up
   */
  sprintf(fontName,BODYFONT,font_encoding);
  XGate.textF   = GrabFont(fontName);
  XGate.textXF   = Tk_FontId(XGate.textF);

  sprintf(fontName,BOLDBODYFONT,font_encoding);
  XGate.textbF  = GrabFont(fontName);
  XGate.textbXF  = Tk_FontId(XGate.textbF);

  sprintf(fontName,SMALLFONT,font_encoding);
  XGate.stextF  = GrabFont(fontName);
  XGate.stextXF  = Tk_FontId(XGate.stextF);

  sprintf(fontName,BOLDSMALLFONT,font_encoding);
  XGate.stextbF = GrabFont(fontName);
  XGate.stextbXF = Tk_FontId(XGate.stextbF);

#if TKGATE_JSUPPORT
  XGate.ktextF = GrabFont(KANJIFONT);
  XGate.ktextXF = Tk_FontId(XGate.ktextF);
#endif


  XGate.inst_pixel = Tkg_GetColor("blue");
  XGate.ledoff_pixel = Tkg_GetColor("firebrick4");
  XGate.ledon_pixel = Tkg_GetColor("red");
  XGate.ledunknown_pixel = Tkg_GetColor("yellow");

  XGate.instGC       = Tkg_createGC(GXxor,"blue",XGate.textF);
  XGate.moduleGC     = Tkg_createGC(GXxor,"magenta4",XGate.textF);
  XGate.modportGC    = Tkg_createGC(GXxor,"cyan4",XGate.textF);
  XGate.frameGC      = Tkg_createGC(GXxor,"tan4",XGate.textF);
  XGate.commentGC    = Tkg_createGC(GXxor,"tan4",XGate.textF);
  XGate.hyperlinkGC  = Tkg_createGC(GXxor,"tan4",XGate.textF);
  XGate.wireGC       = Tkg_createGC(GXxor,"green4",XGate.stextF);
  XGate.busGC        = Tkg_createGC(GXxor,"red",XGate.stextF);
  XGate.selWireGC    = Tkg_createGC(GXxor,"green4",XGate.stextF);
  XGate.selBusGC     = Tkg_createGC(GXxor,"red",XGate.stextF);
  XGate.toolGC       = Tkg_createGC(GXxor,"black",XGate.textF);
  XGate.cpathGC      = Tkg_createGC(GXxor,"red",XGate.textF);
#if TKGATE_JSUPPORT
  XGate.kanjiGC      = Tkg_createGC(GXxor,"tan4",XGate.ktextF);
#endif

  XGate.copyGC       = Tkg_createGC(GXcopy,"black",XGate.textF);
  XSetGraphicsExposures(XGate.D,XGate.copyGC,True);

  XGate.scopeXHairGC   = Tkg_createGC(GXxor,"black",XGate.textF);
  XGate.scopeSelectGC  = Tkg_createGC(GXxor,"grey90",XGate.textF);
  XGate.scopeGridGC    = Tkg_createGC(GXcopy,"black",XGate.textF);
  XGate.scopeOneGC     = Tkg_createGC(GXcopy,"green4",XGate.stextF);
  XGate.scopeZeroGC    = Tkg_createGC(GXcopy,"magenta",XGate.stextF);
  XGate.scopeFloatGC   = Tkg_createGC(GXcopy,"blue",XGate.stextF);
  XGate.scopeUnknownGC = Tkg_createGC(GXcopy,"red",XGate.stextF);
  XGate.scopeClearGC   = Tkg_createGC(GXcopy,"white",XGate.stextF);

  XSetLineAttributes(XGate.D,XGate.busGC,2,LineSolid,CapButt,JoinMiter);
  XSetLineAttributes(XGate.D,XGate.selBusGC,3,LineSolid,CapButt,JoinMiter);
  XSetLineAttributes(XGate.D,XGate.selWireGC,2,LineSolid,CapButt,JoinMiter);

  mask = GCLineStyle | GCDashList;
  values.line_style = LineOnOffDash;
  values.dashes = DASH_LENGTH/2;
  XChangeGC(XGate.D,XGate.frameGC, mask, &values);

  mask = GCForeground | GCBackground | GCFunction | GCGraphicsExposures; 
  values.foreground = 1;
  values.background = 1;
  values.function = GXcopy;
  values.graphics_exposures = False;

  bitPM = XCreatePixmap(XGate.D,XGate.root,8,8,1);
  XGate.bitGC = XCreateGC(XGate.D,bitPM,mask,&values);
  XFreePixmap(XGate.D,bitPM);
}


/*
   The XGateParms object is the top-level data structure used in the
   original xgate program.  Since there is too much depending on it,
   just initialize it appropriately for now.
*/
static void initGateParms(TkgGateWin *gw,XGateParams *P)
{
  Tk_MakeWindowExist(gw->win);

  P->W = Tk_WindowId(gw->win);

  P->circuit->es = new_EditState();
  P->circuit->es->env = env_defineModule("main",1);
  editstate_setCurrent(P->circuit->es);
  XGate.circuit->root_mod = P->circuit->es->env;
  XGate.circuit->no_set_modify = 0;
  XGate_setOrigin(0,0);
}

/*
   Do any necessary cleanup here.
*/
void gateCleanUp()
{
  Tk_FreeFont(XGate.textF);
  Tk_FreeFont(XGate.textbF);
  Tk_FreeFont(XGate.stextF);
  Tk_FreeFont(XGate.stextbF);
#if TKGATE_JSUPPORT
  Tk_FreeFont(XGate.ktextF);
#endif
}


/*
 * This function is called when the tk event queue becomes idle.
 */
void idleGateWin(ClientData data)
{
  extern int want_exit;

  if (want_exit) {
    MajorMode cm = tkgate_currentMode();
    switch (cm) {
    case MM_SIMULATE :
      if (XGate.circuit->simulator.active)
	SimInterface_end(&XGate.circuit->simulator);
      break;
    case MM_ANALYZE :
      cpath_close();
      break;
    default:
      break;
    }
    DoTcl("tkg_exit");
  }

#if TOUCH_XGATE_ED
  ob_begin_framef("-Idle",FF_TRANSPARENT);
#endif

  if (XGate.idle_ev.scroll_area) {
    ob_begin_framef("Scroll",FF_STICKY);
    doScroll(wtoc_x(XGate.idle_ev.scroll_new_x),
	     wtoc_y(XGate.idle_ev.scroll_new_y));
    ob_end_frame();

    XGate.idle_ev.scroll_new_x = XGate.circuit->org_x;
    XGate.idle_ev.scroll_new_y = XGate.circuit->org_y;
    XGate.idle_ev.scroll_area = 0;
    XGate.idle_ev.redraw = 0;
  } else {
    if (XGate.idle_ev.redraw) {
      editstate_fullUpdate(XGate.circuit->es);
      XGate.idle_ev.redraw = 0;
    }
  }

  if (XGate.idle_ev.scope_redraw || XGate.idle_ev.trace_redraw) {
    extern GScope *Scope;
    if (Scope)
      GScope_fullUpdate(Scope);
    XGate.idle_ev.scope_redraw = 0;
    XGate.idle_ev.trace_redraw = 0;
  }

#if TOUCH_XGATE_ED
  ob_end_frame();
#endif

  XGate.idle_ev.pending = 0;
}

/*
 * This function is called to request that a redraw be performed the
 * next time the tk event queue becomes empty.
 */
void FlagRedraw()
{
  XGate.idle_ev.redraw = 1;
  XGate.idle_ev.scroll_area = 0;

  if (!XGate.idle_ev.pending) {
    XGate.idle_ev.pending = 1;
    Tk_DoWhenIdle(idleGateWin,0);
  }
}

/*
 * This function is called to request that scrolling be done the next time
 * the tk event queue becomes empty.  Calls to this function accumulate
 * x and y change, and when the action is performed the combined scrolling
 * action is performed.
 */
void FlagScrolling()
{
  if (!XGate.idle_ev.redraw) {
    XGate.idle_ev.redraw = 1;
    XGate.idle_ev.scroll_area = 1;
  }

  if (!XGate.idle_ev.pending) {
    XGate.idle_ev.pending = 1;
    Tk_DoWhenIdle(idleGateWin,0);
  }
}

/*
  This function is used to indicate that circuit data which might affect
  the scroll bar region has been updated.
 */
void FlagScrollRegion()
{
  
}
 

/*
 *  Handle an X event for a gate window
 */
static void gateWinEvent(ClientData data, XEvent *E)
{
#if TOUCH_XGATE_ED
  ob_begin_framef("-WEvent",FF_TRANSPARENT);
#endif

  switch (E->type) {
  case DestroyNotify :
    gateCleanUp();
    break;
  case GraphicsExpose :
    if (regionUpdate) {
      XGraphicsExposeEvent *xE = &E->xgraphicsexpose;
      editstate_regionUpdate(XGate.circuit->es,xE->x,xE->y,xE->x+xE->width,xE->y+xE->height);
    } else {
      FlagRedraw();
    }
    break;
  case Expose :
    if (regionUpdate) {
      XExposeEvent *xE = &E->xexpose;
      editstate_regionUpdate(XGate.circuit->es,xE->x,xE->y,xE->x+xE->width,xE->y+xE->height);
    } else {
      FlagRedraw();
    }
    break;
  case NoExpose :
    break;
  default :
    FlagRedraw();
  }
#if TOUCH_XGATE_ED
  ob_end_frame();
#endif
}

/*
 * Update the positions of the scroll bars to reflect the new window visiblity.
 */
void scrollbar_update()
{
  int width = XGate.width/XGate.circuit->zoom_factor;
  int height = XGate.height/XGate.circuit->zoom_factor;
  int x0 = -XGate.circuit->org_x;
  int y0 = -XGate.circuit->org_y;
  int min_x = XGate.ed->min_x;
  int min_y = XGate.ed->min_y;
  int max_x = XGate.ed->max_x;
  int max_y = XGate.ed->max_y;
  double range_x = 1.0/(double)imax(10,max_x-min_x);
  double range_y = 1.0/(double)imax(10,max_y-min_y);
  double xb = 0.0;
  double xe = 1.0;
  double yb = 0.0;
  double ye = 1.0;

  xb = (x0          - min_x)*range_x;
  xe = (x0 + width  - min_x)*range_x;
  yb = (y0          - min_y)*range_y;
  ye = (y0 + height - min_y)*range_y;

  if (xb < 0) xb = 0;
  if (xe > 1) xe = 1;
  if (yb < 0) yb = 0;
  if (ye > 1) ye = 1;

  if (min_x >= max_x) {
    xb = yb = 0;
    xe = ye = 1;
  }

  DoTcl("%s %lf %lf",XGate.gw->xscroll,xb,xe);
  DoTcl("%s %lf %lf",XGate.gw->yscroll,yb,ye);
#if 0
  printf("Y-Scroll: [%d..%d] (%d..%d) --> %s %lf %lf\n",min_y,max_y,y0,y0+XGate.height,XGate.gw->yscroll,yb,ye);
  printf("X-Scroll: [%d..%d] (%d..%d) --> %s %lf %lf\n",min_x,max_x,x0,x0+XGate.width ,XGate.gw->xscroll,xb,xe);
#endif
}

void scrollbar_bbx_update()
{
#if TOUCH_XGATE_ED
  ob_touch(XGate.ed);
#endif
  GModuleDef_getBBX(XGate.circuit->es->env,
		    &XGate.ed->min_x,&XGate.ed->max_x,
		    &XGate.ed->min_y,&XGate.ed->max_y);
  scrollbar_update();
}


static int parseScrollDistance(int argc,const char *argv[],int org,int min_c,int max_c,int range_c,int *new_org_c)
{
  int len = max_c - min_c;
  int old_new_org_c = *new_org_c;				/* Old value of coordinate update value */
  int x;

  if (argc == 1) {						/* n */
    sscanf(argv[0],"%d",&x);
  } else if (argc == 2 && strcmp(argv[0],"press") == 0) {	/* press n */
    int b;
    sscanf(argv[1],"%d",&b);

    scrollbar_update();

    XGate.ed->scr_x = XGate.circuit->org_x;
    XGate.ed->scr_y = XGate.circuit->org_y;
  } else if (argc == 2 && strcmp(argv[0],"moveto") == 0) {	/* moveto n */
    double dx;
    sscanf(argv[1],"%lf",&dx);

    *new_org_c = -(min_c + (int) (dx*len)) - imin(0,-org-min_c);


  } else if (argc == 3 && strcmp(argv[0],"scroll") == 0		/* scroll n pages */
	     && strcmp(argv[2],"pages") == 0) {
    double dx;
    sscanf(argv[1],"%lf",&dx);

    *new_org_c -= (int) (dx*len);
  } else if (argc == 3 && strcmp(argv[0],"scroll") == 0		/* scroll n units */
	     && strcmp(argv[2],"units") == 0) {
    double dx;
    sscanf(argv[1],"%lf",&dx);

    *new_org_c -= (int) (dx*50);
  }

  if (-*new_org_c > max_c + SCROLL_LIMIT)
    *new_org_c = -(max_c + SCROLL_LIMIT);
  if (-*new_org_c < min_c - range_c - SCROLL_LIMIT)
    *new_org_c = -(min_c - range_c - SCROLL_LIMIT);


  return old_new_org_c != *new_org_c;
}

static int parseXScrollDistance(int argc,const char *argv[])
{
  int x0 = XGate.ed->scr_x;
  int min_x = XGate.ed->min_x;
  int max_x = imax(XGate.ed->max_x,XGate.ed->min_x+XGate.width/XGate.circuit->zoom_factor);

  return parseScrollDistance(argc,argv,x0,min_x,max_x,XGate.width/XGate.circuit->zoom_factor,&XGate.idle_ev.scroll_new_x);
}

static int parseYScrollDistance(int argc,const char *argv[])
{
  int y0 = XGate.ed->scr_y;
  int min_y = XGate.ed->min_y;
  int max_y = imax(XGate.ed->max_y,XGate.ed->min_y+XGate.height/XGate.circuit->zoom_factor);

  return parseScrollDistance(argc,argv,y0,min_y,max_y,XGate.height/XGate.circuit->zoom_factor,&XGate.idle_ev.scroll_new_y);
}

/*
 * Do a scrolling update in the circuit window.
 */
void doScroll(int dx,int dy)
{
  /*
   * If circuit is empty, return to default position.
   */
  if (XGate.ed->min_x >= XGate.ed->max_x) {
    XGate_setOrigin(0,0);
    return;
  }

  if (dx != 0) {
    int x1,y1,w,h,x2,y2;
    int delta = -dx*XGate.circuit->zoom_factor;

    h = XGate.height;
    w = XGate.width;
    y1 = y2 = 0;
    if (delta > 0) {
      x1 = delta;
      x2 = 0;
    } else {
      x1 = 0;
      x2 = -delta;
    }

    XCopyArea(XGate.D,XGate.W,XGate.W,XGate.copyGC,x1,y1,w,h,x2,y2);

    XGate_setOrigin(XGate.circuit->org_x + dx,XGate.circuit->org_y);
    if (delta > 0) {
      editstate_regionUpdate(XGate.circuit->es,w-delta-1,0,w+1,h+1);
    } else {
      editstate_regionUpdate(XGate.circuit->es,0,0,-delta+1,h+1);
    }
  }
  if (dy != 0) {
    int x1,y1,w,h,x2,y2;
    int delta = -dy*XGate.circuit->zoom_factor;

    h = XGate.height;
    w = XGate.width;
    x1 = x2 = 0;
    if (delta > 0) {
      y1 = delta;
      y2 = 0;
    } else {
      y1 = 0;
      y2 = -delta;
    }

    XCopyArea(XGate.D,XGate.W,XGate.W,XGate.copyGC,x1,y1,w,h,x2,y2);

    XGate_setOrigin(XGate.circuit->org_x,XGate.circuit->org_y + dy);
    if (delta > 0) {
      editstate_regionUpdate(XGate.circuit->es,0,h-delta-1,w+1,h+1);
    } else {
      editstate_regionUpdate(XGate.circuit->es,0,0,w+1,-delta+1);
    }
  }
}


static int gateWinCommand(ClientData data, Tcl_Interp *tcl, int argc, const char *argv[])
{
  TkgGateWin *gw = (TkgGateWin*) data;


  if (strcmp(argv[1],"xview") == 0) {
    XGate.idle_ev.scroll_new_x = XGate.circuit->org_x;
    XGate.idle_ev.scroll_new_y = XGate.circuit->org_y;
    if (parseXScrollDistance(argc-2,argv+2)) {
      if (smoothScroll) {
	FlagScrolling();
      } else {
	ob_begin_framef("Scroll",FF_TRANSPARENT);
	XGate_setOrigin(XGate.idle_ev.scroll_new_x,XGate.circuit->org_y);
	ob_end_frame();
	FlagRedraw();
      }
    }
  } else if (strcmp(argv[1],"yview") == 0) {
    XGate.idle_ev.scroll_new_x = XGate.circuit->org_x;
    XGate.idle_ev.scroll_new_y = XGate.circuit->org_y;
    if (parseYScrollDistance(argc-2,argv+2)) {
      if (smoothScroll) {
	FlagScrolling();
      } else {
	ob_begin_framef("Scroll",FF_TRANSPARENT);
	XGate_setOrigin(XGate.circuit->org_x, XGate.idle_ev.scroll_new_y);
	ob_end_frame();
	FlagRedraw();
      }
    }
  } else if (argc == 3 && strcmp(argv[1],"cget") == 0) {
    int i;

    for (i = 0;i < numConfigSpecs;i++) {
      Tk_ConfigSpec *C = &configSpecs[i];
      if (C->type == TK_CONFIG_STRING && strcmp(C->argvName,argv[2]) == 0) {
	char *value = *(char**)(((char*)gw) + C->offset);
	strcpy(tcl->result,value);
	break;
      }
    }
  }

  return TCL_OK;
}

void configWarning(int p)
{
  printf("\n- FATAL ERROR -\n\n");
  printf("If you get this message, it probably means that Tk_ConfigureWidget is\n");
  printf("broken on your machine.  Try recompiling with TKGATE_BROKENCONFIGWIDGET\n");
  printf("set to 1 in the top-level file config.h.\n");

  exit(1);
}

static int gateWinConfigure(Tcl_Interp *tcl, TkgGateWin *gw, int argc, const char **argv, int flags)
{
#if TKGATE_BROKENCONFIGWIDGET
  char *wstr = Tcl_GetVar(XGate.tcl,"tkg_initialWidth",TCL_GLOBAL_ONLY);
  char *hstr = Tcl_GetVar(XGate.tcl,"tkg_initialHeight",TCL_GLOBAL_ONLY);
  if (wstr) sscanf(wstr,"%d",&gw->width);
  if (hstr) sscanf(hstr,"%d",&gw->height);
  gw->xscroll = ".horz set";
  gw->yscroll = ".vert set";
  gw->takefocus = 0;

  Tk_SetWindowBackground(gw->win, XWhitePixelOfScreen(XGate.S));
#else
  signal(SIGBUS,configWarning);
  signal(SIGSEGV,configWarning);

  if (Tk_ConfigureWidget(tcl, gw->win, configSpecs, argc, argv, (char*) gw, flags) != TCL_OK)
    return TCL_ERROR;

  Tk_SetWindowBackground(gw->win, gw->bgColor->pixel);

  signal(SIGBUS,SIG_DFL);
  signal(SIGSEGV,SIG_DFL);
#endif

  Tk_GeometryRequest(gw->win,gw->width,gw->height);

  if (gw->gc == None) {
    XGCValues gcv;

    gcv.function = GXxor;
    gcv.graphics_exposures = False;
    gw->gc = Tk_GetGC(gw->win,GCFunction|GCGraphicsExposures, &gcv);

  }

  return TCL_OK;
}

static void gateWinDestroy(ClientData data)
{
  printf("destroy\n");
}

/*
   Process a command from creating a gate circuit editing window.
   
   gatewin .w
*/
static int makeGateWin(ClientData data, Tcl_Interp *tcl, int argc, const char **argv)
{
  Tk_Window root = (Tk_Window) data;
  TkgGateWin *gw;
  Tk_Window w;

  if (argc < 2) {
    Tcl_AppendResult(tcl, "wrong # args: should be \"",argv[0], " pathName ?options?\"", 0);
    return TCL_ERROR;
  }

  w = Tk_CreateWindowFromPath(tcl, root, argv[1], 0);
  if (!w) return TCL_ERROR;
  Tk_SetClass(w, "GateWin");

  gw = (TkgGateWin*) ob_malloc(sizeof(TkgGateWin),"TkgGateWin");
  gw->win = w;
  gw->d = Tk_Display(w);
  gw->tcl = tcl;
  gw->width = TGK_GATEWIN_WIDTH;
  gw->height = TGK_GATEWIN_HEIGHT;
  gw->gc = None;
  gw->bgColor = gw->fgColor = 0;
  gw->parms = &XGate;
  gw->xscroll = 0;
  gw->yscroll = 0;
  gw->takefocus = 0;
  XGate.gw = gw;

  initGateParms(gw,&XGate);

  Tk_CreateEventHandler(w, ExposureMask|StructureNotifyMask,
			gateWinEvent, gw);
  Tcl_CreateCommand(tcl, Tk_PathName(w), gateWinCommand, gw, gateWinDestroy);
  if (gateWinConfigure(tcl, gw, argc-2, argv+2,0) != TCL_OK) {
    Tk_DestroyWindow(w);
    return TCL_ERROR;
  }

  setGCcolors();

  tcl->result = Tk_PathName(w);
  return TCL_OK;
}

int DoTcl(char *str,...)
{
  char buf[1024];
  int r;
  va_list ap;

  /*
   * Ignore tcl commands invoked before starting up the editor.
   */
  if (!XGate.tcl) return TCL_OK;

  va_start(ap,str);

  vsprintf(buf,str,ap);
  r = Tcl_Eval(XGate.tcl,buf);
  va_end(ap);

  if (r != TCL_OK) {
    printf("DoTCL Error: %s\n",XGate.tcl->result);
    printf("   while executing: %s\n",buf);
  }

  return r;
}


/*
   Handle a button press in a gate edit window
*/
static int pressGateWin(ClientData data, Tcl_Interp *tcl, int argc, const char *argv[])
{
  EditState *es = XGate.circuit->es;
  int x,y,state;

  DoTcl("tkg_cancel");
  DoTcl("tkg_undoSelections gate");


  sscanf(argv[2],"%d",&x);
  sscanf(argv[3],"%d",&y);
  sscanf(argv[4],"%d",&state);

  x /= XGate.circuit->zoom_factor;
  y /= XGate.circuit->zoom_factor;

  XGate.state = state;
#if TOUCH_XGATE_ED
  ob_touch(XGate.ed);
#endif
  XGate.ed->lx = XGate.ed->tx;
  XGate.ed->ly = XGate.ed->ty;
  XGate.ed->tx = wtoc_x(x);
  XGate.ed->ty = wtoc_y(y);

  switch (tkgate_currentMode()) {
  case MM_SIMULATE :
    SimInterface_hit(&XGate.circuit->simulator,XGate.ed->tx,XGate.ed->ty,0);
    break;
  case MM_ANALYZE :
    cpath_mouseDown(es);
    break;
  case MM_EDIT :
    mousedowntransition(es);
    break;
  }

  showSelectedName();

  return TCL_OK;
}

/*
   Handle a double click in a gate edit window
*/
static int doublePressGateWin(ClientData data, Tcl_Interp *tcl, int argc, const char *argv[])
{
  switch (tkgate_currentMode()) {
  case MM_SIMULATE :
    SimInterface_hit(&XGate.circuit->simulator,XGate.ed->tx,XGate.ed->ty,1);
    break;
  case MM_ANALYZE :
    break;
  case MM_EDIT :
    if (XGate.circuit->mode == MODE_MOVE) {
      did_doubleclick = 1;
    }
    break;
  }

  return TCL_OK;
}

/*****************************************************************************
 * Generate a checkpoint file name from the specified base filename.
 *****************************************************************************/
void getCheckpointFilename(char *checkPointFile,const char *fileName)
{
  const char *p,*l;
  char *q;
  int n;

  if (!fileName) {
      strcpy(checkPointFile,"#checkpoint#");
      return;
  }

  /*
   * Strip off directory part and copy it to the checkpoint file name.
   */
  p = strrchr(fileName,'/');
  if (p) {
    p++;
    q = checkPointFile+(p-fileName)+1;
  } else {
    p = fileName;
    q = checkPointFile;
  }

  if (!*p) {			/* The filename part of the path was empty */
    strcpy(q,"#checkpoint#");
    return;
  }

  l = strrchr(p,'#');			/* Get the trailing '#' if there */
  if (!l) {
    sprintf(q,"#%s#",p);		/* This is a normal checkpoint file name */
    return;
  }

  if (l[1] == 0) {			/* If the original file ends with a '#', */ 
    sprintf(q,"%s2",p+1);		/* Just append a '2' */
    return;
  }

  /*
   * Make sure all trailing characters are digits.
   */
  n = 1;
  for (;l[n] && isdigit(l[n]);n++);

  /*
   * There are non-numeric characters after the '#' or there are too many digits.
   */
  if (l[n] != 0 || n > 4 || sscanf(l,"#%d",&n) != 1) {
    sprintf(q,"%s#",p+1);		/*  append a '#' */
    return;
  }

  /*
   * generate checkpoint of a checkpoint of a checkpoint .... by incrementing the
   * checkpoint count.  Do we really need to handle this case?
   */
  sprintf(q,"%s",p+1);
  sprintf(q+(l-p)+1,"%d",n+1);
}

/*****************************************************************************
 * Test if it we want to write the checkpoint file and if so, write it.
 *****************************************************************************/
static void doCheckpointTest()
{
  extern int wantCheckpoint,checkpointenabled;
  const char *currentFile;

  currentFile = Tcl_GetVar(XGate.tcl,"tkg_currentFile",TCL_GLOBAL_ONLY);

  if (wantCheckpoint && checkpointenabled && changedp && !XGate.circuit->discardChanges && currentFile) {
    char buf[STRMAX];

    getCheckpointFilename(buf,currentFile);	/* Generate checkpoint file name */
    message(0,msgLookup("msg.wroteckpt"),buf);	/* Checkpointed to %s... */
    VerilogWriteModules(buf,0);			/* Write the ckeckpoint file */
  }

  wantCheckpoint = 0;
}

/*
   Handle a button release click in a gate edit window
*/
static int releaseGateWin(ClientData data, Tcl_Interp *tcl, int argc, const char *argv[])
{
  EditState *es = XGate.circuit->es;
  int x,y,state;
  int ret = TCL_OK;

  DoTcl("tkg_hideTempMessage");

  if (XGate.circuit->select || XGate.circuit->nsel)
    message(0,"");

  switch (tkgate_currentMode()) {
  case MM_SIMULATE :
    SimInterface_hitRelease(&XGate.circuit->simulator);
    ret = TCL_OK;
    break;
  case MM_ANALYZE :
    cpath_mouseUp(es);
    ret = TCL_OK;
    break;
  case MM_EDIT :
    sscanf(argv[2],"%d",&x);
    sscanf(argv[3],"%d",&y);
    sscanf(argv[4],"%d",&state);

    x /= XGate.circuit->zoom_factor;
    y /= XGate.circuit->zoom_factor;

    XGate.state = state;
#if TOUCH_XGATE_ED
    ob_touch(XGate.ed);
#endif
    XGate.ed->lx = XGate.ed->tx;
    XGate.ed->ly = XGate.ed->ty;
    XGate.ed->tx = wtoc_x(x);
    XGate.ed->ty = wtoc_y(y);
    mouseuptransition(es);

    if (did_doubleclick && XGate.circuit->mode == MODE_MOVE) {
      GCElement *g;
      GWire *w;
      selectobject(es);
      g = XGate.circuit->select;
      w = XGate.circuit->wsel;
      dropobject(es);
      mark_unpost();
      if (g) {
	if (g->typeinfo->Code != COMMENT || (XGate.state & (ShiftMask|ControlMask|Mod1Mask)) || !g->u.comment.doLink) {
	  ob_suggest_name("EditProps");
	  gat_editProps((ClientData)XGate.gw,tcl,1,0);
	} else
	  dohyperlink(g->u.comment.link);

      } else if (w) {
	ob_suggest_name("EditProps");
	net_editProps(w->net,XGate.ed->tx,XGate.ed->ty);
      }
    }

    did_doubleclick = 0;

    doCheckpointTest();

    scrollbar_bbx_update();

    ret = TCL_OK;
    break;
  }

  if (debugContinuousVerify)
    verify_circuit();

  return ret;
}

/*
   Handle button motion in a gate edit window
*/
static int motionGateWin(ClientData data, Tcl_Interp *tcl, int argc, const char *argv[])
{
  EditState *es = XGate.circuit->es;
  int x,y,state;

  sscanf(argv[2],"%d",&x);
  sscanf(argv[3],"%d",&y);
  sscanf(argv[4],"%d",&state);

  x /= XGate.circuit->zoom_factor;
  y /= XGate.circuit->zoom_factor;

  switch (tkgate_currentMode()) {
  case MM_SIMULATE :
    break;
  case MM_ANALYZE :
    break;
  case MM_EDIT :
    XGate.state = state;
#if TOUCH_XGATE_ED
    ob_touch(XGate.ed);
#endif
    XGate.ed->lx = XGate.ed->tx;
    XGate.ed->ly = XGate.ed->ty;
    XGate.ed->tx = wtoc_x(x);
    XGate.ed->ty = wtoc_y(y);
    mousedownmovement(es);
    break;
  }

  return TCL_OK;
}

void XGate_clearSelection()
{
#if TOUCH_XGATE_ED
  ob_touch(XGate.ed);
#endif
  XGate.ed->tx = XGate.ed->ty = XGate.ed->lx = XGate.ed->ly = XGate.ed->sx = XGate.ed->sy = 0;
  XGate.circuit->select = 0;
  XGate.circuit->last = 0;
  XGate.circuit->wsel = 0;
  XGate.circuit->wnsel = 0;
  XGate.circuit->nsel = 0;
}

/*
   Set up a widget for the main tkgate window in which editing is performed.
*/
void configureMainWindow(Tcl_Interp *tcl)
{
  Tk_Window root = Tk_MainWindow(tcl);
  Tk_MakeWindowExist(root);

  XGate.ed = (EditData*) ob_malloc(sizeof(EditData),"EditData");
  XGate.errl = (ErrorList*) ob_malloc(sizeof(ErrorList),"ErrorList");

  XGate.tcl = tcl;
  XGate.D = Tk_Display(root);
  XGate.root = Tk_WindowId(root);
  XGate.S = XDefaultScreenOfDisplay(XGate.D);
  XGate.CM = DefaultColormapOfScreen(XGate.S);
  XGate.ScopeW = None;
  XGate.rdb = XrmGetDatabase(XGate.D);


  XGate.circuit = new_Circuit(1);

  XGate.ed->mark_vis = 0;
  XGate.ed->mark_posted = 0;
  XGate.ed->major_mode = MM_EDIT;
  XGate.errl->errlist = 0;
  XGate.errl->curerr = 0;
  XGate.errl->errors = 0;
  XGate.errl->ErrorCount = 0;
  XGate.errl->ErrorXPos = 0;
  XGate.errl->ErrorYPos = 0;
  XGate.ErrorMarkTimeout = 0;

  XGate_clearSelection();
  XGate_clearSelection();

  SimInterface_init(&XGate.circuit->simulator);

  XGate.circuit->search = new_GSearchContext();

  if (sync_Xserver) {
    printf("[synchonized X11 connection]\n");
    XSynchronize(XGate.D,True);
  }
  
  initGCs();
  
  Tcl_CreateCommand(tcl,"gat_scope"
		    ,gat_scope
		    ,(ClientData)root
		    ,0);

  Tcl_CreateCommand(tcl,"tkg_gatewin"
		    ,makeGateWin
		    ,(ClientData)root
		    ,0);

  Tcl_CreateCommand(tcl,"tkg_buttonPress"
		    ,pressGateWin
		    ,(ClientData)root
		    ,0);

  Tcl_CreateCommand(tcl,"tkg_buttonDoublePress"
		    ,doublePressGateWin
		    ,(ClientData)root
		    ,0);

  Tcl_CreateCommand(tcl,"tkg_buttonRelease"
		    ,releaseGateWin
		    ,(ClientData)root
		    ,0);

  Tcl_CreateCommand(tcl,"tkg_buttonMotion"
		    ,motionGateWin
		    ,(ClientData)root
		    ,0);
}

