mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-30 22:20:21 +00:00
mobile/divelist: add first version of new MobileListModel proxy model
Create a model which represents all top-level items and, potentially, one expanded trip as a flat list. Pass down roles to the source model and let the source model handle that. We'll have to do some ifdef-ery, but so be it. Additionally, compile the base model on mobile as well. This contains a couple of hacks to make things compile at all. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
parent
90f8c1138e
commit
35b33b8c6a
6 changed files with 656 additions and 1 deletions
|
@ -128,6 +128,8 @@ SOURCES += ../../subsurface-mobile-main.cpp \
|
||||||
../../qt-models/diveimportedmodel.cpp \
|
../../qt-models/diveimportedmodel.cpp \
|
||||||
../../qt-models/messagehandlermodel.cpp \
|
../../qt-models/messagehandlermodel.cpp \
|
||||||
../../qt-models/diveplannermodel.cpp \
|
../../qt-models/diveplannermodel.cpp \
|
||||||
|
../../qt-models/divetripmodel.cpp \
|
||||||
|
../../qt-models/mobilelistmodel.cpp \
|
||||||
../../qt-models/cylindermodel.cpp \
|
../../qt-models/cylindermodel.cpp \
|
||||||
../../qt-models/cleanertablemodel.cpp \
|
../../qt-models/cleanertablemodel.cpp \
|
||||||
../../qt-models/tankinfomodel.cpp \
|
../../qt-models/tankinfomodel.cpp \
|
||||||
|
@ -267,6 +269,8 @@ HEADERS += \
|
||||||
../../qt-models/diveimportedmodel.h \
|
../../qt-models/diveimportedmodel.h \
|
||||||
../../qt-models/messagehandlermodel.h \
|
../../qt-models/messagehandlermodel.h \
|
||||||
../../qt-models/diveplannermodel.h \
|
../../qt-models/diveplannermodel.h \
|
||||||
|
../../qt-models/divetripmodel.h \
|
||||||
|
../../qt-models/mobilelistmodel.h \
|
||||||
../../qt-models/cylindermodel.h \
|
../../qt-models/cylindermodel.h \
|
||||||
../../qt-models/cleanertablemodel.h \
|
../../qt-models/cleanertablemodel.h \
|
||||||
../../qt-models/tankinfomodel.h \
|
../../qt-models/tankinfomodel.h \
|
||||||
|
|
|
@ -17,6 +17,8 @@ set(SUBSURFACE_GENERIC_MODELS_LIB_SRCS
|
||||||
diveplannermodel.h
|
diveplannermodel.h
|
||||||
diveplotdatamodel.cpp
|
diveplotdatamodel.cpp
|
||||||
diveplotdatamodel.h
|
diveplotdatamodel.h
|
||||||
|
divetripmodel.cpp
|
||||||
|
divetripmodel.h
|
||||||
maplocationmodel.cpp
|
maplocationmodel.cpp
|
||||||
maplocationmodel.h
|
maplocationmodel.h
|
||||||
models.cpp
|
models.cpp
|
||||||
|
@ -39,6 +41,10 @@ set(SUBSURFACE_DESKTOP_MODELS_LIB_SRCS
|
||||||
divetripmodel.h
|
divetripmodel.h
|
||||||
filtermodels.cpp
|
filtermodels.cpp
|
||||||
filtermodels.h
|
filtermodels.h
|
||||||
|
models.cpp
|
||||||
|
models.h
|
||||||
|
tankinfomodel.cpp
|
||||||
|
tankinfomodel.h
|
||||||
treemodel.cpp
|
treemodel.cpp
|
||||||
treemodel.h
|
treemodel.h
|
||||||
weightmodel.cpp
|
weightmodel.cpp
|
||||||
|
@ -59,6 +65,8 @@ set(SUBSURFACE_MOBILE_MODELS_LIB_SRCS
|
||||||
gpslistmodel.h
|
gpslistmodel.h
|
||||||
messagehandlermodel.cpp
|
messagehandlermodel.cpp
|
||||||
messagehandlermodel.h
|
messagehandlermodel.h
|
||||||
|
mobilelistmodel.cpp
|
||||||
|
mobilelistmodel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if (SUBSURFACE_TARGET_EXECUTABLE MATCHES "DesktopExecutable")
|
if (SUBSURFACE_TARGET_EXECUTABLE MATCHES "DesktopExecutable")
|
||||||
|
|
|
@ -433,7 +433,11 @@ bool DiveTripModelBase::setData(const QModelIndex &index, const QVariant &value,
|
||||||
if (dive->number == v)
|
if (dive->number == v)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#if defined(SUBSURFACE_MOBILE)
|
||||||
|
d->number = v;
|
||||||
|
#else
|
||||||
Command::editNumber(v, d);
|
Command::editNumber(v, d);
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,8 @@ public:
|
||||||
DIVE_IDX,
|
DIVE_IDX,
|
||||||
SELECTED_ROLE,
|
SELECTED_ROLE,
|
||||||
CURRENT_ROLE,
|
CURRENT_ROLE,
|
||||||
TRIP_HAS_CURRENT_ROLE // Returns true if this is a trip and it contains the current dive
|
TRIP_HAS_CURRENT_ROLE, // Returns true if this is a trip and it contains the current dive
|
||||||
|
LAST_ROLE
|
||||||
};
|
};
|
||||||
enum Layout {
|
enum Layout {
|
||||||
TREE,
|
TREE,
|
||||||
|
|
544
qt-models/mobilelistmodel.cpp
Normal file
544
qt-models/mobilelistmodel.cpp
Normal file
|
@ -0,0 +1,544 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
#include "mobilelistmodel.h"
|
||||||
|
#include "core/divelist.h" // for shown_dives
|
||||||
|
|
||||||
|
MobileListModel::MobileListModel(DiveTripModelBase *sourceIn) : source(sourceIn),
|
||||||
|
expandedRow(-1)
|
||||||
|
{
|
||||||
|
connectSignals();
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileListModel *MobileListModel::instance()
|
||||||
|
{
|
||||||
|
static DiveTripModelTree source;
|
||||||
|
static MobileListModel self(&source);
|
||||||
|
return &self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::connectSignals()
|
||||||
|
{
|
||||||
|
connect(source, &DiveTripModelBase::modelAboutToBeReset, this, &MobileListModel::beginResetModel);
|
||||||
|
connect(source, &DiveTripModelBase::modelReset, this, &MobileListModel::endResetModel);
|
||||||
|
connect(source, &DiveTripModelBase::rowsAboutToBeRemoved, this, &MobileListModel::prepareRemove);
|
||||||
|
connect(source, &DiveTripModelBase::rowsRemoved, this, &MobileListModel::doneRemove);
|
||||||
|
connect(source, &DiveTripModelBase::rowsAboutToBeInserted, this, &MobileListModel::prepareInsert);
|
||||||
|
connect(source, &DiveTripModelBase::rowsInserted, this, &MobileListModel::doneInsert);
|
||||||
|
connect(source, &DiveTripModelBase::rowsAboutToBeMoved, this, &MobileListModel::prepareMove);
|
||||||
|
connect(source, &DiveTripModelBase::rowsMoved, this, &MobileListModel::doneMove);
|
||||||
|
connect(source, &DiveTripModelBase::dataChanged, this, &MobileListModel::changed);
|
||||||
|
connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &MobileListModel::shownChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> MobileListModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
roles[DiveTripModelBase::IS_TRIP_ROLE] = "isTrip";
|
||||||
|
roles[DiveTripModelBase::CURRENT_ROLE] = "current";
|
||||||
|
roles[IsTopLevelRole] = "isTopLevel";
|
||||||
|
roles[DiveDateRole] = "date";
|
||||||
|
roles[TripIdRole] = "tripId";
|
||||||
|
roles[TripNrDivesRole] = "tripNrDives";
|
||||||
|
roles[DateTimeRole] = "dateTime";
|
||||||
|
roles[IdRole] = "id";
|
||||||
|
roles[NumberRole] = "number";
|
||||||
|
roles[LocationRole] = "location";
|
||||||
|
roles[DepthRole] = "depth";
|
||||||
|
roles[DurationRole] = "duration";
|
||||||
|
roles[DepthDurationRole] = "depthDuration";
|
||||||
|
roles[RatingRole] = "rating";
|
||||||
|
roles[VizRole] = "viz";
|
||||||
|
roles[SuitRole] = "suit";
|
||||||
|
roles[AirTempRole] = "airTemp";
|
||||||
|
roles[WaterTempRole] = "waterTemp";
|
||||||
|
roles[SacRole] = "sac";
|
||||||
|
roles[SumWeightRole] = "sumWeight";
|
||||||
|
roles[DiveMasterRole] = "diveMaster";
|
||||||
|
roles[BuddyRole] = "buddy";
|
||||||
|
roles[NotesRole]= "notes";
|
||||||
|
roles[GpsRole] = "gps";
|
||||||
|
roles[GpsDecimalRole] = "gpsDecimal";
|
||||||
|
roles[NoDiveRole] = "noDive";
|
||||||
|
roles[DiveSiteRole] = "diveSite";
|
||||||
|
roles[CylinderRole] = "cylinder";
|
||||||
|
roles[GetCylinderRole] = "getCylinder";
|
||||||
|
roles[CylinderListRole] = "cylinderList";
|
||||||
|
roles[SingleWeightRole] = "singleWeight";
|
||||||
|
roles[StartPressureRole] = "startPressure";
|
||||||
|
roles[EndPressureRole] = "endPressure";
|
||||||
|
roles[FirstGasRole] = "firstGas";
|
||||||
|
roles[SelectedRole] = "selected";
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileListModel::shown() const
|
||||||
|
{
|
||||||
|
return shown_dives;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to show the newest dives first. Therefore, we have to invert
|
||||||
|
// the indexes with respect to the source model. To avoid mental gymnastics
|
||||||
|
// in the rest of the code, we do this right before sending to the
|
||||||
|
// source model and just after recieving from the source model, respectively.
|
||||||
|
QModelIndex MobileListModel::sourceIndex(int row, int col, int parentRow) const
|
||||||
|
{
|
||||||
|
if (row < 0 || col < 0)
|
||||||
|
return QModelIndex();
|
||||||
|
QModelIndex parent;
|
||||||
|
if (parentRow >= 0) {
|
||||||
|
int numTop = source->rowCount(QModelIndex());
|
||||||
|
parent = source->index(numTop - 1 - parentRow, 0);
|
||||||
|
}
|
||||||
|
int numItems = source->rowCount(parent);
|
||||||
|
return source->index(numItems - 1 - row, col, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileListModel::numSubItems() const
|
||||||
|
{
|
||||||
|
if (expandedRow < 0)
|
||||||
|
return 0;
|
||||||
|
return source->rowCount(sourceIndex(expandedRow, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileListModel::invertRow(const QModelIndex &parent, int row) const
|
||||||
|
{
|
||||||
|
int numItems = source->rowCount(parent);
|
||||||
|
return numItems - 1 - row;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileListModel::mapRowFromSourceTopLevel(int row) const
|
||||||
|
{
|
||||||
|
// This is a top-level item. If it is after the expanded row,
|
||||||
|
// we have to add the items of the expanded row.
|
||||||
|
row = invertRow(QModelIndex(), row);
|
||||||
|
return expandedRow >= 0 && row > expandedRow ? row + numSubItems() : row;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileListModel::mapRowFromSourceTopLevelForInsert(int row) const
|
||||||
|
{
|
||||||
|
// This is a top-level item. If it is after the expanded row,
|
||||||
|
// we have to add the items of the expanded row.
|
||||||
|
row = invertRow(QModelIndex(), row) + 1;
|
||||||
|
return expandedRow >= 0 && row > expandedRow ? row + numSubItems() : row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The parentRow parameter is the row of the expanded trip converted into
|
||||||
|
// local "coordinates" as a premature optimization.
|
||||||
|
int MobileListModel::mapRowFromSourceTrip(const QModelIndex &parent, int parentRow, int row) const
|
||||||
|
{
|
||||||
|
row = invertRow(parent, row);
|
||||||
|
if (parentRow != expandedRow) {
|
||||||
|
qWarning("MobileListModel::mapRowFromSourceTrip() called on non-extended row");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return expandedRow + 1 + row; // expandedRow + 1 is the row of the first subitem
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileListModel::mapRowFromSource(const QModelIndex &parent, int row) const
|
||||||
|
{
|
||||||
|
if (row < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!parent.isValid()) {
|
||||||
|
return mapRowFromSourceTopLevel(row);
|
||||||
|
} else {
|
||||||
|
int parentRow = invertRow(QModelIndex(), parent.row());
|
||||||
|
return mapRowFromSourceTrip(parent, parentRow, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileListModel::IndexRange MobileListModel::mapRangeFromSource(const QModelIndex &parent, int first, int last) const
|
||||||
|
{
|
||||||
|
int num = last - first; // Actually, that is num - 1 owing to Qt's bizarre range semantics!
|
||||||
|
// Since we invert the direction, the last will be the first.
|
||||||
|
if (!parent.isValid()) {
|
||||||
|
first = mapRowFromSourceTopLevel(last);
|
||||||
|
// If this includes the extended row, we have to add the subitems
|
||||||
|
if (first <= expandedRow && first + num >= expandedRow)
|
||||||
|
num += numSubItems();
|
||||||
|
return { true, first, first + num };
|
||||||
|
} else {
|
||||||
|
int parentRow = invertRow(QModelIndex(), parent.row());
|
||||||
|
if (parentRow == expandedRow) {
|
||||||
|
first = mapRowFromSourceTrip(parent, parentRow, last);
|
||||||
|
return { true, first, first + num };
|
||||||
|
} else {
|
||||||
|
return { false, -1, -1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is fun: when inserting, we point to the item *before* which we
|
||||||
|
// want to insert. But by inverting the direction we turn that into the item
|
||||||
|
// *after* which we want to insert. Thus, we have to add one to the range.
|
||||||
|
// Moreover, here we have to use the first item. TODO: We can remove this
|
||||||
|
// function once the core-model is sorted appropriately.
|
||||||
|
MobileListModel::IndexRange MobileListModel::mapRangeFromSourceForInsert(const QModelIndex &parent, int first, int last) const
|
||||||
|
{
|
||||||
|
int num = last - first;
|
||||||
|
if (!parent.isValid()) {
|
||||||
|
first = mapRowFromSourceTopLevelForInsert(first);
|
||||||
|
return { true, first, first + num };
|
||||||
|
} else {
|
||||||
|
int parentRow = invertRow(QModelIndex(), parent.row());
|
||||||
|
if (parentRow == expandedRow) {
|
||||||
|
first = mapRowFromSourceTrip(parent, parentRow, first);
|
||||||
|
return { true, first + 1, first + 1 + num };
|
||||||
|
} else {
|
||||||
|
return { false, -1, -1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex MobileListModel::mapFromSource(const QModelIndex &idx) const
|
||||||
|
{
|
||||||
|
return createIndex(mapRowFromSource(idx.parent(), idx.row()), idx.column());
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex MobileListModel::mapToSource(const QModelIndex &idx) const
|
||||||
|
{
|
||||||
|
if (!idx.isValid())
|
||||||
|
return idx;
|
||||||
|
int row = idx.row();
|
||||||
|
int col = idx.column();
|
||||||
|
if (expandedRow < 0 || row <= expandedRow)
|
||||||
|
return sourceIndex(row, col);
|
||||||
|
|
||||||
|
int numSub = numSubItems();
|
||||||
|
if (row > expandedRow + numSub)
|
||||||
|
return sourceIndex(row - numSub, col);
|
||||||
|
|
||||||
|
return sourceIndex(row - expandedRow - 1, col, expandedRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex MobileListModel::index(int row, int column, const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (!hasIndex(row, column, parent))
|
||||||
|
return QModelIndex();
|
||||||
|
|
||||||
|
return createIndex(row, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex MobileListModel::parent(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
// This is a flat model - there is no parent
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileListModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid())
|
||||||
|
return 0; // There is no parent
|
||||||
|
return source->rowCount() + numSubItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileListModel::columnCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
return source->columnCount(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant MobileListModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (role == IsTopLevelRole)
|
||||||
|
return index.row() <= expandedRow || index.row() > expandedRow + numSubItems();
|
||||||
|
|
||||||
|
return source->data(mapToSource(index), role);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::resetModel()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
source->reset();
|
||||||
|
connectSignals();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trivial helper to return and erase the last element of a stack
|
||||||
|
template<typename T>
|
||||||
|
static T pop(std::vector<T> &v)
|
||||||
|
{
|
||||||
|
T res = v.back();
|
||||||
|
v.pop_back();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::prepareRemove(const QModelIndex &parent, int first, int last)
|
||||||
|
{
|
||||||
|
IndexRange range = mapRangeFromSource(parent, first, last);
|
||||||
|
|
||||||
|
// Check whether we remove a dive from an expanded trip
|
||||||
|
if (range.visible && parent.isValid()) {
|
||||||
|
for (int i = first; i <= last; ++i) {
|
||||||
|
QModelIndex index = source->index(i, 0, parent);
|
||||||
|
if (source->data(index, DiveTripModelBase::CURRENT_ROLE).value<bool>()) {
|
||||||
|
// Hack alert: we remove the currently selected dive from a visible trip.
|
||||||
|
// Therefore, simply collapse the expanded trip. This is done in prepareRemove(),
|
||||||
|
// i.e. before the base model has actually done any removal and thus things should
|
||||||
|
// be consistent. Then, we can simply pretend that the range was invisible all along,
|
||||||
|
// i.e. the removal is a no-op.
|
||||||
|
unexpand();
|
||||||
|
range.visible = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rangeStack.push_back(range);
|
||||||
|
if (range.visible)
|
||||||
|
beginRemoveRows(QModelIndex(), range.first, range.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MobileListModel::updateRowAfterRemove(const IndexRange &range, int &row)
|
||||||
|
{
|
||||||
|
if (row < 0)
|
||||||
|
return;
|
||||||
|
else if (range.first <= row && range.last >= row)
|
||||||
|
row = -1;
|
||||||
|
else if (range.first <= row)
|
||||||
|
row -= range.last - range.first + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::doneRemove(const QModelIndex &parent, int first, int last)
|
||||||
|
{
|
||||||
|
IndexRange range = pop(rangeStack);
|
||||||
|
if (range.visible) {
|
||||||
|
// Check if we have to move or remove the expanded or current item
|
||||||
|
updateRowAfterRemove(range, expandedRow);
|
||||||
|
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::prepareInsert(const QModelIndex &parent, int first, int last)
|
||||||
|
{
|
||||||
|
IndexRange range = mapRangeFromSourceForInsert(parent, first, last);
|
||||||
|
rangeStack.push_back(range);
|
||||||
|
if (range.visible)
|
||||||
|
beginInsertRows(QModelIndex(), range.first, range.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::doneInsert(const QModelIndex &parent, int first, int last)
|
||||||
|
{
|
||||||
|
IndexRange range = pop(rangeStack);
|
||||||
|
if (range.visible) {
|
||||||
|
// Check if we have to move the expanded item
|
||||||
|
if (!parent.isValid() && expandedRow >= 0 && range.first <= expandedRow)
|
||||||
|
expandedRow += last - first + 1;
|
||||||
|
endInsertRows();
|
||||||
|
} else {
|
||||||
|
// The range was not visible, thus we inserted into a non-expanded trip.
|
||||||
|
// However, we might have inserted the current item. This means that we
|
||||||
|
// have to expand that trip.
|
||||||
|
// If we inserted a dive that is the current item
|
||||||
|
QModelIndex index = source->index(parent.row(), 0, QModelIndex());
|
||||||
|
if (source->data(index, DiveTripModelBase::TRIP_HAS_CURRENT_ROLE).value<bool>()) {
|
||||||
|
int row = mapRowFromSourceTopLevel(parent.row());
|
||||||
|
expand(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parent.isValid()) {
|
||||||
|
// If we inserted a trip that contains the current item, expand that trip
|
||||||
|
for (int i = first; i <= last; ++i) {
|
||||||
|
// Accessing data via the model/view API is annoying.
|
||||||
|
// Perhaps we should simply add a tripHasCurrent(int row) function?
|
||||||
|
QModelIndex index = source->index(i, 0, QModelIndex());
|
||||||
|
if (source->data(index, DiveTripModelBase::TRIP_HAS_CURRENT_ROLE).value<bool>()) {
|
||||||
|
int row = mapRowFromSourceTopLevel(i);
|
||||||
|
expand(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moving rows is annoying, as there are numerous cases to be considered.
|
||||||
|
// Some of them degrade to removing or inserting rows.
|
||||||
|
void MobileListModel::prepareMove(const QModelIndex &parent, int first, int last, const QModelIndex &dest, int destRow)
|
||||||
|
{
|
||||||
|
IndexRange range = mapRangeFromSource(parent, first, last);
|
||||||
|
IndexRange rangeDest = mapRangeFromSourceForInsert(dest, destRow, destRow);
|
||||||
|
rangeStack.push_back(range);
|
||||||
|
rangeStack.push_back(rangeDest);
|
||||||
|
if (!range.visible && !rangeDest.visible)
|
||||||
|
return;
|
||||||
|
if (range.visible && !rangeDest.visible)
|
||||||
|
return prepareRemove(parent, first, last);
|
||||||
|
if (!range.visible && rangeDest.visible)
|
||||||
|
return prepareInsert(parent, first, last);
|
||||||
|
beginMoveRows(QModelIndex(), range.first, range.last, QModelIndex(), rangeDest.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::updateRowAfterMove(const IndexRange &range, const IndexRange &rangeDest, int &row)
|
||||||
|
{
|
||||||
|
if (row >= 0 && (rangeDest.first < range.first || rangeDest.first > range.last + 1)) {
|
||||||
|
if (range.first <= row && range.last >= row) {
|
||||||
|
// Case 1: the expanded row is in the moved range
|
||||||
|
if (rangeDest.first <= range.first)
|
||||||
|
row -= range.first - rangeDest.first;
|
||||||
|
else if (rangeDest.first > range.last + 1)
|
||||||
|
row += rangeDest.first - (range.last + 1);
|
||||||
|
} else if (range.first > row && rangeDest.first <= row) {
|
||||||
|
// Case 2: moving things from behind to before the expanded row
|
||||||
|
row += range.last - range.first + 1;
|
||||||
|
} else if (range.first < row && rangeDest.first > row) {
|
||||||
|
// Case 3: moving things from before to behind the expanded row
|
||||||
|
row -= range.last - range.first + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::doneMove(const QModelIndex &parent, int first, int last, const QModelIndex &dest, int destRow)
|
||||||
|
{
|
||||||
|
IndexRange rangeDest = pop(rangeStack);
|
||||||
|
IndexRange range = pop(rangeStack);
|
||||||
|
if (!range.visible && !rangeDest.visible)
|
||||||
|
return;
|
||||||
|
if (range.visible && !rangeDest.visible)
|
||||||
|
return doneRemove(parent, first, last);
|
||||||
|
if (!range.visible && rangeDest.visible)
|
||||||
|
return doneInsert(parent, first, last);
|
||||||
|
if (expandedRow >= 0 && (rangeDest.first < range.first || rangeDest.first > range.last + 1)) {
|
||||||
|
if (!parent.isValid() && range.first <= expandedRow && range.last >= expandedRow) {
|
||||||
|
// Case 1: the expanded row is in the moved range
|
||||||
|
// Since we don't support sub-trips, this means that we can't move into another trip
|
||||||
|
if (dest.isValid())
|
||||||
|
qWarning("MobileListModel::doneMove(): moving trips into a subtrip");
|
||||||
|
else if (rangeDest.first <= range.first)
|
||||||
|
expandedRow -= range.first - rangeDest.first;
|
||||||
|
else if (rangeDest.first > range.last + 1)
|
||||||
|
expandedRow += rangeDest.first - (range.last + 1);
|
||||||
|
} else if (range.first > expandedRow && rangeDest.first <= expandedRow) {
|
||||||
|
// Case 2: moving things from behind to before the expanded row
|
||||||
|
expandedRow += range.last - range.first + 1;
|
||||||
|
} else if (range.first < expandedRow && rangeDest.first > expandedRow) {
|
||||||
|
// Case 3: moving things from before to behind the expanded row
|
||||||
|
expandedRow -= range.last - range.first + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateRowAfterMove(range, rangeDest, expandedRow);
|
||||||
|
endMoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::expand(int row)
|
||||||
|
{
|
||||||
|
// First, let us treat the trivial cases: expand an invalid row
|
||||||
|
// or the row is already expanded.
|
||||||
|
if (row < 0) {
|
||||||
|
unexpand();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (row == expandedRow)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Collapse the old expanded row, if any.
|
||||||
|
if (expandedRow >= 0) {
|
||||||
|
int numSub = numSubItems();
|
||||||
|
if (row > expandedRow) {
|
||||||
|
if (row <= expandedRow + numSub) {
|
||||||
|
qWarning("MobileListModel::expand(): trying to expand row in trip");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
row -= numSub;
|
||||||
|
}
|
||||||
|
unexpand();
|
||||||
|
}
|
||||||
|
|
||||||
|
int first = row + 1;
|
||||||
|
QModelIndex tripIdx = sourceIndex(row, 0);
|
||||||
|
int numRow = source->rowCount(tripIdx);
|
||||||
|
int last = first + numRow - 1;
|
||||||
|
if (last < first) {
|
||||||
|
// Amazingly, Qt's model API doesn't properly handle empty ranges!
|
||||||
|
expandedRow = row;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
beginInsertRows(QModelIndex(), first, last);
|
||||||
|
expandedRow = row;
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::changed(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
|
||||||
|
{
|
||||||
|
// We don't support changes beyond levels, sorry.
|
||||||
|
if (topLeft.parent().isValid() != bottomRight.parent().isValid()) {
|
||||||
|
qWarning("MobileListModel::changed(): changes across different levels. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case CURRENT_ROLE: if a dive in a collapsed trip becomes current, expand that trip
|
||||||
|
// and if a dive outside of a trip becomes current, collapse any expanded trip.
|
||||||
|
// Note: changes to current must not be combined with other changes, therefore we can
|
||||||
|
// assume that roles.size() == 1.
|
||||||
|
if (roles.size() == 1 && roles[0] == DiveTripModelBase::CURRENT_ROLE &&
|
||||||
|
source->data(topLeft, DiveTripModelBase::CURRENT_ROLE).value<bool>()) {
|
||||||
|
if (topLeft.parent().isValid()) {
|
||||||
|
int parentRow = mapRowFromSourceTopLevel(topLeft.parent().row());
|
||||||
|
if (parentRow != expandedRow) {
|
||||||
|
expand(parentRow);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unexpand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topLeft.parent().isValid()) {
|
||||||
|
// This is a range in a trip. First do a sanity check.
|
||||||
|
if (topLeft.parent().row() != bottomRight.parent().row()) {
|
||||||
|
qWarning("MobileListModel::changed(): changes inside different trips. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check whether this is expanded
|
||||||
|
IndexRange range = mapRangeFromSource(topLeft.parent(), topLeft.row(), bottomRight.row());
|
||||||
|
if (!range.visible)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dataChanged(createIndex(range.first, topLeft.column()), createIndex(range.last, bottomRight.column()), roles);
|
||||||
|
} else {
|
||||||
|
// This is a top-level range.
|
||||||
|
IndexRange range = mapRangeFromSource(topLeft.parent(), topLeft.row(), bottomRight.row());
|
||||||
|
|
||||||
|
// If the expanded row is outside the region to be updated
|
||||||
|
// or the last entry in the region to be updated, we can simply
|
||||||
|
// forward the signal.
|
||||||
|
if (expandedRow < 0 || expandedRow < range.first || expandedRow >= range.last) {
|
||||||
|
dataChanged(createIndex(range.first, topLeft.column()), createIndex(range.last, bottomRight.column()), roles);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to split this in two parts: before and including the expanded row
|
||||||
|
// and everything after the expanded row.
|
||||||
|
int numSub = numSubItems();
|
||||||
|
dataChanged(createIndex(range.first, topLeft.column()), createIndex(expandedRow, bottomRight.column()), roles);
|
||||||
|
dataChanged(createIndex(expandedRow + 1 + numSub, topLeft.column()), createIndex(range.last, bottomRight.column()), roles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::unexpand()
|
||||||
|
{
|
||||||
|
if (expandedRow < 0)
|
||||||
|
return;
|
||||||
|
int first = expandedRow + 1;
|
||||||
|
int numRows = numSubItems();
|
||||||
|
int last = first + numRows - 1;
|
||||||
|
if (last < first) {
|
||||||
|
// Amazingly, Qt's model API doesn't properly handle empty ranges!
|
||||||
|
expandedRow = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
beginRemoveRows(QModelIndex(), first, last);
|
||||||
|
expandedRow = -1;
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileListModel::toggle(int row)
|
||||||
|
{
|
||||||
|
if (row < 0)
|
||||||
|
return;
|
||||||
|
else if (row == expandedRow)
|
||||||
|
unexpand();
|
||||||
|
else
|
||||||
|
expand(row);
|
||||||
|
}
|
94
qt-models/mobilelistmodel.h
Normal file
94
qt-models/mobilelistmodel.h
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
#ifndef MOBILELISTMODEL_H
|
||||||
|
#define MOBILELISTMODEL_H
|
||||||
|
|
||||||
|
#include "divetripmodel.h"
|
||||||
|
|
||||||
|
class MobileListModel : public QAbstractItemModel {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum Roles {
|
||||||
|
IsTopLevelRole = DiveTripModelBase::LAST_ROLE + 1,
|
||||||
|
DiveDateRole,
|
||||||
|
TripIdRole,
|
||||||
|
TripNrDivesRole,
|
||||||
|
DateTimeRole,
|
||||||
|
IdRole,
|
||||||
|
NumberRole,
|
||||||
|
LocationRole,
|
||||||
|
DepthRole,
|
||||||
|
DurationRole,
|
||||||
|
DepthDurationRole,
|
||||||
|
RatingRole,
|
||||||
|
VizRole,
|
||||||
|
SuitRole,
|
||||||
|
AirTempRole,
|
||||||
|
WaterTempRole,
|
||||||
|
SacRole,
|
||||||
|
SumWeightRole,
|
||||||
|
DiveMasterRole,
|
||||||
|
BuddyRole,
|
||||||
|
NotesRole,
|
||||||
|
GpsDecimalRole,
|
||||||
|
GpsRole,
|
||||||
|
NoDiveRole,
|
||||||
|
DiveSiteRole,
|
||||||
|
CylinderRole,
|
||||||
|
GetCylinderRole,
|
||||||
|
CylinderListRole,
|
||||||
|
SingleWeightRole,
|
||||||
|
StartPressureRole,
|
||||||
|
EndPressureRole,
|
||||||
|
FirstGasRole,
|
||||||
|
SelectedRole,
|
||||||
|
};
|
||||||
|
MobileListModel(DiveTripModelBase *source);
|
||||||
|
static MobileListModel *instance();
|
||||||
|
void resetModel();
|
||||||
|
void expand(int row);
|
||||||
|
void unexpand();
|
||||||
|
void toggle(int row);
|
||||||
|
Q_PROPERTY(int shown READ shown NOTIFY shownChanged);
|
||||||
|
signals:
|
||||||
|
void shownChanged();
|
||||||
|
private:
|
||||||
|
struct IndexRange {
|
||||||
|
bool visible;
|
||||||
|
int first, last;
|
||||||
|
};
|
||||||
|
void connectSignals();
|
||||||
|
std::vector<IndexRange> rangeStack;
|
||||||
|
QModelIndex sourceIndex(int row, int col, int parentRow = -1) const;
|
||||||
|
int numSubItems() const;
|
||||||
|
int mapRowFromSourceTopLevel(int row) const;
|
||||||
|
int mapRowFromSourceTopLevelForInsert(int row) const;
|
||||||
|
int mapRowFromSourceTrip(const QModelIndex &parent, int parentRow, int row) const;
|
||||||
|
int mapRowFromSource(const QModelIndex &parent, int row) const;
|
||||||
|
int invertRow(const QModelIndex &parent, int row) const;
|
||||||
|
IndexRange mapRangeFromSource(const QModelIndex &parent, int first, int last) const;
|
||||||
|
IndexRange mapRangeFromSourceForInsert(const QModelIndex &parent, int first, int last) const;
|
||||||
|
QModelIndex mapFromSource(const QModelIndex &idx) const;
|
||||||
|
QModelIndex mapToSource(const QModelIndex &idx) const;
|
||||||
|
static void updateRowAfterRemove(const IndexRange &range, int &row);
|
||||||
|
static void updateRowAfterMove(const IndexRange &range, const IndexRange &dest, int &row);
|
||||||
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
|
||||||
|
QModelIndex parent(const QModelIndex &index) const override;
|
||||||
|
int rowCount(const QModelIndex &parent) const override;
|
||||||
|
int columnCount(const QModelIndex &parent) const override;
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
int shown() const;
|
||||||
|
|
||||||
|
DiveTripModelBase *source;
|
||||||
|
int expandedRow;
|
||||||
|
private slots:
|
||||||
|
void prepareRemove(const QModelIndex &parent, int first, int last);
|
||||||
|
void doneRemove(const QModelIndex &parent, int first, int last);
|
||||||
|
void prepareInsert(const QModelIndex &parent, int first, int last);
|
||||||
|
void doneInsert(const QModelIndex &parent, int first, int last);
|
||||||
|
void prepareMove(const QModelIndex &parent, int first, int last, const QModelIndex &dest, int destRow);
|
||||||
|
void doneMove(const QModelIndex &parent, int first, int last, const QModelIndex &dest, int destRow);
|
||||||
|
void changed(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in a new issue