summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2020-05-13 15:34:25 +0200
committerGravatar Dirk Hohndel <dirk@hohndel.org>2020-09-29 16:13:03 -0700
commit634152ae43f37a62a67de5a5d5fc4e6b02829ce0 (patch)
tree2fa04e8a9a00196bb2a1bbfff47c08330e28963c /core
parent0b7ba197751ea6ba57db1f9ec116dd791c3b53b7 (diff)
downloadsubsurface-634152ae43f37a62a67de5a5d5fc4e6b02829ce0.tar.gz
filter: add filter constraint object to the core
Adds a filter constraint object to the core, which represents one constraint the user can filter dives with. The plan is to write these constraints to the XML and git logs. Therefore, this code is written in C-style except when it comes to handling strings and dates, which is just too painful in plain C. There is one pointer to QStringList in the class, though when compiled with C, this is simply transformed into a pointer to void. Granted, that smells of an ugly hack. However it's more pragmatic than self-flaggelation with C string and list handling. A filter constraint is supposed to be a very general thing, which can filter for strings, multiple-choice lists, numerical ranges and date ranges. Range constraints have a range mode: less-or-equal, greater-or-equal or in-range. Text constraints have a string mode: startswith, substring or exact. All the data are accessed via setter and getter functions for at least basic levels of isolation, despite being written with a C-interface in mind. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Diffstat (limited to 'core')
-rw-r--r--core/CMakeLists.txt2
-rw-r--r--core/filterconstraint.cpp1055
-rw-r--r--core/filterconstraint.h151
3 files changed, 1208 insertions, 0 deletions
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index b16f546e7..2e84112da 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -84,6 +84,8 @@ set(SUBSURFACE_CORE_LIB_SRCS
exif.h
file.c
file.h
+ filterconstraint.cpp
+ filterconstraint.h
format.cpp
format.h
fulltext.cpp
diff --git a/core/filterconstraint.cpp b/core/filterconstraint.cpp
new file mode 100644
index 000000000..b0a8b43aa
--- /dev/null
+++ b/core/filterconstraint.cpp
@@ -0,0 +1,1055 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "filterconstraint.h"
+#include "dive.h"
+#include "divesite.h"
+#include "errorhelper.h"
+#include "gettextfromc.h"
+#include "qthelper.h"
+#include "tag.h"
+#include "trip.h"
+#include "subsurface-string.h"
+#include "subsurface-time.h"
+#include <QDateTime>
+
+// We use the units enum only internally.
+// Therefore define it here, not in the header file.
+enum filter_constraint_units {
+ FILTER_CONSTRAINT_NO_UNIT = 0,
+ FILTER_CONSTRAINT_LENGTH_UNIT,
+ FILTER_CONSTRAINT_DURATION_UNIT,
+ FILTER_CONSTRAINT_TEMPERATURE_UNIT,
+ FILTER_CONSTRAINT_WEIGHT_UNIT,
+ FILTER_CONSTRAINT_VOLUMETRIC_FLOW_UNIT
+};
+
+static struct type_description {
+ filter_constraint_type type;
+ const char *token; // untranslated token, which will be written to the log and should not contain spaces
+ const char *text_ui; // text for the UI, which will be translated
+ bool has_string_mode; // constraint has textual input
+ bool has_range_mode; // constraint has equal, less than, greater than and from-to modes
+ bool is_star_widget; // is a star widget
+ filter_constraint_units units; // numerical constraints may have units
+ int decimal_places; // number of displayed decimal places for numerical constraints
+ bool has_date; // has a date widget
+ bool has_time; // has a time widget
+} type_descriptions[] = {
+ { FILTER_CONSTRAINT_DATE, "date", QT_TRANSLATE_NOOP("gettextFromC", "date"), false, true, false, FILTER_CONSTRAINT_NO_UNIT, 0, true, false },
+ { FILTER_CONSTRAINT_DATE_TIME, "date_time", QT_TRANSLATE_NOOP("gettextFromC", "date time"), false, true, false, FILTER_CONSTRAINT_NO_UNIT, 0, true, true },
+ { FILTER_CONSTRAINT_TIME_OF_DAY, "time_of_day", QT_TRANSLATE_NOOP("gettextFromC", "time of day"), false, true, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, true },
+ { FILTER_CONSTRAINT_YEAR, "year", QT_TRANSLATE_NOOP("gettextFromC", "year"), false, true, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_DAY_OF_WEEK, "day_of_week", QT_TRANSLATE_NOOP("gettextFromC", "week day"), false, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+
+ { FILTER_CONSTRAINT_RATING, "rating", QT_TRANSLATE_NOOP("gettextFromC", "rating"), false, true, true, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_WAVESIZE, "wavesize", QT_TRANSLATE_NOOP("gettextFromC", "wave size"), false, true, true, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_CURRENT, "current", QT_TRANSLATE_NOOP("gettextFromC", "current"), false, true, true, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_VISIBILITY, "visibility", QT_TRANSLATE_NOOP("gettextFromC", "visibility"), false, true, true, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_SURGE, "surge", QT_TRANSLATE_NOOP("gettextFromC", "surge"), false, true, true, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_CHILL, "chill", QT_TRANSLATE_NOOP("gettextFromC", "chill"), false, true, true, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+
+ { FILTER_CONSTRAINT_DEPTH, "depth", QT_TRANSLATE_NOOP("gettextFromC", "max. depth"), false, true, false, FILTER_CONSTRAINT_LENGTH_UNIT, 1, false, false },
+ { FILTER_CONSTRAINT_DURATION, "duration", QT_TRANSLATE_NOOP("gettextFromC", "duration"), false, true, false, FILTER_CONSTRAINT_DURATION_UNIT, 1, false, false },
+ { FILTER_CONSTRAINT_WEIGHT, "weight", QT_TRANSLATE_NOOP("gettextFromC", "weight"), false, true, false, FILTER_CONSTRAINT_WEIGHT_UNIT, 1, false, false },
+ { FILTER_CONSTRAINT_WATER_TEMP, "water_temp", QT_TRANSLATE_NOOP("gettextFromC", "water temp."), false, true, false, FILTER_CONSTRAINT_TEMPERATURE_UNIT, 1, false, false },
+ { FILTER_CONSTRAINT_AIR_TEMP, "air_temp", QT_TRANSLATE_NOOP("gettextFromC", "air temp."), false, true, false, FILTER_CONSTRAINT_TEMPERATURE_UNIT, 1, false, false },
+ { FILTER_CONSTRAINT_SAC, "sac", QT_TRANSLATE_NOOP("gettextFromC", "SAC"), false, true, false, FILTER_CONSTRAINT_VOLUMETRIC_FLOW_UNIT, 1, false, false },
+
+ { FILTER_CONSTRAINT_LOGGED, "logged", QT_TRANSLATE_NOOP("gettextFromC", "logged"), false, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_PLANNED, "planned", QT_TRANSLATE_NOOP("gettextFromC", "planned"), false, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+
+ { FILTER_CONSTRAINT_DIVE_MODE, "dive_mode", QT_TRANSLATE_NOOP("gettextFromC", "dive mode"), false, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+
+ { FILTER_CONSTRAINT_TAGS, "tags", QT_TRANSLATE_NOOP("gettextFromC", "tags"), true, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_PEOPLE, "people", QT_TRANSLATE_NOOP("gettextFromC", "people"), true, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_LOCATION, "location", QT_TRANSLATE_NOOP("gettextFromC", "location"), true, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_WEIGHT_TYPE, "weight_type", QT_TRANSLATE_NOOP("gettextFromC", "weight type"), true, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_CYLINDER_TYPE, "cylinder_type", QT_TRANSLATE_NOOP("gettextFromC", "cylinder type"), true, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_SUIT, "suit", QT_TRANSLATE_NOOP("gettextFromC", "suit"), true, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+ { FILTER_CONSTRAINT_NOTES, "notes", QT_TRANSLATE_NOOP("gettextFromC", "notes"), true, false, false, FILTER_CONSTRAINT_NO_UNIT, 0, false, false },
+};
+
+static struct string_mode_description {
+ filter_constraint_string_mode mode;
+ const char *token; // untranslated token, which will be written to the log and should not contain spaces
+ const char *text_ui;
+} string_mode_descriptions[] = {
+ { FILTER_CONSTRAINT_STARTS_WITH, "starts_with", QT_TRANSLATE_NOOP("gettextFromC", "starting with") },
+ { FILTER_CONSTRAINT_SUBSTRING, "substring", QT_TRANSLATE_NOOP("gettextFromC", "with substring") },
+ { FILTER_CONSTRAINT_EXACT, "exact", QT_TRANSLATE_NOOP("gettextFromC", "exactly") },
+};
+
+static struct range_mode_description {
+ filter_constraint_range_mode mode;
+ const char *token; // untranslated token, which will be written to the log and should not contain spaces
+ const char *text_ui;
+ const char *text_ui_date;
+} range_mode_descriptions[] = {
+ { FILTER_CONSTRAINT_EQUAL, "equal", QT_TRANSLATE_NOOP("gettextFromC", "equal to"), QT_TRANSLATE_NOOP("gettextFromC", "at") },
+ { FILTER_CONSTRAINT_LESS, "less", QT_TRANSLATE_NOOP("gettextFromC", "at most"), QT_TRANSLATE_NOOP("gettextFromC", "before") },
+ { FILTER_CONSTRAINT_GREATER, "greater", QT_TRANSLATE_NOOP("gettextFromC", "at least"), QT_TRANSLATE_NOOP("gettextFromC", "after") },
+ { FILTER_CONSTRAINT_RANGE, "range", QT_TRANSLATE_NOOP("gettextFromC", "in range"), QT_TRANSLATE_NOOP("gettextFromC", "in range") },
+};
+
+static const char *negate_description[2] {
+ QT_TRANSLATE_NOOP("gettextFromC", "is"),
+ QT_TRANSLATE_NOOP("gettextFromC", "is not"),
+};
+
+// std::size() is only available starting in C++17, so let's roll our own.
+template <typename T, size_t N>
+static constexpr size_t array_size(const T (&)[N])
+{
+ return N;
+}
+
+static constexpr size_t type_descriptions_count()
+{
+ return array_size(type_descriptions);
+}
+
+static constexpr size_t string_mode_descriptions_count()
+{
+ return array_size(string_mode_descriptions);
+}
+
+static constexpr size_t range_mode_descriptions_count()
+{
+ return array_size(range_mode_descriptions);
+}
+
+static const type_description *get_type_description(enum filter_constraint_type type)
+{
+ for (size_t i = 0; i < type_descriptions_count(); ++i) {
+ if (type_descriptions[i].type == type)
+ return &type_descriptions[i];
+ }
+ report_error("unknown filter constraint type: %d", type);
+ return nullptr;
+}
+
+static const string_mode_description *get_string_mode_description(enum filter_constraint_string_mode mode)
+{
+ for (size_t i = 0; i < string_mode_descriptions_count(); ++i) {
+ if (string_mode_descriptions[i].mode == mode)
+ return &string_mode_descriptions[i];
+ }
+ report_error("unknown filter constraint string mode: %d", mode);
+ return nullptr;
+}
+
+static const range_mode_description *get_range_mode_description(enum filter_constraint_range_mode mode)
+{
+ for (size_t i = 0; i < range_mode_descriptions_count(); ++i) {
+ if (range_mode_descriptions[i].mode == mode)
+ return &range_mode_descriptions[i];
+ }
+ report_error("unknown filter constraint range mode: %d", mode);
+ return nullptr;
+}
+
+static enum filter_constraint_type filter_constraint_type_from_string(const char *s)
+{
+ for (size_t i = 0; i < type_descriptions_count(); ++i) {
+ if (same_string(type_descriptions[i].token, s))
+ return type_descriptions[i].type;
+ }
+ report_error("unknown filter constraint type: %s", s);
+ return FILTER_CONSTRAINT_DATE;
+}
+
+static enum filter_constraint_string_mode filter_constraint_string_mode_from_string(const char *s)
+{
+ for (size_t i = 0; i < string_mode_descriptions_count(); ++i) {
+ if (same_string(string_mode_descriptions[i].token, s))
+ return string_mode_descriptions[i].mode;
+ }
+ report_error("unknown filter constraint string mode: %s", s);
+ return FILTER_CONSTRAINT_EXACT;
+}
+
+static enum filter_constraint_range_mode filter_constraint_range_mode_from_string(const char *s)
+{
+ for (size_t i = 0; i < range_mode_descriptions_count(); ++i) {
+ if (same_string(range_mode_descriptions[i].token, s))
+ return range_mode_descriptions[i].mode;
+ }
+ report_error("unknown filter constraint range mode: %s", s);
+ return FILTER_CONSTRAINT_EQUAL;
+}
+
+extern "C" const char *filter_constraint_type_to_string(enum filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ return desc ? desc->token : "unknown";
+}
+
+extern "C" const char *filter_constraint_string_mode_to_string(enum filter_constraint_string_mode mode)
+{
+ const string_mode_description *desc = get_string_mode_description(mode);
+ return desc ? desc->token : "unknown";
+}
+
+extern "C" const char *filter_constraint_range_mode_to_string(enum filter_constraint_range_mode mode)
+{
+ const range_mode_description *desc = get_range_mode_description(mode);
+ return desc ? desc->token : "unknown";
+}
+
+extern "C" int filter_constraint_type_to_index(enum filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ return desc ? desc - type_descriptions : -1;
+}
+
+extern "C" int filter_constraint_string_mode_to_index(enum filter_constraint_string_mode mode)
+{
+ const string_mode_description *desc = get_string_mode_description(mode);
+ return desc ? desc - string_mode_descriptions : -1;
+}
+
+extern "C" int filter_constraint_range_mode_to_index(enum filter_constraint_range_mode mode)
+{
+ const range_mode_description *desc = get_range_mode_description(mode);
+ return desc ? desc - range_mode_descriptions : -1;
+}
+
+extern "C" enum filter_constraint_type filter_constraint_type_from_index(int index)
+{
+ if (index >= 0 && index < (int)type_descriptions_count())
+ return type_descriptions[index].type;
+ return (enum filter_constraint_type)-1;
+}
+
+extern "C" enum filter_constraint_string_mode filter_constraint_string_mode_from_index(int index)
+{
+ if (index >= 0 && index < (int)string_mode_descriptions_count())
+ return string_mode_descriptions[index].mode;
+ return (enum filter_constraint_string_mode)-1;
+}
+
+extern "C" enum filter_constraint_range_mode filter_constraint_range_mode_from_index(int index)
+{
+ if (index >= 0 && index < (int)range_mode_descriptions_count())
+ return range_mode_descriptions[index].mode;
+ return (enum filter_constraint_range_mode)-1;
+}
+
+QString filter_constraint_type_to_string_translated(enum filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ return desc ? gettextFromC::tr(desc->text_ui) : "unknown";
+}
+
+QString filter_constraint_string_mode_to_string_translated(enum filter_constraint_string_mode type)
+{
+ const string_mode_description *desc = get_string_mode_description(type);
+ return desc ? gettextFromC::tr(desc->text_ui) : "unknown";
+}
+
+QString filter_constraint_range_mode_to_string_translated(enum filter_constraint_range_mode type)
+{
+ const range_mode_description *desc = get_range_mode_description(type);
+ return desc ? gettextFromC::tr(desc->text_ui) : "unknown";
+}
+
+QString filter_constraint_negate_to_string_translated(bool negate)
+{
+ return gettextFromC::tr(negate_description[negate]);
+}
+
+static enum filter_constraint_units type_to_unit(enum filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ return desc ? desc->units : FILTER_CONSTRAINT_NO_UNIT;
+}
+
+QString filter_constraint_get_unit(enum filter_constraint_type type)
+{
+ switch (type_to_unit(type)) {
+ case FILTER_CONSTRAINT_NO_UNIT:
+ default:
+ return QString();
+ case FILTER_CONSTRAINT_LENGTH_UNIT:
+ return get_depth_unit();
+ case FILTER_CONSTRAINT_DURATION_UNIT:
+ return "min";
+ case FILTER_CONSTRAINT_TEMPERATURE_UNIT:
+ return get_temp_unit();
+ case FILTER_CONSTRAINT_WEIGHT_UNIT:
+ return get_weight_unit();
+ case FILTER_CONSTRAINT_VOLUMETRIC_FLOW_UNIT:
+ return get_volume_unit() + "/min";
+ }
+}
+
+static int display_to_base_unit(double f, enum filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ if (!desc)
+ return -1;
+ switch (desc->units) {
+ case FILTER_CONSTRAINT_NO_UNIT:
+ default:
+ return (int)lrint(f);
+ case FILTER_CONSTRAINT_LENGTH_UNIT:
+ return prefs.units.length == units::METERS ? lrint(f * 1000.0) : feet_to_mm(f);
+ case FILTER_CONSTRAINT_DURATION_UNIT:
+ return lrint(f * 60.0);
+ case FILTER_CONSTRAINT_TEMPERATURE_UNIT:
+ return prefs.units.temperature == units::CELSIUS ? C_to_mkelvin(f) : F_to_mkelvin(f);
+ case FILTER_CONSTRAINT_WEIGHT_UNIT:
+ return prefs.units.weight == units::KG ? lrint(f * 1000.0) : lbs_to_grams(f);
+ case FILTER_CONSTRAINT_VOLUMETRIC_FLOW_UNIT:
+ return prefs.units.volume == units::LITER ? lrint(f * 1000.0) : lrint(cuft_to_l(f) * 1000.0);
+ }
+}
+
+static double base_to_display_unit(int i, enum filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ if (!desc)
+ return 0.0;
+ switch (desc->units) {
+ case FILTER_CONSTRAINT_NO_UNIT:
+ default:
+ return (double)i;
+ case FILTER_CONSTRAINT_LENGTH_UNIT:
+ return prefs.units.length == units::METERS ? (double)i / 1000.0 : mm_to_feet(i);
+ case FILTER_CONSTRAINT_DURATION_UNIT:
+ return (double)i / 60.0;
+ case FILTER_CONSTRAINT_TEMPERATURE_UNIT:
+ return prefs.units.temperature == units::CELSIUS ? mkelvin_to_C(i) : mkelvin_to_F(i);
+ case FILTER_CONSTRAINT_WEIGHT_UNIT:
+ return prefs.units.weight == units::KG ? (double)i / 1000.0 : grams_to_lbs(i);
+ case FILTER_CONSTRAINT_VOLUMETRIC_FLOW_UNIT:
+ return prefs.units.volume == units::LITER ? (double)i / 1000.0 : ml_to_cuft(i);
+ }
+}
+
+QStringList filter_constraint_type_list_translated()
+{
+ QStringList res;
+ for (size_t i = 0; i < type_descriptions_count(); ++i)
+ res.push_back(gettextFromC::tr(type_descriptions[i].text_ui));
+ return res;
+}
+
+QStringList filter_constraint_string_mode_list_translated()
+{
+ QStringList res;
+ for (size_t i = 0; i < string_mode_descriptions_count(); ++i)
+ res.push_back(gettextFromC::tr(string_mode_descriptions[i].text_ui));
+ return res;
+}
+
+QStringList filter_constraint_range_mode_list_translated()
+{
+ QStringList res;
+ for (size_t i = 0; i < range_mode_descriptions_count(); ++i)
+ res.push_back(gettextFromC::tr(range_mode_descriptions[i].text_ui));
+ return res;
+}
+
+QStringList filter_constraint_range_mode_list_translated_date()
+{
+ QStringList res;
+ for (size_t i = 0; i < range_mode_descriptions_count(); ++i)
+ res.push_back(gettextFromC::tr(range_mode_descriptions[i].text_ui_date));
+ return res;
+}
+
+QStringList filter_constraint_negate_list_translated()
+{
+ return { gettextFromC::tr(negate_description[false]),
+ gettextFromC::tr(negate_description[true]) };
+}
+
+// Currently, we support only two multiple choice items. Hardcode them.
+static bool filter_constraint_is_multiple_choice(enum filter_constraint_type type)
+{
+ return type == FILTER_CONSTRAINT_DIVE_MODE || type == FILTER_CONSTRAINT_DAY_OF_WEEK;
+}
+
+QStringList filter_contraint_multiple_choice_translated(enum filter_constraint_type type)
+{
+ // Currently, we support only two multiple choice lists. Hardcode them.
+ if (type == FILTER_CONSTRAINT_DIVE_MODE) {
+ QStringList types;
+ for (int i = 0; i < NUM_DIVEMODE; i++)
+ types.append(gettextFromC::tr(divemode_text_ui[i]));
+ return types;
+ } else if (type == FILTER_CONSTRAINT_DAY_OF_WEEK) {
+ // I can't wrap my head around the fact that Sunday is the
+ // first day of the week, but that's how it is.
+ return QStringList {
+ gettextFromC::tr("Sunday"),
+ gettextFromC::tr("Monday"),
+ gettextFromC::tr("Tuesday"),
+ gettextFromC::tr("Wednesday"),
+ gettextFromC::tr("Thursday"),
+ gettextFromC::tr("Friday"),
+ gettextFromC::tr("Saturday"),
+ };
+ }
+ return QStringList();
+}
+
+extern "C" bool filter_constraint_is_string(filter_constraint_type type)
+{
+ // Currently a constraint is filter based if and only if it has a string
+ // mode (i.e. starts with, substring, exact). In the future we might also
+ // support string based constraints that do not feature such a mode.
+ return filter_constraint_has_string_mode(type);
+}
+
+extern "C" bool filter_constraint_is_timestamp(filter_constraint_type type)
+{
+ return type == FILTER_CONSTRAINT_DATE || type == FILTER_CONSTRAINT_DATE_TIME;
+}
+
+static bool is_numerical_constraint(filter_constraint_type type)
+{
+ return !filter_constraint_is_string(type) && !filter_constraint_is_timestamp(type);
+}
+
+extern "C" bool filter_constraint_has_string_mode(enum filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ return desc && desc->has_string_mode;
+}
+
+extern "C" bool filter_constraint_has_range_mode(enum filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ return desc && desc->has_range_mode;
+}
+
+extern "C" bool filter_constraint_is_star(filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ return desc && desc->is_star_widget;
+}
+
+extern "C" bool filter_constraint_has_date_widget(filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ return desc && desc->has_date;
+}
+
+extern "C" bool filter_constraint_has_time_widget(filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ return desc && desc->has_time;
+}
+
+extern "C" int filter_constraint_num_decimals(enum filter_constraint_type type)
+{
+ const type_description *desc = get_type_description(type);
+ return desc && desc->decimal_places;
+}
+
+// String constraints are valid if there is at least one term.
+// Other constraints are always valid.
+extern "C" bool filter_constraint_is_valid(const struct filter_constraint *constraint)
+{
+ if (!filter_constraint_is_string(constraint->type))
+ return true;
+ return !constraint->data.string_list->isEmpty();
+}
+
+filter_constraint::filter_constraint(filter_constraint_type typeIn) :
+ type(typeIn),
+ string_mode(FILTER_CONSTRAINT_STARTS_WITH),
+ range_mode(FILTER_CONSTRAINT_GREATER),
+ negate(false)
+{
+ if (filter_constraint_is_timestamp(type)) {
+ // For time constraint, default to something completely arbitrary:
+ // Dives in the last year up to next week (newly added dives typically are in the future).
+ QDateTime now = QDateTime::currentDateTimeUtc();
+ data.timestamp_range.from = dateTimeToTimestamp(now.addYears(-1));
+ data.timestamp_range.to = dateTimeToTimestamp(now.addDays(7));
+ } else if (filter_constraint_is_string(type)) {
+ data.string_list = new QStringList;
+ } else if (filter_constraint_is_star(type)) {
+ data.numerical_range.from = 0;
+ data.numerical_range.to = 5;
+ } else if (filter_constraint_is_multiple_choice(type)) {
+ // By default select all the possible items
+ data.multiple_choice = ~0LLU;
+ } else if (filter_constraint_has_time_widget(type) && !filter_constraint_has_date_widget(type)) {
+ // This is a time field. For now let's set it arbitrarily to the whole day.
+ data.numerical_range.from = 0;
+ data.numerical_range.to = 24 * 3600 - 60;
+ } else if (type == FILTER_CONSTRAINT_YEAR) {
+ // Find dives in the last five years.
+ int year = QDateTime::currentDateTimeUtc().date().year();
+ data.numerical_range.from = year - 5;
+ data.numerical_range.to = year;
+ } else {
+ // For numerical data let's try to find sensible defaults based on the unit.
+ // Obviously, these are arbitrary and we might want to save them.
+ switch (type_to_unit(type)) {
+ case FILTER_CONSTRAINT_NO_UNIT:
+ default:
+ data.numerical_range.from = 0;
+ data.numerical_range.to = 0;
+ break;
+ case FILTER_CONSTRAINT_LENGTH_UNIT:
+ // Length: 0-20 m
+ data.numerical_range.from = 0 * 1000;
+ data.numerical_range.to = 20 * 1000;
+ break;
+ case FILTER_CONSTRAINT_DURATION_UNIT:
+ // Duration: 0-60 min
+ data.numerical_range.from = 0 * 60;
+ data.numerical_range.to = 60 * 60;
+ break;
+ case FILTER_CONSTRAINT_TEMPERATURE_UNIT:
+ // Temperature: 20-30°C
+ data.numerical_range.from = C_to_mkelvin(20);
+ data.numerical_range.to = C_to_mkelvin(30);
+ break;
+ case FILTER_CONSTRAINT_WEIGHT_UNIT:
+ // Weight: 0-10 kg
+ data.numerical_range.from = 0 * 1000;
+ data.numerical_range.to = 10 * 1000;
+ break;
+ case FILTER_CONSTRAINT_VOLUMETRIC_FLOW_UNIT:
+ // SAC: 0-10 l/min
+ data.numerical_range.from = 0 * 1000;
+ data.numerical_range.to = 10 * 1000;
+ break;
+ case FILTER_CONSTRAINT_DENSITY_UNIT:
+ // Water density: 1000-1027 g/l
+ data.numerical_range.from = 1000 * 10;
+ data.numerical_range.to = 1027 * 10;
+ break;
+ }
+ }
+}
+
+filter_constraint::filter_constraint(const filter_constraint &c) :
+ type(c.type),
+ string_mode(c.string_mode),
+ range_mode(c.range_mode),
+ negate(c.negate)
+{
+ if (filter_constraint_is_timestamp(type))
+ data.timestamp_range = c.data.timestamp_range;
+ else if (filter_constraint_is_string(type))
+ data.string_list = new QStringList(*c.data.string_list);
+ else if (filter_constraint_is_multiple_choice(type))
+ data.multiple_choice = c.data.multiple_choice;
+ else
+ data.numerical_range = c.data.numerical_range;
+}
+
+filter_constraint::filter_constraint(const char *type_in, const char *string_mode_in,
+ const char *range_mode_in, bool negate_in, const char *s_in) :
+ type(filter_constraint_type_from_string(type_in)),
+ string_mode(FILTER_CONSTRAINT_STARTS_WITH),
+ range_mode(FILTER_CONSTRAINT_GREATER),
+ negate(negate_in)
+{
+ QString s(s_in);
+ if (filter_constraint_has_string_mode(type))
+ string_mode = filter_constraint_string_mode_from_string(string_mode_in);
+ if (filter_constraint_has_range_mode(type))
+ range_mode = filter_constraint_range_mode_from_string(range_mode_in);
+ if (filter_constraint_is_timestamp(type)) {
+ QStringList l = s.split(',');
+ data.timestamp_range.from = l[0].toLongLong();
+ data.timestamp_range.to = l.size() >= 2 ? l[1].toLongLong() : 0;
+ } else if (filter_constraint_is_string(type)) {
+ // TODO: this obviously breaks if the strings contain ",".
+ // That is currently not supported by the UI, but one day we might
+ // have to escape the strings.
+ data.string_list = new QStringList(s.split(','));
+ } else if (filter_constraint_is_multiple_choice(type)) {
+ data.multiple_choice = s.toLongLong();
+ } else {
+ QStringList l = s.split(',');
+ data.numerical_range.from = l[0].toInt();
+ data.numerical_range.to = l.size() >= 2 ? l[1].toInt() : 0;
+ }
+}
+
+filter_constraint &filter_constraint::operator=(const filter_constraint &c)
+{
+ if (filter_constraint_is_string(type))
+ delete data.string_list;
+ type = c.type;
+ string_mode = c.string_mode;
+ range_mode = c.range_mode;
+ negate = c.negate;
+ if (filter_constraint_is_timestamp(type))
+ data.timestamp_range = c.data.timestamp_range;
+ else if (filter_constraint_is_string(type))
+ data.string_list = new QStringList(*c.data.string_list);
+ else if (filter_constraint_is_multiple_choice(type))
+ data.multiple_choice = c.data.multiple_choice;
+ else
+ data.numerical_range = c.data.numerical_range;
+ return *this;
+}
+
+filter_constraint::~filter_constraint()
+{
+ if (filter_constraint_is_string(type))
+ delete data.string_list;
+}
+
+extern "C" char *filter_constraint_data_to_string(const filter_constraint *c)
+{
+ QString s;
+ if (filter_constraint_is_timestamp(c->type)) {
+ s = QString::number(c->data.timestamp_range.from) + ',' +
+ QString::number(c->data.timestamp_range.to);
+ } else if (filter_constraint_is_string(c->type)) {
+ // TODO: this obviously breaks if the strings contain ",".
+ // That is currently not supported by the UI, but one day we might
+ // have to escape the strings.
+ s = c->data.string_list->join(",");
+ } else if (filter_constraint_is_multiple_choice(c->type)) {
+ s = QString::number(c->data.multiple_choice);
+ } else {
+ s = QString::number(c->data.numerical_range.from) + ',' +
+ QString::number(c->data.numerical_range.to);
+ }
+ return copy_qstring(s);
+}
+
+void filter_constraint_set_stringlist(filter_constraint &c, const QString &s)
+{
+ if (!filter_constraint_is_string(c.type)) {
+ fprintf(stderr, "Setting strings in non-string constraint!\n");
+ return;
+ }
+ c.data.string_list->clear();
+ for (const QString &s: s.split(",", QString::SkipEmptyParts))
+ c.data.string_list->push_back(s.trimmed());
+}
+
+void filter_constraint_set_timestamp_from(filter_constraint &c, timestamp_t from)
+{
+ if (!filter_constraint_is_timestamp(c.type)) {
+ fprintf(stderr, "Setting timestamp from in non-timestamp constraint!\n");
+ return;
+ }
+ c.data.timestamp_range.from = from;
+}
+
+void filter_constraint_set_timestamp_to(filter_constraint &c, timestamp_t to)
+{
+ if (!filter_constraint_is_timestamp(c.type)) {
+ fprintf(stderr, "Setting timestamp to in non-timestamp constraint!\n");
+ return;
+ }
+ c.data.timestamp_range.to = to;
+}
+
+void filter_constraint_set_integer_from(filter_constraint &c, int from)
+{
+ if (!is_numerical_constraint(c.type)) {
+ fprintf(stderr, "Setting integer from of non-numerical constraint!\n");
+ return;
+ }
+ c.data.numerical_range.from = from;
+}
+
+void filter_constraint_set_integer_to(filter_constraint &c, int to)
+{
+ if (!is_numerical_constraint(c.type)) {
+ fprintf(stderr, "Setting integer to of non-numerical constraint!\n");
+ return;
+ }
+ c.data.numerical_range.to = to;
+}
+
+void filter_constraint_set_float_from(filter_constraint &c, double from)
+{
+ if (!is_numerical_constraint(c.type)) {
+ fprintf(stderr, "Setting float from of non-numerical constraint!\n");
+ return;
+ }
+ c.data.numerical_range.from = display_to_base_unit(from, c.type);
+}
+
+void filter_constraint_set_float_to(filter_constraint &c, double to)
+{
+ if (!is_numerical_constraint(c.type)) {
+ fprintf(stderr, "Setting float to of non-numerical constraint!\n");
+ return;
+ }
+ c.data.numerical_range.to = display_to_base_unit(to, c.type);
+}
+
+void filter_constraint_set_multiple_choice(filter_constraint &c, uint64_t multiple_choice)
+{
+ if (!filter_constraint_is_multiple_choice(c.type)) {
+ fprintf(stderr, "Setting multiple-choice to of non-multiple-choice constraint!\n");
+ return;
+ }
+ c.data.multiple_choice = multiple_choice;
+}
+
+QString filter_constraint_get_string(const filter_constraint &c)
+{
+ if (!filter_constraint_is_string(c.type)) {
+ fprintf(stderr, "Getting string of non-string constraint!\n");
+ return QString();
+ }
+ return c.data.string_list->join(",");
+}
+
+int filter_constraint_get_integer_from(const filter_constraint &c)
+{
+ if (!is_numerical_constraint(c.type)) {
+ fprintf(stderr, "Getting integer from of non-numerical constraint!\n");
+ return -1;
+ }
+ return c.data.numerical_range.from;
+}
+
+int filter_constraint_get_integer_to(const filter_constraint &c)
+{
+ if (!is_numerical_constraint(c.type)) {
+ fprintf(stderr, "Getting integer to of non-numerical constraint!\n");
+ return -1;
+ }
+ return c.data.numerical_range.to;
+}
+
+double filter_constraint_get_float_from(const filter_constraint &c)
+{
+ if (!is_numerical_constraint(c.type)) {
+ fprintf(stderr, "Getting float from of non-numerical constraint!\n");
+ return 0.0;
+ }
+ return base_to_display_unit(c.data.numerical_range.from, c.type);
+}
+
+double filter_constraint_get_float_to(const filter_constraint &c)
+{
+ if (!is_numerical_constraint(c.type)) {
+ fprintf(stderr, "Getting float to of non-numerical constraint!\n");
+ return 0.0;
+ }
+ return base_to_display_unit(c.data.numerical_range.to, c.type);
+}
+
+timestamp_t filter_constraint_get_timestamp_from(const filter_constraint &c)
+{
+ if (!filter_constraint_is_timestamp(c.type)) {
+ fprintf(stderr, "Getting timestamp from of non-timestamp constraint!\n");
+ return 0;
+ }
+ return c.data.timestamp_range.from;
+}
+
+timestamp_t filter_constraint_get_timestamp_to(const filter_constraint &c)
+{
+ if (!filter_constraint_is_timestamp(c.type)) {
+ fprintf(stderr, "Getting timestamp to of non-timestamp constraint!\n");
+ return 0;
+ }
+ return c.data.timestamp_range.to;
+}
+
+uint64_t filter_constraint_get_multiple_choice(const filter_constraint &c)
+{
+ if (!filter_constraint_is_multiple_choice(c.type)) {
+ fprintf(stderr, "Getting multiple-choice of non-multiple choice constraint!\n");
+ return 0;
+ }
+ return c.data.multiple_choice;
+}
+
+// Pointer to function that takes two strings and returns whether
+// the first matches the second according to a criterion (substring, starts-with, exact).
+using StrCheck = bool (*) (const QString &s1, const QString &s2);
+
+// Check if a string-list contains at least one string containing the second argument.
+// Comparison is non case sensitive and removes white space.
+static bool listContainsSuperstring(const QStringList &list, const QString &s, StrCheck strchk)
+{
+ return std::any_of(list.begin(), list.end(), [&s,strchk](const QString &s2)
+ { return strchk(s2, s); } );
+}
+
+// Check whether any of the items of the first list is in the second list as a super string.
+// The mode is controlled by the second argument
+static bool check(const filter_constraint &c, const QStringList &list)
+{
+ StrCheck strchk =
+ c.string_mode == FILTER_CONSTRAINT_SUBSTRING ?
+ [](const QString &s1, const QString &s2) { return s1.contains(s2, Qt::CaseInsensitive); } :
+ c.string_mode == FILTER_CONSTRAINT_STARTS_WITH ?
+ [](const QString &s1, const QString &s2) { return s1.startsWith(s2, Qt::CaseInsensitive); } :
+ /* FILTER_CONSTRAINT_EXACT */
+ [](const QString &s1, const QString &s2) { return s1.compare(s2, Qt::CaseInsensitive) == 0; };
+ return std::any_of(c.data.string_list->begin(), c.data.string_list->end(),
+ [&list, strchk](const QString &item)
+ { return listContainsSuperstring(list, item, strchk); }) != c.negate;
+}
+
+static bool has_tags(const filter_constraint &c, const struct dive *d)
+{
+ QStringList dive_tags;
+ for (const tag_entry *tag = d->tag_list; tag; tag = tag->next)
+ dive_tags.push_back(QString(tag->tag->name).trimmed());
+ dive_tags.append(gettextFromC::tr(divemode_text_ui[d->dc.divemode]).trimmed());
+ return check(c, dive_tags);
+}
+
+static bool has_people(const filter_constraint &c, const struct dive *d)
+{
+ QStringList dive_people;
+ for (const QString &s: QString(d->buddy).split(",", QString::SkipEmptyParts))
+ dive_people.push_back(s.trimmed());
+ for (const QString &s: QString(d->divemaster).split(",", QString::SkipEmptyParts))
+ dive_people.push_back(s.trimmed());
+ return check(c, dive_people);
+}
+
+static bool has_locations(const filter_constraint &c, const struct dive *d)
+{
+ QStringList diveLocations;
+ if (d->divetrip)
+ diveLocations.push_back(QString(d->divetrip->location).trimmed());
+
+ if (d->dive_site)
+ diveLocations.push_back(QString(d->dive_site->name).trimmed());
+
+ return check(c, diveLocations);
+}
+
+static bool has_weight_type(const filter_constraint &c, const struct dive *d)
+{
+ QStringList weightsystemTypes;
+ for (int i = 0; i < d->weightsystems.nr; ++i)
+ weightsystemTypes.push_back(d->weightsystems.weightsystems[i].description);
+
+ return check(c, weightsystemTypes);
+}
+
+static bool has_cylinder_type(const filter_constraint &c, const struct dive *d)
+{
+ QStringList cylinderTypes;
+ for (int i = 0; i < d->cylinders.nr; ++i)
+ cylinderTypes.push_back(d->cylinders.cylinders[i].type.description);
+
+ return check(c, cylinderTypes);
+}
+
+static bool has_suits(const filter_constraint &c, const struct dive *d)
+{
+ QStringList diveSuits;
+ if (d->suit)
+ diveSuits.push_back(QString(d->suit));
+ return check(c, diveSuits);
+}
+
+static bool has_notes(const filter_constraint &c, const struct dive *d)
+{
+ QStringList diveNotes;
+ if (d->notes)
+ diveNotes.push_back(QString(d->notes));
+ return check(c, diveNotes);
+}
+
+static bool check_numerical_range(const filter_constraint &c, int v)
+{
+ switch (c.range_mode) {
+ case FILTER_CONSTRAINT_EQUAL:
+ return (v == c.data.numerical_range.from) != c.negate;
+ case FILTER_CONSTRAINT_LESS:
+ return (v <= c.data.numerical_range.to) != c.negate;
+ case FILTER_CONSTRAINT_GREATER:
+ return (v >= c.data.numerical_range.from) != c.negate;
+ case FILTER_CONSTRAINT_RANGE:
+ return (v >= c.data.numerical_range.from && v <= c.data.numerical_range.to) != c.negate;
+ }
+ return false;
+}
+
+// Check a numerical range, but consider a value of 0 as "not set".
+static bool check_numerical_range_non_zero(const filter_constraint &c, int v)
+{
+ if (v == 0)
+ return c.negate;
+ return check_numerical_range(c, v);
+}
+
+static long days_since_epoch(timestamp_t timestamp)
+{
+ return timestamp / (3600 * 24);
+}
+
+static int seconds_since_midnight(timestamp_t timestamp)
+{
+ return timestamp % (3600 * 24);
+}
+
+static bool check_date_range(const filter_constraint &c, const struct dive *d)
+{
+ // We don't consider dives past midnight. Should we?
+ long day = days_since_epoch(d->when);
+ switch (c.range_mode) {
+ case FILTER_CONSTRAINT_EQUAL:
+ return (day == days_since_epoch(c.data.timestamp_range.from)) != c.negate;
+ case FILTER_CONSTRAINT_LESS:
+ return (day <= days_since_epoch(c.data.timestamp_range.to)) != c.negate;
+ case FILTER_CONSTRAINT_GREATER:
+ return (day >= days_since_epoch(c.data.timestamp_range.from)) != c.negate;
+ case FILTER_CONSTRAINT_RANGE:
+ return (day >= days_since_epoch(c.data.timestamp_range.from) &&
+ day <= days_since_epoch(c.data.timestamp_range.to)) != c.negate;
+ }
+ return false;
+}
+
+static bool check_datetime_range(const filter_constraint &c, const struct dive *d)
+{
+ switch (c.range_mode) {
+ case FILTER_CONSTRAINT_EQUAL:
+ // Exact mode is a bit strange for timestamps. Therefore we return any dive
+ // where the given timestamp is during that dive.
+ return time_during_dive_with_offset(d, c.data.timestamp_range.from, 0) != c.negate;
+ case FILTER_CONSTRAINT_LESS:
+ return (dive_endtime(d) <= c.data.timestamp_range.to) != c.negate;
+ case FILTER_CONSTRAINT_GREATER:
+ return (d->when >= c.data.timestamp_range.from) != c.negate;
+ case FILTER_CONSTRAINT_RANGE:
+ return (d->when >= c.data.timestamp_range.from &&
+ dive_endtime(d) <= c.data.timestamp_range.to) != c.negate;
+ }
+ return false;
+}
+
+// This helper function takes explicit from and to values, because the caller might want
+// to reverse them with respect to the order of the constraint. See comment in check_time_of_day_range().
+static bool check_time_of_day_internal(const dive *d, enum filter_constraint_range_mode range_mode, int from, int to, bool negate)
+{
+ switch (range_mode) {
+ case FILTER_CONSTRAINT_EQUAL:
+ // Exact mode is a bit strange for time_of_day. Therefore we return any dive
+ // where the given timestamp is during that dive. Note: this will fail for dives
+ // that run past midnight. We might want to special case that.
+ return (seconds_since_midnight(d->when) <= from &&
+ seconds_since_midnight(dive_endtime(d)) >= from) != negate;
+ case FILTER_CONSTRAINT_LESS:
+ return (seconds_since_midnight(dive_endtime(d)) <= to) != negate;
+ case FILTER_CONSTRAINT_GREATER:
+ return (seconds_since_midnight(d->when) >= from) != negate;
+ case FILTER_CONSTRAINT_RANGE:
+ return (seconds_since_midnight(d->when) >= from &&
+ seconds_since_midnight(dive_endtime(d)) <= to) != negate;
+ }
+ return false;
+}
+
+static bool check_time_of_day_range(const filter_constraint &c, const struct dive *d)
+{
+ // Time-of-day gets special treatment: we consider the range as living on a cyclic support.
+ // This means that if the user searches for dives between 10 pm and 2 am, we flag dives
+ // that happen in either of the [10 pm-midnight] and [midnight-2 am] segments.
+ // This can trivially be realized by inverting the limits and inverting the result.
+ // In the example above: search for all dives which are *not* in the [2 am-10 pm] segment.
+ // This however makes only sense in the EQUAL or RANGE modes.
+ if ((c.range_mode == FILTER_CONSTRAINT_EQUAL || c.range_mode == FILTER_CONSTRAINT_RANGE) &&
+ c.data.numerical_range.from > c.data.numerical_range.to) {
+ return check_time_of_day_internal(d, c.range_mode, c.data.numerical_range.to, c.data.numerical_range.from, !c.negate);
+ } else {
+ return check_time_of_day_internal(d, c.range_mode, c.data.numerical_range.from, c.data.numerical_range.to, c.negate);
+ }
+}
+
+static bool check_year_range(const filter_constraint &c, const struct dive *d)
+{
+ // Note: we don't consider new-year dives. Should we?
+ int year = utc_year(d->when);
+ switch (c.range_mode) {
+ case FILTER_CONSTRAINT_EQUAL:
+ return (year == c.data.numerical_range.from) != c.negate;
+ case FILTER_CONSTRAINT_LESS:
+ return (year <= c.data.numerical_range.to) != c.negate;
+ case FILTER_CONSTRAINT_GREATER:
+ return (year >= c.data.numerical_range.from) != c.negate;
+ case FILTER_CONSTRAINT_RANGE:
+ return (year >= c.data.numerical_range.from &&
+ year <= c.data.numerical_range.to) != c.negate;
+ }
+ return false;
+}
+
+static bool check_multiple_choice(const filter_constraint &c, int v)
+{
+ bool has_bit = c.data.multiple_choice & (1 << v);
+ return has_bit != c.negate;
+}
+
+bool filter_constraint_match_dive(const filter_constraint &c, const struct dive *d)
+{
+ if (filter_constraint_is_string(c.type) && c.data.string_list->isEmpty())
+ return true;
+
+ switch (c.type) {
+ case FILTER_CONSTRAINT_DATE:
+ return check_date_range(c, d);
+ case FILTER_CONSTRAINT_DATE_TIME:
+ return check_datetime_range(c, d);
+ case FILTER_CONSTRAINT_TIME_OF_DAY:
+ return check_time_of_day_range(c, d);
+ case FILTER_CONSTRAINT_YEAR:
+ return check_year_range(c, d);
+ case FILTER_CONSTRAINT_DAY_OF_WEEK:
+ return check_multiple_choice(c, utc_weekday(d->when)); // no support for midnight dives - should we?
+ case FILTER_CONSTRAINT_RATING:
+ return check_numerical_range(c, d->rating);
+ case FILTER_CONSTRAINT_WAVESIZE:
+ return check_numerical_range(c, d->wavesize);
+ case FILTER_CONSTRAINT_CURRENT:
+ return check_numerical_range(c, d->current);
+ case FILTER_CONSTRAINT_VISIBILITY:
+ return check_numerical_range(c, d->visibility);
+ case FILTER_CONSTRAINT_SURGE:
+ return check_numerical_range(c, d->surge);
+ case FILTER_CONSTRAINT_CHILL:
+ return check_numerical_range(c, d->chill);
+ case FILTER_CONSTRAINT_DEPTH:
+ return check_numerical_range(c, d->maxdepth.mm);
+ case FILTER_CONSTRAINT_DURATION:
+ return check_numerical_range(c, d->duration.seconds);
+ case FILTER_CONSTRAINT_WEIGHT:
+ return check_numerical_range(c, total_weight(d));
+ case FILTER_CONSTRAINT_WATER_TEMP:
+ return check_numerical_range(c, d->watertemp.mkelvin);
+ case FILTER_CONSTRAINT_AIR_TEMP:
+ return check_numerical_range(c, d->airtemp.mkelvin);
+ case FILTER_CONSTRAINT_SAC:
+ return check_numerical_range_non_zero(c, d->sac);
+ case FILTER_CONSTRAINT_LOGGED:
+ return has_planned(d, false) != c.negate;
+ case FILTER_CONSTRAINT_PLANNED:
+ return has_planned(d, true) != c.negate;
+ case FILTER_CONSTRAINT_DIVE_MODE:
+ return check_multiple_choice(c, (int)d->dc.divemode); // should we be smarter and check all DCs?
+ case FILTER_CONSTRAINT_TAGS:
+ return has_tags(c, d);
+ case FILTER_CONSTRAINT_PEOPLE:
+ return has_people(c, d);
+ case FILTER_CONSTRAINT_LOCATION:
+ return has_locations(c, d);
+ case FILTER_CONSTRAINT_WEIGHT_TYPE:
+ return has_weight_type(c, d);
+ case FILTER_CONSTRAINT_CYLINDER_TYPE:
+ return has_cylinder_type(c, d);
+ case FILTER_CONSTRAINT_SUIT:
+ return has_suits(c, d);
+ case FILTER_CONSTRAINT_NOTES:
+ return has_notes(c, d);
+ }
+ return false;
+}
diff --git a/core/filterconstraint.h b/core/filterconstraint.h
new file mode 100644
index 000000000..0a9621a86
--- /dev/null
+++ b/core/filterconstraint.h
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+// Structure describing a filter constraint. Accessible from C,
+// since it is written to the log (git or xml).
+#ifndef FILTER_CONSTRAINT_H
+#define FILTER_CONSTRAINT_H
+
+#include "units.h"
+
+struct dive;
+
+#ifdef __cplusplus
+#include <QStringList>
+extern "C" {
+#else
+typedef void QStringList;
+#endif
+
+enum filter_constraint_type {
+ FILTER_CONSTRAINT_DATE,
+ FILTER_CONSTRAINT_DATE_TIME,
+ FILTER_CONSTRAINT_TIME_OF_DAY,
+ FILTER_CONSTRAINT_YEAR,
+ FILTER_CONSTRAINT_DAY_OF_WEEK,
+ FILTER_CONSTRAINT_RATING,
+ FILTER_CONSTRAINT_WAVESIZE,
+ FILTER_CONSTRAINT_CURRENT,
+ FILTER_CONSTRAINT_VISIBILITY,
+ FILTER_CONSTRAINT_SURGE,
+ FILTER_CONSTRAINT_CHILL,
+ FILTER_CONSTRAINT_DEPTH,
+ FILTER_CONSTRAINT_DURATION,
+ FILTER_CONSTRAINT_WEIGHT,
+ FILTER_CONSTRAINT_WATER_TEMP,
+ FILTER_CONSTRAINT_AIR_TEMP,
+ FILTER_CONSTRAINT_SAC,
+ FILTER_CONSTRAINT_LOGGED,
+ FILTER_CONSTRAINT_PLANNED,
+ FILTER_CONSTRAINT_DIVE_MODE,
+ FILTER_CONSTRAINT_TAGS,
+ FILTER_CONSTRAINT_PEOPLE,
+ FILTER_CONSTRAINT_LOCATION,
+ FILTER_CONSTRAINT_WEIGHT_TYPE,
+ FILTER_CONSTRAINT_CYLINDER_TYPE,
+ FILTER_CONSTRAINT_SUIT,
+ FILTER_CONSTRAINT_NOTES
+};
+
+// For string filters
+enum filter_constraint_string_mode {
+ FILTER_CONSTRAINT_STARTS_WITH,
+ FILTER_CONSTRAINT_SUBSTRING,
+ FILTER_CONSTRAINT_EXACT
+};
+
+// For range filters
+enum filter_constraint_range_mode {
+ FILTER_CONSTRAINT_EQUAL,
+ FILTER_CONSTRAINT_LESS,
+ FILTER_CONSTRAINT_GREATER,
+ FILTER_CONSTRAINT_RANGE
+};
+
+struct filter_constraint {
+ enum filter_constraint_type type;
+ enum filter_constraint_string_mode string_mode;
+ enum filter_constraint_range_mode range_mode;
+ bool negate;
+ union {
+ struct {
+ int from; // used for equality comparison
+ int to;
+ } numerical_range;
+ struct {
+ timestamp_t from; // used for equality comparison
+ timestamp_t to;
+ } timestamp_range;
+ QStringList *string_list;
+ uint64_t multiple_choice; // bit-field for multiple choice lists. currently, we support 64 items, extend if needed.
+ } data;
+#ifdef __cplusplus
+ // For C++, define constructors, assignment operators and destructor to make our lives easier.
+ filter_constraint(filter_constraint_type type);
+ filter_constraint(const char *type, const char *string_mode,
+ const char *range_mode, bool negate, const char *data); // from parser data
+ filter_constraint(const filter_constraint &);
+ filter_constraint &operator=(const filter_constraint &);
+ ~filter_constraint();
+#endif
+};
+
+extern const char *filter_constraint_type_to_string(enum filter_constraint_type);
+extern const char *filter_constraint_string_mode_to_string(enum filter_constraint_string_mode);
+extern const char *filter_constraint_range_mode_to_string(enum filter_constraint_range_mode);
+extern bool filter_constraint_is_string(enum filter_constraint_type type);
+extern bool filter_constraint_is_timestamp(enum filter_constraint_type type);
+extern bool filter_constraint_is_star(enum filter_constraint_type type);
+
+// These functions convert enums to indices and vice-versa. We could just define the enums to be identical to the index.
+// However, by using these functions we are more robust, because the lists can be ordered differently than the enums.
+extern int filter_constraint_type_to_index(enum filter_constraint_type);
+extern int filter_constraint_string_mode_to_index(enum filter_constraint_string_mode);
+extern int filter_constraint_range_mode_to_index(enum filter_constraint_range_mode);
+extern enum filter_constraint_type filter_constraint_type_from_index(int index);
+extern enum filter_constraint_string_mode filter_constraint_string_mode_from_index(int index);
+extern enum filter_constraint_range_mode filter_constraint_range_mode_from_index(int index);
+
+extern bool filter_constraint_has_string_mode(enum filter_constraint_type);
+extern bool filter_constraint_has_range_mode(enum filter_constraint_type);
+extern bool filter_constraint_has_date_widget(enum filter_constraint_type);
+extern bool filter_constraint_has_time_widget(enum filter_constraint_type);
+extern int filter_constraint_num_decimals(enum filter_constraint_type);
+extern bool filter_constraint_is_valid(const struct filter_constraint *constraint);
+extern char *filter_constraint_data_to_string(const struct filter_constraint *constraint); // caller takes ownership of returned string
+
+#ifdef __cplusplus
+}
+#endif
+
+// C++ only functions
+#ifdef __cplusplus
+QString filter_constraint_type_to_string_translated(enum filter_constraint_type);
+QString filter_constraint_negate_to_string_translated(bool negate);
+QString filter_constraint_string_mode_to_string_translated(enum filter_constraint_string_mode);
+QString filter_constraint_range_mode_to_string_translated(enum filter_constraint_range_mode);
+QString filter_constraint_get_unit(enum filter_constraint_type); // depends on preferences, empty string if no unit
+QStringList filter_constraint_type_list_translated();
+QStringList filter_constraint_string_mode_list_translated();
+QStringList filter_constraint_range_mode_list_translated();
+QStringList filter_constraint_range_mode_list_translated_date();
+QStringList filter_constraint_negate_list_translated();
+QStringList filter_contraint_multiple_choice_translated(enum filter_constraint_type); // Empty if no multiple-choice
+QString filter_constraint_get_string(const filter_constraint &c);
+int filter_constraint_get_integer_from(const filter_constraint &c);
+int filter_constraint_get_integer_to(const filter_constraint &c);
+double filter_constraint_get_float_from(const filter_constraint &c); // convert according to current units (metric or imperial)
+double filter_constraint_get_float_to(const filter_constraint &c); // convert according to current units (metric or imperial)
+timestamp_t filter_constraint_get_timestamp_from(const filter_constraint &c); // convert according to current units (metric or imperial)
+timestamp_t filter_constraint_get_timestamp_to(const filter_constraint &c); // convert according to current units (metric or imperial)
+uint64_t filter_constraint_get_multiple_choice(const filter_constraint &c);
+void filter_constraint_set_stringlist(filter_constraint &c, const QString &s);
+void filter_constraint_set_integer_from(filter_constraint &c, int from);
+void filter_constraint_set_integer_to(filter_constraint &c, int to);
+void filter_constraint_set_float_from(filter_constraint &c, double from); // convert according to current units (metric or imperial)
+void filter_constraint_set_float_to(filter_constraint &c, double to); // convert according to current units (metric or imperial)
+void filter_constraint_set_timestamp_from(filter_constraint &c, timestamp_t from); // convert according to current units (metric or imperial)
+void filter_constraint_set_timestamp_to(filter_constraint &c, timestamp_t to); // convert according to current units (metric or imperial)
+void filter_constraint_set_multiple_choice(filter_constraint &c, uint64_t);
+bool filter_constraint_match_dive(const filter_constraint &c, const struct dive *d);
+#endif
+
+#endif