diff --git a/.gitignore b/.gitignore index f34ae412..dccc9062 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,7 @@ Makefile* *~ #Doxygen output does not go in repository -doxydoc \ No newline at end of file +doxydoc + +#SourceTrail files +*.json \ No newline at end of file diff --git a/Building/MacOS/Info.plist.in b/Building/MacOS/Info.plist.in index 2d2b4a72..407b487a 100644 --- a/Building/MacOS/Info.plist.in +++ b/Building/MacOS/Info.plist.in @@ -2,25 +2,25 @@ - CFBundleExecutable - OSCAR - CFBundleGetInfoString - Created by Qt/QMake - CFBundleIconFile + CFBundleExecutable + OSCAR + CFBundleGetInfoString + Created by Qt/QMake + CFBundleIconFile ${ASSETCATALOG_COMPILER_APPICON_NAME} - CFBundleIdentifier + CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundlePackageType - APPL - CFBundleSignature - ???? - LSMinimumSystemVersion + CFBundlePackageType + APPL + CFBundleSignature + ???? + LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} - NSPrincipalClass - NSApplication - NSSupportsAutomaticGraphicsSwitching - - NSRequiresAquaSystemAppearance - + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + NSRequiresAquaSystemAppearance + diff --git a/Doxyfile b/Doxyfile index c0accd20..aec9bdb4 100644 --- a/Doxyfile +++ b/Doxyfile @@ -50,7 +50,7 @@ PROJECT_NUMBER = 1.0.x # for a project that appears at the top of each page and should give viewer # a quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = OpenSource CPAP Analysis Reporter +PROJECT_BRIEF = Open_Source_CPAP_Analysis_Reporter # With the PROJECT_LOGO tag one can specify an logo or icon that is # included in the documentation. The maximum height of the logo should not @@ -1643,7 +1643,7 @@ INCLUDED_BY_GRAPH = YES # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. -CALL_GRAPH = NO +CALL_GRAPH = YES # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function @@ -1651,7 +1651,7 @@ CALL_GRAPH = NO # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. -CALLER_GRAPH = NO +CALLER_GRAPH = YES # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will generate a graphical hierarchy of all classes instead of a textual one. @@ -1721,7 +1721,7 @@ DOT_TRANSPARENT = NO # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. -DOT_MULTI_TARGETS = NO +DOT_MULTI_TARGETS = YES # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and diff --git a/OSCAR_QT.pro b/OSCAR_QT.pro index 6a8c3dbb..df4e72bb 100644 --- a/OSCAR_QT.pro +++ b/OSCAR_QT.pro @@ -10,7 +10,3 @@ TEMPLATE = subdirs SUBDIRS += oscar CONFIG += ordered - -macx: { - QMAKE_INFO_PLIST = Building/MacOS/Info.plist.in -} diff --git a/makedoxy.bat b/makedoxy.bat new file mode 100644 index 00000000..a7f75cc6 --- /dev/null +++ b/makedoxy.bat @@ -0,0 +1,4 @@ +setlocal +set path="c:\Program Files (x86)\Graphviz2.38\bin";%path% +doxygen +endlocal \ No newline at end of file diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index b76cd507..0b9af0b9 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -1037,7 +1037,11 @@ void PRS1Loader::ScanFiles(const QStringList & paths, int sessionid_base, Machin // Parse the data chunks and read the files.. if (fi.canonicalFilePath().isEmpty()) { - qWarning() << fi; +#if QT_VERSION < QT_VERSION_CHECK(5,12,0) + qWarning() << fi.fileName() << "canonicalFilePath is empty"; +#else + qWarning() << fi << "cannonicalFilePath is empty"; +#endif } QList Chunks = ParseFile(fi.canonicalFilePath()); diff --git a/oscar/SleepLib/machine_common.h b/oscar/SleepLib/machine_common.h index f8650699..cac60064 100644 --- a/oscar/SleepLib/machine_common.h +++ b/oscar/SleepLib/machine_common.h @@ -46,7 +46,8 @@ qint64 timezoneOffset(); enum SummaryType { ST_CNT, ST_SUM, ST_AVG, ST_WAVG, ST_PERC, ST_90P, ST_MIN, ST_MAX, ST_MID, ST_CPH, ST_SPH, ST_FIRST, ST_LAST, ST_HOURS, ST_SESSIONS, ST_SETMIN, ST_SETAVG, ST_SETMAX, ST_SETWAVG, ST_SETSUM, ST_SESSIONID, ST_DATE }; /*! \enum MachineType - \brief Generalized type of a machine + \brief Generalized type of a machine. MT_CPAP is any type of xPAP machine, MT_OXIMETER any type of Oximeter + \brief MT_SLEEPSTAGE stage of sleep detector (ZEO importer), MT_JOURNAL for optional notes, MT_POSITION for sleep position detector (Somnopose) */ enum MachineType { MT_UNKNOWN = 0, MT_CPAP, MT_OXIMETER, MT_SLEEPSTAGE, MT_JOURNAL, MT_POSITION, MT_UNCATEGORIZED = 99}; //void InitMapsWithoutAwesomeInitializerLists(); diff --git a/oscar/SleepLib/profiles.cpp b/oscar/SleepLib/profiles.cpp index 5ba6d08f..ba76dd75 100644 --- a/oscar/SleepLib/profiles.cpp +++ b/oscar/SleepLib/profiles.cpp @@ -581,15 +581,17 @@ void Profile::UnloadMachineData() return; } + for (auto & mach : m_machlist) { + mach->saveSessionInfo(); + mach->sessionlist.clear(); + mach->day.clear(); + } + for (auto & day : daylist) { delete day; } daylist.clear(); - for (auto & mach : m_machlist) { - mach->sessionlist.clear(); - mach->day.clear(); - } removeLock(); } void Profile::LoadMachineData(ProgressDialog *progress) @@ -1159,6 +1161,7 @@ QList Profile::getDays(MachineType mt, QDate start, QDate end) return list; } +// Counts number of days in range with data for specified machine type int Profile::countDays(MachineType mt, QDate start, QDate end) { if (!start.isValid()) { @@ -1225,6 +1228,7 @@ int Profile::countCompliantDays(MachineType mt, QDate start, QDate end) } +// Count number of events of type code in period EventDataType Profile::calcCount(ChannelID code, MachineType mt, QDate start, QDate end) { if (!start.isValid()) { diff --git a/oscar/SleepLib/profiles.h b/oscar/SleepLib/profiles.h index 52dd3b57..0a3d892d 100644 --- a/oscar/SleepLib/profiles.h +++ b/oscar/SleepLib/profiles.h @@ -367,6 +367,11 @@ const QString STR_US_ShowUnknownFlags = "ShowUnknownFlags"; const QString STR_US_StatReportMode = "StatReportMode"; const QString STR_US_LastOverviewRange = "LastOverviewRange"; +// Values for StatReportMode +const int STAT_MODE_STANDARD = 0; +const int STAT_MODE_MONTHLY = 1; +const int STAT_MODE_RANGE = 2; + class DoctorInfo : public PrefSettings { public: diff --git a/oscar/SleepLib/session.h b/oscar/SleepLib/session.h index 7bb1fb30..663d053e 100644 --- a/oscar/SleepLib/session.h +++ b/oscar/SleepLib/session.h @@ -63,6 +63,7 @@ class Session Session(Machine *, SessionID); virtual ~Session(); + //! \brief Checks whether the supplied time is within the bounds of this session (ms since epoch) inline bool checkInside(qint64 time) { return ((time >= s_first) && (time <= s_last)); } @@ -113,13 +114,16 @@ class Session //! \brief Sets whether or not session is being used. void setEnabled(bool b); + //! \brief Return the earliest time in session (in milliseconds since epoch) inline qint64 realFirst() const { return s_first; } + + //! \brief Return the latest time in session (in milliseconds since epoch) inline qint64 realLast() const { return s_last; } - //! \brief Return the start of this sessions time range (in milliseconds since epoch) + //! \brief Return the start of this sessions time range, adjusted for clock drift (in milliseconds since epoch) qint64 first(); - //! \brief Return the end of this sessions time range (in milliseconds since epoch) + //! \brief Return the end of this sessions time range, adjusted for clock drift (in milliseconds since epoch) qint64 last(); //! \brief Return the millisecond length of this session @@ -156,10 +160,13 @@ class Session //! \brief Just set the end of the timerange without comparing void really_set_last(qint64 d) { s_last = d; } + //! \brief Set time to lower of 'd' and existing s_first void set_first(qint64 d) { if (!s_first) { s_first = d; } else if (d < s_first) { s_first = d; } } + + //! \brief Set last time to higher of 'd' and existing s_last. Throw warning if 'd' less than s_first. void set_last(qint64 d) { if (d <= s_first) { qWarning() << "Session::set_last() d<=s_first"; @@ -387,11 +394,16 @@ class Session //! \brief Returns true if session only contains summary data inline bool summaryOnly() { return s_summaryOnly; } + + //! \brief Returns true if there are no settings for this session inline bool noSettings() { return s_noSettings; } + //! \brief Mark this session as containing summary data only or not (true, false) inline void setSummaryOnly(bool b) { s_summaryOnly = b; } + //! \brief Mark this ssession as having settings data or not (true, false) inline void setNoSettings(bool b) { s_noSettings = b; } + //! \brief Mark whether this session has loaded data (true, false) void setOpened(bool b = true) { s_events_loaded = b; s_summary_loaded = b; @@ -411,6 +423,7 @@ class Session QString eventFile() const; + //! \brief Returns MachineType for this session MachineType type() { return s_machtype; } @@ -423,13 +436,17 @@ protected: SessionID s_session; Machine *s_machine; + //! \brief Time session begins (in ms since epoch) qint64 s_first; + //! \brief Time session ends (in ms since epoch) qint64 s_last; bool s_changed; bool s_lonesession; bool s_evchecksum_checked; bool _first_session; bool s_summaryOnly; + + //! \brief True if there are no settings for this session bool s_noSettings; bool s_summary_loaded; diff --git a/oscar/daily.cpp b/oscar/daily.cpp index 17bcabf1..dec0dbea 100644 --- a/oscar/daily.cpp +++ b/oscar/daily.cpp @@ -964,12 +964,11 @@ QString Daily::getMachineSettings(Day * day) { ChannelID cpapmode = loader->CPAPModeChannel(); schema::Channel & chan = schema::channel[cpapmode]; - first[cpapmode] = QString("

