diff --git a/sleepyhead/Graphs/gGraph.cpp b/sleepyhead/Graphs/gGraph.cpp index 0c3f0e34..3dbc9716 100644 --- a/sleepyhead/Graphs/gGraph.cpp +++ b/sleepyhead/Graphs/gGraph.cpp @@ -375,7 +375,7 @@ void gGraph::paint(QPainter &painter, const QRegion ®ion) if (ll->position() == LayerLeft) { QRect rect(originX + left, originY + top, tmp, height - top - bottom); ll->m_rect = rect; - ll->paint(painter, *this, QRegion(rect)); + // ll->paint(painter, *this, QRegion(rect)); left += tmp; #ifdef DEBUG_LAYOUT QColor col = Qt::red; @@ -388,7 +388,7 @@ void gGraph::paint(QPainter &painter, const QRegion ®ion) right += tmp; QRect rect(originX + width - right, originY + top, tmp, height - top - bottom); ll->m_rect = rect; - ll->paint(painter, *this, QRegion(rect)); + //ll->paint(painter, *this, QRegion(rect)); #ifdef DEBUG_LAYOUT QColor col = Qt::red; painter.setPen(col); @@ -439,6 +439,16 @@ void gGraph::paint(QPainter &painter, const QRegion ®ion) } } + // Draw anything like the YAxis labels afterwards, in case the graph scale was updated during draw + for (int i = 0; i < m_layers.size(); i++) { + Layer *ll = m_layers[i]; + + if (!ll->visible()) { continue; } + if ((ll->position() == LayerLeft) || (ll->position() == LayerRight)) { + ll->paint(painter, *this, QRegion(ll->m_rect)); + } + } + if (m_selection.width() > 0 && m_selecting_area) { QColor col(128, 128, 255, 128); painter.fillRect(originX + m_selection.x(), originY + top, m_selection.width(), height - bottom - top,QBrush(col)); @@ -448,7 +458,7 @@ void gGraph::paint(QPainter &painter, const QRegion ®ion) // originX + m_selection.x(), originY + height - bottom, col.rgba()); } - if (isPinned()) { + if (isPinned() && !printing()) { painter.drawPixmap(-5, originY-10, m_graphview->pin_icon); } diff --git a/sleepyhead/Graphs/gGraphView.cpp b/sleepyhead/Graphs/gGraphView.cpp index 8a62be94..d3a680f1 100644 --- a/sleepyhead/Graphs/gGraphView.cpp +++ b/sleepyhead/Graphs/gGraphView.cpp @@ -1328,7 +1328,7 @@ void gGraphView::paintGL() static int rp = 0; // Show FPS and draw time - if (m_showsplitter && p_profile->general->showDebug()) { + if (m_showsplitter && p_profile->general->showPerformance()) { QString ss; qint64 ela = time.nsecsElapsed(); double ms = double(ela) / 1000000.0; diff --git a/sleepyhead/Graphs/gSessionTimesChart.cpp b/sleepyhead/Graphs/gSessionTimesChart.cpp index a308da50..3677c3aa 100644 --- a/sleepyhead/Graphs/gSessionTimesChart.cpp +++ b/sleepyhead/Graphs/gSessionTimesChart.cpp @@ -42,10 +42,10 @@ gSummaryChart::gSummaryChart(ChannelID code, MachineType machtype) tz_hours = tz_offset / 3600.0; expected_slices = 5; - addCalc(code, ST_MIN); - addCalc(code, ST_MID); - addCalc(code, ST_90P); - addCalc(code, ST_MAX); + addCalc(code, ST_MIN, brighten(schema::channel[code].defaultColor() ,0.90)); + addCalc(code, ST_MID, brighten(schema::channel[code].defaultColor() ,1.30)); + addCalc(code, ST_90P, brighten(schema::channel[code].defaultColor() ,1.50)); + addCalc(code, ST_MAX, brighten(schema::channel[code].defaultColor() ,1.80)); } gSummaryChart::~gSummaryChart() @@ -150,6 +150,146 @@ bool gSummaryChart::mouseReleaseEvent(QMouseEvent *event, gGraph *graph) QMap gSummaryChart::dayindex; QList gSummaryChart::daylist; +void gSummaryChart::preCalc() +{ + for (int i=0; i & slices) +{ + if (slices.size() != calcitems.size()) { + return; + } + float hour = day->hours(m_machtype); + for (int i=0; igeneral->prefCalcMiddle(); + QString midstr; + if (mid == 0) { + midstr = QObject::tr("Med."); + } else if (mid == 1) { + midstr = QObject::tr("Avg"); + } else { + midstr = QObject::tr("W-Avg"); + } + float perc = p_profile->general->prefCalcPercentile(); + QString percstr = QObject::tr("%1%").arg(perc, 0, 'f',0); + + schema::Channel & chan = schema::channel[calcitems.at(0).code]; + + for (int i=0; i= 99998) continue; + txt = QObject::tr("Min: "); + break; + case ST_MAX: + val = calc.max; + if (val <= -99998) continue; + txt = QObject::tr("Max: "); + break; + case ST_SETMIN: + val = calc.min; + if (val >= 99998) continue; + txt = QObject::tr("Min: "); + break; + case ST_SETMAX: + val = calc.max; + if (val <= -99998) continue; + txt = QObject::tr("Max: "); + break; + case ST_MID: + val = calc.sum / calc.divisor; + txt = QObject::tr("%1: ").arg(midstr); + break; + case ST_90P: + val = calc.sum / calc.divisor; + txt = QObject::tr("%1: ").arg(percstr); + break; + default: + val = calc.sum / calc.divisor; + txt = QObject::tr("???: "); + break; + } + strlist.append(QString("%1%2").arg(txt).arg(val,0,'f',2)); + if (calcitems.size() == 1) { + val = calc.max; + if (val > -99998) + strlist.append(QObject::tr("Max: %1").arg(val,0,'f',2)); + } + } + + QString str; + if (totaldays > 1) { + str = QObject::tr("%1 (%2 days): ").arg(chan.fullname()).arg(totaldays); + } else { + str = QObject::tr("%1 (%2 day): ").arg(chan.fullname()).arg(totaldays); + } + str += " "+strlist.join(", "); + + QRectF rec(rect.left(), rect.top(), 0,0); + painter.setFont(*defaultfont); + rec = painter.boundingRect(rec, Qt::AlignTop, str); + rec.moveBottom(rect.top()-3*graph.printScaleY()); + painter.drawText(rec, Qt::AlignTop, str); + +// graph.renderText(str, rect.left(), rect.top()-5*graph.printScaleY(), 0); + + +} + QString gSummaryChart::tooltipData(Day *, int idx) { QList & slices = cache[idx]; @@ -180,7 +320,7 @@ void gSummaryChart::populate(Day * day, int idx) float hours = day->hours(m_machtype); float base = 0; for (int i=0; i < size; ++i) { - const SummaryCalcItem & item = calcitems.at(i); + SummaryCalcItem & item = calcitems[i]; ChannelID code = item.code; schema::Channel & chan = schema::channel[code]; float value = 0; @@ -190,47 +330,47 @@ void gSummaryChart::populate(Day * day, int idx) case ST_CPH: value = day->count(code) / hours; name = chan.label(); - color = chan.defaultColor(); - slices.append(SummaryChartSlice(code, value, value, name, color)); + color = item.color; + slices.append(SummaryChartSlice(&item, value, value, name, color)); break; case ST_SPH: value = (100.0 / hours) * (day->sum(code) / 3600.0); name = QObject::tr("% in %1").arg(chan.label()); - color = chan.defaultColor(); - slices.append(SummaryChartSlice(code, value, value, name, color)); + color = item.color; + slices.append(SummaryChartSlice(&item, value, value, name, color)); break; case ST_HOURS: value = hours; name = QObject::tr("Hours"); color = COLOR_LightBlue; - slices.append(SummaryChartSlice(code, hours, hours, name, color)); + slices.append(SummaryChartSlice(&item, hours, hours, name, color)); break; case ST_MIN: value = day->Min(code); name = QObject::tr("Min %1").arg(chan.label()); - color = brighten(chan.defaultColor(),0.60); - slices.append(SummaryChartSlice(code, value, value - base, name, color)); + color = item.color; + slices.append(SummaryChartSlice(&item, value, value - base, name, color)); base = value; break; case ST_MID: value = day->calcMiddle(code); name = day->calcMiddleLabel(code); - color = brighten(chan.defaultColor(),1.25); - slices.append(SummaryChartSlice(code, value, value - base, name, color)); + color = item.color; + slices.append(SummaryChartSlice(&item, value, value - base, name, color)); base = value; break; case ST_90P: value = day->calcPercentile(code); name = day->calcPercentileLabel(code); - color = brighten(chan.defaultColor(),1.50); - slices.append(SummaryChartSlice(code, value, value - base, name, color)); + color = item.color; + slices.append(SummaryChartSlice(&item, value, value - base, name, color)); base = value; break; case ST_MAX: value = day->calcMax(code); name = day->calcMaxLabel(code); - color = brighten(chan.defaultColor(),2); - slices.append(SummaryChartSlice(code, value, value - base, name, color)); + color = item.color; + slices.append(SummaryChartSlice(&item, value, value - base, name, color)); base = value; break; default: @@ -264,13 +404,14 @@ void gSummaryChart::paint(QPainter &painter, gGraph &graph, const QRegion ®io float lastx1 = rect.left(); QMap::iterator it = dayindex.find(date); - int idx=0; + idx_start=0; if (it != dayindex.end()) { - idx = it.value(); + idx_start = it.value(); } + int idx = idx_start; QMap::iterator ite = dayindex.find(enddate); - int idx_end = daylist.size()-1; + idx_end = daylist.size()-1; if (ite != dayindex.end()) { idx_end = ite.value(); } @@ -315,7 +456,7 @@ void gSummaryChart::paint(QPainter &painter, gGraph &graph, const QRegion ®io /// Calculate Graph Peaks ///////////////////////////////////////////////////////////////////// peak_value = 0; - for (int i=idx; i < idx_end; ++i) { + for (int i=idx; i <= idx_end; ++i) { Day * day = daylist.at(i); if (!day) @@ -414,6 +555,7 @@ void gSummaryChart::paint(QPainter &painter, gGraph &graph, const QRegion ®io for (int i=0; i < listsize; ++i) { SummaryChartSlice & slice = list[i]; + SummaryCalcItem * calc = slice.calc; val = slice.height; y1 = ((lastval-miny) * ymult); @@ -499,13 +641,13 @@ void gUsageChart::populate(Day *day, int idx) float hours = day->hours(); - QColor cpapcolor = day->summaryOnly() ? QColor(128,128,128) : QColor(64,128,255); + QColor cpapcolor = day->summaryOnly() ? QColor(128,128,128) : calcitems[0].color; bool haveoxi = day->hasMachine(MT_OXIMETER); QColor goodcolor = haveoxi ? QColor(128,255,196) : cpapcolor; QColor color = (hours < compliance_threshold) ? QColor(255,64,64) : goodcolor; - slices.append(SummaryChartSlice(NoChannel, hours, hours, QObject::tr("Hours"), color)); + slices.append(SummaryChartSlice(&calcitems[0], hours, hours, QObject::tr("Hours"), color)); } void gUsageChart::preCalc() @@ -514,6 +656,9 @@ void gUsageChart::preCalc() incompdays = 0; totalhours = 0; totaldays = 0; + + hour_data.clear(); + hour_data.reserve(idx_end-idx_start); } void gUsageChart::customCalc(Day *, QList &list) @@ -521,15 +666,21 @@ void gUsageChart::customCalc(Day *, QList &list) SummaryChartSlice & slice = list[0]; if (slice.value < compliance_threshold) incompdays++; totalhours += slice.value; + hour_data.append(slice.value); totaldays++; } void gUsageChart::afterDraw(QPainter &, gGraph &graph, QRect rect) { + if (totaldays == nousedays) return; + if (totaldays > 1) { float comp = 100.0 - ((float(incompdays + nousedays) / float(totaldays)) * 100.0); double avg = totalhours / double(totaldays); - QString txt = QObject::tr("%1 low usage, %2 no usage, out of %3 days (%4% compliant.) Average %5 hours").arg(incompdays).arg(nousedays).arg(totaldays).arg(comp,0,'f',1).arg(avg, 0, 'f', 2); + + float med = median(hour_data.begin(), hour_data.end()); + QString txt = QObject::tr("%1 low usage, %2 no usage, out of %3 days (%4% compliant.) Avg %5, Med %6 hours"). + arg(incompdays).arg(nousedays).arg(totaldays).arg(comp,0,'f',1).arg(avg, 0, 'f', 2).arg(med, 0, 'f', 2); graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); } } @@ -538,10 +689,17 @@ void gUsageChart::afterDraw(QPainter &, gGraph &graph, QRect rect) void gSessionTimesChart::afterDraw(QPainter & /*painter */, gGraph &graph, QRect rect) { + if (totaldays == nousedays) return; + + float med = 0; + if (session_data.size() > 0) + med = median(session_data.begin(), session_data.end()); + + float avgsess = float(num_slices) / float(num_days); double avglength = total_length / double(num_slices); - QString txt = QObject::tr("Avg Sessions: %1 Avg Length: %2").arg(avgsess, 0, 'f', 1).arg(avglength, 0, 'f', 2); + QString txt = QObject::tr("Avg Sessions: %1 Length Avg: %2 Med %3").arg(avgsess, 0, 'f', 1).arg(avglength, 0, 'f', 2).arg(med, 0, 'f', 2); graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); } @@ -632,7 +790,7 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion & float s2 = double(slice.end - slice.start) / 3600000.0; QColor col = (slice.status == EquipmentOn) ? goodcolor : Qt::black; - slices.append(SummaryChartSlice(NoChannel, s1, s2, (slice.status == EquipmentOn) ? QObject::tr("Mask On") : QObject::tr("Mask Off"), col)); + slices.append(SummaryChartSlice(&calcitems[0], s1, s2, (slice.status == EquipmentOn) ? QObject::tr("Mask On") : QObject::tr("Mask Off"), col)); } } else { // otherwise just show session duration @@ -644,7 +802,7 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion & QString txt = QObject::tr("%1\nStart:%2\nLength:%3").arg(it.key().toString(Qt::SystemLocaleDate)).arg(st.time().toString("hh:mm:ss")).arg(s2,0,'f',2); - slices.append(SummaryChartSlice(NoChannel, s1, s2, txt, goodcolor)); + slices.append(SummaryChartSlice(&calcitems[0], s1, s2, txt, goodcolor)); } } @@ -696,10 +854,13 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion & if ((lastx1 + barw) > (rect.left()+rect.width()+1)) break; + totaldays++; + if (!day) { lasty1 = rect.bottom(); lastx1 += barw; + nousedays++; // it++; continue; } @@ -760,7 +921,6 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion & } - // it++; lastx1 = x1; } while (++idx <= idx_end); @@ -771,39 +931,76 @@ void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion & void gAHIChart::preCalc() { - indices.clear(); + gSummaryChart::preCalc(); + ahi_total = 0; calc_cnt = 0; total_hours = 0; + min_ahi = 99999; + max_ahi = -99999; + + ahi_data.clear(); + ahi_data.reserve(idx_end-idx_start); } void gAHIChart::customCalc(Day *day, QList &list) { int size = list.size(); - float hours = day->hours(m_machtype); + if (size == 0) return; + EventDataType hours = day->hours(m_machtype); + EventDataType ahi_cnt = 0; for (int i=0; i < size; ++i) { - const SummaryChartSlice & slice = list.at(i); + SummaryChartSlice & slice = list[i]; + SummaryCalcItem * calc = slice.calc; + EventDataType value = slice.value; - indices[slice.code] += value; - ahi_total += value; + + calc->sum += value; + calc->divisor += hours; + + calc->min = qMin(value / hours, calc->min); + calc->max = qMax(value / hours, calc->max); + + ahi_cnt += value; } + min_ahi = qMin(ahi_cnt / hours, min_ahi); + max_ahi = qMax(ahi_cnt / hours, max_ahi); + + ahi_data.append(ahi_cnt / hours); + + ahi_total += ahi_cnt; total_hours += hours; calc_cnt++; } void gAHIChart::afterDraw(QPainter & /*painter */, gGraph &graph, QRect rect) { - QStringList txtlist; - txtlist.append(QString("%1: %2").arg(STR_TR_AHI).arg(ahi_total / total_hours, 0, 'f', 2)); + if (totaldays == nousedays) return; - QHash::iterator it; - QHash::iterator it_end = indices.end(); + int size = idx_end - idx_start; - for (it = indices.begin(); it != it_end; ++it) { - ChannelID code = it.key(); - schema::Channel & chan = schema::channel[code]; - double indice = it.value() / total_hours; - txtlist.append(QString("%1: %2").arg(chan.label()).arg(indice, 0, 'f', 2)); + int mpos = size /2 ; + + float med = 0; + if (size > 0) { + + //nth_element(ahi_data.begin(), ahi_data.begin()+ mpos, ahi_data.end()); + med = median(ahi_data.begin(), ahi_data.end()); } - QString txt = txtlist.join(" "); + + 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)); + + int num_channels = calcitems.size(); + + for (int i=0; i < num_channels; ++i) { + SummaryCalcItem & calc = calcitems[i]; + if (calc.divisor > 0) { + ChannelID code = calc.code; + schema::Channel & chan = schema::channel[code]; + double indice = calc.sum / calc.divisor; + txtlist.append(QString("%1 %2 / %3 / %4").arg(chan.label()).arg(calc.min, 0, 'f', 2).arg(indice, 0, 'f', 2).arg(calc.max, 0, 'f', 2)); + } + } + QString txt = txtlist.join(", "); graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); } @@ -812,12 +1009,17 @@ void gAHIChart::populate(Day *day, int idx) QList & slices = cache[idx]; float hours = day->hours(); + int num_channels = calcitems.size(); + for (int i=0; i < num_channels; ++i) { - ChannelID code = channels.at(i); + SummaryCalcItem & calc = calcitems[i]; + ChannelID code = calc.code; if (!day->hasData(code, ST_CNT)) continue; + schema::Channel *chan = schema::channel.channels.find(code).value(); + float c = day->count(code); - slices.append(SummaryChartSlice(code, c, c / hours, chan->label(), chan->defaultColor())); + slices.append(SummaryChartSlice(&calc, c, c / hours, chan->label(), calc.color)); } } QString gAHIChart::tooltipData(Day *day, int idx) @@ -835,108 +1037,134 @@ QString gAHIChart::tooltipData(Day *day, int idx) return QString("\n%1: %2").arg(STR_TR_AHI).arg(float(total) / hour,0,'f',2)+txt; } + +gPressureChart::gPressureChart() + :gSummaryChart("Pressure", MT_CPAP) +{ + + // Do not reorder these!!! :P + addCalc(CPAP_Pressure, ST_SETMAX, schema::channel[CPAP_Pressure].defaultColor()); // 00 + addCalc(CPAP_Pressure, ST_MID, schema::channel[CPAP_Pressure].defaultColor()); // 01 + addCalc(CPAP_Pressure, ST_90P, brighten(schema::channel[CPAP_Pressure].defaultColor(), 1.33)); // 02 + addCalc(CPAP_PressureMin, ST_SETMIN, schema::channel[CPAP_PressureMin].defaultColor()); // 03 + addCalc(CPAP_PressureMax, ST_SETMAX, schema::channel[CPAP_PressureMax].defaultColor()); // 04 + + addCalc(CPAP_EPAP, ST_SETMAX, schema::channel[CPAP_EPAP].defaultColor()); // 05 + addCalc(CPAP_IPAP, ST_SETMAX, schema::channel[CPAP_IPAP].defaultColor()); // 06 + addCalc(CPAP_EPAPLo, ST_SETMAX, schema::channel[CPAP_EPAPLo].defaultColor()); // 07 + addCalc(CPAP_IPAPHi, ST_SETMAX, schema::channel[CPAP_IPAPHi].defaultColor()); // 08 + + addCalc(CPAP_EPAP, ST_MID, schema::channel[CPAP_EPAP].defaultColor()); // 09 + addCalc(CPAP_EPAP, ST_90P, brighten(schema::channel[CPAP_EPAP].defaultColor(),1.33)); // 10 + addCalc(CPAP_IPAP, ST_MID, schema::channel[CPAP_IPAP].defaultColor()); // 11 + addCalc(CPAP_IPAP, ST_90P, brighten(schema::channel[CPAP_IPAP].defaultColor(),1.33)); // 12 +} + + void gPressureChart::populate(Day * day, int idx) { float tmp; CPAPMode mode = (CPAPMode)(int)qRound(day->settings_wavg(CPAP_Mode)); + QList & slices = cache[idx]; + if (mode == MODE_CPAP) { float pr = day->settings_max(CPAP_Pressure); - cache[idx].append(SummaryChartSlice(CPAP_Pressure, pr, pr, schema::channel[CPAP_Pressure].label(), schema::channel[CPAP_Pressure].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[0], pr, pr, schema::channel[CPAP_Pressure].label(), calcitems[0].color)); } else if (mode == MODE_APAP) { float min = day->settings_min(CPAP_PressureMin); float max = day->settings_max(CPAP_PressureMax); - QList & slices = cache[idx]; tmp = min; - slices.append(SummaryChartSlice(CPAP_PressureMin, min, min, schema::channel[CPAP_PressureMin].label(), schema::channel[CPAP_PressureMin].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[3], min, min, schema::channel[CPAP_PressureMin].label(), calcitems[3].color)); if (!day->summaryOnly()) { float med = day->calcMiddle(CPAP_Pressure); - slices.append(SummaryChartSlice(CPAP_Pressure, med, med - tmp, day->calcMiddleLabel(CPAP_Pressure), schema::channel[CPAP_Pressure].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[1], med, med - tmp, day->calcMiddleLabel(CPAP_Pressure), calcitems[1].color)); tmp += med - tmp; float p90 = day->calcPercentile(CPAP_Pressure); - slices.append(SummaryChartSlice(CPAP_Pressure, p90, p90 - tmp, day->calcPercentileLabel(CPAP_Pressure), brighten(schema::channel[CPAP_Pressure].defaultColor(), 1.33))); + slices.append(SummaryChartSlice(&calcitems[2], p90, p90 - tmp, day->calcPercentileLabel(CPAP_Pressure), calcitems[2].color)); tmp += p90 - tmp; } - slices.append(SummaryChartSlice(CPAP_PressureMax, max, max - tmp, schema::channel[CPAP_PressureMax].label(), schema::channel[CPAP_PressureMax].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[4], max, max - tmp, schema::channel[CPAP_PressureMax].label(), calcitems[4].color)); } else if (mode == MODE_BILEVEL_FIXED) { float epap = day->settings_max(CPAP_EPAP); float ipap = day->settings_max(CPAP_IPAP); - QList & slices = cache[idx]; - slices.append(SummaryChartSlice(CPAP_EPAP, epap, epap, schema::channel[CPAP_EPAP].label(), schema::channel[CPAP_EPAP].defaultColor())); - slices.append(SummaryChartSlice(CPAP_IPAP, ipap, ipap - epap, schema::channel[CPAP_IPAP].label(), schema::channel[CPAP_IPAP].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[5], epap, epap, schema::channel[CPAP_EPAP].label(), calcitems[5].color)); + slices.append(SummaryChartSlice(&calcitems[6], ipap, ipap - epap, schema::channel[CPAP_IPAP].label(), calcitems[6].color)); } else if (mode == MODE_BILEVEL_AUTO_FIXED_PS) { float epap = day->settings_max(CPAP_EPAPLo); tmp = epap; float ipap = day->settings_max(CPAP_IPAPHi); - QList & slices = cache[idx]; - slices.append(SummaryChartSlice(CPAP_EPAP, epap, epap, schema::channel[CPAP_EPAPLo].label(), schema::channel[CPAP_EPAPLo].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[7], epap, epap, schema::channel[CPAP_EPAPLo].label(), calcitems[7].color)); if (!day->summaryOnly()) { float e50 = day->calcMiddle(CPAP_EPAP); - slices.append(SummaryChartSlice(CPAP_EPAP, e50, e50 - tmp, day->calcMiddleLabel(CPAP_EPAP), schema::channel[CPAP_EPAP].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[9], e50, e50 - tmp, day->calcMiddleLabel(CPAP_EPAP), calcitems[9].color)); tmp += e50 - tmp; float e90 = day->calcPercentile(CPAP_EPAP); - slices.append(SummaryChartSlice(CPAP_EPAP, e90, e90 - tmp, day->calcPercentileLabel(CPAP_EPAP), brighten(schema::channel[CPAP_EPAP].defaultColor(),1.33))); + slices.append(SummaryChartSlice(&calcitems[10], e90, e90 - tmp, day->calcPercentileLabel(CPAP_EPAP), calcitems[10].color)); tmp += e90 - tmp; float i50 = day->calcMiddle(CPAP_IPAP); - slices.append(SummaryChartSlice(CPAP_IPAP, i50, i50 - tmp, day->calcMiddleLabel(CPAP_IPAP), schema::channel[CPAP_IPAP].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[11], i50, i50 - tmp, day->calcMiddleLabel(CPAP_IPAP), calcitems[11].color)); tmp += i50 - tmp; float i90 = day->calcPercentile(CPAP_IPAP); - slices.append(SummaryChartSlice(CPAP_IPAP, i90, i90 - tmp, day->calcPercentileLabel(CPAP_IPAP), brighten(schema::channel[CPAP_IPAP].defaultColor(),1.33))); + slices.append(SummaryChartSlice(&calcitems[12], i90, i90 - tmp, day->calcPercentileLabel(CPAP_IPAP), calcitems[12].color)); tmp += i90 - tmp; } - slices.append(SummaryChartSlice(CPAP_EPAP, ipap, ipap - tmp, schema::channel[CPAP_IPAPHi].label(), schema::channel[CPAP_IPAPHi].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[8], ipap, ipap - tmp, schema::channel[CPAP_IPAPHi].label(), calcitems[8].color)); } else if ((mode == MODE_BILEVEL_AUTO_VARIABLE_PS) || (mode == MODE_ASV_VARIABLE_EPAP)) { float epap = day->settings_max(CPAP_EPAPLo); tmp = epap; - QList & slices = cache[idx]; - slices.append(SummaryChartSlice(CPAP_EPAPLo, epap, epap, schema::channel[CPAP_EPAPLo].label(), schema::channel[CPAP_EPAPLo].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[7], epap, epap, schema::channel[CPAP_EPAPLo].label(), calcitems[7].color)); if (!day->summaryOnly()) { float e50 = day->calcMiddle(CPAP_EPAP); - slices.append(SummaryChartSlice(CPAP_EPAP, e50, e50 - tmp, day->calcMiddleLabel(CPAP_EPAP), schema::channel[CPAP_EPAP].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[9], e50, e50 - tmp, day->calcMiddleLabel(CPAP_EPAP), calcitems[9].color)); tmp += e50 - tmp; float e90 = day->calcPercentile(CPAP_EPAP); - slices.append(SummaryChartSlice(CPAP_EPAP, e90, e90 - tmp, day->calcPercentileLabel(CPAP_EPAP), brighten(schema::channel[CPAP_EPAP].defaultColor(),1.33))); + slices.append(SummaryChartSlice(&calcitems[10], e90, e90 - tmp, day->calcPercentileLabel(CPAP_EPAP), calcitems[10].color)); tmp += e90 - tmp; float i50 = day->calcMiddle(CPAP_IPAP); - slices.append(SummaryChartSlice(CPAP_IPAP, i50, i50 - tmp, day->calcMiddleLabel(CPAP_IPAP), schema::channel[CPAP_IPAP].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[11], i50, i50 - tmp, day->calcMiddleLabel(CPAP_IPAP), calcitems[11].color)); tmp += i50 - tmp; float i90 = day->calcPercentile(CPAP_IPAP); - slices.append(SummaryChartSlice(CPAP_IPAP, i90, i90 - tmp, day->calcPercentileLabel(CPAP_IPAP), brighten(schema::channel[CPAP_IPAP].defaultColor(),1.33))); + slices.append(SummaryChartSlice(&calcitems[12], i90, i90 - tmp, day->calcPercentileLabel(CPAP_IPAP), calcitems[12].color)); tmp += i90 - tmp; } float ipap = day->settings_max(CPAP_IPAPHi); - slices.append(SummaryChartSlice(CPAP_IPAPHi, ipap, ipap - tmp, schema::channel[CPAP_IPAPHi].label(), schema::channel[CPAP_IPAPHi].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[8], ipap, ipap - tmp, schema::channel[CPAP_IPAPHi].label(), calcitems[8].color)); } else if (mode == MODE_ASV) { float epap = day->settings_max(CPAP_EPAP); tmp = epap; - QList & slices = cache[idx]; - slices.append(SummaryChartSlice(CPAP_EPAP, epap, epap, schema::channel[CPAP_EPAP].label(), schema::channel[CPAP_EPAP].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[5], epap, epap, schema::channel[CPAP_EPAP].label(), calcitems[5].color)); if (!day->summaryOnly()) { float i50 = day->calcMiddle(CPAP_IPAP); - slices.append(SummaryChartSlice(CPAP_IPAP, i50, i50 - tmp, day->calcMiddleLabel(CPAP_IPAP), schema::channel[CPAP_IPAP].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[11], i50, i50 - tmp, day->calcMiddleLabel(CPAP_IPAP), calcitems[11].color)); tmp += i50 - tmp; float i90 = day->calcPercentile(CPAP_IPAP); - slices.append(SummaryChartSlice(CPAP_IPAP, i90, i90 - tmp, day->calcPercentileLabel(CPAP_IPAP), brighten(schema::channel[CPAP_IPAP].defaultColor(),1.33))); + slices.append(SummaryChartSlice(&calcitems[12], i90, i90 - tmp, day->calcPercentileLabel(CPAP_IPAP), calcitems[12].color)); tmp += i90 - tmp; } float ipap = day->settings_max(CPAP_IPAPHi); - slices.append(SummaryChartSlice(CPAP_IPAPHi, ipap, ipap - tmp, schema::channel[CPAP_IPAPHi].label(), schema::channel[CPAP_IPAPHi].defaultColor())); + slices.append(SummaryChartSlice(&calcitems[8], ipap, ipap - tmp, schema::channel[CPAP_IPAPHi].label(), calcitems[8].color)); } } + +//void gPressureChart::afterDraw(QPainter &painter, gGraph &graph, QRect rect) +//{ +//} + diff --git a/sleepyhead/Graphs/gSessionTimesChart.h b/sleepyhead/Graphs/gSessionTimesChart.h index 77d752bb..ed947b86 100644 --- a/sleepyhead/Graphs/gSessionTimesChart.h +++ b/sleepyhead/Graphs/gSessionTimesChart.h @@ -14,6 +14,39 @@ #include "gGraphView.h" +///Represents the exception for taking the median of an empty list +class median_of_empty_list_exception:public std::exception{ + virtual const char* what() const throw() { + return "Attempt to take the median of an empty list of numbers. " + "The median of an empty list is undefined."; + } +}; + +///Return the median of a sequence of numbers defined by the random +///access iterators begin and end. The sequence must not be empty +///(median is undefined for an empty set). +/// +///The numbers must be convertible to double. +template +double median(RandAccessIter begin, RandAccessIter end) + throw(median_of_empty_list_exception){ + if(begin == end){ throw median_of_empty_list_exception(); } + std::size_t size = end - begin; + std::size_t middleIdx = size/2; + RandAccessIter target = begin + middleIdx; + std::nth_element(begin, target, end); + + if(size % 2 != 0){ //Odd number of elements + return *target; + }else{ //Even number of elements + double a = *target; + RandAccessIter targetNeighbor= target-1; + std::nth_element(begin, targetNeighbor, end); + return (a+*targetNeighbor)/2.0; + } +} + + struct TimeSpan { public: @@ -32,34 +65,49 @@ struct SummaryCalcItem { SummaryCalcItem() { code = 0; type = ST_CNT; + color = Qt::black; } SummaryCalcItem(const SummaryCalcItem & copy) { code = copy.code; type = copy.type; + color = copy.color; + + sum = copy.sum; + divisor = copy.divisor; + min = copy.min; + max = copy.max; } - SummaryCalcItem(ChannelID code, SummaryType type) - :code(code), type(type) {} + SummaryCalcItem(ChannelID code, SummaryType type, QColor color) + :code(code), type(type), color(color) {} ChannelID code; SummaryType type; + QColor color; + + double sum; + double divisor; + EventDataType min; + EventDataType max; }; struct SummaryChartSlice { SummaryChartSlice() { - code = 0; + calc = nullptr; height = 0; value = 0; name = ST_CNT; + color = Qt::black; } SummaryChartSlice(const SummaryChartSlice & copy) { - code = copy.code; + calc = copy.calc; 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; + + SummaryChartSlice(SummaryCalcItem * calc, EventDataType value, EventDataType height, QString name, QColor color) + :calc(calc), value(value), height(height), name(name), color(color) {} + SummaryCalcItem * calc; EventDataType value; EventDataType height; QString name; @@ -85,18 +133,23 @@ public: virtual void populate(Day *, int idx); //! \brief Override to setup custom stuff before main loop - virtual void preCalc() {} + virtual void preCalc(); //! \brief Override to call stuff in main loop - virtual void customCalc(Day *, QList &) {} + virtual void customCalc(Day *, QList &); //! \brief Override to call stuff after draw is complete - virtual void afterDraw(QPainter &, gGraph &, QRect) {} + 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)); } + void addCalc(ChannelID code, SummaryType type, QColor color) { + calcitems.append(SummaryCalcItem(code, type, color)); + } + void addCalc(ChannelID code, SummaryType type) { + calcitems.append(SummaryCalcItem(code, type, schema::channel[code].defaultColor())); + } virtual Layer * Clone() { gSummaryChart * sc = new gSummaryChart(m_label, m_machtype); @@ -152,6 +205,9 @@ protected: EventDataType peak_value; EventDataType min_value; + + int idx_start; + int idx_end; }; @@ -162,7 +218,9 @@ class gSessionTimesChart : public gSummaryChart { public: gSessionTimesChart() - :gSummaryChart("SessionTimes", MT_CPAP) {} + :gSummaryChart("SessionTimes", MT_CPAP) { + addCalc(NoChannel, ST_SESSIONS, QColor(64,128,255)); + } virtual ~gSessionTimesChart() {} virtual void SetDay(Day * day = nullptr) { @@ -177,6 +235,9 @@ public: num_slices = 0; num_days = 0; total_length = 0; + session_data.clear(); + session_data.reserve(idx_end-idx_start); + } virtual void customCalc(Day *, QList & slices) { int size = slices.size(); @@ -185,6 +246,7 @@ public: for (int i=0; i session_data; + }; @@ -217,7 +281,7 @@ class gUsageChart : public gSummaryChart public: gUsageChart() :gSummaryChart("Usage", MT_CPAP) { - addCalc(NoChannel, ST_HOURS); + addCalc(NoChannel, ST_HOURS, QColor(64,128,255)); } virtual ~gUsageChart() {} @@ -246,6 +310,8 @@ private: EventDataType compliance_threshold; double totalhours; int totaldays; + QList hour_data; + }; @@ -254,13 +320,12 @@ 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); + addCalc(CPAP_ClearAirway, ST_CPH); + addCalc(CPAP_Obstructive, ST_CPH); + addCalc(CPAP_Apnea, ST_CPH); + addCalc(CPAP_Hypopnea, ST_CPH); if (p_profile->general->calculateRDI()) - channels.append(CPAP_RERA); - num_channels = channels.size(); + addCalc(CPAP_RERA, ST_CPH); } virtual ~gAHIChart() {} @@ -280,48 +345,38 @@ public: } 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 channels; - int num_channels; - - QHash indices; double ahi_total; double total_hours; + float max_ahi; + float min_ahi; + int calc_cnt; + QList ahi_data; }; class gPressureChart : public gSummaryChart { public: - gPressureChart() - :gSummaryChart("Pressure", MT_CPAP) { - } + gPressureChart(); 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 afterDraw(QPainter &, gGraph &, QRect); + virtual void populate(Day * day, int idx); + virtual QString tooltipData(Day * day, int idx) { return day->getCPAPMode() + "\n" + day->getPressureSettings() + gSummaryChart::tooltipData(day, idx); - } }; diff --git a/sleepyhead/SleepLib/calcs.cpp b/sleepyhead/SleepLib/calcs.cpp index 90b3f86e..e0f006d9 100644 --- a/sleepyhead/SleepLib/calcs.cpp +++ b/sleepyhead/SleepLib/calcs.cpp @@ -1177,7 +1177,7 @@ struct zMaskProfile { protected: static const quint32 version = 0; void scanLeakList(EventList *leak); - void scanPressureList(EventList *el); + void scanPressureList(Session *session, ChannelID code); MaskType m_type; QString m_name; @@ -1251,60 +1251,49 @@ void zMaskProfile::save() f.close(); } -void zMaskProfile::scanPressureList(EventList *el) +void zMaskProfile::scanPressureList(Session *session, ChannelID code) { - qint64 start = el->first(); - int count = el->count(); - EventStoreType *dptr = el->rawData(); - EventStoreType *eptr = dptr + count; - quint32 *tptr = el->rawTime(); - qint64 time; - EventStoreType pressure; + QHash >::iterator it = session->eventlist.find(code); - for (; dptr < eptr; dptr++) { - time = start + *tptr++; - pressure = *dptr; - Pressure.push_back(TimeValue(time, pressure)); + if (it == session->eventlist.end()) return; + + int prescnt = session->count(code); + Pressure.reserve(Pressure.size() + prescnt); + + QVector &EVL = it.value(); + int size = EVL.size(); + + for (int j = 0; j < size; ++j) { + EventList * el = EVL[j]; + + qint64 start = el->first(); + int count = el->count(); + EventStoreType *dptr = el->rawData(); + EventStoreType *eptr = dptr + count; + quint32 *tptr = el->rawTime(); + qint64 time; + EventStoreType pressure; + + for (; dptr < eptr; dptr++) { + time = start + *tptr++; + pressure = *dptr; + Pressure.push_back(TimeValue(time, pressure)); + } } } void zMaskProfile::scanPressure(Session *session) { Pressure.clear(); - int prescnt = 0; - - //EventStoreType pressure; - if (session->eventlist.contains(CPAP_Pressure)) { - prescnt = session->count(CPAP_Pressure); - Pressure.reserve(prescnt); - - // WTF IS THIS DOING??? WHY THE HECK DID I PUT AN INNER LOOP HERE?? - QVector &EVL=session->eventlist[CPAP_Pressure]; - int size = EVL.size(); - for (int j = 0; j < size; ++j) { - scanPressureList(EVL[j]); -// QVector &el = session->eventlist[CPAP_Pressure]; - -// for (int e = 0; e < el.size(); e++) { -// scanPressureList(el[e]); -// } - } - } else if (session->eventlist.contains(CPAP_IPAP)) { - prescnt = session->count(CPAP_IPAP); - Pressure.reserve(prescnt); - - QVector &EVL=session->eventlist[CPAP_IPAP]; - int size = EVL.size(); - - for (int j = 0; j < size; ++j) { - scanPressureList(EVL[j]); - } - } + scanPressureList(session, CPAP_Pressure); + scanPressureList(session, CPAP_IPAP); qSort(Pressure); } void zMaskProfile::scanLeakList(EventList *el) { + // Scans through Leak list, and builds a histogram of each leak value for each pressure. + qint64 start = el->first(); int count = el->count(); EventStoreType *dptr = el->rawData(); @@ -1325,6 +1314,7 @@ void zMaskProfile::scanLeakList(EventList *el) TimeValue *tvend = tvstr + (psize - 1); TimeValue *p1, *p2; + // Scan through each leak item in event list for (; dptr < eptr; dptr++) { leak = *dptr; ti = start + *tptr++; @@ -1333,14 +1323,17 @@ void zMaskProfile::scanLeakList(EventList *el) pressure = Pressure[0].value; if (psize > 1) { + // Scan through pressure list to find pressure at this particular leak time for (p1 = tvstr; p1 != tvend; ++p1) { p2 = p1+1; if ((p2->time > ti) && (p1->time <= ti)) { + // leak within current pressure range pressure = p1->value; found = true; break; } else if (p2->time == ti) { + // can't remember why a added this condition... pressure = p2->value; found = true; break; @@ -1362,10 +1355,12 @@ void zMaskProfile::scanLeakList(EventList *el) // } } } else { + // were talking CPAP here with no ramp.. found = true; } if (found) { + // update the histogram of leak values for this pressure pressureleaks[pressure][leak]++; // pmin=pressuremin.find(pressure); // fleak=EventDataType(leak) * gain; @@ -1378,8 +1373,6 @@ void zMaskProfile::scanLeakList(EventList *el) // pressurecount[pressure]=pressureleaks[pressure][leak]; // } // } - } else { - //int i=5; } } @@ -1387,7 +1380,10 @@ void zMaskProfile::scanLeakList(EventList *el) } void zMaskProfile::scanLeaks(Session *session) { - QVector &elv = session->eventlist[CPAP_LeakTotal]; + QHash >::iterator ELV = session->eventlist.find(CPAP_LeakTotal); + + if (ELV == session->eventlist.end()) return; + QVector &elv = ELV.value(); int size=elv.size(); if (!size) diff --git a/sleepyhead/SleepLib/profiles.h b/sleepyhead/SleepLib/profiles.h index ab1acc2b..53947b3e 100644 --- a/sleepyhead/SleepLib/profiles.h +++ b/sleepyhead/SleepLib/profiles.h @@ -300,6 +300,7 @@ const QString STR_CS_ShowLeakRedline = "ShowLeakRedline"; // ImportSettings Strings const QString STR_IS_DaySplitTime = "DaySplitTime"; +const QString STR_IS_PreloadSummaries = "PreloadSummaries"; const QString STR_IS_CacheSessions = "MemoryHog"; const QString STR_IS_CombineCloseSessions = "CombineCloserSessions"; const QString STR_IS_IgnoreShorterSessions = "IgnoreShorterSessions"; @@ -333,6 +334,7 @@ const QString STR_US_EventWindowSize = "EventWindowSize"; const QString STR_US_SkipEmptyDays = "SkipEmptyDays"; const QString STR_US_RebuildCache = "RebuildCache"; const QString STR_US_ShowDebug = "ShowDebug"; +const QString STR_US_ShowPerformance = "ShowPerformance"; const QString STR_US_LinkGroups = "LinkGroups"; const QString STR_US_CalculateRDI = "CalculateRDI"; const QString STR_US_ShowSerialNumbers = "ShowSerialNumbers"; @@ -636,6 +638,7 @@ class SessionSettings : public ProfileSettings { initPref(STR_IS_DaySplitTime, QTime(12, 0, 0)); initPref(STR_IS_CacheSessions, false); + initPref(STR_IS_PreloadSummaries, false); initPref(STR_IS_CombineCloseSessions, 240); initPref(STR_IS_IgnoreShorterSessions, 5); initPref(STR_IS_Multithreading, QThread::idealThreadCount() > 1); @@ -650,6 +653,7 @@ class SessionSettings : public ProfileSettings QTime daySplitTime() const { return getPref(STR_IS_DaySplitTime).toTime(); } bool cacheSessions() const { return getPref(STR_IS_CacheSessions).toBool(); } + bool preloadSummaries() const { return getPref(STR_IS_PreloadSummaries).toBool(); } double combineCloseSessions() const { return getPref(STR_IS_CombineCloseSessions).toDouble(); } double ignoreShortSessions() const { return getPref(STR_IS_IgnoreShorterSessions).toDouble(); } bool multithreading() const { return getPref(STR_IS_Multithreading).toBool(); } @@ -662,6 +666,7 @@ class SessionSettings : public ProfileSettings void setDaySplitTime(QTime time) { setPref(STR_IS_DaySplitTime, time); } void setCacheSessions(bool c) { setPref(STR_IS_CacheSessions, c); } + void setPreloadSummaries(bool b) { setPref(STR_IS_PreloadSummaries, b); } void setCombineCloseSessions(double val) { setPref(STR_IS_CombineCloseSessions, val); } void setIgnoreShortSessions(double val) { setPref(STR_IS_IgnoreShorterSessions, val); } void setMultithreading(bool enabled) { setPref(STR_IS_Multithreading, enabled); } @@ -781,7 +786,7 @@ class UserSettings : public ProfileSettings initPref(STR_US_SkipEmptyDays, true); initPref(STR_US_RebuildCache, false); // FIXME: jedimark: can't remember... initPref(STR_US_ShowDebug, false); -// initPref(STR_US_LinkGroups, true); // FIXME: jedimark: can't remember... + initPref(STR_US_ShowPerformance, false); initPref(STR_US_CalculateRDI, false); initPref(STR_US_ShowSerialNumbers, false); initPref(STR_US_PrefCalcMiddle, (int)0); @@ -799,7 +804,7 @@ class UserSettings : public ProfileSettings bool skipEmptyDays() const { return getPref(STR_US_SkipEmptyDays).toBool(); } bool rebuildCache() const { return getPref(STR_US_RebuildCache).toBool(); } bool showDebug() const { return getPref(STR_US_ShowDebug).toBool(); } -// bool linkGroups() const { return getPref(STR_US_LinkGroups).toBool(); } + bool showPerformance() const { return getPref(STR_US_ShowPerformance).toBool(); } bool calculateRDI() const { return getPref(STR_US_CalculateRDI).toBool(); } bool showSerialNumbers() const { return getPref(STR_US_ShowSerialNumbers).toBool(); } int prefCalcMiddle() const { return getPref(STR_US_PrefCalcMiddle).toInt(); } @@ -816,7 +821,7 @@ class UserSettings : public ProfileSettings void setSkipEmptyDays(bool skip) { setPref(STR_US_SkipEmptyDays, skip); } void setRebuildCache(bool rebuild) { setPref(STR_US_RebuildCache, rebuild); } void setShowDebug(bool b) { setPref(STR_US_ShowDebug, b); } - // void setLinkGroups(bool link) { setPref(STR_US_LinkGroups, link); } + void setShowPerformance(bool b) { setPref(STR_US_ShowPerformance, b); } void setCalculateRDI(bool rdi) { setPref(STR_US_CalculateRDI, rdi); } void setShowSerialNumbers(bool enabled) { setPref(STR_US_ShowSerialNumbers, enabled); } void setPrefCalcMiddle(int i) { setPref(STR_US_PrefCalcMiddle, i); } diff --git a/sleepyhead/mainwindow.cpp b/sleepyhead/mainwindow.cpp index 077c7460..47b51d73 100644 --- a/sleepyhead/mainwindow.cpp +++ b/sleepyhead/mainwindow.cpp @@ -240,6 +240,8 @@ MainWindow::MainWindow(QWidget *parent) : ui->logText->hide(); } + ui->actionShow_Performance_Counters->setChecked(p_profile->general->showPerformance()); + #ifdef Q_OS_MAC p_profile->appearance->setAntiAliasing(false); #endif @@ -2661,3 +2663,8 @@ void MainWindow::on_actionExport_Journal_triggered() BackupJournal(filename); } + +void MainWindow::on_actionShow_Performance_Counters_toggled(bool arg1) +{ + p_profile->general->setShowPerformance(arg1); +} diff --git a/sleepyhead/mainwindow.h b/sleepyhead/mainwindow.h index b9beb2d5..942cfecc 100644 --- a/sleepyhead/mainwindow.h +++ b/sleepyhead/mainwindow.h @@ -324,6 +324,8 @@ class MainWindow : public QMainWindow void on_actionExport_Journal_triggered(); + void on_actionShow_Performance_Counters_toggled(bool arg1); + private: void importCPAPBackups(); void finishCPAPImport(); diff --git a/sleepyhead/mainwindow.ui b/sleepyhead/mainwindow.ui index 27d35ed7..0a5f66f7 100644 --- a/sleepyhead/mainwindow.ui +++ b/sleepyhead/mainwindow.ui @@ -1908,8 +1908,8 @@ border: 2px solid #56789a; border-radius: 30px; 0 0 - 180 - 724 + 98 + 28 @@ -3056,8 +3056,8 @@ border-radius: 10px; 0 0 - 180 - 724 + 98 + 28 @@ -3175,6 +3175,7 @@ border-radius: 10px; + @@ -3525,6 +3526,14 @@ border-radius: 10px; Backup &Journal + + + true + + + Show Performance Information + + diff --git a/sleepyhead/overview.cpp b/sleepyhead/overview.cpp index 2ead8ac3..91e35ea7 100644 --- a/sleepyhead/overview.cpp +++ b/sleepyhead/overview.cpp @@ -379,11 +379,11 @@ void Overview::RebuildGraphs(bool reset) 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); + sc->addCalc(code, ST_CPH, schema::channel[code].defaultColor()); G->AddLayer(sc); } else if (chan->type() == schema::SPAN) { gSummaryChart * sc = new gSummaryChart(chan->code(), MT_CPAP); - sc->addCalc(code, ST_SPH); + sc->addCalc(code, ST_SPH, schema::channel[code].defaultColor()); G->AddLayer(sc); } else if (chan->type() == schema::WAVEFORM) { G->AddLayer(new gSummaryChart(code, chan->machtype())); diff --git a/sleepyhead/preferencesdialog.cpp b/sleepyhead/preferencesdialog.cpp index f2acfbf1..1b073d00 100644 --- a/sleepyhead/preferencesdialog.cpp +++ b/sleepyhead/preferencesdialog.cpp @@ -35,27 +35,12 @@ typedef QMessageBox::StandardButtons StandardButtons; QHash channeltype; - -MaskProfile masks[] = { - {Mask_Unknown, QObject::tr("Unspecified"), {{4, 25}, {8, 25}, {12, 25}, {16, 25}, {20, 25}}}, - {Mask_NasalPillows, QObject::tr("Nasal Pillows"), {{4, 20}, {8, 29}, {12, 37}, {16, 43}, {20, 49}}}, - {Mask_Hybrid, QObject::tr("Hybrid F/F Mask"), {{4, 20}, {8, 29}, {12, 37}, {16, 43}, {20, 49}}}, - {Mask_StandardNasal, QObject::tr("Nasal Interface"), {{4, 20}, {8, 29}, {12, 37}, {16, 43}, {20, 49}}}, - {Mask_FullFace, QObject::tr("Full-Face Mask"), {{4, 20}, {8, 29}, {12, 37}, {16, 43}, {20, 49}}}, -}; -const int num_masks = sizeof(masks) / sizeof(MaskProfile); - PreferencesDialog::PreferencesDialog(QWidget *parent, Profile *_profile) : QDialog(parent), ui(new Ui::PreferencesDialog), profile(_profile) { ui->setupUi(this); - ui->leakProfile->setRowCount(5); - ui->leakProfile->setColumnCount(2); - ui->leakProfile->horizontalHeader()->setStretchLastSection(true); - ui->leakProfile->setColumnWidth(0, 100); - ui->maskTypeCombo->clear(); channeltype.clear(); channeltype[schema::FLAG] = tr("Flag"); @@ -63,20 +48,6 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, Profile *_profile) : channeltype[schema::SPAN] = tr("Span"); channeltype[schema::UNKNOWN] = tr("Always Minor"); - //ui->customEventGroupbox->setEnabled(false); - - QString masktype = tr("Nasal Pillows"); - - //masktype=PROFILEMaskType - for (int i = 0; i < num_masks; i++) { - ui->maskTypeCombo->addItem(masks[i].name); - - /*if (masktype==masks[i].name) { - ui->maskTypeCombo->setCurrentIndex(i); - on_maskTypeCombo_activated(i); - }*/ - } - //#ifdef LOCK_RESMED_SESSIONS // QList machines = p_profile->GetMachines(MT_CPAP); // for (QList::iterator it = machines.begin(); it != machines.end(); ++it) { @@ -97,20 +68,10 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, Profile *_profile) : shortformat.replace("yy", "yyyy"); } - ui->startedUsingMask->setDisplayFormat(shortformat); Qt::DayOfWeek dow = firstDayOfWeekFromLocale(); - ui->startedUsingMask->calendarWidget()->setFirstDayOfWeek(dow); - - //ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->colourTab)); - - // Stop both calendar drop downs highlighting weekends in red - QTextCharFormat format = ui->startedUsingMask->calendarWidget()->weekdayTextFormat(Qt::Saturday); - format.setForeground(QBrush(Qt::black, Qt::SolidPattern)); - ui->startedUsingMask->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format); - ui->startedUsingMask->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format); - - //ui->leakProfile->setColumnWidth(1,ui->leakProfile->width()/2); +// QTextCharFormat format = ui->startedUsingMask->calendarWidget()->weekdayTextFormat(Qt::Saturday); +// format.setForeground(QBrush(Qt::black, Qt::SolidPattern)); Q_ASSERT(profile != nullptr); ui->tabWidget->setCurrentIndex(0); @@ -130,7 +91,8 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, Profile *_profile) : ui->pulseChange->setValue(profile->oxi->pulseChangeBPM()); ui->pulseChangeTime->setValue(profile->oxi->pulseChangeDuration()); ui->oxiDiscardThreshold->setValue(profile->oxi->oxiDiscardThreshold()); - ui->AddRERAtoAHI->setChecked(profile->general->calculateRDI()); + + ui->eventIndexCombo->setCurrentIndex(profile->general->calculateRDI() ? 1 : 0); ui->automaticImport->setChecked(profile->cpap->autoImport()); ui->timeEdit->setTime(profile->session->daySplitTime()); @@ -176,17 +138,8 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, Profile *_profile) : ui->lineThicknessSlider->setValue(profile->appearance->lineThickness()*2); - ui->startedUsingMask->setDate(profile->cpap->maskStartDate()); - - ui->leakModeCombo->setCurrentIndex(profile->cpap->leakMode()); - - int mt = (int)profile->cpap->maskType(); - ui->maskTypeCombo->setCurrentIndex(mt); - on_maskTypeCombo_activated(mt); - ui->resyncMachineDetectedEvents->setChecked(profile->cpap->resyncFromUserFlagging()); - ui->maskDescription->setText(profile->cpap->maskDescription()); ui->useAntiAliasing->setChecked(profile->appearance->antiAliasing()); ui->usePixmapCaching->setChecked(profile->appearance->usePixmapCaching()); ui->useSquareWavePlots->setChecked(profile->appearance->squareWavePlots()); @@ -197,14 +150,21 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, Profile *_profile) : ui->skipLoginScreen->setChecked(PREF[STR_GEN_SkipLogin].toBool()); ui->allowEarlyUpdates->setChecked(PREF[STR_PREF_AllowEarlyUpdates].toBool()); - ui->clockDrift->setValue(profile->cpap->clockDrift()); + int s = profile->cpap->clockDrift(); + int m = (s / 60) % 60; + int h = (s / 3600); + s %= 60; + ui->clockDriftHours->setValue(h); + ui->clockDriftMinutes->setValue(m); + ui->clockDriftSeconds->setValue(s); ui->skipEmptyDays->setChecked(profile->general->skipEmptyDays()); ui->showUnknownFlags->setChecked(profile->general->showUnknownFlags()); ui->enableMultithreading->setChecked(profile->session->multithreading()); ui->cacheSessionData->setChecked(profile->session->cacheSessions()); + ui->preloadSummaries->setChecked(profile->session->preloadSummaries()); ui->animationsAndTransitionsCheckbox->setChecked(profile->appearance->animations()); - ui->complianceGroupbox->setChecked(profile->cpap->showComplianceInfo()); + ui->complianceCheckBox->setChecked(profile->cpap->showComplianceInfo()); ui->complianceHours->setValue(profile->cpap->complianceHours()); ui->prefCalcMiddle->setCurrentIndex(profile->general->prefCalcMiddle()); @@ -405,15 +365,15 @@ void PreferencesDialog::InitChanInfo() QStringList headers; headers.append(tr("Name")); headers.append(tr("Color")); - headers.append(tr("Flag Type")); headers.append(tr("Overview")); + headers.append(tr("Flag Type")); 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, 60); + ui->chanView->setColumnWidth(1, 40); + ui->chanView->setColumnWidth(2, 60); + ui->chanView->setColumnWidth(3, 100); ui->chanView->setColumnWidth(4, 100); ui->chanView->setSelectionMode(QAbstractItemView::SingleSelection); ui->chanView->setSelectionBehavior(QAbstractItemView::SelectItems); @@ -467,7 +427,10 @@ void PreferencesDialog::InitChanInfo() it->setCheckState(chan->enabled() ? Qt::Checked : Qt::Unchecked); it->setEditable(true); it->setData(chan->id(), Qt::UserRole); - it->setToolTip(tr("Double click to change the descriptive name this channel.")); + + // Dear translators: %1 is a unique ascii english string used to indentify channels in the code, I'd like feedback on how this goes.. + // It's here in case users mess up which field is which.. it will always show the Channel Code underneath in the tooltip. + it->setToolTip(tr("Double click to change the descriptive name the '%1' channel.").arg(chan->code())); items.push_back(it); @@ -476,10 +439,17 @@ void PreferencesDialog::InitChanInfo() 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(QString()); + it->setToolTip(tr("Whether this flag has a dedicated overview chart.")); + it->setCheckable(true); + it->setCheckState(chan->showInOverview() ? Qt::Checked : Qt::Unchecked); + it->setTextAlignment(Qt::AlignCenter); + it->setData(chan->id(), Qt::UserRole); + items.push_back(it); + schema::ChanType type = chan->type(); it = new QStandardItem(channeltype[type]); @@ -487,13 +457,6 @@ 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.")); @@ -540,7 +503,7 @@ void PreferencesDialog::InitWaveInfo() headers.append(tr("Details")); waveModel->setHorizontalHeaderLabels(headers); ui->waveView->setColumnWidth(0, 200); - ui->waveView->setColumnWidth(1, 50); + ui->waveView->setColumnWidth(1, 40); ui->waveView->setColumnWidth(2, 60); ui->waveView->setColumnWidth(3, 50); ui->waveView->setColumnWidth(4, 50); @@ -685,7 +648,8 @@ bool PreferencesDialog::Save() needs_restart = true; } - if (profile->general->calculateRDI() != ui->AddRERAtoAHI->isChecked()) { + int rdi_set = profile->general->calculateRDI() ? 1 : 0; + if (rdi_set != ui->eventIndexCombo->currentIndex()) { //recalc_events=true; needs_restart = true; } @@ -764,23 +728,22 @@ bool PreferencesDialog::Save() profile->general->setShowUnknownFlags(ui->showUnknownFlags->isChecked()); profile->session->setMultithreading(ui->enableMultithreading->isChecked()); profile->session->setCacheSessions(ui->cacheSessionData->isChecked()); - profile->cpap->setMaskDescription(ui->maskDescription->text()); + profile->session->setPreloadSummaries(ui->preloadSummaries->isChecked()); profile->appearance->setAnimations(ui->animationsAndTransitionsCheckbox->isChecked()); profile->cpap->setShowLeakRedline(ui->showLeakRedline->isChecked()); profile->cpap->setLeakRedline(ui->leakRedlineSpinbox->value()); - profile->cpap->setShowComplianceInfo(ui->complianceGroupbox->isChecked()); + profile->cpap->setShowComplianceInfo(ui->complianceCheckBox->isChecked()); profile->cpap->setComplianceHours(ui->complianceHours->value()); - profile->cpap->setMaskStartDate(ui->startedUsingMask->date()); profile->appearance->setGraphHeight(ui->graphHeight->value()); profile->general->setPrefCalcMiddle(ui->prefCalcMiddle->currentIndex()); profile->general->setPrefCalcMax(ui->prefCalcMax->currentIndex()); profile->general->setPrefCalcPercentile(ui->prefCalcPercentile->value()); - profile->general->setCalculateRDI(ui->AddRERAtoAHI->isChecked()); + profile->general->setCalculateRDI((ui->eventIndexCombo->currentIndex() == 1)); profile->session->setBackupCardData(ui->createSDBackups->isChecked()); profile->session->setCompressBackupData(ui->compressSDBackups->isChecked()); profile->session->setCompressSessionData(ui->compressSessionData->isChecked()); @@ -791,15 +754,13 @@ bool PreferencesDialog::Save() profile->session->setIgnoreOlderSessions(ui->ignoreOlderSessionsCheck->isChecked()); profile->session->setIgnoreOlderSessionsDate(ui->ignoreOlderSessionsDate->date()); - profile->cpap->setClockDrift(ui->clockDrift->value()); + int s = ui->clockDriftHours->value() * 3600 + ui->clockDriftMinutes->value() * 60 + ui->clockDriftSeconds->value(); + profile->cpap->setClockDrift(s); profile->appearance->setOverlayType((OverlayDisplayType)ui->overlayFlagsCombo->currentIndex()); profile->appearance->setOverviewLinechartMode((OverviewLinechartModes) ui->overviewLinecharts->currentIndex()); - profile->cpap->setLeakMode(ui->leakModeCombo->currentIndex()); - profile->cpap->setMaskType((MaskType)ui->maskTypeCombo->currentIndex()); - profile->oxi->setSyncOximetry(ui->oximetrySync->isChecked()); int oxigrp = ui->oximetrySync->isChecked() ? 0 : 1; gGraphView *gv = mainwin->getDaily()->graphView(); @@ -895,6 +856,72 @@ bool PreferencesDialog::Save() bigfont->setItalic(ui->bigFontItalic->isChecked()); + saveChanInfo(); + saveWaveInfo(); + //qDebug() << "TODO: Save channels.xml to update channel data"; + + PREF.Save(); + p_profile->Save(); + + if (recalc_events) { + // send a signal instead? + mainwin->reprocessEvents(needs_restart); + } else if (needs_restart) { + p_profile->removeLock(); + mainwin->RestartApplication(); + } else { + mainwin->getDaily()->LoadDate(mainwin->getDaily()->getDate()); + // Save early.. just in case.. + mainwin->getDaily()->graphView()->SaveSettings("Daily"); + mainwin->getOverview()->graphView()->SaveSettings("Overview"); + } + + return true; +} + +void PreferencesDialog::saveChanInfo() +{ + // Change focus to force save of any open editors.. + ui->channelSearch->setFocus(); + + int toprows = chanModel->rowCount(); + bool ok; + + for (int i=0; i < toprows; i++) { + QStandardItem * topitem = chanModel->item(i,0); + + if (!topitem) continue; + int rows = topitem->rowCount(); + for (int j=0; j< rows; ++j) { + QStandardItem * item = topitem->child(j, 0); + if (!item) continue; + + ChannelID id = item->data(Qt::UserRole).toUInt(&ok); + schema::Channel & chan = schema::channel[id]; + if (chan.isNull()) continue; + chan.setEnabled(item->checkState() == Qt::Checked ? true : false); + chan.setFullname(item->text()); + chan.setDefaultColor(QColor(topitem->child(j,1)->data(Qt::UserRole).toUInt())); + chan.setShowInOverview(topitem->child(j,2)->checkState() == Qt::Checked); + QString ts = topitem->child(j,3)->text(); + schema::ChanType type = schema::MINOR_FLAG; + for (QHash::iterator it = channeltype.begin(); it!= channeltype.end(); ++it) { + if (it.value() == ts) { + type = it.key(); + break; + } + } + chan.setType(type); + chan.setLabel(topitem->child(j,4)->text()); + chan.setDescription(topitem->child(j,5)->text()); + } + } +} +void PreferencesDialog::saveWaveInfo() +{ + // Change focus to force save of any open editors.. + ui->waveSearch->setFocus(); + int toprows = waveModel->rowCount(); bool ok; @@ -920,58 +947,6 @@ bool PreferencesDialog::Save() chan.setDescription(topitem->child(j,6)->text()); } } - - toprows = chanModel->rowCount(); - - for (int i=0; i < toprows; i++) { - QStandardItem * topitem = chanModel->item(i,0); - - if (!topitem) continue; - int rows = topitem->rowCount(); - for (int j=0; j< rows; ++j) { - QStandardItem * item = topitem->child(j, 0); - if (!item) continue; - - ChannelID id = item->data(Qt::UserRole).toUInt(&ok); - schema::Channel & chan = schema::channel[id]; - if (chan.isNull()) continue; - chan.setEnabled(item->checkState() == Qt::Checked ? true : false); - chan.setFullname(item->text()); - chan.setDefaultColor(QColor(topitem->child(j,1)->data(Qt::UserRole).toUInt())); - QString ts = topitem->child(j,2)->text(); - schema::ChanType type = schema::MINOR_FLAG; - for (QHash::iterator it = channeltype.begin(); it!= channeltype.end(); ++it) { - if (it.value() == ts) { - type = it.key(); - break; - } - } - chan.setType(type); - chan.setShowInOverview(topitem->child(j,3)->checkState() == Qt::Checked); - chan.setLabel(topitem->child(j,4)->text()); - chan.setDescription(topitem->child(j,5)->text()); - } - } - - //qDebug() << "TODO: Save channels.xml to update channel data"; - - PREF.Save(); - p_profile->Save(); - - if (recalc_events) { - // send a signal instead? - mainwin->reprocessEvents(needs_restart); - } else if (needs_restart) { - p_profile->removeLock(); - mainwin->RestartApplication(); - } else { - mainwin->getDaily()->LoadDate(mainwin->getDaily()->getDate()); - // Save early.. just in case.. - mainwin->getDaily()->graphView()->SaveSettings("Daily"); - mainwin->getOverview()->graphView()->SaveSettings("Overview"); - } - - return true; } void PreferencesDialog::on_combineSlider_valueChanged(int position) @@ -1001,12 +976,6 @@ void PreferencesDialog::on_checkForUpdatesButton_clicked() mainwin->CheckForUpdates(); } -void PreferencesDialog::on_graphView_activated(const QModelIndex &index) -{ - QString a = index.data().toString(); - qDebug() << "Could do something here with" << a; -} - MySortFilterProxyModel::MySortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { @@ -1025,7 +994,7 @@ bool MySortFilterProxyModel::filterAcceptsRow(int source_row, return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } - +// Might still be useful.. //void PreferencesDialog::on_resetGraphButton_clicked() //{ // QString title = tr("Confirmation"); @@ -1067,38 +1036,6 @@ bool MySortFilterProxyModel::filterAcceptsRow(int source_row, // ui->graphView->update(); //} -/*void PreferencesDialog::on_genOpWidget_itemActivated(QListWidgetItem *item) -{ - item->setCheckState(item->checkState()==Qt::Checked ? Qt::Unchecked : Qt::Checked); -} */ - -void PreferencesDialog::on_maskTypeCombo_activated(int index) -{ - if (index < num_masks) { - QTableWidgetItem *item; - - for (int i = 0; i < 5; i++) { - MaskProfile &mp = masks[index]; - - item = ui->leakProfile->item(i, 0); - QString val = QString::number(mp.pflow[i][0], 'f', 2); - - if (!item) { - item = new QTableWidgetItem(val); - ui->leakProfile->setItem(i, 0, item); - } else { item->setText(val); } - - val = QString::number(mp.pflow[i][1], 'f', 2); - item = ui->leakProfile->item(i, 1); - - if (!item) { - item = new QTableWidgetItem(val); - ui->leakProfile->setItem(i, 1, item); - } else { item->setText(val); } - } - } -} - void PreferencesDialog::on_createSDBackups_toggled(bool checked) { if (profile->session->backupCardData() && !checked) { @@ -1152,6 +1089,7 @@ void PreferencesDialog::on_resetChannelDefaults_clicked() { if (QMessageBox::question(this, STR_MessageBox_Warning, QObject::tr("Are you sure you want to reset all your channel colors and settings to defaults?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { schema::resetChannels(); + saveWaveInfo(); InitChanInfo(); } } @@ -1197,3 +1135,33 @@ void PreferencesDialog::on_waveSearch_textChanged(const QString &arg1) { waveFilterModel->setFilterFixedString(arg1); } + +void PreferencesDialog::on_resetWaveformChannels_clicked() +{ + if (QMessageBox::question(this, STR_MessageBox_Warning, QObject::tr("Are you sure you want to reset all your waveform channel colors and settings to defaults?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { + schema::resetChannels(); + saveChanInfo(); // reset clears EVERYTHING, so have to put these back in case they cancel. + InitWaveInfo(); + } +} + +void PreferencesDialog::on_waveView_doubleClicked(const QModelIndex &index) +{ + if (index.column() == 1) { + QColorDialog a; + + if (!(index.flags() & Qt::ItemIsEnabled)) return; + quint32 color = index.data(Qt::UserRole).toUInt(); + + a.setCurrentColor(QColor((QRgb)color)); + + if (a.exec() == QColorDialog::Accepted) { + quint32 cv = a.currentColor().rgba(); + + waveFilterModel->setData(index, cv, Qt::UserRole); + waveFilterModel->setData(index, a.currentColor(), Qt::BackgroundRole); + + } + + } +} diff --git a/sleepyhead/preferencesdialog.h b/sleepyhead/preferencesdialog.h index e852f09d..51bf0c23 100644 --- a/sleepyhead/preferencesdialog.h +++ b/sleepyhead/preferencesdialog.h @@ -37,15 +37,6 @@ class MySortFilterProxyModel: public QSortFilterProxyModel }; -/*! \struct MaskProfile - \brief This in still a work in progress, and may be used in Unintentional leaks calculations. - */ -struct MaskProfile { - MaskType type; - QString name; - EventDataType pflow[5][2]; -}; - /*! \class PreferencesDialog \brief SleepyHead's Main Preferences Window @@ -72,12 +63,8 @@ class PreferencesDialog : public QDialog void on_checkForUpdatesButton_clicked(); - void on_graphView_activated(const QModelIndex &index); - //void on_genOpWidget_itemActivated(QListWidgetItem *item); - void on_maskTypeCombo_activated(int index); - void on_createSDBackups_toggled(bool checked); void on_okButton_clicked(); @@ -96,10 +83,17 @@ class PreferencesDialog : public QDialog void on_waveSearch_textChanged(const QString &arg1); + void on_resetWaveformChannels_clicked(); + + void on_waveView_doubleClicked(const QModelIndex &index); + private: void InitChanInfo(); void InitWaveInfo(); + void saveChanInfo(); + void saveWaveInfo(); + QHash toplevel; QHash machlevel; diff --git a/sleepyhead/preferencesdialog.ui b/sleepyhead/preferencesdialog.ui index e790bed7..f77e3e24 100644 --- a/sleepyhead/preferencesdialog.ui +++ b/sleepyhead/preferencesdialog.ui @@ -9,7 +9,7 @@ 0 0 - 721 + 809 610 @@ -51,7 +51,7 @@ - 2 + 1 @@ -76,7 +76,7 @@ - Session Settings + Session Splitting Settings @@ -94,6 +94,19 @@ 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Attention ResMed users:</span> There are some pitfalls you may want to consider before trying to split sessions away from ResMed's 12:00 noon day model, click <a href="http://sleepyhead.sf.net/wiki/index.php/Resmed_Session_Splitting">here</a> for more information.</p></body></html> + + + true + + + true + + + @@ -142,7 +155,7 @@ 10 - 60 + 30 0 @@ -160,7 +173,7 @@ QSlider::TicksAbove - 30 + 10 @@ -277,12 +290,28 @@ p, li { white-space: pre-wrap; } - + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 10 + 20 + + + + + + - Keep session data in memory to speed up revisiting days. + <html><head/><body><p><span style=" font-weight:600;">This setting should be used with caution...</span> Switching it off comes with consequences involving accuracy of summary only days, as certain calculations only work properly provided summary only sessions that came from individual day records are kept together. </p><p><span style=" font-weight:600;">ResMed users:</span> Just because it seems natural to you and I that the 12 noon session restart should be in the previous day, does not mean ResMed's data agrees with us. The STF.edf summary index format has serious weaknesses that make doing this not a good idea.</p><p>This option exists to pacify those who don't care and want to see this &quot;fixed&quot; no matter the costs, but know it comes with a cost. If you keep your SD card in every night, and import at least once a week, and you won't see problems with this very often.</p></body></html> - Cache Session Data (uses more system memory) + Don't Split Summary Days (Warning: read the tooltip!) @@ -301,7 +330,112 @@ p, li { white-space: pre-wrap; } - + + + + + + + + 0 + 0 + + + + Session Storage Options + + + + 4 + + + 9 + + + 4 + + + 0 + + + 0 + + + 4 + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-style:italic;">Changing SD Backup compression options doesn't automatically recompress backup data. </span></p></body></html> + + + true + + + + + + + Compress ResMed (EDF) backups to save disk space. +Backed up EDF files are stored in the .gz format, +which is common on Mac & Linux platforms.. + +SleepyHead can import from this compressed backup directory natively.. +To use with ResScan will require the .gz files to be uncompressed first.. + + + Compress SD Card Backups (slower first import, but makes backups smaller) + + + + + + + + true + + + + The following options affect the amount of disk space SleepyHead uses, and all have an effect on how long import takes. + + + true + + + + + + + This makes SleepyHead's data take around half as much space. +But it makes import and day changing take longer.. +If you've got a new computer with a small solid state disk, this is a good option. + + + Compress Session Data (makes SleepyHead data smaller, but day changing slower.) + + + + + + + This maintains a backup of SD-card data for ResMed machines, + +ResMed machines delete high resolution data older than 7 days, +and graph data older than 30 days.. + +SleepyHead can keep a copy of this data if you ever need to reinstall. +(Highly recomended, unless your short on disk space or don't care about the graph data) + + + Create SD Card Backups during Import (Turn this off at your own peril!) + + + + 0 @@ -355,16 +489,6 @@ p, li { white-space: pre-wrap; } - - - - Summary only data is more accurate for ResMed users if this is left on. - - - Don't Split Summary Days - - - @@ -384,104 +508,48 @@ p, li { white-space: pre-wrap; } - - - - 0 - 0 - - + - Session Storage Options + Memory and Startup Options - - - 4 - - - 9 - - - 4 - - - 0 - - - 0 - - - 4 - - - - - This maintains a backup of SD-card data for ResMed machines, - -ResMed machines delete high resolution data older than 7 days, -and graph data older than 30 days.. - -SleepyHead can keep a copy of this data if you ever need to reinstall. -(Highly recomended, unless your short on disk space or don't care about the graph data) - - - Create SD Card Backups during Import (Turn this off at your own peril!) - - - - - - - This makes SleepyHead's data take around half as much space. -But it makes import and day changing take longer.. -If you've got a new computer with a small solid state disk, this is a good option. - - - Compress Session Data (makes SleepyHead data smaller, but day changing slower.) - - - - - - - Compress ResMed (EDF) backups to save disk space. -Backed up EDF files are stored in the .gz format, -which is common on Mac & Linux platforms.. - -SleepyHead can import from this compressed backup directory natively.. -To use with ResScan will require the .gz files to be uncompressed first.. - - - Compress SD Card Backups (slower first import, but makes backups smaller) - - - + - - - - true - + + + <html><head/><body><p>Makes starting SleepyHead a bit slower, by pre-loading all the summary data in advance, which speeds up overview browsing and a few other calculations later on. </p><p>If you have a large amount of data, it might be worth keeping this switched off, but if you typically like to view <span style=" font-style:italic;">everything</span> in overview, all the summary data still has to be loaded anyway. </p><p>Note this setting doesn't affect waveform and event data, which is always demand loaded as needed.</p></body></html> - The following options affect the amount of disk space SleepyHead uses, and all have an effect on how long import takes. - - - true + Pre-Load all summary data at startup - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-style:italic;">Changing SD Backup compression options doesn't automatically recompress backup data. </span></p></body></html> + + + + <html><head/><body><p>This setting keeps waveform and event data in memory after use to speed up revisiting days.</p><p>This is not really a necessary option, as your operating system caches previously used files too.</p><p>Recommendation is to leave it switched off, unless your computer has a ton of memory.</p></body></html> - - true + + Keep Waveform/Event data in memory + + + + + + + <html><head/><body><p>Cuts down on any unimportant confirmation dialogs during import.</p></body></html> + + + Import without asking for confirmation + + + + + + + Bypass the login screen and load the most recent User Profile + + + Skip Login Screen @@ -507,10 +575,7 @@ p, li { white-space: pre-wrap; } &CPAP - - - 4 - + 4 @@ -523,60 +588,325 @@ p, li { white-space: pre-wrap; } 4 - + + 4 + + - + 0 0 - CPAP Mask Information - - - false + General CPAP and Related Settings - - 0 - - - 2 - - - 0 - - - 0 - - - 4 - - - 2 - - - - - Mask Type + + + + + 0 + 0 + + + + Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. + + + hours + + + 1 + + + 8.000000000000000 + + + 4.000000000000000 - - + + + + + 0 + 0 + + - Generic mask type. Select the one that's closest to your mask. + User definable threshold considered large leak + + + L/min + + + 1 + + + + + + + Show flags for machine detected events that haven't been identified yet. + + + Enable Unknown Events Channels + + + + + + + + 0 + 0 + - + AHI + + + RDI + + + + + + + + AHI/Hour Graph Time Window + + + + + + + Preferred major event index + + + + + + + Compliance defined as + + + + + + + + 0 + 0 + + + + Adjusts the amount of data considered for each point in the AHI/Hour graph. +Defaults to 60 minutes.. Highly recommend it's left at this value. + + + minutes + + + 5 + + + 999 + + + 60 + + + + + + + + 50 + false + + + + Whether to show the leak redline in the leak graph + + + Flag leaks over threshold + + + + + + + Reset the counter to zero at beginning of each (time) window. + + + Zero Reset + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + CPAP Clock Drift + + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + -59 + + + 59 + + + 0 + + + + + + + -99 + + + + + + + Seconds + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Note: This is not intended for timezone corrections! Make sure your operating system clock and timezone is set correctly.</p></body></html> + + + true + + + + + + + -9999 + + + 9999 + + + + + + + Minutes + + + + + + + Hours + - + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 0 + 0 + + + + Changes to the following settings needs a restart, but not a recalc. + + + Preferred Calculation Methods + + + + + + Upper Percentile + + + + + + + Maximum Calcs + + + + + 0 @@ -584,118 +914,177 @@ p, li { white-space: pre-wrap; } - Description + Middle Calculations - - + + - The name of your mask, or at least the name you call it. + Median is recommended for ResMed users. + + + + Median + + + + + Weighted Average + + + + + Normal Average + + + + + + + + For consistancy, ResMed users should use 95% here, +as this is the only value available on summary-only days. + + + % + + + 1 - - + + 0 0 + + + 140 + 0 + + - Method of unintentional leaks calculation if not provided by your machine. -Note: Statistical Model is experimental. + ResMed users probably should use 99th Percentile for visual consistency. - Mask Profile + True Maximum - Statistical Model + 99% Percentile - - - - - 0 - 0 - + + + + + + + + 0 + 0 + + + + Enable/disable experimental event flagging enhancements. +It allows detecting borderline events, and some the machine missed. +This option must be enabled before import, otherwise a purge is required. + + + Custom CPAP User Event Flagging + + + false + + + true + + + + 4 + + + 9 + + + 4 + + + 4 + + + 4 + + + + + s - - Leak calcs + + 10.000000000000000 - - - - - 0 - 0 - + + + + % - - Started Using + + 10.000000000000000 - - - - - 0 - 0 - - + + - The date you started using this mask - - - true - - - - - - - - 75 - true - + This experimental option attempts to use SleepyHead's event flagging system to improve machine detected event positioning. - Leak Profile + Resync Machine Detected Events (Experimental) - - - - true + + + + #2 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - false - - - - Pressure - - - - - Leak - - - - + + + + + 0 + 0 + + + + Flow Restriction + + + + + + + + 0 + 0 + + true @@ -706,603 +1095,92 @@ Note: Statistical Model is experimental. <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600; font-style:normal;">Note: </span>Leak profiles currently does not work yet..</p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + 0 + 0 + + + + Percentage of restriction in airflow from the median value. +A value of 20% works well for detecting apneas. + + + % + + + 10.000000000000000 + + + + + + + Show in Event Breakdown Piechart + + + + + + + Duration of airflow restriction + + + s + + + 1.000000000000000 + + + 10.000000000000000 + + + + + + + #1 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Allow duplicates near machine events. + + + + + + + + 0 + 0 + + + + Event Duration - - - - Qt::Vertical - - - - - - - - - Enable/disable experimental event flagging enhancements. -It allows detecting borderline events, and some the machine missed. -This option must be enabled before import, otherwise a purge is required. - - - Custom User Event Flagging - - - false - - - true - - - - 4 - - - 9 - - - 4 - - - 4 - - - 4 - - - - - % - - - 10.000000000000000 - - - - - - - Show in Event Breakdown Piechart - - - - - - - #1 - - - - - - - #2 - - - - - - - - 0 - 0 - - - - Flow Restriction - - - - - - - s - - - 10.000000000000000 - - - - - - - Event Duration - - - - - - - Duration of airflow restriction - - - s - - - 1.000000000000000 - - - 10.000000000000000 - - - - - - - - 0 - 0 - - - - Percentage of restriction in airflow from the median value. -A value of 20% works well for detecting apneas. - - - % - - - 10.000000000000000 - - - - - - - Allow duplicates near machine events. - - - - - - - - 0 - 0 - - - - - true - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:italic;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom flagging is an experimental method of detecting events missed by the machine. They are <span style=" text-decoration: underline;">not</span> included in AHI.</p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - This experimental option attempts to use SleepyHead's event flagging system to improve machine detected event positioning. - - - Resync Machine Detected Events (Experimental) - - - - - - - - - - - 50 - false - - - - Whether to show the leak redline in the leak graph - - - Show Leak Redline - - - - - - - User definable threshold considered large leak - - - L/min - - - 1 - - - - - - - AHI/Hour Graph Settings - - - false - - - false - - - false - - - - 4 - - - 4 - - - 4 - - - 4 - - - 4 - - - - - - 0 - 0 - - - - Window - - - - - - - Adjusts the amount of data considered for each point in the AHI/Hour graph. -Defaults to 60 minutes.. Highly recommend it's left at this value. - - - minutes - - - 5 - - - 999 - - - 60 - - - - - - - Reset the counter to zero at beginning of each (time) window. - - - Zero Reset - - - - - - - - - - - 50 - false - - - - CPAP Clock Drift - - - - - - - Don't touch this unless you know your CPAP clock is out. -Try to sync it to your PC's clock (which should be synced to a timeserver) - - - seconds - - - -7200 - - - 7200 - - - 0 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Don't show any compliance information - - - Show Compliance - - - false - - - true - - - - 4 - - - 4 - - - 4 - - - 4 - - - 4 - - - - - - 0 - 0 - - - - Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. - - - hours - - - 1 - - - 8.000000000000000 - - - 4.000000000000000 - - - - - - - - 0 - 0 - - - - as over - - - - - - - of usage per night - - - - - - - - - - - 50 - false - - - - Shows Respiratory Disturbance Index instead of Apnea/Hypopnea Index (RDI=AHI + RERA) - - - Use RDI instead of AHI (PRS1 only) - - - - - - - - - - Events - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - Search - - - - - - - - - - - - - - - - - - 0 - 0 - - - - Reset &Defaults - - - - - - - - 0 - 0 - - - - <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Waveforms - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - Search - - - - - - - - - - - - - - - - - - 0 - 0 - - - - Reset &Defaults - - - - - - - - 0 - 0 - - - - <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - @@ -1448,27 +1326,28 @@ Try to sync it to your PC's clock (which should be synced to a timeserver)Other oximetry options - - - - Flag SPO2 Desaturations Below - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + % - - - - Discard segments under - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + bpm + + + bpm + + + + Small chunks of oximetry data under this amount will be discarded. @@ -1481,14 +1360,27 @@ Try to sync it to your PC's clock (which should be synced to a timeserver) - - - - % + + + + Flag SPO2 Desaturations Below + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + + + Discard segments under + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + Flag Pulse Rate Above @@ -1498,7 +1390,7 @@ Try to sync it to your PC's clock (which should be synced to a timeserver) - + Flag Pulse Rate Below @@ -1508,17 +1400,13 @@ Try to sync it to your PC's clock (which should be synced to a timeserver) - - - - bpm + + + + If your oximeter supports it, SleepyHead will attempt to set your Oximeters clock from your computers time. - - - - - - bpm + + Update Oximeter Clock during import @@ -1604,16 +1492,6 @@ Try to sync it to your PC's clock (which should be synced to a timeserver) - - - - If your oximeter supports it, SleepyHead will attempt to set your Oximeters clock from your computers time. - - - Update Oximeter Clock during import - - - @@ -1644,6 +1522,150 @@ p, li { white-space: pre-wrap; } + + + Events + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Search + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Reset &Defaults + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Waveforms + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Search + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Reset &Defaults + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Warning: </span>Just because you can, does not mean it's good practice.</p></body></html> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + &General @@ -1664,134 +1686,8 @@ p, li { white-space: pre-wrap; } 4 - - - - Qt::Horizontal - - - - - - - Changes to the following settings needs a restart, but not a recalc. - - - Preferred Calculation Methods - - - - 0 - - - 4 - - - 0 - - - 0 - - - 4 - - - - - - 0 - 0 - - - - Middle Calculations - - - - - - - Upper Percentile - - - - - - - For consistancy, ResMed users should use 95% here, -as this is the only value available on summary-only days. - - - % - - - 1 - - - - - - - Median is recommended for ResMed users. - - - - Median - - - - - Weighted Average - - - - - Normal Average - - - - - - - - - 0 - 0 - - - - - 140 - 0 - - - - ResMed users probably should use 99th Percentile for visual consistency. - - - - True Maximum - - - - - 99% Percentile - - - - - - - - Maximum Calcs - - - - - - @@ -1813,26 +1709,6 @@ as this is the only value available on summary-only days. 4 - - - - Show flags for machine detected events that haven't been identified yet. - - - Show Unknown Flags - - - - - - - Bypass the login screen and load the most recent User Profile - - - Skip Login Screen - - - @@ -1844,23 +1720,6 @@ Mainly affects the importer. - - - - Daily view navigation buttons will skip over days without data records - - - Skip over Empty Days - - - - - - - Import without nagging - - - @@ -2480,6 +2339,16 @@ this application to be unstable with this feature enabled. + + + + Daily view navigation buttons will skip over days without data records + + + Skip over Empty Days + + + @@ -2505,7 +2374,7 @@ this application to be unstable with this feature enabled. - Application Fonts + Fonts (Application wide settings) false