Add software

This commit is contained in:
RocketGod
2022-09-22 09:26:57 -07:00
parent fee0ab05fd
commit 957ea3d712
4511 changed files with 1943182 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,139 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "Gradient.h"
#include <map>
#include <vector>
#include <string>
#include <wx/colour.h>
#define COLOR_THEME_DEFAULT 0
#define COLOR_THEME_DEFAULT_JET 1
#define COLOR_THEME_BW 2
#define COLOR_THEME_SHARP 3
#define COLOR_THEME_RAD 4
#define COLOR_THEME_TOUCH 5
#define COLOR_THEME_HD 6
#define COLOR_THEME_RADAR 7
#define COLOR_THEME_MAX 8
class RGBA4f {
public:
float r, g, b, a;
RGBA4f(float r, float g, float b, float a = 1.0) :
r(r), g(g), b(b), a(a) {
}
RGBA4f(const RGBA4f &other) {
r = other.r;
g = other.g;
b = other.b;
a = other.a;
}
RGBA4f() :
RGBA4f(0, 0, 0) {
}
~RGBA4f() = default;
RGBA4f & operator=(const RGBA4f &other) = default;
RGBA4f operator*(float v) const { return RGBA4f(r*v, g*v, b*v); }
explicit operator wxColour() const {
return wxColour(
(unsigned char) std::min((r * 255.0), 255.0),
(unsigned char) std::min((g * 255.0), 255.0),
(unsigned char) std::min((b * 255.0), 255.0));
}
};
class ColorTheme {
public:
RGBA4f waterfallHighlight;
RGBA4f waterfallNew;
RGBA4f wfHighlight;
RGBA4f waterfallHover;
RGBA4f waterfallDestroy;
RGBA4f fftLine;
RGBA4f fftHighlight;
RGBA4f scopeLine;
RGBA4f tuningBarLight;
RGBA4f tuningBarDark;
RGBA4f tuningBarUp;
RGBA4f tuningBarDown;
RGBA4f meterLevel;
RGBA4f meterValue;
RGBA4f text;
RGBA4f freqLine;
RGBA4f button;
RGBA4f buttonHighlight;
RGBA4f scopeBackground;
RGBA4f fftBackground;
RGBA4f generalBackground;
Gradient waterfallGradient;
std::string name;
};
class ThemeMgr {
public:
ThemeMgr();
~ThemeMgr();
ColorTheme *currentTheme;
std::map<int, ColorTheme *> themes;
void setTheme(int themeId_in);
int getTheme() const;
int themeId;
static ThemeMgr mgr;
};
class DefaultColorTheme: public ColorTheme {
public:
DefaultColorTheme();
};
class DefaultColorThemeJet : public DefaultColorTheme {
public:
DefaultColorThemeJet();
};
class BlackAndWhiteColorTheme: public ColorTheme {
public:
BlackAndWhiteColorTheme();
};
class SharpColorTheme: public ColorTheme {
public:
SharpColorTheme();
};
class RadColorTheme: public ColorTheme {
public:
RadColorTheme();
};
class TouchColorTheme: public ColorTheme {
public:
TouchColorTheme();
};
class HDColorTheme: public ColorTheme {
public:
HDColorTheme();
};
class RadarColorTheme: public ColorTheme {
public:
RadarColorTheme();
};

View File

@ -0,0 +1,315 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "GainCanvas.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#if !wxUSE_GLCANVAS
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
#endif
#include "CubicSDR.h"
#include <algorithm>
#include <cmath>
wxBEGIN_EVENT_TABLE(GainCanvas, wxGLCanvas) EVT_PAINT(GainCanvas::OnPaint)
EVT_IDLE(GainCanvas::OnIdle)
EVT_MOTION(GainCanvas::OnMouseMoved)
EVT_LEFT_DOWN(GainCanvas::OnMouseDown)
EVT_LEFT_UP(GainCanvas::OnMouseReleased)
EVT_LEAVE_WINDOW(GainCanvas::OnMouseLeftWindow)
EVT_ENTER_WINDOW(GainCanvas::OnMouseEnterWindow)
EVT_MOUSEWHEEL(GainCanvas::OnMouseWheelMoved)
wxEND_EVENT_TABLE()
GainCanvas::GainCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs) :
InteractiveCanvas(parent, dispAttrs) {
glContext = new PrimaryGLContext(this, &wxGetApp().GetContext(this), wxGetApp().GetContextAttributes());
bgPanel.setCoordinateSystem(GLPanel::GLPANEL_Y_UP);
bgPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_X);
numGains = 1;
spacing = 2.0/numGains;
barWidth = (1.0/numGains)*0.8;
startPos = spacing/2.0;
barHeight = 0.8f;
refreshCounter = 0;
userGainAsChanged = false;
}
GainCanvas::~GainCanvas() = default;
void GainCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
// wxPaintDC dc(this);
const wxSize ClientSize = GetClientSize() * GetContentScaleFactor();
glContext->SetCurrent(*this);
initGLExtensions();
glViewport(0, 0, ClientSize.x, ClientSize.y);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
bgPanel.draw();
SwapBuffers();
}
void GainCanvas::OnIdle(wxIdleEvent &event) {
if (mouseTracker.mouseInView()) {
Refresh();
} else {
event.Skip();
}
bool areGainsChangedHere = false;
for (auto gi : gainPanels) {
if (gi->getChanged()) {
areGainsChangedHere = true;
// Gain only displays integer gain values, so set the applied gain
//value to exactly that.
wxGetApp().setGain(gi->getName(), (int)(gi->getValue()));
//A gain may be exposed as setting also so assure refresh of the menu also.
wxGetApp().notifyMainUIOfDeviceChange(false); //do not rebuild the gain UI
gi->setChanged(false);
}
}
//User input has changed the gain, so schedule an update of values
//in 150ms in the future, else the device may not have taken the value into account.
if (areGainsChangedHere) {
userGainAsChanged = true;
userGainAsChangedDelayTimer.start();
}
else {
userGainAsChangedDelayTimer.update();
if (!userGainAsChanged || (userGainAsChanged && userGainAsChangedDelayTimer.getMilliseconds() > 150)) {
if (updateGainValues()) {
Refresh();
}
userGainAsChanged = false;
}
}
}
void GainCanvas::SetLevel() {
CubicVR::vec2 mpos = mouseTracker.getGLXY();
for (auto gi : gainPanels) {
if (gi->isMeterHit(mpos)) {
float value = gi->getMeterHitValue(mpos);
gi->setValue(value);
gi->setChanged(true);
break;
}
}
}
void GainCanvas::OnMouseMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseMoved(event);
CubicVR::vec2 mpos = mouseTracker.getGLXY();
for (auto gi : gainPanels) {
if (gi->isMeterHit(mpos)) {
float value = gi->getMeterHitValue(mpos);
gi->setHighlight(value);
gi->setHighlightVisible(true);
wxGetApp().setActiveGainEntry(gi->getName());
} else {
gi->setHighlightVisible(false);
}
}
if (mouseTracker.mouseDown()) {
SetLevel();
}
else {
if (!helpTip.empty()) {
setStatusText(helpTip);
}
}
}
void GainCanvas::OnMouseDown(wxMouseEvent& event) {
InteractiveCanvas::OnMouseDown(event);
SetLevel();
}
void GainCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseWheelMoved(event);
CubicVR::vec2 mpos = mouseTracker.getGLXY();
for (auto gi : gainPanels) {
if (gi->isMeterHit(mpos)) {
float movement = 3.0 * (float)event.GetWheelRotation();
gi->setValue(gi->getValue() + ((movement / 100.0) * ((gi->getHigh() - gi->getLow()) / 100.0)));
gi->setChanged(true);
break;
}
}
}
void GainCanvas::OnMouseReleased(wxMouseEvent& event) {
InteractiveCanvas::OnMouseReleased(event);
}
void GainCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
InteractiveCanvas::OnMouseLeftWindow(event);
SetCursor(wxCURSOR_CROSS);
for (auto gi : gainPanels) {
gi->setHighlightVisible(false);
}
Refresh();
}
void GainCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event);
SetCursor(wxCURSOR_CROSS);
#ifdef _WIN32
if (wxGetApp().getAppFrame()->canFocus()) {
this->SetFocus();
}
#endif
}
void GainCanvas::setHelpTip(std::string tip) {
helpTip = tip;
}
void GainCanvas::updateGainUI() {
SDRDeviceInfo *devInfo = wxGetApp().getDevice();
//possible if we 'Refresh Devices' then devInfo becomes null
//until a new device is selected.
if (devInfo == nullptr) {
return;
}
DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(devInfo->getDeviceId());
//read the gains from the device.
//This may be wrong because the device is not started, or has yet
//to take into account a user gain change. Doesn't matter,
//UpdateGainValues() takes cares of updating the true value realtime.
gains = devInfo->getGains(SOAPY_SDR_RX, 0);
numGains = gains.size();
float i = 0;
if (!numGains) {
return;
}
spacing = 2.0/numGains;
barWidth = (1.0/numGains)*0.7;
startPos = spacing/2.0;
barHeight = 1.0f;
while (!gainPanels.empty()) {
MeterPanel *mDel = gainPanels.back();
gainPanels.pop_back();
bgPanel.removeChild(mDel);
delete mDel;
}
for (const auto& gi : gains) {
auto *mPanel = new MeterPanel(gi.first, gi.second.minimum(), gi.second.maximum(), devConfig->getGain(gi.first,wxGetApp().getGain(gi.first)));
float midPos = -1.0+startPos+spacing*i;
mPanel->setPosition(midPos, 0);
mPanel->setSize(barWidth, barHeight);
bgPanel.addChild(mPanel);
gainPanels.push_back(mPanel);
i++;
}
setThemeColors();
}
// call this to refresh the gain values only, not the whole UI.
bool GainCanvas::updateGainValues() {
bool isRefreshNeeded = false;
SDRDeviceInfo *devInfo = wxGetApp().getDevice();
//possible if we 'Refresh Devices' then devInfo becomes null
//until a new device is selected.
//also, do not attempt an update with the device is not started.
if (devInfo == nullptr || !devInfo->isActive()) {
return false;
}
DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(devInfo->getDeviceId());
gains = devInfo->getGains(SOAPY_SDR_RX, 0);
size_t numGainsToRefresh = std::min(gains.size(), gainPanels.size());
size_t panelIndex = 0;
//actually the order of gains iteration should be constant because map of string,
//and gainPanels were built in that order in updateGainUI()
for (const auto& gi : gains) {
if (panelIndex >= numGainsToRefresh) {
break;
}
// do not update if a change is already pending.
if (!gainPanels[panelIndex]->getChanged()) {
//read the actual gain from the device, round it
float actualRoundedGain = (float)std::round(devInfo->getCurrentGain(SOAPY_SDR_RX, 0, gi.first));
//do nothing if the difference is less than 1.0, since the panel do not show it anyway.
if ((int)actualRoundedGain != (int)(gainPanels[panelIndex]->getValue())) {
gainPanels[panelIndex]->setValue(actualRoundedGain);
//update the config with this value :
//a consequence of such updates is that the use setting
// is overridden by the current one in AGC mode.
//TODO: if it not desirable, do not update in AGC mode.
devConfig->setGain(gi.first, actualRoundedGain);
isRefreshNeeded = true;
}
} //end if no external change pending.
panelIndex++;
}
return isRefreshNeeded;
}
void GainCanvas::setThemeColors() {
RGBA4f c1, c2;
c1 = ThemeMgr::mgr.currentTheme->generalBackground;
c2 = ThemeMgr::mgr.currentTheme->generalBackground * 0.5;
c1.a = 1.0;
c2.a = 1.0;
bgPanel.setFillColor(c1, c2);
Refresh();
}

View File

@ -0,0 +1,64 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/glcanvas.h"
#include "wx/timer.h"
#include <vector>
#include <queue>
#include <atomic>
#include "InteractiveCanvas.h"
#include "MouseTracker.h"
#include "GLPanel.h"
#include "PrimaryGLContext.h"
#include "SDRDeviceInfo.h"
#include "Timer.h"
#include "MeterPanel.h"
class GainCanvas: public InteractiveCanvas {
public:
GainCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs);
~GainCanvas() override;
void setHelpTip(std::string tip);
void updateGainUI();
void setThemeColors();
private:
// call this to refresh the gain values only, return true if refresh is needed
bool updateGainValues();
void OnPaint(wxPaintEvent& event);
void OnIdle(wxIdleEvent &event);
void SetLevel();
void OnShow(wxShowEvent& event);
void OnMouseMoved(wxMouseEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnMouseWheelMoved(wxMouseEvent& event);
void OnMouseReleased(wxMouseEvent& event);
void OnMouseEnterWindow(wxMouseEvent& event);
void OnMouseLeftWindow(wxMouseEvent& event);
PrimaryGLContext *glContext;
std::string helpTip;
std::vector<MeterPanel *> gainPanels;
GLPanel bgPanel;
SDRRangeMap gains;
float spacing, barWidth, startPos, barHeight, numGains;
int refreshCounter;
wxSize clientSize;
std::atomic_bool userGainAsChanged;
Timer userGainAsChangedDelayTimer;
//
wxDECLARE_EVENT_TABLE();
};

View File

@ -0,0 +1,50 @@
#include "ImagePanel.h"
BEGIN_EVENT_TABLE(ImagePanel, wxPanel)
EVT_PAINT(ImagePanel::paintEvent)
END_EVENT_TABLE()
ImagePanel::ImagePanel(wxPanel * parent, wxString file, wxBitmapType format) :
wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE) {
image.LoadFile(file, format);
}
void ImagePanel::paintEvent(wxPaintEvent & /* evt */) {
wxPaintDC dc(this);
render(dc);
}
void ImagePanel::paintNow() {
wxClientDC dc(this);
render(dc);
}
void ImagePanel::render(wxDC& dc) {
double imagew = image.GetWidth();
double imageh = image.GetHeight();
wxSize destSize = dc.GetSize();
double destw = destSize.GetWidth();
double desth = destSize.GetHeight();
double sf, wf, hf;
wf = destw / imagew;
hf = desth / imageh;
sf = (wf < hf)?wf:hf;
double resulth = imageh * sf;
double resultw = imagew * sf;
dc.SetUserScale(sf, sf);
dc.DrawBitmap( image, (destw/2 - resultw/2)/sf, (desth/2 - resulth/2)/sf, false );
}

View File

@ -0,0 +1,16 @@
#include <wx/wx.h>
#include <wx/sizer.h>
class ImagePanel : public wxPanel {
wxBitmap image;
public:
ImagePanel(wxPanel* parent, wxString file, wxBitmapType format);
void paintEvent(wxPaintEvent & evt);
void paintNow();
void render(wxDC& dc);
DECLARE_EVENT_TABLE()
};

View File

@ -0,0 +1,181 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "InteractiveCanvas.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#if !wxUSE_GLCANVAS
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
#endif
#include "CubicSDR.h"
#include <wx/numformatter.h>
InteractiveCanvas::InteractiveCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs) :
wxGLCanvas(parent, dispAttrs, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE),
parent(parent), shiftDown(false), altDown(false), ctrlDown(false), centerFreq(0), bandwidth(0), lastBandwidth(0), isView(
false) {
mouseTracker.setTarget(this);
}
InteractiveCanvas::~InteractiveCanvas() = default;
void InteractiveCanvas::setView(long long center_freq_in, long long bandwidth_in) {
isView = true;
centerFreq = center_freq_in;
bandwidth = bandwidth_in;
lastBandwidth = 0;
}
void InteractiveCanvas::disableView() {
isView = false;
centerFreq = wxGetApp().getFrequency();
bandwidth = wxGetApp().getSampleRate();
lastBandwidth = 0;
}
bool InteractiveCanvas::getViewState() const {
return isView;
}
long long InteractiveCanvas::getFrequencyAt(float x) {
long long iqCenterFreq = getCenterFrequency();
long long iqBandwidth = getBandwidth();
long long freq = iqCenterFreq - (long long)(0.5 * (long double) iqBandwidth) + ((long double) x * (long double) iqBandwidth);
return freq;
}
long long InteractiveCanvas::getFrequencyAt(float x, long long iqCenterFreq, long long iqBandwidth) {
long long freq = iqCenterFreq - (long long)(0.5 * (long double) iqBandwidth) + ((long double) x * (long double) iqBandwidth);
return freq;
}
void InteractiveCanvas::setCenterFrequency(long long center_freq_in) {
centerFreq = center_freq_in;
}
long long InteractiveCanvas::getCenterFrequency() const {
if (isView) {
return centerFreq;
} else {
return wxGetApp().getFrequency();
}
}
void InteractiveCanvas::setBandwidth(long long bandwidth_in) {
bandwidth = bandwidth_in;
}
long long InteractiveCanvas::getBandwidth() const {
if (isView) {
return bandwidth;
} else {
return wxGetApp().getSampleRate();
}
}
MouseTracker *InteractiveCanvas::getMouseTracker() {
return &mouseTracker;
}
bool InteractiveCanvas::isAltDown() const {
return altDown;
}
bool InteractiveCanvas::isCtrlDown() const {
return ctrlDown;
}
bool InteractiveCanvas::isShiftDown() const {
return shiftDown;
}
void InteractiveCanvas::OnKeyUp(wxKeyEvent& event) {
shiftDown = event.ShiftDown();
altDown = event.AltDown();
ctrlDown = event.ControlDown();
}
void InteractiveCanvas::OnKeyDown(wxKeyEvent& event) {
shiftDown = event.ShiftDown();
altDown = event.AltDown();
ctrlDown = event.ControlDown();
}
void InteractiveCanvas::OnMouseMoved(wxMouseEvent& event) {
mouseTracker.OnMouseMoved(event);
shiftDown = event.ShiftDown();
altDown = event.AltDown();
ctrlDown = event.ControlDown();
}
void InteractiveCanvas::OnMouseDown(wxMouseEvent& event) {
mouseTracker.OnMouseDown(event);
shiftDown = event.ShiftDown();
altDown = event.AltDown();
ctrlDown = event.ControlDown();
}
void InteractiveCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
mouseTracker.OnMouseWheelMoved(event);
}
void InteractiveCanvas::OnMouseReleased(wxMouseEvent& event) {
mouseTracker.OnMouseReleased(event);
shiftDown = event.ShiftDown();
altDown = event.AltDown();
ctrlDown = event.ControlDown();
}
void InteractiveCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
mouseTracker.OnMouseLeftWindow(event);
shiftDown = false;
altDown = false;
ctrlDown = false;
}
void InteractiveCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
mouseTracker.OnMouseEnterWindow(event);
shiftDown = event.ShiftDown();
altDown = event.AltDown();
ctrlDown = event.ControlDown();
}
void InteractiveCanvas::setStatusText(std::string statusText) {
wxGetApp().getAppFrame()->setStatusText(this, statusText);
}
void InteractiveCanvas::setStatusText(std::string statusText, int value) {
wxGetApp().getAppFrame()->setStatusText(statusText, value);
}
void InteractiveCanvas::OnMouseRightDown(wxMouseEvent& event) {
mouseTracker.OnMouseRightDown(event);
}
void InteractiveCanvas::OnMouseRightReleased(wxMouseEvent& event) {
mouseTracker.OnMouseRightReleased(event);
}
bool InteractiveCanvas::isMouseInView() {
return mouseTracker.mouseInView();
}
bool InteractiveCanvas::isMouseDown() {
return mouseTracker.mouseInView() && mouseTracker.mouseDown();
}

