Newer
Older
powermon_manager_sw / gui / scope_display.cpp
@Razvan Turiac Razvan Turiac on 8 Jul 33 KB Initial import
/* Copyright (C) 2020 - 2024, Thornwave Labs Inc
 * Written by Razvan Turiac <razvan.turiac@thornwave.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
 * documentation files (the “Software”), to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * Attribution shall be given to Thornwave Labs Inc. and shall be made visible to the final user. 
 * 
 * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <scope_display.h>

#include <algorithm>

#include <debug.h>

#include <math.h>

#include <wx/dcbuffer.h>
#include <wx/mstream.h>


#include <resources/img_arrow_left_png.cpp>
#include <resources/img_arrow_right_png.cpp>
#include <resources/img_arrow_up_png.cpp>
#include <resources/img_arrow_down_png.cpp>
#include <resources/img_arrow_left_double_png.cpp>
#include <resources/img_arrow_right_double_png.cpp>
#include <resources/img_plus_png.cpp>
#include <resources/img_minus_png.cpp>
#include <resources/img_home_png.cpp>


#define SCOPE_CONTROL_BORDER_SIZE		FromDIP(10)

#define SCOPE_TOP_PANEL_HEIGHT			FromDIP(50)
#define SCOPE_BOTTOM_PANEL_HEIGHT		FromDIP(50)
#define SCOPE_LEFT_PANEL_WIDTH			FromDIP(50)


#define SCOPE_LEFT_BORDER_SIZE			FromDIP(20)
#define SCOPE_RIGHT_BORDER_SIZE			FromDIP(20)
#define SCOPE_TOP_BORDER_SIZE			FromDIP(25)
#define SCOPE_BOTTOM_BORDER_SIZE		FromDIP(25)

#define SCOPE_VERTICAL_SCALE_WIDTH			FromDIP(50)

#define GRID_SPACING	FromDIP(60)
#define ARROW_BASE		FromDIP(10)
#define ARROW_HEIGHT	FromDIP(16)


//#define SCOPE_INTEGER_WAVEFORM_HEIGHT		20
//#define SCOPE_INTEGER_WAVEFORM_PITCH		50


BEGIN_EVENT_TABLE(ScopeDisplay, wxWindow)

EVT_KEY_DOWN(ScopeDisplay::OnKeyPressed)
EVT_KEY_UP(ScopeDisplay::OnKeyReleased)

END_EVENT_TABLE()


ScopeDisplay::ScopeDisplay(wxFrame* parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style): wxWindow(parent, id, pos, size, style | wxFULL_REPAINT_ON_RESIZE),
																			mListener(nullptr), mWaveRefresh(true)
{
	mWaveColors.push_back(WaveColor(wxColour(255, 0, 0)));
	mWaveColors.push_back(WaveColor(wxColour(255, 128, 0)));
	mWaveColors.push_back(WaveColor(wxColour(0, 192, 0)));
	mWaveColors.push_back(WaveColor(wxColour(0, 0, 255)));
	mWaveColors.push_back(WaveColor(wxColour(0, 192, 192)));
	mWaveColors.push_back(WaveColor(wxColour(192, 192, 0)));
	
	mCursorsEnable = false;
	mCursor1Dragging = false;
	mCursor2Dragging = false;

	SetBackgroundStyle(wxBG_STYLE_CUSTOM);

	mGridColor = wxColour(192, 192, 192);
	mScalesColor = wxColour(16, 128, 64);
	mCursor1Color = wxColour(0, 0, 255);
	mCursor2Color = wxColour(255, 0, 255);


	//default time scales
	mTimeScalesIndex = 0;
	mTimeScales.push_back(1);			//1us / div
	mTimeResolution = mTimeScales[mTimeScalesIndex];

	mMaxTime = 0;
	mTimePosition = 0;

	//default vertical scale
	mVerticalScale = 1;
	mVerticalScaleExponent = 0;
	mVertPosition = 0;
	

	mFontBig = wxFont(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_BOLD);
	mFontMed = wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_BOLD);
	mFontSmall = wxFont(8, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_BOLD);
	
	mScalesFont = wxFont(6, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);


	//create panels
	wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
	SetSizer(main_sizer);

	mTopPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize/*(0, SCOPE_TOP_PANEL_HEIGHT)*/, wxFULL_REPAINT_ON_RESIZE);
	main_sizer->Add(mTopPanel, 0, wxEXPAND);

	wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
	main_sizer->Add(sizer, 1, wxEXPAND);

	mLeftPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize/*(SCOPE_LEFT_PANEL_WIDTH, 0)*/, wxFULL_REPAINT_ON_RESIZE);
	sizer->Add(mLeftPanel, 0, wxEXPAND);
	
	mScopePanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
	mScopePanel->SetBackgroundStyle(wxBG_STYLE_PAINT);
	sizer->Add(mScopePanel, 1, wxEXPAND);
	
	mBottomPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize/*(0, SCOPE_BOTTOM_PANEL_HEIGHT)*/, wxFULL_REPAINT_ON_RESIZE);
	main_sizer->Add(mBottomPanel, 0, wxEXPAND);

	//create buttons
	mTimeScaleUpButton = new wxBitmapButton(mBottomPanel, ID_BUTTON_TIME_SCALE_UP, wxBITMAP_PNG_FROM_DATA(img_plus), wxDefaultPosition, FromDIP(wxSize(60, 30)));
	mTimeScaleDownButton = new wxBitmapButton(mBottomPanel, ID_BUTTON_TIME_SCALE_DOWN, wxBITMAP_PNG_FROM_DATA(img_minus), wxDefaultPosition, FromDIP(wxSize(60, 30)));
	mTimePosUpButton = new wxBitmapButton(mBottomPanel, ID_BUTTON_TIME_POS_UP, wxBITMAP_PNG_FROM_DATA(img_arrow_right), wxDefaultPosition, FromDIP(wxSize(60, 30)));
	mTimePosDownButton = new wxBitmapButton(mBottomPanel, ID_BUTTON_TIME_POS_DOWN, wxBITMAP_PNG_FROM_DATA(img_arrow_left), wxDefaultPosition, FromDIP(wxSize(60, 30)));
	
	mTimePosEndButton = new wxBitmapButton(mBottomPanel, ID_BUTTON_TIME_POS_END, wxBITMAP_PNG_FROM_DATA(img_arrow_right_double), wxDefaultPosition, FromDIP(wxSize(40, 30)));
	mTimePosHomeButton = new wxBitmapButton(mBottomPanel, ID_BUTTON_TIME_POS_HOME, wxBITMAP_PNG_FROM_DATA(img_arrow_left_double), wxDefaultPosition, FromDIP(wxSize(40, 30)));

	mVertScaleUpButton = new wxBitmapButton(mLeftPanel, ID_BUTTON_VERT_SCALE_UP, wxBITMAP_PNG_FROM_DATA(img_plus), wxDefaultPosition, FromDIP(wxSize(30, 60)));
	mVertScaleDownButton = new wxBitmapButton(mLeftPanel, ID_BUTTON_VERT_SCALE_DOWN, wxBITMAP_PNG_FROM_DATA(img_minus), wxDefaultPosition, FromDIP(wxSize(30, 60)));
	mVertPosUpButton = new wxBitmapButton(mLeftPanel, ID_BUTTON_VERT_POS_UP, wxBITMAP_PNG_FROM_DATA(img_arrow_up), wxDefaultPosition, FromDIP(wxSize(30, 60)));
	mVertPosDownButton = new wxBitmapButton(mLeftPanel, ID_BUTTON_VERT_POS_DOWN, wxBITMAP_PNG_FROM_DATA(img_arrow_down), wxDefaultPosition, FromDIP(wxSize(30, 60)));
	mVertPosHomeButton = new wxBitmapButton(mLeftPanel, ID_BUTTON_VERT_POS_HOME, wxBITMAP_PNG_FROM_DATA(img_home), wxDefaultPosition, FromDIP(wxSize(30, 40)));

	mClearScopeButton = new wxButton(mBottomPanel, ID_BUTTON_CLEAR_SCOPE, wxT("Clear"), wxDefaultPosition, FromDIP(wxSize(60, 30)));
	mSaveImageButton = new wxButton(mBottomPanel, ID_BUTTON_SAVE_IMAGE, wxT("Save Image"), wxDefaultPosition, FromDIP(wxSize(120, 30)));
	mCursorsButton = new wxToggleButton(mBottomPanel, ID_BUTTON_CURSORS, wxT("Cursors"), wxDefaultPosition, FromDIP(wxSize(80, 30)));
	
	mSaveDataButton = new wxButton(mBottomPanel, ID_BUTTON_SAVE_DATA, wxT("Save Data"), wxDefaultPosition, FromDIP(wxSize(120, 30)));
	mLoadDataButton = new wxButton(mBottomPanel, ID_BUTTON_LOAD_DATA, wxT("Load Data"), wxDefaultPosition, FromDIP(wxSize(120, 30)));


	main_sizer = new wxBoxSizer(wxHORIZONTAL);
	mBottomPanel->SetSizer(main_sizer);
	
	main_sizer->Add(mTimeScaleDownButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mTimeScaleUpButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);
	
	main_sizer->AddSpacer(SCOPE_CONTROL_BORDER_SIZE * 4);
	
	main_sizer->Add(mTimePosHomeButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mTimePosDownButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mTimePosUpButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mTimePosEndButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);

	main_sizer->AddStretchSpacer(1);

	main_sizer->Add(mCursorsButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mClearScopeButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mSaveImageButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mLoadDataButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mSaveDataButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);

	main_sizer->SetSizeHints(mBottomPanel);


	main_sizer = new wxBoxSizer(wxVERTICAL);
	mLeftPanel->SetSizer(main_sizer);
	
	main_sizer->Add(mVertScaleUpButton, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mVertScaleDownButton, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, SCOPE_CONTROL_BORDER_SIZE);
	
	main_sizer->AddSpacer(SCOPE_CONTROL_BORDER_SIZE * 4);
		
	main_sizer->Add(mVertPosUpButton, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mVertPosHomeButton, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, SCOPE_CONTROL_BORDER_SIZE);
	main_sizer->Add(mVertPosDownButton, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, SCOPE_CONTROL_BORDER_SIZE);
	
	main_sizer->SetSizeHints(mLeftPanel);


	mTopSizer = new wxBoxSizer(wxHORIZONTAL);
	mTopPanel->SetSizer(mTopSizer);
	mTopSizer->SetSizeHints(mTopPanel);

	GetSizer()->SetSizeHints(this);

	Centre();

	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonVertScaleUpClicked, this, ID_BUTTON_VERT_SCALE_UP);
	
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonVertScaleDownClicked, this, ID_BUTTON_VERT_SCALE_DOWN);
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonTimeScaleUpClicked, this, ID_BUTTON_TIME_SCALE_UP);
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonTimeScaleDownClicked, this, ID_BUTTON_TIME_SCALE_DOWN);
	
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonVertPosUpClicked, this, ID_BUTTON_VERT_POS_UP);
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonVertPosDownClicked, this, ID_BUTTON_VERT_POS_DOWN);
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonVertPosHomeClicked, this, ID_BUTTON_VERT_POS_HOME);
	
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonTimePosUpClicked, this, ID_BUTTON_TIME_POS_UP);
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonTimePosDownClicked, this, ID_BUTTON_TIME_POS_DOWN);
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonTimePosEndClicked, this, ID_BUTTON_TIME_POS_END);
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonTimePosHomeClicked, this, ID_BUTTON_TIME_POS_HOME);
	
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonClearScopeClicked, this, ID_BUTTON_CLEAR_SCOPE);
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonSaveImageClicked, this, ID_BUTTON_SAVE_IMAGE);
	
	Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &ScopeDisplay::OnButtonCursorsClicked, this, ID_BUTTON_CURSORS);


	mScopePanel->Bind(wxEVT_PAINT, &ScopeDisplay::OnPaint, this);
	mScopePanel->Bind(wxEVT_SIZE, &ScopeDisplay::OnSize, this);

	mScopePanel->Bind(wxEVT_LEFT_DOWN, &ScopeDisplay::OnMouseLeftDown, this);
	mScopePanel->Bind(wxEVT_LEFT_UP, &ScopeDisplay::OnMouseLeftUp, this);

	mScopePanel->Bind(wxEVT_RIGHT_DOWN, &ScopeDisplay::OnMouseRightDown, this);
	mScopePanel->Bind(wxEVT_RIGHT_UP, &ScopeDisplay::OnMouseRightUp, this);

	mScopePanel->Bind(wxEVT_MOTION, &ScopeDisplay::OnMouseMotion, this);
	mScopePanel->Bind(wxEVT_MOUSEWHEEL, &ScopeDisplay::OnMouseWheel, this);
}


