Merge branch 'master' into saveCustomRangeOverReload

This commit is contained in:
LoudSnorer 2021-04-16 18:38:27 -04:00
commit 94e5cf2332
24 changed files with 704 additions and 310 deletions

View File

@ -12,16 +12,21 @@
<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> <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> <p>
<b>Changes and fixes in OSCAR v1.X.Y</b> <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> <i>The OSCAR Team</i></p>
<ul> <ul>
<li>[new] Add support for DeVilbiss BLUE (DV6x) CPAP machines.</li>
<li>[new] Additional Philips Respironics devices tested and fully supported: <li>[new] Additional Philips Respironics devices tested and fully supported:
<ul> <ul>
<li>DreamStation Go Auto (500G120)</li> <li>DreamStation Go Auto (500G120)</li>
<li>DreamStation Auto CPAP with A-Flex (500X140)</li> <li>DreamStation Auto CPAP with A-Flex (500X140)</li>
<li>DreamStation BiPAP AVAPS 30 (1130X200)</li>
</ul> </ul>
</li> </li>
<li>[new] Add support for DreamStation Go humidifier Target Time setting.</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>[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 calculation of average leak rate on Welcome page.</li>
<li>[fix] Correct installation of non-English Release Notes on Windows.</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> <li>[fix] About/Credits page now offers Google translations to other languages.</li>
@ -33,6 +38,10 @@
<li>[fix] Purge currently selected day no longer deletes bookmarks for that day.</li> <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] Remove warning from Chromebook when importing from previously used local folder.</li>
<li>[fix] Update link to Contec drivers.</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> </ul>
<p> <p>
<b>Changes and fixes in OSCAR v1.2.0</b> <b>Changes and fixes in OSCAR v1.2.0</b>

View File

@ -1,7 +1,13 @@
lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { lessThan(QT_MAJOR_VERSION,5) {
message("You need to Qt 5.9 or newer to build OSCAR with Help Pages") error("You need Qt 5.7 or newer to build OSCAR");
lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,7) { }
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");
} }
} }

View File

