// This file is a part of the xMule Project.
//
// Copyright (c) 2004 Theodore R. Smith (hopeseekr@xmule.ws / http://www.xmule.ws/)
// DSA-1024 Fingerprint: 10A0 6372 9092 85A2 BB7F 907B CB8B 654B E33B F1ED
//
// 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.

// OScopeCtrl.cpp : implementation file//

#ifdef PRECOMP
#	include "xmule-headers.h"
#else
#	include "OScopeCtrl.h"
#	include "wintypes.h"
#	include "xmule.h"
#	include "xmuleDlg.h"
#endif

#include <math.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;

#endif

#define GetRValue(rgb) (((rgb)>>16)&0xff)
#define GetGValue(rgb) (((rgb)>>8)&0xff)
#define GetBValue(rgb) ((rgb)&0xff)

#define max(a,b) ((a)>(b))?(a):(b)

BEGIN_EVENT_TABLE(COScopeCtrl, wxControl)
EVT_PAINT(COScopeCtrl::OnPaint)
EVT_SIZE(COScopeCtrl::OnSize)
EVT_TIMER(TIMER_OSCOPE, COScopeCtrl::OnTimer)
END_EVENT_TABLE()

/////////////////////////////////////////////////////////////////////////////
// COScopeCtrl
COScopeCtrl::COScopeCtrl(int NTrends, wxWindow *parent)
: wxControl(parent, - 1, wxDefaultPosition, wxDefaultSize), m_nRedrawTimer(this, TIMER_OSCOPE)
{
    int i;
    COLORREF PresetColor [ 16 ] =
    {
        RGB(0xFF, 0x00, 0x00),
        RGB(0xFF, 0xC0, 0xC0),
        RGB(0xFF, 0xFF, 0x00),
        RGB(0xFF, 0xA0, 0x00),
        RGB(0xA0, 0x60, 0x00),
        RGB(0x00, 0xFF, 0x00),
        RGB(0x00, 0xA0, 0x00),
        RGB(0x00, 0x00, 0xFF),
        RGB(0x00, 0xA0, 0xFF),
        RGB(0x00, 0xFF, 0xFF),
        RGB(0x00, 0xA0, 0xA0),
        RGB(0xC0, 0xC0, 0xFF),
        RGB(0xFF, 0x00, 0xFF),
        RGB(0xA0, 0x00, 0xA0),
        RGB(0xFF, 0xFF, 0xFF),
        RGB(0x80, 0x80, 0x80)
        };
    // since plotting is based on a LineTo for each new point
    // we need a starting point (i.e. a "previous" point)
    // use 0.0 as the default first point.
    // these are public member variables, and can be changed outside
    // (after construction).
    // G.Hayduk: NTrends is the number of trends that will be drawn on
    // the plot. First 15 plots have predefined colors, but others will
    // be drawn with white, unless you call SetPlotColor
    m_PlotData = new PlotData_t[ NTrends ];
    m_NTrends = NTrends;
    m_dcPlot = NULL;
    m_dcGrid = NULL;
    m_bitmapPlot = NULL;
    m_bitmapGrid = NULL;
    for (i = 0 ; i < m_NTrends ; i++)
    {
        if (i < 15)
        // see also SetPlotColor:
        m_PlotData[ i ].crPlotColor = PresetColor[i];
        else
        // see also SetPlotColor:
        m_PlotData[ i ].crPlotColor = RGB(255, 255, 255);
        //m_PlotData[ i ].penPlot.CreatePen(PS_SOLID, 0, m_PlotData[ i ].crPlotColor ) ;
        m_PlotData[i].penPlot = * (wxThePenList->FindOrCreatePen(wxColour(GetRValue(m_PlotData[i].crPlotColor), GetGValue(m_PlotData[i].crPlotColor), GetBValue(m_PlotData[i].crPlotColor)), 1, wxSOLID));
        m_PlotData[ i ].dPreviousPosition = 0.0;
        m_PlotData[ i ].nPrevY = - 1;
        m_PlotData[ i ].dLowerLimit = - 10.0;
        m_PlotData[ i ].dUpperLimit = 10.0;
        m_PlotData[ i ].dRange = m_PlotData[ i ].dUpperLimit -
        // protected member variable:
        m_PlotData[ i ].dLowerLimit;
        m_PlotData[ i].lstPoints.AddTail(0.0);
    }
    // public variable for the number of decimal places on the y axis
    // G.Hayduk: I've deleted the possibility of changing this parameter
    // in SetRange, so change it after constructing the plot
    m_nYDecimals = 1;
    m_dcGrid = NULL;
    m_bitmapGrid = NULL;
    // set some initial values for the scaling until "SetRange" is called.
    // these are protected varaibles and must be set with SetRange
    // in order to ensure that m_dRange is updated accordingly
    oldwidth = oldheight = 0;
    // m_nShiftPixels determines how much the plot shifts (in terms of pixels)
    // with the addition of a new data point
    drawBars = false;
    autofitYscale = false;
    m_nShiftPixels = 1;
    m_nTrendPoints = 0;
    m_nMaxPointCnt = 1024;
    CustShift.m_nPointsToDo = 0;
    // G.Hayduk: actually I needed an OScopeCtrl to draw specific number of
    // data samples and stretch them on the plot ctrl. Now, OScopeCtrl has
    // two modes of operation: fixed Shift (when m_nTrendPoints=0,
    // m_nShiftPixels is in use), or fixed number of Points in the plot width
    // (when m_nTrendPoints>0)
    // When m_nTrendPoints>0, CustShift structure is in use
    // background, grid and data colors
    // these are public variables and can be set directly
    // see also SetBackgroundColor:
    m_crBackColor = RGB(0, 0, 0);
    // see also SetGridColor:
    m_crGridColor = RGB(0, 255, 255);
    // protected variables
    m_brushBack = * (wxTheBrushList->FindOrCreateBrush(wxColour(GetRValue(m_crBackColor), GetGValue(m_crBackColor), GetBValue(m_crBackColor)), wxSOLID));
    //*wxBLACK_BRUSH;//.CreateSolidBrush( m_crBackColor ) ;
    // public member variables, can be set directly
    // can also be set with SetXUnits:
    m_str.XUnits.Printf("Samples");
    // can also be set with SetYUnits:
    m_str.YUnits.Printf("Y units");
    // protected bitmaps to restore the memory DC's
    m_pbitmapOldGrid = NULL;
    m_pbitmapOldPlot = NULL;
    // G.Hayduk: configurable number of grids init
    // you are free to change those between contructing the object
    // and calling Create
    m_nXGrids = 6;
    m_nYGrids = 5;
    m_nTrendPoints = - 1;
    m_bDoUpdate = true;
    m_nRedrawTimer.SetOwner(this);
    // COScopeCtrl:
}