ScopeDisplay::~ScopeDisplay()
{
}


void ScopeDisplay::addWaveform(const wxString &name, const wxString &scale_name, const wxString &unit, float resolution)
{
	//create the vertical scale
	VerticalScale* new_vs = new VerticalScale(scale_name, unit, resolution);
		
	//try to find a scale we can use
	auto found = std::find_if(mVerticalScaleList.begin(), mVerticalScaleList.end(), [new_vs](const VerticalScale* scale)
	{
		return *scale == *new_vs;
	});

	if (found == mVerticalScaleList.end())
	{
		mVerticalScaleList.push_back(new_vs);
		found = --mVerticalScaleList.end();
	}

	WaveformDescriptor desc(**found);

	desc.name = name;

	//find first unused color and assign it to the waveform
	desc.color = *wxBLACK;
	for(auto &wc: mWaveColors)
	{
		if (wc.in_use == false)
		{
			desc.color = wc.color;
			wc.in_use = true;
			break;
		}
	}

	wxString button_name = name;
	button_name.insert(0, wxT("  "));
	button_name.append(wxT("  "));
	desc.button = new wxButton(mTopPanel, wxID_ANY, button_name, wxDefaultPosition, wxSize(-1, 25), wxBU_EXACTFIT);
	desc.button->SetForegroundColour(desc.color);
	//desc.button->SetForegroundColour(*wxLIGHT_GREY);
	desc.button->SetFont(mFontMed);
	
	desc.id = desc.button->GetId();
	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonEnableWaveformClicked, this, desc.id);

	mTopSizer->Add(desc.button, 0, wxALL | wxALIGN_CENTER_VERTICAL, SCOPE_CONTROL_BORDER_SIZE);

	mWaveformDescriptorList.push_back(desc);			
	mDataVectors.push_back(DataVector());

	mTopSizer->SetSizeHints(mTopPanel);
	mTopSizer->Fit(mTopPanel);
	
	GetSizer()->Layout();
	
	Refresh();
}