@ -11,7 +11,7 @@
<message> <message>
<location filename="../oscar/aboutdialog.ui" line="35"/> <location filename="../oscar/aboutdialog.ui" line="35"/>
<source>&amp;About</source> <source>&amp;About</source>
<translation>&amp;Относно</translation> <translation type="unfinished">За &amp;приложение</translation>
</message> </message>
<message> <message>
<location filename="../oscar/aboutdialog.ui" line="49"/> <location filename="../oscar/aboutdialog.ui" line="49"/>
@ -22,12 +22,13 @@
<message> <message>
<location filename="../oscar/aboutdialog.ui" line="63"/> <location filename="../oscar/aboutdialog.ui" line="63"/>
<source>Credits</source> <source>Credits</source>
<translation type="unfinished"></translation> <translation type="unfinished">Заслуги</translation>
</message> </message>
<message> <message>
<location filename="../oscar/aboutdialog.ui" line="77"/> <location filename="../oscar/aboutdialog.ui" line="77"/>
<source>GPL License</source> <source>GPL License</source>
<translation type="unfinished"></translation> <translatorcomment>As a whole this actually should read &quot;Общ публичен лиценз на ГНУ&quot;, but that is a bit long on a tab. I think it would be acceptable to just say &quot;license GPL&quot; 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>
<message> <message>
<location filename="../oscar/aboutdialog.ui" line="239"/> <location filename="../oscar/aboutdialog.ui" line="239"/>
@ -37,27 +38,27 @@
<message> <message>
<location filename="../oscar/aboutdialog.cpp" line="35"/> <location filename="../oscar/aboutdialog.cpp" line="35"/>
<source>Show data folder</source> <source>Show data folder</source>
<translation type="unfinished"></translation> <translation type="unfinished">Покажи папката на данните</translation>
</message> </message>
<message> <message>
<location filename="../oscar/aboutdialog.cpp" line="39"/> <location filename="../oscar/aboutdialog.cpp" line="39"/>
<source>About OSCAR %1</source> <source>About OSCAR %1</source>
<translation type="unfinished"></translation> <translation type="unfinished">За предложението OSCAR %1</translation>
</message> </message>
<message> <message>
<location filename="../oscar/aboutdialog.cpp" line="83"/> <location filename="../oscar/aboutdialog.cpp" line="83"/>
<source>Sorry, could not locate About file.</source> <source>Sorry, could not locate About file.</source>
<translation type="unfinished"></translation> <translation type="unfinished">За съжаление, файлът За приложение не се намери.</translation>
</message> </message>
<message> <message>
<location filename="../oscar/aboutdialog.cpp" line="97"/> <location filename="../oscar/aboutdialog.cpp" line="97"/>
<source>Sorry, could not locate Credits file.</source> <source>Sorry, could not locate Credits file.</source>
<translation type="unfinished"></translation> <translation type="unfinished">За съжаление, файлът Заслуги не се намери.</translation>
</message> </message>
<message> <message>
<location filename="../oscar/aboutdialog.cpp" line="111"/> <location filename="../oscar/aboutdialog.cpp" line="111"/>
<source>Sorry, could not locate Release Notes.</source> <source>Sorry, could not locate Release Notes.</source>
<translation type="unfinished"></translation> <translation type="unfinished">За съжаление, Бележки по изданието не се намери.</translation>
</message> </message>
<message> <message>
<location filename="../oscar/aboutdialog.cpp" line="123"/> <location filename="../oscar/aboutdialog.cpp" line="123"/>
@ -72,12 +73,12 @@
<message> <message>
<location filename="../oscar/aboutdialog.cpp" line="127"/> <location filename="../oscar/aboutdialog.cpp" line="127"/>
<source>As this is a pre-release version, it is recommended that you &lt;b&gt;back up your data folder manually&lt;/b&gt; before proceeding, because attempting to roll back later may break things.</source> <source>As this is a pre-release version, it is recommended that you &lt;b&gt;back up your data folder manually&lt;/b&gt; before proceeding, because attempting to roll back later may break things.</source>
<translation type="unfinished"></translation> <translation type="unfinished">Тъй като това е предварително издание, препоръчано е &lt;b&gt;да направите ръчно архивиране на своята папка с данни&lt;/b&gt; преди да продължите, защото е възможно при по-късен опит за връщане назад нещата да се повредят.</translation>
</message> </message>
<message> <message>
<location filename="../oscar/aboutdialog.cpp" line="139"/> <location filename="../oscar/aboutdialog.cpp" line="139"/>
<source>To see if the license text is available in your language, see %1.</source> <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> </message>
</context> </context>
<context> <context>
@ -85,12 +86,12 @@
<message> <message>
<location filename="../oscar/SleepLib/loader_plugins/cms50f37_loader.cpp" line="878"/> <location filename="../oscar/SleepLib/loader_plugins/cms50f37_loader.cpp" line="878"/>
<source>Could not find the oximeter file:</source> <source>Could not find the oximeter file:</source>
<translation type="unfinished"></translation> <translation type="unfinished">Файлът на оксиметър не се намери:</translation>
</message> </message>
<message> <message>
<location filename="../oscar/SleepLib/loader_plugins/cms50f37_loader.cpp" line="884"/> <location filename="../oscar/SleepLib/loader_plugins/cms50f37_loader.cpp" line="884"/>
<source>Could not open the oximeter file:</source> <source>Could not open the oximeter file:</source>
<translation type="unfinished"></translation> <translation type="unfinished">Не може да се отвори файлът на оксиметър:</translation>
</message> </message>
</context> </context>
<context> <context>
@ -108,12 +109,12 @@
<message> <message>
<location filename="../oscar/SleepLib/loader_plugins/cms50_loader.cpp" line="546"/> <location filename="../oscar/SleepLib/loader_plugins/cms50_loader.cpp" line="546"/>
<source>Could not find the oximeter file:</source> <source>Could not find the oximeter file:</source>
<translation type="unfinished"></translation> <translation type="unfinished">Файлът на оксиметър не се намери:</translation>
</message> </message>
<message> <message>
<location filename="../oscar/SleepLib/loader_plugins/cms50_loader.cpp" line="552"/> <location filename="../oscar/SleepLib/loader_plugins/cms50_loader.cpp" line="552"/>
<source>Could not open the oximeter file:</source> <source>Could not open the oximeter file:</source>
<translation type="unfinished"></translation> <translation type="unfinished">Не може да се отвори файлът на оксиметър:</translation>
</message> </message>
</context> </context>
<context> <context>
@ -121,7 +122,7 @@
<message> <message>
<location filename="../oscar/checkupdates.cpp" line="240"/> <location filename="../oscar/checkupdates.cpp" line="240"/>
<source>Checking for newer OSCAR versions</source> <source>Checking for newer OSCAR versions</source>
<translation type="unfinished"></translation> <translation type="unfinished">Проверяваме за за нова версия на OSCAR</translation>
</message> </message>
</context> </context>
<context> <context>
@ -191,12 +192,12 @@
<message> <message>
<location filename="../oscar/daily.ui" line="1199"/> <location filename="../oscar/daily.ui" line="1199"/>
<source>I&apos;m feeling ...</source> <source>I&apos;m feeling ...</source>
<translation type="unfinished"></translation> <translation type="unfinished">Чувствам се ...</translation>
</message> </message>
<message> <message>
<location filename="../oscar/daily.ui" line="1222"/> <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> <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>
<message> <message>
<location filename="../oscar/daily.ui" line="1496"/> <location filename="../oscar/daily.ui" line="1496"/>
@ -211,7 +212,7 @@
<message> <message>
<location filename="../oscar/daily.ui" line="1573"/> <location filename="../oscar/daily.ui" line="1573"/>
<source>Show/hide available graphs.</source> <source>Show/hide available graphs.</source>
<translation type="unfinished"></translation> <translation type="unfinished">Покажи или скрий достъпни графики.</translation>
</message> </message>
<message> <message>
<location filename="../oscar/daily.ui" line="1085"/> <location filename="../oscar/daily.ui" line="1085"/>
@ -361,7 +362,7 @@
<message> <message>
<location filename="../oscar/daily.cpp" line="1474"/> <location filename="../oscar/daily.cpp" line="1474"/>
<source>Unable to display Pie Chart on this system</source> <source>Unable to display Pie Chart on this system</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../oscar/daily.cpp" line="1708"/> <location filename="../oscar/daily.cpp" line="1708"/>
@ -391,7 +392,7 @@
<message> <message>
<location filename="../oscar/daily.cpp" line="1718"/> <location filename="../oscar/daily.cpp" line="1718"/>
<source>Sorry, this machine only provides compliance data.</source> <source>Sorry, this machine only provides compliance data.</source>
<translation type="unfinished"></translation> <translation type="unfinished">За съжаление, тази машина предоставя само данни за съответствие.</translation>
</message> </message>
<message> <message>
<location filename="../oscar/daily.cpp" line="1719"/> <location filename="../oscar/daily.cpp" line="1719"/>
@ -441,7 +442,7 @@
<message> <message>
<location filename="../oscar/daily.cpp" line="1091"/> <location filename="../oscar/daily.cpp" line="1091"/>
<source>&lt;b&gt;Please Note:&lt;/b&gt; All settings shown below are based on assumptions that nothing has changed since previous days.</source> <source>&lt;b&gt;Please Note:&lt;/b&gt; All settings shown below are based on assumptions that nothing has changed since previous days.</source>
<translation type="unfinished"></translation> <translation type="unfinished">&lt;b&gt;Моля, Забележете:&lt;/b&gt;Всичките настройки, които са показани надолу, се основават на предположения, че нищо не се е променило от предишните дни.</translation>
</message> </message>
<message> <message>
<location filename="../oscar/daily.cpp" line="1215"/> <location filename="../oscar/daily.cpp" line="1215"/>

