diff options
author | Tomaz Canabrava <tomaz.canabrava@intel.com> | 2015-05-28 16:23:49 -0300 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2015-05-29 14:09:13 -0700 |
commit | f432b764e78ac3d66f5ab1bfc7c18fbdb75624e5 (patch) | |
tree | c2ec5c81660d22bf986968808bd4ecb7256623ae | |
parent | 6e4aa7d044a344527e61f17c2254851ba799c4bd (diff) | |
download | subsurface-f432b764e78ac3d66f5ab1bfc7c18fbdb75624e5.tar.gz |
Move DivePlannerModel and CylinderModel to qt-models
Still trying to make it easier for the Mobile Port:
This patch is a bit bigger than I hopped, but it was the smallest that I
could get.
A lot of TODO items where added where I broke the code because the current
implementation would break the QML implementtion on the designer. I'll
most probably fix those myself when I finish the transition to the models
to the new folder.
I only moved both models at once because there's an interdependency
between them (seems inevitable, tough, but I'll take a better look at it
later).
Signed-off-by: Tomaz Canabrava <tomaz.canabrava@intel.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | qt-models/cleanertablemodel.h | 9 | ||||
-rw-r--r-- | qt-models/cylindermodel.cpp | 370 | ||||
-rw-r--r-- | qt-models/cylindermodel.h | 49 | ||||
-rw-r--r-- | qt-models/diveplannermodel.cpp | 917 | ||||
-rw-r--r-- | qt-models/diveplannermodel.h | 117 | ||||
-rw-r--r-- | qt-models/models.cpp | 391 | ||||
-rw-r--r-- | qt-models/models.h | 43 | ||||
-rw-r--r-- | qt-ui/diveplanner.cpp | 933 | ||||
-rw-r--r-- | qt-ui/diveplanner.h | 111 | ||||
-rw-r--r-- | qt-ui/maintab.cpp | 2 | ||||
-rw-r--r-- | qt-ui/mainwindow.cpp | 1 | ||||
-rw-r--r-- | qt-ui/modeldelegates.cpp | 1 | ||||
-rw-r--r-- | qt-ui/profile/diveprofileitem.cpp | 3 | ||||
-rw-r--r-- | qt-ui/profile/profilewidget2.cpp | 3 | ||||
-rw-r--r-- | qthelper.cpp | 12 | ||||
-rw-r--r-- | qthelper.h | 3 |
17 files changed, 1507 insertions, 1460 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index d6a1260e5..a599bb4d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -248,6 +248,8 @@ endif() # with the views. set(SUBSURFACE_MODELS_LIB_SRCS qt-models/cleanertablemodel.cpp + qt-models/cylindermodel.cpp + qt-models/diveplannermodel.cpp qt-models/models.cpp qt-models/filtermodels.cpp qt-models/completionmodels.cpp diff --git a/qt-models/cleanertablemodel.h b/qt-models/cleanertablemodel.h index a717d7032..a492685a6 100644 --- a/qt-models/cleanertablemodel.h +++ b/qt-models/cleanertablemodel.h @@ -19,9 +19,18 @@ public: protected: void setHeaderDataStrings(const QStringList &headers); +signals: + + /* instead of using QMessageBox directly, wire a QWidget to this signal and display the result. + * This is because the QModels will be used from the Mobile version and the desktop version. */ + void warningMessage(const QString& title, const QString& message); private: QStringList headers; }; +/* Has the string value changed */ +#define CHANGED() \ + (vString = value.toString()) != data(index, role).toString() + #endif diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp new file mode 100644 index 000000000..70b4f5f30 --- /dev/null +++ b/qt-models/cylindermodel.cpp @@ -0,0 +1,370 @@ +#include "cylindermodel.h" +#include "models.h" +#include "helpers.h" +#include "dive.h" +#include "color.h" +#include "diveplannermodel.h" +#include "gettextfromc.h" + +CylindersModel::CylindersModel(QObject *parent) : changed(false), + rows(0) +{ + // enum {REMOVE, TYPE, SIZE, WORKINGPRESS, START, END, O2, HE, DEPTH}; + setHeaderDataStrings(QStringList() << "" << tr("Type") << tr("Size") << tr("Work press.") << tr("Start press.") << tr("End press.") << tr("O₂%") << tr("He%") + << tr("Switch at") << tr("Use")); + +} + +CylindersModel *CylindersModel::instance() +{ + + static QScopedPointer<CylindersModel> self(new CylindersModel()); + return self.data(); +} + +static QVariant percent_string(fraction_t fraction) +{ + int permille = fraction.permille; + + if (!permille) + return QVariant(); + return QString("%1%").arg(permille / 10.0, 0, 'f', 1); +} + +QVariant CylindersModel::data(const QModelIndex &index, int role) const +{ + QVariant ret; + + if (!index.isValid() || index.row() >= MAX_CYLINDERS) + return ret; + + cylinder_t *cyl = &displayed_dive.cylinder[index.row()]; + switch (role) { + case Qt::BackgroundRole: { + switch (index.column()) { + // mark the cylinder start / end pressure in red if the values + // seem implausible + case START: + case END: + if ((cyl->start.mbar && !cyl->end.mbar) || + (cyl->end.mbar && cyl->start.mbar <= cyl->end.mbar)) + ret = REDORANGE1_HIGH_TRANS; + break; + } + break; + } + case Qt::FontRole: { + QFont font = defaultModelFont(); + switch (index.column()) { + case START: + font.setItalic(!cyl->start.mbar); + break; + case END: + font.setItalic(!cyl->end.mbar); + break; + } + ret = font; + break; + } + case Qt::TextAlignmentRole: + ret = Qt::AlignCenter; + break; + case Qt::DisplayRole: + case Qt::EditRole: + switch (index.column()) { + case TYPE: + ret = QString(cyl->type.description); + break; + case SIZE: + if (cyl->type.size.mliter) + ret = get_volume_string(cyl->type.size, true, cyl->type.workingpressure.mbar); + break; + case WORKINGPRESS: + if (cyl->type.workingpressure.mbar) + ret = get_pressure_string(cyl->type.workingpressure, true); + break; + case START: + if (cyl->start.mbar) + ret = get_pressure_string(cyl->start, true); + else if (cyl->sample_start.mbar) + ret = get_pressure_string(cyl->sample_start, true); + break; + case END: + if (cyl->end.mbar) + ret = get_pressure_string(cyl->end, true); + else if (cyl->sample_end.mbar) + ret = get_pressure_string(cyl->sample_end, true); + break; + case O2: + ret = percent_string(cyl->gasmix.o2); + break; + case HE: + ret = percent_string(cyl->gasmix.he); + break; + case DEPTH: + ret = get_depth_string(cyl->depth, true); + break; + case USE: + ret = gettextFromC::instance()->trGettext(cylinderuse_text[cyl->cylinder_use]); + break; + } + break; + case Qt::DecorationRole: + if (index.column() == REMOVE) + ret = trashIcon(); + break; + case Qt::SizeHintRole: + if (index.column() == REMOVE) + ret = trashIcon().size(); + break; + + case Qt::ToolTipRole: + if (index.column() == REMOVE) + ret = tr("Clicking here will remove this cylinder."); + break; + } + + return ret; +} + +cylinder_t *CylindersModel::cylinderAt(const QModelIndex &index) +{ + return &displayed_dive.cylinder[index.row()]; +} + +// this is our magic 'pass data in' function that allows the delegate to get +// the data here without silly unit conversions; +// so we only implement the two columns we care about +void CylindersModel::passInData(const QModelIndex &index, const QVariant &value) +{ + cylinder_t *cyl = cylinderAt(index); + switch (index.column()) { + case SIZE: + if (cyl->type.size.mliter != value.toInt()) { + cyl->type.size.mliter = value.toInt(); + dataChanged(index, index); + } + break; + case WORKINGPRESS: + if (cyl->type.workingpressure.mbar != value.toInt()) { + cyl->type.workingpressure.mbar = value.toInt(); + dataChanged(index, index); + } + break; + } +} + +bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + QString vString; + bool addDiveMode = DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING; + if (addDiveMode) + DivePlannerPointsModel::instance()->rememberTanks(); + + cylinder_t *cyl = cylinderAt(index); + switch (index.column()) { + case TYPE: + if (!value.isNull()) { + QByteArray ba = value.toByteArray(); + const char *text = ba.constData(); + if (!cyl->type.description || strcmp(cyl->type.description, text)) { + cyl->type.description = strdup(text); + changed = true; + } + } + break; + case SIZE: + if (CHANGED()) { + TankInfoModel *tanks = TankInfoModel::instance(); + QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl->type.description); + + cyl->type.size = string_to_volume(vString.toUtf8().data(), cyl->type.workingpressure); + mark_divelist_changed(true); + if (!matches.isEmpty()) + tanks->setData(tanks->index(matches.first().row(), TankInfoModel::ML), cyl->type.size.mliter); + changed = true; + } + break; + case WORKINGPRESS: + if (CHANGED()) { + TankInfoModel *tanks = TankInfoModel::instance(); + QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl->type.description); + cyl->type.workingpressure = string_to_pressure(vString.toUtf8().data()); + if (!matches.isEmpty()) + tanks->setData(tanks->index(matches.first().row(), TankInfoModel::BAR), cyl->type.workingpressure.mbar / 1000.0); + changed = true; + } + break; + case START: + if (CHANGED()) { + cyl->start = string_to_pressure(vString.toUtf8().data()); + changed = true; + } + break; + case END: + if (CHANGED()) { + //&& (!cyl->start.mbar || string_to_pressure(vString.toUtf8().data()).mbar <= cyl->start.mbar)) { + cyl->end = string_to_pressure(vString.toUtf8().data()); + changed = true; + } + break; + case O2: + if (CHANGED()) { + cyl->gasmix.o2 = string_to_fraction(vString.toUtf8().data()); + pressure_t modpO2; + if (displayed_dive.dc.divemode == PSCR) + modpO2.mbar = prefs.decopo2 + (1000 - get_o2(&cyl->gasmix)) * SURFACE_PRESSURE * + prefs.o2consumption / prefs.decosac / prefs.pscr_ratio; + else + modpO2.mbar = prefs.decopo2; + cyl->depth = gas_mod(&cyl->gasmix, modpO2, M_OR_FT(3, 10)); + changed = true; + } + break; + case HE: + if (CHANGED()) { + cyl->gasmix.he = string_to_fraction(vString.toUtf8().data()); + changed = true; + } + break; + case DEPTH: + if (CHANGED()) { + cyl->depth = string_to_depth(vString.toUtf8().data()); + changed = true; + } + break; + case USE: + if (CHANGED()) { + cyl->cylinder_use = (enum cylinderuse)vString.toInt(); + changed = true; + } + break; + } + if (addDiveMode) + DivePlannerPointsModel::instance()->tanksUpdated(); + dataChanged(index, index); + return true; +} + +int CylindersModel::rowCount(const QModelIndex &parent) const +{ + return rows; +} + +void CylindersModel::add() +{ + if (rows >= MAX_CYLINDERS) { + return; + } + + int row = rows; + fill_default_cylinder(&displayed_dive.cylinder[row]); + displayed_dive.cylinder[row].manually_added = true; + beginInsertRows(QModelIndex(), row, row); + rows++; + changed = true; + endInsertRows(); +} + +void CylindersModel::clear() +{ + if (rows > 0) { + beginRemoveRows(QModelIndex(), 0, rows - 1); + endRemoveRows(); + } +} + +void CylindersModel::updateDive() +{ + clear(); + rows = 0; + for (int i = 0; i < MAX_CYLINDERS; i++) { + if (!cylinder_none(&displayed_dive.cylinder[i]) && + (prefs.display_unused_tanks || + is_cylinder_used(&displayed_dive, i) || + displayed_dive.cylinder[i].manually_added)) + rows = i + 1; + } + if (rows > 0) { + beginInsertRows(QModelIndex(), 0, rows - 1); + endInsertRows(); + } +} + +void CylindersModel::copyFromDive(dive *d) +{ + if (!d) + return; + rows = 0; + for (int i = 0; i < MAX_CYLINDERS; i++) { + if (!cylinder_none(&d->cylinder[i]) && + (is_cylinder_used(d, i) || prefs.display_unused_tanks)) { + rows = i + 1; + } + } + if (rows > 0) { + beginInsertRows(QModelIndex(), 0, rows - 1); + endInsertRows(); + } +} + +Qt::ItemFlags CylindersModel::flags(const QModelIndex &index) const +{ + if (index.column() == REMOVE) + return Qt::ItemIsEnabled; + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; +} + +void CylindersModel::remove(const QModelIndex &index) +{ + int mapping[MAX_CYLINDERS]; + if (index.column() != REMOVE) { + return; + } + int same_gas = -1; + cylinder_t *cyl = &displayed_dive.cylinder[index.row()]; + struct gasmix *mygas = &cyl->gasmix; + for (int i = 0; i < MAX_CYLINDERS; i++) { + mapping[i] = i; + if (i == index.row() || cylinder_none(&displayed_dive.cylinder[i])) + continue; + struct gasmix *gas2 = &displayed_dive.cylinder[i].gasmix; + if (gasmix_distance(mygas, gas2) == 0) + same_gas = i; + } + if (same_gas == -1 && + ((DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING && + DivePlannerPointsModel::instance()->tankInUse(cyl->gasmix)) || + (DivePlannerPointsModel::instance()->currentMode() == DivePlannerPointsModel::NOTHING && + is_cylinder_used(&displayed_dive, index.row())))) { + emit warningMessage(TITLE_OR_TEXT( + tr("Cylinder cannot be removed"), + tr("This gas is in use. Only cylinders that are not used in the dive can be removed."))); + return; + } + beginRemoveRows(QModelIndex(), index.row(), index.row()); // yah, know, ugly. + rows--; + if (index.row() == 0) { + // first gas - we need to make sure that the same gas ends up + // as first gas + memmove(cyl, &displayed_dive.cylinder[same_gas], sizeof(*cyl)); + remove_cylinder(&displayed_dive, same_gas); + mapping[same_gas] = 0; + for (int i = same_gas + 1; i < MAX_CYLINDERS; i++) + mapping[i] = i - 1; + } else { + remove_cylinder(&displayed_dive, index.row()); + if (same_gas > index.row()) + same_gas--; + mapping[index.row()] = same_gas; + for (int i = index.row() + 1; i < MAX_CYLINDERS; i++) + mapping[i] = i - 1; + } + changed = true; + endRemoveRows(); + struct divecomputer *dc = &displayed_dive.dc; + while (dc) { + dc_cylinder_renumber(&displayed_dive, dc, mapping); + dc = dc->next; + } +} diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h new file mode 100644 index 000000000..9556bcc02 --- /dev/null +++ b/qt-models/cylindermodel.h @@ -0,0 +1,49 @@ +#ifndef CYLINDERMODEL_H +#define CYLINDERMODEL_H + +#include "cleanertablemodel.h" +#include "dive.h" + +/* Encapsulation of the Cylinder Model, that presents the + * Current cylinders that are used on a dive. */ +class CylindersModel : public CleanerTableModel { + Q_OBJECT +public: + enum Column { + REMOVE, + TYPE, + SIZE, + WORKINGPRESS, + START, + END, + O2, + HE, + DEPTH, + USE, + COLUMNS + }; + + explicit CylindersModel(QObject *parent = 0); + static CylindersModel *instance(); + /*reimp*/ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex &parent = QModelIndex()) const; + /*reimp*/ Qt::ItemFlags flags(const QModelIndex &index) const; + /*reimp*/ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + void passInData(const QModelIndex &index, const QVariant &value); + void add(); + void clear(); + void updateDive(); + void copyFromDive(struct dive *d); + cylinder_t *cylinderAt(const QModelIndex &index); + bool changed; + +public +slots: + void remove(const QModelIndex &index); + +private: + int rows; +}; + +#endif diff --git a/qt-models/diveplannermodel.cpp b/qt-models/diveplannermodel.cpp new file mode 100644 index 000000000..5dd43c787 --- /dev/null +++ b/qt-models/diveplannermodel.cpp @@ -0,0 +1,917 @@ +#include "diveplannermodel.h" +#include "dive.h" +#include "helpers.h" +#include "cylindermodel.h" +#include "planner.h" +#include "models.h" + +/* TODO: Port this to CleanerTableModel to remove a bit of boilerplate and + * use the signal warningMessage() to communicate errors to the MainWindow. + */ +void DivePlannerPointsModel::removeSelectedPoints(const QVector<int> &rows) +{ + if (!rows.count()) + return; + int firstRow = rowCount() - rows.count(); + QVector<int> v2 = rows; + std::sort(v2.begin(), v2.end()); + + beginRemoveRows(QModelIndex(), firstRow, rowCount() - 1); + for (int i = v2.count() - 1; i >= 0; i--) { + divepoints.remove(v2[i]); + } + endRemoveRows(); +} + +void DivePlannerPointsModel::createSimpleDive() +{ + struct gasmix gas = { 0 }; + + // initialize the start time in the plan + diveplan.when = displayed_dive.when; + + if (isPlanner()) + // let's use the gas from the first cylinder + gas = displayed_dive.cylinder[0].gasmix; + + // If we're in drop_stone_mode, don't add a first point. + // It will be added implicit. + if (!prefs.drop_stone_mode) + addStop(M_OR_FT(15, 45), 1 * 60, &gas, 0, true); + + addStop(M_OR_FT(15, 45), 20 * 60, &gas, 0, true); + if (!isPlanner()) { + addStop(M_OR_FT(5, 15), 42 * 60, &gas, 0, true); + addStop(M_OR_FT(5, 15), 45 * 60, &gas, 0, true); + } +} + +void DivePlannerPointsModel::setupStartTime() +{ + // if the latest dive is in the future, then start an hour after it ends + // otherwise start an hour from now + startTime = QDateTime::currentDateTimeUtc().addSecs(3600 + gettimezoneoffset()); + if (dive_table.nr) { + struct dive *d = get_dive(dive_table.nr - 1); + time_t ends = d->when + d->duration.seconds; + time_t diff = ends - startTime.toTime_t(); + if (diff > 0) { + startTime = startTime.addSecs(diff + 3600); + } + } + emit startTimeChanged(startTime); +} + +void DivePlannerPointsModel::loadFromDive(dive *d) +{ + int depthsum = 0; + int samplecount = 0; + bool oldRec = recalc; + recalc = false; + CylindersModel::instance()->updateDive(); + duration_t lasttime = {}; + duration_t newtime = {}; + struct gasmix gas; + free_dps(&diveplan); + diveplan.when = d->when; + // is this a "new" dive where we marked manually entered samples? + // if yes then the first sample should be marked + // if it is we only add the manually entered samples as waypoints to the diveplan + // otherwise we have to add all of them + bool hasMarkedSamples = d->dc.sample[0].manually_entered; + // if this dive has more than 100 samples (so it is probably a logged dive), + // average samples so we end up with a total of 100 samples. + int plansamples = d->dc.samples <= 100 ? d->dc.samples : 100; + int j = 0; + for (int i = 0; i < plansamples - 1; i++) { + while (j * plansamples <= i * d->dc.samples) { + const sample &s = d->dc.sample[j]; + if (s.time.seconds != 0 && (!hasMarkedSamples || s.manually_entered)) { + depthsum += s.depth.mm; + ++samplecount; + newtime = s.time; + } + j++; + } + if (samplecount) { + get_gas_at_time(d, &d->dc, lasttime, &gas); + addStop(depthsum / samplecount, newtime.seconds, &gas, 0, true); + lasttime = newtime; + depthsum = 0; + samplecount = 0; + } + } + recalc = oldRec; + emitDataChanged(); +} + +// copy the tanks from the current dive, or the default cylinder +// or an unknown cylinder +// setup the cylinder widget accordingly +void DivePlannerPointsModel::setupCylinders() +{ + if (mode == PLAN && current_dive) { + // take the displayed cylinders from the selected dive as starting point + CylindersModel::instance()->copyFromDive(current_dive); + copy_cylinders(current_dive, &displayed_dive, !prefs.display_unused_tanks); + reset_cylinders(&displayed_dive, true); + return; + } + if (!same_string(prefs.default_cylinder, "")) { + fill_default_cylinder(&displayed_dive.cylinder[0]); + } else { + // roughly an AL80 + displayed_dive.cylinder[0].type.description = strdup(tr("unknown").toUtf8().constData()); + displayed_dive.cylinder[0].type.size.mliter = 11100; + displayed_dive.cylinder[0].type.workingpressure.mbar = 207000; + } + reset_cylinders(&displayed_dive, false); + CylindersModel::instance()->copyFromDive(&displayed_dive); +} + +QStringList &DivePlannerPointsModel::getGasList() +{ + static QStringList list; + list.clear(); + for (int i = 0; i < MAX_CYLINDERS; i++) { + cylinder_t *cyl = &displayed_dive.cylinder[i]; + if (cylinder_nodata(cyl)) + break; + list.push_back(get_gas_string(cyl->gasmix)); + } + return list; +} + +void DivePlannerPointsModel::removeDeco() +{ + bool oldrec = setRecalc(false); + QVector<int> computedPoints; + for (int i = 0; i < rowCount(); i++) + if (!at(i).entered) + computedPoints.push_back(i); + removeSelectedPoints(computedPoints); + setRecalc(oldrec); +} + +void DivePlannerPointsModel::addCylinder_clicked() +{ + CylindersModel::instance()->add(); +} + + + +void DivePlannerPointsModel::setPlanMode(Mode m) +{ + mode = m; + // the planner may reset our GF settings that are used to show deco + // reset them to what's in the preferences + if (m != PLAN) + set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); +} + +bool DivePlannerPointsModel::isPlanner() +{ + return mode == PLAN; +} + +/* When the planner adds deco stops to the model, adding those should not trigger a new deco calculation. + * We thus start the planner only when recalc is true. */ + +bool DivePlannerPointsModel::setRecalc(bool rec) +{ + bool old = recalc; + recalc = rec; + return old; +} + +bool DivePlannerPointsModel::recalcQ() +{ + return recalc; +} + +int DivePlannerPointsModel::columnCount(const QModelIndex &parent) const +{ + return COLUMNS; // to disable CCSETPOINT subtract one +} + +QVariant DivePlannerPointsModel::data(const QModelIndex &index, int role) const +{ + divedatapoint p = divepoints.at(index.row()); + if (role == Qt::DisplayRole || role == Qt::EditRole) { + switch (index.column()) { + case CCSETPOINT: + return (double)p.setpoint / 1000; + case DEPTH: + return (int) rint(get_depth_units(p.depth, NULL, NULL)); + case RUNTIME: + return p.time / 60; + case DURATION: + if (index.row()) + return (p.time - divepoints.at(index.row() - 1).time) / 60; + else + return p.time / 60; + case GAS: + return get_divepoint_gas_string(p); + } + } else if (role == Qt::DecorationRole) { + switch (index.column()) { + case REMOVE: + if (rowCount() > 1) + return p.entered ? trashIcon() : QVariant(); + } + } else if (role == Qt::SizeHintRole) { + switch (index.column()) { + case REMOVE: + if (rowCount() > 1) + return p.entered ? trashIcon().size() : QVariant(); + } + } else if (role == Qt::FontRole) { + if (divepoints.at(index.row()).entered) { + return defaultModelFont(); + } else { + QFont font = defaultModelFont(); + font.setBold(true); + return font; + } + } + return QVariant(); +} + +bool DivePlannerPointsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + struct gasmix gas = { 0 }; + int i, shift; + if (role == Qt::EditRole) { + divedatapoint &p = divepoints[index.row()]; + switch (index.column()) { + case DEPTH: + if (value.toInt() >= 0) + p.depth = units_to_depth(value.toInt()); + break; + case RUNTIME: + p.time = value.toInt() * 60; + break; + case DURATION: + i = index.row(); + if (i) + shift = divepoints[i].time - divepoints[i - 1].time - value.toInt() * 60; + else + shift = divepoints[i].time - value.toInt() * 60; + while (i < divepoints.size()) + divepoints[i++].time -= shift; + break; + case CCSETPOINT: { + int po2 = 0; + QByteArray gasv = value.toByteArray(); + if (validate_po2(gasv.data(), &po2)) + p.setpoint = po2; + } break; + case GAS: + QByteArray gasv = value.toByteArray(); + if (validate_gas(gasv.data(), &gas)) + p.gasmix = gas; + break; + } + editStop(index.row(), p); + } + return QAbstractItemModel::setData(index, value, role); +} + +void DivePlannerPointsModel::gaschange(const QModelIndex &index, QString newgas) +{ + int i = index.row(); + gasmix oldgas = divepoints[i].gasmix; + gasmix gas = { 0 }; + if (!validate_gas(newgas.toUtf8().data(), &gas)) + return; + while (i < rowCount() && gasmix_distance(&oldgas, &divepoints[i].gasmix) == 0) + divepoints[i++].gasmix = gas; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +QVariant DivePlannerPointsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + switch (section) { + case DEPTH: + return tr("Final depth"); + case RUNTIME: + return tr("Run time"); + case DURATION: + return tr("Duration"); + case GAS: + return tr("Used gas"); + case CCSETPOINT: + return tr("CC set point"); + } + } else if (role == Qt::FontRole) { + return defaultModelFont(); + } + return QVariant(); +} + +Qt::ItemFlags DivePlannerPointsModel::flags(const QModelIndex &index) const +{ + if (index.column() != REMOVE) + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; + else + return QAbstractItemModel::flags(index); +} + +int DivePlannerPointsModel::rowCount(const QModelIndex &parent) const +{ + return divepoints.count(); +} + +DivePlannerPointsModel::DivePlannerPointsModel(QObject *parent) : QAbstractTableModel(parent), + mode(NOTHING), + recalc(false), + tempGFHigh(100), + tempGFLow(100) +{ + memset(&diveplan, 0, sizeof(diveplan)); +} + +DivePlannerPointsModel *DivePlannerPointsModel::instance() +{ + static QScopedPointer<DivePlannerPointsModel> self(new DivePlannerPointsModel()); + return self.data(); +} + +void DivePlannerPointsModel::emitDataChanged() +{ + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setBottomSac(double sac) +{ + diveplan.bottomsac = units_to_sac(sac); + prefs.bottomsac = diveplan.bottomsac; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setDecoSac(double sac) +{ + diveplan.decosac = units_to_sac(sac); + prefs.decosac = diveplan.decosac; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setGFHigh(const int gfhigh) +{ + tempGFHigh = gfhigh; + // GFHigh <= 34 can cause infinite deco at 6m - don't trigger a recalculation + // for smaller GFHigh unless the user explicitly leaves the field + if (tempGFHigh > 34) + triggerGFHigh(); +} + +void DivePlannerPointsModel::triggerGFHigh() +{ + if (diveplan.gfhigh != tempGFHigh) { + diveplan.gfhigh = tempGFHigh; + emitDataChanged(); + } +} + +void DivePlannerPointsModel::setGFLow(const int ghflow) +{ + tempGFLow = ghflow; + triggerGFLow(); +} + +void DivePlannerPointsModel::setRebreatherMode(int mode) +{ + int i; + displayed_dive.dc.divemode = (dive_comp_type) mode; + for (i=0; i < rowCount(); i++) + divepoints[i].setpoint = mode == CCR ? prefs.defaultsetpoint : 0; + emitDataChanged(); +} + +void DivePlannerPointsModel::triggerGFLow() +{ + if (diveplan.gflow != tempGFLow) { + diveplan.gflow = tempGFLow; + emitDataChanged(); + } +} + +void DivePlannerPointsModel::setSurfacePressure(int pressure) +{ + diveplan.surface_pressure = pressure; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setSalinity(int salinity) +{ + diveplan.salinity = salinity; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +int DivePlannerPointsModel::getSurfacePressure() +{ + return diveplan.surface_pressure; +} + +void DivePlannerPointsModel::setLastStop6m(bool value) +{ + set_last_stop(value); + prefs.last_stop = value; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setVerbatim(bool value) +{ + set_verbatim(value); + prefs.verbatim_plan = value; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setDisplayRuntime(bool value) +{ + set_display_runtime(value); + prefs.display_runtime = value; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setDisplayDuration(bool value) +{ + set_display_duration(value); + prefs.display_duration = value; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setDisplayTransitions(bool value) +{ + set_display_transitions(value); + prefs.display_transitions = value; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setRecreationalMode(bool value) +{ + prefs.recreational_mode = value; + emit recreationChanged(value); + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS -1)); +} + +void DivePlannerPointsModel::setSafetyStop(bool value) +{ + prefs.safetystop = value; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS -1)); +} + +void DivePlannerPointsModel::setReserveGas(int reserve) +{ + prefs.reserve_gas = reserve * 1000; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setDropStoneMode(bool value) +{ + prefs.drop_stone_mode = value; + if (prefs.drop_stone_mode) { + /* Remove the first entry if we enable drop_stone_mode */ + if (rowCount() >= 2) { + beginRemoveRows(QModelIndex(), 0, 0); + divepoints.remove(0); + endRemoveRows(); + } + } else { + /* Add a first entry if we disable drop_stone_mode */ + beginInsertRows(QModelIndex(), 0, 0); + /* Copy the first current point */ + divedatapoint p = divepoints.at(0); + p.time = p.depth / prefs.descrate; + divepoints.push_front(p); + endInsertRows(); + } + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setStartDate(const QDate &date) +{ + startTime.setDate(date); + diveplan.when = startTime.toTime_t(); + displayed_dive.when = diveplan.when; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::setStartTime(const QTime &t) +{ + startTime.setTime(t); + diveplan.when = startTime.toTime_t(); + displayed_dive.when = diveplan.when; + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + + +bool divePointsLessThan(const divedatapoint &p1, const divedatapoint &p2) +{ + return p1.time <= p2.time; +} + +bool DivePlannerPointsModel::addGas(struct gasmix mix) +{ + sanitize_gasmix(&mix); + + for (int i = 0; i < MAX_CYLINDERS; i++) { + cylinder_t *cyl = &displayed_dive.cylinder[i]; + if (cylinder_nodata(cyl)) { + fill_default_cylinder(cyl); + cyl->gasmix = mix; + /* The depth to change to that gas is given by the depth where its pO₂ is 1.6 bar. + * The user should be able to change this depth manually. */ + pressure_t modpO2; + if (displayed_dive.dc.divemode == PSCR) + modpO2.mbar = prefs.decopo2 + (1000 - get_o2(&mix)) * SURFACE_PRESSURE * + prefs.o2consumption / prefs.decosac / prefs.pscr_ratio; + else + modpO2.mbar = prefs.decopo2; + cyl->depth = gas_mod(&mix, modpO2, M_OR_FT(3,10)); + + + + + // FIXME -- need to get rid of stagingDIve + // the following now uses displayed_dive !!!! + + + + CylindersModel::instance()->updateDive(); + return true; + } + if (!gasmix_distance(&cyl->gasmix, &mix)) + return true; + } + qDebug("too many gases"); + return false; +} + +int DivePlannerPointsModel::lastEnteredPoint() +{ + for (int i = divepoints.count() - 1; i >= 0; i--) + if (divepoints.at(i).entered) + return i; + return -1; +} + +int DivePlannerPointsModel::addStop(int milimeters, int seconds, gasmix *gas_in, int ccpoint, bool entered) +{ + struct gasmix air = { 0 }; + struct gasmix gas = { 0 }; + bool usePrevious = false; + if (gas_in) + gas = *gas_in; + else + usePrevious = true; + if (recalcQ()) + removeDeco(); + + int row = divepoints.count(); + if (seconds == 0 && milimeters == 0 && row != 0) { + /* this is only possible if the user clicked on the 'plus' sign on the DivePoints Table */ + const divedatapoint t = divepoints.at(lastEnteredPoint()); + milimeters = t.depth; + seconds = t.time + 600; // 10 minutes. + gas = t.gasmix; + ccpoint = t.setpoint; + } else if (seconds == 0 && milimeters == 0 && row == 0) { + milimeters = M_OR_FT(5, 15); // 5m / 15ft + seconds = 600; // 10 min + //Default to the first defined gas, if we got one. + cylinder_t *cyl = &displayed_dive.cylinder[0]; + if (cyl) + gas = cyl->gasmix; + } + if (!usePrevious) + if (!addGas(gas)) + qDebug("addGas failed"); // FIXME add error propagation + + // check if there's already a new stop before this one: + for (int i = 0; i < row; i++) { + const divedatapoint &dp = divepoints.at(i); + if (dp.time == seconds) { + row = i; + beginRemoveRows(QModelIndex(), row, row); + divepoints.remove(row); + endRemoveRows(); + break; + } + if (dp.time > seconds) { + row = i; + break; + } + } + // Previous, actually means next as we are typically subdiving a segment and the gas for + // the segment is determined by the waypoint at the end. + if (usePrevious) { + if (row < divepoints.count()) { + gas = divepoints.at(row).gasmix; + } else if (row > 0) { + gas = divepoints.at(row - 1).gasmix; + } else { + if (!addGas(air)) + qDebug("addGas failed"); // FIXME add error propagation + + } + } + + // add the new stop + beginInsertRows(QModelIndex(), row, row); + divedatapoint point; + point.depth = milimeters; + point.time = seconds; + point.gasmix = gas; + point.setpoint = ccpoint; + point.entered = entered; + point.next = NULL; + divepoints.append(point); + std::sort(divepoints.begin(), divepoints.end(), divePointsLessThan); + endInsertRows(); + return row; +} + +void DivePlannerPointsModel::editStop(int row, divedatapoint newData) +{ + /* + * When moving divepoints rigorously, we might end up with index + * out of range, thus returning the last one instead. + */ + if (row >= divepoints.count()) + return; + divepoints[row] = newData; + std::sort(divepoints.begin(), divepoints.end(), divePointsLessThan); + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +int DivePlannerPointsModel::size() +{ + return divepoints.size(); +} + +divedatapoint DivePlannerPointsModel::at(int row) +{ + /* + * When moving divepoints rigorously, we might end up with index + * out of range, thus returning the last one instead. + */ + if (row >= divepoints.count()) + return divepoints.at(divepoints.count() - 1); + return divepoints.at(row); +} + +void DivePlannerPointsModel::remove(const QModelIndex &index) +{ + int i; + int rows = rowCount(); + if (index.column() != REMOVE || rowCount() == 1) + return; + + divedatapoint dp = at(index.row()); + if (!dp.entered) + return; + +/* TODO: this seems so wrong. + * We can't do this here if we plan to use QML on mobile + * as mobile has no ControlModifier. + * The correct thing to do is to create a new method + * remove method that will pass the first and last index of the + * removed rows, and remove those in a go. + */ +// if (QApplication::keyboardModifiers() & Qt::ControlModifier) { +// beginRemoveRows(QModelIndex(), index.row(), rows - 1); +// for (i = rows - 1; i >= index.row(); i--) +// divepoints.remove(i); +// } else { + beginRemoveRows(QModelIndex(), index.row(), index.row()); + divepoints.remove(index.row()); +// } + endRemoveRows(); +} + +struct diveplan &DivePlannerPointsModel::getDiveplan() +{ + return diveplan; +} + +void DivePlannerPointsModel::cancelPlan() +{ + /* TODO: + * This check shouldn't be here - this is the interface responsability. + * as soon as the interface thinks that it could cancel the plan, this should be + * called. + */ + + /* + if (mode == PLAN && rowCount()) { + if (QMessageBox::warning(MainWindow::instance(), TITLE_OR_TEXT(tr("Discard the plan?"), + tr("You are about to discard your plan.")), + QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Discard) != QMessageBox::Discard) { + return; + } + } + */ + + setPlanMode(NOTHING); + free_dps(&diveplan); + + emit planCanceled(); +} + +DivePlannerPointsModel::Mode DivePlannerPointsModel::currentMode() const +{ + return mode; +} + +QVector<QPair<int, int> > DivePlannerPointsModel::collectGases(struct dive *d) +{ + QVector<QPair<int, int> > l; + for (int i = 0; i < MAX_CYLINDERS; i++) { + cylinder_t *cyl = &d->cylinder[i]; + if (!cylinder_nodata(cyl)) + l.push_back(qMakePair(get_o2(&cyl->gasmix), get_he(&cyl->gasmix))); + } + return l; +} +void DivePlannerPointsModel::rememberTanks() +{ + oldGases = collectGases(&displayed_dive); +} + +bool DivePlannerPointsModel::tankInUse(struct gasmix gasmix) +{ + for (int j = 0; j < rowCount(); j++) { + divedatapoint &p = divepoints[j]; + if (p.time == 0) // special entries that hold the available gases + continue; + if (!p.entered) // removing deco gases is ok + continue; + if (gasmix_distance(&p.gasmix, &gasmix) < 100) + return true; + } + return false; +} + +void DivePlannerPointsModel::tanksUpdated() +{ + // we don't know exactly what changed - what we care about is + // "did a gas change on us". So we look through the diveplan to + // see if there is a gas that is now missing and if there is, we + // replace it with the matching new gas. + QVector<QPair<int, int> > gases = collectGases(&displayed_dive); + if (gases.count() == oldGases.count()) { + // either nothing relevant changed, or exactly ONE gasmix changed + for (int i = 0; i < gases.count(); i++) { + if (gases.at(i) != oldGases.at(i)) { + if (oldGases.count(oldGases.at(i)) > 1) { + // we had this gas more than once, so don't + // change segments that used this gas as it still exists + break; + } + for (int j = 0; j < rowCount(); j++) { + divedatapoint &p = divepoints[j]; + struct gasmix gas; + gas.o2.permille = oldGases.at(i).first; + gas.he.permille = oldGases.at(i).second; + if (gasmix_distance(&gas, &p.gasmix) < 100) { + p.gasmix.o2.permille = gases.at(i).first; + p.gasmix.he.permille = gases.at(i).second; + } + } + break; + } + } + } + emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); +} + +void DivePlannerPointsModel::clear() +{ + bool oldRecalc = setRecalc(false); + + CylindersModel::instance()->updateDive(); + if (rowCount() > 0) { + beginRemoveRows(QModelIndex(), 0, rowCount() - 1); + divepoints.clear(); + endRemoveRows(); + } + CylindersModel::instance()->clear(); + setRecalc(oldRecalc); +} + +void DivePlannerPointsModel::createTemporaryPlan() +{ + // Get the user-input and calculate the dive info + free_dps(&diveplan); + int lastIndex = -1; + for (int i = 0; i < rowCount(); i++) { + divedatapoint p = at(i); + int deltaT = lastIndex != -1 ? p.time - at(lastIndex).time : p.time; + lastIndex = i; + if (i == 0 && prefs.drop_stone_mode) { + /* Okay, we add a fist segment where we go down to depth */ + plan_add_segment(&diveplan, p.depth / prefs.descrate, p.depth, p.gasmix, p.setpoint, true); + deltaT -= p.depth / prefs.descrate; + } + if (p.entered) + plan_add_segment(&diveplan, deltaT, p.depth, p.gasmix, p.setpoint, true); + } + + // what does the cache do??? + char *cache = NULL; + struct divedatapoint *dp = NULL; + for (int i = 0; i < MAX_CYLINDERS; i++) { + cylinder_t *cyl = &displayed_dive.cylinder[i]; + if (cyl->depth.mm) { + dp = create_dp(0, cyl->depth.mm, cyl->gasmix, 0); + if (diveplan.dp) { + dp->next = diveplan.dp; + diveplan.dp = dp; + } else { + dp->next = NULL; + diveplan.dp = dp; + } + } + } +#if DEBUG_PLAN + dump_plan(&diveplan); +#endif + if (recalcQ() && !diveplan_empty(&diveplan)) { + plan(&diveplan, &cache, isPlanner(), false); + /* TODO: + * Hook this signal to the mainwindow(s), the call to MainWindow + * can't be here as we are now dealing with QML too. + */ + //MainWindow::instance()->setPlanNotes(displayed_dive.notes); + emit calculatedPlanNotes(displayed_dive.notes); + } + // throw away the cache + free(cache); +#if DEBUG_PLAN + save_dive(stderr, &displayed_dive); + dump_plan(&diveplan); +#endif +} + +void DivePlannerPointsModel::deleteTemporaryPlan() +{ + free_dps(&diveplan); +} + +void DivePlannerPointsModel::savePlan() +{ + createPlan(false); +} + +void DivePlannerPointsModel::saveDuplicatePlan() +{ + createPlan(true); +} + +void DivePlannerPointsModel::createPlan(bool replanCopy) +{ + // Ok, so, here the diveplan creates a dive + char *cache = NULL; + bool oldRecalc = setRecalc(false); + removeDeco(); + createTemporaryPlan(); + setRecalc(oldRecalc); + + //TODO: C-based function here? + bool did_deco = plan(&diveplan, &cache, isPlanner(), true); + if (!current_dive || displayed_dive.id != current_dive->id) { + // we were planning a new dive, not re-planning an existing on + record_dive(clone_dive(&displayed_dive)); + } else if (current_dive && displayed_dive.id == current_dive->id) { + // we are replanning a dive - make sure changes are reflected + // correctly in the dive structure and copy it back into the dive table + displayed_dive.maxdepth.mm = 0; + displayed_dive.dc.maxdepth.mm = 0; + fixup_dive(&displayed_dive); + if (replanCopy) { + struct dive *copy = alloc_dive(); + copy_dive(current_dive, copy); + copy->id = 0; + copy->divetrip = NULL; + if (current_dive->divetrip) + add_dive_to_trip(copy, current_dive->divetrip); + record_dive(copy); + QString oldnotes(current_dive->notes); + if (oldnotes.indexOf(QString(disclaimer)) >= 0) + oldnotes.truncate(oldnotes.indexOf(QString(disclaimer))); + if (did_deco) + oldnotes.append(displayed_dive.notes); + displayed_dive.notes = strdup(oldnotes.toUtf8().data()); + } + copy_dive(&displayed_dive, current_dive); + } + mark_divelist_changed(true); + + // Remove and clean the diveplan, so we don't delete + // the dive by mistake. + free_dps(&diveplan); + setPlanMode(NOTHING); + planCreated(); +} diff --git a/qt-models/diveplannermodel.h b/qt-models/diveplannermodel.h new file mode 100644 index 000000000..403b86b6d --- /dev/null +++ b/qt-models/diveplannermodel.h @@ -0,0 +1,117 @@ +#ifndef DIVEPLANNERMODEL_H +#define DIVEPLANNERMODEL_H + +#include <QAbstractTableModel> +#include <QDateTime> + +#include "dive.h" + +class DivePlannerPointsModel : public QAbstractTableModel { + Q_OBJECT +public: + static DivePlannerPointsModel *instance(); + enum Sections { + REMOVE, + DEPTH, + DURATION, + RUNTIME, + GAS, + CCSETPOINT, + COLUMNS + }; + enum Mode { + NOTHING, + PLAN, + ADD + }; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + void gaschange(const QModelIndex &index, QString newgas); + void removeSelectedPoints(const QVector<int> &rows); + void setPlanMode(Mode mode); + bool isPlanner(); + void createSimpleDive(); + void setupStartTime(); + void clear(); + Mode currentMode() const; + bool setRecalc(bool recalc); + bool recalcQ(); + void tanksUpdated(); + void rememberTanks(); + bool tankInUse(struct gasmix gasmix); + void setupCylinders(); + /** + * @return the row number. + */ + void editStop(int row, divedatapoint newData); + divedatapoint at(int row); + int size(); + struct diveplan &getDiveplan(); + QStringList &getGasList(); + QVector<QPair<int, int> > collectGases(dive *d); + int lastEnteredPoint(); + void removeDeco(); + static bool addingDeco; + +public +slots: + int addStop(int millimeters = 0, int seconds = 0, struct gasmix *gas = 0, int ccpoint = 0, bool entered = true); + void addCylinder_clicked(); + void setGFHigh(const int gfhigh); + void triggerGFHigh(); + void setGFLow(const int ghflow); + void triggerGFLow(); + void setSurfacePressure(int pressure); + void setSalinity(int salinity); + int getSurfacePressure(); + void setBottomSac(double sac); + void setDecoSac(double sac); + void setStartTime(const QTime &t); + void setStartDate(const QDate &date); + void setLastStop6m(bool value); + void setDropStoneMode(bool value); + void setVerbatim(bool value); + void setDisplayRuntime(bool value); + void setDisplayDuration(bool value); + void setDisplayTransitions(bool value); + void setRecreationalMode(bool value); + void setSafetyStop(bool value); + void savePlan(); + void saveDuplicatePlan(); + void remove(const QModelIndex &index); + void cancelPlan(); + void createTemporaryPlan(); + void deleteTemporaryPlan(); + void loadFromDive(dive *d); + void emitDataChanged(); + void setRebreatherMode(int mode); + void setReserveGas(int reserve); + +signals: + void planCreated(); + void planCanceled(); + void cylinderModelEdited(); + void startTimeChanged(QDateTime); + void recreationChanged(bool); + void calculatedPlanNotes(const QString& notes); + +private: + explicit DivePlannerPointsModel(QObject *parent = 0); + bool addGas(struct gasmix mix); + void createPlan(bool replanCopy); + struct diveplan diveplan; + Mode mode; + bool recalc; + QVector<divedatapoint> divepoints; + QVector<sample> backupSamples; // For editing added dives. + QVector<QPair<int, int> > oldGases; + QDateTime startTime; + int tempGFHigh; + int tempGFLow; +}; + +#endif diff --git a/qt-models/models.cpp b/qt-models/models.cpp index 637a2d3b1..3bc0f74a2 100644 --- a/qt-models/models.cpp +++ b/qt-models/models.cpp @@ -28,387 +28,12 @@ #include <QMessageBox> #include <QStringListModel> -static QPixmap *trashIconPixmap; - // initialize the trash icon if necessary -static void initTrashIcon() -{ - if (!trashIconPixmap) - trashIconPixmap = new QPixmap(QIcon(":trash").pixmap(defaultIconMetrics().sz_small)); -} const QPixmap &trashIcon() { - return *trashIconPixmap; -} - -CylindersModel::CylindersModel(QObject *parent) : changed(false), - rows(0) -{ - // enum {REMOVE, TYPE, SIZE, WORKINGPRESS, START, END, O2, HE, DEPTH}; - setHeaderDataStrings(QStringList() << "" << tr("Type") << tr("Size") << tr("Work press.") << tr("Start press.") << tr("End press.") << tr("O₂%") << tr("He%") - << tr("Switch at") << tr("Use")); - - initTrashIcon(); -} - -CylindersModel *CylindersModel::instance() -{ - - static QScopedPointer<CylindersModel> self(new CylindersModel()); - return self.data(); -} - -static QVariant percent_string(fraction_t fraction) -{ - int permille = fraction.permille; - - if (!permille) - return QVariant(); - return QString("%1%").arg(permille / 10.0, 0, 'f', 1); -} - -QVariant CylindersModel::data(const QModelIndex &index, int role) const -{ - QVariant ret; - - if (!index.isValid() || index.row() >= MAX_CYLINDERS) - return ret; - - cylinder_t *cyl = &displayed_dive.cylinder[index.row()]; - switch (role) { - case Qt::BackgroundRole: { - switch (index.column()) { - // mark the cylinder start / end pressure in red if the values - // seem implausible - case START: - case END: - if ((cyl->start.mbar && !cyl->end.mbar) || - (cyl->end.mbar && cyl->start.mbar <= cyl->end.mbar)) - ret = REDORANGE1_HIGH_TRANS; - break; - } - break; - } - case Qt::FontRole: { - QFont font = defaultModelFont(); - switch (index.column()) { - case START: - font.setItalic(!cyl->start.mbar); - break; - case END: - font.setItalic(!cyl->end.mbar); - break; - } - ret = font; - break; - } - case Qt::TextAlignmentRole: - ret = Qt::AlignCenter; - break; - case Qt::DisplayRole: - case Qt::EditRole: - switch (index.column()) { - case TYPE: - ret = QString(cyl->type.description); - break; - case SIZE: - if (cyl->type.size.mliter) - ret = get_volume_string(cyl->type.size, true, cyl->type.workingpressure.mbar); - break; - case WORKINGPRESS: - if (cyl->type.workingpressure.mbar) - ret = get_pressure_string(cyl->type.workingpressure, true); - break; - case START: - if (cyl->start.mbar) - ret = get_pressure_string(cyl->start, true); - else if (cyl->sample_start.mbar) - ret = get_pressure_string(cyl->sample_start, true); - break; - case END: - if (cyl->end.mbar) - ret = get_pressure_string(cyl->end, true); - else if (cyl->sample_end.mbar) - ret = get_pressure_string(cyl->sample_end, true); - break; - case O2: - ret = percent_string(cyl->gasmix.o2); - break; - case HE: - ret = percent_string(cyl->gasmix.he); - break; - case DEPTH: - ret = get_depth_string(cyl->depth, true); - break; - case USE: - ret = gettextFromC::instance()->trGettext(cylinderuse_text[cyl->cylinder_use]); - break; - } - break; - case Qt::DecorationRole: - if (index.column() == REMOVE) - ret = trashIcon(); - break; - case Qt::SizeHintRole: - if (index.column() == REMOVE) - ret = trashIcon().size(); - break; - - case Qt::ToolTipRole: - if (index.column() == REMOVE) - ret = tr("Clicking here will remove this cylinder."); - break; - } - - return ret; -} - -cylinder_t *CylindersModel::cylinderAt(const QModelIndex &index) -{ - return &displayed_dive.cylinder[index.row()]; -} - -// this is our magic 'pass data in' function that allows the delegate to get -// the data here without silly unit conversions; -// so we only implement the two columns we care about -void CylindersModel::passInData(const QModelIndex &index, const QVariant &value) -{ - cylinder_t *cyl = cylinderAt(index); - switch (index.column()) { - case SIZE: - if (cyl->type.size.mliter != value.toInt()) { - cyl->type.size.mliter = value.toInt(); - dataChanged(index, index); - } - break; - case WORKINGPRESS: - if (cyl->type.workingpressure.mbar != value.toInt()) { - cyl->type.workingpressure.mbar = value.toInt(); - dataChanged(index, index); - } - break; - } -} - -/* Has the string value changed */ -#define CHANGED() \ - (vString = value.toString()) != data(index, role).toString() - -bool CylindersModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - QString vString; - bool addDiveMode = DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING; - if (addDiveMode) - DivePlannerPointsModel::instance()->rememberTanks(); - - cylinder_t *cyl = cylinderAt(index); - switch (index.column()) { - case TYPE: - if (!value.isNull()) { - QByteArray ba = value.toByteArray(); - const char *text = ba.constData(); - if (!cyl->type.description || strcmp(cyl->type.description, text)) { - cyl->type.description = strdup(text); - changed = true; - } - } - break; - case SIZE: - if (CHANGED()) { - TankInfoModel *tanks = TankInfoModel::instance(); - QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl->type.description); - - cyl->type.size = string_to_volume(vString.toUtf8().data(), cyl->type.workingpressure); - mark_divelist_changed(true); - if (!matches.isEmpty()) - tanks->setData(tanks->index(matches.first().row(), TankInfoModel::ML), cyl->type.size.mliter); - changed = true; - } - break; - case WORKINGPRESS: - if (CHANGED()) { - TankInfoModel *tanks = TankInfoModel::instance(); - QModelIndexList matches = tanks->match(tanks->index(0, 0), Qt::DisplayRole, cyl->type.description); - cyl->type.workingpressure = string_to_pressure(vString.toUtf8().data()); - if (!matches.isEmpty()) - tanks->setData(tanks->index(matches.first().row(), TankInfoModel::BAR), cyl->type.workingpressure.mbar / 1000.0); - changed = true; - } - break; - case START: - if (CHANGED()) { - cyl->start = string_to_pressure(vString.toUtf8().data()); - changed = true; - } - break; - case END: - if (CHANGED()) { - //&& (!cyl->start.mbar || string_to_pressure(vString.toUtf8().data()).mbar <= cyl->start.mbar)) { - cyl->end = string_to_pressure(vString.toUtf8().data()); - changed = true; - } - break; - case O2: - if (CHANGED()) { - cyl->gasmix.o2 = string_to_fraction(vString.toUtf8().data()); - pressure_t modpO2; - if (displayed_dive.dc.divemode == PSCR) - modpO2.mbar = prefs.decopo2 + (1000 - get_o2(&cyl->gasmix)) * SURFACE_PRESSURE * - prefs.o2consumption / prefs.decosac / prefs.pscr_ratio; - else - modpO2.mbar = prefs.decopo2; - cyl->depth = gas_mod(&cyl->gasmix, modpO2, M_OR_FT(3, 10)); - changed = true; - } - break; - case HE: - if (CHANGED()) { - cyl->gasmix.he = string_to_fraction(vString.toUtf8().data()); - changed = true; - } - break; - case DEPTH: - if (CHANGED()) { - cyl->depth = string_to_depth(vString.toUtf8().data()); - changed = true; - } - break; - case USE: - if (CHANGED()) { - cyl->cylinder_use = (enum cylinderuse)vString.toInt(); - changed = true; - } - break; - } - if (addDiveMode) - DivePlannerPointsModel::instance()->tanksUpdated(); - dataChanged(index, index); - return true; -} - -int CylindersModel::rowCount(const QModelIndex &parent) const -{ - return rows; -} - -void CylindersModel::add() -{ - if (rows >= MAX_CYLINDERS) { - return; - } - - int row = rows; - fill_default_cylinder(&displayed_dive.cylinder[row]); - displayed_dive.cylinder[row].manually_added = true; - beginInsertRows(QModelIndex(), row, row); - rows++; - changed = true; - endInsertRows(); -} - -void CylindersModel::clear() -{ - if (rows > 0) { - beginRemoveRows(QModelIndex(), 0, rows - 1); - endRemoveRows(); - } -} - -void CylindersModel::updateDive() -{ - clear(); - rows = 0; - for (int i = 0; i < MAX_CYLINDERS; i++) { - if (!cylinder_none(&displayed_dive.cylinder[i]) && - (prefs.display_unused_tanks || - is_cylinder_used(&displayed_dive, i) || - displayed_dive.cylinder[i].manually_added)) - rows = i + 1; - } - if (rows > 0) { - beginInsertRows(QModelIndex(), 0, rows - 1); - endInsertRows(); - } -} - -void CylindersModel::copyFromDive(dive *d) -{ - if (!d) - return; - rows = 0; - for (int i = 0; i < MAX_CYLINDERS; i++) { - if (!cylinder_none(&d->cylinder[i]) && - (is_cylinder_used(d, i) || prefs.display_unused_tanks)) { - rows = i + 1; - } - } - if (rows > 0) { - beginInsertRows(QModelIndex(), 0, rows - 1); - endInsertRows(); - } -} - -Qt::ItemFlags CylindersModel::flags(const QModelIndex &index) const -{ - if (index.column() == REMOVE) - return Qt::ItemIsEnabled; - return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; -} - -void CylindersModel::remove(const QModelIndex &index) -{ - int mapping[MAX_CYLINDERS]; - if (index.column() != REMOVE) { - return; - } - int same_gas = -1; - cylinder_t *cyl = &displayed_dive.cylinder[index.row()]; - struct gasmix *mygas = &cyl->gasmix; - for (int i = 0; i < MAX_CYLINDERS; i++) { - mapping[i] = i; - if (i == index.row() || cylinder_none(&displayed_dive.cylinder[i])) - continue; - struct gasmix *gas2 = &displayed_dive.cylinder[i].gasmix; - if (gasmix_distance(mygas, gas2) == 0) - same_gas = i; - } - if (same_gas == -1 && - ((DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING && - DivePlannerPointsModel::instance()->tankInUse(cyl->gasmix)) || - (DivePlannerPointsModel::instance()->currentMode() == DivePlannerPointsModel::NOTHING && - is_cylinder_used(&displayed_dive, index.row())))) { - QMessageBox::warning(MainWindow::instance(), TITLE_OR_TEXT( - tr("Cylinder cannot be removed"), - tr("This gas is in use. Only cylinders that are not used in the dive can be removed.")), - QMessageBox::Ok); - return; - } - beginRemoveRows(QModelIndex(), index.row(), index.row()); // yah, know, ugly. - rows--; - if (index.row() == 0) { - // first gas - we need to make sure that the same gas ends up - // as first gas - memmove(cyl, &displayed_dive.cylinder[same_gas], sizeof(*cyl)); - remove_cylinder(&displayed_dive, same_gas); - mapping[same_gas] = 0; - for (int i = same_gas + 1; i < MAX_CYLINDERS; i++) - mapping[i] = i - 1; - } else { - remove_cylinder(&displayed_dive, index.row()); - if (same_gas > index.row()) - same_gas--; - mapping[index.row()] = same_gas; - for (int i = index.row() + 1; i < MAX_CYLINDERS; i++) - mapping[i] = i - 1; - } - changed = true; - endRemoveRows(); - struct divecomputer *dc = &displayed_dive.dc; - while (dc) { - dc_cylinder_renumber(&displayed_dive, dc, mapping); - dc = dc->next; - } + static QPixmap trash = QPixmap(":trash").scaledToHeight(defaultIconMetrics().sz_small); + return trash; } WeightModel::WeightModel(QObject *parent) : CleanerTableModel(parent), @@ -417,8 +42,6 @@ WeightModel::WeightModel(QObject *parent) : CleanerTableModel(parent), { //enum Column {REMOVE, TYPE, WEIGHT}; setHeaderDataStrings(QStringList() << tr("") << tr("Type") << tr("Weight")); - - initTrashIcon(); } weightsystem_t *WeightModel::weightSystemAt(const QModelIndex &index) @@ -1592,7 +1215,6 @@ bool DiveTripModel::setData(const QModelIndex &index, const QVariant &value, int return diveItem->setData(index, value, role); } - /*#################################################################### * * Dive Computer Model @@ -1605,8 +1227,6 @@ DiveComputerModel::DiveComputerModel(QMultiMap<QString, DiveComputerNode> &dcMap setHeaderDataStrings(QStringList() << "" << tr("Model") << tr("Device ID") << tr("Nickname")); dcWorkingMap = dcMap; numRows = 0; - - initTrashIcon(); } QVariant DiveComputerModel::data(const QModelIndex &index, int role) const @@ -2213,8 +1833,15 @@ GasSelectionModel *GasSelectionModel::instance() return self.data(); } +//TODO: Remove this #include here when the issue below is fixed. +#include "diveplannermodel.h" void GasSelectionModel::repopulate() { + /* TODO: + * getGasList shouldn't be a member of DivePlannerPointsModel, + * it has nothing to do with the current plain being calculated: + * it's internal to the current_dive. + */ setStringList(DivePlannerPointsModel::instance()->getGasList()); } diff --git a/qt-models/models.h b/qt-models/models.h index 60b83cd95..2edf89269 100644 --- a/qt-models/models.h +++ b/qt-models/models.h @@ -12,6 +12,7 @@ #include <QStringList> #include <QStringListModel> #include <QSortFilterProxyModel> +#include <QPixmap> #include "metrics.h" @@ -78,48 +79,6 @@ private: /* Retrieve the trash icon pixmap, common to most table models */ const QPixmap &trashIcon(); -/* Encapsulation of the Cylinder Model, that presents the - * Current cylinders that are used on a dive. */ -class CylindersModel : public CleanerTableModel { - Q_OBJECT -public: - enum Column { - REMOVE, - TYPE, - SIZE, - WORKINGPRESS, - START, - END, - O2, - HE, - DEPTH, - USE, - COLUMNS - }; - - explicit CylindersModel(QObject *parent = 0); - static CylindersModel *instance(); - /*reimp*/ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - /*reimp*/ int rowCount(const QModelIndex &parent = QModelIndex()) const; - /*reimp*/ Qt::ItemFlags flags(const QModelIndex &index) const; - /*reimp*/ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - - void passInData(const QModelIndex &index, const QVariant &value); - void add(); - void clear(); - void updateDive(); - void copyFromDive(struct dive *d); - cylinder_t *cylinderAt(const QModelIndex &index); - bool changed; - -public -slots: - void remove(const QModelIndex &index); - -private: - int rows; -}; - /* Encapsulation of the Weight Model, that represents * the current weights on a dive. */ class WeightModel : public CleanerTableModel { diff --git a/qt-ui/diveplanner.cpp b/qt-ui/diveplanner.cpp index c82bc0463..7ab0aac86 100644 --- a/qt-ui/diveplanner.cpp +++ b/qt-ui/diveplanner.cpp @@ -3,8 +3,10 @@ #include "mainwindow.h" #include "planner.h" #include "helpers.h" +#include "cylindermodel.h" #include "models.h" #include "profile/profilewidget2.h" +#include "diveplannermodel.h" #include <QGraphicsSceneMouseEvent> #include <QMessageBox> @@ -18,182 +20,7 @@ #define UNIT_FACTOR ((prefs.units.length == units::METERS) ? 1000.0 / 60.0 : feet_to_mm(1.0) / 60.0) -QString gasToStr(struct gasmix gas) -{ - uint o2 = (get_o2(&gas) + 5) / 10, he = (get_he(&gas) + 5) / 10; - QString result = gasmix_is_air(&gas) ? QObject::tr("AIR") : he == 0 ? (o2 == 100 ? QObject::tr("OXYGEN") : QString("EAN%1").arg(o2, 2, 10, QChar('0'))) : QString("%1/%2").arg(o2).arg(he); - return result; -} - -QString dpGasToStr(const divedatapoint &p) -{ - return gasToStr(p.gasmix); -} - -static DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - -bool intLessThan(int a, int b) -{ - return a <= b; -} -void DivePlannerPointsModel::removeSelectedPoints(const QVector<int> &rows) -{ - if (!rows.count()) - return; - int firstRow = rowCount() - rows.count(); - QVector<int> v2 = rows; - std::sort(v2.begin(), v2.end(), intLessThan); - - beginRemoveRows(QModelIndex(), firstRow, rowCount() - 1); - for (int i = v2.count() - 1; i >= 0; i--) { - divepoints.remove(v2[i]); - } - endRemoveRows(); -} - -void DivePlannerPointsModel::createSimpleDive() -{ - struct gasmix gas = { 0 }; - - // initialize the start time in the plan - diveplan.when = displayed_dive.when; - - if (isPlanner()) - // let's use the gas from the first cylinder - gas = displayed_dive.cylinder[0].gasmix; - - // If we're in drop_stone_mode, don't add a first point. - // It will be added implicit. - if (!prefs.drop_stone_mode) - plannerModel->addStop(M_OR_FT(15, 45), 1 * 60, &gas, 0, true); - - plannerModel->addStop(M_OR_FT(15, 45), 20 * 60, &gas, 0, true); - if (!isPlanner()) { - plannerModel->addStop(M_OR_FT(5, 15), 42 * 60, &gas, 0, true); - plannerModel->addStop(M_OR_FT(5, 15), 45 * 60, &gas, 0, true); - } -} - -void DivePlannerPointsModel::setupStartTime() -{ - // if the latest dive is in the future, then start an hour after it ends - // otherwise start an hour from now - startTime = QDateTime::currentDateTimeUtc().addSecs(3600 + gettimezoneoffset()); - if (dive_table.nr) { - struct dive *d = get_dive(dive_table.nr - 1); - time_t ends = d->when + d->duration.seconds; - time_t diff = ends - startTime.toTime_t(); - if (diff > 0) { - startTime = startTime.addSecs(diff + 3600); - } - } - emit startTimeChanged(startTime); -} - -void DivePlannerPointsModel::loadFromDive(dive *d) -{ - int depthsum = 0; - int samplecount = 0; - bool oldRec = recalc; - recalc = false; - CylindersModel::instance()->updateDive(); - duration_t lasttime = {}; - duration_t newtime = {}; - struct gasmix gas; - free_dps(&diveplan); - diveplan.when = d->when; - // is this a "new" dive where we marked manually entered samples? - // if yes then the first sample should be marked - // if it is we only add the manually entered samples as waypoints to the diveplan - // otherwise we have to add all of them - bool hasMarkedSamples = d->dc.sample[0].manually_entered; - // if this dive has more than 100 samples (so it is probably a logged dive), - // average samples so we end up with a total of 100 samples. - int plansamples = d->dc.samples <= 100 ? d->dc.samples : 100; - int j = 0; - for (int i = 0; i < plansamples - 1; i++) { - while (j * plansamples <= i * d->dc.samples) { - const sample &s = d->dc.sample[j]; - if (s.time.seconds != 0 && (!hasMarkedSamples || s.manually_entered)) { - depthsum += s.depth.mm; - ++samplecount; - newtime = s.time; - } - j++; - } - if (samplecount) { - get_gas_at_time(d, &d->dc, lasttime, &gas); - plannerModel->addStop(depthsum / samplecount, newtime.seconds, &gas, 0, true); - lasttime = newtime; - depthsum = 0; - samplecount = 0; - } - } - recalc = oldRec; - emitDataChanged(); -} - -// copy the tanks from the current dive, or the default cylinder -// or an unknown cylinder -// setup the cylinder widget accordingly -void DivePlannerPointsModel::setupCylinders() -{ - if (mode == PLAN && current_dive) { - // take the displayed cylinders from the selected dive as starting point - CylindersModel::instance()->copyFromDive(current_dive); - copy_cylinders(current_dive, &displayed_dive, !prefs.display_unused_tanks); - reset_cylinders(&displayed_dive, true); - return; - } - if (!same_string(prefs.default_cylinder, "")) { - fill_default_cylinder(&displayed_dive.cylinder[0]); - } else { - // roughly an AL80 - displayed_dive.cylinder[0].type.description = strdup(tr("unknown").toUtf8().constData()); - displayed_dive.cylinder[0].type.size.mliter = 11100; - displayed_dive.cylinder[0].type.workingpressure.mbar = 207000; - } - reset_cylinders(&displayed_dive, false); - CylindersModel::instance()->copyFromDive(&displayed_dive); -} - -QStringList &DivePlannerPointsModel::getGasList() -{ - static QStringList list; - list.clear(); - for (int i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = &displayed_dive.cylinder[i]; - if (cylinder_nodata(cyl)) - break; - list.push_back(gasToStr(cyl->gasmix)); - } - return list; -} - -void DivePlannerPointsModel::removeDeco() -{ - bool oldrec = setRecalc(false); - QVector<int> computedPoints; - for (int i = 0; i < plannerModel->rowCount(); i++) - if (!plannerModel->at(i).entered) - computedPoints.push_back(i); - removeSelectedPoints(computedPoints); - setRecalc(oldrec); -} - -#if 0 -void DivePlannerGraphics::drawProfile() -{ - // Code ported to the new profile is deleted. This part that I left here - // is because I didn't fully understood the reason of the magic with - // the plannerModel. - bool oldRecalc = plannerModel->setRecalc(false); - plannerModel->removeDeco(); - // Here we plotted the old planner profile. why there's the magic with the plannerModel here? - plannerModel->setRecalc(oldRecalc); - plannerModel->deleteTemporaryPlan(); -} -#endif +static DivePlannerPointsModel* plannerModel = DivePlannerPointsModel::instance(); DiveHandler::DiveHandler() : QGraphicsEllipseItem() { @@ -223,7 +50,7 @@ void DiveHandler::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) m.addAction(action); } // don't allow removing the last point - if (DivePlannerPointsModel::instance()->rowCount() > 1) { + if (plannerModel->rowCount() > 1) { m.addSeparator(); m.addAction(QObject::tr("Remove this point"), this, SLOT(selfRemove())); m.exec(event->screenPos()); @@ -275,8 +102,8 @@ DivePlannerWidget::DivePlannerWidget(QWidget *parent, Qt::WindowFlags f) : QWidg ui.setupUi(this); ui.dateEdit->setDisplayFormat(getDateFormat()); ui.tableWidget->setTitle(tr("Dive planner points")); - ui.tableWidget->setModel(DivePlannerPointsModel::instance()); - DivePlannerPointsModel::instance()->setRecalc(true); + ui.tableWidget->setModel(plannerModel); + plannerModel->setRecalc(true); ui.tableWidget->view()->setItemDelegateForColumn(DivePlannerPointsModel::GAS, new AirTypesDelegate(this)); ui.cylinderTableWidget->setTitle(tr("Available gases")); ui.cylinderTableWidget->setModel(CylindersModel::instance()); @@ -285,8 +112,8 @@ DivePlannerWidget::DivePlannerWidget(QWidget *parent, Qt::WindowFlags f) : QWidg view->setColumnHidden(CylindersModel::END, true); view->setColumnHidden(CylindersModel::DEPTH, false); view->setItemDelegateForColumn(CylindersModel::TYPE, new TankInfoDelegate(this)); - connect(ui.cylinderTableWidget, SIGNAL(addButtonClicked()), DivePlannerPointsModel::instance(), SLOT(addCylinder_clicked())); - connect(ui.tableWidget, SIGNAL(addButtonClicked()), DivePlannerPointsModel::instance(), SLOT(addStop())); + connect(ui.cylinderTableWidget, SIGNAL(addButtonClicked()), plannerModel, SLOT(addCylinder_clicked())); + connect(ui.tableWidget, SIGNAL(addButtonClicked()), plannerModel, SLOT(addStop())); connect(CylindersModel::instance(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), GasSelectionModel::instance(), SLOT(repopulate())); @@ -307,7 +134,7 @@ DivePlannerWidget::DivePlannerWidget(QWidget *parent, Qt::WindowFlags f) : QWidg connect(ui.ATMPressure, SIGNAL(valueChanged(int)), this, SLOT(atmPressureChanged(int))); connect(ui.atmHeight, SIGNAL(valueChanged(int)), this, SLOT(heightChanged(int))); connect(ui.salinity, SIGNAL(valueChanged(double)), this, SLOT(salinityChanged(double))); - connect(DivePlannerPointsModel::instance(), SIGNAL(startTimeChanged(QDateTime)), this, SLOT(setupStartTime(QDateTime))); + connect(plannerModel, SIGNAL(startTimeChanged(QDateTime)), this, SLOT(setupStartTime(QDateTime))); // Creating (and canceling) the plan replanButton = ui.buttonBox->addButton(tr("Save new"), QDialogButtonBox::ActionRole); @@ -355,11 +182,6 @@ void DivePlannerWidget::settingsChanged() ui.atmHeight->blockSignals(false); } -void DivePlannerPointsModel::addCylinder_clicked() -{ - CylindersModel::instance()->add(); -} - void DivePlannerWidget::atmPressureChanged(const int pressure) { plannerModel->setSurfacePressure(pressure); @@ -483,7 +305,7 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f) connect(ui.gflow, SIGNAL(editingFinished()), plannerModel, SLOT(triggerGFLow())); connect(ui.backgasBreaks, SIGNAL(toggled(bool)), this, SLOT(setBackgasBreaks(bool))); connect(ui.rebreathermode, SIGNAL(currentIndexChanged(int)), plannerModel, SLOT(setRebreatherMode(int))); - connect(DivePlannerPointsModel::instance(), SIGNAL(recreationChanged(bool)), this, SLOT(disableDecoElements(bool))); + connect(plannerModel, SIGNAL(recreationChanged(bool)), this, SLOT(disableDecoElements(bool))); settingsChanged(); ui.gflow->setValue(prefs.gflow); @@ -623,741 +445,6 @@ void PlannerSettingsWidget::setBackgasBreaks(bool dobreaks) plannerModel->emitDataChanged(); } - -void DivePlannerPointsModel::setPlanMode(Mode m) -{ - mode = m; - // the planner may reset our GF settings that are used to show deco - // reset them to what's in the preferences - if (m != PLAN) - set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); -} - -bool DivePlannerPointsModel::isPlanner() -{ - return mode == PLAN; -} - -/* When the planner adds deco stops to the model, adding those should not trigger a new deco calculation. - * We thus start the planner only when recalc is true. */ - -bool DivePlannerPointsModel::setRecalc(bool rec) -{ - bool old = recalc; - recalc = rec; - return old; -} - -bool DivePlannerPointsModel::recalcQ() -{ - return recalc; -} - -int DivePlannerPointsModel::columnCount(const QModelIndex &parent) const -{ - return COLUMNS; // to disable CCSETPOINT subtract one -} - -QVariant DivePlannerPointsModel::data(const QModelIndex &index, int role) const -{ - divedatapoint p = divepoints.at(index.row()); - if (role == Qt::DisplayRole || role == Qt::EditRole) { - switch (index.column()) { - case CCSETPOINT: - return (double)p.setpoint / 1000; - case DEPTH: - return (int) rint(get_depth_units(p.depth, NULL, NULL)); - case RUNTIME: - return p.time / 60; - case DURATION: - if (index.row()) - return (p.time - divepoints.at(index.row() - 1).time) / 60; - else - return p.time / 60; - case GAS: - return dpGasToStr(p); - } - } else if (role == Qt::DecorationRole) { - switch (index.column()) { - case REMOVE: - if (rowCount() > 1) - return p.entered ? trashIcon() : QVariant(); - } - } else if (role == Qt::SizeHintRole) { - switch (index.column()) { - case REMOVE: - if (rowCount() > 1) - return p.entered ? trashIcon().size() : QVariant(); - } - } else if (role == Qt::FontRole) { - if (divepoints.at(index.row()).entered) { - return defaultModelFont(); - } else { - QFont font = defaultModelFont(); - font.setBold(true); - return font; - } - } - return QVariant(); -} - -bool DivePlannerPointsModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - struct gasmix gas = { 0 }; - int i, shift; - if (role == Qt::EditRole) { - divedatapoint &p = divepoints[index.row()]; - switch (index.column()) { - case DEPTH: - if (value.toInt() >= 0) - p.depth = units_to_depth(value.toInt()); - break; - case RUNTIME: - p.time = value.toInt() * 60; - break; - case DURATION: - i = index.row(); - if (i) - shift = divepoints[i].time - divepoints[i - 1].time - value.toInt() * 60; - else - shift = divepoints[i].time - value.toInt() * 60; - while (i < divepoints.size()) - divepoints[i++].time -= shift; - break; - case CCSETPOINT: { - int po2 = 0; - QByteArray gasv = value.toByteArray(); - if (validate_po2(gasv.data(), &po2)) - p.setpoint = po2; - } break; - case GAS: - QByteArray gasv = value.toByteArray(); - if (validate_gas(gasv.data(), &gas)) - p.gasmix = gas; - break; - } - editStop(index.row(), p); - } - return QAbstractItemModel::setData(index, value, role); -} - -void DivePlannerPointsModel::gaschange(const QModelIndex &index, QString newgas) -{ - int i = index.row(); - gasmix oldgas = divepoints[i].gasmix; - gasmix gas = { 0 }; - if (!validate_gas(newgas.toUtf8().data(), &gas)) - return; - while (i < plannerModel->rowCount() && gasmix_distance(&oldgas, &divepoints[i].gasmix) == 0) - divepoints[i++].gasmix = gas; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -QVariant DivePlannerPointsModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { - switch (section) { - case DEPTH: - return tr("Final depth"); - case RUNTIME: - return tr("Run time"); - case DURATION: - return tr("Duration"); - case GAS: - return tr("Used gas"); - case CCSETPOINT: - return tr("CC set point"); - } - } else if (role == Qt::FontRole) { - return defaultModelFont(); - } - return QVariant(); -} - -Qt::ItemFlags DivePlannerPointsModel::flags(const QModelIndex &index) const -{ - if (index.column() != REMOVE) - return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; - else - return QAbstractItemModel::flags(index); -} - -int DivePlannerPointsModel::rowCount(const QModelIndex &parent) const -{ - return divepoints.count(); -} - -DivePlannerPointsModel::DivePlannerPointsModel(QObject *parent) : QAbstractTableModel(parent), - mode(NOTHING), - recalc(false), - tempGFHigh(100), - tempGFLow(100) -{ - memset(&diveplan, 0, sizeof(diveplan)); -} - -DivePlannerPointsModel *DivePlannerPointsModel::instance() -{ - static QScopedPointer<DivePlannerPointsModel> self(new DivePlannerPointsModel()); - return self.data(); -} - -void DivePlannerPointsModel::emitDataChanged() -{ - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setBottomSac(double sac) -{ - diveplan.bottomsac = units_to_sac(sac); - prefs.bottomsac = diveplan.bottomsac; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setDecoSac(double sac) -{ - diveplan.decosac = units_to_sac(sac); - prefs.decosac = diveplan.decosac; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setGFHigh(const int gfhigh) -{ - tempGFHigh = gfhigh; - // GFHigh <= 34 can cause infinite deco at 6m - don't trigger a recalculation - // for smaller GFHigh unless the user explicitly leaves the field - if (tempGFHigh > 34) - triggerGFHigh(); -} - -void DivePlannerPointsModel::triggerGFHigh() -{ - if (diveplan.gfhigh != tempGFHigh) { - diveplan.gfhigh = tempGFHigh; - plannerModel->emitDataChanged(); - } -} - -void DivePlannerPointsModel::setGFLow(const int ghflow) -{ - tempGFLow = ghflow; - triggerGFLow(); -} - -void DivePlannerPointsModel::setRebreatherMode(int mode) -{ - int i; - displayed_dive.dc.divemode = (dive_comp_type) mode; - for (i=0; i < rowCount(); i++) - divepoints[i].setpoint = mode == CCR ? prefs.defaultsetpoint : 0; - emitDataChanged(); -} - -void DivePlannerPointsModel::triggerGFLow() -{ - if (diveplan.gflow != tempGFLow) { - diveplan.gflow = tempGFLow; - plannerModel->emitDataChanged(); - } -} - -void DivePlannerPointsModel::setSurfacePressure(int pressure) -{ - diveplan.surface_pressure = pressure; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setSalinity(int salinity) -{ - diveplan.salinity = salinity; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -int DivePlannerPointsModel::getSurfacePressure() -{ - return diveplan.surface_pressure; -} - -void DivePlannerPointsModel::setLastStop6m(bool value) -{ - set_last_stop(value); - prefs.last_stop = value; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setVerbatim(bool value) -{ - set_verbatim(value); - prefs.verbatim_plan = value; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setDisplayRuntime(bool value) -{ - set_display_runtime(value); - prefs.display_runtime = value; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setDisplayDuration(bool value) -{ - set_display_duration(value); - prefs.display_duration = value; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setDisplayTransitions(bool value) -{ - set_display_transitions(value); - prefs.display_transitions = value; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setRecreationalMode(bool value) -{ - prefs.recreational_mode = value; - emit recreationChanged(value); - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS -1)); -} - -void DivePlannerPointsModel::setSafetyStop(bool value) -{ - prefs.safetystop = value; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS -1)); -} - -void DivePlannerPointsModel::setReserveGas(int reserve) -{ - prefs.reserve_gas = reserve * 1000; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setDropStoneMode(bool value) -{ - prefs.drop_stone_mode = value; - if (prefs.drop_stone_mode) { - /* Remove the first entry if we enable drop_stone_mode */ - if (rowCount() >= 2) { - beginRemoveRows(QModelIndex(), 0, 0); - divepoints.remove(0); - endRemoveRows(); - } - } else { - /* Add a first entry if we disable drop_stone_mode */ - beginInsertRows(QModelIndex(), 0, 0); - /* Copy the first current point */ - divedatapoint p = divepoints.at(0); - p.time = p.depth / prefs.descrate; - divepoints.push_front(p); - endInsertRows(); - } - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setStartDate(const QDate &date) -{ - startTime.setDate(date); - diveplan.when = startTime.toTime_t(); - displayed_dive.when = diveplan.when; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::setStartTime(const QTime &t) -{ - startTime.setTime(t); - diveplan.when = startTime.toTime_t(); - displayed_dive.when = diveplan.when; - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -bool divePointsLessThan(const divedatapoint &p1, const divedatapoint &p2) -{ - return p1.time <= p2.time; -} - -bool DivePlannerPointsModel::addGas(struct gasmix mix) -{ - sanitize_gasmix(&mix); - - for (int i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = &displayed_dive.cylinder[i]; - if (cylinder_nodata(cyl)) { - fill_default_cylinder(cyl); - cyl->gasmix = mix; - /* The depth to change to that gas is given by the depth where its pO₂ is 1.6 bar. - * The user should be able to change this depth manually. */ - pressure_t modpO2; - if (displayed_dive.dc.divemode == PSCR) - modpO2.mbar = prefs.decopo2 + (1000 - get_o2(&mix)) * SURFACE_PRESSURE * - prefs.o2consumption / prefs.decosac / prefs.pscr_ratio; - else - modpO2.mbar = prefs.decopo2; - cyl->depth = gas_mod(&mix, modpO2, M_OR_FT(3,10)); - - - - - // FIXME -- need to get rid of stagingDIve - // the following now uses displayed_dive !!!! - - - - CylindersModel::instance()->updateDive(); - return true; - } - if (!gasmix_distance(&cyl->gasmix, &mix)) - return true; - } - qDebug("too many gases"); - return false; -} - -int DivePlannerPointsModel::lastEnteredPoint() -{ - for (int i = divepoints.count() - 1; i >= 0; i--) - if (divepoints.at(i).entered) - return i; - return -1; -} - -int DivePlannerPointsModel::addStop(int milimeters, int seconds, gasmix *gas_in, int ccpoint, bool entered) -{ - struct gasmix air = { 0 }; - struct gasmix gas = { 0 }; - bool usePrevious = false; - if (gas_in) - gas = *gas_in; - else - usePrevious = true; - if (recalcQ()) - removeDeco(); - - int row = divepoints.count(); - if (seconds == 0 && milimeters == 0 && row != 0) { - /* this is only possible if the user clicked on the 'plus' sign on the DivePoints Table */ - const divedatapoint t = divepoints.at(lastEnteredPoint()); - milimeters = t.depth; - seconds = t.time + 600; // 10 minutes. - gas = t.gasmix; - ccpoint = t.setpoint; - } else if (seconds == 0 && milimeters == 0 && row == 0) { - milimeters = M_OR_FT(5, 15); // 5m / 15ft - seconds = 600; // 10 min - //Default to the first defined gas, if we got one. - cylinder_t *cyl = &displayed_dive.cylinder[0]; - if (cyl) - gas = cyl->gasmix; - } - if (!usePrevious) - if (!addGas(gas)) - qDebug("addGas failed"); // FIXME add error propagation - - // check if there's already a new stop before this one: - for (int i = 0; i < row; i++) { - const divedatapoint &dp = divepoints.at(i); - if (dp.time == seconds) { - row = i; - beginRemoveRows(QModelIndex(), row, row); - divepoints.remove(row); - endRemoveRows(); - break; - } - if (dp.time > seconds) { - row = i; - break; - } - } - // Previous, actually means next as we are typically subdiving a segment and the gas for - // the segment is determined by the waypoint at the end. - if (usePrevious) { - if (row < divepoints.count()) { - gas = divepoints.at(row).gasmix; - } else if (row > 0) { - gas = divepoints.at(row - 1).gasmix; - } else { - if (!addGas(air)) - qDebug("addGas failed"); // FIXME add error propagation - - } - } - - // add the new stop - beginInsertRows(QModelIndex(), row, row); - divedatapoint point; - point.depth = milimeters; - point.time = seconds; - point.gasmix = gas; - point.setpoint = ccpoint; - point.entered = entered; - point.next = NULL; - divepoints.append(point); - std::sort(divepoints.begin(), divepoints.end(), divePointsLessThan); - endInsertRows(); - return row; -} - -void DivePlannerPointsModel::editStop(int row, divedatapoint newData) -{ - /* - * When moving divepoints rigorously, we might end up with index - * out of range, thus returning the last one instead. - */ - if (row >= divepoints.count()) - return; - divepoints[row] = newData; - std::sort(divepoints.begin(), divepoints.end(), divePointsLessThan); - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -int DivePlannerPointsModel::size() -{ - return divepoints.size(); -} - -divedatapoint DivePlannerPointsModel::at(int row) -{ - /* - * When moving divepoints rigorously, we might end up with index - * out of range, thus returning the last one instead. - */ - if (row >= divepoints.count()) - return divepoints.at(divepoints.count() - 1); - return divepoints.at(row); -} - -void DivePlannerPointsModel::remove(const QModelIndex &index) -{ - int i; - int rows = rowCount(); - if (index.column() != REMOVE || rowCount() == 1) - return; - - divedatapoint dp = at(index.row()); - if (!dp.entered) - return; - - if (QApplication::keyboardModifiers() & Qt::ControlModifier) { - beginRemoveRows(QModelIndex(), index.row(), rows - 1); - for (i = rows - 1; i >= index.row(); i--) - divepoints.remove(i); - } else { - beginRemoveRows(QModelIndex(), index.row(), index.row()); - divepoints.remove(index.row()); - } - endRemoveRows(); -} - -struct diveplan &DivePlannerPointsModel::getDiveplan() -{ - return diveplan; -} - -void DivePlannerPointsModel::cancelPlan() -{ - if (mode == PLAN && rowCount()) { - if (QMessageBox::warning(MainWindow::instance(), TITLE_OR_TEXT(tr("Discard the plan?"), - tr("You are about to discard your plan.")), - QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Discard) != QMessageBox::Discard) { - return; - } - } - setPlanMode(NOTHING); - free_dps(&diveplan); - - emit planCanceled(); -} - -DivePlannerPointsModel::Mode DivePlannerPointsModel::currentMode() const -{ - return mode; -} - -QVector<QPair<int, int> > DivePlannerPointsModel::collectGases(struct dive *d) -{ - QVector<QPair<int, int> > l; - for (int i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = &d->cylinder[i]; - if (!cylinder_nodata(cyl)) - l.push_back(qMakePair(get_o2(&cyl->gasmix), get_he(&cyl->gasmix))); - } - return l; -} -void DivePlannerPointsModel::rememberTanks() -{ - oldGases = collectGases(&displayed_dive); -} - -bool DivePlannerPointsModel::tankInUse(struct gasmix gasmix) -{ - for (int j = 0; j < rowCount(); j++) { - divedatapoint &p = divepoints[j]; - if (p.time == 0) // special entries that hold the available gases - continue; - if (!p.entered) // removing deco gases is ok - continue; - if (gasmix_distance(&p.gasmix, &gasmix) < 100) - return true; - } - return false; -} - -void DivePlannerPointsModel::tanksUpdated() -{ - // we don't know exactly what changed - what we care about is - // "did a gas change on us". So we look through the diveplan to - // see if there is a gas that is now missing and if there is, we - // replace it with the matching new gas. - QVector<QPair<int, int> > gases = collectGases(&displayed_dive); - if (gases.count() == oldGases.count()) { - // either nothing relevant changed, or exactly ONE gasmix changed - for (int i = 0; i < gases.count(); i++) { - if (gases.at(i) != oldGases.at(i)) { - if (oldGases.count(oldGases.at(i)) > 1) { - // we had this gas more than once, so don't - // change segments that used this gas as it still exists - break; - } - for (int j = 0; j < rowCount(); j++) { - divedatapoint &p = divepoints[j]; - struct gasmix gas; - gas.o2.permille = oldGases.at(i).first; - gas.he.permille = oldGases.at(i).second; - if (gasmix_distance(&gas, &p.gasmix) < 100) { - p.gasmix.o2.permille = gases.at(i).first; - p.gasmix.he.permille = gases.at(i).second; - } - } - break; - } - } - } - emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); -} - -void DivePlannerPointsModel::clear() -{ - bool oldRecalc = setRecalc(false); - - CylindersModel::instance()->updateDive(); - if (rowCount() > 0) { - beginRemoveRows(QModelIndex(), 0, rowCount() - 1); - divepoints.clear(); - endRemoveRows(); - } - CylindersModel::instance()->clear(); - setRecalc(oldRecalc); -} - -void DivePlannerPointsModel::createTemporaryPlan() -{ - // Get the user-input and calculate the dive info - free_dps(&diveplan); - int lastIndex = -1; - for (int i = 0; i < rowCount(); i++) { - divedatapoint p = at(i); - int deltaT = lastIndex != -1 ? p.time - at(lastIndex).time : p.time; - lastIndex = i; - if (i == 0 && prefs.drop_stone_mode) { - /* Okay, we add a fist segment where we go down to depth */ - plan_add_segment(&diveplan, p.depth / prefs.descrate, p.depth, p.gasmix, p.setpoint, true); - deltaT -= p.depth / prefs.descrate; - } - if (p.entered) - plan_add_segment(&diveplan, deltaT, p.depth, p.gasmix, p.setpoint, true); - } - - // what does the cache do??? - char *cache = NULL; - struct divedatapoint *dp = NULL; - for (int i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = &displayed_dive.cylinder[i]; - if (cyl->depth.mm) { - dp = create_dp(0, cyl->depth.mm, cyl->gasmix, 0); - if (diveplan.dp) { - dp->next = diveplan.dp; - diveplan.dp = dp; - } else { - dp->next = NULL; - diveplan.dp = dp; - } - } - } -#if DEBUG_PLAN - dump_plan(&diveplan); -#endif - if (plannerModel->recalcQ() && !diveplan_empty(&diveplan)) { - plan(&diveplan, &cache, isPlanner(), false); - MainWindow::instance()->setPlanNotes(displayed_dive.notes); - } - // throw away the cache - free(cache); -#if DEBUG_PLAN - save_dive(stderr, &displayed_dive); - dump_plan(&diveplan); -#endif -} - -void DivePlannerPointsModel::deleteTemporaryPlan() -{ - free_dps(&diveplan); -} - -void DivePlannerPointsModel::savePlan() -{ - createPlan(false); -} - -void DivePlannerPointsModel::saveDuplicatePlan() -{ - createPlan(true); -} - -void DivePlannerPointsModel::createPlan(bool replanCopy) -{ - // Ok, so, here the diveplan creates a dive - char *cache = NULL; - bool oldRecalc = plannerModel->setRecalc(false); - removeDeco(); - createTemporaryPlan(); - plannerModel->setRecalc(oldRecalc); - - //TODO: C-based function here? - bool did_deco = plan(&diveplan, &cache, isPlanner(), true); - if (!current_dive || displayed_dive.id != current_dive->id) { - // we were planning a new dive, not re-planning an existing on - record_dive(clone_dive(&displayed_dive)); - } else if (current_dive && displayed_dive.id == current_dive->id) { - // we are replanning a dive - make sure changes are reflected - // correctly in the dive structure and copy it back into the dive table - displayed_dive.maxdepth.mm = 0; - displayed_dive.dc.maxdepth.mm = 0; - fixup_dive(&displayed_dive); - if (replanCopy) { - struct dive *copy = alloc_dive(); - copy_dive(current_dive, copy); - copy->id = 0; - copy->divetrip = NULL; - if (current_dive->divetrip) - add_dive_to_trip(copy, current_dive->divetrip); - record_dive(copy); - QString oldnotes(current_dive->notes); - if (oldnotes.indexOf(QString(disclaimer)) >= 0) - oldnotes.truncate(oldnotes.indexOf(QString(disclaimer))); - if (did_deco) - oldnotes.append(displayed_dive.notes); - displayed_dive.notes = strdup(oldnotes.toUtf8().data()); - } - copy_dive(&displayed_dive, current_dive); - } - mark_divelist_changed(true); - - // Remove and clean the diveplan, so we don't delete - // the dive by mistake. - free_dps(&diveplan); - setPlanMode(NOTHING); - planCreated(); -} - PlannerDetails::PlannerDetails(QWidget *parent) : QWidget(parent) { ui.setupUi(this); diff --git a/qt-ui/diveplanner.h b/qt-ui/diveplanner.h index 42e0dc44a..8c7ff9c46 100644 --- a/qt-ui/diveplanner.h +++ b/qt-ui/diveplanner.h @@ -10,113 +10,7 @@ class QListView; class QModelIndex; - -class DivePlannerPointsModel : public QAbstractTableModel { - Q_OBJECT -public: - static DivePlannerPointsModel *instance(); - enum Sections { - REMOVE, - DEPTH, - DURATION, - RUNTIME, - GAS, - CCSETPOINT, - COLUMNS - }; - enum Mode { - NOTHING, - PLAN, - ADD - }; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - void gaschange(const QModelIndex &index, QString newgas); - void removeSelectedPoints(const QVector<int> &rows); - void setPlanMode(Mode mode); - bool isPlanner(); - void createSimpleDive(); - void setupStartTime(); - void clear(); - Mode currentMode() const; - bool setRecalc(bool recalc); - bool recalcQ(); - void tanksUpdated(); - void rememberTanks(); - bool tankInUse(struct gasmix gasmix); - void setupCylinders(); - /** - * @return the row number. - */ - void editStop(int row, divedatapoint newData); - divedatapoint at(int row); - int size(); - struct diveplan &getDiveplan(); - QStringList &getGasList(); - QVector<QPair<int, int> > collectGases(dive *d); - int lastEnteredPoint(); - void removeDeco(); - static bool addingDeco; - -public -slots: - int addStop(int millimeters = 0, int seconds = 0, struct gasmix *gas = 0, int ccpoint = 0, bool entered = true); - void addCylinder_clicked(); - void setGFHigh(const int gfhigh); - void triggerGFHigh(); - void setGFLow(const int ghflow); - void triggerGFLow(); - void setSurfacePressure(int pressure); - void setSalinity(int salinity); - int getSurfacePressure(); - void setBottomSac(double sac); - void setDecoSac(double sac); - void setStartTime(const QTime &t); - void setStartDate(const QDate &date); - void setLastStop6m(bool value); - void setDropStoneMode(bool value); - void setVerbatim(bool value); - void setDisplayRuntime(bool value); - void setDisplayDuration(bool value); - void setDisplayTransitions(bool value); - void setRecreationalMode(bool value); - void setSafetyStop(bool value); - void savePlan(); - void saveDuplicatePlan(); - void remove(const QModelIndex &index); - void cancelPlan(); - void createTemporaryPlan(); - void deleteTemporaryPlan(); - void loadFromDive(dive *d); - void emitDataChanged(); - void setRebreatherMode(int mode); - void setReserveGas(int reserve); - -signals: - void planCreated(); - void planCanceled(); - void cylinderModelEdited(); - void startTimeChanged(QDateTime); - void recreationChanged(bool); - -private: - explicit DivePlannerPointsModel(QObject *parent = 0); - bool addGas(struct gasmix mix); - void createPlan(bool replanCopy); - struct diveplan diveplan; - Mode mode; - bool recalc; - QVector<divedatapoint> divepoints; - QVector<sample> backupSamples; // For editing added dives. - QVector<QPair<int, int> > oldGases; - QDateTime startTime; - int tempGFHigh; - int tempGFLow; -}; +class DivePlannerPointsModel; class DiveHandler : public QObject, public QGraphicsEllipseItem { Q_OBJECT @@ -205,7 +99,4 @@ private: Ui::plannerDetails ui; }; - -QString dpGasToStr(const divedatapoint &p); - #endif // DIVEPLANNER_H diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp index 3d17bcdbf..0159029e2 100644 --- a/qt-ui/maintab.cpp +++ b/qt-ui/maintab.cpp @@ -10,12 +10,14 @@ #include "helpers.h" #include "statistics.h" #include "modeldelegates.h" +#include "diveplannermodel.h" #include "models.h" #include "divelistview.h" #include "display.h" #include "profile/profilewidget2.h" #include "diveplanner.h" #include "divesitehelpers.h" +#include "cylindermodel.h" #if defined(FBSUPPORT) #include "socialnetworks.h" diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index e94bce136..9c36b6731 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -29,6 +29,7 @@ #include <QPrintDialog> #include "printdialog.h" #endif +#include "diveplannermodel.h" #include "divelogimportdialog.h" #include "divelogexportdialog.h" #include "usersurvey.h" diff --git a/qt-ui/modeldelegates.cpp b/qt-ui/modeldelegates.cpp index 66533b652..21d497562 100644 --- a/qt-ui/modeldelegates.cpp +++ b/qt-ui/modeldelegates.cpp @@ -2,6 +2,7 @@ #include "dive.h" #include "gettextfromc.h" #include "mainwindow.h" +#include "cylindermodel.h" #include "models.h" #include "starwidget.h" #include "profile/profilewidget2.h" diff --git a/qt-ui/profile/diveprofileitem.cpp b/qt-ui/profile/diveprofileitem.cpp index 7d29d28b4..373dd0027 100644 --- a/qt-ui/profile/diveprofileitem.cpp +++ b/qt-ui/profile/diveprofileitem.cpp @@ -6,6 +6,7 @@ #include "dive.h" #include "profile.h" #include "preferences.h" +#include "diveplannermodel.h" #include "helpers.h" #include "libdivecomputer/parser.h" #include "mainwindow.h" @@ -758,7 +759,7 @@ void DiveGasPressureItem::plotPressureValue(int mbar, int sec, QFlags<Qt::Alignm void DiveGasPressureItem::plotGasValue(int mbar, int sec, struct gasmix gasmix, QFlags<Qt::AlignmentFlag> align, double gasname_offset) { - QString gas = gasToStr(gasmix); + QString gas = get_gas_string(gasmix); DiveTextItem *text = new DiveTextItem(this); text->setPos(hAxis->posAtValue(sec), vAxis->posAtValue(mbar) + gasname_offset ); text->setText(gas); diff --git a/qt-ui/profile/profilewidget2.cpp b/qt-ui/profile/profilewidget2.cpp index a426ceef2..d70d88763 100644 --- a/qt-ui/profile/profilewidget2.cpp +++ b/qt-ui/profile/profilewidget2.cpp @@ -11,6 +11,7 @@ #include "tankitem.h" #include "pref.h" #include "divepicturewidget.h" +#include "diveplannermodel.h" #include "models.h" #include "maintab.h" #include "diveplanner.h" @@ -1562,7 +1563,7 @@ void ProfileWidget2::repositionDiveHandlers() QLineF line(p1, p2); QPointF pos = line.pointAt(0.5); gases[i]->setPos(pos); - gases[i]->setText(dpGasToStr(datapoint)); + gases[i]->setText(get_divepoint_gas_string(datapoint)); gases[i]->setVisible(datapoint.entered && (i == 0 || gases[i]->text() != gases[i-1]->text())); } diff --git a/qthelper.cpp b/qthelper.cpp index 0354a6031..16354e34b 100644 --- a/qthelper.cpp +++ b/qthelper.cpp @@ -897,3 +897,15 @@ picture_load_exit: free(mem.buffer); return; } + +QString get_gas_string(struct gasmix gas) +{ + uint o2 = (get_o2(&gas) + 5) / 10, he = (get_he(&gas) + 5) / 10; + QString result = gasmix_is_air(&gas) ? QObject::tr("AIR") : he == 0 ? (o2 == 100 ? QObject::tr("OXYGEN") : QString("EAN%1").arg(o2, 2, 10, QChar('0'))) : QString("%1/%2").arg(o2).arg(he); + return result; +} + +QString get_divepoint_gas_string(const divedatapoint &p) +{ + return get_gas_string(p.gasmix); +} diff --git a/qthelper.h b/qthelper.h index b0765aeda..0b1e3b111 100644 --- a/qthelper.h +++ b/qthelper.h @@ -16,7 +16,8 @@ QString weight_string(int weight_in_grams); bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out = 0); extern "C" const char *printGPSCoords(int lat, int lon); QList<int> getDivesInTrip(dive_trip_t *trip); -QString gasToStr(struct gasmix gas); +QString get_gas_string(struct gasmix gas); +QString get_divepoint_gas_string(const divedatapoint& dp); void read_hashes(); void write_hashes(); void updateHash(struct picture *picture); |