From f2facb9da90d744f1e48687754ae5ba2392445aa Mon Sep 17 00:00:00 2001
From: Mark Watkins <jedimark@users.sourceforge.net>
Date: Mon, 22 Sep 2014 14:32:15 +1000
Subject: [PATCH] Add AirSense 10 CSR flags. Fixed summary load order messing
 up day splitting.

---
 sleepyhead/Graphs/gFlagsLine.cpp              |  14 +-
 sleepyhead/Graphs/glcommon.h                  |   2 +-
 .../SleepLib/loader_plugins/resmed_loader.cpp | 192 ++++++++++++++++--
 .../SleepLib/loader_plugins/resmed_loader.h   |   4 +
 sleepyhead/SleepLib/machine.cpp               |  23 ++-
 sleepyhead/SleepLib/machine.h                 |   2 +-
 sleepyhead/preferencesdialog.cpp              |   6 +-
 7 files changed, 211 insertions(+), 32 deletions(-)

diff --git a/sleepyhead/Graphs/gFlagsLine.cpp b/sleepyhead/Graphs/gFlagsLine.cpp
index 80c1e412..288cbba0 100644
--- a/sleepyhead/Graphs/gFlagsLine.cpp
+++ b/sleepyhead/Graphs/gFlagsLine.cpp
@@ -176,11 +176,17 @@ void gFlagsGroup::paint(QPainter &painter, gGraph &g, const QRegion &region)
     QColor barcol;
 
     for (int i = 0; i < visflags.size(); i++) {
+        schema::Channel & chan = schema::channel[visflags.at(i)->code()];
+
         // Alternating box color
         if (i & 1) { barcol = COLOR_ALT_BG1; }
         else { barcol = COLOR_ALT_BG2; }
 
-        painter.fillRect(left, linetop, width-1, m_barh, QBrush(barcol));
+        painter.fillRect(left, floor(linetop), width-1, ceil(m_barh), QBrush(barcol));
+
+//        barcol = chan.defaultColor();
+//        barcol.setAlpha(16);
+//        painter.fillRect(left, floor(linetop), width-1, ceil(m_barh), QBrush(barcol));
 
         // Paint the actual flags
         QRect rect(left, linetop, width, m_barh);
@@ -350,12 +356,12 @@ void gFlagsLine::paint(QPainter &painter, gGraph &w, const QRegion &region)
                 for (; dptr < eptr; dptr++) {
                     X = start + * tptr++;
 
-                    if (X > maxx) {
-                        break;
-                    }
 
                     L = *dptr * 1000L;
                     X2 = X - L;
+                    if (X2 > maxx) {
+                        break;
+                    }
 
                     x1 = double(X - minx) * xmult + left;
                     x2 = double(X2 - minx) * xmult + left;
diff --git a/sleepyhead/Graphs/glcommon.h b/sleepyhead/Graphs/glcommon.h
index d63cff99..ba82e79e 100644
--- a/sleepyhead/Graphs/glcommon.h
+++ b/sleepyhead/Graphs/glcommon.h
@@ -50,7 +50,7 @@ const QColor COLOR_Brown = QColor("brown");
 const QColor COLOR_Text = Qt::black;
 const QColor COLOR_Outline = Qt::black;
 
-const QColor COLOR_ALT_BG1 = QColor(0xd8, 0xff, 0xd8, 0xff); // Alternating Background Color 1 (Event Flags)
+const QColor COLOR_ALT_BG1 = QColor(0xc8, 0xff, 0xc8, 0x7f); // Alternating Background Color 1 (Event Flags)
 const QColor COLOR_ALT_BG2 = COLOR_White;                    // Alternating Background Color 2 (Event Flags)
 
 
diff --git a/sleepyhead/SleepLib/loader_plugins/resmed_loader.cpp b/sleepyhead/SleepLib/loader_plugins/resmed_loader.cpp
index 723025e4..bcc49a1b 100644
--- a/sleepyhead/SleepLib/loader_plugins/resmed_loader.cpp
+++ b/sleepyhead/SleepLib/loader_plugins/resmed_loader.cpp
@@ -764,9 +764,11 @@ void ResmedImport::run()
 #endif
     }
 
-    // Load annotations afterwards so durations are set correctly
     Q_FOREACH(QString file, files[EDF_CSL]) {
-//        loader->LoadCSL(sess, file);
+        loader->LoadCSL(sess, file);
+#ifdef SESSION_DEBUG
+        sess->session_files.append(file);
+#endif
     }
 
     bool haveeve = false;
@@ -1198,7 +1200,7 @@ EDFType lookupEDFType(QString text)
 }
 
 // Pretend to parse the EVE file to get the duration out of it.
