subsurface/qt-models/divelistmodel.cpp
Berthold Stoeger 68414531ad Mobile: don't format trip heading for all dives
QML's ListView uses the "section" property to test if items belong to the
same section. Apparently, this must be a string and therefore we can't
pass e.g. a dive-trip object. Therefore a specially formatted string
was passed in, which was guaranteed to be unique (contained the dive-trip
pointer value) and the fully formatted trip-title and short-date.

The disadvantage of that approach is that the formatting is performed for
every dive and not every trip. Perhaps not a problem now, but it makes
it for example necessary to cache the number of filtered dives.

To be more flexible, pass in only the pointer value formatted as
hexadecimal string and provide a function to convert that string
back to a trip-pointer (in the form of a QVariant, so that it can
be passed to QML). Moreover provide two functions for formatting the
title and the short-date.

The three new functions are members of DiveListSortModel. This might not
be the perfect place, but it is easy to reach from the DiveListView.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2018-11-23 13:22:24 -08:00

326 lines
8.7 KiB
C++

// SPDX-License-Identifier: GPL-2.0
#include "qt-models/divelistmodel.h"
#include "core/qthelper.h"
#include "core/settings/qPrefGeneral.h"
#include <QDateTime>
DiveListSortModel::DiveListSortModel(QObject *parent) : QSortFilterProxyModel(parent)
{
updateFilterState();
}
void DiveListSortModel::updateFilterState()
{
if (filterString.isEmpty()) {
filteredRows.clear();
return;
}
// store this in local variables to avoid having to call these methods over and over
bool includeNotes = qPrefGeneral::filterFullTextNotes();
Qt::CaseSensitivity cs = qPrefGeneral::filterCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
// get the underlying model and re-calculate the filter value for each dive
DiveListModel *mySourceModel = qobject_cast<DiveListModel *>(sourceModel());
filteredRows.clear();
filteredRows.resize(mySourceModel->rowCount());
for (int i = 0; i < mySourceModel->rowCount(); i++) {
QString fullText = includeNotes? mySourceModel->at(i)->fullText() : mySourceModel->at(i)->fullTextNoNotes();
filteredRows.at(i) = fullText.contains(filterString, cs);
}
updateDivesShownInTrips();
}
void DiveListSortModel::setSourceModel(QAbstractItemModel *sourceModel)
{
QSortFilterProxyModel::setSourceModel(sourceModel);
}
void DiveListSortModel::setFilter(QString f)
{
filterString = f;
updateFilterState();
invalidateFilter();
updateDivesShownInTrips();
}
void DiveListSortModel::resetFilter()
{
filterString = "";
filteredRows.clear();
invalidateFilter();
updateDivesShownInTrips();
}
// filtering is way too slow on mobile. Maybe we should roll our own?
bool DiveListSortModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
return filteredRows.size() > source_row ? filteredRows[source_row] : true;
}
int DiveListSortModel::shown()
{
return rowCount();
}
int DiveListSortModel::getDiveId(int idx)
{
DiveListModel *mySourceModel = qobject_cast<DiveListModel *>(sourceModel());
return mySourceModel->getDiveId(mapToSource(index(idx,0)).row());
}
int DiveListSortModel::getIdxForId(int id)
{
for (int i = 0; i < rowCount(); i++) {
QVariant v = data(index(i, 0), DiveListModel::DiveRole);
DiveObjectHelper *d = v.value<DiveObjectHelper *>();
if (d->id() == id)
return i;
}
return -1;
}
void DiveListSortModel::clear()
{
DiveListModel *mySourceModel = qobject_cast<DiveListModel *>(sourceModel());
mySourceModel->clear();
filteredRows.clear();
}
void DiveListSortModel::addAllDives()
{
DiveListModel *mySourceModel = qobject_cast<DiveListModel *>(sourceModel());
mySourceModel->addAllDives();
updateFilterState();
}
void DiveListSortModel::updateDivesShownInTrips()
{
// if filtering is active, reset all the counts to zero, otherwise set them to the full count
struct dive_trip *dt = dive_trip_list;
int rc = rowCount();
while (dt) {
dt->showndives = rc ? 0 : dt->dives.nr;
dt = dt->next;
}
for (int i = 0; i < rowCount(); i++) {
QVariant v = data(index(i, 0), DiveListModel::DiveRole);
DiveObjectHelper *d = v.value<DiveObjectHelper *>();
dt = d->getDive()->divetrip;
if (dt)
dt->showndives++;
}
}
// In QML, section headings can only be strings. To identify dives that
// belong to the same trip, a string containing the trip-pointer in hexadecimal
// encoding is passed in. To format the trip heading, the string is then
// converted back with this function.
QVariant DiveListSortModel::tripIdToObject(const QString &s)
{
if (s.isEmpty())
return QVariant();
return QVariant::fromValue((dive_trip *)s.toULongLong(nullptr, 16));
}
// the trip title is designed to be location (# dives)
// or, if there is no location name date range (# dives)
// where the date range is given as "month year" or "month-month year" or "month year - month year"
QString DiveListSortModel::tripTitle(const QVariant &tripIn)
{
dive_trip *dt = tripIn.value<dive_trip *>();
if (!dt)
return QString();
QString numDives = tr("(%n dive(s))", "", dt->showndives);
QString title(dt->location);
if (title.isEmpty()) {
// so use the date range
QDateTime firstTime = QDateTime::fromMSecsSinceEpoch(1000*trip_date(dt), Qt::UTC);
QString firstMonth = firstTime.toString("MMM");
QString firstYear = firstTime.toString("yyyy");
QDateTime lastTime = QDateTime::fromMSecsSinceEpoch(1000*dt->dives.dives[0]->when, Qt::UTC);
QString lastMonth = lastTime.toString("MMM");
QString lastYear = lastTime.toString("yyyy");
if (lastMonth == firstMonth && lastYear == firstYear)
title = firstMonth + " " + firstYear;
else if (lastMonth != firstMonth && lastYear == firstYear)
title = firstMonth + "-" + lastMonth + " " + firstYear;
else
title = firstMonth + " " + firstYear + " - " + lastMonth + " " + lastYear;
}
return QStringLiteral("%1 %2").arg(title, numDives);
}
QString DiveListSortModel::tripShortDate(const QVariant &tripIn)
{
dive_trip *dt = tripIn.value<dive_trip *>();
if (!dt)
return QString();
QDateTime firstTime = QDateTime::fromMSecsSinceEpoch(1000*trip_date(dt), Qt::UTC);
QString firstMonth = firstTime.toString("MMM");
return QStringLiteral("%1\n'%2").arg(firstMonth,firstTime.toString("yy"));
}
DiveListModel *DiveListModel::m_instance = NULL;
DiveListModel::DiveListModel(QObject *parent) : QAbstractListModel(parent)
{
m_instance = this;
}
void DiveListModel::addDive(QList<dive *>listOfDives)
{
if (listOfDives.isEmpty())
return;
beginInsertRows(QModelIndex(), rowCount(), rowCount() + listOfDives.count() - 1);
foreach (dive *d, listOfDives) {
m_dives.append(new DiveObjectHelper(d));
}
endInsertRows();
}
void DiveListModel::addAllDives()
{
QList<dive *>listOfDives;
int i;
struct dive *d;
for_each_dive (i, d)
listOfDives.append(d);
addDive(listOfDives);
}
void DiveListModel::insertDive(int i, DiveObjectHelper *newDive)
{
beginInsertRows(QModelIndex(), i, i);
m_dives.insert(i, newDive);
endInsertRows();
}
void DiveListModel::removeDive(int i)
{
beginRemoveRows(QModelIndex(), i, i);
delete m_dives.at(i);
m_dives.removeAt(i);
endRemoveRows();
}
void DiveListModel::removeDiveById(int id)
{
for (int i = 0; i < rowCount(); i++) {
if (m_dives.at(i)->id() == id) {
removeDive(i);
return;
}
}
}
void DiveListModel::updateDive(int i, dive *d)
{
DiveObjectHelper *newDive = new DiveObjectHelper(d);
// we need to make sure that QML knows that this dive has changed -
// the only reliable way I've found is to remove and re-insert it
removeDive(i);
insertDive(i, newDive);
}
void DiveListModel::clear()
{
if (m_dives.count()) {
beginRemoveRows(QModelIndex(), 0, m_dives.count() - 1);
qDeleteAll(m_dives);
m_dives.clear();
endRemoveRows();
}
}
void DiveListModel::resetInternalData()
{
// this is a hack. There is a long standing issue, that seems related to a
// sync problem between QML engine and underlying model data. It causes delete
// from divelist (on mobile) to crash. But not always. This function is part of
// an attempt to fix this. See commit.
beginResetModel();
endResetModel();
}
int DiveListModel::rowCount(const QModelIndex &) const
{
return m_dives.count();
}
int DiveListModel::getDiveId(int idx) const
{
if (idx < 0 || idx >= m_dives.count())
return -1;
return m_dives[idx]->id();
}
int DiveListModel::getDiveIdx(int id) const
{
int i;
for (i = 0; i < m_dives.count(); i++) {
if (m_dives.at(i)->id() == id)
return i;
}
return -1;
}
QVariant DiveListModel::data(const QModelIndex &index, int role) const
{
if(index.row() < 0 || index.row() > m_dives.count())
return QVariant();
DiveObjectHelper *curr_dive = m_dives[index.row()];
switch(role) {
case DiveRole: return QVariant::fromValue<QObject*>(curr_dive);
case DiveDateRole: return (qlonglong)curr_dive->timestamp();
case FullTextRole: return curr_dive->fullText();
case FullTextNoNotesRole: return curr_dive->fullTextNoNotes();
}
return QVariant();
}
QHash<int, QByteArray> DiveListModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[DiveRole] = "dive";
roles[DiveDateRole] = "date";
roles[FullTextRole] = "fulltext";
roles[FullTextNoNotesRole] = "fulltextnonotes";
return roles;
}
// create a new dive. set the current time and add it to the end of the dive list
QString DiveListModel::startAddDive()
{
struct dive *d;
d = alloc_dive();
d->when = QDateTime::currentMSecsSinceEpoch() / 1000L + gettimezoneoffset();
// find the highest dive nr we have and pick the next one
struct dive *pd;
int i, nr = 0;
for_each_dive(i, pd) {
if (pd->number > nr)
nr = pd->number;
}
nr++;
d->number = nr;
d->dc.model = strdup("manually added dive");
add_single_dive(-1, d);
insertDive(get_idx_by_uniq_id(d->id), new DiveObjectHelper(d));
return QString::number(d->id);
}
DiveListModel *DiveListModel::instance()
{
return m_instance;
}
DiveObjectHelper* DiveListModel::at(int i)
{
return m_dives.at(i);
}