From 85cb05a8122884ca586d632fe11fc44ea7dd0ece Mon Sep 17 00:00:00 2001 From: Brian Moran <brian@trucentive.com> Date: Thu, 29 Sep 2022 08:44:31 -0700 Subject: [PATCH] edit new frequency table fields inline; refresh filter periodically; --- Configuration.cpp | 103 ++++------------- models/Bands.hpp | 2 +- models/FrequencyList.cpp | 234 +++++++++++++++++++++++++++++---------- models/FrequencyList.hpp | 39 +++++-- widgets/mainwindow.cpp | 12 +- widgets/mainwindow.h | 1 + 6 files changed, 238 insertions(+), 153 deletions(-) diff --git a/Configuration.cpp b/Configuration.cpp index 1e7f83e93..9cbd0f6e7 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -185,6 +185,7 @@ #include "item_delegates/ForeignKeyDelegate.hpp" #include "item_delegates/FrequencyDelegate.hpp" #include "item_delegates/FrequencyDeltaDelegate.hpp" +#include "item_delegates/MessageItemDelegate.hpp" #include "Transceiver/TransceiverFactory.hpp" #include "Transceiver/Transceiver.hpp" #include "models/Bands.hpp" @@ -437,31 +438,7 @@ public: }; -// -// Class MessageItemDelegate -// -// Item delegate for message entry such as free text message macros. -// -class MessageItemDelegate final - : public QStyledItemDelegate -{ -public: - explicit MessageItemDelegate (QObject * parent = nullptr) - : QStyledItemDelegate {parent} - { - } - QWidget * createEditor (QWidget * parent - , QStyleOptionViewItem const& /* option*/ - , QModelIndex const& /* index */ - ) const override - { - auto editor = new QLineEdit {parent}; - editor->setFrame (false); - editor->setValidator (new QRegularExpressionValidator {message_alphabet, editor}); - return editor; - } -}; // Internal implementation of the Configuration class. class Configuration::impl final @@ -1268,6 +1245,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network ui_->frequencies_table_view->verticalHeader ()->setResizeContentsPrecision (0); ui_->frequencies_table_view->sortByColumn (FrequencyList_v2::frequency_column, Qt::AscendingOrder); ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true); + ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::source_column, true); // delegates ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::frequency_column, new FrequencyDelegate {this}); @@ -2052,7 +2030,6 @@ TransceiverFactory::ParameterPack Configuration::impl::gather_rig_data () void Configuration::impl::accept () { // Called when OK button is clicked. - if (!validate ()) { return; // not accepting @@ -2627,59 +2604,19 @@ FrequencyList_v2::FrequencyItems Configuration::impl::read_frequencies_file (QSt FrequencyList_v2::FrequencyItems list; FrequencyList_v2_100::FrequencyItems list_v100; - // read file as json if ends with qrg.json - if (file_name.endsWith(".qrg.json", Qt::CaseInsensitive)) + // read file as json if ends with qrg.json. + if (file_name.endsWith(".qrg.json", Qt::CaseInsensitive)) + { + try { - QJsonDocument doc = QJsonDocument::fromJson(frequencies_file.readAll()); - if (doc.isNull()) - { - MessageBox::critical_message (this, tr ("Error reading frequencies file"), tr ("%1 - Invalid Format").arg (file_name)); - return list; - } - QJsonObject obj = doc.object(); - if (obj.isEmpty()) - { - MessageBox::critical_message (this, tr ("Error reading frequencies file"), tr ("%1 - Information Missing ").arg (file_name)); - return list; - } - QJsonArray arr = obj["frequencies"].toArray(); - if (arr.isEmpty()) - { - MessageBox::critical_message (this, tr ("Error reading frequencies file"), tr ("No Frequencies were found")); - return list; - } - int valid_entry_count = 0; - int skipped_entry_count = 0; - for (auto const &item: arr) - { - QString mode_s, region_s; - QJsonObject obj = item.toObject(); - FrequencyList_v2::Item freq; - region_s = obj["region"].toString(); - mode_s = obj["mode"].toString(); - - freq.frequency_ = obj["frequency"].toString().toDouble() * 1e6; - freq.region_ = IARURegions::value(region_s); - freq.mode_ = Modes::value(mode_s); - freq.description_ = obj["description"].toString(); - freq.source_ = obj["source"].toString(); - freq.start_time_ = QDateTime::fromString(obj["start_time"].toString(), Qt::ISODate); - freq.end_time_ = QDateTime::fromString(obj["end_time"].toString(), Qt::ISODate); - //MessageBox::critical_message (this, tr ("Entry"), tr ("Entry: %1 ").arg(freq.toString()+"[sane:" +freq.isSane() + "] [region:" + obj["region"].toString() + "] [mode:" + obj["mode"].toString()+"] ")); - if ((freq.mode_ != Modes::ALL || QString::compare("ALL", mode_s)) && - (freq.region_ != IARURegions::ALL || QString::compare("ALL", region_s, Qt::CaseInsensitive)) && - freq.isSane()) - { - list.push_back(freq); - valid_entry_count++; - } else - skipped_entry_count++; - } - MessageBox::information_message(this, tr("Loaded Frequencies from %1").arg(file_name), - tr("Entries Valid/Skipped %1").arg(QString::number(valid_entry_count) + "/" + - QString::number(skipped_entry_count))); - return list; - } + list = FrequencyList_v2::from_json_file(&frequencies_file); + } + catch (ReadFileException const &e) + { + MessageBox::critical_message(this, tr("Error reading frequency file"), e.message_); + } + return list; + } quint32 magic; ids >> magic; @@ -2727,9 +2664,13 @@ void Configuration::impl::save_frequencies () auto file_name = QFileDialog::getSaveFileName (this, tr ("Save Working Frequencies"), writeable_data_dir_.absolutePath (), tr ("Frequency files (*.qrg *.qrg.json);;All files (*.*)")); if (!file_name.isNull ()) { + bool b_write_json = file_name.endsWith(".qrg.json", Qt::CaseInsensitive); + QFile frequencies_file {file_name}; frequencies_file.open (QFile::WriteOnly); + QDataStream ods {&frequencies_file}; + auto selection_model = ui_->frequencies_table_view->selectionModel (); if (selection_model->hasSelection () && MessageBox::Yes == MessageBox::query_message (this @@ -2739,9 +2680,9 @@ void Configuration::impl::save_frequencies () "Click No to save all."))) { selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - if (file_name.endsWith(".qrg.json", Qt::CaseInsensitive)) + if (b_write_json) { - next_frequencies_.to_json_stream(&ods, "0x" + QString::number(qrg_magic, 16).toUpper(), + next_frequencies_.to_json_file(&frequencies_file, "0x" + QString::number(qrg_magic, 16).toUpper(), "0x" + QString::number(qrg_version, 16).toUpper(), next_frequencies_.frequency_list(selection_model->selectedRows())); } else @@ -2751,9 +2692,9 @@ void Configuration::impl::save_frequencies () } else { - if (file_name.endsWith(".qrg.json", Qt::CaseInsensitive)) + if (b_write_json) { - next_frequencies_.to_json_stream(&ods, + next_frequencies_.to_json_file(&frequencies_file, "0x" + QString::number(qrg_magic, 16).toUpper(), "0x" + QString::number(qrg_version, 16).toUpper(), next_frequencies_.frequency_list()); diff --git a/models/Bands.hpp b/models/Bands.hpp index 170d4c756..77ae5766b 100644 --- a/models/Bands.hpp +++ b/models/Bands.hpp @@ -24,7 +24,7 @@ // // Implements the QAbstractTableModel interface as an immutable table // where rows are bands and columns are band name, lower frequency -// limit and, upper ferquency limit respectively. +// limit and, upper frequency limit respectively. // class Bands final : public QAbstractTableModel diff --git a/models/FrequencyList.cpp b/models/FrequencyList.cpp index e04a7a184..b5d12e859 100644 --- a/models/FrequencyList.cpp +++ b/models/FrequencyList.cpp @@ -23,11 +23,13 @@ #include <QJsonArray> #include <QCoreApplication> #include <QFile> +#include <QException> #include "Radio.hpp" #include "Bands.hpp" #include "pimpl_impl.hpp" #include "revision_utils.hpp" +#include "Logger.hpp" #include "moc_FrequencyList.cpp" @@ -378,7 +380,6 @@ QString FrequencyList_v2::Item::toString () const << end_time_.toString(Qt::ISODate) << ", " << description_ << ", " << source_ << ')'; - return string; } @@ -423,6 +424,7 @@ public: , bands_ {bands} , region_filter_ {IARURegions::ALL} , mode_filter_ {Modes::ALL} + , filter_on_time_ {false} { } @@ -449,6 +451,7 @@ public: FrequencyItems frequency_list_; Region region_filter_; Mode mode_filter_; + bool filter_on_time_; }; FrequencyList_v2::FrequencyList_v2 (Bands const * bands, QObject * parent) @@ -564,7 +567,7 @@ bool FrequencyList_v2::removeDisjointRows (QModelIndexList rows) // We must work with source model indexes because we don't want row // removes to invalidate model indexes we haven't yet processed. We - // achieve that by processing them in decending row order. + // achieve that by processing them in descending row order. for (int r = 0; r < rows.size (); ++r) { rows[r] = mapToSource (rows[r]); @@ -585,10 +588,16 @@ bool FrequencyList_v2::removeDisjointRows (QModelIndexList rows) return result; } -void FrequencyList_v2::filter (Region region, Mode mode) +void FrequencyList_v2::filter (Region region, Mode mode, bool filter_on_time) { m_->region_filter_ = region; m_->mode_filter_ = mode; + m_->filter_on_time_ = filter_on_time; + invalidateFilter (); +} + +void FrequencyList_v2::filter_refresh () +{ invalidateFilter (); } @@ -606,6 +615,11 @@ bool FrequencyList_v2::filterAcceptsRow (int source_row, QModelIndex const& /* p result = (Modes::ALL == item.mode_ && m_->mode_filter_ != Modes::FreqCal) || m_->mode_filter_ == item.mode_; } + if (result && m_->filter_on_time_) + { + result = (!item.start_time_.isValid() || item.start_time_ <= QDateTime::currentDateTimeUtc ()) && + (!item.end_time_.isValid() || item.end_time_ >= QDateTime::currentDateTimeUtc ()); + } return result; } @@ -796,7 +810,7 @@ QVariant FrequencyList_v2::impl::data (QModelIndex const& index, int role) const item = Qt::AlignRight + Qt::AlignVCenter; break; } - break; + break; case description_column: switch (role) @@ -810,14 +824,14 @@ QVariant FrequencyList_v2::impl::data (QModelIndex const& index, int role) const case Qt::ToolTipRole: case Qt::AccessibleDescriptionRole: - item = tr ("Description"); + item = tr("Description"); break; case Qt::TextAlignmentRole: item = Qt::AlignLeft + Qt::AlignVCenter; break; } - break; + break; case source_column: switch (role) @@ -826,9 +840,6 @@ QVariant FrequencyList_v2::impl::data (QModelIndex const& index, int role) const case Qt::DisplayRole: case Qt::EditRole: case Qt::AccessibleTextRole: - item = frequency_item.start_time_ == frequency_item.end_time_ - ? tr ("Equal") - : tr ("NOTEQUAL"); item = frequency_item.source_; break; @@ -847,6 +858,8 @@ QVariant FrequencyList_v2::impl::data (QModelIndex const& index, int role) const switch (role) { case SortRole: + item = frequency_item.start_time_; + break; case Qt::DisplayRole: case Qt::EditRole: case Qt::AccessibleTextRole: @@ -868,6 +881,8 @@ QVariant FrequencyList_v2::impl::data (QModelIndex const& index, int role) const switch (role) { case SortRole: + item = frequency_item.end_time_; + break; case Qt::DisplayRole: case Qt::EditRole: case Qt::AccessibleTextRole: @@ -902,45 +917,111 @@ bool FrequencyList_v2::impl::setData (QModelIndex const& model_index, QVariant c roles << role; auto& item = frequency_list_[row]; - switch (model_index.column ()) + switch (model_index.column()) { - case region_column: - { - auto region = IARURegions::value (value.toString ()); - if (region != item.region_) - { - item.region_ = region; - Q_EMIT dataChanged (model_index, model_index, roles); - changed = true; - } - } - break; - - case mode_column: - { - auto mode = Modes::value (value.toString ()); - if (mode != item.mode_) - { - item.mode_ = mode; - Q_EMIT dataChanged (model_index, model_index, roles); - changed = true; - } - } - break; - - case frequency_column: - if (value.canConvert<Frequency> ()) + case region_column: { - Radio::Frequency frequency {qvariant_cast <Radio::Frequency> (value)}; - if (frequency != item.frequency_) + auto region = IARURegions::value(value.toString()); + if (region != item.region_) { - item.frequency_ = frequency; - // mark derived column (1) changed as well - Q_EMIT dataChanged (index (model_index.row (), 1), model_index, roles); + item.region_ = region; + Q_EMIT dataChanged(model_index, model_index, roles); changed = true; } } break; + + case mode_column: + { + auto mode = Modes::value(value.toString()); + if (mode != item.mode_) + { + item.mode_ = mode; + Q_EMIT dataChanged(model_index, model_index, roles); + changed = true; + } + } + break; + + case frequency_column: + { + if (value.canConvert<Frequency>()) + { + Radio::Frequency frequency{qvariant_cast<Radio::Frequency>(value)}; + if (frequency != item.frequency_) + { + item.frequency_ = frequency; + // mark derived column (1) changed as well + Q_EMIT dataChanged(index(model_index.row(), 1), model_index, roles); + changed = true; + } + } + } + break; + + case description_column: + { + if (value.toString() != item.description_) + { + item.description_ = value.toString(); + Q_EMIT dataChanged(model_index, model_index, roles); + changed = true; + } + } + break; + + case source_column: + { + if (value.toString() != item.source_) + { + item.source_ = value.toString(); + Q_EMIT dataChanged(model_index, model_index, roles); + changed = true; + } + } + break; + + case start_time_column: + { + QDateTime start_time = QDateTime::fromString(value.toString(), Qt::ISODate); + LOG_INFO(QString{"start_time = %1 - isEmpty %2"}.arg(value.toString()).arg(value.toString().isEmpty())); + if (value.toString().isEmpty()) + { // empty string is valid + start_time = QDateTime(); + } + if (start_time.isValid() || start_time.isNull()) + { + item.start_time_ = start_time; + if (item.end_time_.isValid() && !item.start_time_.isNull() && item.end_time_ < item.start_time_) + { + item.end_time_ = item.start_time_; + } + Q_EMIT dataChanged(model_index, index(model_index.row(), end_time_column), roles); + changed = true; + } + } + break; + + case end_time_column: + { + QDateTime end_time = QDateTime::fromString(value.toString(), Qt::ISODate); + if (value.toString().isEmpty()) + { // empty string is valid + end_time = QDateTime(); + } + if (end_time.isValid() || end_time.isNull()) + { + item.end_time_ = end_time; + if (item.start_time_.isValid() && !item.end_time_.isNull() && end_time <= item.start_time_) + { + item.start_time_ = end_time; + } + Q_EMIT dataChanged(index(model_index.row(), start_time_column), model_index, roles); + changed = true; + } + } + break; + } } @@ -1104,8 +1185,57 @@ auto FrequencyList_v2::all_bands (Region region, Mode mode) const -> BandSet return result; } +FrequencyList_v2::FrequencyItems FrequencyList_v2::from_json_file(QFile *input_file) +{ + FrequencyList_v2::FrequencyItems list; + QJsonDocument doc = QJsonDocument::fromJson(input_file->readAll()); + if (doc.isNull()) + { + throw ReadFileException {tr ("Failed to parse JSON file")}; + } + QJsonObject obj = doc.object(); + if (obj.isEmpty()) + { + throw ReadFileException{tr("Information Missing")}; + } + QJsonArray arr = obj["frequencies"].toArray(); + if (arr.isEmpty()) + { + throw ReadFileException{tr ("No Frequencies were found")}; + } + int valid_entry_count = 0; + int skipped_entry_count = 0; + for (auto const &item: arr) + { + QString mode_s, region_s; + QJsonObject obj = item.toObject(); + FrequencyList_v2::Item freq; + region_s = obj["region"].toString(); + mode_s = obj["mode"].toString(); + + freq.frequency_ = obj["frequency"].toString().toDouble() * 1e6; + freq.region_ = IARURegions::value(region_s); + freq.mode_ = Modes::value(mode_s); + freq.description_ = obj["description"].toString(); + freq.source_ = obj["source"].toString(); + freq.start_time_ = QDateTime::fromString(obj["start_time"].toString(), Qt::ISODate); + freq.end_time_ = QDateTime::fromString(obj["end_time"].toString(), Qt::ISODate); + if ((freq.mode_ != Modes::ALL || QString::compare("ALL", mode_s)) && + (freq.region_ != IARURegions::ALL || QString::compare("ALL", region_s, Qt::CaseInsensitive)) && + freq.isSane()) + { + list.push_back(freq); + valid_entry_count++; + } else + skipped_entry_count++; + } + //MessageBox::information_message(this, tr("Loaded Frequencies from %1").arg(file_name), + // tr("Entries Valid/Skipped %1").arg(QString::number(valid_entry_count) + "/" + + // QString::number(skipped_entry_count))); + return list; +} // write JSON format to a file -void FrequencyList_v2::to_json_stream(QDataStream *ods, QString magic_s, QString version_s, +void FrequencyList_v2::to_json_file(QFile *output_file, QString magic_s, QString version_s, FrequencyItems const &frequency_items) { QJsonObject jobject{ @@ -1122,25 +1252,7 @@ void FrequencyList_v2::to_json_stream(QDataStream *ods, QString magic_s, QString jobject["frequencies"] = array; QJsonDocument d = QJsonDocument(jobject); - ods->writeRawData(d.toJson().data(), d.toJson().size()); -} - -QTextStream& qStdOut() -{ - static QTextStream ts( stdout ); - return ts; -} - -FrequencyList_v2::FrequencyItems FrequencyList_v2::from_json_file(QFile *input_file) -{ - // attempt to read the file as JSON - FrequencyList_v2::FrequencyItems list; - QByteArray jsonData = input_file->readAll(); - QJsonDocument jsonDoc(QJsonDocument::fromJson(jsonData)); - QJsonArray array = jsonDoc.object().value("frequencies").toArray(); - qStdOut() << "Frequencies read"; - qStdOut() << array.count(); - return list; + output_file->write(d.toJson()); } // previous version 100 of the FrequencyList_v2 class diff --git a/models/FrequencyList.hpp b/models/FrequencyList.hpp index 40002762c..75b4adcf9 100644 --- a/models/FrequencyList.hpp +++ b/models/FrequencyList.hpp @@ -9,6 +9,7 @@ #include <QJsonObject> #include <QJsonDocument> #include <QFile> +#include <QException> #include "Radio.hpp" #include "IARURegions.hpp" @@ -56,19 +57,19 @@ public: Frequency frequency_; Mode mode_; Region region_; - QString toString () const; - bool isSane() const; - QJsonObject toJson () const; QString description_; QString source_; QDateTime start_time_; QDateTime end_time_; + QString toString () const; + bool isSane() const; + QJsonObject toJson () const; }; using FrequencyItems = QList<Item>; using BandSet = QSet<QString>; - enum Column {region_column, mode_column, frequency_column, frequency_mhz_column, description_column, start_time_column, end_time_column, source_column, SENTINAL}; + enum Column {region_column, mode_column, frequency_column, frequency_mhz_column, description_column, start_time_column, end_time_column, source_column, SENTINAL}; // an iterator that meets the requirements of the C++ for range statement class const_iterator @@ -89,7 +90,6 @@ public: private: FrequencyList_v2 const * parent_; int row_; - //qint32 qrg_version_; }; explicit FrequencyList_v2 (Bands const *, QObject * parent = nullptr); @@ -101,8 +101,8 @@ public: FrequencyItems const& frequency_list () const; FrequencyItems frequency_list (QModelIndexList const&) const; void frequency_list_merge (FrequencyItems const&); - void to_json_stream(QDataStream *, QString, QString, FrequencyItems const&); - FrequencyList_v2::FrequencyItems from_json_file(QFile *); + void to_json_file(QFile *, QString, QString, FrequencyItems const&); + static FrequencyItems from_json_file(QFile *); // Iterators for the sorted and filtered items // @@ -131,7 +131,7 @@ public: int best_working_frequency (QString const& band) const; // Set filter - Q_SLOT void filter (Region, Mode); + Q_SLOT void filter (Region, Mode, bool); // Reset Q_SLOT void reset_to_defaults (); @@ -144,6 +144,9 @@ public: // Proxy API bool filterAcceptsRow (int source_row, QModelIndex const& parent) const override; + // Refresh the filter based on the current filter settings (underlying data may have changed) + void filter_refresh (); + // Custom roles. static int constexpr SortRole = Qt::UserRole; @@ -158,7 +161,11 @@ bool operator == (FrequencyList_v2::Item const& lhs, FrequencyList_v2::Item cons return lhs.frequency_ == rhs.frequency_ && lhs.region_ == rhs.region_ - && lhs.mode_ == rhs.mode_; + && lhs.mode_ == rhs.mode_ + && lhs.description_ == rhs.description_ + && lhs.source_ == rhs.source_ + && lhs.start_time_ == rhs.start_time_ + && lhs.end_time_ == rhs.end_time_; } QDataStream& operator << (QDataStream&, FrequencyList_v2::Item const&); @@ -224,4 +231,18 @@ QDataStream& operator >> (QDataStream&, FrequencyList::Item&); Q_DECLARE_METATYPE (FrequencyList::Item); Q_DECLARE_METATYPE (FrequencyList::FrequencyItems); +class ReadFileException : public QException { +public: + ReadFileException (QString const& message) + : message_ {message} + { + } + + void raise () const override { throw *this; } + ReadFileException * clone () const override { return new ReadFileException {*this}; } + + QString filename_; + QString message_; +}; + #endif diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 29cccad88..6de200886 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -1035,6 +1035,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, m_wideGraph->setMode(m_mode); connect (&minuteTimer, &QTimer::timeout, this, &MainWindow::on_the_minute); + connect (&minuteTimer, &QTimer::timeout, this, &MainWindow::invalidate_frequencies_filter); + minuteTimer.setSingleShot (true); minuteTimer.start (ms_minute_error () + 60 * 1000); @@ -1079,6 +1081,14 @@ void MainWindow::splash_done () m_splash && m_splash->close (); } +void MainWindow::invalidate_frequencies_filter () +{ + // every interval, invalidate the frequency filter, so that if any + // working frequency goes in/out of scope, we pick it up. + m_config.frequencies ()->filter_refresh (); + ui->bandComboBox->update (); +} + void MainWindow::on_the_minute () { if (minuteTimer.isSingleShot ()) @@ -7159,7 +7169,7 @@ void MainWindow::on_actionFreqCal_triggered() void MainWindow::switch_mode (Mode mode) { m_fastGraph->setMode(m_mode); - m_config.frequencies ()->filter (m_config.region (), mode); + m_config.frequencies ()->filter (m_config.region (), mode, true); // filter on current time auto const& row = m_config.frequencies ()->best_working_frequency (m_freqNominal); ui->bandComboBox->setCurrentIndex (row); if (row >= 0) { diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h index 0c7cf3647..dca2e9cbb 100644 --- a/widgets/mainwindow.h +++ b/widgets/mainwindow.h @@ -824,6 +824,7 @@ private: void subProcessError (QProcess *, QProcess::ProcessError); void statusUpdate () const; void update_watchdog_label (); + void invalidate_frequencies_filter (); void on_the_minute (); void add_child_to_event_filter (QObject *); void remove_child_from_event_filter (QObject *);