diff options
author | Berthold Stoeger <bstoeger@mail.tuwien.ac.at> | 2019-11-17 18:13:55 +0100 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2019-11-19 21:13:40 -0800 |
commit | b76f2071589d6a7f45ec2845afa8b49110da608b (patch) | |
tree | 1b7176f4bf706803160c9dc007a7c524dbc59bd2 | |
parent | 6d6d10f03a92a9bac5394fc226c398af61f29d66 (diff) | |
download | subsurface-b76f2071589d6a7f45ec2845afa8b49110da608b.tar.gz |
Filter: split out filter from model
Split out the actual filtering from the MultiFilterSortModel.
Create a DiveFilter class that does the actual filtering.
Currently, mobile and desktop have their own version of this
class, though ultimately we may want to merge them.
The idea here is that the trip-model and undo-commands have
direct access to the filter-function and thus can take care
of keeping track of the number of shown dives, etc.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
-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]; |