summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
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