/* 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