/////////////////////////////////////////////////////////////////////////////
COScopeCtrl::~COScopeCtrl()
{
    // just to be picky restore the bitmaps for the two memory dc's
    // (these dc's are being destroyed so there shouldn't be any leaks)
    if (m_pbitmapOldGrid != NULL)
    m_dcGrid->SelectObject(wxNullBitmap);
    if (m_pbitmapOldPlot != NULL)
    m_dcPlot->SelectObject(wxNullBitmap);
    delete [] m_PlotData;
    delete m_dcGrid;
    delete m_dcPlot;
    if (m_bitmapPlot) delete m_bitmapPlot;
    if (m_bitmapGrid) delete m_bitmapGrid;
    // G.Hayduk: If anyone notices that I'm not freeing or deleting
    // something, please let me know: hayduk@hello.to
    // ~COScopeCtrl:
}

#if 0
BEGIN_MESSAGE_MAP(COScopeCtrl, CWnd)
//{{AFX_MSG_MAP(COScopeCtrl)
ON_WM_PAINT()
ON_WM_SIZE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif

/////////////////////////////////////////////////////////////////////////////
// COScopeCtrl message handlers

/////////////////////////////////////////////////////////////////////////////
#if 0
bool COScopeCtrl::Create(DWORD dwStyle, const RECT &rect,
CWnd *pParentWnd, UINT nID)
{
    bool result;
    static wxString className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW);
    result = CWnd::CreateEx(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE,
    className, NULL, dwStyle,
    rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
    pParentWnd->GetSafeHwnd(), (HMENU) nID);
    if (result != 0)
    InvalidateCtrl();
    return result;
    // Create:
}

#endif

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetRange(double dLower, double dUpper, int iTrend)
{
    m_PlotData[ iTrend ].dLowerLimit = dLower;
    m_PlotData[ iTrend ].dUpperLimit = dUpper;
    m_PlotData[ iTrend ].dRange = m_PlotData[ iTrend ].dUpperLimit - m_PlotData[ iTrend ].dLowerLimit;
    m_PlotData[ iTrend ].dVerticalFactor = (double) m_nPlotHeight / m_PlotData[ iTrend ].dRange;
    // clear out the existing garbage, re-start with a clean plot
    InvalidateCtrl();
    // SetRange:
}

void COScopeCtrl::SetRanges(double dLower, double dUpper)
{
    int iTrend;
    for (iTrend = 0 ; iTrend < m_NTrends ; iTrend++)
    {
        m_PlotData[ iTrend ].dLowerLimit = dLower;
        m_PlotData[ iTrend ].dUpperLimit = dUpper;
        m_PlotData[ iTrend ].dRange = m_PlotData[ iTrend ].dUpperLimit - m_PlotData[ iTrend ].dLowerLimit;
        m_PlotData[ iTrend ].dVerticalFactor = (double) m_nPlotHeight / m_PlotData[ iTrend ].dRange;
    }
    // clear out the existing garbage, re-start with a clean plot
    InvalidateCtrl();
    // SetRange:
}

/////////////////////////////////////////////////////////////////////////////
// G.Hayduk: Apart from setting title of axis, now you can optionally set
// the limits strings
// (string which will be placed on the left and right of axis)
void COScopeCtrl::SetXUnits(wxString string, wxString XMin, wxString XMax)
{
    m_str.XUnits = string;
    m_str.XMin = XMin;
    m_str.XMax = XMax;
    InvalidateCtrl(false);
    // SetXUnits:
}

/////////////////////////////////////////////////////////////////////////////
// G.Hayduk: Apart from setting title of axis, now you can optionally set
// the limits strings
// (string which will be placed on the bottom and top of axis)
void COScopeCtrl::SetYUnits(wxString string, wxString YMin, wxString YMax)
{
    m_str.YUnits = string;
    m_str.YMin = YMin;
    m_str.YMax = YMax;
    // clear out the existing garbage, re-start with a clean plot
    InvalidateCtrl();
    // SetYUnits:
}

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetGridColor(COLORREF color)
{
    m_crGridColor = color;
    // clear out the existing garbage, re-start with a clean plot
    InvalidateCtrl();
    // SetGridColor:
}

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetPlotColor(COLORREF color, int iTrend)
{
    m_PlotData[ iTrend ].crPlotColor = color;
    //m_PlotData[ iTrend ].penPlot.DeleteObject() ;
    m_PlotData[ iTrend ].penPlot = * (wxThePenList->FindOrCreatePen(wxColour(GetRValue(m_PlotData[iTrend].crPlotColor), GetGValue(m_PlotData[iTrend].crPlotColor), GetBValue(m_PlotData[iTrend].crPlotColor)), 1, wxSOLID));
    //CreatePen(PS_SOLID, 0, m_PlotData[ iTrend ].crPlotColor) ;
    // clear out the existing garbage, re-start with a clean plot
    //	InvalidateCtrl() ;
    // SetPlotColor:
}

