mirror of
https://github.com/subsurface/subsurface.git
synced 2024-12-11 19:41:30 +00:00
2d8e343221
This has become a bit of a catch-all overhaul of a large portion of the planner - I started out wanting to improve the CCR mode, but then as I started pulling all the other threads that needed addressing started to come with it. Improve how the gas selection is handled when planning dives in CCR mode, by making the type (OC / CCR) of segments dependent on the gas use type that was set for the selected gas. Add a preference to allow the user to chose to use OC gases as diluent, in a similar fashion to the original implementation. Hide gases that cannot be used in the currently selected dive mode in all drop downs. Include usage type in gas names if this is needed. Hide columns and disable elements in the 'Dive planner points' table if they can they can not be edited in the curently selected dive mode. Visually identify gases and usage types that are not appropriate for the currently selected dive mode. Move the 'Dive mode' selection to the top of the planner view, to accommodate the fact that this is a property of the dive and not a planner setting. Show a warning instead of the dive plan if the plan contains gases that are not usable in the selected dive mode. Fix the data entry for the setpoint in the 'Dive planner points' table. Fix problems with enabling / disabling planner settings when switching between dive modes. Refactor some names to make them more appropriate for their current usage. One point that is still open is to hide gas usage graphs in the planner profile if the gas isn't used for OC, as there is no way to meaningfully interpolate such usage. Signed-off-by: Michael Keller <github@ike.ch>
398 lines
13 KiB
C++
398 lines
13 KiB
C++
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include "profilewidget.h"
|
|
#include "profile-widget/profilewidget2.h"
|
|
#include "commands/command.h"
|
|
#include "core/color.h"
|
|
#include "core/event.h"
|
|
#include "core/sample.h"
|
|
#include "core/selection.h"
|
|
#include "core/settings/qPrefTechnicalDetails.h"
|
|
#include "core/settings/qPrefPartialPressureGas.h"
|
|
#include "core/subsurface-string.h"
|
|
#include "qt-models/diveplannermodel.h"
|
|
|
|
#include <QToolBar>
|
|
#include <QHBoxLayout>
|
|
#include <QStackedWidget>
|
|
#include <QLabel>
|
|
|
|
// A resizing display of the Subsurface logo when no dive is shown
|
|
class EmptyView : public QLabel {
|
|
public:
|
|
EmptyView(QWidget *parent = nullptr);
|
|
~EmptyView();
|
|
private:
|
|
QPixmap logo;
|
|
void update();
|
|
void resizeEvent(QResizeEvent *) override;
|
|
};
|
|
|
|
EmptyView::EmptyView(QWidget *parent) : QLabel(parent),
|
|
logo(":poster-icon")
|
|
{
|
|
QPalette pal;
|
|
pal.setColor(QPalette::Window, getColor(::BACKGROUND));
|
|
setAutoFillBackground(true);
|
|
setPalette(pal);
|
|
setMinimumSize(1,1);
|
|
setAlignment(Qt::AlignHCenter);
|
|
update();
|
|
}
|
|
|
|
EmptyView::~EmptyView()
|
|
{
|
|
}
|
|
|
|
void EmptyView::update()
|
|
{
|
|
setPixmap(logo.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
}
|
|
|
|
void EmptyView::resizeEvent(QResizeEvent *)
|
|
{
|
|
update();
|
|
}
|
|
|
|
ProfileWidget::ProfileWidget() : d(nullptr), dc(0), placingCommand(false)
|
|
{
|
|
ui.setupUi(this);
|
|
|
|
// what is a sane order for those icons? we should have the ones the user is
|
|
// most likely to want towards the top so they are always visible
|
|
// and the ones that someone likely sets and then never touches again towards the bottom
|
|
toolbarActions = { ui.profInfobox, // show / hide the infobox
|
|
ui.profCalcCeiling, ui.profCalcAllTissues, // various ceilings
|
|
ui.profIncrement3m, ui.profDcCeiling,
|
|
ui.profPhe, ui.profPn2, ui.profPO2, // partial pressure graphs
|
|
ui.profRuler, ui.profScaled, // measuring and scaling
|
|
ui.profTogglePicture, ui.profTankbar,
|
|
ui.profMod, ui.profDeco, ui.profNdl_tts, // various values that a user is either interested in or not
|
|
ui.profEad, ui.profSAC,
|
|
ui.profHR, // very few dive computers support this
|
|
ui.profTissues }; // maybe less frequently used
|
|
|
|
emptyView.reset(new EmptyView);
|
|
|
|
view.reset(new ProfileWidget2(DivePlannerPointsModel::instance(), 1.0, this));
|
|
QToolBar *toolBar = new QToolBar(this);
|
|
for (QAction *a: toolbarActions)
|
|
toolBar->addAction(a);
|
|
toolBar->setOrientation(Qt::Vertical);
|
|
toolBar->setIconSize(QSize(24, 24));
|
|
|
|
stack = new QStackedWidget(this);
|
|
stack->addWidget(emptyView.get());
|
|
stack->addWidget(view.get());
|
|
|
|
QHBoxLayout *layout = new QHBoxLayout(this);
|
|
layout->setSpacing(0);
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
layout->setMargin(0);
|
|
#endif
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
layout->addWidget(toolBar);
|
|
layout->addWidget(stack);
|
|
setLayout(layout);
|
|
|
|
// Toolbar Connections related to the Profile Update
|
|
auto tec = qPrefTechnicalDetails::instance();
|
|
connect(ui.profCalcAllTissues, &QAction::triggered, tec, &qPrefTechnicalDetails::set_calcalltissues);
|
|
connect(ui.profCalcCeiling, &QAction::triggered, tec, &qPrefTechnicalDetails::set_calcceiling);
|
|
connect(ui.profDcCeiling, &QAction::triggered, tec, &qPrefTechnicalDetails::set_dcceiling);
|
|
connect(ui.profEad, &QAction::triggered, tec, &qPrefTechnicalDetails::set_ead);
|
|
connect(ui.profIncrement3m, &QAction::triggered, tec, &qPrefTechnicalDetails::set_calcceiling3m);
|
|
connect(ui.profMod, &QAction::triggered, tec, &qPrefTechnicalDetails::set_mod);
|
|
connect(ui.profNdl_tts, &QAction::triggered, tec, &qPrefTechnicalDetails::set_calcndltts);
|
|
connect(ui.profDeco, &QAction::triggered, tec, &qPrefTechnicalDetails::set_decoinfo);
|
|
connect(ui.profHR, &QAction::triggered, tec, &qPrefTechnicalDetails::set_hrgraph);
|
|
connect(ui.profRuler, &QAction::triggered, tec, &qPrefTechnicalDetails::set_rulergraph);
|
|
connect(ui.profSAC, &QAction::triggered, tec, &qPrefTechnicalDetails::set_show_sac);
|
|
connect(ui.profScaled, &QAction::triggered, tec, &qPrefTechnicalDetails::set_zoomed_plot);
|
|
connect(ui.profTogglePicture, &QAction::triggered, tec, &qPrefTechnicalDetails::set_show_pictures_in_profile);
|
|
connect(ui.profTankbar, &QAction::triggered, tec, &qPrefTechnicalDetails::set_tankbar);
|
|
connect(ui.profTissues, &QAction::triggered, tec, &qPrefTechnicalDetails::set_percentagegraph);
|
|
connect(ui.profInfobox, &QAction::triggered, tec, &qPrefTechnicalDetails::set_infobox);
|
|
|
|
connect(ui.profTissues, &QAction::triggered, this, &ProfileWidget::unsetProfHR);
|
|
connect(ui.profHR, &QAction::triggered, this, &ProfileWidget::unsetProfTissues);
|
|
|
|
auto pp_gas = qPrefPartialPressureGas::instance();
|
|
connect(ui.profPhe, &QAction::triggered, pp_gas, &qPrefPartialPressureGas::set_phe);
|
|
connect(ui.profPn2, &QAction::triggered, pp_gas, &qPrefPartialPressureGas::set_pn2);
|
|
connect(ui.profPO2, &QAction::triggered, pp_gas, &qPrefPartialPressureGas::set_po2);
|
|
|
|
connect(&diveListNotifier, &DiveListNotifier::divesChanged, this, &ProfileWidget::divesChanged);
|
|
connect(&diveListNotifier, &DiveListNotifier::settingsChanged, view.get(), &ProfileWidget2::settingsChanged);
|
|
connect(&diveListNotifier, &DiveListNotifier::cylinderAdded, this, &ProfileWidget::cylindersChanged);
|
|
connect(&diveListNotifier, &DiveListNotifier::cylinderRemoved, this, &ProfileWidget::cylindersChanged);
|
|
connect(&diveListNotifier, &DiveListNotifier::cylinderEdited, this, &ProfileWidget::cylindersChanged);
|
|
connect(view.get(), &ProfileWidget2::stopAdded, this, &ProfileWidget::stopAdded);
|
|
connect(view.get(), &ProfileWidget2::stopRemoved, this, &ProfileWidget::stopRemoved);
|
|
connect(view.get(), &ProfileWidget2::stopMoved, this, &ProfileWidget::stopMoved);
|
|
connect(view.get(), &ProfileWidget2::stopEdited, this, &ProfileWidget::stopEdited);
|
|
|
|
ui.profCalcAllTissues->setChecked(qPrefTechnicalDetails::calcalltissues());
|
|
ui.profCalcCeiling->setChecked(qPrefTechnicalDetails::calcceiling());
|
|
ui.profDcCeiling->setChecked(qPrefTechnicalDetails::dcceiling());
|
|
ui.profEad->setChecked(qPrefTechnicalDetails::ead());
|
|
ui.profIncrement3m->setChecked(qPrefTechnicalDetails::calcceiling3m());
|
|
ui.profMod->setChecked(qPrefTechnicalDetails::mod());
|
|
ui.profNdl_tts->setChecked(qPrefTechnicalDetails::calcndltts());
|
|
ui.profDeco->setChecked(qPrefTechnicalDetails::decoinfo());
|
|
ui.profPhe->setChecked(pp_gas->phe());
|
|
ui.profPn2->setChecked(pp_gas->pn2());
|
|
ui.profPO2->setChecked(pp_gas->po2());
|
|
ui.profHR->setChecked(qPrefTechnicalDetails::hrgraph());
|
|
ui.profRuler->setChecked(qPrefTechnicalDetails::rulergraph());
|
|
ui.profSAC->setChecked(qPrefTechnicalDetails::show_sac());
|
|
ui.profTogglePicture->setChecked(qPrefTechnicalDetails::show_pictures_in_profile());
|
|
ui.profTankbar->setChecked(qPrefTechnicalDetails::tankbar());
|
|
ui.profTissues->setChecked(qPrefTechnicalDetails::percentagegraph());
|
|
ui.profScaled->setChecked(qPrefTechnicalDetails::zoomed_plot());
|
|
ui.profInfobox->setChecked(qPrefTechnicalDetails::infobox());
|
|
}
|
|
|
|
ProfileWidget::~ProfileWidget()
|
|
{
|
|
}
|
|
|
|
void ProfileWidget::setEnabledToolbar(bool enabled)
|
|
{
|
|
for (QAction *b: toolbarActions)
|
|
b->setEnabled(enabled);
|
|
}
|
|
|
|
void ProfileWidget::setDive(const struct dive *d, int dcNr)
|
|
{
|
|
stack->setCurrentIndex(1); // show profile
|
|
|
|
bool freeDiveMode = d->get_dc(dcNr)->divemode == FREEDIVE;
|
|
ui.profCalcCeiling->setDisabled(freeDiveMode);
|
|
ui.profCalcCeiling->setDisabled(freeDiveMode);
|
|
ui.profCalcAllTissues ->setDisabled(freeDiveMode);
|
|
ui.profIncrement3m->setDisabled(freeDiveMode);
|
|
ui.profDcCeiling->setDisabled(freeDiveMode);
|
|
ui.profPhe->setDisabled(freeDiveMode);
|
|
ui.profPn2->setDisabled(freeDiveMode); //TODO is the same as scuba?
|
|
ui.profPO2->setDisabled(freeDiveMode); //TODO is the same as scuba?
|
|
ui.profTankbar->setDisabled(freeDiveMode);
|
|
ui.profMod->setDisabled(freeDiveMode);
|
|
ui.profNdl_tts->setDisabled(freeDiveMode);
|
|
ui.profDeco->setDisabled(freeDiveMode);
|
|
ui.profEad->setDisabled(freeDiveMode);
|
|
ui.profSAC->setDisabled(freeDiveMode);
|
|
ui.profTissues->setDisabled(freeDiveMode);
|
|
|
|
ui.profRuler->setDisabled(false);
|
|
ui.profScaled->setDisabled(false); // measuring and scaling
|
|
ui.profTogglePicture->setDisabled(false);
|
|
ui.profHR->setDisabled(false);
|
|
ui.profInfobox->setDisabled(false);
|
|
}
|
|
|
|
void ProfileWidget::plotCurrentDive()
|
|
{
|
|
plotDive(d, dc);
|
|
}
|
|
|
|
void ProfileWidget::plotDive(dive *dIn, int dcIn)
|
|
{
|
|
bool endEditMode = false;
|
|
if (editedDive && (dIn != d || dcIn != dc))
|
|
endEditMode = true;
|
|
|
|
d = dIn;
|
|
|
|
if (dcIn >= 0)
|
|
dc = dcIn;
|
|
|
|
// The following is valid because number_of_computers is always at least 1.
|
|
if (d)
|
|
dc = std::min(dc, d->number_of_computers() - 1);
|
|
|
|
// Exit edit mode if the dive changed
|
|
if (endEditMode)
|
|
exitEditMode();
|
|
|
|
// If this is a manually added dive and we are not in the planner
|
|
// or already editing the dive, switch to edit mode.
|
|
if (d && !editedDive &&
|
|
DivePlannerPointsModel::instance()->currentMode() == DivePlannerPointsModel::NOTHING) {
|
|
struct divecomputer *comp = d->get_dc(dc);
|
|
if (comp && is_dc_manually_added_dive(comp) && !comp->samples.empty() && comp->samples.size() <= 50)
|
|
editDive();
|
|
}
|
|
|
|
setEnabledToolbar(d != nullptr);
|
|
if (editedDive) {
|
|
view->plotDive(editedDive.get(), dc);
|
|
setDive(editedDive.get(), dc);
|
|
} else if (d) {
|
|
view->setProfileState(d, dc);
|
|
view->resetZoom(); // when switching dive, reset the zoomLevel
|
|
view->plotDive(d, dc);
|
|
setDive(d, dc);
|
|
} else {
|
|
view->clear();
|
|
stack->setCurrentIndex(0);
|
|
}
|
|
}
|
|
|
|
void ProfileWidget::nextDC()
|
|
{
|
|
rotateDC(1);
|
|
}
|
|
|
|
void ProfileWidget::prevDC()
|
|
{
|
|
rotateDC(-1);
|
|
}
|
|
|
|
void ProfileWidget::rotateDC(int dir)
|
|
{
|
|
if (!d)
|
|
return;
|
|
int numDC = d->number_of_computers();
|
|
int newDC = (dc + dir) % numDC;
|
|
if (newDC < 0)
|
|
newDC += numDC;
|
|
if (newDC == dc)
|
|
return;
|
|
|
|
plotDive(d, newDC);
|
|
}
|
|
|
|
void ProfileWidget::divesChanged(const QVector<dive *> &dives, DiveField field)
|
|
{
|
|
// If the current dive is not in list of changed dives, do nothing.
|
|
// Also, if we are currently placing a command, don't do anything.
|
|
// Note that we cannot use Command::placingCommand(), because placing
|
|
// a depth or time change on the maintab requires an update.
|
|
if (!d || !dives.contains(d) || !(field.duration || field.depth) || placingCommand)
|
|
return;
|
|
|
|
// If we're editing the current dive and not currently
|
|
// placing command, we have to update the edited dive.
|
|
if (editedDive) {
|
|
copy_dive(d, editedDive.get());
|
|
// TODO: Holy moly that function sends too many signals. Fix it!
|
|
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
|
|
}
|
|
|
|
// Only if duration or depth changed, the profile needs to be replotted.
|
|
if (field.duration || field.depth)
|
|
plotCurrentDive();
|
|
}
|
|
|
|
void ProfileWidget::cylindersChanged(struct dive *changed, int pos)
|
|
{
|
|
// If the current dive is not in list of changed dives, do nothing.
|
|
// Only if duration or depth changed, the profile needs to be replotted.
|
|
// Also, if we are currently placing a command, don't do anything.
|
|
// Note that we cannot use Command::placingCommand(), because placing
|
|
// a depth or time change on the maintab requires an update.
|
|
if (!d || changed != d || !editedDive)
|
|
return;
|
|
|
|
// If we're editing the current dive we have to update the
|
|
// cylinders of the edited dive.
|
|
if (editedDive) {
|
|
editedDive.get()->cylinders = d->cylinders;
|
|
// TODO: Holy moly that function sends too many signals. Fix it!
|
|
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
|
|
}
|
|
}
|
|
|
|
void ProfileWidget::setPlanState(const struct dive *d, int dcNr)
|
|
{
|
|
dc = dcNr;
|
|
view->setPlanState(d, dcNr);
|
|
setDive(d, dcNr);
|
|
}
|
|
|
|
void ProfileWidget::unsetProfHR()
|
|
{
|
|
ui.profHR->setChecked(false);
|
|
qPrefTechnicalDetails::set_hrgraph(false);
|
|
}
|
|
|
|
void ProfileWidget::unsetProfTissues()
|
|
{
|
|
ui.profTissues->setChecked(false);
|
|
qPrefTechnicalDetails::set_percentagegraph(false);
|
|
}
|
|
|
|
void ProfileWidget::editDive()
|
|
{
|
|
editedDive = std::make_unique<dive>();
|
|
copy_dive(d, editedDive.get()); // Work on a copy of the dive
|
|
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::EDIT);
|
|
DivePlannerPointsModel::instance()->loadFromDive(editedDive.get(), dc);
|
|
view->setEditState(editedDive.get(), dc);
|
|
}
|
|
|
|
void ProfileWidget::exitEditMode()
|
|
{
|
|
if (!editedDive)
|
|
return;
|
|
|
|
DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::NOTHING);
|
|
view->setProfileState(d, dc); // switch back to original dive before erasing the copy.
|
|
editedDive.reset();
|
|
}
|
|
|
|
// Update depths of edited dive
|
|
static void calcDepth(dive &d, int dcNr)
|
|
{
|
|
d.maxdepth.mm = d.get_dc(dcNr)->maxdepth.mm = 0;
|
|
divelog.dives.fixup_dive(d);
|
|
}
|
|
|
|
// Silly RAII-variable setter class: reset variable when going out of scope.
|
|
template <typename T>
|
|
struct Setter {
|
|
T &var, old;
|
|
Setter(T &var, T value) : var(var), old(var) {
|
|
var = value;
|
|
}
|
|
~Setter() {
|
|
var = old;
|
|
}
|
|
};
|
|
|
|
void ProfileWidget::stopAdded()
|
|
{
|
|
if (!editedDive)
|
|
return;
|
|
calcDepth(*editedDive, dc);
|
|
Setter s(placingCommand, true);
|
|
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::ADD, 0);
|
|
}
|
|
|
|
void ProfileWidget::stopRemoved(int count)
|
|
{
|
|
if (!editedDive)
|
|
return;
|
|
calcDepth(*editedDive, dc);
|
|
Setter s(placingCommand, true);
|
|
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::REMOVE, count);
|
|
}
|
|
|
|
void ProfileWidget::stopMoved(int count)
|
|
{
|
|
if (!editedDive)
|
|
return;
|
|
calcDepth(*editedDive, dc);
|
|
Setter s(placingCommand, true);
|
|
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::MOVE, count);
|
|
}
|
|
|
|
void ProfileWidget::stopEdited()
|
|
{
|
|
if (!editedDive)
|
|
return;
|
|
|
|
Setter s(placingCommand, true);
|
|
Command::editProfile(editedDive.get(), dc, Command::EditProfileType::EDIT, 0);
|
|
}
|