View File

@ -0,0 +1,68 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/glcanvas.h"
#include "wx/timer.h"
#include "MouseTracker.h"
#include <string>
#include <vector>
class InteractiveCanvas: public wxGLCanvas {
public:
InteractiveCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs);
~InteractiveCanvas() override;
long long getFrequencyAt(float x);
long long getFrequencyAt(float x, long long iqCenterFreq, long long iqBandwidth);
virtual void setView(long long center_freq_in, long long bandwidth_in);
virtual void disableView();
bool getViewState() const;
void setCenterFrequency(long long center_freq_in);
long long getCenterFrequency() const;
void setBandwidth(long long bandwidth_in);
long long getBandwidth() const;
MouseTracker *getMouseTracker();
bool isMouseInView();
bool isMouseDown();
bool isAltDown() const;
bool isCtrlDown() const;
bool isShiftDown() const;
protected:
void OnKeyDown(wxKeyEvent& event);
void OnKeyUp(wxKeyEvent& event);
void OnMouseMoved(wxMouseEvent& event);
void OnMouseWheelMoved(wxMouseEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnMouseReleased(wxMouseEvent& event);
void OnMouseRightDown(wxMouseEvent& event);
void OnMouseRightReleased(wxMouseEvent& event);
void OnMouseEnterWindow(wxMouseEvent& event);
void OnMouseLeftWindow(wxMouseEvent& event);
void setStatusText(std::string statusText);
void setStatusText(std::string statusText, int value);
wxWindow *parent;
MouseTracker mouseTracker;
bool shiftDown;
bool altDown;
bool ctrlDown;
long long centerFreq;
long long bandwidth;
long long lastBandwidth;
bool isView;
};

View File

@ -0,0 +1,189 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "MeterCanvas.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#if !wxUSE_GLCANVAS
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
#endif
#include "CubicSDR.h"
#include "CubicSDRDefs.h"
#include "AppFrame.h"
#include <algorithm>
wxBEGIN_EVENT_TABLE(MeterCanvas, wxGLCanvas) EVT_PAINT(MeterCanvas::OnPaint)
EVT_IDLE(MeterCanvas::OnIdle)
EVT_MOTION(MeterCanvas::OnMouseMoved)
EVT_LEFT_DOWN(MeterCanvas::OnMouseDown)
EVT_LEFT_UP(MeterCanvas::OnMouseReleased)
EVT_MOUSEWHEEL(MeterCanvas::OnMouseWheelMoved)
EVT_RIGHT_DOWN(MeterCanvas::OnMouseRightDown)
EVT_RIGHT_UP(MeterCanvas::OnMouseRightReleased)
EVT_LEAVE_WINDOW(MeterCanvas::OnMouseLeftWindow)
EVT_ENTER_WINDOW(MeterCanvas::OnMouseEnterWindow)
wxEND_EVENT_TABLE()
MeterCanvas::MeterCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs) :
InteractiveCanvas(parent, dispAttrs), level(0), level_min(0), level_max(1), inputValue(0), userInputValue(0), showUserInput(true) {
glContext = new MeterContext(this, &wxGetApp().GetContext(this), wxGetApp().GetContextAttributes());
}
MeterCanvas::~MeterCanvas() = default;
void MeterCanvas::setLevel(float level_in) {
level = level_in;
Refresh();
}
float MeterCanvas::getLevel() const {
return level;
}
void MeterCanvas::setMax(float max_in) {
level_max = max_in;
Refresh();
}
void MeterCanvas::setMin(float min_in) {
level_min = min_in;
Refresh();
}
void MeterCanvas::setUserInputValue(float slider_in) {
userInputValue = slider_in;
Refresh();
}
void MeterCanvas::setInputValue(float slider_in) {
userInputValue = inputValue = slider_in;
Refresh();
}
bool MeterCanvas::inputChanged() const {
return (inputValue != userInputValue);
}
float MeterCanvas::getInputValue() {
inputValue = userInputValue;
return userInputValue;
}
void MeterCanvas::setShowUserInput(bool showUserInput_in) {
showUserInput = showUserInput_in;
}
void MeterCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
// wxPaintDC dc(this);
const wxSize ClientSize = GetClientSize() * GetContentScaleFactor();
glContext->SetCurrent(*this);
initGLExtensions();
glViewport(0, 0, ClientSize.x, ClientSize.y);
glContext->DrawBegin();
glContext->Draw(ThemeMgr::mgr.currentTheme->generalBackground.r, ThemeMgr::mgr.currentTheme->generalBackground.g, ThemeMgr::mgr.currentTheme->generalBackground.b, 0.5, 1.0);
if (mouseTracker.mouseInView()) {
glContext->Draw(0.4f, 0.4f, 0.4f, 0.5f, mouseTracker.getMouseY());
}
glContext->Draw(ThemeMgr::mgr.currentTheme->meterLevel.r, ThemeMgr::mgr.currentTheme->meterLevel.g, ThemeMgr::mgr.currentTheme->meterLevel.b, 0.5, (level-level_min) / (level_max-level_min));
if (showUserInput) {
glContext->Draw(ThemeMgr::mgr.currentTheme->meterValue.r, ThemeMgr::mgr.currentTheme->meterValue.g, ThemeMgr::mgr.currentTheme->meterValue.b, 0.5, (userInputValue-level_min) / (level_max-level_min));
}
glContext->DrawEnd();
SwapBuffers();
}
void MeterCanvas::OnIdle(wxIdleEvent &event) {
if (mouseTracker.mouseInView()) {
Refresh();
} else {
event.Skip();
}
}
void MeterCanvas::OnMouseMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseMoved(event);
if (mouseTracker.mouseDown()) {
userInputValue = mouseTracker.getMouseY() * (level_max-level_min) + level_min;
} else {
if (!helpTip.empty()) {
setStatusText(helpTip);
}
}
}
void MeterCanvas::OnMouseDown(wxMouseEvent& event) {
InteractiveCanvas::OnMouseDown(event);
userInputValue = mouseTracker.getMouseY() * (level_max-level_min) + level_min;
mouseTracker.setHorizDragLock(true);
}
void MeterCanvas::OnMouseReleased(wxMouseEvent& event) {
InteractiveCanvas::OnMouseReleased(event);
userInputValue = mouseTracker.getMouseY() * (level_max-level_min) + level_min;
}
void MeterCanvas::OnMouseRightDown(wxMouseEvent& event) {
InteractiveCanvas::OnMouseRightDown(event);
}
void MeterCanvas::OnMouseRightReleased(wxMouseEvent& event) {
InteractiveCanvas::OnMouseRightReleased(event);
if (showUserInput) {
userInputValue = level - level * 0.02;
}
}
void MeterCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseWheelMoved(event);
float movement = 3.0 * (float)event.GetWheelRotation();
float currentValue;
if (showUserInput) {
currentValue = userInputValue;
} else {
currentValue = level;
}
currentValue = currentValue + ((movement / 100.0) * ((level_max - level_min) / 100.0));
if (currentValue > level_max) {
currentValue = level_max;
}
if (currentValue < level_min) {
currentValue = level_min;
}
userInputValue = currentValue;
}
void MeterCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
InteractiveCanvas::OnMouseLeftWindow(event);
SetCursor(wxCURSOR_CROSS);
Refresh();
}
void MeterCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event);
SetCursor(wxCURSOR_CROSS);
#ifdef _WIN32
if (wxGetApp().getAppFrame()->canFocus()) {
this->SetFocus();
}
#endif
}
void MeterCanvas::setHelpTip(std::string tip) {
helpTip = tip;
}

View File

@ -0,0 +1,64 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/glcanvas.h"
#include "wx/timer.h"
#include <vector>
#include <queue>
#include "InteractiveCanvas.h"
#include "MeterContext.h"
#include "MouseTracker.h"
#include "Timer.h"
class MeterCanvas: public InteractiveCanvas {
public:
MeterCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs);
~MeterCanvas() override;
void setLevel(float level_in);
float getLevel() const;
void setMax(float max_in);
void setMin(float max_in);
void setUserInputValue(float slider_in);
void setInputValue(float slider_in);
bool inputChanged() const;
float getInputValue();
void setShowUserInput(bool showUserInput_in);
void setHelpTip(std::string tip);
private:
void OnPaint(wxPaintEvent& event);
void OnIdle(wxIdleEvent &event);
void OnMouseMoved(wxMouseEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnMouseReleased(wxMouseEvent& event);
void OnMouseWheelMoved(wxMouseEvent& event);
void OnMouseRightDown(wxMouseEvent& event);
void OnMouseRightReleased(wxMouseEvent& event);
void OnMouseEnterWindow(wxMouseEvent& event);
void OnMouseLeftWindow(wxMouseEvent& event);
MeterContext *glContext;
float level;
float level_min, level_max;
float inputValue;
float userInputValue;
bool showUserInput;
std::string helpTip;
//
wxDECLARE_EVENT_TABLE();
};

View File

@ -0,0 +1,54 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "MeterContext.h"
#include "MeterCanvas.h"
#include "ColorTheme.h"
MeterContext::MeterContext(MeterCanvas *canvas, wxGLContext *sharedContext, wxGLContextAttrs *ctxAttrs) :
PrimaryGLContext(canvas, sharedContext, ctxAttrs) {
}
void MeterContext::DrawBegin() {
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glClearColor(ThemeMgr::mgr.currentTheme->generalBackground.r, ThemeMgr::mgr.currentTheme->generalBackground.g, ThemeMgr::mgr.currentTheme->generalBackground.b, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_TEXTURE_2D);
}
void MeterContext::Draw(float r, float g, float b, float a, float level) {
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
glBegin(GL_QUADS);
glColor4f(r*0.65,g*0.65,b*0.65,a);
glVertex2f(-1.0, -1.0 + 2.0 * level);
glVertex2f(-1.0, -1.0);
glColor4f(r,g,b,a);
glVertex2f(0.0, -1.0);
glVertex2f(0.0, -1.0 + 2.0 * level);
glColor4f(r,g,b,a);
glVertex2f(0.0, -1.0 + 2.0 * level);
glVertex2f(0.0, -1.0);
glColor4f(r*0.65,g*0.65,b*0.65,a*0.65);
glVertex2f(1.0, -1.0);
glVertex2f(1.0, -1.0 + 2.0 * level);
glEnd();
glDisable(GL_BLEND);
}
void MeterContext::DrawEnd() {
// glFlush();
// CheckGLError();
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "PrimaryGLContext.h"
#include "Gradient.h"
class MeterCanvas;
class MeterContext: public PrimaryGLContext {
public:
MeterContext(MeterCanvas *canvas, wxGLContext *sharedContext, wxGLContextAttrs *ctxAttrs);
void DrawBegin();
void Draw(float r, float g, float b, float a, float level);
void DrawEnd();
private:
};

View File

@ -0,0 +1,222 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModeSelectorCanvas.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#if !wxUSE_GLCANVAS
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
#endif
#include "CubicSDR.h"
#include "CubicSDRDefs.h"
#include "AppFrame.h"
#include <algorithm>
wxBEGIN_EVENT_TABLE(ModeSelectorCanvas, wxGLCanvas) EVT_PAINT(ModeSelectorCanvas::OnPaint)
EVT_IDLE(ModeSelectorCanvas::OnIdle)
EVT_MOTION(ModeSelectorCanvas::OnMouseMoved)
EVT_LEFT_DOWN(ModeSelectorCanvas::OnMouseDown)
EVT_LEFT_UP(ModeSelectorCanvas::OnMouseReleased)
EVT_LEAVE_WINDOW(ModeSelectorCanvas::OnMouseLeftWindow)
EVT_ENTER_WINDOW(ModeSelectorCanvas::OnMouseEnterWindow)
wxEND_EVENT_TABLE()
ModeSelectorCanvas::ModeSelectorCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs) :
InteractiveCanvas(parent, dispAttrs), numChoices(0), currentSelection(-1), toggleMode(false), inputChanged(false), padX(4.0), padY(4.0), highlightOverride(false) {
glContext = new ModeSelectorContext(this, &wxGetApp().GetContext(this), wxGetApp().GetContextAttributes());
highlightColor = RGBA4f(1.0,1.0,1.0,1.0);
}
ModeSelectorCanvas::~ModeSelectorCanvas() = default;
int ModeSelectorCanvas::getHoveredSelection() {
if (!mouseTracker.mouseInView()) {
return -1;
}
float ypos = 1.0 - (mouseTracker.getMouseY() * 2.0);
float yval = (int) (((ypos + 1.0) / 2.0) * (float) numChoices);
return yval;
}
void ModeSelectorCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
// wxPaintDC dc(this);
const wxSize ClientSize = GetClientSize() * GetContentScaleFactor();
glContext->SetCurrent(*this);
initGLExtensions();
glViewport(0, 0, ClientSize.x, ClientSize.y);
glContext->DrawBegin();
int yval = getHoveredSelection();
for (int i = 0; i < numChoices; i++) {
if (yval == i && !highlightOverride) {
RGBA4f hc = ThemeMgr::mgr.currentTheme->buttonHighlight;
glContext->DrawSelector(selections[i].label, i, numChoices, true, hc.r, hc.g, hc.b, 1.0, padX, padY);
} else {
RGBA4f hc = ThemeMgr::mgr.currentTheme->button;
if (highlightOverride) {
hc = highlightColor;
}
glContext->DrawSelector(selections[i].label, i, numChoices, i == currentSelection, hc.r, hc.g, hc.b, 1.0, padX, padY);
}
}
glContext->DrawEnd();
SwapBuffers();
}
void ModeSelectorCanvas::OnIdle(wxIdleEvent &event) {
if (mouseTracker.mouseInView()) {
Refresh();
} else {
event.Skip();
}
}
void ModeSelectorCanvas::OnMouseMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseMoved(event);
}
void ModeSelectorCanvas::OnMouseDown(wxMouseEvent& event) {
InteractiveCanvas::OnMouseDown(event);
mouseTracker.setHorizDragLock(true);
mouseTracker.setVertDragLock(true);
}
void ModeSelectorCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseWheelMoved(event);
}
void ModeSelectorCanvas::OnMouseReleased(wxMouseEvent& event) {
InteractiveCanvas::OnMouseReleased(event);
mouseTracker.setHorizDragLock(false);
mouseTracker.setVertDragLock(false);
const wxSize ClientSize = GetClientSize();
int selectedButton = currentSelection;
if (mouseTracker.getOriginDeltaMouseX() < 2.0 / ClientSize.y) {
selectedButton = getHoveredSelection();
}
if (toggleMode && (currentSelection == selectedButton)) {
selectedButton = -1;
}
if (currentSelection != selectedButton) {
inputChanged = true;
}
currentSelection = selectedButton;
SetCursor (wxCURSOR_HAND);
Refresh();
}
void ModeSelectorCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
InteractiveCanvas::OnMouseLeftWindow(event);
SetCursor (wxCURSOR_CROSS);
Refresh();
}
void ModeSelectorCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event);
SetCursor (wxCURSOR_HAND);
if (!helpTip.empty()) {
setStatusText(helpTip);
}
Refresh();
}
void ModeSelectorCanvas::setHelpTip(std::string tip) {
helpTip = tip;
}
void ModeSelectorCanvas::setNumChoices(int numChoices_in) {
numChoices = numChoices_in;
Refresh();
}
void ModeSelectorCanvas::addChoice(int value, std::string label) {
selections.push_back(ModeSelectorMode(value, label));
numChoices = selections.size();
}
void ModeSelectorCanvas::addChoice(std::string label) {
selections.push_back(ModeSelectorMode(selections.size()+1, label));
numChoices = selections.size();
}
void ModeSelectorCanvas::setSelection(const std::string& label) {
for (int i = 0; i < numChoices; i++) {
if (selections[i].label == label) {
currentSelection = i;
Refresh();
return;
}
}
currentSelection = -1;
Refresh();
}
std::string ModeSelectorCanvas::getSelectionLabel() {
if (currentSelection == -1) {
return "";
}
return selections[currentSelection].label;
}
void ModeSelectorCanvas::setSelection(int value) {
for (int i = 0; i < numChoices; i++) {
if (selections[i].value == value) {
currentSelection = i;
Refresh();
return;
}
}
currentSelection = -1;
Refresh();
}
int ModeSelectorCanvas::getSelection() {
if (currentSelection == -1) {
return -1;
}
return selections[currentSelection].value;
}
void ModeSelectorCanvas::setToggleMode(bool toggleMode_in) {
toggleMode = toggleMode_in;
}
bool ModeSelectorCanvas::modeChanged() const {
return inputChanged;
}
void ModeSelectorCanvas::clearModeChanged() {
inputChanged = false;
}
void ModeSelectorCanvas::setPadding(float padX_in, float padY_in) {
padX = padX_in;
padY = padY_in;
}
void ModeSelectorCanvas::setHighlightColor(const RGBA4f& hc) {
highlightColor = hc;
highlightOverride = true;
}

View File