COLORREF COScopeCtrl::GetPlotColor(int iTrend)
{
    return m_PlotData[ iTrend ].crPlotColor;
    // GetPlotColor:
}

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::SetBackgroundColor(COLORREF color)
{
    m_crBackColor = color;
    //m_brushBack.DeleteObject() ;
    //m_brushBack.CreateSolidBrush(m_crBackColor) ;
    m_brushBack = * (wxTheBrushList->FindOrCreateBrush(wxColour(GetRValue(m_crBackColor), GetGValue(m_crBackColor), GetBValue(m_crBackColor)), wxSOLID));
    // clear out the existing garbage, re-start with a clean plot
    InvalidateCtrl();
    // SetBackgroundColor:
}

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::InvalidateCtrl(bool deleteGraph)
{
    // There is a lot of drawing going on here - particularly in terms of
    // drawing the grid.  Don't panic, this is all being drawn (only once)
    // to a bitmap.  The result is then BitBlt'd to the control whenever needed.
    int i, j, GridPos;
    int nCharacters;
    // nothing you can do
    if (m_nClientWidth == 0 || m_nClientHeight == 0)
    return;
    wxPen *oldPen;
    //CPen solidPen(PS_SOLID, 0, m_crGridColor) ;
    wxPen solidPen = * (wxThePenList->FindOrCreatePen(wxColour(GetRValue(m_crGridColor), GetGValue(m_crGridColor), GetBValue(m_crGridColor)), 1, wxSOLID));
    //wxFont axisFont, yUnitFont, *oldFont ;
    wxString strTemp;
    // in case we haven't established the memory dc's
    //CClientDC dc(this) ;
    wxClientDC dc(this);
    // if we don't have one yet, set up a memory dc for the grid
    if (m_dcGrid == NULL)
    {
        //m_dcGrid.CreateCompatibleDC(&dc) ;
        m_dcGrid = new wxMemoryDC;
        if (m_bitmapGrid) delete m_bitmapGrid;
        m_bitmapGrid = NULL;
        m_bitmapGrid = new wxBitmap(m_nClientWidth, m_nClientHeight);
        m_dcGrid->SelectObject( *m_bitmapGrid);
    }
    //m_dcGrid.SetBkColor (m_crBackColor) ;
    // fill the grid background
    m_dcGrid->SetBrush(m_brushBack);
    m_dcGrid->SetPen( *wxTRANSPARENT_PEN);
    m_dcGrid->DrawRectangle(m_rectClient.left, m_rectClient.top, m_rectClient.right - m_rectClient.left,
    m_rectClient.bottom - m_rectClient.top);
    //m_dcGrid.FillRect(m_rectClient, &m_brushBack) ;
    // draw the plot rectangle:
    // determine how wide the y axis scaling values are
    nCharacters = abs((int) log10(fabs(m_PlotData[ 0 ].dUpperLimit)));
    nCharacters = max(nCharacters, abs((int) log10(fabs(m_PlotData[ 0 ].dLowerLimit))));
    // add the units digit, decimal point and a minus sign, and an extra space
    // as well as the number of decimal places to display
    nCharacters = nCharacters + 4 + m_nYDecimals;
    // adjust the plot rectangle dimensions
    // assume 6 pixels per character (this may need to be adjusted)
    //(nCharacters) ;:
    m_rectPlot.left = m_rectClient.left + 6 *7 + 4;
    //m_rectPlot.x=m_rectClient.left+6*&;
    //.Width() ;:
    m_nPlotWidth = m_rectPlot.right - m_rectPlot.left;
    // draw the plot rectangle
    //oldPen = m_dcGrid.SelectObject (&solidPen) ;
    m_dcGrid->SetPen(solidPen);
#if 0
    m_dcGrid.MoveTo(m_rectPlot.left, m_rectPlot.top);
    m_dcGrid.LineTo(m_rectPlot.right + 1, m_rectPlot.top);
    m_dcGrid.LineTo(m_rectPlot.right + 1, m_rectPlot.bottom + 1);
    m_dcGrid.LineTo(m_rectPlot.left, m_rectPlot.bottom + 1);
    m_dcGrid.LineTo(m_rectPlot.left, m_rectPlot.top);
    m_dcGrid.SelectObject(oldPen);
#endif
    m_dcGrid->DrawLine(m_rectPlot.left, m_rectPlot.top, m_rectPlot.right + 1, m_rectPlot.top);
    m_dcGrid->DrawLine(m_rectPlot.right + 1, m_rectPlot.top, m_rectPlot.right + 1, m_rectPlot.bottom + 1);
    m_dcGrid->DrawLine(m_rectPlot.right + 1, m_rectPlot.bottom + 1, m_rectPlot.left, m_rectPlot.bottom + 1);
    m_dcGrid->DrawLine(m_rectPlot.left, m_rectPlot.bottom + 1, m_rectPlot.left, m_rectPlot.top);
    m_dcGrid->SetPen(wxNullPen);
    // draw the dotted lines,
    // use SetPixel instead of a dotted pen - this allows for a
    // finer dotted line and a more "technical" look
    // G.Hayduk: added configurable number of grids
    wxPen grPen(wxColour(GetRValue(m_crGridColor), GetGValue(m_crGridColor), GetBValue(m_crGridColor)), 1, wxSOLID);
    m_dcGrid->SetPen(grPen);
    for (j = 1 ; j < (m_nYGrids + 1) ; j++)
    {
        GridPos = (m_rectPlot.bottom - m_rectPlot.top) *j/ (m_nYGrids + 1) + m_rectPlot.top;
        for (i = m_rectPlot.left ; i < m_rectPlot.right ; i += 4)
        //m_dcGrid.SetPixel ( i, GridPos, m_crGridColor ) ;
        m_dcGrid->DrawPoint(i, GridPos);
    }
 /*
 for( j=1 ; j<(m_nXGrids+1) ; j++ ){
 GridPos = m_rectPlot.Width()*j/( m_nXGrids + 1 ) + m_rectPlot.left ;
 for (i=m_rectPlot.top; i<m_rectPlot.bottom; i+=4)
 m_dcGrid.SetPixel ( GridPos, i, m_crGridColor ) ;
 }
    */
    // create some fonts (horizontal and vertical)
    // use a height of 14 pixels and 300 weight
    // (these may need to be adjusted depending on the display)
#if 0
    axisFont.CreateFont(14, 0, 0, 0, 300,
    FALSE, FALSE, 0, ANSI_CHARSET,
    OUT_DEFAULT_PRECIS,
    CLIP_DEFAULT_PRECIS,
    DEFAULT_QUALITY,
    DEFAULT_PITCH |FF_SWISS, "Arial");
    yUnitFont.CreateFont(14, 0, 900, 0, 300,
    FALSE, FALSE, 0, ANSI_CHARSET,
    OUT_DEFAULT_PRECIS,
    CLIP_DEFAULT_PRECIS,
    DEFAULT_QUALITY,
    DEFAULT_PITCH |FF_SWISS, "Arial");
#endif
    wxFont *axisFont, *oldFont;
    axisFont = new wxFont(10, wxSWISS, wxNORMAL, wxNORMAL, FALSE);
    // grab the horizontal font
    //oldFont = m_dcGrid.SelectObject(&axisFont) ;
    //,this);:
    m_dcGrid->SetFont( *axisFont);
    // y max
    //m_dcGrid.SetTextColor (m_crGridColor) ;
    m_dcGrid->SetTextForeground(wxColour(GetRValue(m_crGridColor), GetGValue(m_crGridColor), GetBValue(m_crGridColor)));
    //m_dcGrid.SetTextAlign (TA_RIGHT|TA_TOP) ;
    if (m_str.YMax.IsEmpty())
    strTemp.Printf("%.*lf", m_nYDecimals, m_PlotData[ 0 ].dUpperLimit);
    else
    strTemp = m_str.YMax;
    //m_dcGrid.TextOut (m_rectPlot.left-4, m_rectPlot.top-7, strTemp) ;
    wxCoord sizX, sizY;
#ifdef __WXGTK20__
#if (wxMAJOR_VERSION==2) && (wxMINOR_VERSION==4) && (wxRELEASE_NUMBER==0)
    // well.. it doesn't work. so don't do it
#else
    m_dcGrid->GetTextExtent(strTemp, &sizX, &sizY);
    m_dcGrid->DrawText(strTemp, m_rectPlot.left - 4 - sizX, m_rectPlot.top - 7);
#endif
#else
m_dcGrid->GetTextExtent(strTemp, &sizX, &sizY);

m_dcGrid->DrawText(strTemp, m_rectPlot.left - 4 - sizX, m_rectPlot.top - 7);

#endif
m_dcGrid->SetPen(wxNullPen);

// y/2
strTemp.Printf("%.*lf", m_nYDecimals, m_PlotData[ 0 ].dUpperLimit / 2);

//m_dcGrid.TextOut (m_rectPlot.left-2, m_rectPlot.bottom+((m_rectPlot.top-m_rectPlot.bottom)/2)-7 , strTemp) ;
#ifdef __WXGTK20__
#if (wxMAJOR_VERSION==2) && (wxMINOR_VERSION==4) && (wxRELEASE_NUMBER==0)
// it doesn't work. so don't do it.
#else
m_dcGrid->GetTextExtent(strTemp, &sizX, &sizY);

m_dcGrid->DrawText(strTemp, m_rectPlot.left - 2 - sizX, m_rectPlot.bottom + ((m_rectPlot.top - m_rectPlot.bottom) /2) - 7);

#endif
#else
m_dcGrid->GetTextExtent(strTemp, &sizX, &sizY);

m_dcGrid->DrawText(strTemp, m_rectPlot.left - 2 - sizX, m_rectPlot.bottom + ((m_rectPlot.top - m_rectPlot.bottom) /2) - 7);

#endif
// y min
//m_dcGrid.SetTextAlign (TA_RIGHT|TA_BASELINE) ;
if (m_str.YMin.IsEmpty())
strTemp.Printf("%.*lf", m_nYDecimals, m_PlotData[ 0 ].dLowerLimit);

else
strTemp = m_str.YMin;

#ifdef __WXGTK20__
#if (wxMAJOR_VERSION==2) && (wxMINOR_VERSION==4) && (wxRELEASE_NUMBER==0)
// well.. it doesn't work. so don't do it
#else
m_dcGrid->GetTextExtent(strTemp, &sizX, &sizY);

m_dcGrid->DrawText(strTemp, m_rectPlot.left - 4 - sizX, m_rectPlot.bottom);

//m_dcGrid.TextOut (m_rectPlot.left-4-sizX, m_rectPlot.bottom, strTemp) ;
#endif
#else
m_dcGrid->GetTextExtent(strTemp, &sizX, &sizY);

m_dcGrid->DrawText(strTemp, m_rectPlot.left - 4 - sizX, m_rectPlot.bottom);

#endif
 /*
 // x min
 m_dcGrid.SetTextAlign (TA_LEFT|TA_TOP) ;
 if( m_str.XMin.IsEmpty() )
 m_dcGrid.TextOut (m_rectPlot.left, m_rectPlot.bottom+4, "0") ;
 else
 m_dcGrid.TextOut( m_rectPlot.left, m_rectPlot.bottom+4, (LPCTSTR)m_str.XMin );
 // x max
 m_dcGrid.SetTextAlign (TA_RIGHT|TA_TOP) ;
 if( m_str.XMax.IsEmpty() ){
 if( m_nTrendPoints<0 )
 strTemp.Printf ("%d", m_nPlotWidth/m_nShiftPixels) ;
 else
 strTemp.Printf ("%d", m_nTrendPoints-1 ) ;
 } else
 strTemp = m_str.XMax;
 m_dcGrid.TextOut (m_rectPlot.right, m_rectPlot.bottom+4, strTemp) ;
    */

// x units
//m_dcGrid.SetTextAlign (TA_CENTER|TA_TOP) ;
#ifdef __WXGTK20__
#if (wxMAJOR_VERSION==2) && (wxMINOR_VERSION==4) && (wxRELEASE_NUMBER==0)
// well.. it doesn't work. so don't do it
#else
m_dcGrid->GetTextExtent(m_str.XUnits, &sizX, &sizY);

//m_dcGrid.TextOut ((m_rectPlot.left+m_rectPlot.right)/2,
//	                m_rectPlot.bottom+4, m_str.XUnits) ;
m_dcGrid->DrawText(m_str.XUnits, (m_rectPlot.left + m_rectPlot.right) /2 - sizX/2, m_rectPlot.bottom + 4);

#endif
#else
m_dcGrid->GetTextExtent(m_str.XUnits, &sizX, &sizY);

//m_dcGrid.TextOut ((m_rectPlot.left+m_rectPlot.right)/2,
//	                m_rectPlot.bottom+4, m_str.XUnits) ;
if (!m_str.XUnits.IsEmpty())
m_dcGrid->DrawText(m_str.XUnits, (m_rectPlot.left + m_rectPlot.right) /2 - sizX/2, m_rectPlot.bottom + 4);

#endif
// restore the font
//m_dcGrid.SelectObject(oldFont) ;

// y units
//oldFont = m_dcGrid.SelectObject(&yUnitFont) ;
//m_dcGrid.SetTextAlign (TA_CENTER|TA_BASELINE) ;
//m_dcGrid.TextOut ((m_rectClient.left+m_rectPlot.left)/2,
//	                ((m_rectPlot.bottom+m_rectPlot.top)/2)-40, m_str.YUnits) ;
m_dcGrid->GetTextExtent(m_str.YUnits, &sizX, &sizY);

if (!m_str.YUnits.IsEmpty())
m_dcGrid->DrawRotatedText(m_str.YUnits, (m_rectClient.left + m_rectPlot.left + 4) /2 - (sizY *2),
((m_rectPlot.bottom + m_rectPlot.top) /2) + sizX/2, 90.0);

//m_dcGrid.SelectObject(oldFont) ;

// at this point we are done filling the the grid bitmap,
// no more drawing to this bitmap is needed until the setting are changed

// if we don't have one yet, set up a memory dc for the plot
if (m_dcPlot == NULL)
{
    // what on earth was going on in here??
    m_dcPlot = new wxMemoryDC;
    if (m_bitmapPlot) delete m_bitmapPlot;
    m_bitmapPlot = NULL;
    m_bitmapPlot = new wxBitmap(m_nClientWidth, m_nClientHeight);
    m_dcPlot->SelectObject( *m_bitmapPlot);
}

// make sure the plot bitmap is cleared
if (deleteGraph)
{
    //m_dcPlot.SetBkColor (m_crBackColor) ;
    m_dcPlot->SetBrush(m_brushBack);
    m_dcPlot->SetPen( *wxTRANSPARENT_PEN);
    m_dcPlot->DrawRectangle(m_rectClient.left, m_rectClient.top, m_rectClient.right - m_rectClient.left,
    m_rectClient.bottom - m_rectClient.top);
}

if (true)
{
    int iNewSize = m_rectClient.right - m_rectClient.left;
    if (m_nMaxPointCnt < iNewSize)
    m_nMaxPointCnt = iNewSize;
    m_bDoUpdate = false;
    //if(m_nRedrawTimer)
    //	KillTimer(m_nRedrawTimer);
    m_nRedrawTimer.Stop();
    //m_nRedrawTimer = SetTimer(1612, 100, NULL); // reduce flickering
    m_nRedrawTimer.SetOwner(this, TIMER_OSCOPE);
    m_nRedrawTimer.Start(100);
    //ReCreateGraph();
}

delete axisFont;

// finally, force the plot area to redraw
//InvalidateRect(m_rectClient) ;
wxRect rrc;

rrc.x = m_rectClient.left;

rrc.y = m_rectClient.top;

rrc.width = m_rectClient.right - m_rectClient.left;

rrc.height = m_rectClient.bottom - m_rectClient.top;

Refresh(FALSE, &rrc);

// InvalidateCtrl:
}

