diff options
Diffstat (limited to 'qt-models/divetripmodel.cpp')
-rw-r--r-- | qt-models/divetripmodel.cpp | 725 |
1 files changed, 437 insertions, 288 deletions
diff --git a/qt-models/divetripmodel.cpp b/qt-models/divetripmodel.cpp index 7a76ffbdb..46f0b712e 100644 --- a/qt-models/divetripmodel.cpp +++ b/qt-models/divetripmodel.cpp @@ -9,6 +9,10 @@ #include "core/subsurface-qt/DiveListNotifier.h" #include <QIcon> #include <QDebug> +#include <memory> +#include <algorithm> + +// 1) Base functions static int nitrox_sort_value(const struct dive *dive) { @@ -20,33 +24,33 @@ static int nitrox_sort_value(const struct dive *dive) static QVariant dive_table_alignment(int column) { switch (column) { - case DiveTripModel::DEPTH: - case DiveTripModel::DURATION: - case DiveTripModel::TEMPERATURE: - case DiveTripModel::TOTALWEIGHT: - case DiveTripModel::SAC: - case DiveTripModel::OTU: - case DiveTripModel::MAXCNS: + case DiveTripModelBase::DEPTH: + case DiveTripModelBase::DURATION: + case DiveTripModelBase::TEMPERATURE: + case DiveTripModelBase::TOTALWEIGHT: + case DiveTripModelBase::SAC: + case DiveTripModelBase::OTU: + case DiveTripModelBase::MAXCNS: // Right align numeric columns return int(Qt::AlignRight | Qt::AlignVCenter); // NR needs to be left aligned because its the indent marker for trips too - case DiveTripModel::NR: - case DiveTripModel::DATE: - case DiveTripModel::RATING: - case DiveTripModel::SUIT: - case DiveTripModel::CYLINDER: - case DiveTripModel::GAS: - case DiveTripModel::TAGS: - case DiveTripModel::PHOTOS: - case DiveTripModel::COUNTRY: - case DiveTripModel::BUDDIES: - case DiveTripModel::LOCATION: + case DiveTripModelBase::NR: + case DiveTripModelBase::DATE: + case DiveTripModelBase::RATING: + case DiveTripModelBase::SUIT: + case DiveTripModelBase::CYLINDER: + case DiveTripModelBase::GAS: + case DiveTripModelBase::TAGS: + case DiveTripModelBase::PHOTOS: + case DiveTripModelBase::COUNTRY: + case DiveTripModelBase::BUDDIES: + case DiveTripModelBase::LOCATION: return int(Qt::AlignLeft | Qt::AlignVCenter); } return QVariant(); } -QVariant DiveTripModel::tripData(const dive_trip *trip, int column, int role) +QVariant DiveTripModelBase::tripData(const dive_trip *trip, int column, int role) { if (role == TRIP_ROLE) @@ -54,7 +58,7 @@ QVariant DiveTripModel::tripData(const dive_trip *trip, int column, int role) if (role == Qt::DisplayRole) { switch (column) { - case DiveTripModel::NR: + case DiveTripModelBase::NR: QString shownText; bool oneDayTrip = trip_is_single_day(trip); int countShown = trip_shown_dives(trip); @@ -128,7 +132,7 @@ static QString displayWeight(const struct dive *d, bool units) return s + gettextFromC::tr("lbs"); } -QVariant DiveTripModel::diveData(const struct dive *d, int column, int role) +QVariant DiveTripModelBase::diveData(const struct dive *d, int column, int role) { switch (role) { case Qt::TextAlignmentRole: @@ -251,89 +255,7 @@ QVariant DiveTripModel::diveData(const struct dive *d, int column, int role) return QVariant(); } -DiveTripModel *DiveTripModel::instance() -{ - static DiveTripModel self; - return &self; -} - -DiveTripModel::DiveTripModel(QObject *parent) : - QAbstractItemModel(parent), - currentLayout(TREE) -{ - // Stay informed of changes to the divelist - connect(&diveListNotifier, &DiveListNotifier::divesAdded, this, &DiveTripModel::divesAdded); - connect(&diveListNotifier, &DiveListNotifier::divesDeleted, this, &DiveTripModel::divesDeleted); - connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &DiveTripModel::divesChanged); - connect(&diveListNotifier, &DiveListNotifier::divesMovedBetweenTrips, this, &DiveTripModel::divesMovedBetweenTrips); - connect(&diveListNotifier, &DiveListNotifier::divesTimeChanged, this, &DiveTripModel::divesTimeChanged); - connect(&diveListNotifier, &DiveListNotifier::divesSelected, this, &DiveTripModel::divesSelected); - connect(&diveListNotifier, &DiveListNotifier::divesDeselected, this, &DiveTripModel::divesDeselected); - connect(&diveListNotifier, &DiveListNotifier::currentDiveChanged, this, &DiveTripModel::currentDiveChanged); -} - -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.d_or_t.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 -{ - dive *d = diveOrNull(index); - Qt::ItemFlags base = Qt::ItemIsEnabled | Qt::ItemIsSelectable; - - // 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 +QVariant DiveTripModelBase::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) return QVariant(); @@ -431,33 +353,75 @@ QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, int return QVariant(); } -DiveTripModel::Item::Item(dive_trip *t, const QVector<dive *> &divesIn) : d_or_t{nullptr, t}, - dives(divesIn.toStdVector()) +static std::unique_ptr<DiveTripModelBase> currentModel; +DiveTripModelBase *DiveTripModelBase::instance() { + if (!currentModel) + resetModel(TREE); + return currentModel.get(); } -DiveTripModel::Item::Item(dive_trip *t, dive *d) : d_or_t{nullptr, t}, - dives({ d }) +void DiveTripModelBase::resetModel(DiveTripModelBase::Layout layout) { + if (layout == TREE) + currentModel.reset(new DiveTripModelTree); + else + currentModel.reset(new DiveTripModelList); } -DiveTripModel::Item::Item(dive *d) : d_or_t{d, nullptr} +DiveTripModelBase::DiveTripModelBase(QObject *parent) : QAbstractItemModel(parent) { } -bool DiveTripModel::Item::isDive(const dive *d) const +int DiveTripModelBase::columnCount(const QModelIndex&) const { - return d_or_t.dive == d; + return COLUMNS; } -dive *DiveTripModel::Item::getDive() const +Qt::ItemFlags DiveTripModelBase::flags(const QModelIndex &index) const { - return d_or_t.dive; + dive *d = diveOrNull(index); + Qt::ItemFlags base = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + + // Only dives have editable fields and only the number is editable + return d && index.column() == NR ? base | Qt::ItemIsEditable : base; } -timestamp_t DiveTripModel::Item::when() const +bool DiveTripModelBase::setData(const QModelIndex &index, const QVariant &value, int role) { - return d_or_t.trip ? trip_date(d_or_t.trip) : d_or_t.dive->when; + // We only support setting of data for dives and there, only the number. + dive *d = diveOrNull(index); + if (!d) + return false; + if (role != Qt::EditRole) + return false; + if (index.column() != NR) + return false; + + int v = value.toInt(); + if (v == 0) + return false; + + // Only accept numbers that are not already in use by other dives. + int i; + struct dive *dive; + for_each_dive (i, dive) { + if (dive->number == v) + return false; + } + d->number = v; + mark_divelist_changed(true); + return true; +} + +void DiveTripModelBase::divesSelected(dive_trip *trip, const QVector<dive *> &dives) +{ + changeDiveSelection(trip, dives, true); +} + +void DiveTripModelBase::divesDeselected(dive_trip *trip, const QVector<dive *> &dives) +{ + changeDiveSelection(trip, dives, false); } // Find a range of matching elements in a vector. @@ -547,19 +511,64 @@ void processRangesZip(Vector1 &items1, Vector2 &items2, Predicate cond, Action a }); } -void DiveTripModel::setupModelData() +// Add items from vector "v2" to vector "v1" in batches of contiguous objects. +// The items are inserted at places according to a sort order determined by "comp". +// "v1" and "v2" are supposed to be ordered accordingly. +// TODO: We might use binary search with std::lower_bound(), but not sure if it's worth it. +// Input parameters: +// - v1: destination vector +// - v2: source vector +// - comp: compare-function, which is fed elements from v2 and v1. returns true for "insert here". +// - adder: performs the insertion. Perameters: v1, v2, insertion index, from, to range in v2. +template <typename Vector1, typename Vector2, typename Comparator, typename Inserter> +void addInBatches(Vector1 &v1, const Vector2 &v2, Comparator comp, Inserter insert) { - beginResetModel(); + int idx = 0; // Index where dives will be inserted + int i, j; // Begin and end of range to insert + for (i = 0; i < (int)v2.size(); i = j) { + for (; idx < (int)v1.size() && !comp(v2[i], v1[idx]); ++idx) + ; // Pass + + // We found the index of the first item to add. + // Now search how many items we should insert there. + if (idx == (int)v1.size()) { + // We were at end -> insert the remaining items + j = v2.size(); + } else { + for (j = i + 1; j < (int)v2.size() && comp(v2[j], v1[idx]); ++j) + ; // Pass + } - items.clear(); + // Now add the batch + insert(v1, v2, idx, i, j); + + // Skip over inserted dives for searching the new insertion position plus one. + // If we added at the end, the loop will end anyway. + idx += j - i + 1; + } +} +// 2) TreeModel functions + +DiveTripModelTree::DiveTripModelTree(QObject *parent) : DiveTripModelBase(parent) +{ + // Stay informed of changes to the divelist + connect(&diveListNotifier, &DiveListNotifier::divesAdded, this, &DiveTripModelTree::divesAdded); + connect(&diveListNotifier, &DiveListNotifier::divesDeleted, this, &DiveTripModelTree::divesDeleted); + connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &DiveTripModelTree::divesChanged); + connect(&diveListNotifier, &DiveListNotifier::divesMovedBetweenTrips, this, &DiveTripModelTree::divesMovedBetweenTrips); + connect(&diveListNotifier, &DiveListNotifier::divesTimeChanged, this, &DiveTripModelTree::divesTimeChanged); + connect(&diveListNotifier, &DiveListNotifier::divesSelected, this, &DiveTripModelTree::divesSelected); + connect(&diveListNotifier, &DiveListNotifier::divesDeselected, this, &DiveTripModelTree::divesDeselected); + connect(&diveListNotifier, &DiveListNotifier::currentDiveChanged, this, &DiveTripModelTree::currentDiveChanged); + + // Fill model for (int i = 0; i < dive_table.nr ; ++i) { 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) { + // If this dive doesn't have a trip, add as top-level item. + if (!trip) { items.emplace_back(d); continue; } @@ -576,17 +585,85 @@ void DiveTripModel::setupModelData() it->dives.push_back(d); } } +} + +int DiveTripModelTree::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; - endResetModel(); + // Only trips have items + const Item &entry = items[parent.row()]; + return entry.d_or_t.trip ? entry.dives.size() : 0; +} + +static const quintptr noParent = ~(quintptr)0; // This is the "internalId" marker for top-level item + +QModelIndex DiveTripModelTree::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + // In the "internalId", we store either ~0 for 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 DiveTripModelTree::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); +} + +DiveTripModelTree::Item::Item(dive_trip *t, const QVector<dive *> &divesIn) : d_or_t{nullptr, t}, + dives(divesIn.toStdVector()) +{ +} + +DiveTripModelTree::Item::Item(dive_trip *t, dive *d) : d_or_t{nullptr, t}, + dives({ d }) +{ +} + +DiveTripModelTree::Item::Item(dive *d) : d_or_t{d, nullptr} +{ +} + +bool DiveTripModelTree::Item::isDive(const dive *d) const +{ + return d_or_t.dive == d; } -void DiveTripModel::setLayout(DiveTripModel::Layout layout) +dive *DiveTripModelTree::Item::getDive() const { - currentLayout = layout; - setupModelData(); + return d_or_t.dive; } -dive_or_trip DiveTripModel::tripOrDive(const QModelIndex &index) const +timestamp_t DiveTripModelTree::Item::when() const +{ + return d_or_t.trip ? trip_date(d_or_t.trip) : d_or_t.dive->when; +} + +dive_or_trip DiveTripModelTree::tripOrDive(const QModelIndex &index) const { if (!index.isValid()) return { nullptr, nullptr }; @@ -600,12 +677,12 @@ dive_or_trip DiveTripModel::tripOrDive(const QModelIndex &index) const return { items[parent.row()].dives[index.row()], nullptr }; } -dive *DiveTripModel::diveOrNull(const QModelIndex &index) const +dive *DiveTripModelTree::diveOrNull(const QModelIndex &index) const { return tripOrDive(index).dive; } -QVariant DiveTripModel::data(const QModelIndex &index, int role) const +QVariant DiveTripModelTree::data(const QModelIndex &index, int role) const { // Set the font for all items alike if (role == Qt::FontRole) @@ -620,71 +697,9 @@ QVariant DiveTripModel::data(const QModelIndex &index, int role) const return QVariant(); } -bool DiveTripModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - // We only support setting of data for dives and there, only the number. - dive *d = diveOrNull(index); - if (!d) - return false; - if (role != Qt::EditRole) - return false; - if (index.column() != NR) - return false; - - int v = value.toInt(); - if (v == 0) - return false; - - // Only accept numbers that are not already in use by other dives. - int i; - struct dive *dive; - for_each_dive (i, dive) { - if (dive->number == v) - return false; - } - d->number = v; - mark_divelist_changed(true); - return true; -} - -int DiveTripModel::findTripIdx(const dive_trip *trip) const -{ - for (int i = 0; i < (int)items.size(); ++i) - if (items[i].d_or_t.trip == trip) - return i; - return -1; -} - -int DiveTripModel::findDiveIdx(const dive *d) const -{ - for (int i = 0; i < (int)items.size(); ++i) - if (items[i].isDive(d)) - return i; - return -1; -} - -int DiveTripModel::findDiveInTrip(int tripIdx, const dive *d) const -{ - const Item &item = items[tripIdx]; - for (int i = 0; i < (int)item.dives.size(); ++i) - if (item.dives[i] == d) - return i; - return -1; -} - -int DiveTripModel::findInsertionIndex(const dive_trip *trip) const -{ - dive_or_trip d_or_t{ nullptr, (dive_trip *)trip }; - for (int i = 0; i < (int)items.size(); ++i) { - if (dive_or_trip_less_than(d_or_t, items[i].d_or_t)) - return i; - } - return items.size(); -} - -// After a top-level item changed (notably a trip), it might -// need to be reordered. Move the item and send a "data-changed" signal. -void DiveTripModel::topLevelChanged(int idx) +// After a trip changed, the top level might need to be reordered. +// Move the item and send a "data-changed" signal. +void DiveTripModelTree::topLevelChanged(int idx) { if (idx < 0 || idx >= (int)items.size()) return; @@ -713,44 +728,7 @@ void DiveTripModel::topLevelChanged(int idx) dataChanged(tripIdx, tripIdx); } -// Add items from vector "v2" to vector "v1" in batches of contiguous objects. -// The items are inserted at places according to a sort order determined by "comp". -// "v1" and "v2" are supposed to be ordered accordingly. -// TODO: We might use binary search with std::lower_bound(), but not sure if it's worth it. -// Input parameters: -// - v1: destination vector -// - v2: source vector -// - comp: compare-function, which is fed elements from v2 and v1. returns true for "insert here". -// - adder: performs the insertion. Perameters: v1, v2, insertion index, from, to range in v2. -template <typename Vector1, typename Vector2, typename Comparator, typename Inserter> -void addInBatches(Vector1 &v1, const Vector2 &v2, Comparator comp, Inserter insert) -{ - int idx = 0; // Index where dives will be inserted - int i, j; // Begin and end of range to insert - for (i = 0; i < (int)v2.size(); i = j) { - for (; idx < (int)v1.size() && !comp(v2[i], v1[idx]); ++idx) - ; // Pass - - // We found the index of the first item to add. - // Now search how many items we should insert there. - if (idx == (int)v1.size()) { - // We were at end -> insert the remaining items - j = v2.size(); - } else { - for (j = i + 1; j < (int)v2.size() && comp(v2[j], v1[idx]); ++j) - ; // Pass - } - - // Now add the batch - insert(v1, v2, idx, i, j); - - // Skip over inserted dives for searching the new insertion position plus one. - // If we added at the end, the loop will end anyway. - idx += j - i + 1; - } -} - -void DiveTripModel::addDivesToTrip(int trip, const QVector<dive *> &dives) +void DiveTripModelTree::addDivesToTrip(int trip, const QVector<dive *> &dives) { // Construct the parent index, ie. the index of the trip. QModelIndex parent = createIndex(trip, 0, noParent); @@ -769,22 +747,56 @@ void DiveTripModel::addDivesToTrip(int trip, const QVector<dive *> &dives) topLevelChanged(trip); } +int DiveTripModelTree::findTripIdx(const dive_trip *trip) const +{ + for (int i = 0; i < (int)items.size(); ++i) + if (items[i].d_or_t.trip == trip) + return i; + return -1; +} + +int DiveTripModelTree::findDiveIdx(const dive *d) const +{ + for (int i = 0; i < (int)items.size(); ++i) + if (items[i].isDive(d)) + return i; + return -1; +} + +int DiveTripModelTree::findDiveInTrip(int tripIdx, const dive *d) const +{ + const Item &item = items[tripIdx]; + for (int i = 0; i < (int)item.dives.size(); ++i) + if (item.dives[i] == d) + return i; + return -1; +} + +int DiveTripModelTree::findInsertionIndex(const dive_trip *trip) const +{ + dive_or_trip d_or_t{ nullptr, (dive_trip *)trip }; + for (int i = 0; i < (int)items.size(); ++i) { + if (dive_or_trip_less_than(d_or_t, items[i].d_or_t)) + return i; + } + return items.size(); +} + // This function is used to compare a dive to an arbitrary entry (dive or trip). // For comparing two dives, use the core function dive_less_than_entry, which // effectively sorts by timestamp. // If comparing to a trip, the policy for equal-times is to place the dives // before the trip in the case of equal timestamps. -bool DiveTripModel::dive_before_entry(const dive *d, const Item &entry) +bool DiveTripModelTree::dive_before_entry(const dive *d, const Item &entry) { dive_or_trip d_or_t { (dive *)d, nullptr }; return dive_or_trip_less_than(d_or_t, entry.d_or_t); } -void DiveTripModel::divesAdded(dive_trip *trip, bool addTrip, const QVector<dive *> &dives) +void DiveTripModelTree::divesAdded(dive_trip *trip, bool addTrip, const QVector<dive *> &dives) { - if (!trip || currentLayout == LIST) { - // Either this is outside of a trip or we're in list mode. - // Thus, add dives at the top-level in batches + if (!trip) { + // This is outside of a trip. Add dives at the top-level in batches. addInBatches(items, dives, &dive_before_entry, // comp [&](std::vector<Item> &items, const QVector<dive *> &dives, int idx, int from, int to) { // inserter @@ -805,7 +817,7 @@ void DiveTripModel::divesAdded(dive_trip *trip, bool addTrip, const QVector<dive if (idx < 0) { // We don't know the trip - this shouldn't happen. We seem to have // missed some signals! - qWarning() << "DiveTripModel::divesAdded(): unknown trip"; + qWarning() << "DiveTripModelTree::divesAdded(): unknown trip"; return; } @@ -814,11 +826,10 @@ void DiveTripModel::divesAdded(dive_trip *trip, bool addTrip, const QVector<dive } } -void DiveTripModel::divesDeleted(dive_trip *trip, bool deleteTrip, const QVector<dive *> &dives) +void DiveTripModelTree::divesDeleted(dive_trip *trip, bool deleteTrip, const QVector<dive *> &dives) { - if (!trip || currentLayout == LIST) { - // Either this is outside of a trip or we're in list mode. - // Thus, delete top-level dives. We do this range-wise. + if (!trip) { + // This is outside of a trip. Delete top-level dives in batches. processRangesZip(items, dives, [](const Item &e, dive *d) { return e.getDive() == d; }, // Condition [&](std::vector<Item> &items, const QVector<dive *> &, int from, int to, int) -> int { // Action @@ -833,7 +844,7 @@ void DiveTripModel::divesDeleted(dive_trip *trip, bool deleteTrip, const QVector if (idx < 0) { // We don't know the trip - this shouldn't happen. We seem to have // missed some signals! - qWarning() << "DiveTripModel::divesDeleted(): unknown trip"; + qWarning() << "DiveTripModelTree::divesDeleted(): unknown trip"; return; } @@ -863,11 +874,10 @@ void DiveTripModel::divesDeleted(dive_trip *trip, bool deleteTrip, const QVector } } -void DiveTripModel::divesChanged(dive_trip *trip, const QVector<dive *> &dives) +void DiveTripModelTree::divesChanged(dive_trip *trip, const QVector<dive *> &dives) { - if (!trip || currentLayout == LIST) { - // Either this is outside of a trip or we're in list mode. - // Thus, these are top-level dives. We do this range-wise. + if (!trip) { + // This is outside of a trip. Process top-level items range-wise. // Since we know that the dive list is sorted, we will only ever search for the first element // in dives as this must be the first that we encounter. Once we find a range, increase the @@ -885,13 +895,13 @@ void DiveTripModel::divesChanged(dive_trip *trip, const QVector<dive *> &dives) if (idx < 0) { // We don't know the trip - this shouldn't happen. We seem to have // missed some signals! - qWarning() << "DiveTripModel::divesChanged(): unknown trip"; + qWarning() << "DiveTripModelTree::divesChanged(): unknown trip"; return; } // Change the dives in the trip. We do this range-wise. processRangesZip(items[idx].dives, dives, - [](dive *d1, dive *d2) { return d1 == d2; }, // Condition + [](const dive *d1, const dive *d2) { return d1 == d2; }, // Condition (std::equal_to only in C++14) [&](const std::vector<dive *> &, const QVector<dive *> &, int from, int to, int) -> int { // Action // TODO: We might be smarter about which columns changed! dataChanged(createIndex(from, 0, idx), createIndex(to - 1, COLUMNS - 1, idx)); @@ -903,7 +913,7 @@ void DiveTripModel::divesChanged(dive_trip *trip, const QVector<dive *> &dives) } } -QVector<dive *> filterSelectedDives(const QVector<dive *> &dives) +static QVector<dive *> filterSelectedDives(const QVector<dive *> &dives) { QVector<dive *> res; res.reserve(dives.size()); @@ -913,7 +923,7 @@ QVector<dive *> filterSelectedDives(const QVector<dive *> &dives) return res; } -void DiveTripModel::divesMovedBetweenTrips(dive_trip *from, dive_trip *to, bool deleteFrom, bool createTo, const QVector<dive *> &dives) +void DiveTripModelTree::divesMovedBetweenTrips(dive_trip *from, dive_trip *to, bool deleteFrom, bool createTo, const QVector<dive *> &dives) { // Move dives between trips. This is an "interesting" problem, as we might // move from trip to trip, from trip to top-level or from top-level to trip. @@ -922,9 +932,8 @@ void DiveTripModel::divesMovedBetweenTrips(dive_trip *from, dive_trip *to, bool // functions. This *is* cheating. But let's just try this and see how graceful // this is handled by Qt and if it gives some ugly UI behavior! - // But first let's just rule out the trivial cases: same-to-same trip move - // and list view (in which case we don't care). - if (from == to || currentLayout == LIST) + // But first let's just rule out the trivial case: same-to-same trip move. + if (from == to) return; // Cheating! @@ -936,13 +945,13 @@ void DiveTripModel::divesMovedBetweenTrips(dive_trip *from, dive_trip *to, bool divesSelected(to, selectedDives); } -void DiveTripModel::divesTimeChanged(dive_trip *trip, timestamp_t delta, const QVector<dive *> &dives) +void DiveTripModelTree::divesTimeChanged(dive_trip *trip, timestamp_t delta, const QVector<dive *> &dives) { // As in the case of divesMovedBetweenTrips(), this is a tricky, but solvable, problem. // We have to consider the direction (delta < 0 or delta >0) and that dives at their destination // position have different contiguous batches than at their original position. For now, // cheat and simply do a remove/add pair. Note that for this to work it is crucial the the - // order of the dives don't change. This is indeed the case, as all starting-times where + // order of the dives don't change. This is indeed the case, as all starting-times were // moved by the same delta. // Cheating! @@ -954,25 +963,15 @@ void DiveTripModel::divesTimeChanged(dive_trip *trip, timestamp_t delta, const Q divesSelected(trip, selectedDives); } -void DiveTripModel::divesSelected(dive_trip *trip, const QVector<dive *> &dives) -{ - changeDiveSelection(trip, dives, true); -} - -void DiveTripModel::divesDeselected(dive_trip *trip, const QVector<dive *> &dives) -{ - changeDiveSelection(trip, dives, false); -} - -void DiveTripModel::changeDiveSelection(dive_trip *trip, const QVector<dive *> &dives, bool select) +void DiveTripModelTree::changeDiveSelection(dive_trip *trip, const QVector<dive *> &dives, bool select) { // We got a number of dives that have been selected. Turn this into QModelIndexes and // emit a signal, so that views can change the selection. QVector<QModelIndex> indexes; indexes.reserve(dives.count()); - if (!trip || currentLayout == LIST) { - // Either this is outside of a trip or we're in list mode. + if (!trip) { + // This is at the top level. // Since both lists are sorted, we can do this linearly. Perhaps a binary search // would be better? int j = 0; // Index in items array @@ -989,7 +988,7 @@ void DiveTripModel::changeDiveSelection(dive_trip *trip, const QVector<dive *> & if (idx < 0) { // We don't know the trip - this shouldn't happen. We seem to have // missed some signals! - qWarning() << "DiveTripModel::divesSelected(): unknown trip"; + qWarning() << "DiveTripModelTree::changeDiveSelection(): unknown trip"; return; } // Locate the indices inside the trip. @@ -1009,7 +1008,7 @@ void DiveTripModel::changeDiveSelection(dive_trip *trip, const QVector<dive *> & emit selectionChanged(indexes, select); } -void DiveTripModel::currentDiveChanged() +void DiveTripModelTree::currentDiveChanged() { // The current dive has changed. Transform the current dive into an index and pass it on to the view. if (!current_dive) { @@ -1018,12 +1017,12 @@ void DiveTripModel::currentDiveChanged() } dive_trip *trip = current_dive->divetrip; - if (!trip || currentLayout == LIST) { - // Either this is outside of a trip or we're in list mode. + if (!trip) { + // Outside of a trip - search top-level. int idx = findDiveIdx(current_dive); if (idx < 0) { // We don't know this dive. Something is wrong. Warn and bail. - qWarning() << "DiveTripModel::currentDiveChanged(): unknown top-level dive"; + qWarning() << "DiveTripModelTree::currentDiveChanged(): unknown top-level dive"; emit newCurrentDive(QModelIndex()); return; } @@ -1032,14 +1031,14 @@ void DiveTripModel::currentDiveChanged() int idx = findTripIdx(trip); if (idx < 0) { // We don't know the trip - this shouldn't happen. Warn and bail. - qWarning() << "DiveTripModel::currentDiveChanged(): unknown trip"; + qWarning() << "DiveTripModelTree::currentDiveChanged(): unknown trip"; emit newCurrentDive(QModelIndex()); return; } int diveIdx = findDiveInTrip(idx, current_dive); if (diveIdx < 0) { // We don't know this dive. Something is wrong. Warn and bail. - qWarning() << "DiveTripModel::currentDiveChanged(): unknown top-level dive"; + qWarning() << "DiveTripModelTree::currentDiveChanged(): unknown top-level dive"; emit newCurrentDive(QModelIndex()); return; } @@ -1047,18 +1046,175 @@ void DiveTripModel::currentDiveChanged() } } -void DiveTripModel::filterFinished() +void DiveTripModelTree::filterFinished() { // If the filter finished, update all trip items to show the correct number of displayed dives // in each trip. Without doing this, only trip headers of expanded trips were updated. - if (currentLayout == LIST) - return; // No trips in list mode for (int idx = 0; idx < (int)items.size(); ++idx) { QModelIndex tripIndex = createIndex(idx, 0, noParent); dataChanged(tripIndex, tripIndex); } } +bool DiveTripModelTree::lessThan(const QModelIndex &i1, const QModelIndex &i2) const +{ + // In tree mode we don't support any sorting! + // Simply keep the original position. + return i1.row() < i2.row(); +} + +// 3) ListModel functions + +DiveTripModelList::DiveTripModelList(QObject *parent) : DiveTripModelBase(parent) +{ + // Stay informed of changes to the divelist + connect(&diveListNotifier, &DiveListNotifier::divesAdded, this, &DiveTripModelList::divesAdded); + connect(&diveListNotifier, &DiveListNotifier::divesDeleted, this, &DiveTripModelList::divesDeleted); + connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &DiveTripModelList::divesChanged); + // Does nothing in list-view + //connect(&diveListNotifier, &DiveListNotifier::divesMovedBetweenTrips, this, &DiveTripModelList::divesMovedBetweenTrips); + connect(&diveListNotifier, &DiveListNotifier::divesTimeChanged, this, &DiveTripModelList::divesTimeChanged); + connect(&diveListNotifier, &DiveListNotifier::divesSelected, this, &DiveTripModelList::divesSelected); + connect(&diveListNotifier, &DiveListNotifier::divesDeselected, this, &DiveTripModelList::divesDeselected); + connect(&diveListNotifier, &DiveListNotifier::currentDiveChanged, this, &DiveTripModelList::currentDiveChanged); + + // Fill model + items.reserve(dive_table.nr); + for (int i = 0; i < dive_table.nr ; ++i) + items.push_back(get_dive(i)); +} + +int DiveTripModelList::rowCount(const QModelIndex &parent) const +{ + // In list-mode there is only one level, i.e only top-level + // (=invalid parent) has items. + return parent.isValid() ? 0 : items.size(); +} + +QModelIndex DiveTripModelList::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + return createIndex(row, column); +} + +QModelIndex DiveTripModelList::parent(const QModelIndex &index) const +{ + // In list-mode there is only one level, i.e. no parent + return QModelIndex(); +} + +dive *DiveTripModelList::diveOrNull(const QModelIndex &index) const +{ + int row = index.row(); + if (row < 0 || row > (int)items.size()) + return nullptr; + return items[row]; +} + +QVariant DiveTripModelList::data(const QModelIndex &index, int role) const +{ + // Set the font for all items alike + if (role == Qt::FontRole) + return defaultModelFont(); + + dive *d = diveOrNull(index); + return d ? diveData(d, index.column(), role) : QVariant(); +} + +void DiveTripModelList::divesAdded(dive_trip *, bool, const QVector<dive *> &dives) +{ + addInBatches(items, dives, + &dive_less_than, // comp + [&](std::vector<dive *> &items, const QVector<dive *> &dives, int idx, int from, int to) { // inserter + beginInsertRows(QModelIndex(), idx, idx + to - from - 1); + items.insert(items.begin() + idx, dives.begin() + from, dives.begin() + to); + endInsertRows(); + }); +} + +void DiveTripModelList::divesDeleted(dive_trip *trip, bool deleteTrip, const QVector<dive *> &dives) +{ + processRangesZip(items, dives, + [](const dive *d1, const dive *d2) { return d1 == d2; }, // Condition (std::equal_to only in C++14) + [&](std::vector<dive *> &items, const QVector<dive *> &, int from, int to, int) -> int { // Action + beginRemoveRows(QModelIndex(), from, to - 1); + items.erase(items.begin() + from, items.begin() + to); + endRemoveRows(); + return from - to; // Delta: negate the number of items deleted + }); +} + +void DiveTripModelList::divesChanged(dive_trip *trip, const QVector<dive *> &dives) +{ + // Since we know that the dive list is sorted, we will only ever search for the first element + // in dives as this must be the first that we encounter. Once we find a range, increase the + // index accordingly. + processRangesZip(items, dives, + [](const dive *d1, const dive *d2) { return d1 == d2; }, // Condition (std::equal_to only in C++14) + [&](const std::vector<dive *> &, const QVector<dive *> &, int from, int to, int) -> int { // Action + // TODO: We might be smarter about which columns changed! + dataChanged(createIndex(from, 0, noParent), createIndex(to - 1, COLUMNS - 1, noParent)); + return 0; // No items added or deleted + }); +} + +void DiveTripModelList::divesTimeChanged(dive_trip *trip, timestamp_t delta, const QVector<dive *> &dives) +{ + // See comment for DiveTripModelTree::divesTimeChanged above. + QVector<dive *> selectedDives = filterSelectedDives(dives); + divesDeleted(trip, false, dives); + divesAdded(trip, false, dives); + divesSelected(trip, selectedDives); +} + +void DiveTripModelList::changeDiveSelection(dive_trip *trip, const QVector<dive *> &dives, bool select) +{ + // We got a number of dives that have been selected. Turn this into QModelIndexes and + // emit a signal, so that views can change the selection. + QVector<QModelIndex> indexes; + indexes.reserve(dives.count()); + + // Since both lists are sorted, we can do this linearly. Perhaps a binary search + // would be better? + int j = 0; // Index in items array + for (int i = 0; i < dives.size(); ++i) { + while (j < (int)items.size() && items[j] != dives[i]) + ++j; + if (j >= (int)items.size()) + break; + indexes.append(createIndex(j, 0, noParent)); + } + + emit selectionChanged(indexes, select); +} + +void DiveTripModelList::currentDiveChanged() +{ + // The current dive has changed. Transform the current dive into an index and pass it on to the view. + if (!current_dive) { + emit newCurrentDive(QModelIndex()); // No current dive -> tell view to clear current index with an invalid index + return; + } + + // Either this is outside of a trip or we're in list mode. + auto it = std::find(items.begin(), items.end(), current_dive); + if (it == items.end()) { + // We don't know this dive. Something is wrong. Warn and bail. + qWarning() << "DiveTripModelList::currentDiveChanged(): unknown top-level dive"; + emit newCurrentDive(QModelIndex()); + return; + } + emit newCurrentDive(createIndex(it - items.begin(), 0)); +} + +void DiveTripModelList::filterFinished() +{ + // In list mode, we don't have to change anything after filter finished. +} + + // Simple sorting helper for sorting against a criterium and if // that is undefined against a different criterium. // Return true if diff1 < 0, false if diff1 > 0. @@ -1077,22 +1233,15 @@ static int strCmp(const char *s1, const char *s2) return QString::localeAwareCompare(QString(s1), QString(s2)); // TODO: avoid copy } -bool DiveTripModel::lessThan(const QModelIndex &i1, const QModelIndex &i2) const +bool DiveTripModelList::lessThan(const QModelIndex &i1, const QModelIndex &i2) const { - if (currentLayout != LIST) { - // In tree mode we don't support any sorting! - // Simply keep the original position. - return i1.row() < i2.row(); - } - // We assume that i1.column() == i2.column(). - // We are in list mode, so we know that we only have dives. int row1 = i1.row(); int row2 = i2.row(); if (row1 < 0 || row1 >= (int)items.size() || row2 < 0 || row2 >= (int)items.size()) return false; - const dive *d1 = items[i1.row()].d_or_t.dive; - const dive *d2 = items[i2.row()].d_or_t.dive; + const dive *d1 = items[row1]; + const dive *d2 = items[row2]; // This is used as a second sort criterion: For equal values, sorting is chronologically *descending*. int row_diff = row2 - row1; switch (i1.column()) { |