@ -0,0 +1,78 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/glcanvas.h"
#include "wx/timer.h"
#include <vector>
#include <queue>
#include "InteractiveCanvas.h"
#include "ModeSelectorContext.h"
#include "MouseTracker.h"
#include "Timer.h"
class ModeSelectorMode {
public:
int value;
std::string label;
ModeSelectorMode(int value, std::string label) : value(value), label(label) {
}
};
class ModeSelectorCanvas: public InteractiveCanvas {
public:
ModeSelectorCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs);
~ModeSelectorCanvas() override;
int getHoveredSelection();
void setHelpTip(std::string tip);
void addChoice(int value, std::string label);
void addChoice(std::string label);
void setSelection(const std::string& label);
std::string getSelectionLabel();
void setSelection(int value);
int getSelection();
void setToggleMode(bool toggleMode_in);
bool modeChanged() const;
void clearModeChanged();
void setPadding(float padX_in, float padY_in);
void setHighlightColor(const RGBA4f& hc);
private:
void setNumChoices(int numChoices_in);
void OnPaint(wxPaintEvent& event);
void OnIdle(wxIdleEvent &event);
void OnMouseMoved(wxMouseEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnMouseWheelMoved(wxMouseEvent& event);
void OnMouseReleased(wxMouseEvent& event);
void OnMouseEnterWindow(wxMouseEvent& event);
void OnMouseLeftWindow(wxMouseEvent& event);
ModeSelectorContext *glContext;
std::string helpTip;
int numChoices;
int currentSelection;
bool toggleMode;
bool inputChanged;
std::vector<ModeSelectorMode> selections;
float padX, padY;
RGBA4f highlightColor;
bool highlightOverride;
//
wxDECLARE_EVENT_TABLE();
};

View File

@ -0,0 +1,74 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModeSelectorContext.h"
#include "ModeSelectorCanvas.h"
#include "ColorTheme.h"
ModeSelectorContext::ModeSelectorContext(ModeSelectorCanvas *canvas, wxGLContext *sharedContext, wxGLContextAttrs *ctxAttrs) :
PrimaryGLContext(canvas, sharedContext, ctxAttrs) {
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
}
void ModeSelectorContext::DrawBegin() {
glClearColor(ThemeMgr::mgr.currentTheme->generalBackground.r, ThemeMgr::mgr.currentTheme->generalBackground.g, ThemeMgr::mgr.currentTheme->generalBackground.b,1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_TEXTURE_2D);
}
void ModeSelectorContext::DrawSelector(const std::string& label, int c, int cMax, bool on, float r, float g, float b, float a, float px, float py) {
GLint vp[4];
glGetIntegerv( GL_VIEWPORT, vp);
float viewHeight = (float) vp[3];
float viewWidth = (float) vp[2];
int fontSize = 18;
if (viewWidth < 30 || viewHeight < 200) {
fontSize = 16;
}
glColor4f(r, g, b, a);
float y = 1.0 - ((float) (c+1) / (float) cMax * 2.0);
float height = (2.0 / (float) cMax);
float padX = (px / viewWidth);
float padY = (py / viewHeight);
if (a < 1.0) {
glEnable(GL_BLEND);
}
glBegin(on?GL_QUADS:GL_LINE_LOOP);
glVertex2f(-1.0 + padX, y + padY);
glVertex2f(1.0 - padX, y + padY);
glVertex2f(1.0 - padX, y + height - padY);
glVertex2f(-1.0 + padX, y + height - padY);
glEnd();
if (a < 1.0) {
glDisable(GL_BLEND);
}
if (on) {
glColor4f(0, 0, 0, a);
}
//Do not zoom the selectors
GLFont::getFont(fontSize).drawString(label, 0.0, y + height / 2.0, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER);
}
void ModeSelectorContext::DrawEnd() {
// glFlush();
// CheckGLError();
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "PrimaryGLContext.h"
#include "Gradient.h"
class ModeSelectorCanvas;
class ModeSelectorContext: public PrimaryGLContext {
public:
ModeSelectorContext(ModeSelectorCanvas *canvas, wxGLContext *sharedContext, wxGLContextAttrs *ctxAttrs);
void DrawBegin();
void DrawSelector(const std::string& label, int c, int cMax, bool on, float r, float g, float b, float a, float padx, float pady);
void DrawEnd();
};

View File

@ -0,0 +1,533 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "PrimaryGLContext.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#if !wxUSE_GLCANVAS
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
#endif
#include "CubicSDR.h"
wxString PrimaryGLContext::glGetwxString(GLenum name) {
const GLubyte *v = glGetString(name);
if (v == nullptr) {
// The error is not important. It is GL_INVALID_ENUM.
// We just want to clear the error stack.
glGetError();
return wxString();
}
return wxString((const char*) v);
}
void PrimaryGLContext::CheckGLError() {
GLenum errLast = GL_NO_ERROR;
for (;;) {
GLenum err = glGetError();
if (err == GL_NO_ERROR)
return;
if (err == errLast) {
std::cout << "OpenGL error state couldn't be reset." << std::endl;
return;
}
errLast = err;
std::cout << "OpenGL Error " << err << std::endl;
}
}
PrimaryGLContext::PrimaryGLContext(wxGLCanvas *canvas, wxGLContext *sharedContext, wxGLContextAttrs* ctxAttrs) :
wxGLContext(canvas, sharedContext, (const wxGLContextAttrs*) ctxAttrs), hoverAlpha(1.0) {
//#ifndef __linux__
// SetCurrent(*canvas);
// // Pre-load fonts
// for (int i = 0; i < GLFONT_MAX; i++) {
// getFont((GLFontSize) i);
// }
// CheckGLError();
//#endif
}
void PrimaryGLContext::DrawDemodInfo(const DemodulatorInstancePtr& demod, const RGBA4f& color, long long center_freq, long long srate, bool centerline) {
if (!demod) {
return;
}
if (!srate) {
srate = wxGetApp().getSampleRate();
}
GLint vp[4];
glGetIntegerv( GL_VIEWPORT, vp);
float viewHeight = (float) vp[3];
float viewWidth = (float) vp[2];
if (center_freq == -1) {
center_freq = wxGetApp().getFrequency();
}
long long demodFreq = demod->getFrequency();
if (demod->isDeltaLock()) {
demodFreq = wxGetApp().getFrequency() + demod->getDeltaLockOfs();
}
float uxPos = (float) (demodFreq - (center_freq - srate / 2)) / (float) srate;
uxPos = (uxPos - 0.5) * 2.0;
glDisable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(color.r, color.g, color.b, 0.6f);
float ofs = ((float) demod->getBandwidth()) / (float) srate;
float ofsLeft = (demod->getDemodulatorType()!="USB")?ofs:0, ofsRight = (demod->getDemodulatorType()!="LSB")?ofs:0;
float labelHeight = 20.0 / viewHeight;
float hPos = -1.0 + labelHeight;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
bool soloMode = wxGetApp().getSoloMode();
bool isRecording = demod->isRecording();
bool isSolo = soloMode && demod == wxGetApp().getDemodMgr().getCurrentModem();
RGBA4f labelBg(0, 0, 0, 0.35f);
if (isSolo) {
labelBg.r = labelBg.g = 0.8f;
} else if (demod->isMuted()) {
labelBg.r = 0.8f;
} else if (soloMode) {
labelBg.r = 0.2f;
}
// TODO: Better recording indicator... pulsating red circle?
if (isRecording) {
auto t = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
labelBg.g = sinf(2.0f * M_PI * (float(t) / 1000.0f)) * 0.25f + 0.75f;
}
glColor4f(labelBg.r, labelBg.g, labelBg.b, labelBg.a);
glBegin(GL_QUADS);
glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0);
glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, hPos + labelHeight, 0.0);
glEnd();
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glColor4f(color.r, color.g, color.b, 0.2f);
glBegin(GL_QUADS);
glVertex3f(uxPos - ofsLeft, 1.0, 0.0);
glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, 1.0, 0.0);
glEnd();
if (ofs * 2.0 < 16.0 / viewWidth) {
glColor4f(color.r, color.g, color.b, 0.2f);
glBegin(GL_QUADS);
glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0);
glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, hPos + labelHeight, 0.0);
glEnd();
}
if (centerline) {
glColor4f(color.r, color.g, color.b, 0.5);
glBegin(GL_LINES);
glVertex3f(uxPos, 1.0, 0.0);
glVertex3f(uxPos, -1.0, 0.0);
glEnd();
}
glColor4f(1.0, 1.0, 1.0, 0.8f);
std::string demodLabel, demodPrefix;
if (demod->isDeltaLock()) {
demodPrefix.append("V");
}
if (isRecording) {
demodPrefix.append("R");
}
if (demod->isMuted()) {
demodPrefix.append("M");
} else if (isSolo) {
demodPrefix.append("S");
}
// Set the prefix
if (!demodPrefix.empty()) {
demodLabel = "[" + demodPrefix + "] ";
}
// Append the default label
demodLabel.append(demod->getLabel());
if (demod->getDemodulatorType() == "USB") {
GLFont::getFont(16, GLFont::getScaleFactor()).drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
} else if (demod->getDemodulatorType() == "LSB") {
GLFont::getFont(16, GLFont::getScaleFactor()).drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
} else {
GLFont::getFont(16, GLFont::getScaleFactor()).drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
}
glDisable(GL_BLEND);
}
void PrimaryGLContext::DrawFreqBwInfo(long long freq, int bw, const RGBA4f& color, long long center_freq, long long srate, bool stack, bool centerline) {
if (!srate) {
srate = wxGetApp().getSampleRate();
}
GLint vp[4];
glGetIntegerv( GL_VIEWPORT, vp);
float viewHeight = (float) vp[3];
float viewWidth = (float) vp[2];
if (center_freq == -1) {
center_freq = wxGetApp().getFrequency();
}
float uxPos = (float) (freq - (center_freq - srate / 2)) / (float) srate;
uxPos = (uxPos - 0.5) * 2.0;
std::string lastType = wxGetApp().getDemodMgr().getLastDemodulatorType();
float ofs = (float) bw / (float) srate;
float ofsLeft = (lastType!="USB")?ofs:0, ofsRight = (lastType!="LSB")?ofs:0;
float labelHeight = 20.0 / viewHeight;
float hPos = -1.0 + (stack?(labelHeight*3.0):labelHeight);
glDisable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0, 0, 0, 0.35f);
glBegin(GL_QUADS);
glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0);
glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, hPos + labelHeight, 0.0);
glEnd();
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glColor4f(color.r, color.g, color.b, 0.1f);
glBegin(GL_QUADS);
glVertex3f(uxPos - ofsLeft, 1.0, 0.0);
glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, 1.0, 0.0);
glEnd();
if (ofs * 2.0 < 16.0 / viewWidth) {
glColor4f(color.r, color.g, color.b, 0.1f);
glBegin(GL_QUADS);
glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0);
glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, -1.0, 0.0);
glVertex3f(uxPos + ofsRight, hPos + labelHeight, 0.0);
glEnd();
}
if (centerline) {
glColor4f(color.r, color.g, color.b, 0.5);
glBegin(GL_LINES);
glVertex3f(uxPos, 1.0, 0.0);
glVertex3f(uxPos, -1.0, 0.0);
glEnd();
}
glColor4f(1.0, 1.0, 1.0, 0.8f);
std::string demodLabel = std::to_string((double)freq/1000000.0);
double shadowOfsX = 4.0 / viewWidth, shadowOfsY = 2.0 / viewHeight;
GLFont::Drawer refDrawingFont = GLFont::getFont(16, GLFont::getScaleFactor());
if (lastType == "USB") {
glColor4f(0,0,0, 1.0);
glBlendFunc(GL_ONE, GL_ZERO);
refDrawingFont.drawString(demodLabel, uxPos+shadowOfsX, hPos+shadowOfsY, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER);
refDrawingFont.drawString(demodLabel, uxPos-shadowOfsX, hPos-shadowOfsY, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER);
glColor4f(color.r, color.g, color.b, 1.0);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
refDrawingFont.drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER);
} else if (lastType == "LSB") {
glBlendFunc(GL_ONE, GL_ZERO);
glColor4f(0,0,0, 1.0);
refDrawingFont.drawString(demodLabel, uxPos+shadowOfsX, hPos+shadowOfsY, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER);
refDrawingFont.drawString(demodLabel, uxPos-shadowOfsX, hPos-shadowOfsY, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER);
glColor4f(color.r, color.g, color.b, 1.0);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
refDrawingFont.drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER);
} else {
glBlendFunc(GL_ONE, GL_ZERO);
glColor4f(0,0,0, 1.0);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
refDrawingFont.drawString(demodLabel, uxPos+shadowOfsX, hPos+shadowOfsY, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER);
refDrawingFont.drawString(demodLabel, uxPos-shadowOfsX, hPos-shadowOfsY, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER);
glColor4f(color.r, color.g, color.b, 1.0);
refDrawingFont.drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER);
}
glDisable(GL_BLEND);
}
void PrimaryGLContext::DrawDemod(const DemodulatorInstancePtr& demod, const RGBA4f& color, long long center_freq, long long srate) {
if (!demod) {
return;
}
if (!srate) {
srate = wxGetApp().getSampleRate();
}
if (center_freq == -1) {
center_freq = wxGetApp().getFrequency();
}
long long demodFreq = demod->getFrequency();
if (demod->isDeltaLock()) {
demodFreq = wxGetApp().getFrequency() + demod->getDeltaLockOfs();
}
float uxPos = (float) (demodFreq - (center_freq - srate / 2)) / (float) srate;
glDisable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glColor4f(color.r, color.g, color.b, 0.6f);
float ofs = ((float) demod->getBandwidth()) / (float) srate;
float ofsLeft = (demod->getDemodulatorType()!="USB")?ofs:0, ofsRight = (demod->getDemodulatorType()!="LSB")?ofs:0;
glBegin(GL_LINES);
glVertex3f((uxPos - 0.5) * 2.0, 1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0, -1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0 - ofsLeft, 1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0 - ofsLeft, -1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0 + ofsRight, 1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0 + ofsRight, -1.0, 0.0);
glEnd();
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glColor4f(color.r, color.g, color.b, 0.2*hoverAlpha);
glBegin(GL_QUADS);
glVertex3f((uxPos - 0.5) * 2.0 - ofsLeft, 1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0 - ofsLeft, -1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0 + ofsRight, -1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0 + ofsRight, 1.0, 0.0);
glEnd();
GLint vp[4];
glGetIntegerv( GL_VIEWPORT, vp);
float viewHeight = (float) vp[3];
float viewWidth = (float) vp[2];
float labelHeight = 20.0 / viewHeight;
float xOfs = (2.0 / viewWidth);
float yOfs = (2.0 / viewHeight);
float hPos = labelHeight;
glDisable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_COLOR_MATERIAL);
glEnable(GL_BLEND);
//Displayed string is wstring, so use wxString to do the heavy lifting of converting getDemodulatorType()...
wxString demodStr;
demodStr.assign(demod->getDemodulatorType());
if (demodStr == "LSB") {
uxPos -= xOfs;
} else if (demodStr == "USB") {
uxPos += xOfs;
}
// advanced demodulators start here
// if (demod->getDemodulatorCons() > 0) {
// demodStr = demodStr + std::to_string(demod->getDemodulatorCons());
// }
// add lock to string if we have an lock
if(demod->getDemodulatorLock()) {
demodStr += " Lock";
}
// else {
// demodStr = demodStr + " UnLock";
// }
//Shift the user label from the modem label more for the bigger
//font sizes so they do not step on each other...
double heightShift = GLFont::getScaleFactor();
//demodulator user label if present: type is displayed above the label, which is at the bottom of the screen.
if (!demod->getDemodulatorUserLabel().empty()) {
drawSingleDemodLabel(demodStr.ToStdWstring(), uxPos, hPos * 1.2 + hPos * 1.2 * heightShift, xOfs, yOfs, GLFont::GLFONT_ALIGN_CENTER);
drawSingleDemodLabel(demod->getDemodulatorUserLabel(), uxPos, hPos * 1.2, xOfs, yOfs, GLFont::GLFONT_ALIGN_CENTER);
}
else {
drawSingleDemodLabel(demodStr.ToStdWstring(), uxPos, hPos * 1.2, xOfs, yOfs, GLFont::GLFONT_ALIGN_CENTER);
}
glDisable(GL_BLEND);
}
void PrimaryGLContext::drawSingleDemodLabel(const std::wstring& demodStr, float uxPos, float hPos, float xOfs, float yOfs, GLFont::Align demodAlign) {
GLFont::Drawer refDrawingFont = GLFont::getFont(16, GLFont::getScaleFactor());
glColor3f(0, 0, 0);
refDrawingFont.drawString(demodStr, 2.0 * (uxPos - 0.5) + xOfs, -1.0 + hPos - yOfs, demodAlign, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
glColor3f(1, 1, 1);
refDrawingFont.drawString(demodStr, 2.0 * (uxPos - 0.5), -1.0 + hPos, demodAlign, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
}
void PrimaryGLContext::DrawFreqSelector(float uxPos, const RGBA4f& color, float w, long long /* center_freq */, long long srate) {
DemodulatorInstancePtr demod = wxGetApp().getDemodMgr().getCurrentModem();
long long bw = 0;
std::string last_type = wxGetApp().getDemodMgr().getLastDemodulatorType();
if (!demod) {
bw = wxGetApp().getDemodMgr().getLastBandwidth();
} else {
bw = demod->getBandwidth();
}
if (!srate) {
srate = wxGetApp().getSampleRate();
}
glDisable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glColor4f(color.r, color.g, color.b, 0.6f);
glBegin(GL_LINES);
glVertex3f((uxPos - 0.5) * 2.0, 1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0, -1.0, 0.0);
float ofs;
if (w) {
ofs = w;
} else {
ofs = ((float) bw) / (float) srate;
}
if (last_type != "USB") {
glVertex3f((uxPos - 0.5) * 2.0 - ofs, 1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0 - ofs, -1.0, 0.0);
}
if (last_type != "LSB") {
glVertex3f((uxPos - 0.5) * 2.0 + ofs, 1.0, 0.0);
glVertex3f((uxPos - 0.5) * 2.0 + ofs, -1.0, 0.0);
}
glEnd();
glDisable(GL_BLEND);
}
void PrimaryGLContext::DrawRangeSelector(float uxPos1, float uxPos2, const RGBA4f& color) {
if (uxPos2 < uxPos1) {
float temp = uxPos2;
uxPos2=uxPos1;
uxPos1=temp;
}
std::string last_type = wxGetApp().getDemodMgr().getLastDemodulatorType();
glDisable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glColor4f(color.r, color.g, color.b, 0.6f);
glLineWidth((last_type == "USB")?2.0:1.0);
glBegin(GL_LINES);
glVertex3f((uxPos1 - 0.5) * 2.0, 1.0, 0.0);
glVertex3f((uxPos1 - 0.5) * 2.0, -1.0, 0.0);
glEnd();
glLineWidth((last_type == "LSB")?2.0:1.0);
glBegin(GL_LINES);
glVertex3f((uxPos2 - 0.5) * 2.0, 1.0, 0.0);
glVertex3f((uxPos2 - 0.5) * 2.0, -1.0, 0.0);
glEnd();
glLineWidth(1.0);
glDisable(GL_BLEND);
}
void PrimaryGLContext::BeginDraw(float r, float g, float b) {
glClearColor(r,g,b, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void PrimaryGLContext::EndDraw() {
// glFlush();
// CheckGLError();
}
void PrimaryGLContext::setHoverAlpha(float hoverAlpha_in) {
hoverAlpha = hoverAlpha_in;
}

View File

@ -0,0 +1,39 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/glcanvas.h"
#include "wx/timer.h"
#include <vector>
#include <queue>
#include "CubicSDRDefs.h"
#include "GLFont.h"
#include "DemodulatorMgr.h"
#include "ColorTheme.h"
class PrimaryGLContext: public wxGLContext {
public:
PrimaryGLContext(wxGLCanvas *canvas, wxGLContext *sharedContext, wxGLContextAttrs* ctxAttrs);
static wxString glGetwxString(GLenum name);
static void CheckGLError();
void BeginDraw(float r, float g, float b);
void EndDraw();
void DrawFreqSelector(float uxPos, const RGBA4f& color, float w = 0, long long center_freq = -1, long long srate = 0);
void DrawRangeSelector(float uxPos1, float uxPos2, const RGBA4f& color);
void DrawDemod(const DemodulatorInstancePtr& demod, const RGBA4f& color, long long center_freq = -1, long long srate = 0);
void DrawDemodInfo(const DemodulatorInstancePtr& demod, const RGBA4f& color, long long center_freq = -1, long long srate = 0, bool centerline = false);
void DrawFreqBwInfo(long long freq, int bw, const RGBA4f& color, long long center_freq = - 1, long long srate = 0, bool stack = false, bool centerline = false);
void setHoverAlpha(float hoverAlpha_in);
private:
float hoverAlpha;
void drawSingleDemodLabel(const std::wstring& demodStr, float uxPos, float hPos, float xOfs, float yOfs, GLFont::Align demodAlign);
};

View File

@ -0,0 +1,292 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ScopeCanvas.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#if !wxUSE_GLCANVAS
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
#endif
#include "CubicSDR.h"
#include <algorithm>
#include <cmath>
wxBEGIN_EVENT_TABLE(ScopeCanvas, wxGLCanvas) EVT_PAINT(ScopeCanvas::OnPaint)
EVT_IDLE(ScopeCanvas::OnIdle)
EVT_MOTION(ScopeCanvas::OnMouseMoved)
EVT_LEFT_DOWN(ScopeCanvas::OnMouseDown)
EVT_LEFT_UP(ScopeCanvas::OnMouseReleased)
EVT_RIGHT_DOWN(ScopeCanvas::OnMouseRightDown)
EVT_RIGHT_UP(ScopeCanvas::OnMouseRightReleased)
EVT_LEAVE_WINDOW(ScopeCanvas::OnMouseLeftWindow)
EVT_ENTER_WINDOW(ScopeCanvas::OnMouseEnterWindow)
wxEND_EVENT_TABLE()
ScopeCanvas::ScopeCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs) : InteractiveCanvas(parent, dispAttrs), ppmMode(false), ctr(0), ctrTarget(0), dragAccel(0) {
glContext = new ScopeContext(this, &wxGetApp().GetContext(this), wxGetApp().GetContextAttributes());
inputData->set_max_num_items(2);
bgPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_Y);
bgPanel.setSize(1.0, 0.5f);
bgPanel.setPosition(0.0, -0.5f);
panelSpacing = 0.4f;
parentPanel.addChild(&scopePanel);
parentPanel.addChild(&spectrumPanel);
parentPanel.setFill(GLPanel::GLPANEL_FILL_NONE);
scopePanel.setSize(1.0,-1.0);
spectrumPanel.setSize(1.0,-1.0);
showDb = true;
spectrumPanel.setShowDb(showDb);
//dB offset is a RF value, has no meaning in audio, disable it.
spectrumPanel.setUseDBOffset(false);
}
ScopeCanvas::~ScopeCanvas() = default;
bool ScopeCanvas::scopeVisible() {
float panelInterval = (2.0 + panelSpacing);
ctrTarget = abs(round(ctr / panelInterval));
if (ctrTarget == 0 || dragAccel || (ctr != ctrTarget)) {
return true;
}
return false;
}
bool ScopeCanvas::spectrumVisible() {
float panelInterval = (2.0 + panelSpacing);
ctrTarget = abs(round(ctr / panelInterval));
if (ctrTarget == 1 || dragAccel || (ctr != ctrTarget)) {
return true;
}
return false;
}
void ScopeCanvas::setDeviceName(std::string device_name) {
deviceName = device_name;
deviceName.append(" ");
}
void ScopeCanvas::setPPMMode(bool ppmMode_in) {
ppmMode = ppmMode_in;
}
bool ScopeCanvas::getPPMMode() const {
return ppmMode;
}
void ScopeCanvas::setShowDb(bool show) {
this->showDb = show;
}
bool ScopeCanvas::getShowDb() const {
return showDb;
}
void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
// wxPaintDC dc(this);
const wxSize ClientSize = GetClientSize() * GetContentScaleFactor();
ScopeRenderDataPtr avData;
while (inputData->try_pop(avData)) {
if (!avData->spectrum) {
scopePanel.setMode(avData->mode);
if (!avData->waveform_points.empty()) {
scopePanel.setPoints(avData->waveform_points);
}
} else {
if (!avData->waveform_points.empty()) {
spectrumPanel.setPoints(avData->waveform_points);
spectrumPanel.setFloorValue(avData->fft_floor);
spectrumPanel.setCeilValue(avData->fft_ceil);
spectrumPanel.setBandwidth((avData->sampleRate/2)*1000);
spectrumPanel.setFreq((avData->sampleRate/4)*1000);
spectrumPanel.setFFTSize(avData->fft_size);
spectrumPanel.setShowDb(showDb);
}
}
}
glContext->SetCurrent(*this);
initGLExtensions();
glViewport(0, 0, ClientSize.x, ClientSize.y);
// TODO: find out why frontbuffer drawing has stopped working in wx 3.1.0?
// if (scopePanel.getMode() == ScopePanel::SCOPE_MODE_XY && !spectrumVisible()) {
// glDrawBuffer(GL_FRONT);
// glContext->DrawBegin(false);
// } else {
// glDrawBuffer(GL_BACK);
glContext->DrawBegin();
bgPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground * 3.0, RGBA4f(0,0,0,1));
bgPanel.calcTransform(CubicVR::mat4::identity());
bgPanel.draw();
// }
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glLoadMatrixf(CubicVR::mat4::perspective(45.0, 1.0, 1.0, 1000.0).to_ptr());
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
CubicVR::mat4 modelView = CubicVR::mat4::lookat(0, 0, -1.205f, 0, 0, 0, 0, -1, 0);
float panelWidth = 1.0;
float panelInterval = (panelWidth * 2.0 + panelSpacing);
if (!mouseTracker.mouseDown()) {
ctrTarget = round(ctr / panelInterval);
if (ctrTarget < -1.0) {
ctrTarget = -1.0;
} else if (ctrTarget > 0.0) {
ctrTarget = 0.0;
}
ctrTarget *= panelInterval;
if (!dragAccel) {
if (ctr != ctrTarget) {
ctr += (ctrTarget-ctr)*0.2;
}
if (abs(ctr - ctrTarget) < 0.001) {
ctr=ctrTarget;
}
} else {
dragAccel -= dragAccel * 0.1;
if ((abs(dragAccel) < 0.2) || (ctr < (ctrTarget-panelInterval/2.0)) || (ctr > (ctrTarget+panelInterval/2.0)) ) {
dragAccel = 0;
} else {
ctr += dragAccel;
}
}
}
float roty = 0;
scopePanel.setPosition(ctr, 0);
if (scopeVisible()) {
scopePanel.contentsVisible = true;
roty = atan2(scopePanel.pos[0],1.2);
scopePanel.rot[1] = -(roty * (180.0 / M_PI));
} else {
scopePanel.contentsVisible = false;
}
spectrumPanel.setPosition(panelInterval+ctr, 0);
if (spectrumVisible()) {
spectrumPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground * 2.0, RGBA4f(0,0,0,1));
spectrumPanel.contentsVisible = true;
roty = atan2(spectrumPanel.pos[0],1.2);
spectrumPanel.rot[1] = -(roty * (180.0 / M_PI));
} else {
spectrumPanel.contentsVisible = false;
}
parentPanel.calcTransform(modelView);
parentPanel.draw();
if (spectrumVisible()) {
spectrumPanel.drawChildren();
}
glLoadMatrixf(scopePanel.transform.to_ptr());
if (!deviceName.empty()) {
glContext->DrawDeviceName(deviceName);
}
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glContext->DrawTunerTitles(ppmMode);
glContext->DrawEnd();
// if (scopePanel.getMode() != ScopePanel::SCOPE_MODE_XY || spectrumVisible()) {
SwapBuffers();
// }
}
void ScopeCanvas::OnIdle(wxIdleEvent &event) {
Refresh();
event.RequestMore();
}
ScopeRenderDataQueuePtr ScopeCanvas::getInputQueue() {
return inputData;
}
void ScopeCanvas::OnMouseMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseMoved(event);
if (mouseTracker.mouseDown()) {
dragAccel = 4.0*mouseTracker.getDeltaMouseX();
ctr += dragAccel;
}
}
void ScopeCanvas::OnMouseWheelMoved(wxMouseEvent& /* event */) {
}
void ScopeCanvas::OnMouseDown(wxMouseEvent& event) {
InteractiveCanvas::OnMouseDown(event);
}
void ScopeCanvas::OnMouseReleased(wxMouseEvent& event) {
InteractiveCanvas::OnMouseReleased(event);
}
void ScopeCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
InteractiveCanvas::OnMouseEnterWindow(event);
if (!helpTip.empty()) {
setStatusText(helpTip);
}
SetCursor(wxCURSOR_SIZEWE);
}
void ScopeCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
InteractiveCanvas::OnMouseLeftWindow(event);
}
void ScopeCanvas::setHelpTip(std::string tip) {
helpTip = tip;
}
void ScopeCanvas::OnKeyDown(wxKeyEvent& event) {
InteractiveCanvas::OnKeyDown(event);
switch (event.GetKeyCode()) {
case 'B':
setShowDb(!getShowDb());
break;
default:
event.Skip();
}
}
void ScopeCanvas::OnKeyUp(wxKeyEvent& event) {
InteractiveCanvas::OnKeyUp(event);
}

