/****************************************************************************
    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 Fri Feb 20 10:35:58 2004
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "tkgate.h"

#define SKIPFIELD(C) { \
    for (;*C && isspace(*C);C++); \
    for (;*C && !isspace(*C);C++); \
    }

int RangeVals[MAXRANGEPOS] = {10,20,50,100,200,500,1000,2000,5000,10000,20000,50000};

GScope *Scope = NULL;

void idleGateWin(ClientData);
void DrawScopeScale(GScope *S,int);

void delete_Trace(GTrace *T);

void scope_fixtraces(GScope *S);


int GScope_x2t(GScope *S,int x)
{
  int pmin = ScopeLEFTMARGIN;
  int pmax = S->Width-ScopeRIGHTMARGIN;
  double f = ((double)x-pmin)/(double)(pmax-pmin);
  return (int)(S->LeftTime + S->Range*f);
}

int GScope_t2x(GScope *S,int t)
{
  int pmin = ScopeLEFTMARGIN;
  int pmax = S->Width-ScopeRIGHTMARGIN;
  double f = ((double)t - S->LeftTime)/(double)S->Range;

  return (int) (f*(pmax-pmin)+pmin);
}

/*
  Request a complete redraw of the scope window
 */
void ReqScopeRedisplay()
{
  XGate.idle_ev.scope_redraw = 1;

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

/*
  Request a redraw of only the traces and the left origin
 */
void ReqScopeTraceRedisplay()
{
  XGate.idle_ev.trace_redraw = 1;

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

int IsTransition(GScope *S,GValue *V,int N)
{
  if (!V || !V->v_next) return 0;

  if (N == 1) {
    if (V->v_next->v_time <= S->LeftTime) return 0;
    if (V->v_next->v_time > S->LeftTime + S->Range) return 0;
    if (V->v_code == VC_UNRECORDED) return 0;
    if (V->v_next->v_code == VC_UNRECORDED) return 0;
    if (V->v_code == V->v_next->v_code) return 0;
  } else {
    if (V->v_next->v_time < S->LeftTime) return 0;
    if (V->v_next->v_time > S->LeftTime + S->Range) return 0;
  }
  
  return 1;
}

int FromFloatTransition(GScope *S,GValue *V)
{
  return V->v_code == VC_FLOAT && 
    (V->v_next->v_code == VC_ONE || V->v_next->v_code == VC_ZERO);
}

int ToFloatTransition(GScope *S,GValue *V)
{
  return (V->v_code == VC_ONE || V->v_code == VC_ZERO) &&
    V->v_next->v_code == VC_FLOAT;
}

void GValue_clearVis(GValue *V)
{
  if (V->v_dpyHexValue) ob_free(V->v_dpyHexValue);
  V->v_dpyHexValue = 0;
}


/*
 *   Adjust scope so that T->Current is the first change before LTIme
 */
void GTrace_adjust(GTrace *T,int LTime)
{
  while (T->Current->v_next && T->Current->v_next->v_time <= LTime) {
    T->Current = T->Current->v_next;
  }
  while (T->Current->v_prev && T->Current->v_time > LTime) {
    T->Current = T->Current->v_prev;
  }
}

int GetXPos(GScope *S,GValue *V,int LTime)
{
  int T;


#if 0
  if (V->v_time < S->LeftTime)
    return ScopeLEFTMARGIN;

  if (V->v_time - S->LeftTime > S->Range)
    R = ScopeLEFTMARGIN + S->Range*S->Scale;
  else
    R = ScopeLEFTMARGIN + (V->v_time - S->LeftTime)*S->Scale;
#endif

#if 0
  if ((!V) || (V->v_time > S->LeftTime + S->Range)) {
    if (S->Time - S->LeftTime > S->Range)
      R = ScopeLEFTMARGIN + S->Range*S->Scale;
    else
      R = ScopeLEFTMARGIN + (S->Time - S->LeftTime)*S->Scale;
  } else if (V->v_time < LTime)
    R = ScopeLEFTMARGIN + (LTime - S->LeftTime)*S->Scale;
  else
    R = ScopeLEFTMARGIN + (V->v_time - S->LeftTime)*S->Scale;
#endif

  if ((!V) || (V->v_time > S->LeftTime + S->Range)) {
    if (S->Time - S->LeftTime > S->Range)
      T = S->LeftTime + S->Range;
    else
      T = S->Time;
  } else if (V->v_time < LTime)
    T = LTime;
  else
    T = V->v_time;

  if (T < S->LeftTime)  T = S->LeftTime;

  return ScopeLEFTMARGIN + (T - S->LeftTime)*S->Scale;
}

GC GValue_getColor(GValue *V,int nbits)
{
  switch (V->v_code) {
  case VC_UNRECORDED :
  case VC_UNKNOWN :
  case VC_CONTENTION :
  case VC_LOW :
  case VC_HIGH :
  default :
    return XGate.scopeUnknownGC;
  case VC_FLOAT :
    return XGate.scopeFloatGC;
  case VC_ONE :
    if (nbits == 1) return XGate.scopeOneGC;
    break;
  case VC_ZERO :  
    if (nbits == 1) return XGate.scopeZeroGC;
    break;
  }

  if (V->v_hexValue) {
    if (strchr(V->v_hexValue,'x'))
      return XGate.scopeUnknownGC;
    else if (*V->v_hexValue == '0')
      return XGate.scopeZeroGC;
  }
  return XGate.scopeOneGC;
}

void GTrace_drawTransValue(GTrace *T,GValue *V,GScope *S,int y,int LTime,int isFullUpdate)
{
  int x,nextX,width;
  char *new_dpy = 0;
  GC gc;

  if (!V) return;

  x = GetXPos(S,V,LTime);

  if (V->v_next)
    nextX = GetXPos(S,V->v_next,LTime);
  else
    nextX = -1;

  if (!V->v_hexValue)
    new_dpy = "";
  else {
    width = GKTextWidth(XGate.stextF,V->v_hexValue,strlen(V->v_hexValue));

    if (nextX < 0 || width <= nextX-x-3)
      new_dpy = V->v_hexValue;
    else if (S->hash_width <= nextX-x-3)
      new_dpy = "#";
    else
      new_dpy = "";
  }

  if (isFullUpdate && V->v_dpyHexValue) {
    ob_free(V->v_dpyHexValue);
    V->v_dpyHexValue = 0;
  }

  if (V->v_dpyHexValue && (strcmp(V->v_dpyHexValue,new_dpy) != 0)) {
    char *s = V->v_dpyHexValue;
    gc = XGate.scopeClearGC;

    XDrawString(XGate.D,XGate.ScopeW,gc,x+2,y-ScopeLOW-3,s,strlen(s));
    ob_free(V->v_dpyHexValue);
    V->v_dpyHexValue = 0;
  }

  if (*new_dpy) {
    gc = GValue_getColor(V,T->Bits);
    XDrawString(XGate.D,XGate.ScopeW,gc,x+2,y-ScopeLOW-3,new_dpy,strlen(new_dpy));
  }
  V->v_dpyHexValue = ob_strdup(new_dpy);
}

/*
  Returns a code indicating the proper vertical lines to be drawn
  on a logic transition.  The codes are:

     0		No vertical line
     1		Bottom half only
     2		Top half only
     3		Full vertical line
 */
unsigned transition_type(int from,int to)
{
  /*
    Rows are the 'from' values and columns are the 'to' values.
   */
  static unsigned char trans[VC_NUMCASES][VC_NUMCASES] = {
      /* 0 	1	x	z	H	L	C */
       {0,	3,	3,	1,	3,	1,	3},	/* 0 */
       {3,	0,	3,	2,	2,	3,	3},	/* 1 */
       {3,	3,	0,	3,	3,	3,	3},	/* x */
       {1,	2,	3,	0,	2,	1,	3},	/* z */
       {3,	2,	3,	2,	0,	3,	3},	/* H */
       {1,	1,	3,	1,	3,	0,	3},	/* L */
       {3,	3,	3,	3,	3,	3,	0},	/* C */
  };

  return trans[from][to];
}

void GTrace_updateTransition(GTrace *T,GValue *V,GScope *S,int y,int x1,int x2,int LTime,int isFullUpdate)
{
  if (IsTransition(S,V,T->Bits)) {
    GC gc;

    gc = GValue_getColor(V->v_next,T->Bits);

    if (T->Bits > 1) {

      GTrace_drawTransValue(T,V,S,y,LTime,isFullUpdate);
      GTrace_drawTransValue(T,V->v_next,S,y,LTime,isFullUpdate);
      XDrawLine(XGate.D,XGate.ScopeW,gc,
		x2,y-ScopeLOW,x2,y-ScopeHIGH+1);
    } else {
      unsigned tt;

      if (!V->v_next) return;

      tt = transition_type(V->v_code,V->v_next->v_code);
      switch (tt) {
      case 1 :	/* Low half */
	XDrawLine(XGate.D,XGate.ScopeW,gc,
		  x2,y-ScopeMEDIUM,
		  x2,y-ScopeLOW);
	break;
      case 2 :	/* High half */
	XDrawLine(XGate.D,XGate.ScopeW,gc,
		  x2,y-ScopeMEDIUM,
		  x2,y-ScopeHIGH);
	break;
      case 3 :	/* Full */
	XDrawLine(XGate.D,XGate.ScopeW,gc,
		  x2,y-ScopeLOW,
		  x2,y-ScopeHIGH);
	break;
      }
    }
  }
}

void GTrace_updateValue(GTrace *T,GValue *V,int y,int x1,int x2)
{
  GC gc = GValue_getColor(V,T->Bits);

  switch (V->v_code) {
  case VC_UNRECORDED :
    break;
  case VC_UNKNOWN :
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeLOW,
	      x2,y-ScopeLOW);
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeHIGH,
	      x2,y-ScopeHIGH);
    break;
  case VC_FLOAT :
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeMEDIUM,
	      x2,y-ScopeMEDIUM);
    break;
  case VC_LOW :
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeLOW,
	      x2,y-ScopeLOW);
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeMEDIUM,
	      x2,y-ScopeMEDIUM);
    break;
  case VC_HIGH :
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeHIGH,
	      x2,y-ScopeHIGH);
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeMEDIUM,
	      x2,y-ScopeMEDIUM);
    break;
  case VC_CONTENTION :
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeLOW,
	      x2,y-ScopeLOW);
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeHIGH,
	      x2,y-ScopeHIGH);
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeLOWMED,
	      x2,y-ScopeLOWMED);
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeHIGHMED,
	      x2,y-ScopeHIGHMED);
    XDrawLine(XGate.D,XGate.ScopeW,gc,
	      x1,y-ScopeMEDIUM,
	      x2,y-ScopeMEDIUM);
    break;
  case VC_ONE :
    if (T->Bits == 1) {
      XDrawLine(XGate.D,XGate.ScopeW,gc,
		x1,y-ScopeHIGH,
		x2,y-ScopeHIGH);
    } else {
      XDrawLine(XGate.D,XGate.ScopeW,gc,
		x1,y-ScopeLOW,
		x2,y-ScopeLOW);
      XDrawLine(XGate.D,XGate.ScopeW,gc,
		x1,y-ScopeHIGH,
		x2,y-ScopeHIGH);
    }
    break;
  case VC_ZERO :  
    if (T->Bits == 1) {
      XDrawLine(XGate.D,XGate.ScopeW,gc,
		x1,y-ScopeLOW,
		x2,y-ScopeLOW);
    } else {
      XDrawLine(XGate.D,XGate.ScopeW,gc,
		x1,y-ScopeLOW,
		x2,y-ScopeLOW);
      XDrawLine(XGate.D,XGate.ScopeW,gc,
		x1,y-ScopeHIGH,
		x2,y-ScopeHIGH);
    }
    break;
  }
}