%1

%3") + first[cpapmode] = QString("

%1

%3") .arg(chan.label()) .arg(chan.description()) .arg(day->getCPAPMode()); - if (sess) for (; it != it_end; ++it) { ChannelID code = it.key(); @@ -991,12 +990,14 @@ QString Daily::getMachineSettings(Day * day) { data = it.value().toString() + " "+ chan.units(); } + if (code ==0xe202) // Format EPR relief correctly + data = formatRelief(data); + QString tmp = QString("

%1

%3") .arg(schema::channel[code].label()) .arg(schema::channel[code].description()) .arg(data); - if ((code == CPAP_IPAP) || (code == CPAP_EPAP) || (code == CPAP_IPAPHi) diff --git a/oscar/main.cpp b/oscar/main.cpp index ce3477b0..b6723b28 100644 --- a/oscar/main.cpp +++ b/oscar/main.cpp @@ -457,7 +457,11 @@ int main(int argc, char *argv[]) { qDebug() << "Using " + GetAppData() + " as OSCAR data folder"; QDir newDir(GetAppData()); - if ( ! newDir.exists() || newDir.isEmpty() ) { // directoy doesn't exist yet or is empty, try to migrate old data +#if QT_VERSION < QT_VERSION_CHECK(5,9,0) + if ( ! newDir.exists() || newDir.count() == 0 ) { // directoy doesn't exist yet or is empty, try to migrate old data +#else + if ( ! newDir.exists() || newDir.isEmpty() ) { // directoy doesn't exist yet or is empty, try to migrate old data +#endif if (QMessageBox::question(nullptr, QObject::tr("Migrate SleepyHead Data?"), QObject::tr("On the next screen OSCAR will ask you to select a folder with SleepyHead data") +"\n" + QObject::tr("Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead data."), diff --git a/oscar/mainwindow.cpp b/oscar/mainwindow.cpp index 5171873f..cec80bfa 100644 --- a/oscar/mainwindow.cpp +++ b/oscar/mainwindow.cpp @@ -1345,10 +1345,12 @@ void MainWindow::on_actionCheck_for_Updates_triggered() bool toolbox_visible = false; void MainWindow::on_action_Screenshot_triggered() { + setUpdatesEnabled(false); if (daily) daily->hideSpaceHogs(); toolbox_visible = ui->toolBox->isVisible(); ui->toolBox->hide(); + setUpdatesEnabled(true); QTimer::singleShot(250, this, SLOT(DelayedScreenshot())); } @@ -1381,10 +1383,12 @@ void MainWindow::DelayedScreenshot() } else { Notify(tr("Screenshot saved to file \"%1\"").arg(QDir::toNativeSeparators(a))); } + + setUpdatesEnabled(false); if (daily) daily->showSpaceHogs(); ui->toolBox->setVisible(toolbox_visible); - + setUpdatesEnabled(true); } void MainWindow::on_actionView_Oximetry_triggered() diff --git a/oscar/mainwindow.h b/oscar/mainwindow.h index 184f93be..442499c4 100644 --- a/oscar/mainwindow.h +++ b/oscar/mainwindow.h @@ -47,9 +47,14 @@ class MainWindow; This document is an attempt to provide a little technical insight into OSCAR's program internals. \section project_info Further Information - The project was hosted on sourceforge, and it's original project page can be reached at http://sourceforge.net/projects/sleepyhead. + OSCAR is hosted on Gitlab with full source code available there. - There was also a SleepyHead Wiki containing further information + Help for users can be found in the OSCAR Help Wiki. + + Data structures are described in a OSCAR Data Information Wiki. + + The SleepyHead project was hosted on sourceforge, and it's original project page can be reached at http://sourceforge.net/projects/sleepyhead. + There was also a SleepyHead Wiki containing further information. \section structure Program Structure OSCAR is written in C++ using Qt Toolkit library, and comprises of 3 main components diff --git a/oscar/oscar.pro b/oscar/oscar.pro index de7129bb..a881bbaa 100644 --- a/oscar/oscar.pro +++ b/oscar/oscar.pro @@ -457,3 +457,7 @@ test { tests/sessiontests.h } +# On macOS put a custom Info.plist into the bundle that disables dark mode on Mojave +macx { + QMAKE_INFO_PLIST = "../Building/MacOS/Info.plist.in" +} diff --git a/oscar/statistics.cpp b/oscar/statistics.cpp index 1217ad10..a41c5660 100644 --- a/oscar/statistics.cpp +++ b/oscar/statistics.cpp @@ -497,6 +497,10 @@ void Statistics::updateRXChanges() } +// Statistics constructor is responsible for creating list of rows that will on the Statistics page +// and skeletons of column 1 text that correspond to each calculation type. +// Actual column 1 text is combination of skeleton for the row's calculation time and the text of the row. +// Also creates "machine" names for machine types. Statistics::Statistics(QObject *parent) : QObject(parent) { @@ -575,10 +579,8 @@ Statistics::Statistics(QObject *parent) : } -const QString table_width = "width=99%"; -QString Statistics::htmlHeader(bool showheader) -{ - +// Get the user information block for displaying at top of page +QString Statistics::getUserInfo () { QString address = p_profile->user->address(); address.replace("\n", "
"); @@ -600,61 +602,45 @@ QString Statistics::htmlHeader(bool showheader) } } + return userinfo; +} +const QString table_width = "width=99%"; + +// Create the page header in HTML. Includes everything from through +QString Statistics::htmlHeader(bool showheader) +{ QString html = QString("")+ "" - "" - "" - "" - "" ""; - QPixmap logoPixmap(":/icons/logo-md.png"); + QPixmap logoPixmap(":/icons/logo-lg.png"); + if (showheader) { - html += "
" - +resizeHTMLPixmap(logoPixmap,64,64)+"
" - "" + STR_TR_OSCAR + "
" - "" + tr("Usage Statistics") + "
"; -// "" + tr("(NOT approved for compliance or medical reporting purposes)")+"
"; - if (!userinfo.isEmpty()) - html += "
"+userinfo; - html += "

