/* 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. */ #ifndef _SCOPE_DISPLAY_H #define _SCOPE_DISPLAY_H #include <wx/wx.h> #include <stdint.h> #include <vector> #include <list> #include <wx/tglbtn.h> typedef std::vector<float> DataVector; class ScopeDisplay: public wxWindow { public: class Listener { public: virtual void getChartData(uint64_t start_time, uint64_t last_time, size_t point_count, std::vector<DataVector> &data) = 0; }; ScopeDisplay(wxFrame* parent, wxWindowID id, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = 0); ~ScopeDisplay(); void setListener(Listener* listener) { mListener = listener; } void addWaveform(const wxString &name, const wxString &scale_name, const wxString &unit, float resolution); void clearWaveforms(void); void UpdateScope(void); wxBitmap* SaveImage(void); void ChangeVerticalScale(bool up); void ChangeTimeScale(bool up); void ChangeVerticalPos(bool up, uint32_t increment); void HomeVerticalPos(void); void SetTimeScales(const std::vector<uint64_t> &scales, size_t default_scale_index); void ChangeTimePos(bool up, uint32_t increment); //0 means a full division while, >0 means number of pixels on screen void HomeTimePos(void); void EndTimePos(void); void AdvanceTime(uint64_t time); uint64_t GetTime(void) const { return mMaxTime; } void setMinTime(uint64_t time) { mMinTime = time; } void setMaxTime(uint64_t time) { mMaxTime = time; } void UpdateWaveforms(void) { EndTimePos(); Refresh(); Update(); } void CursorsEnable(bool enable) { mCursorsEnable = enable; if (mCursorsEnable) { mCursor1Pos.x = (mGraphLeft * 2 + mGraphRight) / 3; mCursor2Pos.x = (mGraphLeft + mGraphRight * 2) / 3; } Refresh(); Update(); } wxWindowID SaveDataButtonID(void) const { return ID_BUTTON_SAVE_DATA; } wxWindowID LoadDataButtonID(void) const { return ID_BUTTON_LOAD_DATA; } void OnKeyPressed(wxKeyEvent &event); void OnKeyReleased(wxKeyEvent &event); DECLARE_EVENT_TABLE() private: enum { ID_BUTTON_VERT_SCALE_UP = wxID_HIGHEST + 1, ID_BUTTON_VERT_SCALE_DOWN, ID_BUTTON_VERT_POS_UP, ID_BUTTON_VERT_POS_DOWN, ID_BUTTON_VERT_POS_HOME, ID_BUTTON_TIME_SCALE_UP, ID_BUTTON_TIME_SCALE_DOWN, ID_BUTTON_TIME_POS_END, ID_BUTTON_TIME_POS_UP, ID_BUTTON_TIME_POS_DOWN, ID_BUTTON_TIME_POS_HOME, ID_BUTTON_CLEAR_SCOPE, ID_BUTTON_SAVE_IMAGE, ID_BUTTON_CURSORS, ID_BUTTON_SAVE_DATA, ID_BUTTON_LOAD_DATA }; class VerticalScale { public: VerticalScale(const wxString &init_name, const wxString &init_unit, float init_resolution): name(init_name), unit(init_unit), resolution(init_resolution) { } bool operator==(const VerticalScale &rhs) const { return name == rhs.name; } wxString name; wxString unit; float resolution; //vertical resolution (units / division) }; class WaveformDescriptor { public: WaveformDescriptor(VerticalScale &init_scale): scale(init_scale), button(nullptr), enabled(true), id(wxID_ANY) { } VerticalScale &scale; wxString name; wxColor color; wxButton* button; bool enabled; int32_t id; }; class WaveColor { public: WaveColor(wxColour c): color(c), in_use(false) { } wxColour color; bool in_use; }; Listener* mListener; std::vector<VerticalScale*> mVerticalScaleList; std::vector<WaveformDescriptor> mWaveformDescriptorList; std::vector<DataVector> mDataVectors; bool mWaveRefresh; int32_t mGraphTop; int32_t mGraphBottom; int32_t mGraphLeft; int32_t mGraphRight; wxColor mGridColor; wxColor mScalesColor; wxColor mCursor1Color; wxColor mCursor2Color; std::vector<WaveColor> mWaveColors; wxFont mFontBig; wxFont mFontMed; wxFont mFontSmall; wxFont mScalesFont; int32_t mVGridDivisions; int32_t mHGridDivisions; //time scale size_t mTimeScalesIndex; std::vector<uint64_t> mTimeScales; uint64_t mTimeResolution; //micro-seconds per division - horizontal resolution uint64_t mTimePosition; //in us uint64_t mMinTime; //in us uint64_t mMaxTime; //in us int32_t mVertPosition; //in divisions //this gets multiplied with the resolution int32_t mVerticalScale; int32_t mVerticalScaleExponent; uint64_t mCurrentTimeSpan; //in divisons on screen uint32_t mCurrentVertSpan; //in divisions float mSamplesOnScreen[4096]; bool mCursorsEnable; wxPoint mCursor1Pos; wxPoint mCursor2Pos; bool mCursor1Dragging; bool mCursor2Dragging; uint32_t mWaveStreamSamplePeriod; //in us uint32_t mWaveStreamVerticalOffset; wxPanel* mTopPanel; wxPanel* mBottomPanel; wxPanel* mLeftPanel; wxPanel* mScopePanel; wxBoxSizer* mTopSizer; wxBitmapButton* mTimeScaleUpButton; wxBitmapButton* mTimeScaleDownButton; wxBitmapButton* mTimePosUpButton; wxBitmapButton* mTimePosDownButton; wxBitmapButton* mTimePosEndButton; wxBitmapButton* mTimePosHomeButton; wxBitmapButton* mVertScaleUpButton; wxBitmapButton* mVertScaleDownButton; wxBitmapButton* mVertPosUpButton; wxBitmapButton* mVertPosDownButton; wxBitmapButton* mVertPosHomeButton; wxButton* mClearScopeButton; wxButton* mSaveImageButton; wxToggleButton* mCursorsButton; wxButton* mSaveDataButton; wxButton* mLoadDataButton; inline wxString PrintTimeResolution(uint64_t us) { uint64_t scale = 1; if (us < (scale * 1000)) { return wxString::Format(wxT("%lluus / div"), us / scale); } scale *= 1000; if (us < (scale * 1000)) { return wxString::Format(wxT("%llums / div"), us / scale); } scale *= 1000; if (us < (scale * 60)) { return wxString::Format(wxT("%llus / div"), us / scale); } scale *= 60; if (us < (scale * 60)) { return wxString::Format(wxT("%llum / div"), us / scale); } scale *= 60; if (us < (scale * 24)) { return wxString::Format(wxT("%lluh / div"), us / scale); } scale *= 24; return wxString::Format(wxT("%llud / div"), us / scale); } inline wxString PrintTime(int64_t us) { wxDateTime dt; dt.Set((time_t)us / 1000000); dt.MakeUTC(); return dt.Format(wxT("%I:%M:%S %p\n")) + dt.FormatDate(); } inline wxString PrintDataSize(uint64_t size) { if ((size >= (1 << 30)) || (size == 0)) return wxString::Format(wxT("%.2fGB"), (double)size / (double)(1 << 30)); else if (size >= (1 << 20)) return wxString::Format(wxT("%.2fMB"), (double)size / (double)(1 << 20)); else if (size >= (1 << 10)) return wxString::Format(wxT("%.2fkB"), (double)size / (double)(1 << 10)); else return wxString::Format(wxT("%llubytes"), size); } inline wxString PrintValue(float val, const wxString &unit) { if (!isnan(val)) { const float val_abs = fabs(val); if (val_abs == 0) return wxT("0"); else if (val_abs < 0.001) //use micro return wxString::Format(wxT("%.0fu%s"), val * 1000000, unit.c_str()); else if (val_abs < 1.0) //use milli return wxString::Format(wxT("%.0fm%s "), val * 1000, unit.c_str()); else if (val_abs >= 1000) //use kilo return wxString::Format(wxT("%.3fk%s"), val / 1000, unit.c_str()); else return wxString::Format(wxT("%.3f%s"), val, unit.c_str()); } else { return wxT("N/A"); } } void DrawArrow(wxDC &dc, const wxPoint &pos, uint8_t dir, int32_t height, int32_t base); void DrawAnalogWaveform(wxDC &dc, const std::vector<float> &wave, const WaveformDescriptor &wd, int32_t x0, int32_t y0, int32_t x1, int32_t y1); void render(wxDC &dc); void OnButtonEnableWaveformClicked(wxCommandEvent &event); void OnButtonVertScaleUpClicked(wxCommandEvent &event); void OnButtonVertScaleDownClicked(wxCommandEvent &event); void OnButtonTimeScaleUpClicked(wxCommandEvent &event); void OnButtonTimeScaleDownClicked(wxCommandEvent &event); void OnButtonVertPosUpClicked(wxCommandEvent &event); void OnButtonVertPosDownClicked(wxCommandEvent &event); void OnButtonVertPosHomeClicked(wxCommandEvent &event); void OnButtonTimePosUpClicked(wxCommandEvent &event); void OnButtonTimePosDownClicked(wxCommandEvent &event); void OnButtonTimePosEndClicked(wxCommandEvent &event); void OnButtonTimePosHomeClicked(wxCommandEvent &event); void OnButtonClearScopeClicked(wxCommandEvent &event); void OnButtonSaveImageClicked(wxCommandEvent &event); void OnButtonCursorsClicked(wxCommandEvent &event); void OnPaint(wxPaintEvent &event); void OnSize(wxSizeEvent &event); void OnMouseLeftDown(wxMouseEvent &event); void OnMouseLeftUp(wxMouseEvent &event); void OnMouseRightDown(wxMouseEvent &event); void OnMouseRightUp(wxMouseEvent &event); void OnMouseLeaveWindow(wxMouseEvent &event); void OnMouseWheel(wxMouseEvent &event); void OnMouseMotion(wxMouseEvent &event); }; #endif