void GTrace_update(GTrace *T,GScope *S,int y,int LTime,int isFullUpdate)
{
  /*    int lasttime,value,vcode,*/
  int x1,x2;
  struct value *V;

  GTrace_adjust(T,LTime);

  for (V = T->Current;V;V = V->v_next) {
    x1 = GetXPos(S,V,LTime);
    x2 = GetXPos(S,V->v_next,LTime);

    GTrace_updateValue(T,V,y,x1,x2);
    GTrace_updateTransition(T,V,S,y,x1,x2,LTime,isFullUpdate);
    if (V->v_next && V->v_next->v_time > S->LeftTime + S->Range) break;
  }
}

void GScope_updateTransition(GScope *S,int OldTime,int isFullUpdate)
{
  int i;
  int Y = ScopeTRACEHEIGHT;
  for (i = S->Start;i < S->NumTraces;i++) {
    int x1,x2;
    GTrace *T = S->Traces[i];

    if (T->Last->v_time == OldTime) {
      GTrace_adjust(T,OldTime);
      x1 = GetXPos(S,T->Last,OldTime);
      x2 = GetXPos(S,0,OldTime);
      GTrace_updateTransition(T,T->Last->v_prev,S,Y,x1,x2,-1,isFullUpdate);
    }

    Y += ScopeTRACEHEIGHT;
    if (Y > S->Height-ScopeBOTTOMHEIGHT) break;
  }
}