"; - } + html += "
" + "" + "" + "" + "" + "" + "
" + getUserInfo() + "" + "" + STR_TR_OSCAR + "   
" + "" + QObject::tr("Usage Statistics") + "   " + "
" + resizeHTMLPixmap(logoPixmap,120,120)+"   
" + "
" + "

"; + } return html; } + +// HTML for page footer QString Statistics::htmlFooter(bool showinfo) { QString html; if (showinfo) { - html += "
"; + html += "
"; html += tr("This report was generated by OSCAR v%1").arg(ShortVersionString) + "
" +tr("OSCAR is free open-source CPAP report software"); html += "
"; @@ -665,7 +651,8 @@ QString Statistics::htmlFooter(bool showinfo) } - +// Calculate AHI for a period as total # of events / total hours used +// Add RERA if calculating RDI instead of just AHI EventDataType calcAHI(QDate start, QDate end) { EventDataType val = (p_profile->calcCount(CPAP_Obstructive, MT_CPAP, start, end) @@ -688,6 +675,7 @@ EventDataType calcAHI(QDate start, QDate end) return val; } +// Calculate flow limits per hour EventDataType calcFL(QDate start, QDate end) { EventDataType val = (p_profile->calcCount(CPAP_FlowLimit, MT_CPAP, start, end)); @@ -702,6 +690,7 @@ EventDataType calcFL(QDate start, QDate end) return val; } +// Calculate ...(what are these?) EventDataType calcSA(QDate start, QDate end) { EventDataType val = (p_profile->calcCount(CPAP_SensAwake, MT_CPAP, start, end)); @@ -716,7 +705,7 @@ EventDataType calcSA(QDate start, QDate end) return val; } - +// Structure for recording Prescription Changes (now called Machine Settings Changes) struct RXChange { RXChange() { highlight = 0; machine = nullptr; } RXChange(const RXChange ©) { @@ -767,6 +756,7 @@ struct UsageData { EventDataType ahi; EventDataType hours; }; + bool operator <(const UsageData &c1, const UsageData &c2) { if (c1.ahi < c2.ahi) { @@ -872,7 +862,7 @@ QString Statistics::GenerateRXChanges() QString html = "

