filter: unify desktop and mobile filters

Switch the mobile code to use the constraint-based filter. The one
thing that is still commented out is dive-site mode, since mobile
doesn't (yet) have a dive-site edit feature. And even if it had,
the dive list probably wouldn't be shown at the same time.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2020-06-01 23:37:36 +02:00 committed by Dirk Hohndel
parent 6c443ba841
commit 634e26cbce
3 changed files with 37 additions and 175 deletions

View file

@ -4,6 +4,12 @@
#include "divelist.h" // for filter_dive
#include "qthelper.h"
#include "subsurface-qt/divelistnotifier.h"
#ifndef SUBSURFACE_MOBILE
#include "desktop-widgets/mapwidget.h"
#include "desktop-widgets/mainwindow.h"
#include "desktop-widgets/divelistview.h"
#include "qt-models/filtermodels.h"
#endif
static void updateDiveStatus(dive *d, bool newStatus, ShownChange &change)
{
@ -15,132 +21,6 @@ static void updateDiveStatus(dive *d, bool newStatus, ShownChange &change)
}
}
#ifdef SUBSURFACE_MOBILE
#include "tag.h"
static QStringList getTagList(const dive *d)
{
QStringList res;
for (const tag_entry *tag = d->tag_list; tag; tag = tag->next)
res.push_back(QString(tag->tag->name).trimmed());
res.append(gettextFromC::tr(divemode_text_ui[d->dc.divemode]));
return res;
}
// Check if a string-list contains at least one string that starts with the second argument.
// Comparison is non case sensitive and removes white space.
static bool listContainsSuperstring(const QStringList &list, const QString &s)
{
return std::any_of(list.begin(), list.end(), [&s](const QString &s2)
{ return s2.startsWith(s, Qt::CaseInsensitive); } );
}
static bool check(const QStringList &items, const QStringList &list)
{
return std::all_of(items.begin(), items.end(), [&list](const QString &item)
{ return listContainsSuperstring(list, item); });
}
static bool hasTags(const QStringList &tags, const struct dive *d)
{
if (tags.isEmpty())
return true;
return check(tags, getTagList(d));
}
static bool hasPersons(const QStringList &people, const struct dive *d)
{
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);
}
DiveFilter *DiveFilter::instance()
{
static DiveFilter self;
return &self;
}
DiveFilter::DiveFilter()
{
}
ShownChange DiveFilter::update(const QVector<dive *> &dives) const
{
dive *old_current = current_dive;
ShownChange res;
switch (filterData.mode) {
default:
case FilterData::Mode::NONE:
for (dive *d: dives)
updateDiveStatus(d, true, res);
break;
case FilterData::Mode::FULLTEXT:
for (dive *d: dives)
updateDiveStatus(d, fulltext_dive_matches(d, filterData.fullText, StringFilterMode::STARTSWITH), res);
break;
case FilterData::Mode::PEOPLE:
for (dive *d: dives)
updateDiveStatus(d, hasPersons(filterData.tags, d), res);
break;
case FilterData::Mode::TAGS:
for (dive *d: dives)
updateDiveStatus(d, hasTags(filterData.tags, d), res);
break;
}
res.currentChanged = old_current != current_dive;
return res;
}
ShownChange DiveFilter::updateAll() const
{
dive *old_current = current_dive;
ShownChange res;
int i;
dive *d;
switch (filterData.mode) {
default:
case FilterData::Mode::NONE:
for_each_dive(i, d)
updateDiveStatus(d, true, res);
break;
case FilterData::Mode::FULLTEXT: {
FullTextResult ft = fulltext_find_dives(filterData.fullText, StringFilterMode::STARTSWITH);
for_each_dive(i, d)
updateDiveStatus(d, ft.dive_matches(d), res);
break;
}
case FilterData::Mode::PEOPLE:
for_each_dive(i, d)
updateDiveStatus(d, hasPersons(filterData.tags, d), res);
break;
case FilterData::Mode::TAGS:
for_each_dive(i, d)
updateDiveStatus(d, hasTags(filterData.tags, d), res);
break;
}
res.currentChanged = old_current != current_dive;
return res;
}
void DiveFilter::setFilter(const FilterData &data)
{
filterData = data;
emit diveListNotifier.filterReset();
}
#else // SUBSURFACE_MOBILE
#include "desktop-widgets/mapwidget.h"
#include "desktop-widgets/mainwindow.h"
#include "desktop-widgets/divelistview.h"
#include "qt-models/filtermodels.h"
bool FilterData::validFilter() const
{
@ -216,6 +96,7 @@ bool DiveFilter::showDive(const struct dive *d) const
[d] (const filter_constraint &c) { return filter_constraint_match_dive(c, d); });
}
#ifndef SUBSURFACE_MOBILE
void DiveFilter::startFilterDiveSites(QVector<dive_site *> ds)
{
if (++diveSiteRefCount > 1) {
@ -224,7 +105,7 @@ void DiveFilter::startFilterDiveSites(QVector<dive_site *> ds)
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.
// TODO: why here? why not catch the filterReset signal in the map widget
MapWidget::instance()->reload();
emit diveListNotifier.filterReset();
}
@ -263,10 +144,15 @@ bool DiveFilter::diveSiteMode() const
{
return diveSiteRefCount > 0;
}
#else
bool DiveFilter::diveSiteMode() const
{
return false;
}
#endif
void DiveFilter::setFilter(const FilterData &data)
{
filterData = data;
emit diveListNotifier.filterReset();
}
#endif // SUBSURFACE_MOBILE

View file

@ -10,6 +10,8 @@
#include <QStringList>
struct dive;
struct dive_trip;
struct dive_site;
// Structure describing changes of shown status upon applying the filter
struct ShownChange {
@ -18,43 +20,6 @@ struct ShownChange {
bool currentChanged;
};
// 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
struct FilterData {
// On mobile, we support searching fulltext (all fields), people (buddies and divemasters) and tags
enum class Mode {
NONE = 0,
FULLTEXT = 1,
PEOPLE = 2,
TAGS = 3
};
Mode mode = Mode::NONE;
FullTextQuery fullText; // For fulltext
QStringList tags; // For people and tags
};
class DiveFilter {
public:
static DiveFilter *instance();
ShownChange update(const QVector<dive *> &dives) const; // Update filter status of given dives and return dives whose status changed
ShownChange updateAll() const; // Update filter status of all dives and return dives whose status changed
void setFilter(const FilterData &data);
private:
DiveFilter();
FilterData filterData;
};
#else
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 {
@ -73,11 +38,13 @@ class DiveFilter {
public:
static DiveFilter *instance();
bool diveSiteMode() const; // returns true if we're filtering on dive site
bool diveSiteMode() const; // returns true if we're filtering on dive site (on mobile always returns false)
#ifndef SUBSURFACE_MOBILE
const QVector<dive_site *> &filteredDiveSites() const;
void startFilterDiveSites(QVector<dive_site *> ds);
void setFilterDiveSite(QVector<dive_site *> ds);
void stopFilterDiveSites();
#endif
void setFilter(const FilterData &data);
ShownChange update(const QVector<dive *> &dives) const; // Update filter status of given dives and return dives whose status changed
ShownChange updateAll() const; // Update filter status of all dives and return dives whose status changed
@ -96,6 +63,5 @@ private:
// The filter is now not in dive site mode, even if it should
int diveSiteRefCount;
};
#endif // SUBSURFACE_MOBILE
#endif

View file

@ -29,6 +29,7 @@
#include "core/errorhelper.h"
#include "core/file.h"
#include "core/divefilter.h"
#include "core/filterconstraint.h"
#include "core/qthelper.h"
#include "core/qt-gui.h"
#include "core/git-access.h"
@ -2084,6 +2085,13 @@ void QMLManager::restartDownload(QAndroidJniObject usbDevice)
#endif
static filter_constraint make_filter_constraint(filter_constraint_type type, const QString &s)
{
filter_constraint res(type);
filter_constraint_set_stringlist(res, s);
return res;
}
void QMLManager::setFilter(const QString filterText, int index)
{
QString f = filterText.trimmed();
@ -2091,15 +2099,17 @@ void QMLManager::setFilter(const QString filterText, int index)
if (!f.isEmpty()) {
// This is ugly - the indices of the mode are hardcoded!
switch(index) {
default:
case 0: data.mode = FilterData::Mode::FULLTEXT; break;
case 1: data.mode = FilterData::Mode::PEOPLE; break;
case 2: data.mode = FilterData::Mode::TAGS; break;
}
if (data.mode == FilterData::Mode::FULLTEXT)
default:
case 0:
data.fullText = f;
else
data.tags = f.split(",", QString::SkipEmptyParts);
break;
case 1:
data.constraints.push_back(make_filter_constraint(FILTER_CONSTRAINT_PEOPLE, f));
break;
case 2:
data.constraints.push_back(make_filter_constraint(FILTER_CONSTRAINT_TAGS, f));
break;
}
}
DiveFilter::instance()->setFilter(data);
}