Fix conflict by allowing comment

This commit is contained in:
Phil Olynyk 2021-05-02 08:16:32 -04:00
commit 6b5443e6b8
40 changed files with 2649 additions and 1805 deletions

View File

@ -12,16 +12,23 @@
<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>[new] Multi-file import for non-CPAP loaders (Somnopose, Viatom, Zeo, Dreem)</li>
<li>[new] Weight, BMI and Zombie history appear in statistics</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 +40,12 @@
<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>
<li>[fix] Stop skipping the first 40 seconds of ResMed low-rate pressure data.</li>
<li>[fix] Correct Total Time and AHI in CSV Export when non-CPAP devices are used.</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"/>

File diff suppressed because it is too large Load Diff

View File

@ -9,48 +9,57 @@
#ifndef MINUTESATPRESSURE_H #ifndef MINUTESATPRESSURE_H
#define MINUTESATPRESSURE_H #define MINUTESATPRESSURE_H
#include <QPen>
#include "Graphs/layer.h" #include "Graphs/layer.h"
#include "SleepLib/day.h" #include "SleepLib/day.h"
#include "SleepLib/schema.h"
#include "Graphs/gLineChart.h"
class MinutesAtPressure; class MinutesAtPressure;
struct PressureInfo struct PressureInfo
{ {
PressureInfo() public:
{ PressureInfo();
code = 0; PressureInfo(ChannelID code, qint64 minTime, qint64 maxTime) ;
minx = maxx = 0;
peaktime = peakevents = 0;
min_pressure = max_pressure = 0;
}
PressureInfo(PressureInfo &copy) = default; PressureInfo(PressureInfo &copy) = default;
PressureInfo(ChannelID code, qint64 minx, qint64 maxx) : code(code), minx(minx), maxx(maxx) void AddChannel(ChannelID c);
{ void AddChannels(QList<ChannelID> & chans);
times.resize(300);
}
void AddChannel(ChannelID c)
{
chans.append(c);
events[c].resize(300);
}
void AddChannels(QList<ChannelID> & chans)
{
for (int i=0; i<chans.size(); ++i) {
AddChannel(chans.at(i));
}
}
void finishCalcs(); void finishCalcs();
void setMachineTimes(EventDataType min,EventDataType max);
ChannelID code; ChannelID code;
qint64 minx, maxx; schema::Channel chan;
qint64 minTime, maxTime;
QVector<int> times; QVector<int> times;
int peaktime, peakevents; int peaktime, peakevents;
int min_pressure, max_pressure;
QHash<ChannelID, QVector<int> > events; QHash<ChannelID, QVector<int> > events;
QHash<ChannelID, int> numEvents;
QList<ChannelID> chans; QList<ChannelID> chans;
QVector<EventList*> eventLists;
void updateBucketsPerPressure(Session* sess);
int bucketsPerPressure = 1;
int numberXaxisDivisions =10;
EventDataType rawToPressure ( EventStoreType raw,EventDataType gain);
EventStoreType rawToBucketId ( EventStoreType raw,EventDataType gain);
EventDataType minpressure = 0.0;
EventDataType maxpressure = 0.0;
qint64 totalDuration = 0;
EventDataType machinePressureMin = 0.0;
EventDataType machinePressureMax = 0.0;
int firstPlotBucket =0;
int lastPlotBucket =0;
private:
void init();
}; };
class RecalcMAP:public QRunnable class RecalcMAP:public QRunnable
{ {
friend class MinutesAtPressure; friend class MinutesAtPressure;
@ -58,15 +67,31 @@ public:
explicit RecalcMAP(MinutesAtPressure * map) :map(map), m_quit(false), m_done(false) {} explicit RecalcMAP(MinutesAtPressure * map) :map(map), m_quit(false), m_done(false) {}
virtual ~RecalcMAP(); virtual ~RecalcMAP();
virtual void run(); virtual void run();
void quit(); void quit();
protected: protected:
void updateTimes(PressureInfo & info, Session * sess);
MinutesAtPressure * map; MinutesAtPressure * map;
volatile bool m_quit; volatile bool m_quit;
volatile bool m_done; volatile bool m_done;
private:
void setSelectionRange(gGraph* graph);
qint64 minTime, maxTime;
ChannelID chanId; // required for debug.
PressureInfo * ipap_info;
void updateTimes(PressureInfo & info);
void updateEvents(Session*sess,PressureInfo & info);
void updateTimesValues(qint64 d1,qint64 d2, int key,PressureInfo & info);
void updateEventsChannel(Session * sess,ChannelID id, QVector<int> &background, PressureInfo & info );
void updateFlagData(int &currentLoc, int & currentEL,int& currentData,qint64 eventTime, QVector<int> &dataArray, PressureInfo & info ) ;
void updateSpanData(int &currentLoc, int & currentEL,int& currentData,qint64 startSpan, qint64 eventTime , QVector<int> &dataArray, PressureInfo & info ) ;
}; };
class MinutesAtPressure:public Layer class MinutesAtPressure:public Layer
{ {
friend class RecalcMAP; friend class RecalcMAP;
@ -96,6 +121,10 @@ public:
return map; return map;
} }
protected:
int numCloned =0;
void CloneInto(MinutesAtPressure * layer) { void CloneInto(MinutesAtPressure * layer) {
mutex.lock(); mutex.lock();
timelock.lock(); timelock.lock();
@ -103,47 +132,142 @@ public:
layer->m_minimum_height = m_minimum_height; layer->m_minimum_height = m_minimum_height;
layer->m_lastminx = m_lastminx; layer->m_lastminx = m_lastminx;
layer->m_lastmaxx = m_lastmaxx; layer->m_lastmaxx = m_lastmaxx;
layer->times = times; layer->ipap = ipap;
layer->chans = chans; layer->epap = epap;
layer->events = events; layer->numCloned=numCloned+1;
layer->maxtime = maxtime;
layer->maxevents = maxevents;
layer->m_presChannel = m_presChannel;
layer->m_minpressure = m_minpressure;
layer->m_maxpressure = m_maxpressure;
layer->max_mins = max_mins;
layer->ahis = ahis;
timelock.unlock(); timelock.unlock();
layer->m_enabled = m_enabled;
mutex.unlock(); mutex.unlock();
} }
protected: bool isCLoned() {return numCloned!=0;};
QMutex timelock;
QMutex mutex;
bool m_empty;
int m_minimum_height;
qint64 m_lastminx;
qint64 m_lastmaxx;
gGraph * m_graph;
RecalcMAP * m_remap; RecalcMAP * m_remap;
QMap<EventStoreType, int> times; bool initialized=false;
QMap<EventStoreType, int> epap_times; bool m_empty;
QList<ChannelID> chans; QMutex mutex;
QHash<ChannelID, QMap<EventStoreType, EventDataType> > events; QMutex timelock;
int maxtime; int m_minimum_height;
int maxevents; //QAtomicInteger<int> m_recalcCount;
ChannelID m_presChannel;
EventStoreType m_minpressure; private:
EventStoreType m_maxpressure;
PressureInfo epap, ipap; PressureInfo epap, ipap;
void setEnabled(gGraph &graph);
QHash<ChannelID, bool > m_enabled;
gGraph * m_graph;
qint64 m_lastminx;
qint64 m_lastmaxx;
QPoint last_mouse=QPoint(0,0);
//EventDataType m_last_height=0; // re-calculate only when needed.
//int m_last_peaktime=0; // re-calculate only when needed.
bool isEnabled(ChannelID id) ;
QString topBarLabel;
};
class MapPainter
{
public:
// environment - set in constructor
QPainter& painter;
gGraph& graph;
QRectF& drawingRect;
QRectF& boundingRect;
EventDataType lineThickness;
MapPainter(
QPainter& painter,
gGraph& graph,
QRectF& drawingRect ,
QRectF& boundingRect ) :
painter(painter),
graph(graph),
drawingRect(drawingRect) ,
boundingRect(boundingRect) {
lineThickness= AppSetting->lineThickness();
};
// mouse related
int mouseOverKey;
bool graphSelected;
void setMouse(int mouseOverKey,bool graphSelected) {
this->mouseOverKey=mouseOverKey;
this->graphSelected=graphSelected;
};
// for all graphs horizonatal
int startGraphBucket;
int endGraphBucket;
EventDataType pixelsPerBucket;
EventDataType minpressure;
int bucketsPerPressure;
void setHorizontal( EventDataType minpressure, EventDataType maxpressure,EventDataType pixelsPerBucket , int bucketsPerPressure, int catmullRomSplineNumberOfPoints) {
this->startGraphBucket = minpressure*bucketsPerPressure;
this->endGraphBucket = maxpressure*bucketsPerPressure;
this->pixelsPerBucket = pixelsPerBucket;
this->minpressure = minpressure;
this->bucketsPerPressure = bucketsPerPressure;
initCatmullRomSpline(pixelsPerBucket,catmullRomSplineNumberOfPoints);
};
void drawEvent();
void drawSpanEvents();
void drawEventTick();
// Pen type for drawing - per graph
QPen linePen;
QPen pointSelectionPen;
QPen pointEnhancePen;
QPen tickPen;
QPen tickEnhancePen;
QPen tickEnhanceTransparentPen;
//EventDataType bottom,top,height,left,right;
schema::Channel* channel;
schema::ChanType chanType;
EventDataType yPixelsPerUnit;
QVector<int> dataArray;
int startBucket;
int endBucket;
void setChannelInfo(ChannelID id, QVector<int> dataArray, EventDataType yPixelsPerUnit ,int ,int) ;
void initCatmullRomSpline(EventDataType pixelsPerBucket,int numberOfPoints);
EventDataType catmullRomSplineIncrement, catmullRomSplineInterval;
int catmullRomSplineNumberOfPoints;
EventDataType catmullRomSplineXstep; // based on pixelsPerBucket.
void setPenColorAlpha(ChannelID channelId ,int opacity) ;
void setPenColorAlpha(ChannelID channelId ) ;
void drawPlot();
EventDataType dataToYaxis(int value) ;
EventDataType verifyYaxis(EventDataType value);
void initCatmullRomSpline(int numberOfPoints);
void drawPoint(bool fill,int xp, int yp);
EventDataType drawSegment ( int i , EventDataType fromx,EventDataType fromy) ;
EventDataType yPixelsPerMsec ;
EventDataType yPixelsPerEvent;
EventDataType pixelsPerPressure;
EventDataType yPixelsPerStep ;
EventDataType yMinutesPerStep;
EventDataType peakMinutes;
int singleCharWidth, textHeight;
void calculatePeakY(int peaktime );
int drawYaxis(int peaktime);
void drawEventYaxis(EventDataType peakEvents,int widest_YAxis);
void drawXaxis(int numberXaxisDivisions , int startGraphBucket , int endGraphBucket);
void drawMetaData(QPoint& last_mouse , QString& topBarLabel,PressureInfo& ipap , PressureInfo& epap ,QHash<ChannelID, bool >& enabled , EventDataType minpressure , EventDataType maxpressure);
EventDataType max_mins;
QMap<EventStoreType, EventDataType> ahis;
}; };
#endif // MINUTESATPRESSURE_H #endif // MINUTESATPRESSURE_H

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>
@ -42,15 +43,6 @@ DreemLoader::Detect(const QString & path)
return false; return false;
} }
int
DreemLoader::Open(const QString & dirpath)
{
qDebug() << "DreemLoader::Open(" << dirpath << ")";
// Dreem currently crams everything into a single file like Zeo did.
// See OpenFile.
return false;
}
int DreemLoader::OpenFile(const QString & filename) int DreemLoader::OpenFile(const QString & filename)
{ {
if (!openCSV(filename)) { if (!openCSV(filename)) {

View File

@ -25,8 +25,9 @@ class DreemLoader : public MachineLoader
virtual bool Detect(const QString & path); virtual bool Detect(const QString & path);
virtual int Open(const QString & path); virtual int Open(const QString & path) { Q_UNUSED(path); return 0; } // Only for CPAP
virtual int OpenFile(const QString & path); virtual int OpenFile(const QString & path);
virtual QStringList getNameFilter() { return QStringList("Dreem CSV File (*.csv)"); }
static void Register(); static void Register();
virtual int Version() { return dreem_data_version; } virtual int Version() { return dreem_data_version; }

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

@ -314,6 +314,7 @@ static const PRS1TestedModel s_PRS1TestedModels[] = {
{ "1030X150", 3, 6, "DreamStation BiPAP S/T 30 with AAM" }, { "1030X150", 3, 6, "DreamStation BiPAP S/T 30 with AAM" },
{ "1130X110", 3, 6, "DreamStation BiPAP AVAPS 30" }, { "1130X110", 3, 6, "DreamStation BiPAP AVAPS 30" },
{ "1131X150", 3, 6, "DreamStation BiPAP AVAPS 30 AE" }, { "1131X150", 3, 6, "DreamStation BiPAP AVAPS 30 AE" },
{ "1130X200", 3, 6, "DreamStation BiPAP AVAPS 30" },
{ "", 0, 0, "" }, { "", 0, 0, "" },
}; };
@ -6213,7 +6214,7 @@ bool PRS1DataChunk::ParseSettingsF3V6(const unsigned char* data, int size)
case 2: // Breath Rate (fixed BPM) case 2: // Breath Rate (fixed BPM)
breath_rate = data[pos+1]; breath_rate = data[pos+1];
timed_inspiration = data[pos+2]; timed_inspiration = data[pos+2];
if (breath_rate < 9 || breath_rate > 13) UNEXPECTED_VALUE(breath_rate, "9-13"); if (breath_rate < 9 || breath_rate > 15) UNEXPECTED_VALUE(breath_rate, "9-15");
if (timed_inspiration < 8 || timed_inspiration > 20) UNEXPECTED_VALUE(timed_inspiration, "8-20"); if (timed_inspiration < 8 || timed_inspiration > 20) UNEXPECTED_VALUE(timed_inspiration, "8-20");
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Fixed)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Fixed));
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_RATE, breath_rate)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_RATE, breath_rate));
@ -6248,17 +6249,22 @@ bool PRS1DataChunk::ParseSettingsF3V6(const unsigned char* data, int size)
// Rise time // Rise time
if (data[pos] < 1 || data[pos] > 6) UNEXPECTED_VALUE(data[pos], "1-6"); // 1-6 have been seen if (data[pos] < 1 || data[pos] > 6) UNEXPECTED_VALUE(data[pos], "1-6"); // 1-6 have been seen
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME, data[pos])); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME, data[pos]));
} else {
UNEXPECTED_VALUE(flexmode, "BiFlex or RiseTime");
} }
// Timed inspiration specified in the backup breath rate. // Timed inspiration specified in the backup breath rate.
break; break;
case 0x2f: // Rise Time lock? (was flex lock on F0V6, 0x80 for locked) case 0x2f: // Flex / Rise Time lock
CHECK_VALUE(len, 1); CHECK_VALUE(len, 1);
if (cpapmode == PRS1_MODE_S) { if (flexmode == FLEX_BiFlex) {
CHECK_VALUE(cpapmode, PRS1_MODE_S);
CHECK_VALUES(data[pos], 0, 0x80); // Bi-Flex Lock CHECK_VALUES(data[pos], 0, 0x80); // Bi-Flex Lock
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LOCK, data[pos] != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LOCK, data[pos] != 0));
} else if (flexmode == FLEX_RiseTime) {
CHECK_VALUES(data[pos], 0, 0x80); // Rise Time Lock
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME_LOCK, data[pos] != 0));
} else { } else {
CHECK_VALUE(data[pos], 0); // Rise Time Lock? not yet observed on F3V6 UNEXPECTED_VALUE(flexmode, "BiFlex or RiseTime");
//this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RISE_TIME_LOCK, data[pos] != 0));
} }
break; break;
case 0x35: // Humidifier setting case 0x35: // Humidifier setting
@ -7147,7 +7153,7 @@ void PRS1DataChunk::ParseHumidifierSettingV3(unsigned char byte1, unsigned char
} else if (humidadaptive) { } else if (humidadaptive) {
// All humidity levels seen. // All humidity levels seen.
} else if (humidfixed) { } else if (humidfixed) {
if (humidlevel == 0) UNEXPECTED_VALUE(humidlevel, "1-5"); // All humidity levels seen.
} }
} }
} }