void ScopeDisplay::clearWaveforms(void)
{
	for(auto &desc: mWaveformDescriptorList)
	{
		Unbind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeDisplay::OnButtonEnableWaveformClicked, this, desc.id);
		desc.button->Destroy();
	}

	mWaveformDescriptorList.clear();

	for(const auto sptr: mVerticalScaleList)
		delete sptr;
	mVerticalScaleList.clear();
	mDataVectors.clear();

	for(auto it = mWaveColors.begin(); it != mWaveColors.end(); ++it)
		it->in_use = false;

	mTopSizer->SetSizeHints(mTopPanel);

	mTopSizer->Fit(mTopPanel);
	
	GetSizer()->Layout();
	
	Refresh();
}


void ScopeDisplay::ChangeVerticalScale(bool up)
{
	const float min_scale = 0.005f;		//5mU / div
	const float max_scale = 5.0f;		//5U / div

	int32_t scale = mVerticalScale;
	int32_t exp = mVerticalScaleExponent;
	
	if (up)
	{
		switch(scale)
		{
			case 1: scale = 2; break;
			case 2: scale = 5; break;
			case 5: scale = 1; exp++; break;
			default: assert(0);
		}
	}
	else
	{
		switch(scale)
		{
			case 1: scale = 5; exp--; break;
			case 2: scale = 1; break;
			case 5: scale = 2; break;
			default: assert(0);
		}
	}

	
	//validate new scale is within range
	float final_scale = scale * pow(10, exp);
	if ((final_scale >= min_scale) && (final_scale <= max_scale)) 
	{
		mVerticalScale = scale;
		mVerticalScaleExponent = exp;
	}


	mScopePanel->Refresh();
}




void ScopeDisplay::AdvanceTime(uint64_t time)
{
	mMaxTime += time;
}


