/*
   Copyright (C) 2004 T. Scott Dattalo

This file is part of gpsim.

gpsim 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, or (at your option)
any later version.

gpsim 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 gpsim; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */


/*
           S C O P E 

A simple waveform viewer for gpsim. Inspired from Nuno Sucena Almeida's
gpsim_la - plug in.

*/

/*

TODO:

 -- Scopewindow is emitting an expose event each time a waveform is drawn.

*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>

#include "../config.h"
#ifdef HAVE_GUI

#include <unistd.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <glib.h>

#include "../src/bitlog.h"
#include "../src/symbol.h"

//#define DEBUG

#include "gui_scope.h"




//static GtkObject *bit_adjust; // ,*delay_adjust;
static GdkColor signal_line_color,grid_line_color,grid_v_line_color;
static GdkColor highDensity_line_color;
//static int bit_left,bit_right,bit_points,update_delay;


class WaveformSink;
class Waveform;
/***********************************************************************
  timeMap - a structure that's used to map horizontal screen coordinates 
  into simulation time.
*/
struct timeMap 
{
  // simulation time
  double time;

  // pixel x-coordinate
  int     pos;

  // index into array holding event (fixme - exposes Logger implementation)
  unsigned int eventIndex;

  // The event
  int event;
};

/***********************************************************************
  WaveSource

  This is an attribute that provides the interface between the WaveForm 
  class (below) and the names of nodes or stimuli that source information.

*/
class WaveformSource : public String
{
public:
  WaveformSource(Waveform *pParent, const char *_name);

  // Override the String::set method so that name assignments can
  // be intercepted.
  virtual void set(const char *cp, int len=0);

private:
  Waveform *m_pParent;
  bool m_bHaveSource;
};

/***********************************************************************
 zoom
 */
class ZoomAttribute : public Integer
{
public:
  ZoomAttribute(Scope_Window *);
  virtual void set(gint64 i);

private:
  Scope_Window *m_pSW;
};

/***********************************************************************
 pan
 */
class PanAttribute : public Integer
{
public:
  PanAttribute(Scope_Window *);
  virtual void set(gint64 i);

private:
  Scope_Window *m_pSW;
};

/***********************************************************************
 */
class PixMap
{
public:
  PixMap(GdkDrawable *pParent, gint w, gint h, gint y);
  GdkPixmap *pixmap() { return m_pixmap; }
  gint width;
  gint height;
  gint yoffset;  // 
protected:
  GdkPixmap *m_pixmap;
};

PixMap::PixMap(GdkDrawable *pParent, gint w, gint h, gint y)
  : width(w), height(h), yoffset(y)
{
  m_pixmap = gdk_pixmap_new(pParent, width, height, -1);

}

GtkWidget *waveDrawingArea=0;
GtkWidget *signalDrawingArea=0;
// use this if signalDrawingArea is just a drawing area
//#define SignalWindow signalDrawingArea->window
// use this if signalDrawingArea is a layout widget
#define SignalWindow GTK_LAYOUT (signalDrawingArea)->bin_window

GdkGC *drawing_gc=0;         // Line styles, etc.
GdkGC *highDensity_gc=0;     // Line styles, etc.
GdkGC *text_gc=0;            // signal names
GdkGC *grid_gc=0;            // Grid color
GdkGC *leftMarker_gc=0;      // Marker style & color

/***********************************************************************
  SignalNameEntry - a class to control gui editing of signal names
 */
class SignalNameEntry
{
public:
  SignalNameEntry(Scope_Window *parent,GtkEntry *);


  bool Select(WaveBase *);
  bool unSelect();
  bool isSelected(WaveBase *);
  WaveBase *getSelected();
  GtkEntry *m_entry;
protected:
  Scope_Window *m_parent;
  WaveBase *m_selectedWave;

};

/***********************************************************************
  Wavebase class

*/
class WaveBase
{
public:
  WaveBase(Scope_Window *parent, const char *name);

  virtual void Update(guint64 start=0, guint64 stop=0)=0;
  virtual void Build(PixMap *pWavePixmap, PixMap *pSignalPixmap);

  void setPixmaps(GdkPixmap *, GdkPixmap *);
  PixMap *wavePixmap() { return m_wavePixmap; }
  PixMap *signalPixmap() { return m_signalPixmap;}
  const char *get_text();
  virtual void setSource(const char *) { }
  
protected:

  Scope_Window *sw;          // Parent
  bool isBuilt;              // True after the gui has been built.
  guint64 m_start;           // Start time of plotted waveform
  guint64 m_stop;            // Stop time of plotted waveform
  PixMap *m_wavePixmap;      // The Waveform is rendered in this pixmap.
  PixMap *m_signalPixmap;    // The signal name is rendered in this pixmap.
  PangoLayout *m_layout;     // Pango layout for rendering signal name



};
/***********************************************************************
  TimeAxis - A special 'Waveform' for plotting the time 
 */
class TimeAxis : public WaveBase
{
public:
  TimeAxis(Scope_Window *parent, const char *name);
  virtual void Build(PixMap *pWavePixmap, PixMap *pSignalPixmap);
  virtual void Update(guint64 start=0, guint64 stop=0);

protected:
  PangoLayout *m_TicText;
};

TimeAxis *m_TimeAxis;

/***********************************************************************
  Waveform class

  This holds the gui information related with a gpsim waveform
*/
class Waveform : public WaveBase
{
public:


  Waveform(Scope_Window *parent, const char *name);

  virtual void Update(guint64 start=0, guint64 stop=0);
  virtual void Build(PixMap *pWavePixmap, PixMap *pSignalPixmap);

  void Resize(int width, int height);
  void SearchAndPlot(timeMap &left, timeMap &right);
  void Dump(); // debug
  void setData(char c);
  virtual void setSource(const char *);
  void updateLayout();
protected:
  void PlotTo(timeMap &left, timeMap &right);

  PinMonitor *m_ppm;
  WaveformSink *m_pSink;
  ThreeStateEventLogger *m_logger;
  timeMap m_last;
  WaveformSource *m_pSourceName;
};


