OSCAR-code/oscar/exportcsv.cpp

433 lines
17 KiB
C++

/* ExportCSV module implementation
*
* Copyright (c) 2019-2024 The OSCAR Team
* Copyright (c) 2011-2018 Mark Watkins
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of the source code
* for more details. */
#define TEST_MACROS_ENABLEDoff
#include <test_macros.h>
#include <QFileDialog>
#include <QLocale>
#include <QMessageBox>
#include <QCalendarWidget>
#include <QTextCharFormat>
#include "SleepLib/profiles.h"
#include "SleepLib/day.h"
#include "exportcsv.h"
#include "ui_exportcsv.h"
#include "mainwindow.h"
extern MainWindow *mainwin;
ExportCSV::ExportCSV(QWidget *parent) :
QDialog(parent),
ui(new Ui::ExportCSV)
{
ui->setupUi(this);
ui->rb1_Summary->setChecked(true);
ui->quickRangeCombo->setCurrentIndex(0);
this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
// Set Date controls locale to 4 digit years
QLocale locale = QLocale::system();
QString shortformat = locale.dateFormat(QLocale::ShortFormat);
if (!shortformat.toLower().contains("yyyy")) {
shortformat.replace("yy", "yyyy");
}
ui->startDate->setDisplayFormat(shortformat);
ui->endDate->setDisplayFormat(shortformat);
// Stop both calendar drop downs highlighting weekends in red
QTextCharFormat format = ui->startDate->calendarWidget()->weekdayTextFormat(Qt::Saturday);
format.setForeground(QBrush(Qt::black, Qt::SolidPattern));
ui->startDate->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format);
ui->startDate->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format);
ui->endDate->calendarWidget()->setWeekdayTextFormat(Qt::Saturday, format);
ui->endDate->calendarWidget()->setWeekdayTextFormat(Qt::Sunday, format);
Qt::DayOfWeek dow = firstDayOfWeekFromLocale();
ui->startDate->calendarWidget()->setFirstDayOfWeek(dow);
ui->endDate->calendarWidget()->setFirstDayOfWeek(dow);
// Connect the signals to update which days have CPAP data when the month is changed
connect(ui->startDate->calendarWidget(), SIGNAL(currentPageChanged(int, int)),
SLOT(startDate_currentPageChanged(int, int)));
connect(ui->endDate->calendarWidget(), SIGNAL(currentPageChanged(int, int)),
SLOT(endDate_currentPageChanged(int, int)));
on_quickRangeCombo_activated(tr("Most Recent Day"));
ui->rb1_details->clearFocus();
ui->quickRangeCombo->setFocus();
ui->exportButton->setEnabled(false);
}
ExportCSV::~ExportCSV()
{
delete ui;
}
void ExportCSV::on_filenameBrowseButton_clicked()
{
QString timestamp = QString("OSCAR_");
timestamp += p_profile->Get("UserName") + "_";
if (ui->rb1_details->isChecked()) { timestamp += tr("Details_"); }
if (ui->rb1_Sessions->isChecked()) { timestamp += tr("Sessions_"); }
if (ui->rb1_Summary->isChecked()) { timestamp += tr("Summary_"); }
timestamp += ui->startDate->date().toString(Qt::ISODate);
if (ui->startDate->date() != ui->endDate->date()) { timestamp += "_" + ui->endDate->date().toString(Qt::ISODate); }
timestamp += ".csv";
QString folder = mainwin->profilePath(STR_PREF_LastExportCsvPath);
QString name = QFileDialog::getSaveFileName(this, tr("Select file to export to"),
folder + QDir::separator() + timestamp, tr("CSV Files (*.csv)"));
if (name.isEmpty()) {
ui->exportButton->setEnabled(false);
return;
}
if (!name.toLower().endsWith(".csv")) {
name += ".csv";
}
ui->filenameEdit->setText(name);
ui->exportButton->setEnabled(true);
folder = QFileInfo(name).absolutePath();
mainwin->saveProfilePath(STR_PREF_LastExportCsvPath,folder);
}
void ExportCSV::on_quickRangeCombo_activated(const QString &arg1)
{
QDate first = p_profile->FirstDay();
QDate last = p_profile->LastDay();
if (arg1 == tr("Custom")) {
ui->startDate->setEnabled(true);
ui->endDate->setEnabled(true);
ui->startLabel->setEnabled(true);
ui->endLabel->setEnabled(true);
} else {
ui->startDate->setEnabled(false);
ui->endDate->setEnabled(false);
ui->startLabel->setEnabled(false);
ui->endLabel->setEnabled(false);
if (arg1 == tr("Everything")) {
ui->startDate->setDate(first);
ui->endDate->setDate(last);
} else if (arg1 == tr("Most Recent Day")) {
ui->startDate->setDate(last);
ui->endDate->setDate(last);
} else if (arg1 == tr("Last Week")) {
ui->startDate->setDate(last.addDays(-7));
ui->endDate->setDate(last);
} else if (arg1 == tr("Last Fortnight")) {
ui->startDate->setDate(last.addDays(-14));
ui->endDate->setDate(last);
} else if (arg1 == tr("Last Month")) {
ui->startDate->setDate(last.addMonths(-1));
ui->endDate->setDate(last);
} else if (arg1 == tr("Last 6 Months")) {
ui->startDate->setDate(last.addMonths(-6));
ui->endDate->setDate(last);
} else if (arg1 == tr("Last Year")) {
ui->startDate->setDate(last.addYears(-1));
ui->endDate->setDate(last);
}
}
}
void ExportCSV::on_exportButton_clicked()
{
QFile file(ui->filenameEdit->text());
if (!file.open(QFile::WriteOnly)) {
qWarning() << "Could not open" << ui->filenameEdit->text() << "for writing, error code" << file.error() << file.errorString();
return;
}
QString header;
const QString sep = ",";
const QString newline = "\n";
// if (ui->rb1_details->isChecked()) {
// fields.append(DumpField(NoChannel,MT_CPAP,ST_DATE));
// } else {
// header=tr("DateTime")+sep+tr("Session")+sep+tr("Event")+sep+tr("Data/Duration");
// } else {
// if (ui->rb1_Summary->isChecked()) {
// header=tr("Date")+sep+tr("Session Count")+sep+tr("Start")+sep+tr("End")+sep+tr("Total Time")+sep+tr("AHI");
// } else if (ui->rb1_Sessions->isChecked()) {
// header=tr("Date")+sep+tr("Session")+sep+tr("Start")+sep+tr("End")+sep+tr("Total Time")+sep+tr("AHI");
// }
// }
// fields.append(DumpField(NoChannel,MT_CPAP,ST_SESSIONS));
QList<ChannelID> countlist, avglist, p90list, maxlist;
for (int i = 0; i < ahiChannels.size(); i++)
countlist.append(ahiChannels.at(i));
// countlist.append(CPAP_Hypopnea);
// countlist.append(CPAP_Obstructive);
// countlist.append(CPAP_Apnea);
// countlist.append(CPAP_ClearAirway);
// countlist.append(CPAP_AllApnea);
countlist.append(CPAP_VSnore);
countlist.append(CPAP_VSnore2);
countlist.append(CPAP_RERA);
countlist.append(CPAP_FlowLimit);
countlist.append(CPAP_SensAwake);
countlist.append(CPAP_NRI);
countlist.append(CPAP_ExP);
countlist.append(CPAP_LeakFlag);
countlist.append(CPAP_UserFlag1);
countlist.append(CPAP_UserFlag2);
countlist.append(CPAP_PressurePulse);
QVector<ChannelID> statChannels = { CPAP_Pressure, CPAP_PressureSet, CPAP_IPAP, CPAP_IPAPSet, CPAP_EPAP, CPAP_EPAPSet, CPAP_FLG };
for (auto & chan : statChannels) {
avglist.append(chan);
p90list.append(chan);
maxlist.append(chan);
}
float percentile=p_profile->general->prefCalcPercentile()/100.0; // Pholynyk, 18Aug2015
EventDataType percent = percentile; // was 0.90F
// Not sure this section should be translateable.. :-/
if (ui->rb1_details->isChecked()) {
header = tr("DateTime") + sep + tr("Session") + sep + tr("Event") + sep + tr("Data/Duration");
} else {
if (ui->rb1_Summary->isChecked()) {
header = tr("Date") + sep + tr("Session Count") + sep + tr("Start") + sep + tr("End") + sep +
tr("Total Time") + sep + tr("AHI");
} else if (ui->rb1_Sessions->isChecked()) {
header = tr("Date") + sep + tr("Session") + sep + tr("Start") + sep + tr("End") + sep +
tr("Total Time") + sep + tr("AHI");
}
for (int i = 0; i < countlist.size(); i++) {
header += sep + schema::channel[countlist[i]].label() + tr(" Count");
}
for (int i = 0; i < avglist.size(); i++) {
header += sep + Day::calcMiddleLabel(avglist[i]); // Pholynyk, 18Aug2015
}
for (int i = 0; i < p90list.size(); i++) {
header += sep + QString("%1% ").arg(percent*100.0, 0, 'f', 0) + schema::channel[p90list[i]].label();
}
for (int i = 0; i < maxlist.size(); i++) {
header += sep + Day::calcMaxLabel(maxlist[i]); // added -- Pholynyk, 18Aug2015
}
}
header += newline;
file.write(header.toLatin1());
QDate date = ui->startDate->date();
Daily *daily = mainwin->getDaily();
QDate daily_date = daily->getDate();
ui->progressBar->setValue(0);
ui->progressBar->setMaximum(p_profile->daylist.count());
do {
ui->progressBar->setValue(ui->progressBar->value() + 1);
QApplication::processEvents();
Day *day = p_profile->GetDay(date, MT_CPAP); // Only export days with CPAP data.
if (day) {
QString data;
if (ui->rb1_Summary->isChecked()) {
QDateTime start = QDateTime::fromTime_t(day->first() / 1000L);
QDateTime end = QDateTime::fromTime_t(day->last() / 1000L);
data = date.toString(Qt::ISODate);
data += sep + QString::number(day->size(), 10);
data += sep + start.toString(Qt::ISODate);
data += sep + end.toString(Qt::ISODate);
// Given this is a CPAP specific report, just report CPAP hours
int time = int(day->hours(MT_CPAP) * 3600L);
int h = time / 3600;
int m = int(time / 60) % 60;
int s = int(time) % 60;
data += sep + QString::asprintf("%02i:%02i:%02i", h, m, s);
float ahi = day->calcAHI();
data += sep + QString::number(ahi, 'f', 3);
for (int i = 0; i < countlist.size(); i++) {
data += sep + QString::number(day->count(countlist.at(i)));
}
for (int i = 0; i < avglist.size(); i++) {
float avg = day->calcMiddle(avglist.at(i));
data += sep + QString::number(avg); // Pholynyk, 11Aug2015
}
for (int i = 0; i < p90list.size(); i++) {
float p90 = day->percentile(p90list.at(i), percent);
data += sep + QString::number(p90); // Pholynyk, 11Aug2015
}
for (int i = 0; i < maxlist.size(); i++) {
float max = day->calcMax(maxlist.at(i));
data += sep + QString::number(max); // added -- Pholynyk, 18Aug2015
}
data += newline;
file.write(data.toLatin1());
} else if (ui->rb1_Sessions->isChecked()) {
for (int i = 0; i < day->size(); i++) {
Session *sess = (*day)[i];
if (sess->type() != MT_CPAP) {
continue; // Not every session in a day with CPAP data will be a CPAP session.
}
QDateTime start = QDateTime::fromTime_t(sess->first() / 1000L);
QDateTime end = QDateTime::fromTime_t(sess->last() / 1000L);
sess->OpenEvents();
data = date.toString(Qt::ISODate);
data += sep + QString::number(sess->session(), 10);
data += sep + start.toString(Qt::ISODate);
data += sep + end.toString(Qt::ISODate);
int time = sess->length() / 1000L;
int h = time / 3600;
int m = int(time / 60) % 60;
int s = int(time) % 60;
data += sep + QString::asprintf("%02i:%02i:%02i", h, m, s);
float ahi = sess->count(AllAhiChannels);
//sess->count(CPAP_AllApnea) + sess->count(CPAP_Obstructive) + sess->count(CPAP_Hypopnea)
// + sess->count(CPAP_Apnea) + sess->count(CPAP_ClearAirway);
ahi /= sess->hours();
data += sep + QString::number(ahi, 'f', 3);
for (int j = 0; j < countlist.size(); j++) {
data += sep + QString::number(sess->count(countlist.at(j)));
}
for (int j = 0; j < avglist.size(); j++) {
data += sep + QString::number(sess->calcMiddle(avglist.at(j))); // Pholynyk, 11Aug2015
}
for (int j = 0; j < p90list.size(); j++) {
data += sep + QString::number(sess->percentile(p90list.at(j), percent)); // Pholynyk, 11Aug2015
}
for (int i = 0; i < maxlist.size(); i++) {
data += sep + QString::number(sess->calcMax(maxlist.at(i))); // Pholynyk, 11Aug2015
}
data += newline;
file.write(data.toLatin1());
}
} else if (ui->rb1_details->isChecked()) {
QList<ChannelID> all = countlist;
all.append(avglist);
for (int i = 0; i < day->size(); i++) {
Session *sess = (*day)[i];
sess->OpenEvents();
QHash<ChannelID, QVector<EventList *> >::iterator fnd;
for (int j = 0; j < all.size(); j++) {
ChannelID key = all.at(j);
fnd = sess->eventlist.find(key);
if (fnd != sess->eventlist.end()) {
//header="DateTime"+sep+"Session"+sep+"Event"+sep+"Data/Duration";
for (int e = 0; e < fnd.value().size(); e++) {
EventList *ev = fnd.value()[e];
for (quint32 q = 0; q < ev->count(); q++) {
data = QDateTime::fromTime_t(ev->time(q) / 1000L).toString(Qt::ISODate);
data += sep + QString::number(sess->session());
data += sep + schema::channel[key].code();
data += sep + QString::number(ev->data(q), 'f', 2);
data += newline;
file.write(data.toLatin1());
}
}
}
}
if (daily_date != date) {
sess->TrashEvents();
}
}
}
}
date = date.addDays(1);
} while (date <= ui->endDate->date());
file.close();
ExportCSV::accept();
}
void ExportCSV::UpdateCalendarDay(QDateEdit *dateedit, QDate date)
{
QCalendarWidget *calendar = dateedit->calendarWidget();
QTextCharFormat bold;
QTextCharFormat cpapcol;
QTextCharFormat normal;
QTextCharFormat oxiday;
bold.setFontWeight(QFont::Bold);
cpapcol.setForeground(QBrush(Qt::blue, Qt::SolidPattern));
cpapcol.setFontWeight(QFont::Bold);
oxiday.setForeground(QBrush(Qt::red, Qt::SolidPattern));
oxiday.setFontWeight(QFont::Bold);
bool hascpap = p_profile->GetDay(date, MT_CPAP) != nullptr;
bool hasoxi = p_profile->GetDay(date, MT_OXIMETER) != nullptr;
//bool hasjournal=p_profile->GetDay(date,MT_JOURNAL)!=nullptr;
if (hascpap) {
if (hasoxi) {
calendar->setDateTextFormat(date, oxiday);
} else {
calendar->setDateTextFormat(date, cpapcol);
}
} else if (p_profile->GetDay(date)) {
calendar->setDateTextFormat(date, bold);
} else {
calendar->setDateTextFormat(date, normal);
}
calendar->setHorizontalHeaderFormat(QCalendarWidget::ShortDayNames);
}
void ExportCSV::startDate_currentPageChanged(int year, int month)
{
QDate d(year, month, 1);
int dom = d.daysInMonth();
for (int i = 1; i <= dom; i++) {
d = QDate(year, month, i);
UpdateCalendarDay(ui->startDate, d);
}
}
void ExportCSV::endDate_currentPageChanged(int year, int month)
{
QDate d(year, month, 1);
int dom = d.daysInMonth();
for (int i = 1; i <= dom; i++) {
d = QDate(year, month, i);
UpdateCalendarDay(ui->endDate, d);
}
}