void GScope_update(GScope *S,int OldTime)
{
  int i;

  if ((OldTime >= S->LeftTime + S->Range) && (S->NumTraces > 0)) {
    return;
  }

  if (S->Time >= S->LeftTime + S->Range) {
    S->LeftTime = ((S->Time - S->Range/3)/S->Interval)*S->Interval;
    if (S->LeftTime < 0) S->LeftTime = 0;

    ReqScopeRedisplay();
  } else if (S->Start != -1) {
    int Y = ScopeTRACEHEIGHT;
    for (i = S->Start;i < S->NumTraces;i++) {
      GTrace_update(S->Traces[i],S,Y,OldTime,0);
      Y += ScopeTRACEHEIGHT;
      if (Y > S->Height-ScopeBOTTOMHEIGHT) break;
    }
  }
}

void GTrace_draw(GTrace *T,GScope *S,int y,int doName)
{
  int M;
  GC gc;

  if (!doName) {
    XFillRectangle(XGate.D,XGate.ScopeW,XGate.scopeClearGC,ScopeLEFTMARGIN,y-ScopeHIGH-1,
	       S->Width-ScopeLEFTMARGIN,ScopeHIGH-ScopeLOW+2);
  }


  if (T->Current && !T->Current->v_next)
    GTrace_drawTransValue(T,T->Current,S,y,S->LeftTime,1);

  if (doName) {

    XSetLineAttributes(XGate.D,XGate.scopeGridGC,2,LineSolid,CapButt,JoinBevel);
    XDrawLine(XGate.D,XGate.ScopeW,XGate.scopeGridGC,ScopeLEFTMARGIN,y,
	    S->Width-ScopeRIGHTMARGIN,y);

    for (M = S->LeftTime;M <= S->LeftTime + S->Range;M += S->Interval) {
      int P;

      P = ScopeLEFTMARGIN + (M-S->LeftTime)*S->Scale;
      XDrawLine(XGate.D,XGate.ScopeW,XGate.scopeGridGC,P,y-2,P,y+2);
    }
    XSetLineAttributes(XGate.D,XGate.scopeGridGC,1,LineSolid,CapButt,JoinBevel);

    if (!T->VisName) {
      int w;
      char *p;

      T->VisName = T->dVisName = ob_strdup(T->PrintName);
      while ((w = GKTextWidth(XGate.textF,T->VisName,strlen(T->VisName))) > ScopeLEFTMARGIN-10) {
	if ((p = strchr(T->VisName+1,'.')))
	  T->VisName = p;
	else
	  T->VisName++;
      }
    }


    XDrawLine(XGate.D,XGate.ScopeW,XGate.scopeGridGC,10,y,
	      ScopeLEFTMARGIN-10,y);


    if (T->Bits > 1)
      gc = XGate.busGC;
    else
      gc = XGate.wireGC;

    XSetFont(XGate.D,gc,XGate.textXF);
#if 0
    PosDrawString(XGate.ScopeW,NULL,gc,10,y-ScopeTEXTPOS,
		  T->VisName,AtLeft);
#endif
    XDrawString(XGate.D,XGate.ScopeW,gc,10,y-ScopeTEXTPOS,
		T->VisName,strlen(T->VisName));
    XSetFont(XGate.D,gc,XGate.stextXF);
  }

  GTrace_update(T,S,y,S->LeftTime,1);
}

