Merge branch 'master' into updater

This commit is contained in:
Guy Scharf 2020-07-04 18:21:33 -07:00
commit 44b7261edd
5 changed files with 179 additions and 185 deletions

View File

@ -7,6 +7,17 @@
<meta content="The OSCAR Team" name="author" > <meta content="The OSCAR Team" name="author" >
</head> </head>
<body> <body>
<p>
<b>Changes and fixes in OSCAR v1.X.Y</b>
<br>Portions of OSCAR are © 2019-2020 by
<i>The OSCAR Team</i></p>
<ul>
<li>[new] Additional Philips Respironics devices tested and fully supported:
<ul>
<li>DreamStation BiPAP autoSV (900X150)</li>
</ul>
<li>[fix] Improve support for rare events in 50-series Philips Respironics devices.</li>
</ul>
<p> <p>
<b>Changes and fixes in OSCAR v1.1.1</b> <b>Changes and fixes in OSCAR v1.1.1</b>
<br>Portions of OSCAR are © 2019-2020 by <br>Portions of OSCAR are © 2019-2020 by

8
README
View File

@ -19,10 +19,10 @@ All systems need a C++ compiler and linker and the QT platform download.
Windows Building: See Building/Windows/BUILD-WIN.md Windows Building: See Building/Windows/BUILD-WIN.md
----------------- -----------------
MacOS Building: See Building/Windows/BUILD-mac.md MacOS Building: See Building/MacOS/BUILD-mac.md
--------------- ---------------
Linux Building: See Building/Windows/BUILD-Linux-md Linux Building: See Building/Linux/BUILD-Linux.md
--------------- ---------------
Software Licensing Information Software Licensing Information
@ -30,9 +30,9 @@ Software Licensing Information
OSCAR is released under the GNU GPL v3 License. Please see below for a note on giving correct attribution OSCAR is released under the GNU GPL v3 License. Please see below for a note on giving correct attribution
in redistribution of derivatives. in redistribution of derivatives.
It is built using Qt SDK (Open Source Edition), available from http://qt.io. It is built using Qt SDK (Open Source Edition), available from https://qt.io.
Redistribution of derivatives ( a note added by Mark Watins ) Redistribution of derivatives ( a note added by Mark Watkins )
----------------------------- -----------------------------
Mark Watkins created this software to help lessen the exploitation of others. Seeing his work being used to exploit others Mark Watkins created this software to help lessen the exploitation of others. Seeing his work being used to exploit others
is incredibly un-motivational, and incredibly disrespectful of all the work he put into this project. is incredibly un-motivational, and incredibly disrespectful of all the work he put into this project.

View File

