From aa3327342db02cff2ec22123b563587c6362697c Mon Sep 17 00:00:00 2001 From: Jordan Sherer Date: Thu, 10 Oct 2019 20:40:30 -0400 Subject: [PATCH] Working notification configuration --- Configuration.cpp | 258 +++++++++++++++++++++++++++++----------------- Configuration.hpp | 3 + mainwindow.cpp | 106 ++++++------------- mainwindow.h | 2 +- 4 files changed, 201 insertions(+), 168 deletions(-) diff --git a/Configuration.cpp b/Configuration.cpp index e04a2a0..122bfff 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -534,6 +534,9 @@ private: Type2MsgGen type_2_msg_gen_; + QMap notifications_enabled_; + QMap notifications_paths_; + QStringListModel macros_; RearrangableMacrosModel next_macros_; QAction * macro_delete_action_; @@ -730,6 +733,14 @@ QAudioDeviceInfo const& Configuration::audio_output_device () const {return m_-> AudioDevice::Channel Configuration::audio_output_channel () const {return m_->audio_output_channel_;} QAudioDeviceInfo const& Configuration::notification_audio_output_device () const {return m_->notification_audio_output_device_;} AudioDevice::Channel Configuration::notification_audio_output_channel () const {return m_->notification_audio_output_channel_;} +bool Configuration::notifications_enabled() const { return m_->notifications_enabled_.values().contains(true); } +QString Configuration::notification_path(const QString &key) const { + if(!m_->notifications_enabled_.value(key, false)){ + return ""; + } + + return m_->notifications_paths_.value(key, ""); +} bool Configuration::restart_audio_input () const {return m_->restart_sound_input_device_;} bool Configuration::restart_audio_output () const {return m_->restart_sound_output_device_;} bool Configuration::restart_notification_audio_output () const {return m_->restart_notification_sound_output_device_;} @@ -1361,100 +1372,6 @@ Configuration::impl::impl (Configuration * self, QDir const& temp_directory, ui_->frequencies_table_view->insertAction (nullptr, reset_frequencies_action_); connect (reset_frequencies_action_, &QAction::triggered, this, &Configuration::impl::reset_frequencies); - // - // setup notifications table view - // - QMap notifyRows = { - {"notify_start", "JS8Call Start"}, - {"notify_cq", "CQ Message Received"}, - {"notify_hb", "HB Message Received"}, - {"notify_directed", "Directed Message Received"}, - {"notify_relay", "Relay Message Received"}, - {"notify_new_call", "New Callsign Heard"}, - {"notify_worked_call", "Worked Callsign Heard"}, - }; - - int i = 0; - auto table = ui_->notifications_table_widget; - auto header = table->horizontalHeader(); - header->setStretchLastSection(false); - header->setSectionResizeMode(0, QHeaderView::ResizeToContents); - header->setSectionResizeMode(1, QHeaderView::ResizeToContents); - header->setSectionResizeMode(2, QHeaderView::Stretch); - header->setSectionResizeMode(3, QHeaderView::ResizeToContents); - - foreach(QString key, notifyRows.keys()){ - bool enabled = false; - QString path = ""; - - QCheckBox *enabledCheckbox; - auto enabledWidget = centeredCheckBox(this, &enabledCheckbox); - enabledWidget->setMinimumWidth(100); - if(enabledCheckbox){ - enabledCheckbox->setChecked(enabled); - } - - auto expandingPolicy = QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - expandingPolicy.setHorizontalStretch(1); - - QLabel *pathLabel = new QLabel(this); - pathLabel->setText(path); - pathLabel->setSizePolicy(expandingPolicy); - - auto minimumPolicy = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - minimumPolicy.setHorizontalStretch(0); - - QWidget *buttonWidget = new QWidget(this); - buttonWidget->setSizePolicy(minimumPolicy); - - QHBoxLayout *buttonLayout = new QHBoxLayout(buttonWidget); - buttonLayout->setStretch(0, 0); - buttonLayout->setStretch(1, 0); - buttonLayout->setContentsMargins(9,1,1,1); - buttonWidget->setLayout(buttonLayout); - - QPushButton *selectFilePushButton = new QPushButton(this); - selectFilePushButton->setSizePolicy(minimumPolicy); - selectFilePushButton->setText("Select"); - buttonLayout->addWidget(selectFilePushButton); - - connect(selectFilePushButton, &QPushButton::pressed, this, [this, pathLabel](){ - auto dir = QStandardPaths::standardLocations(QStandardPaths::MusicLocation); - auto path = QFileDialog::getOpenFileName(this, "Audio Notification", dir.first(), "Wave Files (*.wav);;All files (*.*)"); - if(!path.isEmpty()){ - pathLabel->setText(path); - } - }); - - QPushButton *clearPushButton = new QPushButton(this); - clearPushButton->setSizePolicy(minimumPolicy); - clearPushButton->setText("Clear"); - buttonLayout->addWidget(clearPushButton); - - connect(clearPushButton, &QPushButton::pressed, this, [this, pathLabel, enabledCheckbox](){ - pathLabel->clear(); - enabledCheckbox->setChecked(false); - }); - - // row config - int col = 0; - table->insertRow(i); - - // - auto eventLabelItem = new QTableWidgetItem(notifyRows.value(key)); - table->setItem(i, col++, eventLabelItem); - - table->setCellWidget(i, col++, enabledWidget); - - table->setCellWidget(i, col++, pathLabel); - - table->setCellWidget(i, col++, buttonWidget); - i++; - } - for(int i = 0, len = table->columnCount(); i < len; i++){ - table->resizeColumnToContents(i); - } - // // setup stations table model & view // @@ -1667,10 +1584,110 @@ void Configuration::impl::initialize_models () next_frequencies_.frequency_list (frequencies_.frequency_list ()); next_stations_.station_list (stations_.station_list ()); + // + // setup notifications table view + // + QList> notifyRows = { + {"notify_cq", "CQ Message Received"}, + {"notify_hb", "HB Message Received"}, + {"notify_directed", "Directed Message Received"}, + {"notify_inbox", "Inbox Message Received"}, + {"notify_call_new", "New Callsign Heard"}, + {"notify_call_old", "Worked Callsign Heard"}, + }; + + int i = 0; + auto table = ui_->notifications_table_widget; + for(int i = ui_->notifications_table_widget->rowCount()-1; i >= 0; i--){ + ui_->notifications_table_widget->removeRow(i); + } + auto header = table->horizontalHeader(); + header->setStretchLastSection(false); + header->setSectionResizeMode(0, QHeaderView::ResizeToContents); + header->setSectionResizeMode(1, QHeaderView::ResizeToContents); + header->setSectionResizeMode(2, QHeaderView::Stretch); + header->setSectionResizeMode(3, QHeaderView::ResizeToContents); + foreach(auto pair, notifyRows){ + QString key = pair.first; + QString value = pair.second; + bool enabled = notifications_enabled_.value(key, false); + QString path = notifications_paths_.value(key, ""); + + QCheckBox *enabledCheckbox; + auto enabledWidget = centeredCheckBox(this, &enabledCheckbox); + enabledWidget->setMinimumWidth(100); + if(enabledCheckbox){ + enabledCheckbox->setObjectName("enabledCheckbox"); + enabledCheckbox->setChecked(enabled); + } + + auto expandingPolicy = QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + expandingPolicy.setHorizontalStretch(1); + + QLabel *pathLabel = new QLabel(this); + pathLabel->setText(path); + pathLabel->setSizePolicy(expandingPolicy); + + auto minimumPolicy = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + minimumPolicy.setHorizontalStretch(0); + + QWidget *buttonWidget = new QWidget(this); + buttonWidget->setSizePolicy(minimumPolicy); + + QHBoxLayout *buttonLayout = new QHBoxLayout(buttonWidget); + buttonLayout->setStretch(0, 0); + buttonLayout->setStretch(1, 0); + buttonLayout->setContentsMargins(9,1,1,1); + buttonWidget->setLayout(buttonLayout); + + QPushButton *selectFilePushButton = new QPushButton(this); + selectFilePushButton->setSizePolicy(minimumPolicy); + selectFilePushButton->setText("Select"); + buttonLayout->addWidget(selectFilePushButton); + + connect(selectFilePushButton, &QPushButton::pressed, this, [this, pathLabel](){ + auto dir = QStandardPaths::standardLocations(QStandardPaths::MusicLocation); + auto path = QFileDialog::getOpenFileName(this, "Audio Notification", dir.first(), "Wave Files (*.wav);;All files (*.*)"); + if(!path.isEmpty()){ + pathLabel->setText(path); + } + }); + + QPushButton *clearPushButton = new QPushButton(this); + clearPushButton->setSizePolicy(minimumPolicy); + clearPushButton->setText("Clear"); + buttonLayout->addWidget(clearPushButton); + + connect(clearPushButton, &QPushButton::pressed, this, [this, pathLabel, enabledCheckbox](){ + pathLabel->clear(); + enabledCheckbox->setChecked(false); + }); + + // row config + int col = 0; + table->insertRow(i); + + // + auto eventLabelItem = new QTableWidgetItem(value); + eventLabelItem->setData(Qt::UserRole, QVariant(key)); + table->setItem(i, col++, eventLabelItem); + + table->setCellWidget(i, col++, enabledWidget); + + table->setCellWidget(i, col++, pathLabel); + + table->setCellWidget(i, col++, buttonWidget); + i++; + } + for(int i = 0, len = table->columnCount(); i < len; i++){ + table->resizeColumnToContents(i); + } set_rig_invariants (); } + + void Configuration::impl::done (int r) { // do this here since window is still on screen at this point @@ -1980,6 +1997,23 @@ void Configuration::impl::read_settings () calibration_.slope_ppm = settings_->value ("CalibrationSlopePPM", 0.).toDouble (); pwrBandTxMemory_ = settings_->value("pwrBandTxMemory",false).toBool (); pwrBandTuneMemory_ = settings_->value("pwrBandTuneMemory",false).toBool (); + + + // notifications + notifications_enabled_.clear(); + notifications_paths_.clear(); + settings_->beginGroup("Notifications"); + { + foreach(auto group, settings_->childGroups()){ + settings_->beginGroup(group); + { + notifications_enabled_[group] = settings_->value("enabled", QVariant(false)).toBool(); + notifications_paths_[group] = settings_->value("path", QVariant("")).toString(); + } + settings_->endGroup(); + } + } + settings_->endGroup(); } void Configuration::impl::write_settings () @@ -2145,6 +2179,20 @@ void Configuration::impl::write_settings () settings_->setValue ("pwrBandTuneMemory", pwrBandTuneMemory_); settings_->setValue ("Region", QVariant::fromValue (region_)); settings_->setValue ("AutoGrid", use_dynamic_info_); + + // notifications + settings_->beginGroup("Notifications"); + { + foreach(auto key, notifications_enabled_.keys()){ + settings_->beginGroup(key); + { + settings_->setValue("enabled", QVariant(notifications_enabled_.value(key, false))); + settings_->setValue("path", QVariant(notifications_paths_.value(key, ""))); + } + settings_->endGroup(); + } + } + settings_->endGroup(); } void Configuration::impl::set_rig_invariants () @@ -2772,6 +2820,28 @@ void Configuration::impl::accept () } use_dynamic_info_ = ui_->use_dynamic_grid->isChecked(); + // notifications + for(int i = 0; i < ui_->notifications_table_widget->rowCount(); i++){ + // event + auto eventItem = ui_->notifications_table_widget->item(i, 0); + auto key = eventItem->data(Qt::UserRole).toString(); + if(key.isEmpty()){ + continue; + } + + auto enabledWidget = ui_->notifications_table_widget->cellWidget(i, 1); + QCheckBox * enabledCheckbox = enabledWidget->findChild("enabledCheckbox"); + if(enabledCheckbox){ + notifications_enabled_[key] = enabledCheckbox->isChecked(); + } + + auto pathWidget = ui_->notifications_table_widget->cellWidget(i, 2); + QLabel * pathLabel = qobject_cast(pathWidget); + if(pathLabel){ + notifications_paths_[key] = pathLabel->text(); + } + } + write_settings (); // make visible to all } diff --git a/Configuration.hpp b/Configuration.hpp index 1bd7ab0..74ddfd5 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -89,6 +89,9 @@ public: QAudioDeviceInfo const& notification_audio_output_device () const; AudioDevice::Channel notification_audio_output_channel () const; + bool notifications_enabled() const; + QString notification_path(const QString &key) const; + // These query methods should be used after a call to exec() to // determine if either the audio input or audio output stream // parameters have changed. The respective streams should be diff --git a/mainwindow.cpp b/mainwindow.cpp index dcc5d22..5aff7c0 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -515,9 +515,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect (&m_audioThread, &QThread::finished, m_soundOutput, &QObject::deleteLater); connect(this, &MainWindow::initializeNotificationAudioOutputStream, m_notification, &NotificationAudio::init); - connect(m_notification, &NotificationAudio::initialized, this, [this](){ - emit playNotification("/tmp/test.wav"); - }); connect(this, &MainWindow::playNotification, m_notification, &NotificationAudio::play); connect (&m_notificationAudioThread, &QThread::finished, m_notification, &QObject::deleteLater); @@ -843,10 +840,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect(m_wideGraph.data(), &WideGraph::qsy, this, &MainWindow::qsy); - connect(ui->tableWidgetCalls, &QTableWidget::clicked, this, [this](){ - emit playNotification("/tmp/test.wav"); - }); - decodeBusy(false); QString t1[28]={"1 uW","2 uW","5 uW","10 uW","20 uW","50 uW","100 uW","200 uW","500 uW", "1 mW","2 mW","5 mW","10 mW","20 mW","50 mW","100 mW","200 mW","500 mW", @@ -4316,7 +4309,8 @@ void MainWindow::readFromStdout() //readFromStdout // it is not processed elsewhere, so we need to just log it here. logCallActivity(cd, true); - // TODO: jsherer - notification for cq? + // notification for cq + tryNotify("cq"); } else { // convert HEARTBEAT to a directed command and process... @@ -4331,6 +4325,9 @@ void MainWindow::readFromStdout() //readFromStdout cmd.tdrift = cd.tdrift; cmd.mode = cd.mode; m_rxCommandQueue.append(cmd); + + // notification for hb + tryNotify("hb"); } } else { @@ -4647,6 +4644,13 @@ void MainWindow::logCallActivity(CallDetail d, bool spot){ m_callActivity[d.call] = d; } + // notification for new and old call + if(m_logBook.hasWorkedBefore(d.call, "")){ + tryNotify("call_old"); + } else { + tryNotify("call_new"); + } + // enqueue for spotting to psk reporter if(spot){ m_rxCallQueue.append(d); @@ -9498,6 +9502,17 @@ void MainWindow::postDecode (bool is_new, QString const& message) } } +void MainWindow::tryNotify(const QString &key){ + auto k = QString("notify_%1").arg(key); + + auto path = m_config.notification_path(k); + if(path.isEmpty()){ + return; + } + + emit playNotification(path); +} + void MainWindow::displayTransmit(){ // Transmit Activity update_dynamic_property (ui->startTxButton, "transmitting", m_transmitting); @@ -10456,18 +10471,17 @@ void MainWindow::processCommandActivity() { // log the text to directed txt log writeMsgTxt(text, d.snr); - - // we're only responding to allcall, groupcalls, and our callsign at this point, so we'll end after logging the callsigns we've heard - if (!isAllCall && !toMe && !isGroupCall) { - continue; - } - // we're only responding to allcalls if we are participating in the allcall group // but, don't avoid for heartbeats...those are technically allcalls but are processed differently if(isAllCall && m_config.avoid_allcall() && d.cmd != " HB"){ continue; } + // we're only responding to allcall, groupcalls, and our callsign at this point, so we'll end after logging the callsigns we've heard + if (!isAllCall && !toMe && !isGroupCall) { + continue; + } + ActivityDetail ad = {}; ad.isLowConfidence = false; ad.isFree = true; @@ -10530,7 +10544,8 @@ void MainWindow::processCommandActivity() { // if we've received a message to be displayed, we should bump the repeat buttons... resetAutomaticIntervalTransmissions(true, false); - // TODO: jsherer - notification for direct message? + // notification for directed message + tryNotify("directed"); } } @@ -10837,6 +10852,9 @@ void MainWindow::processCommandActivity() { addCommandToMyInbox(d); + // notification + tryNotify("inbox"); + // we haven't replaced the from with the relay path, so we have to use it for the ack if there is one reply = QString("%1 ACK").arg(calls.length() > 1 ? d.relayPath : d.from); @@ -11210,64 +11228,6 @@ QStringList MainWindow::parseRelayPathCallsigns(QString from, QString text){ return calls; } -void MainWindow::processAlertReplyForCommand(CommandDetail d, QString from, QString cmd){ - QMessageBox * msgBox = new QMessageBox(this); - msgBox->setIcon(QMessageBox::Information); - - QList calls = listCopyReverse(from.split(">")); - auto fromLabel = calls.join(" via "); - - calls.removeLast(); - - QString fromReplace = QString{}; - foreach(auto call, calls){ - fromReplace.append(" VIA "); - fromReplace.append(call); - } - - auto text = d.text; - if(!fromReplace.isEmpty()){ - text = text.replace(fromReplace, ""); - } - - auto header = QString("Message from %3 at %1 UTC (%2):"); - header = header.arg(d.utcTimestamp.time().toString()); - header = header.arg(d.freq); - header = header.arg(fromLabel); - msgBox->setText(header); - msgBox->setInformativeText(text); - - auto rb = msgBox->addButton("Reply", QMessageBox::AcceptRole); - auto db = msgBox->addButton("Discard", QMessageBox::NoRole); - - connect(msgBox, &QMessageBox::buttonClicked, this, [this, cmd, from, fromLabel, d, db, rb](QAbstractButton * btn) { - if (btn == db) { - displayCallActivity(); - return; - } - - if(btn == rb){ -#if USE_RELAY_REPLY_DIALOG - auto diag = new MessageReplyDialog(this); - diag->setWindowTitle("Message Reply"); - diag->setLabel(QString("Message to send to %1:").arg(fromLabel)); - - connect(diag, &MessageReplyDialog::accepted, this, [this, diag, from, cmd, d](){ - enqueueMessage(PriorityHigh, QString("%1%2%3").arg(from).arg(cmd).arg(diag->textValue()), -1, nullptr); - }); - - diag->show(); -#else - addMessageText(QString("%1%2[MESSAGE]").arg(from).arg(cmd), true, true); -#endif - } - }); - - // TODO: jsherer - notification for alert? - - msgBox->setModal(false); - msgBox->show(); -} void MainWindow::processSpots() { if(!m_config.spot_to_reporting_networks()){ diff --git a/mainwindow.h b/mainwindow.h index 28576e9..35cb0fe 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -433,6 +433,7 @@ private slots: void checkStartupWarnings (); void clearCallsignSelected(); void refreshTextDisplay(); + void tryNotify(const QString &key); private: Q_SIGNAL void playNotification(const QString &name); @@ -1003,7 +1004,6 @@ private: int addCommandToStorage(QString type, CommandDetail d); int getNextMessageIdForCallsign(QString callsign); QStringList parseRelayPathCallsigns(QString from, QString text); - void processAlertReplyForCommand(CommandDetail d, QString from, QString cmd); void processSpots(); void processTxQueue(); void displayActivity(bool force=false);