1 /* -*- c++ -*- */
2 /*
3  * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt
4  *           https://gqrx.dk/
5  *
6  * Copyright 2011-2014 Alexandru Csete OZ9AEC.
7  * Copyright (C) 2013 by Elias Oenal <EliasOenal@gmail.com>
8  *
9  * Gqrx is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 3, or (at your option)
12  * any later version.
13  *
14  * Gqrx is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Gqrx; see the file COPYING.  If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street,
22  * Boston, MA 02110-1301, USA.
23  */
24 #include <string>
25 #include <vector>
26 #include <volk/volk.h>
27 
28 #include <QSettings>
29 #include <QByteArray>
30 #include <QDateTime>
31 #include <QDesktopServices>
32 #include <QDialogButtonBox>
33 #include <QFile>
34 #include <QGroupBox>
35 #include <QKeySequence>
36 #include <QLineEdit>
37 #include <QMessageBox>
38 #include <QPushButton>
39 #include <QResource>
40 #include <QShortcut>
41 #include <QString>
42 #include <QTextBrowser>
43 #include <QTextCursor>
44 #include <QTextStream>
45 #include <QtGlobal>
46 #include <QTimer>
47 #include <QVBoxLayout>
48 #include <QSvgWidget>
49 #include "qtgui/ioconfig.h"
50 #include "mainwindow.h"
51 #include "qtgui/dxc_options.h"
52 #include "qtgui/dxc_spots.h"
53 
54 /* Qt Designer files */
55 #include "ui_mainwindow.h"
56 
57 /* DSP */
58 #include "receiver.h"
59 #include "remote_control_settings.h"
60 
61 #include "qtgui/bookmarkstaglist.h"
62 #include "qtgui/bandplan.h"
63 
MainWindow(const QString & cfgfile,bool edit_conf,QWidget * parent)64 MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) :
65     QMainWindow(parent),
66     configOk(true),
67     ui(new Ui::MainWindow),
68     d_lnb_lo(0),
69     d_hw_freq(0),
70     d_fftAvg(0.25),
71     d_have_audio(true),
72     dec_afsk1200(nullptr)
73 {
74     ui->setupUi(this);
75     BandPlan::create();
76     Bookmarks::create();
77     DXCSpots::create();
78 
79     /* Initialise default configuration directory */
80     QByteArray xdg_dir = qgetenv("XDG_CONFIG_HOME");
81     if (xdg_dir.isEmpty())
82     {
83         // Qt takes care of conversion to native separators
84         m_cfg_dir = QString("%1/.config/gqrx").arg(QDir::homePath());
85     }
86     else
87     {
88         m_cfg_dir = QString("%1/gqrx").arg(xdg_dir.data());
89     }
90 
91     setWindowTitle(QString("Gqrx %1").arg(VERSION));
92 
93     /* frequency control widget */
94     ui->freqCtrl->setup(0, 0, 9999e6, 1, FCTL_UNIT_NONE);
95     ui->freqCtrl->setFrequency(144500000);
96 
97     d_filter_shape = receiver::FILTER_SHAPE_NORMAL;
98 
99     /* create receiver object */
100     rx = new receiver("", "", 1);
101     rx->set_rf_freq(144500000.0f);
102 
103     // remote controller
104     remote = new RemoteControl();
105 
106     /* meter timer */
107     meter_timer = new QTimer(this);
108     connect(meter_timer, SIGNAL(timeout()), this, SLOT(meterTimeout()));
109 
110     /* FFT timer & data */
111     iq_fft_timer = new QTimer(this);
112     connect(iq_fft_timer, SIGNAL(timeout()), this, SLOT(iqFftTimeout()));
113 
114     audio_fft_timer = new QTimer(this);
115     connect(audio_fft_timer, SIGNAL(timeout()), this, SLOT(audioFftTimeout()));
116 
117     d_fftData = new std::complex<float>[MAX_FFT_SIZE];
118     d_realFftData = new float[MAX_FFT_SIZE];
119     d_iirFftData = new float[MAX_FFT_SIZE];
120     for (int i = 0; i < MAX_FFT_SIZE; i++)
121         d_iirFftData[i] = -140.0;  // dBFS
122 
123     /* timer for data decoders */
124     dec_timer = new QTimer(this);
125     connect(dec_timer, SIGNAL(timeout()), this, SLOT(decoderTimeout()));
126 
127     // create I/Q tool widget
128     iq_tool = new CIqTool(this);
129 
130     // create DXC Objects
131     dxc_options = new DXCOptions(this);
132 
133     /* create dock widgets */
134     uiDockRxOpt = new DockRxOpt();
135     uiDockRDS = new DockRDS();
136     uiDockAudio = new DockAudio();
137     uiDockInputCtl = new DockInputCtl();
138     uiDockFft = new DockFft();
139     BandPlan::Get().setConfigDir(m_cfg_dir);
140     Bookmarks::Get().setConfigDir(m_cfg_dir);
141     BandPlan::Get().load();
142     uiDockBookmarks = new DockBookmarks(this);
143 
144     // setup some toggle view shortcuts
145     uiDockInputCtl->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_J));
146     uiDockRxOpt->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R));
147     uiDockFft->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F));
148     uiDockAudio->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_A));
149     uiDockBookmarks->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_B));
150     ui->mainToolBar->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T));
151 
152     /* frequency setting shortcut */
153     auto *freq_shortcut = new QShortcut(QKeySequence(Qt::Key_F), this);
154     QObject::connect(freq_shortcut, &QShortcut::activated, this, &MainWindow::frequencyFocusShortcut);
155 
156     setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
157     setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
158     setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
159     setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
160 
161     /* Add dock widgets to main window. This should be done even for
162        dock widgets that are going to be hidden, otherwise they will
163        end up floating in their own top-level window and can not be
164        docked to the mainwindow.
165     */
166     addDockWidget(Qt::RightDockWidgetArea, uiDockInputCtl);
167     addDockWidget(Qt::RightDockWidgetArea, uiDockRxOpt);
168     addDockWidget(Qt::RightDockWidgetArea, uiDockFft);
169     tabifyDockWidget(uiDockInputCtl, uiDockRxOpt);
170     tabifyDockWidget(uiDockRxOpt, uiDockFft);
171     uiDockRxOpt->raise();
172 
173     addDockWidget(Qt::RightDockWidgetArea, uiDockAudio);
174     addDockWidget(Qt::RightDockWidgetArea, uiDockRDS);
175     tabifyDockWidget(uiDockAudio, uiDockRDS);
176     uiDockAudio->raise();
177 
178     addDockWidget(Qt::BottomDockWidgetArea, uiDockBookmarks);
179 
180     /* hide docks that we don't want to show initially */
181     uiDockBookmarks->hide();
182     uiDockRDS->hide();
183 
184     /* Add dock widget actions to View menu. By doing it this way all signal/slot
185        connections will be established automagially.
186     */
187     ui->menu_View->addAction(uiDockInputCtl->toggleViewAction());
188     ui->menu_View->addAction(uiDockRxOpt->toggleViewAction());
189     ui->menu_View->addAction(uiDockRDS->toggleViewAction());
190     ui->menu_View->addAction(uiDockAudio->toggleViewAction());
191     ui->menu_View->addAction(uiDockFft->toggleViewAction());
192     ui->menu_View->addAction(uiDockBookmarks->toggleViewAction());
193     ui->menu_View->addSeparator();
194     ui->menu_View->addAction(ui->mainToolBar->toggleViewAction());
195     ui->menu_View->addSeparator();
196     ui->menu_View->addAction(ui->actionFullScreen);
197 
198     /* connect signals and slots */
199     connect(ui->freqCtrl, SIGNAL(newFrequency(qint64)), this, SLOT(setNewFrequency(qint64)));
200     connect(ui->freqCtrl, SIGNAL(newFrequency(qint64)), remote, SLOT(setNewFrequency(qint64)));
201     connect(ui->freqCtrl, SIGNAL(newFrequency(qint64)), uiDockAudio, SLOT(setRxFrequency(qint64)));
202     connect(ui->freqCtrl, SIGNAL(newFrequency(qint64)), uiDockRxOpt, SLOT(setRxFreq(qint64)));
203     connect(uiDockInputCtl, SIGNAL(lnbLoChanged(double)), this, SLOT(setLnbLo(double)));
204     connect(uiDockInputCtl, SIGNAL(lnbLoChanged(double)), remote, SLOT(setLnbLo(double)));
205     connect(uiDockInputCtl, SIGNAL(gainChanged(QString, double)), this, SLOT(setGain(QString,double)));
206     connect(uiDockInputCtl, SIGNAL(gainChanged(QString, double)), remote, SLOT(setGain(QString,double)));
207     connect(uiDockInputCtl, SIGNAL(autoGainChanged(bool)), this, SLOT(setAutoGain(bool)));
208     connect(uiDockInputCtl, SIGNAL(freqCorrChanged(double)), this, SLOT(setFreqCorr(double)));
209     connect(uiDockInputCtl, SIGNAL(iqSwapChanged(bool)), this, SLOT(setIqSwap(bool)));
210     connect(uiDockInputCtl, SIGNAL(dcCancelChanged(bool)), this, SLOT(setDcCancel(bool)));
211     connect(uiDockInputCtl, SIGNAL(iqBalanceChanged(bool)), this, SLOT(setIqBalance(bool)));
212     connect(uiDockInputCtl, SIGNAL(ignoreLimitsChanged(bool)), this, SLOT(setIgnoreLimits(bool)));
213     connect(uiDockInputCtl, SIGNAL(antennaSelected(QString)), this, SLOT(setAntenna(QString)));
214     connect(uiDockInputCtl, SIGNAL(freqCtrlResetChanged(bool)), this, SLOT(setFreqCtrlReset(bool)));
215     connect(uiDockInputCtl, SIGNAL(invertScrollingChanged(bool)), this, SLOT(setInvertScrolling(bool)));
216     connect(uiDockRxOpt, SIGNAL(rxFreqChanged(qint64)), ui->freqCtrl, SLOT(setFrequency(qint64)));
217     connect(uiDockRxOpt, SIGNAL(filterOffsetChanged(qint64)), this, SLOT(setFilterOffset(qint64)));
218     connect(uiDockRxOpt, SIGNAL(filterOffsetChanged(qint64)), remote, SLOT(setFilterOffset(qint64)));
219     connect(uiDockRxOpt, SIGNAL(demodSelected(int)), this, SLOT(selectDemod(int)));
220     connect(uiDockRxOpt, SIGNAL(demodSelected(int)), remote, SLOT(setMode(int)));
221     connect(uiDockRxOpt, SIGNAL(fmMaxdevSelected(float)), this, SLOT(setFmMaxdev(float)));
222     connect(uiDockRxOpt, SIGNAL(fmEmphSelected(double)), this, SLOT(setFmEmph(double)));
223     connect(uiDockRxOpt, SIGNAL(amDcrToggled(bool)), this, SLOT(setAmDcr(bool)));
224     connect(uiDockRxOpt, SIGNAL(cwOffsetChanged(int)), this, SLOT(setCwOffset(int)));
225     connect(uiDockRxOpt, SIGNAL(amSyncDcrToggled(bool)), this, SLOT(setAmSyncDcr(bool)));
226     connect(uiDockRxOpt, SIGNAL(amSyncPllBwSelected(float)), this, SLOT(setAmSyncPllBw(float)));
227     connect(uiDockRxOpt, SIGNAL(agcToggled(bool)), this, SLOT(setAgcOn(bool)));
228     connect(uiDockRxOpt, SIGNAL(agcHangToggled(bool)), this, SLOT(setAgcHang(bool)));
229     connect(uiDockRxOpt, SIGNAL(agcThresholdChanged(int)), this, SLOT(setAgcThreshold(int)));
230     connect(uiDockRxOpt, SIGNAL(agcSlopeChanged(int)), this, SLOT(setAgcSlope(int)));
231     connect(uiDockRxOpt, SIGNAL(agcGainChanged(int)), this, SLOT(setAgcGain(int)));
232     connect(uiDockRxOpt, SIGNAL(agcDecayChanged(int)), this, SLOT(setAgcDecay(int)));
233     connect(uiDockRxOpt, SIGNAL(noiseBlankerChanged(int,bool,float)), this, SLOT(setNoiseBlanker(int,bool,float)));
234     connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), this, SLOT(setSqlLevel(double)));
235     connect(uiDockRxOpt, SIGNAL(sqlAutoClicked()), this, SLOT(setSqlLevelAuto()));
236     connect(uiDockAudio, SIGNAL(audioGainChanged(float)), this, SLOT(setAudioGain(float)));
237     connect(uiDockAudio, SIGNAL(audioStreamingStarted(QString,int,bool)), this, SLOT(startAudioStream(QString,int,bool)));
238     connect(uiDockAudio, SIGNAL(audioStreamingStopped()), this, SLOT(stopAudioStreaming()));
239     connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), this, SLOT(startAudioRec(QString)));
240     connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), remote, SLOT(startAudioRecorder(QString)));
241     connect(uiDockAudio, SIGNAL(audioRecStopped()), this, SLOT(stopAudioRec()));
242     connect(uiDockAudio, SIGNAL(audioRecStopped()), remote, SLOT(stopAudioRecorder()));
243     connect(uiDockAudio, SIGNAL(audioPlayStarted(QString)), this, SLOT(startAudioPlayback(QString)));
244     connect(uiDockAudio, SIGNAL(audioPlayStopped()), this, SLOT(stopAudioPlayback()));
245     connect(uiDockAudio, SIGNAL(fftRateChanged(int)), this, SLOT(setAudioFftRate(int)));
246     connect(uiDockFft, SIGNAL(fftSizeChanged(int)), this, SLOT(setIqFftSize(int)));
247     connect(uiDockFft, SIGNAL(fftRateChanged(int)), this, SLOT(setIqFftRate(int)));
248     connect(uiDockFft, SIGNAL(fftWindowChanged(int)), this, SLOT(setIqFftWindow(int)));
249     connect(uiDockFft, SIGNAL(wfSpanChanged(quint64)), this, SLOT(setWfTimeSpan(quint64)));
250     connect(uiDockFft, SIGNAL(fftSplitChanged(int)), this, SLOT(setIqFftSplit(int)));
251     connect(uiDockFft, SIGNAL(fftAvgChanged(float)), this, SLOT(setIqFftAvg(float)));
252     connect(uiDockFft, SIGNAL(fftZoomChanged(float)), ui->plotter, SLOT(zoomOnXAxis(float)));
253     connect(uiDockFft, SIGNAL(resetFftZoom()), ui->plotter, SLOT(resetHorizontalZoom()));
254     connect(uiDockFft, SIGNAL(gotoFftCenter()), ui->plotter, SLOT(moveToCenterFreq()));
255     connect(uiDockFft, SIGNAL(gotoDemodFreq()), ui->plotter, SLOT(moveToDemodFreq()));
256     connect(uiDockFft, SIGNAL(bandPlanChanged(bool)), ui->plotter, SLOT(toggleBandPlan(bool)));
257     connect(uiDockFft, SIGNAL(wfColormapChanged(const QString)), ui->plotter, SLOT(setWfColormap(const QString)));
258     connect(uiDockFft, SIGNAL(wfColormapChanged(const QString)), uiDockAudio, SLOT(setWfColormap(const QString)));
259 
260     connect(uiDockFft, SIGNAL(pandapterRangeChanged(float,float)),
261             ui->plotter, SLOT(setPandapterRange(float,float)));
262     connect(uiDockFft, SIGNAL(waterfallRangeChanged(float,float)),
263             ui->plotter, SLOT(setWaterfallRange(float,float)));
264     connect(ui->plotter, SIGNAL(pandapterRangeChanged(float,float)),
265             uiDockFft, SLOT(setPandapterRange(float,float)));
266     connect(ui->plotter, SIGNAL(newZoomLevel(float)),
267             uiDockFft, SLOT(setZoomLevel(float)));
268     connect(ui->plotter, SIGNAL(newSize()), this, SLOT(setWfSize()));
269 
270     connect(uiDockFft, SIGNAL(fftColorChanged(QColor)), this, SLOT(setFftColor(QColor)));
271     connect(uiDockFft, SIGNAL(fftFillToggled(bool)), this, SLOT(setFftFill(bool)));
272     connect(uiDockFft, SIGNAL(fftPeakHoldToggled(bool)), this, SLOT(setFftPeakHold(bool)));
273     connect(uiDockFft, SIGNAL(peakDetectionToggled(bool)), this, SLOT(setPeakDetection(bool)));
274     connect(uiDockRDS, SIGNAL(rdsDecoderToggled(bool)), this, SLOT(setRdsDecoder(bool)));
275 
276     // Bookmarks
277     connect(uiDockBookmarks, SIGNAL(newBookmarkActivated(qint64, QString, int)), this, SLOT(onBookmarkActivated(qint64, QString, int)));
278     connect(uiDockBookmarks->actionAddBookmark, SIGNAL(triggered()), this, SLOT(on_actionAddBookmark_triggered()));
279 
280     //DXC Spots
281     connect(&DXCSpots::Get(), SIGNAL(dxcSpotsUpdated()), this, SLOT(updateClusterSpots()));
282 
283     // I/Q playback
284     connect(iq_tool, SIGNAL(startRecording(QString)), this, SLOT(startIqRecording(QString)));
285     connect(iq_tool, SIGNAL(stopRecording()), this, SLOT(stopIqRecording()));
286     connect(iq_tool, SIGNAL(startPlayback(QString,float)), this, SLOT(startIqPlayback(QString,float)));
287     connect(iq_tool, SIGNAL(stopPlayback()), this, SLOT(stopIqPlayback()));
288     connect(iq_tool, SIGNAL(seek(qint64)), this,SLOT(seekIqFile(qint64)));
289 
290     // remote control
291     connect(remote, SIGNAL(newRDSmode(bool)), uiDockRDS, SLOT(setRDSmode(bool)));
292     connect(uiDockRDS, SIGNAL(rdsDecoderToggled(bool)), remote, SLOT(setRDSstatus(bool)));
293     connect(remote, SIGNAL(newFilterOffset(qint64)), this, SLOT(setFilterOffset(qint64)));
294     connect(remote, SIGNAL(newFilterOffset(qint64)), uiDockRxOpt, SLOT(setFilterOffset(qint64)));
295     connect(remote, SIGNAL(newFrequency(qint64)), ui->freqCtrl, SLOT(setFrequency(qint64)));
296     connect(remote, SIGNAL(newLnbLo(double)), uiDockInputCtl, SLOT(setLnbLo(double)));
297     connect(remote, SIGNAL(newLnbLo(double)), this, SLOT(setLnbLo(double)));
298     connect(remote, SIGNAL(newMode(int)), this, SLOT(selectDemod(int)));
299     connect(remote, SIGNAL(newMode(int)), uiDockRxOpt, SLOT(setCurrentDemod(int)));
300     connect(remote, SIGNAL(newSquelchLevel(double)), this, SLOT(setSqlLevel(double)));
301     connect(remote, SIGNAL(newSquelchLevel(double)), uiDockRxOpt, SLOT(setSquelchLevel(double)));
302     connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), remote, SLOT(setSquelchLevel(double)));
303     connect(remote, SIGNAL(startAudioRecorderEvent()), uiDockAudio, SLOT(startAudioRecorder()));
304     connect(remote, SIGNAL(stopAudioRecorderEvent()), uiDockAudio, SLOT(stopAudioRecorder()));
305     connect(ui->plotter, SIGNAL(newFilterFreq(int, int)), remote, SLOT(setPassband(int, int)));
306     connect(remote, SIGNAL(newPassband(int)), this, SLOT(setPassband(int)));
307     connect(remote, SIGNAL(gainChanged(QString, double)), uiDockInputCtl, SLOT(setGain(QString,double)));
308     connect(remote, SIGNAL(dspChanged(bool)), this, SLOT(on_actionDSP_triggered(bool)));
309     connect(uiDockRDS, SIGNAL(rdsPI(QString)), remote, SLOT(rdsPI(QString)));
310 
311     rds_timer = new QTimer(this);
312     connect(rds_timer, SIGNAL(timeout()), this, SLOT(rdsTimeout()));
313 
314     // enable frequency tooltips on FFT plot
315     ui->plotter->setTooltipsEnabled(true);
316 
317     // Create list of input devices. This must be done before the configuration is
318     // restored because device probing might change the device configuration
319     CIoConfig::getDeviceList(devList);
320 
321     m_recent_config = new RecentConfig(m_cfg_dir, ui->menu_RecentConfig);
322     connect(m_recent_config, SIGNAL(loadConfig(const QString &)), this, SLOT(loadConfigSlot(const QString &)));
323 
324     // restore last session
325     if (!loadConfig(cfgfile, true, true))
326     {
327 
328       // first time config
329         qDebug() << "Launching I/O device editor";
330         if (firstTimeConfig() != QDialog::Accepted)
331         {
332             qDebug() << "I/O device configuration cancelled.";
333             configOk = false;
334         }
335         else
336         {
337             configOk = true;
338         }
339     }
340     else if (edit_conf)
341     {
342         qDebug() << "Launching I/O device editor";
343         if (on_actionIoConfig_triggered() != QDialog::Accepted)
344         {
345             qDebug() << "I/O device configuration cancelled.";
346             configOk = false;
347         }
348         else
349         {
350             configOk = true;
351         }
352     }
353 
354     qsvg_dummy = new QSvgWidget();
355 }
356 
~MainWindow()357 MainWindow::~MainWindow()
358 {
359     on_actionDSP_triggered(false);
360 
361     /* stop and delete timers */
362     dec_timer->stop();
363     delete dec_timer;
364 
365     meter_timer->stop();
366     delete meter_timer;
367 
368     iq_fft_timer->stop();
369     delete iq_fft_timer;
370 
371     audio_fft_timer->stop();
372     delete audio_fft_timer;
373 
374     if (m_settings)
375     {
376         m_settings->setValue("configversion", 3);
377         m_settings->setValue("crashed", false);
378 
379         // hide toolbar (default=false)
380         if (ui->mainToolBar->isHidden())
381             m_settings->setValue("gui/hide_toolbar", true);
382         else
383             m_settings->remove("gui/hide_toolbar");
384 
385         m_settings->setValue("gui/geometry", saveGeometry());
386         m_settings->setValue("gui/state", saveState());
387 
388         // save session
389         storeSession();
390 
391         m_settings->sync();
392         delete m_settings;
393     }
394 
395     delete m_recent_config;
396 
397     delete iq_tool;
398     delete dxc_options;
399     delete ui;
400     delete uiDockRxOpt;
401     delete uiDockAudio;
402     delete uiDockBookmarks;
403     delete uiDockFft;
404     delete uiDockInputCtl;
405     delete uiDockRDS;
406     delete rx;
407     delete remote;
408     delete [] d_fftData;
409     delete [] d_realFftData;
410     delete [] d_iirFftData;
411     delete qsvg_dummy;
412 }
413 
414 /**
415  * Load new configuration.
416  * @param cfgfile
417  * @returns True if config is OK, False if not (e.g. no input device specified).
418  *
419  * If cfgfile is an absolute path it will be used as is, otherwise it is assumed
420  * to be the name of a file under m_cfg_dir.
421  *
422  * If cfgfile does not exist it will be created.
423  *
424  * If no input device is specified, we return false to signal that the I/O
425  * configuration dialog should be run.
426  *
427  * FIXME: Refactor.
428  */
loadConfig(const QString & cfgfile,bool check_crash,bool restore_mainwindow)429 bool MainWindow::loadConfig(const QString& cfgfile, bool check_crash,
430                             bool restore_mainwindow)
431 {
432     double      actual_rate;
433     qint64      int64_val;
434     int         int_val;
435     bool        bool_val;
436     bool        conf_ok = false;
437     bool        conv_ok;
438     bool        skip_loading_cfg = false;
439 
440     qDebug() << "Loading configuration from:" << cfgfile;
441 
442     if (m_settings)
443     {
444         // set current config to not crashed before loading new config
445         m_settings->setValue("crashed", false);
446         m_settings->sync();
447         delete m_settings;
448     }
449 
450     if (QDir::isAbsolutePath(cfgfile))
451         m_settings = new QSettings(cfgfile, QSettings::IniFormat);
452     else
453         m_settings = new QSettings(QString("%1/%2").arg(m_cfg_dir).arg(cfgfile),
454                                    QSettings::IniFormat);
455 
456     qDebug() << "Configuration file:" << m_settings->fileName();
457 
458     if (check_crash)
459     {
460         if (m_settings->value("crashed", false).toBool())
461         {
462             qDebug() << "Crash guard triggered!" << endl;
463             auto* askUserAboutConfig =
464                     new QMessageBox(QMessageBox::Warning, tr("Crash Detected!"),
465                                     tr("<p>Gqrx has detected problems with the current configuration. "
466                                        "Loading the configuration again could cause the application to crash.</p>"
467                                        "<p>Do you want to edit the settings?</p>"),
468                                     QMessageBox::Yes | QMessageBox::No);
469             askUserAboutConfig->setDefaultButton(QMessageBox::Yes);
470             askUserAboutConfig->setTextFormat(Qt::RichText);
471             askUserAboutConfig->exec();
472             if (askUserAboutConfig->result() == QMessageBox::Yes)
473                 skip_loading_cfg = true;
474 
475             delete askUserAboutConfig;
476         }
477         else
478         {
479             m_settings->setValue("crashed", true); // clean exit will set this to FALSE
480             m_settings->sync();
481         }
482     }
483 
484     if (skip_loading_cfg)
485         return false;
486 
487     // manual reconf (FIXME: check status)
488     conv_ok = false;
489 
490     // hide toolbar
491     bool_val = m_settings->value("gui/hide_toolbar", false).toBool();
492     if (bool_val)
493         ui->mainToolBar->hide();
494 
495     // main window settings
496     if (restore_mainwindow)
497     {
498         restoreGeometry(m_settings->value("gui/geometry",
499                                           saveGeometry()).toByteArray());
500         restoreState(m_settings->value("gui/state", saveState()).toByteArray());
501     }
502 
503     QString indev = m_settings->value("input/device", "").toString();
504     if (!indev.isEmpty())
505     {
506         try
507         {
508             rx->set_input_device(indev.toStdString());
509             conf_ok = true;
510         }
511         catch (std::runtime_error &x)
512         {
513             QMessageBox::warning(nullptr,
514                              QObject::tr("Failed to set input device"),
515                              QObject::tr("<p><b>%1</b></p>"
516                                          "Please select another device.")
517                                      .arg(x.what()),
518                              QMessageBox::Ok);
519         }
520 
521         // Update window title
522         QRegExp regexp(R"('([a-zA-Z0-9 \-\_\/\.\,\(\)]+)')");
523         QString devlabel;
524         if (regexp.indexIn(indev, 0) != -1)
525             devlabel = regexp.cap(1);
526         else
527             devlabel = indev; //"Unknown";
528 
529         setWindowTitle(QString("Gqrx %1 - %2").arg(VERSION).arg(devlabel));
530 
531         // Add available antenna connectors to the UI
532         std::vector<std::string> antennas = rx->get_antennas();
533         uiDockInputCtl->setAntennas(antennas);
534 
535         // Update gain stages.
536         if (indev.contains("rtl", Qt::CaseInsensitive)
537                 && !m_settings->contains("input/gains"))
538         {
539             /* rtlsdr gain is 0 by default making users think their device is
540              * deaf. Therefore, we don't read gain from the device, but initialize
541              * it to the midpoint.
542              */
543             updateGainStages(false);
544         }
545         else
546             updateGainStages(true);
547     }
548 
549     QString outdev = m_settings->value("output/device", "").toString();
550 
551     try {
552         rx->set_output_device(outdev.toStdString());
553     } catch (std::exception &x) {
554         QMessageBox::warning(nullptr,
555                          QObject::tr("Failed to set output device"),
556                          QObject::tr("<p><b>%1</b></p>"
557                                      "Please select another device.")
558                                  .arg(x.what()),
559                          QMessageBox::Ok);
560     }
561 
562     int_val = m_settings->value("input/sample_rate", 0).toInt(&conv_ok);
563     if (conv_ok && (int_val > 0))
564     {
565         actual_rate = rx->set_input_rate(int_val);
566 
567         if (actual_rate == 0)
568         {
569             // There is an error with the device (perhaps not attached)
570             // Warn user and use 100 ksps (rate used by gr-osmocom null_source)
571             auto *dialog =
572                     new QMessageBox(QMessageBox::Warning, tr("Device Error"),
573                                     tr("There was an error configuring the input device.\n"
574                                        "Please make sure that a supported device is attached "
575                                        "to the computer and restart gqrx."),
576                                     QMessageBox::Ok);
577             dialog->setModal(true);
578             dialog->setAttribute(Qt::WA_DeleteOnClose);
579             dialog->show();
580 
581             actual_rate = int_val;
582         }
583 
584         qDebug() << "Requested sample rate:" << int_val;
585         qDebug() << "Actual sample rate   :" << QString("%1").arg(actual_rate, 0, 'f', 6);
586     }
587     else
588         actual_rate = rx->get_input_rate();
589 
590     if (actual_rate > 0.)
591     {
592         int_val = m_settings->value("input/decimation", 1).toInt(&conv_ok);
593         if (conv_ok && int_val >= 2)
594         {
595             if (rx->set_input_decim(int_val) != (unsigned int)int_val)
596             {
597                 qDebug() << "Failed to set decimation" << int_val;
598                 qDebug() << "  actual decimation:" << rx->get_input_decim();
599             }
600             else
601             {
602                 // update actual rate
603                 actual_rate /= (double)int_val;
604                 qDebug() << "Input decimation:" << int_val;
605                 qDebug() << "Quadrature rate:" << QString("%1").arg(actual_rate, 0, 'f', 6);
606             }
607         }
608         else
609             rx->set_input_decim(1);
610 
611         // update various widgets that need a sample rate
612         uiDockRxOpt->setFilterOffsetRange((qint64)(actual_rate));
613         uiDockFft->setSampleRate(actual_rate);
614         ui->plotter->setSampleRate(actual_rate);
615         ui->plotter->setSpanFreq((quint32)actual_rate);
616         remote->setBandwidth((qint64)actual_rate);
617         iq_tool->setSampleRate((qint64)actual_rate);
618     }
619     else
620         qDebug() << "Error: Actual sample rate is" << actual_rate;
621 
622     int64_val = m_settings->value("input/bandwidth", 0).toInt(&conv_ok);
623     if (conv_ok)
624     {
625         // set analog bw even if 0 since for some devices 0 Hz means "auto"
626         double actual_bw = rx->set_analog_bandwidth((double) int64_val);
627         qDebug() << "Requested bandwidth:" << int64_val << "Hz";
628         qDebug() << "Actual bandwidth   :" << actual_bw << "Hz";
629     }
630 
631     uiDockInputCtl->readSettings(m_settings); // this will also update freq range
632     uiDockRxOpt->readSettings(m_settings);
633     uiDockFft->readSettings(m_settings);
634     uiDockAudio->readSettings(m_settings);
635     dxc_options->readSettings(m_settings);
636 
637     {
638         int64_val = m_settings->value("input/frequency", 14236000).toLongLong(&conv_ok);
639 
640         // If frequency is out of range set frequency to the center of the range.
641         qint64 hw_freq = int64_val - d_lnb_lo - (qint64)(rx->get_filter_offset());
642         if (hw_freq < d_hw_freq_start || hw_freq > d_hw_freq_stop)
643         {
644             int64_val = (d_hw_freq_stop - d_hw_freq_start) / 2 +
645                         (qint64)(rx->get_filter_offset()) + d_lnb_lo;
646         }
647 
648         ui->freqCtrl->setFrequency(int64_val);
649         setNewFrequency(ui->freqCtrl->getFrequency()); // ensure all GUI and RF is updated
650     }
651 
652     {
653         int flo = m_settings->value("receiver/filter_low_cut", 0).toInt(&conv_ok);
654         int fhi = m_settings->value("receiver/filter_high_cut", 0).toInt(&conv_ok);
655 
656         if (conv_ok && uiDockRxOpt->currentDemod() != DockRxOpt::MODE_OFF && flo != fhi)
657         {
658             on_plotter_newFilterFreq(flo, fhi);
659         }
660     }
661 
662     iq_tool->readSettings(m_settings);
663 
664     /*
665      * Initialization the remote control at the end.
666      * We must be sure that all variables initialized before starting RC server.
667      */
668     remote->readSettings(m_settings);
669     bool_val = m_settings->value("remote_control/enabled", false).toBool();
670     if (bool_val)
671     {
672        remote->start_server();
673        ui->actionRemoteControl->setChecked(true);
674     }
675 
676     emit m_recent_config->configLoaded(m_settings->fileName());
677 
678     return conf_ok;
679 }
680 
681 /**
682  * @brief Save current configuration to a file.
683  * @param cfgfile
684  * @returns True if the operation was successful.
685  *
686  * If cfgfile is an absolute path it will be used as is, otherwise it is
687  * assumed to be the name of a file under m_cfg_dir.
688  *
689  * If cfgfile already exists it will be overwritten (we assume that a file
690  * selection dialog has already asked for confirmation of overwrite).
691  *
692  * Since QSettings does not support "save as" we do this by copying the current
693  * settings to a new file.
694  */
saveConfig(const QString & cfgfile)695 bool MainWindow::saveConfig(const QString& cfgfile)
696 {
697     QString oldfile = m_settings->fileName();
698     QString newfile;
699 
700     qDebug() << "Saving configuration to:" << cfgfile;
701 
702     m_settings->sync();
703 
704     if (QDir::isAbsolutePath(cfgfile))
705         newfile = cfgfile;
706     else
707         newfile = QString("%1/%2").arg(m_cfg_dir).arg(cfgfile);
708 
709     if (newfile == oldfile) {
710         qDebug() << "New file is equal to old file => SYNCING...";
711         emit m_recent_config->configSaved(newfile);
712         return true;
713     }
714 
715     if (QFile::exists(newfile))
716     {
717         qDebug() << "File" << newfile << "already exists => DELETING...";
718         if (QFile::remove(newfile))
719             qDebug() << "Deleted" << newfile;
720         else
721             qDebug() << "Failed to delete" << newfile;
722     }
723     if (QFile::copy(oldfile, newfile))
724     {
725         loadConfig(cfgfile, false, false);
726         return true;
727     }
728     else
729     {
730         qDebug() << "Error saving configuration to" << newfile;
731         return false;
732     }
733 }
734 
735 /**
736  * Store session-related parameters (frequency, gain,...)
737  *
738  * This needs to be called when we switch input source, otherwise the
739  * new source would use the parameters stored on last exit.
740  */
storeSession()741 void MainWindow::storeSession()
742 {
743     if (m_settings)
744     {
745         m_settings->setValue("input/frequency", ui->freqCtrl->getFrequency());
746 
747         uiDockInputCtl->saveSettings(m_settings);
748         uiDockRxOpt->saveSettings(m_settings);
749         uiDockFft->saveSettings(m_settings);
750         uiDockAudio->saveSettings(m_settings);
751 
752         remote->saveSettings(m_settings);
753         iq_tool->saveSettings(m_settings);
754         dxc_options->saveSettings(m_settings);
755 
756         {
757             int     flo, fhi;
758             ui->plotter->getHiLowCutFrequencies(&flo, &fhi);
759             if (flo != fhi)
760             {
761                 m_settings->setValue("receiver/filter_low_cut", flo);
762                 m_settings->setValue("receiver/filter_high_cut", fhi);
763             }
764         }
765     }
766 }
767 
768 /**
769  * @brief Update hardware RF frequency range.
770  * @param ignore_limits Whether ignore the hardware specd and allow DC-to-light
771  *                      range.
772  *
773  * This function fetches the frequency range of the receiver. Useful when we
774  * read a new configuration with a new input device or when the ignore_limits
775  * setting is changed.
776  */
updateHWFrequencyRange(bool ignore_limits)777 void MainWindow::updateHWFrequencyRange(bool ignore_limits)
778 {
779     double startd, stopd, stepd;
780 
781     if (ignore_limits)
782     {
783         d_hw_freq_start = (quint64) 0;
784         d_hw_freq_stop  = (quint64) 9999e6;
785     }
786     else if (rx->get_rf_range(&startd, &stopd, &stepd) == receiver::STATUS_OK)
787     {
788         d_hw_freq_start = (quint64) startd;
789         d_hw_freq_stop  = (quint64) stopd;
790     }
791     else
792     {
793         qDebug() << __func__ << "failed fetching new hardware frequency range";
794         d_hw_freq_start = (quint64) 0;
795         d_hw_freq_stop  = (quint64) 9999e6;
796     }
797 
798     updateFrequencyRange(); // Also update the available frequency range
799 }
800 
801 /**
802  * @brief Update available frequency range.
803  *
804  * This function sets the available frequency range based on the hardware
805  * frequency range, the selected filter offset and the LNB LO.
806  *
807  * This function must therefore be called whenever the LNB LO or the filter
808  * offset has changed.
809  */
updateFrequencyRange()810 void MainWindow::updateFrequencyRange()
811 {
812     auto start = (qint64)(rx->get_filter_offset()) + d_hw_freq_start + d_lnb_lo;
813     auto stop  = (qint64)(rx->get_filter_offset()) + d_hw_freq_stop  + d_lnb_lo;
814 
815     ui->freqCtrl->setup(0, start, stop, 1, FCTL_UNIT_NONE);
816     uiDockRxOpt->setRxFreqRange(start, stop);
817 }
818 
819 /**
820  * @brief Update gain stages.
821  * @param read_from_device If true, the gain value will be read from the device,
822  *                         otherwise we set gain to the midpoint.
823  *
824  * This function fetches a list of available gain stages with their range
825  * and sends them to the input control UI widget.
826  */
updateGainStages(bool read_from_device)827 void MainWindow::updateGainStages(bool read_from_device)
828 {
829     gain_list_t gain_list;
830     std::vector<std::string> gain_names = rx->get_gain_names();
831     gain_t gain;
832 
833     std::vector<std::string>::iterator it;
834     for (it = gain_names.begin(); it != gain_names.end(); ++it)
835     {
836         gain.name = *it;
837         rx->get_gain_range(gain.name, &gain.start, &gain.stop, &gain.step);
838         if (read_from_device)
839         {
840             gain.value = rx->get_gain(gain.name);
841         }
842         else
843         {
844             gain.value = (gain.start + gain.stop) / 2;
845             rx->set_gain(gain.name, gain.value);
846         }
847         gain_list.push_back(gain);
848     }
849 
850     uiDockInputCtl->setGainStages(gain_list);
851     remote->setGainStages(gain_list);
852 }
853 
854 /**
855  * @brief Slot for receiving frequency change signals.
856  * @param[in] freq The new frequency.
857  *
858  * This slot is connected to the CFreqCtrl::newFrequency() signal and is used
859  * to set new receive frequency.
860  */
setNewFrequency(qint64 rx_freq)861 void MainWindow::setNewFrequency(qint64 rx_freq)
862 {
863     auto hw_freq = (double)(rx_freq - d_lnb_lo) - rx->get_filter_offset();
864     auto center_freq = rx_freq - (qint64)rx->get_filter_offset();
865 
866     d_hw_freq = (qint64)hw_freq;
867 
868     // set receiver frequency
869     rx->set_rf_freq(hw_freq);
870 
871     // update widgets
872     ui->plotter->setCenterFreq(center_freq);
873     uiDockRxOpt->setHwFreq(d_hw_freq);
874     ui->freqCtrl->setFrequency(rx_freq);
875     uiDockBookmarks->setNewFrequency(rx_freq);
876 }
877 
878 /**
879  * @brief Set new LNB LO frequency.
880  * @param freq_mhz The new frequency in MHz.
881  */
setLnbLo(double freq_mhz)882 void MainWindow::setLnbLo(double freq_mhz)
883 {
884     // calculate current RF frequency
885     auto rf_freq = ui->freqCtrl->getFrequency() - d_lnb_lo;
886 
887     d_lnb_lo = qint64(freq_mhz*1e6);
888     qDebug() << "New LNB LO:" << d_lnb_lo << "Hz";
889 
890     // Update ranges and show updated frequency
891     updateFrequencyRange();
892     ui->freqCtrl->setFrequency(d_lnb_lo + rf_freq);
893     ui->plotter->setCenterFreq(d_lnb_lo + d_hw_freq);
894 
895     // update LNB LO in settings
896     if (freq_mhz == 0.f)
897         m_settings->remove("input/lnb_lo");
898     else
899         m_settings->setValue("input/lnb_lo", d_lnb_lo);
900 }
901 
902 /** Select new antenna connector. */
setAntenna(const QString & antenna)903 void MainWindow::setAntenna(const QString& antenna)
904 {
905     qDebug() << "New antenna selected:" << antenna;
906     rx->set_antenna(antenna.toStdString());
907 }
908 
909 /**
910  * @brief Set new channel filter offset.
911  * @param freq_hz The new filter offset in Hz.
912  */
setFilterOffset(qint64 freq_hz)913 void MainWindow::setFilterOffset(qint64 freq_hz)
914 {
915     rx->set_filter_offset((double) freq_hz);
916     ui->plotter->setFilterOffset(freq_hz);
917 
918     updateFrequencyRange();
919 
920     auto rx_freq = d_hw_freq + d_lnb_lo + freq_hz;
921     ui->freqCtrl->setFrequency(rx_freq);
922 
923     if (rx->is_rds_decoder_active()) {
924         rx->reset_rds_parser();
925     }
926 }
927 
928 /**
929  * @brief Set a specific gain.
930  * @param name The name of the gain stage to adjust.
931  * @param gain The new value.
932  */
setGain(const QString & name,double gain)933 void MainWindow::setGain(const QString& name, double gain)
934 {
935     rx->set_gain(name.toStdString(), gain);
936 }
937 
938 /** Enable / disable hardware AGC. */
setAutoGain(bool enabled)939 void MainWindow::setAutoGain(bool enabled)
940 {
941     rx->set_auto_gain(enabled);
942     if (!enabled)
943         uiDockInputCtl->restoreManualGains();
944 }
945 
946 /**
947  * @brief Set new frequency offset value.
948  * @param ppm Frequency correction.
949  *
950  * The valid range is between -200 and 200.
951  */
setFreqCorr(double ppm)952 void MainWindow::setFreqCorr(double ppm)
953 {
954     if (ppm < -200.0)
955         ppm = -200.0;
956     else if (ppm > 200.0)
957         ppm = 200.0;
958 
959     qDebug() << __FUNCTION__ << ":" << ppm << "ppm";
960     rx->set_freq_corr(ppm);
961 }
962 
963 
964 /** Enable/disable I/Q reversion. */
setIqSwap(bool reversed)965 void MainWindow::setIqSwap(bool reversed)
966 {
967     rx->set_iq_swap(reversed);
968 }
969 
970 /** Enable/disable automatic DC removal. */
setDcCancel(bool enabled)971 void MainWindow::setDcCancel(bool enabled)
972 {
973     rx->set_dc_cancel(enabled);
974 }
975 
976 /** Enable/disable automatic IQ balance. */
setIqBalance(bool enabled)977 void MainWindow::setIqBalance(bool enabled)
978 {
979     try
980     {
981         rx->set_iq_balance(enabled);
982     }
983     catch (std::exception &x)
984     {
985         qCritical() << "Failed to set IQ balance: " << x.what();
986         m_settings->remove("input/iq_balance");
987         uiDockInputCtl->setIqBalance(false);
988         if (enabled)
989         {
990             QMessageBox::warning(this, tr("Gqrx error"),
991                                  tr("Failed to set IQ balance.\n"
992                                     "IQ balance setting in Input Control disabled."),
993                                  QMessageBox::Ok, QMessageBox::Ok);
994         }
995     }
996 }
997 
998 /**
999  * @brief Ignore hardware limits.
1000  * @param ignore_limits Whether hardware limits should be ignored or not.
1001  *
1002  * This slot is triggered when the user changes the "Ignore hardware limits"
1003  * option. It will update the allowed frequency range and also update the
1004  * current RF center frequency, which may change when we switch from ignore to
1005  * don't ignore.
1006  */
setIgnoreLimits(bool ignore_limits)1007 void MainWindow::setIgnoreLimits(bool ignore_limits)
1008 {
1009     updateHWFrequencyRange(ignore_limits);
1010 
1011     auto filter_offset = (qint64)rx->get_filter_offset();
1012     auto freq = (qint64)rx->get_rf_freq();
1013     ui->freqCtrl->setFrequency(d_lnb_lo + freq + filter_offset);
1014 
1015     // This will ensure that if frequency is clamped and that
1016     // the UI is updated with the correct frequency.
1017     freq = ui->freqCtrl->getFrequency();
1018     setNewFrequency(freq);
1019 }
1020 
1021 
1022 /** Reset lower digits of main frequency control widget */
setFreqCtrlReset(bool enabled)1023 void MainWindow::setFreqCtrlReset(bool enabled)
1024 {
1025     ui->freqCtrl->setResetLowerDigits(enabled);
1026     uiDockRxOpt->setResetLowerDigits(enabled);
1027 }
1028 
1029 /** Invert scroll wheel direction */
setInvertScrolling(bool enabled)1030 void MainWindow::setInvertScrolling(bool enabled)
1031 {
1032     ui->freqCtrl->setInvertScrolling(enabled);
1033     ui->plotter->setInvertScrolling(enabled);
1034     uiDockRxOpt->setInvertScrolling(enabled);
1035     uiDockAudio->setInvertScrolling(enabled);
1036 }
1037 
1038 /**
1039  * @brief Select new demodulator.
1040  * @param demod New demodulator.
1041  */
selectDemod(const QString & strModulation)1042 void MainWindow::selectDemod(const QString& strModulation)
1043 {
1044     int iDemodIndex;
1045 
1046     iDemodIndex = DockRxOpt::GetEnumForModulationString(strModulation);
1047     qDebug() << "selectDemod(str):" << strModulation << "-> IDX:" << iDemodIndex;
1048 
1049     return selectDemod(iDemodIndex);
1050 }
1051 
1052 /**
1053  * @brief Select new demodulator.
1054  * @param demod New demodulator index.
1055  *
1056  * This slot basically maps the index of the mode selector to receiver::demod
1057  * and configures the default channel filter.
1058  *
1059  */
selectDemod(int mode_idx)1060 void MainWindow::selectDemod(int mode_idx)
1061 {
1062     double  cwofs = 0.0;
1063     int     filter_preset = uiDockRxOpt->currentFilter();
1064     int     flo=0, fhi=0, click_res=100;
1065     bool    rds_enabled;
1066 
1067     // validate mode_idx
1068     if (mode_idx < DockRxOpt::MODE_OFF || mode_idx >= DockRxOpt::MODE_LAST)
1069     {
1070         qDebug() << "Invalid mode index:" << mode_idx;
1071         mode_idx = DockRxOpt::MODE_OFF;
1072     }
1073     qDebug() << "New mode index:" << mode_idx;
1074 
1075     uiDockRxOpt->getFilterPreset(mode_idx, filter_preset, &flo, &fhi);
1076     d_filter_shape = (receiver::filter_shape)uiDockRxOpt->currentFilterShape();
1077 
1078     rds_enabled = rx->is_rds_decoder_active();
1079     if (rds_enabled)
1080         setRdsDecoder(false);
1081     uiDockRDS->setDisabled();
1082 
1083     switch (mode_idx) {
1084 
1085     case DockRxOpt::MODE_OFF:
1086         /* Spectrum analyzer only */
1087         if (rx->is_recording_audio())
1088         {
1089             stopAudioRec();
1090             uiDockAudio->setAudioRecButtonState(false);
1091         }
1092         rx->set_demod(receiver::RX_DEMOD_OFF);
1093         click_res = 1000;
1094         break;
1095 
1096     case DockRxOpt::MODE_RAW:
1097         /* Raw I/Q; max 96 ksps*/
1098         rx->set_demod(receiver::RX_DEMOD_NONE);
1099         ui->plotter->setDemodRanges(-40000, -200, 200, 40000, true);
1100         uiDockAudio->setFftRange(0,24000);
1101         click_res = 100;
1102         break;
1103 
1104     case DockRxOpt::MODE_AM:
1105         rx->set_demod(receiver::RX_DEMOD_AM);
1106         ui->plotter->setDemodRanges(-40000, -200, 200, 40000, true);
1107         uiDockAudio->setFftRange(0,6000);
1108         click_res = 100;
1109         break;
1110 
1111     case DockRxOpt::MODE_AM_SYNC:
1112         rx->set_demod(receiver::RX_DEMOD_AMSYNC);
1113         ui->plotter->setDemodRanges(-40000, -200, 200, 40000, true);
1114         uiDockAudio->setFftRange(0,6000);
1115         click_res = 100;
1116         break;
1117 
1118     case DockRxOpt::MODE_NFM:
1119         ui->plotter->setDemodRanges(-40000, -1000, 1000, 40000, true);
1120         uiDockAudio->setFftRange(0, 5000);
1121         rx->set_demod(receiver::RX_DEMOD_NFM);
1122         rx->set_fm_maxdev(uiDockRxOpt->currentMaxdev());
1123         rx->set_fm_deemph(uiDockRxOpt->currentEmph());
1124         click_res = 100;
1125         break;
1126 
1127     case DockRxOpt::MODE_WFM_MONO:
1128     case DockRxOpt::MODE_WFM_STEREO:
1129     case DockRxOpt::MODE_WFM_STEREO_OIRT:
1130         /* Broadcast FM */
1131         ui->plotter->setDemodRanges(-120e3, -10000, 10000, 120e3, true);
1132         uiDockAudio->setFftRange(0,24000);  /** FIXME: get audio rate from rx **/
1133         click_res = 1000;
1134         if (mode_idx == DockRxOpt::MODE_WFM_MONO)
1135             rx->set_demod(receiver::RX_DEMOD_WFM_M);
1136         else if (mode_idx == DockRxOpt::MODE_WFM_STEREO_OIRT)
1137             rx->set_demod(receiver::RX_DEMOD_WFM_S_OIRT);
1138         else
1139             rx->set_demod(receiver::RX_DEMOD_WFM_S);
1140 
1141         uiDockRDS->setEnabled();
1142         if (rds_enabled)
1143             setRdsDecoder(true);
1144         break;
1145 
1146     case DockRxOpt::MODE_LSB:
1147         /* LSB */
1148         rx->set_demod(receiver::RX_DEMOD_SSB);
1149         ui->plotter->setDemodRanges(-40000, -100, -5000, 0, false);
1150         uiDockAudio->setFftRange(0,3000);
1151         click_res = 100;
1152         break;
1153 
1154     case DockRxOpt::MODE_USB:
1155         /* USB */
1156         rx->set_demod(receiver::RX_DEMOD_SSB);
1157         ui->plotter->setDemodRanges(0, 5000, 100, 40000, false);
1158         uiDockAudio->setFftRange(0,3000);
1159         click_res = 100;
1160         break;
1161 
1162     case DockRxOpt::MODE_CWL:
1163         /* CW-L */
1164         rx->set_demod(receiver::RX_DEMOD_SSB);
1165         cwofs = -uiDockRxOpt->getCwOffset();
1166         ui->plotter->setDemodRanges(-5000, -100, 100, 5000, true);
1167         uiDockAudio->setFftRange(0,1500);
1168         click_res = 10;
1169         break;
1170 
1171     case DockRxOpt::MODE_CWU:
1172         /* CW-U */
1173         rx->set_demod(receiver::RX_DEMOD_SSB);
1174         cwofs = uiDockRxOpt->getCwOffset();
1175         ui->plotter->setDemodRanges(-5000, -100, 100, 5000, true);
1176         uiDockAudio->setFftRange(0,1500);
1177         click_res = 10;
1178         break;
1179 
1180     default:
1181         qDebug() << "Unsupported mode selection (can't happen!): " << mode_idx;
1182         flo = -5000;
1183         fhi = 5000;
1184         click_res = 100;
1185         break;
1186     }
1187 
1188     qDebug() << "Filter preset for mode" << mode_idx << "LO:" << flo << "HI:" << fhi;
1189     ui->plotter->setHiLowCutFrequencies(flo, fhi);
1190     ui->plotter->setClickResolution(click_res);
1191     ui->plotter->setFilterClickResolution(click_res);
1192     rx->set_filter((double)flo, (double)fhi, d_filter_shape);
1193     rx->set_cw_offset(cwofs);
1194     rx->set_sql_level(uiDockRxOpt->currentSquelchLevel());
1195 
1196     remote->setMode(mode_idx);
1197     remote->setPassband(flo, fhi);
1198 
1199     d_have_audio = (mode_idx != DockRxOpt::MODE_OFF);
1200 
1201     uiDockRxOpt->setCurrentDemod(mode_idx);
1202 }
1203 
1204 
1205 /**
1206  * @brief New FM deviation selected.
1207  * @param max_dev The enw FM deviation.
1208  */
setFmMaxdev(float max_dev)1209 void MainWindow::setFmMaxdev(float max_dev)
1210 {
1211     qDebug() << "FM MAX_DEV: " << max_dev;
1212 
1213     /* receiver will check range */
1214     rx->set_fm_maxdev(max_dev);
1215 }
1216 
1217 
1218 /**
1219  * @brief New FM de-emphasis time consant selected.
1220  * @param tau The new time constant
1221  */
setFmEmph(double tau)1222 void MainWindow::setFmEmph(double tau)
1223 {
1224     qDebug() << "FM TAU: " << tau;
1225 
1226     /* receiver will check range */
1227     rx->set_fm_deemph(tau);
1228 }
1229 
1230 
1231 /**
1232  * @brief AM DCR status changed (slot).
1233  * @param enabled Whether DCR is enabled or not.
1234  */
setAmDcr(bool enabled)1235 void MainWindow::setAmDcr(bool enabled)
1236 {
1237     rx->set_am_dcr(enabled);
1238 }
1239 
setCwOffset(int offset)1240 void MainWindow::setCwOffset(int offset)
1241 {
1242     rx->set_cw_offset(offset);
1243 }
1244 
1245 /**
1246  * @brief AM-Sync DCR status changed (slot).
1247  * @param enabled Whether DCR is enabled or not.
1248  */
setAmSyncDcr(bool enabled)1249 void MainWindow::setAmSyncDcr(bool enabled)
1250 {
1251     rx->set_amsync_dcr(enabled);
1252 }
1253 
1254 /**
1255  * @brief New AM-Sync PLL BW selected.
1256  * @param pll_bw The new PLL BW.
1257  */
setAmSyncPllBw(float pll_bw)1258 void MainWindow::setAmSyncPllBw(float pll_bw)
1259 {
1260     qDebug() << "AM-Sync PLL BW: " << pll_bw;
1261 
1262     /* receiver will check range */
1263     rx->set_amsync_pll_bw(pll_bw);
1264 }
1265 
1266 /**
1267  * @brief Audio gain changed.
1268  * @param value The new audio gain in dB.
1269  */
setAudioGain(float value)1270 void MainWindow::setAudioGain(float value)
1271 {
1272     rx->set_af_gain(value);
1273 }
1274 
1275 /** Set AGC ON/OFF. */
setAgcOn(bool agc_on)1276 void MainWindow::setAgcOn(bool agc_on)
1277 {
1278     rx->set_agc_on(agc_on);
1279 }
1280 
1281 /** AGC hang ON/OFF. */
setAgcHang(bool use_hang)1282 void MainWindow::setAgcHang(bool use_hang)
1283 {
1284     rx->set_agc_hang(use_hang);
1285 }
1286 
1287 /** AGC threshold changed. */
setAgcThreshold(int threshold)1288 void MainWindow::setAgcThreshold(int threshold)
1289 {
1290     rx->set_agc_threshold(threshold);
1291 }
1292 
1293 /** AGC slope factor changed. */
setAgcSlope(int factor)1294 void MainWindow::setAgcSlope(int factor)
1295 {
1296     rx->set_agc_slope(factor);
1297 }
1298 
1299 /** AGC manual gain changed. */
setAgcGain(int gain)1300 void MainWindow::setAgcGain(int gain)
1301 {
1302     rx->set_agc_manual_gain(gain);
1303 }
1304 
1305 /** AGC decay changed. */
setAgcDecay(int msec)1306 void MainWindow::setAgcDecay(int msec)
1307 {
1308     rx->set_agc_decay(msec);
1309 }
1310 
1311 /**
1312  * @brief Noise blanker configuration changed.
1313  * @param nb1 Noise blanker 1 ON/OFF.
1314  * @param nb2 Noise blanker 2 ON/OFF.
1315  * @param threshold Noise blanker threshold.
1316  */
setNoiseBlanker(int nbid,bool on,float threshold)1317 void MainWindow::setNoiseBlanker(int nbid, bool on, float threshold)
1318 {
1319     qDebug() << "Noise blanker NB:" << nbid << " ON:" << on << "THLD:"
1320              << threshold;
1321 
1322     rx->set_nb_on(nbid, on);
1323     rx->set_nb_threshold(nbid, threshold);
1324 }
1325 
1326 /**
1327  * @brief Squelch level changed.
1328  * @param level_db The new squelch level in dBFS.
1329  */
setSqlLevel(double level_db)1330 void MainWindow::setSqlLevel(double level_db)
1331 {
1332     rx->set_sql_level(level_db);
1333     ui->sMeter->setSqlLevel(level_db);
1334 }
1335 
1336 /**
1337  * @brief Squelch level auto clicked.
1338  * @return The new squelch level.
1339  */
setSqlLevelAuto()1340 double MainWindow::setSqlLevelAuto()
1341 {
1342     double level = rx->get_signal_pwr() + 3.0;
1343     if (level > -10.0)  // avoid 0 dBFS
1344         level = uiDockRxOpt->getSqlLevel();
1345 
1346     setSqlLevel(level);
1347     return level;
1348 }
1349 
1350 /** Signal strength meter timeout. */
meterTimeout()1351 void MainWindow::meterTimeout()
1352 {
1353     float level;
1354 
1355     level = rx->get_signal_pwr();
1356     ui->sMeter->setLevel(level);
1357     remote->setSignalLevel(level);
1358 }
1359 
1360 #define LOG2_10 3.321928094887362
1361 
1362 /** Baseband FFT plot timeout. */
iqFftTimeout()1363 void MainWindow::iqFftTimeout()
1364 {
1365     unsigned int    fftsize;
1366     unsigned int    i;
1367     float           pwr_scale;
1368 
1369     // FIXME: fftsize is a reference
1370     rx->get_iq_fft_data(d_fftData, fftsize);
1371 
1372     if (fftsize == 0)
1373     {
1374         /* nothing to do, wait until next activation. */
1375         return;
1376     }
1377 
1378     // NB: without cast to float the multiplication will overflow at 64k
1379     // and pwr_scale will be inf
1380     pwr_scale = 1.0 / ((float)fftsize * (float)fftsize);
1381 
1382     /* Normalize, calculate power and shift the FFT */
1383     volk_32fc_magnitude_squared_32f(d_realFftData, d_fftData + (fftsize/2), fftsize/2);
1384     volk_32fc_magnitude_squared_32f(d_realFftData + (fftsize/2), d_fftData, fftsize/2);
1385     volk_32f_s32f_multiply_32f(d_realFftData, d_realFftData, pwr_scale, fftsize);
1386     volk_32f_log2_32f(d_realFftData, d_realFftData, fftsize);
1387     volk_32f_s32f_multiply_32f(d_realFftData, d_realFftData, 10 / LOG2_10, fftsize);
1388 
1389     for (i = 0; i < fftsize; i++)
1390     {
1391         /* FFT averaging */
1392         d_iirFftData[i] += d_fftAvg * (d_realFftData[i] - d_iirFftData[i]);
1393     }
1394 
1395     ui->plotter->setNewFftData(d_iirFftData, d_realFftData, fftsize);
1396 }
1397 
1398 /** Audio FFT plot timeout. */
audioFftTimeout()1399 void MainWindow::audioFftTimeout()
1400 {
1401     unsigned int    fftsize;
1402     unsigned int    i;
1403     float           pwr;
1404     float           pwr_scale;
1405     std::complex<float> pt;             /* a single FFT point used in calculations */
1406 
1407     if (!d_have_audio || !uiDockAudio->isVisible())
1408         return;
1409 
1410     rx->get_audio_fft_data(d_fftData, fftsize);
1411 
1412     if (fftsize == 0)
1413     {
1414         /* nothing to do, wait until next activation. */
1415         qDebug() << "No audio FFT data.";
1416         return;
1417     }
1418 
1419     pwr_scale = 1.0 / (fftsize * fftsize);
1420 
1421     /** FIXME: move post processing to rx_fft_f **/
1422     /* Normalize, calculate power and shift the FFT */
1423     for (i = 0; i < fftsize; i++)
1424     {
1425         /* normalize and shift */
1426         if (i < fftsize/2)
1427         {
1428             pt = d_fftData[fftsize/2+i];
1429         }
1430         else
1431         {
1432             pt = d_fftData[i-fftsize/2];
1433         }
1434 
1435         /* calculate power in dBFS */
1436         pwr = pwr_scale * (pt.imag() * pt.imag() + pt.real() * pt.real());
1437         d_realFftData[i] = 10.0 * log10f(pwr + 1.0e-20);
1438     }
1439 
1440     uiDockAudio->setNewFftData(d_realFftData, fftsize);
1441 }
1442 
1443 /** RDS message display timeout. */
rdsTimeout()1444 void MainWindow::rdsTimeout()
1445 {
1446     std::string buffer;
1447     int num;
1448 
1449     rx->get_rds_data(buffer, num);
1450     while(num!=-1) {
1451         rx->get_rds_data(buffer, num);
1452         uiDockRDS->updateRDS(QString::fromStdString(buffer), num);
1453     }
1454 }
1455 
1456 /**
1457  * @brief Start audio recorder.
1458  * @param filename The file name into which audio should be recorded.
1459  */
startAudioRec(const QString & filename)1460 void MainWindow::startAudioRec(const QString& filename)
1461 {
1462     if (!d_have_audio)
1463     {
1464         QMessageBox msg_box;
1465         msg_box.setIcon(QMessageBox::Critical);
1466         msg_box.setText(tr("Recording audio requires a demodulator.\n"
1467                            "Currently, demodulation is switched off "
1468                            "(Mode->Demod off)."));
1469         msg_box.exec();
1470         uiDockAudio->setAudioRecButtonState(false);
1471     }
1472     else if (rx->start_audio_recording(filename.toStdString()))
1473     {
1474         ui->statusBar->showMessage(tr("Error starting audio recorder"));
1475 
1476         /* reset state of record button */
1477         uiDockAudio->setAudioRecButtonState(false);
1478     }
1479     else
1480     {
1481         ui->statusBar->showMessage(tr("Recording audio to %1").arg(filename));
1482     }
1483 }
1484 
1485 /** Stop audio recorder. */
stopAudioRec()1486 void MainWindow::stopAudioRec()
1487 {
1488     if (rx->stop_audio_recording())
1489     {
1490         /* okay, this one would be weird if it really happened */
1491         ui->statusBar->showMessage(tr("Error stopping audio recorder"));
1492 
1493         uiDockAudio->setAudioRecButtonState(true);
1494     }
1495     else
1496     {
1497         ui->statusBar->showMessage(tr("Audio recorder stopped"), 5000);
1498     }
1499 }
1500 
1501 
1502 /** Start playback of audio file. */
startAudioPlayback(const QString & filename)1503 void MainWindow::startAudioPlayback(const QString& filename)
1504 {
1505     if (rx->start_audio_playback(filename.toStdString()))
1506     {
1507         ui->statusBar->showMessage(tr("Error trying to play %1").arg(filename));
1508 
1509         /* reset state of record button */
1510         uiDockAudio->setAudioPlayButtonState(false);
1511     }
1512     else
1513     {
1514         ui->statusBar->showMessage(tr("Playing %1").arg(filename));
1515     }
1516 }
1517 
1518 /** Stop playback of audio file. */
stopAudioPlayback()1519 void MainWindow::stopAudioPlayback()
1520 {
1521     if (rx->stop_audio_playback())
1522     {
1523         /* okay, this one would be weird if it really happened */
1524         ui->statusBar->showMessage(tr("Error stopping audio playback"));
1525 
1526         uiDockAudio->setAudioPlayButtonState(true);
1527     }
1528     else
1529     {
1530         ui->statusBar->showMessage(tr("Audio playback stopped"), 5000);
1531     }
1532 }
1533 
1534 /** Start streaming audio over UDP. */
startAudioStream(const QString & udp_host,int udp_port,bool stereo)1535 void MainWindow::startAudioStream(const QString& udp_host, int udp_port, bool stereo)
1536 {
1537     rx->start_udp_streaming(udp_host.toStdString(), udp_port, stereo);
1538 }
1539 
1540 /** Stop streaming audio over UDP. */
stopAudioStreaming()1541 void MainWindow::stopAudioStreaming()
1542 {
1543     rx->stop_udp_streaming();
1544 }
1545 
1546 /** Start I/Q recording. */
startIqRecording(const QString & recdir)1547 void MainWindow::startIqRecording(const QString& recdir)
1548 {
1549     qDebug() << __func__;
1550     // generate file name using date, time, rf freq in kHz and BW in Hz
1551     // gqrx_iq_yyyymmdd_hhmmss_freq_bw_fc.raw
1552     auto freq = (qint64)(rx->get_rf_freq());
1553     auto sr = (qint64)(rx->get_input_rate());
1554     auto dec = (quint32)(rx->get_input_decim());
1555     auto lastRec = QDateTime::currentDateTimeUtc().
1556             toString("%1/gqrx_yyyyMMdd_hhmmss_%2_%3_fc.'raw'")
1557             .arg(recdir).arg(freq).arg(sr/dec);
1558 
1559     // start recorder; fails if recording already in progress
1560     if (rx->start_iq_recording(lastRec.toStdString()))
1561     {
1562         // reset action status
1563         ui->statusBar->showMessage(tr("Error starting I/Q recoder"));
1564 
1565         // show an error message to user
1566         QMessageBox msg_box;
1567         msg_box.setIcon(QMessageBox::Critical);
1568         msg_box.setText(tr("There was an error starting the I/Q recorder.\n"
1569                            "Check write permissions for the selected location."));
1570         msg_box.exec();
1571 
1572     }
1573     else
1574     {
1575         ui->statusBar->showMessage(tr("Recording I/Q data to: %1").arg(lastRec),
1576                                    5000);
1577     }
1578 }
1579 
1580 /** Stop current I/Q recording. */
stopIqRecording()1581 void MainWindow::stopIqRecording()
1582 {
1583     qDebug() << __func__;
1584 
1585     if (rx->stop_iq_recording())
1586         ui->statusBar->showMessage(tr("Error stopping I/Q recoder"));
1587     else
1588         ui->statusBar->showMessage(tr("I/Q data recoding stopped"), 5000);
1589 }
1590 
startIqPlayback(const QString & filename,float samprate)1591 void MainWindow::startIqPlayback(const QString& filename, float samprate)
1592 {
1593     if (ui->actionDSP->isChecked())
1594     {
1595         // suspend DSP while we reload settings
1596         on_actionDSP_triggered(false);
1597     }
1598 
1599     storeSession();
1600 
1601     auto sri = (int)samprate;
1602     QString escapedFilename = receiver::escape_filename(filename.toStdString()).c_str();
1603     auto devstr = QString("file=%1,rate=%2,throttle=true,repeat=false")
1604             .arg(escapedFilename).arg(sri);
1605 
1606     qDebug() << __func__ << ":" << devstr;
1607 
1608     rx->set_input_device(devstr.toStdString());
1609 
1610     // sample rate
1611     auto actual_rate = rx->set_input_rate(samprate);
1612     qDebug() << "Requested sample rate:" << samprate;
1613     qDebug() << "Actual sample rate   :" << QString("%1")
1614                 .arg(actual_rate, 0, 'f', 6);
1615 
1616     uiDockRxOpt->setFilterOffsetRange((qint64)(actual_rate));
1617     ui->plotter->setSampleRate(actual_rate);
1618     ui->plotter->setSpanFreq((quint32)actual_rate);
1619     remote->setBandwidth(actual_rate);
1620 
1621     // FIXME: would be nice with good/bad status
1622     ui->statusBar->showMessage(tr("Playing %1").arg(filename));
1623 
1624     on_actionDSP_triggered(true);
1625 }
1626 
stopIqPlayback()1627 void MainWindow::stopIqPlayback()
1628 {
1629     if (ui->actionDSP->isChecked())
1630     {
1631         // suspend DSP while we reload settings
1632         on_actionDSP_triggered(false);
1633     }
1634 
1635     ui->statusBar->showMessage(tr("I/Q playback stopped"), 5000);
1636 
1637     // restore original input device
1638     auto indev = m_settings->value("input/device", "").toString();
1639     rx->set_input_device(indev.toStdString());
1640 
1641     // restore sample rate
1642     bool conv_ok;
1643     auto sr = m_settings->value("input/sample_rate", 0).toInt(&conv_ok);
1644     if (conv_ok && (sr > 0))
1645     {
1646         auto actual_rate = rx->set_input_rate(sr);
1647         qDebug() << "Requested sample rate:" << sr;
1648         qDebug() << "Actual sample rate   :" << QString("%1")
1649                     .arg(actual_rate, 0, 'f', 6);
1650 
1651         uiDockRxOpt->setFilterOffsetRange((qint64)(actual_rate));
1652         ui->plotter->setSampleRate(actual_rate);
1653         ui->plotter->setSpanFreq((quint32)actual_rate);
1654         remote->setBandwidth(sr);
1655 
1656         // not needed as long as we are not recording in iq_tool
1657         //iq_tool->setSampleRate(sr);
1658     }
1659 
1660     // restore frequency, gain, etc...
1661     uiDockInputCtl->readSettings(m_settings);
1662 
1663     if (ui->actionDSP->isChecked())
1664     {
1665         // restsart DSP
1666         on_actionDSP_triggered(true);
1667     }
1668 }
1669 
1670 
1671 /**
1672  * Go to a specific offset in the IQ file.
1673  * @param seek_pos The byte offset from the beginning of the file.
1674  */
seekIqFile(qint64 seek_pos)1675 void MainWindow::seekIqFile(qint64 seek_pos)
1676 {
1677     rx->seek_iq_file((long)seek_pos);
1678 }
1679 
1680 /** FFT size has changed. */
setIqFftSize(int size)1681 void MainWindow::setIqFftSize(int size)
1682 {
1683     qDebug() << "Changing baseband FFT size to" << size;
1684     rx->set_iq_fft_size(size);
1685     for (int i = 0; i < size; i++)
1686         d_iirFftData[i] = -140.0;  // dBFS
1687 }
1688 
1689 /** Baseband FFT rate has changed. */
setIqFftRate(int fps)1690 void MainWindow::setIqFftRate(int fps)
1691 {
1692     int interval;
1693 
1694     if (fps == 0)
1695     {
1696         interval = 36e7; // 100 hours
1697         ui->plotter->setRunningState(false);
1698     }
1699     else
1700     {
1701         interval = 1000 / fps;
1702 
1703         ui->plotter->setFftRate(fps);
1704         if (iq_fft_timer->isActive())
1705             ui->plotter->setRunningState(true);
1706     }
1707 
1708     if (interval > 9 && iq_fft_timer->isActive())
1709         iq_fft_timer->setInterval(interval);
1710 
1711     uiDockFft->setWfResolution(ui->plotter->getWfTimeRes());
1712 }
1713 
setIqFftWindow(int type)1714 void MainWindow::setIqFftWindow(int type)
1715 {
1716     rx->set_iq_fft_window(type);
1717 }
1718 
1719 /** Waterfall time span has changed. */
setWfTimeSpan(quint64 span_ms)1720 void MainWindow::setWfTimeSpan(quint64 span_ms)
1721 {
1722     // set new time span, then send back new resolution to be shown by GUI label
1723     ui->plotter->setWaterfallSpan(span_ms);
1724     uiDockFft->setWfResolution(ui->plotter->getWfTimeRes());
1725 }
1726 
setWfSize()1727 void MainWindow::setWfSize()
1728 {
1729     uiDockFft->setWfResolution(ui->plotter->getWfTimeRes());
1730 }
1731 
1732 /**
1733  * @brief Vertical split between waterfall and pandapter changed.
1734  * @param pct_pand The percentage of the waterfall.
1735  */
setIqFftSplit(int pct_wf)1736 void MainWindow::setIqFftSplit(int pct_wf)
1737 {
1738     if ((pct_wf >= 0) && (pct_wf <= 100))
1739         ui->plotter->setPercent2DScreen(pct_wf);
1740 }
1741 
setIqFftAvg(float avg)1742 void MainWindow::setIqFftAvg(float avg)
1743 {
1744     if ((avg >= 0) && (avg <= 1.0))
1745         d_fftAvg = avg;
1746 }
1747 
1748 /** Audio FFT rate has changed. */
setAudioFftRate(int fps)1749 void MainWindow::setAudioFftRate(int fps)
1750 {
1751     auto interval = 1000 / fps;
1752 
1753     if (interval < 10)
1754         return;
1755 
1756     if (audio_fft_timer->isActive())
1757         audio_fft_timer->setInterval(interval);
1758 }
1759 
1760 /** Set FFT plot color. */
setFftColor(const QColor & color)1761 void MainWindow::setFftColor(const QColor& color)
1762 {
1763     ui->plotter->setFftPlotColor(color);
1764     uiDockAudio->setFftColor(color);
1765 }
1766 
1767 /** Enable/disable filling the aread below the FFT plot. */
setFftFill(bool enable)1768 void MainWindow::setFftFill(bool enable)
1769 {
1770     ui->plotter->setFftFill(enable);
1771     uiDockAudio->setFftFill(enable);
1772 }
1773 
setFftPeakHold(bool enable)1774 void MainWindow::setFftPeakHold(bool enable)
1775 {
1776     ui->plotter->setPeakHold(enable);
1777 }
1778 
setPeakDetection(bool enabled)1779 void MainWindow::setPeakDetection(bool enabled)
1780 {
1781     ui->plotter->setPeakDetection(enabled ,2);
1782 }
1783 
1784 /**
1785  * @brief Start/Stop DSP processing.
1786  * @param checked Flag indicating whether DSP processing should be ON or OFF.
1787  *
1788  * This slot is executed when the actionDSP is toggled by the user. This can
1789  * either be via the menu bar or the "power on" button in the main toolbar or
1790  * by remote control.
1791  */
on_actionDSP_triggered(bool checked)1792 void MainWindow::on_actionDSP_triggered(bool checked)
1793 {
1794     remote->setReceiverStatus(checked);
1795 
1796     if (checked)
1797     {
1798         /* start receiver */
1799         rx->start();
1800 
1801         /* start GUI timers */
1802         meter_timer->start(100);
1803 
1804         if (uiDockFft->fftRate())
1805         {
1806             iq_fft_timer->start(1000/uiDockFft->fftRate());
1807             ui->plotter->setRunningState(true);
1808         }
1809         else
1810         {
1811             iq_fft_timer->start(36e7); // 100 hours
1812             ui->plotter->setRunningState(false);
1813         }
1814 
1815         audio_fft_timer->start(40);
1816 
1817         /* update menu text and button tooltip */
1818         ui->actionDSP->setToolTip(tr("Stop DSP processing"));
1819         ui->actionDSP->setText(tr("Stop DSP"));
1820     }
1821     else
1822     {
1823         /* stop GUI timers */
1824         meter_timer->stop();
1825         iq_fft_timer->stop();
1826         audio_fft_timer->stop();
1827         rds_timer->stop();
1828 
1829         /* stop receiver */
1830         rx->stop();
1831 
1832         /* update menu text and button tooltip */
1833         ui->actionDSP->setToolTip(tr("Start DSP processing"));
1834         ui->actionDSP->setText(tr("Start DSP"));
1835 
1836         ui->plotter->setRunningState(false);
1837     }
1838 
1839     ui->actionDSP->setChecked(checked); //for remote control
1840 
1841 }
1842 
1843 /**
1844  * @brief Action: I/O device configurator triggered.
1845  *
1846  * This slot is activated when the user selects "I/O Devices" in the
1847  * menu. It activates the I/O configurator and if the user closes the
1848  * configurator using the OK button, the new configuration is read and
1849  * sent to the receiver.
1850  */
on_actionIoConfig_triggered()1851 int MainWindow::on_actionIoConfig_triggered()
1852 {
1853     qDebug() << "Configure I/O devices.";
1854 
1855     auto *ioconf = new CIoConfig(m_settings, devList);
1856     auto confres = ioconf->exec();
1857 
1858     if (confres == QDialog::Accepted)
1859     {
1860         if (ui->actionDSP->isChecked())
1861             // suspend DSP while we reload settings
1862             on_actionDSP_triggered(false);
1863 
1864         // Refresh LNB LO in dock widget, otherwise changes will be lost
1865         uiDockInputCtl->readLnbLoFromSettings(m_settings);
1866         storeSession();
1867         loadConfig(m_settings->fileName(), false, false);
1868 
1869         if (ui->actionDSP->isChecked())
1870             // restsart DSP
1871             on_actionDSP_triggered(true);
1872     }
1873 
1874     delete ioconf;
1875 
1876     return confres;
1877 }
1878 
1879 
1880 /** Run first time configurator. */
firstTimeConfig()1881 int MainWindow::firstTimeConfig()
1882 {
1883     qDebug() << __func__;
1884 
1885     auto *ioconf = new CIoConfig(m_settings, devList);
1886     auto confres = ioconf->exec();
1887 
1888     if (confres == QDialog::Accepted)
1889         loadConfig(m_settings->fileName(), false, false);
1890 
1891     delete ioconf;
1892 
1893     return confres;
1894 }
1895 
1896 
1897 /** Load configuration activated by user. */
on_actionLoadSettings_triggered()1898 void MainWindow::on_actionLoadSettings_triggered()
1899 {
1900     auto cfgfile = QFileDialog::getOpenFileName(this, tr("Load settings"),
1901                                            m_last_dir.isEmpty() ? m_cfg_dir : m_last_dir,
1902                                            tr("Settings (*.conf)"));
1903 
1904     qDebug() << "File to open:" << cfgfile;
1905 
1906     if (cfgfile.isEmpty())
1907         return;
1908 
1909     if (!cfgfile.endsWith(".conf", Qt::CaseSensitive))
1910         cfgfile.append(".conf");
1911 
1912     loadConfig(cfgfile, cfgfile != m_settings->fileName(), cfgfile != m_settings->fileName());
1913 
1914     // store last dir
1915     QFileInfo fi(cfgfile);
1916     if (m_cfg_dir != fi.absolutePath())
1917         m_last_dir = fi.absolutePath();
1918 }
1919 
1920 /** Save configuration activated by user. */
on_actionSaveSettings_triggered()1921 void MainWindow::on_actionSaveSettings_triggered()
1922 {
1923     auto cfgfile = QFileDialog::getSaveFileName(this, tr("Save settings"),
1924                                            m_last_dir.isEmpty() ? m_cfg_dir : m_last_dir,
1925                                            tr("Settings (*.conf)"));
1926 
1927     qDebug() << "File to save:" << cfgfile;
1928 
1929     if (cfgfile.isEmpty())
1930         return;
1931 
1932     if (!cfgfile.endsWith(".conf", Qt::CaseSensitive))
1933         cfgfile.append(".conf");
1934 
1935     storeSession();
1936     saveConfig(cfgfile);
1937 
1938     // store last dir
1939     QFileInfo fi(cfgfile);
1940     if (m_cfg_dir != fi.absolutePath())
1941         m_last_dir = fi.absolutePath();
1942 }
1943 
on_actionSaveWaterfall_triggered()1944 void MainWindow::on_actionSaveWaterfall_triggered()
1945 {
1946     QDateTime   dt(QDateTime::currentDateTimeUtc());
1947 
1948     // previously used location
1949     auto save_path = m_settings->value("wf_save_dir", "").toString();
1950     if (!save_path.isEmpty())
1951         save_path += "/";
1952     save_path += dt.toString("gqrx_wf_yyyyMMdd_hhmmss.png");
1953 
1954     auto wffile = QFileDialog::getSaveFileName(this, tr("Save waterfall"),
1955                                           save_path, nullptr);
1956     if (wffile.isEmpty())
1957         return;
1958 
1959     if (!ui->plotter->saveWaterfall(wffile))
1960     {
1961         QMessageBox::critical(this,
1962                               tr("Error"),
1963                               tr("There was an error saving the waterfall"));
1964     }
1965 
1966     // store the location used for the waterfall file
1967     QFileInfo fi(wffile);
1968     m_settings->setValue("wf_save_dir", fi.absolutePath());
1969 }
1970 
1971 /** Show I/Q player. */
on_actionIqTool_triggered()1972 void MainWindow::on_actionIqTool_triggered()
1973 {
1974     iq_tool->show();
1975 }
1976 
1977 
1978 /* CPlotter::NewDemodFreq() is emitted */
on_plotter_newDemodFreq(qint64 freq,qint64 delta)1979 void MainWindow::on_plotter_newDemodFreq(qint64 freq, qint64 delta)
1980 {
1981     // set RX filter
1982     rx->set_filter_offset((double) delta);
1983 
1984     // update RF freq label and channel filter offset
1985     uiDockRxOpt->setFilterOffset(delta);
1986     ui->freqCtrl->setFrequency(freq);
1987 
1988     if (rx->is_rds_decoder_active())
1989         rx->reset_rds_parser();
1990 }
1991 
1992 /* CPlotter::NewfilterFreq() is emitted or bookmark activated */
on_plotter_newFilterFreq(int low,int high)1993 void MainWindow::on_plotter_newFilterFreq(int low, int high)
1994 {   /* parameter correctness will be checked in receiver class */
1995     receiver::status retcode = rx->set_filter((double) low, (double) high, d_filter_shape);
1996 
1997     /* Update filter range of plotter, in case this slot is triggered by
1998      * switching to a bookmark */
1999     ui->plotter->setHiLowCutFrequencies(low, high);
2000 
2001     if (retcode == receiver::STATUS_OK)
2002         uiDockRxOpt->setFilterParam(low, high);
2003 }
2004 
2005 /** Full screen button or menu item toggled. */
on_actionFullScreen_triggered(bool checked)2006 void MainWindow::on_actionFullScreen_triggered(bool checked)
2007 {
2008     if (checked)
2009     {
2010         ui->statusBar->hide();
2011         showFullScreen();
2012     }
2013     else
2014     {
2015         ui->statusBar->show();
2016         showNormal();
2017     }
2018 }
2019 
2020 /** Remote control button (or menu item) toggled. */
on_actionRemoteControl_triggered(bool checked)2021 void MainWindow::on_actionRemoteControl_triggered(bool checked)
2022 {
2023     if (checked)
2024         remote->start_server();
2025     else
2026         remote->stop_server();
2027 }
2028 
2029 /** Remote control configuration button (or menu item) clicked. */
on_actionRemoteConfig_triggered()2030 void MainWindow::on_actionRemoteConfig_triggered()
2031 {
2032     auto *rcs = new RemoteControlSettings();
2033 
2034     rcs->setPort(remote->getPort());
2035     rcs->setHosts(remote->getHosts());
2036 
2037     if (rcs->exec() == QDialog::Accepted)
2038     {
2039         remote->setPort(rcs->getPort());
2040         remote->setHosts(rcs->getHosts());
2041     }
2042 
2043     delete rcs;
2044 }
2045 
2046 
2047 #define DATA_BUFFER_SIZE 48000
2048 
2049 /**
2050  * AFSK1200 decoder action triggered.
2051  *
2052  * This slot is called when the user activates the AFSK1200
2053  * action. It will create an AFSK1200 decoder window and start
2054  * and start pushing data from the receiver to it.
2055  */
on_actionAFSK1200_triggered()2056 void MainWindow::on_actionAFSK1200_triggered()
2057 {
2058 
2059     if (dec_afsk1200 != nullptr)
2060     {
2061         qDebug() << "AFSK1200 decoder already active.";
2062         dec_afsk1200->raise();
2063     }
2064     else
2065     {
2066         qDebug() << "Starting AFSK1200 decoder.";
2067 
2068         /* start sample sniffer */
2069         if (rx->start_sniffer(22050, DATA_BUFFER_SIZE) == receiver::STATUS_OK)
2070         {
2071             dec_afsk1200 = new Afsk1200Win(this);
2072             connect(dec_afsk1200, SIGNAL(windowClosed()), this, SLOT(afsk1200win_closed()));
2073             dec_afsk1200->setAttribute(Qt::WA_DeleteOnClose);
2074             dec_afsk1200->show();
2075 
2076             dec_timer->start(100);
2077         }
2078         else
2079             QMessageBox::warning(this, tr("Gqrx error"),
2080                                  tr("Error starting sample sniffer.\n"
2081                                     "Close all data decoders and try again."),
2082                                  QMessageBox::Ok, QMessageBox::Ok);
2083     }
2084 }
2085 
2086 
2087 /**
2088  * Destroy AFSK1200 decoder window got closed.
2089  *
2090  * This slot is connected to the windowClosed() signal of the AFSK1200 decoder
2091  * object. We need this to properly destroy the object, stop timeout and clean
2092  * up whatever need to be cleaned up.
2093  */
afsk1200win_closed()2094 void MainWindow::afsk1200win_closed()
2095 {
2096     /* stop cyclic processing */
2097     dec_timer->stop();
2098     rx->stop_sniffer();
2099 
2100     dec_afsk1200 = nullptr;
2101 }
2102 
2103 /** Show DXC Options. */
on_actionDX_Cluster_triggered()2104 void MainWindow::on_actionDX_Cluster_triggered()
2105 {
2106     dxc_options->show();
2107 }
2108 
2109 /**
2110  * Cyclic processing for acquiring samples from receiver and processing them
2111  * with data decoders (see dec_* objects)
2112  */
decoderTimeout()2113 void MainWindow::decoderTimeout()
2114 {
2115     float buffer[DATA_BUFFER_SIZE];
2116     unsigned int num;
2117 
2118     rx->get_sniffer_data(&buffer[0], num);
2119     if (dec_afsk1200)
2120         dec_afsk1200->process_samples(&buffer[0], num);
2121 }
2122 
setRdsDecoder(bool checked)2123 void MainWindow::setRdsDecoder(bool checked)
2124 {
2125     if (checked)
2126     {
2127         qDebug() << "Starting RDS decoder.";
2128         uiDockRDS->showEnabled();
2129         rx->start_rds_decoder();
2130         rx->reset_rds_parser();
2131         rds_timer->start(250);
2132     }
2133     else
2134     {
2135         qDebug() << "Stopping RDS decoder.";
2136         uiDockRDS->showDisabled();
2137         rx->stop_rds_decoder();
2138         rds_timer->stop();
2139     }
2140 }
2141 
onBookmarkActivated(qint64 freq,const QString & demod,int bandwidth)2142 void MainWindow::onBookmarkActivated(qint64 freq, const QString& demod, int bandwidth)
2143 {
2144     setNewFrequency(freq);
2145     selectDemod(demod);
2146 
2147     /* Check if filter is symmetric or not by checking the presets */
2148     auto mode = uiDockRxOpt->currentDemod();
2149     auto preset = uiDockRxOpt->currentFilter();
2150 
2151     int lo, hi;
2152     uiDockRxOpt->getFilterPreset(mode, preset, &lo, &hi);
2153 
2154     if(lo + hi == 0)
2155     {
2156         lo = -bandwidth / 2;
2157         hi =  bandwidth / 2;
2158     }
2159     else if(lo >= 0 && hi >= 0)
2160     {
2161         hi = lo + bandwidth;
2162     }
2163     else if(lo <= 0 && hi <= 0)
2164     {
2165         lo = hi - bandwidth;
2166     }
2167 
2168     on_plotter_newFilterFreq(lo, hi);
2169 }
2170 
setPassband(int bandwidth)2171 void MainWindow::setPassband(int bandwidth)
2172 {
2173     /* Check if filter is symmetric or not by checking the presets */
2174     auto mode = uiDockRxOpt->currentDemod();
2175     auto preset = uiDockRxOpt->currentFilter();
2176 
2177     int lo, hi;
2178     uiDockRxOpt->getFilterPreset(mode, preset, &lo, &hi);
2179 
2180     if(lo + hi == 0)
2181     {
2182         lo = -bandwidth / 2;
2183         hi =  bandwidth / 2;
2184     }
2185     else if(lo >= 0 && hi >= 0)
2186     {
2187         hi = lo + bandwidth;
2188     }
2189     else if(lo <= 0 && hi <= 0)
2190     {
2191         lo = hi - bandwidth;
2192     }
2193 
2194     remote->setPassband(lo, hi);
2195 
2196     on_plotter_newFilterFreq(lo, hi);
2197 }
2198 
2199 /** Launch Gqrx google group website. */
on_actionUserGroup_triggered()2200 void MainWindow::on_actionUserGroup_triggered()
2201 {
2202     auto res = QDesktopServices::openUrl(QUrl("https://groups.google.com/forum/#!forum/gqrx",
2203                                               QUrl::TolerantMode));
2204     if (!res)
2205         QMessageBox::warning(this, tr("Error"),
2206                              tr("Failed to open website:\n"
2207                                 "https://groups.google.com/forum/#!forum/gqrx"),
2208                              QMessageBox::Close);
2209 }
2210 
2211 /**
2212  * Show ftxt in a dialog window.
2213  */
on_actionNews_triggered()2214 void MainWindow::on_actionNews_triggered()
2215 {
2216     showSimpleTextFile(":/textfiles/news.txt", tr("Release news"));
2217 }
2218 
2219 /**
2220  * Show remote-contol.txt in a dialog window.
2221  */
on_actionRemoteProtocol_triggered()2222 void MainWindow::on_actionRemoteProtocol_triggered()
2223 {
2224     showSimpleTextFile(":/textfiles/remote-control.txt",
2225                        tr("Remote control protocol"));
2226 }
2227 
2228 /**
2229  * Show kbd-shortcuts.txt in a dialog window.
2230  */
on_actionKbdShortcuts_triggered()2231 void MainWindow::on_actionKbdShortcuts_triggered()
2232 {
2233     showSimpleTextFile(":/textfiles/kbd-shortcuts.txt",
2234                        tr("Keyboard shortcuts"));
2235 }
2236 
2237 /**
2238  * Show simple text file in a window.
2239  */
showSimpleTextFile(const QString & resource_path,const QString & window_title)2240 void MainWindow::showSimpleTextFile(const QString &resource_path,
2241                                     const QString &window_title)
2242 {
2243     QResource resource(resource_path);
2244     QFile news(resource.absoluteFilePath());
2245 
2246     if (!news.open(QIODevice::ReadOnly | QIODevice::Text))
2247     {
2248         qDebug() << "Unable to open file: " << news.fileName() <<
2249                     " because of error " << news.errorString();
2250 
2251         return;
2252     }
2253 
2254     QTextStream in(&news);
2255     auto content = in.readAll();
2256     news.close();
2257 
2258     auto *browser = new QTextBrowser();
2259     browser->setLineWrapMode(QTextEdit::NoWrap);
2260     browser->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
2261     browser->append(content);
2262     browser->adjustSize();
2263 
2264     // scroll to the beginning
2265     auto cursor = browser->textCursor();
2266     cursor.setPosition(0);
2267     browser->setTextCursor(cursor);
2268 
2269 
2270     auto *layout = new QVBoxLayout();
2271     layout->addWidget(browser);
2272 
2273     auto *dialog = new QDialog(this);
2274     dialog->setWindowTitle(window_title);
2275     dialog->setLayout(layout);
2276     dialog->resize(800, 400);
2277     dialog->exec();
2278 
2279     delete dialog;
2280     // browser and layout deleted automatically
2281 }
2282 
2283 /**
2284  * @brief Slot for handling loadConfig signals
2285  * @param cfgfile
2286  */
loadConfigSlot(const QString & cfgfile)2287 void MainWindow::loadConfigSlot(const QString &cfgfile)
2288 {
2289     loadConfig(cfgfile, cfgfile != m_settings->fileName(), cfgfile != m_settings->fileName());
2290 }
2291 
2292 /**
2293  * @brief Action: About gqrx.
2294  *
2295  * This slot is called when the user activates the
2296  * Help|About menu item (or Gqrx|About on Mac)
2297  */
on_actionAbout_triggered()2298 void MainWindow::on_actionAbout_triggered()
2299 {
2300     QMessageBox::about(this, tr("About Gqrx"),
2301         tr("<p>This is Gqrx %1</p>"
2302            "<p>Copyright (C) 2011-2020 Alexandru Csete & contributors.</p>"
2303            "<p>Gqrx is a software defined radio (SDR) receiver powered by "
2304            "<a href='http://www.gnuradio.org/'>GNU Radio</a> and the Qt toolkit. "
2305            "<p>Gqrx uses the <a href='https://osmocom.org/projects/sdr/wiki/GrOsmoSDR'>GrOsmoSDR</a> "
2306            "input source block and works with any input device supported by it, including "
2307            "Funcube Dongle, RTL-SDR, Airspy, HackRF, RFSpace, BladeRF and USRP receivers."
2308            "</p>"
2309            "<p>You can download the latest version from the "
2310            "<a href='https://gqrx.dk/'>Gqrx website</a>."
2311            "</p>"
2312            "<p>"
2313            "Gqrx is licensed under the <a href='http://www.gnu.org/licenses/gpl.html'>GNU General Public License</a>."
2314            "</p>").arg(VERSION));
2315 }
2316 
2317 /**
2318  * @brief Action: About Qt
2319  *
2320  * This slot is called when the user activates the
2321  * Help|About Qt menu item (or Gqrx|About Qt on Mac)
2322  */
on_actionAboutQt_triggered()2323 void MainWindow::on_actionAboutQt_triggered()
2324 {
2325     QMessageBox::aboutQt(this, tr("About Qt"));
2326 }
2327 
on_actionAddBookmark_triggered()2328 void MainWindow::on_actionAddBookmark_triggered()
2329 {
2330     bool ok=false;
2331     QString name;
2332     QString tags; // list of tags separated by comma
2333 
2334     // Create and show the Dialog for a new Bookmark.
2335     // Write the result into variable 'name'.
2336     {
2337         QDialog dialog(this);
2338         dialog.setWindowTitle("New bookmark");
2339 
2340         auto* LabelAndTextfieldName = new QGroupBox(&dialog);
2341         auto* label1 = new QLabel("Bookmark name:", LabelAndTextfieldName);
2342         auto* textfield = new QLineEdit(LabelAndTextfieldName);
2343         auto *layout = new QHBoxLayout;
2344         layout->addWidget(label1);
2345         layout->addWidget(textfield);
2346         LabelAndTextfieldName->setLayout(layout);
2347 
2348         auto* buttonCreateTag = new QPushButton("Create new Tag", &dialog);
2349 
2350         auto* taglist = new BookmarksTagList(&dialog, false);
2351         taglist->updateTags();
2352         taglist->DeselectAll();
2353 
2354         auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
2355                                               | QDialogButtonBox::Cancel);
2356         connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
2357         connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
2358         connect(buttonCreateTag, SIGNAL(clicked()), taglist, SLOT(AddNewTag()));
2359 
2360         auto *mainLayout = new QVBoxLayout(&dialog);
2361         mainLayout->addWidget(LabelAndTextfieldName);
2362         mainLayout->addWidget(buttonCreateTag);
2363         mainLayout->addWidget(taglist);
2364         mainLayout->addWidget(buttonBox);
2365 
2366         ok = dialog.exec();
2367         if (ok)
2368         {
2369             name = textfield->text();
2370             tags = taglist->getSelectedTagsAsString();
2371             qDebug() << "Tags: " << tags;
2372         }
2373         else
2374         {
2375             name.clear();
2376             tags.clear();
2377         }
2378     }
2379 
2380     // Add new Bookmark to Bookmarks.
2381     if(ok)
2382     {
2383         int i;
2384 
2385         BookmarkInfo info;
2386         info.frequency = ui->freqCtrl->getFrequency();
2387         info.bandwidth = ui->plotter->getFilterBw();
2388         info.modulation = uiDockRxOpt->currentDemodAsString();
2389         info.name=name;
2390         auto listTags = tags.split(",",QString::SkipEmptyParts);
2391         info.tags.clear();
2392         if (listTags.empty())
2393             info.tags.append(&Bookmarks::Get().findOrAddTag(""));
2394 
2395 
2396         for (i = 0; i < listTags.size(); ++i)
2397             info.tags.append(&Bookmarks::Get().findOrAddTag(listTags[i]));
2398 
2399         Bookmarks::Get().add(info);
2400         uiDockBookmarks->updateTags();
2401         uiDockBookmarks->updateBookmarks();
2402         ui->plotter->updateOverlay();
2403     }
2404 }
2405 
updateClusterSpots()2406 void MainWindow::updateClusterSpots()
2407 {
2408     ui->plotter->updateOverlay();
2409 }
2410 
frequencyFocusShortcut()2411 void MainWindow::frequencyFocusShortcut()
2412 {
2413     ui->freqCtrl->setFrequencyFocus();
2414 }
2415