//------------------------------------------------------------------------
//
// Scope_Window data items that need to go into the Scope_Window class.
// The reason they're here now is simply for development convenience.
//
//
const int nSignals=8;
Waveform *signals[nSignals];   // hack
int aw=0;
int ah=0;





/***********************************************************************
  WaveformSink - A "sink" is an object that receives data from a
  "source". In the context of waveform viewer, a source is a stimulus
  and the viewer is the sink.
*/
class WaveformSink : public SignalSink
{
public:
  WaveformSink(Waveform *pParent);
  virtual void setSinkState(char);
protected:
  Waveform *m_pWaveform;
};


//========================================================================
class ZoomInEvent : public KeyEvent
{
public:
  void press(gpointer data)
  {
    Scope_Window *sw = (Scope_Window *)(data);
    if (sw)
      sw->zoom(2);
  }
  void release(gpointer data) {}
};
//========================================================================
class ZoomOutEvent : public KeyEvent
{
public:
  void press(gpointer data)
  {
    Scope_Window *sw = (Scope_Window *)(data);
    if (sw)
      sw->zoom(-2);
  }
  void release(gpointer data) {}
};

//========================================================================
class PanLeftEvent : public KeyEvent
{
public:
  void press(gpointer data)
  {
    Scope_Window *sw = (Scope_Window *)(data);
    if (sw)
      sw->pan(-( (gint64) sw->getSpan()/4));
  }
  void release(gpointer data) {}
};
//========================================================================
class PanRightEvent : public KeyEvent
{
public:
  void press(gpointer data)
  {
    Scope_Window *sw = (Scope_Window *)(data);
    if (sw)
      sw->pan( (gint64) sw->getSpan()/4);
  }
  void release(gpointer data) {}
};

//========================================================================

static map<guint, KeyEvent *> KeyMap;

static gint
key_press(GtkWidget *widget,
	  GdkEventKey *key, 
	  gpointer data)
{
  Dprintf (("press 0x%x\n",key->keyval));

  KeyEvent *pKE = KeyMap[key->keyval];
  if(pKE) 
    {
      pKE->press(data);
      return TRUE;
    }

  return FALSE;
}

static gint
key_release(GtkWidget *widget,
	  GdkEventKey *key, 
	  gpointer data)
{
  return TRUE;
}

static gint
scroll_event(GtkWidget *widget,
	     GdkEventScroll *scroll, 
	     gpointer data)
{
  //printf ("scroll: x:%g y:%g dir:%d keymod:0x%x\n",scroll->x,scroll->y,scroll->direction,scroll->state);

  return TRUE;
}

//========================================================================
// WaveformSource

WaveformSource::WaveformSource(Waveform *pParent, const char *_name)
  : String(_name, "", "view or set gui scope waveforms"),
    m_pParent(pParent), m_bHaveSource(false)
{
  assert(m_pParent);
  // Prevent removal from the symbol table (all clearable symbols are
  // removed from the symbol table when a new processor is loaded).
  m_bClearableSymbol = false;
}

void WaveformSource::set(const char *cp, int len)
{
  if (!m_bHaveSource) {
    String::set(cp,len);
    m_pParent->setSource(cp);
    m_bHaveSource = true;
  }

}

//========================================================================

ZoomAttribute::ZoomAttribute(Scope_Window *pSW)
  : Integer("scope.zoom",0,"Scope Zoom; positive values zoom in, negative values zoom out"), 
    m_pSW(pSW)
{
  assert(m_pSW);
  m_bClearableSymbol = false;

}
void ZoomAttribute::set(gint64 i)
{
  Integer::set(i);
  m_pSW->zoom(i);
}

//========================================================================

PanAttribute::PanAttribute(Scope_Window *pSW)
  : Integer("scope.pan",0,"Scope Pan; positive values pan right, negative values pan left"), 
    m_pSW(pSW)
{
  assert(m_pSW);
  m_bClearableSymbol = false;

}
void PanAttribute::set(gint64 i)
{
  Integer::set(i);
  m_pSW->pan(i);
}

//========================================================================
WaveformSink::WaveformSink(Waveform *pParent)
  : SignalSink(), m_pWaveform(pParent)
{
  assert(m_pWaveform);
}

void WaveformSink::setSinkState(char c)
{
  m_pWaveform->setData(c);
}


//************************************************************************
WaveBase::WaveBase(Scope_Window *parent, const char *name)
  : sw(parent),
    isBuilt(false),
    m_start(1), m_stop(1),
    m_wavePixmap(0),
    m_signalPixmap(0),
    m_layout(0)
{

}
void WaveBase::Build(PixMap *pWavePixmap, PixMap *pSignalPixmap)
{

  if (m_wavePixmap && m_wavePixmap->pixmap())
    gdk_pixmap_unref(m_wavePixmap->pixmap());
  
  m_wavePixmap = pWavePixmap;

  if (m_signalPixmap && m_signalPixmap->pixmap())
    gdk_pixmap_unref(m_signalPixmap->pixmap());
  
  m_signalPixmap = pSignalPixmap;

  m_layout = gtk_widget_create_pango_layout (GTK_WIDGET (signalDrawingArea), "");

  isBuilt = true;
  Update(0,0);
}

const char *WaveBase::get_text()
{
  return m_layout ? pango_layout_get_text(m_layout) : 0;
}

//************************************************************************
Waveform::Waveform(Scope_Window *parent, const char *name)
  : WaveBase(parent,name), m_ppm(0)
{

  m_pSink = new WaveformSink(this);
  m_logger = new ThreeStateEventLogger();
  m_pSourceName = new WaveformSource(this,name);
  get_symbol_table().add(m_pSourceName);

  m_logger->event('0');
}

void Waveform::Build(PixMap *pWavePixmap, PixMap *pSignalPixmap)
{
  WaveBase::Build(pWavePixmap, pSignalPixmap);
  updateLayout();
}
void Waveform::setData(char c)
{
  m_logger->event(c);
}

void Waveform::updateLayout()
{
  if (m_layout) {
    char buffer[100];
    m_pSourceName->get(buffer, sizeof(buffer));
    pango_layout_set_text(m_layout,buffer,-1);
  }
}