View File

@ -0,0 +1,75 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/glcanvas.h"
#include "wx/timer.h"
#include <vector>
#include <queue>
#include <memory>
#include "ScopeContext.h"
#include "ScopeVisualProcessor.h"
#include "ScopePanel.h"
#include "SpectrumPanel.h"
#include "InteractiveCanvas.h"
class ScopeCanvas: public InteractiveCanvas {
public:
ScopeCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs);
~ScopeCanvas();
//This is public because it is indeed forwarded from
//AppFrame::OnGlobalKeyDown, because global key handler intercepts
//calls in all windows.
void OnKeyDown(wxKeyEvent& event);
//This is public because it is indeed forwarded from
//AppFrame::OnGlobalKeyUp, because global key handler intercepts
//calls in all windows.
void OnKeyUp(wxKeyEvent& event);
void setDeviceName(std::string device_name);
void setPPMMode(bool ppmMode_in);
bool getPPMMode() const;
void setShowDb(bool showDb);
bool getShowDb() const;
bool scopeVisible();
bool spectrumVisible();
void setHelpTip(std::string tip);
ScopeRenderDataQueuePtr getInputQueue();
private:
void OnPaint(wxPaintEvent& event);
void OnIdle(wxIdleEvent &event);
void OnMouseMoved(wxMouseEvent& event);
void OnMouseWheelMoved(wxMouseEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnMouseReleased(wxMouseEvent& event);
void OnMouseEnterWindow(wxMouseEvent& event);
void OnMouseLeftWindow(wxMouseEvent& event);
ScopeRenderDataQueuePtr inputData = std::make_shared<ScopeRenderDataQueue>();
ScopePanel scopePanel;
GLPanel parentPanel;
SpectrumPanel spectrumPanel;
GLPanel bgPanel;
ScopeContext *glContext;
std::string deviceName;
bool ppmMode;
bool showDb;
float panelSpacing;
float ctr;
float ctrTarget;
float dragAccel;
std::string helpTip;
// event table
wxDECLARE_EVENT_TABLE();
};

View File

