540 lines
16 KiB
C++
Executable File
540 lines
16 KiB
C++
Executable File
/**
|
|
* @RDE_HEADER@
|
|
* offers zoom-buttons, current zoom in percent and date-time indicators coresponding to the viewport
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
* Created by Mike Wille, SLSE, 2014-10-01
|
|
*/
|
|
|
|
#include "TimelineWidget.h"
|
|
#include "TimelineIndicator.h"
|
|
#include "EventTools.h"
|
|
|
|
#include <NGLLogger/NGLLoggerBootstrap.h>
|
|
//#include <nglsettings/nglsettings_settings.h>
|
|
#include <ngltools/ngltools_dtg.h>
|
|
|
|
#include <QGuiApplication>
|
|
#include <QHeaderView>
|
|
#include <QFont>
|
|
#include <QFontMetrics>
|
|
#include <QPainter>
|
|
#include <QPalette>
|
|
#include <QPushButton>
|
|
#include <QApplication>
|
|
#include <QHBoxLayout>
|
|
#include <QDateTime>
|
|
#include <QPoint>
|
|
#include <QLabel>
|
|
#include <QComboBox>
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
|
|
#ifdef LTS_DEBUG
|
|
// #define VERBOSE
|
|
#endif
|
|
#ifdef VERBOSE
|
|
#define dbg_print(x) qWarning() << #x << x
|
|
#else
|
|
#define dbg_print(x) (void)x;
|
|
#endif
|
|
|
|
namespace LtsTimelineClassic
|
|
{
|
|
|
|
TimelineIndicator::TimelineIndicator(QWidget *parent)
|
|
: QWidget(parent)
|
|
, m_zoomFactorPoints(0)
|
|
, m_pbZoom(new QPushButton(this))
|
|
, m_pbUnzoom(new QPushButton(this))
|
|
, m_cbZoomLevel(new QComboBox(this))
|
|
, m_currentTextHeight(0) // text height of last drawn text
|
|
, m_currentTextWidth(0)
|
|
, m_sectionPos(0)
|
|
, m_sectionSize(0)
|
|
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.Indicator"))
|
|
{
|
|
m_timeArray.reserve(11 + 11 + 7 + 10);
|
|
|
|
m_timeArray << // see addEventToItem when adding / removing times from this array
|
|
// pos 0 1 2 3 4 5 6 7 8 9 10
|
|
/* seconds */ 1 << 2 << 3 << 4 << 5 << 6 << 12 << 15 << 20 << 30 << 60 <<
|
|
|
|
// pos 11 12 13 14 15 16 17 18 19 20 21
|
|
/* minutes */ 1 * 60 << 2 * 60 << 3 * 60 << 4 * 60 << 5 * 60 << 6 * 60 << 12 * 60 << 15 * 60 << 20 * 60 << 30 * 60 << 60 * 60 <<
|
|
|
|
// pos 22 23 24 25 26 27 28
|
|
/* hours */ 1 * 60 * 60 << 2 * 60 * 60 << 4 * 60 * 60 << 6 * 60 * 60 << 8 * 60 * 60 << 12 * 60 * 60 << 24 * 60 * 60 <<
|
|
|
|
// pos 29 30 31 32 33 34 35
|
|
/* da ys */ 1 * 60 * 60 << 2 * 60 * 60 << 4 * 60 * 60 << 6 * 60 * 60 << 8 * 60 * 60 << 10 * 60 * 60 << 14 * 60 * 60 << 18 * 60 * 60 << 22 * 60 * 60 << 26 * 60 * 60 << 30 * 60 * 60;
|
|
|
|
QHBoxLayout *centralLayout = new QHBoxLayout();
|
|
centralLayout->setSpacing(0);
|
|
|
|
setLayout(centralLayout);
|
|
|
|
m_pbZoom->setText(QLatin1String("+"));
|
|
m_pbZoom->setFixedSize(s_pbSize, s_pbSize);
|
|
|
|
m_pbUnzoom->setText(QLatin1String("-"));
|
|
m_pbUnzoom->setFixedSize(s_pbSize, s_pbSize);
|
|
|
|
// adding zoom levels (in seconds)
|
|
m_cbZoomLevel->addItem(tr("10 min"), 600); // zoom-in
|
|
m_cbZoomLevel->addItem(tr("30 min"), 1800); // ...
|
|
m_cbZoomLevel->addItem(tr("1 h"), 3600);
|
|
m_cbZoomLevel->addItem(tr("2 h"), 7200);
|
|
m_cbZoomLevel->addItem(tr("4 h"), 14400); // ...
|
|
m_cbZoomLevel->addItem(tr("8 h"), 28800); // zoom-out
|
|
|
|
m_cbZoomLevel->setCurrentIndex(0);
|
|
//m_cbZoomLevel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
|
m_cbZoomLevel->setFixedHeight(s_pbSize);
|
|
m_zoomFactorPoints = m_cbZoomLevel->currentData().toLongLong();
|
|
|
|
connect(m_cbZoomLevel, &QComboBox::currentTextChanged,
|
|
this, &TimelineIndicator::afterZoom);
|
|
connect(m_pbZoom, &QPushButton::clicked,
|
|
this, &TimelineIndicator::slotZoom);
|
|
connect(m_pbUnzoom, &QPushButton::clicked,
|
|
this, &TimelineIndicator::slotUnzoom);
|
|
connect(this, &TimelineIndicator::signalAddLine,
|
|
this, &TimelineIndicator::slotAddLine);
|
|
connect(this, &TimelineIndicator::signalResetLineList,
|
|
this, &TimelineIndicator::slotResetLineList);
|
|
|
|
centralLayout->addWidget(m_pbZoom, 0, Qt::AlignLeft | Qt::AlignTop);
|
|
centralLayout->addWidget(m_pbUnzoom, 0, Qt::AlignLeft | Qt::AlignTop);
|
|
centralLayout->addWidget(m_cbZoomLevel, 1, Qt::AlignLeft | Qt::AlignTop);
|
|
|
|
// can fontmetrics change while runtime? (configuration, xml, display manager, etc.)
|
|
// no better solution: this routine only works good with raster fonts
|
|
|
|
// measure font to perfect fit at indicator's end
|
|
QFontMetrics fm(QApplication::font());
|
|
m_currentTextHeight = fm.height();
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
|
|
m_currentTextWidth = fm.width(DTG::toString(QDateTime::currentDateTimeUtc(), DTG::Short)); // also example-text-width (with 01.01.1970) - enough to see how much a general datetime's size is
|
|
#else
|
|
m_currentTextWidth = fm.horizontalAdvance(DTG::toString(QDateTime::currentDateTimeUtc(), DTG::Short)); // also example-text-width (with 01.01.1970) - enough to see how much a general datetime's size is
|
|
#endif
|
|
|
|
dbg_print(m_currentTextWidth);
|
|
|
|
// waiting for section resize to set paintingRange
|
|
}
|
|
|
|
TimelineIndicator::~TimelineIndicator()
|
|
{
|
|
//delete m_settings;
|
|
}
|
|
|
|
void TimelineIndicator::afterZoom() // called when cb changes (slotZoom/Unzoom)
|
|
{
|
|
m_zoomFactorPoints = m_cbZoomLevel->currentData().toLongLong() * 1000; // to msecs
|
|
emit signalSetZoomFactor(m_zoomFactorPoints);
|
|
update(); // to make changes visually visible
|
|
}
|
|
|
|
|
|
void TimelineIndicator::slotZoom()
|
|
{
|
|
int pos = m_cbZoomLevel->currentIndex();
|
|
|
|
if (pos == 0) // can't go lower in cb
|
|
return;
|
|
|
|
m_cbZoomLevel->setCurrentIndex(pos - 1);
|
|
emit signalZoomChanged();
|
|
}
|
|
|
|
void TimelineIndicator::slotUnzoom()
|
|
{
|
|
int count = m_cbZoomLevel->count();
|
|
int pos = m_cbZoomLevel->currentIndex();
|
|
|
|
if (pos >= count - 1) // can't go higher in cb
|
|
return;
|
|
|
|
m_cbZoomLevel->setCurrentIndex(pos + 1);
|
|
emit signalZoomChanged();
|
|
}
|
|
|
|
enum highestUnit
|
|
{
|
|
HU_INVALID,
|
|
//HU_WEEKS,
|
|
HU_DAYS,
|
|
HU_HOURS,
|
|
HU_MINUTES,
|
|
HU_SECONDS
|
|
};
|
|
|
|
void TimelineIndicator::refreshData()
|
|
{
|
|
#ifdef VERBOSE
|
|
qWarning() << "<refreshData>";
|
|
#endif
|
|
|
|
//WARNING_LOG(m_logger) << "refreshData" ;
|
|
|
|
emit signalResetLineList(); // reset line list in view
|
|
|
|
#ifdef VERBOSE
|
|
qWarning() << "section x" << m_sectionPos << "length" << m_sectionSize;
|
|
#endif
|
|
|
|
/*/
|
|
*
|
|
* Formel zur Berechnung von Text-Abstaenden und zur Entscheidung zu Zeit-Einheiten
|
|
*
|
|
* beispiel: minimum TimelineView= 09.10.14 12:35
|
|
* 1. groesste Einheit vom Abstand VON TEXTWIDTH UND TOLERANCEPIXELS ermitteln (bei 10.11.14 11:04 und 10.11.14 23:49
|
|
* Unterschied von 0 Years, 0 Months, 0 Days, 12 Hours, 45 Minuten, 0 Sekunden) >> Stunden (da: 12:45 Stunden Unterschied)
|
|
* 2. TextWidth + TolerancePixels zu Time umwandeln (wird bei EventTools mit adjusted Min & adjusted Max automatisch gemacht)
|
|
* 3. Ergebnis als QDateTime
|
|
* 4. groessere Zahl [fuer groesste Einheit - findet die Funktion aber auch ohne highestUnit] suchen (bei 1:44 Stunden ist die groessere
|
|
* Zahl 2 Stunden [z.B. fuer 8 Stunden => 12 und 12 Stunden => 24])
|
|
* 5. von Minimum alle kleineren Einheiten & sich selbst abziehen >> 09.10.14 00:00
|
|
* 6. dem Minimum nun die groessere Zahl einsetzen >> 09.10.14 02:00
|
|
* 7. ab Minimum zeichnen, alle higherValue mal, bis zum Ende
|
|
*
|
|
/*/
|
|
|
|
//QRect paintRect(m_paintingRange.x(), 0, m_paintingRange.y(), 0); // 2x'0' = dummy (no need to draw, so this can be ignored)
|
|
|
|
// 1.:
|
|
double abstand = m_sectionPos + m_currentTextWidth + s_TimeTextDistanceTolerancePixels; // m_paintingRange.x() because the graphics starts there, everything lower than m_paintingRange.x() is a negative value !!1
|
|
dbg_print(abstand);
|
|
|
|
// 2.:
|
|
|
|
qint64 eventTimeOfAbstand = EventTools::getEventTime(abstand);
|
|
dbg_print(eventTimeOfAbstand);
|
|
qint64 adjustedMin = EventTools::getAdjustedMin();
|
|
dbg_print(adjustedMin);
|
|
qint64 textWidthTime = (eventTimeOfAbstand - adjustedMin);
|
|
|
|
if (textWidthTime < 0)
|
|
{
|
|
qWarning() << "TimelineIndicator: paintEvent: textWidthTime invalid! (" << textWidthTime << ")";
|
|
return;
|
|
}
|
|
|
|
qint64 eventTime = EventTools::getEventTime(abstand);
|
|
dbg_print(eventTime);
|
|
|
|
// 3.:
|
|
QDateTime textWidthDt = QDateTime::fromMSecsSinceEpoch(textWidthTime);
|
|
|
|
// 4.:
|
|
//int weeks = textWidthDt.date().weekNumber(); // 0
|
|
int days = textWidthDt.date().day() - 1; // 0
|
|
int hours = textWidthDt.time().hour() - 1; // 12
|
|
int minutes = textWidthDt.time().minute();//-1; // 45
|
|
int seconds = textWidthDt.time().second() - 1; // 59
|
|
|
|
highestUnit unit = HU_INVALID;
|
|
|
|
if (days == 0)
|
|
if (hours == 0)
|
|
if (minutes == 0)
|
|
if (seconds == 0)
|
|
{
|
|
#ifdef VERBOSE
|
|
qWarning() << "refreshData: no lower time unit available" ;
|
|
#endif
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// seconds
|
|
unit = HU_SECONDS;
|
|
}
|
|
else
|
|
{
|
|
// minutes
|
|
unit = HU_MINUTES;
|
|
}
|
|
else
|
|
{
|
|
// hours
|
|
unit = HU_HOURS;
|
|
}
|
|
else
|
|
{
|
|
// days
|
|
unit = HU_DAYS;
|
|
}
|
|
|
|
if (unit == HU_INVALID)
|
|
{
|
|
#ifdef VERBOSE
|
|
qWarning() << "unit == HU_INVALID";
|
|
#endif
|
|
return; // do not draw if it is not possible to draw something
|
|
}
|
|
|
|
int higherValue = 0;
|
|
for (int i = 0; i < m_timeArray.size(); i++) // lookup int list on which time would fit best
|
|
{
|
|
qint64 time = (m_timeArray[i] * 1000); // MSECS!
|
|
|
|
if (time >= textWidthTime) // 1:44 Stunden auf 2 Stunden
|
|
{
|
|
higherValue = time; // perfect fitting time found, return the value BEFORE this value (because this value is <= 0)
|
|
//WARNING_LOG(m_logger) << "refreshData: higherValue = " << higherValue ;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
continue; // so that next declarations are only called if no fitting value has been found
|
|
}
|
|
|
|
// warning never reaches this point!
|
|
// no value found:
|
|
#ifdef VERBOSE
|
|
qWarning() << "refreshData: no fitting highest unit found !" ;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
//warning other solution?
|
|
if (higherValue < 1)
|
|
{
|
|
//WARNING_LOG(m_logger) << "refreshData: no fitting highest unit found !" ;
|
|
return;
|
|
}
|
|
//dbg_print(higherValue);
|
|
|
|
|
|
// 5.:
|
|
//QDateTime minDt = QDateTime::fromMSecsSinceEpoch(EventTools::getTimelineMin()); // minTimeline, because we want to start from real minimum, so that the timeline can be smoothly scrolled in horizontal direction
|
|
QDateTime minDt = QDateTime::fromMSecsSinceEpoch(adjustedMin);
|
|
dbg_print(minDt);
|
|
dbg_print(unit);
|
|
|
|
switch (unit)
|
|
{
|
|
case HU_SECONDS:
|
|
{
|
|
minDt = minDt.addSecs(-(minDt.time().second())); // no seconds
|
|
#ifdef VERBOSE
|
|
qCritical() << "HU_SECONDS";
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
case HU_MINUTES:
|
|
{
|
|
minDt = minDt.addSecs(-(minDt.time().second())); // no seconds
|
|
minDt = minDt.addSecs(-(minDt.time().minute() * 60)); // no minutes
|
|
#ifdef VERBOSE
|
|
qCritical() << "HU_MINUTES";
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
case HU_HOURS:
|
|
{
|
|
minDt = minDt.addSecs(-(minDt.time().second())); // no seconds
|
|
minDt = minDt.addSecs(-(minDt.time().minute() * 60)); // no minutes
|
|
minDt = minDt.addSecs(-(minDt.time().hour() * 60 * 60)); // no hours
|
|
#ifdef VERBOSE
|
|
qCritical() << "HU_HOURS";
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
case HU_DAYS:
|
|
{
|
|
minDt = minDt.addSecs(-(minDt.time().second())); // no seconds
|
|
minDt = minDt.addSecs(-(minDt.time().minute() * 60)); // no minutes
|
|
minDt = minDt.addSecs(-(minDt.time().hour() * 60 * 60)); // no hours
|
|
minDt = minDt.addDays(-(minDt.date().day())); // no days
|
|
#ifdef VERBOSE
|
|
qCritical() << "HU_DAYS";
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
#ifdef VERBOSE
|
|
qWarning() << "unit / scaling is off da charz !";
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
dbg_print(minDt);
|
|
|
|
// 6.:
|
|
minDt = minDt.addMSecs(-higherValue); // *MINUS*, so that the first text can scroll into the widget smoothly (because minTimeline is exactly on the left of section 1
|
|
|
|
dbg_print(minDt);
|
|
|
|
// 7.:
|
|
qint64 maxTimelineView = EventTools::getAdjustedMax() + (higherValue /* because msecs */); // also add higher value to max, as min could be bigger than max
|
|
qint64 additional = textWidthTime / 2; // only the half of the text - because there is m_currentTextWidth/2 more space after max!
|
|
maxTimelineView += additional;
|
|
|
|
qint64 minTimeline = minDt.toMSecsSinceEpoch();
|
|
|
|
dbg_print(minTimeline);
|
|
dbg_print(maxTimelineView);
|
|
dbg_print(higherValue);
|
|
|
|
qint64 steps = (maxTimelineView - minTimeline) / higherValue;
|
|
if (steps < 0)
|
|
steps = steps * (-1);
|
|
|
|
if (steps > 60000 || steps < 1)
|
|
{
|
|
ERROR_LOG(m_logger) << "More than 60000 ("<<steps<<") or less than 1 lines to draw. Loop will exit after 60000 lines.";
|
|
}
|
|
|
|
if (steps < 1)
|
|
steps = 2; // draw at least 2 lines instead of nothing - otherwise if there are x events at the same time, then no event would be visible
|
|
|
|
uint16_t currentStep = 0;
|
|
|
|
dbg_print(steps);
|
|
|
|
//warning calculation failure anywhere, because some time texts are sooner hidden than others
|
|
for (qint64 indicatorTime = minTimeline;
|
|
indicatorTime < maxTimelineView + 1 /* HACK: always draw at least 1 time */;
|
|
indicatorTime += higherValue) // start at minimal position (on view, in time) and until the
|
|
// width of the view (in time) and go standardAddTime-Steps
|
|
{
|
|
if (currentStep > 60000)
|
|
break;
|
|
|
|
currentStep++;
|
|
|
|
int pos = EventTools::getEventPosition(indicatorTime);
|
|
dbg_print(indicatorTime);
|
|
|
|
if (pos + (m_currentTextWidth / 4) + 1 /* tol */ < m_sectionPos)
|
|
{
|
|
#ifdef VERBOSE
|
|
qCritical() << "Indicator: pos" << pos << "invisible #1";
|
|
#endif
|
|
continue;
|
|
}
|
|
else
|
|
if (pos - (m_currentTextWidth /*/ 2*/) > (m_sectionPos + m_sectionSize)) // do not draw invisible texts
|
|
{
|
|
#ifdef VERBOSE
|
|
qCritical() << "Indicator: pos" << pos << "invisible #2";
|
|
#endif
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
#ifdef VERBOSE
|
|
qCritical() << "Indicator: OKAY!! pos" << pos << "visible";
|
|
#endif
|
|
}
|
|
|
|
|
|
//WARNING_LOG(m_logger) << "refreshData: signalAddLine: pos=" << pos << " i=" << i ;
|
|
emit signalAddLine(pos, indicatorTime); // add a new line to the view (pos, time)
|
|
}
|
|
|
|
dbg_print(currentStep);
|
|
|
|
QFontMetrics fm(QApplication::font());
|
|
m_currentTextHeight = fm.height();
|
|
|
|
update();
|
|
}
|
|
|
|
void TimelineIndicator::paintEvent(QPaintEvent *)
|
|
{
|
|
//qWarning() << "<paint>";
|
|
|
|
QPainter painter(this);
|
|
painter.save();
|
|
painter.setClipping(true);
|
|
painter.setClipRect(QRect(x() - 8 + m_sectionPos, 0, m_sectionSize, height()));
|
|
|
|
#ifdef VERBOSE
|
|
int textY = (height() - m_currentTextHeight - 5);
|
|
#endif
|
|
|
|
int h = height();
|
|
|
|
for (auto i = m_lineMap.constBegin(); i != m_lineMap.constEnd(); ++i)
|
|
{
|
|
//QPair<double, qint64> pair = m_lineList.at(i);
|
|
|
|
int pos = i.key(); //pair.first + 1; // (+1) unknown offset!
|
|
//painter.setPen(QPen(QBrush(QGuiApplication::palette().button()), 1));
|
|
|
|
painter.setPen(QPen(QColor("black"), 1.0));
|
|
painter.drawLine(pos, m_currentTextHeight, pos, h); // draw line for text
|
|
|
|
int centeredPos = pos - (m_currentTextWidth / 2);
|
|
qint64 time = (*i); // pair.second;
|
|
|
|
//painter.setPen(QPen(QBrush(QGuiApplication::palette().brightText()), 1));
|
|
|
|
//painter.setPen(QPen(QColor("black"), 1));
|
|
painter.drawText(centeredPos, (m_currentTextHeight / 2) + 4,
|
|
DTG::toStringMSec(time, DTG::Short)); // draw text
|
|
|
|
#ifdef VERBOSE
|
|
WARNING_LOG(m_logger) << "paintEvent: actual drawing position: x=" << centeredPos << " y=" << textY;
|
|
#endif
|
|
}
|
|
|
|
painter.restore();
|
|
|
|
//qWarning() << "</paint>";
|
|
}
|
|
|
|
void TimelineIndicator::slotSectionResized(int logicalIndex, int oldSize, int newSize)
|
|
{
|
|
(void)oldSize;
|
|
|
|
// is called from a QHeaderView which contains all sections ("actuator" & "events")
|
|
QHeaderView *header = qobject_cast<QHeaderView *>(QObject::sender());
|
|
|
|
if (!header)
|
|
return;
|
|
|
|
if (logicalIndex == 1) // event column
|
|
{
|
|
m_sectionPos = header->sectionPosition(1);
|
|
m_sectionSize = newSize;
|
|
//qWarning() << "setting painting range - m_sectionPos" << m_sectionPos << "m_sectionSize" << m_sectionSize;
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
void TimelineIndicator::slotAddLine(double posX, qint64 time) // add posX to a list, so that the view can draw them on itself
|
|
{
|
|
#ifdef VERBOSE
|
|
qCritical() << "slotAddLine: posX" << posX << "time" << time;
|
|
#endif
|
|
m_lineMap.insert(posX, time); //(QPair<double, qint64>(posX, time));
|
|
}
|
|
|
|
void TimelineIndicator::slotResetLineList()
|
|
{
|
|
m_lineMap.clear();
|
|
}
|
|
|
|
} // namespace LtsTimelineClassic
|