506 lines
13 KiB
C++
Executable File
506 lines
13 KiB
C++
Executable File
#include "timeline_view_drag_handler.h"
|
|
#include "item.h"
|
|
#include "track.h"
|
|
|
|
#include <QApplication>
|
|
#include <QMouseEvent>
|
|
#include <QDrag>
|
|
#include <QMimeData>
|
|
#include <QDebug>
|
|
|
|
|
|
namespace LtsTimeline
|
|
{
|
|
|
|
TimelineViewDragHandler::TimelineViewDragHandler(TimelineView *view)
|
|
: m_view(view)
|
|
{}
|
|
|
|
TimelineViewDragHandler::~TimelineViewDragHandler()
|
|
{
|
|
m_view = nullptr;
|
|
}
|
|
|
|
|
|
|
|
|
|
void TimelineViewDragHandler::dragEnterEvent(QDragEnterEvent *e)
|
|
{
|
|
if (!m_view->timelineModel()->isEditable() || e->source() != m_view || m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
|
{
|
|
e->ignore();
|
|
return;
|
|
}
|
|
if (e->mimeData()->hasFormat(Item::mimetype))
|
|
e->acceptProposedAction();
|
|
else
|
|
e->ignore();
|
|
}
|
|
|
|
void TimelineViewDragHandler::dragMoveEvent(QDragMoveEvent *e)
|
|
{
|
|
if (!m_view->timelineModel()->isEditable() || e->source() != m_view || m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
|
{
|
|
e->ignore();
|
|
return;
|
|
}
|
|
if (!e->mimeData()->hasFormat(Item::mimetype))
|
|
return;
|
|
|
|
m_dragPos = e->pos();
|
|
m_view->m_header->setHoverPosition(m_dragPos.x());
|
|
e->setAccepted(canDragItemsTo(e->pos()));
|
|
m_view->viewport()->update();
|
|
}
|
|
|
|
void TimelineViewDragHandler::dragLeaveEvent(QDragLeaveEvent *e)
|
|
{
|
|
Q_UNUSED(e);
|
|
m_dragPos = {};
|
|
}
|
|
|
|
void TimelineViewDragHandler::dropEvent(QDropEvent *e)
|
|
{
|
|
if (!m_view->timelineModel()->isEditable() || e->source() != m_view || m_dragItemIds.isEmpty())
|
|
{
|
|
e->ignore();
|
|
return;
|
|
}
|
|
if (!e->mimeData()->hasFormat(Item::mimetype))
|
|
return;
|
|
|
|
if (!canDragItemsTo(e->pos()))
|
|
{
|
|
clear();
|
|
e->acceptProposedAction();
|
|
return;
|
|
}
|
|
|
|
qint64 dt = deltaTime();
|
|
try
|
|
{
|
|
for (const int &itemId : qAsConst(m_dragItemIds))
|
|
{
|
|
const Item &item = m_view->timelineModel()->item(itemId);
|
|
m_view->timelineModel()->moveItemToTrack(itemId, dragItemTrackId(item), item.startTime() + dt);
|
|
}
|
|
}
|
|
catch (const QString &str) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; }
|
|
catch (...) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; }
|
|
clear();
|
|
e->acceptProposedAction();
|
|
}
|
|
|
|
void TimelineViewDragHandler::mouseMoveEvent(QMouseEvent *e)
|
|
{
|
|
if (!m_view->timelineModel()->isEditable())
|
|
return;
|
|
|
|
if (e->buttons() != Qt::LeftButton)
|
|
{
|
|
clear();
|
|
return;
|
|
}
|
|
|
|
if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
|
return;
|
|
|
|
if ((e->pos() - m_dragStartPos).manhattanLength() < QApplication::startDragDistance())
|
|
return;
|
|
|
|
|
|
const QModelIndex index = m_view->indexAt(e->pos());
|
|
if (!index.isValid() || index.column() == Section::eActuator)
|
|
{
|
|
m_view->setCursor(QCursor(Qt::ForbiddenCursor));
|
|
return;
|
|
}
|
|
|
|
m_dragPos = e->pos();
|
|
m_view->setCursor(QCursor(canDragItemsTo(m_dragPos) ? Qt::ClosedHandCursor : Qt::ForbiddenCursor));
|
|
if (index.data().toInt() == m_dragStartIndex.data().toInt())
|
|
return;
|
|
|
|
QMimeData *mimeData = new QMimeData();
|
|
mimeData->setData(Item::mimetype, {});
|
|
QDrag *drag = new QDrag(m_view);
|
|
drag->setMimeData(mimeData);
|
|
if (drag->exec(Qt::MoveAction) == Qt::MoveAction)
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
void TimelineViewDragHandler::mousePressEvent(QMouseEvent *e)
|
|
{
|
|
if (!m_view->timelineModel()->isEditable())
|
|
return;
|
|
|
|
if (e->button() != Qt::LeftButton)
|
|
return;
|
|
|
|
|
|
const QModelIndex index = m_view->indexAt(e->pos());
|
|
if (!index.isValid() || index.column() == Section::eActuator)
|
|
return;
|
|
|
|
try
|
|
{
|
|
Track &track = m_view->timelineModel()->track(index.data().toInt());
|
|
Item &item = track.itemAt(m_view->toTime(e->pos().x()));
|
|
m_dragItemIds = m_view->m_selectedItemIds;
|
|
if (!m_dragItemIds.contains(item.id()))
|
|
m_dragItemIds = { item.id() };
|
|
|
|
for (const int &itemId : m_dragItemIds)
|
|
if (!m_view->timelineModel()->item(itemId).isEditable())
|
|
return;
|
|
|
|
m_dragStartIndex = index;
|
|
m_dragStartTime = m_view->toTime(e->pos().x());
|
|
m_dragStartPos = e->pos();
|
|
m_dragPos = m_dragStartPos;
|
|
m_view->setCursor(QCursor(Qt::ClosedHandCursor));
|
|
}
|
|
catch (const QString &str) { DEBUG_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; }
|
|
catch (...) {}
|
|
}
|
|
|
|
void TimelineViewDragHandler::mouseReleaseEvent(QMouseEvent *e)
|
|
{
|
|
if (!m_view->timelineModel()->isEditable())
|
|
return;
|
|
|
|
if (e->button() != Qt::LeftButton)
|
|
return;
|
|
|
|
if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
|
return;
|
|
|
|
|
|
const QModelIndex index = m_view->indexAt(e->pos());
|
|
if (!index.isValid() || index.column() == Section::eActuator)
|
|
return;
|
|
|
|
if (index.data().toInt() != m_dragStartIndex.data().toInt())
|
|
return;
|
|
|
|
m_dragPos = e->pos();
|
|
if (m_dragPos.x() == m_dragStartPos.x())
|
|
{
|
|
clear();
|
|
return;
|
|
}
|
|
if (!canMoveItemsTo(m_dragPos))
|
|
{
|
|
clear();
|
|
return;
|
|
}
|
|
|
|
emit m_view->currentItemTimeChanged();
|
|
qint64 dt = deltaTime();
|
|
for (const int &itemId : m_dragItemIds)
|
|
{
|
|
Item &item = m_view->timelineModel()->item(itemId);
|
|
m_view->timelineModel()->setItemStartTime(item, item.startTime() + dt);
|
|
}
|
|
clear();
|
|
}
|
|
|
|
void TimelineViewDragHandler::keyPressEvent(QKeyEvent *e)
|
|
{
|
|
if (!m_view->timelineModel()->isEditable())
|
|
return;
|
|
if (m_view->m_selectedItemIds.isEmpty())
|
|
return;
|
|
|
|
if (e->key() == Qt::Key_Escape)
|
|
{
|
|
clear();
|
|
return;
|
|
}
|
|
|
|
if (e->key() != Qt::Key_Left && e->key() != Qt::Key_Right)
|
|
return;
|
|
|
|
int dt = m_view->timelineModel()->snapStep();
|
|
if (e->key() == Qt::Key_Left)
|
|
moveItemsLeft(m_view->selectedItemIds(), QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? -dt : -10 * dt);
|
|
else
|
|
moveItemsRight(m_view->selectedItemIds(), QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? dt : 10 * dt);
|
|
}
|
|
|
|
|
|
bool TimelineViewDragHandler::isActive() const
|
|
{
|
|
return !m_dragItemIds.isEmpty() && (m_dragPos - m_dragStartPos).manhattanLength() >= QApplication::startDragDistance();
|
|
}
|
|
|
|
|
|
int TimelineViewDragHandler::deltaX() const
|
|
{
|
|
if (!m_view->timelineModel()->isSnapEnabled())
|
|
return m_dragPos.x() - m_dragStartPos.x();
|
|
return m_view->toPixel(m_dragStartTime + deltaTime()) - m_view->toPixel(m_dragStartTime);
|
|
}
|
|
|
|
int TimelineViewDragHandler::deltaY() const
|
|
{
|
|
|
|
const QModelIndex dragIndex = m_view->indexAt(m_dragPos);
|
|
if (!dragIndex.isValid())
|
|
return m_dragPos.y() - m_dragStartPos.y();
|
|
return m_view->visualRect(dragIndex).top() - m_view->visualRect(m_dragStartIndex).top();
|
|
}
|
|
|
|
qint64 TimelineViewDragHandler::deltaTime() const
|
|
{
|
|
if (!m_view->timelineModel()->isSnapEnabled())
|
|
return m_view->toTime(m_dragPos.x()) - m_view->toTime(m_dragStartPos.x());
|
|
|
|
qint64 dt = ((m_view->toTime(m_dragPos.x()) - m_dragStartTime) / m_view->timelineModel()->snapStep()) * m_view->timelineModel()->snapStep();
|
|
int dtAbsMin = 2 * m_view->timelineModel()->snapTolerance();
|
|
int eps = m_view->timelineModel()->snapTolerance();
|
|
for (const int &dragItemId : m_dragItemIds)
|
|
{
|
|
const Item &dragItem = m_view->timelineModel()->item(dragItemId);
|
|
qint64 start = dragItem.startTime() + dt;
|
|
qint64 end = start + dragItem.duration();
|
|
for (int itemId : m_view->timelineModel()->itemIds())
|
|
{
|
|
if (m_dragItemIds.contains(itemId))
|
|
continue;
|
|
|
|
const Item &item = m_view->timelineModel()->item(itemId);
|
|
if (start - eps < item.startTime() + item.duration() && item.startTime() + item.duration() < start + eps)
|
|
{
|
|
qint64 dtCurrent = item.startTime() + item.duration() - dragItem.startTime();
|
|
qint64 dtAbsCurrent = std::abs(dtCurrent);
|
|
if (dtAbsCurrent < dtAbsMin)
|
|
{
|
|
dtAbsMin = dtAbsCurrent;
|
|
dt = dtCurrent;
|
|
}
|
|
}
|
|
else if (end - eps < item.startTime() && item.startTime() < end + eps)
|
|
{
|
|
qint64 dtCurrent = item.startTime() - (dragItem.startTime() + dragItem.duration());
|
|
qint64 dtAbsCurrent = std::abs(dtCurrent);
|
|
if (dtAbsCurrent < dtAbsMin)
|
|
{
|
|
dtAbsMin = dtAbsCurrent;
|
|
dt = dtCurrent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return dt;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TimelineViewDragHandler::canDragItemsTo(const QPoint &point) const
|
|
{
|
|
if (!m_view->timelineModel()->isEditable())
|
|
return false;
|
|
|
|
if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
|
return false;
|
|
|
|
|
|
const QModelIndex index = m_view->indexAt(point);
|
|
if (!index.isValid() || index.column() == Section::eActuator)
|
|
return false;
|
|
|
|
if (index.data().toInt() == m_dragStartIndex.data().toInt())
|
|
return canMoveItemsTo(point);
|
|
else
|
|
{
|
|
try
|
|
{
|
|
qint64 dt = deltaTime();
|
|
for (const int &itemId : m_dragItemIds)
|
|
{
|
|
const Item &item = m_view->timelineModel()->item(itemId);
|
|
int trackId = dragItemTrackId(item);
|
|
if (trackId == 0)
|
|
return false;
|
|
|
|
const Track &track = m_view->timelineModel()->track(trackId);
|
|
qint64 startNew = item.startTime() + dt;
|
|
if (!m_view->timelineModel()->canDropItem(item, track.id(), startNew))
|
|
return false;
|
|
|
|
if (track.isItemsBetween(startNew, startNew + item.duration(), m_dragItemIds, true))
|
|
return false;
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TimelineViewDragHandler::canMoveItemsTo(const QPoint &point) const
|
|
{
|
|
if (!m_view->timelineModel()->isEditable())
|
|
return false;
|
|
if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
|
return false;
|
|
|
|
|
|
const QModelIndex index = m_view->indexAt(point);
|
|
if (!index.isValid() || index.column() == Section::eActuator || index.data().toInt() != m_dragStartIndex.data().toInt())
|
|
return false;
|
|
|
|
qint64 dt = deltaTime();
|
|
for (const int &itemId : m_dragItemIds)
|
|
{
|
|
const Item &item = m_view->timelineModel()->item(itemId);
|
|
qint64 startNew = item.startTime() + dt;
|
|
if (!m_view->timelineModel()->canMoveItem(item, startNew))
|
|
return false;
|
|
|
|
const Track &track = m_view->timelineModel()->track(item.parentTrackId());
|
|
if (track.isItemsBetween(startNew, startNew + item.duration(), m_dragItemIds, true))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void TimelineViewDragHandler::moveItemsLeft(const QVector<int> &itemIds, int dt)
|
|
{
|
|
Q_ASSERT(dt < 0);
|
|
try
|
|
{
|
|
for (int itemId : itemIds)
|
|
{
|
|
const Item &item = m_view->timelineModel()->item(itemId);
|
|
const Track &track = m_view->timelineModel()->track(item.parentTrackId());
|
|
if (track.isItemsBetween(item.startTime() + dt, item.startTime() + item.duration() + dt, itemIds, true))
|
|
{
|
|
if (dt < - m_view->timelineModel()->snapStep())
|
|
moveItemsLeft(itemIds, dt + m_view->timelineModel()->snapStep());
|
|
return;
|
|
}
|
|
if (!m_view->timelineModel()->canMoveItem(item, item.startTime() + dt))
|
|
{
|
|
if (dt < - m_view->timelineModel()->snapStep())
|
|
moveItemsLeft(itemIds, dt + m_view->timelineModel()->snapStep());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
catch (const QString &errMsg)
|
|
{
|
|
ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed: " << errMsg;
|
|
return;
|
|
}
|
|
catch (...)
|
|
{
|
|
ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed";
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
for (int itemId : itemIds)
|
|
{
|
|
Item &item = m_view->timelineModel()->item(itemId);
|
|
m_view->timelineModel()->setItemStartTime(item, item.startTime() + dt);
|
|
}
|
|
}
|
|
catch (const QString &str) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; }
|
|
catch (...) {}
|
|
}
|
|
|
|
void TimelineViewDragHandler::moveItemsRight(const QVector<int> &itemIds, int dt)
|
|
{
|
|
Q_ASSERT(dt > 0);
|
|
try
|
|
{
|
|
for (int itemId : itemIds)
|
|
{
|
|
const Item &item = m_view->timelineModel()->item(itemId);
|
|
const Track &track = m_view->timelineModel()->track(item.parentTrackId());
|
|
if (track.isItemsBetween(item.startTime() + dt, item.startTime() + item.duration() + dt, itemIds, true))
|
|
{
|
|
if (dt > m_view->timelineModel()->snapStep())
|
|
moveItemsRight(itemIds, dt - m_view->timelineModel()->snapStep());
|
|
return;
|
|
}
|
|
if (!m_view->timelineModel()->canMoveItem(item, item.startTime() + dt))
|
|
{
|
|
if (dt > m_view->timelineModel()->snapStep())
|
|
moveItemsRight(itemIds, dt - m_view->timelineModel()->snapStep());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
catch (const QString &errMsg)
|
|
{
|
|
ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed: " << errMsg;
|
|
return;
|
|
}
|
|
catch (...)
|
|
{
|
|
ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed";
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
for (int itemId : itemIds)
|
|
{
|
|
Item &item = m_view->timelineModel()->item(itemId);
|
|
m_view->timelineModel()->setItemStartTime(item, item.startTime() + dt);
|
|
}
|
|
}
|
|
catch (const QString &errMsg) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << errMsg; }
|
|
catch (...) {}
|
|
}
|
|
|
|
|
|
int TimelineViewDragHandler::dragItemTrackId(const Item &item) const
|
|
{
|
|
int dy = deltaY();
|
|
|
|
const QModelIndex index = m_view->indexAt(m_dragStartPos + QPoint(0, dy));
|
|
if (index.data().toInt() == m_dragStartIndex.data().toInt())
|
|
return item.parentTrackId();
|
|
|
|
try
|
|
{
|
|
|
|
const QModelIndex originalIndex = m_view->timelineModel()->indexOfTrack(item.parentTrackId());
|
|
|
|
const QModelIndex newIndex = m_view->indexAt(m_view->visualRect(originalIndex).center() + QPoint(0, dy));
|
|
return newIndex.data().toInt();
|
|
}
|
|
catch (...)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void TimelineViewDragHandler::clear()
|
|
{
|
|
m_dragStartIndex = {};
|
|
m_dragStartTime = -1;
|
|
m_dragStartPos = {};
|
|
m_dragPos = {};
|
|
m_dragItemIds.clear();
|
|
m_view->setCursor(QCursor(Qt::ArrowCursor));
|
|
m_view->viewport()->update();
|
|
}
|
|
|
|
}
|