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