View File

@ -366,23 +366,25 @@ void gFlagsLine::paint(QPainter &painter, gGraph &w, const QRegion &region)
x1 = double(X - minx) * xmult + left; x1 = double(X - minx) * xmult + left;
x2 = double(X2 - 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); brush = QBrush(color);
painter.fillRect(x2, bartop, x1-x2, bottom-bartop, brush); painter.fillRect(x2, bartop, width, bottom-bartop, brush);
if (!w.selectingArea() && !hover && QRect(x2, bartop, x1-x2, bottom-bartop).contains(w.graphView()->currentMousePos())) { if (!w.selectingArea() && !hover && QRect(x2, bartop, width , bottom-bartop).contains(w.graphView()->currentMousePos())) {
hover = true; hover = true;
painter.setPen(QPen(Qt::red,1)); 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 x,y;
int s = *dptr; double s = *dptr;
int m = s / 60; double m;
s %= 60; s=60*modf(s/60,&m);
QString lab = QString("%1").arg(schema::channel[m_code].fullname()); QString lab = QString("%1").arg(schema::channel[m_code].fullname());
if (m>0) { if (m>0) {
lab += QObject::tr(" (%2 min, %3 sec)").arg(m).arg(s); lab += QObject::tr(" (%2 min, %3 sec)").arg(m).arg(s);
} else { } else {
lab += QObject::tr(" (%3 sec)").arg(m).arg(s); lab += QObject::tr(" (%3 sec)").arg(s);
} }
GetTextExtent(lab, x, y); GetTextExtent(lab, x, y);
w.ToolTip(lab, x2 - 10, bartop + (3 * w.printScaleY()), TT_AlignRight, tooltipTimeout); w.ToolTip(lab, x2 - 10, bartop + (3 * w.printScaleY()), TT_AlignRight, tooltipTimeout);

View File

@ -275,7 +275,9 @@ void gGraph::setDay(Day *day)
} }
rmin_y = rmax_y = 0; 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) void gGraph::setZoomY(short zoom)

View File

@ -28,7 +28,6 @@ void gLineOverlayBar::paint(QPainter &painter, gGraph &w, const QRegion &region)
if (!schema::channel[m_code].enabled()) if (!schema::channel[m_code].enabled())
return; return;
int left = region.boundingRect().left(); int left = region.boundingRect().left();
int topp = region.boundingRect().top(); // FIXME: Misspelling intentional. int topp = region.boundingRect().top(); // FIXME: Misspelling intentional.
double width = region.boundingRect().width(); double width = region.boundingRect().width();
@ -42,10 +41,12 @@ void gLineOverlayBar::paint(QPainter &painter, gGraph &w, const QRegion &region)
double xx = w.max_x - w.min_x; double xx = w.max_x - w.min_x;
//double yy = w.max_y - w.min_y; //double yy = w.max_y - w.min_y;
if (xx <= 0) { return; }
double jj = width / xx; double jj = width / xx;
if (xx <= 0) { return; }
double x1, x2; double x1, x2;
@ -138,12 +139,20 @@ void gLineOverlayBar::paint(QPainter &painter, gGraph &w, const QRegion &region)
x1 = jj * double(X - w.min_x); x1 = jj * double(X - w.min_x);
x2 = jj * double(Y - w.min_x); x2 = jj * double(Y - w.min_x);
x2 += (int(x1)==int(x2)) ? 1 : 0;
x2 = qMax(0.0, x2)+left; x2 = qMax(0.0, x2)+left;
x1 = qMin(width, x1)+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) { }/* else if (m_flt == FT_Dot) {
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////

View File

@ -182,7 +182,7 @@ QString Day::calcMiddleLabel(ChannelID code)
} }
QString Day::calcMaxLabel(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) QString Day::calcPercentileLabel(ChannelID code)
{ {

View File

@ -7,10 +7,11 @@
* for more details. */ * for more details. */
//******************************************************************************************** //********************************************************************************************
/// IMPORTANT!!! // 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.
// Please INCREMENT the cms50_data_version in cms50_loader.h when making changes to this loader // Note that changing the data version will require a reimport of existing data for which OSCAR
// that change loader behaviour or modify channels. // 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> #include <QApplication>

View File

@ -7,10 +7,11 @@
* for more details. */ * for more details. */
//******************************************************************************************** //********************************************************************************************
/// IMPORTANT!!! // 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.
// Please INCREMENT the cms50_data_version in cms50_loader.h when making changes to this loader // Note that changing the data version will require a reimport of existing data for which OSCAR
// that change loader behaviour or modify channels. // 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> // #include <QProgressBar>

View File

@ -7,10 +7,11 @@
* for more details. */ * for more details. */
//******************************************************************************************** //********************************************************************************************
// IMPORTANT!!! // 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.
// Please INCREMENT the dreem_data_version in dreem_loader.h when making changes to this loader // Note that changing the data version will require a reimport of existing data for which OSCAR
// that change loader behaviour or modify channels. // 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> #include <QDir>

View File

@ -66,6 +66,26 @@ bool FPIconLoader::Detect(const QString & givenpath)
return false; 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; return true;
} }

View File

