subsurface/qt-models/diveimportedmodel.cpp
Berthold Stoeger 087a80194a Import: keep dive and dive site tables in DiveImportedModel
The DiveImportedModel and DownloadThread used the same table
of dives and dive sites. This made it very hard to keep the
model consistent: Every modification of the download thread
would make the model inconsistent and could lead to memory
corruption owing to dangling pointers.

Therefore, keep a copy in the model. When updating the model,
use move-semantics, i.e. move the data and reset the tables
of the thread to zero elements.

Since the DiveImportedModel and the DownloadThread are very
tightly integrated, remove the accessor-functions of the
dive and dive-site tables. They fulfilled no purpose
whatsoever as they gave the same access-rights as a public
field.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2019-10-02 08:04:49 -07:00

220 lines
5.3 KiB
C++

#include "diveimportedmodel.h"
#include "core/qthelper.h"
#include "core/divelist.h"
DiveImportedModel::DiveImportedModel(QObject *o) : QAbstractTableModel(o),
firstIndex(0),
lastIndex(-1),
diveTable({ 0 }),
sitesTable({ 0 })
{
connect(&thread, &QThread::finished, this, &DiveImportedModel::downloadThreadFinished);
}
int DiveImportedModel::columnCount(const QModelIndex&) const
{
return 3;
}
int DiveImportedModel::rowCount(const QModelIndex&) const
{
return lastIndex - firstIndex + 1;
}
QVariant DiveImportedModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Vertical)
return QVariant();
// widgets access the model via index.column(), qml via role.
int column = section;
if (role == DateTime || role == Duration || role == Depth) {
column = role - DateTime;
role = Qt::DisplayRole;
}
if (role == Qt::DisplayRole) {
switch (column) {
case 0:
return QVariant(tr("Date/time"));
case 1:
return QVariant(tr("Duration"));
case 2:
return QVariant(tr("Depth"));
}
}
return QVariant();
}
QVariant DiveImportedModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() + firstIndex > lastIndex)
return QVariant();
struct dive *d = get_dive_from_table(index.row() + firstIndex, &diveTable);
if (!d)
return QVariant();
// widgets access the model via index.column(), qml via role.
int column = index.column();
if (role >= DateTime) {
column = role - DateTime;
role = Qt::DisplayRole;
}
if (role == Qt::DisplayRole) {
switch (column) {
case 0:
return QVariant(get_short_dive_date_string(d->when));
case 1:
return QVariant(get_dive_duration_string(d->duration.seconds, tr("h"), tr("min")));
case 2:
return QVariant(get_depth_string(d->maxdepth.mm, true, false));
case 3:
return checkStates[index.row()];
}
}
if (role == Qt::CheckStateRole) {
if (index.column() == 0)
return checkStates[index.row()] ? Qt::Checked : Qt::Unchecked;
}
return QVariant();
}
void DiveImportedModel::changeSelected(QModelIndex clickedIndex)
{
checkStates[clickedIndex.row()] = !checkStates[clickedIndex.row()];
dataChanged(index(clickedIndex.row(), 0), index(clickedIndex.row(), 0), QVector<int>() << Qt::CheckStateRole << Selected);
}
void DiveImportedModel::selectAll()
{
std::fill(checkStates.begin(), checkStates.end(), true);
dataChanged(index(0, 0), index(lastIndex - firstIndex, 0), QVector<int>() << Qt::CheckStateRole << Selected);
}
void DiveImportedModel::selectRow(int row)
{
checkStates[row] = !checkStates[row];
dataChanged(index(row, 0), index(row, 0), QVector<int>() << Qt::CheckStateRole << Selected);
}
void DiveImportedModel::selectNone()
{
std::fill(checkStates.begin(), checkStates.end(), false);
dataChanged(index(0, 0), index(lastIndex - firstIndex,0 ), QVector<int>() << Qt::CheckStateRole << Selected);
}
Qt::ItemFlags DiveImportedModel::flags(const QModelIndex &index) const
{
if (index.column() != 0)
return QAbstractTableModel::flags(index);
return QAbstractTableModel::flags(index) | Qt::ItemIsUserCheckable;
}
void DiveImportedModel::clearTable()
{
if (lastIndex < firstIndex) {
// just to be sure it's the right numbers
// but don't call RemoveRows or Qt in debug mode with trigger an ASSERT
lastIndex = -1;
firstIndex = 0;
return;
}
beginRemoveRows(QModelIndex(), 0, lastIndex - firstIndex);
lastIndex = -1;
firstIndex = 0;
endRemoveRows();
}
void DiveImportedModel::downloadThreadFinished()
{
beginResetModel();
// Move the table data from thread to model
move_dive_table(&thread.downloadTable, &diveTable);
move_dive_site_table(&thread.diveSiteTable, &sitesTable);
firstIndex = 0;
lastIndex = diveTable.nr - 1;
checkStates.resize(diveTable.nr);
std::fill(checkStates.begin(), checkStates.end(), true);
endResetModel();
emit downloadFinished();
}
void DiveImportedModel::startDownload()
{
thread.start();
}
std::pair<struct dive_table, struct dive_site_table> DiveImportedModel::consumeTables()
{
beginResetModel();
// Move tables to result
struct dive_table dives = { 0 };
struct dive_site_table sites = { 0 };
move_dive_table(&diveTable, &dives);
move_dive_site_table(&sitesTable, &sites);
// Reset indexes
firstIndex = 0;
lastIndex = -1;
checkStates.clear();
endResetModel();
return std::make_pair(dives, sites);
}
int DiveImportedModel::numDives() const
{
return diveTable.nr;
}
// Delete non-selected dives
void DiveImportedModel::deleteDeselected()
{
int total = diveTable.nr;
int j = 0;
for (int i = 0; i < total; i++) {
if (checkStates[i]) {
j++;
} else {
beginRemoveRows(QModelIndex(), j, j);
delete_dive_from_table(&diveTable, j);
endRemoveRows();
}
}
checkStates.resize(diveTable.nr);
std::fill(checkStates.begin(), checkStates.end(), true);
}
// Note: this function is only used from mobile - perhaps move it there or unify.
void DiveImportedModel::recordDives()
{
if (diveTable.nr == 0)
// nothing to do, just exit
return;
deleteDeselected();
// TODO: Might want to let the user select IMPORT_ADD_TO_NEW_TRIP
add_imported_dives(&diveTable, nullptr, &sitesTable, IMPORT_PREFER_IMPORTED | IMPORT_IS_DOWNLOADED);
}
QHash<int, QByteArray> DiveImportedModel::roleNames() const {
static QHash<int, QByteArray> roles = {
{ DateTime, "datetime"},
{ Depth, "depth"},
{ Duration, "duration"},
{ Selected, "selected"}
};
return roles;
}