void Waveform::setSource(const char *sourceName)
{
  IOPIN *ppin = dynamic_cast<IOPIN*>(get_symbol_table().findStimulus(sourceName));
  if (ppin) {
    if (m_ppm)
      m_ppm->removeSink(m_pSink);

    m_ppm = ppin->getMonitor();
    if (m_ppm)
      m_ppm->addSink(m_pSink);

    updateLayout();

    // Invalidate wave area.
    m_start = m_stop = 1;
    Update(0,0);
    if (sw)
      sw->Expose(this);

  } else if(sourceName)
     printf("'%s' is not a valid source for the scope\n",sourceName);
}
//----------------------------------------
void Waveform::Resize(int w, int h)
{

  Update();

}
static bool plotDebug=false;
//----------------------------------------
//
void Waveform::PlotTo(timeMap &left, timeMap &right)
{
  // Event(s) has(have) been found.
  // The plotting region has been subdivided as finely as possible
  // and there are one or more events. 
  // First draw a horizontal line from the last known event to here:


  gdk_draw_line(m_wavePixmap->pixmap(),drawing_gc,
		m_last.pos, m_last.event,   // last point drawn
		right.pos,  m_last.event);  // right most point of this region.

  // Now draw a vertical line for the event

  int nextEvent = (m_logger->get_state(right.eventIndex) == '1') ? 1 : (m_wavePixmap->height-3);

  // Draw a thicker line if there is more than one event.
  unsigned int nEvents = m_logger->get_nEvents(left.eventIndex,right.eventIndex);
  if (nEvents>1) {

    guint16 c = (nEvents < 4) ? (0x4000*nEvents+0x8000) : 0xffff;

    if (c != highDensity_line_color.blue) {
      gdk_colormap_free_colors(gdk_colormap_get_system(),
			       &highDensity_line_color,
			       1);
      // variations of yellow
      highDensity_line_color.green = 0xffff;
      highDensity_line_color.red = 0xffff;
      highDensity_line_color.blue = c;

      gdk_colormap_alloc_color(gdk_colormap_get_system(), &highDensity_line_color, TRUE, TRUE);

      gdk_gc_set_foreground(highDensity_gc,&highDensity_line_color);

    }

    gdk_draw_line(m_wavePixmap->pixmap(),highDensity_gc,
		  right.pos, 1,
		  right.pos, m_wavePixmap->height-3);
    if (left.pos != right.pos)
      gdk_draw_line(m_wavePixmap->pixmap(),highDensity_gc,
		    left.pos, 1,
		    left.pos, m_wavePixmap->height-3);
  } else 
    gdk_draw_line(m_wavePixmap->pixmap(),drawing_gc,
		  right.pos, m_last.event,    // last point drawn
		  right.pos, nextEvent); // next event

  if (plotDebug)
    printf("pos=%d time=%g\n",right.pos,right.time);

  m_last = right;
  m_last.event = nextEvent;

}
//----------------------------------------
//
// Waveform SearchAndPlot
//
//  Recursively divide the plotting area into smaller and smaller
// regions until either only one event exists or the region can 
// be divided no smaller.
//

void Waveform::SearchAndPlot(timeMap &left, timeMap &right)
{
  if (right.eventIndex == left.eventIndex)
    // The time span cannot be divided any smaller.
    // If there are no events in this subdivided region 
    // So just return.
    // m_last = left;
    ; 
  else if (left.pos+1 >= right.pos)
    PlotTo(left,right);
  else {
    // the subdivided region is larger than 1-pixel wide
    // and there is at least one event. So subdivide even smaller
    // and recursively call 

    timeMap mid;

    mid.time = (left.time + right.time) / 2;
    mid.pos  = (left.pos  + right.pos)  / 2;
    mid.eventIndex = m_logger->get_index ((guint64)mid.time);

    if (plotDebug)
      cout << " Mid pos="<<mid.pos
	   << " Mid.time=" << mid.time
	   << " left.time=" << left.time
	   << " right.time=" << right.time
	   << " evt idx=" << mid.eventIndex
	   << " evt time=" << m_logger->get_time(mid.eventIndex)
	   << endl;

    SearchAndPlot(left, mid);
    SearchAndPlot(mid, right);

  }

}

//----------------------------------------
//
// Waveform Update
//

void Waveform::Dump()
{
  m_logger->dump(0);
}
//----------------------------------------
//
// Waveform Update
//

void Waveform::Update(guint64 uiStart, guint64 uiEnd)
{
  if(!isBuilt)
    return;

  if(!m_wavePixmap) {
    cout << __FUNCTION__ << " pixmap is NULL\n";
    return;
  }

  if (uiEnd == 0) 
    uiEnd = get_cycles().value;

  if (m_start == uiStart && m_stop == uiEnd)
    return;

  m_start = uiStart;
  m_stop  = uiEnd;

  gdk_draw_rectangle (m_wavePixmap->pixmap(),
		      waveDrawingArea->style->black_gc,
		      TRUE,
		      0, 0,
		      m_wavePixmap->width,
		      m_wavePixmap->height);


  gdk_draw_rectangle (m_signalPixmap->pixmap(),
		      signalDrawingArea->style->bg_gc[GTK_STATE_NORMAL],
		      //signalDrawingArea->style->black_gc,
		      TRUE,
		      0, 0,
		      m_signalPixmap->width,
		      m_signalPixmap->height);
  if (m_layout) {
    updateLayout();
    int text_height=0;
    pango_layout_get_pixel_size(m_layout,
				NULL,
				&text_height);
    Dprintf(("signal name update:%s\n",pango_layout_get_text(m_layout)));
    gdk_draw_layout (GDK_DRAWABLE(m_signalPixmap->pixmap()),
		     signalDrawingArea->style->fg_gc[GTK_STATE_NORMAL],
		     0,
		     (m_signalPixmap->height-text_height)/2,
		     m_layout);
  }

  //
  // Draw Vertical Grid Lines:
  //

  int i;
  for (i=0; i<sw->MajorTicks().sze(); i++) {

    int x = sw->MajorTicks().pixel(i);

    gdk_draw_line(m_wavePixmap->pixmap(),grid_gc,
		  x,1,
		  x,m_wavePixmap->height-1);
  }

  //
  // Draw Horizontal Grid Lines:
  //
  gdk_draw_line(m_wavePixmap->pixmap(),grid_gc,
		0,m_wavePixmap->height-1,
		m_wavePixmap->width,m_wavePixmap->height-1);

  if (m_stop == 0)
    return; 

  // Draw Signals:

  timeMap left;
  timeMap right;

  left.pos = 0;
  left.time = m_start;
  left.eventIndex = m_logger->get_index(m_start);
  left.event = (m_logger->get_state(left.eventIndex) == '1') ? 1 : (m_wavePixmap->height-3);

  m_last = left;

  right.pos = m_wavePixmap->width;
  right.time = m_stop;
  right.eventIndex = m_logger->get_index(m_stop);


  SearchAndPlot(left,right);
  if (right.pos > m_last.pos)
    gdk_draw_line(m_wavePixmap->pixmap(),drawing_gc,
		  m_last.pos, m_last.event,   // last point drawn
		  right.pos,  m_last.event);  // right most point


}


