mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-05 02:30:44 +00:00
Mega update: Summary demand loading, Overview summarychart rework, rxchanges caching
This commit is contained in:
parent
04b06a9f6d
commit
4c0b4908bc
23
README
23
README
@ -3,9 +3,21 @@ SleepyHead QT port v0.9 branch
|
||||
SleepyHead is cross platform, opensource sleep tracking program for reviewing CPAP and Oximetry data,
|
||||
which are devices used in the treatment of Sleep Disorders like Obstructive Sleep Apnea.
|
||||
|
||||
To Build:
|
||||
Requirements:
|
||||
-------------
|
||||
Qt5 SDK with webkit (opengl stuff recommended)
|
||||
Linux needs libudev-dev for qserialport to compile
|
||||
|
||||
qmake
|
||||
|
||||
Building:
|
||||
--------
|
||||
|
||||
Recommend shadow building to not cruft up the source code folder:
|
||||
|
||||
cd ..
|
||||
mkdir build_sleepyhead
|
||||
cd build_sleepyhead
|
||||
qmake ../sleepyhead-code/SleepyHeadQT.pro
|
||||
make
|
||||
|
||||
You may need to add a -spec option to qmake to suit your platform.
|
||||
@ -13,15 +25,12 @@ Adding -j3 speeds up the make command on a dual core or greater system.
|
||||
|
||||
|
||||
Author: Mark Watkins <jedimark@users.sourceforge.net>
|
||||
Copyright (C)2011 Mark Watkins
|
||||
Copyright (C)2011-2014 Mark Watkins
|
||||
|
||||
Licence Stuff
|
||||
-------------
|
||||
This software is released under the GNU Public License, at a GPL version of my choosing at a later date.
|
||||
This software is released under the GNU Public License version 3.0
|
||||
|
||||
Exceptions and 3rd Party Libraries:
|
||||
Incorporates QextSerialPort. Insert New BSD license here? (Apparently PD.. need to verify)
|
||||
http://code.google.com/p/qextserialport/
|
||||
|
||||
It uses QuaZip, by Sergey A. Tachenov, which is a C++ wrapper over Gilles Vollant's ZIP/UNZIP package..
|
||||
http://sourceforge.net/projects/quazip/
|
||||
|
@ -145,10 +145,11 @@ void MinutesAtPressure::paint(QPainter &painter, gGraph &graph, const QRegion &r
|
||||
m_minx = graph.min_x;
|
||||
m_maxx = graph.max_x;
|
||||
|
||||
if ((m_lastminx != m_minx) || (m_lastmaxx != m_maxx)) {
|
||||
if (graph.printing() || ((m_lastminx != m_minx) || (m_lastmaxx != m_maxx))) {
|
||||
recalculate(&graph);
|
||||
}
|
||||
|
||||
|
||||
m_lastminx = m_minx;
|
||||
m_lastmaxx = m_maxx;
|
||||
|
||||
@ -158,11 +159,12 @@ void MinutesAtPressure::paint(QPainter &painter, gGraph &graph, const QRegion &r
|
||||
|
||||
if (graph.printing()) {
|
||||
// lock the other mutex...
|
||||
while (recalculating()) {};
|
||||
recalculate(&graph);
|
||||
// while (recalculating()) {};
|
||||
// recalculate(&graph);
|
||||
while (recalculating()) {};
|
||||
|
||||
}
|
||||
if (!painter.isActive()) return;
|
||||
|
||||
|
||||
// Lock the stuff we need to draw
|
||||
|
@ -152,7 +152,7 @@ void gToolTip::paint(QPainter &painter) //actually paints it.
|
||||
if (xx < 0) { xx = 0; }
|
||||
|
||||
rect.setLeft(xx);
|
||||
rect.setTop(rect.y() - rect.height() / 2);
|
||||
rect.setTop(rect.y() - 15);
|
||||
rect.setWidth(w);
|
||||
|
||||
int z = rect.x() + rect.width();
|
||||
@ -256,8 +256,13 @@ void gGraphView::queGraph(gGraph *g, int left, int top, int width, int height)
|
||||
#endif
|
||||
}
|
||||
|
||||
void gGraphView::trashGraphs()
|
||||
void gGraphView::trashGraphs(bool destroy)
|
||||
{
|
||||
if (destroy) {
|
||||
for (int i=0; i< m_graphs.size(); ++i) {
|
||||
delete m_graphs[i];
|
||||
}
|
||||
}
|
||||
// Don't actually want to delete them here.. we are just borrowing the graphs
|
||||
m_graphs.clear();
|
||||
m_graphsbyname.clear();
|
||||
@ -300,6 +305,7 @@ gGraphView::gGraphView(QWidget *parent, gGraphView *shared)
|
||||
m_selected_graph = nullptr;
|
||||
m_scrollbar = nullptr;
|
||||
m_point_released = m_point_clicked = QPoint(0,0);
|
||||
m_showAuthorMessage = true;
|
||||
|
||||
horizScrollTime.start();
|
||||
vertScrollTime.start();
|
||||
@ -889,7 +895,8 @@ void gGraphView::updateScale()
|
||||
|
||||
|
||||
if (th < h) {
|
||||
th -= visibleGraphs() * graphSpacer; // compensate for spacer height
|
||||
th -= graphSpacer;
|
||||
// th -= visibleGraphs() * graphSpacer; // compensate for spacer height
|
||||
m_scaleY = h / th; // less graphs than fits on screen, so scale to fit
|
||||
} else {
|
||||
m_scaleY = 1.0;
|
||||
@ -941,6 +948,7 @@ void gGraphView::GetRXBounds(qint64 &st, qint64 &et)
|
||||
|
||||
void gGraphView::ResetBounds(bool refresh) //short group)
|
||||
{
|
||||
if (m_graphs.size() == 0) return;
|
||||
Q_UNUSED(refresh)
|
||||
qint64 m1 = 0, m2 = 0;
|
||||
gGraph *g = nullptr;
|
||||
@ -964,7 +972,9 @@ void gGraphView::ResetBounds(bool refresh) //short group)
|
||||
// }
|
||||
// }
|
||||
|
||||
if (!g) { g = m_graphs[0]; }
|
||||
if (!g) {
|
||||
g = m_graphs[0];
|
||||
}
|
||||
|
||||
m_minx = g->min_x;
|
||||
m_maxx = g->max_x;
|
||||
@ -989,7 +999,7 @@ void gGraphView::SetXBounds(qint64 minx, qint64 maxx, short group, bool refresh)
|
||||
m_minx = minx;
|
||||
m_maxx = maxx;
|
||||
|
||||
if (refresh) { redraw(); }
|
||||
if (refresh) { timedRedraw(0); }
|
||||
}
|
||||
|
||||
void gGraphView::updateScrollBar()
|
||||
@ -1269,11 +1279,15 @@ void gGraphView::paintGL()
|
||||
graphs_drawn = renderGraphs(painter);
|
||||
|
||||
if (!graphs_drawn) { // No graphs drawn? show something useful :)
|
||||
QString txt = QObject::tr("SleepyHead is proudly brought to you by JediMark.");
|
||||
QString txt;
|
||||
if (m_showAuthorMessage) {
|
||||
if (emptyText() == STR_Empty_Brick) {
|
||||
txt += "\nI'm very sorry your machine doesn't record useful data to graph in Daily View :(";
|
||||
txt = "\nI'm very sorry your machine doesn't record useful data to graph in Daily View :(";
|
||||
} else {
|
||||
// not proud of telling them their machine is a Brick.. ;)
|
||||
txt = QObject::tr("SleepyHead is proudly brought to you by JediMark.");
|
||||
}
|
||||
}
|
||||
|
||||
// int x2, y2;
|
||||
// GetTextExtent(m_emptytext, x2, y2, bigfont);
|
||||
// int tp2, tp1;
|
||||
@ -1872,7 +1886,7 @@ void gGraphView::populateMenu(gGraph * graph)
|
||||
|
||||
gLineChart * lc = dynamic_cast<gLineChart *>(findLayer(graph,LT_LineChart));
|
||||
SummaryChart * sc = dynamic_cast<SummaryChart *>(findLayer(graph,LT_SummaryChart));
|
||||
gSessionTimesChart * stg = dynamic_cast<gSessionTimesChart *>(findLayer(graph,LT_SessionTimes));
|
||||
gSummaryChart * stg = dynamic_cast<gSummaryChart *>(findLayer(graph,LT_Overview));
|
||||
|
||||
|
||||
limits_menu->clear();
|
||||
@ -3212,7 +3226,7 @@ bool gGraphView::LoadSettings(QString title)
|
||||
in.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
quint32 t1;
|
||||
quint16 t2;
|
||||
quint16 version;
|
||||
|
||||
in >> t1;
|
||||
|
||||
@ -3221,9 +3235,9 @@ bool gGraphView::LoadSettings(QString title)
|
||||
return false;
|
||||
}
|
||||
|
||||
in >> t2;
|
||||
in >> version;
|
||||
|
||||
if (t2 < gvversion) {
|
||||
if (version < gvversion) {
|
||||
qDebug() << "gGraphView" << title << "settings will be upgraded.";
|
||||
}
|
||||
|
||||
@ -3272,15 +3286,16 @@ bool gGraphView::LoadSettings(QString title)
|
||||
|
||||
gGraph *g = nullptr;
|
||||
|
||||
if (t2 <= 2) {
|
||||
// Names were stored as translated strings, so look up title instead.
|
||||
g = nullptr;
|
||||
for (int z=0; z<m_graphs.size(); ++z) {
|
||||
if (m_graphs[z]->title() == name) {
|
||||
g = m_graphs[z];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (version <= 2) {
|
||||
continue;
|
||||
// // Names were stored as translated strings, so look up title instead.
|
||||
// g = nullptr;
|
||||
// for (int z=0; z<m_graphs.size(); ++z) {
|
||||
// if (m_graphs[z]->title() == name) {
|
||||
// g = m_graphs[z];
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
gi = m_graphsbyname.find(name);
|
||||
if (gi == m_graphsbyname.end()) {
|
||||
|
@ -470,7 +470,7 @@ class gGraphView
|
||||
void showSplitter() { m_showsplitter = true; }
|
||||
|
||||
//! \brief Trash all graph objects listed (without destroying Graph contents)
|
||||
void trashGraphs();
|
||||
void trashGraphs(bool destroy);
|
||||
|
||||
//! \brief Enable or disable the Text Pixmap Caching system preference overide
|
||||
void setUsePixmapCache(bool b) { use_pixmap_cache = b; }
|
||||
@ -512,6 +512,9 @@ class gGraphView
|
||||
|
||||
void getSelectionTimes(qint64 & start, qint64 & end);
|
||||
|
||||
//! \brief Whether to show a little authorship message down the bottom of empty graphs.
|
||||
void setShowAuthorMessage(bool b) { m_showAuthorMessage = b; }
|
||||
|
||||
// for profiling purposes, a count of lines drawn in a single frame
|
||||
int lines_drawn_this_frame;
|
||||
int quads_drawn_this_frame;
|
||||
@ -563,6 +566,7 @@ class gGraphView
|
||||
//! \brief Add Graph to drawing queue, mainly for the benefit of multithreaded drawing code
|
||||
void queGraph(gGraph *, int originX, int originY, int width, int height);
|
||||
|
||||
|
||||
Day *m_day;
|
||||
|
||||
//! \brief the list of graphs to draw this frame
|
||||
@ -648,6 +652,8 @@ class gGraphView
|
||||
|
||||
QAction * zoom100_action;
|
||||
|
||||
bool m_showAuthorMessage;
|
||||
|
||||
signals:
|
||||
void updateCurrentTime(double);
|
||||
void updateRange(double,double);
|
||||
@ -670,6 +676,7 @@ class gGraphView
|
||||
ResetBounds(true);
|
||||
}
|
||||
|
||||
|
||||
bool hasSnapshots();
|
||||
|
||||
void togglePin();
|
||||
@ -678,6 +685,7 @@ protected slots:
|
||||
void onPlotsClicked(QAction *);
|
||||
void onOverlaysClicked(QAction *);
|
||||
void onSnapshotGraphToggle();
|
||||
|
||||
};
|
||||
|
||||
#endif // GGRAPHVIEW_H
|
||||
|
@ -248,7 +248,7 @@ void gLineOverlayBar::paint(QPainter &painter, gGraph &w, const QRegion ®ion)
|
||||
// painter.drawLine(rect.x(), bottom, rect.x()+d1, bottom);
|
||||
// painter.drawLine(rect.x(), top, rect.x(), bottom);
|
||||
|
||||
// col = QColor("gold");
|
||||
// col = COLOR_Gold;
|
||||
hover = true;
|
||||
painter.setPen(QPen(col,3));
|
||||
} else {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,9 +9,11 @@
|
||||
#ifndef GSESSIONTIMESCHART_H
|
||||
#define GSESSIONTIMESCHART_H
|
||||
|
||||
#include <SleepLib/day.h>
|
||||
#include "SleepLib/day.h"
|
||||
#include "SleepLib/profiles.h"
|
||||
#include "gGraphView.h"
|
||||
|
||||
|
||||
struct TimeSpan
|
||||
{
|
||||
public:
|
||||
@ -26,31 +28,94 @@ public:
|
||||
float end;
|
||||
};
|
||||
|
||||
/*! \class gSessionTimesChart
|
||||
\brief Displays a summary of session times
|
||||
*/
|
||||
class gSessionTimesChart : public Layer
|
||||
struct SummaryCalcItem {
|
||||
SummaryCalcItem() {
|
||||
code = 0;
|
||||
type = ST_CNT;
|
||||
}
|
||||
SummaryCalcItem(const SummaryCalcItem & copy) {
|
||||
code = copy.code;
|
||||
type = copy.type;
|
||||
}
|
||||
SummaryCalcItem(ChannelID code, SummaryType type)
|
||||
:code(code), type(type) {}
|
||||
ChannelID code;
|
||||
SummaryType type;
|
||||
};
|
||||
|
||||
struct SummaryChartSlice {
|
||||
SummaryChartSlice() {
|
||||
code = 0;
|
||||
height = 0;
|
||||
value = 0;
|
||||
name = ST_CNT;
|
||||
}
|
||||
SummaryChartSlice(const SummaryChartSlice & copy) {
|
||||
code = copy.code;
|
||||
value = copy.value;
|
||||
height = copy.height;
|
||||
name = copy.name;
|
||||
color = copy.color;
|
||||
}
|
||||
SummaryChartSlice(ChannelID code, EventDataType value, EventDataType height, QString name, QColor color)
|
||||
:code(code), value(value), height(height), name(name), color(color) {}
|
||||
ChannelID code;
|
||||
EventDataType value;
|
||||
EventDataType height;
|
||||
QString name;
|
||||
QColor color;
|
||||
};
|
||||
|
||||
class gSummaryChart : public Layer
|
||||
{
|
||||
public:
|
||||
gSessionTimesChart(QString label, MachineType machtype);
|
||||
~gSessionTimesChart();
|
||||
gSummaryChart(QString label, MachineType machtype);
|
||||
gSummaryChart(ChannelID code, MachineType machtype);
|
||||
virtual ~gSummaryChart();
|
||||
|
||||
//! \brief Renders the graph to the QPainter object
|
||||
virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion);
|
||||
virtual void paint(QPainter &, gGraph &, const QRegion &);
|
||||
|
||||
//! \brief Precalculation code prior to drawing. Day object is not needed here, it's just here for Layer compatability.
|
||||
//! \brief Called whenever data model changes underneath. Day object is not needed here, it's just here for Layer compatability.
|
||||
virtual void SetDay(Day *day = nullptr);
|
||||
|
||||
//! \brief Returns true if no data was found for this day during SetDay
|
||||
virtual bool isEmpty() { return m_empty; }
|
||||
|
||||
//! \brief Deselect highlighting (the gold bar)
|
||||
virtual void deselect() {
|
||||
hl_day = -1;
|
||||
virtual void populate(Day *, int idx);
|
||||
|
||||
//! \brief Override to setup custom stuff before main loop
|
||||
virtual void preCalc() {}
|
||||
|
||||
//! \brief Override to call stuff in main loop
|
||||
virtual void customCalc(Day *, QList<SummaryChartSlice> &) {}
|
||||
|
||||
//! \brief Override to call stuff after draw is complete
|
||||
virtual void afterDraw(QPainter &, gGraph &, QRect) {}
|
||||
|
||||
//! \brief Return any extra data to show beneath the date in the hover over tooltip
|
||||
virtual QString tooltipData(Day *, int);
|
||||
|
||||
void addCalc(ChannelID code, SummaryType type) { calcitems.append(SummaryCalcItem(code, type)); }
|
||||
|
||||
virtual Layer * Clone() {
|
||||
gSummaryChart * sc = new gSummaryChart(m_label, m_machtype);
|
||||
Layer::CloneInto(sc);
|
||||
CloneInto(sc);
|
||||
return sc;
|
||||
}
|
||||
|
||||
//! \brief Returns true if currently selected..
|
||||
virtual bool isSelected() { return hl_day >= 0; }
|
||||
void CloneInto(gSummaryChart * layer) {
|
||||
layer->m_empty = m_empty;
|
||||
layer->firstday = firstday;
|
||||
layer->lastday = lastday;
|
||||
layer->cache = cache;
|
||||
layer->calcitems = calcitems;
|
||||
layer->expected_slices = expected_slices;
|
||||
layer->nousedays = nousedays;
|
||||
layer->totaldays = totaldays;
|
||||
layer->peak_value = peak_value;
|
||||
}
|
||||
|
||||
protected:
|
||||
//! \brief Key was pressed that effects this layer
|
||||
@ -73,7 +138,192 @@ protected:
|
||||
float tz_hours;
|
||||
QDate firstday;
|
||||
QDate lastday;
|
||||
QMap<quint32, QList<TimeSpan> > sessiontimes;
|
||||
|
||||
static QMap<QDate, int> dayindex;
|
||||
static QList<Day *> daylist;
|
||||
|
||||
QHash<int, QList<SummaryChartSlice> > cache;
|
||||
QList<SummaryCalcItem> calcitems;
|
||||
|
||||
int expected_slices;
|
||||
|
||||
int nousedays;
|
||||
int totaldays;
|
||||
|
||||
EventDataType peak_value;
|
||||
EventDataType min_value;
|
||||
};
|
||||
|
||||
|
||||
/*! \class gSessionTimesChart
|
||||
\brief Displays a summary of session times
|
||||
*/
|
||||
class gSessionTimesChart : public gSummaryChart
|
||||
{
|
||||
public:
|
||||
gSessionTimesChart()
|
||||
:gSummaryChart("SessionTimes", MT_CPAP) {}
|
||||
virtual ~gSessionTimesChart() {}
|
||||
|
||||
virtual void SetDay(Day * day = nullptr) {
|
||||
gSummaryChart::SetDay(day);
|
||||
split = p_profile->session->daySplitTime();
|
||||
|
||||
m_miny = 0;
|
||||
m_maxy = 28;
|
||||
}
|
||||
|
||||
virtual void preCalc() {
|
||||
num_slices = 0;
|
||||
num_days = 0;
|
||||
total_length = 0;
|
||||
}
|
||||
virtual void customCalc(Day *, QList<SummaryChartSlice> & slices) {
|
||||
int size = slices.size();
|
||||
num_slices += size;
|
||||
|
||||
for (int i=0; i<size; ++i) {
|
||||
const SummaryChartSlice & slice = slices.at(i);
|
||||
total_length += slice.height;
|
||||
}
|
||||
|
||||
num_days++;
|
||||
}
|
||||
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() {
|
||||
gSessionTimesChart * sc = new gSessionTimesChart();
|
||||
gSummaryChart::CloneInto(sc);
|
||||
CloneInto(sc);
|
||||
return sc;
|
||||
}
|
||||
|
||||
void CloneInto(gSessionTimesChart * layer) {
|
||||
layer->split = split;
|
||||
}
|
||||
QTime split;
|
||||
int num_slices;
|
||||
int num_days;
|
||||
int total_slices;
|
||||
double total_length;
|
||||
};
|
||||
|
||||
|
||||
class gUsageChart : public gSummaryChart
|
||||
{
|
||||
public:
|
||||
gUsageChart()
|
||||
:gSummaryChart("Usage", MT_CPAP) {
|
||||
addCalc(NoChannel, ST_HOURS);
|
||||
}
|
||||
virtual ~gUsageChart() {}
|
||||
|
||||
virtual void preCalc();
|
||||
virtual void customCalc(Day *, QList<SummaryChartSlice> &);
|
||||
virtual void afterDraw(QPainter &, gGraph &, QRect);
|
||||
virtual void populate(Day *day, int idx);
|
||||
|
||||
virtual QString tooltipData(Day * day, int);
|
||||
|
||||
|
||||
virtual Layer * Clone() {
|
||||
gUsageChart * sc = new gUsageChart();
|
||||
gSummaryChart::CloneInto(sc);
|
||||
CloneInto(sc);
|
||||
return sc;
|
||||
}
|
||||
|
||||
void CloneInto(gUsageChart * layer) {
|
||||
layer->incompdays = incompdays;
|
||||
layer->compliance_threshold = compliance_threshold;
|
||||
}
|
||||
|
||||
private:
|
||||
int incompdays;
|
||||
EventDataType compliance_threshold;
|
||||
double totalhours;
|
||||
int totaldays;
|
||||
|
||||
};
|
||||
|
||||
class gAHIChart : public gSummaryChart
|
||||
{
|
||||
public:
|
||||
gAHIChart()
|
||||
:gSummaryChart("AHIChart", MT_CPAP) {
|
||||
channels.append(CPAP_ClearAirway);
|
||||
channels.append(CPAP_Obstructive);
|
||||
channels.append(CPAP_Apnea);
|
||||
channels.append(CPAP_Hypopnea);
|
||||
if (p_profile->general->calculateRDI())
|
||||
channels.append(CPAP_RERA);
|
||||
num_channels = channels.size();
|
||||
}
|
||||
virtual ~gAHIChart() {}
|
||||
|
||||
virtual void preCalc();
|
||||
virtual void customCalc(Day *, QList<SummaryChartSlice> &);
|
||||
virtual void afterDraw(QPainter &, gGraph &, QRect);
|
||||
|
||||
virtual void populate(Day *, int idx);
|
||||
|
||||
virtual QString tooltipData(Day * day, int);
|
||||
|
||||
virtual Layer * Clone() {
|
||||
gAHIChart * sc = new gAHIChart();
|
||||
gSummaryChart::CloneInto(sc);
|
||||
CloneInto(sc);
|
||||
return sc;
|
||||
}
|
||||
|
||||
void CloneInto(gAHIChart * layer) {
|
||||
layer->channels = channels;
|
||||
layer->num_channels = num_channels;
|
||||
layer->indices = indices;
|
||||
layer->ahi_total = ahi_total;
|
||||
layer->calc_cnt = calc_cnt;
|
||||
}
|
||||
|
||||
QList<ChannelID> channels;
|
||||
int num_channels;
|
||||
|
||||
QHash<ChannelID, double> indices;
|
||||
double ahi_total;
|
||||
double total_hours;
|
||||
int calc_cnt;
|
||||
};
|
||||
|
||||
|
||||
class gPressureChart : public gSummaryChart
|
||||
{
|
||||
public:
|
||||
gPressureChart()
|
||||
:gSummaryChart("Pressure", MT_CPAP) {
|
||||
}
|
||||
virtual ~gPressureChart() {}
|
||||
|
||||
virtual void SetDay(Day * day = nullptr) {
|
||||
gSummaryChart::SetDay(day);
|
||||
m_miny = 0;
|
||||
m_maxy = 24;
|
||||
}
|
||||
|
||||
|
||||
virtual Layer * Clone() {
|
||||
gPressureChart * sc = new gPressureChart();
|
||||
gSummaryChart::CloneInto(sc);
|
||||
return sc;
|
||||
}
|
||||
|
||||
virtual void populate(Day * day, int idx);
|
||||
virtual QString tooltipData(Day * day, int idx) {
|
||||
return day->getCPAPMode() + "\n" + day->getPressureSettings() + gSummaryChart::tooltipData(day, idx);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // GSESSIONTIMESCHART_H
|
||||
|
@ -651,7 +651,7 @@ void SummaryChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion)
|
||||
col = summaryColor;
|
||||
}
|
||||
if (zd == hl_day) {
|
||||
col = QColor("gold");
|
||||
col = COLOR_Gold;
|
||||
}
|
||||
|
||||
QColor col1 = col;
|
||||
@ -749,7 +749,7 @@ void SummaryChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion)
|
||||
}
|
||||
|
||||
if (zd == hl_day) {
|
||||
col = QColor("gold");
|
||||
col = COLOR_Gold;
|
||||
}
|
||||
|
||||
//if (!tmp) continue;
|
||||
|
@ -117,20 +117,17 @@ void gXAxis::paint(QPainter &painter, gGraph &w, const QRegion ®ion)
|
||||
// Allow zoom
|
||||
minx = w.min_x;
|
||||
maxx = w.max_x;
|
||||
|
||||
}
|
||||
|
||||
int days = ceil(double(maxx-minx) / 86400000.0);
|
||||
|
||||
bool buttuglydaysteps = !p_profile->appearance->animations();
|
||||
if (buttuglydaysteps) {
|
||||
if (m_roundDays && (days >= 1)) {
|
||||
if (m_roundDays) {
|
||||
minx = floor(double(minx)/86400000.0);
|
||||
minx *= 86400000L;
|
||||
|
||||
maxx = minx + 86400000L * qint64(days);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// duration of graph display window in milliseconds.
|
||||
qint64 xx = maxx - minx;
|
||||
@ -357,3 +354,96 @@ void gXAxis::paint(QPainter &painter, gGraph &w, const QRegion ®ion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
gXAxisDay::gXAxisDay(QColor col)
|
||||
:Layer(NoChannel)
|
||||
{
|
||||
m_line_color = col;
|
||||
m_text_color = col;
|
||||
m_major_color = Qt::darkGray;
|
||||
m_minor_color = Qt::lightGray;
|
||||
m_show_major_lines = false;
|
||||
m_show_minor_lines = false;
|
||||
m_show_minor_ticks = true;
|
||||
m_show_major_ticks = true;
|
||||
}
|
||||
gXAxisDay::~gXAxisDay()
|
||||
{
|
||||
}
|
||||
|
||||
int gXAxisDay::minimumHeight()
|
||||
{
|
||||
QFontMetrics fm(*defaultfont);
|
||||
int h = fm.height();
|
||||
#if defined(Q_OS_MAC)
|
||||
return 9+h;
|
||||
#else
|
||||
return 11+h;
|
||||
#endif
|
||||
}
|
||||
|
||||
void gXAxisDay::paint(QPainter &painter, gGraph &graph, const QRegion ®ion)
|
||||
{
|
||||
float left = region.boundingRect().left();
|
||||
float top = region.boundingRect().top();
|
||||
float width = region.boundingRect().width();
|
||||
float height = region.boundingRect().height();
|
||||
|
||||
QString months[] = {
|
||||
QObject::tr("Jan"), QObject::tr("Feb"), QObject::tr("Mar"), QObject::tr("Apr"), QObject::tr("May"), QObject::tr("Jun"),
|
||||
QObject::tr("Jul"), QObject::tr("Aug"), QObject::tr("Sep"), QObject::tr("Oct"), QObject::tr("Nov"), QObject::tr("Dec")
|
||||
};
|
||||
qint64 minx;
|
||||
qint64 maxx;
|
||||
|
||||
minx = graph.min_x;
|
||||
maxx = graph.max_x;
|
||||
|
||||
QDateTime date2 = QDateTime::fromMSecsSinceEpoch(minx);
|
||||
QDateTime enddate2 = QDateTime::fromMSecsSinceEpoch(maxx);
|
||||
|
||||
QDate date = date2.date();
|
||||
// QDate enddate = enddate2.date();
|
||||
|
||||
int days = ceil(double(maxx - minx) / 86400000.0);
|
||||
|
||||
float barw = width / float(days);
|
||||
|
||||
qint64 xx = maxx - minx;
|
||||
|
||||
// shouldn't really be negative, but this is safer than an assert
|
||||
if (xx <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
float lastx = left;
|
||||
float y1 = top;
|
||||
|
||||
QString fd = "Mjj 00";
|
||||
int x,y;
|
||||
GetTextExtent(fd, x, y);
|
||||
float xpos = (barw / 2.0) - (float(x) / 2.0);
|
||||
|
||||
float lastxpos = 0;
|
||||
QVector<QLine> lines;
|
||||
for (int i=0; i < days; i++) {
|
||||
if ((lastx + barw) > (left + width + 1))
|
||||
break;
|
||||
|
||||
QString tmpstr = QString("%1 %2").arg(months[date.month() - 1]).arg(date.day(), 2, 10, QChar('0'));
|
||||
|
||||
float x1 = lastx + xpos;
|
||||
//lines.append(QLine(lastx, top, lastx, top+6));
|
||||
if (x1 > (lastxpos + x + 8*graph.printScaleX())) {
|
||||
graph.renderText(tmpstr, x1, y1 + y + 8);
|
||||
lastxpos = x1;
|
||||
lines.append(QLine(lastx+barw/2, top, lastx+barw/2, top+6));
|
||||
}
|
||||
lastx = lastx + barw;
|
||||
date = date.addDays(1);
|
||||
}
|
||||
painter.setPen(QPen(Qt::black,1));
|
||||
painter.drawLines(lines);
|
||||
|
||||
}
|
||||
|
@ -89,4 +89,62 @@ class gXAxis: public Layer
|
||||
bool m_roundDays;
|
||||
};
|
||||
|
||||
class gXAxisDay: public Layer
|
||||
{
|
||||
public:
|
||||
static const int Margin = 30; // How much room does this take up. (Bottom margin)
|
||||
|
||||
public:
|
||||
gXAxisDay(QColor col = Qt::black);
|
||||
virtual ~gXAxisDay();
|
||||
|
||||
virtual void paint(QPainter &painter, gGraph &w, const QRegion ®ion);
|
||||
void SetShowMinorLines(bool b) { m_show_minor_lines = b; }
|
||||
void SetShowMajorLines(bool b) { m_show_major_lines = b; }
|
||||
bool ShowMinorLines() { return m_show_minor_lines; }
|
||||
bool ShowMajorLines() { return m_show_major_lines; }
|
||||
void SetShowMinorTicks(bool b) { m_show_minor_ticks = b; }
|
||||
void SetShowMajorTicks(bool b) { m_show_major_ticks = b; }
|
||||
bool ShowMinorTicks() { return m_show_minor_ticks; }
|
||||
bool ShowMajorTicks() { return m_show_major_ticks; }
|
||||
|
||||
//! \brief Returns the minimum height needed to fit
|
||||
virtual int minimumHeight();
|
||||
|
||||
virtual Layer * Clone() {
|
||||
gXAxisDay * xaxis = new gXAxisDay();
|
||||
Layer::CloneInto(xaxis);
|
||||
CloneInto(xaxis);
|
||||
return xaxis;
|
||||
}
|
||||
|
||||
void CloneInto(gXAxisDay * layer) {
|
||||
layer->m_show_major_ticks = m_show_major_ticks;
|
||||
layer->m_show_minor_ticks = m_show_minor_ticks;
|
||||
layer->m_show_major_lines = m_show_major_lines;
|
||||
layer->m_show_minor_lines = m_show_minor_lines;
|
||||
layer->m_major_color = m_major_color;
|
||||
layer->m_minor_color = m_minor_color;
|
||||
layer->m_line_color = m_line_color;
|
||||
layer->m_text_color = m_text_color;
|
||||
|
||||
layer->m_image = m_image;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
bool m_show_major_lines;
|
||||
bool m_show_minor_lines;
|
||||
bool m_show_minor_ticks;
|
||||
bool m_show_major_ticks;
|
||||
|
||||
QColor m_line_color;
|
||||
QColor m_text_color;
|
||||
QColor m_major_color;
|
||||
QColor m_minor_color;
|
||||
|
||||
QImage m_image;
|
||||
|
||||
};
|
||||
|
||||
#endif // GXAXIS_H
|
||||
|
@ -26,7 +26,7 @@ enum LayerPosition { LayerLeft, LayerRight, LayerTop, LayerBottom, LayerCenter,
|
||||
|
||||
enum ToolTipAlignment { TT_AlignCenter, TT_AlignLeft, TT_AlignRight };
|
||||
|
||||
enum LayerType { LT_Other = 0, LT_LineChart, LT_SummaryChart, LT_EventFlags, LT_Spacer, LT_SessionTimes };
|
||||
enum LayerType { LT_Other = 0, LT_LineChart, LT_SummaryChart, LT_EventFlags, LT_Spacer, LT_Overview };
|
||||
|
||||
/*! \class Layer
|
||||
\brief The base component for all individual Graph layers
|
||||
|
@ -18,6 +18,10 @@
|
||||
Day::Day()
|
||||
{
|
||||
d_firstsession = true;
|
||||
d_summaries_open = false;
|
||||
d_events_open = false;
|
||||
d_invalidate = true;
|
||||
|
||||
}
|
||||
Day::~Day()
|
||||
{
|
||||
@ -26,6 +30,22 @@ Day::~Day()
|
||||
}
|
||||
}
|
||||
|
||||
void Day::updateCPAPCache()
|
||||
{
|
||||
d_count.clear();
|
||||
d_sum.clear();
|
||||
OpenSummary();
|
||||
QList<ChannelID> channels = getSortedMachineChannels(MT_CPAP, schema::FLAG | schema::MINOR_FLAG | schema::SPAN);
|
||||
|
||||
int num_channels = channels.size();
|
||||
for (int i=0; i< num_channels; ++i) {
|
||||
ChannelID code = channels.at(i);
|
||||
d_count[code] = count(code);
|
||||
d_sum[code] = count(code);
|
||||
d_machhours[MT_CPAP] = hours(MT_CPAP);
|
||||
}
|
||||
}
|
||||
|
||||
Session * Day::firstSession(MachineType type)
|
||||
{
|
||||
for (int i=0; i<sessions.size(); i++) {
|
||||
@ -40,6 +60,7 @@ Session * Day::firstSession(MachineType type)
|
||||
|
||||
bool Day::addMachine(Machine *mach)
|
||||
{
|
||||
invalidate();
|
||||
if (!machines.contains(mach->type())) {
|
||||
machines[mach->type()] = mach;
|
||||
return true;
|
||||
@ -86,6 +107,7 @@ Session *Day::find(SessionID sessid)
|
||||
|
||||
void Day::addSession(Session *s)
|
||||
{
|
||||
invalidate();
|
||||
Q_ASSERT(s!=nullptr);
|
||||
QHash<MachineType, Machine *>::iterator mi = machines.find(s->machine()->type());
|
||||
|
||||
@ -435,6 +457,9 @@ EventDataType Day::percentile(ChannelID code, EventDataType percentile)
|
||||
return v1;
|
||||
}
|
||||
|
||||
if (valcnt.size() == 1) {
|
||||
return valcnt[0].value;
|
||||
}
|
||||
v2 = valcnt[k + 1].value;
|
||||
w2 = valcnt[k + 1].count;
|
||||
sum2 = sum1 + w2;
|
||||
@ -1211,17 +1236,31 @@ bool Day::channelHasData(ChannelID id)
|
||||
|
||||
void Day::OpenEvents()
|
||||
{
|
||||
if (d_events_open)
|
||||
return;
|
||||
Q_FOREACH(Session * session, sessions) {
|
||||
if (session->machine()->type() != MT_JOURNAL)
|
||||
session->OpenEvents();
|
||||
}
|
||||
d_events_open = true;
|
||||
}
|
||||
|
||||
void Day::OpenSummary()
|
||||
{
|
||||
if (d_summaries_open) return;
|
||||
Q_FOREACH(Session * session, sessions) {
|
||||
session->LoadSummary();
|
||||
}
|
||||
d_summaries_open = true;
|
||||
}
|
||||
|
||||
|
||||
void Day::CloseEvents()
|
||||
{
|
||||
Q_FOREACH(Session * session, sessions) {
|
||||
session->TrashEvents();
|
||||
}
|
||||
d_events_open = false;
|
||||
}
|
||||
|
||||
QList<ChannelID> Day::getSortedMachineChannels(MachineType type, quint32 chantype)
|
||||
|
@ -159,8 +159,18 @@ class Day
|
||||
bool hasEnabledSessions();
|
||||
|
||||
//! \brief Return the total time in decimal hours for this day
|
||||
EventDataType hours() { return double(total_time()) / 3600000.0; }
|
||||
EventDataType hours(MachineType type) { return double(total_time(type)) / 3600000.0; }
|
||||
EventDataType hours() {
|
||||
if (!d_invalidate) return d_hours;
|
||||
d_invalidate = false;
|
||||
return d_hours = double(total_time()) / 3600000.0;
|
||||
}
|
||||
EventDataType hours(MachineType type) {
|
||||
QHash<MachineType, EventDataType>::iterator it = d_machhours.find(type);
|
||||
if (it == d_machhours.end()) {
|
||||
return d_machhours[type] = double(total_time(type)) / 3600000.0;
|
||||
}
|
||||
return it.value();
|
||||
}
|
||||
|
||||
//! \brief Return the session indexed by i
|
||||
Session *operator [](int i) { return sessions[i]; }
|
||||
@ -183,6 +193,8 @@ class Day
|
||||
|
||||
//! \brief Loads all Events files for this Days Sessions
|
||||
void OpenEvents();
|
||||
void OpenSummary();
|
||||
|
||||
|
||||
//! \brief Closes all Events files for this Days Sessions
|
||||
void CloseEvents();
|
||||
@ -218,21 +230,21 @@ class Day
|
||||
//! \brief Calculate AHI (Apnea Hypopnea Index)
|
||||
EventDataType calcAHI() {
|
||||
EventDataType c = count(CPAP_Hypopnea) + count(CPAP_Obstructive) + count(CPAP_Apnea) + count(CPAP_ClearAirway);
|
||||
EventDataType minutes = hours() * 60.0;
|
||||
EventDataType minutes = hours(MT_CPAP) * 60.0;
|
||||
return (c * 60.0) / minutes;
|
||||
}
|
||||
|
||||
//! \brief Calculate RDI (Respiratory Disturbance Index)
|
||||
EventDataType calcRDI() {
|
||||
EventDataType c = count(CPAP_Hypopnea) + count(CPAP_Obstructive) + count(CPAP_Apnea) + count(CPAP_ClearAirway) + count(CPAP_RERA);
|
||||
EventDataType minutes = hours() * 60.0;
|
||||
EventDataType minutes = hours(MT_CPAP) * 60.0;
|
||||
return (c * 60.0) / minutes;
|
||||
}
|
||||
|
||||
//! \brief Percent of night for specified channel
|
||||
EventDataType calcPON(ChannelID code) {
|
||||
EventDataType c = sum(code);
|
||||
EventDataType minutes = hours() * 60.0;
|
||||
EventDataType minutes = hours(MT_CPAP) * 60.0;
|
||||
|
||||
return (100.0 / minutes) * (c / 60.0);
|
||||
}
|
||||
@ -240,7 +252,7 @@ class Day
|
||||
//! \brief Calculate index (count per hour) for specified channel
|
||||
EventDataType calcIdx(ChannelID code) {
|
||||
EventDataType c = count(code);
|
||||
EventDataType minutes = hours() * 60.0;
|
||||
EventDataType minutes = hours(MT_CPAP) * 60.0;
|
||||
|
||||
return (c * 60.0) / minutes;
|
||||
}
|
||||
@ -248,7 +260,7 @@ class Day
|
||||
//! \brief SleepyyHead Events Index, AHI combined with SleepyHead detected events.. :)
|
||||
EventDataType calcSHEI() {
|
||||
EventDataType c = count(CPAP_Hypopnea) + count(CPAP_Obstructive) + count(CPAP_Apnea) + count(CPAP_ClearAirway) + count(CPAP_UserFlag1) + count(CPAP_UserFlag2);
|
||||
EventDataType minutes = hours() * 60.0;
|
||||
EventDataType minutes = hours(MT_CPAP) * 60.0;
|
||||
return (c * 60.0) / minutes;
|
||||
}
|
||||
//! \brief Total duration of all Apnea/Hypopnea events in seconds,
|
||||
@ -279,6 +291,13 @@ class Day
|
||||
void decUseCounter() { d_useCounter--; if (d_useCounter<0) d_useCounter = 0; }
|
||||
int useCounter() { return d_useCounter; }
|
||||
|
||||
|
||||
void invalidate() {
|
||||
d_invalidate = true;
|
||||
d_machhours.clear();
|
||||
}
|
||||
|
||||
void updateCPAPCache();
|
||||
protected:
|
||||
|
||||
|
||||
@ -288,6 +307,13 @@ class Day
|
||||
private:
|
||||
bool d_firstsession;
|
||||
int d_useCounter;
|
||||
bool d_summaries_open;
|
||||
bool d_events_open;
|
||||
float d_hours;
|
||||
QHash<MachineType, EventDataType> d_machhours;
|
||||
QHash<ChannelID, long> d_count;
|
||||
QHash<ChannelID, double> d_sum;
|
||||
bool d_invalidate;
|
||||
};
|
||||
|
||||
|
||||
|
@ -2827,8 +2827,9 @@ void ResInitModelMap()
|
||||
resmed_codes[CPAP_PSMin].push_back("Min PS");
|
||||
resmed_codes[CPAP_PSMax].push_back("Max PS");
|
||||
|
||||
resmed_codes[CPAP_Leak].push_back("Leak");
|
||||
resmed_codes[CPAP_Leak].push_back("Leak"); // Leak Leck Lekk Läck Fuites
|
||||
resmed_codes[CPAP_Leak].push_back("Leck");
|
||||
resmed_codes[CPAP_Leak].push_back("Fuites");
|
||||
|
||||
resmed_codes[CPAP_Leak].push_back("\xE6\xBC\x8F\xE6\xB0\x94");
|
||||
resmed_codes[CPAP_Leak].push_back("Lekk");
|
||||
@ -2849,6 +2850,7 @@ void ResInitModelMap()
|
||||
resmed_codes[CPAP_TgMV].push_back("TgMV");
|
||||
resmed_codes[OXI_Pulse].push_back("Pulse");
|
||||
resmed_codes[OXI_Pulse].push_back("Puls");
|
||||
resmed_codes[OXI_Pulse].push_back("Pouls");
|
||||
resmed_codes[OXI_Pulse].push_back("Pols");
|
||||
resmed_codes[OXI_SPO2].push_back("SpO2");
|
||||
resmed_codes[CPAP_Obstructive].push_back("Obstructive apnea");
|
||||
|
@ -75,7 +75,7 @@ Session *Machine::SessionExists(SessionID session)
|
||||
}
|
||||
}
|
||||
|
||||
const quint16 sessinfo_version = 0;
|
||||
const quint16 sessinfo_version = 1;
|
||||
|
||||
bool Machine::saveSessionInfo()
|
||||
{
|
||||
@ -90,6 +90,8 @@ bool Machine::saveSessionInfo()
|
||||
out << filetype_sessenabled;
|
||||
out << sessinfo_version;
|
||||
|
||||
out << m_availableChannels;
|
||||
|
||||
QHash<SessionID, Session *>::iterator s;
|
||||
|
||||
out << (int)sessionlist.size();
|
||||
@ -131,6 +133,10 @@ bool Machine::loadSessionInfo()
|
||||
in >> ft16;
|
||||
in >> version;
|
||||
|
||||
if (version >= 1) {
|
||||
in >> m_availableChannels;
|
||||
}
|
||||
|
||||
int size;
|
||||
in >> size;
|
||||
|
||||
@ -184,8 +190,6 @@ QDate Machine::pickDate(qint64 first)
|
||||
|
||||
bool Machine::AddSession(Session *s)
|
||||
{
|
||||
invalidateCache();
|
||||
|
||||
Q_ASSERT(s != nullptr);
|
||||
Q_ASSERT(p_profile);
|
||||
Q_ASSERT(p_profile->isOpen());
|
||||
@ -388,7 +392,10 @@ bool Machine::Purge(int secret)
|
||||
QFile impfile(getDataPath()+"/imported_files.csv");
|
||||
impfile.remove();
|
||||
|
||||
QFile sumfile(getDataPath()+"Summaries.dat");
|
||||
QFile rxcache(p_profile->Get("{" + STR_GEN_DataFolder + "}/RXChanges.cache" ));
|
||||
rxcache.remove();
|
||||
|
||||
QFile sumfile(getDataPath()+"Summaries.xml");
|
||||
sumfile.remove();
|
||||
|
||||
// Create a copy of the list so the hash can be manipulated
|
||||
@ -413,6 +420,10 @@ bool Machine::Purge(int secret)
|
||||
QDir evdir(eventspath);
|
||||
evdir.removeRecursively();
|
||||
|
||||
QString summariespath = getSummariesPath();
|
||||
QDir sumdir(summariespath);
|
||||
sumdir.removeRecursively();
|
||||
|
||||
|
||||
// Clean up any straggling files (like from short sessions not being loaded...)
|
||||
dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
|
||||
@ -595,10 +606,10 @@ bool Machine::Load()
|
||||
|
||||
if (!ok) { continue; }
|
||||
|
||||
QString str = summarypath+filename;
|
||||
Session *sess = new Session(this, sessid);
|
||||
|
||||
if (sess->LoadSummary(str)) {
|
||||
// Forced to load it, because know nothing about this session..
|
||||
if (sess->LoadSummary()) {
|
||||
AddSession(sess);
|
||||
} else {
|
||||
qWarning() << "Error loading summary file" << filename;
|
||||
@ -854,26 +865,25 @@ bool Machine::LoadSummary(bool everything)
|
||||
|
||||
bool s_ok;
|
||||
|
||||
QString sumpath = getSummariesPath();
|
||||
QDomNodeList sessionlist = root.childNodes();
|
||||
|
||||
int size = sessionlist.size();
|
||||
for (int s=0; s < size; ++s) {
|
||||
node = sessionlist.at(s);
|
||||
QDomElement e = node.toElement();
|
||||
SessionID sessid = e.attribute("id", "0").toLong(&s_ok);
|
||||
qint64 first = e.attribute("first", 0).toLongLong();
|
||||
qint64 last = e.attribute("last", 0).toLongLong();
|
||||
|
||||
|
||||
|
||||
qint64 first = e.attribute("first", "0").toLongLong();
|
||||
qint64 last = e.attribute("last", "0").toLongLong();
|
||||
bool enabled = e.attribute("enabled", "1").toInt() == 1;
|
||||
bool events = e.attribute("events", "1").toInt() == 1;
|
||||
if (s_ok) {
|
||||
Session * sess = new Session(this, sessid);
|
||||
QString filename = sumpath + QString().sprintf("%08lx.000", sessid);
|
||||
if (sess->LoadSummary(filename)) {
|
||||
sess->really_set_first(first);
|
||||
sess->really_set_last(last);
|
||||
sess->setEnabled(enabled);
|
||||
sess->setSummaryOnly(!events);
|
||||
AddSession(sess);
|
||||
} else {
|
||||
delete sess;
|
||||
}
|
||||
// sess->LoadSummary();
|
||||
}
|
||||
}
|
||||
|
||||
@ -960,43 +970,29 @@ bool Machine::Save()
|
||||
return true;
|
||||
}
|
||||
|
||||
void Machine::invalidateCache()
|
||||
void Machine::updateChannels(Session * sess)
|
||||
{
|
||||
availableCache.clear();
|
||||
}
|
||||
|
||||
QList<ChannelID> & Machine::availableChannels(quint32 chantype)
|
||||
{
|
||||
QHash<quint32, QList<ChannelID> >::iterator ac = availableCache.find(chantype);
|
||||
if (ac != availableCache.end()) {
|
||||
return ac.value();
|
||||
|
||||
}
|
||||
|
||||
QHash<ChannelID, int> chanhash;
|
||||
|
||||
// look through the daylist and return a list of available channels for this machine
|
||||
QMap<QDate, Day *>::iterator dit;
|
||||
QMap<QDate, Day *>::iterator day_end = day.end();
|
||||
for (dit = day.begin(); dit != day_end; ++dit) {
|
||||
QList<Session *>::iterator sess_end = dit.value()->end();
|
||||
|
||||
for (QList<Session *>::iterator sit = dit.value()->begin(); sit != sess_end; ++sit) {
|
||||
Session * sess = (*sit);
|
||||
if (sess->machine() != this) continue;
|
||||
|
||||
int size = sess->m_availableChannels.size();
|
||||
for (int i=0; i < size; ++i) {
|
||||
ChannelID code = sess->m_availableChannels.at(i);
|
||||
QHash<ChannelID, schema::Channel *>::iterator ch = schema::channel.channels.find(code);
|
||||
schema::Channel * chan = ch.value();
|
||||
if (chan->type() & chantype) {
|
||||
chanhash[code]++;
|
||||
m_availableChannels[code] = true;
|
||||
}
|
||||
}
|
||||
|
||||
QList<ChannelID> Machine::availableChannels(quint32 chantype)
|
||||
{
|
||||
QList<ChannelID> list;
|
||||
|
||||
QHash<ChannelID, bool>::iterator end = m_availableChannels.end();
|
||||
QHash<ChannelID, bool>::iterator it;
|
||||
for (it = m_availableChannels.begin(); it != end; ++it) {
|
||||
ChannelID code = it.key();
|
||||
const schema::Channel & chan = schema::channel[code];
|
||||
if (chan.type() & chantype) {
|
||||
list.push_back(code);
|
||||
}
|
||||
}
|
||||
return availableCache[chantype] = chanhash.keys();
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
|
@ -103,6 +103,10 @@ class Machine
|
||||
|
||||
bool unlinkDay(Day * day);
|
||||
|
||||
inline bool hasChannel(ChannelID code) {
|
||||
return m_availableChannels.contains(code);
|
||||
}
|
||||
|
||||
//! \brief Contains a secondary index of day data, containing just this machines sessions
|
||||
QMap<QDate, Day *> day;
|
||||
|
||||
@ -202,8 +206,7 @@ class Machine
|
||||
|
||||
void setLoaderName(QString value);
|
||||
|
||||
QHash<quint32, QList<ChannelID> > availableCache;
|
||||
QList<ChannelID> & availableChannels(quint32 chantype);
|
||||
QList<ChannelID> availableChannels(quint32 chantype);
|
||||
|
||||
MachineLoader * loader() { return m_loader; }
|
||||
|
||||
@ -215,6 +218,8 @@ class Machine
|
||||
void setInfo(MachineInfo inf);
|
||||
const MachineInfo getInfo() { return info; }
|
||||
|
||||
void updateChannels(Session * sess);
|
||||
|
||||
protected:
|
||||
MachineInfo info;
|
||||
QDate firstday, lastday;
|
||||
@ -235,7 +240,7 @@ class Machine
|
||||
|
||||
QList<ImportTask *> m_tasklist;
|
||||
|
||||
void invalidateCache();
|
||||
QHash<ChannelID, bool> m_availableChannels;
|
||||
};
|
||||
|
||||
|
||||
|
@ -43,7 +43,7 @@ qint64 timezoneOffset();
|
||||
/*! \enum SummaryType
|
||||
\brief Calculation/Display method to select from dealing with summary information
|
||||
*/
|
||||
enum SummaryType { ST_CNT, ST_SUM, ST_AVG, ST_WAVG, ST_PERC, ST_90P, ST_MIN, ST_MAX, ST_CPH, ST_SPH, ST_FIRST, ST_LAST, ST_HOURS, ST_SESSIONS, ST_SETMIN, ST_SETAVG, ST_SETMAX, ST_SETWAVG, ST_SETSUM, ST_SESSIONID, ST_DATE };
|
||||
enum SummaryType { ST_CNT, ST_SUM, ST_AVG, ST_WAVG, ST_PERC, ST_90P, ST_MIN, ST_MAX, ST_MID, ST_CPH, ST_SPH, ST_FIRST, ST_LAST, ST_HOURS, ST_SESSIONS, ST_SETMIN, ST_SETAVG, ST_SETMAX, ST_SETWAVG, ST_SETSUM, ST_SESSIONID, ST_DATE };
|
||||
|
||||
/*! \enum MachineType
|
||||
\brief Generalized type of a machine
|
||||
|
@ -72,6 +72,19 @@ void MachineLoader::removeMachine(Machine * m)
|
||||
|
||||
}
|
||||
|
||||
Machine * MachineLoader::lookupMachine(QString serial)
|
||||
{
|
||||
QHash<QString, QHash<QString, Machine *> >::iterator mlit = MachineList.find(loaderName());
|
||||
if (mlit != MachineList.end()) {
|
||||
QHash<QString, Machine *>::iterator mit = mlit.value().find(serial);
|
||||
if (mit != mlit.value().end()) {
|
||||
return mit.value();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
Machine * MachineLoader::CreateMachine(MachineInfo info, MachineID id)
|
||||
{
|
||||
Q_ASSERT(p_profile != nullptr);
|
||||
|
@ -48,6 +48,7 @@ class MachineLoader: public QObject
|
||||
virtual int Version() = 0;
|
||||
|
||||
static Machine * CreateMachine(MachineInfo info, MachineID id = 0);
|
||||
Machine * lookupMachine(QString serial);
|
||||
|
||||
// !\\brief Used internally by loaders, override to return base MachineInfo record
|
||||
virtual MachineInfo newInfo() { return MachineInfo(); }
|
||||
|
@ -614,14 +614,36 @@ Day *Profile::GetGoodDay(QDate date, MachineType type)
|
||||
// For a machine match, find at least one enabled Session.
|
||||
for (int i = 0; i < day->size(); ++i) {
|
||||
Session * sess = (*day)[i];
|
||||
if (((type == MT_UNKNOWN) || (sess->machine()->type() == type)) && sess->enabled())
|
||||
if (((type == MT_UNKNOWN) || (sess->machine()->type() == type)) && sess->enabled()) {
|
||||
day->OpenSummary();
|
||||
|
||||
return day;
|
||||
}
|
||||
}
|
||||
|
||||
// No enabled Sessions were found.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Day *Profile::FindGoodDay(QDate date, MachineType type)
|
||||
{
|
||||
Day *day = FindDay(date, type);
|
||||
if (!day)
|
||||
return nullptr;
|
||||
|
||||
// For a machine match, find at least one enabled Session.
|
||||
for (int i = 0; i < day->size(); ++i) {
|
||||
Session * sess = (*day)[i];
|
||||
if (((type == MT_UNKNOWN) || (sess->machine()->type() == type)) && sess->enabled()) {
|
||||
return day;
|
||||
}
|
||||
}
|
||||
|
||||
// No enabled Sessions were found.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
Day *Profile::GetDay(QDate date, MachineType type)
|
||||
{
|
||||
QMap<QDate, Day *>::iterator di = daylist.find(date);
|
||||
@ -629,9 +651,33 @@ Day *Profile::GetDay(QDate date, MachineType type)
|
||||
|
||||
Day * day = di.value();
|
||||
|
||||
if (type == MT_UNKNOWN) return day; // just want the day record
|
||||
if (type == MT_UNKNOWN) {
|
||||
day->OpenSummary();
|
||||
return day; // just want the day record
|
||||
}
|
||||
|
||||
if (day->machines.contains(type)) return day;
|
||||
if (day->machines.contains(type)) {
|
||||
day->OpenSummary();
|
||||
return day;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Day *Profile::FindDay(QDate date, MachineType type)
|
||||
{
|
||||
QMap<QDate, Day *>::iterator di = daylist.find(date);
|
||||
if (di == daylist.end()) return nullptr;
|
||||
|
||||
Day * day = di.value();
|
||||
|
||||
if (type == MT_UNKNOWN) {
|
||||
return day; // just want the day record
|
||||
}
|
||||
|
||||
if (day->machines.contains(type)) {
|
||||
return day;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@ -923,7 +969,7 @@ int Profile::countDays(MachineType mt, QDate start, QDate end)
|
||||
int days = 0;
|
||||
|
||||
do {
|
||||
Day *day = GetGoodDay(date, mt);
|
||||
Day *day = FindGoodDay(date, mt);
|
||||
|
||||
if (day) {
|
||||
days++;
|
||||
@ -1552,7 +1598,7 @@ QDate Profile::FirstDay(MachineType mt)
|
||||
QDate d = m_first;
|
||||
|
||||
do {
|
||||
if (GetDay(d, mt) != nullptr) {
|
||||
if (FindDay(d, mt) != nullptr) {
|
||||
return d;
|
||||
}
|
||||
|
||||
@ -1572,7 +1618,7 @@ QDate Profile::LastDay(MachineType mt)
|
||||
QDate d = m_last;
|
||||
|
||||
do {
|
||||
if (GetDay(d, mt) != nullptr) {
|
||||
if (FindDay(d, mt) != nullptr) {
|
||||
return d;
|
||||
}
|
||||
|
||||
@ -1597,7 +1643,7 @@ QDate Profile::FirstGoodDay(MachineType mt)
|
||||
}
|
||||
|
||||
do {
|
||||
if (GetGoodDay(d, mt) != nullptr) {
|
||||
if (FindGoodDay(d, mt) != nullptr) {
|
||||
return d;
|
||||
}
|
||||
|
||||
@ -1620,7 +1666,7 @@ QDate Profile::LastGoodDay(MachineType mt)
|
||||
}
|
||||
|
||||
do {
|
||||
if (GetGoodDay(d, mt) != nullptr) {
|
||||
if (FindGoodDay(d, mt) != nullptr) {
|
||||
return d;
|
||||
}
|
||||
|
||||
@ -1629,6 +1675,20 @@ QDate Profile::LastGoodDay(MachineType mt)
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
bool Profile::channelAvailable(ChannelID code)
|
||||
{
|
||||
QHash<MachineID, Machine *>::iterator it;
|
||||
QHash<MachineID, Machine *>::iterator machlist_end=machlist.end();
|
||||
|
||||
for (it = machlist.begin(); it != machlist_end; it++) {
|
||||
Machine * mach = it.value();
|
||||
if (mach->hasChannel(code))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Profile::hasChannel(ChannelID code)
|
||||
{
|
||||
QDate d = LastDay();
|
||||
@ -1648,6 +1708,7 @@ bool Profile::hasChannel(ChannelID code)
|
||||
if (dit != daylist.end()) {
|
||||
Day *day = dit.value();
|
||||
|
||||
|
||||
if (day->channelHasData(code)) {
|
||||
found = true;
|
||||
break;
|
||||
|
@ -87,10 +87,16 @@ class Profile : public Preferences
|
||||
//! \brief Get Day record if data available for date and machine type, else return nullptr
|
||||
Day *GetDay(QDate date, MachineType type = MT_UNKNOWN);
|
||||
|
||||
//! \brief Same as GetDay but does not open the summaries
|
||||
Day *FindDay(QDate date, MachineType type = MT_UNKNOWN);
|
||||
|
||||
//! \brief Get Day record if data available for date and machine type,
|
||||
// and has enabled session data, else return nullptr
|
||||
Day *GetGoodDay(QDate date, MachineType type);
|
||||
|
||||
//! \breif Same as GetGoodDay but does not open the summaries
|
||||
Day *FindGoodDay(QDate date, MachineType type);
|
||||
|
||||
//! \brief Returns a list of all machines of type t
|
||||
QList<Machine *> GetMachines(MachineType t = MT_UNKNOWN);
|
||||
|
||||
@ -147,6 +153,11 @@ class Profile : public Preferences
|
||||
//! \brief Tests if Channel code is available in all day sets
|
||||
bool hasChannel(ChannelID code);
|
||||
|
||||
|
||||
//! \brief Looks up if any machines report channel is available
|
||||
bool channelAvailable(ChannelID code);
|
||||
|
||||
|
||||
//! \brief Calculates the minimum session settings value for channel code, between start and end dates
|
||||
EventDataType calcSettingsMin(ChannelID code, MachineType mt = MT_CPAP, QDate start = QDate(),
|
||||
QDate end = QDate());
|
||||
|
@ -142,11 +142,11 @@ void init()
|
||||
|
||||
schema::channel.add(GRP_CPAP, new Channel(CPAP_PressureMin = 0x1020, SETTING, MT_CPAP, SESSION,
|
||||
"PressureMin", QObject::tr("Min Pressure") , QObject::tr("Minimum Therapy Pressure"),
|
||||
QObject::tr("Pressure Min"), STR_UNIT_CMH2O, DEFAULT, QColor("black")));
|
||||
QObject::tr("Pressure Min"), STR_UNIT_CMH2O, DEFAULT, QColor("orange")));
|
||||
|
||||
schema::channel.add(GRP_CPAP, new Channel(CPAP_PressureMax = 0x1021, SETTING, MT_CPAP, SESSION,
|
||||
"PressureMax", QObject::tr("Max Pressure"), QObject::tr("Maximum Therapy Pressure"),
|
||||
QObject::tr("Pressure Max"), STR_UNIT_CMH2O, DEFAULT, QColor("black")));
|
||||
QObject::tr("Pressure Max"), STR_UNIT_CMH2O, DEFAULT, QColor("light blue")));
|
||||
|
||||
schema::channel.add(GRP_CPAP, new Channel(CPAP_RampTime = 0x1022, SETTING, MT_CPAP, SESSION,
|
||||
"RampTime", QObject::tr("Ramp Time") , QObject::tr("Ramp Delay Period"),
|
||||
@ -170,13 +170,13 @@ void init()
|
||||
|
||||
|
||||
schema::channel.add(GRP_CPAP, new Channel(CPAP_ClearAirway = 0x1001, FLAG, MT_CPAP, SESSION,
|
||||
"ClearAirway", QObject::tr("Clear Airway Apnea"),
|
||||
"ClearAirway", QObject::tr("Clear Airway"),
|
||||
QObject::tr("An apnea where the airway is open"),
|
||||
QObject::tr("CA"), STR_UNIT_EventsPerHour, DEFAULT, QColor("purple")));
|
||||
|
||||
|
||||
schema::channel.add(GRP_CPAP, new Channel(CPAP_Obstructive = 0x1002, FLAG, MT_CPAP, SESSION,
|
||||
"Obstructive", QObject::tr("Obstructive Apnea"),
|
||||
"Obstructive", QObject::tr("Obstructive"),
|
||||
QObject::tr("An apnea caused by airway obstruction"),
|
||||
QObject::tr("OA"), STR_UNIT_EventsPerHour, DEFAULT, QColor("#40c0ff")));
|
||||
|
||||
@ -197,9 +197,9 @@ void init()
|
||||
QObject::tr("FL"), STR_UNIT_EventsPerHour, DEFAULT, QColor("#404040")));
|
||||
|
||||
schema::channel.add(GRP_CPAP, new Channel(CPAP_RERA = 0x1006, FLAG, MT_CPAP, SESSION, "RERA",
|
||||
QObject::tr("Respiratory Effort Related Arousal"),
|
||||
QObject::tr("An restriction in breathing that causes an either an awakening or sleep disturbance."),
|
||||
QObject::tr("RE"), STR_UNIT_EventsPerHour, DEFAULT, QColor("gold")));
|
||||
QObject::tr("RERA"),
|
||||
QObject::tr("Respiratory Effort Related Arousal: An restriction in breathing that causes an either an awakening or sleep disturbance."),
|
||||
QObject::tr("RE"), STR_UNIT_EventsPerHour, DEFAULT, COLOR_Gold));
|
||||
|
||||
schema::channel.add(GRP_CPAP, new Channel(CPAP_VSnore = 0x1007, FLAG, MT_CPAP, SESSION, "VSnore",
|
||||
QObject::tr("Vibratory Snore"), QObject::tr("A vibratory snore"),
|
||||
@ -235,7 +235,7 @@ void init()
|
||||
schema::channel.add(GRP_CPAP, new Channel(CPAP_SensAwake = 0x100d, FLAG, MT_CPAP, SESSION,
|
||||
"SensAwake", QObject::tr("SensAwake"),
|
||||
QObject::tr("SensAwake feature will reduce pressure when waking is detected."),
|
||||
QObject::tr("SA"), STR_UNIT_EventsPerHour, DEFAULT, QColor("gold")));
|
||||
QObject::tr("SA"), STR_UNIT_EventsPerHour, DEFAULT, COLOR_Gold));
|
||||
|
||||
schema::channel.add(GRP_CPAP, new Channel(CPAP_UserFlag1 = 0x101e, FLAG, MT_CPAP, SESSION,
|
||||
"UserFlag1", QObject::tr("User Flag #1"),
|
||||
@ -592,6 +592,7 @@ Channel::Channel(ChannelID id, ChanType type, MachineType machtype, ScopeType sc
|
||||
calc[Calc_LowerThresh] = ChannelCalc(id, Calc_LowerThresh, Qt::blue, false);
|
||||
calc[Calc_UpperThresh] = ChannelCalc(id, Calc_UpperThresh, Qt::red, false);
|
||||
}
|
||||
m_showInOverview = false;
|
||||
}
|
||||
bool Channel::isNull()
|
||||
{
|
||||
@ -871,6 +872,7 @@ bool ChannelList::Save(QString filename)
|
||||
cn.setAttribute("order", chan->order());
|
||||
cn.setAttribute("type", chan->type());
|
||||
cn.setAttribute("datatype", chan->datatype());
|
||||
cn.setAttribute("overview", chan->showInOverview());
|
||||
QHash<int, QString>::iterator op;
|
||||
for (op = chan->m_options.begin(); op!=chan->m_options.end(); ++op) {
|
||||
QDomElement c2 = doc.createElement("option");
|
||||
|
@ -87,7 +87,7 @@ extern Channel EmptyChannel;
|
||||
class Channel
|
||||
{
|
||||
public:
|
||||
Channel() { m_id = 0; m_upperThreshold = 0; m_lowerThreshold = 0; m_enabled = true; m_order = 255; m_machtype = MT_UNKNOWN; }
|
||||
Channel() { m_id = 0; m_upperThreshold = 0; m_lowerThreshold = 0; m_enabled = true; m_order = 255; m_machtype = MT_UNKNOWN; m_showInOverview = false; }
|
||||
Channel(ChannelID id, ChanType type, MachineType machtype, ScopeType scope, QString code, QString fullname,
|
||||
QString description, QString label, QString unit, DataType datatype = DEFAULT, QColor = Qt::black,
|
||||
int link = 0);
|
||||
@ -105,6 +105,8 @@ class Channel
|
||||
const QString &units() { return m_unit; }
|
||||
inline short order() const { return m_order; }
|
||||
|
||||
bool showInOverview() { return m_showInOverview; }
|
||||
|
||||
inline EventDataType upperThreshold() const { return m_upperThreshold; }
|
||||
inline EventDataType lowerThreshold() const { return m_lowerThreshold; }
|
||||
inline QColor upperThresholdColor() const { return m_upperThresholdColor; }
|
||||
@ -124,6 +126,8 @@ class Channel
|
||||
void setLowerThresholdColor(QColor color) { m_lowerThresholdColor = color; }
|
||||
void setOrder(short order) { m_order = order; }
|
||||
|
||||
void setShowInOverview(bool b) { m_showInOverview = b; }
|
||||
|
||||
QString option(int i) {
|
||||
if (m_options.contains(i)) {
|
||||
return m_options[i];
|
||||
@ -170,6 +174,8 @@ class Channel
|
||||
|
||||
bool m_enabled;
|
||||
short m_order;
|
||||
|
||||
bool m_showInOverview;
|
||||
};
|
||||
|
||||
/*! \class ChannelList
|
||||
|
@ -38,6 +38,7 @@ Session::Session(Machine *m, SessionID session)
|
||||
s_session = session;
|
||||
s_changed = false;
|
||||
s_events_loaded = false;
|
||||
s_summary_loaded = false;
|
||||
_first_session = true;
|
||||
s_enabled = -1;
|
||||
|
||||
@ -110,7 +111,7 @@ bool Session::OpenEvents()
|
||||
// qWarning() << "Error Loading Events" << filename;
|
||||
return false;
|
||||
}
|
||||
qDebug() << "opening" << filename;
|
||||
qDebug() << "Loading" << s_machine->loaderName() << "Events" << filename;
|
||||
|
||||
return s_events_loaded = true;
|
||||
}
|
||||
@ -263,7 +264,15 @@ bool Session::StoreSummary()
|
||||
QString filename = s_machine->getSummariesPath() + QString().sprintf("%08lx.000", s_session);
|
||||
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::WriteOnly);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
QDir dir;
|
||||
dir.mkpath(s_machine->getSummariesPath());
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qDebug() << "Summary open for writing failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QDataStream out(&file);
|
||||
out.setVersion(QDataStream::Qt_4_6);
|
||||
@ -319,9 +328,13 @@ bool Session::StoreSummary()
|
||||
}
|
||||
|
||||
|
||||
bool Session::LoadSummary(QString filename)
|
||||
bool Session::LoadSummary()
|
||||
{
|
||||
s_changed = true;
|
||||
static int sumcnt = 0;
|
||||
|
||||
if (s_summary_loaded) return true;
|
||||
QString filename = s_machine->getSummariesPath() + QString().sprintf("%08lx.000", s_session);
|
||||
|
||||
|
||||
if (filename.isEmpty()) {
|
||||
qDebug() << "Empty summary filename";
|
||||
@ -335,6 +348,9 @@ bool Session::LoadSummary(QString filename)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
qDebug() << "Loading" << s_machine->loaderName() << "Summary" << filename << sumcnt++;
|
||||
|
||||
QDataStream in(&file);
|
||||
in.setVersion(QDataStream::Qt_4_6);
|
||||
in.setByteOrder(QDataStream::LittleEndian);
|
||||
@ -564,9 +580,10 @@ bool Session::LoadSummary(QString filename)
|
||||
} else {
|
||||
// summary only upgrades go here.
|
||||
}
|
||||
SetChanged(true);
|
||||
StoreSummary();
|
||||
}
|
||||
|
||||
s_summary_loaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1083,6 +1100,8 @@ void Session::UpdateSummaries()
|
||||
}
|
||||
}
|
||||
timeAboveThreshold(CPAP_Leak, p_profile->cpap->leakRedline());
|
||||
|
||||
s_machine->updateChannels(this);
|
||||
}
|
||||
|
||||
EventDataType Session::SearchValue(ChannelID code, qint64 time, bool square)
|
||||
|
@ -53,6 +53,8 @@ public:
|
||||
*/
|
||||
class Session
|
||||
{
|
||||
friend class Day;
|
||||
friend class Machine;
|
||||
public:
|
||||
/*! \fn Session(Machine *,SessionID);
|
||||
\brief Create a session object belonging to Machine, with supplied SessionID
|
||||
@ -83,7 +85,7 @@ class Session
|
||||
void LoadSummaryData(QDataStream & in);
|
||||
|
||||
//! \brief Loads the Sessions Summary Indexes from filename, from SleepLibs custom data format.
|
||||
bool LoadSummary(QString filename);
|
||||
bool LoadSummary();
|
||||
|
||||
//! \brief Loads the Sessions EventLists from filename, from SleepLibs custom data format.
|
||||
bool LoadEvents(QString filename);
|
||||
@ -389,6 +391,7 @@ protected:
|
||||
bool _first_session;
|
||||
bool s_summaryOnly;
|
||||
|
||||
bool s_summary_loaded;
|
||||
bool s_events_loaded;
|
||||
bool s_enabled;
|
||||
|
||||
|
@ -693,11 +693,11 @@ void Daily::UpdateCalendarDay(QDate date)
|
||||
nodata.setForeground(QBrush(COLOR_Black, Qt::SolidPattern));
|
||||
nodata.setFontWeight(QFont::Normal);
|
||||
|
||||
bool hascpap=p_profile->GetDay(date,MT_CPAP)!=nullptr;
|
||||
bool hasoxi=p_profile->GetDay(date,MT_OXIMETER)!=nullptr;
|
||||
bool hasjournal=p_profile->GetDay(date,MT_JOURNAL)!=nullptr;
|
||||
bool hasstage=p_profile->GetDay(date,MT_SLEEPSTAGE)!=nullptr;
|
||||
bool haspos=p_profile->GetDay(date,MT_POSITION)!=nullptr;
|
||||
bool hascpap=p_profile->FindDay(date,MT_CPAP)!=nullptr;
|
||||
bool hasoxi=p_profile->FindDay(date,MT_OXIMETER)!=nullptr;
|
||||
bool hasjournal=p_profile->FindDay(date,MT_JOURNAL)!=nullptr;
|
||||
bool hasstage=p_profile->FindDay(date,MT_SLEEPSTAGE)!=nullptr;
|
||||
bool haspos=p_profile->FindDay(date,MT_POSITION)!=nullptr;
|
||||
if (hascpap) {
|
||||
if (hasoxi) {
|
||||
ui->calendar->setDateTextFormat(date,oxicpap);
|
||||
@ -1334,18 +1334,11 @@ void Daily::Load(QDate date)
|
||||
Day * d = di.value();
|
||||
if (d->eventsLoaded()) {
|
||||
if (d->useCounter() == 0) {
|
||||
for (QList<Session *>::iterator s=d->begin();s!=d->end();++s) {
|
||||
(*s)->TrashEvents();
|
||||
d->CloseEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (lastcpapday->useCounter() == 0) {
|
||||
// for (QList<Session *>::iterator s=lastcpapday->begin();s!=lastcpapday->end();++s) {
|
||||
// (*s)->TrashEvents();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// Don't really see a point in unlinked oximetery sessions anymore... All I can say is BLEH...
|
||||
@ -1625,7 +1618,7 @@ void Daily::Load(QDate date)
|
||||
html+="</body></html>";
|
||||
|
||||
QColor cols[]={
|
||||
QColor("gold"),
|
||||
COLOR_Gold,
|
||||
QColor("light blue"),
|
||||
};
|
||||
const int maxcolors=sizeof(cols)/sizeof(QColor);
|
||||
@ -2262,7 +2255,8 @@ void Daily::on_ZombieMeter_valueChanged(int action)
|
||||
journal->settings[Journal_ZombieMeter]=ui->ZombieMeter->value();
|
||||
journal->SetChanged(true);
|
||||
|
||||
if (mainwin->getOverview()) mainwin->getOverview()->ResetGraph("Zombie");
|
||||
// shouldn't be needed anymore with new overview model..
|
||||
//if (mainwin->getOverview()) mainwin->getOverview()->ResetGraph("Zombie");
|
||||
}
|
||||
|
||||
void Daily::on_bookmarkTable_itemChanged(QTableWidgetItem *item)
|
||||
@ -2363,7 +2357,9 @@ void Daily::on_ouncesSpinBox_editingFinished()
|
||||
}
|
||||
}
|
||||
journal->SetChanged(true);
|
||||
if (mainwin->getOverview()) mainwin->getOverview()->ResetGraph(STR_GRAPH_Weight);
|
||||
|
||||
// shouldn't be needed anymore with new overview model
|
||||
//if (mainwin->getOverview()) mainwin->getOverview()->ResetGraph(STR_GRAPH_Weight);
|
||||
}
|
||||
|
||||
QString Daily::GetDetailsText()
|
||||
|
@ -336,7 +336,7 @@ void MainWindow::on_changeWarningMessage()
|
||||
}
|
||||
|
||||
|
||||
quint16 chandata_version = 0;
|
||||
quint16 chandata_version = 1;
|
||||
|
||||
void saveChannels()
|
||||
{
|
||||
@ -370,6 +370,7 @@ void saveChannels()
|
||||
out << chan->lowerThresholdColor();
|
||||
out << chan->upperThreshold();
|
||||
out << chan->upperThresholdColor();
|
||||
out << chan->showInOverview();
|
||||
}
|
||||
|
||||
f.close();
|
||||
@ -420,6 +421,7 @@ void loadChannels()
|
||||
QString fullname;
|
||||
QString label;
|
||||
QString description;
|
||||
bool showOverview = false;
|
||||
|
||||
for (int i=0; i < size; i++) {
|
||||
in >> code;
|
||||
@ -438,6 +440,10 @@ void loadChannels()
|
||||
in >> lowerThresholdColor;
|
||||
in >> upperThreshold;
|
||||
in >> upperThresholdColor;
|
||||
if (version >= 1) {
|
||||
in >> showOverview;
|
||||
}
|
||||
|
||||
if (chan->isNull()) {
|
||||
qDebug() << "loadChannels has no idea about channel" << name;
|
||||
if (in.atEnd()) return;
|
||||
@ -452,6 +458,8 @@ void loadChannels()
|
||||
chan->setLowerThresholdColor(lowerThresholdColor);
|
||||
chan->setUpperThreshold(upperThreshold);
|
||||
chan->setUpperThresholdColor(upperThresholdColor);
|
||||
|
||||
chan->setShowInOverview(showOverview);
|
||||
if (in.atEnd()) return;
|
||||
}
|
||||
|
||||
@ -1470,8 +1478,8 @@ void MainWindow::on_action_Preferences_triggered()
|
||||
}
|
||||
|
||||
if (overview) {
|
||||
overview->ReloadGraphs();
|
||||
overview->RedrawGraphs();
|
||||
overview->RebuildGraphs(true);
|
||||
//overview->RedrawGraphs();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,23 +114,19 @@ Overview::Overview(QWidget *parent, gGraphView *shared) :
|
||||
|
||||
ui->dateLayout->addWidget(dateLabel,1);
|
||||
|
||||
|
||||
|
||||
// uc = new SummaryChart(STR_UNIT_Hours, GT_BAR);
|
||||
// uc->addSlice(NoChannel, COLOR_Green, ST_HOURS);
|
||||
// UC->AddLayer(uc);
|
||||
|
||||
/* return;
|
||||
|
||||
// TODO: Automate graph creation process
|
||||
ChannelID ahicode = p_profile->general->calculateRDI() ? CPAP_RDI : CPAP_AHI;
|
||||
|
||||
if (ahicode == CPAP_RDI) {
|
||||
AHI = createGraph(STR_GRAPH_AHI, STR_TR_RDI, tr("Respiratory\nDisturbance\nIndex"));
|
||||
} else {
|
||||
AHI = createGraph(STR_GRAPH_AHI, STR_TR_AHI, tr("Apnea\nHypopnea\nIndex"));
|
||||
}
|
||||
|
||||
|
||||
STG = createGraph("New Session", tr("Session Times2"), tr("Session Times"), YT_Time);
|
||||
stg = new gSessionTimesChart("STG", MT_CPAP);
|
||||
STG->AddLayer(stg);
|
||||
|
||||
UC = createGraph(STR_GRAPH_Usage, tr("Usage"), tr("Usage\n(hours)"));
|
||||
|
||||
FL = createGraph(schema::channel[CPAP_FlowLimit].code(), schema::channel[CPAP_FlowLimit].label(), STR_TR_FlowLimit);
|
||||
|
||||
float percentile = p_profile->general->prefCalcPercentile() / 100.0;
|
||||
int mididx = p_profile->general->prefCalcMiddle();
|
||||
@ -144,32 +140,13 @@ Overview::Overview(QWidget *parent, gGraphView *shared) :
|
||||
const EventDataType maxperc = 0.995F;
|
||||
|
||||
US = createGraph(STR_GRAPH_SessionTimes, tr("Session Times"), tr("Session Times\n(hours)"), YT_Time);
|
||||
PR = createGraph("Pressure", STR_TR_Pressure, STR_TR_Pressure + "\n(" + STR_UNIT_CMH2O + ")");
|
||||
SET = createGraph("Settings", STR_TR_Settings, STR_TR_Settings);
|
||||
LK = createGraph("Leaks", STR_TR_Leaks, STR_TR_UnintentionalLeaks + "\n(" + STR_UNIT_LPM + ")");
|
||||
TOTLK = createGraph("TotalLeaks", STR_TR_TotalLeaks, STR_TR_TotalLeaks + "\n(" + STR_UNIT_LPM + ")");
|
||||
NPB = createGraph("TimeInPB", tr("% in %1").arg(schema::channel[CPAP_CSR].label()), tr("%1\n(% of night)").arg(schema::channel[CPAP_LargeLeak].description()));
|
||||
NLL = createGraph("TimeInLL", tr("% in %1").arg(schema::channel[CPAP_LargeLeak].label()), tr("Large Leaks\n(% of night)"));
|
||||
|
||||
if (ahicode == CPAP_RDI) {
|
||||
AHIHR = createGraph(STR_GRAPH_PeakAHI, tr("Peak RDI"), tr("Peak RDI\nShows RDI Clusters\n(RDI/hr)"));
|
||||
} else {
|
||||
AHIHR = createGraph(STR_GRAPH_PeakAHI, tr("Peak AHI"), tr("Peak AHI\nShows AHI Clusters\n(AHI/hr)"));
|
||||
}
|
||||
|
||||
RR = createGraph(schema::channel[CPAP_RespRate].code(), schema::channel[CPAP_RespRate].label(), schema::channel[CPAP_RespRate].fullname()+"\n"+schema::channel[CPAP_RespRate].units());
|
||||
TV = createGraph(schema::channel[CPAP_TidalVolume].code(),schema::channel[CPAP_TidalVolume].label(), tr("Tidal\nVolume\n(ml)"));
|
||||
MV = createGraph(schema::channel[CPAP_MinuteVent].code(), schema::channel[CPAP_MinuteVent].label(), tr("Minute\nVentilation\n(L/min)"));
|
||||
TGMV = createGraph(schema::channel[CPAP_TgMV].code(), schema::channel[CPAP_TgMV].label(), tr("Target\nVentilation\n(L/min)"));
|
||||
PTB = createGraph(schema::channel[CPAP_PTB].code(), schema::channel[CPAP_PTB].label(), tr("Patient\nTriggered\nBreaths\n(%)"));
|
||||
SES = createGraph(STR_GRAPH_Sessions, STR_TR_Sessions, STR_TR_Sessions + tr("\n(count)"));
|
||||
PULSE = createGraph(schema::channel[OXI_Pulse].code(), schema::channel[OXI_Pulse].label(), STR_TR_PulseRate + "\n(" + STR_UNIT_BPM + ")");
|
||||
SPO2 = createGraph(schema::channel[OXI_SPO2].code(), schema::channel[OXI_SPO2].label(), tr("Oxygen Saturation\n(%)"));
|
||||
SA = createGraph(schema::channel[CPAP_SensAwake].code(), schema::channel[CPAP_SensAwake].label(), tr("SensAwake\n(count)"));
|
||||
|
||||
WEIGHT = createGraph(STR_GRAPH_Weight, STR_TR_Weight, STR_TR_Weight, YT_Weight);
|
||||
BMI = createGraph(STR_GRAPH_BMI, STR_TR_BMI, tr("Body\nMass\nIndex"));
|
||||
ZOMBIE = createGraph(STR_GRAPH_Zombie, STR_TR_Zombie, tr("How you felt\n(0-10)"));
|
||||
|
||||
ahihr = new SummaryChart(STR_UNIT_EventsPerHour, GT_POINTS);
|
||||
ahihr->addSlice(ahicode, COLOR_Blue, ST_MAX);
|
||||
@ -205,9 +182,6 @@ Overview::Overview(QWidget *parent, gGraphView *shared) :
|
||||
spo2->addSlice(OXI_SPO2, COLOR_Blue, ST_MIN);
|
||||
SPO2->AddLayer(spo2);
|
||||
|
||||
uc = new SummaryChart(STR_UNIT_Hours, GT_BAR);
|
||||
uc->addSlice(NoChannel, COLOR_Green, ST_HOURS);
|
||||
UC->AddLayer(uc);
|
||||
|
||||
fl = new SummaryChart(STR_TR_FL, GT_POINTS);
|
||||
fl->addSlice(CPAP_FlowLimit, COLOR_Brown, ST_CPH);
|
||||
@ -296,13 +270,6 @@ Overview::Overview(QWidget *parent, gGraphView *shared) :
|
||||
// Added in summarychart.. Slightly annoying..
|
||||
PR->AddLayer(pr);
|
||||
|
||||
lk = new SummaryChart(STR_TR_Leaks, GT_POINTS);
|
||||
lk->addSlice(CPAP_Leak, COLOR_LightBlue, ST_mid, 0.5);
|
||||
lk->addSlice(CPAP_Leak, COLOR_DarkGray, ST_PERC, percentile);
|
||||
//lk->addSlice(CPAP_Leak,COLOR_DarkBlue,ST_WAVG);
|
||||
lk->addSlice(CPAP_Leak, COLOR_Gray, ST_max, maxperc);
|
||||
//lk->addSlice(CPAP_Leak,COLOR_DarkYellow);
|
||||
LK->AddLayer(lk);
|
||||
|
||||
totlk = new SummaryChart(STR_TR_TotalLeaks, GT_POINTS);
|
||||
totlk->addSlice(CPAP_LeakTotal, COLOR_LightBlue, ST_mid, 0.5);
|
||||
@ -312,21 +279,25 @@ Overview::Overview(QWidget *parent, gGraphView *shared) :
|
||||
//tot->addSlice(CPAP_Leak, COLOR_DarkYellow);
|
||||
TOTLK->AddLayer(totlk);
|
||||
|
||||
NPB->AddLayer(npb = new SummaryChart(tr("% PB"), GT_POINTS));
|
||||
npb->addSlice(CPAP_CSR, schema::channel[CPAP_CSR].defaultColor(), ST_SPH);
|
||||
|
||||
NLL->AddLayer(nll = new SummaryChart(tr("% %1").arg(schema::channel[CPAP_LargeLeak].fullname()), GT_POINTS));
|
||||
nll->addSlice(CPAP_LargeLeak, schema::channel[CPAP_LargeLeak].defaultColor(), ST_SPH);
|
||||
// <--- The code to the previous marker is crap
|
||||
|
||||
AHI->setPinned(false);
|
||||
ui->rangeCombo->setCurrentIndex(p_profile->general->lastOverviewRange());
|
||||
icon_on = new QIcon(":/icons/session-on.png");
|
||||
icon_off = new QIcon(":/icons/session-off.png");
|
||||
SES->setRecMinY(1);
|
||||
SET->setRecMinY(0);
|
||||
|
||||
//SET->setRecMaxY(5);
|
||||
|
||||
*/
|
||||
RebuildGraphs(false);
|
||||
|
||||
ui->rangeCombo->setCurrentIndex(p_profile->general->lastOverviewRange());
|
||||
|
||||
icon_on = new QIcon(":/icons/session-on.png");
|
||||
icon_off = new QIcon(":/icons/session-off.png");
|
||||
|
||||
GraphView->resetLayout();
|
||||
GraphView->LoadSettings("Overview"); //no trans
|
||||
|
||||
@ -340,8 +311,121 @@ Overview::Overview(QWidget *parent, gGraphView *shared) :
|
||||
Overview::~Overview()
|
||||
{
|
||||
delete ui;
|
||||
delete icon_on;
|
||||
delete icon_off;
|
||||
// delete icon_on;
|
||||
// delete icon_off;
|
||||
}
|
||||
|
||||
void Overview::RebuildGraphs(bool reset)
|
||||
{
|
||||
qint64 minx, maxx;
|
||||
if (reset) {
|
||||
GraphView->GetXBounds(minx, maxx);
|
||||
}
|
||||
|
||||
GraphView->trashGraphs(true);
|
||||
ChannelID ahicode = p_profile->general->calculateRDI() ? CPAP_RDI : CPAP_AHI;
|
||||
|
||||
if (ahicode == CPAP_RDI) {
|
||||
AHI = createGraph("AHIBreakdown", STR_TR_RDI, tr("Respiratory\nDisturbance\nIndex"));
|
||||
} else {
|
||||
AHI = createGraph("AHIBreakdown", STR_TR_AHI, tr("Apnea\nHypopnea\nIndex"));
|
||||
}
|
||||
|
||||
|
||||
ahi = new gAHIChart();
|
||||
AHI->AddLayer(ahi);
|
||||
|
||||
UC = createGraph(STR_GRAPH_Usage, tr("Usage"), tr("Usage\n(hours)"));
|
||||
UC->AddLayer(uc = new gUsageChart());
|
||||
|
||||
STG = createGraph("New Session", tr("Session Times"), tr("Session Times"), YT_Time);
|
||||
stg = new gSessionTimesChart();
|
||||
STG->AddLayer(stg);
|
||||
|
||||
PR = createGraph("Pressure Settings", STR_TR_Pressure, STR_TR_Pressure + "\n(" + STR_UNIT_CMH2O + ")");
|
||||
pres = new gPressureChart();
|
||||
PR->AddLayer(pres);
|
||||
|
||||
// LK = createGraph("Leaks", STR_TR_Leaks, STR_TR_UnintentionalLeaks + "\n(" + STR_UNIT_LPM + ")");
|
||||
// LK->AddLayer(new gSummaryChart(CPAP_Leak, MT_CPAP));
|
||||
|
||||
// TOTLK = createGraph("TotalLeaks", STR_TR_TotalLeaks, STR_TR_TotalLeaks + "\n(" + STR_UNIT_LPM + ")");
|
||||
// TOTLK->AddLayer(new gSummaryChart(CPAP_LeakTotal, MT_CPAP));
|
||||
|
||||
// NLL = createGraph("TimeInLL", tr("% in %1").arg(schema::channel[CPAP_LargeLeak].label()), tr("Large Leaks\n(% of night)"));
|
||||
// NLL->AddLayer(nll = new gSummaryChart("TimeInLL", MT_CPAP));
|
||||
// nll->addCalc(CPAP_LargeLeak, ST_SPH);
|
||||
|
||||
// RR = createGraph(schema::channel[CPAP_RespRate].code(), schema::channel[CPAP_RespRate].label(), schema::channel[CPAP_RespRate].fullname()+"\n"+schema::channel[CPAP_RespRate].units());
|
||||
// RR->AddLayer(new gSummaryChart(CPAP_RespRate, MT_CPAP));
|
||||
// TV = createGraph(schema::channel[CPAP_TidalVolume].code(),schema::channel[CPAP_TidalVolume].label(), tr("Tidal\nVolume\n(ml)"));
|
||||
// TV->AddLayer(new gSummaryChart(CPAP_TidalVolume, MT_CPAP));
|
||||
// MV = createGraph(schema::channel[CPAP_MinuteVent].code(), schema::channel[CPAP_MinuteVent].label(), tr("Minute\nVentilation\n(L/min)"));
|
||||
// MV->AddLayer(new gSummaryChart(CPAP_MinuteVent, MT_CPAP));
|
||||
// FL = createGraph(schema::channel[CPAP_FLG].code(), schema::channel[CPAP_FLG].label(), STR_TR_FlowLimit);
|
||||
// FL->AddLayer(new gSummaryChart(CPAP_FLG, MT_CPAP));
|
||||
// SN = createGraph(schema::channel[CPAP_Snore].code(), schema::channel[CPAP_Snore].label(), schema::channel[CPAP_Snore].fullname()+"\n"+schema::channel[CPAP_Snore].units());
|
||||
// SN->AddLayer(new gSummaryChart(CPAP_Snore, MT_CPAP));
|
||||
|
||||
QHash<ChannelID, schema::Channel *>::iterator chit;
|
||||
QHash<ChannelID, schema::Channel *>::iterator chit_end = schema::channel.channels.end();
|
||||
for (chit = schema::channel.channels.begin(); chit != chit_end; ++chit) {
|
||||
schema::Channel * chan = chit.value();
|
||||
|
||||
if (chan->showInOverview()) {
|
||||
ChannelID code = chan->id();
|
||||
QString name = chan->fullname();
|
||||
if (name.length() > 16) name = chan->label();
|
||||
gGraph *G = createGraph(chan->code(), name, chan->description());
|
||||
if ((chan->type() == schema::FLAG) || (chan->type() == schema::MINOR_FLAG)) {
|
||||
gSummaryChart * sc = new gSummaryChart(chan->code(), MT_CPAP);
|
||||
sc->addCalc(code, ST_CPH);
|
||||
G->AddLayer(sc);
|
||||
} else if (chan->type() == schema::SPAN) {
|
||||
gSummaryChart * sc = new gSummaryChart(chan->code(), MT_CPAP);
|
||||
sc->addCalc(code, ST_SPH);
|
||||
G->AddLayer(sc);
|
||||
} else if (chan->type() == schema::WAVEFORM) {
|
||||
G->AddLayer(new gSummaryChart(code, chan->machtype()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* PULSE = createGraph(schema::channel[OXI_Pulse].code(), schema::channel[OXI_Pulse].label(), STR_TR_PulseRate + "\n(" + STR_UNIT_BPM + ")");
|
||||
PULSE->AddLayer(new gSummaryChart(OXI_Pulse, MT_OXIMETER));
|
||||
|
||||
SPO2 = createGraph(schema::channel[OXI_SPO2].code(), schema::channel[OXI_SPO2].label(), tr("Oxygen Saturation\n(%)"));
|
||||
SPO2->AddLayer(new gSummaryChart(OXI_SPO2, MT_OXIMETER));
|
||||
|
||||
|
||||
NPB = createGraph("TimeInPB", tr("% in %1").arg(schema::channel[CPAP_CSR].label()), tr("%1\n(% of night)").arg(schema::channel[CPAP_LargeLeak].description()));
|
||||
NPB->AddLayer(npb = new gSummaryChart(tr("% PB"), MT_CPAP));
|
||||
npb->addCalc(CPAP_CSR, ST_SPH);
|
||||
|
||||
if (ahicode == CPAP_RDI) {
|
||||
AHIHR = createGraph(STR_GRAPH_PeakAHI, tr("Peak RDI"), tr("Peak RDI\nShows RDI Clusters\n(RDI/hr)"));
|
||||
AHIHR->AddLayer(new gSummaryChart(CPAP_RDI, MT_CPAP));
|
||||
} else {
|
||||
AHIHR = createGraph(STR_GRAPH_PeakAHI, tr("Peak AHI"), tr("Peak AHI\nShows AHI Clusters\n(AHI/hr)"));
|
||||
AHIHR->AddLayer(new gSummaryChart(CPAP_AHI, MT_CPAP));
|
||||
} */
|
||||
|
||||
WEIGHT = createGraph(STR_GRAPH_Weight, STR_TR_Weight, STR_TR_Weight, YT_Weight);
|
||||
BMI = createGraph(STR_GRAPH_BMI, STR_TR_BMI, tr("Body\nMass\nIndex"));
|
||||
ZOMBIE = createGraph(STR_GRAPH_Zombie, STR_TR_Zombie, tr("How you felt\n(0-10)"));
|
||||
|
||||
if (reset) {
|
||||
// GraphView->setDay(nullptr);
|
||||
GraphView->resetLayout();
|
||||
GraphView->setDay(nullptr);
|
||||
// GraphView->resetLayout();
|
||||
GraphView->SetXBounds(minx, maxx, 0, false);
|
||||
GraphView->resetLayout();
|
||||
updateGraphCombo();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Overview::closeEvent(QCloseEvent *event)
|
||||
@ -374,10 +458,8 @@ gGraph *Overview::createGraph(QString code, QString name, QString units, YTicker
|
||||
}
|
||||
|
||||
g->AddLayer(yt, LayerLeft, gYAxis::Margin);
|
||||
gXAxis *x = new gXAxis();
|
||||
x->setUtcFix(true);
|
||||
x->setRoundDays(true);
|
||||
g->AddLayer(x, LayerBottom, 0, gXAxis::Margin);
|
||||
gXAxisDay *x = new gXAxisDay();
|
||||
g->AddLayer(x, LayerBottom, 0, gXAxisDay::Margin);
|
||||
g->AddLayer(new gXGrid());
|
||||
return g;
|
||||
}
|
||||
@ -475,8 +557,8 @@ void Overview::UpdateCalendarDay(QDateEdit *dateedit, QDate date)
|
||||
cpapcol.setFontWeight(QFont::Bold);
|
||||
oxiday.setForeground(QBrush(Qt::red, Qt::SolidPattern));
|
||||
oxiday.setFontWeight(QFont::Bold);
|
||||
bool hascpap = p_profile->GetDay(date, MT_CPAP) != nullptr;
|
||||
bool hasoxi = p_profile->GetDay(date, MT_OXIMETER) != nullptr;
|
||||
bool hascpap = p_profile->FindDay(date, MT_CPAP) != nullptr;
|
||||
bool hasoxi = p_profile->FindDay(date, MT_OXIMETER) != nullptr;
|
||||
//bool hasjournal=p_profile->GetDay(date,MT_JOURNAL)!=nullptr;
|
||||
|
||||
if (hascpap) {
|
||||
|
@ -62,20 +62,21 @@ class Overview : public QWidget
|
||||
\param QString units The units of measurements to show in the popup */
|
||||
gGraph *createGraph(QString code, QString name, QString units = "", YTickerType yttype = YT_Number);
|
||||
gGraph *AHI, *AHIHR, *UC, *FL, *SA, *US, *PR, *LK, *NPB, *SET, *SES, *RR, *MV, *TV, *PTB, *PULSE, *SPO2, *NLL,
|
||||
// gGraph *AHI, *AHIHR, *UC, *FL, *US, *PR, *LK, *NPB, *SET, *SES, *RR, *MV, *TV, *PTB, *PULSE, *SPO2,
|
||||
*WEIGHT, *ZOMBIE, *BMI, *TGMV, *TOTLK, *STG;
|
||||
SummaryChart *bc, *uc, *fl, *sa, *us, *pr, *lk, *npb, *set, *ses, *rr, *mv, *tv, *ptb, *pulse, *spo2,
|
||||
// SummaryChart *bc, *uc, *fl, *us, *pr, *lk, *npb, *set, *ses, *rr, *mv, *tv, *ptb, *pulse, *spo2,
|
||||
*weight, *zombie, *bmi, *ahihr, *tgmv, *totlk, *nll;
|
||||
*WEIGHT, *ZOMBIE, *BMI, *TGMV, *TOTLK, *STG, *SN;
|
||||
SummaryChart *bc, *sa, *us, *pr, *set, *ses, *ptb, *pulse, *spo2,
|
||||
*weight, *zombie, *bmi, *ahihr, *tgmv, *totlk;
|
||||
|
||||
gSessionTimesChart * stg;
|
||||
gSummaryChart * stg, *uc, *ahi, * pres, *lk, *npb, *rr, *mv, *tv, *nll, *sn;
|
||||
|
||||
//! \breif List of SummaryCharts shown on the overview page
|
||||
QVector<SummaryChart *> OverviewCharts;
|
||||
|
||||
void ResetGraph(QString name);
|
||||
|
||||
void RebuildGraphs(bool reset = true);
|
||||
|
||||
public slots:
|
||||
void onRebuildGraphs() { RebuildGraphs(true); }
|
||||
// ! \brief Print button down the bottom, does the same as File->Print
|
||||
//void on_printButton_clicked();
|
||||
|
||||
|
@ -37,6 +37,7 @@ OximeterImport::OximeterImport(QWidget *parent) :
|
||||
oximodule = nullptr;
|
||||
liveView = new gGraphView(this);
|
||||
liveView->setVisible(false);
|
||||
liveView->setShowAuthorMessage(false);
|
||||
ui->retryButton->setVisible(false);
|
||||
ui->stopButton->setVisible(false);
|
||||
ui->saveButton->setVisible(false);
|
||||
|
@ -976,7 +976,7 @@ background: qlineargradient( x1:0 y1:0, x2:1 y2:0, stop:0 white, stop:1 #cccccc)
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">placeholder</string>
|
||||
<string>If you can read this, you likely have your oximeter type set wrong in preferences.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
|
@ -406,17 +406,19 @@ void PreferencesDialog::InitChanInfo()
|
||||
headers.append(tr("Name"));
|
||||
headers.append(tr("Color"));
|
||||
headers.append(tr("Flag Type"));
|
||||
headers.append(tr("Overview"));
|
||||
headers.append(tr("Label"));
|
||||
headers.append(tr("Details"));
|
||||
chanModel->setHorizontalHeaderLabels(headers);
|
||||
ui->chanView->setColumnWidth(0, 200);
|
||||
ui->chanView->setColumnWidth(1, 50);
|
||||
ui->chanView->setColumnWidth(2, 100);
|
||||
ui->chanView->setColumnWidth(3, 100);
|
||||
ui->chanView->setColumnWidth(3, 60);
|
||||
ui->chanView->setColumnWidth(4, 100);
|
||||
ui->chanView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
ui->chanView->setSelectionBehavior(QAbstractItemView::SelectItems);
|
||||
|
||||
chanModel->setColumnCount(5);
|
||||
chanModel->setColumnCount(6);
|
||||
|
||||
QStandardItem *hdr = nullptr;
|
||||
|
||||
@ -437,7 +439,7 @@ void PreferencesDialog::InitChanInfo()
|
||||
hdr->setEditable(false);
|
||||
QList<QStandardItem *> list;
|
||||
list.append(hdr);
|
||||
for (int i=0; i<4; i++) {
|
||||
for (int i=0; i<5; i++) {
|
||||
QStandardItem *it = new QStandardItem();
|
||||
it->setEnabled(false);
|
||||
list.append(it);
|
||||
@ -485,6 +487,13 @@ void PreferencesDialog::InitChanInfo()
|
||||
it->setEditable(type != schema::UNKNOWN);
|
||||
items.push_back(it);
|
||||
|
||||
it = new QStandardItem(QString());
|
||||
it->setToolTip(tr("Whether this flag has a dedicated overview chart."));
|
||||
it->setCheckable(true);
|
||||
it->setCheckState(chan->showInOverview() ? Qt::Checked : Qt::Unchecked);
|
||||
it->setData(chan->id(), Qt::UserRole);
|
||||
items.push_back(it);
|
||||
|
||||
it = new QStandardItem(chan->label());
|
||||
it->setToolTip(tr("This is the short-form label to indicate this channel on screen."));
|
||||
|
||||
@ -524,6 +533,7 @@ void PreferencesDialog::InitWaveInfo()
|
||||
QStringList headers;
|
||||
headers.append(tr("Name"));
|
||||
headers.append(tr("Color"));
|
||||
headers.append(tr("Overview"));
|
||||
headers.append(tr("Lower"));
|
||||
headers.append(tr("Upper"));
|
||||
headers.append(tr("Label"));
|
||||
@ -531,13 +541,14 @@ void PreferencesDialog::InitWaveInfo()
|
||||
waveModel->setHorizontalHeaderLabels(headers);
|
||||
ui->waveView->setColumnWidth(0, 200);
|
||||
ui->waveView->setColumnWidth(1, 50);
|
||||
ui->waveView->setColumnWidth(2, 50);
|
||||
ui->waveView->setColumnWidth(2, 60);
|
||||
ui->waveView->setColumnWidth(3, 50);
|
||||
ui->waveView->setColumnWidth(4, 100);
|
||||
ui->waveView->setColumnWidth(4, 50);
|
||||
ui->waveView->setColumnWidth(5, 100);
|
||||
ui->waveView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
ui->waveView->setSelectionBehavior(QAbstractItemView::SelectItems);
|
||||
|
||||
waveModel->setColumnCount(6);
|
||||
waveModel->setColumnCount(7);
|
||||
|
||||
QStandardItem *hdr = nullptr;
|
||||
|
||||
@ -555,7 +566,7 @@ void PreferencesDialog::InitWaveInfo()
|
||||
hdr->setEditable(false);
|
||||
QList<QStandardItem *> list;
|
||||
list.append(hdr);
|
||||
for (int i=0; i<5; i++) {
|
||||
for (int i=0; i<6; i++) {
|
||||
QStandardItem *it = new QStandardItem();
|
||||
it->setEnabled(false);
|
||||
list.append(it);
|
||||
@ -594,10 +605,18 @@ void PreferencesDialog::InitWaveInfo()
|
||||
it->setEditable(false);
|
||||
it->setData(chan->defaultColor().rgba(), Qt::UserRole);
|
||||
it->setToolTip(tr("Double click to change the default color for this channel plot/flag/data."));
|
||||
|
||||
it->setSelectable(false);
|
||||
items.push_back(it);
|
||||
|
||||
it = new QStandardItem();
|
||||
it->setCheckable(true);
|
||||
it->setCheckState(chan->showInOverview() ? Qt::Checked : Qt::Unchecked);
|
||||
it->setEditable(true);
|
||||
it->setData(chan->id(), Qt::UserRole);
|
||||
it->setToolTip(tr("Whether a breakdown of this waveform displays in overview."));
|
||||
items.push_back(it);
|
||||
|
||||
|
||||
it = new QStandardItem(QString::number(chan->lowerThreshold(),'f',1));
|
||||
it->setToolTip(tr("Here you can set the <b>lower</b> threshold used for certain calculations on the %1 waveform").arg(chan->fullname()));
|
||||
it->setEditable(true);
|
||||
@ -894,10 +913,11 @@ bool PreferencesDialog::Save()
|
||||
chan.setEnabled(item->checkState() == Qt::Checked ? true : false);
|
||||
chan.setFullname(item->text());
|
||||
chan.setDefaultColor(QColor(topitem->child(j,1)->data(Qt::UserRole).toUInt()));
|
||||
chan.setLowerThreshold(topitem->child(j,2)->text().toDouble());
|
||||
chan.setUpperThreshold(topitem->child(j,3)->text().toDouble());
|
||||
chan.setLabel(topitem->child(j,4)->text());
|
||||
chan.setDescription(topitem->child(j,5)->text());
|
||||
chan.setShowInOverview(topitem->child(j,2)->checkState() == Qt::Checked);
|
||||
chan.setLowerThreshold(topitem->child(j,3)->text().toDouble());
|
||||
chan.setUpperThreshold(topitem->child(j,4)->text().toDouble());
|
||||
chan.setLabel(topitem->child(j,5)->text());
|
||||
chan.setDescription(topitem->child(j,6)->text());
|
||||
}
|
||||
}
|
||||
|
||||
@ -927,8 +947,9 @@ bool PreferencesDialog::Save()
|
||||
}
|
||||
}
|
||||
chan.setType(type);
|
||||
chan.setLabel(topitem->child(j,3)->text());
|
||||
chan.setDescription(topitem->child(j,4)->text());
|
||||
chan.setShowInOverview(topitem->child(j,3)->checkState() == Qt::Checked);
|
||||
chan.setLabel(topitem->child(j,4)->text());
|
||||
chan.setDescription(topitem->child(j,5)->text());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,6 @@ private:
|
||||
|
||||
MySortFilterProxyModel * waveFilterModel;
|
||||
QStandardItemModel *waveModel;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -7,6 +7,8 @@
|
||||
* distribution for more details. */
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QDataStream>
|
||||
#include <cmath>
|
||||
|
||||
#include "mainwindow.h"
|
||||
@ -23,6 +25,363 @@ QString formatTime(float time)
|
||||
return QString().sprintf("%02i:%02i", hours, minutes); //,seconds);
|
||||
}
|
||||
|
||||
QDataStream & operator>>(QDataStream & in, RXItem & rx)
|
||||
{
|
||||
in >> rx.start;
|
||||
in >> rx.end;
|
||||
in >> rx.days;
|
||||
in >> rx.ahi;
|
||||
in >> rx.rdi;
|
||||
in >> rx.hours;
|
||||
|
||||
QString loadername;
|
||||
in >> loadername;
|
||||
QString serial;
|
||||
in >> serial;
|
||||
|
||||
MachineLoader * loader = GetLoader(loadername);
|
||||
if (loader) {
|
||||
rx.machine = loader->lookupMachine(serial);
|
||||
} else {
|
||||
qDebug() << "Bad machine object" << loadername << serial;
|
||||
rx.machine = nullptr;
|
||||
}
|
||||
|
||||
in >> rx.relief;
|
||||
in >> rx.mode;
|
||||
in >> rx.pressure;
|
||||
|
||||
QList<QDate> list;
|
||||
in >> list;
|
||||
|
||||
rx.dates.clear();
|
||||
for (int i=0; i<list.size(); ++i) {
|
||||
QDate date = list.at(i);
|
||||
rx.dates[date] = p_profile->FindDay(date, MT_CPAP);
|
||||
}
|
||||
|
||||
in >> rx.s_count;
|
||||
in >> rx.s_sum;
|
||||
|
||||
return in;
|
||||
}
|
||||
QDataStream & operator<<(QDataStream & out, const RXItem & rx)
|
||||
{
|
||||
out << rx.start;
|
||||
out << rx.end;
|
||||
out << rx.days;
|
||||
out << rx.ahi;
|
||||
out << rx.rdi;
|
||||
out << rx.hours;
|
||||
|
||||
out << rx.machine->loaderName();
|
||||
out << rx.machine->serial();
|
||||
out << rx.relief;
|
||||
out << rx.mode;
|
||||
out << rx.pressure;
|
||||
out << rx.dates.keys();
|
||||
out << rx.s_count;
|
||||
out << rx.s_sum;
|
||||
|
||||
return out;
|
||||
}
|
||||
void Statistics::loadRXChanges()
|
||||
{
|
||||
QString path = p_profile->Get("{" + STR_GEN_DataFolder + "}/RXChanges.cache" );
|
||||
QFile file(path);
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
return;
|
||||
}
|
||||
QDataStream in(&file);
|
||||
in.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
quint32 mag32;
|
||||
if (in.version() != QDataStream::Qt_5_0) {
|
||||
}
|
||||
|
||||
in >> mag32;
|
||||
|
||||
if (mag32 != magic) {
|
||||
return;
|
||||
}
|
||||
quint16 version;
|
||||
in >> version;
|
||||
|
||||
in >> rxitems;
|
||||
|
||||
}
|
||||
void Statistics::saveRXChanges()
|
||||
{
|
||||
QString path = p_profile->Get("{" + STR_GEN_DataFolder + "}/RXChanges.cache" );
|
||||
QFile file(path);
|
||||
if (!file.open(QFile::WriteOnly)) {
|
||||
return;
|
||||
}
|
||||
QDataStream out(&file);
|
||||
out.setByteOrder(QDataStream::LittleEndian);
|
||||
out.setVersion(QDataStream::Qt_5_0);
|
||||
out << magic;
|
||||
out << (quint16)0;
|
||||
out << rxitems;
|
||||
|
||||
}
|
||||
|
||||
bool rxAHILessThan(const RXItem * rx1, const RXItem * rx2)
|
||||
{
|
||||
|
||||
return (double(rx1->ahi) / rx1->hours) < (double(rx2->ahi) / rx2->hours);
|
||||
}
|
||||
|
||||
void Statistics::updateRXChanges()
|
||||
{
|
||||
rxitems.clear();
|
||||
loadRXChanges();
|
||||
QMap<QDate, Day *>::iterator di;
|
||||
|
||||
QMap<QDate, Day *>::iterator it;
|
||||
QMap<QDate, Day *>::iterator it_end = p_profile->daylist.end();
|
||||
|
||||
QMap<QDate, RXItem>::iterator ri;
|
||||
QMap<QDate, RXItem>::iterator ri_end = rxitems.end();
|
||||
|
||||
|
||||
quint64 tmp;
|
||||
for (it = p_profile->daylist.begin(); it != it_end; ++it) {
|
||||
const QDate & date = it.key();
|
||||
Day * day = it.value();
|
||||
Machine * mach = day->machine(MT_CPAP);
|
||||
if (mach == nullptr) continue;
|
||||
|
||||
bool fnd = false;
|
||||
|
||||
ri_end = rxitems.end();
|
||||
for (ri = rxitems.begin(); ri != ri_end; ++ri) {
|
||||
RXItem & rx = ri.value();
|
||||
|
||||
if ((date >= rx.start) && (date <= rx.end)) {
|
||||
// Fits in date range
|
||||
if (rx.dates.contains(date)) {
|
||||
fnd = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// First up, check if fits in date range, but isn't loaded for some reason
|
||||
|
||||
// Need summaries for this
|
||||
day->OpenSummary();
|
||||
QList<ChannelID> flags = day->getSortedMachineChannels(MT_CPAP, schema::FLAG | schema::MINOR_FLAG | schema::SPAN);
|
||||
|
||||
QString relief = day->getPressureRelief();
|
||||
QString mode = day->getCPAPMode();
|
||||
QString pressure = day->getPressureSettings();
|
||||
|
||||
if ((rx.relief == relief) && (rx.mode == mode) && (rx.pressure == pressure) && (rx.machine == mach)) {
|
||||
for (int i=0; i < flags.size(); i++) {
|
||||
ChannelID code = flags.at(i);
|
||||
rx.s_count[code] += day->count(code);
|
||||
rx.s_sum[code] += day->sum(code);
|
||||
}
|
||||
|
||||
tmp = day->count(CPAP_Hypopnea) + day->count(CPAP_Obstructive) + day->count(CPAP_Apnea) + day->count(CPAP_ClearAirway);
|
||||
rx.ahi += tmp;
|
||||
rx.rdi += tmp + day->count(CPAP_RERA);
|
||||
rx.hours += day->hours(MT_CPAP);
|
||||
|
||||
rx.dates[date] = day;
|
||||
rx.days = rx.dates.size();
|
||||
fnd = true;
|
||||
break;
|
||||
} else {
|
||||
// Bleh.... split the day record!
|
||||
|
||||
RXItem rx1, rx2;
|
||||
|
||||
// First create the new day..
|
||||
rx1.start = date;
|
||||
rx1.end = date;
|
||||
rx1.days = 1;
|
||||
|
||||
tmp = day->count(CPAP_Hypopnea) + day->count(CPAP_Obstructive) + day->count(CPAP_Apnea) + day->count(CPAP_ClearAirway);
|
||||
rx1.ahi = tmp;
|
||||
rx1.rdi = tmp + day->count(CPAP_RERA);
|
||||
for (int i=0; i < flags.size(); i++) {
|
||||
ChannelID code = flags.at(i);
|
||||
rx1.s_count[code] = day->count(code);
|
||||
rx1.s_sum[code] = day->sum(code);
|
||||
}
|
||||
|
||||
rx1.hours = day->hours(MT_CPAP);
|
||||
rx1.relief = relief;
|
||||
rx1.mode = mode;
|
||||
rx1.pressure = pressure;
|
||||
rx1.machine = day->machine(MT_CPAP);
|
||||
rx1.dates[date] = day;
|
||||
rxitems.insert(date, rx1);
|
||||
|
||||
QMap<QDate, Day *> datecopy = rx.dates;
|
||||
|
||||
rx.dates.clear();
|
||||
|
||||
rx.end = rx.start;
|
||||
rx2.start = rx.end;
|
||||
rx2.end = rx.start;
|
||||
rx2.ahi = 0;
|
||||
rx2.rdi = 0;
|
||||
rx2.hours = 0;
|
||||
rx.ahi = 0;
|
||||
rx.rdi = 0;
|
||||
rx.hours = 0;
|
||||
rx.s_count.clear();
|
||||
rx2.s_count.clear();
|
||||
rx.s_sum.clear();
|
||||
rx2.s_sum.clear();
|
||||
for (di = datecopy.begin(); di != datecopy.end(); ++di) {
|
||||
// Split everything before
|
||||
if (di.key() < date) {
|
||||
Day * dy = rx.dates[di.key()] = p_profile->GetDay(di.key(), MT_CPAP);
|
||||
tmp = dy->count(CPAP_Hypopnea) + dy->count(CPAP_Obstructive) + dy->count(CPAP_Apnea) + dy->count(CPAP_ClearAirway);;
|
||||
rx.ahi += tmp;
|
||||
rx.rdi += tmp + dy->count(CPAP_RERA);
|
||||
QList<ChannelID> flags2 = dy->getSortedMachineChannels(MT_CPAP, schema::FLAG | schema::MINOR_FLAG | schema::SPAN);
|
||||
|
||||
for (int i=0; i < flags2.size(); i++) {
|
||||
ChannelID code = flags2.at(i);
|
||||
rx.s_count[code] += dy->count(code);
|
||||
rx.s_sum[code] += dy->sum(code);
|
||||
}
|
||||
|
||||
rx.hours += dy->hours(MT_CPAP);
|
||||
//rx.days++;
|
||||
rx.end = qMax(di.key(), rx.end);
|
||||
}
|
||||
// Split everything after
|
||||
if (di.key() > date) {
|
||||
Day * dy = rx2.dates[di.key()] = p_profile->GetDay(di.key(), MT_CPAP);
|
||||
tmp = dy->count(CPAP_Hypopnea) + dy->count(CPAP_Obstructive) + dy->count(CPAP_Apnea) + dy->count(CPAP_ClearAirway);;
|
||||
rx2.ahi += tmp;
|
||||
rx2.rdi += tmp + dy->count(CPAP_RERA);
|
||||
QList<ChannelID> flags2 = dy->getSortedMachineChannels(MT_CPAP, schema::FLAG | schema::MINOR_FLAG | schema::SPAN);
|
||||
|
||||
for (int i=0; i < flags2.size(); i++) {
|
||||
ChannelID code = flags2.at(i);
|
||||
rx2.s_count[code] += dy->count(code);
|
||||
rx2.s_sum[code] += dy->sum(code);
|
||||
}
|
||||
|
||||
rx2.hours += dy->hours(MT_CPAP);
|
||||
rx2.end = qMax(di.key(), rx2.end);
|
||||
rx2.start = qMin(di.key(), rx2.start);
|
||||
}
|
||||
}
|
||||
rx.days = rx.dates.size();
|
||||
|
||||
rx2.pressure = rx.pressure;
|
||||
rx2.mode = rx.mode;
|
||||
rx2.relief = rx.relief;
|
||||
rx2.machine = rx.machine;
|
||||
rx2.days = rx2.dates.size();
|
||||
|
||||
rxitems.insert(rx2.end, rx2);
|
||||
fnd = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fnd) continue;
|
||||
|
||||
day->OpenSummary();
|
||||
QList<ChannelID> flags3 = day->getSortedMachineChannels(MT_CPAP, schema::FLAG | schema::MINOR_FLAG | schema::SPAN);
|
||||
|
||||
|
||||
QString relief = day->getPressureRelief();
|
||||
QString mode = day->getCPAPMode();
|
||||
QString pressure = day->getPressureSettings();
|
||||
|
||||
for (ri = rxitems.begin(); ri != ri_end; ++ri) {
|
||||
RXItem & rx = ri.value();
|
||||
|
||||
if (rx.end.daysTo(date) == 1) {
|
||||
|
||||
if ((rx.relief == relief) && (rx.mode == mode) && (rx.pressure == pressure) && (rx.machine == day->machine(MT_CPAP)) ) {
|
||||
|
||||
tmp = day->count(CPAP_Hypopnea) + day->count(CPAP_Obstructive) + day->count(CPAP_Apnea) + day->count(CPAP_ClearAirway);
|
||||
rx.ahi += tmp;
|
||||
rx.rdi += tmp + day->count(CPAP_RERA);
|
||||
|
||||
for (int i=0; i < flags3.size(); i++) {
|
||||
ChannelID code = flags3.at(i);
|
||||
rx.s_count[code] += day->count(code);
|
||||
rx.s_sum[code] += day->sum(code);
|
||||
}
|
||||
|
||||
rx.hours += day->hours(MT_CPAP);
|
||||
|
||||
rx.dates[date] = day;
|
||||
rx.days = rx.dates.size();
|
||||
rx.end = date;
|
||||
fnd = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fnd) {
|
||||
RXItem rx;
|
||||
rx.start = date;
|
||||
rx.end = date;
|
||||
rx.days = 1;
|
||||
tmp = day->count(CPAP_Hypopnea) + day->count(CPAP_Obstructive) + day->count(CPAP_Apnea) + day->count(CPAP_ClearAirway);
|
||||
rx.ahi = tmp;
|
||||
rx.rdi = tmp + day->count(CPAP_RERA);
|
||||
for (int i=0; i < flags3.size(); i++) {
|
||||
ChannelID code = flags3.at(i);
|
||||
rx.s_count[code] = day->count(code);
|
||||
rx.s_sum[code] = day->sum(code);
|
||||
}
|
||||
rx.hours = day->hours();
|
||||
rx.relief = relief;
|
||||
rx.mode = mode;
|
||||
rx.pressure = pressure;
|
||||
rx.machine = day->machine(MT_CPAP);
|
||||
rx.dates.insert(date, day);
|
||||
|
||||
rxitems.insert(date, rx);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
saveRXChanges();
|
||||
|
||||
|
||||
QList<RXItem *> list;
|
||||
ri_end = rxitems.end();
|
||||
|
||||
for (ri = rxitems.begin(); ri != ri_end; ++ri) {
|
||||
list.append(&ri.value());
|
||||
ri.value().highlight = 0;
|
||||
}
|
||||
|
||||
qSort(list.begin(), list.end(), rxAHILessThan);
|
||||
|
||||
if (list.size() >= 4) {
|
||||
list[0]->highlight = 1; // best
|
||||
list[1]->highlight = 2; // best
|
||||
int ls = list.size() - 1;
|
||||
list[ls-1]->highlight = 3; // best
|
||||
list[ls]->highlight = 4;
|
||||
} else if (list.size() >= 2) {
|
||||
list[0]->highlight = 1; // best
|
||||
int ls = list.size() - 1;
|
||||
list[ls]->highlight = 4;
|
||||
} else if (list.size() > 0) {
|
||||
list[0]->highlight = 1; // best
|
||||
}
|
||||
|
||||
// update record box info..
|
||||
|
||||
}
|
||||
|
||||
|
||||
Statistics::Statistics(QObject *parent) :
|
||||
QObject(parent)
|
||||
@ -503,11 +862,178 @@ struct Period {
|
||||
QString header;
|
||||
};
|
||||
|
||||
const QString heading_color="#ffffff";
|
||||
const QString subheading_color="#e0e0e0";
|
||||
const int rxthresh = 5;
|
||||
|
||||
QString Statistics::GenerateMachineList()
|
||||
{
|
||||
QList<Machine *> cpap_machines = p_profile->GetMachines(MT_CPAP);
|
||||
QList<Machine *> oximeters = p_profile->GetMachines(MT_OXIMETER);
|
||||
QList<Machine *> mach;
|
||||
|
||||
mach.append(cpap_machines);
|
||||
mach.append(oximeters);
|
||||
|
||||
QString html;
|
||||
if (mach.size() > 0) {
|
||||
html += "<div align=center><br/>";
|
||||
|
||||
html += QString("<table class=curved style=\"page-break-before:auto;\" "+table_width+">");
|
||||
|
||||
html += "<thead>";
|
||||
html += "<tr bgcolor='"+heading_color+"'><th colspan=7 align=center><font size=+2>" + tr("Machine Information") + "</font></th></tr>";
|
||||
|
||||
html += QString("<tr><td><b>%1</b></td><td><b>%2</b></td><td><b>%3</b></td><td><b>%4</b></td><td><b>%5</b></td><td><b>%6</b></td></tr>")
|
||||
.arg(STR_TR_Brand)
|
||||
.arg(STR_TR_Series)
|
||||
.arg(STR_TR_Model)
|
||||
.arg(STR_TR_Serial)
|
||||
.arg(tr("First Use"))
|
||||
.arg(tr("Last Use"));
|
||||
|
||||
html += "</thead>";
|
||||
|
||||
Machine *m;
|
||||
|
||||
for (int i = 0; i < mach.size(); i++) {
|
||||
m = mach.at(i);
|
||||
|
||||
if (m->type() == MT_JOURNAL) { continue; }
|
||||
|
||||
QDate d1 = m->FirstDay();
|
||||
QDate d2 = m->LastDay();
|
||||
QString mn = m->modelnumber();
|
||||
html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>")
|
||||
.arg(m->brand())
|
||||
.arg(m->series())
|
||||
.arg(m->model() +
|
||||
(mn.isEmpty() ? "" : QString(" (") + mn + QString(")")))
|
||||
.arg(m->serial())
|
||||
.arg(d1.toString(Qt::SystemLocaleShortDate))
|
||||
.arg(d2.toString(Qt::SystemLocaleShortDate));
|
||||
|
||||
}
|
||||
|
||||
html += "</table>";
|
||||
html += "</div>";
|
||||
}
|
||||
return html;
|
||||
}
|
||||
QString Statistics::GenerateRXChanges()
|
||||
{
|
||||
updateRXChanges();
|
||||
|
||||
QString ahitxt;
|
||||
|
||||
bool rdi = p_profile->general->calculateRDI();
|
||||
if (rdi) {
|
||||
ahitxt = STR_TR_RDI;
|
||||
} else {
|
||||
ahitxt = STR_TR_AHI;
|
||||
}
|
||||
|
||||
|
||||
QString html = "<div align=center><br/>";
|
||||
html += QString("<table class=curved style=\"page-break-before:always;\" "+table_width+">");
|
||||
html += "<thead>";
|
||||
html += "<tr bgcolor='"+heading_color+"'><th colspan=10 align=center><font size=+2>" + tr("Changes to Prescription Settings") + "</font></th></tr>";
|
||||
|
||||
QString extratxt;
|
||||
|
||||
QString tooltip;
|
||||
QStringList hdrlist;
|
||||
hdrlist.push_back(STR_TR_First);
|
||||
hdrlist.push_back(STR_TR_Last);
|
||||
hdrlist.push_back(tr("Days"));
|
||||
hdrlist.push_back(ahitxt);
|
||||
hdrlist.push_back(STR_TR_FL);
|
||||
hdrlist.push_back(STR_TR_Machine);
|
||||
hdrlist.push_back(tr("Pressure Relief"));
|
||||
hdrlist.push_back(STR_TR_Mode);
|
||||
hdrlist.push_back(tr("Pressure Settings"));
|
||||
|
||||
html+="<tr>\n";
|
||||
for (int i=0; i < hdrlist.size(); ++i) {
|
||||
html+=QString(" <th align=left><b>%1</b></th>\n").arg(hdrlist.at(i));
|
||||
}
|
||||
html+="</tr>\n";
|
||||
html += "</thead>";
|
||||
// html += "<tfoot>";
|
||||
// html += "<tr><td colspan=10 align=center>";
|
||||
// html += QString("<i>") +
|
||||
// tr("Efficacy highlighting ignores prescription settings with less than %1 days of recorded data.").
|
||||
// arg(rxthresh) + QString("</i><br/>");
|
||||
// html += "</td></tr>";
|
||||
// html += "</tfoot>";
|
||||
|
||||
QMapIterator<QDate, RXItem> it(rxitems);
|
||||
it.toBack();
|
||||
while (it.hasPrevious()) {
|
||||
it.previous();
|
||||
const RXItem & rx = it.value();
|
||||
|
||||
QString color;
|
||||
|
||||
if (rx.highlight == 1) {
|
||||
color = "#c0ffc0";
|
||||
} else if (rx.highlight == 2) {
|
||||
color = "#e0ffe0";
|
||||
} else if (rx.highlight == 3) {
|
||||
color = "#ffe0e0";
|
||||
} else if (rx.highlight == 4) {
|
||||
color = "#ffc0c0";
|
||||
} else { color = ""; }
|
||||
|
||||
QString datarowclass;
|
||||
if (rx.highlight == 0) datarowclass="class=datarow";
|
||||
html += QString("<tr %4 bgcolor='%1' onmouseover='ChangeColor(this, \"#dddddd\");' onmouseout='ChangeColor(this, \"%1\");' onclick='alert(\"overview=%2,%3\");'>")
|
||||
.arg(color)
|
||||
.arg(rx.start.toString(Qt::ISODate))
|
||||
.arg(rx.end.toString(Qt::ISODate))
|
||||
.arg(datarowclass);
|
||||
|
||||
double ahi = rdi ? (double(rx.rdi) / rx.hours) : (double(rx.ahi) /rx.hours);
|
||||
double fli = double(rx.count(CPAP_FlowLimit)) / rx. hours;
|
||||
|
||||
html += QString("<td>%1</td>").arg(rx.start.toString())+
|
||||
QString("<td>%1</td>").arg(rx.end.toString())+
|
||||
QString("<td>%1</td>").arg(rx.days)+
|
||||
QString("<td>%1</td>").arg(ahi, 0, 'f', 2)+
|
||||
QString("<td>%1</td>").arg(fli, 0, 'f', 2)+
|
||||
QString("<td>%1</td>").arg(rx.machine->loaderName())+
|
||||
QString("<td>%1</td>").arg(rx.relief)+
|
||||
QString("<td>%1</td>").arg(rx.mode)+
|
||||
QString("<td>%1</td>").arg(rx.pressure)+
|
||||
"</tr>";
|
||||
}
|
||||
html+="</table></div>";
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
QString Statistics::GenerateHTML()
|
||||
{
|
||||
QList<Machine *> cpap_machines = p_profile->GetMachines(MT_CPAP);
|
||||
QList<Machine *> oximeters = p_profile->GetMachines(MT_OXIMETER);
|
||||
QList<Machine *> mach;
|
||||
|
||||
QString heading_color="#ffffff";
|
||||
QString subheading_color="#e0e0e0";
|
||||
mach.append(cpap_machines);
|
||||
mach.append(oximeters);
|
||||
|
||||
bool havedata = false;
|
||||
for (int i=0; i < mach.size(); ++i) {
|
||||
int daysize = mach[i]->day.size();
|
||||
if (daysize > 0) {
|
||||
havedata = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString html = htmlHeader(havedata);
|
||||
|
||||
// return "";
|
||||
|
||||
|
||||
// Find first and last days with valid CPAP data
|
||||
@ -525,23 +1051,6 @@ QString Statistics::GenerateHTML()
|
||||
if (cpap6month < firstcpap) { cpap6month = firstcpap; }
|
||||
if (cpapyear < firstcpap) { cpapyear = firstcpap; }
|
||||
|
||||
QList<Machine *> cpap_machines = p_profile->GetMachines(MT_CPAP);
|
||||
QList<Machine *> oximeters = p_profile->GetMachines(MT_OXIMETER);
|
||||
QList<Machine *> mach;
|
||||
|
||||
mach.append(cpap_machines);
|
||||
mach.append(oximeters);
|
||||
|
||||
bool havedata = false;
|
||||
for (int i=0; i < mach.size(); ++i) {
|
||||
int daysize = mach[i]->day.size();
|
||||
if (daysize > 0) {
|
||||
havedata = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QString html = htmlHeader(havedata);
|
||||
|
||||
if (!havedata) {
|
||||
html += "<div align=center><table class=curved height=100% "+table_width+">";
|
||||
@ -555,6 +1064,7 @@ QString Statistics::GenerateHTML()
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
int cpapdays = p_profile->countDays(MT_CPAP, firstcpap, lastcpap);
|
||||
|
||||
// CPAPMode cpapmode = (CPAPMode)(int)p_profile->calcSettingsMax(CPAP_Mode, MT_CPAP, firstcpap, lastcpap);
|
||||
@ -592,7 +1102,6 @@ QString Statistics::GenerateHTML()
|
||||
QList<Period> periods;
|
||||
|
||||
|
||||
|
||||
bool skipsection = false;;
|
||||
for (QList<StatisticsRow>::iterator i = rows.begin(); i != rows.end(); ++i) {
|
||||
StatisticsRow &row = (*i);
|
||||
@ -702,7 +1211,7 @@ QString Statistics::GenerateHTML()
|
||||
continue;
|
||||
} else {
|
||||
ChannelID id = schema::channel[row.src].id();
|
||||
if ((id == NoChannel) || (!p_profile->hasChannel(id))) {
|
||||
if ((id == NoChannel) || (!p_profile->channelAvailable(id))) {
|
||||
continue;
|
||||
}
|
||||
name = calcnames[row.calc].arg(schema::channel[id].fullname());
|
||||
@ -733,6 +1242,7 @@ QString Statistics::GenerateHTML()
|
||||
html += "</table>";
|
||||
html += "</div>";
|
||||
|
||||
/*
|
||||
QList<UsageData> AHI;
|
||||
|
||||
if (cpapdays > 0) {
|
||||
@ -1083,13 +1593,13 @@ QString Statistics::GenerateHTML()
|
||||
|
||||
recbox += "</table>";
|
||||
recbox += "</body></html>";
|
||||
mainwin->setRecBoxHTML(recbox);
|
||||
mainwin->setRecBoxHTML(recbox); */
|
||||
|
||||
/*RXsort=RX_min;
|
||||
RXorder=true;
|
||||
qSort(rxchange.begin(),rxchange.end());*/
|
||||
|
||||
html += "<div align=center><br/>";
|
||||
/* html += "<div align=center><br/>";
|
||||
html += QString("<table class=curved style=\"page-break-before:always;\" "+table_width+">");
|
||||
html += "<thead>";
|
||||
html += "<tr bgcolor='"+heading_color+"'><th colspan=10 align=center><font size=+2>" + tr("Changes to Prescription Settings") + "</font></th></tr>";
|
||||
@ -1232,57 +1742,10 @@ QString Statistics::GenerateHTML()
|
||||
html += "</table>";
|
||||
html += "</div>";
|
||||
|
||||
}
|
||||
} */
|
||||
html += GenerateRXChanges();
|
||||
html += GenerateMachineList();
|
||||
|
||||
if (mach.size() > 0) {
|
||||
html += "<div align=center><br/>";
|
||||
|
||||
html += QString("<table class=curved style=\"page-break-before:auto;\" "+table_width+">");
|
||||
|
||||
html += "<thead>";
|
||||
html += "<tr bgcolor='"+heading_color+"'><th colspan=7 align=center><font size=+2>" + tr("Machine Information") + "</font></th></tr>";
|
||||
|
||||
html += QString("<tr><td><b>%1</b></td><td><b>%2</b></td><td><b>%3</b></td><td><b>%4</b></td><td><b>%5</b></td><td><b>%6</b></td><td><b>%7</b></td></tr>")
|
||||
.arg(STR_TR_Brand)
|
||||
.arg(STR_TR_Series)
|
||||
.arg(STR_TR_Model)
|
||||
.arg(STR_TR_Serial)
|
||||
.arg(tr("First Use"))
|
||||
.arg(tr("Last Use"))
|
||||
.arg(STR_TR_AHI);
|
||||
|
||||
html += "</thead>";
|
||||
|
||||
Machine *m;
|
||||
|
||||
for (int i = 0; i < mach.size(); i++) {
|
||||
m = mach.at(i);
|
||||
|
||||
if (m->type() == MT_JOURNAL) { continue; }
|
||||
|
||||
QDate d1 = m->FirstDay();
|
||||
QDate d2 = m->LastDay();
|
||||
QString ahi;
|
||||
if (m->type() == MT_CPAP) {
|
||||
float a = calcAHI(d1,d2);
|
||||
ahi = QString::number(a,'f',2);
|
||||
}
|
||||
QString mn = m->modelnumber();
|
||||
html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td><td>%7</td></tr>")
|
||||
.arg(m->brand())
|
||||
.arg(m->series())
|
||||
.arg(m->model() +
|
||||
(mn.isEmpty() ? "" : QString(" (") + mn + QString(")")))
|
||||
.arg(m->serial())
|
||||
.arg(d1.toString(Qt::SystemLocaleShortDate))
|
||||
.arg(d2.toString(Qt::SystemLocaleShortDate))
|
||||
.arg(ahi);
|
||||
|
||||
}
|
||||
|
||||
html += "</table>";
|
||||
html += "</div>";
|
||||
}
|
||||
|
||||
html += "<script type='text/javascript' language='javascript' src='qrc:/docs/script.js'></script>";
|
||||
//updateFavourites();
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include "SleepLib/schema.h"
|
||||
#include "SleepLib/machine.h"
|
||||
|
||||
enum StatCalcType {
|
||||
SC_UNDEFINED=0, SC_COLUMNHEADERS, SC_HEADING, SC_SUBHEADING, SC_MEDIAN, SC_AVG, SC_WAVG, SC_90P, SC_MIN, SC_MAX, SC_CPH, SC_SPH, SC_AHI, SC_HOURS, SC_COMPLIANCE, SC_DAYS, SC_ABOVE, SC_BELOW
|
||||
@ -93,13 +94,70 @@ struct StatisticsRow {
|
||||
QString value(QDate start, QDate end);
|
||||
};
|
||||
|
||||
class RXItem {
|
||||
public:
|
||||
RXItem() {
|
||||
machine = nullptr;
|
||||
ahi = rdi = 0;
|
||||
highlight = 0;
|
||||
hours = 0;
|
||||
}
|
||||
RXItem(const RXItem & copy) {
|
||||
start = copy.start;
|
||||
end = copy.end;
|
||||
days = copy.days;
|
||||
s_count = copy.s_count;
|
||||
s_sum = copy.s_sum;
|
||||
ahi = copy.ahi;
|
||||
rdi = copy.rdi;
|
||||
hours = copy.hours;
|
||||
machine = copy.machine;
|
||||
relief = copy.relief;
|
||||
mode = copy.mode;
|
||||
pressure = copy.pressure;
|
||||
dates = copy.dates;
|
||||
highlight = copy.highlight;
|
||||
}
|
||||
inline quint64 count(ChannelID id) const {
|
||||
QHash<ChannelID, quint64>::const_iterator it = s_count.find(id);
|
||||
if (it == s_count.end()) return 0;
|
||||
return it.value();
|
||||
}
|
||||
inline double sum(ChannelID id) const{
|
||||
QHash<ChannelID, double>::const_iterator it = s_sum.find(id);
|
||||
if (it == s_sum.end()) return 0;
|
||||
return it.value();
|
||||
}
|
||||
QDate start;
|
||||
QDate end;
|
||||
int days;
|
||||
QHash<ChannelID, quint64> s_count;
|
||||
QHash<ChannelID, double> s_sum;
|
||||
quint64 ahi;
|
||||
quint64 rdi;
|
||||
double hours;
|
||||
Machine * machine;
|
||||
QString relief;
|
||||
QString mode;
|
||||
QString pressure;
|
||||
QMap<QDate, Day *> dates;
|
||||
short highlight;
|
||||
};
|
||||
|
||||
class Statistics : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Statistics(QObject *parent = 0);
|
||||
|
||||
void loadRXChanges();
|
||||
void saveRXChanges();
|
||||
void updateRXChanges();
|
||||
|
||||
QString GenerateHTML();
|
||||
QString GenerateMachineList();
|
||||
QString GenerateRXChanges();
|
||||
|
||||
|
||||
protected:
|
||||
// Using a map to maintain order
|
||||
@ -107,6 +165,8 @@ class Statistics : public QObject
|
||||
QMap<StatCalcType, QString> calcnames;
|
||||
QMap<MachineType, QString> machinenames;
|
||||
|
||||
QMap<QDate, RXItem> rxitems;
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
Loading…
Reference in New Issue
Block a user