diff --git a/sleepyhead/Graphs/gGraphView.cpp b/sleepyhead/Graphs/gGraphView.cpp index 3012e965..e71dee9b 100644 --- a/sleepyhead/Graphs/gGraphView.cpp +++ b/sleepyhead/Graphs/gGraphView.cpp @@ -3170,6 +3170,7 @@ const quint16 gvversion = 4; void gGraphView::SaveSettings(QString title) { + qDebug() << "Saving" << title << "settings"; QString filename = p_profile->Get("{DataFolder}/") + title.toLower() + ".shg"; QFile f(filename); f.open(QFile::WriteOnly); diff --git a/sleepyhead/Graphs/gGraphView.h b/sleepyhead/Graphs/gGraphView.h index d0c4c72f..729f5072 100644 --- a/sleepyhead/Graphs/gGraphView.h +++ b/sleepyhead/Graphs/gGraphView.h @@ -521,7 +521,7 @@ class gGraphView int strings_drawn_this_frame; int strings_cached_this_frame; - QList history; + QVector history; protected: @@ -621,7 +621,7 @@ class gGraphView qint64 m_minx, m_maxx; - QList fwd_history; + QVector fwd_history; float print_scaleX, print_scaleY; QPixmap previous_day_snapshot; diff --git a/sleepyhead/Graphs/gLineChart.h b/sleepyhead/Graphs/gLineChart.h index 2ef8e7c9..4895bda1 100644 --- a/sleepyhead/Graphs/gLineChart.h +++ b/sleepyhead/Graphs/gLineChart.h @@ -121,7 +121,7 @@ class gLineChart: public Layer QString getMetaString(qint64 time); void addDotLine(DottedLine dot) { m_dotlines.append(dot); } - QList m_dotlines; + QVector m_dotlines; QHash m_flags_enabled; QHash > m_dot_enabled; diff --git a/sleepyhead/Graphs/gSessionTimesChart.cpp b/sleepyhead/Graphs/gSessionTimesChart.cpp index 0dcdaf0f..79b67ded 100644 --- a/sleepyhead/Graphs/gSessionTimesChart.cpp +++ b/sleepyhead/Graphs/gSessionTimesChart.cpp @@ -162,13 +162,14 @@ void gSummaryChart::preCalc() } } -void gSummaryChart::customCalc(Day *day, QList & slices) +void gSummaryChart::customCalc(Day *day, QVector & slices) { - if (slices.size() != calcitems.size()) { + int size = slices.size(); + if (size != calcitems.size()) { return; } float hour = day->hours(m_machtype); - for (int i=0; i & slices = cache[idx]; + QVector & slices = cache[idx]; QString txt; for (int i=0; i< slices.size(); ++i) { SummaryChartSlice & slice = slices[i]; @@ -320,7 +321,7 @@ void gSummaryChart::populate(Day * day, int idx) } if (!good) return; - QList & slices = cache[idx]; + QVector & slices = cache[idx]; float hours = day->hours(m_machtype); float base = 0; @@ -476,7 +477,7 @@ void gSummaryChart::paint(QPainter &painter, gGraph &graph, const QRegion ®io day->OpenSummary(); - QHash >::iterator cit = cache.find(i); + QHash >::iterator cit = cache.find(i); if (cit == cache.end()) { populate(day, i); @@ -484,7 +485,7 @@ void gSummaryChart::paint(QPainter &painter, gGraph &graph, const QRegion ®io } if (cit != cache.end()) { - QList & list = cit.value(); + QVector & list = cit.value(); float base = 0, val; int listsize = list.size(); for (int j=0; j < listsize; ++j) { @@ -549,7 +550,7 @@ void gSummaryChart::paint(QPainter &painter, gGraph &graph, const QRegion ®io hlday = true; } - QHash >::iterator cit = cache.find(idx); + QHash >::iterator cit = cache.find(idx); if (cit == cache.end()) { populate(day, idx); @@ -561,7 +562,7 @@ void gSummaryChart::paint(QPainter &painter, gGraph &graph, const QRegion ®io ///////////////////////////////////////////////////////////////////////////////////// /// Draw pressure settings ///////////////////////////////////////////////////////////////////////////////////// - QList & list = cit.value(); + QVector & list = cit.value(); customCalc(day, list); int listsize = list.size(); @@ -651,7 +652,7 @@ QString gUsageChart::tooltipData(Day * day, int) void gUsageChart::populate(Day *day, int idx) { - QList & slices = cache[idx]; + QVector & slices = cache[idx]; float hours = day->hours(); @@ -675,7 +676,7 @@ void gUsageChart::preCalc() calc.reset(idx_end - idx_start); } -void gUsageChart::customCalc(Day *, QList &list) +void gUsageChart::customCalc(Day *, QVector &list) { if (list.size() == 0) { incompdays++; @@ -738,7 +739,7 @@ void gSessionTimesChart::preCalc() { calc2.reset(idx_end - idx_start); } -void gSessionTimesChart::customCalc(Day *, QList & slices) { +void gSessionTimesChart::customCalc(Day *, QVector & slices) { int size = slices.size(); num_slices += size; @@ -867,7 +868,7 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion & continue; } - QHash >::iterator cit = cache.find(i); + QHash >::iterator cit = cache.find(i); if (cit == cache.end()) { day->OpenSummary(); @@ -875,7 +876,7 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion & splittime = QDateTime(date, split); QList::iterator si; - QList & slices = cache[i]; + QVector & slices = cache[i]; bool haveoxi = day->hasMachine(MT_OXIMETER); @@ -923,7 +924,7 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion & } if (cit != cache.end()) { - QList & list = cit.value(); + QVector & list = cit.value(); int listsize = list.size(); float peak = 0, base = 999; @@ -981,7 +982,7 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion & continue; } - QHash >::iterator cit = cache.find(idx); + QHash >::iterator cit = cache.find(idx); float x1 = lastx1 + barw; @@ -995,7 +996,7 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion & } if (cit != cache.end()) { - QList & slices = cit.value(); + QVector & slices = cit.value(); customCalc(day, slices); int size = slices.size(); @@ -1055,7 +1056,7 @@ void gTTIAChart::preCalc() { gSummaryChart::preCalc(); } -void gTTIAChart::customCalc(Day *, QList & slices) +void gTTIAChart::customCalc(Day *, QVector & slices) { if (slices.size() == 0) return; const SummaryChartSlice & slice = slices.at(0); @@ -1069,8 +1070,8 @@ void gTTIAChart::afterDraw(QPainter &, gGraph &graph, QRect rect) for (int i=0; i < num_channels; ++i) { SummaryCalcItem & calc = calcitems[i]; - ChannelID code = calc.code; - schema::Channel & chan = schema::channel[code]; + //ChannelID code = calc.code; + //schema::Channel & chan = schema::channel[code]; float mid = 0; switch (midcalc) { case 0: @@ -1097,7 +1098,7 @@ void gTTIAChart::afterDraw(QPainter &, gGraph &graph, QRect rect) } void gTTIAChart::populate(Day *day, int idx) { - QList & slices = cache[idx]; + QVector & slices = cache[idx]; float ttia = day->sum(CPAP_Obstructive) + day->sum(CPAP_ClearAirway) + day->sum(CPAP_Apnea) + day->sum(CPAP_Hypopnea); int h = ttia / 3600; int m = int(ttia) / 60 % 60; @@ -1108,7 +1109,7 @@ void gTTIAChart::populate(Day *day, int idx) } QString gTTIAChart::tooltipData(Day *, int idx) { - QList & slices = cache[idx]; + QVector & slices = cache[idx]; if (slices.size() == 0) return QString(); const SummaryChartSlice & slice = slices.at(0); @@ -1132,7 +1133,7 @@ void gAHIChart::preCalc() ahi_data.clear(); ahi_data.reserve(idx_end-idx_start); } -void gAHIChart::customCalc(Day *day, QList &list) +void gAHIChart::customCalc(Day *day, QVector &list) { int size = list.size(); if (size == 0) return; @@ -1181,27 +1182,31 @@ void gAHIChart::afterDraw(QPainter & /*painter */, gGraph &graph, QRect rect) //int size = idx_end - idx_start; + bool skip = true; float med = 0; switch (midcalc) { case 0: if (ahi_data.size() > 0) { med = median(ahi_data.begin(), ahi_data.end()); + skip = false; } break; case 1: // wavg if (total_hours > 0) { med = ahi_wavg / total_hours; + skip = false; } break; case 2: // avg if (calc_cnt > 0) { med = ahi_avg / calc_cnt; + skip = false; } break; } QStringList txtlist; - txtlist.append(QObject::tr("%1 %2 / %3 / %4").arg(STR_TR_AHI).arg(min_ahi, 0, 'f', 2).arg(med, 0, 'f', 2).arg(max_ahi, 0, 'f', 2)); + if (!skip) txtlist.append(QObject::tr("%1 %2 / %3 / %4").arg(STR_TR_AHI).arg(min_ahi, 0, 'f', 2).arg(med, 0, 'f', 2).arg(max_ahi, 0, 'f', 2)); int num_channels = calcitems.size(); @@ -1210,25 +1215,29 @@ void gAHIChart::afterDraw(QPainter & /*painter */, gGraph &graph, QRect rect) ChannelID code = calc.code; schema::Channel & chan = schema::channel[code]; float mid = 0; + skip = true; switch (midcalc) { case 0: if (calc.median_data.size() > 0) { mid = median(calc.median_data.begin(), calc.median_data.end()); + skip = false; } break; case 1: if (calc.divisor > 0) { mid = calc.wavg_sum / calc.divisor; + skip = false; } break; case 2: - if (calc.divisor > 0) { - mid = calc.avg_sum / calc.divisor; + if (calc.cnt > 0) { + mid = calc.avg_sum / calc.cnt; + skip = false; } break; } - txtlist.append(QString("%1 %2 / %3 / %4").arg(chan.label()).arg(calc.min, 0, 'f', 2).arg(mid, 0, 'f', 2).arg(calc.max, 0, 'f', 2)); + if (!skip) txtlist.append(QString("%1 %2 / %3 / %4").arg(chan.label()).arg(calc.min, 0, 'f', 2).arg(mid, 0, 'f', 2).arg(calc.max, 0, 'f', 2)); } QString txt = txtlist.join(", "); graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); @@ -1236,7 +1245,7 @@ void gAHIChart::afterDraw(QPainter & /*painter */, gGraph &graph, QRect rect) void gAHIChart::populate(Day *day, int idx) { - QList & slices = cache[idx]; + QVector & slices = cache[idx]; float hours = day->hours(); int num_channels = calcitems.size(); @@ -1254,7 +1263,7 @@ void gAHIChart::populate(Day *day, int idx) } QString gAHIChart::tooltipData(Day *day, int idx) { - QList & slices = cache[idx]; + QVector & slices = cache[idx]; float total = 0; float hour = day->hours(m_machtype); QString txt; @@ -1290,12 +1299,76 @@ gPressureChart::gPressureChart() addCalc(CPAP_IPAP, ST_90P, brighten(schema::channel[CPAP_IPAP].defaultColor(),1.33)); // 12 } +void gPressureChart::afterDraw(QPainter &, gGraph &graph, QRect rect) +{ + int pressure_cnt = calcitems[0].cnt; + int pressuremin_cnt = calcitems[3].cnt; + int epap_cnt = calcitems[5].cnt; + int ipap_cnt = calcitems[6].cnt; + int ipaphi_cnt = calcitems[8].cnt; + int epaplo_cnt = calcitems[7].cnt; + + QStringList presstr; + + float mid = 0; + + if (pressure_cnt > 0) { + mid = calcitems[0].mid(); + presstr.append(QString("%1 %2/%3/%4"). + arg(STR_TR_CPAP). + arg(calcitems[0].min,0,'f',1). + arg(mid, 0, 'f', 1). + arg(calcitems[0].max,0,'f',1)); + } + if (pressuremin_cnt > 0) { + presstr.append(QString("%1 %2/%3/%4/%5"). + arg(STR_TR_APAP). + arg(calcitems[3].min,0,'f',1). + arg(calcitems[1].mid(), 0, 'f', 1). + arg(calcitems[2].mid(),0,'f',1). + arg(calcitems[4].max, 0, 'f', 1)); + + } + if (epap_cnt > 0) { + presstr.append(QString("%1 %2/%3/%4"). + arg(STR_TR_EPAP). + arg(calcitems[5].min,0,'f',1). + arg(calcitems[5].mid(), 0, 'f', 1). + arg(calcitems[5].max, 0, 'f', 1)); + } + if (ipap_cnt > 0) { + presstr.append(QString("%1 %2/%3/%4"). + arg(STR_TR_IPAP). + arg(calcitems[6].min,0,'f',1). + arg(calcitems[6].mid(), 0, 'f', 1). + arg(calcitems[6].max, 0, 'f', 1)); + } + if (epaplo_cnt > 0) { + presstr.append(QString("%1 %2/%3/%4"). + arg(STR_TR_EPAPLo). + arg(calcitems[7].min,0,'f',1). + arg(calcitems[7].mid(), 0, 'f', 1). + arg(calcitems[7].max, 0, 'f', 1)); + } + + if (ipaphi_cnt > 0) { + presstr.append(QString("%1 %2/%3/%4"). + arg(STR_TR_IPAPHi). + arg(calcitems[8].min,0,'f',1). + arg(calcitems[8].mid(), 0, 'f', 1). + arg(calcitems[8].max, 0, 'f', 1)); + } + QString txt = presstr.join(" "); + graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); + +} + void gPressureChart::populate(Day * day, int idx) { float tmp; CPAPMode mode = (CPAPMode)(int)qRound(day->settings_wavg(CPAP_Mode)); - QList & slices = cache[idx]; + QVector & slices = cache[idx]; if (mode == MODE_CPAP) { float pr = day->settings_max(CPAP_Pressure); diff --git a/sleepyhead/Graphs/gSessionTimesChart.h b/sleepyhead/Graphs/gSessionTimesChart.h index 94c2442b..fa41c309 100644 --- a/sleepyhead/Graphs/gSessionTimesChart.h +++ b/sleepyhead/Graphs/gSessionTimesChart.h @@ -58,20 +58,35 @@ struct SummaryCalcItem { SummaryCalcItem(ChannelID code, SummaryType type, QColor color) :code(code), type(type), color(color) { } - - inline void update(float value, float weight) { + float mid() + { + float val = 0; switch (midcalc) { case 0: - median_data.append(value); + if (median_data.size() > 0) + val = median(median_data.begin(), median_data.end()); break; case 1: - wavg_sum += value * weight; - divisor += weight; + if (divisor > 0) + val = wavg_sum / divisor; break; - default: - avg_sum += value; - cnt++; + case 2: + if (cnt > 0) + val = avg_sum / cnt; } + return val; + } + + + inline void update(float value, float weight) { + if (midcalc == 0) { + median_data.append(value); + } + + avg_sum += value; + cnt++; + wavg_sum += value * weight; + divisor += weight; min = qMin(min, value); max = qMax(max, value); } @@ -161,7 +176,7 @@ public: virtual void preCalc(); //! \brief Override to call stuff in main loop - virtual void customCalc(Day *, QList &); + virtual void customCalc(Day *, QVector &); //! \brief Override to call stuff after draw is complete virtual void afterDraw(QPainter &, gGraph &, QRect); @@ -232,8 +247,8 @@ protected: QMap dayindex; QList daylist; - QHash > cache; - QList calcitems; + QHash > cache; + QVector calcitems; int expected_slices; @@ -273,7 +288,7 @@ public: } virtual void preCalc(); - virtual void customCalc(Day *, QList & slices); + virtual void customCalc(Day *, QVector & slices); virtual void afterDraw(QPainter &, gGraph &, QRect); //! \brief Renders the graph to the QPainter object @@ -309,7 +324,7 @@ public: virtual ~gUsageChart() {} virtual void preCalc(); - virtual void customCalc(Day *, QList &); + virtual void customCalc(Day *, QVector &); virtual void afterDraw(QPainter &, gGraph &, QRect); virtual void populate(Day *day, int idx); @@ -343,7 +358,7 @@ public: virtual ~gTTIAChart() {} virtual void preCalc(); - virtual void customCalc(Day *, QList &); + virtual void customCalc(Day *, QVector &); virtual void afterDraw(QPainter &, gGraph &, QRect); virtual void populate(Day *day, int idx); virtual QString tooltipData(Day * day, int); @@ -376,7 +391,7 @@ public: virtual ~gAHIChart() {} virtual void preCalc(); - virtual void customCalc(Day *, QList &); + virtual void customCalc(Day *, QVector &); virtual void afterDraw(QPainter &, gGraph &, QRect); virtual void populate(Day *, int idx); @@ -426,7 +441,18 @@ public: return sc; } -// virtual void afterDraw(QPainter &, gGraph &, QRect); +// virtual void preCalc(); + virtual void customCalc(Day *day, QVector &slices) { + int size = slices.size(); + float hour = day->hours(m_machtype); + for (int i=0; i < size; ++i) { + SummaryChartSlice & slice = slices[i]; + SummaryCalcItem * calc = slices[i].calc; + + calc->update(slice.value, hour); + } + } + virtual void afterDraw(QPainter &, gGraph &, QRect); virtual void populate(Day * day, int idx); diff --git a/sleepyhead/Resources.qrc b/sleepyhead/Resources.qrc index d0189be0..5eb0d2f4 100644 --- a/sleepyhead/Resources.qrc +++ b/sleepyhead/Resources.qrc @@ -51,5 +51,6 @@ icons/intellipap.png icons/pushpin.png icons/eye.png + icons/prs1_60s.png diff --git a/sleepyhead/SleepLib/common.cpp b/sleepyhead/SleepLib/common.cpp index 1b3f1bbb..e58ed01b 100644 --- a/sleepyhead/SleepLib/common.cpp +++ b/sleepyhead/SleepLib/common.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "profiles.h" @@ -507,3 +508,114 @@ void initializeStrings() STR_TR_Avg = QObject::tr("Avg"); // Average STR_TR_WAvg = QObject::tr("W-Avg"); // Weighted Average } + + + + +quint32 CRC32(const char * data, quint32 length) +{ + quint32 crc32 = 0xffffffff; + + for (quint32 idx=0; idx 0; j--) { + if (i & 1) { + i = (i >> 1) ^ 0xedb88320; + } else { + i >>= 1; + } + } + + crc32 = ((crc32) >> 8) ^ i; + } + + return ~crc32; +} + +quint32 crc32buf(const QByteArray& data) +{ + return CRC32(data.constData(), data.size()); +} + +// Gzip function +QByteArray gCompress(const QByteArray& data) +{ + QByteArray compressedData = qCompress(data); + // Strip the first six bytes (a 4-byte length put on by qCompress and a 2-byte zlib header) + // and the last four bytes (a zlib integrity check). + compressedData.remove(0, 6); + compressedData.chop(4); + + QByteArray header; + QDataStream ds1(&header, QIODevice::WriteOnly); + // Prepend a generic 10-byte gzip header (see RFC 1952), + ds1 << quint16(0x1f8b) + << quint16(0x0800) + << quint16(0x0000) + << quint16(0x0000) + << quint16(0x000b); + + // Append a four-byte CRC-32 of the uncompressed data + // Append 4 bytes uncompressed input size modulo 2^32 + QByteArray footer; + QDataStream ds2(&footer, QIODevice::WriteOnly); + ds2.setByteOrder(QDataStream::LittleEndian); + ds2 << crc32buf(data) + << quint32(data.size()); + + return header + compressedData + footer; +} + + +// Pinched from http://stackoverflow.com/questions/2690328/qt-quncompress-gzip-data +QByteArray gUncompress(const QByteArray &data) +{ + if (data.size() <= 4) { + qWarning("gUncompress: Input data is truncated"); + return QByteArray(); + } + + QByteArray result; + + int ret; + z_stream strm; + static const int CHUNK_SIZE = 1024; + char out[CHUNK_SIZE]; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = data.size(); + strm.next_in = (Bytef*)(data.data()); + + ret = inflateInit2(&strm, 15 + 32); // gzip decoding + if (ret != Z_OK) + return QByteArray(); + + // run inflate() + do { + strm.avail_out = CHUNK_SIZE; + strm.next_out = (Bytef*)(out); + + ret = inflate(&strm, Z_NO_FLUSH); + Q_ASSERT(ret != Z_STREAM_ERROR); // state not clobbered + + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; // and fall through + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + return QByteArray(); + } + + result.append(out, CHUNK_SIZE - strm.avail_out); + } while (strm.avail_out == 0); + + // clean up and return + inflateEnd(&strm); + return result; +} diff --git a/sleepyhead/SleepLib/common.h b/sleepyhead/SleepLib/common.h index 71a98c92..d0ed158d 100644 --- a/sleepyhead/SleepLib/common.h +++ b/sleepyhead/SleepLib/common.h @@ -19,6 +19,9 @@ #endif +QByteArray gCompress(const QByteArray& data); +QByteArray gUncompress(const QByteArray &data); + const quint16 filetype_summary = 0; const quint16 filetype_data = 1; const quint16 filetype_sessenabled = 5; diff --git a/sleepyhead/SleepLib/loader_plugins/cms50_loader.cpp b/sleepyhead/SleepLib/loader_plugins/cms50_loader.cpp index eb3f8780..89f03315 100644 --- a/sleepyhead/SleepLib/loader_plugins/cms50_loader.cpp +++ b/sleepyhead/SleepLib/loader_plugins/cms50_loader.cpp @@ -262,6 +262,11 @@ int CMS50Loader::doImportMode() // Either a CMS50D+, has a bad header, or it's really midnight, set a flag anyway for later to help choose the right sync time cms50dplus = (hour == 0) && (minute == 0); + MachineInfo info = newInfo(); + info.model = cms50dplus ? QObject::tr("CMS50D+") : QObject::tr("CMS50E/F"); + info.serial = QString(); + Machine * mach = CreateMachine(info); + qDebug() << QString("Receiving Oximeter transmission %1:%2").arg(hour).arg(minute); // set importing to true or whatever.. diff --git a/sleepyhead/SleepLib/loader_plugins/prs1_loader.cpp b/sleepyhead/SleepLib/loader_plugins/prs1_loader.cpp index 03f64651..bf6c01d6 100644 --- a/sleepyhead/SleepLib/loader_plugins/prs1_loader.cpp +++ b/sleepyhead/SleepLib/loader_plugins/prs1_loader.cpp @@ -121,10 +121,13 @@ struct WaveHeaderList { PRS1Loader::PRS1Loader() { const QString PRS1_ICON = ":/icons/prs1.png"; + const QString PRS1_60_ICON = ":/icons/prs1_60s.png"; QString s = newInfo().series; - m_pixmap_paths[s] = PRS1_ICON; - m_pixmaps[s] = QPixmap(PRS1_ICON); + m_pixmap_paths["System One"] = PRS1_ICON; + m_pixmaps["System One"] = QPixmap(PRS1_ICON); + m_pixmap_paths["System One (60 Series)"] = PRS1_60_ICON; + m_pixmaps["System One (60 Series)"] = QPixmap(PRS1_60_ICON); //genCRCTable(); // find what I did with this.. m_buffer = nullptr; @@ -147,6 +150,14 @@ const QString PR_STR_PSeries = "P-Series"; // Tests path to see if it has (what looks like) a valid PRS1 folder structure bool PRS1Loader::Detect(const QString & path) +{ + QString newpath = checkDir(path); + + return !newpath.isEmpty(); +} + + +QString PRS1Loader::checkDir(const QString & path) { QString newpath = path; @@ -159,33 +170,133 @@ bool PRS1Loader::Detect(const QString & path) QDir dir(newpath); if ((!dir.exists() || !dir.isReadable())) { - return false; + return QString(); } qDebug() << "PRS1Loader::Detect path=" << newpath; QFile lastfile(newpath+"/last.txt"); - if (!lastfile.exists()) { - return false; + + QString machpath; + if (lastfile.exists()) { + if (!lastfile.open(QIODevice::ReadOnly)) { + qDebug() << "PRS1Loader: last.txt exists but I couldn't open it!"; + } else { + QTextStream ts(&lastfile); + QString serial = ts.readLine(64).trimmed(); + lastfile.close(); + + machpath = newpath+"/"+serial; + + if (!QDir(machpath).exists()) { + machpath = QString(); + } + } } - if (!lastfile.open(QIODevice::ReadOnly)) { - qDebug() << "PRS1Loader: last.txt exists but I couldn't open it!"; - return false; + if (machpath.isEmpty()) { + QDir dir(newpath); + QStringList dirs = dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs); + if (dirs.size() > 0) { + machpath = dirs[0]; + } } - QString last = lastfile.readLine(64); - last = last.trimmed(); - lastfile.close(); - QFile f(newpath+"/"+last); - if (!f.exists()) { - qDebug() << "in PRS1Loader::Detect():" << last << "does not exist, despite last.txt saying it does"; + return machpath; +} + +void parseModel(MachineInfo & info, QString modelnum) +{ + info.modelnumber = modelnum; + if (!modelnum.endsWith("P")) { + qDebug() << "Weird PRS1 Model number" << modelnum; + } + modelnum.chop(1); + int country = modelnum[modelnum.length() - 1].digitValue(); + modelnum.chop(1); + int ser = modelnum[modelnum.length() - 1].digitValue(); + modelnum.chop(1); + bool ok; + int typ = modelnum.toInt(&ok); + + switch (typ) { + case 4: // cpap + info.model = QObject::tr("RemStar Plus with C-Flex+"); + break; + case 5: // apap + info.model = QObject::tr("RemStar Auto with A-Flex"); + break; + case 6: // bipap + info.model = QObject::tr("RemStar BiPAP Pro with Bi-Flex"); + break; + case 7: // bipap auto + info.model = QObject::tr("RemStar BiPAP Auto with Bi-Flex"); + break; + case 9: // asv + info.model = QObject::tr("BiPAP autoSV Advanced"); + break; + case 10: // Avaps + info.model = QObject::tr("BiPAP AVAPS"); + break; + default: + info.model = QObject::tr("Unknown Model"); + } + + switch (ser) { + case 5: + info.series = QObject::tr("System One"); + break; + case 6: + info.series = QObject::tr("System One (60 Series)"); + break; + default: + info.series = QObject::tr("unknown"); + break; + + } + switch (country) { + case '0': + break; + case '1': + break; + default: + break; + } +} + +bool PRS1Loader::PeekProperties(MachineInfo & info, QString filename) +{ + QFile f(filename); + if (!f.open(QFile::ReadOnly)) { return false; } - // newpath is a valid path + QTextStream in(&f); + do { + QString line = in.readLine(); + QStringList pair = line.split("="); + + if (pair[0].compare("ModelNumber", Qt::CaseInsensitive) == 0) { + QString modelnum = pair[1]; + parseModel(info, modelnum); + } + } while (!in.atEnd()); return true; } + +MachineInfo PRS1Loader::PeekInfo(const QString & path) +{ + QString newpath = checkDir(path); + if (newpath.isEmpty()) + return MachineInfo(); + + MachineInfo info = newInfo(); + info.serial = newpath.section("/", -1); + PeekProperties(info, newpath+"/properties.txt"); + return info; +} + + int PRS1Loader::Open(QString path) { QString newpath; @@ -216,9 +327,7 @@ int PRS1Loader::Open(QString path) QFileInfo fi = flist.at(i); QString filename = fi.fileName(); - if ((filename[0] == 'P') && (isdigit(filename[1])) && (isdigit(filename[2]))) { - SerialNumbers.push_back(filename); - } else if (isdigit(filename[0]) && isdigit(filename[1])) { + if (fi.isDir() && (filename.size() > 4) && (isdigit(filename[1])) && (isdigit(filename[2]))) { SerialNumbers.push_back(filename); } else if (filename.toLower() == "last.txt") { // last.txt points to the current serial number QString file = fi.canonicalFilePath(); @@ -302,12 +411,12 @@ bool PRS1Loader::ParseProperties(Machine *m, QString filename) if (value == s) { continue; } - if (key.toLower() == "serialnumber") { + if (key.contains("serialnumber",Qt::CaseInsensitive)) { info.serial = value; - } else if (key.toLower() == "modelnumber") { - info.modelnumber = value; + } else if (key.contains("modelnumber",Qt::CaseInsensitive)) { + parseModel(info, value); } else { - if (key.toLower() == "producttype") { + if (key.contains("producttype", Qt::CaseInsensitive)) { int i = value.toInt(&ok, 16); if (ok) { @@ -344,11 +453,6 @@ int PRS1Loader::OpenMachine(Machine *m, QString path) if (!dir.exists() || (!dir.isReadable())) { return 0; } - QString backupPath = m->getBackupPath() + path.section("/", -2); - - if (QDir::cleanPath(path).compare(QDir::cleanPath(backupPath)) != 0) { - copyPath(path, backupPath); - } dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); @@ -360,21 +464,35 @@ int PRS1Loader::OpenMachine(Machine *m, QString path) QStringList paths; + int sessionid_base = 10; + for (int i = 0; i < flist.size(); i++) { QFileInfo fi = flist.at(i); filename = fi.fileName(); - if ((filename[0].toLower() == 'p') && (isdigit(filename[1]))) { - // p0, p1, p2.. etc.. folders contain the session data - paths.push_back(fi.canonicalFilePath()); - } else if ((filename.toLower() == "properties.txt") || (filename.toLower() == "prop.txt")) { + if (fi.isDir()) { + if ((filename[0].toLower() == 'p') && (isdigit(filename[1]))) { + // p0, p1, p2.. etc.. folders contain the session data + paths.push_back(fi.canonicalFilePath()); + } else if (filename.toLower() == "e") { + // Error files.. + // Reminder: I have been given some info about these. should check it over. + } + } else if (filename.compare("properties.txt",Qt::CaseInsensitive) == 0) { + ParseProperties(m, fi.canonicalFilePath()); + } else if (filename.compare("PROP.TXT",Qt::CaseInsensitive) == 0) { + sessionid_base = 16; ParseProperties(m, fi.canonicalFilePath()); - } else if (filename.toLower() == "e") { - // Error files.. - // Reminder: I have been given some info about these. should check it over. } } + QString backupPath = m->getBackupPath() + path.section("/", -2); + + if (QDir::cleanPath(path).compare(QDir::cleanPath(backupPath)) != 0) { + copyPath(path, backupPath); + } + + QString modelstr = m->modelnumber(); if (modelstr.endsWith("P")) @@ -430,7 +548,7 @@ int PRS1Loader::OpenMachine(Machine *m, QString path) } QString session_s = fi.fileName().section(".", 0, -2); - sid = session_s.toInt(&ok); + sid = session_s.toInt(&ok, sessionid_base); if (!ok) { // not a numerical session ID continue; @@ -441,7 +559,7 @@ int PRS1Loader::OpenMachine(Machine *m, QString path) continue; } - if (ext == 5) { + if ((ext == 5) || (ext == 6)) { // Waveform files aren't grouped... so we just want to add the filename for later QHash::iterator it = sesstasks.find(sid); if (it != sesstasks.end()) { @@ -453,8 +571,13 @@ int PRS1Loader::OpenMachine(Machine *m, QString path) queTask(task); } - if (!task->waveform.isEmpty()) continue; - task->waveform = fi.canonicalFilePath(); + if (ext == 5) { + if (!task->wavefile.isEmpty()) continue; + task->wavefile = fi.canonicalFilePath(); + } else if (ext == 6) { + if (!task->oxifile.isEmpty()) continue; + task->oxifile = fi.canonicalFilePath(); + } continue; } @@ -464,6 +587,16 @@ int PRS1Loader::OpenMachine(Machine *m, QString path) for (int i=0; i < Chunks.size(); ++i) { PRS1DataChunk * chunk = Chunks.at(i); + + if (ext <= 1) { + const unsigned char * data = (unsigned char *)chunk->m_data.constData(); + + if (data[0x00] != 0) { + delete chunk; + continue; + } + } + sid = chunk->sessionid; task = nullptr; @@ -500,7 +633,8 @@ int PRS1Loader::OpenMachine(Machine *m, QString path) int tasks = countTasks(); runTasks(p_profile->session->multithreading()); finishAddingSessions(); - return tasks; + + return m->unsupported() ? -1 : tasks; } bool PRS1Import::ParseF5Events() @@ -1138,7 +1272,7 @@ bool PRS1Import::ParseCompliance() session->settings[CPAP_Mode] = (int)MODE_CPAP; EventDataType min_pressure = float(data[0x03]) / 10.0; - EventDataType max_pressure = float(data[0x04]) / 10.0; + // EventDataType max_pressure = float(data[0x04]) / 10.0; session->settings[CPAP_Pressure] = min_pressure; @@ -1638,7 +1772,6 @@ bool PRS1Import::ParseSummary() session->setPhysMax(CPAP_PS, 25); session->setPhysMin(CPAP_PS, 0); - switch (summary->family) { case 0: if (summary->familyVersion == 4) { @@ -1647,7 +1780,8 @@ bool PRS1Import::ParseSummary() return ParseSummaryF0(); } case 3: - return ParseSummaryF3(); + // return ParseSummaryF3(); + break; case 5: if (summary->familyVersion == 0) { return ParseSummaryF5V0(); @@ -1656,7 +1790,12 @@ bool PRS1Import::ParseSummary() } } - // Else machine is unsupported + this->loader->saveMutex.lock(); + if (!mach->unsupported()) { + this->loader->unsupported(mach); + } + this->loader->saveMutex.unlock(); + return false; const unsigned char * data = (unsigned char *)summary->m_data.constData(); @@ -1865,6 +2004,50 @@ bool PRS1Import::ParseEvents() return res; } +bool PRS1Import::ParseOximetery() +{ + int size = oximetery.size(); + + for (int i=0; i < size; ++i) { + PRS1DataChunk * oxi = oximetery.at(i); + int num = oxi->waveformInfo.size(); + + int size = oxi->m_data.size(); + if (size == 0) { + continue; + } + quint64 ti = quint64(oxi->timestamp) * 1000L; + qint64 dur = qint64(oxi->duration) * 1000L; + + if (num > 1) { + // Process interleaved samples + QVector data; + data.resize(num); + + int pos = 0; + do { + for (int n=0; n < num; n++) { + int interleave = oxi->waveformInfo.at(n).interleave; + data[n].append(oxi->m_data.mid(pos, interleave)); + pos += interleave; + } + } while (pos < size); + + if (data[0].size() > 0) { + EventList * pulse = session->AddEventList(OXI_Pulse, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, dur / data[0].size()); + pulse->AddWaveform(ti, (unsigned char *)data[0].data(), data[0].size(), dur); + } + + if (data[1].size() > 0) { + EventList * spo2 = session->AddEventList(OXI_SPO2, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, dur / data[1].size()); + spo2->AddWaveform(ti, (unsigned char *)data[1].data(), data[1].size(), dur); + } + + } + } + return true; +} + bool PRS1Import::ParseWaveforms() { int size = waveforms.size(); @@ -1915,14 +2098,19 @@ bool PRS1Import::ParseWaveforms() void PRS1Import::run() { + if (mach->unsupported()) + return; session = new Session(mach, sessionid); if ((compliance && ParseCompliance()) || (summary && ParseSummary())) { if (event && !ParseEvents()) { } - waveforms = loader->ParseFile(waveform); + waveforms = loader->ParseFile(wavefile); ParseWaveforms(); + oximetery = loader->ParseFile(oxifile); + ParseOximetery(); + if (session->first() > 0) { if (session->last() < session->first()) { // if last isn't set, duration couldn't be gained from summary, parsing events or waveforms.. @@ -2012,7 +2200,7 @@ QList PRS1Loader::ParseFile(QString path) && (lastchunk->htype != chunk->htype)) { QByteArray junk = f.read(lastblocksize - 16); - + Q_UNUSED(junk) if (lastchunk->ext == 5) { // The data is random crap // lastchunk->m_data.append(junk.mid(lastheadersize-16)); @@ -2032,7 +2220,7 @@ QList PRS1Loader::ParseFile(QString path) ////////////////////////////////////////////////////////// // Waveform Header ////////////////////////////////////////////////////////// - if (chunk->ext == 5) { + if ((chunk->ext == 5) || (chunk->ext == 6)) { // Get extra 8 bytes in waveform header. QByteArray extra = f.read(4); if (extra.size() != 4) { @@ -2107,7 +2295,7 @@ QList PRS1Loader::ParseFile(QString path) } #endif - if (chunk->ext == 5) { + if ((chunk->ext == 5) || (chunk->ext == 6)){ if (lastchunk != nullptr) { Q_ASSERT(lastchunk->sessionid == chunk->sessionid); diff --git a/sleepyhead/SleepLib/loader_plugins/prs1_loader.h b/sleepyhead/SleepLib/loader_plugins/prs1_loader.h index 3571433e..e1418f9f 100644 --- a/sleepyhead/SleepLib/loader_plugins/prs1_loader.h +++ b/sleepyhead/SleepLib/loader_plugins/prs1_loader.h @@ -109,13 +109,17 @@ public: PRS1DataChunk * summary; PRS1DataChunk * event; QList waveforms; + QList oximetery; - QString waveform; + QString wavefile; + QString oxifile; bool ParseCompliance(); bool ParseSummary(); bool ParseEvents(); bool ParseWaveforms(); + bool ParseOximetery(); + bool ParseSummaryF0(); bool ParseSummaryF0V4(); @@ -148,9 +152,15 @@ class PRS1Loader : public CPAPLoader PRS1Loader(); virtual ~PRS1Loader(); + QString checkDir(const QString & path); + bool PeekProperties(MachineInfo & info, QString path); + + //! \brief Detect if the given path contains a valid Folder structure virtual bool Detect(const QString & path); + virtual MachineInfo PeekInfo(const QString & path); + //! \brief Scans directory path for valid PRS1 signature virtual int Open(QString path); diff --git a/sleepyhead/SleepLib/machine.cpp b/sleepyhead/SleepLib/machine.cpp index d4adb7f2..12159020 100644 --- a/sleepyhead/SleepLib/machine.cpp +++ b/sleepyhead/SleepLib/machine.cpp @@ -43,6 +43,7 @@ Machine::Machine(MachineID id) { day.clear(); highest_sessionid = 0; + m_unsupported = false; if (!id) { srand(time(nullptr)); @@ -79,8 +80,15 @@ const quint16 sessinfo_version = 1; bool Machine::saveSessionInfo() { - QFile file(getDataPath() + "Sessions.info"); - file.open(QFile::WriteOnly); + if (info.type == MT_JOURNAL) return false; + + qDebug() << "Saving" << info.brand << "session info" << info.loadername; + QString filename = getDataPath() + "Sessions.info"; + QFile file(filename); + if (!file.open(QFile::WriteOnly)) { + qDebug() << "Couldn't open" << filename << "for writing"; + return false; + } QDataStream out(&file); out.setByteOrder(QDataStream::LittleEndian); @@ -99,12 +107,19 @@ bool Machine::saveSessionInfo() Session * sess = s.value(); out << (quint32) sess->session(); out << (bool)(sess->enabled()); + + //out << sess->m_availableChannels; } + qDebug() << "Done Saving" << info.brand << "session info"; + return true; } bool Machine::loadSessionInfo() { + if (info.type == MT_JOURNAL) + return true; + QHash::iterator s; QFile file(getDataPath() + "Sessions.info"); if (!file.open(QFile::ReadOnly)) { @@ -146,12 +161,20 @@ bool Machine::loadSessionInfo() for (int i=0; i< size; ++i) { in >> sid; in >> b; +// QList avail_channels; +// if (version >= 2) { +// in >> avail_channels; +// } + s = sessionlist.find(sid); if (s != sessionlist.end()) { Session * sess = s.value(); sess->setEnabled(b); +// if (version >= 2) { +// sess->m_availableChannels = avail_channels; +// } } } return true; @@ -846,6 +869,7 @@ bool Machine::hasModifiedSessions() } const QString summaryFileName = "Summaries.xml"; +const int summaryxml_version=1; bool Machine::LoadSummary(QProgressBar * progress) { @@ -853,7 +877,7 @@ bool Machine::LoadSummary(QProgressBar * progress) time.start(); qDebug() << "Loading Summaries"; - QString filename = getDataPath() + summaryFileName; + QString filename = getDataPath() + summaryFileName + ".gz"; QDomDocument doc; QFile file(filename); @@ -864,9 +888,9 @@ bool Machine::LoadSummary(QProgressBar * progress) return false; } - QByteArray data=file.readAll(); + QByteArray data = file.readAll(); - QByteArray uncompressed = qUncompress(data); + QByteArray uncompressed = gUncompress(data); QString errorMsg; int errorLine; @@ -879,8 +903,18 @@ bool Machine::LoadSummary(QProgressBar * progress) file.close(); - QDomElement root = doc.documentElement(); + + if (root.tagName().compare("sessions", Qt::CaseInsensitive) != 0) { + qDebug() << "Summaries cache messed up, recreating..."; + return false; + } + bool ok; + int version = root.attribute("version", "").toInt(&ok); + if (!ok || (version != summaryxml_version)) { + qDebug() << "Summaries cache outdated, recreating..."; + return false; + } QDomNode node; bool s_ok; @@ -906,6 +940,37 @@ bool Machine::LoadSummary(QProgressBar * progress) sess->setEnabled(enabled); sess->setSummaryOnly(!events); + if (e.hasChildNodes()) { + QList available_channels; + QList available_settings; + + QDomElement chans = e.firstChildElement("channels"); + if (chans.isElement()) { + QDomNode node = chans.firstChild(); + QString txt = node.nodeValue(); + QStringList channels = txt.split(","); + for (int i=0; im_availableChannels = available_channels; + + QDomElement sete = e.firstChildElement("settings"); + if (sete.isElement()) { + QString sets = sete.firstChild().nodeValue(); + QStringList settings = sets.split(","); + for (int i=0; im_availableSettings = available_settings; + } + + sess_order[first] = sess; } } @@ -935,11 +1000,9 @@ bool Machine::LoadSummary(QProgressBar * progress) return true; } -const int summaryxml_version=0; - bool Machine::SaveSummary() { - qDebug() << "Saving Summaries"; + qDebug() << "Saving" << info.brand << info.model << "Summaries"; QString filename = getDataPath() + summaryFileName; QDomDocument doc("SleepyHeadSessionIndex"); @@ -967,18 +1030,49 @@ bool Machine::SaveSummary() el.setAttribute("last", sess->realLast()); el.setAttribute("enabled", sess->enabled() ? "1" : "0"); el.setAttribute("events", sess->summaryOnly() ? "0" : "1"); + + QHash >::iterator ev; + QHash >::iterator ev_end = sess->eventlist.end(); + QStringList chanlist; + for (ev = sess->eventlist.begin(); ev != ev_end; ++ev) { + chanlist.append(QString::number(ev.key(), 16)); + } + if (chanlist.size() == 0) { + for (int i=0; im_availableChannels.size(); i++) { + ChannelID code = sess->m_availableChannels.at(i); + chanlist.append(QString::number(code, 16)); + } + } + + QDomElement chans = doc.createElement("channels"); + chans.appendChild(doc.createTextNode(chanlist.join(","))); + el.appendChild(chans); + + chanlist.clear(); + QHash::iterator si; + QHash::iterator set_end = sess->settings.end(); + for (si = sess->settings.begin(); si != set_end; ++si) { + chanlist.append(QString::number(si.key(), 16)); + } + QDomElement settings = doc.createElement("settings"); + settings.appendChild(doc.createTextNode(chanlist.join(","))); + el.appendChild(settings); + root.appendChild(el); if (sess->IsChanged()) sess->StoreSummary(); } - QFile file(filename); - file.open(QIODevice::WriteOnly); + QString xmltext; + QTextStream ts(&xmltext); + doc.save(ts, 1); - QByteArray ba = qCompress(doc.toByteArray()); - file.write(ba); + QByteArray data = gCompress(xmltext.toUtf8()); - file.close(); + QFile file(filename + ".gz"); + + file.open(QFile::WriteOnly); + file.write(data); return true; } diff --git a/sleepyhead/SleepLib/machine.h b/sleepyhead/SleepLib/machine.h index c0f0d568..5ba7dc49 100644 --- a/sleepyhead/SleepLib/machine.h +++ b/sleepyhead/SleepLib/machine.h @@ -173,6 +173,11 @@ class Machine QMutex listMutex; QSemaphore *savelistSem; + bool m_unsupported; + + bool unsupported() { return m_unsupported; } + void setUnsupported(bool b) { m_unsupported = b; } + void lockSaveMutex() { listMutex.lock(); } void unlockSaveMutex() { listMutex.unlock(); } void skipSaveTask() { lockSaveMutex(); m_donetasks++; unlockSaveMutex(); } diff --git a/sleepyhead/SleepLib/machine_loader.h b/sleepyhead/SleepLib/machine_loader.h index 7d5199d2..2ab2b126 100644 --- a/sleepyhead/SleepLib/machine_loader.h +++ b/sleepyhead/SleepLib/machine_loader.h @@ -59,6 +59,12 @@ class MachineLoader: public QObject virtual const QString &loaderName() = 0; inline MachineType type() { return m_type; } + void unsupported(Machine * m) { + Q_ASSERT(m != nullptr); + m->setUnsupported(true); + emit machineUnsupported(m); + } + void queTask(ImportTask * task); void addSession(Session * sess) @@ -104,8 +110,9 @@ class MachineLoader: public QObject signals: void updateProgress(int cnt, int total); + void machineUnsupported(Machine *); - protected: +protected: //! \brief Contains a list of Machine records known by this loader QList m_machlist; diff --git a/sleepyhead/SleepLib/schema.cpp b/sleepyhead/SleepLib/schema.cpp index ab317d35..a554347a 100644 --- a/sleepyhead/SleepLib/schema.cpp +++ b/sleepyhead/SleepLib/schema.cpp @@ -860,6 +860,7 @@ void ChannelList::add(QString group, Channel *chan) bool ChannelList::Save(QString filename) { + qDebug() << "Saving Channels.xml"; if (filename.isEmpty()) { filename = p_profile->Get("{DataFolder}/") + "channels.xml"; } @@ -924,6 +925,7 @@ bool ChannelList::Save(QString filename) ts << doc.toString(); file.close(); + return true; } diff --git a/sleepyhead/SleepLib/session.cpp b/sleepyhead/SleepLib/session.cpp index 2502b793..0242a893 100644 --- a/sleepyhead/SleepLib/session.cpp +++ b/sleepyhead/SleepLib/session.cpp @@ -23,7 +23,7 @@ using namespace std; // This is the uber important database version for SleepyHeads internal storage // Increment this after stuffing with Session's save & load code. -const quint16 summary_version = 16; +const quint16 summary_version = 17; const quint16 events_version = 10; Session::Session(Machine *m, SessionID session) @@ -164,7 +164,7 @@ bool Session::Store(QString path) return a; } -QDataStream & operator<<(QDataStream & out, const Session & session) +/*QDataStream & operator<<(QDataStream & out, const Session & session) { session.StoreSummaryData(out); return out; @@ -201,6 +201,7 @@ void Session::StoreSummaryData(QDataStream & out) const out << m_gain; out << m_availableChannels; + out << m_timeAboveTheshold; out << m_upperThreshold; out << m_timeBelowTheshold; @@ -259,7 +260,7 @@ void Session::LoadSummaryData(QDataStream & in) in >> s_summaryOnly; s_enabled = 1; -} +} */ QDataStream & operator>>(QDataStream & in, SessionSlice & slice) { @@ -593,7 +594,14 @@ bool Session::LoadSummary() in >> s_summaryOnly; } - if (version >= 16) { + if (version == 16) { + QList slices; + in >> slices; + m_slices.clear(); + for (int i=0;i= 17) { in >> m_slices; } } diff --git a/sleepyhead/SleepLib/session.h b/sleepyhead/SleepLib/session.h index e1d24c1b..a072a5a0 100644 --- a/sleepyhead/SleepLib/session.h +++ b/sleepyhead/SleepLib/session.h @@ -73,16 +73,16 @@ class Session //! \brief Writes the Sessions Summary Indexes to filename, in SleepLibs custom data format. bool StoreSummary(); - //! \brief Save the Sessions Summary Indexes to the stream - void StoreSummaryData(QDataStream & out) const; +// //! \brief Save the Sessions Summary Indexes to the stream +// void StoreSummaryData(QDataStream & out) const; //! \brief Writes the Sessions EventLists to filename, in SleepLibs custom data format. bool StoreEvents(); //bool Load(QString path); - //! \brief Loads the Sessions Summary Indexes from stream - void LoadSummaryData(QDataStream & in); +// //! \brief Loads the Sessions Summary Indexes from stream +// void LoadSummaryData(QDataStream & in); //! \brief Loads the Sessions Summary Indexes from filename, from SleepLibs custom data format. bool LoadSummary(); @@ -238,8 +238,9 @@ class Session QHash m_timeAboveTheshold; QList m_availableChannels; + QList m_availableSettings; - QList m_slices; + QVector m_slices; //! \brief Generates sum and time data for each distinct value in 'code' events.. void updateCountSummary(ChannelID code); diff --git a/sleepyhead/daily.cpp b/sleepyhead/daily.cpp index 2152274c..b0e18534 100644 --- a/sleepyhead/daily.cpp +++ b/sleepyhead/daily.cpp @@ -184,10 +184,10 @@ Daily::Daily(QWidget *parent,gGraphView * shared) graphlist[schema::channel[code].code()] = new gGraph(schema::channel[code].code(), GraphView, schema::channel[code].label(), channelInfo(code), default_height); } - int oxigrp=p_profile->ExistsAndTrue("SyncOximetry") ? 0 : 1; // Contemplating killing this setting... + //int oxigrp=p_profile->ExistsAndTrue("SyncOximetry") ? 0 : 1; // Contemplating killing this setting... for (int i=0; i < oxisize; ++i) { ChannelID code = oxicodes[i]; - graphlist[schema::channel[code].code()] = new gGraph(schema::channel[code].code(), GraphView, schema::channel[code].label(), channelInfo(code), default_height, oxigrp); + graphlist[schema::channel[code].code()] = new gGraph(schema::channel[code].code(), GraphView, schema::channel[code].label(), channelInfo(code), default_height); } if (p_profile->general->calculateRDI()) { @@ -1123,8 +1123,8 @@ QString Daily::getCPAPInformation(Day * day) html="\n"; - html+="
"+info.series+" "+info.model+""; - QString tooltip=(info.brand+"\n"+info.series+" "+info.modelnumber+"\n"+info.serial); + html+="
"+info.brand + " "+ info.series+"
"+info.model+""; + QString tooltip=("Model "+info.modelnumber+" - "+info.serial); tooltip=tooltip.replace(" "," "); html+=tooltip; diff --git a/sleepyhead/icons/prs1_60s.png b/sleepyhead/icons/prs1_60s.png new file mode 100644 index 00000000..88274aee Binary files /dev/null and b/sleepyhead/icons/prs1_60s.png differ diff --git a/sleepyhead/mainwindow.cpp b/sleepyhead/mainwindow.cpp index e6687272..c835ee8f 100644 --- a/sleepyhead/mainwindow.cpp +++ b/sleepyhead/mainwindow.cpp @@ -634,6 +634,11 @@ void MainWindow::Startup() // profile is a global variable set in main after login p_profile->LoadMachineData(); + QList loaders = GetLoaders(); + for (int i=0; igraphView()); @@ -860,10 +865,16 @@ QList MainWindow::detectCPAPCards() progress.setVisible(true); popup.show(); QApplication::processEvents(); + QString lastpath = (*p_profile)[STR_PREF_LastCPAPPath].toString(); do { // Rescan in case card inserted QStringList AutoScannerPaths = getDriveList(); + //AutoScannerPaths.push_back(lastpath); + + if (!AutoScannerPaths.contains(lastpath)) { + AutoScannerPaths.append(lastpath); + } Q_FOREACH(const QString &path, AutoScannerPaths) { // Scan through available machine loaders and test if this folder contains valid folder structure @@ -883,6 +894,7 @@ QList MainWindow::detectCPAPCards() // needs a slight delay here QThread::msleep(200); } while (detectedCards.size() == 0); + popup.hide(); popup.disconnect(&skipbtn, SIGNAL(clicked()), &popup, SLOT(hide())); @@ -934,7 +946,7 @@ void MainWindow::on_action_Import_Data_triggered() mbox.setDefaultButton(QMessageBox::Yes); mbox.setButtonText(QMessageBox::No, tr("Specify")); - QPixmap pixmap = datacards[0].loader->getPixmap(datacards[0].loader->PeekInfo(datacards[0].path).series); + QPixmap pixmap = datacards[0].loader->getPixmap(datacards[0].loader->PeekInfo(datacards[0].path).series).scaled(64,64); //QPixmap pixmap = QPixmap(getCPAPPixmap(datacards[0].loader->loaderName())).scaled(64,64); mbox.setIconPixmap(pixmap); @@ -1051,7 +1063,7 @@ void MainWindow::on_action_Import_Data_triggered() if (c >= 0) { // goodlocations.push_back(dir); - QDir d(dir.section("/",0,-2)); + QDir d(dir.section("/",0,-1)); (*p_profile)[STR_PREF_LastCPAPPath] = d.absolutePath(); } @@ -2308,6 +2320,12 @@ void MainWindow::FreeSessions() } while (date >= first); } +void MainWindow::MachineUnsupported(Machine * m) +{ + Q_ASSERT(m != nullptr); + QMessageBox::information(this, STR_MessageBox_Error, QObject::tr("Sorry, your %1 %2 machine is not currently supported.").arg(m->brand()).arg(m->model()), QMessageBox::Ok); +} + void MainWindow::doReprocessEvents() { if (p_profile->countDays(MT_CPAP, p_profile->FirstDay(), p_profile->LastDay()) == 0) { diff --git a/sleepyhead/mainwindow.h b/sleepyhead/mainwindow.h index 7d073476..01e6a70a 100644 --- a/sleepyhead/mainwindow.h +++ b/sleepyhead/mainwindow.h @@ -92,6 +92,7 @@ class MainWindow : public QMainWindow //! \brief Start the automatic update checker process void CheckForUpdates(); + /*! \fn Notify(QString s,int ms=5000, QString title="SleepyHead v"+VersionString()); \brief Pops up a message box near the system tray \param QString string @@ -144,6 +145,9 @@ class MainWindow : public QMainWindow //! \brief Recalculate all event summaries and flags void doReprocessEvents(); + void MachineUnsupported(Machine * m); + + protected: virtual void closeEvent(QCloseEvent *); virtual void keyPressEvent(QKeyEvent *event); diff --git a/sleepyhead/mainwindow.ui b/sleepyhead/mainwindow.ui index c4366c11..e0b17f98 100644 --- a/sleepyhead/mainwindow.ui +++ b/sleepyhead/mainwindow.ui @@ -3132,12 +3132,20 @@ border-radius: 10px; &File + + + Exp&ort Data + + + + + - + @@ -3366,11 +3374,6 @@ border-radius: 10px; &Edit Profile - - - Exp&ort Data - - Online Users &Guide @@ -3521,6 +3524,16 @@ border-radius: 10px; Show Performance Information + + + Export as CSV + + + + + Export for Review + + diff --git a/sleepyhead/oximeterimport.cpp b/sleepyhead/oximeterimport.cpp index a91ad724..da00760c 100644 --- a/sleepyhead/oximeterimport.cpp +++ b/sleepyhead/oximeterimport.cpp @@ -993,6 +993,7 @@ void OximeterImport::on_saveButton_clicked() mach->AddSession(session); mach->Save(); mach->SaveSummary(); + p_profile->StoreMachines(); mainwin->getDaily()->LoadDate(mainwin->getDaily()->getDate()); mainwin->getOverview()->ReloadGraphs(); diff --git a/sleepyhead/sessionbar.cpp b/sleepyhead/sessionbar.cpp index bc0a5e7c..d71880c7 100644 --- a/sleepyhead/sessionbar.cpp +++ b/sleepyhead/sessionbar.cpp @@ -48,7 +48,7 @@ SessionBar::SessionBar(QWidget *parent) : // :QWidget(this) //{ // timer.setParent(this); -// QList::const_iterator i; +// QVector::const_iterator i; // for (i=copy.segments.begin();i!=copy.segments.end();++i) { // segments.push_back(*i); // } @@ -60,7 +60,7 @@ SessionBar::~SessionBar() void SessionBar::updateTimer() { if (!underMouse()) { - QList::iterator i; + QVector::iterator i; for (i = segments.begin(); i != segments.end(); ++i) { (*i).highlight = false; @@ -78,7 +78,7 @@ SegType SessionBar::min() return 0; } - QList::iterator i = segments.begin(); + QVector::iterator i = segments.begin(); SegType min = (*i).session->first(); i++; @@ -101,7 +101,7 @@ SegType SessionBar::max() return 0; } - QList::iterator i = segments.begin(); + QVector::iterator i = segments.begin(); SegType max = (*i).session->last(); i++; qint64 val; @@ -132,7 +132,7 @@ void SessionBar::mousePressEvent(QMouseEvent *ev) double px = double(width() ) / double(total); double sx, ex; - QList::iterator i; + QVector::iterator i; int cnt = 0; @@ -174,7 +174,7 @@ void SessionBar::mouseMoveEvent(QMouseEvent *ev) double px = double(width() - 5) / double(total); double sx, ex; - QList::iterator i; + QVector::iterator i; for (i = segments.begin(); i != segments.end(); ++i) { SBSeg &seg = *i; @@ -216,7 +216,7 @@ void SessionBar::paintEvent(QPaintEvent *) double px = double(width() - 5) / double(total); double sx, ex; - QList::iterator i; + QVector::iterator i; QRect selectRect; int cnt = 0; diff --git a/sleepyhead/sessionbar.h b/sleepyhead/sessionbar.h index 67f85aeb..96297960 100644 --- a/sleepyhead/sessionbar.h +++ b/sleepyhead/sessionbar.h @@ -9,7 +9,7 @@ #ifndef SESSIONBAR_H #define SESSIONBAR_H -#include +#include #include #include #include @@ -60,7 +60,7 @@ class SessionBar : public QWidget SegType min(); SegType max(); - QList segments; + QVector segments; QTimer timer; int m_selectIDX; bool m_selectMode; diff --git a/sleepyhead/statistics.cpp b/sleepyhead/statistics.cpp index aa163003..a399dae9 100644 --- a/sleepyhead/statistics.cpp +++ b/sleepyhead/statistics.cpp @@ -76,6 +76,7 @@ QDataStream & operator<<(QDataStream & out, const RXItem & rx) out << rx.machine->loaderName(); out << rx.machine->serial(); + out << rx.relief; out << rx.mode; out << rx.pressure;