Files
qt6-migration-pipeline/Lts/Common/LtsTimeline/timeline_view.cpp
T

673 lines
16 KiB
C++
Executable File

#include "timeline_view.h"
#include "item_delegate.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QAbstractProxyModel>
#include <QApplication>
#include <QBrush>
#include <QDebug>
#include <QGuiApplication>
#include <QHeaderView>
#include <QHelpEvent>
#include <QModelIndex>
#include <QPainter>
#include <QPen>
#include <QRect>
namespace LtsTimeline
{
const int TimelineView::s_textHeight = 20;
TimelineView::TimelineView(QWidget *parent)
: QTreeView(parent)
, m_header(new TimelineHeaderView(this))
, m_dragHandler(new TimelineViewDragHandler(this))
, m_resizeHandler(new TimelineViewResizeHandler(this))
, m_selectHandler(new TimelineViewSelectHandler(this))
, m_trackHandler(new TimelineViewTrackHandler(this))
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.TimelineView"))
{
setAlternatingRowColors(true);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSelectionBehavior(QAbstractItemView::SelectItems);
setSelectionMode(QAbstractItemView::NoSelection);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setHeader(m_header);
setAcceptDrops(true);
}
void TimelineView::setHeader(QHeaderView *header)
{
try
{
if (m_header && m_header != header)
delete m_header;
m_header = qobject_cast<TimelineHeaderView *>(header);
QTreeView::setHeader(m_header);
connect(m_header, &TimelineHeaderView::indicatorTimeChanged, this, [this]()
{
viewport()->update();
});
connect(m_header, &TimelineHeaderView::indicatorVisibilityChanged, this, [this]()
{
viewport()->update();
});
connect(m_header, &TimelineHeaderView::hoverPositionChanged, this, [this]()
{
if (m_model != nullptr)
emit m_model->layoutChanged();
});
connect(m_header, &TimelineHeaderView::hoverVisibilityChanged, this, [this]()
{
viewport()->update();
});
}
catch (...)
{
ERROR_LOG(m_logger) << "header view should be of type TimelineHeaderView";
}
}
void TimelineView::setModel(QAbstractItemModel *model)
{
m_model = model;
QTreeView::setModel(model);
setMouseTracking(true);
m_timelineModel = qobject_cast<TimelineModel *>(model);
if (m_timelineModel != nullptr)
return;
m_proxyModel = qobject_cast<QAbstractProxyModel *>(model);
if (m_proxyModel != nullptr)
m_timelineModel = qobject_cast<TimelineModel *>(m_proxyModel->sourceModel());
if (m_timelineModel == nullptr)
qWarning() << "TimelineSelectHandler:c_tor: model is not a QAbstractItemModel!";
Q_ASSERT(m_timelineModel != nullptr);
}
void TimelineView::setProxyModel(QAbstractProxyModel *proxy) noexcept(false)
{
if (!qobject_cast<TimelineModel *>(proxy->sourceModel()))
throw QStringLiteral("source model cannot be cast to TimelineModel");
m_model = qobject_cast<TimelineModel *>(proxy->sourceModel());
m_proxyModel = proxy;
QTreeView::setModel(m_proxyModel);
}
void TimelineView::setItemDelegate(QAbstractItemDelegate *delegate)
{
try
{
m_delegate = static_cast<ItemDelegate *>(delegate);
QTreeView::setItemDelegate(m_delegate);
}
catch (...)
{
ERROR_LOG(m_logger) << "item delegate for TimelineView must be derived from ItemDelegate";
}
}
void TimelineView::setZoomFactor(ZoomFactor zoomFactor)
{
if (zoomFactor == m_zoomFactor)
return;
if (m_minZoomFactor > zoomFactor)
{
setZoomFactor(m_minZoomFactor);
return;
}
if (m_upperZoomFactor < zoomFactor)
{
setZoomFactor(m_upperZoomFactor);
return;
}
m_zoomFactor = zoomFactor;
emit zoomChanged(m_zoomFactor);
if (m_currentTime + m_zoomFactor > endTime())
setCurrentTime(endTime() - m_zoomFactor);
else
viewport()->update();
}
void TimelineView::setMinimumZoomFactor(ZoomFactor zoomFactor)
{
if (zoomFactor == m_minZoomFactor)
return;
m_minZoomFactor = zoomFactor;
if (m_zoomFactor < m_minZoomFactor)
setZoomFactor(m_minZoomFactor);
}
void TimelineView::setMaximumZoomFactor(ZoomFactor zoomFactor)
{
if (zoomFactor == m_maxZoomFactor)
return;
m_maxZoomFactor = zoomFactor;
if (m_upperZoomFactor > m_maxZoomFactor)
m_upperZoomFactor = m_maxZoomFactor;
if (m_zoomFactor > m_maxZoomFactor)
setZoomFactor(m_maxZoomFactor);
}
void TimelineView::setStartTime(qint64 time)
{
if (time == m_startTime || time < 0)
return;
m_startTime = time;
if (m_currentTime < m_startTime)
setCurrentTime(m_startTime);
if (m_duration > 0)
if (m_currentTime > endTime() - m_zoomFactor)
setCurrentTime(std::max(endTime() - m_zoomFactor, m_startTime));
emit rangeChanged(m_startTime, endTime());
}
void TimelineView::setCurrentTime(qint64 time)
{
if (time == m_currentTime)
return;
if (time < m_startTime)
time = m_startTime;
if (m_duration > 0)
if (time > endTime() - m_zoomFactor)
time = std::max(endTime() - m_zoomFactor, m_startTime);
if (time == m_currentTime)
return;
m_currentTime = time;
emit currentTimeChanged(m_currentTime);
viewport()->update();
}
void TimelineView::setEndTime(qint64 time)
{
if (time == m_startTime + m_duration)
return;
if (time - m_startTime < m_minZoomFactor)
time = m_startTime + m_minZoomFactor;
setDuration(time - m_startTime);
}
void TimelineView::setDuration(int duration)
{
if (duration == m_duration)
return;
if (duration <= m_minZoomFactor)
duration = m_minZoomFactor;
m_duration = duration;
emit rangeChanged(m_startTime, endTime());
ZoomFactor upperZoomFactor = Zoom::floor(duration);
m_upperZoomFactor = std::min(upperZoomFactor, m_maxZoomFactor);
if (duration < m_zoomFactor)
setZoomFactor(m_upperZoomFactor);
if (m_currentTime > endTime())
setCurrentTime(endTime() - m_zoomFactor);
viewport()->update();
}
void TimelineView::setRange(qint64 start, qint64 end)
{
if (start == m_startTime && end - start == m_duration)
return;
if (start > end)
{
m_startTime = end;
m_duration = std::max(start - m_startTime, static_cast<qint64>(m_minZoomFactor));
}
else
{
m_startTime = start;
m_duration = std::max(end - m_startTime, static_cast<qint64>(m_minZoomFactor));
}
if (m_currentTime < m_startTime)
m_currentTime = m_startTime;
if (m_duration > 0)
if (m_currentTime > endTime() - m_zoomFactor)
m_currentTime = std::max(endTime() - m_zoomFactor, m_startTime);
ZoomFactor upperZoomFactor = Zoom::floor(m_duration);
m_upperZoomFactor = upperZoomFactor > m_maxZoomFactor ? m_maxZoomFactor : upperZoomFactor;
emit rangeChanged(m_startTime, endTime());
viewport()->update();
}
void TimelineView::setCursorTextVisible(bool visible)
{
m_isCursorTextVisible = visible;
viewport()->update();
}
void TimelineView::setCursorText(const QString &text)
{
m_cursorText = text;
viewport()->update();
}
int TimelineView::toPixel(qint64 time) const
{
return m_header ? m_header->toPixel(time) : -1;
}
qint64 TimelineView::toTime(int pixel) const
{
return m_header ? m_header->toTime(pixel) : -1;
}
qint64 TimelineView::toDuration(int pixel) const
{
if (!m_header)
return -1;
int pos = m_header->sectionPosition(Section::eItems);
return double(pixel - pos) / (geometry().width() - pos) * m_zoomFactor;
}
int TimelineView::fromDuration(qint64 time) const
{
if (!m_header)
return -1;
return (time - m_startTime) / m_zoomFactor * (geometry().width() - m_header->sectionPosition(Section::eItems));
}
void TimelineView::clearSelectedItems()
{
m_selectedItemIds.clear();
emit selectedItemsChanged(m_selectedItemIds);
viewport()->update();
}
void TimelineView::selectAllItems()
{
if (m_model == nullptr)
return;
m_selectedItemIds.clear();
m_selectedItemIds = m_timelineModel->itemIds();
emit selectedItemsChanged(m_selectedItemIds);
viewport()->update();
}
void TimelineView::selectItem(int itemId, bool reset)
{
if (reset)
m_selectedItemIds.clear();
else if (m_selectedItemIds.contains(itemId))
return;
m_selectedItemIds.append(itemId);
emit selectedItemsChanged(m_selectedItemIds);
viewport()->update();
}
void TimelineView::selectItems(const QVector<int> &itemIds, bool reset)
{
if (reset)
m_selectedItemIds.clear();
for (const int &itemId : qAsConst(itemIds))
if (!m_selectedItemIds.contains(itemId))
m_selectedItemIds.append(itemId);
emit selectedItemsChanged(m_selectedItemIds);
viewport()->update();
}
void TimelineView::unselectItem(int itemId)
{
const int iItem = m_selectedItemIds.indexOf(itemId);
if (iItem == -1)
return;
m_selectedItemIds.remove(iItem);
emit selectedItemsChanged(m_selectedItemIds);
viewport()->update();
}
bool TimelineView::isCursorWithinRangeX(int x) const
{
if (m_header)
return m_header->sectionPosition(Section::eItems) < x && x < geometry().right();
return false;
}
bool TimelineView::isCursorWithinRangeY(int y) const
{
return 0 < y && y < geometry().bottom();
}
void TimelineView::dragEnterEvent(QDragEnterEvent *event)
{
if (m_dragHandler)
m_dragHandler->dragEnterEvent(event);
}
void TimelineView::dragMoveEvent(QDragMoveEvent *event)
{
if (m_dragHandler)
m_dragHandler->dragMoveEvent(event);
}
void TimelineView::dragLeaveEvent(QDragLeaveEvent *event)
{
if (m_dragHandler)
m_dragHandler->dragLeaveEvent(event);
}
void TimelineView::dropEvent(QDropEvent *event)
{
if (m_dragHandler)
m_dragHandler->dropEvent(event);
}
void TimelineView::mouseDoubleClickEvent(QMouseEvent *event)
{
if (m_trackHandler)
m_trackHandler->mouseDoubleClickEvent(event);
if (m_selectHandler)
m_selectHandler->mouseDoubleClickEvent(event);
}
void TimelineView::mouseMoveEvent(QMouseEvent *event)
{
m_cursorPosition = event->pos();
const QModelIndex index(indexAt(event->pos()));
if (m_header)
{
m_header->setHoverPosition(isCursorWithinRange(event->pos()) ? event->pos().x() : -1);
if (m_header->mouseHandler()->isBusy())
{
m_header->mouseHandler()->mouseMoveEvent(event);
return;
}
if (cursor() == Qt::SizeHorCursor)
{
if (m_resizeHandler)
{
if (!m_resizeHandler->isActive())
setCursor(Qt::ArrowCursor);
}
else
setCursor(Qt::ArrowCursor);
}
}
if (!m_model)
return;
if (m_dragHandler && m_timelineModel->isEditable())
if (cursor().shape() == Qt::ClosedHandCursor || cursor().shape() == Qt::ForbiddenCursor)
if (!m_dragHandler->m_dragItemIds.isEmpty() || !m_selectedItemIds.isEmpty())
{
m_dragHandler->mouseMoveEvent(event);
return;
}
if (m_resizeHandler && m_timelineModel->isEditable())
if (cursor().shape() == Qt::SizeHorCursor)
if (m_resizeHandler->isActive())
{
m_resizeHandler->mouseMoveEvent(event);
return;
}
if (m_selectHandler)
m_selectHandler->mouseMoveEvent(event);
if (!index.isValid() || index.column() == Section::eActuator)
{
if (m_resizeHandler && m_timelineModel->isEditable())
if (!m_resizeHandler->isActive())
setCursor(QCursor(Qt::ArrowCursor));
event->ignore();
return;
}
if (!m_timelineModel->isEditable())
return;
try
{
Track &track = m_timelineModel->track(index.data().toInt());
Item &item = track.itemAt(toTime(event->pos().x()));
int startPos = toPixel(item.startTime());
int endPos = toPixel(item.startTime() + item.duration());
if (endPos - startPos < 15 || (startPos + 5 < event->pos().x() && event->pos().x() < endPos - 5))
setCursor(QCursor(Qt::ArrowCursor));
else
setCursor(QCursor(Qt::SizeHorCursor));
}
catch (...)
{
setCursor(QCursor(Qt::ArrowCursor));
}
}
void TimelineView::mousePressEvent(QMouseEvent *event)
{
if (!m_model)
return;
if (m_header)
if (m_header->mouseHandler()->isBusy())
{
m_header->mouseHandler()->mousePressEvent(event);
return;
}
const QModelIndex index = indexAt(event->pos());
if (index.column() == Section::eActuator)
{
QTreeView::mousePressEvent(event);
if (m_trackHandler)
m_trackHandler->mousePressEvent(event);
return;
}
if (m_timelineModel->isEditable() && cursor().shape() == Qt::SizeHorCursor)
{
if (m_resizeHandler)
m_resizeHandler->mousePressEvent(event);
}
else
{
if (m_selectHandler)
m_selectHandler->mousePressEvent(event);
if (m_dragHandler && m_timelineModel->isEditable())
{
m_dragHandler->mousePressEvent(event);
if (m_dragHandler->isActive())
return;
}
if (event->button() == Qt::RightButton)
emit customContextMenuRequested(event->pos());
}
}
void TimelineView::mouseReleaseEvent(QMouseEvent *event)
{
if (!m_model)
return;
if (m_header)
if (m_header->mouseHandler()->isBusy())
{
m_header->mouseHandler()->mouseReleaseEvent(event);
return;
}
if (m_trackHandler)
m_trackHandler->mouseReleaseEvent(event);
if (m_dragHandler && m_timelineModel->isEditable())
if (m_dragHandler->isActive())
{
m_dragHandler->mouseReleaseEvent(event);
viewport()->update();
return;
}
if (m_resizeHandler && m_timelineModel->isEditable())
if (m_resizeHandler->isActive())
{
m_resizeHandler->mouseReleaseEvent(event);
viewport()->update();
return;
}
if (m_selectHandler)
if (m_selectHandler->isActive() || !m_timelineModel->isEditable())
m_selectHandler->mouseReleaseEvent(event);
viewport()->update();
}
void TimelineView::keyPressEvent(QKeyEvent *event)
{
if (m_model == nullptr)
return;
if (m_timelineModel->isEditable())
{
if (m_resizeHandler)
if (m_resizeHandler->isActive())
return;
if (m_dragHandler)
m_dragHandler->keyPressEvent(event);
}
if (m_selectHandler)
m_selectHandler->keyPressEvent(event);
viewport()->update();
}
void TimelineView::wheelEvent(QWheelEvent *event)
{
if (event->pos().y() <= m_header->geometry().bottom())
m_header->wheelEvent(event);
else if (QGuiApplication::queryKeyboardModifiers() == Qt::ControlModifier)
{
if (event->angleDelta().y() < 0)
setZoomFactor(Zoom::increment(m_zoomFactor));
else
setZoomFactor(Zoom::decrement(m_zoomFactor));
}
else
QTreeView::wheelEvent(event);
}
void TimelineView::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QWidget *viewPort = viewport();
QPainter painter(viewPort);
painter.save();
QTreeView::paintEvent(event);
if (m_dragHandler)
{
ItemDelegate *delegate = qobject_cast<ItemDelegate *>(itemDelegate());
if (delegate)
delegate->paintDragItems(&painter);
}
painter.restore();
painter.save();
painter.setClipping(true);
if (m_selectHandler)
if (m_selectHandler->isRubberBandActive())
{
QColor color = TimelineHeaderView::highlightColor();
painter.setPen(color);
color.setAlpha(100);
painter.setBrush(QBrush(color));
painter.drawRect(m_selectHandler->m_rubberBand);
}
if (m_header->isIndicatorVisible() && m_header->indicatorPosition() != -1 &&
m_header->indicatorPosition() >= m_header->sectionPosition(Section::eItems))
{
painter.setPen(TimelineHeaderView::highlightColor());
painter.drawLine(m_header->indicatorPosition(), 0, m_header->indicatorPosition(), geometry().bottom());
}
if (m_header->isHoverVisible() && m_header->hoverPosition() != -1)
{
painter.setPen(TimelineHeaderView::highlightColor());
painter.drawLine(m_header->hoverPosition(), 0, m_header->hoverPosition(), geometry().bottom());
if (m_isCursorTextVisible && !m_cursorText.isEmpty())
{
painter.setBrush(QBrush(TimelineHeaderView::backgroundColor()));
QRect rect = QRect(
m_cursorPosition.x(), m_cursorPosition.y() - s_textHeight - 2,
painter.fontMetrics().horizontalAdvance(m_cursorText) + 2, s_textHeight + 2
);
painter.drawRect(rect);
painter.drawText(rect.adjusted(1, 1, -1, -1), Qt::AlignCenter, m_cursorText);
}
}
if (m_header->isLiveIndicatorVisible() && m_header->liveIndicatorTime() != -1 &&
m_currentTime <= m_header->liveIndicatorTime() && m_header->liveIndicatorTime() <= m_currentTime + m_zoomFactor)
{
painter.setPen(m_header->liveIndicatorColor());
painter.drawLine(m_header->liveIndicatorPosition(), 0, m_header->liveIndicatorPosition(), geometry().bottom());
}
painter.restore();
}
}