diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | core/filterconstraint.cpp | 1055 | ||||
-rw-r--r-- | core/filterconstraint.h | 151 |
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 |