Compare commits
36 Commits
master
...
bstoeger-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4293f39535 | ||
|
|
86309374cb | ||
|
|
fade8ca95d | ||
|
|
0bf1d35a4b | ||
|
|
bd089f3556 | ||
|
|
5f2c7c6504 | ||
|
|
376da40e43 | ||
|
|
8580364481 | ||
|
|
5a0f6fe84f | ||
|
|
3c3997b24e | ||
|
|
199cc61e07 | ||
|
|
d1b0bba530 | ||
|
|
bbfdf75283 | ||
|
|
2c87a057cc | ||
|
|
0a7fc9b350 | ||
|
|
605d7bb2c0 | ||
|
|
7391c60223 | ||
|
|
944670bc62 | ||
|
|
3d9e021d8d | ||
|
|
2aa1a95bde | ||
|
|
489aafe4b3 | ||
|
|
ea96b9909f | ||
|
|
5f9c2b2d04 | ||
|
|
9a418401fa | ||
|
|
0ead02dce1 | ||
|
|
0ede842a43 | ||
|
|
4b801de88a | ||
|
|
0346bc13dc | ||
|
|
ed1604087f | ||
|
|
584d835693 | ||
|
|
cc081fd414 | ||
|
|
78f1e8b513 | ||
|
|
9d4d35976b | ||
|
|
db4108df6d | ||
|
|
e0f71f1a49 | ||
|
|
405399a091 |
@ -395,9 +395,9 @@ if (NOT SUBSURFACE_TARGET_EXECUTABLE MATCHES "DownloaderExecutable")
|
||||
qt_add_resources(SUBSURFACE_RESOURCES map-widget/qml/map-widget.qrc)
|
||||
set(SUBSURFACE_MAPWIDGET subsurface_mapwidget)
|
||||
endif()
|
||||
qt_add_resources(SUBSURFACE_RESOURCES subsurface.qrc profile.qrc stats/statsicons.qrc desktop-widgets/qml/statsview2.qrc)
|
||||
qt_add_resources(SUBSURFACE_RESOURCES subsurface.qrc profile.qrc stats/statsicons.qrc desktop-widgets/qml/statsview2.qrc desktop-widgets/qml/profileview.qrc)
|
||||
else()
|
||||
qt5_add_resources(SUBSURFACE_RESOURCES subsurface.qrc profile.qrc stats/statsicons.qrc map-widget/qml/map-widget.qrc desktop-widgets/qml/statsview2.qrc)
|
||||
qt5_add_resources(SUBSURFACE_RESOURCES subsurface.qrc profile.qrc stats/statsicons.qrc map-widget/qml/map-widget.qrc desktop-widgets/qml/statsview2.qrc desktop-widgets/qml/profileview.qrc)
|
||||
set(SUBSURFACE_MAPWIDGET subsurface_mapwidget)
|
||||
endif()
|
||||
endif()
|
||||
@ -420,6 +420,7 @@ if(MAPSUPPORT)
|
||||
add_subdirectory(map-widget)
|
||||
endif()
|
||||
add_subdirectory(mobile-widgets)
|
||||
add_subdirectory(qt-quick)
|
||||
add_subdirectory(stats)
|
||||
endif()
|
||||
add_subdirectory(backend-shared)
|
||||
@ -473,6 +474,7 @@ if (SUBSURFACE_TARGET_EXECUTABLE MATCHES "MobileExecutable")
|
||||
subsurface_commands
|
||||
subsurface_corelib
|
||||
subsurface_stats
|
||||
subsurface_qtquick
|
||||
kirigamiplugin
|
||||
${SUBSURFACE_LINK_LIBRARIES}
|
||||
)
|
||||
@ -499,6 +501,7 @@ elseif (SUBSURFACE_TARGET_EXECUTABLE MATCHES "DesktopExecutable")
|
||||
subsurface_commands
|
||||
subsurface_corelib
|
||||
subsurface_stats
|
||||
subsurface_qtquick
|
||||
${SUBSURFACE_LINK_LIBRARIES}
|
||||
)
|
||||
add_dependencies(subsurface_desktop_preferences subsurface_generated_ui)
|
||||
|
||||
@ -45,7 +45,7 @@ SOURCES += subsurface-mobile-main.cpp \
|
||||
core/fulltext.cpp \
|
||||
core/subsurfacestartup.cpp \
|
||||
core/pref.c \
|
||||
core/profile.c \
|
||||
core/profile.cpp \
|
||||
core/device.cpp \
|
||||
core/dive.cpp \
|
||||
core/divecomputer.c \
|
||||
@ -126,7 +126,6 @@ SOURCES += subsurface-mobile-main.cpp \
|
||||
core/subsurface-qt/divelistnotifier.cpp \
|
||||
backend-shared/exportfuncs.cpp \
|
||||
backend-shared/plannershared.cpp \
|
||||
backend-shared/roundrectitem.cpp \
|
||||
stats/statsvariables.cpp \
|
||||
stats/statsview.cpp \
|
||||
stats/barseries.cpp \
|
||||
@ -167,7 +166,6 @@ SOURCES += subsurface-mobile-main.cpp \
|
||||
qt-models/weightsysteminfomodel.cpp \
|
||||
qt-models/filterconstraintmodel.cpp \
|
||||
qt-models/filterpresetmodel.cpp \
|
||||
profile-widget/qmlprofile.cpp \
|
||||
profile-widget/divecartesianaxis.cpp \
|
||||
profile-widget/diveeventitem.cpp \
|
||||
profile-widget/divepercentageitem.cpp \
|
||||
@ -175,12 +173,14 @@ SOURCES += subsurface-mobile-main.cpp \
|
||||
profile-widget/profilescene.cpp \
|
||||
profile-widget/animationfunctions.cpp \
|
||||
profile-widget/divepixmapcache.cpp \
|
||||
profile-widget/divepixmapitem.cpp \
|
||||
profile-widget/divetooltipitem.cpp \
|
||||
profile-widget/tankitem.cpp \
|
||||
profile-widget/divelineitem.cpp \
|
||||
profile-widget/diverectitem.cpp \
|
||||
profile-widget/divetextitem.cpp
|
||||
profile-widget/pictureitem.cpp \
|
||||
profile-widget/tooltipitem.cpp \
|
||||
profile-widget/divetextitem.cpp \
|
||||
profile-widget/handleitem.cpp \
|
||||
profile-widget/profileview.cpp \
|
||||
profile-widget/ruleritem.cpp \
|
||||
qt-quick/chartitem.cpp \
|
||||
qt-quick/chartview.cpp
|
||||
|
||||
HEADERS += \
|
||||
commands/command_base.h \
|
||||
@ -282,7 +282,6 @@ HEADERS += \
|
||||
core/subsurface-qt/divelistnotifier.h \
|
||||
backend-shared/exportfuncs.h \
|
||||
backend-shared/plannershared.h \
|
||||
backend-shared/roundrectitem.h \
|
||||
stats/barseries.h \
|
||||
stats/boxseries.h \
|
||||
stats/chartitem.h \
|
||||
@ -327,20 +326,24 @@ HEADERS += \
|
||||
qt-models/weightsysteminfomodel.h \
|
||||
qt-models/filterconstraintmodel.h \
|
||||
qt-models/filterpresetmodel.h \
|
||||
profile-widget/qmlprofile.h \
|
||||
profile-widget/divepercentageitem.h \
|
||||
profile-widget/diveprofileitem.h \
|
||||
profile-widget/profilescene.h \
|
||||
profile-widget/diveeventitem.h \
|
||||
profile-widget/divetooltipitem.h \
|
||||
profile-widget/tankitem.h \
|
||||
profile-widget/pictureitem.h \
|
||||
profile-widget/tooltipitem.h \
|
||||
profile-widget/animationfunctions.h \
|
||||
profile-widget/divecartesianaxis.h \
|
||||
profile-widget/divelineitem.h \
|
||||
profile-widget/divepixmapcache.h \
|
||||
profile-widget/divepixmapitem.h \
|
||||
profile-widget/diverectitem.h \
|
||||
profile-widget/divetextitem.h
|
||||
profile-widget/divetextitem.h \
|
||||
profile-widget/handleitem.h \
|
||||
profile-widget/profileview.h \
|
||||
profile-widget/ruleritem.h \
|
||||
profile-widget/profiletranslations.h \
|
||||
qt-quick/chartitem.h \
|
||||
qt-quick/chartitemhelper.h \
|
||||
qt-quick/chartitem_ptr.h \
|
||||
qt-quick/chartview.h
|
||||
|
||||
RESOURCES += mobile-widgets/qml/mobile-resources.qrc \
|
||||
mobile-widgets/3rdparty/icons.qrc \
|
||||
|
||||
@ -5,8 +5,6 @@ set(BACKEND_SRCS
|
||||
exportfuncs.h
|
||||
plannershared.cpp
|
||||
plannershared.h
|
||||
roundrectitem.cpp
|
||||
roundrectitem.h
|
||||
)
|
||||
|
||||
add_library(subsurface_backend_shared STATIC ${BACKEND_SRCS})
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
#include "roundrectitem.h"
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
|
||||
RoundRectItem::RoundRectItem(double radius, QGraphicsItem *parent) : QGraphicsRectItem(parent),
|
||||
radius(radius)
|
||||
{
|
||||
}
|
||||
|
||||
RoundRectItem::RoundRectItem(double radius) : RoundRectItem(radius, nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void RoundRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
|
||||
{
|
||||
painter->save();
|
||||
painter->setClipRect(option->rect);
|
||||
painter->setPen(pen());
|
||||
painter->setBrush(brush());
|
||||
painter->drawRoundedRect(rect(), radius, radius, Qt::AbsoluteSize);
|
||||
painter->restore();
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef ROUNDRECTITEM_H
|
||||
#define ROUNDRECTITEM_H
|
||||
|
||||
#include <QGraphicsRectItem>
|
||||
|
||||
class RoundRectItem : public QGraphicsRectItem {
|
||||
public:
|
||||
RoundRectItem(double radius, QGraphicsItem *parent);
|
||||
RoundRectItem(double radius);
|
||||
private:
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
double radius;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -146,7 +146,7 @@ set(SUBSURFACE_CORE_LIB_SRCS
|
||||
plannernotes.c
|
||||
pref.h
|
||||
pref.c
|
||||
profile.c
|
||||
profile.cpp
|
||||
profile.h
|
||||
qt-gui.h
|
||||
qt-init.cpp
|
||||
|
||||
10
core/event.c
10
core/event.c
@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "event.h"
|
||||
#include "eventtype.h"
|
||||
#include "divecomputer.h"
|
||||
#include "subsurface-string.h"
|
||||
|
||||
#include <string.h>
|
||||
@ -116,3 +117,12 @@ extern enum event_severity get_event_severity(const struct event *ev)
|
||||
return EVENT_SEVERITY_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
extern bool has_individually_hidden_events(const struct divecomputer *dc)
|
||||
{
|
||||
for (const struct event *ev = dc->events; ev; ev = ev->next) {
|
||||
if (ev->hidden)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
|
||||
#include <libdivecomputer/parser.h>
|
||||
|
||||
struct divecomputer;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -55,6 +57,7 @@ extern struct event *create_event(unsigned int time, int type, int flags, int va
|
||||
extern struct event *clone_event_rename(const struct event *ev, const char *name);
|
||||
extern bool same_event(const struct event *a, const struct event *b);
|
||||
extern enum event_severity get_event_severity(const struct event *ev);
|
||||
extern bool has_individually_hidden_events(const struct divecomputer *dc);
|
||||
|
||||
/* Since C doesn't have parameter-based overloading, two versions of get_next_event. */
|
||||
extern const struct event *get_next_event(const struct event *event, const char *name);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "eventtype.h"
|
||||
#include "event.h"
|
||||
#include "divecomputer.h"
|
||||
#include "gettextfromc.h"
|
||||
#include "subsurface-string.h"
|
||||
|
||||
@ -55,11 +56,23 @@ extern "C" void hide_event_type(const struct event *ev)
|
||||
it->plot = false;
|
||||
}
|
||||
|
||||
extern "C" void show_all_event_types()
|
||||
static bool has_event_type(const divecomputer *dc, const event_type et)
|
||||
{
|
||||
for (event_type &e: event_types)
|
||||
for (const event *ev = dc->events; ev; ev = ev->next) {
|
||||
if (ev->name == et.name &&
|
||||
get_event_severity(ev) == et.severity)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" void show_all_event_types(const divecomputer *dc)
|
||||
{
|
||||
for (event_type &e: event_types) {
|
||||
if (!e.plot && has_event_type(dc, e))
|
||||
e.plot = true;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void show_event_type(int idx)
|
||||
{
|
||||
@ -68,17 +81,12 @@ extern "C" void show_event_type(int idx)
|
||||
event_types[idx].plot = true;
|
||||
}
|
||||
|
||||
extern "C" bool any_event_types_hidden()
|
||||
{
|
||||
return std::any_of(event_types.begin(), event_types.end(),
|
||||
[] (const event_type &e) { return !e.plot; });
|
||||
}
|
||||
|
||||
extern std::vector<int> hidden_event_types()
|
||||
extern std::vector<int> hidden_event_types(const divecomputer *dc)
|
||||
{
|
||||
std::vector<int> res;
|
||||
for (size_t i = 0; i < event_types.size(); ++i) {
|
||||
if (!event_types[i].plot)
|
||||
const event_type &e = event_types[i];
|
||||
if (!e.plot && has_event_type(dc, e))
|
||||
res.push_back(i);
|
||||
}
|
||||
return res;
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
#ifndef EVENTNAME_H
|
||||
#define EVENTNAME_H
|
||||
|
||||
struct divecomputer;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -11,9 +13,8 @@ extern void clear_event_types(void);
|
||||
extern void remember_event_type(const struct event *ev);
|
||||
extern bool is_event_type_hidden(const struct event *ev);
|
||||
extern void hide_event_type(const struct event *ev);
|
||||
extern void show_all_event_types();
|
||||
extern void show_all_event_types(const struct divecomputer *dc);
|
||||
extern void show_event_type(int idx);
|
||||
extern bool any_event_types_hidden();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
@ -22,7 +23,7 @@ extern bool any_event_types_hidden();
|
||||
|
||||
#include <vector>
|
||||
#include <QString>
|
||||
extern std::vector<int> hidden_event_types();
|
||||
extern std::vector<int> hidden_event_types(const divecomputer *dc);
|
||||
QString event_type_name(const event *ev);
|
||||
QString event_type_name(int idx);
|
||||
|
||||
|
||||
@ -117,7 +117,7 @@ int pscr_o2(const double amb_pressure, struct gasmix mix)
|
||||
* *pressures = structure for communicating o2 sensor values from and gas pressures to the calling function.
|
||||
* *mix = structure containing cylinder gas mixture information.
|
||||
* divemode = the dive mode pertaining to this point in the dive profile.
|
||||
* This function called by: calculate_gas_information_new() in profile.c; add_segment() in deco.c.
|
||||
* This function called by: calculate_gas_information_new() in profile.cpp; add_segment() in deco.c.
|
||||
*/
|
||||
void fill_pressures(struct gas_pressures *pressures, const double amb_pressure, struct gasmix mix, double po2, enum divemode_t divemode)
|
||||
{
|
||||
|
||||
10
core/gas.h
10
core/gas.h
@ -18,6 +18,16 @@ enum gas_component { N2, HE, O2 };
|
||||
struct gasmix {
|
||||
fraction_t o2;
|
||||
fraction_t he;
|
||||
#ifdef __cplusplus
|
||||
bool operator==(const gasmix &g2) const
|
||||
{
|
||||
return o2.permille == g2.o2.permille && he.permille == g2.he.permille;
|
||||
}
|
||||
bool operator!=(const gasmix &g2) const
|
||||
{
|
||||
return !(*this == g2);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
static const struct gasmix gasmix_invalid = { { -1 }, { -1 } };
|
||||
static const struct gasmix gasmix_air = { { 0 }, { 0 } };
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/* gaspressures.c
|
||||
* ---------------
|
||||
* This file contains the routines to calculate the gas pressures in the cylinders.
|
||||
* The functions below support the code in profile.c.
|
||||
* The functions below support the code in profile.cpp.
|
||||
* The high-level function is populate_pressure_information(), called by function
|
||||
* create_plot_info_new() in profile.c. The other functions below are, in turn,
|
||||
* called by populate_pressure_information(). The calling sequence is as follows:
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* profile.c */
|
||||
/* profile.cpp */
|
||||
/* creates all the necessary data for drawing the dive profile
|
||||
*/
|
||||
#include "ssrf.h"
|
||||
@ -8,6 +8,7 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <vector>
|
||||
|
||||
#include "dive.h"
|
||||
#include "divelist.h"
|
||||
@ -22,7 +23,6 @@
|
||||
#include "errorhelper.h"
|
||||
#include "libdivecomputer/parser.h"
|
||||
#include "libdivecomputer/version.h"
|
||||
#include "membuffer.h"
|
||||
#include "qthelper.h"
|
||||
#include "format.h"
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
#define MAX_PROFILE_DECO 7200
|
||||
|
||||
extern int ascent_velocity(int depth, int avg_depth, int bottom_time);
|
||||
extern "C" int ascent_velocity(int depth, int avg_depth, int bottom_time);
|
||||
|
||||
#ifdef DEBUG_PI
|
||||
/* debugging tool - not normally used */
|
||||
@ -57,8 +57,18 @@ static void dump_pi(struct plot_info *pi)
|
||||
}
|
||||
#endif
|
||||
|
||||
#define ROUND_UP(x, y) ((((x) + (y) - 1) / (y)) * (y))
|
||||
#define DIV_UP(x, y) (((x) + (y) - 1) / (y))
|
||||
|
||||
template<typename T>
|
||||
static T round_up(T x, T y)
|
||||
{
|
||||
return ((x + y - 1) / y) * y;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static T div_up(T x, T y)
|
||||
{
|
||||
return (x + y - 1) / y;
|
||||
}
|
||||
|
||||
/*
|
||||
* When showing dive profiles, we scale things to the
|
||||
@ -66,6 +76,7 @@ static void dump_pi(struct plot_info *pi)
|
||||
* 30 minutes or 90 ft, just so that small dives show
|
||||
* up as such unless zoom is enabled.
|
||||
*/
|
||||
extern "C"
|
||||
int get_maxtime(const struct plot_info *pi)
|
||||
{
|
||||
int seconds = pi->maxtime;
|
||||
@ -74,6 +85,7 @@ int get_maxtime(const struct plot_info *pi)
|
||||
}
|
||||
|
||||
/* get the maximum depth to which we want to plot */
|
||||
extern "C"
|
||||
int get_maxdepth(const struct plot_info *pi)
|
||||
{
|
||||
/* 3m to spare */
|
||||
@ -185,6 +197,7 @@ static void analyze_plot_info(struct plot_info *pi)
|
||||
* Some dive computers give cylinder indices, some
|
||||
* give just the gas mix.
|
||||
*/
|
||||
extern "C"
|
||||
int get_cylinder_index(const struct dive *dive, const struct event *ev)
|
||||
{
|
||||
int best;
|
||||
@ -206,6 +219,7 @@ int get_cylinder_index(const struct dive *dive, const struct event *ev)
|
||||
return best < 0 ? 0 : best;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
struct event *get_next_event_mutable(struct event *event, const char *name)
|
||||
{
|
||||
if (!name || !*name)
|
||||
@ -218,6 +232,7 @@ struct event *get_next_event_mutable(struct event *event, const char *name)
|
||||
return event;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
const struct event *get_next_event(const struct event *event, const char *name)
|
||||
{
|
||||
return get_next_event_mutable((struct event *)event, name);
|
||||
@ -246,9 +261,8 @@ static int set_setpoint(struct plot_info *pi, int i, int setpoint, int end)
|
||||
return i;
|
||||
}
|
||||
|
||||
static void check_setpoint_events(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi)
|
||||
static void check_setpoint_events(const struct dive *, const struct divecomputer *dc, struct plot_info *pi)
|
||||
{
|
||||
UNUSED(dive);
|
||||
int i = 0;
|
||||
pressure_t setpoint;
|
||||
setpoint.mbar = 0;
|
||||
@ -381,6 +395,7 @@ static void insert_entry(struct plot_info *pi, int idx, int time, int depth, int
|
||||
entry->bearing = -1;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
void free_plot_info_data(struct plot_info *pi)
|
||||
{
|
||||
free(pi->entry);
|
||||
@ -390,7 +405,6 @@ void free_plot_info_data(struct plot_info *pi)
|
||||
|
||||
static void populate_plot_entries(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi)
|
||||
{
|
||||
UNUSED(dive);
|
||||
int idx, maxtime, nr, i;
|
||||
int lastdepth, lasttime, lasttemp = 0;
|
||||
struct plot_data *plot_data;
|
||||
@ -407,10 +421,10 @@ static void populate_plot_entries(const struct dive *dive, const struct divecomp
|
||||
* past "maxtime" in the original sample data)
|
||||
*/
|
||||
nr = dc->samples + 6 + maxtime / 10 + count_events(dc);
|
||||
plot_data = calloc(nr, sizeof(struct plot_data));
|
||||
plot_data = (struct plot_data *)calloc(nr, sizeof(struct plot_data));
|
||||
pi->entry = plot_data;
|
||||
pi->nr_cylinders = dive->cylinders.nr;
|
||||
pi->pressures = calloc(nr * (size_t)pi->nr_cylinders, sizeof(struct plot_pressure_data));
|
||||
pi->pressures = (plot_pressure_data *)calloc(nr * (size_t)pi->nr_cylinders, sizeof(struct plot_pressure_data));
|
||||
if (!plot_data)
|
||||
return;
|
||||
pi->nr = nr;
|
||||
@ -532,7 +546,7 @@ static void populate_plot_entries(const struct dive *dive, const struct divecomp
|
||||
*
|
||||
* Everything in between has a cylinder pressure for at least some of the cylinders.
|
||||
*/
|
||||
static int sac_between(const struct dive *dive, struct plot_info *pi, int first, int last, const bool gases[])
|
||||
static int sac_between(const struct dive *dive, struct plot_info *pi, int first, int last, const std::vector<char> &gases)
|
||||
{
|
||||
int i, airuse;
|
||||
double pressuretime;
|
||||
@ -580,7 +594,7 @@ static int sac_between(const struct dive *dive, struct plot_info *pi, int first,
|
||||
}
|
||||
|
||||
/* Is there pressure data for all gases? */
|
||||
static bool all_pressures(struct plot_info *pi, int idx, const bool gases[])
|
||||
static bool all_pressures(struct plot_info *pi, int idx, const std::vector<char> &gases)
|
||||
{
|
||||
int i;
|
||||
|
||||
@ -593,7 +607,7 @@ static bool all_pressures(struct plot_info *pi, int idx, const bool gases[])
|
||||
}
|
||||
|
||||
/* Which of the set of gases have pressure data? Returns false if none of them. */
|
||||
static bool filter_pressures(struct plot_info *pi, int idx, const bool gases_in[], bool gases_out[])
|
||||
static bool filter_pressures(struct plot_info *pi, int idx, const std::vector<char> &gases_in, std::vector<char> &gases_out)
|
||||
{
|
||||
int i;
|
||||
bool has_pressure = false;
|
||||
@ -612,7 +626,7 @@ static bool filter_pressures(struct plot_info *pi, int idx, const bool gases_in[
|
||||
* an array of gases, the caller passes in scratch memory in the last
|
||||
* argument.
|
||||
*/
|
||||
static void fill_sac(const struct dive *dive, struct plot_info *pi, int idx, const bool gases_in[], bool gases[])
|
||||
static void fill_sac(const struct dive *dive, struct plot_info *pi, int idx, const std::vector<char> &gases_in, std::vector<char> &gases)
|
||||
{
|
||||
struct plot_data *entry = pi->entry + idx;
|
||||
int first, last;
|
||||
@ -670,7 +684,7 @@ static void fill_sac(const struct dive *dive, struct plot_info *pi, int idx, con
|
||||
/*
|
||||
* Create a bitmap of cylinders that match our current gasmix
|
||||
*/
|
||||
static void matching_gases(const struct dive *dive, struct gasmix gasmix, bool gases[])
|
||||
static void matching_gases(const struct dive *dive, struct gasmix gasmix, std::vector<char> &gases)
|
||||
{
|
||||
int i;
|
||||
|
||||
@ -682,13 +696,14 @@ static void calculate_sac(const struct dive *dive, const struct divecomputer *dc
|
||||
{
|
||||
struct gasmix gasmix = gasmix_invalid;
|
||||
const struct event *ev = NULL;
|
||||
bool *gases, *gases_scratch;
|
||||
|
||||
gases = calloc(pi->nr_cylinders, sizeof(*gases));
|
||||
// Use std::vector<char> instead of std::vector<bool> to avoid silly "optimization"
|
||||
// by the standard library.
|
||||
std::vector<char> gases(pi->nr_cylinders, 0);
|
||||
|
||||
/* This might be premature optimization, but let's allocate the gas array for
|
||||
* the fill_sac function only once an not once per sample */
|
||||
gases_scratch = malloc(pi->nr_cylinders * sizeof(*gases));
|
||||
std::vector<char>gases_scratch(pi->nr_cylinders, 0);
|
||||
|
||||
for (int i = 0; i < pi->nr; i++) {
|
||||
struct plot_data *entry = pi->entry + i;
|
||||
@ -700,18 +715,17 @@ static void calculate_sac(const struct dive *dive, const struct divecomputer *dc
|
||||
|
||||
fill_sac(dive, pi, i, gases, gases_scratch);
|
||||
}
|
||||
|
||||
free(gases);
|
||||
free(gases_scratch);
|
||||
}
|
||||
|
||||
static void populate_secondary_sensor_data(const struct divecomputer *dc, struct plot_info *pi)
|
||||
{
|
||||
int *seen = calloc(pi->nr_cylinders, sizeof(*seen));
|
||||
for (int idx = 0; idx < pi->nr; ++idx)
|
||||
for (int c = 0; c < pi->nr_cylinders; ++c)
|
||||
std::vector<int> seen(pi->nr_cylinders, 0);
|
||||
for (int idx = 0; idx < pi->nr; ++idx) {
|
||||
for (int c = 0; c < pi->nr_cylinders; ++c) {
|
||||
if (get_plot_pressure_data(pi, idx, SENSOR_PR, c))
|
||||
++seen[c]; // Count instances so we can differentiate a real sensor from just start and end pressure
|
||||
}
|
||||
}
|
||||
int idx = 0;
|
||||
/* We should try to see if it has interesting pressure data here */
|
||||
for (int i = 0; i < dc->samples && idx < pi->nr; i++) {
|
||||
@ -727,7 +741,6 @@ static void populate_secondary_sensor_data(const struct divecomputer *dc, struct
|
||||
if (sample->sensor[s] != NO_SENSOR && seen[sample->sensor[s]] < 3 && sample->pressure[s].mbar)
|
||||
set_plot_pressure_data(pi, idx, SENSOR_PR, sample->sensor[s], sample->pressure[s].mbar);
|
||||
}
|
||||
free(seen);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -754,16 +767,11 @@ static void setup_gas_sensor_pressure(const struct dive *dive, const struct dive
|
||||
|
||||
/* FIXME: The planner uses a dummy one-past-end cylinder for surface air! */
|
||||
int num_cyl = pi->nr_cylinders + 1;
|
||||
int *seen = malloc(num_cyl * sizeof(*seen));
|
||||
int *first = malloc(num_cyl * sizeof(*first));
|
||||
int *last = malloc(num_cyl * sizeof(*last));
|
||||
std::vector<int> seen (num_cyl, 0);
|
||||
std::vector<int> first(num_cyl, 0);
|
||||
std::vector<int> last(num_cyl, INT_MAX);
|
||||
const struct divecomputer *secondary;
|
||||
|
||||
for (i = 0; i < num_cyl; i++) {
|
||||
seen[i] = 0;
|
||||
first[i] = 0;
|
||||
last[i] = INT_MAX;
|
||||
}
|
||||
prev = explicit_first_cylinder(dive, dc);
|
||||
seen[prev] = 1;
|
||||
|
||||
@ -841,10 +849,6 @@ static void setup_gas_sensor_pressure(const struct dive *dive, const struct dive
|
||||
continue;
|
||||
populate_secondary_sensor_data(secondary, pi);
|
||||
} while ((secondary = secondary->next) != NULL);
|
||||
|
||||
free(seen);
|
||||
free(first);
|
||||
free(last);
|
||||
}
|
||||
|
||||
/* calculate DECO STOP / TTS / NDL */
|
||||
@ -859,7 +863,7 @@ static void calculate_ndl_tts(struct deco_state *ds, const struct dive *dive, st
|
||||
const int time_stepsize = 60;
|
||||
const int deco_stepsize = M_OR_FT(3, 10);
|
||||
/* at what depth is the current deco-step? */
|
||||
int next_stop = ROUND_UP(deco_allowed_depth(
|
||||
int next_stop = round_up(deco_allowed_depth(
|
||||
tissue_tolerance_calc(ds, dive, depth_to_bar(entry->depth, dive), in_planner),
|
||||
surface_pressure, dive, 1), deco_stepsize);
|
||||
int ascent_depth = entry->depth;
|
||||
@ -894,7 +898,7 @@ static void calculate_ndl_tts(struct deco_state *ds, const struct dive *dive, st
|
||||
for (; ascent_depth > next_stop; ascent_depth -= ascent_s_per_step * ascent_velocity(ascent_depth, entry->running_sum / entry->sec, 0), entry->tts_calc += ascent_s_per_step) {
|
||||
add_segment(ds, depth_to_bar(ascent_depth, dive),
|
||||
gasmix, ascent_s_per_step, entry->o2pressure.mbar, divemode, prefs.decosac, in_planner);
|
||||
next_stop = ROUND_UP(deco_allowed_depth(tissue_tolerance_calc(ds, dive, depth_to_bar(ascent_depth, dive), in_planner),
|
||||
next_stop = round_up(deco_allowed_depth(tissue_tolerance_calc(ds, dive, depth_to_bar(ascent_depth, dive), in_planner),
|
||||
surface_pressure, dive, 1), deco_stepsize);
|
||||
}
|
||||
ascent_depth = next_stop;
|
||||
@ -1058,7 +1062,7 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_
|
||||
entry->depth < max_ceiling - 100 && entry->sec > 0) {
|
||||
struct dive *non_const_dive = (struct dive *)dive; // cast away const!
|
||||
add_event(&non_const_dive->dc, entry->sec, SAMPLE_EVENT_CEILING, -1, max_ceiling / 1000,
|
||||
translate("gettextFromC", "planned waypoint above ceiling"));
|
||||
qPrintable(gettextFromC::tr("planned waypoint above ceiling")));
|
||||
pi->waypoint_above_ceiling = true;
|
||||
}
|
||||
|
||||
@ -1101,7 +1105,7 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_
|
||||
* at end of whole minute after clearing ceiling. The deepest ceiling when planning a dive
|
||||
* comes typically 10-60s after the end of the bottom time, so add 20s to the calculated
|
||||
* deco time. */
|
||||
ds->deco_time = ROUND_UP(time_clear_ceiling - time_deep_ceiling + 20, 60) + 20;
|
||||
ds->deco_time = round_up(time_clear_ceiling - time_deep_ceiling + 20, 60) + 20;
|
||||
vpmb_next_gradient(ds, ds->deco_time, surface_pressure / 1000.0, in_planner);
|
||||
final_tts = 0;
|
||||
last_ndl_tts_calc_time = 0;
|
||||
@ -1308,6 +1312,7 @@ static void debug_print_profiledata(struct plot_info *pi)
|
||||
/*
|
||||
* Initialize a plot_info structure to all-zeroes
|
||||
*/
|
||||
extern "C"
|
||||
void init_plot_info(struct plot_info *pi)
|
||||
{
|
||||
memset(pi, 0, sizeof(*pi));
|
||||
@ -1323,6 +1328,7 @@ void init_plot_info(struct plot_info *pi)
|
||||
* The old data will be freed. Before the first call, the plot
|
||||
* info must be initialized with init_plot_info().
|
||||
*/
|
||||
extern "C"
|
||||
void create_plot_info_new(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi, const struct deco_state *planner_ds)
|
||||
{
|
||||
int o2, he, o2max;
|
||||
@ -1333,14 +1339,14 @@ void create_plot_info_new(const struct dive *dive, const struct divecomputer *dc
|
||||
calculate_max_limits_new(dive, dc, pi, in_planner);
|
||||
get_dive_gas(dive, &o2, &he, &o2max);
|
||||
if (dc->divemode == FREEDIVE) {
|
||||
pi->dive_type = FREEDIVING;
|
||||
pi->dive_type = plot_info::FREEDIVING;
|
||||
} else if (he > 0) {
|
||||
pi->dive_type = TRIMIX;
|
||||
pi->dive_type = plot_info::TRIMIX;
|
||||
} else {
|
||||
if (o2)
|
||||
pi->dive_type = NITROX;
|
||||
pi->dive_type = plot_info::NITROX;
|
||||
else
|
||||
pi->dive_type = AIR;
|
||||
pi->dive_type = plot_info::AIR;
|
||||
}
|
||||
|
||||
populate_plot_entries(dive, dc, pi);
|
||||
@ -1364,7 +1370,7 @@ void create_plot_info_new(const struct dive *dive, const struct divecomputer *dc
|
||||
analyze_plot_info(pi);
|
||||
}
|
||||
|
||||
static void plot_string(const struct dive *d, const struct plot_info *pi, int idx, struct membuffer *b)
|
||||
static std::vector<QString> plot_string(const struct dive *d, const struct plot_info *pi, int idx)
|
||||
{
|
||||
int pressurevalue, mod, ead, end, eadd;
|
||||
const char *depth_unit, *pressure_unit, *temp_unit, *vertical_speed_unit;
|
||||
@ -1372,65 +1378,69 @@ static void plot_string(const struct dive *d, const struct plot_info *pi, int id
|
||||
int decimals, cyl;
|
||||
const char *unit;
|
||||
const struct plot_data *entry = pi->entry + idx;
|
||||
std::vector<QString> res;
|
||||
|
||||
depthvalue = get_depth_units(entry->depth, NULL, &depth_unit);
|
||||
put_format_loc(b, translate("gettextFromC", "@: %d:%02d\nD: %.1f%s\n"), FRACTION(entry->sec, 60), depthvalue, depth_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "@: %d:%02d"), FRACTION(entry->sec, 60), depthvalue));
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "D: %.1f%s"), depth_unit));
|
||||
for (cyl = 0; cyl < pi->nr_cylinders; cyl++) {
|
||||
int mbar = get_plot_pressure(pi, idx, cyl);
|
||||
if (!mbar)
|
||||
continue;
|
||||
struct gasmix mix = get_cylinder(d, cyl)->gasmix;
|
||||
pressurevalue = get_pressure_units(mbar, &pressure_unit);
|
||||
put_format_loc(b, translate("gettextFromC", "P: %d%s (%s)\n"), pressurevalue, pressure_unit, gasname(mix));
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "P: %d%s (%s)"), pressurevalue, pressure_unit, gasname(mix)));
|
||||
}
|
||||
if (entry->temperature) {
|
||||
tempvalue = get_temp_units(entry->temperature, &temp_unit);
|
||||
put_format_loc(b, translate("gettextFromC", "T: %.1f%s\n"), tempvalue, temp_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "T: %.1f%s"), tempvalue, temp_unit));
|
||||
}
|
||||
speedvalue = get_vertical_speed_units(abs(entry->speed), NULL, &vertical_speed_unit);
|
||||
/* Ascending speeds are positive, descending are negative */
|
||||
if (entry->speed > 0)
|
||||
speedvalue *= -1;
|
||||
put_format_loc(b, translate("gettextFromC", "V: %.1f%s\n"), speedvalue, vertical_speed_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "V: %.1f%s"), speedvalue, vertical_speed_unit));
|
||||
sacvalue = get_volume_units(entry->sac, &decimals, &unit);
|
||||
if (entry->sac && prefs.show_sac)
|
||||
put_format_loc(b, translate("gettextFromC", "SAC: %.*f%s/min\n"), decimals, sacvalue, unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "SAC: %.*f%s/min"), decimals, sacvalue, unit));
|
||||
if (entry->cns)
|
||||
put_format_loc(b, translate("gettextFromC", "CNS: %u%%\n"), entry->cns);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "CNS: %u%%"), entry->cns));
|
||||
if (prefs.pp_graphs.po2 && entry->pressures.o2 > 0) {
|
||||
put_format_loc(b, translate("gettextFromC", "pO₂: %.2fbar\n"), entry->pressures.o2);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "pO₂: %.2fbar"), entry->pressures.o2));
|
||||
if (entry->scr_OC_pO2.mbar)
|
||||
put_format_loc(b, translate("gettextFromC", "SCR ΔpO₂: %.2fbar\n"), entry->scr_OC_pO2.mbar/1000.0 - entry->pressures.o2);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "SCR ΔpO₂: %.2fbar"), entry->scr_OC_pO2.mbar/1000.0 - entry->pressures.o2));
|
||||
}
|
||||
if (prefs.pp_graphs.pn2 && entry->pressures.n2 > 0)
|
||||
put_format_loc(b, translate("gettextFromC", "pN₂: %.2fbar\n"), entry->pressures.n2);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "pN₂: %.2fbar"), entry->pressures.n2));
|
||||
if (prefs.pp_graphs.phe && entry->pressures.he > 0)
|
||||
put_format_loc(b, translate("gettextFromC", "pHe: %.2fbar\n"), entry->pressures.he);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "pHe: %.2fbar"), entry->pressures.he));
|
||||
if (prefs.mod && entry->mod > 0) {
|
||||
mod = lrint(get_depth_units(entry->mod, NULL, &depth_unit));
|
||||
put_format_loc(b, translate("gettextFromC", "MOD: %d%s\n"), mod, depth_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "MOD: %d%s"), mod, depth_unit));
|
||||
}
|
||||
eadd = lrint(get_depth_units(entry->eadd, NULL, &depth_unit));
|
||||
|
||||
if (prefs.ead) {
|
||||
switch (pi->dive_type) {
|
||||
case NITROX:
|
||||
case plot_info::NITROX:
|
||||
if (entry->ead > 0) {
|
||||
ead = lrint(get_depth_units(entry->ead, NULL, &depth_unit));
|
||||
put_format_loc(b, translate("gettextFromC", "EAD: %d%s\nEADD: %d%s / %.1fg/ℓ\n"), ead, depth_unit, eadd, depth_unit, entry->density);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "EAD: %d%s"), ead, depth_unit));
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "EADD: %d%s / %.1fg/ℓ"), eadd, depth_unit, entry->density));
|
||||
break;
|
||||
}
|
||||
case TRIMIX:
|
||||
case plot_info::TRIMIX:
|
||||
if (entry->end > 0) {
|
||||
end = lrint(get_depth_units(entry->end, NULL, &depth_unit));
|
||||
put_format_loc(b, translate("gettextFromC", "END: %d%s\nEADD: %d%s / %.1fg/ℓ\n"), end, depth_unit, eadd, depth_unit, entry->density);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "END: %d%s"), end, depth_unit));
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "EADD: %d%s / %.1fg/ℓ"), eadd, depth_unit, entry->density));
|
||||
break;
|
||||
}
|
||||
case AIR:
|
||||
case plot_info::AIR:
|
||||
if (entry->density > 0) {
|
||||
put_format_loc(b, translate("gettextFromC", "Density: %.1fg/ℓ\n"), entry->density);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "Density: %.1fg/ℓ"), entry->density));
|
||||
}
|
||||
case FREEDIVING:
|
||||
case plot_info::FREEDIVING:
|
||||
/* nothing */
|
||||
break;
|
||||
}
|
||||
@ -1440,151 +1450,143 @@ static void plot_string(const struct dive *d, const struct plot_info *pi, int id
|
||||
if (entry->ndl > 0) {
|
||||
/* this is a safety stop as we still have ndl */
|
||||
if (entry->stoptime)
|
||||
put_format_loc(b, translate("gettextFromC", "Safety stop: %umin @ %.0f%s\n"), DIV_UP(entry->stoptime, 60),
|
||||
depthvalue, depth_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "Safety stop: %umin @ %.0f%s"), div_up(entry->stoptime, 60),
|
||||
depthvalue, depth_unit));
|
||||
else
|
||||
put_format_loc(b, translate("gettextFromC", "Safety stop: unknown time @ %.0f%s\n"),
|
||||
depthvalue, depth_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "Safety stop: unknown time @ %.0f%s"),
|
||||
depthvalue, depth_unit));
|
||||
} else {
|
||||
/* actual deco stop */
|
||||
if (entry->stoptime)
|
||||
put_format_loc(b, translate("gettextFromC", "Deco: %umin @ %.0f%s\n"), DIV_UP(entry->stoptime, 60),
|
||||
depthvalue, depth_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "Deco: %umin @ %.0f%s"), div_up(entry->stoptime, 60),
|
||||
depthvalue, depth_unit));
|
||||
else
|
||||
put_format_loc(b, translate("gettextFromC", "Deco: unknown time @ %.0f%s\n"),
|
||||
depthvalue, depth_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "Deco: unknown time @ %.0f%s"),
|
||||
depthvalue, depth_unit));
|
||||
}
|
||||
} else if (entry->in_deco) {
|
||||
put_string(b, translate("gettextFromC", "In deco\n"));
|
||||
res.push_back(translate("gettextFromC", "In deco"));
|
||||
} else if (entry->ndl >= 0) {
|
||||
put_format_loc(b, translate("gettextFromC", "NDL: %umin\n"), DIV_UP(entry->ndl, 60));
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "NDL: %umin"), div_up(entry->ndl, 60)));
|
||||
}
|
||||
if (entry->tts)
|
||||
put_format_loc(b, translate("gettextFromC", "TTS: %umin\n"), DIV_UP(entry->tts, 60));
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "TTS: %umin"), div_up(entry->tts, 60)));
|
||||
if (entry->stopdepth_calc && entry->stoptime_calc) {
|
||||
depthvalue = get_depth_units(entry->stopdepth_calc, NULL, &depth_unit);
|
||||
put_format_loc(b, translate("gettextFromC", "Deco: %umin @ %.0f%s (calc)\n"), DIV_UP(entry->stoptime_calc, 60),
|
||||
depthvalue, depth_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "Deco: %umin @ %.0f%s (calc)"), div_up(entry->stoptime_calc, 60),
|
||||
depthvalue, depth_unit));
|
||||
} else if (entry->in_deco_calc) {
|
||||
/* This means that we have no NDL left,
|
||||
* and we have no deco stop,
|
||||
* so if we just accend to the surface slowly
|
||||
* (ascent_mm_per_step / ascent_s_per_step)
|
||||
* everything will be ok. */
|
||||
put_string(b, translate("gettextFromC", "In deco (calc)\n"));
|
||||
res.push_back(translate("gettextFromC", "In deco (calc)"));
|
||||
} else if (prefs.calcndltts && entry->ndl_calc != 0) {
|
||||
if(entry->ndl_calc < MAX_PROFILE_DECO)
|
||||
put_format_loc(b, translate("gettextFromC", "NDL: %umin (calc)\n"), DIV_UP(entry->ndl_calc, 60));
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "NDL: %umin (calc)"), div_up(entry->ndl_calc, 60)));
|
||||
else
|
||||
put_string(b, translate("gettextFromC", "NDL: >2h (calc)\n"));
|
||||
res.push_back(translate("gettextFromC", "NDL: >2h (calc)"));
|
||||
}
|
||||
if (entry->tts_calc) {
|
||||
if (entry->tts_calc < MAX_PROFILE_DECO)
|
||||
put_format_loc(b, translate("gettextFromC", "TTS: %umin (calc)\n"), DIV_UP(entry->tts_calc, 60));
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "TTS: %umin (calc)"), div_up(entry->tts_calc, 60)));
|
||||
else
|
||||
put_string(b, translate("gettextFromC", "TTS: >2h (calc)\n"));
|
||||
res.push_back(translate("gettextFromC", "TTS: >2h (calc)"));
|
||||
}
|
||||
if (entry->rbt)
|
||||
put_format_loc(b, translate("gettextFromC", "RBT: %umin\n"), DIV_UP(entry->rbt, 60));
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "RBT: %umin"), div_up(entry->rbt, 60)));
|
||||
if (prefs.decoinfo) {
|
||||
if (entry->current_gf > 0.0)
|
||||
put_format(b, translate("gettextFromC", "GF %d%%\n"), (int)(100.0 * entry->current_gf));
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "GF %d%%"), (int)(100.0 * entry->current_gf)));
|
||||
if (entry->surface_gf > 0.0)
|
||||
put_format(b, translate("gettextFromC", "Surface GF %.0f%%\n"), entry->surface_gf);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "Surface GF %.0f%%"), entry->surface_gf));
|
||||
if (entry->ceiling) {
|
||||
depthvalue = get_depth_units(entry->ceiling, NULL, &depth_unit);
|
||||
put_format_loc(b, translate("gettextFromC", "Calculated ceiling %.1f%s\n"), depthvalue, depth_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "Calculated ceiling %.1f%s"), depthvalue, depth_unit));
|
||||
if (prefs.calcalltissues) {
|
||||
int k;
|
||||
for (k = 0; k < 16; k++) {
|
||||
if (entry->ceilings[k]) {
|
||||
depthvalue = get_depth_units(entry->ceilings[k], NULL, &depth_unit);
|
||||
put_format_loc(b, translate("gettextFromC", "Tissue %.0fmin: %.1f%s\n"), buehlmann_N2_t_halflife[k], depthvalue, depth_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "Tissue %.0fmin: %.1f%s"), buehlmann_N2_t_halflife[k], depthvalue, depth_unit));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entry->icd_warning)
|
||||
put_format(b, "%s", translate("gettextFromC", "ICD in leading tissue\n"));
|
||||
res.push_back(translate("gettextFromC", "ICD in leading tissue"));
|
||||
if (entry->heartbeat && prefs.hrgraph)
|
||||
put_format_loc(b, translate("gettextFromC", "heart rate: %d\n"), entry->heartbeat);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "heart rate: %d"), entry->heartbeat));
|
||||
if (entry->bearing >= 0)
|
||||
put_format_loc(b, translate("gettextFromC", "bearing: %d\n"), entry->bearing);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "bearing: %d"), entry->bearing));
|
||||
if (entry->running_sum) {
|
||||
depthvalue = get_depth_units(entry->running_sum / entry->sec, NULL, &depth_unit);
|
||||
put_format_loc(b, translate("gettextFromC", "mean depth to here %.1f%s\n"), depthvalue, depth_unit);
|
||||
res.push_back(qasprintf_loc(translate("gettextFromC", "mean depth to here %.1f%s"), depthvalue, depth_unit));
|
||||
}
|
||||
|
||||
strip_mb(b);
|
||||
return res;
|
||||
}
|
||||
|
||||
int get_plot_details_new(const struct dive *d, const struct plot_info *pi, int time, struct membuffer *mb)
|
||||
std::pair<int, std::vector<QString>> get_plot_details_new(const struct dive *d, const struct plot_info *pi, int time)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* The two first and the two last plot entries do not have useful data */
|
||||
if (pi->nr <= 4)
|
||||
return 0;
|
||||
for (i = 2; i < pi->nr - 3; i++) {
|
||||
if (pi->entry[i].sec >= time)
|
||||
break;
|
||||
}
|
||||
plot_string(d, pi, i, mb);
|
||||
return i;
|
||||
return { 0, {} };
|
||||
|
||||
// binary search for sample index
|
||||
auto it = std::lower_bound(pi->entry + 2, pi->entry + pi->nr - 3, time,
|
||||
[] (const plot_data &d, int time)
|
||||
{ return d.sec < time; });
|
||||
int idx = it - pi->entry;
|
||||
|
||||
auto strings = plot_string(d, pi, idx);
|
||||
return std::make_pair(idx, strings);
|
||||
}
|
||||
|
||||
/* Compare two plot_data entries and writes the results into a string */
|
||||
void compare_samples(const struct dive *d, const struct plot_info *pi, int idx1, int idx2, char *buf, int bufsize, bool sum)
|
||||
static QString space = QStringLiteral(" ");
|
||||
|
||||
/* Compare two plot_data entries and writes the results into a set of strings */
|
||||
std::vector<QString> compare_samples(const struct dive *d, const struct plot_info *pi, int idx1, int idx2, bool sum)
|
||||
{
|
||||
struct plot_data *start, *stop, *data;
|
||||
const char *depth_unit, *pressure_unit, *vertical_speed_unit;
|
||||
char *buf2 = malloc(bufsize);
|
||||
int avg_speed, max_asc_speed, max_desc_speed;
|
||||
int delta_depth, avg_depth, max_depth, min_depth;
|
||||
int pressurevalue;
|
||||
int last_sec, delta_time;
|
||||
|
||||
double depthvalue, speedvalue;
|
||||
|
||||
if (bufsize > 0)
|
||||
buf[0] = '\0';
|
||||
if (idx1 < 0 || idx2 < 0) {
|
||||
free(buf2);
|
||||
return;
|
||||
}
|
||||
std::vector<QString> res;
|
||||
if (idx1 < 0 || idx2 < 0)
|
||||
return res;
|
||||
|
||||
if (pi->entry[idx1].sec > pi->entry[idx2].sec) {
|
||||
int tmp = idx2;
|
||||
idx2 = idx1;
|
||||
idx1 = tmp;
|
||||
} else if (pi->entry[idx1].sec == pi->entry[idx2].sec) {
|
||||
free(buf2);
|
||||
return;
|
||||
return res;
|
||||
}
|
||||
start = pi->entry + idx1;
|
||||
stop = pi->entry + idx2;
|
||||
struct plot_data *start = pi->entry + idx1;
|
||||
struct plot_data *stop = pi->entry + idx2;
|
||||
|
||||
avg_speed = 0;
|
||||
max_asc_speed = 0;
|
||||
max_desc_speed = 0;
|
||||
int avg_speed = 0;
|
||||
int max_asc_speed = 0;
|
||||
int max_desc_speed = 0;
|
||||
|
||||
delta_depth = abs(start->depth - stop->depth);
|
||||
delta_time = abs(start->sec - stop->sec);
|
||||
avg_depth = 0;
|
||||
max_depth = 0;
|
||||
min_depth = INT_MAX;
|
||||
int delta_depth = abs(start->depth - stop->depth);
|
||||
int delta_time = abs(start->sec - stop->sec);
|
||||
int avg_depth = 0;
|
||||
int max_depth = 0;
|
||||
int min_depth = INT_MAX;
|
||||
|
||||
last_sec = start->sec;
|
||||
int last_sec = start->sec;
|
||||
|
||||
volume_t cylinder_volume = { .mliter = 0, };
|
||||
int *start_pressures = calloc((size_t)pi->nr_cylinders, sizeof(int));
|
||||
int *last_pressures = calloc((size_t)pi->nr_cylinders, sizeof(int));
|
||||
int *bar_used = calloc((size_t)pi->nr_cylinders, sizeof(int));
|
||||
int *volumes_used = calloc((size_t)pi->nr_cylinders, sizeof(int));
|
||||
bool *cylinder_is_used = calloc((size_t)pi->nr_cylinders, sizeof(bool));
|
||||
std::vector<int> start_pressures(pi->nr_cylinders, 0);
|
||||
std::vector<int> last_pressures(pi->nr_cylinders, 0);
|
||||
std::vector<int> bar_used(pi->nr_cylinders, 0);
|
||||
std::vector<int> volumes_used(pi->nr_cylinders, 0);
|
||||
std::vector<char> cylinder_is_used(pi->nr_cylinders, false);
|
||||
|
||||
data = start;
|
||||
struct plot_data *data = start;
|
||||
for (int i = idx1; i < idx2; ++i) {
|
||||
data = pi->entry + i;
|
||||
if (sum)
|
||||
@ -1603,7 +1605,7 @@ void compare_samples(const struct dive *d, const struct plot_info *pi, int idx1,
|
||||
if (data->depth > max_depth)
|
||||
max_depth = data->depth;
|
||||
|
||||
for (unsigned cylinder_index = 0; cylinder_index < pi->nr_cylinders; cylinder_index++) {
|
||||
for (int cylinder_index = 0; cylinder_index < pi->nr_cylinders; cylinder_index++) {
|
||||
int next_pressure = get_plot_pressure(pi, i, cylinder_index);
|
||||
if (next_pressure && !start_pressures[cylinder_index])
|
||||
start_pressures[cylinder_index] = next_pressure;
|
||||
@ -1629,47 +1631,38 @@ void compare_samples(const struct dive *d, const struct plot_info *pi, int idx1,
|
||||
last_sec = data->sec;
|
||||
}
|
||||
|
||||
free(start_pressures);
|
||||
free(last_pressures);
|
||||
|
||||
avg_depth /= stop->sec - start->sec;
|
||||
avg_speed /= stop->sec - start->sec;
|
||||
|
||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "ΔT:%d:%02dmin"), delta_time / 60, delta_time % 60);
|
||||
memcpy(buf2, buf, bufsize);
|
||||
QString l = qasprintf_loc(translate("gettextFromC", "ΔT:%d:%02dmin"), delta_time / 60, delta_time % 60);
|
||||
|
||||
depthvalue = get_depth_units(delta_depth, NULL, &depth_unit);
|
||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s ΔD:%.1f%s"), buf2, depthvalue, depth_unit);
|
||||
memcpy(buf2, buf, bufsize);
|
||||
l += space + qasprintf_loc(translate("gettextFromC", "ΔD:%.1f%s"), depthvalue, depth_unit);
|
||||
|
||||
depthvalue = get_depth_units(min_depth, NULL, &depth_unit);
|
||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s ↓D:%.1f%s"), buf2, depthvalue, depth_unit);
|
||||
memcpy(buf2, buf, bufsize);
|
||||
l += space + qasprintf_loc(translate("gettextFromC", "↓D:%.1f%s"), depthvalue, depth_unit);
|
||||
|
||||
depthvalue = get_depth_units(max_depth, NULL, &depth_unit);
|
||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s ↑D:%.1f%s"), buf2, depthvalue, depth_unit);
|
||||
memcpy(buf2, buf, bufsize);
|
||||
l += space + qasprintf_loc(translate("gettextFromC", "↑D:%.1f%s"), depthvalue, depth_unit);
|
||||
|
||||
depthvalue = get_depth_units(avg_depth, NULL, &depth_unit);
|
||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s øD:%.1f%s\n"), buf2, depthvalue, depth_unit);
|
||||
memcpy(buf2, buf, bufsize);
|
||||
l += space + qasprintf_loc(translate("gettextFromC", "øD:%.1f%s"), depthvalue, depth_unit);
|
||||
res.push_back(l);
|
||||
|
||||
speedvalue = get_vertical_speed_units(abs(max_desc_speed), NULL, &vertical_speed_unit);
|
||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s ↓V:%.2f%s"), buf2, speedvalue, vertical_speed_unit);
|
||||
memcpy(buf2, buf, bufsize);
|
||||
l = qasprintf_loc(translate("gettextFromC", "↓V:%.2f%s"), speedvalue, vertical_speed_unit);
|
||||
|
||||
speedvalue = get_vertical_speed_units(abs(max_asc_speed), NULL, &vertical_speed_unit);
|
||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s ↑V:%.2f%s"), buf2, speedvalue, vertical_speed_unit);
|
||||
memcpy(buf2, buf, bufsize);
|
||||
l += space + qasprintf_loc(translate("gettextFromC", "↑V:%.2f%s"), speedvalue, vertical_speed_unit);
|
||||
|
||||
speedvalue = get_vertical_speed_units(abs(avg_speed), NULL, &vertical_speed_unit);
|
||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s øV:%.2f%s"), buf2, speedvalue, vertical_speed_unit);
|
||||
l += space + qasprintf_loc(translate("gettextFromC", "øV:%.2f%s"), speedvalue, vertical_speed_unit);
|
||||
|
||||
int total_bar_used = 0;
|
||||
int total_volume_used = 0;
|
||||
bool cylindersizes_are_identical = true;
|
||||
bool sac_is_determinable = true;
|
||||
for (unsigned cylinder_index = 0; cylinder_index < pi->nr_cylinders; cylinder_index++)
|
||||
for (int cylinder_index = 0; cylinder_index < pi->nr_cylinders; cylinder_index++) {
|
||||
if (cylinder_is_used[cylinder_index]) {
|
||||
total_bar_used += bar_used[cylinder_index];
|
||||
total_volume_used += volumes_used[cylinder_index];
|
||||
@ -1685,20 +1678,16 @@ void compare_samples(const struct dive *d, const struct plot_info *pi, int idx1,
|
||||
sac_is_determinable = false;
|
||||
}
|
||||
}
|
||||
free(bar_used);
|
||||
free(volumes_used);
|
||||
free(cylinder_is_used);
|
||||
}
|
||||
|
||||
// No point printing 'bar used' if we know it's meaningless because cylinders of different size were used
|
||||
if (cylindersizes_are_identical && total_bar_used) {
|
||||
pressurevalue = get_pressure_units(total_bar_used, &pressure_unit);
|
||||
memcpy(buf2, buf, bufsize);
|
||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s ΔP:%d%s"), buf2, pressurevalue, pressure_unit);
|
||||
int pressurevalue = get_pressure_units(total_bar_used, &pressure_unit);
|
||||
l += space + qasprintf_loc(translate("gettextFromC", "ΔP:%d%s"), pressurevalue, pressure_unit);
|
||||
}
|
||||
|
||||
// We can't calculate the SAC if the volume for some of the cylinders used is unknown
|
||||
if (sac_is_determinable && total_volume_used) {
|
||||
double volume_value;
|
||||
int volume_precision;
|
||||
const char *volume_unit;
|
||||
|
||||
@ -1707,10 +1696,10 @@ void compare_samples(const struct dive *d, const struct plot_info *pi, int idx1,
|
||||
|
||||
/* milliliters per minute */
|
||||
int sac = lrint(total_volume_used / atm * 60 / delta_time);
|
||||
memcpy(buf2, buf, bufsize);
|
||||
volume_value = get_volume_units(sac, &volume_precision, &volume_unit);
|
||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s SAC:%.*f%s/min"), buf2, volume_precision, volume_value, volume_unit);
|
||||
double volume_value = get_volume_units(sac, &volume_precision, &volume_unit);
|
||||
l += space + qasprintf_loc(translate("gettextFromC", "SAC:%.*f%s/min"), volume_precision, volume_value, volume_unit);
|
||||
}
|
||||
res.push_back(l);
|
||||
|
||||
free(buf2);
|
||||
return res;
|
||||
}
|
||||
@ -97,11 +97,9 @@ struct plot_info {
|
||||
|
||||
#define AMB_PERCENTAGE 50.0
|
||||
|
||||
extern void compare_samples(const struct dive *d, const struct plot_info *pi, int idx1, int idx2, char *buf, int bufsize, bool sum);
|
||||
extern void init_plot_info(struct plot_info *pi);
|
||||
/* when planner_dc is non-null, this is called in planner mode. */
|
||||
extern void create_plot_info_new(const struct dive *dive, const struct divecomputer *dc, struct plot_info *pi, const struct deco_state *planner_ds);
|
||||
extern int get_plot_details_new(const struct dive *d, const struct plot_info *pi, int time, struct membuffer *);
|
||||
extern void free_plot_info_data(struct plot_info *pi);
|
||||
|
||||
/*
|
||||
@ -145,5 +143,12 @@ static inline int get_plot_pressure(const struct plot_info *pi, int idx, int cyl
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
// C++ only formatting functions
|
||||
#include <QString>
|
||||
// Returns index of sample and array of strings describing the dive details at given time
|
||||
std::pair<int, std::vector<QString>> get_plot_details_new(const struct dive *d, const struct plot_info *pi, int time);
|
||||
std::vector<QString> compare_samples(const struct dive *d, const struct plot_info *pi, int idx1, int idx2, bool sum);
|
||||
|
||||
#endif
|
||||
#endif // PROFILE_H
|
||||
|
||||
@ -22,7 +22,6 @@
|
||||
#include "desktop-widgets/divelogexportdialog.h"
|
||||
#include "desktop-widgets/diveshareexportdialog.h"
|
||||
#include "desktop-widgets/subsurfacewebservices.h"
|
||||
#include "profile-widget/profilewidget2.h"
|
||||
|
||||
// Retrieves the current unit settings defined in the Subsurface preferences.
|
||||
#define GET_UNIT(name, field, f, t) \
|
||||
|
||||
@ -56,7 +56,6 @@
|
||||
#include "commands/command.h"
|
||||
|
||||
#include "profilewidget.h"
|
||||
#include "profile-widget/profilewidget2.h"
|
||||
|
||||
#ifndef NO_PRINTING
|
||||
#include "desktop-widgets/printdialog.h"
|
||||
@ -671,7 +670,7 @@ void MainWindow::on_actionReplanDive_triggered()
|
||||
|
||||
disableShortcuts(true);
|
||||
plannerWidgets->prepareReplanDive(current_dive);
|
||||
profile->setPlanState(plannerWidgets->getDive(), profile->dc);
|
||||
profile->plotDive(plannerWidgets->getDive(), profile->dc, true);
|
||||
plannerWidgets->replanDive(profile->dc);
|
||||
}
|
||||
|
||||
@ -685,7 +684,7 @@ void MainWindow::on_actionDivePlanner_triggered()
|
||||
|
||||
disableShortcuts(true);
|
||||
plannerWidgets->preparePlanDive(current_dive);
|
||||
profile->setPlanState(plannerWidgets->getDive(), 0);
|
||||
profile->plotDive(plannerWidgets->getDive(), 0, true);
|
||||
plannerWidgets->planDive();
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
#include "qt-models/cylindermodel.h"
|
||||
#include "qt-models/models.h"
|
||||
#include "desktop-widgets/starwidget.h"
|
||||
#include "profile-widget/profilewidget2.h"
|
||||
#include "qt-models/tankinfomodel.h"
|
||||
#include "qt-models/weightsysteminfomodel.h"
|
||||
#include "qt-models/weightmodel.h"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include "profilewidget.h"
|
||||
#include "profile-widget/profilewidget2.h"
|
||||
#include "profile-widget/profileview.h"
|
||||
#include "commands/command.h"
|
||||
#include "core/color.h"
|
||||
#include "core/selection.h"
|
||||
@ -10,10 +10,14 @@
|
||||
#include "core/subsurface-string.h"
|
||||
#include "qt-models/diveplannermodel.h"
|
||||
|
||||
#include <QToolBar>
|
||||
#include <QHBoxLayout>
|
||||
#include <QStackedWidget>
|
||||
#include <QLabel>
|
||||
#include <QMimeData>
|
||||
#include <QQmlEngine>
|
||||
#include <QQuickWidget>
|
||||
#include <QSignalBlocker>
|
||||
#include <QStackedWidget>
|
||||
#include <QToolBar>
|
||||
|
||||
// A resizing display of the Subsurface logo when no dive is shown
|
||||
class EmptyView : public QLabel {
|
||||
@ -52,6 +56,58 @@ void EmptyView::resizeEvent(QResizeEvent *)
|
||||
update();
|
||||
}
|
||||
|
||||
// We subclass the QQuickWidget so that we can easily react to drag&drop events
|
||||
class ProfileViewWidget : public QQuickWidget
|
||||
{
|
||||
public:
|
||||
ProfileViewWidget(ProfileWidget &w) : w(w)
|
||||
{
|
||||
setAcceptDrops(true);
|
||||
}
|
||||
private:
|
||||
static constexpr const char *picture_mime_format = "application/x-subsurfaceimagedrop";
|
||||
ProfileWidget &w;
|
||||
void dropEvent(QDropEvent *event) override
|
||||
{
|
||||
if (event->mimeData()->hasFormat(picture_mime_format)) {
|
||||
QByteArray itemData = event->mimeData()->data(picture_mime_format);
|
||||
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
|
||||
|
||||
QString filename;
|
||||
dataStream >> filename;
|
||||
w.dropPicture(filename, event->pos());
|
||||
|
||||
if (event->source() == this) {
|
||||
event->setDropAction(Qt::MoveAction);
|
||||
event->accept();
|
||||
} else {
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
void dragEnterEvent(QDragEnterEvent *event) override
|
||||
{
|
||||
// Does the same thing as dragMove event...?
|
||||
dragMoveEvent(event);
|
||||
}
|
||||
void dragMoveEvent(QDragMoveEvent *event) override
|
||||
{
|
||||
if (event->mimeData()->hasFormat(picture_mime_format)) {
|
||||
if (event->source() == this) {
|
||||
event->setDropAction(Qt::MoveAction);
|
||||
event->accept();
|
||||
} else {
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const QUrl urlProfileView = QUrl(QStringLiteral("qrc:/qml/profileview.qml"));
|
||||
ProfileWidget::ProfileWidget() : d(nullptr), dc(0), originalDive(nullptr), placingCommand(false)
|
||||
{
|
||||
ui.setupUi(this);
|
||||
@ -72,7 +128,10 @@ ProfileWidget::ProfileWidget() : d(nullptr), dc(0), originalDive(nullptr), placi
|
||||
|
||||
emptyView.reset(new EmptyView);
|
||||
|
||||
view.reset(new ProfileWidget2(DivePlannerPointsModel::instance(), 1.0, this));
|
||||
viewWidget.reset(new ProfileViewWidget(*this));
|
||||
viewWidget->setSource(urlProfileView);
|
||||
viewWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
||||
|
||||
QToolBar *toolBar = new QToolBar(this);
|
||||
for (QAction *a: toolbarActions)
|
||||
toolBar->addAction(a);
|
||||
@ -81,7 +140,7 @@ ProfileWidget::ProfileWidget() : d(nullptr), dc(0), originalDive(nullptr), placi
|
||||
|
||||
stack = new QStackedWidget(this);
|
||||
stack->addWidget(emptyView.get());
|
||||
stack->addWidget(view.get());
|
||||
stack->addWidget(viewWidget.get());
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout(this);
|
||||
layout->setSpacing(0);
|
||||
@ -121,10 +180,6 @@ ProfileWidget::ProfileWidget() : d(nullptr), dc(0), originalDive(nullptr), placi
|
||||
connect(ui.profPO2, &QAction::triggered, pp_gas, &qPrefPartialPressureGas::set_po2);
|
||||
|
||||
connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &ProfileWidget::divesChanged);
|
||||
connect(&diveListNotifier, &DiveListNotifier::settingsChanged, view.get(), &ProfileWidget2::settingsChanged);
|
||||
connect(view.get(), &ProfileWidget2::stopAdded, this, &ProfileWidget::stopAdded);
|
||||
connect(view.get(), &ProfileWidget2::stopRemoved, this, &ProfileWidget::stopRemoved);
|
||||
connect(view.get(), &ProfileWidget2::stopMoved, this, &ProfileWidget::stopMoved);
|
||||
|
||||
ui.profCalcAllTissues->setChecked(qPrefTechnicalDetails::calcalltissues());
|
||||
ui.profCalcCeiling->setChecked(qPrefTechnicalDetails::calcceiling());
|
||||
@ -151,6 +206,33 @@ ProfileWidget::~ProfileWidget()
|
||||
{
|
||||
}
|
||||
|
||||
// hack around the Qt6 bug where the QML object gets destroyed and recreated
|
||||
ProfileView *ProfileWidget::getView()
|
||||
{
|
||||
ProfileView *view = qobject_cast<ProfileView *>(viewWidget->rootObject());
|
||||
if (!view)
|
||||
qWarning("Oops. The root of the StatsView is not a StatsView.");
|
||||
if (view) {
|
||||
// try to prevent the JS garbage collection from freeing the object
|
||||
// this appears to fail with Qt6 which is why we still look up the
|
||||
// object from the rootObject
|
||||
viewWidget->engine()->setObjectOwnership(view, QQmlEngine::CppOwnership);
|
||||
view->setParent(this);
|
||||
view->setVisible(isVisible()); // Synchronize visibility of widget and QtQuick-view.
|
||||
|
||||
if (!view->initialized) {
|
||||
view->setPlannerModel(*DivePlannerPointsModel::instance());
|
||||
|
||||
//connect(&diveListNotifier, &DiveListNotifier::settingsChanged, view.get(), &ProfileWidget2::settingsChanged);
|
||||
connect(view, &ProfileView::stopAdded, this, &ProfileWidget::stopAdded);
|
||||
connect(view, &ProfileView::stopRemoved, this, &ProfileWidget::stopRemoved);
|
||||
connect(view, &ProfileView::stopMoved, this, &ProfileWidget::stopMoved);
|
||||
view->initialized = true;
|
||||
}
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
void ProfileWidget::setEnabledToolbar(bool enabled)
|
||||
{
|
||||
for (QAction *b: toolbarActions)
|
||||
@ -190,7 +272,7 @@ void ProfileWidget::plotCurrentDive()
|
||||
plotDive(d, dc);
|
||||
}
|
||||
|
||||
void ProfileWidget::plotDive(dive *dIn, int dcIn)
|
||||
void ProfileWidget::plotDive(dive *dIn, int dcIn, bool planMode)
|
||||
{
|
||||
d = dIn;
|
||||
|
||||
@ -214,14 +296,16 @@ void ProfileWidget::plotDive(dive *dIn, int dcIn)
|
||||
editDive();
|
||||
}
|
||||
|
||||
auto view = getView();
|
||||
setEnabledToolbar(d != nullptr);
|
||||
if (editedDive) {
|
||||
view->plotDive(editedDive.get(), editedDc);
|
||||
view->plotDive(editedDive.get(), editedDc, ProfileView::RenderFlags::EditMode);
|
||||
setDive(editedDive.get());
|
||||
} else if (d) {
|
||||
view->setProfileState(d, dc);
|
||||
int flags = planMode ? ProfileView::RenderFlags::PlanMode
|
||||
: ProfileView::RenderFlags::None;
|
||||
view->resetZoom(); // when switching dive, reset the zoomLevel
|
||||
view->plotDive(d, dc);
|
||||
view->plotDive(d, dc, flags);
|
||||
setDive(d);
|
||||
} else {
|
||||
view->clear();
|
||||
@ -263,8 +347,8 @@ void ProfileWidget::divesChanged(const QVector<dive *> &dives, DiveField field)
|
||||
if (!d || !dives.contains(d) || !(field.duration || field.depth) || placingCommand)
|
||||
return;
|
||||
|
||||
// If were editing the current dive and not currently
|
||||
// placing command, we have to update the edited dive.
|
||||
// If we're editing the current dive and not currently
|
||||
// placing a command, we have to update the edited dive.
|
||||
if (editedDive) {
|
||||
copy_dive(d, editedDive.get());
|
||||
// TODO: Holy moly that function sends too many signals. Fix it!
|
||||
@ -274,13 +358,6 @@ void ProfileWidget::divesChanged(const QVector<dive *> &dives, DiveField field)
|
||||
plotCurrentDive();
|
||||
}
|
||||
|
||||
void ProfileWidget::setPlanState(const struct dive *d, int dc)
|
||||
{
|
||||
exitEditMode();
|
||||
view->setPlanState(d, dc);
|
||||
setDive(d);
|
||||
}
|
||||
|
||||
void ProfileWidget::unsetProfHR()
|
||||
{
|
||||
ui.profHR->setChecked(false);
|
||||
@ -299,9 +376,13 @@ void ProfileWidget::editDive()
|
||||
editedDc = dc;
|
||||
copy_dive(d, editedDive.get()); // Work on a copy of the dive
|
||||
originalDive = d;
|
||||
|
||||
// We don't want the DivePlannerPointsModel send signals while reloading,
|
||||
// because that would reload the just deleted dive. And we will be reloading
|
||||
// the dive anyway. Control-flow here is truly horrible.
|
||||
QSignalBlocker blocker(DivePlannerPointsModel::instance());
|
||||
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::ADD);
|
||||
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), editedDc);
|
||||
view->setEditState(editedDive.get(), editedDc);
|
||||
}
|
||||
|
||||
void ProfileWidget::exitEditMode()
|
||||
@ -309,7 +390,7 @@ void ProfileWidget::exitEditMode()
|
||||
if (!editedDive)
|
||||
return;
|
||||
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING);
|
||||
view->setProfileState(d, dc); // switch back to original dive before erasing the copy.
|
||||
//view->setProfileState(d, dc); // switch back to original dive before erasing the copy.
|
||||
editedDive.reset();
|
||||
originalDive = nullptr;
|
||||
}
|
||||
@ -359,3 +440,12 @@ void ProfileWidget::stopMoved(int count)
|
||||
Setter s(placingCommand, true);
|
||||
Command::editProfile(editedDive.get(), editedDc, Command::EditProfileType::MOVE, count);
|
||||
}
|
||||
|
||||
void ProfileWidget::dropPicture(const QString &filename, QPoint pos)
|
||||
{
|
||||
auto view = getView();
|
||||
if (!d || !view)
|
||||
return;
|
||||
offset_t offset { (int32_t)lrint(view->timeAt(pos)) };
|
||||
Command::setPictureOffset(d, filename, offset);
|
||||
}
|
||||
|
||||
@ -11,8 +11,9 @@
|
||||
#include <vector>
|
||||
|
||||
struct dive;
|
||||
class ProfileWidget2;
|
||||
class ProfileView;
|
||||
class EmptyView;
|
||||
class QQuickWidget;
|
||||
class QStackedWidget;
|
||||
|
||||
extern "C" void free_dive(struct dive *);
|
||||
@ -22,13 +23,12 @@ class ProfileWidget : public QWidget {
|
||||
public:
|
||||
ProfileWidget();
|
||||
~ProfileWidget();
|
||||
std::unique_ptr<ProfileWidget2> view;
|
||||
void plotDive(struct dive *d, int dc); // Attempt to keep DC number id dc < 0
|
||||
void plotDive(struct dive *d, int dc, bool planMode = false); // Attempt to keep DC number id dc < 0
|
||||
void plotCurrentDive();
|
||||
void setPlanState(const struct dive *d, int dc);
|
||||
void setEnabledToolbar(bool enabled);
|
||||
void nextDC();
|
||||
void prevDC();
|
||||
void dropPicture(const QString &filename, QPoint p);
|
||||
dive *d;
|
||||
int dc;
|
||||
private
|
||||
@ -40,6 +40,8 @@ slots:
|
||||
void stopRemoved(int count);
|
||||
void stopMoved(int count);
|
||||
private:
|
||||
ProfileView *getView();
|
||||
std::unique_ptr<QQuickWidget> viewWidget;
|
||||
std::unique_ptr<EmptyView> emptyView;
|
||||
std::vector<QAction *> toolbarActions;
|
||||
Ui::ProfileWidget ui;
|
||||
|
||||
6
desktop-widgets/qml/profileview.qml
Normal file
6
desktop-widgets/qml/profileview.qml
Normal file
@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
import org.subsurfacedivelog.mobile 1.0
|
||||
|
||||
ProfileView {
|
||||
property real dpr: 1.0
|
||||
}
|
||||
5
desktop-widgets/qml/profileview.qrc
Normal file
5
desktop-widgets/qml/profileview.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/qml">
|
||||
<file>profileview.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@ -20,7 +20,6 @@
|
||||
#include "libdivecomputer/parser.h"
|
||||
#include "desktop-widgets/divelistview.h"
|
||||
#include "core/selection.h"
|
||||
#include "profile-widget/profilewidget2.h"
|
||||
#include "commands/command.h"
|
||||
#include "core/metadata.h"
|
||||
#include "core/tag.h"
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
#include "TabDiveInformation.h"
|
||||
#include "maintab.h"
|
||||
#include "ui_TabDiveInformation.h"
|
||||
#include "profile-widget/profilewidget2.h"
|
||||
#include "../tagwidget.h"
|
||||
#include "commands/command.h"
|
||||
#include "core/subsurface-string.h"
|
||||
|
||||
@ -231,13 +231,13 @@ Item {
|
||||
Layout.columnSpan: 3
|
||||
clip: true
|
||||
|
||||
QMLProfile {
|
||||
ProfileView {
|
||||
id: qmlProfile
|
||||
visible: !noDive
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
property real lastScale: 1.0 // final scale at the end of previous pinch
|
||||
diveId: detailsView.myId
|
||||
property real dpr: 0.8 // TODO: make this dynamic
|
||||
Rectangle {
|
||||
color: "transparent"
|
||||
opacity: 0.6
|
||||
@ -253,36 +253,20 @@ Item {
|
||||
// before realizing that this is actually a pinch/zoom. So let's reset this
|
||||
// just in case
|
||||
qmlProfile.opacity = 1.0
|
||||
if (manager.verboseEnabled)
|
||||
manager.appendTextToLog("pinch started w/ previousScale " + qmlProfile.lastScale)
|
||||
qmlProfile.pinchStart()
|
||||
}
|
||||
onPinchUpdated: {
|
||||
if (pinch.scale * qmlProfile.lastScale < 1.0)
|
||||
qmlProfile.lastScale = 1.0 / pinch.scale // this way we never shrink and the changes stay smooth
|
||||
// the underlying widget deals with the scaling, no need to send an update request
|
||||
qmlProfile.scale = pinch.scale * qmlProfile.lastScale
|
||||
if (manager.verboseEnabled)
|
||||
manager.appendTextToLog("pinch updated to scale " + qmlProfile.scale);
|
||||
}
|
||||
onPinchFinished: {
|
||||
// remember the final scale value so we can continue from there next time the user pinches
|
||||
qmlProfile.lastScale = pinch.scale * qmlProfile.lastScale
|
||||
qmlProfile.pinch(pinch.scale)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// we want to pan the profile if we are zoomed in, but we want to immediately
|
||||
// pass the mouse events through to the ListView if we are not. That way you
|
||||
// can swipe through the dive list, even if you happen to swipe the profile
|
||||
property bool isZoomed: qmlProfile.scale - 1.0 > 0.02
|
||||
property bool isZoomed: qmlProfile.zoomLevel > 1.02
|
||||
|
||||
// this indicates that we are actually dragging
|
||||
property bool dragging: false
|
||||
// cursor/finger position as we start dragging
|
||||
property real initialX
|
||||
property real initialY
|
||||
// the offset previously used to show the profile
|
||||
property real oldXOffset
|
||||
property real oldYOffset
|
||||
|
||||
// if the profile is not scaled in, don't start panning
|
||||
// but if the profile is scaled in, then start almost immediately
|
||||
@ -291,16 +275,6 @@ Item {
|
||||
// pass events through to the parent and eventually into the ListView
|
||||
propagateComposedEvents: true
|
||||
|
||||
// for testing / debugging on a desktop
|
||||
scrollGestureEnabled: true
|
||||
onWheel: {
|
||||
manager.appendTextToLog("wheel " + wheel.angleDelta)
|
||||
if (wheel.angleDelta.y > 0)
|
||||
qmlProfile.scale += 0.2
|
||||
if (wheel.angleDelta.y < 0 && qmlProfile.scale > 1.1)
|
||||
qmlProfile.scale -= 0.2
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
drag.target: qmlProfile
|
||||
drag.axis: Drag.XAndYAxis
|
||||
@ -311,10 +285,7 @@ Item {
|
||||
}
|
||||
onPressAndHold: {
|
||||
dragging = true;
|
||||
oldXOffset = qmlProfile.xOffset
|
||||
oldYOffset = qmlProfile.yOffset
|
||||
initialX = mouse.x
|
||||
initialY = mouse.y
|
||||
qmlProfile.panStart(mouse.x, mouse.y)
|
||||
if (manager.verboseEnabled)
|
||||
manager.appendTextToLog("press and hold at mouse" + Math.round(10 * mouse.x) / 10 + " / " + Math.round(10 * mouse.y) / 10)
|
||||
// give visual feedback to the user that they now can drag
|
||||
@ -322,13 +293,9 @@ Item {
|
||||
}
|
||||
onPositionChanged: {
|
||||
if (dragging) {
|
||||
var x = (mouse.x - initialX) / qmlProfile.scale
|
||||
var y = (mouse.y - initialY) / qmlProfile.scale
|
||||
if (manager.verboseEnabled)
|
||||
manager.appendTextToLog("drag mouse " + Math.round(10 * mouse.x) / 10 + " / " + Math.round(10 * mouse.y) / 10 + " delta " + Math.round(x) + " / " + Math.round(y))
|
||||
qmlProfile.xOffset = oldXOffset + x
|
||||
qmlProfile.yOffset = oldYOffset + y
|
||||
qmlProfile.update()
|
||||
qmlProfile.pan(mouse.x, mouse.y)
|
||||
} else {
|
||||
mouse.accepted = false
|
||||
}
|
||||
@ -344,7 +311,6 @@ Item {
|
||||
onClicked: {
|
||||
// reset the position if not zoomed in
|
||||
if (!isZoomed) {
|
||||
qmlProfile.xOffset = qmlProfile.yOffset = oldXOffset = oldYOffset = 0
|
||||
mouse.accepted = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,42 +6,37 @@ set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||
divecartesianaxis.h
|
||||
diveeventitem.cpp
|
||||
diveeventitem.h
|
||||
divelineitem.cpp
|
||||
divelineitem.h
|
||||
divepixmapcache.cpp
|
||||
divepixmapcache.h
|
||||
divepixmapitem.cpp
|
||||
divepixmapitem.h
|
||||
divepercentageitem.cpp
|
||||
divepercentageitem.h
|
||||
diveprofileitem.cpp
|
||||
diveprofileitem.h
|
||||
diverectitem.cpp
|
||||
diverectitem.h
|
||||
divetextitem.cpp
|
||||
divetextitem.h
|
||||
divetooltipitem.cpp
|
||||
divetooltipitem.h
|
||||
handleitem.cpp
|
||||
handleitem.h
|
||||
pictureitem.h
|
||||
pictureitem.cpp
|
||||
profilescene.cpp
|
||||
profilescene.h
|
||||
profiletranslations.h
|
||||
profileview.cpp
|
||||
profileview.h
|
||||
ruleritem.cpp
|
||||
ruleritem.h
|
||||
tankitem.cpp
|
||||
tankitem.h
|
||||
tooltipitem.h
|
||||
tooltipitem.cpp
|
||||
)
|
||||
if (SUBSURFACE_TARGET_EXECUTABLE MATCHES "MobileExecutable")
|
||||
set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||
${SUBSURFACE_PROFILE_LIB_SRCS}
|
||||
qmlprofile.cpp
|
||||
qmlprofile.h
|
||||
)
|
||||
else ()
|
||||
set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||
${SUBSURFACE_PROFILE_LIB_SRCS}
|
||||
divehandler.cpp
|
||||
divehandler.h
|
||||
profilewidget2.cpp
|
||||
profilewidget2.h
|
||||
ruleritem.cpp
|
||||
ruleritem.h
|
||||
)
|
||||
endif ()
|
||||
source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS})
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
#include "core/qthelper.h"
|
||||
#include "core/subsurface-float.h"
|
||||
#include "profile-widget/animationfunctions.h"
|
||||
#include "profile-widget/divelineitem.h"
|
||||
#include "profile-widget/profilescene.h"
|
||||
|
||||
static const double labelSpaceHorizontal = 2.0; // space between label and ticks
|
||||
@ -277,7 +276,7 @@ DiveCartesianAxis::Label DiveCartesianAxis::createLabel(double value, double pos
|
||||
if (lineVisibility) {
|
||||
label.lineStart = linePos(posStart);
|
||||
label.lineEnd = linePos(pos);
|
||||
label.line = std::make_unique<DiveLineItem>(this);
|
||||
label.line = std::make_unique<QGraphicsLineItem>(this);
|
||||
label.line->setPen(gridPen);
|
||||
label.line->setZValue(0);
|
||||
label.line->setLine(animSpeed <= 0 ? label.lineEnd : label.lineStart);
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
|
||||
class ProfileScene;
|
||||
class DiveTextItem;
|
||||
class DiveLineItem;
|
||||
|
||||
class DiveCartesianAxis : public QGraphicsLineItem {
|
||||
private:
|
||||
@ -57,7 +56,7 @@ private:
|
||||
QLineF lineStart;
|
||||
QLineF lineEnd;
|
||||
std::unique_ptr<DiveTextItem> label;
|
||||
std::unique_ptr<DiveLineItem> line;
|
||||
std::unique_ptr<QGraphicsLineItem> line;
|
||||
};
|
||||
Position position;
|
||||
bool inverted; // Top-to-bottom or right-to-left axis.
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
#include "profile-widget/divepixmapcache.h"
|
||||
#include "profile-widget/animationfunctions.h"
|
||||
#include "core/event.h"
|
||||
#include "core/eventtype.h"
|
||||
#include "core/format.h"
|
||||
#include "core/profile.h"
|
||||
#include "core/gettextfromc.h"
|
||||
@ -17,7 +16,9 @@ static int depthAtTime(const plot_info &pi, duration_t time);
|
||||
|
||||
DiveEventItem::DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix,
|
||||
const plot_info &pi, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis,
|
||||
int speed, const DivePixmaps &pixmaps, QGraphicsItem *parent) : DivePixmapItem(parent),
|
||||
int speed, const DivePixmaps &pixmaps, QGraphicsItem *parent) : QGraphicsPixmapItem(parent),
|
||||
text(setupToolTipString(d, ev, lastgasmix)),
|
||||
pixmap(setupPixmap(d, ev, lastgasmix, pixmaps)),
|
||||
vAxis(vAxis),
|
||||
hAxis(hAxis),
|
||||
ev(ev),
|
||||
@ -25,10 +26,11 @@ DiveEventItem::DiveEventItem(const struct dive *d, struct event *ev, struct gasm
|
||||
depth(depthAtTime(pi, ev->time))
|
||||
{
|
||||
setFlag(ItemIgnoresTransformations);
|
||||
|
||||
setupPixmap(lastgasmix, pixmaps);
|
||||
setupToolTipString(lastgasmix);
|
||||
setPixmap(pixmap);
|
||||
recalculatePos();
|
||||
|
||||
if (ev->type == SAMPLE_EVENT_BOOKMARK)
|
||||
setOffset(QPointF(0.0, -pixmap.height()));
|
||||
}
|
||||
|
||||
DiveEventItem::~DiveEventItem()
|
||||
@ -45,45 +47,31 @@ struct event *DiveEventItem::getEventMutable()
|
||||
return ev;
|
||||
}
|
||||
|
||||
void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps)
|
||||
QPixmap DiveEventItem::setupPixmap(const struct dive *dive, const struct event *ev, struct gasmix lastgasmix, const DivePixmaps &pixmaps)
|
||||
{
|
||||
event_severity severity = get_event_severity(ev);
|
||||
if (empty_string(ev->name)) {
|
||||
setPixmap(pixmaps.warning);
|
||||
} else if (same_string_caseinsensitive(ev->name, "modechange")) {
|
||||
if (ev->value == 0)
|
||||
setPixmap(pixmaps.bailout);
|
||||
else
|
||||
setPixmap(pixmaps.onCCRLoop);
|
||||
} else if (ev->type == SAMPLE_EVENT_BOOKMARK) {
|
||||
setPixmap(pixmaps.bookmark);
|
||||
setOffset(QPointF(0.0, -pixmap().height()));
|
||||
} else if (event_is_gaschange(ev)) {
|
||||
if (empty_string(ev->name))
|
||||
return pixmaps.warning;
|
||||
|
||||
if (same_string_caseinsensitive(ev->name, "modechange"))
|
||||
return ev->value == 0 ? pixmaps.bailout : pixmaps.onCCRLoop;
|
||||
|
||||
if (ev->type == SAMPLE_EVENT_BOOKMARK)
|
||||
return pixmaps.bookmark;
|
||||
|
||||
if (event_is_gaschange(ev)) {
|
||||
struct gasmix mix = get_gasmix_from_event(dive, ev);
|
||||
struct icd_data icd_data;
|
||||
bool icd = isobaric_counterdiffusion(lastgasmix, mix, &icd_data);
|
||||
if (mix.he.permille) {
|
||||
if (icd)
|
||||
setPixmap(pixmaps.gaschangeTrimixICD);
|
||||
else
|
||||
setPixmap(pixmaps.gaschangeTrimix);
|
||||
} else if (gasmix_is_air(mix)) {
|
||||
if (icd)
|
||||
setPixmap(pixmaps.gaschangeAirICD);
|
||||
else
|
||||
setPixmap(pixmaps.gaschangeAir);
|
||||
} else if (mix.o2.permille == 1000) {
|
||||
if (icd)
|
||||
setPixmap(pixmaps.gaschangeOxygenICD);
|
||||
else
|
||||
setPixmap(pixmaps.gaschangeOxygen);
|
||||
} else {
|
||||
if (icd)
|
||||
setPixmap(pixmaps.gaschangeEANICD);
|
||||
else
|
||||
setPixmap(pixmaps.gaschangeEAN);
|
||||
if (mix.he.permille)
|
||||
return icd ? pixmaps.gaschangeTrimixICD : pixmaps.gaschangeTrimix;
|
||||
if (gasmix_is_air(mix))
|
||||
return icd ? pixmaps.gaschangeAirICD : pixmaps.gaschangeAir;
|
||||
if (mix.o2.permille == 1000)
|
||||
return icd ? pixmaps.gaschangeOxygenICD : pixmaps.gaschangeOxygen;
|
||||
return icd ? pixmaps.gaschangeEANICD : pixmaps.gaschangeEAN;
|
||||
}
|
||||
} else if ((((ev->flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 1) ||
|
||||
if ((((ev->flags & SAMPLE_FLAGS_SEVERITY_MASK) >> SAMPLE_FLAGS_SEVERITY_SHIFT) == 1) ||
|
||||
// those are useless internals of the dive computer
|
||||
same_string_caseinsensitive(ev->name, "heading") ||
|
||||
(same_string_caseinsensitive(ev->name, "SP change") && ev->time.seconds == 0)) {
|
||||
@ -94,35 +82,35 @@ void DiveEventItem::setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pix
|
||||
// so set an "almost invisible" pixmap (a narrow but somewhat tall, basically transparent pixmap)
|
||||
// that allows tooltips to work when we don't want to show a specific
|
||||
// pixmap for an event, but want to show the event value in the tooltip
|
||||
setPixmap(pixmaps.transparent);
|
||||
} else if (severity == EVENT_SEVERITY_INFO) {
|
||||
setPixmap(pixmaps.info);
|
||||
} else if (severity == EVENT_SEVERITY_WARN) {
|
||||
setPixmap(pixmaps.warning);
|
||||
} else if (severity == EVENT_SEVERITY_ALARM) {
|
||||
setPixmap(pixmaps.violation);
|
||||
} else if (same_string_caseinsensitive(ev->name, "violation") || // generic libdivecomputer
|
||||
return pixmaps.transparent;
|
||||
}
|
||||
if (severity == EVENT_SEVERITY_INFO)
|
||||
return pixmaps.info;
|
||||
if (severity == EVENT_SEVERITY_WARN)
|
||||
return pixmaps.warning;
|
||||
if (severity == EVENT_SEVERITY_ALARM)
|
||||
return pixmaps.violation;
|
||||
if (same_string_caseinsensitive(ev->name, "violation") || // generic libdivecomputer
|
||||
same_string_caseinsensitive(ev->name, "Safety stop violation") || // the rest are from the Uemis downloader
|
||||
same_string_caseinsensitive(ev->name, "pO₂ ascend alarm") ||
|
||||
same_string_caseinsensitive(ev->name, "RGT alert") ||
|
||||
same_string_caseinsensitive(ev->name, "Dive time alert") ||
|
||||
same_string_caseinsensitive(ev->name, "Low battery alert") ||
|
||||
same_string_caseinsensitive(ev->name, "Speed alarm")) {
|
||||
setPixmap(pixmaps.violation);
|
||||
} else if (same_string_caseinsensitive(ev->name, "non stop time") || // generic libdivecomputer
|
||||
same_string_caseinsensitive(ev->name, "Speed alarm"))
|
||||
return pixmaps.violation;
|
||||
if (same_string_caseinsensitive(ev->name, "non stop time") || // generic libdivecomputer
|
||||
same_string_caseinsensitive(ev->name, "safety stop") ||
|
||||
same_string_caseinsensitive(ev->name, "safety stop (voluntary)") ||
|
||||
same_string_caseinsensitive(ev->name, "Tank change suggested") || // Uemis downloader
|
||||
same_string_caseinsensitive(ev->name, "Marker")) {
|
||||
setPixmap(pixmaps.info);
|
||||
} else {
|
||||
same_string_caseinsensitive(ev->name, "Marker"))
|
||||
return pixmaps.info;
|
||||
|
||||
// we should do some guessing based on the type / name of the event;
|
||||
// for now they all get the warning icon
|
||||
setPixmap(pixmaps.warning);
|
||||
}
|
||||
return pixmaps.warning;
|
||||
}
|
||||
|
||||
void DiveEventItem::setupToolTipString(struct gasmix lastgasmix)
|
||||
QString DiveEventItem::setupToolTipString(const struct dive *dive, const struct event *ev, struct gasmix lastgasmix)
|
||||
{
|
||||
// we display the event on screen - so translate
|
||||
QString name = gettextFromC::tr(ev->name);
|
||||
@ -166,7 +154,7 @@ void DiveEventItem::setupToolTipString(struct gasmix lastgasmix)
|
||||
name += ev->flags & SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") :
|
||||
ev->flags & SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : "";
|
||||
}
|
||||
setToolTip(QString("<img height=\"16\" src=\":status-warning-icon\"> ") + name);
|
||||
return name;
|
||||
}
|
||||
|
||||
void DiveEventItem::eventVisibilityChanged(const QString&, bool)
|
||||
@ -228,7 +216,6 @@ void DiveEventItem::recalculatePos()
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
setVisible(!ev->hidden && !is_event_type_hidden(ev));
|
||||
double x = hAxis->posAtValue(ev->time.seconds);
|
||||
double y = vAxis->posAtValue(depth);
|
||||
setPos(x, y);
|
||||
|
||||
@ -2,15 +2,16 @@
|
||||
#ifndef DIVEEVENTITEM_H
|
||||
#define DIVEEVENTITEM_H
|
||||
|
||||
#include "divepixmapitem.h"
|
||||
#include <QCoreApplication> // for Q_DECLARE_TR_FUNCTIONS
|
||||
#include <QGraphicsPixmapItem>
|
||||
|
||||
class DiveCartesianAxis;
|
||||
class DivePixmaps;
|
||||
struct event;
|
||||
struct plot_info;
|
||||
|
||||
class DiveEventItem : public DivePixmapItem {
|
||||
Q_OBJECT
|
||||
class DiveEventItem : public QGraphicsPixmapItem {
|
||||
Q_DECLARE_TR_FUNCTIONS(DiveEventItem)
|
||||
public:
|
||||
DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix,
|
||||
const struct plot_info &pi, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis,
|
||||
@ -24,10 +25,11 @@ public:
|
||||
static bool isInteresting(const struct dive *d, const struct divecomputer *dc,
|
||||
const struct event *ev, const struct plot_info &pi,
|
||||
int firstSecond, int lastSecond);
|
||||
|
||||
const QString text;
|
||||
const QPixmap pixmap;
|
||||
private:
|
||||
void setupToolTipString(struct gasmix lastgasmix);
|
||||
void setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps);
|
||||
static QString setupToolTipString(const struct dive *d, const struct event *ev, struct gasmix lastgasmix);
|
||||
static QPixmap setupPixmap(const struct dive *d, const struct event *ev, struct gasmix lastgasmix, const DivePixmaps &pixmaps);
|
||||
void recalculatePos();
|
||||
DiveCartesianAxis *vAxis;
|
||||
DiveCartesianAxis *hAxis;
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "divehandler.h"
|
||||
#include "profilewidget2.h"
|
||||
#include "profilescene.h"
|
||||
#include "core/dive.h"
|
||||
#include "core/gettextfromc.h"
|
||||
#include "core/qthelper.h"
|
||||
#include "qt-models/diveplannermodel.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QSettings>
|
||||
|
||||
DiveHandler::DiveHandler(const struct dive *d) : dive(d)
|
||||
{
|
||||
setRect(-5, -5, 10, 10);
|
||||
setFlags(ItemIgnoresTransformations | ItemIsSelectable | ItemIsMovable | ItemSendsGeometryChanges);
|
||||
setBrush(Qt::white);
|
||||
setZValue(2);
|
||||
t.start();
|
||||
}
|
||||
|
||||
int DiveHandler::parentIndex()
|
||||
{
|
||||
ProfileWidget2 *view = qobject_cast<ProfileWidget2 *>(scene()->views().first());
|
||||
return view->handleIndex(this);
|
||||
}
|
||||
|
||||
void DiveHandler::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
|
||||
{
|
||||
QMenu m;
|
||||
// Don't have a gas selection for the last point
|
||||
emit released();
|
||||
DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
|
||||
QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS);
|
||||
if (index.sibling(index.row() + 1, index.column()).isValid()) {
|
||||
QStringList gases = get_dive_gas_list(dive);
|
||||
for (int i = 0; i < gases.size(); i++) {
|
||||
QAction *action = new QAction(&m);
|
||||
action->setText(gases[i]);
|
||||
action->setData(i);
|
||||
connect(action, &QAction::triggered, this, &DiveHandler::changeGas);
|
||||
m.addAction(action);
|
||||
}
|
||||
}
|
||||
// don't allow removing the last point
|
||||
if (plannerModel->rowCount() > 1) {
|
||||
m.addSeparator();
|
||||
m.addAction(gettextFromC::tr("Remove this point"), this, &DiveHandler::selfRemove);
|
||||
m.exec(event->screenPos());
|
||||
}
|
||||
}
|
||||
|
||||
void DiveHandler::selfRemove()
|
||||
{
|
||||
#ifndef SUBSURFACE_MOBILE
|
||||
setSelected(true);
|
||||
ProfileWidget2 *view = qobject_cast<ProfileWidget2 *>(scene()->views().first());
|
||||
view->keyDeleteAction();
|
||||
#endif
|
||||
}
|
||||
|
||||
void DiveHandler::changeGas()
|
||||
{
|
||||
QAction *action = qobject_cast<QAction *>(sender());
|
||||
DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
|
||||
QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS);
|
||||
plannerModel->gasChange(index.sibling(index.row() + 1, index.column()), action->data().toInt());
|
||||
}
|
||||
|
||||
void DiveHandler::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (t.elapsed() < 40)
|
||||
return;
|
||||
t.start();
|
||||
|
||||
ProfileWidget2 *view = qobject_cast<ProfileWidget2*>(scene()->views().first());
|
||||
if(!view->profileScene->pointOnProfile(event->scenePos()))
|
||||
return;
|
||||
|
||||
QGraphicsEllipseItem::mouseMoveEvent(event);
|
||||
emit moved();
|
||||
}
|
||||
|
||||
void DiveHandler::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
QGraphicsItem::mousePressEvent(event);
|
||||
emit clicked();
|
||||
}
|
||||
|
||||
void DiveHandler::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
QGraphicsItem::mouseReleaseEvent(event);
|
||||
emit released();
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef DIVEHANDLER_HPP
|
||||
#define DIVEHANDLER_HPP
|
||||
|
||||
#include <QGraphicsPathItem>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
struct dive;
|
||||
|
||||
class DiveHandler : public QObject, public QGraphicsEllipseItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DiveHandler(const struct dive *d);
|
||||
|
||||
protected:
|
||||
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event);
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event);
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
|
||||
signals:
|
||||
void moved();
|
||||
void clicked();
|
||||
void released();
|
||||
private:
|
||||
int parentIndex();
|
||||
public
|
||||
slots:
|
||||
void selfRemove();
|
||||
void changeGas();
|
||||
private:
|
||||
const struct dive *dive;
|
||||
QElapsedTimer t;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,6 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "divelineitem.h"
|
||||
|
||||
DiveLineItem::DiveLineItem(QGraphicsItem *parent) : QGraphicsLineItem(parent)
|
||||
{
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef DIVELINEITEM_H
|
||||
#define DIVELINEITEM_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QGraphicsLineItem>
|
||||
|
||||
class DiveLineItem : public QObject, public QGraphicsLineItem {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QPointF pos READ pos WRITE setPos)
|
||||
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity)
|
||||
public:
|
||||
DiveLineItem(QGraphicsItem *parent = 0);
|
||||
};
|
||||
|
||||
#endif // DIVELINEITEM_H
|
||||
@ -1,107 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "profile-widget/divepixmapitem.h"
|
||||
#include "profile-widget/animationfunctions.h"
|
||||
#include "core/pref.h"
|
||||
#include "core/qthelper.h"
|
||||
#include "core/settings/qPrefDisplay.h"
|
||||
#include "core/subsurface-qt/divelistnotifier.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QPen>
|
||||
#include <QUrl>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
|
||||
DivePixmapItem::DivePixmapItem(QGraphicsItem *parent) : QGraphicsPixmapItem(parent)
|
||||
{
|
||||
}
|
||||
|
||||
CloseButtonItem::CloseButtonItem(QGraphicsItem *parent): DivePixmapItem(parent)
|
||||
{
|
||||
static QPixmap p = QPixmap(":list-remove-icon");
|
||||
setPixmap(p);
|
||||
setFlag(ItemIgnoresTransformations);
|
||||
}
|
||||
|
||||
void CloseButtonItem::mousePressEvent(QGraphicsSceneMouseEvent *)
|
||||
{
|
||||
emit clicked();
|
||||
}
|
||||
|
||||
DivePictureItem::DivePictureItem(QGraphicsItem *parent): DivePixmapItem(parent),
|
||||
canvas(new QGraphicsRectItem(this)),
|
||||
shadow(new QGraphicsRectItem(this)),
|
||||
button(new CloseButtonItem(this)),
|
||||
baseZValue(0.0)
|
||||
{
|
||||
setFlag(ItemIgnoresTransformations);
|
||||
setAcceptHoverEvents(true);
|
||||
setScale(0.2);
|
||||
connect(&diveListNotifier, &DiveListNotifier::settingsChanged, this, &DivePictureItem::settingsChanged);
|
||||
connect(button, &CloseButtonItem::clicked, [this] () { emit removePicture(fileUrl); });
|
||||
|
||||
canvas->setPen(Qt::NoPen);
|
||||
canvas->setBrush(QColor(Qt::white));
|
||||
canvas->setFlag(ItemStacksBehindParent);
|
||||
canvas->setZValue(-1);
|
||||
|
||||
shadow->setPos(5,5);
|
||||
shadow->setPen(Qt::NoPen);
|
||||
shadow->setBrush(QColor(Qt::lightGray));
|
||||
shadow->setFlag(ItemStacksBehindParent);
|
||||
shadow->setZValue(-2);
|
||||
|
||||
button->setScale(0.2);
|
||||
button->setZValue(7);
|
||||
button->hide();
|
||||
}
|
||||
|
||||
// The base z-value is used for correct paint-order of the thumbnails. On hoverEnter the z-value is raised
|
||||
// so that the thumbnail is drawn on top of all other thumbnails and on hoverExit it is restored to the base value.
|
||||
void DivePictureItem::setBaseZValue(double z)
|
||||
{
|
||||
baseZValue = z;
|
||||
setZValue(z);
|
||||
}
|
||||
|
||||
void DivePictureItem::settingsChanged()
|
||||
{
|
||||
setVisible(prefs.show_pictures_in_profile);
|
||||
}
|
||||
|
||||
void DivePictureItem::setPixmap(const QPixmap &pix)
|
||||
{
|
||||
DivePixmapItem::setPixmap(pix);
|
||||
QRectF r = boundingRect();
|
||||
canvas->setRect(0 - 10, 0 -10, r.width() + 20, r.height() + 20);
|
||||
shadow->setRect(canvas->rect());
|
||||
button->setPos(boundingRect().width() - button->boundingRect().width() * 0.2,
|
||||
boundingRect().height() - button->boundingRect().height() * 0.2);
|
||||
}
|
||||
|
||||
void DivePictureItem::hoverEnterEvent(QGraphicsSceneHoverEvent*)
|
||||
{
|
||||
Animations::scaleTo(this, qPrefDisplay::animation_speed(), 1.0);
|
||||
setZValue(baseZValue + 5.0);
|
||||
|
||||
button->setOpacity(0);
|
||||
button->show();
|
||||
Animations::show(button, qPrefDisplay::animation_speed());
|
||||
}
|
||||
|
||||
void DivePictureItem::setFileUrl(const QString &s)
|
||||
{
|
||||
fileUrl = s;
|
||||
}
|
||||
|
||||
void DivePictureItem::hoverLeaveEvent(QGraphicsSceneHoverEvent*)
|
||||
{
|
||||
Animations::scaleTo(this, qPrefDisplay::animation_speed(), 0.2);
|
||||
setZValue(baseZValue);
|
||||
Animations::hide(button, qPrefDisplay::animation_speed());
|
||||
}
|
||||
|
||||
void DivePictureItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(localFilePath(fileUrl)));
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef DIVEPIXMAPITEM_H
|
||||
#define DIVEPIXMAPITEM_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QGraphicsPixmapItem>
|
||||
|
||||
class DivePixmapItem : public QObject, public QGraphicsPixmapItem {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity)
|
||||
Q_PROPERTY(QPointF pos WRITE setPos READ pos)
|
||||
Q_PROPERTY(qreal x WRITE setX READ x)
|
||||
Q_PROPERTY(qreal y WRITE setY READ y)
|
||||
public:
|
||||
DivePixmapItem(QGraphicsItem *parent = 0);
|
||||
};
|
||||
|
||||
class CloseButtonItem : public DivePixmapItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CloseButtonItem(QGraphicsItem *parent = 0);
|
||||
signals:
|
||||
void clicked();
|
||||
private:
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
};
|
||||
|
||||
class DivePictureItem : public DivePixmapItem {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(qreal scale WRITE setScale READ scale)
|
||||
public:
|
||||
DivePictureItem(QGraphicsItem *parent = 0);
|
||||
void setPixmap(const QPixmap& pix);
|
||||
void setBaseZValue(double z);
|
||||
void setFileUrl(const QString& s);
|
||||
signals:
|
||||
void removePicture(const QString &fileUrl);
|
||||
public slots:
|
||||
void settingsChanged();
|
||||
private:
|
||||
void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
|
||||
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event);
|
||||
QString fileUrl;
|
||||
QGraphicsRectItem *canvas;
|
||||
QGraphicsRectItem *shadow;
|
||||
CloseButtonItem *button;
|
||||
double baseZValue;
|
||||
};
|
||||
|
||||
#endif // DIVEPIXMAPITEM_H
|
||||
@ -9,7 +9,8 @@
|
||||
#include "core/settings/qPrefTechnicalDetails.h"
|
||||
#include "core/settings/qPrefLog.h"
|
||||
#include "libdivecomputer/parser.h"
|
||||
#include "profile-widget/profilewidget2.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
AbstractProfilePolygonItem::AbstractProfilePolygonItem(const plot_info &pInfo, const DiveCartesianAxis &horizontal,
|
||||
const DiveCartesianAxis &vertical, DataAccessor accessor,
|
||||
|
||||
@ -5,8 +5,6 @@
|
||||
#include <QGraphicsPolygonItem>
|
||||
#include <memory>
|
||||
|
||||
#include "divelineitem.h"
|
||||
|
||||
/* This is the Profile Item, it should be used for quite a lot of things
|
||||
on the profile view. The usage should be pretty simple:
|
||||
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "diverectitem.h"
|
||||
|
||||
DiveRectItem::DiveRectItem(QObject *parent, QGraphicsItem *parentItem) : QObject(parent), QGraphicsRectItem(parentItem)
|
||||
{
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef DIVERECTITEM_H
|
||||
#define DIVERECTITEM_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QGraphicsRectItem>
|
||||
|
||||
class DiveRectItem : public QObject, public QGraphicsRectItem {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QRectF rect WRITE setRect READ rect)
|
||||
Q_PROPERTY(QPointF pos WRITE setPos READ pos)
|
||||
Q_PROPERTY(qreal x WRITE setX READ x)
|
||||
Q_PROPERTY(qreal y WRITE setY READ y)
|
||||
public:
|
||||
DiveRectItem(QObject *parent = 0, QGraphicsItem *parentItem = 0);
|
||||
};
|
||||
|
||||
#endif // DIVERECTITEM_H
|
||||
@ -1,11 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "divetextitem.h"
|
||||
#include "profilewidget2.h"
|
||||
#include "core/color.h"
|
||||
#include "core/errorhelper.h"
|
||||
|
||||
#include <QBrush>
|
||||
#include <cmath>
|
||||
#include <QApplication>
|
||||
#include <QBrush>
|
||||
#include <QPainter>
|
||||
|
||||
static const double outlineSize = 3.0;
|
||||
|
||||
@ -89,6 +90,11 @@ double DiveTextItem::fontHeight(double dpr, double scale)
|
||||
return (double)fm.height();
|
||||
}
|
||||
|
||||
double DiveTextItem::width() const
|
||||
{
|
||||
return boundingRect().width();
|
||||
}
|
||||
|
||||
double DiveTextItem::height() const
|
||||
{
|
||||
return fontHeight(dpr, scale) + outlineSize * dpr;
|
||||
|
||||
@ -2,17 +2,12 @@
|
||||
#ifndef DIVETEXTITEM_H
|
||||
#define DIVETEXTITEM_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFont>
|
||||
#include <QGraphicsPixmapItem>
|
||||
|
||||
class QBrush;
|
||||
|
||||
/* A Line Item that has animated-properties. */
|
||||
class DiveTextItem : public QObject, public QGraphicsPixmapItem {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QPointF pos READ pos WRITE setPos)
|
||||
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity)
|
||||
class DiveTextItem : public QGraphicsPixmapItem {
|
||||
public:
|
||||
// Note: vertical centring is based on the actual rendered text, not on the font metrics.
|
||||
// This is fine for placing text in the "tankbar", but it will look disastrous when
|
||||
@ -22,6 +17,7 @@ public:
|
||||
const QString &text();
|
||||
static double fontHeight(double dpr, double scale);
|
||||
static std::pair<double, double> getLabelSize(double dpr, double scale, const QString &label);
|
||||
double width() const;
|
||||
double height() const;
|
||||
|
||||
private:
|
||||
|
||||
@ -1,279 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "profile-widget/divetooltipitem.h"
|
||||
#include "profile-widget/divecartesianaxis.h"
|
||||
#include "core/membuffer.h"
|
||||
#include "core/metrics.h"
|
||||
#include "core/settings/qPrefDisplay.h"
|
||||
#include "core/settings/qPrefTechnicalDetails.h"
|
||||
#include <QPropertyAnimation>
|
||||
#include <QGraphicsView>
|
||||
#include "core/qthelper.h"
|
||||
|
||||
void ToolTipItem::addToolTip(const QString &toolTip, const QPixmap &pixmap)
|
||||
{
|
||||
const IconMetrics &iconMetrics = defaultIconMetrics();
|
||||
|
||||
QGraphicsPixmapItem *iconItem = 0;
|
||||
double yValue = title->boundingRect().height() + iconMetrics.spacing;
|
||||
for (ToolTip t: toolTips)
|
||||
yValue += t.second->boundingRect().height();
|
||||
if (entryToolTip.second)
|
||||
yValue += entryToolTip.second->boundingRect().height();
|
||||
iconItem = new QGraphicsPixmapItem(this);
|
||||
if (!pixmap.isNull())
|
||||
iconItem->setPixmap(pixmap);
|
||||
const int sp2 = iconMetrics.spacing * 2;
|
||||
iconItem->setPos(sp2, yValue);
|
||||
|
||||
QGraphicsTextItem *textItem = new QGraphicsTextItem(this);
|
||||
textItem->setHtml(toolTip);
|
||||
textItem->setPos(sp2 + iconMetrics.sz_small + sp2, yValue);
|
||||
textItem->setDefaultTextColor(Qt::white);
|
||||
textItem->setFlag(ItemIgnoresTransformations);
|
||||
toolTips.push_back(qMakePair(iconItem, textItem));
|
||||
}
|
||||
|
||||
void ToolTipItem::clear()
|
||||
{
|
||||
for (ToolTip t: toolTips) {
|
||||
delete t.first;
|
||||
delete t.second;
|
||||
}
|
||||
toolTips.clear();
|
||||
}
|
||||
|
||||
void ToolTipItem::setRect(const QRectF &r)
|
||||
{
|
||||
if (r == rect())
|
||||
return;
|
||||
|
||||
QGraphicsRectItem::setRect(r);
|
||||
updateTitlePosition();
|
||||
}
|
||||
|
||||
void ToolTipItem::collapse()
|
||||
{
|
||||
int dim = defaultIconMetrics().sz_small;
|
||||
|
||||
if (qPrefDisplay::animation_speed()) {
|
||||
QPropertyAnimation *animation = new QPropertyAnimation(this, "rect");
|
||||
animation->setDuration(100);
|
||||
animation->setStartValue(nextRectangle);
|
||||
animation->setEndValue(QRect(0, 0, dim, dim));
|
||||
animation->start(QAbstractAnimation::DeleteWhenStopped);
|
||||
} else {
|
||||
setRect(nextRectangle);
|
||||
}
|
||||
clear();
|
||||
|
||||
status = COLLAPSED;
|
||||
}
|
||||
|
||||
void ToolTipItem::expand()
|
||||
{
|
||||
if (!title)
|
||||
return;
|
||||
|
||||
const IconMetrics &iconMetrics = defaultIconMetrics();
|
||||
|
||||
double width = 0, height = title->boundingRect().height() + iconMetrics.spacing;
|
||||
for (const ToolTip &t: toolTips) {
|
||||
QRectF sRect = t.second->boundingRect();
|
||||
if (sRect.width() > width)
|
||||
width = sRect.width();
|
||||
height += sRect.height();
|
||||
}
|
||||
|
||||
if (entryToolTip.first) {
|
||||
QRectF sRect = entryToolTip.second->boundingRect();
|
||||
if (sRect.width() > width)
|
||||
width = sRect.width();
|
||||
height += sRect.height();
|
||||
}
|
||||
|
||||
const int sp2 = iconMetrics.spacing * 2;
|
||||
// pixmap left padding, icon, pixmap right padding, right padding
|
||||
width += sp2 + iconMetrics.sz_small + sp2 + sp2 * 2;
|
||||
// bottom padding
|
||||
height += sp2;
|
||||
|
||||
// clip the tooltip width
|
||||
if (width < title->boundingRect().width() + sp2)
|
||||
width = title->boundingRect().width() + sp2;
|
||||
// clip the height
|
||||
if (entryToolTip.first) {
|
||||
const int minH = lrint(entryToolTip.first->y() + entryToolTip.first->pixmap().height() + sp2);
|
||||
if (height < minH)
|
||||
height = minH;
|
||||
} else if (height < iconMetrics.sz_small) {
|
||||
height = iconMetrics.sz_small;
|
||||
}
|
||||
|
||||
nextRectangle.setWidth(width);
|
||||
nextRectangle.setHeight(height);
|
||||
|
||||
if (nextRectangle != rect()) {
|
||||
if (qPrefDisplay::animation_speed()) {
|
||||
QPropertyAnimation *animation = new QPropertyAnimation(this, "rect", this);
|
||||
animation->setDuration(prefs.animation_speed);
|
||||
animation->setStartValue(rect());
|
||||
animation->setEndValue(nextRectangle);
|
||||
animation->start(QAbstractAnimation::DeleteWhenStopped);
|
||||
} else {
|
||||
setRect(nextRectangle);
|
||||
}
|
||||
}
|
||||
|
||||
status = EXPANDED;
|
||||
}
|
||||
|
||||
ToolTipItem::ToolTipItem(QGraphicsItem *parent) : RoundRectItem(8.0, parent),
|
||||
title(new QGraphicsSimpleTextItem(tr("Information"), this)),
|
||||
status(COLLAPSED),
|
||||
tissues(16,60),
|
||||
painter(&tissues),
|
||||
timeAxis(0),
|
||||
lastTime(-1)
|
||||
{
|
||||
clearPlotInfo();
|
||||
entryToolTip.first = NULL;
|
||||
entryToolTip.second = NULL;
|
||||
setFlags(ItemIgnoresTransformations | ItemIsMovable | ItemClipsChildrenToShape);
|
||||
|
||||
QColor c = QColor(Qt::black);
|
||||
c.setAlpha(155);
|
||||
setBrush(c);
|
||||
|
||||
setZValue(99);
|
||||
|
||||
addToolTip(QString(), QPixmap(16,60));
|
||||
entryToolTip = toolTips.first();
|
||||
toolTips.clear();
|
||||
|
||||
title->setFlag(ItemIgnoresTransformations);
|
||||
title->setPen(QPen(Qt::white, 1));
|
||||
title->setBrush(Qt::white);
|
||||
|
||||
setPen(QPen(Qt::white, 2));
|
||||
|
||||
connect(qPrefTechnicalDetails::instance(), &qPrefTechnicalDetails::infoboxChanged, this, &ToolTipItem::settingsChanged);
|
||||
|
||||
refreshTime.start();
|
||||
}
|
||||
|
||||
ToolTipItem::~ToolTipItem()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void ToolTipItem::updateTitlePosition()
|
||||
{
|
||||
const IconMetrics &iconMetrics = defaultIconMetrics();
|
||||
if (rect().width() < title->boundingRect().width() + iconMetrics.spacing * 4) {
|
||||
QRectF newRect = rect();
|
||||
newRect.setWidth(title->boundingRect().width() + iconMetrics.spacing * 4);
|
||||
newRect.setHeight((newRect.height() && isExpanded()) ? newRect.height() : iconMetrics.sz_small);
|
||||
setRect(newRect);
|
||||
}
|
||||
|
||||
title->setPos(rect().width() / 2 - title->boundingRect().width() / 2 - 1, 0);
|
||||
}
|
||||
|
||||
bool ToolTipItem::isExpanded() const
|
||||
{
|
||||
return status == EXPANDED;
|
||||
}
|
||||
|
||||
void ToolTipItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
persistPos();
|
||||
QGraphicsRectItem::mouseReleaseEvent(event);
|
||||
for (QGraphicsItem *item: oldSelection)
|
||||
item->setSelected(true);
|
||||
}
|
||||
|
||||
void ToolTipItem::persistPos() const
|
||||
{
|
||||
qPrefDisplay::set_tooltip_position(pos());
|
||||
}
|
||||
|
||||
void ToolTipItem::readPos()
|
||||
{
|
||||
QPointF value = qPrefDisplay::tooltip_position();
|
||||
if (!scene()->sceneRect().contains(value))
|
||||
value = QPointF(0, 0);
|
||||
setPos(value);
|
||||
}
|
||||
|
||||
void ToolTipItem::setPlotInfo(const plot_info &plot)
|
||||
{
|
||||
pInfo = plot;
|
||||
}
|
||||
|
||||
void ToolTipItem::clearPlotInfo()
|
||||
{
|
||||
memset(&pInfo, 0, sizeof(pInfo));
|
||||
}
|
||||
|
||||
void ToolTipItem::setTimeAxis(DiveCartesianAxis *axis)
|
||||
{
|
||||
timeAxis = axis;
|
||||
}
|
||||
|
||||
void ToolTipItem::refresh(const dive *d, const QPointF &pos, bool inPlanner)
|
||||
{
|
||||
struct membufferpp mb;
|
||||
|
||||
if(refreshTime.elapsed() < 40)
|
||||
return;
|
||||
refreshTime.start();
|
||||
|
||||
int time = lrint(timeAxis->valueAt(pos));
|
||||
|
||||
lastTime = time;
|
||||
clear();
|
||||
|
||||
int idx = get_plot_details_new(d, &pInfo, time, &mb);
|
||||
|
||||
tissues.fill();
|
||||
painter.setPen(QColor(0, 0, 0, 0));
|
||||
painter.setBrush(QColor(LIMENADE1));
|
||||
painter.drawRect(0, 10 + (100 - AMB_PERCENTAGE) / 2, 16, AMB_PERCENTAGE / 2);
|
||||
painter.setBrush(QColor(SPRINGWOOD1));
|
||||
painter.drawRect(0, 10, 16, (100 - AMB_PERCENTAGE) / 2);
|
||||
painter.setBrush(QColor(Qt::red));
|
||||
painter.drawRect(0,0,16,10);
|
||||
if (idx) {
|
||||
const struct plot_data *entry = &pInfo.entry[idx];
|
||||
painter.setPen(QColor(0, 0, 0, 255));
|
||||
if (decoMode(inPlanner) == BUEHLMANN)
|
||||
painter.drawLine(0, lrint(60 - entry->gfline / 2), 16, lrint(60 - entry->gfline / 2));
|
||||
painter.drawLine(0, lrint(60 - AMB_PERCENTAGE * (entry->pressures.n2 + entry->pressures.he) / entry->ambpressure / 2),
|
||||
16, lrint(60 - AMB_PERCENTAGE * (entry->pressures.n2 + entry->pressures.he) / entry->ambpressure /2));
|
||||
painter.setPen(QColor(0, 0, 0, 127));
|
||||
for (int i = 0; i < 16; i++)
|
||||
painter.drawLine(i, 60, i, 60 - entry->percentages[i] / 2);
|
||||
entryToolTip.second->setPlainText(QString::fromUtf8(mb.buffer, mb.len));
|
||||
}
|
||||
entryToolTip.first->setPixmap(tissues);
|
||||
|
||||
const auto l = scene()->items(pos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder,
|
||||
scene()->views().first()->transform());
|
||||
for (QGraphicsItem *item: l) {
|
||||
if (!item->toolTip().isEmpty())
|
||||
addToolTip(item->toolTip(), QPixmap());
|
||||
}
|
||||
expand();
|
||||
}
|
||||
|
||||
void ToolTipItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
oldSelection = scene()->selectedItems();
|
||||
scene()->clearSelection();
|
||||
QGraphicsItem::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void ToolTipItem::settingsChanged(bool value)
|
||||
{
|
||||
setVisible(value);
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef DIVETOOLTIPITEM_H
|
||||
#define DIVETOOLTIPITEM_H
|
||||
|
||||
#include <QVector>
|
||||
#include <QPair>
|
||||
#include <QRectF>
|
||||
#include <QIcon>
|
||||
#include <QElapsedTimer>
|
||||
#include <QPainter>
|
||||
#include "backend-shared/roundrectitem.h"
|
||||
#include "core/profile.h"
|
||||
|
||||
struct dive;
|
||||
class DiveCartesianAxis;
|
||||
class QGraphicsLineItem;
|
||||
class QGraphicsSimpleTextItem;
|
||||
class QGraphicsPixmapItem;
|
||||
|
||||
/* To use a tooltip, simply ->setToolTip on the QGraphicsItem that you want
|
||||
* or, if it's a "global" tooltip, set it on the mouseMoveEvent of the ProfileGraphicsView.
|
||||
*/
|
||||
class ToolTipItem : public QObject, public RoundRectItem {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QRectF rect READ rect WRITE setRect)
|
||||
|
||||
public:
|
||||
enum Status {
|
||||
COLLAPSED,
|
||||
EXPANDED
|
||||
};
|
||||
|
||||
explicit ToolTipItem(QGraphicsItem *parent = 0);
|
||||
~ToolTipItem();
|
||||
|
||||
void refresh(const dive *d, const QPointF &pos, bool inPlanner);
|
||||
void readPos();
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event);
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
|
||||
void setTimeAxis(DiveCartesianAxis *axis);
|
||||
void setPlotInfo(const plot_info &plot);
|
||||
void clearPlotInfo();
|
||||
void settingsChanged(bool value);
|
||||
public
|
||||
slots:
|
||||
void setRect(const QRectF &rect);
|
||||
|
||||
private:
|
||||
typedef QPair<QGraphicsPixmapItem *, QGraphicsTextItem *> ToolTip;
|
||||
QVector<ToolTip> toolTips;
|
||||
ToolTip entryToolTip;
|
||||
QGraphicsSimpleTextItem *title;
|
||||
Status status;
|
||||
QPixmap tissues;
|
||||
QPainter painter;
|
||||
QRectF rectangle;
|
||||
QRectF nextRectangle;
|
||||
DiveCartesianAxis *timeAxis;
|
||||
plot_info pInfo;
|
||||
int lastTime;
|
||||
QElapsedTimer refreshTime;
|
||||
QList<QGraphicsItem*> oldSelection;
|
||||
|
||||
void addToolTip(const QString &toolTip, const QPixmap &pixmap);
|
||||
void collapse();
|
||||
void expand();
|
||||
void clear();
|
||||
bool isExpanded() const;
|
||||
void persistPos() const;
|
||||
void updateTitlePosition();
|
||||
};
|
||||
|
||||
#endif // DIVETOOLTIPITEM_H
|
||||
153
profile-widget/handleitem.cpp
Normal file
153
profile-widget/handleitem.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "handleitem.h"
|
||||
#include "profileview.h"
|
||||
#include "zvalues.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
static QColor handleBorderColor(Qt::black);
|
||||
static QColor handleColor(Qt::white);
|
||||
static QColor gasColor(Qt::black);
|
||||
static constexpr double handleRadius = 5.0;
|
||||
|
||||
class HandleItemHandle : public ChartDiskItem {
|
||||
ProfileView &profileView;
|
||||
int idx;
|
||||
public:
|
||||
HandleItemHandle(ChartView &view, double dpr, int idx, ProfileView &profileView) :
|
||||
ChartDiskItem(view,
|
||||
ProfileZValue::Handles,
|
||||
QPen(handleBorderColor, dpr),
|
||||
QBrush(handleColor),
|
||||
true),
|
||||
profileView(profileView),
|
||||
idx(idx)
|
||||
{
|
||||
}
|
||||
void setIdx(int idxIn)
|
||||
{
|
||||
idx = idxIn;
|
||||
}
|
||||
void drag(QPointF pos) override
|
||||
{
|
||||
profileView.handleDragged(idx, pos);
|
||||
}
|
||||
void startDrag(QPointF) override
|
||||
{
|
||||
profileView.handleSelected(idx);
|
||||
}
|
||||
void stopDrag(QPointF) override
|
||||
{
|
||||
profileView.handleReleased(idx);
|
||||
}
|
||||
};
|
||||
|
||||
HandleItem::HandleItem(ProfileView &view, double dpr, int idx) :
|
||||
handle(view.createChartItem<HandleItemHandle>(dpr, idx, view)),
|
||||
dpr(dpr),
|
||||
view(view)
|
||||
{
|
||||
handle->resize(handleRadius * dpr);
|
||||
}
|
||||
|
||||
HandleItem::~HandleItem()
|
||||
{
|
||||
}
|
||||
|
||||
void HandleItem::del()
|
||||
{
|
||||
handle.del();
|
||||
if (text)
|
||||
text.del();
|
||||
}
|
||||
|
||||
void HandleItem::setIdx(int idx)
|
||||
{
|
||||
handle->setIdx(idx);
|
||||
}
|
||||
|
||||
void HandleItem::setPos(QPointF pos)
|
||||
{
|
||||
handle->setPos(pos);
|
||||
}
|
||||
|
||||
QPointF HandleItem::getPos() const
|
||||
{
|
||||
return handle->getPos();
|
||||
}
|
||||
|
||||
void HandleItem::setTextPos(QPointF pos)
|
||||
{
|
||||
if (text)
|
||||
text->setPos(pos);
|
||||
}
|
||||
|
||||
void HandleItem::setVisible(bool handleVisible, bool textVisible)
|
||||
{
|
||||
handle->setVisible(handleVisible);
|
||||
if (text)
|
||||
text->setVisible(textVisible);
|
||||
}
|
||||
|
||||
// duplicate code in tooltipitem.cpp
|
||||
static QFont makeFont(double dpr)
|
||||
{
|
||||
QFont font(qApp->font());
|
||||
if (dpr != 1.0) {
|
||||
int pixelSize = font.pixelSize();
|
||||
if (pixelSize > 0) {
|
||||
pixelSize = lrint(static_cast<double>(pixelSize) * dpr);
|
||||
font.setPixelSize(pixelSize);
|
||||
} else {
|
||||
font.setPointSizeF(font.pointSizeF() * dpr);
|
||||
}
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
void HandleItem::setText(const QString &s)
|
||||
{
|
||||
if (text && std::exchange(oldText, s) == s)
|
||||
return;
|
||||
if (text)
|
||||
text.del();
|
||||
|
||||
QFont f = makeFont(dpr);
|
||||
text = view.createChartItem<ChartTextItem>(ProfileZValue::Handles, f, s);
|
||||
text->setColor(gasColor);
|
||||
}
|
||||
|
||||
/*
|
||||
void HandleItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
|
||||
{
|
||||
QMenu m;
|
||||
// Don't have a gas selection for the last point
|
||||
emit released();
|
||||
DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
|
||||
QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS);
|
||||
if (index.sibling(index.row() + 1, index.column()).isValid()) {
|
||||
QStringList gases = get_dive_gas_list(dive);
|
||||
for (int i = 0; i < gases.size(); i++) {
|
||||
QAction *action = new QAction(&m);
|
||||
action->setText(gases[i]);
|
||||
action->setData(i);
|
||||
connect(action, &QAction::triggered, this, &HandleItem::changeGas);
|
||||
m.addAction(action);
|
||||
}
|
||||
}
|
||||
// don't allow removing the last point
|
||||
if (plannerModel->rowCount() > 1) {
|
||||
m.addSeparator();
|
||||
m.addAction(gettextFromC::tr("Remove this point"), this, &HandleItem::selfRemove);
|
||||
m.exec(event->screenPos());
|
||||
}
|
||||
}
|
||||
|
||||
void HandleItem::changeGas()
|
||||
{
|
||||
QAction *action = qobject_cast<QAction *>(sender());
|
||||
DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance();
|
||||
QModelIndex index = plannerModel->index(parentIndex(), DivePlannerPointsModel::GAS);
|
||||
plannerModel->gasChange(index.sibling(index.row() + 1, index.column()), action->data().toInt());
|
||||
}
|
||||
*/
|
||||
38
profile-widget/handleitem.h
Normal file
38
profile-widget/handleitem.h
Normal file
@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef DIVEHANDLER_HPP
|
||||
#define DIVEHANDLER_HPP
|
||||
|
||||
#include "qt-quick/chartitem.h"
|
||||
|
||||
class HandleItemHandle;
|
||||
class HandleItemText;
|
||||
class ProfileView;
|
||||
|
||||
class HandleItem {
|
||||
ChartItemPtr<HandleItemHandle> handle;
|
||||
ChartItemPtr<ChartTextItem> text;
|
||||
QString oldText;
|
||||
public:
|
||||
HandleItem(ProfileView &view, double dpr, int idx);
|
||||
~HandleItem();
|
||||
void setVisible(bool handle, bool text);
|
||||
void setPos(QPointF point);
|
||||
QPointF getPos() const;
|
||||
void setTextPos(QPointF point);
|
||||
void setText(const QString &text);
|
||||
void setIdx(int idx); // we may have to rearrange the handles when editing the dive
|
||||
void del(); // Deletes objects - must not be used any longer
|
||||
private:
|
||||
double dpr;
|
||||
ProfileView &view;
|
||||
protected:
|
||||
//void contextMenuEvent(QGraphicsSceneContextMenuEvent *event);
|
||||
//void mousePressEvent(QGraphicsSceneMouseEvent *event);
|
||||
//void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
|
||||
public
|
||||
slots:
|
||||
//void selfRemove();
|
||||
//void changeGas();
|
||||
};
|
||||
|
||||
#endif
|
||||
91
profile-widget/pictureitem.cpp
Normal file
91
profile-widget/pictureitem.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "pictureitem.h"
|
||||
#include "zvalues.h"
|
||||
#include <cmath>
|
||||
|
||||
static constexpr double scaleFactor = 0.2;
|
||||
static constexpr double shadowSize = 5.0;
|
||||
static constexpr double removeIconSize = 20.0;
|
||||
|
||||
PictureItem::PictureItem(ChartView &view, double dpr) :
|
||||
ChartPixmapItem(view, ProfileZValue::Pictures, false),
|
||||
dpr(dpr)
|
||||
{
|
||||
setScale(scaleFactor); // Start small
|
||||
}
|
||||
|
||||
PictureItem::~PictureItem()
|
||||
{
|
||||
}
|
||||
|
||||
void PictureItem::setPixmap(const QPixmap &picture)
|
||||
{
|
||||
static QPixmap removeIcon = QPixmap(":list-remove-icon");
|
||||
|
||||
int shadowSizeInt = lrint(shadowSize * dpr);
|
||||
resize(QSizeF(picture.width() + shadowSizeInt, picture.height() + shadowSizeInt)); // initializes canvas
|
||||
img->fill(Qt::transparent);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(QColor(Qt::lightGray));
|
||||
painter->drawRect(shadowSizeInt, shadowSizeInt, picture.width(), picture.height());
|
||||
painter->drawPixmap(0, 0, picture, 0, 0, picture.width(), picture.height());
|
||||
|
||||
int removeIconSizeInt = lrint(::removeIconSize * dpr);
|
||||
QPixmap icon = removeIcon.scaledToWidth(removeIconSizeInt, Qt::SmoothTransformation);
|
||||
removeIconRect = QRect(picture.width() - icon.width(), 0, icon.width(), icon.height());
|
||||
painter->drawPixmap(picture.width() - icon.width(), 0, icon, 0, 0, icon.width(), icon.height());
|
||||
}
|
||||
|
||||
double PictureItem::left() const
|
||||
{
|
||||
return rect.left();
|
||||
}
|
||||
|
||||
double PictureItem::right() const
|
||||
{
|
||||
return rect.right();
|
||||
}
|
||||
|
||||
bool PictureItem::underMouse(QPointF pos) const
|
||||
{
|
||||
return rect.contains(pos);
|
||||
}
|
||||
|
||||
bool PictureItem::removeIconUnderMouse(QPointF pos) const
|
||||
{
|
||||
if (!underMouse(pos))
|
||||
return false;
|
||||
QPointF pos_rel = (pos - rect.topLeft()) / scale;
|
||||
return removeIconRect.contains(pos_rel);
|
||||
}
|
||||
|
||||
void PictureItem::initAnimation(double scale, int animSpeed)
|
||||
{
|
||||
if (animSpeed <= 0)
|
||||
return setScale(1.0);
|
||||
|
||||
fromScale = this->scale;
|
||||
toScale = scale;
|
||||
}
|
||||
|
||||
void PictureItem::grow(int animSpeed)
|
||||
{
|
||||
initAnimation(1.0, animSpeed);
|
||||
}
|
||||
|
||||
void PictureItem::shrink(int animSpeed)
|
||||
{
|
||||
initAnimation(scaleFactor, animSpeed);
|
||||
}
|
||||
|
||||
static double mid(double from, double to, double fraction)
|
||||
{
|
||||
return fraction == 1.0 ? to
|
||||
: from + (to - from) * fraction;
|
||||
}
|
||||
|
||||
void PictureItem::anim(double progress)
|
||||
{
|
||||
setScale(mid(fromScale, toScale, progress));
|
||||
setPositionDirty();
|
||||
}
|
||||
33
profile-widget/pictureitem.h
Normal file
33
profile-widget/pictureitem.h
Normal file
@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Shows a picture or video on the profile
|
||||
#ifndef PICTUREMAPITEM_H
|
||||
#define PICTUREMAPITEM_H
|
||||
|
||||
#include "qt-quick/chartitem.h"
|
||||
#include <QString>
|
||||
#include <QRectF>
|
||||
|
||||
class QPixmap;
|
||||
|
||||
class PictureItem : public ChartPixmapItem {
|
||||
public:
|
||||
PictureItem(ChartView &view, double dpr);
|
||||
~PictureItem();
|
||||
void setPixmap(const QPixmap &pix);
|
||||
void setFileUrl(const QString &s);
|
||||
double right() const;
|
||||
double left() const;
|
||||
bool underMouse(QPointF pos) const;
|
||||
bool removeIconUnderMouse(QPointF pos) const;
|
||||
void grow(int animSpeed);
|
||||
void shrink(int animSpeed);
|
||||
void anim(double progress);
|
||||
private:
|
||||
double dpr;
|
||||
QRectF removeIconRect;
|
||||
double fromScale, toScale; // For animation
|
||||
|
||||
void initAnimation(double scale, int animSpeed);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -9,6 +9,7 @@
|
||||
#include "tankitem.h"
|
||||
#include "core/device.h"
|
||||
#include "core/event.h"
|
||||
#include "core/eventtype.h"
|
||||
#include "core/pref.h"
|
||||
#include "core/profile.h"
|
||||
#include "core/qthelper.h" // for decoMode()
|
||||
@ -16,37 +17,9 @@
|
||||
#include "core/subsurface-string.h"
|
||||
#include "core/settings/qPrefDisplay.h"
|
||||
#include "qt-models/diveplannermodel.h"
|
||||
#include <QAbstractAnimation>
|
||||
|
||||
static const double diveComputerTextBorder = 1.0;
|
||||
|
||||
// Class for animations (if any). Might want to do our own.
|
||||
class ProfileAnimation : public QAbstractAnimation {
|
||||
ProfileScene &scene;
|
||||
// For historical reasons, speed is actually the duration
|
||||
// (i.e. the reciprocal of speed). Ouch, that hurts.
|
||||
int speed;
|
||||
|
||||
int duration() const override
|
||||
{
|
||||
return speed;
|
||||
}
|
||||
void updateCurrentTime(int time) override
|
||||
{
|
||||
// Note: we explicitly pass 1.0 at the end, so that
|
||||
// the callee can do a simple float comparison for "end".
|
||||
scene.anim(time == speed ? 1.0
|
||||
: static_cast<double>(time) / speed);
|
||||
}
|
||||
public:
|
||||
ProfileAnimation(ProfileScene &scene, int animSpeed) :
|
||||
scene(scene),
|
||||
speed(animSpeed)
|
||||
{
|
||||
start();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, class... Args>
|
||||
T *ProfileScene::createItem(const DiveCartesianAxis &vAxis, DataAccessor accessor, int z, Args&&... args)
|
||||
{
|
||||
@ -120,6 +93,7 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) :
|
||||
[](const plot_data &item) { return 0.0; }, // unused
|
||||
1, dpr)),
|
||||
diveComputerText(new DiveTextItem(dpr, 1.0, Qt::AlignRight | Qt::AlignTop, nullptr)),
|
||||
eventsHiddenText(new DiveTextItem(dpr, 1.0, Qt::AlignRight | Qt::AlignTop, nullptr)),
|
||||
reportedCeiling(createItem<DiveReportedCeiling>(*profileYAxis,
|
||||
[](const plot_data &item) { return (double)item.ceiling; },
|
||||
1, dpr)),
|
||||
@ -170,6 +144,7 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) :
|
||||
|
||||
// Add items to scene
|
||||
addItem(diveComputerText);
|
||||
addItem(eventsHiddenText);
|
||||
addItem(tankItem);
|
||||
addItem(decoModelParameters);
|
||||
addItem(profileYAxis);
|
||||
@ -183,6 +158,8 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) :
|
||||
|
||||
for (AbstractProfilePolygonItem *item: profileItems)
|
||||
addItem(item);
|
||||
|
||||
eventsHiddenText->set(tr("Hidden events!"), getColor(TIME_TEXT, isGrayscale));
|
||||
}
|
||||
|
||||
ProfileScene::~ProfileScene()
|
||||
@ -190,15 +167,16 @@ ProfileScene::~ProfileScene()
|
||||
free_plot_info_data(&plotInfo);
|
||||
}
|
||||
|
||||
const plot_info &ProfileScene::getPlotInfo() const
|
||||
{
|
||||
return plotInfo;
|
||||
}
|
||||
|
||||
void ProfileScene::clear()
|
||||
{
|
||||
for (AbstractProfilePolygonItem *item: profileItems)
|
||||
item->clear();
|
||||
|
||||
// the events will have connected slots which can fire after
|
||||
// the dive and its data have been deleted - so explictly delete
|
||||
// the DiveEventItems
|
||||
qDeleteAll(eventItems);
|
||||
eventItems.clear();
|
||||
free_plot_info_data(&plotInfo);
|
||||
empty = true;
|
||||
@ -314,8 +292,10 @@ void ProfileScene::updateAxes(bool diveHasHeartBeat, bool simplified)
|
||||
profileYAxis->setGridIsMultipleOfThree( qPrefDisplay::three_m_based_grid() );
|
||||
|
||||
// Place the fixed dive computer text at the bottom
|
||||
double bottomBorder = sceneRect().height() - diveComputerText->height() - 2.0 * dpr * diveComputerTextBorder;
|
||||
diveComputerText->setPos(0.0, bottomBorder + dpr * diveComputerTextBorder);
|
||||
double bottomTextHeight = std::max(diveComputerText->height(), eventsHiddenText->height());
|
||||
double bottomBorder = sceneRect().height() - bottomTextHeight - 2.0 * dpr * diveComputerTextBorder;
|
||||
diveComputerText->setPos(0.0, round(bottomBorder + dpr * diveComputerTextBorder));
|
||||
eventsHiddenText->setPos(width - dpr * diveComputerTextBorder - eventsHiddenText->width(), bottomBorder + dpr * diveComputerTextBorder);
|
||||
|
||||
double topBorder = 0.0;
|
||||
|
||||
@ -394,6 +374,17 @@ bool ProfileScene::pointOnProfile(const QPointF &point) const
|
||||
return timeAxis->pointInRange(point.x()) && profileYAxis->pointInRange(point.y());
|
||||
}
|
||||
|
||||
bool ProfileScene::pointOnDiveComputerText(const QPointF &point) const
|
||||
{
|
||||
return diveComputerText->boundingRect().contains(point - diveComputerText->pos());
|
||||
}
|
||||
|
||||
bool ProfileScene::pointOnEventsHiddenText(const QPointF &point) const
|
||||
{
|
||||
return eventsHiddenText->isVisible() &&
|
||||
eventsHiddenText->boundingRect().contains(point - eventsHiddenText->pos());
|
||||
}
|
||||
|
||||
static double max_gas(const plot_info &pi, double gas_pressures::*gas)
|
||||
{
|
||||
double ret = -1;
|
||||
@ -404,8 +395,9 @@ static double max_gas(const plot_info &pi, double gas_pressures::*gas)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsModel *plannerModel,
|
||||
bool inPlanner, bool instant, bool keepPlotInfo, bool calcMax, double zoom, double zoomedPosition)
|
||||
void ProfileScene::plotDive(const struct dive *dIn, int dcIn, int animSpeed, bool simplified,
|
||||
DivePlannerPointsModel *plannerModel,
|
||||
bool inPlanner, bool keepPlotInfo, bool calcMax, double zoom, double zoomedPosition)
|
||||
{
|
||||
d = dIn;
|
||||
dc = dcIn;
|
||||
@ -439,8 +431,6 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
|
||||
keepPlotInfo = false;
|
||||
empty = false;
|
||||
|
||||
int animSpeed = instant || printMode ? 0 : qPrefDisplay::animation_speed();
|
||||
|
||||
// A non-null planner_ds signals to create_plot_info_new that the dive is currently planned.
|
||||
struct deco_state *planner_ds = inPlanner && plannerModel ? &plannerModel->final_deco_state : nullptr;
|
||||
|
||||
@ -457,14 +447,13 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
|
||||
|
||||
bool hasHeartBeat = plotInfo.maxhr;
|
||||
// For mobile we might want to turn of some features that are normally shown.
|
||||
#ifdef SUBSURFACE_MOBILE
|
||||
bool simplified = true;
|
||||
#else
|
||||
bool simplified = false;
|
||||
#endif
|
||||
updateVisibility(hasHeartBeat, simplified);
|
||||
updateAxes(hasHeartBeat, simplified);
|
||||
|
||||
// When we found that we don't have enough place to draw, the state was set to empty.
|
||||
if (empty)
|
||||
return;
|
||||
|
||||
int newMaxtime = get_maxtime(&plotInfo);
|
||||
if (calcMax || newMaxtime > maxtime)
|
||||
maxtime = newMaxtime;
|
||||
@ -546,15 +535,15 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
|
||||
if (prefs.percentagegraph)
|
||||
percentageItem->replot(d, currentdc, plotInfo);
|
||||
|
||||
// The event items are a bit special since we don't know how many events are going to
|
||||
// exist on a dive, so I cant create cache items for that. that's why they are here
|
||||
// while all other items are up there on the constructor.
|
||||
qDeleteAll(eventItems);
|
||||
eventItems.clear();
|
||||
struct event *event = currentdc->events;
|
||||
struct gasmix lastgasmix = get_gasmix_at_time(d, currentdc, duration_t{1});
|
||||
|
||||
while (event) {
|
||||
bool has_hidden_events = false;
|
||||
for (struct event *event = currentdc->events; event; event = event->next) {
|
||||
if (event->hidden || is_event_type_hidden(event)) {
|
||||
has_hidden_events = true;
|
||||
continue;
|
||||
}
|
||||
// if print mode is selected only draw headings, SP change, gas events or bookmark event
|
||||
if (printMode) {
|
||||
if (empty_string(event->name) ||
|
||||
@ -562,21 +551,20 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
|
||||
(same_string(event->name, "SP change") && event->time.seconds == 0) ||
|
||||
event_is_gaschange(event) ||
|
||||
event->type == SAMPLE_EVENT_BOOKMARK)) {
|
||||
event = event->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (DiveEventItem::isInteresting(d, currentdc, event, plotInfo, firstSecond, lastSecond)) {
|
||||
auto item = new DiveEventItem(d, event, lastgasmix, plotInfo,
|
||||
auto item = std::make_unique<DiveEventItem>(d, event, lastgasmix, plotInfo,
|
||||
timeAxis, profileYAxis, animSpeed, *pixmaps);
|
||||
item->setZValue(2);
|
||||
addItem(item);
|
||||
eventItems.push_back(item);
|
||||
addItem(item.get());
|
||||
eventItems.push_back(std::move(item));
|
||||
}
|
||||
if (event_is_gaschange(event))
|
||||
lastgasmix = get_gasmix_from_event(d, event);
|
||||
event = event->next;
|
||||
}
|
||||
eventsHiddenText->setVisible(has_hidden_events);
|
||||
|
||||
QString dcText = get_dc_nickname(currentdc);
|
||||
if (dcText == "planned dive")
|
||||
@ -589,12 +577,6 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
|
||||
if (nr > 1)
|
||||
dcText += tr(" (#%1 of %2)").arg(dc + 1).arg(nr);
|
||||
diveComputerText->set(dcText, getColor(TIME_TEXT, isGrayscale));
|
||||
|
||||
// Reset animation.
|
||||
if (animSpeed <= 0)
|
||||
animation.reset();
|
||||
else
|
||||
animation = std::make_unique<ProfileAnimation>(*this, animSpeed);
|
||||
}
|
||||
|
||||
void ProfileScene::anim(double fraction)
|
||||
@ -609,7 +591,7 @@ void ProfileScene::draw(QPainter *painter, const QRect &pos,
|
||||
{
|
||||
QSize size = pos.size();
|
||||
resize(QSizeF(size));
|
||||
plotDive(d, dc, plannerModel, inPlanner, true, false, true);
|
||||
plotDive(d, dc, 0, false, plannerModel, inPlanner, false, true);
|
||||
|
||||
QImage image(pos.size(), QImage::Format_ARGB32);
|
||||
image.fill(getColor(::BACKGROUND, isGrayscale));
|
||||
@ -649,3 +631,64 @@ double ProfileScene::calcZoomPosition(double zoom, double originalPos, double de
|
||||
double newPos = newRelStart / factor;
|
||||
return std::clamp(newPos, 0.0, 1.0);
|
||||
}
|
||||
|
||||
int ProfileScene::timeAt(QPointF pos) const
|
||||
{
|
||||
return lrint(timeAxis->valueAt(pos));
|
||||
}
|
||||
|
||||
int ProfileScene::depthAt(QPointF pos) const
|
||||
{
|
||||
return lrint(profileYAxis->valueAt(pos));
|
||||
}
|
||||
|
||||
std::pair<double, double> ProfileScene::minMaxTime() const
|
||||
{
|
||||
return { timeAxis->minimum(), timeAxis->maximum() };
|
||||
}
|
||||
|
||||
std::pair<double, double> ProfileScene::minMaxX() const
|
||||
{
|
||||
return timeAxis->screenMinMax();
|
||||
}
|
||||
|
||||
std::pair<double, double> ProfileScene::minMaxY() const
|
||||
{
|
||||
return profileYAxis->screenMinMax();
|
||||
}
|
||||
|
||||
double ProfileScene::yToScreen(double y) const
|
||||
{
|
||||
auto [min, max] = profileYAxis->screenMinMax();
|
||||
return min + (max - min) * y;
|
||||
}
|
||||
|
||||
double ProfileScene::posAtTime(double time) const
|
||||
{
|
||||
return timeAxis->posAtValue(time);
|
||||
}
|
||||
|
||||
double ProfileScene::posAtDepth(double depth) const
|
||||
{
|
||||
return profileYAxis->posAtValue(depth);
|
||||
}
|
||||
|
||||
std::vector<std::pair<QString, QPixmap>> ProfileScene::eventsAt(QPointF pos) const
|
||||
{
|
||||
std::vector<std::pair<QString, QPixmap>> res;
|
||||
for (const auto &item: eventItems) {
|
||||
if (!item->contains(item->mapFromScene(pos)))
|
||||
continue;
|
||||
res.emplace_back(item->text, item->pixmap);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
DiveEventItem *ProfileScene::eventAtPosition(QPointF pos) const
|
||||
{
|
||||
for (const auto &item: eventItems) {
|
||||
if (item->contains(item->mapFromScene(pos)))
|
||||
return item.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -27,7 +27,6 @@ class DiveReportedCeiling;
|
||||
class DiveTemperatureItem;
|
||||
class DiveTextItem;
|
||||
class PartialPressureGasItem;
|
||||
class ProfileAnimation;
|
||||
class TankItem;
|
||||
|
||||
class ProfileScene : public QGraphicsScene {
|
||||
@ -38,20 +37,35 @@ public:
|
||||
void resize(QSizeF size);
|
||||
void clear();
|
||||
bool pointOnProfile(const QPointF &point) const;
|
||||
bool pointOnDiveComputerText(const QPointF &point) const;
|
||||
bool pointOnEventsHiddenText(const QPointF &point) const;
|
||||
void anim(double fraction); // Called by the animation with 0.0-1.0 (start to stop).
|
||||
// Can be compared with literal 1.0 to determine "end" state.
|
||||
|
||||
// If a plannerModel is passed, the deco-information is taken from there.
|
||||
void plotDive(const struct dive *d, int dc, DivePlannerPointsModel *plannerModel = nullptr, bool inPlanner = false,
|
||||
bool instant = false, bool keepPlotInfo = false, bool calcMax = true, double zoom = 1.0, double zoomedPosition = 0.0);
|
||||
void plotDive(const struct dive *d, int dc, int animSpeed, bool simplified,
|
||||
DivePlannerPointsModel *plannerModel = nullptr, bool inPlanner = false,
|
||||
bool keepPlotInfo = false, bool calcMax = true, double zoom = 1.0, double zoomedPosition = 0.0);
|
||||
|
||||
void draw(QPainter *painter, const QRect &pos,
|
||||
const struct dive *d, int dc,
|
||||
DivePlannerPointsModel *plannerModel = nullptr, bool inPlanner = false);
|
||||
double calcZoomPosition(double zoom, double originalPos, double delta);
|
||||
const plot_info &getPlotInfo() const;
|
||||
int timeAt(QPointF pos) const; // time in seconds
|
||||
int depthAt(QPointF pos) const; // depth in mm
|
||||
std::pair<double, double> minMaxTime() const; // time in seconds
|
||||
std::pair<double, double> minMaxX() const; // minimum and maximum x positions of canvas
|
||||
std::pair<double, double> minMaxY() const; // minimum and maximum y positions of canvas
|
||||
double posAtTime(double time) const; // time in seconds
|
||||
double posAtDepth(double depth) const;
|
||||
double yToScreen(double y) const; // For pictures: depth given in fration of displayed range.
|
||||
std::vector<std::pair<QString, QPixmap>> eventsAt(QPointF pos) const;
|
||||
DiveEventItem *eventAtPosition(QPointF pos) const; // null if no event icon at position.
|
||||
|
||||
const struct dive *d;
|
||||
int dc;
|
||||
QRectF profileRegion; // Region inside the axes, where the crosshair is painted in plan and edit mode.
|
||||
private:
|
||||
using DataAccessor = double (*)(const plot_data &data);
|
||||
template<typename T, class... Args> T *createItem(const DiveCartesianAxis &vAxis, DataAccessor accessor, int z, Args&&... args);
|
||||
@ -61,7 +75,6 @@ private:
|
||||
void updateVisibility(bool diveHasHeartBeat, bool simplified); // Update visibility of non-interactive chart features according to preferences
|
||||
void updateAxes(bool diveHasHeartBeat, bool simplified); // Update axes according to preferences
|
||||
|
||||
friend class ProfileWidget2; // For now, give the ProfileWidget full access to the objects on the scene
|
||||
double dpr; // Device Pixel Ratio. A DPR of one corresponds to a "standard" PC screen.
|
||||
bool printMode;
|
||||
bool isGrayscale;
|
||||
@ -70,7 +83,6 @@ private:
|
||||
int maxdepth;
|
||||
|
||||
struct plot_info plotInfo;
|
||||
QRectF profileRegion; // Region inside the axes, where the crosshair is painted in plan and edit mode.
|
||||
DiveCartesianAxis *profileYAxis;
|
||||
DiveCartesianAxis *gasYAxis;
|
||||
DiveCartesianAxis *temperatureAxis;
|
||||
@ -83,8 +95,9 @@ private:
|
||||
DiveTemperatureItem *temperatureItem;
|
||||
DiveMeanDepthItem *meanDepthItem;
|
||||
DiveGasPressureItem *gasPressureItem;
|
||||
QList<DiveEventItem *> eventItems;
|
||||
std::vector<std::unique_ptr<DiveEventItem>> eventItems;
|
||||
DiveTextItem *diveComputerText;
|
||||
DiveTextItem *eventsHiddenText;
|
||||
DiveReportedCeiling *reportedCeiling;
|
||||
PartialPressureGasItem *pn2GasItem;
|
||||
PartialPressureGasItem *pheGasItem;
|
||||
@ -101,7 +114,6 @@ private:
|
||||
DivePercentageItem *percentageItem;
|
||||
TankItem *tankItem;
|
||||
std::shared_ptr<const DivePixmaps> pixmaps;
|
||||
std::unique_ptr<ProfileAnimation> animation;
|
||||
std::vector<DiveCartesianAxis *> animatedAxes;
|
||||
};
|
||||
|
||||
|
||||
14
profile-widget/profiletranslations.h
Normal file
14
profile-widget/profiletranslations.h
Normal file
@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Dummy object for profile-module related translations
|
||||
|
||||
#ifndef PROFILE_TRANSLATIONS_H
|
||||
#define PROFILE_TRANSLATIONS_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class ProfileTranslations : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
};
|
||||
|
||||
#endif
|
||||
1469
profile-widget/profileview.cpp
Normal file
1469
profile-widget/profileview.cpp
Normal file
File diff suppressed because it is too large
Load Diff
189
profile-widget/profileview.h
Normal file
189
profile-widget/profileview.h
Normal file
@ -0,0 +1,189 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef PROFILE_VIEW_H
|
||||
#define PROFILE_VIEW_H
|
||||
|
||||
#include "qt-quick/chartview.h"
|
||||
#include "core/pictureobj.h"
|
||||
#include "core/units.h"
|
||||
#include <memory>
|
||||
|
||||
class ChartGraphicsSceneItem;
|
||||
class ChartLineItem;
|
||||
class ChartRectItem;
|
||||
class DivePlannerPointsModel;
|
||||
class HandleItem;
|
||||
class PictureItem;
|
||||
class ProfileAnimation;
|
||||
class ProfileScene;
|
||||
class ToolTipItem;
|
||||
class RulerItem;
|
||||
class QModelIndex;
|
||||
|
||||
class ProfileView : public ChartView {
|
||||
Q_OBJECT
|
||||
|
||||
// Communication with the mobile interface is via properties. I hate it.
|
||||
Q_PROPERTY(int diveId READ getDiveId WRITE setDiveId)
|
||||
Q_PROPERTY(int numDC READ numDC NOTIFY numDCChanged)
|
||||
Q_PROPERTY(double zoomLevel MEMBER zoomLevel NOTIFY zoomLevelChanged)
|
||||
public:
|
||||
ProfileView();
|
||||
ProfileView(QQuickItem *parent);
|
||||
~ProfileView();
|
||||
|
||||
// Flag set when constructing the object. Because QtQuick may decide to destroy the old one. :(
|
||||
bool initialized;
|
||||
void setPlannerModel(DivePlannerPointsModel &model); // enables planning and edit mdoe
|
||||
|
||||
struct RenderFlags {
|
||||
static constexpr int None = 0;
|
||||
static constexpr int Instant = 1 << 0;
|
||||
static constexpr int DontRecalculatePlotInfo = 1 << 1;
|
||||
static constexpr int EditMode = 1 << 2;
|
||||
static constexpr int PlanMode = 1 << 3;
|
||||
static constexpr int Simplified = 1 << 4; // For mobile's overview page
|
||||
static constexpr int DontCalculateMax = 1 << 5;
|
||||
};
|
||||
|
||||
void plotDive(const struct dive *d, int dc, int flags = RenderFlags::None);
|
||||
int timeAt(QPointF pos) const;
|
||||
void clear();
|
||||
void resetZoom();
|
||||
void anim(double fraction);
|
||||
void rulerDragged(); // Called by the RulterItem when a handle was dragged.
|
||||
void handleSelected(int idx); // Called by the HandleItem when it is clicked.
|
||||
void handleDragged(int idx, QPointF pos); // Called by the HandleItem when it is dragged.
|
||||
void handleReleased(int idx); // Called by the HandleItem when it is released.
|
||||
|
||||
// For mobile
|
||||
Q_INVOKABLE void pinchStart();
|
||||
Q_INVOKABLE void pinch(double factor);
|
||||
Q_INVOKABLE void nextDC();
|
||||
Q_INVOKABLE void prevDC();
|
||||
Q_INVOKABLE void panStart(double x, double y);
|
||||
Q_INVOKABLE void pan(double x, double y);
|
||||
|
||||
signals:
|
||||
void numDCChanged();
|
||||
void zoomLevelChanged();
|
||||
void stopAdded(); // only emitted in edit mode
|
||||
void stopRemoved(int count); // only emitted in edit mode
|
||||
void stopMoved(int count); // only emitted in edit mode
|
||||
private:
|
||||
enum Mode {
|
||||
Normal,
|
||||
Edit,
|
||||
Plan
|
||||
};
|
||||
const struct dive *d;
|
||||
int dc;
|
||||
DivePlannerPointsModel *plannerModel;
|
||||
Mode mode;
|
||||
bool simplified;
|
||||
double dpr;
|
||||
double zoomLevel, zoomLevelPinchStart;
|
||||
double zoomedPosition; // Position when zoomed: 0.0 = beginning, 1.0 = end.
|
||||
bool panning; // Currently panning.
|
||||
double panningOriginalMousePosition;
|
||||
double panningOriginalProfilePosition;
|
||||
bool empty; // No dive shown.
|
||||
QColor background;
|
||||
std::unique_ptr<ProfileScene> profileScene;
|
||||
ChartItemPtr<ChartGraphicsSceneItem> profileItem;
|
||||
std::unique_ptr<ProfileAnimation> animation;
|
||||
ChartItemPtr<ChartLineItem> mouseFollowerHorizontal, mouseFollowerVertical;
|
||||
dive *mutable_dive() const; // for functions manipulating the dive
|
||||
|
||||
void plotAreaChanged(const QSizeF &size) override;
|
||||
void resetPointers() override;
|
||||
void replot();
|
||||
int rerenderFlags() const;
|
||||
void setZoom(double level);
|
||||
|
||||
void hoverEnterEvent(QHoverEvent *event) override;
|
||||
void hoverMoveEvent(QHoverEvent *event) override;
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void contextMenu(const QPointF pos, const QPoint globalPos);
|
||||
void addSetpointChange(int seconds);
|
||||
|
||||
ChartItemPtr<ToolTipItem> tooltip;
|
||||
void updateTooltip(QPointF pos, bool plannerMode, int animSpeed);
|
||||
std::unique_ptr<ProfileAnimation> tooltip_animation;
|
||||
|
||||
std::unique_ptr<RulerItem> ruler;
|
||||
void updateRuler(int animSpeed);
|
||||
std::unique_ptr<ProfileAnimation> ruler_animation;
|
||||
|
||||
void updateMouseFollowers(QPointF pos);
|
||||
|
||||
QPointF previousHoverMovePosition;
|
||||
std::vector<std::unique_ptr<HandleItem>> handles;
|
||||
int selectedHandleIdx;
|
||||
void clearHandles();
|
||||
void resetHandles();
|
||||
void placeHandles();
|
||||
void reindexHandles();
|
||||
void moveHandle(int time, int depth);
|
||||
void deleteHandle();
|
||||
|
||||
void pointsInserted(const QModelIndex &, int from, int to);
|
||||
void pointsRemoved(const QModelIndex &, int start, int end);
|
||||
void pointsMoved(const QModelIndex &, int start, int end, const QModelIndex &destination, int row);
|
||||
|
||||
// DC related
|
||||
void renameCurrentDC();
|
||||
|
||||
// Event related
|
||||
void editEventName(struct event *event);
|
||||
void unhideEvents();
|
||||
|
||||
// The list of pictures in this plot. The pictures are sorted by offset in seconds.
|
||||
// For the same offset, sort by filename.
|
||||
// Pictures that are outside of the dive time are not shown.
|
||||
struct PictureEntry {
|
||||
offset_t offset;
|
||||
double x, y;
|
||||
duration_t duration;
|
||||
QString filename;
|
||||
ChartItemPtr<PictureItem> thumbnail;
|
||||
// For videos with known duration, we represent the duration of the video by a line
|
||||
ChartItemPtr<ChartRectItem> durationLine;
|
||||
std::unique_ptr<ProfileAnimation> animation;
|
||||
PictureEntry (offset_t offset, const QString &filename, ChartItemPtr<PictureItem> thumbnail, double dpr, bool synchronous);
|
||||
bool operator< (const PictureEntry &e) const;
|
||||
};
|
||||
std::vector<PictureEntry> pictures;
|
||||
PictureEntry *highlightedPicture;
|
||||
|
||||
// Picture (media) related functions
|
||||
void picturesRemoved(dive *d, QVector<QString> filenames);
|
||||
void picturesAdded(dive *d, QVector<PictureObj> pics);
|
||||
void pictureOffsetChanged(dive *d, QString filename, offset_t offset);
|
||||
void updateDurationLine(PictureEntry &e);
|
||||
void updateThumbnail(QString filename, QImage thumbnail, duration_t duration);
|
||||
void updateThumbnailPaintOrder();
|
||||
void calculatePictureYPositions();
|
||||
void updateThumbnailXPos(PictureEntry &e);
|
||||
void plotPictures(const struct dive *d, bool synchronous);
|
||||
void updateThumbnails();
|
||||
void clearPictures();
|
||||
void removePictureThumbnail(PictureEntry &entry);
|
||||
void unhighlightPicture();
|
||||
void shrinkPictureItem(PictureEntry &e, int animSpeed);
|
||||
void growPictureItem(PictureEntry &e, int animSpeed);
|
||||
void moveThumbnailBefore(PictureEntry &e, std::vector<PictureEntry>::iterator &before);
|
||||
PictureEntry *getPictureUnderMouse(QPointF pos);
|
||||
|
||||
// For mobile
|
||||
int getDiveId() const;
|
||||
void setDiveId(int id);
|
||||
int numDC() const;
|
||||
void rotateDC(int dir);
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,205 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef PROFILEWIDGET2_H
|
||||
#define PROFILEWIDGET2_H
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// /* The idea of this widget is to display and edit the profile.
|
||||
// * It has:
|
||||
// * 1 - ToolTip / Legend item, displays every information of the current mouse position on it, plus the legends of the maps.
|
||||
// * 2 - ToolBox, displays the QActions that are used to do special stuff on the profile ( like activating the plugins. )
|
||||
// * 3 - Cartesian Axis for depth ( y )
|
||||
// * 4 - Cartesian Axis for Gases ( y )
|
||||
// * 5 - Cartesian Axis for Time ( x )
|
||||
// *
|
||||
// * It needs to be dynamic, things should *flow* on it, not just appear / disappear.
|
||||
// */
|
||||
#include "profile-widget/divelineitem.h"
|
||||
#include "core/pictureobj.h"
|
||||
#include "core/units.h"
|
||||
#include "core/subsurface-qt/divelistnotifier.h"
|
||||
|
||||
class ProfileScene;
|
||||
class RulerItem2;
|
||||
struct dive;
|
||||
class ToolTipItem;
|
||||
class DiveEventItem;
|
||||
class DivePlannerPointsModel;
|
||||
class DiveHandler;
|
||||
class QGraphicsSimpleTextItem;
|
||||
class QModelIndex;
|
||||
class DivePictureItem;
|
||||
|
||||
class ProfileWidget2 : public QGraphicsView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum State {
|
||||
PROFILE,
|
||||
EDIT,
|
||||
PLAN,
|
||||
INIT
|
||||
};
|
||||
|
||||
struct RenderFlags {
|
||||
static constexpr int None = 0;
|
||||
static constexpr int Instant = 1 << 0;
|
||||
static constexpr int DontRecalculatePlotInfo = 1 << 1;
|
||||
};
|
||||
|
||||
// Pass null as plannerModel if no support for planning required
|
||||
ProfileWidget2(DivePlannerPointsModel *plannerModel, double dpr, QWidget *parent = 0);
|
||||
~ProfileWidget2();
|
||||
void resetZoom();
|
||||
void plotDive(const struct dive *d, int dc, int flags = RenderFlags::None);
|
||||
void setProfileState(const struct dive *d, int dc);
|
||||
void setPlanState(const struct dive *d, int dc);
|
||||
void setEditState(const struct dive *d, int dc);
|
||||
bool isPlanner() const;
|
||||
void clear();
|
||||
#ifndef SUBSURFACE_MOBILE
|
||||
bool eventFilter(QObject *, QEvent *) override;
|
||||
#endif
|
||||
std::unique_ptr<ProfileScene> profileScene;
|
||||
State currentState;
|
||||
|
||||
signals:
|
||||
void stopAdded(); // only emitted in edit mode
|
||||
void stopRemoved(int count); // only emitted in edit mode
|
||||
void stopMoved(int count); // only emitted in edit mode
|
||||
|
||||
public
|
||||
slots: // Necessary to call from QAction's signals.
|
||||
void settingsChanged();
|
||||
void actionRequestedReplot(bool triggered);
|
||||
void divesChanged(const QVector<dive *> &dives, DiveField field);
|
||||
#ifndef SUBSURFACE_MOBILE
|
||||
void plotPictures();
|
||||
void picturesRemoved(dive *d, QVector<QString> filenames);
|
||||
void picturesAdded(dive *d, QVector<PictureObj> pics);
|
||||
void pointsReset();
|
||||
void pointInserted(const QModelIndex &parent, int start, int end);
|
||||
void pointsRemoved(const QModelIndex &, int start, int end);
|
||||
void pointsMoved(const QModelIndex &, int start, int end, const QModelIndex &destination, int row);
|
||||
void updateThumbnail(QString filename, QImage thumbnail, duration_t duration);
|
||||
void profileChanged(dive *d);
|
||||
void pictureOffsetChanged(dive *d, QString filename, offset_t offset);
|
||||
void removePicture(const QString &fileUrl);
|
||||
|
||||
/* this is called for every move on the handlers. maybe we can speed up this a bit? */
|
||||
void divePlannerHandlerMoved();
|
||||
void divePlannerHandlerClicked();
|
||||
void divePlannerHandlerReleased();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void setProfileState(); // keep currently displayed dive
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
#ifndef SUBSURFACE_MOBILE
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
#endif
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||
|
||||
void replot();
|
||||
void setZoom(int level);
|
||||
void changeGas(int tank, int seconds);
|
||||
void setupSceneAndFlags();
|
||||
void addItemsToScene();
|
||||
void setupItemOnScene();
|
||||
void disconnectTemporaryConnections();
|
||||
struct plot_data *getEntryFromPos(QPointF pos);
|
||||
void clearPictures();
|
||||
void plotPicturesInternal(const struct dive *d, bool synchronous);
|
||||
void updateThumbnails();
|
||||
void addDivemodeSwitch(int seconds, int divemode);
|
||||
void addBookmark(int seconds);
|
||||
void splitDive(int seconds);
|
||||
void addSetpointChange(int seconds);
|
||||
void removeEvent(DiveEventItem *item);
|
||||
void hideEvent(DiveEventItem *item);
|
||||
void hideEventType(DiveEventItem *item);
|
||||
void editName(DiveEventItem *item);
|
||||
void unhideEvents();
|
||||
void unhideEventTypes();
|
||||
void makeFirstDC();
|
||||
void deleteCurrentDC();
|
||||
void splitCurrentDC();
|
||||
void renameCurrentDC();
|
||||
|
||||
DivePlannerPointsModel *plannerModel; // If null, no planning supported.
|
||||
int zoomLevel;
|
||||
double zoomedPosition; // Position, when zoomed: 0.0 = beginning, 1.0 = end.
|
||||
#ifndef SUBSURFACE_MOBILE
|
||||
ToolTipItem *toolTipItem;
|
||||
#endif
|
||||
const struct dive *d;
|
||||
int dc;
|
||||
bool empty; // No dive shown.
|
||||
bool panning; // Currently panning.
|
||||
double panningOriginalMousePosition;
|
||||
double panningOriginalProfilePosition;
|
||||
#ifndef SUBSURFACE_MOBILE
|
||||
DiveLineItem *mouseFollowerVertical;
|
||||
DiveLineItem *mouseFollowerHorizontal;
|
||||
RulerItem2 *rulerItem;
|
||||
#endif
|
||||
|
||||
std::vector<std::unique_ptr<QGraphicsSimpleTextItem>> gases;
|
||||
|
||||
#ifndef SUBSURFACE_MOBILE
|
||||
// The list of pictures in this plot. The pictures are sorted by offset in seconds.
|
||||
// For the same offset, sort by filename.
|
||||
// Pictures that are outside of the dive time are not shown.
|
||||
struct PictureEntry {
|
||||
offset_t offset;
|
||||
duration_t duration;
|
||||
QString filename;
|
||||
std::unique_ptr<DivePictureItem> thumbnail;
|
||||
// For videos with known duration, we represent the duration of the video by a line
|
||||
std::unique_ptr<QGraphicsRectItem> durationLine;
|
||||
PictureEntry (offset_t offsetIn, const QString &filenameIn, ProfileWidget2 *profile, bool synchronous);
|
||||
bool operator< (const PictureEntry &e) const;
|
||||
};
|
||||
void updateThumbnailXPos(PictureEntry &e);
|
||||
std::vector<PictureEntry> pictures;
|
||||
void calculatePictureYPositions();
|
||||
void updateDurationLine(PictureEntry &e);
|
||||
void updateThumbnailPaintOrder();
|
||||
|
||||
void keyDeleteAction();
|
||||
void keyUpAction();
|
||||
void keyDownAction();
|
||||
void keyLeftAction();
|
||||
void keyRightAction();
|
||||
#endif
|
||||
|
||||
std::vector<std::unique_ptr<DiveHandler>> handles;
|
||||
int handleIndex(const DiveHandler *h) const;
|
||||
#ifndef SUBSURFACE_MOBILE
|
||||
void connectPlannerModel();
|
||||
void repositionDiveHandlers();
|
||||
int fixHandlerIndex(DiveHandler *activeHandler);
|
||||
DiveHandler *createHandle();
|
||||
QGraphicsSimpleTextItem *createGas();
|
||||
#endif
|
||||
friend class DiveHandler;
|
||||
bool shouldCalculateMax; // Calculate maximum time and depth (default). False when dragging handles.
|
||||
std::vector<int> selectedDiveHandleIndices() const;
|
||||
|
||||
// We store a const pointer to the shown dive. However, the undo commands want
|
||||
// (understandably) a non-const pointer. Since the profile has a context-menu
|
||||
// with actions, it needs such a non-const pointer. This function turns the
|
||||
// currently shown dive into such a pointer. Ugly, yes.
|
||||
struct dive *mutable_dive() const;
|
||||
};
|
||||
|
||||
#endif // PROFILEWIDGET2_H
|
||||
@ -1,162 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.
|
||||
#include "qmlprofile.h"
|
||||
#include "profilescene.h"
|
||||
#include "mobile-widgets/qmlmanager.h"
|
||||
#include "core/errorhelper.h"
|
||||
#include "core/subsurface-float.h"
|
||||
#include "core/metrics.h"
|
||||
#include "core/subsurface-string.h"
|
||||
#include <QTransform>
|
||||
#include <QScreen>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
QMLProfile::QMLProfile(QQuickItem *parent) :
|
||||
QQuickPaintedItem(parent),
|
||||
m_diveId(0),
|
||||
m_dc(0),
|
||||
m_devicePixelRatio(1.0),
|
||||
m_margin(0),
|
||||
m_xOffset(0.0),
|
||||
m_yOffset(0.0)
|
||||
{
|
||||
createProfileView();
|
||||
setAntialiasing(true);
|
||||
setFlags(QQuickItem::ItemClipsChildrenToShape | QQuickItem::ItemHasContents );
|
||||
connect(QMLManager::instance(), &QMLManager::sendScreenChanged, this, &QMLProfile::screenChanged);
|
||||
connect(this, &QMLProfile::scaleChanged, this, &QMLProfile::triggerUpdate);
|
||||
connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &QMLProfile::divesChanged);
|
||||
setDevicePixelRatio(QMLManager::instance()->lastDevicePixelRatio());
|
||||
}
|
||||
|
||||
QMLProfile::~QMLProfile()
|
||||
{
|
||||
}
|
||||
|
||||
void QMLProfile::createProfileView()
|
||||
{
|
||||
m_profileWidget.reset(new ProfileScene(m_devicePixelRatio * 0.8, false, false));
|
||||
}
|
||||
|
||||
// we need this so we can connect update() to the scaleChanged() signal - which the connect above cannot do
|
||||
// directly as it chokes on the default parameter for update().
|
||||
// If the scale changes we may need to change our offsets to ensure that we still only show a subset of
|
||||
// the profile and not empty space around it, which the paint() method below will take care of, which will
|
||||
// eventually get called after we call update()
|
||||
void QMLProfile::triggerUpdate()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void QMLProfile::paint(QPainter *painter)
|
||||
{
|
||||
QElapsedTimer timer;
|
||||
if (verbose)
|
||||
timer.start();
|
||||
|
||||
// let's look at the intended size of the content and scale our scene accordingly
|
||||
// for some odd reason the painter transformation is set up to scale by the dpr - which results
|
||||
// in applying that dpr scaling twice. So we hard-code it here to be the identity matrix
|
||||
QRect painterRect = painter->viewport();
|
||||
painter->resetTransform();
|
||||
if (m_diveId < 0)
|
||||
return;
|
||||
struct dive *d = get_dive_by_uniq_id(m_diveId);
|
||||
if (!d)
|
||||
return;
|
||||
m_profileWidget->draw(painter, painterRect, d, m_dc, nullptr, false);
|
||||
}
|
||||
|
||||
void QMLProfile::setMargin(int margin)
|
||||
{
|
||||
m_margin = margin;
|
||||
}
|
||||
|
||||
int QMLProfile::diveId() const
|
||||
{
|
||||
return m_diveId;
|
||||
}
|
||||
|
||||
void QMLProfile::setDiveId(int diveId)
|
||||
{
|
||||
m_diveId = diveId;
|
||||
emit numDCChanged();
|
||||
}
|
||||
|
||||
qreal QMLProfile::devicePixelRatio() const
|
||||
{
|
||||
return m_devicePixelRatio;
|
||||
}
|
||||
|
||||
void QMLProfile::setDevicePixelRatio(qreal dpr)
|
||||
{
|
||||
if (dpr != m_devicePixelRatio) {
|
||||
m_devicePixelRatio = dpr;
|
||||
// Recreate the view to redraw the text items with the new scale.
|
||||
createProfileView();
|
||||
emit devicePixelRatioChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// don't update the profile here, have the user update x and y and then manually trigger an update
|
||||
void QMLProfile::setXOffset(qreal value)
|
||||
{
|
||||
if (nearly_equal(value, m_xOffset))
|
||||
return;
|
||||
m_xOffset = value;
|
||||
emit xOffsetChanged();
|
||||
}
|
||||
|
||||
// don't update the profile here, have the user update x and y and then manually trigger an update
|
||||
void QMLProfile::setYOffset(qreal value)
|
||||
{
|
||||
if (nearly_equal(value, m_yOffset))
|
||||
return;
|
||||
m_yOffset = value;
|
||||
emit yOffsetChanged();
|
||||
}
|
||||
|
||||
void QMLProfile::screenChanged(QScreen *screen)
|
||||
{
|
||||
setDevicePixelRatio(screen->devicePixelRatio());
|
||||
}
|
||||
|
||||
void QMLProfile::divesChanged(const QVector<dive *> &dives, DiveField)
|
||||
{
|
||||
for (struct dive *d: dives) {
|
||||
if (d->id == m_diveId) {
|
||||
qDebug() << "dive #" << d->number << "changed, trigger profile update";
|
||||
triggerUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QMLProfile::nextDC()
|
||||
{
|
||||
rotateDC(1);
|
||||
}
|
||||
|
||||
void QMLProfile::prevDC()
|
||||
{
|
||||
rotateDC(-1);
|
||||
}
|
||||
|
||||
void QMLProfile::rotateDC(int dir)
|
||||
{
|
||||
struct dive *d = get_dive_by_uniq_id(m_diveId);
|
||||
if (!d)
|
||||
return;
|
||||
int numDC = number_of_computers(d);
|
||||
if (numDC == 1)
|
||||
return;
|
||||
m_dc = (m_dc + dir) % numDC;
|
||||
if (m_dc < 0)
|
||||
m_dc += numDC;
|
||||
triggerUpdate();
|
||||
}
|
||||
|
||||
int QMLProfile::numDC() const
|
||||
{
|
||||
struct dive *d = get_dive_by_uniq_id(m_diveId);
|
||||
return d ? number_of_computers(d) : 0;
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef QMLPROFILE_H
|
||||
#define QMLPROFILE_H
|
||||
|
||||
#include "core/subsurface-qt/divelistnotifier.h"
|
||||
#include <QQuickPaintedItem>
|
||||
#include <memory>
|
||||
|
||||
class ProfileScene;
|
||||
|
||||
class QMLProfile : public QQuickPaintedItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int diveId MEMBER m_diveId WRITE setDiveId)
|
||||
Q_PROPERTY(int numDC READ numDC NOTIFY numDCChanged)
|
||||
Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged)
|
||||
Q_PROPERTY(qreal xOffset MEMBER m_xOffset WRITE setXOffset NOTIFY xOffsetChanged)
|
||||
Q_PROPERTY(qreal yOffset MEMBER m_yOffset WRITE setYOffset NOTIFY yOffsetChanged)
|
||||
|
||||
public:
|
||||
explicit QMLProfile(QQuickItem *parent = 0);
|
||||
~QMLProfile();
|
||||
|
||||
void paint(QPainter *painter);
|
||||
|
||||
int diveId() const;
|
||||
void setDiveId(int diveId);
|
||||
qreal devicePixelRatio() const;
|
||||
void setDevicePixelRatio(qreal dpr);
|
||||
void setXOffset(qreal value);
|
||||
void setYOffset(qreal value);
|
||||
Q_INVOKABLE void nextDC();
|
||||
Q_INVOKABLE void prevDC();
|
||||
|
||||
public slots:
|
||||
void setMargin(int margin);
|
||||
void screenChanged(QScreen *screen);
|
||||
void triggerUpdate();
|
||||
|
||||
private:
|
||||
int m_diveId;
|
||||
int m_dc;
|
||||
qreal m_devicePixelRatio;
|
||||
int m_margin;
|
||||
qreal m_xOffset, m_yOffset;
|
||||
std::unique_ptr<ProfileScene> m_profileWidget;
|
||||
void createProfileView();
|
||||
void rotateDC(int dir);
|
||||
int numDC() const;
|
||||
|
||||
private slots:
|
||||
void divesChanged(const QVector<dive *> &dives, DiveField);
|
||||
|
||||
signals:
|
||||
void rightAlignedChanged();
|
||||
void devicePixelRatioChanged();
|
||||
void xOffsetChanged();
|
||||
void yOffsetChanged();
|
||||
void numDCChanged();
|
||||
};
|
||||
|
||||
#endif // QMLPROFILE_H
|
||||
@ -1,177 +1,191 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "profile-widget/ruleritem.h"
|
||||
#include "profile-widget/profilewidget2.h"
|
||||
#include "ruleritem.h"
|
||||
#include "profilescene.h"
|
||||
#include "profileview.h"
|
||||
#include "zvalues.h"
|
||||
#include "core/settings/qPrefTechnicalDetails.h"
|
||||
|
||||
#include <qgraphicssceneevent.h>
|
||||
|
||||
#include "core/profile.h"
|
||||
|
||||
RulerNodeItem2::RulerNodeItem2() :
|
||||
pInfo(NULL),
|
||||
idx(-1),
|
||||
ruler(NULL),
|
||||
timeAxis(NULL),
|
||||
depthAxis(NULL)
|
||||
#include <QApplication>
|
||||
|
||||
static QColor handleBorderColor(Qt::red);
|
||||
static QColor handleColor(0xff, 0, 0, 0x80);
|
||||
static constexpr double handleRadius = 8.0;
|
||||
|
||||
static QColor lineColor(Qt::black);
|
||||
static constexpr double lineWidth = 1.0;
|
||||
|
||||
static constexpr int tooltipBorder = 1;
|
||||
static constexpr double tooltipBorderRadius = 2.0; // Radius of rounded corners
|
||||
static QColor tooltipBorderColor(Qt::black);
|
||||
static QColor tooltipColor(0xff, 0xff, 0xff, 190);
|
||||
static QColor tooltipFontColor(Qt::black);
|
||||
|
||||
class RulerItemHandle : public ChartDiskItem
|
||||
{
|
||||
public:
|
||||
ProfileView &profileView;
|
||||
double xpos;
|
||||
// The first argument is passed twice, to avoid an downcast. Yes, that's silly.
|
||||
RulerItemHandle(ChartView &view, ProfileView &profileView, double dpr) :
|
||||
ChartDiskItem(view, ProfileZValue::RulerItem,
|
||||
QPen(handleBorderColor, dpr),
|
||||
QBrush(handleColor),
|
||||
true),
|
||||
profileView(profileView),
|
||||
xpos(0.0)
|
||||
{
|
||||
setRect(-8, -8, 16, 16);
|
||||
setBrush(QColor(0xff, 0, 0, 127));
|
||||
setPen(QColor(Qt::red));
|
||||
setFlag(ItemIsMovable);
|
||||
setFlag(ItemSendsGeometryChanges);
|
||||
setFlag(ItemIgnoresTransformations);
|
||||
}
|
||||
|
||||
void RulerNodeItem2::setPlotInfo(const plot_info &info)
|
||||
// The call chain here is weird: this calls into the ProfileScene, which then calls
|
||||
// back into the RulerItem. The reason is that the ProfileScene knows the current
|
||||
// dive etc. This seems more robust than storing the current dive in the subobject.
|
||||
void drag(QPointF pos) override
|
||||
{
|
||||
pInfo = &info;
|
||||
idx = 0;
|
||||
xpos = pos.x();
|
||||
profileView.rulerDragged();
|
||||
}
|
||||
};
|
||||
|
||||
void RulerNodeItem2::setRuler(RulerItem2 *r)
|
||||
// duplicate code in tooltipitem.cpp
|
||||
static QFont makeFont(double dpr)
|
||||
{
|
||||
ruler = r;
|
||||
}
|
||||
|
||||
void RulerNodeItem2::recalculate()
|
||||
{
|
||||
if (!pInfo || pInfo->nr <= 0)
|
||||
return;
|
||||
|
||||
const struct plot_data &last = pInfo->entry[pInfo->nr - 1];
|
||||
if (x() < 0) {
|
||||
setPos(0, y());
|
||||
} else if (x() > timeAxis->posAtValue(last.sec)) {
|
||||
setPos(timeAxis->posAtValue(last.sec), depthAxis->posAtValue(last.depth));
|
||||
QFont font(qApp->font());
|
||||
if (dpr != 1.0) {
|
||||
int pixelSize = font.pixelSize();
|
||||
if (pixelSize > 0) {
|
||||
pixelSize = lrint(static_cast<double>(pixelSize) * dpr);
|
||||
font.setPixelSize(pixelSize);
|
||||
} else {
|
||||
idx = 0;
|
||||
while (idx < pInfo->nr && timeAxis->posAtValue(pInfo->entry[idx].sec) < x())
|
||||
++idx;
|
||||
const struct plot_data &data = pInfo->entry[idx];
|
||||
setPos(timeAxis->posAtValue(data.sec), depthAxis->posAtValue(data.depth));
|
||||
font.setPointSizeF(font.pointSizeF() * dpr);
|
||||
}
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
void RulerNodeItem2::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
static ChartItemPtr<RulerItemHandle> makeHandle(ProfileView &view, double dpr)
|
||||
{
|
||||
qreal x = event->scenePos().x();
|
||||
if (x < 0.0)
|
||||
x = 0.0;
|
||||
setPos(x, event->scenePos().y());
|
||||
recalculate();
|
||||
ruler->recalculate();
|
||||
auto res = view.createChartItem<RulerItemHandle>(view, dpr);
|
||||
res->resize(handleRadius * dpr);
|
||||
return res;
|
||||
}
|
||||
|
||||
RulerItem2::RulerItem2() : pInfo(NULL),
|
||||
source(new RulerNodeItem2()),
|
||||
dest(new RulerNodeItem2()),
|
||||
timeAxis(NULL),
|
||||
depthAxis(NULL),
|
||||
textItemBack(new QGraphicsRectItem(this)),
|
||||
textItem(new QGraphicsSimpleTextItem(this))
|
||||
RulerItem::RulerItem(ProfileView &view, double dpr) :
|
||||
line(view.createChartItem<ChartLineItem>(ProfileZValue::RulerItem,
|
||||
lineColor, lineWidth * dpr)),
|
||||
handle1(makeHandle(view, dpr)),
|
||||
handle2(makeHandle(view, dpr)),
|
||||
tooltip(view.createChartItem<AnimatedChartRectItem>(ProfileZValue::RulerItem,
|
||||
QPen(tooltipBorderColor, lrint(tooltipBorder * dpr)),
|
||||
QBrush(tooltipColor), tooltipBorderRadius * dpr,
|
||||
false)),
|
||||
font(makeFont(dpr)),
|
||||
fm(font),
|
||||
fontHeight(fm.height())
|
||||
{
|
||||
source->setRuler(this);
|
||||
dest->setRuler(this);
|
||||
textItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
|
||||
textItemBack->setBrush(QColor(0xff, 0xff, 0xff, 190));
|
||||
textItemBack->setPen(QColor(Qt::white));
|
||||
textItemBack->setFlag(QGraphicsItem::ItemIgnoresTransformations);
|
||||
setPen(QPen(QColor(Qt::black), 0.0));
|
||||
#ifndef SUBSURFACE_MOBILE
|
||||
connect(qPrefTechnicalDetails::instance(), &qPrefTechnicalDetails::rulergraphChanged, this, &RulerItem2::settingsChanged);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RulerItem2::settingsChanged(bool value)
|
||||
RulerItem::~RulerItem()
|
||||
{
|
||||
ProfileWidget2 *profWidget = NULL;
|
||||
if (scene() && scene()->views().count())
|
||||
profWidget = qobject_cast<ProfileWidget2 *>(scene()->views().first());
|
||||
|
||||
setVisible( (profWidget && profWidget->currentState == ProfileWidget2::PROFILE) ? value : false);
|
||||
}
|
||||
|
||||
void RulerItem2::recalculate()
|
||||
void RulerItem::setVisible(bool visible)
|
||||
{
|
||||
char buffer[500];
|
||||
QPointF tmp;
|
||||
QFont font;
|
||||
QFontMetrics fm(font);
|
||||
line->setVisible(visible);
|
||||
handle1->setVisible(visible);
|
||||
handle2->setVisible(visible);
|
||||
tooltip->setVisible(visible);
|
||||
}
|
||||
|
||||
if (timeAxis == NULL || depthAxis == NULL || !pInfo || pInfo->nr == 0)
|
||||
// Binary search to find index at time stamp
|
||||
static int get_idx_at_time(const plot_info &info, int time)
|
||||
{
|
||||
auto entry = std::lower_bound(info.entry, info.entry + info.nr, time,
|
||||
[](const plot_data &d, int time)
|
||||
{ return d.sec < time; });
|
||||
return entry < info.entry + info.nr ? entry - info.entry
|
||||
: info.nr - 1;
|
||||
}
|
||||
|
||||
void RulerItem::update(const dive *d, double dpr, const ProfileScene &scene, const plot_info &info, int animspeed)
|
||||
{
|
||||
if (info.nr == 0)
|
||||
return; // Nothing to display
|
||||
|
||||
auto [minX, maxX] = scene.minMaxX();
|
||||
auto [minY, maxY] = scene.minMaxY();
|
||||
double x1 = std::clamp(handle1->xpos, minX, maxX);
|
||||
double x2 = std::clamp(handle2->xpos, minX, maxX);
|
||||
|
||||
int time1 = lrint(scene.timeAt(QPointF(x1, 0.0)));
|
||||
int time2 = lrint(scene.timeAt(QPointF(x2, 0.0)));
|
||||
|
||||
int idx1 = get_idx_at_time(info, time1);
|
||||
int idx2 = get_idx_at_time(info, time2);
|
||||
|
||||
double y1 = scene.posAtDepth(info.entry[idx1].depth);
|
||||
double y2 = scene.posAtDepth(info.entry[idx2].depth);
|
||||
|
||||
QPointF pos1(x1, y1);
|
||||
QPointF pos2(x2, y2);
|
||||
line->setLine(pos1, pos2);
|
||||
handle1->setPos(pos1);
|
||||
handle2->setPos(pos2);
|
||||
|
||||
if (idx1 == idx2) {
|
||||
tooltip->setVisible(false);
|
||||
return;
|
||||
|
||||
prepareGeometryChange();
|
||||
startPoint = mapFromItem(source, 0, 0);
|
||||
endPoint = mapFromItem(dest, 0, 0);
|
||||
|
||||
if (startPoint.x() > endPoint.x()) {
|
||||
tmp = endPoint;
|
||||
endPoint = startPoint;
|
||||
startPoint = tmp;
|
||||
}
|
||||
QLineF line(startPoint, endPoint);
|
||||
setLine(line);
|
||||
compare_samples(dive, pInfo, source->idx, dest->idx, buffer, 500, 1);
|
||||
text = QString(buffer);
|
||||
|
||||
// draw text
|
||||
QGraphicsView *view = scene()->views().first();
|
||||
QPoint begin = view->mapFromScene(mapToScene(startPoint));
|
||||
textItem->setText(text);
|
||||
qreal tgtX = startPoint.x();
|
||||
const qreal diff = begin.x() + textItem->boundingRect().width();
|
||||
// clamp so that the text doesn't go out of the screen to the right
|
||||
if (diff > view->width()) {
|
||||
begin.setX(lrint(begin.x() - (diff - view->width())));
|
||||
tgtX = mapFromScene(view->mapToScene(begin)).x();
|
||||
}
|
||||
// always show the text bellow the lowest of the start and end points
|
||||
qreal tgtY = (startPoint.y() >= endPoint.y()) ? startPoint.y() : endPoint.y();
|
||||
// this isn't exactly optimal, since we want to scale the 1.0, 4.0 distances as well
|
||||
textItem->setPos(tgtX - 1.0, tgtY + 4.0);
|
||||
|
||||
// setup the text background
|
||||
textItemBack->setVisible(startPoint.x() != endPoint.x());
|
||||
textItemBack->setPos(textItem->x(), textItem->y());
|
||||
textItemBack->setRect(0, 0, textItem->boundingRect().width(), textItem->boundingRect().height());
|
||||
}
|
||||
|
||||
RulerNodeItem2 *RulerItem2::sourceNode() const
|
||||
auto strings = compare_samples(d, &info, idx1, idx2, true);
|
||||
if (strings.empty()) {
|
||||
tooltip->setVisible(false);
|
||||
return;
|
||||
}
|
||||
tooltip->setVisible(true);
|
||||
|
||||
double width = 0;
|
||||
std::vector<int> string_widths;
|
||||
string_widths.reserve(strings.size());
|
||||
for (const QString &s: strings) {
|
||||
int w = fm.size(Qt::TextSingleLine, s).width();
|
||||
width = std::max(width, static_cast<double>(w));
|
||||
string_widths.push_back(w);
|
||||
}
|
||||
|
||||
width += 6.0 * tooltipBorder * dpr;
|
||||
|
||||
double height = static_cast<double>(strings.size()) * fontHeight +
|
||||
4.0 * tooltipBorder * dpr;
|
||||
|
||||
QPixmap pixmap(lrint(width), lrint(height));
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
|
||||
painter.setFont(font);
|
||||
painter.setPen(QPen(tooltipFontColor)); // QPainter uses QPen to set text color!
|
||||
double x = 4.0 * tooltipBorder * dpr;
|
||||
double y = 2.0 * tooltipBorder * dpr;
|
||||
for (size_t i = 0; i < strings.size(); ++i) {
|
||||
QRectF rect(x, y, string_widths[i], fontHeight);
|
||||
painter.drawText(rect, strings[i]);
|
||||
y += fontHeight;
|
||||
}
|
||||
|
||||
tooltip->setPixmap(pixmap, animspeed);
|
||||
|
||||
if (pos2.x() < pos1.x())
|
||||
std::swap(pos1, pos2);
|
||||
double xpos = width < maxX - minX ?
|
||||
std::clamp(pos1.x() + handleRadius * dpr, 0.0, maxX - width) : 0.0;
|
||||
|
||||
double ypos = height < maxY - minY ?
|
||||
std::clamp(pos1.y() + handleRadius * dpr, 0.0, maxY - height) : 0.0;
|
||||
|
||||
tooltip->setPos(QPointF(xpos, ypos));
|
||||
}
|
||||
|
||||
void RulerItem::anim(double progress)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
RulerNodeItem2 *RulerItem2::destNode() const
|
||||
{
|
||||
return dest;
|
||||
}
|
||||
|
||||
void RulerItem2::setPlotInfo(const struct dive *d, const plot_info &info)
|
||||
{
|
||||
dive = d;
|
||||
pInfo = &info;
|
||||
dest->setPlotInfo(info);
|
||||
source->setPlotInfo(info);
|
||||
dest->recalculate();
|
||||
source->recalculate();
|
||||
recalculate();
|
||||
}
|
||||
|
||||
void RulerItem2::setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth)
|
||||
{
|
||||
timeAxis = time;
|
||||
depthAxis = depth;
|
||||
dest->depthAxis = depth;
|
||||
dest->timeAxis = time;
|
||||
source->depthAxis = depth;
|
||||
source->timeAxis = time;
|
||||
recalculate();
|
||||
}
|
||||
|
||||
void RulerItem2::setVisible(bool visible)
|
||||
{
|
||||
QGraphicsLineItem::setVisible(visible);
|
||||
source->setVisible(visible);
|
||||
dest->setVisible(visible);
|
||||
tooltip->anim(progress);
|
||||
}
|
||||
|
||||
@ -2,59 +2,32 @@
|
||||
#ifndef RULERITEM_H
|
||||
#define RULERITEM_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QGraphicsEllipseItem>
|
||||
#include <QGraphicsObject>
|
||||
#include "profile-widget/divecartesianaxis.h"
|
||||
#include "qt-quick/chartitem.h"
|
||||
|
||||
struct plot_data;
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
|
||||
class ProfileView;
|
||||
class ProfileScene;
|
||||
class RulerItemHandle;
|
||||
struct plot_info;
|
||||
class RulerItem2;
|
||||
struct dive;
|
||||
|
||||
class RulerNodeItem2 : public QObject, public QGraphicsEllipseItem {
|
||||
Q_OBJECT
|
||||
friend class RulerItem2;
|
||||
class RulerItem {
|
||||
ChartItemPtr<ChartLineItem> line;
|
||||
ChartItemPtr<RulerItemHandle> handle1, handle2;
|
||||
ChartItemPtr<AnimatedChartRectItem> tooltip;
|
||||
|
||||
QFont font;
|
||||
QFontMetrics fm;
|
||||
double fontHeight;
|
||||
QPixmap title;
|
||||
public:
|
||||
explicit RulerNodeItem2();
|
||||
void setRuler(RulerItem2 *r);
|
||||
void setPlotInfo(const struct plot_info &info);
|
||||
void recalculate();
|
||||
|
||||
private:
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
const struct plot_info *pInfo;
|
||||
int idx;
|
||||
RulerItem2 *ruler;
|
||||
DiveCartesianAxis *timeAxis;
|
||||
DiveCartesianAxis *depthAxis;
|
||||
};
|
||||
|
||||
class RulerItem2 : public QObject, public QGraphicsLineItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RulerItem2();
|
||||
void recalculate();
|
||||
|
||||
void setPlotInfo(const struct dive *d, const struct plot_info &pInfo);
|
||||
RulerNodeItem2 *sourceNode() const;
|
||||
RulerNodeItem2 *destNode() const;
|
||||
void setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth);
|
||||
RulerItem(ProfileView &view, double dpr);
|
||||
~RulerItem();
|
||||
void setVisible(bool visible);
|
||||
|
||||
public
|
||||
slots:
|
||||
void settingsChanged(bool toggled);
|
||||
|
||||
private:
|
||||
const struct dive *dive;
|
||||
const struct plot_info *pInfo;
|
||||
QPointF startPoint, endPoint;
|
||||
RulerNodeItem2 *source, *dest;
|
||||
QString text;
|
||||
DiveCartesianAxis *timeAxis;
|
||||
DiveCartesianAxis *depthAxis;
|
||||
QGraphicsRectItem *textItemBack;
|
||||
QGraphicsSimpleTextItem *textItem;
|
||||
void update(const dive *d, double dpr, const ProfileScene &scene, const plot_info &info, int animspeed);
|
||||
void anim(double progress);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
#ifndef TANKITEM_H
|
||||
#define TANKITEM_H
|
||||
|
||||
#include "profile-widget/divelineitem.h"
|
||||
#include "core/gas.h"
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QBrush>
|
||||
|
||||
168
profile-widget/tooltipitem.cpp
Normal file
168
profile-widget/tooltipitem.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "tooltipitem.h"
|
||||
#include "profiletranslations.h"
|
||||
#include "zvalues.h"
|
||||
|
||||
#include "core/color.h"
|
||||
#include "core/membuffer.h"
|
||||
#include "core/profile.h"
|
||||
#include "core/qthelper.h" // for decoMode
|
||||
#include "core/settings/qPrefDisplay.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QApplication>
|
||||
|
||||
static const int tooltipBorder = 2;
|
||||
static const double tooltipBorderRadius = 4.0; // Radius of rounded corners
|
||||
|
||||
static QColor tooltipBorderColor(Qt::white);
|
||||
static QColor tooltipColor(0, 0, 0, 155);
|
||||
static QColor tooltipFontColor(Qt::white);
|
||||
|
||||
static QFont makeFont(double dpr)
|
||||
{
|
||||
QFont font(qApp->font());
|
||||
if (dpr != 1.0) {
|
||||
int pixelSize = font.pixelSize();
|
||||
if (pixelSize > 0) {
|
||||
pixelSize = lrint(static_cast<double>(pixelSize) * dpr);
|
||||
font.setPixelSize(pixelSize);
|
||||
} else {
|
||||
font.setPointSizeF(font.pointSizeF() * dpr);
|
||||
}
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
ToolTipItem::ToolTipItem(ChartView &view, double dpr) :
|
||||
AnimatedChartRectItem(view, ProfileZValue::ToolTipItem,
|
||||
QPen(tooltipBorderColor, lrint(tooltipBorder * dpr)),
|
||||
QBrush(tooltipColor), tooltipBorderRadius * dpr,
|
||||
true),
|
||||
font(makeFont(dpr)),
|
||||
fm(font),
|
||||
fontHeight(fm.height())
|
||||
{
|
||||
title = stringToPixmap(ProfileTranslations::tr("Information"));
|
||||
|
||||
QPointF pos = qPrefDisplay::tooltip_position();
|
||||
setPos(pos);
|
||||
}
|
||||
|
||||
QPixmap ToolTipItem::stringToPixmap(const QString &str) const
|
||||
{
|
||||
QSize s = fm.size(Qt::TextSingleLine, str);
|
||||
if (s.width() <= 0 || s.height() <= 0)
|
||||
return QPixmap(1,1);
|
||||
QPixmap res(s);
|
||||
res.fill(Qt::transparent);
|
||||
QPainter painter(&res);
|
||||
painter.setFont(font);
|
||||
painter.setPen(tooltipFontColor);
|
||||
painter.drawText(QRect(QPoint(), s), str);
|
||||
return res;
|
||||
}
|
||||
|
||||
static QPixmap drawTissues(const plot_info &pInfo, double dpr, int idx, bool inPlanner)
|
||||
{
|
||||
QPixmap tissues(16,60);
|
||||
QPainter painter(&tissues);
|
||||
tissues.fill();
|
||||
painter.setPen(QColor(0, 0, 0, 0));
|
||||
painter.setBrush(QColor(LIMENADE1));
|
||||
painter.drawRect(0, 10 + (100 - AMB_PERCENTAGE) / 2, 16, AMB_PERCENTAGE / 2);
|
||||
painter.setBrush(QColor(SPRINGWOOD1));
|
||||
painter.drawRect(0, 10, 16, (100 - AMB_PERCENTAGE) / 2);
|
||||
painter.setBrush(QColor(Qt::red));
|
||||
painter.drawRect(0,0,16,10);
|
||||
|
||||
if (!idx)
|
||||
return tissues;
|
||||
|
||||
const struct plot_data *entry = &pInfo.entry[idx];
|
||||
painter.setPen(QColor(0, 0, 0, 255));
|
||||
if (decoMode(inPlanner) == BUEHLMANN)
|
||||
painter.drawLine(0, lrint(60 - entry->gfline / 2), 16, lrint(60 - entry->gfline / 2));
|
||||
painter.drawLine(0, lrint(60 - AMB_PERCENTAGE * (entry->pressures.n2 + entry->pressures.he) / entry->ambpressure / 2),
|
||||
16, lrint(60 - AMB_PERCENTAGE * (entry->pressures.n2 + entry->pressures.he) / entry->ambpressure /2));
|
||||
painter.setPen(QColor(0, 0, 0, 127));
|
||||
for (int i = 0; i < 16; i++)
|
||||
painter.drawLine(i, 60, i, 60 - entry->percentages[i] / 2);
|
||||
|
||||
if (dpr == 1.0)
|
||||
return tissues;
|
||||
|
||||
// Scale according to DPR
|
||||
int new_width = lrint(tissues.width() * dpr);
|
||||
int new_height = lrint(tissues.width() * dpr);
|
||||
return tissues.scaled(new_width, new_height);
|
||||
}
|
||||
|
||||
void ToolTipItem::update(const dive *d, double dpr, int time, const plot_info &pInfo,
|
||||
const std::vector<std::pair<QString, QPixmap>> &events, bool inPlanner, int animSpeed)
|
||||
{
|
||||
auto [idx, strings] = get_plot_details_new(d, &pInfo, time);
|
||||
|
||||
QPixmap tissues = drawTissues(pInfo, dpr, idx, inPlanner);
|
||||
std::vector<QSizeF> event_sizes;
|
||||
|
||||
std::vector<int> string_widths;
|
||||
string_widths.reserve(strings.size());
|
||||
width = title.size().width();
|
||||
for (const QString &s: strings) {
|
||||
int w = fm.size(Qt::TextSingleLine, s).width();
|
||||
width = std::max(width, static_cast<double>(w));
|
||||
string_widths.push_back(w);
|
||||
}
|
||||
|
||||
height = (static_cast<double>(strings.size()) + 1.0) * fontHeight;
|
||||
|
||||
for (auto &[s, pixmap]: events) {
|
||||
double text_width = fm.size(Qt::TextSingleLine, s).width();
|
||||
double total_width = pixmap.width() + text_width;
|
||||
double h = std::max(static_cast<double>(pixmap.height()), fontHeight);
|
||||
width = std::max(width, total_width);
|
||||
height += h;
|
||||
event_sizes.emplace_back(text_width, h);
|
||||
}
|
||||
|
||||
width += tissues.width();
|
||||
width += 6.0 * tooltipBorder * dpr;
|
||||
height = std::max(height, static_cast<double>(tissues.height()));
|
||||
height += 4.0 * tooltipBorder * dpr + title.height();
|
||||
|
||||
QPixmap pixmap(lrint(width), lrint(height));
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
|
||||
painter.setFont(font);
|
||||
painter.setPen(QPen(tooltipFontColor)); // QPainter uses QPen to set text color!
|
||||
double x = 4.0 * tooltipBorder * dpr + tissues.width();
|
||||
double y = 2.0 * tooltipBorder * dpr;
|
||||
double titleOffset = (width - title.width()) / 2.0;
|
||||
painter.drawPixmap(lrint(titleOffset), lrint(y), title, 0, 0, title.width(), title.height());
|
||||
y += round(fontHeight);
|
||||
painter.drawPixmap(lrint(2.0 * tooltipBorder * dpr), lrint(y), tissues, 0, 0, tissues.width(), tissues.height());
|
||||
y += round(fontHeight);
|
||||
for (size_t i = 0; i < strings.size(); ++i) {
|
||||
QRectF rect(x, y, string_widths[i], fontHeight);
|
||||
painter.drawText(rect, strings[i]);
|
||||
y += fontHeight;
|
||||
}
|
||||
for (size_t i = 0; i < events.size(); ++i) {
|
||||
QSizeF size = event_sizes[i];
|
||||
auto &[s, pixmap] = events[i];
|
||||
painter.drawPixmap(lrint(x), lrint(y + (size.height() - pixmap.height())/2.0), pixmap,
|
||||
0, 0, pixmap.width(), pixmap.height());
|
||||
QRectF rect(x + pixmap.width(), round(y + (size.height() - fontHeight) / 2.0), size.width(), fontHeight);
|
||||
painter.drawText(rect, s);
|
||||
y += size.height();
|
||||
}
|
||||
|
||||
AnimatedChartRectItem::setPixmap(pixmap, animSpeed);
|
||||
}
|
||||
|
||||
void ToolTipItem::stopDrag(QPointF pos)
|
||||
{
|
||||
qPrefDisplay::set_tooltip_position(pos);
|
||||
}
|
||||
32
profile-widget/tooltipitem.h
Normal file
32
profile-widget/tooltipitem.h
Normal file
@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef PROFILE_TOOLTIPITEM_H
|
||||
#define PROFILE_TOOLTIPITEM_H
|
||||
|
||||
#include "qt-quick/chartitem.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
#include <QPixmap>
|
||||
|
||||
struct dive;
|
||||
struct plot_info;
|
||||
|
||||
class ToolTipItem : public AnimatedChartRectItem {
|
||||
public:
|
||||
ToolTipItem(ChartView &view, double dpr);
|
||||
void update(const dive *d, double dpr, int time, const plot_info &pInfo,
|
||||
const std::vector<std::pair<QString, QPixmap>> &events, bool inPlanner, int animSpeed);
|
||||
private:
|
||||
QFont font;
|
||||
QFontMetrics fm;
|
||||
double fontHeight;
|
||||
QPixmap title;
|
||||
double width, height;
|
||||
|
||||
QPixmap stringToPixmap(const QString &s) const;
|
||||
|
||||
void stopDrag(QPointF pos) override;
|
||||
};
|
||||
|
||||
|
||||
#endif // PROFILE_TOOLTIPITEM
|
||||
24
profile-widget/zvalues.h
Normal file
24
profile-widget/zvalues.h
Normal file
@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Defines the z-values of features in the profile view.
|
||||
// Objects with higher z-values are painted on top of objects
|
||||
// with smaller z-values. For the same z-value objects are
|
||||
// drawn in order of addition to the scene.
|
||||
#ifndef PROFILE_ZVALUES_H
|
||||
#define PROFILE_ZVALUES_H
|
||||
|
||||
// Encapsulating an enum in a struct is stupid, but allows us
|
||||
// to not poison the namespace and yet autoconvert to int
|
||||
// (in constrast to enum class). enum is so broken!
|
||||
struct ProfileZValue {
|
||||
enum ZValues {
|
||||
Profile = 0,
|
||||
Pictures,
|
||||
RulerItem,
|
||||
Handles,
|
||||
MouseFollower,
|
||||
ToolTipItem,
|
||||
Count
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
19
qt-quick/CMakeLists.txt
Normal file
19
qt-quick/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
||||
# code for qt-quick based charts
|
||||
include_directories(.
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_BINARY_DIR}
|
||||
)
|
||||
|
||||
set(SUBSURFACE_QTQUICK_SRCS
|
||||
chartitem.cpp
|
||||
chartitem.h
|
||||
chartitemhelper.h
|
||||
chartitem_ptr.h
|
||||
chartview.cpp
|
||||
chartview.h
|
||||
)
|
||||
|
||||
source_group("Subsurface qtquick sourcecode" FILES ${SUBSURFACE_QTQUICK_SRCS})
|
||||
|
||||
add_library(subsurface_qtquick STATIC ${SUBSURFACE_QTQUICK_SRCS})
|
||||
target_link_libraries(subsurface_qtquick ${QT_LIBRARIES})
|
||||
370
qt-quick/chartitem.cpp
Normal file
370
qt-quick/chartitem.cpp
Normal file
@ -0,0 +1,370 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "chartitem.h"
|
||||
#include "chartitemhelper.h"
|
||||
#include "chartitem_private.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QGraphicsScene>
|
||||
#include <QQuickWindow>
|
||||
#include <QSGFlatColorMaterial>
|
||||
#include <QSGImageNode>
|
||||
|
||||
ChartItem::ChartItem(ChartView &v, size_t z, bool dragable) :
|
||||
dirty(false), prev(nullptr), next(nullptr),
|
||||
zValue(z), dragable(dragable), view(v)
|
||||
{
|
||||
// Register before the derived constructors run, so that the
|
||||
// derived classes can mark the item as dirty in the constructor.
|
||||
v.registerChartItem(*this);
|
||||
}
|
||||
|
||||
ChartItem::~ChartItem()
|
||||
{
|
||||
}
|
||||
|
||||
QSizeF ChartItem::sceneSize() const
|
||||
{
|
||||
return view.size();
|
||||
}
|
||||
|
||||
void ChartItem::markDirty()
|
||||
{
|
||||
view.registerDirtyChartItem(*this);
|
||||
}
|
||||
|
||||
QRectF ChartItem::getRect() const
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
|
||||
void ChartItem::startDrag(QPointF pos)
|
||||
{
|
||||
}
|
||||
|
||||
void ChartItem::drag(QPointF)
|
||||
{
|
||||
}
|
||||
|
||||
void ChartItem::stopDrag(QPointF pos)
|
||||
{
|
||||
}
|
||||
|
||||
static int round_up(double f)
|
||||
{
|
||||
return static_cast<int>(ceil(f));
|
||||
}
|
||||
|
||||
ChartPixmapItem::ChartPixmapItem(ChartView &v, size_t z, bool dragable) : HideableChartItem(v, z, dragable),
|
||||
scale(1.0), positionDirty(false), textureDirty(false)
|
||||
{
|
||||
}
|
||||
|
||||
ChartPixmapItem::~ChartPixmapItem()
|
||||
{
|
||||
painter.reset(); // Make sure to destroy painter before image that is painted on
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setTextureDirty()
|
||||
{
|
||||
textureDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setPositionDirty()
|
||||
{
|
||||
positionDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::render()
|
||||
{
|
||||
doRearrange();
|
||||
if (!node) {
|
||||
createNode(view.w()->createImageNode());
|
||||
addNodeToView();
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (!img) {
|
||||
resize(QSizeF(1,1));
|
||||
img->fill(Qt::transparent);
|
||||
}
|
||||
if (textureDirty) {
|
||||
texture.reset(view.w()->createTextureFromImage(*img, QQuickWindow::TextureHasAlphaChannel));
|
||||
node->node->setTexture(texture.get());
|
||||
textureDirty = false;
|
||||
}
|
||||
if (positionDirty) {
|
||||
node->node->setRect(rect);
|
||||
positionDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Scale size and round to integer (because non-integers give strange artifacts for me).
|
||||
static QSizeF scaleSize(const QSizeF &s, double scale)
|
||||
{
|
||||
return { round(s.width() * scale),
|
||||
round(s.height() * scale) };
|
||||
}
|
||||
|
||||
void ChartPixmapItem::resize(QSizeF size)
|
||||
{
|
||||
QSize s_int(round_up(size.width()), round_up(size.height()));
|
||||
if (img && s_int == img->size())
|
||||
return;
|
||||
painter.reset();
|
||||
img.reset(new QImage(s_int, QImage::Format_ARGB32));
|
||||
if (!img->isNull()) {
|
||||
painter.reset(new QPainter(img.get()));
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
}
|
||||
rect.setSize(scaleSize(size, scale));
|
||||
setPositionDirty(); // position includes the size.
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::drag(QPointF pos)
|
||||
{
|
||||
setPos(pos);
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setPos(QPointF pos)
|
||||
{
|
||||
rect.moveTopLeft(pos);
|
||||
setPositionDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setScale(double scaleIn)
|
||||
{
|
||||
scale = scaleIn;
|
||||
if (img) {
|
||||
rect.setSize(scaleSize(img->size(), scale));
|
||||
setPositionDirty(); // position includes the size.
|
||||
}
|
||||
}
|
||||
|
||||
void ChartGraphicsSceneItem::draw(QSizeF s, QColor background, QGraphicsScene &scene)
|
||||
{
|
||||
resize(s); // Noop if size doesn't change
|
||||
if (!painter)
|
||||
return; // Happens if we resize to (0,0)
|
||||
img->fill(background);
|
||||
scene.render(painter.get(), QRect(QPoint(), img->size()), scene.sceneRect(), Qt::IgnoreAspectRatio);
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
ChartRectItem::ChartRectItem(ChartView &v, size_t z, const QPen &pen,
|
||||
const QBrush &brush, double radius, bool dragable) :
|
||||
ChartPixmapItem(v, z, dragable),
|
||||
pen(pen), brush(brush), radius(radius)
|
||||
{
|
||||
}
|
||||
|
||||
ChartRectItem::~ChartRectItem()
|
||||
{
|
||||
}
|
||||
|
||||
void ChartRectItem::resize(QSizeF size)
|
||||
{
|
||||
ChartPixmapItem::resize(size);
|
||||
if (!painter)
|
||||
return;
|
||||
img->fill(Qt::transparent);
|
||||
painter->setPen(pen);
|
||||
painter->setBrush(brush);
|
||||
QSize imgSize = img->size();
|
||||
int width = pen.width();
|
||||
QRect rect(width / 2, width / 2, imgSize.width() - width, imgSize.height() - width);
|
||||
painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
|
||||
}
|
||||
|
||||
AnimatedChartRectItem::~AnimatedChartRectItem()
|
||||
{
|
||||
}
|
||||
|
||||
void AnimatedChartRectItem::setPixmap(const QPixmap &p, int animSpeed)
|
||||
{
|
||||
if (animSpeed <= 0) {
|
||||
resize(p.size());
|
||||
if (painter)
|
||||
painter->drawPixmap(0, 0, p, 0, 0, p.width(), p.height());
|
||||
setTextureDirty();
|
||||
return;
|
||||
}
|
||||
pixmap = p;
|
||||
originalSize = img ? img->size() : QSize(1,1);
|
||||
}
|
||||
|
||||
static int mid(int from, int to, double fraction)
|
||||
{
|
||||
return fraction == 1.0 ? to
|
||||
: lrint(from + (to - from) * fraction);
|
||||
}
|
||||
|
||||
void AnimatedChartRectItem::anim(double fraction)
|
||||
{
|
||||
QSize s(mid(originalSize.width(), pixmap.width(), fraction),
|
||||
mid(originalSize.height(), pixmap.height(), fraction));
|
||||
resize(s);
|
||||
if (painter)
|
||||
painter->drawPixmap(0, 0, pixmap, 0, 0, s.width(), s.height());
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
ChartDiskItem::ChartDiskItem(ChartView &v, size_t z, const QPen &pen, const QBrush &brush, bool dragable) :
|
||||
ChartPixmapItem(v, z, dragable),
|
||||
pen(pen), brush(brush)
|
||||
{
|
||||
}
|
||||
|
||||
ChartDiskItem::~ChartDiskItem()
|
||||
{
|
||||
}
|
||||
|
||||
void ChartDiskItem::resize(double radius)
|
||||
{
|
||||
ChartPixmapItem::resize(QSizeF(2.0 * radius, 2.0 * radius));
|
||||
if (!painter)
|
||||
return;
|
||||
img->fill(Qt::transparent);
|
||||
painter->setPen(pen);
|
||||
painter->setBrush(brush);
|
||||
QSize imgSize = img->size();
|
||||
int width = pen.width();
|
||||
QRect rect(width / 2, width / 2, imgSize.width() - width, imgSize.height() - width);
|
||||
painter->drawEllipse(rect);
|
||||
}
|
||||
|
||||
// Moves the _center_ of the disk to given position.
|
||||
void ChartDiskItem::setPos(QPointF pos)
|
||||
{
|
||||
rect.moveCenter(pos);
|
||||
setPositionDirty();
|
||||
}
|
||||
|
||||
QPointF ChartDiskItem::getPos() const
|
||||
{
|
||||
return rect.center();
|
||||
}
|
||||
|
||||
ChartTextItem::ChartTextItem(ChartView &v, size_t z, const QFont &f, const std::vector<QString> &text, bool center) :
|
||||
ChartPixmapItem(v, z), f(f), center(center)
|
||||
{
|
||||
QFontMetrics fm(f);
|
||||
double totalWidth = 1.0;
|
||||
fontHeight = static_cast<double>(fm.height());
|
||||
double totalHeight = std::max(1.0, static_cast<double>(text.size()) * fontHeight);
|
||||
|
||||
items.reserve(text.size());
|
||||
for (const QString &s: text) {
|
||||
double w = fm.size(Qt::TextSingleLine, s).width();
|
||||
items.push_back({ s, w });
|
||||
if (w > totalWidth)
|
||||
totalWidth = w;
|
||||
}
|
||||
resize(QSizeF(totalWidth, totalHeight));
|
||||
}
|
||||
|
||||
ChartTextItem::ChartTextItem(ChartView &v, size_t z, const QFont &f, const QString &text) :
|
||||
ChartTextItem(v, z, f, std::vector<QString>({ text }), true)
|
||||
{
|
||||
}
|
||||
|
||||
void ChartTextItem::setColor(const QColor &c)
|
||||
{
|
||||
setColor(c, Qt::transparent);
|
||||
}
|
||||
|
||||
void ChartTextItem::setColor(const QColor &c, const QColor &background)
|
||||
{
|
||||
img->fill(background);
|
||||
double y = 0.0;
|
||||
painter->setPen(QPen(c));
|
||||
painter->setFont(f);
|
||||
double totalWidth = getRect().width();
|
||||
for (const auto &[s, w]: items) {
|
||||
double x = center ? round((totalWidth - w) / 2.0) : 0.0;
|
||||
QRectF rect(x, y, w, fontHeight);
|
||||
painter->drawText(rect, s);
|
||||
y += fontHeight;
|
||||
}
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
ChartLineItemBase::~ChartLineItemBase()
|
||||
{
|
||||
}
|
||||
|
||||
void ChartLineItemBase::setLine(QPointF from, QPointF to)
|
||||
{
|
||||
rect = QRectF(from, to);
|
||||
positionDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void ChartLineItem::render()
|
||||
{
|
||||
doRearrange();
|
||||
if (!node) {
|
||||
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2));
|
||||
geometry->setDrawingMode(QSGGeometry::DrawLines);
|
||||
material.reset(new QSGFlatColorMaterial);
|
||||
createNode();
|
||||
node->setGeometry(geometry.get());
|
||||
node->setMaterial(material.get());
|
||||
addNodeToView();
|
||||
positionDirty = materialDirty = true;
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (positionDirty) {
|
||||
// Attention: width is a geometry property and therefore handled by position dirty!
|
||||
geometry->setLineWidth(static_cast<float>(width));
|
||||
auto vertices = geometry->vertexDataAsPoint2D();
|
||||
setPoint(vertices[0], rect.topLeft());
|
||||
setPoint(vertices[1], rect.bottomRight());
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
|
||||
if (materialDirty) {
|
||||
material->setColor(color);
|
||||
node->markDirty(QSGNode::DirtyMaterial);
|
||||
}
|
||||
|
||||
positionDirty = materialDirty = false;
|
||||
}
|
||||
|
||||
void ChartRectLineItem::render()
|
||||
{
|
||||
doRearrange();
|
||||
if (!node) {
|
||||
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 5));
|
||||
geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
|
||||
material.reset(new QSGFlatColorMaterial);
|
||||
createNode();
|
||||
node->setGeometry(geometry.get());
|
||||
node->setMaterial(material.get());
|
||||
addNodeToView();
|
||||
positionDirty = materialDirty = true;
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (positionDirty) {
|
||||
// Attention: width is a geometry property and therefore handled by position dirty!
|
||||
geometry->setLineWidth(static_cast<float>(width));
|
||||
auto vertices = geometry->vertexDataAsPoint2D();
|
||||
setPoint(vertices[0], rect.topLeft());
|
||||
setPoint(vertices[1], rect.bottomLeft());
|
||||
setPoint(vertices[2], rect.bottomRight());
|
||||
setPoint(vertices[3], rect.topRight());
|
||||
setPoint(vertices[4], rect.topLeft());
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
|
||||
if (materialDirty) {
|
||||
material->setColor(color);
|
||||
node->markDirty(QSGNode::DirtyMaterial);
|
||||
}
|
||||
|
||||
positionDirty = materialDirty = false;
|
||||
}
|
||||
250
qt-quick/chartitem.h
Normal file
250
qt-quick/chartitem.h
Normal file
@ -0,0 +1,250 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Wrappers around QSGImageNode that allow painting onto an image
|
||||
// and then turning that into a texture to be displayed in a QQuickItem.
|
||||
#ifndef CHART_ITEM_H
|
||||
#define CHART_ITEM_H
|
||||
|
||||
#include "chartitem_ptr.h"
|
||||
#include "chartitemhelper.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QPainter>
|
||||
|
||||
class ChartView;
|
||||
class QGraphicsScene;
|
||||
class QSGGeometry;
|
||||
class QSGGeometryNode;
|
||||
class QSGFlatColorMaterial;
|
||||
class QSGImageNode;
|
||||
class QSGRectangleNode;
|
||||
class QSGTexture;
|
||||
class QSGTextureMaterial;
|
||||
|
||||
class ChartItem {
|
||||
public:
|
||||
// Only call on render thread!
|
||||
virtual void render() = 0;
|
||||
bool dirty; // If true, call render() when rebuilding the scene
|
||||
ChartItem *prev, *next; // Double linked list of items
|
||||
const size_t zValue;
|
||||
const bool dragable; // Item can be dragged with the mouse. Must be set in constructor.
|
||||
virtual ~ChartItem(); // Attention: must only be called by render thread.
|
||||
QRectF getRect() const;
|
||||
virtual void startDrag(QPointF pos); // Called when user clicks on a draggable item
|
||||
virtual void drag(QPointF pos); // Called when dragging the item
|
||||
virtual void stopDrag(QPointF pos); // Called when dragging the item finished
|
||||
protected:
|
||||
template <typename T> friend class ChartItemPtr;
|
||||
ChartItem(ChartView &v, size_t z, bool dragable = false);
|
||||
QSizeF sceneSize() const;
|
||||
ChartView &view;
|
||||
void markDirty();
|
||||
QRectF rect;
|
||||
};
|
||||
|
||||
template <typename Node>
|
||||
class HideableChartItem : public ChartItem {
|
||||
protected:
|
||||
HideableChartItem(ChartView &v, size_t z, bool dragable = false);
|
||||
std::unique_ptr<Node> node;
|
||||
bool visible;
|
||||
bool visibleChanged;
|
||||
enum class MoveMode {
|
||||
none, before, after
|
||||
} moveMode; // Node will be moved before or after other node.
|
||||
QSGNode *moveNode; // Node to be moved before/after, or nullptr if move to beginning/end.
|
||||
template<class... Args>
|
||||
void createNode(Args&&... args); // Call to create node with visibility flag.
|
||||
|
||||
void updateVisible(); // Must be called by child class to update visibility flag!
|
||||
void addNodeToView(); // Must be called by child class after creating and initializing the QSG node.
|
||||
void doRearrange(); // Call at beginning of render(), so that the node can be rearranged, if necessary.
|
||||
public:
|
||||
template <typename Node2>
|
||||
friend class HideableChartItem;
|
||||
template <typename Node2>
|
||||
void moveBefore(HideableChartItem<Node2> &item);
|
||||
void moveBack();
|
||||
template <typename Node2>
|
||||
void moveAfter(HideableChartItem<Node2> &item);
|
||||
void moveFront();
|
||||
void setVisible(bool visible);
|
||||
bool isVisible() const;
|
||||
};
|
||||
|
||||
// A shortcut for ChartItems based on a hideable proxy item
|
||||
template <typename Node>
|
||||
using HideableChartProxyItem = HideableChartItem<HideableQSGNode<QSGProxyNode<Node>>>;
|
||||
|
||||
// A chart item that blits a precalculated pixmap onto the scene.
|
||||
// Can be scaled with setScale().
|
||||
class ChartPixmapItem : public HideableChartProxyItem<QSGImageNode> {
|
||||
public:
|
||||
ChartPixmapItem(ChartView &v, size_t z, bool dragable = false);
|
||||
~ChartPixmapItem();
|
||||
|
||||
virtual void setPos(QPointF pos);
|
||||
void drag(QPointF pos) override; // calls setPos() by default
|
||||
void setScale(double scale);
|
||||
void render() override;
|
||||
protected:
|
||||
void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*.
|
||||
std::unique_ptr<QPainter> painter;
|
||||
std::unique_ptr<QImage> img;
|
||||
void setTextureDirty();
|
||||
void setPositionDirty();
|
||||
double scale;
|
||||
private:
|
||||
bool positionDirty; // true if the position changed since last render
|
||||
bool textureDirty; // true if the pixmap changed since last render
|
||||
std::unique_ptr<QSGTexture> texture;
|
||||
};
|
||||
|
||||
// Renders a QGraphicsScene
|
||||
class ChartGraphicsSceneItem : public ChartPixmapItem
|
||||
{
|
||||
public:
|
||||
using ChartPixmapItem::ChartPixmapItem;
|
||||
void draw(QSizeF s, QColor background, QGraphicsScene &scene);
|
||||
};
|
||||
|
||||
// Draw a rectangular background after resize. Children are responsible for calling update().
|
||||
class ChartRectItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartRectItem(ChartView &v, size_t z, const QPen &pen, const QBrush &brush, double radius, bool dragable = false);
|
||||
~ChartRectItem();
|
||||
void resize(QSizeF size);
|
||||
private:
|
||||
QPen pen;
|
||||
QBrush brush;
|
||||
double radius;
|
||||
};
|
||||
|
||||
// A ChartRectItem that is animated on size change.
|
||||
// Same as ChartPixmapItem, but resize is called with a "speed" parameter
|
||||
// (which is actually a time parameter for historical reasons).
|
||||
class AnimatedChartRectItem : public ChartRectItem {
|
||||
public:
|
||||
using ChartRectItem::ChartRectItem;
|
||||
~AnimatedChartRectItem();
|
||||
void setPixmap(const QPixmap &pixmap, int animSpeed);
|
||||
void anim(double progress);
|
||||
private:
|
||||
QPixmap pixmap;
|
||||
QSize originalSize;
|
||||
};
|
||||
|
||||
// A solid disk, potentially with border.
|
||||
class ChartDiskItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartDiskItem(ChartView &v, size_t z, const QPen &pen, const QBrush &brush, bool dragable = false);
|
||||
~ChartDiskItem();
|
||||
void resize(double radius);
|
||||
void setPos(QPointF pos) override;
|
||||
QPointF getPos() const;
|
||||
private:
|
||||
QPen pen;
|
||||
QBrush brush;
|
||||
};
|
||||
|
||||
// Attention: text is only drawn after calling setColor()!
|
||||
class ChartTextItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartTextItem(ChartView &v, size_t z, const QFont &f, const std::vector<QString> &text, bool center);
|
||||
ChartTextItem(ChartView &v, size_t z, const QFont &f, const QString &text);
|
||||
void setColor(const QColor &color); // Draw on transparent background
|
||||
void setColor(const QColor &color, const QColor &background); // Fill rectangle with given background color
|
||||
private:
|
||||
const QFont &f;
|
||||
double fontHeight;
|
||||
bool center;
|
||||
struct Item {
|
||||
QString s;
|
||||
double width;
|
||||
};
|
||||
std::vector<Item> items;
|
||||
};
|
||||
|
||||
// Common data for line and rect items.
|
||||
// Both use the "QRect rect" item of the base class.
|
||||
// Lines are drawn from top-left to bottom-right.
|
||||
class ChartLineItemBase : public HideableChartItem<HideableQSGNode<QSGGeometryNode>> {
|
||||
public:
|
||||
ChartLineItemBase(ChartView &v, size_t z, QColor color, double width);
|
||||
~ChartLineItemBase();
|
||||
void setLine(QPointF from, QPointF to);
|
||||
protected:
|
||||
QColor color;
|
||||
double width;
|
||||
bool positionDirty;
|
||||
bool materialDirty;
|
||||
std::unique_ptr<QSGFlatColorMaterial> material;
|
||||
std::unique_ptr<QSGGeometry> geometry;
|
||||
};
|
||||
|
||||
class ChartLineItem : public ChartLineItemBase {
|
||||
public:
|
||||
using ChartLineItemBase::ChartLineItemBase;
|
||||
void render() override;
|
||||
};
|
||||
|
||||
// A simple rectangle without fill. Specified by any two opposing vertices.
|
||||
class ChartRectLineItem : public ChartLineItemBase {
|
||||
public:
|
||||
using ChartLineItemBase::ChartLineItemBase;
|
||||
void render() override;
|
||||
};
|
||||
|
||||
// Implementation detail of templates - move to serparate header file
|
||||
|
||||
template <typename Node>
|
||||
template <typename Node2>
|
||||
void HideableChartItem<Node>::moveBefore(HideableChartItem<Node2> &item)
|
||||
{
|
||||
moveMode = MoveMode::before;
|
||||
moveNode = item.node.get();
|
||||
markDirty();
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::moveBack()
|
||||
{
|
||||
moveMode = MoveMode::before;
|
||||
moveNode = nullptr;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
template <typename Node2>
|
||||
void HideableChartItem<Node>::moveAfter(HideableChartItem<Node2> &item)
|
||||
{
|
||||
moveMode = MoveMode::after;
|
||||
moveNode = item.node.get();
|
||||
markDirty();
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::moveFront()
|
||||
{
|
||||
moveMode = MoveMode::after;
|
||||
moveNode = nullptr;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::setVisible(bool visibleIn)
|
||||
{
|
||||
if (visible == visibleIn)
|
||||
return;
|
||||
visible = visibleIn;
|
||||
visibleChanged = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
bool HideableChartItem<Node>::isVisible() const
|
||||
{
|
||||
return visible;
|
||||
}
|
||||
|
||||
#endif
|
||||
66
qt-quick/chartitem_private.h
Normal file
66
qt-quick/chartitem_private.h
Normal file
@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Private template implementation for ChartItem child classes
|
||||
|
||||
#ifndef CHARTITEM_PRIVATE_H
|
||||
#define CHARTITEM_PRIVATE_H
|
||||
|
||||
#include "chartitem.h"
|
||||
#include "chartview.h"
|
||||
|
||||
template <typename Node>
|
||||
template<class... Args>
|
||||
void HideableChartItem<Node>::createNode(Args&&... args)
|
||||
{
|
||||
node.reset(new Node(visible, std::forward<Args>(args)...));
|
||||
visibleChanged = false;
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::addNodeToView()
|
||||
{
|
||||
view.addQSGNode(node.get(), zValue, moveMode == MoveMode::after, moveNode);
|
||||
moveNode = nullptr;
|
||||
moveMode = MoveMode::none;
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
HideableChartItem<Node>::HideableChartItem(ChartView &v, size_t z, bool dragable) : ChartItem(v, z, dragable),
|
||||
visible(true), visibleChanged(false), moveMode(MoveMode::none), moveNode(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::updateVisible()
|
||||
{
|
||||
if (visibleChanged)
|
||||
node->setVisible(visible);
|
||||
visibleChanged = false;
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::doRearrange()
|
||||
{
|
||||
if (!node)
|
||||
return;
|
||||
switch (moveMode) {
|
||||
default:
|
||||
case MoveMode::none:
|
||||
return;
|
||||
case MoveMode::before:
|
||||
if (moveNode)
|
||||
view.moveNodeBefore(node.get(), zValue, moveNode);
|
||||
else
|
||||
view.moveNodeBack(node.get(), zValue);
|
||||
break;
|
||||
case MoveMode::after:
|
||||
if (moveNode)
|
||||
view.moveNodeAfter(node.get(), zValue, moveNode);
|
||||
else
|
||||
view.moveNodeFront(node.get(), zValue);
|
||||
break;
|
||||
}
|
||||
moveNode = nullptr;
|
||||
moveMode = MoveMode::none;
|
||||
}
|
||||
|
||||
#endif
|
||||
60
qt-quick/chartitem_ptr.h
Normal file
60
qt-quick/chartitem_ptr.h
Normal file
@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// A stupid pointer class that initializes to null and can be copy
|
||||
// assigned. This is for historical reasons: unique_ptrs to ChartItems
|
||||
// were replaced by plain pointers. Instead of nulling the plain pointers
|
||||
// in the constructors, use this. Ultimately, we might think about making
|
||||
// this thing smarter, once removal of individual ChartItems is implemented.
|
||||
#ifndef CHARITEM_PTR_H
|
||||
#define CHARITEM_PTR_H
|
||||
|
||||
template <typename T>
|
||||
class ChartItemPtr {
|
||||
friend class ChartView; // Only the chart view can create these pointers
|
||||
T *ptr;
|
||||
ChartItemPtr(T *ptr) : ptr(ptr)
|
||||
{
|
||||
}
|
||||
public:
|
||||
ChartItemPtr() : ptr(nullptr)
|
||||
{
|
||||
}
|
||||
ChartItemPtr(const ChartItemPtr &p) : ptr(p.ptr)
|
||||
{
|
||||
}
|
||||
ChartItemPtr(ChartItemPtr &&p) : ptr(p.ptr)
|
||||
{
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
ptr = nullptr;
|
||||
}
|
||||
ChartItemPtr &operator=(const ChartItemPtr &p)
|
||||
{
|
||||
ptr = p.ptr;
|
||||
return *this;
|
||||
}
|
||||
operator bool() const
|
||||
{
|
||||
return !!ptr;
|
||||
}
|
||||
bool operator!() const
|
||||
{
|
||||
return !ptr;
|
||||
}
|
||||
T &operator*() const
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
T *operator->() const
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
void del()
|
||||
{
|
||||
if (!ptr)
|
||||
return;
|
||||
ptr->view.deleteChartItem(*this);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
100
qt-quick/chartitemhelper.h
Normal file
100
qt-quick/chartitemhelper.h
Normal file
@ -0,0 +1,100 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// QSGNode template jugglery to overcome API flaws.
|
||||
|
||||
#ifndef CHARTITEM_HELPER_H
|
||||
#define CHARTITEM_HELPER_H
|
||||
|
||||
#include <memory>
|
||||
#include <QSGNode>
|
||||
|
||||
// In general, we want chart items to be hideable. For example to show/hide
|
||||
// labels on demand. Very sadly, the QSG API is absolutely terrible with
|
||||
// respect to temporarily disabling. Instead of simply having a flag,
|
||||
// a QSGNode is queried using the "isSubtreeBlocked()" virtual function(!).
|
||||
//
|
||||
// Not only is this a slow operation performed on every single node, it
|
||||
// also is often not possible to override this function: For improved
|
||||
// performance, the documentation recommends to create QSG nodes via
|
||||
// QQuickWindow. This provides nodes optimized for the actual hardware.
|
||||
// However, this obviously means that these nodes cannot be derived from!
|
||||
//
|
||||
// In that case, there are two possibilities: Add a proxy node with an
|
||||
// overridden "isSubtreeBlocked()" function or remove the node from the
|
||||
// scene. The former was chosen here, because it is less complex.
|
||||
//
|
||||
// The following slightly cryptic templates are used to unify the two
|
||||
// cases: The QSGNode is generated by our own code or the QSGNode is
|
||||
// obtained from QQuickWindow.
|
||||
//
|
||||
// The "HideableQSGNode<Node>" template augments the QSGNode "Node"
|
||||
// by a "setVisible()" function and overrides "isSubtreeBlocked()"
|
||||
//
|
||||
// The "QSGProxyNode<Node>" template is a QSGNode with a single
|
||||
// child of type "Node".
|
||||
//
|
||||
// Thus, if the node can be created, use:
|
||||
// HideableQSGNode<NodeTypeThatCanBeCreated> node
|
||||
// and if the node can only be obtained from QQuickWindow, use:
|
||||
// HideableQSGNode<QSGProxyNode<NodeThatCantBeCreated>> node
|
||||
// The latter should obviously be typedef-ed.
|
||||
//
|
||||
// Yes, that's all horrible, but if nothing else it teaches us about
|
||||
// composition.
|
||||
template <typename Node>
|
||||
class HideableQSGNode : public Node {
|
||||
bool hidden;
|
||||
bool isSubtreeBlocked() const override final;
|
||||
public:
|
||||
template<class... Args>
|
||||
HideableQSGNode(bool visible, Args&&... args);
|
||||
void setVisible(bool visible);
|
||||
};
|
||||
|
||||
template <typename Node>
|
||||
class QSGProxyNode : public QSGNode {
|
||||
public:
|
||||
std::unique_ptr<Node> node;
|
||||
QSGProxyNode(Node *node);
|
||||
};
|
||||
|
||||
// Implementation detail of templates - move to serparate header file
|
||||
template <typename Node>
|
||||
QSGProxyNode<Node>::QSGProxyNode(Node *node) : node(node)
|
||||
{
|
||||
appendChildNode(node);
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
bool HideableQSGNode<Node>::isSubtreeBlocked() const
|
||||
{
|
||||
return hidden;
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
template<class... Args>
|
||||
HideableQSGNode<Node>::HideableQSGNode(bool visible, Args&&... args) :
|
||||
Node(std::forward<Args>(args)...),
|
||||
hidden(!visible)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableQSGNode<Node>::setVisible(bool visible)
|
||||
{
|
||||
hidden = !visible;
|
||||
Node::markDirty(QSGNode::DirtySubtreeBlocked);
|
||||
}
|
||||
|
||||
// Helper function to set points
|
||||
inline void setPoint(QSGGeometry::Point2D &v, const QPointF &p)
|
||||
{
|
||||
v.set(static_cast<float>(p.x()), static_cast<float>(p.y()));
|
||||
}
|
||||
|
||||
inline void setPoint(QSGGeometry::TexturedPoint2D &v, const QPointF &p, const QPointF &t)
|
||||
{
|
||||
v.set(static_cast<float>(p.x()), static_cast<float>(p.y()),
|
||||
static_cast<float>(t.x()), static_cast<float>(t.y()));
|
||||
}
|
||||
|
||||
#endif
|
||||
353
qt-quick/chartview.cpp
Normal file
353
qt-quick/chartview.cpp
Normal file
@ -0,0 +1,353 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "chartview.h"
|
||||
#include "chartitem.h"
|
||||
|
||||
#include <QQuickWindow>
|
||||
#include <QSGRectangleNode>
|
||||
|
||||
ChartView::ChartView(QQuickItem *parent, size_t maxZ) : QQuickItem(parent),
|
||||
maxZ(maxZ),
|
||||
backgroundDirty(true),
|
||||
rootNode(nullptr)
|
||||
{
|
||||
setFlag(ItemHasContents, true);
|
||||
}
|
||||
|
||||
// Define a hideable dummy QSG node that is used as a parent node to make
|
||||
// all objects of a z-level visible / invisible.
|
||||
using ZNode = HideableQSGNode<QSGNode>;
|
||||
|
||||
class RootNode : public QSGNode
|
||||
{
|
||||
public:
|
||||
RootNode(ChartView *view, QColor backgroundColor, size_t maxZ);
|
||||
~RootNode();
|
||||
ChartView *view;
|
||||
std::unique_ptr<QSGRectangleNode> backgroundNode; // solid background
|
||||
// We entertain one node per Z-level.
|
||||
std::vector<std::unique_ptr<ZNode>> zNodes;
|
||||
};
|
||||
|
||||
RootNode::RootNode(ChartView *view, QColor backgroundColor, size_t maxZ) : view(view)
|
||||
{
|
||||
zNodes.resize(maxZ);
|
||||
|
||||
// Add a background rectangle with a solid color. This could
|
||||
// also be done on the widget level, but would have to be done
|
||||
// separately for desktop and mobile, so do it here.
|
||||
backgroundNode.reset(view->w()->createRectangleNode());
|
||||
appendChildNode(backgroundNode.get());
|
||||
|
||||
for (auto &zNode: zNodes) {
|
||||
zNode.reset(new ZNode(true));
|
||||
appendChildNode(zNode.get());
|
||||
}
|
||||
}
|
||||
|
||||
RootNode::~RootNode()
|
||||
{
|
||||
if (view)
|
||||
view->emergencyShutdown();
|
||||
}
|
||||
|
||||
ChartView::~ChartView()
|
||||
{
|
||||
// Sometimes the rootNode is destructed before the view,
|
||||
// sometimes the other way around. QtQuick is a mess!
|
||||
if (rootNode)
|
||||
rootNode->view = nullptr;
|
||||
}
|
||||
|
||||
void ChartView::freeDeletedChartItems()
|
||||
{
|
||||
ChartItem *nextitem;
|
||||
for (ChartItem *item = deletedItems.first; item; item = nextitem) {
|
||||
nextitem = item->next;
|
||||
delete item;
|
||||
}
|
||||
deletedItems.clear();
|
||||
}
|
||||
|
||||
QSGNode *ChartView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
|
||||
{
|
||||
// The QtQuick drawing interface is utterly bizzare with a distinct 1980ies-style memory management.
|
||||
// This is just a copy of what is found in Qt's documentation.
|
||||
RootNode *n = static_cast<RootNode *>(oldNode);
|
||||
if (!n)
|
||||
n = rootNode = new RootNode(this, backgroundColor, maxZ);
|
||||
|
||||
// Delete all chart items that are marked for deletion.
|
||||
freeDeletedChartItems();
|
||||
|
||||
if (backgroundDirty) {
|
||||
rootNode->backgroundNode->setRect(plotRect);
|
||||
backgroundDirty = false;
|
||||
}
|
||||
|
||||
for (ChartItem *item = dirtyItems.first; item; item = item->next) {
|
||||
item->render();
|
||||
item->dirty = false;
|
||||
}
|
||||
dirtyItems.splice(cleanItems);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// When reparenting the QQuickWidget, QtQuick decides to delete our rootNode
|
||||
// and with it all the QSG nodes, even though we have *not* given the
|
||||
// permission to do so! If the widget is reused, we try to delete the
|
||||
// stale items, whose nodes have already been deleted by QtQuick, leading
|
||||
// to a double-free(). Instead of searching for the cause of this behavior,
|
||||
// let's just hook into the rootNode's destructor and delete the objects
|
||||
// in a controlled manner, so that QtQuick has no more access to them.
|
||||
void ChartView::emergencyShutdown()
|
||||
{
|
||||
// Mark clean and dirty chart items for deletion...
|
||||
cleanItems.splice(deletedItems);
|
||||
dirtyItems.splice(deletedItems);
|
||||
dragableItems.clear();
|
||||
draggedItem.reset();
|
||||
|
||||
// ...and delete them.
|
||||
freeDeletedChartItems();
|
||||
|
||||
// Now delete all the pointers we might have to chart features,
|
||||
// axes, etc. Note that all pointers to chart items are non
|
||||
// owning, so this only resets stale references, but does not
|
||||
// lead to any additional deletion of chart items.
|
||||
resetPointers();
|
||||
|
||||
// The rootNode is being deleted -> remove the reference to that
|
||||
rootNode = nullptr;
|
||||
}
|
||||
|
||||
void ChartView::clearItems()
|
||||
{
|
||||
cleanItems.splice(deletedItems);
|
||||
dirtyItems.splice(deletedItems);
|
||||
}
|
||||
|
||||
static ZNode &getZNode(RootNode &rootNode, size_t z)
|
||||
{
|
||||
size_t idx = std::clamp(z, (size_t)0, rootNode.zNodes.size());
|
||||
return *rootNode.zNodes[idx];
|
||||
}
|
||||
|
||||
void ChartView::addQSGNode(QSGNode *node, size_t z, bool moveAfter, QSGNode *node2)
|
||||
{
|
||||
auto &parent = getZNode(*rootNode, z);
|
||||
if (node2) {
|
||||
if (moveAfter)
|
||||
parent.insertChildNodeAfter(node, node2);
|
||||
else
|
||||
parent.insertChildNodeBefore(node, node2);
|
||||
} else {
|
||||
if (moveAfter)
|
||||
parent.prependChildNode(node);
|
||||
else
|
||||
parent.appendChildNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::moveNodeBefore(QSGNode *node, size_t z, QSGNode *before)
|
||||
{
|
||||
auto &parent = getZNode(*rootNode, z);
|
||||
parent.removeChildNode(node);
|
||||
parent.insertChildNodeBefore(node, before);
|
||||
}
|
||||
|
||||
void ChartView::moveNodeBack(QSGNode *node, size_t z)
|
||||
{
|
||||
auto &parent = getZNode(*rootNode, z);
|
||||
parent.removeChildNode(node);
|
||||
parent.appendChildNode(node);
|
||||
}
|
||||
|
||||
void ChartView::moveNodeAfter(QSGNode *node, size_t z, QSGNode *before)
|
||||
{
|
||||
auto &parent = getZNode(*rootNode, z);
|
||||
parent.removeChildNode(node);
|
||||
parent.insertChildNodeAfter(node, before);
|
||||
}
|
||||
|
||||
void ChartView::moveNodeFront(QSGNode *node, size_t z)
|
||||
{
|
||||
auto &parent = getZNode(*rootNode, z);
|
||||
parent.removeChildNode(node);
|
||||
parent.prependChildNode(node);
|
||||
}
|
||||
|
||||
void ChartView::registerChartItem(ChartItem &item)
|
||||
{
|
||||
cleanItems.append(item);
|
||||
if (item.dragable)
|
||||
dragableItems.push_back(&item);
|
||||
}
|
||||
|
||||
void ChartView::registerDirtyChartItem(ChartItem &item)
|
||||
{
|
||||
if (item.dirty)
|
||||
return;
|
||||
cleanItems.remove(item);
|
||||
dirtyItems.append(item);
|
||||
item.dirty = true;
|
||||
}
|
||||
|
||||
void ChartView::deleteChartItemInternal(ChartItem &item)
|
||||
{
|
||||
if (item.dirty)
|
||||
dirtyItems.remove(item);
|
||||
else
|
||||
cleanItems.remove(item);
|
||||
deletedItems.append(item);
|
||||
if (item.dragable) {
|
||||
// This becomes inefficient if there are many dragable items!
|
||||
auto it = std::find(dragableItems.begin(), dragableItems.end(), &item);
|
||||
if (it == dragableItems.end())
|
||||
fprintf(stderr, "warning: dragable item not found\n");
|
||||
else
|
||||
dragableItems.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
ChartView::ChartItemList::ChartItemList() : first(nullptr), last(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void ChartView::ChartItemList::clear()
|
||||
{
|
||||
first = last = nullptr;
|
||||
}
|
||||
|
||||
void ChartView::ChartItemList::remove(ChartItem &item)
|
||||
{
|
||||
if (item.next)
|
||||
item.next->prev = item.prev;
|
||||
else
|
||||
last = item.prev;
|
||||
if (item.prev)
|
||||
item.prev->next = item.next;
|
||||
else
|
||||
first = item.next;
|
||||
item.prev = item.next = nullptr;
|
||||
}
|
||||
|
||||
void ChartView::ChartItemList::append(ChartItem &item)
|
||||
{
|
||||
if (!first) {
|
||||
item.prev = nullptr;
|
||||
first = &item;
|
||||
} else {
|
||||
item.prev = last;
|
||||
last->next = &item;
|
||||
}
|
||||
item.next = nullptr;
|
||||
last = &item;
|
||||
}
|
||||
|
||||
void ChartView::ChartItemList::splice(ChartItemList &l2)
|
||||
{
|
||||
if (!first) // if list is empty -> nothing to do.
|
||||
return;
|
||||
if (!l2.first) {
|
||||
l2 = *this;
|
||||
} else {
|
||||
l2.last->next = first;
|
||||
first->prev = l2.last;
|
||||
l2.last = last;
|
||||
}
|
||||
clear();
|
||||
}
|
||||
|
||||
QQuickWindow *ChartView::w() const
|
||||
{
|
||||
return window();
|
||||
}
|
||||
|
||||
void ChartView::setBackgroundColor(QColor color)
|
||||
{
|
||||
backgroundColor = color;
|
||||
if (rootNode)
|
||||
rootNode->backgroundNode->setColor(color);
|
||||
}
|
||||
|
||||
QSizeF ChartView::size() const
|
||||
{
|
||||
return boundingRect().size();
|
||||
}
|
||||
|
||||
QRectF ChartView::plotArea() const
|
||||
{
|
||||
return plotRect;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void ChartView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
#else
|
||||
void ChartView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
#endif
|
||||
{
|
||||
plotRect = QRectF(QPointF(0.0, 0.0), newGeometry.size());
|
||||
backgroundDirty = true;
|
||||
plotAreaChanged(plotRect.size());
|
||||
|
||||
// Do we need to call the base-class' version of geometryChanged? Probably for QML?
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
#else
|
||||
QQuickItem::geometryChanged(newGeometry, oldGeometry);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ChartView::setLayerVisibility(size_t z, bool visible)
|
||||
{
|
||||
if (rootNode && z < rootNode->zNodes.size())
|
||||
rootNode->zNodes[z]->setVisible(visible);
|
||||
}
|
||||
|
||||
void ChartView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
// Only left-button for drag events.
|
||||
if (event->button() != Qt::LeftButton)
|
||||
return event->ignore();
|
||||
|
||||
QPointF pos = event->localPos();
|
||||
|
||||
for (auto item: dragableItems) {
|
||||
QRectF rect = item->getRect();
|
||||
if (rect.contains(pos)) {
|
||||
dragStartMouse = pos;
|
||||
dragStartItem = rect.topLeft();
|
||||
draggedItem = item;
|
||||
draggedItem->startDrag(pos);
|
||||
grabMouse();
|
||||
setKeepMouseGrab(true); // don't allow Qt to steal the grab
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void ChartView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (draggedItem) {
|
||||
QPointF pos = event->localPos();
|
||||
draggedItem->stopDrag(pos);
|
||||
draggedItem.reset();
|
||||
ungrabMouse();
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (draggedItem) {
|
||||
QSizeF sceneSize = size();
|
||||
if (sceneSize.width() <= 1.0 || sceneSize.height() <= 1.0)
|
||||
return;
|
||||
draggedItem->drag(event->pos() - dragStartMouse + dragStartItem);
|
||||
update();
|
||||
}
|
||||
}
|
||||
114
qt-quick/chartview.h
Normal file
114
qt-quick/chartview.h
Normal file
@ -0,0 +1,114 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef CHART_VIEW_H
|
||||
#define CHART_VIEW_H
|
||||
|
||||
#include "chartitem_ptr.h"
|
||||
|
||||
#include <QQuickItem>
|
||||
|
||||
class ChartItem;
|
||||
class QSGTexture;
|
||||
class RootNode; // Internal implementation detail
|
||||
|
||||
class ChartView : public QQuickItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ChartView(QQuickItem *parent, size_t maxZ);
|
||||
~ChartView();
|
||||
|
||||
QQuickWindow *w() const; // Make window available to items
|
||||
QSizeF size() const;
|
||||
QRectF plotArea() const;
|
||||
void setBackgroundColor(QColor color); // Chart must be replot for color to become effective.
|
||||
void addQSGNode(QSGNode *node, size_t z, bool moveAfter, QSGNode *node2);
|
||||
// Must only be called in render thread!
|
||||
// If node2 is nullptr move to begin or end of list.
|
||||
void moveNodeBefore(QSGNode *node, size_t z, QSGNode *before);
|
||||
void moveNodeBack(QSGNode *node, size_t z);
|
||||
void moveNodeAfter(QSGNode *node, size_t z, QSGNode *after);
|
||||
void moveNodeFront(QSGNode *node, size_t z);
|
||||
void registerChartItem(ChartItem &item);
|
||||
void registerDirtyChartItem(ChartItem &item);
|
||||
void emergencyShutdown(); // Called when QQuick decides to delete our root node.
|
||||
|
||||
// Create a chart item and add it to the scene.
|
||||
// The item must not be deleted by the caller, but can be
|
||||
// scheduled for deletion using deleteChartItem() below.
|
||||
// Most items can be made invisible, which is preferred over deletion.
|
||||
// All items on the scene will be deleted once the chart is reset.
|
||||
template <typename T, class... Args>
|
||||
ChartItemPtr<T> createChartItem(Args&&... args);
|
||||
|
||||
template <typename T>
|
||||
void deleteChartItem(ChartItemPtr<T> &item);
|
||||
|
||||
protected:
|
||||
void setLayerVisibility(size_t z, bool visible);
|
||||
void clearItems();
|
||||
|
||||
// This is called when Qt decided to reset our rootNode, which invalidates all items on the chart.
|
||||
// The base class must invalidate all pointers and references.
|
||||
virtual void resetPointers() = 0;
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
private:
|
||||
// QtQuick related things
|
||||
size_t maxZ;
|
||||
bool backgroundDirty;
|
||||
QRectF plotRect;
|
||||
QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;
|
||||
QColor backgroundColor;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
#else
|
||||
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
#endif
|
||||
virtual void plotAreaChanged(const QSizeF &size) = 0;
|
||||
|
||||
RootNode *rootNode;
|
||||
|
||||
// There are three double linked lists of chart items:
|
||||
// clean items, dirty items and items to be deleted.
|
||||
// Note that only the render thread must delete chart items,
|
||||
// and therefore these lists are the only owning pointers
|
||||
// to chart items. All other pointers are non-owning and
|
||||
// can therefore become stale.
|
||||
struct ChartItemList {
|
||||
ChartItemList();
|
||||
ChartItem *first, *last;
|
||||
void append(ChartItem &item);
|
||||
void remove(ChartItem &item);
|
||||
void clear();
|
||||
void splice(ChartItemList &list);
|
||||
};
|
||||
ChartItemList cleanItems, dirtyItems, deletedItems;
|
||||
void deleteChartItemInternal(ChartItem &item);
|
||||
void freeDeletedChartItems();
|
||||
|
||||
// Keep a list of dragable items. For now, there are no many,
|
||||
// so keep it unsorted. In the future we might want to sort by
|
||||
// coordinates.
|
||||
std::vector<ChartItem *> dragableItems;
|
||||
ChartItemPtr<ChartItem> draggedItem;
|
||||
QPointF dragStartMouse, dragStartItem;
|
||||
};
|
||||
|
||||
// This implementation detail must be known to users of the class.
|
||||
// Perhaps move it into a chartview_impl.h file.
|
||||
template <typename T, class... Args>
|
||||
ChartItemPtr<T> ChartView::createChartItem(Args&&... args)
|
||||
{
|
||||
return ChartItemPtr(new T(*this, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ChartView::deleteChartItem(ChartItemPtr<T> &item)
|
||||
{
|
||||
deleteChartItemInternal(*item);
|
||||
item.reset();
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -266,7 +266,7 @@ std::vector<BarSeries::SubItem> BarSeries::makeSubItems(std::vector<SubItemDesc>
|
||||
for (auto &[v, dives, label]: items) {
|
||||
if (v > 0.0) {
|
||||
bool selected = allDivesSelected(dives);
|
||||
res.push_back({ view.createChartItem<ChartBarItem>(ChartZValue::Series, barBorderWidth),
|
||||
res.push_back({ view.createChartItem<ChartBarItem>(ChartZValue::Series, theme, barBorderWidth),
|
||||
std::move(dives),
|
||||
{}, from, from + v, bin_nr, selected });
|
||||
if (!label.empty())
|
||||
@ -412,7 +412,7 @@ bool BarSeries::hover(QPointF pos)
|
||||
Item &item = items[highlighted.bar];
|
||||
item.highlight(index.subitem, true, binCount(), theme);
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information = view.createChartItem<InformationBox>(theme);
|
||||
information->setText(makeInfo(item, highlighted.subitem), pos);
|
||||
information->setVisible(true);
|
||||
} else {
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
#ifndef BAR_SERIES_H
|
||||
#define BAR_SERIES_H
|
||||
|
||||
#include "statshelper.h"
|
||||
#include "statsseries.h"
|
||||
#include "statsvariables.h"
|
||||
#include "qt-quick/chartitem_ptr.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@ -32,7 +32,7 @@ BoxSeries::Item::Item(StatsView &view, BoxSeries *series, double lowerBound, dou
|
||||
binName(binName),
|
||||
selected(allDivesSelected(q.dives))
|
||||
{
|
||||
item = view.createChartItem<ChartBoxItem>(ChartZValue::Series, boxBorderWidth);
|
||||
item = view.createChartItem<ChartBoxItem>(ChartZValue::Series, theme, boxBorderWidth);
|
||||
highlight(false, theme);
|
||||
updatePosition(series);
|
||||
}
|
||||
@ -130,7 +130,7 @@ bool BoxSeries::hover(QPointF pos)
|
||||
Item &item = *items[highlighted];
|
||||
item.highlight(true, theme);
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information = view.createChartItem<InformationBox>(theme);
|
||||
information->setText(formatInformation(item), pos);
|
||||
information->setVisible(true);
|
||||
} else {
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
#include "statscolors.h"
|
||||
#include "statsview.h"
|
||||
#include "core/globals.h"
|
||||
#include "qt-quick/chartitemhelper.h"
|
||||
#include "qt-quick/chartitem_private.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QQuickWindow>
|
||||
@ -14,104 +16,11 @@
|
||||
|
||||
static int selectionOverlayPixelSize = 2;
|
||||
|
||||
static int round_up(double f)
|
||||
{
|
||||
return static_cast<int>(ceil(f));
|
||||
}
|
||||
|
||||
ChartItem::ChartItem(StatsView &v, ChartZValue z) :
|
||||
dirty(false), prev(nullptr), next(nullptr),
|
||||
zValue(z), view(v)
|
||||
{
|
||||
// Register before the derived constructors run, so that the
|
||||
// derived classes can mark the item as dirty in the constructor.
|
||||
v.registerChartItem(*this);
|
||||
}
|
||||
|
||||
ChartItem::~ChartItem()
|
||||
{
|
||||
}
|
||||
|
||||
QSizeF ChartItem::sceneSize() const
|
||||
{
|
||||
return view.size();
|
||||
}
|
||||
|
||||
void ChartItem::markDirty()
|
||||
{
|
||||
view.registerDirtyChartItem(*this);
|
||||
}
|
||||
|
||||
ChartPixmapItem::ChartPixmapItem(StatsView &v, ChartZValue z) : HideableChartItem(v, z),
|
||||
positionDirty(false), textureDirty(false)
|
||||
{
|
||||
}
|
||||
|
||||
ChartPixmapItem::~ChartPixmapItem()
|
||||
{
|
||||
painter.reset(); // Make sure to destroy painter before image that is painted on
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setTextureDirty()
|
||||
{
|
||||
textureDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setPositionDirty()
|
||||
{
|
||||
positionDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::render(const StatsTheme &)
|
||||
{
|
||||
if (!node) {
|
||||
createNode(view.w()->createImageNode());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (!img) {
|
||||
resize(QSizeF(1,1));
|
||||
img->fill(Qt::transparent);
|
||||
}
|
||||
if (textureDirty) {
|
||||
texture.reset(view.w()->createTextureFromImage(*img, QQuickWindow::TextureHasAlphaChannel));
|
||||
node->node->setTexture(texture.get());
|
||||
textureDirty = false;
|
||||
}
|
||||
if (positionDirty) {
|
||||
node->node->setRect(rect);
|
||||
positionDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ChartPixmapItem::resize(QSizeF size)
|
||||
{
|
||||
painter.reset();
|
||||
img.reset(new QImage(round_up(size.width()), round_up(size.height()), QImage::Format_ARGB32));
|
||||
painter.reset(new QPainter(img.get()));
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
rect.setSize(size);
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setPos(QPointF pos)
|
||||
{
|
||||
rect.moveTopLeft(pos);
|
||||
setPositionDirty();
|
||||
}
|
||||
|
||||
QRectF ChartPixmapItem::getRect() const
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
|
||||
static const int scatterItemDiameter = 10;
|
||||
static const int scatterItemBorder = 1;
|
||||
|
||||
ChartScatterItem::ChartScatterItem(StatsView &v, ChartZValue z, bool selected) : HideableChartItem(v, z),
|
||||
ChartScatterItem::ChartScatterItem(ChartView &v, size_t z, const StatsTheme &theme, bool selected) : HideableChartItem(v, z),
|
||||
theme(theme),
|
||||
positionDirty(false), textureDirty(false),
|
||||
highlight(selected ? Highlight::Selected : Highlight::Unselected)
|
||||
{
|
||||
@ -122,7 +31,7 @@ ChartScatterItem::~ChartScatterItem()
|
||||
{
|
||||
}
|
||||
|
||||
static QSGTexture *createScatterTexture(StatsView &view, const QColor &color, const QColor &borderColor)
|
||||
static QSGTexture *createScatterTexture(ChartView &view, const QColor &color, const QColor &borderColor)
|
||||
{
|
||||
QImage img(scatterItemDiameter, scatterItemDiameter, QImage::Format_ARGB32);
|
||||
img.fill(Qt::transparent);
|
||||
@ -138,7 +47,7 @@ static QSGTexture *createScatterTexture(StatsView &view, const QColor &color, co
|
||||
return view.w()->createTextureFromImage(img, QQuickWindow::TextureHasAlphaChannel);
|
||||
}
|
||||
|
||||
QSGTexture *ChartScatterItem::getTexture(const StatsTheme &theme) const
|
||||
QSGTexture *ChartScatterItem::getTexture() const
|
||||
{
|
||||
switch (highlight) {
|
||||
default:
|
||||
@ -151,8 +60,9 @@ QSGTexture *ChartScatterItem::getTexture(const StatsTheme &theme) const
|
||||
}
|
||||
}
|
||||
|
||||
void ChartScatterItem::render(const StatsTheme &theme)
|
||||
void ChartScatterItem::render()
|
||||
{
|
||||
doRearrange();
|
||||
if (!theme.scatterItemTexture) {
|
||||
theme.scatterItemTexture = register_global(createScatterTexture(view, theme.fillColor, theme.borderColor));
|
||||
theme.scatterItemSelectedTexture = register_global(createScatterTexture(view, theme.selectedColor, theme.selectedBorderColor));
|
||||
@ -160,12 +70,12 @@ void ChartScatterItem::render(const StatsTheme &theme)
|
||||
}
|
||||
if (!node) {
|
||||
createNode(view.w()->createImageNode());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
addNodeToView();
|
||||
textureDirty = positionDirty = true;
|
||||
}
|
||||
updateVisible();
|
||||
if (textureDirty) {
|
||||
node->node->setTexture(getTexture(theme));
|
||||
node->node->setTexture(getTexture());
|
||||
textureDirty = false;
|
||||
}
|
||||
if (positionDirty) {
|
||||
@ -213,73 +123,8 @@ QRectF ChartScatterItem::getRect() const
|
||||
return rect;
|
||||
}
|
||||
|
||||
ChartRectItem::ChartRectItem(StatsView &v, ChartZValue z,
|
||||
const QPen &pen, const QBrush &brush, double radius) : ChartPixmapItem(v, z),
|
||||
pen(pen), brush(brush), radius(radius)
|
||||
{
|
||||
}
|
||||
|
||||
ChartRectItem::~ChartRectItem()
|
||||
{
|
||||
}
|
||||
|
||||
void ChartRectItem::resize(QSizeF size)
|
||||
{
|
||||
ChartPixmapItem::resize(size);
|
||||
img->fill(Qt::transparent);
|
||||
painter->setPen(pen);
|
||||
painter->setBrush(brush);
|
||||
QSize imgSize = img->size();
|
||||
int width = pen.width();
|
||||
QRect rect(width / 2, width / 2, imgSize.width() - width, imgSize.height() - width);
|
||||
painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
|
||||
}
|
||||
|
||||
ChartTextItem::ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const std::vector<QString> &text, bool center) :
|
||||
ChartPixmapItem(v, z), f(f), center(center)
|
||||
{
|
||||
QFontMetrics fm(f);
|
||||
double totalWidth = 1.0;
|
||||
fontHeight = static_cast<double>(fm.height());
|
||||
double totalHeight = std::max(1.0, static_cast<double>(text.size()) * fontHeight);
|
||||
|
||||
items.reserve(text.size());
|
||||
for (const QString &s: text) {
|
||||
double w = fm.size(Qt::TextSingleLine, s).width();
|
||||
items.push_back({ s, w });
|
||||
if (w > totalWidth)
|
||||
totalWidth = w;
|
||||
}
|
||||
resize(QSizeF(totalWidth, totalHeight));
|
||||
}
|
||||
|
||||
ChartTextItem::ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const QString &text) :
|
||||
ChartTextItem(v, z, f, std::vector<QString>({ text }), true)
|
||||
{
|
||||
}
|
||||
|
||||
void ChartTextItem::setColor(const QColor &c)
|
||||
{
|
||||
setColor(c, Qt::transparent);
|
||||
}
|
||||
|
||||
void ChartTextItem::setColor(const QColor &c, const QColor &background)
|
||||
{
|
||||
img->fill(background);
|
||||
double y = 0.0;
|
||||
painter->setPen(QPen(c));
|
||||
painter->setFont(f);
|
||||
double totalWidth = getRect().width();
|
||||
for (const auto &[s, w]: items) {
|
||||
double x = center ? round((totalWidth - w) / 2.0) : 0.0;
|
||||
QRectF rect(x, y, w, fontHeight);
|
||||
painter->drawText(rect, s);
|
||||
y += fontHeight;
|
||||
}
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
ChartPieItem::ChartPieItem(StatsView &v, ChartZValue z, double borderWidth) : ChartPixmapItem(v, z),
|
||||
ChartPieItem::ChartPieItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth) : ChartPixmapItem(v, z),
|
||||
theme(theme),
|
||||
borderWidth(borderWidth)
|
||||
{
|
||||
}
|
||||
@ -300,7 +145,7 @@ static QBrush makeBrush(QColor fill, bool selected, const StatsTheme &theme)
|
||||
return QBrush(img);
|
||||
}
|
||||
|
||||
void ChartPieItem::drawSegment(double from, double to, QColor fill, QColor border, bool selected, const StatsTheme &theme)
|
||||
void ChartPieItem::drawSegment(double from, double to, QColor fill, QColor border, bool selected)
|
||||
{
|
||||
painter->setPen(QPen(border, borderWidth));
|
||||
painter->setBrush(makeBrush(fill, selected, theme));
|
||||
@ -320,101 +165,13 @@ void ChartPieItem::resize(QSizeF size)
|
||||
img->fill(Qt::transparent);
|
||||
}
|
||||
|
||||
ChartLineItemBase::ChartLineItemBase(StatsView &v, ChartZValue z, QColor color, double width) : HideableChartItem(v, z),
|
||||
ChartLineItemBase::ChartLineItemBase(ChartView &v, size_t z, QColor color, double width) : HideableChartItem(v, z),
|
||||
color(color), width(width), positionDirty(false), materialDirty(false)
|
||||
{
|
||||
}
|
||||
|
||||
ChartLineItemBase::~ChartLineItemBase()
|
||||
{
|
||||
}
|
||||
|
||||
void ChartLineItemBase::setLine(QPointF fromIn, QPointF toIn)
|
||||
{
|
||||
from = fromIn;
|
||||
to = toIn;
|
||||
positionDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
// Helper function to set points
|
||||
void setPoint(QSGGeometry::Point2D &v, const QPointF &p)
|
||||
{
|
||||
v.set(static_cast<float>(p.x()), static_cast<float>(p.y()));
|
||||
}
|
||||
|
||||
void setPoint(QSGGeometry::TexturedPoint2D &v, const QPointF &p, const QPointF &t)
|
||||
{
|
||||
v.set(static_cast<float>(p.x()), static_cast<float>(p.y()),
|
||||
static_cast<float>(t.x()), static_cast<float>(t.y()));
|
||||
}
|
||||
|
||||
void ChartLineItem::render(const StatsTheme &)
|
||||
{
|
||||
if (!node) {
|
||||
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2));
|
||||
geometry->setDrawingMode(QSGGeometry::DrawLines);
|
||||
material.reset(new QSGFlatColorMaterial);
|
||||
createNode();
|
||||
node->setGeometry(geometry.get());
|
||||
node->setMaterial(material.get());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
positionDirty = materialDirty = true;
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (positionDirty) {
|
||||
// Attention: width is a geometry property and therefore handled by position dirty!
|
||||
geometry->setLineWidth(static_cast<float>(width));
|
||||
auto vertices = geometry->vertexDataAsPoint2D();
|
||||
setPoint(vertices[0], from);
|
||||
setPoint(vertices[1], to);
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
|
||||
if (materialDirty) {
|
||||
material->setColor(color);
|
||||
node->markDirty(QSGNode::DirtyMaterial);
|
||||
}
|
||||
|
||||
positionDirty = materialDirty = false;
|
||||
}
|
||||
|
||||
void ChartRectLineItem::render(const StatsTheme &)
|
||||
{
|
||||
if (!node) {
|
||||
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 5));
|
||||
geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
|
||||
material.reset(new QSGFlatColorMaterial);
|
||||
createNode();
|
||||
node->setGeometry(geometry.get());
|
||||
node->setMaterial(material.get());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
positionDirty = materialDirty = true;
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (positionDirty) {
|
||||
// Attention: width is a geometry property and therefore handled by position dirty!
|
||||
geometry->setLineWidth(static_cast<float>(width));
|
||||
auto vertices = geometry->vertexDataAsPoint2D();
|
||||
setPoint(vertices[0], from);
|
||||
setPoint(vertices[1], QPointF(from.x(), to.y()));
|
||||
setPoint(vertices[2], to);
|
||||
setPoint(vertices[3], QPointF(to.x(), from.y()));
|
||||
setPoint(vertices[4], from);
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
|
||||
if (materialDirty) {
|
||||
material->setColor(color);
|
||||
node->markDirty(QSGNode::DirtyMaterial);
|
||||
}
|
||||
|
||||
positionDirty = materialDirty = false;
|
||||
}
|
||||
|
||||
ChartBarItem::ChartBarItem(StatsView &v, ChartZValue z, double borderWidth) : HideableChartItem(v, z),
|
||||
ChartBarItem::ChartBarItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth) : HideableChartItem(v, z),
|
||||
theme(theme),
|
||||
borderWidth(borderWidth), selected(false),
|
||||
positionDirty(false), colorDirty(false), selectedDirty(false)
|
||||
{
|
||||
@ -424,7 +181,7 @@ ChartBarItem::~ChartBarItem()
|
||||
{
|
||||
}
|
||||
|
||||
QSGTexture *ChartBarItem::getSelectedTexture(const StatsTheme &theme) const
|
||||
QSGTexture *ChartBarItem::getSelectedTexture() const
|
||||
{
|
||||
if (!theme.selectedTexture) {
|
||||
QImage img(2, 2, QImage::Format_ARGB32);
|
||||
@ -436,8 +193,9 @@ QSGTexture *ChartBarItem::getSelectedTexture(const StatsTheme &theme) const
|
||||
return theme.selectedTexture;
|
||||
}
|
||||
|
||||
void ChartBarItem::render(const StatsTheme &theme)
|
||||
void ChartBarItem::render()
|
||||
{
|
||||
doRearrange();
|
||||
if (!node) {
|
||||
createNode(view.w()->createRectangleNode());
|
||||
|
||||
@ -450,7 +208,7 @@ void ChartBarItem::render(const StatsTheme &theme)
|
||||
borderNode->setMaterial(borderMaterial.get());
|
||||
|
||||
node->node->appendChildNode(borderNode.get());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
addNodeToView();
|
||||
positionDirty = colorDirty = selectedDirty = true;
|
||||
}
|
||||
updateVisible();
|
||||
@ -481,7 +239,7 @@ void ChartBarItem::render(const StatsTheme &theme)
|
||||
selectionGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4));
|
||||
selectionGeometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
|
||||
selectionMaterial.reset(new QSGTextureMaterial);
|
||||
selectionMaterial->setTexture(getSelectedTexture(theme));
|
||||
selectionMaterial->setTexture(getSelectedTexture());
|
||||
selectionMaterial->setHorizontalWrapMode(QSGTexture::Repeat);
|
||||
selectionMaterial->setVerticalWrapMode(QSGTexture::Repeat);
|
||||
selectionNode.reset(new QSGGeometryNode);
|
||||
@ -545,8 +303,8 @@ QRectF ChartBarItem::getRect() const
|
||||
return rect;
|
||||
}
|
||||
|
||||
ChartBoxItem::ChartBoxItem(StatsView &v, ChartZValue z, double borderWidth) :
|
||||
ChartBarItem(v, z, borderWidth)
|
||||
ChartBoxItem::ChartBoxItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth) :
|
||||
ChartBarItem(v, z, theme, borderWidth)
|
||||
{
|
||||
}
|
||||
|
||||
@ -554,12 +312,13 @@ ChartBoxItem::~ChartBoxItem()
|
||||
{
|
||||
}
|
||||
|
||||
void ChartBoxItem::render(const StatsTheme &theme)
|
||||
void ChartBoxItem::render()
|
||||
{
|
||||
doRearrange();
|
||||
// Remember old dirty values, since ChartBarItem::render() will clear them
|
||||
bool oldPositionDirty = positionDirty;
|
||||
bool oldColorDirty = colorDirty;
|
||||
ChartBarItem::render(theme); // This will create the base node, so no need to check for that.
|
||||
ChartBarItem::render(); // This will create the base node, so no need to check for that.
|
||||
if (!whiskersNode) {
|
||||
whiskersGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 10));
|
||||
whiskersGeometry->setDrawingMode(QSGGeometry::DrawLines);
|
||||
|
||||
@ -1,160 +1,36 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Wrappers around QSGImageNode that allow painting onto an image
|
||||
// and then turning that into a texture to be displayed in a QQuickItem.
|
||||
#ifndef CHART_ITEM_H
|
||||
#define CHART_ITEM_H
|
||||
// Chart item specific to the statistics module
|
||||
#ifndef STATS_CHART_ITEM_H
|
||||
#define STATS_CHART_ITEM_H
|
||||
|
||||
#include "statshelper.h"
|
||||
#include "qt-quick/chartitem.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QPainter>
|
||||
|
||||
class QSGGeometry;
|
||||
class QSGGeometryNode;
|
||||
class QSGFlatColorMaterial;
|
||||
class QSGImageNode;
|
||||
class QSGRectangleNode;
|
||||
class QSGTexture;
|
||||
class QSGTextureMaterial;
|
||||
class StatsTheme;
|
||||
class StatsView;
|
||||
enum class ChartZValue : int;
|
||||
|
||||
class ChartItem {
|
||||
public:
|
||||
// Only call on render thread!
|
||||
virtual void render(const StatsTheme &theme) = 0;
|
||||
bool dirty; // If true, call render() when rebuilding the scene
|
||||
ChartItem *prev, *next; // Double linked list of items
|
||||
const ChartZValue zValue;
|
||||
virtual ~ChartItem(); // Attention: must only be called by render thread.
|
||||
protected:
|
||||
ChartItem(StatsView &v, ChartZValue z);
|
||||
QSizeF sceneSize() const;
|
||||
StatsView &view;
|
||||
void markDirty();
|
||||
};
|
||||
|
||||
template <typename Node>
|
||||
class HideableChartItem : public ChartItem {
|
||||
protected:
|
||||
HideableChartItem(StatsView &v, ChartZValue z);
|
||||
std::unique_ptr<Node> node;
|
||||
bool visible;
|
||||
bool visibleChanged;
|
||||
template<class... Args>
|
||||
void createNode(Args&&... args); // Call to create node with visibility flag.
|
||||
void updateVisible(); // Must be called by child class to update visibility flag!
|
||||
public:
|
||||
void setVisible(bool visible);
|
||||
};
|
||||
|
||||
// A shortcut for ChartItems based on a hideable proxy item
|
||||
template <typename Node>
|
||||
using HideableChartProxyItem = HideableChartItem<HideableQSGNode<QSGProxyNode<Node>>>;
|
||||
|
||||
// A chart item that blits a precalculated pixmap onto the scene.
|
||||
class ChartPixmapItem : public HideableChartProxyItem<QSGImageNode> {
|
||||
public:
|
||||
ChartPixmapItem(StatsView &v, ChartZValue z);
|
||||
~ChartPixmapItem();
|
||||
|
||||
void setPos(QPointF pos);
|
||||
void render(const StatsTheme &theme) override;
|
||||
QRectF getRect() const;
|
||||
protected:
|
||||
void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*.
|
||||
std::unique_ptr<QPainter> painter;
|
||||
std::unique_ptr<QImage> img;
|
||||
void setTextureDirty();
|
||||
void setPositionDirty();
|
||||
QRectF rect;
|
||||
private:
|
||||
bool positionDirty; // true if the position changed since last render
|
||||
bool textureDirty; // true if the pixmap changed since last render
|
||||
std::unique_ptr<QSGTexture> texture;
|
||||
};
|
||||
|
||||
// Draw a rectangular background after resize. Children are responsible for calling update().
|
||||
class ChartRectItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartRectItem(StatsView &v, ChartZValue z, const QPen &pen, const QBrush &brush, double radius);
|
||||
~ChartRectItem();
|
||||
void resize(QSizeF size);
|
||||
private:
|
||||
QPen pen;
|
||||
QBrush brush;
|
||||
double radius;
|
||||
};
|
||||
|
||||
// Attention: text is only drawn after calling setColor()!
|
||||
class ChartTextItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const std::vector<QString> &text, bool center);
|
||||
ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const QString &text);
|
||||
void setColor(const QColor &color); // Draw on transparent background
|
||||
void setColor(const QColor &color, const QColor &background); // Fill rectangle with given background color
|
||||
private:
|
||||
const QFont &f;
|
||||
double fontHeight;
|
||||
bool center;
|
||||
struct Item {
|
||||
QString s;
|
||||
double width;
|
||||
};
|
||||
std::vector<Item> items;
|
||||
};
|
||||
class ChartView;
|
||||
|
||||
// A pie chart item: draws disk segments onto a pixmap.
|
||||
class ChartPieItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartPieItem(StatsView &v, ChartZValue z, double borderWidth);
|
||||
void drawSegment(double from, double to, QColor fill, QColor border, bool selected, const StatsTheme &theme); // from and to are relative (0-1 is full disk).
|
||||
ChartPieItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||
void drawSegment(double from, double to, QColor fill, QColor border, bool selected); // from and to are relative (0-1 is full disk).
|
||||
void resize(QSizeF size); // As in base class, but clears the canvas
|
||||
private:
|
||||
const StatsTheme &theme;
|
||||
double borderWidth;
|
||||
};
|
||||
|
||||
// Common data for line and rect items. Both are represented by two points.
|
||||
class ChartLineItemBase : public HideableChartItem<HideableQSGNode<QSGGeometryNode>> {
|
||||
public:
|
||||
ChartLineItemBase(StatsView &v, ChartZValue z, QColor color, double width);
|
||||
~ChartLineItemBase();
|
||||
void setLine(QPointF from, QPointF to);
|
||||
protected:
|
||||
QPointF from, to;
|
||||
QColor color;
|
||||
double width;
|
||||
bool positionDirty;
|
||||
bool materialDirty;
|
||||
std::unique_ptr<QSGFlatColorMaterial> material;
|
||||
std::unique_ptr<QSGGeometry> geometry;
|
||||
};
|
||||
|
||||
class ChartLineItem : public ChartLineItemBase {
|
||||
public:
|
||||
using ChartLineItemBase::ChartLineItemBase;
|
||||
void render(const StatsTheme &theme) override;
|
||||
};
|
||||
|
||||
// A simple rectangle without fill. Specified by any two opposing vertices.
|
||||
class ChartRectLineItem : public ChartLineItemBase {
|
||||
public:
|
||||
using ChartLineItemBase::ChartLineItemBase;
|
||||
void render(const StatsTheme &theme) override;
|
||||
};
|
||||
|
||||
// A bar in a bar chart: a rectangle bordered by lines.
|
||||
class ChartBarItem : public HideableChartProxyItem<QSGRectangleNode> {
|
||||
public:
|
||||
ChartBarItem(StatsView &v, ChartZValue z, double borderWidth);
|
||||
ChartBarItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||
~ChartBarItem();
|
||||
void setColor(QColor color, QColor borderColor);
|
||||
void setRect(const QRectF &rect);
|
||||
void setSelected(bool selected);
|
||||
QRectF getRect() const;
|
||||
void render(const StatsTheme &theme) override;
|
||||
void render() override;
|
||||
protected:
|
||||
const StatsTheme &theme;
|
||||
QColor color, borderColor;
|
||||
double borderWidth;
|
||||
QRectF rect;
|
||||
@ -170,17 +46,17 @@ private:
|
||||
std::unique_ptr<QSGGeometryNode> selectionNode;
|
||||
std::unique_ptr<QSGTextureMaterial> selectionMaterial;
|
||||
std::unique_ptr<QSGGeometry> selectionGeometry;
|
||||
QSGTexture *getSelectedTexture(const StatsTheme &theme) const;
|
||||
QSGTexture *getSelectedTexture() const;
|
||||
};
|
||||
|
||||
// A box-and-whiskers item. This is a bit lazy: derive from the bar item and add whiskers.
|
||||
class ChartBoxItem : public ChartBarItem {
|
||||
public:
|
||||
ChartBoxItem(StatsView &v, ChartZValue z, double borderWidth);
|
||||
ChartBoxItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||
~ChartBoxItem();
|
||||
void setBox(const QRectF &rect, double min, double max, double median); // The rect describes Q1, Q3.
|
||||
QRectF getRect() const; // Note: this extends the center rectangle to include the whiskers.
|
||||
void render(const StatsTheme &theme) override;
|
||||
void render() override;
|
||||
private:
|
||||
double min, max, median;
|
||||
std::unique_ptr<QSGGeometryNode> whiskersNode;
|
||||
@ -194,7 +70,7 @@ private:
|
||||
// scatter item here, but so it is for now.
|
||||
class ChartScatterItem : public HideableChartProxyItem<QSGImageNode> {
|
||||
public:
|
||||
ChartScatterItem(StatsView &v, ChartZValue z, bool selected);
|
||||
ChartScatterItem(ChartView &v, size_t z, const StatsTheme &theme, bool selected);
|
||||
~ChartScatterItem();
|
||||
|
||||
// Currently, there is no highlighted and selected status.
|
||||
@ -205,49 +81,17 @@ public:
|
||||
};
|
||||
void setPos(QPointF pos); // Specifies the *center* of the item.
|
||||
void setHighlight(Highlight highlight); // In the future, support different kinds of scatter items.
|
||||
void render(const StatsTheme &theme) override;
|
||||
void render() override;
|
||||
QRectF getRect() const;
|
||||
bool contains(QPointF point) const;
|
||||
bool inRect(const QRectF &rect) const;
|
||||
private:
|
||||
QSGTexture *getTexture(const StatsTheme &theme) const;
|
||||
const StatsTheme &theme;
|
||||
QSGTexture *getTexture() const;
|
||||
QRectF rect;
|
||||
QSizeF textureSize;
|
||||
bool positionDirty, textureDirty;
|
||||
Highlight highlight;
|
||||
};
|
||||
|
||||
// Implementation detail of templates - move to serparate header file
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::setVisible(bool visibleIn)
|
||||
{
|
||||
if (visible == visibleIn)
|
||||
return;
|
||||
visible = visibleIn;
|
||||
visibleChanged = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
template<class... Args>
|
||||
void HideableChartItem<Node>::createNode(Args&&... args)
|
||||
{
|
||||
node.reset(new Node(visible, std::forward<Args>(args)...));
|
||||
visibleChanged = false;
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
HideableChartItem<Node>::HideableChartItem(StatsView &v, ChartZValue z) : ChartItem(v, z),
|
||||
visible(true), visibleChanged(false)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::updateVisible()
|
||||
{
|
||||
if (visibleChanged)
|
||||
node->setVisible(visible);
|
||||
visibleChanged = false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
static const double histogramMarkerWidth = 2.0;
|
||||
|
||||
HistogramMarker::HistogramMarker(StatsView &view, double val, bool horizontal,
|
||||
HistogramMarker::HistogramMarker(ChartView &view, double val, bool horizontal,
|
||||
QColor color, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
ChartLineItem(view, ChartZValue::ChartFeatures, color, histogramMarkerWidth),
|
||||
xAxis(xAxis), yAxis(yAxis),
|
||||
|
||||
@ -5,12 +5,11 @@
|
||||
#include "chartitem.h"
|
||||
|
||||
class StatsAxis;
|
||||
class StatsView;
|
||||
|
||||
// A line marking median or mean in histograms
|
||||
class HistogramMarker : public ChartLineItem {
|
||||
public:
|
||||
HistogramMarker(StatsView &view, double val, bool horizontal, QColor color, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
HistogramMarker(ChartView &view, double val, bool horizontal, QColor color, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
void updatePosition();
|
||||
private:
|
||||
StatsAxis *xAxis, *yAxis;
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
#include "informationbox.h"
|
||||
#include "statscolors.h"
|
||||
#include "statsview.h"
|
||||
#include "zvalues.h"
|
||||
|
||||
#include <QFontMetrics>
|
||||
@ -9,11 +8,11 @@ static const int informationBorder = 2;
|
||||
static const double informationBorderRadius = 4.0; // Radius of rounded corners
|
||||
static const int distanceFromPointer = 10; // Distance to place box from mouse pointer or scatter item
|
||||
|
||||
InformationBox::InformationBox(StatsView &v) :
|
||||
InformationBox::InformationBox(ChartView &v, const StatsTheme &theme) :
|
||||
ChartRectItem(v, ChartZValue::InformationBox,
|
||||
QPen(v.getCurrentTheme().informationBorderColor, informationBorder),
|
||||
QBrush(v.getCurrentTheme().informationColor), informationBorderRadius),
|
||||
theme(v.getCurrentTheme()),
|
||||
QPen(theme.informationBorderColor, informationBorder),
|
||||
QBrush(theme.informationColor), informationBorderRadius),
|
||||
theme(theme),
|
||||
width(0.0),
|
||||
height(0.0)
|
||||
{
|
||||
|
||||
@ -10,11 +10,12 @@
|
||||
#include <memory>
|
||||
|
||||
struct dive;
|
||||
class StatsView;
|
||||
class ChartView;
|
||||
class StatsTheme;
|
||||
|
||||
// Information window showing data of highlighted dive
|
||||
struct InformationBox : ChartRectItem {
|
||||
InformationBox(StatsView &);
|
||||
InformationBox(ChartView &, const StatsTheme &theme);
|
||||
void setText(const std::vector<QString> &text, QPointF pos);
|
||||
void setPos(QPointF pos);
|
||||
int recommendedMaxLines() const;
|
||||
|
||||
@ -14,12 +14,13 @@ static const double legendBoxBorderRadius = 4.0; // radius of rounded corners
|
||||
static const double legendBoxScale = 0.8; // 1.0: text-height of the used font
|
||||
static const double legendInternalBorderSize = 2.0;
|
||||
|
||||
Legend::Legend(StatsView &view, const std::vector<QString> &names) :
|
||||
Legend::Legend(ChartView &view, const StatsTheme &theme, const std::vector<QString> &names) :
|
||||
ChartRectItem(view, ChartZValue::Legend,
|
||||
QPen(view.getCurrentTheme().legendBorderColor, legendBorderSize),
|
||||
QBrush(view.getCurrentTheme().legendColor), legendBoxBorderRadius),
|
||||
QPen(theme.legendBorderColor, legendBorderSize),
|
||||
QBrush(theme.legendColor), legendBoxBorderRadius,
|
||||
true),
|
||||
displayedItems(0), width(0.0), height(0.0),
|
||||
theme(view.getCurrentTheme()),
|
||||
theme(theme),
|
||||
posInitialized(false)
|
||||
{
|
||||
entries.reserve(names.size());
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#ifndef STATS_LEGEND_H
|
||||
#define STATS_LEGEND_H
|
||||
|
||||
#include "chartitem.h"
|
||||
#include "qt-quick/chartitem.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -13,7 +13,7 @@ class StatsTheme;
|
||||
|
||||
class Legend : public ChartRectItem {
|
||||
public:
|
||||
Legend(StatsView &view, const std::vector<QString> &names);
|
||||
Legend(ChartView &view, const StatsTheme &theme, const std::vector<QString> &names);
|
||||
void resize(); // called when the chart size changes.
|
||||
void setPos(QPointF pos); // Attention: not virtual - always call on this class.
|
||||
private:
|
||||
|
||||
@ -78,13 +78,13 @@ void PieSeries::Item::highlight(ChartPieItem &item, int bin_nr, bool highlight,
|
||||
QColor border = highlight ? theme.highlightedBorderColor : theme.borderColor;
|
||||
if (innerLabel)
|
||||
innerLabel->setColor(highlight ? theme.darkLabelColor : theme.labelColor(bin_nr, numBins), fill);
|
||||
item.drawSegment(angleFrom, angleTo, fill, border, selected, theme);
|
||||
item.drawSegment(angleFrom, angleTo, fill, border, selected);
|
||||
}
|
||||
|
||||
PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName,
|
||||
std::vector<std::pair<QString, std::vector<dive *>>> data, ChartSortMode sortMode) :
|
||||
StatsSeries(view, xAxis, yAxis),
|
||||
item(view.createChartItem<ChartPieItem>(ChartZValue::Series, pieBorderWidth)),
|
||||
item(view.createChartItem<ChartPieItem>(ChartZValue::Series, theme, pieBorderWidth)),
|
||||
categoryName(categoryName),
|
||||
radius(0),
|
||||
highlighted(-1),
|
||||
@ -257,7 +257,7 @@ bool PieSeries::hover(QPointF pos)
|
||||
if (highlighted >= 0 && highlighted < (int)items.size()) {
|
||||
items[highlighted].highlight(*item, highlighted, true, (int)items.size(), theme);
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information = view.createChartItem<InformationBox>(theme);
|
||||
information->setText(makeInfo(highlighted), pos);
|
||||
information->setVisible(true);
|
||||
} else {
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
#ifndef PIE_SERIES_H
|
||||
#define PIE_SERIES_H
|
||||
|
||||
#include "statshelper.h"
|
||||
#include "statsseries.h"
|
||||
#include "qt-quick/chartitem_ptr.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
|
||||
static const double quartileMarkerSize = 15.0;
|
||||
|
||||
QuartileMarker::QuartileMarker(StatsView &view, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
ChartLineItem(view, ChartZValue::ChartFeatures, view.getCurrentTheme().quartileMarkerColor, 2.0),
|
||||
QuartileMarker::QuartileMarker(ChartView &view, const StatsTheme &theme, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
ChartLineItem(view, ChartZValue::ChartFeatures, theme.quartileMarkerColor, 2.0),
|
||||
xAxis(xAxis), yAxis(yAxis),
|
||||
pos(pos),
|
||||
value(value)
|
||||
|
||||
@ -5,11 +5,10 @@
|
||||
#include "chartitem.h"
|
||||
|
||||
class StatsAxis;
|
||||
class StatsView;
|
||||
|
||||
class QuartileMarker : public ChartLineItem {
|
||||
public:
|
||||
QuartileMarker(StatsView &view, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
QuartileMarker(ChartView &view, const StatsTheme &theme, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
~QuartileMarker();
|
||||
void updatePosition();
|
||||
private:
|
||||
|
||||
@ -9,10 +9,10 @@
|
||||
|
||||
static const double regressionLineWidth = 2.0;
|
||||
|
||||
RegressionItem::RegressionItem(StatsView &view, regression_data reg,
|
||||
RegressionItem::RegressionItem(ChartView &view, const StatsTheme &theme, regression_data reg,
|
||||
StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
ChartPixmapItem(view, ChartZValue::ChartFeatures),
|
||||
theme(view.getCurrentTheme()),
|
||||
theme(theme),
|
||||
xAxis(xAxis), yAxis(yAxis), reg(reg),
|
||||
regression(true), confidence(true)
|
||||
{
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
|
||||
class StatsAxis;
|
||||
class StatsTheme;
|
||||
class StatsView;
|
||||
|
||||
struct regression_data {
|
||||
double a,b;
|
||||
@ -16,7 +15,7 @@ struct regression_data {
|
||||
|
||||
class RegressionItem : public ChartPixmapItem {
|
||||
public:
|
||||
RegressionItem(StatsView &view, regression_data data, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
RegressionItem(ChartView &view, const StatsTheme &theme, regression_data data, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
~RegressionItem();
|
||||
void updatePosition();
|
||||
void setFeatures(bool regression, bool confidence);
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
#include "chartitem.h"
|
||||
#include "informationbox.h"
|
||||
#include "statscolors.h"
|
||||
#include "statshelper.h"
|
||||
#include "statstranslations.h"
|
||||
#include "statsvariables.h"
|
||||
#include "statsview.h"
|
||||
@ -24,8 +23,8 @@ ScatterSeries::~ScatterSeries()
|
||||
{
|
||||
}
|
||||
|
||||
ScatterSeries::Item::Item(StatsView &view, ScatterSeries *series, dive *d, double pos, double value) :
|
||||
item(view.createChartItem<ChartScatterItem>(ChartZValue::Series, d->selected)),
|
||||
ScatterSeries::Item::Item(StatsView &view, ScatterSeries *series, const StatsTheme &theme, dive *d, double pos, double value) :
|
||||
item(view.createChartItem<ChartScatterItem>(ChartZValue::Series, theme, d->selected)),
|
||||
d(d),
|
||||
selected(d->selected),
|
||||
pos(pos),
|
||||
@ -50,7 +49,7 @@ void ScatterSeries::Item::highlight(bool highlight)
|
||||
|
||||
void ScatterSeries::append(dive *d, double pos, double value)
|
||||
{
|
||||
items.emplace_back(view, this, d, pos, value);
|
||||
items.emplace_back(view, this, theme, d, pos, value);
|
||||
}
|
||||
|
||||
void ScatterSeries::updatePositions()
|
||||
@ -183,7 +182,7 @@ bool ScatterSeries::hover(QPointF pos)
|
||||
return false;
|
||||
} else {
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information = view.createChartItem<InformationBox>(theme);
|
||||
|
||||
std::vector<QString> text;
|
||||
text.reserve(highlighted.size() * 5);
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
#include "statshelper.h"
|
||||
#include "statsseries.h"
|
||||
#include "qt-quick/chartitem_ptr.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -41,7 +42,7 @@ private:
|
||||
dive *d;
|
||||
bool selected;
|
||||
double pos, value;
|
||||
Item(StatsView &view, ScatterSeries *series, dive *d, double pos, double value);
|
||||
Item(StatsView &view, ScatterSeries *series, const StatsTheme &theme, dive *d, double pos, double value);
|
||||
void updatePosition(ScatterSeries *series);
|
||||
void highlight(bool highlight);
|
||||
};
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "statsaxis.h"
|
||||
#include "statscolors.h"
|
||||
#include "statshelper.h"
|
||||
#include "statstranslations.h"
|
||||
#include "statsvariables.h"
|
||||
#include "statsview.h"
|
||||
@ -25,9 +24,9 @@ static const double axisLabelSpaceVertical = 2.0; // Space between axis or ticks
|
||||
static const double axisTitleSpaceHorizontal = 2.0; // Space between labels and title
|
||||
static const double axisTitleSpaceVertical = 2.0; // Space between labels and title
|
||||
|
||||
StatsAxis::StatsAxis(StatsView &view, const QString &title, bool horizontal, bool labelsBetweenTicks) :
|
||||
StatsAxis::StatsAxis(ChartView &view, const StatsTheme &theme, const QString &title, bool horizontal, bool labelsBetweenTicks) :
|
||||
ChartPixmapItem(view, ChartZValue::Axes),
|
||||
theme(view.getCurrentTheme()),
|
||||
theme(theme),
|
||||
line(view.createChartItem<ChartLineItem>(ChartZValue::Axes, theme.axisColor, axisWidth)),
|
||||
title(title), horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks),
|
||||
size(1.0), zeroOnScreen(0.0), min(0.0), max(1.0), labelWidth(0.0)
|
||||
@ -256,8 +255,9 @@ void StatsAxis::setPos(QPointF pos)
|
||||
}
|
||||
}
|
||||
|
||||
ValueAxis::ValueAxis(StatsView &view, const QString &title, double min, double max, int decimals, bool horizontal) :
|
||||
StatsAxis(view, title, horizontal, false),
|
||||
ValueAxis::ValueAxis(ChartView &view, const StatsTheme &theme,
|
||||
const QString &title, double min, double max, int decimals, bool horizontal) :
|
||||
StatsAxis(view, theme, title, horizontal, false),
|
||||
min(min), max(max), decimals(decimals)
|
||||
{
|
||||
// Avoid degenerate cases
|
||||
@ -317,8 +317,8 @@ void ValueAxis::updateLabels()
|
||||
}
|
||||
}
|
||||
|
||||
CountAxis::CountAxis(StatsView &view, const QString &title, int count, bool horizontal) :
|
||||
ValueAxis(view, title, 0.0, (double)count, 0, horizontal),
|
||||
CountAxis::CountAxis(ChartView &view, const StatsTheme &theme, const QString &title, int count, bool horizontal) :
|
||||
ValueAxis(view, theme, title, 0.0, (double)count, 0, horizontal),
|
||||
count(count)
|
||||
{
|
||||
}
|
||||
@ -376,8 +376,9 @@ void CountAxis::updateLabels()
|
||||
}
|
||||
}
|
||||
|
||||
CategoryAxis::CategoryAxis(StatsView &view, const QString &title, const std::vector<QString> &labels, bool horizontal) :
|
||||
StatsAxis(view, title, horizontal, true),
|
||||
CategoryAxis::CategoryAxis(ChartView &view, const StatsTheme &theme,
|
||||
const QString &title, const std::vector<QString> &labels, bool horizontal) :
|
||||
StatsAxis(view, theme, title, horizontal, true),
|
||||
labelsText(labels)
|
||||
{
|
||||
if (!labels.empty())
|
||||
@ -437,8 +438,9 @@ void CategoryAxis::updateLabels()
|
||||
}
|
||||
}
|
||||
|
||||
HistogramAxis::HistogramAxis(StatsView &view, const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) :
|
||||
StatsAxis(view, title, horizontal, false),
|
||||
HistogramAxis::HistogramAxis(ChartView &view, const StatsTheme &theme,
|
||||
const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) :
|
||||
StatsAxis(view, theme, title, horizontal, false),
|
||||
bin_values(std::move(bins))
|
||||
{
|
||||
if (bin_values.size() < 2) // Less than two makes no sense -> there must be at least one category
|
||||
@ -643,7 +645,7 @@ static std::vector<HistogramAxisEntry> timeRangeToBins(double from, double to)
|
||||
return res;
|
||||
}
|
||||
|
||||
DateAxis::DateAxis(StatsView &view, const QString &title, double from, double to, bool horizontal) :
|
||||
HistogramAxis(view, title, timeRangeToBins(from, to), horizontal)
|
||||
DateAxis::DateAxis(ChartView &view, const StatsTheme &theme, const QString &title, double from, double to, bool horizontal) :
|
||||
HistogramAxis(view, theme, title, timeRangeToBins(from, to), horizontal)
|
||||
{
|
||||
}
|
||||
|
||||
@ -3,12 +3,10 @@
|
||||
#define STATS_AXIS_H
|
||||
|
||||
#include "chartitem.h"
|
||||
#include "statshelper.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class StatsView;
|
||||
class ChartLineItem;
|
||||
class QFontMetrics;
|
||||
|
||||
@ -34,7 +32,7 @@ public:
|
||||
|
||||
std::vector<double> ticksPositions() const; // Positions in screen coordinates
|
||||
protected:
|
||||
StatsAxis(StatsView &view, const QString &title, bool horizontal, bool labelsBetweenTicks);
|
||||
StatsAxis(ChartView &view, const StatsTheme &theme, const QString &title, bool horizontal, bool labelsBetweenTicks);
|
||||
|
||||
const StatsTheme &theme; // Initialized once in constructor.
|
||||
ChartItemPtr<ChartLineItem> line;
|
||||
@ -73,7 +71,8 @@ private:
|
||||
|
||||
class ValueAxis : public StatsAxis {
|
||||
public:
|
||||
ValueAxis(StatsView &view, const QString &title, double min, double max, int decimals, bool horizontal);
|
||||
ValueAxis(ChartView &view, const StatsTheme &theme, const QString &title,
|
||||
double min, double max, int decimals, bool horizontal);
|
||||
private:
|
||||
double min, max;
|
||||
int decimals;
|
||||
@ -83,7 +82,7 @@ private:
|
||||
|
||||
class CountAxis : public ValueAxis {
|
||||
public:
|
||||
CountAxis(StatsView &view, const QString &title, int count, bool horizontal);
|
||||
CountAxis(ChartView &view, const StatsTheme &theme, const QString &title, int count, bool horizontal);
|
||||
private:
|
||||
int count;
|
||||
void updateLabels() override;
|
||||
@ -92,7 +91,8 @@ private:
|
||||
|
||||
class CategoryAxis : public StatsAxis {
|
||||
public:
|
||||
CategoryAxis(StatsView &view, const QString &title, const std::vector<QString> &labels, bool horizontal);
|
||||
CategoryAxis(ChartView &view, const StatsTheme &theme, const QString &title,
|
||||
const std::vector<QString> &labels, bool horizontal);
|
||||
private:
|
||||
std::vector<QString> labelsText;
|
||||
void updateLabels();
|
||||
@ -107,7 +107,8 @@ struct HistogramAxisEntry {
|
||||
|
||||
class HistogramAxis : public StatsAxis {
|
||||
public:
|
||||
HistogramAxis(StatsView &view, const QString &title, std::vector<HistogramAxisEntry> bin_values, bool horizontal);
|
||||
HistogramAxis(ChartView &view, const StatsTheme &theme, const QString &title,
|
||||
std::vector<HistogramAxisEntry> bin_values, bool horizontal);
|
||||
private:
|
||||
void updateLabels() override;
|
||||
std::pair<QString, QString> getFirstLastLabel() const override;
|
||||
@ -117,7 +118,7 @@ private:
|
||||
|
||||
class DateAxis : public HistogramAxis {
|
||||
public:
|
||||
DateAxis(StatsView &view, const QString &title, double from, double to, bool horizontal);
|
||||
DateAxis(ChartView &view, const StatsTheme &theme, const QString &title, double from, double to, bool horizontal);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// The background grid of a chart
|
||||
|
||||
#include "statshelper.h"
|
||||
#include "qt-quick/chartitem_ptr.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Helper functions to render the stats. Includes
|
||||
// QSGNode template jugglery to overcome API flaws.
|
||||
// Helper functions to render the stats.
|
||||
#ifndef STATSHELPER_H
|
||||
#define STATSHELPER_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <QPointF>
|
||||
#include <QSGNode>
|
||||
#include "core/dive.h"
|
||||
|
||||
struct dive;
|
||||
|
||||
// Round positions to integer values to avoid ugly artifacts
|
||||
QPointF roundPos(const QPointF &p);
|
||||
@ -16,128 +14,4 @@ QPointF roundPos(const QPointF &p);
|
||||
// Are all dives in this vector selected?
|
||||
bool allDivesSelected(const std::vector<dive *> &dives);
|
||||
|
||||
// A stupid pointer class that initializes to null and can be copy
|
||||
// assigned. This is for historical reasons: unique_ptrs to ChartItems
|
||||
// were replaced by plain pointers. Instead of nulling the plain pointers
|
||||
// in the constructors, use this. Ultimately, we might think about making
|
||||
// this thing smarter, once removal of individual ChartItems is implemented.
|
||||
template <typename T>
|
||||
class ChartItemPtr {
|
||||
friend class StatsView; // Only the stats view can create these pointers
|
||||
T *ptr;
|
||||
ChartItemPtr(T *ptr) : ptr(ptr)
|
||||
{
|
||||
}
|
||||
public:
|
||||
ChartItemPtr() : ptr(nullptr)
|
||||
{
|
||||
}
|
||||
ChartItemPtr(const ChartItemPtr &p) : ptr(p.ptr)
|
||||
{
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
ptr = nullptr;
|
||||
}
|
||||
ChartItemPtr &operator=(const ChartItemPtr &p)
|
||||
{
|
||||
ptr = p.ptr;
|
||||
return *this;
|
||||
}
|
||||
operator bool() const
|
||||
{
|
||||
return !!ptr;
|
||||
}
|
||||
bool operator!() const
|
||||
{
|
||||
return !ptr;
|
||||
}
|
||||
T &operator*() const
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
T *operator->() const
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
// In general, we want chart items to be hideable. For example to show/hide
|
||||
// labels on demand. Very sadly, the QSG API is absolutely terrible with
|
||||
// respect to temporarily disabling. Instead of simply having a flag,
|
||||
// a QSGNode is queried using the "isSubtreeBlocked()" virtual function(!).
|
||||
//
|
||||
// Not only is this a slow operation performed on every single node, it
|
||||
// also is often not possible to override this function: For improved
|
||||
// performance, the documentation recommends to create QSG nodes via
|
||||
// QQuickWindow. This provides nodes optimized for the actual hardware.
|
||||
// However, this obviously means that these nodes cannot be derived from!
|
||||
//
|
||||
// In that case, there are two possibilities: Add a proxy node with an
|
||||
// overridden "isSubtreeBlocked()" function or remove the node from the
|
||||
// scene. The former was chosen here, because it is less complex.
|
||||
//
|
||||
// The following slightly cryptic templates are used to unify the two
|
||||
// cases: The QSGNode is generated by our own code or the QSGNode is
|
||||
// obtained from QQuickWindow.
|
||||
//
|
||||
// The "HideableQSGNode<Node>" template augments the QSGNode "Node"
|
||||
// by a "setVisible()" function and overrides "isSubtreeBlocked()"
|
||||
//
|
||||
// The "QSGProxyNode<Node>" template is a QSGNode with a single
|
||||
// child of type "Node".
|
||||
//
|
||||
// Thus, if the node can be created, use:
|
||||
// HideableQSGNode<NodeTypeThatCanBeCreated> node
|
||||
// and if the node can only be obtained from QQuickWindow, use:
|
||||
// HideableQSGNode<QSGProxyNode<NodeThatCantBeCreated>> node
|
||||
// The latter should obviously be typedef-ed.
|
||||
//
|
||||
// Yes, that's all horrible, but if nothing else it teaches us about
|
||||
// composition.
|
||||
template <typename Node>
|
||||
class HideableQSGNode : public Node {
|
||||
bool hidden;
|
||||
bool isSubtreeBlocked() const override final;
|
||||
public:
|
||||
template<class... Args>
|
||||
HideableQSGNode(bool visible, Args&&... args);
|
||||
void setVisible(bool visible);
|
||||
};
|
||||
|
||||
template <typename Node>
|
||||
class QSGProxyNode : public QSGNode {
|
||||
public:
|
||||
std::unique_ptr<Node> node;
|
||||
QSGProxyNode(Node *node);
|
||||
};
|
||||
|
||||
// Implementation detail of templates - move to serparate header file
|
||||
template <typename Node>
|
||||
QSGProxyNode<Node>::QSGProxyNode(Node *node) : node(node)
|
||||
{
|
||||
appendChildNode(node);
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
bool HideableQSGNode<Node>::isSubtreeBlocked() const
|
||||
{
|
||||
return hidden;
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
template<class... Args>
|
||||
HideableQSGNode<Node>::HideableQSGNode(bool visible, Args&&... args) :
|
||||
Node(std::forward<Args>(args)...),
|
||||
hidden(!visible)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableQSGNode<Node>::setVisible(bool visible)
|
||||
{
|
||||
hidden = !visible;
|
||||
Node::markDirty(QSGNode::DirtySubtreeBlocked);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -21,29 +21,21 @@
|
||||
#include "core/selection.h"
|
||||
#include "core/trip.h"
|
||||
|
||||
#include <array> // for std::array
|
||||
#include <cmath>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickWindow>
|
||||
#include <QSGImageNode>
|
||||
#include <QSGRectangleNode>
|
||||
#include <QSGTexture>
|
||||
|
||||
// Constants that control the graph layouts
|
||||
static const double sceneBorder = 5.0; // Border between scene edges and statitistics view
|
||||
static const double titleBorder = 2.0; // Border between title and chart
|
||||
static const double selectionLassoWidth = 2.0; // Border between title and chart
|
||||
|
||||
StatsView::StatsView(QQuickItem *parent) : QQuickItem(parent),
|
||||
backgroundDirty(true),
|
||||
StatsView::StatsView(QQuickItem *parent) : ChartView(parent, ChartZValue::Count),
|
||||
currentTheme(&getStatsTheme(false)),
|
||||
highlightedSeries(nullptr),
|
||||
xAxis(nullptr),
|
||||
yAxis(nullptr),
|
||||
draggedItem(nullptr),
|
||||
restrictDives(false),
|
||||
rootNode(nullptr)
|
||||
restrictDives(false)
|
||||
{
|
||||
setBackgroundColor(currentTheme->backgroundColor);
|
||||
setFlag(ItemHasContents, true);
|
||||
|
||||
connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &StatsView::replotIfVisible);
|
||||
@ -67,22 +59,12 @@ StatsView::~StatsView()
|
||||
|
||||
void StatsView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QPointF pos = event->localPos();
|
||||
|
||||
// Currently, we only support dragging of the legend. If other objects
|
||||
// should be made draggable, this needs to be generalized.
|
||||
if (legend) {
|
||||
QRectF rect = legend->getRect();
|
||||
if (legend->getRect().contains(pos)) {
|
||||
dragStartMouse = pos;
|
||||
dragStartItem = rect.topLeft();
|
||||
draggedItem = &*legend;
|
||||
grabMouse();
|
||||
setKeepMouseGrab(true); // don't allow Qt to steal the grab
|
||||
// Handle dragging of items
|
||||
ChartView::mousePressEvent(event);
|
||||
if (event->isAccepted())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QPointF pos = event->localPos();
|
||||
SelectionModifier modifier;
|
||||
modifier.shift = (event->modifiers() & Qt::ShiftModifier) != 0;
|
||||
modifier.ctrl = (event->modifiers() & Qt::ControlModifier) != 0;
|
||||
@ -97,7 +79,7 @@ void StatsView::mousePressEvent(QMouseEvent *event)
|
||||
{ return s->supportsLassoSelection(); })) {
|
||||
if (selectionRect)
|
||||
deleteChartItem(selectionRect); // Ooops. Already a selection in place.
|
||||
dragStartMouse = pos;
|
||||
selectionStartMouse = pos;
|
||||
selectionRect = createChartItem<ChartRectLineItem>(ChartZValue::Selection, currentTheme->selectionLassoColor, selectionLassoWidth);
|
||||
selectionModifier = modifier;
|
||||
oldSelection = modifier.ctrl ? getDiveSelection() : std::vector<dive *>();
|
||||
@ -107,12 +89,9 @@ void StatsView::mousePressEvent(QMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void StatsView::mouseReleaseEvent(QMouseEvent *)
|
||||
void StatsView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (draggedItem) {
|
||||
draggedItem = nullptr;
|
||||
ungrabMouse();
|
||||
}
|
||||
ChartView::mouseReleaseEvent(event);
|
||||
|
||||
if (selectionRect) {
|
||||
deleteChartItem(selectionRect);
|
||||
@ -121,187 +100,10 @@ void StatsView::mouseReleaseEvent(QMouseEvent *)
|
||||
}
|
||||
}
|
||||
|
||||
// Define a hideable dummy QSG node that is used as a parent node to make
|
||||
// all objects of a z-level visible / invisible.
|
||||
using ZNode = HideableQSGNode<QSGNode>;
|
||||
|
||||
class RootNode : public QSGNode
|
||||
{
|
||||
public:
|
||||
RootNode(StatsView &view);
|
||||
~RootNode();
|
||||
StatsView &view;
|
||||
std::unique_ptr<QSGRectangleNode> backgroundNode; // solid background
|
||||
// We entertain one node per Z-level.
|
||||
std::array<std::unique_ptr<ZNode>, (size_t)ChartZValue::Count> zNodes;
|
||||
};
|
||||
|
||||
RootNode::RootNode(StatsView &view) : view(view)
|
||||
{
|
||||
// Add a background rectangle with a solid color. This could
|
||||
// also be done on the widget level, but would have to be done
|
||||
// separately for desktop and mobile, so do it here.
|
||||
backgroundNode.reset(view.w()->createRectangleNode());
|
||||
backgroundNode->setColor(view.getCurrentTheme().backgroundColor);
|
||||
appendChildNode(backgroundNode.get());
|
||||
|
||||
for (auto &zNode: zNodes) {
|
||||
zNode.reset(new ZNode(true));
|
||||
appendChildNode(zNode.get());
|
||||
}
|
||||
}
|
||||
|
||||
RootNode::~RootNode()
|
||||
{
|
||||
view.emergencyShutdown();
|
||||
}
|
||||
|
||||
void StatsView::freeDeletedChartItems()
|
||||
{
|
||||
ChartItem *nextitem;
|
||||
for (ChartItem *item = deletedItems.first; item; item = nextitem) {
|
||||
nextitem = item->next;
|
||||
delete item;
|
||||
}
|
||||
deletedItems.clear();
|
||||
}
|
||||
|
||||
QSGNode *StatsView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
|
||||
{
|
||||
// The QtQuick drawing interface is utterly bizzare with a distinct 1980ies-style memory management.
|
||||
// This is just a copy of what is found in Qt's documentation.
|
||||
RootNode *n = static_cast<RootNode *>(oldNode);
|
||||
if (!n)
|
||||
n = rootNode = new RootNode(*this);
|
||||
|
||||
// Delete all chart items that are marked for deletion.
|
||||
freeDeletedChartItems();
|
||||
|
||||
if (backgroundDirty) {
|
||||
rootNode->backgroundNode->setRect(plotRect);
|
||||
backgroundDirty = false;
|
||||
}
|
||||
|
||||
for (ChartItem *item = dirtyItems.first; item; item = item->next) {
|
||||
item->render(*currentTheme);
|
||||
item->dirty = false;
|
||||
}
|
||||
dirtyItems.splice(cleanItems);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// When reparenting the QQuickWidget, QtQuick decides to delete our rootNode
|
||||
// and with it all the QSG nodes, even though we have *not* given the
|
||||
// permission to do so! If the widget is reused, we try to delete the
|
||||
// stale items, whose nodes have already been deleted by QtQuick, leading
|
||||
// to a double-free(). Instead of searching for the cause of this behavior,
|
||||
// let's just hook into the rootNodes destructor and delete the objects
|
||||
// in a controlled manner, so that QtQuick has no more access to them.
|
||||
void StatsView::emergencyShutdown()
|
||||
{
|
||||
// Mark clean and dirty chart items for deletion...
|
||||
cleanItems.splice(deletedItems);
|
||||
dirtyItems.splice(deletedItems);
|
||||
|
||||
// ...and delete them.
|
||||
freeDeletedChartItems();
|
||||
|
||||
// Now delete all the pointers we might have to chart features,
|
||||
// axes, etc. Note that all pointers to chart items are non
|
||||
// owning, so this only resets stale references, but does not
|
||||
// lead to any additional deletion of chart items.
|
||||
reset();
|
||||
|
||||
// The rootNode is being deleted -> remove the reference to that
|
||||
rootNode = nullptr;
|
||||
}
|
||||
|
||||
void StatsView::addQSGNode(QSGNode *node, ChartZValue z)
|
||||
{
|
||||
int idx = std::clamp((int)z, 0, (int)ChartZValue::Count - 1);
|
||||
rootNode->zNodes[idx]->appendChildNode(node);
|
||||
}
|
||||
|
||||
void StatsView::registerChartItem(ChartItem &item)
|
||||
{
|
||||
cleanItems.append(item);
|
||||
}
|
||||
|
||||
void StatsView::registerDirtyChartItem(ChartItem &item)
|
||||
{
|
||||
if (item.dirty)
|
||||
return;
|
||||
cleanItems.remove(item);
|
||||
dirtyItems.append(item);
|
||||
item.dirty = true;
|
||||
}
|
||||
|
||||
void StatsView::deleteChartItemInternal(ChartItem &item)
|
||||
{
|
||||
if (item.dirty)
|
||||
dirtyItems.remove(item);
|
||||
else
|
||||
cleanItems.remove(item);
|
||||
deletedItems.append(item);
|
||||
}
|
||||
|
||||
StatsView::ChartItemList::ChartItemList() : first(nullptr), last(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void StatsView::ChartItemList::clear()
|
||||
{
|
||||
first = last = nullptr;
|
||||
}
|
||||
|
||||
void StatsView::ChartItemList::remove(ChartItem &item)
|
||||
{
|
||||
if (item.next)
|
||||
item.next->prev = item.prev;
|
||||
else
|
||||
last = item.prev;
|
||||
if (item.prev)
|
||||
item.prev->next = item.next;
|
||||
else
|
||||
first = item.next;
|
||||
item.prev = item.next = nullptr;
|
||||
}
|
||||
|
||||
void StatsView::ChartItemList::append(ChartItem &item)
|
||||
{
|
||||
if (!first) {
|
||||
first = &item;
|
||||
} else {
|
||||
item.prev = last;
|
||||
last->next = &item;
|
||||
}
|
||||
last = &item;
|
||||
}
|
||||
|
||||
void StatsView::ChartItemList::splice(ChartItemList &l2)
|
||||
{
|
||||
if (!first) // if list is empty -> nothing to do.
|
||||
return;
|
||||
if (!l2.first) {
|
||||
l2 = *this;
|
||||
} else {
|
||||
l2.last->next = first;
|
||||
first->prev = l2.last;
|
||||
l2.last = last;
|
||||
}
|
||||
clear();
|
||||
}
|
||||
|
||||
QQuickWindow *StatsView::w() const
|
||||
{
|
||||
return window();
|
||||
}
|
||||
|
||||
void StatsView::setTheme(bool dark)
|
||||
{
|
||||
currentTheme = &getStatsTheme(dark);
|
||||
rootNode->backgroundNode->setColor(currentTheme->backgroundColor);
|
||||
setBackgroundColor(currentTheme->backgroundColor);
|
||||
}
|
||||
|
||||
const StatsTheme &StatsView::getCurrentTheme() const
|
||||
@ -309,34 +111,6 @@ const StatsTheme &StatsView::getCurrentTheme() const
|
||||
return *currentTheme;
|
||||
}
|
||||
|
||||
QSizeF StatsView::size() const
|
||||
{
|
||||
return boundingRect().size();
|
||||
}
|
||||
|
||||
QRectF StatsView::plotArea() const
|
||||
{
|
||||
return plotRect;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void StatsView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
#else
|
||||
void StatsView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
#endif
|
||||
{
|
||||
plotRect = QRectF(QPointF(0.0, 0.0), newGeometry.size());
|
||||
backgroundDirty = true;
|
||||
plotAreaChanged(plotRect.size());
|
||||
|
||||
// Do we need to call the base-class' version of geometryChanged? Probably for QML?
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
#else
|
||||
QQuickItem::geometryChanged(newGeometry, oldGeometry);
|
||||
#endif
|
||||
}
|
||||
|
||||
void StatsView::plotAreaChanged(const QSizeF &s)
|
||||
{
|
||||
double left = sceneBorder;
|
||||
@ -404,17 +178,11 @@ void StatsView::divesSelected(const QVector<dive *> &dives)
|
||||
|
||||
void StatsView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (draggedItem) {
|
||||
QSizeF sceneSize = size();
|
||||
if (sceneSize.width() <= 1.0 || sceneSize.height() <= 1.0)
|
||||
return;
|
||||
draggedItem->setPos(event->pos() - dragStartMouse + dragStartItem);
|
||||
update();
|
||||
}
|
||||
ChartView::mouseMoveEvent(event);
|
||||
|
||||
if (selectionRect) {
|
||||
QPointF p1 = event->pos();
|
||||
QPointF p2 = dragStartMouse;
|
||||
QPointF p2 = selectionStartMouse;
|
||||
selectionRect->setLine(p1, p2);
|
||||
QRectF rect(std::min(p1.x(), p2.x()), std::min(p1.y(), p2.y()),
|
||||
fabs(p2.x() - p1.x()), fabs(p2.y() - p1.y()));
|
||||
@ -483,7 +251,7 @@ void StatsView::updateTitlePos()
|
||||
template <typename T, class... Args>
|
||||
T *StatsView::createAxis(const QString &title, Args&&... args)
|
||||
{
|
||||
return &*createChartItem<T>(title, std::forward<Args>(args)...);
|
||||
return &*createChartItem<T>(*currentTheme, title, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void StatsView::setAxes(StatsAxis *x, StatsAxis *y)
|
||||
@ -495,10 +263,15 @@ void StatsView::setAxes(StatsAxis *x, StatsAxis *y)
|
||||
}
|
||||
|
||||
void StatsView::reset()
|
||||
{
|
||||
resetPointers();
|
||||
clearItems();
|
||||
}
|
||||
|
||||
void StatsView::resetPointers()
|
||||
{
|
||||
highlightedSeries = nullptr;
|
||||
xAxis = yAxis = nullptr;
|
||||
draggedItem = nullptr;
|
||||
title.reset();
|
||||
legend.reset();
|
||||
regressionItem.reset();
|
||||
@ -506,10 +279,6 @@ void StatsView::reset()
|
||||
medianMarker.reset();
|
||||
selectionRect.reset();
|
||||
|
||||
// Mark clean and dirty chart items for deletion
|
||||
cleanItems.splice(deletedItems);
|
||||
dirtyItems.splice(deletedItems);
|
||||
|
||||
series.clear();
|
||||
quartileMarkers.clear();
|
||||
grid.reset();
|
||||
@ -539,7 +308,7 @@ void StatsView::plot(const StatsState &stateIn)
|
||||
state = stateIn;
|
||||
plotChart();
|
||||
updateFeatures(); // Show / hide chart features, such as legend, etc.
|
||||
plotAreaChanged(plotRect.size());
|
||||
plotAreaChanged(plotArea().size());
|
||||
update();
|
||||
}
|
||||
|
||||
@ -610,8 +379,7 @@ void StatsView::updateFeatures()
|
||||
legend->setVisible(state.legend);
|
||||
|
||||
// For labels, we are brutal: simply show/hide the whole z-level with the labels
|
||||
if (rootNode)
|
||||
rootNode->zNodes[(int)ChartZValue::SeriesLabels]->setVisible(state.labels);
|
||||
setLayerVisibility(ChartZValue::SeriesLabels, state.labels);
|
||||
|
||||
if (meanMarker)
|
||||
meanMarker->setVisible(state.mean);
|
||||
@ -764,7 +532,7 @@ void StatsView::plotBarChart(const std::vector<dive *> &dives,
|
||||
setAxes(catAxis, valAxis);
|
||||
|
||||
// Paint legend first, because the bin-names will be moved away from.
|
||||
legend = createChartItem<Legend>(data.vbinNames);
|
||||
legend = createChartItem<Legend>(*currentTheme, data.vbinNames);
|
||||
|
||||
std::vector<BarSeries::MultiItem> items;
|
||||
items.reserve(data.hbins.size());
|
||||
@ -989,7 +757,7 @@ void StatsView::plotPieChart(const std::vector<dive *> &dives, ChartSortMode sor
|
||||
|
||||
PieSeries *series = createSeries<PieSeries>(categoryVariable->name(), std::move(data), sortMode);
|
||||
|
||||
legend = createChartItem<Legend>(series->binNames());
|
||||
legend = createChartItem<Legend>(*currentTheme, series->binNames());
|
||||
}
|
||||
|
||||
void StatsView::plotDiscreteBoxChart(const std::vector<dive *> &dives,
|
||||
@ -1059,11 +827,11 @@ void StatsView::plotDiscreteScatter(const std::vector<dive *> &dives,
|
||||
StatsQuartiles quartiles = StatsVariable::quartiles(array);
|
||||
if (quartiles.isValid()) {
|
||||
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
||||
x, quartiles.q1, catAxis, valAxis));
|
||||
*currentTheme, x, quartiles.q1, catAxis, valAxis));
|
||||
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
||||
x, quartiles.q2, catAxis, valAxis));
|
||||
*currentTheme, x, quartiles.q2, catAxis, valAxis));
|
||||
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
||||
x, quartiles.q3, catAxis, valAxis));
|
||||
*currentTheme, x, quartiles.q3, catAxis, valAxis));
|
||||
}
|
||||
x += 1.0;
|
||||
}
|
||||
@ -1214,7 +982,7 @@ void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives,
|
||||
*categoryBinner, categoryBins, !isHorizontal);
|
||||
|
||||
BarPlotData data(categoryBins, *valueBinner);
|
||||
legend = createChartItem<Legend>(data.vbinNames);
|
||||
legend = createChartItem<Legend>(*currentTheme, data.vbinNames);
|
||||
|
||||
CountAxis *valAxis = createCountAxis(data.maxCategoryCount, isHorizontal);
|
||||
|
||||
@ -1365,5 +1133,5 @@ void StatsView::plotScatter(const std::vector<dive *> &dives, const StatsVariabl
|
||||
// y = ax + b
|
||||
struct regression_data reg = linear_regression(points);
|
||||
if (!std::isnan(reg.a))
|
||||
regressionItem = createChartItem<RegressionItem>(reg, xAxis, yAxis);
|
||||
regressionItem = createChartItem<RegressionItem>(*currentTheme, reg, xAxis, yAxis);
|
||||
}
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
#define STATS_VIEW_H
|
||||
|
||||
#include "statsstate.h"
|
||||
#include "statshelper.h"
|
||||
#include "statsselection.h"
|
||||
#include "qt-quick/chartview.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QQuickItem>
|
||||
|
||||
struct dive;
|
||||
struct StatsBinner;
|
||||
@ -18,7 +18,6 @@ struct StatsVariable;
|
||||
|
||||
class StatsSeries;
|
||||
class CategoryAxis;
|
||||
class ChartItem;
|
||||
class ChartRectLineItem;
|
||||
class ChartTextItem;
|
||||
class CountAxis;
|
||||
@ -30,15 +29,12 @@ class StatsAxis;
|
||||
class StatsGrid;
|
||||
class StatsTheme;
|
||||
class Legend;
|
||||
class QSGTexture;
|
||||
class RootNode; // Internal implementation detail
|
||||
|
||||
enum class ChartSubType : int;
|
||||
enum class ChartZValue : int;
|
||||
enum class StatsOperation : int;
|
||||
enum class ChartSortMode : int;
|
||||
|
||||
class StatsView : public QQuickItem {
|
||||
class StatsView : public ChartView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
StatsView();
|
||||
@ -50,42 +46,15 @@ public:
|
||||
void restrictToSelection();
|
||||
void unrestrict();
|
||||
int restrictionCount() const; // <0: no restriction
|
||||
QQuickWindow *w() const; // Make window available to items
|
||||
QSizeF size() const;
|
||||
QRectF plotArea() const;
|
||||
void setTheme(bool dark); // Chart must be replot for theme to become effective.
|
||||
const StatsTheme &getCurrentTheme() const;
|
||||
void addQSGNode(QSGNode *node, ChartZValue z); // Must only be called in render thread!
|
||||
void registerChartItem(ChartItem &item);
|
||||
void registerDirtyChartItem(ChartItem &item);
|
||||
void emergencyShutdown(); // Called when QQuick decides to delete our root node.
|
||||
|
||||
// Create a chart item and add it to the scene.
|
||||
// The item must not be deleted by the caller, but can be
|
||||
// scheduled for deletion using deleteChartItem() below.
|
||||
// Most items can be made invisible, which is preferred over deletion.
|
||||
// All items on the scene will be deleted once the chart is reset.
|
||||
template <typename T, class... Args>
|
||||
ChartItemPtr<T> createChartItem(Args&&... args);
|
||||
|
||||
template <typename T>
|
||||
void deleteChartItem(ChartItemPtr<T> &item);
|
||||
private slots:
|
||||
void replotIfVisible();
|
||||
void divesSelected(const QVector<dive *> &dives);
|
||||
private:
|
||||
// QtQuick related things
|
||||
bool backgroundDirty;
|
||||
QRectF plotRect;
|
||||
QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
#else
|
||||
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
#endif
|
||||
void plotAreaChanged(const QSizeF &size);
|
||||
void plotAreaChanged(const QSizeF &size) override;
|
||||
void reset(); // clears all series and axes
|
||||
void resetPointers() override;
|
||||
void setAxes(StatsAxis *x, StatsAxis *y);
|
||||
void plotBarChart(const std::vector<dive *> &dives,
|
||||
ChartSubType subType, ChartSortMode sortMode,
|
||||
@ -151,10 +120,9 @@ private:
|
||||
StatsAxis *xAxis, *yAxis;
|
||||
ChartItemPtr<ChartTextItem> title;
|
||||
ChartItemPtr<Legend> legend;
|
||||
Legend *draggedItem;
|
||||
ChartItemPtr<RegressionItem> regressionItem;
|
||||
ChartItemPtr<ChartRectLineItem> selectionRect;
|
||||
QPointF dragStartMouse, dragStartItem;
|
||||
QPointF selectionStartMouse;
|
||||
SelectionModifier selectionModifier;
|
||||
std::vector<dive *> oldSelection;
|
||||
bool restrictDives;
|
||||
@ -165,40 +133,6 @@ private:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
RootNode *rootNode;
|
||||
|
||||
// There are three double linked lists of chart items:
|
||||
// clean items, dirty items and items to be deleted.
|
||||
// Note that only the render thread must delete chart items,
|
||||
// and therefore these lists are the only owning pointers
|
||||
// to chart items. All other pointers are non-owning and
|
||||
// can therefore become stale.
|
||||
struct ChartItemList {
|
||||
ChartItemList();
|
||||
ChartItem *first, *last;
|
||||
void append(ChartItem &item);
|
||||
void remove(ChartItem &item);
|
||||
void clear();
|
||||
void splice(ChartItemList &list);
|
||||
};
|
||||
ChartItemList cleanItems, dirtyItems, deletedItems;
|
||||
void deleteChartItemInternal(ChartItem &item);
|
||||
void freeDeletedChartItems();
|
||||
};
|
||||
|
||||
// This implementation detail must be known to users of the class.
|
||||
// Perhaps move it into a statsview_impl.h file.
|
||||
template <typename T, class... Args>
|
||||
ChartItemPtr<T> StatsView::createChartItem(Args&&... args)
|
||||
{
|
||||
return ChartItemPtr(new T(*this, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void StatsView::deleteChartItem(ChartItemPtr<T> &item)
|
||||
{
|
||||
deleteChartItemInternal(*item);
|
||||
item.reset();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -4,8 +4,13 @@
|
||||
// with smaller z-values. For the same z-value objects are
|
||||
// drawn in order of addition to the scene.
|
||||
#ifndef ZVALUES_H
|
||||
#define ZVALUES_H
|
||||
|
||||
enum class ChartZValue {
|
||||
// Encapsulating an enum in a struct is stupid, but allows us
|
||||
// to not poison the namespace and yet autoconvert to int
|
||||
// (in constrast to enum class). enum is so broken!
|
||||
struct ChartZValue {
|
||||
enum ZValues {
|
||||
Grid = 0,
|
||||
Series,
|
||||
Axes,
|
||||
@ -16,5 +21,6 @@ enum class ChartZValue {
|
||||
Legend,
|
||||
Count
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "qt-models/maplocationmodel.h"
|
||||
#endif
|
||||
|
||||
#include "profile-widget/profileview.h"
|
||||
#include "stats/statsview.h"
|
||||
#include "core/devicedetails.h"
|
||||
#include "core/globals.h"
|
||||
@ -26,7 +27,6 @@
|
||||
#include "qt-models/divesummarymodel.h"
|
||||
#include "qt-models/messagehandlermodel.h"
|
||||
#include "qt-models/mobilelistmodel.h"
|
||||
#include "profile-widget/qmlprofile.h"
|
||||
#include "core/downloadfromdcthread.h"
|
||||
#include "core/subsurfacestartup.h" // for testqml
|
||||
#include "core/metrics.h"
|
||||
@ -219,7 +219,6 @@ static void register_qml_types(QQmlEngine *engine)
|
||||
#ifdef SUBSURFACE_MOBILE
|
||||
register_qml_type<QMLManager>("QMLManager");
|
||||
register_qml_type<StatsManager>("StatsManager");
|
||||
register_qml_type<QMLProfile>("QMLProfile");
|
||||
register_qml_type<DiveImportedModel>("DCImportModel");
|
||||
register_qml_type<DiveSummaryModel>("DiveSummaryModel");
|
||||
register_qml_type<ChartListModel>("ChartListModel");
|
||||
@ -229,5 +228,6 @@ static void register_qml_types(QQmlEngine *engine)
|
||||
register_qml_type<MapWidgetHelper>("MapWidgetHelper");
|
||||
register_qml_type<MapLocationModel>("MapLocationModel");
|
||||
#endif
|
||||
register_qml_type<ProfileView>("ProfileView");
|
||||
register_qml_type<StatsView>("StatsView");
|
||||
}
|
||||
|
||||
@ -31,6 +31,8 @@ void TestProfile::init()
|
||||
QCoreApplication::setOrganizationName("Subsurface");
|
||||
QCoreApplication::setOrganizationDomain("subsurface.hohndel.org");
|
||||
QCoreApplication::setApplicationName("Subsurface");
|
||||
|
||||
setlocale(LC_ALL, "C");
|
||||
}
|
||||
void TestProfile::testProfileExport()
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user