/////////////////////////////////////////////////////////////////////////////
// G.Hayduk: now, there are two methods: AppendPoints and AppendEmptyPoints
void COScopeCtrl::AppendPoints(double dNewPoint[], bool bInvalidate, bool bAdd2List)
{
    int iTrend;
    // append a data point to the plot
    for (iTrend = 0 ; iTrend < m_NTrends ; iTrend++)
    {
        m_PlotData[iTrend].dCurrentPosition = dNewPoint[iTrend];
        if (bAdd2List)
        {
            m_PlotData[iTrend].lstPoints.AddTail((float) dNewPoint[iTrend]);
            while (m_PlotData[iTrend].lstPoints.GetCount() > m_nMaxPointCnt)
            m_PlotData[iTrend].lstPoints.RemoveHead();
        }
    }
    if (m_nTrendPoints > 0)
    {
        if (CustShift.m_nPointsToDo == 0)
        {
            CustShift.m_nPointsToDo = m_nTrendPoints - 1;
            CustShift.m_nWidthToDo = m_nPlotWidth;
            CustShift.m_nRmndr = 0;
        }
        // a little bit tricky setting m_nShiftPixels in "fixed number of points through plot width" mode
        m_nShiftPixels = (CustShift.m_nWidthToDo + CustShift.m_nRmndr) / CustShift.m_nPointsToDo;
        CustShift.m_nRmndr = (CustShift.m_nWidthToDo + CustShift.m_nRmndr) % CustShift.m_nPointsToDo;
        if (CustShift.m_nPointsToDo == 1)
        m_nShiftPixels = CustShift.m_nWidthToDo;
        CustShift.m_nWidthToDo -= m_nShiftPixels;
        CustShift.m_nPointsToDo--;
    }
    DrawPoint();
    if (bInvalidate &&m_bDoUpdate)
    Refresh(FALSE);
    return;
    // AppendPoint:
}

