diff options
-rw-r--r-- | desktop-widgets/divelistview.cpp | 122 | ||||
-rw-r--r-- | desktop-widgets/divelistview.h | 3 | ||||
-rw-r--r-- | desktop-widgets/mainwindow.cpp | 2 | ||||
-rw-r--r-- | desktop-widgets/modeldelegates.cpp | 2 | ||||
-rw-r--r-- | qt-models/divetripmodel.cpp | 725 | ||||
-rw-r--r-- | qt-models/divetripmodel.h | 127 | ||||
-rw-r--r-- | qt-models/filtermodels.cpp | 17 | ||||
-rw-r--r-- | qt-models/filtermodels.h | 5 |
8 files changed, 612 insertions, 391 deletions
diff --git a/desktop-widgets/divelistview.cpp b/desktop-widgets/divelistview.cpp index c340b556b..3ba9ba58f 100644 --- a/desktop-widgets/divelistview.cpp +++ b/desktop-widgets/divelistview.cpp @@ -28,20 +28,20 @@ #include "desktop-widgets/simplewidgets.h" DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelection(false), - currentLayout(DiveTripModel::TREE), dontEmitDiveChangedSignal(false), selectionSaved(false), - initialColumnWidths(DiveTripModel::COLUMNS, 50) // Set up with default length 50 + currentLayout(DiveTripModelBase::TREE), dontEmitDiveChangedSignal(false), selectionSaved(false), + initialColumnWidths(DiveTripModelBase::COLUMNS, 50) // Set up with default length 50 { setItemDelegate(new DiveListDelegate(this)); setUniformRowHeights(true); - setItemDelegateForColumn(DiveTripModel::RATING, new StarWidgetsDelegate(this)); + setItemDelegateForColumn(DiveTripModelBase::RATING, new StarWidgetsDelegate(this)); setModel(MultiFilterSortModel::instance()); setSortingEnabled(true); setContextMenuPolicy(Qt::DefaultContextMenu); setSelectionMode(ExtendedSelection); header()->setContextMenuPolicy(Qt::ActionsContextMenu); - connect(DiveTripModel::instance(), &DiveTripModel::selectionChanged, this, &DiveListView::diveSelectionChanged); - connect(DiveTripModel::instance(), &DiveTripModel::newCurrentDive, this, &DiveListView::currentDiveChanged); + + resetModel(); // Update selection if all selected dives were hidden by filter connect(MultiFilterSortModel::instance(), &MultiFilterSortModel::filterFinished, this, &DiveListView::filterFinished); @@ -53,7 +53,7 @@ DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelec installEventFilter(this); - for (int i = DiveTripModel::NR; i < DiveTripModel::COLUMNS; i++) + for (int i = DiveTripModelBase::NR; i < DiveTripModelBase::COLUMNS; i++) calculateInitialColumnWidth(i); setColumnWidths(); } @@ -63,7 +63,7 @@ DiveListView::~DiveListView() QSettings settings; settings.beginGroup("ListWidget"); // don't set a width for the last column - location is supposed to be "the rest" - for (int i = DiveTripModel::NR; i < DiveTripModel::COLUMNS - 1; i++) { + for (int i = DiveTripModelBase::NR; i < DiveTripModelBase::COLUMNS - 1; i++) { if (isColumnHidden(i)) continue; // we used to hardcode them all to 100 - so that might still be in the settings @@ -72,41 +72,50 @@ DiveListView::~DiveListView() else settings.setValue(QString("colwidth%1").arg(i), columnWidth(i)); } - settings.remove(QString("colwidth%1").arg(DiveTripModel::COLUMNS - 1)); + settings.remove(QString("colwidth%1").arg(DiveTripModelBase::COLUMNS - 1)); settings.endGroup(); } +void DiveListView::resetModel() +{ + MultiFilterSortModel::instance()->resetModel(currentLayout); + // If the model was reset, we have to reconnect the signals and tell + // the filter model to update its source model. + connect(DiveTripModelBase::instance(), &DiveTripModelBase::selectionChanged, this, &DiveListView::diveSelectionChanged); + connect(DiveTripModelBase::instance(), &DiveTripModelBase::newCurrentDive, this, &DiveListView::currentDiveChanged); +} + void DiveListView::calculateInitialColumnWidth(int col) { const QFontMetrics metrics(defaultModelFont()); int em = metrics.width('m'); int zw = metrics.width('0'); - QString header_txt = DiveTripModel::instance()->headerData(col, Qt::Horizontal, Qt::DisplayRole).toString(); + QString header_txt = DiveTripModelBase::instance()->headerData(col, Qt::Horizontal, Qt::DisplayRole).toString(); int width = metrics.width(header_txt); int sw = 0; switch (col) { - case DiveTripModel::NR: - case DiveTripModel::DURATION: + case DiveTripModelBase::NR: + case DiveTripModelBase::DURATION: sw = 8*zw; break; - case DiveTripModel::DATE: + case DiveTripModelBase::DATE: sw = 14*em; break; - case DiveTripModel::RATING: + case DiveTripModelBase::RATING: sw = static_cast<StarWidgetsDelegate*>(itemDelegateForColumn(col))->starSize().width(); break; - case DiveTripModel::SUIT: - case DiveTripModel::SAC: + case DiveTripModelBase::SUIT: + case DiveTripModelBase::SAC: sw = 7*em; break; - case DiveTripModel::PHOTOS: + case DiveTripModelBase::PHOTOS: sw = 5*em; break; - case DiveTripModel::BUDDIES: + case DiveTripModelBase::BUDDIES: sw = 50*em; break; - case DiveTripModel::LOCATION: + case DiveTripModelBase::LOCATION: sw = 50*em; break; default: @@ -126,7 +135,7 @@ void DiveListView::setColumnWidths() /* if no width are set, use the calculated width for each column; * for that to work we need to temporarily expand all rows */ expandAll(); - for (int i = DiveTripModel::NR; i < DiveTripModel::COLUMNS; i++) { + for (int i = DiveTripModelBase::NR; i < DiveTripModelBase::COLUMNS; i++) { if (isColumnHidden(i)) continue; QVariant width = settings.value(QString("colwidth%1").arg(i)); @@ -143,7 +152,7 @@ void DiveListView::setColumnWidths() int DiveListView::lastVisibleColumn() { int lastColumn = -1; - for (int i = DiveTripModel::NR; i < DiveTripModel::COLUMNS; i++) { + for (int i = DiveTripModelBase::NR; i < DiveTripModelBase::COLUMNS; i++) { if (isColumnHidden(i)) continue; lastColumn = i; @@ -249,11 +258,11 @@ void DiveListView::rememberSelection() Q_FOREACH (const QModelIndex &index, selection.indexes()) { if (index.column() != 0) // We only care about the dives, so, let's stick to rows and discard columns. continue; - struct dive *d = index.data(DiveTripModel::DIVE_ROLE).value<struct dive *>(); + struct dive *d = index.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>(); if (d) { selectedDives.insert(d->divetrip, get_divenr(d)); } else { - struct dive_trip *t = index.data(DiveTripModel::TRIP_ROLE).value<dive_trip *>(); + struct dive_trip *t = index.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>(); if (t) selectedDives.insert(t, -1); } @@ -289,7 +298,7 @@ void DiveListView::selectTrip(dive_trip_t *trip) return; QAbstractItemModel *m = model(); - QModelIndexList match = m->match(m->index(0, 0), DiveTripModel::TRIP_ROLE, QVariant::fromValue(trip), 2, Qt::MatchRecursive); + QModelIndexList match = m->match(m->index(0, 0), DiveTripModelBase::TRIP_ROLE, QVariant::fromValue(trip), 2, Qt::MatchRecursive); QItemSelectionModel::SelectionFlags flags; if (!match.count()) return; @@ -314,7 +323,7 @@ void DiveListView::clearTripSelection() // we want to make sure no trips are selected Q_FOREACH (const QModelIndex &index, selectionModel()->selectedRows()) { - dive_trip_t *trip = index.data(DiveTripModel::TRIP_ROLE).value<dive_trip *>(); + dive_trip_t *trip = index.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>(); if (!trip) continue; selectionModel()->select(index, QItemSelectionModel::Deselect); @@ -343,7 +352,7 @@ QList<dive_trip_t *> DiveListView::selectedTrips() { QList<dive_trip_t *> ret; Q_FOREACH (const QModelIndex &index, selectionModel()->selectedRows()) { - dive_trip_t *trip = index.data(DiveTripModel::TRIP_ROLE).value<dive_trip *>(); + dive_trip_t *trip = index.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>(); if (!trip) continue; ret.push_back(trip); @@ -374,7 +383,7 @@ void DiveListView::selectDive(int i, bool scrollto, bool toggle) if (i == -1) return; QAbstractItemModel *m = model(); - QModelIndexList match = m->match(m->index(0, 0), DiveTripModel::DIVE_IDX, i, 2, Qt::MatchRecursive); + QModelIndexList match = m->match(m->index(0, 0), DiveTripModelBase::DIVE_IDX, i, 2, Qt::MatchRecursive); if (match.isEmpty()) return; QModelIndex idx = match.first(); @@ -411,7 +420,7 @@ void DiveListView::selectDives(const QList<int> &newDiveSelection) selectDive(newSelection); } QAbstractItemModel *m = model(); - QModelIndexList idxList = m->match(m->index(0, 0), DiveTripModel::DIVE_IDX, get_divenr(current_dive), 2, Qt::MatchRecursive); + QModelIndexList idxList = m->match(m->index(0, 0), DiveTripModelBase::DIVE_IDX, get_divenr(current_dive), 2, Qt::MatchRecursive); if (!idxList.isEmpty()) { QModelIndex idx = idxList.first(); if (idx.parent().isValid()) @@ -462,7 +471,7 @@ bool DiveListView::eventFilter(QObject *, QEvent *event) void DiveListView::sortIndicatorChanged(int i, Qt::SortOrder order) { - DiveTripModel::Layout newLayout = i == (int)DiveTripModel::NR ? DiveTripModel::TREE : DiveTripModel::LIST; + DiveTripModelBase::Layout newLayout = i == (int)DiveTripModelBase::NR ? DiveTripModelBase::TREE : DiveTripModelBase::LIST; /* No layout change? Just re-sort, and scroll to first selection, making sure all selections are expanded */ if (currentLayout == newLayout) { sortByColumn(i, order); @@ -470,12 +479,12 @@ void DiveListView::sortIndicatorChanged(int i, Qt::SortOrder order) // clear the model, repopulate with new indexes. rememberSelection(); unselectDives(); - if (currentLayout == DiveTripModel::TREE) + if (currentLayout == DiveTripModelBase::TREE) backupExpandedRows(); currentLayout = newLayout; - MultiFilterSortModel::instance()->setLayout(newLayout); + resetModel(); sortByColumn(i, order); - if (newLayout == DiveTripModel::TREE) + if (newLayout == DiveTripModelBase::TREE) restoreExpandedRows(); restoreSelection(); } @@ -489,8 +498,7 @@ void DiveListView::setSortOrder(int i, Qt::SortOrder order) void DiveListView::reload() { - // A side-effect of setting the layout is reloading the model data - MultiFilterSortModel::instance()->setLayout(currentLayout); + resetModel(); if (amount_selected && current_dive != NULL) selectDive(get_divenr(current_dive), true); @@ -519,15 +527,15 @@ void DiveListView::reloadHeaderActions() QString title = QString("%1").arg(model()->headerData(i, Qt::Horizontal).toString()); QString settingName = QString("showColumn%1").arg(i); QAction *a = new QAction(title, header()); - bool showHeaderFirstRun = !(i == DiveTripModel::MAXCNS || - i == DiveTripModel::GAS || - i == DiveTripModel::OTU || - i == DiveTripModel::TEMPERATURE || - i == DiveTripModel::TOTALWEIGHT || - i == DiveTripModel::SUIT || - i == DiveTripModel::CYLINDER || - i == DiveTripModel::SAC || - i == DiveTripModel::TAGS); + bool showHeaderFirstRun = !(i == DiveTripModelBase::MAXCNS || + i == DiveTripModelBase::GAS || + i == DiveTripModelBase::OTU || + i == DiveTripModelBase::TEMPERATURE || + i == DiveTripModelBase::TOTALWEIGHT || + i == DiveTripModelBase::SUIT || + i == DiveTripModelBase::CYLINDER || + i == DiveTripModelBase::SAC || + i == DiveTripModelBase::TAGS); bool shown = s.value(settingName, showHeaderFirstRun).toBool(); a->setCheckable(true); a->setChecked(shown); @@ -608,9 +616,9 @@ void DiveListView::selectionChanged(const QItemSelection &selected, const QItemS if (index.column() != 0) continue; const QAbstractItemModel *model = index.model(); - struct dive *dive = model->data(index, DiveTripModel::DIVE_ROLE).value<struct dive *>(); + struct dive *dive = model->data(index, DiveTripModelBase::DIVE_ROLE).value<struct dive *>(); if (!dive) // it's a trip! - deselect_dives_in_trip(model->data(index, DiveTripModel::TRIP_ROLE).value<dive_trip *>()); + deselect_dives_in_trip(model->data(index, DiveTripModelBase::TRIP_ROLE).value<dive_trip *>()); else deselect_dive(dive); } @@ -619,11 +627,11 @@ void DiveListView::selectionChanged(const QItemSelection &selected, const QItemS continue; const QAbstractItemModel *model = index.model(); - struct dive *dive = model->data(index, DiveTripModel::DIVE_ROLE).value<struct dive *>(); + struct dive *dive = model->data(index, DiveTripModelBase::DIVE_ROLE).value<struct dive *>(); if (!dive) { // it's a trip! if (model->rowCount(index)) { QItemSelection selection; - select_dives_in_trip(model->data(index, DiveTripModel::TRIP_ROLE).value<dive_trip *>()); + select_dives_in_trip(model->data(index, DiveTripModelBase::TRIP_ROLE).value<dive_trip *>()); selection.select(index.child(0, 0), index.child(model->rowCount(index) - 1, 0)); selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows); selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select | QItemSelectionModel::NoUpdate); @@ -725,8 +733,8 @@ void DiveListView::merge_trip(const QModelIndex &a, int offset) int i = a.row() + offset; QModelIndex b = a.sibling(i, 0); - dive_trip_t *trip_a = a.data(DiveTripModel::TRIP_ROLE).value<dive_trip *>(); - dive_trip_t *trip_b = b.data(DiveTripModel::TRIP_ROLE).value<dive_trip *>(); + dive_trip_t *trip_a = a.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>(); + dive_trip_t *trip_b = b.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>(); if (trip_a == trip_b || !trip_a || !trip_b) return; Command::mergeTrips(trip_a, trip_b); @@ -757,7 +765,7 @@ void DiveListView::removeFromTrip() void DiveListView::newTripAbove() { - struct dive *d = contextMenuIndex.data(DiveTripModel::DIVE_ROLE).value<struct dive *>(); + struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>(); if (!d) // shouldn't happen as we only are setting up this action if this is a dive return; //TODO: port to c-code. @@ -783,7 +791,7 @@ void DiveListView::addToTripAbove() void DiveListView::addToTrip(int delta) { // d points to the row that has (mouse-)pointer focus, and there are nr rows selected - struct dive *d = contextMenuIndex.data(DiveTripModel::DIVE_ROLE).value<struct dive *>(); + struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>(); int nr = selectionModel()->selectedRows().count(); QModelIndex t; dive_trip_t *trip = NULL; @@ -792,7 +800,7 @@ void DiveListView::addToTrip(int delta) // check if its sibling is a trip. for (int i = 1; i <= nr; i++) { t = contextMenuIndex.sibling(contextMenuIndex.row() + (delta > 0 ? i: i * -1), 0); - trip = t.data(DiveTripModel::TRIP_ROLE).value<dive_trip *>(); + trip = t.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>(); if (trip) break; } @@ -815,7 +823,7 @@ void DiveListView::addToTrip(int delta) void DiveListView::markDiveInvalid() { int i; - struct dive *d = contextMenuIndex.data(DiveTripModel::DIVE_ROLE).value<struct dive *>(); + struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>(); if (!d) return; for_each_dive (i, d) { @@ -834,7 +842,7 @@ void DiveListView::markDiveInvalid() void DiveListView::deleteDive() { - struct dive *d = contextMenuIndex.data(DiveTripModel::DIVE_ROLE).value<struct dive *>(); + struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>(); if (!d) return; @@ -852,17 +860,17 @@ void DiveListView::contextMenuEvent(QContextMenuEvent *event) QAction *collapseAction = NULL; // let's remember where we are contextMenuIndex = indexAt(event->pos()); - struct dive *d = contextMenuIndex.data(DiveTripModel::DIVE_ROLE).value<struct dive *>(); - dive_trip_t *trip = contextMenuIndex.data(DiveTripModel::TRIP_ROLE).value<dive_trip *>(); + struct dive *d = contextMenuIndex.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>(); + dive_trip_t *trip = contextMenuIndex.data(DiveTripModelBase::TRIP_ROLE).value<dive_trip *>(); QMenu popup(this); - if (currentLayout == DiveTripModel::TREE) { + if (currentLayout == DiveTripModelBase::TREE) { // verify if there is a node that`s not expanded. bool needs_expand = false; bool needs_collapse = false; uint expanded_nodes = 0; for(int i = 0, end = model()->rowCount(); i < end; i++) { QModelIndex idx = model()->index(i, 0); - if (idx.data(DiveTripModel::DIVE_ROLE).value<struct dive *>()) + if (idx.data(DiveTripModelBase::DIVE_ROLE).value<struct dive *>()) continue; if (!isExpanded(idx)) { diff --git a/desktop-widgets/divelistview.h b/desktop-widgets/divelistview.h index 85d8bd21c..830447d92 100644 --- a/desktop-widgets/divelistview.h +++ b/desktop-widgets/divelistview.h @@ -68,7 +68,7 @@ slots: private: bool mouseClickSelection; QList<int> expandedRows; - DiveTripModel::Layout currentLayout; + DiveTripModelBase::Layout currentLayout; QModelIndex contextMenuIndex; bool dontEmitDiveChangedSignal; bool selectionSaved; @@ -77,6 +77,7 @@ private: /* if dive_trip_t is null, there's no problem. */ QMultiHash<dive_trip_t *, int> selectedDives; + void resetModel(); // Call after model changed void merge_trip(const QModelIndex &a, const int offset); void setColumnWidths(); void calculateInitialColumnWidth(int col); diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 6c0b42c9d..aba1cfc63 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -702,7 +702,7 @@ void MainWindow::cleanUpEmpty() mainTab->updateDiveInfo(true); graphics->setEmptyState(); diveList->reload(); - diveList->setSortOrder(DiveTripModel::NR, Qt::DescendingOrder); + diveList->setSortOrder(DiveTripModelBase::NR, Qt::DescendingOrder); MapWidget::instance()->reload(); if (!existing_filename) setTitle(); diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index 41fe1c083..a237c1306 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -52,7 +52,7 @@ void StarWidgetsDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o if (!index.isValid()) return; - QVariant value = index.model()->data(index, DiveTripModel::STAR_ROLE); + QVariant value = index.model()->data(index, DiveTripModelBase::STAR_ROLE); if (!value.isValid()) return; 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()) { diff --git a/qt-models/divetripmodel.h b/qt-models/divetripmodel.h index aab154069..67a4f090d 100644 --- a/qt-models/divetripmodel.h +++ b/qt-models/divetripmodel.h @@ -5,7 +5,23 @@ #include "core/dive.h" #include <QAbstractItemModel> -class DiveTripModel : public QAbstractItemModel { +// There are two different representations of the dive list: +// 1) Tree view: two-level model where dives are grouped by trips +// 2) List view: one-level model where dives are sorted by one out +// of many keys (e.g. date, depth, etc.). +// +// These two representations are realized by two classe, viz. +// DiveTripModelTree and DiveTripModelList. Both classes derive +// from DiveTripModelBase, which implements common features (e.g. +// definition of the column types, access of data from the core +// structures) and a common interface. +// +// The currently active model is set via DiveTripModelBase::resetModel(). +// This will create a new model. The model can be accessed with +// DiveTripModelBase::instance(). Any pointer obtained by instance() +// is invalid after a call to resetModel()! Yes, this is surprising +// behavior, so care must be taken. +class DiveTripModelBase : public QAbstractItemModel { Q_OBJECT public: enum Column { @@ -40,25 +56,28 @@ public: enum Layout { TREE, LIST, - CURRENT }; - static DiveTripModel *instance(); + // Functions implemented by base class + static DiveTripModelBase *instance(); + + // Reset the model using the given layout. After this call instance() will return + // a newly allocated object and the old model will have been destroyed! Thus, the + // caller is repsonsible of removing all references to any previous model obtained + // by insance(). + static void resetModel(Layout layout); + Qt::ItemFlags flags(const QModelIndex &index) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - DiveTripModel(QObject *parent = 0); - void setLayout(Layout layout); - QVariant data(const QModelIndex &index, int role) const; + DiveTripModelBase(QObject *parent = 0); 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; - void filterFinished(); + virtual void filterFinished() = 0; // Used for sorting. This is a bit of a layering violation, as sorting should be performed // by the higher-up QSortFilterProxyModel, but it makes things so much easier! - bool lessThan(const QModelIndex &i1, const QModelIndex &i2) const; + virtual bool lessThan(const QModelIndex &i1, const QModelIndex &i2) const = 0; + signals: // The propagation of selection changes is complex. // The control flow of dive-selection goes: @@ -69,17 +88,44 @@ signals: // perform the appropriate actions. void selectionChanged(const QVector<QModelIndex> &indexes, bool select); void newCurrentDive(QModelIndex index); -private slots: +protected slots: + void divesSelected(dive_trip *trip, const QVector<dive *> &dives); + void divesDeselected(dive_trip *trip, const QVector<dive *> &dives); +protected: + // Access trip and dive data + static QVariant diveData(const struct dive *d, int column, int role); + static QVariant tripData(const dive_trip *trip, int column, int role); + + // Select or deselect dives + virtual void changeDiveSelection(dive_trip *trip, const QVector<dive *> &dives, bool select) = 0; + + virtual dive *diveOrNull(const QModelIndex &index) const = 0; // Returns a dive if this index represents a dive, null otherwise +}; + +class DiveTripModelTree : public DiveTripModelBase +{ + Q_OBJECT +public slots: void divesAdded(dive_trip *trip, bool addTrip, const QVector<dive *> &dives); void divesDeleted(dive_trip *trip, bool deleteTrip, const QVector<dive *> &dives); void divesChanged(dive_trip *trip, const QVector<dive *> &dives); void divesTimeChanged(dive_trip *trip, timestamp_t delta, const QVector<dive *> &dives); void divesMovedBetweenTrips(dive_trip *from, dive_trip *to, bool deleteFrom, bool createTo, const QVector<dive *> &dives); - void divesSelected(dive_trip *trip, const QVector<dive *> &dives); - void divesDeselected(dive_trip *trip, const QVector<dive *> &dives); void currentDiveChanged(); + +public: + DiveTripModelTree(QObject *parent = nullptr); private: - // The model has up to two levels. At the top level, we have either trips or dives + int rowCount(const QModelIndex &parent) const override; + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &index) const override; + QVariant data(const QModelIndex &index, int role) const override; + void filterFinished() override; + bool lessThan(const QModelIndex &i1, const QModelIndex &i2) const override; + void changeDiveSelection(dive_trip *trip, const QVector<dive *> &dives, bool select) override; + dive *diveOrNull(const QModelIndex &index) const override; + + // The tree model has 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, which is based on the dive_or_trip structure. // If it is a trip, additionally, the dives are collected in a vector. @@ -97,8 +143,14 @@ private: dive *getDive() const; // Helper function: returns top-level-dive or null timestamp_t when() const; // Helper function: start time of dive *or* trip }; - // Comparison function between dive and arbitrary entry - static bool dive_before_entry(const dive *d, const Item &entry); + std::vector<Item> items; // Use std::vector for convenience of emplace_back() + + dive_or_trip 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 + // Addition and deletion of dives + void addDivesToTrip(int idx, const QVector<dive *> &dives); + void topLevelChanged(int idx); // Access trips and dives int findTripIdx(const dive_trip *trip) const; @@ -106,24 +158,35 @@ private: int findDiveInTrip(int tripIdx, const dive *d) const; // Find dive inside trip. Second parameter is index of trip int findInsertionIndex(const dive_trip *trip) const; // Where to insert trip - // Access trip and dive data - static QVariant diveData(const struct dive *d, int column, int role); - static QVariant tripData(const dive_trip *trip, int column, int role); + // Comparison function between dive and arbitrary entry + static bool dive_before_entry(const dive *d, const Item &entry); +}; - // Select or deselect dives - void changeDiveSelection(dive_trip *trip, const QVector<dive *> &dives, bool select); +class DiveTripModelList : public DiveTripModelBase +{ + Q_OBJECT +public slots: + void divesAdded(dive_trip *trip, bool addTrip, const QVector<dive *> &dives); + void divesDeleted(dive_trip *trip, bool deleteTrip, const QVector<dive *> &dives); + void divesChanged(dive_trip *trip, const QVector<dive *> &dives); + void divesTimeChanged(dive_trip *trip, timestamp_t delta, const QVector<dive *> &dives); + // Does nothing in list view. + //void divesMovedBetweenTrips(dive_trip *from, dive_trip *to, bool deleteFrom, bool createTo, const QVector<dive *> &dives); + void currentDiveChanged(); - // Addition and deletion of dives - void addDivesToTrip(int idx, const QVector<dive *> &dives); - void topLevelChanged(int idx); +public: + DiveTripModelList(QObject *parent = nullptr); +private: + int rowCount(const QModelIndex &parent) const override; + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &index) const override; + QVariant data(const QModelIndex &index, int role) const override; + void filterFinished() override; + bool lessThan(const QModelIndex &i1, const QModelIndex &i2) const override; + void changeDiveSelection(dive_trip *trip, const QVector<dive *> &dives, bool select) override; + dive *diveOrNull(const QModelIndex &index) const override; - dive *diveOrNull(const QModelIndex &index) const; // Returns a dive if this index represents a dive, null otherwise - dive_or_trip 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; + std::vector<dive *> items; // TODO: access core data directly }; #endif diff --git a/qt-models/filtermodels.cpp b/qt-models/filtermodels.cpp index 785d36123..4e4134033 100644 --- a/qt-models/filtermodels.cpp +++ b/qt-models/filtermodels.cpp @@ -88,13 +88,14 @@ MultiFilterSortModel::MultiFilterSortModel(QObject *parent) : QSortFilterProxyMo { setFilterKeyColumn(-1); // filter all columns setFilterCaseSensitivity(Qt::CaseInsensitive); - setSourceModel(DiveTripModel::instance()); } -void MultiFilterSortModel::setLayout(DiveTripModel::Layout layout) +void MultiFilterSortModel::resetModel(DiveTripModelBase::Layout layout) { - DiveTripModel *tripModel = DiveTripModel::instance(); - tripModel->setLayout(layout); // Note: setLayout() resets the whole model + DiveTripModelBase::resetModel(layout); + // DiveTripModelBase::resetModel() generates a new instance. + // Thus, the source model must be reset. + setSourceModel(DiveTripModelBase::instance()); } bool MultiFilterSortModel::showDive(const struct dive *d) const @@ -143,14 +144,14 @@ bool MultiFilterSortModel::showDive(const struct dive *d) const bool MultiFilterSortModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { QModelIndex index0 = sourceModel()->index(source_row, 0, source_parent); - struct dive *d = sourceModel()->data(index0, DiveTripModel::DIVE_ROLE).value<struct dive *>(); + struct dive *d = sourceModel()->data(index0, DiveTripModelBase::DIVE_ROLE).value<struct dive *>(); // For dives, simply check the hidden_by_filter flag if (d) return !d->hidden_by_filter; // Since this is not a dive, it must be a trip - dive_trip *trip = sourceModel()->data(index0, DiveTripModel::TRIP_ROLE).value<dive_trip *>(); + dive_trip *trip = sourceModel()->data(index0, DiveTripModelBase::TRIP_ROLE).value<dive_trip *>(); if (!trip) return false; // Oops. Neither dive nor trip, something is seriously wrong. @@ -189,7 +190,7 @@ void MultiFilterSortModel::myInvalidate() invalidateFilter(); // Tell the dive trip model to update the displayed-counts - DiveTripModel::instance()->filterFinished(); + DiveTripModelBase::instance()->filterFinished(); emit filterFinished(); #if !defined(SUBSURFACE_MOBILE) @@ -218,7 +219,7 @@ void MultiFilterSortModel::stopFilterDiveSite() bool MultiFilterSortModel::lessThan(const QModelIndex &i1, const QModelIndex &i2) const { // Hand sorting down to the source model. - return DiveTripModel::instance()->lessThan(i1, i2); + return DiveTripModelBase::instance()->lessThan(i1, i2); } void MultiFilterSortModel::filterDataChanged(const FilterData& data) diff --git a/qt-models/filtermodels.h b/qt-models/filtermodels.h index 442bdec5e..a8a601ecc 100644 --- a/qt-models/filtermodels.h +++ b/qt-models/filtermodels.h @@ -2,7 +2,7 @@ #ifndef FILTERMODELS_H #define FILTERMODELS_H -#include "divetripmodel.h" // For DiveTripModel::Layout. TODO: remove in due course +#include "divetripmodel.h" #include <QStringListModel> #include <QSortFilterProxyModel> @@ -13,7 +13,6 @@ struct dive; struct dive_trip; -class DiveTripModel; struct FilterData { bool validFilter = false; @@ -49,7 +48,7 @@ slots: void startFilterDiveSite(struct dive_site *ds); void stopFilterDiveSite(); void filterChanged(const QModelIndex &from, const QModelIndex &to, const QVector<int> &roles); - void setLayout(DiveTripModel::Layout layout); + void resetModel(DiveTripModelBase::Layout layout); void filterDataChanged(const FilterData& data); signals: |