//========================================================================
TimeAxis::TimeAxis(Scope_Window *parent, const char *name)
  : WaveBase(parent,name),
    m_TicText(0)
{

}
//----------------------------------------
//
// TimeAxis Update
//

void TimeAxis::Update(guint64 uiStart, guint64 uiEnd)
{
  if(!isBuilt)
    return;

  if(!m_wavePixmap) {
    cout << __FUNCTION__ << " pixmap is NULL\n";
    return;
  }

  if (uiEnd == 0) 
    uiEnd = get_cycles().value;

  if (m_start == uiStart && m_stop == uiEnd)
    return;

  m_start = uiStart;
  m_stop  = uiEnd;

  gdk_draw_rectangle (m_wavePixmap->pixmap(),
		      waveDrawingArea->style->bg_gc[GTK_STATE_NORMAL],
		      TRUE,
		      0, 0,
		      m_wavePixmap->width,
		      m_wavePixmap->height);

  gdk_draw_rectangle (m_signalPixmap->pixmap(),
		      signalDrawingArea->style->bg_gc[GTK_STATE_NORMAL],
		      TRUE,
		      0, 0,
		      m_signalPixmap->width,
		      m_signalPixmap->height);

  //
  // Draw Major Ticks:
  //
  int i;
  for (i=0; i<sw->MajorTicks().sze(); i++) {

    int x = sw->MajorTicks().pixel(i);

    gdk_draw_line(m_wavePixmap->pixmap(),grid_gc,
		  x,m_wavePixmap->height-3,
		  x,m_wavePixmap->height-1);

    if (m_TicText) {
      char buff[100];
      snprintf(buff, sizeof(buff),"%" PRINTF_INT64_MODIFIER "d", sw->MajorTicks().cycle(i));
      pango_layout_set_text(m_TicText, buff, -1);

      int text_height=0;
      int text_width=0;

      pango_layout_get_pixel_size(m_TicText,
				  &text_width,
				  &text_height);
      text_width /= 2;
      x = ((x-text_width) < 0) ? 0 : (x-text_width);
      x = ((x+text_width) > m_wavePixmap->width) ? (x-text_width) : x;
      
      gdk_draw_layout (GDK_DRAWABLE(m_wavePixmap->pixmap()),
		       waveDrawingArea->style->fg_gc[GTK_STATE_NORMAL],
		       x,
		       (m_wavePixmap->height-text_height)/2,
		       m_TicText);

      }

  }

  //
  // Draw Minor Ticks:
  //
  for (i=0; i<sw->MinorTicks().sze(); i++) {

    int x = sw->MinorTicks().pixel(i);

    gdk_draw_line(m_wavePixmap->pixmap(),grid_gc,
		  x,m_wavePixmap->height-3,
		  x,m_wavePixmap->height-1);
  }
  //
  // Draw Horizontal Grid Lines:
  //
  gdk_draw_line(m_wavePixmap->pixmap(),grid_gc,
		0,m_wavePixmap->height-1,
		m_wavePixmap->width,m_wavePixmap->height-1);

}



void TimeAxis::Build(PixMap *pWavePixmap, PixMap *pSignalPixmap)
{
  WaveBase::Build(pWavePixmap, pSignalPixmap);
  // Invalidate the start time after the build.
  // This is for the case where the scope window gets opened after
  // the simulation has already been started.
  m_start=m_stop=0;
  /*
  if (m_layout)
    pango_layout_set_text(m_layout,"Time", -1);
  */
  m_TicText = gtk_widget_create_pango_layout (GTK_WIDGET (waveDrawingArea), "");

}


//------------------------------------------------------------------------
// Signals


//----------------------------------------
//
// When a user clicks on the "X" in the window managers border of the
// scope window, we'll capture that event and hide the scope.
//
static int delete_event(GtkWidget *widget,
			GdkEvent  *event,
                        Scope_Window *sw)
{

  sw->ChangeView(VIEW_HIDE);
  return TRUE;
}

static gint Scope_Window_expose_event (GtkWidget *widget,
				       GdkEventExpose  *event,
				      Scope_Window   *sw)
{
  Dprintf(( " %s\n",__FUNCTION__));

  g_return_val_if_fail (widget != NULL, TRUE);
  //  g_return_val_if_fail (GTK_IS_DRAWING_AREA (widget), TRUE);

  
  if(sw) {
    sw->refreshSignalNameGraphics();

  }

  return FALSE;
}
static gint DrawingArea_expose_event (GtkWidget *widget,
				      GdkEventExpose *event,
				      Scope_Window   *sw)
{
  Dprintf(( " %s\n",__FUNCTION__));

  if(sw)
    sw->Update();

  return FALSE;
}

//------------------------------------------------------------------------
class TimeMarker : public Integer
{
public:
  TimeMarker(Scope_Window *parent, const char *_name, const char *desc);
  virtual void set(gint64 i);
private:
  Scope_Window *m_pParent;
};

TimeMarker::TimeMarker(Scope_Window *parent, const char *_name, const char *desc)
  : Integer(_name,0,desc),
    m_pParent(parent)
{
  assert(m_pParent);
  m_bClearableSymbol = false;
}
void TimeMarker::set(gint64 i)
{
  Integer::set(i);
  m_pParent->Update();
}

