diff --git a/models/CabrilloLog.cpp b/models/CabrilloLog.cpp index f447bca79..965d6f957 100644 --- a/models/CabrilloLog.cpp +++ b/models/CabrilloLog.cpp @@ -52,7 +52,7 @@ CabrilloLog::impl::impl (Configuration const * configuration) SQL_error_check (export_query_, &QSqlQuery::prepare, "SELECT frequency, \"when\", exchange_sent, call, exchange_rcvd FROM cabrillo_log ORDER BY \"when\""); - setEditStrategy (QSqlTableModel::OnManualSubmit); + setEditStrategy (QSqlTableModel::OnRowChange); setTable ("cabrillo_log"); setHeaderData (fieldIndex ("frequency"), Qt::Horizontal, tr ("Freq(kHz)")); setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)")); @@ -73,7 +73,7 @@ CabrilloLog::~CabrilloLog () { } -QAbstractItemModel * CabrilloLog::model () +QSqlTableModel * CabrilloLog::model () { return &*m_; } @@ -96,7 +96,6 @@ namespace bool CabrilloLog::add_QSO (Frequency frequency, QDateTime const& when, QString const& call , QString const& exchange_sent, QString const& exchange_received) { - ConditionalTransaction transaction {*m_}; auto record = m_->record (); record.setValue ("frequency", frequency / 1000ull); // kHz if (!when.isNull ()) @@ -111,13 +110,12 @@ bool CabrilloLog::add_QSO (Frequency frequency, QDateTime const& when, QString c set_value_maybe_null (record, "exchange_sent", exchange_sent); set_value_maybe_null (record, "exchange_rcvd", exchange_received); set_value_maybe_null (record, "band", m_->configuration_->bands ()->find (frequency)); - SQL_error_check (*m_, &QSqlTableModel::insertRecord, -1, record); - if (!transaction.submit (false)) + auto ok = m_->insertRecord (-1, record); + if (ok) { - transaction.revert (); - return false; + m_->select (); // to refresh views } - return true; + return ok; } bool CabrilloLog::dupe (Frequency frequency, QString const& call) const @@ -133,9 +131,12 @@ void CabrilloLog::reset () { if (m_->rowCount ()) { + m_->setEditStrategy (QSqlTableModel::OnManualSubmit); ConditionalTransaction transaction {*m_}; SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {}); transaction.submit (); + m_->select (); // to refresh views + m_->setEditStrategy (QSqlTableModel::OnRowChange); } } diff --git a/models/CabrilloLog.hpp b/models/CabrilloLog.hpp index e328d0633..43b9e8f94 100644 --- a/models/CabrilloLog.hpp +++ b/models/CabrilloLog.hpp @@ -8,7 +8,7 @@ class Configuration; class QDateTime; class QString; -class QAbstractItemModel; +class QSqlTableModel; class QTextStream; class CabrilloLog final @@ -25,7 +25,7 @@ public: , QString const& report_sent, QString const& report_received); bool dupe (Frequency, QString const& call) const; - QAbstractItemModel * model (); + QSqlTableModel * model (); void reset (); void export_qsos (QTextStream&) const; diff --git a/models/FoxLog.cpp b/models/FoxLog.cpp index 43cdd2b8f..1503e5662 100644 --- a/models/FoxLog.cpp +++ b/models/FoxLog.cpp @@ -43,7 +43,7 @@ FoxLog::impl::impl () SQL_error_check (dupe_query_, &QSqlQuery::prepare, "SELECT COUNT(*) FROM fox_log WHERE call = :call AND band = :band"); - setEditStrategy (QSqlTableModel::OnManualSubmit); + setEditStrategy (QSqlTableModel::OnRowChange); setTable ("fox_log"); setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)")); setHeaderData (fieldIndex ("call"), Qt::Horizontal, tr ("Call")); @@ -62,7 +62,7 @@ FoxLog::~FoxLog () { } -QAbstractItemModel * FoxLog::model () +QSqlTableModel * FoxLog::model () { return &*m_; } @@ -86,7 +86,6 @@ bool FoxLog::add_QSO (QDateTime const& when, QString const& call, QString const& , QString const& report_sent, QString const& report_received , QString const& band) { - ConditionalTransaction transaction {*m_}; auto record = m_->record (); if (!when.isNull ()) { @@ -101,13 +100,12 @@ bool FoxLog::add_QSO (QDateTime const& when, QString const& call, QString const& set_value_maybe_null (record, "report_sent", report_sent); set_value_maybe_null (record, "report_rcvd", report_received); set_value_maybe_null (record, "band", band); - SQL_error_check (*m_, &QSqlTableModel::insertRecord, -1, record); - if (!transaction.submit (false)) + auto ok = m_->insertRecord (-1, record); + if (ok) { - transaction.revert (); - return false; + m_->select (); // to refresh views } - return true; + return ok; } bool FoxLog::dupe (QString const& call, QString const& band) const @@ -123,8 +121,11 @@ void FoxLog::reset () { if (m_->rowCount ()) { + m_->setEditStrategy (QSqlTableModel::OnManualSubmit); ConditionalTransaction transaction {*m_}; SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {}); transaction.submit (); + m_->select (); // to refresh views + m_->setEditStrategy (QSqlTableModel::OnRowChange); } } diff --git a/models/FoxLog.hpp b/models/FoxLog.hpp index 028f8205c..caa8e358f 100644 --- a/models/FoxLog.hpp +++ b/models/FoxLog.hpp @@ -6,7 +6,7 @@ class QDateTime; class QString; -class QAbstractItemModel; +class QSqlTableModel; class FoxLog final : private boost::noncopyable @@ -21,7 +21,7 @@ public: , QString const& band); bool dupe (QString const& call, QString const& band) const; - QAbstractItemModel * model (); + QSqlTableModel * model (); void reset (); private: diff --git a/qt_db_helpers.hpp b/qt_db_helpers.hpp index da823b0cb..49a082f72 100644 --- a/qt_db_helpers.hpp +++ b/qt_db_helpers.hpp @@ -30,32 +30,51 @@ public: { model_.database ().transaction (); } + bool submit (bool throw_on_error = true) { - Q_ASSERT (model_.isDirty ()); bool ok {true}; if (throw_on_error) { - SQL_error_check (model_, &QSqlTableModel::submitAll); + SQL_error_check (model_ + , QSqlTableModel::OnManualSubmit == model_.editStrategy () + ? &QSqlTableModel::submitAll + : &QSqlTableModel::submit); } else { - ok = model_.submitAll (); + ok = QSqlTableModel::OnManualSubmit == model_.editStrategy () + ? model_.submitAll () : model_.submit (); } submitted_ = submitted_ || ok; return ok; } + void revert () { - Q_ASSERT (model_.isDirty ()); - model_.revertAll (); + if (QSqlTableModel::OnManualSubmit == model_.editStrategy ()) + { + model_.revertAll (); + } + else + { + model_.revert (); + } } + ~ConditionalTransaction () { if (model_.isDirty ()) { // abandon un-submitted changes to the model - model_.revertAll (); + if (QSqlTableModel::OnManualSubmit == model_.editStrategy ()) + { + model_.revertAll (); + } + else + { + model_.revert (); + } } auto database = model_.database (); if (submitted_) @@ -67,6 +86,7 @@ public: database.rollback (); } } + private: QSqlTableModel& model_; bool submitted_; diff --git a/widgets/AbstractLogWindow.cpp b/widgets/AbstractLogWindow.cpp index ebc901cd8..e796cdc3e 100644 --- a/widgets/AbstractLogWindow.cpp +++ b/widgets/AbstractLogWindow.cpp @@ -1,25 +1,36 @@ #include "AbstractLogWindow.hpp" +#include #include #include #include #include +#include +#include +#include +#include #include "Configuration.hpp" #include "SettingsGroup.hpp" +#include "MessageBox.hpp" #include "models/FontOverrideModel.hpp" #include "pimpl_impl.hpp" class AbstractLogWindow::impl final { public: - impl (QString const& settings_key, QSettings * settings, Configuration const * configuration) - : settings_key_ {settings_key} + impl (AbstractLogWindow * self, QString const& settings_key, QSettings * settings + , Configuration const * configuration) + : self_ {self} + , settings_key_ {settings_key} , settings_ {settings} , configuration_ {configuration} , log_view_ {nullptr} { } + void delete_QSOs (); + + AbstractLogWindow * self_; QString settings_key_; QSettings * settings_; Configuration const * configuration_; @@ -27,11 +38,51 @@ public: FontOverrideModel model_; }; +namespace +{ + bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs) + { + return lhs.row () > rhs.row (); + } +} + +void AbstractLogWindow::impl::delete_QSOs () +{ + auto selection_model = log_view_->selectionModel (); + selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + auto row_indexes = selection_model->selectedRows (); + + if (row_indexes.size () + && MessageBox::Yes == MessageBox::query_message (self_ + , tr ("Confirm Delete") + , tr ("Are you sure you want to delete the %n " + "selected QSO(s) from the log", "" + , row_indexes.size ()))) + { + // 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. + for (auto& row_index : row_indexes) + { + row_index = model_.mapToSource (row_index); + } + + // reverse sort by row + std::sort (row_indexes.begin (), row_indexes.end (), row_is_higher); + for (auto index : row_indexes) + { + auto row = model_.mapFromSource (index).row (); + model_.removeRow (row); + self_->log_model_changed (); + } + } +} + AbstractLogWindow::AbstractLogWindow (QString const& settings_key, QSettings * settings , Configuration const * configuration , QWidget * parent) : QWidget {parent} - , m_ {settings_key, settings, configuration} + , m_ {this, settings_key, settings, configuration} { // ensure view scrolls to latest new row connect (&m_->model_, &QAbstractItemModel::rowsInserted, [this] (QModelIndex const& /*parent*/, int /*first*/, int /*last*/) { @@ -45,18 +96,17 @@ AbstractLogWindow::~AbstractLogWindow () m_->settings_->setValue ("window/geometry", saveGeometry ()); } -void AbstractLogWindow::set_log_model (QAbstractItemModel * log_model) -{ - m_->model_.setSourceModel (log_model); -} - void AbstractLogWindow::set_log_view (QTableView * log_view) { // do this here because we know the UI must be setup before this SettingsGroup g {m_->settings_, m_->settings_key_}; restoreGeometry (m_->settings_->value ("window/geometry").toByteArray ()); - m_->log_view_ = log_view; + m_->log_view_->setContextMenuPolicy (Qt::ActionsContextMenu); + m_->log_view_->setAlternatingRowColors (true); + m_->log_view_->setSelectionBehavior (QAbstractItemView::SelectRows); + m_->log_view_->setSelectionMode (QAbstractItemView::ExtendedSelection); + m_->model_.setSourceModel (m_->log_view_->model ()); m_->log_view_->setModel (&m_->model_); m_->log_view_->setColumnHidden (0, true); auto horizontal_header = log_view->horizontalHeader (); @@ -65,6 +115,13 @@ void AbstractLogWindow::set_log_view (QTableView * log_view) m_->log_view_->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); set_log_view_font (m_->configuration_->decoded_text_font ()); m_->log_view_->scrollToBottom (); + + // actions + auto delete_action = new QAction {tr ("&Delete ..."), m_->log_view_}; + m_->log_view_->insertAction (nullptr, delete_action); + connect (delete_action, &QAction::triggered, [this] (bool /*checked*/) { + m_->delete_QSOs (); + }); } void AbstractLogWindow::set_log_view_font (QFont const& font) diff --git a/widgets/AbstractLogWindow.hpp b/widgets/AbstractLogWindow.hpp index 35d5d27cf..581212d82 100644 --- a/widgets/AbstractLogWindow.hpp +++ b/widgets/AbstractLogWindow.hpp @@ -7,10 +7,15 @@ class QString; class QSettings; class Configuration; -class QAbstractItemModel; class QTableView; class QFont; +// +// AbstractLogWindow - Base class for log view windows +// +// QWidget that manages the common functionality shared by windows +// that include a QSO log view. +// class AbstractLogWindow : public QWidget { @@ -20,11 +25,15 @@ public: , QWidget * parent = nullptr); virtual ~AbstractLogWindow () = 0; - void set_log_model (QAbstractItemModel *); + // set the QTableView that shows the log records, must have its + // model set before calling this void set_log_view (QTableView *); + void set_log_view_font (QFont const&); private: + virtual void log_model_changed (int row = -1) = 0; + class impl; pimpl m_; }; diff --git a/widgets/CabrilloLogWindow.cpp b/widgets/CabrilloLogWindow.cpp index ab83a79f2..20f39e4e6 100644 --- a/widgets/CabrilloLogWindow.cpp +++ b/widgets/CabrilloLogWindow.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "Configuration.hpp" #include "models/Bands.hpp" #include "item_delegates/ForeignKeyDelegate.hpp" @@ -43,26 +44,44 @@ namespace class CabrilloLogWindow::impl final { public: - explicit impl () = default; + explicit impl (QSqlTableModel * log_model) + : log_model_ {log_model} + { + } + + QSqlTableModel * log_model_; FormatProxyModel format_model_; Ui::CabrilloLogWindow ui_; }; CabrilloLogWindow::CabrilloLogWindow (QSettings * settings, Configuration const * configuration - , QAbstractItemModel * cabrillo_log_model, QWidget * parent) + , QSqlTableModel * cabrillo_log_model, QWidget * parent) : AbstractLogWindow {"Cabrillo Log Window", settings, configuration, parent} + , m_{cabrillo_log_model} { setWindowTitle (QApplication::applicationName () + " - Cabrillo Log"); m_->ui_.setupUi (this); - m_->format_model_.setSourceModel (cabrillo_log_model); - set_log_model (&m_->format_model_); + m_->format_model_.setSourceModel (m_->log_model_); + m_->ui_.log_table_view->setModel (&m_->format_model_); set_log_view (m_->ui_.log_table_view); m_->ui_.log_table_view->setItemDelegateForColumn (2, new DateTimeAsSecsSinceEpochDelegate {this}); m_->ui_.log_table_view->setItemDelegateForColumn (3, new CallsignDelegate {this}); - m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), cabrillo_log_model, 0, 6, this}); + m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), m_->log_model_, 0, 6, this}); m_->ui_.log_table_view->horizontalHeader ()->moveSection (6, 1); // band to first column } CabrilloLogWindow::~CabrilloLogWindow () { } + +void CabrilloLogWindow::log_model_changed (int row) +{ + if (row >= 0) + { + m_->log_model_->selectRow (row); + } + else + { + m_->log_model_->select (); + } +} diff --git a/widgets/CabrilloLogWindow.hpp b/widgets/CabrilloLogWindow.hpp index c0a964ce5..e2aa1627a 100644 --- a/widgets/CabrilloLogWindow.hpp +++ b/widgets/CabrilloLogWindow.hpp @@ -7,17 +7,19 @@ class QSettings; class Configuration; class QFont; -class QAbstractItemModel; +class QSqlTableModel; class CabrilloLogWindow final : public AbstractLogWindow { public: - explicit CabrilloLogWindow (QSettings *, Configuration const *, QAbstractItemModel * cabrillo_log_model + explicit CabrilloLogWindow (QSettings *, Configuration const *, QSqlTableModel * cabrillo_log_model , QWidget * parent = nullptr); ~CabrilloLogWindow (); private: + void log_model_changed (int row) override; + class impl; pimpl m_; }; diff --git a/widgets/CabrilloLogWindow.ui b/widgets/CabrilloLogWindow.ui index 43061f132..929b41a7b 100644 --- a/widgets/CabrilloLogWindow.ui +++ b/widgets/CabrilloLogWindow.ui @@ -6,7 +6,7 @@ 0 0 - 274 + 493 210 @@ -16,12 +16,6 @@ - - true - - - QAbstractItemView::SingleSelection - true diff --git a/widgets/FoxLogWindow.cpp b/widgets/FoxLogWindow.cpp index f5e7e517d..21029c73c 100644 --- a/widgets/FoxLogWindow.cpp +++ b/widgets/FoxLogWindow.cpp @@ -1,9 +1,14 @@ #include "FoxLogWindow.hpp" #include +#include +#include +#include +#include #include "SettingsGroup.hpp" #include "Configuration.hpp" +#include "MessageBox.hpp" #include "models/Bands.hpp" #include "item_delegates/ForeignKeyDelegate.hpp" #include "item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp" @@ -16,26 +21,47 @@ class FoxLogWindow::impl final { public: - explicit impl () = default; + explicit impl (QSqlTableModel * log_model) + : log_model_ {log_model} + { + } + + QSqlTableModel * log_model_; Ui::FoxLogWindow ui_; }; FoxLogWindow::FoxLogWindow (QSettings * settings, Configuration const * configuration - , QAbstractItemModel * fox_log_model, QWidget * parent) + , QSqlTableModel * fox_log_model, QWidget * parent) : AbstractLogWindow {"Fox Log Window", settings, configuration, parent} + , m_ {fox_log_model} { setWindowTitle (QApplication::applicationName () + " - Fox Log"); m_->ui_.setupUi (this); - set_log_model (fox_log_model); + m_->ui_.log_table_view->setModel (m_->log_model_); set_log_view (m_->ui_.log_table_view); m_->ui_.log_table_view->setItemDelegateForColumn (1, new DateTimeAsSecsSinceEpochDelegate {this}); m_->ui_.log_table_view->setItemDelegateForColumn (2, new CallsignDelegate {this}); m_->ui_.log_table_view->setItemDelegateForColumn (3, new MaidenheadLocatorDelegate {this}); - m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), fox_log_model, 0, 6, this}); + m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), m_->log_model_, 0, 6, this}); m_->ui_.log_table_view->horizontalHeader ()->moveSection (6, 1); // move band to first column m_->ui_.rate_label->setNum (0); m_->ui_.queued_label->setNum (0); m_->ui_.callers_label->setNum (0); + + // actions + auto reset_action = new QAction {tr ("&Reset ..."), m_->ui_.log_table_view}; + m_->ui_.log_table_view->insertAction (nullptr, reset_action); + connect (reset_action, &QAction::triggered, [this, configuration] (bool /*checked*/) { + if (MessageBox::Yes == MessageBox::query_message( this + , tr ("Confirm Reset") + , tr ("Are you sure you want to erase file FoxQSO.txt " + "and start a new Fox log?"))) + { + QFile f{configuration->writeable_data_dir ().absoluteFilePath ("FoxQSO.txt")}; + f.remove (); + Q_EMIT reset_log_model (); + } + }); } FoxLogWindow::~FoxLogWindow () @@ -56,3 +82,15 @@ void FoxLogWindow::rate (int n) { m_->ui_.rate_label->setNum (n); } + +void FoxLogWindow::log_model_changed (int row) +{ + if (row >= 0) + { + m_->log_model_->selectRow (row); + } + else + { + m_->log_model_->select (); + } +} diff --git a/widgets/FoxLogWindow.hpp b/widgets/FoxLogWindow.hpp index d27ccdea6..65bcb0bd0 100644 --- a/widgets/FoxLogWindow.hpp +++ b/widgets/FoxLogWindow.hpp @@ -7,13 +7,15 @@ class QSettings; class Configuration; class QFont; -class QAbstractItemModel; +class QSqlTableModel; class FoxLogWindow final : public AbstractLogWindow { + Q_OBJECT + public: - explicit FoxLogWindow (QSettings *, Configuration const *, QAbstractItemModel * fox_log_model + explicit FoxLogWindow (QSettings *, Configuration const *, QSqlTableModel * fox_log_model , QWidget * parent = nullptr); ~FoxLogWindow (); @@ -21,7 +23,11 @@ public: void queued (int); void rate (int); + Q_SIGNAL void reset_log_model () const; + private: + void log_model_changed (int row) override; + class impl; pimpl m_; }; diff --git a/widgets/FoxLogWindow.ui b/widgets/FoxLogWindow.ui index 96ef8b112..0a9815478 100644 --- a/widgets/FoxLogWindow.ui +++ b/widgets/FoxLogWindow.ui @@ -6,21 +6,21 @@ 0 0 - 331 + 453 238 + + Qt::DefaultContextMenu + Fox Log - - true - - - QAbstractItemView::SingleSelection + + Qt::ActionsContextMenu true diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 895569942..18f6cee84 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -2419,6 +2419,10 @@ void MainWindow::on_fox_log_action_triggered() // Connect signals from fox log window connect (this, &MainWindow::finished, m_foxLogWindow.data (), &FoxLogWindow::close); + connect (m_foxLogWindow.data (), &FoxLogWindow::reset_log_model, [this] () { + if (!m_foxLog) m_foxLog.reset (new FoxLog); + m_foxLog->reset (); + }); } m_foxLogWindow->showNormal (); m_foxLogWindow->raise (); @@ -5332,7 +5336,6 @@ void MainWindow::on_logQSOButton_clicked() //Log QSO button default: break; } - using SpOp = Configuration::SpecialOperatingActivity; auto special_op = m_config.special_op_id (); if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) { @@ -5377,7 +5380,6 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, if (m_config.clear_DX () and SpecOp::HOUND != m_config.special_op_id()) clearDX (); m_dateTimeQSOOn = QDateTime {}; - using SpOp = Configuration::SpecialOperatingActivity; auto special_op = m_config.special_op_id (); if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) { @@ -6119,18 +6121,6 @@ void MainWindow::on_actionErase_ALL_TXT_triggered() //Erase ALL.TXT } } -void MainWindow::on_reset_fox_log_action_triggered () -{ - int ret = MessageBox::query_message(this, tr("Confirm Reset"), - tr("Are you sure you want to erase file FoxQSO.txt and start a new Fox log?")); - if(ret==MessageBox::Yes) { - QFile f{m_config.writeable_data_dir().absoluteFilePath("FoxQSO.txt")}; - f.remove(); - if (!m_foxLog) m_foxLog.reset (new FoxLog); - m_foxLog->reset (); - } -} - void MainWindow::on_reset_cabrillo_log_action_triggered () { if (MessageBox::Yes == MessageBox::query_message (this, tr ("Confirm Reset"), diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h index e5b9de9a8..4cab787b5 100644 --- a/widgets/mainwindow.h +++ b/widgets/mainwindow.h @@ -204,7 +204,6 @@ private slots: void on_actionDeepestDecode_toggled (bool); void bumpFqso(int n); void on_actionErase_ALL_TXT_triggered(); - void on_reset_fox_log_action_triggered (); void on_reset_cabrillo_log_action_triggered (); void on_actionErase_wsjtx_log_adi_triggered(); void on_actionExport_Cabrillo_log_triggered(); diff --git a/widgets/mainwindow.ui b/widgets/mainwindow.ui index 8256eb606..21799434e 100644 --- a/widgets/mainwindow.ui +++ b/widgets/mainwindow.ui @@ -2653,7 +2653,6 @@ list. The list can be maintained in Settings (F2). -