diff options
author | Dirk Hohndel <dirk@hohndel.org> | 2013-05-17 22:01:41 -0700 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2013-05-17 22:01:41 -0700 |
commit | f3f7bf51fa1dbe9cdb859e1a45b20c108613275b (patch) | |
tree | a28814b4d3d59ff26b41ecfe71670c03fe2ce71d /qt-ui | |
parent | 082ec43eeabe92c7d65d3ab0f189e8fb43016f35 (diff) | |
parent | 56dbb7c2ff697a393f5051e2b5363bd4c0f2bb6e (diff) | |
download | subsurface-f3f7bf51fa1dbe9cdb859e1a45b20c108613275b.tar.gz |
Merge branch 'Qt'
After the 3.1 release it is time to shift the focus on the Qt effort - and
the best way to do this is to merge the changes in the Qt branch into
master.
Linus was extremely nice and did a merge for me. I decided to do my own
merge instead (which by accident actually based on a different version of
the Qt branch) and then used his merge to double check what I was doing.
I resolved a few things differently but overall what we did was very much
the same (and I say this with pride since Linus is a professional git
merger)
Here's his merge commit message:
This is a rough and tumble merge of the Qt branch into 'master',
trying to sort out the conflicts as best as I could.
There were two major kinds of conflicts:
- the Makefile changes, in particular the split of the single
Makefile into Rules.mk and Configure.mk, along with the obvious Qt
build changes themselves.
Those changes conflicted with some of the updates done in mainline
wrt "release" targets and some helper macros ($(NAME) etc).
Resolved by largely taking the Qt branch versions, and then editing
in the most obvious parts of the Makefile updates from mainline.
NOTE! The script/get_version shell script was made to just fail
silently on not finding a git repository, which avoided having to
take some particularly ugly Makefile changes.
- Various random updates in mainline to support things like dive tags.
The conflicts were mainly to the gtk GUI parts, which obviously
looked different afterwards. I fixed things up to look like the
newer code, but since the gtk files themselves are actually dead in
the Qt branch, this is largely irrelevant.
NOTE! This does *NOT* introduce the equivalent Qt functionality.
The fields are there in the code now, but there's no Qt UI for the
whole dive tag stuff etc.
This seems to compile for me (although I have to force
"QMAKE=qmake-qt4" on f19), and results in a Linux binary that seems to
work, but it is otherwise largely untested.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Diffstat (limited to 'qt-ui')
-rw-r--r-- | qt-ui/addcylinderdialog.cpp | 56 | ||||
-rw-r--r-- | qt-ui/addcylinderdialog.h | 33 | ||||
-rw-r--r-- | qt-ui/addcylinderdialog.ui | 303 | ||||
-rw-r--r-- | qt-ui/addweightsystemdialog.cpp | 39 | ||||
-rw-r--r-- | qt-ui/addweightsystemdialog.h | 30 | ||||
-rw-r--r-- | qt-ui/addweightsystemdialog.ui | 109 | ||||
-rw-r--r-- | qt-ui/divelistview.cpp | 132 | ||||
-rw-r--r-- | qt-ui/divelistview.h | 40 | ||||
-rw-r--r-- | qt-ui/globe.cpp | 113 | ||||
-rw-r--r-- | qt-ui/globe.h | 34 | ||||
-rw-r--r-- | qt-ui/maintab.cpp | 204 | ||||
-rw-r--r-- | qt-ui/maintab.h | 46 | ||||
-rw-r--r-- | qt-ui/maintab.ui | 885 | ||||
-rw-r--r-- | qt-ui/mainwindow.cpp | 464 | ||||
-rw-r--r-- | qt-ui/mainwindow.h | 89 | ||||
-rw-r--r-- | qt-ui/mainwindow.ui | 383 | ||||
-rw-r--r-- | qt-ui/modeldelegates.cpp | 49 | ||||
-rw-r--r-- | qt-ui/modeldelegates.h | 15 | ||||
-rw-r--r-- | qt-ui/models.cpp | 705 | ||||
-rw-r--r-- | qt-ui/models.h | 126 | ||||
-rw-r--r-- | qt-ui/plotareascene.cpp | 6 | ||||
-rw-r--r-- | qt-ui/plotareascene.h | 6 | ||||
-rw-r--r-- | qt-ui/profilegraphics.cpp | 1397 | ||||
-rw-r--r-- | qt-ui/profilegraphics.h | 110 | ||||
-rw-r--r-- | qt-ui/starwidget.cpp | 100 | ||||
-rw-r--r-- | qt-ui/starwidget.h | 37 |
26 files changed, 5511 insertions, 0 deletions
diff --git a/qt-ui/addcylinderdialog.cpp b/qt-ui/addcylinderdialog.cpp new file mode 100644 index 000000000..5b91617ed --- /dev/null +++ b/qt-ui/addcylinderdialog.cpp @@ -0,0 +1,56 @@ +/* + * addcylinderdialog.cpp + * + * classes for the add cylinder dialog of Subsurface + * + */ +#include "addcylinderdialog.h" +#include "ui_addcylinderdialog.h" +#include <QComboBox> +#include <QDoubleSpinBox> +#include "../conversions.h" +#include "models.h" + +AddCylinderDialog::AddCylinderDialog(QWidget *parent) : ui(new Ui::AddCylinderDialog()) +, tankInfoModel(new TankInfoModel()) +{ + ui->setupUi(this); + ui->cylinderType->setModel(tankInfoModel); +} + +void AddCylinderDialog::setCylinder(cylinder_t *cylinder) +{ + double volume, pressure; + int index; + + currentCylinder = cylinder; + convert_volume_pressure(cylinder->type.size.mliter, cylinder->type.workingpressure.mbar, &volume, &pressure); + + index = ui->cylinderType->findText(QString(cylinder->type.description)); + ui->cylinderType->setCurrentIndex(index); + ui->size->setValue(volume); + ui->pressure->setValue(pressure); + + ui->o2percent->setValue(cylinder->gasmix.o2.permille / 10.0); + ui->hepercent->setValue(cylinder->gasmix.he.permille / 10.0); + + convert_pressure(cylinder->start.mbar, &pressure); + ui->start->setValue(pressure); + + convert_pressure(cylinder->end.mbar, &pressure); + ui->end->setValue(pressure); +} + +void AddCylinderDialog::updateCylinder() +{ + QByteArray description = ui->cylinderType->currentText().toLocal8Bit(); + + currentCylinder->type.description = description.data(); + currentCylinder->type.size.mliter = ui->size->value(); + currentCylinder->type.workingpressure.mbar = ui->pressure->value(); + currentCylinder->gasmix.o2.permille = ui->o2percent->value(); + currentCylinder->gasmix.he.permille = ui->hepercent->value(); + currentCylinder->start.mbar = ui->start->value(); + currentCylinder->end.mbar = ui->end->value(); +} + diff --git a/qt-ui/addcylinderdialog.h b/qt-ui/addcylinderdialog.h new file mode 100644 index 000000000..fc68faa72 --- /dev/null +++ b/qt-ui/addcylinderdialog.h @@ -0,0 +1,33 @@ +/* + * addcylinderdialog.h + * + * header file for the add cylinder dialog of Subsurface + * + */ +#ifndef ADDCYLINDERDIALOG_H +#define ADDCYLINDERDIALOG_H + +#include <QDialog> +#include "../dive.h" + +namespace Ui{ + class AddCylinderDialog; +} + +class TankInfoModel; + +class AddCylinderDialog : public QDialog{ + Q_OBJECT +public: + explicit AddCylinderDialog(QWidget* parent = 0); + void setCylinder(cylinder_t *cylinder); + void updateCylinder(); + +private: + Ui::AddCylinderDialog *ui; + cylinder_t *currentCylinder; + TankInfoModel *tankInfoModel; +}; + + +#endif diff --git a/qt-ui/addcylinderdialog.ui b/qt-ui/addcylinderdialog.ui new file mode 100644 index 000000000..8bb0428b7 --- /dev/null +++ b/qt-ui/addcylinderdialog.ui @@ -0,0 +1,303 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AddCylinderDialog</class> + <widget class="QDialog" name="AddCylinderDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>408</width> + <height>298</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" rowspan="2"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Cylinder</string> + </property> + <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::ExpandingFieldsGrow</enum> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Type</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="cylinderType"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Size</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QDoubleSpinBox" name="size"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Pressure</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QSpinBox" name="pressure"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Pressure</string> + </property> + <layout class="QFormLayout" name="formLayout_2"> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Start</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>End</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="start"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QSpinBox" name="end"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="checkBox"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="1"> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Gas Mix</string> + </property> + <layout class="QFormLayout" name="formLayout_3"> + <item row="1" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>O2%</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QDoubleSpinBox" name="o2percent"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>He%</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QDoubleSpinBox" name="hepercent"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="checkBox_2"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>AddCylinderDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>269</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>260</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>AddCylinderDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>290</x> + <y>269</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>260</y> + </hint> + </hints> + </connection> + <connection> + <sender>checkBox</sender> + <signal>clicked(bool)</signal> + <receiver>start</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>216</x> + <y>46</y> + </hint> + <hint type="destinationlabel"> + <x>280</x> + <y>66</y> + </hint> + </hints> + </connection> + <connection> + <sender>checkBox</sender> + <signal>clicked(bool)</signal> + <receiver>end</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>226</x> + <y>48</y> + </hint> + <hint type="destinationlabel"> + <x>268</x> + <y>100</y> + </hint> + </hints> + </connection> + <connection> + <sender>checkBox_2</sender> + <signal>clicked(bool)</signal> + <receiver>o2percent</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>214</x> + <y>165</y> + </hint> + <hint type="destinationlabel"> + <x>260</x> + <y>190</y> + </hint> + </hints> + </connection> + <connection> + <sender>checkBox_2</sender> + <signal>clicked(bool)</signal> + <receiver>hepercent</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>228</x> + <y>165</y> + </hint> + <hint type="destinationlabel"> + <x>262</x> + <y>216</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/qt-ui/addweightsystemdialog.cpp b/qt-ui/addweightsystemdialog.cpp new file mode 100644 index 000000000..48a399de9 --- /dev/null +++ b/qt-ui/addweightsystemdialog.cpp @@ -0,0 +1,39 @@ +/* + * addweightsystemdialog.cpp + * + * classes for the add weightsystem dialog of Subsurface + * + */ +#include "addweightsystemdialog.h" +#include "ui_addweightsystemdialog.h" +#include <QComboBox> +#include <QDoubleSpinBox> +#include "../conversions.h" +#include "models.h" + +AddWeightsystemDialog::AddWeightsystemDialog(QWidget *parent) : ui(new Ui::AddWeightsystemDialog()) +{ + ui->setupUi(this); + currentWeightsystem = NULL; +} + +void AddWeightsystemDialog::setWeightsystem(weightsystem_t *ws) +{ + currentWeightsystem = ws; + + ui->description->insert(QString(ws->description)); + if (get_units()->weight == units::KG) + ui->weight->setValue(ws->weight.grams / 1000); + else + ui->weight->setValue(grams_to_lbs(ws->weight.grams)); +} + +void AddWeightsystemDialog::updateWeightsystem() +{ + currentWeightsystem->description = strdup(ui->description->text().toUtf8().data()); + if (get_units()->weight == units::KG) + currentWeightsystem->weight.grams = ui->weight->value() * 1000; + else + currentWeightsystem->weight.grams = lbs_to_grams(ui->weight->value()); +} + diff --git a/qt-ui/addweightsystemdialog.h b/qt-ui/addweightsystemdialog.h new file mode 100644 index 000000000..e99dc08d8 --- /dev/null +++ b/qt-ui/addweightsystemdialog.h @@ -0,0 +1,30 @@ +/* + * addweightsystemdialog.h + * + * header file for the add weightsystem dialog of Subsurface + * + */ +#ifndef ADDWEIGHTSYSTEMDIALOG_H +#define ADDWEIGHTSYSTEMDIALOG_H + +#include <QDialog> +#include "../dive.h" + +namespace Ui{ + class AddWeightsystemDialog; +} + +class AddWeightsystemDialog : public QDialog{ + Q_OBJECT +public: + explicit AddWeightsystemDialog(QWidget* parent = 0); + void setWeightsystem(weightsystem_t *ws); + void updateWeightsystem(); + +private: + Ui::AddWeightsystemDialog *ui; + weightsystem_t *currentWeightsystem; +}; + + +#endif diff --git a/qt-ui/addweightsystemdialog.ui b/qt-ui/addweightsystemdialog.ui new file mode 100644 index 000000000..11e60d562 --- /dev/null +++ b/qt-ui/addweightsystemdialog.ui @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AddWeightsystemDialog</class> + <widget class="QDialog" name="AddWeightsystemDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>408</width> + <height>186</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" rowspan="2"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Weightsystem</string> + </property> + <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::ExpandingFieldsGrow</enum> + </property> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Description</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Weight</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="weight"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="inputMethodHints"> + <set>Qt::ImhFormattedNumbersOnly</set> + </property> + <property name="accelerated"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="description"/> + </item> + </layout> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>AddWeightsystemDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>269</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>260</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>AddWeightsystemDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>290</x> + <y>269</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>260</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/qt-ui/divelistview.cpp b/qt-ui/divelistview.cpp new file mode 100644 index 000000000..5c8a93ff0 --- /dev/null +++ b/qt-ui/divelistview.cpp @@ -0,0 +1,132 @@ +/* + * divelistview.cpp + * + * classes for the divelist of Subsurface + * + */ +#include "divelistview.h" +#include "models.h" +#include "modeldelegates.h" +#include <QApplication> +#include <QHeaderView> +#include <QDebug> +#include <QKeyEvent> +#include <QSortFilterProxyModel> + + +DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelection(false) +{ + setUniformRowHeights(true); + setItemDelegateForColumn(TreeItemDT::RATING, new StarWidgetsDelegate()); + QSortFilterProxyModel *model = new QSortFilterProxyModel(this); + setModel(model); +} + +void DiveListView::reload() +{ + QSortFilterProxyModel *m = qobject_cast<QSortFilterProxyModel*>(model()); + QAbstractItemModel *oldModel = m->sourceModel(); + if (oldModel) + oldModel->deleteLater(); + m->setSourceModel(new DiveTripModel(this)); + sortByColumn(0, Qt::DescendingOrder); + QModelIndex firstDiveOrTrip = m->index(0,0); + if (firstDiveOrTrip.isValid()){ + if (m->index(0,0, firstDiveOrTrip).isValid()) + setCurrentIndex(m->index(0,0, firstDiveOrTrip)); + else + setCurrentIndex(firstDiveOrTrip); + } +} + +void DiveListView::setModel(QAbstractItemModel* model) +{ + QTreeView::setModel(model); +} + +void DiveListView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command) +{ + if (mouseClickSelection) + QTreeView::setSelection(rect, command); +} + +void DiveListView::mousePressEvent(QMouseEvent* event) +{ + mouseClickSelection = true; + QTreeView::mousePressEvent(event); +} + +void DiveListView::mouseReleaseEvent(QMouseEvent* event) +{ + mouseClickSelection = false; + QTreeView::mouseReleaseEvent(event); +} + +void DiveListView::keyPressEvent(QKeyEvent* event) +{ + if(event->modifiers()) + mouseClickSelection = true; + QTreeView::keyPressEvent(event); +} + +void DiveListView::keyReleaseEvent(QKeyEvent* event) +{ + mouseClickSelection = false; + QWidget::keyReleaseEvent(event); +} + +void DiveListView::currentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + if (!current.isValid()) + return; + const QAbstractItemModel *model = current.model(); + int selectedDive = 0; + struct dive *dive = (struct dive*) model->data(current, TreeItemDT::DIVE_ROLE).value<void*>(); + if (!dive) { // it's a trip! select first child. + dive = (struct dive*) model->data(current.child(0,0), TreeItemDT::DIVE_ROLE).value<void*>(); + selectedDive = get_divenr(dive); + }else{ + selectedDive = get_divenr(dive); + } + if (selectedDive == selected_dive) + return; + Q_EMIT currentDiveChanged(selectedDive); +} + +void DiveListView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + QList<QModelIndex> parents; + Q_FOREACH(const QModelIndex& index, deselected.indexes()) { + const QAbstractItemModel *model = index.model(); + struct dive *dive = (struct dive*) model->data(index, TreeItemDT::DIVE_ROLE).value<void*>(); + if (!dive) { // it's a trip! + if (model->rowCount(index)) { + expand(index); // leave this - even if it looks like it shouldn't be here. looks like I've found a Qt bug. + // the subselection is removed, but the painting is not. this cleans the area. + } + } else if (!parents.contains(index.parent())) { + parents.push_back(index.parent()); + } + } + + Q_FOREACH(const QModelIndex& index, selected.indexes()) { + const QAbstractItemModel *model = index.model(); + struct dive *dive = (struct dive*) model->data(index, TreeItemDT::DIVE_ROLE).value<void*>(); + if (!dive) { // it's a trip! + if (model->rowCount(index)) { + QItemSelection selection; + selection.select(index.child(0,0), index.child(model->rowCount(index) -1 , 0)); + selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows); + selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select | QItemSelectionModel::NoUpdate); + if (!isExpanded(index)) { + expand(index); + } + } + } else if (!parents.contains(index.parent())) { + parents.push_back(index.parent()); + } + } + + Q_FOREACH(const QModelIndex& index, parents) + expand(index); +} diff --git a/qt-ui/divelistview.h b/qt-ui/divelistview.h new file mode 100644 index 000000000..09830b7b5 --- /dev/null +++ b/qt-ui/divelistview.h @@ -0,0 +1,40 @@ +/* + * divelistview.h + * + * header file for the dive list of Subsurface + * + */ +#ifndef DIVELISTVIEW_H +#define DIVELISTVIEW_H + +/*! A view subclass for use with dives + + Note: calling this a list view might be misleading? + + +*/ + +#include <QTreeView> + +class DiveListView : public QTreeView +{ + Q_OBJECT +public: + DiveListView(QWidget *parent = 0); + void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void currentChanged(const QModelIndex& current, const QModelIndex& previous); + void setModel(QAbstractItemModel* model); + void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + void keyPressEvent(QKeyEvent* event); + void keyReleaseEvent(QKeyEvent*); + void setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command); + void reload(); + +Q_SIGNALS: + void currentDiveChanged(int divenr); +private: + bool mouseClickSelection; +}; + +#endif // DIVELISTVIEW_H diff --git a/qt-ui/globe.cpp b/qt-ui/globe.cpp new file mode 100644 index 000000000..93d1ab7c4 --- /dev/null +++ b/qt-ui/globe.cpp @@ -0,0 +1,113 @@ +#include "globe.h" +#include "../dive.h" + +#include <QDebug> + +#include <marble/AbstractFloatItem.h> +#include <marble/GeoDataPlacemark.h> +#include <marble/GeoDataDocument.h> +#include <marble/MarbleModel.h> +#include <marble/GeoDataTreeModel.h> + +#include <QMouseEvent> +#include <QMessageBox> + +GlobeGPS::GlobeGPS(QWidget* parent) : MarbleWidget(parent), loadedDives(0) +{ + setMapThemeId("earth/bluemarble/bluemarble.dgml"); + setProjection( Marble::Spherical ); + + setAnimationsEnabled(true); + setShowClouds( false ); + setShowBorders( false ); + setShowPlaces( false ); + setShowCrosshairs( false ); + setShowGrid( false ); + setShowOverviewMap(false); + setShowScaleBar(false); + + Q_FOREACH( AbstractFloatItem * floatItem, floatItems() ){ + if ( floatItem && floatItem->nameId() == "compass" ) { + floatItem->setPosition( QPoint( 10, 10 ) ); + floatItem->setContentSize( QSize( 50, 50 ) ); + } + } +} + +void GlobeGPS::reload() +{ + if (loadedDives){ + model()->treeModel()->removeDocument(loadedDives); + delete loadedDives; + } + if (editingDiveCoords){ + editingDiveCoords = 0; + } + + loadedDives = new GeoDataDocument; + + int idx = 0; + struct dive *dive; + for_each_dive(idx, dive) { + if (dive_has_gps_location(dive)) { + // don't add dive locations twice. + if( diveLocations.contains( QString(dive->location))) + continue; + + GeoDataPlacemark *place = new GeoDataPlacemark( dive->location ); + place->setCoordinate(dive->longitude.udeg / 1000000.0,dive->latitude.udeg / 1000000.0 , 0, GeoDataCoordinates::Degree ); + loadedDives->append( place ); + } + } + model()->treeModel()->addDocument( loadedDives ); +} + +void GlobeGPS::centerOn(dive* dive) +{ + qreal longitude = dive->longitude.udeg / 1000000.0; + qreal latitude = dive->latitude.udeg / 1000000.0; + + if (!longitude || !latitude){ + prepareForGetDiveCoordinates(dive); + return; + } + + centerOn(longitude,latitude, true); +} + +void GlobeGPS::prepareForGetDiveCoordinates(dive* dive) +{ + QMessageBox::warning(parentWidget(), + tr("This dive has no location!"), + tr("Move the planet to the desired position, then \n double-click to set the new location of this dive.")); + + editingDiveCoords = dive; +} + +void GlobeGPS::changeDiveGeoPosition(qreal lon, qreal lat, GeoDataCoordinates::Unit unit) +{ + // convert to degrees if in radian. + if (unit == GeoDataCoordinates::Radian){ + lon = lon * 180 / M_PI; + lat = lat * 180 / M_PI; + } + + if (!editingDiveCoords){ + return; + } + + editingDiveCoords->latitude.udeg = (int) lat * 1000000.0; + editingDiveCoords->longitude.udeg = (int) lon * 1000000.0; + centerOn(lon, lat, true); + reload(); + editingDiveCoords = 0; +} + +void GlobeGPS::mousePressEvent(QMouseEvent* event) +{ + qreal lat, lon; + if (editingDiveCoords && geoCoordinates(event->pos().x(), event->pos().y(), lon,lat, GeoDataCoordinates::Radian)){ + changeDiveGeoPosition(lon, lat, GeoDataCoordinates::Radian); + } +} + diff --git a/qt-ui/globe.h b/qt-ui/globe.h new file mode 100644 index 000000000..5f207a502 --- /dev/null +++ b/qt-ui/globe.h @@ -0,0 +1,34 @@ +#ifndef GLOBE_H +#define GLOBE_H + +#include <marble/MarbleWidget.h> +#include <marble/GeoDataCoordinates.h> + +#include <QHash> + +using namespace Marble; +struct dive; + +class GlobeGPS : public MarbleWidget{ + Q_OBJECT + void prepareForGetDiveCoordinates(struct dive* dive); +public: + using MarbleWidget::centerOn; + GlobeGPS(QWidget *parent); + void reload(); + void centerOn(struct dive* dive); + +protected: + virtual void mousePressEvent(QMouseEvent* event); + +private: + GeoDataDocument *loadedDives; + QStringList diveLocations; + struct dive* editingDiveCoords; + +public Q_SLOTS: + void changeDiveGeoPosition(qreal lon,qreal lat,GeoDataCoordinates::Unit); + +}; + +#endif diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp new file mode 100644 index 000000000..70cb3caea --- /dev/null +++ b/qt-ui/maintab.cpp @@ -0,0 +1,204 @@ +/* + * maintab.cpp + * + * classes for the "notebook" area of the main window of Subsurface + * + */ +#include "maintab.h" +#include "ui_maintab.h" +#include "addcylinderdialog.h" +#include "addweightsystemdialog.h" +#include "../helpers.h" +#include "../statistics.h" + +#include <QLabel> + +MainTab::MainTab(QWidget *parent) : QTabWidget(parent), + ui(new Ui::MainTab()), + weightModel(new WeightModel()), + cylindersModel(new CylindersModel()) +{ + ui->setupUi(this); + ui->cylinders->setModel(cylindersModel); + ui->weights->setModel(weightModel); + + /* example of where code is more concise than Qt designer */ + QList<QObject *> infoTabWidgets = ui->infoTab->children(); + Q_FOREACH( QObject* obj, infoTabWidgets ){ + QLabel* label = qobject_cast<QLabel *>(obj); + if (label) + label->setAlignment(Qt::AlignHCenter); + } + QList<QObject *> statisticsTabWidgets = ui->statisticsTab->children(); + Q_FOREACH( QObject* obj, statisticsTabWidgets ){ + QLabel* label = qobject_cast<QLabel *>(obj); + if (label) + label->setAlignment(Qt::AlignHCenter); + } +} + +void MainTab::clearEquipment() +{ +} + +void MainTab::clearInfo() +{ + ui->sacText->clear(); + ui->otuText->clear(); + ui->oxygenHeliumText->clear(); + ui->gasUsedText->clear(); + ui->dateText->clear(); + ui->diveTimeText->clear(); + ui->surfaceIntervalText->clear(); + ui->maximumDepthText->clear(); + ui->averageDepthText->clear(); + ui->visibilityText->clear(); + ui->waterTemperatureText->clear(); + ui->airTemperatureText->clear(); + ui->airPressureText->clear(); +} + +void MainTab::clearStats() +{ + ui->maximumDepthAllText->clear(); + ui->minimumDepthAllText->clear(); + ui->averageDepthAllText->clear(); + ui->maximumSacAllText->clear(); + ui->minimumSacAllText->clear(); + ui->averageSacAllText->clear(); + ui->divesAllText->clear(); + ui->maximumTemperatureAllText->clear(); + ui->minimumTemperatureAllText->clear(); + ui->averageTemperatureAllText->clear(); + ui->totalTimeAllText->clear(); + ui->averageTimeAllText->clear(); + ui->longestAllText->clear(); + ui->shortestAllText->clear(); +} + +#define UPDATE_TEXT(d, field) \ + if (!d || !d->field) \ + ui->field->setText(""); \ + else \ + ui->field->setText(d->field) + + +void MainTab::updateDiveInfo(int dive) +{ + // So, this is what happens now: + // Every tab should be populated from this method, + // it will be called whenever a new dive is selected + // I'm already populating the 'notes' box + // to show how it can be done. + // If you are unsure about the name of something, + // open the file maintab.ui on the designer + // click on the item and check its objectName, + // the access is ui->objectName from here on. + volume_t sacVal; + struct dive *d = get_dive(dive); + UPDATE_TEXT(d, notes); + UPDATE_TEXT(d, location); + UPDATE_TEXT(d, suit); + UPDATE_TEXT(d, divemaster); + UPDATE_TEXT(d, buddy); + /* infoTab */ + if (d) { + ui->rating->setCurrentStars(d->rating); + ui->maximumDepthText->setText(get_depth_string(d->maxdepth, TRUE)); + ui->averageDepthText->setText(get_depth_string(d->meandepth, TRUE)); + ui->otuText->setText(QString("%1").arg(d->otu)); + ui->waterTemperatureText->setText(get_temperature_string(d->watertemp, TRUE)); + ui->airTemperatureText->setText(get_temperature_string(d->airtemp, TRUE)); + ui->gasUsedText->setText(get_volume_string(get_gas_used(d), TRUE)); + if ((sacVal.mliter = d->sac) > 0) + ui->sacText->setText(get_volume_string(sacVal, TRUE).append("/min")); + else + ui->sacText->clear(); + if (d->surface_pressure.mbar) + /* this is ALWAYS displayed in mbar */ + ui->airPressureText->setText(QString("%1mbar").arg(d->surface_pressure.mbar)); + else + ui->airPressureText->clear(); + } else { + ui->rating->setCurrentStars(0); + ui->sacText->clear(); + ui->otuText->clear(); + ui->oxygenHeliumText->clear(); + ui->dateText->clear(); + ui->diveTimeText->clear(); + ui->surfaceIntervalText->clear(); + ui->maximumDepthText->clear(); + ui->averageDepthText->clear(); + ui->visibilityText->clear(); + ui->waterTemperatureText->clear(); + ui->airTemperatureText->clear(); + ui->gasUsedText->clear(); + ui->airPressureText->clear(); + } + /* statisticsTab*/ + /* we can access the stats_selection struct, but how do we ensure the relevant dives are selected + * if we don't use the gtk widget to drive this? + * Maybe call process_selected_dives? Or re-write to query our Qt list view. + */ +// qDebug("max temp %u",stats_selection.max_temp); +// qDebug("min temp %u",stats_selection.min_temp); +} + +void MainTab::on_addCylinder_clicked() +{ + if (cylindersModel->rowCount() >= MAX_CYLINDERS) + return; + + AddCylinderDialog dialog(this); + cylinder_t *newCylinder = (cylinder_t*) malloc(sizeof(cylinder_t)); + newCylinder->type.description = ""; + + dialog.setCylinder(newCylinder); + int result = dialog.exec(); + if (result == QDialog::Rejected){ + return; + } + + dialog.updateCylinder(); + cylindersModel->add(newCylinder); +} + +void MainTab::on_editCylinder_clicked() +{ +} + +void MainTab::on_delCylinder_clicked() +{ +} + +void MainTab::on_addWeight_clicked() +{ + if (weightModel->rowCount() >= MAX_WEIGHTSYSTEMS) + return; + + AddWeightsystemDialog dialog(this); + weightsystem_t newWeightsystem; + newWeightsystem.description = ""; + newWeightsystem.weight.grams = 0; + + dialog.setWeightsystem(&newWeightsystem); + int result = dialog.exec(); + if (result == QDialog::Rejected) + return; + + dialog.updateWeightsystem(); + weightModel->add(&newWeightsystem); +} + +void MainTab::on_editWeight_clicked() +{ +} + +void MainTab::on_delWeight_clicked() +{ +} + +void MainTab::reload() +{ + cylindersModel->update(); +} diff --git a/qt-ui/maintab.h b/qt-ui/maintab.h new file mode 100644 index 000000000..e09781362 --- /dev/null +++ b/qt-ui/maintab.h @@ -0,0 +1,46 @@ +/* + * maintab.h + * + * header file for the main tab of Subsurface + * + */ +#ifndef MAINTAB_H +#define MAINTAB_H + +#include <QTabWidget> +#include <QDialog> + +#include "models.h" + +namespace Ui +{ + class MainTab; +} + +class MainTab : public QTabWidget +{ + Q_OBJECT +public: + MainTab(QWidget *parent); + void clearStats(); + void clearInfo(); + void clearEquipment(); + void reload(); + +public Q_SLOTS: + void on_addCylinder_clicked(); + void on_editCylinder_clicked(); + void on_delCylinder_clicked(); + void on_addWeight_clicked(); + void on_editWeight_clicked(); + void on_delWeight_clicked(); + + void updateDiveInfo(int dive); + +private: + Ui::MainTab *ui; + WeightModel *weightModel; + CylindersModel *cylindersModel; +}; + +#endif diff --git a/qt-ui/maintab.ui b/qt-ui/maintab.ui new file mode 100644 index 000000000..70f88caec --- /dev/null +++ b/qt-ui/maintab.ui @@ -0,0 +1,885 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainTab</class> + <widget class="QTabWidget" name="MainTab"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>325</height> + </rect> + </property> + <property name="windowTitle"> + <string>TabWidget</string> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="notesTab"> + <attribute name="title"> + <string>Dive Notes</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Location</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QLineEdit" name="location"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Divemaster</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Buddy</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLineEdit" name="divemaster"/> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="buddy"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string>Rating</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="label_19"> + <property name="text"> + <string>Suit</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLineEdit" name="suit"/> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>Notes</string> + </property> + </widget> + </item> + <item row="7" column="0" colspan="2"> + <widget class="QTextEdit" name="notes"/> + </item> + <item row="5" column="0"> + <widget class="StarWidget" name="rating" native="true"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="equipmentTab"> + <attribute name="title"> + <string>Equipment</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Cylinders</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QTableView" name="cylinders"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="editCylinder"> + <property name="text"> + <string>Edit</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="addCylinder"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="delCylinder"> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Weight</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QTableView" name="weights"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QPushButton" name="editWeight"> + <property name="text"> + <string>Edit</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="addWeight"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="delWeight"> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="infoTab"> + <attribute name="title"> + <string>Dive Info</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <spacer name="verticalSpacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QGridLayout" name="diveInfoUpperGridLayout"> + <property name="horizontalSpacing"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>15</number> + </property> + <property name="margin"> + <number>10</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="sacLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>SAC</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="outLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>OTU</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="oxygenHeliumLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>0²/He</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="gasUsedLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Gas Used</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QLabel" name="gasUsedText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="sacText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="otuText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="oxygenHeliumText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QGridLayout" name="diveInfoLowerGridLayout"> + <property name="margin"> + <number>10</number> + </property> + <property name="spacing"> + <number>15</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="dateLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Date</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="maximumDepthText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QLabel" name="visibilityText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="visibilityLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Visibility</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="waterTempertureLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Water Temp.</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="airTemperatureText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="diveTimeText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="surfaceIntervalText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QLabel" name="airPressureLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Air Pressure</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="averageDepthLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Ave. Depth</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="maximumDepthLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Max. Depth</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="airTempertatureLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Air Temp.</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="averageDepthText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QLabel" name="airPressureText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="waterTemperatureText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="dateText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="surfaceIntervalLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Interval</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="diveTimeLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Dive Time</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="statisticsTab"> + <attribute name="title"> + <string>Stats</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <spacer name="verticalSpacer_6"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QGridLayout" name="statsUpperGridLayout"> + <property name="margin"> + <number>10</number> + </property> + <property name="spacing"> + <number>15</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="maximumDepthAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Max. Depth</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="minimumDepthAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Min. Depth</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="averageDepthAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Ave. Depth</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="maximumDepthAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="minimumDepthAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="averageDepthAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="maximumSacAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Max. SAC</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="minimumSacAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Min. SAC</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="averageSacAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Ave. SAC</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="maximumSacAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="minimumSacAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QLabel" name="averageSacAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_5"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QGridLayout" name="statsLowerGridLayout"> + <property name="margin"> + <number>10</number> + </property> + <property name="spacing"> + <number>15</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="divesAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Dives</string> + </property> + <property name="alignment"> + <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="maximumTemperatureLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Max. Temp.</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="minimumTemperatureAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Min. Temp.</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="averageTemperatureAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Ave. Temp.</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="divesAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="maximumTemperatureAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="minimumTemperatureAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QLabel" name="averageTemperatureAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="totalTimeAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Total Time</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="averageTimeAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Ave. Time</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="longestAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Longest</string> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QLabel" name="shortestAllLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Shortest</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="totalTimeAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="averageTimeAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QLabel" name="longestAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="3" column="3"> + <widget class="QLabel" name="shortestAllText"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + <customwidgets> + <customwidget> + <class>StarWidget</class> + <extends>QWidget</extends> + <header>starwidget.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp new file mode 100644 index 000000000..c5a4e5ce3 --- /dev/null +++ b/qt-ui/mainwindow.cpp @@ -0,0 +1,464 @@ +/* + * mainwindow.cpp + * + * classes for the main UI window in Subsurface + */ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include <QVBoxLayout> +#include <QFileDialog> +#include <QMessageBox> +#include <QtDebug> +#include <QDateTime> +#include <QSettings> +#include <QCloseEvent> +#include <QApplication> +#include <QFontMetrics> + +#include "divelistview.h" +#include "starwidget.h" + +#include "glib.h" +#include "../dive.h" +#include "../divelist.h" +#include "../pref.h" +#include "modeldelegates.h" +#include "models.h" + +MainWindow::MainWindow() : ui(new Ui::MainWindow()) +{ + ui->setupUi(this); + readSettings(); + setWindowIcon(QIcon(":subsurface-icon")); + connect(ui->ListWidget, SIGNAL(currentDiveChanged(int)), this, SLOT(current_dive_changed(int))); + ui->ProfileWidget->setFocusProxy(ui->ListWidget); + ui->ListWidget->reload(); + ui->ListWidget->setFocus(); + ui->widget->reload(); +} + +void MainWindow::current_dive_changed(int divenr) +{ + select_dive(divenr); + ui->widget->centerOn(get_dive(selected_dive)); + redrawProfile(); + ui->InfoWidget->updateDiveInfo(divenr); +} + +void MainWindow::redrawProfile() +{ + ui->ProfileWidget->plot(get_dive(selected_dive)); +} + +void MainWindow::on_actionNew_triggered() +{ + qDebug("actionNew"); +} + +void MainWindow::on_actionOpen_triggered() +{ + QString filename = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::homePath(), filter()); + if (filename.isEmpty()) + return; + + // Needed to convert to char* + QByteArray fileNamePtr = filename.toLocal8Bit(); + + on_actionClose_triggered(); + + GError *error = NULL; + parse_file(fileNamePtr.data(), &error); + set_filename(fileNamePtr.data(), TRUE); + + if (error != NULL) { + QMessageBox::warning(this, "Error", error->message); + g_error_free(error); + error = NULL; + } + process_dives(FALSE, FALSE); + + ui->InfoWidget->reload(); + ui->widget->reload(); + ui->ListWidget->reload(); + ui->ListWidget->setFocus(); +} + +void MainWindow::on_actionSave_triggered() +{ + qDebug("actionSave"); +} + +void MainWindow::on_actionSaveAs_triggered() +{ + qDebug("actionSaveAs"); +} +void MainWindow::on_actionClose_triggered() +{ + if (unsaved_changes() && (askSaveChanges() == FALSE)) + return; + + /* free the dives and trips */ + while (dive_table.nr) + delete_single_dive(0); + + /* clear the selection and the statistics */ + selected_dive = -1; + + //WARNING: Port this to Qt. + //process_selected_dives(); + + ui->InfoWidget->clearStats(); + ui->InfoWidget->clearInfo(); + ui->InfoWidget->clearEquipment(); + ui->ProfileWidget->clear(); + ui->ListWidget->reload(); + + clear_events(); +#if USE_GTK_UI + show_dive_stats(NULL); + + /* redraw the screen */ + //WARNING: Port this to Qt. + dive_list_update_dives(); + + // WARNING? Port this to Qt. + show_dive_info(NULL); +#endif /* USE_GTK_UI */ +} + +void MainWindow::on_actionImport_triggered() +{ + qDebug("actionImport"); +} + +void MainWindow::on_actionExportUDDF_triggered() +{ + qDebug("actionExportUDDF"); +} + +void MainWindow::on_actionPrint_triggered() +{ + qDebug("actionPrint"); +} + +void MainWindow::on_actionPreferences_triggered() +{ + qDebug("actionPreferences"); +} + +void MainWindow::on_actionQuit_triggered() +{ + if (unsaved_changes() && (askSaveChanges() == FALSE)) + return; + writeSettings(); + QApplication::quit(); +} + +void MainWindow::on_actionDownloadDC_triggered() +{ + qDebug("actionDownloadDC"); +} + +void MainWindow::on_actionDownloadWeb_triggered() +{ + qDebug("actionDownloadWeb");} + +void MainWindow::on_actionEditDeviceNames_triggered() +{ + qDebug("actionEditDeviceNames");} + +void MainWindow::on_actionAddDive_triggered() +{ + qDebug("actionAddDive"); +} + +void MainWindow::on_actionRenumber_triggered() +{ + qDebug("actionRenumber"); +} + +void MainWindow::on_actionAutoGroup_triggered() +{ + qDebug("actionAutoGroup"); +} + +void MainWindow::on_actionToggleZoom_triggered() +{ + qDebug("actionToggleZoom"); +} + +void MainWindow::on_actionYearlyStatistics_triggered() +{ + qDebug("actionYearlyStatistics"); +} + +void MainWindow::on_actionViewList_triggered() +{ + ui->InfoWidget->setVisible(false); + ui->ListWidget->setVisible(true); + ui->ProfileWidget->setVisible(false); +} + +void MainWindow::on_actionViewProfile_triggered() +{ + ui->InfoWidget->setVisible(false); + ui->ListWidget->setVisible(false); + ui->ProfileWidget->setVisible(true); +} + +void MainWindow::on_actionViewInfo_triggered() +{ + ui->InfoWidget->setVisible(true); + ui->ListWidget->setVisible(false); + ui->ProfileWidget->setVisible(false); +} + +void MainWindow::on_actionViewAll_triggered() +{ + ui->InfoWidget->setVisible(true); + ui->ListWidget->setVisible(true); + ui->ProfileWidget->setVisible(true); +} + +void MainWindow::on_actionPreviousDC_triggered() +{ + dc_number--; + redrawProfile(); +} + +void MainWindow::on_actionNextDC_triggered() +{ + dc_number++; + redrawProfile(); +} + +void MainWindow::on_actionSelectEvents_triggered() +{ + qDebug("actionSelectEvents"); +} + +void MainWindow::on_actionInputPlan_triggered() +{ + qDebug("actionInputPlan"); +} + +void MainWindow::on_actionAboutSubsurface_triggered() +{ + qDebug("actionAboutSubsurface"); +} + +void MainWindow::on_actionUserManual_triggered() +{ + qDebug("actionUserManual"); +} + +QString MainWindow::filter() +{ + QString f; + f += "ALL ( *.xml *.XML *.uddf *.udcf *.UDFC *.jlb *.JLB "; +#ifdef LIBZIP + f += "*.sde *.SDE *.dld *.DLD "; +#endif +#ifdef SQLITE3 + f += "*.db"; +#endif + f += ");;"; + + f += "XML (*.xml *.XML);;"; + f += "UDDF (*.uddf);;"; + f += "UDCF (*.udcf *.UDCF);;"; + f += "JLB (*.jlb *.JLB);;"; + +#ifdef LIBZIP + f += "SDE (*.sde *.SDE);;"; + f += "DLD (*.dld *.DLD);;"; +#endif +#ifdef SQLITE3 + f += "DB (*.db)"; +#endif + + return f; +} + +bool MainWindow::askSaveChanges() +{ + QString message = ! existing_filename ? tr("You have unsaved changes\nWould you like to save those before closing the datafile?") + : tr("You have unsaved changes to file: %1 \nWould you like to save those before closing the datafile?").arg(existing_filename); + + if (QMessageBox::question(this, tr("Save Changes?"), message) == QMessageBox::Ok) { + // WARNING: Port. + // file_save(NULL,NULL); + return true; + } + return false; +} + +#define GET_UNIT(v, name, field, f, t) \ + v = settings.value(QString(name)); \ + if (v.isValid()) \ + prefs.units.field = (v.toInt() == (t)) ? (t) : (f) + +#define GET_BOOL(v, name, field) \ + v = settings.value(QString(name)); \ + if (v.isValid() && v.toInt()) \ + field = TRUE; \ + else \ + field = FALSE + +void MainWindow::readSettings() +{ + int i; + QVariant v; + QSettings settings("hohndel.org","subsurface"); + + settings.beginGroup("MainWindow"); + QSize sz = settings.value("size").value<QSize>(); + resize(sz); + ui->mainSplitter->restoreState(settings.value("mainSplitter").toByteArray()); + ui->infoProfileSplitter->restoreState(settings.value("infoProfileSplitter").toByteArray()); + settings.endGroup(); + + settings.beginGroup("ListWidget"); + /* if no width are set, use the calculated width for each column; + * for that to work we need to temporarily expand all rows */ + ui->ListWidget->expandAll(); + for (i = TreeItemDT::NR; i < TreeItemDT::COLUMNS; i++) { + QVariant width = settings.value(QString("colwidth%1").arg(i)); + if (width.isValid()) + ui->ListWidget->setColumnWidth(i, width.toInt()); + else + ui->ListWidget->resizeColumnToContents(i); + } + ui->ListWidget->collapseAll(); + ui->ListWidget->scrollTo(ui->ListWidget->model()->index(0,0), QAbstractItemView::PositionAtCenter); + + settings.endGroup(); + settings.beginGroup("Units"); + GET_UNIT(v, "feet", length, units::METERS, units::FEET); + GET_UNIT(v, "psi", pressure, units::BAR, units::PSI); + GET_UNIT(v, "cuft", volume, units::LITER, units::CUFT); + GET_UNIT(v, "fahrenheit", temperature, units::CELSIUS, units::FAHRENHEIT); + GET_UNIT(v, "lbs", weight, units::KG, units::LBS); + settings.endGroup(); + settings.beginGroup("DisplayListColumns"); + GET_BOOL(v, "CYLINDER", prefs.visible_cols.cylinder); + GET_BOOL(v, "TEMPERATURE", prefs.visible_cols.temperature); + GET_BOOL(v, "TOTALWEIGHT", prefs.visible_cols.totalweight); + GET_BOOL(v, "SUIT", prefs.visible_cols.suit); + GET_BOOL(v, "NITROX", prefs.visible_cols.nitrox); + GET_BOOL(v, "OTU", prefs.visible_cols.otu); + GET_BOOL(v, "MAXCNS", prefs.visible_cols.maxcns); + GET_BOOL(v, "SAC", prefs.visible_cols.sac); + GET_BOOL(v, "po2graph", prefs.pp_graphs.po2); + GET_BOOL(v, "pn2graph", prefs.pp_graphs.pn2); + GET_BOOL(v, "phegraph", prefs.pp_graphs.phe); + settings.endGroup(); + settings.beginGroup("TecDetails"); + v = settings.value(QString("po2threshold")); + if (v.isValid()) + prefs.pp_graphs.po2_threshold = v.toDouble(); + v = settings.value(QString("pn2threshold")); + if (v.isValid()) + prefs.pp_graphs.pn2_threshold = v.toDouble(); + v = settings.value(QString("phethreshold")); + if (v.isValid()) + prefs.pp_graphs.phe_threshold = v.toDouble(); + GET_BOOL(v, "mod", prefs.mod); + v = settings.value(QString("modppO2")); + if (v.isValid()) + prefs.mod_ppO2 = v.toDouble(); + GET_BOOL(v, "ead", prefs.ead); + GET_BOOL(v, "redceiling", prefs.profile_red_ceiling); + GET_BOOL(v, "calcceiling", prefs.profile_calc_ceiling); + GET_BOOL(v, "calcceiling3m", prefs.calc_ceiling_3m_incr); + v = settings.value(QString("gflow")); + if (v.isValid()) + prefs.gflow = v.toInt() / 100.0; + v = settings.value(QString("gfhigh")); + if (v.isValid()) + prefs.gfhigh = v.toInt() / 100.0; + set_gf(prefs.gflow, prefs.gfhigh); + settings.endGroup(); + +#if ONCE_WE_CAN_SET_FONTS + settings.beginGroup("Display"); + v = settings.value(QString("divelist_font")); + if (v.isValid()) + /* I don't think this is right */ + prefs.divelist_font = strdup(v.toString); +#endif + +#if ONCE_WE_HAVE_MAPS + v = settings.value(QString_int("map_provider")); + if(v.isValid()) + prefs.map_provider = v.toInt(); +#endif +} + +#define SAVE_VALUE(name, field) \ + if (prefs.field != default_prefs.field) \ + settings.setValue(name, prefs.field) + +void MainWindow::writeSettings() +{ + int i; + QSettings settings("hohndel.org","subsurface"); + + settings.beginGroup("MainWindow"); + settings.setValue("size",size()); + settings.setValue("mainSplitter", ui->mainSplitter->saveState()); + settings.setValue("infoProfileSplitter", ui->infoProfileSplitter->saveState()); + settings.endGroup(); + + settings.beginGroup("ListWidget"); + for (i = TreeItemDT::NR; i < TreeItemDT::COLUMNS; i++) + settings.setValue(QString("colwidth%1").arg(i), ui->ListWidget->columnWidth(i)); + settings.endGroup(); + settings.beginGroup("Units"); + SAVE_VALUE("feet", units.length); + SAVE_VALUE("psi", units.pressure); + SAVE_VALUE("cuft", units.volume); + SAVE_VALUE("fahrenheit", units.temperature); + SAVE_VALUE("lbs", units.weight); + settings.endGroup(); + settings.beginGroup("DisplayListColumns"); + SAVE_VALUE("TEMPERATURE", visible_cols.temperature); + SAVE_VALUE("TOTALWEIGHT", visible_cols.totalweight); + SAVE_VALUE("SUIT", visible_cols.suit); + SAVE_VALUE("CYLINDER", visible_cols.cylinder); + SAVE_VALUE("NITROX", visible_cols.nitrox); + SAVE_VALUE("SAC", visible_cols.sac); + SAVE_VALUE("OTU", visible_cols.otu); + SAVE_VALUE("MAXCNS", visible_cols.maxcns); + settings.endGroup(); + settings.beginGroup("TecDetails"); + SAVE_VALUE("po2graph", pp_graphs.po2); + SAVE_VALUE("pn2graph", pp_graphs.pn2); + SAVE_VALUE("phegraph", pp_graphs.phe); + SAVE_VALUE("po2threshold", pp_graphs.po2_threshold); + SAVE_VALUE("pn2threshold", pp_graphs.pn2_threshold); + SAVE_VALUE("phethreshold", pp_graphs.phe_threshold); + SAVE_VALUE("mod", mod); + SAVE_VALUE("modppO2", mod_ppO2); + SAVE_VALUE("ead", ead); + SAVE_VALUE("redceiling", profile_red_ceiling); + SAVE_VALUE("calcceiling", profile_calc_ceiling); + SAVE_VALUE("calcceiling3m", calc_ceiling_3m_incr); + SAVE_VALUE("gflow", gflow); + SAVE_VALUE("gfhigh", gfhigh); + settings.endGroup(); + settings.beginGroup("GeneralSettings"); + SAVE_VALUE("default_filename", default_filename); + settings.endGroup(); +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + if (unsaved_changes() && (askSaveChanges() == FALSE)) { + event->ignore(); + return; + } + event->accept(); + writeSettings(); +} diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h new file mode 100644 index 000000000..60f0d4609 --- /dev/null +++ b/qt-ui/mainwindow.h @@ -0,0 +1,89 @@ +/* + * mainwindow.h + * + * header file for the main window of Subsurface + * + */ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +#include <QModelIndex> +#include <QAction> + +class QSortFilterProxyModel; +class DiveTripModel; + +namespace Ui +{ + class MainWindow; +} + +class DiveInfo; +class DiveNotes; +class Stats; +class Equipment; +class QItemSelection; + +class MainWindow : public QMainWindow +{ +Q_OBJECT +public: + MainWindow(); + +private Q_SLOTS: + + /* file menu action */ + void on_actionNew_triggered(); + void on_actionOpen_triggered(); + void on_actionSave_triggered(); + void on_actionSaveAs_triggered(); + void on_actionClose_triggered(); + void on_actionImport_triggered(); + void on_actionExportUDDF_triggered(); + void on_actionPrint_triggered(); + void on_actionPreferences_triggered(); + void on_actionQuit_triggered(); + + /* log menu actions */ + void on_actionDownloadDC_triggered(); + void on_actionDownloadWeb_triggered(); + void on_actionEditDeviceNames_triggered(); + void on_actionAddDive_triggered(); + void on_actionRenumber_triggered(); + void on_actionAutoGroup_triggered(); + void on_actionToggleZoom_triggered(); + void on_actionYearlyStatistics_triggered(); + + /* view menu actions */ + void on_actionViewList_triggered(); + void on_actionViewProfile_triggered(); + void on_actionViewInfo_triggered(); + void on_actionViewAll_triggered(); + void on_actionPreviousDC_triggered(); + void on_actionNextDC_triggered(); + + /* other menu actions */ + void on_actionSelectEvents_triggered(); + void on_actionInputPlan_triggered(); + void on_actionAboutSubsurface_triggered(); + void on_actionUserManual_triggered(); + + void current_dive_changed(int divenr); + +protected: + void closeEvent(QCloseEvent *); + +private: + Ui::MainWindow *ui; + QAction *actionNextDive; + QAction *actionPreviousDive; + + QString filter(); + bool askSaveChanges(); + void readSettings(); + void writeSettings(); + void redrawProfile(); +}; + +#endif diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui new file mode 100644 index 000000000..ab0cd5f49 --- /dev/null +++ b/qt-ui/mainwindow.ui @@ -0,0 +1,383 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>763</width> + <height>548</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QSplitter" name="mainSplitter"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <widget class="QSplitter" name="infoProfileSplitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="MainTab" name="InfoWidget" native="true"/> + <widget class="ProfileGraphicsView" name="ProfileWidget"/> + </widget> + <widget class="QSplitter" name="globeListSplitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="DiveListView" name="ListWidget"> + <property name="styleSheet"> + <string notr="true"> QTreeView { + show-decoration-selected: 1; + } + + QTreeView::item { + border: 1px solid #d9d9d9; + border-top-color: transparent; + border-bottom-color: transparent; + padding: 2px; + } + + QTreeView::item:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #e7effd, stop: 1 #cbdaf1); + border: 1px solid #bfcde4; + } + + QTreeView::item:selected { + border: 1px solid #567dbc; + } + + QTreeView::item:selected:active{ + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #6ea1f1, stop: 1 #567dbc); + } + + QTreeView::item:selected:!active { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #6b9be8, stop: 1 #577fbf); + } + +</string> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="rootIsDecorated"> + <bool>true</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <property name="animated"> + <bool>true</bool> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + </widget> + <widget class="GlobeGPS" name="widget" native="true"/> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>763</width> + <height>25</height> + </rect> + </property> + <widget class="QMenu" name="menuFile"> + <property name="title"> + <string>File</string> + </property> + <addaction name="actionNew"/> + <addaction name="actionOpen"/> + <addaction name="actionSave"/> + <addaction name="actionSaveAs"/> + <addaction name="actionClose"/> + <addaction name="separator"/> + <addaction name="actionImport"/> + <addaction name="actionExportUDDF"/> + <addaction name="separator"/> + <addaction name="actionPrint"/> + <addaction name="separator"/> + <addaction name="actionPreferences"/> + <addaction name="separator"/> + <addaction name="actionQuit"/> + </widget> + <widget class="QMenu" name="menuLog"> + <property name="title"> + <string>Log</string> + </property> + <addaction name="actionDownloadDC"/> + <addaction name="actionDownloadWeb"/> + <addaction name="actionEditDeviceNames"/> + <addaction name="separator"/> + <addaction name="actionAddDive"/> + <addaction name="separator"/> + <addaction name="actionRenumber"/> + <addaction name="actionAutoGroup"/> + <addaction name="actionToggleZoom"/> + <addaction name="actionYearlyStatistics"/> + </widget> + <widget class="QMenu" name="menuView"> + <property name="title"> + <string>View</string> + </property> + <addaction name="actionViewList"/> + <addaction name="actionViewProfile"/> + <addaction name="actionViewInfo"/> + <addaction name="actionViewAll"/> + <addaction name="actionPreviousDC"/> + <addaction name="actionNextDC"/> + </widget> + <widget class="QMenu" name="menuFilter"> + <property name="title"> + <string>Filter</string> + </property> + <addaction name="actionSelectEvents"/> + </widget> + <widget class="QMenu" name="menuPlanner"> + <property name="title"> + <string>Planner</string> + </property> + <addaction name="actionInputPlan"/> + </widget> + <widget class="QMenu" name="menuHelp"> + <property name="title"> + <string>Help</string> + </property> + <addaction name="actionAboutSubsurface"/> + <addaction name="actionUserManual"/> + </widget> + <addaction name="menuFile"/> + <addaction name="menuLog"/> + <addaction name="menuView"/> + <addaction name="menuFilter"/> + <addaction name="menuPlanner"/> + <addaction name="menuHelp"/> + </widget> + <action name="actionNew"> + <property name="text"> + <string>New</string> + </property> + <property name="shortcut"> + <string>Ctrl+N</string> + </property> + </action> + <action name="actionOpen"> + <property name="text"> + <string>Open</string> + </property> + <property name="shortcut"> + <string>Ctrl+O</string> + </property> + </action> + <action name="actionSave"> + <property name="text"> + <string>Save</string> + </property> + <property name="shortcut"> + <string>Ctrl+S</string> + </property> + </action> + <action name="actionSaveAs"> + <property name="text"> + <string>Save as</string> + </property> + <property name="shortcut"> + <string>Ctrl+Shift+S</string> + </property> + </action> + <action name="actionClose"> + <property name="text"> + <string>Close</string> + </property> + <property name="shortcut"> + <string>Ctrl+W</string> + </property> + </action> + <action name="actionImport"> + <property name="text"> + <string>Import Files</string> + </property> + <property name="shortcut"> + <string>Ctrl+I</string> + </property> + </action> + <action name="actionExportUDDF"> + <property name="text"> + <string>Export UDDF</string> + </property> + </action> + <action name="actionPrint"> + <property name="text"> + <string>Print</string> + </property> + <property name="shortcut"> + <string>Ctrl+P</string> + </property> + </action> + <action name="actionPreferences"> + <property name="text"> + <string>Preferences</string> + </property> + </action> + <action name="actionQuit"> + <property name="text"> + <string>Quit</string> + </property> + <property name="shortcut"> + <string>Ctrl+Q</string> + </property> + </action> + <action name="actionDownloadDC"> + <property name="text"> + <string>Download from Dive computer</string> + </property> + <property name="shortcut"> + <string>Ctrl+D</string> + </property> + </action> + <action name="actionDownloadWeb"> + <property name="text"> + <string>Download from Web Service</string> + </property> + </action> + <action name="actionEditDeviceNames"> + <property name="text"> + <string>Edit Device Names</string> + </property> + </action> + <action name="actionAddDive"> + <property name="text"> + <string>Add Dive</string> + </property> + </action> + <action name="actionRenumber"> + <property name="text"> + <string>Renumber</string> + </property> + </action> + <action name="actionAutoGroup"> + <property name="text"> + <string>Auto Group</string> + </property> + </action> + <action name="actionToggleZoom"> + <property name="text"> + <string>Toggle Zoom</string> + </property> + </action> + <action name="actionYearlyStatistics"> + <property name="text"> + <string>Yearly Statistics</string> + </property> + </action> + <action name="actionViewList"> + <property name="text"> + <string>View List</string> + </property> + <property name="shortcut"> + <string>Ctrl+1</string> + </property> + </action> + <action name="actionViewProfile"> + <property name="text"> + <string>View Profile</string> + </property> + <property name="shortcut"> + <string>Ctrl+2</string> + </property> + </action> + <action name="actionViewInfo"> + <property name="text"> + <string>View Info</string> + </property> + <property name="shortcut"> + <string>Ctrl+3</string> + </property> + </action> + <action name="actionViewAll"> + <property name="text"> + <string>View All</string> + </property> + <property name="shortcut"> + <string>Ctrl+4</string> + </property> + </action> + <action name="actionPreviousDC"> + <property name="text"> + <string>Prev DC</string> + </property> + <property name="shortcut"> + <string>Left</string> + </property> + </action> + <action name="actionNextDC"> + <property name="text"> + <string>Next DC</string> + </property> + <property name="shortcut"> + <string>Right</string> + </property> + </action> + <action name="actionSelectEvents"> + <property name="text"> + <string>Select Events</string> + </property> + </action> + <action name="actionInputPlan"> + <property name="text"> + <string>Input Plan</string> + </property> + </action> + <action name="actionAboutSubsurface"> + <property name="text"> + <string>About Subsurface</string> + </property> + </action> + <action name="actionUserManual"> + <property name="text"> + <string>User Manual</string> + </property> + </action> + </widget> + <customwidgets> + <customwidget> + <class>MainTab</class> + <extends>QWidget</extends> + <header>maintab.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>DiveListView</class> + <extends>QTreeView</extends> + <header>divelistview.h</header> + </customwidget> + <customwidget> + <class>ProfileGraphicsView</class> + <extends>QGraphicsView</extends> + <header>profilegraphics.h</header> + </customwidget> + <customwidget> + <class>GlobeGPS</class> + <extends>QWidget</extends> + <header>globe.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/qt-ui/modeldelegates.cpp b/qt-ui/modeldelegates.cpp new file mode 100644 index 000000000..be47198e2 --- /dev/null +++ b/qt-ui/modeldelegates.cpp @@ -0,0 +1,49 @@ +#include "modeldelegates.h" +#include "../dive.h" +#include "../divelist.h" +#include "starwidget.h" +#include "models.h" + +#include <QtDebug> +#include <QPainter> +#include <QSortFilterProxyModel> +#include <QStyle> +#include <QStyleOption> + +StarWidgetsDelegate::StarWidgetsDelegate(QWidget* parent): + QStyledItemDelegate(parent), + parentWidget(parent) +{ + +} + +void StarWidgetsDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QStyledItemDelegate::paint(painter, option, index); + + if (!index.isValid()) + return; + + QVariant value = index.model()->data(index, TreeItemDT::STAR_ROLE); + + if (!value.isValid()) + return; + + int rating = value.toInt(); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + + for(int i = 0; i < rating; i++) + painter->drawPixmap(option.rect.x() + i * IMG_SIZE + SPACING, option.rect.y(), StarWidget::starActive()); + + for(int i = rating; i < TOTALSTARS; i++) + painter->drawPixmap(option.rect.x() + i * IMG_SIZE + SPACING, option.rect.y(), StarWidget::starInactive()); + + painter->restore(); +} + +QSize StarWidgetsDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + return QSize(IMG_SIZE * TOTALSTARS + SPACING * (TOTALSTARS-1), IMG_SIZE); +} diff --git a/qt-ui/modeldelegates.h b/qt-ui/modeldelegates.h new file mode 100644 index 000000000..5f90a3061 --- /dev/null +++ b/qt-ui/modeldelegates.h @@ -0,0 +1,15 @@ +#ifndef MODELDELEGATES_H +#define MODELDELEGATES_H + +#include <QStyledItemDelegate> + +class StarWidgetsDelegate : public QStyledItemDelegate { + Q_OBJECT +public: + explicit StarWidgetsDelegate(QWidget* parent = 0); + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; +private: + QWidget *parentWidget; +}; +#endif diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp new file mode 100644 index 000000000..9bc8db0bb --- /dev/null +++ b/qt-ui/models.cpp @@ -0,0 +1,705 @@ +/* + * models.cpp + * + * classes for the equipment models of Subsurface + * + */ +#include "models.h" +#include <QCoreApplication> +#include <QDebug> +#include <QColor> +#include <QBrush> + +extern struct tank_info tank_info[100]; + +CylindersModel::CylindersModel(QObject* parent): QAbstractTableModel(parent) +{ +} + +QVariant CylindersModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant ret; + if (orientation == Qt::Vertical) + return ret; + + if (role == Qt::DisplayRole) { + switch(section) { + case TYPE: + ret = tr("Type"); + break; + case SIZE: + ret = tr("Size"); + break; + case MAXPRESS: + ret = tr("MaxPress"); + break; + case START: + ret = tr("Start"); + break; + case END: + ret = tr("End"); + break; + case O2: + ret = tr("O2%"); + break; + case HE: + ret = tr("He%"); + break; + } + } + return ret; +} + +int CylindersModel::columnCount(const QModelIndex& parent) const +{ + return 7; +} + +QVariant CylindersModel::data(const QModelIndex& index, int role) const +{ + QVariant ret; + if (!index.isValid() || index.row() >= MAX_CYLINDERS) + return ret; + + cylinder_t& cyl = current_dive->cylinder[index.row()]; + + if (role == Qt::DisplayRole) { + switch(index.column()) { + case TYPE: + ret = QString(cyl.type.description); + break; + case SIZE: + ret = cyl.type.size.mliter; + break; + case MAXPRESS: + ret = cyl.type.workingpressure.mbar; + break; + case START: + ret = cyl.start.mbar; + break; + case END: + ret = cyl.end.mbar; + break; + case O2: + ret = cyl.gasmix.o2.permille; + break; + case HE: + ret = cyl.gasmix.he.permille; + break; + } + } + return ret; +} + +int CylindersModel::rowCount(const QModelIndex& parent) const +{ + return usedRows[current_dive]; +} + +void CylindersModel::add(cylinder_t* cyl) +{ + if (usedRows[current_dive] >= MAX_CYLINDERS) { + free(cyl); + return; + } + + int row = usedRows[current_dive]; + + cylinder_t& cylinder = current_dive->cylinder[row]; + + cylinder.end.mbar = cyl->end.mbar; + cylinder.start.mbar = cyl->start.mbar; + + beginInsertRows(QModelIndex(), row, row); + usedRows[current_dive]++; + endInsertRows(); +} + +void CylindersModel::update() +{ + if (usedRows[current_dive] > 0) { + beginRemoveRows(QModelIndex(), 0, usedRows[current_dive]-1); + endRemoveRows(); + } + if (usedRows[current_dive] > 0) { + beginInsertRows(QModelIndex(), 0, usedRows[current_dive]-1); + endInsertRows(); + } +} + +void CylindersModel::clear() +{ + if (usedRows[current_dive] > 0) { + beginRemoveRows(QModelIndex(), 0, usedRows[current_dive]-1); + usedRows[current_dive] = 0; + endRemoveRows(); + } +} + +void WeightModel::clear() +{ + if (usedRows[current_dive] > 0) { + beginRemoveRows(QModelIndex(), 0, usedRows[current_dive]-1); + usedRows[current_dive] = 0; + endRemoveRows(); + } +} + +int WeightModel::columnCount(const QModelIndex& parent) const +{ + return 2; +} + +QVariant WeightModel::data(const QModelIndex& index, int role) const +{ + QVariant ret; + if (!index.isValid() || index.row() >= MAX_WEIGHTSYSTEMS) + return ret; + + weightsystem_t *ws = ¤t_dive->weightsystem[index.row()]; + + if (role == Qt::DisplayRole) { + switch(index.column()) { + case TYPE: + ret = QString(ws->description); + break; + case WEIGHT: + if (get_units()->weight == units::KG) { + int gr = ws->weight.grams % 1000; + int kg = ws->weight.grams / 1000; + ret = QString("%1.%2").arg(kg).arg((unsigned) gr / 100); + } else { + ret = QString("%1").arg((unsigned)(grams_to_lbs(ws->weight.grams))); + } + break; + } + } + return ret; +} + +int WeightModel::rowCount(const QModelIndex& parent) const +{ + return usedRows[current_dive]; +} + +QVariant WeightModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant ret; + if (orientation == Qt::Vertical) + return ret; + + if (role == Qt::DisplayRole) { + switch(section) { + case TYPE: + ret = tr("Type"); + break; + case WEIGHT: + ret = tr("Weight"); + break; + } + } + return ret; +} + +void WeightModel::add(weightsystem_t* weight) +{ + if (usedRows[current_dive] >= MAX_WEIGHTSYSTEMS) { + free(weight); + return; + } + + int row = usedRows[current_dive]; + + weightsystem_t *ws = ¤t_dive->weightsystem[row]; + + ws->description = weight->description; + ws->weight.grams = weight->weight.grams; + + beginInsertRows(QModelIndex(), row, row); + usedRows[current_dive]++; + endInsertRows(); +} + +void WeightModel::update() +{ +} + +void TankInfoModel::add(const QString& description) +{ + // When the user `creates` a new one on the combobox. + // for now, empty till dirk cleans the GTK code. +} + +void TankInfoModel::clear() +{ +} + +int TankInfoModel::columnCount(const QModelIndex& parent) const +{ + return 3; +} + +QVariant TankInfoModel::data(const QModelIndex& index, int role) const +{ + QVariant ret; + if (!index.isValid()) { + return ret; + } + struct tank_info *info = &tank_info[index.row()]; + + int ml = info->ml; + + int bar = ((info->psi) ? psi_to_bar(info->psi) : info->bar) * 1000 + 0.5; + + if (info->cuft) { + double airvolume = cuft_to_l(info->cuft) * 1000.0; + ml = airvolume / bar_to_atm(bar) + 0.5; + } + if (role == Qt::DisplayRole) { + switch(index.column()) { + case BAR: + ret = bar; + break; + case ML: + ret = ml; + break; + case DESCRIPTION: + ret = QString(info->name); + break; + } + } + return ret; +} + +QVariant TankInfoModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant ret; + + if (orientation != Qt::Horizontal) + return ret; + + if (role == Qt::DisplayRole) { + switch(section) { + case BAR: + ret = tr("Bar"); + break; + case ML: + ret = tr("Ml"); + break; + case DESCRIPTION: + ret = tr("Description"); + break; + } + } + return ret; +} + +int TankInfoModel::rowCount(const QModelIndex& parent) const +{ + return rows+1; +} + +TankInfoModel::TankInfoModel() : QAbstractTableModel(), rows(-1) +{ + struct tank_info *info = tank_info; + for (info = tank_info; info->name; info++, rows++); + + if (rows > -1) { + beginInsertRows(QModelIndex(), 0, rows); + endInsertRows(); + } +} + +void TankInfoModel::update() +{ + if(rows > -1) { + beginRemoveRows(QModelIndex(), 0, rows); + endRemoveRows(); + } + struct tank_info *info = tank_info; + for (info = tank_info; info->name; info++, rows++); + + if (rows > -1) { + beginInsertRows(QModelIndex(), 0, rows); + endInsertRows(); + } +} + + +/*! A DiveItem for use with a DiveTripModel + * + * A simple class which wraps basic stats for a dive (e.g. duration, depth) and + * tidies up after it's children. This is done manually as we don't inherit from + * QObject. + * +*/ + +TreeItemDT::~TreeItemDT() +{ + qDeleteAll(children); +} + +int TreeItemDT::row() const +{ + if (parent) + return parent->children.indexOf(const_cast<TreeItemDT*>(this)); + + return 0; +} + +QVariant TreeItemDT::data(int column, int role) const +{ + QVariant ret; + switch (column) { + case NR: + ret = tr("#"); + break; + case DATE: + ret = tr("Date"); + break; + case RATING: + ret = UTF8_BLACKSTAR; + break; + case DEPTH: + ret = (get_units()->length == units::METERS) ? tr("m") : tr("ft"); + break; + case DURATION: + ret = tr("min"); + break; + case TEMPERATURE: + ret = QString("%1%2").arg(UTF8_DEGREE).arg( (get_units()->temperature == units::CELSIUS) ? "C" : "F"); + break; + case TOTALWEIGHT: + ret = (get_units()->weight == units::KG) ? tr("kg") : tr("lbs"); + break; + case SUIT: + ret = tr("Suit"); + break; + case CYLINDER: + ret = tr("Cyl"); + break; + case NITROX: + ret = QString("O%1%").arg(UTF8_SUBSCRIPT_2); + break; + case SAC: + ret = tr("SAC"); + break; + case OTU: + ret = tr("OTU"); + break; + case MAXCNS: + ret = tr("maxCNS"); + break; + case LOCATION: + ret = tr("Location"); + break; + } + return ret; +} + +struct TripItem : public TreeItemDT { + virtual QVariant data(int column, int role) const; + dive_trip_t* trip; +}; + +QVariant TripItem::data(int column, int role) const +{ + QVariant ret; + + if (role == Qt::DisplayRole) { + switch (column) { + case LOCATION: + ret = QString(trip->location); + break; + case DATE: + ret = QString(get_trip_date_string(trip->when, trip->nrdives)); + break; + } + } + + return ret; +} + +struct DiveItem : public TreeItemDT { + virtual QVariant data(int column, int role) const; + struct dive* dive; + + QString displayDuration() const; + QString displayDepth() const; + QString displayTemperature() const; + QString displayWeight() const; + QString displaySac() const; + int weight() const; +}; + +QVariant DiveItem::data(int column, int role) const +{ + QVariant retVal; + + switch (role) { + case Qt::TextAlignmentRole: + switch (column) { + case DATE: /* fall through */ + case SUIT: /* fall through */ + case LOCATION: + retVal = Qt::AlignLeft; + break; + default: + retVal = Qt::AlignRight; + break; + } + break; + case Qt::DisplayRole: + switch (column) { + case NR: + retVal = dive->number; + break; + case DATE: + retVal = QString(get_dive_date_string(dive->when)); + break; + case DEPTH: + retVal = displayDepth(); + break; + case DURATION: + retVal = displayDuration(); + break; + case TEMPERATURE: + retVal = displayTemperature(); + break; + case TOTALWEIGHT: + retVal = displayWeight(); + break; + case SUIT: + retVal = QString(dive->suit); + break; + case CYLINDER: + retVal = QString(dive->cylinder[0].type.description); + break; + case NITROX: + retVal = QString(get_nitrox_string(dive)); + break; + case SAC: + retVal = displaySac(); + break; + case OTU: + retVal = dive->otu; + break; + case MAXCNS: + retVal = dive->maxcns; + break; + case LOCATION: + retVal = QString(dive->location); + break; + } + break; + } + + if(role == STAR_ROLE){ + retVal = dive->rating; + } + + if(role == DIVE_ROLE){ + retVal = QVariant::fromValue<void*>(dive); + } + + return retVal; +} + +QString DiveItem::displayDepth() const +{ + const int scale = 1000; + QString fract, str; + if (get_units()->length == units::METERS) { + fract = QString::number((unsigned)(dive->maxdepth.mm % scale) / 10); + str = QString("%1.%2").arg((unsigned)(dive->maxdepth.mm / scale)).arg(fract, 2, QChar('0')); + } + if (get_units()->length == units::FEET) { + str = QString::number(mm_to_feet(dive->maxdepth.mm),'f',0); + } + return str; +} + +QString DiveItem::displayDuration() const +{ + int hrs, mins, secs; + secs = dive->duration.seconds % 60; + mins = dive->duration.seconds / 60; + hrs = mins / 60; + mins -= hrs * 60; + + QString displayTime; + if (hrs) + displayTime = QString("%1:%2:").arg(hrs).arg(mins, 2, 10, QChar('0')); + else + displayTime = QString("%1:").arg(mins); + displayTime += QString("%1").arg(secs, 2, 10, QChar('0')); + return displayTime; +} + +QString DiveItem::displayTemperature() const +{ + QString str; + + if (!dive->watertemp.mkelvin) + return str; + + if (get_units()->temperature == units::CELSIUS) + str = QString::number(mkelvin_to_C(dive->watertemp.mkelvin), 'f', 1); + else + str = QString::number(mkelvin_to_F(dive->watertemp.mkelvin), 'f', 1); + + return str; +} + +QString DiveItem::displaySac() const +{ + QString str; + + if (get_units()->volume == units::LITER) + str = QString::number(dive->sac / 1000, 'f', 1); + else + str = QString::number(ml_to_cuft(dive->sac), 'f', 2); + + return str; +} + +QString DiveItem::displayWeight() const +{ + QString str; + + if (get_units()->weight == units::KG) { + int gr = weight() % 1000; + int kg = weight() / 1000; + str = QString("%1.%2").arg(kg).arg((unsigned)(gr) / 100); + } else { + str = QString("%1").arg((unsigned)(grams_to_lbs(weight()))); + } + + return str; +} + +int DiveItem::weight() const +{ + weight_t tw = { total_weight(dive) }; + return tw.grams; +} + + +DiveTripModel::DiveTripModel(QObject* parent) : + QAbstractItemModel(parent) +{ + rootItem = new TreeItemDT(); + setupModelData(); +} + +DiveTripModel::~DiveTripModel() +{ + delete rootItem; +} + +int DiveTripModel::columnCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return static_cast<TreeItemDT*>(parent.internalPointer())->columnCount(); + else + return rootItem->columnCount(); +} + +QVariant DiveTripModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + TreeItemDT* item = static_cast<TreeItemDT*>(index.internalPointer()); + + return item->data(index.column(), role); +} + +Qt::ItemFlags DiveTripModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return 0; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + return rootItem->data(section, role); + + return QVariant(); +} + +QModelIndex DiveTripModel::index(int row, int column, const QModelIndex& parent) +const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + TreeItemDT* parentItem = (!parent.isValid()) ? rootItem : static_cast<TreeItemDT*>(parent.internalPointer()); + + TreeItemDT* childItem = parentItem->children[row]; + + return (childItem) ? createIndex(row, column, childItem) : QModelIndex(); +} + +QModelIndex DiveTripModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + TreeItemDT* childItem = static_cast<TreeItemDT*>(index.internalPointer()); + TreeItemDT* parentItem = childItem->parent; + + if (parentItem == rootItem || !parentItem) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); +} + +int DiveTripModel::rowCount(const QModelIndex& parent) const +{ + TreeItemDT* parentItem; + + if (!parent.isValid()) + parentItem = rootItem; + else + parentItem = static_cast<TreeItemDT*>(parent.internalPointer()); + + int amount = parentItem->children.count(); + + return amount; +} + +void DiveTripModel::setupModelData() +{ + int i = dive_table.nr; + + while (--i >= 0) { + struct dive* dive = get_dive(i); + update_cylinder_related_info(dive); + dive_trip_t* trip = dive->divetrip; + + DiveItem* diveItem = new DiveItem(); + diveItem->dive = dive; + + if (!trip) { + diveItem->parent = rootItem; + rootItem->children.push_back(diveItem); + continue; + } + if (!trips.keys().contains(trip)) { + TripItem* tripItem = new TripItem(); + tripItem->trip = trip; + tripItem->parent = rootItem; + tripItem->children.push_back(diveItem); + trips[trip] = tripItem; + rootItem->children.push_back(tripItem); + continue; + } + TripItem* tripItem = trips[trip]; + tripItem->children.push_back(diveItem); + } +} diff --git a/qt-ui/models.h b/qt-ui/models.h new file mode 100644 index 000000000..ac533fd71 --- /dev/null +++ b/qt-ui/models.h @@ -0,0 +1,126 @@ +/* + * models.h + * + * header file for the equipment models of Subsurface + * + */ +#ifndef MODELS_H +#define MODELS_H + +#include <QAbstractTableModel> +#include <QCoreApplication> + +#include "../dive.h" +#include "../divelist.h" + +/* Encapsulates the tank_info global variable + * to show on Qt's Model View System.*/ +class TankInfoModel : public QAbstractTableModel { +Q_OBJECT +public: + enum Column { DESCRIPTION, ML, BAR}; + TankInfoModel(); + + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int columnCount(const QModelIndex& parent = QModelIndex()) const; + /*reimp*/ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex& parent = QModelIndex()) const; + + void add(const QString& description); + void clear(); + void update(); +private: + int rows; +}; + +/* Encapsulation of the Cylinder Model, that presents the + * Current cylinders that are used on a dive. */ +class CylindersModel : public QAbstractTableModel { +Q_OBJECT +public: + enum Column {TYPE, SIZE, MAXPRESS, START, END, O2, HE}; + + explicit CylindersModel(QObject* parent = 0); + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int columnCount(const QModelIndex& parent = QModelIndex()) const; + /*reimp*/ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex& parent = QModelIndex()) const; + + void add(cylinder_t *cyl); + void clear(); + void update(); +private: + /* Since the dive doesn't stores the number of cylinders that + * it has (max 8) and since I don't want to make a + * model-for-each-dive, let's hack this here instead. */ + QMap<struct dive *, int> usedRows; +}; + +/* Encapsulation of the Weight Model, that represents + * the current weights on a dive. */ +class WeightModel : public QAbstractTableModel { +Q_OBJECT +public: + enum Column {TYPE, WEIGHT}; + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int columnCount(const QModelIndex& parent = QModelIndex()) const; + /*reimp*/ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex& parent = QModelIndex()) const; + + void add(weightsystem_t *weight); + void clear(); + void update(); +private: + /* Remember the number of rows in a dive */ + QMap<struct dive *, int> usedRows; +}; + +/*! An AbstractItemModel for recording dive trip information such as a list of dives. +* +*/ + +struct TreeItemDT { + Q_DECLARE_TR_FUNCTIONS ( TreeItemDT ); +public: + enum Column {NR, DATE, RATING, DEPTH, DURATION, TEMPERATURE, TOTALWEIGHT, + SUIT, CYLINDER, NITROX, SAC, OTU, MAXCNS, LOCATION, DIVE, COLUMNS }; + + enum ExtraRoles{STAR_ROLE = Qt::UserRole + 1, DIVE_ROLE}; + + virtual ~TreeItemDT(); + int columnCount() const { + return COLUMNS; + }; + + virtual QVariant data ( int column, int role ) const; + int row() const; + QList<TreeItemDT *> children; + TreeItemDT *parent; +}; + +struct TripItem; + +class DiveTripModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + DiveTripModel(QObject *parent = 0); + ~DiveTripModel(); + + /*reimp*/ Qt::ItemFlags flags(const QModelIndex &index) const; + /*reimp*/ QVariant data(const QModelIndex &index, int role) const; + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex &parent = QModelIndex()) const; + /*reimp*/ int columnCount(const QModelIndex &parent = QModelIndex()) const; + /*reimp*/ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + /*reimp*/ QModelIndex parent(const QModelIndex &child) const; + +private: + void setupModelData(); + + TreeItemDT *rootItem; + QMap<dive_trip_t*, TripItem*> trips; +}; + +#endif diff --git a/qt-ui/plotareascene.cpp b/qt-ui/plotareascene.cpp new file mode 100644 index 000000000..a728040f5 --- /dev/null +++ b/qt-ui/plotareascene.cpp @@ -0,0 +1,6 @@ +/* + * plotareascene.cpp + * + * classes for profile plot area scene of Subsurface + * + */ diff --git a/qt-ui/plotareascene.h b/qt-ui/plotareascene.h new file mode 100644 index 000000000..a5b07d1be --- /dev/null +++ b/qt-ui/plotareascene.h @@ -0,0 +1,6 @@ +/* + * plotareascene.h + * + * header file for the profile plot area scene of Subsurface + * + */ diff --git a/qt-ui/profilegraphics.cpp b/qt-ui/profilegraphics.cpp new file mode 100644 index 000000000..637c7fe95 --- /dev/null +++ b/qt-ui/profilegraphics.cpp @@ -0,0 +1,1397 @@ +#include "profilegraphics.h" + +#include <QGraphicsScene> +#include <QResizeEvent> +#include <QGraphicsLineItem> +#include <QPen> +#include <QBrush> +#include <QDebug> +#include <QLineF> +#include <QSettings> +#include <QIcon> +#include <QPropertyAnimation> +#include <QGraphicsSceneHoverEvent> +#include <QMouseEvent> + +#include "../color.h" +#include "../display.h" +#include "../dive.h" +#include "../profile.h" + +#include <libdivecomputer/parser.h> +#include <libdivecomputer/version.h> + +#define SAC_COLORS_START_IDX SAC_1 +#define SAC_COLORS 9 +#define VELOCITY_COLORS_START_IDX VELO_STABLE +#define VELOCITY_COLORS 5 + +static struct graphics_context last_gc; +static double plot_scale = SCALE_SCREEN; + +typedef enum { + /* SAC colors. Order is important, the SAC_COLORS_START_IDX define above. */ + SAC_1, SAC_2, SAC_3, SAC_4, SAC_5, SAC_6, SAC_7, SAC_8, SAC_9, + + /* Velocity colors. Order is still important, ref VELOCITY_COLORS_START_IDX. */ + VELO_STABLE, VELO_SLOW, VELO_MODERATE, VELO_FAST, VELO_CRAZY, + + /* gas colors */ + PO2, PO2_ALERT, PN2, PN2_ALERT, PHE, PHE_ALERT, PP_LINES, + + /* Other colors */ + TEXT_BACKGROUND, ALERT_BG, ALERT_FG, EVENTS, SAMPLE_DEEP, SAMPLE_SHALLOW, + SMOOTHED, MINUTE, TIME_GRID, TIME_TEXT, DEPTH_GRID, MEAN_DEPTH, DEPTH_TOP, + DEPTH_BOTTOM, TEMP_TEXT, TEMP_PLOT, SAC_DEFAULT, BOUNDING_BOX, PRESSURE_TEXT, BACKGROUND, + CEILING_SHALLOW, CEILING_DEEP, CALC_CEILING_SHALLOW, CALC_CEILING_DEEP +} color_indice_t; + + +#define COLOR(x, y, z) QVector<QColor>() << x << y << z; +/* profile_color[color indice] = COLOR(screen color, b/w printer color, color printer}} printer & screen colours could be different */ +QMap<color_indice_t, QVector<QColor> > profile_color; +void fill_profile_color() +{ + profile_color[SAC_1] = COLOR(FUNGREEN1, BLACK1_LOW_TRANS, FUNGREEN1); + profile_color[SAC_2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1); + profile_color[SAC_3] = COLOR(ATLANTIS1, BLACK1_LOW_TRANS, ATLANTIS1); + profile_color[SAC_4] = COLOR(ATLANTIS2, BLACK1_LOW_TRANS, ATLANTIS2); + profile_color[SAC_5] = COLOR(EARLSGREEN1, BLACK1_LOW_TRANS, EARLSGREEN1); + profile_color[SAC_6] = COLOR(HOKEYPOKEY1, BLACK1_LOW_TRANS, HOKEYPOKEY1); + profile_color[SAC_7] = COLOR(TUSCANY1, BLACK1_LOW_TRANS, TUSCANY1); + profile_color[SAC_8] = COLOR(CINNABAR1, BLACK1_LOW_TRANS, CINNABAR1); + profile_color[SAC_9] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1); + + profile_color[VELO_STABLE] = COLOR(CAMARONE1, BLACK1_LOW_TRANS, CAMARONE1); + profile_color[VELO_SLOW] = COLOR(LIMENADE1, BLACK1_LOW_TRANS, LIMENADE1); + profile_color[VELO_MODERATE] = COLOR(RIOGRANDE1, BLACK1_LOW_TRANS, RIOGRANDE1); + profile_color[VELO_FAST] = COLOR(PIRATEGOLD1, BLACK1_LOW_TRANS, PIRATEGOLD1); + profile_color[VELO_CRAZY] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + + profile_color[PO2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1); + profile_color[PO2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + profile_color[PN2] = COLOR(BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS); + profile_color[PN2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + profile_color[PHE] = COLOR(PEANUT, BLACK1_LOW_TRANS, PEANUT); + profile_color[PHE_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + profile_color[PP_LINES] = COLOR(BLACK1_HIGH_TRANS, BLACK1_HIGH_TRANS, BLACK1_HIGH_TRANS); + + profile_color[TEXT_BACKGROUND] = COLOR(CONCRETE1_LOWER_TRANS, WHITE1, CONCRETE1_LOWER_TRANS); + profile_color[ALERT_BG] = COLOR(BROOM1_LOWER_TRANS, BLACK1_LOW_TRANS, BROOM1_LOWER_TRANS); + profile_color[ALERT_FG] = COLOR(BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS); + profile_color[EVENTS] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1); + profile_color[SAMPLE_DEEP] = COLOR(PERSIANRED1, BLACK1_LOW_TRANS, PERSIANRED1); + profile_color[SAMPLE_SHALLOW] = COLOR(PERSIANRED1, BLACK1_LOW_TRANS, PERSIANRED1); + profile_color[SMOOTHED] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_LOW_TRANS, REDORANGE1_HIGH_TRANS); + profile_color[MINUTE] = COLOR(MEDIUMREDVIOLET1_HIGHER_TRANS, BLACK1_LOW_TRANS, MEDIUMREDVIOLET1_HIGHER_TRANS); + profile_color[TIME_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS); + profile_color[TIME_TEXT] = COLOR(FORESTGREEN1, BLACK1_LOW_TRANS, FORESTGREEN1); + profile_color[DEPTH_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS); + profile_color[MEAN_DEPTH] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); + profile_color[DEPTH_BOTTOM] = COLOR(GOVERNORBAY1_MED_TRANS, BLACK1_HIGH_TRANS, GOVERNORBAY1_MED_TRANS); + profile_color[DEPTH_TOP] = COLOR(MERCURY1_MED_TRANS, WHITE1_MED_TRANS, MERCURY1_MED_TRANS); + profile_color[TEMP_TEXT] = COLOR(GOVERNORBAY2, BLACK1_LOW_TRANS, GOVERNORBAY2); + profile_color[TEMP_PLOT] = COLOR(ROYALBLUE2_LOW_TRANS, BLACK1_LOW_TRANS, ROYALBLUE2_LOW_TRANS); + profile_color[SAC_DEFAULT] = COLOR(WHITE1, BLACK1_LOW_TRANS, FORESTGREEN1); + profile_color[BOUNDING_BOX] = COLOR(WHITE1, BLACK1_LOW_TRANS, TUNDORA1_MED_TRANS); + profile_color[PRESSURE_TEXT] = COLOR(KILLARNEY1, BLACK1_LOW_TRANS, KILLARNEY1); + profile_color[BACKGROUND] = COLOR(SPRINGWOOD1, BLACK1_LOW_TRANS, SPRINGWOOD1); + profile_color[CEILING_SHALLOW] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_HIGH_TRANS, REDORANGE1_HIGH_TRANS); + profile_color[CEILING_DEEP] = COLOR(RED1_MED_TRANS, BLACK1_HIGH_TRANS, RED1_MED_TRANS); + profile_color[CALC_CEILING_SHALLOW] = COLOR(FUNGREEN1_HIGH_TRANS, BLACK1_HIGH_TRANS, FUNGREEN1_HIGH_TRANS); + profile_color[CALC_CEILING_DEEP] = COLOR(APPLE1_HIGH_TRANS, BLACK1_HIGH_TRANS, APPLE1_HIGH_TRANS); + +} +#undef COLOR + +struct text_render_options{ + double size; + color_indice_t color; + double hpos, vpos; +}; + +extern struct ev_select *ev_namelist; +extern int evn_allocated; +extern int evn_used; + +ProfileGraphicsView::ProfileGraphicsView(QWidget* parent) : QGraphicsView(parent), toolTip(0) , dive(0) +{ + gc.printer = false; + setScene(new QGraphicsScene()); + setBackgroundBrush(QColor("#F3F3E6")); + scene()->installEventFilter(this); + + setRenderHint(QPainter::Antialiasing); + setRenderHint(QPainter::HighQualityAntialiasing); + setRenderHint(QPainter::SmoothPixmapTransform); + + defaultPen.setJoinStyle(Qt::RoundJoin); + defaultPen.setCapStyle(Qt::RoundCap); + defaultPen.setWidth(2); + defaultPen.setCosmetic(true); + + setHorizontalScrollBarPolicy ( Qt::ScrollBarAlwaysOff ); + setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOff ); + + fill_profile_color(); +} + +void ProfileGraphicsView::wheelEvent(QWheelEvent* event) +{ + if (!toolTip) + return; + + setTransformationAnchor(QGraphicsView::AnchorUnderMouse); + + // Scale the view / do the zoom + QPoint toolTipPos = mapFromScene(toolTip->pos()); + double scaleFactor = 1.15; + if(event->delta() > 0 && zoomLevel <= 10) { + scale(scaleFactor, scaleFactor); + zoomLevel++; + } else if (zoomLevel >= 0) { + // Zooming out + scale(1.0 / scaleFactor, 1.0 / scaleFactor); + zoomLevel--; + } + toolTip->setPos(mapToScene(toolTipPos).x(), mapToScene(toolTipPos).y()); +} + +void ProfileGraphicsView::mouseMoveEvent(QMouseEvent* event) +{ + if (!toolTip) + return; + + toolTip->refresh(&gc, mapToScene(event->pos())); + + QPoint toolTipPos = mapFromScene(toolTip->pos()); + + double dx = sceneRect().x(); + double dy = sceneRect().y(); + + ensureVisible(event->pos().x() + dx, event->pos().y() + dy, 1, 1); + + toolTip->setPos(mapToScene(toolTipPos).x(), mapToScene(toolTipPos).y()); + + if (zoomLevel < 0) + QGraphicsView::mouseMoveEvent(event); +} + +bool ProfileGraphicsView::eventFilter(QObject* obj, QEvent* event) +{ + // This will "Eat" the default tooltip behavior. + if (event->type() == QEvent::GraphicsSceneHelp) { + event->ignore(); + return true; + } + return QGraphicsView::eventFilter(obj, event); +} + +static void plot_set_scale(scale_mode_t scale) +{ + switch (scale) { + default: + case SC_SCREEN: + plot_scale = SCALE_SCREEN; + break; + case SC_PRINT: + plot_scale = SCALE_PRINT; + break; + } +} + +void ProfileGraphicsView::showEvent(QShowEvent* event) +{ + // Program just opened, + // but the dive was not ploted. + // force a replot by modifying the dive + // hold by the view, and issuing a plot. + if (dive){ + dive = 0; + plot(get_dive(selected_dive)); + } +} + +void ProfileGraphicsView::clear() +{ + scene()->clear(); + resetTransform(); + zoomLevel = 0; + toolTip = 0; +} + +void ProfileGraphicsView::plot(struct dive *d) +{ + if (dive == d) + return; + + clear(); + dive = d; + + if(!isVisible() || !dive){ + return; + } + + scene()->setSceneRect(0,0, viewport()->width()-50, viewport()->height()-50); + + QSettings s; + s.beginGroup("ProfileMap"); + QPointF toolTipPos = s.value("tooltip_position", QPointF(0,0)).toPointF(); + s.endGroup(); + + toolTip = new ToolTipItem(); + toolTip->setPos(toolTipPos); + + scene()->addItem(toolTip); + + struct divecomputer *dc = &dive->dc; + + + // Fix this for printing / screen later. + // plot_set_scale(scale_mode_t); + + if (!dc->samples) { + static struct sample fake[4]; + static struct divecomputer fakedc; + fakedc = dive->dc; + fakedc.sample = fake; + fakedc.samples = 4; + + /* The dive has no samples, so create a few fake ones. This assumes an + ascent/descent rate of 9 m/min, which is just below the limit for FAST. */ + int duration = dive->dc.duration.seconds; + int maxdepth = dive->dc.maxdepth.mm; + int asc_desc_time = dive->dc.maxdepth.mm*60/9000; + if (asc_desc_time * 2 >= duration) + asc_desc_time = duration / 2; + fake[1].time.seconds = asc_desc_time; + fake[1].depth.mm = maxdepth; + fake[2].time.seconds = duration - asc_desc_time; + fake[2].depth.mm = maxdepth; + fake[3].time.seconds = duration * 1.00; + fakedc.events = dc->events; + dc = &fakedc; + } + + /* + * Set up limits that are independent of + * the dive computer + */ + calculate_max_limits(dive, dc, &gc); + + QRectF profile_grid_area = scene()->sceneRect(); + gc.maxx = (profile_grid_area.width() - 2 * profile_grid_area.x()); + gc.maxy = (profile_grid_area.height() - 2 * profile_grid_area.y()); + + dc = select_dc(dc); + + /* This is per-dive-computer. Right now we just do the first one */ + gc.pi = *create_plot_info(dive, dc, &gc); + + /* Depth profile */ + plot_depth_profile(); + + plot_events(dc); + + /* Temperature profile */ + plot_temperature_profile(); + + /* Cylinder pressure plot */ + plot_cylinder_pressure(dive, dc); + + /* Text on top of all graphs.. */ + plot_temperature_text(); + + plot_depth_text(); + + plot_cylinder_pressure_text(); + + plot_deco_text(); + + /* Bounding box */ + QPen pen = defaultPen; + pen.setColor(profile_color[TIME_GRID].at(0)); + QGraphicsRectItem *rect = new QGraphicsRectItem(profile_grid_area); + rect->setPen(pen); + scene()->addItem(rect); + + /* Put the dive computer name in the lower left corner */ + QString nick(get_dc_nickname(dc->model, dc->deviceid)); + if (nick.isEmpty()) + nick = QString(dc->model); + + if (nick.isEmpty()) + nick = tr("unknown divecomputer"); + + gc.leftx = 0; gc.rightx = 1.0; + gc.topy = 0; gc.bottomy = 1.0; + + text_render_options_t computer = {DC_TEXT_SIZE, TIME_TEXT, LEFT, MIDDLE}; + diveComputer = plot_text(&computer, QPointF(gc.leftx, gc.bottomy), nick); + // The Time ruler should be right after the DiveComputer: + timeMarkers->setPos(0, diveComputer->y()); + + if (PP_GRAPHS_ENABLED) { + plot_pp_gas_profile(); + plot_pp_text(); + } + + + /* now shift the translation back by half the margin; + * this way we can draw the vertical scales on both sides */ + //cairo_translate(gc->cr, -drawing_area->x / 2.0, 0); + + //gc->maxx += drawing_area->x; + //gc->leftx = -(drawing_area->x / drawing_area->width) / 2.0; + //gc->rightx = 1.0 - gc->leftx; + + plot_depth_scale(); + +#if 0 + if (gc->printer) { + free(pi->entry); + last_pi_entry = pi->entry = NULL; + pi->nr = 0; + } +#endif + + QRectF r = scene()->itemsBoundingRect(); + scene()->setSceneRect(r.x() - 15, r.y() -15, r.width() + 30, r.height() + 30); + if(zoomLevel == 0){ + fitInView(sceneRect()); + } +} + +void ProfileGraphicsView::plot_depth_scale() +{ + int i, maxdepth, marker; + static text_render_options_t tro = {DEPTH_TEXT_SIZE, SAMPLE_DEEP, RIGHT, MIDDLE}; + + /* Depth markers: every 30 ft or 10 m*/ + maxdepth = get_maxdepth(&gc.pi); + gc.topy = 0; gc.bottomy = maxdepth; + + switch (prefs.units.length) { + case units::METERS: marker = 10000; break; + case units::FEET: marker = 9144; break; /* 30 ft */ + } + + QColor c(profile_color[DEPTH_GRID].first()); + + /* don't write depth labels all the way to the bottom as + * there may be other graphs below the depth plot (like + * partial pressure graphs) where this would look out + * of place - so we only make sure that we print the next + * marker below the actual maxdepth of the dive */ + depthMarkers = new QGraphicsRectItem(); + for (i = marker; i <= gc.pi.maxdepth + marker; i += marker) { + double d = get_depth_units(i, NULL, NULL); + plot_text(&tro, QPointF(-0.002, i), QString::number(d), depthMarkers); + } + scene()->addItem(depthMarkers); + depthMarkers->setPos(depthMarkers->pos().x() - 10, 0); +} + +void ProfileGraphicsView::plot_pp_text() +{ + double pp, dpp, m; + int hpos; + static text_render_options_t tro = {PP_TEXT_SIZE, PP_LINES, LEFT, MIDDLE}; + + setup_pp_limits(&gc); + pp = floor(gc.pi.maxpp * 10.0) / 10.0 + 0.2; + dpp = pp > 4 ? 1.0 : 0.5; + hpos = gc.pi.entry[gc.pi.nr - 1].sec; + QColor c = profile_color[PP_LINES].first(); + + for (m = 0.0; m <= pp; m += dpp) { + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(0, m), SCALEGC(hpos, m)); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + plot_text(&tro, QPointF(hpos + 30, m), QString::number(m)); + } +} + +void ProfileGraphicsView::plot_pp_gas_profile() +{ + int i; + struct plot_data *entry; + struct plot_info *pi = &gc.pi; + + setup_pp_limits(&gc); + QColor c; + QPointF from, to; + //if (prefs.pp_graphs.pn2) { + c = profile_color[PN2].first(); + entry = pi->entry; + from = QPointF(SCALEGC(entry->sec, entry->pn2)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->pn2 < prefs.pp_graphs.pn2_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->pn2)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->pn2)); + } + } + + c = profile_color[PN2_ALERT].first(); + entry = pi->entry; + from = QPointF(SCALEGC(entry->sec, entry->pn2)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->pn2 >= prefs.pp_graphs.pn2_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->pn2)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->pn2)); + } + } + //} + + //if (prefs.pp_graphs.phe) { + c = profile_color[PHE].first(); + entry = pi->entry; + + from = QPointF(SCALEGC(entry->sec, entry->phe)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->phe < prefs.pp_graphs.phe_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->phe)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->phe)); + } + } + + c = profile_color[PHE_ALERT].first(); + entry = pi->entry; + from = QPointF(SCALEGC(entry->sec, entry->phe)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->phe >= prefs.pp_graphs.phe_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->phe)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->phe)); + } + } + //} + //if (prefs.pp_graphs.po2) { + c = profile_color[PO2].first(); + entry = pi->entry; + from = QPointF(SCALEGC(entry->sec, entry->po2)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->po2 < prefs.pp_graphs.po2_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->po2)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->po2)); + } + } + + c = profile_color[PO2_ALERT].first(); + entry = pi->entry; + from = QPointF(SCALEGC(entry->sec, entry->po2)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->po2 >= prefs.pp_graphs.po2_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->po2)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + item->setPen(QPen(c)); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->po2)); + } + } + //} +} + +void ProfileGraphicsView::plot_deco_text() +{ + if (prefs.profile_calc_ceiling) { + float x = gc.leftx + (gc.rightx - gc.leftx) / 2; + float y = gc.topy = 1.0; + static text_render_options_t tro = {PRESSURE_TEXT_SIZE, PRESSURE_TEXT, CENTER, -0.2}; + gc.bottomy = 0.0; + plot_text(&tro, QPointF(x, y), QString("GF %1/%2").arg(prefs.gflow * 100).arg(prefs.gfhigh * 100)); + } +} + +void ProfileGraphicsView::plot_cylinder_pressure_text() +{ + int i; + int mbar, cyl; + int seen_cyl[MAX_CYLINDERS] = { FALSE, }; + int last_pressure[MAX_CYLINDERS] = { 0, }; + int last_time[MAX_CYLINDERS] = { 0, }; + struct plot_data *entry; + struct plot_info *pi = &gc.pi; + + if (!get_cylinder_pressure_range(&gc)) + return; + + cyl = -1; + for (i = 0; i < pi->nr; i++) { + entry = pi->entry + i; + mbar = GET_PRESSURE(entry); + + if (!mbar) + continue; + if (cyl != entry->cylinderindex) { + cyl = entry->cylinderindex; + if (!seen_cyl[cyl]) { + plot_pressure_value(mbar, entry->sec, LEFT, BOTTOM); + seen_cyl[cyl] = TRUE; + } + } + last_pressure[cyl] = mbar; + last_time[cyl] = entry->sec; + } + + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { + if (last_time[cyl]) { + plot_pressure_value(last_pressure[cyl], last_time[cyl], CENTER, TOP); + } + } +} + +void ProfileGraphicsView::plot_pressure_value(int mbar, int sec, double xalign, double yalign) +{ + int pressure; + const char *unit; + + pressure = get_pressure_units(mbar, &unit); + static text_render_options_t tro = {PRESSURE_TEXT_SIZE, PRESSURE_TEXT, xalign, yalign}; + plot_text(&tro, QPointF(sec, mbar), QString("%1 %2").arg(pressure).arg(unit)); +} + +void ProfileGraphicsView::plot_depth_text() +{ + int maxtime, maxdepth; + + /* Get plot scaling limits */ + maxtime = get_maxtime(&gc.pi); + maxdepth = get_maxdepth(&gc.pi); + + gc.leftx = 0; gc.rightx = maxtime; + gc.topy = 0; gc.bottomy = maxdepth; + + plot_text_samples(); +} + +void ProfileGraphicsView::plot_text_samples() +{ + static text_render_options_t deep = {14, SAMPLE_DEEP, CENTER, TOP}; + static text_render_options_t shallow = {14, SAMPLE_SHALLOW, CENTER, BOTTOM}; + int i; + int last = -1; + + struct plot_info* pi = &gc.pi; + + for (i = 0; i < pi->nr; i++) { + struct plot_data *entry = pi->entry + i; + + if (entry->depth < 2000) + continue; + + if ((entry == entry->max[2]) && entry->depth / 100 != last) { + plot_depth_sample(entry, &deep); + last = entry->depth / 100; + } + + if ((entry == entry->min[2]) && entry->depth / 100 != last) { + plot_depth_sample(entry, &shallow); + last = entry->depth / 100; + } + + if (entry->depth != last) + last = -1; + } +} + +void ProfileGraphicsView::plot_depth_sample(struct plot_data *entry,text_render_options_t *tro) +{ + int sec = entry->sec, decimals; + double d; + + d = get_depth_units(entry->depth, &decimals, NULL); + + plot_text(tro, QPointF(sec, entry->depth), QString("%1").arg(d, 0, 'f', 1)); +} + + +void ProfileGraphicsView::plot_temperature_text() +{ + int i; + int last = -300, sec = 0; + int last_temperature = 0, last_printed_temp = 0; + plot_info *pi = &gc.pi; + + if (!setup_temperature_limits(&gc)) + return; + + for (i = 0; i < pi->nr; i++) { + struct plot_data *entry = pi->entry+i; + int mkelvin = entry->temperature; + sec = entry->sec; + + if (!mkelvin) + continue; + last_temperature = mkelvin; + /* don't print a temperature + * if it's been less than 5min and less than a 2K change OR + * if it's been less than 2min OR if the change from the + * last print is less than .4K (and therefore less than 1F) */ + if (((sec < last + 300) && (abs(mkelvin - last_printed_temp) < 2000)) || + (sec < last + 120) || + (abs(mkelvin - last_printed_temp) < 400)) + continue; + last = sec; + if (mkelvin > 200000) + plot_single_temp_text(sec,mkelvin); + last_printed_temp = mkelvin; + } + /* it would be nice to print the end temperature, if it's + * different or if the last temperature print has been more + * than a quarter of the dive back */ + if (last_temperature > 200000 && + ((abs(last_temperature - last_printed_temp) > 500) || ((double)last / (double)sec < 0.75))) + plot_single_temp_text(sec, last_temperature); +} + +void ProfileGraphicsView::plot_single_temp_text(int sec, int mkelvin) +{ + double deg; + const char *unit; + static text_render_options_t tro = {TEMP_TEXT_SIZE, TEMP_TEXT, LEFT, TOP}; + deg = get_temp_units(mkelvin, &unit); + plot_text(&tro, QPointF(sec, mkelvin), QString("%1%2").arg(deg, 0, 'f', 1).arg(unit)); //"%.2g%s" +} + +void ProfileGraphicsView::plot_cylinder_pressure(struct dive *dive, struct divecomputer *dc) +{ + int i; + int last = -1, last_index = -1; + int lift_pen = FALSE; + int first_plot = TRUE; + int sac = 0; + struct plot_data *last_entry = NULL; + + if (!get_cylinder_pressure_range(&gc)) + return; + + QPointF from, to; + for (i = 0; i < gc.pi.nr; i++) { + int mbar; + struct plot_data *entry = gc.pi.entry + i; + + mbar = GET_PRESSURE(entry); + if (entry->cylinderindex != last_index) { + lift_pen = TRUE; + last_entry = NULL; + } + if (!mbar) { + lift_pen = TRUE; + continue; + } + if (!last_entry) { + last = i; + last_entry = entry; + sac = get_local_sac(entry, gc.pi.entry + i + 1, dive); + } else { + int j; + sac = 0; + for (j = last; j < i; j++) + sac += get_local_sac(gc.pi.entry + j, gc.pi.entry + j + 1, dive); + sac /= (i - last); + if (entry->sec - last_entry->sec >= SAC_WINDOW) { + last++; + last_entry = gc.pi.entry + last; + } + } + + QColor c = get_sac_color(sac, dive->sac); + + if (lift_pen) { + if (!first_plot && entry->cylinderindex == last_index) { + /* if we have a previous event from the same tank, + * draw at least a short line */ + int prev_pr; + prev_pr = GET_PRESSURE(entry - 1); + + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC((entry-1)->sec, prev_pr), SCALEGC(entry->sec, mbar)); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + } else { + first_plot = FALSE; + from = QPointF(SCALEGC(entry->sec, mbar)); + } + lift_pen = FALSE; + } else { + to = QPointF(SCALEGC(entry->sec, mbar)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + } + + + from = QPointF(SCALEGC(entry->sec, mbar)); + last_index = entry->cylinderindex; + } +} + + +/* set the color for the pressure plot according to temporary sac rate + * as compared to avg_sac; the calculation simply maps the delta between + * sac and avg_sac to indexes 0 .. (SAC_COLORS - 1) with everything + * more than 6000 ml/min below avg_sac mapped to 0 */ +QColor ProfileGraphicsView::get_sac_color(int sac, int avg_sac) +{ + int sac_index = 0; + int delta = sac - avg_sac + 7000; + + if (!gc.printer) { + sac_index = delta / 2000; + if (sac_index < 0) + sac_index = 0; + if (sac_index > SAC_COLORS - 1) + sac_index = SAC_COLORS - 1; + return profile_color[ (color_indice_t) (SAC_COLORS_START_IDX + sac_index)].first(); + } + return profile_color[SAC_DEFAULT].first(); +} + +void ProfileGraphicsView::plot_events(struct divecomputer *dc) +{ + struct event *event = dc->events; + +// if (gc->printer){ +// return; +// } + + while (event) { + plot_one_event(event); + event = event->next; + } +} + +void ProfileGraphicsView::plot_one_event(struct event *ev) +{ + int i, depth = 0; + struct plot_info *pi = &gc.pi; + + /* is plotting of this event disabled? */ + if (ev->name) { + for (i = 0; i < evn_used; i++) { + if (! strcmp(ev->name, ev_namelist[i].ev_name)) { + if (ev_namelist[i].plot_ev) + break; + else + return; + } + } + } + + if (ev->time.seconds < 30 && !strcmp(ev->name, "gaschange")) + /* a gas change in the first 30 seconds is the way of some dive computers + * to tell us the gas that is used; let's not plot a marker for that */ + return; + + for (i = 0; i < pi->nr; i++) { + struct plot_data *data = pi->entry + i; + if (ev->time.seconds < data->sec) + break; + depth = data->depth; + } + + /* draw a little triangular marker and attach tooltip */ + + int x = SCALEXGC(ev->time.seconds); + int y = SCALEYGC(depth); + + EventItem *item = new EventItem(); + item->setPos(x, y); + scene()->addItem(item); + + /* we display the event on screen - so translate */ + QString name = tr(ev->name); + if (ev->value) { + if (ev->name && name == "gaschange") { + unsigned int he = ev->value >> 16; + unsigned int o2 = ev->value & 0xffff; + + name += ": "; + name += (he) ? QString("%1/%2").arg(o2, he) + : (o2 == 21) ? name += tr("air") + : QString("%1% %2").arg(o2).arg("O" UTF8_SUBSCRIPT_2); + + } else if (ev->name && !strcmp(ev->name, "SP change")) { + name += QString(":%1").arg((double) ev->value / 1000); + } else { + name += QString(":%1").arg(ev->value); + } + } else if (ev->name && name == "SP change") { + name += tr("Bailing out to OC"); + } else { + name += ev->flags == SAMPLE_FLAGS_BEGIN ? tr("Starts with space!"," begin") : + ev->flags == SAMPLE_FLAGS_END ? tr("Starts with space!", " end") : ""; + } + + //item->setToolTipController(toolTip); + //item->addToolTip(name); + item->setToolTip(name); +} + +void ProfileGraphicsView::plot_depth_profile() +{ + int i, incr; + int sec, depth; + struct plot_data *entry; + int maxtime, maxdepth, marker, maxline; + int increments[8] = { 10, 20, 30, 60, 5*60, 10*60, 15*60, 30*60 }; + + /* Get plot scaling limits */ + maxtime = get_maxtime(&gc.pi); + maxdepth = get_maxdepth(&gc.pi); + + gc.maxtime = maxtime; + + /* Time markers: at most every 10 seconds, but no more than 12 markers. + * We start out with 10 seconds and increment up to 30 minutes, + * depending on the dive time. + * This allows for 6h dives - enough (I hope) for even the craziest + * divers - but just in case, for those 8h depth-record-breaking dives, + * we double the interval if this still doesn't get us to 12 or fewer + * time markers */ + i = 0; + while (maxtime / increments[i] > 12 && i < 7) + i++; + incr = increments[i]; + while (maxtime / incr > 12) + incr *= 2; + + gc.leftx = 0; gc.rightx = maxtime; + gc.topy = 0; gc.bottomy = 1.0; + + last_gc = gc; + + QColor c = profile_color[TIME_GRID].at(0); + for (i = incr; i < maxtime; i += incr) { + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(i, 0), SCALEGC(i, 1)); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + } + + timeMarkers = new QGraphicsRectItem(); + /* now the text on the time markers */ + struct text_render_options tro = {DEPTH_TEXT_SIZE, TIME_TEXT, CENTER, TOP}; + if (maxtime < 600) { + /* Be a bit more verbose with shorter dives */ + for (i = incr; i < maxtime; i += incr) + plot_text(&tro, QPointF(i, 0), QString("%1:%2").arg(i/60).arg(i%60), timeMarkers); + } else { + /* Only render the time on every second marker for normal dives */ + for (i = incr; i < maxtime; i += 2 * incr) + plot_text(&tro, QPointF(i, 0), QString("%1").arg(QString::number(i/60)), timeMarkers); + } + timeMarkers->setPos(0,0); + scene()->addItem(timeMarkers); + + /* Depth markers: every 30 ft or 10 m*/ + gc.leftx = 0; gc.rightx = 1.0; + gc.topy = 0; gc.bottomy = maxdepth; + switch (prefs.units.length) { + case units::METERS: + marker = 10000; + break; + case units::FEET: + marker = 9144; + break; /* 30 ft */ + } + maxline = MAX(gc.pi.maxdepth + marker, maxdepth * 2 / 3); + + c = profile_color[DEPTH_GRID].at(0); + + for (i = marker; i < maxline; i += marker) { + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(0, i), SCALEGC(1, i)); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + } + + gc.leftx = 0; gc.rightx = maxtime; + c = profile_color[MEAN_DEPTH].at(0); + + /* Show mean depth */ + if (! gc.printer) { + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(0, gc.pi.meandepth), + SCALEGC(gc.pi.entry[gc.pi.nr - 1].sec, gc.pi.meandepth)); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + } + +#if 0 + /* + * These are good for debugging text placement etc, + * but not for actual display.. + */ + if (0) { + plot_smoothed_profile(gc, pi); + plot_minmax_profile(gc, pi); + } +#endif + + /* Do the depth profile for the neat fill */ + gc.topy = 0; gc.bottomy = maxdepth; + + entry = gc.pi.entry; + + QPolygonF p; + QLinearGradient pat(0.0,0.0,0.0,scene()->height()); + QGraphicsPolygonItem *neatFill = NULL; + + p.append(QPointF(SCALEGC(0, 0))); + for (i = 0; i < gc.pi.nr; i++, entry++) + p.append(QPointF(SCALEGC(entry->sec, entry->depth))); + + /* Show any ceiling we may have encountered */ + for (i = gc.pi.nr - 1; i >= 0; i--, entry--) { + if (entry->ndl) { + /* non-zero NDL implies this is a safety stop, no ceiling */ + p.append(QPointF(SCALEGC(entry->sec, 0))); + } else if (entry->stopdepth < entry->depth) { + p.append(QPointF(SCALEGC(entry->sec, entry->stopdepth))); + } else { + p.append(QPointF(SCALEGC(entry->sec, entry->depth))); + } + } + pat.setColorAt(1, profile_color[DEPTH_BOTTOM].first()); + pat.setColorAt(0, profile_color[DEPTH_TOP].first()); + + neatFill = new QGraphicsPolygonItem(); + neatFill->setPolygon(p); + neatFill->setBrush(QBrush(pat)); + neatFill->setPen(QPen(QBrush(Qt::transparent),0)); + scene()->addItem(neatFill); + + + /* if the user wants the deco ceiling more visible, do that here (this + * basically draws over the background that we had allowed to shine + * through so far) */ + // TODO: port the prefs.profile_red_ceiling to QSettings + + //if (prefs.profile_red_ceiling) { + p.clear(); + pat.setColorAt(0, profile_color[CEILING_SHALLOW].first()); + pat.setColorAt(1, profile_color[CEILING_DEEP].first()); + + entry = gc.pi.entry; + p.append(QPointF(SCALEGC(0, 0))); + for (i = 0; i < gc.pi.nr; i++, entry++) { + if (entry->ndl == 0 && entry->stopdepth) { + if (entry->ndl == 0 && entry->stopdepth < entry->depth) { + p.append(QPointF(SCALEGC(entry->sec, entry->stopdepth))); + } else { + p.append(QPointF(SCALEGC(entry->sec, entry->depth))); + } + } else { + p.append(QPointF(SCALEGC(entry->sec, 0))); + } + } + + neatFill = new QGraphicsPolygonItem(); + neatFill->setBrush(QBrush(pat)); + neatFill->setPolygon(p); + neatFill->setPen(QPen(QBrush(Qt::NoBrush),0)); + scene()->addItem(neatFill); + //} + + /* finally, plot the calculated ceiling over all this */ + // TODO: Port the profile_calc_ceiling to QSettings + // if (prefs.profile_calc_ceiling) { + + pat.setColorAt(0, profile_color[CALC_CEILING_SHALLOW].first()); + pat.setColorAt(1, profile_color[CALC_CEILING_DEEP].first()); + + entry = gc.pi.entry; + p.clear(); + p.append(QPointF(SCALEGC(0, 0))); + for (i = 0; i < gc.pi.nr; i++, entry++) { + if (entry->ceiling) + p.append(QPointF(SCALEGC(entry->sec, entry->ceiling))); + else + p.append(QPointF(SCALEGC(entry->sec, 0))); + } + p.append(QPointF(SCALEGC((entry-1)->sec, 0))); + neatFill = new QGraphicsPolygonItem(); + neatFill->setPolygon(p); + neatFill->setPen(QPen(QBrush(Qt::NoBrush),0)); + neatFill->setBrush(pat); + scene()->addItem(neatFill); + //} + /* next show where we have been bad and crossed the dc's ceiling */ + pat.setColorAt(0, profile_color[CEILING_SHALLOW].first()); + pat.setColorAt(1, profile_color[CEILING_DEEP].first()); + + entry = gc.pi.entry; + p.clear(); + p.append(QPointF(SCALEGC(0, 0))); + for (i = 0; i < gc.pi.nr; i++, entry++) + p.append(QPointF(SCALEGC(entry->sec, entry->depth))); + + for (i = gc.pi.nr - 1; i >= 0; i--, entry--) { + if (entry->ndl == 0 && entry->stopdepth > entry->depth) { + p.append(QPointF(SCALEGC(entry->sec, entry->stopdepth))); + } else { + p.append(QPointF(SCALEGC(entry->sec, entry->depth))); + } + } + + neatFill = new QGraphicsPolygonItem(); + neatFill->setPolygon(p); + neatFill->setPen(QPen(QBrush(Qt::NoBrush),0)); + neatFill->setBrush(QBrush(pat)); + scene()->addItem(neatFill); + + /* Now do it again for the velocity colors */ + entry = gc.pi.entry; + for (i = 1; i < gc.pi.nr; i++) { + entry++; + sec = entry->sec; + /* we want to draw the segments in different colors + * representing the vertical velocity, so we need to + * chop this into short segments */ + depth = entry->depth; + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(entry[-1].sec, entry[-1].depth), SCALEGC(sec, depth)); + QPen pen(defaultPen); + pen.setColor(profile_color[ (color_indice_t) (VELOCITY_COLORS_START_IDX + entry->velocity)].first()); + item->setPen(pen); + scene()->addItem(item); + } +} + +QGraphicsSimpleTextItem *ProfileGraphicsView::plot_text(text_render_options_t *tro,const QPointF& pos, const QString& text, QGraphicsItem *parent) +{ + QFontMetrics fm(font()); + + double dx = tro->hpos * (fm.width(text)); + double dy = tro->vpos * (fm.height()); + + QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem(text, parent); + QPointF point(SCALEGC(pos.x(), pos.y())); // This is neded because of the SCALE macro. + + item->setPos(point.x() + dx, point.y() + dy); + item->setBrush(QBrush(profile_color[tro->color].first())); + item->setFlag(QGraphicsItem::ItemIgnoresTransformations); + + if(!parent) + scene()->addItem(item); + return item; +} + +void ProfileGraphicsView::resizeEvent(QResizeEvent *event) +{ + plot(dive); +} + +void ProfileGraphicsView::plot_temperature_profile() +{ + int last = 0; + + if (!setup_temperature_limits(&gc)) + return; + + QPointF from; + QPointF to; + QColor color = profile_color[TEMP_PLOT].first(); + + for (int i = 0; i < gc.pi.nr; i++) { + struct plot_data *entry = gc.pi.entry + i; + int mkelvin = entry->temperature; + int sec = entry->sec; + if (!mkelvin) { + if (!last) + continue; + mkelvin = last; + } + if (last) { + to = QPointF(SCALEGC(sec, mkelvin)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(color); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(sec, mkelvin)); + } + last = mkelvin; + } +} + +void ToolTipItem::addToolTip(const QString& toolTip, const QIcon& icon) +{ + QGraphicsPixmapItem *iconItem = 0; + double yValue = title->boundingRect().height() + SPACING; + Q_FOREACH(ToolTip t, toolTips) { + yValue += t.second->boundingRect().height(); + } + if (!icon.isNull()) { + iconItem = new QGraphicsPixmapItem(icon.pixmap(ICON_SMALL,ICON_SMALL), this); + iconItem->setPos(SPACING, yValue); + } + + QGraphicsSimpleTextItem *textItem = new QGraphicsSimpleTextItem(toolTip, this); + textItem->setPos(SPACING + ICON_SMALL + SPACING, yValue); + textItem->setBrush(QBrush(Qt::white)); + textItem->setFlag(ItemIgnoresTransformations); + toolTips[toolTip] = qMakePair(iconItem, textItem); + expand(); +} + +void ToolTipItem::removeToolTip(const QString& toolTip) +{ + ToolTip toBeRemoved = toolTips[toolTip]; + delete toBeRemoved.first; + delete toBeRemoved.second; + toolTips.remove(toolTip); + + int toolTipIndex = 0; + + // We removed a toolTip, let's move the others to the correct location + Q_FOREACH(ToolTip t, toolTips) { + double yValue = title->boundingRect().height() + SPACING + toolTipIndex * ICON_SMALL + SPACING; + + // Icons can be null. + if (t.first) + t.first->setPos(SPACING, yValue); + + t.second->setPos(SPACING + ICON_SMALL + SPACING, yValue); + toolTipIndex++; + } + + expand(); +} + +void ToolTipItem::refresh(struct graphics_context *gc, QPointF pos) +{ + clear(); + int time = (pos.x() * gc->maxtime) / scene()->sceneRect().width(); + char buffer[500]; + get_plot_details(gc, time, buffer, 500); + addToolTip(QString(buffer)); + + QList<QGraphicsItem*> items = scene()->items(pos, Qt::IntersectsItemShape, Qt::DescendingOrder, transform()); + Q_FOREACH(QGraphicsItem *item, items) { + if (!item->toolTip().isEmpty()) + addToolTip(item->toolTip()); + } + +} + +void ToolTipItem::clear() +{ + Q_FOREACH(ToolTip t, toolTips) { + delete t.first; + delete t.second; + } + toolTips.clear(); + expand(); +} + +void ToolTipItem::setRect(const QRectF& r) +{ + + // qDeleteAll(childItems()); + delete background; + + rectangle = r; + setBrush(QBrush(Qt::white)); + setPen(QPen(Qt::black, 0.5)); + + // Creates a 2pixels border + QPainterPath border; + border.addRoundedRect(-4, -4, rectangle.width() + 8, rectangle.height() + 10, 3, 3); + border.addRoundedRect(-1, -1, rectangle.width() + 3, rectangle.height() + 4, 3, 3); + setPath(border); + + QPainterPath bg; + bg.addRoundedRect(-1, -1, rectangle.width() + 3, rectangle.height() + 4, 3, 3); + + QColor c = QColor(Qt::black); + c.setAlpha(155); + + QGraphicsPathItem *b = new QGraphicsPathItem(bg, this); + b->setFlag(ItemStacksBehindParent); + b->setFlags(ItemIgnoresTransformations); + b->setBrush(c); + b->setPen(QPen(QBrush(Qt::transparent), 0)); + b->setZValue(-10); + background = b; + + updateTitlePosition(); +} + + +void ToolTipItem::collapse() +{ + QPropertyAnimation *animation = new QPropertyAnimation(this, "rect"); + animation->setDuration(100); + animation->setStartValue(boundingRect()); + animation->setEndValue(QRect(0, 0, ICON_SMALL, ICON_SMALL)); + animation->start(QAbstractAnimation::DeleteWhenStopped); +} + +void ToolTipItem::expand() +{ + + if (!title){ + return; + } + + QRectF nextRectangle; + double width = 0, height = title->boundingRect().height() + SPACING; + Q_FOREACH(ToolTip t, toolTips) { + if (t.second->boundingRect().width() > width) + width = t.second->boundingRect().width(); + height += t.second->boundingRect().height(); + } + /* Left padding, Icon Size, space, right padding */ + width += SPACING + ICON_SMALL + SPACING + SPACING; + + if (width < title->boundingRect().width() + SPACING*2) + width = title->boundingRect().width() + SPACING*2; + + if(height < ICON_SMALL) + height = ICON_SMALL; + + nextRectangle.setWidth(width); + nextRectangle.setHeight(height); + + QPropertyAnimation *animation = new QPropertyAnimation(this, "rect"); + animation->setDuration(100); + animation->setStartValue(rectangle); + animation->setEndValue(nextRectangle); + animation->start(QAbstractAnimation::DeleteWhenStopped); + +} + +ToolTipItem::ToolTipItem(QGraphicsItem* parent): QGraphicsPathItem(parent), background(0) +{ + title = new QGraphicsSimpleTextItem(tr("Information"), this); + separator = new QGraphicsLineItem(this); + + setFlag(ItemIgnoresTransformations); + setFlag(ItemIsMovable); + + updateTitlePosition(); + setZValue(99); +} + +void ToolTipItem::updateTitlePosition() +{ + if (rectangle.width() < title->boundingRect().width() + SPACING*4) { + QRectF newRect = rectangle; + newRect.setWidth(title->boundingRect().width() + SPACING*4); + newRect.setHeight(newRect.height() ? newRect.height() : ICON_SMALL); + setRect(newRect); + } + + title->setPos(boundingRect().width()/2 - title->boundingRect().width()/2 -1, 0); + title->setFlag(ItemIgnoresTransformations); + title->setPen(QPen(Qt::white, 1)); + title->setBrush(Qt::white); + + if (toolTips.size() > 0) { + double x1 = 0; + double y1 = title->pos().y() + SPACING/2 + title->boundingRect().height(); + double x2 = boundingRect().width() - 10; + double y2 = y1; + + separator->setLine(x1, y1, x2, y2); + separator->setFlag(ItemIgnoresTransformations); + separator->setPen(QPen(Qt::white)); + separator->show(); + } else { + separator->hide(); + } +} + +EventItem::EventItem(QGraphicsItem* parent): QGraphicsPolygonItem(parent) +{ + setFlag(ItemIgnoresTransformations); + setFlag(ItemIsFocusable); + setAcceptHoverEvents(true); + + QPolygonF poly; + poly.push_back(QPointF(-8, 16)); + poly.push_back(QPointF(8, 16)); + poly.push_back(QPointF(0, 0)); + poly.push_back(QPointF(-8, 16)); + + QPen defaultPen ; + defaultPen.setJoinStyle(Qt::RoundJoin); + defaultPen.setCapStyle(Qt::RoundCap); + defaultPen.setWidth(2); + defaultPen.setCosmetic(true); + + QPen pen = defaultPen; + pen.setBrush(QBrush(profile_color[ALERT_BG].first())); + + setPolygon(poly); + setBrush(QBrush(profile_color[ALERT_BG].first())); + setPen(pen); + + QGraphicsLineItem *line = new QGraphicsLineItem(0,5,0,10, this); + line->setPen(QPen(Qt::black, 2)); + + QGraphicsEllipseItem *ball = new QGraphicsEllipseItem(-1, 12, 2,2, this); + ball->setBrush(QBrush(Qt::black)); + +} diff --git a/qt-ui/profilegraphics.h b/qt-ui/profilegraphics.h new file mode 100644 index 000000000..453b8cf03 --- /dev/null +++ b/qt-ui/profilegraphics.h @@ -0,0 +1,110 @@ +#ifndef PROFILEGRAPHICS_H +#define PROFILEGRAPHICS_H + +#include "../display.h" +#include <QGraphicsView> +#include <QGraphicsItem> +#include <QIcon> + +struct text_render_options; +struct graphics_context; +struct plot_info; +typedef struct text_render_options text_render_options_t; + +/* To use a tooltip, simply ->setToolTip on the QGraphicsItem that you want + * or, if it's a "global" tooltip, set it on the mouseMoveEvent of the ProfileGraphicsView. + */ +class ToolTipItem :public QObject, public QGraphicsPathItem +{ + Q_OBJECT + void updateTitlePosition(); + Q_PROPERTY(QRectF rect READ boundingRect WRITE setRect) + +public: + enum Status{COLLAPSED, EXPANDED}; + enum {ICON_SMALL = 16, ICON_MEDIUM = 24, ICON_BIG = 32, SPACING=4}; + + explicit ToolTipItem(QGraphicsItem* parent = 0); + + void collapse(); + void expand(); + void clear(); + void addToolTip(const QString& toolTip, const QIcon& icon = QIcon()); + void removeToolTip(const QString& toolTip); + void refresh(struct graphics_context* gc, QPointF pos); + +public Q_SLOTS: + void setRect(const QRectF& rect); + +private: + typedef QPair<QGraphicsPixmapItem*, QGraphicsSimpleTextItem*> ToolTip; + QMap<QString, ToolTip > toolTips; + QGraphicsPathItem *background; + QGraphicsLineItem *separator; + QGraphicsSimpleTextItem *title; + + QRectF rectangle; +}; + +class EventItem : public QGraphicsPolygonItem +{ +public: + explicit EventItem(QGraphicsItem* parent = 0); + +private: + ToolTipItem *controller; + QString text; + QIcon icon; +}; + +class ProfileGraphicsView : public QGraphicsView +{ +Q_OBJECT +public: + ProfileGraphicsView(QWidget* parent = 0); + void plot(struct dive *d); + bool eventFilter(QObject* obj, QEvent* event); + void clear(); + +protected: + void resizeEvent(QResizeEvent *event); + void mouseMoveEvent(QMouseEvent* event); + void wheelEvent(QWheelEvent* event); + void showEvent(QShowEvent* event); + +private: + void plot_depth_profile(); + QGraphicsSimpleTextItem* plot_text(text_render_options_t *tro, const QPointF& pos, const QString &text, QGraphicsItem *parent = 0); + void plot_events(struct divecomputer *dc); + void plot_one_event(struct event *event); + void plot_temperature_profile(); + void plot_cylinder_pressure(struct dive *dive, struct divecomputer *dc); + void plot_temperature_text(); + void plot_single_temp_text(int sec, int mkelvin); + void plot_depth_text(); + void plot_text_samples(); + void plot_depth_sample(struct plot_data *entry, text_render_options_t *tro); + void plot_cylinder_pressure_text(); + void plot_pressure_value(int mbar, int sec, double xalign, double yalign); + void plot_deco_text(); + void plot_pp_gas_profile(); + void plot_pp_text(); + void plot_depth_scale(); + + QColor get_sac_color(int sac, int avg_sac); + + QPen defaultPen; + QBrush defaultBrush; + ToolTipItem *toolTip; + graphics_context gc; + struct dive *dive; + int zoomLevel; + + // Top Level Items. + QGraphicsItem* profileGrid; + QGraphicsItem* timeMarkers; + QGraphicsItem* depthMarkers; + QGraphicsItem* diveComputer; +}; + +#endif diff --git a/qt-ui/starwidget.cpp b/qt-ui/starwidget.cpp new file mode 100644 index 000000000..4d1fa066c --- /dev/null +++ b/qt-ui/starwidget.cpp @@ -0,0 +1,100 @@ +#include "starwidget.h" +#include <QSvgRenderer> +#include <QPainter> +#include <QPaintEvent> +#include <QDebug> +#include <QMouseEvent> + +QPixmap* StarWidget::activeStar = 0; +QPixmap* StarWidget::inactiveStar = 0; + +QPixmap StarWidget::starActive() +{ + return (*activeStar); +} + +QPixmap StarWidget::starInactive() +{ + return (*inactiveStar); +} + +int StarWidget::currentStars() const +{ + return current; +} + +void StarWidget::mouseReleaseEvent(QMouseEvent* event) +{ + int starClicked = event->pos().x() / IMG_SIZE + 1; + if (starClicked > TOTALSTARS) + starClicked = TOTALSTARS; + + if (current == starClicked) + current -= 1; + else + current = starClicked; + + update(); +} + +void StarWidget::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + + for(int i = 0; i < current; i++) + p.drawPixmap(i * IMG_SIZE + SPACING, 0, starActive()); + + for(int i = current; i < TOTALSTARS; i++) + p.drawPixmap(i * IMG_SIZE + SPACING, 0, starInactive()); +} + +void StarWidget::setCurrentStars(int value) +{ + current = value; + update(); + Q_EMIT valueChanged(current); +} + +StarWidget::StarWidget(QWidget* parent, Qt::WindowFlags f): + QWidget(parent, f), + current(0) +{ + if(!activeStar){ + activeStar = new QPixmap(); + QSvgRenderer render(QString(":star")); + QPixmap renderedStar(IMG_SIZE, IMG_SIZE); + + renderedStar.fill(Qt::transparent); + QPainter painter(&renderedStar); + + render.render(&painter, QRectF(0, 0, IMG_SIZE, IMG_SIZE)); + (*activeStar) = renderedStar; + } + if(!inactiveStar){ + inactiveStar = new QPixmap(); + (*inactiveStar) = grayImage(activeStar); + } +} + +QPixmap StarWidget::grayImage(QPixmap* coloredImg) +{ + QImage img = coloredImg->toImage(); + for (int i = 0; i < img.width(); ++i) { + for (int j = 0; j < img.height(); ++j) { + QRgb rgb = img.pixel(i, j); + if (!rgb) + continue; + + QColor c(rgb); + int gray = (c.red() + c.green() + c.blue()) / 3; + img.setPixel(i, j, qRgb(gray, gray, gray)); + } + } + + return QPixmap::fromImage(img); +} + +QSize StarWidget::sizeHint() const +{ + return QSize(IMG_SIZE * TOTALSTARS + SPACING * (TOTALSTARS-1), IMG_SIZE); +} diff --git a/qt-ui/starwidget.h b/qt-ui/starwidget.h new file mode 100644 index 000000000..d92be5a98 --- /dev/null +++ b/qt-ui/starwidget.h @@ -0,0 +1,37 @@ +#ifndef STARWIDGET_H +#define STARWIDGET_H + +#include <QWidget> + +enum StarConfig {SPACING = 2, IMG_SIZE = 16, TOTALSTARS = 5}; + +class StarWidget : public QWidget +{ + Q_OBJECT +public: + explicit StarWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); + int currentStars() const; + + /*reimp*/ QSize sizeHint() const; + + static QPixmap starActive(); + static QPixmap starInactive(); + +Q_SIGNALS: + void valueChanged(int stars); + +public Q_SLOTS: + void setCurrentStars(int value); + +protected: + /*reimp*/ void mouseReleaseEvent(QMouseEvent* ); + /*reimp*/ void paintEvent(QPaintEvent* ); + +private: + int current; + static QPixmap* activeStar; + static QPixmap* inactiveStar; + QPixmap grayImage(QPixmap *coloredImg); +}; + +#endif // STARWIDGET_H |