dl-fldigi/src/waterfall/waterfall.cxx

1792 wiersze
46 KiB
C++

//
// Waterfall Spectrum Analyzer Widget
//
// Copyright W1HKJ, Dave Freese 2006
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA.
//
// Please report all bugs and problems to "w1hkj@w1hkj.com".
//
//#define USE_BLACKMAN
//#define USE_HAMMING
//#define USE_HANNING
#include <config.h>
#include <sstream>
#include <vector>
#include <algorithm>
#include <map>
#include "waterfall.h"
#include "threads.h"
#include "main.h"
#include "modem.h"
#include "qrunner.h"
#if USE_HAMLIB
#include "hamlib.h"
#endif
#include "rigMEM.h"
#include "rigio.h"
#include "fldigi-config.h"
#include "configuration.h"
#include "Viewer.h"
#include "macros.h"
#include "arq_io.h"
#include "confdialog.h"
#include "gettext.h"
extern modem *active_modem;
static RGB RGByellow = {254,254,0};
//static RGB RGBgreen = {0,254,0};
//static RGB RGBdkgreen = {0,128,0};
//static RGB RGBblue = {0,0,255};
static RGB RGBred = {254,0,0};
//static RGB RGBwhite = {254,254,254};
//static RGB RGBblack = {0,0,0};
//static RGB RGBmagenta = {196,0,196};
//static RGB RGBblack = {0,0,0};
// RGBI is a structure consisting of the values RED, GREEN, BLUE, INTENSITY
// each value can range from 0 (extinguished) to 255 (full on)
// the INTENSITY value is used for the grayscale waterfall display
RGBI mag2RGBI[256];
RGB palette[9];
short int *tmp_fft_db;
WFdisp::WFdisp (int x0, int y0, int w0, int h0, char *lbl) :
Fl_Widget(x0,y0,w0,h0,"") {
disp_width = w();
if (disp_width > IMAGE_WIDTH/4)
disp_width = IMAGE_WIDTH/4;
scale_width = IMAGE_WIDTH + 1000;
image_height = h() - WFTEXT - WFSCALE - WFMARKER;
image_area = IMAGE_WIDTH * image_height;
sig_image_area = IMAGE_WIDTH * h();
RGBsize = sizeof(RGB);
RGBwidth = RGBsize * IMAGE_WIDTH;
fft_img = new RGBI[image_area];
markerimage = new RGB[IMAGE_WIDTH * WFMARKER];
scaleimage = new uchar[scale_width * WFSCALE];
scline = new uchar[scale_width];
fft_sig_img = new uchar[image_area];
sig_img = new uchar[sig_image_area];
pwr = new double[IMAGE_WIDTH];
fft_db = new short int[image_area];
tmp_fft_db = new short int[image_area];
circbuff = new double[FFT_LEN * 2];
fftout = new double[FFT_LEN * 2];
wfft = new Cfft(FFT_LEN);
fftwindow = new double[FFT_LEN * 2];
setPrefilter(progdefaults.wfPreFilter);
for (int i = 0; i < FFT_LEN*2; i++)
circbuff[i] = fftout[i] = 0.0;
mag = 1;
step = 4;
dispcolor = true;
offset = 0;
sigoffset = 0;
ampspan = 75;
reflevel = -10;
initmaps();
bandwidth = 32;
RGBmarker = RGBred;
RGBcursor = RGByellow;
mode = WATERFALL;
centercarrier = false;
overload = false;
peakaudio = 0.0;
rfc = 0L;
usb = true;
wfspeed = NORMAL;
srate = 8000;
wfspdcnt = 0;
dispcnt = 4;
wantcursor = false;
cursormoved = false;
// usebands = false;
for (int i = 0; i < IMAGE_WIDTH; i++)
pwr[i] = 0.0;
carrier(1000);
oldcarrier = newcarrier = 0;
tmp_carrier = false;
ptrCB = 0;
ptrFFTbuff = 0;
for (int i = 0; i < 256; i++)
mag2RGBI[i].I = mag2RGBI[i].R = mag2RGBI[i].G = mag2RGBI[i].B = 0;
}
WFdisp::~WFdisp() {
delete wfft;
delete [] fft_img;
delete [] scaleimage;
delete [] markerimage;
delete [] fft_sig_img;
delete [] sig_img;
delete [] pwr;
delete [] scline;
delete [] fft_db;
delete [] tmp_fft_db;
}
void WFdisp::initMarkers() {
uchar *c1 = (uchar *)markerimage,
*c2 = c1 + RGBwidth * (WFMARKER - 1);
memset(c1, 196, RGBwidth);
memset(c2, 196, RGBwidth);
}
void WFdisp::makeMarker() {
RGB *clrMin, *clrMax, *clrM, *clrPos;
clrMin = markerimage + IMAGE_WIDTH;
clrMax = clrMin + (WFMARKER - 2) * IMAGE_WIDTH;
memset(clrMin, 0, RGBwidth * (WFMARKER - 2));
clrM = clrMin + (int)((double)carrierfreq + 0.5);
int bw, marker_width = bandwidth;
if (active_modem) {
int mode = active_modem->get_mode();
if (mode >= MODE_BPSK31 && mode <= MODE_QPSK250)
marker_width += mailserver ? progdefaults.ServerOffset :
progdefaults.SearchRange;
if (mode >= MODE_FELDHELL && mode <= MODE_HELL80)
marker_width = (int)progdefaults.HELL_BW;
}
marker_width = (int)(marker_width / 2.0 + 1);
RGBmarker.R = progdefaults.bwTrackRGBI.R;
RGBmarker.G = progdefaults.bwTrackRGBI.G;
RGBmarker.B = progdefaults.bwTrackRGBI.B;
// clamp marker to image width
bw = marker_width;
int bw_lower = -bw, bw_upper = +bw;
if (bw_lower + static_cast<int>(carrierfreq+0.5) < 0)
bw_lower -= bw_lower + static_cast<int>(carrierfreq+0.5);
if (bw_upper + static_cast<int>(carrierfreq+0.5) > IMAGE_WIDTH)
bw_upper -= bw_upper + static_cast<int>(carrierfreq+0.5) - IMAGE_WIDTH;
for (int y = 0; y < WFMARKER - 2; y++) {
for (int i = bw_lower; i <= bw_upper ; i++) {
clrPos = clrM + i + y * IMAGE_WIDTH;
if (clrPos > clrMin && clrPos < clrMax)
*clrPos = RGBmarker;
}
}
if (!wantcursor) return;
if (cursorpos > disp_width - bandwidth / 2 / step)
cursorpos = disp_width - bandwidth / 2 / step;
if (cursorpos >= (IMAGE_WIDTH - offset - bandwidth/2)/step)
cursorpos = (IMAGE_WIDTH - offset - bandwidth/2)/step - 1;
if (cursorpos < bandwidth / 2 / step)
cursorpos = bandwidth / 2 / step + 1;
// Create the cursor marker
double xp = offset + step * cursorpos;
if (xp < bandwidth / 2.0 || xp > (IMAGE_WIDTH - bandwidth / 2.0))
return;
clrM = markerimage + IMAGE_WIDTH + (int)(xp + 0.5);
RGBcursor.R = progdefaults.cursorLineRGBI.R;
RGBcursor.G = progdefaults.cursorLineRGBI.G;
RGBcursor.B = progdefaults.cursorLineRGBI.B;
bw = marker_width;
for (int y = 0; y < WFMARKER - 2; y++) {
int incr = y * IMAGE_WIDTH;
int msize = (WFMARKER - 2 - y)*RGBsize*step/4;
*(clrM + incr - 1) =
*(clrM + incr) =
*(clrM + incr + 1) = RGBcursor;
if (xp - (bw + msize) > 0)
for (int i = bw - msize; i <= bw + msize; i++)
*(clrM - i + incr) = RGBcursor;
if (xp + (bw + msize) < IMAGE_WIDTH)
for (int i = bw - msize; i <= bw + msize; i++)
*(clrM + i + incr) = RGBcursor;
}
}
void WFdisp::makeScale() {
uchar *gmap = scaleimage;
int hwidth = step / 2;
memset(scline, 0, scale_width);
for (int tic = 500; tic < scale_width; tic += 500) {
if (hwidth)
for (int ticn = -hwidth; ticn < hwidth; ticn++)
scline[tic + ticn] = 255;
else
scline[tic] = 255;
}
for (int i = 0; i < WFSCALE - 5; i++) {
memcpy(gmap, scline, scale_width);
gmap += (scale_width);
}
for (int tic = 100; tic < scale_width ; tic += 100) {
if (hwidth)
for (int ticn = -hwidth; ticn < hwidth; ticn++)
scline[tic + ticn] = 255;
else
scline[tic] = 255;
}
for (int i = 0; i < 5; i++) {
memcpy(gmap, scline, scale_width);
gmap += (scale_width);
}
}
void WFdisp::setcolors() {
double di;
int r, g, b;
for (int i = 0; i < 256; i++) {
di = sqrt((double)i / 256.0);
mag2RGBI[i].I = (uchar)(200*di);
}
for (int n = 0; n < 8; n++) {
for (int i = 0; i < 32; i++) {
r = palette[n].R + (int)(1.0 * i * (palette[n+1].R - palette[n].R) / 32.0);
g = palette[n].G + (int)(1.0 * i * (palette[n+1].G - palette[n].G) / 32.0);
b = palette[n].B + (int)(1.0 * i * (palette[n+1].B - palette[n].B) / 32.0);
mag2RGBI[i + 32*n].R = r;
mag2RGBI[i + 32*n].G = g;
mag2RGBI[i + 32*n].B = b;
}
}
}
void WFdisp::initmaps() {
for (int i = 0; i < image_area; i++) fft_db[i] = tmp_fft_db[i] = log2disp(-1000);
memset (fft_img, 0, image_area * sizeof(RGBI) );
memset (scaleimage, 0, scale_width * WFSCALE);
memset (markerimage, 0, IMAGE_WIDTH * WFMARKER);
memset (fft_sig_img, 0, image_area);
memset (sig_img, 0, sig_image_area);
memset (mag2RGBI, 0, sizeof(mag2RGBI));
initMarkers();
makeScale();
setcolors();
}
int WFdisp::peakFreq(int f0, int delta)
{
double threshold = 0.0;
int f1, fmin = (int)((f0 - delta)),
f2, fmax = (int)((f0 + delta));
f1 = fmin; f2 = fmax;
if (fmin < 0 || fmax > IMAGE_WIDTH) return f0;
for (int f = fmin; f <= fmax; f++)
threshold += pwr[f];
threshold /= delta;
for (int f = fmin; f <= fmax; f++)
if (pwr[f] > threshold) {
f2 = f;
}
for (int f = fmax; f >= fmin; f--)
if (pwr[f] > threshold) {
f1 = f;
}
return (f1 + f2) / 2;
}
double WFdisp::powerDensity(double f0, double bw)
{
double pwrdensity = 0.0;
int flower = (int)((f0 - bw/2)),
fupper = (int)((f0 + bw/2));
if (flower < 0 || fupper > IMAGE_WIDTH)
return 0.0;
for (int i = flower; i <= fupper; i++)
pwrdensity += pwr[i];
return pwrdensity/(bw+1);
}
int WFdisp::log2disp(int v)
{
double val = 255.0 * (reflevel- v) / ampspan;
if (val < 0) val = 0;
if (val > 255 ) val = 255;
return (int)(255 - val);
}
void WFdisp::processFFT() {
int n;
double scale;
int ptrSample;
scale = (double)SC_SMPLRATE / srate;
scale *= FFT_LEN / 2000.0;
if (dispcnt == 0) {
memset (fftout, 0, FFT_LEN*2*sizeof(double));
ptrSample = ptrCB;
for (int i = 0; i < FFT_LEN * 2 * progdefaults.latency / 8; i++) {
fftout[i] = fftwindow[i * 8 / progdefaults.latency] * circbuff[ptrSample];
ptrSample = (ptrSample + 1) % (FFT_LEN *2);
}
wfft->rdft(fftout);
FL_LOCK_D();
double pw;
int ffth;
for (int i = 0; i < IMAGE_WIDTH; i++) {
n = 2*(int)((scale * i / 2 + 0.5));
if (i <= progdefaults.LowFreqCutoff)
pw = 0.0;
else
pw = fftout[n]*fftout[n];
pwr[i] = pw;
ffth = (int)(10.0 * log10(pw + 1e-10) );
fft_db[ptrFFTbuff * IMAGE_WIDTH + i] = log2disp(ffth);
}
ptrFFTbuff--;
if (ptrFFTbuff < 0) ptrFFTbuff += image_height;
FL_UNLOCK_D();
}
if (dispcnt == 0) {
FL_LOCK_D();
if (dispcnt == 0) {
for (int i = 0; i < image_height; i++) {
int j = (i + 1 + ptrFFTbuff) % image_height;
memmove( (void *)(tmp_fft_db + i * IMAGE_WIDTH),
(void *)(fft_db + j * IMAGE_WIDTH),
IMAGE_WIDTH * sizeof(short int));
}
}
redraw();
FL_UNLOCK_D();
}
if (dispcnt == 0) {
if (srate == 8000)
dispcnt = wfspeed;
else if (srate == 11025)
dispcnt = wfspeed * 4 / 3;
else
dispcnt = wfspeed * 2;
}
--dispcnt;
}
void WFdisp::process_analog (double *sig, int len) {
int h1, h2, h3, sigw, sigy, sigpixel, ynext, graylevel;
h1 = h()/8 - 1;
h2 = h()/2 - 1;
h3 = h()*7/8 + 1;
sigw = IMAGE_WIDTH;
graylevel = 220;
// clear the signal display area
sigy = 0;
sigpixel = IMAGE_WIDTH*h2;
//FL_LOCK();
FL_LOCK_D();
memset (sig_img, 0, sig_image_area);
memset (&sig_img[h1*IMAGE_WIDTH], 160, IMAGE_WIDTH);
memset (&sig_img[h2*IMAGE_WIDTH], 255, IMAGE_WIDTH);
memset (&sig_img[h3*IMAGE_WIDTH], 160, IMAGE_WIDTH);
int cbc = ptrCB;
for (int c = 0; c < IMAGE_WIDTH; c++) {
ynext = (int)(h2 * sig[cbc]);
cbc = (cbc + 1) % (FFT_LEN *2);
for (; sigy < ynext; sigy++) sig_img[sigpixel -= IMAGE_WIDTH] = graylevel;
for (; sigy > ynext; sigy--) sig_img[sigpixel += IMAGE_WIDTH] = graylevel;
sig_img[sigpixel++] = graylevel;
}
redraw();
//FL_UNLOCK();
FL_UNLOCK_D();
}
void WFdisp::redrawCursor()
{
redraw();
// cursormoved = true;
}
void WFdisp::sig_data( double *sig, int len, int sr ) {
if (wfspeed == PAUSE)
return;
//if sound card sampling rate changed reset the waterfall buffer
if (srate != sr) {
srate = sr;
memset (circbuff, 0, FFT_LEN * 2 * sizeof(double));
ptrCB = 0;
}
overload = false;
double overval, peak = 0.0;
for (int i = 0; i < len; i++) {
overval = fabs(circbuff[ptrCB] = sig[i]);
ptrCB = (ptrCB + 1) % (FFT_LEN *2);
if (overval > peak) peak = overval;
}
peakaudio = 0.1 * peak + 0.9 * peakaudio;
if (mode == SCOPE)
process_analog(circbuff, FFT_LEN * 2);
else
processFFT();
put_WARNstatus(peakaudio);
static char szFrequency[14];
// if (usebands)
// rfc = (long long)(atof(cboBand->value()) * 1000.0);
if (rfc != 0) { // use a boolean for the waterfall
if (usb)
dfreq = rfc + active_modem->get_txfreq();
else
dfreq = rfc - active_modem->get_txfreq();
snprintf(szFrequency, sizeof(szFrequency), "%-.3f", dfreq / 1000.0);
} else {
dfreq = active_modem->get_txfreq();
snprintf(szFrequency, sizeof(szFrequency), "%-.0f", dfreq);
}
FL_LOCK_D();
inpFreq->value(szFrequency);
FL_UNLOCK_D();
return;
}
// Check the display offset & limit to 0 to max IMAGE_WIDTH displayed
void WFdisp::checkoffset() {
if (mode == SCOPE) {
if (sigoffset < 0)
sigoffset = 0;
if (sigoffset > (IMAGE_WIDTH - disp_width))
sigoffset = IMAGE_WIDTH - disp_width;
} else {
if (offset < 0)
offset = 0;
if (offset > (int)(IMAGE_WIDTH - step * disp_width))
offset = (int)(IMAGE_WIDTH - step * disp_width);
}
}
void WFdisp::slew(int dir) {
if (mode == SCOPE)
sigoffset += dir;
else
offset += dir;
checkoffset();
}
void WFdisp::movetocenter() {
if (mode == SCOPE)
sigoffset = IMAGE_WIDTH / 2;
else
offset = carrierfreq - (disp_width * step / 2);
checkoffset();
}
void WFdisp::carrier(int cf) {
if (cf > bandwidth / 2 && cf < (IMAGE_WIDTH - bandwidth / 2)) {
carrierfreq = cf;
makeMarker();
redrawCursor();
}
}
int WFdisp::carrier() {
return carrierfreq;
}
void WFdisp::checkWidth()
{
disp_width = w();
if (mag == MAG_1) step = 4;
if (mag == MAG_1 && disp_width > IMAGE_WIDTH/4)
disp_width = IMAGE_WIDTH/4;
if (mag == MAG_2) step = 2;
if (mag == MAG_2 && disp_width > IMAGE_WIDTH/2)
disp_width = IMAGE_WIDTH/2;
if (mag == MAG_4) step = 1;
}
int WFdisp::checkMag()
{
checkWidth();
makeScale();
return mag;
}
int WFdisp::setMag(int m)
{
int mid = offset + (disp_width * step / 2);
mag = m;
checkMag();
if (centercarrier || Fl::event_shift()) {
offset = mid - (disp_width * step / 2);
}
else {
movetocenter();
}
return mag;
}
int WFdisp::wfmag() {
int mid = offset + (disp_width * step / 2);
if (mag == MAG_1) mag = MAG_2;
else if (mag == MAG_2) mag = MAG_4;
else mag = MAG_1;
checkMag();
if (centercarrier || Fl::event_shift()) {
offset = mid - (disp_width * step / 2);
}
else {
movetocenter();
}
return mag;
}
void WFdisp::drawScale() {
int fw = 60, xchar;
static char szFreq[20];
double fr;
uchar *pixmap;
if (usb || !rfc)
pixmap = (scaleimage + (int)((rfc % 1000 + offset)) );
else
pixmap = (scaleimage + (int)((1000 - rfc % 1000 + offset)));
fl_draw_image_mono(
pixmap,
x(), y() + WFTEXT,
disp_width, WFSCALE,
step, scale_width);
fl_color(FL_BLACK);
fl_rectf(x(), y(), disp_width, WFTEXT);
fl_color(fl_rgb_color(228));
fl_font(progdefaults.WaterfallFontnbr, progdefaults.WaterfallFontsize);
for (int i = 1; i < 10; i++) {
if (progdefaults.wf_audioscale)
fr = 500.0 * i;
else {
if (usb)
fr = (rfc - (rfc%500))/1000.0 + 0.5*i;
else
fr = (rfc - (rfc %500))/1000.0 + 0.5 - 0.5*i;
}
if (progdefaults.wf_audioscale)
snprintf(szFreq, sizeof(szFreq), "%7.0f", fr);
else
snprintf(szFreq, sizeof(szFreq), "%7.1f", fr);
fw = (int)fl_width(szFreq);
if (usb || progdefaults.wf_audioscale)
xchar = (int) ( ( (1000.0/step) * i - fw) / 2.0 -
(offset + rfc % 500) /step );
else
xchar = (int) ( ( (1000.0/step) * i - fw) / 2.0 -
(offset + 500 - rfc % 500) /step );
if (xchar > 0 && (xchar + fw) < disp_width)
fl_draw(szFreq, x() + xchar, y() + 10 );
}
}
void WFdisp::drawMarker() {
if (mode == SCOPE) return;
uchar *pixmap = (uchar *)(markerimage + (int)(offset));
fl_draw_image(
pixmap,
x(), y() + WFSCALE + WFTEXT,
disp_width, WFMARKER,
step * RGBsize, RGBwidth);
}
void WFdisp::update_waterfall() {
// transfer the fft history data into the WF image
short int *p1, *p2;
RGBI *p3, *p4;
p1 = tmp_fft_db + offset;
p2 = p1;
p3 = fft_img;
p4 = p3;
short* limit = tmp_fft_db + image_area - step + 1;
for (int row = 0; row < image_height; row++) {
p2 = p1;
p4 = p3;
if (progdefaults.WFaveraging) {
if (step == 4)
for (int col = 0; col < disp_width; col++) {
*(p4++) = mag2RGBI[ (*p2+ *(p2+1)+ *(p2+2)+ *(p2+3))/4 ];
p2 += step;
if (p2 > limit) break;
}
else if (step == 2)
for (int col = 0; col < disp_width; col++) {
*(p4++) = mag2RGBI[ (*p2 + *(p2+1))/2 ];
p2 += step;
if (p2 > limit) break;
}
else
for (int col = 0; col < disp_width; col++) {
*(p4++) = mag2RGBI[ *p2 ];
p2 += step;
if (p2 > limit) break;
}
} else {
if (step == 4)
for (int col = 0; col < disp_width; col++) {
*(p4++) = mag2RGBI[ MAX( MAX ( MAX ( *p2, *(p2+1) ), *(p2+2) ), *(p2+3) ) ];
p2 += step;
if (p2 > limit) break;
}
else if (step == 2)
for (int col = 0; col < disp_width; col++) {
*(p4++) = mag2RGBI[ MAX( *p2, *(p2+1) ) ];
p2 += step;
if (p2 > limit) break;
}
else
for (int col = 0; col < disp_width; col++) {
*(p4++) = mag2RGBI[ *p2 ];
p2 += step;
if (p2 > limit) break;
}
}
p1 += IMAGE_WIDTH;
p3 += disp_width;
}
if (progdefaults.UseBWTracks) {
RGBI *pos1 = fft_img + (carrierfreq - offset - bandwidth/2) / step;
RGBI *pos2 = fft_img + (carrierfreq - offset + bandwidth/2) / step;
if (pos1 >= fft_img && pos2 < fft_img + disp_width)
for (int y = 0; y < image_height; y ++) {
*pos1 = *pos2 = progdefaults.bwTrackRGBI;
pos1 += disp_width;
pos2 += disp_width;
}
}
}
void WFdisp::drawcolorWF() {
uchar *pixmap = (uchar *)fft_img;
fl_color(FL_BLACK);
fl_rectf(x() + disp_width, y(), w() - disp_width, h());
update_waterfall();
if (wantcursor && (progdefaults.UseCursorLines || progdefaults.UseCursorCenterLine) ) {
RGBI *pos0 = (fft_img + cursorpos);
RGBI *pos1 = (fft_img + cursorpos - bandwidth/2/step);
RGBI *pos2 = (fft_img + cursorpos + bandwidth/2/step);
if (pos1 >= fft_img && pos2 < fft_img + disp_width)
for (int y = 0; y < image_height; y ++) {
if (progdefaults.UseCursorLines)
*pos1 = *pos2 = progdefaults.cursorLineRGBI;
if (progdefaults.UseCursorCenterLine)
*pos0 = progdefaults.cursorCenterRGBI;
pos0 += disp_width;
pos1 += disp_width;
pos2 += disp_width;
}
}
fl_draw_image(
pixmap, x(), y() + WFSCALE + WFMARKER + WFTEXT,
disp_width, image_height,
sizeof(RGBI), disp_width * sizeof(RGBI) );
drawScale();
}
void WFdisp::drawgrayWF() {
uchar *pixmap = (uchar*)fft_img;
fl_color(FL_BLACK);
fl_rectf(x() + disp_width, y(), w() - disp_width, h());
update_waterfall();
if (wantcursor && (progdefaults.UseCursorLines || progdefaults.UseCursorCenterLine) ) {
RGBI *pos0 = (fft_img + cursorpos);
RGBI *pos1 = (fft_img + cursorpos - bandwidth/2/step);
RGBI *pos2 = (fft_img + cursorpos + bandwidth/2/step);
if (pos1 >= fft_img && pos2 < fft_img + disp_width)
for (int y = 0; y < image_height; y ++) {
if (progdefaults.UseCursorLines)
*pos1 = *pos2 = progdefaults.cursorLineRGBI;
if (progdefaults.UseCursorCenterLine)
*pos0 = progdefaults.cursorCenterRGBI;
pos0 += disp_width;
pos1 += disp_width;
pos2 += disp_width;
}
}
fl_draw_image_mono(
pixmap + 3,
x(), y() + WFSCALE + WFMARKER + WFTEXT,
disp_width, image_height,
sizeof(RGBI), disp_width * sizeof(RGBI));
drawScale();
}
void WFdisp::drawspectrum() {
int sig;
int ynext,
h1 = image_height - 1,
ffty = 0,
fftpixel = IMAGE_WIDTH * h1,
graylevel = 220;
uchar *pixmap = (uchar *)fft_sig_img + offset / step;
memset (fft_sig_img, 0, image_area);
fftpixel /= step;
for (int c = 0; c < IMAGE_WIDTH; c += step) {
sig = tmp_fft_db[c];
if (step == 1)
sig = tmp_fft_db[c];
else if (step == 2)
sig = MAX(tmp_fft_db[c], tmp_fft_db[c+1]);
else
sig = MAX( MAX ( MAX ( tmp_fft_db[c], tmp_fft_db[c+1] ), tmp_fft_db[c+2] ), tmp_fft_db[c+3]);
ynext = h1 * sig / 256;
while (ffty < ynext) { fft_sig_img[fftpixel -= IMAGE_WIDTH/step] = graylevel; ffty++;}
while (ffty > ynext) { fft_sig_img[fftpixel += IMAGE_WIDTH/step] = graylevel; ffty--;}
fft_sig_img[fftpixel++] = graylevel;
}
if (progdefaults.UseBWTracks) {
uchar *pos1 = pixmap + (carrierfreq - offset - bandwidth/2) / step;
uchar *pos2 = pixmap + (carrierfreq - offset + bandwidth/2) / step;
if (pos1 >= pixmap && pos2 < pixmap + disp_width)
for (int y = 0; y < image_height; y ++) {
*pos1 = *pos2 = 255;
pos1 += IMAGE_WIDTH/step;
pos2 += IMAGE_WIDTH/step;
}
}
if (wantcursor && (progdefaults.UseCursorLines || progdefaults.UseCursorCenterLine)) {
uchar *pos0 = pixmap + cursorpos;
uchar *pos1 = (pixmap + cursorpos - bandwidth/2/step);
uchar *pos2 = (pixmap + cursorpos + bandwidth/2/step);
for (int y = 0; y < h1; y ++) {
if (progdefaults.UseCursorLines)
*pos1 = *pos2 = 255;
if (progdefaults.UseCursorCenterLine)
*pos0 = 255;
pos0 += IMAGE_WIDTH/step;
pos1 += IMAGE_WIDTH/step;
pos2 += IMAGE_WIDTH/step;
}
}
fl_color(FL_BLACK);
fl_rectf(x() + disp_width, y(), w() - disp_width, h());
fl_draw_image_mono(
pixmap,
x(), y() + WFSCALE + WFMARKER + WFTEXT,
disp_width, image_height,
1, IMAGE_WIDTH / step);
drawScale();
}
void WFdisp::drawsignal() {
uchar *pixmap = (uchar *)(sig_img + sigoffset);
fl_color(FL_BLACK);
fl_rectf(x() + disp_width, y(), w() - disp_width, h());
fl_draw_image_mono(pixmap, x(), y(), disp_width, h(), 1, IMAGE_WIDTH);
}
void WFdisp::draw() {
checkoffset();
checkWidth();
switch (mode) {
case SPECTRUM :
drawspectrum();
drawMarker();
break;
case SCOPE :
drawsignal();
break;
case WATERFALL :
default:
if (dispcolor)
drawcolorWF();
else
drawgrayWF();
drawMarker();
}
}
//=======================================================================
// waterfall
//=======================================================================
void x1_cb(Fl_Widget *w, void* v) {
waterfall *wf = (waterfall *)w->parent();
int m = wf->wfdisp->wfmag();
if (m == MAG_1) w->label("x1");
if (m == MAG_2) w->label("x2");
if (m == MAG_4) w->label("x4");
restoreFocus();
}
void slew_left(Fl_Widget *w, void * v) {
waterfall *wf = (waterfall *)w->parent();
wf->wfdisp->slew(-100);
restoreFocus();
}
void slew_right(Fl_Widget *w, void * v) {
waterfall *wf = (waterfall *)w->parent();
wf->wfdisp->slew(100);
restoreFocus();
}
void center_cb(Fl_Widget *w, void *v) {
waterfall *wf = (waterfall *)w->parent();
wf->wfdisp->movetocenter();
restoreFocus();
}
void carrier_cb(Fl_Widget *w, void *v) {
Fl_Counter *cntr = (Fl_Counter *)w;
waterfall *wf = (waterfall *)w->parent();
int selfreq = (int) cntr->value();
active_modem->set_freq(selfreq);
wf->wfdisp->carrier(selfreq);
restoreFocus();
}
void qsy_cb(Fl_Widget *w, void *v)
{
static vector<qrg_mode_t> qsy_stack;
qrg_mode_t m;
if (Fl::event_button() != FL_RIGHT_MOUSE) {
// store
m.rfcarrier = wf->rfcarrier();
m.carrier = active_modem->get_freq();
qsy_stack.push_back(m);
// qsy to the sweet spot frequency that is the center of the PBF in the rig
switch (active_modem->get_mode()) {
case MODE_CW:
m.carrier = (long long)progdefaults.CWsweetspot;
break;
case MODE_RTTY:
m.carrier = (long long)progdefaults.RTTYsweetspot;
break;
default:
m.carrier = (long long)progdefaults.PSKsweetspot;
break;
}
if (wf->USB())
m.rfcarrier += (wf->carrier() - m.carrier);
else
m.rfcarrier -= (wf->carrier() - m.carrier);
}
else { // qsy to top of stack
if (qsy_stack.size()) {
m = qsy_stack.back();
qsy_stack.pop_back();
}
}
if (m.carrier > 0)
qsy(m.rfcarrier, m.carrier);
restoreFocus();
}
void rate_cb(Fl_Widget *w, void *v) {
waterfall* wf = static_cast<waterfall*>(w->parent());
WFspeed new_speed;
switch (wf->wfdisp->Speed()) {
case SLOW:
new_speed = NORMAL;
break;
case NORMAL: default:
new_speed = FAST;
break;
case FAST:
new_speed = PAUSE;
break;
case PAUSE:
new_speed = SLOW;
break;
}
wf->Speed(new_speed);
restoreFocus();
}
void xmtrcv_cb(Fl_Widget *w, void *vi)
{
FL_LOCK_D();
Fl_Light_Button *b = (Fl_Light_Button *)w;
int v = b->value();
FL_UNLOCK_D();
if (v == 1) {
stopMacroTimer();
active_modem->set_stopflag(false);
trx_transmit();
} else {
extern Fl_Button* btnTune;
if (btnTune->value()) {
btnTune->value(0);
btnTune->do_callback();
}
else {
active_modem->set_stopflag(true);
TransmitText->clear();
if (arq_text_available)
AbortARQ();
if (progdefaults.useTimer)
progdefaults.useTimer = false;
}
}
restoreFocus();
}
void xmtlock_cb(Fl_Widget *w, void *vi)
{
FL_LOCK_D();
Fl_Light_Button *b = (Fl_Light_Button *)w;
int v = b->value();
FL_UNLOCK_D();
active_modem->set_freqlock(v ? true : false );
restoreFocus();
}
void waterfall::set_XmtRcvBtn(bool val)
{
FL_LOCK_D();
xmtrcv->value(val);
FL_UNLOCK_D();
}
void mode_cb(Fl_Widget *w, void *v) {
FL_LOCK_D();
waterfall *wf = (waterfall *)w->parent();
if (Fl::event_shift()) {
wf->wfdisp->Mode(SCOPE);
w->label("sig");
wf->x1->deactivate();
// wf->bw_rsid->deactivate();
wf->wfcarrier->deactivate();
wf->wfRefLevel->deactivate();
wf->wfAmpSpan->deactivate();
} else if (wf->wfdisp->Mode() == WATERFALL) {
wf->wfdisp->Mode(SPECTRUM);
w->label("fft");
} else if (wf->wfdisp->Mode() == SPECTRUM) {
wf->wfdisp->Mode(WATERFALL);
w->label("Wtr");
} else {
wf->wfdisp->Mode(WATERFALL);
w->label("Wtr");
wf->x1->activate();
// wf->bw_rsid->activate();
wf->wfcarrier->activate();
wf->wfRefLevel->activate();
wf->wfAmpSpan->activate();
}
FL_UNLOCK_D();
restoreFocus();
}
void reflevel_cb(Fl_Widget *w, void *v) {
FL_LOCK_D();
waterfall *wf = (waterfall *)w->parent();
double val = wf->wfRefLevel->value();
FL_UNLOCK_D();
wf->wfdisp->Reflevel(val);
progdefaults.wfRefLevel = val;
restoreFocus();
}
void ampspan_cb(Fl_Widget *w, void *v) {
FL_LOCK_D();
waterfall *wf = (waterfall *)w->parent();
double val = wf->wfAmpSpan->value();
FL_UNLOCK_D();
wf->wfdisp->Ampspan(val);
progdefaults.wfAmpSpan = val;
restoreFocus();
}
void btnRev_cb(Fl_Widget *w, void *v) {
FL_LOCK_D();
waterfall *wf = (waterfall *)w->parent();
Fl_Light_Button *b = (Fl_Light_Button *)w;
wf->Reverse(b->value());
FL_UNLOCK_D();
active_modem->set_reverse(wf->Reverse());
restoreFocus();
}
void btnMem_cb(Fl_Widget *, void *menu_event)
{
static std::vector<qrg_mode_t> qrg_list;
enum { SELECT, APPEND, REPLACE, REMOVE, CLEAR };
int op = SELECT, elem = 0;
if (menu_event) { // event on popup menu
elem = wf->mbtnMem->value();
switch (Fl::event_button()) {
case FL_MIDDLE_MOUSE:
op = REPLACE;
break;
case FL_LEFT_MOUSE: case FL_RIGHT_MOUSE: default:
op = (Fl::event_state() & FL_SHIFT) ? REMOVE : SELECT;
break;
}
}
else { // button press
switch (Fl::event_button()) {
case FL_RIGHT_MOUSE:
return;
case FL_MIDDLE_MOUSE: // select last
if ((elem = qrg_list.size() - 1) < 0)
return;
op = SELECT;
break;
case FL_LEFT_MOUSE: default:
op = (Fl::event_state() & FL_SHIFT) ? CLEAR : APPEND;
break;
}
}
qrg_mode_t m;
switch (op) {
case SELECT:
m = qrg_list[elem];
if (active_modem != *mode_info[m.mode].modem)
init_modem_sync(m.mode);
if (m.rfcarrier && m.rfcarrier != wf->rfcarrier())
qsy(m.rfcarrier, m.carrier);
else
active_modem->set_freq(m.carrier);
break;
case REMOVE:
wf->mbtnMem->remove(elem);
qrg_list.erase(qrg_list.begin() + elem);
break;
case CLEAR:
wf->mbtnMem->clear();
qrg_list.clear();
break;
case APPEND: case REPLACE:
m.rfcarrier = wf->rfcarrier();
m.carrier = active_modem->get_freq();
m.mode = active_modem->get_mode();
if (op == APPEND) {
if (find(qrg_list.begin(), qrg_list.end(), m) == qrg_list.end())
qrg_list.push_back(m);
else
break;
}
else
qrg_list[elem] = m;
// write the menu item text
{
ostringstream o;
o << mode_info[m.mode].sname << " @@ ";
if (m.rfcarrier > 0) { // write 1000s separators
char s[20], *p = s + sizeof(s) - 1;
int i = 0;
*p = '\0';
do {
if (i % 3 == 0 && i)
*--p = '.';
*--p = '0' + m.rfcarrier % 10;
++i;
} while ((m.rfcarrier /= 10) && p > s);
o << p << (wf->USB() ? " + " : " - ");
}
o << m.carrier;
if (op == APPEND)
wf->mbtnMem->add(o.str().c_str());
else
wf->mbtnMem->replace(elem, o.str().c_str());
}
break;
}
restoreFocus();
}
void waterfall::opmode() {
int val = (int)active_modem->get_bandwidth();
if (wfdisp->carrier() < val/2)
wfdisp->carrier( val/2 );
if (wfdisp->carrier() > IMAGE_WIDTH - val/2)
wfdisp->carrier( IMAGE_WIDTH - val/2);
wfdisp->Bandwidth( val );
FL_LOCK_D();
wfcarrier->range(val/2-1, IMAGE_WIDTH - val/2-1);
FL_UNLOCK_D();
}
void waterfall::carrier(int f) {
wfdisp->carrier(f);
FL_LOCK_D();
wfcarrier->value(f);
wfcarrier->damage(FL_DAMAGE_ALL);
FL_UNLOCK_D();
}
int waterfall::Speed() {
return (int)wfdisp->Speed();
}
void waterfall::Speed(int rate)
{
WFspeed speed = static_cast<WFspeed>(rate);
wfdisp->Speed(speed);
const char* label;
switch (speed) {
case SLOW:
label = "SLOW";
break;
case NORMAL: default:
label = "NORM";
break;
case FAST:
label = "FAST";
break;
case PAUSE:
label = "PAUSE";
break;
}
wfrate->label(label);
wfrate->redraw_label();
}
int waterfall::Mag() {
return wfdisp->Mag();
}
void waterfall::Mag(int m) {
FL_LOCK_D();
wfdisp->Mag(m);
if (m == MAG_1) x1->label("x1");
if (m == MAG_2) x1->label("x2");
if (m == MAG_4) x1->label("x4");
x1->redraw_label();
FL_UNLOCK_D();
}
int waterfall::Carrier()
{
return wfdisp->carrier();
}
void waterfall::Carrier(int f)
{
active_modem->set_freq(f);
}
void waterfall::rfcarrier(long long cf) {
extern void viewer_redraw();
wfdisp->rfcarrier(cf);
viewer_redraw();
}
long long waterfall::rfcarrier() {
return wfdisp->rfcarrier();
}
void waterfall::setRefLevel() {
FL_LOCK_D();
wfRefLevel->value(progdefaults.wfRefLevel);
wfdisp->Reflevel(progdefaults.wfRefLevel);
FL_UNLOCK_D();
}
void waterfall::setAmpSpan() {
FL_LOCK_D();
wfAmpSpan->value(progdefaults.wfAmpSpan);
wfdisp->Ampspan(progdefaults.wfAmpSpan);
FL_UNLOCK_D();
}
void waterfall::USB(bool b) {
if (wfdisp->USB() == b)
return;
wfdisp->USB(b);
active_modem->set_reverse(reverse);
extern void viewer_redraw();
viewer_redraw();
}
bool waterfall::USB() {
return wfdisp->USB();
}
waterfall::waterfall(int x0, int y0, int w0, int h0, char *lbl) :
Fl_Group(x0,y0,w0,h0,lbl) {
int xpos;
float ratio = w0 < 600 ? w0 / 600.0 : 1.0;
buttonrow = h() + y() - BTN_HEIGHT - BEZEL;
bezel = new Fl_Box(
FL_DOWN_BOX,
x(),
y(),
w(),
h() - BTN_HEIGHT - 2 * BEZEL, 0);
wfdisp = new WFdisp(x() + BEZEL,
y() + BEZEL,
w() - 2 * BEZEL,
h() - BTN_HEIGHT - 4 * BEZEL);
xpos = x() + wSpace;
// bw_rsid = new Fl_Button(xpos, buttonrow, (int)(bwColor*ratio), BTN_HEIGHT, "Id?");
// bw_rsid->callback(bw_rsid_cb, 0);
// bw_rsid->tooltip(_("Auto detect RSID"));
// xpos = xpos + (int)(bwColor*ratio) + wSpace;
mode = new Fl_Button(xpos, buttonrow, (int)(bwFFT*ratio), BTN_HEIGHT,"Wtr");
mode->callback(mode_cb, 0);
mode->tooltip(_("Waterfall/FFT - Shift click for signal scope"));
xpos = xpos + (int)(bwFFT*ratio) + wSpace;
wfRefLevel = new Fl_Counter(xpos, buttonrow, (int)(cwRef*ratio), BTN_HEIGHT );
wfRefLevel->callback(reflevel_cb, 0);
wfRefLevel->step(1.0);
wfRefLevel->precision(0);
wfRefLevel->range(-40.0, 0.0);
wfRefLevel->value(-20.0);
wfdisp->Reflevel(-20.0);
wfRefLevel->tooltip(_("Upper signal level (dB)"));
wfRefLevel->type(FL_SIMPLE_COUNTER);
xpos = xpos + (int)(cwRef*ratio) + wSpace;
wfAmpSpan = new Fl_Counter(xpos, buttonrow, (int)(cwRef*ratio), BTN_HEIGHT );
wfAmpSpan->callback(ampspan_cb, 0);
wfAmpSpan->step(1.0);
wfAmpSpan->precision(0);
wfAmpSpan->range(6.0, 90.0);
wfAmpSpan->value(70.0);
wfdisp->Ampspan(70.0);
wfAmpSpan->tooltip(_("Signal range (dB)"));
wfAmpSpan->type(FL_SIMPLE_COUNTER);
xpos = xpos + (int)(cwRef*ratio) + wSpace;
x1 = new Fl_Button(xpos, buttonrow, (int)(bwX1*ratio), BTN_HEIGHT, "x1");
x1->callback(x1_cb, 0);
x1->tooltip(_("Change waterfall scale"));
xpos = xpos + (int)(bwX1*ratio) + wSpace;
left = new Fl_Repeat_Button(xpos, buttonrow, (int)(bwMov*ratio), BTN_HEIGHT, "@<");
left->callback(slew_left, 0);
left->tooltip(_("Slew display lower in freq"));
xpos += (int)(bwMov*ratio);
center = new Fl_Button(xpos, buttonrow, (int)(bwMov*ratio), BTN_HEIGHT, "@||");
center->callback(center_cb, 0);
center->tooltip(_("Center display on signal"));
xpos += (int)(bwMov*ratio);
right = new Fl_Repeat_Button(xpos, buttonrow, (int)(bwMov*ratio), BTN_HEIGHT, "@>");
right->callback(slew_right, 0);
right->tooltip(_("Slew display higher in freq"));
xpos = xpos + (int)(bwMov*ratio) + wSpace;
wfrate = new Fl_Button(xpos, buttonrow, (int)(bwRate*ratio), BTN_HEIGHT, "Norm");
wfrate->callback(rate_cb, 0);
wfrate->tooltip(_("Waterfall drop speed"));
xpos = xpos + (int)(bwRate*ratio) + 2*wSpace;
wfcarrier = new Fl_Counter(xpos, buttonrow, (int)(cwCnt*ratio), BTN_HEIGHT );
wfcarrier->callback(carrier_cb, 0);
wfcarrier->step(1.0);
wfcarrier->lstep(10.0);
wfcarrier->precision(0);
wfcarrier->range(16.0, IMAGE_WIDTH - 16.0);
wfcarrier->value(wfdisp->carrier());
wfcarrier->tooltip(_("Adjust cursor frequency"));
xpos = xpos + (int)(cwCnt*ratio) + 2*wSpace;
qsy = new Fl_Button(xpos, buttonrow, (int)(bwQsy*ratio), BTN_HEIGHT, "QSY");
qsy->callback(qsy_cb, 0);
qsy->tooltip(_("Cntr in Xcvr PB\nRight click to undo"));
qsy->deactivate();
xpos = xpos + (int)(bwQsy*ratio) + wSpace;
btnMem = new Fl_Button(xpos, buttonrow, (int)(bwMem*ratio), BTN_HEIGHT, "Store");
btnMem->callback(btnMem_cb, 0);
btnMem->tooltip(_("Store mode and frequency\nRight click for list"));
mbtnMem = new Fl_Menu_Button(btnMem->x(), btnMem->y(), btnMem->w(), btnMem->h(), 0);
mbtnMem->callback(btnMem->callback(), mbtnMem);
mbtnMem->type(Fl_Menu_Button::POPUP3);
xpos = xpos + (int)(bwMem*ratio) + 2*wSpace;
xmtlock = new Fl_Light_Button(xpos, buttonrow, (int)(bwXmtLock*ratio), BTN_HEIGHT, "Lk");
xmtlock->callback(xmtlock_cb, 0);
xmtlock->value(0);
xmtlock->selection_color(FL_RED);
xmtlock->tooltip(_("Xmt freq locked"));
xpos = xpos + (int)(bwXmtLock*ratio) + wSpace;
btnRev = new Fl_Light_Button(xpos, buttonrow, (int)(bwRev*ratio), BTN_HEIGHT, "Rv");
btnRev->callback(btnRev_cb, 0);
btnRev->value(0);
btnRev->selection_color(FL_GREEN);
btnRev->tooltip(_("Reverse"));
reverse = false;
xpos = w() - (int)(bwXmtRcv*ratio) - wSpace;
xmtrcv = new Fl_Light_Button(xpos, buttonrow, (int)(bwXmtRcv*ratio) - BEZEL, BTN_HEIGHT, "T/R");
xmtrcv->callback(xmtrcv_cb, 0);
xmtrcv->selection_color(FL_RED);
xmtrcv->value(0);
xmtrcv->tooltip(_("Transmit/Receive"));
}
int waterfall::handle(int event)
{
if (event != FL_MOUSEWHEEL || Fl::event_inside(wfdisp))
return Fl_Group::handle(event);
int d;
if ( !((d = Fl::event_dy()) || (d = Fl::event_dx())) )
return 1;
Fl_Valuator* v[] = { sldrSquelch, wfcarrier, wfRefLevel, wfAmpSpan, valRcvMixer, valXmtMixer };
for (size_t i = 0; i < sizeof(v)/sizeof(v[0]); i++) {
if (Fl::event_inside(v[i])) {
if ((v[i] == sldrSquelch && !progdefaults.docked_scope) ||
v[i] == valRcvMixer || v[i] == valXmtMixer)
d = -d;
v[i]->value(v[i]->clamp(v[i]->increment(v[i]->value(), -d)));
v[i]->do_callback();
return 1;
}
}
// this does not belong here, but we don't have access to this widget's
// handle method (or its parent's)
if (Fl::event_inside(MODEstatus)) {
init_modem(d > 0 ? MODE_NEXT : MODE_PREV);
return 1;
}
// as above; handle wheel events for the macro bar
extern void altmacro_cb(Fl_Widget *w, void *v);
for (int i = 0; i < NUMMACKEYS; i++) {
if (Fl::event_inside(btnMacro[i])) {
altmacro_cb(btnAltMacros, reinterpret_cast<void *>(d));
return 1;
}
}
if (Fl::event_inside(btnAltMacros)) {
altmacro_cb(btnAltMacros, reinterpret_cast<void *>(d));
return 1;
}
return 0;
}
static Fl_Cursor cursor = FL_CURSOR_DEFAULT;
static void hide_cursor(void *w)
{
if (cursor != FL_CURSOR_NONE)
reinterpret_cast<Fl_Widget *>(w)->window()->cursor(cursor = FL_CURSOR_NONE);
}
map<string, qrg_mode_t> qsy_map;
static qrg_mode_t last;
void note_qrg(bool check, char prefix, char suffix)
{
qrg_mode_t m;
m.rfcarrier = wf->rfcarrier();
m.carrier = active_modem->get_freq();
m.mode = active_modem->get_mode();
if (check && last == m)
return;
last = m;
char buf[64];
time_t t = time(NULL);
struct tm tm;
gmtime_r(&t, &tm);
size_t r1;
if ((r1 = strftime(buf, sizeof(buf), "<<%FT%H:%MZ ", &tm)) == 0)
return;
size_t r2;
if (m.rfcarrier)
r2 = snprintf(buf+r1, sizeof(buf)-r1, "%s @ %lld%c%04d>>",
mode_info[m.mode].name, m.rfcarrier, (wf->USB() ? '+' : '-'), m.carrier);
else
r2 = snprintf(buf+r1, sizeof(buf)-r1, "%s @ %04d>>", mode_info[m.mode].name, m.carrier);
if (r2 >= sizeof(buf)-r1)
return;
qsy_map[buf] = m;
ReceiveText->add(prefix);
ReceiveText->add(buf, FTextBase::QSY);
ReceiveText->add(suffix);
}
static void insert_text(bool check = false)
{
if (check) {
qrg_mode_t m;
m.rfcarrier = wf->rfcarrier();
m.carrier = active_modem->get_freq();
m.mode = active_modem->get_mode();
if (last.mode == m.mode && last.rfcarrier == m.rfcarrier &&
abs(last.carrier - m.carrier) <= 16)
return;
last = m;
}
string::size_type i;
if ((i = progdefaults.WaterfallClickText.find("<FREQ>")) != string::npos) {
string s = progdefaults.WaterfallClickText;
s[i] = '\0';
ReceiveText->add(s.c_str());
note_qrg(false);
ReceiveText->add(s.c_str() + i + strlen("<FREQ>"));
}
else
ReceiveText->add(progdefaults.WaterfallClickText.c_str(), FTextView::SKIP);
}
static void find_signal_text(void)
{
int freq = active_modem->get_freq();
trx_mode mode = active_modem->get_mode();
map<string, qrg_mode_t>::const_iterator i;
for (i = qsy_map.begin(); i != qsy_map.end(); ++i)
if (i->second.mode == mode && abs(i->second.carrier - freq) <= 20)
break;
if (i != qsy_map.end()) {
// Search backward from the current text cursor position, then
// try the other direction
int pos = ReceiveText->insert_position();
if (ReceiveText->buffer()->search_backward(pos, i->first.c_str(), &pos, 1) ||
ReceiveText->buffer()->search_forward(pos, i->first.c_str(), &pos, 1)) {
ReceiveText->insert_position(pos);
ReceiveText->show_insert_position();
}
}
}
int WFdisp::handle(int event)
{
static int pxpos, push;
if (!(event == FL_LEAVE || Fl::event_inside(this))) {
if (event == FL_RELEASE)
push = 0;
return 0;
}
if (trx_state != STATE_RX)
return 1;
int xpos = Fl::event_x() - x();
int ypos = Fl::event_y() - y();
int eb;
switch (event) {
case FL_MOVE:
if (progdefaults.WaterfallQSY && ypos < WFTEXT + WFSCALE) {
Fl::remove_timeout(hide_cursor, this);
if (cursor != FL_CURSOR_WE)
window()->cursor(cursor = FL_CURSOR_WE);
if (wantcursor) {
wantcursor = false;
makeMarker();
}
break;
}
if (cursor != FL_CURSOR_DEFAULT)
window()->cursor(cursor = FL_CURSOR_DEFAULT);
if (!Fl::has_timeout(hide_cursor, this))
Fl::add_timeout(1, hide_cursor, this);
wantcursor = true;
cursorpos = xpos;
makeMarker();
redrawCursor();
break;
case FL_DRAG: case FL_PUSH:
switch (eb = Fl::event_button()) {
case FL_RIGHT_MOUSE:
wantcursor = false;
if (event == FL_PUSH) {
tmp_carrier = true;
oldcarrier = carrier();
if (progdefaults.WaterfallHistoryDefault)
bHistory = true;
}
goto lrclick;
case FL_LEFT_MOUSE:
if (event == FL_PUSH) {
push = ypos;
pxpos = xpos;
if (Fl::event_clicks())
return 1;
}
if (progdefaults.WaterfallQSY && push < WFTEXT + WFSCALE) {
long long newrfc = (pxpos - xpos) * step;
if (!USB())
newrfc = -newrfc;
newrfc += rfcarrier();
qsy(newrfc, active_modem->get_freq());
pxpos = xpos;
return 1;
}
lrclick:
if (Fl::event_state() & FL_CTRL) {
if (event == FL_DRAG)
break;
if (!progdefaults.WaterfallHistoryDefault)
bHistory = true;
if (eb == FL_LEFT_MOUSE) {
restoreFocus();
break;
}
}
if (progdefaults.WaterfallHistoryDefault)
bHistory = true;
if ((newcarrier = cursorFreq(xpos)) > wf->wfcarrier->maximum())
break;
active_modem->set_freq(newcarrier);
if (!(Fl::event_state() & FL_SHIFT))
active_modem->set_sigsearch(SIGSEARCH);
redrawCursor();
restoreFocus();
break;
case FL_MIDDLE_MOUSE:
if (event == FL_DRAG)
break;
if (Fl::event_state() & FL_CTRL)
viewer_paste_freq(cursorFreq(xpos));
else {
btn_afconoff->value(!btn_afconoff->value());
btn_afconoff->do_callback();
}
}
break;
case FL_RELEASE:
switch (eb = Fl::event_button()) {
case FL_RIGHT_MOUSE:
tmp_carrier = false;
active_modem->set_freq(oldcarrier);
redrawCursor();
restoreFocus();
// fall through
case FL_LEFT_MOUSE:
push = 0;
oldcarrier = newcarrier;
if (eb != FL_LEFT_MOUSE || !ReceiveText->visible())
break;
if (!(Fl::event_state() & (FL_CTRL | FL_META | FL_ALT | FL_SHIFT))) {
if (Fl::event_clicks() == 1)
note_qrg(true, '\n', '\n');
else
if (progdefaults.WaterfallClickInsert)
insert_text(true);
}
else if (Fl::event_state() & (FL_META | FL_ALT))
find_signal_text();
break;
}
break;
case FL_MOUSEWHEEL:
{
int d;
if ( !((d = Fl::event_dy()) || (d = Fl::event_dx())) )
break;
int state = Fl::event_state();
if (state & FL_CTRL)
wf->handle_mouse_wheel(waterfall::WF_AFC_BW, d);
else if (state & (FL_META | FL_ALT))
wf->handle_mouse_wheel(waterfall::WF_SIGNAL_SEARCH, d);
else if (state & FL_SHIFT)
wf->handle_mouse_wheel(waterfall::WF_SQUELCH, d);
else {
if (progdefaults.WaterfallQSY && Fl::event_inside(x(), y(), w(), WFTEXT+WFSCALE+WFMARKER))
qsy(wf->rfcarrier() - 500*d);
else
wf->handle_mouse_wheel(progdefaults.WaterfallWheelAction, d);
}
return handle(FL_MOVE);
}
case FL_SHORTCUT:
if (Fl::event_inside(this))
take_focus();
break;
case FL_KEYBOARD:
{
int d = (Fl::event_state() & FL_CTRL) ? 10 : 1;
switch (Fl::event_key()) {
case FL_Left:
if (xpos > 0) {
active_modem->set_freq(oldcarrier = newcarrier = carrier() - d);
redrawCursor();
}
break;
case FL_Right:
if (xpos < w()) {
active_modem->set_freq(oldcarrier = newcarrier = carrier() + d);
redrawCursor();
}
break;
case FL_Tab:
restoreFocus();
break;
}
break;
}
case FL_KEYUP:
{
if (Fl::event_inside(this)) {
int k = Fl::event_key();
if (k == FL_Shift_L || k == FL_Shift_R || k == FL_Control_L ||
k == FL_Control_R || k == FL_Meta_L || k == FL_Meta_R ||
k == FL_Alt_L || k == FL_Alt_R)
restoreFocus();
}
break;
}
case FL_LEAVE:
Fl::remove_timeout(hide_cursor, this);
if (cursor != FL_CURSOR_DEFAULT)
window()->cursor(cursor = FL_CURSOR_DEFAULT);
wantcursor = false;
makeMarker();
// restoreFocus();
break;
}
return 1;
}
void waterfall::handle_mouse_wheel(int what, int d)
{
if (d == 0)
return;
Fl_Valuator *val = 0;
const char* msg_fmt = 0, *msg_label = 0;
switch (what) {
case WF_NOP:
return;
case WF_AFC_BW:
{
trx_mode m = active_modem->get_mode();
if (m >= MODE_PSK_FIRST && m <= MODE_PSK_LAST)
val = mailserver ? cntServerOffset : cntSearchRange;
else if (m >= MODE_HELL_FIRST && m <= MODE_HELL_LAST)
val = sldrHellBW;
else if (m == MODE_CW)
val = sldrCWbandwidth;
else
return;
msg_fmt = "%s = %2.0f Hz";
msg_label = val->label();
break;
}
case WF_SIGNAL_SEARCH:
if (d > 0)
active_modem->searchDown();
else
active_modem->searchUp();
return;
case WF_SQUELCH:
val = sldrSquelch;
if (!progdefaults.docked_scope)
d = -d;
msg_fmt = "%s = %2.0f %%";
msg_label = "Squelch";
break;
case WF_CARRIER:
val = wfcarrier;
break;
case WF_MODEM:
init_modem(d > 0 ? MODE_NEXT : MODE_PREV);
return;
case WF_SCROLL:
(d > 0 ? right : left)->do_callback();
return;
}
val->value(val->clamp(val->increment(val->value(), -d)));
bool changed_save = progdefaults.changed;
val->do_callback();
progdefaults.changed = changed_save;
if (val == cntServerOffset || val == cntSearchRange)
active_modem->set_sigsearch(SIGSEARCH);
else if (val == sldrSquelch) // sldrSquelch gives focus to TransmitText
take_focus();
if (msg_fmt) {
char msg[60];
snprintf(msg, sizeof(msg), msg_fmt, msg_label, val->value());
put_status(msg, 2.0);
}
}
const char waterfall::wf_wheel_action[] = "None|AFC range or BW|"
"Signal search|Squelch level|"
"Modem carrier|Modem|Scroll";