"; html += QString(""); html += ""; - html += ""; + html += ""; // QString extratxt; @@ -947,6 +937,25 @@ QString Statistics::GenerateRXChanges() return html; } +// Report no data available +QString Statistics::htmlNoData() +{ + QString html = "
"; + html += QString( "


" + tr("I can haz data?!?") + "

"+ + "

" + "

"+tr("Oscar has no data to report :(")+"

"); + return html; +} + +// Get RDI or AHI text depending on user preferences +QString Statistics::getRDIorAHIText() { + if (p_profile->general->calculateRDI()) { + return STR_TR_RDI; + } + return STR_TR_AHI; +} + +// Create the HTML that will be the Statistics page. QString Statistics::GenerateHTML() { QList cpap_machines = p_profile->GetMachines(MT_CPAP); @@ -956,6 +965,7 @@ QString Statistics::GenerateHTML() mach.append(cpap_machines); mach.append(oximeters); + // Go through all CPAP and Oximeter machines and see if any data is present bool havedata = false; for (int i=0; i < mach.size(); ++i) { int daysize = mach[i]->day.size(); @@ -965,69 +975,52 @@ QString Statistics::GenerateHTML() } } - + // Create HTML header and statement QString html = htmlHeader(havedata); - // return ""; - + // If we don't have any data, return HTML that says that and we are done + if (!havedata) { + html += htmlNoData(); + html += htmlFooter(havedata); + return html; + } // Find first and last days with valid CPAP data QDate lastcpap = p_profile->LastGoodDay(MT_CPAP); QDate firstcpap = p_profile->FirstGoodDay(MT_CPAP); - + // Get dates for standard report (last week, month, 6 months, year) QDate cpapweek = lastcpap.addDays(-6); QDate cpapmonth = lastcpap.addDays(-29); QDate cpap6month = lastcpap.addMonths(-6); QDate cpapyear = lastcpap.addMonths(-12); - if (cpapweek < firstcpap) { cpapweek = firstcpap; } - if (cpapmonth < firstcpap) { cpapmonth = firstcpap; } + // but not before the first available date of course + if (cpapweek < firstcpap) { cpapweek = firstcpap; } + if (cpapmonth < firstcpap) { cpapmonth = firstcpap; } if (cpap6month < firstcpap) { cpap6month = firstcpap; } - if (cpapyear < firstcpap) { cpapyear = firstcpap; } + if (cpapyear < firstcpap) { cpapyear = firstcpap; } - if (!havedata) { -// html += "
" + tr("Changes to Prescription Settings") + "
" + tr("Changes to Machine Settings") + "
"; - html += "
"; -// html += QString("
") + - html += QString( "


" + tr("I can haz data?!?") + "

"+ - "

" - "

"+tr("Oscar has no data to report :(")+"

"); + QString ahitxt = getRDIorAHIText(); -// "
"; - html += htmlFooter(havedata); - return html; - } - - - // int cpapdays = p_profile->countDays(MT_CPAP, firstcpap, lastcpap); - -// CPAPMode cpapmode = (CPAPMode)(int)p_profile->calcSettingsMax(CPAP_Mode, MT_CPAP, firstcpap, lastcpap); - - // float percentile = p_profile->general->prefCalcPercentile() / 100.0; - - // int mididx=p_profile->general->prefCalcMiddle(); - // SummaryType ST_mid; - // if (mididx==0) ST_mid=ST_PERC; - // if (mididx==1) ST_mid=ST_WAVG; - // if (mididx==2) ST_mid=ST_AVG; - - QString ahitxt; - - if (p_profile->general->calculateRDI()) { - ahitxt = STR_TR_RDI; - } else { - ahitxt = STR_TR_AHI; - } - - // int decimals = 2; + // Prepare top of table html += "
"; html += ""; + // Compute number of monthly periods for a monthly rather than standard time distribution int number_periods = 0; - if (p_profile->general->statReportMode() == 1) { - number_periods = p_profile->FirstDay().daysTo(p_profile->LastDay()) / 30; + if (p_profile->general->statReportMode() == STAT_MODE_MONTHLY) { + QDate beginDate = qMax(firstcpap, lastcpap.addYears(-1)); + int beginMonth = beginDate.month(); + int lastMonth = lastcpap.month(); + if (lastMonth < beginMonth) lastMonth += 12; // handle time extending to next year + number_periods = lastMonth - beginMonth + 1; + if (number_periods < 1) { + qDebug() << "*** Begin" << beginDate << "beginMonth" << beginMonth << "lastMonth" << lastMonth << "periods" << number_periods; + number_periods = 1; + } + // But not more than one year if (number_periods > 12) { number_periods = 12; } @@ -1037,8 +1030,8 @@ QString Statistics::GenerateHTML() QList periods; - bool skipsection = false;; + // Loop through all rows of the Statistics report for (QList::iterator i = rows.begin(); i != rows.end(); ++i) { StatisticsRow &row = (*i); QString name; @@ -1047,14 +1040,15 @@ QString Statistics::GenerateHTML() last = p_profile->LastGoodDay(row.type); first = p_profile->FirstGoodDay(row.type); + // Clear the periods (columns) periods.clear(); - if (p_profile->general->statReportMode() == 0) { + if (p_profile->general->statReportMode() == STAT_MODE_STANDARD) { periods.push_back(Period(last,last,tr("Most Recent"))); periods.push_back(Period(qMax(last.addDays(-6), first), last, tr("Last Week"))); periods.push_back(Period(qMax(last.addDays(-29),first), last, tr("Last 30 Days"))); periods.push_back(Period(qMax(last.addMonths(-6), first), last, tr("Last 6 Months"))); periods.push_back(Period(qMax(last.addMonths(-12), first), last, tr("Last Year"))); - } else { + } else { // STAT_MODE_MONTHLY or STAT_MODE_RANGE QDate l=last,s=last; periods.push_back(Period(last,last,tr("Last Session"))); @@ -1157,7 +1151,7 @@ QString Statistics::GenerateHTML() int np = periods.size(); int width; for (int j=0; j < np; j++) { - if (p_profile->general->statReportMode() == 1) { + if (p_profile->general->statReportMode() == STAT_MODE_MONTHLY) { width = j < np-1 ? 6 : 100 - (25 + 6*(np-1)); } else { width = 75/np; @@ -1498,7 +1492,9 @@ QString StatisticsRow::value(QDate start, QDate end) value = QString("%1").arg(formatTime(p_profile->calcHours(type, start, end) / days)); } else if (calc == SC_COMPLIANCE) { float c = p_profile->countCompliantDays(type, start, end); - float p = (100.0 / days) * c; +// float p = (100.0 / days) * c; + float realDays = qAbs(start.daysTo(end)) + 1; + float p = (100.0 / realDays) * c; value = QString("%1%").arg(p, 0, 'f', 0); } else if (calc == SC_DAYS) { value = QString("%1").arg(p_profile->countDays(type, start, end)); diff --git a/oscar/statistics.h b/oscar/statistics.h index 1a9c86c2..be33e6d1 100644 --- a/oscar/statistics.h +++ b/oscar/statistics.h @@ -15,10 +15,14 @@ #include "SleepLib/schema.h" #include "SleepLib/machine.h" +//! \brief Type of calculation on one statistics row enum StatCalcType { SC_UNDEFINED=0, SC_COLUMNHEADERS, SC_HEADING, SC_SUBHEADING, SC_MEDIAN, SC_AVG, SC_WAVG, SC_90P, SC_MIN, SC_MAX, SC_CPH, SC_SPH, SC_AHI, SC_HOURS, SC_COMPLIANCE, SC_DAYS, SC_ABOVE, SC_BELOW }; +/*! \struct StatisticsRow + \brief Describes a single row on the statistics page + */ struct StatisticsRow { StatisticsRow() { calc=SC_UNDEFINED; } StatisticsRow(QString src, QString calc, QString type) { @@ -40,6 +44,7 @@ struct StatisticsRow { StatCalcType calc; MachineType type; + //! \brief Looks up calculation type for this row StatCalcType lookupCalc(QString calc) { if (calc.compare("avg",Qt::CaseInsensitive)==0) { @@ -74,6 +79,7 @@ struct StatisticsRow { return SC_UNDEFINED; } + //! \brief Look up machine type MachineType lookupType(QString type) { if (type.compare("cpap", Qt::CaseInsensitive)==0) { @@ -94,6 +100,7 @@ struct StatisticsRow { QString value(QDate start, QDate end); }; +//! \class Prescription (Machine) setting class RXItem { public: RXItem() { @@ -156,6 +163,8 @@ class Statistics : public QObject void saveRXChanges(); void updateRXChanges(); + QString getUserInfo(); + QString getRDIorAHIText(); QString GenerateHTML(); QString GenerateMachineList(); QString GenerateRXChanges(); @@ -164,6 +173,7 @@ class Statistics : public QObject protected: + QString htmlNoData(); QString htmlHeader(bool showheader); QString htmlFooter(bool showinfo=true);