#include "timeline_header_view.h" #include "timeline_view.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace LtsTimeline { QPixmap getMinLimitIndicator() { QPixmap pixmap(11, 11); pixmap.fill(Qt::transparent); QPolygon triangle; triangle << QPoint(4, 5); triangle << QPoint(10, 0); triangle << QPoint(10, 10); triangle << QPoint(4, 5); QPainter painter(&pixmap); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); painter.setPen(QGuiApplication::palette().color(QPalette::Text)); painter.setBrush(QBrush(QGuiApplication::palette().color(QPalette::Text))); painter.drawLine(1, 0, 1, 10); painter.drawConvexPolygon(triangle); return pixmap; } QPixmap getMaxLimitIndicator() { QPixmap pixmap(11, 11); pixmap.fill(Qt::transparent); QPolygon triangle; triangle << QPoint(6, 5); triangle << QPoint(0, 0); triangle << QPoint(0, 10); triangle << QPoint(6, 5); QPainter painter(&pixmap); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); painter.setPen(QGuiApplication::palette().color(QPalette::Text)); painter.setBrush(QBrush(QGuiApplication::palette().color(QPalette::Text))); painter.drawLine(10, 0, 10, 10); painter.drawConvexPolygon(triangle); return pixmap; } const QColor TimelineHeaderView::backgroundColor() { return QGuiApplication::palette().color(QPalette::Base); } const QColor TimelineHeaderView::highlightColor() { return QRgb(0x43b0e0); } const int TimelineHeaderView::s_limitHeight = 22; TimelineHeaderView::TimelineHeaderView(QWidget *parent) : QHeaderView(Qt::Horizontal, parent) , m_mouseHandler(new TimelineHeaderViewMouseHandler(this)) , m_liveTimer(new QTimer(this)) , m_minLimitIndicator(getMinLimitIndicator()) , m_maxLimitIndicator(getMaxLimitIndicator()) { const QColor color = QGuiApplication::palette().color(QPalette::Inactive, QPalette::Highlight); setStyleSheet(QStringLiteral("QTreeView { background-color: %1 }").arg(color.name(QColor::HexRgb))); try { m_timelineView = qobject_cast(parent); updateSection(Section::eActuator); updateSection(Section::eItems); if (m_timelineView) { connect(m_timelineView, &TimelineView::currentTimeChanged, this, &TimelineHeaderView::onUpdateTicks); connect(m_timelineView, &TimelineView::rangeChanged, this, &TimelineHeaderView::onUpdateTicks); connect(m_timelineView, &TimelineView::zoomChanged, this, &TimelineHeaderView::onUpdateTicks); } else qWarning() << "WARNING: main functionalities are inoperable without a TimelineView as parent."; } catch (...) { qWarning() << "WARNING: main functionalities are inoperable without a TimelineView as parent."; } setStretchLastSection(true); setMinimumSectionSize(200); setFixedHeight(50); connect(this, &TimelineHeaderView::sectionResized, this, &TimelineHeaderView::onUpdateTicks); connect(m_liveTimer, &QTimer::timeout, this, &TimelineHeaderView::onLiveTimeout); m_liveIndicatorColor = QGuiApplication::palette().color(QPalette::Link); } TimelineHeaderView::~TimelineHeaderView() { m_timelineView = nullptr; } void TimelineHeaderView::setParent(QWidget *) { qWarning() << QStringLiteral("WARNING: setting parent after construction is not allowed."); } void TimelineHeaderView::setTrackLabel(const QString &trackLabel) { m_trackLabel = trackLabel; updateSection(Section::eActuator); } void TimelineHeaderView::setDateTimeFormat(DateTimeFormat dateTimeFormat) { if (!m_isDateTimeFormatChangable) return; m_dateTimeFormat = dateTimeFormat; for (Tick &majorTick : m_majorTicks) majorTick.label = DateTime::toString(majorTick.time, m_dateTimeFormat); for (Tick &mediumTick : m_mediumTicks) mediumTick.label = DateTime::toString(mediumTick.time, m_dateTimeFormat); for (Tick &minorTick : m_minorTicks) minorTick.label = DateTime::toString(minorTick.time, m_dateTimeFormat); updateSection(Section::eItems); } void TimelineHeaderView::setIndicatorScalingFactor(double factor) { if (factor == m_indicatorScalingFactor || factor <= 0.) return; m_indicatorScalingFactor = factor; viewport()->update(); } void TimelineHeaderView::setIndicatorTime(qint64 time) { if (time != -1 && m_timelineView) if (m_timelineView->timelineModel()) if (m_timelineView->timelineModel()->isSnapEnabled()) time = m_timelineView->timelineModel()->snapTime(time); if (time == m_indicatorTime) return; m_indicatorTime = time; viewport()->update(); emit indicatorTimeChanged(m_indicatorTime); } void TimelineHeaderView::setIndicatorPosition(int position) { setIndicatorTime(toTime(position)); } void TimelineHeaderView::setIndicatorVisible(bool isIndicatorVisible) { if (isIndicatorVisible == m_isIndicatorVisible) return; m_isIndicatorVisible = isIndicatorVisible; m_timelineView->viewport()->update(); emit indicatorVisibilityChanged(m_isIndicatorVisible); } void TimelineHeaderView::setLiveIndicatorScalingFactor(double factor) { if (factor == m_liveIndicatorScalingFactor || factor <= 0.) return; m_liveIndicatorScalingFactor = factor; viewport()->update(); } void TimelineHeaderView::setLiveIndicatorColor(const QColor &color) { if (color == m_liveIndicatorColor) return; m_liveIndicatorColor = color; viewport()->update(); } void TimelineHeaderView::setLiveIndicatorTime(qint64 time) { if (time != -1 && m_timelineView) if (m_timelineView->timelineModel()) if (m_timelineView->timelineModel()->isSnapEnabled()) time = m_timelineView->timelineModel()->snapTime(time); if (time == m_liveIndicatorTime) return; m_liveIndicatorTime = time; viewport()->update(); emit liveIndicatorTimeChanged(m_liveIndicatorTime); } void TimelineHeaderView::setLiveIndicatorPosition(int position) { setLiveIndicatorTime(toTime(position)); } void TimelineHeaderView::setLiveIndicatorVisible(bool isLiveIndicatorVisible) { if (isLiveIndicatorVisible == m_isLiveIndicatorVisible) return; m_isLiveIndicatorVisible = isLiveIndicatorVisible; viewport()->update(); m_timelineView->viewport()->update(); emit liveIndicatorVisibilityChanged(m_isLiveIndicatorVisible); } void TimelineHeaderView::startLiveTimer(qint64 startTime, int interval) { if (interval <= 0) { qCritical() << LOGFUNCTION << "invalid time interval:" << interval; return; } if (m_liveTimer->isActive()) { qWarning() << LOGFUNCTION << "timer is already active - restarting"; m_liveTimer->stop(); } if (startTime < 0) startTime = m_timelineView->currentTime(); m_liveTimerInterval = interval; setLiveIndicatorTime(startTime); setLiveIndicatorVisible(true); m_liveTimer->setSingleShot(false); m_liveTimer->start(m_liveTimerInterval); } void TimelineHeaderView::stopLiveTimer() { if (!m_liveTimer->isActive()) return; qDebug() << LOGFUNCTION; m_liveTimer->stop(); m_liveIndicatorTime = -1; setLiveIndicatorVisible(false); } void TimelineHeaderView::setHoverTime(qint64 time) { if (!m_isHoverVisible) return; if (time < m_timelineView->currentTime() || m_timelineView->currentTime() + m_timelineView->zoomFactor() < time) time = -1; m_hoverTime = time; m_hoverPosition = toPixel(m_hoverTime); updateSection(Section::eItems); emit hoverPositionChanged(m_hoverPosition); } void TimelineHeaderView::setHoverPosition(int position) { if (!m_isHoverVisible) return; m_hoverPosition = position; m_hoverTime = toTime(m_hoverPosition); updateSection(Section::eItems); emit hoverPositionChanged(m_hoverPosition); } void TimelineHeaderView::setHoverVisible(bool visible) { if (visible == m_isHoverVisible) return; m_isHoverVisible = visible; updateSection(Section::eItems); m_timelineView->viewport()->update(); emit hoverVisibilityChanged(m_isHoverVisible); } void TimelineHeaderView::setHoverTimeVisible(bool visible) { if (visible == m_isHoverTimeVisible) return; m_isHoverTimeVisible = visible; updateSection(Section::eItems); m_timelineView->viewport()->update(); } void TimelineHeaderView::setHoverDateTimeFormat(DateTimeFormat format) { if (format == m_hoverDateTimeFormat) return; m_hoverDateTimeFormat = format; updateSection(Section::eItems); m_timelineView->viewport()->update(); } void TimelineHeaderView::setLimitsVisible(bool visible) { if (visible == m_isLimitsVisible) return; m_isLimitsVisible = visible; if (m_isLimitsVisible) setFixedHeight(70); else setFixedHeight(50); viewport()->update(); } void TimelineHeaderView::setLimitsDateTimeFormat(DateTimeFormat format) { if (format == m_limitsDateTimeFormat) return; m_limitsDateTimeFormat = format; viewport()->update(); } int TimelineHeaderView::toPixel(qint64 time) const { if (time == -1) return -1; return static_cast(double(time - m_timelineView->currentTime()) / m_timelineView->zoomFactor() * (geometry().width() - sectionPosition(Section::eItems)) + sectionPosition(Section::eItems)); } qint64 TimelineHeaderView::toTime(int pixel) const { return m_timelineView->currentTime() + (m_timelineView->zoomFactor() * (double(pixel - sectionPosition(Section::eItems)) / (geometry().width() - sectionPosition(Section::eItems)))); } void TimelineHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const { if (logicalIndex == Section::eActuator) { painter->save(); painter->setRenderHints(QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing); painter->setPen(backgroundColor()); painter->setBrush(QBrush(backgroundColor())); painter->drawRect(rect); painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 2)); painter->drawLine(rect.topRight(), QPoint(rect.right() - rect.height() / 2, rect.top() + rect.height() / 2)); painter->drawLine(QPoint(rect.right() - rect.height() / 2, rect.top() + rect.height() / 2), rect.bottomRight()); painter->setFont(m_font); painter->drawText(rect, Qt::AlignCenter, m_trackLabel); painter->restore(); return; } painter->save(); painter->setClipping(true); painter->setClipRect(rect); painter->setFont(m_font); painter->setRenderHints(QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing); painter->setPen(backgroundColor()); painter->setBrush(backgroundColor()); painter->drawRect(rect); int bottom = m_isLimitsVisible ? rect.bottom() - s_limitHeight : rect.bottom(); int height = (bottom - rect.top()) / 2; for (const Tick &tick : qAsConst(m_majorTicks)) { painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 1)); painter->drawText(QRect(tick.pos - 160 / 2, rect.top(), 160, height), Qt::AlignCenter, tick.label); if (m_isLimitsVisible) painter->drawLine(tick.pos, bottom, tick.pos, rect.bottom()); painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 1)); painter->drawLine(tick.pos, rect.top() + height, tick.pos, bottom); } painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 1)); for (const Tick &tick : qAsConst(m_mediumTicks)) painter->drawLine(tick.pos, bottom - static_cast(double(height) * 2 / 3), tick.pos, bottom); for (const Tick &tick : qAsConst(m_minorTicks)) painter->drawLine(tick.pos, bottom - static_cast(double(height) / 3), tick.pos, bottom); if (m_isLimitsVisible) { QString minLimitString = DateTime::toString(m_timelineView->m_currentTime, m_limitsDateTimeFormat); QString maxLimitString = DateTime::toString(m_timelineView->m_currentTime + m_timelineView->m_zoomFactor, m_limitsDateTimeFormat); int minLimitWidth = painter->fontMetrics().horizontalAdvance(minLimitString); int maxLimitWidth = painter->fontMetrics().horizontalAdvance(maxLimitString); painter->setPen(backgroundColor()); painter->setBrush(QBrush(backgroundColor())); painter->drawRect(rect.left() + 4, geometry().bottom() - s_limitHeight, minLimitWidth + 11, 19); painter->drawRect(rect.right() - maxLimitWidth - 18, geometry().bottom() - s_limitHeight, maxLimitWidth + 11, 19); painter->setPen(QGuiApplication::palette().color(QPalette::Text)); painter->drawPixmap(rect.left() + 4, geometry().bottom() - 15, 11, 11, m_minLimitIndicator); painter->drawPixmap(rect.right() - 15, geometry().bottom() - 15, 11, 11, m_maxLimitIndicator); painter->drawText( QRect(rect.left() + 18, geometry().bottom() - s_limitHeight, minLimitWidth, s_limitHeight - 1), Qt::AlignLeft, minLimitString ); painter->drawText( QRect(rect.right() - maxLimitWidth - 18, geometry().bottom() - s_limitHeight, maxLimitWidth, s_limitHeight - 1), Qt::AlignRight, maxLimitString ); } if (m_isHoverVisible) { if (m_hoverPosition != -1) { painter->setPen(highlightColor()); painter->drawLine(m_hoverPosition, rect.top(), m_hoverPosition, rect.bottom()); if (m_isHoverTimeVisible) { QString dateTimeString = DateTime::toString(m_hoverTime, m_hoverDateTimeFormat); const int textWidth = painter->fontMetrics().horizontalAdvance(dateTimeString) + 2; const int textHeight = m_isLimitsVisible ? s_limitHeight - 1 : 19; QRect dateTimeRect(m_hoverPosition, geometry().bottom() - textHeight, textWidth, textHeight - 1); if (dateTimeRect.right() >= geometry().right()) { dateTimeRect.setLeft(m_hoverPosition - textWidth); dateTimeRect.setWidth(textWidth); } painter->setBrush(QBrush(backgroundColor())); painter->drawRect(dateTimeRect); painter->drawText(dateTimeRect, Qt::AlignCenter, dateTimeString); } } } if (m_isIndicatorVisible) { int pos = toPixel(m_indicatorTime); if (m_indicatorTime != -1 && (m_timelineView->currentTime() <= m_indicatorTime && m_indicatorTime <= m_timelineView->currentTime() + m_timelineView->zoomFactor())) { painter->setPen(highlightColor()); painter->setBrush(QBrush(highlightColor())); int sideLength = double(m_isLimitsVisible ? rect.height() - height : rect.height()) * m_indicatorScalingFactor; int top = bottom - static_cast(0.866 * sideLength); QPolygon polygon; polygon << QPoint(pos, bottom); polygon << QPoint(pos + sideLength / 2, top); polygon << QPoint(pos - sideLength / 2, top); polygon << QPoint(pos, bottom); QString dateTimeString = DateTime::toString(m_indicatorTime, m_hoverDateTimeFormat); const int textWidth = painter->fontMetrics().horizontalAdvance(dateTimeString); painter->setClipRect(rect.adjusted(-textWidth / 2, 0, 0, 0)); painter->drawPolygon(polygon); if (m_isLimitsVisible) painter->drawLine(pos, bottom, pos, rect.bottom()); QRect dateTimeRect(pos - textWidth / 2, geometry().top(), textWidth + 2, 19); painter->setBrush(QBrush(backgroundColor())); painter->drawRect(dateTimeRect); painter->drawText(dateTimeRect, Qt::AlignCenter, dateTimeString); painter->drawLine(pos, geometry().top() + 19, pos, bottom); } } if (m_isLiveIndicatorVisible) { int pos = toPixel(m_liveIndicatorTime); if (pos != -1 && (m_timelineView->currentTime() <= m_liveIndicatorTime && m_liveIndicatorTime <= m_timelineView->currentTime() + m_timelineView->zoomFactor())) { painter->setPen(m_liveIndicatorColor); painter->setBrush(QBrush(m_liveIndicatorColor)); int sideLength = double(m_isLimitsVisible ? rect.height() - height : rect.height()) * m_liveIndicatorScalingFactor; int top = bottom - static_cast(0.866 * sideLength); QPolygon polygon; polygon << QPoint(pos, bottom); polygon << QPoint(pos + sideLength / 2, top); polygon << QPoint(pos - sideLength / 2, top); polygon << QPoint(pos, bottom); QString dateTimeString = DateTime::toString(m_liveIndicatorTime, m_hoverDateTimeFormat); const int textWidth = painter->fontMetrics().horizontalAdvance(dateTimeString); painter->setClipRect(rect.adjusted(-textWidth / 2, 0, 0, 0)); painter->drawPolygon(polygon); QRect dateTimeRect(pos - textWidth / 2, geometry().top(), textWidth + 2, 19); if (dateTimeRect.right() > geometry().right() - 20) dateTimeRect.adjust(-22, 0, -22, 0); painter->setBrush(QBrush(backgroundColor())); painter->drawRect(dateTimeRect); painter->drawText(dateTimeRect, Qt::AlignCenter, dateTimeString); } } painter->restore(); } void TimelineHeaderView::updateMajorTicks() { m_majorTicks.clear(); if (m_timelineView->duration() <= 0) return; const int interval = Zoom::majorInterval(m_timelineView->zoomFactor()); if (interval == -1) return; qint64 time = m_timelineView->currentTime(); const int mod = time % interval; if (mod) time += interval - mod; const qint64 end = m_timelineView->currentTime() + m_timelineView->zoomFactor(); while (time <= end) { QString label = DateTime::toString(time, m_dateTimeFormat); Tick tick(toPixel(time), time, label); m_majorTicks.append(tick); time += interval; } } void TimelineHeaderView::updateMediumTicks() { m_mediumTicks.clear(); if (m_timelineView->duration() <= 0) return; const int interval = Zoom::mediumInterval(m_timelineView->zoomFactor()); if (interval == -1) return; qint64 time = m_timelineView->currentTime(); const int mod = time % interval; if (mod) time += interval - mod; const qint64 end = m_timelineView->currentTime() + m_timelineView->zoomFactor(); while (time <= end) { Tick tick(time); auto it = std::find_if(m_majorTicks.begin(), m_majorTicks.end(), [tick](const Tick &other) { return tick.time == other.time; }); if (it != m_majorTicks.end()) { time += interval; continue; } tick.pos = toPixel(tick.time); tick.label = DateTime::toString(tick.time, m_dateTimeFormat); m_mediumTicks.append(tick); time += interval; } } void TimelineHeaderView::updateMinorTicks() { m_minorTicks.clear(); if (m_timelineView->duration() <= 0) return; const int interval = Zoom::minorInterval(m_timelineView->zoomFactor()); if (interval == -1) return; qint64 time = m_timelineView->currentTime(); const int mod = time % interval; if (mod) time += interval - mod; const qint64 end = m_timelineView->currentTime() + m_timelineView->zoomFactor(); while (time <= end) { Tick tick(time); auto it = std::find_if(m_majorTicks.begin(), m_majorTicks.end(), [tick](const Tick &other) { return tick.time == other.time; }); if (it != m_majorTicks.end()) { time += interval; continue; } it = std::find_if(m_mediumTicks.begin(), m_mediumTicks.end(), [tick](const Tick &other) { return tick.time == other.time; }); if (it != m_mediumTicks.end()) { time += interval; continue; } tick.pos = toPixel(tick.time); tick.label = DateTime::toString(tick.time, m_dateTimeFormat); m_minorTicks.append(tick); time += interval; } } void TimelineHeaderView::onUpdateTicks() { updateMajorTicks(); updateMediumTicks(); updateMinorTicks(); if (m_isIndicatorVisible) setIndicatorTime(m_indicatorTime); if (m_isLiveIndicatorVisible) setLiveIndicatorTime(m_liveIndicatorTime); updateSection(Section::eItems); emit ticksChanged(m_majorTicks); } void TimelineHeaderView::onLiveTimeout() { setLiveIndicatorTime(m_liveIndicatorTime + m_liveTimerInterval); } }