/////////////////////////////////////////////////////////////////////////////
// G.Hayduk:
// AppendEmptyPoints adds a vector of data points, without drawing them
// (but shifting the plot), this way you can do a "hole" (space) in the plot
// i.e. indicating "no data here". When points are available, call AppendEmptyPoints
// for first valid vector of data points, and then call AppendPoints again and again
// for valid points

void COScopeCtrl::AppendEmptyPoints(double dNewPoint[], bool bInvalidate, bool bAdd2List)
{
    int iTrend, currY;
    RECT ScrollRect, rectCleanUp;
    // append a data point to the plot
    // return the previous point
    for (iTrend = 0 ; iTrend < m_NTrends ; iTrend++)
    {
        m_PlotData[ iTrend ].dCurrentPosition = dNewPoint[ iTrend ];
        if (bAdd2List)
        m_PlotData[iTrend].lstPoints.AddTail(dNewPoint[iTrend]);
    }
    if (m_nTrendPoints > 0)
    {
        if (CustShift.m_nPointsToDo == 0)
        {
            CustShift.m_nPointsToDo = m_nTrendPoints - 1;
            CustShift.m_nWidthToDo = m_nPlotWidth;
            CustShift.m_nRmndr = 0;
        }
        m_nShiftPixels = (CustShift.m_nWidthToDo + CustShift.m_nRmndr) / CustShift.m_nPointsToDo;
        CustShift.m_nRmndr = (CustShift.m_nWidthToDo + CustShift.m_nRmndr) % CustShift.m_nPointsToDo;
        if (CustShift.m_nPointsToDo == 1)
        m_nShiftPixels = CustShift.m_nWidthToDo;
        CustShift.m_nWidthToDo -= m_nShiftPixels;
        CustShift.m_nPointsToDo--;
    }
    // now comes DrawPoint's shift process
    if (m_dcPlot != NULL)
    {
        if (m_nShiftPixels > 0)
        {
            ScrollRect.left = m_rectPlot.left;
            ScrollRect.top = m_rectPlot.top + 1;
            ScrollRect.right = m_rectPlot.left + m_nPlotWidth;
            ScrollRect.bottom = m_rectPlot.top + 1 + m_nPlotHeight;
            ScrollRect = m_rectPlot;
            ScrollRect.right++;
            // no scroll in wxWin
            //m_dcPlot.ScrollDC( -m_nShiftPixels, 0, (LPCRECT)&ScrollRect, (LPCRECT)&ScrollRect, NULL, NULL );
            m_dcPlot->Blit(m_rectPlot.left, m_rectPlot.top + 1, m_nPlotWidth, m_nPlotHeight, m_dcPlot,
            m_rectPlot.left + m_nShiftPixels, m_rectPlot.top + 1);
            // establish a rectangle over the right side of plot
            // which now needs to be cleaned up proir to adding the new point
            rectCleanUp = m_rectPlot;
            rectCleanUp.left = rectCleanUp.right - m_nShiftPixels + 1;
            rectCleanUp.right++;
            // fill the cleanup area with the background
            //m_dcPlot.FillRect(rectCleanUp, &m_brushBack) ;
            m_dcPlot->SetBrush(m_brushBack);
            m_dcPlot->SetPen( *wxTRANSPARENT_PEN);
            m_dcPlot->DrawRectangle(rectCleanUp.left, rectCleanUp.top, rectCleanUp.right - rectCleanUp.left,
            rectCleanUp.bottom - rectCleanUp.top);
        }
        // draw the next line segement
        for (iTrend = 0 ; iTrend < m_NTrends ; iTrend++)
        {
            currY = m_rectPlot.bottom -
            (long)((m_PlotData[ iTrend ].dCurrentPosition - m_PlotData[ iTrend ].dLowerLimit) *m_PlotData[ iTrend ].dVerticalFactor);
            m_PlotData[ iTrend ].nPrevY = currY;
            // store the current point for connection to the next point
            m_PlotData[ iTrend ].dPreviousPosition = m_PlotData[ iTrend ].dCurrentPosition;
        }
    }
    // -----------------------------------------
    if (bInvalidate &&m_bDoUpdate)
    Refresh(FALSE);
    //Invalidate() ;
    return;
    // AppendEmptyPoint:
}

