mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-05 10:40:42 +00:00
New OximeterImport Module
This commit is contained in:
parent
16672fcd4a
commit
ce1e03e86a
@ -248,6 +248,7 @@ gGraphView::gGraphView(QWidget *parent, gGraphView *shared)
|
||||
m_minx = m_maxx = 0;
|
||||
m_day = nullptr;
|
||||
m_selected_graph = nullptr;
|
||||
m_scrollbar = nullptr;
|
||||
|
||||
horizScrollTime.start();
|
||||
vertScrollTime.start();
|
||||
@ -1034,7 +1035,9 @@ void gGraphView::paintGL()
|
||||
GetTextExtent(ss, w, h);
|
||||
QColor col = Qt::white;
|
||||
|
||||
painter.fillRect(width() - m_graphs[0]->marginRight(), 0, m_graphs[0]->marginRight(), w, QBrush(col));
|
||||
if (m_graphs.size() > 0) {
|
||||
painter.fillRect(width() - m_graphs[0]->marginRight(), 0, m_graphs[0]->marginRight(), w, QBrush(col));
|
||||
}
|
||||
#ifndef Q_OS_MAC
|
||||
// if (usePixmapCache()) xx+=4; else xx-=3;
|
||||
#endif
|
||||
@ -1680,7 +1683,8 @@ void gGraphView::wheelEvent(QWheelEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
m_scrollbar->SendWheelEvent(event); // Just forwarding the event to scrollbar for now..
|
||||
if (m_scrollbar)
|
||||
m_scrollbar->SendWheelEvent(event); // Just forwarding the event to scrollbar for now..
|
||||
m_tooltip->cancel();
|
||||
vertScrollTime.start();
|
||||
} else { //Horizontal Panning
|
||||
@ -1753,19 +1757,23 @@ void gGraphView::keyPressEvent(QKeyEvent *event)
|
||||
}
|
||||
|
||||
if (event->key() == Qt::Key_PageUp) {
|
||||
m_offsetY -= PROFILE.appearance->graphHeight() * 3 * m_scaleY;
|
||||
m_scrollbar->setValue(m_offsetY);
|
||||
m_offsetY = m_scrollbar->value();
|
||||
redraw();
|
||||
if (m_scrollbar) {
|
||||
m_offsetY -= PROFILE.appearance->graphHeight() * 3 * m_scaleY;
|
||||
m_scrollbar->setValue(m_offsetY);
|
||||
m_offsetY = m_scrollbar->value();
|
||||
redraw();
|
||||
}
|
||||
return;
|
||||
} else if (event->key() == Qt::Key_PageDown) {
|
||||
m_offsetY += PROFILE.appearance->graphHeight() * 3 * m_scaleY; //PROFILE.appearance->graphHeight();
|
||||
if (m_scrollbar) {
|
||||
m_offsetY += PROFILE.appearance->graphHeight() * 3 * m_scaleY; //PROFILE.appearance->graphHeight();
|
||||
|
||||
if (m_offsetY < 0) { m_offsetY = 0; }
|
||||
if (m_offsetY < 0) { m_offsetY = 0; }
|
||||
|
||||
m_scrollbar->setValue(m_offsetY);
|
||||
m_offsetY = m_scrollbar->value();
|
||||
redraw();
|
||||
m_scrollbar->setValue(m_offsetY);
|
||||
m_offsetY = m_scrollbar->value();
|
||||
redraw();
|
||||
}
|
||||
return;
|
||||
// redraw();
|
||||
}
|
||||
|
@ -24,6 +24,12 @@
|
||||
#include <QFile>
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
#include <QMessageBox>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPushButton>
|
||||
|
||||
#include <QtSerialPort/QSerialPortInfo>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@ -33,10 +39,416 @@ using namespace std;
|
||||
|
||||
extern QProgressBar *qprogress;
|
||||
|
||||
const int START_TIMEOUT = 30000;
|
||||
|
||||
// Possibly need to replan this to include oximetry
|
||||
|
||||
bool SerialLoader::scanDevice(QString keyword,quint16 vendor_id, quint16 product_id)
|
||||
{
|
||||
QStringList ports;
|
||||
|
||||
//qDebug() << "Scanning for USB Serial devices";
|
||||
QList<QSerialPortInfo> list=QSerialPortInfo::availablePorts();
|
||||
|
||||
// How does the mac detect this as a SPO2 device?
|
||||
for (int i=0;i<list.size();i++) {
|
||||
const QSerialPortInfo * info=&list.at(i);
|
||||
QString name=info->portName();
|
||||
QString desc=info->description();
|
||||
|
||||
if ((!keyword.isEmpty() && desc.contains(keyword)) ||
|
||||
((info->hasVendorIdentifier() && (info->vendorIdentifier()==vendor_id))
|
||||
&& (info->hasProductIdentifier() && (info->productIdentifier()==product_id))))
|
||||
{
|
||||
ports.push_back(name);
|
||||
QString dbg=QString("Found Serial Port: %1 %2 %3 %4").arg(name).arg(desc).arg(info->manufacturer()).arg(info->systemLocation());
|
||||
|
||||
if (info->hasProductIdentifier()) //60000
|
||||
dbg+=QString(" PID: %1").arg(info->productIdentifier());
|
||||
if (info->hasVendorIdentifier()) // 4292
|
||||
dbg+=QString(" VID: %1").arg(info->vendorIdentifier());
|
||||
|
||||
qDebug() << dbg.toLocal8Bit().data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ports.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (ports.size()>1) {
|
||||
qDebug() << "More than one serial device matching these parameters was found, choosing the first by default";
|
||||
}
|
||||
port=ports.at(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SerialLoader::closeDevice()
|
||||
{
|
||||
killTimers();
|
||||
disconnect(&serial,SIGNAL(readyRead()), this, SLOT(dataAvailable()));
|
||||
serial.close();
|
||||
m_streaming = false;
|
||||
qDebug() << "Port" << port << "closed";
|
||||
}
|
||||
|
||||
bool SerialLoader::openDevice()
|
||||
{
|
||||
if (port.isEmpty()) {
|
||||
if (!scanDevice("",m_vendorID, m_productID))
|
||||
return false;
|
||||
}
|
||||
serial.setPortName(port);
|
||||
if (!serial.open(QSerialPort::ReadWrite))
|
||||
return false;
|
||||
|
||||
// forward this stuff
|
||||
|
||||
// Set up serial port attributes
|
||||
serial.setBaudRate(QSerialPort::Baud19200);
|
||||
serial.setParity(QSerialPort::OddParity);
|
||||
serial.setStopBits(QSerialPort::OneStop);
|
||||
serial.setDataBits(QSerialPort::Data8);
|
||||
serial.setFlowControl(QSerialPort::NoFlowControl);
|
||||
|
||||
m_streaming = true;
|
||||
m_abort = false;
|
||||
m_importing = false;
|
||||
|
||||
// connect relevant signals
|
||||
connect(&serial,SIGNAL(readyRead()), this, SLOT(dataAvailable()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SerialLoader::dataAvailable()
|
||||
{
|
||||
QByteArray bytes;
|
||||
|
||||
int available = serial.bytesAvailable();
|
||||
bytes.resize(available);
|
||||
|
||||
int bytesread = serial.read(bytes.data(), available);
|
||||
if (bytesread == 0)
|
||||
return;
|
||||
|
||||
if (m_abort) {
|
||||
closeDevice();
|
||||
return;
|
||||
}
|
||||
|
||||
processBytes(bytes);
|
||||
}
|
||||
|
||||
void CMS50Loader::processBytes(QByteArray bytes)
|
||||
{
|
||||
// Sync to start of message type we are interested in
|
||||
quint8 c;
|
||||
quint8 msgcode = (m_status == IMPORTING) ? 0xf0 : 0x80;
|
||||
|
||||
int idx=0;
|
||||
int bytesread = bytes.size();
|
||||
while ((idx < bytesread) && (((c=(quint8)bytes.at(idx)) & msgcode)!=msgcode)) {
|
||||
if (buffer.length()>0) {
|
||||
// If buffer is the start of a valid but short frame, add to it..
|
||||
buffer.append(c);
|
||||
}// otherwise dump these bytes, as they are out of sequence.
|
||||
++idx;
|
||||
}
|
||||
|
||||
// Copy the rest to the buffer.
|
||||
buffer.append(bytes.mid(idx));
|
||||
|
||||
int available = buffer.length();
|
||||
|
||||
switch (status()) {
|
||||
case IMPORTING:
|
||||
idx = doImportMode();
|
||||
break;
|
||||
case LIVE:
|
||||
idx = doLiveMode();
|
||||
break;
|
||||
default:
|
||||
qDebug() << "Device mode not supported by" << ClassName();
|
||||
}
|
||||
|
||||
if (idx >= available) {
|
||||
buffer.clear();
|
||||
} else if (idx > 0) {
|
||||
// Trim any processed bytes from the buffer.
|
||||
buffer = buffer.mid(idx);
|
||||
}
|
||||
|
||||
if (buffer.length() > 0) {
|
||||
// If what's left doesn't start with a marker bit, dump it
|
||||
if (((unsigned char)buffer.at(0) & 0x80) != 0x80) {
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int CMS50Loader::doImportMode()
|
||||
{
|
||||
if (finished_import) {
|
||||
// CMS50E/F continue streaming after import, CMS50D+ stops dead
|
||||
// there is a timer running at this stage that will kill the 50D
|
||||
killTimers();
|
||||
closeDevice();
|
||||
m_importing = false;
|
||||
imp_callbacks = 0;
|
||||
return 0;
|
||||
}
|
||||
int hour,minute;
|
||||
int available = buffer.size();
|
||||
int idx = 0;
|
||||
while (idx < available) {
|
||||
unsigned char c=(unsigned char)buffer.at(idx);
|
||||
if (!started_import) {
|
||||
if (c != 0xf2) { // If not started, continue scanning for a valie header.
|
||||
idx++;
|
||||
continue;
|
||||
} else {
|
||||
received_bytes=0;
|
||||
|
||||
hour=(unsigned char)buffer.at(idx + 1) & 0x7f;
|
||||
minute=(unsigned char)buffer.at(idx + 2) & 0x7f;
|
||||
|
||||
qDebug() << QString("Receiving Oximeter transmission %1:%2").arg(hour).arg(minute);
|
||||
// set importing to true or whatever..
|
||||
|
||||
finished_import = false;
|
||||
started_import = true;
|
||||
started_reading = false;
|
||||
|
||||
m_importing = true;
|
||||
|
||||
m_itemCnt=0;
|
||||
m_itemTotal=5000;
|
||||
|
||||
killTimers();
|
||||
qDebug() << "Getting ready for import";
|
||||
|
||||
oxirec.clear();
|
||||
oxirec.reserve(10000);
|
||||
|
||||
QDate oda=QDate::currentDate();
|
||||
QTime oti=QTime(hour,minute); // Only CMS50E/F's have a realtime clock. CMS50D+ will set this to midnight
|
||||
|
||||
cms50dplus = (hour == 0) && (minute == 0); // Either a CMS50D+ or it's really midnight, set a flag anyway for later to help choose the right sync time
|
||||
|
||||
// If the oximeter record time is more than the current time, then assume it was from the day before
|
||||
// Or should I use split time preference instead??? Foggy Confusements..
|
||||
if (oti > QTime::currentTime()) {
|
||||
oda = oda.addDays(-1);
|
||||
}
|
||||
|
||||
oxitime = QDateTime(oda,oti);
|
||||
|
||||
// Convert it to UTC
|
||||
oxitime=oxitime.toTimeSpec(Qt::UTC);
|
||||
|
||||
qDebug() << "Session start (according to CMS50)" << oxitime<< hex << buffer.at(idx + 1) << buffer.at(idx + 2) << ":" << dec << hour << minute ;
|
||||
|
||||
cb_reset = 1;
|
||||
|
||||
// CMS50D+ needs an end timer because it just stops dead after uploading
|
||||
resetTimer.singleShot(2000,this,SLOT(resetImportTimeout()));
|
||||
}
|
||||
idx += 3;
|
||||
} else { // have started import
|
||||
if (c == 0xf2) { // Header is repeated 3 times, ignore the extras
|
||||
|
||||
hour=(unsigned char)buffer.at(idx + 1) & 0x7f;
|
||||
minute=(unsigned char)buffer.at(idx + 2) & 0x7f;
|
||||
// check..
|
||||
|
||||
idx += 3;
|
||||
continue;
|
||||
} else if ((c & 0xf0) == 0xf0) { // Data trio
|
||||
started_reading=true; // Sometimes errornous crap is sent after data rec header
|
||||
|
||||
// Recording import
|
||||
if ((idx + 2) >= available) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
quint8 pulse=(unsigned char)buffer.at(idx + 1) & 0xff;
|
||||
quint8 spo2=(unsigned char)buffer.at(idx + 2) & 0xff;
|
||||
|
||||
oxirec.append(OxiRecord(pulse,spo2));
|
||||
received_bytes+=3;
|
||||
|
||||
// TODO: Store the data to the session
|
||||
|
||||
m_itemCnt++;
|
||||
m_itemCnt=m_itemCnt % m_itemTotal;
|
||||
emit updateProgress(m_itemCnt, m_itemTotal);
|
||||
|
||||
idx += 3;
|
||||
} else if (!started_reading) { // have not got a valid trio yet, skip...
|
||||
idx += 1;
|
||||
} else {
|
||||
// trio's are over.. finish up.
|
||||
killTimers();
|
||||
closeDevice();
|
||||
|
||||
started_import = false;
|
||||
started_reading = false;
|
||||
finished_import = true;
|
||||
m_importing = false;
|
||||
break;
|
||||
//Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!started_import) {
|
||||
imp_callbacks = 0;
|
||||
} else {
|
||||
imp_callbacks++;
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
int CMS50Loader::doLiveMode()
|
||||
{
|
||||
int available = buffer.size();
|
||||
int idx = 0;
|
||||
while (idx < available) {
|
||||
if (((unsigned char)buffer.at(idx) & 0x80) != 0x80) {
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
int pwave=(unsigned char)buffer.at(idx + 1);
|
||||
int pbeat=(unsigned char)buffer.at(idx + 2);
|
||||
int pulse=((unsigned char)buffer.at(idx + 3) & 0x7f) | ((pbeat & 0x40) << 1);
|
||||
int spo2=(unsigned char)buffer.at(idx + 4) & 0x7f;
|
||||
idx += 5;
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
void CMS50Loader::resetDevice() // Switch CMS50D+ device to live streaming mode
|
||||
{
|
||||
//qDebug() << "Sending reset code to CMS50 device";
|
||||
//m_port->flush();
|
||||
static unsigned char b1[3]={0xf6,0xf6,0xf6};
|
||||
if (serial.write((char *)b1,3)==-1) {
|
||||
qDebug() << "Couldn't write data reset bytes to CMS50";
|
||||
}
|
||||
}
|
||||
|
||||
void CMS50Loader::requestData() // Switch CMS50D+ device to record transmission mode
|
||||
{
|
||||
static unsigned char b1[2]={0xf5,0xf5};
|
||||
|
||||
//qDebug() << "Sending request code to CMS50 device";
|
||||
if (serial.write((char *)b1,2)==-1) {
|
||||
qDebug() << "Couldn't write data request bytes to CMS50";
|
||||
}
|
||||
}
|
||||
|
||||
void CMS50Loader::killTimers()
|
||||
{
|
||||
startTimer.stop();
|
||||
resetTimer.stop();
|
||||
}
|
||||
|
||||
void CMS50Loader::startImportTimeout()
|
||||
{
|
||||
Q_ASSERT(m_streaming == true);
|
||||
|
||||
if (started_import) {
|
||||
return;
|
||||
}
|
||||
Q_ASSERT(finished_import == false);
|
||||
|
||||
//qDebug() << "Starting oximeter import timeout";
|
||||
|
||||
// Wait until events really are jammed on the CMS50D+ before re-requesting data.
|
||||
|
||||
const int delay = 500;
|
||||
|
||||
if (m_abort) {
|
||||
m_streaming = false;
|
||||
closeDevice();
|
||||
return;
|
||||
}
|
||||
|
||||
if (imp_callbacks == 0) { // Frozen, but still hasn't started?
|
||||
m_itemCnt = m_time.elapsed();
|
||||
if (m_itemCnt > START_TIMEOUT) { // Give up after START_TIMEOUT
|
||||
closeDevice();
|
||||
abort();
|
||||
QMessageBox::warning(nullptr, STR_MessageBox_Error, "<h2>"+tr("Could not get data transmission from oximeter.")+"<br/><br/>"+tr("Please ensure you select 'upload' from the oximeter devices menu.")+"</h2>");
|
||||
return;
|
||||
} else {
|
||||
// Note: Newer CMS50 devices transmit from user input, but there is no way of differentiating between models
|
||||
requestData();
|
||||
}
|
||||
emit updateProgress(m_itemCnt, START_TIMEOUT);
|
||||
|
||||
// Schedule another callback to make sure it's started
|
||||
startTimer.singleShot(delay, this, SLOT(startImportTimeout()));
|
||||
}
|
||||
}
|
||||
|
||||
void CMS50Loader::resetImportTimeout()
|
||||
{
|
||||
if (finished_import) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (imp_callbacks != cb_reset) {
|
||||
// Still receiving data.. reset timer
|
||||
qDebug() << "Still receiving data in resetImportTimeout()";
|
||||
if (resetTimer.isActive())
|
||||
resetTimer.stop();
|
||||
|
||||
if (!finished_import) resetTimer.singleShot(2000, this, SLOT(resetImportTimeout()));
|
||||
} else {
|
||||
qDebug() << "Oximeter device stopped transmitting.. Transfer complete";
|
||||
// We were importing, but now are done
|
||||
if (!finished_import && (started_import && started_reading)) {
|
||||
qDebug() << "Switching CMS50 back to live mode and finalizing import";
|
||||
// Turn back on live streaming so the end of capture can be dealt with
|
||||
resetTimer.stop();
|
||||
|
||||
serial.flush();
|
||||
resetDevice(); // Send Reset to CMS50D+
|
||||
//started_import = false;
|
||||
finished_import = true;
|
||||
m_streaming=false;
|
||||
|
||||
closeDevice();
|
||||
//emit transferComplete();
|
||||
//doImportComplete();
|
||||
return;
|
||||
}
|
||||
qDebug() << "Should CMS50 resetImportTimeout reach here?";
|
||||
// else what???
|
||||
}
|
||||
cb_reset=imp_callbacks;
|
||||
}
|
||||
|
||||
|
||||
|
||||
CMS50Loader::CMS50Loader()
|
||||
{
|
||||
m_type = MT_OXIMETER;
|
||||
m_abort = false;
|
||||
m_streaming = false;
|
||||
m_importing = false;
|
||||
imp_callbacks = 0;
|
||||
|
||||
m_vendorID = 0x10c4;
|
||||
m_productID = 0xea60;
|
||||
|
||||
startTimer.setParent(this);
|
||||
resetTimer.setParent(this);
|
||||
|
||||
}
|
||||
|
||||
CMS50Loader::~CMS50Loader()
|
||||
@ -49,36 +461,94 @@ bool CMS50Loader::Detect(const QString &path)
|
||||
return false;
|
||||
}
|
||||
|
||||
int CMS50Loader::Open(QString &path, Profile *profile)
|
||||
int CMS50Loader::Open(QString path, Profile *profile)
|
||||
{
|
||||
// CMS50 folder structure detection stuff here.
|
||||
|
||||
// Not sure whether to both supporting SpO2 files here, as the timestamps are utterly useless for overlay purposes.
|
||||
// May just ignore the crap and support my CMS50 logger
|
||||
// Only one active Oximeter module at a time, set in preferences
|
||||
Q_UNUSED(profile)
|
||||
|
||||
// Contains three files
|
||||
// Data Folder
|
||||
// SpO2 Review.ini
|
||||
// SpO2.ini
|
||||
if (!profile) {
|
||||
qWarning() << "Empty Profile in CMS50Loader::Open()";
|
||||
return 0;
|
||||
m_itemCnt = 0;
|
||||
m_itemTotal = 0;
|
||||
|
||||
m_abort = false;
|
||||
m_importing = false;
|
||||
|
||||
started_import = false;
|
||||
started_reading = false;
|
||||
finished_import = false;
|
||||
|
||||
imp_callbacks = 0;
|
||||
cb_reset = 0;
|
||||
|
||||
// Cheating using path for two serial oximetry modes
|
||||
|
||||
if (path.compare("import") == 0) {
|
||||
setStatus(IMPORTING);
|
||||
|
||||
m_time.start();
|
||||
|
||||
startTimer.stop();
|
||||
startImportTimeout();
|
||||
return 1;
|
||||
} else if (path.compare("live") == 0) {
|
||||
setStatus(LIVE);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This bit needs modifying for the SPO2 folder detection.
|
||||
QDir dir(path);
|
||||
QString tmp = path + "/Data"; // The directory path containing the .spor/.spo2 files
|
||||
|
||||
if ((dir.exists("SpO2 Review.ini") || dir.exists("SpO2.ini"))
|
||||
&& dir.exists("Data")) {
|
||||
// SPO2Review/etc software
|
||||
|
||||
// return OpenCMS50(tmp, profile);
|
||||
}
|
||||
|
||||
return 0;
|
||||
// try to read and process SpoR file..
|
||||
return readSpoRFile(path);
|
||||
}
|
||||
|
||||
bool CMS50Loader::readSpoRFile(QString path)
|
||||
{
|
||||
QFile file(path);
|
||||
if (!file.exists() || !file.isReadable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString ext = path.section(".",1);
|
||||
if ((ext.compare("spo",Qt::CaseInsensitive)==0)
|
||||
&& (ext.compare("spor",Qt::CaseInsensitive)==0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file.open(QFile::ReadOnly);
|
||||
|
||||
QByteArray data;
|
||||
|
||||
data = file.readAll();
|
||||
long size = data.size();
|
||||
|
||||
// position data stream starts at
|
||||
int pos = ((unsigned char)data.at(1) << 8) | (unsigned char)data.at(0);
|
||||
|
||||
// Read date and time (it's a 16bit charset)
|
||||
char dchr[20];
|
||||
int j = 0;
|
||||
for (int i = 0; i < 18 * 2; i += 2) {
|
||||
dchr[j++] = data.at(8 + i);
|
||||
}
|
||||
|
||||
dchr[j] = 0;
|
||||
QString dstr(dchr);
|
||||
oxitime = QDateTime::fromString(dstr, "MM/dd/yy HH:mm:ss");
|
||||
|
||||
if (oxitime.date().year() < 2000) { oxitime = oxitime.addYears(100); }
|
||||
|
||||
unsigned char o2, pr;
|
||||
|
||||
// Read all Pulse and SPO2 data
|
||||
for (int i = pos; i < size - 2;) {
|
||||
o2 = (unsigned char)(data.at(i + 1));
|
||||
pr = (unsigned char)(data.at(i + 0));
|
||||
oxirec.append(OxiRecord(pr, o2));
|
||||
i += 2;
|
||||
}
|
||||
|
||||
// processing gets done later
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Machine *CMS50Loader::CreateMachine(Profile *profile)
|
||||
{
|
||||
if (!profile) {
|
||||
@ -112,6 +582,224 @@ Machine *CMS50Loader::CreateMachine(Profile *profile)
|
||||
return m;
|
||||
}
|
||||
|
||||
Qt::DayOfWeek firstDayOfWeekFromLocale();
|
||||
#include <QAction>
|
||||
|
||||
DateTimeDialog::DateTimeDialog(QString message, QWidget * parent, Qt::WindowFlags flags)
|
||||
:QDialog(parent, flags)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setModal(true);
|
||||
font.setPointSize(25);
|
||||
m_msglabel = new QLabel(message);
|
||||
|
||||
m_msglabel->setAlignment(Qt::AlignHCenter);
|
||||
m_msglabel->setFont(font);
|
||||
layout->addWidget(m_msglabel);
|
||||
|
||||
m_cal = new QCalendarWidget(this);
|
||||
m_cal->setFirstDayOfWeek(Qt::Sunday);
|
||||
QTextCharFormat format = m_cal->weekdayTextFormat(Qt::Saturday);
|
||||
format.setForeground(QBrush(Qt::black, Qt::SolidPattern));
|
||||
m_cal->setWeekdayTextFormat(Qt::Saturday, format);
|
||||
m_cal->setWeekdayTextFormat(Qt::Sunday, format);
|
||||
m_cal->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader);
|
||||
Qt::DayOfWeek dow=firstDayOfWeekFromLocale();
|
||||
m_cal->setFirstDayOfWeek(dow);
|
||||
|
||||
m_timeedit = new MyTimeEdit(this);
|
||||
m_timeedit->setDisplayFormat("hh:mm:ss ap");
|
||||
m_timeedit->setMinimumHeight(m_timeedit->height() + 10);
|
||||
m_timeedit->setFont(font);
|
||||
m_timeedit->setCurrentSectionIndex(0);
|
||||
m_timeedit->editor()->setStyleSheet(
|
||||
"selection-color: white;"
|
||||
"selection-background-color: lightgray;"
|
||||
);
|
||||
|
||||
layout->addWidget(m_cal);
|
||||
m_bottomlabel = new QLabel(this);
|
||||
m_bottomlabel->setVisible(false);
|
||||
m_bottomlabel->setAlignment(Qt::AlignCenter);
|
||||
layout->addWidget(m_bottomlabel);
|
||||
|
||||
sessbar = new SessionBar(this);
|
||||
sessbar->setMinimumHeight(40);
|
||||
sessbar->setSelectMode(true);
|
||||
sessbar->setMouseTracking(true);
|
||||
connect(sessbar, SIGNAL(sessionClicked(Session*)), this, SLOT(onSessionSelected(Session*)));
|
||||
layout->addWidget(sessbar,1);
|
||||
|
||||
layout2 = new QHBoxLayout();
|
||||
layout->addLayout(layout2);
|
||||
|
||||
acceptbutton = new QPushButton (QObject::tr("&Accept"));
|
||||
resetbutton = new QPushButton (QObject::tr("&Abort"));
|
||||
|
||||
resetbutton->setShortcut(QKeySequence(Qt::Key_Escape));
|
||||
// shortcutQuit = new QShortcut(, this, SLOT(reject()), SLOT(reject()));
|
||||
|
||||
layout2->addWidget(m_timeedit);
|
||||
layout2->addWidget(acceptbutton);
|
||||
layout2->addWidget(resetbutton);
|
||||
|
||||
connect(resetbutton, SIGNAL(clicked()), this, SLOT(reject()));
|
||||
connect(acceptbutton, SIGNAL(clicked()), this, SLOT(accept()));
|
||||
connect(m_cal, SIGNAL(clicked(QDate)), this, SLOT(onDateSelected(QDate)));
|
||||
connect(m_cal, SIGNAL(activated(QDate)), this, SLOT(onDateSelected(QDate)));
|
||||
connect(m_cal, SIGNAL(selectionChanged()), this, SLOT(onSelectionChanged()));
|
||||
connect(m_cal, SIGNAL(currentPageChanged(int,int)), this, SLOT(onCurrentPageChanged(int,int)));
|
||||
|
||||
}
|
||||
DateTimeDialog::~DateTimeDialog()
|
||||
{
|
||||
disconnect(m_cal, SIGNAL(currentPageChanged(int,int)), this, SLOT(onCurrentPageChanged(int,int)));
|
||||
disconnect(m_cal, SIGNAL(selectionChanged()), this, SLOT(onSelectionChanged()));
|
||||
disconnect(m_cal, SIGNAL(activated(QDate)), this, SLOT(onDateSelected(QDate)));
|
||||
disconnect(m_cal, SIGNAL(clicked(QDate)), this, SLOT(onDateSelected(QDate)));
|
||||
disconnect(acceptbutton, SIGNAL(clicked()), this, SLOT(accept()));
|
||||
disconnect(resetbutton, SIGNAL(clicked()), this, SLOT(reject()));
|
||||
disconnect(sessbar, SIGNAL(sessionClicked(Session*)), this, SLOT(onSessionSelected(Session*)));
|
||||
}
|
||||
|
||||
void DateTimeDialog::onCurrentPageChanged(int year, int month)
|
||||
{
|
||||
Q_UNUSED(year);
|
||||
Q_UNUSED(month);
|
||||
onDateSelected(m_cal->selectedDate());
|
||||
}
|
||||
void DateTimeDialog::onSelectionChanged()
|
||||
{
|
||||
onDateSelected(m_cal->selectedDate());
|
||||
}
|
||||
|
||||
void DateTimeDialog::onDateSelected(QDate date)
|
||||
{
|
||||
Day * day = PROFILE.GetGoodDay(date, MT_CPAP);
|
||||
|
||||
sessbar->clear();
|
||||
if (day) {
|
||||
QDateTime time=QDateTime::fromMSecsSinceEpoch(day->first());
|
||||
sessbar->clear();
|
||||
QList<QColor> colors;
|
||||
colors.push_back("#ffffe0");
|
||||
colors.push_back("#ffe0ff");
|
||||
colors.push_back("#e0ffff");
|
||||
QList<Session *>::iterator i;
|
||||
int j=0;
|
||||
for (i=day->begin(); i != day->end(); ++i) {
|
||||
sessbar->add((*i),colors.at(j++ % colors.size()));
|
||||
}
|
||||
//sessbar->setVisible(true);
|
||||
setBottomMessage(QString("%1 session(s), starting at %2").arg(day->size()).arg(time.time().toString("hh:mm:ssap")));
|
||||
} else {
|
||||
setBottomMessage("No CPAP Data available for this date");
|
||||
// sessbar->setVisible(false);
|
||||
}
|
||||
|
||||
sessbar->update();
|
||||
}
|
||||
|
||||
void DateTimeDialog::onSessionSelected(Session * session)
|
||||
{
|
||||
QDateTime time=QDateTime::fromMSecsSinceEpoch(session->first());
|
||||
m_timeedit->setTime(time.time());
|
||||
}
|
||||
|
||||
QDateTime DateTimeDialog::execute(QDateTime datetime)
|
||||
{
|
||||
m_timeedit->setTime(datetime.time());
|
||||
m_cal->setCurrentPage(datetime.date().year(), datetime.date().month());
|
||||
m_cal->setSelectedDate(datetime.date());
|
||||
m_cal->setMinimumDate(PROFILE.FirstDay());
|
||||
m_cal->setMaximumDate(QDate::currentDate());
|
||||
onDateSelected(datetime.date());
|
||||
if (QDialog::exec() == QDialog::Accepted) {
|
||||
return QDateTime(m_cal->selectedDate(), m_timeedit->time());
|
||||
} else {
|
||||
return datetime;
|
||||
}
|
||||
}
|
||||
void DateTimeDialog::reject()
|
||||
{
|
||||
if (QMessageBox::question(this,STR_MessageBox_Warning,
|
||||
QObject::tr("Closing this dialog will leave this time unedited.")+"<br/><br/>"+
|
||||
QObject::tr("Are you sure you want to do this?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
|
||||
QDialog::reject();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CMS50Loader::process()
|
||||
{
|
||||
int size=oxirec.size();
|
||||
if (size<10)
|
||||
return;
|
||||
|
||||
return;
|
||||
|
||||
QDateTime cpaptime;
|
||||
Day *day = PROFILE.GetDay(PROFILE.LastDay(), MT_CPAP);
|
||||
|
||||
if (day) {
|
||||
int ti = day->first() / 1000L;
|
||||
|
||||
cpaptime = QDateTime::fromTime_t(ti);
|
||||
|
||||
if (cms50dplus) {
|
||||
if (QMessageBox::question(nullptr, STR_MessageBox_Question,
|
||||
"<h2>"+tr("Oximeter import completed")+"</h2><br/><br/>"+
|
||||
tr("Oximeter import completed successfully, but your device did not record a starting time.")+"<br/><br/>"+
|
||||
tr("If you remembered to start your oximeter at exactly the same time as your CPAP session, you can sync to the first CPAP session of the day last recorded.")+
|
||||
tr("Otherwise you can adjust the time yourself."), tr("Use CPAP"), tr("Set Manually"), "", 0, 1)==0) {
|
||||
oxitime=cpaptime;
|
||||
} else {
|
||||
DateTimeDialog dlg(tr("Oximeter starting time"));
|
||||
oxitime = dlg.execute(oxitime);
|
||||
}
|
||||
} else {
|
||||
if (qAbs(oxitime.secsTo(cpaptime)) > 60) {
|
||||
if (QMessageBox::question(nullptr, STR_MessageBox_Question,
|
||||
"<h2>"+tr("Oximeter import completed")+"</h2><br/>"+
|
||||
tr("Which devices starting time do you wish to use for this oximetry session?")+"<br/><br/>"+
|
||||
tr("If you started both devices at exactly the same time, or don't know if the clock is set correctly on either devices, choose CPAP."),STR_TR_CPAP, STR_TR_Oximeter, "", 0, -1)==0) {
|
||||
oxitime=cpaptime;
|
||||
qDebug() << "Chose CPAP starting time";
|
||||
} else {
|
||||
DateTimeDialog dlg(tr("Oximeter starting time"));
|
||||
oxitime = dlg.execute(oxitime);
|
||||
qDebug() << "Chose Oximeter starting time";
|
||||
}
|
||||
} else {
|
||||
// don't bother asking.. use CPAP
|
||||
oxitime=cpaptime;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cms50dplus) {
|
||||
QMessageBox::information(nullptr, STR_MessageBox_Information,
|
||||
"<h2>"+tr("Oximeter import completed")+"</h2><br/>"+
|
||||
tr("Your oximeter does not record a starting time, and no CPAP session is available to match it to")+"<br/><br/>"+
|
||||
tr("You will have to adjust the starting time of this oximetery session manually."),
|
||||
QMessageBox::Ok, QMessageBox::Ok);
|
||||
DateTimeDialog dlg(tr("Oximeter starting time"));
|
||||
oxitime = dlg.execute(oxitime);
|
||||
}
|
||||
}
|
||||
|
||||
EventList *PULSE=new EventList(EVL_Event);
|
||||
EventList *SPO2=new EventList(EVL_Event);
|
||||
|
||||
|
||||
quint64 ti = oxitime.toMSecsSinceEpoch();
|
||||
|
||||
for (int i=0; i < size; ++i) {
|
||||
//PULSE->AddWaveform
|
||||
}
|
||||
qDebug() << "Processing" << oxirec.size() << "oximetry records";
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool cms50_initialized = false;
|
||||
|
@ -12,17 +12,78 @@
|
||||
#ifndef CMS50LOADER_H
|
||||
#define CMS50LOADER_H
|
||||
|
||||
#include <QtSerialPort/QSerialPort>
|
||||
#include <QTimer>
|
||||
|
||||
#include "SleepLib/machine_loader.h"
|
||||
|
||||
const QString cms50_class_name = "CMS50";
|
||||
const int cms50_data_version = 4;
|
||||
|
||||
struct OxiRecord
|
||||
{
|
||||
quint8 pulse;
|
||||
quint8 spo2;
|
||||
OxiRecord():pulse(0), spo2(0) {}
|
||||
OxiRecord(quint8 p, quint8 s): pulse(p), spo2(s) {}
|
||||
OxiRecord(const OxiRecord & copy) { pulse = copy.pulse; spo2= copy.spo2; }
|
||||
};
|
||||
|
||||
class SerialLoader : public MachineLoader
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SerialLoader() : MachineLoader() {}
|
||||
virtual ~SerialLoader() {};
|
||||
|
||||
virtual bool Detect(const QString &path)=0;
|
||||
virtual int Open(QString path, Profile *profile)=0;
|
||||
|
||||
static void Register() {}
|
||||
|
||||
virtual int Version()=0;
|
||||
virtual const QString &ClassName()=0;
|
||||
|
||||
|
||||
// Serial Stuff
|
||||
virtual bool scanDevice(QString keyword="",quint16 vendor_id=0, quint16 product_id=0);
|
||||
virtual bool openDevice();
|
||||
virtual void closeDevice();
|
||||
|
||||
virtual void process() {}
|
||||
|
||||
signals:
|
||||
void noDeviceFound();
|
||||
void deviceDetected();
|
||||
|
||||
protected slots:
|
||||
virtual void dataAvailable();
|
||||
virtual void resetImportTimeout() {}
|
||||
virtual void startImportTimeout() {}
|
||||
|
||||
protected:
|
||||
virtual void processBytes(QByteArray buffer) { Q_UNUSED(buffer) }
|
||||
|
||||
virtual void killTimers() {}
|
||||
virtual void resetDevice() {}
|
||||
virtual void requestData() {}
|
||||
|
||||
QString port;
|
||||
QSerialPort serial;
|
||||
|
||||
QTimer startTimer;
|
||||
QTimer resetTimer;
|
||||
|
||||
quint16 m_productID;
|
||||
quint16 m_vendorID;
|
||||
};
|
||||
|
||||
/*! \class CMS50Loader
|
||||
\brief Bulk Importer for CMS50 SPO2Review format.. Deprecated, as the Oximetry module does a better job
|
||||
*/
|
||||
class CMS50Loader : public MachineLoader
|
||||
class CMS50Loader : public SerialLoader
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
|
||||
@ -30,7 +91,7 @@ class CMS50Loader : public MachineLoader
|
||||
virtual ~CMS50Loader();
|
||||
|
||||
virtual bool Detect(const QString &path);
|
||||
virtual int Open(QString &path, Profile *profile);
|
||||
virtual int Open(QString path, Profile *profile);
|
||||
|
||||
static void Register();
|
||||
|
||||
@ -39,13 +100,118 @@ class CMS50Loader : public MachineLoader
|
||||
|
||||
Machine *CreateMachine(Profile *profile);
|
||||
|
||||
virtual void process();
|
||||
|
||||
protected:
|
||||
int OpenCMS50(QString &path, Profile *profile);
|
||||
// bool OpenSPORFile(QString path, Machine *machine, Profile *profile);
|
||||
protected slots:
|
||||
// virtual void dataAvailable();
|
||||
virtual void resetImportTimeout();
|
||||
virtual void startImportTimeout();
|
||||
|
||||
protected:
|
||||
|
||||
bool readSpoRFile(QString path);
|
||||
virtual void processBytes(QByteArray bytes);
|
||||
|
||||
int doImportMode();
|
||||
int doLiveMode();
|
||||
|
||||
QVector<OxiRecord> oxirec;
|
||||
|
||||
virtual void killTimers();
|
||||
|
||||
// Switch CMS50D+ device to live streaming mode
|
||||
virtual void resetDevice();
|
||||
|
||||
// Switch CMS50D+ device to record transmission mode
|
||||
void requestData();
|
||||
|
||||
private:
|
||||
char *buffer;
|
||||
EventList *PULSE;
|
||||
EventList *SPO2;
|
||||
|
||||
QTime m_time;
|
||||
|
||||
QByteArray buffer;
|
||||
|
||||
bool started_import;
|
||||
bool finished_import;
|
||||
bool started_reading;
|
||||
bool cms50dplus;
|
||||
QDateTime oxitime;
|
||||
|
||||
int cb_reset,imp_callbacks;
|
||||
|
||||
int received_bytes;
|
||||
|
||||
int m_itemCnt;
|
||||
int m_itemTotal;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#include <QTimeEdit>
|
||||
#include <QCalendarWidget>
|
||||
#include <QHBoxLayout>
|
||||
#include <QTextCharFormat>
|
||||
#include <QFont>
|
||||
#include <QShortcut>
|
||||
#include <QLineEdit>
|
||||
|
||||
class MyTimeEdit: public QDateTimeEdit
|
||||
{
|
||||
public:
|
||||
MyTimeEdit(QWidget *parent):QDateTimeEdit(parent) {
|
||||
setKeyboardTracking(true);
|
||||
this->setDisplayFormat("hh:mm:ssap");
|
||||
|
||||
editor()->setAlignment(Qt::AlignCenter);
|
||||
}
|
||||
QLineEdit * editor() const {
|
||||
return this->lineEdit();
|
||||
}
|
||||
};
|
||||
|
||||
#include <QLabel>
|
||||
#include <QDialog>
|
||||
|
||||
#include "sessionbar.h"
|
||||
|
||||
class DateTimeDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DateTimeDialog(QString message, QWidget * parent = nullptr, Qt::WindowFlags flags = Qt::Dialog);
|
||||
~DateTimeDialog();
|
||||
|
||||
QDateTime execute(QDateTime datetime);
|
||||
void setMessage(QString msg) { m_msglabel->setText(msg); }
|
||||
void setBottomMessage(QString msg) { m_bottomlabel->setText(msg); m_bottomlabel->setVisible(true);}
|
||||
|
||||
QCalendarWidget * cal() { return m_cal; }
|
||||
signals:
|
||||
public slots:
|
||||
void reject();
|
||||
void onDateSelected(QDate date);
|
||||
void onSelectionChanged();
|
||||
void onCurrentPageChanged(int year, int month);
|
||||
void onSessionSelected(Session *);
|
||||
|
||||
protected:
|
||||
QVBoxLayout * layout;
|
||||
QHBoxLayout * layout2;
|
||||
|
||||
QPushButton *acceptbutton;
|
||||
QPushButton *resetbutton;
|
||||
|
||||
QLabel * m_msglabel;
|
||||
QLabel * m_bottomlabel;
|
||||
|
||||
QCalendarWidget * m_cal;
|
||||
MyTimeEdit * m_timeedit;
|
||||
SessionBar * sessbar;
|
||||
QFont font;
|
||||
QShortcut * shortcutQuit;
|
||||
};
|
||||
|
||||
|
||||
#endif // CMS50LOADER_H
|
||||
|
@ -35,6 +35,7 @@ FPIcon::~FPIcon()
|
||||
FPIconLoader::FPIconLoader()
|
||||
{
|
||||
m_buffer = nullptr;
|
||||
m_type = MT_CPAP;
|
||||
}
|
||||
|
||||
FPIconLoader::~FPIconLoader()
|
||||
@ -63,7 +64,7 @@ bool FPIconLoader::Detect(const QString & givenpath)
|
||||
}
|
||||
|
||||
|
||||
int FPIconLoader::Open(QString &path, Profile *profile)
|
||||
int FPIconLoader::Open(QString path, Profile *profile)
|
||||
{
|
||||
QString newpath;
|
||||
|
||||
@ -290,7 +291,7 @@ int FPIconLoader::OpenMachine(Machine *mach, QString &path, Profile *profile)
|
||||
// !\brief Convert F&P 32bit date format to 32bit UNIX Timestamp
|
||||
quint32 convertDate(quint32 timestamp)
|
||||
{
|
||||
quint8 day, month,hour, minute, second;
|
||||
quint16 day, month,hour=0, minute=0, second=0;
|
||||
quint16 year;
|
||||
|
||||
|
||||
@ -298,25 +299,36 @@ quint32 convertDate(quint32 timestamp)
|
||||
month = (timestamp >> 5) & 0x0f;
|
||||
year = 2000 + ((timestamp >> 9) & 0x3f);
|
||||
timestamp >>= 15;
|
||||
timestamp |= (timestamp >> 15) & 1;
|
||||
|
||||
// Okay, why did I swap the first and last bits of the time field?
|
||||
// What am I forgetting?? This seems to work properly like this
|
||||
// Was I looking at older data that worked like this?
|
||||
// second = timestamp & 0x3f;
|
||||
// hour = (timestamp >> 6) & 0x1f;
|
||||
// minute = (timestamp >> 12) & 0x3f;
|
||||
|
||||
second = timestamp & 0x3f;
|
||||
second = timestamp & 0x3f;
|
||||
minute = (timestamp >> 6) & 0x3f;
|
||||
hour = (timestamp >> 12) & 0x1f;
|
||||
hour = (timestamp >> 12)+1;
|
||||
|
||||
QDateTime dt = QDateTime(QDate(year, month, day), QTime(hour, minute, second),Qt::UTC);
|
||||
|
||||
QDateTime dt = QDateTime(QDate(year, month, day), QTime(hour, minute, second), Qt::UTC);
|
||||
Q_ASSERT(dt.isValid());
|
||||
if ((year == 2013) && (month == 9) && (day == 18)) {
|
||||
// this is for testing.. set a breakpoint on here and
|
||||
int i=5;
|
||||
}
|
||||
|
||||
|
||||
// From Rudd's data set compared to times reported from his F&P software's report (just the time bits left over)
|
||||
// 90514 = 00:06:18 WET 23:06:18 UTC 09:06:18 AEST
|
||||
// 94360 = 01:02:24 WET
|
||||
// 91596 = 00:23:12 WET
|
||||
// 19790 = 23:23:50 WET
|
||||
|
||||
return dt.toTime_t();
|
||||
}
|
||||
|
||||
quint32 convertFLWDate(quint32 timestamp)
|
||||
{
|
||||
quint8 day, month, hour, minute, second;
|
||||
quint16 day, month, hour, minute, second;
|
||||
quint16 year;
|
||||
|
||||
day = timestamp & 0x1f;
|
||||
@ -328,13 +340,19 @@ quint32 convertFLWDate(quint32 timestamp)
|
||||
// What am I forgetting?? This seems to work properly like this
|
||||
// Was I looking at older data that worked like this?
|
||||
|
||||
second = timestamp & 0x3f;
|
||||
second = timestamp & 0x3f;
|
||||
minute = (timestamp >> 6) & 0x3f;
|
||||
hour = (timestamp >> 12) & 0x1f;
|
||||
hour = (timestamp >> 12)+1;
|
||||
// second = timestamp & 0x3f;
|
||||
// minute = (timestamp >> 6) & 0x3f;
|
||||
// hour = (timestamp >> 12) & 0x1f;
|
||||
QDateTime dt = QDateTime(QDate(year, month, day), QTime(hour, minute, second), Qt::UTC);
|
||||
Q_ASSERT(dt.isValid());
|
||||
|
||||
return dt.toTime_t();
|
||||
if ((year == 2013) && (month == 9) && (day == 18)) {
|
||||
int i=5;
|
||||
}
|
||||
// 87922 23:23:50 WET
|
||||
return dt.addSecs(-360).toTime_t();
|
||||
}
|
||||
|
||||
//QDateTime FPIconLoader::readFPDateTime(quint8 *data)
|
||||
@ -438,20 +456,29 @@ bool FPIconLoader::OpenFLW(Machine *mach, QString filename, Profile *profile)
|
||||
mach->properties[STR_PROP_Model] = model + " " + type;
|
||||
}
|
||||
|
||||
// fname.chop(4);
|
||||
// QString num = fname.right(4);
|
||||
// int filenum = num.toInt();
|
||||
|
||||
|
||||
QByteArray buf = file.read(4);
|
||||
unsigned char * data = (unsigned char *)buf.data();
|
||||
|
||||
ts = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24;
|
||||
quint32 t2 = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24;
|
||||
|
||||
if (ts == 0xffffffff)
|
||||
// this line is probably superflous crud.
|
||||
if (t2 == 0xffffffff) return false;
|
||||
|
||||
QByteArray block = file.readAll();
|
||||
file.close();
|
||||
data = (unsigned char *)block.data();
|
||||
|
||||
// Abort if crapy
|
||||
if (!(data[103]==0xff && data[104]==0xff))
|
||||
return false;
|
||||
|
||||
ts = convertFLWDate(ts);
|
||||
ts = convertFLWDate(t2);
|
||||
|
||||
if (ts > QDateTime(QDate(2015,1,1), QTime(0,0,0)).toTime_t()) {
|
||||
ts = convertFLWDate(t2);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
ti = qint64(ts) * 1000L;
|
||||
|
||||
@ -463,7 +490,7 @@ bool FPIconLoader::OpenFLW(Machine *mach, QString filename, Profile *profile)
|
||||
|
||||
if (sit != Sessions.end()) {
|
||||
sess = sit.value();
|
||||
// qDebug() << filenum << ":" << date << sess->session() << ":" << sess->hours() * 60.0;
|
||||
// qDebug() << filenum << ":" << date << sess->session() << ":" << sess->hours() * 60.0;
|
||||
} else {
|
||||
// Create a session
|
||||
qint64 k = -1;
|
||||
@ -474,7 +501,7 @@ bool FPIconLoader::OpenFLW(Machine *mach, QString filename, Profile *profile)
|
||||
int cnt=0;
|
||||
if (Sessions.begin() != sit) {
|
||||
do {
|
||||
sit --;
|
||||
sit--;
|
||||
s1 = sit.value();
|
||||
qint64 z = qAbs(sit.key() - ts);
|
||||
if (z < 3600) {
|
||||
@ -499,7 +526,6 @@ bool FPIconLoader::OpenFLW(Machine *mach, QString filename, Profile *profile)
|
||||
// qDebug() << filenum << ":" << date << "couldn't find matching session for" << ts;
|
||||
}
|
||||
}
|
||||
|
||||
const int samples_per_block = 50;
|
||||
const double rate = 1000.0 / double(samples_per_block);
|
||||
|
||||
@ -510,54 +536,47 @@ bool FPIconLoader::OpenFLW(Machine *mach, QString filename, Profile *profile)
|
||||
flow->setFirst(ti);
|
||||
pressure->setFirst(ti);
|
||||
|
||||
|
||||
quint16 endMarker;
|
||||
quint8 offset; // offset from center for this block
|
||||
qint8 offset; // offset from center for this block
|
||||
quint16 pres; // mask pressure
|
||||
|
||||
qint16 tmp;
|
||||
QByteArray block;
|
||||
|
||||
qint16 samples[samples_per_block];
|
||||
|
||||
EventDataType val;
|
||||
|
||||
// Each block represents 1 second of data.. therefore Flow waveform is at 50hz, and Pressure is at 1hz
|
||||
unsigned char *p = data;
|
||||
int datasize = block.size();
|
||||
unsigned char *end = data+datasize;
|
||||
do {
|
||||
block = file.read(105);
|
||||
if (block.size() != 105) {
|
||||
break;
|
||||
endMarker = *((quint16 *)p);
|
||||
if (endMarker == 0xffff) {
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
data = (unsigned char *)block.data();
|
||||
endMarker = data[1] << 8 | data[0];
|
||||
if (endMarker == 0x7fff) {
|
||||
// Reached end of file
|
||||
break;
|
||||
}
|
||||
pres = data[101] << 8 | data[100];
|
||||
|
||||
offset = data[102];
|
||||
|
||||
pressure->AddEvent(ti, pres);
|
||||
|
||||
for (int i=0; i < samples_per_block; i++) {
|
||||
tmp = ((char *)data)[(i<<1) + 1] << 8 | data[(i << 1)];
|
||||
|
||||
offset = ((qint8*)p)[102];
|
||||
for (int i=0; i< samples_per_block; ++i) {
|
||||
tmp = ((char *)p)[1] << 8 | p[0];
|
||||
p += 2;
|
||||
// Assuming Litres per hour, converting to litres per minute and applying offset?
|
||||
// As in should be 60.0?
|
||||
val = (EventDataType(tmp) / 100.0) - offset;
|
||||
|
||||
// if (val < -128) { val = -128; }
|
||||
// else if (val > 128) { val = 128; }
|
||||
|
||||
samples[i]=val;
|
||||
samples[i] = val;
|
||||
}
|
||||
flow->AddWaveform(ti, samples, samples_per_block, rate);
|
||||
|
||||
endMarker = data[103] << 8 | data[104];
|
||||
ti += samples_per_block * rate;
|
||||
} while (endMarker == 0xffff);
|
||||
|
||||
pres = *((quint16 *)p);
|
||||
p+=3; // (offset too)
|
||||
} while (p < end);
|
||||
|
||||
if (endMarker != 0x7fff) {
|
||||
qDebug() << fname << "waveform does not end with the corrent marker";
|
||||
qDebug() << fname << "waveform does not end with the corrent marker" << hex << endMarker;
|
||||
}
|
||||
|
||||
if (sess) {
|
||||
@ -647,8 +666,10 @@ bool FPIconLoader::OpenSummary(Machine *mach, QString filename, Profile *profile
|
||||
|
||||
do {
|
||||
in >> ts;
|
||||
if (ts == 0xffffffff) break;
|
||||
if ((ts & 0xfafe) == 0xfafe) break;
|
||||
if (ts == 0xffffffff)
|
||||
break;
|
||||
if ((ts & 0xfafe) == 0xfafe)
|
||||
break;
|
||||
|
||||
ts = convertDate(ts);
|
||||
|
||||
@ -827,6 +848,7 @@ bool FPIconLoader::OpenDetail(Machine *mach, QString filename, Profile *profile)
|
||||
sess = Sessions[sessid];
|
||||
ti = qint64(sessid) * 1000L;
|
||||
sess->really_set_first(ti);
|
||||
ti -= 360000;
|
||||
EventList *LK = sess->AddEventList(CPAP_LeakTotal, EVL_Event, 1);
|
||||
EventList *PR = sess->AddEventList(CPAP_Pressure, EVL_Event, 0.1F);
|
||||
EventList *OA = sess->AddEventList(CPAP_Obstructive, EVL_Event);
|
||||
@ -843,10 +865,10 @@ bool FPIconLoader::OpenDetail(Machine *mach, QString filename, Profile *profile)
|
||||
for (int i = 0; i < rec; ++i) {
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
pressure = data[idx];
|
||||
PR->AddEvent(ti, pressure);
|
||||
PR->AddEvent(ti+360000/2, pressure);
|
||||
|
||||
leak = data[idx + 1];
|
||||
LK->AddEvent(ti, leak);
|
||||
LK->AddEvent(ti+360000/2, leak);
|
||||
|
||||
a1 = data[idx + 2]; // [0..5] Obstructive flag, [6..7] Unknown
|
||||
a2 = data[idx + 3]; // [0..5] Hypopnea, [6..7] Unknown
|
||||
|
@ -58,7 +58,7 @@ class FPIconLoader : public MachineLoader
|
||||
virtual bool Detect(const QString & path);
|
||||
|
||||
//! \brief Scans path for F&P Icon data signature, and Loads any new data
|
||||
virtual int Open(QString &path, Profile *profile);
|
||||
virtual int Open(QString path, Profile *profile);
|
||||
|
||||
int OpenMachine(Machine *mach, QString &path, Profile *profile);
|
||||
|
||||
|
@ -30,6 +30,7 @@ Intellipap::~Intellipap()
|
||||
IntellipapLoader::IntellipapLoader()
|
||||
{
|
||||
m_buffer = nullptr;
|
||||
m_type = MT_CPAP;
|
||||
}
|
||||
|
||||
IntellipapLoader::~IntellipapLoader()
|
||||
@ -57,7 +58,7 @@ bool IntellipapLoader::Detect(const QString & givenpath)
|
||||
return true;
|
||||
}
|
||||
|
||||
int IntellipapLoader::Open(QString &path, Profile *profile)
|
||||
int IntellipapLoader::Open(QString path, Profile *profile)
|
||||
{
|
||||
// Check for SL directory
|
||||
// Check for DV5MFirm.bin?
|
||||
|
@ -56,7 +56,7 @@ class IntellipapLoader : public MachineLoader
|
||||
virtual bool Detect(const QString & path);
|
||||
|
||||
//! \brief Scans path for Intellipap data signature, and Loads any new data
|
||||
virtual int Open(QString &path, Profile *profile);
|
||||
virtual int Open(QString path, Profile *profile);
|
||||
|
||||
//! \brief Returns SleepLib database version of this IntelliPap loader
|
||||
virtual int Version() { return intellipap_data_version; }
|
||||
|
@ -31,6 +31,8 @@ MSeries::~MSeries()
|
||||
|
||||
MSeriesLoader::MSeriesLoader()
|
||||
{
|
||||
m_type = MT_CPAP;
|
||||
|
||||
epoch = QDateTime(QDate(2000, 1, 1), QTime(0, 0, 0), Qt::UTC).toTime_t();
|
||||
epoch -= QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0), Qt::UTC).toTime_t();
|
||||
}
|
||||
@ -113,7 +115,7 @@ blockLayoutOffsets {
|
||||
*/
|
||||
|
||||
|
||||
int MSeriesLoader::Open(QString &path, Profile *profile)
|
||||
int MSeriesLoader::Open(QString path, Profile *profile)
|
||||
{
|
||||
Q_UNUSED(profile);
|
||||
// Until a smartcard reader is written, this is not an auto-scanner.. it just opens a block file..
|
||||
|
@ -51,7 +51,7 @@ class MSeriesLoader : public MachineLoader
|
||||
virtual bool Detect(const QString & path) { Q_UNUSED(path); return false; }
|
||||
|
||||
//! \brief Opens M-Series block device
|
||||
virtual int Open(QString &file, Profile *profile);
|
||||
virtual int Open(QString file, Profile *profile);
|
||||
|
||||
//! \brief Returns the database version of this loader
|
||||
virtual int Version() { return mseries_data_version; }
|
||||
|
@ -121,9 +121,9 @@ PRS1Loader::PRS1Loader()
|
||||
{
|
||||
// Todo: Register PRS1 custom channels
|
||||
|
||||
|
||||
//genCRCTable();
|
||||
m_buffer = nullptr;
|
||||
m_type = MT_CPAP;
|
||||
}
|
||||
|
||||
PRS1Loader::~PRS1Loader()
|
||||
@ -225,7 +225,7 @@ bool PRS1Loader::Detect(const QString & path)
|
||||
return true;
|
||||
}
|
||||
|
||||
int PRS1Loader::Open(QString &path, Profile *profile)
|
||||
int PRS1Loader::Open(QString path, Profile *profile)
|
||||
{
|
||||
QString newpath;
|
||||
path = path.replace("\\", "/");
|
||||
|
@ -56,7 +56,7 @@ class PRS1Loader : public MachineLoader
|
||||
virtual bool Detect(const QString & path);
|
||||
|
||||
//! \brief Scans directory path for valid PRS1 signature
|
||||
virtual int Open(QString &path, Profile *profile);
|
||||
virtual int Open(QString path, Profile *profile);
|
||||
|
||||
//! \brief Returns the database version of this loader
|
||||
virtual int Version() { return prs1_data_version; }
|
||||
|
@ -699,6 +699,7 @@ void ResmedImport::run()
|
||||
|
||||
ResmedLoader::ResmedLoader()
|
||||
{
|
||||
m_type = MT_CPAP;
|
||||
}
|
||||
ResmedLoader::~ResmedLoader()
|
||||
{
|
||||
@ -778,7 +779,7 @@ bool ResmedLoader::Detect(const QString & givenpath)
|
||||
return true;
|
||||
}
|
||||
|
||||
int ResmedLoader::Open(QString &path, Profile *profile)
|
||||
int ResmedLoader::Open(QString path, Profile *profile)
|
||||
{
|
||||
|
||||
QString serial; // Serial number
|
||||
|
@ -326,7 +326,7 @@ class ResmedLoader : public MachineLoader
|
||||
virtual bool Detect(const QString & path);
|
||||
|
||||
//! \brief Scans for S9 SD folder structure signature, and loads any new data if found
|
||||
virtual int Open(QString &path, Profile *profile);
|
||||
virtual int Open(QString path, Profile *profile);
|
||||
|
||||
//! \brief Returns the version number of this ResMed loader
|
||||
virtual int Version() { return resmed_data_version; }
|
||||
|
@ -23,14 +23,13 @@
|
||||
|
||||
SomnoposeLoader::SomnoposeLoader()
|
||||
{
|
||||
//ctor
|
||||
m_type = MT_POSITION;
|
||||
}
|
||||
|
||||
SomnoposeLoader::~SomnoposeLoader()
|
||||
{
|
||||
//dtor
|
||||
}
|
||||
int SomnoposeLoader::Open(QString &path, Profile *profile)
|
||||
int SomnoposeLoader::Open(QString path, Profile *profile)
|
||||
{
|
||||
Q_UNUSED(path)
|
||||
Q_UNUSED(profile)
|
||||
|
@ -28,7 +28,7 @@ class SomnoposeLoader : public MachineLoader
|
||||
virtual ~SomnoposeLoader();
|
||||
virtual bool Detect(const QString &path) { Q_UNUSED(path); return false; } // bypass autoscanner
|
||||
|
||||
virtual int Open(QString &path, Profile *profile);
|
||||
virtual int Open(QString path, Profile *profile);
|
||||
virtual int OpenFile(QString filename);
|
||||
static void Register();
|
||||
|
||||
|
@ -23,15 +23,14 @@
|
||||
|
||||
ZEOLoader::ZEOLoader()
|
||||
{
|
||||
//ctor
|
||||
m_type = MT_SLEEPSTAGE;
|
||||
}
|
||||
|
||||
ZEOLoader::~ZEOLoader()
|
||||
{
|
||||
//dtor
|
||||
}
|
||||
|
||||
int ZEOLoader::Open(QString &path, Profile *profile)
|
||||
int ZEOLoader::Open(QString path, Profile *profile)
|
||||
{
|
||||
Q_UNUSED(path)
|
||||
Q_UNUSED(profile)
|
||||
|
@ -28,7 +28,7 @@ class ZEOLoader : public MachineLoader
|
||||
virtual ~ZEOLoader();
|
||||
virtual bool Detect(const QString &path) { Q_UNUSED(path); return false; } // bypass autoscanner
|
||||
|
||||
virtual int Open(QString &path, Profile *profile);
|
||||
virtual int Open(QString path, Profile *profile);
|
||||
virtual int OpenFile(QString filename);
|
||||
static void Register();
|
||||
|
||||
|
@ -515,7 +515,7 @@ void SaveTask::run()
|
||||
|
||||
void Machine::queTask(ImportTask * task)
|
||||
{
|
||||
if (PROFILE.session->multithreading()) {
|
||||
if (0) { //PROFILE.session->multithreading()) {
|
||||
m_tasklist.push_back(task);
|
||||
return;
|
||||
}
|
||||
@ -526,7 +526,7 @@ void Machine::queTask(ImportTask * task)
|
||||
|
||||
void Machine::runTasks()
|
||||
{
|
||||
if (!PROFILE.session->multithreading()) {
|
||||
if (0) { //!PROFILE.session->multithreading()) {
|
||||
Q_ASSERT(m_tasklist.isEmpty());
|
||||
return;
|
||||
}
|
||||
|
@ -29,7 +29,8 @@ QList<MachineLoader *> GetLoaders(MachineType mt)
|
||||
if (mt == MT_UNKNOWN) {
|
||||
list.push_back(m_loaders.at(i));
|
||||
} else {
|
||||
if (m_loaders.at(i)->type() == mt) {
|
||||
MachineType mtype = m_loaders.at(i)->type();
|
||||
if (mtype == mt) {
|
||||
list.push_back(m_loaders.at(i));
|
||||
}
|
||||
}
|
||||
@ -52,6 +53,9 @@ void DestroyLoaders()
|
||||
|
||||
MachineLoader::MachineLoader() :QObject(nullptr)
|
||||
{
|
||||
m_importing = m_abort = m_streaming = false;
|
||||
m_type = MT_UNKNOWN;
|
||||
m_status = NEUTRAL;
|
||||
}
|
||||
|
||||
MachineLoader::~MachineLoader()
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "zlib.h"
|
||||
|
||||
class MachineLoader;
|
||||
enum DeviceStatus { NEUTRAL, IMPORTING, LIVE };
|
||||
|
||||
|
||||
/*! \class MachineLoader
|
||||
@ -39,7 +40,7 @@ class MachineLoader: public QObject
|
||||
virtual bool Detect(const QString & path) = 0;
|
||||
|
||||
//! \brief Override this to scan path and detect new machine data
|
||||
virtual int Open(QString &path, Profile *) = 0; // Scans for new content
|
||||
virtual int Open(QString path, Profile *) = 0; // Scans for new content
|
||||
|
||||
//! \brief Override to returns the Version number of this MachineLoader
|
||||
virtual int Version() = 0;
|
||||
@ -48,10 +49,42 @@ class MachineLoader: public QObject
|
||||
virtual const QString &ClassName() = 0;
|
||||
inline MachineType type() { return m_type; }
|
||||
|
||||
virtual bool openDevice() { return false; }
|
||||
virtual void closeDevice() {}
|
||||
virtual bool scanDevice(QString keyword="", quint16 vendor_id=0, quint16 product_id=0) {
|
||||
Q_UNUSED(keyword)
|
||||
Q_UNUSED(vendor_id)
|
||||
Q_UNUSED(product_id)
|
||||
return false;
|
||||
}
|
||||
|
||||
void queTask(ImportTask * task);
|
||||
|
||||
//! \brief Process Task list using all available threads.
|
||||
void runTasks();
|
||||
|
||||
inline bool isStreaming() { return m_streaming; }
|
||||
inline bool isImporting() { return m_importing; }
|
||||
inline bool isAborted() { return m_abort; }
|
||||
void abort() { m_abort = true; }
|
||||
|
||||
virtual void process() {}
|
||||
|
||||
DeviceStatus status() { return m_status; }
|
||||
void setStatus(DeviceStatus status) { m_status = status; }
|
||||
|
||||
signals:
|
||||
void updateProgress(int cnt, int total);
|
||||
|
||||
protected slots:
|
||||
virtual void dataAvailable() {}
|
||||
virtual void resetImportTimeout() {}
|
||||
virtual void startImportTimeout() {}
|
||||
|
||||
protected:
|
||||
virtual void killTimers(){}
|
||||
virtual void resetDevice(){}
|
||||
|
||||
protected:
|
||||
//! \brief Contains a list of Machine records known by this loader
|
||||
QList<Machine *> m_machlist;
|
||||
@ -63,6 +96,12 @@ class MachineLoader: public QObject
|
||||
int m_currenttask;
|
||||
int m_totaltasks;
|
||||
|
||||
bool m_streaming;
|
||||
bool m_importing;
|
||||
bool m_abort;
|
||||
|
||||
DeviceStatus m_status;
|
||||
|
||||
private:
|
||||
QList<ImportTask *> m_tasklist;
|
||||
};
|
||||
|
@ -392,7 +392,7 @@ int Profile::Import(QString path)
|
||||
path.chop(1);
|
||||
}
|
||||
|
||||
QList<MachineLoader *>loaders = GetLoaders();
|
||||
QList<MachineLoader *>loaders = GetLoaders(MT_CPAP);
|
||||
|
||||
Q_FOREACH(MachineLoader * loader, loaders) {
|
||||
if (c += loader->Open(path, this)) {
|
||||
@ -405,7 +405,7 @@ int Profile::Import(QString path)
|
||||
|
||||
MachineLoader *GetLoader(QString name)
|
||||
{
|
||||
QList<MachineLoader *>loaders = GetLoaders();
|
||||
QList<MachineLoader *>loaders = GetLoaders(MT_CPAP);
|
||||
|
||||
Q_FOREACH(MachineLoader * loader, loaders) {
|
||||
if (loader->ClassName() == name) {
|
||||
|
@ -454,6 +454,8 @@ void Daily::closeEvent(QCloseEvent *event)
|
||||
void Daily::doToggleSession(Session * sess)
|
||||
{
|
||||
Q_UNUSED(sess)
|
||||
sess->setEnabled(!sess->enabled());
|
||||
|
||||
// sess->StoreSummary();
|
||||
Day *day=PROFILE.GetDay(previous_date,MT_CPAP);
|
||||
if (day) {
|
||||
@ -553,6 +555,8 @@ void Daily::ReloadGraphs()
|
||||
ui->calendar->setSelectedDate(d);
|
||||
ui->calendar->blockSignals(false);
|
||||
Load(d);
|
||||
ui->calButton->setText(ui->calendar->selectedDate().toString(Qt::TextDate));
|
||||
|
||||
}
|
||||
|
||||
void Daily::on_calendar_currentPageChanged(int year, int month)
|
||||
@ -1451,7 +1455,7 @@ void Daily::Load(QDate date)
|
||||
if (cpap) {
|
||||
sessbar=new SessionBar(this);
|
||||
sessbar->setMouseTracking(true);
|
||||
connect(sessbar, SIGNAL(toggledSession(Session*)), this, SLOT(doToggleSession(Session*)));
|
||||
connect(sessbar, SIGNAL(sessionClicked(Session*)), this, SLOT(doToggleSession(Session*)));
|
||||
int c=0;
|
||||
|
||||
for (i=cpap->begin();i!=cpap->end();++i) {
|
||||
|
@ -9,14 +9,18 @@
|
||||
|
||||
<b>New features & bugs fixes in v0.9.6</b><br/>
|
||||
<list>
|
||||
<li>Switched from OpenGL to Qt QPainter backend</li>
|
||||
<li>Deprecated old Oximetery page</li>
|
||||
<li>Completely redesigned Oximetery with new Import Wizard</li>
|
||||
<li>Improved data Purge and Reimport from backup abilities</li>
|
||||
<li>Improved ResMed STR.edf summary import support</li>
|
||||
<li>Several Fisher & Paykel Icon improvements (including automatic backup)</li>
|
||||
<li>Switched from raw OpenGL to Qt QPainter wrapped backend</li>
|
||||
<li>Switched to Qt's pixmap caching engine</li>
|
||||
<li>Fix ResMed session grouping bug that caused session double-ups</li>
|
||||
<li>Fix EDF importer glitch on windows builds that led to faulty graph displays</li>
|
||||
<li>Fixed CPAP card autoscanner on Windows platform</li>
|
||||
<li>Added timeout dialog to CPAP card autoscanner</li>
|
||||
<li>Microsoft compiler fixes</li>
|
||||
<li>Fisher & Paykel Icon timestamp fix</li>
|
||||
</list>
|
||||
<br/>
|
||||
<b>New features & bugs fixes since v0.9.5</b><br/>
|
||||
|
@ -245,11 +245,11 @@ retry_directory:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
initialize();
|
||||
PRS1Loader::Register();
|
||||
CMS50Loader::Register();
|
||||
//ZEOLoader::Register(); // Use outside of directory importer..
|
||||
ResmedLoader::Register();
|
||||
IntellipapLoader::Register();
|
||||
FPIconLoader::Register();
|
||||
CMS50Loader::Register();
|
||||
//ZEOLoader::Register(); // Use outside of directory importer..
|
||||
|
||||
p_pref = new Preferences("Preferences");
|
||||
p_layout = new Preferences("Layout");
|
||||
|
@ -32,6 +32,9 @@
|
||||
#include <QTextDocument>
|
||||
#include <QTranslator>
|
||||
#include <QPushButton>
|
||||
#include <QCalendarWidget>
|
||||
#include "common_gui.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
// Custom loaders that don't autoscan..
|
||||
@ -160,6 +163,22 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
|
||||
ui->actionDebug->setChecked(PROFILE.general->showDebug());
|
||||
|
||||
QTextCharFormat format = ui->statStartDate->calendarWidget()->weekdayTextFormat(Qt::Saturday);
|
||||
format.setForeground(QBrush(Qt::black, Qt::SolidPattern));
|
||||
Qt::DayOfWeek dow=firstDayOfWeekFromLocale();
|
||||
ui->statStartDate->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format);
|
||||
ui->statStartDate->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format);
|
||||
ui->statStartDate->calendarWidget()->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader);
|
||||
ui->statStartDate->calendarWidget()->setFirstDayOfWeek(dow);
|
||||
ui->statEndDate->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format);
|
||||
ui->statEndDate->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format);
|
||||
ui->statEndDate->calendarWidget()->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader);
|
||||
ui->statEndDate->calendarWidget()->setFirstDayOfWeek(dow);
|
||||
|
||||
ui->statEndDate->setVisible(false);
|
||||
ui->statStartDate->setVisible(false);
|
||||
|
||||
ui->reportModeRange->setVisible(false);
|
||||
switch(PROFILE.general->statReportMode()) {
|
||||
case 0:
|
||||
ui->reportModeStandard->setChecked(true);
|
||||
@ -167,6 +186,11 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
case 1:
|
||||
ui->reportModeMonthly->setChecked(true);
|
||||
break;
|
||||
case 2:
|
||||
ui->reportModeRange->setChecked(true);
|
||||
ui->statEndDate->setVisible(true);
|
||||
ui->statStartDate->setVisible(true);
|
||||
break;
|
||||
default:
|
||||
PROFILE.general->setStatReportMode(0);
|
||||
}
|
||||
@ -243,19 +267,16 @@ void MainWindow::closeEvent(QCloseEvent * event)
|
||||
if (daily) {
|
||||
daily->close();
|
||||
daily->deleteLater();
|
||||
// delete daily;
|
||||
}
|
||||
|
||||
if (overview) {
|
||||
overview->close();
|
||||
overview->deleteLater();
|
||||
// delete overview;
|
||||
}
|
||||
|
||||
if (oximetry) {
|
||||
oximetry->close();
|
||||
oximetry->deleteLater();
|
||||
// delete oximetry;
|
||||
}
|
||||
|
||||
// Shutdown and Save the current User profile
|
||||
@ -317,7 +338,8 @@ void MainWindow::PopulatePurgeMenu()
|
||||
QString name = mach->properties[STR_PROP_Brand]+" "+
|
||||
mach->properties[STR_PROP_Model]+" "+
|
||||
mach->properties[STR_PROP_Serial];
|
||||
QAction * action = new QAction(name, ui->menu_Purge_CPAP_Data);
|
||||
|
||||
QAction * action = new QAction(name.replace("&","&&"), ui->menu_Purge_CPAP_Data);
|
||||
action->setData(mach->GetClass()+":"+mach->properties[STR_PROP_Serial]);
|
||||
ui->menu_Purge_CPAP_Data->addAction(action);
|
||||
ui->menu_Purge_CPAP_Data->connect(ui->menu_Purge_CPAP_Data, SIGNAL(triggered(QAction*)), this, SLOT(on_actionPurgeMachine(QAction*)));
|
||||
@ -369,6 +391,9 @@ void MainWindow::Startup()
|
||||
GenerateStatistics();
|
||||
ui->tabWidget->setCurrentWidget(ui->statisticsTab);
|
||||
|
||||
ui->statStartDate->setDate(PROFILE.FirstDay());
|
||||
ui->statEndDate->setDate(PROFILE.LastDay());
|
||||
|
||||
if (daily) { daily->ReloadGraphs(); }
|
||||
|
||||
if (overview) { overview->ReloadGraphs(); }
|
||||
@ -526,7 +551,7 @@ QStringList MainWindow::detectCPAPCards()
|
||||
|
||||
QStringList datapaths;
|
||||
|
||||
QList<MachineLoader *>loaders = GetLoaders();
|
||||
QList<MachineLoader *>loaders = GetLoaders(MT_CPAP);
|
||||
QTime time;
|
||||
time.start();
|
||||
|
||||
@ -583,7 +608,7 @@ void MainWindow::on_action_Import_Data_triggered()
|
||||
|
||||
QStringList datapaths = detectCPAPCards();
|
||||
|
||||
QList<MachineLoader *>loaders = GetLoaders();
|
||||
QList<MachineLoader *>loaders = GetLoaders(MT_CPAP);
|
||||
|
||||
QTime time;
|
||||
time.start();
|
||||
@ -1197,30 +1222,66 @@ void MainWindow::selectOximetryTab()
|
||||
on_oximetryButton_clicked();
|
||||
}
|
||||
|
||||
#include "oximeterimport.h"
|
||||
QDateTime datetimeDialog(QDateTime datetime, QString message);
|
||||
|
||||
void MainWindow::on_oximetryButton_clicked()
|
||||
{
|
||||
bool first = false;
|
||||
OximeterImport oxiimp(this);
|
||||
oxiimp.exec();
|
||||
|
||||
if (!oximetry) {
|
||||
if (!PROFILE.oxi->oximetryEnabled()) {
|
||||
if (QMessageBox::question(this, STR_MessageBox_Question,
|
||||
tr("Do you have a CMS50[x] Oximeter?\nOne is required to use this section."), QMessageBox::Yes,
|
||||
QMessageBox::No) == QMessageBox::No) { return; }
|
||||
return;
|
||||
|
||||
// QDateTime current=QDateTime::currentDateTime();
|
||||
// DateTimeDialog datedlg("Oximetry Session Start Time", this);
|
||||
// current = datedlg.execute(current);
|
||||
// return;
|
||||
// bool first = false;
|
||||
|
||||
// if (!oximetry) {
|
||||
// if (!PROFILE.oxi->oximetryEnabled()) {
|
||||
// if (QMessageBox::question(this, STR_MessageBox_Question,
|
||||
// tr("Do you have a CMS50[x] Oximeter?\nOne is required to use this section."), QMessageBox::Yes,
|
||||
// QMessageBox::No) == QMessageBox::No) { return; }
|
||||
|
||||
// PROFILE.oxi->setOximetryEnabled(true);
|
||||
// }
|
||||
|
||||
// // oximetry = new Oximetry(ui->tabWidget, daily->graphView());
|
||||
// // ui->tabWidget->insertTab(3, oximetry, STR_TR_Oximetry);
|
||||
// first = true;
|
||||
// }
|
||||
|
||||
|
||||
// QDialog dlg(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog);
|
||||
|
||||
// dlg.setWindowModality(Qt::ApplicationModal);
|
||||
// dlg.setModal(true);
|
||||
|
||||
// QVBoxLayout layout(&dlg);
|
||||
// QLabel msg("<h2>"+tr("Please connect your %1 oximeter.").arg(active)+"</h2>");
|
||||
// QPushButton cancel(STR_MessageBox_Cancel);
|
||||
|
||||
// layout.addWidget(&msg,1);
|
||||
// layout.addWidget(qprogress,1);
|
||||
// layout.addWidget(&cancel);
|
||||
|
||||
// qprogress->setMaximum(PORTSCAN_TIMEOUT);
|
||||
// qprogress->setVisible(true);
|
||||
|
||||
// dlg.connect(&cancel, SIGNAL(clicked()), &dlg, SLOT(hide()));
|
||||
// dlg.show();
|
||||
|
||||
// QApplication::processEvents();
|
||||
|
||||
PROFILE.oxi->setOximetryEnabled(true);
|
||||
}
|
||||
|
||||
oximetry = new Oximetry(ui->tabWidget, daily->graphView());
|
||||
ui->tabWidget->insertTab(3, oximetry, STR_TR_Oximetry);
|
||||
first = true;
|
||||
}
|
||||
|
||||
// MW: Instead, how about starting a direct import?
|
||||
oximetry->serialImport();
|
||||
// oximetry->serialImport();
|
||||
|
||||
ui->tabWidget->setCurrentWidget(oximetry);
|
||||
// ui->tabWidget->setCurrentWidget(oximetry);
|
||||
|
||||
if (!first) { oximetry->RedrawGraphs(); }
|
||||
// if (!first) { oximetry->RedrawGraphs(); }
|
||||
|
||||
qstatus2->setText(STR_TR_Oximetry);
|
||||
}
|
||||
@ -1346,10 +1407,14 @@ void MainWindow::on_actionPrint_Report_triggered()
|
||||
|
||||
void MainWindow::on_action_Edit_Profile_triggered()
|
||||
{
|
||||
NewProfile newprof(this);
|
||||
newprof.edit(PREF[STR_GEN_Profile].toString());
|
||||
newprof.exec();
|
||||
|
||||
NewProfile *newprof = new NewProfile(this);
|
||||
QString name =PREF[STR_GEN_Profile].toString();
|
||||
newprof->edit(name);
|
||||
newprof->setWindowModality(Qt::ApplicationModal);
|
||||
newprof->setModal(true);
|
||||
newprof->exec();
|
||||
qDebug() << newprof;
|
||||
delete newprof;
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Link_Graph_Groups_toggled(bool arg1)
|
||||
@ -2152,6 +2217,14 @@ void MainWindow::on_actionImport_Somnopose_Data_triggered()
|
||||
|
||||
void MainWindow::GenerateStatistics()
|
||||
{
|
||||
QDate first = PROFILE.FirstDay();
|
||||
QDate last = PROFILE.LastDay();
|
||||
ui->statStartDate->setMinimumDate(first);
|
||||
ui->statStartDate->setMaximumDate(last);
|
||||
|
||||
ui->statEndDate->setMinimumDate(first);
|
||||
ui->statEndDate->setMaximumDate(last);
|
||||
|
||||
Statistics stats;
|
||||
QString html = stats.GenerateHTML();
|
||||
|
||||
@ -2183,6 +2256,8 @@ void MainWindow::on_statisticsView_linkClicked(const QUrl &arg1)
|
||||
|
||||
void MainWindow::on_reportModeMonthly_clicked()
|
||||
{
|
||||
ui->statStartDate->setVisible(false);
|
||||
ui->statEndDate->setVisible(false);
|
||||
if (PROFILE.general->statReportMode() != 1) {
|
||||
PROFILE.general->setStatReportMode(1);
|
||||
GenerateStatistics();
|
||||
@ -2191,8 +2266,21 @@ void MainWindow::on_reportModeMonthly_clicked()
|
||||
|
||||
void MainWindow::on_reportModeStandard_clicked()
|
||||
{
|
||||
ui->statStartDate->setVisible(false);
|
||||
ui->statEndDate->setVisible(false);
|
||||
if (PROFILE.general->statReportMode() != 0) {
|
||||
PROFILE.general->setStatReportMode(0);
|
||||
GenerateStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::on_reportModeRange_clicked()
|
||||
{
|
||||
ui->statStartDate->setVisible(true);
|
||||
ui->statEndDate->setVisible(true);
|
||||
if (PROFILE.general->statReportMode() != 2) {
|
||||
PROFILE.general->setStatReportMode(2);
|
||||
GenerateStatistics();
|
||||
}
|
||||
}
|
||||
|
@ -310,6 +310,7 @@ class MainWindow : public QMainWindow
|
||||
|
||||
void on_actionPurgeMachine(QAction *action);
|
||||
|
||||
void on_reportModeRange_clicked();
|
||||
|
||||
private:
|
||||
int importCPAP(const QString &path, const QString &message);
|
||||
|
@ -1045,6 +1045,27 @@ color: yellow;</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="reportModeRange">
|
||||
<property name="text">
|
||||
<string>Date Range</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDateEdit" name="statStartDate">
|
||||
<property name="calendarPopup">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDateEdit" name="statEndDate">
|
||||
<property name="calendarPopup">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
|
404
sleepyhead/oximeterimport.cpp
Normal file
404
sleepyhead/oximeterimport.cpp
Normal file
@ -0,0 +1,404 @@
|
||||
#include <QThread>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "Graphs/gYAxis.h"
|
||||
#include "Graphs/gXAxis.h"
|
||||
|
||||
#include "oximeterimport.h"
|
||||
#include "ui_oximeterimport.h"
|
||||
|
||||
#include "SleepLib/loader_plugins/cms50_loader.h"
|
||||
|
||||
Qt::DayOfWeek firstDayOfWeekFromLocale();
|
||||
|
||||
OximeterImport::OximeterImport(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::OximeterImport)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowTitle(tr("Oximeter Import Wizard"));
|
||||
ui->stackedWidget->setCurrentIndex(0);
|
||||
oximodule = nullptr;
|
||||
ui->retryButton->setVisible(false);
|
||||
liveView = new gGraphView(this);
|
||||
liveView->setVisible(false);
|
||||
ui->stopButton->setVisible(false);
|
||||
ui->syncSaveButton->setVisible(false);
|
||||
|
||||
QVBoxLayout * lvlayout = new QVBoxLayout;
|
||||
lvlayout->setMargin(0);
|
||||
ui->liveViewFrame->setLayout(lvlayout);
|
||||
lvlayout->addWidget(liveView);
|
||||
PLETHY = new gGraph(liveView, STR_TR_Plethy, STR_UNIT_Hz);
|
||||
PLETHY->AddLayer(new gYAxis(), LayerLeft, gYAxis::Margin);
|
||||
PLETHY->AddLayer(new gXAxis(), LayerBottom, 0, 20);
|
||||
PLETHY->AddLayer(plethyChart = new gLineChart(OXI_Plethy));
|
||||
|
||||
ui->calendarWidget->setFirstDayOfWeek(Qt::Sunday);
|
||||
QTextCharFormat format = ui->calendarWidget->weekdayTextFormat(Qt::Saturday);
|
||||
format.setForeground(QBrush(Qt::black, Qt::SolidPattern));
|
||||
ui->calendarWidget->setWeekdayTextFormat(Qt::Saturday, format);
|
||||
ui->calendarWidget->setWeekdayTextFormat(Qt::Sunday, format);
|
||||
ui->calendarWidget->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader);
|
||||
Qt::DayOfWeek dow=firstDayOfWeekFromLocale();
|
||||
ui->calendarWidget->setFirstDayOfWeek(dow);
|
||||
|
||||
ui->dateTimeEdit->setMinimumHeight(ui->dateTimeEdit->height() + 10);
|
||||
ui->syncCPAPGroup->setVisible(false);
|
||||
|
||||
|
||||
QVBoxLayout * layout = new QVBoxLayout;
|
||||
layout->setMargin(0);
|
||||
ui->sessBarFrame->setLayout(layout);
|
||||
sessbar = new SessionBar(this);
|
||||
sessbar->setSelectMode(true);
|
||||
sessbar->setMouseTracking(true);
|
||||
sessbar->setMinimumHeight(40);
|
||||
connect(sessbar, SIGNAL(sessionClicked(Session*)), this, SLOT(onSessionSelected(Session*)));
|
||||
layout->addWidget(sessbar, 1);
|
||||
}
|
||||
|
||||
OximeterImport::~OximeterImport()
|
||||
{
|
||||
disconnect(sessbar, SIGNAL(sessionClicked(Session*)), this, SLOT(onSessionSelected(Session*)));
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void OximeterImport::on_nextButton_clicked()
|
||||
{
|
||||
int i = ui->stackedWidget->currentIndex();
|
||||
i++;
|
||||
if (i >= ui->stackedWidget->count()) i = 0;
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
ui->nextButton->setVisible(true);
|
||||
ui->nextButton->setText("&Start");
|
||||
break;
|
||||
case 1:
|
||||
ui->nextButton->setVisible(false);
|
||||
break;
|
||||
default:
|
||||
ui->nextButton->setVisible(true);
|
||||
|
||||
|
||||
}
|
||||
ui->stackedWidget->setCurrentIndex(i);
|
||||
}
|
||||
|
||||
void OximeterImport::updateStatus(QString msg)
|
||||
{
|
||||
ui->logBox->appendPlainText(msg);
|
||||
ui->directImportStatus->setText(msg);
|
||||
ui->liveStatusLabel->setText(msg);
|
||||
}
|
||||
|
||||
|
||||
MachineLoader * OximeterImport::detectOximeter()
|
||||
{
|
||||
const int PORTSCAN_TIMEOUT=30000;
|
||||
const int delay=100;
|
||||
|
||||
|
||||
ui->retryButton->setVisible(false);
|
||||
|
||||
QList<MachineLoader *> loaders = GetLoaders(MT_OXIMETER);
|
||||
|
||||
|
||||
updateStatus(tr("Scanning for compatible oximeters"));
|
||||
|
||||
ui->progressBar->setMaximum(PORTSCAN_TIMEOUT);
|
||||
|
||||
QTime time;
|
||||
time.start();
|
||||
|
||||
oximodule = nullptr;
|
||||
int elapsed=0;
|
||||
do {
|
||||
for (int i=0; i < loaders.size(); ++i) {
|
||||
MachineLoader * loader = loaders[i];
|
||||
if (loader->openDevice()) {
|
||||
oximodule = loader;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (oximodule)
|
||||
break;
|
||||
|
||||
QThread::msleep(delay);
|
||||
elapsed = time.elapsed();
|
||||
ui->progressBar->setValue(elapsed);
|
||||
QApplication::processEvents();
|
||||
if (!isVisible()) {
|
||||
return oximodule = nullptr;
|
||||
}
|
||||
|
||||
} while (elapsed < PORTSCAN_TIMEOUT);
|
||||
|
||||
if (!oximodule) {
|
||||
updateStatus(tr("Could not detect any connected oximeter devices."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
updateStatus(tr("Connecting to %1 Oximeter").arg(oximodule->ClassName()));
|
||||
|
||||
return oximodule;
|
||||
}
|
||||
|
||||
void OximeterImport::on_directImportButton_clicked()
|
||||
{
|
||||
ui->stackedWidget->setCurrentWidget(ui->directImportPage);
|
||||
|
||||
oximodule = detectOximeter();
|
||||
if (!oximodule)
|
||||
return;
|
||||
|
||||
ui->connectLabel->setText("<h2>"+tr("Select upload option on %1").arg(oximodule->ClassName())+"</h2>");
|
||||
updateStatus(tr("Waiting for you to start the upload process..."));
|
||||
|
||||
|
||||
connect(oximodule, SIGNAL(updateProgress(int,int)), this, SLOT(doUpdateProgress(int,int)));
|
||||
|
||||
oximodule->Open("import", p_profile);
|
||||
|
||||
// Wait to start import streaming..
|
||||
while (!oximodule->isImporting() && !oximodule->isAborted()) {
|
||||
QThread::msleep(100);
|
||||
QApplication::processEvents();
|
||||
if (!isVisible()) {
|
||||
disconnect(oximodule, SIGNAL(updateProgress(int,int)), this, SLOT(doUpdateProgress(int,int)));
|
||||
oximodule->abort();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!oximodule->isStreaming()) {
|
||||
disconnect(oximodule, SIGNAL(updateProgress(int,int)), this, SLOT(doUpdateProgress(int,int)));
|
||||
ui->retryButton->setVisible(true);
|
||||
ui->progressBar->setValue(0);
|
||||
oximodule->abort();
|
||||
return;
|
||||
}
|
||||
ui->connectLabel->setText("<h2>"+tr("%1 device is uploading data...").arg(oximodule->ClassName())+"</h2>");
|
||||
updateStatus(tr("Please wait until oximeter upload process completes. Do not unplug your oximeter."));
|
||||
|
||||
// Wait for import streaming to finish
|
||||
|
||||
// Can't abort this bit or the oximeter will get confused...
|
||||
ui->cancelButton->setVisible(false);
|
||||
while (oximodule->isImporting() && !oximodule->isAborted()) {
|
||||
QThread::msleep(50);
|
||||
QApplication::processEvents();
|
||||
}
|
||||
ui->cancelButton->setVisible(true);
|
||||
updateStatus(tr("Oximeter import completed.. Processing data"));
|
||||
oximodule->process();
|
||||
disconnect(oximodule, SIGNAL(updateProgress(int,int)), this, SLOT(doUpdateProgress(int,int)));
|
||||
|
||||
ui->stackedWidget->setCurrentWidget(ui->syncPage);
|
||||
ui->syncSaveButton->setVisible(true);
|
||||
|
||||
ui->calendarWidget->setMinimumDate(PROFILE.FirstDay());
|
||||
ui->calendarWidget->setMaximumDate(PROFILE.LastDay());
|
||||
|
||||
on_calendarWidget_clicked(PROFILE.LastDay());
|
||||
|
||||
}
|
||||
|
||||
void OximeterImport::doUpdateProgress(int v, int t)
|
||||
{
|
||||
ui->progressBar->setMaximum(t);
|
||||
ui->progressBar->setValue(v);
|
||||
QApplication::processEvents();
|
||||
}
|
||||
|
||||
|
||||
void OximeterImport::on_fileImportButton_clicked()
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
|
||||
const QString documentsFolder = QDesktopServices::storageLocation(
|
||||
QDesktopServices::DocumentsLocation);
|
||||
#else
|
||||
const QString documentsFolder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
||||
#endif
|
||||
|
||||
|
||||
QString filename = QFileDialog::getOpenFileName(this, tr("Select a valid oximetry data file"), documentsFolder, "Oximetry Files (*.spo *.spor *.spo2)");
|
||||
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
QList<MachineLoader *> loaders = GetLoaders(MT_OXIMETER);
|
||||
|
||||
bool success = false;
|
||||
|
||||
oximodule = nullptr;
|
||||
Q_FOREACH(MachineLoader * loader, loaders) {
|
||||
if (loader->Open(filename,p_profile)) {
|
||||
success = true;
|
||||
oximodule = loader;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
QMessageBox::warning(this, STR_MessageBox_Warning, tr("No Oximetery module could parse the given file:")+QString("\n\n%1").arg(filename), QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
ui->stackedWidget->setCurrentWidget(ui->syncPage);
|
||||
ui->syncSaveButton->setVisible(true);
|
||||
|
||||
ui->calendarWidget->setMinimumDate(PROFILE.FirstDay());
|
||||
ui->calendarWidget->setMaximumDate(PROFILE.LastDay());
|
||||
|
||||
on_calendarWidget_clicked(PROFILE.LastDay());
|
||||
}
|
||||
|
||||
void OximeterImport::on_liveImportButton_clicked()
|
||||
{
|
||||
ui->stackedWidget->setCurrentWidget(ui->liveImportPage);
|
||||
ui->liveImportPage->layout()->addWidget(ui->progressBar);
|
||||
QApplication::processEvents();
|
||||
|
||||
liveView->setEmptyText(tr("Oximeter not detected"));
|
||||
liveView->setVisible(true);
|
||||
QApplication::processEvents();
|
||||
|
||||
MachineLoader * oximodule = detectOximeter();
|
||||
|
||||
if (!oximodule) {
|
||||
updateStatus("Couldn't access oximeter");
|
||||
ui->retryButton->setVisible(true);
|
||||
ui->progressBar->setValue(0);
|
||||
|
||||
return;
|
||||
}
|
||||
ui->liveConnectLabel->setText("Live Oximetery Mode");
|
||||
liveView->setEmptyText(tr("Still Under Construction")); // Recording...
|
||||
ui->progressBar->hide();
|
||||
liveView->update();
|
||||
oximodule->Open("live",p_profile);
|
||||
ui->stopButton->setVisible(true);
|
||||
while (oximodule->isStreaming() && !oximodule->isAborted()) {
|
||||
QThread::msleep(50);
|
||||
QApplication::processEvents();
|
||||
}
|
||||
ui->stopButton->setVisible(false);
|
||||
ui->liveConnectLabel->setText("Live Import Stopped");
|
||||
liveView->setEmptyText(tr("Live Oximetery Stopped"));
|
||||
updateStatus(tr("Live Oximetery import has been stopped"));
|
||||
|
||||
oximodule->closeDevice();
|
||||
// detect oximeter
|
||||
}
|
||||
|
||||
void OximeterImport::on_retryButton_clicked()
|
||||
{
|
||||
if (ui->stackedWidget->currentWidget() == ui->directImportPage) {
|
||||
on_directImportButton_clicked();
|
||||
} else if (ui->stackedWidget->currentWidget() == ui->liveImportPage) {
|
||||
on_liveImportButton_clicked();
|
||||
}
|
||||
}
|
||||
|
||||
void OximeterImport::on_stopButton_clicked()
|
||||
{
|
||||
if (oximodule) {
|
||||
oximodule->abort();
|
||||
}
|
||||
}
|
||||
|
||||
void OximeterImport::on_calendarWidget_clicked(const QDate &date)
|
||||
{
|
||||
Day * day = PROFILE.GetGoodDay(date, MT_CPAP);
|
||||
|
||||
sessbar->clear();
|
||||
if (day) {
|
||||
QDateTime time=QDateTime::fromMSecsSinceEpoch(day->first());
|
||||
sessbar->clear();
|
||||
QList<QColor> colors;
|
||||
colors.push_back("#ffffe0");
|
||||
colors.push_back("#ffe0ff");
|
||||
colors.push_back("#e0ffff");
|
||||
QList<Session *>::iterator i;
|
||||
int j=0;
|
||||
for (i=day->begin(); i != day->end(); ++i) {
|
||||
sessbar->add((*i),colors.at(j++ % colors.size()));
|
||||
}
|
||||
sessbar->setVisible(true);
|
||||
ui->sessbarLabel->setText(QString("%1 session(s), starting at %2").arg(day->size()).arg(time.time().toString("hh:mm:ssap")));
|
||||
// sessbar->setSelected(0);
|
||||
// ui->dateTimeEdit->setDateTime(time);
|
||||
} else {
|
||||
ui->sessbarLabel->setText("No CPAP Data available for this date"); // sessbar->setVisible(false);
|
||||
}
|
||||
|
||||
sessbar->update();
|
||||
}
|
||||
|
||||
void OximeterImport::on_calendarWidget_selectionChanged()
|
||||
{
|
||||
on_calendarWidget_clicked(ui->calendarWidget->selectedDate());
|
||||
}
|
||||
void OximeterImport::onSessionSelected(Session * session)
|
||||
{
|
||||
QDateTime time=QDateTime::fromMSecsSinceEpoch(session->first());
|
||||
ui->dateTimeEdit->setDateTime(time);
|
||||
}
|
||||
|
||||
void OximeterImport::on_sessionBackButton_clicked()
|
||||
{
|
||||
int idx = (sessbar->selected()-1);
|
||||
if (idx >= 0) {
|
||||
sessbar->setSelected(idx);
|
||||
QDateTime datetime = QDateTime::fromMSecsSinceEpoch(sessbar->session(idx)->first());
|
||||
ui->dateTimeEdit->setDateTime(datetime);
|
||||
sessbar->update();
|
||||
}
|
||||
}
|
||||
|
||||
void OximeterImport::on_sessionForwardButton_clicked()
|
||||
{
|
||||
int idx = (sessbar->selected()+1);
|
||||
if (idx < sessbar->count()) {
|
||||
sessbar->setSelected(idx);
|
||||
QDateTime datetime = QDateTime::fromMSecsSinceEpoch(sessbar->session(idx)->first());
|
||||
ui->dateTimeEdit->setDateTime(datetime);
|
||||
sessbar->update();
|
||||
}
|
||||
}
|
||||
|
||||
void OximeterImport::on_radioSyncCPAP_clicked()
|
||||
{
|
||||
if (!ui->syncCPAPGroup->isVisible()) {
|
||||
if (sessbar->selected() < 0) {
|
||||
int idx = 0;
|
||||
if (idx < sessbar->count()) {
|
||||
sessbar->setSelected(idx);
|
||||
QDateTime datetime = QDateTime::fromMSecsSinceEpoch(sessbar->session(idx)->first());
|
||||
ui->dateTimeEdit->setDateTime(datetime);
|
||||
sessbar->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
ui->syncCPAPGroup->setVisible(true);
|
||||
|
||||
}
|
||||
|
||||
void OximeterImport::on_radioSyncOximeter_clicked()
|
||||
{
|
||||
ui->syncCPAPGroup->setVisible(false);
|
||||
}
|
||||
|
||||
void OximeterImport::on_radioSyncManually_clicked()
|
||||
{
|
||||
ui->syncCPAPGroup->setVisible(false);
|
||||
}
|
||||
|
||||
void OximeterImport::on_syncSaveButton_clicked()
|
||||
{
|
||||
|
||||
}
|
68
sleepyhead/oximeterimport.h
Normal file
68
sleepyhead/oximeterimport.h
Normal file
@ -0,0 +1,68 @@
|
||||
#ifndef OXIMETERIMPORT_H
|
||||
#define OXIMETERIMPORT_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "Graphs/gGraphView.h"
|
||||
#include "Graphs/gLineChart.h"
|
||||
#include "SleepLib/machine_loader.h"
|
||||
#include "sessionbar.h"
|
||||
|
||||
namespace Ui {
|
||||
class OximeterImport;
|
||||
}
|
||||
|
||||
class OximeterImport : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OximeterImport(QWidget *parent = 0);
|
||||
~OximeterImport();
|
||||
|
||||
private slots:
|
||||
void on_nextButton_clicked();
|
||||
|
||||
void on_directImportButton_clicked();
|
||||
void doUpdateProgress(int, int);
|
||||
void on_fileImportButton_clicked();
|
||||
|
||||
void on_liveImportButton_clicked();
|
||||
|
||||
void on_retryButton_clicked();
|
||||
|
||||
void on_stopButton_clicked();
|
||||
|
||||
void on_calendarWidget_clicked(const QDate &date);
|
||||
|
||||
void on_calendarWidget_selectionChanged();
|
||||
|
||||
void onSessionSelected(Session * session);
|
||||
|
||||
void on_sessionBackButton_clicked();
|
||||
|
||||
void on_sessionForwardButton_clicked();
|
||||
|
||||
void on_radioSyncCPAP_clicked();
|
||||
|
||||
void on_radioSyncOximeter_clicked();
|
||||
|
||||
void on_radioSyncManually_clicked();
|
||||
|
||||
void on_syncSaveButton_clicked();
|
||||
|
||||
protected:
|
||||
MachineLoader * detectOximeter();
|
||||
void updateStatus(QString msg);
|
||||
|
||||
private:
|
||||
Ui::OximeterImport *ui;
|
||||
MachineLoader * oximodule;
|
||||
gGraphView * liveView;
|
||||
gGraph * PLETHY;
|
||||
gLineChart * plethyChart;
|
||||
SessionBar * sessbar;
|
||||
|
||||
};
|
||||
|
||||
#endif // OXIMETERIMPORT_H
|
1290
sleepyhead/oximeterimport.ui
Normal file
1290
sleepyhead/oximeterimport.ui
Normal file
File diff suppressed because it is too large
Load Diff
@ -43,6 +43,9 @@ SessionBar::SessionBar(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
timer.setParent(this);
|
||||
m_selectIDX = -1;
|
||||
m_selectColor = Qt::red;
|
||||
m_selectMode = false;
|
||||
}
|
||||
//SessionBar::SessionBar(const SessionBar & copy)
|
||||
// :QWidget(this)
|
||||
@ -129,7 +132,7 @@ void SessionBar::mousePressEvent(QMouseEvent *ev)
|
||||
}
|
||||
|
||||
SegType total = mx - mn;
|
||||
double px = double(width() - 5) / double(total);
|
||||
double px = double(width() ) / double(total);
|
||||
|
||||
double sx, ex;
|
||||
QList<SBSeg>::iterator i;
|
||||
@ -141,14 +144,14 @@ void SessionBar::mousePressEvent(QMouseEvent *ev)
|
||||
sx = double(sess->first() - mn) * px;
|
||||
ex = double(sess->last() - mn) * px;
|
||||
|
||||
if (ex > width() - 5) { ex = width() - 5; }
|
||||
if (ex > width()) { ex = width(); }
|
||||
|
||||
//ex-=sx;
|
||||
|
||||
if ((ev->x() > sx) && (ev->x() < ex)
|
||||
if ((ev->x() >= sx) && (ev->x() < ex)
|
||||
&& (ev->y() > 0) && (ev->y() < height())) {
|
||||
(*i).session->setEnabled(!(*i).session->enabled());
|
||||
emit toggledSession((*i).session);
|
||||
m_selectIDX = cnt;
|
||||
emit sessionClicked((*i).session);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -217,7 +220,9 @@ void SessionBar::paintEvent(QPaintEvent *)
|
||||
|
||||
double sx, ex;
|
||||
QList<SBSeg>::iterator i;
|
||||
QRect selectRect;
|
||||
|
||||
int cnt = 0;
|
||||
for (i = segments.begin(); i != segments.end(); ++i) {
|
||||
SBSeg &seg = *i;
|
||||
qint64 mm = seg.session->first(), MM = seg.session->last(), L = MM - mm;
|
||||
@ -242,8 +247,9 @@ void SessionBar::paintEvent(QPaintEvent *)
|
||||
QLinearGradient linearGrad(QPointF(0, 0), QPointF(0, height() / 2));
|
||||
linearGrad.setSpread(QGradient::ReflectSpread);
|
||||
QColor col = seg.color;
|
||||
|
||||
if (seg.highlight) { col = brighten(col); }
|
||||
if (m_selectMode && (cnt == m_selectIDX)) {
|
||||
// col = m_selectColor;
|
||||
} else if (seg.highlight) { col = brighten(col); }
|
||||
|
||||
linearGrad.setColorAt(0, col);
|
||||
linearGrad.setColorAt(1, brighten(col));
|
||||
@ -261,10 +267,24 @@ void SessionBar::paintEvent(QPaintEvent *)
|
||||
QRect rect = painter.boundingRect(segrect, Qt::AlignCenter, msg);
|
||||
|
||||
if (rect.width() < segrect.width()) {
|
||||
painter.setPen(Qt::black);
|
||||
painter.drawText(segrect, Qt::AlignCenter, msg);
|
||||
}
|
||||
|
||||
|
||||
if (m_selectMode && (cnt == m_selectIDX)) {
|
||||
painter.setPen(QPen(m_selectColor, 3));
|
||||
} else {
|
||||
painter.setPen(QPen(Qt::black, 1));
|
||||
}
|
||||
painter.drawRect(segrect);
|
||||
cnt++;
|
||||
|
||||
}
|
||||
if (!cnt) {
|
||||
QString msg = tr("No Sessions Present");
|
||||
QRect rct = painter.boundingRect(this->rect(), Qt::AlignCenter, msg);
|
||||
painter.setPen(Qt::black);
|
||||
painter.drawText(rct, Qt::AlignCenter, msg);
|
||||
}
|
||||
}
|
||||
|
@ -43,13 +43,19 @@ class SessionBar : public QWidget
|
||||
// SessionBar(const SessionBar &);
|
||||
|
||||
virtual ~SessionBar();
|
||||
void clear() { segments.clear(); }
|
||||
void clear() { segments.clear(); m_selectIDX = -1; }
|
||||
void add(Session *sess, QColor col) { if (sess) { segments.push_back(SBSeg(sess, col)); } }
|
||||
void setSelectMode(bool b) { m_selectMode = b; }
|
||||
void setSelectColor(QColor col) { m_selectColor = col; }
|
||||
int count() { return segments.size(); }
|
||||
int selected() { return m_selectIDX; }
|
||||
Session * session(int idx) { Q_ASSERT(idx < segments.size()); return segments[idx].session; }
|
||||
void setSelected(int idx) { m_selectIDX = idx; }
|
||||
|
||||
protected slots:
|
||||
void updateTimer();
|
||||
signals:
|
||||
void toggledSession(Session *sess);
|
||||
void sessionClicked(Session *sess);
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event);
|
||||
void mouseMoveEvent(QMouseEvent *);
|
||||
@ -59,6 +65,9 @@ class SessionBar : public QWidget
|
||||
|
||||
QList<SBSeg> segments;
|
||||
QTimer timer;
|
||||
int m_selectIDX;
|
||||
bool m_selectMode;
|
||||
QColor m_selectColor;
|
||||
};
|
||||
//Q_DECLARE_METATYPE(SessionBar)
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
QT += core gui network xml
|
||||
QT += core gui network xml serialport
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION,4) {
|
||||
QT += widgets webkitwidgets
|
||||
@ -12,7 +12,6 @@ greaterThan(QT_MAJOR_VERSION,4) {
|
||||
QT += webkit
|
||||
}
|
||||
|
||||
|
||||
#Windows XP with older intel cards needs the following variable defined
|
||||
#It slows other platforms down way too much
|
||||
#DEFINES += BROKEN_OPENGL_BUILD
|
||||
@ -143,7 +142,8 @@ SOURCES += \
|
||||
SleepLib/loader_plugins/somnopose_loader.cpp \
|
||||
SleepLib/loader_plugins/zeo_loader.cpp \
|
||||
translation.cpp \
|
||||
statistics.cpp
|
||||
statistics.cpp \
|
||||
oximeterimport.cpp
|
||||
|
||||
HEADERS += \
|
||||
common_gui.h \
|
||||
@ -194,7 +194,8 @@ HEADERS += \
|
||||
SleepLib/loader_plugins/somnopose_loader.h \
|
||||
SleepLib/loader_plugins/zeo_loader.h \
|
||||
translation.h \
|
||||
statistics.h
|
||||
statistics.h \
|
||||
oximeterimport.h
|
||||
|
||||
FORMS += \
|
||||
daily.ui \
|
||||
@ -206,7 +207,8 @@ FORMS += \
|
||||
profileselect.ui \
|
||||
newprofile.ui \
|
||||
exportcsv.ui \
|
||||
UpdaterWindow.ui
|
||||
UpdaterWindow.ui \
|
||||
oximeterimport.ui
|
||||
|
||||
RESOURCES += \
|
||||
Resources.qrc
|
||||
|
Loading…
Reference in New Issue
Block a user