void ScopeDisplay::SetTimeScales(const std::vector<uint64_t> &scales, size_t default_scale_index)
{
	mTimeScales = scales;
	if (default_scale_index >= mTimeScales.size())
		default_scale_index = mTimeScales.size() - 1;

	mTimeScalesIndex = default_scale_index;
	mTimeResolution = mTimeScales[mTimeScalesIndex];
}


void ScopeDisplay::ChangeTimeScale(bool up)
{
	if (up)
	{
		if (mTimeScalesIndex < (mTimeScales.size() - 1))
		{
			mTimeScalesIndex++;
			mTimeResolution = mTimeScales[mTimeScalesIndex];
		}
	}
	else
	{
		if (mTimeScalesIndex > 0)
		{
			mTimeScalesIndex--;
			mTimeResolution = mTimeScales[mTimeScalesIndex];
		}
	}

	mTimePosition -= mTimePosition % mTimeResolution;

	mWaveRefresh = true;
	mScopePanel->Refresh();
}



void ScopeDisplay::HomeTimePos(void)
{
	mTimePosition = mMinTime;
	mWaveRefresh = true;

	mScopePanel->Refresh();
}


void ScopeDisplay::UpdateScope(void)
{
	EndTimePos();
}


void ScopeDisplay::EndTimePos(void)
{
	mTimePosition = mMaxTime - mCurrentTimeSpan;
	if (mTimePosition < mMinTime)
		mTimePosition = mMinTime;

	mWaveRefresh = true;
	mScopePanel->Refresh();
}



void ScopeDisplay::ChangeVerticalPos(bool up, uint32_t increment)
{
	if (up)
		mVertPosition += increment;
	else
		mVertPosition -= increment;

	mVertPosition -= mVertPosition % (int32_t)increment;
	
	mScopePanel->Refresh();
}



void ScopeDisplay::HomeVerticalPos(void)
{
	mVertPosition = 0;
	mScopePanel->Refresh();
}


void ScopeDisplay::ChangeTimePos(bool up, uint32_t increment)
{
	uint64_t step = mTimeResolution;

	if (increment)
		step = (step * increment) / GRID_SPACING;
	if (step == 0)
		step = 1;
	
	if (up)
	{
			mTimePosition += step;
	}
	else if (mTimePosition >= step)
	{
		mTimePosition -= step;
	}

	mWaveRefresh = true;
	mScopePanel->Refresh();
}




void ScopeDisplay::OnPaint(wxPaintEvent &event)
{
	wxAutoBufferedPaintDC dc(mScopePanel);
	render(dc);
}


void ScopeDisplay::OnSize(wxSizeEvent &event)
{
	mWaveRefresh = true;
	//wxSize size = event.GetSize();
	//wxLogMessage(wxT("size: %d %d"), size.GetWidth(), size.GetHeight());
	
//	mTopPanel->SetSize(wxRect(0, 0, size.x, SCOPE_TOP_PANEL_HEIGHT));
//	mBottomPanel->SetSize(wxRect(0, size.y - SCOPE_BOTTOM_PANEL_HEIGHT, size.x, SCOPE_BOTTOM_PANEL_HEIGHT));
//	mLeftPanel->SetSize(wxRect(0, SCOPE_TOP_PANEL_HEIGHT, SCOPE_LEFT_PANEL_WIDTH, size.y - SCOPE_TOP_PANEL_HEIGHT - SCOPE_BOTTOM_PANEL_HEIGHT));
}


