/* 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();
}