mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
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:
parent
c341bc53c3
commit
4fbb8ef399
2 changed files with 163 additions and 51 deletions
|
@ -310,14 +310,6 @@ QVariant DiveItem::data(int column, int role) const
|
||||||
return retVal;
|
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)
|
bool DiveItem::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||||
{
|
{
|
||||||
if (role != Qt::EditRole)
|
if (role != Qt::EditRole)
|
||||||
|
@ -436,19 +428,70 @@ DiveTripModel *DiveTripModel::instance()
|
||||||
}
|
}
|
||||||
|
|
||||||
DiveTripModel::DiveTripModel(QObject *parent) :
|
DiveTripModel::DiveTripModel(QObject *parent) :
|
||||||
TreeModel(parent),
|
QAbstractItemModel(parent),
|
||||||
currentLayout(TREE)
|
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
|
Qt::ItemFlags DiveTripModel::flags(const QModelIndex &index) const
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
dive *d = diveOrNull(index);
|
||||||
return 0;
|
Qt::ItemFlags base = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||||
|
|
||||||
TripItem *item = static_cast<TripItem *>(index.internalPointer());
|
// Only dives have editable fields and only the number is editable
|
||||||
return item->flags(index);
|
return d && index.column() == NR ? base | Qt::ItemIsEditable : base;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, int role) const
|
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;
|
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()
|
void DiveTripModel::setupModelData()
|
||||||
{
|
{
|
||||||
int i = dive_table.nr;
|
int i = dive_table.nr;
|
||||||
|
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
|
||||||
clear();
|
|
||||||
if (autogroup)
|
if (autogroup)
|
||||||
autogroup_dives();
|
autogroup_dives();
|
||||||
QMap<dive_trip_t *, TripItem *> trips;
|
items.clear();
|
||||||
while (--i >= 0) {
|
while (--i >= 0) {
|
||||||
struct dive *dive = get_dive(i);
|
dive *d = get_dive(i);
|
||||||
update_cylinder_related_info(dive);
|
update_cylinder_related_info(d);
|
||||||
dive_trip_t *trip = dive->divetrip;
|
dive_trip_t *trip = d->divetrip;
|
||||||
|
|
||||||
DiveItem *diveItem = new DiveItem();
|
|
||||||
diveItem->d = dive;
|
|
||||||
|
|
||||||
|
// If this dive doesn't have a trip or we are in list-mode, add
|
||||||
|
// as top-level item.
|
||||||
if (!trip || currentLayout == LIST) {
|
if (!trip || currentLayout == LIST) {
|
||||||
diveItem->parent = rootItem.get();
|
items.emplace_back(d);
|
||||||
rootItem->children.push_back(diveItem);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (currentLayout == LIST)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!trips.keys().contains(trip)) {
|
// Check if that trip is already known to us: search for the first item
|
||||||
TripItem *tripItem = new TripItem();
|
// where item->trip is equal to trip.
|
||||||
tripItem->trip = trip;
|
auto it = std::find_if(items.begin(), items.end(), [trip](const Item &item)
|
||||||
tripItem->parent = rootItem.get();
|
{ return item.trip == trip; });
|
||||||
tripItem->children.push_back(diveItem);
|
if (it == items.end()) {
|
||||||
trips[trip] = tripItem;
|
// We didn't find an entry for this trip -> add one
|
||||||
rootItem->children.push_back(tripItem);
|
items.emplace_back(trip, d);
|
||||||
continue;
|
|
||||||
|
} else {
|
||||||
|
// We found the trip -> simply add the dive
|
||||||
|
it->dives.push_back(d);
|
||||||
}
|
}
|
||||||
TripItem *tripItem = trips[trip];
|
|
||||||
tripItem->children.push_back(diveItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
@ -637,11 +686,47 @@ void DiveTripModel::setLayout(DiveTripModel::Layout layout)
|
||||||
setupModelData();
|
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)
|
bool DiveTripModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||||
{
|
{
|
||||||
TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
|
dive *d = diveOrNull(index);
|
||||||
DiveItem *diveItem = dynamic_cast<DiveItem *>(item);
|
return d ? DiveItem(d).setData(index, value, role) : false;
|
||||||
if (!diveItem)
|
|
||||||
return false;
|
|
||||||
return diveItem->setData(index, value, role);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
#ifndef DIVETRIPMODEL_H
|
#ifndef DIVETRIPMODEL_H
|
||||||
#define DIVETRIPMODEL_H
|
#define DIVETRIPMODEL_H
|
||||||
|
|
||||||
#include "treemodel.h"
|
|
||||||
#include "core/dive.h"
|
#include "core/dive.h"
|
||||||
#include <string>
|
#include <QAbstractItemModel>
|
||||||
|
#include <QCoreApplication> // For Q_DECLARE_TR_FUNCTIONS
|
||||||
|
|
||||||
struct DiveItem : public TreeItem {
|
struct DiveItem {
|
||||||
Q_DECLARE_TR_FUNCTIONS(TripItem)
|
Q_DECLARE_TR_FUNCTIONS(TripItem) // Is that TripItem on purpose?
|
||||||
public:
|
public:
|
||||||
enum Column {
|
enum Column {
|
||||||
NR,
|
NR,
|
||||||
|
@ -31,10 +31,10 @@ public:
|
||||||
COLUMNS
|
COLUMNS
|
||||||
};
|
};
|
||||||
|
|
||||||
QVariant data(int column, int role) const override;
|
QVariant data(int column, int role) const;
|
||||||
dive *d;
|
dive *d;
|
||||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
DiveItem(dive *dIn) : d(dIn) {} // Trivial constructor
|
||||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
|
||||||
QString displayDate() const;
|
QString displayDate() const;
|
||||||
QString displayDuration() const;
|
QString displayDuration() const;
|
||||||
QString displayDepth() const;
|
QString displayDepth() const;
|
||||||
|
@ -50,14 +50,15 @@ public:
|
||||||
int weight() const;
|
int weight() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TripItem : public TreeItem {
|
struct TripItem {
|
||||||
Q_DECLARE_TR_FUNCTIONS(TripItem)
|
Q_DECLARE_TR_FUNCTIONS(TripItem)
|
||||||
public:
|
public:
|
||||||
QVariant data(int column, int role) const override;
|
QVariant data(int column, int role) const;
|
||||||
dive_trip_t *trip;
|
dive_trip_t *trip;
|
||||||
|
TripItem(dive_trip_t *tIn) : trip(tIn) {} // Trivial constructor
|
||||||
};
|
};
|
||||||
|
|
||||||
class DiveTripModel : public TreeModel {
|
class DiveTripModel : public QAbstractItemModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum Column {
|
enum Column {
|
||||||
|
@ -102,9 +103,35 @@ public:
|
||||||
DiveTripModel(QObject *parent = 0);
|
DiveTripModel(QObject *parent = 0);
|
||||||
Layout layout() const;
|
Layout layout() const;
|
||||||
void setLayout(Layout layout);
|
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:
|
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();
|
void setupModelData();
|
||||||
|
std::vector<Item> items; // Use std::vector for convenience of emplace_back()
|
||||||
Layout currentLayout;
|
Layout currentLayout;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue