Add software
This commit is contained in:
287
Software/CubicSDR/src/visual/ColorTheme.cpp
Normal file
287
Software/CubicSDR/src/visual/ColorTheme.cpp
Normal file
File diff suppressed because one or more lines are too long
139
Software/CubicSDR/src/visual/ColorTheme.h
Normal file
139
Software/CubicSDR/src/visual/ColorTheme.h
Normal 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();
|
||||
};
|
||||
|
315
Software/CubicSDR/src/visual/GainCanvas.cpp
Normal file
315
Software/CubicSDR/src/visual/GainCanvas.cpp
Normal 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();
|
||||
}
|
||||
|
64
Software/CubicSDR/src/visual/GainCanvas.h
Normal file
64
Software/CubicSDR/src/visual/GainCanvas.h
Normal 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();
|
||||
};
|
||||
|
50
Software/CubicSDR/src/visual/ImagePanel.cpp
Normal file
50
Software/CubicSDR/src/visual/ImagePanel.cpp
Normal 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 );
|
||||
}
|
||||
|
||||
|
||||
|
16
Software/CubicSDR/src/visual/ImagePanel.h
Normal file
16
Software/CubicSDR/src/visual/ImagePanel.h
Normal 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()
|
||||
};
|
181
Software/CubicSDR/src/visual/InteractiveCanvas.cpp
Normal file
181
Software/CubicSDR/src/visual/InteractiveCanvas.cpp
Normal 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();
|
||||
}
|
68
Software/CubicSDR/src/visual/InteractiveCanvas.h
Normal file
68
Software/CubicSDR/src/visual/InteractiveCanvas.h
Normal 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;
|
||||
};
|
||||
|
189
Software/CubicSDR/src/visual/MeterCanvas.cpp
Normal file
189
Software/CubicSDR/src/visual/MeterCanvas.cpp
Normal 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;
|
||||
}
|
64
Software/CubicSDR/src/visual/MeterCanvas.h
Normal file
64
Software/CubicSDR/src/visual/MeterCanvas.h
Normal 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();
|
||||
};
|
||||
|
54
Software/CubicSDR/src/visual/MeterContext.cpp
Normal file
54
Software/CubicSDR/src/visual/MeterContext.cpp
Normal 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();
|
||||
}
|
||||
|
20
Software/CubicSDR/src/visual/MeterContext.h
Normal file
20
Software/CubicSDR/src/visual/MeterContext.h
Normal 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:
|
||||
};
|
222
Software/CubicSDR/src/visual/ModeSelectorCanvas.cpp
Normal file
222
Software/CubicSDR/src/visual/ModeSelectorCanvas.cpp
Normal 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;
|
||||
}
|
78
Software/CubicSDR/src/visual/ModeSelectorCanvas.h
Normal file
78
Software/CubicSDR/src/visual/ModeSelectorCanvas.h
Normal 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();
|
||||
};
|
||||
|
74
Software/CubicSDR/src/visual/ModeSelectorContext.cpp
Normal file
74
Software/CubicSDR/src/visual/ModeSelectorContext.cpp
Normal 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();
|
||||
}
|
||||
|
18
Software/CubicSDR/src/visual/ModeSelectorContext.h
Normal file
18
Software/CubicSDR/src/visual/ModeSelectorContext.h
Normal 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();
|
||||
};
|
533
Software/CubicSDR/src/visual/PrimaryGLContext.cpp
Normal file
533
Software/CubicSDR/src/visual/PrimaryGLContext.cpp
Normal 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;
|
||||
}
|
39
Software/CubicSDR/src/visual/PrimaryGLContext.h
Normal file
39
Software/CubicSDR/src/visual/PrimaryGLContext.h
Normal 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);
|
||||
};
|
292
Software/CubicSDR/src/visual/ScopeCanvas.cpp
Normal file
292
Software/CubicSDR/src/visual/ScopeCanvas.cpp
Normal 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);
|
||||
}
|
||||
|
75
Software/CubicSDR/src/visual/ScopeCanvas.h
Normal file
75
Software/CubicSDR/src/visual/ScopeCanvas.h
Normal 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();
|
||||
};
|
||||
|
74
Software/CubicSDR/src/visual/ScopeContext.cpp
Normal file
74
Software/CubicSDR/src/visual/ScopeContext.cpp
Normal 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();
|
||||
}
|
22
Software/CubicSDR/src/visual/ScopeContext.h
Normal file
22
Software/CubicSDR/src/visual/ScopeContext.h
Normal 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:
|
||||
};
|
334
Software/CubicSDR/src/visual/SpectrumCanvas.cpp
Normal file
334
Software/CubicSDR/src/visual/SpectrumCanvas.cpp
Normal 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);
|
||||
}
|
81
Software/CubicSDR/src/visual/SpectrumCanvas.h
Normal file
81
Software/CubicSDR/src/visual/SpectrumCanvas.h
Normal 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();
|
||||
};
|
||||
|
474
Software/CubicSDR/src/visual/TuningCanvas.cpp
Normal file
474
Software/CubicSDR/src/visual/TuningCanvas.cpp
Normal 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;
|
||||
}
|
84
Software/CubicSDR/src/visual/TuningCanvas.h
Normal file
84
Software/CubicSDR/src/visual/TuningCanvas.h
Normal 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();
|
||||
};
|
||||
|
199
Software/CubicSDR/src/visual/TuningContext.cpp
Normal file
199
Software/CubicSDR/src/visual/TuningContext.cpp
Normal 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);
|
||||
}
|
||||
|
29
Software/CubicSDR/src/visual/TuningContext.h
Normal file
29
Software/CubicSDR/src/visual/TuningContext.h
Normal 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;
|
||||
};
|
924
Software/CubicSDR/src/visual/WaterfallCanvas.cpp
Normal file
924
Software/CubicSDR/src/visual/WaterfallCanvas.cpp
Normal 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;
|
||||
}
|
102
Software/CubicSDR/src/visual/WaterfallCanvas.h
Normal file
102
Software/CubicSDR/src/visual/WaterfallCanvas.h
Normal 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();
|
||||
};
|
||||
|
Reference in New Issue
Block a user