void GScope_showCrossLine(GScope *S)
{
  if (Scope->show_xhair) return;
  Scope->show_xhair = 1;

  if (Scope->xhair_x >= ScopeLEFTMARGIN && Scope->xhair_x <= Scope->Width - ScopeRIGHTMARGIN) {
    if (Scope->enable_xhair)
      XDrawLine(XGate.D,XGate.ScopeW,XGate.scopeXHairGC,Scope->xhair_x,0,
		Scope->xhair_x,Scope->Height-ScopeBOTTOMHEIGHT);
  }
}

void GScope_hideCrossLine(GScope *S)
{
  if (!Scope->show_xhair) return;
  if (Scope->NumTraces <= 0) return;

  if (Scope->xhair_x >= ScopeLEFTMARGIN && Scope->xhair_x <= Scope->Width - ScopeRIGHTMARGIN) {
    if (Scope->enable_xhair)
      XDrawLine(XGate.D,XGate.ScopeW,XGate.scopeXHairGC,Scope->xhair_x,0,
		Scope->xhair_x,Scope->Height-ScopeBOTTOMHEIGHT);
  }

  Scope->show_xhair = 0;
}

void GScope_drawCrossLine(GScope *S,int x)
{
  if (Scope->NumTraces <= 0) return;
  if (!Scope->show_xhair) return;

  if (Scope->xhair_x >= ScopeLEFTMARGIN && Scope->xhair_x <= Scope->Width - ScopeRIGHTMARGIN) {
    if (Scope->enable_xhair)
    XDrawLine(XGate.D,XGate.ScopeW,XGate.scopeXHairGC,Scope->xhair_x,0,
	      Scope->xhair_x,Scope->Height-ScopeBOTTOMHEIGHT);
  }

  Scope->xhair_x = x;

  if (Scope->xhair_x >= ScopeLEFTMARGIN && Scope->xhair_x <= Scope->Width - ScopeRIGHTMARGIN) {
    if (Scope->enable_xhair)
      XDrawLine(XGate.D,XGate.ScopeW,XGate.scopeXHairGC,Scope->xhair_x,0,
		Scope->xhair_x,Scope->Height-ScopeBOTTOMHEIGHT);
  }
}


