diff --git a/sleepyhead/Graphs/gGraph.cpp b/sleepyhead/Graphs/gGraph.cpp
index 3dbc9716..08b381d6 100644
--- a/sleepyhead/Graphs/gGraph.cpp
+++ b/sleepyhead/Graphs/gGraph.cpp
@@ -676,6 +676,15 @@ void gGraph::AddLayer(Layer *l, LayerPosition position, short width, short heigh
l->addref();
m_layers.push_back(l);
}
+
+void gGraph::dataChanged()
+{
+ int size = m_layers.size();
+ for (int i=0; i < size; i++) {
+ m_layers[i]->dataChanged();
+ }
+}
+
void gGraph::redraw()
{
m_graphview->redraw();
diff --git a/sleepyhead/Graphs/gGraph.h b/sleepyhead/Graphs/gGraph.h
index cf08ab7d..3bf46e0f 100644
--- a/sleepyhead/Graphs/gGraph.h
+++ b/sleepyhead/Graphs/gGraph.h
@@ -265,6 +265,8 @@ class gGraph : public QObject
double screenToTime(int xpos);
+ void dataChanged();
+
//! \brief Sets the margins for the four sides of this graph.
void setMargins(short left, short right, short top, short bottom) {
m_marginleft = left;
diff --git a/sleepyhead/Graphs/gGraphView.cpp b/sleepyhead/Graphs/gGraphView.cpp
index d3a680f1..3012e965 100644
--- a/sleepyhead/Graphs/gGraphView.cpp
+++ b/sleepyhead/Graphs/gGraphView.cpp
@@ -3365,6 +3365,14 @@ int gGraphView::visibleGraphs()
return cnt;
}
+void gGraphView::dataChanged()
+{
+ for (int i = 0; i < m_graphs.size(); i++) {
+ m_graphs[i]->dataChanged();
+ }
+}
+
+
void gGraphView::redraw()
{
#ifdef BROKEN_OPENGL_BUILD
diff --git a/sleepyhead/Graphs/gGraphView.h b/sleepyhead/Graphs/gGraphView.h
index cec6108c..d0c4c72f 100644
--- a/sleepyhead/Graphs/gGraphView.h
+++ b/sleepyhead/Graphs/gGraphView.h
@@ -676,6 +676,8 @@ class gGraphView
ResetBounds(true);
}
+ void dataChanged();
+
bool hasSnapshots();
diff --git a/sleepyhead/Graphs/gSessionTimesChart.cpp b/sleepyhead/Graphs/gSessionTimesChart.cpp
index 3677c3aa..9f499979 100644
--- a/sleepyhead/Graphs/gSessionTimesChart.cpp
+++ b/sleepyhead/Graphs/gSessionTimesChart.cpp
@@ -154,10 +154,7 @@ void gSummaryChart::preCalc()
{
for (int i=0; i & slices)
const SummaryChartSlice & slice = slices.at(i);
SummaryCalcItem & calc = calcitems[i];
- calc.min = qMin(calc.min, slice.value);
- calc.max = qMax(calc.max, slice.value);
-
- switch (calc.type) {
- case ST_CPH:
- case ST_SPH:
- calc.sum += slice.value * hour;
- calc.divisor += hour;
- break;
- default:
- calc.sum += slice.value;
- calc.divisor += 1;
- break;
-
- }
- }
+ calc.update(slice.value, hour);
+ }
}
void gSummaryChart::afterDraw(QPainter &painter, gGraph &graph, QRect rect)
@@ -198,37 +181,54 @@ void gSummaryChart::afterDraw(QPainter &painter, gGraph &graph, QRect rect)
QStringList strlist;
QString txt;
- int mid = p_profile->general->prefCalcMiddle();
+ int midcalc = p_profile->general->prefCalcMiddle();
QString midstr;
- if (mid == 0) {
+ if (midcalc == 0) {
midstr = QObject::tr("Med.");
- } else if (mid == 1) {
- midstr = QObject::tr("Avg");
- } else {
+ } else if (midcalc == 1) {
midstr = QObject::tr("W-Avg");
+ } else {
+ midstr = QObject::tr("Avg");
}
+
+
float perc = p_profile->general->prefCalcPercentile();
QString percstr = QObject::tr("%1%").arg(perc, 0, 'f',0);
schema::Channel & chan = schema::channel[calcitems.at(0).code];
for (int i=0; i 0) {
+ mid = median(calc.median_data.begin(), calc.median_data.end());
+ }
+ break;
+ case 1:
+ mid = calc.wavg_sum / calc.divisor;
+ break;
+ case 2:
+ mid = calc.avg_sum / calc.cnt;
+ break;
+ }
+
float val = 0;
switch (calc.type) {
case ST_CPH:
- val = calc.sum / calc.divisor;
- txt = QObject::tr("Avg: ");
+ val = mid;
+ txt = midstr+": ";
break;
case ST_SPH:
- val = calc.sum / calc.divisor;
- txt = QObject::tr("Avg: ");
+ val = mid;
+ txt = midstr+": ";
break;
case ST_MIN:
val = calc.min;
@@ -251,15 +251,15 @@ void gSummaryChart::afterDraw(QPainter &painter, gGraph &graph, QRect rect)
txt = QObject::tr("Max: ");
break;
case ST_MID:
- val = calc.sum / calc.divisor;
+ val = mid;
txt = QObject::tr("%1: ").arg(midstr);
break;
case ST_90P:
- val = calc.sum / calc.divisor;
+ val = mid;
txt = QObject::tr("%1: ").arg(percstr);
break;
default:
- val = calc.sum / calc.divisor;
+ val = mid;
txt = QObject::tr("???: ");
break;
}
@@ -421,7 +421,7 @@ void gSummaryChart::paint(QPainter &painter, gGraph &graph, const QRegion ®io
nousedays = 0;
totaldays = 0;
- QRectF hl_rect, hl2_rect;
+ QRectF hl_rect;
QDate hl_date;
Day * hl_day = nullptr;
int hl_idx = -1;
@@ -555,7 +555,6 @@ void gSummaryChart::paint(QPainter &painter, gGraph &graph, const QRegion ®io
for (int i=0; i < listsize; ++i) {
SummaryChartSlice & slice = list[i];
- SummaryCalcItem * calc = slice.calc;
val = slice.height;
y1 = ((lastval-miny) * ymult);
@@ -654,20 +653,24 @@ void gUsageChart::preCalc()
{
compliance_threshold = p_profile->cpap->complianceHours();
incompdays = 0;
- totalhours = 0;
- totaldays = 0;
- hour_data.clear();
- hour_data.reserve(idx_end-idx_start);
+ SummaryCalcItem & calc = calcitems[0];
+ calc.reset(idx_end - idx_start);
}
void gUsageChart::customCalc(Day *, QList &list)
{
+ if (list.size() == 0) {
+ incompdays++;
+ return;
+ }
+
SummaryChartSlice & slice = list[0];
+ SummaryCalcItem & calc = calcitems[0];
+
if (slice.value < compliance_threshold) incompdays++;
- totalhours += slice.value;
- hour_data.append(slice.value);
- totaldays++;
+
+ calc.update(slice.value, 1);
}
void gUsageChart::afterDraw(QPainter &, gGraph &graph, QRect rect)
@@ -676,30 +679,106 @@ void gUsageChart::afterDraw(QPainter &, gGraph &graph, QRect rect)
if (totaldays > 1) {
float comp = 100.0 - ((float(incompdays + nousedays) / float(totaldays)) * 100.0);
- double avg = totalhours / double(totaldays);
- float med = median(hour_data.begin(), hour_data.end());
- QString txt = QObject::tr("%1 low usage, %2 no usage, out of %3 days (%4% compliant.) Avg %5, Med %6 hours").
- arg(incompdays).arg(nousedays).arg(totaldays).arg(comp,0,'f',1).arg(avg, 0, 'f', 2).arg(med, 0, 'f', 2);
+ int midcalc = p_profile->general->prefCalcMiddle();
+ float mid = 0;
+ SummaryCalcItem & calc = calcitems[0];
+ switch (midcalc) {
+ case 0: // median
+ mid = median(calc.median_data.begin(), calc.median_data.end());
+ break;
+ case 1: // w-avg
+ mid = calc.wavg_sum / calc.divisor;
+ break;
+ case 2:
+ mid = calc.avg_sum / calc.cnt;
+ break;
+ }
+
+ QString txt = QObject::tr("%1 low usage, %2 no usage, out of %3 days (%4% compliant.) Length: %5 / %6 / %7").
+ arg(incompdays).arg(nousedays).arg(totaldays).arg(comp,0,'f',1).arg(calc.min, 0, 'f', 2).arg(mid, 0, 'f', 2).arg(calc.max, 0, 'f', 2);;
graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0);
}
}
+void gSessionTimesChart::preCalc() {
+ num_slices = 0;
+ num_days = 0;
+ total_length = 0;
+ SummaryCalcItem & calc = calcitems[0];
+
+ calc.reset((idx_end - idx_start) * 6);
+
+ SummaryCalcItem & calc1 = calcitems[1];
+
+ calc1.reset(idx_end - idx_start);
+
+ SummaryCalcItem & calc2 = calcitems[2];
+ calc2.reset(idx_end - idx_start);
+}
+
+void gSessionTimesChart::customCalc(Day *, QList & slices) {
+ int size = slices.size();
+ num_slices += size;
+
+ SummaryCalcItem & calc1 = calcitems[1];
+ calc1.update(slices.size(), 1);
+
+ EventDataType max = 0;
+ for (int i=0; i 0)
- med = median(session_data.begin(), session_data.end());
+ SummaryCalcItem & calc = calcitems[0]; // session length
+ SummaryCalcItem & calc1 = calcitems[1]; // number of sessions
+ SummaryCalcItem & calc2 = calcitems[2]; // number of sessions
+
+ int midcalc = p_profile->general->prefCalcMiddle();
+
+ float mid = 0, mid1 = 0, midlongest = 0;
+ switch (midcalc) {
+ case 0:
+ if (calc.median_data.size() > 0) {
+ mid = median(calc.median_data.begin(), calc.median_data.end());
+ mid1 = median(calc1.median_data.begin(), calc1.median_data.end());
+ midlongest = median(calc2.median_data.begin(), calc2.median_data.end());
+ }
+ break;
+ case 1:
+ mid = calc.wavg_sum / calc.divisor;
+ mid1 = calc1.wavg_sum / calc1.divisor;
+ midlongest = calc2.wavg_sum / calc2.divisor;
+ break;
+ case 2:
+ mid = calc.avg_sum / calc.cnt;
+ mid1 = calc1.avg_sum / calc1.cnt;
+ midlongest = calc2.avg_sum / calc2.cnt;
+ break;
+ }
- float avgsess = float(num_slices) / float(num_days);
- double avglength = total_length / double(num_slices);
+// float avgsess = float(num_slices) / float(num_days);
- QString txt = QObject::tr("Avg Sessions: %1 Length Avg: %2 Med %3").arg(avgsess, 0, 'f', 1).arg(avglength, 0, 'f', 2).arg(med, 0, 'f', 2);
+ QString txt = QObject::tr("Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9")
+ .arg(calc1.min, 0, 'f', 2).arg(mid1, 0, 'f', 2).arg(calc1.max, 0, 'f', 2)
+ .arg(calc.min, 0, 'f', 2).arg(mid, 0, 'f', 2).arg(calc.max, 0, 'f', 2)
+ .arg(calc2.min, 0, 'f', 2).arg(midlongest, 0, 'f', 2).arg(calc2.max, 0, 'f', 2);
graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0);
}
@@ -844,6 +923,8 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion &
preCalc();
+ totaldays = 0;
+ nousedays = 0;
/////////////////////////////////////////////////////////////////////
/// Main Loop scaling
@@ -933,7 +1014,8 @@ void gAHIChart::preCalc()
{
gSummaryChart::preCalc();
- ahi_total = 0;
+ ahi_wavg = 0;
+ ahi_avg = 0;
calc_cnt = 0;
total_hours = 0;
min_ahi = 99999;
@@ -954,11 +1036,18 @@ void gAHIChart::customCalc(Day *day, QList &list)
EventDataType value = slice.value;
- calc->sum += value;
+ calc->wavg_sum += value;
calc->divisor += hours;
- calc->min = qMin(value / hours, calc->min);
- calc->max = qMax(value / hours, calc->max);
+ calc->avg_sum += value;
+ calc->cnt++;
+
+ float valh = value/ hours;
+
+ calc->median_data.append(valh);
+
+ calc->min = qMin(valh, calc->min);
+ calc->max = qMax(valh, calc->max);
ahi_cnt += value;
}
@@ -967,7 +1056,8 @@ void gAHIChart::customCalc(Day *day, QList &list)
ahi_data.append(ahi_cnt / hours);
- ahi_total += ahi_cnt;
+ ahi_wavg += ahi_cnt;
+ ahi_avg += ahi_cnt;
total_hours += hours;
calc_cnt++;
}
@@ -977,6 +1067,8 @@ void gAHIChart::afterDraw(QPainter & /*painter */, gGraph &graph, QRect rect)
int size = idx_end - idx_start;
+ int midcalc = p_profile->general->prefCalcMiddle();
+
int mpos = size /2 ;
float med = 0;
@@ -996,8 +1088,22 @@ void gAHIChart::afterDraw(QPainter & /*painter */, gGraph &graph, QRect rect)
if (calc.divisor > 0) {
ChannelID code = calc.code;
schema::Channel & chan = schema::channel[code];
- double indice = calc.sum / calc.divisor;
- txtlist.append(QString("%1 %2 / %3 / %4").arg(chan.label()).arg(calc.min, 0, 'f', 2).arg(indice, 0, 'f', 2).arg(calc.max, 0, 'f', 2));
+ float mid = 0;
+ switch (midcalc) {
+ case 0:
+ if (calc.median_data.size() > 0) {
+ mid = median(calc.median_data.begin(), calc.median_data.end());
+ }
+ break;
+ case 1:
+ mid = calc.wavg_sum / calc.divisor;
+ break;
+ case 2:
+ mid = calc.avg_sum / calc.divisor;
+ break;
+ }
+
+ txtlist.append(QString("%1 %2 / %3 / %4").arg(chan.label()).arg(calc.min, 0, 'f', 2).arg(mid, 0, 'f', 2).arg(calc.max, 0, 'f', 2));
}
}
QString txt = txtlist.join(", ");
diff --git a/sleepyhead/Graphs/gSessionTimesChart.h b/sleepyhead/Graphs/gSessionTimesChart.h
index ed947b86..aee81cc5 100644
--- a/sleepyhead/Graphs/gSessionTimesChart.h
+++ b/sleepyhead/Graphs/gSessionTimesChart.h
@@ -14,39 +14,6 @@
#include "gGraphView.h"
-///Represents the exception for taking the median of an empty list
-class median_of_empty_list_exception:public std::exception{
- virtual const char* what() const throw() {
- return "Attempt to take the median of an empty list of numbers. "
- "The median of an empty list is undefined.";
- }
-};
-
-///Return the median of a sequence of numbers defined by the random
-///access iterators begin and end. The sequence must not be empty
-///(median is undefined for an empty set).
-///
-///The numbers must be convertible to double.
-template
-double median(RandAccessIter begin, RandAccessIter end)
- throw(median_of_empty_list_exception){
- if(begin == end){ throw median_of_empty_list_exception(); }
- std::size_t size = end - begin;
- std::size_t middleIdx = size/2;
- RandAccessIter target = begin + middleIdx;
- std::nth_element(begin, target, end);
-
- if(size % 2 != 0){ //Odd number of elements
- return *target;
- }else{ //Even number of elements
- double a = *target;
- RandAccessIter targetNeighbor= target-1;
- std::nth_element(begin, targetNeighbor, end);
- return (a+*targetNeighbor)/2.0;
- }
-}
-
-
struct TimeSpan
{
public:
@@ -66,27 +33,63 @@ struct SummaryCalcItem {
code = 0;
type = ST_CNT;
color = Qt::black;
+ wavg_sum = 0;
+ avg_sum = 0;
+ cnt = 0;
+ divisor = 0;
+ min = 0;
+ max = 0;
}
SummaryCalcItem(const SummaryCalcItem & copy) {
code = copy.code;
type = copy.type;
color = copy.color;
- sum = copy.sum;
- divisor = copy.divisor;
- min = copy.min;
- max = copy.max;
+ wavg_sum = 0;
+ avg_sum = 0;
+ cnt = 0;
+ divisor = 0;
+ min = 0;
+ max = 0;
}
+
SummaryCalcItem(ChannelID code, SummaryType type, QColor color)
- :code(code), type(type), color(color) {}
+ :code(code), type(type), color(color) {
+ }
+
+ inline void update(float value, float weight) {
+ wavg_sum += value * weight;
+ divisor += weight;
+ avg_sum += value;
+ cnt++;
+ median_data.append(value);
+ min = qMin(min, value);
+ max = qMax(max, value);
+ }
+
+ void reset(int reserve) {
+ wavg_sum = 0;
+ avg_sum = 0;
+ divisor = 0;
+ cnt = 0;
+ min = 99999;
+ max = -99999;
+ median_data.clear();
+ median_data.reserve(reserve);
+ }
ChannelID code;
SummaryType type;
QColor color;
- double sum;
+ double wavg_sum;
double divisor;
+ double avg_sum;
+ int cnt;
EventDataType min;
EventDataType max;
+
+ QList median_data;
+
};
struct SummaryChartSlice {
@@ -144,6 +147,11 @@ public:
//! \brief Return any extra data to show beneath the date in the hover over tooltip
virtual QString tooltipData(Day *, int);
+ virtual void dataChanged() {
+ cache.clear();
+ }
+
+
void addCalc(ChannelID code, SummaryType type, QColor color) {
calcitems.append(SummaryCalcItem(code, type, color));
}
@@ -162,12 +170,14 @@ public:
layer->m_empty = m_empty;
layer->firstday = firstday;
layer->lastday = lastday;
- layer->cache = cache;
- layer->calcitems = calcitems;
+// layer->calcitems = calcitems;
layer->expected_slices = expected_slices;
layer->nousedays = nousedays;
layer->totaldays = totaldays;
layer->peak_value = peak_value;
+ layer->idx_start = idx_start;
+ layer->idx_end = idx_end;
+ layer->cache.clear();
}
protected:
@@ -220,6 +230,8 @@ public:
gSessionTimesChart()
:gSummaryChart("SessionTimes", MT_CPAP) {
addCalc(NoChannel, ST_SESSIONS, QColor(64,128,255));
+ addCalc(NoChannel, ST_SESSIONS, QColor(64,128,255));
+ addCalc(NoChannel, ST_SESSIONS, QColor(64,128,255));
}
virtual ~gSessionTimesChart() {}
@@ -231,29 +243,10 @@ public:
m_maxy = 28;
}
- virtual void preCalc() {
- num_slices = 0;
- num_days = 0;
- total_length = 0;
- session_data.clear();
- session_data.reserve(idx_end-idx_start);
-
- }
- virtual void customCalc(Day *, QList & slices) {
- int size = slices.size();
- num_slices += size;
-
- for (int i=0; i & slices);
virtual void afterDraw(QPainter &, gGraph &, QRect);
-
//! \brief Renders the graph to the QPainter object
virtual void paint(QPainter &painter, gGraph &graph, const QRegion ®ion);
virtual Layer * Clone() {
@@ -308,11 +301,6 @@ public:
private:
int incompdays;
EventDataType compliance_threshold;
- double totalhours;
- int totaldays;
- QList hour_data;
-
-
};
class gAHIChart : public gSummaryChart
@@ -345,11 +333,20 @@ public:
}
void CloneInto(gAHIChart * layer) {
- layer->ahi_total = ahi_total;
- layer->calc_cnt = calc_cnt;
+// layer->ahicalc = ahicalc;
+// layer->ahi_wavg = ahi_wavg;
+// layer->ahi_avg = ahi_avg;
+// layer->total_hours = total_hours;
+// layer->max_ahi = max_ahi;
+// layer->min_ahi = min_ahi;
+// layer->calc_cnt = calc_cnt;
+// layer->ahi_data = ahi_data;
}
- double ahi_total;
+ // SummaryCalcItem ahicalc;
+ double ahi_wavg;
+ double ahi_avg;
+
double total_hours;
float max_ahi;
float min_ahi;
diff --git a/sleepyhead/Graphs/layer.h b/sleepyhead/Graphs/layer.h
index ce0a6ee6..47f22ea6 100644
--- a/sleepyhead/Graphs/layer.h
+++ b/sleepyhead/Graphs/layer.h
@@ -129,6 +129,8 @@ class Layer
inline bool recalculating() const { return m_recalculating; }
+ virtual void dataChanged() {}
+
/*! \brief Override this for the drawing code, using GLBuffer components for drawing
\param gGraph & gv Graph Object that holds this layer
\param int left
diff --git a/sleepyhead/SleepLib/common.h b/sleepyhead/SleepLib/common.h
index d62b557b..71a98c92 100644
--- a/sleepyhead/SleepLib/common.h
+++ b/sleepyhead/SleepLib/common.h
@@ -56,6 +56,39 @@ QString weightString(float kg, UnitSystem us = US_Undefined);
//! \brief Mercilessly trash a directory
bool removeDir(const QString &path);
+///Represents the exception for taking the median of an empty list
+class median_of_empty_list_exception:public std::exception{
+ virtual const char* what() const throw() {
+ return "Attempt to take the median of an empty list of numbers. "
+ "The median of an empty list is undefined.";
+ }
+};
+
+///Return the median of a sequence of numbers defined by the random
+///access iterators begin and end. The sequence must not be empty
+///(median is undefined for an empty set).
+///
+///The numbers must be convertible to double.
+template
+double median(RandAccessIter begin, RandAccessIter end)
+ throw (median_of_empty_list_exception)
+{
+ if (begin == end) { throw median_of_empty_list_exception(); }
+ std::size_t size = end - begin;
+ std::size_t middleIdx = size/2;
+ RandAccessIter target = begin + middleIdx;
+ std::nth_element(begin, target, end);
+
+ if (size % 2 != 0) { //Odd number of elements
+ return *target;
+ } else { //Even number of elements
+ double a = *target;
+ RandAccessIter targetNeighbor= target-1;
+ std::nth_element(begin, targetNeighbor, end);
+ return (a+*targetNeighbor)/2.0;
+ }
+}
+
#ifndef nullptr
#define nullptr NULL
diff --git a/sleepyhead/SleepLib/profiles.cpp b/sleepyhead/SleepLib/profiles.cpp
index 1e6ca267..50ccd310 100644
--- a/sleepyhead/SleepLib/profiles.cpp
+++ b/sleepyhead/SleepLib/profiles.cpp
@@ -34,7 +34,8 @@ Profile *p_profile;
Profile::Profile(QString path)
: is_first_day(true),
- m_opened(false)
+ m_opened(false),
+ m_machopened(false)
{
p_name = STR_GEN_Profile;
@@ -94,7 +95,7 @@ Profile::~Profile()
bool Profile::Save(QString filename)
{
if (m_opened) {
- return Preferences::Save(filename);
+ return Preferences::Save(filename) && p_profile->StoreMachines();
} else return false;
}
@@ -132,14 +133,6 @@ bool Profile::Open(QString filename)
}
bool b = Preferences::Open(filename);
- QString lockfile=p_path+"/lockfile";
- QFile file(lockfile);
- file.open(QFile::WriteOnly);
- QByteArray ba;
- ba.append(QHostInfo::localHostName());
- file.write(ba);
- file.close();
-
m_opened=true;
doctor = new DoctorInfo(this);
user = new UserInfo(this);
@@ -151,6 +144,192 @@ bool Profile::Open(QString filename)
return b;
}
+const QString STR_PROP_Brand = "brand";
+const QString STR_PROP_Model = "model";
+const QString STR_PROP_Series = "series";
+const QString STR_PROP_ModelNumber = "modelnumber";
+const QString STR_PROP_SubModel = "submodel";
+const QString STR_PROP_Serial = "serial";
+const QString STR_PROP_DataVersion = "dataversion";
+const QString STR_PROP_LastImported = "lastimported";
+
+bool Profile::OpenMachines()
+{
+ if (m_machopened)
+ return true;
+
+ if (!m_opened) {
+ Open();
+ }
+ QFile lockfile(p_path+"lockfile");
+ lockfile.open(QFile::WriteOnly);
+ QByteArray ba;
+ ba.append(QHostInfo::localHostName());
+ lockfile.write(ba);
+ lockfile.close();
+
+ QString filename = p_path+"machines.xml";
+ QFile file(filename);
+ if (!file.open(QFile::ReadOnly)) {
+ qWarning() << "Could not open" << QDir::toNativeSeparators(filename);
+ return false;
+ }
+ QDomDocument doc("machines.xml");
+
+ if (!doc.setContent(&file)) {
+ qWarning() << "Invalid XML Content in" << QDir::toNativeSeparators(filename);
+ return false;
+ }
+ file.close();
+ QDomElement root = doc.firstChild().toElement();
+
+ if (root.tagName().toLower() != "machines") {
+ //qDebug() << "No Machines Tag in Profiles.xml";
+ return false;
+ }
+
+ QDomElement elem = root.firstChildElement();
+
+ while (!elem.isNull()) {
+ QString pKey = elem.tagName();
+
+ if (pKey.toLower() != "machine") {
+ qWarning() << "Profile::ExtraLoad() pKey!=\"machine\"";
+ elem = elem.nextSiblingElement();
+ continue;
+ }
+
+ int m_id;
+ bool ok;
+ m_id = elem.attribute("id", "").toInt(&ok);
+ int mt;
+ mt = elem.attribute("type", "").toInt(&ok);
+ MachineType m_type = (MachineType)mt;
+
+ QString m_class = elem.attribute("class", "");
+
+ MachineInfo info;
+
+ info.type = m_type;
+ info.loadername = m_class;
+
+ QHash prop;
+
+ QDomElement e = elem.firstChildElement();
+
+ for (; !e.isNull(); e = e.nextSiblingElement()) {
+ QString pKey = e.tagName();
+ QString key = pKey.toLower();
+ if (key == STR_PROP_Brand) {
+ info.brand = e.text();
+ } else if (key == STR_PROP_Model) {
+ info.model = e.text();
+ } else if (key == STR_PROP_ModelNumber) {
+ info.modelnumber = e.text();
+ } else if (key == STR_PROP_Serial) {
+ info.serial = e.text();
+ } else if (key == STR_PROP_Series) {
+ info.series = e.text();
+ } else if (key == STR_PROP_DataVersion) {
+ info.version = e.text().toInt();
+ } else if (key == STR_PROP_LastImported) {
+ info.lastimported = QDateTime::fromString(e.text(), Qt::ISODate);
+ } else if (key == "properties") {
+ QDomElement pe = e.firstChildElement();
+ for (; !pe.isNull(); pe = pe.nextSiblingElement()) {
+ prop[pe.tagName()] = pe.text();
+ }
+ } else {
+ // skip any old rubbish
+ if ((key == "backuppath") || (key == "path") || (key == "submodel")) continue;
+
+ prop[pKey] = e.text();
+ }
+ }
+
+
+ Machine *m = nullptr;
+
+ m = MachineLoader::CreateMachine(info, m_id);
+ //m->setId(m_id);
+ if (m) m->properties = prop;
+
+ elem = elem.nextSiblingElement();
+ }
+
+ m_machopened = true;
+
+ return true;
+
+}
+
+bool Profile::StoreMachines()
+{
+ QDomDocument doc("Machines");
+ QDomElement elem = ExtraSave(doc);
+ doc.appendChild(elem);
+
+ QDomElement mach = doc.createElement("machines");
+
+ for (QHash::iterator i = machlist.begin(); i != machlist.end(); i++) {
+ QDomElement me = doc.createElement("machine");
+ Machine *m = i.value();
+ me.setAttribute("id", (int)m->id());
+ me.setAttribute("type", (int)m->type());
+ me.setAttribute("class", m->loaderName());
+
+ QDomElement pe = doc.createElement("properties");
+ me.appendChild(pe);
+
+ for (QHash::iterator j = i.value()->properties.begin(); j != i.value()->properties.end(); j++) {
+ QDomElement pp = doc.createElement(j.key());
+ pp.appendChild(doc.createTextNode(j.value()));
+ pe.appendChild(pp);
+ }
+
+ QDomElement mp = doc.createElement(STR_PROP_Brand);
+ mp.appendChild(doc.createTextNode(m->brand()));
+ me.appendChild(mp);
+
+ mp = doc.createElement(STR_PROP_Model);
+ mp.appendChild(doc.createTextNode(m->model()));
+ me.appendChild(mp);
+
+ mp = doc.createElement(STR_PROP_ModelNumber);
+ mp.appendChild(doc.createTextNode(m->modelnumber()));
+ me.appendChild(mp);
+
+ mp = doc.createElement(STR_PROP_Serial);
+ mp.appendChild(doc.createTextNode(m->serial()));
+ me.appendChild(mp);
+
+ mp = doc.createElement(STR_PROP_Series);
+ mp.appendChild(doc.createTextNode(m->series()));
+ me.appendChild(mp);
+
+ mp = doc.createElement(STR_PROP_DataVersion);
+ mp.appendChild(doc.createTextNode(QString::number(m->version())));
+ me.appendChild(mp);
+
+ mp = doc.createElement(STR_PROP_LastImported);
+ mp.appendChild(doc.createTextNode(m->lastImported().toString(Qt::ISODate)));
+ me.appendChild(mp);
+
+ mach.appendChild(me);
+ }
+
+ doc.appendChild(mach);
+
+ QString filename = p_path+"machines.xml";
+ QFile file(filename);
+ if (!file.open(QFile::WriteOnly)) {
+ return false;
+ }
+ file.write(doc.toByteArray());
+ return true;
+}
+
+
#if defined(Q_OS_WIN)
class Environment
{
@@ -387,6 +566,7 @@ void Profile::DataFormatError(Machine *m)
}
void Profile::LoadMachineData()
{
+ if (!m_machopened) OpenMachines();
QHash > > cache;
for (QHash::iterator i = machlist.begin(); i != machlist.end(); i++) {
@@ -411,96 +591,30 @@ void Profile::LoadMachineData()
}
}
-const QString STR_PROP_Brand = "brand";
-const QString STR_PROP_Model = "model";
-const QString STR_PROP_Series = "series";
-const QString STR_PROP_ModelNumber = "modelnumber";
-const QString STR_PROP_SubModel = "submodel";
-const QString STR_PROP_Serial = "serial";
-const QString STR_PROP_DataVersion = "dataversion";
-const QString STR_PROP_LastImported = "lastimported";
-
-
/**
- * @brief Machine XML section in profile.
+ * @brief Upgrade Machine XML section from old "profile.xml"
* @param root
*/
void Profile::ExtraLoad(QDomElement &root)
{
if (root.tagName().toLower() != "machines") {
- qDebug() << "No Machines Tag in Profiles.xml";
+ // Good!
return;
}
- QDomElement elem = root.firstChildElement();
+ // Save this sucker
+ QDomDocument doc("Machines");
- while (!elem.isNull()) {
- QString pKey = elem.tagName();
+ doc.appendChild(root);
- if (pKey.toLower() != "machine") {
- qWarning() << "Profile::ExtraLoad() pKey!=\"machine\"";
- elem = elem.nextSiblingElement();
- continue;
- }
+ QFile file(p_path+"/machines.xml");
- int m_id;
- bool ok;
- m_id = elem.attribute("id", "").toInt(&ok);
- int mt;
- mt = elem.attribute("type", "").toInt(&ok);
- MachineType m_type = (MachineType)mt;
+ file.open(QFile::WriteOnly);
- QString m_class = elem.attribute("class", "");
+ file.write(doc.toByteArray());
- MachineInfo info;
-
- info.type = m_type;
- info.loadername = m_class;
-
- QHash prop;
-
- QDomElement e = elem.firstChildElement();
-
- for (; !e.isNull(); e = e.nextSiblingElement()) {
- QString pKey = e.tagName();
- QString key = pKey.toLower();
- if (key == STR_PROP_Brand) {
- info.brand = e.text();
- } else if (key == STR_PROP_Model) {
- info.model = e.text();
- } else if (key == STR_PROP_ModelNumber) {
- info.modelnumber = e.text();
- } else if (key == STR_PROP_Serial) {
- info.serial = e.text();
- } else if (key == STR_PROP_Series) {
- info.series = e.text();
- } else if (key == STR_PROP_DataVersion) {
- info.version = e.text().toInt();
- } else if (key == STR_PROP_LastImported) {
- info.lastimported = QDateTime::fromString(e.text(), Qt::ISODate);
- } else if (key == "properties") {
- QDomElement pe = e.firstChildElement();
- for (; !pe.isNull(); pe = pe.nextSiblingElement()) {
- prop[pe.tagName()] = pe.text();
- }
- } else {
- // skip any old rubbish
- if ((key == "backuppath") || (key == "path") || (key == "submodel")) continue;
-
- prop[pKey] = e.text();
- }
- }
-
-
- Machine *m = nullptr;
-
- m = MachineLoader::CreateMachine(info, m_id);
- //m->setId(m_id);
- if (m) m->properties = prop;
-
- elem = elem.nextSiblingElement();
- }
+ file.close();
}
void Profile::AddMachine(Machine *m)
{
@@ -523,63 +637,6 @@ void Profile::DelMachine(Machine *m)
machlist.erase(machlist.find(m->id()));
}
-
-// Potential Memory Leak Here..
-QDomElement Profile::ExtraSave(QDomDocument &doc)
-{
- QDomElement mach = doc.createElement("machines");
-
- for (QHash::iterator i = machlist.begin(); i != machlist.end(); i++) {
- QDomElement me = doc.createElement("machine");
- Machine *m = i.value();
- me.setAttribute("id", (int)m->id());
- me.setAttribute("type", (int)m->type());
- me.setAttribute("class", m->loaderName());
-
- QDomElement pe = doc.createElement("properties");
- me.appendChild(pe);
-
- for (QHash::iterator j = i.value()->properties.begin(); j != i.value()->properties.end(); j++) {
- QDomElement pp = doc.createElement(j.key());
- pp.appendChild(doc.createTextNode(j.value()));
- pe.appendChild(pp);
- }
-
- QDomElement mp = doc.createElement(STR_PROP_Brand);
- mp.appendChild(doc.createTextNode(m->brand()));
- me.appendChild(mp);
-
- mp = doc.createElement(STR_PROP_Model);
- mp.appendChild(doc.createTextNode(m->model()));
- me.appendChild(mp);
-
- mp = doc.createElement(STR_PROP_ModelNumber);
- mp.appendChild(doc.createTextNode(m->modelnumber()));
- me.appendChild(mp);
-
- mp = doc.createElement(STR_PROP_Serial);
- mp.appendChild(doc.createTextNode(m->serial()));
- me.appendChild(mp);
-
- mp = doc.createElement(STR_PROP_Series);
- mp.appendChild(doc.createTextNode(m->series()));
- me.appendChild(mp);
-
- mp = doc.createElement(STR_PROP_DataVersion);
- mp.appendChild(doc.createTextNode(QString::number(m->version())));
- me.appendChild(mp);
-
- mp = doc.createElement(STR_PROP_LastImported);
- mp.appendChild(doc.createTextNode(m->lastImported().toString(Qt::ISODate)));
- me.appendChild(mp);
-
- mach.appendChild(me);
- }
-
- return mach;
-}
-
-
Day *Profile::addDay(QDate date)
{
QMap::iterator dit = daylist.find(date);
@@ -870,6 +927,32 @@ Profile *Get()
return profiles[getUserName()];;
}
+void saveProfileList()
+{
+ QString filename = PREF.Get("{home}/profiles.xml");
+
+ QDomDocument doc("profiles");
+
+ QDomElement root = doc.createElement("profiles");
+ doc.appendChild(root);
+
+ QMap::iterator it;
+
+ for (it = profiles.begin(); it != profiles.end(); ++it) {
+ QDomElement elem = doc.createElement("profile");
+ elem.setAttribute("name", it.key());
+ // Not technically nessesary..
+ elem.setAttribute("path", QString("{home}/Profiles/%1/Profile.xml").arg(it.key()));
+ root.appendChild(elem);
+ }
+
+ QFile file(filename);
+ file.open(QFile::WriteOnly);
+
+ file.write(doc.toByteArray());
+
+ file.close();
+}
/**
@@ -899,10 +982,13 @@ void Scan()
QFileInfo fi = list.at(i);
QString npath = fi.canonicalFilePath();
Profile *prof = new Profile(npath);
+ //prof->Open();
profiles[fi.fileName()] = prof;
}
+ // Update profiles.xml for mobile version
+ saveProfileList();
}
diff --git a/sleepyhead/SleepLib/profiles.h b/sleepyhead/SleepLib/profiles.h
index 53947b3e..3268e217 100644
--- a/sleepyhead/SleepLib/profiles.h
+++ b/sleepyhead/SleepLib/profiles.h
@@ -50,6 +50,10 @@ class Profile : public Preferences
//! \brief Open profile, parse profile.xml file, and initialize helper classes
virtual bool Open(QString filename = "");
+ //! \brief Parse machines.xml
+ bool OpenMachines();
+ bool StoreMachines();
+
//! \brief Returns hostname that locked profile, or empty string if unlocked
QString checkLock();
@@ -71,7 +75,9 @@ class Profile : public Preferences
//! \brief Barf because data format has changed. This does a purge of CPAP data for machine *m
void DataFormatError(Machine *m);
- /*! \brief Import Machine Data
+ QString path() { return p_path; }
+
+ /*! \brief Import Machine Data
\param path containing import location
*/
int Import(QString path);
@@ -177,7 +183,6 @@ class Profile : public Preferences
// XML load components
virtual void ExtraLoad(QDomElement &root);
- virtual QDomElement ExtraSave(QDomDocument &doc);
//! \brief Looks for the first date containing a day record matching machinetype
QDate FirstDay(MachineType mt = MT_UNKNOWN);
@@ -218,6 +223,7 @@ class Profile : public Preferences
QDate m_last;
bool m_opened;
+ bool m_machopened;
};
class MachineLoader;
diff --git a/sleepyhead/SleepLib/schema.cpp b/sleepyhead/SleepLib/schema.cpp
index a8544a4e..97adb290 100644
--- a/sleepyhead/SleepLib/schema.cpp
+++ b/sleepyhead/SleepLib/schema.cpp
@@ -542,6 +542,13 @@ void init()
ZEO_TimeInLight = schema::channel["TimeInLight"].id();
ZEO_TimeInDeep = schema::channel["TimeInDeep"].id();
ZEO_TimeToZ = schema::channel["TimeToZ"].id();
+
+ schema::channel[CPAP_Leak].setShowInOverview(true);
+ schema::channel[CPAP_RespRate].setShowInOverview(true);
+ schema::channel[CPAP_MinuteVent].setShowInOverview(true);
+ schema::channel[CPAP_TidalVolume].setShowInOverview(true);
+ schema::channel[CPAP_CSR].setShowInOverview(true);
+ schema::channel[CPAP_LargeLeak].setShowInOverview(true);
}
diff --git a/sleepyhead/daily.cpp b/sleepyhead/daily.cpp
index 2922449f..d17237f8 100644
--- a/sleepyhead/daily.cpp
+++ b/sleepyhead/daily.cpp
@@ -453,6 +453,7 @@ void Daily::doToggleSession(Session * sess)
sess->setEnabled(!sess->enabled());
LoadDate(previous_date);
+ mainwin->getOverview()->graphView()->dataChanged();
}
void Daily::Link_clicked(const QUrl &url)
@@ -903,10 +904,7 @@ QString Daily::getSessionInformation(Day * day)
int s1=len % 60;
Session *sess=*s;
- if (!sess->settings.contains(SESSION_ENABLED)) {
- sess->settings[SESSION_ENABLED]=true;
- }
- bool b=sess->settings[SESSION_ENABLED].toBool();
+ bool b=sess->enabled();
html+=QString("%2 |
"
""
@@ -969,8 +967,8 @@ QString Daily::getMachineSettings(Day * day) {
ChannelID cpapmode = loader->CPAPModeChannel();
schema::Channel & chan = schema::channel[cpapmode];
first[cpapmode] = QString("
%1%2 | %3 |
")
- .arg(schema::channel[cpapmode].label())
- .arg(schema::channel[cpapmode].description())
+ .arg(chan.label())
+ .arg(chan.description())
.arg(day->getCPAPMode());
@@ -1248,15 +1246,15 @@ QString Daily::getStatisticsInfo(Day * day)
html+=""+tr("Time outside of ramp")+
QString(" | %1:%2:%3 |
").arg(q / 3600, 2, 10, QChar('0')).arg((q / 60) % 60, 2, 10, QChar('0')).arg(q % 60, 2, 10, QChar('0'));
- EventDataType hc = day->count(CPAP_Hypopnea) - day->countInsideSpan(CPAP_Ramp, CPAP_Hypopnea);
- EventDataType oc = day->count(CPAP_Obstructive) - day->countInsideSpan(CPAP_Ramp, CPAP_Obstructive);
+// EventDataType hc = day->count(CPAP_Hypopnea) - day->countInsideSpan(CPAP_Ramp, CPAP_Hypopnea);
+// EventDataType oc = day->count(CPAP_Obstructive) - day->countInsideSpan(CPAP_Ramp, CPAP_Obstructive);
- EventDataType tc = day->count(CPAP_Hypopnea) + day->count(CPAP_Obstructive);
- EventDataType ahi = (hc+oc) / v;
+ //EventDataType tc = day->count(CPAP_Hypopnea) + day->count(CPAP_Obstructive);
+// EventDataType ahi = (hc+oc) / v;
// Not sure if i was trying to be funny, and left on my replication of Devilbiss's bug here... :P
- html+=""+tr("AHI excluding ramp")+
- QString(" | %1 |
").arg(ahi, 0, 'f', 2);
+// html+=""+tr("AHI excluding ramp")+
+// QString(" | %1 |
").arg(ahi, 0, 'f', 2);
}
}
diff --git a/sleepyhead/docs/release_notes.html b/sleepyhead/docs/release_notes.html
index f1600c3b..bb344ce5 100644
--- a/sleepyhead/docs/release_notes.html
+++ b/sleepyhead/docs/release_notes.html
@@ -1,12 +1,14 @@
-SleepyHead v0.9.7-11 Testing
+SleepyHead v0.9.8 Testing
Release Notes
Greetings!
Here is a definitely new and improved SleepyHead build :)
+The New Features and Bugfixes list is getting a little scary, and I completely rewrote some important parts.. so let's have a Version Bump!
+
Right clicky menu has tons of new stuff for you to play with... some of this you're hopefully going to love! :)
New Graph Clone ability allows you to make a temporary copy of a graph, and operate it completely independently...
You can even take these graph clones with you to another day! They aren't saved though. they are gone when you close SleepyHead.
@@ -18,6 +20,14 @@ I still need to to see more AirSense data to get everything right though!
Sleep Well, and good luck!
JediMark
+
+New features & bug fixes in v0.9.8
+
+Removed old unused Mask Preferences and other junk
+Complete Overview SummaryChart overhaul
+Prescription changes now caches to disk to save having to reload all data every time
+Implemented Demand loading for SleepyHead Summary data files
+
New features & bug fixes in v0.9.7
diff --git a/sleepyhead/preferencesdialog.cpp b/sleepyhead/preferencesdialog.cpp
index 1b073d00..d331e7d1 100644
--- a/sleepyhead/preferencesdialog.cpp
+++ b/sleepyhead/preferencesdialog.cpp
@@ -68,7 +68,7 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, Profile *_profile) :
shortformat.replace("yy", "yyyy");
}
- Qt::DayOfWeek dow = firstDayOfWeekFromLocale();
+// Qt::DayOfWeek dow = firstDayOfWeekFromLocale();
// QTextCharFormat format = ui->startedUsingMask->calendarWidget()->weekdayTextFormat(Qt::Saturday);
// format.setForeground(QBrush(Qt::black, Qt::SolidPattern));
diff --git a/sleepyhead/preferencesdialog.ui b/sleepyhead/preferencesdialog.ui
index f77e3e24..595c8209 100644
--- a/sleepyhead/preferencesdialog.ui
+++ b/sleepyhead/preferencesdialog.ui
@@ -905,6 +905,20 @@ Defaults to 60 minutes.. Highly recommend it's left at this value.
+ -
+
+
+ For consistancy, ResMed users should use 95% here,
+as this is the only value available on summary-only days.
+
+
+ %
+
+
+ 1
+
+
+
-
@@ -940,20 +954,6 @@ Defaults to 60 minutes.. Highly recommend it's left at this value.
- -
-
-
- For consistancy, ResMed users should use 95% here,
-as this is the only value available on summary-only days.
-
-
- %
-
-
- 1
-
-
-
-
@@ -969,7 +969,7 @@ as this is the only value available on summary-only days.
- ResMed users probably should use 99th Percentile for visual consistency.
+ <html><head/><body><p>True maximum is the maximum of the data set.</p><p>99th percentile filters out the rarest outliers.</p></body></html>
-
@@ -983,6 +983,37 @@ as this is the only value available on summary-only days.
+ -
+
+
-
+
+ Combined Count divided by Total Hours
+
+
+ -
+
+ Time Weighted average of Indice
+
+
+ -
+
+ Standard average of indice
+
+
+ -
+
+ Median
+
+
+
+
+ -
+
+
+ Culminative Indices
+
+
+
diff --git a/sleepyhead/scripts/build_number b/sleepyhead/scripts/build_number
index b4de3947..573541ac 100644
--- a/sleepyhead/scripts/build_number
+++ b/sleepyhead/scripts/build_number
@@ -1 +1 @@
-11
+0
diff --git a/sleepyhead/version.h b/sleepyhead/version.h
index f71a736e..ec9d2ec1 100644
--- a/sleepyhead/version.h
+++ b/sleepyhead/version.h
@@ -17,7 +17,7 @@
const int major_version = 0; // incompatible API changes
const int minor_version = 9; // new features that don't break things
-const int revision_number = 7; // bugfixes, revisions
+const int revision_number = 8; // bugfixes, revisions
#ifdef TEST_BUILD
const QString ReleaseStatus = "testing";