Dive list: hand-code the DiveTripModel

The dive list is fed data by means of a sorted "DiveTripModel".
There are two modes: list and tree. This was implemented rather
elegantly with a general "TreeModel", which can represent trees
of arbitrary depths.

Nevertheless, we have at most two levels and on the second level
only dives can reside. Implementing proper model-semantics
(insert, delete, move) will be quite a challenge and implementing
it under the umbrella of a very general model will not make it
easier.

Therefore, for now, hardcode the model:
At the top-level there are items which may either be a trip
(can contain multiple dives) or a dive (contains exactly one dive).

Thus, we can completely de-virutalize the DiveItem and TripItem
classes, which are now trivial wrappers around dive * and dive_trip *.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2018-09-30 16:06:17 +02:00 committed by Dirk Hohndel
parent c341bc53c3
commit 4fbb8ef399
2 changed files with 163 additions and 51 deletions

View file

@ -310,14 +310,6 @@ QVariant DiveItem::data(int column, int role) const
return retVal;
}
Qt::ItemFlags DiveItem::flags(const QModelIndex &index) const
{
if (index.column() == NR) {
return TreeItem::flags(index) | Qt::ItemIsEditable;
}
return TreeItem::flags(index);
}
bool DiveItem::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role != Qt::EditRole)
@ -436,19 +428,70 @@ DiveTripModel *DiveTripModel::instance()
}
DiveTripModel::DiveTripModel(QObject *parent) :
TreeModel(parent),
QAbstractItemModel(parent),
currentLayout(TREE)
{
columns = COLUMNS;
}
int DiveTripModel::columnCount(const QModelIndex&) const
{
return COLUMNS;
}
int DiveTripModel::rowCount(const QModelIndex &parent) const
{
// No parent means top level - return the number of top-level items
if (!parent.isValid())
return items.size();
// If the parent has a parent, this is a dive -> no entries
if (parent.parent().isValid())
return 0;
// If this is outside of our top-level list -> no entries
int row = parent.row();
if (row < 0 || row >= (int)items.size())
return 0;
// Only trips have items
const Item &entry = items[parent.row()];
return entry.trip ? entry.dives.size() : 0;
}
static const quintptr noParent = ~(quintptr)0; // This is the "internalId" marker for top-level item
QModelIndex DiveTripModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
// In the "internalId", we store either ~0 no top-level items or the
// index of the parent item. A top-level item has an invalid parent.
return createIndex(row, column, parent.isValid() ? parent.row() : noParent);
}
QModelIndex DiveTripModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
// In the "internalId", we store either ~0 for top-level items
// or the index of the parent item.
quintptr id = index.internalId();
if (id == noParent)
return QModelIndex();
// Parent must be top-level item
return createIndex(id, 0, noParent);
}
Qt::ItemFlags DiveTripModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
dive *d = diveOrNull(index);
Qt::ItemFlags base = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
TripItem *item = static_cast<TripItem *>(index.internalPointer());
return item->flags(index);
// Only dives have editable fields and only the number is editable
return d && index.column() == NR ? base | Qt::ItemIsEditable : base;
}
QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, int role) const
@ -584,43 +627,49 @@ QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, int
return ret;
}
DiveTripModel::Item::Item(dive_trip *t, dive *d) : trip(t),
dives({ d })
{
}
DiveTripModel::Item::Item(dive *d) : trip(nullptr),
dives({ d })
{
}
void DiveTripModel::setupModelData()
{
int i = dive_table.nr;
beginResetModel();
clear();
if (autogroup)
autogroup_dives();
QMap<dive_trip_t *, TripItem *> trips;
items.clear();
while (--i >= 0) {
struct dive *dive = get_dive(i);
update_cylinder_related_info(dive);
dive_trip_t *trip = dive->divetrip;
DiveItem *diveItem = new DiveItem();
diveItem->d = dive;
dive *d = get_dive(i);
update_cylinder_related_info(d);
dive_trip_t *trip = d->divetrip;
// If this dive doesn't have a trip or we are in list-mode, add
// as top-level item.
if (!trip || currentLayout == LIST) {
diveItem->parent = rootItem.get();
rootItem->children.push_back(diveItem);
items.emplace_back(d);
continue;
}
if (currentLayout == LIST)
continue;
if (!trips.keys().contains(trip)) {
TripItem *tripItem = new TripItem();
tripItem->trip = trip;
tripItem->parent = rootItem.get();
tripItem->children.push_back(diveItem);
trips[trip] = tripItem;
rootItem->children.push_back(tripItem);
continue;
// Check if that trip is already known to us: search for the first item
// where item->trip is equal to trip.
auto it = std::find_if(items.begin(), items.end(), [trip](const Item &item)
{ return item.trip == trip; });
if (it == items.end()) {
// We didn't find an entry for this trip -> add one
items.emplace_back(trip, d);
} else {
// We found the trip -> simply add the dive
it->dives.push_back(d);
}
TripItem *tripItem = trips[trip];
tripItem->children.push_back(diveItem);
}
endResetModel();
@ -637,11 +686,47 @@ void DiveTripModel::setLayout(DiveTripModel::Layout layout)
setupModelData();
}
QPair<dive_trip *, dive *> DiveTripModel::tripOrDive(const QModelIndex &index) const
{
if (!index.isValid())
return { nullptr, nullptr };
QModelIndex parent = index.parent();
// An invalid parent means that we're at the top-level
if (!parent.isValid()) {
const Item &entry = items[index.row()];
if (entry.trip)
return { entry.trip, nullptr }; // A trip
else
return { nullptr, entry.dives[0] }; // A dive
}
// Otherwise, we're at a leaf -> thats a dive
return { nullptr, items[parent.row()].dives[index.row()] };
}
dive *DiveTripModel::diveOrNull(const QModelIndex &index) const
{
return tripOrDive(index).second;
}
QVariant DiveTripModel::data(const QModelIndex &index, int role) const
{
// Set the font for all items alike
if (role == Qt::FontRole)
return defaultModelFont();
auto entry = tripOrDive(index);
if (entry.first)
return TripItem(entry.first).data(index.column(), role);
else if (entry.second)
return DiveItem(entry.second).data(index.column(), role);
else
return QVariant();
}
bool DiveTripModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
DiveItem *diveItem = dynamic_cast<DiveItem *>(item);
if (!diveItem)
return false;
return diveItem->setData(index, value, role);
dive *d = diveOrNull(index);
return d ? DiveItem(d).setData(index, value, role) : false;
}

