/** * @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 //#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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() << ""; #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 ("<