////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::OnPaint(wxPaintEvent &evt)
{
    // device context for painting:
    wxPaintDC dc(this);
    wxMemoryDC memDC;
    wxBitmap *memBitmap;
    // bitmap originally found in CMemDC:
    wxBitmap *oldBitmap;
    // no real plotting work is performed here,
    // just putting the existing bitmaps on the client
    // to avoid flicker, establish a memory dc, draw to it
    // and then BitBlt it to the client
    //memDC.CreateCompatibleDC(&dc) ;
    //memBitmap.CreateCompatibleBitmap(&dc, m_nClientWidth, m_nClientHeight) ;
    memBitmap = new wxBitmap(m_nClientWidth, m_nClientHeight);
    //oldBitmap = (CBitmap *)memDC.SelectObject(&memBitmap) ;
    memDC.SelectObject( *memBitmap);
    if (memDC.Ok())
    {
        // first drop the grid on the memory dc
        //memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
        //             &m_dcGrid, 0, 0, SRCCOPY) ;
        memDC.Blit(0, 0, m_nClientWidth, m_nClientHeight, m_dcGrid, 0, 0);
        // now add the plot on top as a "pattern" via SRCPAINT.
        // works well with dark background and a light plot
        //memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
        //            &m_dcPlot, 0, 0, SRCPAINT) ;  //SRCPAINT
        memDC.Blit(0, 0, m_nClientWidth, m_nClientHeight, m_dcPlot, 0, 0, wxOR);
        // finally send the result to the display
        //dc.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
        //        &memDC, 0, 0, SRCCOPY) ;
        dc.Blit(0, 0, m_nClientWidth, m_nClientHeight, &memDC, 0, 0);
    }
    //memDC.SelectObject(oldBitmap) ;
    memDC.SelectObject(wxNullBitmap);
    // free it. please. please. please.
    delete memBitmap;
    // OnPaint:
}

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::DrawPoint()
{
    // this does the work of "scrolling" the plot to the left
    // and appending a new data point all of the plotting is
    // directed to the memory based bitmap associated with m_dcPlot
    // the will subsequently be BitBlt'd to the client in OnPaint
    int currX, prevX, currY, prevY, iTrend;
    //CPen *oldPen ;
    RECT ScrollRect, rectCleanUp;
    if (m_dcPlot != NULL)
    {
        //	BitBlt was replaced by call to ScrollDC
        //		m_dcPlot.BitBlt(m_rectPlot.left, m_rectPlot.top+1,
        //		                m_nPlotWidth, m_nPlotHeight, &m_dcPlot,
        //		                m_rectPlot.left+m_nShiftPixels, m_rectPlot.top+1,
        //		                SRCCOPY) ;
        if (m_nShiftPixels > 0)
        {
            ScrollRect.left = m_rectPlot.left;
            ScrollRect.top = m_rectPlot.top + 1;
            ScrollRect.right = m_rectPlot.left + m_nPlotWidth;
            ScrollRect.bottom = m_rectPlot.top + 1 + m_nPlotHeight;
            ScrollRect = m_rectPlot;
            ScrollRect.right++;
            // no scroll in wxWIn
            //m_dcPlot.ScrollDC( -m_nShiftPixels, 0, (LPCRECT)&ScrollRect, (LPCRECT)&ScrollRect, NULL, NULL );
            m_dcPlot->Blit(m_rectPlot.left, m_rectPlot.top + 1, m_nPlotWidth, m_nPlotHeight, m_dcPlot,
            m_rectPlot.left + m_nShiftPixels, m_rectPlot.top + 1);
            //printf("TODO: Scroll\n");
            // establish a rectangle over the right side of plot
            // which now needs to be cleaned up proir to adding the new point
            rectCleanUp = m_rectPlot;
            // + 1;:
            rectCleanUp.left = rectCleanUp.right - m_nShiftPixels;
            rectCleanUp.right++;
            // fill the cleanup area with the background
            m_dcPlot->SetPen( *wxTRANSPARENT_PEN);
            m_dcPlot->SetBrush(m_brushBack);
            m_dcPlot->DrawRectangle(rectCleanUp.left, rectCleanUp.top, rectCleanUp.right - rectCleanUp.left,
            rectCleanUp.bottom - rectCleanUp.top);
            //m_dcPlot.FillRect(rectCleanUp, &m_brushBack) ;
        }
        // draw the next line segement
        for (iTrend = 0 ; iTrend < m_NTrends ; iTrend++)
        {
            // grab the plotting pen
            //oldPen = m_dcPlot.SelectObject(&m_PlotData[ iTrend ].penPlot) ;
            m_dcPlot->SetPen(m_PlotData[iTrend].penPlot);
            // move to the previous point
            prevX = m_rectPlot.right - m_nShiftPixels;
            if (m_PlotData[ iTrend ].nPrevY > 0)
            {
                prevY = m_PlotData[ iTrend ].nPrevY;
            }
            else
            prevY = m_rectPlot.bottom -
            (long)((m_PlotData[ iTrend ].dPreviousPosition - m_PlotData[ iTrend ].dLowerLimit) *m_PlotData[ iTrend ].dVerticalFactor);
            wxCoord sX, sY;
            sX = prevX - 1;
            sY = prevY;
            //m_dcPlot.MoveTo (prevX-1, prevY) ;
            // draw to the current point
            currX = m_rectPlot.right;
            currY = m_rectPlot.bottom -
            (long)((m_PlotData[ iTrend ].dCurrentPosition - m_PlotData[ iTrend ].dLowerLimit) *m_PlotData[ iTrend ].dVerticalFactor);
            m_PlotData[ iTrend ].nPrevY = currY;
            if (abs(prevX - currX) > abs(prevY - currY))
            {
                currX += prevX - currX > 0 ? - 1: 1;
            }
            else
            {
                currY += prevY - currY > 0 ? - 1: 1;
            }
            //m_dcPlot.LineTo (currX-1, currY) ;
            m_dcPlot->DrawLine(sX, sY, currX - 1, currY);
            //if (drawBars) m_dcPlot.LineTo(currX-1,m_rectPlot.bottom);
            if (drawBars) m_dcPlot->DrawLine(currX - 1, currY, currX - 1, m_rectPlot.bottom);
            //m_dcPlot.Rectangle(currX-1,currY,currX-1,m_rectPlot.bottom);
            // restore the pen
            //m_dcPlot.SelectObject(oldPen) ;
            // if the data leaks over the upper or lower plot boundaries
            // fill the upper and lower leakage with the background
            // this will facilitate clipping on an as needed basis
            // as opposed to always calling IntersectClipRect
            if ((prevY <= m_rectPlot.top) || (currY <= m_rectPlot.top))
            {
                //m_dcPlot.FillRect(CRect(prevX-1, m_rectClient.top, currX+5, m_rectPlot.top+1), &m_brushBack) ;
                m_dcPlot->SetBrush(m_brushBack);
                m_dcPlot->SetPen( *wxTRANSPARENT_PEN);
                m_dcPlot->DrawRectangle(prevX - 1, m_rectClient.top, (currX + 5) - (prevX - 1), (m_rectPlot.top + 1) - (m_rectClient.top));
            }
            if ((prevY >= m_rectPlot.bottom) || (currY >= m_rectPlot.bottom))
            {
                //m_dcPlot.FillRect(CRect(prevX-1, m_rectPlot.bottom+1, currX+5, m_rectClient.bottom+1), &m_brushBack) ;
                m_dcPlot->SetBrush(m_brushBack);
                m_dcPlot->SetPen( *wxTRANSPARENT_PEN);
                m_dcPlot->DrawRectangle(prevX - 1, m_rectPlot.bottom + 1, (currX + 5) - (prevX - 1), (m_rectClient.bottom + 1) - (m_rectPlot.bottom + 1));
            }
            // store the current point for connection to the next point
            m_PlotData[ iTrend ].dPreviousPosition = m_PlotData[ iTrend ].dCurrentPosition;
        }
    }
    // end DrawPoint:
}