void ScopeDisplay::render(wxDC &dc)
{
	int32_t x, y1, y2;

	const wxSize scope_size = mScopePanel->GetSize();
	
	//clear the background
	dc.SetPen(wxPen(*wxWHITE, 1));
	dc.SetBrush(wxBrush(*wxWHITE));
	dc.DrawRectangle(wxPoint(0, 0), scope_size);
	
	mGraphTop = SCOPE_TOP_BORDER_SIZE;
	mGraphLeft = SCOPE_LEFT_BORDER_SIZE + SCOPE_VERTICAL_SCALE_WIDTH * mVerticalScaleList.size();
	
	//draw grids	
	dc.SetTextForeground(mScalesColor);
	dc.SetFont(mScalesFont);

	mHGridDivisions = (scope_size.y - SCOPE_BOTTOM_BORDER_SIZE - SCOPE_TOP_BORDER_SIZE) / GRID_SPACING;
	mVGridDivisions = (scope_size.x - SCOPE_RIGHT_BORDER_SIZE - SCOPE_LEFT_BORDER_SIZE - SCOPE_VERTICAL_SCALE_WIDTH * mVerticalScaleList.size()) / GRID_SPACING;

	mCurrentTimeSpan = mTimeResolution * mVGridDivisions;		//in us

	if (mTimePosition > (mMaxTime - mCurrentTimeSpan))
		mTimePosition = mMaxTime - mCurrentTimeSpan;

	mGraphBottom = mGraphTop + GRID_SPACING * mHGridDivisions;
	mGraphRight = mGraphLeft + GRID_SPACING * mVGridDivisions;
	const int32_t graph_height = mGraphBottom - mGraphTop;

	dc.SetPen(wxPen(mGridColor, 1, wxPENSTYLE_DOT));
	int32_t h_grid = mGraphBottom - GRID_SPACING;
	for(int32_t i = 1; i <= mHGridDivisions; i++)
	{
		dc.DrawLine(wxPoint(mGraphLeft, h_grid), wxPoint(mGraphRight, h_grid));
		h_grid -= GRID_SPACING;
	}


	int32_t v_grid = mGraphLeft;
	uint64_t time_grid_value = mTimePosition;
	for(int32_t i = 0; i <= mVGridDivisions; i++)
	{
		dc.SetPen(wxPen(mGridColor, 1, wxPENSTYLE_DOT));
		dc.DrawLine(wxPoint(v_grid, mGraphBottom), wxPoint(v_grid, mGraphTop));
		
		dc.SetPen(wxPen(mScalesColor, 1));
		dc.DrawLine(wxPoint(v_grid, mGraphBottom - 3), wxPoint(v_grid, mGraphBottom + 4));

		const wxString text = PrintTime(time_grid_value);
		const wxSize ts = dc.GetTextExtent(text);

		time_grid_value += mTimeResolution;
		
		dc.DrawText(text, v_grid - (ts.x / 2), mGraphBottom + 4);

		v_grid += GRID_SPACING;
	}
	

	//draw waveforms
	if (mWaveRefresh && mListener)
	{
		mWaveRefresh = false;
		mListener->getChartData(mTimePosition, mTimePosition + mCurrentTimeSpan, mGraphRight - mGraphLeft, mDataVectors);
	}

	dc.SetFont(mScalesFont);
	size_t i = 0;
	for (auto &wd: mWaveformDescriptorList)
	{
		if (wd.enabled)
		{
			wd.button->SetForegroundColour(wd.color);
			dc.SetPen(wxPen(wd.color, 2));
			dc.SetTextForeground(wd.color);

			DrawAnalogWaveform(dc, mDataVectors[i], wd, mGraphLeft, mGraphBottom, mGraphRight, mGraphTop);
		}
		else
		{
			wd.button->SetForegroundColour(*wxLIGHT_GREY);
		}

		i++;
	}

	//draw vertical axes
	dc.SetBrush(wxBrush(*wxBLACK));

	int32_t scale_x = SCOPE_LEFT_BORDER_SIZE;
	for(auto scale: mVerticalScaleList)
	{
		scale_x += SCOPE_VERTICAL_SCALE_WIDTH;

		//print scale
		dc.SetPen(wxPen(mScalesColor, 1));
		dc.SetFont(mScalesFont);
		dc.SetTextForeground(mScalesColor);
		int32_t h_grid = mGraphBottom;
		float scale_value = (mVertPosition / (float)GRID_SPACING) * scale->resolution * mVerticalScale * pow(10, mVerticalScaleExponent);

		for(int32_t i = 0; i < mHGridDivisions + 1; i++)
		{
			const wxString text = PrintValue(scale_value, scale->unit);
			const wxSize ts = dc.GetTextExtent(text);

			dc.SetPen(wxPen(*wxBLACK, 2));
			dc.DrawLine(wxPoint(scale_x - 3, h_grid), wxPoint(scale_x + 3, h_grid));			
			dc.DrawText(text, scale_x - ts.x - 5, h_grid - ts.y / 2);
			
			h_grid -= GRID_SPACING;
			scale_value += scale->resolution * mVerticalScale * pow(10, mVerticalScaleExponent);
		}

		//draw axis
		dc.SetPen(wxPen(*wxBLACK, 2));
		dc.DrawLine(wxPoint(scale_x, mGraphBottom), wxPoint(scale_x, mGraphTop - 4));
		DrawArrow(dc, wxPoint(scale_x, mGraphTop - 20), 0, ARROW_HEIGHT, ARROW_BASE);

		//print axis name
		dc.SetFont(mFontBig);
		dc.SetTextForeground(*wxBLACK);
		const wxSize ts = dc.GetTextExtent(scale->name);
		dc.DrawText(scale->name, scale_x - ts.x - ARROW_BASE / 2 - 2, 0);
	}

	//draw time axis
	dc.SetPen(wxPen(*wxBLACK, 2));
	dc.SetBrush(wxBrush(*wxBLACK));
	dc.DrawLine(wxPoint(mGraphLeft, mGraphBottom), wxPoint(mGraphRight + 4, mGraphBottom));
	
	dc.SetTextBackground(*wxWHITE);
	dc.SetTextForeground(*wxBLACK);
	dc.SetFont(mFontBig);
	DrawArrow(dc, wxPoint(mGraphRight + 20, mGraphBottom), 3, ARROW_HEIGHT, ARROW_BASE);
	dc.DrawText(wxT("t"), scope_size.x - SCOPE_RIGHT_BORDER_SIZE + 2, mGraphBottom - ARROW_BASE / 2 - dc.GetCharHeight());

	dc.DrawText(wxT("Time Scale:"), SCOPE_LEFT_BORDER_SIZE, scope_size.y - SCOPE_BOTTOM_BORDER_SIZE);
	const wxSize ts = dc.GetTextExtent(wxT("Time Scale:"));
	
	const wxString text = PrintTimeResolution(mTimeResolution);
	dc.SetTextForeground(*wxRED);
	dc.DrawText(text, SCOPE_LEFT_BORDER_SIZE + ts.x + dc.GetCharWidth(), scope_size.y - SCOPE_BOTTOM_BORDER_SIZE);

	if (mCursorsEnable)
	{
		dc.SetTextBackground(*wxWHITE);
		dc.SetFont(mFontSmall);
		
		dc.SetTextForeground(*wxBLACK);
		dc.DrawText(wxT("Cursors"), 40, mGraphTop - 3 * dc.GetCharHeight());
		
		//draw cursor 1
		dc.SetTextForeground(mCursor1Color);
		dc.SetPen(wxPen(mCursor1Color, 1));
		
		dc.DrawLine(wxPoint(mCursor1Pos.x, mGraphTop), wxPoint(mCursor1Pos.x, mGraphBottom));

		//draw cursor 2
		dc.SetTextForeground(mCursor2Color);
		dc.SetPen(wxPen(mCursor2Color, 1));
		
		dc.DrawLine(wxPoint(mCursor2Pos.x, mGraphTop), wxPoint(mCursor2Pos.x, mGraphBottom));
		
		const int64_t time1 = (((mCursor1Pos.x - mGraphLeft) * (uint64_t)mTimeResolution) / GRID_SPACING) + mTimePosition;
		const int64_t time2 = (((mCursor2Pos.x - mGraphLeft) * (uint64_t)mTimeResolution) / GRID_SPACING) + mTimePosition;
		

		//draw cursor value text		
		wxString text = PrintTime(time1);
		dc.SetTextForeground(mCursor1Color);
		wxSize ts = dc.GetTextExtent(text);

		y1 = mGraphBottom - ts.y - 10;

		if ((mGraphRight - mCursor1Pos.x) > ts.x + 40)
			x = mCursor1Pos.x + 10;
		else
			x = mCursor1Pos.x - ts.x - 10;
		
		dc.DrawText(text, x, y1);
		
		
		text = PrintTime(time2);
		dc.SetTextForeground(mCursor2Color);
		ts = dc.GetTextExtent(text);

		y2 = mGraphBottom - ts.GetHeight() - 10;

		if ((mGraphRight - mCursor2Pos.x) > ts.x + 40)
			x = mCursor2Pos.x + 10;
		else
			x = mCursor2Pos.x - ts.x - 10;
		dc.DrawText(text, x, y2);
		

		dc.SetClippingRegion(mGraphLeft - 1, mGraphTop - 1, mGraphRight - mGraphLeft + 1, graph_height + 1);
		
		dc.SetPen(wxNullPen);
		int32_t text_pos1_y = mGraphTop;
		int32_t text_pos2_y = mGraphTop;

		const int32_t x1 = mCursor1Pos.x - mGraphLeft;
		const int32_t x2 = mCursor2Pos.x - mGraphLeft;

		for(uint32_t i = 0; i < mWaveformDescriptorList.size(); i++)
		{
			const auto &wd = mWaveformDescriptorList[i];

			if (wd.enabled)
			{
				const float v1 = mDataVectors[i][x1];
				const float v2 = mDataVectors[i][x2];

				const float vertical_resolution = wd.scale.resolution * mVerticalScale * pow(10, mVerticalScaleExponent);
				const float vertical_reference = mVertPosition * vertical_resolution / GRID_SPACING;
				const float vertical_span = (graph_height / GRID_SPACING) * vertical_resolution;

				dc.SetBrush(wxBrush(wd.color));
				dc.SetTextForeground(wd.color);

				if (!isnan(v1))
				{
					const int32_t y1 = ((v1 - vertical_reference) / vertical_span) * graph_height;
					dc.DrawCircle(mCursor1Pos.x, mGraphBottom - y1, 4);

					const wxString text = PrintValue(v1, wd.scale.unit);
					dc.DrawText(wd.name + wxT(": ") + text, mCursor1Pos.x + 10, text_pos1_y);
					text_pos1_y += dc.GetCharHeight();
				}

				if (!isnan(v2))
				{
					const int32_t y2 = ((v2 - vertical_reference) / vertical_span) * graph_height;
					dc.DrawCircle(mCursor2Pos.x, mGraphBottom - y2, 4);

					const wxString text = PrintValue(v2, wd.scale.unit);
					dc.DrawText(wd.name + wxT(": ") + text, mCursor2Pos.x + 10, text_pos2_y);
					text_pos2_y += dc.GetCharHeight();
				}
			}
		}
		dc.DestroyClippingRegion();
	}
}