View File

@ -478,10 +478,6 @@ class PRS1Loader : public CPAPLoader
quint16 size, int family, int familyVersion); quint16 size, int family, int familyVersion);
//! \brief Open a PRS1 data file, and break into data chunks, delivering them to the correct parser.
bool OpenFile(Machine *mach, const QString & filename);
QHash<SessionID, Session *> extra_session; QHash<SessionID, Session *> extra_session;
//! \brief PRS1 Data files can store multiple sessions, so store them in this list for later processing. //! \brief PRS1 Data files can store multiple sessions, so store them in this list for later processing.

View File

@ -251,7 +251,7 @@ void backupSTRfiles( const QString strpath, const QString importPath, const QStr
MachineInfo & info, QMap<QDate, STRFile> & STRmap ); // forward MachineInfo & info, QMap<QDate, STRFile> & STRmap ); // forward
ResMedEDFInfo * fetchSTRandVerify( QString filename, QString serialNumber ); // forward ResMedEDFInfo * fetchSTRandVerify( QString filename, QString serialNumber ); // forward
int ResmedLoader::Open(const QString & dirpath, ResDaySaveCallback s) // alternate for unit testing int ResmedLoader::OpenWithCallback(const QString & dirpath, ResDaySaveCallback s) // alternate for unit testing
{ {
ResDaySaveCallback origCallback = saveCallback; ResDaySaveCallback origCallback = saveCallback;
saveCallback = s; saveCallback = s;
@ -3076,6 +3076,7 @@ void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFInfo &edf, EDFSignal &es,
int startpos = 0; int startpos = 0;
// There's no reason to skip the first 40 seconds of slow data
// if ((code == CPAP_Pressure) || (code == CPAP_IPAP) || (code == CPAP_EPAP)) { // if ((code == CPAP_Pressure) || (code == CPAP_IPAP) || (code == CPAP_EPAP)) {
// startpos = 20; // Shave the first 40 seconds of pressure data // startpos = 20; // Shave the first 40 seconds of pressure data
// tt += rate * startpos; // tt += rate * startpos;

View File

@ -134,7 +134,7 @@ class ResmedLoader : public CPAPLoader
volatile int sessionCount; volatile int sessionCount;
static void SaveSession(ResmedLoader* loader, Session* session); static void SaveSession(ResmedLoader* loader, Session* session);
ResDaySaveCallback saveCallback; ResDaySaveCallback saveCallback;
int Open(const QString & dirpath, ResDaySaveCallback s); int OpenWithCallback(const QString & dirpath, ResDaySaveCallback s);
protected: protected:
//! \brief The STR.edf file is a unique edf file with many signals //! \brief The STR.edf file is a unique edf file with many signals

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>
@ -26,28 +27,6 @@ SomnoposeLoader::SomnoposeLoader()
SomnoposeLoader::~SomnoposeLoader() SomnoposeLoader::~SomnoposeLoader()
{ {
} }
int SomnoposeLoader::Open(const QString & dirpath)
{
QString newpath;
QString dirtag = "somnopose";
QString path(dirpath);
path = path.replace("\\", "/");
if (path.toLower().endsWith("/" + dirtag)) {
return 0;
//newpath=path;
} else {
newpath = path + "/" + dirtag.toUpper();
}
//QString filename;
// Somnopose folder structure detection stuff here.
return 0; // number of machines affected
}
int SomnoposeLoader::OpenFile(const QString & filename) int SomnoposeLoader::OpenFile(const QString & filename)
{ {
@ -56,10 +35,10 @@ int SomnoposeLoader::OpenFile(const QString & filename)
if (filename.toLower().endsWith(".csv")) { if (filename.toLower().endsWith(".csv")) {
if (!file.open(QFile::ReadOnly)) { if (!file.open(QFile::ReadOnly)) {
qDebug() << "Couldn't open Somnopose data file" << filename; qDebug() << "Couldn't open Somnopose data file" << filename;
return 0; return -1;
} }
} else { } else {
return 0; return -1;
} }
qDebug() << "Opening file" << filename; qDebug() << "Opening file" << filename;
@ -106,12 +85,12 @@ int SomnoposeLoader::OpenFile(const QString & filename)
// Check we have all fields available // Check we have all fields available
if (col_timestamp < 0) { if (col_timestamp < 0) {
qDebug() << "Header missing timestamp"; qDebug() << "Header missing timestamp";
return 0; return -1;
} }
if ((col_inclination < 0) && (col_orientation < 0) && (col_movement < 0)) { if ((col_inclination < 0) && (col_orientation < 0) && (col_movement < 0)) {
qDebug() << "Header missing all of inclination, orientation, movement (at least one must be present)"; qDebug() << "Header missing all of inclination, orientation, movement (at least one must be present)";
return 0; return -1;
} }
QDateTime epoch(QDate(2001, 1, 1)); QDateTime epoch(QDate(2001, 1, 1));
@ -168,7 +147,7 @@ int SomnoposeLoader::OpenFile(const QString & filename)
if (mach->SessionExists(sid)) { if (mach->SessionExists(sid)) {
qDebug() << "File " << filename << " already loaded... skipping"; qDebug() << "File " << filename << " already loaded... skipping";
return -1; // Already imported return 0; // Already imported
} }
sess = new Session(mach, sid); sess = new Session(mach, sid);
@ -221,7 +200,7 @@ int SomnoposeLoader::OpenFile(const QString & filename)
p_profile->StoreMachines(); p_profile->StoreMachines();
} }
return true; return 1;
} }