void GScope_drawSelection(GScope *S)
{
  int Y,i;
  int x1,x2;
  int pmin = ScopeLEFTMARGIN;
  int pmax = S->Width-ScopeRIGHTMARGIN;

  x1 = GScope_t2x(S,S->mark_val[0]);
  x2 = GScope_t2x(S,S->mark_val[1]);

  if (x2 < x1) {
    int t = x1; x1 = x2; x2 = t;
  }
#if 0
  printf("Scope_drawSelection[%d..%d]\n",x1,x2);
#endif

  if (x1 < pmin) x1 = pmin;
  if (x1 > pmax) x1 = pmax;
  if (x2 < pmin) x2 = pmin;
  if (x2 > pmax) x2 = pmax;
  if (x1 == x2) return;

  if (S->mark_count == 0) return;

  Y = ScopeTRACEHEIGHT;
  for (i = S->Start;i < S->NumTraces;i++) {
    XFillRectangle(XGate.D,XGate.ScopeW,XGate.scopeSelectGC,
		   x1,Y-ScopeHIGH-1,
		   x2-x1,ScopeHIGH-ScopeLOW+2);

    Y += ScopeTRACEHEIGHT;
    if (Y > S->Height-ScopeBOTTOMHEIGHT) break;
  }
}

void GScope_showSelection(GScope *S)
{
#if 0
  printf("GScope_showSelection() show_mark=%d mark_count=%d\n",S->show_mark,S->mark_count);
#endif

  if (S->show_mark) return;
  S->show_mark = 1;

  GScope_drawSelection(S);
}

void GScope_hideSelection(GScope *S)
{
#if 0
  printf("GScope_hideSelection() show_mark=%d\n",S->show_mark);
#endif

  if (!S->show_mark) return;
  S->show_mark = 0;

  GScope_drawSelection(S);
}

/*
 * Update selected region during a selection drag on the scope.
 */
void GScope_moveSelection(GScope *S)
{
  int Y,i;
  int x1,x2;
  int pmin = ScopeLEFTMARGIN;
  int pmax = S->Width-ScopeRIGHTMARGIN;

  x1 = GScope_t2x(S,S->mark_val[1]);
  if (S->mark_count == 1) {
    S->mark_val[1] = GScope_x2t(S,S->xhair_x);
  }
  x2 = GScope_t2x(S,S->mark_val[1]);

  if (x2 < x1) {
    int t = x1; x1 = x2; x2 = t;
  }

  if (x1 < pmin) x1 = pmin;
  if (x1 > pmax) x1 = pmax;
  if (x2 < pmin) x2 = pmin;
  if (x2 > pmax) x2 = pmax;
  if (x1 == x2) return;

  if (S->mark_count == 0) return;

  Y = ScopeTRACEHEIGHT;
  for (i = S->Start;i < S->NumTraces;i++) {
    XFillRectangle(XGate.D,XGate.ScopeW,XGate.scopeSelectGC,
		   x1,Y-ScopeHIGH-1,
		   x2-x1,ScopeHIGH-ScopeLOW+2);

    Y += ScopeTRACEHEIGHT;
    if (Y > S->Height-ScopeBOTTOMHEIGHT) break;
  }
}