@ -89,6 +89,7 @@ int IntellipapLoader::OpenDV5(const QString & path)
QString newpath = path + SL_DIR; QString newpath = path + SL_DIR;
QString filename; QString filename;
qDebug() << "DV5 Loader started";
////////////////////////// //////////////////////////
// Parse the Settings File // Parse the Settings File
@ -607,38 +608,6 @@ int IntellipapLoader::OpenDV5(const QString & path)
// May be same as what we call large leak time for other machines? // 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 struct DV6TestedModel
{ {
QString model; QString model;
@ -658,9 +627,9 @@ struct DV6_S_Data // Daily summary
Session * sess; Session * sess;
unsigned char u1; //00 (position) unsigned char u1; //00 (position)
***/ ***/
unsigned int start_time; //01 unsigned int start_time; //01 Start time for date
unsigned int stop_time; //05 unsigned int stop_time; //05 End time
unsigned int atpressure_time;//09 unsigned int written; //09 timestamp when this record was written
EventDataType hours; //13 EventDataType hours; //13
// EventDataType unknown14; //14 // EventDataType unknown14; //14
EventDataType pressureAvg; //15 EventDataType pressureAvg; //15
@ -796,13 +765,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 // Unless explicitly noted, all other DV6_x_REC are definitions for the repeating data structure that follows the header
PACK (struct DV6_HEADER { PACK (struct DV6_HEADER {
unsigned char unknown; // 0 always zero 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 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 recordLength; // 17 Length of data record (always 117)
unsigned char recordStart[4]; // 18 First record in wrap-around buffer unsigned char recordStart[4]; // 18 First record in wrap-around buffer
unsigned char unknown_22[21]; // 22 Unknown values 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 unsigned char checksum; // 55 Checksum
}); });
@ -902,6 +872,20 @@ struct DV6_SessionInfo {
CPAPMode mode = MODE_UNKNOWN; 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; unsigned int ep = 0;
// Convert a 4-character number in DV6 data file to a standard int // Convert a 4-character number in DV6 data file to a standard int
@ -918,41 +902,204 @@ unsigned int convertTime (unsigned char time[]) {
return ((time[3] << 24) + (time[2] << 16) + (time[1] << 8) + time[0]) + ep; // Time as Unix epoch 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; bool open (const QString filetype, DV6_HEADER * newhdr); // Open the file
file.setFileName(filename); 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;
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);
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;
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" + histfile.fileName() << histfile.error() << histfile.errorString();
histfile.close();
return false;
}
}
}
#else
Q_UNUSED(filetype)
Q_UNUSED(newhdr)
#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)) { if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "DV6 RollingFile could not open" << filename << "for reading, error code" << file.error() << file.errorString(); qWarning() << "DV6 RollingFile could not open" << filename << "for reading, error code" << file.error() << file.errorString();
return false; return false;
} }
// Save header for use in making backups of data
hdr = new DV6_HEADER;
QByteArray dataBA = file.read(sizeof(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; record_length = hdr->recordLength;
wrap_record = convertNum(hdr->recordStart); wrap_record = convertNum(hdr->recordStart);
record_number = wrap_record; record_number = wrap_record;
number_read = 0; number_read = 0;
wrapping = false; wrapping = false;
// Create buffer to hold each record as it is read
data = new unsigned char[record_length]; data = new unsigned char[record_length];
// Seek to first data record in file
if (!file.seek(sizeof(DV6_HEADER) + wrap_record * record_length)) { 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(); qWarning() << "DV6 RollingFile unable to make initial seek to record" << wrap_record << "in" + filename << file.error() << file.errorString();
file.close(); file.close();
return false; 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; return true;
} }
bool RollingFile::close() { bool RollingFile::close() {
file.close(); file.close();
if (data != nullptr)
#ifdef ROLLBACKUP
rb.close();
#endif
if (data)
delete [] data; delete [] data;
data = nullptr; data = nullptr;
if (hdr)
delete hdr;
hdr = nullptr;
return true; return true;
} }
@ -987,6 +1134,11 @@ unsigned char * RollingFile::get() {
file.close(); file.close();
return nullptr; return nullptr;
} }
#ifdef ROLLBACKUP
if (!rb.save(dataBA)) {
qWarning() << "DV6 RollingBackup failed";
}
#endif
number_read++; number_read++;
@ -995,21 +1147,51 @@ unsigned char * RollingFile::get() {
return data; return data;
} }
MachineInfo info; // Returns empty QByteArray() on failure.
Machine * mach = nullptr; 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; // Return date used within OSCAR, assuming day ends at split time in preferences (usually noon)
QMap<SessionID, DV6_SessionInfo> SessionData; QDate getNominalDate (QDateTime dt) {
SET_BIN_REC * settings; 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 // U.BIN - Open and parse session list and create session data structures
// with session start and stop times. // with session start and stop times.
/////////////////////////////////////////////// ///////////////////////////////////////////////
bool load6Sessions (const QString & path) { bool load6Sessions () {
RollingFile rf; RollingFile rf;
unsigned int ts1,ts2; unsigned int ts1,ts2;
@ -1018,7 +1200,7 @@ bool load6Sessions (const QString & path) {
qDebug() << "Parsing U.BIN"; qDebug() << "Parsing U.BIN";
if (!rf.open(path+"/U.BIN")) { if (!rf.open("U.BIN")) {
qWarning() << "Unable to open U.BIN"; qWarning() << "Unable to open U.BIN";
return false; return false;
} }
@ -1079,12 +1261,12 @@ bool load6Settings (const QString & path) {
// S.BIN - Open and load day summary list // S.BIN - Open and load day summary list
//////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
bool load6DailySummaries (const QString & path) { bool load6DailySummaries () {
RollingFile rf; RollingFile rf;
DailySummaries.clear(); DailySummaries.clear();
if (!rf.open(path+"/S.BIN")) { if (!rf.open("S.BIN")) {
qWarning() << "Unable to open S.BIN"; qWarning() << "Unable to open S.BIN";
return false; return false;
} }
@ -1100,7 +1282,13 @@ bool load6DailySummaries (const QString & path) {
dailyData.start_time = convertTime(rec->begin); dailyData.start_time = convertTime(rec->begin);
dailyData.stop_time = convertTime(rec->end); 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
<< "written" << dailyData.written;
#endif
dailyData.hours = float(rec->hours) / 10.0F; dailyData.hours = float(rec->hours) / 10.0F;
dailyData.pressureSetMin = float(rec->pressureSetMin) / 10.0F; dailyData.pressureSetMin = float(rec->pressureSetMin) / 10.0F;
@ -1135,6 +1323,26 @@ bool load6DailySummaries (const QString & path) {
DailySummaries[dailyData.start_time] = dailyData; 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); } while (true);
rf.close(); rf.close();
@ -1293,14 +1501,14 @@ int create6Sessions() {
// Parse R.BIN for high resolution flow data // Parse R.BIN for high resolution flow data
//////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
bool load6HighResData (const QString & path) { bool load6HighResData () {
RollingFile rf; RollingFile rf;
Session *sess = nullptr; Session *sess = nullptr;
unsigned int rec_ts1, previousRecBegin = 0; unsigned int rec_ts1, previousRecBegin = 0;
bool inSession = false; // true if we are adding data to this session 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"; qWarning() << "DV6 Unable to open R.BIN";
return false; return false;
} }
@ -1806,14 +2014,14 @@ bool load6HighResData (const QString & path) {
// Parse L.BIN for per minute data // Parse L.BIN for per minute data
//////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
bool load6PerMinute (const QString & path) { bool load6PerMinute () {
RollingFile rf; RollingFile rf;
Session *sess = nullptr; Session *sess = nullptr;
unsigned int rec_ts1, previousRecBegin = 0; unsigned int rec_ts1, previousRecBegin = 0;
bool inSession = false; // true if we are adding data to this session 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"; qWarning() << "DV6 Unable to open L.BIN";
return false; return false;
} }
@ -1848,18 +2056,18 @@ bool load6PerMinute (const QString & path) {
<< QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << rec_ts1; << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << rec_ts1;
continue; continue;
} }
/****
// Look for a gap in DV6_L records. They should be at one minute intervals. // 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 there is a gap, we are probably in a new session
if (inSession && ((rec_ts1 - previousRecBegin) > 60)) { if (inSession && ((rec_ts1 - previousRecBegin) > 60)) {
// qDebug() << "L.BIN record gap, current" << QDateTime::fromTime_t(rec_ts1).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"); << "previous" << QDateTime::fromTime_t(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss");
sess->set_last(maxleak->last()); sess->set_last(maxleak->last());
sess = nullptr; sess = nullptr;
leak = maxleak = MV = TV = RR = Pressure = nullptr; leak = maxleak = MV = TV = RR = Pressure = nullptr;
inSession = false; inSession = false;
} }
****/
// Skip over sessions until we find one that this record is in // Skip over sessions until we find one that this record is in
while (rec_ts1 > sinfo->end) { while (rec_ts1 > sinfo->end) {
#ifdef DEBUG6 #ifdef DEBUG6
@ -1960,7 +2168,7 @@ bool load6PerMinute (const QString & path) {
// Parse E.BIN for event data // Parse E.BIN for event data
//////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
bool load6EventData (const QString & path) { bool load6EventData () {
RollingFile rf; RollingFile rf;
Session *sess = nullptr; Session *sess = nullptr;
@ -1977,7 +2185,7 @@ bool load6EventData (const QString & path) {
EventList * SN = nullptr; EventList * SN = nullptr;
EventList * FL = nullptr; EventList * FL = nullptr;
if (!rf.open(path+"/E.BIN")) { if (!rf.open("E.BIN")) {
qWarning() << "DV6 Unable to open E.BIN"; qWarning() << "DV6 Unable to open E.BIN";
return false; return false;
} }
@ -2170,63 +2378,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 backup of input files
// Create dated backup files when necesaary // Create dated backup of settings file if changed
//////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
bool backup6 (const QString & path) { bool backup6 (const QString & path) {
// Are backups enabled? if (rebuild_from_backups || !create_backups)
if (!p_profile->session->backupCardData())
return true; 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 ipath(path);
QDir cpath(card_path);
QDir bpath(backup_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.exists()) {
if ( ! bpath.mkpath(backup_path) ) { if ( ! bpath.mkpath(backup_path) ) {
qWarning() << "Could not create DV6 backup directory" << backup_path; qWarning() << "Could not create DV6 backup directory" << backup_path;
@ -2249,67 +2414,104 @@ bool backup6 (const QString & path) {
bool backup_settings = true; bool backup_settings = true;
QStringList filters; QStringList filters;
filters << "set_*.bin";
QFile settingsFile;
QString inputFile = cpath.absolutePath() + "/SET.BIN";
settingsFile.setFileName(inputFile);
filters << "SET_*.BIN";
hpath.setNameFilters(filters); hpath.setNameFilters(filters);
hpath.setFilter(QDir::Files); hpath.setFilter(QDir::Files);
QDir::Name | QDir::Reversed; hpath.setSorting(QDir::Name | QDir::Reversed);
QStringList fileNames = hpath.entryList(); // Get list of files QStringList fileNames = hpath.entryList(); // Get list of files
if (! fileNames.isEmpty()) { if (! fileNames.isEmpty()) {
QString lastFile = fileNames.first(); QString lastFile = fileNames.first();
QString newFile = ipath.absolutePath() + "/set.bin"; qDebug() << "last settings file is" << lastFile << "new file is" << settingsFile;
qDebug() << "last settings file is" << lastFile << "new file is" << newFile; QByteArray newMD5 = fileChecksum(settingsFile.fileName(), QCryptographicHash::Md5);
QByteArray newMD5 = fileChecksum(newFile, QCryptographicHash::Md5); QByteArray oldMD5 = fileChecksum(hpath.absolutePath()+"/"+lastFile, QCryptographicHash::Md5);
QByteArray oldMD5 = fileChecksum(lastFile, QCryptographicHash::Md5);
if (newMD5 == oldMD5) if (newMD5 == oldMD5)
backup_settings = false; backup_settings = false;
} }
if (backup_settings) { if (backup_settings && !DailySummaries.isEmpty()) {
QString newFile = hpath.absolutePath() + "/set-" + "1234" + ".bin"; DV6_S_Data ds = DailySummaries.last();
qDebug() << "history filename is" << newFile; 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! // We're done!
return true; 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 // Open a DV6 SD card, parse everything, add to OSCAR database
//////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
int IntellipapLoader::OpenDV6(const QString & path) int IntellipapLoader::OpenDV6(const QString & path)
{ {
QString newpath = path + DV6_DIR; qDebug() << "DV6 loader started";
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(); info = newInfo();
// VER.BIN - Parse model number, serial, etc. // 2. VER.BIN - Parse model number, serial, etc. into info structure
if (!load6VersionInfo(newpath)) if (!load6VersionInfo(card_path))
return -1; return -1;
// Now, create Machine database record if it doesn't exist already // 3. Initialize rest of the DV6 loader environment
mach = p_profile->CreateMachine(info); if (!init6Environment (path))
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))
return -1; return -1;
// S.BIN - Open and parse day summary list and create a list of days // 4. SET.BIN - Parse settings file (which is only the latest settings)
if (!load6DailySummaries(newpath)) if (!load6Settings(card_path))
return -1; 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)) if (!backup6(path))
return -1; 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) // (S.BIN must already be loaded)
if (!load6Sessions(newpath)) if (!load6Sessions())
return -1; return -1;
// Create OSCAR session list from session times and summary data // Create OSCAR session list from session times and summary data
@ -2317,15 +2519,15 @@ int IntellipapLoader::OpenDV6(const QString & path)
return -1; return -1;
// R.BIN - Open and parse flow data // R.BIN - Open and parse flow data
if (!load6HighResData(newpath)) if (!load6HighResData())
return -1; return -1;
// L.BIN - Open and parse per minute data // L.BIN - Open and parse per minute data
if (!load6PerMinute(newpath)) if (!load6PerMinute())
return -1; return -1;
// E.BIN - Open and parse event data // E.BIN - Open and parse event data
if (!load6EventData(newpath)) if (!load6EventData())
return -1; return -1;
// Finalize input // Finalize input

View File

@ -7,10 +7,11 @@
* for more details. */ * for more details. */
//******************************************************************************************** //********************************************************************************************
// IMPORTANT!!! // 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.
// Please INCREMENT the somnopose_data_version in somnopose_loader.h when making changes to this loader // Note that changing the data version will require a reimport of existing data for which OSCAR
// that change loader behaviour or modify channels. // 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> #include <QDir>

View File

@ -9,10 +9,11 @@
* for more details. */ * for more details. */
//******************************************************************************************** //********************************************************************************************
// IMPORTANT!!! // 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.
// Please INCREMENT the viatom_data_version in viatom_loader.h when making changes to this loader // Note that changing the data version will require a reimport of existing data for which OSCAR
// that change loader behaviour or modify channels. // 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> #include <QDir>

View File

@ -14,7 +14,7 @@
#include "SleepLib/machine_loader.h" #include "SleepLib/machine_loader.h"
const QString viatom_class_name = "Viatom"; const QString viatom_class_name = "Viatom";
const int viatom_data_version = 3; //CN increased from 2 const int viatom_data_version = 2;
/*! \class ViatomLoader /*! \class ViatomLoader

View File

@ -8,10 +8,11 @@
* for more details. */ * for more details. */
//******************************************************************************************** //********************************************************************************************
// IMPORTANT!!! // 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.
// Please INCREMENT the zeo_data_version in zel_loader.h when making changes to this loader // Note that changing the data version will require a reimport of existing data for which OSCAR
// that change loader behaviour or modify channels. // 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> #include <QDir>

View File

@ -509,6 +509,9 @@ Daily::Daily(QWidget *parent,gGraphView * shared)
connect(GraphView, SIGNAL(updateCurrentTime(double)), this, SLOT(on_LineCursorUpdate(double))); 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(updateRange(double,double)), this, SLOT(on_RangeUpdate(double,double)));
connect(GraphView, SIGNAL(GraphsChanged()), this, SLOT(updateGraphCombo())); 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"; // qDebug() << "Finished making new Daily object";
// sleep(3); // sleep(3);
} }
@ -521,9 +524,11 @@ Daily::~Daily()
disconnect(sessionbar, SIGNAL(sessionClicked(Session*)), this, SLOT(doToggleSession(Session*))); disconnect(sessionbar, SIGNAL(sessionClicked(Session*)), this, SLOT(doToggleSession(Session*)));
disconnect(webView,SIGNAL(anchorClicked(QUrl)),this,SLOT(Link_clicked(QUrl))); 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); Unload(previous_date);
}
// Save graph orders and pin status, etc... // Save graph orders and pin status, etc...
GraphView->SaveSettings("Daily"); GraphView->SaveSettings("Daily");
@ -571,6 +576,7 @@ void Daily::Link_clicked(const QUrl &url)
// webView->page()->mainFrame()->setScrollBarValue(Qt::Vertical, webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-i); // webView->page()->mainFrame()->setScrollBarValue(Qt::Vertical, webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-i);
} else if (code=="toggleoxisession") { // Enable/Disable Oximetry session } else if (code=="toggleoxisession") { // Enable/Disable Oximetry session
day=p_profile->GetDay(previous_date,MT_OXIMETER); day=p_profile->GetDay(previous_date,MT_OXIMETER);
if (!day) return;
Session *sess=day->find(sid, MT_OXIMETER); Session *sess=day->find(sid, MT_OXIMETER);
if (!sess) if (!sess)
return; return;
@ -580,6 +586,20 @@ void Daily::Link_clicked(const QUrl &url)
// Reload day // Reload day
LoadDate(previous_date); LoadDate(previous_date);
// webView->page()->mainFrame()->setScrollBarValue(Qt::Vertical, webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-i); // 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") { } else if (code=="cpap") {
day=p_profile->GetDay(previous_date,MT_CPAP); day=p_profile->GetDay(previous_date,MT_CPAP);
if (day) { if (day) {
@ -1005,7 +1025,7 @@ QString Daily::getSessionInformation(Day * day)
case MT_SLEEPSTAGE: type="stage"; case MT_SLEEPSTAGE: type="stage";
html+=tr("Sleep Stage Sessions"); html+=tr("Sleep Stage Sessions");
break; break;
case MT_POSITION: type="stage"; case MT_POSITION: type="position";
html+=tr("Position Sensor Sessions"); html+=tr("Position Sensor Sessions");
break; break;
@ -1530,10 +1550,10 @@ QVariant MyTextBrowser::loadResource(int type, const QUrl &url)
void Daily::Load(QDate date) 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"; qDebug() << "Setting App font in Daily::Load";
setApplicationFont(); setApplicationFont();
dateDisplay->setText("<i>"+date.toString(Qt::SystemLocaleLongDate)+"</i>"); dateDisplay->setText("<i>"+date.toString(Qt::SystemLocaleLongDate)+"</i>");
previous_date=date; previous_date=date;
@ -2206,6 +2226,9 @@ void Daily::on_JournalNotesUnderline_clicked()
void Daily::on_prevDayButton_clicked() void Daily::on_prevDayButton_clicked()
{ {
if (previous_date.isValid()) {
Unload(previous_date);
}
if (!p_profile->ExistsAndTrue("SkipEmptyDays")) { if (!p_profile->ExistsAndTrue("SkipEmptyDays")) {
LoadDate(previous_date.addDays(-1)); LoadDate(previous_date.addDays(-1));
} else { } else {
@ -2220,8 +2243,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() void Daily::on_nextDayButton_clicked()
{ {
if (previous_date.isValid()) {
Unload(previous_date);
}
if (!p_profile->ExistsAndTrue("SkipEmptyDays")) { if (!p_profile->ExistsAndTrue("SkipEmptyDays")) {
LoadDate(previous_date.addDays(1)); LoadDate(previous_date.addDays(1));
} else { } else {
@ -2252,6 +2290,9 @@ void Daily::on_calButton_toggled(bool checked)
void Daily::on_todayButton_clicked() void Daily::on_todayButton_clicked()
{ {
if (previous_date.isValid()) {
Unload(previous_date);
}
// QDate d=QDate::currentDate(); // QDate d=QDate::currentDate();
// if (d > p_profile->LastDay()) { // if (d > p_profile->LastDay()) {
QDate lastcpap = p_profile->LastDay(MT_CPAP); QDate lastcpap = p_profile->LastDay(MT_CPAP);
@ -2424,21 +2465,10 @@ void Daily::on_bookmarkTable_itemChanged(QTableWidgetItem *item)
void Daily::on_weightSpinBox_valueChanged(double arg1) void Daily::on_weightSpinBox_valueChanged(double arg1)
{ {
// Update the BMI display // This is called if up/down arrows are used, in which case editingFinished is
double kg; // never called. So always call editingFinished instead
if (p_profile->general->unitSystem()==US_English) { Q_UNUSED(arg1);
kg=((arg1*pound_convert) + (ui->ouncesSpinBox->value()*ounce_convert)) / 1000.0; this->on_weightSpinBox_editingFinished();
} 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);
}
} }
void Daily::on_weightSpinBox_editingFinished() void Daily::on_weightSpinBox_editingFinished()
@ -2457,7 +2487,25 @@ void Daily::on_weightSpinBox_editingFinished()
} else { } else {
kg=arg1; 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(); gGraphView *gv=mainwin->getOverview()->graphView();
gGraph *g; gGraph *g;
if (gv) { if (gv) {
@ -2470,66 +2518,35 @@ void Daily::on_weightSpinBox_editingFinished()
ui->BMI->setVisible(true); ui->BMI->setVisible(true);
ui->BMIlabel->setVisible(true); ui->BMIlabel->setVisible(true);
journal->settings[Journal_BMI]=bmi; journal->settings[Journal_BMI]=bmi;
if (gv) {
g=gv->findGraph(STR_GRAPH_BMI);
if (g) g->setDay(nullptr);
}
} else { } 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->BMI->setVisible(false);
ui->BMIlabel->setVisible(false); ui->BMIlabel->setVisible(false);
} }
if (gv) {
g=gv->findGraph(STR_GRAPH_BMI);
if (g) g->setDay(nullptr);
}
journal->SetChanged(true); journal->SetChanged(true);
} }
void Daily::on_ouncesSpinBox_valueChanged(int arg1) void Daily::on_ouncesSpinBox_valueChanged(int arg1)
{ {
// just update for BMI display // This is called if up/down arrows are used, in which case editingFinished is
double height=p_profile->user->height()/100.0; // never called. So always call editingFinished instead
double kg=((ui->weightSpinBox->value()*pound_convert) + (arg1*ounce_convert)) / 1000.0; Q_UNUSED(arg1);
if ((height>0) && (kg>0)) { this->on_weightSpinBox_editingFinished();
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);
}
} }
void Daily::on_ouncesSpinBox_editingFinished() void Daily::on_ouncesSpinBox_editingFinished()
{ {
double arg1=ui->ouncesSpinBox->value(); // This is functionally identical to the weightSpinBox_editingFinished, so just call that
Session *journal=GetJournalSession(previous_date); this->on_weightSpinBox_editingFinished();
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);
} }
QString Daily::GetDetailsText() QString Daily::GetDetailsText()

View File

@ -304,6 +304,8 @@ private:
*/ */
void UpdateEventsTree(QTreeWidget * tree,Day *day); void UpdateEventsTree(QTreeWidget * tree,Day *day);
virtual bool eventFilter(QObject *object, QEvent *event) override;
void updateCube(); void updateCube();

View File

@ -70,7 +70,9 @@ void initializeLogger()
s_LoggerRunning.lock(); // wait until the thread begins running s_LoggerRunning.lock(); // wait until the thread begins running
s_LoggerRunning.unlock(); // we no longer need the lock 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. qInstallMessageHandler(MyOutputHandler); // NOTE: comment this line out when debugging a crash, otherwise the deferred output will mislead you.
#endif
if (b) { if (b) {
qDebug() << "Started logging thread"; qDebug() << "Started logging thread";
} else { } else {

View File

@ -1805,51 +1805,101 @@ void MainWindow::RestartApplication(bool force_login, QString cmdline)
} }
void MainWindow::on_actionPurge_Current_Day_triggered() 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) if (!daily)
return; return;
QDate date = daily->getDate(); QDate date = daily->getDate();
qDebug() << "Purging CPAP data from" << date; qDebug() << "Purging data from" << date;
daily->Unload(date); daily->Unload(date);
Day *day = p_profile->GetDay(date, MT_CPAP); Day *day = p_profile->GetDay(date, MT_UNKNOWN);
Machine *cpap = nullptr; Machine *cpap = nullptr;
if (day) if (!day)
cpap = day->machine(MT_CPAP); return;
if (cpap) { QList<Session *>::iterator s;
QList<Session *>::iterator s;
QList<Session *> list; QList<Session *> list;
for (s = day->begin(); s != day->end(); ++s) { for (s = day->begin(); s != day->end(); ++s) {
Session *sess = *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) { if (sess->type() == MT_CPAP) {
list.append(*s); cpap = day->machine(MT_CPAP);
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();
} }
} 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)); // m->day.erase(m->day.find(date));
QSet<Machine *> machines;
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
Session *sess = list.at(i); Session *sess = list.at(i);
machines += sess->machine();
sess->Destroy(); // remove the summary and event files sess->Destroy(); // remove the summary and event files
delete sess; delete sess;
} }
// save purge date where later import should start for (auto & mach : machines) {
QDate pd = cpap->purgeDate(); mach->SaveSummaryCache();
if (pd.isNull() || day->date() < pd) }
cpap->setPurgeDate(day->date());
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); Q_UNUSED(day);
daily->clearLastDay(); daily->clearLastDay();

View File

@ -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 //! \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_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); void on_action_Sidebar_Toggle_toggled(bool arg1);
@ -372,6 +377,7 @@ private:
QList<ImportPath> selectCPAPDataCards(const QString & prompt); QList<ImportPath> selectCPAPDataCards(const QString & prompt);
void importCPAPDataCards(const QList<ImportPath> & datacards); void importCPAPDataCards(const QList<ImportPath> & datacards);
void addMachineToMenu(Machine* mach, QMenu* menu); void addMachineToMenu(Machine* mach, QMenu* menu);
void purgeDay(MachineType type);
// QString getWelcomeHTML(); // QString getWelcomeHTML();
void FreeSessions(); void FreeSessions();

View File

@ -2903,7 +2903,19 @@ p, li { white-space: pre-wrap; }
<string>Purge ALL Machine Data</string> <string>Purge ALL Machine Data</string>
</property> </property>
</widget> </widget>
<addaction name="actionPurge_Current_Day"/> <widget class="QMenu" name="menuPurge_Current_Selected_Day">
<property name="title">
<string>Purge &amp;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="menuPurge_CPAP_Data"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="menuPurge_Oximetry_Data"/> <addaction name="menuPurge_Oximetry_Data"/>
@ -3084,11 +3096,6 @@ p, li { white-space: pre-wrap; }
<string>Change &amp;User</string> <string>Change &amp;User</string>
</property> </property>
</action> </action>
<action name="actionPurge_Current_Day">
<property name="text">
<string>Purge &amp;Current Selected Day</string>
</property>
</action>
<action name="action_Sidebar_Toggle"> <action name="action_Sidebar_Toggle">
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
@ -3318,6 +3325,41 @@ p, li { white-space: pre-wrap; }
<bool>true</bool> <bool>true</bool>
</property> </property>
</action> </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>&amp;CPAP</string>
</property>
</action>
<action name="actionPurgeCurrentDayOximetry">
<property name="text">
<string>&amp;Oximetry</string>
</property>
</action>
<action name="actionPurgeCurrentDaySleepStage">
<property name="text">
<string>&amp;Sleep Stage</string>
</property>
</action>
<action name="actionPurgeCurrentDayPosition">
<property name="text">
<string>&amp;Position</string>
</property>
</action>
<action name="actionPurgeCurrentDayAllExceptNotes">
<property name="text">
<string>&amp;All except Notes</string>
</property>
</action>
<action name="actionPurgeCurrentDayAll">
<property name="text">
<string>All including &amp;Notes</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View File

@ -6,14 +6,20 @@
message(Platform is $$QMAKESPEC ) message(Platform is $$QMAKESPEC )
lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { lessThan(QT_MAJOR_VERSION,5) {
message("You need Qt 5.9 to build OSCAR with Help Pages")
DEFINES += helpless
}
lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,7) {
error("You need Qt 5.7 or newer to build OSCAR"); 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 # get rid of the help browser, at least for now
DEFINES += helpless DEFINES += helpless

View File

@ -231,8 +231,20 @@ void Overview::CreateAllGraphs() {
} // for chit } // for chit
WEIGHT = createGraph(STR_GRAPH_Weight, STR_TR_Weight, STR_TR_Weight, YT_Weight); 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 = 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 = 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 // Recalculates Overview chart info