673 lines
16 KiB
C++
Executable File
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();
|
|
}
|
|
|
|
}
|