void GScope_setMark(GScope *S,int x,int isDn,unsigned state)
{
  int val;
  int is_shift = ((state & ShiftMask) != 0);
  int pmin = ScopeLEFTMARGIN;
  int pmax = S->Width-ScopeRIGHTMARGIN;
  x = imin(x,pmax);
  x = imax(x,pmin);
  val = GScope_x2t(S,x);

  switch (S->mark_count) {
  case 2 :
    if (!isDn) return;			/* Ignore up clicks when no selection */
    if (is_shift) {
      int d0 = iabs(S->mark_val[0] - val);
      int d1 = iabs(S->mark_val[1] - val);
      if (d0 < d1)
	S->mark_val[0] = val;
      else
	S->mark_val[1] = val;
      break;
    } else
      S->mark_count = 0;
    /* pass through */
  case 0 :
    if (!isDn) return;			/* Ignore up clicks when no selection */

    S->mark_x[0] = x;
    S->mark_x[1] = x;
    S->mark_val[0] = val;
    S->mark_val[1] = val;
    S->mark_count++;
    break;
  case 1 :
    if (!isDn && is_shift) return;	/* Ignore up clicks with shift */

    S->mark_x[1] = x;
    S->mark_val[1] = val;
    S->mark_count++;

    if (S->mark_val[0] == S->mark_val[1])
      S->mark_count = 0;

    break;
  }
}

void GScope_setShowXHairState(int n)
{
  extern int scope_active;
  if (!scope_active) return;

  if (Scope->enable_xhair == n) return;

  GScope_hideCrossLine(Scope);
  Scope->enable_xhair = n;
  Scope->xhair_x = 0;
  GScope_showCrossLine(Scope);
}

void GScope_fullUpdate(GScope *S)
{
  extern int scope_active;
  Window R;
  int i,X,Y,W,H,BW,DP;
  double vs,ve,hs,he;


  if (!scope_active) return;

  if (XGate.idle_ev.scope_redraw) {
    XClearWindow(XGate.D,XGate.ScopeW);
    XGetGeometry(XGate.D,XGate.ScopeW,&R,&X,&Y,&W,&H,&BW,&DP);
    S->Width = W;
    S->Height = H;

    S->show_xhair = 0;
    S->show_mark = 0;
  } else {
    W = S->Width;
    H = S->Height;

    GScope_hideCrossLine(S);
    GScope_hideSelection(S);
  }


  if (S->NumTraces > 0) {
    DrawScopeScale(S,XGate.idle_ev.scope_redraw);

    Y = ScopeTRACEHEIGHT;
    for (i = S->Start;i < S->NumTraces;i++) {
      GTrace_draw(Scope->Traces[i],S,Y,XGate.idle_ev.scope_redraw);
      Y += ScopeTRACEHEIGHT;
      if (Y > H-ScopeBOTTOMHEIGHT) break;
    }

    vs = (double)S->Start/(double)S->NumTraces;
    ve = (double)(i+1)/(double)S->NumTraces;

    if (S->Time > 0) {
      hs = (double)S->LeftTime/(double)S->Time;
      he = (double)(S->LeftTime+S->Range)/(double)S->Time;
      if (he > 1.0) he = 1.0;
    } else {
      hs = 0.0;
      he = 1.0;
    }
  } else {
    char *msg = msgLookup("scope.emptymsg");

    vs = 0.0;
    ve = 1.0;
    hs = 0.0;
    he = 1.0;
    XDrawString(XGate.D,XGate.ScopeW,XGate.scopeGridGC,
		20,H/2,msg,strlen(msg));
  }

  GScope_showCrossLine(S);
  GScope_showSelection(S);

  DoTcl(".scope.main.vert set %f %f",vs,ve);
  DoTcl(".scope.main.horz set %f %f",hs,he);
}

void Scope_stepTo(int t)
{
  int OldTime = Scope->Time;
  Scope->Time = t;
  GScope_update(Scope,OldTime);
}

void Scope_setOneBitValue(GTrace *T,int Time,int c)
{
  switch (c) {
  case '1' :
    trace_observe(T,Time,VC_ONE,0);
    break;
  case '0' :
    trace_observe(T,Time,VC_ZERO,0);
    break;
  case 'x' :
  case 'X' :
    trace_observe(T,Time,VC_UNKNOWN,0);
    break;
  case 'z' :
  case 'Z' :
    trace_observe(T,Time,VC_FLOAT,0);
    break;
  case 'l' :
  case 'L' :
    trace_observe(T,Time,VC_LOW,0);
    break;
  case 'h' :
  case 'H' :
    trace_observe(T,Time,VC_HIGH,0);
    break;
  }
}

