mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-04 02:00:43 +00:00
491 lines
14 KiB
C++
491 lines
14 KiB
C++
/* gFlagsLine Implementation
|
|
*
|
|
* Copyright (c) 2011-2018 Mark Watkins
|
|
* Copyright (c) 2019-2024 The OSCAR Team
|
|
*
|
|
* 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>
|
|
#define BAR_TITLE_BAR_DEBUGoff
|
|
|
|
|
|
#include <cmath>
|
|
#include <QVector>
|
|
#include "SleepLib/profiles.h"
|
|
#include "gFlagsLine.h"
|
|
#include "gYAxis.h"
|
|
|
|
gLabelArea::gLabelArea(Layer * layer)
|
|
: gSpacer(20)
|
|
{
|
|
m_layertype = LT_Spacer;
|
|
m_mainlayer = layer;
|
|
}
|
|
bool gLabelArea::mouseMoveEvent(QMouseEvent *event, gGraph *graph)
|
|
{
|
|
if (m_mainlayer) {
|
|
return m_mainlayer->mouseMoveEvent(event, graph);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
int gLabelArea::minimumWidth()
|
|
{
|
|
return gYAxis::Margin;
|
|
}
|
|
|
|
|
|
|
|
gFlagsGroup::gFlagsGroup()
|
|
{
|
|
m_barh = 0;
|
|
m_empty = true;
|
|
}
|
|
gFlagsGroup::~gFlagsGroup()
|
|
{
|
|
}
|
|
|
|
|
|
qint64 gFlagsGroup::Minx()
|
|
{
|
|
if (m_day) {
|
|
return m_day->first();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
qint64 gFlagsGroup::Maxx()
|
|
{
|
|
if (m_day) {
|
|
return m_day->last();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
void gFlagsGroup::SetDay(Day *d)
|
|
{
|
|
LayerGroup::SetDay(d);
|
|
int cnt = 0;
|
|
|
|
if (!d) {
|
|
m_empty = true;
|
|
return;
|
|
}
|
|
|
|
m_sessions = d->getSessions(MT_CPAP);
|
|
m_start = d->first(MT_CPAP);
|
|
m_duration = d->last(MT_CPAP) - m_start;
|
|
if (m_duration<=0) m_duration = 1; // avoid divide by zero
|
|
|
|
quint32 z = schema::FLAG | schema::SPAN | schema::MINOR_FLAG;
|
|
if (p_profile->general->showUnknownFlags()) z |= schema::UNKNOWN;
|
|
availableChans = d->getSortedMachineChannels(z);
|
|
|
|
m_rebuild_cpap = (availableChans.size() == 0);
|
|
|
|
if (m_rebuild_cpap) {
|
|
QHash<ChannelID, schema::Channel *> chans;
|
|
|
|
for (const auto & sess : m_day->sessions) {
|
|
for (auto it=sess->eventlist.begin(), end=sess->eventlist.end(); it != end; ++it) {
|
|
ChannelID code = it.key();
|
|
if (chans.contains(code)) continue;
|
|
|
|
schema::Channel * chan = &schema::channel[code];
|
|
|
|
if (chan->type() == schema::FLAG) {
|
|
availableChans.push_back(code);
|
|
} else if (chan->type() == schema::MINOR_FLAG) {
|
|
availableChans.push_back(code);
|
|
} else if (chan->type() == schema::SPAN) {
|
|
availableChans.push_back(code);
|
|
} else if (chan->type() == schema::UNKNOWN) {
|
|
availableChans.push_back(code);
|
|
}
|
|
}
|
|
}
|
|
availableChans = chans.keys();
|
|
}
|
|
|
|
lvisible.clear();
|
|
for (const auto code : availableChans) {
|
|
// const schema::Channel & chan = schema::channel[code];
|
|
gFlagsLine * fl = new gFlagsLine(code);
|
|
fl->SetDay(d);
|
|
lvisible.push_back(fl);
|
|
}
|
|
|
|
|
|
cnt = lvisible.size();
|
|
m_empty = (cnt == 0);
|
|
|
|
if (m_empty) {
|
|
if (d) {
|
|
m_empty = !d->hasEvents();
|
|
}
|
|
}
|
|
|
|
m_barh = 0;
|
|
}
|
|
|
|
bool gFlagsGroup::isEmpty()
|
|
{
|
|
if (m_day) {
|
|
if (m_day->hasEnabledSessions() && m_day->hasEvents())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void gFlagsGroup::refreshConfiguration(gGraph* graph)
|
|
{
|
|
int numOn=0;
|
|
for (const auto & flagsline : lvisible) {
|
|
if (schema::channel[flagsline->code()].enabled()) numOn++;
|
|
}
|
|
if (numOn==0) numOn=1; // always have an area showing in graph.
|
|
float barHeight = QFontMetrics(*defaultfont).capHeight() + QFontMetrics(*defaultfont).descent() ;
|
|
int height (barHeight * numOn);
|
|
height += sessionBarHeight();
|
|
setMinimumHeight (height);
|
|
if (graph->height()<height) graph->setHeight (height);
|
|
}
|
|
|
|
int gFlagsGroup::sessionBarHeight() {
|
|
static const int m_sessionHeight = 7;
|
|
return (m_sessions.size()>1 ) ? m_sessionHeight : 0;
|
|
};
|
|
|
|
void gFlagsGroup::paint(QPainter &painter, gGraph &g, const QRegion ®ion)
|
|
{
|
|
if (!m_visible) { return; }
|
|
if (!m_day) { return; }
|
|
|
|
QRectF outline(region.boundingRect());
|
|
outline.translate(0.0f, 0.001f);
|
|
|
|
int left = region.boundingRect().left()+1;
|
|
int top = region.boundingRect().top()+1 ;
|
|
int width = region.boundingRect().width();
|
|
int height = region.boundingRect().height();
|
|
|
|
QVector<gFlagsLine *> visflags;
|
|
|
|
for (const auto & flagsline : lvisible) {
|
|
if (schema::channel[flagsline->code()].enabled())
|
|
visflags.push_back(flagsline);
|
|
}
|
|
|
|
int sheight = (m_sessions.size()>1?sessionBarHeight():0);
|
|
int vis = visflags.size();
|
|
m_barh = float(height-sheight) / float(vis);
|
|
float linetop = top+sheight-2;
|
|
|
|
qint64 minx,maxx,dur;
|
|
g.graphView()->GetXBounds(minx,maxx);
|
|
dur = maxx - minx;
|
|
|
|
#if BAR_TITLE_BAR_DEBUG
|
|
// debug for minimum size for event flags. adding required height for enabled events , number eventTypes , height of an event bar
|
|
QString text= QString("%1 -> %2 %3: %4 H:%5 Vis:%6 barH:%7").
|
|
arg(QDateTime::fromMSecsSinceEpoch(minx).time().toString()).
|
|
arg(QDateTime::fromMSecsSinceEpoch(maxx).time().toString()).
|
|
arg(QObject::tr("Selection Length")).
|
|
arg(QTime(0,0).addMSecs(dur).toString("H:mm:ss.zzz"))
|
|
.arg(height)
|
|
.arg(vis)
|
|
.arg(m_barh)
|
|
;
|
|
#else
|
|
QString text= QString("%1 -> %2 %3: %4").
|
|
arg(QDateTime::fromMSecsSinceEpoch(minx).time().toString()).
|
|
arg(QDateTime::fromMSecsSinceEpoch(maxx).time().toString()).
|
|
arg(QObject::tr("Selection Length")).
|
|
arg(QTime(0,0).addMSecs(dur).toString("H:mm:ss.zzz")) ;
|
|
#endif
|
|
g.renderText(text, left , top -5 );
|
|
|
|
QColor barcol;
|
|
|
|
for (int i=0, end=visflags.size(); i < end; i++) {
|
|
// Alternating box color
|
|
barcol = COLOR_ALT_BG2;
|
|
if (i & 1) {
|
|
if (g.printing() && AppSetting->monochromePrinting()) {
|
|
barcol = QColor(0xe4, 0xe4, 0xe4, 0xff);
|
|
} else {
|
|
barcol = COLOR_ALT_BG1;
|
|
}
|
|
}
|
|
|
|
painter.fillRect(left, floor(linetop), width-1, ceil(m_barh), QBrush(barcol));
|
|
|
|
// Paint the actual flags
|
|
QRect rect(left, linetop, width, m_barh);
|
|
visflags[i]->m_rect = rect;
|
|
visflags[i]->paint(painter, g, QRegion(rect));
|
|
linetop += m_barh;
|
|
}
|
|
|
|
// graph each session at top
|
|
if ( p_profile->appearance->eventFlagSessionBar() && m_sessions.size()>1 ) {
|
|
QRect sessBox(0,g.top,0,sessionBarHeight());
|
|
double adjustment = width/(double)m_duration;
|
|
for (const auto & sess : m_sessions) {
|
|
sessBox.setX(left + (sess->first()-m_start)*adjustment);
|
|
sessBox.setWidth( sess->length() * adjustment);
|
|
painter.fillRect(sessBox, QBrush(Qt::gray));
|
|
}
|
|
}
|
|
|
|
painter.setPen(COLOR_Outline);
|
|
painter.drawRect(outline);
|
|
|
|
if (m_rebuild_cpap) {
|
|
|
|
QString txt = QObject::tr("Database Outdated\nPlease Rebuild CPAP Data");
|
|
painter.setFont(*bigfont);
|
|
painter.setPen(QPen(QColor(0,0,0,32)));
|
|
painter.drawText(region.boundingRect(), Qt::AlignCenter | Qt::AlignVCenter, txt);
|
|
}
|
|
}
|
|
|
|
bool gFlagsGroup::mouseMoveEvent(QMouseEvent *event, gGraph *graph)
|
|
{
|
|
|
|
//if (p_profile->appearance->lineCursorMode()) {
|
|
graph->timedRedraw(0);
|
|
// }
|
|
|
|
if (!AppSetting->graphTooltips()) {
|
|
return false;
|
|
}
|
|
|
|
for (int i=0, end=lvisible.size(); i < end; i++) {
|
|
gFlagsLine *fl = lvisible.at(i);
|
|
|
|
if (fl->m_rect.contains(event->x(), event->y())) {
|
|
if (fl->mouseMoveEvent(event, graph)) { return true; }
|
|
} else {
|
|
// Inside main graph area?
|
|
if ((event->y() > fl->m_rect.y()) && (event->y()) < (fl->m_rect.y() + fl->m_rect.height())) {
|
|
if (event->x() < fl->m_rect.x()) {
|
|
// Display tooltip
|
|
QString ttip = schema::channel[fl->code()].fullname() + "\n" +
|
|
schema::channel[fl->code()].description();
|
|
graph->ToolTip(ttip, event->x()+15, event->y(), TT_AlignLeft);
|
|
graph->timedRedraw(0);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
gFlagsLine::gFlagsLine(ChannelID code)
|
|
: Layer(code)
|
|
{
|
|
}
|
|
gFlagsLine::~gFlagsLine()
|
|
{
|
|
}
|
|
void gFlagsLine::paint(QPainter &painter, gGraph &w, const QRegion ®ion)
|
|
{
|
|
int left = region.boundingRect().left();
|
|
int top = region.boundingRect().top();
|
|
int width = region.boundingRect().width();
|
|
int height = region.boundingRect().height();
|
|
|
|
if (!m_visible) { return; }
|
|
|
|
if (!m_day) { return; }
|
|
|
|
double minx;
|
|
double maxx;
|
|
|
|
if (w.blockZoom()) {
|
|
minx = w.rmin_x;
|
|
maxx = w.rmax_x;
|
|
} else {
|
|
minx = w.min_x;
|
|
maxx = w.max_x;
|
|
}
|
|
|
|
double xx = maxx - minx;
|
|
|
|
if (xx <= 0) { return; }
|
|
|
|
double xmult = width / xx;
|
|
|
|
schema::Channel & chan = schema::channel[m_code];
|
|
GetTextExtent(chan.label(), m_lx, m_ly);
|
|
|
|
// Draw text label
|
|
w.renderText(chan.label(), left - m_lx - 10, top + (height / 2) + (m_ly / 2));
|
|
|
|
float x1, x2;
|
|
|
|
float bartop = top + 2;
|
|
float bottom = top + height - 2;
|
|
qint64 X, X2, L;
|
|
|
|
qint64 start;
|
|
quint32 *tptr;
|
|
EventStoreType *dptr, * eptr;
|
|
int idx;
|
|
QHash<ChannelID, QVector<EventList *> >::iterator cei;
|
|
|
|
qint64 clockdrift = qint64(p_profile->cpap->clockDrift()) * 1000L;
|
|
qint64 drift = 0;
|
|
|
|
QVector<QLine> vlines;
|
|
|
|
QColor color=schema::channel[m_code].defaultColor();
|
|
if (w.printing() && AppSetting->monochromePrinting()) {
|
|
color = Qt::black;
|
|
}
|
|
QBrush brush(color);
|
|
|
|
int tooltipTimeout = AppSetting->tooltipTimeout();
|
|
|
|
bool hover = false;
|
|
for (const auto & sess : m_day->sessions) {
|
|
if (!sess->enabled()) {
|
|
continue;
|
|
}
|
|
|
|
drift = (sess->type() == MT_CPAP) ? clockdrift : 0;
|
|
|
|
cei = sess->eventlist.find(m_code);
|
|
|
|
if (cei == sess->eventlist.end()) {
|
|
continue;
|
|
}
|
|
|
|
|
|
for (const auto & el : cei.value()) {
|
|
|
|
start = el->first() + drift;
|
|
tptr = el->rawTime();
|
|
dptr = el->rawData();
|
|
int np = el->count();
|
|
eptr = dptr + np;
|
|
|
|
for (idx = 0; dptr < eptr; dptr++, tptr++, idx++) {
|
|
X = start + *tptr;
|
|
L = *dptr * 1000;
|
|
|
|
if (X >= minx) {
|
|
break;
|
|
}
|
|
|
|
X2 = X - L;
|
|
|
|
if (X2 >= minx) {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
np -= idx;
|
|
|
|
if (chan.type() == schema::SPAN) {
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Draw Event Flag Spans
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
for (; dptr < eptr; dptr++) {
|
|
X = start + * tptr++;
|
|
|
|
|
|
L = *dptr * 1000L;
|
|
X2 = X - L;
|
|
if (X2 > maxx) {
|
|
break;
|
|
}
|
|
|
|
x1 = double(X - minx) * xmult + left;
|
|
x2 = double(X2 - minx) * xmult + left;
|
|
int width = x1-x2;
|
|
width = qMax(2,width); // Insure Rectangle will be visable. Flag events are 2 pixels wide.
|
|
|
|
brush = QBrush(color);
|
|
painter.fillRect(x2, bartop, width, bottom-bartop, brush);
|
|
if (!w.selectingArea() && !hover && QRect(x2, bartop, width , bottom-bartop).contains(w.graphView()->currentMousePos())) {
|
|
hover = true;
|
|
painter.setPen(QPen(Qt::red,1));
|
|
|
|
painter.drawRect(x2, bartop, width, bottom-bartop);
|
|
int x,y;
|
|
double s = *dptr;
|
|
double m;
|
|
s=60*modf(s/60,&m);
|
|
QString lab = QString("%1").arg(schema::channel[m_code].fullname());
|
|
if (m>0) {
|
|
lab += QObject::tr(" (%2 min, %3 sec)").arg(m).arg(s);
|
|
} else {
|
|
lab += QObject::tr(" (%3 sec)").arg(s);
|
|
}
|
|
GetTextExtent(lab, x, y);
|
|
w.ToolTip(lab, x2 - 10, bartop + (3 * w.printScaleY()), TT_AlignRight, tooltipTimeout);
|
|
|
|
}
|
|
}
|
|
|
|
} else { //if (chan.type() == schema::FLAG) {
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Draw Event Flag Bars
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
for (int i = 0; i < np; i++, dptr++) {
|
|
X = start + *tptr++;
|
|
|
|
if (X > maxx) {
|
|
break;
|
|
}
|
|
|
|
x1 = (X - minx) * xmult + left;
|
|
|
|
if (!w.selectingArea() && !hover && QRect(x1-3, bartop-2, 6, bottom-bartop+4).contains(w.graphView()->currentMousePos())) {
|
|
hover = true;
|
|
painter.setPen(QPen(Qt::red,1));
|
|
|
|
painter.drawRect(x1-2, bartop-2, 4, bottom-bartop+4);
|
|
int x,y;
|
|
// QString lab = QString("%1 (%2)").arg(schema::channel[m_code].fullname()).arg(*dptr);
|
|
QString lab = QString("%1").arg(schema::channel[m_code].fullname());
|
|
if (*dptr != 0)
|
|
lab += QString(" (%2)").arg(*dptr);
|
|
GetTextExtent(lab, x, y);
|
|
|
|
w.ToolTip(lab, x1 - 10, bartop + (3 * w.printScaleY()), TT_AlignRight, tooltipTimeout);
|
|
}
|
|
|
|
vlines.append(QLine(x1, bartop, x1, bottom));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (w.printing() && AppSetting->monochromePrinting()) {
|
|
painter.setPen(QPen(Qt::black, 1.5));
|
|
} else {
|
|
painter.setPen(color);
|
|
}
|
|
painter.drawLines(vlines);
|
|
}
|
|
|
|
bool gFlagsLine::mouseMoveEvent(QMouseEvent *event, gGraph *graph)
|
|
{
|
|
Q_UNUSED(event)
|
|
Q_UNUSED(graph)
|
|
// qDebug() << code() << event->x() << event->y() << graph->rect();
|
|
|
|
return false;
|
|
}
|