diff options
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 |