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)
|
qt_add_resources(SUBSURFACE_RESOURCES map-widget/qml/map-widget.qrc)
|
||||||
set(SUBSURFACE_MAPWIDGET subsurface_mapwidget)
|
set(SUBSURFACE_MAPWIDGET subsurface_mapwidget)
|
||||||
endif()
|
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()
|
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)
|
set(SUBSURFACE_MAPWIDGET subsurface_mapwidget)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
@ -420,6 +420,7 @@ if(MAPSUPPORT)
|
|||||||
add_subdirectory(map-widget)
|
add_subdirectory(map-widget)
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(mobile-widgets)
|
add_subdirectory(mobile-widgets)
|
||||||
|
add_subdirectory(qt-quick)
|
||||||
add_subdirectory(stats)
|
add_subdirectory(stats)
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(backend-shared)
|
add_subdirectory(backend-shared)
|
||||||
@ -473,6 +474,7 @@ if (SUBSURFACE_TARGET_EXECUTABLE MATCHES "MobileExecutable")
|
|||||||
subsurface_commands
|
subsurface_commands
|
||||||
subsurface_corelib
|
subsurface_corelib
|
||||||
subsurface_stats
|
subsurface_stats
|
||||||
|
subsurface_qtquick
|
||||||
kirigamiplugin
|
kirigamiplugin
|
||||||
${SUBSURFACE_LINK_LIBRARIES}
|
${SUBSURFACE_LINK_LIBRARIES}
|
||||||
)
|
)
|
||||||
@ -499,6 +501,7 @@ elseif (SUBSURFACE_TARGET_EXECUTABLE MATCHES "DesktopExecutable")
|
|||||||
subsurface_commands
|
subsurface_commands
|
||||||
subsurface_corelib
|
subsurface_corelib
|
||||||
subsurface_stats
|
subsurface_stats
|
||||||
|
subsurface_qtquick
|
||||||
${SUBSURFACE_LINK_LIBRARIES}
|
${SUBSURFACE_LINK_LIBRARIES}
|
||||||
)
|
)
|
||||||
add_dependencies(subsurface_desktop_preferences subsurface_generated_ui)
|
add_dependencies(subsurface_desktop_preferences subsurface_generated_ui)
|
||||||
|
|||||||
@ -45,7 +45,7 @@ SOURCES += subsurface-mobile-main.cpp \
|
|||||||
core/fulltext.cpp \
|
core/fulltext.cpp \
|
||||||
core/subsurfacestartup.cpp \
|
core/subsurfacestartup.cpp \
|
||||||
core/pref.c \
|
core/pref.c \
|
||||||
core/profile.c \
|
core/profile.cpp \
|
||||||
core/device.cpp \
|
core/device.cpp \
|
||||||
core/dive.cpp \
|
core/dive.cpp \
|
||||||
core/divecomputer.c \
|
core/divecomputer.c \
|
||||||
@ -126,7 +126,6 @@ SOURCES += subsurface-mobile-main.cpp \
|
|||||||
core/subsurface-qt/divelistnotifier.cpp \
|
core/subsurface-qt/divelistnotifier.cpp \
|
||||||
backend-shared/exportfuncs.cpp \
|
backend-shared/exportfuncs.cpp \
|
||||||
backend-shared/plannershared.cpp \
|
backend-shared/plannershared.cpp \
|
||||||
backend-shared/roundrectitem.cpp \
|
|
||||||
stats/statsvariables.cpp \
|
stats/statsvariables.cpp \
|
||||||
stats/statsview.cpp \
|
stats/statsview.cpp \
|
||||||
stats/barseries.cpp \
|
stats/barseries.cpp \
|
||||||
@ -167,7 +166,6 @@ SOURCES += subsurface-mobile-main.cpp \
|
|||||||
qt-models/weightsysteminfomodel.cpp \
|
qt-models/weightsysteminfomodel.cpp \
|
||||||
qt-models/filterconstraintmodel.cpp \
|
qt-models/filterconstraintmodel.cpp \
|
||||||
qt-models/filterpresetmodel.cpp \
|
qt-models/filterpresetmodel.cpp \
|
||||||
profile-widget/qmlprofile.cpp \
|
|
||||||
profile-widget/divecartesianaxis.cpp \
|
profile-widget/divecartesianaxis.cpp \
|
||||||
profile-widget/diveeventitem.cpp \
|
profile-widget/diveeventitem.cpp \
|
||||||
profile-widget/divepercentageitem.cpp \
|
profile-widget/divepercentageitem.cpp \
|
||||||
@ -175,12 +173,14 @@ SOURCES += subsurface-mobile-main.cpp \
|
|||||||
profile-widget/profilescene.cpp \
|
profile-widget/profilescene.cpp \
|
||||||
profile-widget/animationfunctions.cpp \
|
profile-widget/animationfunctions.cpp \
|
||||||
profile-widget/divepixmapcache.cpp \
|
profile-widget/divepixmapcache.cpp \
|
||||||
profile-widget/divepixmapitem.cpp \
|
profile-widget/pictureitem.cpp \
|
||||||
profile-widget/divetooltipitem.cpp \
|
profile-widget/tooltipitem.cpp \
|
||||||
profile-widget/tankitem.cpp \
|
profile-widget/divetextitem.cpp \
|
||||||
profile-widget/divelineitem.cpp \
|
profile-widget/handleitem.cpp \
|
||||||
profile-widget/diverectitem.cpp \
|
profile-widget/profileview.cpp \
|
||||||
profile-widget/divetextitem.cpp
|
profile-widget/ruleritem.cpp \
|
||||||
|
qt-quick/chartitem.cpp \
|
||||||
|
qt-quick/chartview.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
commands/command_base.h \
|
commands/command_base.h \
|
||||||
@ -282,7 +282,6 @@ HEADERS += \
|
|||||||
core/subsurface-qt/divelistnotifier.h \
|
core/subsurface-qt/divelistnotifier.h \
|
||||||
backend-shared/exportfuncs.h \
|
backend-shared/exportfuncs.h \
|
||||||
backend-shared/plannershared.h \
|
backend-shared/plannershared.h \
|
||||||
backend-shared/roundrectitem.h \
|
|
||||||
stats/barseries.h \
|
stats/barseries.h \
|
||||||
stats/boxseries.h \
|
stats/boxseries.h \
|
||||||
stats/chartitem.h \
|
stats/chartitem.h \
|
||||||
@ -327,20 +326,24 @@ HEADERS += \
|
|||||||
qt-models/weightsysteminfomodel.h \
|
qt-models/weightsysteminfomodel.h \
|
||||||
qt-models/filterconstraintmodel.h \
|
qt-models/filterconstraintmodel.h \
|
||||||
qt-models/filterpresetmodel.h \
|
qt-models/filterpresetmodel.h \
|
||||||
profile-widget/qmlprofile.h \
|
|
||||||
profile-widget/divepercentageitem.h \
|
profile-widget/divepercentageitem.h \
|
||||||
profile-widget/diveprofileitem.h \
|
profile-widget/diveprofileitem.h \
|
||||||
profile-widget/profilescene.h \
|
profile-widget/profilescene.h \
|
||||||
profile-widget/diveeventitem.h \
|
profile-widget/diveeventitem.h \
|
||||||
profile-widget/divetooltipitem.h \
|
profile-widget/pictureitem.h \
|
||||||
profile-widget/tankitem.h \
|
profile-widget/tooltipitem.h \
|
||||||
profile-widget/animationfunctions.h \
|
profile-widget/animationfunctions.h \
|
||||||
profile-widget/divecartesianaxis.h \
|
profile-widget/divecartesianaxis.h \
|
||||||
profile-widget/divelineitem.h \
|
|
||||||
profile-widget/divepixmapcache.h \
|
profile-widget/divepixmapcache.h \
|
||||||
profile-widget/divepixmapitem.h \
|
profile-widget/divetextitem.h \
|
||||||
profile-widget/diverectitem.h \
|
profile-widget/handleitem.h \
|
||||||
profile-widget/divetextitem.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 \
|
RESOURCES += mobile-widgets/qml/mobile-resources.qrc \
|
||||||
mobile-widgets/3rdparty/icons.qrc \
|
mobile-widgets/3rdparty/icons.qrc \
|
||||||
|
|||||||
@ -5,8 +5,6 @@ set(BACKEND_SRCS
|
|||||||
exportfuncs.h
|
exportfuncs.h
|
||||||
plannershared.cpp
|
plannershared.cpp
|
||||||
plannershared.h
|
plannershared.h
|
||||||
roundrectitem.cpp
|
|
||||||
roundrectitem.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(subsurface_backend_shared STATIC ${BACKEND_SRCS})
|
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
|
plannernotes.c
|
||||||
pref.h
|
pref.h
|
||||||
pref.c
|
pref.c
|
||||||
profile.c
|
profile.cpp
|
||||||
profile.h
|
profile.h
|
||||||
qt-gui.h
|
qt-gui.h
|
||||||
qt-init.cpp
|
qt-init.cpp
|
||||||
|
|||||||
10
core/event.c
10
core/event.c
@ -1,6 +1,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
#include "event.h"
|
#include "event.h"
|
||||||
#include "eventtype.h"
|
#include "eventtype.h"
|
||||||
|
#include "divecomputer.h"
|
||||||
#include "subsurface-string.h"
|
#include "subsurface-string.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -116,3 +117,12 @@ extern enum event_severity get_event_severity(const struct event *ev)
|
|||||||
return EVENT_SEVERITY_NONE;
|
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>
|
#include <libdivecomputer/parser.h>
|
||||||
|
|
||||||
|
struct divecomputer;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#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 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 bool same_event(const struct event *a, const struct event *b);
|
||||||
extern enum event_severity get_event_severity(const struct event *ev);
|
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. */
|
/* 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);
|
extern const struct event *get_next_event(const struct event *event, const char *name);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
#include "eventtype.h"
|
#include "eventtype.h"
|
||||||
#include "event.h"
|
#include "event.h"
|
||||||
|
#include "divecomputer.h"
|
||||||
#include "gettextfromc.h"
|
#include "gettextfromc.h"
|
||||||
#include "subsurface-string.h"
|
#include "subsurface-string.h"
|
||||||
|
|
||||||
@ -55,10 +56,22 @@ extern "C" void hide_event_type(const struct event *ev)
|
|||||||
it->plot = false;
|
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;
|
e.plot = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void show_event_type(int idx)
|
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;
|
event_types[idx].plot = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" bool any_event_types_hidden()
|
extern std::vector<int> hidden_event_types(const divecomputer *dc)
|
||||||
{
|
|
||||||
return std::any_of(event_types.begin(), event_types.end(),
|
|
||||||
[] (const event_type &e) { return !e.plot; });
|
|
||||||
}
|
|
||||||
|
|
||||||
extern std::vector<int> hidden_event_types()
|
|
||||||
{
|
{
|
||||||
std::vector<int> res;
|
std::vector<int> res;
|
||||||
for (size_t i = 0; i < event_types.size(); ++i) {
|
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);
|
res.push_back(i);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
#ifndef EVENTNAME_H
|
#ifndef EVENTNAME_H
|
||||||
#define EVENTNAME_H
|
#define EVENTNAME_H
|
||||||
|
|
||||||
|
struct divecomputer;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
@ -11,9 +13,8 @@ extern void clear_event_types(void);
|
|||||||
extern void remember_event_type(const struct event *ev);
|
extern void remember_event_type(const struct event *ev);
|
||||||
extern bool is_event_type_hidden(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 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 void show_event_type(int idx);
|
||||||
extern bool any_event_types_hidden();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
@ -22,7 +23,7 @@ extern bool any_event_types_hidden();
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QString>
|
#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(const event *ev);
|
||||||
QString event_type_name(int idx);
|
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.
|
* *pressures = structure for communicating o2 sensor values from and gas pressures to the calling function.
|
||||||
* *mix = structure containing cylinder gas mixture information.
|
* *mix = structure containing cylinder gas mixture information.
|
||||||
* divemode = the dive mode pertaining to this point in the dive profile.
|
* 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)
|
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 {
|
struct gasmix {
|
||||||
fraction_t o2;
|
fraction_t o2;
|
||||||
fraction_t he;
|
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_invalid = { { -1 }, { -1 } };
|
||||||
static const struct gasmix gasmix_air = { { 0 }, { 0 } };
|
static const struct gasmix gasmix_air = { { 0 }, { 0 } };
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
/* gaspressures.c
|
/* gaspressures.c
|
||||||
* ---------------
|
* ---------------
|
||||||
* This file contains the routines to calculate the gas pressures in the cylinders.
|
* 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
|
* 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,
|
* 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:
|
* called by populate_pressure_information(). The calling sequence is as follows:
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/* profile.c */
|
/* profile.cpp */
|
||||||
/* creates all the necessary data for drawing the dive profile
|
/* creates all the necessary data for drawing the dive profile
|
||||||
*/
|
*/
|
||||||
#include "ssrf.h"
|
#include "ssrf.h"
|
||||||
@ -8,6 +8,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "dive.h"
|
#include "dive.h"
|
||||||
#include "divelist.h"
|
#include "divelist.h"
|
||||||
@ -22,7 +23,6 @@
|
|||||||
#include "errorhelper.h"
|
#include "errorhelper.h"
|
||||||
#include "libdivecomputer/parser.h"
|
#include "libdivecomputer/parser.h"
|
||||||
#include "libdivecomputer/version.h"
|
#include "libdivecomputer/version.h"
|
||||||
#include "membuffer.h"
|
|
||||||
#include "qthelper.h"
|
#include "qthelper.h"
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
#define MAX_PROFILE_DECO 7200
|
#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
|
#ifdef DEBUG_PI
|
||||||
/* debugging tool - not normally used */
|
/* debugging tool - not normally used */
|
||||||
@ -57,8 +57,18 @@ static void dump_pi(struct plot_info *pi)
|
|||||||
}
|
}
|
||||||
#endif
|
#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
|
* 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
|
* 30 minutes or 90 ft, just so that small dives show
|
||||||
* up as such unless zoom is enabled.
|
* up as such unless zoom is enabled.
|
||||||
*/
|
*/
|
||||||
|
extern "C"
|
||||||
int get_maxtime(const struct plot_info *pi)
|
int get_maxtime(const struct plot_info *pi)
|
||||||
{
|
{
|
||||||
int seconds = pi->maxtime;
|
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 */
|
/* get the maximum depth to which we want to plot */
|
||||||
|
extern "C"
|
||||||
int get_maxdepth(const struct plot_info *pi)
|
int get_maxdepth(const struct plot_info *pi)
|
||||||
{
|
{
|
||||||
/* 3m to spare */
|
/* 3m to spare */
|
||||||
@ -185,6 +197,7 @@ static void analyze_plot_info(struct plot_info *pi)
|
|||||||
* Some dive computers give cylinder indices, some
|
* Some dive computers give cylinder indices, some
|
||||||
* give just the gas mix.
|
* give just the gas mix.
|
||||||
*/
|
*/
|
||||||
|
extern "C"
|
||||||
int get_cylinder_index(const struct dive *dive, const struct event *ev)
|
int get_cylinder_index(const struct dive *dive, const struct event *ev)
|
||||||
{
|
{
|
||||||
int best;
|
int best;
|
||||||
@ -206,6 +219,7 @@ int get_cylinder_index(const struct dive *dive, const struct event *ev)
|
|||||||
return best < 0 ? 0 : best;
|
return best < 0 ? 0 : best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
struct event *get_next_event_mutable(struct event *event, const char *name)
|
struct event *get_next_event_mutable(struct event *event, const char *name)
|
||||||
{
|
{
|
||||||
if (!name || !*name)
|
if (!name || !*name)
|
||||||
@ -218,6 +232,7 @@ struct event *get_next_event_mutable(struct event *event, const char *name)
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
const struct event *get_next_event(const struct event *event, const char *name)
|
const struct event *get_next_event(const struct event *event, const char *name)
|
||||||
{
|
{
|
||||||
return get_next_event_mutable((struct event *)event, 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;
|
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;
|
int i = 0;
|
||||||
pressure_t setpoint;
|
pressure_t setpoint;
|
||||||
setpoint.mbar = 0;
|
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;
|
entry->bearing = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
void free_plot_info_data(struct plot_info *pi)
|
void free_plot_info_data(struct plot_info *pi)
|
||||||
{
|
{
|
||||||
free(pi->entry);
|
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)
|
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 idx, maxtime, nr, i;
|
||||||
int lastdepth, lasttime, lasttemp = 0;
|
int lastdepth, lasttime, lasttemp = 0;
|
||||||
struct plot_data *plot_data;
|
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)
|
* past "maxtime" in the original sample data)
|
||||||
*/
|
*/
|
||||||
nr = dc->samples + 6 + maxtime / 10 + count_events(dc);
|
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->entry = plot_data;
|
||||||
pi->nr_cylinders = dive->cylinders.nr;
|
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)
|
if (!plot_data)
|
||||||
return;
|
return;
|
||||||
pi->nr = nr;
|
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.
|
* 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;
|
int i, airuse;
|
||||||
double pressuretime;
|
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? */
|
/* 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;
|
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. */
|
/* 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;
|
int i;
|
||||||
bool has_pressure = false;
|
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
|
* an array of gases, the caller passes in scratch memory in the last
|
||||||
* argument.
|
* 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;
|
struct plot_data *entry = pi->entry + idx;
|
||||||
int first, last;
|
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
|
* 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;
|
int i;
|
||||||
|
|
||||||
@ -682,13 +696,14 @@ static void calculate_sac(const struct dive *dive, const struct divecomputer *dc
|
|||||||
{
|
{
|
||||||
struct gasmix gasmix = gasmix_invalid;
|
struct gasmix gasmix = gasmix_invalid;
|
||||||
const struct event *ev = NULL;
|
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
|
/* This might be premature optimization, but let's allocate the gas array for
|
||||||
* the fill_sac function only once an not once per sample */
|
* 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++) {
|
for (int i = 0; i < pi->nr; i++) {
|
||||||
struct plot_data *entry = pi->entry + 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);
|
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)
|
static void populate_secondary_sensor_data(const struct divecomputer *dc, struct plot_info *pi)
|
||||||
{
|
{
|
||||||
int *seen = calloc(pi->nr_cylinders, sizeof(*seen));
|
std::vector<int> seen(pi->nr_cylinders, 0);
|
||||||
for (int idx = 0; idx < pi->nr; ++idx)
|
for (int idx = 0; idx < pi->nr; ++idx) {
|
||||||
for (int c = 0; c < pi->nr_cylinders; ++c)
|
for (int c = 0; c < pi->nr_cylinders; ++c) {
|
||||||
if (get_plot_pressure_data(pi, idx, SENSOR_PR, 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
|
++seen[c]; // Count instances so we can differentiate a real sensor from just start and end pressure
|
||||||
|
}
|
||||||
|
}
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
/* We should try to see if it has interesting pressure data here */
|
/* We should try to see if it has interesting pressure data here */
|
||||||
for (int i = 0; i < dc->samples && idx < pi->nr; i++) {
|
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)
|
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);
|
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! */
|
/* FIXME: The planner uses a dummy one-past-end cylinder for surface air! */
|
||||||
int num_cyl = pi->nr_cylinders + 1;
|
int num_cyl = pi->nr_cylinders + 1;
|
||||||
int *seen = malloc(num_cyl * sizeof(*seen));
|
std::vector<int> seen (num_cyl, 0);
|
||||||
int *first = malloc(num_cyl * sizeof(*first));
|
std::vector<int> first(num_cyl, 0);
|
||||||
int *last = malloc(num_cyl * sizeof(*last));
|
std::vector<int> last(num_cyl, INT_MAX);
|
||||||
const struct divecomputer *secondary;
|
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);
|
prev = explicit_first_cylinder(dive, dc);
|
||||||
seen[prev] = 1;
|
seen[prev] = 1;
|
||||||
|
|
||||||
@ -841,10 +849,6 @@ static void setup_gas_sensor_pressure(const struct dive *dive, const struct dive
|
|||||||
continue;
|
continue;
|
||||||
populate_secondary_sensor_data(secondary, pi);
|
populate_secondary_sensor_data(secondary, pi);
|
||||||
} while ((secondary = secondary->next) != NULL);
|
} while ((secondary = secondary->next) != NULL);
|
||||||
|
|
||||||
free(seen);
|
|
||||||
free(first);
|
|
||||||
free(last);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* calculate DECO STOP / TTS / NDL */
|
/* 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 time_stepsize = 60;
|
||||||
const int deco_stepsize = M_OR_FT(3, 10);
|
const int deco_stepsize = M_OR_FT(3, 10);
|
||||||
/* at what depth is the current deco-step? */
|
/* 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),
|
tissue_tolerance_calc(ds, dive, depth_to_bar(entry->depth, dive), in_planner),
|
||||||
surface_pressure, dive, 1), deco_stepsize);
|
surface_pressure, dive, 1), deco_stepsize);
|
||||||
int ascent_depth = entry->depth;
|
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) {
|
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),
|
add_segment(ds, depth_to_bar(ascent_depth, dive),
|
||||||
gasmix, ascent_s_per_step, entry->o2pressure.mbar, divemode, prefs.decosac, in_planner);
|
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);
|
surface_pressure, dive, 1), deco_stepsize);
|
||||||
}
|
}
|
||||||
ascent_depth = next_stop;
|
ascent_depth = next_stop;
|
||||||
@ -1016,7 +1020,7 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_
|
|||||||
vpmb_start_gradient(ds);
|
vpmb_start_gradient(ds);
|
||||||
/* For CVA calculations, deco time = dive time remaining is a good guess,
|
/* For CVA calculations, deco time = dive time remaining is a good guess,
|
||||||
but we want to over-estimate deco_time for the first iteration so it
|
but we want to over-estimate deco_time for the first iteration so it
|
||||||
converges correctly, so add 30min*/
|
converges correctly, so add 30min */
|
||||||
if (!in_planner)
|
if (!in_planner)
|
||||||
ds->deco_time = pi->maxtime - t1 + 1800;
|
ds->deco_time = pi->maxtime - t1 + 1800;
|
||||||
vpmb_next_gradient(ds, ds->deco_time, surface_pressure / 1000.0, in_planner);
|
vpmb_next_gradient(ds, ds->deco_time, surface_pressure / 1000.0, in_planner);
|
||||||
@ -1058,7 +1062,7 @@ static void calculate_deco_information(struct deco_state *ds, const struct deco_
|
|||||||
entry->depth < max_ceiling - 100 && entry->sec > 0) {
|
entry->depth < max_ceiling - 100 && entry->sec > 0) {
|
||||||
struct dive *non_const_dive = (struct dive *)dive; // cast away const!
|
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,
|
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;
|
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
|
* 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
|
* comes typically 10-60s after the end of the bottom time, so add 20s to the calculated
|
||||||
* deco time. */
|
* 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);
|
vpmb_next_gradient(ds, ds->deco_time, surface_pressure / 1000.0, in_planner);
|
||||||
final_tts = 0;
|
final_tts = 0;
|
||||||
last_ndl_tts_calc_time = 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
|
* Initialize a plot_info structure to all-zeroes
|
||||||
*/
|
*/
|
||||||
|
extern "C"
|
||||||
void init_plot_info(struct plot_info *pi)
|
void init_plot_info(struct plot_info *pi)
|
||||||
{
|
{
|
||||||
memset(pi, 0, sizeof(*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
|
* The old data will be freed. Before the first call, the plot
|
||||||
* info must be initialized with init_plot_info().
|
* 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)
|
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;
|
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);
|
calculate_max_limits_new(dive, dc, pi, in_planner);
|
||||||
get_dive_gas(dive, &o2, &he, &o2max);
|
get_dive_gas(dive, &o2, &he, &o2max);
|
||||||
if (dc->divemode == FREEDIVE) {
|
if (dc->divemode == FREEDIVE) {
|
||||||
pi->dive_type = FREEDIVING;
|
pi->dive_type = plot_info::FREEDIVING;
|
||||||
} else if (he > 0) {
|
} else if (he > 0) {
|
||||||
pi->dive_type = TRIMIX;
|
pi->dive_type = plot_info::TRIMIX;
|
||||||
} else {
|
} else {
|
||||||
if (o2)
|
if (o2)
|
||||||
pi->dive_type = NITROX;
|
pi->dive_type = plot_info::NITROX;
|
||||||
else
|
else
|
||||||
pi->dive_type = AIR;
|
pi->dive_type = plot_info::AIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
populate_plot_entries(dive, dc, pi);
|
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);
|
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;
|
int pressurevalue, mod, ead, end, eadd;
|
||||||
const char *depth_unit, *pressure_unit, *temp_unit, *vertical_speed_unit;
|
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;
|
int decimals, cyl;
|
||||||
const char *unit;
|
const char *unit;
|
||||||
const struct plot_data *entry = pi->entry + idx;
|
const struct plot_data *entry = pi->entry + idx;
|
||||||
|
std::vector<QString> res;
|
||||||
|
|
||||||
depthvalue = get_depth_units(entry->depth, NULL, &depth_unit);
|
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++) {
|
for (cyl = 0; cyl < pi->nr_cylinders; cyl++) {
|
||||||
int mbar = get_plot_pressure(pi, idx, cyl);
|
int mbar = get_plot_pressure(pi, idx, cyl);
|
||||||
if (!mbar)
|
if (!mbar)
|
||||||
continue;
|
continue;
|
||||||
struct gasmix mix = get_cylinder(d, cyl)->gasmix;
|
struct gasmix mix = get_cylinder(d, cyl)->gasmix;
|
||||||
pressurevalue = get_pressure_units(mbar, &pressure_unit);
|
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) {
|
if (entry->temperature) {
|
||||||
tempvalue = get_temp_units(entry->temperature, &temp_unit);
|
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);
|
speedvalue = get_vertical_speed_units(abs(entry->speed), NULL, &vertical_speed_unit);
|
||||||
/* Ascending speeds are positive, descending are negative */
|
/* Ascending speeds are positive, descending are negative */
|
||||||
if (entry->speed > 0)
|
if (entry->speed > 0)
|
||||||
speedvalue *= -1;
|
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);
|
sacvalue = get_volume_units(entry->sac, &decimals, &unit);
|
||||||
if (entry->sac && prefs.show_sac)
|
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)
|
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) {
|
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)
|
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)
|
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)
|
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) {
|
if (prefs.mod && entry->mod > 0) {
|
||||||
mod = lrint(get_depth_units(entry->mod, NULL, &depth_unit));
|
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));
|
eadd = lrint(get_depth_units(entry->eadd, NULL, &depth_unit));
|
||||||
|
|
||||||
if (prefs.ead) {
|
if (prefs.ead) {
|
||||||
switch (pi->dive_type) {
|
switch (pi->dive_type) {
|
||||||
case NITROX:
|
case plot_info::NITROX:
|
||||||
if (entry->ead > 0) {
|
if (entry->ead > 0) {
|
||||||
ead = lrint(get_depth_units(entry->ead, NULL, &depth_unit));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case TRIMIX:
|
case plot_info::TRIMIX:
|
||||||
if (entry->end > 0) {
|
if (entry->end > 0) {
|
||||||
end = lrint(get_depth_units(entry->end, NULL, &depth_unit));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case AIR:
|
case plot_info::AIR:
|
||||||
if (entry->density > 0) {
|
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 */
|
/* nothing */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1440,151 +1450,143 @@ static void plot_string(const struct dive *d, const struct plot_info *pi, int id
|
|||||||
if (entry->ndl > 0) {
|
if (entry->ndl > 0) {
|
||||||
/* this is a safety stop as we still have ndl */
|
/* this is a safety stop as we still have ndl */
|
||||||
if (entry->stoptime)
|
if (entry->stoptime)
|
||||||
put_format_loc(b, translate("gettextFromC", "Safety stop: %umin @ %.0f%s\n"), DIV_UP(entry->stoptime, 60),
|
res.push_back(qasprintf_loc(translate("gettextFromC", "Safety stop: %umin @ %.0f%s"), div_up(entry->stoptime, 60),
|
||||||
depthvalue, depth_unit);
|
depthvalue, depth_unit));
|
||||||
else
|
else
|
||||||
put_format_loc(b, translate("gettextFromC", "Safety stop: unknown time @ %.0f%s\n"),
|
res.push_back(qasprintf_loc(translate("gettextFromC", "Safety stop: unknown time @ %.0f%s"),
|
||||||
depthvalue, depth_unit);
|
depthvalue, depth_unit));
|
||||||
} else {
|
} else {
|
||||||
/* actual deco stop */
|
/* actual deco stop */
|
||||||
if (entry->stoptime)
|
if (entry->stoptime)
|
||||||
put_format_loc(b, translate("gettextFromC", "Deco: %umin @ %.0f%s\n"), DIV_UP(entry->stoptime, 60),
|
res.push_back(qasprintf_loc(translate("gettextFromC", "Deco: %umin @ %.0f%s"), div_up(entry->stoptime, 60),
|
||||||
depthvalue, depth_unit);
|
depthvalue, depth_unit));
|
||||||
else
|
else
|
||||||
put_format_loc(b, translate("gettextFromC", "Deco: unknown time @ %.0f%s\n"),
|
res.push_back(qasprintf_loc(translate("gettextFromC", "Deco: unknown time @ %.0f%s"),
|
||||||
depthvalue, depth_unit);
|
depthvalue, depth_unit));
|
||||||
}
|
}
|
||||||
} else if (entry->in_deco) {
|
} 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) {
|
} 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)
|
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) {
|
if (entry->stopdepth_calc && entry->stoptime_calc) {
|
||||||
depthvalue = get_depth_units(entry->stopdepth_calc, NULL, &depth_unit);
|
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),
|
res.push_back(qasprintf_loc(translate("gettextFromC", "Deco: %umin @ %.0f%s (calc)"), div_up(entry->stoptime_calc, 60),
|
||||||
depthvalue, depth_unit);
|
depthvalue, depth_unit));
|
||||||
} else if (entry->in_deco_calc) {
|
} else if (entry->in_deco_calc) {
|
||||||
/* This means that we have no NDL left,
|
/* This means that we have no NDL left,
|
||||||
* and we have no deco stop,
|
* and we have no deco stop,
|
||||||
* so if we just accend to the surface slowly
|
* so if we just accend to the surface slowly
|
||||||
* (ascent_mm_per_step / ascent_s_per_step)
|
* (ascent_mm_per_step / ascent_s_per_step)
|
||||||
* everything will be ok. */
|
* 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) {
|
} else if (prefs.calcndltts && entry->ndl_calc != 0) {
|
||||||
if(entry->ndl_calc < MAX_PROFILE_DECO)
|
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
|
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) {
|
||||||
if (entry->tts_calc < MAX_PROFILE_DECO)
|
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
|
else
|
||||||
put_string(b, translate("gettextFromC", "TTS: >2h (calc)\n"));
|
res.push_back(translate("gettextFromC", "TTS: >2h (calc)"));
|
||||||
}
|
}
|
||||||
if (entry->rbt)
|
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 (prefs.decoinfo) {
|
||||||
if (entry->current_gf > 0.0)
|
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)
|
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) {
|
if (entry->ceiling) {
|
||||||
depthvalue = get_depth_units(entry->ceiling, NULL, &depth_unit);
|
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) {
|
if (prefs.calcalltissues) {
|
||||||
int k;
|
int k;
|
||||||
for (k = 0; k < 16; k++) {
|
for (k = 0; k < 16; k++) {
|
||||||
if (entry->ceilings[k]) {
|
if (entry->ceilings[k]) {
|
||||||
depthvalue = get_depth_units(entry->ceilings[k], NULL, &depth_unit);
|
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)
|
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)
|
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)
|
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) {
|
if (entry->running_sum) {
|
||||||
depthvalue = get_depth_units(entry->running_sum / entry->sec, NULL, &depth_unit);
|
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 */
|
/* The two first and the two last plot entries do not have useful data */
|
||||||
if (pi->nr <= 4)
|
if (pi->nr <= 4)
|
||||||
return 0;
|
return { 0, {} };
|
||||||
for (i = 2; i < pi->nr - 3; i++) {
|
|
||||||
if (pi->entry[i].sec >= time)
|
// binary search for sample index
|
||||||
break;
|
auto it = std::lower_bound(pi->entry + 2, pi->entry + pi->nr - 3, time,
|
||||||
}
|
[] (const plot_data &d, int time)
|
||||||
plot_string(d, pi, i, mb);
|
{ return d.sec < time; });
|
||||||
return i;
|
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 */
|
static QString space = QStringLiteral(" ");
|
||||||
void compare_samples(const struct dive *d, const struct plot_info *pi, int idx1, int idx2, char *buf, int bufsize, 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;
|
|
||||||
|
|
||||||
|
/* 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)
|
||||||
|
{
|
||||||
|
const char *depth_unit, *pressure_unit, *vertical_speed_unit;
|
||||||
double depthvalue, speedvalue;
|
double depthvalue, speedvalue;
|
||||||
|
|
||||||
if (bufsize > 0)
|
std::vector<QString> res;
|
||||||
buf[0] = '\0';
|
if (idx1 < 0 || idx2 < 0)
|
||||||
if (idx1 < 0 || idx2 < 0) {
|
return res;
|
||||||
free(buf2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pi->entry[idx1].sec > pi->entry[idx2].sec) {
|
if (pi->entry[idx1].sec > pi->entry[idx2].sec) {
|
||||||
int tmp = idx2;
|
int tmp = idx2;
|
||||||
idx2 = idx1;
|
idx2 = idx1;
|
||||||
idx1 = tmp;
|
idx1 = tmp;
|
||||||
} else if (pi->entry[idx1].sec == pi->entry[idx2].sec) {
|
} else if (pi->entry[idx1].sec == pi->entry[idx2].sec) {
|
||||||
free(buf2);
|
return res;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
start = pi->entry + idx1;
|
struct plot_data *start = pi->entry + idx1;
|
||||||
stop = pi->entry + idx2;
|
struct plot_data *stop = pi->entry + idx2;
|
||||||
|
|
||||||
avg_speed = 0;
|
int avg_speed = 0;
|
||||||
max_asc_speed = 0;
|
int max_asc_speed = 0;
|
||||||
max_desc_speed = 0;
|
int max_desc_speed = 0;
|
||||||
|
|
||||||
delta_depth = abs(start->depth - stop->depth);
|
int delta_depth = abs(start->depth - stop->depth);
|
||||||
delta_time = abs(start->sec - stop->sec);
|
int delta_time = abs(start->sec - stop->sec);
|
||||||
avg_depth = 0;
|
int avg_depth = 0;
|
||||||
max_depth = 0;
|
int max_depth = 0;
|
||||||
min_depth = INT_MAX;
|
int min_depth = INT_MAX;
|
||||||
|
|
||||||
last_sec = start->sec;
|
int last_sec = start->sec;
|
||||||
|
|
||||||
volume_t cylinder_volume = { .mliter = 0, };
|
volume_t cylinder_volume = { .mliter = 0, };
|
||||||
int *start_pressures = calloc((size_t)pi->nr_cylinders, sizeof(int));
|
std::vector<int> start_pressures(pi->nr_cylinders, 0);
|
||||||
int *last_pressures = calloc((size_t)pi->nr_cylinders, sizeof(int));
|
std::vector<int> last_pressures(pi->nr_cylinders, 0);
|
||||||
int *bar_used = calloc((size_t)pi->nr_cylinders, sizeof(int));
|
std::vector<int> bar_used(pi->nr_cylinders, 0);
|
||||||
int *volumes_used = calloc((size_t)pi->nr_cylinders, sizeof(int));
|
std::vector<int> volumes_used(pi->nr_cylinders, 0);
|
||||||
bool *cylinder_is_used = calloc((size_t)pi->nr_cylinders, sizeof(bool));
|
std::vector<char> cylinder_is_used(pi->nr_cylinders, false);
|
||||||
|
|
||||||
data = start;
|
struct plot_data *data = start;
|
||||||
for (int i = idx1; i < idx2; ++i) {
|
for (int i = idx1; i < idx2; ++i) {
|
||||||
data = pi->entry + i;
|
data = pi->entry + i;
|
||||||
if (sum)
|
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)
|
if (data->depth > max_depth)
|
||||||
max_depth = data->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);
|
int next_pressure = get_plot_pressure(pi, i, cylinder_index);
|
||||||
if (next_pressure && !start_pressures[cylinder_index])
|
if (next_pressure && !start_pressures[cylinder_index])
|
||||||
start_pressures[cylinder_index] = next_pressure;
|
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;
|
last_sec = data->sec;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(start_pressures);
|
|
||||||
free(last_pressures);
|
|
||||||
|
|
||||||
avg_depth /= stop->sec - start->sec;
|
avg_depth /= stop->sec - start->sec;
|
||||||
avg_speed /= 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);
|
QString l = qasprintf_loc(translate("gettextFromC", "ΔT:%d:%02dmin"), delta_time / 60, delta_time % 60);
|
||||||
memcpy(buf2, buf, bufsize);
|
|
||||||
|
|
||||||
depthvalue = get_depth_units(delta_depth, NULL, &depth_unit);
|
depthvalue = get_depth_units(delta_depth, NULL, &depth_unit);
|
||||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s ΔD:%.1f%s"), buf2, depthvalue, depth_unit);
|
l += space + qasprintf_loc(translate("gettextFromC", "ΔD:%.1f%s"), depthvalue, depth_unit);
|
||||||
memcpy(buf2, buf, bufsize);
|
|
||||||
|
|
||||||
depthvalue = get_depth_units(min_depth, NULL, &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);
|
l += space + qasprintf_loc(translate("gettextFromC", "↓D:%.1f%s"), depthvalue, depth_unit);
|
||||||
memcpy(buf2, buf, bufsize);
|
|
||||||
|
|
||||||
depthvalue = get_depth_units(max_depth, NULL, &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);
|
l += space + qasprintf_loc(translate("gettextFromC", "↑D:%.1f%s"), depthvalue, depth_unit);
|
||||||
memcpy(buf2, buf, bufsize);
|
|
||||||
|
|
||||||
depthvalue = get_depth_units(avg_depth, NULL, &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);
|
l += space + qasprintf_loc(translate("gettextFromC", "øD:%.1f%s"), depthvalue, depth_unit);
|
||||||
memcpy(buf2, buf, bufsize);
|
res.push_back(l);
|
||||||
|
|
||||||
speedvalue = get_vertical_speed_units(abs(max_desc_speed), NULL, &vertical_speed_unit);
|
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);
|
l = qasprintf_loc(translate("gettextFromC", "↓V:%.2f%s"), speedvalue, vertical_speed_unit);
|
||||||
memcpy(buf2, buf, bufsize);
|
|
||||||
|
|
||||||
speedvalue = get_vertical_speed_units(abs(max_asc_speed), NULL, &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);
|
l += space + qasprintf_loc(translate("gettextFromC", "↑V:%.2f%s"), speedvalue, vertical_speed_unit);
|
||||||
memcpy(buf2, buf, bufsize);
|
|
||||||
|
|
||||||
speedvalue = get_vertical_speed_units(abs(avg_speed), NULL, &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_bar_used = 0;
|
||||||
int total_volume_used = 0;
|
int total_volume_used = 0;
|
||||||
bool cylindersizes_are_identical = true;
|
bool cylindersizes_are_identical = true;
|
||||||
bool sac_is_determinable = 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]) {
|
if (cylinder_is_used[cylinder_index]) {
|
||||||
total_bar_used += bar_used[cylinder_index];
|
total_bar_used += bar_used[cylinder_index];
|
||||||
total_volume_used += volumes_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;
|
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
|
// 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) {
|
if (cylindersizes_are_identical && total_bar_used) {
|
||||||
pressurevalue = get_pressure_units(total_bar_used, &pressure_unit);
|
int pressurevalue = get_pressure_units(total_bar_used, &pressure_unit);
|
||||||
memcpy(buf2, buf, bufsize);
|
l += space + qasprintf_loc(translate("gettextFromC", "ΔP:%d%s"), pressurevalue, pressure_unit);
|
||||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s ΔP:%d%s"), buf2, pressurevalue, pressure_unit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can't calculate the SAC if the volume for some of the cylinders used is unknown
|
// We can't calculate the SAC if the volume for some of the cylinders used is unknown
|
||||||
if (sac_is_determinable && total_volume_used) {
|
if (sac_is_determinable && total_volume_used) {
|
||||||
double volume_value;
|
|
||||||
int volume_precision;
|
int volume_precision;
|
||||||
const char *volume_unit;
|
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 */
|
/* milliliters per minute */
|
||||||
int sac = lrint(total_volume_used / atm * 60 / delta_time);
|
int sac = lrint(total_volume_used / atm * 60 / delta_time);
|
||||||
memcpy(buf2, buf, bufsize);
|
double volume_value = get_volume_units(sac, &volume_precision, &volume_unit);
|
||||||
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);
|
||||||
snprintf_loc(buf, bufsize, translate("gettextFromC", "%s SAC:%.*f%s/min"), buf2, 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
|
#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);
|
extern void init_plot_info(struct plot_info *pi);
|
||||||
/* when planner_dc is non-null, this is called in planner mode. */
|
/* 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 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);
|
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
|
#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
|
||||||
#endif // PROFILE_H
|
#endif // PROFILE_H
|
||||||
|
|||||||
@ -22,7 +22,6 @@
|
|||||||
#include "desktop-widgets/divelogexportdialog.h"
|
#include "desktop-widgets/divelogexportdialog.h"
|
||||||
#include "desktop-widgets/diveshareexportdialog.h"
|
#include "desktop-widgets/diveshareexportdialog.h"
|
||||||
#include "desktop-widgets/subsurfacewebservices.h"
|
#include "desktop-widgets/subsurfacewebservices.h"
|
||||||
#include "profile-widget/profilewidget2.h"
|
|
||||||
|
|
||||||
// Retrieves the current unit settings defined in the Subsurface preferences.
|
// Retrieves the current unit settings defined in the Subsurface preferences.
|
||||||
#define GET_UNIT(name, field, f, t) \
|
#define GET_UNIT(name, field, f, t) \
|
||||||
|
|||||||
@ -56,7 +56,6 @@
|
|||||||
#include "commands/command.h"
|
#include "commands/command.h"
|
||||||
|
|
||||||
#include "profilewidget.h"
|
#include "profilewidget.h"
|
||||||
#include "profile-widget/profilewidget2.h"
|
|
||||||
|
|
||||||
#ifndef NO_PRINTING
|
#ifndef NO_PRINTING
|
||||||
#include "desktop-widgets/printdialog.h"
|
#include "desktop-widgets/printdialog.h"
|
||||||
@ -671,7 +670,7 @@ void MainWindow::on_actionReplanDive_triggered()
|
|||||||
|
|
||||||
disableShortcuts(true);
|
disableShortcuts(true);
|
||||||
plannerWidgets->prepareReplanDive(current_dive);
|
plannerWidgets->prepareReplanDive(current_dive);
|
||||||
profile->setPlanState(plannerWidgets->getDive(), profile->dc);
|
profile->plotDive(plannerWidgets->getDive(), profile->dc, true);
|
||||||
plannerWidgets->replanDive(profile->dc);
|
plannerWidgets->replanDive(profile->dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -685,7 +684,7 @@ void MainWindow::on_actionDivePlanner_triggered()
|
|||||||
|
|
||||||
disableShortcuts(true);
|
disableShortcuts(true);
|
||||||
plannerWidgets->preparePlanDive(current_dive);
|
plannerWidgets->preparePlanDive(current_dive);
|
||||||
profile->setPlanState(plannerWidgets->getDive(), 0);
|
profile->plotDive(plannerWidgets->getDive(), 0, true);
|
||||||
plannerWidgets->planDive();
|
plannerWidgets->planDive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
#include "qt-models/cylindermodel.h"
|
#include "qt-models/cylindermodel.h"
|
||||||
#include "qt-models/models.h"
|
#include "qt-models/models.h"
|
||||||
#include "desktop-widgets/starwidget.h"
|
#include "desktop-widgets/starwidget.h"
|
||||||
#include "profile-widget/profilewidget2.h"
|
|
||||||
#include "qt-models/tankinfomodel.h"
|
#include "qt-models/tankinfomodel.h"
|
||||||
#include "qt-models/weightsysteminfomodel.h"
|
#include "qt-models/weightsysteminfomodel.h"
|
||||||
#include "qt-models/weightmodel.h"
|
#include "qt-models/weightmodel.h"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
#include "profilewidget.h"
|
#include "profilewidget.h"
|
||||||
#include "profile-widget/profilewidget2.h"
|
#include "profile-widget/profileview.h"
|
||||||
#include "commands/command.h"
|
#include "commands/command.h"
|
||||||
#include "core/color.h"
|
#include "core/color.h"
|
||||||
#include "core/selection.h"
|
#include "core/selection.h"
|
||||||
@ -10,10 +10,14 @@
|
|||||||
#include "core/subsurface-string.h"
|
#include "core/subsurface-string.h"
|
||||||
#include "qt-models/diveplannermodel.h"
|
#include "qt-models/diveplannermodel.h"
|
||||||
|
|
||||||
#include <QToolBar>
|
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QStackedWidget>
|
|
||||||
#include <QLabel>
|
#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
|
// A resizing display of the Subsurface logo when no dive is shown
|
||||||
class EmptyView : public QLabel {
|
class EmptyView : public QLabel {
|
||||||
@ -52,6 +56,58 @@ void EmptyView::resizeEvent(QResizeEvent *)
|
|||||||
update();
|
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)
|
ProfileWidget::ProfileWidget() : d(nullptr), dc(0), originalDive(nullptr), placingCommand(false)
|
||||||
{
|
{
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
@ -72,7 +128,10 @@ ProfileWidget::ProfileWidget() : d(nullptr), dc(0), originalDive(nullptr), placi
|
|||||||
|
|
||||||
emptyView.reset(new EmptyView);
|
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);
|
QToolBar *toolBar = new QToolBar(this);
|
||||||
for (QAction *a: toolbarActions)
|
for (QAction *a: toolbarActions)
|
||||||
toolBar->addAction(a);
|
toolBar->addAction(a);
|
||||||
@ -81,7 +140,7 @@ ProfileWidget::ProfileWidget() : d(nullptr), dc(0), originalDive(nullptr), placi
|
|||||||
|
|
||||||
stack = new QStackedWidget(this);
|
stack = new QStackedWidget(this);
|
||||||
stack->addWidget(emptyView.get());
|
stack->addWidget(emptyView.get());
|
||||||
stack->addWidget(view.get());
|
stack->addWidget(viewWidget.get());
|
||||||
|
|
||||||
QHBoxLayout *layout = new QHBoxLayout(this);
|
QHBoxLayout *layout = new QHBoxLayout(this);
|
||||||
layout->setSpacing(0);
|
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(ui.profPO2, &QAction::triggered, pp_gas, &qPrefPartialPressureGas::set_po2);
|
||||||
|
|
||||||
connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &ProfileWidget::divesChanged);
|
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.profCalcAllTissues->setChecked(qPrefTechnicalDetails::calcalltissues());
|
||||||
ui.profCalcCeiling->setChecked(qPrefTechnicalDetails::calcceiling());
|
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)
|
void ProfileWidget::setEnabledToolbar(bool enabled)
|
||||||
{
|
{
|
||||||
for (QAction *b: toolbarActions)
|
for (QAction *b: toolbarActions)
|
||||||
@ -190,7 +272,7 @@ void ProfileWidget::plotCurrentDive()
|
|||||||
plotDive(d, dc);
|
plotDive(d, dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileWidget::plotDive(dive *dIn, int dcIn)
|
void ProfileWidget::plotDive(dive *dIn, int dcIn, bool planMode)
|
||||||
{
|
{
|
||||||
d = dIn;
|
d = dIn;
|
||||||
|
|
||||||
@ -214,14 +296,16 @@ void ProfileWidget::plotDive(dive *dIn, int dcIn)
|
|||||||
editDive();
|
editDive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto view = getView();
|
||||||
setEnabledToolbar(d != nullptr);
|
setEnabledToolbar(d != nullptr);
|
||||||
if (editedDive) {
|
if (editedDive) {
|
||||||
view->plotDive(editedDive.get(), editedDc);
|
view->plotDive(editedDive.get(), editedDc, ProfileView::RenderFlags::EditMode);
|
||||||
setDive(editedDive.get());
|
setDive(editedDive.get());
|
||||||
} else if (d) {
|
} 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->resetZoom(); // when switching dive, reset the zoomLevel
|
||||||
view->plotDive(d, dc);
|
view->plotDive(d, dc, flags);
|
||||||
setDive(d);
|
setDive(d);
|
||||||
} else {
|
} else {
|
||||||
view->clear();
|
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)
|
if (!d || !dives.contains(d) || !(field.duration || field.depth) || placingCommand)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// If were editing the current dive and not currently
|
// If we're editing the current dive and not currently
|
||||||
// placing command, we have to update the edited dive.
|
// placing a command, we have to update the edited dive.
|
||||||
if (editedDive) {
|
if (editedDive) {
|
||||||
copy_dive(d, editedDive.get());
|
copy_dive(d, editedDive.get());
|
||||||
// TODO: Holy moly that function sends too many signals. Fix it!
|
// 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();
|
plotCurrentDive();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileWidget::setPlanState(const struct dive *d, int dc)
|
|
||||||
{
|
|
||||||
exitEditMode();
|
|
||||||
view->setPlanState(d, dc);
|
|
||||||
setDive(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProfileWidget::unsetProfHR()
|
void ProfileWidget::unsetProfHR()
|
||||||
{
|
{
|
||||||
ui.profHR->setChecked(false);
|
ui.profHR->setChecked(false);
|
||||||
@ -299,9 +376,13 @@ void ProfileWidget::editDive()
|
|||||||
editedDc = dc;
|
editedDc = dc;
|
||||||
copy_dive(d, editedDive.get()); // Work on a copy of the dive
|
copy_dive(d, editedDive.get()); // Work on a copy of the dive
|
||||||
originalDive = d;
|
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()->setPlanMode(DivePlannerPointsModel::ADD);
|
||||||
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), editedDc);
|
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), editedDc);
|
||||||
view->setEditState(editedDive.get(), editedDc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileWidget::exitEditMode()
|
void ProfileWidget::exitEditMode()
|
||||||
@ -309,7 +390,7 @@ void ProfileWidget::exitEditMode()
|
|||||||
if (!editedDive)
|
if (!editedDive)
|
||||||
return;
|
return;
|
||||||
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING);
|
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();
|
editedDive.reset();
|
||||||
originalDive = nullptr;
|
originalDive = nullptr;
|
||||||
}
|
}
|
||||||
@ -359,3 +440,12 @@ void ProfileWidget::stopMoved(int count)
|
|||||||
Setter s(placingCommand, true);
|
Setter s(placingCommand, true);
|
||||||
Command::editProfile(editedDive.get(), editedDc, Command::EditProfileType::MOVE, count);
|
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>
|
#include <vector>
|
||||||
|
|
||||||
struct dive;
|
struct dive;
|
||||||
class ProfileWidget2;
|
class ProfileView;
|
||||||
class EmptyView;
|
class EmptyView;
|
||||||
|
class QQuickWidget;
|
||||||
class QStackedWidget;
|
class QStackedWidget;
|
||||||
|
|
||||||
extern "C" void free_dive(struct dive *);
|
extern "C" void free_dive(struct dive *);
|
||||||
@ -22,13 +23,12 @@ class ProfileWidget : public QWidget {
|
|||||||
public:
|
public:
|
||||||
ProfileWidget();
|
ProfileWidget();
|
||||||
~ProfileWidget();
|
~ProfileWidget();
|
||||||
std::unique_ptr<ProfileWidget2> view;
|
void plotDive(struct dive *d, int dc, bool planMode = false); // Attempt to keep DC number id dc < 0
|
||||||
void plotDive(struct dive *d, int dc); // Attempt to keep DC number id dc < 0
|
|
||||||
void plotCurrentDive();
|
void plotCurrentDive();
|
||||||
void setPlanState(const struct dive *d, int dc);
|
|
||||||
void setEnabledToolbar(bool enabled);
|
void setEnabledToolbar(bool enabled);
|
||||||
void nextDC();
|
void nextDC();
|
||||||
void prevDC();
|
void prevDC();
|
||||||
|
void dropPicture(const QString &filename, QPoint p);
|
||||||
dive *d;
|
dive *d;
|
||||||
int dc;
|
int dc;
|
||||||
private
|
private
|
||||||
@ -40,6 +40,8 @@ slots:
|
|||||||
void stopRemoved(int count);
|
void stopRemoved(int count);
|
||||||
void stopMoved(int count);
|
void stopMoved(int count);
|
||||||
private:
|
private:
|
||||||
|
ProfileView *getView();
|
||||||
|
std::unique_ptr<QQuickWidget> viewWidget;
|
||||||
std::unique_ptr<EmptyView> emptyView;
|
std::unique_ptr<EmptyView> emptyView;
|
||||||
std::vector<QAction *> toolbarActions;
|
std::vector<QAction *> toolbarActions;
|
||||||
Ui::ProfileWidget ui;
|
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 "libdivecomputer/parser.h"
|
||||||
#include "desktop-widgets/divelistview.h"
|
#include "desktop-widgets/divelistview.h"
|
||||||
#include "core/selection.h"
|
#include "core/selection.h"
|
||||||
#include "profile-widget/profilewidget2.h"
|
|
||||||
#include "commands/command.h"
|
#include "commands/command.h"
|
||||||
#include "core/metadata.h"
|
#include "core/metadata.h"
|
||||||
#include "core/tag.h"
|
#include "core/tag.h"
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
#include "TabDiveInformation.h"
|
#include "TabDiveInformation.h"
|
||||||
#include "maintab.h"
|
#include "maintab.h"
|
||||||
#include "ui_TabDiveInformation.h"
|
#include "ui_TabDiveInformation.h"
|
||||||
#include "profile-widget/profilewidget2.h"
|
|
||||||
#include "../tagwidget.h"
|
#include "../tagwidget.h"
|
||||||
#include "commands/command.h"
|
#include "commands/command.h"
|
||||||
#include "core/subsurface-string.h"
|
#include "core/subsurface-string.h"
|
||||||
|
|||||||
@ -231,13 +231,13 @@ Item {
|
|||||||
Layout.columnSpan: 3
|
Layout.columnSpan: 3
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
QMLProfile {
|
ProfileView {
|
||||||
id: qmlProfile
|
id: qmlProfile
|
||||||
visible: !noDive
|
visible: !noDive
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
property real lastScale: 1.0 // final scale at the end of previous pinch
|
|
||||||
diveId: detailsView.myId
|
diveId: detailsView.myId
|
||||||
|
property real dpr: 0.8 // TODO: make this dynamic
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
@ -253,36 +253,20 @@ Item {
|
|||||||
// before realizing that this is actually a pinch/zoom. So let's reset this
|
// before realizing that this is actually a pinch/zoom. So let's reset this
|
||||||
// just in case
|
// just in case
|
||||||
qmlProfile.opacity = 1.0
|
qmlProfile.opacity = 1.0
|
||||||
if (manager.verboseEnabled)
|
qmlProfile.pinchStart()
|
||||||
manager.appendTextToLog("pinch started w/ previousScale " + qmlProfile.lastScale)
|
|
||||||
}
|
}
|
||||||
onPinchUpdated: {
|
onPinchUpdated: {
|
||||||
if (pinch.scale * qmlProfile.lastScale < 1.0)
|
qmlProfile.pinch(pinch.scale)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
// we want to pan the profile if we are zoomed in, but we want to immediately
|
// 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
|
// 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
|
// 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
|
// this indicates that we are actually dragging
|
||||||
property bool dragging: false
|
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
|
// if the profile is not scaled in, don't start panning
|
||||||
// but if the profile is scaled in, then start almost immediately
|
// 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
|
// pass events through to the parent and eventually into the ListView
|
||||||
propagateComposedEvents: true
|
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
|
anchors.fill: parent
|
||||||
drag.target: qmlProfile
|
drag.target: qmlProfile
|
||||||
drag.axis: Drag.XAndYAxis
|
drag.axis: Drag.XAndYAxis
|
||||||
@ -311,10 +285,7 @@ Item {
|
|||||||
}
|
}
|
||||||
onPressAndHold: {
|
onPressAndHold: {
|
||||||
dragging = true;
|
dragging = true;
|
||||||
oldXOffset = qmlProfile.xOffset
|
qmlProfile.panStart(mouse.x, mouse.y)
|
||||||
oldYOffset = qmlProfile.yOffset
|
|
||||||
initialX = mouse.x
|
|
||||||
initialY = mouse.y
|
|
||||||
if (manager.verboseEnabled)
|
if (manager.verboseEnabled)
|
||||||
manager.appendTextToLog("press and hold at mouse" + Math.round(10 * mouse.x) / 10 + " / " + Math.round(10 * mouse.y) / 10)
|
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
|
// give visual feedback to the user that they now can drag
|
||||||
@ -322,13 +293,9 @@ Item {
|
|||||||
}
|
}
|
||||||
onPositionChanged: {
|
onPositionChanged: {
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
var x = (mouse.x - initialX) / qmlProfile.scale
|
|
||||||
var y = (mouse.y - initialY) / qmlProfile.scale
|
|
||||||
if (manager.verboseEnabled)
|
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))
|
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.pan(mouse.x, mouse.y)
|
||||||
qmlProfile.yOffset = oldYOffset + y
|
|
||||||
qmlProfile.update()
|
|
||||||
} else {
|
} else {
|
||||||
mouse.accepted = false
|
mouse.accepted = false
|
||||||
}
|
}
|
||||||
@ -344,7 +311,6 @@ Item {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
// reset the position if not zoomed in
|
// reset the position if not zoomed in
|
||||||
if (!isZoomed) {
|
if (!isZoomed) {
|
||||||
qmlProfile.xOffset = qmlProfile.yOffset = oldXOffset = oldYOffset = 0
|
|
||||||
mouse.accepted = false
|
mouse.accepted = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,42 +6,37 @@ set(SUBSURFACE_PROFILE_LIB_SRCS
|
|||||||
divecartesianaxis.h
|
divecartesianaxis.h
|
||||||
diveeventitem.cpp
|
diveeventitem.cpp
|
||||||
diveeventitem.h
|
diveeventitem.h
|
||||||
divelineitem.cpp
|
|
||||||
divelineitem.h
|
|
||||||
divepixmapcache.cpp
|
divepixmapcache.cpp
|
||||||
divepixmapcache.h
|
divepixmapcache.h
|
||||||
divepixmapitem.cpp
|
|
||||||
divepixmapitem.h
|
|
||||||
divepercentageitem.cpp
|
divepercentageitem.cpp
|
||||||
divepercentageitem.h
|
divepercentageitem.h
|
||||||
diveprofileitem.cpp
|
diveprofileitem.cpp
|
||||||
diveprofileitem.h
|
diveprofileitem.h
|
||||||
diverectitem.cpp
|
|
||||||
diverectitem.h
|
|
||||||
divetextitem.cpp
|
divetextitem.cpp
|
||||||
divetextitem.h
|
divetextitem.h
|
||||||
divetooltipitem.cpp
|
handleitem.cpp
|
||||||
divetooltipitem.h
|
handleitem.h
|
||||||
|
pictureitem.h
|
||||||
|
pictureitem.cpp
|
||||||
profilescene.cpp
|
profilescene.cpp
|
||||||
profilescene.h
|
profilescene.h
|
||||||
|
profiletranslations.h
|
||||||
|
profileview.cpp
|
||||||
|
profileview.h
|
||||||
|
ruleritem.cpp
|
||||||
|
ruleritem.h
|
||||||
tankitem.cpp
|
tankitem.cpp
|
||||||
tankitem.h
|
tankitem.h
|
||||||
|
tooltipitem.h
|
||||||
|
tooltipitem.cpp
|
||||||
)
|
)
|
||||||
if (SUBSURFACE_TARGET_EXECUTABLE MATCHES "MobileExecutable")
|
if (SUBSURFACE_TARGET_EXECUTABLE MATCHES "MobileExecutable")
|
||||||
set(SUBSURFACE_PROFILE_LIB_SRCS
|
set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||||
${SUBSURFACE_PROFILE_LIB_SRCS}
|
${SUBSURFACE_PROFILE_LIB_SRCS}
|
||||||
qmlprofile.cpp
|
|
||||||
qmlprofile.h
|
|
||||||
)
|
)
|
||||||
else ()
|
else ()
|
||||||
set(SUBSURFACE_PROFILE_LIB_SRCS
|
set(SUBSURFACE_PROFILE_LIB_SRCS
|
||||||
${SUBSURFACE_PROFILE_LIB_SRCS}
|
${SUBSURFACE_PROFILE_LIB_SRCS}
|
||||||
divehandler.cpp
|
|
||||||
divehandler.h
|
|
||||||
profilewidget2.cpp
|
|
||||||
profilewidget2.h
|
|
||||||
ruleritem.cpp
|
|
||||||
ruleritem.h
|
|
||||||
)
|
)
|
||||||
endif ()
|
endif ()
|
||||||
source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS})
|
source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS})
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
#include "core/qthelper.h"
|
#include "core/qthelper.h"
|
||||||
#include "core/subsurface-float.h"
|
#include "core/subsurface-float.h"
|
||||||
#include "profile-widget/animationfunctions.h"
|
#include "profile-widget/animationfunctions.h"
|
||||||
#include "profile-widget/divelineitem.h"
|
|
||||||
#include "profile-widget/profilescene.h"
|
#include "profile-widget/profilescene.h"
|
||||||
|
|
||||||
static const double labelSpaceHorizontal = 2.0; // space between label and ticks
|
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) {
|
if (lineVisibility) {
|
||||||
label.lineStart = linePos(posStart);
|
label.lineStart = linePos(posStart);
|
||||||
label.lineEnd = linePos(pos);
|
label.lineEnd = linePos(pos);
|
||||||
label.line = std::make_unique<DiveLineItem>(this);
|
label.line = std::make_unique<QGraphicsLineItem>(this);
|
||||||
label.line->setPen(gridPen);
|
label.line->setPen(gridPen);
|
||||||
label.line->setZValue(0);
|
label.line->setZValue(0);
|
||||||
label.line->setLine(animSpeed <= 0 ? label.lineEnd : label.lineStart);
|
label.line->setLine(animSpeed <= 0 ? label.lineEnd : label.lineStart);
|
||||||
|
|||||||
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
class ProfileScene;
|
class ProfileScene;
|
||||||
class DiveTextItem;
|
class DiveTextItem;
|
||||||
class DiveLineItem;
|
|
||||||
|
|
||||||
class DiveCartesianAxis : public QGraphicsLineItem {
|
class DiveCartesianAxis : public QGraphicsLineItem {
|
||||||
private:
|
private:
|
||||||
@ -57,7 +56,7 @@ private:
|
|||||||
QLineF lineStart;
|
QLineF lineStart;
|
||||||
QLineF lineEnd;
|
QLineF lineEnd;
|
||||||
std::unique_ptr<DiveTextItem> label;
|
std::unique_ptr<DiveTextItem> label;
|
||||||
std::unique_ptr<DiveLineItem> line;
|
std::unique_ptr<QGraphicsLineItem> line;
|
||||||
};
|
};
|
||||||
Position position;
|
Position position;
|
||||||
bool inverted; // Top-to-bottom or right-to-left axis.
|
bool inverted; // Top-to-bottom or right-to-left axis.
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
#include "profile-widget/divepixmapcache.h"
|
#include "profile-widget/divepixmapcache.h"
|
||||||
#include "profile-widget/animationfunctions.h"
|
#include "profile-widget/animationfunctions.h"
|
||||||
#include "core/event.h"
|
#include "core/event.h"
|
||||||
#include "core/eventtype.h"
|
|
||||||
#include "core/format.h"
|
#include "core/format.h"
|
||||||
#include "core/profile.h"
|
#include "core/profile.h"
|
||||||
#include "core/gettextfromc.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,
|
DiveEventItem::DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix,
|
||||||
const plot_info &pi, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis,
|
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),
|
vAxis(vAxis),
|
||||||
hAxis(hAxis),
|
hAxis(hAxis),
|
||||||
ev(ev),
|
ev(ev),
|
||||||
@ -25,10 +26,11 @@ DiveEventItem::DiveEventItem(const struct dive *d, struct event *ev, struct gasm
|
|||||||
depth(depthAtTime(pi, ev->time))
|
depth(depthAtTime(pi, ev->time))
|
||||||
{
|
{
|
||||||
setFlag(ItemIgnoresTransformations);
|
setFlag(ItemIgnoresTransformations);
|
||||||
|
setPixmap(pixmap);
|
||||||
setupPixmap(lastgasmix, pixmaps);
|
|
||||||
setupToolTipString(lastgasmix);
|
|
||||||
recalculatePos();
|
recalculatePos();
|
||||||
|
|
||||||
|
if (ev->type == SAMPLE_EVENT_BOOKMARK)
|
||||||
|
setOffset(QPointF(0.0, -pixmap.height()));
|
||||||
}
|
}
|
||||||
|
|
||||||
DiveEventItem::~DiveEventItem()
|
DiveEventItem::~DiveEventItem()
|
||||||
@ -45,45 +47,31 @@ struct event *DiveEventItem::getEventMutable()
|
|||||||
return ev;
|
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);
|
event_severity severity = get_event_severity(ev);
|
||||||
if (empty_string(ev->name)) {
|
if (empty_string(ev->name))
|
||||||
setPixmap(pixmaps.warning);
|
return pixmaps.warning;
|
||||||
} else if (same_string_caseinsensitive(ev->name, "modechange")) {
|
|
||||||
if (ev->value == 0)
|
if (same_string_caseinsensitive(ev->name, "modechange"))
|
||||||
setPixmap(pixmaps.bailout);
|
return ev->value == 0 ? pixmaps.bailout : pixmaps.onCCRLoop;
|
||||||
else
|
|
||||||
setPixmap(pixmaps.onCCRLoop);
|
if (ev->type == SAMPLE_EVENT_BOOKMARK)
|
||||||
} else if (ev->type == SAMPLE_EVENT_BOOKMARK) {
|
return pixmaps.bookmark;
|
||||||
setPixmap(pixmaps.bookmark);
|
|
||||||
setOffset(QPointF(0.0, -pixmap().height()));
|
if (event_is_gaschange(ev)) {
|
||||||
} else if (event_is_gaschange(ev)) {
|
|
||||||
struct gasmix mix = get_gasmix_from_event(dive, ev);
|
struct gasmix mix = get_gasmix_from_event(dive, ev);
|
||||||
struct icd_data icd_data;
|
struct icd_data icd_data;
|
||||||
bool icd = isobaric_counterdiffusion(lastgasmix, mix, &icd_data);
|
bool icd = isobaric_counterdiffusion(lastgasmix, mix, &icd_data);
|
||||||
if (mix.he.permille) {
|
if (mix.he.permille)
|
||||||
if (icd)
|
return icd ? pixmaps.gaschangeTrimixICD : pixmaps.gaschangeTrimix;
|
||||||
setPixmap(pixmaps.gaschangeTrimixICD);
|
if (gasmix_is_air(mix))
|
||||||
else
|
return icd ? pixmaps.gaschangeAirICD : pixmaps.gaschangeAir;
|
||||||
setPixmap(pixmaps.gaschangeTrimix);
|
if (mix.o2.permille == 1000)
|
||||||
} else if (gasmix_is_air(mix)) {
|
return icd ? pixmaps.gaschangeOxygenICD : pixmaps.gaschangeOxygen;
|
||||||
if (icd)
|
return icd ? pixmaps.gaschangeEANICD : pixmaps.gaschangeEAN;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
} 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
|
// those are useless internals of the dive computer
|
||||||
same_string_caseinsensitive(ev->name, "heading") ||
|
same_string_caseinsensitive(ev->name, "heading") ||
|
||||||
(same_string_caseinsensitive(ev->name, "SP change") && ev->time.seconds == 0)) {
|
(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)
|
// 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
|
// 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
|
// pixmap for an event, but want to show the event value in the tooltip
|
||||||
setPixmap(pixmaps.transparent);
|
return pixmaps.transparent;
|
||||||
} else if (severity == EVENT_SEVERITY_INFO) {
|
}
|
||||||
setPixmap(pixmaps.info);
|
if (severity == EVENT_SEVERITY_INFO)
|
||||||
} else if (severity == EVENT_SEVERITY_WARN) {
|
return pixmaps.info;
|
||||||
setPixmap(pixmaps.warning);
|
if (severity == EVENT_SEVERITY_WARN)
|
||||||
} else if (severity == EVENT_SEVERITY_ALARM) {
|
return pixmaps.warning;
|
||||||
setPixmap(pixmaps.violation);
|
if (severity == EVENT_SEVERITY_ALARM)
|
||||||
} else if (same_string_caseinsensitive(ev->name, "violation") || // generic libdivecomputer
|
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, "Safety stop violation") || // the rest are from the Uemis downloader
|
||||||
same_string_caseinsensitive(ev->name, "pO₂ ascend alarm") ||
|
same_string_caseinsensitive(ev->name, "pO₂ ascend alarm") ||
|
||||||
same_string_caseinsensitive(ev->name, "RGT alert") ||
|
same_string_caseinsensitive(ev->name, "RGT alert") ||
|
||||||
same_string_caseinsensitive(ev->name, "Dive time alert") ||
|
same_string_caseinsensitive(ev->name, "Dive time alert") ||
|
||||||
same_string_caseinsensitive(ev->name, "Low battery alert") ||
|
same_string_caseinsensitive(ev->name, "Low battery alert") ||
|
||||||
same_string_caseinsensitive(ev->name, "Speed alarm")) {
|
same_string_caseinsensitive(ev->name, "Speed alarm"))
|
||||||
setPixmap(pixmaps.violation);
|
return pixmaps.violation;
|
||||||
} else if (same_string_caseinsensitive(ev->name, "non stop time") || // generic libdivecomputer
|
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") ||
|
||||||
same_string_caseinsensitive(ev->name, "safety stop (voluntary)") ||
|
same_string_caseinsensitive(ev->name, "safety stop (voluntary)") ||
|
||||||
same_string_caseinsensitive(ev->name, "Tank change suggested") || // Uemis downloader
|
same_string_caseinsensitive(ev->name, "Tank change suggested") || // Uemis downloader
|
||||||
same_string_caseinsensitive(ev->name, "Marker")) {
|
same_string_caseinsensitive(ev->name, "Marker"))
|
||||||
setPixmap(pixmaps.info);
|
return pixmaps.info;
|
||||||
} else {
|
|
||||||
// we should do some guessing based on the type / name of the event;
|
// we should do some guessing based on the type / name of the event;
|
||||||
// for now they all get the warning icon
|
// 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
|
// we display the event on screen - so translate
|
||||||
QString name = gettextFromC::tr(ev->name);
|
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!") :
|
name += ev->flags & SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") :
|
||||||
ev->flags & SAMPLE_FLAGS_END ? tr(" end", "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)
|
void DiveEventItem::eventVisibilityChanged(const QString&, bool)
|
||||||
@ -228,7 +216,6 @@ void DiveEventItem::recalculatePos()
|
|||||||
hide();
|
hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setVisible(!ev->hidden && !is_event_type_hidden(ev));
|
|
||||||
double x = hAxis->posAtValue(ev->time.seconds);
|
double x = hAxis->posAtValue(ev->time.seconds);
|
||||||
double y = vAxis->posAtValue(depth);
|
double y = vAxis->posAtValue(depth);
|
||||||
setPos(x, y);
|
setPos(x, y);
|
||||||
|
|||||||
@ -2,15 +2,16 @@
|
|||||||
#ifndef DIVEEVENTITEM_H
|
#ifndef DIVEEVENTITEM_H
|
||||||
#define DIVEEVENTITEM_H
|
#define DIVEEVENTITEM_H
|
||||||
|
|
||||||
#include "divepixmapitem.h"
|
#include <QCoreApplication> // for Q_DECLARE_TR_FUNCTIONS
|
||||||
|
#include <QGraphicsPixmapItem>
|
||||||
|
|
||||||
class DiveCartesianAxis;
|
class DiveCartesianAxis;
|
||||||
class DivePixmaps;
|
class DivePixmaps;
|
||||||
struct event;
|
struct event;
|
||||||
struct plot_info;
|
struct plot_info;
|
||||||
|
|
||||||
class DiveEventItem : public DivePixmapItem {
|
class DiveEventItem : public QGraphicsPixmapItem {
|
||||||
Q_OBJECT
|
Q_DECLARE_TR_FUNCTIONS(DiveEventItem)
|
||||||
public:
|
public:
|
||||||
DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix,
|
DiveEventItem(const struct dive *d, struct event *ev, struct gasmix lastgasmix,
|
||||||
const struct plot_info &pi, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis,
|
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,
|
static bool isInteresting(const struct dive *d, const struct divecomputer *dc,
|
||||||
const struct event *ev, const struct plot_info &pi,
|
const struct event *ev, const struct plot_info &pi,
|
||||||
int firstSecond, int lastSecond);
|
int firstSecond, int lastSecond);
|
||||||
|
const QString text;
|
||||||
|
const QPixmap pixmap;
|
||||||
private:
|
private:
|
||||||
void setupToolTipString(struct gasmix lastgasmix);
|
static QString setupToolTipString(const struct dive *d, const struct event *ev, struct gasmix lastgasmix);
|
||||||
void setupPixmap(struct gasmix lastgasmix, const DivePixmaps &pixmaps);
|
static QPixmap setupPixmap(const struct dive *d, const struct event *ev, struct gasmix lastgasmix, const DivePixmaps &pixmaps);
|
||||||
void recalculatePos();
|
void recalculatePos();
|
||||||
DiveCartesianAxis *vAxis;
|
DiveCartesianAxis *vAxis;
|
||||||
DiveCartesianAxis *hAxis;
|
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/qPrefTechnicalDetails.h"
|
||||||
#include "core/settings/qPrefLog.h"
|
#include "core/settings/qPrefLog.h"
|
||||||
#include "libdivecomputer/parser.h"
|
#include "libdivecomputer/parser.h"
|
||||||
#include "profile-widget/profilewidget2.h"
|
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
AbstractProfilePolygonItem::AbstractProfilePolygonItem(const plot_info &pInfo, const DiveCartesianAxis &horizontal,
|
AbstractProfilePolygonItem::AbstractProfilePolygonItem(const plot_info &pInfo, const DiveCartesianAxis &horizontal,
|
||||||
const DiveCartesianAxis &vertical, DataAccessor accessor,
|
const DiveCartesianAxis &vertical, DataAccessor accessor,
|
||||||
|
|||||||
@ -5,8 +5,6 @@
|
|||||||
#include <QGraphicsPolygonItem>
|
#include <QGraphicsPolygonItem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "divelineitem.h"
|
|
||||||
|
|
||||||
/* This is the Profile Item, it should be used for quite a lot of things
|
/* 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:
|
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
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
#include "divetextitem.h"
|
#include "divetextitem.h"
|
||||||
#include "profilewidget2.h"
|
|
||||||
#include "core/color.h"
|
#include "core/color.h"
|
||||||
#include "core/errorhelper.h"
|
#include "core/errorhelper.h"
|
||||||
|
|
||||||
#include <QBrush>
|
#include <cmath>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QBrush>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
static const double outlineSize = 3.0;
|
static const double outlineSize = 3.0;
|
||||||
|
|
||||||
@ -89,6 +90,11 @@ double DiveTextItem::fontHeight(double dpr, double scale)
|
|||||||
return (double)fm.height();
|
return (double)fm.height();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double DiveTextItem::width() const
|
||||||
|
{
|
||||||
|
return boundingRect().width();
|
||||||
|
}
|
||||||
|
|
||||||
double DiveTextItem::height() const
|
double DiveTextItem::height() const
|
||||||
{
|
{
|
||||||
return fontHeight(dpr, scale) + outlineSize * dpr;
|
return fontHeight(dpr, scale) + outlineSize * dpr;
|
||||||
|
|||||||
@ -2,17 +2,12 @@
|
|||||||
#ifndef DIVETEXTITEM_H
|
#ifndef DIVETEXTITEM_H
|
||||||
#define DIVETEXTITEM_H
|
#define DIVETEXTITEM_H
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QFont>
|
|
||||||
#include <QGraphicsPixmapItem>
|
#include <QGraphicsPixmapItem>
|
||||||
|
|
||||||
class QBrush;
|
class QBrush;
|
||||||
|
|
||||||
/* A Line Item that has animated-properties. */
|
/* A Line Item that has animated-properties. */
|
||||||
class DiveTextItem : public QObject, public QGraphicsPixmapItem {
|
class DiveTextItem : public QGraphicsPixmapItem {
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(QPointF pos READ pos WRITE setPos)
|
|
||||||
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity)
|
|
||||||
public:
|
public:
|
||||||
// Note: vertical centring is based on the actual rendered text, not on the font metrics.
|
// 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
|
// This is fine for placing text in the "tankbar", but it will look disastrous when
|
||||||
@ -22,6 +17,7 @@ public:
|
|||||||
const QString &text();
|
const QString &text();
|
||||||
static double fontHeight(double dpr, double scale);
|
static double fontHeight(double dpr, double scale);
|
||||||
static std::pair<double, double> getLabelSize(double dpr, double scale, const QString &label);
|
static std::pair<double, double> getLabelSize(double dpr, double scale, const QString &label);
|
||||||
|
double width() const;
|
||||||
double height() const;
|
double height() const;
|
||||||
|
|
||||||
private:
|
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 "tankitem.h"
|
||||||
#include "core/device.h"
|
#include "core/device.h"
|
||||||
#include "core/event.h"
|
#include "core/event.h"
|
||||||
|
#include "core/eventtype.h"
|
||||||
#include "core/pref.h"
|
#include "core/pref.h"
|
||||||
#include "core/profile.h"
|
#include "core/profile.h"
|
||||||
#include "core/qthelper.h" // for decoMode()
|
#include "core/qthelper.h" // for decoMode()
|
||||||
@ -16,37 +17,9 @@
|
|||||||
#include "core/subsurface-string.h"
|
#include "core/subsurface-string.h"
|
||||||
#include "core/settings/qPrefDisplay.h"
|
#include "core/settings/qPrefDisplay.h"
|
||||||
#include "qt-models/diveplannermodel.h"
|
#include "qt-models/diveplannermodel.h"
|
||||||
#include <QAbstractAnimation>
|
|
||||||
|
|
||||||
static const double diveComputerTextBorder = 1.0;
|
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>
|
template<typename T, class... Args>
|
||||||
T *ProfileScene::createItem(const DiveCartesianAxis &vAxis, DataAccessor accessor, int z, Args&&... 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
|
[](const plot_data &item) { return 0.0; }, // unused
|
||||||
1, dpr)),
|
1, dpr)),
|
||||||
diveComputerText(new DiveTextItem(dpr, 1.0, Qt::AlignRight | Qt::AlignTop, nullptr)),
|
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,
|
reportedCeiling(createItem<DiveReportedCeiling>(*profileYAxis,
|
||||||
[](const plot_data &item) { return (double)item.ceiling; },
|
[](const plot_data &item) { return (double)item.ceiling; },
|
||||||
1, dpr)),
|
1, dpr)),
|
||||||
@ -170,6 +144,7 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) :
|
|||||||
|
|
||||||
// Add items to scene
|
// Add items to scene
|
||||||
addItem(diveComputerText);
|
addItem(diveComputerText);
|
||||||
|
addItem(eventsHiddenText);
|
||||||
addItem(tankItem);
|
addItem(tankItem);
|
||||||
addItem(decoModelParameters);
|
addItem(decoModelParameters);
|
||||||
addItem(profileYAxis);
|
addItem(profileYAxis);
|
||||||
@ -183,6 +158,8 @@ ProfileScene::ProfileScene(double dpr, bool printMode, bool isGrayscale) :
|
|||||||
|
|
||||||
for (AbstractProfilePolygonItem *item: profileItems)
|
for (AbstractProfilePolygonItem *item: profileItems)
|
||||||
addItem(item);
|
addItem(item);
|
||||||
|
|
||||||
|
eventsHiddenText->set(tr("Hidden events!"), getColor(TIME_TEXT, isGrayscale));
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileScene::~ProfileScene()
|
ProfileScene::~ProfileScene()
|
||||||
@ -190,15 +167,16 @@ ProfileScene::~ProfileScene()
|
|||||||
free_plot_info_data(&plotInfo);
|
free_plot_info_data(&plotInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const plot_info &ProfileScene::getPlotInfo() const
|
||||||
|
{
|
||||||
|
return plotInfo;
|
||||||
|
}
|
||||||
|
|
||||||
void ProfileScene::clear()
|
void ProfileScene::clear()
|
||||||
{
|
{
|
||||||
for (AbstractProfilePolygonItem *item: profileItems)
|
for (AbstractProfilePolygonItem *item: profileItems)
|
||||||
item->clear();
|
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();
|
eventItems.clear();
|
||||||
free_plot_info_data(&plotInfo);
|
free_plot_info_data(&plotInfo);
|
||||||
empty = true;
|
empty = true;
|
||||||
@ -314,8 +292,10 @@ void ProfileScene::updateAxes(bool diveHasHeartBeat, bool simplified)
|
|||||||
profileYAxis->setGridIsMultipleOfThree( qPrefDisplay::three_m_based_grid() );
|
profileYAxis->setGridIsMultipleOfThree( qPrefDisplay::three_m_based_grid() );
|
||||||
|
|
||||||
// Place the fixed dive computer text at the bottom
|
// Place the fixed dive computer text at the bottom
|
||||||
double bottomBorder = sceneRect().height() - diveComputerText->height() - 2.0 * dpr * diveComputerTextBorder;
|
double bottomTextHeight = std::max(diveComputerText->height(), eventsHiddenText->height());
|
||||||
diveComputerText->setPos(0.0, bottomBorder + dpr * diveComputerTextBorder);
|
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;
|
double topBorder = 0.0;
|
||||||
|
|
||||||
@ -394,6 +374,17 @@ bool ProfileScene::pointOnProfile(const QPointF &point) const
|
|||||||
return timeAxis->pointInRange(point.x()) && profileYAxis->pointInRange(point.y());
|
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)
|
static double max_gas(const plot_info &pi, double gas_pressures::*gas)
|
||||||
{
|
{
|
||||||
double ret = -1;
|
double ret = -1;
|
||||||
@ -404,8 +395,9 @@ static double max_gas(const plot_info &pi, double gas_pressures::*gas)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsModel *plannerModel,
|
void ProfileScene::plotDive(const struct dive *dIn, int dcIn, int animSpeed, bool simplified,
|
||||||
bool inPlanner, bool instant, bool keepPlotInfo, bool calcMax, double zoom, double zoomedPosition)
|
DivePlannerPointsModel *plannerModel,
|
||||||
|
bool inPlanner, bool keepPlotInfo, bool calcMax, double zoom, double zoomedPosition)
|
||||||
{
|
{
|
||||||
d = dIn;
|
d = dIn;
|
||||||
dc = dcIn;
|
dc = dcIn;
|
||||||
@ -439,8 +431,6 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
|
|||||||
keepPlotInfo = false;
|
keepPlotInfo = false;
|
||||||
empty = 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.
|
// 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;
|
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;
|
bool hasHeartBeat = plotInfo.maxhr;
|
||||||
// For mobile we might want to turn of some features that are normally shown.
|
// 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);
|
updateVisibility(hasHeartBeat, simplified);
|
||||||
updateAxes(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);
|
int newMaxtime = get_maxtime(&plotInfo);
|
||||||
if (calcMax || newMaxtime > maxtime)
|
if (calcMax || newMaxtime > maxtime)
|
||||||
maxtime = newMaxtime;
|
maxtime = newMaxtime;
|
||||||
@ -546,15 +535,15 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
|
|||||||
if (prefs.percentagegraph)
|
if (prefs.percentagegraph)
|
||||||
percentageItem->replot(d, currentdc, plotInfo);
|
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();
|
eventItems.clear();
|
||||||
struct event *event = currentdc->events;
|
|
||||||
struct gasmix lastgasmix = get_gasmix_at_time(d, currentdc, duration_t{1});
|
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 print mode is selected only draw headings, SP change, gas events or bookmark event
|
||||||
if (printMode) {
|
if (printMode) {
|
||||||
if (empty_string(event->name) ||
|
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) ||
|
(same_string(event->name, "SP change") && event->time.seconds == 0) ||
|
||||||
event_is_gaschange(event) ||
|
event_is_gaschange(event) ||
|
||||||
event->type == SAMPLE_EVENT_BOOKMARK)) {
|
event->type == SAMPLE_EVENT_BOOKMARK)) {
|
||||||
event = event->next;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (DiveEventItem::isInteresting(d, currentdc, event, plotInfo, firstSecond, lastSecond)) {
|
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);
|
timeAxis, profileYAxis, animSpeed, *pixmaps);
|
||||||
item->setZValue(2);
|
item->setZValue(2);
|
||||||
addItem(item);
|
addItem(item.get());
|
||||||
eventItems.push_back(item);
|
eventItems.push_back(std::move(item));
|
||||||
}
|
}
|
||||||
if (event_is_gaschange(event))
|
if (event_is_gaschange(event))
|
||||||
lastgasmix = get_gasmix_from_event(d, event);
|
lastgasmix = get_gasmix_from_event(d, event);
|
||||||
event = event->next;
|
|
||||||
}
|
}
|
||||||
|
eventsHiddenText->setVisible(has_hidden_events);
|
||||||
|
|
||||||
QString dcText = get_dc_nickname(currentdc);
|
QString dcText = get_dc_nickname(currentdc);
|
||||||
if (dcText == "planned dive")
|
if (dcText == "planned dive")
|
||||||
@ -589,12 +577,6 @@ void ProfileScene::plotDive(const struct dive *dIn, int dcIn, DivePlannerPointsM
|
|||||||
if (nr > 1)
|
if (nr > 1)
|
||||||
dcText += tr(" (#%1 of %2)").arg(dc + 1).arg(nr);
|
dcText += tr(" (#%1 of %2)").arg(dc + 1).arg(nr);
|
||||||
diveComputerText->set(dcText, getColor(TIME_TEXT, isGrayscale));
|
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)
|
void ProfileScene::anim(double fraction)
|
||||||
@ -609,7 +591,7 @@ void ProfileScene::draw(QPainter *painter, const QRect &pos,
|
|||||||
{
|
{
|
||||||
QSize size = pos.size();
|
QSize size = pos.size();
|
||||||
resize(QSizeF(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);
|
QImage image(pos.size(), QImage::Format_ARGB32);
|
||||||
image.fill(getColor(::BACKGROUND, isGrayscale));
|
image.fill(getColor(::BACKGROUND, isGrayscale));
|
||||||
@ -649,3 +631,64 @@ double ProfileScene::calcZoomPosition(double zoom, double originalPos, double de
|
|||||||
double newPos = newRelStart / factor;
|
double newPos = newRelStart / factor;
|
||||||
return std::clamp(newPos, 0.0, 1.0);
|
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 DiveTemperatureItem;
|
||||||
class DiveTextItem;
|
class DiveTextItem;
|
||||||
class PartialPressureGasItem;
|
class PartialPressureGasItem;
|
||||||
class ProfileAnimation;
|
|
||||||
class TankItem;
|
class TankItem;
|
||||||
|
|
||||||
class ProfileScene : public QGraphicsScene {
|
class ProfileScene : public QGraphicsScene {
|
||||||
@ -38,20 +37,35 @@ public:
|
|||||||
void resize(QSizeF size);
|
void resize(QSizeF size);
|
||||||
void clear();
|
void clear();
|
||||||
bool pointOnProfile(const QPointF &point) const;
|
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).
|
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.
|
// Can be compared with literal 1.0 to determine "end" state.
|
||||||
|
|
||||||
// If a plannerModel is passed, the deco-information is taken from there.
|
// 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,
|
void plotDive(const struct dive *d, int dc, int animSpeed, bool simplified,
|
||||||
bool instant = false, bool keepPlotInfo = false, bool calcMax = true, double zoom = 1.0, double zoomedPosition = 0.0);
|
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,
|
void draw(QPainter *painter, const QRect &pos,
|
||||||
const struct dive *d, int dc,
|
const struct dive *d, int dc,
|
||||||
DivePlannerPointsModel *plannerModel = nullptr, bool inPlanner = false);
|
DivePlannerPointsModel *plannerModel = nullptr, bool inPlanner = false);
|
||||||
double calcZoomPosition(double zoom, double originalPos, double delta);
|
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;
|
const struct dive *d;
|
||||||
int dc;
|
int dc;
|
||||||
|
QRectF profileRegion; // Region inside the axes, where the crosshair is painted in plan and edit mode.
|
||||||
private:
|
private:
|
||||||
using DataAccessor = double (*)(const plot_data &data);
|
using DataAccessor = double (*)(const plot_data &data);
|
||||||
template<typename T, class... Args> T *createItem(const DiveCartesianAxis &vAxis, DataAccessor accessor, int z, Args&&... args);
|
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 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
|
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.
|
double dpr; // Device Pixel Ratio. A DPR of one corresponds to a "standard" PC screen.
|
||||||
bool printMode;
|
bool printMode;
|
||||||
bool isGrayscale;
|
bool isGrayscale;
|
||||||
@ -70,7 +83,6 @@ private:
|
|||||||
int maxdepth;
|
int maxdepth;
|
||||||
|
|
||||||
struct plot_info plotInfo;
|
struct plot_info plotInfo;
|
||||||
QRectF profileRegion; // Region inside the axes, where the crosshair is painted in plan and edit mode.
|
|
||||||
DiveCartesianAxis *profileYAxis;
|
DiveCartesianAxis *profileYAxis;
|
||||||
DiveCartesianAxis *gasYAxis;
|
DiveCartesianAxis *gasYAxis;
|
||||||
DiveCartesianAxis *temperatureAxis;
|
DiveCartesianAxis *temperatureAxis;
|
||||||
@ -83,8 +95,9 @@ private:
|
|||||||
DiveTemperatureItem *temperatureItem;
|
DiveTemperatureItem *temperatureItem;
|
||||||
DiveMeanDepthItem *meanDepthItem;
|
DiveMeanDepthItem *meanDepthItem;
|
||||||
DiveGasPressureItem *gasPressureItem;
|
DiveGasPressureItem *gasPressureItem;
|
||||||
QList<DiveEventItem *> eventItems;
|
std::vector<std::unique_ptr<DiveEventItem>> eventItems;
|
||||||
DiveTextItem *diveComputerText;
|
DiveTextItem *diveComputerText;
|
||||||
|
DiveTextItem *eventsHiddenText;
|
||||||
DiveReportedCeiling *reportedCeiling;
|
DiveReportedCeiling *reportedCeiling;
|
||||||
PartialPressureGasItem *pn2GasItem;
|
PartialPressureGasItem *pn2GasItem;
|
||||||
PartialPressureGasItem *pheGasItem;
|
PartialPressureGasItem *pheGasItem;
|
||||||
@ -101,7 +114,6 @@ private:
|
|||||||
DivePercentageItem *percentageItem;
|
DivePercentageItem *percentageItem;
|
||||||
TankItem *tankItem;
|
TankItem *tankItem;
|
||||||
std::shared_ptr<const DivePixmaps> pixmaps;
|
std::shared_ptr<const DivePixmaps> pixmaps;
|
||||||
std::unique_ptr<ProfileAnimation> animation;
|
|
||||||
std::vector<DiveCartesianAxis *> animatedAxes;
|
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
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
#include "profile-widget/ruleritem.h"
|
#include "ruleritem.h"
|
||||||
#include "profile-widget/profilewidget2.h"
|
#include "profilescene.h"
|
||||||
|
#include "profileview.h"
|
||||||
|
#include "zvalues.h"
|
||||||
#include "core/settings/qPrefTechnicalDetails.h"
|
#include "core/settings/qPrefTechnicalDetails.h"
|
||||||
|
|
||||||
#include <qgraphicssceneevent.h>
|
|
||||||
|
|
||||||
#include "core/profile.h"
|
#include "core/profile.h"
|
||||||
|
|
||||||
RulerNodeItem2::RulerNodeItem2() :
|
#include <QApplication>
|
||||||
pInfo(NULL),
|
|
||||||
idx(-1),
|
|
||||||
ruler(NULL),
|
|
||||||
timeAxis(NULL),
|
|
||||||
depthAxis(NULL)
|
|
||||||
{
|
|
||||||
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)
|
static QColor handleBorderColor(Qt::red);
|
||||||
{
|
static QColor handleColor(0xff, 0, 0, 0x80);
|
||||||
pInfo = &info;
|
static constexpr double handleRadius = 8.0;
|
||||||
idx = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RulerNodeItem2::setRuler(RulerItem2 *r)
|
static QColor lineColor(Qt::black);
|
||||||
{
|
static constexpr double lineWidth = 1.0;
|
||||||
ruler = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RulerNodeItem2::recalculate()
|
static constexpr int tooltipBorder = 1;
|
||||||
{
|
static constexpr double tooltipBorderRadius = 2.0; // Radius of rounded corners
|
||||||
if (!pInfo || pInfo->nr <= 0)
|
static QColor tooltipBorderColor(Qt::black);
|
||||||
return;
|
static QColor tooltipColor(0xff, 0xff, 0xff, 190);
|
||||||
|
static QColor tooltipFontColor(Qt::black);
|
||||||
|
|
||||||
const struct plot_data &last = pInfo->entry[pInfo->nr - 1];
|
class RulerItemHandle : public ChartDiskItem
|
||||||
if (x() < 0) {
|
{
|
||||||
setPos(0, y());
|
public:
|
||||||
} else if (x() > timeAxis->posAtValue(last.sec)) {
|
ProfileView &profileView;
|
||||||
setPos(timeAxis->posAtValue(last.sec), depthAxis->posAtValue(last.depth));
|
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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
xpos = pos.x();
|
||||||
|
profileView.rulerDragged();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 {
|
} else {
|
||||||
idx = 0;
|
font.setPointSizeF(font.pointSizeF() * dpr);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RulerNodeItem2::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
static ChartItemPtr<RulerItemHandle> makeHandle(ProfileView &view, double dpr)
|
||||||
{
|
{
|
||||||
qreal x = event->scenePos().x();
|
auto res = view.createChartItem<RulerItemHandle>(view, dpr);
|
||||||
if (x < 0.0)
|
res->resize(handleRadius * dpr);
|
||||||
x = 0.0;
|
return res;
|
||||||
setPos(x, event->scenePos().y());
|
|
||||||
recalculate();
|
|
||||||
ruler->recalculate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RulerItem2::RulerItem2() : pInfo(NULL),
|
RulerItem::RulerItem(ProfileView &view, double dpr) :
|
||||||
source(new RulerNodeItem2()),
|
line(view.createChartItem<ChartLineItem>(ProfileZValue::RulerItem,
|
||||||
dest(new RulerNodeItem2()),
|
lineColor, lineWidth * dpr)),
|
||||||
timeAxis(NULL),
|
handle1(makeHandle(view, dpr)),
|
||||||
depthAxis(NULL),
|
handle2(makeHandle(view, dpr)),
|
||||||
textItemBack(new QGraphicsRectItem(this)),
|
tooltip(view.createChartItem<AnimatedChartRectItem>(ProfileZValue::RulerItem,
|
||||||
textItem(new QGraphicsSimpleTextItem(this))
|
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];
|
line->setVisible(visible);
|
||||||
QPointF tmp;
|
handle1->setVisible(visible);
|
||||||
QFont font;
|
handle2->setVisible(visible);
|
||||||
QFontMetrics fm(font);
|
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;
|
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
|
auto strings = compare_samples(d, &info, idx1, idx2, true);
|
||||||
QGraphicsView *view = scene()->views().first();
|
if (strings.empty()) {
|
||||||
QPoint begin = view->mapFromScene(mapToScene(startPoint));
|
tooltip->setVisible(false);
|
||||||
textItem->setText(text);
|
return;
|
||||||
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
|
tooltip->setVisible(true);
|
||||||
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
|
double width = 0;
|
||||||
textItemBack->setVisible(startPoint.x() != endPoint.x());
|
std::vector<int> string_widths;
|
||||||
textItemBack->setPos(textItem->x(), textItem->y());
|
string_widths.reserve(strings.size());
|
||||||
textItemBack->setRect(0, 0, textItem->boundingRect().width(), textItem->boundingRect().height());
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
RulerNodeItem2 *RulerItem2::sourceNode() const
|
void RulerItem::anim(double progress)
|
||||||
{
|
{
|
||||||
return source;
|
tooltip->anim(progress);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,59 +2,32 @@
|
|||||||
#ifndef RULERITEM_H
|
#ifndef RULERITEM_H
|
||||||
#define RULERITEM_H
|
#define RULERITEM_H
|
||||||
|
|
||||||
#include <QObject>
|
#include "qt-quick/chartitem.h"
|
||||||
#include <QGraphicsEllipseItem>
|
|
||||||
#include <QGraphicsObject>
|
|
||||||
#include "profile-widget/divecartesianaxis.h"
|
|
||||||
|
|
||||||
struct plot_data;
|
#include <QFont>
|
||||||
|
#include <QFontMetrics>
|
||||||
|
|
||||||
|
class ProfileView;
|
||||||
|
class ProfileScene;
|
||||||
|
class RulerItemHandle;
|
||||||
struct plot_info;
|
struct plot_info;
|
||||||
class RulerItem2;
|
struct dive;
|
||||||
|
|
||||||
class RulerNodeItem2 : public QObject, public QGraphicsEllipseItem {
|
class RulerItem {
|
||||||
Q_OBJECT
|
ChartItemPtr<ChartLineItem> line;
|
||||||
friend class RulerItem2;
|
ChartItemPtr<RulerItemHandle> handle1, handle2;
|
||||||
|
ChartItemPtr<AnimatedChartRectItem> tooltip;
|
||||||
|
|
||||||
|
QFont font;
|
||||||
|
QFontMetrics fm;
|
||||||
|
double fontHeight;
|
||||||
|
QPixmap title;
|
||||||
public:
|
public:
|
||||||
explicit RulerNodeItem2();
|
RulerItem(ProfileView &view, double dpr);
|
||||||
void setRuler(RulerItem2 *r);
|
~RulerItem();
|
||||||
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);
|
|
||||||
void setVisible(bool visible);
|
void setVisible(bool visible);
|
||||||
|
void update(const dive *d, double dpr, const ProfileScene &scene, const plot_info &info, int animspeed);
|
||||||
public
|
void anim(double progress);
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
#ifndef TANKITEM_H
|
#ifndef TANKITEM_H
|
||||||
#define TANKITEM_H
|
#define TANKITEM_H
|
||||||
|
|
||||||
#include "profile-widget/divelineitem.h"
|
|
||||||
#include "core/gas.h"
|
#include "core/gas.h"
|
||||||
#include <QGraphicsRectItem>
|
#include <QGraphicsRectItem>
|
||||||
#include <QBrush>
|
#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) {
|
for (auto &[v, dives, label]: items) {
|
||||||
if (v > 0.0) {
|
if (v > 0.0) {
|
||||||
bool selected = allDivesSelected(dives);
|
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),
|
std::move(dives),
|
||||||
{}, from, from + v, bin_nr, selected });
|
{}, from, from + v, bin_nr, selected });
|
||||||
if (!label.empty())
|
if (!label.empty())
|
||||||
@ -412,7 +412,7 @@ bool BarSeries::hover(QPointF pos)
|
|||||||
Item &item = items[highlighted.bar];
|
Item &item = items[highlighted.bar];
|
||||||
item.highlight(index.subitem, true, binCount(), theme);
|
item.highlight(index.subitem, true, binCount(), theme);
|
||||||
if (!information)
|
if (!information)
|
||||||
information = view.createChartItem<InformationBox>();
|
information = view.createChartItem<InformationBox>(theme);
|
||||||
information->setText(makeInfo(item, highlighted.subitem), pos);
|
information->setText(makeInfo(item, highlighted.subitem), pos);
|
||||||
information->setVisible(true);
|
information->setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
#ifndef BAR_SERIES_H
|
#ifndef BAR_SERIES_H
|
||||||
#define BAR_SERIES_H
|
#define BAR_SERIES_H
|
||||||
|
|
||||||
#include "statshelper.h"
|
|
||||||
#include "statsseries.h"
|
#include "statsseries.h"
|
||||||
#include "statsvariables.h"
|
#include "statsvariables.h"
|
||||||
|
#include "qt-quick/chartitem_ptr.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|||||||
@ -32,7 +32,7 @@ BoxSeries::Item::Item(StatsView &view, BoxSeries *series, double lowerBound, dou
|
|||||||
binName(binName),
|
binName(binName),
|
||||||
selected(allDivesSelected(q.dives))
|
selected(allDivesSelected(q.dives))
|
||||||
{
|
{
|
||||||
item = view.createChartItem<ChartBoxItem>(ChartZValue::Series, boxBorderWidth);
|
item = view.createChartItem<ChartBoxItem>(ChartZValue::Series, theme, boxBorderWidth);
|
||||||
highlight(false, theme);
|
highlight(false, theme);
|
||||||
updatePosition(series);
|
updatePosition(series);
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ bool BoxSeries::hover(QPointF pos)
|
|||||||
Item &item = *items[highlighted];
|
Item &item = *items[highlighted];
|
||||||
item.highlight(true, theme);
|
item.highlight(true, theme);
|
||||||
if (!information)
|
if (!information)
|
||||||
information = view.createChartItem<InformationBox>();
|
information = view.createChartItem<InformationBox>(theme);
|
||||||
information->setText(formatInformation(item), pos);
|
information->setText(formatInformation(item), pos);
|
||||||
information->setVisible(true);
|
information->setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
#include "statscolors.h"
|
#include "statscolors.h"
|
||||||
#include "statsview.h"
|
#include "statsview.h"
|
||||||
#include "core/globals.h"
|
#include "core/globals.h"
|
||||||
|
#include "qt-quick/chartitemhelper.h"
|
||||||
|
#include "qt-quick/chartitem_private.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <QQuickWindow>
|
#include <QQuickWindow>
|
||||||
@ -14,104 +16,11 @@
|
|||||||
|
|
||||||
static int selectionOverlayPixelSize = 2;
|
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 scatterItemDiameter = 10;
|
||||||
static const int scatterItemBorder = 1;
|
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),
|
positionDirty(false), textureDirty(false),
|
||||||
highlight(selected ? Highlight::Selected : Highlight::Unselected)
|
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);
|
QImage img(scatterItemDiameter, scatterItemDiameter, QImage::Format_ARGB32);
|
||||||
img.fill(Qt::transparent);
|
img.fill(Qt::transparent);
|
||||||
@ -138,7 +47,7 @@ static QSGTexture *createScatterTexture(StatsView &view, const QColor &color, co
|
|||||||
return view.w()->createTextureFromImage(img, QQuickWindow::TextureHasAlphaChannel);
|
return view.w()->createTextureFromImage(img, QQuickWindow::TextureHasAlphaChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
QSGTexture *ChartScatterItem::getTexture(const StatsTheme &theme) const
|
QSGTexture *ChartScatterItem::getTexture() const
|
||||||
{
|
{
|
||||||
switch (highlight) {
|
switch (highlight) {
|
||||||
default:
|
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) {
|
if (!theme.scatterItemTexture) {
|
||||||
theme.scatterItemTexture = register_global(createScatterTexture(view, theme.fillColor, theme.borderColor));
|
theme.scatterItemTexture = register_global(createScatterTexture(view, theme.fillColor, theme.borderColor));
|
||||||
theme.scatterItemSelectedTexture = register_global(createScatterTexture(view, theme.selectedColor, theme.selectedBorderColor));
|
theme.scatterItemSelectedTexture = register_global(createScatterTexture(view, theme.selectedColor, theme.selectedBorderColor));
|
||||||
@ -160,12 +70,12 @@ void ChartScatterItem::render(const StatsTheme &theme)
|
|||||||
}
|
}
|
||||||
if (!node) {
|
if (!node) {
|
||||||
createNode(view.w()->createImageNode());
|
createNode(view.w()->createImageNode());
|
||||||
view.addQSGNode(node.get(), zValue);
|
addNodeToView();
|
||||||
textureDirty = positionDirty = true;
|
textureDirty = positionDirty = true;
|
||||||
}
|
}
|
||||||
updateVisible();
|
updateVisible();
|
||||||
if (textureDirty) {
|
if (textureDirty) {
|
||||||
node->node->setTexture(getTexture(theme));
|
node->node->setTexture(getTexture());
|
||||||
textureDirty = false;
|
textureDirty = false;
|
||||||
}
|
}
|
||||||
if (positionDirty) {
|
if (positionDirty) {
|
||||||
@ -213,73 +123,8 @@ QRectF ChartScatterItem::getRect() const
|
|||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChartRectItem::ChartRectItem(StatsView &v, ChartZValue z,
|
ChartPieItem::ChartPieItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth) : ChartPixmapItem(v, z),
|
||||||
const QPen &pen, const QBrush &brush, double radius) : ChartPixmapItem(v, z),
|
theme(theme),
|
||||||
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),
|
|
||||||
borderWidth(borderWidth)
|
borderWidth(borderWidth)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -300,7 +145,7 @@ static QBrush makeBrush(QColor fill, bool selected, const StatsTheme &theme)
|
|||||||
return QBrush(img);
|
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->setPen(QPen(border, borderWidth));
|
||||||
painter->setBrush(makeBrush(fill, selected, theme));
|
painter->setBrush(makeBrush(fill, selected, theme));
|
||||||
@ -320,101 +165,13 @@ void ChartPieItem::resize(QSizeF size)
|
|||||||
img->fill(Qt::transparent);
|
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)
|
color(color), width(width), positionDirty(false), materialDirty(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ChartLineItemBase::~ChartLineItemBase()
|
ChartBarItem::ChartBarItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth) : HideableChartItem(v, z),
|
||||||
{
|
theme(theme),
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
borderWidth(borderWidth), selected(false),
|
borderWidth(borderWidth), selected(false),
|
||||||
positionDirty(false), colorDirty(false), selectedDirty(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) {
|
if (!theme.selectedTexture) {
|
||||||
QImage img(2, 2, QImage::Format_ARGB32);
|
QImage img(2, 2, QImage::Format_ARGB32);
|
||||||
@ -436,8 +193,9 @@ QSGTexture *ChartBarItem::getSelectedTexture(const StatsTheme &theme) const
|
|||||||
return theme.selectedTexture;
|
return theme.selectedTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChartBarItem::render(const StatsTheme &theme)
|
void ChartBarItem::render()
|
||||||
{
|
{
|
||||||
|
doRearrange();
|
||||||
if (!node) {
|
if (!node) {
|
||||||
createNode(view.w()->createRectangleNode());
|
createNode(view.w()->createRectangleNode());
|
||||||
|
|
||||||
@ -450,7 +208,7 @@ void ChartBarItem::render(const StatsTheme &theme)
|
|||||||
borderNode->setMaterial(borderMaterial.get());
|
borderNode->setMaterial(borderMaterial.get());
|
||||||
|
|
||||||
node->node->appendChildNode(borderNode.get());
|
node->node->appendChildNode(borderNode.get());
|
||||||
view.addQSGNode(node.get(), zValue);
|
addNodeToView();
|
||||||
positionDirty = colorDirty = selectedDirty = true;
|
positionDirty = colorDirty = selectedDirty = true;
|
||||||
}
|
}
|
||||||
updateVisible();
|
updateVisible();
|
||||||
@ -481,7 +239,7 @@ void ChartBarItem::render(const StatsTheme &theme)
|
|||||||
selectionGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4));
|
selectionGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4));
|
||||||
selectionGeometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
|
selectionGeometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
|
||||||
selectionMaterial.reset(new QSGTextureMaterial);
|
selectionMaterial.reset(new QSGTextureMaterial);
|
||||||
selectionMaterial->setTexture(getSelectedTexture(theme));
|
selectionMaterial->setTexture(getSelectedTexture());
|
||||||
selectionMaterial->setHorizontalWrapMode(QSGTexture::Repeat);
|
selectionMaterial->setHorizontalWrapMode(QSGTexture::Repeat);
|
||||||
selectionMaterial->setVerticalWrapMode(QSGTexture::Repeat);
|
selectionMaterial->setVerticalWrapMode(QSGTexture::Repeat);
|
||||||
selectionNode.reset(new QSGGeometryNode);
|
selectionNode.reset(new QSGGeometryNode);
|
||||||
@ -545,8 +303,8 @@ QRectF ChartBarItem::getRect() const
|
|||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChartBoxItem::ChartBoxItem(StatsView &v, ChartZValue z, double borderWidth) :
|
ChartBoxItem::ChartBoxItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth) :
|
||||||
ChartBarItem(v, z, 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
|
// Remember old dirty values, since ChartBarItem::render() will clear them
|
||||||
bool oldPositionDirty = positionDirty;
|
bool oldPositionDirty = positionDirty;
|
||||||
bool oldColorDirty = colorDirty;
|
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) {
|
if (!whiskersNode) {
|
||||||
whiskersGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 10));
|
whiskersGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 10));
|
||||||
whiskersGeometry->setDrawingMode(QSGGeometry::DrawLines);
|
whiskersGeometry->setDrawingMode(QSGGeometry::DrawLines);
|
||||||
|
|||||||
@ -1,160 +1,36 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
// Wrappers around QSGImageNode that allow painting onto an image
|
// Chart item specific to the statistics module
|
||||||
// and then turning that into a texture to be displayed in a QQuickItem.
|
#ifndef STATS_CHART_ITEM_H
|
||||||
#ifndef CHART_ITEM_H
|
#define STATS_CHART_ITEM_H
|
||||||
#define 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 StatsTheme;
|
||||||
class StatsView;
|
class ChartView;
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A pie chart item: draws disk segments onto a pixmap.
|
// A pie chart item: draws disk segments onto a pixmap.
|
||||||
class ChartPieItem : public ChartPixmapItem {
|
class ChartPieItem : public ChartPixmapItem {
|
||||||
public:
|
public:
|
||||||
ChartPieItem(StatsView &v, ChartZValue z, double borderWidth);
|
ChartPieItem(ChartView &v, size_t z, const StatsTheme &theme, 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).
|
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
|
void resize(QSizeF size); // As in base class, but clears the canvas
|
||||||
private:
|
private:
|
||||||
|
const StatsTheme &theme;
|
||||||
double borderWidth;
|
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.
|
// A bar in a bar chart: a rectangle bordered by lines.
|
||||||
class ChartBarItem : public HideableChartProxyItem<QSGRectangleNode> {
|
class ChartBarItem : public HideableChartProxyItem<QSGRectangleNode> {
|
||||||
public:
|
public:
|
||||||
ChartBarItem(StatsView &v, ChartZValue z, double borderWidth);
|
ChartBarItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||||
~ChartBarItem();
|
~ChartBarItem();
|
||||||
void setColor(QColor color, QColor borderColor);
|
void setColor(QColor color, QColor borderColor);
|
||||||
void setRect(const QRectF &rect);
|
void setRect(const QRectF &rect);
|
||||||
void setSelected(bool selected);
|
void setSelected(bool selected);
|
||||||
QRectF getRect() const;
|
QRectF getRect() const;
|
||||||
void render(const StatsTheme &theme) override;
|
void render() override;
|
||||||
protected:
|
protected:
|
||||||
|
const StatsTheme &theme;
|
||||||
QColor color, borderColor;
|
QColor color, borderColor;
|
||||||
double borderWidth;
|
double borderWidth;
|
||||||
QRectF rect;
|
QRectF rect;
|
||||||
@ -170,17 +46,17 @@ private:
|
|||||||
std::unique_ptr<QSGGeometryNode> selectionNode;
|
std::unique_ptr<QSGGeometryNode> selectionNode;
|
||||||
std::unique_ptr<QSGTextureMaterial> selectionMaterial;
|
std::unique_ptr<QSGTextureMaterial> selectionMaterial;
|
||||||
std::unique_ptr<QSGGeometry> selectionGeometry;
|
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.
|
// A box-and-whiskers item. This is a bit lazy: derive from the bar item and add whiskers.
|
||||||
class ChartBoxItem : public ChartBarItem {
|
class ChartBoxItem : public ChartBarItem {
|
||||||
public:
|
public:
|
||||||
ChartBoxItem(StatsView &v, ChartZValue z, double borderWidth);
|
ChartBoxItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||||
~ChartBoxItem();
|
~ChartBoxItem();
|
||||||
void setBox(const QRectF &rect, double min, double max, double median); // The rect describes Q1, Q3.
|
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.
|
QRectF getRect() const; // Note: this extends the center rectangle to include the whiskers.
|
||||||
void render(const StatsTheme &theme) override;
|
void render() override;
|
||||||
private:
|
private:
|
||||||
double min, max, median;
|
double min, max, median;
|
||||||
std::unique_ptr<QSGGeometryNode> whiskersNode;
|
std::unique_ptr<QSGGeometryNode> whiskersNode;
|
||||||
@ -194,7 +70,7 @@ private:
|
|||||||
// scatter item here, but so it is for now.
|
// scatter item here, but so it is for now.
|
||||||
class ChartScatterItem : public HideableChartProxyItem<QSGImageNode> {
|
class ChartScatterItem : public HideableChartProxyItem<QSGImageNode> {
|
||||||
public:
|
public:
|
||||||
ChartScatterItem(StatsView &v, ChartZValue z, bool selected);
|
ChartScatterItem(ChartView &v, size_t z, const StatsTheme &theme, bool selected);
|
||||||
~ChartScatterItem();
|
~ChartScatterItem();
|
||||||
|
|
||||||
// Currently, there is no highlighted and selected status.
|
// Currently, there is no highlighted and selected status.
|
||||||
@ -205,49 +81,17 @@ public:
|
|||||||
};
|
};
|
||||||
void setPos(QPointF pos); // Specifies the *center* of the item.
|
void setPos(QPointF pos); // Specifies the *center* of the item.
|
||||||
void setHighlight(Highlight highlight); // In the future, support different kinds of scatter items.
|
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;
|
QRectF getRect() const;
|
||||||
bool contains(QPointF point) const;
|
bool contains(QPointF point) const;
|
||||||
bool inRect(const QRectF &rect) const;
|
bool inRect(const QRectF &rect) const;
|
||||||
private:
|
private:
|
||||||
QSGTexture *getTexture(const StatsTheme &theme) const;
|
const StatsTheme &theme;
|
||||||
|
QSGTexture *getTexture() const;
|
||||||
QRectF rect;
|
QRectF rect;
|
||||||
QSizeF textureSize;
|
QSizeF textureSize;
|
||||||
bool positionDirty, textureDirty;
|
bool positionDirty, textureDirty;
|
||||||
Highlight highlight;
|
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
|
#endif
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
static const double histogramMarkerWidth = 2.0;
|
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) :
|
QColor color, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||||
ChartLineItem(view, ChartZValue::ChartFeatures, color, histogramMarkerWidth),
|
ChartLineItem(view, ChartZValue::ChartFeatures, color, histogramMarkerWidth),
|
||||||
xAxis(xAxis), yAxis(yAxis),
|
xAxis(xAxis), yAxis(yAxis),
|
||||||
|
|||||||
@ -5,12 +5,11 @@
|
|||||||
#include "chartitem.h"
|
#include "chartitem.h"
|
||||||
|
|
||||||
class StatsAxis;
|
class StatsAxis;
|
||||||
class StatsView;
|
|
||||||
|
|
||||||
// A line marking median or mean in histograms
|
// A line marking median or mean in histograms
|
||||||
class HistogramMarker : public ChartLineItem {
|
class HistogramMarker : public ChartLineItem {
|
||||||
public:
|
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();
|
void updatePosition();
|
||||||
private:
|
private:
|
||||||
StatsAxis *xAxis, *yAxis;
|
StatsAxis *xAxis, *yAxis;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
#include "informationbox.h"
|
#include "informationbox.h"
|
||||||
#include "statscolors.h"
|
#include "statscolors.h"
|
||||||
#include "statsview.h"
|
|
||||||
#include "zvalues.h"
|
#include "zvalues.h"
|
||||||
|
|
||||||
#include <QFontMetrics>
|
#include <QFontMetrics>
|
||||||
@ -9,11 +8,11 @@ static const int informationBorder = 2;
|
|||||||
static const double informationBorderRadius = 4.0; // Radius of rounded corners
|
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
|
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,
|
ChartRectItem(v, ChartZValue::InformationBox,
|
||||||
QPen(v.getCurrentTheme().informationBorderColor, informationBorder),
|
QPen(theme.informationBorderColor, informationBorder),
|
||||||
QBrush(v.getCurrentTheme().informationColor), informationBorderRadius),
|
QBrush(theme.informationColor), informationBorderRadius),
|
||||||
theme(v.getCurrentTheme()),
|
theme(theme),
|
||||||
width(0.0),
|
width(0.0),
|
||||||
height(0.0)
|
height(0.0)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -10,11 +10,12 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
struct dive;
|
struct dive;
|
||||||
class StatsView;
|
class ChartView;
|
||||||
|
class StatsTheme;
|
||||||
|
|
||||||
// Information window showing data of highlighted dive
|
// Information window showing data of highlighted dive
|
||||||
struct InformationBox : ChartRectItem {
|
struct InformationBox : ChartRectItem {
|
||||||
InformationBox(StatsView &);
|
InformationBox(ChartView &, const StatsTheme &theme);
|
||||||
void setText(const std::vector<QString> &text, QPointF pos);
|
void setText(const std::vector<QString> &text, QPointF pos);
|
||||||
void setPos(QPointF pos);
|
void setPos(QPointF pos);
|
||||||
int recommendedMaxLines() const;
|
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 legendBoxScale = 0.8; // 1.0: text-height of the used font
|
||||||
static const double legendInternalBorderSize = 2.0;
|
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,
|
ChartRectItem(view, ChartZValue::Legend,
|
||||||
QPen(view.getCurrentTheme().legendBorderColor, legendBorderSize),
|
QPen(theme.legendBorderColor, legendBorderSize),
|
||||||
QBrush(view.getCurrentTheme().legendColor), legendBoxBorderRadius),
|
QBrush(theme.legendColor), legendBoxBorderRadius,
|
||||||
|
true),
|
||||||
displayedItems(0), width(0.0), height(0.0),
|
displayedItems(0), width(0.0), height(0.0),
|
||||||
theme(view.getCurrentTheme()),
|
theme(theme),
|
||||||
posInitialized(false)
|
posInitialized(false)
|
||||||
{
|
{
|
||||||
entries.reserve(names.size());
|
entries.reserve(names.size());
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
#ifndef STATS_LEGEND_H
|
#ifndef STATS_LEGEND_H
|
||||||
#define STATS_LEGEND_H
|
#define STATS_LEGEND_H
|
||||||
|
|
||||||
#include "chartitem.h"
|
#include "qt-quick/chartitem.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -13,7 +13,7 @@ class StatsTheme;
|
|||||||
|
|
||||||
class Legend : public ChartRectItem {
|
class Legend : public ChartRectItem {
|
||||||
public:
|
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 resize(); // called when the chart size changes.
|
||||||
void setPos(QPointF pos); // Attention: not virtual - always call on this class.
|
void setPos(QPointF pos); // Attention: not virtual - always call on this class.
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -78,13 +78,13 @@ void PieSeries::Item::highlight(ChartPieItem &item, int bin_nr, bool highlight,
|
|||||||
QColor border = highlight ? theme.highlightedBorderColor : theme.borderColor;
|
QColor border = highlight ? theme.highlightedBorderColor : theme.borderColor;
|
||||||
if (innerLabel)
|
if (innerLabel)
|
||||||
innerLabel->setColor(highlight ? theme.darkLabelColor : theme.labelColor(bin_nr, numBins), fill);
|
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,
|
PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName,
|
||||||
std::vector<std::pair<QString, std::vector<dive *>>> data, ChartSortMode sortMode) :
|
std::vector<std::pair<QString, std::vector<dive *>>> data, ChartSortMode sortMode) :
|
||||||
StatsSeries(view, xAxis, yAxis),
|
StatsSeries(view, xAxis, yAxis),
|
||||||
item(view.createChartItem<ChartPieItem>(ChartZValue::Series, pieBorderWidth)),
|
item(view.createChartItem<ChartPieItem>(ChartZValue::Series, theme, pieBorderWidth)),
|
||||||
categoryName(categoryName),
|
categoryName(categoryName),
|
||||||
radius(0),
|
radius(0),
|
||||||
highlighted(-1),
|
highlighted(-1),
|
||||||
@ -257,7 +257,7 @@ bool PieSeries::hover(QPointF pos)
|
|||||||
if (highlighted >= 0 && highlighted < (int)items.size()) {
|
if (highlighted >= 0 && highlighted < (int)items.size()) {
|
||||||
items[highlighted].highlight(*item, highlighted, true, (int)items.size(), theme);
|
items[highlighted].highlight(*item, highlighted, true, (int)items.size(), theme);
|
||||||
if (!information)
|
if (!information)
|
||||||
information = view.createChartItem<InformationBox>();
|
information = view.createChartItem<InformationBox>(theme);
|
||||||
information->setText(makeInfo(highlighted), pos);
|
information->setText(makeInfo(highlighted), pos);
|
||||||
information->setVisible(true);
|
information->setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
#ifndef PIE_SERIES_H
|
#ifndef PIE_SERIES_H
|
||||||
#define PIE_SERIES_H
|
#define PIE_SERIES_H
|
||||||
|
|
||||||
#include "statshelper.h"
|
|
||||||
#include "statsseries.h"
|
#include "statsseries.h"
|
||||||
|
#include "qt-quick/chartitem_ptr.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
static const double quartileMarkerSize = 15.0;
|
static const double quartileMarkerSize = 15.0;
|
||||||
|
|
||||||
QuartileMarker::QuartileMarker(StatsView &view, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis) :
|
QuartileMarker::QuartileMarker(ChartView &view, const StatsTheme &theme, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||||
ChartLineItem(view, ChartZValue::ChartFeatures, view.getCurrentTheme().quartileMarkerColor, 2.0),
|
ChartLineItem(view, ChartZValue::ChartFeatures, theme.quartileMarkerColor, 2.0),
|
||||||
xAxis(xAxis), yAxis(yAxis),
|
xAxis(xAxis), yAxis(yAxis),
|
||||||
pos(pos),
|
pos(pos),
|
||||||
value(value)
|
value(value)
|
||||||
|
|||||||
@ -5,11 +5,10 @@
|
|||||||
#include "chartitem.h"
|
#include "chartitem.h"
|
||||||
|
|
||||||
class StatsAxis;
|
class StatsAxis;
|
||||||
class StatsView;
|
|
||||||
|
|
||||||
class QuartileMarker : public ChartLineItem {
|
class QuartileMarker : public ChartLineItem {
|
||||||
public:
|
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();
|
~QuartileMarker();
|
||||||
void updatePosition();
|
void updatePosition();
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -9,10 +9,10 @@
|
|||||||
|
|
||||||
static const double regressionLineWidth = 2.0;
|
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) :
|
StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||||
ChartPixmapItem(view, ChartZValue::ChartFeatures),
|
ChartPixmapItem(view, ChartZValue::ChartFeatures),
|
||||||
theme(view.getCurrentTheme()),
|
theme(theme),
|
||||||
xAxis(xAxis), yAxis(yAxis), reg(reg),
|
xAxis(xAxis), yAxis(yAxis), reg(reg),
|
||||||
regression(true), confidence(true)
|
regression(true), confidence(true)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
class StatsAxis;
|
class StatsAxis;
|
||||||
class StatsTheme;
|
class StatsTheme;
|
||||||
class StatsView;
|
|
||||||
|
|
||||||
struct regression_data {
|
struct regression_data {
|
||||||
double a,b;
|
double a,b;
|
||||||
@ -16,7 +15,7 @@ struct regression_data {
|
|||||||
|
|
||||||
class RegressionItem : public ChartPixmapItem {
|
class RegressionItem : public ChartPixmapItem {
|
||||||
public:
|
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();
|
~RegressionItem();
|
||||||
void updatePosition();
|
void updatePosition();
|
||||||
void setFeatures(bool regression, bool confidence);
|
void setFeatures(bool regression, bool confidence);
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
#include "chartitem.h"
|
#include "chartitem.h"
|
||||||
#include "informationbox.h"
|
#include "informationbox.h"
|
||||||
#include "statscolors.h"
|
#include "statscolors.h"
|
||||||
#include "statshelper.h"
|
|
||||||
#include "statstranslations.h"
|
#include "statstranslations.h"
|
||||||
#include "statsvariables.h"
|
#include "statsvariables.h"
|
||||||
#include "statsview.h"
|
#include "statsview.h"
|
||||||
@ -24,8 +23,8 @@ ScatterSeries::~ScatterSeries()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ScatterSeries::Item::Item(StatsView &view, ScatterSeries *series, dive *d, double pos, double value) :
|
ScatterSeries::Item::Item(StatsView &view, ScatterSeries *series, const StatsTheme &theme, dive *d, double pos, double value) :
|
||||||
item(view.createChartItem<ChartScatterItem>(ChartZValue::Series, d->selected)),
|
item(view.createChartItem<ChartScatterItem>(ChartZValue::Series, theme, d->selected)),
|
||||||
d(d),
|
d(d),
|
||||||
selected(d->selected),
|
selected(d->selected),
|
||||||
pos(pos),
|
pos(pos),
|
||||||
@ -50,7 +49,7 @@ void ScatterSeries::Item::highlight(bool highlight)
|
|||||||
|
|
||||||
void ScatterSeries::append(dive *d, double pos, double value)
|
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()
|
void ScatterSeries::updatePositions()
|
||||||
@ -183,7 +182,7 @@ bool ScatterSeries::hover(QPointF pos)
|
|||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if (!information)
|
if (!information)
|
||||||
information = view.createChartItem<InformationBox>();
|
information = view.createChartItem<InformationBox>(theme);
|
||||||
|
|
||||||
std::vector<QString> text;
|
std::vector<QString> text;
|
||||||
text.reserve(highlighted.size() * 5);
|
text.reserve(highlighted.size() * 5);
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "statshelper.h"
|
#include "statshelper.h"
|
||||||
#include "statsseries.h"
|
#include "statsseries.h"
|
||||||
|
#include "qt-quick/chartitem_ptr.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -41,7 +42,7 @@ private:
|
|||||||
dive *d;
|
dive *d;
|
||||||
bool selected;
|
bool selected;
|
||||||
double pos, value;
|
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 updatePosition(ScatterSeries *series);
|
||||||
void highlight(bool highlight);
|
void highlight(bool highlight);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
#include "statsaxis.h"
|
#include "statsaxis.h"
|
||||||
#include "statscolors.h"
|
#include "statscolors.h"
|
||||||
#include "statshelper.h"
|
|
||||||
#include "statstranslations.h"
|
#include "statstranslations.h"
|
||||||
#include "statsvariables.h"
|
#include "statsvariables.h"
|
||||||
#include "statsview.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 axisTitleSpaceHorizontal = 2.0; // Space between labels and title
|
||||||
static const double axisTitleSpaceVertical = 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),
|
ChartPixmapItem(view, ChartZValue::Axes),
|
||||||
theme(view.getCurrentTheme()),
|
theme(theme),
|
||||||
line(view.createChartItem<ChartLineItem>(ChartZValue::Axes, theme.axisColor, axisWidth)),
|
line(view.createChartItem<ChartLineItem>(ChartZValue::Axes, theme.axisColor, axisWidth)),
|
||||||
title(title), horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks),
|
title(title), horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks),
|
||||||
size(1.0), zeroOnScreen(0.0), min(0.0), max(1.0), labelWidth(0.0)
|
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) :
|
ValueAxis::ValueAxis(ChartView &view, const StatsTheme &theme,
|
||||||
StatsAxis(view, title, horizontal, false),
|
const QString &title, double min, double max, int decimals, bool horizontal) :
|
||||||
|
StatsAxis(view, theme, title, horizontal, false),
|
||||||
min(min), max(max), decimals(decimals)
|
min(min), max(max), decimals(decimals)
|
||||||
{
|
{
|
||||||
// Avoid degenerate cases
|
// Avoid degenerate cases
|
||||||
@ -317,8 +317,8 @@ void ValueAxis::updateLabels()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CountAxis::CountAxis(StatsView &view, const QString &title, int count, bool horizontal) :
|
CountAxis::CountAxis(ChartView &view, const StatsTheme &theme, const QString &title, int count, bool horizontal) :
|
||||||
ValueAxis(view, title, 0.0, (double)count, 0, horizontal),
|
ValueAxis(view, theme, title, 0.0, (double)count, 0, horizontal),
|
||||||
count(count)
|
count(count)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -376,8 +376,9 @@ void CountAxis::updateLabels()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CategoryAxis::CategoryAxis(StatsView &view, const QString &title, const std::vector<QString> &labels, bool horizontal) :
|
CategoryAxis::CategoryAxis(ChartView &view, const StatsTheme &theme,
|
||||||
StatsAxis(view, title, horizontal, true),
|
const QString &title, const std::vector<QString> &labels, bool horizontal) :
|
||||||
|
StatsAxis(view, theme, title, horizontal, true),
|
||||||
labelsText(labels)
|
labelsText(labels)
|
||||||
{
|
{
|
||||||
if (!labels.empty())
|
if (!labels.empty())
|
||||||
@ -437,8 +438,9 @@ void CategoryAxis::updateLabels()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HistogramAxis::HistogramAxis(StatsView &view, const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) :
|
HistogramAxis::HistogramAxis(ChartView &view, const StatsTheme &theme,
|
||||||
StatsAxis(view, title, horizontal, false),
|
const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) :
|
||||||
|
StatsAxis(view, theme, title, horizontal, false),
|
||||||
bin_values(std::move(bins))
|
bin_values(std::move(bins))
|
||||||
{
|
{
|
||||||
if (bin_values.size() < 2) // Less than two makes no sense -> there must be at least one category
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateAxis::DateAxis(StatsView &view, const QString &title, double from, double to, bool horizontal) :
|
DateAxis::DateAxis(ChartView &view, const StatsTheme &theme, const QString &title, double from, double to, bool horizontal) :
|
||||||
HistogramAxis(view, title, timeRangeToBins(from, to), horizontal)
|
HistogramAxis(view, theme, title, timeRangeToBins(from, to), horizontal)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,10 @@
|
|||||||
#define STATS_AXIS_H
|
#define STATS_AXIS_H
|
||||||
|
|
||||||
#include "chartitem.h"
|
#include "chartitem.h"
|
||||||
#include "statshelper.h"
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class StatsView;
|
|
||||||
class ChartLineItem;
|
class ChartLineItem;
|
||||||
class QFontMetrics;
|
class QFontMetrics;
|
||||||
|
|
||||||
@ -34,7 +32,7 @@ public:
|
|||||||
|
|
||||||
std::vector<double> ticksPositions() const; // Positions in screen coordinates
|
std::vector<double> ticksPositions() const; // Positions in screen coordinates
|
||||||
protected:
|
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.
|
const StatsTheme &theme; // Initialized once in constructor.
|
||||||
ChartItemPtr<ChartLineItem> line;
|
ChartItemPtr<ChartLineItem> line;
|
||||||
@ -73,7 +71,8 @@ private:
|
|||||||
|
|
||||||
class ValueAxis : public StatsAxis {
|
class ValueAxis : public StatsAxis {
|
||||||
public:
|
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:
|
private:
|
||||||
double min, max;
|
double min, max;
|
||||||
int decimals;
|
int decimals;
|
||||||
@ -83,7 +82,7 @@ private:
|
|||||||
|
|
||||||
class CountAxis : public ValueAxis {
|
class CountAxis : public ValueAxis {
|
||||||
public:
|
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:
|
private:
|
||||||
int count;
|
int count;
|
||||||
void updateLabels() override;
|
void updateLabels() override;
|
||||||
@ -92,7 +91,8 @@ private:
|
|||||||
|
|
||||||
class CategoryAxis : public StatsAxis {
|
class CategoryAxis : public StatsAxis {
|
||||||
public:
|
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:
|
private:
|
||||||
std::vector<QString> labelsText;
|
std::vector<QString> labelsText;
|
||||||
void updateLabels();
|
void updateLabels();
|
||||||
@ -107,7 +107,8 @@ struct HistogramAxisEntry {
|
|||||||
|
|
||||||
class HistogramAxis : public StatsAxis {
|
class HistogramAxis : public StatsAxis {
|
||||||
public:
|
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:
|
private:
|
||||||
void updateLabels() override;
|
void updateLabels() override;
|
||||||
std::pair<QString, QString> getFirstLastLabel() const override;
|
std::pair<QString, QString> getFirstLastLabel() const override;
|
||||||
@ -117,7 +118,7 @@ private:
|
|||||||
|
|
||||||
class DateAxis : public HistogramAxis {
|
class DateAxis : public HistogramAxis {
|
||||||
public:
|
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
|
#endif
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
// The background grid of a chart
|
// The background grid of a chart
|
||||||
|
|
||||||
#include "statshelper.h"
|
#include "qt-quick/chartitem_ptr.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
// Helper functions to render the stats. Includes
|
// Helper functions to render the stats.
|
||||||
// QSGNode template jugglery to overcome API flaws.
|
|
||||||
#ifndef STATSHELPER_H
|
#ifndef STATSHELPER_H
|
||||||
#define STATSHELPER_H
|
#define STATSHELPER_H
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QPointF>
|
#include <QPointF>
|
||||||
#include <QSGNode>
|
|
||||||
#include "core/dive.h"
|
struct dive;
|
||||||
|
|
||||||
// Round positions to integer values to avoid ugly artifacts
|
// Round positions to integer values to avoid ugly artifacts
|
||||||
QPointF roundPos(const QPointF &p);
|
QPointF roundPos(const QPointF &p);
|
||||||
@ -16,128 +14,4 @@ QPointF roundPos(const QPointF &p);
|
|||||||
// Are all dives in this vector selected?
|
// Are all dives in this vector selected?
|
||||||
bool allDivesSelected(const std::vector<dive *> &dives);
|
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
|
#endif
|
||||||
|
|||||||
@ -21,29 +21,21 @@
|
|||||||
#include "core/selection.h"
|
#include "core/selection.h"
|
||||||
#include "core/trip.h"
|
#include "core/trip.h"
|
||||||
|
|
||||||
#include <array> // for std::array
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <QQuickItem>
|
|
||||||
#include <QQuickWindow>
|
|
||||||
#include <QSGImageNode>
|
|
||||||
#include <QSGRectangleNode>
|
|
||||||
#include <QSGTexture>
|
|
||||||
|
|
||||||
// Constants that control the graph layouts
|
// Constants that control the graph layouts
|
||||||
static const double sceneBorder = 5.0; // Border between scene edges and statitistics view
|
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 titleBorder = 2.0; // Border between title and chart
|
||||||
static const double selectionLassoWidth = 2.0; // Border between title and chart
|
static const double selectionLassoWidth = 2.0; // Border between title and chart
|
||||||
|
|
||||||
StatsView::StatsView(QQuickItem *parent) : QQuickItem(parent),
|
StatsView::StatsView(QQuickItem *parent) : ChartView(parent, ChartZValue::Count),
|
||||||
backgroundDirty(true),
|
|
||||||
currentTheme(&getStatsTheme(false)),
|
currentTheme(&getStatsTheme(false)),
|
||||||
highlightedSeries(nullptr),
|
highlightedSeries(nullptr),
|
||||||
xAxis(nullptr),
|
xAxis(nullptr),
|
||||||
yAxis(nullptr),
|
yAxis(nullptr),
|
||||||
draggedItem(nullptr),
|
restrictDives(false)
|
||||||
restrictDives(false),
|
|
||||||
rootNode(nullptr)
|
|
||||||
{
|
{
|
||||||
|
setBackgroundColor(currentTheme->backgroundColor);
|
||||||
setFlag(ItemHasContents, true);
|
setFlag(ItemHasContents, true);
|
||||||
|
|
||||||
connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &StatsView::replotIfVisible);
|
connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &StatsView::replotIfVisible);
|
||||||
@ -67,22 +59,12 @@ StatsView::~StatsView()
|
|||||||
|
|
||||||
void StatsView::mousePressEvent(QMouseEvent *event)
|
void StatsView::mousePressEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
QPointF pos = event->localPos();
|
// Handle dragging of items
|
||||||
|
ChartView::mousePressEvent(event);
|
||||||
// Currently, we only support dragging of the legend. If other objects
|
if (event->isAccepted())
|
||||||
// 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
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
QPointF pos = event->localPos();
|
||||||
SelectionModifier modifier;
|
SelectionModifier modifier;
|
||||||
modifier.shift = (event->modifiers() & Qt::ShiftModifier) != 0;
|
modifier.shift = (event->modifiers() & Qt::ShiftModifier) != 0;
|
||||||
modifier.ctrl = (event->modifiers() & Qt::ControlModifier) != 0;
|
modifier.ctrl = (event->modifiers() & Qt::ControlModifier) != 0;
|
||||||
@ -97,7 +79,7 @@ void StatsView::mousePressEvent(QMouseEvent *event)
|
|||||||
{ return s->supportsLassoSelection(); })) {
|
{ return s->supportsLassoSelection(); })) {
|
||||||
if (selectionRect)
|
if (selectionRect)
|
||||||
deleteChartItem(selectionRect); // Ooops. Already a selection in place.
|
deleteChartItem(selectionRect); // Ooops. Already a selection in place.
|
||||||
dragStartMouse = pos;
|
selectionStartMouse = pos;
|
||||||
selectionRect = createChartItem<ChartRectLineItem>(ChartZValue::Selection, currentTheme->selectionLassoColor, selectionLassoWidth);
|
selectionRect = createChartItem<ChartRectLineItem>(ChartZValue::Selection, currentTheme->selectionLassoColor, selectionLassoWidth);
|
||||||
selectionModifier = modifier;
|
selectionModifier = modifier;
|
||||||
oldSelection = modifier.ctrl ? getDiveSelection() : std::vector<dive *>();
|
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) {
|
ChartView::mouseReleaseEvent(event);
|
||||||
draggedItem = nullptr;
|
|
||||||
ungrabMouse();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectionRect) {
|
if (selectionRect) {
|
||||||
deleteChartItem(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)
|
void StatsView::setTheme(bool dark)
|
||||||
{
|
{
|
||||||
currentTheme = &getStatsTheme(dark);
|
currentTheme = &getStatsTheme(dark);
|
||||||
rootNode->backgroundNode->setColor(currentTheme->backgroundColor);
|
setBackgroundColor(currentTheme->backgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatsTheme &StatsView::getCurrentTheme() const
|
const StatsTheme &StatsView::getCurrentTheme() const
|
||||||
@ -309,34 +111,6 @@ const StatsTheme &StatsView::getCurrentTheme() const
|
|||||||
return *currentTheme;
|
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)
|
void StatsView::plotAreaChanged(const QSizeF &s)
|
||||||
{
|
{
|
||||||
double left = sceneBorder;
|
double left = sceneBorder;
|
||||||
@ -404,17 +178,11 @@ void StatsView::divesSelected(const QVector<dive *> &dives)
|
|||||||
|
|
||||||
void StatsView::mouseMoveEvent(QMouseEvent *event)
|
void StatsView::mouseMoveEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (draggedItem) {
|
ChartView::mouseMoveEvent(event);
|
||||||
QSizeF sceneSize = size();
|
|
||||||
if (sceneSize.width() <= 1.0 || sceneSize.height() <= 1.0)
|
|
||||||
return;
|
|
||||||
draggedItem->setPos(event->pos() - dragStartMouse + dragStartItem);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectionRect) {
|
if (selectionRect) {
|
||||||
QPointF p1 = event->pos();
|
QPointF p1 = event->pos();
|
||||||
QPointF p2 = dragStartMouse;
|
QPointF p2 = selectionStartMouse;
|
||||||
selectionRect->setLine(p1, p2);
|
selectionRect->setLine(p1, p2);
|
||||||
QRectF rect(std::min(p1.x(), p2.x()), std::min(p1.y(), p2.y()),
|
QRectF rect(std::min(p1.x(), p2.x()), std::min(p1.y(), p2.y()),
|
||||||
fabs(p2.x() - p1.x()), fabs(p2.y() - p1.y()));
|
fabs(p2.x() - p1.x()), fabs(p2.y() - p1.y()));
|
||||||
@ -483,7 +251,7 @@ void StatsView::updateTitlePos()
|
|||||||
template <typename T, class... Args>
|
template <typename T, class... Args>
|
||||||
T *StatsView::createAxis(const QString &title, Args&&... 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)
|
void StatsView::setAxes(StatsAxis *x, StatsAxis *y)
|
||||||
@ -495,10 +263,15 @@ void StatsView::setAxes(StatsAxis *x, StatsAxis *y)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StatsView::reset()
|
void StatsView::reset()
|
||||||
|
{
|
||||||
|
resetPointers();
|
||||||
|
clearItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatsView::resetPointers()
|
||||||
{
|
{
|
||||||
highlightedSeries = nullptr;
|
highlightedSeries = nullptr;
|
||||||
xAxis = yAxis = nullptr;
|
xAxis = yAxis = nullptr;
|
||||||
draggedItem = nullptr;
|
|
||||||
title.reset();
|
title.reset();
|
||||||
legend.reset();
|
legend.reset();
|
||||||
regressionItem.reset();
|
regressionItem.reset();
|
||||||
@ -506,10 +279,6 @@ void StatsView::reset()
|
|||||||
medianMarker.reset();
|
medianMarker.reset();
|
||||||
selectionRect.reset();
|
selectionRect.reset();
|
||||||
|
|
||||||
// Mark clean and dirty chart items for deletion
|
|
||||||
cleanItems.splice(deletedItems);
|
|
||||||
dirtyItems.splice(deletedItems);
|
|
||||||
|
|
||||||
series.clear();
|
series.clear();
|
||||||
quartileMarkers.clear();
|
quartileMarkers.clear();
|
||||||
grid.reset();
|
grid.reset();
|
||||||
@ -539,7 +308,7 @@ void StatsView::plot(const StatsState &stateIn)
|
|||||||
state = stateIn;
|
state = stateIn;
|
||||||
plotChart();
|
plotChart();
|
||||||
updateFeatures(); // Show / hide chart features, such as legend, etc.
|
updateFeatures(); // Show / hide chart features, such as legend, etc.
|
||||||
plotAreaChanged(plotRect.size());
|
plotAreaChanged(plotArea().size());
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,8 +379,7 @@ void StatsView::updateFeatures()
|
|||||||
legend->setVisible(state.legend);
|
legend->setVisible(state.legend);
|
||||||
|
|
||||||
// For labels, we are brutal: simply show/hide the whole z-level with the labels
|
// For labels, we are brutal: simply show/hide the whole z-level with the labels
|
||||||
if (rootNode)
|
setLayerVisibility(ChartZValue::SeriesLabels, state.labels);
|
||||||
rootNode->zNodes[(int)ChartZValue::SeriesLabels]->setVisible(state.labels);
|
|
||||||
|
|
||||||
if (meanMarker)
|
if (meanMarker)
|
||||||
meanMarker->setVisible(state.mean);
|
meanMarker->setVisible(state.mean);
|
||||||
@ -764,7 +532,7 @@ void StatsView::plotBarChart(const std::vector<dive *> &dives,
|
|||||||
setAxes(catAxis, valAxis);
|
setAxes(catAxis, valAxis);
|
||||||
|
|
||||||
// Paint legend first, because the bin-names will be moved away from.
|
// 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;
|
std::vector<BarSeries::MultiItem> items;
|
||||||
items.reserve(data.hbins.size());
|
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);
|
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,
|
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);
|
StatsQuartiles quartiles = StatsVariable::quartiles(array);
|
||||||
if (quartiles.isValid()) {
|
if (quartiles.isValid()) {
|
||||||
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
||||||
x, quartiles.q1, catAxis, valAxis));
|
*currentTheme, x, quartiles.q1, catAxis, valAxis));
|
||||||
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
||||||
x, quartiles.q2, catAxis, valAxis));
|
*currentTheme, x, quartiles.q2, catAxis, valAxis));
|
||||||
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
||||||
x, quartiles.q3, catAxis, valAxis));
|
*currentTheme, x, quartiles.q3, catAxis, valAxis));
|
||||||
}
|
}
|
||||||
x += 1.0;
|
x += 1.0;
|
||||||
}
|
}
|
||||||
@ -1214,7 +982,7 @@ void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives,
|
|||||||
*categoryBinner, categoryBins, !isHorizontal);
|
*categoryBinner, categoryBins, !isHorizontal);
|
||||||
|
|
||||||
BarPlotData data(categoryBins, *valueBinner);
|
BarPlotData data(categoryBins, *valueBinner);
|
||||||
legend = createChartItem<Legend>(data.vbinNames);
|
legend = createChartItem<Legend>(*currentTheme, data.vbinNames);
|
||||||
|
|
||||||
CountAxis *valAxis = createCountAxis(data.maxCategoryCount, isHorizontal);
|
CountAxis *valAxis = createCountAxis(data.maxCategoryCount, isHorizontal);
|
||||||
|
|
||||||
@ -1365,5 +1133,5 @@ void StatsView::plotScatter(const std::vector<dive *> &dives, const StatsVariabl
|
|||||||
// y = ax + b
|
// y = ax + b
|
||||||
struct regression_data reg = linear_regression(points);
|
struct regression_data reg = linear_regression(points);
|
||||||
if (!std::isnan(reg.a))
|
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
|
#define STATS_VIEW_H
|
||||||
|
|
||||||
#include "statsstate.h"
|
#include "statsstate.h"
|
||||||
#include "statshelper.h"
|
|
||||||
#include "statsselection.h"
|
#include "statsselection.h"
|
||||||
|
#include "qt-quick/chartview.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
struct dive;
|
struct dive;
|
||||||
struct StatsBinner;
|
struct StatsBinner;
|
||||||
@ -18,7 +18,6 @@ struct StatsVariable;
|
|||||||
|
|
||||||
class StatsSeries;
|
class StatsSeries;
|
||||||
class CategoryAxis;
|
class CategoryAxis;
|
||||||
class ChartItem;
|
|
||||||
class ChartRectLineItem;
|
class ChartRectLineItem;
|
||||||
class ChartTextItem;
|
class ChartTextItem;
|
||||||
class CountAxis;
|
class CountAxis;
|
||||||
@ -30,15 +29,12 @@ class StatsAxis;
|
|||||||
class StatsGrid;
|
class StatsGrid;
|
||||||
class StatsTheme;
|
class StatsTheme;
|
||||||
class Legend;
|
class Legend;
|
||||||
class QSGTexture;
|
|
||||||
class RootNode; // Internal implementation detail
|
|
||||||
|
|
||||||
enum class ChartSubType : int;
|
enum class ChartSubType : int;
|
||||||
enum class ChartZValue : int;
|
|
||||||
enum class StatsOperation : int;
|
enum class StatsOperation : int;
|
||||||
enum class ChartSortMode : int;
|
enum class ChartSortMode : int;
|
||||||
|
|
||||||
class StatsView : public QQuickItem {
|
class StatsView : public ChartView {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
StatsView();
|
StatsView();
|
||||||
@ -50,42 +46,15 @@ public:
|
|||||||
void restrictToSelection();
|
void restrictToSelection();
|
||||||
void unrestrict();
|
void unrestrict();
|
||||||
int restrictionCount() const; // <0: no restriction
|
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.
|
void setTheme(bool dark); // Chart must be replot for theme to become effective.
|
||||||
const StatsTheme &getCurrentTheme() const;
|
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 replotIfVisible();
|
||||||
void divesSelected(const QVector<dive *> &dives);
|
void divesSelected(const QVector<dive *> &dives);
|
||||||
private:
|
private:
|
||||||
// QtQuick related things
|
void plotAreaChanged(const QSizeF &size) override;
|
||||||
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 reset(); // clears all series and axes
|
void reset(); // clears all series and axes
|
||||||
|
void resetPointers() override;
|
||||||
void setAxes(StatsAxis *x, StatsAxis *y);
|
void setAxes(StatsAxis *x, StatsAxis *y);
|
||||||
void plotBarChart(const std::vector<dive *> &dives,
|
void plotBarChart(const std::vector<dive *> &dives,
|
||||||
ChartSubType subType, ChartSortMode sortMode,
|
ChartSubType subType, ChartSortMode sortMode,
|
||||||
@ -151,10 +120,9 @@ private:
|
|||||||
StatsAxis *xAxis, *yAxis;
|
StatsAxis *xAxis, *yAxis;
|
||||||
ChartItemPtr<ChartTextItem> title;
|
ChartItemPtr<ChartTextItem> title;
|
||||||
ChartItemPtr<Legend> legend;
|
ChartItemPtr<Legend> legend;
|
||||||
Legend *draggedItem;
|
|
||||||
ChartItemPtr<RegressionItem> regressionItem;
|
ChartItemPtr<RegressionItem> regressionItem;
|
||||||
ChartItemPtr<ChartRectLineItem> selectionRect;
|
ChartItemPtr<ChartRectLineItem> selectionRect;
|
||||||
QPointF dragStartMouse, dragStartItem;
|
QPointF selectionStartMouse;
|
||||||
SelectionModifier selectionModifier;
|
SelectionModifier selectionModifier;
|
||||||
std::vector<dive *> oldSelection;
|
std::vector<dive *> oldSelection;
|
||||||
bool restrictDives;
|
bool restrictDives;
|
||||||
@ -165,40 +133,6 @@ private:
|
|||||||
void mousePressEvent(QMouseEvent *event) override;
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
void mouseMoveEvent(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
|
#endif
|
||||||
|
|||||||
@ -4,8 +4,13 @@
|
|||||||
// with smaller z-values. For the same z-value objects are
|
// with smaller z-values. For the same z-value objects are
|
||||||
// drawn in order of addition to the scene.
|
// drawn in order of addition to the scene.
|
||||||
#ifndef ZVALUES_H
|
#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,
|
Grid = 0,
|
||||||
Series,
|
Series,
|
||||||
Axes,
|
Axes,
|
||||||
@ -15,6 +20,7 @@ enum class ChartZValue {
|
|||||||
InformationBox,
|
InformationBox,
|
||||||
Legend,
|
Legend,
|
||||||
Count
|
Count
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "qt-models/maplocationmodel.h"
|
#include "qt-models/maplocationmodel.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "profile-widget/profileview.h"
|
||||||
#include "stats/statsview.h"
|
#include "stats/statsview.h"
|
||||||
#include "core/devicedetails.h"
|
#include "core/devicedetails.h"
|
||||||
#include "core/globals.h"
|
#include "core/globals.h"
|
||||||
@ -26,7 +27,6 @@
|
|||||||
#include "qt-models/divesummarymodel.h"
|
#include "qt-models/divesummarymodel.h"
|
||||||
#include "qt-models/messagehandlermodel.h"
|
#include "qt-models/messagehandlermodel.h"
|
||||||
#include "qt-models/mobilelistmodel.h"
|
#include "qt-models/mobilelistmodel.h"
|
||||||
#include "profile-widget/qmlprofile.h"
|
|
||||||
#include "core/downloadfromdcthread.h"
|
#include "core/downloadfromdcthread.h"
|
||||||
#include "core/subsurfacestartup.h" // for testqml
|
#include "core/subsurfacestartup.h" // for testqml
|
||||||
#include "core/metrics.h"
|
#include "core/metrics.h"
|
||||||
@ -219,7 +219,6 @@ static void register_qml_types(QQmlEngine *engine)
|
|||||||
#ifdef SUBSURFACE_MOBILE
|
#ifdef SUBSURFACE_MOBILE
|
||||||
register_qml_type<QMLManager>("QMLManager");
|
register_qml_type<QMLManager>("QMLManager");
|
||||||
register_qml_type<StatsManager>("StatsManager");
|
register_qml_type<StatsManager>("StatsManager");
|
||||||
register_qml_type<QMLProfile>("QMLProfile");
|
|
||||||
register_qml_type<DiveImportedModel>("DCImportModel");
|
register_qml_type<DiveImportedModel>("DCImportModel");
|
||||||
register_qml_type<DiveSummaryModel>("DiveSummaryModel");
|
register_qml_type<DiveSummaryModel>("DiveSummaryModel");
|
||||||
register_qml_type<ChartListModel>("ChartListModel");
|
register_qml_type<ChartListModel>("ChartListModel");
|
||||||
@ -229,5 +228,6 @@ static void register_qml_types(QQmlEngine *engine)
|
|||||||
register_qml_type<MapWidgetHelper>("MapWidgetHelper");
|
register_qml_type<MapWidgetHelper>("MapWidgetHelper");
|
||||||
register_qml_type<MapLocationModel>("MapLocationModel");
|
register_qml_type<MapLocationModel>("MapLocationModel");
|
||||||
#endif
|
#endif
|
||||||
|
register_qml_type<ProfileView>("ProfileView");
|
||||||
register_qml_type<StatsView>("StatsView");
|
register_qml_type<StatsView>("StatsView");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,8 @@ void TestProfile::init()
|
|||||||
QCoreApplication::setOrganizationName("Subsurface");
|
QCoreApplication::setOrganizationName("Subsurface");
|
||||||
QCoreApplication::setOrganizationDomain("subsurface.hohndel.org");
|
QCoreApplication::setOrganizationDomain("subsurface.hohndel.org");
|
||||||
QCoreApplication::setApplicationName("Subsurface");
|
QCoreApplication::setApplicationName("Subsurface");
|
||||||
|
|
||||||
|
setlocale(LC_ALL, "C");
|
||||||
}
|
}
|
||||||
void TestProfile::testProfileExport()
|
void TestProfile::testProfileExport()
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user