-int PeekEVE(const QString & path, quint32 &start, quint32 &end)
+int PeekAnnotations(const QString & path, quint32 &start, quint32 &end)
 {
     EDFParser edf(path);
     if (!edf.Parse())
@@ -1251,7 +1253,7 @@ int PeekEVE(const QString & path, quint32 &start, quint32 &end)
             d = t.toDouble(&ok);
 
             if (!ok) {
-                qDebug() << "Faulty EDF EVE file " << edf.filename;
+                qDebug() << "Faulty EDF Annotations file " << edf.filename;
                 break;
             }
 
@@ -1275,7 +1277,7 @@ int PeekEVE(const QString & path, quint32 &start, quint32 &end)
                 duration = t.toDouble(&ok);
 
                 if (!ok) {
-                    qDebug() << "Faulty EDF EVE file (at %" << pos << ") " << edf.filename;
+                    qDebug() << "Faulty EDF Annotations file (at %" << pos << ") " << edf.filename;
                     break;
                 }
             }
@@ -1300,23 +1302,13 @@ int PeekEVE(const QString & path, quint32 &start, quint32 &end)
 
                 if (!t.isEmpty() && (t!="recording starts")) {
                     goodrecs++;
-//                    if (matchSignal(CPAP_Obstructive, t)) {
-//                    } else if (matchSignal(CPAP_Hypopnea, t)) {
-//                    } else if (matchSignal(CPAP_Apnea, t)) {
-//                    } else if (matchSignal(CPAP_ClearAirway, t)) {
-//                    } else {
-//                        if (t != "recording starts") {
-//                            qDebug() << "Unobserved ResMed annotation field: " << t;
-//                        }
-//                    }
                 }
 
                 if (pos >= recs) {
-                    qDebug() << "Short EDF EVE file" << edf.filename;
+                    qDebug() << "Short EDF Annotations file" << edf.filename;
                     break;
                 }
 
-                // pos++;
             }
 
             while ((data[pos] == 0) && (pos < recs)) { pos++; }
@@ -1429,12 +1421,12 @@ EDFduration getEDFDuration(QString filename)
 
     if (end < start) end = qMax(st2, start);
 
