diff options
-rw-r--r-- | commands/command_divelist.cpp | 3 | ||||
-rw-r--r-- | core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | core/divefilter.cpp | 238 | ||||
-rw-r--r-- | core/divefilter.h | 97 | ||||
-rw-r--r-- | desktop-widgets/divelistview.cpp | 5 | ||||
-rw-r--r-- | desktop-widgets/divelogexportdialog.cpp | 5 | ||||
-rw-r--r-- | desktop-widgets/filterwidget2.cpp | 2 | ||||
-rw-r--r-- | desktop-widgets/filterwidget2.h | 2 | ||||
-rw-r--r-- | desktop-widgets/locationinformation.cpp | 6 | ||||
-rw-r--r-- | desktop-widgets/mainwindow.h | 1 | ||||
-rw-r--r-- | desktop-widgets/tab-widgets/TabDiveSite.cpp | 8 | ||||
-rw-r--r-- | map-widget/qmlmapwidgethelper.cpp | 4 | ||||
-rw-r--r-- | qt-models/divetripmodel.cpp | 1 | ||||
-rw-r--r-- | qt-models/filtermodels.cpp | 233 | ||||
-rw-r--r-- | qt-models/filtermodels.h | 65 | ||||
-rw-r--r-- | qt-models/maplocationmodel.cpp | 5 |
16 files changed, 369 insertions, 308 deletions
diff --git a/commands/command_divelist.cpp b/commands/command_divelist.cpp index 9943651eb..4fabac9fc 100644 --- a/commands/command_divelist.cpp +++ b/commands/command_divelist.cpp @@ -8,6 +8,7 @@ #include "core/subsurface-qt/DiveListNotifier.h" #include "qt-models/filtermodels.h" #include "../profile-widget/profilewidget2.h" +#include "core/divefilter.h" #include <array> @@ -68,7 +69,7 @@ dive *DiveListBase::addDive(DiveToAdd &d) dive *res = d.dive.release(); // Give up ownership of dive // Set the filter flag according to current filter settings - bool show = MultiFilterSortModel::instance()->showDive(res); + bool show = DiveFilter::instance()->showDive(res); res->hidden_by_filter = !show; if (show) ++shown_dives; diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 3a0806454..3d58dded7 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -66,6 +66,8 @@ set(SUBSURFACE_CORE_LIB_SRCS dive.h divecomputer.cpp divecomputer.h + divefilter.cpp + divefilter.h divelist.c divelist.h divelogexportlogic.cpp diff --git a/core/divefilter.cpp b/core/divefilter.cpp new file mode 100644 index 000000000..0b0e446d0 --- /dev/null +++ b/core/divefilter.cpp @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "divefilter.h" + +#ifdef SUBSURFACE_MOBILE + +DiveFilter::DiveFilter() +{ +} + +bool DiveFilter::showDive(const struct dive *d) const +{ + // TODO: Do something useful + return true; +} + +#else // SUBSURFACE_MOBILE + +#include "desktop-widgets/mapwidget.h" +#include "desktop-widgets/mainwindow.h" +#include "desktop-widgets/divelistview.h" +#include "core/qthelper.h" +#include "core/trip.h" +#include "core/divesite.h" +#include "qt-models/filtermodels.h" + +namespace { + // Check if a string-list contains at least one string containing the second argument. + // Comparison is non case sensitive and removes white space. + bool listContainsSuperstring(const QStringList &list, const QString &s) + { + return std::any_of(list.begin(), list.end(), [&s](const QString &s2) + { return s2.trimmed().contains(s.trimmed(), Qt::CaseInsensitive); } ); + } + + // Check whether either all, any or none of the items of the first list is + // in the second list as a super string. + // The mode is controlled by the second argument + bool check(const QStringList &items, const QStringList &list, FilterData::Mode mode) + { + bool negate = mode == FilterData::Mode::NONE_OF; + bool any_of = mode == FilterData::Mode::ANY_OF; + auto fun = [&list, negate](const QString &item) + { return listContainsSuperstring(list, item) != negate; }; + return any_of ? std::any_of(items.begin(), items.end(), fun) + : std::all_of(items.begin(), items.end(), fun); + } + + bool hasTags(const QStringList &tags, const struct dive *d, FilterData::Mode mode) + { + if (tags.isEmpty()) + return true; + QStringList dive_tags = get_taglist_string(d->tag_list).split(","); + dive_tags.append(gettextFromC::tr(divemode_text_ui[d->dc.divemode])); + return check(tags, dive_tags, mode); + } + + bool hasPersons(const QStringList &people, const struct dive *d, FilterData::Mode mode) + { + if (people.isEmpty()) + return true; + QStringList dive_people = QString(d->buddy).split(",", QString::SkipEmptyParts) + + QString(d->divemaster).split(",", QString::SkipEmptyParts); + return check(people, dive_people, mode); + } + + bool hasLocations(const QStringList &locations, const struct dive *d, FilterData::Mode mode) + { + if (locations.isEmpty()) + return true; + QStringList diveLocations; + if (d->divetrip) + diveLocations.push_back(QString(d->divetrip->location)); + + if (d->dive_site) + diveLocations.push_back(QString(d->dive_site->name)); + + return check(locations, diveLocations, mode); + } + + // TODO: Finish this implementation. + bool hasEquipment(const QStringList &, const struct dive *, FilterData::Mode) + { + return true; + } + + bool hasSuits(const QStringList &suits, const struct dive *d, FilterData::Mode mode) + { + if (suits.isEmpty()) + return true; + QStringList diveSuits; + if (d->suit) + diveSuits.push_back(QString(d->suit)); + return check(suits, diveSuits, mode); + } + + bool hasNotes(const QStringList &dnotes, const struct dive *d, FilterData::Mode mode) + { + if (dnotes.isEmpty()) + return true; + QStringList diveNotes; + if (d->notes) + diveNotes.push_back(QString(d->notes)); + return check(dnotes, diveNotes, mode); + } + +} + +DiveFilter *DiveFilter::instance() +{ + static DiveFilter self; + return &self; +} + +DiveFilter::DiveFilter() : diveSiteRefCount(0) +{ +} + +bool DiveFilter::showDive(const struct dive *d) const +{ + if (diveSiteMode()) + return dive_sites.contains(d->dive_site); + + if (!filterData.validFilter) + return true; + + if (d->visibility < filterData.minVisibility || d->visibility > filterData.maxVisibility) + return false; + + if (d->rating < filterData.minRating || d->rating > filterData.maxRating) + return false; + + auto temp_comp = prefs.units.temperature == units::CELSIUS ? C_to_mkelvin : F_to_mkelvin; + if (d->watertemp.mkelvin && + (d->watertemp.mkelvin < (*temp_comp)(filterData.minWaterTemp) || d->watertemp.mkelvin > (*temp_comp)(filterData.maxWaterTemp))) + return false; + + if (d->airtemp.mkelvin && + (d->airtemp.mkelvin < (*temp_comp)(filterData.minAirTemp) || d->airtemp.mkelvin > (*temp_comp)(filterData.maxAirTemp))) + return false; + + QDateTime t = filterData.fromDate; + t.setTime(filterData.fromTime); + if (filterData.fromDate.isValid() && filterData.fromTime.isValid() && + d->when < t.toMSecsSinceEpoch()/1000 + t.offsetFromUtc()) + return false; + + t = filterData.toDate; + t.setTime(filterData.toTime); + if (filterData.toDate.isValid() && filterData.toTime.isValid() && + d->when > t.toMSecsSinceEpoch()/1000 + t.offsetFromUtc()) + return false; + + // tags. + if (!hasTags(filterData.tags, d, filterData.tagsMode)) + return false; + + // people + if (!hasPersons(filterData.people, d, filterData.peopleMode)) + return false; + + // Location + if (!hasLocations(filterData.location, d, filterData.locationMode)) + return false; + + // Suit + if (!hasSuits(filterData.suit, d, filterData.suitMode)) + return false; + + // Notes + if (!hasNotes(filterData.dnotes, d, filterData.dnotesMode)) + return false; + + if (!hasEquipment(filterData.equipment, d, filterData.equipmentMode)) + return false; + + // Planned/Logged + if (!filterData.logged && !has_planned(d, true)) + return false; + if (!filterData.planned && !has_planned(d, false)) + return false; + + return true; +} + +void DiveFilter::startFilterDiveSites(QVector<dive_site *> ds) +{ + if (++diveSiteRefCount > 1) { + setFilterDiveSite(ds); + } else { + std::sort(ds.begin(), ds.end()); + dive_sites = ds; + // When switching into dive site mode, reload the dive sites. + // We won't do this in myInvalidate() once we are in dive site mode. + MapWidget::instance()->reload(); + MultiFilterSortModel::instance()->myInvalidate(); + } +} + +void DiveFilter::stopFilterDiveSites() +{ + if (--diveSiteRefCount > 0) + return; + dive_sites.clear(); + MultiFilterSortModel::instance()->myInvalidate(); + MapWidget::instance()->reload(); +} + +void DiveFilter::setFilterDiveSite(QVector<dive_site *> ds) +{ + // If the filter didn't change, return early to avoid a full + // map reload. For a well-defined comparison, sort the vector first. + std::sort(ds.begin(), ds.end()); + if (ds == dive_sites) + return; + dive_sites = ds; + + MultiFilterSortModel::instance()->myInvalidate(); + MapWidget::instance()->setSelected(dive_sites); + MainWindow::instance()->diveList->expandAll(); +} + +const QVector<dive_site *> &DiveFilter::filteredDiveSites() const +{ + return dive_sites; +} + +bool DiveFilter::diveSiteMode() const +{ + return diveSiteRefCount > 0; +} + +void DiveFilter::setFilter(const FilterData &data) +{ + filterData = data; + MultiFilterSortModel::instance()->myInvalidate(); +} +#endif // SUBSURFACE_MOBILE diff --git a/core/divefilter.h b/core/divefilter.h new file mode 100644 index 000000000..d225d3c81 --- /dev/null +++ b/core/divefilter.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 +// A class that filters dives. +#ifndef DIVE_FILTER_H +#define DIVE_FILTER_H + +// The dive filter for mobile is currently much simpler than for desktop. +// Therefore, for now we have two completely separate implementations. +// This should be unified in the future. +#ifdef SUBSURFACE_MOBILE + +class DiveFilter { +public: + static DiveFilter *instance(); + + bool showDive(const struct dive *d) const; +private: + DiveFilter(); +}; + +#else + +#include <QDateTime> +#include <QStringList> +#include <QVector> + +struct dive; +struct dive_trip; +struct dive_site; + +struct FilterData { + // The mode ids are chosen such that they can be directly converted from / to combobox indices. + enum class Mode { + ALL_OF = 0, + ANY_OF = 1, + NONE_OF = 2 + }; + + bool validFilter = false; + int minVisibility = 0; + int maxVisibility = 5; + int minRating = 0; + int maxRating = 5; + // The default minimum and maximum temperatures are set such that all + // physically reasonable dives are shown. Note that these values should + // work for both Celcius and Fahrenheit scales. + double minWaterTemp = -10; + double maxWaterTemp = 200; + double minAirTemp = -50; + double maxAirTemp = 200; + QDateTime fromDate = QDateTime(QDate(1980,1,1)); + QTime fromTime = QTime(0,0); + QDateTime toDate = QDateTime::currentDateTime(); + QTime toTime = QTime::currentTime(); + QStringList tags; + QStringList people; + QStringList location; + QStringList suit; + QStringList dnotes; + QStringList equipment; + Mode tagsMode = Mode::ALL_OF; + Mode peopleMode = Mode::ALL_OF; + Mode locationMode = Mode::ANY_OF; + Mode dnotesMode = Mode::ALL_OF; + Mode suitMode = Mode::ANY_OF; + Mode equipmentMode = Mode::ALL_OF; + bool logged = true; + bool planned = true; +}; + +class DiveFilter { +public: + static DiveFilter *instance(); + + bool showDive(const struct dive *d) const; + bool diveSiteMode() const; // returns true if we're filtering on dive site + const QVector<dive_site *> &filteredDiveSites() const; + void startFilterDiveSites(QVector<dive_site *> ds); + void setFilterDiveSite(QVector<dive_site *> ds); + void stopFilterDiveSites(); + void setFilter(const FilterData &data); +private: + DiveFilter(); + + QVector<dive_site *> dive_sites; + FilterData filterData; + + // We use ref-counting for the dive site mode. The reason is that when switching + // between two tabs that both need dive site mode, the following course of + // events may happen: + // 1) The new tab appears -> enter dive site mode. + // 2) The old tab gets its hide() signal -> exit dive site mode. + // The filter is now not in dive site mode, even if it should + int diveSiteRefCount; +}; +#endif // SUBSURFACE_MOBILE + +#endif diff --git a/desktop-widgets/divelistview.cpp b/desktop-widgets/divelistview.cpp index aa2195434..d6a4d507e 100644 --- a/desktop-widgets/divelistview.cpp +++ b/desktop-widgets/divelistview.cpp @@ -10,6 +10,7 @@ #include "desktop-widgets/mainwindow.h" #include "desktop-widgets/divepicturewidget.h" #include "core/display.h" +#include "core/divefilter.h" #include <unistd.h> #include <QSettings> #include <QKeyEvent> @@ -463,7 +464,7 @@ void DiveListView::selectDives(const QList<int> &newDiveSelection) // But don't do this if we are in divesite mode, because then // the dive-site selection is controlled by the filter not // by the selected dives. - if (!MultiFilterSortModel::instance()->diveSiteMode()) { + if (!DiveFilter::instance()->diveSiteMode()) { QVector<dive_site *> selectedSites; for (int idx: newDiveSelection) { dive *d = get_dive(idx); @@ -698,7 +699,7 @@ void DiveListView::selectionChanged(const QItemSelection &selected, const QItemS // But don't do this if we are in divesite mode, because then // the dive-site selection is controlled by the filter not // by the selected dives. - if (!MultiFilterSortModel::instance()->diveSiteMode()) { + if (!DiveFilter::instance()->diveSiteMode()) { QVector<dive_site *> selectedSites; for (QModelIndex index: selectionModel()->selection().indexes()) { const QAbstractItemModel *model = index.model(); diff --git a/desktop-widgets/divelogexportdialog.cpp b/desktop-widgets/divelogexportdialog.cpp index cf859e2b9..99f6eb8a8 100644 --- a/desktop-widgets/divelogexportdialog.cpp +++ b/desktop-widgets/divelogexportdialog.cpp @@ -11,6 +11,7 @@ #include "core/save-html.h" #include "core/settings/qPrefDisplay.h" #include "core/save-profiledata.h" +#include "core/divefilter.h" #include "core/divesite.h" #include "core/errorhelper.h" #include "core/file.h" @@ -134,10 +135,10 @@ static std::vector<const dive_site *> getDiveSitesToExport(bool selectedOnly) { std::vector<const dive_site *> res; - if (selectedOnly && MultiFilterSortModel::instance()->diveSiteMode()) { + if (selectedOnly && DiveFilter::instance()->diveSiteMode()) { // Special case in dive site mode: export all selected dive sites, // not the dive sites of selected dives. - QVector<dive_site *> sites = MultiFilterSortModel::instance()->filteredDiveSites(); + QVector<dive_site *> sites = DiveFilter::instance()->filteredDiveSites(); res.reserve(sites.size()); for (const dive_site *ds: sites) res.push_back(ds); diff --git a/desktop-widgets/filterwidget2.cpp b/desktop-widgets/filterwidget2.cpp index 0ab6bb022..140016a03 100644 --- a/desktop-widgets/filterwidget2.cpp +++ b/desktop-widgets/filterwidget2.cpp @@ -237,7 +237,7 @@ void FilterWidget2::hideEvent(QHideEvent *event) void FilterWidget2::filterDataChanged(const FilterData &data) { - MultiFilterSortModel::instance()->filterDataChanged(data); + DiveFilter::instance()->setFilter(data); } QString FilterWidget2::shownText() diff --git a/desktop-widgets/filterwidget2.h b/desktop-widgets/filterwidget2.h index 212f6b5ae..b52386fe7 100644 --- a/desktop-widgets/filterwidget2.h +++ b/desktop-widgets/filterwidget2.h @@ -8,7 +8,7 @@ #include <memory> #include "ui_filterwidget2.h" -#include "qt-models/filtermodels.h" +#include "core/divefilter.h" namespace Ui { class FilterWidget2; diff --git a/desktop-widgets/locationinformation.cpp b/desktop-widgets/locationinformation.cpp index bbf43ec5a..331a8555e 100644 --- a/desktop-widgets/locationinformation.cpp +++ b/desktop-widgets/locationinformation.cpp @@ -5,7 +5,7 @@ #include "desktop-widgets/divelistview.h" #include "core/qthelper.h" #include "desktop-widgets/mapwidget.h" -#include "qt-models/filtermodels.h" +#include "core/divefilter.h" #include "core/divesitehelpers.h" #include "desktop-widgets/modeldelegates.h" #include "core/subsurface-qt/DiveListNotifier.h" @@ -194,7 +194,7 @@ void LocationInformationWidget::acceptChanges() MainWindow::instance()->diveList->setEnabled(true); MainWindow::instance()->setEnabledToolbar(true); MainWindow::instance()->setApplicationState(ApplicationState::Default); - MultiFilterSortModel::instance()->stopFilterDiveSites(); + DiveFilter::instance()->stopFilterDiveSites(); // Subtlety alert: diveSite must be cleared *after* exiting the dive-site mode. // Exiting dive-site mode removes the focus from the active widget and @@ -211,7 +211,7 @@ void LocationInformationWidget::initFields(dive_site *ds) filter_model.set(ds, ds->location); updateLabels(); enableLocationButtons(dive_site_has_gps_location(ds)); - MultiFilterSortModel::instance()->startFilterDiveSites(QVector<dive_site *>{ ds }); + DiveFilter::instance()->startFilterDiveSites(QVector<dive_site *>{ ds }); filter_model.invalidate(); } else { filter_model.set(0, location_t { degrees_t{ 0 }, degrees_t{ 0 } }); diff --git a/desktop-widgets/mainwindow.h b/desktop-widgets/mainwindow.h index 0eaa5e1fa..075999707 100644 --- a/desktop-widgets/mainwindow.h +++ b/desktop-widgets/mainwindow.h @@ -21,6 +21,7 @@ #include "desktop-widgets/filterwidget2.h" #include "core/applicationstate.h" #include "core/gpslocation.h" +#include "core/dive.h" #define NUM_RECENT_FILES 4 diff --git a/desktop-widgets/tab-widgets/TabDiveSite.cpp b/desktop-widgets/tab-widgets/TabDiveSite.cpp index d5591c152..3ff693e59 100644 --- a/desktop-widgets/tab-widgets/TabDiveSite.cpp +++ b/desktop-widgets/tab-widgets/TabDiveSite.cpp @@ -2,8 +2,8 @@ #include "TabDiveSite.h" #include "core/subsurface-qt/DiveListNotifier.h" #include "core/divesite.h" +#include "core/divefilter.h" #include "qt-models/divelocationmodel.h" -#include "qt-models/filtermodels.h" #include "commands/command.h" #include <qt-models/divecomputerextradatamodel.h> @@ -97,18 +97,18 @@ QVector<dive_site *> TabDiveSite::selectedDiveSites() void TabDiveSite::selectionChanged(const QItemSelection &, const QItemSelection &) { - MultiFilterSortModel::instance()->setFilterDiveSite(selectedDiveSites()); + DiveFilter::instance()->setFilterDiveSite(selectedDiveSites()); } void TabDiveSite::showEvent(QShowEvent *) { // If the user switches to the dive site tab and there was already a selection, // filter on that selection. - MultiFilterSortModel::instance()->startFilterDiveSites(selectedDiveSites()); + DiveFilter::instance()->startFilterDiveSites(selectedDiveSites()); } void TabDiveSite::hideEvent(QHideEvent *) { // If the user switches to a different tab, stop the dive site filtering - MultiFilterSortModel::instance()->stopFilterDiveSites(); + DiveFilter::instance()->stopFilterDiveSites(); } diff --git a/map-widget/qmlmapwidgethelper.cpp b/map-widget/qmlmapwidgethelper.cpp index 9dba8bfd3..ff5f47f35 100644 --- a/map-widget/qmlmapwidgethelper.cpp +++ b/map-widget/qmlmapwidgethelper.cpp @@ -7,10 +7,10 @@ #include "qmlmapwidgethelper.h" #include "core/divesite.h" #include "core/qthelper.h" +#include "core/divefilter.h" #include "qt-models/maplocationmodel.h" #include "qt-models/divelocationmodel.h" #ifndef SUBSURFACE_MOBILE -#include "qt-models/filtermodels.h" #include "desktop-widgets/mapwidget.h" #endif @@ -106,7 +106,7 @@ void MapWidgetHelper::updateEditMode() // The filter being set to dive site is the signal that we are in dive site edit mode. // This is the case when either the dive site edit tab or the dive site list tab are active. bool old = m_editMode; - m_editMode = MultiFilterSortModel::instance()->diveSiteMode(); + m_editMode = DiveFilter::instance()->diveSiteMode(); if (old != m_editMode) emit editModeChanged(); #endif diff --git a/qt-models/divetripmodel.cpp b/qt-models/divetripmodel.cpp index 5fedd432f..7f9ca9e32 100644 --- a/qt-models/divetripmodel.cpp +++ b/qt-models/divetripmodel.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include "qt-models/divetripmodel.h" #include "qt-models/filtermodels.h" +#include "core/divefilter.h" #include "core/gettextfromc.h" #include "core/metrics.h" #include "core/trip.h" diff --git a/qt-models/filtermodels.cpp b/qt-models/filtermodels.cpp index 9736d3335..b04fce127 100644 --- a/qt-models/filtermodels.cpp +++ b/qt-models/filtermodels.cpp @@ -1,113 +1,26 @@ // SPDX-License-Identifier: GPL-2.0 #include "qt-models/filtermodels.h" -#include "qt-models/models.h" #include "core/display.h" #include "core/qthelper.h" -#include "core/divesite.h" #include "core/trip.h" #include "core/subsurface-string.h" #include "core/subsurface-qt/DiveListNotifier.h" #include "qt-models/divetripmodel.h" #if !defined(SUBSURFACE_MOBILE) -#include "desktop-widgets/divelistview.h" -#include "desktop-widgets/mainwindow.h" -#include "desktop-widgets/mapwidget.h" +#include "core/divefilter.h" #endif #include <QDebug> #include <algorithm> -namespace { - // Check if a string-list contains at least one string containing the second argument. - // Comparison is non case sensitive and removes white space. - bool listContainsSuperstring(const QStringList &list, const QString &s) - { - return std::any_of(list.begin(), list.end(), [&s](const QString &s2) - { return s2.trimmed().contains(s.trimmed(), Qt::CaseInsensitive); } ); - } - - // Check whether either all, any or none of the items of the first list is - // in the second list as a super string. - // The mode is controlled by the second argument - bool check(const QStringList &items, const QStringList &list, FilterData::Mode mode) - { - bool negate = mode == FilterData::Mode::NONE_OF; - bool any_of = mode == FilterData::Mode::ANY_OF; - auto fun = [&list, negate](const QString &item) - { return listContainsSuperstring(list, item) != negate; }; - return any_of ? std::any_of(items.begin(), items.end(), fun) - : std::all_of(items.begin(), items.end(), fun); - } - - bool hasTags(const QStringList &tags, const struct dive *d, FilterData::Mode mode) - { - if (tags.isEmpty()) - return true; - QStringList dive_tags = get_taglist_string(d->tag_list).split(","); - dive_tags.append(gettextFromC::tr(divemode_text_ui[d->dc.divemode])); - return check(tags, dive_tags, mode); - } - - bool hasPersons(const QStringList &people, const struct dive *d, FilterData::Mode mode) - { - if (people.isEmpty()) - return true; - QStringList dive_people = QString(d->buddy).split(",", QString::SkipEmptyParts) - + QString(d->divemaster).split(",", QString::SkipEmptyParts); - return check(people, dive_people, mode); - } - - bool hasLocations(const QStringList &locations, const struct dive *d, FilterData::Mode mode) - { - if (locations.isEmpty()) - return true; - QStringList diveLocations; - if (d->divetrip) - diveLocations.push_back(QString(d->divetrip->location)); - - if (d->dive_site) - diveLocations.push_back(QString(d->dive_site->name)); - - return check(locations, diveLocations, mode); - } - - // TODO: Finish this implementation. - bool hasEquipment(const QStringList &, const struct dive *, FilterData::Mode) - { - return true; - } - - bool hasSuits(const QStringList &suits, const struct dive *d, FilterData::Mode mode) - { - if (suits.isEmpty()) - return true; - QStringList diveSuits; - if (d->suit) - diveSuits.push_back(QString(d->suit)); - return check(suits, diveSuits, mode); - } - - bool hasNotes(const QStringList &dnotes, const struct dive *d, FilterData::Mode mode) - { - if (dnotes.isEmpty()) - return true; - QStringList diveNotes; - if (d->notes) - diveNotes.push_back(QString(d->notes)); - return check(dnotes, diveNotes, mode); - } - -} - MultiFilterSortModel *MultiFilterSortModel::instance() { static MultiFilterSortModel self; return &self; } -MultiFilterSortModel::MultiFilterSortModel(QObject *parent) : QSortFilterProxyModel(parent), - diveSiteRefCount(0) +MultiFilterSortModel::MultiFilterSortModel(QObject *parent) : QSortFilterProxyModel(parent) { setFilterKeyColumn(-1); // filter all columns setFilterCaseSensitivity(Qt::CaseInsensitive); @@ -121,73 +34,6 @@ void MultiFilterSortModel::resetModel(DiveTripModelBase::Layout layout) setSourceModel(DiveTripModelBase::instance()); } -bool MultiFilterSortModel::showDive(const struct dive *d) const -{ - if (diveSiteMode()) - return dive_sites.contains(d->dive_site); - - if (!filterData.validFilter) - return true; - - if (d->visibility < filterData.minVisibility || d->visibility > filterData.maxVisibility) - return false; - - if (d->rating < filterData.minRating || d->rating > filterData.maxRating) - return false; - - auto temp_comp = prefs.units.temperature == units::CELSIUS ? C_to_mkelvin : F_to_mkelvin; - if (d->watertemp.mkelvin && - (d->watertemp.mkelvin < (*temp_comp)(filterData.minWaterTemp) || d->watertemp.mkelvin > (*temp_comp)(filterData.maxWaterTemp))) - return false; - - if (d->airtemp.mkelvin && - (d->airtemp.mkelvin < (*temp_comp)(filterData.minAirTemp) || d->airtemp.mkelvin > (*temp_comp)(filterData.maxAirTemp))) - return false; - - QDateTime t = filterData.fromDate; - t.setTime(filterData.fromTime); - if (filterData.fromDate.isValid() && filterData.fromTime.isValid() && - d->when < t.toMSecsSinceEpoch()/1000 + t.offsetFromUtc()) - return false; - - t = filterData.toDate; - t.setTime(filterData.toTime); - if (filterData.toDate.isValid() && filterData.toTime.isValid() && - d->when > t.toMSecsSinceEpoch()/1000 + t.offsetFromUtc()) - return false; - - // tags. - if (!hasTags(filterData.tags, d, filterData.tagsMode)) - return false; - - // people - if (!hasPersons(filterData.people, d, filterData.peopleMode)) - return false; - - // Location - if (!hasLocations(filterData.location, d, filterData.locationMode)) - return false; - - // Suit - if (!hasSuits(filterData.suit, d, filterData.suitMode)) - return false; - - // Notes - if (!hasNotes(filterData.dnotes, d, filterData.dnotesMode)) - return false; - - if (!hasEquipment(filterData.equipment, d, filterData.equipmentMode)) - return false; - - // Planned/Logged - if (!filterData.logged && !has_planned(d, true)) - return false; - if (!filterData.planned && !has_planned(d, false)) - return false; - - return true; -} - bool MultiFilterSortModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { QAbstractItemModel *m = sourceModel(); @@ -213,6 +59,7 @@ void MultiFilterSortModel::myInvalidate() shown_dives = 0; + DiveFilter *filter = DiveFilter::instance(); for (int i = 0; i < m->rowCount(QModelIndex()); ++i) { QModelIndex idx = m->index(i, 0, QModelIndex()); @@ -227,7 +74,7 @@ void MultiFilterSortModel::myInvalidate() qWarning("MultiFilterSortModel::myInvalidate(): subitem not a dive!?"); continue; } - bool show = showDive(d); + bool show = filter->showDive(d); if (show) { shown_dives++; showTrip = true; @@ -237,7 +84,7 @@ void MultiFilterSortModel::myInvalidate() m->setData(idx, showTrip, DiveTripModelBase::SHOWN_ROLE); } else { dive *d = m->data(idx, DiveTripModelBase::DIVE_ROLE).value<dive *>(); - bool show = (d != NULL) && showDive(d); + bool show = (d != NULL) && filter->showDive(d); if (show) shown_dives++; m->setData(idx, show, DiveTripModelBase::SHOWN_ROLE); @@ -251,26 +98,13 @@ void MultiFilterSortModel::myInvalidate() countsChanged(); } -#if !defined(SUBSURFACE_MOBILE) - // The shown maps may have changed -> reload the map widget. - // But don't do this in dive site mode, because then we show all - // dive sites and only change the selected flag. - if (!diveSiteMode()) - MapWidget::instance()->reload(); -#endif - emit filterFinished(); - -#if !defined(SUBSURFACE_MOBILE) - if (diveSiteMode()) - MainWindow::instance()->diveList->expandAll(); -#endif } bool MultiFilterSortModel::updateDive(struct dive *d) { bool oldStatus = !d->hidden_by_filter; - bool newStatus = showDive(d); + bool newStatus = DiveFilter::instance()->showDive(d); bool changed = oldStatus != newStatus; if (changed) { filter_dive(d, newStatus); @@ -279,67 +113,12 @@ bool MultiFilterSortModel::updateDive(struct dive *d) return changed; } -void MultiFilterSortModel::startFilterDiveSites(QVector<dive_site *> ds) -{ - if (++diveSiteRefCount > 1) { - setFilterDiveSite(ds); - } else { - std::sort(ds.begin(), ds.end()); - dive_sites = ds; -#if !defined(SUBSURFACE_MOBILE) - // When switching into dive site mode, reload the dive sites. - // We won't do this in myInvalidate() once we are in dive site mode. - MapWidget::instance()->reload(); -#endif - myInvalidate(); - } -} - -void MultiFilterSortModel::stopFilterDiveSites() -{ - if (--diveSiteRefCount > 0) - return; - dive_sites.clear(); - myInvalidate(); -} - -void MultiFilterSortModel::setFilterDiveSite(QVector<dive_site *> ds) -{ - // If the filter didn't change, return early to avoid a full - // map reload. For a well-defined comparison, sort the vector first. - std::sort(ds.begin(), ds.end()); - if (ds == dive_sites) - return; - dive_sites = ds; - -#if !defined(SUBSURFACE_MOBILE) - MapWidget::instance()->setSelected(dive_sites); -#endif - myInvalidate(); -} - -const QVector<dive_site *> &MultiFilterSortModel::filteredDiveSites() const -{ - return dive_sites; -} - -bool MultiFilterSortModel::diveSiteMode() const -{ - return diveSiteRefCount > 0; -} - bool MultiFilterSortModel::lessThan(const QModelIndex &i1, const QModelIndex &i2) const { // Hand sorting down to the source model. return DiveTripModelBase::instance()->lessThan(i1, i2); } -void MultiFilterSortModel::filterDataChanged(const FilterData &data) -{ - filterData = data; - myInvalidate(); -} - void MultiFilterSortModel::countsChanged() { updateWindowTitle(); diff --git a/qt-models/filtermodels.h b/qt-models/filtermodels.h index d4a03d134..d279470b1 100644 --- a/qt-models/filtermodels.h +++ b/qt-models/filtermodels.h @@ -11,67 +11,16 @@ #include <stdint.h> #include <vector> -struct dive; -struct dive_trip; - -struct FilterData { - // The mode ids are chosen such that they can be directly converted from / to combobox indices. - enum class Mode { - ALL_OF = 0, - ANY_OF = 1, - NONE_OF = 2 - }; - - bool validFilter = false; - int minVisibility = 0; - int maxVisibility = 5; - int minRating = 0; - int maxRating = 5; - // The default minimum and maximum temperatures are set such that all - // physically reasonable dives are shown. Note that these values should - // work for both Celcius and Fahrenheit scales. - double minWaterTemp = -10; - double maxWaterTemp = 200; - double minAirTemp = -50; - double maxAirTemp = 200; - QDateTime fromDate = QDateTime(QDate(1980,1,1)); - QTime fromTime = QTime(0,0); - QDateTime toDate = QDateTime::currentDateTime(); - QTime toTime = QTime::currentTime(); - QStringList tags; - QStringList people; - QStringList location; - QStringList suit; - QStringList dnotes; - QStringList equipment; - Mode tagsMode = Mode::ALL_OF; - Mode peopleMode = Mode::ALL_OF; - Mode locationMode = Mode::ANY_OF; - Mode dnotesMode = Mode::ALL_OF; - Mode suitMode = Mode::ANY_OF; - Mode equipmentMode = Mode::ALL_OF; - bool logged = true; - bool planned = true; -}; - class MultiFilterSortModel : public QSortFilterProxyModel { Q_OBJECT public: static MultiFilterSortModel *instance(); bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; - bool showDive(const struct dive *d) const; bool updateDive(struct dive *d); // returns true if visibility status changed bool lessThan(const QModelIndex &, const QModelIndex &) const override; - bool diveSiteMode() const; // returns true if we're filtering on dive site - const QVector<dive_site *> &filteredDiveSites() const; -public -slots: - void myInvalidate(); - void startFilterDiveSites(QVector<dive_site *> ds); - void setFilterDiveSite(QVector<dive_site *> ds); - void stopFilterDiveSites(); + void resetModel(DiveTripModelBase::Layout layout); - void filterDataChanged(const FilterData &data); + void myInvalidate(); signals: void filterFinished(); @@ -79,17 +28,7 @@ signals: private: MultiFilterSortModel(QObject *parent = 0); // Dive site filtering has priority over other filters - QVector<dive_site *> dive_sites; void countsChanged(); - FilterData filterData; - - // We use ref-counting for the dive site mode. The reason is that when switching - // between two tabs that both need dive site mode, the following course of - // events may happen: - // 1) The new tab appears -> enter dive site mode. - // 2) The old tab gets its hide() signal -> exit dive site mode. - // The filter is now not in dive site mode, even if it should - int diveSiteRefCount; }; #endif diff --git a/qt-models/maplocationmodel.cpp b/qt-models/maplocationmodel.cpp index 9b21f7142..c123b19c5 100644 --- a/qt-models/maplocationmodel.cpp +++ b/qt-models/maplocationmodel.cpp @@ -2,6 +2,7 @@ #include "maplocationmodel.h" #include "divelocationmodel.h" #include "core/divesite.h" +#include "core/divefilter.h" #ifndef SUBSURFACE_MOBILE #include "qt-models/filtermodels.h" #include "desktop-widgets/mapwidget.h" @@ -133,9 +134,9 @@ void MapLocationModel::reload(QObject *map) // the dive site tab), we want to show all dive sites, not only those // of the non-hidden dives. Moreover, the selected dive sites are those // that we filter for. - bool diveSiteMode = MultiFilterSortModel::instance()->diveSiteMode(); + bool diveSiteMode = DiveFilter::instance()->diveSiteMode(); if (diveSiteMode) - m_selectedDs = MultiFilterSortModel::instance()->filteredDiveSites(); + m_selectedDs = DiveFilter::instance()->filteredDiveSites(); #endif for (int i = 0; i < dive_site_table.nr; ++i) { struct dive_site *ds = dive_site_table.dive_sites[i]; |