void Scope_setValue(const char *name,const char *val)
{
  int nbits;
  char c,*p,*q;
  GTrace *T = GScope_findTrace(Scope,name);

  if (!T) return;		/* Not an observed value */

  if (sscanf(val,"%d'%c",&nbits,&c) != 2) {
    trace_observe(T,Scope->Time,VC_FLOAT,0);
    return;
  }
  p = strchr(val,'\'') + 2;

  /*
   * Handle single bit values here.
   */
  if (nbits == 1) {
    Scope_setOneBitValue(T,Scope->Time,*p);
    GScope_updateTransition(Scope,Scope->Time,0);
    return;
  }

  /*
   * If multi-bit signal and all bits are floating, treat as
   * a single-bit floating signal.
   */
  for (q = p;*q;q++)
    if (*q != 'z') break;
  if (!*q) {
    Scope_setOneBitValue(T,Scope->Time,'z');
    GScope_updateTransition(Scope,Scope->Time,0);
    return;
  }

  trace_observe(T,Scope->Time,VC_ZERO,p);

  if (Scope->Time <= Scope->LeftTime + Scope->Range)
    GScope_updateTransition(Scope,Scope->Time,0);
}

/*
  Report observations on repName to traceName as well.
*/
void Scope_rename(const char *oldName,const char *newName)
{
  GTrace *T = GScope_findTrace(Scope,oldName);
  if (T) {
    ob_free(T->Name);
    T->Name = ob_strdup(newName);
  }
}

/*
  Observe the value of a trace at a particular time.  The value
  may be specified with a vcode (for 1-bit traces), or a value string
  (for n-bit traces).  If the value of the trace changes the change
  will be recorded.
 */
void trace_observe(GTrace *T,int time,int vcode,const char *value)
{
  if (T->Last->v_time == time) {
    char *old_str = T->Last->v_hexValue;
    const char *new_str = value;

    if (new_str)
      while (*new_str == '0' && new_str[1]) new_str++;

    if (new_str) {
      T->Last->v_hexValue = ob_strdup(new_str);
    } else
      T->Last->v_hexValue = 0;

    if (old_str) ob_free(old_str);

    T->Last->v_code = vcode;
  } else if (T->Last->v_time < time) {
    if (T->Last->v_code != vcode || 
	(value && !T->Last->v_hexValue) ||
	(value && T->Last->v_hexValue && strcmp(value,T->Last->v_hexValue) != 0)) {
      T->Last->v_next = new_Value(time,vcode,value,T->Last);
      T->Last = T->Last->v_next;
    }
  } else {
    logError(ERL_WARN,"Time travel not allowed.");
    return;
  }
}

GScope *new_GScope()
{
  GScope *S;

  S = (GScope *) ob_malloc(sizeof(GScope),"GScope");
  S->Width = S->Height = 0;
  S->Start = 0;
  S->NumTraces = 0;
  S->Time = S->LeftTime = 0;
  S->Range = RangeVals[(S->RangePos = DEFAULTRANGEPOS)];
  PickInterval(&S->Range,&S->Interval);
  S->Scale = 0.0;

  S->hash_width = GKTextWidth(XGate.stextF,"#",1);

  S->enable_xhair = 1;
  S->show_xhair = 1;
  S->show_mark = 0;
  S->xhair_x = 0;
  S->mark_count = 0;

  return S;
}

void delete_GScope(GScope *S)
{
  int i;

  for (i = 0;i < S->NumTraces;i++) {
    GTrace *T = S->Traces[i];
    delete_GTrace(T);
  }
  S->NumTraces = 0;
}

void GScope_deleteTrace(GScope *S,const char *name)
{
  int i;

  for (i = 0;i < S->NumTraces;i++) {
    GTrace *T = S->Traces[i];
    if (strcmp(T->PrintName,name) == 0) {
      delete_GTrace(T);
      break;
    }
  }

  S->NumTraces--;
  for (;i < S->NumTraces;i++)
    S->Traces[i] = S->Traces[i+1];

  if (S->NumTraces == 0)
    S->Start = 0; 
  else if (S->Start >= S->NumTraces)
    S->Start = S->NumTraces-1;

  ReqScopeRedisplay();
}