-    if (ext == "EVE") {
+    if ((ext == "EVE") || (ext == "CSL")) {
         // S10 Forces us to parse EVE files to find their real durations
         quint32 en2;
 
         // Have to get the actual duration of the EVE file by parsing the annotations. :(
-        int recs = PeekEVE(filename, st2, en2);
+        int recs = PeekAnnotations(filename, st2, en2);
         if (recs > 0) {
             start = qMin(st2, start);
             end = qMax(en2, end);
@@ -1444,7 +1436,7 @@ EDFduration getEDFDuration(QString filename)
 
             return dur;
         } else {
-            // empty EVE file, don't give a crap about it...
+            // empty annotations file, don't give a crap about it...
             return EDFduration(0, 0, filename);
         }
         // A Firmware bug causes (perhaps with failing SD card) sessions to sometimes take a long time to write
@@ -1593,9 +1585,8 @@ int ResmedLoader::scanFiles(Machine * mach, QString datalog_path)
     EDForder.push_back(EDF_PLD);
     EDForder.push_back(EDF_BRP);
     EDForder.push_back(EDF_SAD);
-    EDForder.push_back(EDF_CSL);
 
-    for (int i=0; i<4; i++) {
+    for (int i=0; i<3; i++) {
         EDFType basetype = EDForder.takeFirst();
 
         // Process PLD files
@@ -1659,6 +1650,24 @@ int ResmedLoader::scanFiles(Machine * mach, QString datalog_path)
                 }
             }
 
+            // CSL files contain CSR flags
+            QList<EDFduration *> & CSL_list = filesbytype[EDF_CSL];
+            list_end = CSL_list.end();
+            for (item = CSL_list.begin(); item != list_end; ++item) {
+                const EDFduration * dur2 = *item;
+                if (dur2->start == 0) continue;
+
+                // Do the sessions Overlap?
+                if ((start < dur2->end) && ( dur2->start < end)) {
+//                    start = qMin(start, dur2->start);
+//                    end = qMax(end, dur2->end);
+
+                    files.append(dur2->filename);
+
+                    grp[EDF_CSL].append(create_backups ? backup(dur2->path, backup_path) : dur2->path);
+                }
+            }
+
 
 
             if (mach->SessionExists(start) == nullptr) {
@@ -2257,6 +2266,147 @@ QString ResmedLoader::backup(QString fullname, QString backup_path)
     return newname;
 }
 
+bool ResmedLoader::LoadCSL(Session *sess, const QString & path)
+{
+    EDFParser edf(path);
+    if (!edf.Parse())
+        return false;
+
+    QString t;
+
+    long recs;
+    double duration;
+    char *data;
+    char c;
+    long pos;
+    bool sign, ok;
+    double d;
+    double tt;
+
+    // Notes: Event records have useless duration record.
+   // sess->updateFirst(edf.startdate);
+
+    EventList *CSR = nullptr;
+
+    // Allow for empty sessions..
+    qint64 csr_starts = 0;
+
+
+    // Process event annotation records
+    for (int s = 0; s < edf.GetNumSignals(); s++) {
+        recs = edf.edfsignals[s].nr * edf.GetNumDataRecords() * 2;
+
+        data = (char *)edf.edfsignals[s].data;
+        pos = 0;
+        tt = edf.startdate;
+    //    sess->updateFirst(tt);
+        duration = 0;
+
+        while (pos < recs) {
+            c = data[pos];
+
+            if ((c != '+') && (c != '-')) {
+                break;
+            }
+
+            if (data[pos++] == '+') { sign = true; }
+            else { sign = false; }
+
+            t = "";
+            c = data[pos];
+
+            do {
+                t += c;
+                pos++;
+                c = data[pos];
+            } while ((c != 20) && (c != 21)); // start code
+
+            d = t.toDouble(&ok);
+
+            if (!ok) {
+                qDebug() << "Faulty EDF CSL file " << edf.filename;
+                break;
+            }
+
+            if (!sign) { d = -d; }
+
+            tt = edf.startdate + qint64(d * 1000.0);
+            duration = 0;
+            // First entry
+
+            if (data[pos] == 21) {
+                pos++;
+                // get duration.
+                t = "";
+
+                do {
+                    t += data[pos];
+                    pos++;
+                } while ((data[pos] != 20) && (pos < recs)); // start code
+
+                duration = t.toDouble(&ok);
+
+                if (!ok) {
+                    qDebug() << "Faulty EDF CSL file (at %" << pos << ") " << edf.filename;
+                    break;
+                }
+            }
+
+            while ((data[pos] == 20) && (pos < recs)) {
+                t = "";
+                pos++;
+
+                if (data[pos] == 0) {
+                    break;
+                }
+
+                if (data[pos] == 20) {
+                    pos++;
+                    break;
+                }
+
+                do {
+                    t += tolower(data[pos++]);
+                } while ((data[pos] != 20) && (pos < recs)); // start code
+
+                if (!t.isEmpty()) {
+                    if (t == "csr start") {
+                        csr_starts = tt;
+                    } else if (t == "csr end") {
+                        if (!CSR) {
+                            CSR = sess->AddEventList(CPAP_CSR, EVL_Event);
+                        }
+
+                        if (csr_starts > 0) {
+                            if (sess->checkInside(csr_starts))
+                                CSR->AddEvent(tt, double(tt - csr_starts) / 1000.0);
+                            csr_starts = 0;
+                        } else {
+                            qDebug() << "If you can read this, ResMed sucks and split CSR flagging!";
+                        }
+                    } else if (t != "recording starts") {
+                        qDebug() << "Unobserved ResMed CSL annotation field: " << t;
+                    }
+                }
+
+                if (pos >= recs) {
+                    qDebug() << "Short EDF CSL file" << edf.filename;
+                    break;
+                }
+
+                // pos++;
+            }
+
+            while ((data[pos] == 0) && (pos < recs)) { pos++; }
+
+            if (pos >= recs) { break; }
+        }
+
+    //    sess->updateLast(tt);
+    }
+
+    return true;
+}
 
 bool ResmedLoader::LoadEVE(Session *sess, const QString & path)
 {
diff --git a/sleepyhead/SleepLib/loader_plugins/resmed_loader.h b/sleepyhead/SleepLib/loader_plugins/resmed_loader.h
index 49e9fdf5..3ea70972 100644
--- a/sleepyhead/SleepLib/loader_plugins/resmed_loader.h
+++ b/sleepyhead/SleepLib/loader_plugins/resmed_loader.h
@@ -424,6 +424,10 @@ class ResmedLoader : public CPAPLoader
     //! This contains all Hypopnea, Obstructive Apnea, Central and Apnea codes
     bool LoadEVE(Session *sess, const QString & path);
 
+    //! \brief Parse the CSL Event annotation data, and save to Session * sess
+    //! This contains Cheyne Stokes Respiration flagging on the AirSense 10
+    bool LoadCSL(Session *sess, const QString & path);
+
     //! \brief Parse the BRP High Resolution data, and save to Session * sess
     //! This contains Flow Rate, Mask Pressure, and Resp. Event  data
     bool LoadBRP(Session *sess, const QString & path);
diff --git a/sleepyhead/SleepLib/machine.cpp b/sleepyhead/SleepLib/machine.cpp
index 687777af..ebe2bbd6 100644
--- a/sleepyhead/SleepLib/machine.cpp
+++ b/sleepyhead/SleepLib/machine.cpp
@@ -844,7 +844,7 @@ bool Machine::hasModifiedSessions()
 
 const QString summaryFileName = "Summaries.xml";
 
-bool Machine::LoadSummary(bool everything)
+bool Machine::LoadSummary()
 {
     QTime time;
     time.start();
@@ -885,6 +885,9 @@ bool Machine::LoadSummary(bool everything)
     QDomNodeList sessionlist = root.childNodes();
 
     int size = sessionlist.size();
+
+    QMap<qint64, Session *>  sess_order;
+
     for (int s=0; s < size; ++s) {
         node = sessionlist.at(s);
         QDomElement e = node.toElement();
@@ -899,9 +902,21 @@ bool Machine::LoadSummary(bool everything)
             sess->really_set_last(last);
             sess->setEnabled(enabled);
             sess->setSummaryOnly(!events);
-            if (!AddSession(sess))
-                delete sess;
-      //      sess->LoadSummary();
+
+            sess_order[first] = sess;
+        }
+    }
+    QMap<qint64, Session *>::iterator it_end = sess_order.end();
+    QMap<qint64, Session *>::iterator it;
+    int cnt = 0;
+    bool loadSummaries = p_profile->session->preloadSummaries();
+
+    for (it = sess_order.begin(); it != it_end; ++it, ++cnt) {
+        Session * sess = it.value();
+        if (!AddSession(sess)) {
+            delete sess;
+        } else {
+            if (loadSummaries) sess->LoadSummary();
         }
     }
 
diff --git a/sleepyhead/SleepLib/machine.h b/sleepyhead/SleepLib/machine.h
index dae02682..5677beaa 100644
--- a/sleepyhead/SleepLib/machine.h
+++ b/sleepyhead/SleepLib/machine.h
@@ -87,7 +87,7 @@ class Machine
 
     //! \brief Load all Machine summary data
     bool Load();
-    bool LoadSummary(bool everything = false);
+    bool LoadSummary();
 
     //! \brief Save all Sessions where changed bit is set.
     bool Save();
diff --git a/sleepyhead/preferencesdialog.cpp b/sleepyhead/preferencesdialog.cpp
index 54da4ddf..2d4c4325 100644
--- a/sleepyhead/preferencesdialog.cpp
+++ b/sleepyhead/preferencesdialog.cpp
@@ -728,7 +728,11 @@ bool PreferencesDialog::Save()
     profile->cpap->setShowComplianceInfo(ui->complianceCheckBox->isChecked());
     profile->cpap->setComplianceHours(ui->complianceHours->value());
 
-    profile->appearance->setGraphHeight(ui->graphHeight->value());
+    if (ui->graphHeight->value() != profile->appearance->graphHeight()) {
+        profile->appearance->setGraphHeight(ui->graphHeight->value());
+        mainwin->getDaily()->ResetGraphLayout();
+        mainwin->getOverview()->ResetGraphLayout();
+    }
 
     profile->general->setPrefCalcMiddle(ui->prefCalcMiddle->currentIndex());
     profile->general->setPrefCalcMax(ui->prefCalcMax->currentIndex());