Move Fox log reset action to Fox log window context menu and allow deletes of QSOs

Move to OnRowChange edit strategy for log tables so that deletes from
view can be implemented cleanly.

Improve layout of log view tables by resizing to contents.
This commit is contained in:
Bill Somerville 2018-11-25 22:19:41 +00:00
parent 314d8a645b
commit db51726da2
16 changed files with 214 additions and 79 deletions

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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:

View File

@ -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_;

View File

@ -1,25 +1,36 @@
#include "AbstractLogWindow.hpp"
#include <algorithm>
#include <QSettings>
#include <QString>
#include <QTableView>
#include <QHeaderView>
#include <QAction>
#include <QSqlTableModel>
#include <QItemSelectionModel>
#include <QItemSelection>
#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)

View File

@ -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<impl> m_;
};

View File

@ -2,6 +2,7 @@
#include <QApplication>
#include <QIdentityProxyModel>
#include <QSqlTableModel>
#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 ();
}
}

View File

@ -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<impl> m_;
};

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>274</width>
<width>493</width>
<height>210</height>
</rect>
</property>
@ -16,12 +16,6 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="log_table_view">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>

View File

@ -1,9 +1,14 @@
#include "FoxLogWindow.hpp"
#include <QApplication>
#include <QSqlTableModel>
#include <QAction>
#include <QFile>
#include <QDir>
#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 ();
}
}

View File

@ -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<impl> m_;
};

View File

@ -6,21 +6,21 @@
<rect>
<x>0</x>
<y>0</y>
<width>331</width>
<width>453</width>
<height>238</height>
</rect>
</property>
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="windowTitle">
<string>Fox Log</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="log_table_view">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
<property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>

View File

@ -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"),

View File

@ -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();

View File

@ -2653,7 +2653,6 @@ list. The list can be maintained in Settings (F2).</string>
<addaction name="separator"/>
<addaction name="actionDelete_all_wav_files_in_SaveDir"/>
<addaction name="actionErase_ALL_TXT"/>
<addaction name="reset_fox_log_action"/>
<addaction name="actionErase_wsjtx_log_adi"/>
<addaction name="reset_cabrillo_log_action"/>
<addaction name="actionExport_Cabrillo_log"/>