@ -295,6 +295,7 @@ static const PRS1TestedModel s_PRS1TestedModels[] = {
{ "960T", 5, 2, "BiPAP autoSV Advanced 30 (System One 60 Series)" }, // omits "(System One 60 Series)" on official reports { "960T", 5, 2, "BiPAP autoSV Advanced 30 (System One 60 Series)" }, // omits "(System One 60 Series)" on official reports
{ "900X110", 5, 3, "DreamStation BiPAP autoSV" }, { "900X110", 5, 3, "DreamStation BiPAP autoSV" },
{ "900X120", 5, 3, "DreamStation BiPAP autoSV" }, { "900X120", 5, 3, "DreamStation BiPAP autoSV" },
{ "900X150", 5, 3, "DreamStation BiPAP autoSV" },
{ "1061401", 3, 0, "BiPAP S/T (C Series)" }, { "1061401", 3, 0, "BiPAP S/T (C Series)" },
{ "1061T", 3, 3, "BiPAP S/T 30 (System One 60 Series)" }, { "1061T", 3, 3, "BiPAP S/T 30 (System One 60 Series)" },
@ -3602,23 +3603,33 @@ bool PRS1DataChunk::ParseEventsF0V23()
break; break;
} }
startpos = pos; startpos = pos;
if (code != 0x12) { // This one event has no timestamp in F0V6 if (code != 0x12 && code != 0x01) { // This one event has no timestamp in F0V6
t += data[pos] | (data[pos+1] << 8); elapsed = data[pos] | (data[pos+1] << 8);
if (elapsed > 0x7FFF) UNEXPECTED_VALUE(elapsed, "<32768s"); // check whether this is generally unsigned, since 0x01 isn't
t += elapsed;
pos += 2; pos += 2;
} }
switch (code) { switch (code) {
case 0x00: // ??? So far only seen on 451P and 551P occasionally, usually no more than once per session case 0x00: // Humidifier setting change (logged in summary in 60 series)
// A nonzero delta corresponds to an N-second gap in data (value was 0x85, only seen once). Look for more. ParseHumidifierSetting50Series(data[pos]);
if (sessionid != 122) CHECK_VALUE(data[startpos], 0); // skip the onc occurrence already seen
CHECK_VALUE(data[startpos+1], 0);
if (data[pos] < 0x80 || data[pos] > 0x85) {
UNEXPECTED_VALUE(data[pos], "0x80-0x85");
DUMP_EVENT();
}
if (this->familyVersion == 3) DUMP_EVENT(); if (this->familyVersion == 3) DUMP_EVENT();
break; break;
//case 0x01: // never seen case 0x01: // Time elapsed?
// Only seen once, on a 550P.
// It looks almost like a time-elapsed event 4 found in F0V4 summaries, but
// 0xFFCC looks like it represents a time adjustment of -52 seconds,
// since the subsequent 0x11 statistics event has a time offset of 172 seconds,
// and counting this as -52 seconds results in a total session time that
// matches the summary and waveform data. Very weird.
CHECK_VALUE(data[pos], 0xCC);
CHECK_VALUE(data[pos+1], 0xFF);
elapsed = data[pos] | (data[pos+1] << 8);
if (elapsed & 0x8000) {
elapsed = (~0xFFFF | elapsed); // sign extend 16-bit number to native int
}
t += elapsed;
break;
case 0x02: // Pressure adjustment case 0x02: // Pressure adjustment
// See notes in ParseEventsF0V6. // See notes in ParseEventsF0V6.
this->AddEvent(new PRS1PressureSetEvent(t, data[pos])); this->AddEvent(new PRS1PressureSetEvent(t, data[pos]));
@ -5083,7 +5094,7 @@ bool PRS1DataChunk::ParseSummaryF0V4(void)
CHECK_VALUE(chunk_size, 1); // and the only record in the session. CHECK_VALUE(chunk_size, 1); // and the only record in the session.
if (this->sessionid == 1) UNEXPECTED_VALUE(this->sessionid, ">1"); if (this->sessionid == 1) UNEXPECTED_VALUE(this->sessionid, ">1");
break; break;
case 7: // Humidifier setting change case 7: // Humidifier setting change (logged in events in 50 series)
tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report) tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report)
this->ParseHumidifierSetting60Series(data[pos+2], data[pos+3]); this->ParseHumidifierSetting60Series(data[pos+2], data[pos+3]);
break; break;
@ -6783,8 +6794,7 @@ void PRS1DataChunk::ParseHumidifierSettingV3(unsigned char byte1, unsigned char
// All variations seen. // All variations seen.
} else if (family == 5) { } else if (family == 5) {
if (tubepresent) { if (tubepresent) {
if (tubetemp != 0 && tubetemp > 4) UNEXPECTED_VALUE(tubetemp, "<= 4"); // All tube temperature and humidity levels seen.
// All humidity levels seen.
} else if (humidadaptive) { } else if (humidadaptive) {
// All humidity levels seen. // All humidity levels seen.
} else if (humidfixed) { } else if (humidfixed) {
@ -8689,7 +8699,7 @@ bool PRS1DataChunk::ReadHeader(QFile & f)
// Do a few early sanity checks before any variable-length header data. // Do a few early sanity checks before any variable-length header data.
if (this->blockSize == 0) { if (this->blockSize == 0) {
qWarning() << this->m_path << "blocksize 0?"; qWarning() << this->m_path << "@" << hex << this->m_filepos << "blocksize 0, skipping remainder of file";
break; break;
} }
if (this->fileVersion < 2 || this->fileVersion > 3) { if (this->fileVersion < 2 || this->fileVersion > 3) {

View File

@ -833,57 +833,6 @@ QStringList getDriveList()
} }
} }
} }
#elif defined(Q_OS_MAC) || defined(Q_OS_BSD4)
struct statfs *mounts;
int num_mounts = getmntinfo(&mounts, MNT_WAIT);
if (num_mounts >= 0) {
for (int i = 0; i < num_mounts; i++) {
QString name = mounts[i].f_mntonname;
// Only interested in drives mounted under /Volumes
if (name.toLower().startsWith("/volumes/")) {
drivelist.push_back(name);
// qDebug() << QString("Disk type '%1' mounted at: %2").arg(mounts[i].f_fstypename).arg(mounts[i].f_mntonname);
}
}
}
#elif defined(Q_OS_UNIX) && !defined(Q_OS_HAIKU)
// Unix / Linux (except BSD)
FILE *mtab = setmntent("/etc/mtab", "r");
struct mntent *m;
struct mntent mnt;
char strings[4096];
// NOTE: getmntent_r is a GNU extension, requiring glibc.
while ((m = getmntent_r(mtab, &mnt, strings, sizeof(strings)))) {
struct statfs fs;
if ((mnt.mnt_dir != NULL) && (statfs(mnt.mnt_dir, &fs) == 0)) {
QString name = mnt.mnt_dir;
quint64 size = fs.f_blocks * fs.f_bsize;
if (size > 0) { // this should theoretically ignore /dev, /proc, /sys etc..
drivelist.push_back(name);
}
// quint64 free = fs.f_bfree * fs.f_bsize;
// quint64 avail = fs.f_bavail * fs.f_bsize;
}
}
endmntent(mtab);
#elif defined(Q_OS_WIN) || defined(Q_OS_HAIKU)
QFileInfoList list = QDir::drives();
for (int i = 0; i < list.size(); ++i) {
QFileInfo fileInfo = list.at(i);
QString name = fileInfo.filePath();
if (name.at(0).toUpper() != QChar('C')) { // Ignore the C drive
drivelist.push_back(name);
}
}
#endif #endif
return drivelist; return drivelist;
} }
@ -897,9 +846,18 @@ void ImportDialogScan::cancelbutton()
QList<ImportPath> MainWindow::detectCPAPCards() QList<ImportPath> MainWindow::detectCPAPCards()
{ {
const int timeout = 20000; const int timeout = 20000; // twenty seconds
QList<ImportPath> detectedCards; QList<ImportPath> detectedCards;
importScanCancelled = false;
QString lastpath = (*p_profile)[STR_PREF_LastCPAPPath].toString();
// #if defined (Q_OS_LINUX)
// if (detectedCards.size() == 0) {
// qDebug() << "Skipping card detection on Linux";
// return detectedCards;
// }
// #endif
QList<MachineLoader *>loaders = GetLoaders(MT_CPAP); QList<MachineLoader *>loaders = GetLoaders(MT_CPAP);
QTime time; QTime time;
@ -928,18 +886,20 @@ QList<ImportPath> MainWindow::detectCPAPCards()
progress.setValue(0); progress.setValue(0);
progress.setMaximum(timeout); progress.setMaximum(timeout);
progress.setVisible(true); progress.setVisible(true);
importScanCancelled = false; // importScanCancelled = false;
popup.show(); popup.show();
QApplication::processEvents(); QApplication::processEvents();
QString lastpath = (*p_profile)[STR_PREF_LastCPAPPath].toString(); // QString lastpath = (*p_profile)[STR_PREF_LastCPAPPath].toString();
do { do {
// Rescan in case card inserted // Rescan in case card inserted
QStringList AutoScannerPaths = getDriveList(); QStringList AutoScannerPaths = getDriveList();
// AutoScannerPaths.push_back(lastpath); // AutoScannerPaths.push_back(lastpath);
qDebug() << "Drive list size:" << AutoScannerPaths.size();
if (!AutoScannerPaths.contains(lastpath)) { if ( (lastpath.size()>0) && ( ! AutoScannerPaths.contains(lastpath))) {
AutoScannerPaths.append(lastpath); if (QFile(lastpath).exists())
AutoScannerPaths.insert(0, lastpath);
} }
Q_FOREACH(const QString &path, AutoScannerPaths) { Q_FOREACH(const QString &path, AutoScannerPaths) {
@ -955,10 +915,12 @@ QList<ImportPath> MainWindow::detectCPAPCards()
} }
int el=time.elapsed(); int el=time.elapsed();
progress.setValue(el); progress.setValue(el);
if (el > timeout) break; if (el > timeout)
if (!popup.isVisible()) break; break;
if ( ! popup.isVisible())
break;
// needs a slight delay here // needs a slight delay here
for (int i=0; i<5; ++i) { for (int i=0; i<20; ++i) {
QThread::msleep(50); QThread::msleep(50);
QApplication::processEvents(); QApplication::processEvents();
} }

View File

@ -39,7 +39,7 @@ QString resizeHTMLPixmap(QPixmap &pixmap, int width, int height) {
QBuffer buffer(&byteArray); // use buffer to store pixmap into byteArray QBuffer buffer(&byteArray); // use buffer to store pixmap into byteArray
buffer.open(QIODevice::WriteOnly); buffer.open(QIODevice::WriteOnly);
pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(&buffer, "PNG"); pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(&buffer, "PNG");
return QString("<img src=\"data:image/png;base64,"+byteArray.toBase64()+"\">"); return QString("<img src='data:image/png;base64,"+byteArray.toBase64()+"' ALT='logo'>");
} }
QString formatTime(float time) QString formatTime(float time)
@ -624,39 +624,41 @@ QString Statistics::getUserInfo () {
return ""; return "";
QString address = p_profile->user->address(); QString address = p_profile->user->address();
address.replace("\n", "<br/>"); address.replace("\n", "<br>");
QString userinfo = ""; QString userinfo = "";
if (!p_profile->user->firstName().isEmpty()) { if (!p_profile->user->firstName().isEmpty()) {
userinfo = tr("Name: %1, %2").arg(p_profile->user->lastName()).arg(p_profile->user->firstName()) + "<br/>"; userinfo = tr("Name: %1, %2").arg(p_profile->user->lastName()).arg(p_profile->user->firstName()) + "<br>";
if (!p_profile->user->DOB().isNull()) { if (!p_profile->user->DOB().isNull()) {
userinfo += tr("DOB: %1").arg(p_profile->user->DOB().toString(MedDateFormat)) + "<br/>"; userinfo += tr("DOB: %1").arg(p_profile->user->DOB().toString(MedDateFormat)) + "<br>";
} }
if (!p_profile->user->phone().isEmpty()) { if (!p_profile->user->phone().isEmpty()) {
userinfo += tr("Phone: %1").arg(p_profile->user->phone()) + "<br/>"; userinfo += tr("Phone: %1").arg(p_profile->user->phone()) + "<br>";
} }
if (!p_profile->user->email().isEmpty()) { if (!p_profile->user->email().isEmpty()) {
userinfo += tr("Email: %1").arg(p_profile->user->email()) + "<br/><br/>"; userinfo += tr("Email: %1").arg(p_profile->user->email()) + "<br><br>";
} }
if (!p_profile->user->address().isEmpty()) { if (!p_profile->user->address().isEmpty()) {
userinfo += tr("Address:")+"<br/>"+address; userinfo += tr("Address:")+"<br>"+address;
} }
} }
while (userinfo.length() > 0 && userinfo.endsWith("<br/>")) // Strip trailing newlines while (userinfo.length() > 0 && userinfo.endsWith("<br>")) // Strip trailing newlines
userinfo = userinfo.mid(0, userinfo.length()-5); userinfo = userinfo.mid(0, userinfo.length()-4);
return userinfo; return userinfo;
} }
const QString table_width = "width=100%"; const QString table_width = "width='100%'";
// Create the page header in HTML. Includes everything from <head> through <body> // Create the page header in HTML. Includes everything from <head> through <body>
QString Statistics::generateHeader(bool onScreen) QString Statistics::generateHeader(bool onScreen)
{ {
QString html = QString("<html><head>")+ QString html = QString("<html><head>");
"<style type='text/css'>"; html += "<title>Oscar Statistics Report</title>";
html += "<style type='text/css'>";
if (onScreen) { if (onScreen) {
html += "p,a,td,body { font-family: '" + QApplication::font().family() + "'; }" html += "p,a,td,body { font-family: '" + QApplication::font().family() + "'; }"
"p,a,td,body { font-size: " + QString::number(QApplication::font().pointSize() + 2) + "px; }"; "p,a,td,body { font-size: " + QString::number(QApplication::font().pointSize() + 2) + "px; }";
@ -685,7 +687,7 @@ QString Statistics::generateHeader(bool onScreen)
"</style>" "</style>"
"<link rel='stylesheet' type='text/css' href='qrc:/docs/tooltips.css' />" "<link rel='stylesheet' type='text/css' href='qrc:/docs/tooltips.css' >"
"<script type='text/javascript'>" "<script type='text/javascript'>"
"function ChangeColor(tableRow, highLight)" "function ChangeColor(tableRow, highLight)"
@ -695,18 +697,19 @@ QString Statistics::generateHeader(bool onScreen)
"</head>" "</head>"
"<body leftmargin=0 topmargin=5 rightmargin=0>"; "<body>"; //leftmargin=0 topmargin=5 rightmargin=0>";
QPixmap logoPixmap(":/icons/logo-lg.png"); QPixmap logoPixmap(":/icons/logo-lg.png");
html += "<div align=center><table class=curved width='100%'>" // html += "<div align=center><table class=curved width='100%'>"
html += "<div align=center><table class=curved " + table_width + ">"
"<tr>" "<tr>"
"<td align='left' valign='middle'>" + getUserInfo() + "</td>" "<td align='left' valign='middle'>" + getUserInfo() + "</td>"
"<td align='right' valign='middle' width='200'>" "<td align='right' valign='middle' width='200'>"
"<font size='+2'>" + STR_TR_OSCAR + "&nbsp;&nbsp;&nbsp;</font><br/>" "<font size='+2'>" + STR_TR_OSCAR + "&nbsp;&nbsp;&nbsp;</font><br>"
"<font size='+1'>" + QObject::tr("Usage Statistics") + "&nbsp;&nbsp;&nbsp;</font>" "<font size='+1'>" + QObject::tr("Usage Statistics") + "&nbsp;&nbsp;&nbsp;</font>"
"</td>" "</td>"
"<td align='right' valign='middle' width='110'>" + resizeHTMLPixmap(logoPixmap,80,80)+"&nbsp;&nbsp;&nbsp;<br/>" "<td align='right' valign='middle' width='110'>" + resizeHTMLPixmap(logoPixmap,80,80)+"&nbsp;&nbsp;&nbsp;<br>"
"</td>" "</td>"
"</tr>" "</tr>"
"</table>" "</table>"
@ -725,7 +728,7 @@ QString Statistics::generateFooter(bool showinfo)
QDateTime timestamp = QDateTime::currentDateTime(); QDateTime timestamp = QDateTime::currentDateTime();
html += tr("This report was prepared on %1 by OSCAR %2").arg(timestamp.toString(MedDateFormat + " hh:mm")) html += tr("This report was prepared on %1 by OSCAR %2").arg(timestamp.toString(MedDateFormat + " hh:mm"))
.arg(getVersion()) .arg(getVersion())
+ "<br/>" + "<br>"
+ tr("OSCAR is free open-source CPAP report software"); + tr("OSCAR is free open-source CPAP report software");
html += "</i></font></div>"; html += "</i></font></div>";
} }
@ -895,12 +898,12 @@ QString Statistics::GenerateMachineList()
QString html; QString html;
if (mach.size() > 0) { if (mach.size() > 0) {
html += "<div align=center><br/>"; html += "<div align=center><br>";
html += QString("<table class=curved style=\"page-break-before:auto;\" "+table_width+">"); html += QString("<table class=curved style='page-break-before:auto' "+table_width+">");
html += "<thead>"; html += "<thead>";
html += "<tr bgcolor='"+heading_color+"'><th colspan=7 align=center><font size=+2>" + tr("Machine Information") + "</font></th></tr>"; html += "<tr bgcolor='"+heading_color+"'><th colspan=7 align=center><font size='+2'>" + tr("Machine Information") + "</font></th></tr>";
html += QString("<tr><td><b>%1</b></td><td><b>%2</b></td><td><b>%3</b></td><td><b>%4</b></td><td><b>%5</b></td></tr>") html += QString("<tr><td><b>%1</b></td><td><b>%2</b></td><td><b>%3</b></td><td><b>%4</b></td><td><b>%5</b></td></tr>")
.arg(STR_TR_Brand) .arg(STR_TR_Brand)
@ -956,10 +959,10 @@ QString Statistics::GenerateRXChanges()
} }
QString html = "<div align=center><br/>"; QString html = "<div align=center><br>";
html += QString("<table class=curved style=\"page-break-before:always;\" "+table_width+">"); html += QString("<table class=curved style='page-break-before:always' " + table_width+">");
html += "<thead>"; html += "<thead>";
html += "<tr bgcolor='"+heading_color+"'><th colspan=9 align=center><font size=+2>" + tr("Changes to Machine Settings") + "</font></th></tr>"; html += "<tr bgcolor='"+heading_color+"'><th colspan=9 align=center><font size='+2'>" + tr("Changes to Machine Settings") + "</font></th></tr>";
// QString extratxt; // QString extratxt;
@ -975,17 +978,17 @@ QString Statistics::GenerateRXChanges()
hdrlist.push_back(STR_TR_Mode); hdrlist.push_back(STR_TR_Mode);
hdrlist.push_back(tr("Pressure Settings")); hdrlist.push_back(tr("Pressure Settings"));
html+="<tr>\n"; html+="<tr>";
for (int i=0; i < hdrlist.size(); ++i) { for (int i=0; i < hdrlist.size(); ++i) {
html+=QString(" <th align=left><b>%1</b></th>\n").arg(hdrlist.at(i)); html+=QString(" <th align=left><b>%1</b></th>").arg(hdrlist.at(i));
} }
html+="</tr>\n"; html+="</tr>";
html += "</thead>"; html += "</thead>";
// html += "<tfoot>"; // html += "<tfoot>";
// html += "<tr><td colspan=10 align=center>"; // html += "<tr><td colspan=10 align=center>";
// html += QString("<i>") + // html += QString("<i>") +
// tr("Efficacy highlighting ignores prescription settings with less than %1 days of recorded data."). // tr("Efficacy highlighting ignores prescription settings with less than %1 days of recorded data.").
// arg(rxthresh) + QString("</i><br/>"); // arg(rxthresh) + QString("</i><br>");
// html += "</td></tr>"; // html += "</td></tr>";
// html += "</tfoot>"; // html += "</tfoot>";
@ -1045,8 +1048,8 @@ QString Statistics::GenerateRXChanges()
QString Statistics::htmlNoData() QString Statistics::htmlNoData()
{ {
QString html = "<div align=center>"; QString html = "<div align=center>";
html += QString( "<p><font size=\"+3\"><br />" + tr("No data found?!?") + "</font></p>"+ html += QString( "<p><font size=\"+3\"><br>" + tr("No data found?!?") + "</font></p>"+
"<p><img src='qrc:/icons/logo-lm.png' width=\"100\" height=\"100\"></p>" "<p><img src='qrc:/icons/logo-lm.png' alt='logo' width='100' height='100'></p>"
"<p><i>"+tr("Oscar has no data to report :(")+"</i></p>"); "<p><i>"+tr("Oscar has no data to report :(")+"</i></p>");
return html; return html;
} }
@ -1107,7 +1110,7 @@ QString Statistics::GenerateCPAPUsage()
// Prepare top of table // Prepare top of table
html += "<div align=center>"; html += "<div align=center>";
html += "<table class=curved width="+table_width+">"; html += "<table class=curved "+table_width+">";
// Compute number of monthly periods for a monthly rather than standard time distribution // Compute number of monthly periods for a monthly rather than standard time distribution
int number_periods = 0; int number_periods = 0;
@ -1179,7 +1182,7 @@ QString Statistics::GenerateCPAPUsage()
int days = p_profile->countDays(row.type, first, last); int days = p_profile->countDays(row.type, first, last);
skipsection = (days == 0); skipsection = (days == 0);
if (days > 0) { if (days > 0) {
html+=QString("<tr bgcolor='%1'><th colspan=%2 align=center><font size=+2>%3</font></th></tr>\n"). html+=QString("<tr bgcolor='%1'><th colspan=%2 align=center><font size='+2'>%3</font></th></tr>").
arg(heading_color).arg(periods.size()+1).arg(row.src); arg(heading_color).arg(periods.size()+1).arg(row.src);
} }
continue; continue;
@ -1195,11 +1198,11 @@ QString Statistics::GenerateCPAPUsage()
} else if (row.calc == SC_COMPLIANCE) { } else if (row.calc == SC_COMPLIANCE) {
name = QString(row.src).arg(p_profile->cpap->m_complianceHours); name = QString(row.src).arg(p_profile->cpap->m_complianceHours);
} else if (row.calc == SC_COLUMNHEADERS) { } else if (row.calc == SC_COLUMNHEADERS) {
html += QString("<tr><td><b>%1</b></td>\n").arg(tr("Details")); html += QString("<tr><td><b>%1</b></td>").arg(tr("Details"));
for (int j=0; j < periods.size(); j++) { for (int j=0; j < periods.size(); j++) {
html += QString("<td onmouseover='ChangeColor(this, \"#eeeeee\");' onmouseout='ChangeColor(this, \"#ffffff\");' onclick='alert(\"overview=%1,%2\");'><b>%3</b></td>\n").arg(periods.at(j).start.toString(Qt::ISODate)).arg(periods.at(j).end.toString(Qt::ISODate)).arg(periods.at(j).header); html += QString("<td onmouseover='ChangeColor(this, \"#eeeeee\");' onmouseout='ChangeColor(this, \"#ffffff\");' onclick='alert(\"overview=%1,%2\");'><b>%3</b></td>").arg(periods.at(j).start.toString(Qt::ISODate)).arg(periods.at(j).end.toString(Qt::ISODate)).arg(periods.at(j).header);
} }
html += "</tr>\n"; html += "</tr>";
continue; continue;
} else if (row.calc == SC_DAYS) { } else if (row.calc == SC_DAYS) {
QDate first=p_profile->FirstGoodDay(row.type); QDate first=p_profile->FirstGoodDay(row.type);
@ -1208,16 +1211,16 @@ QString Statistics::GenerateCPAPUsage()
int value=p_profile->countDays(row.type, first, last); int value=p_profile->countDays(row.type, first, last);
if (value == 0) { if (value == 0) {
html+=QString("<tr><td colspan=%1 align=center>%2</td></tr>\n").arg(periods.size()+1). html+=QString("<tr><td colspan=%1 align=center>%2</td></tr>").arg(periods.size()+1).
arg(tr("No %1 data available.").arg(machine)); arg(tr("No %1 data available.").arg(machine));
} else if (value == 1) { } else if (value == 1) {
html+=QString("<tr><td colspan=%1 align=center>%2</td></tr>\n").arg(periods.size()+1). html+=QString("<tr><td colspan=%1 align=center>%2</td></tr>").arg(periods.size()+1).
arg(tr("%1 day of %2 Data on %3") arg(tr("%1 day of %2 Data on %3")
.arg(value) .arg(value)
.arg(machine) .arg(machine)
.arg(last.toString(MedDateFormat))); .arg(last.toString(MedDateFormat)));
} else { } else {
html+=QString("<tr><td colspan=%1 align=center>%2</td></tr>\n").arg(periods.size()+1). html+=QString("<tr><td colspan=%1 align=center>%2</td></tr>").arg(periods.size()+1).
arg(tr("%1 days of %2 Data, between %3 and %4") arg(tr("%1 days of %2 Data, between %3 and %4")
.arg(value) .arg(value)
.arg(machine) .arg(machine)
@ -1226,7 +1229,7 @@ QString Statistics::GenerateCPAPUsage()
} }
continue; continue;
} else if (row.calc == SC_SUBHEADING) { // subheading.. } else if (row.calc == SC_SUBHEADING) { // subheading..
html+=QString("<tr bgcolor='%1'><td colspan=%2 align=center><b>%3</b></td></tr>\n"). html+=QString("<tr bgcolor='%1'><td colspan=%2 align=center><b>%3</b></td></tr>").
arg(subheading_color).arg(periods.size()+1).arg(row.src); arg(subheading_color).arg(periods.size()+1).arg(row.src);
continue; continue;
} else if (row.calc == SC_UNDEFINED) { } else if (row.calc == SC_UNDEFINED) {
@ -1239,20 +1242,18 @@ QString Statistics::GenerateCPAPUsage()
name = calcnames[row.calc].arg(schema::channel[id].fullname()); name = calcnames[row.calc].arg(schema::channel[id].fullname());
} }
QString line; QString line;
line += QString("<tr class=datarow><td width=22%>%1</td>").arg(name);
int np = periods.size(); int np = periods.size();
int width; int width;
for (int j=0; j < np; j++) { int dataWidth = 14;
/*** int headerWidth = 30;
if (p_profile->general->statReportMode() == STAT_MODE_MONTHLY) { if (p_profile->general->statReportMode() == STAT_MODE_MONTHLY) {
width = j < np-1 ? 6 : 100 - (22 + 6*(np-1)); dataWidth = 6;
} else { headerWidth = 22;
width = 78/np;
} }
***/ line += QString("<tr class=datarow><td width='%1%'>%2</td>").arg(headerWidth).arg(name);
width = 78/np; for (int j=0; j < np; j++) {
width = j < np-1 ? dataWidth : 100 - (headerWidth + dataWidth*(np-1));
line += QString("<td width=%1%>").arg(width); line += QString("<td width='%1%'>").arg(width);
if (!periods.at(j).header.isEmpty()) { if (!periods.at(j).header.isEmpty()) {
line += row.value(periods.at(j).start, periods.at(j).end); line += row.value(periods.at(j).start, periods.at(j).end);
} else { } else {
@ -1261,7 +1262,7 @@ QString Statistics::GenerateCPAPUsage()
line += "</td>"; line += "</td>";
} }
html += line; html += line;
html += "</tr>\n"; html += "</tr>";
} }
html += "</table>"; html += "</table>";
@ -1311,7 +1312,11 @@ void Statistics::printReport(QWidget * parent) {
printer.setOrientation(QPrinter::Portrait); printer.setOrientation(QPrinter::Portrait);
printer.setFullPage(false); // Print only on printable area of page and not in non-printable margins printer.setFullPage(false); // Print only on printable area of page and not in non-printable margins
printer.setNumCopies(1); printer.setNumCopies(1);
printer.setPageMargins(10, 10, 10, 10, QPrinter::Millimeter);
QMarginsF minMargins = printer.pageLayout().margins(QPageLayout::Millimeter);
printer.setPageMargins(fmax(10,minMargins.left()), fmax(10,minMargins.top()), fmax(10,minMargins.right()), fmax(12,minMargins.bottom()), QPrinter::Millimeter);
QMarginsF setMargins = printer.pageLayout().margins(QPageLayout::Millimeter);
qDebug () << "Min margins" << minMargins << "Set margins" << setMargins << "millimeters";
// Show print dialog to user and allow them to change settings as desired // Show print dialog to user and allow them to change settings as desired
QPrintDialog pdlg(&printer, parent); QPrintDialog pdlg(&printer, parent);
@ -1320,12 +1325,12 @@ void Statistics::printReport(QWidget * parent) {
QTextDocument doc; QTextDocument doc;
QSizeF printArea = printer.pageRect(QPrinter::Point).size(); QSizeF printArea = printer.pageRect(QPrinter::Point).size();
QSizeF originalPrintArea = printArea;
printArea.setWidth(printArea.width()*2); // scale up for better font appearance printArea.setWidth(printArea.width()*2); // scale up for better font appearance
printArea.setHeight(printArea.height()*2); printArea.setHeight(printArea.height()*2);
doc.setPageSize(printArea); // Set document to print area, in pixels, removing default 2cm margins doc.setPageSize(printArea); // Set document to print area, in pixels, removing default 2cm margins
qDebug() << "print area (in points)" << printArea; qDebug() << "print area (points)" << originalPrintArea << "Enlarged print area" << printArea << "paper size" << printer.paperRect(QPrinter::Point).size();
qDebug() << "page area (in points)" << printer.paperRect(QPrinter::Point).size();
// Determine appropriate font and font size // Determine appropriate font and font size
QFont font = QFont("Helvetica"); QFont font = QFont("Helvetica");
@ -1337,10 +1342,14 @@ void Statistics::printReport(QWidget * parent) {
font.setPointSize(round(pointSize)); // Scale the font font.setPointSize(round(pointSize)); // Scale the font
doc.setDefaultFont(font); doc.setDefaultFont(font);
qDebug() << "Printer font set to" << font << "and printer default font is now" << doc.defaultFont(); qDebug() << "Enlarged printer font" << font << "printer default font set" << doc.defaultFont();
doc.setHtml(htmlReportHeaderPrint + htmlUsage + htmlMachineSettings + htmlMachines + htmlReportFooter); doc.setHtml(htmlReportHeaderPrint + htmlUsage + htmlMachineSettings + htmlMachines + htmlReportFooter);
// Dump HTML for use with HTML4 validator
// QString html = htmlReportHeaderPrint + htmlUsage + htmlMachineSettings + htmlMachines + htmlReportFooter;
// qDebug() << "Html:" << html;
doc.print(&printer); doc.print(&printer);
} }
} }
@ -1352,7 +1361,9 @@ QString Statistics::UpdateRecordsBox()
"p,a,td,body { font-size: " + QString::number(QApplication::font().pointSize() + 2) + "px; }" "p,a,td,body { font-size: " + QString::number(QApplication::font().pointSize() + 2) + "px; }"
"a:link,a:visited { color: inherit; text-decoration: none; }" //font-weight: normal; "a:link,a:visited { color: inherit; text-decoration: none; }" //font-weight: normal;
"a:hover { background-color: inherit; color: white; text-decoration:none; font-weight: bold; }" "a:hover { background-color: inherit; color: white; text-decoration:none; font-weight: bold; }"
"</style></head><body>"; "</style>"
"<title>Machine Statistics Panel</title>"
"</head><body>";
Machine * cpap = p_profile->GetMachine(MT_CPAP); Machine * cpap = p_profile->GetMachine(MT_CPAP);
if (cpap) { if (cpap) {
@ -1367,10 +1378,10 @@ QString Statistics::UpdateRecordsBox()
float comperc = (100.0 / float(totaldays)) * float(compliant); float comperc = (100.0 / float(totaldays)) * float(compliant);
html += "<b>"+tr("CPAP Usage")+"</b><br/>"; html += "<b>"+tr("CPAP Usage")+"</b><br>";
html += tr("Days Used: %1").arg(totaldays) + "<br/>"; html += tr("Days Used: %1").arg(totaldays) + "<br>";
html += tr("Low Use Days: %1").arg(totaldays - compliant) + "<br/>"; html += tr("Low Use Days: %1").arg(totaldays - compliant) + "<br>";
html += tr("Compliance: %1%").arg(comperc, 0, 'f', 1) + "<br/>"; html += tr("Compliance: %1%").arg(comperc, 0, 'f', 1) + "<br>";
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
/// AHI Records /// AHI Records
@ -1394,34 +1405,34 @@ QString Statistics::UpdateRecordsBox()
} }
ahilist.insert(ahi, date); ahilist.insert(ahi, date);
} }
html += tr("Days AHI of 5 or greater: %1").arg(baddays) + "<br/><br/>"; html += tr("Days AHI of 5 or greater: %1").arg(baddays) + "<br><br>";
if (ahilist.size() > (show_records * 2)) { if (ahilist.size() > (show_records * 2)) {
it = ahilist.begin(); it = ahilist.begin();
it_end = ahilist.end(); it_end = ahilist.end();
html += "<b>"+tr("Best AHI")+"</b><br/>"; html += "<b>"+tr("Best AHI")+"</b><br>";
for (int i=0; (i<show_records) && (it != it_end); ++i, ++it) { for (int i=0; (i<show_records) && (it != it_end); ++i, ++it) {
html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate)) html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate))
+tr("Date: %1 AHI: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br/>"; +tr("Date: %1 AHI: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br>";
} }
html += "<br/>"; html += "<br>";
html += "<b>"+tr("Worst AHI")+"</b><br/>"; html += "<b>"+tr("Worst AHI")+"</b><br>";
it = ahilist.end() - 1; it = ahilist.end() - 1;
it_end = ahilist.begin(); it_end = ahilist.begin();
for (int i=0; (i<show_records) && (it != it_end); ++i, --it) { for (int i=0; (i<show_records) && (it != it_end); ++i, --it) {
html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate)) html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate))
+tr("Date: %1 AHI: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br/>"; +tr("Date: %1 AHI: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br>";
} }
html += "<br/>"; html += "<br>";
} }
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
@ -1448,32 +1459,32 @@ QString Statistics::UpdateRecordsBox()
it = ahilist.begin(); it = ahilist.begin();
it_end = ahilist.end(); it_end = ahilist.end();
html += "<b>"+tr("Best Flow Limitation")+"</b><br/>"; html += "<b>"+tr("Best Flow Limitation")+"</b><br>";
for (int i=0; (i<show_records) && (it != it_end); ++i, ++it) { for (int i=0; (i<show_records) && (it != it_end); ++i, ++it) {
html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate)) html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate))
+tr("Date: %1 FL: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br/>"; +tr("Date: %1 FL: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br>";
} }
html += "<br/>"; html += "<br>";
html += "<b>"+tr("Worst Flow Limtation")+"</b><br/>"; html += "<b>"+tr("Worst Flow Limtation")+"</b><br>";
it = ahilist.end() - 1; it = ahilist.end() - 1;
it_end = ahilist.begin(); it_end = ahilist.begin();
for (int i=0; (i<show_records) && (it != it_end); ++i, --it) { for (int i=0; (i<show_records) && (it != it_end); ++i, --it) {
if (it.key() > 0) { if (it.key() > 0) {
html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate)) html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate))
+tr("Date: %1 FL: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br/>"; +tr("Date: %1 FL: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br>";
cnt++; cnt++;
} }
} }
if (cnt == 0) { if (cnt == 0) {
html+= "<i>"+tr("No Flow Limitation on record")+"</i><br/>"; html+= "<i>"+tr("No Flow Limitation on record")+"</i><br>";
} }
html += "<br/>"; html += "<br>";
} }
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
@ -1491,7 +1502,7 @@ QString Statistics::UpdateRecordsBox()
cnt = 0; cnt = 0;
if (ahilist.size() > (show_records * 2)) { if (ahilist.size() > (show_records * 2)) {
html += "<b>"+tr("Worst Large Leaks")+"</b><br/>"; html += "<b>"+tr("Worst Large Leaks")+"</b><br>";
it = ahilist.end() - 1; it = ahilist.end() - 1;
it_end = ahilist.begin(); it_end = ahilist.begin();
@ -1499,16 +1510,16 @@ QString Statistics::UpdateRecordsBox()
for (int i=0; (i<show_records) && (it != it_end); ++i, --it) { for (int i=0; (i<show_records) && (it != it_end); ++i, --it) {
if (it.key() > 0) { if (it.key() > 0) {
html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate)) html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate))
+tr("Date: %1 Leak: %2%").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br/>"; +tr("Date: %1 Leak: %2%").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br>";
cnt++; cnt++;
} }
} }
if (cnt == 0) { if (cnt == 0) {
html+= "<i>"+tr("No Large Leaks on record")+"</i><br/>"; html+= "<i>"+tr("No Large Leaks on record")+"</i><br>";
} }
html += "<br/>"; html += "<br>";
} }
@ -1528,7 +1539,7 @@ QString Statistics::UpdateRecordsBox()
} }
if (ahilist.size() > (show_records * 2)) { if (ahilist.size() > (show_records * 2)) {
html += "<b>"+tr("Worst CSR")+"</b><br/>"; html += "<b>"+tr("Worst CSR")+"</b><br>";
it = ahilist.end() - 1; it = ahilist.end() - 1;
it_end = ahilist.begin(); it_end = ahilist.begin();
@ -1536,15 +1547,15 @@ QString Statistics::UpdateRecordsBox()
if (it.key() > 0) { if (it.key() > 0) {
html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate)) html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate))
+tr("Date: %1 CSR: %2%").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br/>"; +tr("Date: %1 CSR: %2%").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br>";
cnt++; cnt++;
} }
} }
if (cnt == 0) { if (cnt == 0) {
html+= "<i>"+tr("No CSR on record")+"</i><br/>"; html+= "<i>"+tr("No CSR on record")+"</i><br>";
} }
html += "<br/>"; html += "<br>";
} }
} }
if (p_profile->hasChannel(CPAP_PB)) { if (p_profile->hasChannel(CPAP_PB)) {
@ -1558,7 +1569,7 @@ QString Statistics::UpdateRecordsBox()
} }
if (ahilist.size() > (show_records * 2)) { if (ahilist.size() > (show_records * 2)) {
html += "<b>"+tr("Worst PB")+"</b><br/>"; html += "<b>"+tr("Worst PB")+"</b><br>";
it = ahilist.end() - 1; it = ahilist.end() - 1;
it_end = ahilist.begin(); it_end = ahilist.begin();
@ -1566,22 +1577,22 @@ QString Statistics::UpdateRecordsBox()
if (it.key() > 0) { if (it.key() > 0) {
html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate)) html += QString("<a href='daily=%1'>").arg(it.value().toString(Qt::ISODate))
+tr("Date: %1 PB: %2%").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br/>"; +tr("Date: %1 PB: %2%").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "</a><br>";
cnt++; cnt++;
} }
} }
if (cnt == 0) { if (cnt == 0) {
html+= "<i>"+tr("No PB on record")+"</i><br/>"; html+= "<i>"+tr("No PB on record")+"</i><br>";
} }
html += "<br/>"; html += "<br>";
} }
} }
} else { } else {
html += "<br/><b>"+tr("Want more information?")+"</b><br/>"; html += "<br><b>"+tr("Want more information?")+"</b><br>";
html += "<i>"+tr("OSCAR needs all summary data loaded to calculate best/worst data for individual days.")+"</i><br/><br/>"; html += "<i>"+tr("OSCAR needs all summary data loaded to calculate best/worst data for individual days.")+"</i><br><br>";
html += "<i>"+tr("Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available.")+"</i><br/><br/>"; html += "<i>"+tr("Please enable Pre-Load Summaries checkbox in preferences to make sure this data is available.")+"</i><br><br>";
} }
@ -1601,29 +1612,29 @@ QString Statistics::UpdateRecordsBox()
if (list.size() >= 2) { if (list.size() >= 2) {
html += "<b>"+tr("Best RX Setting")+"</b><br/>"; html += "<b>"+tr("Best RX Setting")+"</b><br>";
const RXItem & rxbest = *list.at(0); const RXItem & rxbest = *list.at(0);
html += QString("<a href='overview=%1,%2'>").arg(rxbest.start.toString(Qt::ISODate)).arg(rxbest.end.toString(Qt::ISODate)) + html += QString("<a href='overview=%1,%2'>").arg(rxbest.start.toString(Qt::ISODate)).arg(rxbest.end.toString(Qt::ISODate)) +
tr("Date: %1 - %2").arg(rxbest.start.toString(Qt::SystemLocaleShortDate)).arg(rxbest.end.toString(Qt::SystemLocaleShortDate)) + "</a><br/>"; tr("Date: %1 - %2").arg(rxbest.start.toString(Qt::SystemLocaleShortDate)).arg(rxbest.end.toString(Qt::SystemLocaleShortDate)) + "</a><br>";
html += QString("%1").arg(rxbest.machine->model()) + "<br/>"; html += QString("%1").arg(rxbest.machine->model()) + "<br>";
html += QString("Serial: %1").arg(rxbest.machine->serial()) + "<br/>"; html += QString("Serial: %1").arg(rxbest.machine->serial()) + "<br>";
html += tr("AHI: %1").arg(double(rxbest.ahi) / rxbest.hours, 0, 'f', 2) + "<br/>"; html += tr("AHI: %1").arg(double(rxbest.ahi) / rxbest.hours, 0, 'f', 2) + "<br>";
html += tr("Total Hours: %1").arg(rxbest.hours, 0, 'f', 2) + "<br/>"; html += tr("Total Hours: %1").arg(rxbest.hours, 0, 'f', 2) + "<br>";
html += QString("%1").arg(rxbest.pressure) + "<br/>"; html += QString("%1").arg(rxbest.pressure) + "<br>";
html += QString("%1").arg(formatRelief(rxbest.relief)) + "<br/>"; html += QString("%1").arg(formatRelief(rxbest.relief)) + "<br>";
html += "<br/>"; html += "<br>";
html += "<b>"+tr("Worst RX Setting")+"</b><br/>"; html += "<b>"+tr("Worst RX Setting")+"</b><br>";
const RXItem & rxworst = *list.at(list.size() -1); const RXItem & rxworst = *list.at(list.size() -1);
html += QString("<a href='overview=%1,%2'>").arg(rxworst.start.toString(Qt::ISODate)).arg(rxworst.end.toString(Qt::ISODate)) + html += QString("<a href='overview=%1,%2'>").arg(rxworst.start.toString(Qt::ISODate)).arg(rxworst.end.toString(Qt::ISODate)) +
tr("Date: %1 - %2").arg(rxworst.start.toString(Qt::SystemLocaleShortDate)).arg(rxworst.end.toString(Qt::SystemLocaleShortDate)) + "</a><br/>"; tr("Date: %1 - %2").arg(rxworst.start.toString(Qt::SystemLocaleShortDate)).arg(rxworst.end.toString(Qt::SystemLocaleShortDate)) + "</a><br>";
html += QString("%1").arg(rxworst.machine->model()) + "<br/>"; html += QString("%1").arg(rxworst.machine->model()) + "<br>";
html += QString("Serial: %1").arg(rxworst.machine->serial()) + "<br/>"; html += QString("Serial: %1").arg(rxworst.machine->serial()) + "<br>";
html += tr("AHI: %1").arg(double(rxworst.ahi) / rxworst.hours, 0, 'f', 2) + "<br/>"; html += tr("AHI: %1").arg(double(rxworst.ahi) / rxworst.hours, 0, 'f', 2) + "<br>";
html += tr("Total Hours: %1").arg(rxworst.hours, 0, 'f', 2) + "<br/>"; html += tr("Total Hours: %1").arg(rxworst.hours, 0, 'f', 2) + "<br>";
html += QString("%1").arg(rxworst.pressure) + "<br/>"; html += QString("%1").arg(rxworst.pressure) + "<br>";
html += QString("%1").arg(formatRelief(rxworst.relief)) + "<br/>"; html += QString("%1").arg(formatRelief(rxworst.relief)) + "<br>";
} }
} }