diff options
57 files changed, 1162 insertions, 1489 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc29adb8..3021da2d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ +- Core: shift dive time in correct direction [#1893] +- Include average max depth in statistics +- Fix bug in cloud save after removing dives from a trip - Dive: Perform more accurate OTU calculations, and include OTU calculations for rebreather dives [#1851 & #1865]. +- Mobile: UI for copy-paste - Mobile: add initial copy-paste support - Desktop: translate trip date --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cc383d98..1556707e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -411,14 +411,12 @@ if(ANDROID) if((DEFINED ENV{KEYSTORE}) AND (DEFINED ENV{KEYSTORE_PASSWORD})) add_qt_android_apk(${SUBSURFACE_TARGET}.apk ${SUBSURFACE_TARGET} PACKAGE_SOURCES ${CMAKE_BINARY_DIR}/android-mobile DEPENDS ${ANDROID_NATIVE_LIBSSL} ${ANDROID_NATIVE_LIBCRYPT} - BUILDTOOLS_REVISION ${BUILDTOOLS_REVISION} KEYSTORE $ENV{KEYSTORE} Subsurface-mobile KEYSTORE_PASSWORD $ENV{KEYSTORE_PASSWORD} ) message(STATUS "KEYSTORE=$ENV{KEYSTORE} KEYSTORE_PASSWORD=$ENV{KEYSTORE_PASSWORD}") else() add_qt_android_apk(${SUBSURFACE_TARGET}.apk ${SUBSURFACE_TARGET} PACKAGE_SOURCES ${CMAKE_BINARY_DIR}/android-mobile DEPENDS ${ANDROID_NATIVE_LIBSSL} ${ANDROID_NATIVE_LIBCRYPT} - BUILDTOOLS_REVISION ${BUILDTOOLS_REVISION} ) message(STATUS "no KEYSTORE") endif() diff --git a/Documentation/mobile-manual.txt b/Documentation/mobile-manual.txt index 5b48cf920..2a869674d 100644 --- a/Documentation/mobile-manual.txt +++ b/Documentation/mobile-manual.txt @@ -280,10 +280,9 @@ The dive is deleted without asking any confirmation because _Subsurface-mobile_ combination of a long tap on the dive with another tap on the red dustbin is an unambiguous instruction to delete the dive. -A dive can also be deleted from the Details View which has an Action Bar with a dustbin. If this is tapped, -the dive shown in the _Details View_ is deleted. You have a brief opportunity to undo -the delete by tapping the grey _Undo_ button in the message that appears at the bottom of -the screen (see image below). +To choose what dive details to copy, long-press the copy button. This will open +up a configuration page where you can toggle the details you want to copy over +to the destination. By default, the following fields are copied: image::mobile-images/Delete_undo.jpg["FIGURE: Undo delete dive",align="center"] diff --git a/android-mobile/build.gradle b/android-mobile/build.gradle new file mode 100644 index 000000000..4dd4a9f32 --- /dev/null +++ b/android-mobile/build.gradle @@ -0,0 +1,64 @@ +/******************************************************* + * SPDX-License-Identifier: GPL-2.0 + * Subsurface-Mobile own Gradle build spec. Derived from + * the one supplied by Qt. + *******************************************************/ +buildscript { + repositories { + jcenter() + maven { url "https://dl.bintray.com/android/android-tools/" } + } + + dependencies { + classpath 'com.android.tools.build:gradle:2.3.3' + } +} + +allprojects { + repositories { + jcenter() + maven { url "https://dl.bintray.com/android/android-tools/" } + } +} + +apply plugin: 'com.android.application' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} + +android { + /******************************************************* + * The following variables: + * - androidBuildToolsVersion, + * - androidCompileSdkVersion + * - qt5AndroidDir - holds the path to qt android files + * needed to build any Qt application + * on Android. + * + * are defined in gradle.properties file. This file is + * updated by QtCreator and androiddeployqt tools. + * Changing them manually might break the compilation! + *******************************************************/ + + compileSdkVersion androidCompileSdkVersion.toInteger() + + buildToolsVersion androidBuildToolsVersion + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qt5AndroidDir + '/res', 'res'] + resources.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + lintOptions { + abortOnError false + } +} diff --git a/core/btdiscovery.cpp b/core/btdiscovery.cpp index 6ec4bba7f..207dfacf8 100644 --- a/core/btdiscovery.cpp +++ b/core/btdiscovery.cpp @@ -74,6 +74,11 @@ static dc_descriptor_t *getDeviceType(QString btName) product = "i770R"; } + if (btName.contains(QRegularExpression("^ER\\d{6}$"))) { + vendor = "Oceanic"; + product = "Pro Plus X"; + } + if (!vendor.isEmpty() && !product.isEmpty()) return descriptorLookup.value(vendor + product); diff --git a/core/dive.c b/core/dive.c index 47143aaa3..49431bffe 100644 --- a/core/dive.c +++ b/core/dive.c @@ -3357,70 +3357,6 @@ void dump_taglist(const char *intro, struct tag_entry *tl) fprintf(stderr, "\n"); } -// count the dives where the tag list contains the given tag -int count_dives_with_tag(const char *tag) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (empty_string(tag)) { - // count dives with no tags - if (d->tag_list == NULL) - counter++; - } else if (taglist_contains(d->tag_list, tag)) { - counter++; - } - } - return counter; -} - -extern bool string_sequence_contains(const char *string_sequence, const char *text); - -// count the dives where the person is included in the comma separated string sequences of buddies or divemasters -int count_dives_with_person(const char *person) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (empty_string(person)) { - // solo dive - if (empty_string(d->buddy) && empty_string(d->divemaster)) - counter++; - } else if (string_sequence_contains(d->buddy, person) || string_sequence_contains(d->divemaster, person)) { - counter++; - } - } - return counter; -} - -// count the dives with exactly the location -int count_dives_with_location(const char *location) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (same_string(get_dive_location(d), location)) - counter++; - } - return counter; -} - -// count the dives with exactly the suit -int count_dives_with_suit(const char *suit) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (same_string(d->suit, suit)) - counter++; - } - return counter; -} - /* * Merging two dives can be subtle, because there's two different ways * of merging: @@ -3686,17 +3622,6 @@ static int split_dive_at(const struct dive *dive, int a, int b, struct dive **ou return nr; } -static void finish_split(int nr, struct dive *old, struct dive *d1, struct dive *d2) -{ - if (old->divetrip) { - add_dive_to_trip(d1, old->divetrip); - add_dive_to_trip(d2, old->divetrip); - } - delete_single_dive(nr); - add_single_dive(nr, d1); - add_single_dive(nr + 1, d2); -} - /* in freedive mode we split for as little as 10 seconds on the surface, * otherwise we use a minute */ static bool should_split(const struct divecomputer *dc, int t1, int t2) @@ -3716,7 +3641,7 @@ static bool should_split(const struct divecomputer *dc, int t1, int t2) * * In other words, this is a (simplified) reversal of the dive merging. */ -int split_dive_dont_insert(const struct dive *dive, struct dive **new1, struct dive **new2) +int split_dive(const struct dive *dive, struct dive **new1, struct dive **new2) { int i; int at_surface, surface_start; @@ -3758,16 +3683,7 @@ int split_dive_dont_insert(const struct dive *dive, struct dive **new1, struct d return -1; } -void split_dive(struct dive *dive) -{ - int nr; - struct dive *new1, *new2; - - if ((nr = split_dive_dont_insert(dive, &new1, &new2)) >= 0) - finish_split(nr, dive, new1, new2); -} - -int split_dive_at_time_dont_insert(const struct dive *dive, duration_t time, struct dive **new1, struct dive **new2) +int split_dive_at_time(const struct dive *dive, duration_t time, struct dive **new1, struct dive **new2) { int i = 0; struct sample *sample = dive->dc.sample; @@ -3783,15 +3699,6 @@ int split_dive_at_time_dont_insert(const struct dive *dive, duration_t time, str return split_dive_at(dive, i, i - 1, new1, new2); } -void split_dive_at_time(struct dive *dive, duration_t time) -{ - int nr; - struct dive *new1, *new2; - - if ((nr = split_dive_at_time_dont_insert(dive, time, &new1, &new2)) >= 0) - finish_split(nr, dive, new1, new2); -} - /* * "dc_maxtime()" is how much total time this dive computer * has for this dive. Note that it can differ from "duration" @@ -4004,20 +3911,51 @@ static bool new_picture_for_dive(struct dive *d, const char *filename) return true; } +/* Return distance of timestamp to time of dive. Result is always positive, 0 means during dive. */ +static timestamp_t time_from_dive(const struct dive *d, timestamp_t timestamp) +{ + timestamp_t end_time = dive_endtime(d); + if (timestamp < d->when) + return d->when - timestamp; + else if (timestamp > end_time) + return timestamp - end_time; + else + return 0; +} + // only add pictures that have timestamps between 30 minutes before the dive and // 30 minutes after the dive ends #define D30MIN (30 * 60) -bool dive_check_picture_time(const struct dive *d, int shift_time, timestamp_t timestamp) +static bool dive_check_picture_time(const struct dive *d, timestamp_t timestamp) { - offset_t offset; - if (timestamp) { - offset.seconds = timestamp - d->when + shift_time; - if (offset.seconds > -D30MIN && offset.seconds < dive_totaltime(d) + D30MIN) { - // this picture belongs to this dive - return true; + return time_from_dive(d, timestamp) < D30MIN; +} + +/* Return dive closest selected dive to given timestamp or NULL if no dives are selected. */ +static struct dive *nearest_selected_dive(timestamp_t timestamp) +{ + struct dive *d, *res = NULL; + int i; + timestamp_t offset, min = 0; + + for_each_dive(i, d) { + if (!d->selected) + continue; + offset = time_from_dive(d, timestamp); + if (!res || offset < min) { + res = d; + min = offset; } + + /* We suppose that dives are sorted chronologically. Thus + * if the offset starts to increase, we can end. This ignores + * pathological cases such as overlapping dives. In such a + * case the user will have to add pictures manually. + */ + if (offset == 0 || offset > min) + break; } - return false; + return res; } bool picture_check_valid_time(timestamp_t timestamp, int shift_time) @@ -4026,18 +3964,26 @@ bool picture_check_valid_time(timestamp_t timestamp, int shift_time) struct dive *dive; for_each_dive (i, dive) - if (dive->selected && dive_check_picture_time(dive, shift_time, timestamp)) + if (dive->selected && dive_check_picture_time(dive, timestamp + shift_time)) return true; return false; } -void dive_create_picture(struct dive *dive, const char *filename, int shift_time, bool match_all) +void create_picture(const char *filename, int shift_time, bool match_all) { struct metadata metadata; + struct dive *dive; + timestamp_t timestamp; + get_metadata(filename, &metadata); + timestamp = metadata.timestamp + shift_time; + dive = nearest_selected_dive(timestamp); + + if (!dive) + return; if (!new_picture_for_dive(dive, filename)) return; - if (!match_all && !dive_check_picture_time(dive, shift_time, metadata.timestamp)) + if (!match_all && !dive_check_picture_time(dive, timestamp)) return; struct picture *picture = alloc_picture(); diff --git a/core/dive.h b/core/dive.h index 92bb1bb33..6bc0f6fae 100644 --- a/core/dive.h +++ b/core/dive.h @@ -227,12 +227,7 @@ void taglist_cleanup(struct tag_entry **tag_list); void taglist_init_global(); void taglist_free(struct tag_entry *tag_list); - bool taglist_contains(struct tag_entry *tag_list, const char *tag); -int count_dives_with_tag(const char *tag); -int count_dives_with_person(const char *person); -int count_dives_with_location(const char *location); -int count_dives_with_suit(const char *suit); struct extra_data { const char *key; @@ -278,10 +273,10 @@ struct divecomputer { #define W_IDX_PRIMARY 0 #define W_IDX_SECONDARY 1 -struct dive_table { +typedef struct dive_table { int nr, allocated; struct dive **dives; -}; +} dive_table_t; typedef struct dive_trip { @@ -376,8 +371,7 @@ struct picture { extern struct picture *alloc_picture(); extern void free_picture(struct picture *picture); -extern bool dive_check_picture_time(const struct dive *d, int shift_time, timestamp_t timestamp); -extern void dive_create_picture(struct dive *d, const char *filename, int shift_time, bool match_all); +extern void create_picture(const char *filename, int shift_time, bool match_all); extern void dive_add_picture(struct dive *d, struct picture *newpic); extern bool dive_remove_picture(struct dive *d, const char *filename); extern unsigned int dive_get_picture_count(struct dive *d); @@ -425,7 +419,7 @@ extern const struct units SI_units, IMPERIAL_units; extern const struct units *get_units(void); extern int run_survey, verbose, quit, force_root; -extern struct dive_table dive_table, downloadTable; +extern struct dive_table dive_table; extern struct dive displayed_dive; extern unsigned int dc_number; extern struct dive *current_dive; @@ -553,10 +547,8 @@ extern void fixup_dc_duration(struct divecomputer *dc); extern int dive_getUniqID(); extern unsigned int dc_airtemp(const struct divecomputer *dc); extern unsigned int dc_watertemp(const struct divecomputer *dc); -extern int split_dive_dont_insert(const struct dive *dive, struct dive **new1, struct dive **new2); -extern void split_dive(struct dive *); -extern int split_dive_at_time_dont_insert(const struct dive *dive, duration_t time, struct dive **new1, struct dive **new2); -extern void split_dive_at_time(struct dive *dive, duration_t time); +extern int split_dive(const struct dive *dive, struct dive **new1, struct dive **new2); +extern int split_dive_at_time(const struct dive *dive, duration_t time, struct dive **new1, struct dive **new2); extern struct dive *merge_dives(const struct dive *a, const struct dive *b, int offset, bool prefer_downloaded, struct dive_trip **trip); extern struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded); extern struct event *clone_event(const struct event *src_ev); @@ -759,10 +751,14 @@ extern void average_max_depth(struct diveplan *dive, int *avg_depth, int *max_de #ifdef __cplusplus } -/* Make pointers to dive and dive_trip "Qt metatypes" so that they can - * be passed through QVariants. */ +/* Make pointers to dive, dive_trip and dive_table "Qt metatypes" so that they can + * be passed through QVariants and through QML. + * Note: we have to use the typedef "dive_table_t" instead of "struct dive_table", + * because MOC removes the "struct", but dive_table is already the name of a global + * variable, leading to compilation errors. */ Q_DECLARE_METATYPE(struct dive *); Q_DECLARE_METATYPE(struct dive_trip *); +Q_DECLARE_METATYPE(dive_table_t *); #endif diff --git a/core/divelist.c b/core/divelist.c index ee7986466..4659261cd 100644 --- a/core/divelist.c +++ b/core/divelist.c @@ -19,7 +19,6 @@ * void insert_trip(dive_trip_t *dive_trip_p) * void unregister_trip(dive_trip_t *trip) * void free_trip(dive_trip_t *trip) - * void remove_dive_from_trip(struct dive *dive) * void remove_dive_from_trip(struct dive *dive, bool was_autogen) * void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) * dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) @@ -32,12 +31,10 @@ * void delete_single_dive(int idx) * void add_dive_to_table(struct dive_table *table, int idx, struct dive *dive) * void add_single_dive(int idx, struct dive *dive) - * struct dive *merge_two_dives(struct dive *a, struct dive *b) * void select_dive(struct dive *dive) * void deselect_dive(struct dive *dive) * void mark_divelist_changed(int changed) * int unsaved_changes() - * void remove_autogen_trips() * bool dive_less_than(const struct dive *a, const struct dive *b) * bool trip_less_than(const struct dive_trip *a, const struct dive_trip *b) * bool dive_or_trip_less_than(struct dive_or_trip a, struct dive_or_trip b) @@ -77,9 +74,6 @@ dive_trip_t *dive_trip_list; unsigned int amount_selected; -// We need to stop using globals, really. -struct dive_table downloadTable; - #if DEBUG_SELECTION_TRACKING void dump_selection(void) { @@ -912,11 +906,14 @@ void remove_dive_from_trip(struct dive *dive, short was_autogen) delete_trip(trip); } +/* Add dive to a trip. Caller is responsible for removing dive + * from trip beforehand. */ void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) { if (dive->divetrip == trip) return; - remove_dive_from_trip(dive, false); + if (dive->divetrip) + fprintf(stderr, "Warning: adding dive to trip that has trip set\n"); add_dive_to_table(&trip->dives, -1, dive); dive->divetrip = trip; } @@ -1075,16 +1072,15 @@ void delete_dive_from_table(struct dive_table *table, int idx) unregister_dive_from_table(table, idx); } -/* this removes a dive from the dive table and trip-list but doesn't - * free the resources associated with the dive. It returns a pointer - * to the unregistered dive. The returned dive has the selection- - * and hidden-flags cleared. */ +/* This removes a dive from the global dive table but doesn't free the + * resources associated with the dive. The caller must removed the dive + * from the trip-list. Returns a pointer to the unregistered dive. + * The unregistered dive has the selection- and hidden-flags cleared. */ struct dive *unregister_dive(int idx) { struct dive *dive = get_dive(idx); if (!dive) return NULL; /* this should never happen */ - remove_dive_from_trip(dive, false); unregister_dive_from_table(&dive_table, idx); if (dive->selected) amount_selected--; @@ -1092,8 +1088,8 @@ struct dive *unregister_dive(int idx) return dive; } -/* this implements the mechanics of removing the dive from the table, - * but doesn't deal with updating dive trips, etc */ +/* this implements the mechanics of removing the dive from the global + * dive table and the trip, but doesn't deal with updating dive trips, etc */ void delete_single_dive(int idx) { struct dive *dive = get_dive(idx); @@ -1101,8 +1097,8 @@ void delete_single_dive(int idx) return; /* this should never happen */ if (dive->selected) deselect_dive(dive); - dive = unregister_dive(idx); - free_dive(dive); + remove_dive_from_trip(dive, false); + delete_dive_from_table(&dive_table, idx); } struct dive **grow_dive_table(struct dive_table *table) @@ -1184,87 +1180,6 @@ bool consecutive_selected() return consecutive; } -/* - * Merge two dives. 'a' is always before 'b' in the dive list - * (and thus in time). - */ -struct dive *merge_two_dives(struct dive *a, struct dive *b) -{ - struct dive *res; - int i, j, nr, nrdiff; - int id; - - if (!a || !b) - return NULL; - - id = a->id; - i = get_divenr(a); - j = get_divenr(b); - if (i < 0 || j < 0) - // something is wrong with those dives. Bail - return NULL; - res = merge_dives(a, b, b->when - a->when, false, NULL); - if (!res) - return NULL; - - /* - * If 'a' and 'b' were numbered, and in proper order, - * then the resulting dive will get the first number, - * and the subsequent dives will be renumbered by the - * difference. - * - * So if you had a dive list 1 3 6 7 8, and you - * merge 1 and 3, the resulting numbered list will - * be 1 4 5 6, because we assume that there were - * some missing dives (originally dives 4 and 5), - * that now will still be missing (dives 2 and 3 - * in the renumbered world). - * - * Obviously the normal case is that everything is - * consecutive, and the difference will be 1, so the - * above example is not supposed to be normal. - */ - nrdiff = 0; - nr = a->number; - if (a->number && b->number > a->number) { - res->number = nr; - nrdiff = b->number - nr; - } - - add_single_dive(i, res); - delete_single_dive(i + 1); - delete_single_dive(j); - // now make sure that we keep the id of the first dive. - // why? - // because this way one of the previously selected ids is still around - res->id = id; - - // renumber dives from merged one in advance by difference between - // merged dives numbers. Do not renumber if actual number is zero. - for (; j < dive_table.nr; j++) { - struct dive *dive = dive_table.dives[j]; - int newnr; - - if (!dive->number) - continue; - newnr = dive->number - nrdiff; - - /* - * Don't renumber stuff that isn't in order! - * - * So if the new dive number isn't larger than the - * previous dive number, just stop here. - */ - if (newnr <= nr) - break; - dive->number = newnr; - nr = newnr; - } - - mark_divelist_changed(true); - return res; -} - void select_dive(struct dive *dive) { if (!dive) @@ -1336,34 +1251,15 @@ void filter_dive(struct dive *d, bool shown) } -/* This only gets called with non-NULL trips. - * It does not combine notes or location, just picks the first one - * (or the second one if the first one is empty */ -void combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b) -{ - if (empty_string(trip_a->location) && trip_b->location) { - free(trip_a->location); - trip_a->location = strdup(trip_b->location); - } - if (empty_string(trip_a->notes) && trip_b->notes) { - free(trip_a->notes); - trip_a->notes = strdup(trip_b->notes); - } - /* this also removes the dives from trip_b and eventually - * calls delete_trip(trip_b) when the last dive has been moved */ - while (trip_b->dives.nr > 0) - add_dive_to_trip(trip_b->dives.dives[0], trip_a); -} - /* Out of two strings, copy the string that is not empty (if any). */ static char *copy_non_empty_string(const char *a, const char *b) { return copy_string(empty_string(b) ? a : b); } -/* Combine trips new. This combines two trips, generating a +/* This combines the information of two trips, generating a * new trip. To support undo, we have to preserve the old trips. */ -dive_trip_t *combine_trips_create(struct dive_trip *trip_a, struct dive_trip *trip_b) +dive_trip_t *combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b) { dive_trip_t *trip; @@ -1387,19 +1283,6 @@ int unsaved_changes() return dive_list_changed; } -void remove_autogen_trips() -{ - int i; - struct dive *dive; - - for_each_dive(i, dive) { - dive_trip_t *trip = dive->divetrip; - - if (trip && trip->autogen) - remove_dive_from_trip(dive, true); - } -} - /* * When adding dives to the dive table, we try to renumber * the new dives based on any old dives in the dive table. @@ -1522,10 +1405,8 @@ static bool try_to_merge_into(struct dive *dive_to_add, int idx, bool prefer_imp merged->id = old_dive->id; merged->selected = old_dive->selected; dive_table.dives[idx] = merged; - if (trip) { + if (trip) remove_dive_from_trip(old_dive, false); - add_dive_to_trip(merged, trip); - } free_dive(old_dive); remove_dive_from_trip(dive_to_add, false); free_dive(dive_to_add); diff --git a/core/divelist.h b/core/divelist.h index 11fa75f50..ce4943660 100644 --- a/core/divelist.h +++ b/core/divelist.h @@ -14,7 +14,6 @@ struct dive; extern void update_cylinder_related_info(struct dive *); extern void mark_divelist_changed(bool); extern int unsaved_changes(void); -extern void remove_autogen_trips(void); extern int init_decompression(struct deco_state *ds, struct dive *dive); /* divelist core logic functions */ @@ -37,15 +36,13 @@ extern dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive); extern dive_trip_t *get_dives_to_autogroup(int start, int *from, int *to, bool *allocated); extern dive_trip_t *get_trip_for_new_dive(struct dive *new_dive, bool *allocated); extern void autogroup_dives(void); -extern struct dive *merge_two_dives(struct dive *a, struct dive *b); extern bool consecutive_selected(); extern void select_dive(struct dive *dive); extern void deselect_dive(struct dive *dive); extern void select_dives_in_trip(struct dive_trip *trip); extern void deselect_dives_in_trip(struct dive_trip *trip); extern void filter_dive(struct dive *d, bool shown); -extern void combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b); -extern dive_trip_t *combine_trips_create(struct dive_trip *trip_a, struct dive_trip *trip_b); +extern dive_trip_t *combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b); extern struct dive *first_selected_dive(); extern struct dive *last_selected_dive(); extern bool is_trip_before_after(const struct dive *dive, bool before); diff --git a/core/downloadfromdcthread.cpp b/core/downloadfromdcthread.cpp index 8e937346d..2fdebf129 100644 --- a/core/downloadfromdcthread.cpp +++ b/core/downloadfromdcthread.cpp @@ -24,7 +24,6 @@ static QString str_error(const char *fmt, ...) return str; } - static void updateRememberedDCs() { QString current = qPrefDiveComputer::vendor() + " - " + qPrefDiveComputer::product() + " - " + qPrefDiveComputer::device(); @@ -60,9 +59,9 @@ static void updateRememberedDCs() } -DownloadThread::DownloadThread() +DownloadThread::DownloadThread() : downloadTable({ 0 }), + m_data(DCDeviceData::instance()) { - m_data = DCDeviceData::instance(); } void DownloadThread::run() @@ -81,7 +80,7 @@ void DownloadThread::run() internalData->devname = "ftdi"; #endif qDebug() << "Starting download from " << (internalData->bluetooth_mode ? "BT" : internalData->devname); - downloadTable.nr = 0; + clear_table(&downloadTable); Q_ASSERT(internalData->download_table != nullptr); const char *errorText; @@ -254,7 +253,6 @@ void show_computer_list() qDebug() << msg; } } -DCDeviceData *DCDeviceData::m_instance = NULL; DCDeviceData::DCDeviceData() { @@ -276,18 +274,12 @@ DCDeviceData::DCDeviceData() #else data.libdc_log = false; #endif - if (m_instance) { - qDebug() << "already have an instance of DCDevieData"; - return; - } - m_instance = this; } DCDeviceData *DCDeviceData::instance() { - if (!m_instance) - m_instance = new DCDeviceData; - return m_instance; + static DCDeviceData self; + return &self; } QStringList DCDeviceData::getProductListFromVendor(const QString &vendor) @@ -310,6 +302,11 @@ DCDeviceData *DownloadThread::data() return m_data; } +struct dive_table *DownloadThread::table() +{ + return &downloadTable; +} + QString DCDeviceData::vendor() const { return data.vendor; diff --git a/core/downloadfromdcthread.h b/core/downloadfromdcthread.h index 3e2d7ddc0..b380a88a1 100644 --- a/core/downloadfromdcthread.h +++ b/core/downloadfromdcthread.h @@ -6,6 +6,7 @@ #include <QHash> #include <QLoggingCategory> +#include "dive.h" #include "libdivecomputer.h" #include "connectionlistmodel.h" #if BT_SUPPORT @@ -51,7 +52,6 @@ public: void setSaveDump(bool dumpMode); void setSaveLog(bool saveLog); private: - static DCDeviceData *m_instance; device_data_t data; // Bluetooth name is managed outside of libdivecomputer @@ -60,15 +60,18 @@ private: class DownloadThread : public QThread { Q_OBJECT + Q_PROPERTY(dive_table_t *table READ table CONSTANT) public: DownloadThread(); void run() override; DCDeviceData *data(); + struct dive_table *table(); QString error; private: + struct dive_table downloadTable; DCDeviceData *m_data; }; diff --git a/core/libdivecomputer.c b/core/libdivecomputer.c index a2e2890b8..4d2d29bad 100644 --- a/core/libdivecomputer.c +++ b/core/libdivecomputer.c @@ -1387,7 +1387,7 @@ const char *do_libdivecomputer_import(device_data_t *data) /* TODO: Show the logfile to the user on error. */ dc_device_close(data->device); data->device = NULL; - if (!downloadTable.nr) + if (!data->download_table->nr) dev_info(data, translate("gettextFromC", "No new dives downloaded from dive computer")); } dc_iostream_close(data->iostream); diff --git a/core/qthelper.cpp b/core/qthelper.cpp index 8fbaa31af..d47a39fdf 100644 --- a/core/qthelper.cpp +++ b/core/qthelper.cpp @@ -362,20 +362,6 @@ extern "C" void copy_image_and_overwrite(const char *cfileName, const char *path qDebug() << "copy of" << fileName << "to" << newName << "failed"; } -extern "C" bool string_sequence_contains(const char *string_sequence, const char *text) -{ - if (empty_string(text) || empty_string(string_sequence)) - return false; - - QString stringSequence(string_sequence); - QStringList strings = stringSequence.split(",", QString::SkipEmptyParts); - Q_FOREACH (const QString& string, strings) { - if (string.trimmed().compare(QString(text).trimmed(), Qt::CaseInsensitive) == 0) - return true; - } - return false; -} - static bool lessThan(const QPair<QString, int> &a, const QPair<QString, int> &b) { return a.second < b.second; diff --git a/core/statistics.c b/core/statistics.c index 6f5efe64f..d6d9418d7 100644 --- a/core/statistics.c +++ b/core/statistics.c @@ -53,6 +53,7 @@ static void process_dive(struct dive *dive, stats_t *stats) stats->max_depth.mm = dive->maxdepth.mm; if (stats->min_depth.mm == 0 || dive->maxdepth.mm < stats->min_depth.mm) stats->min_depth.mm = dive->maxdepth.mm; + stats->combined_max_depth.mm += dive->maxdepth.mm; process_temperatures(dive, stats); diff --git a/core/statistics.h b/core/statistics.h index d3707b9cb..6072f93b2 100644 --- a/core/statistics.h +++ b/core/statistics.h @@ -24,6 +24,7 @@ typedef struct depth_t max_depth; depth_t min_depth; depth_t avg_depth; + depth_t combined_max_depth; volume_t max_sac; volume_t min_sac; volume_t avg_sac; diff --git a/core/subsurface-qt/DiveObjectHelper.cpp b/core/subsurface-qt/DiveObjectHelper.cpp index e8e11e7d9..c85a3475e 100644 --- a/core/subsurface-qt/DiveObjectHelper.cpp +++ b/core/subsurface-qt/DiveObjectHelper.cpp @@ -8,14 +8,13 @@ #include "core/subsurface-string.h" #include "qt-models/tankinfomodel.h" -static QString EMPTY_DIVE_STRING = QStringLiteral(""); enum returnPressureSelector {START_PRESSURE, END_PRESSURE}; static QString getFormattedWeight(struct dive *dive, unsigned int idx) { weightsystem_t *weight = &dive->weightsystem[idx]; if (!weight->description) - return QString(EMPTY_DIVE_STRING); + return QString(); QString fmt = QString(weight->description); fmt += ", " + get_weight_string(weight->weight, true); return fmt; @@ -26,7 +25,7 @@ static QString getFormattedCylinder(struct dive *dive, unsigned int idx) cylinder_t *cyl = &dive->cylinder[idx]; const char *desc = cyl->type.description; if (!desc && idx > 0) - return QString(EMPTY_DIVE_STRING); + return QString(); QString fmt = desc ? QString(desc) : gettextFromC::tr("unknown"); fmt += ", " + get_volume_string(cyl->type.size, true); fmt += ", " + get_pressure_string(cyl->type.workingpressure, true); @@ -107,7 +106,7 @@ QString DiveObjectHelper::time() const QString DiveObjectHelper::location() const { - return get_dive_location(m_dive) ? QString::fromUtf8(get_dive_location(m_dive)) : EMPTY_DIVE_STRING; + return get_dive_location(m_dive) ? QString::fromUtf8(get_dive_location(m_dive)) : QString(); } QString DiveObjectHelper::gps() const @@ -149,35 +148,27 @@ QString DiveObjectHelper::depth() const QString DiveObjectHelper::divemaster() const { - return m_dive->divemaster ? m_dive->divemaster : EMPTY_DIVE_STRING; + return m_dive->divemaster ? m_dive->divemaster : QString(); } QString DiveObjectHelper::buddy() const { - return m_dive->buddy ? m_dive->buddy : EMPTY_DIVE_STRING; + return m_dive->buddy ? m_dive->buddy : QString(); } QString DiveObjectHelper::airTemp() const { - QString temp = get_temperature_string(m_dive->airtemp, true); - if (temp.isEmpty()) { - temp = EMPTY_DIVE_STRING; - } - return temp; + return get_temperature_string(m_dive->airtemp, true); } QString DiveObjectHelper::waterTemp() const { - QString temp = get_temperature_string(m_dive->watertemp, true); - if (temp.isEmpty()) { - temp = EMPTY_DIVE_STRING; - } - return temp; + return get_temperature_string(m_dive->watertemp, true); } QString DiveObjectHelper::notes() const { - QString tmp = m_dive->notes ? QString::fromUtf8(m_dive->notes) : EMPTY_DIVE_STRING; + QString tmp = m_dive->notes ? QString::fromUtf8(m_dive->notes) : QString(); if (same_string(m_dive->dc.model, "planned dive")) { QTextDocument notes; #define _NOTES_BR "\n" @@ -238,7 +229,7 @@ QString DiveObjectHelper::weightList() const QString weights; for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) { QString w = getFormattedWeight(m_dive, i); - if (w == EMPTY_DIVE_STRING) + if (w.isEmpty()) continue; weights += w + "; "; } @@ -250,7 +241,7 @@ QStringList DiveObjectHelper::weights() const QStringList weights; for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) { QString w = getFormattedWeight(m_dive, i); - if (w == EMPTY_DIVE_STRING) + if (w.isEmpty()) continue; weights << w; } @@ -265,13 +256,13 @@ bool DiveObjectHelper::singleWeight() const QString DiveObjectHelper::weight(int idx) const { if ( (idx < 0) || idx > MAX_WEIGHTSYSTEMS ) - return QString(EMPTY_DIVE_STRING); + return QString(); return getFormattedWeight(m_dive, idx); } QString DiveObjectHelper::suit() const { - return m_dive->suit ? m_dive->suit : EMPTY_DIVE_STRING; + return m_dive->suit ? m_dive->suit : QString(); } QStringList DiveObjectHelper::cylinderList() const @@ -282,7 +273,7 @@ QStringList DiveObjectHelper::cylinderList() const for_each_dive (i, d) { for (int j = 0; j < MAX_CYLINDERS; j++) { QString cyl = d->cylinder[j].type.description; - if (cyl == EMPTY_DIVE_STRING) + if (cyl.isEmpty()) continue; cylinders << cyl; } @@ -290,7 +281,7 @@ QStringList DiveObjectHelper::cylinderList() const for (unsigned long ti = 0; ti < MAX_TANK_INFO && tank_info[ti].name != NULL; ti++) { QString cyl = tank_info[ti].name; - if (cyl == EMPTY_DIVE_STRING) + if (cyl.isEmpty()) continue; cylinders << cyl; } @@ -305,7 +296,7 @@ QStringList DiveObjectHelper::cylinders() const QStringList cylinders; for (int i = 0; i < MAX_CYLINDERS; i++) { QString cyl = getFormattedCylinder(m_dive, i); - if (cyl == EMPTY_DIVE_STRING) + if (cyl.isEmpty()) continue; cylinders << cyl; } @@ -315,7 +306,7 @@ QStringList DiveObjectHelper::cylinders() const QString DiveObjectHelper::cylinder(int idx) const { if ( (idx < 0) || idx > MAX_CYLINDERS) - return QString(EMPTY_DIVE_STRING); + return QString(); return getFormattedCylinder(m_dive, idx); } @@ -412,6 +403,6 @@ QString DiveObjectHelper::fullText() const QString DiveObjectHelper::fullTextNoNotes() const { - QString tripLocation = m_dive->divetrip ? m_dive->divetrip->location : EMPTY_DIVE_STRING; + QString tripLocation = m_dive->divetrip ? m_dive->divetrip->location : QString(); return tripLocation + ":-:" + location() + ":-:" + buddy() + ":-:" + divemaster() + ":-:" + suit() + ":-:" + tags(); } diff --git a/desktop-widgets/CMakeLists.txt b/desktop-widgets/CMakeLists.txt index 519061ac0..629c0507e 100644 --- a/desktop-widgets/CMakeLists.txt +++ b/desktop-widgets/CMakeLists.txt @@ -33,7 +33,7 @@ set (SUBSURFACE_UI diveplanner.ui diveshareexportdialog.ui downloadfromdivecomputer.ui - filterwidget.ui + filterwidget2.ui findmovedimagesdialog.ui listfilter.ui locationInformation.ui @@ -90,6 +90,7 @@ set(SUBSURFACE_INTERFACE command_divelist.cpp locationinformation.cpp qtwaitingspinner.cpp + filterwidget2.cpp tab-widgets/TabDiveStatistics.cpp tab-widgets/TabDiveInformation.cpp tab-widgets/TabDivePhotos.cpp diff --git a/desktop-widgets/command_divelist.cpp b/desktop-widgets/command_divelist.cpp index 2c474cf94..b85b9a65f 100644 --- a/desktop-widgets/command_divelist.cpp +++ b/desktop-widgets/command_divelist.cpp @@ -92,6 +92,7 @@ dive *DiveListBase::addDive(DiveToAdd &d) res->hidden_by_filter = !show; add_single_dive(d.idx, res); // Return ownership to backend + invalidate_dive_cache(res); // Ensure that dive is written in git_save() // If the dive to be removed is selected, we will inform the frontend // later via a signal that the dive changed. @@ -109,11 +110,6 @@ std::vector<DiveToAdd> DiveListBase::removeDives(std::vector<dive *> &divesToDel std::vector<DiveToAdd> res; res.reserve(divesToDelete.size()); - // First, tell the filters that dives are removed. This could - // be done later using the emitted signals, but we do this here - // for symmetry with addDives() - MultiFilterSortModel::instance()->divesDeleted(QVector<dive *>::fromStdVector(divesToDelete)); - for (dive *d: divesToDelete) res.push_back(removeDive(d)); divesToDelete.clear(); @@ -153,7 +149,6 @@ std::vector<dive *> DiveListBase::addDives(std::vector<DiveToAdd> &divesToAdd) QVector<dive *> divesForFilter; for (const DiveToAdd &entry: divesToAdd) divesForFilter.push_back(entry.dive.get()); - MultiFilterSortModel::instance()->divesAdded(divesForFilter); // At the end of the function, to send the proper dives-added signals, // we the the list of added trips. Create this list now. @@ -191,7 +186,7 @@ std::vector<dive *> DiveListBase::addDives(std::vector<DiveToAdd> &divesToAdd) // This helper function renumbers dives according to an array of id/number pairs. // The old numbers are stored in the array, thus calling this function twice has no effect. -// TODO: switch from uniq-id to indexes once all divelist-actions are controlled by undo-able commands +// TODO: switch from uniq-id to indices once all divelist-actions are controlled by undo-able commands static void renumberDives(QVector<QPair<dive *, int>> &divesToRenumber) { for (auto &pair: divesToRenumber) { @@ -199,6 +194,7 @@ static void renumberDives(QVector<QPair<dive *, int>> &divesToRenumber) if (!d) continue; std::swap(d->number, pair.second); + invalidate_dive_cache(d); } // Emit changed signals per trip. @@ -239,6 +235,7 @@ static OwningTripPtr moveDiveToTrip(DiveToTrip &diveToTrip) // Store old trip and get new trip we should associate this dive with std::swap(trip, diveToTrip.trip); add_dive_to_trip(diveToTrip.dive, trip); + invalidate_dive_cache(diveToTrip.dive); // Ensure that dive is written in git_save() return res; } @@ -302,9 +299,12 @@ static void moveDivesBetweenTrips(DivesToTrip &dives) for (size_t k = i; k < j; ++k) divesInTrip[k - i] = divesMoved[k].d; - // Check if the from-trip was deleted: If yes, it was recorded in the tripsToAdd structure + // Check if the from-trip was deleted: If yes, it was recorded in the tripsToAdd structure. + // Only set the flag if this is that last time this trip is featured. bool deleteFrom = from && - std::find_if(dives.tripsToAdd.begin(), dives.tripsToAdd.end(), + std::find_if(divesMoved.begin() + j, divesMoved.end(), // Is this the last occurence of "from"? + [from](const DiveMoved &entry) { return entry.from == from; }) == divesMoved.end() && + std::find_if(dives.tripsToAdd.begin(), dives.tripsToAdd.end(), // Is "from" in tripsToAdd? [from](const OwningTripPtr &trip) { return trip.get() == from; }) != dives.tripsToAdd.end(); // Check if the to-trip has to be created. For this purpose, we saved an array of trips to be created. bool createTo = false; @@ -600,7 +600,7 @@ ShiftTime::ShiftTime(const QVector<dive *> &changedDives, int amount) void ShiftTime::redoit() { for (dive *d: diveList) - d->when -= timeChanged; + d->when += timeChanged; // Changing times may have unsorted the dive table sort_table(&dive_table); @@ -737,7 +737,7 @@ MergeTrips::MergeTrips(dive_trip *trip1, dive_trip *trip2) { if (trip1 == trip2) return; - dive_trip *newTrip = combine_trips_create(trip1, trip2); + dive_trip *newTrip = combine_trips(trip1, trip2); divesToMove.tripsToAdd.emplace_back(newTrip); for (int i = 0; i < trip1->dives.nr; ++i) divesToMove.divesToMove.push_back( { trip1->dives.dives[i], newTrip } ); @@ -752,8 +752,8 @@ SplitDives::SplitDives(dive *d, duration_t time) // Split the dive dive *new1, *new2; int idx = time.seconds < 0 ? - split_dive_dont_insert(d, &new1, &new2) : - split_dive_at_time_dont_insert(d, time, &new1, &new2); + split_dive(d, &new1, &new2) : + split_dive_at_time(d, time, &new1, &new2); // If this didn't work, simply return. Empty arrays indicate that nothing is to be done. if (idx < 0) diff --git a/desktop-widgets/divelistview.cpp b/desktop-widgets/divelistview.cpp index 9167bea5e..c340b556b 100644 --- a/desktop-widgets/divelistview.cpp +++ b/desktop-widgets/divelistview.cpp @@ -25,6 +25,7 @@ #include "qt-models/divepicturemodel.h" #include "core/metrics.h" #include "core/subsurface-qt/DiveListNotifier.h" +#include "desktop-widgets/simplewidgets.h" DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelection(false), currentLayout(DiveTripModel::TREE), dontEmitDiveChangedSignal(false), selectionSaved(false), @@ -969,15 +970,8 @@ void DiveListView::matchImagesToDives(QStringList fileNames) return; updateLastImageTimeOffset(shiftDialog.amount()); - Q_FOREACH (const QString &fileName, fileNames) { - int j = 0; - struct dive *dive; - for_each_dive (j, dive) { - if (!dive->selected) - continue; - dive_create_picture(dive, copy_qstring(fileName), shiftDialog.amount(), shiftDialog.matchAll()); - } - } + for (const QString &fileName: fileNames) + create_picture(qPrintable(fileName), shiftDialog.amount(), shiftDialog.matchAll()); mark_divelist_changed(true); copy_dive(current_dive, &displayed_dive); diff --git a/desktop-widgets/divelogimportdialog.cpp b/desktop-widgets/divelogimportdialog.cpp index 668b230a8..f4f9311ff 100644 --- a/desktop-widgets/divelogimportdialog.cpp +++ b/desktop-widgets/divelogimportdialog.cpp @@ -9,6 +9,7 @@ #include <QMimeData> #include <QRegExp> #include <QUndoStack> +#include <QPainter> #include "core/qthelper.h" #include "core/import-csv.h" diff --git a/desktop-widgets/downloadfromdivecomputer.cpp b/desktop-widgets/downloadfromdivecomputer.cpp index 3c70d8686..f92fdbb70 100644 --- a/desktop-widgets/downloadfromdivecomputer.cpp +++ b/desktop-widgets/downloadfromdivecomputer.cpp @@ -29,14 +29,12 @@ DownloadFromDCWidget::DownloadFromDCWidget(QWidget *parent, Qt::WindowFlags f) : currentState(INITIAL) { diveImportedModel = new DiveImportedModel(this); - diveImportedModel->setDiveTable(&downloadTable); vendorModel.setStringList(vendorList); QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), this); QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this); int startingWidth = defaultModelFont().pointSize(); - clear_table(&downloadTable); ui.setupUi(this); ui.progressBar->hide(); ui.progressBar->setMinimum(0); @@ -254,7 +252,7 @@ void DownloadFromDCWidget::updateState(states state) markChildrenAsEnabled(); progress_bar_text = ""; } else { - if (downloadTable.nr != 0) + if (thread.table()->nr != 0) progress_bar_text = ""; ui.progressBar->setValue(100); markChildrenAsEnabled(); @@ -349,7 +347,7 @@ void DownloadFromDCWidget::on_downloadCancelRetryButton_clicked() // this means we are retrying - so we better clean out the partial // list of downloaded dives from the last attempt diveImportedModel->clearTable(); - clear_table(&downloadTable); + clear_table(thread.table()); } updateState(DOWNLOADING); @@ -492,10 +490,7 @@ void DownloadFromDCWidget::onDownloadThreadFinished() } ui.downloadCancelRetryButton->setText(tr("Retry download")); ui.downloadCancelRetryButton->setEnabled(true); - // regardless, if we got dives, we should show them to the user - if (downloadTable.nr) { - diveImportedModel->setImportedDivesIndexes(0, downloadTable.nr - 1); - } + diveImportedModel->repopulate(thread.table()); } void DownloadFromDCWidget::on_cancel_clicked() @@ -504,7 +499,7 @@ void DownloadFromDCWidget::on_cancel_clicked() return; // now discard all the dives - clear_table(&downloadTable); + clear_table(thread.table()); done(-1); } @@ -512,23 +507,24 @@ void DownloadFromDCWidget::on_ok_clicked() { if (currentState != DONE && currentState != ERROR) return; + struct dive_table *table = thread.table(); // delete non-selected dives - int total = downloadTable.nr; + int total = table->nr; int j = 0; for (int i = 0; i < total; i++) { if (diveImportedModel->data(diveImportedModel->index(i, 0), Qt::CheckStateRole) == Qt::Checked) j++; else - delete_dive_from_table(&downloadTable, j); + delete_dive_from_table(thread.table(), j); } - if (downloadTable.nr > 0) { + if (table->nr > 0) { MainWindow::instance()->diveList->unselectDives(); // remember the last downloaded dive (on most dive computers this will be the chronologically // first new dive) and select it again after processing all the dives - int uniqId = downloadTable.dives[downloadTable.nr - 1]->id; - process_imported_dives(&downloadTable, preferDownloaded(), true); + int uniqId = table->dives[table->nr - 1]->id; + process_imported_dives(table, preferDownloaded(), true); autogroup_dives(); Command::clear(); // after process_imported_dives does any merging or resorting needed, we need diff --git a/desktop-widgets/filterwidget.ui b/desktop-widgets/filterwidget.ui deleted file mode 100644 index 7f548a931..000000000 --- a/desktop-widgets/filterwidget.ui +++ /dev/null @@ -1,140 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>FilterWidget2</class> - <widget class="QWidget" name="FilterWidget2"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>594</width> - <height>362</height> - </rect> - </property> - <property name="windowTitle"> - <string></string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="spacing"> - <number>0</number> - </property> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLabel" name="filterText"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QToolButton" name="clear"> - <property name="toolTip"> - <string>Reset filters</string> - </property> - <property name="icon"> - <iconset resource="../subsurface.qrc"> - <normaloff>:edit-clear-icon</normaloff>:edit-clear-icon</iconset> - </property> - <property name="autoRaise"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="maximize"> - <property name="toolTip"> - <string>Show/hide filters</string> - </property> - <property name="icon"> - <iconset resource="../subsurface.qrc"> - <normaloff>:hide-icon</normaloff>:hide-icon</iconset> - </property> - <property name="autoRaise"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="close"> - <property name="toolTip"> - <string>Close and reset filters</string> - </property> - <property name="icon"> - <iconset resource="../subsurface.qrc"> - <normaloff>:filter-close</normaloff>:filter-close</iconset> - </property> - <property name="autoRaise"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QScrollArea" name="scrollArea"> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> - </property> - <property name="widgetResizable"> - <bool>true</bool> - </property> - <widget class="QWidget" name="scrollAreaWidgetContents"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>594</width> - <height>337</height> - </rect> - </property> - </widget> - </widget> - </item> - </layout> - </widget> - <resources> - <include location="../subsurface.qrc"/> - </resources> - <connections/> -</ui> diff --git a/desktop-widgets/filterwidget2.cpp b/desktop-widgets/filterwidget2.cpp new file mode 100644 index 000000000..d120ffe4d --- /dev/null +++ b/desktop-widgets/filterwidget2.cpp @@ -0,0 +1,105 @@ +#include "desktop-widgets/filterwidget2.h" +#include "desktop-widgets/simplewidgets.h" + +#include <QDoubleSpinBox> + +FilterWidget2::FilterWidget2(QWidget* parent) +: QWidget(parent) +, ui(new Ui::FilterWidget2()) +{ + ui->setupUi(this); + + FilterData data; + ui->minRating->setCurrentStars(data.minRating); + ui->maxRating->setCurrentStars(data.maxRating); + ui->minVisibility->setCurrentStars(data.minVisibility); + ui->maxVisibility->setCurrentStars(data.maxVisibility); + ui->minAirTemp->setValue(data.minAirTemp); + ui->maxAirTemp->setValue(data.maxAirTemp); + ui->minWaterTemp->setValue(data.minWaterTemp); + ui->maxWaterTemp->setValue(data.maxWaterTemp); + + // TODO: unhide this when we discover how to search for equipment. + ui->equipment->hide(); + ui->labelEquipment->hide(); + ui->invertFilter->hide(); + + ui->to->setDate(data.to.date()); + + connect(ui->maxRating, &StarWidget::valueChanged, + this, &FilterWidget2::updateFilter); + + connect(ui->minRating, &StarWidget::valueChanged, + this, &FilterWidget2::updateFilter); + + connect(ui->maxVisibility, &StarWidget::valueChanged, + this, &FilterWidget2::updateFilter); + + connect(ui->minVisibility, &StarWidget::valueChanged, + this, &FilterWidget2::updateFilter); + + connect(ui->maxAirTemp, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), + this, &FilterWidget2::updateFilter); + + connect(ui->minAirTemp, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), + this, &FilterWidget2::updateFilter); + + connect(ui->maxWaterTemp, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), + this, &FilterWidget2::updateFilter); + + connect(ui->minWaterTemp, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), + this, &FilterWidget2::updateFilter); + + connect(ui->from, &QDateTimeEdit::dateTimeChanged, + this, &FilterWidget2::updateFilter); + + connect(ui->to, &QDateTimeEdit::dateTimeChanged, + this, &FilterWidget2::updateFilter); + + connect(ui->tags, &QLineEdit::textChanged, + this, &FilterWidget2::updateFilter); + + connect(ui->people, &QLineEdit::textChanged, + this, &FilterWidget2::updateFilter); + + connect(ui->location, &QLineEdit::textChanged, + this, &FilterWidget2::updateFilter); +} + +void FilterWidget2::updateFilter() +{ + FilterData data; + + data.validFilter = true; + data.minVisibility = ui->minVisibility->currentStars(); + data.maxVisibility = ui->maxVisibility->currentStars(); + data.minRating = ui->minRating->currentStars(); + data.maxRating = ui->maxRating->currentStars(); + data.minWaterTemp = ui->minWaterTemp->value(); + data.maxWaterTemp = ui->maxWaterTemp->value(); + data.minAirTemp = ui->minAirTemp->value(); + data.maxWaterTemp = ui->maxWaterTemp->value(); + data.from = ui->from->dateTime(); + data.to = ui->to->dateTime(); + data.tags = ui->tags->text().split(",", QString::SkipEmptyParts); + data.people = ui->people->text().split(",", QString::SkipEmptyParts); + data.location = ui->location->text().split(",", QString::SkipEmptyParts); + data.equipment = ui->equipment->text().split(",", QString::SkipEmptyParts); + data.invertFilter = ui->invertFilter->isChecked(); + + filterData = data; + emit filterDataChanged(data); +} + +void FilterWidget2::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + emit filterDataChanged(filterData); +} + +void FilterWidget2::hideEvent(QHideEvent *event) +{ + QWidget::hideEvent(event); + FilterData data; + emit filterDataChanged(data); +} diff --git a/desktop-widgets/filterwidget2.h b/desktop-widgets/filterwidget2.h new file mode 100644 index 000000000..80629f0cc --- /dev/null +++ b/desktop-widgets/filterwidget2.h @@ -0,0 +1,35 @@ +#ifndef FILTERWIDGET_2_H +#define FILTERWIDGET_2_H + +#include <QWidget> +#include <QHideEvent> +#include <QShowEvent> + +#include <memory> + +#include "ui_filterwidget2.h" +#include "qt-models/filtermodels.h" + +namespace Ui { + class FilterWidget2; +} + +class FilterWidget2 : public QWidget { + Q_OBJECT + +public: + explicit FilterWidget2(QWidget *parent = 0); + void updateFilter(); +protected: + void hideEvent(QHideEvent *event) override; + void showEvent(QShowEvent *event) override; + +signals: + void filterDataChanged(const FilterData& data); + +private: + std::unique_ptr<Ui::FilterWidget2> ui; + FilterData filterData; +}; + +#endif diff --git a/desktop-widgets/filterwidget2.ui b/desktop-widgets/filterwidget2.ui new file mode 100644 index 000000000..978dafc6e --- /dev/null +++ b/desktop-widgets/filterwidget2.ui @@ -0,0 +1,260 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>FilterWidget2</class> + <widget class="QWidget" name="FilterWidget2"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>510</width> + <height>320</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="3" column="1"> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string>Min</string> + </property> + </widget> + </item> + <item row="2" column="4"> + <widget class="StarWidget" name="maxVisibility" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QDoubleSpinBox" name="minWaterTemp"/> + </item> + <item row="2" column="2"> + <widget class="StarWidget" name="minVisibility" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Tags</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string> Rating</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="StarWidget" name="minRating" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>People</string> + </property> + </widget> + </item> + <item row="3" column="3"> + <widget class="QLabel" name="label_13"> + <property name="text"> + <string>Max</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Min</string> + </property> + </widget> + </item> + <item row="7" column="1" colspan="4"> + <widget class="QLineEdit" name="tags"/> + </item> + <item row="11" column="1" colspan="4"> + <widget class="QCheckBox" name="invertFilter"> + <property name="toolTip"> + <string>Display dives that will not match the search, only applies to tags, people, location and equipment</string> + </property> + <property name="text"> + <string>Invert filter</string> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>Max</string> + </property> + </widget> + </item> + <item row="5" column="1" colspan="4"> + <widget class="QDateTimeEdit" name="from"/> + </item> + <item row="8" column="1" colspan="4"> + <widget class="QLineEdit" name="people"/> + </item> + <item row="1" column="4"> + <widget class="StarWidget" name="maxRating" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item row="3" column="4"> + <widget class="QDoubleSpinBox" name="maxWaterTemp"/> + </item> + <item row="10" column="0"> + <widget class="QLabel" name="labelEquipment"> + <property name="text"> + <string>Equipment</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Max</string> + </property> + </widget> + </item> + <item row="9" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Location</string> + </property> + </widget> + </item> + <item row="9" column="1" colspan="4"> + <widget class="QLineEdit" name="location"/> + </item> + <item row="6" column="1" colspan="4"> + <widget class="QDateTimeEdit" name="to"/> + </item> + <item row="10" column="1" colspan="4"> + <widget class="QLineEdit" name="equipment"/> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>From</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>To</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Visibility</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Water Temp</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string>Min</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Air Temp</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string>Min</string> + </property> + </widget> + </item> + <item row="4" column="3"> + <widget class="QLabel" name="label_18"> + <property name="text"> + <string>Max</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QDoubleSpinBox" name="minAirTemp"/> + </item> + <item row="4" column="4"> + <widget class="QDoubleSpinBox" name="maxAirTemp"/> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>StarWidget</class> + <extends>QWidget</extends> + <header location="global">desktop-widgets/starwidget.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>minRating</tabstop> + <tabstop>maxRating</tabstop> + <tabstop>minVisibility</tabstop> + <tabstop>maxVisibility</tabstop> + <tabstop>from</tabstop> + <tabstop>to</tabstop> + <tabstop>tags</tabstop> + <tabstop>people</tabstop> + <tabstop>location</tabstop> + <tabstop>equipment</tabstop> + <tabstop>invertFilter</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/desktop-widgets/listfilter.ui b/desktop-widgets/listfilter.ui index 06f1889a8..91d430617 100644 --- a/desktop-widgets/listfilter.ui +++ b/desktop-widgets/listfilter.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>FilterWidget</class> - <widget class="QWidget" name="FilterWidget"> + <class>ListFilter</class> + <widget class="QWidget" name="ListFilter"> <property name="geometry"> <rect> <x>0</x> diff --git a/desktop-widgets/locationinformation.cpp b/desktop-widgets/locationinformation.cpp index 2d0807474..7356f8370 100644 --- a/desktop-widgets/locationinformation.cpp +++ b/desktop-widgets/locationinformation.cpp @@ -34,8 +34,6 @@ LocationInformationWidget::LocationInformationWidget(QWidget *parent) : QGroupBo ui.diveSiteMessage->addAction(rejectAction); connect(ui.geoCodeButton, SIGNAL(clicked()), this, SLOT(reverseGeocode())); - connect(this, SIGNAL(nameChanged(const QString &, const QString &)), - LocationFilterModel::instance(), SLOT(changeName(const QString &, const QString &))); connect(ui.updateLocationButton, SIGNAL(clicked()), this, SLOT(updateLocationOnMap())); connect(ui.diveSiteCoordinates, SIGNAL(returnPressed()), this, SLOT(updateLocationOnMap())); ui.diveSiteCoordinates->installEventFilter(this); diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 99942ec56..6c0b42c9d 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -53,6 +53,8 @@ #include "desktop-widgets/tab-widgets/maintab.h" #include "desktop-widgets/updatemanager.h" #include "desktop-widgets/usersurvey.h" +#include "desktop-widgets/filterwidget2.h" +#include "desktop-widgets/simplewidgets.h" #include "profile-widget/profilewidget2.h" @@ -141,10 +143,10 @@ MainWindow::MainWindow() : QMainWindow(), diveList = new DiveListView(this); graphics = new ProfileWidget2(this); MapWidget *mapWidget = MapWidget::instance(); - divePlannerSettingsWidget = new PlannerSettingsWidget(this); divePlannerWidget = new DivePlannerWidget(this); plannerDetails = new PlannerDetails(this); + auto *filterWidget2 = new FilterWidget2(); // what is a sane order for those icons? we should have the ones the user is // most likely to want towards the top so they are always visible @@ -193,6 +195,7 @@ MainWindow::MainWindow() : QMainWindow(), registerApplicationState("PlanDive", divePlannerWidget, profileContainer, divePlannerSettingsWidget, plannerDetails ); registerApplicationState("EditPlannedDive", divePlannerWidget, profileContainer, diveList, mapWidget ); registerApplicationState("EditDiveSite", diveSiteEdit, profileContainer, diveList, mapWidget); + registerApplicationState("FilterDive", mainTab, profileContainer, diveList, filterWidget2); setStateProperties("Default", enabledList, enabledList, enabledList,enabledList); setStateProperties("AddDive", enabledList, enabledList, enabledList,enabledList); @@ -200,11 +203,9 @@ MainWindow::MainWindow() : QMainWindow(), setStateProperties("PlanDive", enabledList, enabledList, enabledList,enabledList); setStateProperties("EditPlannedDive", enabledList, enabledList, enabledList,enabledList); setStateProperties("EditDiveSite", enabledList, disabledList, disabledList, enabledList); - + setStateProperties("FilterDive", enabledList, enabledList, enabledList, enabledList); setApplicationState("Default"); - ui.multiFilter->hide(); - setWindowIcon(QIcon(":subsurface-icon")); if (!QIcon::hasThemeIcon("window-close")) { QIcon::setThemeName("subsurface"); @@ -495,10 +496,6 @@ void MainWindow::refreshDisplay(bool doRecreateDiveList) void MainWindow::recreateDiveList() { diveList->reload(); - TagFilterModel::instance()->repopulate(); - BuddyFilterModel::instance()->repopulate(); - LocationFilterModel::instance()->repopulate(); - SuitsFilterModel::instance()->repopulate(); MultiFilterSortModel::instance()->myInvalidate(); } @@ -759,9 +756,8 @@ void MainWindow::on_actionClose_triggered() { if (okToClose(tr("Please save or cancel the current dive edit before closing the file."))) { closeCurrentFile(); - // hide any pictures and the filter DivePictureModel::instance()->updateDivePictures(); - ui.multiFilter->closeFilter(); + setApplicationState("Default"); recreateDiveList(); } } @@ -1874,13 +1870,7 @@ void MainWindow::on_paste_triggered() void MainWindow::on_actionFilterTags_triggered() { - if (ui.multiFilter->isVisible()) { - ui.multiFilter->closeFilter(); - ui.actionFilterTags->setChecked(false); - } else { - ui.multiFilter->setVisible(true); - ui.actionFilterTags->setChecked(true); - } + setApplicationState(getCurrentAppState() == "FilterDive" ? "Default" : "FilterDive"); } void MainWindow::setCheckedActionFilterTags(bool checked) diff --git a/desktop-widgets/mainwindow.h b/desktop-widgets/mainwindow.h index be35ecf1e..a18738de4 100644 --- a/desktop-widgets/mainwindow.h +++ b/desktop-widgets/mainwindow.h @@ -18,6 +18,7 @@ #include "ui_plannerDetails.h" #include "desktop-widgets/notificationwidget.h" #include "core/gpslocation.h" +#include "core/dive.h" #define NUM_RECENT_FILES 4 @@ -35,6 +36,7 @@ class ProfileWidget2; class PlannerDetails; class PlannerSettingsWidget; class LocationInformationWidget; +class FilterWidget2; typedef std::pair<QByteArray, QVariant> WidgetProperty; typedef QVector<WidgetProperty> PropertyList; diff --git a/desktop-widgets/mainwindow.ui b/desktop-widgets/mainwindow.ui index 1263b4093..2dcd3e8a6 100644 --- a/desktop-widgets/mainwindow.ui +++ b/desktop-widgets/mainwindow.ui @@ -15,12 +15,18 @@ <property name="spacing"> <number>0</number> </property> - <property name="margin"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> <number>0</number> </property> - <item> - <widget class="MultiFilter" name="multiFilter" native="true"/> - </item> <item> <widget class="QSplitter" name="mainSplitter"> <property name="orientation"> @@ -53,7 +59,7 @@ <x>0</x> <y>0</y> <width>861</width> - <height>23</height> + <height>29</height> </rect> </property> <widget class="QMenu" name="menuFile"> @@ -133,7 +139,7 @@ </widget> <widget class="QMenu" name="menuShare_on"> <property name="title"> - <string>Share on</string> + <string>Share o&n</string> </property> <addaction name="separator"/> </widget> @@ -438,7 +444,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:pp-o2-icon</normaloff>:pp-o2-icon</iconset> </property> <property name="text"> @@ -450,7 +456,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:pp-n2-icon</normaloff>:pp-n2-icon</iconset> </property> <property name="text"> @@ -462,7 +468,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:pp-he-icon</normaloff>:pp-he-icon</iconset> </property> <property name="text"> @@ -474,7 +480,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:ceiling-dc-icon</normaloff>:ceiling-dc-icon</iconset> </property> <property name="text"> @@ -486,7 +492,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:ceiling-calculated-icon</normaloff>:ceiling-calculated-icon</iconset> </property> <property name="text"> @@ -498,7 +504,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:ceiling-tissues-icon</normaloff>:ceiling-tissues-icon</iconset> </property> <property name="text"> @@ -510,7 +516,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:ceiling-increments-icon</normaloff>:ceiling-increments-icon</iconset> </property> <property name="text"> @@ -522,7 +528,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:rate-heart-icon</normaloff>:rate-heart-icon</iconset> </property> <property name="text"> @@ -534,7 +540,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:depth-mod-icon</normaloff>:depth-mod-icon</iconset> </property> <property name="text"> @@ -546,7 +552,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:depth-ead-icon</normaloff>:depth-ead-icon</iconset> </property> <property name="text"> @@ -558,7 +564,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:depth-ndl-icon</normaloff>:depth-ndl-icon</iconset> </property> <property name="text"> @@ -570,7 +576,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:rate-sac-icon</normaloff>:rate-sac-icon</iconset> </property> <property name="text"> @@ -582,7 +588,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:ruler-icon</normaloff>:ruler-icon</iconset> </property> <property name="text"> @@ -594,7 +600,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:scale-graph-icon</normaloff>:scale-graph-icon</iconset> </property> <property name="text"> @@ -606,7 +612,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:photo-icon</normaloff>:photo-icon</iconset> </property> <property name="text"> @@ -618,7 +624,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:gaschange-icon</normaloff>:gaschange-icon</iconset> </property> <property name="text"> @@ -641,7 +647,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../subsurface.qrc"> + <iconset> <normaloff>:heatmap-icon</normaloff>:heatmap-icon</iconset> </property> <property name="text"> @@ -704,7 +710,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Cloud storage online</string> + <string>Cloud stora&ge online</string> </property> </action> </widget> @@ -715,12 +721,6 @@ <header>desktop-widgets/notificationwidget.h</header> <container>1</container> </customwidget> - <customwidget> - <class>MultiFilter</class> - <extends>QWidget</extends> - <header>desktop-widgets/simplewidgets.h</header> - <container>1</container> - </customwidget> </customwidgets> <resources> <include location="../subsurface.qrc"/> diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index f008f4bc2..41fe1c083 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 + #include "desktop-widgets/modeldelegates.h" #include "core/subsurface-string.h" #include "core/gettextfromc.h" @@ -13,6 +14,7 @@ #include "qt-models/divetripmodel.h" #include "qt-models/divelocationmodel.h" #include "core/qthelper.h" +#include "desktop-widgets/simplewidgets.h" #include <QCompleter> #include <QKeyEvent> @@ -22,6 +24,9 @@ #include <QBrush> #include <QColor> #include <QAbstractProxyModel> +#include <QLineEdit> +#include <QAbstractItemView> +#include <QSpinBox> QSize DiveListDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const { diff --git a/desktop-widgets/printdialog.cpp b/desktop-widgets/printdialog.cpp index bbbade2e4..4c863fbea 100644 --- a/desktop-widgets/printdialog.cpp +++ b/desktop-widgets/printdialog.cpp @@ -10,6 +10,7 @@ #include <QShortcut> #include <QSettings> #include <QMessageBox> +#include <QDialogButtonBox> #define SETTINGS_GROUP "PrintDialog" diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index 42747cd3e..42569a270 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -200,7 +200,7 @@ void SetpointDialog::setpointData(struct divecomputer *divecomputer, int second) void SetpointDialog::buttonClicked(QAbstractButton *button) { if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole && dc) { - add_event(dc, time, SAMPLE_EVENT_PO2, 0, (int)(1000.0 * ui.spinbox->value()), + add_event(dc, time, SAMPLE_EVENT_PO2, 0, (int)(1000.0 * ui.spinbox->value()), QT_TRANSLATE_NOOP("gettextFromC", "SP change")); invalidate_dive_cache(current_dive); } @@ -493,117 +493,6 @@ void DiveComponentSelection::buttonClicked(QAbstractButton *button) } } -void FilterBase::addContextMenuEntry(const QString &s, void (FilterModelBase::*fn)()) -{ - QAction *act = new QAction(s, this); - connect(act, &QAction::triggered, model, fn); - ui.filterList->addAction(act); -} - -FilterBase::FilterBase(FilterModelBase *model_, QWidget *parent) : QWidget(parent), - model(model_) -{ - ui.setupUi(this); -#if QT_VERSION >= 0x050200 - ui.filterInternalList->setClearButtonEnabled(true); -#endif - QSortFilterProxyModel *filter = new QSortFilterProxyModel(); - filter->setSourceModel(model); - filter->setFilterCaseSensitivity(Qt::CaseInsensitive); - connect(ui.filterInternalList, SIGNAL(textChanged(QString)), filter, SLOT(setFilterFixedString(QString))); - connect(ui.notButton, &QToolButton::toggled, model, &FilterModelBase::setNegate); - ui.filterList->setModel(filter); - - addContextMenuEntry(tr("Select All"), &FilterModelBase::selectAll); - addContextMenuEntry(tr("Unselect All"), &FilterModelBase::clearFilter); - addContextMenuEntry(tr("Invert Selection"), &FilterModelBase::invertSelection); - ui.filterList->setContextMenuPolicy(Qt::ActionsContextMenu); -} - -void FilterBase::showEvent(QShowEvent *event) -{ - MultiFilterSortModel::instance()->addFilterModel(model); - QWidget::showEvent(event); -} - -void FilterBase::hideEvent(QHideEvent *event) -{ - MultiFilterSortModel::instance()->removeFilterModel(model); - QWidget::hideEvent(event); -} - -TagFilter::TagFilter(QWidget *parent) : FilterBase(TagFilterModel::instance(), parent) -{ - ui.label->setText(tr("Tags") + QStringLiteral(": ")); -} - -BuddyFilter::BuddyFilter(QWidget *parent) : FilterBase(BuddyFilterModel::instance(), parent) -{ - ui.label->setText(tr("Person") + QStringLiteral(": ")); - ui.label->setToolTip(tr("Searches for buddies and divemasters")); -} - -LocationFilter::LocationFilter(QWidget *parent) : FilterBase(LocationFilterModel::instance(), parent) -{ - ui.label->setText(tr("Location") + QStringLiteral(": ")); -} - -SuitFilter::SuitFilter(QWidget *parent) : FilterBase(SuitsFilterModel::instance(), parent) -{ - ui.label->setText(tr("Suits") + QStringLiteral(": ")); -} - -MultiFilter::MultiFilter(QWidget *parent) : QWidget(parent) -{ - ui.setupUi(this); - - QWidget *expandedWidget = new QWidget(); - QHBoxLayout *l = new QHBoxLayout(); - - TagFilter *tagFilter = new TagFilter(this); - int minimumHeight = tagFilter->ui.filterInternalList->height() + - tagFilter->ui.verticalLayout->spacing() * tagFilter->ui.verticalLayout->count(); - - QListView *dummyList = new QListView(); - QStringListModel *dummy = new QStringListModel(QStringList() << "Dummy Text"); - dummyList->setModel(dummy); - - connect(ui.close, SIGNAL(clicked(bool)), this, SLOT(closeFilter())); - connect(ui.clear, SIGNAL(clicked(bool)), MultiFilterSortModel::instance(), SLOT(clearFilter())); - connect(ui.maximize, SIGNAL(clicked(bool)), this, SLOT(adjustHeight())); - - l->addWidget(tagFilter); - l->addWidget(new BuddyFilter()); - l->addWidget(new LocationFilter()); - l->addWidget(new SuitFilter()); - l->setContentsMargins(0, 0, 0, 0); - l->setSpacing(0); - expandedWidget->setLayout(l); - - ui.scrollArea->setWidget(expandedWidget); - expandedWidget->resize(expandedWidget->width(), minimumHeight + dummyList->sizeHintForRow(0) * 5); - ui.scrollArea->setMinimumHeight(expandedWidget->height() + 5); - - connect(MultiFilterSortModel::instance(), SIGNAL(filterFinished()), this, SLOT(filterFinished())); -} - -void MultiFilter::filterFinished() -{ - ui.filterText->setText(tr("Filter shows %1 (of %2) dives").arg(MultiFilterSortModel::instance()->divesDisplayed).arg(dive_table.nr)); -} - -void MultiFilter::adjustHeight() -{ - ui.scrollArea->setVisible(!ui.scrollArea->isVisible()); -} - -void MultiFilter::closeFilter() -{ - MultiFilterSortModel::instance()->clearFilter(); - hide(); - MainWindow::instance()->setCheckedActionFilterTags(false); -} - TextHyperlinkEventFilter::TextHyperlinkEventFilter(QTextEdit *txtEdit) : QObject(txtEdit), textEdit(txtEdit), scrollView(textEdit->viewport()) diff --git a/desktop-widgets/simplewidgets.h b/desktop-widgets/simplewidgets.h index 48e5e7e3d..a814519c4 100644 --- a/desktop-widgets/simplewidgets.h +++ b/desktop-widgets/simplewidgets.h @@ -20,7 +20,6 @@ class FilterModelBase; #include "ui_urldialog.h" #include "ui_divecomponentselection.h" #include "ui_listfilter.h" -#include "ui_filterwidget.h" #include "core/exif.h" #include "core/dive.h" @@ -150,59 +149,6 @@ private: struct dive_components *what; }; -namespace Ui{ - class FilterWidget2; -}; - -class MultiFilter : public QWidget { - Q_OBJECT -public -slots: - void closeFilter(); - void adjustHeight(); - void filterFinished(); - -public: - MultiFilter(QWidget *parent); - Ui::FilterWidget2 ui; -}; - -class FilterBase : public QWidget { - Q_OBJECT - void addContextMenuEntry(const QString &s, void (FilterModelBase::*)()); -protected: - FilterBase(FilterModelBase *model, QWidget *parent = 0); - FilterModelBase *model; - Ui::FilterWidget ui; - void showEvent(QShowEvent *) override; - void hideEvent(QHideEvent *) override; - friend class MultiFilter; -}; - -class TagFilter : public FilterBase { - Q_OBJECT -public: - TagFilter(QWidget *parent = 0); -}; - -class BuddyFilter : public FilterBase { - Q_OBJECT -public: - BuddyFilter(QWidget *parent = 0); -}; - -class SuitFilter : public FilterBase { - Q_OBJECT -public: - SuitFilter(QWidget *parent = 0); -}; - -class LocationFilter : public FilterBase { - Q_OBJECT -public: - LocationFilter(QWidget *parent = 0); -}; - class TextHyperlinkEventFilter : public QObject { Q_OBJECT public: diff --git a/desktop-widgets/subsurfacewebservices.cpp b/desktop-widgets/subsurfacewebservices.cpp index 2acc7451e..d62cd8552 100644 --- a/desktop-widgets/subsurfacewebservices.cpp +++ b/desktop-widgets/subsurfacewebservices.cpp @@ -484,6 +484,10 @@ void DivelogsDeWebServices::prepareDivesForUpload(bool selected) { /* generate a random filename and create/open that file with zip_open */ QString filename = QDir::tempPath() + "/import-" + QString::number(qrand() % 99999999) + ".dld"; + if (!amount_selected) { + report_error(tr("no dives were selected").toUtf8()); + return; + } if (prepare_dives_for_divelogs(filename, selected)) { QFile f(filename); if (f.open(QIODevice::ReadOnly)) { diff --git a/desktop-widgets/tab-widgets/TabDiveStatistics.cpp b/desktop-widgets/tab-widgets/TabDiveStatistics.cpp index 67e80ed24..1b3fe9522 100644 --- a/desktop-widgets/tab-widgets/TabDiveStatistics.cpp +++ b/desktop-widgets/tab-widgets/TabDiveStatistics.cpp @@ -48,17 +48,13 @@ void TabDiveStatistics::updateData() calculate_stats_selected(&stats_selection); clear(); ui->depthLimits->setMaximum(get_depth_string(stats_selection.max_depth, true)); - if (amount_selected > 1) + if (amount_selected > 1) { ui->depthLimits->setMinimum(get_depth_string(stats_selection.min_depth, true)); - else + ui->depthLimits->setAverage(get_depth_string(stats_selection.combined_max_depth.mm / stats_selection.selection_size, true)); + } else { ui->depthLimits->setMinimum(""); - // the overall average depth is really confusing when listed between the - // deepest and shallowest dive - let's just not set it - // ui->depthLimits->setAverage(get_depth_string(stats_selection.avg_depth, true)); - - // Also hide the avgIco, so its clear that its not there. - ui->depthLimits->overrideAvgToolTipText(""); - ui->depthLimits->setAvgVisibility(false); + ui->depthLimits->setAverage(""); + } if (stats_selection.max_sac.mliter && (stats_selection.max_sac.mliter != stats_selection.avg_sac.mliter)) ui->sacLimits->setMaximum(get_volume_string(stats_selection.max_sac, true).append(tr("/min"))); diff --git a/desktop-widgets/tab-widgets/TabDiveStatistics.ui b/desktop-widgets/tab-widgets/TabDiveStatistics.ui index d954dca3b..1be4bbcfc 100644 --- a/desktop-widgets/tab-widgets/TabDiveStatistics.ui +++ b/desktop-widgets/tab-widgets/TabDiveStatistics.ui @@ -70,7 +70,7 @@ <item row="1" column="0"> <widget class="QGroupBox" name="groupBoxb"> <property name="title"> - <string>Depth</string> + <string>Max. depth</string> </property> <layout class="QHBoxLayout" name="statsDepthLayout"> <item> diff --git a/desktop-widgets/tab-widgets/maintab.cpp b/desktop-widgets/tab-widgets/maintab.cpp index 567e1737f..f22b7cf1e 100644 --- a/desktop-widgets/tab-widgets/maintab.cpp +++ b/desktop-widgets/tab-widgets/maintab.cpp @@ -27,6 +27,7 @@ #include "core/gettextfromc.h" #include "desktop-widgets/locationinformation.h" #include "desktop-widgets/command.h" +#include "desktop-widgets/simplewidgets.h" #include "TabDiveExtraInfo.h" #include "TabDiveInformation.h" @@ -688,7 +689,6 @@ struct dive_site *MainTab::updateDiveSite(struct dive_site *pickedDs, dive *d) QString name = ui.location->text().isEmpty() ? tr("New dive site") : ui.location->text(); pickedDs = create_dive_site(qPrintable(name), displayed_dive.when); createdNewDive = true; - LocationFilterModel::instance()->addName(name); } if (origDs) { diff --git a/libdivecomputer b/libdivecomputer -Subproject abde311d3a6ea97c7a586e4cc879e07d4ce0fd4 +Subproject e4c96e93caf68cf59e601da798e5dbaaf5bad60 diff --git a/mobile-widgets/qml/CopySettings.qml b/mobile-widgets/qml/CopySettings.qml new file mode 100644 index 000000000..172a0173c --- /dev/null +++ b/mobile-widgets/qml/CopySettings.qml @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +import QtQuick 2.6 +import QtQuick.Controls 2.2 as Controls +import QtQuick.Window 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.4 as Kirigami +import org.subsurfacedivelog.mobile 1.0 + +Kirigami.ScrollablePage { + objectName: "CopySettings" + id: settingsCopy + + title: qsTr("Copy Settings") + background: Rectangle { color: subsurfaceTheme.backgroundColor } + + property real gridWidth: settingsCopy.width - Kirigami.Units.gridUnit + + ColumnLayout { + width: gridWidth + + GridLayout { + id: copy_settings + columns: 2 + Controls.Label { + text: qsTr("Selection for copy-paste") + font.pointSize: subsurfaceTheme.headingPointSize + font.weight: Font.Light + color: subsurfaceTheme.textColor + Layout.topMargin: Kirigami.Units.largeSpacing + Layout.bottomMargin: Kirigami.Units.largeSpacing / 2 + Layout.columnSpan: 2 + } + + Controls.Label { + text: qsTr("Dive site") + font.pointSize: subsurfaceTheme.regularPointSize + Layout.preferredWidth: gridWidth * 0.75 + } + SsrfSwitch { + checked: manager.toggleDiveSite(false) + Layout.preferredWidth: gridWidth * 0.25 + onClicked: { + manager.toggleDiveSite(true) + } + } + Controls.Label { + text: qsTr("Notes") + font.pointSize: subsurfaceTheme.regularPointSize + Layout.preferredWidth: gridWidth * 0.75 + } + SsrfSwitch { + checked: manager.toggleNotes(false) + Layout.preferredWidth: gridWidth * 0.25 + onClicked: { + manager.toggleNotes(true) + } + } + Controls.Label { + text: qsTr("Dive master") + font.pointSize: subsurfaceTheme.regularPointSize + Layout.preferredWidth: gridWidth * 0.75 + } + SsrfSwitch { + checked: manager.toggleDiveMaster(false) + Layout.preferredWidth: gridWidth * 0.25 + onClicked: { + manager.toggleDiveMaster(true) + } + } + Controls.Label { + text: qsTr("Buddy") + font.pointSize: subsurfaceTheme.regularPointSize + Layout.preferredWidth: gridWidth * 0.75 + } + SsrfSwitch { + checked: manager.toggleBuddy(false) + Layout.preferredWidth: gridWidth * 0.25 + onClicked: { + manager.toggleBuddy(true) + } + } + Controls.Label { + text: qsTr("Suit") + font.pointSize: subsurfaceTheme.regularPointSize + Layout.preferredWidth: gridWidth * 0.75 + } + SsrfSwitch { + checked: manager.toggleSuit(false) + Layout.preferredWidth: gridWidth * 0.25 + onClicked: { + manager.toggleSuit(true) + } + } + Controls.Label { + text: qsTr("Rating") + font.pointSize: subsurfaceTheme.regularPointSize + Layout.preferredWidth: gridWidth * 0.75 + } + SsrfSwitch { + checked: manager.toggleRating(false) + Layout.preferredWidth: gridWidth * 0.25 + onClicked: { + manager.toggleRating(true) + } + } + Controls.Label { + text: qsTr("Visibility") + font.pointSize: subsurfaceTheme.regularPointSize + Layout.preferredWidth: gridWidth * 0.75 + } + SsrfSwitch { + checked: manager.toggleVisibility(false) + Layout.preferredWidth: gridWidth * 0.25 + onClicked: { + manager.toggleVisibility(true) + } + } + Controls.Label { + text: qsTr("Tags") + font.pointSize: subsurfaceTheme.regularPointSize + Layout.preferredWidth: gridWidth * 0.75 + } + SsrfSwitch { + checked: manager.toggleTags(false) + Layout.preferredWidth: gridWidth * 0.25 + onClicked: { + manager.toggleTags(true) + } + } + Controls.Label { + text: qsTr("Cylinders") + font.pointSize: subsurfaceTheme.regularPointSize + Layout.preferredWidth: gridWidth * 0.75 + } + SsrfSwitch { + checked: manager.toggleCylinders(false) + Layout.preferredWidth: gridWidth * 0.25 + onClicked: { + manager.toggleCylinders(true) + } + } + Controls.Label { + text: qsTr("Weights") + font.pointSize: subsurfaceTheme.regularPointSize + Layout.preferredWidth: gridWidth * 0.75 + } + SsrfSwitch { + checked: manager.toggleWeights(false) + Layout.preferredWidth: gridWidth * 0.25 + onClicked: { + manager.toggleWeights(true) + } + } + } + + Rectangle { + color: subsurfaceTheme.darkerPrimaryColor + height: 1 + opacity: 0.5 + Layout.fillWidth: true + } + + Item { + height: Kirigami.Units.gridUnit * 6 + } + } +} diff --git a/mobile-widgets/qml/DiveList.qml b/mobile-widgets/qml/DiveList.qml index 3d29a1ec4..aecd2f003 100644 --- a/mobile-widgets/qml/DiveList.qml +++ b/mobile-widgets/qml/DiveList.qml @@ -232,6 +232,11 @@ Kirigami.ScrollablePage { timer.stop() manager.copyDiveData(dive.id) } + onPressAndHold: { + globalDrawer.close() + manager.copyDiveData(dive.id) + pageStack.push(settingsCopyWindow) + } } } Rectangle { diff --git a/mobile-widgets/qml/DownloadFromDiveComputer.qml b/mobile-widgets/qml/DownloadFromDiveComputer.qml index 142465f87..a1403b07d 100644 --- a/mobile-widgets/qml/DownloadFromDiveComputer.qml +++ b/mobile-widgets/qml/DownloadFromDiveComputer.qml @@ -28,7 +28,7 @@ Kirigami.Page { id: downloadThread onFinished : { - importModel.repopulate() + importModel.repopulate(table) progressBar.visible = false if (dcImportModel.rowCount() > 0) { console.log(dcImportModel.rowCount() + " dive downloaded") @@ -167,18 +167,6 @@ Kirigami.Page { elide: Text.ElideRight } onCurrentTextChanged: { - // pattern that matches BT addresses - var btAddr = /[0-9A-Fa-f][0-9A-Fa-f]:[0-9A-Fa-f][0-9A-Fa-f]:[0-9A-Fa-f][0-9A-Fa-f]:[0-9A-Fa-f][0-9A-Fa-f]:[0-9A-Fa-f][0-9A-Fa-f]:[0-9A-Fa-f][0-9A-Fa-f]/ ; - - // On iOS we store UUID instead of device address. - if (Qt.platform.os === 'ios') - btAddr = /\{?[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}/; - - if (btAddr.test(currentText)) - manager.DC_bluetoothMode = true - else - manager.DC_bluetoothMode = false - manager.DC_devName = currentText dc1.enabled = dc2.enabled = dc3.enabled = dc4.enabled = true for (var i = 1; i < 5; i++) { if (comboProduct.currentIndex === -1 && currentText === "FTDI"){ @@ -198,7 +186,6 @@ Kirigami.Page { } } download.text = qsTr("Download") - } } } @@ -277,10 +264,28 @@ Kirigami.Page { comboConnection.currentIndex != -1 onClicked: { text = qsTr("Retry") - // strip any BT Name from the address - var devName = manager.DC_devName - if (devName != qsTr("USB device")) - manager.DC_devName = devName.replace(/^(.*) /, "") + + var connectionString = comboConnection.currentText + // separate BT address and BT name (if applicable) + // pattern that matches BT addresses + var btAddr = "(LE:)?([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"; + + // On iOS we store UUID instead of device address. + if (Qt.platform.os === 'ios') + btAddr = "(LE:)?\{?[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}"; + + var pattern = new RegExp(btAddr); + var devAddress = ""; + devAddress = pattern.exec(connectionString); + if (devAddress !== null) { + manager.DC_bluetoothMode = true; + manager.DC_devName = devAddress[0]; // exec returns an array with the matched text in element 0 + manager.retrieveBluetoothName(); + manager.appendTextToLog("setting btName to " + manager.DC_devBluetoothName); + } else { + manager.DC_bluetoothMode = false; + manager.DC_devName = connectionString; + } manager.appendTextToLog("DCDownloadThread started for " + manager.DC_vendor + " " + manager.DC_product + " on "+ manager.DC_devName) progressBar.visible = true downloadThread.start() @@ -392,7 +397,7 @@ Kirigami.Page { comboVendor.currentIndex = manager.getDetectedVendorIndex() comboProduct.currentIndex = manager.getDetectedProductIndex(comboVendor.currentText) comboConnection.currentIndex = manager.getMatchingAddress(comboVendor.currentText, comboProduct.currentText) - + } } } diff --git a/mobile-widgets/qml/main.qml b/mobile-widgets/qml/main.qml index a954dfbc4..8c67c9947 100644 --- a/mobile-widgets/qml/main.qml +++ b/mobile-widgets/qml/main.qml @@ -592,6 +592,11 @@ if you have network connectivity and want to sync your data to cloud storage."), visible: false } + CopySettings { + id: settingsCopyWindow + visible: false + } + About { id: aboutWindow visible: false diff --git a/mobile-widgets/qml/mobile-resources.qrc b/mobile-widgets/qml/mobile-resources.qrc index beb1d93f0..989a7e936 100644 --- a/mobile-widgets/qml/mobile-resources.qrc +++ b/mobile-widgets/qml/mobile-resources.qrc @@ -15,6 +15,7 @@ <file>main.qml</file> <file>MapPage.qml</file> <file>Settings.qml</file> + <file>CopySettings.qml</file> <file>ThemeTest.qml</file> <file>StartPage.qml</file> <file>SsrfButton.qml</file> diff --git a/mobile-widgets/qmlmanager.cpp b/mobile-widgets/qmlmanager.cpp index 70ab87ea5..232b50cfa 100644 --- a/mobile-widgets/qmlmanager.cpp +++ b/mobile-widgets/qmlmanager.cpp @@ -144,7 +144,6 @@ QMLManager::QMLManager() : m_locationServiceEnabled(false), m_updateSelectedDive(-1), m_selectedDiveTimestamp(0), alreadySaving(false), - m_device_data(new DCDeviceData), m_pluggedInDeviceName("") { LOG_STP("qmlmgr starting"); @@ -225,6 +224,18 @@ QMLManager::QMLManager() : m_locationServiceEnabled(false), // make sure we know if the current cloud repo has been successfully synced syncLoadFromCloud(); LOG_STP("qmlmgr sync load cloud"); + + memset(&m_copyPasteDive, 0, sizeof(m_copyPasteDive)); + memset(&what, 0, sizeof(what)); + + // Let's set some defaults to be copied so users don't necessarily need + // to know how to configure this + what.divemaster = true; + what.buddy = true; + what.suit = true; + what.tags = true; + what.cylinders = true; + what.weights = true; } void QMLManager::applicationStateChanged(Qt::ApplicationState state) @@ -1318,6 +1329,86 @@ void QMLManager::deleteDive(int id) changesNeedSaving(); } +bool QMLManager::toggleDiveSite(bool toggle) +{ + if (toggle) + what.divesite = what.divesite ? false : true; + + return what.divesite; +} + +bool QMLManager::toggleNotes(bool toggle) +{ + if (toggle) + what.notes = what.notes ? false : true; + + return what.notes; +} + +bool QMLManager::toggleDiveMaster(bool toggle) +{ + if (toggle) + what.divemaster = what.divemaster ? false : true; + + return what.divemaster; +} + +bool QMLManager::toggleBuddy(bool toggle) +{ + if (toggle) + what.buddy = what.buddy ? false : true; + + return what.buddy; +} + +bool QMLManager::toggleSuit(bool toggle) +{ + if (toggle) + what.suit = what.suit ? false : true; + + return what.suit; +} + +bool QMLManager::toggleRating(bool toggle) +{ + if (toggle) + what.rating = what.rating ? false : true; + + return what.rating; +} + +bool QMLManager::toggleVisibility(bool toggle) +{ + if (toggle) + what.visibility = what.visibility ? false : true; + + return what.visibility; +} + +bool QMLManager::toggleTags(bool toggle) +{ + if (toggle) + what.tags = what.tags ? false : true; + + return what.tags; +} + +bool QMLManager::toggleCylinders(bool toggle) +{ + if (toggle) + what.cylinders = what.cylinders ? false : true; + + return what.cylinders; +} + +bool QMLManager::toggleWeights(bool toggle) +{ + if (toggle) + what.weights = what.weights ? false : true; + + return what.weights; +} + void QMLManager::copyDiveData(int id) { m_copyPasteDive = get_dive_by_uniq_id(id); @@ -1326,14 +1417,6 @@ void QMLManager::copyDiveData(int id) return; } - // TODO: selection dialog for the data to be copied - what.divemaster = true; - what.buddy = true; - what.suit = true; - what.tags = true; - what.cylinders = true; - what.weights = true; - setNotificationText("Copy"); } @@ -1354,6 +1437,9 @@ void QMLManager::pasteDiveData(int id) mark_divelist_changed(true); changesNeedSaving(); setNotificationText("Paste"); + + int modelIdx = DiveListModel::instance()->getDiveIdx(id); + DiveListModel::instance()->updateDive(modelIdx, d); } void QMLManager::cancelDownloadDC() @@ -1671,119 +1757,130 @@ void QMLManager::setStatusbarColor(QColor) #endif +void QMLManager::retrieveBluetoothName() +{ + QString name = DC_devName(); + QList<BTDiscovery::btVendorProduct> btDCs = BTDiscovery::instance()->getBtDcs(); + foreach (BTDiscovery::btVendorProduct btDC, btDCs) { + qDebug() << "compare" <<name << btDC.btpdi.address; + if (name.contains(btDC.btpdi.address)) + DC_setDevBluetoothName(btDC.btpdi.name); + } +} + QString QMLManager::DC_vendor() const { - return m_device_data->vendor(); + return DCDeviceData::instance()->vendor(); } QString QMLManager::DC_product() const { - return m_device_data->product(); + return DCDeviceData::instance()->product(); } QString QMLManager::DC_devName() const { - return m_device_data->devName(); + return DCDeviceData::instance()->devName(); } QString QMLManager::DC_devBluetoothName() const { - return m_device_data->devBluetoothName(); + return DCDeviceData::instance()->devBluetoothName(); } QString QMLManager::DC_descriptor() const { - return m_device_data->descriptor(); + return DCDeviceData::instance()->descriptor(); } bool QMLManager::DC_forceDownload() const { - return m_device_data->forceDownload(); + return DCDeviceData::instance()->forceDownload(); } bool QMLManager::DC_bluetoothMode() const { - return m_device_data->bluetoothMode(); + return DCDeviceData::instance()->bluetoothMode(); } bool QMLManager::DC_createNewTrip() const { - return m_device_data->createNewTrip(); + return DCDeviceData::instance()->createNewTrip(); } bool QMLManager::DC_saveDump() const { - return m_device_data->saveDump(); + return DCDeviceData::instance()->saveDump(); } int QMLManager::DC_deviceId() const { - return m_device_data->deviceId(); + return DCDeviceData::instance()->deviceId(); } void QMLManager::DC_setDeviceId(int deviceId) { - m_device_data->setDeviceId(deviceId); + DCDeviceData::instance()->setDeviceId(deviceId); } void QMLManager::DC_setVendor(const QString& vendor) { - m_device_data->setVendor(vendor); + DCDeviceData::instance()->setVendor(vendor); } void QMLManager::DC_setProduct(const QString& product) { - m_device_data->setProduct(product); + DCDeviceData::instance()->setProduct(product); } void QMLManager::DC_setDevName(const QString& devName) { - m_device_data->setDevName(devName); + DCDeviceData::instance()->setDevName(devName); } void QMLManager::DC_setDevBluetoothName(const QString& devBluetoothName) { - m_device_data->setDevBluetoothName(devBluetoothName); + DCDeviceData::instance()->setDevBluetoothName(devBluetoothName); } void QMLManager::DC_setBluetoothMode(bool mode) { - m_device_data->setBluetoothMode(mode); + DCDeviceData::instance()->setBluetoothMode(mode); } void QMLManager::DC_setForceDownload(bool force) { - m_device_data->setForceDownload(force); + DCDeviceData::instance()->setForceDownload(force); } void QMLManager::DC_setCreateNewTrip(bool create) { - m_device_data->setCreateNewTrip(create); + DCDeviceData::instance()->setCreateNewTrip(create); } void QMLManager::DC_setSaveDump(bool dumpMode) { - m_device_data->setSaveDump(dumpMode); + DCDeviceData::instance()->setSaveDump(dumpMode); } QStringList QMLManager::getProductListFromVendor(const QString &vendor) { - return m_device_data->getProductListFromVendor(vendor); + return DCDeviceData::instance()->getProductListFromVendor(vendor); } int QMLManager::getMatchingAddress(const QString &vendor, const QString &product) { - return m_device_data->getMatchingAddress(vendor, product); + return DCDeviceData::instance()->getMatchingAddress(vendor, product); } int QMLManager::getDetectedVendorIndex() { - return m_device_data->getDetectedVendorIndex(); + return DCDeviceData::instance()->getDetectedVendorIndex(); } int QMLManager::getDetectedProductIndex(const QString ¤tVendorText) { - return m_device_data->getDetectedProductIndex(currentVendorText); + return DCDeviceData::instance()->getDetectedProductIndex(currentVendorText); } int QMLManager::getConnectionIndex(const QString &deviceSubstr) diff --git a/mobile-widgets/qmlmanager.h b/mobile-widgets/qmlmanager.h index 1f2790854..2812ae08f 100644 --- a/mobile-widgets/qmlmanager.h +++ b/mobile-widgets/qmlmanager.h @@ -62,6 +62,8 @@ public: QString DC_devName() const; void DC_setDevName(const QString& devName); + Q_INVOKABLE void retrieveBluetoothName(); + QString DC_devBluetoothName() const; void DC_setDevBluetoothName(const QString& devBluetoothName); @@ -165,6 +167,16 @@ public slots: void deleteDive(int id); void copyDiveData(int id); void pasteDiveData(int id); + bool toggleDiveSite(bool toggle); + bool toggleNotes(bool toggle); + bool toggleDiveMaster(bool toggle); + bool toggleBuddy(bool toggle); + bool toggleSuit(bool toggle); + bool toggleRating(bool toggle); + bool toggleVisibility(bool toggle); + bool toggleTags(bool toggle); + bool toggleCylinders(bool toggle); + bool toggleWeights(bool toggle); bool undoDelete(int id); QString addDive(); void addDiveAborted(int id); diff --git a/packaging/android/android-build-wrapper.sh b/packaging/android/android-build-wrapper.sh index 16d50ce82..999cca221 100755 --- a/packaging/android/android-build-wrapper.sh +++ b/packaging/android/android-build-wrapper.sh @@ -83,6 +83,7 @@ if [ ! -d "$ANDROID_SDK"/build-tools/"${ANDROID_BUILDTOOLS_REVISION}" ] ; then unzip -q ../"$SDK_TOOLS" yes | tools/bin/sdkmanager --licenses > /dev/null 2>&1 || echo "d56f5187479451eabf01fb78af6dfcb131a6481e" > licenses/android-sdk-license cat licenses/android-sdk-license + tools/bin/sdkmanager tools platform-tools 'platforms;'"${ANDROID_PLATFORMS}" 'build-tools;'"${ANDROID_BUILDTOOLS_REVISION}" echo "" else pushd "$ANDROID_SDK" diff --git a/packaging/android/build.sh b/packaging/android/build.sh index 99c33b1bc..81db27488 100755 --- a/packaging/android/build.sh +++ b/packaging/android/build.sh @@ -416,7 +416,6 @@ cmake $MOBILE_CMAKE \ -DFTDISUPPORT=${FTDI} \ -DANDROID_NATIVE_LIBSSL="$BUILDROOT/ndk-$ARCH/sysroot/usr/lib/libssl.so" \ -DANDROID_NATIVE_LIBCRYPT="$BUILDROOT/ndk-$ARCH/sysroot/usr/lib/libcrypto.so" \ - -DBUILDTOOLS_REVISION="$ANDROID_BUILDTOOLS_REVISION" \ -DCMAKE_MAKE_PROGRAM="make" \ "$SUBSURFACE_SOURCE" diff --git a/qt-models/diveimportedmodel.cpp b/qt-models/diveimportedmodel.cpp index e988a8678..3e8386b94 100644 --- a/qt-models/diveimportedmodel.cpp +++ b/qt-models/diveimportedmodel.cpp @@ -4,11 +4,8 @@ DiveImportedModel::DiveImportedModel(QObject *o) : QAbstractTableModel(o), firstIndex(0), lastIndex(-1), - checkStates(nullptr), diveTable(nullptr) { - // Defaults to downloadTable, can be changed later. - diveTable = &downloadTable; } int DiveImportedModel::columnCount(const QModelIndex&) const @@ -46,11 +43,6 @@ QVariant DiveImportedModel::headerData(int section, Qt::Orientation orientation, return QVariant(); } -void DiveImportedModel::setDiveTable(struct dive_table* table) -{ - diveTable = table; -} - QVariant DiveImportedModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) @@ -97,7 +89,7 @@ void DiveImportedModel::changeSelected(QModelIndex clickedIndex) void DiveImportedModel::selectAll() { - memset(checkStates, true, lastIndex - firstIndex + 1); + std::fill(checkStates.begin(), checkStates.end(), true); dataChanged(index(0, 0), index(lastIndex - firstIndex, 0), QVector<int>() << Qt::CheckStateRole << Selected); } @@ -109,7 +101,7 @@ void DiveImportedModel::selectRow(int row) void DiveImportedModel::selectNone() { - memset(checkStates, false, lastIndex - firstIndex + 1); + std::fill(checkStates.begin(), checkStates.end(), false); dataChanged(index(0, 0), index(lastIndex - firstIndex,0 ), QVector<int>() << Qt::CheckStateRole << Selected); } @@ -135,26 +127,17 @@ void DiveImportedModel::clearTable() endRemoveRows(); } -void DiveImportedModel::setImportedDivesIndexes(int first, int last) +void DiveImportedModel::repopulate(dive_table_t *table) { - if (lastIndex >= firstIndex) { - beginRemoveRows(QModelIndex(), 0, lastIndex - firstIndex); - endRemoveRows(); - } - if (last >= first) - beginInsertRows(QModelIndex(), 0, last - first); - lastIndex = last; - firstIndex = first; - delete[] checkStates; - checkStates = new bool[last - first + 1]; - memset(checkStates, true, last - first + 1); - if (last >= first) - endInsertRows(); -} + beginResetModel(); -void DiveImportedModel::repopulate() -{ - setImportedDivesIndexes(0, diveTable->nr-1); + diveTable = table; + firstIndex = 0; + lastIndex = diveTable->nr - 1; + checkStates.resize(diveTable->nr); + std::fill(checkStates.begin(), checkStates.end(), true); + + endResetModel(); } void DiveImportedModel::recordDives() @@ -170,7 +153,7 @@ void DiveImportedModel::recordDives() if (checkStates[i]) j++; else - delete_dive_from_table(&downloadTable, j); + delete_dive_from_table(diveTable, j); } process_imported_dives(diveTable, true, true); diff --git a/qt-models/diveimportedmodel.h b/qt-models/diveimportedmodel.h index eeea0b82c..0c5ba34cd 100644 --- a/qt-models/diveimportedmodel.h +++ b/qt-models/diveimportedmodel.h @@ -2,6 +2,8 @@ #define DIVEIMPORTEDMODEL_H #include <QAbstractTableModel> +#include <vector> +#include "core/dive.h" class DiveImportedModel : public QAbstractTableModel { @@ -10,7 +12,6 @@ public: enum roleTypes { DateTime = Qt::UserRole + 1, Duration, Depth, Selected}; DiveImportedModel(QObject *parent = 0); - void setDiveTable(struct dive_table *table); int columnCount(const QModelIndex& index = QModelIndex()) const; int rowCount(const QModelIndex& index = QModelIndex()) const; QVariant data(const QModelIndex& index, int role) const; @@ -19,7 +20,7 @@ public: Qt::ItemFlags flags(const QModelIndex &index) const; Q_INVOKABLE void clearTable(); QHash<int, QByteArray> roleNames() const; - Q_INVOKABLE void repopulate(); + Q_INVOKABLE void repopulate(dive_table_t *table); Q_INVOKABLE void recordDives(); public slots: @@ -31,7 +32,7 @@ slots: private: int firstIndex; int lastIndex; - bool *checkStates; + std::vector<char> checkStates; // char instead of bool to avoid silly pessimization of std::vector. struct dive_table *diveTable; }; diff --git a/qt-models/divetripmodel.cpp b/qt-models/divetripmodel.cpp index ae7641ba2..7a76ffbdb 100644 --- a/qt-models/divetripmodel.cpp +++ b/qt-models/divetripmodel.cpp @@ -696,12 +696,13 @@ void DiveTripModel::topLevelChanged(int idx) // If that didn't change, try to move forward if (newIdx == idx) { - while (newIdx <= (int)items.size() && !dive_or_trip_less_than(items[idx].d_or_t, items[newIdx + 1].d_or_t)) + ++newIdx; + while (newIdx < (int)items.size() && !dive_or_trip_less_than(items[idx].d_or_t, items[newIdx].d_or_t)) ++newIdx; } // If index changed, move items - if (newIdx != idx) { + if (newIdx != idx && newIdx != idx + 1) { beginMoveRows(QModelIndex(), idx, idx, QModelIndex(), newIdx); moveInVector(items, idx, idx + 1, newIdx); endMoveRows(); diff --git a/qt-models/filtermodels.cpp b/qt-models/filtermodels.cpp index 4063ba149..25344b712 100644 --- a/qt-models/filtermodels.cpp +++ b/qt-models/filtermodels.cpp @@ -15,547 +15,71 @@ #include <QDebug> #include <algorithm> -#define CREATE_INSTANCE_METHOD(CLASS) \ - CLASS *CLASS::instance() \ - { \ - static CLASS *self = new CLASS(); \ - return self; \ - } - -CREATE_INSTANCE_METHOD(TagFilterModel) -CREATE_INSTANCE_METHOD(BuddyFilterModel) -CREATE_INSTANCE_METHOD(LocationFilterModel) -CREATE_INSTANCE_METHOD(SuitsFilterModel) -CREATE_INSTANCE_METHOD(MultiFilterSortModel) - -FilterModelBase::FilterModelBase(QObject *parent) : QAbstractListModel(parent), - anyChecked(false), - negate(false) -{ -} - -// Get index of item with given name, but ignore last item, as this -// is the "Show Empty Tags" item. Return -1 for not found. -int FilterModelBase::indexOf(const QString &name) const -{ - for (int i = 0; i < rowCount() - 1; i++) { - if (name == items[i].name) - return i; - } - return -1; -} - -int FilterModelBase::findInsertionIndex(const QString &name) -{ - // Find insertion position. Note: we search only up to the last - // item, because the last item is the "Show Empty Tags" item. - // N.B: We might do a binary search using std::lower_bound() - int i; - for (i = 0; i < rowCount() - 1; i++) { - if (name < items[i].name) - return i; - } - return i; -} - -void FilterModelBase::addItem(const QString &name, bool checked, int count) -{ - int idx = findInsertionIndex(name); - beginInsertRows(QModelIndex(), idx, idx); - items.insert(items.begin() + idx, { name, checked, count }); - endInsertRows(); -} - -void FilterModelBase::changeName(const QString &oldName, const QString &newName) -{ - if (oldName.isEmpty() || newName.isEmpty() || oldName == newName) - return; - int oldIndex = indexOf(oldName); - if (oldIndex < 0) - return; - int newIndex = indexOf(newName); - - if (newIndex >= 0) { - // If there was already an entry with the new name, we are merging entries. - // Thus, if the old entry was selected, also select the new entry. - if (items[oldIndex].checked && !items[newIndex].checked) { - items[newIndex].checked = true; - dataChanged(createIndex(newIndex, 0), createIndex(newIndex, 0)); - } - // Now, delete the old item - beginRemoveRows(QModelIndex(), oldIndex, oldIndex); - items.erase(items.begin() + oldIndex); - endRemoveRows(); - } else { - // There was no entry of the same name. We might have to move the item. - newIndex = findInsertionIndex(newName); - if (oldIndex != newIndex && oldIndex + 1 != newIndex) { - beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex); - moveInVector(items, oldIndex, oldIndex + 1, newIndex); - endMoveRows(); - } - - // The item was moved, but the name still has to be modified - items[newIndex].name = newName; - dataChanged(createIndex(newIndex, 0), createIndex(newIndex, 0)); - } -} - -// Update the the items array. -// The last item is supposed to be the "Show Empty Tags" entry. -// All other items will be sorted alphabetically. Attention: the passed-in list is modified! -void FilterModelBase::updateList(QStringList &newList) -{ - // Sort list, but leave out last element by using std::prev() - if (!newList.empty()) - std::sort(newList.begin(), std::prev(newList.end())); - - beginResetModel(); - - // Keep copy of the old items array to reimport the checked state later. - // Note that by using std::move(), this is an essentially free operation: - // The data is moved from the old array to the new one and the old array - // is reset to zero size. - std::vector<Item> oldItems = std::move(items); - - // Resize the cleared array to the new size. This leaves the checked - // flag in an undefined state (since we didn't define a default constructor). - items.resize(newList.count()); - - // First, reset all checked states to false and set the names - anyChecked = false; - for (int i = 0; i < rowCount(); ++i) { - items[i].name = newList[i]; - items[i].checked = false; - } - - // Then, restore the checked state. Ignore the last item, since - // this is the "Show Empty Tags" entry. - for (int i = 0; i < (int)oldItems.size() - 1; i++) { - if (oldItems[i].checked) { - int ind = newList.indexOf(oldItems[i].name); - if (ind >= 0 && ind < newList.count() - 1) { - items[ind].checked = true; - anyChecked = true; - } - } - } - - // Reset the state of the "Show Empty Tags" entry. But be careful: - // on program startup, the old list is empty. - if (!oldItems.empty() && !items.empty() && oldItems.back().checked) { - items.back().checked = true; - anyChecked = true; - } - - // Finally, calculate and cache the counts. Ignore the last item, since - // this is the "Show Empty Tags" entry. - for (int i = 0; i < (int)newList.size() - 1; i++) - items[i].count = countDives(qPrintable(newList[i])); - - // Calculate count of "Empty Tags". - if (!items.empty()) - items.back().count = countDives(""); - - endResetModel(); -} - -// Decrease count of entry with given name. Remove if count reaches zero. -// Exception: Don't remove the "Show Empty Tags" entry. -void FilterModelBase::decreaseCount(const QString &name) -{ - if (name.isEmpty()) { - // Decrease the "Show Empty Tags" entry. Keep it even if count reaches 0. - if (items.empty() || items.back().count <= 0) - return; // Shouldn't happen! - --items.back().count; - int idx = items.size() - 1; - dataChanged(createIndex(idx, 0), createIndex(idx, 0)); - return; - } - - int idx = indexOf(name); - if (idx < 0 || items[idx].count <= 0) - return; // Shouldn't happen - - if(--items[idx].count == 0) { - beginRemoveRows(QModelIndex(), idx, idx); - items.erase(items.begin() + idx); - endRemoveRows(); - } else { - dataChanged(createIndex(idx, 0), createIndex(idx, 0)); - } -} - -// Increase count of entry with given name. If entry doesn't yet exist, add it. -void FilterModelBase::increaseCount(const QString &name) -{ - if (name.isEmpty()) { - // Increase the "Show Empty Tags" entry. Keep it even if count reaches 0. - if (items.empty()) - return; // Shouldn't happen! - ++items.back().count; - int idx = items.size() - 1; - dataChanged(createIndex(idx, 0), createIndex(idx, 0)); - return; - } - - int idx = indexOf(name); - if (idx < 0) { - idx = findInsertionIndex(name); - beginInsertRows(QModelIndex(), idx, idx); - items.insert(items.begin() + idx, { name, anyChecked, 1 }); - endInsertRows(); - } else { - ++items[idx].count; - dataChanged(createIndex(idx, 0), createIndex(idx, 0)); - } -} - -Qt::ItemFlags FilterModelBase::flags(const QModelIndex &index) const -{ - return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable; -} - -bool FilterModelBase::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (role == Qt::CheckStateRole) { - items[index.row()].checked = value.toBool(); - anyChecked = false; - for (const Item &item: items) { - if (item.checked) { - anyChecked = true; - break; - } +namespace { + bool hasTag(const QStringList tags, const struct dive *d) + { + if (!tags.isEmpty()) { + auto dive_tags = get_taglist_string(d->tag_list).split(","); + bool found_tag = false; + for (const auto& filter_tag : tags) + for (const auto& dive_tag : dive_tags) + if (dive_tag.trimmed().toUpper().contains(filter_tag.trimmed().toUpper())) + found_tag = true; + + return found_tag; } - dataChanged(index, index, { role }); - return true; - } - return false; -} - -int FilterModelBase::rowCount(const QModelIndex &) const -{ - return items.size(); -} - -QVariant FilterModelBase::data(const QModelIndex &index, int role) const -{ - if (role == Qt::CheckStateRole) { - return items[index.row()].checked ? Qt::Checked : Qt::Unchecked; - } else if (role == Qt::DisplayRole) { - const Item &item = items[index.row()]; - return QStringLiteral("%1 (%2)").arg(item.name, QString::number(item.count)); - } - return QVariant(); -} - -void FilterModelBase::clearFilter() -{ - for (Item &item: items) - item.checked = false; - anyChecked = false; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0)); -} - -void FilterModelBase::selectAll() -{ - for (Item &item: items) - item.checked = true; - anyChecked = true; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0)); -} - -void FilterModelBase::invertSelection() -{ - for (Item &item: items) - item.checked = !item.checked; - anyChecked = std::any_of(items.begin(), items.end(), [](Item &item) { return !!item.checked; }); - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0)); -} - -void FilterModelBase::setNegate(bool negateParam) -{ - negate = negateParam; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0)); -} - -SuitsFilterModel::SuitsFilterModel(QObject *parent) : FilterModelBase(parent) -{ -} - -int SuitsFilterModel::countDives(const char *s) const -{ - return count_dives_with_suit(s); -} - -bool SuitsFilterModel::doFilter(const dive *d) const -{ - // rowCount() == 0 should never happen, because we have the "no suits" row - // let's handle it gracefully anyway. - if (!anyChecked || rowCount() == 0) return true; - - // Checked means 'Show', Unchecked means 'Hide'. - QString suit(d->suit); - // only show empty suit dives if the user checked that. - if (suit.isEmpty()) - return items[rowCount() - 1].checked != negate; - - // there is a suit selected - // Ignore last item, since this is the "Show Empty Tags" entry - for (int i = 0; i < rowCount() - 1; i++) { - if (items[i].checked && suit == items[i].name) - return !negate; } - return negate; -} -void SuitsFilterModel::diveAdded(const dive *d) -{ - increaseCount(QString(d->suit)); -} + bool hasPerson(const QStringList people, const struct dive *d) + { + if (!people.isEmpty()) { + QStringList dive_people = QString(d->buddy).split(",", QString::SkipEmptyParts) + + QString(d->divemaster).split(",", QString::SkipEmptyParts); -void SuitsFilterModel::diveDeleted(const dive *d) -{ - decreaseCount(QString(d->suit)); -} + bool found_person = false; + for(const auto& filter_person : people) + for(const auto& dive_person : dive_people) + if (dive_person.trimmed().toUpper().contains(filter_person.trimmed().toUpper())) + found_person = true; -void SuitsFilterModel::repopulate() -{ - QStringList list; - struct dive *dive; - int i = 0; - for_each_dive (i, dive) { - QString suit(dive->suit); - if (!suit.isEmpty() && !list.contains(suit)) { - list.append(suit); + return found_person; } - } - list << tr("No suit set"); - updateList(list); -} - -TagFilterModel::TagFilterModel(QObject *parent) : FilterModelBase(parent) -{ -} - -int TagFilterModel::countDives(const char *s) const -{ - return count_dives_with_tag(s); -} - -void TagFilterModel::repopulate() -{ - if (g_tag_list == NULL) - return; - QStringList list; - struct tag_entry *current_tag_entry = g_tag_list; - while (current_tag_entry != NULL) { - if (count_dives_with_tag(current_tag_entry->tag->name) > 0) - list.append(QString(current_tag_entry->tag->name)); - current_tag_entry = current_tag_entry->next; - } - list << tr("Empty tags"); - updateList(list); -} - -bool TagFilterModel::doFilter(const dive *d) const -{ - // If there's nothing checked, this should show everything - // rowCount() == 0 should never happen, because we have the "no tags" row - // let's handle it gracefully anyway. - if (!anyChecked || rowCount() == 0) return true; - - // Checked means 'Show', Unchecked means 'Hide'. - struct tag_entry *head = d->tag_list; - - if (!head) // last tag means "Show empty tags"; - return items[rowCount() - 1].checked != negate; - - // have at least one tag. - while (head) { - QString tagName(head->tag->name); - int index = indexOf(tagName); - if (index >= 0 && items[index].checked) - return !negate; - head = head->next; - } - return negate; -} - -void TagFilterModel::diveAdded(const dive *d) -{ - struct tag_entry *head = d->tag_list; - if (!head) { - increaseCount(QString()); - return; - } - while (head) { - increaseCount(QString()); - increaseCount(QString(head->tag->name)); - head = head->next; } -} - -void TagFilterModel::diveDeleted(const dive *d) -{ - struct tag_entry *head = d->tag_list; - if (!head) { - decreaseCount(QString()); - return; - } - while (head) { - decreaseCount(QString(head->tag->name)); - head = head->next; - } -} -BuddyFilterModel::BuddyFilterModel(QObject *parent) : FilterModelBase(parent) -{ -} - -int BuddyFilterModel::countDives(const char *s) const -{ - return count_dives_with_person(s); -} - -static QStringList getDiveBuddies(const dive *d) -{ - QString persons = QString(d->buddy) + "," + QString(d->divemaster); - QStringList personsList = persons.split(',', QString::SkipEmptyParts); - for (QString &s: personsList) - s = s.trimmed(); - return personsList; -} - -bool BuddyFilterModel::doFilter(const dive *d) const -{ - // If there's nothing checked, this should show everything - // rowCount() == 0 should never happen, because we have the "no tags" row - // let's handle it gracefully anyway. - if (!anyChecked || rowCount() == 0) - return true; - - QStringList personsList = getDiveBuddies(d); - // only show empty buddie dives if the user checked that. - if (personsList.isEmpty()) - return items[rowCount() - 1].checked != negate; + bool hasLocation(const QStringList locations, const struct dive *d) + { + if (!locations.isEmpty()) { + QStringList diveLocations; + if (d->divetrip) + diveLocations.push_back(QString(d->divetrip->location)); - // have at least one buddy - // Ignore last item, since this is the "Show Empty Tags" entry - for (int i = 0; i < rowCount() - 1; i++) { - if (items[i].checked && personsList.contains(items[i].name, Qt::CaseInsensitive)) - return !negate; - } - return negate; -} - -void BuddyFilterModel::diveAdded(const dive *d) -{ - QStringList buddies = getDiveBuddies(d); - if (buddies.empty()) { - increaseCount(QString()); - return; - } - for(const QString &buddy: buddies) - increaseCount(buddy); -} + if (d->dive_site) + diveLocations.push_back(QString(d->dive_site->name)); -void BuddyFilterModel::diveDeleted(const dive *d) -{ - QStringList buddies = getDiveBuddies(d); - if (buddies.empty()) { - decreaseCount(QString()); - return; - } - for(const QString &buddy: buddies) - decreaseCount(buddy); -} + bool found_location = false; + for (const auto& filter_location : locations) + for (const auto& dive_location : diveLocations) + if (dive_location.trimmed().toUpper().contains(filter_location.trimmed().toUpper())) + found_location = true; -void BuddyFilterModel::repopulate() -{ - QStringList list; - struct dive *dive; - int i = 0; - for_each_dive (i, dive) { - QString persons = QString(dive->buddy) + "," + QString(dive->divemaster); - Q_FOREACH (const QString &person, persons.split(',', QString::SkipEmptyParts)) { - // Remove any leading spaces - if (!list.contains(person.trimmed())) { - list.append(person.trimmed()); - } + return found_location; } - } - list << tr("No buddies"); - updateList(list); -} - -LocationFilterModel::LocationFilterModel(QObject *parent) : FilterModelBase(parent) -{ -} - -int LocationFilterModel::countDives(const char *s) const -{ - return count_dives_with_location(s); -} - -bool LocationFilterModel::doFilter(const dive *d) const -{ - // rowCount() == 0 should never happen, because we have the "no location" row - // let's handle it gracefully anyway. - if (!anyChecked || rowCount() == 0) return true; - - // Checked means 'Show', Unchecked means 'Hide'. - QString location(get_dive_location(d)); - // only show empty location dives if the user checked that. - if (location.isEmpty()) - return items[rowCount() - 1].checked != negate; - - // There is a location selected - // Ignore last item, since this is the "Show Empty Tags" entry - for (int i = 0; i < rowCount() - 1; i++) { - if (items[i].checked && location == items[i].name) - return !negate; } - return negate; -} - -void LocationFilterModel::diveAdded(const dive *d) -{ - increaseCount(get_dive_location(d)); -} - -void LocationFilterModel::diveDeleted(const dive *d) -{ - decreaseCount(get_dive_location(d)); -} -void LocationFilterModel::repopulate() -{ - QStringList list; - struct dive *dive; - int i = 0; - for_each_dive (i, dive) { - QString location(get_dive_location(dive)); - if (!location.isEmpty() && !list.contains(location)) { - list.append(location); - } + // TODO: Finish this iimplementation. + bool hasEquipment(const QStringList& equipment, const struct dive *d) + { + return true; } - list << tr("No location set"); - updateList(list); } -void LocationFilterModel::addName(const QString &newName) +MultiFilterSortModel *MultiFilterSortModel::instance() { - if (newName.isEmpty() || indexOf(newName) >= 0) - return; - int count = countDives(qPrintable(newName)); - // If any old item was checked, also check the new one so that - // dives with the added dive site are shown. - addItem(newName, anyChecked, count); + static MultiFilterSortModel self; + return &self; } MultiFilterSortModel::MultiFilterSortModel(QObject *parent) : QSortFilterProxyModel(parent), @@ -574,44 +98,45 @@ void MultiFilterSortModel::setLayout(DiveTripModel::Layout layout) tripModel->setLayout(layout); // Note: setLayout() resets the whole model } -void MultiFilterSortModel::divesAdded(const QVector<dive *> &dives) +bool MultiFilterSortModel::showDive(const struct dive *d) const { - // TODO: We call diveAdded for every dive and model. - // If multiple dives are added (e.g. import dive) this will lead to a large - // number of model changes and might be a pessimization compared to a full - // model reload. Instead, the models should take the vector, calculate the - // new fields and add them at once. - for (FilterModelBase *model: models) { - for (const dive *d: dives) - model->diveAdded(d); - } -} + if (!filterData.validFilter) + return true; -void MultiFilterSortModel::divesDeleted(const QVector<dive *> &dives) -{ - // TODO: See comment for divesDeleted - for (FilterModelBase *model: models) { - for (const dive *d: dives) - model->diveDeleted(d); - } -} + if (d->visibility < filterData.minVisibility || d->visibility > filterData.maxVisibility) + return false; -bool MultiFilterSortModel::showDive(const struct dive *d) const -{ - if (curr_dive_site) { - dive_site *ds = d->dive_site; - if (!ds) - return false; - return ds == curr_dive_site || same_string(ds->name, curr_dive_site->name); - } + if (d->rating < filterData.minRating || d->rating > filterData.maxRating) + return false; - if (models.isEmpty()) - return true; + // TODO: get the preferences for the imperial vs metric data. + // ignore the check if it doesn't makes sense. + if (d->watertemp.mkelvin < C_to_mkelvin(filterData.minWaterTemp) || d->watertemp.mkelvin > C_to_mkelvin((filterData.maxWaterTemp))) + return false; - for (const FilterModelBase *model: models) { - if (!model->doFilter(d)) - return false; - } + if (d->airtemp.mkelvin < C_to_mkelvin(filterData.minAirTemp) || d->airtemp.mkelvin > C_to_mkelvin(filterData.maxAirTemp)) + return false; + + if (filterData.from.isValid() && d->when < filterData.from.toTime_t()) + return false; + + if (filterData.to.isValid() && d->when > filterData.to.toTime_t()) + return false; + + // tags. + if (!hasTag(filterData.tags, d)) + return false; + + // people + if (!hasPerson(filterData.people, d)) + return false; + + // Location + if (!hasLocation(filterData.location, d)) + return false; + + if (!hasEquipment(filterData.equipment, d)) + return false; return true; } @@ -674,23 +199,8 @@ void MultiFilterSortModel::myInvalidate() #endif } -void MultiFilterSortModel::addFilterModel(FilterModelBase *model) -{ - models.append(model); - connect(model, &FilterModelBase::dataChanged, this, &MultiFilterSortModel::filterChanged); -} - -void MultiFilterSortModel::removeFilterModel(FilterModelBase *model) -{ - models.removeAll(model); - disconnect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(myInvalidate())); -} - void MultiFilterSortModel::clearFilter() { - Q_FOREACH (FilterModelBase *iface, models) { - iface->clearFilter(); - } myInvalidate(); } @@ -711,3 +221,9 @@ bool MultiFilterSortModel::lessThan(const QModelIndex &i1, const QModelIndex &i2 // Hand sorting down to the source model. return model->lessThan(i1, i2); } + +void MultiFilterSortModel::filterDataChanged(const FilterData& data) +{ + filterData = data; + myInvalidate(); +} diff --git a/qt-models/filtermodels.h b/qt-models/filtermodels.h index 8830e99c3..61b82be33 100644 --- a/qt-models/filtermodels.h +++ b/qt-models/filtermodels.h @@ -6,6 +6,8 @@ #include <QStringListModel> #include <QSortFilterProxyModel> +#include <QDateTime> + #include <stdint.h> #include <vector> @@ -13,108 +15,23 @@ struct dive; struct dive_trip; class DiveTripModel; -class FilterModelBase : public QAbstractListModel { - Q_OBJECT -private: - int findInsertionIndex(const QString &name); -protected: - struct Item { - QString name; - bool checked; - int count; - }; - std::vector<Item> items; - int indexOf(const QString &name) const; - void addItem(const QString &name, bool checked, int count); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - void decreaseCount(const QString &d); - void increaseCount(const QString &d); -public: - virtual bool doFilter(const dive *d) const = 0; - virtual void diveAdded(const dive *d) = 0; - virtual void diveDeleted(const dive *d) = 0; - void clearFilter(); - void selectAll(); - void invertSelection(); - bool anyChecked; - bool negate; -public -slots: - void setNegate(bool negate); - void changeName(const QString &oldName, const QString &newName); -protected: - explicit FilterModelBase(QObject *parent = 0); - void updateList(QStringList &new_list); - virtual int countDives(const char *) const = 0; -private: - Qt::ItemFlags flags(const QModelIndex &index) const; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); -}; - -class TagFilterModel : public FilterModelBase { - Q_OBJECT -public: - static TagFilterModel *instance(); - bool doFilter(const dive *d) const; -public -slots: - void repopulate(); - -private: - explicit TagFilterModel(QObject *parent = 0); - int countDives(const char *) const; - void diveAdded(const dive *d); - void diveDeleted(const dive *d); -}; - -class BuddyFilterModel : public FilterModelBase { - Q_OBJECT -public: - static BuddyFilterModel *instance(); - bool doFilter(const dive *d) const; -public -slots: - void repopulate(); - -private: - explicit BuddyFilterModel(QObject *parent = 0); - int countDives(const char *) const; - void diveAdded(const dive *d); - void diveDeleted(const dive *d); -}; - -class LocationFilterModel : public FilterModelBase { - Q_OBJECT -public: - static LocationFilterModel *instance(); - bool doFilter(const dive *d) const; -public -slots: - void repopulate(); - void addName(const QString &newName); - -private: - explicit LocationFilterModel(QObject *parent = 0); - int countDives(const char *) const; - void diveAdded(const dive *d); - void diveDeleted(const dive *d); -}; - -class SuitsFilterModel : public FilterModelBase { - Q_OBJECT -public: - static SuitsFilterModel *instance(); - bool doFilter(const dive *d) const; -public -slots: - void repopulate(); - -private: - explicit SuitsFilterModel(QObject *parent = 0); - int countDives(const char *) const; - void diveAdded(const dive *d); - void diveDeleted(const dive *d); +struct FilterData { + bool validFilter = false; + int minVisibility = 0; + int maxVisibility = 5; + int minRating = 0; + int maxRating = 5; + double minWaterTemp = 0; + double maxWaterTemp = 100; + double minAirTemp = 0; + double maxAirTemp = 100; + QDateTime from; + QDateTime to = QDateTime::currentDateTime(); + QStringList tags; + QStringList people; + QStringList location; + QStringList equipment; + bool invertFilter; }; class MultiFilterSortModel : public QSortFilterProxyModel { @@ -122,10 +39,6 @@ class MultiFilterSortModel : public QSortFilterProxyModel { public: static MultiFilterSortModel *instance(); bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; - void addFilterModel(FilterModelBase *model); - void removeFilterModel(FilterModelBase *model); - void divesAdded(const QVector<dive *> &dives); - void divesDeleted(const QVector<dive *> &dives); bool showDive(const struct dive *d) const; int divesDisplayed; bool lessThan(const QModelIndex &, const QModelIndex &) const override; @@ -137,14 +50,16 @@ slots: void stopFilterDiveSite(); void filterChanged(const QModelIndex &from, const QModelIndex &to, const QVector<int> &roles); void setLayout(DiveTripModel::Layout layout); + void filterDataChanged(const FilterData& data); signals: void filterFinished(); + private: MultiFilterSortModel(QObject *parent = 0); - QList<FilterModelBase *> models; struct dive_site *curr_dive_site; DiveTripModel *model; + FilterData filterData; }; #endif diff --git a/qt-models/yearlystatisticsmodel.cpp b/qt-models/yearlystatisticsmodel.cpp index c8aa2639b..fa86cffbe 100644 --- a/qt-models/yearlystatisticsmodel.cpp +++ b/qt-models/yearlystatisticsmodel.cpp @@ -15,6 +15,7 @@ public: SHORTEST_TIME, LONGEST_TIME, AVG_DEPTH, + AVG_MAX_DEPTH, MIN_DEPTH, MAX_DEPTH, AVG_SAC, @@ -74,6 +75,10 @@ QVariant YearStatisticsItem::data(int column, int role) const case AVG_DEPTH: ret = get_depth_string(stats_interval.avg_depth); break; + case AVG_MAX_DEPTH: + if (stats_interval.selection_size) + ret = get_depth_string(stats_interval.combined_max_depth.mm / stats_interval.selection_size); + break; case MIN_DEPTH: ret = get_depth_string(stats_interval.min_depth); break; @@ -143,6 +148,9 @@ QVariant YearlyStatisticsModel::headerData(int section, Qt::Orientation orientat case AVG_DEPTH: val = QString(tr("Depth (%1)\n Average")).arg(get_depth_unit()); break; + case AVG_MAX_DEPTH: + val = tr("\nAverage maximum"); + break; case MIN_DEPTH: val = tr("\nMinimum"); break; diff --git a/qt-models/yearlystatisticsmodel.h b/qt-models/yearlystatisticsmodel.h index 77a5ae074..99e646388 100644 --- a/qt-models/yearlystatisticsmodel.h +++ b/qt-models/yearlystatisticsmodel.h @@ -15,6 +15,7 @@ public: SHORTEST_TIME, LONGEST_TIME, AVG_DEPTH, + AVG_MAX_DEPTH, MIN_DEPTH, MAX_DEPTH, AVG_SAC, diff --git a/scripts/windows-container/before_install.sh b/scripts/windows-container/before_install.sh index bcfcf761a..b0d0e5f13 100644 --- a/scripts/windows-container/before_install.sh +++ b/scripts/windows-container/before_install.sh @@ -38,6 +38,7 @@ docker run -v $PWD/win32:/win/win32 -v $PWD/subsurface:/win/subsurface --name=bu # for some reason this package was installed but still isn't there? # hmmmm. The container doesn't seem to have libtool installed +docker exec -t builder apt-get update docker exec -t builder apt-get install -y ca-certificates libtool # now set up our other dependencies diff --git a/tests/testpicture.cpp b/tests/testpicture.cpp index 695c965b0..a84925c72 100644 --- a/tests/testpicture.cpp +++ b/tests/testpicture.cpp @@ -25,13 +25,15 @@ void TestPicture::addPicture() QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test44.xml", &dive_table), 0); dive = get_dive(0); + // Pictures will be added to selected dives + dive->selected = true; QVERIFY(dive != NULL); pic1 = dive->picture_list; // So far no picture in dive QVERIFY(pic1 == NULL); - dive_create_picture(dive, SUBSURFACE_TEST_DATA "/dives/images/wreck.jpg", 0, false); - dive_create_picture(dive, SUBSURFACE_TEST_DATA "/dives/images/data_after_EOI.jpg", 0, false); + create_picture(SUBSURFACE_TEST_DATA "/dives/images/wreck.jpg", 0, false); + create_picture(SUBSURFACE_TEST_DATA "/dives/images/data_after_EOI.jpg", 0, false); pic1 = dive->picture_list; pic2 = pic1->next; // Now there are two picture2 |