void ScopeDisplay::DrawAnalogWaveform(wxDC &dc, const std::vector<float> &wave, const WaveformDescriptor &wd, int32_t x0, int32_t y0, int32_t x1, int32_t y1)
{
	const int32_t screen_size_x = ((x1 - x0) < wave.size()) ? (x1 - x0) : wave.size();
	if (screen_size_x == 0)
		return;
	
	const int32_t screen_size_y = y0 - y1; 

	dc.SetClippingRegion(x0 - 1, y1 - 1, screen_size_x + 1, screen_size_y + 1);
	
	const float vertical_resolution = wd.scale.resolution * mVerticalScale * pow(10, mVerticalScaleExponent);
	const float vertical_reference = mVertPosition * vertical_resolution / GRID_SPACING;
	const float vertical_span = (screen_size_y / GRID_SPACING) * vertical_resolution;

	const float* vptr = wave.data();

	float left_val, right_val;
	
	int32_t left_x, left_y;
	int32_t right_y;

	left_val = *vptr++;
	left_x = 0;
	left_y = ((left_val - vertical_reference) / vertical_span) * screen_size_y;

	if (left_val == left_val)
		dc.DrawPoint(x0 + left_x, y0 - left_y);

	for(int32_t right_x = 1; right_x < screen_size_x; right_x++)
	{
		right_val = *vptr++;

		right_y = ((right_val - vertical_reference) / vertical_span) * screen_size_y;

		if ((right_val == right_val) && (left_val == left_val))
			dc.DrawLine(x0 + left_x, y0 - left_y, x0 + right_x, y0 - right_y);
			
		left_y = right_y;
		left_x = right_x;

		left_val = right_val;
	}

	dc.DestroyClippingRegion();
}


	
/*void ScopeDisplay::DrawIntegerWaveform(wxDC &dc, IntegerWaveform &wave, int32_t x0, int32_t y0, int32_t x1, int32_t y1)
{

	const int32_t screen_size_x = x1 - x0;
	const int32_t screen_size_y = y1 - y0;

	const uint64_t time_per_pixel = mTimeResolution / GRID_SPACING;

	
	if ((wave.ypos + SCOPE_INTEGER_WAVEFORM_PITCH) > screen_size_y)
		return;

	int32_t max_x = x0 + (mMaxTime - mTimePosition) / time_per_pixel;
	if (max_x >= screen_size_x)
		max_x = screen_size_x - 1;
	
	if (!wave.data->seek(0))
		return;


	//search for the first value change in the screen
	ValueChange vc;
	uint32_t prev_val = 0;
	//move to the first change in the time window
	while(wave.data->pop(vc))
	{
		if (vc.time >= mTimePosition)
		{
			if (mTimePosition == 0)
				prev_val = vc.val;

			wave.data->advance(-1);	//move back one - this is the first value to be displayed
			break;
		}
		prev_val = vc.val;
	}

	const int32_t text_ypos = ((SCOPE_INTEGER_WAVEFORM_HEIGHT + dc.GetCharHeight()) / 2) + wave.ypos - 1;

	int32_t prev_x = x0;

	if (wave.data->pop(vc))
	{
		do
		{
			int32_t vc_x = x0 + (vc.time - mTimePosition) / time_per_pixel;
			if (vc_x >= x1)
				break;
	
			//draw the cross
			if (vc_x == x0)
			{
				if (mTimePosition)
				{
					prev_x = x0 + 5;
					dc.DrawLine(vc_x, wave.ypos + SCOPE_INTEGER_WAVEFORM_HEIGHT / 2, prev_x, wave.ypos + SCOPE_INTEGER_WAVEFORM_HEIGHT);
					dc.DrawLine(vc_x, wave.ypos + SCOPE_INTEGER_WAVEFORM_HEIGHT / 2, prev_x, wave.ypos);				
				}
			
				prev_val = vc.val;
			}
			else
			{
				if ((vc_x - 5) > prev_x)
				{
					dc.DrawLine(prev_x, wave.ypos, vc_x - 5, wave.ypos);
					dc.DrawLine(prev_x, wave.ypos + SCOPE_INTEGER_WAVEFORM_HEIGHT, vc_x - 5, wave.ypos + SCOPE_INTEGER_WAVEFORM_HEIGHT);		
				}

				if ((vc_x - prev_x - 5) > 25)
				{
					wxString text;
					text.Printf(wxT("0x%X"), prev_val);
					dc.DrawText(text, prev_x, text_ypos);
				}
				
				dc.DrawLine(vc_x - 5, wave.ypos, vc_x + 5, wave.ypos + SCOPE_INTEGER_WAVEFORM_HEIGHT);
				dc.DrawLine(vc_x - 5, wave.ypos + SCOPE_INTEGER_WAVEFORM_HEIGHT, vc_x + 5, wave.ypos);

				prev_x = vc_x + 5;			
				prev_val = vc.val;
			}
		}while(wave.data->pop(vc));
	}

	if (max_x > prev_x)
	{
		dc.DrawLine(prev_x, wave.ypos, max_x, wave.ypos);
		dc.DrawLine(prev_x, wave.ypos + SCOPE_INTEGER_WAVEFORM_HEIGHT, max_x, wave.ypos + SCOPE_INTEGER_WAVEFORM_HEIGHT);		
	}
	if ((max_x - prev_x - 5) > 25)
	{
		wxString text;
		text.Printf(wxT("0x%X"), prev_val);
		dc.DrawText(text, prev_x, text_ypos);
	}
	
	
}
*/