@ -0,0 +1,74 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ScopeContext.h"
#include "ScopeCanvas.h"
#include "ColorTheme.h"
ScopeContext::ScopeContext(ScopeCanvas *canvas, wxGLContext *sharedContext, wxGLContextAttrs *ctxAttrs) :
PrimaryGLContext(canvas, sharedContext, ctxAttrs) {
glDisable (GL_CULL_FACE);
glDisable (GL_DEPTH_TEST);
glMatrixMode (GL_PROJECTION);
glLoadIdentity();
}
void ScopeContext::DrawBegin(bool clear) {
if (clear) {
glClearColor(ThemeMgr::mgr.currentTheme->scopeBackground.r, ThemeMgr::mgr.currentTheme->scopeBackground.g,
ThemeMgr::mgr.currentTheme->scopeBackground.b, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
glMatrixMode (GL_MODELVIEW);
glLoadIdentity();
glDisable (GL_TEXTURE_2D);
}
void ScopeContext::DrawTunerTitles(bool ppmMode) {
glLoadIdentity();
GLint vp[4];
glGetIntegerv(GL_VIEWPORT, vp);
float viewHeight = (float) vp[3];
float hPos = (float) (13) / viewHeight;
glColor3f(0.65f, 0.65f, 0.65f);
GLFont::Drawer refDrawingFont = GLFont::getFont(12, GLFont::getScaleFactor());
//better position frequency/bandwidth labels according to font scale
double shiftFactor = GLFont::getScaleFactor()+0.5;
refDrawingFont.drawString(ppmMode?"Device PPM":"Frequency", -0.66f, -1.0 +hPos*shiftFactor, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
refDrawingFont.drawString("Bandwidth", 0.0, -1.0 +hPos*shiftFactor, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
refDrawingFont.drawString("Center Frequency", 0.66f, -1.0 +hPos*shiftFactor, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
}
void ScopeContext::DrawDeviceName(const std::string& deviceName) {
GLint vp[4];
glGetIntegerv(GL_VIEWPORT, vp);
float viewHeight = (float) vp[3];
float hPos = (float) (viewHeight - 20) / viewHeight;
glColor3f(0.65f, 0.65f, 0.65f);
GLFont::getFont(12, GLFont::getScaleFactor()).drawString(deviceName, 1.0, hPos, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
}
void ScopeContext::DrawEnd() {
// glFlush();
// CheckGLError();
}
void ScopeContext::DrawDivider() {
glColor3f(1.0, 1.0, 1.0);
glBegin (GL_LINES);
glVertex2f(0.0, -1.0);
glVertex2f(0.0, 1.0);
glEnd();
}

View File

@ -0,0 +1,22 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "PrimaryGLContext.h"
#include "Gradient.h"
class ScopeCanvas;
class ScopeContext: public PrimaryGLContext {
public:
ScopeContext(ScopeCanvas *canvas, wxGLContext *sharedContext, wxGLContextAttrs *ctxAttrs);
void DrawBegin(bool clear=true);
void DrawTunerTitles(bool ppmMode=false);
void DrawDeviceName(const std::string& deviceName);
void DrawDivider();
void DrawEnd();
private:
};

View File

@ -0,0 +1,334 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "SpectrumCanvas.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#if !wxUSE_GLCANVAS
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
#endif
#include "CubicSDR.h"
#include "CubicSDRDefs.h"
#include "AppFrame.h"
#include <algorithm>
#include "WaterfallCanvas.h"
wxBEGIN_EVENT_TABLE(SpectrumCanvas, wxGLCanvas) EVT_PAINT(SpectrumCanvas::OnPaint)
EVT_IDLE(SpectrumCanvas::OnIdle)
EVT_MOTION(SpectrumCanvas::OnMouseMoved)
EVT_LEFT_DOWN(SpectrumCanvas::OnMouseDown)
EVT_LEFT_UP(SpectrumCanvas::OnMouseReleased)
EVT_ENTER_WINDOW(SpectrumCanvas::OnMouseEnterWindow)
EVT_LEAVE_WINDOW(SpectrumCanvas::OnMouseLeftWindow)
EVT_MOUSEWHEEL(SpectrumCanvas::OnMouseWheelMoved)
EVT_RIGHT_DOWN(SpectrumCanvas::OnMouseRightDown)
EVT_RIGHT_UP(SpectrumCanvas::OnMouseRightReleased)
wxEND_EVENT_TABLE()
SpectrumCanvas::SpectrumCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs) :
InteractiveCanvas(parent, dispAttrs), waterfallCanvas(nullptr) {
glContext = new PrimaryGLContext(this, &wxGetApp().GetContext(this), wxGetApp().GetContextAttributes());
visualDataQueue->set_max_num_items(1);
SetCursor(wxCURSOR_SIZEWE);
scaleFactor = 1.0;
resetScaleFactor = false;
scaleFactorEnabled = false;
bwChange = 0.0;
}
SpectrumCanvas::~SpectrumCanvas() = default;
void SpectrumCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
// wxPaintDC dc(this);
const wxSize ClientSize = GetClientSize() * GetContentScaleFactor();
SpectrumVisualDataPtr vData;
if (visualDataQueue->try_pop(vData)) {
if (vData) {
spectrumPanel.setPoints(vData->spectrum_points);
spectrumPanel.setPeakPoints(vData->spectrum_hold_points);
spectrumPanel.setFloorValue(vData->fft_floor);
spectrumPanel.setCeilValue(vData->fft_ceiling);
}
}
if (resetScaleFactor) {
scaleFactor += (1.0-scaleFactor)*0.05;
if (fabs(scaleFactor-1.0) < 0.01) {
scaleFactor = 1.0;
resetScaleFactor = false;
}
updateScaleFactor(scaleFactor);
}
glContext->SetCurrent(*this);
initGLExtensions();
glViewport(0, 0, ClientSize.x, ClientSize.y);
glContext->BeginDraw(0,0,0);
spectrumPanel.setFreq(getCenterFrequency());
spectrumPanel.setBandwidth(getBandwidth());
spectrumPanel.calcTransform(CubicVR::mat4::identity());
spectrumPanel.draw();
glLoadIdentity();
auto demods = wxGetApp().getDemodMgr().getDemodulators();
auto activeDemodulator = wxGetApp().getDemodMgr().getActiveContextModem();
for (auto & demod : demods) {
if (!demod->isActive()) {
continue;
}
glContext->DrawDemodInfo(demod, ThemeMgr::mgr.currentTheme->fftHighlight, getCenterFrequency(), getBandwidth(), activeDemodulator==demod);
}
if (waterfallCanvas && !activeDemodulator) {
MouseTracker *wfmt = waterfallCanvas->getMouseTracker();
if (wfmt->mouseInView()) {
int snap = wxGetApp().getFrequencySnap();
long long freq = getFrequencyAt(wfmt->getMouseX());
if (snap > 1) {
freq = roundf((float)freq/(float)snap)*snap;
}
auto lastActiveDemodulator = wxGetApp().getDemodMgr().getCurrentModem();
bool isNew = (((waterfallCanvas->isShiftDown() || (lastActiveDemodulator && !lastActiveDemodulator->isActive())) && lastActiveDemodulator) || (!lastActiveDemodulator));
glContext->DrawFreqBwInfo(freq, wxGetApp().getDemodMgr().getLastBandwidth(), isNew?ThemeMgr::mgr.currentTheme->waterfallNew:ThemeMgr::mgr.currentTheme->waterfallHover, getCenterFrequency(), getBandwidth(), true, true);
}
}
glContext->EndDraw();
spectrumPanel.drawChildren();
SwapBuffers();
}
void SpectrumCanvas::OnIdle(wxIdleEvent &event) {
Refresh();
event.RequestMore();
}
void SpectrumCanvas::moveCenterFrequency(long long freqChange) {
long long freq = wxGetApp().getFrequency();
if (isView) {
if (centerFreq - freqChange < bandwidth/2) {
centerFreq = bandwidth/2;
} else {
centerFreq -= freqChange;
}
if (waterfallCanvas) {
waterfallCanvas->setCenterFrequency(centerFreq);
}
long long bwOfs = (centerFreq > freq) ? ((long long) bandwidth / 2) : (-(long long) bandwidth / 2);
long long freqEdge = centerFreq + bwOfs;
if (abs(freq - freqEdge) > (wxGetApp().getSampleRate() / 2)) {
freqChange = -((centerFreq > freq) ? (freqEdge - freq - (wxGetApp().getSampleRate() / 2)) : (freqEdge - freq + (wxGetApp().getSampleRate() / 2)));
} else {
freqChange = 0;
}
}
if (freqChange) {
if (freq - freqChange < wxGetApp().getSampleRate()/2) {
freq = wxGetApp().getSampleRate()/2;
} else {
freq -= freqChange;
}
wxGetApp().setFrequency(freq);
}
}
void SpectrumCanvas::setShowDb(bool showDb) {
spectrumPanel.setShowDb(showDb);
}
bool SpectrumCanvas::getShowDb() {
return spectrumPanel.getShowDb();
}
void SpectrumCanvas::setUseDBOfs(bool showDb) {
spectrumPanel.setUseDBOffset(showDb);
}
bool SpectrumCanvas::getUseDBOfs() {
return spectrumPanel.getUseDBOffset();
}
void SpectrumCanvas::setView(long long center_freq_in, int bandwidth_in) {
bwChange += bandwidth_in-bandwidth;
#define BW_RESET_TH 400000
if (bwChange > BW_RESET_TH || bwChange < -BW_RESET_TH) {
resetScaleFactor = true;
bwChange = 0;
}
InteractiveCanvas::setView(center_freq_in, bandwidth_in);
}
void SpectrumCanvas::disableView() {
InteractiveCanvas::disableView();
}
void SpectrumCanvas::setScaleFactorEnabled(bool en) {
scaleFactorEnabled = en;
}
void SpectrumCanvas::setFFTSize(int fftSize) {
spectrumPanel.setFFTSize(fftSize);
}
void SpectrumCanvas::updateScaleFactor(float factor) {
SpectrumVisualProcessor *sp = wxGetApp().getSpectrumProcessor();
FFTVisualDataThread *wdt = wxGetApp().getAppFrame()->getWaterfallDataThread();
SpectrumVisualProcessor *wp = wdt->getProcessor();
scaleFactor = factor;
sp->setScaleFactor(factor);
wp->setScaleFactor(factor);
}
void SpectrumCanvas::updateScaleFactorFromYMove(float yDeltaMouseMove) {
scaleFactor += yDeltaMouseMove * 2.0;
if (scaleFactor < 0.25) {
scaleFactor = 0.25;
}
if (scaleFactor > 10.0) {
scaleFactor = 10.0;
}
resetScaleFactor = false;
updateScaleFactor(scaleFactor);
}
void SpectrumCanvas::OnMouseMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseMoved(event);
if (mouseTracker.mouseDown()) {
int freqChange = mouseTracker.getDeltaMouseX() * getBandwidth();
if (freqChange != 0) {
moveCenterFrequency(freqChange);
}
}
else if (scaleFactorEnabled && mouseTracker.mouseRightDown()) {
updateScaleFactorFromYMove(mouseTracker.getDeltaMouseY());
} else {
if (scaleFactorEnabled) {
setStatusText("Drag horizontal to adjust center frequency. Arrow keys or wheel to navigate/zoom bandwidth. Right-drag or SHIFT+UP/DOWN to adjust visual gain, right-click to reset it. 'B' to toggle decibels display.");
} else {
setStatusText("Displaying spectrum of active demodulator.");
}
}
}
void SpectrumCanvas::OnMouseDown(wxMouseEvent& event) {
SetCursor(wxCURSOR_SIZEWE);
mouseTracker.setVertDragLock(true);
InteractiveCanvas::OnMouseDown(event);
}
void SpectrumCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseWheelMoved(event);
if (waterfallCanvas) {
waterfallCanvas->OnMouseWheelMoved(event);
}
}
void SpectrumCanvas::OnMouseReleased(wxMouseEvent& event) {
mouseTracker.setVertDragLock(false);
InteractiveCanvas::OnMouseReleased(event);
SetCursor(wxCURSOR_CROSS);
}
void SpectrumCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
InteractiveCanvas::OnMouseEnterWindow(event);
SetCursor(wxCURSOR_CROSS);
#ifdef _WIN32
if (wxGetApp().getAppFrame()->canFocus()) {
this->SetFocus();
}
#endif
}
void SpectrumCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
InteractiveCanvas::OnMouseLeftWindow(event);
SetCursor(wxCURSOR_CROSS);
}
void SpectrumCanvas::attachWaterfallCanvas(WaterfallCanvas* canvas_in) {
waterfallCanvas = canvas_in;
}
SpectrumVisualDataQueuePtr SpectrumCanvas::getVisualDataQueue() {
return visualDataQueue;
}
void SpectrumCanvas::OnMouseRightDown(wxMouseEvent& event) {
SetCursor(wxCURSOR_SIZENS);
mouseTracker.setHorizDragLock(true);
mouseTracker.OnMouseRightDown(event);
scaleFactor = wxGetApp().getSpectrumProcessor()->getScaleFactor();
}
void SpectrumCanvas::OnMouseRightReleased(wxMouseEvent& event) {
SetCursor(wxCURSOR_CROSS);
mouseTracker.setHorizDragLock(false);
if (!mouseTracker.getOriginDeltaMouseY()) {
resetScaleFactor = true;
wxGetApp().getSpectrumProcessor()->setPeakHold(wxGetApp().getSpectrumProcessor()->getPeakHold());
//make the peak hold act on the current dmod also, like a zoomed-in version.
if (wxGetApp().getDemodSpectrumProcessor()) {
wxGetApp().getDemodSpectrumProcessor()->setPeakHold(wxGetApp().getSpectrumProcessor()->getPeakHold());
}
}
mouseTracker.OnMouseRightReleased(event);
}
void SpectrumCanvas::OnKeyDown(wxKeyEvent& event) {
InteractiveCanvas::OnKeyDown(event);
switch (event.GetKeyCode()) {
case 'B':
setShowDb(!getShowDb());
break;
default:
event.Skip();
}
}
void SpectrumCanvas::OnKeyUp(wxKeyEvent& event) {
InteractiveCanvas::OnKeyUp(event);
}

View File

@ -0,0 +1,81 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <vector>
#include <queue>
#include <memory>
#include "InteractiveCanvas.h"
#include "PrimaryGLContext.h"
#include "MouseTracker.h"
#include "SpectrumVisualProcessor.h"
#include "SpectrumPanel.h"
class WaterfallCanvas;
class SpectrumCanvas: public InteractiveCanvas {
public:
SpectrumCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs);
~SpectrumCanvas() override;
//This is public because it is indeed forwarded from
//AppFrame::OnGlobalKeyDown, because global key handler intercepts
//calls in all windows.
void OnKeyDown(wxKeyEvent& event);
//This is public because it is indeed forwarded from
//AppFrame::OnGlobalKeyUp, because global key handler intercepts
//calls in all windows.
void OnKeyUp(wxKeyEvent& event);
void attachWaterfallCanvas(WaterfallCanvas *canvas_in);
void moveCenterFrequency(long long freqChange);
void setShowDb(bool showDb);
bool getShowDb();
void setUseDBOfs(bool showDb);
bool getUseDBOfs();
void setView(long long center_freq_in, int bandwidth_in);
void disableView() override;
void setScaleFactorEnabled(bool en);
void setFFTSize(int fftSize);
SpectrumVisualDataQueuePtr getVisualDataQueue();
// called by Waterfall to forward the update of the vertical scale.
void updateScaleFactorFromYMove(float yDeltaMouseMove);
private:
void OnPaint(wxPaintEvent& event);
void OnIdle(wxIdleEvent &event);
void OnMouseMoved(wxMouseEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnMouseWheelMoved(wxMouseEvent& event);
void OnMouseReleased(wxMouseEvent& event);
void OnMouseEnterWindow(wxMouseEvent& event);
void OnMouseLeftWindow(wxMouseEvent& event);
void OnMouseRightDown(wxMouseEvent& event);
void OnMouseRightReleased(wxMouseEvent& event);
void updateScaleFactor(float factor);
PrimaryGLContext *glContext;
WaterfallCanvas *waterfallCanvas;
SpectrumPanel spectrumPanel;
float scaleFactor;
int bwChange;
bool resetScaleFactor, scaleFactorEnabled;
SpectrumVisualDataQueuePtr visualDataQueue = std::make_shared<SpectrumVisualDataQueue>();
// event table
wxDECLARE_EVENT_TABLE();
};

View File

