/* * 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 "subsurfacewebservices.h" #include "divecomputermanagementdialog.h" #include "about.h" #include "updatemanager.h" #include "planner.h" #include "filtermodels.h" #include "profile-widget/profilewidget2.h" #include "globe.h" #include "divecomputer.h" #include "maintab.h" #include "diveplanner.h" #ifndef NO_PRINTING #include #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" #include "preferences/preferencesdialog.h" #ifndef NO_USERMANUAL #include "usermanual.h" #endif #include "divepicturemodel.h" #include "git-access.h" #include #include #include #include #include "subsurface-core/color.h" #include "subsurface-core/isocialnetworkintegration.h" #include "subsurface-core/pluginmanager.h" #if defined(FBSUPPORT) #include "plugins/facebook/facebook_integration.h" #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); 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(information(), SIGNAL(dateTimeChanged()), graphics(), SLOT(dateTimeChanged())); 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 enableDisableCloudActions(); GpsLocation *locationProvider = new GpsLocation(&report_message, this); if (!locationProvider->hasLocationsSource()) { ui.menuFile->removeAction(ui.add_GPS_location_here); } 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 setupSocialNetworkMenu(); set_git_update_cb(&updateProgress); } MainWindow::~MainWindow() { write_hashes(); m_Instance = NULL; } void MainWindow::setupSocialNetworkMenu() { #ifdef FBSUPPORT QMenu *connections = new QMenu(tr("Connect to")); FacebookPlugin *facebookPlugin = new FacebookPlugin(); QAction *toggle_connection = new QAction(this); QObject *obj = qobject_cast(facebookPlugin); toggle_connection->setText(facebookPlugin->socialNetworkName()); toggle_connection->setIcon(QIcon(facebookPlugin->socialNetworkIcon())); toggle_connection->setData(QVariant::fromValue(obj)); connect(toggle_connection, SIGNAL(triggered()), this, SLOT(socialNetworkRequestConnect())); QAction *share_on = new QAction(this); share_on->setText(facebookPlugin->socialNetworkName()); share_on->setIcon(QIcon(facebookPlugin->socialNetworkIcon())); share_on->setData(QVariant::fromValue(obj)); ui.menuShare_on->addAction(share_on); connections->addAction(toggle_connection); connect(share_on, SIGNAL(triggered()), this, SLOT(socialNetworkRequestUpload())); ui.menuShare_on->addSeparator(); ui.menuShare_on->addMenu(connections); ui.menubar->show(); #endif } void MainWindow::socialNetworkRequestConnect() { QAction *action = qobject_cast(sender()); ISocialNetworkIntegration *plugin = qobject_cast(action->data().value()); if (plugin->isConnected()) plugin->requestLogoff(); else plugin->requestLogin(); } void MainWindow::socialNetworkRequestUpload() { QAction *action = qobject_cast(sender()); ISocialNetworkIntegration *plugin = action->data().value(); plugin->requestUpload(); } 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() { ui.actionCloudstorageopen->setEnabled(prefs.cloud_verification_status == CS_VERIFIED); ui.actionCloudstoragesave->setEnabled(prefs.cloud_verification_status == CS_VERIFIED); } 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); } } 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; /* render the profile as a pixmap that is inserted as base64 data into a HTML tag * make it fit a page width defined by 2 cm margins via QTextDocument->print() (cannot be changed?) * the height of the profile is 40% of the page height. */ QSizeF renderSize = printer.pageRect(QPrinter::Inch).size(); const qreal marginsInch = 1.57480315; // = (2 x 2cm) / 2.45cm/inch renderSize.setWidth((renderSize.width() - marginsInch) * printer.resolution()); renderSize.setHeight(((renderSize.height() - marginsInch) * printer.resolution()) / 2.5); QPixmap pixmap(renderSize.toSize()); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); ProfileWidget2 *profile = graphics(); QSize origSize = profile->size(); profile->resize(renderSize.toSize()); profile->setPrintMode(true); profile->render(&painter); profile->resize(origSize); profile->setPrintMode(false); QByteArray byteArray; QBuffer buffer(&byteArray); pixmap.save(&buffer, "PNG"); QString profileImage = QString("

"; withDisclaimer = profileImage + withDisclaimer; 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(const QSettings &s,const 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(); init_proxy(); // now make sure that the cloud menu items are enabled IFF cloud account is verified enableDisableCloudActions(); #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 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); } } // TODO: Remove the dependency to the PreferencesDialog here. #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() { 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; } }