/* OSCAR MainWindow Implementation * * Copyright (c) 2020-2022 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ #define TEST_MACROS_ENABLEDoff #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common_gui.h" #include "version.h" // Custom loaders that don't autoscan.. #include #include #include #include #ifdef REMSTAR_M_SUPPORT #include #endif #include "logger.h" #include "mainwindow.h" #include "ui_mainwindow.h" #include "aboutdialog.h" #include "newprofile.h" #include "exportcsv.h" #include "SleepLib/schema.h" #include "Graphs/glcommon.h" #include "checkupdates.h" #include "SleepLib/calcs.h" #include "SleepLib/progressdialog.h" #include "SleepLib/importcontext.h" #include "reports.h" #include "statistics.h" #include "zip.h" #if QT_VERSION >= QT_VERSION_CHECK(5,4,0) #include #endif CheckUpdates *updateChecker; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); if (logger) { connect(logger, SIGNAL(outputLog(QString)), this, SLOT(logMessage(QString))); logger->connectionReady(); } // Bring window to top (useful when language is changed) this->activateWindow(); this->raise(); // Initialise oscar app registry stuff QSettings settings; // Load previous Window geometry restoreGeometry(settings.value("MainWindow/geometry").toByteArray()); // Nifty Notification popups in System Tray (uses Growl on Mac) if (QSystemTrayIcon::isSystemTrayAvailable() && QSystemTrayIcon::supportsMessages()) { qDebug() << "Using System Tray for Menu"; systray = new QSystemTrayIcon(QIcon(":/icons/logo-sm.png"), this); systray->show(); // seems to need the systray menu for notifications to work systraymenu = new QMenu(this); systray->setContextMenu(systraymenu); QAction *a = systraymenu->addAction(STR_TR_OSCAR + " " + getVersion().displayString()); a->setEnabled(false); systraymenu->addSeparator(); systraymenu->addAction(tr("&About"), this, SLOT(on_action_About_triggered())); // systraymenu->addAction(tr("Check for &Updates"), this, SLOT(on_actionCheck_for_Updates_triggered())); systraymenu->addSeparator(); systraymenu->addAction(tr("E&xit"), this, SLOT(close())); // systraymenu = nullptr; } else { // if not available, the messages will popup in the taskbar qDebug() << "No System Tray menues"; systray = nullptr; systraymenu = nullptr; } } bool setupRunning = false; QString MainWindow::getMainWindowTitle() { QString title = STR_TR_OSCAR + " " + getVersion().displayString(); #ifdef BROKEN_OPENGL_BUILD title += " ["+CSTR_GFX_BrokenGL+"]"; #endif return title; } void MainWindow::SetupGUI() { setupRunning = true; setWindowTitle(getMainWindowTitle()); #ifdef Q_OS_MAC ui->action_About->setMenuRole(QAction::AboutRole); ui->action_Preferences->setMenuRole(QAction::PreferencesRole); #endif ui->actionPrint_Report->setShortcuts(QKeySequence::Print); ui->action_Fullscreen->setShortcuts(QKeySequence::FullScreen); ui->actionLine_Cursor->setChecked(AppSetting->lineCursorMode()); ui->actionPie_Chart->setChecked(AppSetting->showPieChart()); ui->actionDebug->setChecked(AppSetting->showDebug()); ui->actionShow_Performance_Counters->setChecked(AppSetting->showPerformance()); overview = nullptr; daily = nullptr; prefdialog = nullptr; profileSelector = nullptr; welcome = nullptr; #ifdef NO_CHECKUPDATES ui->action_Check_for_Updates->setVisible(false); #endif ui->oximetryButton->setDisabled(true); ui->dailyButton->setDisabled(true); ui->overviewButton->setDisabled(true); ui->statisticsButton->setDisabled(true); ui->importButton->setDisabled(true); #ifdef helpless ui->helpButton->setVisible(false); #endif m_inRecalculation = false; m_restartRequired = false; // Initialize Status Bar objects QTextCharFormat format = ui->statStartDate->calendarWidget()->weekdayTextFormat(Qt::Saturday); format.setForeground(QBrush(Qt::black, Qt::SolidPattern)); Qt::DayOfWeek dow=firstDayOfWeekFromLocale(); ui->statStartDate->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format); ui->statStartDate->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format); ui->statStartDate->calendarWidget()->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader); ui->statStartDate->calendarWidget()->setFirstDayOfWeek(dow); ui->statEndDate->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format); ui->statEndDate->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format); ui->statEndDate->calendarWidget()->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader); ui->statEndDate->calendarWidget()->setFirstDayOfWeek(dow); ui->statEndDate->setVisible(false); ui->statStartDate->setVisible(false); // ui->reportModeRange->setVisible(false); int srm = 0; if (p_profile) { srm = p_profile->general->statReportMode(); } switch(srm) { case 0: ui->reportModeStandard->setChecked(true); break; case 1: ui->reportModeMonthly->setChecked(true); break; case 2: ui->reportModeRange->setChecked(true); ui->statEndDate->setVisible(true); ui->statStartDate->setVisible(true); break; default: if (p_profile) { p_profile->general->setStatReportMode(0); } break; } if (!AppSetting->showDebug()) { ui->logText->hide(); } #ifdef Q_OS_MAC //p_profile->appearance->setAntiAliasing(false); #endif //ui->action_Link_Graph_Groups->setChecked(p_profile->general->linkGroups()); first_load = true; profileSelector = new ProfileSelector(ui->tabWidget); ui->tabWidget->insertTab(0, profileSelector, STR_TR_Profile); // Profiles haven't been loaded here... profileSelector->updateProfileList(); // Start with the new Profile Tab ui->tabWidget->setCurrentWidget(profileSelector); // setting this to daily shows the cube during loading.. ui->tabWidget->setTabEnabled(1, false); // this should be the Statistics tab ui->toolBox->setCurrentIndex(0); bool b = AppSetting->rightSidebarVisible(); ui->action_Sidebar_Toggle->setChecked(b); ui->toolBox->setVisible(b); bool oldState = ui->actionShowPersonalData->blockSignals(true); ui->actionShowPersonalData->setChecked(AppSetting->showPersonalData()); ui->actionShowPersonalData->blockSignals(oldState); oldState = ui->actionPie_Chart->blockSignals(true); ui->actionPie_Chart->setChecked(AppSetting->showPieChart()); ui->actionPie_Chart->blockSignals(oldState); oldState = ui->actionDaily_Calendar->blockSignals(true); ui->actionDaily_Calendar->setChecked(AppSetting->calendarVisible()); ui->actionDaily_Calendar->blockSignals(oldState); on_tabWidget_currentChanged(0); #ifndef REMSTAR_M_SUPPORT ui->actionImport_RemStar_MSeries_Data->setVisible(false); #endif QRandomGenerator(QDateTime::currentDateTime().toTime_t()); QList a; int panel_width = AppSetting->rightPanelWidth(); a.push_back(this->width() - panel_width); a.push_back(panel_width); ui->mainsplitter->setSizes(a); ui->mainsplitter->setStretchFactor(0,1); ui->mainsplitter->setStretchFactor(1,0); QTimer::singleShot(50, this, SLOT(Startup())); ui->actionChange_Data_Folder->setVisible(false); ui->action_Frequently_Asked_Questions->setVisible(false); ui->actionReport_a_Bug->setVisible(false); // remove this once we actually implement it ui->actionExport_Review->setVisible(false); // remove this once we actually implement it #ifndef helpless help = new Help(this); ui->tabWidget->addTab(help, tr("Help Browser")); #endif setupRunning = false; } void MainWindow::logMessage(QString msg) { ui->logText->appendPlainText(msg); } void MainWindow::closeEvent(QCloseEvent * event) { Q_UNUSED(event); static bool runonce = false; if (!runonce) { if (AppSetting->removeCardReminder()) { Notify(QObject::tr("Don't forget to place your datacard back in your CPAP device"), QObject::tr("OSCAR Reminder")); QThread::msleep(1000); QApplication::processEvents(); } schema::channel.Save(); if (p_profile) { CloseProfile(); } // Shutdown and Save the current User profile Profiles::Done(); // Save current window position QSettings settings; settings.setValue("MainWindow/geometry", saveGeometry()); // Trash anything allocated by the Graph objects DestroyGraphGlobals(); if (systraymenu) { delete systraymenu; systraymenu = nullptr; } if (systray) { delete systray; systray = nullptr; } disconnect(logger, SIGNAL(outputLog(QString)), this, SLOT(logMessage(QString))); shutdownLogger(); runonce = true; } else { qDebug() << "Qt is still calling closevent multiple times"; QApplication::processEvents(); } } MainWindow::~MainWindow() { delete ui; QCoreApplication::quit(); } void MainWindow::log(QString text) { logger->appendClean(text); } void MainWindow::EnableTabs(bool b) { ui->tabWidget->setTabEnabled(2, b); ui->tabWidget->setTabEnabled(3, b); ui->tabWidget->setTabEnabled(4, b); } void MainWindow::Notify(QString s, QString title, int ms) { if (title.isEmpty()) { title = STR_TR_OSCAR + " " + getVersion().displayString(); } if (systray) { QString msg = s; #ifdef Q_OS_UNIX // GNOME3's systray hides the last line of the displayed Qt message. // As a workaround, add an extra line to bump the message back // into the visible area. char *desktop = getenv("DESKTOP_SESSION"); if (desktop && !strncmp(desktop, "gnome", 5)) { msg += "\n"; } #endif systray->showMessage(title, msg, QSystemTrayIcon::Information, ms); } } QString getCPAPPixmap(QString mach_class) { QString cpapimage; if (mach_class == STR_MACH_ResMed) cpapimage = ":/icons/rms9.png"; else if (mach_class == STR_MACH_PRS1) cpapimage = ":/icons/prs1.png"; else if (mach_class == STR_MACH_Intellipap) cpapimage = ":/icons/intellipap.png"; return cpapimage; } //QIcon getCPAPIcon(QString mach_class) //{ // QString cpapimage = getCPAPPixmap(mach_class); // return QIcon(cpapimage); //} void MainWindow::PopulatePurgeMenu() { ui->menu_Rebuild_CPAP_Data->disconnect(ui->menu_Rebuild_CPAP_Data, SIGNAL(triggered(QAction*)), this, SLOT(on_actionRebuildCPAP(QAction *))); ui->menu_Rebuild_CPAP_Data->clear(); ui->menuPurge_CPAP_Data->disconnect(ui->menuPurge_CPAP_Data, SIGNAL(triggered(QAction*)), this, SLOT(on_actionPurgeMachine(QAction *))); ui->menuPurge_CPAP_Data->clear(); // Only allow rebuilding for CPAP for now, since that's the only thing that makes backups. QList machines = p_profile->GetMachines(MT_CPAP); for (int i=0; i < machines.size(); ++i) { Machine *mach = machines.at(i); addMachineToMenu(mach, ui->menu_Rebuild_CPAP_Data); } // Add any imported device (except the built-in journal) to the purge menu. machines = p_profile->GetMachines(); for (int i=0; i < machines.size(); ++i) { Machine *mach = machines.at(i); if (mach->type() == MT_JOURNAL) { continue; } addMachineToMenu(mach, ui->menuPurge_CPAP_Data); } ui->menu_Rebuild_CPAP_Data->connect(ui->menu_Rebuild_CPAP_Data, SIGNAL(triggered(QAction*)), this, SLOT(on_actionRebuildCPAP(QAction*))); ui->menuPurge_CPAP_Data->connect(ui->menuPurge_CPAP_Data, SIGNAL(triggered(QAction*)), this, SLOT(on_actionPurgeMachine(QAction*))); } void MainWindow::addMachineToMenu(Machine* mach, QMenu* menu) { QString name = mach->brand(); if (name.isEmpty()) { name = mach->loaderName(); } name += " " + mach->model() + " " + mach->serial(); QAction * action = new QAction(name.replace("&","&&"), menu); action->setIconVisibleInMenu(true); action->setIcon(mach->getPixmap()); action->setData(mach->loaderName()+":"+mach->serial()); menu->addAction(action); } void MainWindow::firstRunMessage() { if (AppSetting->showAboutDialog() > 0) { AboutDialog * about = new AboutDialog(this); about->exec(); AppSetting->setShowAboutDialog(-1); about->deleteLater(); } } // QString GenerateWelcomeHTML(); bool MainWindow::OpenProfile(QString profileName, bool skippassword) { qDebug() << "Opening profile" << profileName; auto pit = Profiles::profiles.find(profileName); if (pit == Profiles::profiles.end()) return false; Profile * prof = pit.value(); if (p_profile) { if ((prof != p_profile)) { CloseProfile(); } else { // Already open return false; } } prof = profileSelector->SelectProfile(profileName, skippassword); // asks for the password and updates stuff in profileSelector tab if (!prof) { return false; } // TODO: Check profile password // Check Lockfile QString lockhost = prof->checkLock(); if (!lockhost.isEmpty()) { if (lockhost.compare(QHostInfo::localHostName()) != 0) { if (QMessageBox::warning(nullptr, STR_MessageBox_Warning, QObject::tr("There is a lockfile already present for this profile '%1', claimed on '%2'.").arg(prof->user->userName()).arg(lockhost)+"\n\n"+ QObject::tr("You can only work with one instance of an individual OSCAR profile at a time.")+"\n\n"+ QObject::tr("If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding."), QMessageBox::Cancel |QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Cancel) { return false; } } // not worried about localhost locks anymore, just silently drop it. prof->removeLock(); } p_profile = prof; ProgressDialog * progress = new ProgressDialog(this); progress->setMessage(QObject::tr("Loading profile \"%1\"...").arg(profileName)); progress->open(); QList machines = p_profile->GetMachines(MT_CPAP); if (machines.isEmpty()) { qDebug() << "No devices in profile"; } else { #ifdef LOCK_RESMED_SESSIONS for (QList::iterator it = machines.begin(); it != machines.end(); ++it) { QString mclass=(*it)->loaderName(); if (mclass == STR_MACH_ResMed) { qDebug() << "ResMed device found.. locking OSCAR preferences to suit it's summary system"; // Have to sacrifice these features to get access to summary data. p_profile->session->setCombineCloseSessions(0); p_profile->session->setDaySplitTime(QTime(12,0,0)); p_profile->session->setIgnoreShortSessions(false); p_profile->session->setLockSummarySessions(true); p_profile->general->setPrefCalcPercentile(95.0); // 95% p_profile->general->setPrefCalcMiddle(0); // Median (50%) p_profile->general->setPrefCalcMax(1); // Dodgy max break; } } #endif } // Todo: move this to AHIWIndow check to profile Load function... if (p_profile->cpap->AHIWindow() < 30.0) { p_profile->cpap->setAHIWindow(60.0); } if (p_profile->p_preferences[STR_PREF_ReimportBackup].toBool()) { importCPAPBackups(); p_profile->p_preferences[STR_PREF_ReimportBackup]=false; } p_profile->LoadMachineData(progress); progress->setMessage(tr("Loading profile \"%1\"").arg(profileName)); // Show the logo? // QPixmap logo=QPixmap(":/icons/logo-md.png").scaled(64,64); // progress->setPixmap(logo); QApplication::processEvents(); ui->statStartDate->setDate(p_profile->FirstDay()); ui->statEndDate->setDate(p_profile->LastDay()); // Reload everything profile related if (daily) { qCritical() << "OpenProfile called with active Daily object!"; qDebug() << "Abandon opening Profile"; return false; } welcome = new Welcome(ui->tabWidget); ui->tabWidget->insertTab(1, welcome, tr("Welcome")); daily = new Daily(ui->tabWidget, nullptr); ui->tabWidget->insertTab(2, daily, STR_TR_Daily); daily->ReloadGraphs(); if (overview) { qCritical() << "OpenProfile called with active Overview object!"; qDebug() << "Abandon opening Profile"; return false; } overview = new Overview(ui->tabWidget, daily->graphView()); ui->tabWidget->insertTab(3, overview, STR_TR_Overview); overview->ReloadGraphs(); // Should really create welcome and statistics here, but they need redoing later anyway to kill off webkit ui->tabWidget->setCurrentIndex(AppSetting->openTabAtStart()); GenerateStatistics(); PopulatePurgeMenu(); AppSetting->setProfileName(p_profile->user->userName()); setWindowTitle(tr("%1 (Profile: %2)").arg(getMainWindowTitle()).arg(AppSetting->profileName())); QList oximachines = p_profile->GetMachines(MT_OXIMETER); // Machines of any type except Journal QList posmachines = p_profile->GetMachines(MT_POSITION); QList stgmachines = p_profile->GetMachines(MT_SLEEPSTAGE); bool noMachines = machines.isEmpty() && posmachines.isEmpty() && oximachines.isEmpty() && stgmachines.isEmpty(); ui->importButton->setDisabled(false); ui->oximetryButton->setDisabled(false); ui->dailyButton->setDisabled(noMachines); ui->overviewButton->setDisabled(noMachines); ui->statisticsButton->setDisabled(noMachines); ui->tabWidget->setTabEnabled(2, !noMachines); // daily, STR_TR_Daily); ui->tabWidget->setTabEnabled(3, !noMachines); // overview, STR_TR_Overview); ui->tabWidget->setTabEnabled(4, !noMachines); // statistics, STR_TR_Statistics); int srm = 0; if (p_profile) { srm = p_profile->general->statReportMode(); } switch (srm) { case 0: ui->reportModeStandard->setChecked(true); ui->statEndDate->setVisible(false); ui->statStartDate->setVisible(false); break; case 1: ui->reportModeMonthly->setChecked(true); ui->statEndDate->setVisible(false); ui->statStartDate->setVisible(false); break; case 2: ui->reportModeRange->setChecked(true); ui->statEndDate->setVisible(true); ui->statStartDate->setVisible(true); break; default: if (p_profile) { p_profile->general->setStatReportMode(0); } break; } progress->close(); delete progress; qDebug() << "Finished opening Profile"; if (updateChecker != nullptr) updateChecker->showMessage(); return true; } void MainWindow::CloseProfile() { if (updateChecker != nullptr) updateChecker->showMessage(); if (daily) { daily->Unload(); daily->clearLastDay(); // otherwise Daily will crash delete daily; daily = nullptr; } if (welcome) { delete welcome; welcome = nullptr; } if (overview) { delete overview; overview = nullptr; } if (p_profile) { p_profile->StoreMachines(); p_profile->UnloadMachineData(); p_profile->saveChannels(); p_profile->Save(); p_profile->removeLock(); p_profile = nullptr; } } #ifdef Q_OS_WIN void MainWindow::TestWindowsOpenGL() { #if (QT_VERSION >= QT_VERSION_CHECK(5,4,0)) && !defined(BROKEN_OPENGL_BUILD) // 1. Set OpenGLCompatibilityCheck=1 in registry. QSettings settings; settings.setValue("OpenGLCompatibilityCheck", true); // 2. See if OpenGL crashes the application: QOpenGLWidget* gl; gl = new QOpenGLWidget(ui->tabWidget); ui->tabWidget->insertTab(2, gl, ""); //qDebug() << __LINE__; QCoreApplication::processEvents(); // this triggers the SIGSEGV //qDebug() << __LINE__; // If we get here, OpenGL won't crash the application. ui->tabWidget->removeTab(2); delete gl; // 3. Remove OpenGLCompatibilityCheck from the registry upon success. settings.remove("OpenGLCompatibilityCheck"); #endif } #endif void MainWindow::Startup() { #ifdef Q_OS_WIN TestWindowsOpenGL(); #endif for (auto & loader : GetLoaders()) { loader->setParent(this); } QString lastProfile = AppSetting->profileName(); firstRunMessage(); if (Profiles::profiles.contains(lastProfile) && AppSetting->autoOpenLastUsed()) { if (OpenProfile(lastProfile)) { ui->tabWidget->setCurrentIndex(AppSetting->openTabAtStart()); if (AppSetting->autoLaunchImport()) { on_importButton_clicked(); } } } else { ui->tabWidget->setCurrentWidget(profileSelector); } } int MainWindow::importCPAP(ImportPath import, const QString &message) { if (!import.loader) { return 0; } ui->tabWidget->setCurrentWidget(welcome); QApplication::processEvents(); ProgressDialog * progdlg = new ProgressDialog(this); QPixmap image = import.loader->getPixmap(import.loader->PeekInfo(import.path).series); image = image.scaled(64,64); progdlg->setPixmap(image); progdlg->addAbortButton(); progdlg->setWindowModality(Qt::ApplicationModal); progdlg->open(); progdlg->setMessage(message); connect(import.loader, SIGNAL(updateMessage(QString)), progdlg, SLOT(setMessage(QString))); connect(import.loader, SIGNAL(setProgressMax(int)), progdlg, SLOT(setProgressMax(int))); connect(import.loader, SIGNAL(setProgressValue(int)), progdlg, SLOT(setProgressValue(int))); connect(progdlg, SIGNAL(abortClicked()), import.loader, SLOT(abortImport())); ImportUI importui(p_profile); ImportContext* ctx = new ProfileImportContext(p_profile); import.loader->SetContext(ctx); connect(ctx, &ImportContext::importEncounteredUnexpectedData, &importui, &ImportUI::onUnexpectedData); connect(import.loader, &MachineLoader::deviceReportsUsageOnly, &importui, &ImportUI::onDeviceReportsUsageOnly); connect(import.loader, &MachineLoader::deviceIsUntested, &importui, &ImportUI::onDeviceIsUntested); connect(import.loader, &MachineLoader::deviceIsUnsupported, &importui, &ImportUI::onDeviceIsUnsupported); int c = import.loader->Open(import.path); progdlg->setMessage(QObject::tr("Finishing up...")); QCoreApplication::processEvents(); ctx->Commit(); import.loader->SetContext(nullptr); delete ctx; if (c > 0) { Notify(tr("Imported %1 CPAP session(s) from\n\n%2").arg(c).arg(import.path), tr("Import Success")); } else if (c == 0) { Notify(tr("Already up to date with CPAP data at\n\n%1").arg(import.path), tr("Up to date")); } else { Notify(tr("Couldn't find any valid Device Data at\n\n%1").arg(import.path),tr("Import Problem")); } disconnect(progdlg, SIGNAL(abortClicked()), import.loader, SLOT(abortImport())); disconnect(import.loader, SIGNAL(setProgressMax(int)), progdlg, SLOT(setProgressMax(int))); disconnect(import.loader, SIGNAL(setProgressValue(int)), progdlg, SLOT(setProgressValue(int))); disconnect(import.loader, SIGNAL(updateMessage(QString)), progdlg, SLOT(setMessage(QString))); progdlg->close(); delete progdlg; if (AppSetting->openTabAfterImport()>0) { ui->tabWidget->setCurrentIndex(AppSetting->openTabAfterImport()); } return c; } void MainWindow::updateOverview() { if (overview) overview->ReloadGraphs(); } void MainWindow::finishCPAPImport() { if (daily) daily->Unload(daily->getDate()); p_profile->StoreMachines(); QList machines = p_profile->GetMachines(MT_CPAP); for (Machine * mach : machines) { mach->saveSessionInfo(); mach->SaveSummaryCache(); } GenerateStatistics(); profileSelector->updateProfileList(); if (welcome) welcome->refreshPage(); if (overview) { overview->ReloadGraphs(); } if (daily) { // daily->populateSessionWidget(); daily->ReloadGraphs(); } if (AppSetting->openTabAfterImport()>0) { ui->tabWidget->setCurrentIndex(AppSetting->openTabAfterImport()); } } void MainWindow::importCPAPBackups() { // Get BackupPaths for all CPAP devices QList machlist = p_profile->GetMachines(MT_CPAP); QList paths; Q_FOREACH(Machine *m, machlist) { paths.append(ImportPath(m->getBackupPath(), lookupLoader(m))); } if (paths.size() > 0) { int c=0; Q_FOREACH(ImportPath path, paths) { c+=importCPAP(path, tr("Please wait, importing from backup folder(s)...")); } if (c>0) { finishCPAPImport(); } } } #ifdef Q_OS_UNIX # include # include # if defined(Q_OS_MAC) || defined(Q_OS_BSD4) # include # elif defined(Q_OS_HAIKU) // nothing needed # else # include # include # endif // Q_OS_MAC/BSD #endif // Q_OS_UNIX //! \brief Returns a list of drive mountpoints QStringList getDriveList() { QStringList drivelist; bool crostini_detected = false; #if QT_VERSION >= QT_VERSION_CHECK(5,4,0) #if defined(Q_OS_LINUX) #define VFAT "vfat" #elif defined(Q_OS_WIN) #define VFAT "FAT32" Q_UNUSED(crostini_detected) #elif defined(Q_OS_MAC) #define VFAT "msdos" Q_UNUSED(crostini_detected) #endif foreach (const QStorageInfo &storage, QStorageInfo::mountedVolumes()) { if (storage.isValid() && storage.isReady()) { #ifdef DEBUG_SDCARD if (storage.fileSystemType() != "tmpfs") { // Don't show all the Linux tmpfs mount points! qDebug() << "Device:" << storage.device(); qDebug() << " Path:" << storage.rootPath(); qDebug() << " Name:" << storage.name(); // ... qDebug() << " FS Type:" << storage.fileSystemType(); } #endif if (storage.fileSystemType() == VFAT) { qDebug() << "Adding" << storage.name() << "on" << storage.rootPath() << "to drivelist"; drivelist.append(storage.rootPath()); } else if (storage.fileSystemType() == "9p") { qDebug() << "Crostini filesystem 9p found"; crostini_detected = true; } } } #endif #if defined(Q_OS_LINUX) if (crostini_detected) { QString mntName("/mnt/chromeos/removable"); QDir mnt(mntName); qDebug() << "Checking for" << mntName; if ( mnt.exists() ) { qDebug() << "Checking Crostini removable folders"; QFileInfoList mntPts = mnt.entryInfoList(); foreach ( const auto dir, mntPts ) { qDebug() << "Adding" << dir.filePath() << "to drivelist"; drivelist.append(dir.filePath() ); } } else { drivelist.clear(); drivelist.append("CROSTINI"); } } #endif return drivelist; } extern MainWindow * mainwin; void ImportDialogScan::cancelbutton() { mainwin->importScanCancelled = true; hide(); } QList MainWindow::detectCPAPCards() { const int timeout = 20000; // twenty seconds QList detectedCards; importScanCancelled = false; QString lastpath = (*p_profile)[STR_PREF_LastCPAPPath].toString(); QListloaders = GetLoaders(MT_CPAP); QElapsedTimer time; time.start(); // Create dialog ImportDialogScan popup(this) ;//, Qt::SplashScreen); QLabel waitmsg(tr("Please insert your CPAP data card...")); QProgressBar progress; QVBoxLayout waitlayout; popup.setLayout(&waitlayout); QHBoxLayout layout2; QIcon icon("://icons/sdcard.png"); QPushButton skipbtn(icon, tr("Choose a folder")); QPushButton cancelbtn(STR_MessageBox_Cancel); skipbtn.setMinimumHeight(40); cancelbtn.setMinimumHeight(40); waitlayout.addWidget(&waitmsg,1,Qt::AlignCenter); waitlayout.addWidget(&progress,1); waitlayout.addLayout(&layout2,1); layout2.addWidget(&skipbtn); layout2.addWidget(&cancelbtn); popup.connect(&skipbtn, SIGNAL(clicked()), &popup, SLOT(hide())); popup.connect(&cancelbtn, SIGNAL(clicked()), &popup, SLOT(cancelbutton())); progress.setValue(0); progress.setMaximum(timeout); progress.setVisible(true); // importScanCancelled = false; popup.show(); QApplication::processEvents(); // QString lastpath = (*p_profile)[STR_PREF_LastCPAPPath].toString(); do { // Rescan in case card inserted QStringList AutoScannerPaths = getDriveList(); if (AutoScannerPaths.contains("CROSTINI")) { // no Crostini removable drives found! if (( lastpath.size() > 0) && ( ! AutoScannerPaths.contains(lastpath))) { if (QFile(lastpath).exists() ) { AutoScannerPaths.insert(0, lastpath); } } else { QMessageBox::warning(nullptr, STR_MessageBox_Warning, QObject::tr("Chromebook file system detected, but no removable device found\n") + QObject::tr("You must share your SD card with Linux using the ChromeOS Files program")); break; // break out of the 20 second wait loop } } // AutoScannerPaths.push_back(lastpath); qDebug() << "Drive list size:" << AutoScannerPaths.size(); if ( (lastpath.size()>0) && ( ! AutoScannerPaths.contains(lastpath))) { if (QFile(lastpath).exists()) AutoScannerPaths.insert(0, lastpath); } Q_FOREACH(const QString &path, AutoScannerPaths) { // Scan through available device loaders and test if this folder contains valid folder structure Q_FOREACH(MachineLoader * loader, loaders) { if (loader->Detect(path)) { detectedCards.append(ImportPath(path, loader)); qDebug() << "Found" << loader->loaderName() << "datacard at" << path; } QApplication::processEvents(); } } int el=time.elapsed(); progress.setValue(el); if (el > timeout) break; if ( ! popup.isVisible()) break; // needs a slight delay here for (int i=0; i<20; ++i) { QThread::msleep(50); QApplication::processEvents(); } } while (detectedCards.size() == 0); popup.hide(); popup.disconnect(&skipbtn, SIGNAL(clicked()), &popup, SLOT(hide())); popup.disconnect(&cancelbtn, SIGNAL(clicked()), &popup, SLOT(hide())); return detectedCards; } void MainWindow::on_action_Import_Data_triggered() { static bool in_import = false; if ( p_profile == nullptr ) { Notify(tr("No profile has been selected for Import."), STR_MessageBox_Busy); return; } if (m_inRecalculation) { Notify(tr("Access to Import has been blocked while recalculations are in progress."),STR_MessageBox_Busy); return; } if (in_import) { Notify(tr("Import is already running in the background."),STR_MessageBox_Busy); return; } in_import=true; ui->tabWidget->setCurrentWidget(welcome); QApplication::processEvents(); QList datacards = selectCPAPDataCards(tr("Would you like to import from this location?")); if (datacards.size() > 0) { importCPAPDataCards(datacards); } in_import=false; } QList MainWindow::selectCPAPDataCards(const QString & prompt, bool alwaysPrompt) { QList datacards = detectCPAPCards(); if (importScanCancelled) { datacards.clear(); return datacards; } QListloaders = GetLoaders(MT_CPAP); QElapsedTimer time; time.start(); bool asknew = false; // TODO: This should either iterate over all detected cards and prompt for each, or it should only // provide the one confirmed card in the list. if (datacards.size() > 0) { MachineInfo info = datacards[0].loader->PeekInfo(datacards[0].path); QString infostr; if (!info.model.isEmpty()) { QString infostr2 = info.model+" ("+info.serial+")"; infostr = tr("A %1 file structure for a %2 was located at:").arg(info.brand).arg(infostr2); } else { infostr = tr("A %1 file structure was located at:").arg(datacards[0].loader->loaderName()); } if (alwaysPrompt || !p_profile->cpap->autoImport()) { QMessageBox mbox(QMessageBox::NoIcon, tr("CPAP Data Located"), infostr+"\n\n"+QDir::toNativeSeparators(datacards[0].path)+"\n\n"+ prompt, QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, this); mbox.setDefaultButton(QMessageBox::Yes); mbox.setButtonText(QMessageBox::No, tr("Specify")); QPixmap pixmap = datacards[0].loader->getPixmap(datacards[0].loader->PeekInfo(datacards[0].path).series).scaled(64,64); //QPixmap pixmap = QPixmap(getCPAPPixmap(datacards[0].loader->loaderName())).scaled(64,64); mbox.setIconPixmap(pixmap); int res = mbox.exec(); if (res == QMessageBox::Cancel) { // Give the communal progress bar back datacards.clear(); return datacards; } else if (res == QMessageBox::No) { //waitmsg->setText(tr("Please wait, launching file dialog...")); datacards.clear(); asknew = true; } } } else { //waitmsg->setText(tr("No CPAP data card detected, launching file dialog...")); asknew = true; } // TODO: Get rid of the reminder and instead validate the user's selection (using the loader detection // below) and loop until the user either cancels or selects a valid folder. // // It doesn't look like there's any way to implement such a programmatic filter within the file // selection dialog without resorting to a non-native dialog. if (asknew) { // popup.show(); mainwin->Notify(tr("Please remember to select the root folder or drive letter of your data card, and not a folder inside it."), tr("Import Reminder"),8000); QFileDialog w(this); QString folder; if (p_profile->contains(STR_PREF_LastCPAPPath)) { folder = (*p_profile)[STR_PREF_LastCPAPPath].toString(); } else { // TODO: Is a writable path really the best place to direct the user to find their SD card data? folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); } w.setDirectory(folder); w.setFileMode(QFileDialog::Directory); w.setOption(QFileDialog::ShowDirsOnly, true); w.setWindowTitle(tr("Find your CPAP data card")); // This doesn't work on WinXP #if defined(Q_OS_MAC) w.setOption(QFileDialog::DontUseNativeDialog,false); #elif defined(Q_OS_UNIX) w.setOption(QFileDialog::DontUseNativeDialog,false); #elif defined(Q_OS_WIN) // check the Os version.. winxp chokes w.setOption(QFileDialog::DontUseNativeDialog, true); #endif //#else // w.setOption(QFileDialog::DontUseNativeDialog, false); // QListView *l = w.findChild("listView"); // if (l) { // l->setSelectionMode(QAbstractItemView::MultiSelection); // } // QTreeView *t = w.findChild(); // if (t) { // t->setSelectionMode(QAbstractItemView::MultiSelection); // } //#endif if (w.exec() != QDialog::Accepted) { datacards.clear(); return datacards; } for (int i = 0; i < w.selectedFiles().size(); i++) { Q_FOREACH(MachineLoader * loader, loaders) { if (loader->Detect(w.selectedFiles().at(i))) { datacards.append(ImportPath(w.selectedFiles().at(i), loader)); break; } } } } return datacards; } void MainWindow::importCPAPDataCards(const QList & datacards) { bool newdata = false; int c = -1; for (int i = 0; i < datacards.size(); i++) { QString dir = datacards[i].path; MachineLoader * loader = datacards[i].loader; if (!loader) continue; if (!dir.isEmpty()) { c = importCPAP(datacards[i], tr("Importing Data")); qDebug() << "Finished Importing data" << c; if (c >= 0) { QDir d(dir.section("/",0,-1)); (*p_profile)[STR_PREF_LastCPAPPath] = d.absolutePath(); } if (c > 0) { newdata = true; } } } if (newdata) { finishCPAPImport(); PopulatePurgeMenu(); } } QMenu *MainWindow::CreateMenu(QString title) { QMenu *menu = new QMenu(title, ui->menubar); ui->menubar->insertMenu(ui->menu_Help->menuAction(), menu); return menu; } void MainWindow::on_action_Fullscreen_triggered() { if (ui->action_Fullscreen->isChecked()) { this->showMaximized(); } else { this->showNormal(); } } void MainWindow::setRecBoxHTML(QString html) { ui->recordsBox->setHtml(html); } void MainWindow::setStatsHTML(QString html) { ui->statisticsView->setHtml(html); } void MainWindow::updateFavourites() { QDate date = p_profile->LastDay(MT_JOURNAL); if (!date.isValid()) { return; } QString html = "" ""; do { Day *journal = p_profile->GetDay(date, MT_JOURNAL); if (journal) { if (journal->size() > 0) { Session *sess = journal->firstSession(MT_JOURNAL); if (!sess) { qWarning() << "null session for MT_JOURNAL first session"; } else { QString tmp; bool filtered = !bookmarkFilter.isEmpty(); bool found = !filtered; if (sess->settings.contains(Bookmark_Start)) { //QVariantList start=sess->settings[Bookmark_Start].toList(); //QVariantList end=sess->settings[Bookmark_End].toList(); QStringList notes = sess->settings[Bookmark_Notes].toStringList(); if (notes.size() > 0) { tmp += QString(""; } } if (found) { html += tmp; } } } } date = date.addDays(-1); } while (date >= p_profile->FirstDay(MT_JOURNAL)); html += "
%2
") .arg(date.toString(Qt::ISODate)) .arg(date.toString(MedDateFormat)); tmp += ""; for (int i = 0; i < notes.size(); i++) { //QDate d=start[i].toDate(); QString note = notes[i]; if (filtered && note.contains(bookmarkFilter, Qt::CaseInsensitive)) { found = true; } tmp += "
  • " + note + "
  • "; } tmp += "
    "; ui->bookmarkView->setHtml(html); } void MainWindow::on_dailyButton_clicked() { if (daily) { ui->tabWidget->setCurrentWidget(daily); daily->RedrawGraphs(); } } void MainWindow::on_overviewButton_clicked() { if (overview) { ui->tabWidget->setCurrentWidget(overview); } } void MainWindow::aboutBoxLinkClicked(const QUrl &url) { QDesktopServices::openUrl(url); } void MainWindow::on_action_About_triggered() { AboutDialog * about = new AboutDialog(this); about->exec(); about->deleteLater(); } void MainWindow::on_actionDebug_toggled(bool checked) { AppSetting->setShowDebug(checked); logger->strlock.lock(); if (checked) { ui->logText->show(); } else { ui->logText->hide(); } // QApplication::processEvents(); logger->strlock.unlock(); } void MainWindow::on_action_Reset_Graph_Layout_triggered() { if (daily && (ui->tabWidget->currentWidget() == daily)) { daily->ResetGraphLayout(); } if (overview && (ui->tabWidget->currentWidget() == overview)) { overview->ResetGraphLayout(); } } /* void MainWindow::on_action_Reset_Graph_Order_triggered() { if (daily && (ui->tabWidget->currentWidget() == daily)) { daily->ResetGraphOrder(0); } if (overview && (ui->tabWidget->currentWidget() == overview)) { overview->ResetGraphOrder(0); } } */ void MainWindow::on_action_Standard_Graph_Order_triggered() { if (daily && (ui->tabWidget->currentWidget() == daily)) { daily->ResetGraphOrder(1); } if (overview && (ui->tabWidget->currentWidget() == overview)) { overview->ResetGraphOrder(1); } } void MainWindow::on_action_Advanced_Graph_Order_triggered() { if (daily && (ui->tabWidget->currentWidget() == daily)) { daily->ResetGraphOrder(2); } if (overview && (ui->tabWidget->currentWidget() == overview)) { overview->ResetGraphOrder(2); } } void MainWindow::on_action_Preferences_triggered() { if (!p_profile) { mainwin->Notify(tr("Please open a profile first.")); return; } if (m_inRecalculation) { mainwin->Notify(tr("Access to Preferences has been blocked until recalculation completes.")); return; } PreferencesDialog pd(this, p_profile); prefdialog = &pd; if (pd.exec() == PreferencesDialog::Accepted) { // Apply any updates from preference changes (notably fonts) setApplicationFont(); if (daily) { daily->RedrawGraphs(); } if (overview) { overview->ResetFont(); overview->RebuildGraphs(true); } if (welcome) welcome->refreshPage(); if (profileSelector) profileSelector->updateProfileList(); // These attempts to update calendar after a change to application font do NOT work, and I can find no QT documentation suggesting // that changing the font after Calendar is created is even possible. // qDebug() << "application font family set to" << QApplication::font().family() << "and font" << QApplication::font(); // ui->statStartDate->calendarWidget()->setFont(QApplication::font()); // ui->statStartDate->calendarWidget()->repaint(); } prefdialog = nullptr; } #include "oximeterimport.h" QDateTime datetimeDialog(QDateTime datetime, QString message); void MainWindow::on_oximetryButton_clicked() { if (p_profile) { OximeterImport oxiimp(this); oxiimp.exec(); PopulatePurgeMenu(); if (overview) overview->ReloadGraphs(); if (welcome) welcome->refreshPage(); } } // Called for automatic check for updates void MainWindow::CheckForUpdates(bool showWhenCurrent) { updateChecker = new CheckUpdates(this); #ifdef NO_CHECKUPDATES if (showWhenCurrent) QMessageBox::information(nullptr, STR_MessageBox_Information, tr("Check for updates not implemented")); #else updateChecker->checkForUpdates(showWhenCurrent); #endif } // Called for manual check for updates void MainWindow::on_action_Check_for_Updates_triggered() { CheckForUpdates(true); } bool toolbox_visible = false; void MainWindow::on_action_Screenshot_triggered() { setUpdatesEnabled(false); if (daily) daily->hideSpaceHogs(); toolbox_visible = ui->toolBox->isVisible(); ui->toolBox->hide(); setUpdatesEnabled(true); QTimer::singleShot(250, this, SLOT(DelayedScreenshot())); } void MainWindow::DelayedScreenshot() { // Make sure to scale for high resolution displays (like Retina) // qreal pr = devicePixelRatio(); auto screenshotRect = geometry(); auto titleBarHeight = QApplication::style()->pixelMetric(QStyle::PM_TitleBarHeight); auto pixmap = QApplication::primaryScreen()->grabWindow(QDesktopWidget().winId(), screenshotRect.left(), screenshotRect.top() - titleBarHeight, screenshotRect.width(), screenshotRect.height() + titleBarHeight); QString default_filename = "/screenshot-" + QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss") + ".png"; QString png_filepath; if (AppSetting->dontAskWhenSavingScreenshots()) { png_filepath = p_pref->Get("{home}/Screenshots"); QDir dir(png_filepath); if (!dir.exists()) { dir.mkdir(png_filepath); } png_filepath += default_filename; } else { QString folder = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + default_filename; png_filepath = QFileDialog::getSaveFileName(this, tr("Choose where to save screenshot"), folder, tr("Image files (*.png)")); if (png_filepath.isEmpty() == false && png_filepath.toLower().endsWith(".png") == false) { png_filepath += ".png"; } } // png_filepath will be empty if the user canceled the file selection above. if (png_filepath.isEmpty() == false) { qDebug() << "Saving screenshot to" << png_filepath; if (!pixmap.save(png_filepath)) { Notify(tr("There was an error saving screenshot to file \"%1\"").arg(QDir::toNativeSeparators(png_filepath))); } else { Notify(tr("Screenshot saved to file \"%1\"").arg(QDir::toNativeSeparators(png_filepath))); } } setUpdatesEnabled(false); if (daily) daily->showSpaceHogs(); ui->toolBox->setVisible(toolbox_visible); setUpdatesEnabled(true); } void MainWindow::on_actionView_Oximetry_triggered() { on_oximetryButton_clicked(); } void MainWindow::on_actionPrint_Report_triggered() { Report report; if (ui->tabWidget->currentWidget() == overview) { Report::PrintReport(overview->graphView(), STR_TR_Overview); } else if (ui->tabWidget->currentWidget() == daily) { Report::PrintReport(daily->graphView(), STR_TR_Daily, daily->getDate()); } else if (ui->tabWidget->currentWidget() == ui->statisticsTab) { Statistics::printReport(this); #ifndef helpless } else if (ui->tabWidget->currentWidget() == help) { help->print(&printer); // **** THIS DID NOT SURVIVE REFACTORING STATISTICS PRINT #endif } } void MainWindow::on_action_Edit_Profile_triggered() { NewProfile *newprof = new NewProfile(this); QString name = AppSetting->profileName(); newprof->edit(name); newprof->setWindowModality(Qt::ApplicationModal); newprof->setModal(true); newprof->exec(); qDebug() << newprof; delete newprof; } void MainWindow::on_action_CycleTabs_triggered() { int i; qDebug() << "Switching Tabs"; i = ui->tabWidget->currentIndex() + 1; if (i >= ui->tabWidget->count()) { i = 0; } ui->tabWidget->setCurrentIndex(i); } void MainWindow::on_actionOnline_Users_Guide_triggered() { // QDesktopServices::openUrl(QUrl("http://sleepyhead.sourceforge.net/wiki/index.php?title=OSCAR_Users_Guide")); // QMessageBox::information(nullptr, STR_MessageBox_Information, tr("The User's Guide is not yet available")); if (QMessageBox::question(nullptr, STR_MessageBox_Question, tr("The User's Guide will open in your default browser"), QMessageBox::Ok|QMessageBox::Cancel, QMessageBox::Ok) == QMessageBox::Ok ) QDesktopServices::openUrl(QUrl("https://www.apneaboard.com/wiki/index.php?title=OSCAR_Help")); } void MainWindow::on_action_Frequently_Asked_Questions_triggered() { // QDesktopServices::openUrl(QUrl("http://sleepyhead.sourceforge.net/wiki/index.php?title=Frequently_Asked_Questions")); QMessageBox::information(nullptr, STR_MessageBox_Information, tr("The FAQ is not yet implemented")); } void MainWindow::on_action_Rebuild_Oximetry_Index_triggered() { QVector invalid; QList machines = p_profile->GetMachines(MT_OXIMETER); qint64 f = 0, l = 0; int discard_threshold = p_profile->oxi->oxiDiscardThreshold(); Machine *m; for (int z = 0; z < machines.size(); z++) { m = machines.at(z); //m->sessionlist.erase(m->sessionlist.find(0)); QList valid = m->availableChannels(schema::ChanType::ALL); valid.removeAll(OXI_PulseChange); // Delete only these and recalculate. valid.removeAll(OXI_SPO2Drop); // For each Session for (QHash::iterator s = m->sessionlist.begin(); s != m->sessionlist.end(); s++) { Session *sess = s.value(); if (!sess) { continue; } sess->OpenEvents(); // For each EventList contained in session invalid.clear(); f = 0; l = 0; for (QHash >::iterator e = sess->eventlist.begin(); e != sess->eventlist.end(); e++) { // Discard any non data events. if (!valid.contains(e.key())) { // delete and push aside for later to clean up for (int i = 0; i < e.value().size(); i++) { delete e.value()[i]; } e.value().clear(); invalid.push_back(e.key()); } else { QVector newlist; for (int i = 0; i < e.value().size(); i++) { if (e.value()[i]->count() > (unsigned)discard_threshold) { newlist.push_back(e.value()[i]); } else { delete e.value()[i]; } } for (int i = 0; i < newlist.size(); i++) { EventList *el = newlist[i]; if (!f || f > el->first()) { f = el->first(); } if (!l || l < el->last()) { l = el->last(); } } e.value() = newlist; } } for (int i = 0; i < invalid.size(); i++) { sess->eventlist.erase(sess->eventlist.find(invalid[i])); } if (f) { sess->really_set_first(f); } if (l) { sess->really_set_last(l); } sess->m_cnt.clear(); sess->m_sum.clear(); sess->m_min.clear(); sess->m_max.clear(); sess->m_cph.clear(); sess->m_sph.clear(); sess->m_avg.clear(); sess->m_wavg.clear(); sess->m_valuesummary.clear(); sess->m_timesummary.clear(); sess->m_firstchan.clear(); sess->m_lastchan.clear(); sess->SetChanged(true); } } for (int i = 0; i < machines.size(); i++) { Machine *m = machines[i]; m->Save(); m->SaveSummaryCache(); } daily->LoadDate(daily->getDate()); overview->ReloadGraphs(); } void MainWindow::reloadProfile() { QString username = p_profile->user->userName(); int tabidx = ui->tabWidget->currentIndex(); CloseProfile(); OpenProfile(username); ui->tabWidget->setCurrentIndex(tabidx); } void MainWindow::RestartApplication(bool force_login, QString cmdline) { CloseProfile(); p_pref->Save(); QString apppath; #ifdef Q_OS_MAC // In Mac OS the full path of aplication binary is: // /myApp.app/Contents/MacOS/myApp // prune the extra bits to just get the app bundle path apppath = QApplication::instance()->applicationDirPath().section("/", 0, -3); QStringList args; args << "-n"; // -n option is important, as it opens a new process args << apppath; args << "--args"; // OSCAR binary options after this args << "-p"; // -p starts with 1 second delay, to give this process time to save.. if (force_login) { args << "-l"; } args << cmdline; if (QProcess::startDetached("/usr/bin/open", args)) { QApplication::instance()->exit(); } else { QMessageBox::warning(nullptr, STR_MessageBox_Error, tr("If you can read this, the restart command didn't work. You will have to do it yourself manually."), QMessageBox::Ok); } #else apppath = QApplication::instance()->applicationFilePath(); // If this doesn't work on windoze, try uncommenting this method // Technically should be the same thing.. //if (QDesktopServices::openUrl(apppath)) { // QApplication::instance()->exit(); //} else QStringList args; args << "-p"; if (force_login) { args << "-l"; } args << cmdline; //if (change_datafolder) { args << "-d"; } if (QProcess::startDetached(apppath, args)) { QApplication::instance()->exit(); // ::exit(0); } else { QMessageBox::warning(nullptr, STR_MessageBox_Error, tr("If you can read this, the restart command didn't work. You will have to do it yourself manually."), QMessageBox::Ok); } #endif } void MainWindow::on_actionPurge_Current_Day_triggered() { this->purgeDay(MT_CPAP); } void MainWindow::on_actionPurgeCurrentDayOximetry_triggered() { this->purgeDay(MT_OXIMETER); } void MainWindow::on_actionPurgeCurrentDaySleepStage_triggered() { this->purgeDay(MT_SLEEPSTAGE); } void MainWindow::on_actionPurgeCurrentDayPosition_triggered() { this->purgeDay(MT_POSITION); } void MainWindow::on_actionPurgeCurrentDayAllExceptNotes_triggered() { this->purgeDay(MT_UNKNOWN); } void MainWindow::on_actionPurgeCurrentDayAll_triggered() { this->purgeDay(MT_JOURNAL); } // Purge data for a given device type. // Special handling: MT_JOURNAL == All data. MT_UNKNOWN == All except journal void MainWindow::purgeDay(MachineType type) { if (!daily) return; QDate date = daily->getDate(); qDebug() << "Purging data from" << date; daily->Unload(date); Day *day = p_profile->GetDay(date, MT_UNKNOWN); Machine *cpap = nullptr; if (!day) return; QList::iterator s; QList list; for (s = day->begin(); s != day->end(); ++s) { Session *sess = *s; if (type == MT_JOURNAL || (type == MT_UNKNOWN && sess->type() != MT_JOURNAL) || sess->type() == type) { list.append(*s); qDebug() << "Purging session from " << (*s)->machine()->loaderName() << " ID:" << (*s)->session() << "["+QDateTime::fromTime_t((*s)->session()).toString()+"]"; qDebug() << "First Time:" << QDateTime::fromMSecsSinceEpoch((*s)->realFirst()).toString(); qDebug() << "Last Time:" << QDateTime::fromMSecsSinceEpoch((*s)->realLast()).toString(); if (sess->type() == MT_CPAP) { cpap = day->machine(MT_CPAP); } } else { qDebug() << "Skipping session from " << (*s)->machine()->loaderName() << " ID:" << (*s)->session() << "["+QDateTime::fromTime_t((*s)->session()).toString()+"]"; } } if (list.size() > 0) { if (cpap) { QFile rxcache(p_profile->Get("{" + STR_GEN_DataFolder + "}/RXChanges.cache" )); rxcache.remove(); QFile sumfile(cpap->getDataPath()+"Summaries.xml.gz"); sumfile.remove(); } // m->day.erase(m->day.find(date)); QSet machines; for (int i = 0; i < list.size(); i++) { Session *sess = list.at(i); machines += sess->machine(); sess->Destroy(); // remove the summary and event files delete sess; } for (auto & mach : machines) { mach->SaveSummaryCache(); } if (cpap) { // save purge date where later import should start QDate pd = cpap->purgeDate(); if (pd.isNull() || day->date() < pd) cpap->setPurgeDate(day->date()); } } else { // No data purged... could notify user? return; } day = p_profile->GetDay(date, MT_UNKNOWN); Q_UNUSED(day); daily->clearLastDay(); daily->LoadDate(date); if (overview) overview->ReloadGraphs(); if (welcome) welcome->refreshPage(); GenerateStatistics(); } void MainWindow::on_actionRebuildCPAP(QAction *action) { ui->tabWidget->setCurrentWidget(welcome); // Daily view can't run during rebuild QApplication::processEvents(); QString data = action->data().toString(); QString cls = data.section(":",0,0); QString serial = data.section(":", 1); QList machines = p_profile->GetMachines(MT_CPAP); Machine * mach = nullptr; for (int i=0; i < machines.size(); ++i) { Machine * m = machines.at(i); if ((m->loaderName() == cls) && (m->serial() == serial)) { mach = m; break; } } if (!mach) return; QString bpath = mach->getBackupPath(); bool backups = (dirCount(bpath) > 0) ? true : false; if (backups) { if (QMessageBox::question(this, STR_MessageBox_Question, tr("Are you sure you want to rebuild all CPAP data for the following device:\n\n") + mach->brand() + " " + mach->model() + " " + mach->modelnumber() + " (" + mach->serial() + ")\n\n" + tr("Please note, that this could result in loss of data if OSCAR's backups have been disabled."), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { return; } } else { if (QMessageBox::question(this, STR_MessageBox_Warning, "

    "+STR_MessageBox_Warning+": "+tr("For some reason, OSCAR does not have any backups for the following device:")+ "

    " + "

    "+mach->brand() + " " + mach->model() + " " + mach->modelnumber() + " (" + mach->serial() + ")

    "+ "

    "+tr("Provided you have made your own backups for ALL of your CPAP data, you can still complete this operation, but you will have to restore from your backups manually.")+"

    " + "

    "+tr("Are you really sure you want to do this?")+"

    ", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { return; } } QString path = mach->getBackupPath(); MachineLoader *loader = lookupLoader(mach); purgeMachine(mach); // purge destroys device record if (backups) { importCPAP(ImportPath(path, loader), tr("Please wait, importing from backup folder(s)...")); } else { if (QMessageBox::information(this, STR_MessageBox_Warning, tr("Because there are no internal backups to rebuild from, you will have to restore from your own.")+"\n\n"+ tr("Would you like to import from your own backups now? (you will have no data visible for this device until you do)"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { on_action_Import_Data_triggered(); } else { } } if (overview) overview->ReloadGraphs(); if (daily) { daily->Unload(); daily->clearLastDay(); // otherwise Daily will crash daily->ReloadGraphs(); } if (welcome) welcome->refreshPage(); PopulatePurgeMenu(); GenerateStatistics(); p_profile->StoreMachines(); } void MainWindow::on_actionPurgeMachine(QAction *action) { QString data = action->data().toString(); QString cls = data.section(":",0,0); QString serial = data.section(":", 1); QList machines = p_profile->GetMachines(); Machine * mach = nullptr; for (int i=0; i < machines.size(); ++i) { Machine * m = machines.at(i); if ((m->loaderName() == cls) && (m->serial() == serial)) { mach = m; break; } } if (!mach) return; QString machname = mach->brand(); if (machname.isEmpty()) { machname = mach->loaderName(); } machname += " " + mach->model() + " " + mach->modelnumber(); if (!mach->serial().isEmpty()) { machname += QString(" (%1)").arg(mach->serial()); } QString backupnotice; QString bpath = mach->getBackupPath(); bool backups = (dirCount(bpath) > 0) ? true : false; if (backups) { backupnotice = "

    " + tr("Note as a precaution, the backup folder will be left in place.") + "

    "; } else { backupnotice = "

    " + tr("OSCAR does not have any backups for this device!") + "

    " + "

    " + tr("Unless you have made your own backups for ALL of your data for this device, " "you will lose this device's data permanently!") + "

    "; } if (QMessageBox::question(this, STR_MessageBox_Warning, "

    "+STR_MessageBox_Warning+": " + tr("You are about to obliterate OSCAR's device database for the following device:

    ") + "

    " + machname + "

    " + backupnotice+ "

    "+tr("Are you absolutely sure you want to proceed?")+"

    ", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { qDebug() << "Purging" << machname; purgeMachine(mach); } } void MainWindow::purgeMachine(Machine * mach) { // detect backups daily->Unload(daily->getDate()); // Technicially the above won't sessions under short session limit.. Using Purge to clean up the rest. if (mach->Purge(3478216)) { mach->sessionlist.clear(); mach->day.clear(); QDir dir; QString path = mach->getDataPath(); path.chop(1); qDebug() << "path to device" << path; p_profile->DelMachine(mach); delete mach; // remove the directory unless it's got unexpected crap in it.. bool deleted = false; if ( ! dir.rmdir(path)) { #ifdef Q_OS_WIN wchar_t* directoryPtr = (wchar_t*)path.utf16(); SetFileAttributes(directoryPtr, GetFileAttributes(directoryPtr) & ~FILE_ATTRIBUTE_READONLY); if (!::RemoveDirectory(directoryPtr)) { DWORD lastError = ::GetLastError(); qDebug() << "RemoveDirectory" << path << "GetLastError: " << lastError << "(Error 145 is expected)"; if (lastError == 145) { qDebug() << path << "remaining directory contents are" << QDir(path).entryList(); } } else { qDebug() << "Success on second attempt deleting folder with windows API " << path; deleted = true; } #else qWarning() << "Couldn't remove directory" << path; #endif } else { deleted = true; } if ( ! deleted) { qWarning() << "Leaving backup folder intact"; } PopulatePurgeMenu(); p_profile->StoreMachines(); } else { QMessageBox::warning(this, STR_MessageBox_Error, tr("A file permission error caused the purge process to fail; you will have to delete the following folder manually:") + "\n\n" + QDir::toNativeSeparators(mach->getDataPath()), QMessageBox::Ok, QMessageBox::Ok); if (overview) overview->ReloadGraphs(); if (daily) { daily->clearLastDay(); // otherwise Daily will crash daily->ReloadGraphs(); } if (welcome) welcome->refreshPage(); //GenerateStatistics(); return; } if (overview) overview->ReloadGraphs(); QFile rxcache(p_profile->Get("{" + STR_GEN_DataFolder + "}/RXChanges.cache" )); rxcache.remove(); if (daily) { daily->clearLastDay(); // otherwise Daily will crash daily->ReloadGraphs(); } if (welcome) welcome->refreshPage(); QApplication::processEvents(); } void MainWindow::keyPressEvent(QKeyEvent *event) { Q_UNUSED(event) //qDebug() << "Keypress:" << event->key(); } void MainWindow::on_action_Sidebar_Toggle_toggled(bool visible) { ui->toolBox->setVisible(visible); AppSetting->setRightSidebarVisible(visible); } void MainWindow::on_helpButton_clicked() { #ifndef helpless ui->tabWidget->setCurrentWidget(help); #else QMessageBox::information(nullptr, STR_MessageBox_Error, tr("No help is available.")); #endif } void MainWindow::on_actionView_Statistics_triggered() { ui->tabWidget->setCurrentWidget(ui->statisticsTab); } void MainWindow::on_tabWidget_currentChanged(int index) { Q_UNUSED(index); // QWidget *widget = ui->tabWidget->currentWidget(); } void MainWindow::on_filterBookmarks_editingFinished() { bookmarkFilter = ui->filterBookmarks->text(); updateFavourites(); } void MainWindow::on_filterBookmarksButton_clicked() { if (!bookmarkFilter.isEmpty()) { ui->filterBookmarks->setText(""); bookmarkFilter = ""; updateFavourites(); } } void MainWindow::recompressEvents() { QTimer::singleShot(0, this, SLOT(doRecompressEvents())); } void MainWindow::reprocessEvents(bool restart) { m_restartRequired = restart; QTimer::singleShot(0, this, SLOT(doReprocessEvents())); } void MainWindow::FreeSessions() { QDate first = p_profile->FirstDay(); QDate date = p_profile->LastDay(); Day *day; QDate current = daily->getDate(); do { day = p_profile->GetDay(date, MT_CPAP); if (day) { if (date != current) { day->CloseEvents(); } } date = date.addDays(-1); } while (date >= first); } void MainWindow::doRecompressEvents() { if (!p_profile) return; ProgressDialog progress(this); progress.setMessage(QObject::tr("Recompressing Session Files")); progress.setProgressMax(p_profile->daylist.size()); QPixmap icon = QPixmap(":/icons/logo-md.png").scaled(64,64); progress.setPixmap(icon); progress.open(); bool isopen; int idx = 0; for (Day * day : p_profile->daylist) { for (Session * sess : day->sessions) { isopen = sess->eventsLoaded(); // Load the events and summary if they aren't loaded already sess->LoadSummary(); sess->OpenEvents(); sess->SetChanged(true); sess->machine()->SaveSession(sess); if (!isopen) { sess->TrashEvents(); } } progress.setProgressValue(++idx); QApplication::processEvents(); } progress.close(); } void MainWindow::doReprocessEvents() { if (!p_profile) return; ProgressDialog progress(this); progress.setMessage("Recalculating summaries"); progress.setProgressMax(p_profile->daylist.size()); QPixmap icon = QPixmap(":/icons/logo-md.png").scaled(64,64); progress.setPixmap(icon); progress.open(); if (daily) { daily->Unload(); daily->clearLastDay(); // otherwise Daily will crash delete daily; daily = nullptr; } if (welcome) { delete welcome; welcome = nullptr; } if (overview) { delete overview; overview = nullptr; } for (Day * day : p_profile->daylist) { for (Session * sess : day->sessions) { bool isopen = sess->eventsLoaded(); // Load the events if they aren't loaded already sess->LoadSummary(); sess->OpenEvents(); // Destroy any current user flags.. sess->destroyEvent(CPAP_UserFlag1); sess->destroyEvent(CPAP_UserFlag2); sess->destroyEvent(CPAP_UserFlag3); // AHI flags sess->destroyEvent(CPAP_AHI); sess->destroyEvent(CPAP_RDI); if (sess->machine()->loaderName() != STR_MACH_PRS1) { sess->destroyEvent(CPAP_LargeLeak); } else { sess->destroyEvent(CPAP_Leak); } sess->SetChanged(true); sess->UpdateSummaries(); sess->machine()->SaveSession(sess); if (!isopen) { sess->TrashEvents(); } } } progress.close(); welcome = new Welcome(ui->tabWidget); ui->tabWidget->insertTab(1, welcome, tr("Welcome")); daily = new Daily(ui->tabWidget, nullptr); ui->tabWidget->insertTab(2, daily, STR_TR_Daily); daily->ReloadGraphs(); overview = new Overview(ui->tabWidget, daily->graphView()); ui->tabWidget->insertTab(3, overview, STR_TR_Overview); overview->ReloadGraphs(); // Should really create welcome and statistics here, but they need redoing later anyway to kill off webkit ui->tabWidget->setCurrentIndex(AppSetting->openTabAtStart()); GenerateStatistics(); PopulatePurgeMenu(); } void MainWindow::on_actionImport_ZEO_Data_triggered() { ZEOLoader zeo; importNonCPAP(zeo); } void MainWindow::on_actionImport_Dreem_Data_triggered() { DreemLoader dreem; importNonCPAP(dreem); } void MainWindow::on_actionImport_RemStar_MSeries_Data_triggered() { #ifdef REMSTAR_M_SUPPORT QFileDialog w; w.setFileMode(QFileDialog::ExistingFiles); w.setOption(QFileDialog::ShowDirsOnly, false); w.setOption(QFileDialog::DontUseNativeDialog, true); w.setNameFilters(QStringList("M-Series data file (*.bin)")); MSeriesLoader mseries; if (w.exec() == QFileDialog::Accepted) { QString filename = w.selectedFiles()[0]; if (!mseries.Open(filename, p_profile)) { Notify(tr("There was a problem opening MSeries block File: ") + filename); return; } Notify(tr("MSeries Import complete")); daily->LoadDate(daily->getDate()); } #endif } void MainWindow::on_actionSleep_Disorder_Terms_Glossary_triggered() { // QDesktopServices::openUrl(QUrl("http://sleepyhead.sourceforge.net/wiki/index.php?title=Glossary")); // QMessageBox::information(nullptr, STR_MessageBox_Information, tr("The Glossary is not yet implemented")); if (QMessageBox::question(nullptr, STR_MessageBox_Question, tr("The Glossary will open in your default browser"), QMessageBox::Ok|QMessageBox::Cancel, QMessageBox::Ok) == QMessageBox::Ok ) QDesktopServices::openUrl(QUrl("https://www.apneaboard.com/wiki/index.php?title=Definitions")); } /* void MainWindow::on_actionHelp_Support_OSCAR_Development_triggered() { // QDesktopServices().openUrl(QUrl("https://sleepyhead.jedimark.net/donate.php")); QMessageBox::information(nullptr, STR_MessageBox_Information, tr("Donations are not implemented")); } */ void MainWindow::on_actionChange_Language_triggered() { // if (QMessageBox::question(this,STR_MessageBox_Warning,tr("Changing the language will reset custom Event and Waveform names/labels/descriptions.")+"\n\n"+tr("Are you sure you want to do this?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { // return; // } RestartApplication(true, "--language"); } void MainWindow::on_actionChange_Data_Folder_triggered() { if (p_profile) { p_profile->Save(); p_profile->removeLock(); } p_pref->Save(); RestartApplication(false, "-d"); } void MainWindow::importNonCPAP(MachineLoader &loader) { QFileDialog w; w.setFileMode(QFileDialog::ExistingFiles); w.setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); w.setOption(QFileDialog::ShowDirsOnly, false); #if defined(Q_OS_WIN) // Windows can't handle Viatom name filter - use non-native for all non-CPAP loaders. w.setOption(QFileDialog::DontUseNativeDialog, true); #endif w.setNameFilters(loader.getNameFilter()); // Display progress if we have more than 1 file to load... ProgressDialog progress(this); if (w.exec() == QFileDialog::Accepted) { QStringList files = w.selectedFiles(); int size = files.size(); if (size > 1) { progress.setMessage(QObject::tr("Importing Sessions...")); progress.setProgressMax(size); progress.setProgressValue(0); progress.addAbortButton(); progress.setWindowModality(Qt::ApplicationModal); connect(&loader, SIGNAL(setProgressValue(int)), &progress, SLOT(setProgressValue(int))); connect(&progress, SIGNAL(abortClicked()), &loader, SLOT(abortImport())); progress.open(); QCoreApplication::processEvents(); } QString name = loader.loaderName(); ImportUI importui(p_profile); ImportContext* ctx = new ProfileImportContext(p_profile); loader.SetContext(ctx); connect(ctx, &ImportContext::importEncounteredUnexpectedData, &importui, &ImportUI::onUnexpectedData); connect(&loader, &MachineLoader::deviceReportsUsageOnly, &importui, &ImportUI::onDeviceReportsUsageOnly); connect(&loader, &MachineLoader::deviceIsUntested, &importui, &ImportUI::onDeviceIsUntested); connect(&loader, &MachineLoader::deviceIsUnsupported, &importui, &ImportUI::onDeviceIsUnsupported); int res = loader.Open(files); progress.setMessage(QObject::tr("Finishing up...")); QCoreApplication::processEvents(); ctx->Commit(); loader.SetContext(nullptr); delete ctx; if (size > 1) { disconnect(&loader, SIGNAL(setProgressValue(int)), &progress, SLOT(setProgressValue(int))); disconnect(&progress, SIGNAL(abortClicked()), &loader, SLOT(abortImport())); progress.close(); QCoreApplication::processEvents(); } if (res == 0) { Notify(tr("There was a problem opening %1 Data File: %2").arg(name, files[0])); return; } else if (res < size){ Notify(tr("%1 Data Import of %2 file(s) complete").arg(name).arg(res) + "\n\n" + tr("There was a problem opening %1 Data File: %2").arg(name, files[res]), tr("%1 Import Partial Success").arg(name)); } else { Notify(tr("%1 Data Import complete").arg(name)); } PopulatePurgeMenu(); if (overview) overview->ReloadGraphs(); if (welcome) welcome->refreshPage(); daily->LoadDate(daily->getDate()); } } void MainWindow::on_actionImport_Somnopose_Data_triggered() { SomnoposeLoader somno; importNonCPAP(somno); } void MainWindow::on_actionImport_Viatom_Data_triggered() { ViatomLoader viatom; importNonCPAP(viatom); } void MainWindow::GenerateStatistics() { QDate first = p_profile->FirstDay(); QDate last = p_profile->LastDay(); ui->statStartDate->setMinimumDate(first); ui->statStartDate->setMaximumDate(last); ui->statEndDate->setMinimumDate(first); ui->statEndDate->setMaximumDate(last); Statistics stats; QString htmlStats = stats.GenerateHTML(); QString htmlRecords = stats.UpdateRecordsBox(); bool brange = (p_profile->general->statReportMode() == 2); ui->statEndDate->setVisible(brange); ui->statStartDate->setVisible(brange); updateFavourites(); setStatsHTML(htmlStats); setRecBoxHTML(htmlRecords); } void MainWindow::JumpDaily() { qDebug() << "Set current Widget to Daily"; // sleep(3); ui->tabWidget->setCurrentWidget(daily); } void MainWindow::JumpOverview() { ui->tabWidget->setCurrentWidget(overview); } void MainWindow::JumpStatistics() { ui->tabWidget->setCurrentWidget(ui->statisticsTab); } void MainWindow::JumpImport() { on_importButton_clicked(); } void MainWindow::JumpOxiWizard() { on_oximetryButton_clicked(); } void MainWindow::on_statisticsButton_clicked() { if (p_profile) { ui->tabWidget->setCurrentWidget(ui->statisticsTab); } } void MainWindow::on_reportModeMonthly_clicked() { ui->statStartDate->setVisible(false); ui->statEndDate->setVisible(false); if (p_profile->general->statReportMode() != 1) { p_profile->general->setStatReportMode(1); GenerateStatistics(); } } void MainWindow::on_reportModeStandard_clicked() { ui->statStartDate->setVisible(false); ui->statEndDate->setVisible(false); if (p_profile->general->statReportMode() != 0) { p_profile->general->setStatReportMode(0); GenerateStatistics(); } } void MainWindow::on_reportModeRange_clicked() { ui->statStartDate->setVisible(true); ui->statEndDate->setVisible(true); if (p_profile->general->statReportMode() != 2) { p_profile->general->setStatReportMode(2); GenerateStatistics(); } } void MainWindow::on_statEndDate_dateChanged(const QDate &date) { p_profile->general->setStatReportRangeEnd(date); GenerateStatistics(); } void MainWindow::on_statStartDate_dateChanged(const QDate &date) { p_profile->general->setStatReportRangeStart(date); GenerateStatistics(); } void MainWindow::on_actionPurgeCurrentDaysOximetry_triggered() { if (!daily) return; QDate date = daily->getDate(); Day * day = p_profile->GetDay(date, MT_OXIMETER); if (day) { if (QMessageBox::question(this, STR_MessageBox_Warning, tr("Are you sure you want to delete oximetry data for %1"). arg(daily->getDate().toString(Qt::DefaultLocaleLongDate))+"

    "+ tr("Please be aware you can not undo this operation!"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { return; } QList sessionlist=day->getSessions(MT_OXIMETER); QSet machines; for (auto & sess : sessionlist) { machines += sess->machine(); sess->Destroy(); delete sess; } // We have to update the summary cache for the affected device(s), // otherwise OSCAR will still think this day has oximetry data at next launch. for (auto & mach : machines) { mach->SaveSummaryCache(); } if (daily) { daily->Unload(date); daily->clearLastDay(); // otherwise Daily will crash daily->ReloadGraphs(); } if (overview) overview->ReloadGraphs(); if (welcome) welcome->refreshPage(); } else { QMessageBox::information(this, STR_MessageBox_Information, tr("Select the day with valid oximetry data in daily view first."),QMessageBox::Ok); } } void MainWindow::on_importButton_clicked() { on_action_Import_Data_triggered(); } void MainWindow::on_actionLine_Cursor_toggled(bool b) { AppSetting->setLineCursorMode(b); if (ui->tabWidget->currentWidget() == daily) { daily->graphView()->timedRedraw(0); } else if (ui->tabWidget->currentWidget() == overview) { overview->graphView()->timedRedraw(0); } } void MainWindow::on_actionPie_Chart_toggled(bool visible) { AppSetting->setShowPieChart(visible); if (!setupRunning && daily) { daily->updateLeftSidebar(); // daily->ReloadGraphs(); } } void MainWindow::on_actionLeft_Daily_Sidebar_toggled(bool visible) { if (daily) daily->setSidebarVisible(visible); } void MainWindow::on_actionDaily_Calendar_toggled(bool visible) { if (daily) daily->setCalendarVisible(visible); } void MainWindow::on_actionShowPersonalData_toggled(bool visible) { // This uses the Prefs routines, which require p_profile to be set if ( p_profile != nullptr ) { AppSetting->setShowPersonalData(visible); if ( ! setupRunning ) GenerateStatistics(); } else { QMessageBox::information(this, "OSCAR", tr("You must select and open the profile you wish to modify"), QMessageBox::Ok); } } #include "SleepLib/journal.h" void MainWindow::on_actionExport_Journal_triggered() { QString folder; folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); folder += QDir::separator() + tr("%1's Journal").arg(p_profile->user->userName()) + ".xml"; QString filename = QFileDialog::getSaveFileName(this, tr("Choose where to save journal"), folder, tr("XML Files (*.xml)")); BackupJournal(filename); } void MainWindow::on_actionShow_Performance_Counters_toggled(bool arg1) { AppSetting->setShowPerformance(arg1); } void MainWindow::on_actionExport_CSV_triggered() { ExportCSV ex(this); if (ex.exec() == ExportCSV::Accepted) { } } void MainWindow::on_actionExport_Review_triggered() { QMessageBox::information(nullptr, STR_MessageBox_Information, tr("Export review is not yet implemented")); } void MainWindow::on_mainsplitter_splitterMoved(int, int) { AppSetting->setRightPanelWidth(ui->mainsplitter->sizes()[1]); } void MainWindow::on_actionCreate_Card_zip_triggered() { QList datacards = selectCPAPDataCards(tr("Would you like to zip this card?"), true); for (auto & datacard : datacards) { QString cardPath = QDir(datacard.path).canonicalPath(); QString filename; QString prefix; // Loop until a valid folder is selected or the user cancels. Disallow the SD card itself! while (true) { // Note: macOS ignores this and points to OSCAR's most recently used directory for saving. QString folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); MachineInfo info = datacard.loader->PeekInfo(datacard.path); QString infostr; if (!info.modelnumber.isEmpty()) { infostr = datacard.loader->loaderName() + "-" + info.modelnumber + "-" +info.serial; } else { infostr = datacard.loader->loaderName(); } prefix = infostr; folder += "/" + infostr + ".zip"; filename = QFileDialog::getSaveFileName(this, tr("Choose where to save zip"), folder, tr("ZIP files (*.zip)")); if (filename.isEmpty()) { return; // aborted } // Try again if the selected filename is within the SD card itself. QString selectedPath = QFileInfo(filename).dir().canonicalPath(); if (selectedPath.startsWith(cardPath)) { if (QMessageBox::warning(nullptr, STR_MessageBox_Error, QObject::tr("Please select a location for your zip other than the data card itself!"), QMessageBox::Ok)) { continue; } } break; } if (!filename.toLower().endsWith(".zip")) { filename += ".zip"; } qDebug() << "Create zip of SD card:" << cardPath; ZipFile z; bool ok = z.Open(filename); if (ok) { ProgressDialog * prog = new ProgressDialog(this); // Very full cards can sometimes take nearly a minute to scan, // so display the progress dialog immediately. prog->setMessage(tr("Calculating size...")); prog->setWindowModality(Qt::ApplicationModal); prog->open(); // Build the list of files. FileQueue files; bool ok = files.AddDirectory(cardPath, prefix); // If there were any unexpected errors scanning the media, add the debug log to the zip. if (!ok) { qWarning() << "Unexpected errors when scanning SD card, adding debug log to zip."; QString debugLog = logger->logFileName(); files.AddFile(debugLog, prefix + "-debug.txt"); } prog->setMessage(tr("Creating zip...")); // Create the zip. ok = z.AddFiles(files, prog); z.Close(); } else { qWarning() << "Unable to open" << filename; } if (!ok) { QMessageBox::warning(nullptr, STR_MessageBox_Error, QObject::tr("Unable to create zip!"), QMessageBox::Ok); } } } void MainWindow::on_actionCreate_Log_zip_triggered() { QString folder; // Note: macOS ignores this and points to OSCAR's most recently used directory for saving. folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); folder += "/OSCAR-logs.zip"; QString filename = QFileDialog::getSaveFileName(this, tr("Choose where to save zip"), folder, tr("ZIP files (*.zip)")); if (filename.isEmpty()) { return; // aborted } if (!filename.toLower().endsWith(".zip")) { filename += ".zip"; } qDebug() << "Create zip of OSCAR diagnostic logs:" << filename; ZipFile z; bool ok = z.Open(filename); if (ok) { ProgressDialog * prog = new ProgressDialog(this); prog->setMessage(tr("Creating zip...")); // Build the list of files. FileQueue files; files.AddDirectory(GetLogDir(), "logs"); // Defer the current debug log to the end. QString debugLog = logger->logFileName(); QString debugLogZipName; int exists = files.Remove(debugLog, &debugLogZipName); if (exists) { files.AddFile(debugLog, debugLogZipName); } // Create the zip. ok = z.AddFiles(files, prog); z.Close(); } else { qWarning() << "Unable to open" << filename; } if (!ok) { QMessageBox::warning(nullptr, STR_MessageBox_Error, QObject::tr("Unable to create zip!"), QMessageBox::Ok); } } void MainWindow::on_actionCreate_OSCAR_Data_zip_triggered() { QString folder; // Note: macOS ignores this and points to OSCAR's most recently used directory for saving. folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); folder += "/" + STR_AppData + ".zip"; QString filename = QFileDialog::getSaveFileName(this, tr("Choose where to save zip"), folder, tr("ZIP files (*.zip)")); if (filename.isEmpty()) { return; // aborted } if (!filename.toLower().endsWith(".zip")) { filename += ".zip"; } qDebug() << "Create zip of OSCAR data folder:" << filename; QDir oscarData(GetAppData()); ZipFile z; bool ok = z.Open(filename); if (ok) { ProgressDialog * prog = new ProgressDialog(this); prog->setMessage(tr("Calculating size...")); prog->setWindowModality(Qt::ApplicationModal); prog->open(); // Build the list of files. FileQueue files; files.AddDirectory(oscarData.canonicalPath(), oscarData.dirName()); // Defer the current debug log to the end. QString debugLog = logger->logFileName(); QString debugLogZipName; int exists = files.Remove(debugLog, &debugLogZipName); if (exists) { files.AddFile(debugLog, debugLogZipName); } prog->setMessage(tr("Creating zip...")); // Create the zip. ok = z.AddFiles(files, prog); z.Close(); } else { qWarning() << "Unable to open" << filename; } if (!ok) { QMessageBox::warning(nullptr, STR_MessageBox_Error, QObject::tr("Unable to create zip!"), QMessageBox::Ok); } } #include "translation.h" void MainWindow::on_actionReport_a_Bug_triggered() { // QSettings settings; // QString language = settings.value(LangSetting).toString(); // // QDesktopServices::openUrl(QUrl(QString("https://sleepyhead.jedimark.net/report_bugs.php?lang=%1&version=%2&platform=%3").arg(language).arg(getVersion()).arg(PlatformString))); QMessageBox::information(nullptr, STR_MessageBox_Error, tr("Reporting issues is not yet implemented")); } void MainWindow::on_actionSystem_Information_triggered() { QString text = ""; // tr("OSCAR version:") + "
    "; QStringList info = getBuildInfo(); for (int i = 0; i < info.size(); ++i) text += info.at(i) + "
    "; QMessageBox::information(nullptr, tr("OSCAR Information"), text); } void MainWindow::on_profilesButton_clicked() { ui->tabWidget->setCurrentWidget(profileSelector); } void MainWindow::on_bookmarkView_anchorClicked(const QUrl &arg1) { on_recordsBox_anchorClicked(arg1); } void MainWindow::on_recordsBox_anchorClicked(const QUrl &linkurl) { QString link = linkurl.toString().section("=", 0, 0).toLower(); QString data = linkurl.toString().section("=", 1).toLower(); qDebug() << linkurl.toString() << link << data; if (link == "daily") { QDate date = QDate::fromString(data, Qt::ISODate); ui->tabWidget->setCurrentWidget(daily); QApplication::processEvents(); daily->LoadDate(date); } else if (link == "overview") { QString date1 = data.section(",", 0, 0); QString date2 = data.section(",", 1); QDate d1 = QDate::fromString(date1, Qt::ISODate); QDate d2 = QDate::fromString(date2, Qt::ISODate); overview->setRange(d1, d2); ui->tabWidget->setCurrentWidget(overview); } else if (link == "import") { // Don't run the importer directly from here because it destroys the object that called this function.. // Schedule it instead if (data == "cpap") QTimer::singleShot(0, mainwin, SLOT(on_importButton_clicked())); if (data == "oximeter") QTimer::singleShot(0, mainwin, SLOT(on_oximetryButton_clicked())); } else if (link == "statistics") { ui->tabWidget->setCurrentWidget(ui->statisticsTab); } } void MainWindow::on_statisticsView_anchorClicked(const QUrl &url) { on_recordsBox_anchorClicked(url); }