@ -0,0 +1,474 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "TuningCanvas.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#if !wxUSE_GLCANVAS
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
#endif
#include "CubicSDR.h"
#include "CubicSDRDefs.h"
#include "AppFrame.h"
#include <algorithm>
wxBEGIN_EVENT_TABLE(TuningCanvas, wxGLCanvas) EVT_PAINT(TuningCanvas::OnPaint)
EVT_IDLE(TuningCanvas::OnIdle)
EVT_MOTION(TuningCanvas::OnMouseMoved)
EVT_LEFT_DOWN(TuningCanvas::OnMouseDown)
EVT_LEFT_UP(TuningCanvas::OnMouseReleased)
EVT_RIGHT_DOWN(TuningCanvas::OnMouseRightDown)
EVT_RIGHT_UP(TuningCanvas::OnMouseRightReleased)
EVT_LEAVE_WINDOW(TuningCanvas::OnMouseLeftWindow)
EVT_ENTER_WINDOW(TuningCanvas::OnMouseEnterWindow)
EVT_MOUSEWHEEL(TuningCanvas::OnMouseWheelMoved)
//EVT_KEY_DOWN(TuningCanvas::OnKeyDown)
//EVT_KEY_UP(TuningCanvas::OnKeyUp)
wxEND_EVENT_TABLE()
TuningCanvas::TuningCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs) :
InteractiveCanvas(parent, dispAttrs), dragAccum(0), uxDown(0), top(false), bottom(false), freq(-1), bw(-1), center(-1), halfBand(false) {
glContext = new TuningContext(this, &wxGetApp().GetContext(this), wxGetApp().GetContextAttributes());
hoverIndex = 0;
downIndex = 0;
hoverState = TUNING_HOVER_NONE;
downState = TUNING_HOVER_NONE;
dragging = false;
freqDP = -1.0;
freqW = (1.0f / 3.0f) * 2.0f;
bwDP = -1.0 + (2.25 / 3.0);
bwW = (1.0 / 4.0) * 2.0;
centerDP = -1.0f + (2.0f / 3.0f) * 2.0f;
centerW = (1.0f / 3.0f) * 2.0f;
currentPPM = lastPPM = 0;
}
TuningCanvas::~TuningCanvas() = default;
bool TuningCanvas::changed() {
auto activeDemod = wxGetApp().getDemodMgr().getCurrentModem();
long long current_freq = 0;
if (activeDemod != nullptr) {
freq = activeDemod->getFrequency();
}
long long current_bw = wxGetApp().getDemodMgr().getLastBandwidth();
long long current_center = wxGetApp().getFrequency();
if (current_freq != freq || current_bw != bw || current_center != center) {
return true;
}
return false;
}
void TuningCanvas::setHalfBand(bool hb) {
halfBand = hb;
Refresh();
}
void TuningCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
// wxPaintDC dc(this);
const wxSize ClientSize = GetClientSize() * GetContentScaleFactor();
glContext->SetCurrent(*this);
initGLExtensions();
glViewport(0, 0, ClientSize.x, ClientSize.y);
glContext->DrawBegin();
auto activeDemod = wxGetApp().getDemodMgr().getCurrentModem();
freq = 0;
if (activeDemod != nullptr) {
freq = activeDemod->getFrequency();
}
bw = wxGetApp().getDemodMgr().getLastBandwidth();
center = wxGetApp().getFrequency();
if (mouseTracker.mouseDown()) {
glContext->Draw(ThemeMgr::mgr.currentTheme->tuningBarDark.r, ThemeMgr::mgr.currentTheme->tuningBarDark.g, ThemeMgr::mgr.currentTheme->tuningBarDark.b,
0.75, mouseTracker.getOriginMouseX(), mouseTracker.getMouseX());
}
RGBA4f clr = top ? ThemeMgr::mgr.currentTheme->tuningBarUp : ThemeMgr::mgr.currentTheme->tuningBarDown;
RGBA4f clrDark = ThemeMgr::mgr.currentTheme->tuningBarDark;
RGBA4f clrMid = ThemeMgr::mgr.currentTheme->tuningBarLight;
glContext->DrawTunerBarIndexed(1, 3, 11, freqDP, freqW, clrMid, 0.25, true, true); // freq
glContext->DrawTunerBarIndexed(4, 6, 11, freqDP, freqW, clrDark, 0.25, true, true);
glContext->DrawTunerBarIndexed(7, 9, 11, freqDP, freqW, clrMid, 0.25, true, true);
glContext->DrawTunerBarIndexed(10, 11, 11, freqDP, freqW, clrDark, 0.25, true, true);
glContext->DrawTunerBarIndexed(1, 3, 7, bwDP, bwW, clrMid, 0.25, true, true); // bw
glContext->DrawTunerBarIndexed(4, 6, 7, bwDP, bwW, clrDark, 0.25, true, true);
glContext->DrawTunerBarIndexed(7, 7, 7, bwDP, bwW, clrMid, 0.25, true, true);
glContext->DrawTunerBarIndexed(1, 3, 11, centerDP, centerW, clrMid, 0.25, true, true); // freq
glContext->DrawTunerBarIndexed(4, 6, 11, centerDP, centerW, clrDark, 0.25, true, true);
glContext->DrawTunerBarIndexed(7, 9, 11, centerDP, centerW, clrMid, 0.25, true, true);
glContext->DrawTunerBarIndexed(10, 11, 11, centerDP, centerW, clrDark, 0.25, true, true);
if (hoverIndex > 0 && !mouseTracker.mouseDown()) {
switch (hoverState) {
case TUNING_HOVER_FREQ:
glContext->DrawTunerBarIndexed(hoverIndex, hoverIndex, 11, freqDP, freqW, clr, 0.25, top, bottom); // freq
break;
case TUNING_HOVER_BW:
glContext->DrawTunerBarIndexed(hoverIndex, hoverIndex, 7, bwDP, bwW, clr, 0.25, top, bottom); // bw
break;
case TUNING_HOVER_CENTER:
glContext->DrawTunerBarIndexed(hoverIndex, hoverIndex, 11, centerDP, centerW, clr, 0.25, top, bottom); // center
break;
case TUNING_HOVER_NONE:
break;
case TUNING_HOVER_PPM:
glContext->DrawTunerBarIndexed(hoverIndex, hoverIndex, 11, freqDP, freqW, clr, 0.25, top, bottom); // freq
break;
}
}
if (altDown) {
glContext->DrawTuner(currentPPM, 11, freqDP, freqW);
} else {
glContext->DrawTuner(freq, 11, freqDP, freqW);
int snap = wxGetApp().getFrequencySnap();
if (snap != 1) {
glContext->DrawTunerDigitBox((int)log10(snap), 11, freqDP, freqW, RGBA4f(1.0,0.0,0.0));
}
}
glContext->DrawTuner(halfBand?(bw/2):bw, 7, bwDP, bwW);
glContext->DrawTuner(center, 11, centerDP, centerW);
glContext->DrawEnd();
SwapBuffers();
}
/***
* Perform tuner step
*
* @param state Current hover state
* @param digit Digit position
* @param tuningDir Tuning direction, true for up
* @param preventCarry Prevent carry operation on digit overflow
* @param zeroOut Zero out 'digit' and lower digits
*/
void TuningCanvas::StepTuner(ActiveState state, TuningDirection tuningDir, int digit, bool preventCarry, bool zeroOut) {
double exp = pow(10, digit);
long long amount = tuningDir ? exp : -exp;
if (halfBand && state == TUNING_HOVER_BW) {
amount *= 2;
}
auto activeDemod = wxGetApp().getDemodMgr().getCurrentModem();
if (state == TUNING_HOVER_FREQ && activeDemod) {
long long demod_freq = activeDemod->getFrequency();
long long diff = abs(wxGetApp().getFrequency() - demod_freq);
if (zeroOut) { // Zero digits to right
double intpart;
modf(demod_freq / (exp * 10), &intpart);
demod_freq = intpart * exp * 10;
} else if (preventCarry) { // Prevent digit from carrying
bool carried = (long long)((demod_freq) / (exp * 10)) != (long long)((demod_freq + amount) / (exp * 10)) || (bottom && demod_freq < exp);
demod_freq += carried ? (9 * -amount) : amount;
} else {
demod_freq += amount;
}
if (wxGetApp().getSampleRate() / 2 < diff) {
wxGetApp().setFrequency(demod_freq);
}
activeDemod->setTracking(true);
activeDemod->setFollow(true);
activeDemod->setFrequency(demod_freq);
if (activeDemod->isDeltaLock()) {
activeDemod->setDeltaLockOfs(activeDemod->getFrequency() - wxGetApp().getFrequency());
}
activeDemod->updateLabel(demod_freq);
}
if (state == TUNING_HOVER_BW) {
long nbw = wxGetApp().getDemodMgr().getLastBandwidth();
if (zeroOut) { // Zero digits to right
double intpart;
modf(nbw / (exp * 10), &intpart);
nbw = intpart * exp * 10;
} else if (preventCarry) { // Prevent digit from carrying
bool carried = (long)((nbw) / (exp * 10)) != (long)((nbw + amount) / (exp * 10)) || (bottom && nbw < exp);
nbw += carried ? (9 * -amount) : amount;
} else {
nbw += amount;
}
if (nbw > CHANNELIZER_RATE_MAX) {
nbw = CHANNELIZER_RATE_MAX;
}
wxGetApp().getDemodMgr().setLastBandwidth(nbw);
if (activeDemod) {
activeDemod->setBandwidth(wxGetApp().getDemodMgr().getLastBandwidth());
}
}
if (state == TUNING_HOVER_CENTER) {
long long ctr = wxGetApp().getFrequency();
if (zeroOut) { // Zero digits to right
double intpart;
modf(ctr / (exp * 10), &intpart);
ctr = intpart * exp * 10;
} else if (preventCarry) { // Prevent digit from carrying
bool carried = (long long)((ctr) / (exp * 10)) != (long long)((ctr + amount) / (exp * 10)) || (bottom && ctr < exp);
ctr += carried?(9*-amount):amount;
} else {
ctr += amount;
}
wxGetApp().setFrequency(ctr);
}
if (state == TUNING_HOVER_PPM) {
if (preventCarry) {
bool carried = (long long)((currentPPM) / (exp * 10)) != (long long)((currentPPM + amount) / (exp * 10)) || (bottom && currentPPM < exp);
currentPPM += carried?(9*-amount):amount;
} else {
currentPPM += amount;
}
if (currentPPM > 2000) {
currentPPM = 2000;
}
if (currentPPM < -2000) {
currentPPM = -2000;
}
wxGetApp().setPPM(currentPPM);
wxGetApp().notifyMainUIOfDeviceChange();
}
}
void TuningCanvas::OnIdle(wxIdleEvent & /* event */) {
if (mouseTracker.mouseDown()) {
if (downState != TUNING_HOVER_NONE) {
dragAccum += 5.0*mouseTracker.getOriginDeltaMouseX();
while (dragAccum > 1.0) {
StepTuner(downState, TUNING_DIRECTION_UP, downIndex - 1, shiftDown, false);
dragAccum -= 1.0;
dragging = true;
}
while (dragAccum < -1.0) {
StepTuner(downState, TUNING_DIRECTION_DOWN, downIndex - 1, shiftDown, false);
dragAccum += 1.0;
dragging = true;
}
} else {
dragAccum = 0;
dragging = false;
}
}
if (mouseTracker.mouseInView() || changed()) {
Refresh();
}
}
void TuningCanvas::OnMouseMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseMoved(event);
top = mouseTracker.getMouseY() >= 0.5;
bottom = mouseTracker.getMouseY() <= 0.5;
int index = glContext->GetTunerDigitIndex(mouseTracker.getMouseX(), 11, freqDP, freqW); // freq
if (index > 0) {
hoverIndex = index;
hoverState = altDown?TUNING_HOVER_PPM:TUNING_HOVER_FREQ;
}
if (!index) {
index = glContext->GetTunerDigitIndex(mouseTracker.getMouseX(), 7, bwDP, bwW); // bw
if (index > 0) {
hoverIndex = index;
hoverState = TUNING_HOVER_BW;
}
}
if (!index) {
index = glContext->GetTunerDigitIndex(mouseTracker.getMouseX(), 11, centerDP, centerW); // center
if (index > 0) {
hoverIndex = index;
hoverState = TUNING_HOVER_CENTER;
}
}
if (!index) {
hoverIndex = 0;
hoverState = TUNING_HOVER_NONE;
} else {
switch (hoverState) {
case TUNING_HOVER_FREQ:
setStatusText("Click, wheel or drag(left/right) a digit to change frequency; SPACE or numeric key for direct input. Right click to set/clear snap. Hold ALT to change PPM. Hold SHIFT to disable carry. SHIFT-right click to Zero Right.");
break;
case TUNING_HOVER_BW:
setStatusText("Click, wheel or drag(left/right) a digit to change bandwidth; SPACE or numeric key for direct input. Hold SHIFT to disable carry. SHIFT-right click to Zero Right.");
break;
case TUNING_HOVER_CENTER:
setStatusText("Click, wheel or drag(left/right) a digit to change center frequency; SPACE or numeric key for direct input. Hold SHIFT to disable carry. SHIFT-right click to Zero Right.");
break;
case TUNING_HOVER_PPM:
setStatusText("Click, wheel or drag(left/right) a digit to change device PPM offset. Hold SHIFT to disable carry.");
break;
case TUNING_HOVER_NONE:
setStatusText("");
break;
}
}
if (hoverState == TUNING_HOVER_BW || hoverState == TUNING_HOVER_FREQ) {
wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getCurrentModem());
} else {
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr);
}
}
void TuningCanvas::OnMouseDown(wxMouseEvent& event) {
InteractiveCanvas::OnMouseDown(event);
uxDown = 2.0 * (mouseTracker.getMouseX() - 0.5);
dragAccum = 0;
mouseTracker.setVertDragLock(true);
downIndex = hoverIndex;
downState = hoverState;
}
void TuningCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseWheelMoved(event);
int hExponent = hoverIndex - 1;
if (hoverState != TUNING_HOVER_NONE && !mouseTracker.mouseDown() && hoverIndex) {
if (event.m_wheelAxis == wxMOUSE_WHEEL_VERTICAL) {
StepTuner(hoverState, (event.m_wheelRotation > 0) ? TUNING_DIRECTION_UP : TUNING_DIRECTION_DOWN, hExponent, shiftDown, false);
} else {
StepTuner(hoverState, (event.m_wheelRotation < 0) ? TUNING_DIRECTION_UP : TUNING_DIRECTION_DOWN, hExponent, shiftDown, false);
}
}
}
void TuningCanvas::OnMouseReleased(wxMouseEvent& event) {
InteractiveCanvas::OnMouseReleased(event);
int hExponent = hoverIndex - 1;
if (hoverState != TUNING_HOVER_NONE && !dragging && (downState == hoverState) && (downIndex == hoverIndex)) {
StepTuner(hoverState, top ? TUNING_DIRECTION_UP : TUNING_DIRECTION_DOWN, hExponent, shiftDown, false);
}
mouseTracker.setVertDragLock(false);
dragging = false;
SetCursor(wxCURSOR_ARROW);
}
void TuningCanvas::OnMouseRightDown(wxMouseEvent& event) {
InteractiveCanvas::OnMouseRightDown(event);
uxDown = 2.0 * (mouseTracker.getMouseX() - 0.5);
downIndex = hoverIndex;
downState = hoverState;
}
void TuningCanvas::OnMouseRightReleased(wxMouseEvent& event) {
InteractiveCanvas::OnMouseRightReleased(event);
if (shiftDown) {
int hExponent = hoverIndex - 1;
if (hoverState != TUNING_HOVER_NONE && !dragging && (downState == hoverState) && (downIndex == hoverIndex)) {
StepTuner(hoverState, top ? TUNING_DIRECTION_UP : TUNING_DIRECTION_DOWN, hExponent, false, true);
}
} else if (hoverState == TUNING_HOVER_FREQ) {
if (hoverIndex == 1) {
wxGetApp().setFrequencySnap(1);
} else if (hoverIndex > 1 && hoverIndex < 8) {
int exp = pow(10, hoverIndex-1);
if (wxGetApp().getFrequencySnap() == exp) {
wxGetApp().setFrequencySnap(1);
} else {
wxGetApp().setFrequencySnap(exp);
}
}
}
}
void TuningCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
InteractiveCanvas::OnMouseLeftWindow(event);
SetCursor(wxCURSOR_CROSS);
hoverIndex = 0;
hoverState = TUNING_HOVER_NONE;
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr);
if (currentPPM != lastPPM) {
wxGetApp().saveConfig();
}
Refresh();
}
void TuningCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event);
SetCursor(wxCURSOR_ARROW);
hoverIndex = 0;
hoverState = TUNING_HOVER_NONE;
lastPPM = currentPPM = wxGetApp().getPPM();
#ifdef _WIN32
if (wxGetApp().getAppFrame()->canFocus()) {
this->SetFocus();
}
#endif
}
void TuningCanvas::setHelpTip(std::string tip) {
helpTip = tip;
}
void TuningCanvas::OnKeyDown(wxKeyEvent& event) {
InteractiveCanvas::OnKeyDown(event);
if (event.GetKeyCode() == WXK_SPACE) {
if (hoverState == TUNING_HOVER_CENTER || hoverState == TUNING_HOVER_FREQ) {
wxGetApp().showFrequencyInput(FrequencyDialog::FDIALOG_TARGET_DEFAULT);
} else if (hoverState == TUNING_HOVER_BW) {
wxGetApp().showFrequencyInput(FrequencyDialog::FDIALOG_TARGET_BANDWIDTH);
}
}
}
void TuningCanvas::OnKeyUp(wxKeyEvent& event) {
InteractiveCanvas::OnKeyUp(event);
}
TuningCanvas::ActiveState TuningCanvas::getHoverState() {
return hoverState;
}

View File

@ -0,0 +1,84 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/glcanvas.h"
#include "wx/timer.h"
#include <vector>
#include <queue>
#include "InteractiveCanvas.h"
#include "TuningContext.h"
#include "MouseTracker.h"
#include "Timer.h"
class TuningCanvas: public InteractiveCanvas {
public:
enum ActiveState {
TUNING_HOVER_NONE, TUNING_HOVER_FREQ, TUNING_HOVER_BW, TUNING_HOVER_PPM, TUNING_HOVER_CENTER
};
enum TuningDirection {
TUNING_DIRECTION_DOWN, TUNING_DIRECTION_UP
};
TuningCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs);
~TuningCanvas() override;
void setHelpTip(std::string tip);
bool changed();
void setHalfBand(bool hb);
void OnKeyDown(wxKeyEvent& event);
void OnKeyUp(wxKeyEvent& event);
ActiveState getHoverState();
private:
void OnPaint(wxPaintEvent& event);
void OnIdle(wxIdleEvent &event);
void OnMouseMoved(wxMouseEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnMouseWheelMoved(wxMouseEvent& event);
void OnMouseReleased(wxMouseEvent& event);
void OnMouseEnterWindow(wxMouseEvent& event);
void OnMouseLeftWindow(wxMouseEvent& event);
void OnMouseRightDown(wxMouseEvent& event);
void OnMouseRightReleased(wxMouseEvent& event);
void StepTuner(ActiveState state, TuningDirection tuningDir, int digit, bool preventCarry = false, bool zeroOut = false);
TuningContext *glContext;
std::string helpTip;
float dragAccum;
float uxDown;
ActiveState hoverState;
ActiveState downState;
int hoverIndex;
int downIndex;
bool dragging;
float freqDP;
float freqW;
float bwDP;
float bwW;
float centerDP;
float centerW;
bool top;
bool bottom;
int currentPPM;
int lastPPM;
long long freq, bw, center;
bool halfBand;
//
wxDECLARE_EVENT_TABLE();
};

View File

@ -0,0 +1,199 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "TuningContext.h"
#include "TuningCanvas.h"
#include "ColorTheme.h"
// http://stackoverflow.com/questions/7276826/c-format-number-with-commas
class comma_numpunct: public std::numpunct<char> {
protected:
char do_thousands_sep() const override {
return ',';
}
std::string do_grouping() const override {
return "\03";
}
};
TuningContext::TuningContext(TuningCanvas *canvas, wxGLContext *sharedContext, wxGLContextAttrs *ctxAttrs) :
PrimaryGLContext(canvas, sharedContext, ctxAttrs) {
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
comma_locale = std::locale(std::locale(), new comma_numpunct());
freqStrFormatted.imbue(comma_locale);
}
void TuningContext::DrawBegin() {
glClearColor(ThemeMgr::mgr.currentTheme->generalBackground.r, ThemeMgr::mgr.currentTheme->generalBackground.g,
ThemeMgr::mgr.currentTheme->generalBackground.b, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_TEXTURE_2D);
}
void TuningContext::Draw(float r, float g, float b, float a, float p1, float p2) {
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
glBegin(GL_QUADS);
glColor4f(r * 0.5, g * 0.5, b * 0.5, a);
glVertex2f(-1.0 + p2 * 2.0, 1.0);
glVertex2f(-1.0 + p1 * 2.0, 1.0);
glColor4f(r, g, b, a);
glVertex2f(-1.0 + p1 * 2.0, 0.0);
glVertex2f(-1.0 + p2 * 2.0, 0.0);
glVertex2f(-1.0 + p2 * 2.0, 0.0);
glVertex2f(-1.0 + p1 * 2.0, 0.0);
glColor4f(r * 0.5, g * 0.5, b * 0.5, a);
glVertex2f(-1.0 + p1 * 2.0, -1.0);
glVertex2f(-1.0 + p2 * 2.0, -1.0);
glEnd();
glDisable(GL_BLEND);
}
void TuningContext::DrawEnd() {
// glFlush();
// CheckGLError();
}
void TuningContext::DrawTuner(long long freq, int count, float displayPos, float displayWidth) {
GLint vp[4];
glGetIntegerv( GL_VIEWPORT, vp);
float viewWidth = (float) vp[2];
float viewHeight = (float) vp[3];
freqStr.str("");
freqStr << freq;
std::string freqChars = freqStr.str();
int fontSize = 32;
if (viewWidth < 300) {
fontSize = 18;
} else if (viewWidth < 500) {
fontSize = 24;
}
if (viewHeight < 18) {
fontSize = 12;
} else if (viewHeight < 24) {
fontSize = 16;
} else if (viewHeight < 28) {
fontSize = 18;
}
glColor3f(ThemeMgr::mgr.currentTheme->text.r, ThemeMgr::mgr.currentTheme->text.g, ThemeMgr::mgr.currentTheme->text.b);
int numChars = freqChars.length();
int ofs = count - numChars;
//do not zoom this one:
GLFont::Drawer refDrawingFont = GLFont::getFont(fontSize);
for (int i = ofs; i < count; i++) {
float xpos = displayPos + (displayWidth / (float) count) * (float) i + ((displayWidth / 2.0) / (float) count);
refDrawingFont.drawString(freqStr.str().substr(i - ofs, 1), xpos, 0, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER);
}
glColor4f(0.65f, 0.65f, 0.65f, 0.25f);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBegin(GL_LINES);
for (int i = count; i >= 0; i--) {
float xpos = displayPos + (displayWidth / (float) count) * (float) i;
glVertex2f(xpos, -1.0);
glVertex2f(xpos, 1.0);
}
glEnd();
glDisable(GL_BLEND);
}
void TuningContext::DrawTunerDigitBox(int index, int count, float displayPos, float displayWidth, const RGBA4f& /* c */) {
GLint vp[4];
glGetIntegerv( GL_VIEWPORT, vp);
float viewHeight = (float) vp[3];
float pixelHeight = 2.0/viewHeight;
glColor4f(1.0, 0,0,1);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
float xpos = displayPos + (displayWidth / (float) count) * (float) (count-index);
float xpos2 = displayPos + (displayWidth / (float) count) * (float) ((count-1)-index);
glBegin(GL_LINE_STRIP);
glVertex2f(xpos, 1.0-pixelHeight);
glVertex2f(xpos, -1.0+pixelHeight);
glVertex2f(xpos2, -1.0+pixelHeight);
glVertex2f(xpos2, 1.0-pixelHeight);
glVertex2f(xpos, 1.0-pixelHeight);
glEnd();
glDisable(GL_BLEND);
}
int TuningContext::GetTunerDigitIndex(float mPos, int count, float displayPos, float displayWidth) {
mPos -= 0.5;
mPos *= 2.0;
float delta = mPos - displayPos;
if (delta < 0 || delta > displayWidth) {
return 0;
}
int index = floor((delta / displayWidth) * (count));
return count - index;
}
void TuningContext::DrawTunerBarIndexed(int start, int end, int count, float displayPos, float displayWidth, const RGBA4f& color, float /* alpha */, bool top,
bool bottom) {
float ofs = (displayWidth / (float) count);
float p2 = displayPos + ofs * (float) (count - start + 1);
float p1 = displayPos + ofs * (float) (count - end);
float r = color.r, g = color.g, b = color.b, a = 0.6f;
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
glBegin(GL_QUADS);
if (top) {
glColor4f(r * 0.5, g * 0.5, b * 0.5, a);
glVertex2f(p2, 1.0);
glVertex2f(p1, 1.0);
glColor4f(r, g, b, a);
glVertex2f(p1, 0.0);
glVertex2f(p2, 0.0);
}
if (bottom) {
glColor4f(r, g, b, a);
glVertex2f(p2, 0.0);
glVertex2f(p1, 0.0);
glColor4f(r * 0.5, g * 0.5, b * 0.5, a);
glVertex2f(p1, -1.0);
glVertex2f(p2, -1.0);
}
glEnd();
glDisable(GL_BLEND);
}
void TuningContext::DrawDemodFreqBw(long long freq, unsigned int bw, long long center) {
DrawTuner(freq, 11, -1.0, (1.0f / 3.0f) * 2.0f);
DrawTuner(bw, 7, -1.0 + (2.25f / 3.0f), (1.0f / 4.0f) * 2.0f);
DrawTuner(center, 11, -1.0 + (2.0f / 3.0f) * 2.0, (1.0f / 3.0f) * 2.0f);
}

