summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2019-11-17 18:13:55 +0100
committerGravatar Dirk Hohndel <dirk@hohndel.org>2019-11-19 21:13:40 -0800
commitb76f2071589d6a7f45ec2845afa8b49110da608b (patch)
tree1b7176f4bf706803160c9dc007a7c524dbc59bd2 /core
parent6d6d10f03a92a9bac5394fc226c398af61f29d66 (diff)
downloadsubsurface-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>
Diffstat (limited to 'core')
-rw-r--r--core/CMakeLists.txt2
-rw-r--r--core/divefilter.cpp238
-rw-r--r--core/divefilter.h97
3 files changed, 337 insertions, 0 deletions
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