//========================================================================
GridPointMapping::GridPointMapping(int nPointsToMap)
  : m_pointsAllocated(nPointsToMap), m_nPoints(0)
{
  m_pixel = new int[m_pointsAllocated];
  m_cycle = new guint64[m_pointsAllocated];

}

//========================================================================
// SignalNameEntry
SignalNameEntry::SignalNameEntry(Scope_Window *parent,GtkEntry *entry)
  : m_entry(entry), m_parent(parent), m_selectedWave(0)
{
  gtk_widget_hide (GTK_WIDGET(m_entry));
}
bool SignalNameEntry::Select(WaveBase *pWave)
{

  if (pWave) {
    gtk_entry_set_text (GTK_ENTRY(m_entry), 
			pWave->get_text());
    gtk_widget_show (GTK_WIDGET(m_entry));
    gtk_widget_grab_focus (GTK_WIDGET(m_entry));

    m_selectedWave = pWave;
    return true;
  } 

  return unSelect();
}

bool SignalNameEntry::unSelect()
{
  gtk_widget_hide (GTK_WIDGET(m_entry));
  m_selectedWave = 0;
  return false;
}
bool SignalNameEntry::isSelected(WaveBase *pWave)
{
  return pWave == m_selectedWave;
}

WaveBase *SignalNameEntry::getSelected()
{
  return m_selectedWave;
}

//========================================================================
void Scope_Window::refreshSignalNameGraphics()
{
  GTKWAIT;

  Expose(m_TimeAxis);

  for (int i=0; i<nSignals; i++)
    Expose(signals[i]);
}

//========================================================================
void Scope_Window::refreshWaveFormGraphics()
{
}

//========================================================================
void Scope_Window::gridPoints(guint64 *uiStart, guint64 *uiEnd)
{
  guint64 start = m_Markers[eStart]->getVal();
  guint64 stop  = m_Markers[eStop]->getVal();
  if (!stop)
    stop = get_cycles().value;

  if (uiStart)
    *uiStart = start;
  if (uiEnd)
    *uiEnd = stop;

  //
  // Draw Vertical Grid Lines:
  //

  double t1 = (double) start;
  double t2 = (double) stop;
  double dt = t2-t1;

  int iMajor=0;
  int iMinor=0;
  m_MajorTicks.m_nPoints = 0;
  m_MinorTicks.m_nPoints = 0;

  if (dt > 1.0) {

    double exp1 = floor(log10(dt));
    double dt_tic = pow(10.0,exp1);

    double nTics = floor(dt/dt_tic);
    if (nTics < 5.0  && exp1>0.0)
      dt_tic /= 2.0;

    const int nMinorTics=5;
    const double dt_minor= dt_tic/nMinorTics;

    double ta = ceil(t1/dt_tic);
    double tb = floor(t2/dt_tic);

    for (double t=ta; t<=tb; t+=1.0) {
      double ttic=t*dt_tic;
      guint64 uiTime = (guint64)(floor(ttic));

      m_MajorTicks.m_pixel[iMajor] = mapTimeToPixel(uiTime);
      m_MajorTicks.m_cycle[iMajor] = uiTime;

      /*
      printf ("t=%g dt_tic=%g tmajor=%g %d %lld\n",
	      t,dt_tic,ttic,m_MajorTicks.m_pixel[iMajor],uiTime);
      */
      iMajor++;

      ttic+=dt_minor;

      for (int it=1; it<nMinorTics; it++,ttic+=dt_minor) {
	uiTime = (guint64)(ttic);
	m_MinorTicks.m_pixel[iMinor] = mapTimeToPixel(uiTime);
	m_MinorTicks.m_cycle[iMinor] = uiTime;
	/*
	printf ("     tminor=%g %d %lld\n",
		ttic,m_MinorTicks.m_pixel[iMinor],uiTime);
	*/
	iMinor++;

      }
    }
  }

  m_MajorTicks.m_nPoints = iMajor;
  m_MinorTicks.m_nPoints = iMinor;

}

static gdouble gNormalizedHorizontalPosition=0.0;
static GtkWidget *pvbox=0;
GtkObject *m_hAdj=0;


int Scope_Window::waveXoffset()
{
  return (int)((m_PixmapWidth - 
		(m_pHpaned->allocation.width - gtk_paned_get_position(GTK_PANED(m_pHpaned))))
	       *gNormalizedHorizontalPosition);
}

void Scope_Window::Expose(WaveBase *wf)
{
  if(!wf || !waveDrawingArea)
    return;

  int xoffset = waveXoffset();

  PixMap *pm = wf->wavePixmap();

  gdk_draw_pixmap(waveDrawingArea->window,
		  waveDrawingArea->style->fg_gc[GTK_STATE_NORMAL],
		  pm->pixmap(),
		  xoffset,0,      // source
		  0,pm->yoffset,  // destination
		  pm->width,pm->height);

  pm = wf->signalPixmap();

  // If the Waveform is selected for editing, then there's a gtk_entry
  // widget exposed showing the signal name. If we're not editing, then
  // show the pixmap with the signal name:

  if (!m_entry->isSelected(wf)) {

    gdk_draw_pixmap(SignalWindow, 
		    signalDrawingArea->style->fg_gc[GTK_STATE_NORMAL],
		    pm->pixmap(),
		    0,0,            // source
		    0,pm->yoffset,  // destination
		    pm->width,pm->height);
  }
}


//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#if 0 // defined but not used
static void ScrollAdjustments(GtkViewport   *viewport,
				GtkAdjustment *arg1,
				GtkAdjustment *arg2,
				gpointer       user_data)
{
  printf("%s\n",__FUNCTION__);
}

static void ScrollChildren(GtkScrolledWindow *scrolledwindow,
			    GtkScrollType     *arg1,
			    gboolean           arg2,
			    gpointer           user_data)
{
  printf("%s\n",__FUNCTION__);
}
#endif

