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