View File

@ -0,0 +1,29 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "PrimaryGLContext.h"
#include "Gradient.h"
class TuningCanvas;
class TuningContext: public PrimaryGLContext {
public:
TuningContext(TuningCanvas *canvas, wxGLContext *sharedContext, wxGLContextAttrs *ctxAttrs);
void DrawBegin();
void Draw(float r, float g, float b, float a, float p1, float p2);
void DrawTuner(long long freq, int count, float displayPos, float displayWidth);
static void DrawTunerDigitBox(int index, int count, float displayPos, float displayWidth, const RGBA4f& c);
int GetTunerDigitIndex(float mPos, int count, float displayPos, float displayWidth);
void DrawTunerBarIndexed(int start, int end, int count, float displayPos, float displayWidth, const RGBA4f& color, float alpha, bool top, bool bottom);
void DrawDemodFreqBw(long long freq, unsigned int bw, long long center);
void DrawEnd();
private:
std::locale comma_locale;
std::stringstream freqStr;
std::stringstream freqStrFormatted;
};

View File

@ -0,0 +1,924 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "WaterfallCanvas.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#if !wxUSE_GLCANVAS
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
#endif
#include "CubicSDR.h"
#include "CubicSDRDefs.h"
#include "AppFrame.h"
#include <algorithm>
#ifdef USE_HAMLIB
#include "RigThread.h"
#endif
#include <wx/numformatter.h>
#include "DemodulatorThread.h"
wxBEGIN_EVENT_TABLE(WaterfallCanvas, wxGLCanvas)
EVT_PAINT(WaterfallCanvas::OnPaint)
EVT_IDLE(WaterfallCanvas::OnIdle)
EVT_MOTION(WaterfallCanvas::OnMouseMoved)
EVT_LEFT_DOWN(WaterfallCanvas::OnMouseDown)
EVT_LEFT_UP(WaterfallCanvas::OnMouseReleased)
EVT_RIGHT_DOWN(WaterfallCanvas::OnMouseRightDown)
EVT_RIGHT_UP(WaterfallCanvas::OnMouseRightReleased)
EVT_LEAVE_WINDOW(WaterfallCanvas::OnMouseLeftWindow)
EVT_ENTER_WINDOW(WaterfallCanvas::OnMouseEnterWindow)
EVT_MOUSEWHEEL(WaterfallCanvas::OnMouseWheelMoved)
wxEND_EVENT_TABLE()
WaterfallCanvas::WaterfallCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs) :
InteractiveCanvas(parent, dispAttrs), dragState(WF_DRAG_NONE), nextDragState(WF_DRAG_NONE), fft_size(0), new_fft_size(0), waterfall_lines(0),
dragOfs(0), mouseZoom(1), zoom(1), freqMoving(false), freqMove(0.0), hoverAlpha(1.0) {
glContext = new PrimaryGLContext(this, &wxGetApp().GetContext(this), wxGetApp().GetContextAttributes());
linesPerSecond = DEFAULT_WATERFALL_LPS;
lpsIndex = 0;
preBuf = false;
SetCursor(wxCURSOR_CROSS);
scaleMove = 0;
minBandwidth = 30000;
fft_size_changed.store(false);
}
WaterfallCanvas::~WaterfallCanvas() = default;
void WaterfallCanvas::setup(unsigned int fft_size_in, int waterfall_lines_in) {
if (fft_size == fft_size_in && waterfall_lines_in == waterfall_lines) {
return;
}
fft_size = fft_size_in;
waterfall_lines = waterfall_lines_in;
waterfallPanel.setup(fft_size, waterfall_lines);
gTimer.start();
}
void WaterfallCanvas::setFFTSize(unsigned int fft_size_in) {
if (fft_size_in == fft_size) {
return;
}
new_fft_size = fft_size_in;
fft_size_changed.store(true);
}
WaterfallCanvas::DragState WaterfallCanvas::getDragState() {
return dragState;
}
WaterfallCanvas::DragState WaterfallCanvas::getNextDragState() {
return nextDragState;
}
void WaterfallCanvas::attachSpectrumCanvas(SpectrumCanvas *canvas_in) {
spectrumCanvas = canvas_in;
}
void WaterfallCanvas::processInputQueue() {
std::lock_guard < std::mutex > lock(tex_update);
gTimer.update();
double targetVis = 1.0 / (double)linesPerSecond;
lpsIndex += gTimer.lastUpdateSeconds();
bool updated = false;
if (linesPerSecond) {
if (lpsIndex >= targetVis) {
while (lpsIndex >= targetVis) {
SpectrumVisualDataPtr vData;
if (visualDataQueue->try_pop(vData)) {
if (vData) {
if (vData->spectrum_points.size() == fft_size * 2) {
waterfallPanel.setPoints(vData->spectrum_points);
}
waterfallPanel.step();
updated = true;
}
lpsIndex-=targetVis;
} else {
break;
}
}
}
}
if (updated) {
wxClientDC(this);
glContext->SetCurrent(*this);
waterfallPanel.update();
}
}
void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
std::lock_guard < std::mutex > lock(tex_update);
// wxPaintDC dc(this);
const wxSize ClientSize = GetClientSize() * GetContentScaleFactor();
long double currentZoom = zoom;
if (mouseZoom != 1) {
currentZoom = mouseZoom;
mouseZoom = mouseZoom + (1.0 - mouseZoom) * 0.2;
if (fabs(mouseZoom-1.0)<0.01) {
mouseZoom = 1;
}
}
if (scaleMove != 0) {
SpectrumVisualProcessor *sp = wxGetApp().getSpectrumProcessor();
FFTVisualDataThread *wdt = wxGetApp().getAppFrame()->getWaterfallDataThread();
SpectrumVisualProcessor *wp = wdt->getProcessor();
float factor = sp->getScaleFactor();
factor += scaleMove * 0.02;
if (factor < 0.25) {
factor = 0.25;
}
if (factor > 10.0) {
factor = 10.0;
}
sp->setScaleFactor(factor);
wp->setScaleFactor(factor);
}
if (freqMove != 0.0) {
long long newFreq = getCenterFrequency() + (long long)((long double)getBandwidth()*freqMove) * 0.01;
long long minFreq = bandwidth/2;
if (newFreq < minFreq) {
newFreq = minFreq;
}
updateCenterFrequency(newFreq);
if (!freqMoving) {
freqMove -= (freqMove * 0.2);
if (fabs(freqMove) < 0.01) {
freqMove = 0.0;
}
}
}
long long bw;
if (currentZoom != 1) {
long long freq = wxGetApp().getFrequency();
bw = getBandwidth();
double mpos = 0;
float mouseInView = false;
if (mouseTracker.mouseInView()) {
mpos = mouseTracker.getMouseX();
mouseInView = true;
} else if (spectrumCanvas && spectrumCanvas->getMouseTracker()->mouseInView()) {
mpos = spectrumCanvas->getMouseTracker()->getMouseX();
mouseInView = true;
}
if (currentZoom < 1) {
bw = (long long) ceil((long double) bw * currentZoom);
if (bw < minBandwidth) {
bw = minBandwidth;
}
if (mouseInView) {
long long mfreqA = getFrequencyAt(mpos, centerFreq, getBandwidth());
long long mfreqB = getFrequencyAt(mpos, centerFreq, bw);
centerFreq += mfreqA - mfreqB;
}
setView(centerFreq, bw);
} else {
if (isView) {
bw = (long long) ceil((long double) bw * currentZoom);
if (bw >= wxGetApp().getSampleRate()) {
disableView();
if (spectrumCanvas) {
spectrumCanvas->disableView();
}
bw = wxGetApp().getSampleRate();
centerFreq = wxGetApp().getFrequency();
} else {
if (mouseInView) {
long long mfreqA = getFrequencyAt(mpos, centerFreq, getBandwidth());
long long mfreqB = getFrequencyAt(mpos, centerFreq, bw);
centerFreq += mfreqA - mfreqB;
setBandwidth(bw);
} else {
setBandwidth(bw);
}
}
}
}
if (centerFreq < freq && (centerFreq - bandwidth / 2) < (freq - wxGetApp().getSampleRate() / 2)) {
centerFreq = (freq - wxGetApp().getSampleRate() / 2) + bandwidth / 2;
}
if (centerFreq > freq && (centerFreq + bandwidth / 2) > (freq + wxGetApp().getSampleRate() / 2)) {
centerFreq = (freq + wxGetApp().getSampleRate() / 2) - bandwidth / 2;
}
if (spectrumCanvas) {
if ((spectrumCanvas->getCenterFrequency() != centerFreq) || (spectrumCanvas->getBandwidth() != bw)) {
if (getViewState()) {
spectrumCanvas->setView(centerFreq,bw);
} else {
spectrumCanvas->disableView();
spectrumCanvas->setCenterFrequency(centerFreq);
spectrumCanvas->setBandwidth(bw);
}
}
}
}
glContext->SetCurrent(*this);
initGLExtensions();
glViewport(0, 0, ClientSize.x, ClientSize.y);
if (fft_size_changed.load()) {
fft_size = new_fft_size;
waterfallPanel.setup(fft_size, waterfall_lines);
fft_size_changed.store(false);
}
glContext->BeginDraw(0,0,0);
waterfallPanel.calcTransform(CubicVR::mat4::identity());
waterfallPanel.draw();
auto demods = wxGetApp().getDemodMgr().getDemodulators();
auto activeDemodulator = wxGetApp().getDemodMgr().getActiveContextModem();
auto lastActiveDemodulator = wxGetApp().getDemodMgr().getCurrentModem();
bool isNew = shiftDown
|| (wxGetApp().getDemodMgr().getCurrentModem() && !wxGetApp().getDemodMgr().getCurrentModem()->isActive());
int currentBandwidth = getBandwidth();
long long currentCenterFreq = getCenterFrequency();
ColorTheme *currentTheme = ThemeMgr::mgr.currentTheme;
std::string last_type = wxGetApp().getDemodMgr().getLastDemodulatorType();
if (mouseTracker.mouseInView() || wxGetApp().getDemodMgr().getActiveContextModem()) {
hoverAlpha += (1.0f-hoverAlpha)*0.1f;
if (hoverAlpha > 1.5f) {
hoverAlpha = 1.5f;
}
glContext->setHoverAlpha(hoverAlpha);
if (nextDragState == WF_DRAG_RANGE) {
float width = (1.0 / (float) ClientSize.x);
float rangeWidth = mouseTracker.getOriginDeltaMouseX();
float centerPos;
if (mouseTracker.mouseDown()) {
if (rangeWidth) {
width = rangeWidth;
}
centerPos = mouseTracker.getOriginMouseX() + width / 2.0;
} else {
centerPos = mouseTracker.getMouseX();
}
glContext->DrawDemod(lastActiveDemodulator, isNew?currentTheme->waterfallHighlight:currentTheme->waterfallDestroy, currentCenterFreq, currentBandwidth);
if ((last_type == "LSB" || last_type == "USB") && mouseTracker.mouseDown()) {
centerPos = mouseTracker.getMouseX();
glContext->DrawRangeSelector(centerPos, centerPos-width, isNew?currentTheme->waterfallNew:currentTheme->waterfallHover);
} else {
glContext->DrawFreqSelector(centerPos, isNew?currentTheme->waterfallNew:currentTheme->waterfallHover, width, currentCenterFreq, currentBandwidth);
}
} else {
if (lastActiveDemodulator) {
glContext->DrawDemod(lastActiveDemodulator, ((isNew && activeDemodulator == nullptr) || (activeDemodulator != nullptr))?currentTheme->waterfallHighlight:currentTheme->waterfallDestroy, currentCenterFreq, currentBandwidth);
}
if (activeDemodulator == nullptr) {
glContext->DrawFreqSelector(mouseTracker.getMouseX(), ((isNew && lastActiveDemodulator) || (!lastActiveDemodulator) )?currentTheme->waterfallNew:currentTheme->waterfallHover, 0, currentCenterFreq, currentBandwidth);
} else {
glContext->DrawDemod(activeDemodulator, currentTheme->waterfallHover, currentCenterFreq, currentBandwidth);
}
}
} else {
hoverAlpha += (0.0f-hoverAlpha)*0.05f;
if (hoverAlpha < 1.0e-5f) {
hoverAlpha = 0;
}
glContext->setHoverAlpha(hoverAlpha);
if (activeDemodulator) {
glContext->DrawDemod(activeDemodulator, currentTheme->waterfallHighlight, currentCenterFreq, currentBandwidth);
}
if (lastActiveDemodulator) {
glContext->DrawDemod(lastActiveDemodulator, currentTheme->waterfallHighlight, currentCenterFreq, currentBandwidth);
}
}
glContext->setHoverAlpha(0);
for (auto & demod : demods) {
if (!demod->isActive()) {
continue;
}
if (activeDemodulator == demod || lastActiveDemodulator == demod) {
continue;
}
glContext->DrawDemod(demod, currentTheme->waterfallHighlight, currentCenterFreq, currentBandwidth);
}
for (auto & demod : demods) {
demod->getVisualCue()->step();
int squelchBreak = demod->getVisualCue()->getSquelchBreak();
if (squelchBreak) {
glContext->setHoverAlpha((float(squelchBreak) / 60.0));
glContext->DrawDemod(demod, currentTheme->waterfallHover, currentCenterFreq, currentBandwidth);
}
}
glContext->EndDraw();
SwapBuffers();
}
void WaterfallCanvas::OnKeyUp(wxKeyEvent& event) {
InteractiveCanvas::OnKeyUp(event);
shiftDown = event.ShiftDown();
altDown = event.AltDown();
ctrlDown = event.ControlDown();
switch (event.GetKeyCode()) {
case WXK_UP:
case WXK_NUMPAD_UP:
scaleMove = 0.0;
zoom = 1.0;
if (mouseZoom != 1.0) {
mouseZoom = 0.95f;
}
break;
case WXK_DOWN:
case WXK_NUMPAD_DOWN:
scaleMove = 0.0;
zoom = 1.0;
if (mouseZoom != 1.0) {
mouseZoom = 1.05f;
}
break;
case WXK_LEFT:
case WXK_NUMPAD_LEFT:
case WXK_RIGHT:
case WXK_NUMPAD_RIGHT:
freqMoving = false;
break;
}
}
void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) {
InteractiveCanvas::OnKeyDown(event);
auto activeDemod = wxGetApp().getDemodMgr().getActiveContextModem();
long long originalFreq = getCenterFrequency();
long long freq = originalFreq;
switch (event.GetKeyCode()) {
case WXK_UP:
case WXK_NUMPAD_UP:
if (!shiftDown) {
mouseZoom = 1.0;
zoom = 0.95f;
} else {
scaleMove = 1.0;
}
break;
case WXK_DOWN:
case WXK_NUMPAD_DOWN:
if (!shiftDown) {
mouseZoom = 1.0;
zoom = 1.05f;
} else {
scaleMove = -1.0;
}
break;
case WXK_RIGHT:
case WXK_NUMPAD_RIGHT:
if (isView) {
freqMove = shiftDown?5.0:1.0;
freqMoving = true;
} else {
freq += shiftDown?(getBandwidth() * 10):(getBandwidth() / 2);
}
break;
case WXK_LEFT:
case WXK_NUMPAD_LEFT:
if (isView) {
freqMove = shiftDown?-5.0:-1.0;
freqMoving = true;
} else {
freq -= shiftDown?(getBandwidth() * 10):(getBandwidth() / 2);
}
break;
case 'D':
case WXK_DELETE:
if (!activeDemod) {
break;
}
wxGetApp().removeDemodulator(activeDemod);
wxGetApp().getDemodMgr().deleteThread(activeDemod);
break;
case WXK_SPACE:
wxGetApp().showFrequencyInput();
break;
case 'E': //E is for 'Edit the label' of the active demodulator.
wxGetApp().showLabelInput();
break;
case 'C':
if (wxGetApp().getDemodMgr().getActiveContextModem()) {
wxGetApp().setFrequency(wxGetApp().getDemodMgr().getActiveContextModem()->getFrequency());
} else if (mouseTracker.mouseInView()) {
long long nfreq = getFrequencyAt(mouseTracker.getMouseX());
int snap = wxGetApp().getFrequencySnap();
if (snap > 1) {
nfreq = roundf((float)nfreq / (float)snap) * snap;
}
wxGetApp().setFrequency(nfreq);
}
#ifdef USE_HAMLIB
if (wxGetApp().rigIsActive() && (!wxGetApp().getRigThread()->getControlMode() || wxGetApp().getRigThread()->getCenterLock())) {
wxGetApp().getRigThread()->setFrequency(wxGetApp().getFrequency(),true);
}
#endif
break;
default:
event.Skip();
return;
}
long long minFreq = bandwidth/2;
if (freq < minFreq) {
freq = minFreq;
}
if (freq != originalFreq) {
updateCenterFrequency(freq);
}
}
void WaterfallCanvas::OnIdle(wxIdleEvent & /* event */) {
processInputQueue();
Refresh();
}
void WaterfallCanvas::updateHoverState() {
long long freqPos = getFrequencyAt(mouseTracker.getMouseX());
auto demodsHover = wxGetApp().getDemodMgr().getDemodulatorsAt(freqPos, 15000);
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr);
if (altDown) {
nextDragState = WF_DRAG_RANGE;
mouseTracker.setVertDragLock(true);
mouseTracker.setHorizDragLock(false);
if (shiftDown) {
setStatusText("Click and drag to create a new demodulator by range.");
} else {
setStatusText("Click and drag to set the current demodulator range.");
}
} else if (!demodsHover.empty() && !shiftDown) {
long near_dist = getBandwidth();
DemodulatorInstancePtr activeDemodulator = nullptr;
for (auto demod : demodsHover) {
long long freqDiff = demod->getFrequency() - freqPos;
long halfBw = (demod->getBandwidth() / 2);
long long currentBw = getBandwidth();
long long globalBw = wxGetApp().getSampleRate();
long dist = abs(freqDiff);
double bufferBw = 10000.0 * ((double)currentBw / (double)globalBw);
double maxDist = ((double)halfBw + bufferBw);
if ((double)dist <= maxDist) {
if ((freqDiff > 0 && demod->getDemodulatorType() == "USB") ||
(freqDiff < 0 && demod->getDemodulatorType() == "LSB")) {
continue;
}
if (dist < near_dist) {
activeDemodulator = demod;
near_dist = dist;
}
long edge_dist = abs(halfBw - dist);
if (edge_dist < near_dist) {
activeDemodulator = demod;
near_dist = edge_dist;
}
}
}
if (activeDemodulator == nullptr) {
nextDragState = WF_DRAG_NONE;
SetCursor(wxCURSOR_CROSS);
return;
}
wxGetApp().getDemodMgr().setActiveDemodulator(activeDemodulator);
long long freqDiff = activeDemodulator->getFrequency() - freqPos;
if (abs(freqDiff) > (activeDemodulator->getBandwidth() / 3)) {
if (freqDiff > 0) {
if (activeDemodulator->getDemodulatorType() != "USB") {
nextDragState = WF_DRAG_BANDWIDTH_LEFT;
SetCursor(wxCURSOR_SIZEWE);
}
} else {
if (activeDemodulator->getDemodulatorType() != "LSB") {
nextDragState = WF_DRAG_BANDWIDTH_RIGHT;
SetCursor(wxCURSOR_SIZEWE);
}
}
mouseTracker.setVertDragLock(true);
mouseTracker.setHorizDragLock(false);
setStatusText("Drag to change bandwidth. SPACE or 0-9 for direct frequency input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label, R to record.");
} else {
SetCursor(wxCURSOR_SIZING);
nextDragState = WF_DRAG_FREQUENCY;
mouseTracker.setVertDragLock(true);
mouseTracker.setHorizDragLock(false);
setStatusText("Drag to change frequency; SPACE or 0-9 for direct input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label, R to record.");
}
}
else {
SetCursor(wxCURSOR_CROSS);
nextDragState = WF_DRAG_NONE;
if (shiftDown) {
setStatusText("Click to create a new demodulator or hold ALT to drag new range.");
}
else {
setStatusText(
"Click to set demodulator frequency or hold ALT to drag range; hold SHIFT to create new. Arrow keys or wheel to navigate/zoom bandwidth, C to center. Right-drag or SHIFT+UP/DOWN to adjust visual gain. Shift-R record/stop all.");
}
}
}
void WaterfallCanvas::OnMouseMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseMoved(event);
auto demod = wxGetApp().getDemodMgr().getActiveContextModem();
if (mouseTracker.mouseDown()) {
if (demod == nullptr) {
return;
}
if (dragState == WF_DRAG_BANDWIDTH_LEFT || dragState == WF_DRAG_BANDWIDTH_RIGHT) {
int bwDiff = (int) (mouseTracker.getDeltaMouseX() * (float) getBandwidth()) * 2;
if (dragState == WF_DRAG_BANDWIDTH_LEFT) {
bwDiff = -bwDiff;
}
int currentBW = dragBW;
currentBW = currentBW + bwDiff;
if (currentBW > CHANNELIZER_RATE_MAX) {
currentBW = CHANNELIZER_RATE_MAX;
}
if (currentBW < MIN_BANDWIDTH) {
currentBW = MIN_BANDWIDTH;
}
demod->setBandwidth(currentBW);
dragBW = currentBW;
}
if (dragState == WF_DRAG_FREQUENCY) {
long long bwTarget = getFrequencyAt(mouseTracker.getMouseX()) - dragOfs;
long long currentFreq = demod->getFrequency();
long long bwDiff = bwTarget - currentFreq;
int snap = wxGetApp().getFrequencySnap();
if (snap > 1) {
bwDiff = roundf((float)bwDiff/(float)snap)*snap;
}
if (bwDiff) {
demod->setFrequency(currentFreq + bwDiff);
if (demod->isDeltaLock()) {
demod->setDeltaLockOfs(demod->getFrequency() - wxGetApp().getFrequency());
}
currentFreq = demod->getFrequency();
demod->updateLabel(currentFreq);
}
}
} else if (mouseTracker.mouseRightDown() && spectrumCanvas) {
//Right-drag has the same effect on both Waterfall and Spectrum.
spectrumCanvas->updateScaleFactorFromYMove(mouseTracker.getDeltaMouseY());
} else {
updateHoverState();
}
}
void WaterfallCanvas::OnMouseDown(wxMouseEvent& event) {
InteractiveCanvas::OnMouseDown(event);
updateHoverState();
dragState = nextDragState;
wxGetApp().getDemodMgr().updateLastState();
if (dragState && dragState != WF_DRAG_RANGE) {
auto demod = wxGetApp().getDemodMgr().getActiveContextModem();
if (demod) {
dragOfs = (long long) (mouseTracker.getMouseX() * (float) getBandwidth()) + getCenterFrequency() - (getBandwidth() / 2) - demod->getFrequency();
dragBW = demod->getBandwidth();
}
wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getActiveContextModem(), false);
}
}
void WaterfallCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
InteractiveCanvas::OnMouseWheelMoved(event);
float movement = (float)event.GetWheelRotation() / (float)event.GetLinesPerAction();
mouseZoom = 1.0f - movement/1000.0f;
}
void WaterfallCanvas::OnMouseReleased(wxMouseEvent& event) {
InteractiveCanvas::OnMouseReleased(event);
wxGetApp().getDemodMgr().updateLastState();
bool isNew = shiftDown || (wxGetApp().getDemodMgr().getCurrentModem() == nullptr)
|| (wxGetApp().getDemodMgr().getCurrentModem() && !wxGetApp().getDemodMgr().getCurrentModem()->isActive());
mouseTracker.setVertDragLock(false);
mouseTracker.setHorizDragLock(false);
DemodulatorInstancePtr demod = isNew?nullptr: wxGetApp().getDemodMgr().getCurrentModem();
DemodulatorInstancePtr activeDemod = isNew?nullptr: wxGetApp().getDemodMgr().getActiveContextModem();
DemodulatorMgr *mgr = &wxGetApp().getDemodMgr();
if (mouseTracker.getOriginDeltaMouseX() == 0 && mouseTracker.getOriginDeltaMouseY() == 0) {
float pos = mouseTracker.getMouseX();
long long input_center_freq = getCenterFrequency();
long long freqTarget = input_center_freq - (long long) (0.5 * (float) getBandwidth()) + (long long) ((float) pos * (float) getBandwidth());
long long demodFreq = demod?demod->getFrequency():freqTarget;
long long bwDiff = freqTarget - demodFreq;
long long freq = demodFreq;
int snap = wxGetApp().getFrequencySnap();
if (snap > 1) {
if (demod) {
bwDiff = roundf((double)bwDiff/(double)snap)*snap;
freq += bwDiff;
} else {
freq = roundl((long double)freq/(double)snap)*snap;
}
} else {
freq += bwDiff;
}
if (dragState == WF_DRAG_NONE) {
if (!isNew && !wxGetApp().getDemodMgr().getDemodulators().empty()) {
mgr->updateLastState();
demod = wxGetApp().getDemodMgr().getCurrentModem();
} else {
isNew = true;
demod = wxGetApp().getDemodMgr().newThread();
demod->setFrequency(freq);
demod->setDemodulatorType(mgr->getLastDemodulatorType());
demod->setBandwidth(mgr->getLastBandwidth());
demod->setSquelchLevel(mgr->getLastSquelchLevel());
demod->setSquelchEnabled(mgr->isLastSquelchEnabled());
demod->setGain(mgr->getLastGain());
demod->setMuted(mgr->isLastMuted());
if (mgr->getLastDeltaLock()) {
demod->setDeltaLock(true);
demod->setDeltaLockOfs(wxGetApp().getFrequency()-freq);
} else {
demod->setDeltaLock(false);
}
demod->writeModemSettings(mgr->getLastModemSettings(mgr->getLastDemodulatorType()));
demod->run();
wxGetApp().notifyDemodulatorsChanged();
DemodulatorThread::releaseSquelchLock(nullptr);
}
if (!demod) {
dragState = WF_DRAG_NONE;
return;
}
demod->updateLabel(freq);
demod->setFrequency(freq);
if (demod->isDeltaLock()) {
demod->setDeltaLockOfs(demod->getFrequency() - wxGetApp().getFrequency());
}
if (isNew) {
setStatusText("New demodulator at frequency: %s", freq);
} else {
setStatusText("Moved demodulator to frequency: %s", freq);
}
wxGetApp().getDemodMgr().setActiveDemodulator(demod, false);
SetCursor(wxCURSOR_SIZING);
nextDragState = WF_DRAG_FREQUENCY;
mouseTracker.setVertDragLock(true);
mouseTracker.setHorizDragLock(false);
} else {
if (activeDemod) {
wxGetApp().getDemodMgr().setActiveDemodulator(activeDemod, false);
mgr->updateLastState();
activeDemod->setTracking(true);
nextDragState = WF_DRAG_FREQUENCY;
} else {
nextDragState = WF_DRAG_NONE;
}
}
} else if (dragState == WF_DRAG_RANGE) {
float width = mouseTracker.getOriginDeltaMouseX();
float pos;
std::string last_type = mgr->getLastDemodulatorType();
if (last_type == "LSB" || last_type == "USB") {
float pos1 = mouseTracker.getOriginMouseX();
float pos2 = mouseTracker.getMouseX();
if (pos2 < pos1) {
float tmp = pos1;
pos1 = pos2;
pos2 = tmp;
}
pos = (last_type == "LSB")?pos2:pos1;
width *= 2;
} else {
pos = mouseTracker.getOriginMouseX() + width / 2.0;
}
long long input_center_freq = getCenterFrequency();
long long freq = input_center_freq - (long long) (0.5 * (float) getBandwidth()) + (long long) ((float) pos * (float) getBandwidth());
unsigned int bw = (unsigned int) (fabs(width) * (float) getBandwidth());
if (bw < MIN_BANDWIDTH) {
bw = MIN_BANDWIDTH;
}
if (!bw) {
dragState = WF_DRAG_NONE;
return;
}
int snap = wxGetApp().getFrequencySnap();
if (snap > 1) {
freq = roundl((long double)freq/(double)snap)*snap;
}
if (!isNew && !wxGetApp().getDemodMgr().getDemodulators().empty()) {
mgr->updateLastState();
demod = wxGetApp().getDemodMgr().getCurrentModem();
} else {
demod = wxGetApp().getDemodMgr().newThread();
demod->setFrequency(freq);
demod->setDemodulatorType(mgr->getLastDemodulatorType());
demod->setBandwidth(bw);
demod->setSquelchLevel(mgr->getLastSquelchLevel());
demod->setSquelchEnabled(mgr->isLastSquelchEnabled());
demod->setGain(mgr->getLastGain());
demod->setMuted(mgr->isLastMuted());
if (mgr->getLastDeltaLock()) {
demod->setDeltaLock(true);
demod->setDeltaLockOfs(wxGetApp().getFrequency()-freq);
} else {
demod->setDeltaLock(false);
}
demod->writeModemSettings(mgr->getLastModemSettings(mgr->getLastDemodulatorType()));
demod->run();
wxGetApp().notifyDemodulatorsChanged();
}
if (demod == nullptr) {
dragState = WF_DRAG_NONE;
return;
}
setStatusText("New demodulator at frequency: %s", freq);
demod->updateLabel(freq);
demod->setFrequency(freq);
demod->setBandwidth(bw);
mgr->setActiveDemodulator(demod, false);
mgr->updateLastState();
}
dragState = WF_DRAG_NONE;
}
void WaterfallCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
InteractiveCanvas::OnMouseLeftWindow(event);
SetCursor(wxCURSOR_CROSS);
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr);
mouseZoom = 1.0;
}
void WaterfallCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
InteractiveCanvas::OnMouseEnterWindow(event);
SetCursor(wxCURSOR_CROSS);
#ifdef _WIN32
if (wxGetApp().getAppFrame()->canFocus()) {
this->SetFocus();
}
#endif
}
void WaterfallCanvas::OnMouseRightDown(wxMouseEvent& event) {
InteractiveCanvas::OnMouseRightDown(event);
SetCursor(wxCURSOR_SIZENS);
mouseTracker.setVertDragLock(true);
mouseTracker.setHorizDragLock(true);
}
void WaterfallCanvas::OnMouseRightReleased(wxMouseEvent& event) {
InteractiveCanvas::OnMouseRightReleased(event);
SetCursor(wxCURSOR_CROSS);
mouseTracker.setVertDragLock(false);
mouseTracker.setHorizDragLock(false);
}
SpectrumVisualDataQueuePtr WaterfallCanvas::getVisualDataQueue() {
return visualDataQueue;
}
void WaterfallCanvas::updateCenterFrequency(long long freq) {
if (isView) {
setView(freq, getBandwidth());
if (spectrumCanvas) {
spectrumCanvas->setView(freq, getBandwidth());
}
long long minFreq = wxGetApp().getFrequency()-(wxGetApp().getSampleRate()/2);
long long maxFreq = wxGetApp().getFrequency()+(wxGetApp().getSampleRate()/2);
if (freq - bandwidth / 2 < minFreq) {
wxGetApp().setFrequency(wxGetApp().getFrequency() - (minFreq - (freq - bandwidth/2)));
}
if (freq + bandwidth / 2 > maxFreq) {
wxGetApp().setFrequency(wxGetApp().getFrequency() + ((freq + bandwidth/2) - maxFreq));
}
} else {
if (spectrumCanvas) {
spectrumCanvas->setCenterFrequency(freq);
}
wxGetApp().setFrequency(freq);
}
}
void WaterfallCanvas::setLinesPerSecond(int lps) {
std::lock_guard < std::mutex > lock(tex_update);
linesPerSecond = lps;
//empty all
visualDataQueue->flush();
}
void WaterfallCanvas::setMinBandwidth(int min) {
minBandwidth = min;
}

