From e49d6213ad129284a45d53c3fcdc03249e84efe2 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Thu, 3 Sep 2015 14:20:19 -0300 Subject: Move qt-ui to desktop-widgets Since we have now destkop and mobile versions, 'qt-ui' was a very poor name choice for a folder that contains only destkop-enabled widgets. Also, move the graphicsview-common.h/cpp to subsurface-core because it doesn't depend on qgraphicsview, it merely implements all the colors that we use throughout Subsurface, and we will use colors on both desktop and mobile versions Same thing applies for metrics.h/cpp Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- desktop-widgets/mainwindow.cpp | 1923 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1923 insertions(+) create mode 100644 desktop-widgets/mainwindow.cpp (limited to 'desktop-widgets/mainwindow.cpp') diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp new file mode 100644 index 000000000..e1e0d81a2 --- /dev/null +++ b/desktop-widgets/mainwindow.cpp @@ -0,0 +1,1923 @@ +/* + * mainwindow.cpp + * + * classes for the main UI window in Subsurface + */ +#include "mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include "version.h" +#include "divelistview.h" +#include "downloadfromdivecomputer.h" +#include "preferences.h" +#include "subsurfacewebservices.h" +#include "divecomputermanagementdialog.h" +#include "about.h" +#include "updatemanager.h" +#include "planner.h" +#include "filtermodels.h" +#include "profile/profilewidget2.h" +#include "globe.h" +#include "divecomputer.h" +#include "maintab.h" +#include "diveplanner.h" +#ifndef NO_PRINTING +#include +#include "printdialog.h" +#endif +#include "tankinfomodel.h" +#include "weigthsysteminfomodel.h" +#include "yearlystatisticsmodel.h" +#include "diveplannermodel.h" +#include "divelogimportdialog.h" +#include "divelogexportdialog.h" +#include "usersurvey.h" +#include "divesitehelpers.h" +#include "windowtitleupdate.h" +#include "locationinformation.h" + +#ifndef NO_USERMANUAL +#include "usermanual.h" +#endif +#include "divepicturemodel.h" +#include "git-access.h" +#include +#include +#include +#include +#include "subsurface-core/color.h" + +#if defined(FBSUPPORT) +#include "socialnetworks.h" +#endif + +QProgressDialog *progressDialog = NULL; +bool progressDialogCanceled = false; + +extern "C" int updateProgress(int percent) +{ + if (progressDialog) + progressDialog->setValue(percent); + return progressDialogCanceled; +} + +MainWindow *MainWindow::m_Instance = NULL; + +MainWindow::MainWindow() : QMainWindow(), + actionNextDive(0), + actionPreviousDive(0), + helpView(0), + state(VIEWALL), + survey(0) +{ + Q_ASSERT_X(m_Instance == NULL, "MainWindow", "MainWindow recreated!"); + m_Instance = this; + ui.setupUi(this); + read_hashes(); + // Define the States of the Application Here, Currently the states are situations where the different + // widgets will change on the mainwindow. + + // for the "default" mode + MainTab *mainTab = new MainTab(); + DiveListView *diveListView = new DiveListView(); + ProfileWidget2 *profileWidget = new ProfileWidget2(); + +#ifndef NO_MARBLE + GlobeGPS *globeGps = GlobeGPS::instance(); +#else + QWidget *globeGps = NULL; +#endif + + PlannerSettingsWidget *plannerSettings = new PlannerSettingsWidget(); + DivePlannerWidget *plannerWidget = new DivePlannerWidget(); + PlannerDetails *plannerDetails = new PlannerDetails(); + + // what is a sane order for those icons? we should have the ones the user is + // most likely to want towards the top so they are always visible + // and the ones that someone likely sets and then never touches again towards the bottom + profileToolbarActions << ui.profCalcCeiling << ui.profCalcAllTissues << // start with various ceilings + ui.profIncrement3m << ui.profDcCeiling << + ui.profPhe << ui.profPn2 << ui.profPO2 << // partial pressure graphs + ui.profRuler << ui.profScaled << // measuring and scaling + ui.profTogglePicture << ui.profTankbar << + ui.profMod << ui.profNdl_tts << // various values that a user is either interested in or not + ui.profEad << ui.profSAC << + ui.profHR << // very few dive computers support this + ui.profTissues; // maybe less frequently used + + QToolBar *toolBar = new QToolBar(); + Q_FOREACH (QAction *a, profileToolbarActions) + toolBar->addAction(a); + toolBar->setOrientation(Qt::Vertical); + toolBar->setIconSize(QSize(24,24)); + QWidget *profileContainer = new QWidget(); + QHBoxLayout *profLayout = new QHBoxLayout(); + profLayout->setSpacing(0); + profLayout->setMargin(0); + profLayout->setContentsMargins(0,0,0,0); + profLayout->addWidget(toolBar); + profLayout->addWidget(profileWidget); + profileContainer->setLayout(profLayout); + + LocationInformationWidget * diveSiteEdit = new LocationInformationWidget(); + connect(diveSiteEdit, &LocationInformationWidget::endEditDiveSite, + this, &MainWindow::setDefaultState); + + connect(diveSiteEdit, &LocationInformationWidget::endEditDiveSite, + mainTab, &MainTab::refreshDiveInfo); + + connect(diveSiteEdit, &LocationInformationWidget::endEditDiveSite, + mainTab, &MainTab::refreshDisplayedDiveSite); + + QWidget *diveSitePictures = new QWidget(); // Placeholder + + std::pair enabled = std::make_pair("enabled", QVariant(true)); + std::pair disabled = std::make_pair("enabled", QVariant(false)); + PropertyList enabledList; + PropertyList disabledList; + enabledList.push_back(enabled); + disabledList.push_back(disabled); + + registerApplicationState("Default", mainTab, profileContainer, diveListView, globeGps ); + registerApplicationState("AddDive", mainTab, profileContainer, diveListView, globeGps ); + registerApplicationState("EditDive", mainTab, profileContainer, diveListView, globeGps ); + registerApplicationState("PlanDive", plannerWidget, profileContainer, plannerSettings, plannerDetails ); + registerApplicationState("EditPlannedDive", plannerWidget, profileContainer, diveListView, globeGps ); + registerApplicationState("EditDiveSite", diveSiteEdit, profileContainer, diveListView, globeGps); + + setStateProperties("Default", enabledList, enabledList, enabledList,enabledList); + setStateProperties("AddDive", enabledList, enabledList, enabledList,enabledList); + setStateProperties("EditDive", enabledList, enabledList, enabledList,enabledList); + setStateProperties("PlanDive", enabledList, enabledList, enabledList,enabledList); + setStateProperties("EditPlannedDive", enabledList, enabledList, enabledList,enabledList); + setStateProperties("EditDiveSite", enabledList, disabledList, disabledList, enabledList); + + setApplicationState("Default"); + + ui.multiFilter->hide(); + + setWindowIcon(QIcon(":subsurface-icon")); + if (!QIcon::hasThemeIcon("window-close")) { + QIcon::setThemeName("subsurface"); + } + connect(dive_list(), SIGNAL(currentDiveChanged(int)), this, SLOT(current_dive_changed(int))); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(readSettings())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), diveListView, SLOT(update())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), diveListView, SLOT(reloadHeaderActions())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), information(), SLOT(updateDiveInfo())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), divePlannerWidget(), SLOT(settingsChanged())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), divePlannerSettingsWidget(), SLOT(settingsChanged())); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), TankInfoModel::instance(), SLOT(update())); + connect(ui.actionRecent1, SIGNAL(triggered(bool)), this, SLOT(recentFileTriggered(bool))); + connect(ui.actionRecent2, SIGNAL(triggered(bool)), this, SLOT(recentFileTriggered(bool))); + connect(ui.actionRecent3, SIGNAL(triggered(bool)), this, SLOT(recentFileTriggered(bool))); + connect(ui.actionRecent4, SIGNAL(triggered(bool)), this, SLOT(recentFileTriggered(bool))); + connect(information(), SIGNAL(addDiveFinished()), graphics(), SLOT(setProfileState())); + connect(DivePlannerPointsModel::instance(), SIGNAL(planCreated()), this, SLOT(planCreated())); + connect(DivePlannerPointsModel::instance(), SIGNAL(planCanceled()), this, SLOT(planCanceled())); + connect(plannerDetails->printPlan(), SIGNAL(pressed()), divePlannerWidget(), SLOT(printDecoPlan())); + connect(this, SIGNAL(startDiveSiteEdit()), this, SLOT(on_actionDiveSiteEdit_triggered())); + +#ifndef NO_MARBLE + connect(information(), SIGNAL(diveSiteChanged(struct dive_site *)), globeGps, SLOT(centerOnDiveSite(struct dive_site *))); +#endif + wtu = new WindowTitleUpdate(); + connect(WindowTitleUpdate::instance(), SIGNAL(updateTitle()), this, SLOT(setAutomaticTitle())); +#ifdef NO_PRINTING + plannerDetails->printPlan()->hide(); + ui.menuFile->removeAction(ui.actionPrint); +#endif +#ifndef USE_LIBGIT23_API + ui.menuFile->removeAction(ui.actionCloudstorageopen); + ui.menuFile->removeAction(ui.actionCloudstoragesave); + qDebug() << "disabled / made invisible the cloud storage stuff"; +#else + enableDisableCloudActions(); +#endif + + ui.mainErrorMessage->hide(); + graphics()->setEmptyState(); + initialUiSetup(); + readSettings(); + diveListView->reload(DiveTripModel::TREE); + diveListView->reloadHeaderActions(); + diveListView->setFocus(); + GlobeGPS::instance()->reload(); + diveListView->expand(dive_list()->model()->index(0, 0)); + diveListView->scrollTo(dive_list()->model()->index(0, 0), QAbstractItemView::PositionAtCenter); + divePlannerWidget()->settingsChanged(); + divePlannerSettingsWidget()->settingsChanged(); +#ifdef NO_MARBLE + ui.menuView->removeAction(ui.actionViewGlobe); +#endif +#ifdef NO_USERMANUAL + ui.menuHelp->removeAction(ui.actionUserManual); +#endif + memset(©PasteDive, 0, sizeof(copyPasteDive)); + memset(&what, 0, sizeof(what)); + + updateManager = new UpdateManager(this); + undoStack = new QUndoStack(this); + QAction *undoAction = undoStack->createUndoAction(this, tr("&Undo")); + QAction *redoAction = undoStack->createRedoAction(this, tr("&Redo")); + undoAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Z)); + redoAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z)); + QListundoRedoActions; + undoRedoActions.append(undoAction); + undoRedoActions.append(redoAction); + ui.menu_Edit->addActions(undoRedoActions); + + ReverseGeoLookupThread *geoLookup = ReverseGeoLookupThread::instance(); + connect(geoLookup, SIGNAL(started()),information(), SLOT(disableGeoLookupEdition())); + connect(geoLookup, SIGNAL(finished()), information(), SLOT(enableGeoLookupEdition())); +#ifndef NO_PRINTING + // copy the bundled print templates to the user path; no overwriting occurs! + copyPath(getPrintingTemplatePathBundle(), getPrintingTemplatePathUser()); + find_all_templates(); +#endif + +#if defined(FBSUPPORT) + FacebookManager *fb = FacebookManager::instance(); + connect(fb, SIGNAL(justLoggedIn(bool)), ui.actionFacebook, SLOT(setEnabled(bool))); + connect(fb, SIGNAL(justLoggedOut(bool)), ui.actionFacebook, SLOT(setEnabled(bool))); + connect(ui.actionFacebook, SIGNAL(triggered(bool)), fb, SLOT(sendDive())); + ui.actionFacebook->setEnabled(fb->loggedIn()); +#else + ui.actionFacebook->setEnabled(false); +#endif + + + ui.menubar->show(); + set_git_update_cb(&updateProgress); +} + +MainWindow::~MainWindow() +{ + write_hashes(); + m_Instance = NULL; +} + +void MainWindow::setStateProperties(const QByteArray& state, const PropertyList& tl, const PropertyList& tr, const PropertyList& bl, const PropertyList& br) +{ + stateProperties[state] = PropertiesForQuadrant(tl, tr, bl, br); +} + +void MainWindow::on_actionDiveSiteEdit_triggered() { + setApplicationState("EditDiveSite"); +} + +void MainWindow::enableDisableCloudActions() +{ +#ifdef USE_LIBGIT23_API + ui.actionCloudstorageopen->setEnabled(prefs.cloud_verification_status == CS_VERIFIED); + ui.actionCloudstoragesave->setEnabled(prefs.cloud_verification_status == CS_VERIFIED); +#endif +} + +PlannerDetails *MainWindow::plannerDetails() const { + return qobject_cast(applicationState["PlanDive"].bottomRight); +} + +PlannerSettingsWidget *MainWindow::divePlannerSettingsWidget() { + return qobject_cast(applicationState["PlanDive"].bottomLeft); +} + +void MainWindow::setDefaultState() { + setApplicationState("Default"); + if (information()->getEditMode() != MainTab::NONE) { + ui.bottomLeft->currentWidget()->setEnabled(false); + } +} + +void MainWindow::setLoadedWithFiles(bool f) +{ + filesAsArguments = f; +} + +bool MainWindow::filesFromCommandLine() const +{ + return filesAsArguments; +} + +MainWindow *MainWindow::instance() +{ + return m_Instance; +} + +// this gets called after we download dives from a divecomputer +void MainWindow::refreshDisplay(bool doRecreateDiveList) +{ + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + information()->reload(); + TankInfoModel::instance()->update(); + GlobeGPS::instance()->reload(); + if (doRecreateDiveList) + recreateDiveList(); + + setApplicationState("Default"); + dive_list()->setEnabled(true); + dive_list()->setFocus(); + WSInfoModel::instance()->updateInfo(); + if (amount_selected == 0) + cleanUpEmpty(); +} + +void MainWindow::recreateDiveList() +{ + dive_list()->reload(DiveTripModel::CURRENT); + TagFilterModel::instance()->repopulate(); + BuddyFilterModel::instance()->repopulate(); + LocationFilterModel::instance()->repopulate(); + SuitsFilterModel::instance()->repopulate(); +} + +void MainWindow::configureToolbar() { + if (selected_dive>0) { + if (current_dive->dc.divemode == FREEDIVE) { + ui.profCalcCeiling->setDisabled(true); + ui.profCalcAllTissues ->setDisabled(true); + ui.profIncrement3m->setDisabled(true); + ui.profDcCeiling->setDisabled(true); + ui.profPhe->setDisabled(true); + ui.profPn2->setDisabled(true); //TODO is the same as scuba? + ui.profPO2->setDisabled(true); //TODO is the same as scuba? + ui.profRuler->setDisabled(false); + ui.profScaled->setDisabled(false); // measuring and scaling + ui.profTogglePicture->setDisabled(false); + ui.profTankbar->setDisabled(true); + ui.profMod->setDisabled(true); + ui.profNdl_tts->setDisabled(true); + ui.profEad->setDisabled(true); + ui.profSAC->setDisabled(true); + ui.profHR->setDisabled(false); + ui.profTissues->setDisabled(true); + } else { + ui.profCalcCeiling->setDisabled(false); + ui.profCalcAllTissues ->setDisabled(false); + ui.profIncrement3m->setDisabled(false); + ui.profDcCeiling->setDisabled(false); + ui.profPhe->setDisabled(false); + ui.profPn2->setDisabled(false); + ui.profPO2->setDisabled(false); // partial pressure graphs + ui.profRuler->setDisabled(false); + ui.profScaled->setDisabled(false); // measuring and scaling + ui.profTogglePicture->setDisabled(false); + ui.profTankbar->setDisabled(false); + ui.profMod->setDisabled(false); + ui.profNdl_tts->setDisabled(false); // various values that a user is either interested in or not + ui.profEad->setDisabled(false); + ui.profSAC->setDisabled(false); + ui.profHR->setDisabled(false); // very few dive computers support this + ui.profTissues->setDisabled(false);; // maybe less frequently used + } + } +} + +void MainWindow::current_dive_changed(int divenr) +{ + if (divenr >= 0) { + select_dive(divenr); + } + graphics()->plotDive(); + information()->updateDiveInfo(); + configureToolbar(); + GlobeGPS::instance()->reload(); +} + +void MainWindow::on_actionNew_triggered() +{ + on_actionClose_triggered(); +} + +void MainWindow::on_actionOpen_triggered() +{ + if (!okToClose(tr("Please save or cancel the current dive edit before opening a new file."))) + return; + + // yes, this look wrong to use getSaveFileName() for the open dialog, but we need to be able + // to enter file names that don't exist in order to use our git syntax /path/to/dir[branch] + // with is a potentially valid input, but of course won't exist. So getOpenFileName() wouldn't work + QFileDialog dialog(this, tr("Open file"), lastUsedDir(), filter()); + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setViewMode(QFileDialog::Detail); + dialog.setLabelText(QFileDialog::Accept, tr("Open")); + dialog.setLabelText(QFileDialog::Reject, tr("Cancel")); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + QStringList filenames; + if (dialog.exec()) + filenames = dialog.selectedFiles(); + if (filenames.isEmpty()) + return; + updateLastUsedDir(QFileInfo(filenames.first()).dir().path()); + closeCurrentFile(); + // some file dialogs decide to add the default extension to a filename without extension + // so we would get dir[branch].ssrf when trying to select dir[branch]. + // let's detect that and remove the incorrect extension + QStringList cleanFilenames; + QRegularExpression reg(".*\\[[^]]+]\\.ssrf", QRegularExpression::CaseInsensitiveOption); + + Q_FOREACH (QString filename, filenames) { + if (reg.match(filename).hasMatch()) + filename.remove(QRegularExpression("\\.ssrf$", QRegularExpression::CaseInsensitiveOption)); + cleanFilenames << filename; + } + loadFiles(cleanFilenames); +} + +void MainWindow::on_actionSave_triggered() +{ + file_save(); +} + +void MainWindow::on_actionSaveAs_triggered() +{ + file_save_as(); +} + +void MainWindow::on_actionCloudstorageopen_triggered() +{ + if (!okToClose(tr("Please save or cancel the current dive edit before opening a new file."))) + return; + + QString filename; + if (getCloudURL(filename)) { + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + return; + } + qDebug() << filename; + + closeCurrentFile(); + + int error; + + showProgressBar(); + QByteArray fileNamePtr = QFile::encodeName(filename); + error = parse_file(fileNamePtr.data()); + if (!error) { + set_filename(fileNamePtr.data(), true); + setTitle(MWTF_FILENAME); + } + getNotificationWidget()->hideNotification(); + process_dives(false, false); + hideProgressBar(); + refreshDisplay(); + ui.actionAutoGroup->setChecked(autogroup); +} + +void MainWindow::on_actionCloudstoragesave_triggered() +{ + QString filename; + if (getCloudURL(filename)) { + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + return; + } + qDebug() << filename; + if (information()->isEditing()) + information()->acceptChanges(); + + showProgressBar(); + + if (save_dives(filename.toUtf8().data())) { + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + return; + } + + hideProgressBar(); + + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + set_filename(filename.toUtf8().data(), true); + setTitle(MWTF_FILENAME); + mark_divelist_changed(false); +} + +void learnImageDirs(QStringList dirnames) +{ + QList > futures; + foreach (QString dir, dirnames) { + futures << QtConcurrent::run(learnImages, QDir(dir), 10, false); + } + DivePictureModel::instance()->updateDivePicturesWhenDone(futures); +} + +void MainWindow::on_actionHash_images_triggered() +{ + QFuture future; + QFileDialog dialog(this, tr("Traverse image directories"), lastUsedDir(), filter()); + dialog.setFileMode(QFileDialog::Directory); + dialog.setViewMode(QFileDialog::Detail); + dialog.setLabelText(QFileDialog::Accept, tr("Scan")); + dialog.setLabelText(QFileDialog::Reject, tr("Cancel")); + QStringList dirnames; + if (dialog.exec()) + dirnames = dialog.selectedFiles(); + if (dirnames.isEmpty()) + return; + future = QtConcurrent::run(learnImageDirs,dirnames); + MainWindow::instance()->getNotificationWidget()->showNotification(tr("Scanning images...(this can take a while)"), KMessageWidget::Information); + MainWindow::instance()->getNotificationWidget()->setFuture(future); + +} + +ProfileWidget2 *MainWindow::graphics() const +{ + return qobject_cast(applicationState["Default"].topRight->layout()->itemAt(1)->widget()); +} + +void MainWindow::cleanUpEmpty() +{ + information()->clearStats(); + information()->clearInfo(); + information()->clearEquipment(); + information()->updateDiveInfo(true); + graphics()->setEmptyState(); + dive_list()->reload(DiveTripModel::TREE); + GlobeGPS::instance()->reload(); + if (!existing_filename) + setTitle(MWTF_DEFAULT); + disableShortcuts(); +} + +bool MainWindow::okToClose(QString message) +{ + if (DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING || + information()->isEditing() ) { + QMessageBox::warning(this, tr("Warning"), message); + return false; + } + if (unsaved_changes() && askSaveChanges() == false) + return false; + + return true; +} + +void MainWindow::closeCurrentFile() +{ + graphics()->setEmptyState(); + /* free the dives and trips */ + clear_git_id(); + clear_dive_file_data(); + cleanUpEmpty(); + mark_divelist_changed(false); + + clear_events(); + + dcList.dcMap.clear(); +} + +void MainWindow::on_actionClose_triggered() +{ + if (okToClose(tr("Please save or cancel the current dive edit before closing the file."))) { + closeCurrentFile(); + // hide any pictures and the filter + DivePictureModel::instance()->updateDivePictures(); + ui.multiFilter->closeFilter(); + recreateDiveList(); + } +} + +QString MainWindow::lastUsedDir() +{ + QSettings settings; + QString lastDir = QDir::homePath(); + + settings.beginGroup("FileDialog"); + if (settings.contains("LastDir")) + if (QDir::setCurrent(settings.value("LastDir").toString())) + lastDir = settings.value("LastDir").toString(); + return lastDir; +} + +void MainWindow::updateLastUsedDir(const QString &dir) +{ + QSettings s; + s.beginGroup("FileDialog"); + s.setValue("LastDir", dir); +} + +void MainWindow::on_actionPrint_triggered() +{ +#ifndef NO_PRINTING + PrintDialog dlg(this); + + dlg.exec(); +#endif +} + +void MainWindow::disableShortcuts(bool disablePaste) +{ + ui.actionPreviousDC->setShortcut(QKeySequence()); + ui.actionNextDC->setShortcut(QKeySequence()); + ui.copy->setShortcut(QKeySequence()); + if (disablePaste) + ui.paste->setShortcut(QKeySequence()); +} + +void MainWindow::enableShortcuts() +{ + ui.actionPreviousDC->setShortcut(Qt::Key_Left); + ui.actionNextDC->setShortcut(Qt::Key_Right); + ui.copy->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C)); + ui.paste->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_V)); +} + +void MainWindow::showProfile() +{ + enableShortcuts(); + graphics()->setProfileState(); + setApplicationState("Default"); +} + +void MainWindow::on_actionPreferences_triggered() +{ + PreferencesDialog::instance()->show(); + PreferencesDialog::instance()->raise(); +} + +void MainWindow::on_actionQuit_triggered() +{ + if (information()->isEditing()) { + information()->rejectChanges(); + if (information()->isEditing()) + // didn't discard the edits + return; + } + if (DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING) { + DivePlannerPointsModel::instance()->cancelPlan(); + if (DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING) + // The planned dive was not discarded + return; + } + + if (unsaved_changes() && (askSaveChanges() == false)) + return; + writeSettings(); + QApplication::quit(); +} + +void MainWindow::on_actionDownloadDC_triggered() +{ + DownloadFromDCWidget dlg(this); + + dlg.exec(); +} + +void MainWindow::on_actionDownloadWeb_triggered() +{ + SubsurfaceWebServices dlg(this); + + dlg.exec(); +} + +void MainWindow::on_actionDivelogs_de_triggered() +{ + DivelogsDeWebServices::instance()->downloadDives(); +} + +void MainWindow::on_actionEditDeviceNames_triggered() +{ + DiveComputerManagementDialog::instance()->init(); + DiveComputerManagementDialog::instance()->update(); + DiveComputerManagementDialog::instance()->show(); +} + +bool MainWindow::plannerStateClean() +{ + if (DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING || + information()->isEditing()) { + QMessageBox::warning(this, tr("Warning"), tr("Please save or cancel the current dive edit before trying to add a dive.")); + return false; + } + return true; +} + +void MainWindow::refreshProfile() +{ + showProfile(); + configureToolbar(); + graphics()->replot(get_dive(selected_dive)); + DivePictureModel::instance()->updateDivePictures(); +} + +void MainWindow::planCanceled() +{ + // while planning we might have modified the displayed_dive + // let's refresh what's shown on the profile + refreshProfile(); + refreshDisplay(false); +} + +void MainWindow::planCreated() +{ + // get the new dive selected and assign a number if reasonable + graphics()->setProfileState(); + if (displayed_dive.id == 0) { + // we might have added a new dive (so displayed_dive was cleared out by clone_dive() + dive_list()->unselectDives(); + select_dive(dive_table.nr - 1); + dive_list()->selectDive(selected_dive); + set_dive_nr_for_current_dive(); + } + // make sure our UI is in a consistent state + information()->updateDiveInfo(); + showProfile(); + refreshDisplay(); +} + +void MainWindow::setPlanNotes() +{ + plannerDetails()->divePlanOutput()->setHtml(displayed_dive.notes); +} + +void MainWindow::printPlan() +{ +#ifndef NO_PRINTING + QString diveplan = plannerDetails()->divePlanOutput()->toHtml(); + QString withDisclaimer = QString(" ") + diveplan + QString(disclaimer); + + QPrinter printer; + QPrintDialog *dialog = new QPrintDialog(&printer, this); + dialog->setWindowTitle(tr("Print runtime table")); + if (dialog->exec() != QDialog::Accepted) + return; + + plannerDetails()->divePlanOutput()->setHtml(withDisclaimer); + plannerDetails()->divePlanOutput()->print(&printer); + plannerDetails()->divePlanOutput()->setHtml(diveplan); +#endif +} + +void MainWindow::setupForAddAndPlan(const char *model) +{ + // clean out the dive and give it an id and the correct dc model + clear_dive(&displayed_dive); + clear_dive_site(&displayed_dive_site); + displayed_dive.id = dive_getUniqID(&displayed_dive); + displayed_dive.when = QDateTime::currentMSecsSinceEpoch() / 1000L + gettimezoneoffset() + 3600; + displayed_dive.dc.model = model; // don't translate! this is stored in the XML file + // setup the dive cylinders + DivePlannerPointsModel::instance()->clear(); + DivePlannerPointsModel::instance()->setupCylinders(); +} + +void MainWindow::on_actionReplanDive_triggered() +{ + if (!plannerStateClean() || !current_dive || !current_dive->dc.model) + return; + else if (strcmp(current_dive->dc.model, "planned dive")) { + if (QMessageBox::warning(this, tr("Warning"), tr("Trying to replan a dive that's not a planned dive."), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) + return; + } + // put us in PLAN mode + DivePlannerPointsModel::instance()->clear(); + DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::PLAN); + + graphics()->setPlanState(); + graphics()->clearHandlers(); + setApplicationState("PlanDive"); + divePlannerWidget()->setReplanButton(true); + DivePlannerPointsModel::instance()->loadFromDive(current_dive); + reset_cylinders(&displayed_dive, true); +} + +void MainWindow::on_actionDivePlanner_triggered() +{ + if (!plannerStateClean()) + return; + + // put us in PLAN mode + DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::PLAN); + setApplicationState("PlanDive"); + + graphics()->setPlanState(); + + // create a simple starting dive, using the first gas from the just copied cylinders + setupForAddAndPlan("planned dive"); // don't translate, stored in XML file + DivePlannerPointsModel::instance()->setupStartTime(); + DivePlannerPointsModel::instance()->createSimpleDive(); + DivePictureModel::instance()->updateDivePictures(); + divePlannerWidget()->setReplanButton(false); +} + +DivePlannerWidget* MainWindow::divePlannerWidget() { + return qobject_cast(applicationState["PlanDive"].topLeft); +} + +void MainWindow::on_actionAddDive_triggered() +{ + if (!plannerStateClean()) + return; + + if (dive_list()->selectedTrips().count() >= 1) { + dive_list()->rememberSelection(); + dive_list()->clearSelection(); + } + + setApplicationState("AddDive"); + DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::ADD); + + // setup things so we can later create our starting dive + setupForAddAndPlan("manually added dive"); // don't translate, stored in the XML file + + // now show the mostly empty main tab + information()->updateDiveInfo(); + + // show main tab + information()->setCurrentIndex(0); + + information()->addDiveStarted(); + + graphics()->setAddState(); + DivePlannerPointsModel::instance()->createSimpleDive(); + configureToolbar(); + graphics()->plotDive(); +} + +void MainWindow::on_actionEditDive_triggered() +{ + if (information()->isEditing() || DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING) { + QMessageBox::warning(this, tr("Warning"), tr("Please, first finish the current edition before trying to do another.")); + return; + } + + const bool isTripEdit = dive_list()->selectedTrips().count() >= 1; + if (!current_dive || isTripEdit || (current_dive->dc.model && strcmp(current_dive->dc.model, "manually added dive"))) { + QMessageBox::warning(this, tr("Warning"), tr("Trying to edit a dive that's not a manually added dive.")); + return; + } + + DivePlannerPointsModel::instance()->clear(); + disableShortcuts(); + DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::ADD); + graphics()->setAddState(); + GlobeGPS::instance()->endGetDiveCoordinates(); + setApplicationState("EditDive"); + DivePlannerPointsModel::instance()->loadFromDive(current_dive); + information()->enableEdition(MainTab::MANUALLY_ADDED_DIVE); +} + +void MainWindow::on_actionRenumber_triggered() +{ + RenumberDialog::instance()->renumberOnlySelected(false); + RenumberDialog::instance()->show(); +} + +void MainWindow::on_actionAutoGroup_triggered() +{ + autogroup = ui.actionAutoGroup->isChecked(); + if (autogroup) + autogroup_dives(); + else + remove_autogen_trips(); + refreshDisplay(); + mark_divelist_changed(true); +} + +void MainWindow::on_actionYearlyStatistics_triggered() +{ + QDialog d; + QVBoxLayout *l = new QVBoxLayout(&d); + YearlyStatisticsModel *m = new YearlyStatisticsModel(); + QTreeView *view = new QTreeView(); + view->setModel(m); + l->addWidget(view); + d.resize(width() * .8, height() / 2); + d.move(width() * .1, height() / 4); + QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), &d); + connect(close, SIGNAL(activated()), &d, SLOT(close())); + QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), &d); + connect(quit, SIGNAL(activated()), this, SLOT(close())); + d.setWindowFlags(Qt::Window | Qt::CustomizeWindowHint + | Qt::WindowCloseButtonHint | Qt::WindowTitleHint); + d.setWindowTitle(tr("Yearly statistics")); + d.setWindowIcon(QIcon(":/subsurface-icon")); + d.exec(); +} + +#define BEHAVIOR QList() + +#define TOGGLE_COLLAPSABLE( X ) \ + ui.mainSplitter->setCollapsible(0, X); \ + ui.mainSplitter->setCollapsible(1, X); \ + ui.topSplitter->setCollapsible(0, X); \ + ui.topSplitter->setCollapsible(1, X); \ + ui.bottomSplitter->setCollapsible(0, X); \ + ui.bottomSplitter->setCollapsible(1, X); + +void MainWindow::on_actionViewList_triggered() +{ + TOGGLE_COLLAPSABLE( true ); + beginChangeState(LIST_MAXIMIZED); + ui.topSplitter->setSizes(BEHAVIOR << EXPANDED << COLLAPSED); + ui.mainSplitter->setSizes(BEHAVIOR << COLLAPSED << EXPANDED); +} + +void MainWindow::on_actionViewProfile_triggered() +{ + TOGGLE_COLLAPSABLE( true ); + beginChangeState(PROFILE_MAXIMIZED); + ui.topSplitter->setSizes(BEHAVIOR << COLLAPSED << EXPANDED); + ui.mainSplitter->setSizes(BEHAVIOR << EXPANDED << COLLAPSED); +} + +void MainWindow::on_actionViewInfo_triggered() +{ + TOGGLE_COLLAPSABLE( true ); + beginChangeState(INFO_MAXIMIZED); + ui.topSplitter->setSizes(BEHAVIOR << EXPANDED << COLLAPSED); + ui.mainSplitter->setSizes(BEHAVIOR << EXPANDED << COLLAPSED); +} + +void MainWindow::on_actionViewGlobe_triggered() +{ + TOGGLE_COLLAPSABLE( true ); + beginChangeState(GLOBE_MAXIMIZED); + ui.mainSplitter->setSizes(BEHAVIOR << COLLAPSED << EXPANDED); + ui.bottomSplitter->setSizes(BEHAVIOR << COLLAPSED << EXPANDED); +} +#undef BEHAVIOR + +void MainWindow::on_actionViewAll_triggered() +{ + TOGGLE_COLLAPSABLE( false ); + beginChangeState(VIEWALL); + static QList mainSizes; + const int appH = qApp->desktop()->size().height(); + const int appW = qApp->desktop()->size().width(); + if (mainSizes.empty()) { + mainSizes.append(appH * 0.7); + mainSizes.append(appH * 0.3); + } + static QList infoProfileSizes; + if (infoProfileSizes.empty()) { + infoProfileSizes.append(appW * 0.3); + infoProfileSizes.append(appW * 0.7); + } + + static QList listGlobeSizes; + if (listGlobeSizes.empty()) { + listGlobeSizes.append(appW * 0.7); + listGlobeSizes.append(appW * 0.3); + } + + QSettings settings; + settings.beginGroup("MainWindow"); + if (settings.value("mainSplitter").isValid()) { + ui.mainSplitter->restoreState(settings.value("mainSplitter").toByteArray()); + ui.topSplitter->restoreState(settings.value("topSplitter").toByteArray()); + ui.bottomSplitter->restoreState(settings.value("bottomSplitter").toByteArray()); + if (ui.mainSplitter->sizes().first() == 0 || ui.mainSplitter->sizes().last() == 0) + ui.mainSplitter->setSizes(mainSizes); + if (ui.topSplitter->sizes().first() == 0 || ui.topSplitter->sizes().last() == 0) + ui.topSplitter->setSizes(infoProfileSizes); + if (ui.bottomSplitter->sizes().first() == 0 || ui.bottomSplitter->sizes().last() == 0) + ui.bottomSplitter->setSizes(listGlobeSizes); + + } else { + ui.mainSplitter->setSizes(mainSizes); + ui.topSplitter->setSizes(infoProfileSizes); + ui.bottomSplitter->setSizes(listGlobeSizes); + } + ui.mainSplitter->setCollapsible(0, false); + ui.mainSplitter->setCollapsible(1, false); + ui.topSplitter->setCollapsible(0, false); + ui.topSplitter->setCollapsible(1, false); + ui.bottomSplitter->setCollapsible(0,false); + ui.bottomSplitter->setCollapsible(1,false); +} + +#undef TOGGLE_COLLAPSABLE + +void MainWindow::beginChangeState(CurrentState s) +{ + if (state == VIEWALL && state != s) { + saveSplitterSizes(); + } + state = s; +} + +void MainWindow::saveSplitterSizes() +{ + QSettings settings; + settings.beginGroup("MainWindow"); + settings.setValue("mainSplitter", ui.mainSplitter->saveState()); + settings.setValue("topSplitter", ui.topSplitter->saveState()); + settings.setValue("bottomSplitter", ui.bottomSplitter->saveState()); +} + +void MainWindow::on_actionPreviousDC_triggered() +{ + unsigned nrdc = number_of_computers(current_dive); + dc_number = (dc_number + nrdc - 1) % nrdc; + configureToolbar(); + graphics()->plotDive(); + information()->updateDiveInfo(); +} + +void MainWindow::on_actionNextDC_triggered() +{ + unsigned nrdc = number_of_computers(current_dive); + dc_number = (dc_number + 1) % nrdc; + configureToolbar(); + graphics()->plotDive(); + information()->updateDiveInfo(); +} + +void MainWindow::on_actionFullScreen_triggered(bool checked) +{ + if (checked) { + setWindowState(windowState() | Qt::WindowFullScreen); + } else { + setWindowState(windowState() & ~Qt::WindowFullScreen); + } +} + +void MainWindow::on_actionAboutSubsurface_triggered() +{ + SubsurfaceAbout dlg(this); + + dlg.exec(); +} + +void MainWindow::on_action_Check_for_Updates_triggered() +{ + if (!updateManager) + updateManager = new UpdateManager(this); + + updateManager->checkForUpdates(); +} + +void MainWindow::on_actionUserManual_triggered() +{ +#ifndef NO_USERMANUAL + if (!helpView) { + helpView = new UserManual(); + } + helpView->show(); +#endif +} + +void MainWindow::on_actionUserSurvey_triggered() +{ + if(!survey) { + survey = new UserSurvey(this); + } + survey->show(); +} + +QString MainWindow::filter() +{ + QString f; + f += "Dive log files ( *.ssrf "; + f += "*.can *.CAN "; + f += "*.db *.DB " ; + f += "*.sql *.SQL " ; + f += "*.dld *.DLD "; + f += "*.jlb *.JLB "; + f += "*.lvd *.LVD "; + f += "*.sde *.SDE "; + f += "*.udcf *.UDCF "; + f += "*.uddf *.UDDF "; + f += "*.xml *.XML "; + f += "*.dlf *.DLF "; + f += ");;"; + + f += "Subsurface (*.ssrf);;"; + f += "Cochran (*.can *.CAN);;"; + f += "DiveLogs.de (*.dld *.DLD);;"; + f += "JDiveLog (*.jlb *.JLB);;"; + f += "Liquivision (*.lvd *.LVD);;"; + f += "Suunto (*.sde *.SDE *.db *.DB);;"; + f += "UDCF (*.udcf *.UDCF);;"; + f += "UDDF (*.uddf *.UDDF);;"; + f += "XML (*.xml *.XML)"; + f += "Divesoft (*.dlf *.DLF)"; + f += "Datatrak/WLog Files (*.log *.LOG)"; + + return f; +} + +bool MainWindow::askSaveChanges() +{ + QString message; + QMessageBox response(this); + + if (existing_filename) + message = tr("Do you want to save the changes that you made in the file %1?") + .arg(displayedFilename(existing_filename)); + else + message = tr("Do you want to save the changes that you made in the data file?"); + + response.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + response.setDefaultButton(QMessageBox::Save); + response.setText(message); + response.setWindowTitle(tr("Save changes?")); // Not displayed on MacOSX as described in Qt API + response.setInformativeText(tr("Changes will be lost if you don't save them.")); + response.setIcon(QMessageBox::Warning); + response.setWindowModality(Qt::WindowModal); + int ret = response.exec(); + + switch (ret) { + case QMessageBox::Save: + file_save(); + return true; + case QMessageBox::Discard: + return true; + } + return false; +} + +void MainWindow::initialUiSetup() +{ + QSettings settings; + settings.beginGroup("MainWindow"); + if (settings.value("maximized", isMaximized()).value()) { + showMaximized(); + } else { + restoreGeometry(settings.value("geometry").toByteArray()); + restoreState(settings.value("windowState", 0).toByteArray()); + } + + state = (CurrentState)settings.value("lastState", 0).toInt(); + switch (state) { + case VIEWALL: + on_actionViewAll_triggered(); + break; + case GLOBE_MAXIMIZED: + on_actionViewGlobe_triggered(); + break; + case INFO_MAXIMIZED: + on_actionViewInfo_triggered(); + break; + case LIST_MAXIMIZED: + on_actionViewList_triggered(); + break; + case PROFILE_MAXIMIZED: + on_actionViewProfile_triggered(); + break; + } + settings.endGroup(); + show(); +} + +const char *getSetting(QSettings &s, QString name) +{ + QVariant v; + v = s.value(name); + if (v.isValid()) { + return strdup(v.toString().toUtf8().data()); + } + return NULL; +} + +#define TOOLBOX_PREF_BUTTON(pref, setting, button) \ + prefs.pref = s.value(#setting).toBool(); \ + ui.button->setChecked(prefs.pref); + +void MainWindow::readSettings() +{ + static bool firstRun = true; + QSettings s; + // the static object for preferences already reads in the settings + // and sets up the font, so just get what we need for the toolbox and other widgets here + + s.beginGroup("TecDetails"); + TOOLBOX_PREF_BUTTON(calcalltissues, calcalltissues, profCalcAllTissues); + TOOLBOX_PREF_BUTTON(calcceiling, calcceiling, profCalcCeiling); + TOOLBOX_PREF_BUTTON(dcceiling, dcceiling, profDcCeiling); + TOOLBOX_PREF_BUTTON(ead, ead, profEad); + TOOLBOX_PREF_BUTTON(calcceiling3m, calcceiling3m, profIncrement3m); + TOOLBOX_PREF_BUTTON(mod, mod, profMod); + TOOLBOX_PREF_BUTTON(calcndltts, calcndltts, profNdl_tts); + TOOLBOX_PREF_BUTTON(pp_graphs.phe, phegraph, profPhe); + TOOLBOX_PREF_BUTTON(pp_graphs.pn2, pn2graph, profPn2); + TOOLBOX_PREF_BUTTON(pp_graphs.po2, po2graph, profPO2); + TOOLBOX_PREF_BUTTON(hrgraph, hrgraph, profHR); + TOOLBOX_PREF_BUTTON(rulergraph, rulergraph, profRuler); + TOOLBOX_PREF_BUTTON(show_sac, show_sac, profSAC); + TOOLBOX_PREF_BUTTON(show_pictures_in_profile, show_pictures_in_profile, profTogglePicture); + TOOLBOX_PREF_BUTTON(tankbar, tankbar, profTankbar); + TOOLBOX_PREF_BUTTON(percentagegraph, percentagegraph, profTissues); + TOOLBOX_PREF_BUTTON(zoomed_plot, zoomed_plot, profScaled); + s.endGroup(); // note: why doesn't the list of 17 buttons match the order in the gui? + s.beginGroup("DiveComputer"); + default_dive_computer_vendor = getSetting(s, "dive_computer_vendor"); + default_dive_computer_product = getSetting(s, "dive_computer_product"); + default_dive_computer_device = getSetting(s, "dive_computer_device"); + default_dive_computer_download_mode = s.value("dive_computer_download_mode").toInt(); + s.endGroup(); + QNetworkProxy proxy; + proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type)); + proxy.setHostName(prefs.proxy_host); + proxy.setPort(prefs.proxy_port); + if (prefs.proxy_auth) { + proxy.setUser(prefs.proxy_user); + proxy.setPassword(prefs.proxy_pass); + } + QNetworkProxy::setApplicationProxy(proxy); + +#if !defined(SUBSURFACE_MOBILE) + loadRecentFiles(&s); + if (firstRun) { + checkSurvey(&s); + firstRun = false; + } +#endif +} + +#undef TOOLBOX_PREF_BUTTON + +void MainWindow::checkSurvey(QSettings *s) +{ + s->beginGroup("UserSurvey"); + if (!s->contains("FirstUse42")) { + QVariant value = QDate().currentDate(); + s->setValue("FirstUse42", value); + } + // wait a week for production versions, but not at all for non-tagged builds + QString ver(subsurface_version()); + int waitTime = 7; + QDate firstUse42 = s->value("FirstUse42").toDate(); + if (run_survey || (firstUse42.daysTo(QDate().currentDate()) > waitTime && !s->contains("SurveyDone"))) { + if (!survey) + survey = new UserSurvey(this); + survey->show(); + } + s->endGroup(); +} + +void MainWindow::writeSettings() +{ + QSettings settings; + + settings.beginGroup("MainWindow"); + settings.setValue("geometry", saveGeometry()); + settings.setValue("windowState", saveState()); + settings.setValue("maximized", isMaximized()); + settings.setValue("lastState", (int)state); + if (state == VIEWALL) + saveSplitterSizes(); + settings.endGroup(); +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + if (DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING || + information()->isEditing()) { + on_actionQuit_triggered(); + event->ignore(); + return; + } + +#ifndef NO_USERMANUAL + if (helpView && helpView->isVisible()) { + helpView->close(); + helpView->deleteLater(); + } +#endif + + if (survey && survey->isVisible()) { + survey->close(); + survey->deleteLater(); + } + + if (unsaved_changes() && (askSaveChanges() == false)) { + event->ignore(); + return; + } + event->accept(); + writeSettings(); + QApplication::closeAllWindows(); +} + +DiveListView *MainWindow::dive_list() +{ + return qobject_cast(applicationState["Default"].bottomLeft); +} + +MainTab *MainWindow::information() +{ + return qobject_cast(applicationState["Default"].topLeft); +} + +void MainWindow::loadRecentFiles(QSettings *s) +{ + QStringList files; + bool modified = false; + + s->beginGroup("Recent_Files"); + for (int c = 1; c <= 4; c++) { + QString key = QString("File_%1").arg(c); + if (s->contains(key)) { + QString file = s->value(key).toString(); + + if (QFile::exists(file)) { + files.append(file); + } else { + modified = true; + } + } else { + break; + } + } + + if (modified) { + for (int c = 0; c < 4; c++) { + QString key = QString("File_%1").arg(c + 1); + + if (files.count() > c) { + s->setValue(key, files.at(c)); + } else { + if (s->contains(key)) { + s->remove(key); + } + } + } + + s->sync(); + } + s->endGroup(); + + for (int c = 0; c < 4; c++) { + QAction *action = this->findChild(QString("actionRecent%1").arg(c + 1)); + + if (files.count() > c) { + QFileInfo fi(files.at(c)); + action->setText(fi.fileName()); + action->setToolTip(fi.absoluteFilePath()); + action->setVisible(true); + } else { + action->setVisible(false); + } + } +} + +void MainWindow::addRecentFile(const QStringList &newFiles) +{ + QStringList files; + QSettings s; + + if (newFiles.isEmpty()) + return; + + s.beginGroup("Recent_Files"); + + for (int c = 1; c <= 4; c++) { + QString key = QString("File_%1").arg(c); + if (s.contains(key)) { + QString file = s.value(key).toString(); + + files.append(file); + } else { + break; + } + } + + foreach (const QString &file, newFiles) { + int index = files.indexOf(QDir::toNativeSeparators(file)); + + if (index >= 0) { + files.removeAt(index); + } + } + + foreach (const QString &file, newFiles) { + if (QFile::exists(file)) { + files.prepend(QDir::toNativeSeparators(file)); + } + } + + while (files.count() > 4) { + files.removeLast(); + } + + for (int c = 1; c <= 4; c++) { + QString key = QString("File_%1").arg(c); + + if (files.count() >= c) { + s.setValue(key, files.at(c - 1)); + } else { + if (s.contains(key)) { + s.remove(key); + } + } + } + s.endGroup(); + s.sync(); + + loadRecentFiles(&s); +} + +void MainWindow::removeRecentFile(QStringList failedFiles) +{ + QStringList files; + QSettings s; + + if (failedFiles.isEmpty()) + return; + + s.beginGroup("Recent_Files"); + + for (int c = 1; c <= 4; c++) { + QString key = QString("File_%1").arg(c); + + if (s.contains(key)) { + QString file = s.value(key).toString(); + files.append(file); + } else { + break; + } + } + + foreach (const QString &file, failedFiles) + files.removeAll(file); + + for (int c = 1; c <= 4; c++) { + QString key = QString("File_%1").arg(c); + + if (files.count() >= c) + s.setValue(key, files.at(c - 1)); + else if (s.contains(key)) + s.remove(key); + } + + s.endGroup(); + s.sync(); + + loadRecentFiles(&s); +} + +void MainWindow::recentFileTriggered(bool checked) +{ + Q_UNUSED(checked); + + if (!okToClose(tr("Please save or cancel the current dive edit before opening a new file."))) + return; + + QAction *actionRecent = (QAction *)sender(); + + const QString &filename = actionRecent->toolTip(); + + updateLastUsedDir(QFileInfo(filename).dir().path()); + closeCurrentFile(); + loadFiles(QStringList() << filename); +} + +int MainWindow::file_save_as(void) +{ + QString filename; + const char *default_filename = existing_filename; + + // if the default is to save to cloud storage, pick something that will work as local file: + // simply extract the branch name which should be the users email address + if (default_filename && strstr(default_filename, prefs.cloud_git_url)) { + QString filename(default_filename); + filename.remove(prefs.cloud_git_url); + filename.remove(0, filename.indexOf("[") + 1); + filename.replace("]", ".ssrf"); + default_filename = strdup(qPrintable(filename)); + } + // create a file dialog that allows us to save to a new file + QFileDialog selection_dialog(this, tr("Save file as"), default_filename, + tr("Subsurface XML files (*.ssrf *.xml *.XML)")); + selection_dialog.setAcceptMode(QFileDialog::AcceptSave); + selection_dialog.setFileMode(QFileDialog::AnyFile); + selection_dialog.setDefaultSuffix(""); + if (same_string(default_filename, "")) { + QFileInfo defaultFile(system_default_filename()); + selection_dialog.setDirectory(qPrintable(defaultFile.absolutePath())); + } + /* if the exit/cancel button is pressed return */ + if (!selection_dialog.exec()) + return 0; + + /* get the first selected file */ + filename = selection_dialog.selectedFiles().at(0); + + /* now for reasons I don't understand we appear to add a .ssrf to + * git style filenames /directory[branch] + * so let's remove that */ + QRegularExpression reg(".*\\[[^]]+]\\.ssrf", QRegularExpression::CaseInsensitiveOption); + if (reg.match(filename).hasMatch()) + filename.remove(QRegularExpression("\\.ssrf$", QRegularExpression::CaseInsensitiveOption)); + if (filename.isNull() || filename.isEmpty()) + return report_error("No filename to save into"); + + if (information()->isEditing()) + information()->acceptChanges(); + + if (save_dives(filename.toUtf8().data())) { + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + return -1; + } + + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + set_filename(filename.toUtf8().data(), true); + setTitle(MWTF_FILENAME); + mark_divelist_changed(false); + addRecentFile(QStringList() << filename); + return 0; +} + +int MainWindow::file_save(void) +{ + const char *current_default; + bool is_cloud = false; + + if (!existing_filename) + return file_save_as(); + + is_cloud = (strncmp(existing_filename, "http", 4) == 0); + + if (information()->isEditing()) + information()->acceptChanges(); + + current_default = prefs.default_filename; + if (strcmp(existing_filename, current_default) == 0) { + /* if we are using the default filename the directory + * that we are creating the file in may not exist */ + QDir current_def_dir = QFileInfo(current_default).absoluteDir(); + if (!current_def_dir.exists()) + current_def_dir.mkpath(current_def_dir.absolutePath()); + } + if (is_cloud) + showProgressBar(); + if (save_dives(existing_filename)) { + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + if (is_cloud) + hideProgressBar(); + return -1; + } + if (is_cloud) + hideProgressBar(); + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + mark_divelist_changed(false); + addRecentFile(QStringList() << QString(existing_filename)); + return 0; +} + +NotificationWidget *MainWindow::getNotificationWidget() +{ + return ui.mainErrorMessage; +} + +void MainWindow::showError() +{ + getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); +} + +QString MainWindow::displayedFilename(QString fullFilename) +{ + QFile f(fullFilename); + QFileInfo fileInfo(f); + QString fileName(fileInfo.fileName()); + + if (fullFilename.contains(prefs.cloud_git_url)) + return tr("[cloud storage for] %1").arg(fileName.left(fileName.indexOf('['))); + else + return fileName; +} + +void MainWindow::setAutomaticTitle() +{ + setTitle(); +} + +void MainWindow::setTitle(enum MainWindowTitleFormat format) +{ + switch (format) { + case MWTF_DEFAULT: + setWindowTitle("Subsurface"); + break; + case MWTF_FILENAME: + if (!existing_filename) { + setTitle(MWTF_DEFAULT); + return; + } + QString unsaved = (unsaved_changes() ? " *" : ""); + setWindowTitle("Subsurface: " + displayedFilename(existing_filename) + unsaved); + break; + } +} + +void MainWindow::importFiles(const QStringList fileNames) +{ + if (fileNames.isEmpty()) + return; + + QByteArray fileNamePtr; + + for (int i = 0; i < fileNames.size(); ++i) { + fileNamePtr = QFile::encodeName(fileNames.at(i)); + parse_file(fileNamePtr.data()); + } + process_dives(true, false); + refreshDisplay(); +} + +void MainWindow::importTxtFiles(const QStringList fileNames) +{ + if (fileNames.isEmpty()) + return; + + QByteArray fileNamePtr, csv; + + for (int i = 0; i < fileNames.size(); ++i) { + fileNamePtr = QFile::encodeName(fileNames.at(i)); + csv = fileNamePtr.data(); + csv.replace(strlen(csv.data()) - 3, 3, "csv"); + parse_txt_file(fileNamePtr.data(), csv); + } + process_dives(true, false); + refreshDisplay(); +} + +void MainWindow::loadFiles(const QStringList fileNames) +{ + bool showWarning = false; + if (fileNames.isEmpty()) { + refreshDisplay(); + return; + } + QByteArray fileNamePtr; + QStringList failedParses; + + showProgressBar(); + for (int i = 0; i < fileNames.size(); ++i) { + int error; + + fileNamePtr = QFile::encodeName(fileNames.at(i)); + error = parse_file(fileNamePtr.data()); + if (!error) { + set_filename(fileNamePtr.data(), true); + setTitle(MWTF_FILENAME); + // if there were any messages, show them + QString warning = get_error_string(); + if (!warning.isEmpty()) { + showWarning = true; + getNotificationWidget()->showNotification(warning , KMessageWidget::Information); + } + } else { + failedParses.append(fileNames.at(i)); + } + } + hideProgressBar(); + if (!showWarning) + getNotificationWidget()->hideNotification(); + process_dives(false, false); + addRecentFile(fileNames); + removeRecentFile(failedParses); + + refreshDisplay(); + ui.actionAutoGroup->setChecked(autogroup); + + int min_datafile_version = get_min_datafile_version(); + if (min_datafile_version >0 && min_datafile_version < DATAFORMAT_VERSION) { + QMessageBox::warning(this, tr("Opening datafile from older version"), + tr("You opened a data file from an older version of Subsurface. We recommend " + "you read the manual to learn about the changes in the new version, especially " + "about dive site management which has changed significantly.\n" + "Subsurface has already tried to pre-populate the data but it might be worth " + "while taking a look at the new dive site management system and to make " + "sure that everything looks correct.")); + } +} + +void MainWindow::on_actionImportDiveLog_triggered() +{ + QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open dive log file"), lastUsedDir(), + tr("Dive log files (*.ssrf *.can *.csv *.db *.sql *.dld *.jlb *.lvd *.sde *.udcf *.uddf *.xml *.txt *.dlf *.apd" + "*.SSRF *.CAN *.CSV *.DB *.SQL *.DLD *.JLB *.LVD *.SDE *.UDCF *.UDDF *.xml *.TXT *.DLF *.APD);;" + "Cochran files (*.can *.CAN);;" + "CSV files (*.csv *.CSV);;" + "DiveLog.de files (*.dld *.DLD);;" + "JDiveLog files (*.jlb *.JLB);;" + "Liquivision files (*.lvd *.LVD);;" + "MkVI files (*.txt *.TXT);;" + "Suunto files (*.sde *.db *.SDE *.DB);;" + "Divesoft files (*.dlf *.DLF);;" + "UDDF/UDCF files (*.uddf *.udcf *.UDDF *.UDCF);;" + "XML files (*.xml *.XML);;" + "APD log viewer (*.apd *.APD);;" + "Datatrak/WLog Files (*.log *.LOG);;" + "OSTCtools Files (*.dive *.DIVE);;" + "All files (*)")); + + if (fileNames.isEmpty()) + return; + updateLastUsedDir(QFileInfo(fileNames[0]).dir().path()); + + QStringList logFiles = fileNames.filter(QRegExp("^.*\\.(?!(csv|txt|apd))", Qt::CaseInsensitive)); + QStringList csvFiles = fileNames.filter(".csv", Qt::CaseInsensitive); + csvFiles += fileNames.filter(".apd", Qt::CaseInsensitive); + QStringList txtFiles = fileNames.filter(".txt", Qt::CaseInsensitive); + + if (logFiles.size()) { + importFiles(logFiles); + } + + if (csvFiles.size()) { + DiveLogImportDialog *diveLogImport = new DiveLogImportDialog(csvFiles, this); + diveLogImport->show(); + process_dives(true, false); + refreshDisplay(); + } + + if (txtFiles.size()) { + importTxtFiles(txtFiles); + } +} + +void MainWindow::editCurrentDive() +{ + if (information()->isEditing() || DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING) { + QMessageBox::warning(this, tr("Warning"), tr("Please, first finish the current edition before trying to do another.")); + return; + } + + struct dive *d = current_dive; + QString defaultDC(d->dc.model); + DivePlannerPointsModel::instance()->clear(); + if (defaultDC == "manually added dive") { + disableShortcuts(); + DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::ADD); + graphics()->setAddState(); + setApplicationState("EditDive"); + DivePlannerPointsModel::instance()->loadFromDive(d); + information()->enableEdition(MainTab::MANUALLY_ADDED_DIVE); + } else if (defaultDC == "planned dive") { + disableShortcuts(); + DivePlannerPointsModel::instance()->setPlanMode(DivePlannerPointsModel::PLAN); + setApplicationState("EditPlannedDive"); + DivePlannerPointsModel::instance()->loadFromDive(d); + information()->enableEdition(MainTab::MANUALLY_ADDED_DIVE); + } +} + +#define PREF_PROFILE(QT_PREFS) \ + QSettings s; \ + s.beginGroup("TecDetails"); \ + s.setValue(#QT_PREFS, triggered); \ + PreferencesDialog::instance()->emitSettingsChanged(); + +#define TOOLBOX_PREF_PROFILE(METHOD, INTERNAL_PREFS, QT_PREFS) \ + void MainWindow::on_##METHOD##_triggered(bool triggered) \ + { \ + prefs.INTERNAL_PREFS = triggered; \ + PREF_PROFILE(QT_PREFS); \ + } + +// note: why doesn't the list of 17 buttons match the order in the gui? or the order above? (line 1136) +TOOLBOX_PREF_PROFILE(profCalcAllTissues, calcalltissues, calcalltissues); +TOOLBOX_PREF_PROFILE(profCalcCeiling, calcceiling, calcceiling); +TOOLBOX_PREF_PROFILE(profDcCeiling, dcceiling, dcceiling); +TOOLBOX_PREF_PROFILE(profEad, ead, ead); +TOOLBOX_PREF_PROFILE(profIncrement3m, calcceiling3m, calcceiling3m); +TOOLBOX_PREF_PROFILE(profMod, mod, mod); +TOOLBOX_PREF_PROFILE(profNdl_tts, calcndltts, calcndltts); +TOOLBOX_PREF_PROFILE(profPhe, pp_graphs.phe, phegraph); +TOOLBOX_PREF_PROFILE(profPn2, pp_graphs.pn2, pn2graph); +TOOLBOX_PREF_PROFILE(profPO2, pp_graphs.po2, po2graph); +TOOLBOX_PREF_PROFILE(profHR, hrgraph, hrgraph); +TOOLBOX_PREF_PROFILE(profRuler, rulergraph, rulergraph); +TOOLBOX_PREF_PROFILE(profSAC, show_sac, show_sac); +TOOLBOX_PREF_PROFILE(profScaled, zoomed_plot, zoomed_plot); +TOOLBOX_PREF_PROFILE(profTogglePicture, show_pictures_in_profile, show_pictures_in_profile); +TOOLBOX_PREF_PROFILE(profTankbar, tankbar, tankbar); +TOOLBOX_PREF_PROFILE(profTissues, percentagegraph, percentagegraph); +// couldn't the args to TOOLBOX_PREF_PROFILE be made to go in the same sequence as TOOLBOX_PREF_BUTTON? + +void MainWindow::turnOffNdlTts() +{ + const bool triggered = false; + prefs.calcndltts = triggered; + PREF_PROFILE(calcndltts); +} + +#undef TOOLBOX_PREF_PROFILE +#undef PERF_PROFILE + +void MainWindow::on_actionExport_triggered() +{ + DiveLogExportDialog diveLogExport; + diveLogExport.exec(); +} + +void MainWindow::on_actionConfigure_Dive_Computer_triggered() +{ + ConfigureDiveComputerDialog *dcConfig = new ConfigureDiveComputerDialog(this); + dcConfig->show(); +} + +void MainWindow::setEnabledToolbar(bool arg1) +{ + Q_FOREACH (QAction *b, profileToolbarActions) + b->setEnabled(arg1); +} + +void MainWindow::on_copy_triggered() +{ + // open dialog to select what gets copied + // copy the displayed dive + DiveComponentSelection dialog(this, ©PasteDive, &what); + dialog.exec(); +} + +void MainWindow::on_paste_triggered() +{ + // take the data in our copyPasteDive and apply it to selected dives + selective_copy_dive(©PasteDive, &displayed_dive, what, false); + information()->showAndTriggerEditSelective(what); +} + +void MainWindow::on_actionFilterTags_triggered() +{ + if (ui.multiFilter->isVisible()) + ui.multiFilter->closeFilter(); + else + ui.multiFilter->setVisible(true); +} + +void MainWindow::registerApplicationState(const QByteArray& state, QWidget *topLeft, QWidget *topRight, QWidget *bottomLeft, QWidget *bottomRight) +{ + applicationState[state] = WidgetForQuadrant(topLeft, topRight, bottomLeft, bottomRight); + if (ui.topLeft->indexOf(topLeft) == -1 && topLeft) { + ui.topLeft->addWidget(topLeft); + } + if (ui.topRight->indexOf(topRight) == -1 && topRight) { + ui.topRight->addWidget(topRight); + } + if (ui.bottomLeft->indexOf(bottomLeft) == -1 && bottomLeft) { + ui.bottomLeft->addWidget(bottomLeft); + } + if(ui.bottomRight->indexOf(bottomRight) == -1 && bottomRight) { + ui.bottomRight->addWidget(bottomRight); + } +} + +void MainWindow::setApplicationState(const QByteArray& state) { + if (!applicationState.keys().contains(state)) + return; + + if (getCurrentAppState() == state) + return; + + setCurrentAppState(state); + +#define SET_CURRENT_INDEX( X ) \ + if (applicationState[state].X) { \ + ui.X->setCurrentWidget( applicationState[state].X); \ + ui.X->show(); \ + } else { \ + ui.X->hide(); \ + } + + SET_CURRENT_INDEX( topLeft ) + Q_FOREACH(const WidgetProperty& p, stateProperties[state].topLeft) { + ui.topLeft->currentWidget()->setProperty( p.first.data(), p.second); + } + SET_CURRENT_INDEX( topRight ) + Q_FOREACH(const WidgetProperty& p, stateProperties[state].topRight) { + ui.topRight->currentWidget()->setProperty( p.first.data(), p.second); + } + SET_CURRENT_INDEX( bottomLeft ) + Q_FOREACH(const WidgetProperty& p, stateProperties[state].bottomLeft) { + ui.bottomLeft->currentWidget()->setProperty( p.first.data(), p.second); + } + SET_CURRENT_INDEX( bottomRight ) + Q_FOREACH(const WidgetProperty& p, stateProperties[state].bottomRight) { + ui.bottomRight->currentWidget()->setProperty( p.first.data(), p.second); + } +#undef SET_CURRENT_INDEX +} + +void MainWindow::showProgressBar() +{ + if (progressDialog) + delete progressDialog; + + progressDialog = new QProgressDialog(tr("Contacting cloud service..."), tr("Cancel"), 0, 100, this); + progressDialog->setWindowModality(Qt::WindowModal); + progressDialog->setMinimumDuration(200); + progressDialogCanceled = false; + connect(progressDialog, SIGNAL(canceled()), this, SLOT(cancelCloudStorageOperation())); +} + +void MainWindow::cancelCloudStorageOperation() +{ + progressDialogCanceled = true; +} + +void MainWindow::hideProgressBar() +{ + if (progressDialog) { + progressDialog->setValue(100); + progressDialog->deleteLater(); + progressDialog = NULL; + } +} -- cgit v1.2.3-70-g09d2 From 1d6683f3e07d9a73af5fab702bc3a551ec7dabc9 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Thu, 3 Sep 2015 15:56:37 -0300 Subject: Move Profile widget out of desktop-widgets The reason for that is, even if profile widget is made with qpainter and for that reason it should be a desktop widget, it's being used on the mobile version because of a lack of QML plotting library that is fast and reliable. We discovered that it was faster just to encapsulate our Profile in a QML class and call it directly. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- CMakeLists.txt | 3 +- desktop-widgets/CMakeLists.txt | 19 - desktop-widgets/diveplanner.cpp | 6 +- desktop-widgets/maintab.cpp | 2 +- desktop-widgets/mainwindow.cpp | 2 +- desktop-widgets/modeldelegates.cpp | 2 +- desktop-widgets/printer.cpp | 1 + desktop-widgets/printer.h | 1 - desktop-widgets/profile/animationfunctions.cpp | 75 - desktop-widgets/profile/animationfunctions.h | 18 - desktop-widgets/profile/divecartesianaxis.cpp | 459 ------ desktop-widgets/profile/divecartesianaxis.h | 122 -- desktop-widgets/profile/diveeventitem.cpp | 172 --- desktop-widgets/profile/diveeventitem.h | 34 - desktop-widgets/profile/divelineitem.cpp | 5 - desktop-widgets/profile/divelineitem.h | 15 - desktop-widgets/profile/divepixmapitem.cpp | 130 -- desktop-widgets/profile/divepixmapitem.h | 57 - desktop-widgets/profile/diveprofileitem.cpp | 979 ------------- desktop-widgets/profile/diveprofileitem.h | 225 --- desktop-widgets/profile/diverectitem.cpp | 5 - desktop-widgets/profile/diverectitem.h | 17 - desktop-widgets/profile/divetextitem.cpp | 113 -- desktop-widgets/profile/divetextitem.h | 38 - desktop-widgets/profile/divetooltipitem.cpp | 285 ---- desktop-widgets/profile/divetooltipitem.h | 67 - desktop-widgets/profile/profilewidget2.cpp | 1836 ------------------------ desktop-widgets/profile/profilewidget2.h | 211 --- desktop-widgets/profile/ruleritem.cpp | 179 --- desktop-widgets/profile/ruleritem.h | 59 - desktop-widgets/profile/tankitem.cpp | 120 -- desktop-widgets/profile/tankitem.h | 39 - desktop-widgets/simplewidgets.cpp | 2 +- desktop-widgets/socialnetworks.cpp | 2 +- profile-widget/CMakeLists.txt | 19 + profile-widget/animationfunctions.cpp | 75 + profile-widget/animationfunctions.h | 18 + profile-widget/divecartesianaxis.cpp | 459 ++++++ profile-widget/divecartesianaxis.h | 122 ++ profile-widget/diveeventitem.cpp | 172 +++ profile-widget/diveeventitem.h | 34 + profile-widget/divelineitem.cpp | 5 + profile-widget/divelineitem.h | 15 + profile-widget/divepixmapitem.cpp | 130 ++ profile-widget/divepixmapitem.h | 57 + profile-widget/diveprofileitem.cpp | 979 +++++++++++++ profile-widget/diveprofileitem.h | 225 +++ profile-widget/diverectitem.cpp | 5 + profile-widget/diverectitem.h | 17 + profile-widget/divetextitem.cpp | 113 ++ profile-widget/divetextitem.h | 38 + profile-widget/divetooltipitem.cpp | 285 ++++ profile-widget/divetooltipitem.h | 67 + profile-widget/profilewidget2.cpp | 1836 ++++++++++++++++++++++++ profile-widget/profilewidget2.h | 211 +++ profile-widget/ruleritem.cpp | 179 +++ profile-widget/ruleritem.h | 59 + profile-widget/tankitem.cpp | 120 ++ profile-widget/tankitem.h | 39 + qt-models/CMakeLists.txt | 1 + 60 files changed, 5291 insertions(+), 5289 deletions(-) delete mode 100644 desktop-widgets/profile/animationfunctions.cpp delete mode 100644 desktop-widgets/profile/animationfunctions.h delete mode 100644 desktop-widgets/profile/divecartesianaxis.cpp delete mode 100644 desktop-widgets/profile/divecartesianaxis.h delete mode 100644 desktop-widgets/profile/diveeventitem.cpp delete mode 100644 desktop-widgets/profile/diveeventitem.h delete mode 100644 desktop-widgets/profile/divelineitem.cpp delete mode 100644 desktop-widgets/profile/divelineitem.h delete mode 100644 desktop-widgets/profile/divepixmapitem.cpp delete mode 100644 desktop-widgets/profile/divepixmapitem.h delete mode 100644 desktop-widgets/profile/diveprofileitem.cpp delete mode 100644 desktop-widgets/profile/diveprofileitem.h delete mode 100644 desktop-widgets/profile/diverectitem.cpp delete mode 100644 desktop-widgets/profile/diverectitem.h delete mode 100644 desktop-widgets/profile/divetextitem.cpp delete mode 100644 desktop-widgets/profile/divetextitem.h delete mode 100644 desktop-widgets/profile/divetooltipitem.cpp delete mode 100644 desktop-widgets/profile/divetooltipitem.h delete mode 100644 desktop-widgets/profile/profilewidget2.cpp delete mode 100644 desktop-widgets/profile/profilewidget2.h delete mode 100644 desktop-widgets/profile/ruleritem.cpp delete mode 100644 desktop-widgets/profile/ruleritem.h delete mode 100644 desktop-widgets/profile/tankitem.cpp delete mode 100644 desktop-widgets/profile/tankitem.h create mode 100644 profile-widget/CMakeLists.txt create mode 100644 profile-widget/animationfunctions.cpp create mode 100644 profile-widget/animationfunctions.h create mode 100644 profile-widget/divecartesianaxis.cpp create mode 100644 profile-widget/divecartesianaxis.h create mode 100644 profile-widget/diveeventitem.cpp create mode 100644 profile-widget/diveeventitem.h create mode 100644 profile-widget/divelineitem.cpp create mode 100644 profile-widget/divelineitem.h create mode 100644 profile-widget/divepixmapitem.cpp create mode 100644 profile-widget/divepixmapitem.h create mode 100644 profile-widget/diveprofileitem.cpp create mode 100644 profile-widget/diveprofileitem.h create mode 100644 profile-widget/diverectitem.cpp create mode 100644 profile-widget/diverectitem.h create mode 100644 profile-widget/divetextitem.cpp create mode 100644 profile-widget/divetextitem.h create mode 100644 profile-widget/divetooltipitem.cpp create mode 100644 profile-widget/divetooltipitem.h create mode 100644 profile-widget/profilewidget2.cpp create mode 100644 profile-widget/profilewidget2.h create mode 100644 profile-widget/ruleritem.cpp create mode 100644 profile-widget/ruleritem.h create mode 100644 profile-widget/tankitem.cpp create mode 100644 profile-widget/tankitem.h (limited to 'desktop-widgets/mainwindow.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index cdee60858..b5b0195a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -313,6 +313,8 @@ endif() add_subdirectory(translations) add_subdirectory(subsurface-core) add_subdirectory(qt-models) +add_subdirectory(profile-widget) +add_subdirectory(desktop-widgets) if(FBSUPPORT) add_definitions(-DFBSUPPORT) @@ -338,7 +340,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT ANDROID) set(SUBSURFACE_LINK_LIBRARIES ${SUBSURFACE_LINK_LIBRARIES} -lpthread) endif() -add_subdirectory(desktop-widgets) # create the executables if(SUBSURFACE_MOBILE) diff --git a/desktop-widgets/CMakeLists.txt b/desktop-widgets/CMakeLists.txt index 2c373b83f..635a8b68e 100644 --- a/desktop-widgets/CMakeLists.txt +++ b/desktop-widgets/CMakeLists.txt @@ -75,23 +75,6 @@ endif() source_group("Subsurface Interface" FILES ${SUBSURFACE_INTERFACE}) -# the profile widget -set(SUBSURFACE_PROFILE_LIB_SRCS - profile/profilewidget2.cpp - profile/diverectitem.cpp - profile/divepixmapitem.cpp - profile/divelineitem.cpp - profile/divetextitem.cpp - profile/animationfunctions.cpp - profile/divecartesianaxis.cpp - profile/diveprofileitem.cpp - profile/diveeventitem.cpp - profile/divetooltipitem.cpp - profile/ruleritem.cpp - profile/tankitem.cpp -) -source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS}) - # the yearly statistics widget. set(SUBSURFACE_STATISTICS_LIB_SRCS statistics/statisticswidget.cpp @@ -101,8 +84,6 @@ set(SUBSURFACE_STATISTICS_LIB_SRCS ) source_group("Subsurface Statistics" FILES ${SUBSURFACE_STATISTICS_LIB_SRCS}) -add_library(subsurface_profile STATIC ${SUBSURFACE_PROFILE_LIB_SRCS}) -target_link_libraries(subsurface_profile ${QT_LIBRARIES}) add_library(subsurface_statistics STATIC ${SUBSURFACE_STATISTICS_LIB_SRCS}) target_link_libraries(subsurface_statistics ${QT_LIBRARIES}) add_library(subsurface_generated_ui STATIC ${SUBSURFACE_UI_HDRS}) diff --git a/desktop-widgets/diveplanner.cpp b/desktop-widgets/diveplanner.cpp index b4413d11a..86f03b1d2 100644 --- a/desktop-widgets/diveplanner.cpp +++ b/desktop-widgets/diveplanner.cpp @@ -5,7 +5,7 @@ #include "helpers.h" #include "cylindermodel.h" #include "models.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "diveplannermodel.h" #include @@ -328,11 +328,11 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f) modeMapper->setMapping(ui.recreational_deco, int(RECREATIONAL)); modeMapper->setMapping(ui.buehlmann_deco, int(BUEHLMANN)); modeMapper->setMapping(ui.vpmb_deco, int(VPMB)); - + connect(ui.recreational_deco, SIGNAL(clicked()), modeMapper, SLOT(map())); connect(ui.buehlmann_deco, SIGNAL(clicked()), modeMapper, SLOT(map())); connect(ui.vpmb_deco, SIGNAL(clicked()), modeMapper, SLOT(map())); - + connect(ui.lastStop, SIGNAL(toggled(bool)), plannerModel, SLOT(setLastStop6m(bool))); connect(ui.verbatim_plan, SIGNAL(toggled(bool)), plannerModel, SLOT(setVerbatim(bool))); connect(ui.display_duration, SIGNAL(toggled(bool)), plannerModel, SLOT(setDisplayDuration(bool))); diff --git a/desktop-widgets/maintab.cpp b/desktop-widgets/maintab.cpp index 0afb7b4c0..4d4cd3a5e 100644 --- a/desktop-widgets/maintab.cpp +++ b/desktop-widgets/maintab.cpp @@ -13,7 +13,7 @@ #include "diveplannermodel.h" #include "divelistview.h" #include "display.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "diveplanner.h" #include "divesitehelpers.h" #include "cylindermodel.h" diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index e1e0d81a2..43b433d62 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -21,7 +21,7 @@ #include "updatemanager.h" #include "planner.h" #include "filtermodels.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "globe.h" #include "divecomputer.h" #include "maintab.h" diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index 881037a83..1b1a31c0b 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -5,7 +5,7 @@ #include "cylindermodel.h" #include "models.h" #include "starwidget.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "tankinfomodel.h" #include "weigthsysteminfomodel.h" #include "weightmodel.h" diff --git a/desktop-widgets/printer.cpp b/desktop-widgets/printer.cpp index f0197d446..33ee71b55 100644 --- a/desktop-widgets/printer.cpp +++ b/desktop-widgets/printer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "profile-widget/profilewidget2.h" Printer::Printer(QPaintDevice *paintDevice, print_options *printOptions, template_options *templateOptions, PrintMode printMode) { diff --git a/desktop-widgets/printer.h b/desktop-widgets/printer.h index 979cacd6a..e5f16d77d 100644 --- a/desktop-widgets/printer.h +++ b/desktop-widgets/printer.h @@ -6,7 +6,6 @@ #include #include -#include "profile/profilewidget2.h" #include "printoptions.h" #include "templateedit.h" diff --git a/desktop-widgets/profile/animationfunctions.cpp b/desktop-widgets/profile/animationfunctions.cpp deleted file mode 100644 index a19d50c9d..000000000 --- a/desktop-widgets/profile/animationfunctions.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "animationfunctions.h" -#include "pref.h" -#include - -namespace Animations { - - void hide(QObject *obj) - { - if (prefs.animation_speed != 0) { - QPropertyAnimation *animation = new QPropertyAnimation(obj, "opacity"); - animation->setStartValue(1); - animation->setEndValue(0); - animation->start(QAbstractAnimation::DeleteWhenStopped); - } else { - obj->setProperty("opacity", 0); - } - } - - void show(QObject *obj) - { - if (prefs.animation_speed != 0) { - QPropertyAnimation *animation = new QPropertyAnimation(obj, "opacity"); - animation->setStartValue(0); - animation->setEndValue(1); - animation->start(QAbstractAnimation::DeleteWhenStopped); - } else { - obj->setProperty("opacity", 1); - } - } - - void animDelete(QObject *obj) - { - if (prefs.animation_speed != 0) { - QPropertyAnimation *animation = new QPropertyAnimation(obj, "opacity"); - obj->connect(animation, SIGNAL(finished()), SLOT(deleteLater())); - animation->setStartValue(1); - animation->setEndValue(0); - animation->start(QAbstractAnimation::DeleteWhenStopped); - } else { - obj->setProperty("opacity", 0); - } - } - - void moveTo(QObject *obj, qreal x, qreal y) - { - if (prefs.animation_speed != 0) { - QPropertyAnimation *animation = new QPropertyAnimation(obj, "pos"); - animation->setDuration(prefs.animation_speed); - animation->setStartValue(obj->property("pos").toPointF()); - animation->setEndValue(QPointF(x, y)); - animation->start(QAbstractAnimation::DeleteWhenStopped); - } else { - obj->setProperty("pos", QPointF(x, y)); - } - } - - void scaleTo(QObject *obj, qreal scale) - { - if (prefs.animation_speed != 0) { - QPropertyAnimation *animation = new QPropertyAnimation(obj, "scale"); - animation->setDuration(prefs.animation_speed); - animation->setStartValue(obj->property("scale").toReal()); - animation->setEndValue(QVariant::fromValue(scale)); - animation->setEasingCurve(QEasingCurve::InCubic); - animation->start(QAbstractAnimation::DeleteWhenStopped); - } else { - obj->setProperty("scale", QVariant::fromValue(scale)); - } - } - - void moveTo(QObject *obj, const QPointF &pos) - { - moveTo(obj, pos.x(), pos.y()); - } -} diff --git a/desktop-widgets/profile/animationfunctions.h b/desktop-widgets/profile/animationfunctions.h deleted file mode 100644 index 3cfcff563..000000000 --- a/desktop-widgets/profile/animationfunctions.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef ANIMATIONFUNCTIONS_H -#define ANIMATIONFUNCTIONS_H - -#include -#include - -class QObject; - -namespace Animations { - void hide(QObject *obj); - void show(QObject *obj); - void moveTo(QObject *obj, qreal x, qreal y); - void moveTo(QObject *obj, const QPointF &pos); - void animDelete(QObject *obj); - void scaleTo(QObject *obj, qreal scale); -} - -#endif // ANIMATIONFUNCTIONS_H diff --git a/desktop-widgets/profile/divecartesianaxis.cpp b/desktop-widgets/profile/divecartesianaxis.cpp deleted file mode 100644 index bf5a5380c..000000000 --- a/desktop-widgets/profile/divecartesianaxis.cpp +++ /dev/null @@ -1,459 +0,0 @@ -#include "divecartesianaxis.h" -#include "divetextitem.h" -#include "helpers.h" -#include "preferences.h" -#include "diveplotdatamodel.h" -#include "animationfunctions.h" -#include "mainwindow.h" -#include "divelineitem.h" -#include "profilewidget2.h" - -QPen DiveCartesianAxis::gridPen() -{ - QPen pen; - pen.setColor(getColor(TIME_GRID)); - /* cosmetic width() == 0 for lines in printMode - * having setCosmetic(true) and width() > 0 does not work when - * printing on OSX and Linux */ - pen.setWidth(DiveCartesianAxis::printMode ? 0 : 2); - pen.setCosmetic(true); - return pen; -} - -double DiveCartesianAxis::tickInterval() const -{ - return interval; -} - -double DiveCartesianAxis::tickSize() const -{ - return tick_size; -} - -void DiveCartesianAxis::setFontLabelScale(qreal scale) -{ - labelScale = scale; - changed = true; -} - -void DiveCartesianAxis::setPrintMode(bool mode) -{ - printMode = mode; - // update the QPen of all lines depending on printMode - QPen newPen = gridPen(); - QColor oldColor = pen().brush().color(); - newPen.setBrush(oldColor); - setPen(newPen); - Q_FOREACH (DiveLineItem *item, lines) - item->setPen(pen()); -} - -void DiveCartesianAxis::setMaximum(double maximum) -{ - if (IS_FP_SAME(max, maximum)) - return; - max = maximum; - changed = true; - emit maxChanged(); -} - -void DiveCartesianAxis::setMinimum(double minimum) -{ - if (IS_FP_SAME(min, minimum)) - return; - min = minimum; - changed = true; -} - -void DiveCartesianAxis::setTextColor(const QColor &color) -{ - textColor = color; -} - -DiveCartesianAxis::DiveCartesianAxis() : QObject(), - QGraphicsLineItem(), - printMode(false), - unitSystem(0), - orientation(LeftToRight), - min(0), - max(0), - interval(1), - tick_size(0), - textVisibility(true), - lineVisibility(true), - labelScale(1.0), - line_size(1), - changed(true) -{ - setPen(gridPen()); -} - -DiveCartesianAxis::~DiveCartesianAxis() -{ -} - -void DiveCartesianAxis::setLineSize(qreal lineSize) -{ - line_size = lineSize; - changed = true; -} - -void DiveCartesianAxis::setOrientation(Orientation o) -{ - orientation = o; - changed = true; -} - -QColor DiveCartesianAxis::colorForValue(double value) -{ - return QColor(Qt::black); -} - -void DiveCartesianAxis::setTextVisible(bool arg1) -{ - if (textVisibility == arg1) { - return; - } - textVisibility = arg1; - Q_FOREACH (DiveTextItem *item, labels) { - item->setVisible(textVisibility); - } -} - -void DiveCartesianAxis::setLinesVisible(bool arg1) -{ - if (lineVisibility == arg1) { - return; - } - lineVisibility = arg1; - Q_FOREACH (DiveLineItem *item, lines) { - item->setVisible(lineVisibility); - } -} - -template -void emptyList(QList &list, double steps) -{ - if (!list.isEmpty() && list.size() > steps) { - while (list.size() > steps) { - T *removedItem = list.takeLast(); - Animations::animDelete(removedItem); - } - } -} - -void DiveCartesianAxis::updateTicks(color_indice_t color) -{ - if (!scene() || (!changed && !MainWindow::instance()->graphics()->getPrintMode())) - return; - QLineF m = line(); - // unused so far: - // QGraphicsView *view = scene()->views().first(); - double steps = (max - min) / interval; - double currValueText = min; - double currValueLine = min; - - if (steps < 1) - return; - - emptyList(labels, steps); - emptyList(lines, steps); - - // Move the remaining Ticks / Text to it's corerct position - // Regartind the possibly new values for the Axis - qreal begin, stepSize; - if (orientation == TopToBottom) { - begin = m.y1(); - stepSize = (m.y2() - m.y1()); - } else if (orientation == BottomToTop) { - begin = m.y2(); - stepSize = (m.y2() - m.y1()); - } else if (orientation == LeftToRight) { - begin = m.x1(); - stepSize = (m.x2() - m.x1()); - } else /* if (orientation == RightToLeft) */ { - begin = m.x2(); - stepSize = (m.x2() - m.x1()); - } - stepSize = stepSize / steps; - - for (int i = 0, count = labels.size(); i < count; i++, currValueText += interval) { - qreal childPos = (orientation == TopToBottom || orientation == LeftToRight) ? - begin + i * stepSize : - begin - i * stepSize; - - labels[i]->setText(textForValue(currValueText)); - if (orientation == LeftToRight || orientation == RightToLeft) { - Animations::moveTo(labels[i],childPos, m.y1() + tick_size); - } else { - Animations::moveTo(labels[i],m.x1() - tick_size, childPos); - } - } - - for (int i = 0, count = lines.size(); i < count; i++, currValueLine += interval) { - qreal childPos = (orientation == TopToBottom || orientation == LeftToRight) ? - begin + i * stepSize : - begin - i * stepSize; - - if (orientation == LeftToRight || orientation == RightToLeft) { - Animations::moveTo(lines[i],childPos, m.y1()); - } else { - Animations::moveTo(lines[i],m.x1(), childPos); - } - } - - // Add's the rest of the needed Ticks / Text. - for (int i = labels.size(); i < steps; i++, currValueText += interval) { - qreal childPos; - if (orientation == TopToBottom || orientation == LeftToRight) { - childPos = begin + i * stepSize; - } else { - childPos = begin - i * stepSize; - } - DiveTextItem *label = new DiveTextItem(this); - label->setText(textForValue(currValueText)); - label->setBrush(colorForValue(currValueText)); - label->setScale(fontLabelScale()); - label->setZValue(1); - labels.push_back(label); - if (orientation == RightToLeft || orientation == LeftToRight) { - label->setAlignment(Qt::AlignBottom | Qt::AlignHCenter); - label->setPos(scene()->sceneRect().width() + 10, m.y1() + tick_size); // position it outside of the scene); - Animations::moveTo(label,childPos, m.y1() + tick_size); - } else { - label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); - label->setPos(m.x1() - tick_size, scene()->sceneRect().height() + 10); - Animations::moveTo(label,m.x1() - tick_size, childPos); - } - } - - // Add's the rest of the needed Ticks / Text. - for (int i = lines.size(); i < steps; i++, currValueText += interval) { - qreal childPos; - if (orientation == TopToBottom || orientation == LeftToRight) { - childPos = begin + i * stepSize; - } else { - childPos = begin - i * stepSize; - } - DiveLineItem *line = new DiveLineItem(this); - QPen pen = gridPen(); - pen.setBrush(getColor(color)); - line->setPen(pen); - line->setZValue(0); - lines.push_back(line); - if (orientation == RightToLeft || orientation == LeftToRight) { - line->setLine(0, -line_size, 0, 0); - line->setPos(scene()->sceneRect().width() + 10, m.y1()); // position it outside of the scene); - Animations::moveTo(line,childPos, m.y1()); - } else { - QPointF p1 = mapFromScene(3, 0); - QPointF p2 = mapFromScene(line_size, 0); - line->setLine(p1.x(), 0, p2.x(), 0); - line->setPos(m.x1(), scene()->sceneRect().height() + 10); - Animations::moveTo(line,m.x1(), childPos); - } - } - - Q_FOREACH (DiveTextItem *item, labels) - item->setVisible(textVisibility); - Q_FOREACH (DiveLineItem *item, lines) - item->setVisible(lineVisibility); - changed = false; -} - -void DiveCartesianAxis::setLine(const QLineF &line) -{ - QGraphicsLineItem::setLine(line); - changed = true; -} - -void DiveCartesianAxis::animateChangeLine(const QLineF &newLine) -{ - setLine(newLine); - updateTicks(); - sizeChanged(); -} - -QString DiveCartesianAxis::textForValue(double value) -{ - return QString::number(value); -} - -void DiveCartesianAxis::setTickSize(qreal size) -{ - tick_size = size; -} - -void DiveCartesianAxis::setTickInterval(double i) -{ - interval = i; -} - -qreal DiveCartesianAxis::valueAt(const QPointF &p) const -{ - QLineF m = line(); - QPointF relativePosition = p; - relativePosition -= pos(); // normalize p based on the axis' offset on screen - - double retValue = (orientation == LeftToRight || orientation == RightToLeft) ? - max * (relativePosition.x() - m.x1()) / (m.x2() - m.x1()) : - max * (relativePosition.y() - m.y1()) / (m.y2() - m.y1()); - return retValue; -} - -qreal DiveCartesianAxis::posAtValue(qreal value) -{ - QLineF m = line(); - QPointF p = pos(); - - double size = max - min; - // unused for now: - // double distanceFromOrigin = value - min; - double percent = IS_FP_SAME(min, max) ? 0.0 : (value - min) / size; - - - double realSize = orientation == LeftToRight || orientation == RightToLeft ? - m.x2() - m.x1() : - m.y2() - m.y1(); - - // Inverted axis, just invert the percentage. - if (orientation == RightToLeft || orientation == BottomToTop) - percent = 1 - percent; - - double retValue = realSize * percent; - double adjusted = - orientation == LeftToRight ? retValue + m.x1() + p.x() : - orientation == RightToLeft ? retValue + m.x1() + p.x() : - orientation == TopToBottom ? retValue + m.y1() + p.y() : - /* entation == BottomToTop */ retValue + m.y1() + p.y(); - return adjusted; -} - -qreal DiveCartesianAxis::percentAt(const QPointF &p) -{ - qreal value = valueAt(p); - double size = max - min; - double percent = value / size; - return percent; -} - -double DiveCartesianAxis::maximum() const -{ - return max; -} - -double DiveCartesianAxis::minimum() const -{ - return min; -} - -double DiveCartesianAxis::fontLabelScale() const -{ - return labelScale; -} - -void DiveCartesianAxis::setColor(const QColor &color) -{ - QPen defaultPen = gridPen(); - defaultPen.setColor(color); - defaultPen.setJoinStyle(Qt::RoundJoin); - defaultPen.setCapStyle(Qt::RoundCap); - setPen(defaultPen); -} - -QString DepthAxis::textForValue(double value) -{ - if (value == 0) - return QString(); - return get_depth_string(value, false, false); -} - -QColor DepthAxis::colorForValue(double value) -{ - Q_UNUSED(value); - return QColor(Qt::red); -} - -DepthAxis::DepthAxis() -{ - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); - changed = true; - settingsChanged(); -} - -void DepthAxis::settingsChanged() -{ - static int unitSystem = prefs.units.length; - if ( unitSystem == prefs.units.length ) - return; - changed = true; - updateTicks(); - unitSystem = prefs.units.length; -} - -QColor TimeAxis::colorForValue(double value) -{ - Q_UNUSED(value); - return QColor(Qt::blue); -} - -QString TimeAxis::textForValue(double value) -{ - int nr = value / 60; - if (maximum() < 600) - return QString("%1:%2").arg(nr).arg((int)value % 60, 2, 10, QChar('0')); - return QString::number(nr); -} - -void TimeAxis::updateTicks() -{ - DiveCartesianAxis::updateTicks(); - if (maximum() > 600) { - for (int i = 0; i < labels.count(); i++) { - labels[i]->setVisible(i % 2); - } - } -} - -QString TemperatureAxis::textForValue(double value) -{ - return QString::number(mkelvin_to_C((int)value)); -} - -PartialGasPressureAxis::PartialGasPressureAxis() : - DiveCartesianAxis(), - model(NULL) -{ - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); -} - -void PartialGasPressureAxis::setModel(DivePlotDataModel *m) -{ - model = m; - connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(settingsChanged())); - settingsChanged(); -} - -void PartialGasPressureAxis::settingsChanged() -{ - bool showPhe = prefs.pp_graphs.phe; - bool showPn2 = prefs.pp_graphs.pn2; - bool showPo2 = prefs.pp_graphs.po2; - setVisible(showPhe || showPn2 || showPo2); - if (!model->rowCount()) - return; - - double max = showPhe ? model->pheMax() : -1; - if (showPn2 && model->pn2Max() > max) - max = model->pn2Max(); - if (showPo2 && model->po2Max() > max) - max = model->po2Max(); - - qreal pp = floor(max * 10.0) / 10.0 + 0.2; - if (IS_FP_SAME(maximum(), pp)) - return; - - setMaximum(pp); - setTickInterval(pp > 4 ? 0.5 : 0.25); - updateTicks(); -} diff --git a/desktop-widgets/profile/divecartesianaxis.h b/desktop-widgets/profile/divecartesianaxis.h deleted file mode 100644 index cc7d0bcf7..000000000 --- a/desktop-widgets/profile/divecartesianaxis.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef DIVECARTESIANAXIS_H -#define DIVECARTESIANAXIS_H - -#include -#include -#include "subsurface-core/color.h" - -class QPropertyAnimation; -class DiveTextItem; -class DiveLineItem; -class DivePlotDataModel; - -class DiveCartesianAxis : public QObject, public QGraphicsLineItem { - Q_OBJECT - Q_PROPERTY(QLineF line WRITE setLine READ line) - Q_PROPERTY(QPointF pos WRITE setPos READ pos) - Q_PROPERTY(qreal x WRITE setX READ x) - Q_PROPERTY(qreal y WRITE setY READ y) -private: - bool printMode; - QPen gridPen(); -public: - enum Orientation { - TopToBottom, - BottomToTop, - LeftToRight, - RightToLeft - }; - DiveCartesianAxis(); - virtual ~DiveCartesianAxis(); - void setPrintMode(bool mode); - void setMinimum(double minimum); - void setMaximum(double maximum); - void setTickInterval(double interval); - void setOrientation(Orientation orientation); - void setTickSize(qreal size); - void setFontLabelScale(qreal scale); - double minimum() const; - double maximum() const; - double tickInterval() const; - double tickSize() const; - double fontLabelScale() const; - qreal valueAt(const QPointF &p) const; - qreal percentAt(const QPointF &p); - qreal posAtValue(qreal value); - void setColor(const QColor &color); - void setTextColor(const QColor &color); - void animateChangeLine(const QLineF &newLine); - void setTextVisible(bool arg1); - void setLinesVisible(bool arg1); - void setLineSize(qreal lineSize); - void setLine(const QLineF& line); - int unitSystem; -public -slots: - virtual void updateTicks(color_indice_t color = TIME_GRID); - -signals: - void sizeChanged(); - void maxChanged(); - -protected: - virtual QString textForValue(double value); - virtual QColor colorForValue(double value); - Orientation orientation; - QList labels; - QList lines; - double min; - double max; - double interval; - double tick_size; - QColor textColor; - bool textVisibility; - bool lineVisibility; - double labelScale; - qreal line_size; - bool changed; -}; - -class DepthAxis : public DiveCartesianAxis { - Q_OBJECT -public: - DepthAxis(); - -protected: - QString textForValue(double value); - QColor colorForValue(double value); -private -slots: - void settingsChanged(); -}; - -class TimeAxis : public DiveCartesianAxis { - Q_OBJECT -public: - virtual void updateTicks(); - -protected: - QString textForValue(double value); - QColor colorForValue(double value); -}; - -class TemperatureAxis : public DiveCartesianAxis { - Q_OBJECT -protected: - QString textForValue(double value); -}; - -class PartialGasPressureAxis : public DiveCartesianAxis { - Q_OBJECT -public: - PartialGasPressureAxis(); - void setModel(DivePlotDataModel *model); -public -slots: - void settingsChanged(); - -private: - DivePlotDataModel *model; -}; - -#endif // DIVECARTESIANAXIS_H diff --git a/desktop-widgets/profile/diveeventitem.cpp b/desktop-widgets/profile/diveeventitem.cpp deleted file mode 100644 index 0bbc84267..000000000 --- a/desktop-widgets/profile/diveeventitem.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include "diveeventitem.h" -#include "diveplotdatamodel.h" -#include "divecartesianaxis.h" -#include "animationfunctions.h" -#include "libdivecomputer.h" -#include "profile.h" -#include "gettextfromc.h" -#include "metrics.h" - -extern struct ev_select *ev_namelist; -extern int evn_used; - -DiveEventItem::DiveEventItem(QObject *parent) : DivePixmapItem(parent), - vAxis(NULL), - hAxis(NULL), - dataModel(NULL), - internalEvent(NULL) -{ - setFlag(ItemIgnoresTransformations); -} - - -void DiveEventItem::setHorizontalAxis(DiveCartesianAxis *axis) -{ - hAxis = axis; - recalculatePos(true); -} - -void DiveEventItem::setModel(DivePlotDataModel *model) -{ - dataModel = model; - recalculatePos(true); -} - -void DiveEventItem::setVerticalAxis(DiveCartesianAxis *axis) -{ - vAxis = axis; - recalculatePos(true); - connect(vAxis, SIGNAL(sizeChanged()), this, SLOT(recalculatePos())); -} - -struct event *DiveEventItem::getEvent() -{ - return internalEvent; -} - -void DiveEventItem::setEvent(struct event *ev) -{ - if (!ev) - return; - internalEvent = ev; - setupPixmap(); - setupToolTipString(); - recalculatePos(true); -} - -void DiveEventItem::setupPixmap() -{ - const IconMetrics& metrics = defaultIconMetrics(); - int sz_bigger = metrics.sz_med + metrics.sz_small; // ex 40px - int sz_pix = sz_bigger/2; // ex 20px - -#define EVENT_PIXMAP(PIX) QPixmap(QString(PIX)).scaled(sz_pix, sz_pix, Qt::KeepAspectRatio, Qt::SmoothTransformation) -#define EVENT_PIXMAP_BIGGER(PIX) QPixmap(QString(PIX)).scaled(sz_bigger, sz_bigger, Qt::KeepAspectRatio, Qt::SmoothTransformation) - if (same_string(internalEvent->name, "")) { - setPixmap(EVENT_PIXMAP(":warning")); - } else if (internalEvent->type == SAMPLE_EVENT_BOOKMARK) { - setPixmap(EVENT_PIXMAP(":flag")); - } else if (strcmp(internalEvent->name, "heading") == 0 || - (same_string(internalEvent->name, "SP change") && internalEvent->time.seconds == 0)) { - // 2 cases: - // a) some dive computers have heading in every sample - // b) at t=0 we might have an "SP change" to indicate dive type - // in both cases we want to get the right data into the tooltip but don't want the visual clutter - // so set an "almost invisible" pixmap (a narrow but somewhat tall, basically transparent pixmap) - // that allows tooltips to work when we don't want to show a specific - // pixmap for an event, but want to show the event value in the tooltip - QPixmap transparentPixmap(4, 20); - transparentPixmap.fill(QColor::fromRgbF(1.0, 1.0, 1.0, 0.01)); - setPixmap(transparentPixmap); - } else if (event_is_gaschange(internalEvent)) { - if (internalEvent->gas.mix.he.permille) - setPixmap(EVENT_PIXMAP_BIGGER(":gaschangeTrimix")); - else if (gasmix_is_air(&internalEvent->gas.mix)) - setPixmap(EVENT_PIXMAP_BIGGER(":gaschangeAir")); - else - setPixmap(EVENT_PIXMAP_BIGGER(":gaschangeNitrox")); - } else { - setPixmap(EVENT_PIXMAP(":warning")); - } -#undef EVENT_PIXMAP -} - -void DiveEventItem::setupToolTipString() -{ - // we display the event on screen - so translate - QString name = gettextFromC::instance()->tr(internalEvent->name); - int value = internalEvent->value; - int type = internalEvent->type; - if (value) { - if (event_is_gaschange(internalEvent)) { - name += ": "; - name += gasname(&internalEvent->gas.mix); - - /* Do we have an explicit cylinder index? Show it. */ - if (internalEvent->gas.index >= 0) - name += QString(" (cyl %1)").arg(internalEvent->gas.index+1); - } else if (type == SAMPLE_EVENT_PO2 && name == "SP change") { - name += QString(":%1").arg((double)value / 1000); - } else { - name += QString(":%1").arg(value); - } - } else if (type == SAMPLE_EVENT_PO2 && name == "SP change") { - // this is a bad idea - we are abusing an existing event type that is supposed to - // warn of high or low pOâ‚‚ and are turning it into a set point change event - name += "\n" + tr("Manual switch to OC"); - } else { - name += internalEvent->flags == SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") : - internalEvent->flags == SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : ""; - } - // qDebug() << name; - setToolTip(name); -} - -void DiveEventItem::eventVisibilityChanged(const QString &eventName, bool visible) -{ -} - -bool DiveEventItem::shouldBeHidden() -{ - struct event *event = internalEvent; - - /* - * Some gas change events are special. Some dive computers just tell us the initial gas this way. - * Don't bother showing those - */ - struct sample *first_sample = &get_dive_dc(&displayed_dive, dc_number)->sample[0]; - if (!strcmp(event->name, "gaschange") && - (event->time.seconds == 0 || - (first_sample && event->time.seconds == first_sample->time.seconds))) - return true; - - for (int i = 0; i < evn_used; i++) { - if (!strcmp(event->name, ev_namelist[i].ev_name) && ev_namelist[i].plot_ev == false) - return true; - } - return false; -} - -void DiveEventItem::recalculatePos(bool instant) -{ - if (!vAxis || !hAxis || !internalEvent || !dataModel) - return; - - QModelIndexList result = dataModel->match(dataModel->index(0, DivePlotDataModel::TIME), Qt::DisplayRole, internalEvent->time.seconds); - if (result.isEmpty()) { - Q_ASSERT("can't find a spot in the dataModel"); - hide(); - return; - } - if (!isVisible() && !shouldBeHidden()) - show(); - int depth = dataModel->data(dataModel->index(result.first().row(), DivePlotDataModel::DEPTH)).toInt(); - qreal x = hAxis->posAtValue(internalEvent->time.seconds); - qreal y = vAxis->posAtValue(depth); - if (!instant) - Animations::moveTo(this, x, y); - else - setPos(x, y); - if (isVisible() && shouldBeHidden()) - hide(); -} diff --git a/desktop-widgets/profile/diveeventitem.h b/desktop-widgets/profile/diveeventitem.h deleted file mode 100644 index f358fee6d..000000000 --- a/desktop-widgets/profile/diveeventitem.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef DIVEEVENTITEM_H -#define DIVEEVENTITEM_H - -#include "divepixmapitem.h" - -class DiveCartesianAxis; -class DivePlotDataModel; -struct event; - -class DiveEventItem : public DivePixmapItem { - Q_OBJECT -public: - DiveEventItem(QObject *parent = 0); - void setEvent(struct event *ev); - struct event *getEvent(); - void eventVisibilityChanged(const QString &eventName, bool visible); - void setVerticalAxis(DiveCartesianAxis *axis); - void setHorizontalAxis(DiveCartesianAxis *axis); - void setModel(DivePlotDataModel *model); - bool shouldBeHidden(); -public -slots: - void recalculatePos(bool instant = false); - -private: - void setupToolTipString(); - void setupPixmap(); - DiveCartesianAxis *vAxis; - DiveCartesianAxis *hAxis; - DivePlotDataModel *dataModel; - struct event *internalEvent; -}; - -#endif // DIVEEVENTITEM_H diff --git a/desktop-widgets/profile/divelineitem.cpp b/desktop-widgets/profile/divelineitem.cpp deleted file mode 100644 index f9e288a44..000000000 --- a/desktop-widgets/profile/divelineitem.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "divelineitem.h" - -DiveLineItem::DiveLineItem(QGraphicsItem *parent) : QGraphicsLineItem(parent) -{ -} diff --git a/desktop-widgets/profile/divelineitem.h b/desktop-widgets/profile/divelineitem.h deleted file mode 100644 index ec88e9da5..000000000 --- a/desktop-widgets/profile/divelineitem.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef DIVELINEITEM_H -#define DIVELINEITEM_H - -#include -#include - -class DiveLineItem : public QObject, public QGraphicsLineItem { - Q_OBJECT - Q_PROPERTY(QPointF pos READ pos WRITE setPos) - Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) -public: - DiveLineItem(QGraphicsItem *parent = 0); -}; - -#endif // DIVELINEITEM_H diff --git a/desktop-widgets/profile/divepixmapitem.cpp b/desktop-widgets/profile/divepixmapitem.cpp deleted file mode 100644 index 581f6f9b4..000000000 --- a/desktop-widgets/profile/divepixmapitem.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "divepixmapitem.h" -#include "animationfunctions.h" -#include "divepicturemodel.h" -#include - -#include -#include -#include - -DivePixmapItem::DivePixmapItem(QObject *parent) : QObject(parent), QGraphicsPixmapItem() -{ -} - -DiveButtonItem::DiveButtonItem(QObject *parent): DivePixmapItem(parent) -{ -} - -void DiveButtonItem::mousePressEvent(QGraphicsSceneMouseEvent *event) -{ - QGraphicsItem::mousePressEvent(event); - emit clicked(); -} - -// If we have many many pictures on screen, maybe a shared-pixmap would be better to -// paint on screen, but for now, this. -CloseButtonItem::CloseButtonItem(QObject *parent): DiveButtonItem(parent) -{ - static QPixmap p = QPixmap(":trash"); - setPixmap(p); - setFlag(ItemIgnoresTransformations); -} - -void CloseButtonItem::hide() -{ - DiveButtonItem::hide(); -} - -void CloseButtonItem::show() -{ - DiveButtonItem::show(); -} - -DivePictureItem::DivePictureItem(QObject *parent): DivePixmapItem(parent), - canvas(new QGraphicsRectItem(this)), - shadow(new QGraphicsRectItem(this)) -{ - setFlag(ItemIgnoresTransformations); - setAcceptHoverEvents(true); - setScale(0.2); - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); - setVisible(prefs.show_pictures_in_profile); - - canvas->setPen(Qt::NoPen); - canvas->setBrush(QColor(Qt::white)); - canvas->setFlag(ItemStacksBehindParent); - canvas->setZValue(-1); - - shadow->setPos(5,5); - shadow->setPen(Qt::NoPen); - shadow->setBrush(QColor(Qt::lightGray)); - shadow->setFlag(ItemStacksBehindParent); - shadow->setZValue(-2); -} - -void DivePictureItem::settingsChanged() -{ - setVisible(prefs.show_pictures_in_profile); -} - -void DivePictureItem::setPixmap(const QPixmap &pix) -{ - DivePixmapItem::setPixmap(pix); - QRectF r = boundingRect(); - canvas->setRect(0 - 10, 0 -10, r.width() + 20, r.height() + 20); - shadow->setRect(canvas->rect()); -} - -CloseButtonItem *button = NULL; -void DivePictureItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) -{ - Animations::scaleTo(this, 1.0); - setZValue(5); - - if(!button) { - button = new CloseButtonItem(); - button->setScale(0.2); - button->setZValue(7); - scene()->addItem(button); - } - button->setParentItem(this); - button->setPos(boundingRect().width() - button->boundingRect().width() * 0.2, - boundingRect().height() - button->boundingRect().height() * 0.2); - button->setOpacity(0); - button->show(); - Animations::show(button); - button->disconnect(); - connect(button, SIGNAL(clicked()), this, SLOT(removePicture())); -} - -void DivePictureItem::setFileUrl(const QString &s) -{ - fileUrl = s; -} - -void DivePictureItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) -{ - Animations::scaleTo(this, 0.2); - setZValue(0); - if(button){ - button->setParentItem(NULL); - Animations::hide(button); - } -} - -DivePictureItem::~DivePictureItem(){ - if(button){ - button->setParentItem(NULL); - Animations::hide(button); - } -} - -void DivePictureItem::mousePressEvent(QGraphicsSceneMouseEvent *event) -{ - QDesktopServices::openUrl(QUrl::fromLocalFile(fileUrl)); -} - -void DivePictureItem::removePicture() -{ - DivePictureModel::instance()->removePicture(fileUrl); -} diff --git a/desktop-widgets/profile/divepixmapitem.h b/desktop-widgets/profile/divepixmapitem.h deleted file mode 100644 index 02c1523f7..000000000 --- a/desktop-widgets/profile/divepixmapitem.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef DIVEPIXMAPITEM_H -#define DIVEPIXMAPITEM_H - -#include -#include - -class DivePixmapItem : public QObject, public QGraphicsPixmapItem { - Q_OBJECT - Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity) - Q_PROPERTY(QPointF pos WRITE setPos READ pos) - Q_PROPERTY(qreal x WRITE setX READ x) - Q_PROPERTY(qreal y WRITE setY READ y) -public: - DivePixmapItem(QObject *parent = 0); -}; - -class DivePictureItem : public DivePixmapItem { - Q_OBJECT - Q_PROPERTY(qreal scale WRITE setScale READ scale) -public: - DivePictureItem(QObject *parent = 0); - virtual ~DivePictureItem(); - void setPixmap(const QPixmap& pix); -public slots: - void settingsChanged(); - void removePicture(); - void setFileUrl(const QString& s); -protected: - void hoverEnterEvent(QGraphicsSceneHoverEvent *event); - void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); - void mousePressEvent(QGraphicsSceneMouseEvent *event); -private: - QString fileUrl; - QGraphicsRectItem *canvas; - QGraphicsRectItem *shadow; -}; - -class DiveButtonItem : public DivePixmapItem { - Q_OBJECT -public: - DiveButtonItem(QObject *parent = 0); -protected: - virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); -signals: - void clicked(); -}; - -class CloseButtonItem : public DiveButtonItem { - Q_OBJECT -public: - CloseButtonItem(QObject *parent = 0); -public slots: - void hide(); - void show(); -}; - -#endif // DIVEPIXMAPITEM_H diff --git a/desktop-widgets/profile/diveprofileitem.cpp b/desktop-widgets/profile/diveprofileitem.cpp deleted file mode 100644 index 2c814678a..000000000 --- a/desktop-widgets/profile/diveprofileitem.cpp +++ /dev/null @@ -1,979 +0,0 @@ -#include "diveprofileitem.h" -#include "diveplotdatamodel.h" -#include "divecartesianaxis.h" -#include "divetextitem.h" -#include "animationfunctions.h" -#include "dive.h" -#include "profile.h" -#include "preferences.h" -#include "diveplannermodel.h" -#include "helpers.h" -#include "libdivecomputer/parser.h" -#include "mainwindow.h" -#include "maintab.h" -#include "profile/profilewidget2.h" -#include "diveplanner.h" - -#include - -AbstractProfilePolygonItem::AbstractProfilePolygonItem() : QObject(), QGraphicsPolygonItem(), hAxis(NULL), vAxis(NULL), dataModel(NULL), hDataColumn(-1), vDataColumn(-1) -{ - setCacheMode(DeviceCoordinateCache); - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); -} - -void AbstractProfilePolygonItem::settingsChanged() -{ -} - -void AbstractProfilePolygonItem::setHorizontalAxis(DiveCartesianAxis *horizontal) -{ - hAxis = horizontal; - connect(hAxis, SIGNAL(sizeChanged()), this, SLOT(modelDataChanged())); - modelDataChanged(); -} - -void AbstractProfilePolygonItem::setHorizontalDataColumn(int column) -{ - hDataColumn = column; - modelDataChanged(); -} - -void AbstractProfilePolygonItem::setModel(DivePlotDataModel *model) -{ - dataModel = model; - connect(dataModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(modelDataChanged(QModelIndex, QModelIndex))); - connect(dataModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(modelDataRemoved(QModelIndex, int, int))); - modelDataChanged(); -} - -void AbstractProfilePolygonItem::modelDataRemoved(const QModelIndex &parent, int from, int to) -{ - setPolygon(QPolygonF()); - qDeleteAll(texts); - texts.clear(); -} - -void AbstractProfilePolygonItem::setVerticalAxis(DiveCartesianAxis *vertical) -{ - vAxis = vertical; - connect(vAxis, SIGNAL(sizeChanged()), this, SLOT(modelDataChanged())); - connect(vAxis, SIGNAL(maxChanged()), this, SLOT(modelDataChanged())); - modelDataChanged(); -} - -void AbstractProfilePolygonItem::setVerticalDataColumn(int column) -{ - vDataColumn = column; - modelDataChanged(); -} - -bool AbstractProfilePolygonItem::shouldCalculateStuff(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - if (!hAxis || !vAxis) - return false; - if (!dataModel || dataModel->rowCount() == 0) - return false; - if (hDataColumn == -1 || vDataColumn == -1) - return false; - if (topLeft.isValid() && bottomRight.isValid()) { - if ((topLeft.column() >= vDataColumn || topLeft.column() >= hDataColumn) && - (bottomRight.column() <= vDataColumn || topLeft.column() <= hDataColumn)) { - return true; - } - } - return true; -} - -void AbstractProfilePolygonItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - // We don't have enougth data to calculate things, quit. - - // Calculate the polygon. This is the polygon that will be painted on screen - // on the ::paint method. Here we calculate the correct position of the points - // regarting our cartesian plane ( made by the hAxis and vAxis ), the QPolygonF - // is an array of QPointF's, so we basically get the point from the model, convert - // to our coordinates, store. no painting is done here. - QPolygonF poly; - for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { - qreal horizontalValue = dataModel->index(i, hDataColumn).data().toReal(); - qreal verticalValue = dataModel->index(i, vDataColumn).data().toReal(); - QPointF point(hAxis->posAtValue(horizontalValue), vAxis->posAtValue(verticalValue)); - poly.append(point); - } - setPolygon(poly); - - qDeleteAll(texts); - texts.clear(); -} - -DiveProfileItem::DiveProfileItem() : show_reported_ceiling(0), reported_ceiling_in_red(0) -{ -} - -void DiveProfileItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - Q_UNUSED(widget); - if (polygon().isEmpty()) - return; - - painter->save(); - // This paints the Polygon + Background. I'm setting the pen to QPen() so we don't get a black line here, - // after all we need to plot the correct velocities colors later. - setPen(Qt::NoPen); - QGraphicsPolygonItem::paint(painter, option, widget); - - // Here we actually paint the boundaries of the Polygon using the colors that the model provides. - // Those are the speed colors of the dives. - QPen pen; - pen.setCosmetic(true); - pen.setWidth(2); - QPolygonF poly = polygon(); - // This paints the colors of the velocities. - for (int i = 1, count = dataModel->rowCount(); i < count; i++) { - QModelIndex colorIndex = dataModel->index(i, DivePlotDataModel::COLOR); - pen.setBrush(QBrush(colorIndex.data(Qt::BackgroundRole).value())); - painter->setPen(pen); - painter->drawLine(poly[i - 1], poly[i]); - } - painter->restore(); -} - -int DiveProfileItem::maxCeiling(int row) -{ - int max = -1; - plot_data *entry = dataModel->data().entry + row; - for (int tissue = 0; tissue < 16; tissue++) { - if (max < entry->ceilings[tissue]) - max = entry->ceilings[tissue]; - } - return max; -} - -void DiveProfileItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - bool eventAdded = false; - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - - AbstractProfilePolygonItem::modelDataChanged(topLeft, bottomRight); - if (polygon().isEmpty()) - return; - - show_reported_ceiling = prefs.dcceiling; - reported_ceiling_in_red = prefs.redceiling; - profileColor = getColor(DEPTH_BOTTOM); - - int currState = qobject_cast(scene()->views().first())->currentState; - if (currState == ProfileWidget2::PLAN) { - plot_data *entry = dataModel->data().entry; - for (int i = 0; i < dataModel->rowCount(); i++, entry++) { - int max = maxCeiling(i); - // Don't scream if we violate the ceiling by a few cm - if (entry->depth < max - 100 && entry->sec > 0) { - profileColor = QColor(Qt::red); - if (!eventAdded) { - add_event(&displayed_dive.dc, entry->sec, SAMPLE_EVENT_CEILING, -1, max / 1000, "planned waypoint above ceiling"); - eventAdded = true; - } - } - } - } - - /* Show any ceiling we may have encountered */ - if (prefs.dcceiling && !prefs.redceiling) { - QPolygonF p = polygon(); - plot_data *entry = dataModel->data().entry + dataModel->rowCount() - 1; - for (int i = dataModel->rowCount() - 1; i >= 0; i--, entry--) { - if (!entry->in_deco) { - /* not in deco implies this is a safety stop, no ceiling */ - p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(0))); - } else { - p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(qMin(entry->stopdepth, entry->depth)))); - } - } - setPolygon(p); - } - - // This is the blueish gradient that the Depth Profile should have. - // It's a simple QLinearGradient with 2 stops, starting from top to bottom. - QLinearGradient pat(0, polygon().boundingRect().top(), 0, polygon().boundingRect().bottom()); - pat.setColorAt(1, profileColor); - pat.setColorAt(0, getColor(DEPTH_TOP)); - setBrush(QBrush(pat)); - - int last = -1; - for (int i = 0, count = dataModel->rowCount(); i < count; i++) { - - struct plot_data *entry = dataModel->data().entry + i; - if (entry->depth < 2000) - continue; - - if ((entry == entry->max[2]) && entry->depth / 100 != last) { - plot_depth_sample(entry, Qt::AlignHCenter | Qt::AlignBottom, getColor(SAMPLE_DEEP)); - last = entry->depth / 100; - } - - if ((entry == entry->min[2]) && entry->depth / 100 != last) { - plot_depth_sample(entry, Qt::AlignHCenter | Qt::AlignTop, getColor(SAMPLE_SHALLOW)); - last = entry->depth / 100; - } - - if (entry->depth != last) - last = -1; - } -} - -void DiveProfileItem::settingsChanged() -{ - //TODO: Only modelDataChanged() here if we need to rebuild the graph ( for instance, - // if the prefs.dcceiling are enabled, but prefs.redceiling is disabled - // and only if it changed something. let's not waste cpu cycles repoloting something we don't need to. - modelDataChanged(); -} - -void DiveProfileItem::plot_depth_sample(struct plot_data *entry, QFlags flags, const QColor &color) -{ - int decimals; - double d = get_depth_units(entry->depth, &decimals, NULL); - DiveTextItem *item = new DiveTextItem(this); - item->setPos(hAxis->posAtValue(entry->sec), vAxis->posAtValue(entry->depth)); - item->setText(QString("%1").arg(d, 0, 'f', 1)); - item->setAlignment(flags); - item->setBrush(color); - texts.append(item); -} - -DiveHeartrateItem::DiveHeartrateItem() -{ - QPen pen; - pen.setBrush(QBrush(getColor(::HR_PLOT))); - pen.setCosmetic(true); - pen.setWidth(1); - setPen(pen); - settingsChanged(); -} - -void DiveHeartrateItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - int last = -300, last_printed_hr = 0, sec = 0; - struct { - int sec; - int hr; - } hist[3] = {}; - - // We don't have enougth data to calculate things, quit. - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - - qDeleteAll(texts); - texts.clear(); - // Ignore empty values. a heartrate of 0 would be a bad sign. - QPolygonF poly; - for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { - int hr = dataModel->index(i, vDataColumn).data().toInt(); - if (!hr) - continue; - sec = dataModel->index(i, hDataColumn).data().toInt(); - QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr)); - poly.append(point); - if (hr == hist[2].hr) - // same as last one, no point in looking at printing - continue; - hist[0] = hist[1]; - hist[1] = hist[2]; - hist[2].sec = sec; - hist[2].hr = hr; - // don't print a HR - // if it's not a local min / max - // if it's been less than 5min and less than a 20 beats change OR - // if it's been less than 2min OR if the change from the - // last print is less than 10 beats - // to test min / max requires three points, so we now look at the - // previous one - sec = hist[1].sec; - hr = hist[1].hr; - if ((hist[0].hr < hr && hr < hist[2].hr) || - (hist[0].hr > hr && hr > hist[2].hr) || - ((sec < last + 300) && (abs(hr - last_printed_hr) < 20)) || - (sec < last + 120) || - (abs(hr - last_printed_hr) < 10)) - continue; - last = sec; - createTextItem(sec, hr); - last_printed_hr = hr; - } - setPolygon(poly); - - if (texts.count()) - texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom); -} - -void DiveHeartrateItem::createTextItem(int sec, int hr) -{ - DiveTextItem *text = new DiveTextItem(this); - text->setAlignment(Qt::AlignRight | Qt::AlignBottom); - text->setBrush(getColor(HR_TEXT)); - text->setPos(QPointF(hAxis->posAtValue(sec), vAxis->posAtValue(hr))); - text->setScale(0.7); // need to call this BEFORE setText() - text->setText(QString("%1").arg(hr)); - texts.append(text); -} - -void DiveHeartrateItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (polygon().isEmpty()) - return; - painter->save(); - painter->setPen(pen()); - painter->drawPolyline(polygon()); - painter->restore(); -} - -void DiveHeartrateItem::settingsChanged() -{ - setVisible(prefs.hrgraph); -} - -DivePercentageItem::DivePercentageItem(int i) -{ - QPen pen; - QColor color; - color.setHsl(100 + 10 * i, 200, 100); - pen.setBrush(QBrush(color)); - pen.setCosmetic(true); - pen.setWidth(1); - setPen(pen); - settingsChanged(); -} - -void DivePercentageItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - int sec = 0; - - // We don't have enougth data to calculate things, quit. - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - - // Ignore empty values. a heartrate of 0 would be a bad sign. - QPolygonF poly; - for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { - int hr = dataModel->index(i, vDataColumn).data().toInt(); - if (!hr) - continue; - sec = dataModel->index(i, hDataColumn).data().toInt(); - QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr)); - poly.append(point); - } - setPolygon(poly); - - if (texts.count()) - texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom); -} - -void DivePercentageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (polygon().isEmpty()) - return; - painter->save(); - painter->setPen(pen()); - painter->drawPolyline(polygon()); - painter->restore(); -} - -void DivePercentageItem::settingsChanged() -{ - setVisible(prefs.percentagegraph); -} - -DiveAmbPressureItem::DiveAmbPressureItem() -{ - QPen pen; - pen.setBrush(QBrush(getColor(::AMB_PRESSURE_LINE))); - pen.setCosmetic(true); - pen.setWidth(2); - setPen(pen); - settingsChanged(); -} - -void DiveAmbPressureItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - int sec = 0; - - // We don't have enougth data to calculate things, quit. - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - - // Ignore empty values. a heartrate of 0 would be a bad sign. - QPolygonF poly; - for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { - int hr = dataModel->index(i, vDataColumn).data().toInt(); - if (!hr) - continue; - sec = dataModel->index(i, hDataColumn).data().toInt(); - QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr)); - poly.append(point); - } - setPolygon(poly); - - if (texts.count()) - texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom); -} - -void DiveAmbPressureItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (polygon().isEmpty()) - return; - painter->save(); - painter->setPen(pen()); - painter->drawPolyline(polygon()); - painter->restore(); -} - -void DiveAmbPressureItem::settingsChanged() -{ - setVisible(prefs.percentagegraph); -} - -DiveGFLineItem::DiveGFLineItem() -{ - QPen pen; - pen.setBrush(QBrush(getColor(::GF_LINE))); - pen.setCosmetic(true); - pen.setWidth(2); - setPen(pen); - settingsChanged(); -} - -void DiveGFLineItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - int sec = 0; - - // We don't have enougth data to calculate things, quit. - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - - // Ignore empty values. a heartrate of 0 would be a bad sign. - QPolygonF poly; - for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { - int hr = dataModel->index(i, vDataColumn).data().toInt(); - if (!hr) - continue; - sec = dataModel->index(i, hDataColumn).data().toInt(); - QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr)); - poly.append(point); - } - setPolygon(poly); - - if (texts.count()) - texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom); -} - -void DiveGFLineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (polygon().isEmpty()) - return; - painter->save(); - painter->setPen(pen()); - painter->drawPolyline(polygon()); - painter->restore(); -} - -void DiveGFLineItem::settingsChanged() -{ - setVisible(prefs.percentagegraph); -} - -DiveTemperatureItem::DiveTemperatureItem() -{ - QPen pen; - pen.setBrush(QBrush(getColor(::TEMP_PLOT))); - pen.setCosmetic(true); - pen.setWidth(2); - setPen(pen); -} - -void DiveTemperatureItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - int last = -300, last_printed_temp = 0, sec = 0, last_valid_temp = 0; - // We don't have enougth data to calculate things, quit. - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - - qDeleteAll(texts); - texts.clear(); - // Ignore empty values. things do not look good with '0' as temperature in kelvin... - QPolygonF poly; - for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { - int mkelvin = dataModel->index(i, vDataColumn).data().toInt(); - if (!mkelvin) - continue; - last_valid_temp = mkelvin; - sec = dataModel->index(i, hDataColumn).data().toInt(); - QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(mkelvin)); - poly.append(point); - - /* 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) - createTextItem(sec, mkelvin); - last_printed_temp = mkelvin; - } - setPolygon(poly); - - /* 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_valid_temp > 200000 && - ((abs(last_valid_temp - last_printed_temp) > 500) || ((double)last / (double)sec < 0.75))) { - createTextItem(sec, last_valid_temp); - } - if (texts.count()) - texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom); -} - -void DiveTemperatureItem::createTextItem(int sec, int mkelvin) -{ - double deg; - const char *unit; - deg = get_temp_units(mkelvin, &unit); - - DiveTextItem *text = new DiveTextItem(this); - text->setAlignment(Qt::AlignRight | Qt::AlignBottom); - text->setBrush(getColor(TEMP_TEXT)); - text->setPos(QPointF(hAxis->posAtValue(sec), vAxis->posAtValue(mkelvin))); - text->setScale(0.8); // need to call this BEFORE setText() - text->setText(QString("%1%2").arg(deg, 0, 'f', 1).arg(unit)); - texts.append(text); -} - -void DiveTemperatureItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (polygon().isEmpty()) - return; - painter->save(); - painter->setPen(pen()); - painter->drawPolyline(polygon()); - painter->restore(); -} - -DiveMeanDepthItem::DiveMeanDepthItem() -{ - QPen pen; - pen.setBrush(QBrush(getColor(::HR_AXIS))); - pen.setCosmetic(true); - pen.setWidth(2); - setPen(pen); - settingsChanged(); -} - -void DiveMeanDepthItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - double meandepthvalue = 0.0; - // We don't have enougth data to calculate things, quit. - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - - QPolygonF poly; - plot_data *entry = dataModel->data().entry; - for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++, entry++) { - // Ignore empty values - if (entry->running_sum == 0 || entry->sec == 0) - continue; - - meandepthvalue = entry->running_sum / entry->sec; - QPointF point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(meandepthvalue)); - poly.append(point); - } - lastRunningSum = meandepthvalue; - setPolygon(poly); - createTextItem(); -} - - -void DiveMeanDepthItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (polygon().isEmpty()) - return; - painter->save(); - painter->setPen(pen()); - painter->drawPolyline(polygon()); - painter->restore(); -} - -void DiveMeanDepthItem::settingsChanged() -{ - setVisible(prefs.show_average_depth); -} - -void DiveMeanDepthItem::createTextItem() { - plot_data *entry = dataModel->data().entry; - int sec = entry[dataModel->rowCount()-1].sec; - qDeleteAll(texts); - texts.clear(); - int decimals; - const char *unitText; - double d = get_depth_units(lastRunningSum, &decimals, &unitText); - DiveTextItem *text = new DiveTextItem(this); - text->setAlignment(Qt::AlignRight | Qt::AlignTop); - text->setBrush(getColor(TEMP_TEXT)); - text->setPos(QPointF(hAxis->posAtValue(sec) + 1, vAxis->posAtValue(lastRunningSum))); - text->setScale(0.8); // need to call this BEFORE setText() - text->setText(QString("%1%2").arg(d, 0, 'f', 1).arg(unitText)); - texts.append(text); -} - -void DiveGasPressureItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - // We don't have enougth data to calculate things, quit. - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - int last_index = -1; - int o2mbar; - QPolygonF boundingPoly, o2Poly; // This is the "Whole Item", but a pressure can be divided in N Polygons. - polygons.clear(); - if (displayed_dive.dc.divemode == CCR) - polygons.append(o2Poly); - - for (int i = 0, count = dataModel->rowCount(); i < count; i++) { - o2mbar = 0; - plot_data *entry = dataModel->data().entry + i; - int mbar = GET_PRESSURE(entry); - if (displayed_dive.dc.divemode == CCR) - o2mbar = GET_O2CYLINDER_PRESSURE(entry); - - if (entry->cylinderindex != last_index) { - polygons.append(QPolygonF()); // this is the polygon that will be actually drawn on screen. - last_index = entry->cylinderindex; - } - if (!mbar) { - continue; - } - if (o2mbar) { - QPointF o2point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(o2mbar)); - boundingPoly.push_back(o2point); - polygons.first().push_back(o2point); - } - - QPointF point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(mbar)); - boundingPoly.push_back(point); // The BoundingRect - polygons.last().push_back(point); // The polygon thta will be plotted. - } - setPolygon(boundingPoly); - qDeleteAll(texts); - texts.clear(); - 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; - - cyl = -1; - o2mbar = 0; - - double print_y_offset[8][2] = { { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 } ,{ 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 } }; - // CCR dives: These are offset values used to print the gas lables and pressures on a CCR dive profile at - // appropriate Y-coordinates: One doublet of values for each of 8 cylinders. - // Order of offsets within a doublet: gas lable offset; gas pressure offset. - // The array is initialised with default values that apply to non-CCR dives. - - bool offsets_initialised = false; - int o2cyl = -1, dilcyl = -1; - QFlags alignVar= Qt::AlignTop, align_dil = Qt::AlignBottom, align_o2 = Qt::AlignTop; - double axisRange = (vAxis->maximum() - vAxis->minimum())/1000; // Convert axis pressure range to bar - double axisLog = log10(log10(axisRange)); - for (int i = 0, count = dataModel->rowCount(); i < count; i++) { - entry = dataModel->data().entry + i; - mbar = GET_PRESSURE(entry); - if (displayed_dive.dc.divemode == CCR && displayed_dive.oxygen_cylinder_index >= 0) - o2mbar = GET_O2CYLINDER_PRESSURE(entry); - - if (o2mbar) { // If there is an o2mbar value then this is a CCR dive. Then do: - // The first time an o2 value is detected, see if the oxygen cyl pressure graph starts above or below the dil graph - if (!offsets_initialised) { // Initialise the parameters for placing the text correctly near the graph line: - o2cyl = displayed_dive.oxygen_cylinder_index; - dilcyl = displayed_dive.diluent_cylinder_index; - if ((o2mbar > mbar)) { // If above, write o2 start cyl pressure above graph and diluent pressure below graph: - print_y_offset[o2cyl][0] = -7 * axisLog; // y offset for oxygen gas lable (above); pressure offsets=-0.5, already initialised - print_y_offset[dilcyl][0] = 5 * axisLog; // y offset for diluent gas lable (below) - } else { // ... else write o2 start cyl pressure below graph: - print_y_offset[o2cyl][0] = 5 * axisLog; // o2 lable & pressure below graph; pressure offsets=-0.5, already initialised - print_y_offset[dilcyl][0] = -7.8 * axisLog; // and diluent lable above graph. - align_dil = Qt::AlignTop; - align_o2 = Qt::AlignBottom; - } - offsets_initialised = true; - } - - if (!seen_cyl[displayed_dive.oxygen_cylinder_index]) { //For o2, on the left of profile, write lable and pressure - plotPressureValue(o2mbar, entry->sec, align_o2, print_y_offset[o2cyl][1]); - plotGasValue(o2mbar, entry->sec, displayed_dive.cylinder[displayed_dive.oxygen_cylinder_index].gasmix, align_o2, print_y_offset[o2cyl][0]); - seen_cyl[displayed_dive.oxygen_cylinder_index] = true; - } - last_pressure[displayed_dive.oxygen_cylinder_index] = o2mbar; - last_time[displayed_dive.oxygen_cylinder_index] = entry->sec; - alignVar = align_dil; - } - - if (!mbar) - continue; - - if (cyl != entry->cylinderindex) { // Pressure value near the left hand edge of the profile - other cylinders: - cyl = entry->cylinderindex; // For each other cylinder, write the gas lable and pressure - if (!seen_cyl[cyl]) { - plotPressureValue(mbar, entry->sec, alignVar, print_y_offset[cyl][1]); - plotGasValue(mbar, entry->sec, displayed_dive.cylinder[cyl].gasmix, align_dil, print_y_offset[cyl][0]); - seen_cyl[cyl] = true; - } - } - last_pressure[cyl] = mbar; - last_time[cyl] = entry->sec; - } - - for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { // For each cylinder, on right hand side of profile, write cylinder pressure - alignVar = ((o2cyl >= 0) && (cyl == displayed_dive.oxygen_cylinder_index)) ? align_o2 : align_dil; - if (last_time[cyl]) { - plotPressureValue(last_pressure[cyl], last_time[cyl], (alignVar | Qt::AlignLeft), print_y_offset[cyl][1]); - } - } -} - -void DiveGasPressureItem::plotPressureValue(int mbar, int sec, QFlags align, double pressure_offset) -{ - const char *unit; - int pressure = get_pressure_units(mbar, &unit); - DiveTextItem *text = new DiveTextItem(this); - text->setPos(hAxis->posAtValue(sec), vAxis->posAtValue(mbar) + pressure_offset ); - text->setText(QString("%1 %2").arg(pressure).arg(unit)); - text->setAlignment(align); - text->setBrush(getColor(PRESSURE_TEXT)); - texts.push_back(text); -} - -void DiveGasPressureItem::plotGasValue(int mbar, int sec, struct gasmix gasmix, QFlags align, double gasname_offset) -{ - QString gas = get_gas_string(gasmix); - DiveTextItem *text = new DiveTextItem(this); - text->setPos(hAxis->posAtValue(sec), vAxis->posAtValue(mbar) + gasname_offset ); - text->setText(gas); - text->setAlignment(align); - text->setBrush(getColor(PRESSURE_TEXT)); - texts.push_back(text); -} - -void DiveGasPressureItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (polygon().isEmpty()) - return; - QPen pen; - pen.setCosmetic(true); - pen.setWidth(2); - painter->save(); - struct plot_data *entry; - Q_FOREACH (const QPolygonF &poly, polygons) { - entry = dataModel->data().entry; - for (int i = 1, count = poly.count(); i < count; i++, entry++) { - if (entry->sac) - pen.setBrush(getSacColor(entry->sac, displayed_dive.sac)); - else - pen.setBrush(MED_GRAY_HIGH_TRANS); - painter->setPen(pen); - painter->drawLine(poly[i - 1], poly[i]); - } - } - painter->restore(); -} - -DiveCalculatedCeiling::DiveCalculatedCeiling() : is3mIncrement(false) -{ - settingsChanged(); -} - -void DiveCalculatedCeiling::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - if (MainWindow::instance()->information()) - connect(MainWindow::instance()->information(), SIGNAL(dateTimeChanged()), this, SLOT(recalc()), Qt::UniqueConnection); - - // We don't have enougth data to calculate things, quit. - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - AbstractProfilePolygonItem::modelDataChanged(topLeft, bottomRight); - // Add 2 points to close the polygon. - QPolygonF poly = polygon(); - if (poly.isEmpty()) - return; - QPointF p1 = poly.first(); - QPointF p2 = poly.last(); - - poly.prepend(QPointF(p1.x(), vAxis->posAtValue(0))); - poly.append(QPointF(p2.x(), vAxis->posAtValue(0))); - setPolygon(poly); - - QLinearGradient pat(0, polygon().boundingRect().top(), 0, polygon().boundingRect().bottom()); - pat.setColorAt(0, getColor(CALC_CEILING_SHALLOW)); - pat.setColorAt(1, getColor(CALC_CEILING_DEEP)); - setPen(QPen(QBrush(Qt::NoBrush), 0)); - setBrush(pat); -} - -void DiveCalculatedCeiling::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (polygon().isEmpty()) - return; - QGraphicsPolygonItem::paint(painter, option, widget); -} - -DiveCalculatedTissue::DiveCalculatedTissue() -{ - settingsChanged(); -} - -void DiveCalculatedTissue::settingsChanged() -{ - setVisible(prefs.calcalltissues && prefs.calcceiling); -} - -void DiveReportedCeiling::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - - QPolygonF p; - p.append(QPointF(hAxis->posAtValue(0), vAxis->posAtValue(0))); - plot_data *entry = dataModel->data().entry; - for (int i = 0, count = dataModel->rowCount(); i < count; i++, entry++) { - if (entry->in_deco && entry->stopdepth) { - p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(qMin(entry->stopdepth, entry->depth)))); - } else { - p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(0))); - } - } - setPolygon(p); - QLinearGradient pat(0, p.boundingRect().top(), 0, p.boundingRect().bottom()); - // does the user want the ceiling in "surface color" or in red? - if (prefs.redceiling) { - pat.setColorAt(0, getColor(CEILING_SHALLOW)); - pat.setColorAt(1, getColor(CEILING_DEEP)); - } else { - pat.setColorAt(0, getColor(BACKGROUND_TRANS)); - pat.setColorAt(1, getColor(BACKGROUND_TRANS)); - } - setPen(QPen(QBrush(Qt::NoBrush), 0)); - setBrush(pat); -} - -void DiveCalculatedCeiling::recalc() -{ - dataModel->calculateDecompression(); -} - -void DiveCalculatedCeiling::settingsChanged() -{ - if (dataModel && is3mIncrement != prefs.calcceiling3m) { - // recalculate that part. - recalc(); - } - is3mIncrement = prefs.calcceiling3m; - setVisible(prefs.calcceiling); -} - -void DiveReportedCeiling::settingsChanged() -{ - setVisible(prefs.dcceiling); -} - -void DiveReportedCeiling::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (polygon().isEmpty()) - return; - QGraphicsPolygonItem::paint(painter, option, widget); -} - -void PartialPressureGasItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - //AbstractProfilePolygonItem::modelDataChanged(); - if (!shouldCalculateStuff(topLeft, bottomRight)) - return; - - plot_data *entry = dataModel->data().entry; - QPolygonF poly; - QPolygonF alertpoly; - alertPolygons.clear(); - QSettings s; - s.beginGroup("TecDetails"); - double threshold = 0.0; - if (thresholdPtr) - threshold = *thresholdPtr; - bool inAlertFragment = false; - for (int i = 0; i < dataModel->rowCount(); i++, entry++) { - double value = dataModel->index(i, vDataColumn).data().toDouble(); - int time = dataModel->index(i, hDataColumn).data().toInt(); - QPointF point(hAxis->posAtValue(time), vAxis->posAtValue(value)); - poly.push_back(point); - if (value >= threshold) { - if (inAlertFragment) { - alertPolygons.back().push_back(point); - } else { - alertpoly.clear(); - alertpoly.push_back(point); - alertPolygons.append(alertpoly); - inAlertFragment = true; - } - } else { - inAlertFragment = false; - } - } - setPolygon(poly); - /* - createPPLegend(trUtf8("pN" UTF8_SUBSCRIPT_2),getColor(PN2), legendPos); - */ -} - -void PartialPressureGasItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - const qreal pWidth = 0.0; - painter->save(); - painter->setPen(QPen(normalColor, pWidth)); - painter->drawPolyline(polygon()); - - QPolygonF poly; - painter->setPen(QPen(alertColor, pWidth)); - Q_FOREACH (const QPolygonF &poly, alertPolygons) - painter->drawPolyline(poly); - painter->restore(); -} - -void PartialPressureGasItem::setThreshouldSettingsKey(double *prefPointer) -{ - thresholdPtr = prefPointer; -} - -PartialPressureGasItem::PartialPressureGasItem() : - thresholdPtr(NULL) -{ -} - -void PartialPressureGasItem::settingsChanged() -{ - QSettings s; - s.beginGroup("TecDetails"); - setVisible(s.value(visibilityKey).toBool()); -} - -void PartialPressureGasItem::setVisibilitySettingsKey(const QString &key) -{ - visibilityKey = key; -} - -void PartialPressureGasItem::setColors(const QColor &normal, const QColor &alert) -{ - normalColor = normal; - alertColor = alert; -} diff --git a/desktop-widgets/profile/diveprofileitem.h b/desktop-widgets/profile/diveprofileitem.h deleted file mode 100644 index 0bba7f7a3..000000000 --- a/desktop-widgets/profile/diveprofileitem.h +++ /dev/null @@ -1,225 +0,0 @@ -#ifndef DIVEPROFILEITEM_H -#define DIVEPROFILEITEM_H - -#include -#include -#include - -#include "divelineitem.h" - -/* This is the Profile Item, it should be used for quite a lot of things - on the profile view. The usage should be pretty simple: - - DiveProfileItem *profile = new DiveProfileItem(); - profile->setVerticalAxis( profileYAxis ); - profile->setHorizontalAxis( timeAxis ); - profile->setModel( DiveDataModel ); - profile->setHorizontalDataColumn( DiveDataModel::TIME ); - profile->setVerticalDataColumn( DiveDataModel::DEPTH ); - scene()->addItem(profile); - - This is a generically item and should be used as a base for others, I think... -*/ - -class DivePlotDataModel; -class DiveTextItem; -class DiveCartesianAxis; -class QAbstractTableModel; -struct plot_data; - -class AbstractProfilePolygonItem : public QObject, public QGraphicsPolygonItem { - Q_OBJECT - Q_PROPERTY(QPointF pos WRITE setPos READ pos) - Q_PROPERTY(qreal x WRITE setX READ x) - Q_PROPERTY(qreal y WRITE setY READ y) -public: - AbstractProfilePolygonItem(); - void setVerticalAxis(DiveCartesianAxis *vertical); - void setHorizontalAxis(DiveCartesianAxis *horizontal); - void setModel(DivePlotDataModel *model); - void setHorizontalDataColumn(int column); - void setVerticalDataColumn(int column); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) = 0; - virtual void clear() - { - } -public -slots: - virtual void settingsChanged(); - virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); - virtual void modelDataRemoved(const QModelIndex &parent, int from, int to); - -protected: - /* when the model emits a 'datachanged' signal, this method below should be used to check if the - * modified data affects this particular item ( for example, when setting the '3m increment' - * the data for Ceiling and tissues will be changed, and only those. so, the topLeft will be the CEILING - * column and the bottomRight will have the TISSUE_16 column. this method takes the vDataColumn and hDataColumn - * into consideration when returning 'true' for "yes, continue the calculation', and 'false' for - * 'do not recalculate, we already have the right data. - */ - bool shouldCalculateStuff(const QModelIndex &topLeft, const QModelIndex &bottomRight); - - DiveCartesianAxis *hAxis; - DiveCartesianAxis *vAxis; - DivePlotDataModel *dataModel; - int hDataColumn; - int vDataColumn; - QList texts; -}; - -class DiveProfileItem : public AbstractProfilePolygonItem { - Q_OBJECT - -public: - DiveProfileItem(); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); - virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); - virtual void settingsChanged(); - void plot_depth_sample(struct plot_data *entry, QFlags flags, const QColor &color); - int maxCeiling(int row); - -private: - unsigned int show_reported_ceiling; - unsigned int reported_ceiling_in_red; - QColor profileColor; -}; - -class DiveMeanDepthItem : public AbstractProfilePolygonItem { - Q_OBJECT -public: - DiveMeanDepthItem(); - virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); - virtual void settingsChanged(); - -private: - void createTextItem(); - double lastRunningSum; - QString visibilityKey; -}; - -class DiveTemperatureItem : public AbstractProfilePolygonItem { - Q_OBJECT -public: - DiveTemperatureItem(); - virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); - -private: - void createTextItem(int seconds, int mkelvin); -}; - -class DiveHeartrateItem : public AbstractProfilePolygonItem { - Q_OBJECT -public: - DiveHeartrateItem(); - virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - virtual void settingsChanged(); - -private: - void createTextItem(int seconds, int hr); - QString visibilityKey; -}; - -class DivePercentageItem : public AbstractProfilePolygonItem { - Q_OBJECT -public: - DivePercentageItem(int i); - virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - virtual void settingsChanged(); - -private: - QString visibilityKey; -}; - -class DiveAmbPressureItem : public AbstractProfilePolygonItem { - Q_OBJECT -public: - DiveAmbPressureItem(); - virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - virtual void settingsChanged(); - -private: - QString visibilityKey; -}; - -class DiveGFLineItem : public AbstractProfilePolygonItem { - Q_OBJECT -public: - DiveGFLineItem(); - virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - virtual void settingsChanged(); - -private: - QString visibilityKey; -}; - -class DiveGasPressureItem : public AbstractProfilePolygonItem { - Q_OBJECT - -public: - virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); - -private: - void plotPressureValue(int mbar, int sec, QFlags align, double offset); - void plotGasValue(int mbar, int sec, struct gasmix gasmix, QFlags align, double offset); - QVector polygons; -}; - -class DiveCalculatedCeiling : public AbstractProfilePolygonItem { - Q_OBJECT - -public: - DiveCalculatedCeiling(); - virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); - virtual void settingsChanged(); - -public -slots: - void recalc(); - -private: - bool is3mIncrement; -}; - -class DiveReportedCeiling : public AbstractProfilePolygonItem { - Q_OBJECT - -public: - virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); - virtual void settingsChanged(); -}; - -class DiveCalculatedTissue : public DiveCalculatedCeiling { - Q_OBJECT -public: - DiveCalculatedTissue(); - virtual void settingsChanged(); -}; - -class PartialPressureGasItem : public AbstractProfilePolygonItem { - Q_OBJECT -public: - PartialPressureGasItem(); - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); - virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); - virtual void settingsChanged(); - void setThreshouldSettingsKey(double *prefPointer); - void setVisibilitySettingsKey(const QString &setVisibilitySettingsKey); - void setColors(const QColor &normalColor, const QColor &alertColor); - -private: - QVector alertPolygons; - double *thresholdPtr; - QString visibilityKey; - QColor normalColor; - QColor alertColor; -}; -#endif // DIVEPROFILEITEM_H diff --git a/desktop-widgets/profile/diverectitem.cpp b/desktop-widgets/profile/diverectitem.cpp deleted file mode 100644 index 8cb60c3f5..000000000 --- a/desktop-widgets/profile/diverectitem.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "diverectitem.h" - -DiveRectItem::DiveRectItem(QObject *parent, QGraphicsItem *parentItem) : QObject(parent), QGraphicsRectItem(parentItem) -{ -} diff --git a/desktop-widgets/profile/diverectitem.h b/desktop-widgets/profile/diverectitem.h deleted file mode 100644 index e616cf591..000000000 --- a/desktop-widgets/profile/diverectitem.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef DIVERECTITEM_H -#define DIVERECTITEM_H - -#include -#include - -class DiveRectItem : public QObject, public QGraphicsRectItem { - Q_OBJECT - Q_PROPERTY(QRectF rect WRITE setRect READ rect) - Q_PROPERTY(QPointF pos WRITE setPos READ pos) - Q_PROPERTY(qreal x WRITE setX READ x) - Q_PROPERTY(qreal y WRITE setY READ y) -public: - DiveRectItem(QObject *parent = 0, QGraphicsItem *parentItem = 0); -}; - -#endif // DIVERECTITEM_H diff --git a/desktop-widgets/profile/divetextitem.cpp b/desktop-widgets/profile/divetextitem.cpp deleted file mode 100644 index 3bf00d68f..000000000 --- a/desktop-widgets/profile/divetextitem.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "divetextitem.h" -#include "mainwindow.h" -#include "profilewidget2.h" -#include "subsurface-core/color.h" - -#include - -DiveTextItem::DiveTextItem(QGraphicsItem *parent) : QGraphicsItemGroup(parent), - internalAlignFlags(Qt::AlignHCenter | Qt::AlignVCenter), - textBackgroundItem(new QGraphicsPathItem(this)), - textItem(new QGraphicsPathItem(this)), - printScale(1.0), - scale(1.0), - connected(false) -{ - setFlag(ItemIgnoresTransformations); - textBackgroundItem->setBrush(QBrush(getColor(TEXT_BACKGROUND))); - textBackgroundItem->setPen(Qt::NoPen); - textItem->setPen(Qt::NoPen); -} - -void DiveTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - updateText(); - QGraphicsItemGroup::paint(painter, option, widget); -} - -void DiveTextItem::fontPrintScaleUpdate(double scale) -{ - printScale = scale; -} - -void DiveTextItem::setAlignment(int alignFlags) -{ - if (alignFlags != internalAlignFlags) { - internalAlignFlags = alignFlags; - } -} - -void DiveTextItem::setBrush(const QBrush &b) -{ - textItem->setBrush(b); -} - -void DiveTextItem::setScale(double newscale) -{ - if (scale != newscale) { - scale = newscale; - } -} - -void DiveTextItem::setText(const QString &t) -{ - if (internalText != t) { - if (!connected) { - if (scene()) { - // by now we should be on a scene. grab the profile widget from it and setup our printScale - // and connect to the signal that makes sure we keep track if that changes - ProfileWidget2 *profile = qobject_cast(scene()->views().first()); - connect(profile, SIGNAL(fontPrintScaleChanged(double)), this, SLOT(fontPrintScaleUpdate(double)), Qt::UniqueConnection); - fontPrintScaleUpdate(profile->getFontPrintScale()); - connected = true; - } else { - qDebug() << "called before scene was set up" << t; - } - } - internalText = t; - updateText(); - } -} - -const QString &DiveTextItem::text() -{ - return internalText; -} - -void DiveTextItem::updateText() -{ - double size; - if (internalText.isEmpty()) { - return; - } - - QFont fnt(qApp->font()); - if ((size = fnt.pixelSize()) > 0) { - // set in pixels - so the scale factor may not make a difference if it's too close to 1 - size *= scale * printScale; - fnt.setPixelSize(size); - } else { - size = fnt.pointSizeF(); - size *= scale * printScale; - fnt.setPointSizeF(size); - } - QFontMetrics fm(fnt); - - QPainterPath textPath; - qreal xPos = 0, yPos = 0; - - QRectF rect = fm.boundingRect(internalText); - yPos = (internalAlignFlags & Qt::AlignTop) ? 0 : - (internalAlignFlags & Qt::AlignBottom) ? +rect.height() : - /*(internalAlignFlags & Qt::AlignVCenter ? */ +rect.height() / 4; - - xPos = (internalAlignFlags & Qt::AlignLeft) ? -rect.width() : - (internalAlignFlags & Qt::AlignHCenter) ? -rect.width() / 2 : - /* (internalAlignFlags & Qt::AlignRight) */ 0; - - textPath.addText(xPos, yPos, fnt, internalText); - QPainterPathStroker stroker; - stroker.setWidth(3); - textBackgroundItem->setPath(stroker.createStroke(textPath)); - textItem->setPath(textPath); -} diff --git a/desktop-widgets/profile/divetextitem.h b/desktop-widgets/profile/divetextitem.h deleted file mode 100644 index be0adf292..000000000 --- a/desktop-widgets/profile/divetextitem.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef DIVETEXTITEM_H -#define DIVETEXTITEM_H - -#include -#include - -class QBrush; - -/* A Line Item that has animated-properties. */ -class DiveTextItem : public QObject, public QGraphicsItemGroup { - Q_OBJECT - Q_PROPERTY(QPointF pos READ pos WRITE setPos) - Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) -public: - DiveTextItem(QGraphicsItem *parent = 0); - void setText(const QString &text); - void setAlignment(int alignFlags); - void setScale(double newscale); - void setBrush(const QBrush &brush); - const QString &text(); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - -private -slots: - void fontPrintScaleUpdate(double scale); - -private: - void updateText(); - int internalAlignFlags; - QGraphicsPathItem *textBackgroundItem; - QGraphicsPathItem *textItem; - QString internalText; - double printScale; - double scale; - bool connected; -}; - -#endif // DIVETEXTITEM_H diff --git a/desktop-widgets/profile/divetooltipitem.cpp b/desktop-widgets/profile/divetooltipitem.cpp deleted file mode 100644 index d4818422b..000000000 --- a/desktop-widgets/profile/divetooltipitem.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include "divetooltipitem.h" -#include "divecartesianaxis.h" -#include "dive.h" -#include "profile.h" -#include "membuffer.h" -#include "metrics.h" -#include -#include -#include -#include - -#define PORT_IN_PROGRESS 1 -#ifdef PORT_IN_PROGRESS -#include "display.h" -#endif - -void ToolTipItem::addToolTip(const QString &toolTip, const QIcon &icon, const QPixmap& pixmap) -{ - const IconMetrics& iconMetrics = defaultIconMetrics(); - - QGraphicsPixmapItem *iconItem = 0; - double yValue = title->boundingRect().height() + iconMetrics.spacing; - Q_FOREACH (ToolTip t, toolTips) { - yValue += t.second->boundingRect().height(); - } - if (entryToolTip.second) { - yValue += entryToolTip.second->boundingRect().height(); - } - iconItem = new QGraphicsPixmapItem(this); - if (!icon.isNull()) { - iconItem->setPixmap(icon.pixmap(iconMetrics.sz_small, iconMetrics.sz_small)); - } else if (!pixmap.isNull()) { - iconItem->setPixmap(pixmap); - } - iconItem->setPos(iconMetrics.spacing, yValue); - - QGraphicsSimpleTextItem *textItem = new QGraphicsSimpleTextItem(toolTip, this); - textItem->setPos(iconMetrics.spacing + iconMetrics.sz_small + iconMetrics.spacing, yValue); - textItem->setBrush(QBrush(Qt::white)); - textItem->setFlag(ItemIgnoresTransformations); - toolTips.push_back(qMakePair(iconItem, textItem)); -} - -void ToolTipItem::clear() -{ - Q_FOREACH (ToolTip t, toolTips) { - delete t.first; - delete t.second; - } - toolTips.clear(); -} - -void ToolTipItem::setRect(const QRectF &r) -{ - if( r == rect() ) { - return; - } - - QGraphicsRectItem::setRect(r); - updateTitlePosition(); -} - -void ToolTipItem::collapse() -{ - int dim = defaultIconMetrics().sz_small; - - if (prefs.animation_speed) { - QPropertyAnimation *animation = new QPropertyAnimation(this, "rect"); - animation->setDuration(100); - animation->setStartValue(nextRectangle); - animation->setEndValue(QRect(0, 0, dim, dim)); - animation->start(QAbstractAnimation::DeleteWhenStopped); - } else { - setRect(nextRectangle); - } - clear(); - - status = COLLAPSED; -} - -void ToolTipItem::expand() -{ - if (!title) - return; - - const IconMetrics& iconMetrics = defaultIconMetrics(); - - double width = 0, height = title->boundingRect().height() + iconMetrics.spacing; - Q_FOREACH (const ToolTip& t, toolTips) { - QRectF sRect = t.second->boundingRect(); - if (sRect.width() > width) - width = sRect.width(); - height += sRect.height(); - } - - if (entryToolTip.first) { - QRectF sRect = entryToolTip.second->boundingRect(); - if (sRect.width() > width) - width = sRect.width(); - height += sRect.height(); - } - - /* Left padding, Icon Size, space, right padding */ - width += iconMetrics.spacing + iconMetrics.sz_small + iconMetrics.spacing + iconMetrics.spacing; - - if (width < title->boundingRect().width() + iconMetrics.spacing * 2) - width = title->boundingRect().width() + iconMetrics.spacing * 2; - - if (height < iconMetrics.sz_small) - height = iconMetrics.sz_small; - - nextRectangle.setWidth(width); - nextRectangle.setHeight(height); - - if (nextRectangle != rect()) { - if (prefs.animation_speed) { - QPropertyAnimation *animation = new QPropertyAnimation(this, "rect", this); - animation->setDuration(prefs.animation_speed); - animation->setStartValue(rect()); - animation->setEndValue(nextRectangle); - animation->start(QAbstractAnimation::DeleteWhenStopped); - } else { - setRect(nextRectangle); - } - } - - status = EXPANDED; -} - -ToolTipItem::ToolTipItem(QGraphicsItem *parent) : QGraphicsRectItem(parent), - title(new QGraphicsSimpleTextItem(tr("Information"), this)), - status(COLLAPSED), - timeAxis(0), - lastTime(-1) -{ - memset(&pInfo, 0, sizeof(pInfo)); - entryToolTip.first = NULL; - entryToolTip.second = NULL; - setFlags(ItemIgnoresTransformations | ItemIsMovable | ItemClipsChildrenToShape); - - QColor c = QColor(Qt::black); - c.setAlpha(155); - setBrush(c); - - setZValue(99); - - addToolTip(QString(), QIcon(), QPixmap(16,60)); - entryToolTip = toolTips.first(); - toolTips.clear(); - - title->setFlag(ItemIgnoresTransformations); - title->setPen(QPen(Qt::white, 1)); - title->setBrush(Qt::white); - - setPen(QPen(Qt::white, 2)); - refreshTime.start(); -} - -ToolTipItem::~ToolTipItem() -{ - clear(); -} - -void ToolTipItem::updateTitlePosition() -{ - const IconMetrics& iconMetrics = defaultIconMetrics(); - if (rect().width() < title->boundingRect().width() + iconMetrics.spacing * 4) { - QRectF newRect = rect(); - newRect.setWidth(title->boundingRect().width() + iconMetrics.spacing * 4); - newRect.setHeight((newRect.height() && isExpanded()) ? newRect.height() : iconMetrics.sz_small); - setRect(newRect); - } - - title->setPos(rect().width() / 2 - title->boundingRect().width() / 2 - 1, 0); -} - -bool ToolTipItem::isExpanded() const -{ - return status == EXPANDED; -} - -void ToolTipItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) -{ - persistPos(); - QGraphicsRectItem::mouseReleaseEvent(event); - Q_FOREACH (QGraphicsItem *item, oldSelection) { - item->setSelected(true); - } -} - -void ToolTipItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - Q_UNUSED(widget); - painter->save(); - painter->setClipRect(option->rect); - painter->setPen(pen()); - painter->setBrush(brush()); - painter->drawRoundedRect(rect(), 10, 10, Qt::AbsoluteSize); - painter->restore(); -} - -void ToolTipItem::persistPos() -{ - QSettings s; - s.beginGroup("ProfileMap"); - s.setValue("tooltip_position", pos()); - s.endGroup(); -} - -void ToolTipItem::readPos() -{ - QSettings s; - s.beginGroup("ProfileMap"); - QPointF value = s.value("tooltip_position").toPoint(); - if (!scene()->sceneRect().contains(value)) { - value = QPointF(0, 0); - } - setPos(value); -} - -void ToolTipItem::setPlotInfo(const plot_info &plot) -{ - pInfo = plot; -} - -void ToolTipItem::setTimeAxis(DiveCartesianAxis *axis) -{ - timeAxis = axis; -} - -void ToolTipItem::refresh(const QPointF &pos) -{ - struct plot_data *entry; - static QPixmap tissues(16,60); - static QPainter painter(&tissues); - static struct membuffer mb = { 0 }; - - if(refreshTime.elapsed() < 40) - return; - refreshTime.start(); - - int time = timeAxis->valueAt(pos); - if (time == lastTime) - return; - - lastTime = time; - clear(); - - mb.len = 0; - entry = get_plot_details_new(&pInfo, time, &mb); - if (entry) { - tissues.fill(); - painter.setPen(QColor(0, 0, 0, 0)); - painter.setBrush(QColor(LIMENADE1)); - painter.drawRect(0, 10 + (100 - AMB_PERCENTAGE) / 2, 16, AMB_PERCENTAGE / 2); - painter.setBrush(QColor(SPRINGWOOD1)); - painter.drawRect(0, 10, 16, (100 - AMB_PERCENTAGE) / 2); - painter.setBrush(QColor(Qt::red)); - painter.drawRect(0,0,16,10); - painter.setPen(QColor(0, 0, 0, 255)); - painter.drawLine(0, 60 - entry->gfline / 2, 16, 60 - entry->gfline / 2); - painter.drawLine(0, 60 - AMB_PERCENTAGE * (entry->pressures.n2 + entry->pressures.he) / entry->ambpressure / 2, - 16, 60 - AMB_PERCENTAGE * (entry->pressures.n2 + entry->pressures.he) / entry->ambpressure /2); - painter.setPen(QColor(0, 0, 0, 127)); - for (int i=0; i<16; i++) { - painter.drawLine(i, 60, i, 60 - entry->percentages[i] / 2); - } - entryToolTip.first->setPixmap(tissues); - entryToolTip.second->setText(QString::fromUtf8(mb.buffer, mb.len)); - } - - Q_FOREACH (QGraphicsItem *item, scene()->items(pos, Qt::IntersectsItemBoundingRect - ,Qt::DescendingOrder, scene()->views().first()->transform())) { - if (!item->toolTip().isEmpty()) - addToolTip(item->toolTip()); - } - expand(); -} - -void ToolTipItem::mousePressEvent(QGraphicsSceneMouseEvent *event) -{ - oldSelection = scene()->selectedItems(); - scene()->clearSelection(); - QGraphicsItem::mousePressEvent(event); -} diff --git a/desktop-widgets/profile/divetooltipitem.h b/desktop-widgets/profile/divetooltipitem.h deleted file mode 100644 index 4fa7ec2d7..000000000 --- a/desktop-widgets/profile/divetooltipitem.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef DIVETOOLTIPITEM_H -#define DIVETOOLTIPITEM_H - -#include -#include -#include -#include -#include -#include -#include "display.h" - -class DiveCartesianAxis; -class QGraphicsLineItem; -class QGraphicsSimpleTextItem; -class QGraphicsPixmapItem; -struct graphics_context; - -/* 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 QGraphicsRectItem { - Q_OBJECT - void updateTitlePosition(); - Q_PROPERTY(QRectF rect READ rect WRITE setRect) - -public: - enum Status { - COLLAPSED, - EXPANDED - }; - - explicit ToolTipItem(QGraphicsItem *parent = 0); - virtual ~ToolTipItem(); - - void collapse(); - void expand(); - void clear(); - void addToolTip(const QString &toolTip, const QIcon &icon = QIcon(), const QPixmap &pixmap = QPixmap()); - void refresh(const QPointF &pos); - bool isExpanded() const; - void persistPos(); - void readPos(); - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); - void setTimeAxis(DiveCartesianAxis *axis); - void setPlotInfo(const plot_info &plot); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); -public -slots: - void setRect(const QRectF &rect); - -private: - typedef QPair ToolTip; - QVector toolTips; - ToolTip entryToolTip; - QGraphicsSimpleTextItem *title; - Status status; - QRectF rectangle; - QRectF nextRectangle; - DiveCartesianAxis *timeAxis; - plot_info pInfo; - int lastTime; - QTime refreshTime; - QList oldSelection; -}; - -#endif // DIVETOOLTIPITEM_H diff --git a/desktop-widgets/profile/profilewidget2.cpp b/desktop-widgets/profile/profilewidget2.cpp deleted file mode 100644 index 3ccd1bb6d..000000000 --- a/desktop-widgets/profile/profilewidget2.cpp +++ /dev/null @@ -1,1836 +0,0 @@ -#include "profilewidget2.h" -#include "diveplotdatamodel.h" -#include "helpers.h" -#include "profile.h" -#include "diveeventitem.h" -#include "divetextitem.h" -#include "divetooltipitem.h" -#include "planner.h" -#include "device.h" -#include "ruleritem.h" -#include "tankitem.h" -#include "pref.h" -#include "divepicturewidget.h" -#include "diveplannermodel.h" -#include "models.h" -#include "divepicturemodel.h" -#include "maintab.h" -#include "diveplanner.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifndef QT_NO_DEBUG -#include -#endif -#include "mainwindow.h" -#include - -/* This is the global 'Item position' variable. - * it should tell you where to position things up - * on the canvas. - * - * please, please, please, use this instead of - * hard coding the item on the scene with a random - * value. - */ -static struct _ItemPos { - struct _Pos { - QPointF on; - QPointF off; - }; - struct _Axis { - _Pos pos; - QLineF shrinked; - QLineF expanded; - QLineF intermediate; - }; - _Pos background; - _Pos dcLabel; - _Pos tankBar; - _Axis depth; - _Axis partialPressure; - _Axis partialPressureTissue; - _Axis partialPressureWithTankBar; - _Axis percentage; - _Axis percentageWithTankBar; - _Axis time; - _Axis cylinder; - _Axis temperature; - _Axis temperatureAll; - _Axis heartBeat; - _Axis heartBeatWithTankBar; -} itemPos; - -ProfileWidget2::ProfileWidget2(QWidget *parent) : QGraphicsView(parent), - currentState(INVALID), - dataModel(new DivePlotDataModel(this)), - zoomLevel(0), - zoomFactor(1.15), - background(new DivePixmapItem()), - backgroundFile(":poster"), - toolTipItem(new ToolTipItem()), - isPlotZoomed(prefs.zoomed_plot),// no! bad use of prefs. 'PreferencesDialog::loadSettings' NOT CALLED yet. - profileYAxis(new DepthAxis()), - gasYAxis(new PartialGasPressureAxis()), - temperatureAxis(new TemperatureAxis()), - timeAxis(new TimeAxis()), - diveProfileItem(new DiveProfileItem()), - temperatureItem(new DiveTemperatureItem()), - meanDepthItem(new DiveMeanDepthItem()), - cylinderPressureAxis(new DiveCartesianAxis()), - gasPressureItem(new DiveGasPressureItem()), - diveComputerText(new DiveTextItem()), - diveCeiling(new DiveCalculatedCeiling()), - gradientFactor(new DiveTextItem()), - reportedCeiling(new DiveReportedCeiling()), - pn2GasItem(new PartialPressureGasItem()), - pheGasItem(new PartialPressureGasItem()), - po2GasItem(new PartialPressureGasItem()), - o2SetpointGasItem(new PartialPressureGasItem()), - ccrsensor1GasItem(new PartialPressureGasItem()), - ccrsensor2GasItem(new PartialPressureGasItem()), - ccrsensor3GasItem(new PartialPressureGasItem()), - heartBeatAxis(new DiveCartesianAxis()), - heartBeatItem(new DiveHeartrateItem()), - percentageAxis(new DiveCartesianAxis()), - ambPressureItem(new DiveAmbPressureItem()), - gflineItem(new DiveGFLineItem()), - mouseFollowerVertical(new DiveLineItem()), - mouseFollowerHorizontal(new DiveLineItem()), - rulerItem(new RulerItem2()), - tankItem(new TankItem()), - isGrayscale(false), - printMode(false), - shouldCalculateMaxTime(true), - shouldCalculateMaxDepth(true), - fontPrintScale(1.0) -{ - // would like to be able to ASSERT here that PreferencesDialog::loadSettings has been called. - isPlotZoomed = prefs.zoomed_plot; // now it seems that 'prefs' has loaded our preferences - - memset(&plotInfo, 0, sizeof(plotInfo)); - - setupSceneAndFlags(); - setupItemSizes(); - setupItemOnScene(); - addItemsToScene(); - scene()->installEventFilter(this); - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); - QAction *action = NULL; -#define ADD_ACTION(SHORTCUT, Slot) \ - action = new QAction(this); \ - action->setShortcut(SHORTCUT); \ - action->setShortcutContext(Qt::WindowShortcut); \ - addAction(action); \ - connect(action, SIGNAL(triggered(bool)), this, SLOT(Slot)); \ - actionsForKeys[SHORTCUT] = action; - - ADD_ACTION(Qt::Key_Escape, keyEscAction()); - ADD_ACTION(Qt::Key_Delete, keyDeleteAction()); - ADD_ACTION(Qt::Key_Up, keyUpAction()); - ADD_ACTION(Qt::Key_Down, keyDownAction()); - ADD_ACTION(Qt::Key_Left, keyLeftAction()); - ADD_ACTION(Qt::Key_Right, keyRightAction()); -#undef ADD_ACTION - -#if !defined(QT_NO_DEBUG) && defined(SHOW_PLOT_INFO_TABLE) - QTableView *diveDepthTableView = new QTableView(); - diveDepthTableView->setModel(dataModel); - diveDepthTableView->show(); -#endif -} - - -ProfileWidget2::~ProfileWidget2() -{ - delete background; - delete toolTipItem; - delete profileYAxis; - delete gasYAxis; - delete temperatureAxis; - delete timeAxis; - delete diveProfileItem; - delete temperatureItem; - delete meanDepthItem; - delete cylinderPressureAxis; - delete gasPressureItem; - delete diveComputerText; - delete diveCeiling; - delete reportedCeiling; - delete pn2GasItem; - delete pheGasItem; - delete po2GasItem; - delete o2SetpointGasItem; - delete ccrsensor1GasItem; - delete ccrsensor2GasItem; - delete ccrsensor3GasItem; - delete heartBeatAxis; - delete heartBeatItem; - delete percentageAxis; - delete ambPressureItem; - delete gflineItem; - delete mouseFollowerVertical; - delete mouseFollowerHorizontal; - delete rulerItem; - delete tankItem; -} - -#define SUBSURFACE_OBJ_DATA 1 -#define SUBSURFACE_OBJ_DC_TEXT 0x42 - -void ProfileWidget2::addItemsToScene() -{ - scene()->addItem(background); - scene()->addItem(toolTipItem); - scene()->addItem(profileYAxis); - scene()->addItem(gasYAxis); - scene()->addItem(temperatureAxis); - scene()->addItem(timeAxis); - scene()->addItem(diveProfileItem); - scene()->addItem(cylinderPressureAxis); - scene()->addItem(temperatureItem); - scene()->addItem(meanDepthItem); - scene()->addItem(gasPressureItem); - // I cannot seem to figure out if an object that I find with itemAt() on the scene - // is the object I am looking for - my guess is there's a simple way in Qt to do that - // but nothing I tried worked. - // so instead this adds a special magic key/value pair to the object to mark it - diveComputerText->setData(SUBSURFACE_OBJ_DATA, SUBSURFACE_OBJ_DC_TEXT); - scene()->addItem(diveComputerText); - scene()->addItem(diveCeiling); - scene()->addItem(gradientFactor); - scene()->addItem(reportedCeiling); - scene()->addItem(pn2GasItem); - scene()->addItem(pheGasItem); - scene()->addItem(po2GasItem); - scene()->addItem(o2SetpointGasItem); - scene()->addItem(ccrsensor1GasItem); - scene()->addItem(ccrsensor2GasItem); - scene()->addItem(ccrsensor3GasItem); - scene()->addItem(percentageAxis); - scene()->addItem(heartBeatAxis); - scene()->addItem(heartBeatItem); - scene()->addItem(rulerItem); - scene()->addItem(rulerItem->sourceNode()); - scene()->addItem(rulerItem->destNode()); - scene()->addItem(tankItem); - scene()->addItem(mouseFollowerHorizontal); - scene()->addItem(mouseFollowerVertical); - QPen pen(QColor(Qt::red).lighter()); - pen.setWidth(0); - mouseFollowerHorizontal->setPen(pen); - mouseFollowerVertical->setPen(pen); - Q_FOREACH (DiveCalculatedTissue *tissue, allTissues) { - scene()->addItem(tissue); - } - Q_FOREACH (DivePercentageItem *percentage, allPercentages) { - scene()->addItem(percentage); - } - scene()->addItem(ambPressureItem); - scene()->addItem(gflineItem); -} - -void ProfileWidget2::setupItemOnScene() -{ - background->setZValue(9999); - toolTipItem->setZValue(9998); - toolTipItem->setTimeAxis(timeAxis); - rulerItem->setZValue(9997); - tankItem->setZValue(100); - - profileYAxis->setOrientation(DiveCartesianAxis::TopToBottom); - profileYAxis->setMinimum(0); - profileYAxis->setTickInterval(M_OR_FT(10, 30)); - profileYAxis->setTickSize(0.5); - profileYAxis->setLineSize(96); - - timeAxis->setLineSize(92); - timeAxis->setTickSize(-0.5); - - gasYAxis->setOrientation(DiveCartesianAxis::BottomToTop); - gasYAxis->setTickInterval(1); - gasYAxis->setTickSize(1); - gasYAxis->setMinimum(0); - gasYAxis->setModel(dataModel); - gasYAxis->setFontLabelScale(0.7); - gasYAxis->setLineSize(96); - - heartBeatAxis->setOrientation(DiveCartesianAxis::BottomToTop); - heartBeatAxis->setTickSize(0.2); - heartBeatAxis->setTickInterval(10); - heartBeatAxis->setFontLabelScale(0.7); - heartBeatAxis->setLineSize(96); - - percentageAxis->setOrientation(DiveCartesianAxis::BottomToTop); - percentageAxis->setTickSize(0.2); - percentageAxis->setTickInterval(10); - percentageAxis->setFontLabelScale(0.7); - percentageAxis->setLineSize(96); - - temperatureAxis->setOrientation(DiveCartesianAxis::BottomToTop); - temperatureAxis->setTickSize(2); - temperatureAxis->setTickInterval(300); - - cylinderPressureAxis->setOrientation(DiveCartesianAxis::BottomToTop); - cylinderPressureAxis->setTickSize(2); - cylinderPressureAxis->setTickInterval(30000); - - - diveComputerText->setAlignment(Qt::AlignRight | Qt::AlignTop); - diveComputerText->setBrush(getColor(TIME_TEXT, isGrayscale)); - - rulerItem->setAxis(timeAxis, profileYAxis); - tankItem->setHorizontalAxis(timeAxis); - - // show the gradient factor at the top in the center - gradientFactor->setY(0); - gradientFactor->setX(50); - gradientFactor->setBrush(getColor(PRESSURE_TEXT)); - gradientFactor->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - - setupItem(reportedCeiling, timeAxis, profileYAxis, dataModel, DivePlotDataModel::CEILING, DivePlotDataModel::TIME, 1); - setupItem(diveCeiling, timeAxis, profileYAxis, dataModel, DivePlotDataModel::CEILING, DivePlotDataModel::TIME, 1); - for (int i = 0; i < 16; i++) { - DiveCalculatedTissue *tissueItem = new DiveCalculatedTissue(); - setupItem(tissueItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::TISSUE_1 + i, DivePlotDataModel::TIME, 1 + i); - allTissues.append(tissueItem); - DivePercentageItem *percentageItem = new DivePercentageItem(i); - setupItem(percentageItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::PERCENTAGE_1 + i, DivePlotDataModel::TIME, 1 + i); - allPercentages.append(percentageItem); - } - setupItem(gasPressureItem, timeAxis, cylinderPressureAxis, dataModel, DivePlotDataModel::TEMPERATURE, DivePlotDataModel::TIME, 1); - setupItem(temperatureItem, timeAxis, temperatureAxis, dataModel, DivePlotDataModel::TEMPERATURE, DivePlotDataModel::TIME, 1); - setupItem(heartBeatItem, timeAxis, heartBeatAxis, dataModel, DivePlotDataModel::HEARTBEAT, DivePlotDataModel::TIME, 1); - setupItem(ambPressureItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::AMBPRESSURE, DivePlotDataModel::TIME, 1); - setupItem(gflineItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::GFLINE, DivePlotDataModel::TIME, 1); - setupItem(diveProfileItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::DEPTH, DivePlotDataModel::TIME, 0); - setupItem(meanDepthItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::INSTANT_MEANDEPTH, DivePlotDataModel::TIME, 1); - - -#define CREATE_PP_GAS(ITEM, VERTICAL_COLUMN, COLOR, COLOR_ALERT, THRESHOULD_SETTINGS, VISIBILITY_SETTINGS) \ - setupItem(ITEM, timeAxis, gasYAxis, dataModel, DivePlotDataModel::VERTICAL_COLUMN, DivePlotDataModel::TIME, 0); \ - ITEM->setThreshouldSettingsKey(THRESHOULD_SETTINGS); \ - ITEM->setVisibilitySettingsKey(VISIBILITY_SETTINGS); \ - ITEM->setColors(getColor(COLOR, isGrayscale), getColor(COLOR_ALERT, isGrayscale)); \ - ITEM->settingsChanged(); \ - ITEM->setZValue(99); - - CREATE_PP_GAS(pn2GasItem, PN2, PN2, PN2_ALERT, &prefs.pp_graphs.pn2_threshold, "pn2graph"); - CREATE_PP_GAS(pheGasItem, PHE, PHE, PHE_ALERT, &prefs.pp_graphs.phe_threshold, "phegraph"); - CREATE_PP_GAS(po2GasItem, PO2, PO2, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "po2graph"); - CREATE_PP_GAS(o2SetpointGasItem, O2SETPOINT, PO2_ALERT, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "po2graph"); - CREATE_PP_GAS(ccrsensor1GasItem, CCRSENSOR1, CCRSENSOR1, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); - CREATE_PP_GAS(ccrsensor2GasItem, CCRSENSOR2, CCRSENSOR2, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); - CREATE_PP_GAS(ccrsensor3GasItem, CCRSENSOR3, CCRSENSOR3, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); -#undef CREATE_PP_GAS - - temperatureAxis->setTextVisible(false); - temperatureAxis->setLinesVisible(false); - cylinderPressureAxis->setTextVisible(false); - cylinderPressureAxis->setLinesVisible(false); - timeAxis->setLinesVisible(true); - profileYAxis->setLinesVisible(true); - gasYAxis->setZValue(timeAxis->zValue() + 1); - heartBeatAxis->setTextVisible(true); - heartBeatAxis->setLinesVisible(true); - percentageAxis->setTextVisible(true); - percentageAxis->setLinesVisible(true); - - replotEnabled = true; -} - -void ProfileWidget2::replot(struct dive *d) -{ - if (!replotEnabled) - return; - dataModel->clear(); - plotDive(d, true); -} - -void ProfileWidget2::setupItemSizes() -{ - // Scene is *always* (double) 100 / 100. - // Background Config - /* Much probably a better math is needed here. - * good thing is that we only need to change the - * Axis and everything else is auto-adjusted.* - */ - - itemPos.background.on.setX(0); - itemPos.background.on.setY(0); - itemPos.background.off.setX(0); - itemPos.background.off.setY(110); - - //Depth Axis Config - itemPos.depth.pos.on.setX(3); - itemPos.depth.pos.on.setY(3); - itemPos.depth.pos.off.setX(-2); - itemPos.depth.pos.off.setY(3); - itemPos.depth.expanded.setP1(QPointF(0, 0)); - itemPos.depth.expanded.setP2(QPointF(0, 85)); - itemPos.depth.shrinked.setP1(QPointF(0, 0)); - itemPos.depth.shrinked.setP2(QPointF(0, 55)); - itemPos.depth.intermediate.setP1(QPointF(0, 0)); - itemPos.depth.intermediate.setP2(QPointF(0, 65)); - - // Time Axis Config - itemPos.time.pos.on.setX(3); - itemPos.time.pos.on.setY(95); - itemPos.time.pos.off.setX(3); - itemPos.time.pos.off.setY(110); - itemPos.time.expanded.setP1(QPointF(0, 0)); - itemPos.time.expanded.setP2(QPointF(94, 0)); - - // Partial Gas Axis Config - itemPos.partialPressure.pos.on.setX(97); - itemPos.partialPressure.pos.on.setY(75); - itemPos.partialPressure.pos.off.setX(110); - itemPos.partialPressure.pos.off.setY(63); - itemPos.partialPressure.expanded.setP1(QPointF(0, 0)); - itemPos.partialPressure.expanded.setP2(QPointF(0, 19)); - itemPos.partialPressureWithTankBar = itemPos.partialPressure; - itemPos.partialPressureWithTankBar.expanded.setP2(QPointF(0, 17)); - itemPos.partialPressureTissue = itemPos.partialPressure; - itemPos.partialPressureTissue.pos.on.setX(97); - itemPos.partialPressureTissue.pos.on.setY(65); - itemPos.partialPressureTissue.expanded.setP2(QPointF(0, 16)); - - // cylinder axis config - itemPos.cylinder.pos.on.setX(3); - itemPos.cylinder.pos.on.setY(20); - itemPos.cylinder.pos.off.setX(-10); - itemPos.cylinder.pos.off.setY(20); - itemPos.cylinder.expanded.setP1(QPointF(0, 15)); - itemPos.cylinder.expanded.setP2(QPointF(0, 50)); - itemPos.cylinder.shrinked.setP1(QPointF(0, 0)); - itemPos.cylinder.shrinked.setP2(QPointF(0, 20)); - itemPos.cylinder.intermediate.setP1(QPointF(0, 0)); - itemPos.cylinder.intermediate.setP2(QPointF(0, 20)); - - // Temperature axis config - itemPos.temperature.pos.on.setX(3); - itemPos.temperature.pos.on.setY(60); - itemPos.temperatureAll.pos.on.setY(51); - itemPos.temperature.pos.off.setX(-10); - itemPos.temperature.pos.off.setY(40); - itemPos.temperature.expanded.setP1(QPointF(0, 20)); - itemPos.temperature.expanded.setP2(QPointF(0, 33)); - itemPos.temperature.shrinked.setP1(QPointF(0, 2)); - itemPos.temperature.shrinked.setP2(QPointF(0, 12)); - itemPos.temperature.intermediate.setP1(QPointF(0, 2)); - itemPos.temperature.intermediate.setP2(QPointF(0, 12)); - - // Heartbeat axis config - itemPos.heartBeat.pos.on.setX(3); - itemPos.heartBeat.pos.on.setY(82); - itemPos.heartBeat.expanded.setP1(QPointF(0, 0)); - itemPos.heartBeat.expanded.setP2(QPointF(0, 10)); - itemPos.heartBeatWithTankBar = itemPos.heartBeat; - itemPos.heartBeatWithTankBar.expanded.setP2(QPointF(0, 7)); - - // Percentage axis config - itemPos.percentage.pos.on.setX(3); - itemPos.percentage.pos.on.setY(80); - itemPos.percentage.expanded.setP1(QPointF(0, 0)); - itemPos.percentage.expanded.setP2(QPointF(0, 15)); - itemPos.percentageWithTankBar = itemPos.percentage; - itemPos.percentageWithTankBar.expanded.setP2(QPointF(0, 12)); - - itemPos.dcLabel.on.setX(3); - itemPos.dcLabel.on.setY(100); - itemPos.dcLabel.off.setX(-10); - itemPos.dcLabel.off.setY(100); - - itemPos.tankBar.on.setX(0); - itemPos.tankBar.on.setY(91.5); -} - -void ProfileWidget2::setupItem(AbstractProfilePolygonItem *item, DiveCartesianAxis *hAxis, - DiveCartesianAxis *vAxis, DivePlotDataModel *model, - int vData, int hData, int zValue) -{ - item->setHorizontalAxis(hAxis); - item->setVerticalAxis(vAxis); - item->setModel(model); - item->setVerticalDataColumn(vData); - item->setHorizontalDataColumn(hData); - item->setZValue(zValue); -} - -void ProfileWidget2::setupSceneAndFlags() -{ - setScene(new QGraphicsScene(this)); - scene()->setSceneRect(0, 0, 100, 100); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scene()->setItemIndexMethod(QGraphicsScene::NoIndex); - setOptimizationFlags(QGraphicsView::DontSavePainterState); - setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); - setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); - setMouseTracking(true); - background->setFlag(QGraphicsItem::ItemIgnoresTransformations); -} - -void ProfileWidget2::resetZoom() -{ - if (!zoomLevel) - return; - const qreal defScale = 1.0 / qPow(zoomFactor, (qreal)zoomLevel); - scale(defScale, defScale); - zoomLevel = 0; -} - -// Currently just one dive, but the plan is to enable All of the selected dives. -void ProfileWidget2::plotDive(struct dive *d, bool force) -{ - static bool firstCall = true; - QTime measureDuration; // let's measure how long this takes us (maybe we'll turn of TTL calculation later - measureDuration.start(); - - if (currentState != ADD && currentState != PLAN) { - if (!d) { - if (selected_dive == -1) - return; - d = current_dive; // display the current dive - } - - // No need to do this again if we are already showing the same dive - // computer of the same dive, so we check the unique id of the dive - // and the selected dive computer number against the ones we are - // showing (can't compare the dive pointers as those might change). - if (d->id == displayed_dive.id && dc_number == dataModel->dcShown() && !force) - return; - - // this copies the dive and makes copies of all the relevant additional data - copy_dive(d, &displayed_dive); - gradientFactor->setText(QString("GF %1/%2").arg(prefs.gflow).arg(prefs.gfhigh)); - } else { - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - plannerModel->createTemporaryPlan(); - struct diveplan &diveplan = plannerModel->getDiveplan(); - if (!diveplan.dp) { - plannerModel->deleteTemporaryPlan(); - return; - } - gradientFactor->setText(QString("GF %1/%2").arg(diveplan.gflow).arg(diveplan.gfhigh)); - } - - // special handling for the first time we display things - int animSpeedBackup = 0; - if (firstCall && MainWindow::instance()->filesFromCommandLine()) { - animSpeedBackup = prefs.animation_speed; - prefs.animation_speed = 0; - firstCall = false; - } - - // restore default zoom level - resetZoom(); - - // reset some item visibility on printMode changes - toolTipItem->setVisible(!printMode); - rulerItem->setVisible(prefs.rulergraph && !printMode && currentState != PLAN && currentState != ADD); - - if (currentState == EMPTY) - setProfileState(); - - // next get the dive computer structure - if there are no samples - // let's create a fake profile that's somewhat reasonable for the - // data that we have - struct divecomputer *currentdc = select_dc(&displayed_dive); - Q_ASSERT(currentdc); - if (!currentdc || !currentdc->samples) { - currentdc = fake_dc(currentdc); - } - - bool setpointflag = (currentdc->divemode == CCR) && prefs.pp_graphs.po2 && current_dive; - bool sensorflag = setpointflag && prefs.show_ccr_sensors; - o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint); - ccrsensor1GasItem->setVisible(sensorflag); - ccrsensor2GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 1)); - ccrsensor3GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 2)); - - /* This struct holds all the data that's about to be plotted. - * I'm not sure this is the best approach ( but since we are - * interpolating some points of the Dive, maybe it is... ) - * The Calculation of the points should be done per graph, - * so I'll *not* calculate everything if something is not being - * shown. - */ - plotInfo = calculate_max_limits_new(&displayed_dive, currentdc); - create_plot_info_new(&displayed_dive, currentdc, &plotInfo, !shouldCalculateMaxDepth); - if (shouldCalculateMaxTime) - maxtime = get_maxtime(&plotInfo); - - /* Only update the max depth if it's bigger than the current ones - * when we are dragging the handler to plan / add dive. - * otherwhise, update normally. - */ - int newMaxDepth = get_maxdepth(&plotInfo); - if (!shouldCalculateMaxDepth) { - if (maxdepth < newMaxDepth) { - maxdepth = newMaxDepth; - } - } else { - maxdepth = newMaxDepth; - } - - dataModel->setDive(&displayed_dive, plotInfo); - toolTipItem->setPlotInfo(plotInfo); - - // It seems that I'll have a lot of boilerplate setting the model / axis for - // each item, I'll mostly like to fix this in the future, but I'll keep at this for now. - profileYAxis->setMaximum(maxdepth); - profileYAxis->updateTicks(); - - temperatureAxis->setMinimum(plotInfo.mintemp); - temperatureAxis->setMaximum(plotInfo.maxtemp - plotInfo.mintemp > 2000 ? plotInfo.maxtemp : plotInfo.mintemp + 2000); - - if (plotInfo.maxhr) { - heartBeatAxis->setMinimum(plotInfo.minhr); - heartBeatAxis->setMaximum(plotInfo.maxhr); - heartBeatAxis->updateTicks(HR_AXIS); // this shows the ticks - } - heartBeatAxis->setVisible(prefs.hrgraph && plotInfo.maxhr); - - percentageAxis->setMinimum(0); - percentageAxis->setMaximum(100); - percentageAxis->setVisible(false); - percentageAxis->updateTicks(HR_AXIS); - - timeAxis->setMaximum(maxtime); - int i, incr; - static int increments[8] = { 10, 20, 30, 60, 5 * 60, 10 * 60, 15 * 60, 30 * 60 }; - /* 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 (i < 7 && maxtime / increments[i] > 12) - i++; - incr = increments[i]; - while (maxtime / incr > 12) - incr *= 2; - timeAxis->setTickInterval(incr); - timeAxis->updateTicks(); - cylinderPressureAxis->setMinimum(plotInfo.minpressure); - cylinderPressureAxis->setMaximum(plotInfo.maxpressure); - - rulerItem->setPlotInfo(plotInfo); - tankItem->setData(dataModel, &plotInfo, &displayed_dive); - - dataModel->emitDataChanged(); - // The event items are a bit special since we don't know how many events are going to - // exist on a dive, so I cant create cache items for that. that's why they are here - // while all other items are up there on the constructor. - qDeleteAll(eventItems); - eventItems.clear(); - struct event *event = currentdc->events; - while (event) { - // if print mode is selected only draw headings, SP change, gas events or bookmark event - if (printMode) { - if (same_string(event->name, "") || - !(strcmp(event->name, "heading") == 0 || - (same_string(event->name, "SP change") && event->time.seconds == 0) || - event_is_gaschange(event) || - event->type == SAMPLE_EVENT_BOOKMARK)) { - event = event->next; - continue; - } - } - DiveEventItem *item = new DiveEventItem(); - item->setHorizontalAxis(timeAxis); - item->setVerticalAxis(profileYAxis); - item->setModel(dataModel); - item->setEvent(event); - item->setZValue(2); - scene()->addItem(item); - eventItems.push_back(item); - event = event->next; - } - // Only set visible the events that should be visible - Q_FOREACH (DiveEventItem *event, eventItems) { - event->setVisible(!event->shouldBeHidden()); - } - QString dcText = get_dc_nickname(currentdc->model, currentdc->deviceid); - int nr; - if ((nr = number_of_computers(&displayed_dive)) > 1) - dcText += tr(" (#%1 of %2)").arg(dc_number + 1).arg(nr); - if (dcText.isEmpty()) - dcText = tr("Unknown dive computer"); - diveComputerText->setText(dcText); - if (MainWindow::instance()->filesFromCommandLine() && animSpeedBackup != 0) { - prefs.animation_speed = animSpeedBackup; - } - - if (currentState == ADD || currentState == PLAN) { // TODO: figure a way to move this from here. - repositionDiveHandlers(); - DivePlannerPointsModel *model = DivePlannerPointsModel::instance(); - model->deleteTemporaryPlan(); - } - plotPictures(); - - // OK, how long did this take us? Anything above the second is way too long, - // so if we are calculation TTS / NDL then let's force that off. - if (measureDuration.elapsed() > 1000 && prefs.calcndltts) { - MainWindow::instance()->turnOffNdlTts(); - MainWindow::instance()->getNotificationWidget()->showNotification(tr("Show NDL / TTS was disabled because of excessive processing time"), KMessageWidget::Error); - } - MainWindow::instance()->getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); - -} - -void ProfileWidget2::recalcCeiling() -{ - diveCeiling->recalc(); -} - -void ProfileWidget2::settingsChanged() -{ - // if we are showing calculated ceilings then we have to replot() - // because the GF could have changed; otherwise we try to avoid replot() - bool needReplot = prefs.calcceiling; - if ((prefs.percentagegraph||prefs.hrgraph) && PP_GRAPHS_ENABLED) { - profileYAxis->animateChangeLine(itemPos.depth.shrinked); - temperatureAxis->setPos(itemPos.temperatureAll.pos.on); - temperatureAxis->animateChangeLine(itemPos.temperature.shrinked); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.shrinked); - - if (prefs.tankbar) { - percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); - percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); - heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); - }else { - percentageAxis->setPos(itemPos.percentage.pos.on); - percentageAxis->animateChangeLine(itemPos.percentage.expanded); - heartBeatAxis->setPos(itemPos.heartBeat.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); - } - gasYAxis->setPos(itemPos.partialPressureTissue.pos.on); - gasYAxis->animateChangeLine(itemPos.partialPressureTissue.expanded); - - } else if (PP_GRAPHS_ENABLED || prefs.hrgraph || prefs.percentagegraph) { - profileYAxis->animateChangeLine(itemPos.depth.intermediate); - temperatureAxis->setPos(itemPos.temperature.pos.on); - temperatureAxis->animateChangeLine(itemPos.temperature.intermediate); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.intermediate); - if (prefs.tankbar) { - percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); - percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); - gasYAxis->setPos(itemPos.partialPressureWithTankBar.pos.on); - gasYAxis->setLine(itemPos.partialPressureWithTankBar.expanded); - heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); - } else { - gasYAxis->setPos(itemPos.partialPressure.pos.on); - gasYAxis->animateChangeLine(itemPos.partialPressure.expanded); - percentageAxis->setPos(itemPos.percentage.pos.on); - percentageAxis->setLine(itemPos.percentage.expanded); - heartBeatAxis->setPos(itemPos.heartBeat.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); - } - } else { - profileYAxis->animateChangeLine(itemPos.depth.expanded); - if (prefs.tankbar) { - temperatureAxis->setPos(itemPos.temperatureAll.pos.on); - } else { - temperatureAxis->setPos(itemPos.temperature.pos.on); - } - temperatureAxis->animateChangeLine(itemPos.temperature.expanded); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.expanded); - } - - tankItem->setVisible(prefs.tankbar); - if (prefs.zoomed_plot != isPlotZoomed) { - isPlotZoomed = prefs.zoomed_plot; - needReplot = true; - } - if (needReplot) - replot(); -} - -void ProfileWidget2::resizeEvent(QResizeEvent *event) -{ - QGraphicsView::resizeEvent(event); - fitInView(sceneRect(), Qt::IgnoreAspectRatio); - fixBackgroundPos(); -} - -void ProfileWidget2::mousePressEvent(QMouseEvent *event) -{ - if (zoomLevel) - return; - QGraphicsView::mousePressEvent(event); - if (currentState == PLAN) - shouldCalculateMaxTime = false; -} - -void ProfileWidget2::divePlannerHandlerClicked() -{ - if (zoomLevel) - return; - shouldCalculateMaxDepth = false; - replot(); -} - -void ProfileWidget2::divePlannerHandlerReleased() -{ - if (zoomLevel) - return; - shouldCalculateMaxDepth = true; - replot(); -} - -void ProfileWidget2::mouseReleaseEvent(QMouseEvent *event) -{ - if (zoomLevel) - return; - QGraphicsView::mouseReleaseEvent(event); - if (currentState == PLAN) { - shouldCalculateMaxTime = true; - replot(); - } -} - -void ProfileWidget2::fixBackgroundPos() -{ - static QPixmap toBeScaled(backgroundFile); - if (currentState != EMPTY) - return; - QPixmap p = toBeScaled.scaledToHeight(viewport()->height() - 40, Qt::SmoothTransformation); - int x = viewport()->width() / 2 - p.width() / 2; - int y = viewport()->height() / 2 - p.height() / 2; - background->setPixmap(p); - background->setX(mapToScene(x, 0).x()); - background->setY(mapToScene(y, 20).y()); -} - -void ProfileWidget2::wheelEvent(QWheelEvent *event) -{ - if (currentState == EMPTY) - return; - QPoint toolTipPos = mapFromScene(toolTipItem->pos()); - if (event->buttons() == Qt::LeftButton) - return; - if (event->delta() > 0 && zoomLevel < 20) { - scale(zoomFactor, zoomFactor); - zoomLevel++; - } else if (event->delta() < 0 && zoomLevel > 0) { - // Zooming out - scale(1.0 / zoomFactor, 1.0 / zoomFactor); - zoomLevel--; - } - scrollViewTo(event->pos()); - toolTipItem->setPos(mapToScene(toolTipPos)); -} - -void ProfileWidget2::mouseDoubleClickEvent(QMouseEvent *event) -{ - if (currentState == PLAN || currentState == ADD) { - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - QPointF mappedPos = mapToScene(event->pos()); - if (isPointOutOfBoundaries(mappedPos)) - return; - - int minutes = rint(timeAxis->valueAt(mappedPos) / 60); - int milimeters = rint(profileYAxis->valueAt(mappedPos) / M_OR_FT(1, 1)) * M_OR_FT(1, 1); - plannerModel->addStop(milimeters, minutes * 60, 0, 0, true); - } -} - -bool ProfileWidget2::isPointOutOfBoundaries(const QPointF &point) const -{ - double xpos = timeAxis->valueAt(point); - double ypos = profileYAxis->valueAt(point); - return (xpos > timeAxis->maximum() || - xpos < timeAxis->minimum() || - ypos > profileYAxis->maximum() || - ypos < profileYAxis->minimum()); -} - -void ProfileWidget2::scrollViewTo(const QPoint &pos) -{ - /* since we cannot use translate() directly on the scene we hack on - * the scroll bars (hidden) functionality */ - if (!zoomLevel || currentState == EMPTY) - return; - QScrollBar *vs = verticalScrollBar(); - QScrollBar *hs = horizontalScrollBar(); - const qreal yRat = (qreal)pos.y() / viewport()->height(); - const qreal xRat = (qreal)pos.x() / viewport()->width(); - vs->setValue(yRat * vs->maximum()); - hs->setValue(xRat * hs->maximum()); -} - -void ProfileWidget2::mouseMoveEvent(QMouseEvent *event) -{ - QPointF pos = mapToScene(event->pos()); - toolTipItem->refresh(pos); - if (zoomLevel == 0) { - QGraphicsView::mouseMoveEvent(event); - } else { - QPoint toolTipPos = mapFromScene(toolTipItem->pos()); - scrollViewTo(event->pos()); - toolTipItem->setPos(mapToScene(toolTipPos)); - } - - qreal vValue = profileYAxis->valueAt(pos); - qreal hValue = timeAxis->valueAt(pos); - if (profileYAxis->maximum() >= vValue && profileYAxis->minimum() <= vValue) { - mouseFollowerHorizontal->setPos(timeAxis->pos().x(), pos.y()); - } - if (timeAxis->maximum() >= hValue && timeAxis->minimum() <= hValue) { - mouseFollowerVertical->setPos(pos.x(), profileYAxis->line().y1()); - } -} - -bool ProfileWidget2::eventFilter(QObject *object, QEvent *event) -{ - QGraphicsScene *s = qobject_cast(object); - if (s && event->type() == QEvent::GraphicsSceneHelp) { - event->ignore(); - return true; - } - return QGraphicsView::eventFilter(object, event); -} - -void ProfileWidget2::setEmptyState() -{ - // Then starting Empty State, move the background up. - if (currentState == EMPTY) - return; - - disconnectTemporaryConnections(); - setBackgroundBrush(getColor(::BACKGROUND, isGrayscale)); - dataModel->clear(); - currentState = EMPTY; - MainWindow::instance()->setEnabledToolbar(false); - - fixBackgroundPos(); - background->setVisible(true); - - profileYAxis->setVisible(false); - gasYAxis->setVisible(false); - timeAxis->setVisible(false); - temperatureAxis->setVisible(false); - cylinderPressureAxis->setVisible(false); - toolTipItem->setVisible(false); - diveComputerText->setVisible(false); - diveCeiling->setVisible(false); - gradientFactor->setVisible(false); - reportedCeiling->setVisible(false); - rulerItem->setVisible(false); - tankItem->setVisible(false); - pn2GasItem->setVisible(false); - po2GasItem->setVisible(false); - o2SetpointGasItem->setVisible(false); - ccrsensor1GasItem->setVisible(false); - ccrsensor2GasItem->setVisible(false); - ccrsensor3GasItem->setVisible(false); - pheGasItem->setVisible(false); - ambPressureItem->setVisible(false); - gflineItem->setVisible(false); - mouseFollowerHorizontal->setVisible(false); - mouseFollowerVertical->setVisible(false); - -#define HIDE_ALL(TYPE, CONTAINER) \ - Q_FOREACH (TYPE *item, CONTAINER) item->setVisible(false); - HIDE_ALL(DiveCalculatedTissue, allTissues); - HIDE_ALL(DivePercentageItem, allPercentages); - HIDE_ALL(DiveEventItem, eventItems); - HIDE_ALL(DiveHandler, handles); - HIDE_ALL(QGraphicsSimpleTextItem, gases); -#undef HIDE_ALL -} - -void ProfileWidget2::setProfileState() -{ - // Then starting Empty State, move the background up. - if (currentState == PROFILE) - return; - - disconnectTemporaryConnections(); - connect(DivePictureModel::instance(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(plotPictures())); - connect(DivePictureModel::instance(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(plotPictures())); - connect(DivePictureModel::instance(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(plotPictures())); - /* show the same stuff that the profile shows. */ - - //TODO: Move the DC handling to another method. - MainWindow::instance()->enableShortcuts(); - - currentState = PROFILE; - MainWindow::instance()->setEnabledToolbar(true); - toolTipItem->readPos(); - setBackgroundBrush(getColor(::BACKGROUND, isGrayscale)); - - background->setVisible(false); - toolTipItem->setVisible(true); - profileYAxis->setVisible(true); - gasYAxis->setVisible(true); - timeAxis->setVisible(true); - temperatureAxis->setVisible(true); - cylinderPressureAxis->setVisible(true); - - profileYAxis->setPos(itemPos.depth.pos.on); - if ((prefs.percentagegraph||prefs.hrgraph) && PP_GRAPHS_ENABLED) { - profileYAxis->animateChangeLine(itemPos.depth.shrinked); - temperatureAxis->setPos(itemPos.temperatureAll.pos.on); - temperatureAxis->animateChangeLine(itemPos.temperature.shrinked); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.shrinked); - - if (prefs.tankbar) { - percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); - percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); - heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); - }else { - percentageAxis->setPos(itemPos.percentage.pos.on); - percentageAxis->animateChangeLine(itemPos.percentage.expanded); - heartBeatAxis->setPos(itemPos.heartBeat.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); - } - gasYAxis->setPos(itemPos.partialPressureTissue.pos.on); - gasYAxis->animateChangeLine(itemPos.partialPressureTissue.expanded); - - } else if (PP_GRAPHS_ENABLED || prefs.hrgraph || prefs.percentagegraph) { - profileYAxis->animateChangeLine(itemPos.depth.intermediate); - temperatureAxis->setPos(itemPos.temperature.pos.on); - temperatureAxis->animateChangeLine(itemPos.temperature.intermediate); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.intermediate); - if (prefs.tankbar) { - percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); - percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); - gasYAxis->setPos(itemPos.partialPressureWithTankBar.pos.on); - gasYAxis->setLine(itemPos.partialPressureWithTankBar.expanded); - heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); - } else { - gasYAxis->setPos(itemPos.partialPressure.pos.on); - gasYAxis->animateChangeLine(itemPos.partialPressure.expanded); - percentageAxis->setPos(itemPos.percentage.pos.on); - percentageAxis->setLine(itemPos.percentage.expanded); - heartBeatAxis->setPos(itemPos.heartBeat.pos.on); - heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); - } - } else { - profileYAxis->animateChangeLine(itemPos.depth.expanded); - if (prefs.tankbar) { - temperatureAxis->setPos(itemPos.temperatureAll.pos.on); - } else { - temperatureAxis->setPos(itemPos.temperature.pos.on); - } - temperatureAxis->animateChangeLine(itemPos.temperature.expanded); - cylinderPressureAxis->animateChangeLine(itemPos.cylinder.expanded); - } - pn2GasItem->setVisible(prefs.pp_graphs.pn2); - po2GasItem->setVisible(prefs.pp_graphs.po2); - pheGasItem->setVisible(prefs.pp_graphs.phe); - - bool setpointflag = current_dive && (current_dc->divemode == CCR) && prefs.pp_graphs.po2; - bool sensorflag = setpointflag && prefs.show_ccr_sensors; - o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint); - ccrsensor1GasItem->setVisible(sensorflag); - ccrsensor2GasItem->setVisible(sensorflag && (current_dc->no_o2sensors > 1)); - ccrsensor3GasItem->setVisible(sensorflag && (current_dc->no_o2sensors > 2)); - - timeAxis->setPos(itemPos.time.pos.on); - timeAxis->setLine(itemPos.time.expanded); - - cylinderPressureAxis->setPos(itemPos.cylinder.pos.on); - heartBeatItem->setVisible(prefs.hrgraph); - meanDepthItem->setVisible(prefs.show_average_depth); - - diveComputerText->setVisible(true); - diveComputerText->setPos(itemPos.dcLabel.on); - - diveCeiling->setVisible(prefs.calcceiling); - gradientFactor->setVisible(prefs.calcceiling); - reportedCeiling->setVisible(prefs.dcceiling); - - if (prefs.calcalltissues) { - Q_FOREACH (DiveCalculatedTissue *tissue, allTissues) { - tissue->setVisible(true); - } - } - - if (prefs.percentagegraph) { - Q_FOREACH (DivePercentageItem *percentage, allPercentages) { - percentage->setVisible(true); - } - - ambPressureItem->setVisible(true); - gflineItem->setVisible(true); - } - - rulerItem->setVisible(prefs.rulergraph); - tankItem->setVisible(prefs.tankbar); - tankItem->setPos(itemPos.tankBar.on); - -#define HIDE_ALL(TYPE, CONTAINER) \ - Q_FOREACH (TYPE *item, CONTAINER) item->setVisible(false); - HIDE_ALL(DiveHandler, handles); - HIDE_ALL(QGraphicsSimpleTextItem, gases); -#undef HIDE_ALL - mouseFollowerHorizontal->setVisible(false); - mouseFollowerVertical->setVisible(false); -} - -void ProfileWidget2::clearHandlers() -{ - if (handles.count()) { - foreach (DiveHandler *handle, handles) { - scene()->removeItem(handle); - delete handle; - } - handles.clear(); - } -} - -void ProfileWidget2::setToolTipVisibile(bool visible) -{ - toolTipItem->setVisible(visible); -} - -void ProfileWidget2::setAddState() -{ - if (currentState == ADD) - return; - - clearHandlers(); - setProfileState(); - mouseFollowerHorizontal->setVisible(true); - mouseFollowerVertical->setVisible(true); - mouseFollowerHorizontal->setLine(timeAxis->line()); - mouseFollowerVertical->setLine(QLineF(0, profileYAxis->pos().y(), 0, timeAxis->pos().y())); - disconnectTemporaryConnections(); - //TODO: Move this method to another place, shouldn't be on mainwindow. - MainWindow::instance()->disableShortcuts(false); - actionsForKeys[Qt::Key_Left]->setShortcut(Qt::Key_Left); - actionsForKeys[Qt::Key_Right]->setShortcut(Qt::Key_Right); - actionsForKeys[Qt::Key_Up]->setShortcut(Qt::Key_Up); - actionsForKeys[Qt::Key_Down]->setShortcut(Qt::Key_Down); - actionsForKeys[Qt::Key_Escape]->setShortcut(Qt::Key_Escape); - actionsForKeys[Qt::Key_Delete]->setShortcut(Qt::Key_Delete); - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - connect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot())); - connect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot())); - connect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(pointInserted(const QModelIndex &, int, int))); - connect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(pointsRemoved(const QModelIndex &, int, int))); - /* show the same stuff that the profile shows. */ - currentState = ADD; /* enable the add state. */ - diveCeiling->setVisible(true); - gradientFactor->setVisible(true); - setBackgroundBrush(QColor("#A7DCFF")); -} - -void ProfileWidget2::setPlanState() -{ - if (currentState == PLAN) - return; - - setProfileState(); - mouseFollowerHorizontal->setVisible(true); - mouseFollowerVertical->setVisible(true); - mouseFollowerHorizontal->setLine(timeAxis->line()); - mouseFollowerVertical->setLine(QLineF(0, profileYAxis->pos().y(), 0, timeAxis->pos().y())); - disconnectTemporaryConnections(); - //TODO: Move this method to another place, shouldn't be on mainwindow. - MainWindow::instance()->disableShortcuts(); - actionsForKeys[Qt::Key_Left]->setShortcut(Qt::Key_Left); - actionsForKeys[Qt::Key_Right]->setShortcut(Qt::Key_Right); - actionsForKeys[Qt::Key_Up]->setShortcut(Qt::Key_Up); - actionsForKeys[Qt::Key_Down]->setShortcut(Qt::Key_Down); - actionsForKeys[Qt::Key_Escape]->setShortcut(Qt::Key_Escape); - actionsForKeys[Qt::Key_Delete]->setShortcut(Qt::Key_Delete); - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - connect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot())); - connect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot())); - connect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(pointInserted(const QModelIndex &, int, int))); - connect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(pointsRemoved(const QModelIndex &, int, int))); - /* show the same stuff that the profile shows. */ - currentState = PLAN; /* enable the add state. */ - diveCeiling->setVisible(true); - gradientFactor->setVisible(true); - setBackgroundBrush(QColor("#D7E3EF")); -} - -extern struct ev_select *ev_namelist; -extern int evn_allocated; -extern int evn_used; - -bool ProfileWidget2::isPlanner() -{ - return currentState == PLAN; -} - -bool ProfileWidget2::isAddOrPlanner() -{ - return currentState == PLAN || currentState == ADD; -} - -struct plot_data *ProfileWidget2::getEntryFromPos(QPointF pos) -{ - // find the time stamp corresponding to the mouse position - int seconds = timeAxis->valueAt(pos); - struct plot_data *entry = NULL; - - for (int i = 0; i < plotInfo.nr; i++) { - entry = plotInfo.entry + i; - if (entry->sec >= seconds) - break; - } - return entry; -} - -void ProfileWidget2::setReplot(bool state) -{ - replotEnabled = state; -} - -void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) -{ - if (currentState == ADD || currentState == PLAN) { - QGraphicsView::contextMenuEvent(event); - return; - } - QMenu m; - bool isDCName = false; - if (selected_dive == -1) - return; - // figure out if we are ontop of the dive computer name in the profile - QGraphicsItem *sceneItem = itemAt(mapFromGlobal(event->globalPos())); - if (sceneItem) { - QGraphicsItem *parentItem = sceneItem; - while (parentItem) { - if (parentItem->data(SUBSURFACE_OBJ_DATA) == SUBSURFACE_OBJ_DC_TEXT) { - isDCName = true; - break; - } - parentItem = parentItem->parentItem(); - } - if (isDCName) { - if (dc_number == 0 && count_divecomputers() == 1) - // nothing to do, can't delete or reorder - return; - // create menu to show when right clicking on dive computer name - if (dc_number > 0) - m.addAction(tr("Make first divecomputer"), this, SLOT(makeFirstDC())); - if (count_divecomputers() > 1) - m.addAction(tr("Delete this divecomputer"), this, SLOT(deleteCurrentDC())); - m.exec(event->globalPos()); - // don't show the regular profile context menu - return; - } - } - // create the profile context menu - QPointF scenePos = mapToScene(event->pos()); - struct plot_data *entry = getEntryFromPos(scenePos); - GasSelectionModel *model = GasSelectionModel::instance(); - model->repopulate(); - int rowCount = model->rowCount(); - if (rowCount > 1) { - // if we have more than one gas, offer to switch to another one - QMenu *gasChange = m.addMenu(tr("Add gas change")); - for (int i = 0; i < rowCount; i++) { - QAction *action = new QAction(&m); - action->setText(model->data(model->index(i, 0), Qt::DisplayRole).toString() + QString(tr(" (Tank %1)")).arg(i + 1)); - connect(action, SIGNAL(triggered(bool)), this, SLOT(changeGas())); - action->setData(event->globalPos()); - if (i == entry->cylinderindex) - action->setDisabled(true); - gasChange->addAction(action); - } - } - QAction *setpointAction = m.addAction(tr("Add set-point change"), this, SLOT(addSetpointChange())); - setpointAction->setData(event->globalPos()); - QAction *action = m.addAction(tr("Add bookmark"), this, SLOT(addBookmark())); - action->setData(event->globalPos()); - - if (same_string(current_dc->model, "manually added dive")) - QAction *editProfileAction = m.addAction(tr("Edit the profile"), MainWindow::instance(), SLOT(editCurrentDive())); - - if (DiveEventItem *item = dynamic_cast(sceneItem)) { - action = new QAction(&m); - action->setText(tr("Remove event")); - action->setData(QVariant::fromValue(item)); // so we know what to remove. - connect(action, SIGNAL(triggered(bool)), this, SLOT(removeEvent())); - m.addAction(action); - action = new QAction(&m); - action->setText(tr("Hide similar events")); - action->setData(QVariant::fromValue(item)); - connect(action, SIGNAL(triggered(bool)), this, SLOT(hideEvents())); - m.addAction(action); - struct event *dcEvent = item->getEvent(); - if (dcEvent->type == SAMPLE_EVENT_BOOKMARK) { - action = new QAction(&m); - action->setText(tr("Edit name")); - action->setData(QVariant::fromValue(item)); - connect(action, SIGNAL(triggered(bool)), this, SLOT(editName())); - m.addAction(action); - } -#if 0 // FIXME::: FINISH OR DISABLE - // this shows how to figure out if we should ask the user if they want adjust interpolated pressures - // at either side of a gas change - if (dcEvent->type == SAMPLE_EVENT_GASCHANGE || dcEvent->type == SAMPLE_EVENT_GASCHANGE2) { - qDebug() << "figure out if there are interpolated pressures"; - struct plot_data *gasChangeEntry = entry; - struct plot_data *newGasEntry; - while (gasChangeEntry > plotInfo.entry) { - --gasChangeEntry; - if (gasChangeEntry->sec <= dcEvent->time.seconds) - break; - } - qDebug() << "at gas change at" << gasChangeEntry->sec << ": sensor pressure" << gasChangeEntry->pressure[0] << "interpolated" << gasChangeEntry->pressure[1]; - // now gasChangeEntry points at the gas change, that entry has the final pressure of - // the old tank, the next entry has the starting pressure of the next tank - if (gasChangeEntry + 1 <= plotInfo.entry + plotInfo.nr) { - newGasEntry = gasChangeEntry + 1; - qDebug() << "after gas change at " << newGasEntry->sec << ": sensor pressure" << newGasEntry->pressure[0] << "interpolated" << newGasEntry->pressure[1]; - if (SENSOR_PRESSURE(gasChangeEntry) == 0 || displayed_dive.cylinder[gasChangeEntry->cylinderindex].sample_start.mbar == 0) { - // if we have no sensorpressure or if we have no pressure from samples we can assume that - // we only have interpolated pressure (the pressure in the entry may be stored in the sensor - // pressure field if this is the first or last entry for this tank... see details in gaspressures.c - pressure_t pressure; - pressure.mbar = INTERPOLATED_PRESSURE(gasChangeEntry) ? : SENSOR_PRESSURE(gasChangeEntry); - QAction *adjustOldPressure = m.addAction(tr("Adjust pressure of tank %1 (currently interpolated as %2)") - .arg(gasChangeEntry->cylinderindex + 1).arg(get_pressure_string(pressure))); - } - if (SENSOR_PRESSURE(newGasEntry) == 0 || displayed_dive.cylinder[newGasEntry->cylinderindex].sample_start.mbar == 0) { - // we only have interpolated press -- see commend above - pressure_t pressure; - pressure.mbar = INTERPOLATED_PRESSURE(newGasEntry) ? : SENSOR_PRESSURE(newGasEntry); - QAction *adjustOldPressure = m.addAction(tr("Adjust pressure of tank %1 (currently interpolated as %2)") - .arg(newGasEntry->cylinderindex + 1).arg(get_pressure_string(pressure))); - } - } - } -#endif - } - bool some_hidden = false; - for (int i = 0; i < evn_used; i++) { - if (ev_namelist[i].plot_ev == false) { - some_hidden = true; - break; - } - } - if (some_hidden) { - action = m.addAction(tr("Unhide all events"), this, SLOT(unhideEvents())); - action->setData(event->globalPos()); - } - m.exec(event->globalPos()); -} - -void ProfileWidget2::deleteCurrentDC() -{ - delete_current_divecomputer(); - mark_divelist_changed(true); - // we need to force it since it's likely the same dive and same dc_number - but that's a different dive computer now - MainWindow::instance()->graphics()->plotDive(0, true); - MainWindow::instance()->refreshDisplay(); -} - -void ProfileWidget2::makeFirstDC() -{ - make_first_dc(); - mark_divelist_changed(true); - // this is now the first DC, so we need to redraw the profile and refresh the dive list - // (and no, it's not just enough to rewrite the text - the first DC is special so values in the - // dive list may change). - // As a side benefit, this returns focus to the dive list. - dc_number = 0; - MainWindow::instance()->refreshDisplay(); -} - -void ProfileWidget2::hideEvents() -{ - QAction *action = qobject_cast(sender()); - DiveEventItem *item = static_cast(action->data().value()); - struct event *event = item->getEvent(); - - if (QMessageBox::question(MainWindow::instance(), - TITLE_OR_TEXT(tr("Hide events"), tr("Hide all %1 events?").arg(event->name)), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { - if (!same_string(event->name, "")) { - for (int i = 0; i < evn_used; i++) { - if (same_string(event->name, ev_namelist[i].ev_name)) { - ev_namelist[i].plot_ev = false; - break; - } - } - Q_FOREACH (DiveEventItem *evItem, eventItems) { - if (same_string(evItem->getEvent()->name, event->name)) - evItem->hide(); - } - } else { - item->hide(); - } - } -} - -void ProfileWidget2::unhideEvents() -{ - for (int i = 0; i < evn_used; i++) { - ev_namelist[i].plot_ev = true; - } - Q_FOREACH (DiveEventItem *item, eventItems) - item->show(); -} - -void ProfileWidget2::removeEvent() -{ - QAction *action = qobject_cast(sender()); - DiveEventItem *item = static_cast(action->data().value()); - struct event *event = item->getEvent(); - - if (QMessageBox::question(MainWindow::instance(), TITLE_OR_TEXT( - tr("Remove the selected event?"), - tr("%1 @ %2:%3").arg(event->name).arg(event->time.seconds / 60).arg(event->time.seconds % 60, 2, 10, QChar('0'))), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { - remove_event(event); - mark_divelist_changed(true); - replot(); - } -} - -void ProfileWidget2::addBookmark() -{ - QAction *action = qobject_cast(sender()); - QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); - add_event(current_dc, timeAxis->valueAt(scenePos), SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark"); - mark_divelist_changed(true); - replot(); -} - -void ProfileWidget2::addSetpointChange() -{ - QAction *action = qobject_cast(sender()); - QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); - SetpointDialog::instance()->setpointData(current_dc, timeAxis->valueAt(scenePos)); - SetpointDialog::instance()->show(); -} - -void ProfileWidget2::changeGas() -{ - QAction *action = qobject_cast(sender()); - QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); - QString gas = action->text(); - gas.remove(QRegExp(" \\(.*\\)")); - - // backup the things on the dataModel, since we will clear that out. - struct gasmix gasmix; - qreal sec_val = timeAxis->valueAt(scenePos); - - // no gas changes before the dive starts - unsigned int seconds = (sec_val < 0.0) ? 0 : (unsigned int)sec_val; - - // if there is a gas change at this time stamp, remove it before adding the new one - struct event *gasChangeEvent = current_dc->events; - while ((gasChangeEvent = get_next_event(gasChangeEvent, "gaschange")) != NULL) { - if (gasChangeEvent->time.seconds == seconds) { - remove_event(gasChangeEvent); - gasChangeEvent = current_dc->events; - } else { - gasChangeEvent = gasChangeEvent->next; - } - } - validate_gas(gas.toUtf8().constData(), &gasmix); - QRegExp rx("\\(\\D*(\\d+)"); - int tank; - if (rx.indexIn(action->text()) > -1) { - tank = rx.cap(1).toInt() - 1; // we display the tank 1 based - } else { - qDebug() << "failed to parse tank number"; - tank = get_gasidx(&displayed_dive, &gasmix); - } - // add this both to the displayed dive and the current dive - add_gas_switch_event(current_dive, current_dc, seconds, tank); - add_gas_switch_event(&displayed_dive, get_dive_dc(&displayed_dive, dc_number), seconds, tank); - // this means we potentially have a new tank that is being used and needs to be shown - fixup_dive(&displayed_dive); - - // FIXME - this no longer gets written to the dive list - so we need to enableEdition() here - - MainWindow::instance()->information()->updateDiveInfo(); - mark_divelist_changed(true); - replot(); -} - -bool ProfileWidget2::getPrintMode() -{ - return printMode; -} - -void ProfileWidget2::setPrintMode(bool mode, bool grayscale) -{ - printMode = mode; - resetZoom(); - - // set printMode for axes - profileYAxis->setPrintMode(mode); - gasYAxis->setPrintMode(mode); - temperatureAxis->setPrintMode(mode); - timeAxis->setPrintMode(mode); - cylinderPressureAxis->setPrintMode(mode); - heartBeatAxis->setPrintMode(mode); - percentageAxis->setPrintMode(mode); - - isGrayscale = mode ? grayscale : false; - mouseFollowerHorizontal->setVisible(!mode); - mouseFollowerVertical->setVisible(!mode); -} - -void ProfileWidget2::setFontPrintScale(double scale) -{ - fontPrintScale = scale; - emit fontPrintScaleChanged(scale); -} - -double ProfileWidget2::getFontPrintScale() -{ - if (printMode) - return fontPrintScale; - else - return 1.0; -} - -void ProfileWidget2::editName() -{ - QAction *action = qobject_cast(sender()); - DiveEventItem *item = static_cast(action->data().value()); - struct event *event = item->getEvent(); - bool ok; - QString newName = QInputDialog::getText(MainWindow::instance(), tr("Edit name of bookmark"), - tr("Custom name:"), QLineEdit::Normal, - event->name, &ok); - if (ok && !newName.isEmpty()) { - if (newName.length() > 22) { //longer names will display as garbage. - QMessageBox lengthWarning; - lengthWarning.setText(tr("Name is too long!")); - lengthWarning.exec(); - return; - } - // order is important! first update the current dive (by matching the unchanged event), - // then update the displayed dive (as event is part of the events on displayed dive - // and will be freed as part of changing the name! - update_event_name(current_dive, event, newName.toUtf8().data()); - update_event_name(&displayed_dive, event, newName.toUtf8().data()); - mark_divelist_changed(true); - replot(); - } -} - -void ProfileWidget2::disconnectTemporaryConnections() -{ - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - disconnect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot())); - disconnect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot())); - - disconnect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(pointInserted(const QModelIndex &, int, int))); - disconnect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(pointsRemoved(const QModelIndex &, int, int))); - - Q_FOREACH (QAction *action, actionsForKeys.values()) { - action->setShortcut(QKeySequence()); - action->setShortcutContext(Qt::WidgetShortcut); - } -} - -void ProfileWidget2::pointInserted(const QModelIndex &parent, int start, int end) -{ - DiveHandler *item = new DiveHandler(); - scene()->addItem(item); - handles << item; - - connect(item, SIGNAL(moved()), this, SLOT(recreatePlannedDive())); - connect(item, SIGNAL(clicked()), this, SLOT(divePlannerHandlerClicked())); - connect(item, SIGNAL(released()), this, SLOT(divePlannerHandlerReleased())); - QGraphicsSimpleTextItem *gasChooseBtn = new QGraphicsSimpleTextItem(); - scene()->addItem(gasChooseBtn); - gasChooseBtn->setZValue(10); - gasChooseBtn->setFlag(QGraphicsItem::ItemIgnoresTransformations); - gases << gasChooseBtn; - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - if (plannerModel->recalcQ()) - replot(); -} - -void ProfileWidget2::pointsRemoved(const QModelIndex &, int start, int end) -{ // start and end are inclusive. - int num = (end - start) + 1; - for (int i = num; i != 0; i--) { - delete handles.back(); - handles.pop_back(); - delete gases.back(); - gases.pop_back(); - } - scene()->clearSelection(); - replot(); -} - -void ProfileWidget2::repositionDiveHandlers() -{ - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - // Re-position the user generated dive handlers - struct gasmix mix, lastmix; - for (int i = 0; i < plannerModel->rowCount(); i++) { - struct divedatapoint datapoint = plannerModel->at(i); - if (datapoint.time == 0) // those are the magic entries for tanks - continue; - DiveHandler *h = handles.at(i); - h->setVisible(datapoint.entered); - h->setPos(timeAxis->posAtValue(datapoint.time), profileYAxis->posAtValue(datapoint.depth)); - QPointF p1; - if (i == 0) { - if (prefs.drop_stone_mode) - // place the text on the straight line from the drop to stone position - p1 = QPointF(timeAxis->posAtValue(datapoint.depth / prefs.descrate), - profileYAxis->posAtValue(datapoint.depth)); - else - // place the text on the straight line from the origin to the first position - p1 = QPointF(timeAxis->posAtValue(0), profileYAxis->posAtValue(0)); - } else { - // place the text on the line from the last position - p1 = handles[i - 1]->pos(); - } - QPointF p2 = handles[i]->pos(); - QLineF line(p1, p2); - QPointF pos = line.pointAt(0.5); - gases[i]->setPos(pos); - gases[i]->setText(get_divepoint_gas_string(datapoint)); - gases[i]->setVisible(datapoint.entered && - (i == 0 || gases[i]->text() != gases[i-1]->text())); - } -} - -int ProfileWidget2::fixHandlerIndex(DiveHandler *activeHandler) -{ - int index = handles.indexOf(activeHandler); - if (index > 0 && index < handles.count() - 1) { - DiveHandler *before = handles[index - 1]; - if (before->pos().x() > activeHandler->pos().x()) { - handles.swap(index, index - 1); - return index - 1; - } - DiveHandler *after = handles[index + 1]; - if (after->pos().x() < activeHandler->pos().x()) { - handles.swap(index, index + 1); - return index + 1; - } - } - return index; -} - -void ProfileWidget2::recreatePlannedDive() -{ - DiveHandler *activeHandler = qobject_cast(sender()); - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - int index = fixHandlerIndex(activeHandler); - int mintime = 0, maxtime = (timeAxis->maximum() + 10) * 60; - if (index > 0) - mintime = plannerModel->at(index - 1).time; - if (index < plannerModel->size() - 1) - maxtime = plannerModel->at(index + 1).time; - - int minutes = rint(timeAxis->valueAt(activeHandler->pos()) / 60); - if (minutes * 60 <= mintime || minutes * 60 >= maxtime) - return; - - divedatapoint data = plannerModel->at(index); - data.depth = rint(profileYAxis->valueAt(activeHandler->pos()) / M_OR_FT(1, 1)) * M_OR_FT(1, 1); - data.time = rint(timeAxis->valueAt(activeHandler->pos())); - - plannerModel->editStop(index, data); -} - -void ProfileWidget2::keyDownAction() -{ - if (currentState != ADD && currentState != PLAN) - return; - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { - if (DiveHandler *handler = qgraphicsitem_cast(i)) { - int row = handles.indexOf(handler); - divedatapoint dp = plannerModel->at(row); - if (dp.depth >= profileYAxis->maximum()) - continue; - - dp.depth += M_OR_FT(1, 5); - plannerModel->editStop(row, dp); - } - } -} - -void ProfileWidget2::keyUpAction() -{ - if (currentState != ADD && currentState != PLAN) - return; - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { - if (DiveHandler *handler = qgraphicsitem_cast(i)) { - int row = handles.indexOf(handler); - divedatapoint dp = plannerModel->at(row); - - if (dp.depth <= 0) - continue; - - dp.depth -= M_OR_FT(1, 5); - plannerModel->editStop(row, dp); - } - } -} - -void ProfileWidget2::keyLeftAction() -{ - if (currentState != ADD && currentState != PLAN) - return; - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { - if (DiveHandler *handler = qgraphicsitem_cast(i)) { - int row = handles.indexOf(handler); - divedatapoint dp = plannerModel->at(row); - - if (dp.time / 60 <= 0) - continue; - - // don't overlap positions. - // maybe this is a good place for a 'goto'? - double xpos = timeAxis->posAtValue((dp.time - 60) / 60); - bool nextStep = false; - Q_FOREACH (DiveHandler *h, handles) { - if (IS_FP_SAME(h->pos().x(), xpos)) { - nextStep = true; - break; - } - } - if (nextStep) - continue; - - dp.time -= 60; - plannerModel->editStop(row, dp); - } - } -} - -void ProfileWidget2::keyRightAction() -{ - if (currentState != ADD && currentState != PLAN) - return; - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { - if (DiveHandler *handler = qgraphicsitem_cast(i)) { - int row = handles.indexOf(handler); - divedatapoint dp = plannerModel->at(row); - if (dp.time / 60.0 >= timeAxis->maximum()) - continue; - - // don't overlap positions. - // maybe this is a good place for a 'goto'? - double xpos = timeAxis->posAtValue((dp.time + 60) / 60); - bool nextStep = false; - Q_FOREACH (DiveHandler *h, handles) { - if (IS_FP_SAME(h->pos().x(), xpos)) { - nextStep = true; - break; - } - } - if (nextStep) - continue; - - dp.time += 60; - plannerModel->editStop(row, dp); - } - } -} - -void ProfileWidget2::keyDeleteAction() -{ - if (currentState != ADD && currentState != PLAN) - return; - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - int selCount = scene()->selectedItems().count(); - if (selCount) { - QVector selectedIndexes; - Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { - if (DiveHandler *handler = qgraphicsitem_cast(i)) { - selectedIndexes.push_back(handles.indexOf(handler)); - handler->hide(); - } - } - plannerModel->removeSelectedPoints(selectedIndexes); - } -} - -void ProfileWidget2::keyEscAction() -{ - if (currentState != ADD && currentState != PLAN) - return; - - if (scene()->selectedItems().count()) { - scene()->clearSelection(); - return; - } - - DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); - if (plannerModel->isPlanner()) - plannerModel->cancelPlan(); -} - -void ProfileWidget2::plotPictures() -{ - Q_FOREACH (DivePictureItem *item, pictures) { - item->hide(); - item->deleteLater(); - } - pictures.clear(); - - if (printMode) - return; - - double x, y, lastX = -1.0, lastY = -1.0; - DivePictureModel *m = DivePictureModel::instance(); - for (int i = 0; i < m->rowCount(); i++) { - int offsetSeconds = m->index(i, 1).data(Qt::UserRole).value(); - // it's a correct picture, but doesn't have a timestamp: only show on the widget near the - // information area. - if (!offsetSeconds) - continue; - DivePictureItem *item = new DivePictureItem(); - item->setPixmap(m->index(i, 0).data(Qt::DecorationRole).value()); - item->setFileUrl(m->index(i, 1).data().toString()); - // let's put the picture at the correct time, but at a fixed "depth" on the profile - // not sure this is ideal, but it seems to look right. - x = timeAxis->posAtValue(offsetSeconds); - if (i == 0) - y = 10; - else if (fabs(x - lastX) < 4) - y = lastY + 3; - else - y = 10; - lastX = x; - lastY = y; - item->setPos(x, y); - scene()->addItem(item); - pictures.push_back(item); - } -} diff --git a/desktop-widgets/profile/profilewidget2.h b/desktop-widgets/profile/profilewidget2.h deleted file mode 100644 index f11ec5be1..000000000 --- a/desktop-widgets/profile/profilewidget2.h +++ /dev/null @@ -1,211 +0,0 @@ -#ifndef PROFILEWIDGET2_H -#define PROFILEWIDGET2_H - -#include - -// /* The idea of this widget is to display and edit the profile. -// * It has: -// * 1 - ToolTip / Legend item, displays every information of the current mouse position on it, plus the legends of the maps. -// * 2 - ToolBox, displays the QActions that are used to do special stuff on the profile ( like activating the plugins. ) -// * 3 - Cartesian Axis for depth ( y ) -// * 4 - Cartesian Axis for Gases ( y ) -// * 5 - Cartesian Axis for Time ( x ) -// * -// * It needs to be dynamic, things should *flow* on it, not just appear / disappear. -// */ -#include "divelineitem.h" -#include "diveprofileitem.h" -#include "display.h" - -class RulerItem2; -struct dive; -struct plot_info; -class ToolTipItem; -class DiveMeanDepth; -class DiveReportedCeiling; -class DiveTextItem; -class TemperatureAxis; -class DiveEventItem; -class DivePlotDataModel; -class DivePixmapItem; -class DiveRectItem; -class DepthAxis; -class DiveCartesianAxis; -class DiveProfileItem; -class TimeAxis; -class DiveTemperatureItem; -class DiveHeartrateItem; -class PercentageItem; -class DiveGasPressureItem; -class DiveCalculatedCeiling; -class DiveCalculatedTissue; -class PartialPressureGasItem; -class PartialGasPressureAxis; -class AbstractProfilePolygonItem; -class TankItem; -class DiveHandler; -class QGraphicsSimpleTextItem; -class QModelIndex; -class DivePictureItem; - -class ProfileWidget2 : public QGraphicsView { - Q_OBJECT -public: - enum State { - EMPTY, - PROFILE, - EDIT, - ADD, - PLAN, - INVALID - }; - enum Items { - BACKGROUND, - PROFILE_Y_AXIS, - GAS_Y_AXIS, - TIME_AXIS, - DEPTH_CONTROLLER, - TIME_CONTROLLER, - COLUMNS - }; - - ProfileWidget2(QWidget *parent = 0); - void resetZoom(); - void plotDive(struct dive *d = 0, bool force = false); - virtual bool eventFilter(QObject *, QEvent *); - void setupItem(AbstractProfilePolygonItem *item, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis, DivePlotDataModel *model, int vData, int hData, int zValue); - void setPrintMode(bool mode, bool grayscale = false); - bool getPrintMode(); - bool isPointOutOfBoundaries(const QPointF &point) const; - bool isPlanner(); - bool isAddOrPlanner(); - double getFontPrintScale(); - void setFontPrintScale(double scale); - void clearHandlers(); - void recalcCeiling(); - void setToolTipVisibile(bool visible); - State currentState; - -signals: - void fontPrintScaleChanged(double scale); - -public -slots: // Necessary to call from QAction's signals. - void settingsChanged(); - void setEmptyState(); - void setProfileState(); - void setPlanState(); - void setAddState(); - void changeGas(); - void addSetpointChange(); - void addBookmark(); - void hideEvents(); - void unhideEvents(); - void removeEvent(); - void editName(); - void makeFirstDC(); - void deleteCurrentDC(); - void pointInserted(const QModelIndex &parent, int start, int end); - void pointsRemoved(const QModelIndex &, int start, int end); - void plotPictures(); - void setReplot(bool state); - void replot(dive *d = 0); - - /* this is called for every move on the handlers. maybe we can speed up this a bit? */ - void recreatePlannedDive(); - - /* key press handlers */ - void keyEscAction(); - void keyDeleteAction(); - void keyUpAction(); - void keyDownAction(); - void keyLeftAction(); - void keyRightAction(); - - void divePlannerHandlerClicked(); - void divePlannerHandlerReleased(); - -protected: - virtual ~ProfileWidget2(); - virtual void resizeEvent(QResizeEvent *event); - virtual void wheelEvent(QWheelEvent *event); - virtual void mouseMoveEvent(QMouseEvent *event); - virtual void contextMenuEvent(QContextMenuEvent *event); - virtual void mouseDoubleClickEvent(QMouseEvent *event); - virtual void mousePressEvent(QMouseEvent *event); - virtual void mouseReleaseEvent(QMouseEvent *event); - -private: /*methods*/ - void fixBackgroundPos(); - void scrollViewTo(const QPoint &pos); - void setupSceneAndFlags(); - void setupItemSizes(); - void addItemsToScene(); - void setupItemOnScene(); - void disconnectTemporaryConnections(); - struct plot_data *getEntryFromPos(QPointF pos); - -private: - DivePlotDataModel *dataModel; - int zoomLevel; - qreal zoomFactor; - DivePixmapItem *background; - QString backgroundFile; - ToolTipItem *toolTipItem; - bool isPlotZoomed; - bool replotEnabled; - // All those here should probably be merged into one structure, - // So it's esyer to replicate for more dives later. - // In the meantime, keep it here. - struct plot_info plotInfo; - DepthAxis *profileYAxis; - PartialGasPressureAxis *gasYAxis; - TemperatureAxis *temperatureAxis; - TimeAxis *timeAxis; - DiveProfileItem *diveProfileItem; - DiveTemperatureItem *temperatureItem; - DiveMeanDepthItem *meanDepthItem; - DiveCartesianAxis *cylinderPressureAxis; - DiveGasPressureItem *gasPressureItem; - QList eventItems; - DiveTextItem *diveComputerText; - DiveCalculatedCeiling *diveCeiling; - DiveTextItem *gradientFactor; - QList allTissues; - DiveReportedCeiling *reportedCeiling; - PartialPressureGasItem *pn2GasItem; - PartialPressureGasItem *pheGasItem; - PartialPressureGasItem *po2GasItem; - PartialPressureGasItem *o2SetpointGasItem; - PartialPressureGasItem *ccrsensor1GasItem; - PartialPressureGasItem *ccrsensor2GasItem; - PartialPressureGasItem *ccrsensor3GasItem; - DiveCartesianAxis *heartBeatAxis; - DiveHeartrateItem *heartBeatItem; - DiveCartesianAxis *percentageAxis; - QList allPercentages; - DiveAmbPressureItem *ambPressureItem; - DiveGFLineItem *gflineItem; - DiveLineItem *mouseFollowerVertical; - DiveLineItem *mouseFollowerHorizontal; - RulerItem2 *rulerItem; - TankItem *tankItem; - bool isGrayscale; - bool printMode; - - //specifics for ADD and PLAN - QList handles; - QList gases; - QList pictures; - void repositionDiveHandlers(); - int fixHandlerIndex(DiveHandler *activeHandler); - friend class DiveHandler; - QHash actionsForKeys; - bool shouldCalculateMaxTime; - bool shouldCalculateMaxDepth; - int maxtime; - int maxdepth; - double fontPrintScale; -}; - -#endif // PROFILEWIDGET2_H diff --git a/desktop-widgets/profile/ruleritem.cpp b/desktop-widgets/profile/ruleritem.cpp deleted file mode 100644 index 830985552..000000000 --- a/desktop-widgets/profile/ruleritem.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include "ruleritem.h" -#include "preferences.h" -#include "mainwindow.h" -#include "profilewidget2.h" -#include "display.h" - -#include - -#include "profile.h" - -RulerNodeItem2::RulerNodeItem2() : - entry(NULL), - ruler(NULL), - timeAxis(NULL), - depthAxis(NULL) -{ - memset(&pInfo, 0, sizeof(pInfo)); - setRect(-8, -8, 16, 16); - setBrush(QColor(0xff, 0, 0, 127)); - setPen(QColor(Qt::red)); - setFlag(ItemIsMovable); - setFlag(ItemSendsGeometryChanges); - setFlag(ItemIgnoresTransformations); -} - -void RulerNodeItem2::setPlotInfo(plot_info &info) -{ - pInfo = info; - entry = pInfo.entry; -} - -void RulerNodeItem2::setRuler(RulerItem2 *r) -{ - ruler = r; -} - -void RulerNodeItem2::recalculate() -{ - struct plot_data *data = pInfo.entry + (pInfo.nr - 1); - uint16_t count = 0; - if (x() < 0) { - setPos(0, y()); - } else if (x() > timeAxis->posAtValue(data->sec)) { - setPos(timeAxis->posAtValue(data->sec), depthAxis->posAtValue(data->depth)); - } else { - data = pInfo.entry; - count = 0; - while (timeAxis->posAtValue(data->sec) < x() && count < pInfo.nr) { - data = pInfo.entry + count; - count++; - } - setPos(timeAxis->posAtValue(data->sec), depthAxis->posAtValue(data->depth)); - entry = data; - } -} - -void RulerNodeItem2::mouseMoveEvent(QGraphicsSceneMouseEvent *event) -{ - qreal x = event->scenePos().x(); - if (x < 0.0) - x = 0.0; - setPos(x, event->scenePos().y()); - recalculate(); - ruler->recalculate(); -} - -RulerItem2::RulerItem2() : source(new RulerNodeItem2()), - dest(new RulerNodeItem2()), - timeAxis(NULL), - depthAxis(NULL), - textItemBack(new QGraphicsRectItem(this)), - textItem(new QGraphicsSimpleTextItem(this)) -{ - memset(&pInfo, 0, sizeof(pInfo)); - source->setRuler(this); - dest->setRuler(this); - textItem->setFlag(QGraphicsItem::ItemIgnoresTransformations); - textItemBack->setBrush(QColor(0xff, 0xff, 0xff, 190)); - textItemBack->setPen(QColor(Qt::white)); - textItemBack->setFlag(QGraphicsItem::ItemIgnoresTransformations); - setPen(QPen(QColor(Qt::black), 0.0)); - connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); -} - -void RulerItem2::settingsChanged() -{ - ProfileWidget2 *profWidget = NULL; - if (scene() && scene()->views().count()) - profWidget = qobject_cast(scene()->views().first()); - - if (profWidget && profWidget->currentState == ProfileWidget2::PROFILE) - setVisible(prefs.rulergraph); - else - setVisible(false); -} - -void RulerItem2::recalculate() -{ - char buffer[500]; - QPointF tmp; - QFont font; - QFontMetrics fm(font); - - if (timeAxis == NULL || depthAxis == NULL || pInfo.nr == 0) - return; - - prepareGeometryChange(); - startPoint = mapFromItem(source, 0, 0); - endPoint = mapFromItem(dest, 0, 0); - - if (startPoint.x() > endPoint.x()) { - tmp = endPoint; - endPoint = startPoint; - startPoint = tmp; - } - QLineF line(startPoint, endPoint); - setLine(line); - compare_samples(source->entry, dest->entry, buffer, 500, 1); - text = QString(buffer); - - // draw text - QGraphicsView *view = scene()->views().first(); - QPoint begin = view->mapFromScene(mapToScene(startPoint)); - textItem->setText(text); - qreal tgtX = startPoint.x(); - const qreal diff = begin.x() + textItem->boundingRect().width(); - // clamp so that the text doesn't go out of the screen to the right - if (diff > view->width()) { - begin.setX(begin.x() - (diff - view->width())); - tgtX = mapFromScene(view->mapToScene(begin)).x(); - } - // always show the text bellow the lowest of the start and end points - qreal tgtY = (startPoint.y() >= endPoint.y()) ? startPoint.y() : endPoint.y(); - // this isn't exactly optimal, since we want to scale the 1.0, 4.0 distances as well - textItem->setPos(tgtX - 1.0, tgtY + 4.0); - - // setup the text background - textItemBack->setVisible(startPoint.x() != endPoint.x()); - textItemBack->setPos(textItem->x(), textItem->y()); - textItemBack->setRect(0, 0, textItem->boundingRect().width(), textItem->boundingRect().height()); -} - -RulerNodeItem2 *RulerItem2::sourceNode() const -{ - return source; -} - -RulerNodeItem2 *RulerItem2::destNode() const -{ - return dest; -} - -void RulerItem2::setPlotInfo(plot_info info) -{ - pInfo = info; - dest->setPlotInfo(info); - source->setPlotInfo(info); - dest->recalculate(); - source->recalculate(); - recalculate(); -} - -void RulerItem2::setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth) -{ - timeAxis = time; - depthAxis = depth; - dest->depthAxis = depth; - dest->timeAxis = time; - source->depthAxis = depth; - source->timeAxis = time; - recalculate(); -} - -void RulerItem2::setVisible(bool visible) -{ - QGraphicsLineItem::setVisible(visible); - source->setVisible(visible); - dest->setVisible(visible); -} diff --git a/desktop-widgets/profile/ruleritem.h b/desktop-widgets/profile/ruleritem.h deleted file mode 100644 index 4fad0451c..000000000 --- a/desktop-widgets/profile/ruleritem.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef RULERITEM_H -#define RULERITEM_H - -#include -#include -#include -#include "divecartesianaxis.h" -#include "display.h" - -struct plot_data; -class RulerItem2; - -class RulerNodeItem2 : public QObject, public QGraphicsEllipseItem { - Q_OBJECT - friend class RulerItem2; - -public: - explicit RulerNodeItem2(); - void setRuler(RulerItem2 *r); - void setPlotInfo(struct plot_info &info); - void recalculate(); - -protected: - virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); -private: - struct plot_info pInfo; - struct plot_data *entry; - RulerItem2 *ruler; - DiveCartesianAxis *timeAxis; - DiveCartesianAxis *depthAxis; -}; - -class RulerItem2 : public QObject, public QGraphicsLineItem { - Q_OBJECT -public: - explicit RulerItem2(); - void recalculate(); - - void setPlotInfo(struct plot_info pInfo); - RulerNodeItem2 *sourceNode() const; - RulerNodeItem2 *destNode() const; - void setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth); - void setVisible(bool visible); - -public -slots: - void settingsChanged(); - -private: - struct plot_info pInfo; - QPointF startPoint, endPoint; - RulerNodeItem2 *source, *dest; - QString text; - DiveCartesianAxis *timeAxis; - DiveCartesianAxis *depthAxis; - QGraphicsRectItem *textItemBack; - QGraphicsSimpleTextItem *textItem; -}; -#endif diff --git a/desktop-widgets/profile/tankitem.cpp b/desktop-widgets/profile/tankitem.cpp deleted file mode 100644 index c0e75a371..000000000 --- a/desktop-widgets/profile/tankitem.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "tankitem.h" -#include "diveplotdatamodel.h" -#include "divetextitem.h" -#include "profile.h" -#include - -TankItem::TankItem(QObject *parent) : - QGraphicsRectItem(), - dataModel(0), - pInfoEntry(0), - pInfoNr(0) -{ - height = 3; - QColor red(PERSIANRED1); - QColor blue(AIR_BLUE); - QColor yellow(NITROX_YELLOW); - QColor green(NITROX_GREEN); - QLinearGradient nitroxGradient(QPointF(0, 0), QPointF(0, height)); - nitroxGradient.setColorAt(0.0, green); - nitroxGradient.setColorAt(0.49, green); - nitroxGradient.setColorAt(0.5, yellow); - nitroxGradient.setColorAt(1.0, yellow); - nitrox = nitroxGradient; - oxygen = green; - QLinearGradient trimixGradient(QPointF(0, 0), QPointF(0, height)); - trimixGradient.setColorAt(0.0, green); - trimixGradient.setColorAt(0.49, green); - trimixGradient.setColorAt(0.5, red); - trimixGradient.setColorAt(1.0, red); - trimix = trimixGradient; - air = blue; - memset(&diveCylinderStore, 0, sizeof(diveCylinderStore)); -} - -TankItem::~TankItem() -{ - // Should this be clear_dive(diveCylinderStore)? - for (int i = 0; i < MAX_CYLINDERS; i++) - free((void *)diveCylinderStore.cylinder[i].type.description); -} - -void TankItem::setData(DivePlotDataModel *model, struct plot_info *plotInfo, struct dive *d) -{ - free(pInfoEntry); - // the plotInfo and dive structures passed in could become invalid before we stop using them, - // so copy the data that we need - int size = plotInfo->nr * sizeof(plotInfo->entry[0]); - pInfoEntry = (struct plot_data *)malloc(size); - pInfoNr = plotInfo->nr; - memcpy(pInfoEntry, plotInfo->entry, size); - copy_cylinders(d, &diveCylinderStore, false); - dataModel = model; - connect(dataModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(modelDataChanged(QModelIndex, QModelIndex)), Qt::UniqueConnection); - modelDataChanged(); -} - -void TankItem::createBar(qreal x, qreal w, struct gasmix *gas) -{ - // pick the right gradient, size, position and text - QGraphicsRectItem *rect = new QGraphicsRectItem(x, 0, w, height, this); - if (gasmix_is_air(gas)) - rect->setBrush(air); - else if (gas->he.permille) - rect->setBrush(trimix); - else if (gas->o2.permille == 1000) - rect->setBrush(oxygen); - else - rect->setBrush(nitrox); - rect->setPen(QPen(QBrush(), 0.0)); // get rid of the thick line around the rectangle - rects.push_back(rect); - DiveTextItem *label = new DiveTextItem(rect); - label->setText(gasname(gas)); - label->setBrush(Qt::black); - label->setPos(x + 1, 0); - label->setAlignment(Qt::AlignBottom | Qt::AlignRight); - label->setZValue(101); -} - -void TankItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - // We don't have enougth data to calculate things, quit. - - if (!dataModel || !pInfoEntry || !pInfoNr) - return; - - // remove the old rectangles - foreach (QGraphicsRectItem *r, rects) { - delete(r); - } - rects.clear(); - - // walk the list and figure out which tanks go where - struct plot_data *entry = pInfoEntry; - int cylIdx = entry->cylinderindex; - int i = -1; - int startTime = 0; - struct gasmix *gas = &diveCylinderStore.cylinder[cylIdx].gasmix; - qreal width, left; - while (++i < pInfoNr) { - entry = &pInfoEntry[i]; - if (entry->cylinderindex == cylIdx) - continue; - width = hAxis->posAtValue(entry->sec) - hAxis->posAtValue(startTime); - left = hAxis->posAtValue(startTime); - createBar(left, width, gas); - cylIdx = entry->cylinderindex; - gas = &diveCylinderStore.cylinder[cylIdx].gasmix; - startTime = entry->sec; - } - width = hAxis->posAtValue(entry->sec) - hAxis->posAtValue(startTime); - left = hAxis->posAtValue(startTime); - createBar(left, width, gas); -} - -void TankItem::setHorizontalAxis(DiveCartesianAxis *horizontal) -{ - hAxis = horizontal; - connect(hAxis, SIGNAL(sizeChanged()), this, SLOT(modelDataChanged())); - modelDataChanged(); -} diff --git a/desktop-widgets/profile/tankitem.h b/desktop-widgets/profile/tankitem.h deleted file mode 100644 index fd685fc82..000000000 --- a/desktop-widgets/profile/tankitem.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef TANKITEM_H -#define TANKITEM_H - -#include -#include -#include -#include "divelineitem.h" -#include "divecartesianaxis.h" -#include "dive.h" - -class TankItem : public QObject, public QGraphicsRectItem -{ - Q_OBJECT - -public: - explicit TankItem(QObject *parent = 0); - ~TankItem(); - void setHorizontalAxis(DiveCartesianAxis *horizontal); - void setData(DivePlotDataModel *model, struct plot_info *plotInfo, struct dive *d); - -signals: - -public slots: - virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); - -private: - void createBar(qreal x, qreal w, struct gasmix *gas); - DivePlotDataModel *dataModel; - DiveCartesianAxis *hAxis; - int hDataColumn; - struct dive diveCylinderStore; - struct plot_data *pInfoEntry; - int pInfoNr; - qreal height; - QBrush air, nitrox, oxygen, trimix; - QList rects; -}; - -#endif // TANKITEM_H diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index 62a9cc646..43ad1ddc0 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -14,7 +14,7 @@ #include "libdivecomputer/parser.h" #include "divelistview.h" #include "display.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "undocommands.h" class MinMaxAvgWidgetPrivate { diff --git a/desktop-widgets/socialnetworks.cpp b/desktop-widgets/socialnetworks.cpp index 6e191267a..0794c764e 100644 --- a/desktop-widgets/socialnetworks.cpp +++ b/desktop-widgets/socialnetworks.cpp @@ -16,7 +16,7 @@ #include #include #include "mainwindow.h" -#include "profile/profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "pref.h" #include "helpers.h" #include "ui_socialnetworksdialog.h" diff --git a/profile-widget/CMakeLists.txt b/profile-widget/CMakeLists.txt new file mode 100644 index 000000000..f0a1d8439 --- /dev/null +++ b/profile-widget/CMakeLists.txt @@ -0,0 +1,19 @@ +# the profile widget +set(SUBSURFACE_PROFILE_LIB_SRCS + profilewidget2.cpp + diverectitem.cpp + divepixmapitem.cpp + divelineitem.cpp + divetextitem.cpp + animationfunctions.cpp + divecartesianaxis.cpp + diveprofileitem.cpp + diveeventitem.cpp + divetooltipitem.cpp + ruleritem.cpp + tankitem.cpp +) +source_group("Subsurface Profile" FILES ${SUBSURFACE_PROFILE_LIB_SRCS}) + +add_library(subsurface_profile STATIC ${SUBSURFACE_PROFILE_LIB_SRCS}) +target_link_libraries(subsurface_profile ${QT_LIBRARIES}) \ No newline at end of file diff --git a/profile-widget/animationfunctions.cpp b/profile-widget/animationfunctions.cpp new file mode 100644 index 000000000..a19d50c9d --- /dev/null +++ b/profile-widget/animationfunctions.cpp @@ -0,0 +1,75 @@ +#include "animationfunctions.h" +#include "pref.h" +#include + +namespace Animations { + + void hide(QObject *obj) + { + if (prefs.animation_speed != 0) { + QPropertyAnimation *animation = new QPropertyAnimation(obj, "opacity"); + animation->setStartValue(1); + animation->setEndValue(0); + animation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + obj->setProperty("opacity", 0); + } + } + + void show(QObject *obj) + { + if (prefs.animation_speed != 0) { + QPropertyAnimation *animation = new QPropertyAnimation(obj, "opacity"); + animation->setStartValue(0); + animation->setEndValue(1); + animation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + obj->setProperty("opacity", 1); + } + } + + void animDelete(QObject *obj) + { + if (prefs.animation_speed != 0) { + QPropertyAnimation *animation = new QPropertyAnimation(obj, "opacity"); + obj->connect(animation, SIGNAL(finished()), SLOT(deleteLater())); + animation->setStartValue(1); + animation->setEndValue(0); + animation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + obj->setProperty("opacity", 0); + } + } + + void moveTo(QObject *obj, qreal x, qreal y) + { + if (prefs.animation_speed != 0) { + QPropertyAnimation *animation = new QPropertyAnimation(obj, "pos"); + animation->setDuration(prefs.animation_speed); + animation->setStartValue(obj->property("pos").toPointF()); + animation->setEndValue(QPointF(x, y)); + animation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + obj->setProperty("pos", QPointF(x, y)); + } + } + + void scaleTo(QObject *obj, qreal scale) + { + if (prefs.animation_speed != 0) { + QPropertyAnimation *animation = new QPropertyAnimation(obj, "scale"); + animation->setDuration(prefs.animation_speed); + animation->setStartValue(obj->property("scale").toReal()); + animation->setEndValue(QVariant::fromValue(scale)); + animation->setEasingCurve(QEasingCurve::InCubic); + animation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + obj->setProperty("scale", QVariant::fromValue(scale)); + } + } + + void moveTo(QObject *obj, const QPointF &pos) + { + moveTo(obj, pos.x(), pos.y()); + } +} diff --git a/profile-widget/animationfunctions.h b/profile-widget/animationfunctions.h new file mode 100644 index 000000000..3cfcff563 --- /dev/null +++ b/profile-widget/animationfunctions.h @@ -0,0 +1,18 @@ +#ifndef ANIMATIONFUNCTIONS_H +#define ANIMATIONFUNCTIONS_H + +#include +#include + +class QObject; + +namespace Animations { + void hide(QObject *obj); + void show(QObject *obj); + void moveTo(QObject *obj, qreal x, qreal y); + void moveTo(QObject *obj, const QPointF &pos); + void animDelete(QObject *obj); + void scaleTo(QObject *obj, qreal scale); +} + +#endif // ANIMATIONFUNCTIONS_H diff --git a/profile-widget/divecartesianaxis.cpp b/profile-widget/divecartesianaxis.cpp new file mode 100644 index 000000000..bf5a5380c --- /dev/null +++ b/profile-widget/divecartesianaxis.cpp @@ -0,0 +1,459 @@ +#include "divecartesianaxis.h" +#include "divetextitem.h" +#include "helpers.h" +#include "preferences.h" +#include "diveplotdatamodel.h" +#include "animationfunctions.h" +#include "mainwindow.h" +#include "divelineitem.h" +#include "profilewidget2.h" + +QPen DiveCartesianAxis::gridPen() +{ + QPen pen; + pen.setColor(getColor(TIME_GRID)); + /* cosmetic width() == 0 for lines in printMode + * having setCosmetic(true) and width() > 0 does not work when + * printing on OSX and Linux */ + pen.setWidth(DiveCartesianAxis::printMode ? 0 : 2); + pen.setCosmetic(true); + return pen; +} + +double DiveCartesianAxis::tickInterval() const +{ + return interval; +} + +double DiveCartesianAxis::tickSize() const +{ + return tick_size; +} + +void DiveCartesianAxis::setFontLabelScale(qreal scale) +{ + labelScale = scale; + changed = true; +} + +void DiveCartesianAxis::setPrintMode(bool mode) +{ + printMode = mode; + // update the QPen of all lines depending on printMode + QPen newPen = gridPen(); + QColor oldColor = pen().brush().color(); + newPen.setBrush(oldColor); + setPen(newPen); + Q_FOREACH (DiveLineItem *item, lines) + item->setPen(pen()); +} + +void DiveCartesianAxis::setMaximum(double maximum) +{ + if (IS_FP_SAME(max, maximum)) + return; + max = maximum; + changed = true; + emit maxChanged(); +} + +void DiveCartesianAxis::setMinimum(double minimum) +{ + if (IS_FP_SAME(min, minimum)) + return; + min = minimum; + changed = true; +} + +void DiveCartesianAxis::setTextColor(const QColor &color) +{ + textColor = color; +} + +DiveCartesianAxis::DiveCartesianAxis() : QObject(), + QGraphicsLineItem(), + printMode(false), + unitSystem(0), + orientation(LeftToRight), + min(0), + max(0), + interval(1), + tick_size(0), + textVisibility(true), + lineVisibility(true), + labelScale(1.0), + line_size(1), + changed(true) +{ + setPen(gridPen()); +} + +DiveCartesianAxis::~DiveCartesianAxis() +{ +} + +void DiveCartesianAxis::setLineSize(qreal lineSize) +{ + line_size = lineSize; + changed = true; +} + +void DiveCartesianAxis::setOrientation(Orientation o) +{ + orientation = o; + changed = true; +} + +QColor DiveCartesianAxis::colorForValue(double value) +{ + return QColor(Qt::black); +} + +void DiveCartesianAxis::setTextVisible(bool arg1) +{ + if (textVisibility == arg1) { + return; + } + textVisibility = arg1; + Q_FOREACH (DiveTextItem *item, labels) { + item->setVisible(textVisibility); + } +} + +void DiveCartesianAxis::setLinesVisible(bool arg1) +{ + if (lineVisibility == arg1) { + return; + } + lineVisibility = arg1; + Q_FOREACH (DiveLineItem *item, lines) { + item->setVisible(lineVisibility); + } +} + +template +void emptyList(QList &list, double steps) +{ + if (!list.isEmpty() && list.size() > steps) { + while (list.size() > steps) { + T *removedItem = list.takeLast(); + Animations::animDelete(removedItem); + } + } +} + +void DiveCartesianAxis::updateTicks(color_indice_t color) +{ + if (!scene() || (!changed && !MainWindow::instance()->graphics()->getPrintMode())) + return; + QLineF m = line(); + // unused so far: + // QGraphicsView *view = scene()->views().first(); + double steps = (max - min) / interval; + double currValueText = min; + double currValueLine = min; + + if (steps < 1) + return; + + emptyList(labels, steps); + emptyList(lines, steps); + + // Move the remaining Ticks / Text to it's corerct position + // Regartind the possibly new values for the Axis + qreal begin, stepSize; + if (orientation == TopToBottom) { + begin = m.y1(); + stepSize = (m.y2() - m.y1()); + } else if (orientation == BottomToTop) { + begin = m.y2(); + stepSize = (m.y2() - m.y1()); + } else if (orientation == LeftToRight) { + begin = m.x1(); + stepSize = (m.x2() - m.x1()); + } else /* if (orientation == RightToLeft) */ { + begin = m.x2(); + stepSize = (m.x2() - m.x1()); + } + stepSize = stepSize / steps; + + for (int i = 0, count = labels.size(); i < count; i++, currValueText += interval) { + qreal childPos = (orientation == TopToBottom || orientation == LeftToRight) ? + begin + i * stepSize : + begin - i * stepSize; + + labels[i]->setText(textForValue(currValueText)); + if (orientation == LeftToRight || orientation == RightToLeft) { + Animations::moveTo(labels[i],childPos, m.y1() + tick_size); + } else { + Animations::moveTo(labels[i],m.x1() - tick_size, childPos); + } + } + + for (int i = 0, count = lines.size(); i < count; i++, currValueLine += interval) { + qreal childPos = (orientation == TopToBottom || orientation == LeftToRight) ? + begin + i * stepSize : + begin - i * stepSize; + + if (orientation == LeftToRight || orientation == RightToLeft) { + Animations::moveTo(lines[i],childPos, m.y1()); + } else { + Animations::moveTo(lines[i],m.x1(), childPos); + } + } + + // Add's the rest of the needed Ticks / Text. + for (int i = labels.size(); i < steps; i++, currValueText += interval) { + qreal childPos; + if (orientation == TopToBottom || orientation == LeftToRight) { + childPos = begin + i * stepSize; + } else { + childPos = begin - i * stepSize; + } + DiveTextItem *label = new DiveTextItem(this); + label->setText(textForValue(currValueText)); + label->setBrush(colorForValue(currValueText)); + label->setScale(fontLabelScale()); + label->setZValue(1); + labels.push_back(label); + if (orientation == RightToLeft || orientation == LeftToRight) { + label->setAlignment(Qt::AlignBottom | Qt::AlignHCenter); + label->setPos(scene()->sceneRect().width() + 10, m.y1() + tick_size); // position it outside of the scene); + Animations::moveTo(label,childPos, m.y1() + tick_size); + } else { + label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + label->setPos(m.x1() - tick_size, scene()->sceneRect().height() + 10); + Animations::moveTo(label,m.x1() - tick_size, childPos); + } + } + + // Add's the rest of the needed Ticks / Text. + for (int i = lines.size(); i < steps; i++, currValueText += interval) { + qreal childPos; + if (orientation == TopToBottom || orientation == LeftToRight) { + childPos = begin + i * stepSize; + } else { + childPos = begin - i * stepSize; + } + DiveLineItem *line = new DiveLineItem(this); + QPen pen = gridPen(); + pen.setBrush(getColor(color)); + line->setPen(pen); + line->setZValue(0); + lines.push_back(line); + if (orientation == RightToLeft || orientation == LeftToRight) { + line->setLine(0, -line_size, 0, 0); + line->setPos(scene()->sceneRect().width() + 10, m.y1()); // position it outside of the scene); + Animations::moveTo(line,childPos, m.y1()); + } else { + QPointF p1 = mapFromScene(3, 0); + QPointF p2 = mapFromScene(line_size, 0); + line->setLine(p1.x(), 0, p2.x(), 0); + line->setPos(m.x1(), scene()->sceneRect().height() + 10); + Animations::moveTo(line,m.x1(), childPos); + } + } + + Q_FOREACH (DiveTextItem *item, labels) + item->setVisible(textVisibility); + Q_FOREACH (DiveLineItem *item, lines) + item->setVisible(lineVisibility); + changed = false; +} + +void DiveCartesianAxis::setLine(const QLineF &line) +{ + QGraphicsLineItem::setLine(line); + changed = true; +} + +void DiveCartesianAxis::animateChangeLine(const QLineF &newLine) +{ + setLine(newLine); + updateTicks(); + sizeChanged(); +} + +QString DiveCartesianAxis::textForValue(double value) +{ + return QString::number(value); +} + +void DiveCartesianAxis::setTickSize(qreal size) +{ + tick_size = size; +} + +void DiveCartesianAxis::setTickInterval(double i) +{ + interval = i; +} + +qreal DiveCartesianAxis::valueAt(const QPointF &p) const +{ + QLineF m = line(); + QPointF relativePosition = p; + relativePosition -= pos(); // normalize p based on the axis' offset on screen + + double retValue = (orientation == LeftToRight || orientation == RightToLeft) ? + max * (relativePosition.x() - m.x1()) / (m.x2() - m.x1()) : + max * (relativePosition.y() - m.y1()) / (m.y2() - m.y1()); + return retValue; +} + +qreal DiveCartesianAxis::posAtValue(qreal value) +{ + QLineF m = line(); + QPointF p = pos(); + + double size = max - min; + // unused for now: + // double distanceFromOrigin = value - min; + double percent = IS_FP_SAME(min, max) ? 0.0 : (value - min) / size; + + + double realSize = orientation == LeftToRight || orientation == RightToLeft ? + m.x2() - m.x1() : + m.y2() - m.y1(); + + // Inverted axis, just invert the percentage. + if (orientation == RightToLeft || orientation == BottomToTop) + percent = 1 - percent; + + double retValue = realSize * percent; + double adjusted = + orientation == LeftToRight ? retValue + m.x1() + p.x() : + orientation == RightToLeft ? retValue + m.x1() + p.x() : + orientation == TopToBottom ? retValue + m.y1() + p.y() : + /* entation == BottomToTop */ retValue + m.y1() + p.y(); + return adjusted; +} + +qreal DiveCartesianAxis::percentAt(const QPointF &p) +{ + qreal value = valueAt(p); + double size = max - min; + double percent = value / size; + return percent; +} + +double DiveCartesianAxis::maximum() const +{ + return max; +} + +double DiveCartesianAxis::minimum() const +{ + return min; +} + +double DiveCartesianAxis::fontLabelScale() const +{ + return labelScale; +} + +void DiveCartesianAxis::setColor(const QColor &color) +{ + QPen defaultPen = gridPen(); + defaultPen.setColor(color); + defaultPen.setJoinStyle(Qt::RoundJoin); + defaultPen.setCapStyle(Qt::RoundCap); + setPen(defaultPen); +} + +QString DepthAxis::textForValue(double value) +{ + if (value == 0) + return QString(); + return get_depth_string(value, false, false); +} + +QColor DepthAxis::colorForValue(double value) +{ + Q_UNUSED(value); + return QColor(Qt::red); +} + +DepthAxis::DepthAxis() +{ + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); + changed = true; + settingsChanged(); +} + +void DepthAxis::settingsChanged() +{ + static int unitSystem = prefs.units.length; + if ( unitSystem == prefs.units.length ) + return; + changed = true; + updateTicks(); + unitSystem = prefs.units.length; +} + +QColor TimeAxis::colorForValue(double value) +{ + Q_UNUSED(value); + return QColor(Qt::blue); +} + +QString TimeAxis::textForValue(double value) +{ + int nr = value / 60; + if (maximum() < 600) + return QString("%1:%2").arg(nr).arg((int)value % 60, 2, 10, QChar('0')); + return QString::number(nr); +} + +void TimeAxis::updateTicks() +{ + DiveCartesianAxis::updateTicks(); + if (maximum() > 600) { + for (int i = 0; i < labels.count(); i++) { + labels[i]->setVisible(i % 2); + } + } +} + +QString TemperatureAxis::textForValue(double value) +{ + return QString::number(mkelvin_to_C((int)value)); +} + +PartialGasPressureAxis::PartialGasPressureAxis() : + DiveCartesianAxis(), + model(NULL) +{ + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); +} + +void PartialGasPressureAxis::setModel(DivePlotDataModel *m) +{ + model = m; + connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(settingsChanged())); + settingsChanged(); +} + +void PartialGasPressureAxis::settingsChanged() +{ + bool showPhe = prefs.pp_graphs.phe; + bool showPn2 = prefs.pp_graphs.pn2; + bool showPo2 = prefs.pp_graphs.po2; + setVisible(showPhe || showPn2 || showPo2); + if (!model->rowCount()) + return; + + double max = showPhe ? model->pheMax() : -1; + if (showPn2 && model->pn2Max() > max) + max = model->pn2Max(); + if (showPo2 && model->po2Max() > max) + max = model->po2Max(); + + qreal pp = floor(max * 10.0) / 10.0 + 0.2; + if (IS_FP_SAME(maximum(), pp)) + return; + + setMaximum(pp); + setTickInterval(pp > 4 ? 0.5 : 0.25); + updateTicks(); +} diff --git a/profile-widget/divecartesianaxis.h b/profile-widget/divecartesianaxis.h new file mode 100644 index 000000000..cc7d0bcf7 --- /dev/null +++ b/profile-widget/divecartesianaxis.h @@ -0,0 +1,122 @@ +#ifndef DIVECARTESIANAXIS_H +#define DIVECARTESIANAXIS_H + +#include +#include +#include "subsurface-core/color.h" + +class QPropertyAnimation; +class DiveTextItem; +class DiveLineItem; +class DivePlotDataModel; + +class DiveCartesianAxis : public QObject, public QGraphicsLineItem { + Q_OBJECT + Q_PROPERTY(QLineF line WRITE setLine READ line) + Q_PROPERTY(QPointF pos WRITE setPos READ pos) + Q_PROPERTY(qreal x WRITE setX READ x) + Q_PROPERTY(qreal y WRITE setY READ y) +private: + bool printMode; + QPen gridPen(); +public: + enum Orientation { + TopToBottom, + BottomToTop, + LeftToRight, + RightToLeft + }; + DiveCartesianAxis(); + virtual ~DiveCartesianAxis(); + void setPrintMode(bool mode); + void setMinimum(double minimum); + void setMaximum(double maximum); + void setTickInterval(double interval); + void setOrientation(Orientation orientation); + void setTickSize(qreal size); + void setFontLabelScale(qreal scale); + double minimum() const; + double maximum() const; + double tickInterval() const; + double tickSize() const; + double fontLabelScale() const; + qreal valueAt(const QPointF &p) const; + qreal percentAt(const QPointF &p); + qreal posAtValue(qreal value); + void setColor(const QColor &color); + void setTextColor(const QColor &color); + void animateChangeLine(const QLineF &newLine); + void setTextVisible(bool arg1); + void setLinesVisible(bool arg1); + void setLineSize(qreal lineSize); + void setLine(const QLineF& line); + int unitSystem; +public +slots: + virtual void updateTicks(color_indice_t color = TIME_GRID); + +signals: + void sizeChanged(); + void maxChanged(); + +protected: + virtual QString textForValue(double value); + virtual QColor colorForValue(double value); + Orientation orientation; + QList labels; + QList lines; + double min; + double max; + double interval; + double tick_size; + QColor textColor; + bool textVisibility; + bool lineVisibility; + double labelScale; + qreal line_size; + bool changed; +}; + +class DepthAxis : public DiveCartesianAxis { + Q_OBJECT +public: + DepthAxis(); + +protected: + QString textForValue(double value); + QColor colorForValue(double value); +private +slots: + void settingsChanged(); +}; + +class TimeAxis : public DiveCartesianAxis { + Q_OBJECT +public: + virtual void updateTicks(); + +protected: + QString textForValue(double value); + QColor colorForValue(double value); +}; + +class TemperatureAxis : public DiveCartesianAxis { + Q_OBJECT +protected: + QString textForValue(double value); +}; + +class PartialGasPressureAxis : public DiveCartesianAxis { + Q_OBJECT +public: + PartialGasPressureAxis(); + void setModel(DivePlotDataModel *model); +public +slots: + void settingsChanged(); + +private: + DivePlotDataModel *model; +}; + +#endif // DIVECARTESIANAXIS_H diff --git a/profile-widget/diveeventitem.cpp b/profile-widget/diveeventitem.cpp new file mode 100644 index 000000000..0bbc84267 --- /dev/null +++ b/profile-widget/diveeventitem.cpp @@ -0,0 +1,172 @@ +#include "diveeventitem.h" +#include "diveplotdatamodel.h" +#include "divecartesianaxis.h" +#include "animationfunctions.h" +#include "libdivecomputer.h" +#include "profile.h" +#include "gettextfromc.h" +#include "metrics.h" + +extern struct ev_select *ev_namelist; +extern int evn_used; + +DiveEventItem::DiveEventItem(QObject *parent) : DivePixmapItem(parent), + vAxis(NULL), + hAxis(NULL), + dataModel(NULL), + internalEvent(NULL) +{ + setFlag(ItemIgnoresTransformations); +} + + +void DiveEventItem::setHorizontalAxis(DiveCartesianAxis *axis) +{ + hAxis = axis; + recalculatePos(true); +} + +void DiveEventItem::setModel(DivePlotDataModel *model) +{ + dataModel = model; + recalculatePos(true); +} + +void DiveEventItem::setVerticalAxis(DiveCartesianAxis *axis) +{ + vAxis = axis; + recalculatePos(true); + connect(vAxis, SIGNAL(sizeChanged()), this, SLOT(recalculatePos())); +} + +struct event *DiveEventItem::getEvent() +{ + return internalEvent; +} + +void DiveEventItem::setEvent(struct event *ev) +{ + if (!ev) + return; + internalEvent = ev; + setupPixmap(); + setupToolTipString(); + recalculatePos(true); +} + +void DiveEventItem::setupPixmap() +{ + const IconMetrics& metrics = defaultIconMetrics(); + int sz_bigger = metrics.sz_med + metrics.sz_small; // ex 40px + int sz_pix = sz_bigger/2; // ex 20px + +#define EVENT_PIXMAP(PIX) QPixmap(QString(PIX)).scaled(sz_pix, sz_pix, Qt::KeepAspectRatio, Qt::SmoothTransformation) +#define EVENT_PIXMAP_BIGGER(PIX) QPixmap(QString(PIX)).scaled(sz_bigger, sz_bigger, Qt::KeepAspectRatio, Qt::SmoothTransformation) + if (same_string(internalEvent->name, "")) { + setPixmap(EVENT_PIXMAP(":warning")); + } else if (internalEvent->type == SAMPLE_EVENT_BOOKMARK) { + setPixmap(EVENT_PIXMAP(":flag")); + } else if (strcmp(internalEvent->name, "heading") == 0 || + (same_string(internalEvent->name, "SP change") && internalEvent->time.seconds == 0)) { + // 2 cases: + // a) some dive computers have heading in every sample + // b) at t=0 we might have an "SP change" to indicate dive type + // in both cases we want to get the right data into the tooltip but don't want the visual clutter + // so set an "almost invisible" pixmap (a narrow but somewhat tall, basically transparent pixmap) + // that allows tooltips to work when we don't want to show a specific + // pixmap for an event, but want to show the event value in the tooltip + QPixmap transparentPixmap(4, 20); + transparentPixmap.fill(QColor::fromRgbF(1.0, 1.0, 1.0, 0.01)); + setPixmap(transparentPixmap); + } else if (event_is_gaschange(internalEvent)) { + if (internalEvent->gas.mix.he.permille) + setPixmap(EVENT_PIXMAP_BIGGER(":gaschangeTrimix")); + else if (gasmix_is_air(&internalEvent->gas.mix)) + setPixmap(EVENT_PIXMAP_BIGGER(":gaschangeAir")); + else + setPixmap(EVENT_PIXMAP_BIGGER(":gaschangeNitrox")); + } else { + setPixmap(EVENT_PIXMAP(":warning")); + } +#undef EVENT_PIXMAP +} + +void DiveEventItem::setupToolTipString() +{ + // we display the event on screen - so translate + QString name = gettextFromC::instance()->tr(internalEvent->name); + int value = internalEvent->value; + int type = internalEvent->type; + if (value) { + if (event_is_gaschange(internalEvent)) { + name += ": "; + name += gasname(&internalEvent->gas.mix); + + /* Do we have an explicit cylinder index? Show it. */ + if (internalEvent->gas.index >= 0) + name += QString(" (cyl %1)").arg(internalEvent->gas.index+1); + } else if (type == SAMPLE_EVENT_PO2 && name == "SP change") { + name += QString(":%1").arg((double)value / 1000); + } else { + name += QString(":%1").arg(value); + } + } else if (type == SAMPLE_EVENT_PO2 && name == "SP change") { + // this is a bad idea - we are abusing an existing event type that is supposed to + // warn of high or low pOâ‚‚ and are turning it into a set point change event + name += "\n" + tr("Manual switch to OC"); + } else { + name += internalEvent->flags == SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") : + internalEvent->flags == SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : ""; + } + // qDebug() << name; + setToolTip(name); +} + +void DiveEventItem::eventVisibilityChanged(const QString &eventName, bool visible) +{ +} + +bool DiveEventItem::shouldBeHidden() +{ + struct event *event = internalEvent; + + /* + * Some gas change events are special. Some dive computers just tell us the initial gas this way. + * Don't bother showing those + */ + struct sample *first_sample = &get_dive_dc(&displayed_dive, dc_number)->sample[0]; + if (!strcmp(event->name, "gaschange") && + (event->time.seconds == 0 || + (first_sample && event->time.seconds == first_sample->time.seconds))) + return true; + + for (int i = 0; i < evn_used; i++) { + if (!strcmp(event->name, ev_namelist[i].ev_name) && ev_namelist[i].plot_ev == false) + return true; + } + return false; +} + +void DiveEventItem::recalculatePos(bool instant) +{ + if (!vAxis || !hAxis || !internalEvent || !dataModel) + return; + + QModelIndexList result = dataModel->match(dataModel->index(0, DivePlotDataModel::TIME), Qt::DisplayRole, internalEvent->time.seconds); + if (result.isEmpty()) { + Q_ASSERT("can't find a spot in the dataModel"); + hide(); + return; + } + if (!isVisible() && !shouldBeHidden()) + show(); + int depth = dataModel->data(dataModel->index(result.first().row(), DivePlotDataModel::DEPTH)).toInt(); + qreal x = hAxis->posAtValue(internalEvent->time.seconds); + qreal y = vAxis->posAtValue(depth); + if (!instant) + Animations::moveTo(this, x, y); + else + setPos(x, y); + if (isVisible() && shouldBeHidden()) + hide(); +} diff --git a/profile-widget/diveeventitem.h b/profile-widget/diveeventitem.h new file mode 100644 index 000000000..f358fee6d --- /dev/null +++ b/profile-widget/diveeventitem.h @@ -0,0 +1,34 @@ +#ifndef DIVEEVENTITEM_H +#define DIVEEVENTITEM_H + +#include "divepixmapitem.h" + +class DiveCartesianAxis; +class DivePlotDataModel; +struct event; + +class DiveEventItem : public DivePixmapItem { + Q_OBJECT +public: + DiveEventItem(QObject *parent = 0); + void setEvent(struct event *ev); + struct event *getEvent(); + void eventVisibilityChanged(const QString &eventName, bool visible); + void setVerticalAxis(DiveCartesianAxis *axis); + void setHorizontalAxis(DiveCartesianAxis *axis); + void setModel(DivePlotDataModel *model); + bool shouldBeHidden(); +public +slots: + void recalculatePos(bool instant = false); + +private: + void setupToolTipString(); + void setupPixmap(); + DiveCartesianAxis *vAxis; + DiveCartesianAxis *hAxis; + DivePlotDataModel *dataModel; + struct event *internalEvent; +}; + +#endif // DIVEEVENTITEM_H diff --git a/profile-widget/divelineitem.cpp b/profile-widget/divelineitem.cpp new file mode 100644 index 000000000..f9e288a44 --- /dev/null +++ b/profile-widget/divelineitem.cpp @@ -0,0 +1,5 @@ +#include "divelineitem.h" + +DiveLineItem::DiveLineItem(QGraphicsItem *parent) : QGraphicsLineItem(parent) +{ +} diff --git a/profile-widget/divelineitem.h b/profile-widget/divelineitem.h new file mode 100644 index 000000000..ec88e9da5 --- /dev/null +++ b/profile-widget/divelineitem.h @@ -0,0 +1,15 @@ +#ifndef DIVELINEITEM_H +#define DIVELINEITEM_H + +#include +#include + +class DiveLineItem : public QObject, public QGraphicsLineItem { + Q_OBJECT + Q_PROPERTY(QPointF pos READ pos WRITE setPos) + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) +public: + DiveLineItem(QGraphicsItem *parent = 0); +}; + +#endif // DIVELINEITEM_H diff --git a/profile-widget/divepixmapitem.cpp b/profile-widget/divepixmapitem.cpp new file mode 100644 index 000000000..581f6f9b4 --- /dev/null +++ b/profile-widget/divepixmapitem.cpp @@ -0,0 +1,130 @@ +#include "divepixmapitem.h" +#include "animationfunctions.h" +#include "divepicturemodel.h" +#include + +#include +#include +#include + +DivePixmapItem::DivePixmapItem(QObject *parent) : QObject(parent), QGraphicsPixmapItem() +{ +} + +DiveButtonItem::DiveButtonItem(QObject *parent): DivePixmapItem(parent) +{ +} + +void DiveButtonItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mousePressEvent(event); + emit clicked(); +} + +// If we have many many pictures on screen, maybe a shared-pixmap would be better to +// paint on screen, but for now, this. +CloseButtonItem::CloseButtonItem(QObject *parent): DiveButtonItem(parent) +{ + static QPixmap p = QPixmap(":trash"); + setPixmap(p); + setFlag(ItemIgnoresTransformations); +} + +void CloseButtonItem::hide() +{ + DiveButtonItem::hide(); +} + +void CloseButtonItem::show() +{ + DiveButtonItem::show(); +} + +DivePictureItem::DivePictureItem(QObject *parent): DivePixmapItem(parent), + canvas(new QGraphicsRectItem(this)), + shadow(new QGraphicsRectItem(this)) +{ + setFlag(ItemIgnoresTransformations); + setAcceptHoverEvents(true); + setScale(0.2); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); + setVisible(prefs.show_pictures_in_profile); + + canvas->setPen(Qt::NoPen); + canvas->setBrush(QColor(Qt::white)); + canvas->setFlag(ItemStacksBehindParent); + canvas->setZValue(-1); + + shadow->setPos(5,5); + shadow->setPen(Qt::NoPen); + shadow->setBrush(QColor(Qt::lightGray)); + shadow->setFlag(ItemStacksBehindParent); + shadow->setZValue(-2); +} + +void DivePictureItem::settingsChanged() +{ + setVisible(prefs.show_pictures_in_profile); +} + +void DivePictureItem::setPixmap(const QPixmap &pix) +{ + DivePixmapItem::setPixmap(pix); + QRectF r = boundingRect(); + canvas->setRect(0 - 10, 0 -10, r.width() + 20, r.height() + 20); + shadow->setRect(canvas->rect()); +} + +CloseButtonItem *button = NULL; +void DivePictureItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Animations::scaleTo(this, 1.0); + setZValue(5); + + if(!button) { + button = new CloseButtonItem(); + button->setScale(0.2); + button->setZValue(7); + scene()->addItem(button); + } + button->setParentItem(this); + button->setPos(boundingRect().width() - button->boundingRect().width() * 0.2, + boundingRect().height() - button->boundingRect().height() * 0.2); + button->setOpacity(0); + button->show(); + Animations::show(button); + button->disconnect(); + connect(button, SIGNAL(clicked()), this, SLOT(removePicture())); +} + +void DivePictureItem::setFileUrl(const QString &s) +{ + fileUrl = s; +} + +void DivePictureItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Animations::scaleTo(this, 0.2); + setZValue(0); + if(button){ + button->setParentItem(NULL); + Animations::hide(button); + } +} + +DivePictureItem::~DivePictureItem(){ + if(button){ + button->setParentItem(NULL); + Animations::hide(button); + } +} + +void DivePictureItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + QDesktopServices::openUrl(QUrl::fromLocalFile(fileUrl)); +} + +void DivePictureItem::removePicture() +{ + DivePictureModel::instance()->removePicture(fileUrl); +} diff --git a/profile-widget/divepixmapitem.h b/profile-widget/divepixmapitem.h new file mode 100644 index 000000000..02c1523f7 --- /dev/null +++ b/profile-widget/divepixmapitem.h @@ -0,0 +1,57 @@ +#ifndef DIVEPIXMAPITEM_H +#define DIVEPIXMAPITEM_H + +#include +#include + +class DivePixmapItem : public QObject, public QGraphicsPixmapItem { + Q_OBJECT + Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity) + Q_PROPERTY(QPointF pos WRITE setPos READ pos) + Q_PROPERTY(qreal x WRITE setX READ x) + Q_PROPERTY(qreal y WRITE setY READ y) +public: + DivePixmapItem(QObject *parent = 0); +}; + +class DivePictureItem : public DivePixmapItem { + Q_OBJECT + Q_PROPERTY(qreal scale WRITE setScale READ scale) +public: + DivePictureItem(QObject *parent = 0); + virtual ~DivePictureItem(); + void setPixmap(const QPixmap& pix); +public slots: + void settingsChanged(); + void removePicture(); + void setFileUrl(const QString& s); +protected: + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); +private: + QString fileUrl; + QGraphicsRectItem *canvas; + QGraphicsRectItem *shadow; +}; + +class DiveButtonItem : public DivePixmapItem { + Q_OBJECT +public: + DiveButtonItem(QObject *parent = 0); +protected: + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); +signals: + void clicked(); +}; + +class CloseButtonItem : public DiveButtonItem { + Q_OBJECT +public: + CloseButtonItem(QObject *parent = 0); +public slots: + void hide(); + void show(); +}; + +#endif // DIVEPIXMAPITEM_H diff --git a/profile-widget/diveprofileitem.cpp b/profile-widget/diveprofileitem.cpp new file mode 100644 index 000000000..7cdccee32 --- /dev/null +++ b/profile-widget/diveprofileitem.cpp @@ -0,0 +1,979 @@ +#include "diveprofileitem.h" +#include "diveplotdatamodel.h" +#include "divecartesianaxis.h" +#include "divetextitem.h" +#include "animationfunctions.h" +#include "dive.h" +#include "profile.h" +#include "preferences.h" +#include "diveplannermodel.h" +#include "helpers.h" +#include "libdivecomputer/parser.h" +#include "mainwindow.h" +#include "maintab.h" +#include "profilewidget2.h" +#include "diveplanner.h" + +#include + +AbstractProfilePolygonItem::AbstractProfilePolygonItem() : QObject(), QGraphicsPolygonItem(), hAxis(NULL), vAxis(NULL), dataModel(NULL), hDataColumn(-1), vDataColumn(-1) +{ + setCacheMode(DeviceCoordinateCache); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); +} + +void AbstractProfilePolygonItem::settingsChanged() +{ +} + +void AbstractProfilePolygonItem::setHorizontalAxis(DiveCartesianAxis *horizontal) +{ + hAxis = horizontal; + connect(hAxis, SIGNAL(sizeChanged()), this, SLOT(modelDataChanged())); + modelDataChanged(); +} + +void AbstractProfilePolygonItem::setHorizontalDataColumn(int column) +{ + hDataColumn = column; + modelDataChanged(); +} + +void AbstractProfilePolygonItem::setModel(DivePlotDataModel *model) +{ + dataModel = model; + connect(dataModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(modelDataChanged(QModelIndex, QModelIndex))); + connect(dataModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(modelDataRemoved(QModelIndex, int, int))); + modelDataChanged(); +} + +void AbstractProfilePolygonItem::modelDataRemoved(const QModelIndex &parent, int from, int to) +{ + setPolygon(QPolygonF()); + qDeleteAll(texts); + texts.clear(); +} + +void AbstractProfilePolygonItem::setVerticalAxis(DiveCartesianAxis *vertical) +{ + vAxis = vertical; + connect(vAxis, SIGNAL(sizeChanged()), this, SLOT(modelDataChanged())); + connect(vAxis, SIGNAL(maxChanged()), this, SLOT(modelDataChanged())); + modelDataChanged(); +} + +void AbstractProfilePolygonItem::setVerticalDataColumn(int column) +{ + vDataColumn = column; + modelDataChanged(); +} + +bool AbstractProfilePolygonItem::shouldCalculateStuff(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (!hAxis || !vAxis) + return false; + if (!dataModel || dataModel->rowCount() == 0) + return false; + if (hDataColumn == -1 || vDataColumn == -1) + return false; + if (topLeft.isValid() && bottomRight.isValid()) { + if ((topLeft.column() >= vDataColumn || topLeft.column() >= hDataColumn) && + (bottomRight.column() <= vDataColumn || topLeft.column() <= hDataColumn)) { + return true; + } + } + return true; +} + +void AbstractProfilePolygonItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + // We don't have enougth data to calculate things, quit. + + // Calculate the polygon. This is the polygon that will be painted on screen + // on the ::paint method. Here we calculate the correct position of the points + // regarting our cartesian plane ( made by the hAxis and vAxis ), the QPolygonF + // is an array of QPointF's, so we basically get the point from the model, convert + // to our coordinates, store. no painting is done here. + QPolygonF poly; + for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { + qreal horizontalValue = dataModel->index(i, hDataColumn).data().toReal(); + qreal verticalValue = dataModel->index(i, vDataColumn).data().toReal(); + QPointF point(hAxis->posAtValue(horizontalValue), vAxis->posAtValue(verticalValue)); + poly.append(point); + } + setPolygon(poly); + + qDeleteAll(texts); + texts.clear(); +} + +DiveProfileItem::DiveProfileItem() : show_reported_ceiling(0), reported_ceiling_in_red(0) +{ +} + +void DiveProfileItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(widget); + if (polygon().isEmpty()) + return; + + painter->save(); + // This paints the Polygon + Background. I'm setting the pen to QPen() so we don't get a black line here, + // after all we need to plot the correct velocities colors later. + setPen(Qt::NoPen); + QGraphicsPolygonItem::paint(painter, option, widget); + + // Here we actually paint the boundaries of the Polygon using the colors that the model provides. + // Those are the speed colors of the dives. + QPen pen; + pen.setCosmetic(true); + pen.setWidth(2); + QPolygonF poly = polygon(); + // This paints the colors of the velocities. + for (int i = 1, count = dataModel->rowCount(); i < count; i++) { + QModelIndex colorIndex = dataModel->index(i, DivePlotDataModel::COLOR); + pen.setBrush(QBrush(colorIndex.data(Qt::BackgroundRole).value())); + painter->setPen(pen); + painter->drawLine(poly[i - 1], poly[i]); + } + painter->restore(); +} + +int DiveProfileItem::maxCeiling(int row) +{ + int max = -1; + plot_data *entry = dataModel->data().entry + row; + for (int tissue = 0; tissue < 16; tissue++) { + if (max < entry->ceilings[tissue]) + max = entry->ceilings[tissue]; + } + return max; +} + +void DiveProfileItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + bool eventAdded = false; + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + + AbstractProfilePolygonItem::modelDataChanged(topLeft, bottomRight); + if (polygon().isEmpty()) + return; + + show_reported_ceiling = prefs.dcceiling; + reported_ceiling_in_red = prefs.redceiling; + profileColor = getColor(DEPTH_BOTTOM); + + int currState = qobject_cast(scene()->views().first())->currentState; + if (currState == ProfileWidget2::PLAN) { + plot_data *entry = dataModel->data().entry; + for (int i = 0; i < dataModel->rowCount(); i++, entry++) { + int max = maxCeiling(i); + // Don't scream if we violate the ceiling by a few cm + if (entry->depth < max - 100 && entry->sec > 0) { + profileColor = QColor(Qt::red); + if (!eventAdded) { + add_event(&displayed_dive.dc, entry->sec, SAMPLE_EVENT_CEILING, -1, max / 1000, "planned waypoint above ceiling"); + eventAdded = true; + } + } + } + } + + /* Show any ceiling we may have encountered */ + if (prefs.dcceiling && !prefs.redceiling) { + QPolygonF p = polygon(); + plot_data *entry = dataModel->data().entry + dataModel->rowCount() - 1; + for (int i = dataModel->rowCount() - 1; i >= 0; i--, entry--) { + if (!entry->in_deco) { + /* not in deco implies this is a safety stop, no ceiling */ + p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(0))); + } else { + p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(qMin(entry->stopdepth, entry->depth)))); + } + } + setPolygon(p); + } + + // This is the blueish gradient that the Depth Profile should have. + // It's a simple QLinearGradient with 2 stops, starting from top to bottom. + QLinearGradient pat(0, polygon().boundingRect().top(), 0, polygon().boundingRect().bottom()); + pat.setColorAt(1, profileColor); + pat.setColorAt(0, getColor(DEPTH_TOP)); + setBrush(QBrush(pat)); + + int last = -1; + for (int i = 0, count = dataModel->rowCount(); i < count; i++) { + + struct plot_data *entry = dataModel->data().entry + i; + if (entry->depth < 2000) + continue; + + if ((entry == entry->max[2]) && entry->depth / 100 != last) { + plot_depth_sample(entry, Qt::AlignHCenter | Qt::AlignBottom, getColor(SAMPLE_DEEP)); + last = entry->depth / 100; + } + + if ((entry == entry->min[2]) && entry->depth / 100 != last) { + plot_depth_sample(entry, Qt::AlignHCenter | Qt::AlignTop, getColor(SAMPLE_SHALLOW)); + last = entry->depth / 100; + } + + if (entry->depth != last) + last = -1; + } +} + +void DiveProfileItem::settingsChanged() +{ + //TODO: Only modelDataChanged() here if we need to rebuild the graph ( for instance, + // if the prefs.dcceiling are enabled, but prefs.redceiling is disabled + // and only if it changed something. let's not waste cpu cycles repoloting something we don't need to. + modelDataChanged(); +} + +void DiveProfileItem::plot_depth_sample(struct plot_data *entry, QFlags flags, const QColor &color) +{ + int decimals; + double d = get_depth_units(entry->depth, &decimals, NULL); + DiveTextItem *item = new DiveTextItem(this); + item->setPos(hAxis->posAtValue(entry->sec), vAxis->posAtValue(entry->depth)); + item->setText(QString("%1").arg(d, 0, 'f', 1)); + item->setAlignment(flags); + item->setBrush(color); + texts.append(item); +} + +DiveHeartrateItem::DiveHeartrateItem() +{ + QPen pen; + pen.setBrush(QBrush(getColor(::HR_PLOT))); + pen.setCosmetic(true); + pen.setWidth(1); + setPen(pen); + settingsChanged(); +} + +void DiveHeartrateItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + int last = -300, last_printed_hr = 0, sec = 0; + struct { + int sec; + int hr; + } hist[3] = {}; + + // We don't have enougth data to calculate things, quit. + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + + qDeleteAll(texts); + texts.clear(); + // Ignore empty values. a heartrate of 0 would be a bad sign. + QPolygonF poly; + for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { + int hr = dataModel->index(i, vDataColumn).data().toInt(); + if (!hr) + continue; + sec = dataModel->index(i, hDataColumn).data().toInt(); + QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr)); + poly.append(point); + if (hr == hist[2].hr) + // same as last one, no point in looking at printing + continue; + hist[0] = hist[1]; + hist[1] = hist[2]; + hist[2].sec = sec; + hist[2].hr = hr; + // don't print a HR + // if it's not a local min / max + // if it's been less than 5min and less than a 20 beats change OR + // if it's been less than 2min OR if the change from the + // last print is less than 10 beats + // to test min / max requires three points, so we now look at the + // previous one + sec = hist[1].sec; + hr = hist[1].hr; + if ((hist[0].hr < hr && hr < hist[2].hr) || + (hist[0].hr > hr && hr > hist[2].hr) || + ((sec < last + 300) && (abs(hr - last_printed_hr) < 20)) || + (sec < last + 120) || + (abs(hr - last_printed_hr) < 10)) + continue; + last = sec; + createTextItem(sec, hr); + last_printed_hr = hr; + } + setPolygon(poly); + + if (texts.count()) + texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom); +} + +void DiveHeartrateItem::createTextItem(int sec, int hr) +{ + DiveTextItem *text = new DiveTextItem(this); + text->setAlignment(Qt::AlignRight | Qt::AlignBottom); + text->setBrush(getColor(HR_TEXT)); + text->setPos(QPointF(hAxis->posAtValue(sec), vAxis->posAtValue(hr))); + text->setScale(0.7); // need to call this BEFORE setText() + text->setText(QString("%1").arg(hr)); + texts.append(text); +} + +void DiveHeartrateItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (polygon().isEmpty()) + return; + painter->save(); + painter->setPen(pen()); + painter->drawPolyline(polygon()); + painter->restore(); +} + +void DiveHeartrateItem::settingsChanged() +{ + setVisible(prefs.hrgraph); +} + +DivePercentageItem::DivePercentageItem(int i) +{ + QPen pen; + QColor color; + color.setHsl(100 + 10 * i, 200, 100); + pen.setBrush(QBrush(color)); + pen.setCosmetic(true); + pen.setWidth(1); + setPen(pen); + settingsChanged(); +} + +void DivePercentageItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + int sec = 0; + + // We don't have enougth data to calculate things, quit. + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + + // Ignore empty values. a heartrate of 0 would be a bad sign. + QPolygonF poly; + for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { + int hr = dataModel->index(i, vDataColumn).data().toInt(); + if (!hr) + continue; + sec = dataModel->index(i, hDataColumn).data().toInt(); + QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr)); + poly.append(point); + } + setPolygon(poly); + + if (texts.count()) + texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom); +} + +void DivePercentageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (polygon().isEmpty()) + return; + painter->save(); + painter->setPen(pen()); + painter->drawPolyline(polygon()); + painter->restore(); +} + +void DivePercentageItem::settingsChanged() +{ + setVisible(prefs.percentagegraph); +} + +DiveAmbPressureItem::DiveAmbPressureItem() +{ + QPen pen; + pen.setBrush(QBrush(getColor(::AMB_PRESSURE_LINE))); + pen.setCosmetic(true); + pen.setWidth(2); + setPen(pen); + settingsChanged(); +} + +void DiveAmbPressureItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + int sec = 0; + + // We don't have enougth data to calculate things, quit. + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + + // Ignore empty values. a heartrate of 0 would be a bad sign. + QPolygonF poly; + for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { + int hr = dataModel->index(i, vDataColumn).data().toInt(); + if (!hr) + continue; + sec = dataModel->index(i, hDataColumn).data().toInt(); + QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr)); + poly.append(point); + } + setPolygon(poly); + + if (texts.count()) + texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom); +} + +void DiveAmbPressureItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (polygon().isEmpty()) + return; + painter->save(); + painter->setPen(pen()); + painter->drawPolyline(polygon()); + painter->restore(); +} + +void DiveAmbPressureItem::settingsChanged() +{ + setVisible(prefs.percentagegraph); +} + +DiveGFLineItem::DiveGFLineItem() +{ + QPen pen; + pen.setBrush(QBrush(getColor(::GF_LINE))); + pen.setCosmetic(true); + pen.setWidth(2); + setPen(pen); + settingsChanged(); +} + +void DiveGFLineItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + int sec = 0; + + // We don't have enougth data to calculate things, quit. + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + + // Ignore empty values. a heartrate of 0 would be a bad sign. + QPolygonF poly; + for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { + int hr = dataModel->index(i, vDataColumn).data().toInt(); + if (!hr) + continue; + sec = dataModel->index(i, hDataColumn).data().toInt(); + QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(hr)); + poly.append(point); + } + setPolygon(poly); + + if (texts.count()) + texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom); +} + +void DiveGFLineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (polygon().isEmpty()) + return; + painter->save(); + painter->setPen(pen()); + painter->drawPolyline(polygon()); + painter->restore(); +} + +void DiveGFLineItem::settingsChanged() +{ + setVisible(prefs.percentagegraph); +} + +DiveTemperatureItem::DiveTemperatureItem() +{ + QPen pen; + pen.setBrush(QBrush(getColor(::TEMP_PLOT))); + pen.setCosmetic(true); + pen.setWidth(2); + setPen(pen); +} + +void DiveTemperatureItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + int last = -300, last_printed_temp = 0, sec = 0, last_valid_temp = 0; + // We don't have enougth data to calculate things, quit. + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + + qDeleteAll(texts); + texts.clear(); + // Ignore empty values. things do not look good with '0' as temperature in kelvin... + QPolygonF poly; + for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++) { + int mkelvin = dataModel->index(i, vDataColumn).data().toInt(); + if (!mkelvin) + continue; + last_valid_temp = mkelvin; + sec = dataModel->index(i, hDataColumn).data().toInt(); + QPointF point(hAxis->posAtValue(sec), vAxis->posAtValue(mkelvin)); + poly.append(point); + + /* 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) + createTextItem(sec, mkelvin); + last_printed_temp = mkelvin; + } + setPolygon(poly); + + /* 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_valid_temp > 200000 && + ((abs(last_valid_temp - last_printed_temp) > 500) || ((double)last / (double)sec < 0.75))) { + createTextItem(sec, last_valid_temp); + } + if (texts.count()) + texts.last()->setAlignment(Qt::AlignLeft | Qt::AlignBottom); +} + +void DiveTemperatureItem::createTextItem(int sec, int mkelvin) +{ + double deg; + const char *unit; + deg = get_temp_units(mkelvin, &unit); + + DiveTextItem *text = new DiveTextItem(this); + text->setAlignment(Qt::AlignRight | Qt::AlignBottom); + text->setBrush(getColor(TEMP_TEXT)); + text->setPos(QPointF(hAxis->posAtValue(sec), vAxis->posAtValue(mkelvin))); + text->setScale(0.8); // need to call this BEFORE setText() + text->setText(QString("%1%2").arg(deg, 0, 'f', 1).arg(unit)); + texts.append(text); +} + +void DiveTemperatureItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (polygon().isEmpty()) + return; + painter->save(); + painter->setPen(pen()); + painter->drawPolyline(polygon()); + painter->restore(); +} + +DiveMeanDepthItem::DiveMeanDepthItem() +{ + QPen pen; + pen.setBrush(QBrush(getColor(::HR_AXIS))); + pen.setCosmetic(true); + pen.setWidth(2); + setPen(pen); + settingsChanged(); +} + +void DiveMeanDepthItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + double meandepthvalue = 0.0; + // We don't have enougth data to calculate things, quit. + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + + QPolygonF poly; + plot_data *entry = dataModel->data().entry; + for (int i = 0, modelDataCount = dataModel->rowCount(); i < modelDataCount; i++, entry++) { + // Ignore empty values + if (entry->running_sum == 0 || entry->sec == 0) + continue; + + meandepthvalue = entry->running_sum / entry->sec; + QPointF point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(meandepthvalue)); + poly.append(point); + } + lastRunningSum = meandepthvalue; + setPolygon(poly); + createTextItem(); +} + + +void DiveMeanDepthItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (polygon().isEmpty()) + return; + painter->save(); + painter->setPen(pen()); + painter->drawPolyline(polygon()); + painter->restore(); +} + +void DiveMeanDepthItem::settingsChanged() +{ + setVisible(prefs.show_average_depth); +} + +void DiveMeanDepthItem::createTextItem() { + plot_data *entry = dataModel->data().entry; + int sec = entry[dataModel->rowCount()-1].sec; + qDeleteAll(texts); + texts.clear(); + int decimals; + const char *unitText; + double d = get_depth_units(lastRunningSum, &decimals, &unitText); + DiveTextItem *text = new DiveTextItem(this); + text->setAlignment(Qt::AlignRight | Qt::AlignTop); + text->setBrush(getColor(TEMP_TEXT)); + text->setPos(QPointF(hAxis->posAtValue(sec) + 1, vAxis->posAtValue(lastRunningSum))); + text->setScale(0.8); // need to call this BEFORE setText() + text->setText(QString("%1%2").arg(d, 0, 'f', 1).arg(unitText)); + texts.append(text); +} + +void DiveGasPressureItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + // We don't have enougth data to calculate things, quit. + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + int last_index = -1; + int o2mbar; + QPolygonF boundingPoly, o2Poly; // This is the "Whole Item", but a pressure can be divided in N Polygons. + polygons.clear(); + if (displayed_dive.dc.divemode == CCR) + polygons.append(o2Poly); + + for (int i = 0, count = dataModel->rowCount(); i < count; i++) { + o2mbar = 0; + plot_data *entry = dataModel->data().entry + i; + int mbar = GET_PRESSURE(entry); + if (displayed_dive.dc.divemode == CCR) + o2mbar = GET_O2CYLINDER_PRESSURE(entry); + + if (entry->cylinderindex != last_index) { + polygons.append(QPolygonF()); // this is the polygon that will be actually drawn on screen. + last_index = entry->cylinderindex; + } + if (!mbar) { + continue; + } + if (o2mbar) { + QPointF o2point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(o2mbar)); + boundingPoly.push_back(o2point); + polygons.first().push_back(o2point); + } + + QPointF point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(mbar)); + boundingPoly.push_back(point); // The BoundingRect + polygons.last().push_back(point); // The polygon thta will be plotted. + } + setPolygon(boundingPoly); + qDeleteAll(texts); + texts.clear(); + 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; + + cyl = -1; + o2mbar = 0; + + double print_y_offset[8][2] = { { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 } ,{ 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 } }; + // CCR dives: These are offset values used to print the gas lables and pressures on a CCR dive profile at + // appropriate Y-coordinates: One doublet of values for each of 8 cylinders. + // Order of offsets within a doublet: gas lable offset; gas pressure offset. + // The array is initialised with default values that apply to non-CCR dives. + + bool offsets_initialised = false; + int o2cyl = -1, dilcyl = -1; + QFlags alignVar= Qt::AlignTop, align_dil = Qt::AlignBottom, align_o2 = Qt::AlignTop; + double axisRange = (vAxis->maximum() - vAxis->minimum())/1000; // Convert axis pressure range to bar + double axisLog = log10(log10(axisRange)); + for (int i = 0, count = dataModel->rowCount(); i < count; i++) { + entry = dataModel->data().entry + i; + mbar = GET_PRESSURE(entry); + if (displayed_dive.dc.divemode == CCR && displayed_dive.oxygen_cylinder_index >= 0) + o2mbar = GET_O2CYLINDER_PRESSURE(entry); + + if (o2mbar) { // If there is an o2mbar value then this is a CCR dive. Then do: + // The first time an o2 value is detected, see if the oxygen cyl pressure graph starts above or below the dil graph + if (!offsets_initialised) { // Initialise the parameters for placing the text correctly near the graph line: + o2cyl = displayed_dive.oxygen_cylinder_index; + dilcyl = displayed_dive.diluent_cylinder_index; + if ((o2mbar > mbar)) { // If above, write o2 start cyl pressure above graph and diluent pressure below graph: + print_y_offset[o2cyl][0] = -7 * axisLog; // y offset for oxygen gas lable (above); pressure offsets=-0.5, already initialised + print_y_offset[dilcyl][0] = 5 * axisLog; // y offset for diluent gas lable (below) + } else { // ... else write o2 start cyl pressure below graph: + print_y_offset[o2cyl][0] = 5 * axisLog; // o2 lable & pressure below graph; pressure offsets=-0.5, already initialised + print_y_offset[dilcyl][0] = -7.8 * axisLog; // and diluent lable above graph. + align_dil = Qt::AlignTop; + align_o2 = Qt::AlignBottom; + } + offsets_initialised = true; + } + + if (!seen_cyl[displayed_dive.oxygen_cylinder_index]) { //For o2, on the left of profile, write lable and pressure + plotPressureValue(o2mbar, entry->sec, align_o2, print_y_offset[o2cyl][1]); + plotGasValue(o2mbar, entry->sec, displayed_dive.cylinder[displayed_dive.oxygen_cylinder_index].gasmix, align_o2, print_y_offset[o2cyl][0]); + seen_cyl[displayed_dive.oxygen_cylinder_index] = true; + } + last_pressure[displayed_dive.oxygen_cylinder_index] = o2mbar; + last_time[displayed_dive.oxygen_cylinder_index] = entry->sec; + alignVar = align_dil; + } + + if (!mbar) + continue; + + if (cyl != entry->cylinderindex) { // Pressure value near the left hand edge of the profile - other cylinders: + cyl = entry->cylinderindex; // For each other cylinder, write the gas lable and pressure + if (!seen_cyl[cyl]) { + plotPressureValue(mbar, entry->sec, alignVar, print_y_offset[cyl][1]); + plotGasValue(mbar, entry->sec, displayed_dive.cylinder[cyl].gasmix, align_dil, print_y_offset[cyl][0]); + seen_cyl[cyl] = true; + } + } + last_pressure[cyl] = mbar; + last_time[cyl] = entry->sec; + } + + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { // For each cylinder, on right hand side of profile, write cylinder pressure + alignVar = ((o2cyl >= 0) && (cyl == displayed_dive.oxygen_cylinder_index)) ? align_o2 : align_dil; + if (last_time[cyl]) { + plotPressureValue(last_pressure[cyl], last_time[cyl], (alignVar | Qt::AlignLeft), print_y_offset[cyl][1]); + } + } +} + +void DiveGasPressureItem::plotPressureValue(int mbar, int sec, QFlags align, double pressure_offset) +{ + const char *unit; + int pressure = get_pressure_units(mbar, &unit); + DiveTextItem *text = new DiveTextItem(this); + text->setPos(hAxis->posAtValue(sec), vAxis->posAtValue(mbar) + pressure_offset ); + text->setText(QString("%1 %2").arg(pressure).arg(unit)); + text->setAlignment(align); + text->setBrush(getColor(PRESSURE_TEXT)); + texts.push_back(text); +} + +void DiveGasPressureItem::plotGasValue(int mbar, int sec, struct gasmix gasmix, QFlags align, double gasname_offset) +{ + QString gas = get_gas_string(gasmix); + DiveTextItem *text = new DiveTextItem(this); + text->setPos(hAxis->posAtValue(sec), vAxis->posAtValue(mbar) + gasname_offset ); + text->setText(gas); + text->setAlignment(align); + text->setBrush(getColor(PRESSURE_TEXT)); + texts.push_back(text); +} + +void DiveGasPressureItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (polygon().isEmpty()) + return; + QPen pen; + pen.setCosmetic(true); + pen.setWidth(2); + painter->save(); + struct plot_data *entry; + Q_FOREACH (const QPolygonF &poly, polygons) { + entry = dataModel->data().entry; + for (int i = 1, count = poly.count(); i < count; i++, entry++) { + if (entry->sac) + pen.setBrush(getSacColor(entry->sac, displayed_dive.sac)); + else + pen.setBrush(MED_GRAY_HIGH_TRANS); + painter->setPen(pen); + painter->drawLine(poly[i - 1], poly[i]); + } + } + painter->restore(); +} + +DiveCalculatedCeiling::DiveCalculatedCeiling() : is3mIncrement(false) +{ + settingsChanged(); +} + +void DiveCalculatedCeiling::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (MainWindow::instance()->information()) + connect(MainWindow::instance()->information(), SIGNAL(dateTimeChanged()), this, SLOT(recalc()), Qt::UniqueConnection); + + // We don't have enougth data to calculate things, quit. + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + AbstractProfilePolygonItem::modelDataChanged(topLeft, bottomRight); + // Add 2 points to close the polygon. + QPolygonF poly = polygon(); + if (poly.isEmpty()) + return; + QPointF p1 = poly.first(); + QPointF p2 = poly.last(); + + poly.prepend(QPointF(p1.x(), vAxis->posAtValue(0))); + poly.append(QPointF(p2.x(), vAxis->posAtValue(0))); + setPolygon(poly); + + QLinearGradient pat(0, polygon().boundingRect().top(), 0, polygon().boundingRect().bottom()); + pat.setColorAt(0, getColor(CALC_CEILING_SHALLOW)); + pat.setColorAt(1, getColor(CALC_CEILING_DEEP)); + setPen(QPen(QBrush(Qt::NoBrush), 0)); + setBrush(pat); +} + +void DiveCalculatedCeiling::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (polygon().isEmpty()) + return; + QGraphicsPolygonItem::paint(painter, option, widget); +} + +DiveCalculatedTissue::DiveCalculatedTissue() +{ + settingsChanged(); +} + +void DiveCalculatedTissue::settingsChanged() +{ + setVisible(prefs.calcalltissues && prefs.calcceiling); +} + +void DiveReportedCeiling::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + + QPolygonF p; + p.append(QPointF(hAxis->posAtValue(0), vAxis->posAtValue(0))); + plot_data *entry = dataModel->data().entry; + for (int i = 0, count = dataModel->rowCount(); i < count; i++, entry++) { + if (entry->in_deco && entry->stopdepth) { + p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(qMin(entry->stopdepth, entry->depth)))); + } else { + p.append(QPointF(hAxis->posAtValue(entry->sec), vAxis->posAtValue(0))); + } + } + setPolygon(p); + QLinearGradient pat(0, p.boundingRect().top(), 0, p.boundingRect().bottom()); + // does the user want the ceiling in "surface color" or in red? + if (prefs.redceiling) { + pat.setColorAt(0, getColor(CEILING_SHALLOW)); + pat.setColorAt(1, getColor(CEILING_DEEP)); + } else { + pat.setColorAt(0, getColor(BACKGROUND_TRANS)); + pat.setColorAt(1, getColor(BACKGROUND_TRANS)); + } + setPen(QPen(QBrush(Qt::NoBrush), 0)); + setBrush(pat); +} + +void DiveCalculatedCeiling::recalc() +{ + dataModel->calculateDecompression(); +} + +void DiveCalculatedCeiling::settingsChanged() +{ + if (dataModel && is3mIncrement != prefs.calcceiling3m) { + // recalculate that part. + recalc(); + } + is3mIncrement = prefs.calcceiling3m; + setVisible(prefs.calcceiling); +} + +void DiveReportedCeiling::settingsChanged() +{ + setVisible(prefs.dcceiling); +} + +void DiveReportedCeiling::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (polygon().isEmpty()) + return; + QGraphicsPolygonItem::paint(painter, option, widget); +} + +void PartialPressureGasItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + //AbstractProfilePolygonItem::modelDataChanged(); + if (!shouldCalculateStuff(topLeft, bottomRight)) + return; + + plot_data *entry = dataModel->data().entry; + QPolygonF poly; + QPolygonF alertpoly; + alertPolygons.clear(); + QSettings s; + s.beginGroup("TecDetails"); + double threshold = 0.0; + if (thresholdPtr) + threshold = *thresholdPtr; + bool inAlertFragment = false; + for (int i = 0; i < dataModel->rowCount(); i++, entry++) { + double value = dataModel->index(i, vDataColumn).data().toDouble(); + int time = dataModel->index(i, hDataColumn).data().toInt(); + QPointF point(hAxis->posAtValue(time), vAxis->posAtValue(value)); + poly.push_back(point); + if (value >= threshold) { + if (inAlertFragment) { + alertPolygons.back().push_back(point); + } else { + alertpoly.clear(); + alertpoly.push_back(point); + alertPolygons.append(alertpoly); + inAlertFragment = true; + } + } else { + inAlertFragment = false; + } + } + setPolygon(poly); + /* + createPPLegend(trUtf8("pN" UTF8_SUBSCRIPT_2),getColor(PN2), legendPos); + */ +} + +void PartialPressureGasItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + const qreal pWidth = 0.0; + painter->save(); + painter->setPen(QPen(normalColor, pWidth)); + painter->drawPolyline(polygon()); + + QPolygonF poly; + painter->setPen(QPen(alertColor, pWidth)); + Q_FOREACH (const QPolygonF &poly, alertPolygons) + painter->drawPolyline(poly); + painter->restore(); +} + +void PartialPressureGasItem::setThreshouldSettingsKey(double *prefPointer) +{ + thresholdPtr = prefPointer; +} + +PartialPressureGasItem::PartialPressureGasItem() : + thresholdPtr(NULL) +{ +} + +void PartialPressureGasItem::settingsChanged() +{ + QSettings s; + s.beginGroup("TecDetails"); + setVisible(s.value(visibilityKey).toBool()); +} + +void PartialPressureGasItem::setVisibilitySettingsKey(const QString &key) +{ + visibilityKey = key; +} + +void PartialPressureGasItem::setColors(const QColor &normal, const QColor &alert) +{ + normalColor = normal; + alertColor = alert; +} diff --git a/profile-widget/diveprofileitem.h b/profile-widget/diveprofileitem.h new file mode 100644 index 000000000..0bba7f7a3 --- /dev/null +++ b/profile-widget/diveprofileitem.h @@ -0,0 +1,225 @@ +#ifndef DIVEPROFILEITEM_H +#define DIVEPROFILEITEM_H + +#include +#include +#include + +#include "divelineitem.h" + +/* This is the Profile Item, it should be used for quite a lot of things + on the profile view. The usage should be pretty simple: + + DiveProfileItem *profile = new DiveProfileItem(); + profile->setVerticalAxis( profileYAxis ); + profile->setHorizontalAxis( timeAxis ); + profile->setModel( DiveDataModel ); + profile->setHorizontalDataColumn( DiveDataModel::TIME ); + profile->setVerticalDataColumn( DiveDataModel::DEPTH ); + scene()->addItem(profile); + + This is a generically item and should be used as a base for others, I think... +*/ + +class DivePlotDataModel; +class DiveTextItem; +class DiveCartesianAxis; +class QAbstractTableModel; +struct plot_data; + +class AbstractProfilePolygonItem : public QObject, public QGraphicsPolygonItem { + Q_OBJECT + Q_PROPERTY(QPointF pos WRITE setPos READ pos) + Q_PROPERTY(qreal x WRITE setX READ x) + Q_PROPERTY(qreal y WRITE setY READ y) +public: + AbstractProfilePolygonItem(); + void setVerticalAxis(DiveCartesianAxis *vertical); + void setHorizontalAxis(DiveCartesianAxis *horizontal); + void setModel(DivePlotDataModel *model); + void setHorizontalDataColumn(int column); + void setVerticalDataColumn(int column); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) = 0; + virtual void clear() + { + } +public +slots: + virtual void settingsChanged(); + virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); + virtual void modelDataRemoved(const QModelIndex &parent, int from, int to); + +protected: + /* when the model emits a 'datachanged' signal, this method below should be used to check if the + * modified data affects this particular item ( for example, when setting the '3m increment' + * the data for Ceiling and tissues will be changed, and only those. so, the topLeft will be the CEILING + * column and the bottomRight will have the TISSUE_16 column. this method takes the vDataColumn and hDataColumn + * into consideration when returning 'true' for "yes, continue the calculation', and 'false' for + * 'do not recalculate, we already have the right data. + */ + bool shouldCalculateStuff(const QModelIndex &topLeft, const QModelIndex &bottomRight); + + DiveCartesianAxis *hAxis; + DiveCartesianAxis *vAxis; + DivePlotDataModel *dataModel; + int hDataColumn; + int vDataColumn; + QList texts; +}; + +class DiveProfileItem : public AbstractProfilePolygonItem { + Q_OBJECT + +public: + DiveProfileItem(); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); + virtual void settingsChanged(); + void plot_depth_sample(struct plot_data *entry, QFlags flags, const QColor &color); + int maxCeiling(int row); + +private: + unsigned int show_reported_ceiling; + unsigned int reported_ceiling_in_red; + QColor profileColor; +}; + +class DiveMeanDepthItem : public AbstractProfilePolygonItem { + Q_OBJECT +public: + DiveMeanDepthItem(); + virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + virtual void settingsChanged(); + +private: + void createTextItem(); + double lastRunningSum; + QString visibilityKey; +}; + +class DiveTemperatureItem : public AbstractProfilePolygonItem { + Q_OBJECT +public: + DiveTemperatureItem(); + virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +private: + void createTextItem(int seconds, int mkelvin); +}; + +class DiveHeartrateItem : public AbstractProfilePolygonItem { + Q_OBJECT +public: + DiveHeartrateItem(); + virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + virtual void settingsChanged(); + +private: + void createTextItem(int seconds, int hr); + QString visibilityKey; +}; + +class DivePercentageItem : public AbstractProfilePolygonItem { + Q_OBJECT +public: + DivePercentageItem(int i); + virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + virtual void settingsChanged(); + +private: + QString visibilityKey; +}; + +class DiveAmbPressureItem : public AbstractProfilePolygonItem { + Q_OBJECT +public: + DiveAmbPressureItem(); + virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + virtual void settingsChanged(); + +private: + QString visibilityKey; +}; + +class DiveGFLineItem : public AbstractProfilePolygonItem { + Q_OBJECT +public: + DiveGFLineItem(); + virtual void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + virtual void settingsChanged(); + +private: + QString visibilityKey; +}; + +class DiveGasPressureItem : public AbstractProfilePolygonItem { + Q_OBJECT + +public: + virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +private: + void plotPressureValue(int mbar, int sec, QFlags align, double offset); + void plotGasValue(int mbar, int sec, struct gasmix gasmix, QFlags align, double offset); + QVector polygons; +}; + +class DiveCalculatedCeiling : public AbstractProfilePolygonItem { + Q_OBJECT + +public: + DiveCalculatedCeiling(); + virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + virtual void settingsChanged(); + +public +slots: + void recalc(); + +private: + bool is3mIncrement; +}; + +class DiveReportedCeiling : public AbstractProfilePolygonItem { + Q_OBJECT + +public: + virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + virtual void settingsChanged(); +}; + +class DiveCalculatedTissue : public DiveCalculatedCeiling { + Q_OBJECT +public: + DiveCalculatedTissue(); + virtual void settingsChanged(); +}; + +class PartialPressureGasItem : public AbstractProfilePolygonItem { + Q_OBJECT +public: + PartialPressureGasItem(); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); + virtual void settingsChanged(); + void setThreshouldSettingsKey(double *prefPointer); + void setVisibilitySettingsKey(const QString &setVisibilitySettingsKey); + void setColors(const QColor &normalColor, const QColor &alertColor); + +private: + QVector alertPolygons; + double *thresholdPtr; + QString visibilityKey; + QColor normalColor; + QColor alertColor; +}; +#endif // DIVEPROFILEITEM_H diff --git a/profile-widget/diverectitem.cpp b/profile-widget/diverectitem.cpp new file mode 100644 index 000000000..8cb60c3f5 --- /dev/null +++ b/profile-widget/diverectitem.cpp @@ -0,0 +1,5 @@ +#include "diverectitem.h" + +DiveRectItem::DiveRectItem(QObject *parent, QGraphicsItem *parentItem) : QObject(parent), QGraphicsRectItem(parentItem) +{ +} diff --git a/profile-widget/diverectitem.h b/profile-widget/diverectitem.h new file mode 100644 index 000000000..e616cf591 --- /dev/null +++ b/profile-widget/diverectitem.h @@ -0,0 +1,17 @@ +#ifndef DIVERECTITEM_H +#define DIVERECTITEM_H + +#include +#include + +class DiveRectItem : public QObject, public QGraphicsRectItem { + Q_OBJECT + Q_PROPERTY(QRectF rect WRITE setRect READ rect) + Q_PROPERTY(QPointF pos WRITE setPos READ pos) + Q_PROPERTY(qreal x WRITE setX READ x) + Q_PROPERTY(qreal y WRITE setY READ y) +public: + DiveRectItem(QObject *parent = 0, QGraphicsItem *parentItem = 0); +}; + +#endif // DIVERECTITEM_H diff --git a/profile-widget/divetextitem.cpp b/profile-widget/divetextitem.cpp new file mode 100644 index 000000000..3bf00d68f --- /dev/null +++ b/profile-widget/divetextitem.cpp @@ -0,0 +1,113 @@ +#include "divetextitem.h" +#include "mainwindow.h" +#include "profilewidget2.h" +#include "subsurface-core/color.h" + +#include + +DiveTextItem::DiveTextItem(QGraphicsItem *parent) : QGraphicsItemGroup(parent), + internalAlignFlags(Qt::AlignHCenter | Qt::AlignVCenter), + textBackgroundItem(new QGraphicsPathItem(this)), + textItem(new QGraphicsPathItem(this)), + printScale(1.0), + scale(1.0), + connected(false) +{ + setFlag(ItemIgnoresTransformations); + textBackgroundItem->setBrush(QBrush(getColor(TEXT_BACKGROUND))); + textBackgroundItem->setPen(Qt::NoPen); + textItem->setPen(Qt::NoPen); +} + +void DiveTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + updateText(); + QGraphicsItemGroup::paint(painter, option, widget); +} + +void DiveTextItem::fontPrintScaleUpdate(double scale) +{ + printScale = scale; +} + +void DiveTextItem::setAlignment(int alignFlags) +{ + if (alignFlags != internalAlignFlags) { + internalAlignFlags = alignFlags; + } +} + +void DiveTextItem::setBrush(const QBrush &b) +{ + textItem->setBrush(b); +} + +void DiveTextItem::setScale(double newscale) +{ + if (scale != newscale) { + scale = newscale; + } +} + +void DiveTextItem::setText(const QString &t) +{ + if (internalText != t) { + if (!connected) { + if (scene()) { + // by now we should be on a scene. grab the profile widget from it and setup our printScale + // and connect to the signal that makes sure we keep track if that changes + ProfileWidget2 *profile = qobject_cast(scene()->views().first()); + connect(profile, SIGNAL(fontPrintScaleChanged(double)), this, SLOT(fontPrintScaleUpdate(double)), Qt::UniqueConnection); + fontPrintScaleUpdate(profile->getFontPrintScale()); + connected = true; + } else { + qDebug() << "called before scene was set up" << t; + } + } + internalText = t; + updateText(); + } +} + +const QString &DiveTextItem::text() +{ + return internalText; +} + +void DiveTextItem::updateText() +{ + double size; + if (internalText.isEmpty()) { + return; + } + + QFont fnt(qApp->font()); + if ((size = fnt.pixelSize()) > 0) { + // set in pixels - so the scale factor may not make a difference if it's too close to 1 + size *= scale * printScale; + fnt.setPixelSize(size); + } else { + size = fnt.pointSizeF(); + size *= scale * printScale; + fnt.setPointSizeF(size); + } + QFontMetrics fm(fnt); + + QPainterPath textPath; + qreal xPos = 0, yPos = 0; + + QRectF rect = fm.boundingRect(internalText); + yPos = (internalAlignFlags & Qt::AlignTop) ? 0 : + (internalAlignFlags & Qt::AlignBottom) ? +rect.height() : + /*(internalAlignFlags & Qt::AlignVCenter ? */ +rect.height() / 4; + + xPos = (internalAlignFlags & Qt::AlignLeft) ? -rect.width() : + (internalAlignFlags & Qt::AlignHCenter) ? -rect.width() / 2 : + /* (internalAlignFlags & Qt::AlignRight) */ 0; + + textPath.addText(xPos, yPos, fnt, internalText); + QPainterPathStroker stroker; + stroker.setWidth(3); + textBackgroundItem->setPath(stroker.createStroke(textPath)); + textItem->setPath(textPath); +} diff --git a/profile-widget/divetextitem.h b/profile-widget/divetextitem.h new file mode 100644 index 000000000..be0adf292 --- /dev/null +++ b/profile-widget/divetextitem.h @@ -0,0 +1,38 @@ +#ifndef DIVETEXTITEM_H +#define DIVETEXTITEM_H + +#include +#include + +class QBrush; + +/* A Line Item that has animated-properties. */ +class DiveTextItem : public QObject, public QGraphicsItemGroup { + Q_OBJECT + Q_PROPERTY(QPointF pos READ pos WRITE setPos) + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) +public: + DiveTextItem(QGraphicsItem *parent = 0); + void setText(const QString &text); + void setAlignment(int alignFlags); + void setScale(double newscale); + void setBrush(const QBrush &brush); + const QString &text(); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + +private +slots: + void fontPrintScaleUpdate(double scale); + +private: + void updateText(); + int internalAlignFlags; + QGraphicsPathItem *textBackgroundItem; + QGraphicsPathItem *textItem; + QString internalText; + double printScale; + double scale; + bool connected; +}; + +#endif // DIVETEXTITEM_H diff --git a/profile-widget/divetooltipitem.cpp b/profile-widget/divetooltipitem.cpp new file mode 100644 index 000000000..d4818422b --- /dev/null +++ b/profile-widget/divetooltipitem.cpp @@ -0,0 +1,285 @@ +#include "divetooltipitem.h" +#include "divecartesianaxis.h" +#include "dive.h" +#include "profile.h" +#include "membuffer.h" +#include "metrics.h" +#include +#include +#include +#include + +#define PORT_IN_PROGRESS 1 +#ifdef PORT_IN_PROGRESS +#include "display.h" +#endif + +void ToolTipItem::addToolTip(const QString &toolTip, const QIcon &icon, const QPixmap& pixmap) +{ + const IconMetrics& iconMetrics = defaultIconMetrics(); + + QGraphicsPixmapItem *iconItem = 0; + double yValue = title->boundingRect().height() + iconMetrics.spacing; + Q_FOREACH (ToolTip t, toolTips) { + yValue += t.second->boundingRect().height(); + } + if (entryToolTip.second) { + yValue += entryToolTip.second->boundingRect().height(); + } + iconItem = new QGraphicsPixmapItem(this); + if (!icon.isNull()) { + iconItem->setPixmap(icon.pixmap(iconMetrics.sz_small, iconMetrics.sz_small)); + } else if (!pixmap.isNull()) { + iconItem->setPixmap(pixmap); + } + iconItem->setPos(iconMetrics.spacing, yValue); + + QGraphicsSimpleTextItem *textItem = new QGraphicsSimpleTextItem(toolTip, this); + textItem->setPos(iconMetrics.spacing + iconMetrics.sz_small + iconMetrics.spacing, yValue); + textItem->setBrush(QBrush(Qt::white)); + textItem->setFlag(ItemIgnoresTransformations); + toolTips.push_back(qMakePair(iconItem, textItem)); +} + +void ToolTipItem::clear() +{ + Q_FOREACH (ToolTip t, toolTips) { + delete t.first; + delete t.second; + } + toolTips.clear(); +} + +void ToolTipItem::setRect(const QRectF &r) +{ + if( r == rect() ) { + return; + } + + QGraphicsRectItem::setRect(r); + updateTitlePosition(); +} + +void ToolTipItem::collapse() +{ + int dim = defaultIconMetrics().sz_small; + + if (prefs.animation_speed) { + QPropertyAnimation *animation = new QPropertyAnimation(this, "rect"); + animation->setDuration(100); + animation->setStartValue(nextRectangle); + animation->setEndValue(QRect(0, 0, dim, dim)); + animation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + setRect(nextRectangle); + } + clear(); + + status = COLLAPSED; +} + +void ToolTipItem::expand() +{ + if (!title) + return; + + const IconMetrics& iconMetrics = defaultIconMetrics(); + + double width = 0, height = title->boundingRect().height() + iconMetrics.spacing; + Q_FOREACH (const ToolTip& t, toolTips) { + QRectF sRect = t.second->boundingRect(); + if (sRect.width() > width) + width = sRect.width(); + height += sRect.height(); + } + + if (entryToolTip.first) { + QRectF sRect = entryToolTip.second->boundingRect(); + if (sRect.width() > width) + width = sRect.width(); + height += sRect.height(); + } + + /* Left padding, Icon Size, space, right padding */ + width += iconMetrics.spacing + iconMetrics.sz_small + iconMetrics.spacing + iconMetrics.spacing; + + if (width < title->boundingRect().width() + iconMetrics.spacing * 2) + width = title->boundingRect().width() + iconMetrics.spacing * 2; + + if (height < iconMetrics.sz_small) + height = iconMetrics.sz_small; + + nextRectangle.setWidth(width); + nextRectangle.setHeight(height); + + if (nextRectangle != rect()) { + if (prefs.animation_speed) { + QPropertyAnimation *animation = new QPropertyAnimation(this, "rect", this); + animation->setDuration(prefs.animation_speed); + animation->setStartValue(rect()); + animation->setEndValue(nextRectangle); + animation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + setRect(nextRectangle); + } + } + + status = EXPANDED; +} + +ToolTipItem::ToolTipItem(QGraphicsItem *parent) : QGraphicsRectItem(parent), + title(new QGraphicsSimpleTextItem(tr("Information"), this)), + status(COLLAPSED), + timeAxis(0), + lastTime(-1) +{ + memset(&pInfo, 0, sizeof(pInfo)); + entryToolTip.first = NULL; + entryToolTip.second = NULL; + setFlags(ItemIgnoresTransformations | ItemIsMovable | ItemClipsChildrenToShape); + + QColor c = QColor(Qt::black); + c.setAlpha(155); + setBrush(c); + + setZValue(99); + + addToolTip(QString(), QIcon(), QPixmap(16,60)); + entryToolTip = toolTips.first(); + toolTips.clear(); + + title->setFlag(ItemIgnoresTransformations); + title->setPen(QPen(Qt::white, 1)); + title->setBrush(Qt::white); + + setPen(QPen(Qt::white, 2)); + refreshTime.start(); +} + +ToolTipItem::~ToolTipItem() +{ + clear(); +} + +void ToolTipItem::updateTitlePosition() +{ + const IconMetrics& iconMetrics = defaultIconMetrics(); + if (rect().width() < title->boundingRect().width() + iconMetrics.spacing * 4) { + QRectF newRect = rect(); + newRect.setWidth(title->boundingRect().width() + iconMetrics.spacing * 4); + newRect.setHeight((newRect.height() && isExpanded()) ? newRect.height() : iconMetrics.sz_small); + setRect(newRect); + } + + title->setPos(rect().width() / 2 - title->boundingRect().width() / 2 - 1, 0); +} + +bool ToolTipItem::isExpanded() const +{ + return status == EXPANDED; +} + +void ToolTipItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + persistPos(); + QGraphicsRectItem::mouseReleaseEvent(event); + Q_FOREACH (QGraphicsItem *item, oldSelection) { + item->setSelected(true); + } +} + +void ToolTipItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(widget); + painter->save(); + painter->setClipRect(option->rect); + painter->setPen(pen()); + painter->setBrush(brush()); + painter->drawRoundedRect(rect(), 10, 10, Qt::AbsoluteSize); + painter->restore(); +} + +void ToolTipItem::persistPos() +{ + QSettings s; + s.beginGroup("ProfileMap"); + s.setValue("tooltip_position", pos()); + s.endGroup(); +} + +void ToolTipItem::readPos() +{ + QSettings s; + s.beginGroup("ProfileMap"); + QPointF value = s.value("tooltip_position").toPoint(); + if (!scene()->sceneRect().contains(value)) { + value = QPointF(0, 0); + } + setPos(value); +} + +void ToolTipItem::setPlotInfo(const plot_info &plot) +{ + pInfo = plot; +} + +void ToolTipItem::setTimeAxis(DiveCartesianAxis *axis) +{ + timeAxis = axis; +} + +void ToolTipItem::refresh(const QPointF &pos) +{ + struct plot_data *entry; + static QPixmap tissues(16,60); + static QPainter painter(&tissues); + static struct membuffer mb = { 0 }; + + if(refreshTime.elapsed() < 40) + return; + refreshTime.start(); + + int time = timeAxis->valueAt(pos); + if (time == lastTime) + return; + + lastTime = time; + clear(); + + mb.len = 0; + entry = get_plot_details_new(&pInfo, time, &mb); + if (entry) { + tissues.fill(); + painter.setPen(QColor(0, 0, 0, 0)); + painter.setBrush(QColor(LIMENADE1)); + painter.drawRect(0, 10 + (100 - AMB_PERCENTAGE) / 2, 16, AMB_PERCENTAGE / 2); + painter.setBrush(QColor(SPRINGWOOD1)); + painter.drawRect(0, 10, 16, (100 - AMB_PERCENTAGE) / 2); + painter.setBrush(QColor(Qt::red)); + painter.drawRect(0,0,16,10); + painter.setPen(QColor(0, 0, 0, 255)); + painter.drawLine(0, 60 - entry->gfline / 2, 16, 60 - entry->gfline / 2); + painter.drawLine(0, 60 - AMB_PERCENTAGE * (entry->pressures.n2 + entry->pressures.he) / entry->ambpressure / 2, + 16, 60 - AMB_PERCENTAGE * (entry->pressures.n2 + entry->pressures.he) / entry->ambpressure /2); + painter.setPen(QColor(0, 0, 0, 127)); + for (int i=0; i<16; i++) { + painter.drawLine(i, 60, i, 60 - entry->percentages[i] / 2); + } + entryToolTip.first->setPixmap(tissues); + entryToolTip.second->setText(QString::fromUtf8(mb.buffer, mb.len)); + } + + Q_FOREACH (QGraphicsItem *item, scene()->items(pos, Qt::IntersectsItemBoundingRect + ,Qt::DescendingOrder, scene()->views().first()->transform())) { + if (!item->toolTip().isEmpty()) + addToolTip(item->toolTip()); + } + expand(); +} + +void ToolTipItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + oldSelection = scene()->selectedItems(); + scene()->clearSelection(); + QGraphicsItem::mousePressEvent(event); +} diff --git a/profile-widget/divetooltipitem.h b/profile-widget/divetooltipitem.h new file mode 100644 index 000000000..4fa7ec2d7 --- /dev/null +++ b/profile-widget/divetooltipitem.h @@ -0,0 +1,67 @@ +#ifndef DIVETOOLTIPITEM_H +#define DIVETOOLTIPITEM_H + +#include +#include +#include +#include +#include +#include +#include "display.h" + +class DiveCartesianAxis; +class QGraphicsLineItem; +class QGraphicsSimpleTextItem; +class QGraphicsPixmapItem; +struct graphics_context; + +/* 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 QGraphicsRectItem { + Q_OBJECT + void updateTitlePosition(); + Q_PROPERTY(QRectF rect READ rect WRITE setRect) + +public: + enum Status { + COLLAPSED, + EXPANDED + }; + + explicit ToolTipItem(QGraphicsItem *parent = 0); + virtual ~ToolTipItem(); + + void collapse(); + void expand(); + void clear(); + void addToolTip(const QString &toolTip, const QIcon &icon = QIcon(), const QPixmap &pixmap = QPixmap()); + void refresh(const QPointF &pos); + bool isExpanded() const; + void persistPos(); + void readPos(); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void setTimeAxis(DiveCartesianAxis *axis); + void setPlotInfo(const plot_info &plot); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); +public +slots: + void setRect(const QRectF &rect); + +private: + typedef QPair ToolTip; + QVector toolTips; + ToolTip entryToolTip; + QGraphicsSimpleTextItem *title; + Status status; + QRectF rectangle; + QRectF nextRectangle; + DiveCartesianAxis *timeAxis; + plot_info pInfo; + int lastTime; + QTime refreshTime; + QList oldSelection; +}; + +#endif // DIVETOOLTIPITEM_H diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp new file mode 100644 index 000000000..3ccd1bb6d --- /dev/null +++ b/profile-widget/profilewidget2.cpp @@ -0,0 +1,1836 @@ +#include "profilewidget2.h" +#include "diveplotdatamodel.h" +#include "helpers.h" +#include "profile.h" +#include "diveeventitem.h" +#include "divetextitem.h" +#include "divetooltipitem.h" +#include "planner.h" +#include "device.h" +#include "ruleritem.h" +#include "tankitem.h" +#include "pref.h" +#include "divepicturewidget.h" +#include "diveplannermodel.h" +#include "models.h" +#include "divepicturemodel.h" +#include "maintab.h" +#include "diveplanner.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_DEBUG +#include +#endif +#include "mainwindow.h" +#include + +/* This is the global 'Item position' variable. + * it should tell you where to position things up + * on the canvas. + * + * please, please, please, use this instead of + * hard coding the item on the scene with a random + * value. + */ +static struct _ItemPos { + struct _Pos { + QPointF on; + QPointF off; + }; + struct _Axis { + _Pos pos; + QLineF shrinked; + QLineF expanded; + QLineF intermediate; + }; + _Pos background; + _Pos dcLabel; + _Pos tankBar; + _Axis depth; + _Axis partialPressure; + _Axis partialPressureTissue; + _Axis partialPressureWithTankBar; + _Axis percentage; + _Axis percentageWithTankBar; + _Axis time; + _Axis cylinder; + _Axis temperature; + _Axis temperatureAll; + _Axis heartBeat; + _Axis heartBeatWithTankBar; +} itemPos; + +ProfileWidget2::ProfileWidget2(QWidget *parent) : QGraphicsView(parent), + currentState(INVALID), + dataModel(new DivePlotDataModel(this)), + zoomLevel(0), + zoomFactor(1.15), + background(new DivePixmapItem()), + backgroundFile(":poster"), + toolTipItem(new ToolTipItem()), + isPlotZoomed(prefs.zoomed_plot),// no! bad use of prefs. 'PreferencesDialog::loadSettings' NOT CALLED yet. + profileYAxis(new DepthAxis()), + gasYAxis(new PartialGasPressureAxis()), + temperatureAxis(new TemperatureAxis()), + timeAxis(new TimeAxis()), + diveProfileItem(new DiveProfileItem()), + temperatureItem(new DiveTemperatureItem()), + meanDepthItem(new DiveMeanDepthItem()), + cylinderPressureAxis(new DiveCartesianAxis()), + gasPressureItem(new DiveGasPressureItem()), + diveComputerText(new DiveTextItem()), + diveCeiling(new DiveCalculatedCeiling()), + gradientFactor(new DiveTextItem()), + reportedCeiling(new DiveReportedCeiling()), + pn2GasItem(new PartialPressureGasItem()), + pheGasItem(new PartialPressureGasItem()), + po2GasItem(new PartialPressureGasItem()), + o2SetpointGasItem(new PartialPressureGasItem()), + ccrsensor1GasItem(new PartialPressureGasItem()), + ccrsensor2GasItem(new PartialPressureGasItem()), + ccrsensor3GasItem(new PartialPressureGasItem()), + heartBeatAxis(new DiveCartesianAxis()), + heartBeatItem(new DiveHeartrateItem()), + percentageAxis(new DiveCartesianAxis()), + ambPressureItem(new DiveAmbPressureItem()), + gflineItem(new DiveGFLineItem()), + mouseFollowerVertical(new DiveLineItem()), + mouseFollowerHorizontal(new DiveLineItem()), + rulerItem(new RulerItem2()), + tankItem(new TankItem()), + isGrayscale(false), + printMode(false), + shouldCalculateMaxTime(true), + shouldCalculateMaxDepth(true), + fontPrintScale(1.0) +{ + // would like to be able to ASSERT here that PreferencesDialog::loadSettings has been called. + isPlotZoomed = prefs.zoomed_plot; // now it seems that 'prefs' has loaded our preferences + + memset(&plotInfo, 0, sizeof(plotInfo)); + + setupSceneAndFlags(); + setupItemSizes(); + setupItemOnScene(); + addItemsToScene(); + scene()->installEventFilter(this); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); + QAction *action = NULL; +#define ADD_ACTION(SHORTCUT, Slot) \ + action = new QAction(this); \ + action->setShortcut(SHORTCUT); \ + action->setShortcutContext(Qt::WindowShortcut); \ + addAction(action); \ + connect(action, SIGNAL(triggered(bool)), this, SLOT(Slot)); \ + actionsForKeys[SHORTCUT] = action; + + ADD_ACTION(Qt::Key_Escape, keyEscAction()); + ADD_ACTION(Qt::Key_Delete, keyDeleteAction()); + ADD_ACTION(Qt::Key_Up, keyUpAction()); + ADD_ACTION(Qt::Key_Down, keyDownAction()); + ADD_ACTION(Qt::Key_Left, keyLeftAction()); + ADD_ACTION(Qt::Key_Right, keyRightAction()); +#undef ADD_ACTION + +#if !defined(QT_NO_DEBUG) && defined(SHOW_PLOT_INFO_TABLE) + QTableView *diveDepthTableView = new QTableView(); + diveDepthTableView->setModel(dataModel); + diveDepthTableView->show(); +#endif +} + + +ProfileWidget2::~ProfileWidget2() +{ + delete background; + delete toolTipItem; + delete profileYAxis; + delete gasYAxis; + delete temperatureAxis; + delete timeAxis; + delete diveProfileItem; + delete temperatureItem; + delete meanDepthItem; + delete cylinderPressureAxis; + delete gasPressureItem; + delete diveComputerText; + delete diveCeiling; + delete reportedCeiling; + delete pn2GasItem; + delete pheGasItem; + delete po2GasItem; + delete o2SetpointGasItem; + delete ccrsensor1GasItem; + delete ccrsensor2GasItem; + delete ccrsensor3GasItem; + delete heartBeatAxis; + delete heartBeatItem; + delete percentageAxis; + delete ambPressureItem; + delete gflineItem; + delete mouseFollowerVertical; + delete mouseFollowerHorizontal; + delete rulerItem; + delete tankItem; +} + +#define SUBSURFACE_OBJ_DATA 1 +#define SUBSURFACE_OBJ_DC_TEXT 0x42 + +void ProfileWidget2::addItemsToScene() +{ + scene()->addItem(background); + scene()->addItem(toolTipItem); + scene()->addItem(profileYAxis); + scene()->addItem(gasYAxis); + scene()->addItem(temperatureAxis); + scene()->addItem(timeAxis); + scene()->addItem(diveProfileItem); + scene()->addItem(cylinderPressureAxis); + scene()->addItem(temperatureItem); + scene()->addItem(meanDepthItem); + scene()->addItem(gasPressureItem); + // I cannot seem to figure out if an object that I find with itemAt() on the scene + // is the object I am looking for - my guess is there's a simple way in Qt to do that + // but nothing I tried worked. + // so instead this adds a special magic key/value pair to the object to mark it + diveComputerText->setData(SUBSURFACE_OBJ_DATA, SUBSURFACE_OBJ_DC_TEXT); + scene()->addItem(diveComputerText); + scene()->addItem(diveCeiling); + scene()->addItem(gradientFactor); + scene()->addItem(reportedCeiling); + scene()->addItem(pn2GasItem); + scene()->addItem(pheGasItem); + scene()->addItem(po2GasItem); + scene()->addItem(o2SetpointGasItem); + scene()->addItem(ccrsensor1GasItem); + scene()->addItem(ccrsensor2GasItem); + scene()->addItem(ccrsensor3GasItem); + scene()->addItem(percentageAxis); + scene()->addItem(heartBeatAxis); + scene()->addItem(heartBeatItem); + scene()->addItem(rulerItem); + scene()->addItem(rulerItem->sourceNode()); + scene()->addItem(rulerItem->destNode()); + scene()->addItem(tankItem); + scene()->addItem(mouseFollowerHorizontal); + scene()->addItem(mouseFollowerVertical); + QPen pen(QColor(Qt::red).lighter()); + pen.setWidth(0); + mouseFollowerHorizontal->setPen(pen); + mouseFollowerVertical->setPen(pen); + Q_FOREACH (DiveCalculatedTissue *tissue, allTissues) { + scene()->addItem(tissue); + } + Q_FOREACH (DivePercentageItem *percentage, allPercentages) { + scene()->addItem(percentage); + } + scene()->addItem(ambPressureItem); + scene()->addItem(gflineItem); +} + +void ProfileWidget2::setupItemOnScene() +{ + background->setZValue(9999); + toolTipItem->setZValue(9998); + toolTipItem->setTimeAxis(timeAxis); + rulerItem->setZValue(9997); + tankItem->setZValue(100); + + profileYAxis->setOrientation(DiveCartesianAxis::TopToBottom); + profileYAxis->setMinimum(0); + profileYAxis->setTickInterval(M_OR_FT(10, 30)); + profileYAxis->setTickSize(0.5); + profileYAxis->setLineSize(96); + + timeAxis->setLineSize(92); + timeAxis->setTickSize(-0.5); + + gasYAxis->setOrientation(DiveCartesianAxis::BottomToTop); + gasYAxis->setTickInterval(1); + gasYAxis->setTickSize(1); + gasYAxis->setMinimum(0); + gasYAxis->setModel(dataModel); + gasYAxis->setFontLabelScale(0.7); + gasYAxis->setLineSize(96); + + heartBeatAxis->setOrientation(DiveCartesianAxis::BottomToTop); + heartBeatAxis->setTickSize(0.2); + heartBeatAxis->setTickInterval(10); + heartBeatAxis->setFontLabelScale(0.7); + heartBeatAxis->setLineSize(96); + + percentageAxis->setOrientation(DiveCartesianAxis::BottomToTop); + percentageAxis->setTickSize(0.2); + percentageAxis->setTickInterval(10); + percentageAxis->setFontLabelScale(0.7); + percentageAxis->setLineSize(96); + + temperatureAxis->setOrientation(DiveCartesianAxis::BottomToTop); + temperatureAxis->setTickSize(2); + temperatureAxis->setTickInterval(300); + + cylinderPressureAxis->setOrientation(DiveCartesianAxis::BottomToTop); + cylinderPressureAxis->setTickSize(2); + cylinderPressureAxis->setTickInterval(30000); + + + diveComputerText->setAlignment(Qt::AlignRight | Qt::AlignTop); + diveComputerText->setBrush(getColor(TIME_TEXT, isGrayscale)); + + rulerItem->setAxis(timeAxis, profileYAxis); + tankItem->setHorizontalAxis(timeAxis); + + // show the gradient factor at the top in the center + gradientFactor->setY(0); + gradientFactor->setX(50); + gradientFactor->setBrush(getColor(PRESSURE_TEXT)); + gradientFactor->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); + + setupItem(reportedCeiling, timeAxis, profileYAxis, dataModel, DivePlotDataModel::CEILING, DivePlotDataModel::TIME, 1); + setupItem(diveCeiling, timeAxis, profileYAxis, dataModel, DivePlotDataModel::CEILING, DivePlotDataModel::TIME, 1); + for (int i = 0; i < 16; i++) { + DiveCalculatedTissue *tissueItem = new DiveCalculatedTissue(); + setupItem(tissueItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::TISSUE_1 + i, DivePlotDataModel::TIME, 1 + i); + allTissues.append(tissueItem); + DivePercentageItem *percentageItem = new DivePercentageItem(i); + setupItem(percentageItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::PERCENTAGE_1 + i, DivePlotDataModel::TIME, 1 + i); + allPercentages.append(percentageItem); + } + setupItem(gasPressureItem, timeAxis, cylinderPressureAxis, dataModel, DivePlotDataModel::TEMPERATURE, DivePlotDataModel::TIME, 1); + setupItem(temperatureItem, timeAxis, temperatureAxis, dataModel, DivePlotDataModel::TEMPERATURE, DivePlotDataModel::TIME, 1); + setupItem(heartBeatItem, timeAxis, heartBeatAxis, dataModel, DivePlotDataModel::HEARTBEAT, DivePlotDataModel::TIME, 1); + setupItem(ambPressureItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::AMBPRESSURE, DivePlotDataModel::TIME, 1); + setupItem(gflineItem, timeAxis, percentageAxis, dataModel, DivePlotDataModel::GFLINE, DivePlotDataModel::TIME, 1); + setupItem(diveProfileItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::DEPTH, DivePlotDataModel::TIME, 0); + setupItem(meanDepthItem, timeAxis, profileYAxis, dataModel, DivePlotDataModel::INSTANT_MEANDEPTH, DivePlotDataModel::TIME, 1); + + +#define CREATE_PP_GAS(ITEM, VERTICAL_COLUMN, COLOR, COLOR_ALERT, THRESHOULD_SETTINGS, VISIBILITY_SETTINGS) \ + setupItem(ITEM, timeAxis, gasYAxis, dataModel, DivePlotDataModel::VERTICAL_COLUMN, DivePlotDataModel::TIME, 0); \ + ITEM->setThreshouldSettingsKey(THRESHOULD_SETTINGS); \ + ITEM->setVisibilitySettingsKey(VISIBILITY_SETTINGS); \ + ITEM->setColors(getColor(COLOR, isGrayscale), getColor(COLOR_ALERT, isGrayscale)); \ + ITEM->settingsChanged(); \ + ITEM->setZValue(99); + + CREATE_PP_GAS(pn2GasItem, PN2, PN2, PN2_ALERT, &prefs.pp_graphs.pn2_threshold, "pn2graph"); + CREATE_PP_GAS(pheGasItem, PHE, PHE, PHE_ALERT, &prefs.pp_graphs.phe_threshold, "phegraph"); + CREATE_PP_GAS(po2GasItem, PO2, PO2, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "po2graph"); + CREATE_PP_GAS(o2SetpointGasItem, O2SETPOINT, PO2_ALERT, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "po2graph"); + CREATE_PP_GAS(ccrsensor1GasItem, CCRSENSOR1, CCRSENSOR1, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); + CREATE_PP_GAS(ccrsensor2GasItem, CCRSENSOR2, CCRSENSOR2, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); + CREATE_PP_GAS(ccrsensor3GasItem, CCRSENSOR3, CCRSENSOR3, PO2_ALERT, &prefs.pp_graphs.po2_threshold, "ccrsensorgraph"); +#undef CREATE_PP_GAS + + temperatureAxis->setTextVisible(false); + temperatureAxis->setLinesVisible(false); + cylinderPressureAxis->setTextVisible(false); + cylinderPressureAxis->setLinesVisible(false); + timeAxis->setLinesVisible(true); + profileYAxis->setLinesVisible(true); + gasYAxis->setZValue(timeAxis->zValue() + 1); + heartBeatAxis->setTextVisible(true); + heartBeatAxis->setLinesVisible(true); + percentageAxis->setTextVisible(true); + percentageAxis->setLinesVisible(true); + + replotEnabled = true; +} + +void ProfileWidget2::replot(struct dive *d) +{ + if (!replotEnabled) + return; + dataModel->clear(); + plotDive(d, true); +} + +void ProfileWidget2::setupItemSizes() +{ + // Scene is *always* (double) 100 / 100. + // Background Config + /* Much probably a better math is needed here. + * good thing is that we only need to change the + * Axis and everything else is auto-adjusted.* + */ + + itemPos.background.on.setX(0); + itemPos.background.on.setY(0); + itemPos.background.off.setX(0); + itemPos.background.off.setY(110); + + //Depth Axis Config + itemPos.depth.pos.on.setX(3); + itemPos.depth.pos.on.setY(3); + itemPos.depth.pos.off.setX(-2); + itemPos.depth.pos.off.setY(3); + itemPos.depth.expanded.setP1(QPointF(0, 0)); + itemPos.depth.expanded.setP2(QPointF(0, 85)); + itemPos.depth.shrinked.setP1(QPointF(0, 0)); + itemPos.depth.shrinked.setP2(QPointF(0, 55)); + itemPos.depth.intermediate.setP1(QPointF(0, 0)); + itemPos.depth.intermediate.setP2(QPointF(0, 65)); + + // Time Axis Config + itemPos.time.pos.on.setX(3); + itemPos.time.pos.on.setY(95); + itemPos.time.pos.off.setX(3); + itemPos.time.pos.off.setY(110); + itemPos.time.expanded.setP1(QPointF(0, 0)); + itemPos.time.expanded.setP2(QPointF(94, 0)); + + // Partial Gas Axis Config + itemPos.partialPressure.pos.on.setX(97); + itemPos.partialPressure.pos.on.setY(75); + itemPos.partialPressure.pos.off.setX(110); + itemPos.partialPressure.pos.off.setY(63); + itemPos.partialPressure.expanded.setP1(QPointF(0, 0)); + itemPos.partialPressure.expanded.setP2(QPointF(0, 19)); + itemPos.partialPressureWithTankBar = itemPos.partialPressure; + itemPos.partialPressureWithTankBar.expanded.setP2(QPointF(0, 17)); + itemPos.partialPressureTissue = itemPos.partialPressure; + itemPos.partialPressureTissue.pos.on.setX(97); + itemPos.partialPressureTissue.pos.on.setY(65); + itemPos.partialPressureTissue.expanded.setP2(QPointF(0, 16)); + + // cylinder axis config + itemPos.cylinder.pos.on.setX(3); + itemPos.cylinder.pos.on.setY(20); + itemPos.cylinder.pos.off.setX(-10); + itemPos.cylinder.pos.off.setY(20); + itemPos.cylinder.expanded.setP1(QPointF(0, 15)); + itemPos.cylinder.expanded.setP2(QPointF(0, 50)); + itemPos.cylinder.shrinked.setP1(QPointF(0, 0)); + itemPos.cylinder.shrinked.setP2(QPointF(0, 20)); + itemPos.cylinder.intermediate.setP1(QPointF(0, 0)); + itemPos.cylinder.intermediate.setP2(QPointF(0, 20)); + + // Temperature axis config + itemPos.temperature.pos.on.setX(3); + itemPos.temperature.pos.on.setY(60); + itemPos.temperatureAll.pos.on.setY(51); + itemPos.temperature.pos.off.setX(-10); + itemPos.temperature.pos.off.setY(40); + itemPos.temperature.expanded.setP1(QPointF(0, 20)); + itemPos.temperature.expanded.setP2(QPointF(0, 33)); + itemPos.temperature.shrinked.setP1(QPointF(0, 2)); + itemPos.temperature.shrinked.setP2(QPointF(0, 12)); + itemPos.temperature.intermediate.setP1(QPointF(0, 2)); + itemPos.temperature.intermediate.setP2(QPointF(0, 12)); + + // Heartbeat axis config + itemPos.heartBeat.pos.on.setX(3); + itemPos.heartBeat.pos.on.setY(82); + itemPos.heartBeat.expanded.setP1(QPointF(0, 0)); + itemPos.heartBeat.expanded.setP2(QPointF(0, 10)); + itemPos.heartBeatWithTankBar = itemPos.heartBeat; + itemPos.heartBeatWithTankBar.expanded.setP2(QPointF(0, 7)); + + // Percentage axis config + itemPos.percentage.pos.on.setX(3); + itemPos.percentage.pos.on.setY(80); + itemPos.percentage.expanded.setP1(QPointF(0, 0)); + itemPos.percentage.expanded.setP2(QPointF(0, 15)); + itemPos.percentageWithTankBar = itemPos.percentage; + itemPos.percentageWithTankBar.expanded.setP2(QPointF(0, 12)); + + itemPos.dcLabel.on.setX(3); + itemPos.dcLabel.on.setY(100); + itemPos.dcLabel.off.setX(-10); + itemPos.dcLabel.off.setY(100); + + itemPos.tankBar.on.setX(0); + itemPos.tankBar.on.setY(91.5); +} + +void ProfileWidget2::setupItem(AbstractProfilePolygonItem *item, DiveCartesianAxis *hAxis, + DiveCartesianAxis *vAxis, DivePlotDataModel *model, + int vData, int hData, int zValue) +{ + item->setHorizontalAxis(hAxis); + item->setVerticalAxis(vAxis); + item->setModel(model); + item->setVerticalDataColumn(vData); + item->setHorizontalDataColumn(hData); + item->setZValue(zValue); +} + +void ProfileWidget2::setupSceneAndFlags() +{ + setScene(new QGraphicsScene(this)); + scene()->setSceneRect(0, 0, 100, 100); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scene()->setItemIndexMethod(QGraphicsScene::NoIndex); + setOptimizationFlags(QGraphicsView::DontSavePainterState); + setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); + setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); + setMouseTracking(true); + background->setFlag(QGraphicsItem::ItemIgnoresTransformations); +} + +void ProfileWidget2::resetZoom() +{ + if (!zoomLevel) + return; + const qreal defScale = 1.0 / qPow(zoomFactor, (qreal)zoomLevel); + scale(defScale, defScale); + zoomLevel = 0; +} + +// Currently just one dive, but the plan is to enable All of the selected dives. +void ProfileWidget2::plotDive(struct dive *d, bool force) +{ + static bool firstCall = true; + QTime measureDuration; // let's measure how long this takes us (maybe we'll turn of TTL calculation later + measureDuration.start(); + + if (currentState != ADD && currentState != PLAN) { + if (!d) { + if (selected_dive == -1) + return; + d = current_dive; // display the current dive + } + + // No need to do this again if we are already showing the same dive + // computer of the same dive, so we check the unique id of the dive + // and the selected dive computer number against the ones we are + // showing (can't compare the dive pointers as those might change). + if (d->id == displayed_dive.id && dc_number == dataModel->dcShown() && !force) + return; + + // this copies the dive and makes copies of all the relevant additional data + copy_dive(d, &displayed_dive); + gradientFactor->setText(QString("GF %1/%2").arg(prefs.gflow).arg(prefs.gfhigh)); + } else { + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + plannerModel->createTemporaryPlan(); + struct diveplan &diveplan = plannerModel->getDiveplan(); + if (!diveplan.dp) { + plannerModel->deleteTemporaryPlan(); + return; + } + gradientFactor->setText(QString("GF %1/%2").arg(diveplan.gflow).arg(diveplan.gfhigh)); + } + + // special handling for the first time we display things + int animSpeedBackup = 0; + if (firstCall && MainWindow::instance()->filesFromCommandLine()) { + animSpeedBackup = prefs.animation_speed; + prefs.animation_speed = 0; + firstCall = false; + } + + // restore default zoom level + resetZoom(); + + // reset some item visibility on printMode changes + toolTipItem->setVisible(!printMode); + rulerItem->setVisible(prefs.rulergraph && !printMode && currentState != PLAN && currentState != ADD); + + if (currentState == EMPTY) + setProfileState(); + + // next get the dive computer structure - if there are no samples + // let's create a fake profile that's somewhat reasonable for the + // data that we have + struct divecomputer *currentdc = select_dc(&displayed_dive); + Q_ASSERT(currentdc); + if (!currentdc || !currentdc->samples) { + currentdc = fake_dc(currentdc); + } + + bool setpointflag = (currentdc->divemode == CCR) && prefs.pp_graphs.po2 && current_dive; + bool sensorflag = setpointflag && prefs.show_ccr_sensors; + o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint); + ccrsensor1GasItem->setVisible(sensorflag); + ccrsensor2GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 1)); + ccrsensor3GasItem->setVisible(sensorflag && (currentdc->no_o2sensors > 2)); + + /* This struct holds all the data that's about to be plotted. + * I'm not sure this is the best approach ( but since we are + * interpolating some points of the Dive, maybe it is... ) + * The Calculation of the points should be done per graph, + * so I'll *not* calculate everything if something is not being + * shown. + */ + plotInfo = calculate_max_limits_new(&displayed_dive, currentdc); + create_plot_info_new(&displayed_dive, currentdc, &plotInfo, !shouldCalculateMaxDepth); + if (shouldCalculateMaxTime) + maxtime = get_maxtime(&plotInfo); + + /* Only update the max depth if it's bigger than the current ones + * when we are dragging the handler to plan / add dive. + * otherwhise, update normally. + */ + int newMaxDepth = get_maxdepth(&plotInfo); + if (!shouldCalculateMaxDepth) { + if (maxdepth < newMaxDepth) { + maxdepth = newMaxDepth; + } + } else { + maxdepth = newMaxDepth; + } + + dataModel->setDive(&displayed_dive, plotInfo); + toolTipItem->setPlotInfo(plotInfo); + + // It seems that I'll have a lot of boilerplate setting the model / axis for + // each item, I'll mostly like to fix this in the future, but I'll keep at this for now. + profileYAxis->setMaximum(maxdepth); + profileYAxis->updateTicks(); + + temperatureAxis->setMinimum(plotInfo.mintemp); + temperatureAxis->setMaximum(plotInfo.maxtemp - plotInfo.mintemp > 2000 ? plotInfo.maxtemp : plotInfo.mintemp + 2000); + + if (plotInfo.maxhr) { + heartBeatAxis->setMinimum(plotInfo.minhr); + heartBeatAxis->setMaximum(plotInfo.maxhr); + heartBeatAxis->updateTicks(HR_AXIS); // this shows the ticks + } + heartBeatAxis->setVisible(prefs.hrgraph && plotInfo.maxhr); + + percentageAxis->setMinimum(0); + percentageAxis->setMaximum(100); + percentageAxis->setVisible(false); + percentageAxis->updateTicks(HR_AXIS); + + timeAxis->setMaximum(maxtime); + int i, incr; + static int increments[8] = { 10, 20, 30, 60, 5 * 60, 10 * 60, 15 * 60, 30 * 60 }; + /* 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 (i < 7 && maxtime / increments[i] > 12) + i++; + incr = increments[i]; + while (maxtime / incr > 12) + incr *= 2; + timeAxis->setTickInterval(incr); + timeAxis->updateTicks(); + cylinderPressureAxis->setMinimum(plotInfo.minpressure); + cylinderPressureAxis->setMaximum(plotInfo.maxpressure); + + rulerItem->setPlotInfo(plotInfo); + tankItem->setData(dataModel, &plotInfo, &displayed_dive); + + dataModel->emitDataChanged(); + // The event items are a bit special since we don't know how many events are going to + // exist on a dive, so I cant create cache items for that. that's why they are here + // while all other items are up there on the constructor. + qDeleteAll(eventItems); + eventItems.clear(); + struct event *event = currentdc->events; + while (event) { + // if print mode is selected only draw headings, SP change, gas events or bookmark event + if (printMode) { + if (same_string(event->name, "") || + !(strcmp(event->name, "heading") == 0 || + (same_string(event->name, "SP change") && event->time.seconds == 0) || + event_is_gaschange(event) || + event->type == SAMPLE_EVENT_BOOKMARK)) { + event = event->next; + continue; + } + } + DiveEventItem *item = new DiveEventItem(); + item->setHorizontalAxis(timeAxis); + item->setVerticalAxis(profileYAxis); + item->setModel(dataModel); + item->setEvent(event); + item->setZValue(2); + scene()->addItem(item); + eventItems.push_back(item); + event = event->next; + } + // Only set visible the events that should be visible + Q_FOREACH (DiveEventItem *event, eventItems) { + event->setVisible(!event->shouldBeHidden()); + } + QString dcText = get_dc_nickname(currentdc->model, currentdc->deviceid); + int nr; + if ((nr = number_of_computers(&displayed_dive)) > 1) + dcText += tr(" (#%1 of %2)").arg(dc_number + 1).arg(nr); + if (dcText.isEmpty()) + dcText = tr("Unknown dive computer"); + diveComputerText->setText(dcText); + if (MainWindow::instance()->filesFromCommandLine() && animSpeedBackup != 0) { + prefs.animation_speed = animSpeedBackup; + } + + if (currentState == ADD || currentState == PLAN) { // TODO: figure a way to move this from here. + repositionDiveHandlers(); + DivePlannerPointsModel *model = DivePlannerPointsModel::instance(); + model->deleteTemporaryPlan(); + } + plotPictures(); + + // OK, how long did this take us? Anything above the second is way too long, + // so if we are calculation TTS / NDL then let's force that off. + if (measureDuration.elapsed() > 1000 && prefs.calcndltts) { + MainWindow::instance()->turnOffNdlTts(); + MainWindow::instance()->getNotificationWidget()->showNotification(tr("Show NDL / TTS was disabled because of excessive processing time"), KMessageWidget::Error); + } + MainWindow::instance()->getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + +} + +void ProfileWidget2::recalcCeiling() +{ + diveCeiling->recalc(); +} + +void ProfileWidget2::settingsChanged() +{ + // if we are showing calculated ceilings then we have to replot() + // because the GF could have changed; otherwise we try to avoid replot() + bool needReplot = prefs.calcceiling; + if ((prefs.percentagegraph||prefs.hrgraph) && PP_GRAPHS_ENABLED) { + profileYAxis->animateChangeLine(itemPos.depth.shrinked); + temperatureAxis->setPos(itemPos.temperatureAll.pos.on); + temperatureAxis->animateChangeLine(itemPos.temperature.shrinked); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.shrinked); + + if (prefs.tankbar) { + percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); + percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); + heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); + }else { + percentageAxis->setPos(itemPos.percentage.pos.on); + percentageAxis->animateChangeLine(itemPos.percentage.expanded); + heartBeatAxis->setPos(itemPos.heartBeat.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); + } + gasYAxis->setPos(itemPos.partialPressureTissue.pos.on); + gasYAxis->animateChangeLine(itemPos.partialPressureTissue.expanded); + + } else if (PP_GRAPHS_ENABLED || prefs.hrgraph || prefs.percentagegraph) { + profileYAxis->animateChangeLine(itemPos.depth.intermediate); + temperatureAxis->setPos(itemPos.temperature.pos.on); + temperatureAxis->animateChangeLine(itemPos.temperature.intermediate); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.intermediate); + if (prefs.tankbar) { + percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); + percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); + gasYAxis->setPos(itemPos.partialPressureWithTankBar.pos.on); + gasYAxis->setLine(itemPos.partialPressureWithTankBar.expanded); + heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); + } else { + gasYAxis->setPos(itemPos.partialPressure.pos.on); + gasYAxis->animateChangeLine(itemPos.partialPressure.expanded); + percentageAxis->setPos(itemPos.percentage.pos.on); + percentageAxis->setLine(itemPos.percentage.expanded); + heartBeatAxis->setPos(itemPos.heartBeat.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); + } + } else { + profileYAxis->animateChangeLine(itemPos.depth.expanded); + if (prefs.tankbar) { + temperatureAxis->setPos(itemPos.temperatureAll.pos.on); + } else { + temperatureAxis->setPos(itemPos.temperature.pos.on); + } + temperatureAxis->animateChangeLine(itemPos.temperature.expanded); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.expanded); + } + + tankItem->setVisible(prefs.tankbar); + if (prefs.zoomed_plot != isPlotZoomed) { + isPlotZoomed = prefs.zoomed_plot; + needReplot = true; + } + if (needReplot) + replot(); +} + +void ProfileWidget2::resizeEvent(QResizeEvent *event) +{ + QGraphicsView::resizeEvent(event); + fitInView(sceneRect(), Qt::IgnoreAspectRatio); + fixBackgroundPos(); +} + +void ProfileWidget2::mousePressEvent(QMouseEvent *event) +{ + if (zoomLevel) + return; + QGraphicsView::mousePressEvent(event); + if (currentState == PLAN) + shouldCalculateMaxTime = false; +} + +void ProfileWidget2::divePlannerHandlerClicked() +{ + if (zoomLevel) + return; + shouldCalculateMaxDepth = false; + replot(); +} + +void ProfileWidget2::divePlannerHandlerReleased() +{ + if (zoomLevel) + return; + shouldCalculateMaxDepth = true; + replot(); +} + +void ProfileWidget2::mouseReleaseEvent(QMouseEvent *event) +{ + if (zoomLevel) + return; + QGraphicsView::mouseReleaseEvent(event); + if (currentState == PLAN) { + shouldCalculateMaxTime = true; + replot(); + } +} + +void ProfileWidget2::fixBackgroundPos() +{ + static QPixmap toBeScaled(backgroundFile); + if (currentState != EMPTY) + return; + QPixmap p = toBeScaled.scaledToHeight(viewport()->height() - 40, Qt::SmoothTransformation); + int x = viewport()->width() / 2 - p.width() / 2; + int y = viewport()->height() / 2 - p.height() / 2; + background->setPixmap(p); + background->setX(mapToScene(x, 0).x()); + background->setY(mapToScene(y, 20).y()); +} + +void ProfileWidget2::wheelEvent(QWheelEvent *event) +{ + if (currentState == EMPTY) + return; + QPoint toolTipPos = mapFromScene(toolTipItem->pos()); + if (event->buttons() == Qt::LeftButton) + return; + if (event->delta() > 0 && zoomLevel < 20) { + scale(zoomFactor, zoomFactor); + zoomLevel++; + } else if (event->delta() < 0 && zoomLevel > 0) { + // Zooming out + scale(1.0 / zoomFactor, 1.0 / zoomFactor); + zoomLevel--; + } + scrollViewTo(event->pos()); + toolTipItem->setPos(mapToScene(toolTipPos)); +} + +void ProfileWidget2::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (currentState == PLAN || currentState == ADD) { + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + QPointF mappedPos = mapToScene(event->pos()); + if (isPointOutOfBoundaries(mappedPos)) + return; + + int minutes = rint(timeAxis->valueAt(mappedPos) / 60); + int milimeters = rint(profileYAxis->valueAt(mappedPos) / M_OR_FT(1, 1)) * M_OR_FT(1, 1); + plannerModel->addStop(milimeters, minutes * 60, 0, 0, true); + } +} + +bool ProfileWidget2::isPointOutOfBoundaries(const QPointF &point) const +{ + double xpos = timeAxis->valueAt(point); + double ypos = profileYAxis->valueAt(point); + return (xpos > timeAxis->maximum() || + xpos < timeAxis->minimum() || + ypos > profileYAxis->maximum() || + ypos < profileYAxis->minimum()); +} + +void ProfileWidget2::scrollViewTo(const QPoint &pos) +{ + /* since we cannot use translate() directly on the scene we hack on + * the scroll bars (hidden) functionality */ + if (!zoomLevel || currentState == EMPTY) + return; + QScrollBar *vs = verticalScrollBar(); + QScrollBar *hs = horizontalScrollBar(); + const qreal yRat = (qreal)pos.y() / viewport()->height(); + const qreal xRat = (qreal)pos.x() / viewport()->width(); + vs->setValue(yRat * vs->maximum()); + hs->setValue(xRat * hs->maximum()); +} + +void ProfileWidget2::mouseMoveEvent(QMouseEvent *event) +{ + QPointF pos = mapToScene(event->pos()); + toolTipItem->refresh(pos); + if (zoomLevel == 0) { + QGraphicsView::mouseMoveEvent(event); + } else { + QPoint toolTipPos = mapFromScene(toolTipItem->pos()); + scrollViewTo(event->pos()); + toolTipItem->setPos(mapToScene(toolTipPos)); + } + + qreal vValue = profileYAxis->valueAt(pos); + qreal hValue = timeAxis->valueAt(pos); + if (profileYAxis->maximum() >= vValue && profileYAxis->minimum() <= vValue) { + mouseFollowerHorizontal->setPos(timeAxis->pos().x(), pos.y()); + } + if (timeAxis->maximum() >= hValue && timeAxis->minimum() <= hValue) { + mouseFollowerVertical->setPos(pos.x(), profileYAxis->line().y1()); + } +} + +bool ProfileWidget2::eventFilter(QObject *object, QEvent *event) +{ + QGraphicsScene *s = qobject_cast(object); + if (s && event->type() == QEvent::GraphicsSceneHelp) { + event->ignore(); + return true; + } + return QGraphicsView::eventFilter(object, event); +} + +void ProfileWidget2::setEmptyState() +{ + // Then starting Empty State, move the background up. + if (currentState == EMPTY) + return; + + disconnectTemporaryConnections(); + setBackgroundBrush(getColor(::BACKGROUND, isGrayscale)); + dataModel->clear(); + currentState = EMPTY; + MainWindow::instance()->setEnabledToolbar(false); + + fixBackgroundPos(); + background->setVisible(true); + + profileYAxis->setVisible(false); + gasYAxis->setVisible(false); + timeAxis->setVisible(false); + temperatureAxis->setVisible(false); + cylinderPressureAxis->setVisible(false); + toolTipItem->setVisible(false); + diveComputerText->setVisible(false); + diveCeiling->setVisible(false); + gradientFactor->setVisible(false); + reportedCeiling->setVisible(false); + rulerItem->setVisible(false); + tankItem->setVisible(false); + pn2GasItem->setVisible(false); + po2GasItem->setVisible(false); + o2SetpointGasItem->setVisible(false); + ccrsensor1GasItem->setVisible(false); + ccrsensor2GasItem->setVisible(false); + ccrsensor3GasItem->setVisible(false); + pheGasItem->setVisible(false); + ambPressureItem->setVisible(false); + gflineItem->setVisible(false); + mouseFollowerHorizontal->setVisible(false); + mouseFollowerVertical->setVisible(false); + +#define HIDE_ALL(TYPE, CONTAINER) \ + Q_FOREACH (TYPE *item, CONTAINER) item->setVisible(false); + HIDE_ALL(DiveCalculatedTissue, allTissues); + HIDE_ALL(DivePercentageItem, allPercentages); + HIDE_ALL(DiveEventItem, eventItems); + HIDE_ALL(DiveHandler, handles); + HIDE_ALL(QGraphicsSimpleTextItem, gases); +#undef HIDE_ALL +} + +void ProfileWidget2::setProfileState() +{ + // Then starting Empty State, move the background up. + if (currentState == PROFILE) + return; + + disconnectTemporaryConnections(); + connect(DivePictureModel::instance(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(plotPictures())); + connect(DivePictureModel::instance(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(plotPictures())); + connect(DivePictureModel::instance(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(plotPictures())); + /* show the same stuff that the profile shows. */ + + //TODO: Move the DC handling to another method. + MainWindow::instance()->enableShortcuts(); + + currentState = PROFILE; + MainWindow::instance()->setEnabledToolbar(true); + toolTipItem->readPos(); + setBackgroundBrush(getColor(::BACKGROUND, isGrayscale)); + + background->setVisible(false); + toolTipItem->setVisible(true); + profileYAxis->setVisible(true); + gasYAxis->setVisible(true); + timeAxis->setVisible(true); + temperatureAxis->setVisible(true); + cylinderPressureAxis->setVisible(true); + + profileYAxis->setPos(itemPos.depth.pos.on); + if ((prefs.percentagegraph||prefs.hrgraph) && PP_GRAPHS_ENABLED) { + profileYAxis->animateChangeLine(itemPos.depth.shrinked); + temperatureAxis->setPos(itemPos.temperatureAll.pos.on); + temperatureAxis->animateChangeLine(itemPos.temperature.shrinked); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.shrinked); + + if (prefs.tankbar) { + percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); + percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); + heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); + }else { + percentageAxis->setPos(itemPos.percentage.pos.on); + percentageAxis->animateChangeLine(itemPos.percentage.expanded); + heartBeatAxis->setPos(itemPos.heartBeat.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); + } + gasYAxis->setPos(itemPos.partialPressureTissue.pos.on); + gasYAxis->animateChangeLine(itemPos.partialPressureTissue.expanded); + + } else if (PP_GRAPHS_ENABLED || prefs.hrgraph || prefs.percentagegraph) { + profileYAxis->animateChangeLine(itemPos.depth.intermediate); + temperatureAxis->setPos(itemPos.temperature.pos.on); + temperatureAxis->animateChangeLine(itemPos.temperature.intermediate); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.intermediate); + if (prefs.tankbar) { + percentageAxis->setPos(itemPos.percentageWithTankBar.pos.on); + percentageAxis->animateChangeLine(itemPos.percentageWithTankBar.expanded); + gasYAxis->setPos(itemPos.partialPressureWithTankBar.pos.on); + gasYAxis->setLine(itemPos.partialPressureWithTankBar.expanded); + heartBeatAxis->setPos(itemPos.heartBeatWithTankBar.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeatWithTankBar.expanded); + } else { + gasYAxis->setPos(itemPos.partialPressure.pos.on); + gasYAxis->animateChangeLine(itemPos.partialPressure.expanded); + percentageAxis->setPos(itemPos.percentage.pos.on); + percentageAxis->setLine(itemPos.percentage.expanded); + heartBeatAxis->setPos(itemPos.heartBeat.pos.on); + heartBeatAxis->animateChangeLine(itemPos.heartBeat.expanded); + } + } else { + profileYAxis->animateChangeLine(itemPos.depth.expanded); + if (prefs.tankbar) { + temperatureAxis->setPos(itemPos.temperatureAll.pos.on); + } else { + temperatureAxis->setPos(itemPos.temperature.pos.on); + } + temperatureAxis->animateChangeLine(itemPos.temperature.expanded); + cylinderPressureAxis->animateChangeLine(itemPos.cylinder.expanded); + } + pn2GasItem->setVisible(prefs.pp_graphs.pn2); + po2GasItem->setVisible(prefs.pp_graphs.po2); + pheGasItem->setVisible(prefs.pp_graphs.phe); + + bool setpointflag = current_dive && (current_dc->divemode == CCR) && prefs.pp_graphs.po2; + bool sensorflag = setpointflag && prefs.show_ccr_sensors; + o2SetpointGasItem->setVisible(setpointflag && prefs.show_ccr_setpoint); + ccrsensor1GasItem->setVisible(sensorflag); + ccrsensor2GasItem->setVisible(sensorflag && (current_dc->no_o2sensors > 1)); + ccrsensor3GasItem->setVisible(sensorflag && (current_dc->no_o2sensors > 2)); + + timeAxis->setPos(itemPos.time.pos.on); + timeAxis->setLine(itemPos.time.expanded); + + cylinderPressureAxis->setPos(itemPos.cylinder.pos.on); + heartBeatItem->setVisible(prefs.hrgraph); + meanDepthItem->setVisible(prefs.show_average_depth); + + diveComputerText->setVisible(true); + diveComputerText->setPos(itemPos.dcLabel.on); + + diveCeiling->setVisible(prefs.calcceiling); + gradientFactor->setVisible(prefs.calcceiling); + reportedCeiling->setVisible(prefs.dcceiling); + + if (prefs.calcalltissues) { + Q_FOREACH (DiveCalculatedTissue *tissue, allTissues) { + tissue->setVisible(true); + } + } + + if (prefs.percentagegraph) { + Q_FOREACH (DivePercentageItem *percentage, allPercentages) { + percentage->setVisible(true); + } + + ambPressureItem->setVisible(true); + gflineItem->setVisible(true); + } + + rulerItem->setVisible(prefs.rulergraph); + tankItem->setVisible(prefs.tankbar); + tankItem->setPos(itemPos.tankBar.on); + +#define HIDE_ALL(TYPE, CONTAINER) \ + Q_FOREACH (TYPE *item, CONTAINER) item->setVisible(false); + HIDE_ALL(DiveHandler, handles); + HIDE_ALL(QGraphicsSimpleTextItem, gases); +#undef HIDE_ALL + mouseFollowerHorizontal->setVisible(false); + mouseFollowerVertical->setVisible(false); +} + +void ProfileWidget2::clearHandlers() +{ + if (handles.count()) { + foreach (DiveHandler *handle, handles) { + scene()->removeItem(handle); + delete handle; + } + handles.clear(); + } +} + +void ProfileWidget2::setToolTipVisibile(bool visible) +{ + toolTipItem->setVisible(visible); +} + +void ProfileWidget2::setAddState() +{ + if (currentState == ADD) + return; + + clearHandlers(); + setProfileState(); + mouseFollowerHorizontal->setVisible(true); + mouseFollowerVertical->setVisible(true); + mouseFollowerHorizontal->setLine(timeAxis->line()); + mouseFollowerVertical->setLine(QLineF(0, profileYAxis->pos().y(), 0, timeAxis->pos().y())); + disconnectTemporaryConnections(); + //TODO: Move this method to another place, shouldn't be on mainwindow. + MainWindow::instance()->disableShortcuts(false); + actionsForKeys[Qt::Key_Left]->setShortcut(Qt::Key_Left); + actionsForKeys[Qt::Key_Right]->setShortcut(Qt::Key_Right); + actionsForKeys[Qt::Key_Up]->setShortcut(Qt::Key_Up); + actionsForKeys[Qt::Key_Down]->setShortcut(Qt::Key_Down); + actionsForKeys[Qt::Key_Escape]->setShortcut(Qt::Key_Escape); + actionsForKeys[Qt::Key_Delete]->setShortcut(Qt::Key_Delete); + + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + connect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot())); + connect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot())); + connect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(pointInserted(const QModelIndex &, int, int))); + connect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(pointsRemoved(const QModelIndex &, int, int))); + /* show the same stuff that the profile shows. */ + currentState = ADD; /* enable the add state. */ + diveCeiling->setVisible(true); + gradientFactor->setVisible(true); + setBackgroundBrush(QColor("#A7DCFF")); +} + +void ProfileWidget2::setPlanState() +{ + if (currentState == PLAN) + return; + + setProfileState(); + mouseFollowerHorizontal->setVisible(true); + mouseFollowerVertical->setVisible(true); + mouseFollowerHorizontal->setLine(timeAxis->line()); + mouseFollowerVertical->setLine(QLineF(0, profileYAxis->pos().y(), 0, timeAxis->pos().y())); + disconnectTemporaryConnections(); + //TODO: Move this method to another place, shouldn't be on mainwindow. + MainWindow::instance()->disableShortcuts(); + actionsForKeys[Qt::Key_Left]->setShortcut(Qt::Key_Left); + actionsForKeys[Qt::Key_Right]->setShortcut(Qt::Key_Right); + actionsForKeys[Qt::Key_Up]->setShortcut(Qt::Key_Up); + actionsForKeys[Qt::Key_Down]->setShortcut(Qt::Key_Down); + actionsForKeys[Qt::Key_Escape]->setShortcut(Qt::Key_Escape); + actionsForKeys[Qt::Key_Delete]->setShortcut(Qt::Key_Delete); + + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + connect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot())); + connect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot())); + connect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(pointInserted(const QModelIndex &, int, int))); + connect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(pointsRemoved(const QModelIndex &, int, int))); + /* show the same stuff that the profile shows. */ + currentState = PLAN; /* enable the add state. */ + diveCeiling->setVisible(true); + gradientFactor->setVisible(true); + setBackgroundBrush(QColor("#D7E3EF")); +} + +extern struct ev_select *ev_namelist; +extern int evn_allocated; +extern int evn_used; + +bool ProfileWidget2::isPlanner() +{ + return currentState == PLAN; +} + +bool ProfileWidget2::isAddOrPlanner() +{ + return currentState == PLAN || currentState == ADD; +} + +struct plot_data *ProfileWidget2::getEntryFromPos(QPointF pos) +{ + // find the time stamp corresponding to the mouse position + int seconds = timeAxis->valueAt(pos); + struct plot_data *entry = NULL; + + for (int i = 0; i < plotInfo.nr; i++) { + entry = plotInfo.entry + i; + if (entry->sec >= seconds) + break; + } + return entry; +} + +void ProfileWidget2::setReplot(bool state) +{ + replotEnabled = state; +} + +void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event) +{ + if (currentState == ADD || currentState == PLAN) { + QGraphicsView::contextMenuEvent(event); + return; + } + QMenu m; + bool isDCName = false; + if (selected_dive == -1) + return; + // figure out if we are ontop of the dive computer name in the profile + QGraphicsItem *sceneItem = itemAt(mapFromGlobal(event->globalPos())); + if (sceneItem) { + QGraphicsItem *parentItem = sceneItem; + while (parentItem) { + if (parentItem->data(SUBSURFACE_OBJ_DATA) == SUBSURFACE_OBJ_DC_TEXT) { + isDCName = true; + break; + } + parentItem = parentItem->parentItem(); + } + if (isDCName) { + if (dc_number == 0 && count_divecomputers() == 1) + // nothing to do, can't delete or reorder + return; + // create menu to show when right clicking on dive computer name + if (dc_number > 0) + m.addAction(tr("Make first divecomputer"), this, SLOT(makeFirstDC())); + if (count_divecomputers() > 1) + m.addAction(tr("Delete this divecomputer"), this, SLOT(deleteCurrentDC())); + m.exec(event->globalPos()); + // don't show the regular profile context menu + return; + } + } + // create the profile context menu + QPointF scenePos = mapToScene(event->pos()); + struct plot_data *entry = getEntryFromPos(scenePos); + GasSelectionModel *model = GasSelectionModel::instance(); + model->repopulate(); + int rowCount = model->rowCount(); + if (rowCount > 1) { + // if we have more than one gas, offer to switch to another one + QMenu *gasChange = m.addMenu(tr("Add gas change")); + for (int i = 0; i < rowCount; i++) { + QAction *action = new QAction(&m); + action->setText(model->data(model->index(i, 0), Qt::DisplayRole).toString() + QString(tr(" (Tank %1)")).arg(i + 1)); + connect(action, SIGNAL(triggered(bool)), this, SLOT(changeGas())); + action->setData(event->globalPos()); + if (i == entry->cylinderindex) + action->setDisabled(true); + gasChange->addAction(action); + } + } + QAction *setpointAction = m.addAction(tr("Add set-point change"), this, SLOT(addSetpointChange())); + setpointAction->setData(event->globalPos()); + QAction *action = m.addAction(tr("Add bookmark"), this, SLOT(addBookmark())); + action->setData(event->globalPos()); + + if (same_string(current_dc->model, "manually added dive")) + QAction *editProfileAction = m.addAction(tr("Edit the profile"), MainWindow::instance(), SLOT(editCurrentDive())); + + if (DiveEventItem *item = dynamic_cast(sceneItem)) { + action = new QAction(&m); + action->setText(tr("Remove event")); + action->setData(QVariant::fromValue(item)); // so we know what to remove. + connect(action, SIGNAL(triggered(bool)), this, SLOT(removeEvent())); + m.addAction(action); + action = new QAction(&m); + action->setText(tr("Hide similar events")); + action->setData(QVariant::fromValue(item)); + connect(action, SIGNAL(triggered(bool)), this, SLOT(hideEvents())); + m.addAction(action); + struct event *dcEvent = item->getEvent(); + if (dcEvent->type == SAMPLE_EVENT_BOOKMARK) { + action = new QAction(&m); + action->setText(tr("Edit name")); + action->setData(QVariant::fromValue(item)); + connect(action, SIGNAL(triggered(bool)), this, SLOT(editName())); + m.addAction(action); + } +#if 0 // FIXME::: FINISH OR DISABLE + // this shows how to figure out if we should ask the user if they want adjust interpolated pressures + // at either side of a gas change + if (dcEvent->type == SAMPLE_EVENT_GASCHANGE || dcEvent->type == SAMPLE_EVENT_GASCHANGE2) { + qDebug() << "figure out if there are interpolated pressures"; + struct plot_data *gasChangeEntry = entry; + struct plot_data *newGasEntry; + while (gasChangeEntry > plotInfo.entry) { + --gasChangeEntry; + if (gasChangeEntry->sec <= dcEvent->time.seconds) + break; + } + qDebug() << "at gas change at" << gasChangeEntry->sec << ": sensor pressure" << gasChangeEntry->pressure[0] << "interpolated" << gasChangeEntry->pressure[1]; + // now gasChangeEntry points at the gas change, that entry has the final pressure of + // the old tank, the next entry has the starting pressure of the next tank + if (gasChangeEntry + 1 <= plotInfo.entry + plotInfo.nr) { + newGasEntry = gasChangeEntry + 1; + qDebug() << "after gas change at " << newGasEntry->sec << ": sensor pressure" << newGasEntry->pressure[0] << "interpolated" << newGasEntry->pressure[1]; + if (SENSOR_PRESSURE(gasChangeEntry) == 0 || displayed_dive.cylinder[gasChangeEntry->cylinderindex].sample_start.mbar == 0) { + // if we have no sensorpressure or if we have no pressure from samples we can assume that + // we only have interpolated pressure (the pressure in the entry may be stored in the sensor + // pressure field if this is the first or last entry for this tank... see details in gaspressures.c + pressure_t pressure; + pressure.mbar = INTERPOLATED_PRESSURE(gasChangeEntry) ? : SENSOR_PRESSURE(gasChangeEntry); + QAction *adjustOldPressure = m.addAction(tr("Adjust pressure of tank %1 (currently interpolated as %2)") + .arg(gasChangeEntry->cylinderindex + 1).arg(get_pressure_string(pressure))); + } + if (SENSOR_PRESSURE(newGasEntry) == 0 || displayed_dive.cylinder[newGasEntry->cylinderindex].sample_start.mbar == 0) { + // we only have interpolated press -- see commend above + pressure_t pressure; + pressure.mbar = INTERPOLATED_PRESSURE(newGasEntry) ? : SENSOR_PRESSURE(newGasEntry); + QAction *adjustOldPressure = m.addAction(tr("Adjust pressure of tank %1 (currently interpolated as %2)") + .arg(newGasEntry->cylinderindex + 1).arg(get_pressure_string(pressure))); + } + } + } +#endif + } + bool some_hidden = false; + for (int i = 0; i < evn_used; i++) { + if (ev_namelist[i].plot_ev == false) { + some_hidden = true; + break; + } + } + if (some_hidden) { + action = m.addAction(tr("Unhide all events"), this, SLOT(unhideEvents())); + action->setData(event->globalPos()); + } + m.exec(event->globalPos()); +} + +void ProfileWidget2::deleteCurrentDC() +{ + delete_current_divecomputer(); + mark_divelist_changed(true); + // we need to force it since it's likely the same dive and same dc_number - but that's a different dive computer now + MainWindow::instance()->graphics()->plotDive(0, true); + MainWindow::instance()->refreshDisplay(); +} + +void ProfileWidget2::makeFirstDC() +{ + make_first_dc(); + mark_divelist_changed(true); + // this is now the first DC, so we need to redraw the profile and refresh the dive list + // (and no, it's not just enough to rewrite the text - the first DC is special so values in the + // dive list may change). + // As a side benefit, this returns focus to the dive list. + dc_number = 0; + MainWindow::instance()->refreshDisplay(); +} + +void ProfileWidget2::hideEvents() +{ + QAction *action = qobject_cast(sender()); + DiveEventItem *item = static_cast(action->data().value()); + struct event *event = item->getEvent(); + + if (QMessageBox::question(MainWindow::instance(), + TITLE_OR_TEXT(tr("Hide events"), tr("Hide all %1 events?").arg(event->name)), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { + if (!same_string(event->name, "")) { + for (int i = 0; i < evn_used; i++) { + if (same_string(event->name, ev_namelist[i].ev_name)) { + ev_namelist[i].plot_ev = false; + break; + } + } + Q_FOREACH (DiveEventItem *evItem, eventItems) { + if (same_string(evItem->getEvent()->name, event->name)) + evItem->hide(); + } + } else { + item->hide(); + } + } +} + +void ProfileWidget2::unhideEvents() +{ + for (int i = 0; i < evn_used; i++) { + ev_namelist[i].plot_ev = true; + } + Q_FOREACH (DiveEventItem *item, eventItems) + item->show(); +} + +void ProfileWidget2::removeEvent() +{ + QAction *action = qobject_cast(sender()); + DiveEventItem *item = static_cast(action->data().value()); + struct event *event = item->getEvent(); + + if (QMessageBox::question(MainWindow::instance(), TITLE_OR_TEXT( + tr("Remove the selected event?"), + tr("%1 @ %2:%3").arg(event->name).arg(event->time.seconds / 60).arg(event->time.seconds % 60, 2, 10, QChar('0'))), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { + remove_event(event); + mark_divelist_changed(true); + replot(); + } +} + +void ProfileWidget2::addBookmark() +{ + QAction *action = qobject_cast(sender()); + QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); + add_event(current_dc, timeAxis->valueAt(scenePos), SAMPLE_EVENT_BOOKMARK, 0, 0, "bookmark"); + mark_divelist_changed(true); + replot(); +} + +void ProfileWidget2::addSetpointChange() +{ + QAction *action = qobject_cast(sender()); + QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); + SetpointDialog::instance()->setpointData(current_dc, timeAxis->valueAt(scenePos)); + SetpointDialog::instance()->show(); +} + +void ProfileWidget2::changeGas() +{ + QAction *action = qobject_cast(sender()); + QPointF scenePos = mapToScene(mapFromGlobal(action->data().toPoint())); + QString gas = action->text(); + gas.remove(QRegExp(" \\(.*\\)")); + + // backup the things on the dataModel, since we will clear that out. + struct gasmix gasmix; + qreal sec_val = timeAxis->valueAt(scenePos); + + // no gas changes before the dive starts + unsigned int seconds = (sec_val < 0.0) ? 0 : (unsigned int)sec_val; + + // if there is a gas change at this time stamp, remove it before adding the new one + struct event *gasChangeEvent = current_dc->events; + while ((gasChangeEvent = get_next_event(gasChangeEvent, "gaschange")) != NULL) { + if (gasChangeEvent->time.seconds == seconds) { + remove_event(gasChangeEvent); + gasChangeEvent = current_dc->events; + } else { + gasChangeEvent = gasChangeEvent->next; + } + } + validate_gas(gas.toUtf8().constData(), &gasmix); + QRegExp rx("\\(\\D*(\\d+)"); + int tank; + if (rx.indexIn(action->text()) > -1) { + tank = rx.cap(1).toInt() - 1; // we display the tank 1 based + } else { + qDebug() << "failed to parse tank number"; + tank = get_gasidx(&displayed_dive, &gasmix); + } + // add this both to the displayed dive and the current dive + add_gas_switch_event(current_dive, current_dc, seconds, tank); + add_gas_switch_event(&displayed_dive, get_dive_dc(&displayed_dive, dc_number), seconds, tank); + // this means we potentially have a new tank that is being used and needs to be shown + fixup_dive(&displayed_dive); + + // FIXME - this no longer gets written to the dive list - so we need to enableEdition() here + + MainWindow::instance()->information()->updateDiveInfo(); + mark_divelist_changed(true); + replot(); +} + +bool ProfileWidget2::getPrintMode() +{ + return printMode; +} + +void ProfileWidget2::setPrintMode(bool mode, bool grayscale) +{ + printMode = mode; + resetZoom(); + + // set printMode for axes + profileYAxis->setPrintMode(mode); + gasYAxis->setPrintMode(mode); + temperatureAxis->setPrintMode(mode); + timeAxis->setPrintMode(mode); + cylinderPressureAxis->setPrintMode(mode); + heartBeatAxis->setPrintMode(mode); + percentageAxis->setPrintMode(mode); + + isGrayscale = mode ? grayscale : false; + mouseFollowerHorizontal->setVisible(!mode); + mouseFollowerVertical->setVisible(!mode); +} + +void ProfileWidget2::setFontPrintScale(double scale) +{ + fontPrintScale = scale; + emit fontPrintScaleChanged(scale); +} + +double ProfileWidget2::getFontPrintScale() +{ + if (printMode) + return fontPrintScale; + else + return 1.0; +} + +void ProfileWidget2::editName() +{ + QAction *action = qobject_cast(sender()); + DiveEventItem *item = static_cast(action->data().value()); + struct event *event = item->getEvent(); + bool ok; + QString newName = QInputDialog::getText(MainWindow::instance(), tr("Edit name of bookmark"), + tr("Custom name:"), QLineEdit::Normal, + event->name, &ok); + if (ok && !newName.isEmpty()) { + if (newName.length() > 22) { //longer names will display as garbage. + QMessageBox lengthWarning; + lengthWarning.setText(tr("Name is too long!")); + lengthWarning.exec(); + return; + } + // order is important! first update the current dive (by matching the unchanged event), + // then update the displayed dive (as event is part of the events on displayed dive + // and will be freed as part of changing the name! + update_event_name(current_dive, event, newName.toUtf8().data()); + update_event_name(&displayed_dive, event, newName.toUtf8().data()); + mark_divelist_changed(true); + replot(); + } +} + +void ProfileWidget2::disconnectTemporaryConnections() +{ + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + disconnect(plannerModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(replot())); + disconnect(plannerModel, SIGNAL(cylinderModelEdited()), this, SLOT(replot())); + + disconnect(plannerModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(pointInserted(const QModelIndex &, int, int))); + disconnect(plannerModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(pointsRemoved(const QModelIndex &, int, int))); + + Q_FOREACH (QAction *action, actionsForKeys.values()) { + action->setShortcut(QKeySequence()); + action->setShortcutContext(Qt::WidgetShortcut); + } +} + +void ProfileWidget2::pointInserted(const QModelIndex &parent, int start, int end) +{ + DiveHandler *item = new DiveHandler(); + scene()->addItem(item); + handles << item; + + connect(item, SIGNAL(moved()), this, SLOT(recreatePlannedDive())); + connect(item, SIGNAL(clicked()), this, SLOT(divePlannerHandlerClicked())); + connect(item, SIGNAL(released()), this, SLOT(divePlannerHandlerReleased())); + QGraphicsSimpleTextItem *gasChooseBtn = new QGraphicsSimpleTextItem(); + scene()->addItem(gasChooseBtn); + gasChooseBtn->setZValue(10); + gasChooseBtn->setFlag(QGraphicsItem::ItemIgnoresTransformations); + gases << gasChooseBtn; + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + if (plannerModel->recalcQ()) + replot(); +} + +void ProfileWidget2::pointsRemoved(const QModelIndex &, int start, int end) +{ // start and end are inclusive. + int num = (end - start) + 1; + for (int i = num; i != 0; i--) { + delete handles.back(); + handles.pop_back(); + delete gases.back(); + gases.pop_back(); + } + scene()->clearSelection(); + replot(); +} + +void ProfileWidget2::repositionDiveHandlers() +{ + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + // Re-position the user generated dive handlers + struct gasmix mix, lastmix; + for (int i = 0; i < plannerModel->rowCount(); i++) { + struct divedatapoint datapoint = plannerModel->at(i); + if (datapoint.time == 0) // those are the magic entries for tanks + continue; + DiveHandler *h = handles.at(i); + h->setVisible(datapoint.entered); + h->setPos(timeAxis->posAtValue(datapoint.time), profileYAxis->posAtValue(datapoint.depth)); + QPointF p1; + if (i == 0) { + if (prefs.drop_stone_mode) + // place the text on the straight line from the drop to stone position + p1 = QPointF(timeAxis->posAtValue(datapoint.depth / prefs.descrate), + profileYAxis->posAtValue(datapoint.depth)); + else + // place the text on the straight line from the origin to the first position + p1 = QPointF(timeAxis->posAtValue(0), profileYAxis->posAtValue(0)); + } else { + // place the text on the line from the last position + p1 = handles[i - 1]->pos(); + } + QPointF p2 = handles[i]->pos(); + QLineF line(p1, p2); + QPointF pos = line.pointAt(0.5); + gases[i]->setPos(pos); + gases[i]->setText(get_divepoint_gas_string(datapoint)); + gases[i]->setVisible(datapoint.entered && + (i == 0 || gases[i]->text() != gases[i-1]->text())); + } +} + +int ProfileWidget2::fixHandlerIndex(DiveHandler *activeHandler) +{ + int index = handles.indexOf(activeHandler); + if (index > 0 && index < handles.count() - 1) { + DiveHandler *before = handles[index - 1]; + if (before->pos().x() > activeHandler->pos().x()) { + handles.swap(index, index - 1); + return index - 1; + } + DiveHandler *after = handles[index + 1]; + if (after->pos().x() < activeHandler->pos().x()) { + handles.swap(index, index + 1); + return index + 1; + } + } + return index; +} + +void ProfileWidget2::recreatePlannedDive() +{ + DiveHandler *activeHandler = qobject_cast(sender()); + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + int index = fixHandlerIndex(activeHandler); + int mintime = 0, maxtime = (timeAxis->maximum() + 10) * 60; + if (index > 0) + mintime = plannerModel->at(index - 1).time; + if (index < plannerModel->size() - 1) + maxtime = plannerModel->at(index + 1).time; + + int minutes = rint(timeAxis->valueAt(activeHandler->pos()) / 60); + if (minutes * 60 <= mintime || minutes * 60 >= maxtime) + return; + + divedatapoint data = plannerModel->at(index); + data.depth = rint(profileYAxis->valueAt(activeHandler->pos()) / M_OR_FT(1, 1)) * M_OR_FT(1, 1); + data.time = rint(timeAxis->valueAt(activeHandler->pos())); + + plannerModel->editStop(index, data); +} + +void ProfileWidget2::keyDownAction() +{ + if (currentState != ADD && currentState != PLAN) + return; + + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { + if (DiveHandler *handler = qgraphicsitem_cast(i)) { + int row = handles.indexOf(handler); + divedatapoint dp = plannerModel->at(row); + if (dp.depth >= profileYAxis->maximum()) + continue; + + dp.depth += M_OR_FT(1, 5); + plannerModel->editStop(row, dp); + } + } +} + +void ProfileWidget2::keyUpAction() +{ + if (currentState != ADD && currentState != PLAN) + return; + + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { + if (DiveHandler *handler = qgraphicsitem_cast(i)) { + int row = handles.indexOf(handler); + divedatapoint dp = plannerModel->at(row); + + if (dp.depth <= 0) + continue; + + dp.depth -= M_OR_FT(1, 5); + plannerModel->editStop(row, dp); + } + } +} + +void ProfileWidget2::keyLeftAction() +{ + if (currentState != ADD && currentState != PLAN) + return; + + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { + if (DiveHandler *handler = qgraphicsitem_cast(i)) { + int row = handles.indexOf(handler); + divedatapoint dp = plannerModel->at(row); + + if (dp.time / 60 <= 0) + continue; + + // don't overlap positions. + // maybe this is a good place for a 'goto'? + double xpos = timeAxis->posAtValue((dp.time - 60) / 60); + bool nextStep = false; + Q_FOREACH (DiveHandler *h, handles) { + if (IS_FP_SAME(h->pos().x(), xpos)) { + nextStep = true; + break; + } + } + if (nextStep) + continue; + + dp.time -= 60; + plannerModel->editStop(row, dp); + } + } +} + +void ProfileWidget2::keyRightAction() +{ + if (currentState != ADD && currentState != PLAN) + return; + + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { + if (DiveHandler *handler = qgraphicsitem_cast(i)) { + int row = handles.indexOf(handler); + divedatapoint dp = plannerModel->at(row); + if (dp.time / 60.0 >= timeAxis->maximum()) + continue; + + // don't overlap positions. + // maybe this is a good place for a 'goto'? + double xpos = timeAxis->posAtValue((dp.time + 60) / 60); + bool nextStep = false; + Q_FOREACH (DiveHandler *h, handles) { + if (IS_FP_SAME(h->pos().x(), xpos)) { + nextStep = true; + break; + } + } + if (nextStep) + continue; + + dp.time += 60; + plannerModel->editStop(row, dp); + } + } +} + +void ProfileWidget2::keyDeleteAction() +{ + if (currentState != ADD && currentState != PLAN) + return; + + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + int selCount = scene()->selectedItems().count(); + if (selCount) { + QVector selectedIndexes; + Q_FOREACH (QGraphicsItem *i, scene()->selectedItems()) { + if (DiveHandler *handler = qgraphicsitem_cast(i)) { + selectedIndexes.push_back(handles.indexOf(handler)); + handler->hide(); + } + } + plannerModel->removeSelectedPoints(selectedIndexes); + } +} + +void ProfileWidget2::keyEscAction() +{ + if (currentState != ADD && currentState != PLAN) + return; + + if (scene()->selectedItems().count()) { + scene()->clearSelection(); + return; + } + + DivePlannerPointsModel *plannerModel = DivePlannerPointsModel::instance(); + if (plannerModel->isPlanner()) + plannerModel->cancelPlan(); +} + +void ProfileWidget2::plotPictures() +{ + Q_FOREACH (DivePictureItem *item, pictures) { + item->hide(); + item->deleteLater(); + } + pictures.clear(); + + if (printMode) + return; + + double x, y, lastX = -1.0, lastY = -1.0; + DivePictureModel *m = DivePictureModel::instance(); + for (int i = 0; i < m->rowCount(); i++) { + int offsetSeconds = m->index(i, 1).data(Qt::UserRole).value(); + // it's a correct picture, but doesn't have a timestamp: only show on the widget near the + // information area. + if (!offsetSeconds) + continue; + DivePictureItem *item = new DivePictureItem(); + item->setPixmap(m->index(i, 0).data(Qt::DecorationRole).value()); + item->setFileUrl(m->index(i, 1).data().toString()); + // let's put the picture at the correct time, but at a fixed "depth" on the profile + // not sure this is ideal, but it seems to look right. + x = timeAxis->posAtValue(offsetSeconds); + if (i == 0) + y = 10; + else if (fabs(x - lastX) < 4) + y = lastY + 3; + else + y = 10; + lastX = x; + lastY = y; + item->setPos(x, y); + scene()->addItem(item); + pictures.push_back(item); + } +} diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h new file mode 100644 index 000000000..f11ec5be1 --- /dev/null +++ b/profile-widget/profilewidget2.h @@ -0,0 +1,211 @@ +#ifndef PROFILEWIDGET2_H +#define PROFILEWIDGET2_H + +#include + +// /* The idea of this widget is to display and edit the profile. +// * It has: +// * 1 - ToolTip / Legend item, displays every information of the current mouse position on it, plus the legends of the maps. +// * 2 - ToolBox, displays the QActions that are used to do special stuff on the profile ( like activating the plugins. ) +// * 3 - Cartesian Axis for depth ( y ) +// * 4 - Cartesian Axis for Gases ( y ) +// * 5 - Cartesian Axis for Time ( x ) +// * +// * It needs to be dynamic, things should *flow* on it, not just appear / disappear. +// */ +#include "divelineitem.h" +#include "diveprofileitem.h" +#include "display.h" + +class RulerItem2; +struct dive; +struct plot_info; +class ToolTipItem; +class DiveMeanDepth; +class DiveReportedCeiling; +class DiveTextItem; +class TemperatureAxis; +class DiveEventItem; +class DivePlotDataModel; +class DivePixmapItem; +class DiveRectItem; +class DepthAxis; +class DiveCartesianAxis; +class DiveProfileItem; +class TimeAxis; +class DiveTemperatureItem; +class DiveHeartrateItem; +class PercentageItem; +class DiveGasPressureItem; +class DiveCalculatedCeiling; +class DiveCalculatedTissue; +class PartialPressureGasItem; +class PartialGasPressureAxis; +class AbstractProfilePolygonItem; +class TankItem; +class DiveHandler; +class QGraphicsSimpleTextItem; +class QModelIndex; +class DivePictureItem; + +class ProfileWidget2 : public QGraphicsView { + Q_OBJECT +public: + enum State { + EMPTY, + PROFILE, + EDIT, + ADD, + PLAN, + INVALID + }; + enum Items { + BACKGROUND, + PROFILE_Y_AXIS, + GAS_Y_AXIS, + TIME_AXIS, + DEPTH_CONTROLLER, + TIME_CONTROLLER, + COLUMNS + }; + + ProfileWidget2(QWidget *parent = 0); + void resetZoom(); + void plotDive(struct dive *d = 0, bool force = false); + virtual bool eventFilter(QObject *, QEvent *); + void setupItem(AbstractProfilePolygonItem *item, DiveCartesianAxis *hAxis, DiveCartesianAxis *vAxis, DivePlotDataModel *model, int vData, int hData, int zValue); + void setPrintMode(bool mode, bool grayscale = false); + bool getPrintMode(); + bool isPointOutOfBoundaries(const QPointF &point) const; + bool isPlanner(); + bool isAddOrPlanner(); + double getFontPrintScale(); + void setFontPrintScale(double scale); + void clearHandlers(); + void recalcCeiling(); + void setToolTipVisibile(bool visible); + State currentState; + +signals: + void fontPrintScaleChanged(double scale); + +public +slots: // Necessary to call from QAction's signals. + void settingsChanged(); + void setEmptyState(); + void setProfileState(); + void setPlanState(); + void setAddState(); + void changeGas(); + void addSetpointChange(); + void addBookmark(); + void hideEvents(); + void unhideEvents(); + void removeEvent(); + void editName(); + void makeFirstDC(); + void deleteCurrentDC(); + void pointInserted(const QModelIndex &parent, int start, int end); + void pointsRemoved(const QModelIndex &, int start, int end); + void plotPictures(); + void setReplot(bool state); + void replot(dive *d = 0); + + /* this is called for every move on the handlers. maybe we can speed up this a bit? */ + void recreatePlannedDive(); + + /* key press handlers */ + void keyEscAction(); + void keyDeleteAction(); + void keyUpAction(); + void keyDownAction(); + void keyLeftAction(); + void keyRightAction(); + + void divePlannerHandlerClicked(); + void divePlannerHandlerReleased(); + +protected: + virtual ~ProfileWidget2(); + virtual void resizeEvent(QResizeEvent *event); + virtual void wheelEvent(QWheelEvent *event); + virtual void mouseMoveEvent(QMouseEvent *event); + virtual void contextMenuEvent(QContextMenuEvent *event); + virtual void mouseDoubleClickEvent(QMouseEvent *event); + virtual void mousePressEvent(QMouseEvent *event); + virtual void mouseReleaseEvent(QMouseEvent *event); + +private: /*methods*/ + void fixBackgroundPos(); + void scrollViewTo(const QPoint &pos); + void setupSceneAndFlags(); + void setupItemSizes(); + void addItemsToScene(); + void setupItemOnScene(); + void disconnectTemporaryConnections(); + struct plot_data *getEntryFromPos(QPointF pos); + +private: + DivePlotDataModel *dataModel; + int zoomLevel; + qreal zoomFactor; + DivePixmapItem *background; + QString backgroundFile; + ToolTipItem *toolTipItem; + bool isPlotZoomed; + bool replotEnabled; + // All those here should probably be merged into one structure, + // So it's esyer to replicate for more dives later. + // In the meantime, keep it here. + struct plot_info plotInfo; + DepthAxis *profileYAxis; + PartialGasPressureAxis *gasYAxis; + TemperatureAxis *temperatureAxis; + TimeAxis *timeAxis; + DiveProfileItem *diveProfileItem; + DiveTemperatureItem *temperatureItem; + DiveMeanDepthItem *meanDepthItem; + DiveCartesianAxis *cylinderPressureAxis; + DiveGasPressureItem *gasPressureItem; + QList eventItems; + DiveTextItem *diveComputerText; + DiveCalculatedCeiling *diveCeiling; + DiveTextItem *gradientFactor; + QList allTissues; + DiveReportedCeiling *reportedCeiling; + PartialPressureGasItem *pn2GasItem; + PartialPressureGasItem *pheGasItem; + PartialPressureGasItem *po2GasItem; + PartialPressureGasItem *o2SetpointGasItem; + PartialPressureGasItem *ccrsensor1GasItem; + PartialPressureGasItem *ccrsensor2GasItem; + PartialPressureGasItem *ccrsensor3GasItem; + DiveCartesianAxis *heartBeatAxis; + DiveHeartrateItem *heartBeatItem; + DiveCartesianAxis *percentageAxis; + QList allPercentages; + DiveAmbPressureItem *ambPressureItem; + DiveGFLineItem *gflineItem; + DiveLineItem *mouseFollowerVertical; + DiveLineItem *mouseFollowerHorizontal; + RulerItem2 *rulerItem; + TankItem *tankItem; + bool isGrayscale; + bool printMode; + + //specifics for ADD and PLAN + QList handles; + QList gases; + QList pictures; + void repositionDiveHandlers(); + int fixHandlerIndex(DiveHandler *activeHandler); + friend class DiveHandler; + QHash actionsForKeys; + bool shouldCalculateMaxTime; + bool shouldCalculateMaxDepth; + int maxtime; + int maxdepth; + double fontPrintScale; +}; + +#endif // PROFILEWIDGET2_H diff --git a/profile-widget/ruleritem.cpp b/profile-widget/ruleritem.cpp new file mode 100644 index 000000000..830985552 --- /dev/null +++ b/profile-widget/ruleritem.cpp @@ -0,0 +1,179 @@ +#include "ruleritem.h" +#include "preferences.h" +#include "mainwindow.h" +#include "profilewidget2.h" +#include "display.h" + +#include + +#include "profile.h" + +RulerNodeItem2::RulerNodeItem2() : + entry(NULL), + ruler(NULL), + timeAxis(NULL), + depthAxis(NULL) +{ + memset(&pInfo, 0, sizeof(pInfo)); + setRect(-8, -8, 16, 16); + setBrush(QColor(0xff, 0, 0, 127)); + setPen(QColor(Qt::red)); + setFlag(ItemIsMovable); + setFlag(ItemSendsGeometryChanges); + setFlag(ItemIgnoresTransformations); +} + +void RulerNodeItem2::setPlotInfo(plot_info &info) +{ + pInfo = info; + entry = pInfo.entry; +} + +void RulerNodeItem2::setRuler(RulerItem2 *r) +{ + ruler = r; +} + +void RulerNodeItem2::recalculate() +{ + struct plot_data *data = pInfo.entry + (pInfo.nr - 1); + uint16_t count = 0; + if (x() < 0) { + setPos(0, y()); + } else if (x() > timeAxis->posAtValue(data->sec)) { + setPos(timeAxis->posAtValue(data->sec), depthAxis->posAtValue(data->depth)); + } else { + data = pInfo.entry; + count = 0; + while (timeAxis->posAtValue(data->sec) < x() && count < pInfo.nr) { + data = pInfo.entry + count; + count++; + } + setPos(timeAxis->posAtValue(data->sec), depthAxis->posAtValue(data->depth)); + entry = data; + } +} + +void RulerNodeItem2::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + qreal x = event->scenePos().x(); + if (x < 0.0) + x = 0.0; + setPos(x, event->scenePos().y()); + recalculate(); + ruler->recalculate(); +} + +RulerItem2::RulerItem2() : source(new RulerNodeItem2()), + dest(new RulerNodeItem2()), + timeAxis(NULL), + depthAxis(NULL), + textItemBack(new QGraphicsRectItem(this)), + textItem(new QGraphicsSimpleTextItem(this)) +{ + memset(&pInfo, 0, sizeof(pInfo)); + source->setRuler(this); + dest->setRuler(this); + textItem->setFlag(QGraphicsItem::ItemIgnoresTransformations); + textItemBack->setBrush(QColor(0xff, 0xff, 0xff, 190)); + textItemBack->setPen(QColor(Qt::white)); + textItemBack->setFlag(QGraphicsItem::ItemIgnoresTransformations); + setPen(QPen(QColor(Qt::black), 0.0)); + connect(PreferencesDialog::instance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); +} + +void RulerItem2::settingsChanged() +{ + ProfileWidget2 *profWidget = NULL; + if (scene() && scene()->views().count()) + profWidget = qobject_cast(scene()->views().first()); + + if (profWidget && profWidget->currentState == ProfileWidget2::PROFILE) + setVisible(prefs.rulergraph); + else + setVisible(false); +} + +void RulerItem2::recalculate() +{ + char buffer[500]; + QPointF tmp; + QFont font; + QFontMetrics fm(font); + + if (timeAxis == NULL || depthAxis == NULL || pInfo.nr == 0) + return; + + prepareGeometryChange(); + startPoint = mapFromItem(source, 0, 0); + endPoint = mapFromItem(dest, 0, 0); + + if (startPoint.x() > endPoint.x()) { + tmp = endPoint; + endPoint = startPoint; + startPoint = tmp; + } + QLineF line(startPoint, endPoint); + setLine(line); + compare_samples(source->entry, dest->entry, buffer, 500, 1); + text = QString(buffer); + + // draw text + QGraphicsView *view = scene()->views().first(); + QPoint begin = view->mapFromScene(mapToScene(startPoint)); + textItem->setText(text); + qreal tgtX = startPoint.x(); + const qreal diff = begin.x() + textItem->boundingRect().width(); + // clamp so that the text doesn't go out of the screen to the right + if (diff > view->width()) { + begin.setX(begin.x() - (diff - view->width())); + tgtX = mapFromScene(view->mapToScene(begin)).x(); + } + // always show the text bellow the lowest of the start and end points + qreal tgtY = (startPoint.y() >= endPoint.y()) ? startPoint.y() : endPoint.y(); + // this isn't exactly optimal, since we want to scale the 1.0, 4.0 distances as well + textItem->setPos(tgtX - 1.0, tgtY + 4.0); + + // setup the text background + textItemBack->setVisible(startPoint.x() != endPoint.x()); + textItemBack->setPos(textItem->x(), textItem->y()); + textItemBack->setRect(0, 0, textItem->boundingRect().width(), textItem->boundingRect().height()); +} + +RulerNodeItem2 *RulerItem2::sourceNode() const +{ + return source; +} + +RulerNodeItem2 *RulerItem2::destNode() const +{ + return dest; +} + +void RulerItem2::setPlotInfo(plot_info info) +{ + pInfo = info; + dest->setPlotInfo(info); + source->setPlotInfo(info); + dest->recalculate(); + source->recalculate(); + recalculate(); +} + +void RulerItem2::setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth) +{ + timeAxis = time; + depthAxis = depth; + dest->depthAxis = depth; + dest->timeAxis = time; + source->depthAxis = depth; + source->timeAxis = time; + recalculate(); +} + +void RulerItem2::setVisible(bool visible) +{ + QGraphicsLineItem::setVisible(visible); + source->setVisible(visible); + dest->setVisible(visible); +} diff --git a/profile-widget/ruleritem.h b/profile-widget/ruleritem.h new file mode 100644 index 000000000..4fad0451c --- /dev/null +++ b/profile-widget/ruleritem.h @@ -0,0 +1,59 @@ +#ifndef RULERITEM_H +#define RULERITEM_H + +#include +#include +#include +#include "divecartesianaxis.h" +#include "display.h" + +struct plot_data; +class RulerItem2; + +class RulerNodeItem2 : public QObject, public QGraphicsEllipseItem { + Q_OBJECT + friend class RulerItem2; + +public: + explicit RulerNodeItem2(); + void setRuler(RulerItem2 *r); + void setPlotInfo(struct plot_info &info); + void recalculate(); + +protected: + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); +private: + struct plot_info pInfo; + struct plot_data *entry; + RulerItem2 *ruler; + DiveCartesianAxis *timeAxis; + DiveCartesianAxis *depthAxis; +}; + +class RulerItem2 : public QObject, public QGraphicsLineItem { + Q_OBJECT +public: + explicit RulerItem2(); + void recalculate(); + + void setPlotInfo(struct plot_info pInfo); + RulerNodeItem2 *sourceNode() const; + RulerNodeItem2 *destNode() const; + void setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth); + void setVisible(bool visible); + +public +slots: + void settingsChanged(); + +private: + struct plot_info pInfo; + QPointF startPoint, endPoint; + RulerNodeItem2 *source, *dest; + QString text; + DiveCartesianAxis *timeAxis; + DiveCartesianAxis *depthAxis; + QGraphicsRectItem *textItemBack; + QGraphicsSimpleTextItem *textItem; +}; +#endif diff --git a/profile-widget/tankitem.cpp b/profile-widget/tankitem.cpp new file mode 100644 index 000000000..c0e75a371 --- /dev/null +++ b/profile-widget/tankitem.cpp @@ -0,0 +1,120 @@ +#include "tankitem.h" +#include "diveplotdatamodel.h" +#include "divetextitem.h" +#include "profile.h" +#include + +TankItem::TankItem(QObject *parent) : + QGraphicsRectItem(), + dataModel(0), + pInfoEntry(0), + pInfoNr(0) +{ + height = 3; + QColor red(PERSIANRED1); + QColor blue(AIR_BLUE); + QColor yellow(NITROX_YELLOW); + QColor green(NITROX_GREEN); + QLinearGradient nitroxGradient(QPointF(0, 0), QPointF(0, height)); + nitroxGradient.setColorAt(0.0, green); + nitroxGradient.setColorAt(0.49, green); + nitroxGradient.setColorAt(0.5, yellow); + nitroxGradient.setColorAt(1.0, yellow); + nitrox = nitroxGradient; + oxygen = green; + QLinearGradient trimixGradient(QPointF(0, 0), QPointF(0, height)); + trimixGradient.setColorAt(0.0, green); + trimixGradient.setColorAt(0.49, green); + trimixGradient.setColorAt(0.5, red); + trimixGradient.setColorAt(1.0, red); + trimix = trimixGradient; + air = blue; + memset(&diveCylinderStore, 0, sizeof(diveCylinderStore)); +} + +TankItem::~TankItem() +{ + // Should this be clear_dive(diveCylinderStore)? + for (int i = 0; i < MAX_CYLINDERS; i++) + free((void *)diveCylinderStore.cylinder[i].type.description); +} + +void TankItem::setData(DivePlotDataModel *model, struct plot_info *plotInfo, struct dive *d) +{ + free(pInfoEntry); + // the plotInfo and dive structures passed in could become invalid before we stop using them, + // so copy the data that we need + int size = plotInfo->nr * sizeof(plotInfo->entry[0]); + pInfoEntry = (struct plot_data *)malloc(size); + pInfoNr = plotInfo->nr; + memcpy(pInfoEntry, plotInfo->entry, size); + copy_cylinders(d, &diveCylinderStore, false); + dataModel = model; + connect(dataModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(modelDataChanged(QModelIndex, QModelIndex)), Qt::UniqueConnection); + modelDataChanged(); +} + +void TankItem::createBar(qreal x, qreal w, struct gasmix *gas) +{ + // pick the right gradient, size, position and text + QGraphicsRectItem *rect = new QGraphicsRectItem(x, 0, w, height, this); + if (gasmix_is_air(gas)) + rect->setBrush(air); + else if (gas->he.permille) + rect->setBrush(trimix); + else if (gas->o2.permille == 1000) + rect->setBrush(oxygen); + else + rect->setBrush(nitrox); + rect->setPen(QPen(QBrush(), 0.0)); // get rid of the thick line around the rectangle + rects.push_back(rect); + DiveTextItem *label = new DiveTextItem(rect); + label->setText(gasname(gas)); + label->setBrush(Qt::black); + label->setPos(x + 1, 0); + label->setAlignment(Qt::AlignBottom | Qt::AlignRight); + label->setZValue(101); +} + +void TankItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + // We don't have enougth data to calculate things, quit. + + if (!dataModel || !pInfoEntry || !pInfoNr) + return; + + // remove the old rectangles + foreach (QGraphicsRectItem *r, rects) { + delete(r); + } + rects.clear(); + + // walk the list and figure out which tanks go where + struct plot_data *entry = pInfoEntry; + int cylIdx = entry->cylinderindex; + int i = -1; + int startTime = 0; + struct gasmix *gas = &diveCylinderStore.cylinder[cylIdx].gasmix; + qreal width, left; + while (++i < pInfoNr) { + entry = &pInfoEntry[i]; + if (entry->cylinderindex == cylIdx) + continue; + width = hAxis->posAtValue(entry->sec) - hAxis->posAtValue(startTime); + left = hAxis->posAtValue(startTime); + createBar(left, width, gas); + cylIdx = entry->cylinderindex; + gas = &diveCylinderStore.cylinder[cylIdx].gasmix; + startTime = entry->sec; + } + width = hAxis->posAtValue(entry->sec) - hAxis->posAtValue(startTime); + left = hAxis->posAtValue(startTime); + createBar(left, width, gas); +} + +void TankItem::setHorizontalAxis(DiveCartesianAxis *horizontal) +{ + hAxis = horizontal; + connect(hAxis, SIGNAL(sizeChanged()), this, SLOT(modelDataChanged())); + modelDataChanged(); +} diff --git a/profile-widget/tankitem.h b/profile-widget/tankitem.h new file mode 100644 index 000000000..fd685fc82 --- /dev/null +++ b/profile-widget/tankitem.h @@ -0,0 +1,39 @@ +#ifndef TANKITEM_H +#define TANKITEM_H + +#include +#include +#include +#include "divelineitem.h" +#include "divecartesianaxis.h" +#include "dive.h" + +class TankItem : public QObject, public QGraphicsRectItem +{ + Q_OBJECT + +public: + explicit TankItem(QObject *parent = 0); + ~TankItem(); + void setHorizontalAxis(DiveCartesianAxis *horizontal); + void setData(DivePlotDataModel *model, struct plot_info *plotInfo, struct dive *d); + +signals: + +public slots: + virtual void modelDataChanged(const QModelIndex &topLeft = QModelIndex(), const QModelIndex &bottomRight = QModelIndex()); + +private: + void createBar(qreal x, qreal w, struct gasmix *gas); + DivePlotDataModel *dataModel; + DiveCartesianAxis *hAxis; + int hDataColumn; + struct dive diveCylinderStore; + struct plot_data *pInfoEntry; + int pInfoNr; + qreal height; + QBrush air, nitrox, oxygen, trimix; + QList rects; +}; + +#endif // TANKITEM_H diff --git a/qt-models/CMakeLists.txt b/qt-models/CMakeLists.txt index 463d61412..c9bcf5c3d 100644 --- a/qt-models/CMakeLists.txt +++ b/qt-models/CMakeLists.txt @@ -23,6 +23,7 @@ set(SUBSURFACE_MODELS_LIB_SRCS divesitepicturesmodel.cpp ssrfsortfilterproxymodel.cpp ) + source_group("Subsurface Models" FILES ${SUBSURFACE_MODELS}) add_library(subsurface_models STATIC ${SUBSURFACE_MODELS_LIB_SRCS}) target_link_libraries(subsurface_models ${QT_LIBRARIES}) \ No newline at end of file -- cgit v1.2.3-70-g09d2 From b7a476169d50dbf4d40b134faac5b88cabd4fa17 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Thu, 17 Sep 2015 17:16:40 -0300 Subject: Preferecnes: add the initial skeleton This Preferences dialog should be visually similar to the old one - the main difference is how it acts on the preferences. It's also not based on .ui files since it's a very simple widget I prefered to mount it by hand - no more than 6 lines of c++ code. Right now we have only one preference page on this, and nothing is hoocked up. I've also changed mainwindow a bit to only show this dialog for testing purposes. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- desktop-widgets/CMakeLists.txt | 2 +- desktop-widgets/mainwindow.cpp | 3 + desktop-widgets/preferences/CMakeLists.txt | 1 + desktop-widgets/preferences/preferencesdialog.cpp | 83 +++++++++++++++++++++++ desktop-widgets/preferences/preferencesdialog.h | 30 ++++++++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 desktop-widgets/preferences/preferencesdialog.cpp create mode 100644 desktop-widgets/preferences/preferencesdialog.h (limited to 'desktop-widgets/mainwindow.cpp') diff --git a/desktop-widgets/CMakeLists.txt b/desktop-widgets/CMakeLists.txt index 6d9051b10..7b3e60f3b 100644 --- a/desktop-widgets/CMakeLists.txt +++ b/desktop-widgets/CMakeLists.txt @@ -91,4 +91,4 @@ target_link_libraries(subsurface_statistics ${QT_LIBRARIES}) add_library(subsurface_generated_ui STATIC ${SUBSURFACE_UI_HDRS}) target_link_libraries(subsurface_generated_ui ${QT_LIBRARIES}) add_library(subsurface_interface STATIC ${SUBSURFACE_INTERFACE}) -target_link_libraries(subsurface_interface ${QT_LIBRARIES} ${MARBLE_LIBRARIES}) +target_link_libraries(subsurface_interface ${QT_LIBRARIES} ${MARBLE_LIBRARIES} subsurface_desktop_preferences) diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 43b433d62..156034598 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -40,6 +40,7 @@ #include "divesitehelpers.h" #include "windowtitleupdate.h" #include "locationinformation.h" +#include "preferences/preferencesdialog.h" #ifndef NO_USERMANUAL #include "usermanual.h" @@ -254,6 +255,8 @@ MainWindow::MainWindow() : QMainWindow(), ui.menubar->show(); set_git_update_cb(&updateProgress); + PreferencesDialogV2 *d = new PreferencesDialogV2(); + d->show(); } MainWindow::~MainWindow() diff --git a/desktop-widgets/preferences/CMakeLists.txt b/desktop-widgets/preferences/CMakeLists.txt index 6af8b4a73..468501e7d 100644 --- a/desktop-widgets/preferences/CMakeLists.txt +++ b/desktop-widgets/preferences/CMakeLists.txt @@ -11,6 +11,7 @@ source_group("Subsurface Interface Files" FILES ${SUBSURFACE_PREFERENCES_UI}) set(SUBSURFACE_PREFERENCES_LIB_SRCS abstractpreferenceswidget.cpp + preferencesdialog.cpp preferences_language.cpp ) diff --git a/desktop-widgets/preferences/preferencesdialog.cpp b/desktop-widgets/preferences/preferencesdialog.cpp new file mode 100644 index 000000000..b2eb77975 --- /dev/null +++ b/desktop-widgets/preferences/preferencesdialog.cpp @@ -0,0 +1,83 @@ +#include "preferencesdialog.h" + +#include "abstractpreferenceswidget.h" +#include "preferences_language.h" + +#include +#include +#include +#include +#include + +PreferencesDialogV2::PreferencesDialogV2() +{ + pagesList = new QListWidget(); + pagesStack = new QStackedWidget(); + buttonBox = new QDialogButtonBox(QDialogButtonBox::Apply|QDialogButtonBox::RestoreDefaults|QDialogButtonBox::Cancel); + + pagesList->setMinimumWidth(120); + pagesList->setMaximumWidth(120); + + QHBoxLayout *h = new QHBoxLayout(); + h->addWidget(pagesList); + h->addWidget(pagesStack); + + QVBoxLayout *v = new QVBoxLayout(); + v->addLayout(h); + v->addWidget(buttonBox); + + setLayout(v); + + addPreferencePage(new PreferencesLanguage()); + refreshPages(); + connect(pagesList, &QListWidget::currentRowChanged, + pagesStack, &QStackedWidget::setCurrentIndex); +} + +PreferencesDialogV2::~PreferencesDialogV2() +{ +} + +bool abstractpreferenceswidget_lessthan(AbstractPreferencesWidget *p1, AbstractPreferencesWidget *p2) +{ + return p1->positionHeight() <= p2->positionHeight(); +} + +void PreferencesDialogV2::addPreferencePage(AbstractPreferencesWidget *page) +{ + pages.push_back(page); + qSort(pages.begin(), pages.end(), abstractpreferenceswidget_lessthan); +} + +void PreferencesDialogV2::refreshPages() +{ + // Remove things + pagesList->clear(); + while(pagesStack->count()) { + QWidget *curr = pagesStack->widget(0); + pagesStack->removeWidget(curr); + curr->setParent(0); + } + + // Readd things. + Q_FOREACH(AbstractPreferencesWidget *page, pages) { + QListWidgetItem *item = new QListWidgetItem(page->icon(), page->name()); + pagesList->addItem(item); + pagesStack->addWidget(page); + } +} + +void PreferencesDialogV2::applyRequested() +{ + //TODO +} + +void PreferencesDialogV2::cancelRequested() +{ + //TODO +} + +void PreferencesDialogV2::defaultsRequested() +{ + //TODO +} diff --git a/desktop-widgets/preferences/preferencesdialog.h b/desktop-widgets/preferences/preferencesdialog.h new file mode 100644 index 000000000..a1db94b43 --- /dev/null +++ b/desktop-widgets/preferences/preferencesdialog.h @@ -0,0 +1,30 @@ +#ifndef PREFERENCES_WIDGET_H +#define PREFERENCES_WIDGET_H + +#include +#include "pref.h" + +class AbstractPreferencesWidget; +class QListWidget; +class QStackedWidget; +class QDialogButtonBox; + +class PreferencesDialogV2 : public QDialog { + Q_OBJECT +public: + PreferencesDialogV2(); + virtual ~PreferencesDialogV2(); + void addPreferencePage(AbstractPreferencesWidget *page); + void refreshPages(); +private: + void cancelRequested(); + void applyRequested(); + void defaultsRequested(); + + QList pages; + QListWidget *pagesList; + QStackedWidget *pagesStack; + QDialogButtonBox *buttonBox; +}; + +#endif \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 628eb76af456374bbab90f785d8285a916e50399 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Fri, 9 Oct 2015 18:19:10 -0300 Subject: It's safe to delete a Null Pointer So don't check it with an if. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- desktop-widgets/mainwindow.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'desktop-widgets/mainwindow.cpp') diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 156034598..dcdbb89b4 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -1901,8 +1901,7 @@ void MainWindow::setApplicationState(const QByteArray& state) { void MainWindow::showProgressBar() { - if (progressDialog) - delete progressDialog; + delete progressDialog; progressDialog = new QProgressDialog(tr("Contacting cloud service..."), tr("Cancel"), 0, 100, this); progressDialog->setWindowModality(Qt::WindowModal); -- cgit v1.2.3-70-g09d2 From 9d1117cd6c513b6b64f45520a60051a1717ca50b Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Fri, 9 Oct 2015 19:36:03 -0300 Subject: Constify, Referencify Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- desktop-widgets/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'desktop-widgets/mainwindow.cpp') diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index dcdbb89b4..2afc8f979 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -1168,7 +1168,7 @@ void MainWindow::initialUiSetup() show(); } -const char *getSetting(QSettings &s, QString name) +const char *getSetting(const QSettings &s,const QString& name) { QVariant v; v = s.value(name); -- cgit v1.2.3-70-g09d2 From d3a8288ad5a25db502cb0c782b72c3242f3f30e9 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Fri, 9 Oct 2015 20:15:26 -0300 Subject: Populate the MainMenu with social network actions The magic happens here: We are iterating over the plugins and populating the main menu with all actions provided by them. Currently we can't test this as we don't have a single plugin. Next patch series of commits will be adding the Facebook plugin. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- desktop-widgets/mainwindow.cpp | 21 +++++++++++++++++++++ subsurface-core/isocialnetworkintegration.h | 1 + 2 files changed, 22 insertions(+) (limited to 'desktop-widgets/mainwindow.cpp') diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 2afc8f979..0150a7c6f 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -11,6 +11,7 @@ #include #include #include + #include "version.h" #include "divelistview.h" #include "downloadfromdivecomputer.h" @@ -52,6 +53,8 @@ #include #include #include "subsurface-core/color.h" +#include "subsurface-core/isocialnetworkintegration.h" +#include "subsurface-core/pluginmanager.h" #if defined(FBSUPPORT) #include "socialnetworks.h" @@ -252,6 +255,24 @@ MainWindow::MainWindow() : QMainWindow(), ui.actionFacebook->setEnabled(false); #endif + if(PluginManager::instance().socialNetworkIntegrationPlugins().count()) { + QMenu *connections = new QMenu(); + for(ISocialNetworkIntegration *plugin : PluginManager::instance().socialNetworkIntegrationPlugins()){ + QAction *toggle_connection = new QAction(this); + toggle_connection->setText(plugin->socialNetworkName()); + toggle_connection->setIcon(QIcon(plugin->socialNetworkIcon())); + + QAction *share_on = new QAction(this); + share_on->setText(plugin->socialNetworkName()); + share_on->setIcon(QIcon(plugin->socialNetworkIcon())); + ui.menuShare_on->addAction(share_on); + connections->addAction(toggle_connection); + } + ui.menuShare_on->addSeparator(); + ui.menuShare_on->addMenu(connections); + } else { + ui.menubar->removeAction(ui.menuShare_on->menuAction()); + } ui.menubar->show(); set_git_update_cb(&updateProgress); diff --git a/subsurface-core/isocialnetworkintegration.h b/subsurface-core/isocialnetworkintegration.h index 9e54c87ab..97ff91dc2 100644 --- a/subsurface-core/isocialnetworkintegration.h +++ b/subsurface-core/isocialnetworkintegration.h @@ -11,6 +11,7 @@ */ class ISocialNetworkIntegration { +public: /*! * @name socialNetworkName * @brief The name of this social network -- cgit v1.2.3-70-g09d2 From 8dad3457ef1f412517c4d81f08ebd14680788ec3 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Thu, 29 Oct 2015 21:47:08 -0200 Subject: Make the skeleton Facebook plugin and make sure it is loaded Currently we need to copy manually the plugin dynamic library to the /plugins folder. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- desktop-widgets/mainwindow.cpp | 4 ++- desktop-widgets/plugins/facebook/CMakeLists.txt | 5 +-- .../plugins/facebook/facebook_integration.cpp | 36 ++++++++++++++++++++++ .../plugins/facebook/facebook_integration.h | 21 +++++++++++++ subsurface-core/isocialnetworkintegration.h | 2 +- subsurface-core/pluginmanager.cpp | 24 +++++++++------ 6 files changed, 78 insertions(+), 14 deletions(-) (limited to 'desktop-widgets/mainwindow.cpp') diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 0150a7c6f..23b4fb9b0 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -256,15 +256,17 @@ MainWindow::MainWindow() : QMainWindow(), #endif if(PluginManager::instance().socialNetworkIntegrationPlugins().count()) { - QMenu *connections = new QMenu(); + QMenu *connections = new QMenu(tr("Connect to")); for(ISocialNetworkIntegration *plugin : PluginManager::instance().socialNetworkIntegrationPlugins()){ QAction *toggle_connection = new QAction(this); toggle_connection->setText(plugin->socialNetworkName()); toggle_connection->setIcon(QIcon(plugin->socialNetworkIcon())); + toggle_connection->setData(QVariant::fromValue(plugin)); QAction *share_on = new QAction(this); share_on->setText(plugin->socialNetworkName()); share_on->setIcon(QIcon(plugin->socialNetworkIcon())); + share_on->setData(QVariant::fromValue(plugin)); ui.menuShare_on->addAction(share_on); connections->addAction(toggle_connection); } diff --git a/desktop-widgets/plugins/facebook/CMakeLists.txt b/desktop-widgets/plugins/facebook/CMakeLists.txt index cccfec1f5..8628bd070 100644 --- a/desktop-widgets/plugins/facebook/CMakeLists.txt +++ b/desktop-widgets/plugins/facebook/CMakeLists.txt @@ -1,5 +1,6 @@ set(FACEBOOK_PLUGIN_SRCS facebook_integration.cpp) -add_library(facebook_integration ${FACEBOOK_PLUGIN_SRCS}) +add_library(facebook_integration SHARED ${FACEBOOK_PLUGIN_SRCS}) -target_link_libraries(facebook_integration subsurface_core ${QT_LIBRARIES}) +target_link_libraries(facebook_integration subsurface_corelib ${QT_LIBRARIES}) +add_dependencies(facebook_integration subsurface_corelib) \ No newline at end of file diff --git a/desktop-widgets/plugins/facebook/facebook_integration.cpp b/desktop-widgets/plugins/facebook/facebook_integration.cpp index e69de29bb..e9b2297a0 100644 --- a/desktop-widgets/plugins/facebook/facebook_integration.cpp +++ b/desktop-widgets/plugins/facebook/facebook_integration.cpp @@ -0,0 +1,36 @@ +#include "facebook_integration.h" + +FacebookPlugin::FacebookPlugin(QObject* parent): QObject(parent) +{ + +} + +bool FacebookPlugin::isConnected() +{ + +} + +void FacebookPlugin::requestLogin() +{ + +} + +void FacebookPlugin::requestLogoff() +{ + +} + +QString FacebookPlugin::socialNetworkIcon() const +{ + return QString(); +} + +QString FacebookPlugin::socialNetworkName() const +{ + return tr("Facebook"); +} + +void FacebookPlugin::uploadCurrentDive() +{ + +} diff --git a/desktop-widgets/plugins/facebook/facebook_integration.h b/desktop-widgets/plugins/facebook/facebook_integration.h index e69de29bb..a9d212e7e 100644 --- a/desktop-widgets/plugins/facebook/facebook_integration.h +++ b/desktop-widgets/plugins/facebook/facebook_integration.h @@ -0,0 +1,21 @@ +#ifndef FACEBOOK_INTEGRATION_H +#define FACEBOOK_INTEGRATION_H + +#include "subsurface-core/isocialnetworkintegration.h" +#include + +class FacebookPlugin : public QObject, public ISocialNetworkIntegration { + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.subsurface.plugins.ISocialNetworkIntegration") + Q_INTERFACES(ISocialNetworkIntegration) +public: + explicit FacebookPlugin(QObject* parent = 0); + virtual bool isConnected(); + virtual void requestLogin(); + virtual void requestLogoff(); + virtual QString socialNetworkIcon() const; + virtual QString socialNetworkName() const; + virtual void uploadCurrentDive(); +}; + +#endif \ No newline at end of file diff --git a/subsurface-core/isocialnetworkintegration.h b/subsurface-core/isocialnetworkintegration.h index 97ff91dc2..778a171b3 100644 --- a/subsurface-core/isocialnetworkintegration.h +++ b/subsurface-core/isocialnetworkintegration.h @@ -68,5 +68,5 @@ public: }; Q_DECLARE_INTERFACE(ISocialNetworkIntegration, "org.subsurface.ISocialNetworkIntegration.v1") - +Q_DECLARE_METATYPE(ISocialNetworkIntegration*); #endif \ No newline at end of file diff --git a/subsurface-core/pluginmanager.cpp b/subsurface-core/pluginmanager.cpp index 290f43df0..5c0f22525 100644 --- a/subsurface-core/pluginmanager.cpp +++ b/subsurface-core/pluginmanager.cpp @@ -3,18 +3,22 @@ #include #include #include +#include static QList _socialNetworks; -PluginManager& PluginManager::instance() { +PluginManager& PluginManager::instance() +{ static PluginManager self; return self; } -PluginManager::PluginManager() { +PluginManager::PluginManager() +{ } -void PluginManager::loadPlugins() { +void PluginManager::loadPlugins() +{ QDir pluginsDir(qApp->applicationDirPath()); #if defined(Q_OS_WIN) @@ -29,19 +33,19 @@ void PluginManager::loadPlugins() { #endif pluginsDir.cd("plugins"); + qDebug() << "Plugins Directory: " << pluginsDir; foreach (const QString& fileName, pluginsDir.entryList(QDir::Files)) { QPluginLoader loader(pluginsDir.absoluteFilePath(fileName)); QObject *plugin = loader.instance(); - if(!plugin) { + if(!plugin) continue; - } - if (ISocialNetworkIntegration *social = qobject_cast(plugin)){ + if (ISocialNetworkIntegration *social = qobject_cast(plugin)) _socialNetworks.push_back(social); - } - } + } } -QList PluginManager::socialNetworkIntegrationPlugins() const { +QList PluginManager::socialNetworkIntegrationPlugins() const +{ return _socialNetworks; -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From ff57881265a305fe06691b065bc3f0efae88e6b3 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Sat, 31 Oct 2015 21:02:16 -0200 Subject: Preferences: Remove the old dialog and use the new one The new preferences dialog still needs a bit of fine tuning but should already work. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- desktop-widgets/CMakeLists.txt | 1 - desktop-widgets/mainwindow.cpp | 4 +- desktop-widgets/preferences.h | 43 ---------------------- .../preferences/preferences_network.cpp | 1 - desktop-widgets/preferences/preferencesdialog.cpp | 29 ++++++++++----- desktop-widgets/preferences/preferencesdialog.h | 8 ++-- profile-widget/divecartesianaxis.cpp | 2 +- profile-widget/divepixmapitem.cpp | 2 +- profile-widget/diveprofileitem.cpp | 2 +- profile-widget/profilewidget2.cpp | 2 +- profile-widget/ruleritem.cpp | 2 +- 11 files changed, 31 insertions(+), 65 deletions(-) delete mode 100644 desktop-widgets/preferences.h (limited to 'desktop-widgets/mainwindow.cpp') diff --git a/desktop-widgets/CMakeLists.txt b/desktop-widgets/CMakeLists.txt index 62be916cc..0a0059ee3 100644 --- a/desktop-widgets/CMakeLists.txt +++ b/desktop-widgets/CMakeLists.txt @@ -30,7 +30,6 @@ set(SUBSURFACE_INTERFACE mainwindow.cpp modeldelegates.cpp notificationwidget.cpp - preferences.cpp simplewidgets.cpp starwidget.cpp subsurfacewebservices.cpp diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index 23b4fb9b0..dc45d1d8d 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -15,7 +15,6 @@ #include "version.h" #include "divelistview.h" #include "downloadfromdivecomputer.h" -#include "preferences.h" #include "subsurfacewebservices.h" #include "divecomputermanagementdialog.h" #include "about.h" @@ -278,8 +277,6 @@ MainWindow::MainWindow() : QMainWindow(), ui.menubar->show(); set_git_update_cb(&updateProgress); - PreferencesDialogV2 *d = new PreferencesDialogV2(); - d->show(); } MainWindow::~MainWindow() @@ -1785,6 +1782,7 @@ void MainWindow::editCurrentDive() } } +// TODO: Remove the dependency to the PreferencesDialog here. #define PREF_PROFILE(QT_PREFS) \ QSettings s; \ s.beginGroup("TecDetails"); \ diff --git a/desktop-widgets/preferences.h b/desktop-widgets/preferences.h deleted file mode 100644 index 4b619dde4..000000000 --- a/desktop-widgets/preferences.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef PREFERENCES_H -#define PREFERENCES_H - -#include -#include "pref.h" - -#include "ui_preferences.h" - -#ifndef Q_OS_ANDROID - class QWebView; -#endif - -class QAbstractButton; - -class PreferencesDialog : public QDialog { - Q_OBJECT -public: - static PreferencesDialog *instance(); - void showEvent(QShowEvent *); - void emitSettingsChanged(); - -signals: - void settingsChanged(); -public -slots: - void buttonClicked(QAbstractButton *button); - void syncSettings(); - void loadSettings(); - void restorePrefs(); - void rememberPrefs(); - void facebookLoggedIn(); - void facebookDisconnect(); -private: - explicit PreferencesDialog(QWidget *parent = 0, Qt::WindowFlags f = 0); - void setUiFromPrefs(); - Ui::PreferencesDialog ui; - struct preferences oldPrefs; - #ifndef Q_OS_ANDROID - QWebView *facebookWebView; - #endif -}; - -#endif // PREFERENCES_H diff --git a/desktop-widgets/preferences/preferences_network.cpp b/desktop-widgets/preferences/preferences_network.cpp index 000df1e7f..3780a6c91 100644 --- a/desktop-widgets/preferences/preferences_network.cpp +++ b/desktop-widgets/preferences/preferences_network.cpp @@ -1,6 +1,5 @@ #include "preferences_network.h" #include "ui_preferences_network.h" -#include "preferences.h" #include "dive.h" #include "subsurfacewebservices.h" #include "subsurface-core/prefs-macros.h" diff --git a/desktop-widgets/preferences/preferencesdialog.cpp b/desktop-widgets/preferences/preferencesdialog.cpp index 5b4cc560e..d59296519 100644 --- a/desktop-widgets/preferences/preferencesdialog.cpp +++ b/desktop-widgets/preferences/preferencesdialog.cpp @@ -16,7 +16,18 @@ #include #include -PreferencesDialogV2::PreferencesDialogV2() +PreferencesDialog* PreferencesDialog::instance() +{ + PreferencesDialog *self = new PreferencesDialog(); + return self; +} + +void PreferencesDialog::emitSettingsChanged() +{ + emit settingsChanged(); +} + +PreferencesDialog::PreferencesDialog() { pagesList = new QListWidget(); pagesStack = new QStackedWidget(); @@ -48,14 +59,14 @@ PreferencesDialogV2::PreferencesDialogV2() connect(pagesList, &QListWidget::currentRowChanged, pagesStack, &QStackedWidget::setCurrentIndex); connect(buttonBox, &QDialogButtonBox::clicked, - this, &PreferencesDialogV2::buttonClicked); + this, &PreferencesDialog::buttonClicked); } -PreferencesDialogV2::~PreferencesDialogV2() +PreferencesDialog::~PreferencesDialog() { } -void PreferencesDialogV2::buttonClicked(QAbstractButton* btn) +void PreferencesDialog::buttonClicked(QAbstractButton* btn) { QDialogButtonBox::ButtonRole role = buttonBox->buttonRole(btn); switch(role) { @@ -70,13 +81,13 @@ bool abstractpreferenceswidget_lessthan(AbstractPreferencesWidget *p1, AbstractP return p1->positionHeight() <= p2->positionHeight(); } -void PreferencesDialogV2::addPreferencePage(AbstractPreferencesWidget *page) +void PreferencesDialog::addPreferencePage(AbstractPreferencesWidget *page) { pages.push_back(page); qSort(pages.begin(), pages.end(), abstractpreferenceswidget_lessthan); } -void PreferencesDialogV2::refreshPages() +void PreferencesDialog::refreshPages() { // Remove things pagesList->clear(); @@ -95,7 +106,7 @@ void PreferencesDialogV2::refreshPages() } } -void PreferencesDialogV2::applyRequested() +void PreferencesDialog::applyRequested() { Q_FOREACH(AbstractPreferencesWidget *page, pages) { page->syncSettings(); @@ -104,7 +115,7 @@ void PreferencesDialogV2::applyRequested() accept(); } -void PreferencesDialogV2::cancelRequested() +void PreferencesDialog::cancelRequested() { Q_FOREACH(AbstractPreferencesWidget *page, pages) { page->refreshSettings(); @@ -112,7 +123,7 @@ void PreferencesDialogV2::cancelRequested() reject(); } -void PreferencesDialogV2::defaultsRequested() +void PreferencesDialog::defaultsRequested() { prefs = default_prefs; Q_FOREACH(AbstractPreferencesWidget *page, pages) { diff --git a/desktop-widgets/preferences/preferencesdialog.h b/desktop-widgets/preferences/preferencesdialog.h index 720b94c25..611bd5fac 100644 --- a/desktop-widgets/preferences/preferencesdialog.h +++ b/desktop-widgets/preferences/preferencesdialog.h @@ -10,16 +10,18 @@ class QStackedWidget; class QDialogButtonBox; class QAbstractButton; -class PreferencesDialogV2 : public QDialog { +class PreferencesDialog : public QDialog { Q_OBJECT public: - PreferencesDialogV2(); - virtual ~PreferencesDialogV2(); + static PreferencesDialog* instance(); + virtual ~PreferencesDialog(); void addPreferencePage(AbstractPreferencesWidget *page); void refreshPages(); + void emitSettingsChanged(); signals: void settingsChanged(); private: + PreferencesDialog(); void cancelRequested(); void applyRequested(); void defaultsRequested(); diff --git a/profile-widget/divecartesianaxis.cpp b/profile-widget/divecartesianaxis.cpp index bf5a5380c..f40e1c3e5 100644 --- a/profile-widget/divecartesianaxis.cpp +++ b/profile-widget/divecartesianaxis.cpp @@ -1,7 +1,7 @@ #include "divecartesianaxis.h" #include "divetextitem.h" #include "helpers.h" -#include "preferences.h" +#include "preferences/preferencesdialog.h" #include "diveplotdatamodel.h" #include "animationfunctions.h" #include "mainwindow.h" diff --git a/profile-widget/divepixmapitem.cpp b/profile-widget/divepixmapitem.cpp index 581f6f9b4..627473c2f 100644 --- a/profile-widget/divepixmapitem.cpp +++ b/profile-widget/divepixmapitem.cpp @@ -1,7 +1,7 @@ #include "divepixmapitem.h" #include "animationfunctions.h" #include "divepicturemodel.h" -#include +#include "preferences/preferencesdialog.h" #include #include diff --git a/profile-widget/diveprofileitem.cpp b/profile-widget/diveprofileitem.cpp index 7cdccee32..14efa9123 100644 --- a/profile-widget/diveprofileitem.cpp +++ b/profile-widget/diveprofileitem.cpp @@ -5,7 +5,7 @@ #include "animationfunctions.h" #include "dive.h" #include "profile.h" -#include "preferences.h" +#include "preferences/preferencesdialog.h" #include "diveplannermodel.h" #include "helpers.h" #include "libdivecomputer/parser.h" diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 3ccd1bb6d..8ff8e8669 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -29,7 +29,7 @@ #include #endif #include "mainwindow.h" -#include +#include "preferences/preferencesdialog.h" /* This is the global 'Item position' variable. * it should tell you where to position things up diff --git a/profile-widget/ruleritem.cpp b/profile-widget/ruleritem.cpp index 830985552..a5a61c0fe 100644 --- a/profile-widget/ruleritem.cpp +++ b/profile-widget/ruleritem.cpp @@ -1,5 +1,5 @@ #include "ruleritem.h" -#include "preferences.h" +#include "preferences/preferencesdialog.h" #include "mainwindow.h" #include "profilewidget2.h" #include "display.h" -- cgit v1.2.3-70-g09d2