View File

@ -26,8 +26,9 @@ class SomnoposeLoader : public MachineLoader
virtual bool Detect(const QString & path) { Q_UNUSED(path); return false; } // bypass autoscanner virtual bool Detect(const QString & path) { Q_UNUSED(path); return false; } // bypass autoscanner
virtual int Open(const QString & path); virtual int Open(const QString & path) { Q_UNUSED(path); return 0; } // Only for CPAP
virtual int OpenFile(const QString & filename); virtual int OpenFile(const QString & filename);
virtual QStringList getNameFilter() { return QStringList("Somnopose CSV File (*.csv)"); }
static void Register(); static void Register();
virtual int Version() { return somnopose_data_version; } virtual int Version() { return somnopose_data_version; }

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>
@ -33,33 +34,30 @@ ViatomLoader::Detect(const QString & path)
} }
int int
ViatomLoader::Open(const QString & dirpath) ViatomLoader::Open(const QStringList & paths)
{ {
qDebug() << "ViatomLoader::Open(" << dirpath << ")"; qDebug() << "ViatomLoader::Open(" << paths.join("; ") << ")";
m_mach = nullptr; m_mach = nullptr;
int imported = 0; int imported = 0;
int found = 0; int found = 0;
s_unexpectedMessages.clear(); s_unexpectedMessages.clear();
if (QFileInfo(dirpath).isDir()) { int size = paths.size();
QDir dir(dirpath); for (int i=0; i < size; i++) {
dir.setFilter(QDir::NoDotAndDotDot | QDir::Files | QDir::Hidden); if (isAborted()) {
dir.setNameFilters(getNameFilter()); break;
dir.setSorting(QDir::Name);
for (auto & fi : dir.entryInfoList()) {
if (OpenFile(fi.canonicalFilePath())) {
imported++;
}
found++;
} }
}
else {
// This filename has already been filtered by QFileDialog. // This filename has already been filtered by QFileDialog.
if (OpenFile(dirpath)) { int ok = OpenFile(paths[i]);
if (ok > 0) {
imported++; imported++;
} else if (ok < 0) {
// Stop on error...
break;
} }
found++; found++;
emit setProgressValue(i+1);
QCoreApplication::processEvents();
} }
if (!found) { if (!found) {
@ -90,25 +88,30 @@ ViatomLoader::Open(const QString & dirpath)
} }
} }
return imported; return found;
} }
bool ViatomLoader::OpenFile(const QString & filename) int ViatomLoader::OpenFile(const QString & filename)
{ {
Machine* mach = nullptr; Machine* mach = nullptr;
bool existing = false;
Session* sess = ParseFile(filename); Session* sess = ParseFile(filename, &existing);
if (sess) { if (sess) {
SaveSessionToDatabase(sess); SaveSessionToDatabase(sess);
mach = sess->machine(); mach = sess->machine();
m_mach = mach; m_mach = mach;
return 1;
} }
return mach != nullptr; return existing ? 0 : -1; // -1 = error
} }
Session* ViatomLoader::ParseFile(const QString & filename) Session* ViatomLoader::ParseFile(const QString & filename, bool *existing)
{ {
if (existing) {
*existing = false;
}
QFile file(filename); QFile file(filename);
if (!file.open(QFile::ReadOnly)) { if (!file.open(QFile::ReadOnly)) {
qDebug() << "Couldn't open Viatom data file" << filename; qDebug() << "Couldn't open Viatom data file" << filename;
@ -135,6 +138,10 @@ Session* ViatomLoader::ParseFile(const QString & filename)
if (mach->SessionExists(v.sessionid())) { if (mach->SessionExists(v.sessionid())) {
// Skip already imported session // Skip already imported session
//qDebug() << filename << "session already exists, skipping" << v.sessionid(); //qDebug() << filename << "session already exists, skipping" << v.sessionid();
if (existing) {
// Inform the caller (if they are interested) that this session was already imported
*existing = true;
}
return nullptr; return nullptr;
} }

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
@ -28,8 +28,9 @@ class ViatomLoader : public MachineLoader
virtual bool Detect(const QString & path); virtual bool Detect(const QString & path);
virtual int Open(const QString & path); virtual int Open(const QString & path) { Q_UNUSED(path); return 0; } // Only for CPAP
Session* ParseFile(const QString & filename); virtual int Open(const QStringList & paths);
Session* ParseFile(const QString & filename, bool *existing=0);
static void Register(); static void Register();
@ -40,12 +41,12 @@ class ViatomLoader : public MachineLoader
return MachineInfo(MT_OXIMETER, 0, viatom_class_name, QObject::tr("Viatom"), QString(), QString(), QString(), QObject::tr("Viatom Software"), QDateTime::currentDateTime(), viatom_data_version); return MachineInfo(MT_OXIMETER, 0, viatom_class_name, QObject::tr("Viatom"), QString(), QString(), QString(), QObject::tr("Viatom Software"), QDateTime::currentDateTime(), viatom_data_version);
} }
QStringList getNameFilter(); virtual QStringList getNameFilter();
//Machine *CreateMachine(); //Machine *CreateMachine();
protected: protected:
bool OpenFile(const QString & filename); int OpenFile(const QString & filename);
void SaveSessionToDatabase(Session* session); void SaveSessionToDatabase(Session* session);
void AddEvent(ChannelID channel, qint64 t, EventDataType value); void AddEvent(ChannelID channel, qint64 t, EventDataType value);

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>
@ -31,31 +32,6 @@ ZEOLoader::~ZEOLoader()
closeCSV(); closeCSV();
} }
int ZEOLoader::Open(const QString & dirpath)
{
QString newpath;
QString dirtag = "zeo";
// Could Scan the ZEO folder for a list of CSVs
QString path(dirpath);
path = path.replace("\\", "/");
if (path.toLower().endsWith("/" + dirtag)) {
return 0;
//newpath=path;
} else {
newpath = path + "/" + dirtag.toUpper();
}
//QString filename;
// ZEO folder structure detection stuff here.
return 0; // number of machines affected
}
/*15233: "Sleep Date" /*15233: "Sleep Date"
15234: "ZQ" 15234: "ZQ"
15236: "Total Z" 15236: "Total Z"

View File

@ -27,8 +27,9 @@ class ZEOLoader : public MachineLoader
virtual bool Detect(const QString &path) { Q_UNUSED(path); return false; } // bypass autoscanner virtual bool Detect(const QString &path) { Q_UNUSED(path); return false; } // bypass autoscanner
virtual int Open(const QString & path); virtual int Open(const QString & path) { Q_UNUSED(path); return 0; } // Only for CPAP
virtual int OpenFile(const QString & filename); virtual int OpenFile(const QString & filename);
virtual QStringList getNameFilter() { return QStringList("Zeo CSV File (*.csv)"); }
static void Register(); static void Register();
virtual int Version() { return zeo_data_version; } virtual int Version() { return zeo_data_version; }

View File

@ -316,3 +316,26 @@ bool compressFile(QString infile, QString outfile)
return true; return true;
} }
int MachineLoader::Open(const QStringList & paths)
{
int i, skipped = 0;
int size = paths.size();
for (i=0; i < size; i++) {
if (isAborted()) {
break;
}
QString filename = paths[i];
int res = OpenFile(filename);
if (res < 0) {
break;
}
if (res == 0) {
// Should we report on skipped count?
skipped++;
}
emit setProgressValue(i+1);
QCoreApplication::processEvents();
}
return i;
}

View File

@ -56,9 +56,18 @@ class MachineLoader: public QObject
//! \brief Override this to scan path and detect new machine data //! \brief Override this to scan path and detect new machine data
virtual int Open(const QString & path) = 0; virtual int Open(const QString & path) = 0;
//! \brief Load all of the given files and update dialog with progress (for non-CPAP devices)
virtual int Open(const QStringList & paths);
//! \brief Load a specific (non-CPAP) file
virtual int OpenFile(const QString & path) { Q_UNUSED(path); return 0; }
//! \brief Override to returns the Version number of this MachineLoader //! \brief Override to returns the Version number of this MachineLoader
virtual int Version() = 0; virtual int Version() = 0;
//! \brief Name filter for files for this loader
virtual QStringList getNameFilter() { return QStringList(""); }
// !\\brief Used internally by loaders, override to return base MachineInfo record // !\\brief Used internally by loaders, override to return base MachineInfo record
virtual MachineInfo newInfo() { return MachineInfo(); } virtual MachineInfo newInfo() { return MachineInfo(); }

View File

@ -369,6 +369,8 @@ const QString STR_US_PrefCalcMax = "PrefCalcMax";
const QString STR_US_ShowUnknownFlags = "ShowUnknownFlags"; const QString STR_US_ShowUnknownFlags = "ShowUnknownFlags";
const QString STR_US_StatReportMode = "StatReportMode"; const QString STR_US_StatReportMode = "StatReportMode";
const QString STR_US_LastOverviewRange = "LastOverviewRange"; const QString STR_US_LastOverviewRange = "LastOverviewRange";
const QString STR_US_CustomOverviewRangeStart = "CustomOverviewRangeStart";
const QString STR_US_CustomOverviewRangeEnd = "CustomOverviewRangeEnd";
// Values for StatReportMode // Values for StatReportMode
const int STAT_MODE_STANDARD = 0; const int STAT_MODE_STANDARD = 0;
@ -742,6 +744,8 @@ class UserSettings : public PrefSettings
int statReportMode() const { return getPref(STR_US_StatReportMode).toInt(); } int statReportMode() const { return getPref(STR_US_StatReportMode).toInt(); }
inline bool showUnknownFlags() const { return m_showUnownFlags; } inline bool showUnknownFlags() const { return m_showUnownFlags; }
int lastOverviewRange() const { return getPref(STR_US_LastOverviewRange).toInt(); } int lastOverviewRange() const { return getPref(STR_US_LastOverviewRange).toInt(); }
QDate customOverviewRangeStart () const { return getPref(STR_US_CustomOverviewRangeStart).toDate(); }
QDate customOverviewRangeEnd () const { return getPref(STR_US_CustomOverviewRangeEnd).toDate(); }
void setUnitSystem(UnitSystem us) { setPref(STR_US_UnitSystem, (int)us); } void setUnitSystem(UnitSystem us) { setPref(STR_US_UnitSystem, (int)us); }
void setEventWindowSize(double size) { setPref(STR_US_EventWindowSize, size); } void setEventWindowSize(double size) { setPref(STR_US_EventWindowSize, size); }
@ -754,6 +758,8 @@ class UserSettings : public PrefSettings
void setStatReportMode(int i) { setPref(STR_US_StatReportMode, i); } void setStatReportMode(int i) { setPref(STR_US_StatReportMode, i); }
void setShowUnknownFlags(bool b) { setPref(STR_US_ShowUnknownFlags, m_showUnownFlags=b); } void setShowUnknownFlags(bool b) { setPref(STR_US_ShowUnknownFlags, m_showUnownFlags=b); }
void setLastOverviewRange(int i) { setPref(STR_US_LastOverviewRange, i); } void setLastOverviewRange(int i) { setPref(STR_US_LastOverviewRange, i); }
void setCustomOverviewRangeStart(QDate i) { setPref(STR_US_CustomOverviewRangeStart, i); }
void setCustomOverviewRangeEnd(QDate i) { setPref(STR_US_CustomOverviewRangeEnd, i); }
bool m_calculateRDI, m_showUnownFlags, m_skipEmptyDays; bool m_calculateRDI, m_showUnownFlags, m_skipEmptyDays;
int m_prefCalcMiddle, m_prefCalcMax; int m_prefCalcMiddle, m_prefCalcMax;

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);
void updateCube(); void updateCube();

View File

@ -248,14 +248,13 @@ void ExportCSV::on_exportButton_clicked()
data += sep + QString::number(day->size(), 10); data += sep + QString::number(day->size(), 10);
data += sep + start.toString(Qt::ISODate); data += sep + start.toString(Qt::ISODate);
data += sep + end.toString(Qt::ISODate); data += sep + end.toString(Qt::ISODate);
int time = day->total_time() / 1000L; // Given this is a CPAP specific report, just report CPAP hours
int time = int(day->hours(MT_CPAP) * 3600L);
int h = time / 3600; int h = time / 3600;
int m = int(time / 60) % 60; int m = int(time / 60) % 60;
int s = int(time) % 60; int s = int(time) % 60;
data += sep + QString().sprintf("%02i:%02i:%02i", h, m, s); data += sep + QString().sprintf("%02i:%02i:%02i", h, m, s);
float ahi = day->count(CPAP_Obstructive) + day->count(CPAP_Hypopnea) + day->count( float ahi = day->calcAHI();
CPAP_Apnea) + day->count(CPAP_ClearAirway);
ahi /= day->hours();
data += sep + QString::number(ahi, 'f', 3); data += sep + QString::number(ahi, 'f', 3);
for (int i = 0; i < countlist.size(); i++) { for (int i = 0; i < countlist.size(); i++) {

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();
@ -2261,64 +2311,14 @@ void MainWindow::doReprocessEvents()
void MainWindow::on_actionImport_ZEO_Data_triggered() void MainWindow::on_actionImport_ZEO_Data_triggered()
{ {
QFileDialog w;
w.setFileMode(QFileDialog::ExistingFiles);
w.setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
w.setOption(QFileDialog::ShowDirsOnly, false);
w.setNameFilters(QStringList("Zeo CSV File (*.csv)"));
ZEOLoader zeo; ZEOLoader zeo;
importNonCPAP(zeo);
if (w.exec() == QFileDialog::Accepted) {
QString filename = w.selectedFiles()[0];
qDebug() << "Loading ZEO data from" << filename;
int c = zeo.OpenFile(filename);
if (c > 0) {
Notify(tr("Imported %1 ZEO session(s) from\n\n%2").arg(c).arg(filename), tr("Import Success"));
qDebug() << "Imported" << c << "ZEO sessions";
PopulatePurgeMenu();
if (overview) overview->ReloadGraphs();
if (welcome) welcome->refreshPage();
} else if (c == 0) {
Notify(tr("Already up to date with ZEO data at\n\n%1").arg(filename), tr("Up to date"));
} else {
Notify(tr("Couldn't find any valid ZEO CSV data at\n\n%1").arg(filename),tr("Import Problem"));
}
daily->LoadDate(daily->getDate());
}
} }
void MainWindow::on_actionImport_Dreem_Data_triggered() void MainWindow::on_actionImport_Dreem_Data_triggered()
{ {
QFileDialog w;
w.setFileMode(QFileDialog::ExistingFiles);
w.setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
w.setOption(QFileDialog::ShowDirsOnly, false);
w.setNameFilters(QStringList("Dreem CSV File (*.csv)"));
DreemLoader dreem; DreemLoader dreem;
importNonCPAP(dreem);
if (w.exec() == QFileDialog::Accepted) {
QString filename = w.selectedFiles()[0];
qDebug() << "Loading Dreem data from" << filename;
int c = dreem.OpenFile(filename);
if (c > 0) {
Notify(tr("Imported %1 Dreem session(s) from\n\n%2").arg(c).arg(filename), tr("Import Success"));
qDebug() << "Imported" << c << "Dreem sessions";
PopulatePurgeMenu();
if (overview) overview->ReloadGraphs();
if (welcome) welcome->refreshPage();
} else if (c == 0) {
Notify(tr("Already up to date with Dreem data at\n\n%1").arg(filename), tr("Up to date"));
} else {
Notify(tr("Couldn't find any valid Dreem CSV data at\n\n%1").arg(filename),tr("Import Problem"));
}
daily->LoadDate(daily->getDate());
}
} }
void MainWindow::on_actionImport_RemStar_MSeries_Data_triggered() void MainWindow::on_actionImport_RemStar_MSeries_Data_triggered()
@ -2384,102 +2384,70 @@ void MainWindow::on_actionChange_Data_Folder_triggered()
RestartApplication(false, "-d"); RestartApplication(false, "-d");
} }
void MainWindow::on_actionImport_Somnopose_Data_triggered() void MainWindow::importNonCPAP(MachineLoader &loader)
{ {
QFileDialog w; QFileDialog w;
w.setFileMode(QFileDialog::ExistingFiles); w.setFileMode(QFileDialog::ExistingFiles);
w.setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); w.setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
w.setOption(QFileDialog::ShowDirsOnly, false); w.setOption(QFileDialog::ShowDirsOnly, false);
#if defined(Q_OS_WIN)
// Windows can't handle Viatom name filter - use non-native for all non-CPAP loaders.
w.setOption(QFileDialog::DontUseNativeDialog, true); w.setOption(QFileDialog::DontUseNativeDialog, true);
w.setNameFilters(QStringList("Somnopause CSV File (*.csv)")); #endif
w.setNameFilters(loader.getNameFilter());
SomnoposeLoader somno;
// Display progress if we have more than 1 file to load... // Display progress if we have more than 1 file to load...
ProgressDialog progress(this); ProgressDialog progress(this);
if (w.exec() == QFileDialog::Accepted) { if (w.exec() == QFileDialog::Accepted) {
int i, skipped = 0; QStringList files = w.selectedFiles();
int size = w.selectedFiles().size(); int size = files.size();
if (size > 1) { if (size > 1) {
progress.setMessage(QObject::tr("Importing Sessions...")); progress.setMessage(QObject::tr("Importing Sessions..."));
progress.setProgressMax(size); progress.setProgressMax(size);
progress.setProgressValue(0); progress.setProgressValue(0);
progress.addAbortButton();
progress.setWindowModality(Qt::ApplicationModal); progress.setWindowModality(Qt::ApplicationModal);
connect(&loader, SIGNAL(setProgressValue(int)), &progress, SLOT(setProgressValue(int)));
connect(&progress, SIGNAL(abortClicked()), &loader, SLOT(abortImport()));
progress.open(); progress.open();
QCoreApplication::processEvents(); QCoreApplication::processEvents();
} }
for (i=0; i < size; i++) { QString name = loader.loaderName();
QString filename = w.selectedFiles()[i]; int res = loader.Open(files);
if (size > 1) {
int res = somno.OpenFile(filename); disconnect(&loader, SIGNAL(setProgressValue(int)), &progress, SLOT(setProgressValue(int)));
if (!res) { disconnect(&progress, SIGNAL(abortClicked()), &loader, SLOT(abortImport()));
if (i == 0) { progress.close();
Notify(tr("There was a problem opening Somnopose Data File: ") + filename);
return;
} else {
Notify(tr("Somnopause Data Import of %1 file(s) complete").arg(i) + "\n\n" +
tr("There was a problem opening Somnopose Data File: ") + filename,
tr("Somnopose Import Partial Success"));
break;
}
}
if (res < 0) {
// Should we report on skipped count?
skipped++;
}
progress.setProgressValue(i+1);
QCoreApplication::processEvents(); QCoreApplication::processEvents();
} }
if (res == 0) {
if (i == size) { Notify(tr("There was a problem opening %1 Data File: %2").arg(name, files[0]));
Notify(tr("Somnopause Data Import complete")); return;
} else if (res < size){
Notify(tr("%1 Data Import of %2 file(s) complete").arg(name).arg(res) + "\n\n" +
tr("There was a problem opening %1 Data File: %2").arg(name, files[res]),
tr("%1 Import Partial Success").arg(name));
} else {
Notify(tr("%1 Data Import complete").arg(name));
} }
PopulatePurgeMenu(); PopulatePurgeMenu();
if (overview) overview->ReloadGraphs(); if (overview) overview->ReloadGraphs();
if (welcome) welcome->refreshPage(); if (welcome) welcome->refreshPage();
daily->LoadDate(daily->getDate()); daily->LoadDate(daily->getDate());
} }
}
void MainWindow::on_actionImport_Somnopose_Data_triggered()
{
SomnoposeLoader somno;
importNonCPAP(somno);
} }
void MainWindow::on_actionImport_Viatom_Data_triggered() void MainWindow::on_actionImport_Viatom_Data_triggered()
{ {
ViatomLoader viatom; ViatomLoader viatom;
importNonCPAP(viatom);
QFileDialog w;
w.setFileMode(QFileDialog::AnyFile);
w.setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
w.setOption(QFileDialog::ShowDirsOnly, false);
w.setNameFilters(viatom.getNameFilter());
#if defined(Q_OS_WIN)
// Windows can't handle this name filter.
w.setOption(QFileDialog::DontUseNativeDialog, true);
// And since the non-native dialog can't select both directories and files,
// it needs the following to enable selecting multiple files.
w.setFileMode(QFileDialog::ExistingFiles);
#endif
if (w.exec() == QFileDialog::Accepted) {
QString filename = w.selectedFiles()[0];
if (w.selectedFiles().size() > 1) {
// The user selected multiple files in a directory, so use the parent directory as the filename.
filename = QFileInfo(filename).absoluteDir().canonicalPath();
}
int c = viatom.Open(filename);
if (c > 0) {
Notify(tr("Imported %1 oximetry session(s) from\n\n%2").arg(c).arg(filename), tr("Import Success"));
PopulatePurgeMenu();
if (overview) overview->ReloadGraphs();
if (welcome) welcome->refreshPage();
} else if (c == 0) {
Notify(tr("Already up to date with oximetry data at\n\n%1").arg(filename), tr("Up to date"));
} else {
Notify(tr("Couldn't find any valid data at\n\n%1").arg(filename),tr("Import Problem"));
}
daily->LoadDate(daily->getDate());
}
} }
void MainWindow::GenerateStatistics() void MainWindow::GenerateStatistics()

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,8 @@ 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);
void importNonCPAP(MachineLoader &loader);
// 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

@ -151,11 +151,6 @@ Overview::~Overview()
disconnect(ui->dateEnd->calendarWidget(), SIGNAL(currentPageChanged(int, int)), this, SLOT(dateEnd_currentPageChanged(int, int))); disconnect(ui->dateEnd->calendarWidget(), SIGNAL(currentPageChanged(int, int)), this, SLOT(dateEnd_currentPageChanged(int, int)));
disconnect(ui->dateStart->calendarWidget(), SIGNAL(currentPageChanged(int, int)), this, SLOT(dateStart_currentPageChanged(int, int))); disconnect(ui->dateStart->calendarWidget(), SIGNAL(currentPageChanged(int, int)), this, SLOT(dateStart_currentPageChanged(int, int)));
// Don't save custom date range. Default to last 3 months
if (p_profile->general->lastOverviewRange() == 8) {
p_profile->general->setLastOverviewRange(4);
}
// Save graph orders and pin status, etc... // Save graph orders and pin status, etc...
GraphView->SaveSettings("Overview");//no trans GraphView->SaveSettings("Overview");//no trans
@ -236,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
@ -325,9 +332,6 @@ void Overview::updateGraphCombo()
{ {
ui->graphCombo->clear(); ui->graphCombo->clear();
gGraph *g; gGraph *g;
// ui->graphCombo->addItem("Show All Graphs");
// ui->graphCombo->addItem("Hide All Graphs");
// ui->graphCombo->addItem("---------------");
for (int i = 0; i < GraphView->size(); i++) { for (int i = 0; i < GraphView->size(); i++) {
g = (*GraphView)[i]; g = (*GraphView)[i];
@ -345,6 +349,7 @@ void Overview::updateGraphCombo()
updateCube(); updateCube();
} }
#if 0
void Overview::ResetGraphs() void Overview::ResetGraphs()
{ {
QDate start = ui->dateStart->date(); QDate start = ui->dateStart->date();
@ -366,6 +371,7 @@ void Overview::ResetGraph(QString name)
g->setDay(nullptr); g->setDay(nullptr);
GraphView->redraw(); GraphView->redraw();
} }
#endif
void Overview::RedrawGraphs() void Overview::RedrawGraphs()
{ {
@ -430,6 +436,9 @@ void Overview::on_dateEnd_dateChanged(const QDate &date)
qint64 d2 = qint64(QDateTime(date, QTime(23, 0, 0)/*, Qt::UTC*/).toTime_t()) * 1000L; qint64 d2 = qint64(QDateTime(date, QTime(23, 0, 0)/*, Qt::UTC*/).toTime_t()) * 1000L;
GraphView->SetXBounds(d1, d2); GraphView->SetXBounds(d1, d2);
ui->dateStart->setMaximumDate(date); ui->dateStart->setMaximumDate(date);
if (customMode) {
p_profile->general->setCustomOverviewRangeEnd(date);
}
} }
void Overview::on_dateStart_dateChanged(const QDate &date) void Overview::on_dateStart_dateChanged(const QDate &date)
@ -438,6 +447,10 @@ void Overview::on_dateStart_dateChanged(const QDate &date)
qint64 d2 = qint64(QDateTime(ui->dateEnd->date(), QTime(23, 0, 0)/*, Qt::UTC*/).toTime_t()) * 1000L; qint64 d2 = qint64(QDateTime(ui->dateEnd->date(), QTime(23, 0, 0)/*, Qt::UTC*/).toTime_t()) * 1000L;
GraphView->SetXBounds(d1, d2); GraphView->SetXBounds(d1, d2);
ui->dateEnd->setMinimumDate(date); ui->dateEnd->setMinimumDate(date);
if (customMode) {
p_profile->general->setCustomOverviewRangeStart(date);
}
} }
// Zoom to 100% button clicked or called back from 100% zoom in popup menu // Zoom to 100% button clicked or called back from 100% zoom in popup menu
@ -463,7 +476,6 @@ void Overview::ResetGraphOrder(int type)
// Process new range selection from combo button // Process new range selection from combo button
void Overview::on_rangeCombo_activated(int index) void Overview::on_rangeCombo_activated(int index)
{ {
p_profile->general->setLastOverviewRange(index); // type of range in last use
ui->dateStart->setMinimumDate(p_profile->FirstDay()); // first and last dates for ANY machine type ui->dateStart->setMinimumDate(p_profile->FirstDay()); // first and last dates for ANY machine type
ui->dateEnd->setMaximumDate(p_profile->LastDay()); ui->dateEnd->setMaximumDate(p_profile->LastDay());
@ -474,22 +486,6 @@ void Overview::on_rangeCombo_activated(int index)
end = max(end, p_profile->LastDay(MT_SLEEPSTAGE)); end = max(end, p_profile->LastDay(MT_SLEEPSTAGE));
QDate start; QDate start;
if (index == 8) { // Custom
ui->dateStartLabel->setEnabled(true);
ui->dateEndLabel->setEnabled(true);
ui->dateEnd->setEnabled(true);
ui->dateStart->setEnabled(true);
ui->dateStart->setMaximumDate(ui->dateEnd->date());
ui->dateEnd->setMinimumDate(ui->dateStart->date());
p_profile->general->setLastOverviewRange(8);
return;
}
ui->dateEnd->setEnabled(false);
ui->dateStart->setEnabled(false);
ui->dateStartLabel->setEnabled(false);
ui->dateEndLabel->setEnabled(false);
if (index == 0) { if (index == 0) {
start = end.addDays(-6); start = end.addDays(-6);
@ -507,10 +503,48 @@ void Overview::on_rangeCombo_activated(int index)
start = end.addYears(-1).addDays(1); start = end.addYears(-1).addDays(1);
} else if (index == 7) { // Everything } else if (index == 7) { // Everything
start = p_profile->FirstDay(); start = p_profile->FirstDay();
} else if (index == 8 || index == 9) { // Custom
// Validate save Overview Custom Range for first access.
if (!p_profile->general->customOverviewRangeStart().isValid()
|| (!p_profile->general->customOverviewRangeEnd().isValid() )
|| (index==9 /* New Coustom mode - to reset custom range to displayed date range*/)
) {
// Reset Custom Range to current range displayed
// on first initialization of this version of OSCAR
// or on new custom Mode to reset range.
qint64 istart,iend;
GraphView->GetXBounds(istart , iend);
start = QDateTime::fromMSecsSinceEpoch( istart ).date();
end = QDateTime::fromMSecsSinceEpoch( iend ).date();
p_profile->general->setCustomOverviewRangeStart(start);
p_profile->general->setCustomOverviewRangeEnd(end);
index=8;
ui->rangeCombo->setCurrentIndex(index);
} else if (customMode) { // last mode was custom.
// Reset Custom Range to current range in calendar widget
// Custom mode MUST be initialized to false when the Custom Instance is created.
start = ui->dateStart->date();
end = ui->dateEnd->date();
p_profile->general->setCustomOverviewRangeStart(start);
p_profile->general->setCustomOverviewRangeEnd(end);
} else {
// have a change in RangeCombo selection. Use last saved values.
start = p_profile->general->customOverviewRangeStart() ;
end = p_profile->general->customOverviewRangeEnd() ;
}
} }
if (start < p_profile->FirstDay()) { start = p_profile->FirstDay(); } if (start < p_profile->FirstDay()) { start = p_profile->FirstDay(); }
customMode = (index == 8) ;
ui->dateStartLabel->setEnabled(customMode);
ui->dateEndLabel->setEnabled(customMode);
ui->dateEnd->setEnabled(customMode);
ui->dateStart->setEnabled(customMode);
p_profile->general->setLastOverviewRange(index); // type of range in last use
// Ensure that all summary files are available and update version numbers if required // Ensure that all summary files are available and update version numbers if required
int size = start.daysTo(end); int size = start.daysTo(end);
qDebug() << "Overview range combo from" << start << "to" << end << "with" << size << "days"; qDebug() << "Overview range combo from" << start << "to" << end << "with" << size << "days";

View File

@ -50,7 +50,7 @@ class Overview : public QWidget
void ResetFont(); void ResetFont();
//! \brief Recalculates Overview chart info, but keeps the date set //! \brief Recalculates Overview chart info, but keeps the date set
void ResetGraphs(); //void ResetGraphs();
//! \brief Reset graphs to uniform heights //! \brief Reset graphs to uniform heights
void ResetGraphLayout(); void ResetGraphLayout();
@ -80,7 +80,7 @@ class Overview : public QWidget
//! \brief List of SummaryCharts shown on the overview page //! \brief List of SummaryCharts shown on the overview page
QVector<SummaryChart *> OverviewCharts; QVector<SummaryChart *> OverviewCharts;
void ResetGraph(QString name); //void ResetGraph(QString name);
void RebuildGraphs(bool reset = true); void RebuildGraphs(bool reset = true);
@ -128,6 +128,7 @@ class Overview : public QWidget
QIcon *icon_on; QIcon *icon_on;
QIcon *icon_off; QIcon *icon_off;
MyLabel *dateLabel; MyLabel *dateLabel;
bool customMode=false;
//! \brief Updates the calendar highlighting for the calendar object for this date. //! \brief Updates the calendar highlighting for the calendar object for this date.
void UpdateCalendarDay(QDateEdit *calendar, QDate date); void UpdateCalendarDay(QDateEdit *calendar, QDate date);

View File

@ -122,6 +122,11 @@
<string>Custom</string> <string>Custom</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>Snapshot</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item> <item>

View File

@ -57,7 +57,7 @@ static void parseAndEmitSessionYaml(const QString & path)
// necessary for testing. Both are used for now in order to introduce the minimal // necessary for testing. Both are used for now in order to introduce the minimal
// set of changes into the Resmed loader needed for testing. // set of changes into the Resmed loader needed for testing.
s_currentPath = path; s_currentPath = path;
s_loader->Open(path, emitSessionYaml); s_loader->OpenWithCallback(path, emitSessionYaml);
} }
void ResmedTests::testSessionsToYaml() void ResmedTests::testSessionsToYaml()