static void hAdjVChange(GtkAdjustment *pAdj,
			gpointer       user_data)
{

  gdouble width = pAdj->upper - pAdj->lower - pAdj->page_size;
  gNormalizedHorizontalPosition = pAdj->value/ (width ? width : 1.0);

}
static gint
button_press(GtkWidget *widget,
	     GdkEventButton *pEventButton, 
	     Scope_Window *sw)
{
  
  if (pEventButton) {

    Dprintf (("button: button:%d x=%g y=%g evt=%d modifier=0x%x\n",
	      pEventButton->button,
	      pEventButton->x,pEventButton->y,pEventButton->type,pEventButton->state));
    if (sw) {
      sw->UpdateMarker(pEventButton->x,pEventButton->y, pEventButton->button, pEventButton->state);
    }
  }

  return TRUE;
}

bool Scope_Window::endSignalNameSelection(bool bAccept)
{

  gtk_widget_grab_focus (GTK_WIDGET(waveDrawingArea));

  WaveBase *pwf = m_entry->getSelected();
  if (pwf) {

    if (bAccept)
      pwf->setSource(gtk_entry_get_text(m_entry->m_entry));

    m_entry->Select(0);

    return true;
  }

  return false;

}

//------------------------------------------------------------------------
bool Scope_Window::selectSignalName(int y)
{
  int timeHeight=15;
  int waveHeight=20;
  bool bRet=true;

  y = (y > timeHeight) ? ((y-timeHeight)/waveHeight) : -1;

  if (y>=0 && y<nSignals) {

    if (m_entry->isSelected(signals[y]))
      return false;

    m_entry->unSelect();

    PixMap *pm = signals[y]->wavePixmap();

    if (pm)
      gtk_layout_move(GTK_LAYOUT(signalDrawingArea),
		      GTK_WIDGET(m_entry->m_entry),
		      0,pm->yoffset-2);
    bRet = m_entry->Select(signals[y]);

  } else
    bRet = endSignalNameSelection(true);

  if (bRet)
    refreshSignalNameGraphics();

  return bRet;
}


//************************************************************************
//           S I G N A L S  
//************************************************************************

gint Scope_Window::signalEntryKeyPress(GtkEntry *widget,
				       GdkEventKey *key, 
				       Scope_Window *sw)
{
  Dprintf (("Entry keypress 0x%x\n",key->keyval));

  if ( key){

    if ( key->keyval==GDK_Return)
      sw->endSignalNameSelection(true);
    
    if ( key->keyval==GDK_Escape)
      sw->endSignalNameSelection(false);
  }

  return FALSE;
}


gint Scope_Window::signalButtonPress(GtkWidget *widget,
				     GdkEventButton *pEventButton, 
				     Scope_Window *sw)
{
  
  if (pEventButton) {

    Dprintf ((" Signal: button:%d x=%g y=%g evt=%d modifier=0x%x\n",
	      pEventButton->button,
	      pEventButton->x,pEventButton->y,pEventButton->type,pEventButton->state));

    sw->selectSignalName((int)(pEventButton->y));

  }

  return TRUE;
}
//------------------------------------------------------------------------
//
// Scope_Window member functions
//
//
//         ------------------------------------------
//         |                  Window
//         |   --------------------------------------
//         |   |            Horizontal pane  
//         |   |  ---------------+  +------------------------+
//         |   |  | signal name  |  |    Wave layout
//         |   |  | drawing area |  |  +---------------------
//         |   |  | 
//
//------------------------------------------------------------------------

void Scope_Window::Build()
{

  GtkTooltips *tooltips;    

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  if (!window)
    return;


  // The "realize" operation creates basic resources like GC's etc. that 
  // are referenced below. Without this call, those resources will be NULL.
  gtk_widget_realize (window);

  gtk_window_set_title(GTK_WINDOW(window), "Scope");


  tooltips = gtk_tooltips_new();



  gtk_signal_connect(GTK_OBJECT (window), "delete_event",
		     GTK_SIGNAL_FUNC(delete_event), this);

  gtk_signal_connect (GTK_OBJECT (window),
		      "expose_event",
		      GTK_SIGNAL_FUNC (Scope_Window_expose_event),
		      this);

#if 0
  GtkWidget *spin_button;
  button = gtk_button_new_with_label ("Zoom In");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      GTK_SIGNAL_FUNC (analyzer_zoom_in_callback),this);    
  gtk_table_attach_defaults (GTK_TABLE(table),button,2,4,9,10);    
  button = gtk_button_new_with_label ("Zoom Out");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      GTK_SIGNAL_FUNC (analyzer_zoom_out_callback),this);    
  gtk_table_attach_defaults (GTK_TABLE(table),button,4,6,9,10);
  button = gtk_button_new_with_label ("About");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      GTK_SIGNAL_FUNC (analyzer_screen_about),this);    
  gtk_table_attach_defaults (GTK_TABLE(table),button,6,8,9,10);    