/////////////////////////////////////////////////////////////////////////////
//UINT nType, int cx, int cy):
void COScopeCtrl::OnSize(wxSizeEvent &evt)
{
    int iTrend;
    //CWnd::OnSize(nType, cx, cy) ;
#if 0
    if (!theApp.xmuledlg)
    return;
    if (!theApp.xmuledlg->IsRunning())
    return;
#endif
    if (oldwidth == evt.GetSize() .GetWidth() &&oldheight == evt.GetSize() .GetHeight())
    {
        // same size. don't touch
        return;
    }
    oldwidth = evt.GetSize() .GetWidth();
    oldheight = evt.GetSize() .GetHeight();
    // NOTE: OnSize automatically gets called during the setup of the control
    wxRect myrect;
    myrect = GetClientRect();
    //GetClientRect(m_rectClient) ;
    m_rectClient.left = myrect.x;
    m_rectClient.top = myrect.y;
    m_rectClient.right = myrect.x + myrect.width;
    m_rectClient.bottom = myrect.y + myrect.height;
    //
    if (myrect.width < 1 || myrect.height < 1)
    return;
    // set some member variables to avoid multiple function calls
    //m_rectClient.Height() ;:
    m_nClientHeight = myrect.height;
    //m_rectClient.Width() ;:
    m_nClientWidth = myrect.width;
    // the "left" coordinate and "width" will be modified in
    // InvalidateCtrl to be based on the width of the y axis scaling
    m_rectPlot.left = 20;
    m_rectPlot.top = 10;
    m_rectPlot.right = m_rectClient.right - 10;
    m_rectPlot.bottom = m_rectClient.bottom - 25;
    // set some member variables to avoid multiple function calls
    //Height() ;:
    m_nPlotHeight = m_rectPlot.bottom - m_rectPlot.top;
    //Width() ;:
    m_nPlotWidth = m_rectPlot.right - m_rectPlot.left;
    // set the scaling factor for now, this can be adjusted
    // in the SetRange functions
    for (iTrend = 0 ; iTrend < m_NTrends ; iTrend++)
    {
        m_PlotData[ iTrend ].dVerticalFactor = (double) m_nPlotHeight / m_PlotData[ iTrend ].dRange;
    }
    // destroy grid dc and bitmap (Invalidate recreates it)
    if (m_dcGrid)
    {
        m_dcGrid->SelectObject(wxNullBitmap);
        if (m_bitmapGrid) delete m_bitmapGrid;
        delete m_dcGrid;
        m_bitmapGrid = NULL;
        m_dcGrid = NULL;
    }
    if (m_dcPlot)
    {
        m_dcPlot->SelectObject(wxNullBitmap);
        if (m_bitmapPlot) delete m_bitmapPlot;
        delete m_dcPlot;
        m_bitmapPlot = NULL;
        m_dcPlot = NULL;
    }
#if 0
    // destroy and recreate the grid bitmap
    CClientDC dc(this);
    if (m_pbitmapOldGrid &&m_bitmapGrid.GetSafeHandle() &&m_dcGrid.GetSafeHdc())
    {
        m_dcGrid.SelectObject(m_pbitmapOldGrid);
        m_bitmapGrid.DeleteObject();
        m_bitmapGrid.CreateCompatibleBitmap( &dc, m_nClientWidth, m_nClientHeight);
        m_pbitmapOldGrid = m_dcGrid.SelectObject( &m_bitmapGrid);
    }
    // destroy and recreate the plot bitmap
    if (m_pbitmapOldPlot &&m_bitmapPlot.GetSafeHandle() &&m_dcPlot.GetSafeHdc())
    {
        m_dcPlot.SelectObject(m_pbitmapOldPlot);
        m_bitmapPlot.DeleteObject();
        m_bitmapPlot.CreateCompatibleBitmap( &dc, m_nClientWidth, m_nClientHeight);
        m_pbitmapOldPlot = m_dcPlot.SelectObject( &m_bitmapPlot);
    }
    if (m_nPlotWidth > 0 &&m_nPlotHeight > 0)
    InvalidateCtrl();
#endif
    if (m_nPlotWidth > 0 &&m_nPlotHeight > 0)
    InvalidateCtrl();
    // OnSize:
}

