diff --git a/sleepyhead/Graphs/gGraphView.cpp b/sleepyhead/Graphs/gGraphView.cpp index b41b332c..9de47511 100644 --- a/sleepyhead/Graphs/gGraphView.cpp +++ b/sleepyhead/Graphs/gGraphView.cpp @@ -32,6 +32,7 @@ #include "Graphs/glcommon.h" #include "Graphs/gLineChart.h" #include "Graphs/gSummaryChart.h" +#include "Graphs/gSessionTimesChart.h" #include "Graphs/gYAxis.h" #include "Graphs/gFlagsLine.h" #include "SleepLib/profiles.h" @@ -1269,6 +1270,9 @@ void gGraphView::paintGL() if (!graphs_drawn) { // No graphs drawn? show something useful :) QString txt = QObject::tr("SleepyHead is proudly brought to you by JediMark."); + if (emptyText() == STR_Empty_Brick) { + txt += "\nI'm very sorry your machine doesn't record useful data to graph in Daily View :("; + } // int x2, y2; // GetTextExtent(m_emptytext, x2, y2, bigfont); @@ -1868,9 +1872,11 @@ void gGraphView::populateMenu(gGraph * graph) gLineChart * lc = dynamic_cast(findLayer(graph,LT_LineChart)); SummaryChart * sc = dynamic_cast(findLayer(graph,LT_SummaryChart)); + gSessionTimesChart * stg = dynamic_cast(findLayer(graph,LT_SessionTimes)); + limits_menu->clear(); - if (lc || sc) { + if (lc || sc || stg) { QWidgetAction * widget = new QWidgetAction(this); MinMaxWidget * minmax = new MinMaxWidget(graph, this); diff --git a/sleepyhead/Graphs/gSessionTimesChart.cpp b/sleepyhead/Graphs/gSessionTimesChart.cpp index 84d3a02a..3cd4e41d 100644 --- a/sleepyhead/Graphs/gSessionTimesChart.cpp +++ b/sleepyhead/Graphs/gSessionTimesChart.cpp @@ -18,6 +18,7 @@ gSessionTimesChart::gSessionTimesChart(QString label, MachineType machtype) :Layer(NoChannel), m_label(label), m_machtype(machtype) { + m_layertype = LT_SessionTimes; QDateTime d1 = QDateTime::currentDateTime(); QDateTime d2 = d1; d1.setTimeSpec(Qt::UTC); // CHECK: Does this deal with DST? @@ -34,106 +35,213 @@ void gSessionTimesChart::SetDay(Day *unused_day) Q_UNUSED(unused_day) Layer::SetDay(nullptr); - QDate firstday = p_profile->FirstDay(m_machtype); - QDate lastday = p_profile->LastDay(m_machtype); + firstday = p_profile->FirstDay(m_machtype); + lastday = p_profile->LastDay(m_machtype); + + QDate date = firstday; + do { + QMap::iterator di = p_profile->daylist.find(date); + Day * day = di.value(); + if (di == p_profile->daylist.end()) { + } + } while ((date = date.addDays(1)) < lastday); m_minx = QDateTime(firstday, QTime(0,0,0)).toMSecsSinceEpoch(); m_maxx = QDateTime(lastday, QTime(23,59,59)).toMSecsSinceEpoch(); + m_miny = 0; + m_maxy = 30; + m_empty = false; - // Get list of valid day records in supplied date range - QList daylist = p_profile->getDays(m_machtype, firstday, lastday); - - if (daylist.size() == 0) { - m_miny = m_maxy = 0; - return; - } - - int cnt = 0; - bool first = true; - QList::iterator end = daylist.end(); - - quint32 dn; // day number - - // For each day - for (QList::iterator it = daylist.begin(); it != end; ++it) { - Day * day = (*it); - - if (day->size() == 0) continue; - dn = day->first() / 86400000L; - - // For each session - for (int i=0; i < day->size(); i++) { - Session * session = (*day)[i]; - if (!session->enabled()) continue; - - // calculate start and end hours - float start = ((session->first() / 1000L) % 86400) / 3600.0; - float end = ((session->last() / 1000L) % 86400) / 3600.0; - - // apply tzoffset?? - - // update min & max Y values - if (first) { - first = false; - m_miny = start; - m_maxy = end; - } else { - if (start < m_miny) m_miny = start; - if (end > m_maxy) m_maxy = end; - } - - sessiontimes[cnt].push_back(TimeSpan(start,end)); - } - } } -void gSessionTimesChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) +QColor brighten(QColor color, float mult = 2.0); + + +void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion ®ion) { - Q_UNUSED(painter) - Q_UNUSED(w) - Q_UNUSED(region) + QRect rect = region.boundingRect(); - QMap >::iterator st_end = sessiontimes.end(); - QMap >::iterator it; + painter.setPen(QColor(Qt::black)); + painter.drawRect(rect); + + m_minx = graph.min_x; + m_maxx = graph.max_x; + + EventDataType miny; + EventDataType maxy; + + graph.roundY(miny, maxy); + + QDateTime date2 = QDateTime::fromMSecsSinceEpoch(m_minx); + QDateTime enddate2 = QDateTime::fromMSecsSinceEpoch(m_maxx); + + QDate date = date2.date(); + QDate enddate = enddate2.date(); + + QString text = QString("Work in progress, I know about the bugs :P There is a very good and urgent reason for redoing this graph... "); //.arg(date2.toString("yyyyMMdd hh:mm:ss")).arg(enddate2.toString("yyyyMMdd hh:mm:ss")); + painter.setFont(*defaultfont); + painter.drawText(rect.left(), rect.top()-4, text); + + int days = ceil(double(m_maxx - m_minx) / 86400000.0); + + float barw = float(rect.width()) / float(days); + + QTime split = p_profile->session->daySplitTime(); + QDateTime splittime; - for (it = sessiontimes.begin(); it != st_end; ++it) { -// int dn = it.key(); - QList & st = it.value(); - int stsize = st.size(); + //float maxy = m_maxy; + float ymult = float(rect.height()) / (maxy-miny); - // Skip if empty - if (stsize == 0) continue; + int dn = 0; + float lasty1 = rect.bottom(); + float lastx1 = rect.left(); + + do { + QMap::iterator di = p_profile->daylist.find(date); + + if (di == p_profile->daylist.end()) { + dn++; + lasty1 = rect.bottom(); + lastx1 += barw; + continue; + } + Day * day = di.value(); +// if (day->first() > m_maxx) { //|| (day->last() < m_minx)) { +// continue; +// } + splittime = QDateTime(date, split); + + float x1 = lastx1 + barw; + QList::iterator si; - } + if ((lastx1 + barw) > (rect.left()+rect.width()+1)) + break; + bool hl = false; + + QPoint mouse = graph.graphView()->currentMousePos(); + + QRect rec2(lastx1, rect.top(), barw, rect.height()); + if (rec2.contains(mouse)) { + QColor col2(255,0,0,64); + painter.fillRect(rec2, QBrush(col2)); + hl = true; + } + + bool haveoxi = day->hasMachine(MT_OXIMETER); + + QColor goodcolor = haveoxi ? QColor(128,196,255) : Qt::blue; + + for (si = day->begin(); si != day->end(); ++si) { + Session *sess = (*si); + if (!sess->enabled() || (sess->machine()->type() != m_machtype)) continue; + + int slize = sess->m_slices.size(); + if (slize > 0) { + // segments + for (int i=0; im_slices.at(i); + float s1 = float(splittime.secsTo(QDateTime::fromMSecsSinceEpoch(slice.start))) / 3600.0; + + float s2 = double(slice.end - slice.start) / 3600000.0; + + float y1 = (s1 * ymult); + float y2 = (s2 * ymult); + + QColor col = (slice.status == EquipmentOn) ? goodcolor : Qt::black; + QColor col2 = brighten(col,2.5); + + + QRect rec(lastx1, rect.bottom() - y1 - y2, barw, y2); + QLinearGradient gradient(lastx1, rect.bottom(), lastx1+barw, rect.bottom()); + + if (rec.contains(mouse)) { +// if (hl) { + col = Qt::yellow; + } + + gradient.setColorAt(0,col); + gradient.setColorAt(1,col2); + painter.fillRect(rec, QBrush(gradient)); + painter.setPen(QPen(Qt::black,1)); + painter.drawRect(rec); + + } + } else { + qint64 sf = sess->first(); + QDateTime st = QDateTime::fromMSecsSinceEpoch(sf); + float s1 = float(splittime.secsTo(st)) / 3600.0; + + float s2 = sess->hours(); + + float y1 = (s1 * ymult); + float y2 = (s2 * ymult); + + QColor col = goodcolor; + + QLinearGradient gradient(lastx1, rect.bottom(), lastx1+barw, rect.bottom()); + QRect rec(lastx1, rect.bottom() - y1 - y2, barw, y2); + if (rec.contains(mouse)) { + QString text = QObject::tr("%1\nBedtime:%2\nLength:%3").arg(st.date().toString(Qt::SystemLocaleDate)).arg(st.time().toString("hh:mm:ss")).arg(s2,0,'f',2); + graph.ToolTip(text,mouse.x() - 15,mouse.y() + 15, TT_AlignRight); + + col = QColor("gold"); + } + + QColor col2 = brighten(col,2.5); + + gradient.setColorAt(0,col); + gradient.setColorAt(1,col2); + + painter.fillRect(rec, QBrush(gradient)); + painter.setPen(QPen(Qt::black,1)); + painter.drawRect(rec); + + + // no segments + } + } + +// float y = double(day->total_time(m_machtype)) / 3600000.0; +// float y1 = rect.bottom() - (y * ymult); +// float x1 = lastx1 + barw; +// painter.drawLine(lastx1, lasty1, lastx1,y1); +// painter.drawLine(lastx1, y1, x1, y1); + + dn++; +// lasty1 = y1; + lastx1 = x1; + + } while ((date = date.addDays(1)) <= enddate); + } bool gSessionTimesChart::keyPressEvent(QKeyEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) - return true; + return false; } bool gSessionTimesChart::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) - return true; + return false; } bool gSessionTimesChart::mousePressEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) - return true; + return false; } bool gSessionTimesChart::mouseReleaseEvent(QMouseEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) - return true; + return false; } diff --git a/sleepyhead/Graphs/gSessionTimesChart.h b/sleepyhead/Graphs/gSessionTimesChart.h index ae12b204..0da9eae4 100644 --- a/sleepyhead/Graphs/gSessionTimesChart.h +++ b/sleepyhead/Graphs/gSessionTimesChart.h @@ -71,6 +71,8 @@ protected: int hl_day; int tz_offset; float tz_hours; + QDate firstday; + QDate lastday; QMap > sessiontimes; }; diff --git a/sleepyhead/Graphs/gYAxis.cpp b/sleepyhead/Graphs/gYAxis.cpp index f65d1b59..39f0e094 100644 --- a/sleepyhead/Graphs/gYAxis.cpp +++ b/sleepyhead/Graphs/gYAxis.cpp @@ -320,11 +320,11 @@ const QString gYAxisTime::Format(EventDataType v, int dp) int m = int(v * 60) % 60; int s = int(v * 3600) % 60; - char pm[3] = {"am"}; + char pm[3] = {"pm"}; if (show_12hr) { - h >= 12 ? pm[0] = 'p' : pm[0] = 'a'; + h >= 12 ? pm[0] = 'a' : pm[0] = 'p'; h %= 12; if (h == 0) { h = 12; } diff --git a/sleepyhead/Graphs/layer.h b/sleepyhead/Graphs/layer.h index 3e63cc2a..43d020e2 100644 --- a/sleepyhead/Graphs/layer.h +++ b/sleepyhead/Graphs/layer.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 }; +enum LayerType { LT_Other = 0, LT_LineChart, LT_SummaryChart, LT_EventFlags, LT_Spacer, LT_SessionTimes }; /*! \class Layer \brief The base component for all individual Graph layers diff --git a/sleepyhead/SleepLib/day.h b/sleepyhead/SleepLib/day.h index cb663d63..56b767bf 100644 --- a/sleepyhead/SleepLib/day.h +++ b/sleepyhead/SleepLib/day.h @@ -91,6 +91,8 @@ class Day //! \brief Returns if the cache contains SummaryType information about the requested code bool hasData(ChannelID code, SummaryType type); + inline bool hasMachine(MachineType mt) const { return machines.contains(mt); } + //! \brief Returns the Average of all Sessions setting 'code' for this day EventDataType settings_avg(ChannelID code); @@ -278,7 +280,9 @@ class Day int useCounter() { return d_useCounter; } protected: - //! \brief A Vector containing all sessions for this day + + + QHash > perc_cache; //qint64 d_first,d_last; private: diff --git a/sleepyhead/SleepLib/loader_plugins/prs1_loader.cpp b/sleepyhead/SleepLib/loader_plugins/prs1_loader.cpp index fd707d75..b4640f03 100644 --- a/sleepyhead/SleepLib/loader_plugins/prs1_loader.cpp +++ b/sleepyhead/SleepLib/loader_plugins/prs1_loader.cpp @@ -1173,13 +1173,39 @@ bool PRS1Import::ParseCompliance() session->settings[PRS1_HumidStatus] = (bool)(data[0x0A] & 0x80); // Humidifier Connected session->settings[PRS1_HumidLevel] = (int)(data[0x0A] & 7); // Humidifier Value + // need to parse a repeating structure here containing lengths of mask on/off.. + // 0x03 = mask on + // 0x01 = mask off - // This is probably wrong - summary_duration = data[0x12] | data[0x13] << 8; + qint64 start = qint64(compliance->timestamp) * 1000L; + qint64 tt = start; - session->set_first(qint64(compliance->timestamp) * 1000L); - session->set_last(qint64(compliance->timestamp + (summary_duration * 2)) * 1000L); + int len = compliance->size()-3; + int pos = 0x11; + do { + quint8 c = data[pos++]; + quint64 duration = data[pos] | data[pos+1] << 8; + pos+=2; + duration *= 1000L; + SliceStatus status; + if (c == 0x03) { + status = EquipmentOn; + } else if (c == 0x02) { + status = EquipmentLeaking; + } else if (c == 0x01) { + status = EquipmentOff; + } else { + qDebug() << compliance->sessionid << "Wasn't expecting" << c; + break; + } + session->m_slices.append(SessionSlice(tt, tt + duration, status)); + qDebug() << compliance->sessionid << "Added Slice" << tt << (tt+duration) << status; + tt += duration; + } while (pos < len); + + session->set_first(start); + session->set_last(tt); // Bleh!! There is probably 10 different formats for these useless piece of junk machines return true; diff --git a/sleepyhead/SleepLib/session.h b/sleepyhead/SleepLib/session.h index 9cbe1813..32a02977 100644 --- a/sleepyhead/SleepLib/session.h +++ b/sleepyhead/SleepLib/session.h @@ -23,6 +23,29 @@ //class EventList; class Machine; +enum SliceStatus { + UnknownStatus=0, EquipmentOff, EquipmentLeaking, EquipmentOn +}; + +class SessionSlice +{ +public: + SessionSlice() { + start = end = 0; + status = UnknownStatus; + } + SessionSlice(const SessionSlice & copy) { + start = copy.start; + end = copy.end; + status = copy.status; + } + SessionSlice(qint64 start, qint64 end, SliceStatus status):start(start), end(end), status(status) {} + + qint64 start; + qint64 end; + SliceStatus status; +}; + /*! \class Session \brief Contains a single Sessions worth of machine event/waveform information. @@ -179,6 +202,8 @@ class Session QList m_availableChannels; + QList m_slices; + const QList & availableChannels() { return m_availableChannels; } //! \brief Generates sum and time data for each distinct value in 'code' events.. diff --git a/sleepyhead/overview.cpp b/sleepyhead/overview.cpp index 57dfe388..6aa8e99c 100644 --- a/sleepyhead/overview.cpp +++ b/sleepyhead/overview.cpp @@ -124,6 +124,10 @@ Overview::Overview(QWidget *parent, gGraphView *shared) : } + 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); diff --git a/sleepyhead/overview.h b/sleepyhead/overview.h index 84bfcada..891d87e0 100644 --- a/sleepyhead/overview.h +++ b/sleepyhead/overview.h @@ -16,6 +16,7 @@ #include "SleepLib/profiles.h" #include "Graphs/gGraphView.h" #include "Graphs/gSummaryChart.h" +#include "Graphs/gSessionTimesChart.h" namespace Ui { class Overview; @@ -62,11 +63,13 @@ class Overview : public QWidget 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; + *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; + gSessionTimesChart * stg; + //! \breif List of SummaryCharts shown on the overview page QVector OverviewCharts;