#endif


  //
  // Define the drawing colors
  //

  // The signal color is bright red
  signal_line_color.red = 0xff00;
  signal_line_color.green = 0x0000;
  signal_line_color.blue = 0x0000;
  gdk_colormap_alloc_color(gdk_colormap_get_system(), &signal_line_color, FALSE, TRUE);
  // The grid color is bright green
  grid_line_color.red = 0x4000;
  grid_line_color.green = 0x4000;
  grid_line_color.blue = 0x4000;
  gdk_colormap_alloc_color(gdk_colormap_get_system(), &grid_line_color, FALSE, TRUE);
  // The vertical grid color is dark green
  grid_v_line_color.red = 0x0000;
  grid_v_line_color.green = 0x2200;
  grid_v_line_color.blue = 0x0000;
  gdk_colormap_alloc_color(gdk_colormap_get_system(), &grid_v_line_color, FALSE, TRUE);
  // The vertical grid color is dark green
  highDensity_line_color.red = 0xff00;
  highDensity_line_color.green = 0xff00;
  highDensity_line_color.blue = 0xff00;
  gdk_colormap_alloc_color(gdk_colormap_get_system(), &highDensity_line_color, TRUE, TRUE);


  waveDrawingArea = gtk_drawing_area_new ();
  gint dawidth  = 400;
  gint daheight = 100;

  gtk_widget_set_usize (waveDrawingArea,dawidth,daheight);
  gtk_widget_set_events (waveDrawingArea,
			 GDK_EXPOSURE_MASK | 
			 GDK_BUTTON_PRESS_MASK | 
			 GDK_KEY_PRESS_MASK | 
			 GDK_KEY_RELEASE_MASK  );

  signalDrawingArea = gtk_layout_new (NULL, NULL);
  gtk_widget_set_usize (signalDrawingArea,100,daheight);
  gtk_widget_set_events (signalDrawingArea,
			 GDK_EXPOSURE_MASK | 
			 GDK_BUTTON_PRESS_MASK | 
			 GDK_KEY_PRESS_MASK | 
			 GDK_KEY_RELEASE_MASK  );



  pvbox = gtk_vbox_new(FALSE,0);
  gtk_container_add (GTK_CONTAINER (window), pvbox);
  


  m_pHpaned = gtk_hpaned_new ();
  gtk_widget_show (m_pHpaned);

  gtk_box_pack_start_defaults (GTK_BOX (pvbox), m_pHpaned);


  m_hAdj = gtk_adjustment_new 
    (0.0,    // value
     0.0,    // lower
     m_PixmapWidth,      // upper
     m_PixmapWidth/100.0,// step_increment
     m_PixmapWidth/10.0, // page_increment
     m_PixmapWidth/5.0); // page_size

  m_phScrollBar = gtk_hscrollbar_new(GTK_ADJUSTMENT(m_hAdj));
  gtk_box_pack_start_defaults(GTK_BOX(pvbox),m_phScrollBar);
  gtk_signal_connect(m_hAdj,"value-changed",
		     (GtkSignalFunc) hAdjVChange, this);

  // Add the drawing areas to the panes
  gtk_paned_add1(GTK_PANED(m_pHpaned), signalDrawingArea);
  gtk_paned_add2(GTK_PANED(m_pHpaned), waveDrawingArea);
  gtk_paned_set_position(GTK_PANED(m_pHpaned),50);

  gtk_widget_show(waveDrawingArea);
  gtk_widget_show(signalDrawingArea);

  // Graphics Context:
  drawing_gc = gdk_gc_new(waveDrawingArea->window);
  grid_gc = gdk_gc_new(waveDrawingArea->window);
  highDensity_gc = gdk_gc_new(waveDrawingArea->window);
  leftMarker_gc = gdk_gc_new(waveDrawingArea->window);

  gdk_gc_set_foreground(grid_gc,&grid_line_color);    
  gdk_gc_set_foreground(drawing_gc,&signal_line_color);    
  gdk_gc_set_foreground(highDensity_gc,&highDensity_line_color);
  gdk_gc_set_foreground(leftMarker_gc,&highDensity_line_color);
  gdk_gc_set_function(leftMarker_gc,GDK_XOR);
  text_gc = waveDrawingArea->style->white_gc;

  guint64 start,stop;
  gridPoints(&start,&stop);

  int timeHeight = 15;
  m_TimeAxis->Build(new PixMap(waveDrawingArea->window, m_PixmapWidth, timeHeight, 0),
		    new PixMap(waveDrawingArea->window, 100, timeHeight, 0));
  m_TimeAxis->Update(start,stop);

  for(int i=0; i<nSignals; i++) {
    int waveHeight=20;
    int yoffset = timeHeight +i*waveHeight;
    signals[i]->Build(new PixMap(waveDrawingArea->window, m_PixmapWidth, waveHeight, yoffset),
		      new PixMap(waveDrawingArea->window, 100, waveHeight, yoffset));
  }

  gtk_signal_connect (GTK_OBJECT (waveDrawingArea),
		      "expose_event",
		      GTK_SIGNAL_FUNC (DrawingArea_expose_event),
		      this);

  KeyMap['z'] = new ZoomInEvent();
  KeyMap['Z'] = new ZoomOutEvent();
  KeyMap['l'] = new PanLeftEvent();
  KeyMap['r'] = new PanRightEvent();


  /* Add a signal handler for key press events. This will capture
   * key commands for single stepping, running, etc.
   */
  gtk_signal_connect(GTK_OBJECT(waveDrawingArea),
		     "key_press_event",
		     (GtkSignalFunc) key_press,
		     (gpointer) this);

  gtk_signal_connect(GTK_OBJECT(waveDrawingArea),
		     "button_press_event",
		     (GtkSignalFunc) button_press,
		     (gpointer) this);

  gtk_signal_connect(GTK_OBJECT(waveDrawingArea),
		     "key_release_event",
		     (GtkSignalFunc) key_release,
		     (gpointer) this);
  gtk_signal_connect(GTK_OBJECT(waveDrawingArea),
		     "scroll-event",
		     (GtkSignalFunc) scroll_event,
		     (gpointer) this);
  GTK_WIDGET_SET_FLAGS( waveDrawingArea,
			GTK_CAN_FOCUS );


  gtk_signal_connect(GTK_OBJECT(signalDrawingArea),
		     "button_press_event",
		     (GtkSignalFunc) signalButtonPress,
		     (gpointer) this);

  gtk_widget_show_all (window);
    
  bIsBuilt = true;

  UpdateMenuItem();

  aw = window->allocation.width;
  ah = window->allocation.height;

  m_entry = new SignalNameEntry(this, GTK_ENTRY(gtk_entry_new ()));

  gtk_layout_put(GTK_LAYOUT(signalDrawingArea),
		 GTK_WIDGET(m_entry->m_entry),
		 0,0);

  gtk_signal_connect(GTK_OBJECT(m_entry->m_entry),
		     "key_press_event",
		     (GtkSignalFunc) signalEntryKeyPress,
		     (gpointer) this);

}