/////////////////////////////////////////////////////////////////////////////
void COScopeCtrl::Reset()
{
    // to clear the existing data (in the form of a bitmap)
    // simply invalidate the entire control
    InvalidateCtrl();
}

int COScopeCtrl::ReCreateGraph(void)
{
    int i;
    for (i = 0 ; i < m_NTrends ; i++)
    {
        m_PlotData[i].dPreviousPosition = 0.0;
        m_PlotData[i].nPrevY = - 1;
    }
    double *pAddPoints = new double[m_NTrends];
    int iCnt = m_PlotData[0].lstPoints.GetCount();
    for (int i = 0 ; i < iCnt ; i++)
    {
        for (int iTrend = 0 ; iTrend < m_NTrends ; iTrend++)
        {
#if 0
            m_PlotData[iTrend].nPrevY = - 1;
            m_PlotData[iTrend].dPreviousPosition = 0.0;
#endif
            POSITION pos = m_PlotData[iTrend].lstPoints.FindIndex(i);
            if (pos)
            pAddPoints[iTrend] = m_PlotData[iTrend].lstPoints.GetAt(pos);
            else
            pAddPoints[iTrend] = 0;
        }
        AppendPoints(pAddPoints, false, false);
    }
    delete[] pAddPoints;
    return 0;
}

//void COScopeCtrl::OnTimer(UINT nIDEvent)
void COScopeCtrl::OnTimer(wxTimerEvent &evt)
{
    if (!theApp.xmuledlg->IsRunning())
    return;
    m_nRedrawTimer.Stop();
    m_bDoUpdate = true;
    ReCreateGraph();
#if 0
    if (nIDEvent == m_nRedrawTimer)
    {
        KillTimer(m_nRedrawTimer);
        m_nRedrawTimer = 0;
        m_bDoUpdate = true;
        ReCreateGraph();
    }
    CWnd::OnTimer(nIDEvent);
#endif
}

