OSCAR-code/sleepyhead/Graphs/gGraphView.cpp
reznet 59a0823d20 Update all calls to fromMSecsSinceEpoch to specify UTC
QT 5 changed the behavior of QDateTime::fromMSecsSinceEpoch to return the result in local time zone, whereas in QT 4, it returned UTC.  On systems that do not support time zones, the QT 5 version still returns UTC.  But for all other systems, the api change causes confusion in the SH UI because some date and time values are displayed in UTC instead of the local time zone.  This manifests itself when the user is in USA which has a negative UTC offset.  When selecting a date range to display in the overview screen, the displayed dates appear to be one day behind the selected date range.

For consistently, SH should always use UTC internally and only convert to the local time zone when displaying data to the user.  This will ensure that the time zone information is preserved correctly when the UTC offset of the user's machine changes due to DST changes or traveling.

There are a few calls to fromMSecsSinceEpoch which should be using local time, and those will be updated in future commits.
2015-08-11 15:01:24 -05:00

3385 lines
97 KiB
C++

/* gGraphView Implementation
*
* Copyright (c) 2011-2014 Mark Watkins <jedimark@users.sourceforge.net>
*
* 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 Linux
* distribution for more details. */
#include "Graphs/gGraphView.h"
#include <QDir>
#include <QFontMetrics>
#include <QLabel>
#include <QPixmapCache>
#include <QTimer>
#include <QFontMetrics>
#include <QWidgetAction>
#include <QGridLayout>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
# include <QWindow>
#endif
#ifdef DEBUG_EFFICIENCY
// Only works in 4.8
# include <QElapsedTimer>
#endif
#include <cmath>
#include "mainwindow.h"
#include "Graphs/glcommon.h"
#include "Graphs/gLineChart.h"
#include "Graphs/gSummaryChart.h"
#include "Graphs/gSessionTimesChart.h"
#include "Graphs/gYAxis.h"
#include "Graphs/gFlagsLine.h"
#include "SleepLib/profiles.h"
extern MainWindow *mainwin;
#include <QApplication>
MyLabel::MyLabel(QWidget * parent)
: QWidget(parent) {
m_font = QApplication::font();
time.start();
}
MyLabel::~MyLabel()
{
}
void MyLabel::setText(QString text) {
m_text = text;
update();
}
void MyLabel::setFont(QFont & font)
{
m_font=font;
}
void MyLabel::doRedraw()
{
update();
}
void MyLabel::setAlignment(Qt::Alignment alignment) {
m_alignment = alignment;
doRedraw();
}
void MyLabel::paintEvent(QPaintEvent * /*event*/)
{
QPainter painter(this);
painter.setFont(m_font);
painter.drawText(rect(), m_alignment, m_text);
}
gToolTip::gToolTip(gGraphView *graphview)
: m_graphview(graphview)
{
m_pos.setX(0);
m_pos.setY(0);
m_visible = false;
m_alignment = TT_AlignCenter;
m_spacer = 8; // pixels around text area
timer = new QTimer(graphview);
connect(timer, SIGNAL(timeout()), SLOT(timerDone()));
}
gToolTip::~gToolTip()
{
disconnect(timer, 0, 0, 0);
delete timer;
}
//void gToolTip::calcSize(QString text,int &w, int &h)
//{
/*GetTextExtent(text,w,h);
w+=m_spacer*2;
h+=m_spacer*2; */
//}
void gToolTip::display(QString text, int x, int y, ToolTipAlignment align, int timeout)
{
if (timeout <= 0) {
timeout = p_profile->general->tooltipTimeout();
}
m_alignment = align;
m_text = text;
m_visible = true;
// TODO: split multiline here
//calcSize(m_text,tw,th);
m_pos.setX(x);
m_pos.setY(y);
//tw+=m_spacer*2;
//th+=m_spacer*2;
//th*=2;
if (timer->isActive()) {
timer->stop();
}
timer->setSingleShot(true);
timer->start(timeout);
m_invalidate = true;
}
void gToolTip::cancel()
{
m_visible = false;
timer->stop();
}
void gToolTip::paint(QPainter &painter) //actually paints it.
{
if (!m_visible) { return; }
int x = m_pos.x();
int y = m_pos.y();
QRect rect(x, y, 0, 0);
painter.setFont(*defaultfont);
rect = painter.boundingRect(rect, Qt::AlignCenter, m_text);
int w = rect.width() + m_spacer * 2;
int xx = rect.x() - m_spacer;
if (xx < 0) { xx = 0; }
rect.setLeft(xx);
rect.setTop(rect.y() - 15);
rect.setWidth(w);
int z = rect.x() + rect.width();
if (z > m_graphview->width() - 10) {
rect.setLeft(m_graphview->width() - 2 - rect.width());
rect.setRight(m_graphview->width() - 2);
}
int h = rect.height();
if (rect.y() < 0) {
rect.setY(0);
rect.setHeight(h);
}
if (m_alignment == TT_AlignRight) {
rect.moveTopRight(m_pos);
if ((x-w) < 0) {
rect.moveLeft(0);
}
} else if (m_alignment == TT_AlignLeft) {
rect.moveTopLeft(m_pos);
}
QBrush brush(QColor(255, 255, 128, 230));
brush.setStyle(Qt::SolidPattern);
painter.setBrush(brush);
painter.setPen(QColor(0, 0, 0, 255));
painter.drawRoundedRect(rect, 5, 5);
painter.setBrush(Qt::black);
painter.setFont(*defaultfont);
painter.drawText(rect, Qt::AlignCenter, m_text);
}
void gToolTip::timerDone()
{
m_visible = false;
m_graphview->redraw();
m_graphview->resetMouse();
}
#ifdef ENABLE_THREADED_DRAWING
gThread::gThread(gGraphView *g)
{
graphview = g;
mutex.lock();
}
gThread::~gThread()
{
if (isRunning()) {
m_running = false;
mutex.unlock();
wait();
terminate();
}
}
void gThread::run()
{
m_running = true;
gGraph *g;
while (m_running) {
mutex.lock();
//mutex.unlock();
if (!m_running) { break; }
do {
g = graphview->popGraph();
if (g) {
g->paint(QRegion(g->m_lastbounds));
//int i=0;
} else {
//mutex.lock();
graphview->masterlock->release(1); // This thread gives up for now..
}
} while (g);
}
}
#endif // ENABLE_THREADED_DRAWING
void gGraphView::queGraph(gGraph *g, int left, int top, int width, int height)
{
g->m_rect = QRect(left, top, width, height);
#ifdef ENABLED_THREADED_DRAWING
dl_mutex.lock();
#endif
m_drawlist.push_back(g);
#ifdef ENABLED_THREADED_DRAWING
dl_mutex.unlock();
#endif
}
void gGraphView::trashGraphs(bool destroy)
{
if (destroy) {
for (int i=0; i< m_graphs.size(); ++i) {
delete m_graphs[i];
}
}
// Don't actually want to delete them here.. we are just borrowing the graphs
m_graphs.clear();
m_graphsbyname.clear();
}
// Take the next graph to render from the drawing list
gGraph *gGraphView::popGraph()
{
gGraph *g;
#ifdef ENABLED_THREADED_DRAWING
dl_mutex.lock();
#endif
if (!m_drawlist.isEmpty()) {
g = m_drawlist.at(0);
m_drawlist.pop_front();
} else { g = nullptr; }
#ifdef ENABLED_THREADED_DRAWING
dl_mutex.unlock();
#endif
return g;
}
gGraphView::gGraphView(QWidget *parent, gGraphView *shared)
#ifdef BROKEN_OPENGL_BUILD
: QWidget(parent),
#else
: QGLWidget(QGLFormat(QGL::DoubleBuffer | QGL::DirectRendering | QGL::HasOverlay | QGL::Rgba),parent,shared),
#endif
m_offsetY(0), m_offsetX(0), m_scaleY(0.0), m_scrollbar(nullptr)
{
m_shared = shared;
m_sizer_index = m_graph_index = 0;
m_metaselect = m_button_down = m_graph_dragging = m_sizer_dragging = false;
m_lastypos = m_lastxpos = 0;
m_horiz_travel = 0;
m_minx = m_maxx = 0;
m_day = nullptr;
m_selected_graph = nullptr;
m_scrollbar = nullptr;
m_point_released = m_point_clicked = QPoint(0,0);
m_showAuthorMessage = true;
horizScrollTime.start();
vertScrollTime.start();
this->setMouseTracking(true);
m_emptytext = STR_Empty_NoData;
InitGraphGlobals(); // FIXME: sstangl: handle error return.
#ifdef ENABLE_THREADED_DRAWING
m_idealthreads = QThread::idealThreadCount();
if (m_idealthreads <= 0) { m_idealthreads = 1; }
masterlock = new QSemaphore(m_idealthreads);
#endif
m_tooltip = new gToolTip(this);
/*for (int i=0;i<m_idealthreads;i++) {
gThread * gt=new gThread(this);
m_threads.push_back(gt);
//gt->start();
}*/
setFocusPolicy(Qt::StrongFocus);
m_showsplitter = true;
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), SLOT(refreshTimeout()));
print_scaleY = print_scaleX = 1.0;
redrawtimer = new QTimer(this);
connect(redrawtimer, SIGNAL(timeout()), SLOT(redraw()));
m_fadingOut = false;
m_fadingIn = false;
m_inAnimation = false;
m_limbo = false;
m_fadedir = false;
m_blockUpdates = false;
use_pixmap_cache = p_profile->appearance->usePixmapCaching();
pin_graph = nullptr;
// pixmapcache.setCacheLimit(10240*2);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
m_dpr = devicePixelRatio();
m_dpr = 1;
#endif
#ifndef BROKEN_OPENGL_BUILD
setAutoFillBackground(false);
setAutoBufferSwap(false);
#endif
context_menu = new QMenu(this);
pin_action = context_menu->addAction(QString(), this, SLOT(togglePin()));
pin_icon = QPixmap(":/icons/pushpin.png");
snap_action = context_menu->addAction(QString(), this, SLOT(onSnapshotGraphToggle()));
context_menu->addSeparator();
zoom100_action = context_menu->addAction(tr("100% zoom level"), this, SLOT(resetZoom()));
zoom100_action->setToolTip(tr("Restore X-axis zoom too 100% to view entire days data."));
QAction * action = context_menu->addAction(tr("Reset Graph Layout"), this, SLOT(resetLayout()));
action->setToolTip(tr("Resets all graphs to a uniform height and default order."));
context_menu->addSeparator();
limits_menu = context_menu->addMenu(tr("Y-Axis"));
plots_menu = context_menu->addMenu(tr("Plots"));
connect(plots_menu, SIGNAL(triggered(QAction*)), this, SLOT(onPlotsClicked(QAction*)));
// overlay_menu = context_menu->addMenu("Overlays");
cpap_menu = context_menu->addMenu(tr("CPAP Overlays"));
connect(cpap_menu, SIGNAL(triggered(QAction*)), this, SLOT(onOverlaysClicked(QAction*)));
oximeter_menu = context_menu->addMenu(tr("Oximeter Overlays"));
connect(oximeter_menu, SIGNAL(triggered(QAction*)), this, SLOT(onOverlaysClicked(QAction*)));
lines_menu = context_menu->addMenu(tr("Dotted Lines"));
connect(lines_menu, SIGNAL(triggered(QAction*)), this, SLOT(onLinesClicked(QAction*)));
#if !defined(Q_OS_MAC)
context_menu->setStyleSheet("QMenu {\
background-color: #f0f0f0; /* sets background of the menu */\
border: 1px solid black;\
}\
QMenu::item {\
/* sets background of menu item. set this to something non-transparent\
if you want menu color and menu item color to be different */\
background-color: #f0f0f0;\
}\
QMenu::item:selected { /* when user selects item using mouse or keyboard */\
background-color: #ABCDEF;\
}");
#else
context_menu->setStyleSheet("QMenu::item:selected { /* when user selects item using mouse or keyboard */\
background-color: #ABCDEF;\
}");
#endif
}
void gGraphView::togglePin()
{
if (pin_graph) {
pin_graph->setPinned(!pin_graph->isPinned());
timedRedraw(0);
}
}
void gGraphView::closeEvent(QCloseEvent * event)
{
timer->stop();
redrawtimer->stop();
disconnect(redrawtimer, 0, 0, 0);
disconnect(timer, 0, 0, 0);
timer->deleteLater();
redrawtimer->deleteLater();
pixmapcache.clear();
if (m_scrollbar) {
this->disconnect(m_scrollbar, SIGNAL(sliderMoved(int)), 0, 0);
}
#ifdef BROKEN_OPENGL_BUILD
QWidget::closeEvent(event);
#else
QGLWidget::closeEvent(event);
#endif
}
gGraphView::~gGraphView()
{
#ifndef BROKEN_OPENGL_BUILD
doneCurrent();
#endif
#ifdef ENABLE_THREADED_DRAWING
for (int i = 0; i < m_threads.size(); i++) {
delete m_threads[i];
}
delete masterlock;
#endif
// Note: This will cause a crash if two graphs accidentally have the same name
for (QList<gGraph *>::iterator g = m_graphs.begin(); g!= m_graphs.end(); ++g) {
gGraph * graph = *g;
delete graph;
}
delete m_tooltip;
m_graphs.clear();
}
void gGraphView::dumpInfo()
{
QDate date = mainwin->getDaily()->getDate();
QString text = "==================== CPAP Information Dump ====================";
mainwin->log(text);
Day * day = p_profile->GetGoodDay(date, MT_CPAP);
if (day) {
QDateTime dt=QDateTime::fromMSecsSinceEpoch(day->first(), Qt::UTC);
mainwin->log(QString("Available Channels for %1").arg(dt.toString("MMM dd yyyy")));
QHash<schema::ChanType, QList<schema::Channel *> > list;
for (int i=0; i< day->size(); ++i) {
Session * sess = day->sessions.at(i);
QHash<ChannelID, QVector<EventList *> >::iterator it;
for (it = sess->eventlist.begin(); it != sess->eventlist.end(); ++it) {
ChannelID code = it.key();
schema::Channel * chan = &schema::channel[code];
list[chan->type()].append(chan);
}
}
QHash<schema::ChanType, QList<schema::Channel *> >::iterator lit;
for (lit = list.begin(); lit != list.end(); ++lit) {
switch (lit.key()) {
case schema::DATA:
text = "DATA: ";
break;
case schema::SETTING:
text = "SETTING: ";
break;
case schema::FLAG:
text = "FLAG: ";
break;
case schema::MINOR_FLAG:
text = "MINOR_FLAG: ";
break;
case schema::SPAN:
text = "SPAN: ";
break;
case schema::WAVEFORM:
text = "WAVEFORM: ";
break;
case schema::UNKNOWN:
text = "UNKNOWN: ";
break;
default:
break;
}
QStringList str;
for (int i=0; i< lit.value().size(); ++i) {
str.append(lit.value().at(i)->code());
}
str.sort();
text.append(str.join(", "));
mainwin->log(text);
}
}
// for (int i=0;i<m_graphs.size();i++) {
// m_graphs[i]->dumpInfo();
// }
}
bool gGraphView::usePixmapCache()
{
//use_pixmap_cache is an overide setting
return p_profile->appearance->usePixmapCaching();
}
#define CACHE_DRAWTEXT
#ifndef CACHE_DRAWTEXT
// Render all qued text via QPainter method
void gGraphView::DrawTextQue(QPainter &painter)
{
int w, h;
// not sure if global antialiasing would be better..
//painter.setRenderHint(QPainter::TextAntialiasing, p_profile->appearance->antiAliasing());
int items = m_textque.size();
for (int i = 0; i < items; ++i) {
TextQue &q = m_textque[i];
painter.setPen(q.color);
painter.setRenderHint(QPainter::TextAntialiasing, q.antialias);
QFont font = *q.font;
painter.setFont(font);
if (q.angle == 0) { // normal text
painter.drawText(q.x, q.y, q.text);
} else { // rotated text
w = painter.fontMetrics().width(q.text);
h = painter.fontMetrics().xHeight() + 2;
painter.translate(q.x, q.y);
painter.rotate(-q.angle);
painter.drawText(floor(-w / 2.0), floor(-h / 2.0), q.text);
painter.rotate(+q.angle);
painter.translate(-q.x, -q.y);
}
strings_drawn_this_frame++;
q.text.clear();
}
m_textque.clear();
items = m_textqueRect.size();
for (int i=0; i< items; ++i) {
TextQueRect &q = m_textqueRect[i];
painter.setPen(q.color);
painter.setRenderHint(QPainter::TextAntialiasing, q.antialias);
QFont font = *q.font;
painter.setFont(font);
if (q.angle == 0) { // normal text
painter.drawText(q.rect, q.flags, q.text);
} else { // rotated text
int x = q.rect.x();
int y = q.rect.y();
w = painter.fontMetrics().width(q.text);
h = painter.fontMetrics().xHeight() + 2;
painter.translate(x, y);
painter.rotate(-q.angle);
painter.drawText(floor(-w / 2.0), floor(-h / 2.0), q.text);
painter.rotate(+q.angle);
painter.translate(-x, -y);
}
strings_drawn_this_frame++;
q.text.clear();
}
m_textqueRect.clear();
}
#else
// Render graphs with QPainter or pixmap caching, depending on preferences
void gGraphView::DrawTextQue(QPainter &painter)
{
{
// process the text drawing queue
int m_textque_items = m_textque.size();
int h,w;
for (int i = 0; i < m_textque_items; ++i) {
const TextQue &q = m_textque.at(i);
// can do antialiased text via texture cache fine on mac
if (usePixmapCache()) {
// Generate the pixmap cache "key"
QString hstr = QString("%1:%2:%3").
arg(q.text).
arg(q.color.name()).
arg(q.font->pointSize());
QPixmap pm;
const int buf = 8;
if (!QPixmapCache::find(hstr, &pm)) {
QFontMetrics fm(*q.font);
// QRect rect=fm.tightBoundingRect(q.text);
w = fm.width(q.text);
h = fm.height()+buf;
pm=QPixmap(w, h);
pm.fill(Qt::transparent);
QPainter imgpainter(&pm);
imgpainter.setPen(q.color);
imgpainter.setFont(*q.font);
imgpainter.setRenderHint(QPainter::TextAntialiasing, q.antialias);
imgpainter.drawText(0, h-buf, q.text);
imgpainter.end();
QPixmapCache::insert(hstr, pm);
strings_drawn_this_frame++;
} else {
//cached
strings_cached_this_frame++;
}
h = pm.height();
w = pm.width();
if (q.angle != 0) {
float xxx = q.x - h - (h / 2);
float yyy = q.y + w / 2; // + buf / 2;
xxx+=4;
yyy+=4;
painter.translate(xxx, yyy);
painter.rotate(-q.angle);
painter.drawPixmap(QRect(0, h / 2, w, h), pm);
painter.rotate(+q.angle);
painter.translate(-xxx, -yyy);
} else {
QRect r1(q.x - buf / 2 + 4, q.y - h + buf, w, h);
painter.drawPixmap(r1, pm);
}
} else {
// Just draw the fonts..
painter.setPen(QColor(q.color));
painter.setFont(*q.font);
if (q.angle == 0) {
painter.drawText(q.x, q.y, q.text);
} else {
painter.setFont(*q.font);
w = painter.fontMetrics().width(q.text);
h = painter.fontMetrics().xHeight() + 2;
painter.translate(q.x, q.y);
painter.rotate(-q.angle);
painter.drawText(floor(-w / 2.0)-6, floor(-h / 2.0), q.text);
painter.rotate(+q.angle);
painter.translate(-q.x, -q.y);
}
strings_drawn_this_frame++;
}
//q.text.clear();
//q.text.squeeze();
}
m_textque.clear();
}
////////////////////////////////////////////////////////////////////////
// Text Rectangle Queues..
////////////////////////////////////////////////////////////////////////
int items = m_textqueRect.size();
float ww, hh;
for (int i = 0; i < items; ++i) {
const TextQueRect &q = m_textqueRect.at(i);
// can do antialiased text via texture cache fine on mac
if (usePixmapCache()) {
// Generate the pixmap cache "key"
QString hstr = QString("%1:%2:%3").
arg(q.text).
arg(q.color.name()).
arg(q.font->pointSize());
QPixmap pm;
if (!QPixmapCache::find(hstr, &pm)) {
ww = q.rect.width();
hh = q.rect.height();
pm=QPixmap(ww, hh);
//int aaw1 = pm.width();
pm.fill(Qt::transparent);
QPainter imgpainter(&pm);
//int aaw2 = pm.width();
imgpainter.setPen(q.color);
imgpainter.setFont(*q.font);
imgpainter.setRenderHint(QPainter::Antialiasing, true);
imgpainter.setRenderHint(QPainter::TextAntialiasing, true);
QRectF rect(0,0, ww, hh);
imgpainter.drawText(rect, q.flags, q.text);
//int aaw3 = pm.width();
imgpainter.end();
QPixmapCache::insert(hstr, pm);
//int aaw4 = pm.width();
strings_drawn_this_frame++;
} else {
//cached
strings_cached_this_frame++;
}
hh = pm.height();
ww = pm.width();
if (q.angle != 0) {
float xxx = q.rect.x() - hh - (hh / 2);
float yyy = q.rect.y() + ww / 2; // + buf / 2;
xxx+=4;
yyy+=4;
painter.translate(xxx, yyy);
painter.rotate(-q.angle);
painter.drawPixmap(QRect(0, hh / 2, ww, hh), pm);
painter.rotate(+q.angle);
painter.translate(-xxx, -yyy);
} else {
//painter.drawPixmap(QPoint(q.rect.x(), q.rect.y()), pm);
painter.drawPixmap(q.rect,pm, QRect(0,0,ww,hh));
}
} else {
// Just draw the fonts..
painter.setPen(QColor(q.color));
painter.setFont(*q.font);
if (q.angle == 0) {
painter.drawText(q.rect, q.flags, q.text);
} else {
painter.setFont(*q.font);
ww = painter.fontMetrics().width(q.text);
hh = painter.fontMetrics().xHeight() + 2;
painter.translate(q.rect.x(), q.rect.y());
painter.rotate(-q.angle);
painter.drawText(floor(-ww / 2.0), floor(-hh / 2.0), q.text);
painter.rotate(+q.angle);
painter.translate(-q.rect.x(), -q.rect.y());
}
strings_drawn_this_frame++;
}
//q.text.clear();
//q.text.squeeze();
}
m_textqueRect.clear();
}
#endif
void gGraphView::AddTextQue(const QString &text, QRectF rect, quint32 flags, float angle, QColor color, QFont *font, bool antialias)
{
#ifdef ENABLED_THREADED_DRAWING
text_mutex.lock();
#endif
m_textqueRect.append(TextQueRect(rect,flags,text,angle,color,font,antialias));
#ifdef ENABLED_THREADED_DRAWING
text_mutex.unlock();
#endif
}
void gGraphView::AddTextQue(const QString &text, short x, short y, float angle, QColor color, QFont *font, bool antialias)
{
#ifdef ENABLED_THREADED_DRAWING
text_mutex.lock();
#endif
m_textque.append(TextQue(x,y,angle,text,color,font,antialias));
#ifdef ENABLED_THREADED_DRAWING
text_mutex.unlock();
#endif
}
void gGraphView::addGraph(gGraph *g, short group)
{
if (!g) {
qDebug() << "Attempted to add an empty graph!";
return;
}
if (!m_graphs.contains(g)) {
g->setGroup(group);
m_graphs.push_back(g);
if (!m_graphsbyname.contains(g->name())) {
m_graphsbyname[g->name()] = g;
} else {
qDebug() << "Can't have two graphs with the same code string in the same GraphView!!";
}
// updateScrollBar();
}
}
// Calculate total height of all graphs including spacers
float gGraphView::totalHeight()
{
float th = 0;
for (int i = 0; i < m_graphs.size(); i++) {
gGraph * g = m_graphs[i];
if (g->isEmpty() || (!g->visible())) { continue; }
th += g->height() + graphSpacer;
}
return ceil(th);
}
float gGraphView::findTop(gGraph *graph)
{
float th = -m_offsetY;
for (int i = 0; i < m_graphs.size(); i++) {
if (m_graphs[i] == graph) { break; }
if (m_graphs[i]->isEmpty() || (!m_graphs[i]->visible())) { continue; }
th += m_graphs[i]->height() * m_scaleY + graphSpacer;
}
return ceil(th);
}
float gGraphView::scaleHeight()
{
float th = 0;
for (int i = 0; i < m_graphs.size(); i++) {
if (m_graphs[i]->isEmpty() || (!m_graphs[i]->visible())) { continue; }
th += m_graphs[i]->height() * m_scaleY + graphSpacer;
}
return ceil(th);
}
void gGraphView::updateScale()
{
if (!isVisible()) {
m_scaleY = 0.0;
return;
}
float th = totalHeight(); // height of all graphs
float h = height(); // height of main widget
if (th < h) {
th -= graphSpacer;
// th -= visibleGraphs() * graphSpacer; // compensate for spacer height
m_scaleY = h / th; // less graphs than fits on screen, so scale to fit
} else {
m_scaleY = 1.0;
}
updateScrollBar();
}
void gGraphView::resizeEvent(QResizeEvent *e)
{
// QWidget::resizeEvent(e); // This ques a redraw event..
updateScale();
if (m_scaleY > 0.0001) {
for (int i = 0; i < m_graphs.size(); i++) {
m_graphs[i]->resize(e->size().width(), m_graphs[i]->height() * m_scaleY);
}
}
e->accept();
}
void gGraphView::scrollbarValueChanged(int val)
{
//qDebug() << "Scrollbar Changed" << val;
if (m_offsetY != val) {
m_offsetY = val;
timedRedraw(); // do this on a timer?
}
}
void gGraphView::GetRXBounds(qint64 &st, qint64 &et)
{
//qint64 m1=0,m2=0;
gGraph *g = nullptr;
for (int i = 0; i < m_graphs.size(); i++) {
g = m_graphs[i];
if (g->group() == 0) {
break;
}
}
st = g->rmin_x;
et = g->rmax_x;
}
void gGraphView::ResetBounds(bool refresh) //short group)
{
if (m_graphs.size() == 0) return;
Q_UNUSED(refresh)
qint64 m1 = 0, m2 = 0;
gGraph *g = nullptr;
for (int i = 0; i < m_graphs.size(); i++) {
m_graphs[i]->ResetBounds();
if (!m_graphs[i]->min_x) { continue; }
g = m_graphs[i];
if (!m1 || m_graphs[i]->min_x < m1) { m1 = m_graphs[i]->min_x; }
if (!m2 || m_graphs[i]->max_x > m2) { m2 = m_graphs[i]->max_x; }
}
// if (p_profile->general->linkGroups()) {
// for (int i = 0; i < m_graphs.size(); i++) {
// m_graphs[i]->SetMinX(m1);
// m_graphs[i]->SetMaxX(m2);
// }
// }
if (!g) {
g = m_graphs[0];
}
m_minx = g->min_x;
m_maxx = g->max_x;
updateScale();
}
void gGraphView::GetXBounds(qint64 &st, qint64 &et)
{
st = m_minx;
et = m_maxx;
}
void gGraphView::SetXBounds(qint64 minx, qint64 maxx, short group, bool refresh)
{
for (int i = 0; i < m_graphs.size(); i++) {
if ((m_graphs[i]->group() == group)) {
m_graphs[i]->SetXBounds(minx, maxx);
}
}
m_minx = minx;
m_maxx = maxx;
if (refresh) { timedRedraw(0); }
}
void gGraphView::updateScrollBar()
{
if (!m_scrollbar || (m_graphs.size() == 0)) {
return;
}
float th = scaleHeight(); // height of all graphs
float h = height(); // height of main widget
float vis = 0;
for (int i = 0; i < m_graphs.size(); i++) {
vis += (m_graphs[i]->isEmpty() || !m_graphs[i]->visible()) ? 0 : 1;
}
if (th < h) { // less graphs than fits on screen
m_scrollbar->setMaximum(0); // turn scrollbar off.
} else { // more graphs than fit on screen
//m_scaleY=1.0;
float avgheight = th / vis;
m_scrollbar->setPageStep(avgheight);
m_scrollbar->setSingleStep(avgheight / 8.0);
m_scrollbar->setMaximum(th - height());
if (m_offsetY > th - height()) {
m_offsetY = th - height();
}
}
}
void gGraphView::setScrollBar(MyScrollBar *sb)
{
m_scrollbar = sb;
m_scrollbar->setMinimum(0);
updateScrollBar();
this->connect(m_scrollbar, SIGNAL(valueChanged(int)), SLOT(scrollbarValueChanged(int)));
}
bool gGraphView::renderGraphs(QPainter &painter)
{
float px = m_offsetX;
float py = -m_offsetY;
int numgraphs = 0;
float h, w;
//ax=px;//-m_offsetX;
//bool threaded;
// Tempory hack using this pref..
//#ifdef ENABLED_THREADED_DRAWING
/*if (profile->session->multithreading()) { // && (m_idealthreads>1)) {
threaded=true;
for (int i=0;i<m_idealthreads;i++) {
if (!m_threads[i]->isRunning())
m_threads[i]->start();
}
} else threaded=false; */
//#endif
//threaded=false;
if (height() < 40) return false;
if (m_scaleY < 0.0000001) {
updateScale();
}
lines_drawn_this_frame = 0;
quads_drawn_this_frame = 0;
// Calculate the height of pinned graphs
float pinned_height = 0; // pixel height total
int pinned_graphs = 0; // count
gGraph * g = nullptr;
for (int i = 0; i < m_graphs.size(); i++) {
g = m_graphs[i];
int minh = g->minHeight();
if (g->height() < minh) {
g->setHeight(minh);
}
if (g->isEmpty()) { continue; }
if (!g->visible()) { continue; }
if (!g->isPinned()) { continue; }
h = g->height() * m_scaleY;
pinned_height += h + graphSpacer;
pinned_graphs++;
}
py += pinned_height; // start drawing at the end of pinned space
// Draw non pinned graphs
for (int i = 0; i < m_graphs.size(); i++) {
g = m_graphs[i];
if (g->isEmpty()) { continue; }
if (!g->visible()) { continue; }
if (g->isPinned()) { continue; }
numgraphs++;
h = g->height() * m_scaleY;
// set clipping?
if (py > height()) {
break; // we are done.. can't draw anymore
}
if ((py + h + graphSpacer) >= 0) {
w = width();
int tw = 0; // (g->showTitle() ? titleWidth : 0);
queGraph(g, px + tw, py, width() - tw, h);
if ((m_graphs.size() > 1) && m_showsplitter) {
// draw the splitter handle
painter.setPen(QColor(220, 220, 220, 255));
painter.drawLine(0, py + h, w, py + h);
painter.setPen(QColor(158,158,158,255));
painter.drawLine(0, py + h + 1, w, py + h + 1);
painter.setPen(QColor(240, 240, 240, 255));
painter.drawLine(0, py + h + 2, w, py + h + 2);
}
}
py = ceil(py + h + graphSpacer);
}
// Physically draw the unpinned graphs
int s = m_drawlist.size();
for (int i = 0; i < s; i++) {
g = m_drawlist.at(0);
m_drawlist.pop_front();
g->paint(painter, QRegion(g->m_rect));
}
if (m_graphs.size() > 1) {
DrawTextQue(painter);
// Draw a gradient behind pinned graphs
QLinearGradient linearGrad(QPointF(100, 100), QPointF(width() / 2, 100));
linearGrad.setColorAt(0, QColor(216, 216, 255));
linearGrad.setColorAt(1, Qt::white);
painter.fillRect(0, 0, width(), pinned_height, QBrush(linearGrad));
}
py = 0; // start drawing at top...
// Draw Pinned graphs
for (int i = 0; i < m_graphs.size(); i++) {
g = m_graphs[i];
if (g->isEmpty()) { continue; }
if (!g->visible()) { continue; }
if (!g->isPinned()) { continue; }
h = g->height() * m_scaleY;
numgraphs++;
if (py > height()) {
break; // we are done.. can't draw anymore
}
if ((py + h + graphSpacer) >= 0) {
w = width();
int tw = 0; //(g->showTitle() ? titleWidth : 0);
queGraph(g, px + tw, py, width() - tw, h);
if ((m_graphs.size() > 1) && m_showsplitter) {
// draw the splitter handle
painter.setPen(QColor(220, 220, 220, 255));
painter.drawLine(0, py + h, w, py + h);
painter.setPen(QColor(128, 128, 128, 255));
painter.drawLine(0, py + h + 1, w, py + h + 1);
painter.setPen(QColor(190, 190, 190, 255));
painter.drawLine(0, py + h + 2, w, py + h + 2);
}
}
py = ceil(py + h + graphSpacer);
}
//int thr=m_idealthreads;
#ifdef ENABLED_THREADED_DRAWING
if (threaded) {
for (int i = 0; i < m_idealthreads; i++) {
masterlock->acquire(1);
m_threads[i]->mutex.unlock();
}
// wait till all the threads are done
// ask for all the CPU's back..
masterlock->acquire(m_idealthreads);
masterlock->release(m_idealthreads);
} else {
#endif
s = m_drawlist.size();
for (int i = 0; i < s; i++) {
g = m_drawlist.at(0);
m_drawlist.pop_front();
g->paint(painter, QRegion(g->m_rect));
}
#ifdef ENABLED_THREADED_DRAWING
}
#endif
//int elapsed=time.elapsed();
//QColor col=Qt::black;
// lines->setSize(linesize);
DrawTextQue(painter);
//glDisable(GL_TEXTURE_2D);
//glDisable(GL_DEPTH_TEST);
return numgraphs > 0;
}
#include "version.h"
#ifdef BROKEN_OPENGL_BUILD
void gGraphView::paintEvent(QPaintEvent *)
#else
void gGraphView::paintGL()
#endif
{
if (!isVisible()) {
// wtf is this even getting CALLED??
return;
}
#ifdef DEBUG_EFFICIENCY
QElapsedTimer time;
time.start();
#endif
if (redrawtimer->isActive()) {
redrawtimer->stop();
}
bool render_cube = false; //p_profile->appearance->animations(); // do something to
if (width() <= 0) { return; }
if (height() <= 0) { return; }
// Create QPainter object, note this is only valid from paintGL events!
QPainter painter(this);
painter.setRenderHint(QPainter::TextAntialiasing, true);
QRect bgrect(0, 0, width(), height());
painter.fillRect(bgrect,QBrush(QColor(255,255,255)));
bool graphs_drawn = true;
lines_drawn_this_frame = 0;
quads_drawn_this_frame = 0;
strings_drawn_this_frame = 0;
strings_cached_this_frame = 0;
graphs_drawn = renderGraphs(painter);
if (!graphs_drawn) { // No graphs drawn? show something useful :)
QString txt;
if (m_showAuthorMessage) {
if (emptyText() == STR_Empty_Brick) {
txt = "\nI'm very sorry your machine doesn't record useful data to graph in Daily View :(";
} else {
// not proud of telling them their machine is a Brick.. ;)
txt = QObject::tr("SleepyHead is proudly brought to you by JediMark.");
}
}
// int x2, y2;
// GetTextExtent(m_emptytext, x2, y2, bigfont);
// int tp2, tp1;
if (!m_emptyimage.isNull()) {
painter.drawPixmap(width() /2 - m_emptyimage.width() /2, height() /2 - m_emptyimage.height() /2 , m_emptyimage);
// tp2 = height() /2 + m_emptyimage.height()/2 + y2;
} /*else {
tp2 = height() / 2 + y2;
}*/
QColor col = Qt::black;
painter.setPen(col);
// painter.setFont(*bigfont);
// painter.drawText((width() / 2) - x2 / 2, tp2, m_emptytext);
QRectF rec(0,0,width(),0);
painter.setFont(*defaultfont);
rec = painter.boundingRect(rec, Qt::AlignHCenter | Qt::AlignBottom, txt);
rec.moveBottom(height()-5);
painter.drawText(rec, Qt::AlignHCenter | Qt::AlignBottom, txt);
}
if (p_profile->appearance->lineCursorMode()) {
emit updateCurrentTime(graphs_drawn ? m_currenttime : 0.0F);
} else {
emit updateRange(graphs_drawn ? m_minx : 0.0F, m_maxx);
}
DrawTextQue(painter);
m_tooltip->paint(painter);
#ifdef DEBUG_EFFICIENCY
const int rs = 10;
static double ring[rs] = {0};
static int rp = 0;
// Show FPS and draw time
if (m_showsplitter && p_profile->general->showPerformance()) {
QString ss;
qint64 ela = time.nsecsElapsed();
double ms = double(ela) / 1000000.0;
ring[rp++] = 1000.0 / ms;
rp %= rs;
double v = 0;
for (int i = 0; i < rs; i++) {
v += ring[i];
}
double fps = v / double(rs);
ss = "Debug Mode " + QString::number(fps, 'f', 1) + "fps "
+ QString::number(lines_drawn_this_frame, 'f', 0) + " lines "
// + QString::number(quads_drawn_this_frame, 'f', 0) + " quads "
+ QString::number(strings_drawn_this_frame, 'f', 0) + " strings "
+ QString::number(strings_cached_this_frame, 'f', 0) + " cached ";
int w, h;
// this uses tightBoundingRect, which is different on Mac than it is on Windows & Linux.
GetTextExtent(ss, w, h);
QColor col = Qt::white;
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
AddTextQue(ss, width(), w / 2, 90, QColor(Qt::black), defaultfont);
DrawTextQue(painter);
}
// painter.setPen(Qt::lightGray);
// painter.drawLine(0, 0, 0, height());
// painter.drawLine(0, 0, width(), 0);
// painter.setPen(Qt::darkGray);
//painter.drawLine(width(), 0, width(), height());
#endif
painter.end();
#ifndef BROKEN_OPENGL_BUILD
swapBuffers();
#endif
if (this->isVisible() && !graphs_drawn && render_cube) { // keep the cube spinning
redrawtimer->setInterval(1000.0 / 50); // 50 FPS
redrawtimer->setSingleShot(true);
redrawtimer->start();
}
}
QString gGraphView::getRangeString()
{
QString fmt;
qint64 diff = m_maxx - m_minx;
if (diff > 86400000) {
int days = ceil(double(m_maxx-m_minx) / 86400000.0);
qint64 minx = floor(double(m_minx)/86400000.0);
minx *= 86400000L;
qint64 maxx = minx + 86400000L * qint64(days)-1;
QDateTime st = QDateTime::fromMSecsSinceEpoch(minx, Qt::UTC);
QDateTime et = QDateTime::fromMSecsSinceEpoch(maxx, Qt::UTC);
QString txt = st.toString("d MMM") + " - " + et.addDays(-1).toString("d MMM yyyy");
return txt;
} else if (diff > 60000) {
fmt = "HH:mm:ss";
} else {
fmt = "HH:mm:ss:zzz";
}
QDateTime st = QDateTime::fromMSecsSinceEpoch(m_minx, Qt::UTC);
QDateTime et = QDateTime::fromMSecsSinceEpoch(m_maxx, Qt::UTC);
QString txt = st.toString(QObject::tr("d MMM [ %1 - %2 ]").arg(fmt).arg(et.toString(fmt))) ;
return txt;
}
void gGraphView::leaveEvent(QEvent * event)
{
Q_UNUSED(event);
if (m_metaselect) {
m_metaselect = false;
timedRedraw(0);
}
releaseKeyboard();
}
// For manual scrolling
void gGraphView::setOffsetY(int offsetY)
{
if (m_offsetY != offsetY) {
m_offsetY = offsetY;
redraw(); //issue full redraw..
}
}
// For manual X scrolling (not really needed)
void gGraphView::setOffsetX(int offsetX)
{
if (m_offsetX != offsetX) {
m_offsetX = offsetX;
redraw(); //issue redraw
}
}
void gGraphView::mouseMoveEvent(QMouseEvent *event)
{
grabKeyboard();
int x = event->x();
int y = event->y();
m_mouse = QPoint(x, y);
if (m_sizer_dragging) { // Resize handle being dragged
float my = y - m_sizer_point.y();
//qDebug() << "Sizer moved vertically" << m_sizer_index << my*m_scaleY;
float h = m_graphs[m_sizer_index]->height();
h += my / m_scaleY;
if (h > m_graphs[m_sizer_index]->minHeight()) {
m_graphs[m_sizer_index]->setHeight(h);
m_sizer_point.setX(x);
m_sizer_point.setY(y);
updateScrollBar();
timedRedraw();
}
return;
}
if (m_graph_dragging) { // Title bar being dragged to reorder
gGraph *p;
int yy = m_sizer_point.y();
bool empty;
if (y < yy) {
for (int i = m_graph_index - 1; i >= 0; i--) {
if (m_graphs[i]->isPinned() != m_graphs[m_graph_index]->isPinned()) {
// fix cursor
continue;
}
empty = m_graphs[i]->isEmpty() || (!m_graphs[i]->visible());
// swapping upwards.
int yy2 = yy - graphSpacer - m_graphs[i]->height() * m_scaleY;
yy2 += m_graphs[m_graph_index]->height() * m_scaleY;
if (y < yy2) {
//qDebug() << "Graph Reorder" << m_graph_index;
p = m_graphs[m_graph_index];
m_graphs[m_graph_index] = m_graphs[i];
m_graphs[i] = p;
if (!empty) {
m_sizer_point.setY(yy - graphSpacer - m_graphs[m_graph_index]->height()*m_scaleY);
redraw();
}
m_graph_index--;
}
if (!empty) { break; }
}
} else if (y > yy + graphSpacer + m_graphs[m_graph_index]->height()*m_scaleY) {
// swapping downwards
//qDebug() << "Graph Reorder" << m_graph_index;
for (int i = m_graph_index + 1; i < m_graphs.size(); i++) {
if (m_graphs[i]->isPinned() != m_graphs[m_graph_index]->isPinned()) {
//m_graph_dragging=false;
// fix cursor
continue;
}
empty = m_graphs[i]->isEmpty() || (!m_graphs[i]->visible());
p = m_graphs[m_graph_index];
m_graphs[m_graph_index] = m_graphs[i];
m_graphs[i] = p;
if (!empty) {
m_sizer_point.setY(yy + graphSpacer + m_graphs[m_graph_index]->height()*m_scaleY);
timedRedraw();
}
m_graph_index++;
if (!empty) { break; }
}
}
return;
}
float py = 0, pinned_height = 0, h;
bool done = false;
// Do pinned graphs first
for (int i = 0; i < m_graphs.size(); i++) {
if (m_graphs[i]->isEmpty() || !m_graphs[i]->visible() || !m_graphs[i]->isPinned()) {
continue;
}
h = m_graphs[i]->height() * m_scaleY;
pinned_height += h + graphSpacer;
if (py > height()) {
break; // we are done.. can't draw anymore
}
if (!((y >= py + m_graphs[i]->top) && (y < py + h - m_graphs[i]->bottom))) {
if (m_graphs[i]->isSelected()) {
m_graphs[i]->deselect();
timedRedraw(150);
}
}
// Update Mouse Cursor shape
if ((y >= py + h - 1) && (y < (py + h + graphSpacer))) {
this->setCursor(Qt::SplitVCursor);
done = true;
} else if ((y >= py + 1) && (y < py + h)) {
if (x >= titleWidth + 10) {
this->setCursor(Qt::ArrowCursor);
} else {
m_tooltip->display("Double click title to pin / unpin\nClick and drag to reorder graphs", x + 10, y, TT_AlignLeft);
timedRedraw(0);
this->setCursor(Qt::OpenHandCursor);
}
m_horiz_travel += qAbs(x - m_lastxpos) + qAbs(y - m_lastypos);
m_lastxpos = x;
m_lastypos = y;
// QPoint p(x,y);
// QMouseEvent e(event->type(),p,event->button(),event->buttons(),event->modifiers());
m_graphs[i]->mouseMoveEvent(event);
done = true;
}
py += h + graphSpacer;
}
py = -m_offsetY;
py += pinned_height;
// Propagate mouseMove events to relevant graphs
if (!done)
for (int i = 0; i < m_graphs.size(); i++) {
if (m_graphs[i]->isEmpty() || !m_graphs[i]->visible() || m_graphs[i]->isPinned()) {
continue;
}
h = m_graphs[i]->height() * m_scaleY;
if (py > height()) {
break; // we are done.. can't draw anymore
}
if (!((y >= py + m_graphs[i]->top) && (y < py + h - m_graphs[i]->bottom))) {
if (m_graphs[i]->isSelected()) {
m_graphs[i]->deselect();
timedRedraw(150);
}
}
// Update Mouse Cursor shape
if ((y >= py + h - 1) && (y < (py + h + graphSpacer))) {
this->setCursor(Qt::SplitVCursor);
} else if ((y >= py + 1) && (y < py + h)) {
if (x >= titleWidth + 10) {
this->setCursor(Qt::ArrowCursor);
} else {
m_tooltip->display("Double click title to pin / unpin\nClick and drag to reorder graphs", x + 10, y, TT_AlignLeft);
timedRedraw(0);
this->setCursor(Qt::OpenHandCursor);
}
m_horiz_travel += qAbs(x - m_lastxpos) + qAbs(y - m_lastypos);
m_lastxpos = x;
m_lastypos = y;
gGraph *g = m_graphs[i];
if (g) {
g->mouseMoveEvent(event);
}
}
/* else if (!m_button_down && (y >= py) && (y < py+m_graphs[i]->top)) {
// Mouse cursor is in top graph margin.
} else if (!m_button_down && (y >= py+h-m_graphs[i]->bottom) && (y <= py+h)) {
// Mouse cursor is in bottom grpah margin.
} else if (m_button_down || ((y >= py+m_graphs[i]->top) && (y < py + h-m_graphs[i]->bottom))) {
if (m_button_down || (x >= titleWidth+10)) { //(gYAxis::Margin-5)
this->setCursor(Qt::ArrowCursor);
m_horiz_travel+=qAbs(x-m_lastxpos)+qAbs(y-m_lastypos);
m_lastxpos=x;
m_lastypos=y;
QPoint p(x-titleWidth,y-py);
QMouseEvent e(event->type(),p,event->button(),event->buttons(),event->modifiers());
m_graphs[i]->mouseMoveEvent(&e);
if (!m_button_down && (x<=titleWidth+(gYAxis::Margin-5))) {
//qDebug() << "Hovering over" << m_graphs[i]->title();
if (m_graphsbytitle[STR_TR_EventFlags]==m_graphs[i]) {
QVector<Layer *> & layers=m_graphs[i]->layers();
gFlagsGroup *fg;
for (int i=0;i<layers.size();i++) {
if ((fg=dynamic_cast<gFlagsGroup *>(layers[i]))!=nullptr) {
float bh=fg->barHeight();
int count=fg->count();
float yp=py+m_graphs[i]->marginTop();
yp=y-yp;
float th=(float(count)*bh);
if (yp>=0 && yp<th) {
int i=yp/bh;
if (i<count) {
ChannelID code=fg->visibleLayers()[i]->code();
QString ttip=schema::channel[code].description();
m_tooltip->display(ttip,x,y-20,p_profile->general->tooltipTimeout());
redraw();
//qDebug() << code << ttip;
}
}
break;
}
}
} else {
if (!m_graphs[i]->units().isEmpty()) {
m_tooltip->display(m_graphs[i]->units(),x,y-20,p_profile->general->tooltipTimeout());
redraw();
}
}
}
} else {
this->setCursor(Qt::OpenHandCursor);
}
} */
// }
py += h + graphSpacer;
}
}
Layer * gGraphView::findLayer(gGraph * graph, LayerType type)
{
for (int i=0; i< graph->layers().size(); i++) {
Layer * l = graph->layers()[i];
if (l->layerType() == type) {
return l;
}
}
return nullptr;
}
class MyWidgetAction : public QWidgetAction
{
public:
MyWidgetAction(ChannelID code, QObject * parent = nullptr) :QWidgetAction(parent), code(code) { chbox = nullptr; }
protected:
virtual QWidget * createWidget(QWidget * /*parent*/) {
connect(chbox, SIGNAL(toggled(bool)), this, SLOT(setChecked(bool)));
connect(chbox, SIGNAL(clicked()), this, SLOT(trigger()));
return chbox;
}
QCheckBox * chbox;
ChannelID code;
};
MinMaxWidget::MinMaxWidget(gGraph * graph, QWidget *parent)
:QWidget(parent), graph(graph)
{
step = 1;
createLayout();
}
void MinMaxWidget::onMinChanged(double d)
{
graph->rec_miny = d;
graph->timedRedraw(0);
}
void MinMaxWidget::onMaxChanged(double d)
{
graph->rec_maxy = d;
graph->timedRedraw(0);
}
void MinMaxWidget::onResetClicked()
{
int tmp = graph->zoomY();
graph->setZoomY(0);
EventDataType miny = graph->MinY(),
maxy = graph->MaxY();
graph->roundY(miny, maxy);
setMin(graph->rec_miny = miny);
setMax(graph->rec_maxy = maxy);
float r = maxy-miny;
if (r > 400) {
step = 50;
} else if (r > 100) {
step = 10;
} else if (r > 50) {
step = 5;
} else {
step = 1;
}
graph->setZoomY(tmp);
}
void MinMaxWidget::onComboChanged(int idx)
{
minbox->setEnabled(idx == 2);
maxbox->setEnabled(idx == 2);
reset->setEnabled(idx == 2);
graph->setZoomY(idx);
if (idx == 2) {
if (qAbs(graph->rec_maxy - graph->rec_miny) < 0.0001) {
onResetClicked();
}
}
}
void MinMaxWidget::createLayout()
{
QGridLayout * layout = new QGridLayout;
layout->setMargin(4);
layout->setSpacing(4);
combobox = new QComboBox(this);
combobox->addItem(tr("Auto-Fit"), 0);
combobox->addItem(tr("Defaults"), 1);
combobox->addItem(tr("Override"), 2);
combobox->setToolTip(tr("The Y-Axis scaling mode, 'Auto-Fit' for automatic scaling, 'Defaults' for settings according to manufacturer, and 'Override' to choose your own."));
connect(combobox, SIGNAL(activated(int)), this, SLOT(onComboChanged(int)));
minbox = new QDoubleSpinBox(this);
maxbox = new QDoubleSpinBox(this);
minbox->setToolTip(tr("The Minimum Y-Axis value.. Note this can be a negative number if you wish."));
maxbox->setToolTip(tr("The Maximum Y-Axis value.. Must be greater than Minimum to work."));
int idx = graph->zoomY();
combobox->setCurrentIndex(idx);
minbox->setEnabled(idx == 2);
maxbox->setEnabled(idx == 2);
minbox->setAlignment(Qt::AlignRight);
maxbox->setAlignment(Qt::AlignRight);
minbox->setMinimum(-9999.0);
maxbox->setMinimum(-9999.0);
minbox->setMaximum(9999.99);
maxbox->setMaximum(9999.99);
setMin(graph->rec_miny);
setMax(graph->rec_maxy);
float r = graph->rec_maxy - graph->rec_miny;
if (r > 400) {
step = 50;
} else if (r > 100) {
step = 10;
} else if (r > 50) {
step = 5;
} else {
step = 1;
}
minbox->setSingleStep(step);
maxbox->setSingleStep(step);
connect(minbox, SIGNAL(valueChanged(double)), this, SLOT(onMinChanged(double)));
connect(maxbox, SIGNAL(valueChanged(double)), this, SLOT(onMaxChanged(double)));
QLabel * label = new QLabel(tr("Scaling Mode"));
QFont font = label->font();
font.setBold(true);
label->setFont(font);
label->setAlignment(Qt::AlignCenter);
layout->addWidget(label,0,0);
layout->addWidget(combobox,1,0);
label = new QLabel(STR_TR_Min);
label->setFont(font);
label->setAlignment(Qt::AlignCenter);
layout->addWidget(label,0,1);
layout->addWidget(minbox,1,1);
label = new QLabel(STR_TR_Max);
label->setFont(font);
label->setAlignment(Qt::AlignCenter);
layout->addWidget(label,0,2);
layout->addWidget(maxbox,1,2);
reset = new QToolButton(this);
reset->setIcon(QIcon(":/icons/refresh.png"));
reset->setToolTip(tr("This button resets the Min and Max to match the Auto-Fit"));
reset->setEnabled(idx == 2);
layout->addWidget(reset,1,3);
connect(reset, SIGNAL(clicked()), this, SLOT(onResetClicked()));
this->setLayout(layout);
}
void gGraphView::populateMenu(gGraph * graph)
{
QAction * action;
if (graph->isSnapshot()) {
snap_action->setText(tr("Remove Clone"));
snap_action->setData(graph->name()+"|remove");
// zoom100_action->setVisible(false);
} else {
snap_action->setText(tr("Clone %1 Graph").arg(graph->title()));
snap_action->setData(graph->name()+"|snapshot");
// zoom100_action->setVisible(true);
}
// Menu title fonts
QFont font = QApplication::font();
font.setBold(true);
font.setPointSize(font.pointSize() + 3);
gLineChart * lc = dynamic_cast<gLineChart *>(findLayer(graph,LT_LineChart));
SummaryChart * sc = dynamic_cast<SummaryChart *>(findLayer(graph,LT_SummaryChart));
gSummaryChart * stg = dynamic_cast<gSummaryChart *>(findLayer(graph,LT_Overview));
limits_menu->clear();
if (lc || sc || stg) {
QWidgetAction * widget = new QWidgetAction(this);
MinMaxWidget * minmax = new MinMaxWidget(graph, this);
widget->setDefaultWidget(minmax);
limits_menu->addAction(widget);
limits_menu->menuAction()->setVisible(true);
} else {
limits_menu->menuAction()->setVisible(false);
}
// First check for any linechart for this graph..
if (lc) {
lines_menu->clear();
for (int i=0; i < lc->m_dotlines.size(); i++) {
DottedLine & dot = lc->m_dotlines[i];
if (!lc->m_enabled[dot.code]) continue;
schema::Channel &chan = schema::channel[dot.code];
if (dot.available) {
QWidgetAction * widget = new QWidgetAction(context_menu);
QCheckBox *chbox = new QCheckBox(chan.calc[dot.type].label(), context_menu);
chbox->setMouseTracking(true);
chbox->setStyleSheet(QString("QCheckBox:hover { background: %1; }").arg(QApplication::palette().highlight().color().name()));
widget->setDefaultWidget(chbox);
widget->setCheckable(true);
widget->setData(QString("%1|%2").arg(graph->name()).arg(i));
connect(chbox, SIGNAL(toggled(bool)), widget, SLOT(setChecked(bool)));
connect(chbox, SIGNAL(clicked()), widget, SLOT(trigger()));
bool b = lc->m_dot_enabled[dot.code][dot.type]; //chan.calc[dot.type].enabled;
chbox->setChecked(b);
lines_menu->addAction(widget);
// QAction *action = lines_menu->addAction(chan.calc[dot.type].label());
// action->setData(graph->name());
// action->setCheckable(true);
// action->setChecked(chan.calc[dot.type].enabled);
}
}
lines_menu->menuAction()->setVisible(lines_menu->actions().size() > 0);
if (lines_menu->actions().size() > 0) {
lines_menu->insertSeparator(lines_menu->actions()[0]);
action = new QAction(QObject::tr("%1").arg(graph->title()), lines_menu);
lines_menu->insertAction(lines_menu->actions()[0], action);
action->setFont(font);
action->setData(QString(""));
action->setEnabled(false);
}
//////////////////////////////////////////////////////////////////////////////////////
// Populate Plots Menus
//////////////////////////////////////////////////////////////////////////////////////
plots_menu->clear();
if (lc->m_codes.size() > 1) {
for (int i=0; i <lc->m_codes.size(); ++i) {
ChannelID code = lc->m_codes[i];
if (lc->m_day && !lc->m_day->channelHasData(code)) continue;
QWidgetAction * widget = new QWidgetAction(context_menu);
QCheckBox *chbox = new QCheckBox(schema::channel[code].label(), context_menu);
chbox->setMouseTracking(true);
chbox->setToolTip(schema::channel[code].description());
chbox->setStyleSheet(QString("QCheckBox:hover { background: %1; }").arg(QApplication::palette().highlight().color().name()));
widget->setDefaultWidget(chbox);
widget->setCheckable(true);
widget->setData(QString("%1|%2").arg(graph->name()).arg(code));
connect(chbox, SIGNAL(toggled(bool)), widget, SLOT(setChecked(bool)));
connect(chbox, SIGNAL(clicked()), widget, SLOT(trigger()));
bool b = lc->m_enabled[code];
chbox->setChecked(b);
plots_menu->addAction(widget);
// QAction * action = plots_menu->addAction(schema::channel[code].label());
// action->setData(QString("%1|%2").arg(graph->name()).arg(code));
// action->setCheckable(true);
// action->setChecked(lc->m_enabled[code]);
}
}
plots_menu->menuAction()->setVisible((plots_menu->actions().size() > 1));
if (plots_menu->actions().size() > 0) {
plots_menu->insertSeparator(plots_menu->actions()[0]);
action = new QAction(QObject::tr("%1").arg(graph->title()), plots_menu);
plots_menu->insertAction(plots_menu->actions()[0], action);
action->setFont(font);
action->setData(QString(""));
action->setEnabled(false);
}
//////////////////////////////////////////////////////////////////////////////////////
// Populate Event Menus
//////////////////////////////////////////////////////////////////////////////////////
oximeter_menu->clear();
cpap_menu->clear();
using namespace schema;
quint32 showflags = schema::FLAG | schema::MINOR_FLAG | schema::SPAN;
if (p_profile->general->showUnknownFlags()) showflags |= schema::UNKNOWN;
QList<ChannelID> chans = lc->m_day->getSortedMachineChannels(showflags);
QHash<MachineType, int> Vis;
for (int i=0; i < chans.size() ; ++i) {
ChannelID code = chans.at(i);
schema::Channel & chan = schema::channel[code];
QWidgetAction * widget = new QWidgetAction(context_menu);
QCheckBox *chbox = new QCheckBox(schema::channel[code].fullname(), context_menu);
chbox->setPalette(context_menu->palette());
chbox->setMouseTracking(true);
chbox->setToolTip(schema::channel[code].description());
chbox->setStyleSheet(QString("QCheckBox:hover { background: %1; }").arg(QApplication::palette().highlight().color().name()));
widget->setDefaultWidget(chbox);
widget->setCheckable(true);
widget->setData(QString("%1|%2").arg(graph->name()).arg(code));
connect(chbox, SIGNAL(toggled(bool)), widget, SLOT(setChecked(bool)));
connect(chbox, SIGNAL(clicked()), widget, SLOT(trigger()));
bool b = lc->m_flags_enabled[code];
chbox->setChecked(b);
Vis[chan.machtype()] += b ? 1 : 0;
action = nullptr;
if (chan.machtype() == MT_OXIMETER) {
oximeter_menu->insertAction(nullptr, widget);
} else if ( chan.machtype() == MT_CPAP) {
cpap_menu->insertAction(nullptr,widget);
}
}
QString HideAllEvents = QObject::tr("Hide All Events");
QString ShowAllEvents = QObject::tr("Show All Events");
oximeter_menu->menuAction()->setVisible(oximeter_menu->actions().size() > 0);
cpap_menu->menuAction()->setVisible(cpap_menu->actions().size() > 0);
if (cpap_menu->actions().size() > 0) {
cpap_menu->addSeparator();
if (Vis[MT_CPAP] > 0) {
action = cpap_menu->addAction(HideAllEvents);
action->setData(QString("%1|HideAll:CPAP").arg(graph->name()));
} else {
action = cpap_menu->addAction(ShowAllEvents);
action->setData(QString("%1|ShowAll:CPAP").arg(graph->name()));
}
// Show CPAP Events menu Header...
cpap_menu->insertSeparator(cpap_menu->actions()[0]);
action = new QAction(QObject::tr("%1").arg(graph->title()), cpap_menu);
cpap_menu->insertAction(cpap_menu->actions()[0], action);
action->setFont(font);
action->setData(QString(""));
action->setEnabled(false);
}
if (oximeter_menu->actions().size() > 0) {
oximeter_menu->addSeparator();
if (Vis[MT_OXIMETER] > 0) {
action = oximeter_menu->addAction(HideAllEvents);
action->setData(QString("%1|HideAll:OXI").arg(graph->name()));
} else {
action = oximeter_menu->addAction(ShowAllEvents);
action->setData(QString("%1|ShowAll:OXI").arg(graph->name()));
}
oximeter_menu->insertSeparator(oximeter_menu->actions()[0]);
action = new QAction(QObject::tr("%1").arg(graph->title()), oximeter_menu);
oximeter_menu->insertAction(oximeter_menu->actions()[0], action);
action->setFont(font);
action->setData(QString(""));
action->setEnabled(false);
}
} else {
lines_menu->clear();
lines_menu->menuAction()->setVisible(false);
plots_menu->clear();
plots_menu->menuAction()->setVisible(false);
oximeter_menu->clear();
oximeter_menu->menuAction()->setVisible(false);
cpap_menu->clear();
cpap_menu->menuAction()->setVisible(false);
}
}
void gGraphView::onSnapshotGraphToggle()
{
QString name = snap_action->data().toString().section("|",0,0);
QString cmd = snap_action->data().toString().section("|",-1).toLower();
QHash<QString, gGraph *>::iterator it = m_graphsbyname.find(name);
if (it == m_graphsbyname.end()) return;
gGraph * graph = it.value();
if (cmd == "snapshot") {
QString basename = name+";";
if (graph->m_day) {
QDateTime date = QDateTime::fromMSecsSinceEpoch(graph->min_x, Qt::UTC);
basename += date.date().toString(Qt::SystemLocaleLongDate);
}
QString newname;
// Find a new name.. How many snapshots for each graph counts as stupid?
for (int i=1;i < 100;i++) {
newname = basename+" ("+QString::number(i)+")";
it = m_graphsbyname.find(newname);
if (it == m_graphsbyname.end()) {
break;
}
}
QString newtitle;
bool fnd = false;
// someday, some clown will keep adding new graphs to break this..
for (int i=1; i < 100; i++) {
newtitle = graph->title()+"-"+QString::number(i);
fnd = false;
for (int j=0; j<m_graphs.size(); ++j) {
if (m_graphs[j]->title() == newtitle) {
fnd = true;
break;
}
}
if (!fnd) break;
}
if (fnd) {
// holy crap.. what patience. but not what I meant by as many as you like ;)
return;
}
gGraph * newgraph = new gGraph(newname, nullptr, newtitle, graph->units(), graph->height(), graph->group());
// newgraph->setBlockSelect(true);
newgraph->setHeight(graph->height());
short group = 0;
m_graphs.insert(m_graphs.indexOf(graph)+1, newgraph);
m_graphsbyname[newname] = newgraph;
newgraph->m_graphview = this;
for (int i=0; i < graph->m_layers.size(); ++i) {
Layer * layer = graph->m_layers.at(i)->Clone();
if (layer) {
newgraph->m_layers.append(layer);
}
}
for (int i=0;i<m_graphs.size();i++) {
gGraph *g = m_graphs.at(i);
group = qMax(g->group(), group);
}
newgraph->setGroup(group+1);
//newgraph->setMinHeight(pm.height());
newgraph->setDay(graph->m_day);
if (graph->m_day) {
graph->m_day->incUseCounter();
}
newgraph->min_x = graph->min_x;
newgraph->max_x = graph->max_x;
if (graph->blockZoom()) {
newgraph->setBlockZoom(graph->blockZoom());
newgraph->setBlockSelect(true);
}
if (graph->blockSelect()) {
newgraph->setBlockSelect(true);
}
newgraph->setZoomY(graph->zoomY());
newgraph->setSnapshot(true);
emit GraphsChanged();
// addGraph(newgraph);
updateScale();
timedRedraw(0);
} else if (cmd == "remove") {
if (graph->m_day) {
graph->m_day->decUseCounter();
if (graph->m_day->useCounter() == 0) {
}
}
m_graphsbyname.remove(graph->name());
m_graphs.removeAll(it.value());
delete graph;
updateScale();
timedRedraw(0);
emit GraphsChanged();
}
qDebug() << cmd << name;
}
bool gGraphView::hasSnapshots()
{
int size = m_graphs.size();
bool snap = false;
for (int i=0; i< size; ++i) {
gGraph * graph = m_graphs.at(i);
if (graph->isSnapshot()) {
snap = true;
break;
}
}
return snap;
}
void gGraphView::onPlotsClicked(QAction *action)
{
QString name = action->data().toString().section("|",0,0);
ChannelID code = action->data().toString().section("|",-1).toInt();
QHash<QString, gGraph *>::iterator it = m_graphsbyname.find(name);
if (it == m_graphsbyname.end()) return;
gGraph * graph = it.value();
gLineChart * lc = dynamic_cast<gLineChart *>(findLayer(graph, LT_LineChart));
if (!lc) return;
lc->m_enabled[code] = !lc->m_enabled[code];
graph->min_y = graph->MinY();
graph->max_y = graph->MaxY();
graph->timedRedraw(0);
// lc->Miny();
// lc->Maxy();
}
void gGraphView::onOverlaysClicked(QAction *action)
{
QString name = action->data().toString().section("|",0,0);
QString data = action->data().toString().section("|",-1);
QHash<QString, gGraph *>::iterator it = m_graphsbyname.find(name);
if (it == m_graphsbyname.end()) return;
gGraph * graph = it.value();
gLineChart * lc = dynamic_cast<gLineChart *>(findLayer(graph, LT_LineChart));
if (!lc) return;
bool ok;
ChannelID code = data.toInt(&ok);
if (ok) {
// Just toggling a flag on/off
bool b = ! lc->m_flags_enabled[code];
lc->m_flags_enabled[code] = b;
QWidgetAction * widget = qobject_cast<QWidgetAction *>(action);
if (widget) {
widget->setChecked(b);
}
timedRedraw(0);
return;
}
QString hideall = data.section(":",0,0);
if ((hideall == "HideAll") || (hideall == "ShowAll")) {
bool value = (hideall == "HideAll") ? false : true;
QString group = data.section(":",-1).toUpper();
MachineType mtype;
if (group == "CPAP") mtype = MT_CPAP;
else if (group == "OXI") mtype = MT_OXIMETER;
else mtype = MT_UNKNOWN;
QHash<ChannelID, bool>::iterator it;
QHash<ChannelID, bool>::iterator mfe = lc->m_flags_enabled.end();
// First toggle the actual flag bits
for (it = lc->m_flags_enabled.begin(); it != mfe; ++it) {
if (schema::channel[it.key()].machtype() == mtype) {
lc->m_flags_enabled[it.key()] = value;
}
}
// Now toggle the menu actions.. bleh
if (mtype == MT_CPAP) {
for (int i=0; i< cpap_menu->actions().size(); i++) {
if (cpap_menu->actions().at(i)->isCheckable()) {
cpap_menu->actions().at(i)->setChecked(value);
}
}
} else if (mtype == MT_OXIMETER) {
for (int i=0; i< oximeter_menu->actions().size(); i++) {
if (oximeter_menu->actions().at(i)->isCheckable()) {
oximeter_menu->actions().at(i)->setChecked(value);
}
}
}
}
}
void gGraphView::onLinesClicked(QAction *action)
{
QString name = action->data().toString().section("|",0,0);
QString data = action->data().toString().section("|",-1);
QHash<QString, gGraph *>::iterator it = m_graphsbyname.find(name);
if (it == m_graphsbyname.end()) return;
gGraph * graph = it.value();
gLineChart * lc = dynamic_cast<gLineChart *>(findLayer(graph, LT_LineChart));
if (!lc) return;
bool ok;
int i = data.toInt(&ok);
if (ok) {
DottedLine & dot = lc->m_dotlines[i];
schema::Channel &chan = schema::channel[dot.code];
chan.calc[dot.type].enabled = !chan.calc[dot.type].enabled;
lc->m_dot_enabled[dot.code][dot.type] = !lc->m_dot_enabled[dot.code][dot.type];
}
timedRedraw(0);
}
void gGraphView::mousePressEvent(QMouseEvent *event)
{
int x = event->x();
int y = event->y();
float h, pinned_height = 0, py = 0;
bool done = false;
// first handle pinned graphs.
// Calculate total height of all pinned graphs
for (int i = 0; i < m_graphs.size(); i++) {
gGraph *g = m_graphs[i];
if (!g || g->isEmpty() || !g->visible() || !g->isPinned()) {
continue;
}
h = g->height() * m_scaleY;
pinned_height += h + graphSpacer;
if (py > height()) {
break;
}
if ((py + h + graphSpacer) >= 0) {
if ((y >= py + h - 1) && (y <= py + h + graphSpacer)) {
this->setCursor(Qt::SplitVCursor);
m_sizer_dragging = true;
m_sizer_index = i;
m_sizer_point.setX(x);
m_sizer_point.setY(y);
done = true;
} else if ((y >= py) && (y < py + h)) {
//qDebug() << "Clicked" << i;
if ((event->button() == Qt::LeftButton) && (x < titleWidth + 20)) {
// clicked on title to drag graph..
// Note: reorder has to be limited to pinned graphs.
m_graph_dragging = true;
m_tooltip->cancel();
timedRedraw(50);
m_graph_index = i;
m_sizer_point.setX(x);
m_sizer_point.setY(py); // point at top of graph..
this->setCursor(Qt::ClosedHandCursor);
done=true;
} else if ((event->button() == Qt::RightButton) && (x < (titleWidth + gYAxis::Margin))) {
this->setCursor(Qt::ArrowCursor);
pin_action->setText(QObject::tr("Unpin %1 Graph").arg(g->title()));
pin_graph = g;
populateMenu(g);
context_menu->popup(event->globalPos());
done=true;
} else if (!g->blockSelect()) {
if (m_metaselect) {
if (m_selected_graph) {
m_selected_graph->m_selecting_area = false;
}
}
// send event to graph..
m_point_clicked = QPoint(event->x(), event->y());
//QMouseEvent e(event->type(),m_point_clicked,event->button(),event->buttons(),event->modifiers());
m_button_down = true;
m_metaselect = event->modifiers() && Qt::AltModifier;
m_horiz_travel = 0;
m_graph_index = i;
m_selected_graph = g;
g->mousePressEvent(event);
}
done = true;
}
}
py += h + graphSpacer;
}
// then handle the remainder...
py = -m_offsetY;
py += pinned_height;
if (!done)
for (int i = 0; i < m_graphs.size(); i++) {
gGraph * g = m_graphs[i];
if (!g) continue;
if (!g || g->isEmpty() || !g->visible() || g->isPinned()) { continue; }
h = g->height() * m_scaleY;
if (py > height()) {
break;
}
if ((py + h + graphSpacer) >= 0) {
if ((y >= py + h - 1) && (y <= py + h + graphSpacer)) {
this->setCursor(Qt::SplitVCursor);
m_sizer_dragging = true;
m_sizer_index = i;
m_sizer_point.setX(x);
m_sizer_point.setY(y);
//qDebug() << "Sizer clicked" << i;
done=true;
} else if ((y >= py) && (y < py + h)) {
//qDebug() << "Clicked" << i;
if ((event->button() == Qt::LeftButton) && (x < (titleWidth + 20))) { // clicked on title to drag graph..
m_graph_dragging = true;
m_tooltip->cancel();
redraw();
m_graph_index = i;
m_sizer_point.setX(x);
m_sizer_point.setY(py); // point at top of graph..
this->setCursor(Qt::ClosedHandCursor);
done=true;
} else if ((event->button() == Qt::RightButton) && (x < (titleWidth + gYAxis::Margin))) {
this->setCursor(Qt::ArrowCursor);
pin_action->setText(QObject::tr("Pin %1 Graph").arg(g->title()));
pin_graph = g;
populateMenu(g);
context_menu->popup(event->globalPos());
done=true;
} else if (!g->blockSelect()) {
if (m_metaselect) {
if (m_selected_graph) {
m_selected_graph->m_selecting_area = false;
}
}
// send event to graph..
m_point_clicked = QPoint(event->x(), event->y());
//QMouseEvent e(event->type(),m_point_clicked,event->button(),event->buttons(),event->modifiers());
m_button_down = true;
m_metaselect = event->modifiers() && Qt::AltModifier;
m_horiz_travel = 0;
m_graph_index = i;
m_selected_graph = g;
g->mousePressEvent(event);
}
}
}
py += h + graphSpacer;
done=true;
}
if (!done) {
// if (event->button() == Qt::RightButton) {
// this->setCursor(Qt::ArrowCursor);
// context_menu->popup(event->globalPos());
// done=true;
// }
}
}
void gGraphView::mouseReleaseEvent(QMouseEvent *event)
{
int x = event->x();
int y = event->y();
float h, py = 0, pinned_height = 0;
bool done = false;
// Copy to a local variable to make sure this gets cleared
bool button_down = m_button_down;
m_button_down = false;
// Handle pinned graphs first
for (int i = 0; i < m_graphs.size(); i++) {
gGraph *g = m_graphs[i];
if (!g || g->isEmpty() || !g->visible() || !g->isPinned()) {
continue;
}
h = g->height() * m_scaleY;
pinned_height += h + graphSpacer;
if (py > height()) {
break; // we are done.. can't draw anymore
}
if ((y >= py + h - 1) && (y < (py + h + graphSpacer))) {
this->setCursor(Qt::SplitVCursor);
done = true;
} else if ((y >= py + 1) && (y <= py + h)) {
// if (!m_sizer_dragging && !m_graph_dragging) {
// g->mouseReleaseEvent(event);
// }
if (x >= titleWidth + 10) {
this->setCursor(Qt::ArrowCursor);
} else {
this->setCursor(Qt::OpenHandCursor);
}
done = true;
}
py += h + graphSpacer;
}
// Now do the unpinned ones
py = -m_offsetY;
py += pinned_height;
if (done)
for (int i = 0; i < m_graphs.size(); i++) {
gGraph *g = m_graphs[i];
if (!g || g->isEmpty() || !g->visible() || g->isPinned()) {
continue;
}
h = g->height() * m_scaleY;
if (py > height()) {
break; // we are done.. can't draw anymore
}
if ((y >= py + h - 1) && (y < (py + h + graphSpacer))) {
this->setCursor(Qt::SplitVCursor);
} else if ((y >= py + 1) && (y <= py + h)) {
// if (!m_sizer_dragging && !m_graph_dragging) {
// g->mouseReleaseEvent(event);
// }
if (x >= titleWidth + 10) {
this->setCursor(Qt::ArrowCursor);
} else {
this->setCursor(Qt::OpenHandCursor);
}
}
py += h + graphSpacer;
}
if (m_sizer_dragging) {
m_sizer_dragging = false;
return;
}
if (m_graph_dragging) {
m_graph_dragging = false;
// not sure why the cursor code doesn't catch this..
if (x >= titleWidth + 10) {
this->setCursor(Qt::ArrowCursor);
} else {
this->setCursor(Qt::OpenHandCursor);
}
return;
}
// The graph that got the button press gets the release event
if (button_down) {
// m_button_down = false;
m_metaselect = event->modifiers() & Qt::AltModifier;
saveHistory();
if (m_metaselect) {
m_point_released = event->pos();
} else {
if ((m_graph_index >= 0) && (m_graphs[m_graph_index])) {
m_graphs[m_graph_index]->mouseReleaseEvent(event);
}
}
}
timedRedraw(0);
}
void gGraphView::keyReleaseEvent(QKeyEvent *event)
{
if (m_metaselect && !(event->modifiers() & Qt::AltModifier)) {
QMouseEvent mevent(QEvent::MouseButtonRelease, m_point_released, Qt::LeftButton, Qt::LeftButton, event->modifiers());
if (m_graph_index>=0) {
m_graphs[m_graph_index]->mouseReleaseEvent(&mevent);
}
m_metaselect = false;
timedRedraw(50);
}
if (event->key() == Qt::Key_Escape) {
if (history.size() > 0) {
SelectionHistoryItem h = history.takeFirst();
SetXBounds(h.minx, h.maxx);
// could Forward push this to another list?
} else {
ResetBounds();
}
return;
}
#ifdef BROKEN_OPENGL_BUILD
QWidget::keyReleaseEvent(event);
#else
QGLWidget::keyReleaseEvent(event);
#endif
}
void gGraphView::mouseDoubleClickEvent(QMouseEvent *event)
{
mousePressEvent(event); // signal missing.. a qt change might "fix" this if we are not careful.
int x = event->x();
int y = event->y();
float h, py = 0, pinned_height = 0;
bool done = false;
// Handle pinned graphs first
for (int i = 0; i < m_graphs.size(); i++) {
gGraph *g = m_graphs[i];
if (!g || g->isEmpty() || !g->visible() || !g->isPinned()) {
continue;
}
h = g->height() * m_scaleY;
pinned_height += h + graphSpacer;
if (py > height()) {
break; // we are done.. can't draw anymore
}
if ((py + h + graphSpacer) >= 0) {
if ((y >= py) && (y <= py + h)) {
if (x < titleWidth) {
// What to do when double clicked on the graph title ??
g->mouseDoubleClickEvent(event);
// pin the graph??
g->setPinned(false);
redraw();
} else {
// send event to graph..
g->mouseDoubleClickEvent(event);
}
done = true;
} else if ((y >= py + h) && (y <= py + h + graphSpacer + 1)) {
// What to do when double clicked on the resize handle?
done = true;
}
}
py += h;
py += graphSpacer; // do we want the extra spacer down the bottom?
}
py = -m_offsetY;
py += pinned_height;
if (!done) // then handle unpinned graphs
for (int i = 0; i < m_graphs.size(); i++) {
gGraph *g = m_graphs[i];
if (!g || g->isEmpty() || !g->visible() || g->isPinned()) {
continue;
}
h = g->height() * m_scaleY;
if (py > height()) {
break;
}
if ((py + h + graphSpacer) >= 0) {
if ((y >= py) && (y <= py + h)) {
if (x < titleWidth) {
// What to do when double clicked on the graph title ??
g->mouseDoubleClickEvent(event);
g->setPinned(true);
redraw();
} else {
// send event to graph..
g->mouseDoubleClickEvent(event);
}
} else if ((y >= py + h) && (y <= py + h + graphSpacer + 1)) {
// What to do when double clicked on the resize handle?
}
}
py += h;
py += graphSpacer; // do we want the extra spacer down the bottom?
}
}
void gGraphView::wheelEvent(QWheelEvent *event)
{
// Hmm.. I could optionalize this to change mousewheel behaviour without affecting the scrollbar now..
if (m_button_down)
return;
if (event->modifiers() == Qt::NoModifier) {
int scrollDampening = p_profile->general->scrollDampening();
if (event->orientation() == Qt::Vertical) { // Vertical Scrolling
if (horizScrollTime.elapsed() < scrollDampening) {
return;
}
if (m_scrollbar)
m_scrollbar->SendWheelEvent(event); // Just forwarding the event to scrollbar for now..
m_tooltip->cancel();
vertScrollTime.start();
return;
}
// (This is a total pain in the butt on MacBook touchpads..)
if (vertScrollTime.elapsed() < scrollDampening) {
return;
}
horizScrollTime.start();
}
gGraph *graph = nullptr;
int group = 0;
//int x = event->x();
int y = event->y();
float h, py = 0, pinned_height = 0;
// Find graph hovered over
for (int i = 0; i < m_graphs.size(); i++) {
gGraph *g = m_graphs[i];
if (!g || g->isEmpty() || !g->visible() || !g->isPinned()) {
continue;
}
h = g->height() * m_scaleY;
pinned_height += h + graphSpacer;
if (py > height()) {
break; // we are done.. can't draw anymore
}
if ((py + h + graphSpacer) >= 0) {
if ((y >= py) && (y <= py + h)) {
graph = g;
break;
} else if ((y >= py + h) && (y <= py + h + graphSpacer + 1)) {
// What to do when double clicked on the resize handle?
graph = g;
break;
}
}
py += h;
py += graphSpacer; // do we want the extra spacer down the bottom?
}
if (!graph) {
py = -m_offsetY;
py += pinned_height;
for (int i = 0; i < m_graphs.size(); i++) {
gGraph *g = m_graphs[i];
if (!g || g->isEmpty() || !g->visible() || g->isPinned()) {
continue;
}
h = g->height() * m_scaleY;
if (py > height()) {
break;
}
if ((py + h + graphSpacer) >= 0) {
if ((y >= py) && (y <= py + h)) {
graph = g;
break;
} else if ((y >= py + h) && (y <= py + h + graphSpacer + 1)) {
// What to do when double clicked on the resize handle?
graph = g;
break;
}
}
py += h;
py += graphSpacer; // do we want the extra spacer down the bottom?
}
}
if (event->modifiers() == Qt::NoModifier) {
if (!graph) {
// just pick any graph then
for (int i = 0; i < m_graphs.size(); i++) {
if (!m_graphs[i]) continue;
if (!m_graphs[i]->isEmpty()) {
graph = m_graphs[i];
group = graph->group();
break;
}
}
} else group=graph->group();
if (!graph) { return; }
double xx = (graph->max_x - graph->min_x);
double zoom = 240.0;
int delta = event->delta();
if (delta > 0) {
graph->min_x -= (xx / zoom) * (float)abs(delta);
} else {
graph->min_x += (xx / zoom) * (float)abs(delta);
}
graph->max_x = graph->min_x + xx;
if (graph->min_x < graph->rmin_x) {
graph->min_x = graph->rmin_x;
graph->max_x = graph->rmin_x + xx;
}
if (graph->max_x > graph->rmax_x) {
graph->max_x = graph->rmax_x;
graph->min_x = graph->max_x - xx;
}
saveHistory();
SetXBounds(graph->min_x, graph->max_x, group);
} else if ((event->modifiers() & Qt::ControlModifier)) {
if (graph) graph->wheelEvent(event);
// int x = event->x();
// int y = event->y();
// float py = -m_offsetY;
// float h;
// for (int i = 0; i < m_graphs.size(); i++) {
// gGraph *g = m_graphs[i];
// if (!g || g->isEmpty() || !g->visible()) { continue; }
// h = g->height() * m_scaleY;
// if (py > height()) {
// break;
// }
// if ((py + h + graphSpacer) >= 0) {
// if ((y >= py) && (y <= py + h)) {
// if (x < titleWidth) {
// // What to do when ctrl+wheel is used on the graph title ??
// } else {
// // send event to graph..
// g->wheelEvent(event);
// }
// } else if ((y >= py + h) && (y <= py + h + graphSpacer + 1)) {
// // What to do when the wheel is used on the resize handle?
// }
// }
// py += h;
// py += graphSpacer; // do we want the extra spacer down the bottom?
// }
}
}
void gGraphView::getSelectionTimes(qint64 & start, qint64 & end)
{
if (m_graph_index >= 0) {
gGraph *g = m_graphs[m_graph_index];
if (!g) {
start = 0;
end = 0;
return;
}
int x1 = g->m_selection.x() + titleWidth;
int x2 = x1 + g->m_selection.width();
start = g->screenToTime(x1);
end = g->screenToTime(x2);
}
}
void gGraphView::keyPressEvent(QKeyEvent *event)
{
m_metaselect = event->modifiers() & Qt::AltModifier;
if (m_metaselect && ((event->key() == Qt::Key_B) || (event->key() == 8747))) {
if (mainwin->getDaily()->graphView() == this) {
if (m_graph_index >= 0) {
m_metaselect=false;
qint64 start,end;
getSelectionTimes(start,end);
QDateTime d1 = QDateTime::fromMSecsSinceEpoch(start, Qt::UTC);
mainwin->getDaily()->addBookmark(start, end, QString("Bookmark at %1").arg(d1.time().toString("HH:mm:ss")));
m_graphs[m_graph_index]->cancelSelection();
m_graph_index = -1;
timedRedraw(0);
}
event->accept();
return;
}
}
if ((m_metaselect) && (event->key() >= Qt::Key_0) && (event->key() <= Qt::Key_9)) {
//int bk = (int)event->key()-Qt::Key_0;
m_metaselect = false;
timedRedraw(0);
}
if (event->key() == Qt::Key_F3) {
p_profile->appearance->setLineCursorMode(!p_profile->appearance->lineCursorMode());
timedRedraw(0);
}
if ((event->key() == Qt::Key_F1)) {
dumpInfo();
}
if (event->key() == Qt::Key_Tab) {
event->ignore();
return;
}
if (event->key() == Qt::Key_PageUp) {
if (m_scrollbar) {
m_offsetY -= p_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) {
if (m_scrollbar) {
m_offsetY += p_profile->appearance->graphHeight() * 3 * m_scaleY; //p_profile->appearance->graphHeight();
if (m_offsetY < 0) { m_offsetY = 0; }
m_scrollbar->setValue(m_offsetY);
m_offsetY = m_scrollbar->value();
redraw();
}
return;
// redraw();
}
gGraph *g = nullptr;
int group = 0;
// Pick the first valid graph in the primary group
for (int i = 0; i < m_graphs.size(); i++) {
if (m_graphs[i]->group() == group) {
if (!m_graphs[i]->isEmpty() && m_graphs[i]->visible()) {
g = m_graphs[i];
break;
}
}
}
if (!g) {
for (int i = 0; i < m_graphs.size(); i++) {
if (!m_graphs[i]->isEmpty()) {
g = m_graphs[i];
group = g->group();
break;
}
}
}
if (!g) { return; }
g->keyPressEvent(event);
if (event->key() == Qt::Key_Left) {
double xx = g->max_x - g->min_x;
double zoom = 8.0;
if (event->modifiers() & Qt::ControlModifier) { zoom /= 4; }
g->min_x -= xx / zoom;;
g->max_x = g->min_x + xx;
if (g->min_x < g->rmin_x) {
g->min_x = g->rmin_x;
g->max_x = g->rmin_x + xx;
}
saveHistory();
SetXBounds(g->min_x, g->max_x, group);
} else if (event->key() == Qt::Key_Right) {
double xx = g->max_x - g->min_x;
double zoom = 8.0;
if (event->modifiers() & Qt::ControlModifier) { zoom /= 4; }
g->min_x += xx / zoom;
g->max_x = g->min_x + xx;
if (g->max_x > g->rmax_x) {
g->max_x = g->rmax_x;
g->min_x = g->rmax_x - xx;
}
saveHistory();
SetXBounds(g->min_x, g->max_x, group);
} else if (event->key() == Qt::Key_Up) {
float zoom = 0.75F;
if (event->modifiers() & Qt::ControlModifier) { zoom /= 1.5; }
g->ZoomX(zoom, 0); // zoom in.
} else if (event->key() == Qt::Key_Down) {
float zoom = 1.33F;
if (event->modifiers() & Qt::ControlModifier) { zoom *= 1.5; }
g->ZoomX(zoom, 0); // Zoom out
}
//qDebug() << "Keypress??";
}
void gGraphView::setDay(Day *day)
{
m_day = day;
for (int i = 0; i < m_graphs.size(); i++) {
if (m_graphs[i]) m_graphs[i]->setDay(day);
}
ResetBounds(false);
}
bool gGraphView::isEmpty()
{
bool res = true;
for (int i = 0; i < m_graphs.size(); i++) {
gGraph * graph = m_graphs.at(i);
if (!graph->isSnapshot() && !graph->isEmpty()) {
res = false;
break;
}
}
return res;
}
void gGraphView::refreshTimeout()
{
if (this->isVisible()) {
redraw();
}
}
void gGraphView::timedRedraw(int ms)
{
if (timer->isActive()) {
if (ms == 0) {
timer->stop();
} else {
int m = timer->remainingTime();
if (m > ms) {
timer->stop();
} else return;
}
}
timer->setSingleShot(true);
timer->start(ms);
}
void gGraphView::resetLayout()
{
int default_height = p_profile->appearance->graphHeight();
for (int i = 0; i < m_graphs.size(); i++) {
if (m_graphs[i]) m_graphs[i]->setHeight(default_height);
}
updateScale();
timedRedraw(0);
}
void gGraphView::deselect()
{
for (int i = 0; i < m_graphs.size(); i++) {
if (m_graphs[i]) m_graphs[i]->deselect();
}
}
const quint32 gvmagic = 0x41756728;
const quint16 gvversion = 4;
void gGraphView::SaveSettings(QString title)
{
qDebug() << "Saving" << title << "settings";
QString filename = p_profile->Get("{DataFolder}/") + title.toLower() + ".shg";
QFile f(filename);
f.open(QFile::WriteOnly);
QDataStream out(&f);
out.setVersion(QDataStream::Qt_4_6);
out.setByteOrder(QDataStream::LittleEndian);
out << (quint32)gvmagic;
out << (quint16)gvversion;
out << (qint16)size();
for (qint16 i = 0; i < size(); i++) {
gGraph * graph = m_graphs[i];
if (!graph) continue;
if (graph->isSnapshot()) continue;
out << graph->name();
out << graph->height();
out << graph->visible();
out << graph->RecMinY();
out << graph->RecMaxY();
out << graph->zoomY();
out << (bool)graph->isPinned();
gLineChart * lc = dynamic_cast<gLineChart *>(findLayer(graph, LT_LineChart));
if (lc) {
out << (quint32)LT_LineChart;
out << lc->m_flags_enabled;
out << lc->m_enabled;
out << lc->m_dot_enabled;
} else {
out << (quint32)LT_Other;
}
}
f.close();
}
bool gGraphView::LoadSettings(QString title)
{
QString filename = p_profile->Get("{DataFolder}/") + title.toLower() + ".shg";
QFile f(filename);
if (!f.exists()) {
return false;
}
f.open(QFile::ReadOnly);
QDataStream in(&f);
in.setVersion(QDataStream::Qt_4_6);
in.setByteOrder(QDataStream::LittleEndian);
quint32 t1;
quint16 version;
in >> t1;
if (t1 != gvmagic) {
qDebug() << "gGraphView" << title << "settings magic doesn't match" << t1 << gvmagic;
return false;
}
in >> version;
if (version < gvversion) {
qDebug() << "gGraphView" << title << "settings will be upgraded.";
}
qint16 siz;
in >> siz;
QString name;
float hght;
bool vis;
EventDataType recminy, recmaxy;
bool pinned;
short zoomy = 0;
QList<gGraph *> neworder;
QHash<QString, gGraph *>::iterator gi;
for (int i = 0; i < siz; i++) {
in >> name;
in >> hght;
in >> vis;
in >> recminy;
in >> recmaxy;
if (gvversion >= 1) {
in >> zoomy;
}
if (gvversion >= 2) {
in >> pinned;
}
QHash<ChannelID, bool> flags_enabled;
QHash<ChannelID, bool> plots_enabled;
QHash<ChannelID, QHash<quint32, bool> > dot_enabled;
// Warning: Do not break the follow section up!!!
quint32 layertype;
if (gvversion >= 4) {
in >> layertype;
if (layertype == LT_LineChart) {
in >> flags_enabled;
in >> plots_enabled;
in >> dot_enabled;
}
}
gGraph *g = nullptr;
if (version <= 2) {
continue;
// // Names were stored as translated strings, so look up title instead.
// g = nullptr;
// for (int z=0; z<m_graphs.size(); ++z) {
// if (m_graphs[z]->title() == name) {
// g = m_graphs[z];
// break;
// }
// }
} else {
gi = m_graphsbyname.find(name);
if (gi == m_graphsbyname.end()) {
qDebug() << "Graph" << name << "has been renamed or removed";
} else {
g = gi.value();
}
}
if (g) {
neworder.push_back(g);
g->setHeight(hght);
g->setVisible(vis);
g->setRecMinY(recminy);
g->setRecMaxY(recmaxy);
g->setZoomY(zoomy);
g->setPinned(pinned);
if (gvversion >= 4) {
if (layertype == LT_LineChart) {
gLineChart * lc = dynamic_cast<gLineChart *>(findLayer(g, LT_LineChart));
if (lc) {
lc->m_flags_enabled = flags_enabled;
lc->m_enabled = plots_enabled;
lc->m_dot_enabled = dot_enabled;
}
}
}
}
}
if (neworder.size() == m_graphs.size()) {
m_graphs = neworder;
}
f.close();
updateScale();
return true;
}
gGraph *gGraphView::findGraph(QString name)
{
QHash<QString, gGraph *>::iterator i = m_graphsbyname.find(name);
if (i == m_graphsbyname.end()) { return nullptr; }
return i.value();
}
gGraph *gGraphView::findGraphTitle(QString title)
{
for (int i=0; i< m_graphs.size(); ++i) {
if (m_graphs[i]->title() == title) return m_graphs[i];
}
return nullptr;
}
int gGraphView::visibleGraphs()
{
int cnt = 0;
for (int i = 0; i < m_graphs.size(); i++) {
if (m_graphs[i]->visible() && !m_graphs[i]->isEmpty()) { cnt++; }
}
return cnt;
}
void gGraphView::dataChanged()
{
for (int i = 0; i < m_graphs.size(); i++) {
m_graphs[i]->dataChanged();
}
}
void gGraphView::redraw()
{
#ifdef BROKEN_OPENGL_BUILD
repaint();
#else
updateGL();
#endif
}