Working notification configuration

This commit is contained in:
Jordan Sherer 2019-10-10 20:40:30 -04:00
parent fe31f54b97
commit aa3327342d
4 changed files with 201 additions and 168 deletions

View File

@ -534,6 +534,9 @@ private:
Type2MsgGen type_2_msg_gen_; Type2MsgGen type_2_msg_gen_;
QMap<QString, bool> notifications_enabled_;
QMap<QString, QString> notifications_paths_;
QStringListModel macros_; QStringListModel macros_;
RearrangableMacrosModel next_macros_; RearrangableMacrosModel next_macros_;
QAction * macro_delete_action_; 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_;} 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_;} 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_;} 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_input () const {return m_->restart_sound_input_device_;}
bool Configuration::restart_audio_output () const {return m_->restart_sound_output_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_;} 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_); ui_->frequencies_table_view->insertAction (nullptr, reset_frequencies_action_);
connect (reset_frequencies_action_, &QAction::triggered, this, &Configuration::impl::reset_frequencies); connect (reset_frequencies_action_, &QAction::triggered, this, &Configuration::impl::reset_frequencies);
//
// setup notifications table view
//
QMap<QString, QString> 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 // setup stations table model & view
// //
@ -1667,10 +1584,110 @@ void Configuration::impl::initialize_models ()
next_frequencies_.frequency_list (frequencies_.frequency_list ()); next_frequencies_.frequency_list (frequencies_.frequency_list ());
next_stations_.station_list (stations_.station_list ()); next_stations_.station_list (stations_.station_list ());
//
// setup notifications table view
//
QList<QPair<QString, QString>> 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 (); set_rig_invariants ();
} }
void Configuration::impl::done (int r) void Configuration::impl::done (int r)
{ {
// do this here since window is still on screen at this point // 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 (); calibration_.slope_ppm = settings_->value ("CalibrationSlopePPM", 0.).toDouble ();
pwrBandTxMemory_ = settings_->value("pwrBandTxMemory",false).toBool (); pwrBandTxMemory_ = settings_->value("pwrBandTxMemory",false).toBool ();
pwrBandTuneMemory_ = settings_->value("pwrBandTuneMemory",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 () void Configuration::impl::write_settings ()
@ -2145,6 +2179,20 @@ void Configuration::impl::write_settings ()
settings_->setValue ("pwrBandTuneMemory", pwrBandTuneMemory_); settings_->setValue ("pwrBandTuneMemory", pwrBandTuneMemory_);
settings_->setValue ("Region", QVariant::fromValue (region_)); settings_->setValue ("Region", QVariant::fromValue (region_));
settings_->setValue ("AutoGrid", use_dynamic_info_); 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 () void Configuration::impl::set_rig_invariants ()
@ -2772,6 +2820,28 @@ void Configuration::impl::accept ()
} }
use_dynamic_info_ = ui_->use_dynamic_grid->isChecked(); 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<QCheckBox*>("enabledCheckbox");
if(enabledCheckbox){
notifications_enabled_[key] = enabledCheckbox->isChecked();
}
auto pathWidget = ui_->notifications_table_widget->cellWidget(i, 2);
QLabel * pathLabel = qobject_cast<QLabel*>(pathWidget);
if(pathLabel){
notifications_paths_[key] = pathLabel->text();
}
}
write_settings (); // make visible to all write_settings (); // make visible to all
} }

View File

@ -89,6 +89,9 @@ public:
QAudioDeviceInfo const& notification_audio_output_device () const; QAudioDeviceInfo const& notification_audio_output_device () const;
AudioDevice::Channel notification_audio_output_channel () 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 // These query methods should be used after a call to exec() to
// determine if either the audio input or audio output stream // determine if either the audio input or audio output stream
// parameters have changed. The respective streams should be // parameters have changed. The respective streams should be

View File