View File

@ -0,0 +1,102 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/glcanvas.h"
#include "wx/timer.h"
#include <vector>
#include <queue>
#include <memory>
#include "InteractiveCanvas.h"
#include "MouseTracker.h"
#include "SpectrumCanvas.h"
#include "WaterfallPanel.h"
#include "Timer.h"
class WaterfallCanvas: public InteractiveCanvas {
public:
enum DragState {
WF_DRAG_NONE, WF_DRAG_BANDWIDTH_LEFT, WF_DRAG_BANDWIDTH_RIGHT, WF_DRAG_FREQUENCY, WF_DRAG_RANGE
};
WaterfallCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs);
void setup(unsigned int fft_size_in, int waterfall_lines_in);
void setFFTSize(unsigned int fft_size_in);
~WaterfallCanvas() override;
DragState getDragState();
DragState getNextDragState();
void attachSpectrumCanvas(SpectrumCanvas *canvas_in);
void processInputQueue();
SpectrumVisualDataQueuePtr getVisualDataQueue();
void setLinesPerSecond(int lps);
void setMinBandwidth(int min);
//This is public because it is indeed forwarded from
//AppFrame::OnGlobalKeyDown, because global key handler intercepts
//calls in all windows.
void OnKeyDown(wxKeyEvent& event);
//This is public because it is indeed forwarded from
//AppFrame::OnGlobalKeyUp, because global key handler intercepts
//calls in all windows.
void OnKeyUp(wxKeyEvent& event);
//public because called by SpectrumCanvas.
void OnMouseWheelMoved(wxMouseEvent& event);
private:
void OnPaint(wxPaintEvent& event);
void OnIdle(wxIdleEvent &event);
void updateHoverState();
void OnMouseMoved(wxMouseEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnMouseReleased(wxMouseEvent& event);
void OnMouseRightDown(wxMouseEvent& event);
void OnMouseRightReleased(wxMouseEvent& event);
void OnMouseEnterWindow(wxMouseEvent& event);
void OnMouseLeftWindow(wxMouseEvent& event);
void updateCenterFrequency(long long freq);
std::vector<float> spectrum_points;
SpectrumCanvas *spectrumCanvas = nullptr;
PrimaryGLContext *glContext = nullptr;
WaterfallPanel waterfallPanel;
DragState dragState;
DragState nextDragState;
unsigned int fft_size, new_fft_size;
int waterfall_lines;
int dragOfs;
float mouseZoom, zoom;
bool freqMoving;
long double freqMove;
float hoverAlpha;
int linesPerSecond;
float scaleMove;
int dragBW;
SpectrumVisualDataQueuePtr visualDataQueue = std::make_shared<SpectrumVisualDataQueue>();
Timer gTimer;
double lpsIndex;
bool preBuf;
std::mutex tex_update;
int minBandwidth;
std::atomic_bool fft_size_changed;
// event table
wxDECLARE_EVENT_TABLE();
};