void GScope_addTrace(GScope *S,const char *Name,const char *PrintName,int Bits)
{
  GTrace *T;
  int i,j;
  int doSort = 1;
  const char *str_doSort = Tcl_GetVar(XGate.tcl,"tkg_simSortTraces",TCL_GLOBAL_ONLY);

  if (str_doSort) sscanf(str_doSort,"%d",&doSort);

  if (!(T = GScope_findTrace(S,Name))) {
    T = new_GTrace(Name,PrintName,Bits,S->Time);
  } else {
    if (T->PrintName) ob_free(T->PrintName);
    T->PrintName = (char*) ob_strdup(PrintName);
    return;
  }

  if (doSort) {
    for (i = 0;i < S->NumTraces;i++) {
      GTrace *sT = S->Traces[i];
      if (strcasecmp(T->PrintName,sT->PrintName) <= 0)
	break;
    }
  } else
    i = S->NumTraces;

  for (j = S->NumTraces;j > i;j--)
    S->Traces[j] = S->Traces[j-1];
  S->NumTraces++;
  S->Traces[i] = T;

  ReqScopeRedisplay();
}

GTrace *new_GTrace(const char *Name,const char *PrintName,int Bits,int CurTime)
{
  GTrace *T;

  T = (GTrace*) ob_malloc(sizeof(GTrace),"GTrace");
  T->Name = (char*) ob_strdup(Name);
  T->PrintName = (char*) ob_strdup(PrintName);
  T->VisName = 0;
  T->dVisName = 0;
  T->Bits = Bits;

  T->First = T->Last = T->Current = new_Value(0,VC_UNRECORDED,0,NULL);
  trace_observe(T,CurTime,VC_UNRECORDED,0);

  return T;
}

void delete_GTrace(GTrace *T)
{
  GValue *V;

  ob_free(T->Name);
  ob_free(T->PrintName);
  if (T->dVisName) ob_free(T->dVisName);

  for (V = T->First;V;V = V->v_next) {
    if (V->v_dpyHexValue) ob_free(V->v_dpyHexValue);
    if (V->v_hexValue) ob_free(V->v_hexValue);
  }

  ob_free(T);
}

GTrace *GScope_findTrace(GScope *S,const char *Name)
{
  int i;

  for (i = 0;i < S->NumTraces;i++) {
    GTrace *T = S->Traces[i];
    if (!strcmp(T->Name,Name))
      return T;
  }

  return NULL;
}


GValue *new_Value(int CurTime,int Code,const char *value,GValue *Prev)
{
  GValue *V;

  V = (GValue *) ob_malloc(sizeof(GValue),"GValue");
  V->v_time = CurTime;
  V->v_code = Code;
  if (value) {
    while (*value == '0' && value[1]) value++;
    V->v_hexValue = ob_strdup(value);
  } else
    V->v_hexValue = 0;
  V->v_dpyHexValue = 0;
  V->v_prev = Prev;
  V->v_next = NULL;

  return V;
}

void DrawScopeScale(GScope *S,int doTicks)
{
  double W,UnitSize;
  int T,LineY;
  char Buf[STRMAX];
  int base = 0;

  W = S->Width - ScopeLEFTMARGIN - ScopeRIGHTMARGIN;
  S->Scale = UnitSize = W / (double) S->Range;

  LineY = S->Height - 10;
  PickInterval(&S->Range,&S->Interval);

  if (!doTicks) {
    XFillRectangle(XGate.D,XGate.ScopeW,XGate.scopeClearGC,0,Scope->Height-ScopeBOTTOMHEIGHT,ScopeLEFTMARGIN-10,ScopeBOTTOMHEIGHT);
  }

  if (S->LeftTime >= 10000) {
    base = S->LeftTime;
    sprintf(Buf,"%d",base);
    XDrawString(XGate.D,XGate.ScopeW,XGate.scopeGridGC,
		 10,LineY,Buf,strlen(Buf));
  }

  if (doTicks) {
    for (T = S->LeftTime;T < S->LeftTime + S->Range + S->Interval-1;T += S->Interval) {
      int W;

      if (base)
	sprintf(Buf,"+%d",T-base);
      else
	sprintf(Buf,"%d",T-base);

      W = GKTextWidth(XGate.textF,Buf,strlen(Buf))/2;
      XDrawString(XGate.D,XGate.ScopeW,XGate.scopeGridGC,
		  ScopeLEFTMARGIN+(int)((T-S->LeftTime)*UnitSize-W),
		  LineY,Buf,strlen(Buf));
    }
  }
}

/*
ScopeExposePred(D,E,arg)
Display *D;
XEvent *E;
char *arg;
{
    return E->type == Expose && E->xany.window == XGate.ScopeW;
}
*/