View file

@ -2,12 +2,12 @@
#ifndef DIVETRIPMODEL_H
#define DIVETRIPMODEL_H
#include "treemodel.h"
#include "core/dive.h"
#include <string>
#include <QAbstractItemModel>
#include <QCoreApplication> // For Q_DECLARE_TR_FUNCTIONS
struct DiveItem : public TreeItem {
Q_DECLARE_TR_FUNCTIONS(TripItem)
struct DiveItem {
Q_DECLARE_TR_FUNCTIONS(TripItem) // Is that TripItem on purpose?
public:
enum Column {
NR,
@ -31,10 +31,10 @@ public:
COLUMNS
};
QVariant data(int column, int role) const override;
QVariant data(int column, int role) const;
dive *d;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
DiveItem(dive *dIn) : d(dIn) {} // Trivial constructor
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
QString displayDate() const;
QString displayDuration() const;
QString displayDepth() const;
@ -50,14 +50,15 @@ public:
int weight() const;
};
struct TripItem : public TreeItem {
struct TripItem {
Q_DECLARE_TR_FUNCTIONS(TripItem)
public:
QVariant data(int column, int role) const override;
QVariant data(int column, int role) const;
dive_trip_t *trip;
TripItem(dive_trip_t *tIn) : trip(tIn) {} // Trivial constructor
};
class DiveTripModel : public TreeModel {
class DiveTripModel : public QAbstractItemModel {
Q_OBJECT
public:
enum Column {
@ -102,9 +103,35 @@ public:
DiveTripModel(QObject *parent = 0);
Layout layout() const;
void setLayout(Layout layout);
QVariant data(const QModelIndex &index, int role) const;
int columnCount(const QModelIndex&) const;
int rowCount(const QModelIndex &parent) const;
QModelIndex index(int row, int column, const QModelIndex &parent) const;
QModelIndex parent(const QModelIndex &index) const;
private:
// The model has up to two levels. At the top level, we have either trips or dives
// that do not belong to trips. Such a top-level item is represented by the "Item"
// struct. Two cases two consider:
// 1) If "trip" is non-null, then this is a dive-trip and the dives are collected
// in the dives vector. Note that in principle we could also get the dives in a
// trip from the backend, but there they are collected in a linked-list, which is
// quite inconvenient to access.
// 2) If "trip" is null, this is a dive and dives is supposed to contain exactly
// one element, which is the corresponding dive.
struct Item {
dive_trip *trip;
QVector<dive *> dives;
Item(dive_trip *t, dive *d); // Initialize a trip with one dive
Item(dive *d); // Initialize a top-level dive
};
dive *diveOrNull(const QModelIndex &index) const; // Returns a dive if this index represents a dive, null otherwise
QPair<dive_trip *, dive *> tripOrDive(const QModelIndex &index) const;
// Returns either a pointer to a trip or a dive, or twice null of index is invalid
// null, something is really wrong
void setupModelData();
std::vector<Item> items; // Use std::vector for convenience of emplace_back()
Layout currentLayout;
};