From 562cd9cc37af2e997533f00a4b095b75c98ac2fe Mon Sep 17 00:00:00 2001 From: LoudSnorer <Just4Me.1947@gmail.com> Date: Sun, 21 Mar 2021 14:15:47 -0400 Subject: [PATCH 01/20] fix display issues for short span events --- oscar/Graphs/gFlagsLine.cpp | 16 +++++++++------- oscar/Graphs/gLineOverlay.cpp | 19 ++++++++++++++----- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/oscar/Graphs/gFlagsLine.cpp b/oscar/Graphs/gFlagsLine.cpp index 50f39e37..f4738c1f 100644 --- a/oscar/Graphs/gFlagsLine.cpp +++ b/oscar/Graphs/gFlagsLine.cpp @@ -366,23 +366,25 @@ void gFlagsLine::paint(QPainter &painter, gGraph &w, const QRegion ®ion) x1 = double(X - minx) * xmult + left; x2 = double(X2 - minx) * xmult + left; + int width = x1-x2; + width = qMax(2,width); // Insure Rectangle will be visable. Flag events are 2 pixels wide. brush = QBrush(color); - painter.fillRect(x2, bartop, x1-x2, bottom-bartop, brush); - if (!w.selectingArea() && !hover && QRect(x2, bartop, x1-x2, bottom-bartop).contains(w.graphView()->currentMousePos())) { + painter.fillRect(x2, bartop, width, bottom-bartop, brush); + if (!w.selectingArea() && !hover && QRect(x2, bartop, width , bottom-bartop).contains(w.graphView()->currentMousePos())) { hover = true; painter.setPen(QPen(Qt::red,1)); - painter.drawRect(x2, bartop, x1-x2, bottom-bartop); + painter.drawRect(x2, bartop, width, bottom-bartop); int x,y; - int s = *dptr; - int m = s / 60; - s %= 60; + double s = *dptr; + double m; + s=60*modf(s/60,&m); QString lab = QString("%1").arg(schema::channel[m_code].fullname()); if (m>0) { lab += QObject::tr(" (%2 min, %3 sec)").arg(m).arg(s); } else { - lab += QObject::tr(" (%3 sec)").arg(m).arg(s); + lab += QObject::tr(" (%3 sec)").arg(s); } GetTextExtent(lab, x, y); w.ToolTip(lab, x2 - 10, bartop + (3 * w.printScaleY()), TT_AlignRight, tooltipTimeout); diff --git a/oscar/Graphs/gLineOverlay.cpp b/oscar/Graphs/gLineOverlay.cpp index b6e1484f..ce42c37c 100644 --- a/oscar/Graphs/gLineOverlay.cpp +++ b/oscar/Graphs/gLineOverlay.cpp @@ -28,7 +28,6 @@ void gLineOverlayBar::paint(QPainter &painter, gGraph &w, const QRegion ®ion) if (!schema::channel[m_code].enabled()) return; - int left = region.boundingRect().left(); int topp = region.boundingRect().top(); // FIXME: Misspelling intentional. double width = region.boundingRect().width(); @@ -42,10 +41,12 @@ void gLineOverlayBar::paint(QPainter &painter, gGraph &w, const QRegion ®ion) double xx = w.max_x - w.min_x; //double yy = w.max_y - w.min_y; + + if (xx <= 0) { return; } + double jj = width / xx; - if (xx <= 0) { return; } double x1, x2; @@ -138,12 +139,20 @@ void gLineOverlayBar::paint(QPainter &painter, gGraph &w, const QRegion ®ion) x1 = jj * double(X - w.min_x); x2 = jj * double(Y - w.min_x); - x2 += (int(x1)==int(x2)) ? 1 : 0; - x2 = qMax(0.0, x2)+left; x1 = qMin(width, x1)+left; - painter.fillRect(QRect(x2, start_py, x1-x2, height), brush); + // x2 represents the begining of a span in pixels + // x1 represent the end of the span in pixels + // BUG HERE + //x2 += (int(x1)==int(x2)) ? 1 : 0; + // Fixed BY + int duration = x1-x2; + if (duration<2) duration=2; // display minial span with 2 pixels. + x2 =x1-duration; + + painter.fillRect(QRect(x2, start_py, duration, height), brush); + } }/* else if (m_flt == FT_Dot) { //////////////////////////////////////////////////////////////////////////// From 2d502f6a6804c764b90a6a8197e35d7f08bc7130 Mon Sep 17 00:00:00 2001 From: kappa44 <6469032-kappa44@users.noreply.gitlab.com> Date: Tue, 23 Mar 2021 09:42:47 +1100 Subject: [PATCH 02/20] Revert viatom data version to avoid purge --- oscar/SleepLib/loader_plugins/viatom_loader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oscar/SleepLib/loader_plugins/viatom_loader.h b/oscar/SleepLib/loader_plugins/viatom_loader.h index 97697daf..7b326ac4 100644 --- a/oscar/SleepLib/loader_plugins/viatom_loader.h +++ b/oscar/SleepLib/loader_plugins/viatom_loader.h @@ -14,7 +14,7 @@ #include "SleepLib/machine_loader.h" const QString viatom_class_name = "Viatom"; -const int viatom_data_version = 3; //CN increased from 2 +const int viatom_data_version = 2; /*! \class ViatomLoader From 00ad97ff70184d56af7f6fde360943b8957f5c49 Mon Sep 17 00:00:00 2001 From: kappa44 <6469032-kappa44@users.noreply.gitlab.com> Date: Tue, 23 Mar 2021 09:43:14 +1100 Subject: [PATCH 03/20] Update loader version change comment --- oscar/SleepLib/loader_plugins/cms50_loader.cpp | 9 +++++---- oscar/SleepLib/loader_plugins/cms50f37_loader.cpp | 9 +++++---- oscar/SleepLib/loader_plugins/dreem_loader.cpp | 9 +++++---- oscar/SleepLib/loader_plugins/somnopose_loader.cpp | 9 +++++---- oscar/SleepLib/loader_plugins/viatom_loader.cpp | 9 +++++---- oscar/SleepLib/loader_plugins/zeo_loader.cpp | 9 +++++---- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/cms50_loader.cpp b/oscar/SleepLib/loader_plugins/cms50_loader.cpp index 2d79cfb8..b7b89888 100644 --- a/oscar/SleepLib/loader_plugins/cms50_loader.cpp +++ b/oscar/SleepLib/loader_plugins/cms50_loader.cpp @@ -7,10 +7,11 @@ * for more details. */ //******************************************************************************************** -/// IMPORTANT!!! -//******************************************************************************************** -// Please INCREMENT the cms50_data_version in cms50_loader.h when making changes to this loader -// that change loader behaviour or modify channels. +// Please only INCREMENT the cms50_data_version in cms50_loader.h when making changes +// that change loader behaviour or modify channels in a manner that fixes old data imports. +// Note that changing the data version will require a reimport of existing data for which OSCAR +// does not keep a backup - so it should be avoided if possible. +// i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include <QApplication> diff --git a/oscar/SleepLib/loader_plugins/cms50f37_loader.cpp b/oscar/SleepLib/loader_plugins/cms50f37_loader.cpp index 59b02928..fa88bc38 100644 --- a/oscar/SleepLib/loader_plugins/cms50f37_loader.cpp +++ b/oscar/SleepLib/loader_plugins/cms50f37_loader.cpp @@ -7,10 +7,11 @@ * for more details. */ //******************************************************************************************** -/// IMPORTANT!!! -//******************************************************************************************** -// Please INCREMENT the cms50_data_version in cms50_loader.h when making changes to this loader -// that change loader behaviour or modify channels. +// Please only INCREMENT the cms50f37_data_version in cms50f37_loader.h when making changes +// that change loader behaviour or modify channels in a manner that fixes old data imports. +// Note that changing the data version will require a reimport of existing data for which OSCAR +// does not keep a backup - so it should be avoided if possible. +// i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** // #include <QProgressBar> diff --git a/oscar/SleepLib/loader_plugins/dreem_loader.cpp b/oscar/SleepLib/loader_plugins/dreem_loader.cpp index 872dacca..a6c032d6 100644 --- a/oscar/SleepLib/loader_plugins/dreem_loader.cpp +++ b/oscar/SleepLib/loader_plugins/dreem_loader.cpp @@ -7,10 +7,11 @@ * for more details. */ //******************************************************************************************** -// IMPORTANT!!! -//******************************************************************************************** -// Please INCREMENT the dreem_data_version in dreem_loader.h when making changes to this loader -// that change loader behaviour or modify channels. +// Please only INCREMENT the dreem_data_version in dreem_loader.h when making changes +// that change loader behaviour or modify channels in a manner that fixes old data imports. +// Note that changing the data version will require a reimport of existing data for which OSCAR +// does not keep a backup - so it should be avoided if possible. +// i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include <QDir> diff --git a/oscar/SleepLib/loader_plugins/somnopose_loader.cpp b/oscar/SleepLib/loader_plugins/somnopose_loader.cpp index 605a3a89..40f45de0 100644 --- a/oscar/SleepLib/loader_plugins/somnopose_loader.cpp +++ b/oscar/SleepLib/loader_plugins/somnopose_loader.cpp @@ -7,10 +7,11 @@ * for more details. */ //******************************************************************************************** -// IMPORTANT!!! -//******************************************************************************************** -// Please INCREMENT the somnopose_data_version in somnopose_loader.h when making changes to this loader -// that change loader behaviour or modify channels. +// Please only INCREMENT the somnopose_data_version in somnopose_loader.h when making changes +// that change loader behaviour or modify channels in a manner that fixes old data imports. +// Note that changing the data version will require a reimport of existing data for which OSCAR +// does not keep a backup - so it should be avoided if possible. +// i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include <QDir> diff --git a/oscar/SleepLib/loader_plugins/viatom_loader.cpp b/oscar/SleepLib/loader_plugins/viatom_loader.cpp index 7c2f3e44..19147c4e 100644 --- a/oscar/SleepLib/loader_plugins/viatom_loader.cpp +++ b/oscar/SleepLib/loader_plugins/viatom_loader.cpp @@ -9,10 +9,11 @@ * for more details. */ //******************************************************************************************** -// IMPORTANT!!! -//******************************************************************************************** -// Please INCREMENT the viatom_data_version in viatom_loader.h when making changes to this loader -// that change loader behaviour or modify channels. +// Please only INCREMENT the viatom_data_version in viatom_loader.h when making changes +// that change loader behaviour or modify channels in a manner that fixes old data imports. +// Note that changing the data version will require a reimport of existing data for which OSCAR +// does not keep a backup - so it should be avoided if possible. +// i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include <QDir> diff --git a/oscar/SleepLib/loader_plugins/zeo_loader.cpp b/oscar/SleepLib/loader_plugins/zeo_loader.cpp index e282d075..bdaf4c4d 100644 --- a/oscar/SleepLib/loader_plugins/zeo_loader.cpp +++ b/oscar/SleepLib/loader_plugins/zeo_loader.cpp @@ -8,10 +8,11 @@ * for more details. */ //******************************************************************************************** -// IMPORTANT!!! -//******************************************************************************************** -// Please INCREMENT the zeo_data_version in zel_loader.h when making changes to this loader -// that change loader behaviour or modify channels. +// Please only INCREMENT the zeo_data_version in zeo_loader.h when making changes +// that change loader behaviour or modify channels in a manner that fixes old data imports. +// Note that changing the data version will require a reimport of existing data for which OSCAR +// does not keep a backup - so it should be avoided if possible. +// i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include <QDir> From 37483de62a432d2183de85d4c3822c9cf20ca3cc Mon Sep 17 00:00:00 2001 From: kappa44 <6469032-kappa44@users.noreply.gitlab.com> Date: Sun, 28 Mar 2021 14:15:10 +1100 Subject: [PATCH 04/20] Display Weight/BMI/Zombie graphs --- oscar/overview.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/oscar/overview.cpp b/oscar/overview.cpp index de2b930b..4260b8c3 100644 --- a/oscar/overview.cpp +++ b/oscar/overview.cpp @@ -236,8 +236,20 @@ void Overview::CreateAllGraphs() { } // for chit WEIGHT = createGraph(STR_GRAPH_Weight, STR_TR_Weight, STR_TR_Weight, YT_Weight); + weight = new SummaryChart("Weight", GT_LINE); + weight->setMachineType(MT_JOURNAL); + weight->addSlice(Journal_Weight, QColor("black"), ST_SETAVG); + WEIGHT->AddLayer(weight); BMI = createGraph(STR_GRAPH_BMI, STR_TR_BMI, tr("Body\nMass\nIndex")); + bmi = new SummaryChart("BMI", GT_LINE); + bmi->setMachineType(MT_JOURNAL); + bmi->addSlice(Journal_BMI, QColor("black"), ST_SETAVG); + BMI->AddLayer(bmi); ZOMBIE = createGraph(STR_GRAPH_Zombie, STR_TR_Zombie, tr("How you felt\n(0-10)")); + zombie = new SummaryChart("Zombie", GT_LINE); + zombie->setMachineType(MT_JOURNAL); + zombie->addSlice(Journal_ZombieMeter, QColor("black"), ST_SETAVG); + ZOMBIE->AddLayer(zombie); } // Recalculates Overview chart info From 2331bbba267cc795f4d08af042456f77ed905edb Mon Sep 17 00:00:00 2001 From: kappa44 <6469032-kappa44@users.noreply.gitlab.com> Date: Sun, 28 Mar 2021 14:20:19 +1100 Subject: [PATCH 05/20] Don't ResetBounds when setDay is called to avoid changing overview graphs to full date range on BMI/Weight change --- oscar/Graphs/gGraph.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oscar/Graphs/gGraph.cpp b/oscar/Graphs/gGraph.cpp index 5244d6be..9c82f9b8 100644 --- a/oscar/Graphs/gGraph.cpp +++ b/oscar/Graphs/gGraph.cpp @@ -275,7 +275,9 @@ void gGraph::setDay(Day *day) } rmin_y = rmax_y = 0; - ResetBounds(); + // This resets weight and bmi overview graphs to full date range when they are changed. + // is it required ever? + // ResetBounds(); } void gGraph::setZoomY(short zoom) From a0b9488aa96d5e63bc611afe6332eb293f597d76 Mon Sep 17 00:00:00 2001 From: kappa44 <6469032-kappa44@users.noreply.gitlab.com> Date: Sun, 28 Mar 2021 14:22:31 +1100 Subject: [PATCH 06/20] Save notes immediately when focus leaves notes widget. Fix saving of weight changes when using up/down arrows --- oscar/daily.cpp | 130 ++++++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/oscar/daily.cpp b/oscar/daily.cpp index 84b8a86f..98e2e8d4 100644 --- a/oscar/daily.cpp +++ b/oscar/daily.cpp @@ -509,6 +509,9 @@ Daily::Daily(QWidget *parent,gGraphView * shared) connect(GraphView, SIGNAL(updateCurrentTime(double)), this, SLOT(on_LineCursorUpdate(double))); connect(GraphView, SIGNAL(updateRange(double,double)), this, SLOT(on_RangeUpdate(double,double))); connect(GraphView, SIGNAL(GraphsChanged()), this, SLOT(updateGraphCombo())); + + // Watch for focusOut events on the JournalNotes widget + ui->JournalNotes->installEventFilter(this); // qDebug() << "Finished making new Daily object"; // sleep(3); } @@ -521,9 +524,11 @@ Daily::~Daily() disconnect(sessionbar, SIGNAL(sessionClicked(Session*)), this, SLOT(doToggleSession(Session*))); disconnect(webView,SIGNAL(anchorClicked(QUrl)),this,SLOT(Link_clicked(QUrl))); + ui->JournalNotes->removeEventFilter(this); - if (previous_date.isValid()) + if (previous_date.isValid()) { Unload(previous_date); + } // Save graph orders and pin status, etc... GraphView->SaveSettings("Daily"); @@ -2206,6 +2211,9 @@ void Daily::on_JournalNotesUnderline_clicked() void Daily::on_prevDayButton_clicked() { + if (previous_date.isValid()) { + Unload(previous_date); + } if (!p_profile->ExistsAndTrue("SkipEmptyDays")) { LoadDate(previous_date.addDays(-1)); } else { @@ -2220,8 +2228,23 @@ void Daily::on_prevDayButton_clicked() } } +bool Daily::eventFilter(QObject *object, QEvent *event) +{ + if (object == ui->JournalNotes && event->type() == QEvent::FocusOut) { + // Trigger immediate save of journal when we focus out from it so we never + // lose any journal entry text... + if (previous_date.isValid()) { + Unload(previous_date); + } + } + return false; +} + void Daily::on_nextDayButton_clicked() { + if (previous_date.isValid()) { + Unload(previous_date); + } if (!p_profile->ExistsAndTrue("SkipEmptyDays")) { LoadDate(previous_date.addDays(1)); } else { @@ -2252,6 +2275,9 @@ void Daily::on_calButton_toggled(bool checked) void Daily::on_todayButton_clicked() { + if (previous_date.isValid()) { + Unload(previous_date); + } // QDate d=QDate::currentDate(); // if (d > p_profile->LastDay()) { QDate lastcpap = p_profile->LastDay(MT_CPAP); @@ -2424,21 +2450,10 @@ void Daily::on_bookmarkTable_itemChanged(QTableWidgetItem *item) void Daily::on_weightSpinBox_valueChanged(double arg1) { - // Update the BMI display - double kg; - if (p_profile->general->unitSystem()==US_English) { - kg=((arg1*pound_convert) + (ui->ouncesSpinBox->value()*ounce_convert)) / 1000.0; - } else kg=arg1; - double height=p_profile->user->height()/100.0; - if ((height>0) && (kg>0)) { - double bmi=kg/(height * height); - ui->BMI->display(bmi); - ui->BMI->setVisible(true); - ui->BMIlabel->setVisible(true); - } else { - ui->BMI->setVisible(false); - ui->BMIlabel->setVisible(false); - } + // This is called if up/down arrows are used, in which case editingFinished is + // never called. So always call editingFinished instead + Q_UNUSED(arg1); + this->on_weightSpinBox_editingFinished(); } void Daily::on_weightSpinBox_editingFinished() @@ -2457,7 +2472,25 @@ void Daily::on_weightSpinBox_editingFinished() } else { kg=arg1; } - journal->settings[Journal_Weight]=kg; + if (journal->settings.contains(Journal_Weight)) { + QVariant old = journal->settings[Journal_Weight]; + if (old == kg && kg > 0) { + // No change to weight - skip + return; + } + } else if (kg == 0) { + // Still zero - skip + return; + } + if (kg > 0) { + journal->settings[Journal_Weight]=kg; + } else { + // Weight now zero - remove from journal + auto jit = journal->settings.find(Journal_Weight); + if (jit != journal->settings.end()) { + journal->settings.erase(jit); + } + } gGraphView *gv=mainwin->getOverview()->graphView(); gGraph *g; if (gv) { @@ -2470,66 +2503,35 @@ void Daily::on_weightSpinBox_editingFinished() ui->BMI->setVisible(true); ui->BMIlabel->setVisible(true); journal->settings[Journal_BMI]=bmi; - if (gv) { - g=gv->findGraph(STR_GRAPH_BMI); - if (g) g->setDay(nullptr); - } } else { + // BMI now zero - remove it + auto jit = journal->settings.find(Journal_BMI); + if (jit != journal->settings.end()) { + journal->settings.erase(jit); + } + // And make it invisible ui->BMI->setVisible(false); ui->BMIlabel->setVisible(false); } + if (gv) { + g=gv->findGraph(STR_GRAPH_BMI); + if (g) g->setDay(nullptr); + } journal->SetChanged(true); } void Daily::on_ouncesSpinBox_valueChanged(int arg1) { - // just update for BMI display - double height=p_profile->user->height()/100.0; - double kg=((ui->weightSpinBox->value()*pound_convert) + (arg1*ounce_convert)) / 1000.0; - if ((height>0) && (kg>0)) { - double bmi=kg/(height * height); - ui->BMI->display(bmi); - ui->BMI->setVisible(true); - ui->BMIlabel->setVisible(true); - } else { - ui->BMI->setVisible(false); - ui->BMIlabel->setVisible(false); - } + // This is called if up/down arrows are used, in which case editingFinished is + // never called. So always call editingFinished instead + Q_UNUSED(arg1); + this->on_weightSpinBox_editingFinished(); } void Daily::on_ouncesSpinBox_editingFinished() { - double arg1=ui->ouncesSpinBox->value(); - Session *journal=GetJournalSession(previous_date); - if (!journal) { - journal=CreateJournalSession(previous_date); - } - double height=p_profile->user->height()/100.0; - double kg=((ui->weightSpinBox->value()*pound_convert) + (arg1*ounce_convert)) / 1000.0; - journal->settings[Journal_Weight]=kg; - - gGraph *g; - if (mainwin->getOverview()) { - g=mainwin->getOverview()->graphView()->findGraph(STR_GRAPH_Weight); - if (g) g->setDay(nullptr); - } - - if ((height>0) && (kg>0)) { - double bmi=kg/(height * height); - ui->BMI->display(bmi); - ui->BMI->setVisible(true); - ui->BMIlabel->setVisible(true); - - journal->settings[Journal_BMI]=bmi; - if (mainwin->getOverview()) { - g=mainwin->getOverview()->graphView()->findGraph(STR_GRAPH_BMI); - if (g) g->setDay(nullptr); - } - } else { - ui->BMI->setVisible(false); - ui->BMIlabel->setVisible(false); - } - journal->SetChanged(true); + // This is functionally identical to the weightSpinBox_editingFinished, so just call that + this->on_weightSpinBox_editingFinished(); } QString Daily::GetDetailsText() From 00225103fd4313096b7eeb2653a21752d62c0c85 Mon Sep 17 00:00:00 2001 From: kappa44 <6469032-kappa44@users.noreply.gitlab.com> Date: Sun, 28 Mar 2021 14:22:57 +1100 Subject: [PATCH 07/20] Save notes immediately when focus leaves notes widget. --- oscar/daily.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oscar/daily.h b/oscar/daily.h index 35e9d4cb..94aad87a 100644 --- a/oscar/daily.h +++ b/oscar/daily.h @@ -304,6 +304,8 @@ private: */ void UpdateEventsTree(QTreeWidget * tree,Day *day); + virtual bool eventFilter(QObject *object, QEvent *event) override; + void updateCube(); From 3362fa4a4d1cf858c9dab91d04ad1560b9900391 Mon Sep 17 00:00:00 2001 From: Arie Klerk <arie.klerk@gmail.com> Date: Mon, 29 Mar 2021 20:54:36 +0200 Subject: [PATCH 08/20] We have a new translator for BULGARIAN! Here's his first update. --- Translations/Bulgarian.bg.ts | 43 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/Translations/Bulgarian.bg.ts b/Translations/Bulgarian.bg.ts index 2e3b91be..d777a896 100644 --- a/Translations/Bulgarian.bg.ts +++ b/Translations/Bulgarian.bg.ts @@ -11,7 +11,7 @@ <message> <location filename="../oscar/aboutdialog.ui" line="35"/> <source>&About</source> - <translation>&Относно</translation> + <translation type="unfinished">За &приложение</translation> </message> <message> <location filename="../oscar/aboutdialog.ui" line="49"/> @@ -22,12 +22,13 @@ <message> <location filename="../oscar/aboutdialog.ui" line="63"/> <source>Credits</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Заслуги</translation> </message> <message> <location filename="../oscar/aboutdialog.ui" line="77"/> <source>GPL License</source> - <translation type="unfinished"></translation> + <translatorcomment>As a whole this actually should read "Общ публичен лиценз на ГНУ", but that is a bit long on a tab. I think it would be acceptable to just say "license GPL" and then the explanation is written in the tab. The Bulgarian wikipedia page for the GPL gives a couple of translation options.</translatorcomment> + <translation type="unfinished">лиценз GPL</translation> </message> <message> <location filename="../oscar/aboutdialog.ui" line="239"/> @@ -37,27 +38,27 @@ <message> <location filename="../oscar/aboutdialog.cpp" line="35"/> <source>Show data folder</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Покажи папката на данните</translation> </message> <message> <location filename="../oscar/aboutdialog.cpp" line="39"/> <source>About OSCAR %1</source> - <translation type="unfinished"></translation> + <translation type="unfinished">За предложението OSCAR %1</translation> </message> <message> <location filename="../oscar/aboutdialog.cpp" line="83"/> <source>Sorry, could not locate About file.</source> - <translation type="unfinished"></translation> + <translation type="unfinished">За съжаление, файлът За приложение не се намери.</translation> </message> <message> <location filename="../oscar/aboutdialog.cpp" line="97"/> <source>Sorry, could not locate Credits file.</source> - <translation type="unfinished"></translation> + <translation type="unfinished">За съжаление, файлът Заслуги не се намери.</translation> </message> <message> <location filename="../oscar/aboutdialog.cpp" line="111"/> <source>Sorry, could not locate Release Notes.</source> - <translation type="unfinished"></translation> + <translation type="unfinished">За съжаление, Бележки по изданието не се намери.</translation> </message> <message> <location filename="../oscar/aboutdialog.cpp" line="123"/> @@ -72,12 +73,12 @@ <message> <location filename="../oscar/aboutdialog.cpp" line="127"/> <source>As this is a pre-release version, it is recommended that you <b>back up your data folder manually</b> before proceeding, because attempting to roll back later may break things.</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Тъй като това е предварително издание, препоръчано е <b>да направите ръчно архивиране на своята папка с данни</b> преди да продължите, защото е възможно при по-късен опит за връщане назад нещата да се повредят.</translation> </message> <message> <location filename="../oscar/aboutdialog.cpp" line="139"/> <source>To see if the license text is available in your language, see %1.</source> - <translation type="unfinished"></translation> + <translation type="unfinished">За да проверите дали съществува превода на лиценз на Вашия език, вижте %1.</translation> </message> </context> <context> @@ -85,12 +86,12 @@ <message> <location filename="../oscar/SleepLib/loader_plugins/cms50f37_loader.cpp" line="878"/> <source>Could not find the oximeter file:</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Файлът на оксиметър не се намери:</translation> </message> <message> <location filename="../oscar/SleepLib/loader_plugins/cms50f37_loader.cpp" line="884"/> <source>Could not open the oximeter file:</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Не може да се отвори файлът на оксиметър:</translation> </message> </context> <context> @@ -108,12 +109,12 @@ <message> <location filename="../oscar/SleepLib/loader_plugins/cms50_loader.cpp" line="546"/> <source>Could not find the oximeter file:</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Файлът на оксиметър не се намери:</translation> </message> <message> <location filename="../oscar/SleepLib/loader_plugins/cms50_loader.cpp" line="552"/> <source>Could not open the oximeter file:</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Не може да се отвори файлът на оксиметър:</translation> </message> </context> <context> @@ -121,7 +122,7 @@ <message> <location filename="../oscar/checkupdates.cpp" line="240"/> <source>Checking for newer OSCAR versions</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Проверяваме за за нова версия на OSCAR</translation> </message> </context> <context> @@ -191,12 +192,12 @@ <message> <location filename="../oscar/daily.ui" line="1199"/> <source>I'm feeling ...</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Чувствам се ...</translation> </message> <message> <location filename="../oscar/daily.ui" line="1222"/> <source>If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Ако в настройки ръст е над нула, задаване на тегло тук ще се покажи стойността на индекса на телесната маса (ИТМ)</translation> </message> <message> <location filename="../oscar/daily.ui" line="1496"/> @@ -211,7 +212,7 @@ <message> <location filename="../oscar/daily.ui" line="1573"/> <source>Show/hide available graphs.</source> - <translation type="unfinished"></translation> + <translation type="unfinished">Покажи или скрий достъпни графики.</translation> </message> <message> <location filename="../oscar/daily.ui" line="1085"/> @@ -361,7 +362,7 @@ <message> <location filename="../oscar/daily.cpp" line="1474"/> <source>Unable to display Pie Chart on this system</source> - <translation type="unfinished"></translation> + <translation></translation> </message> <message> <location filename="../oscar/daily.cpp" line="1708"/> @@ -391,7 +392,7 @@ <message> <location filename="../oscar/daily.cpp" line="1718"/> <source>Sorry, this machine only provides compliance data.</source> - <translation type="unfinished"></translation> + <translation type="unfinished">За съжаление, тази машина предоставя само данни за съответствие.</translation> </message> <message> <location filename="../oscar/daily.cpp" line="1719"/> @@ -441,7 +442,7 @@ <message> <location filename="../oscar/daily.cpp" line="1091"/> <source><b>Please Note:</b> All settings shown below are based on assumptions that nothing has changed since previous days.</source> - <translation type="unfinished"></translation> + <translation type="unfinished"><b>Моля, Забележете:</b>Всичките настройки, които са показани надолу, се основават на предположения, че нищо не се е променило от предишните дни.</translation> </message> <message> <location filename="../oscar/daily.cpp" line="1215"/> From 85b4013b2d681fcccf1ae4aec0761e39d57918f4 Mon Sep 17 00:00:00 2001 From: kappa44 <6469032-kappa44@users.noreply.gitlab.com> Date: Thu, 1 Apr 2021 11:32:58 +1100 Subject: [PATCH 09/20] Fix stage & position session toggle --- oscar/daily.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/oscar/daily.cpp b/oscar/daily.cpp index 98e2e8d4..8511a362 100644 --- a/oscar/daily.cpp +++ b/oscar/daily.cpp @@ -576,6 +576,7 @@ void Daily::Link_clicked(const QUrl &url) // webView->page()->mainFrame()->setScrollBarValue(Qt::Vertical, webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-i); } else if (code=="toggleoxisession") { // Enable/Disable Oximetry session day=p_profile->GetDay(previous_date,MT_OXIMETER); + if (!day) return; Session *sess=day->find(sid, MT_OXIMETER); if (!sess) return; @@ -585,6 +586,20 @@ void Daily::Link_clicked(const QUrl &url) // Reload day LoadDate(previous_date); // webView->page()->mainFrame()->setScrollBarValue(Qt::Vertical, webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-i); + } else if (code=="togglestagesession") { // Enable/Disable Sleep Stage session + day=p_profile->GetDay(previous_date,MT_SLEEPSTAGE); + if (!day) return; + Session *sess=day->find(sid, MT_SLEEPSTAGE); + if (!sess) return; + sess->setEnabled(!sess->enabled()); + LoadDate(previous_date); + } else if (code=="togglepositionsession") { // Enable/Disable Position session + day=p_profile->GetDay(previous_date,MT_POSITION); + if (!day) return; + Session *sess=day->find(sid, MT_POSITION); + if (!sess) return; + sess->setEnabled(!sess->enabled()); + LoadDate(previous_date); } else if (code=="cpap") { day=p_profile->GetDay(previous_date,MT_CPAP); if (day) { @@ -1010,7 +1025,7 @@ QString Daily::getSessionInformation(Day * day) case MT_SLEEPSTAGE: type="stage"; html+=tr("Sleep Stage Sessions"); break; - case MT_POSITION: type="stage"; + case MT_POSITION: type="position"; html+=tr("Position Sensor Sessions"); break; @@ -1535,10 +1550,10 @@ QVariant MyTextBrowser::loadResource(int type, const QUrl &url) void Daily::Load(QDate date) { - qDebug() << "Daily::Load called for" << date.toString() << "using" << QApplication::font().toString(); + qDebug() << "Daily::Load called for" << date.toString() << "using" << QApplication::font().toString(); - qDebug() << "Setting App font in Daily::Load"; - setApplicationFont(); + qDebug() << "Setting App font in Daily::Load"; + setApplicationFont(); dateDisplay->setText("<i>"+date.toString(Qt::SystemLocaleLongDate)+"</i>"); previous_date=date; From e358d31f269dd01bf63a420267e9d9a23f387ac1 Mon Sep 17 00:00:00 2001 From: Guy Scharf <guy.oscar@moxis.com> Date: Thu, 1 Apr 2021 21:39:06 -0700 Subject: [PATCH 10/20] Add logic to read rolling files created by DeVilbiss BLUE CPAPs --- .../loader_plugins/intellipap_loader.cpp | 469 +++++++++++++----- 1 file changed, 332 insertions(+), 137 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/intellipap_loader.cpp b/oscar/SleepLib/loader_plugins/intellipap_loader.cpp index 7c6cedb8..e4a98e87 100644 --- a/oscar/SleepLib/loader_plugins/intellipap_loader.cpp +++ b/oscar/SleepLib/loader_plugins/intellipap_loader.cpp @@ -607,38 +607,6 @@ int IntellipapLoader::OpenDV5(const QString & path) // May be same as what we call large leak time for other machines? //////////////////////////////////////////////////////////////////////////// -class RollingFile -{ -public: - RollingFile () { } - - ~RollingFile () { - if (data) - delete [] data; - data = nullptr; - } - - bool open (QString fn); // Open the file - bool close(); // close the file - unsigned char * get(); // read the next record in the file - - int numread () {return number_read;}; // Return number of records read - int recnum () {return record_number;}; // Return last-read record number - -private: - QString filename; - QFile file; - int record_length; - int wrap_record; - bool wrapping = false; - - int number_read = 0; // Number of records read - - int record_number = 0; // Number of record. First record in the file is #1. First record read is wrap_record; - - unsigned char * data = nullptr; -}; - struct DV6TestedModel { QString model; @@ -658,9 +626,9 @@ struct DV6_S_Data // Daily summary Session * sess; unsigned char u1; //00 (position) ***/ - unsigned int start_time; //01 - unsigned int stop_time; //05 - unsigned int atpressure_time;//09 + unsigned int start_time; //01 Start time for date + unsigned int stop_time; //05 End time + unsigned int written; //09 timestamp when this record was written EventDataType hours; //13 // EventDataType unknown14; //14 EventDataType pressureAvg; //15 @@ -796,13 +764,14 @@ PACK (struct SET_BIN_REC { // Unless explicitly noted, all other DV6_x_REC are definitions for the repeating data structure that follows the header PACK (struct DV6_HEADER { unsigned char unknown; // 0 always zero - unsigned char filetype; // 1 always "R" + unsigned char filetype; // 1 e.g. "R" for a R.BIN file unsigned char serial[11]; // 2 serial number - unsigned char numRecords[4]; // 13 Number of records in file (always 180,000) + unsigned char numRecords[4]; // 13 Number of records in file (always fixed, 180,000 for R.BIN) unsigned char recordLength; // 17 Length of data record (always 117) unsigned char recordStart[4]; // 18 First record in wrap-around buffer unsigned char unknown_22[21]; // 22 Unknown values - unsigned char unknown_43[12]; // 43 Seems always to be zero + unsigned char unknown_43[8]; // 43 Seems always to be zero + unsigned char lasttime[4]; // 51 OSCAR only: Last timestamp, in history files only unsigned char checksum; // 55 Checksum }); @@ -902,6 +871,20 @@ struct DV6_SessionInfo { CPAPMode mode = MODE_UNKNOWN; }; +QString card_path; +QString backup_path; +QString history_path; + +MachineInfo info; +Machine * mach = nullptr; + +bool rebuild_from_backups = false; +bool create_backups = false; + +QMap<SessionID, DV6_S_Data> DailySummaries; +QMap<SessionID, DV6_SessionInfo> SessionData; +SET_BIN_REC * settings; + unsigned int ep = 0; // Convert a 4-character number in DV6 data file to a standard int @@ -918,41 +901,199 @@ unsigned int convertTime (unsigned char time[]) { return ((time[3] << 24) + (time[2] << 16) + (time[1] << 8) + time[0]) + ep; // Time as Unix epoch time } -bool RollingFile::open(QString fn) { +class RollingBackup +{ +public: + RollingBackup () {} + ~RollingBackup () { + } - filename = fn; - file.setFileName(filename); + bool open (const QString filetype, DV6_HEADER * newhdr); // Open the file + bool close(); // close the file + bool save(QByteArray dataBA); // save the next record in the file + +private: + DV6_HEADER hdr; // file header + QString filetype; + QFile hFile; + + int record_length; // Length of record block in incoming file + const int maxHistFileSize = 20*10e6; // Maximum size of file before we create a new file + + int numWritten; // Number of records written + quint32 lastTimestamp; +}; + +bool RollingBackup::open (const QString filetype, DV6_HEADER * newhdr) { + if (!create_backups) + return true; + + this->filetype = filetype; + + QDir hpath(history_path); + QStringList filters; + + numWritten = 0; + + filters.append(filetype); + filters[0].insert(1, "_*"); + hpath.setNameFilters(filters); + hpath.setFilter(QDir::Files); + hpath.setSorting(QDir::Name | QDir::Reversed); + + QStringList fileNames = hpath.entryList(); // Get list of files + QFile histfile(fileNames.first()); + +// bool needNewFile = false; + + // Handle first time a history file is being created + if (fileNames.isEmpty()) { + memcpy (&hdr, newhdr, sizeof(DV6_HEADER)); + for (int i = 0; i < 4; i++) { + hdr.recordStart[i] = 0; + hdr.lasttime[i] = 0; + } + record_length = hdr.recordLength; + } + + // We have an existing history record + if (! fileNames.isEmpty()) { + // See if this file is large enough that we want to create a new file + if (histfile.size() > maxHistFileSize) { + memcpy (&hdr, newhdr, sizeof(DV6_HEADER)); + for (int i = 0; i < 4; i++) + hdr.recordStart[i] = 0; + + if (!histfile.open(QIODevice::ReadOnly)) { + qWarning() << "DV6 RollingBackup could not open" << fileNames.first() << "for reading, error code" << histfile.error() << histfile.errorString(); + return false; + } + record_length = hdr.recordLength; + +#ifdef ROLLBACKUP + wrap_record = convertNum(hdr.recordStart); + if (!histfile.seek(sizeof(DV6_HEADER) + (wrap_record-1) * record_length)) { + qWarning() << "DV6 RollingBackup unable to make initial seek to record" << wrap_record << "in" + filename << file.error() << file.errorString(); + file.close(); + return false; + } +#endif + + } + } + + return true; +} + +bool RollingBackup::close() { + if (!create_backups) + return true; + return true; +} + +bool RollingBackup::save(QByteArray dataBA) { + Q_UNUSED(dataBA) + if (!create_backups) + return true; + return true; +} + +class RollingFile +{ +public: + RollingFile () { } + + ~RollingFile () { + if (data) + delete [] data; + data = nullptr; + if (hdr) + delete hdr; + hdr = nullptr; + } + + bool open (QString fn); // Open the file + bool close(); // close the file + unsigned char * get(); // read the next record in the file + + int numread () {return number_read;}; // Return number of records read + int recnum () {return record_number;}; // Return last-read record number + + RollingBackup rb; + +private: + QString filename; + QFile file; + int record_length; + int wrap_record; + bool wrapping = false; + + int number_read = 0; // Number of records read + + int record_number = 0; // Number of record. First record in the file is #1. First record read is wrap_record; + + DV6_HEADER * hdr; // file header + + unsigned char * data = nullptr; // record pointer +}; + +bool RollingFile::open(QString filetype) { + + filename = filetype; + file.setFileName(card_path + "/" +filetype); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "DV6 RollingFile could not open" << filename << "for reading, error code" << file.error() << file.errorString(); return false; } + // Save header for use in making backups of data + hdr = new DV6_HEADER; QByteArray dataBA = file.read(sizeof(DV6_HEADER)); - DV6_HEADER * hdr = (DV6_HEADER *) dataBA.data(); + memcpy (hdr, dataBA.data(), sizeof(DV6_HEADER)); + + // Extract control information from header record_length = hdr->recordLength; wrap_record = convertNum(hdr->recordStart); record_number = wrap_record; number_read = 0; wrapping = false; + // Create buffer to hold each record as it is read data = new unsigned char[record_length]; + // Seek to first data record in file if (!file.seek(sizeof(DV6_HEADER) + wrap_record * record_length)) { qWarning() << "DV6 RollingFile unable to make initial seek to record" << wrap_record << "in" + filename << file.error() << file.errorString(); file.close(); return false; } +#ifdef ROLLBACKUP + if (!rb.open(filetype, hdr)) { + qWarning() << "DV6 RollingBackup failed"; + file.close(); + return false; + } +#endif - qDebug() << "RollingFile opening" << filename << "at wrap record" << wrap_record; + qDebug() << "DV6 RollingFile opening" << filename << "at wrap record" << wrap_record; return true; } bool RollingFile::close() { file.close(); - if (data != nullptr) + +#ifdef ROLLBACKUP + rb.close(); +#endif + + if (data) delete [] data; data = nullptr; + if (hdr) + delete hdr; + hdr = nullptr; + return true; } @@ -987,6 +1128,11 @@ unsigned char * RollingFile::get() { file.close(); return nullptr; } +#ifdef ROLLBACKUP + if (!rb.save(dataBA)) { + qWarning() << "DV6 RollingBackup failed"; + } +#endif number_read++; @@ -995,21 +1141,51 @@ unsigned char * RollingFile::get() { return data; } -MachineInfo info; -Machine * mach = nullptr; +// Returns empty QByteArray() on failure. +QByteArray fileChecksum(const QString &fileName, + QCryptographicHash::Algorithm hashAlgorithm) +{ + QFile f(fileName); + if (f.open(QFile::ReadOnly)) { + QCryptographicHash hash(hashAlgorithm); + bool res = hash.addData(&f); + f.close(); + if (res) { + return hash.result(); + } + } + return QByteArray(); +} -bool rebuild_from_backups = false; +/*** +// Return the OSCAR date that the last data was written. +// This will be considered to be the last day for which we have any data. +// Adjust to get the correct date for sessions starting after midnight. +QDate getLastDate () { + return QDate(); +} +***/ -QMap<SessionID, DV6_S_Data> DailySummaries; -QMap<SessionID, DV6_SessionInfo> SessionData; -SET_BIN_REC * settings; +// Return date used within OSCAR, assuming day ends at split time in preferences (usually noon) +QDate getNominalDate (QDateTime dt) { + QDate d = dt.date(); + QTime tm = dt.time(); + QTime daySplitTime = p_profile->session->getPref(STR_IS_DaySplitTime).toTime(); + if (tm < daySplitTime) + d = d.addDays(-1); + return d; +} +QDate getNominalDate (unsigned int dt) { + QDateTime xdt = QDateTime::fromSecsSinceEpoch(dt); + return getNominalDate(xdt); +} /////////////////////////////////////////////// // U.BIN - Open and parse session list and create session data structures // with session start and stop times. /////////////////////////////////////////////// -bool load6Sessions (const QString & path) { +bool load6Sessions () { RollingFile rf; unsigned int ts1,ts2; @@ -1018,7 +1194,7 @@ bool load6Sessions (const QString & path) { qDebug() << "Parsing U.BIN"; - if (!rf.open(path+"/U.BIN")) { + if (!rf.open("U.BIN")) { qWarning() << "Unable to open U.BIN"; return false; } @@ -1079,12 +1255,12 @@ bool load6Settings (const QString & path) { // S.BIN - Open and load day summary list //////////////////////////////////////////////////////////////////////////////////////// -bool load6DailySummaries (const QString & path) { +bool load6DailySummaries () { RollingFile rf; DailySummaries.clear(); - if (!rf.open(path+"/S.BIN")) { + if (!rf.open("S.BIN")) { qWarning() << "Unable to open S.BIN"; return false; } @@ -1100,7 +1276,13 @@ bool load6DailySummaries (const QString & path) { dailyData.start_time = convertTime(rec->begin); dailyData.stop_time = convertTime(rec->end); - dailyData.atpressure_time = convertTime(rec->written); + dailyData.written = convertTime(rec->written); + +#ifdef DEBUG6 + qDebug() << "DV6 S.BIN start" << dailyData.start_time + << "stop" << dailyData.stop_time + << "at pressure?" << dailyData.atpressure_time; +#endif dailyData.hours = float(rec->hours) / 10.0F; dailyData.pressureSetMin = float(rec->pressureSetMin) / 10.0F; @@ -1135,6 +1317,26 @@ bool load6DailySummaries (const QString & path) { DailySummaries[dailyData.start_time] = dailyData; +/**** Previous loader did this: + if (!mach->sessionlist.contains(ts1)) { // Check if already imported + qDebug() << "Detected new Session" << ts1; + R.sess = new Session(mach, ts1); + R.sess->SetChanged(true); + + R.sess->really_set_first(qint64(ts1) * 1000L); + R.sess->really_set_last(qint64(ts2) * 1000L); + + if (data[49] != data[50]) { + R.sess->settings[CPAP_PressureMin] = R.pressureSetMin; + R.sess->settings[CPAP_PressureMax] = R.pressureSetMax; + R.sess->settings[CPAP_Mode] = MODE_APAP; + } else { + R.sess->settings[CPAP_Mode] = MODE_CPAP; + R.sess->settings[CPAP_Pressure] = R.pressureSetMin; + } + R.hasMaskPressure = false; +***/ + } while (true); rf.close(); @@ -1293,14 +1495,14 @@ int create6Sessions() { // Parse R.BIN for high resolution flow data //////////////////////////////////////////////////////////////////////////////////////// -bool load6HighResData (const QString & path) { +bool load6HighResData () { RollingFile rf; Session *sess = nullptr; unsigned int rec_ts1, previousRecBegin = 0; bool inSession = false; // true if we are adding data to this session - if (!rf.open(path+"/R.BIN")) { + if (!rf.open("R.BIN")) { qWarning() << "DV6 Unable to open R.BIN"; return false; } @@ -1806,14 +2008,14 @@ bool load6HighResData (const QString & path) { // Parse L.BIN for per minute data //////////////////////////////////////////////////////////////////////////////////////// -bool load6PerMinute (const QString & path) { +bool load6PerMinute () { RollingFile rf; Session *sess = nullptr; unsigned int rec_ts1, previousRecBegin = 0; bool inSession = false; // true if we are adding data to this session - if (!rf.open(path+"/L.BIN")) { + if (!rf.open("L.BIN")) { qWarning() << "DV6 Unable to open L.BIN"; return false; } @@ -1960,7 +2162,7 @@ bool load6PerMinute (const QString & path) { // Parse E.BIN for event data //////////////////////////////////////////////////////////////////////////////////////// -bool load6EventData (const QString & path) { +bool load6EventData () { RollingFile rf; Session *sess = nullptr; @@ -1977,7 +2179,7 @@ bool load6EventData (const QString & path) { EventList * SN = nullptr; EventList * FL = nullptr; - if (!rf.open(path+"/E.BIN")) { + if (!rf.open("E.BIN")) { qWarning() << "DV6 Unable to open E.BIN"; return false; } @@ -2170,63 +2372,20 @@ int addSessions() { } -// Returns empty QByteArray() on failure. -QByteArray fileChecksum(const QString &fileName, - QCryptographicHash::Algorithm hashAlgorithm) -{ - QFile f(fileName); - if (f.open(QFile::ReadOnly)) { - QCryptographicHash hash(hashAlgorithm); - if (hash.addData(&f)) { - return hash.result(); - } - } - return QByteArray(); -} - -/**** -// Return the OSCAR date that the last data was written. -// This will be considered to be the last day for which we have any data. -// Adjust to get the correct date for sessions starting after midnight. -QDate getLastDate () { - return QDate(); -} - -// Return date used within OSCAR, assuming day ends at noon -QDate getOscarDate (QDateTime dt) { - QDate d = dt.date(); - QTime tm = dt.time(); - if (tm.hour() < 11) - d = d.addDays(-1); - return d; -} -***/ - //////////////////////////////////////////////////////////////////////////////////////// // Create backup of input files -// Create dated backup files when necesaary +// Create dated backup of settings file if changed //////////////////////////////////////////////////////////////////////////////////////// bool backup6 (const QString & path) { - // Are backups enabled? - if (!p_profile->session->backupCardData()) + if (rebuild_from_backups || !create_backups) return true; - QString backup_path = mach->getBackupPath(); - QString history_path = backup_path + "/DV6/HISTORY"; - - // Compare QDirs rather than QStrings because separators may be different, especially on Windows. - // We want to check whether import and backup paths are the same, regardless of variations in the string representations. QDir ipath(path); + QDir cpath(card_path); QDir bpath(backup_path); - if (ipath == bpath) { - // Don't create backups if importing from backup folder - rebuild_from_backups = true; - return true; - } - if ( ! bpath.exists()) { if ( ! bpath.mkpath(backup_path) ) { qWarning() << "Could not create DV6 backup directory" << backup_path; @@ -2249,67 +2408,103 @@ bool backup6 (const QString & path) { bool backup_settings = true; QStringList filters; - filters << "set_*.bin"; + + QFile settingsFile; + QString inputFile = cpath.absolutePath() + "/SET.BIN"; + settingsFile.setFileName(inputFile); + + filters << "SET_*.BIN"; hpath.setNameFilters(filters); hpath.setFilter(QDir::Files); - QDir::Name | QDir::Reversed; + hpath.setSorting(QDir::Name | QDir::Reversed); QStringList fileNames = hpath.entryList(); // Get list of files if (! fileNames.isEmpty()) { QString lastFile = fileNames.first(); - QString newFile = ipath.absolutePath() + "/set.bin"; - qDebug() << "last settings file is" << lastFile << "new file is" << newFile; - QByteArray newMD5 = fileChecksum(newFile, QCryptographicHash::Md5); - QByteArray oldMD5 = fileChecksum(lastFile, QCryptographicHash::Md5); + qDebug() << "last settings file is" << lastFile << "new file is" << settingsFile; + QByteArray newMD5 = fileChecksum(settingsFile.fileName(), QCryptographicHash::Md5); + QByteArray oldMD5 = fileChecksum(hpath.absolutePath()+"/"+lastFile, QCryptographicHash::Md5); if (newMD5 == oldMD5) backup_settings = false; } - if (backup_settings) { - QString newFile = hpath.absolutePath() + "/set-" + "1234" + ".bin"; - qDebug() << "history filename is" << newFile; + if (backup_settings && !DailySummaries.isEmpty()) { + DV6_S_Data ds = DailySummaries.last(); + QString newFile = hpath.absolutePath() + "/SET_" + getNominalDate(ds.start_time).toString("yyyyMMdd") + ".BIN"; + if (!settingsFile.copy(inputFile, newFile)) { + qWarning() << "DV6 backup could not copy" << inputFile << "to" << newFile << ", error code" << settingsFile.error() << settingsFile.errorString(); + } } // We're done! return true; } +//////////////////////////////////////////////////////////////////////////////////////// +// Initialize DV6 environment +//////////////////////////////////////////////////////////////////////////////////////// + +bool init6Environment (const QString & path) { + + // Create Machine database record if it doesn't exist already + mach = p_profile->CreateMachine(info); + if (mach == nullptr) { + qWarning() << "Could not create DV6 Machine data structure"; + return false; + } + + backup_path = mach->getBackupPath(); + history_path = backup_path + "/HISTORY"; + + // Compare QDirs rather than QStrings because separators may be different, especially on Windows. + QDir ipath(path); + QDir bpath(backup_path); + + if (ipath == bpath) { + // Don't create backups if importing from backup folder + rebuild_from_backups = true; + create_backups = false; + } else { + rebuild_from_backups = false; + create_backups = p_profile->session->backupCardData(); + } + + return true; +} + //////////////////////////////////////////////////////////////////////////////////////// // Open a DV6 SD card, parse everything, add to OSCAR database //////////////////////////////////////////////////////////////////////////////////////// int IntellipapLoader::OpenDV6(const QString & path) { - QString newpath = path + DV6_DIR; + card_path = path + DV6_DIR; - // Prime the machine database's info field with stuff relevant to this machine + // 1. Prime the machine database's info field with this machine info = newInfo(); - // VER.BIN - Parse model number, serial, etc. - if (!load6VersionInfo(newpath)) + // 2. VER.BIN - Parse model number, serial, etc. into info structure + if (!load6VersionInfo(card_path)) return -1; - // Now, create Machine database record if it doesn't exist already - mach = p_profile->CreateMachine(info); - if (mach == nullptr) { - qWarning() << "Could not create Machine data structure"; - return -1; - } - - // SET.BIN - Parse settings file (which is only the latest settings) - if (!load6Settings(newpath)) + // 3. Initialize rest of the DV6 loader environment + if (!init6Environment (path)) return -1; - // S.BIN - Open and parse day summary list and create a list of days - if (!load6DailySummaries(newpath)) + // 4. SET.BIN - Parse settings file (which is only the latest settings) + if (!load6Settings(card_path)) return -1; - // Back up data files (must do after parsing VER.BIN, S.BIN, and creating Machine) + // 5. S.BIN - Open and parse day summary list and create a list of days + if (!load6DailySummaries()) + return -1; + + // 6. Back up data files (must do after parsing VER.BIN, S.BIN, and creating Machine) if (!backup6(path)) return -1; - // U.BIN - Open and parse session list and create a list of session times + // 7. U.BIN - Open and parse session list and create a list of session times // (S.BIN must already be loaded) - if (!load6Sessions(newpath)) + if (!load6Sessions()) return -1; // Create OSCAR session list from session times and summary data @@ -2317,15 +2512,15 @@ int IntellipapLoader::OpenDV6(const QString & path) return -1; // R.BIN - Open and parse flow data - if (!load6HighResData(newpath)) + if (!load6HighResData()) return -1; // L.BIN - Open and parse per minute data - if (!load6PerMinute(newpath)) + if (!load6PerMinute()) return -1; // E.BIN - Open and parse event data - if (!load6EventData(newpath)) + if (!load6EventData()) return -1; // Finalize input From e699ea182d435794ca1633102b3c7f71c2b1ef50 Mon Sep 17 00:00:00 2001 From: Guy Scharf <guy.oscar@moxis.com> Date: Fri, 2 Apr 2021 17:17:50 -0700 Subject: [PATCH 11/20] Update version checking to allow QT 6 --- OSCAR_QT.pro | 14 ++++++++++---- oscar/oscar.pro | 16 +++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/OSCAR_QT.pro b/OSCAR_QT.pro index df4e72bb..a9aa515c 100644 --- a/OSCAR_QT.pro +++ b/OSCAR_QT.pro @@ -1,7 +1,13 @@ -lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { - message("You need to Qt 5.9 or newer to build OSCAR with Help Pages") - lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,7) { - error("You need Qt 5.7 or newer to build OSCAR") +lessThan(QT_MAJOR_VERSION,5) { + error("You need Qt 5.7 or newer to build OSCAR"); +} + +if (equals(QT_MAJOR_VERSION,5)) { + lessThan(QT_MINOR_VERSION,9) { + message("You need Qt 5.9 to build OSCAR with Help Pages") + } + lessThan(QT_MINOR_VERSION,7) { + error("You need Qt 5.7 or newer to build OSCAR"); } } diff --git a/oscar/oscar.pro b/oscar/oscar.pro index eb704b56..b2f8e977 100644 --- a/oscar/oscar.pro +++ b/oscar/oscar.pro @@ -6,14 +6,20 @@ message(Platform is $$QMAKESPEC ) -lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { - message("You need Qt 5.9 to build OSCAR with Help Pages") - DEFINES += helpless -} -lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,7) { +lessThan(QT_MAJOR_VERSION,5) { error("You need Qt 5.7 or newer to build OSCAR"); } +if (equals(QT_MAJOR_VERSION,5)) { + lessThan(QT_MINOR_VERSION,9) { + message("You need Qt 5.9 to build OSCAR with Help Pages") + DEFINES += helpless + } + lessThan(QT_MINOR_VERSION,7) { + error("You need Qt 5.7 or newer to build OSCAR"); + } +} + # get rid of the help browser, at least for now DEFINES += helpless From e3a87a31481d36e3a7365ddbb6bc9c2fe9c46d6c Mon Sep 17 00:00:00 2001 From: Guy Scharf <guy.oscar@moxis.com> Date: Sun, 4 Apr 2021 04:56:06 -0700 Subject: [PATCH 12/20] Update release notes with recent changes. --- Htmldocs/release_notes.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html index e2dd1c9b..304a8ca6 100644 --- a/Htmldocs/release_notes.html +++ b/Htmldocs/release_notes.html @@ -12,16 +12,20 @@ <br><a href=http://www.apneaboard.com/wiki/index.php/OSCAR_Release_Notes>http://www.apneaboard.com/wiki/index.php/OSCAR_Release_Notes</a></p> <p> <b>Changes and fixes in OSCAR v1.X.Y</b> - <br>Portions of OSCAR are © 2019-2020 by + <br>Portions of OSCAR are © 2019-2021 by <i>The OSCAR Team</i></p> <ul> + <li>[new] Add support for DeVilbiss BLUE (DV6x) CPAP machines [experimental].</li> <li>[new] Additional Philips Respironics devices tested and fully supported: <ul> <li>DreamStation Go Auto (500G120)</li> <li>DreamStation Auto CPAP with A-Flex (500X140)</li> + <li>DreamStation BiPAP AVAPS 30 (1130X200)</li> </ul> </li> <li>[new] Add support for DreamStation Go humidifier Target Time setting.</li> + <li>[new] Add Bulgarian translation; update other languages.</li> + <li>[new] Improve Somnopose import options.</li> <li>[fix] Correct calculation of average leak rate on Welcome page.</li> <li>[fix] Correct installation of non-English Release Notes on Windows.</li> <li>[fix] About/Credits page now offers Google translations to other languages.</li> @@ -33,6 +37,10 @@ <li>[fix] Purge currently selected day no longer deletes bookmarks for that day.</li> <li>[fix] Remove warning from Chromebook when importing from previously used local folder.</li> <li>[fix] Update link to Contec drivers.</li> + <li>[fix] Fix display problems for short duration events.</li> + <li>[fix] Statistics headings will now be 99.5% or Max, depending on machine type and preference settings.</li> + <li>[fix] Mark exported Journal backup file as UTF-8.</li> + <li>[fix] Improve error message when unable to access OSCAR database.</li> </ul> <p> <b>Changes and fixes in OSCAR v1.2.0</b> From 3114cf517d00b59b0664d510e73193ea220d681e Mon Sep 17 00:00:00 2001 From: Guy Scharf <guy.oscar@moxis.com> Date: Sun, 4 Apr 2021 04:56:39 -0700 Subject: [PATCH 13/20] Improve file identification in Icon loader Will no longer attempt to process SleepStyle data with Icon loader A new loader for SleepStyle machines is forthcoming. --- oscar/SleepLib/loader_plugins/icon_loader.cpp | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/oscar/SleepLib/loader_plugins/icon_loader.cpp b/oscar/SleepLib/loader_plugins/icon_loader.cpp index f2ad2963..097d1e85 100644 --- a/oscar/SleepLib/loader_plugins/icon_loader.cpp +++ b/oscar/SleepLib/loader_plugins/icon_loader.cpp @@ -66,6 +66,26 @@ bool FPIconLoader::Detect(const QString & givenpath) return false; } + // ICON serial numbers (directory names) are all digits (SleepStyle are mixed alpha and numeric) + QString serialDir(dir.path() + "/FPHCARE/ICON"); + QDir iconDir(serialDir); + + iconDir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); + iconDir.setSorting(QDir::Name); + QFileInfoList flist = iconDir.entryInfoList(); + + bool ok; + + for (int i = 0; i < flist.size(); i++) { + QFileInfo fi = flist.at(i); + QString filename = fi.fileName(); + + filename.toInt(&ok); + + if (!ok) { + return false; + } + } return true; } From 94faff5297d9671def8762790813a59129ef135b Mon Sep 17 00:00:00 2001 From: kappa44 <6469032-kappa44@users.noreply.gitlab.com> Date: Mon, 5 Apr 2021 12:05:05 +1000 Subject: [PATCH 14/20] Allow purge current day for all machine types --- oscar/mainwindow.cpp | 100 ++++++++++++++++++++++++++++++++----------- oscar/mainwindow.h | 6 +++ oscar/mainwindow.ui | 54 ++++++++++++++++++++--- 3 files changed, 129 insertions(+), 31 deletions(-) diff --git a/oscar/mainwindow.cpp b/oscar/mainwindow.cpp index 390fed99..f49bc6ef 100644 --- a/oscar/mainwindow.cpp +++ b/oscar/mainwindow.cpp @@ -1805,51 +1805,101 @@ void MainWindow::RestartApplication(bool force_login, QString cmdline) } 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 machine 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 CPAP data from" << date; + qDebug() << "Purging data from" << date; daily->Unload(date); - Day *day = p_profile->GetDay(date, MT_CPAP); + Day *day = p_profile->GetDay(date, MT_UNKNOWN); Machine *cpap = nullptr; - if (day) - cpap = day->machine(MT_CPAP); + if (!day) + return; - if (cpap) { - QList<Session *>::iterator s; + QList<Session *>::iterator s; - QList<Session *> list; - for (s = day->begin(); s != day->end(); ++s) { - Session *sess = *s; + QList<Session *> 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) { - list.append(*s); - qDebug() << "Purging session 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(); + 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(); } - 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<Machine *> 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; } - // save purge date where later import should start - QDate pd = cpap->purgeDate(); - if (pd.isNull() || day->date() < pd) - cpap->setPurgeDate(day->date()); + 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_CPAP); + day = p_profile->GetDay(date, MT_UNKNOWN); Q_UNUSED(day); daily->clearLastDay(); diff --git a/oscar/mainwindow.h b/oscar/mainwindow.h index 93834d6b..d2352277 100644 --- a/oscar/mainwindow.h +++ b/oscar/mainwindow.h @@ -268,6 +268,11 @@ class MainWindow : public QMainWindow //! \brief Destroy the CPAP data for the currently selected day, so it can be freshly imported again void on_actionPurge_Current_Day_triggered(); + void on_actionPurgeCurrentDayOximetry_triggered(); + void on_actionPurgeCurrentDaySleepStage_triggered(); + void on_actionPurgeCurrentDayPosition_triggered(); + void on_actionPurgeCurrentDayAllExceptNotes_triggered(); + void on_actionPurgeCurrentDayAll_triggered(); void on_action_Sidebar_Toggle_toggled(bool arg1); @@ -372,6 +377,7 @@ private: QList<ImportPath> selectCPAPDataCards(const QString & prompt); void importCPAPDataCards(const QList<ImportPath> & datacards); void addMachineToMenu(Machine* mach, QMenu* menu); + void purgeDay(MachineType type); // QString getWelcomeHTML(); void FreeSessions(); diff --git a/oscar/mainwindow.ui b/oscar/mainwindow.ui index 1b68d8c9..10d6b4ea 100644 --- a/oscar/mainwindow.ui +++ b/oscar/mainwindow.ui @@ -2903,7 +2903,19 @@ p, li { white-space: pre-wrap; } <string>Purge ALL Machine Data</string> </property> </widget> - <addaction name="actionPurge_Current_Day"/> + <widget class="QMenu" name="menuPurge_Current_Selected_Day"> + <property name="title"> + <string>Purge &Current Selected Day</string> + </property> + <addaction name="actionPurge_Current_Day"/> + <addaction name="actionPurgeCurrentDayOximetry"/> + <addaction name="actionPurgeCurrentDaySleepStage"/> + <addaction name="actionPurgeCurrentDayPosition"/> + <addaction name="separator"/> + <addaction name="actionPurgeCurrentDayAllExceptNotes"/> + <addaction name="actionPurgeCurrentDayAll"/> + </widget> + <addaction name="menuPurge_Current_Selected_Day"/> <addaction name="menuPurge_CPAP_Data"/> <addaction name="separator"/> <addaction name="menuPurge_Oximetry_Data"/> @@ -3084,11 +3096,6 @@ p, li { white-space: pre-wrap; } <string>Change &User</string> </property> </action> - <action name="actionPurge_Current_Day"> - <property name="text"> - <string>Purge &Current Selected Day</string> - </property> - </action> <action name="action_Sidebar_Toggle"> <property name="checkable"> <bool>true</bool> @@ -3318,6 +3325,41 @@ p, li { white-space: pre-wrap; } <bool>true</bool> </property> </action> + <action name="actionPurge_Current_Selected_Day"> + <property name="text"> + <string>Purge Current Selected Day</string> + </property> + </action> + <action name="actionPurge_Current_Day"> + <property name="text"> + <string>&CPAP</string> + </property> + </action> + <action name="actionPurgeCurrentDayOximetry"> + <property name="text"> + <string>&Oximetry</string> + </property> + </action> + <action name="actionPurgeCurrentDaySleepStage"> + <property name="text"> + <string>&Sleep Stage</string> + </property> + </action> + <action name="actionPurgeCurrentDayPosition"> + <property name="text"> + <string>&Position</string> + </property> + </action> + <action name="actionPurgeCurrentDayAllExceptNotes"> + <property name="text"> + <string>&All except Notes</string> + </property> + </action> + <action name="actionPurgeCurrentDayAll"> + <property name="text"> + <string>All including &Notes</string> + </property> + </action> </widget> <customwidgets> <customwidget> From 113d592af399b284888a4f073c69f8548fdaf36c Mon Sep 17 00:00:00 2001 From: kappa44 <6469032-kappa44@users.noreply.gitlab.com> Date: Mon, 5 Apr 2021 12:12:16 +1000 Subject: [PATCH 15/20] Allow purge current day for all machine types --- Htmldocs/release_notes.html | 1 + 1 file changed, 1 insertion(+) diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html index e2dd1c9b..c5b72957 100644 --- a/Htmldocs/release_notes.html +++ b/Htmldocs/release_notes.html @@ -33,6 +33,7 @@ <li>[fix] Purge currently selected day no longer deletes bookmarks for that day.</li> <li>[fix] Remove warning from Chromebook when importing from previously used local folder.</li> <li>[fix] Update link to Contec drivers.</li> + <li>[new] Purge Current Selected Day allows purge of each machine type separately</li> </ul> <p> <b>Changes and fixes in OSCAR v1.2.0</b> From 889bc8842c8c6c11e7625d54f804ff79056c616c Mon Sep 17 00:00:00 2001 From: Guy Scharf <guy.oscar@moxis.com> Date: Fri, 9 Apr 2021 14:03:37 +1000 Subject: [PATCH 16/20] Logger.cpp: Add #define to allow all debug messages to be seen I have DEFINES+=HARDLOG set up in my QT Debug configurations so I can always see messages in the event of a crash. --- oscar/logger.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oscar/logger.cpp b/oscar/logger.cpp index 6594f3e6..e3f4d108 100644 --- a/oscar/logger.cpp +++ b/oscar/logger.cpp @@ -70,7 +70,9 @@ void initializeLogger() s_LoggerRunning.lock(); // wait until the thread begins running s_LoggerRunning.unlock(); // we no longer need the lock } +#ifndef HARDLOG qInstallMessageHandler(MyOutputHandler); // NOTE: comment this line out when debugging a crash, otherwise the deferred output will mislead you. +#endif if (b) { qDebug() << "Started logging thread"; } else { From 4234cb34a46f29d3119ac87ae0d8f20873f181de Mon Sep 17 00:00:00 2001 From: Guy Scharf <guy.oscar@moxis.com> Date: Fri, 9 Apr 2021 14:05:54 +1000 Subject: [PATCH 17/20] Fix problem with truncated per-minute data in graphs. --- oscar/SleepLib/loader_plugins/intellipap_loader.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/intellipap_loader.cpp b/oscar/SleepLib/loader_plugins/intellipap_loader.cpp index e4a98e87..c40357ff 100644 --- a/oscar/SleepLib/loader_plugins/intellipap_loader.cpp +++ b/oscar/SleepLib/loader_plugins/intellipap_loader.cpp @@ -89,6 +89,7 @@ int IntellipapLoader::OpenDV5(const QString & path) QString newpath = path + SL_DIR; QString filename; + qDebug() << "DV5 Loader started"; ////////////////////////// // Parse the Settings File @@ -1281,7 +1282,7 @@ bool load6DailySummaries () { #ifdef DEBUG6 qDebug() << "DV6 S.BIN start" << dailyData.start_time << "stop" << dailyData.stop_time - << "at pressure?" << dailyData.atpressure_time; + << "written" << dailyData.written; #endif dailyData.hours = float(rec->hours) / 10.0F; @@ -2050,18 +2051,18 @@ bool load6PerMinute () { << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << rec_ts1; continue; } - +/**** // Look for a gap in DV6_L records. They should be at one minute intervals. // If there is a gap, we are probably in a new session if (inSession && ((rec_ts1 - previousRecBegin) > 60)) { -// qDebug() << "L.BIN record gap, current" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") -// << "previous" << QDateTime::fromTime_t(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss"); + qDebug() << "L.BIN record gap, current" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") + << "previous" << QDateTime::fromTime_t(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss"); sess->set_last(maxleak->last()); sess = nullptr; leak = maxleak = MV = TV = RR = Pressure = nullptr; inSession = false; } - +****/ // Skip over sessions until we find one that this record is in while (rec_ts1 > sinfo->end) { #ifdef DEBUG6 @@ -2477,6 +2478,7 @@ bool init6Environment (const QString & path) { int IntellipapLoader::OpenDV6(const QString & path) { + qDebug() << "DV6 loader started"; card_path = path + DV6_DIR; // 1. Prime the machine database's info field with this machine From bbfa4aed6bd11c8b357315b0513ed0ef7dec95e6 Mon Sep 17 00:00:00 2001 From: Guy Scharf <guy.oscar@moxis.com> Date: Fri, 9 Apr 2021 14:08:30 +1000 Subject: [PATCH 18/20] Update release notes for latest changes (nothing important here) --- Htmldocs/release_notes.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html index b20a2275..24370a09 100644 --- a/Htmldocs/release_notes.html +++ b/Htmldocs/release_notes.html @@ -15,7 +15,7 @@ <br>Portions of OSCAR are © 2019-2021 by <i>The OSCAR Team</i></p> <ul> - <li>[new] Add support for DeVilbiss BLUE (DV6x) CPAP machines [experimental].</li> + <li>[new] Add support for DeVilbiss BLUE (DV6x) CPAP machines.</li> <li>[new] Additional Philips Respironics devices tested and fully supported: <ul> <li>DreamStation Go Auto (500G120)</li> @@ -26,6 +26,7 @@ <li>[new] Add support for DreamStation Go humidifier Target Time setting.</li> <li>[new] Add Bulgarian translation; update other languages.</li> <li>[new] Improve Somnopose import options.</li> + <li>[new] Purge Current Selected Day allows purge of each machine type separately</li> <li>[fix] Correct calculation of average leak rate on Welcome page.</li> <li>[fix] Correct installation of non-English Release Notes on Windows.</li> <li>[fix] About/Credits page now offers Google translations to other languages.</li> @@ -41,7 +42,6 @@ <li>[fix] Statistics headings will now be 99.5% or Max, depending on machine type and preference settings.</li> <li>[fix] Mark exported Journal backup file as UTF-8.</li> <li>[fix] Improve error message when unable to access OSCAR database.</li> - <li>[new] Purge Current Selected Day allows purge of each machine type separately</li> </ul> <p> <b>Changes and fixes in OSCAR v1.2.0</b> From 037b13c73c8f138cd4f612321fb884ae6126408a Mon Sep 17 00:00:00 2001 From: Guy Scharf <guy.oscar@moxis.com> Date: Tue, 13 Apr 2021 17:47:15 -0600 Subject: [PATCH 19/20] Adjust labels on Daily and Overview pages to show 99.5% instead of Max when max is not used Note that "Max" on the Statistics page really is the "max" and not 99.5% --- oscar/SleepLib/day.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oscar/SleepLib/day.cpp b/oscar/SleepLib/day.cpp index a752b6cf..a18f657a 100644 --- a/oscar/SleepLib/day.cpp +++ b/oscar/SleepLib/day.cpp @@ -182,7 +182,7 @@ QString Day::calcMiddleLabel(ChannelID code) } QString Day::calcMaxLabel(ChannelID code) { - return QObject::tr("%1 %2").arg(p_profile->general->prefCalcMax() ? QObject::tr("Peak") : STR_TR_Max).arg(schema::channel[code].label()); + return QObject::tr("%1 %2").arg(p_profile->general->prefCalcMax() ? QObject::tr("99.5%") : STR_TR_Max).arg(schema::channel[code].label()); } QString Day::calcPercentileLabel(ChannelID code) { From 5a64262ff6d4fcef583717798f760a919a33f7e1 Mon Sep 17 00:00:00 2001 From: Guy Scharf <guy.oscar@moxis.com> Date: Tue, 13 Apr 2021 17:48:39 -0600 Subject: [PATCH 20/20] Minor fix to #ifdefs in DV6 loader --- oscar/SleepLib/loader_plugins/intellipap_loader.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/intellipap_loader.cpp b/oscar/SleepLib/loader_plugins/intellipap_loader.cpp index c40357ff..4a5a4717 100644 --- a/oscar/SleepLib/loader_plugins/intellipap_loader.cpp +++ b/oscar/SleepLib/loader_plugins/intellipap_loader.cpp @@ -923,12 +923,14 @@ private: int numWritten; // Number of records written quint32 lastTimestamp; + unsigned int wrap_record; }; bool RollingBackup::open (const QString filetype, DV6_HEADER * newhdr) { if (!create_backups) return true; +#ifdef ROLLBACKUP this->filetype = filetype; QDir hpath(history_path); @@ -971,17 +973,20 @@ bool RollingBackup::open (const QString filetype, DV6_HEADER * newhdr) { } record_length = hdr.recordLength; -#ifdef ROLLBACKUP wrap_record = convertNum(hdr.recordStart); if (!histfile.seek(sizeof(DV6_HEADER) + (wrap_record-1) * record_length)) { - qWarning() << "DV6 RollingBackup unable to make initial seek to record" << wrap_record << "in" + filename << file.error() << file.errorString(); - file.close(); + qWarning() << "DV6 RollingBackup unable to make initial seek to record" << wrap_record + << "in" + histfile.fileName() << histfile.error() << histfile.errorString(); + histfile.close(); return false; } -#endif } } +#else + Q_UNUSED(filetype) + Q_UNUSED(newhdr) +#endif return true; }