void ScopeDisplay::DrawArrow(wxDC &dc, const wxPoint &pos, uint8_t dir, int32_t height, int32_t base)
{
	wxPoint points[3];
	
	points[0] = wxPoint(0, 0);

	switch(dir)
	{
		case 0:		//up
			points[1] = wxPoint(-base / 2, height);
			points[2] = wxPoint(base / 2, height);
			break;
			
		case 1:		//left
			points[1] = wxPoint(height, -base / 2);
			points[2] = wxPoint(height, base / 2);
			break;
			
		case 2:		//down
			points[1] = wxPoint(-base / 2, -height);
			points[2] = wxPoint(base / 2, -height);
			break;
			
		case 3:		//right
			points[1] = wxPoint(-height, -base / 2);
			points[2] = wxPoint(-height, base / 2);
			break;
	}

	dc.DrawPolygon(3, points, pos.x, pos.y);
}



wxBitmap* ScopeDisplay::SaveImage(void)
{
//TODO 
	wxBitmap* image = new wxBitmap(1200, 800);

	wxMemoryDC dc;
	
	dc.SelectObject(*image);

	render(dc);

	return image;
}



void ScopeDisplay::OnKeyPressed(wxKeyEvent &event)
{

}


void ScopeDisplay::OnKeyReleased(wxKeyEvent &event)
{

}


