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