void Scope_Window::Update()
{
  int i;

  if(!enabled)
    return;
    
  if(!bIsBuilt)
    Build();

  if(m_bFrozen)
    return;
  /*
  cout << "function:" << __FUNCTION__ << "\n";
  cout << " a  x "  << window->allocation.x
       << " a y "  << window->allocation.y
       << " a  width "  << window->allocation.width
       << " a height "  << window->allocation.height
       << endl;
  cout << " r  width "  << window->requisition.width
       << " r height "  << window->requisition.height
       << endl;
  */

  // Compute the grid points
  guint64 start,stop;
  gridPoints(&start,&stop);


  // Horizontal scroll
  // the scroll bar maps pixel positions into time.
  // the span of the scroll bar is the span of time that is currently
  //  cached (or soon to be cached) in the waveform pixmaps.
  // the thumb position is the current view into the cache. 
  // the page-size property is 20% of the span


  double dspan = stop-start;
  dspan = (dspan<m_PixmapWidth) ? m_PixmapWidth : dspan;
  g_object_set(G_OBJECT(m_hAdj),
	       "page-size", m_PixmapWidth * 200.0/dspan,
	       NULL);
  gtk_widget_queue_draw (m_phScrollBar);


  m_TimeAxis->Update(start,stop);
  Expose(m_TimeAxis);

  for(i=0; i<nSignals; i++) {
    //plotDebug = i==0;
    if(signals[i]) {
      signals[i]->Update(start,stop);
      Expose(signals[i]);
    }

  }

  // Markers
  int xpos = mapTimeToPixel(m_Markers[eLeftButton]->getVal()) + waveXoffset();
  if (xpos)
    gdk_draw_line(waveDrawingArea->window,leftMarker_gc,
		  xpos, 0,
		  xpos, 1000);
  /*
  cout << "Left marker pos="<<dec<<xpos 
       <<" time=" <<m_Markers[eLeftButton]->getVal()
       <<" start=" << start
       <<" stop=" << stop
       <<endl;
  */
  gtk_widget_show_all(window);

  if (m_entry->isSelected(0)) {
    gtk_widget_hide (GTK_WIDGET(m_entry->m_entry));
  }

}

void Scope_Window::UpdateMarker(gdouble x, gdouble y, guint button, guint state)
{
  switch (button) {

  case 1: // Left Button
    
  case 2: // Right Button
    ;
  }

}

Scope_Window::Scope_Window(GUI_Processor *_gp)
  : m_pHpaned(0), m_phScrollBar(0), m_PixmapWidth(1024),
    m_MajorTicks(32), m_MinorTicks(256),
    m_entry(0)
{

  gp = _gp;
  window = 0;
  wc = WC_data;
  wt = WT_scope_window;

  menu = "<main>/Windows/Scope";
  set_name("scope");

  get_config();

  m_Markers[eStart] = new TimeMarker(this, "scope.start", "Scope window start time");
  m_Markers[eStop]  = new TimeMarker(this, "scope.stop",  "Scope window stop time");
  m_Markers[eLeftButton]  = new TimeMarker(this, "scope.left",  "Scope window left marker");
  m_Markers[eRightButton] = new TimeMarker(this, "scope.right", "Scope window right marker");

  m_zoom = new ZoomAttribute(this);
  m_pan  = new PanAttribute(this);

  get_symbol_table().add(m_Markers[eStart]);
  get_symbol_table().add(m_Markers[eStop]);
  get_symbol_table().add(m_Markers[eLeftButton]);
  get_symbol_table().add(m_Markers[eRightButton]);
  get_symbol_table().add(m_zoom);
  get_symbol_table().add(m_pan);

  m_bFrozen = false;

  //
  // Create the signals for the scope window.
  //

  signals[0] = new Waveform(this,"scope.ch0");
  signals[1] = new Waveform(this,"scope.ch1");
  signals[2] = new Waveform(this,"scope.ch2");
  signals[3] = new Waveform(this,"scope.ch3");
  signals[4] = new Waveform(this,"scope.ch4");
  signals[5] = new Waveform(this,"scope.ch5");
  signals[6] = new Waveform(this,"scope.ch6");
  signals[7] = new Waveform(this,"scope.ch7");

  m_TimeAxis = new TimeAxis(this,"scope.time");

  if(enabled)
    Build();

}

// zoom(i)
// 
  const int minSpan = 10;
void Scope_Window::zoom(int i)
{
  m_bFrozen = true;
  gint64 start = (gint64) m_Markers[eStart]->getVal();
  gint64 stop  = (gint64) m_Markers[eStop]->getVal();
  gint64 now  = (gint64) get_cycles().value;

  if (!stop)
    stop = now;

  gint64 mid  = (start + stop)/2;
  gint64 span = (stop - start)/2;


  if (i>0)
    span /= i;
  else
    span *= -i;

  span = span < minSpan ? minSpan : span;

  start = mid - span;
  stop  = mid + span;

  if (start > stop) {
    start = mid - 1;
    stop = mid + 1;
  }
  
  start = (start < 0) ? 0 : start;
  stop  = (stop >= now) ? 0 : stop;

  m_Markers[eStart]->set(start);
  m_Markers[eStop]->set(stop);

  m_bFrozen = false;

  Update();
}

void Scope_Window::pan(int i)
{

  gint64 start = i+(gint64) m_Markers[eStart]->getVal();
  gint64 stop  = (gint64) m_Markers[eStop]->getVal();
  gint64 now  = (gint64) get_cycles().value;

  if (start < 0)
    return;

  if (!stop)
    return;

  if ((stop+i) > now)
    return;

  m_Markers[eStart]->set(start);
  m_Markers[eStop]->set(stop+i);

}

gdouble Scope_Window::getSpan()
{
  guint64 start = m_Markers[eStart]->getVal();
  guint64 stop  = m_Markers[eStop]->getVal();
  stop = stop ? stop : get_cycles().value;
  return start > stop ? 0.0 : ((gdouble)(stop-start));
}

guint64 Scope_Window::mapPixelToTime(int pixel)
{
  gdouble x = (pixel>=0 && pixel<m_PixmapWidth) ? pixel : 0;

  return (guint64)(getSpan()*x/m_PixmapWidth + m_Markers[eStart]->getVal());
}

/// mapTimeToPixel - convert time to a pixel horizontal offset.
int Scope_Window::mapTimeToPixel(guint64 time)
{
  gdouble span = getSpan();
  guint64 start = m_Markers[eStart]->getVal();

  return (int) ((time>start && time<=(start+span)) ? ((time-start)*m_PixmapWidth)/span : 0);
}

#endif //HAVE_GUI


syntax highlighted by Code2HTML, v. 0.9.1