From 272d630c9ee96689d5aad18da44f56d02b38dfe4 Mon Sep 17 00:00:00 2001 From: LoudSnorer Date: Sun, 17 Apr 2022 19:34:42 -0400 Subject: [PATCH 1/6] Add error message via a tooltip when invalid date is entered ifor Overview date range --- oscar/Graphs/gGraphView.cpp | 110 ++++++++++++++++++++++--- oscar/Graphs/gGraphView.h | 33 +++++++- oscar/overview.cpp | 157 ++++++++++++++++++++++++++---------- oscar/overview.h | 32 +++++++- 4 files changed, 273 insertions(+), 59 deletions(-) diff --git a/oscar/Graphs/gGraphView.cpp b/oscar/Graphs/gGraphView.cpp index 3e25a339..6070d49e 100644 --- a/oscar/Graphs/gGraphView.cpp +++ b/oscar/Graphs/gGraphView.cpp @@ -11,10 +11,12 @@ #ifdef DEBUG_FUNCTIONS #include #define DEBUG qDebug()<isActive()) { timer->stop(); } - timer->setSingleShot(true); timer->start(timeout); - m_invalidate = true; } void gToolTip::cancel() @@ -151,17 +151,14 @@ void gToolTip::cancel() timer->stop(); } -void gToolTip::paint(QPainter &painter) //actually paints it. +QRect gToolTip::calculateRect(QPainter &painter) { - if (!m_visible) { return; } - int x = m_pos.x(); int y = m_pos.y(); QRect rect(x, y, 0, 0); - painter.setFont(*defaultfont); - + painter.setFont(*m_font); rect = painter.boundingRect(rect, Qt::AlignCenter, m_text); int w = rect.width() + m_spacer * 2; @@ -201,19 +198,25 @@ void gToolTip::paint(QPainter &painter) //actually paints it. rect.setTop(rect.top()-bot); rect.setBottom(m_graphview->height()); } + return rect; +} +void gToolTip::paint(QPainter &painter) //actually paints it. +{ + if (!m_visible) { return; } + QRect a_rect=calculateRect(painter); QBrush brush(QColor(255, 255, 128, 230)); brush.setStyle(Qt::SolidPattern); painter.setBrush(brush); painter.setPen(QColor(0, 0, 0, 255)); - painter.drawRoundedRect(rect, 5, 5); + painter.drawRoundedRect(a_rect, 5, 5); painter.setBrush(Qt::black); - painter.setFont(*defaultfont); + painter.setFont(*m_font); - painter.drawText(rect, Qt::AlignCenter, m_text); + painter.drawText(a_rect, Qt::AlignCenter, m_text); } void gToolTip::timerDone() @@ -223,6 +226,88 @@ void gToolTip::timerDone() m_graphview->resetMouse(); } +/* Parent tool tip + Allow the parent (overview or daily) to add tooltip or short messages to the user. + The basic problem is that the parent does not know the current dimensions of the graph view. + the parent does have knowledge of the location of fixed widgets which makes it possible to + locate tool tips in an appropiate location. +*/ +gParentToolTip::gParentToolTip(gGraphView *graphview) + : gToolTip(graphview) { + m_parent_visible=false; +} + +gParentToolTip::~gParentToolTip() { +} + +void gParentToolTip::display(gGraphView* gv,QString text, int verticalOffset, int alignOffset, ToolTipAlignment align , int timeout ,QFont *font ) { + m_text=text; + m_verticalOffset=verticalOffset; + m_alignOffset=alignOffset; + m_alignment=align; + m_timeout=timeout; + m_font=font; + m_parent_visible=true; + gv->timedRedraw(0); +}; + + +QRect gParentToolTip::calculateRect(QPainter& painter ) { + QRect rect(0, 0, 0, 0); + painter.setFont(*m_font); + rect = painter.boundingRect(rect, m_alignment, m_text); + + // update space arround text + int space=2*m_spacer; + rect.setHeight(space+rect.height()); + rect.setWidth(space+rect.width()); + + rect.moveTo(m_alignOffset,m_height-(m_verticalOffset+rect.height())); + + // move rect accounding to alignment. default is left. + + if (m_alignment == TT_AlignRight) { + // move rect left by width of rect. if <0 use 0; + rect.moveLeft(rect.left()-rect.width()); + } else if (m_alignment == TT_AlignCenter) { + // left by 1/2 width of rect. if < 0 then use 0 + rect.moveLeft(rect.left()-rect.width()/2); + } + + if (rect.top()<0) {rect.setTop(0);}; + if (rect.left()<0) {rect.setLeft(0);}; + + return rect; +} + +void gParentToolTip::paint(QPainter &painter,int width,int height) { + if (!m_parent_visible) {return ;}; + m_width=width; + m_height=height; + gToolTip::display(m_text, 0, 0,m_alignment, m_timeout); + gToolTip::paint(painter); +}; + +void gParentToolTip::timerDone() { + gToolTip::timerDone(); + if (m_parent_visible) { + m_graphview->timedRedraw(0); + } + m_parent_visible=false; +}; + +void gParentToolTip::cancel() { + gToolTip::cancel(); + m_parent_visible=false; +}; + +bool gParentToolTip::visible() { + return gToolTip::visible() && m_parent_visible; +}; + + + + #ifdef ENABLE_THREADED_DRAWING gThread::gThread(gGraphView *g) @@ -359,6 +444,7 @@ gGraphView::gGraphView(QWidget *parent, gGraphView *shared, QWidget *caller) masterlock = new QSemaphore(m_idealthreads); #endif m_tooltip = new gToolTip(this); + m_parent_tooltip = new gParentToolTip(this); /*for (int i=0;iusePixmapCaching() ? DrawTextQueCached(painter) :DrawTextQue(painter); m_tooltip->paint(painter); + m_parent_tooltip->paint(painter,width(), height() ); #ifdef DEBUG_EFFICIENCY const int rs = 20; diff --git a/oscar/Graphs/gGraphView.h b/oscar/Graphs/gGraphView.h index 0dd6226e..8952ee31 100644 --- a/oscar/Graphs/gGraphView.h +++ b/oscar/Graphs/gGraphView.h @@ -240,10 +240,12 @@ class gToolTip : public QObject virtual void paint(QPainter &paint); //actually paints it. //! \brief Close the tooltip early. - void cancel(); + virtual void cancel(); //! \brief Returns true if the tooltip is currently visible - bool visible() { return m_visible; } + virtual bool visible() { return m_visible; } + + virtual QRect calculateRect(QPainter &painter); protected: gGraphView *m_graphview; @@ -254,13 +256,35 @@ class gToolTip : public QObject bool m_visible; int m_spacer; QImage m_image; - bool m_invalidate; ToolTipAlignment m_alignment; + QFont* m_font=defaultfont; protected slots: //! \brief Timeout to hide tooltip, and redraw without it. - void timerDone(); + virtual void timerDone(); +}; + +class gParentToolTip : public gToolTip +{ + Q_OBJECT + public: + gParentToolTip(gGraphView *graphview); + ~gParentToolTip(); + virtual void display(gGraphView* gv,QString text,int verticalOffset, int alignOffset , ToolTipAlignment align = TT_AlignCenter, int timeout = 0,QFont *font = defaultfont) ; + virtual void cancel(); + virtual bool visible(); + virtual QRect calculateRect(QPainter &painter); + virtual void paint(QPainter &paint,int width,int height) ; + private: + int m_verticalOffset; + int m_alignOffset; + int m_width; + int m_height; + bool m_parent_visible; + int m_timeout; + protected slots: + virtual void timerDone(); }; struct SelectionHistoryItem { @@ -425,6 +449,7 @@ class gGraphView gGraph *m_selected_graph; gToolTip *m_tooltip; + gParentToolTip *m_parent_tooltip; QTimer *timer; //! \brief Add the Text information to the Text Drawing Queue (called by gGraphs renderText method) diff --git a/oscar/overview.cpp b/oscar/overview.cpp index 7e0e5c09..a77cb967 100644 --- a/oscar/overview.cpp +++ b/oscar/overview.cpp @@ -7,16 +7,19 @@ * License. See the file COPYING in the main directory of the source code * for more details. */ +#define NEWSTUFF + #define xDEBUG_FUNCTIONS #ifdef DEBUG_FUNCTIONS #include #define DEBUGQ qDebug() #define DEBUGL qDebug()<LoadSettings("Overview"); //no trans GraphView->setEmptyImage(QPixmap(":/icons/logo-md.png")); + dateErrorDisplay = new DateErrorDisplay(this); connect(ui->dateStart->calendarWidget(), SIGNAL(currentPageChanged(int, int)), this, SLOT(dateStart_currentPageChanged(int, int))); connect(ui->dateEnd->calendarWidget(), SIGNAL(currentPageChanged(int, int)), this, SLOT(dateEnd_currentPageChanged(int, int))); @@ -196,6 +200,11 @@ Overview::~Overview() GraphView->SaveSettings("Overview");//no trans delete ui; + delete dateErrorDisplay; + delete icon_on ; + delete icon_off ; + delete icon_up_down ; + delete icon_warning ; } void Overview::ResetFont() @@ -235,12 +244,10 @@ void Overview::on_summaryChartEmpty(gSummaryChart*sc,qint64 firstI,qint64 lastI, if (empty) { // on next range change allow empty flag to be recalculated chartsEmpty.insert(sc,graph); - //DEBUGF << graph->name() << "Chart is Empty" << "Range" << convertTimeToDate(firstI) << convertTimeToDate(lastI); } else { // The chart has some entry with data. chartsEmpty.remove(sc); updateGraphCombo(); - //DEBUGF << graph->name() << "Chart is enabled with range:" << convertTimeToDate(firstI) << "==>" << convertTimeToDate(lastI) ; } Q_UNUSED(firstI); Q_UNUSED(lastI); @@ -322,11 +329,8 @@ void Overview::CreateAllGraphs() { G->AddLayer(sc); chartsToBeMonitored.insert(sc,G); } - if (sc== nullptr) { - //DEBUGF << "Channel" << name << "type" << chan->type() << "machine type" << chan->machtype() << "IGNORED"; - } else { + if (sc!= nullptr) { sc ->reCalculate(); - //DEBUGF << "Channel" << name << "type" << chan->type() << "machine type" << chan->machtype() << OO(Empty,sc->isEmpty()); } } // if showInOverview() } // for chit @@ -549,12 +553,6 @@ void Overview::on_XBoundsChanged(qint64 start,qint64 end) // Only occurs when custom mode is switched to/from a latest mode. custom mode to/from last week. // All other displays expand the existing range. // reset all empty flags to not empty - if (displayStartDate>maxRangeEndDate) { - //DEBUGF << "Two ranges" O(displayStartDate) <<">" << O(maxRangeEndDate); - } - if (minRangeStartDate>displayEndDate) { - //DEBUGF << "Two ranges" O(minRangeStartDate) <<">" << O(displayEndDate); - } largerRange=true; chartsEmpty = QHash( chartsToBeMonitored ); minRangeStartDate = displayStartDate; @@ -562,24 +560,14 @@ void Overview::on_XBoundsChanged(qint64 start,qint64 end) } else { // new range overlaps with old range if (displayStartDate" <maxRangeEndDate) { - //DEBUGF << "End Higher" <maxRangeEndDate) { - //DEBUGF << "ERROR" <error(false,date); + return; + } QDate d2(date); if (customMode) { p_profile->general->setCustomOverviewRangeEnd(d2); @@ -636,6 +628,11 @@ void Overview::on_dateEnd_dateChanged(const QDate &date) void Overview::on_dateStart_dateChanged(const QDate &date) { + if (date>uiEndDate) { + // change date back to last date. + dateErrorDisplay->error(true,date); + return; + } QDate d1(date); if (customMode) { p_profile->general->setCustomOverviewRangeStart(d1); @@ -654,6 +651,7 @@ void Overview::on_zoomButton_clicked() on_rangeCombo_activated(p_profile->general->lastOverviewRange()); // type of range in last use } + void Overview::ResetGraphLayout() { GraphView->resetLayout(); @@ -732,7 +730,7 @@ void Overview::on_rangeCombo_activated(int index) // Ensure that all summary files are available and update version numbers if required int size = start.daysTo(end); - qDebug() << "Overview range combo from" << start << "to" << end << "with" << size << "days"; + // qDebug() << "Overview range combo from" << start << "to" << end << "with" << size << "days"; QDate dateback = end; CProgressBar * progress = new CProgressBar (QObject::tr("Loading summaries"), mainwin, size); for (int i=1; i < size; ++i) { @@ -754,12 +752,101 @@ void Overview::on_rangeCombo_activated(int index) setRange(start, end); } + +DateErrorDisplay::DateErrorDisplay (Overview* overview) + : m_overview(overview) +{ + m_visible=false; + m_timer = new QTimer(); + connect(m_timer, SIGNAL(timeout()),this, SLOT(timerDone())); +}; + +DateErrorDisplay::~DateErrorDisplay() { + disconnect(m_timer, SIGNAL(timeout()),this, SLOT(timerDone())); + delete m_timer; +}; + +void DateErrorDisplay::cancel() { + m_visible=false; + m_timer->stop(); +}; + +void DateErrorDisplay::timerDone() { + m_visible=false; + m_overview->resetUiDates(); + m_overview->graphView()->m_parent_tooltip->cancel(); +}; + +int Overview::calculatePixels(bool startDate,ToolTipAlignment& align) { + // Center error message over start and end dates combined. + // Other allignement were tested but this is the best for this problem. + Q_UNUSED(startDate); + int space=4; + align=TT_AlignCenter; + return ((4*space) + ui->label_3->width() + ui->rangeCombo->width() + ui->dateStartLabel->width() +ui->dateStart->width() ); +} + +void DateErrorDisplay::error(bool startDate,const QDate& dateEntered) { + m_startDate =m_overview->uiStartDate; + m_endDate =m_overview->uiEndDate; + ToolTipAlignment align=TT_AlignCenter; + + + QString dateFormatted =dateEntered.toString(m_overview->shortformat); + + QString txt = QString(tr("ERROR\nThe start date MUST be before the end date")); + txt.append("\n"); + if (startDate) { + txt.append(tr("The entered start date %1 is after the end date %2").arg(dateFormatted).arg(m_endDate.toString(m_overview->shortformat))); + txt.append(tr("\nHint: Change the end date first")); + } else { + txt.append(tr("The entered end date %1 ").arg(dateFormatted)); + txt.append(tr("is before the start date %1").arg(m_startDate.toString(m_overview->shortformat))); + txt.append(tr("\nHint: Change the start date first")); + } + + gGraphView* gv=m_overview->graphView(); + + int pixelsFromLeft = m_overview->calculatePixels(startDate,align); + int pixelsAboveBottom = 0; + int warningDurationMS =4000; + int resetUiDatesTimerDelayMS =1000; + //QFont* font=mediumfont; + QFont* font=defaultfont; + + gv->m_parent_tooltip->display(gv,txt,pixelsAboveBottom,pixelsFromLeft, align, warningDurationMS , font); + + m_timer->setInterval( resetUiDatesTimerDelayMS ); + m_timer->setSingleShot(true); + m_timer->start(); + +}; + +void Overview::resetUiDates() { + ui->dateStart->blockSignals(true); + ui->dateStart->setMinimumDate(p_profile->FirstDay()); // first and last dates for ANY machine type + ui->dateStart->setMaximumDate(p_profile->LastDay()); + ui->dateStart->setDate(uiStartDate); + ui->dateStart->blockSignals(false); + + ui->dateEnd->blockSignals(true); + ui->dateEnd->setMinimumDate(p_profile->FirstDay()); // first and last dates for ANY machine type + ui->dateEnd->setMaximumDate(p_profile->LastDay()); + ui->dateEnd->setDate(uiEndDate); + ui->dateEnd->blockSignals(false); +} + // Saves dates in UI, clicks zoom button, and updates combo box // 1. Updates the dates in the start / end date boxs // 2. optionally also changes display range for graphs. void Overview::setRange(QDate& start, QDate& end, bool updateGraphs/*zoom*/) { + if (start>end) { + // this is an ERROR and shold NEVER occur. + return; + } + // first setting of the date (setDate) will cause pageChanged to be executed. // The pageChanged processing requires access to the other ui's date. // so save them to memory before the first call. @@ -777,21 +864,7 @@ void Overview::setRange(QDate& start, QDate& end, bool updateGraphs/*zoom*/) samePage=nextSamePage; } - ui->dateEnd->blockSignals(true); - ui->dateStart->blockSignals(true); - - // Calling these methods for the first time trigger pageChange actions. - ui->dateEnd->setDate(end); - ui->dateEnd->setMinimumDate(start); - - ui->dateStart->setMinimumDate(p_profile->FirstDay()); // first and last dates for ANY machine type - ui->dateEnd->setMaximumDate(p_profile->LastDay()); - - ui->dateStart->setDate(start); - ui->dateStart->setMaximumDate(end); - - ui->dateEnd->blockSignals(false); - ui->dateStart->blockSignals(false); + resetUiDates(); if (updateGraphs) SetXBounds(uiStartDate,uiEndDate); updateGraphCombo(); } diff --git a/oscar/overview.h b/oscar/overview.h index 14e97d96..f01fa71d 100644 --- a/oscar/overview.h +++ b/oscar/overview.h @@ -16,6 +16,7 @@ #endif #include #include +#include #include "SleepLib/profiles.h" #include "Graphs/gGraphView.h" #include "Graphs/gSummaryChart.h" @@ -26,6 +27,27 @@ class Overview; } class Report; +class Overview; + +class DateErrorDisplay:QObject { + Q_OBJECT +public: + DateErrorDisplay (Overview* overview) ; + ~DateErrorDisplay() ; + bool visible() {return m_visible;}; + void cancel(); + void error(bool startDate,const QDate& date); +protected: + +private: + QTimer* m_timer; + bool m_visible=false; + Overview* m_overview; + QDate m_startDate; + QDate m_endDate; +private slots: + void timerDone(); +}; enum YTickerType { YT_Number, YT_Time, YT_Weight }; @@ -35,6 +57,7 @@ enum YTickerType { YT_Number, YT_Time, YT_Weight }; */ class Overview : public QWidget { + friend class DateErrorDisplay; Q_OBJECT public: @@ -103,8 +126,6 @@ class Overview : public QWidget //! \brief Updates the calendar highlighting when changing to a new month void dateEnd_currentPageChanged(int year, int month); - //void on_printDailyButton_clicked(); - void on_rangeCombo_activated(int index); void on_graphCombo_activated(int index); @@ -143,6 +164,10 @@ class Overview : public QWidget void disconnectgSummaryCharts() ; void SetXBounds(qint64 minx, qint64 maxx, short group = 0, bool refresh = true); void SetXBounds(QDate & start, QDate& end, short group =0 , bool refresh = true); + void resetUiDates(); + DateErrorDisplay* dateErrorDisplay; + int calculatePixels(bool startDate,ToolTipAlignment& align); + QString shortformat; // Start and of dates of the current graph display QDate displayStartDate; @@ -166,4 +191,7 @@ class Overview : public QWidget }; + + + #endif // OVERVIEW_H From b4e4832f5079746b631326893d8c6821d819caa9 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sun, 24 Apr 2022 14:52:31 -0400 Subject: [PATCH 2/6] Fix compilation failure introduced in 272d630. --- oscar/Graphs/gGraphView.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oscar/Graphs/gGraphView.h b/oscar/Graphs/gGraphView.h index 8952ee31..8f984c79 100644 --- a/oscar/Graphs/gGraphView.h +++ b/oscar/Graphs/gGraphView.h @@ -271,10 +271,12 @@ class gParentToolTip : public gToolTip public: gParentToolTip(gGraphView *graphview); ~gParentToolTip(); + using gToolTip::display; virtual void display(gGraphView* gv,QString text,int verticalOffset, int alignOffset , ToolTipAlignment align = TT_AlignCenter, int timeout = 0,QFont *font = defaultfont) ; virtual void cancel(); virtual bool visible(); virtual QRect calculateRect(QPainter &painter); + using gToolTip::paint; virtual void paint(QPainter &paint,int width,int height) ; private: int m_verticalOffset; From 184fe0d2ff34f9c5cc4072eaafa53b547b204ed9 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sun, 24 Apr 2022 17:02:25 -0400 Subject: [PATCH 3/6] Add 961TCA to the list of tested machines. In doing so, add PP, CA, and VS support to F5V2 generally, which so far is just the 960T. We still haven't encountered LL on an F5V2. --- Htmldocs/release_notes.html | 2 + oscar/SleepLib/loader_plugins/prs1_loader.cpp | 1 + oscar/SleepLib/loader_plugins/prs1_parser.cpp | 5 +- .../loader_plugins/prs1_parser_asv.cpp | 172 +++--------------- 4 files changed, 35 insertions(+), 145 deletions(-) diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html index 4b196123..5b48e3e3 100644 --- a/Htmldocs/release_notes.html +++ b/Htmldocs/release_notes.html @@ -18,8 +18,10 @@
  • [new] Additional Philips Respironics devices tested and fully supported:
    • BiPAP Auto (System One 60 Series) (761P)
    • +
    • BiPAP autoSV Advanced 30 (System One 60 Series) (961TCA)
  • +
  • [fix] Added support for pressure pulse, CA, and VS on BiPAP autoSV Advanced 30 (System One 60 Series) (960T).
  • Changes and fixes in OSCAR v1.3.5-alpha.2 diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 67196786..84c914b3 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -141,6 +141,7 @@ static const PRS1TestedModel s_PRS1TestedModels[] = { { "960P", 5, 1, "BiPAP autoSV Advanced (System One 60 Series)" }, { "961P", 5, 1, "BiPAP autoSV Advanced (System One 60 Series)" }, { "960T", 5, 2, "BiPAP autoSV Advanced 30 (System One 60 Series)" }, // omits "(System One 60 Series)" on official reports + { "961TCA", 5, 2, "BiPAP autoSV Advanced 30 (System One 60 Series)" }, { "900X110", 5, 3, "DreamStation BiPAP autoSV" }, { "900X120", 5, 3, "DreamStation BiPAP autoSV" }, { "900X150", 5, 3, "DreamStation BiPAP autoSV" }, diff --git a/oscar/SleepLib/loader_plugins/prs1_parser.cpp b/oscar/SleepLib/loader_plugins/prs1_parser.cpp index a9f2c3a5..47a1bebb 100644 --- a/oscar/SleepLib/loader_plugins/prs1_parser.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_parser.cpp @@ -713,12 +713,11 @@ void PRS1DataChunk::ParseHumidifierSetting60Series(unsigned char humid1, unsigne } else if (this->familyVersion == 2) { // F5V2 if (tubepresent) { - CHECK_VALUES(tubetemp, 0, 3); + // all tube temperatures seen if (tubetemp) { - CHECK_VALUE(tubehumidlevel, 1); + CHECK_VALUES(tubehumidlevel, 1, 3); } } - CHECK_VALUE(humidsystemone, false); CHECK_VALUE(humidclassic, false); } } diff --git a/oscar/SleepLib/loader_plugins/prs1_parser_asv.cpp b/oscar/SleepLib/loader_plugins/prs1_parser_asv.cpp index cb1e8c5a..76d1f54b 100644 --- a/oscar/SleepLib/loader_plugins/prs1_parser_asv.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_parser_asv.cpp @@ -10,11 +10,6 @@ #include "prs1_parser.h" #include "prs1_loader.h" -static QString hex(int i) -{ - return QString("0x") + QString::number(i, 16).toUpper(); -} - //******************************************************************************************** // MARK: - // MARK: 50 and 60 Series @@ -664,7 +659,7 @@ bool PRS1DataChunk::ParseEventsF5V1(void) elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; - case 0x0a: // Vibratory Snore, note this is 0x9 in F5V3 + case 0x0a: // Vibratory Snore, note this is 0xb in F5V2 and 0x9 in F5V3 // VS events are instantaneous flags with no duration, drawn on the official waveform. // The current thinking is that these are the snores that cause a change in auto-titrating // pressure. The snoring statistic above seems to be a total count. It's unclear whether @@ -672,7 +667,7 @@ bool PRS1DataChunk::ParseEventsF5V1(void) // no data bytes this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); break; - case 0x0b: // Periodic Breathing, note this is 0xa in F5V3 + case 0x0b: // Periodic Breathing, note this is 0xc in F5V2 and 0xa in F5V3 // PB events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); // confirmed to double in F5V0 elapsed = data[pos+2]; @@ -721,12 +716,13 @@ bool PRS1DataChunk::ParseEventsF5V1(void) const QVector ParsedEventsF5V2 = { PRS1EPAPSetEvent::TYPE, + PRS1PressurePulseEvent::TYPE, PRS1TimedBreathEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, - //PRS1ClearAirwayEvent::TYPE, // not yet seen + PRS1ClearAirwayEvent::TYPE, PRS1HypopneaEvent::TYPE, PRS1FlowLimitationEvent::TYPE, - //PRS1VibratorySnoreEvent::TYPE, // not yet seen + PRS1VibratorySnoreEvent::TYPE, PRS1PeriodicBreathingEvent::TYPE, //PRS1LargeLeakEvent::TYPE, // not yet seen PRS1IPAPAverageEvent::TYPE, @@ -751,7 +747,7 @@ bool PRS1DataChunk::ParseEventsF5V2(void) } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); - static const QMap event_sizes = { {0,4}, {1,2}, {3,4}, {8,3}, {9,4}, {0xa,3}, {0xb,5}, {0xc,5}, {0xd,5}, {0xe,0xd}, {0xf,5}, {0x10,5}, {0x11,2}, {0x12,6} }; + static const QMap event_sizes = { {0,4}, {1,2}, {8,3}, {9,4}, {0xa,3}, {0xb,2}, {0xc,5}, {0xd,5}, {0xe,0xd}, {0xf,5}, {0x10,5}, {0x11,2}, {0x12,6} }; if (chunk_size < 1) { // This does occasionally happen in F0V6. @@ -780,51 +776,20 @@ bool PRS1DataChunk::ParseEventsF5V2(void) } startpos = pos; if (code != 0 && code != 0x12) { // These two codes have no timestamp TODO: verify this applies to F5V012 - t += data[pos] /*| (data[pos+1] << 8)*/; // TODO: Is this really only 1 byte? - if (data[pos+1] != 0) qWarning() << this->sessionid << "nonzero time? byte" << hex(startpos); - CHECK_VALUE(data[pos+1], 0); + t += data[pos] | (data[pos+1] << 8); pos += 2; } switch (code) { -/* - case 0x00: // Unknown (ASV Pressure value) - DUMP_EVENT(); - // offset? - data0 = data[pos++]; - - if (!data[pos - 1]) { // WTH??? - data1 = data[pos++]; - } - - if (!data[pos - 1]) { - //data2 = data[pos++]; - pos++; - } - - break; - - case 0x01: // Unknown - DUMP_EVENT(); - this->AddEvent(new PRS1UnknownValueEvent(code, t, 0, 0.1F)); - break; -*/ + //case 0x00: // never seen on F5V2 + //case 0x01: // never seen on F5V2 case 0x02: // Pressure adjustment this->AddEvent(new PRS1EPAPSetEvent(t, data[pos++], GAIN)); break; -/* - case 0x03: // BIPAP Pressure - DUMP_EVENT(); - qDebug() << "0x03 Observed in ASV data!!????"; - - data0 = data[pos++]; - data1 = data[pos++]; - // data0/=10.0; - // data1/=10.0; - // session->AddEvent(new Event(t,CPAP_EAP, 0, data, 1)); - // session->AddEvent(new Event(t,CPAP_IAP, 0, &data1, 1)); - break; -*/ + case 0x03: // Pressure Pulse + duration = data[pos]; // TODO: is this a duration? + this->AddEvent(new PRS1PressurePulseEvent(t, duration)); + break; case 0x04: // Timed Breath // TB events have a duration in 0.1s, based on the review of pressure waveforms. // TODO: Ideally the starting time here would be adjusted here, but PRS1ParsedEvents @@ -839,15 +804,13 @@ bool PRS1DataChunk::ParseEventsF5V2(void) elapsed = data[pos]; this->AddEvent(new PRS1ObstructiveApneaEvent(t - elapsed, 0)); break; -/* - case 0x06: - DUMP_EVENT(); - //code=CPAP_ClearAirway; - data0 = data[pos++]; - this->AddEvent(new PRS1ClearAirwayEvent(t - data0, data0)); - break; -*/ - + case 0x06: // Clear Airway Apnea + // CA events are instantaneous flags with no duration: reviewing waveforms + // shows that the time elapsed between the flag and reporting often includes + // non-apnea breathing. + elapsed = data[pos]; + this->AddEvent(new PRS1ClearAirwayEvent(t - elapsed, 0)); + break; case 0x07: // Hypopnea // NOTE: No additional (unknown) first byte as in F5V3 0x07, but see below. // This seems closer to F5V3 0x0d or 0x0e. @@ -862,21 +825,7 @@ bool PRS1DataChunk::ParseEventsF5V2(void) elapsed = data[pos]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; -/* - case 0x09: // ASV Codes - DUMP_EVENT(); - / * - if (this->familyVersion<2) { - //code=CPAP_FlowLimit; - data0 = data[pos++]; - - this->AddEvent(new PRS1FlowLimitationEvent(t - data0, data0)); - } else { - * / - data0 = data[pos++]; - data1 = data[pos++]; - break; -*/ + //case 0x09: // never seen on F5V2 case 0x0a: // Flow Limitation, note this is 0x9 in F5V1 and 0x8 in F5V3 // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating flow limitations ourselves. Flow limitations aren't @@ -884,35 +833,21 @@ bool PRS1DataChunk::ParseEventsF5V2(void) elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; -/* - case 0x0b: // Cheyne Stokes - DUMP_EVENT(); - data0 = ((unsigned char *)data)[pos + 1] << 8 | ((unsigned char *)data)[pos]; - //data0*=2; - pos += 2; - data1 = ((unsigned char *)data)[pos]; //|data[pos+1] << 8 - pos += 1; - //tt-=delta; - this->AddEvent(new PRS1PeriodicBreathingEvent(t - data1, data0)); - break; -*/ + case 0x0b: // Vibratory Snore, note this is 0xa in F5V1 and 0x9 in F5V3 + // VS events are instantaneous flags with no duration, drawn on the official waveform. + // The current thinking is that these are the snores that cause a change in auto-titrating + // pressure. The snoring statistic above seems to be a total count. It's unclear whether + // the trigger for pressure change is severity or count or something else. + // no data bytes + this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); + break; case 0x0c: // Periodic Breathing, note this is 0xb in F5V1 and 0xa in F5V3 // PB events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); // confirmed to double in F5V0 elapsed = data[pos+2]; this->AddEvent(new PRS1PeriodicBreathingEvent(t - elapsed - duration, duration)); break; -/* - case 0x0d: - DUMP_EVENT(); - - data0 = (data[pos + 1] << 8 | data[pos]); - data0 *= 2; - pos += 2; - data1 = data[pos++]; - //tt = t - qint64(data1) * 1000L; - break; -*/ + //case 0x0d: // never seen on F5V2 case 0x0e: // Statistics, note this was 0x0d in F5V0 and F5V1 // These appear every 2 minutes, so presumably summarize the preceding period. this->AddEvent(new PRS1IPAPAverageEvent(t, data[pos+0], GAIN)); // 00=IPAP @@ -928,53 +863,6 @@ bool PRS1DataChunk::ParseEventsF5V2(void) this->AddEvent(new PRS1LeakEvent(t, data[pos+0xa])); // 0A=Leak (average?) new to F5V1 (originally found in F5V3) this->AddEvent(new PRS1IntervalBoundaryEvent(t)); break; -/* - case 0x0f: - DUMP_EVENT(); - qDebug() << "0x0f Observed in ASV data!!????"; - - data0 = data[pos + 1] << 8 | data[pos]; - pos += 2; - data1 = data[pos]; //|data[pos+1] << 8 - pos += 1; - //tt -= qint64(data1) * 1000L; - //session->AddEvent(new Event(tt,cpapcode, 0, data, 2)); - break; - - case 0x10: // Unknown - DUMP_EVENT(); - data0 = data[pos + 1] << 8 | data[pos]; - pos += 2; - data1 = data[pos++]; - - this->AddEvent(new PRS1LargeLeakEvent(t - data1, data0)); - -// qDebug() << "0x10 Observed in ASV data!!????"; -// data0 = data[pos++]; // << 8) | data[pos]; -// data1 = data[pos++]; -// data2 = data[pos++]; - //session->AddEvent(new Event(t,cpapcode, 0, data, 3)); - break; - case 0x11: // Not Leak Rate - DUMP_EVENT(); - qDebug() << "0x11 Observed in ASV data!!????"; - //if (!Code[24]) { - // Code[24]=new EventList(cpapcode,EVL_Event); - //} - //Code[24]->AddEvent(t,data[pos++]); - break; - - - case 0x12: // Summary - DUMP_EVENT(); - qDebug() << "0x12 Observed in ASV data!!????"; - data0 = data[pos++]; - data1 = data[pos++]; - //data2 = data[pos + 1] << 8 | data[pos]; - pos += 2; - //session->AddEvent(new Event(t,cpapcode, 0, data,3)); - break; -*/ default: DUMP_EVENT(); UNEXPECTED_VALUE(code, "known event code"); From 2b0845a32cbe167f5e3e3b0cadb4023b0928e330 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sun, 24 Apr 2022 17:29:19 -0400 Subject: [PATCH 4/6] Add 410X150C to the list of tested machines. There's one new setting that we don't yet support since we don't know what it is. --- Htmldocs/release_notes.html | 1 + oscar/SleepLib/loader_plugins/prs1_loader.cpp | 1 + oscar/SleepLib/loader_plugins/prs1_parser_xpap.cpp | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html index 5b48e3e3..c8f8c362 100644 --- a/Htmldocs/release_notes.html +++ b/Htmldocs/release_notes.html @@ -17,6 +17,7 @@

    • [new] Additional Philips Respironics devices tested and fully supported:
        +
      • DreamStation 2 CPAP (410X150C)
      • BiPAP Auto (System One 60 Series) (761P)
      • BiPAP autoSV Advanced 30 (System One 60 Series) (961TCA)
      diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 84c914b3..018022b5 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -132,6 +132,7 @@ static const PRS1TestedModel s_PRS1TestedModels[] = { { "700X130", 0, 6, "DreamStation Auto BiPAP" }, { "700X150", 0, 6, "DreamStation Auto BiPAP" }, + { "410X150C", 0, 6, "DreamStation 2 CPAP" }, { "520X110C", 0, 6, "DreamStation 2 Auto CPAP Advanced" }, // based on bottom label, boot screen says "Advanced Auto CPAP" { "520X150C", 0, 6, "DreamStation 2 Auto CPAP Advanced" }, // from user report { "521X120C", 0, 6, "DreamStation 2 Auto CPAP Advanced with P-Flex" }, // inferred from 501X120 and presence of "P-Flex" on bottom label diff --git a/oscar/SleepLib/loader_plugins/prs1_parser_xpap.cpp b/oscar/SleepLib/loader_plugins/prs1_parser_xpap.cpp index 7772c2cc..f6d0846a 100644 --- a/oscar/SleepLib/loader_plugins/prs1_parser_xpap.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_parser_xpap.cpp @@ -2078,6 +2078,14 @@ bool PRS1DataChunk::ParseSettingsF0V6(const unsigned char* data, int size) // TODO: Confirm that 4 is 12HT and update ParseTubingTypeV3. this->ParseTubingTypeV3(data[pos]); break; + case 0x48: // ??? Seen on DreamStation 2 non-Advanced (410) + // Appears between 0x2C and 0x2E, but the only values we've seen other than 0 is + // 1, which doesn't seem to be like a reasonable pressure (for ramp pressure) nor + // a boolean setting, which tends to be 0x80. + CHECK_VALUE(len, 1); + CHECK_VALUES(data[pos], 0, 1); + //this->AddEvent(new PRS1UnknownDataEvent(QByteArray((const char*) data, size), pos, len)); + break; case 0x4a: // Patient controls access, specific to DreamStation 2. CHECK_VALUE(len, 1); CHECK_VALUES(data[pos], 0, 0x80); From f63bb39d54eae0181079c65c782af9e766364381 Mon Sep 17 00:00:00 2001 From: LoudSnorer Date: Tue, 26 Apr 2022 19:40:40 -0400 Subject: [PATCH 5/6] Dynamic Scaling --- oscar/Graphs/MinutesAtPressure.cpp | 31 +------ oscar/Graphs/gGraph.cpp | 116 ++++++++++++++++++-------- oscar/Graphs/gGraph.h | 24 ++++-- oscar/Graphs/gGraphView.cpp | 124 +++++++++++++--------------- oscar/Graphs/gGraphView.h | 32 +++---- oscar/Graphs/gLineChart.cpp | 115 ++++++++++++-------------- oscar/Graphs/gLineChart.h | 7 ++ oscar/Graphs/gSessionTimesChart.cpp | 15 +--- oscar/Graphs/gYAxis.cpp | 14 ++-- oscar/Graphs/layer.cpp | 3 + oscar/Graphs/layer.h | 4 + oscar/test_macros.h | 119 ++++++++++++++++++++++++++ 12 files changed, 369 insertions(+), 235 deletions(-) create mode 100644 oscar/test_macros.h diff --git a/oscar/Graphs/MinutesAtPressure.cpp b/oscar/Graphs/MinutesAtPressure.cpp index 0f3a7c85..fafe8379 100644 --- a/oscar/Graphs/MinutesAtPressure.cpp +++ b/oscar/Graphs/MinutesAtPressure.cpp @@ -143,34 +143,10 @@ some messages from Apnea Board. //#define MAP_LOG_EVENTS //#define ENABLE_UNEVEN_MACHINE_MIN_MAX_TEST -// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +/ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // Define Display macros to enhance displays -#define DEBUGQ qDebug() -#define DEBUGT qDebug()< -#define DEBUG qDebug()<m_flags_enabled[ch]; } - //DEBUGF << FULLNAME(ch) << O(value); m_enabled[ch]=value; } }; diff --git a/oscar/Graphs/gGraph.cpp b/oscar/Graphs/gGraph.cpp index 6b2645ac..057621e5 100644 --- a/oscar/Graphs/gGraph.cpp +++ b/oscar/Graphs/gGraph.cpp @@ -7,6 +7,9 @@ * License. See the file COPYING in the main directory of the source code * for more details. */ +#define TEST_MACROS_ENABLEDoff +#include "test_macros.h" + #include "Graphs/gGraph.h" #include @@ -131,6 +134,7 @@ gGraph::gGraph(QString name, gGraphView *graphview, QString title, QString units m_units(units), m_visible(true) { + // DEBUGF Q(name) Q(title) QQ("UNITS",units) Q(height) Q(group); if (height == 0) { height = AppSetting->graphHeight(); Q_UNUSED(height) @@ -153,7 +157,7 @@ gGraph::gGraph(QString name, gGraphView *graphview, QString title, QString units min_x = min_y = 0; rec_miny = rec_maxy = 0; rphysmax_y = rphysmin_y = 0; - m_zoomY = 0; + m_zoomY = ZS_AUTO_FIT; m_selectedDuration = 0; if (graphview) { @@ -281,9 +285,23 @@ void gGraph::setDay(Day *day) // ResetBounds(); } -void gGraph::setZoomY(short zoom) -{ - m_zoomY = zoom; +void gGraph::setZoomY(ZoomyScaling zoomy) { + m_zoomY = zoomy; + dynamicScalingOn =false; + timedRedraw(0); +} + +void gGraph::mouseDoubleClickYAxis(QMouseEvent * ) { + if ( isDynamicScalingEnabled() ) { + dynamicScalingOn = !dynamicScalingOn; + } else { + dynamicScalingOn =false; + if (m_zoomY == ZS_AUTO_FIT ) { + m_zoomY = ZS_DEFAULT; + } else if (m_zoomY == ZS_DEFAULT) { + m_zoomY = ZS_AUTO_FIT ; + } + } timedRedraw(0); } @@ -564,22 +582,67 @@ void gGraph::ToolTip(QString text, int x, int y, ToolTipAlignment align, int tim m_graphview->m_tooltip->display(text, x, y, align, timeout); } +bool gGraph::isDynamicScalingEnabled() { + return ((m_lineChart_layer!=nullptr) && AppSetting->allowYAxisScaling() ); +} + +QString gGraph::unitsTooltip() { + if (isDynamicScalingEnabled()) { + if(dynamicScalingOn) { + if (zoomY() == ZS_AUTO_FIT ) { + return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Return to AUTO-FIT Scaling")); + } else if (zoomY() == ZS_DEFAULT ) { + return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Return to DEFAULT Scaling")); + } else { + return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Return to OVERRIDE Scaling")); + } + } else { + return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: For Dynamic Scaling")); + } + } else { + if (zoomY() == ZS_AUTO_FIT ) { + return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Select DEFAULT Scaling")); + } else if (zoomY() == ZS_DEFAULT ) { + return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Select AUTO-FIT Scaling")); + } + } + return m_units; +} + +void gGraph::dynamicScaling(EventDataType &miny, EventDataType &maxy) { + // Have new Dynamic mode; + miny = m_lineChart_layer->actualMinY(); + maxy = m_lineChart_layer->actualMaxY(); + EventDataType diff= (maxy-miny); + maxy += diff*0.08; // more space at top for event ticks. + miny -= diff*0.04; + if (m_saved_minY!=m_lineChart_layer->actualMinY() || m_saved_maxY!=m_lineChart_layer->actualMaxY() ) { + // DEBUGF O(m_name) Q(m_saved_minY) QQ("==>",m_lineChart_layer->actualMinY() ) QQ("==>",miny) Q(m_saved_maxY) QQ("==>",m_lineChart_layer->actualMaxY() ) QQ("==>",maxy); + m_saved_minY=m_lineChart_layer->actualMinY(); + m_saved_maxY=m_lineChart_layer->actualMaxY(); + } +} + // YAxis Autoscaling code void gGraph::roundY(EventDataType &miny, EventDataType &maxy) { - - if (zoomY() == 2) { + if (dynamicScalingOn) { + dynamicScaling(miny, maxy) ; + if (maxy > miny) return; + }; + if (zoomY() == ZS_OVERRIDE) { // Have override mode + // set min and max to override values. miny = rec_miny; maxy = rec_maxy; - if (maxy > miny) return; - } else if (zoomY() ==1) { + if (maxy > miny) return; // Not Autoscaling + } else if (zoomY() ==ZS_DEFAULT) { // Have Default mode + // set min and max to physical Min / max values. miny = physMinY(); maxy = physMaxY(); - if (maxy > miny) return; + if (maxy > miny) return; // Not Autoscaling } miny = MinY(); maxy = MaxY(); - int m, t; bool ymin_good = false, ymax_good = false; @@ -685,6 +748,11 @@ void gGraph::AddLayer(Layer *l, LayerPosition position, short width, short heigh l->setPos(x, y); l->addref(); m_layers.push_back(l); + + if (l->layerType()==LT_LineChart) { + m_lineChart_layer = dynamic_cast(l); + } + } void gGraph::dataChanged() @@ -1111,24 +1179,6 @@ void gGraph::mouseDoubleClickEvent(QMouseEvent *event) layer->mouseDoubleClickEvent(event, this); } } - - //int w=m_lastbounds.width()-(m_marginleft+left+right+m_marginright); - //int h=m_lastbounds.height()-(bottom+m_marginbottom); - //int x2=m_graphview->pointClicked().x(),y2=m_graphview->pointClicked().y(); - // if ((m_graphview->horizTravel()left+m_marginleft && xtop+m_margintop && ybutton() & Qt::RightButton) { - // ZoomX(1.66,x); // Zoon out - // return; - // } else if (event->button() & Qt::LeftButton) { - // ZoomX(0.75/2.0,x); // zoom in. - // return; - // } - // } else { - // Propagate the events to graph Layers - // } - //mousePressEvent(event); - //mouseReleaseEvent(event); - //qDebug() << m_title << "Double Clicked" << event->x() << event->y(); } void gGraph::keyPressEvent(QKeyEvent *event) { @@ -1416,16 +1466,14 @@ void gGraph::SetMaxY(EventDataType v) Layer *gGraph::getLineChart() { - gLineChart *lc; - + if (m_lineChart_layer) return m_lineChart_layer; for (auto & layer : m_layers) { - lc = dynamic_cast(layer); - - if (lc) { return lc; } + Layer *tmp = dynamic_cast(layer); + if (tmp) m_lineChart_layer = tmp; } - return nullptr; } + int gGraph::minHeight() { int minheight = m_min_height; diff --git a/oscar/Graphs/gGraph.h b/oscar/Graphs/gGraph.h index 4fc78406..c79b8d79 100644 --- a/oscar/Graphs/gGraph.h +++ b/oscar/Graphs/gGraph.h @@ -35,6 +35,8 @@ void DestroyGraphGlobals(); const int mouse_movement_threshold = 6; +enum ZoomyScaling {ZS_AUTO_FIT =0 , ZS_DEFAULT =1 , ZS_OVERRIDE =2}; + float CatmullRomSpline(float p0, float p1, float p2, float p3, float t = 0.5); inline void GetTextExtent(QString text, int &width, int &height, QFont *font) @@ -164,6 +166,9 @@ class gGraph : public QObject //! \brief Returns the measurement Units the Y scale is referring to QString units() { return m_units; } + //! \brief Returns the measurement Units the Y scale is referring to plus tooltip. + QString unitsTooltip() ; + //! \brief Sets the measurement Units the Y scale is referring to void setUnits(const QString units) { m_units = units; } @@ -303,9 +308,14 @@ class gGraph : public QObject bool isSnapshot() { return m_snapshot; } void setSnapshot(bool b) { m_snapshot = b; } + // returns if graph is a daily line chart with Dynamic Scaling mode enabled. + bool isDynamicScalingEnabled(); + short left, right, top, bottom; // dirty magin hacks.. Layer *getLineChart(); + Layer *m_lineChart_layer =nullptr ; + bool dynamicScalingOn =false; QTimer *timer; // This gets set to true to force a redraw of the yAxis tickers when graphs are resized. @@ -318,10 +328,8 @@ class gGraph : public QObject gGraphView *graphView() { return m_graphview; } short m_marginleft, m_marginright, m_margintop, m_marginbottom; - inline short zoomY() { return m_zoomY; } - void setZoomY(short zoom); - - static const short maxZoomY = 2; + inline ZoomyScaling zoomY() { return m_zoomY; } + void setZoomY(ZoomyScaling zoomy); inline qint64 selectedDuration() const { return m_selectedDuration; } inline QString selDurString() const { return m_selDurString; } @@ -331,6 +339,8 @@ class gGraph : public QObject inline bool printing() const { return m_printing; } + void mouseDoubleClickYAxis(QMouseEvent *event); + protected: //! \brief Mouse Wheel events virtual void wheelEvent(QWheelEvent *event); @@ -353,6 +363,8 @@ class gGraph : public QObject //! \brief Key Pressed event virtual void keyReleaseEvent(QKeyEvent *event); + void dynamicScaling(EventDataType &miny, EventDataType &maxy); + void cancelSelection() { m_selecting_area = false; m_selection = QRect(0,0,0,0); @@ -389,7 +401,7 @@ class gGraph : public QObject bool m_showTitle; bool m_printing; bool m_pinned; - short m_zoomY; + ZoomyScaling m_zoomY; bool m_block_select; QRect m_rect; @@ -401,6 +413,8 @@ class gGraph : public QObject QString m_selDurString; bool m_snapshot; + EventDataType m_saved_minY=0; + EventDataType m_saved_maxY=0; protected slots: //! \brief Deselects any highlights, and schedules a main gGraphView redraw diff --git a/oscar/Graphs/gGraphView.cpp b/oscar/Graphs/gGraphView.cpp index 6070d49e..9a915513 100644 --- a/oscar/Graphs/gGraphView.cpp +++ b/oscar/Graphs/gGraphView.cpp @@ -7,21 +7,9 @@ * License. See the file COPYING in the main directory of the source code * for more details. */ -#define xDEBUG_FUNCTIONS -#ifdef DEBUG_FUNCTIONS -#include -#define DEBUG qDebug()<height()); } - return rect; + return rect; } void gToolTip::paint(QPainter &painter) //actually paints it. { if (!m_visible) { return; } - QRect a_rect=calculateRect(painter); + QRect a_rect=calculateRect(painter); QBrush brush(QColor(255, 255, 128, 230)); brush.setStyle(Qt::SolidPattern); @@ -233,22 +221,22 @@ void gToolTip::timerDone() locate tool tips in an appropiate location. */ gParentToolTip::gParentToolTip(gGraphView *graphview) - : gToolTip(graphview) { - m_parent_visible=false; + : gToolTip(graphview) { + m_parent_visible=false; } gParentToolTip::~gParentToolTip() { } void gParentToolTip::display(gGraphView* gv,QString text, int verticalOffset, int alignOffset, ToolTipAlignment align , int timeout ,QFont *font ) { - m_text=text; - m_verticalOffset=verticalOffset; - m_alignOffset=alignOffset; - m_alignment=align; - m_timeout=timeout; - m_font=font; - m_parent_visible=true; - gv->timedRedraw(0); + m_text=text; + m_verticalOffset=verticalOffset; + m_alignOffset=alignOffset; + m_alignment=align; + m_timeout=timeout; + m_font=font; + m_parent_visible=true; + gv->timedRedraw(0); }; @@ -257,52 +245,52 @@ QRect gParentToolTip::calculateRect(QPainter& painter ) { painter.setFont(*m_font); rect = painter.boundingRect(rect, m_alignment, m_text); - // update space arround text - int space=2*m_spacer; - rect.setHeight(space+rect.height()); - rect.setWidth(space+rect.width()); + // update space arround text + int space=2*m_spacer; + rect.setHeight(space+rect.height()); + rect.setWidth(space+rect.width()); - rect.moveTo(m_alignOffset,m_height-(m_verticalOffset+rect.height())); + rect.moveTo(m_alignOffset,m_height-(m_verticalOffset+rect.height())); - // move rect accounding to alignment. default is left. + // move rect accounding to alignment. default is left. - if (m_alignment == TT_AlignRight) { - // move rect left by width of rect. if <0 use 0; - rect.moveLeft(rect.left()-rect.width()); - } else if (m_alignment == TT_AlignCenter) { - // left by 1/2 width of rect. if < 0 then use 0 - rect.moveLeft(rect.left()-rect.width()/2); - } + if (m_alignment == TT_AlignRight) { + // move rect left by width of rect. if <0 use 0; + rect.moveLeft(rect.left()-rect.width()); + } else if (m_alignment == TT_AlignCenter) { + // left by 1/2 width of rect. if < 0 then use 0 + rect.moveLeft(rect.left()-rect.width()/2); + } - if (rect.top()<0) {rect.setTop(0);}; - if (rect.left()<0) {rect.setLeft(0);}; + if (rect.top()<0) {rect.setTop(0);}; + if (rect.left()<0) {rect.setLeft(0);}; - return rect; + return rect; } void gParentToolTip::paint(QPainter &painter,int width,int height) { - if (!m_parent_visible) {return ;}; - m_width=width; - m_height=height; - gToolTip::display(m_text, 0, 0,m_alignment, m_timeout); - gToolTip::paint(painter); + if (!m_parent_visible) {return ;}; + m_width=width; + m_height=height; + gToolTip::display(m_text, 0, 0,m_alignment, m_timeout); + gToolTip::paint(painter); }; void gParentToolTip::timerDone() { - gToolTip::timerDone(); - if (m_parent_visible) { - m_graphview->timedRedraw(0); - } - m_parent_visible=false; + gToolTip::timerDone(); + if (m_parent_visible) { + m_graphview->timedRedraw(0); + } + m_parent_visible=false; }; void gParentToolTip::cancel() { - gToolTip::cancel(); - m_parent_visible=false; + gToolTip::cancel(); + m_parent_visible=false; }; bool gParentToolTip::visible() { - return gToolTip::visible() && m_parent_visible; + return gToolTip::visible() && m_parent_visible; }; @@ -560,7 +548,6 @@ void gGraphView::popoutGraph() dock->resize(width(),0); // QScrollArea } - //////// Create dock widget and resize dock to hold new widget QDockWidget * newDockWidget = new QDockWidget(dock); newDockWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); @@ -2061,8 +2048,8 @@ void MinMaxWidget::onMaxChanged(double d) } void MinMaxWidget::onResetClicked() { - int tmp = graph->zoomY(); - graph->setZoomY(0); + ZoomyScaling tmp = graph->zoomY(); + graph->setZoomY(ZS_AUTO_FIT); EventDataType miny = graph->MinY(), maxy = graph->MaxY(); @@ -2084,15 +2071,16 @@ void MinMaxWidget::onResetClicked() graph->setZoomY(tmp); } -void MinMaxWidget::onComboChanged(int idx) +void MinMaxWidget::onComboChanged(int _idx) { - minbox->setEnabled(idx == 2); - maxbox->setEnabled(idx == 2); - reset->setEnabled(idx == 2); + ZoomyScaling idx = static_cast(_idx) ; + minbox->setEnabled(idx == ZS_OVERRIDE); + maxbox->setEnabled(idx == ZS_OVERRIDE); + reset->setEnabled(idx == ZS_OVERRIDE); graph->setZoomY(idx); - if (idx == 2) { + if (idx == ZS_OVERRIDE) { if (qAbs(graph->rec_maxy - graph->rec_miny) < 0.0001) { onResetClicked(); } @@ -2106,9 +2094,9 @@ void MinMaxWidget::createLayout() layout->setSpacing(4); combobox = new QComboBox(this); - combobox->addItem(tr("Auto-Fit"), 0); - combobox->addItem(tr("Defaults"), 1); - combobox->addItem(tr("Override"), 2); + combobox->addItem(tr("Auto-Fit"), ZS_AUTO_FIT); + combobox->addItem(tr("Defaults"), ZS_DEFAULT); + combobox->addItem(tr("Override"), ZS_OVERRIDE); combobox->setToolTip(tr("The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own.")); connect(combobox, SIGNAL(activated(int)), this, SLOT(onComboChanged(int))); @@ -3533,7 +3521,7 @@ void gGraphView::SaveSettings(QString title) out << graph->visible(); out << graph->RecMinY(); out << graph->RecMaxY(); - out << graph->zoomY(); + out << (short)graph->zoomY(); // the return type of zoomY was changed from a short to an enum (int) so much type cast it here out << (bool)graph->isPinned(); gLineChart * lc = dynamic_cast(findLayer(graph, LT_LineChart)); @@ -3663,7 +3651,7 @@ bool gGraphView::LoadSettings(QString title) g->setVisible(vis); g->setRecMinY(recminy); g->setRecMaxY(recmaxy); - g->setZoomY(zoomy); + g->setZoomY(static_cast(zoomy)); g->setPinned(pinned); if (gvversion >= 4) { diff --git a/oscar/Graphs/gGraphView.h b/oscar/Graphs/gGraphView.h index 8f984c79..1e1bfeea 100644 --- a/oscar/Graphs/gGraphView.h +++ b/oscar/Graphs/gGraphView.h @@ -267,26 +267,26 @@ class gToolTip : public QObject class gParentToolTip : public gToolTip { - Q_OBJECT + Q_OBJECT public: - gParentToolTip(gGraphView *graphview); - ~gParentToolTip(); - using gToolTip::display; - virtual void display(gGraphView* gv,QString text,int verticalOffset, int alignOffset , ToolTipAlignment align = TT_AlignCenter, int timeout = 0,QFont *font = defaultfont) ; - virtual void cancel(); - virtual bool visible(); + gParentToolTip(gGraphView *graphview); + ~gParentToolTip(); + using gToolTip::display; + virtual void display(gGraphView* gv,QString text,int verticalOffset, int alignOffset , ToolTipAlignment align = TT_AlignCenter, int timeout = 0,QFont *font = defaultfont) ; + virtual void cancel(); + virtual bool visible(); virtual QRect calculateRect(QPainter &painter); - using gToolTip::paint; - virtual void paint(QPainter &paint,int width,int height) ; + using gToolTip::paint; + virtual void paint(QPainter &paint,int width,int height) ; private: - int m_verticalOffset; - int m_alignOffset; - int m_width; - int m_height; - bool m_parent_visible; - int m_timeout; + int m_verticalOffset; + int m_alignOffset; + int m_width; + int m_height; + bool m_parent_visible; + int m_timeout; protected slots: - virtual void timerDone(); + virtual void timerDone(); }; struct SelectionHistoryItem { diff --git a/oscar/Graphs/gLineChart.cpp b/oscar/Graphs/gLineChart.cpp index c56ad118..2077bdb3 100644 --- a/oscar/Graphs/gLineChart.cpp +++ b/oscar/Graphs/gLineChart.cpp @@ -7,6 +7,10 @@ * License. See the file COPYING in the main directory of the source code * for more details. */ + +#define TEST_MACROS_ENABLEDoff +#include "test_macros.h" + #include "Graphs/gLineChart.h" #include @@ -275,6 +279,7 @@ skipcheck: } } } + EventDataType gLineChart::Miny() { if (m_codes.size() == 0) return 0; @@ -373,6 +378,7 @@ QString gLineChart::getMetaString(qint64 time) // Time Domain Line Chart void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { + EventDataType actual_min_y, actual_max_y; QRectF rect = region.boundingRect(); rect.translate(0.0f, 0.001f); // TODO: Just use QRect directly. @@ -395,6 +401,8 @@ void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) return; } + actual_min_y = INT_MAX; + actual_max_y = -INT_MAX; top++; @@ -551,6 +559,7 @@ void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) painter.setPen(QPen(QBrush(color), lineThickness, Qt::DotLine)); EventDataType y=top + height + 1 - ((dot.value - miny) * ymult); painter.drawLine(left + 1, y, left + 1 + width, y); + DEBUGF NAME(dot.code) Q(dot.type) QQ(y,(int)y) Q(ratioX) O(QLine(left + 1, y, left + 1 + width, y)) Q(legendx) O(dot.value) ; } } @@ -742,6 +751,8 @@ void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) time += rate; // This is much faster than QVector access. data = *ptr * gain; + if (actual_min_y>data) { actual_min_y=data; } + if (actual_max_ydata) { actual_min_y=data; } + if (actual_max_ydata) { actual_min_y=data; } + if (actual_max_ydata) { actual_min_y=data; } + if (actual_max_y xst + width) { px = xst + width; } - -// lines.append(QLine(lastpx, lastpy, px, lastpy)); -// lines.append(QLine(px, lastpy, px, py)); - } // else { - // Letting the scissor do the dirty work for non horizontal lines - // This really should be changed, as it might be cause that weird - // display glitch on Linux.. - - lines.append(QLine(lastpx, lastpy, px, lastpy)); - lines.append(QLine(px, lastpy, px, py)); -// } - - lastpx = px; - lastpy = py; - - if (time > maxx) { - done = true; // Let this iteration finish.. (This point will be in far clipping) - break; - } + // Cap px to right margin + if (px > xst + width) { px = xst + width; } + } + //else { + // Letting the scissor do the dirty work for non horizontal lines + // This really should be changed, as it might be cause that weird + // display glitch on Linux.. + //} + if (square_plot) { + lines.append(QLine(lastpx, lastpy, px, lastpy)); + lines.append(QLine(px, lastpy, px, py)); + } else { + lines.append(QLine(lastpx, lastpy, px, py)); } - } else { - for (; dptr < eptr; dptr++) { - //for (int i=0;i xst + width) { px = xst + width; } - - // lines.append(QLine(lastpx, lastpy, px, py)); - } //else { - // Letting the scissor do the dirty work for non horizontal lines - // This really should be changed, as it might be cause that weird - // display glitch on Linux.. - lines.append(QLine(lastpx, lastpy, px, py)); - //} - - lastpx = px; - lastpy = py; - - if (time > maxx) { // Past right edge, abort further drawing.. - done = true; - break; - } + if (time > maxx) { // Past right edge, abort further drawing.. + done = true; + break; } } + if (w.printing() && AppSetting->monochromePrinting()) { painter.setPen(QPen(Qt::black, lineThickness + 0.5)); } else { @@ -1127,9 +1111,7 @@ void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) double f = double(cnt) / hours; // / (sum / 3600.0); QString txt = QObject::tr("Duration %1:%2:%3").arg(h,2,10,QChar('0')).arg(m,2,10,QChar('0')).arg(s,2,10,QChar('0')) + " "+ - QObject::tr("AHI %1").arg(f,0,'f',2);// +" " + -// QObject::tr("Events %1").arg(cnt) + " " + -// QObject::tr("Hours %1").arg(hours,0,'f',2); + QObject::tr("AHI %1").arg(f,0,'f',2);// +" " if (linecursormode) txt+=lasttext; @@ -1138,4 +1120,9 @@ void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) // painter.setRenderHint(QPainter::Antialiasing, false); + +if (actual_max_y>0) { + m_actual_min_y=actual_min_y; + m_actual_max_y=actual_max_y; +} } diff --git a/oscar/Graphs/gLineChart.h b/oscar/Graphs/gLineChart.h index 15831373..ae550869 100644 --- a/oscar/Graphs/gLineChart.h +++ b/oscar/Graphs/gLineChart.h @@ -112,6 +112,12 @@ class gLineChart: public Layer //! \brief Returns Maximum Y-axis value for this layer virtual EventDataType Maxy(); + //! \brief Returns Minimum Y-axis value for this layer + virtual EventDataType actualMinY() {return m_actual_min_y;}; + + //! \brief Returns Maximum Y-axis value for this layer + virtual EventDataType actualMaxY() {return m_actual_max_y;}; + //! \brief Returns true if all subplots contain no data virtual bool isEmpty(); @@ -164,6 +170,7 @@ class gLineChart: public Layer bool m_report_empty; bool m_square_plot; bool m_disable_accel; + EventDataType m_actual_min_y=0,m_actual_max_y=0; //! \brief Used by accelerated waveform plots. Must be >= Screen Resolution (or at least graph width) static const int max_drawlist_size = 10000; diff --git a/oscar/Graphs/gSessionTimesChart.cpp b/oscar/Graphs/gSessionTimesChart.cpp index 1f9ec008..5bec0b86 100644 --- a/oscar/Graphs/gSessionTimesChart.cpp +++ b/oscar/Graphs/gSessionTimesChart.cpp @@ -7,19 +7,8 @@ * License. See the file COPYING in the main directory of the source code * for more details. */ -#define xDEBUG_FUNCTIONS -#ifdef DEBUG_FUNCTIONS -#include -#define DEBUG qDebug()< #include diff --git a/oscar/Graphs/gYAxis.cpp b/oscar/Graphs/gYAxis.cpp index 37c9e7bc..b84e7310 100644 --- a/oscar/Graphs/gYAxis.cpp +++ b/oscar/Graphs/gYAxis.cpp @@ -7,6 +7,9 @@ * License. See the file COPYING in the main directory of the source code * for more details. */ +#define TEST_MACROS_ENABLEDoff +#include + #include "Graphs/gYAxis.h" #include @@ -20,6 +23,7 @@ #include + gXGrid::gXGrid(QColor col) : Layer(NoChannel) { @@ -306,7 +310,7 @@ bool gYAxis::mouseMoveEvent(QMouseEvent *event, gGraph *graph) int y = event->y(); if (!graph->units().isEmpty()) { - graph->ToolTip(graph->units(), x+10, y+10, TT_AlignLeft); + graph->ToolTip(graph->unitsTooltip(), x+10, y+10, TT_AlignLeft); // graph->redraw(); } @@ -316,14 +320,8 @@ bool gYAxis::mouseMoveEvent(QMouseEvent *event, gGraph *graph) bool gYAxis::mouseDoubleClickEvent(QMouseEvent *event, gGraph *graph) { if (graph) { - // int x=event->x(); - // int y=event->y(); - short z = (graph->zoomY() + 1) % gGraph::maxZoomY; - graph->setZoomY(z); - qDebug() << "Mouse double clicked for" << graph->name() << z; + graph->mouseDoubleClickYAxis(event); } - - Q_UNUSED(event); return false; } diff --git a/oscar/Graphs/layer.cpp b/oscar/Graphs/layer.cpp index d32e3a6c..a76faf29 100644 --- a/oscar/Graphs/layer.cpp +++ b/oscar/Graphs/layer.cpp @@ -7,6 +7,9 @@ * License. See the file COPYING in the main directory of the source code * for more details. */ +#define TEST_MACROS_ENABLEDoff +#include "test_macros.h" + #include "Graphs/layer.h" Layer::~Layer() diff --git a/oscar/Graphs/layer.h b/oscar/Graphs/layer.h index 100e70e6..38c29ec2 100644 --- a/oscar/Graphs/layer.h +++ b/oscar/Graphs/layer.h @@ -230,6 +230,10 @@ public: Q_UNUSED(graph); return false; } + + virtual EventDataType actualMinY() {return 0;}; + virtual EventDataType actualMaxY() {return 0;}; + }; /*! \class LayerGroup diff --git a/oscar/test_macros.h b/oscar/test_macros.h new file mode 100644 index 00000000..c2f5e93d --- /dev/null +++ b/oscar/test_macros.h @@ -0,0 +1,119 @@ +/* Test macros Implemntation + * + * Copyright (c) 2019-2022 The OSCAR Team + * Copyright (c) 2011-2018 Mark Watkins + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of the source code + * for more details. */ + +/* +These functions will display formatted debug information. +The macro TEST_MACROS_ENABLED will enable these macros to display information +When The macro TEST_MACROS_ENABLED is undefined then these marcos will expand to white space. + +When these macos are used then debugging is disabled +When only these macos are used then debugging is disabled. +SO for production code rename TEST_MACROS_ENABLED to TEST_MACROS_ENABLEDoff + +########################################### +The the following to source cpp files +to turn on the debug macros for use. + +#define TEST_MACROS_ENABLED +#include + +To turn off the the test macros. +#define TEST_MACROS_ENABLEDoff +#include +########################################### + + +*/ + +#ifndef TEST_MACROS_ENABLED_ONCE +#define TEST_MACROS_ENABLED_ONCE + +#ifdef TEST_MACROS_ENABLED +#include +#include + +#define DEBUGL qDebug() < Date: Wed, 27 Apr 2022 13:49:40 -0400 Subject: [PATCH 6/6] Updated compiler branches to work with big sur + big sur + now reads compiler branches correctly added -Wno-error for deprecated-copy for qt 5.12 --- oscar/oscar.pro | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/oscar/oscar.pro b/oscar/oscar.pro index ff48ecb9..860d38a3 100644 --- a/oscar/oscar.pro +++ b/oscar/oscar.pro @@ -527,7 +527,6 @@ message("CXXFLAGS pre-mods $$QMAKE_CXXFLAGS ") QMAKE_CFLAGS += -Werror QMAKE_CXXFLAGS += -Werror - gcc | clang { COMPILER_VERSION = $$system($$QMAKE_CXX " -dumpversion") COMPILER_MAJOR = $$split(COMPILER_VERSION, ".") @@ -536,25 +535,28 @@ gcc | clang { message("$$QMAKE_CXX major version $$COMPILER_MAJOR") } -## equals($$QMAKE_CXX, "gcc") : { // guess what! the name is really "g++" -equals(QMAKE_CXX, g++) { - message("Detected compiler g++") +gcc:!clang { + message("Building for $$QMAKE_HOST.os") greaterThan(COMPILER_MAJOR, 10) : { QMAKE_CFLAGS += -Wno-error=stringop-overread QMAKE_CXXFLAGS += -Wno-error=stringop-overread message("Making stringop-overread a non-error") } } -equals(QMAKE_CXX, clang++) { - message("Detected compiler clang++") -} +clang { + message("Building for $$QMAKE_HOST.os") + QMAKE_CFLAGS_WARN_ON += -Wno-error=deprecated-copy + QMAKE_CXXFLAGS_WARN_ON += -Wno-error=deprecated-copy + message("Making deprecated-copy a non-error") +} # Make deprecation warnings just warnings QMAKE_CFLAGS += -Wno-error=deprecated-declarations QMAKE_CXXFLAGS += -Wno-error=deprecated-declarations message("CXXFLAGS post-mods $$QMAKE_CXXFLAGS ") +message("CXXFLAGS_WARN_ON $$QMAKE_CXXFLAGS_WARN_ON") lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { QMAKE_CFLAGS += -Wno-error=strict-aliasing