@ -515,9 +515,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
connect (&m_audioThread, &QThread::finished, m_soundOutput, &QObject::deleteLater); connect (&m_audioThread, &QThread::finished, m_soundOutput, &QObject::deleteLater);
connect(this, &MainWindow::initializeNotificationAudioOutputStream, m_notification, &NotificationAudio::init); 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(this, &MainWindow::playNotification, m_notification, &NotificationAudio::play);
connect (&m_notificationAudioThread, &QThread::finished, m_notification, &QObject::deleteLater); 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(m_wideGraph.data(), &WideGraph::qsy, this, &MainWindow::qsy);
connect(ui->tableWidgetCalls, &QTableWidget::clicked, this, [this](){
emit playNotification("/tmp/test.wav");
});
decodeBusy(false); decodeBusy(false);
QString t1[28]={"1 uW","2 uW","5 uW","10 uW","20 uW","50 uW","100 uW","200 uW","500 uW", 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", "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. // it is not processed elsewhere, so we need to just log it here.
logCallActivity(cd, true); logCallActivity(cd, true);
// TODO: jsherer - notification for cq? // notification for cq
tryNotify("cq");
} else { } else {
// convert HEARTBEAT to a directed command and process... // convert HEARTBEAT to a directed command and process...
@ -4331,6 +4325,9 @@ void MainWindow::readFromStdout() //readFromStdout
cmd.tdrift = cd.tdrift; cmd.tdrift = cd.tdrift;
cmd.mode = cd.mode; cmd.mode = cd.mode;
m_rxCommandQueue.append(cmd); m_rxCommandQueue.append(cmd);
// notification for hb
tryNotify("hb");
} }
} else { } else {
@ -4647,6 +4644,13 @@ void MainWindow::logCallActivity(CallDetail d, bool spot){
m_callActivity[d.call] = d; 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 // enqueue for spotting to psk reporter
if(spot){ if(spot){
m_rxCallQueue.append(d); 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(){ void MainWindow::displayTransmit(){
// Transmit Activity // Transmit Activity
update_dynamic_property (ui->startTxButton, "transmitting", m_transmitting); update_dynamic_property (ui->startTxButton, "transmitting", m_transmitting);
@ -10456,18 +10471,17 @@ void MainWindow::processCommandActivity() {
// log the text to directed txt log // log the text to directed txt log
writeMsgTxt(text, d.snr); 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 // 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 // but, don't avoid for heartbeats...those are technically allcalls but are processed differently
if(isAllCall && m_config.avoid_allcall() && d.cmd != " HB"){ if(isAllCall && m_config.avoid_allcall() && d.cmd != " HB"){
continue; 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 = {}; ActivityDetail ad = {};
ad.isLowConfidence = false; ad.isLowConfidence = false;
ad.isFree = true; 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... // if we've received a message to be displayed, we should bump the repeat buttons...
resetAutomaticIntervalTransmissions(true, false); resetAutomaticIntervalTransmissions(true, false);
// TODO: jsherer - notification for direct message? // notification for directed message
tryNotify("directed");
} }
} }
@ -10837,6 +10852,9 @@ void MainWindow::processCommandActivity() {
addCommandToMyInbox(d); 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 // 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); 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; return calls;
} }
void MainWindow::processAlertReplyForCommand(CommandDetail d, QString from, QString cmd){
QMessageBox * msgBox = new QMessageBox(this);
msgBox->setIcon(QMessageBox::Information);
QList<QString> calls = listCopyReverse<QString>(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() { void MainWindow::processSpots() {
if(!m_config.spot_to_reporting_networks()){ if(!m_config.spot_to_reporting_networks()){

View File

@ -433,6 +433,7 @@ private slots:
void checkStartupWarnings (); void checkStartupWarnings ();
void clearCallsignSelected(); void clearCallsignSelected();
void refreshTextDisplay(); void refreshTextDisplay();
void tryNotify(const QString &key);
private: private:
Q_SIGNAL void playNotification(const QString &name); Q_SIGNAL void playNotification(const QString &name);
@ -1003,7 +1004,6 @@ private:
int addCommandToStorage(QString type, CommandDetail d); int addCommandToStorage(QString type, CommandDetail d);
int getNextMessageIdForCallsign(QString callsign); int getNextMessageIdForCallsign(QString callsign);
QStringList parseRelayPathCallsigns(QString from, QString text); QStringList parseRelayPathCallsigns(QString from, QString text);
void processAlertReplyForCommand(CommandDetail d, QString from, QString cmd);
void processSpots(); void processSpots();
void processTxQueue(); void processTxQueue();
void displayActivity(bool force=false); void displayActivity(bool force=false);