void ScopeDisplay::OnMouseWheel(wxMouseEvent &event)
{
	int32_t dir = event.GetWheelRotation() / event.GetWheelDelta();
	uint32_t step = 1;
	if (event.ShiftDown())
		step = 10;

	const int32_t mouse_x = event.GetPosition().x - SCOPE_LEFT_BORDER_SIZE;
	const uint32_t index = mouse_x / SCOPE_VERTICAL_SCALE_WIDTH;

	if (index < mVerticalScaleList.size())
	{
		if (dir > 0)
			mVerticalScaleList[index]->resolution *= 2;
		else if (dir < 0)
			mVerticalScaleList[index]->resolution /= 2;
		
		mScopePanel->Refresh();
	}
	else
	{
		if (event.AltDown())
		{
			if (dir > 0)
				ChangeTimePos(true, step);
			else if (dir < 0)
				ChangeTimePos(false, step);	
		}
		else
		{
			if (dir > 0)
				ChangeVerticalPos(true, step);
			else if (dir < 0)
				ChangeVerticalPos(false, step);
		}
	}
}



void ScopeDisplay::OnMouseLeftDown(wxMouseEvent &event)
{
	if (mCursor1Dragging == false)
	{
		mScopePanel->CaptureMouse();
		mCursor1Dragging = true;
	}
}


void ScopeDisplay::OnMouseLeftUp(wxMouseEvent &event)
{
	if (mCursor1Dragging == true)
	{
		mScopePanel->ReleaseMouse();
		mCursor1Dragging = false;
	}
}


void ScopeDisplay::OnMouseRightDown(wxMouseEvent &event)
{
	if (mCursor2Dragging == false)
	{
		mScopePanel->CaptureMouse();
		mCursor2Dragging = true;
	}
}


void ScopeDisplay::OnMouseRightUp(wxMouseEvent &event)
{
	if (mCursor2Dragging == true)
	{
		mScopePanel->ReleaseMouse();
		mCursor2Dragging = false;
	}
}


void ScopeDisplay::OnMouseLeaveWindow(wxMouseEvent &event)
{

}


void ScopeDisplay::OnMouseMotion(wxMouseEvent &event)
{
	wxPoint pos = event.GetPosition();
	
	if (pos.x< mGraphLeft)
		pos.x = mGraphLeft;
	else if (pos.x > mGraphRight)
		pos.x = mGraphRight;

	if (pos.y < mGraphTop)
		pos.y = mGraphTop;
	else if (pos.y > mGraphBottom)
		pos.y = mGraphBottom;

	if (mCursor1Dragging)
	{
		mCursor1Pos = pos;
		mScopePanel->Refresh();
	}
	else if (mCursor2Dragging)
	{
		mCursor2Pos = pos;
		mScopePanel->Refresh();
	}
}


void ScopeDisplay::OnButtonVertScaleUpClicked(wxCommandEvent &event)
{
	ChangeVerticalScale(true);
}


void ScopeDisplay::OnButtonVertScaleDownClicked(wxCommandEvent &event)
{
	ChangeVerticalScale(false);
}


void ScopeDisplay::OnButtonTimeScaleUpClicked(wxCommandEvent &event)
{
	ChangeTimeScale(true);
}


void ScopeDisplay::OnButtonTimeScaleDownClicked(wxCommandEvent &event)
{
	ChangeTimeScale(false);
}


void ScopeDisplay::OnButtonVertPosUpClicked(wxCommandEvent &event)
{
	ChangeVerticalPos(true, GRID_SPACING);
}


void ScopeDisplay::OnButtonVertPosDownClicked(wxCommandEvent &event)
{
	ChangeVerticalPos(false, GRID_SPACING);
}


void ScopeDisplay::OnButtonVertPosHomeClicked(wxCommandEvent &event)
{
	HomeVerticalPos();
}


void ScopeDisplay::OnButtonTimePosUpClicked(wxCommandEvent &event)
{
	ChangeTimePos(true, 0);
}


void ScopeDisplay::OnButtonTimePosDownClicked(wxCommandEvent &event)
{
	ChangeTimePos(false, 0);
}


void ScopeDisplay::OnButtonTimePosEndClicked(wxCommandEvent &event)
{
	EndTimePos();
}


void ScopeDisplay::OnButtonTimePosHomeClicked(wxCommandEvent &event)
{
	HomeTimePos();
}


void ScopeDisplay::OnButtonClearScopeClicked(wxCommandEvent &event)
{
/*	for(auto it = mWaveforms->begin(); it != mWaveforms->end(); ++it)
		it->data.clear();*/
		
	/*for(IntegerWaveformList::iterator it = mIntegerWaveforms.begin(); it != mIntegerWaveforms.end(); ++it)
		it->data->clear();*/
		
	mScopePanel->Refresh();
}


void ScopeDisplay::OnButtonCursorsClicked(wxCommandEvent &event)
{
	CursorsEnable(mCursorsButton->GetValue());
}



void ScopeDisplay::OnButtonSaveImageClicked(wxCommandEvent &event)
{
//TODO bug
	wxFileDialog *dlg = new wxFileDialog(this, wxT("Choose File to Save Image To"), wxString(), wxString(), wxT("PNG Image File (*.png)|*.png"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
	
	if (dlg->ShowModal() == wxID_OK)
	{
		wxString file = dlg->GetPath();
		
		wxBitmap *image = SaveImage();

		image->SaveFile(file, wxBITMAP_TYPE_PNG, 0);
		
		delete image;
	}

	dlg->Destroy();
}


void ScopeDisplay::OnButtonEnableWaveformClicked(wxCommandEvent &event)
{
	const int32_t id = event.GetId();
	
	for(auto &wd: mWaveformDescriptorList)
	{
		if (wd.id == id)
		{
			if (wd.enabled)
			{
				wd.enabled = false;
				wd.button->SetForegroundColour(*wxLIGHT_GREY);
			}
			else
			{
				wd.enabled = true;
				wd.button->SetForegroundColour(wd.color);
			}
		}
	}

	mScopePanel->Refresh();
}