#include "timeline_view_drag_handler.h" #include "item.h" #include "track.h" #include #include #include #include #include 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 &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 &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(); } }