#include "track_content_widget.h" #include "track_entry.h" #include "tools.h" #include "track_entry_painters.h" #include "track_widget.h" #include "script_timeline.h" #include "filter_background_widget.h" #include "track_drag_handler.h" #include "track_select_handler.h" #include "track_resize_handler.h" #include "temporary_pointer_vector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace LtsScriptTimeline { QDataStream &operator<<(QDataStream &ds, const TrackEntry &te) { const QByteArray jsonContent = QJsonDocument(te.m_detailData).toBinaryData(); ds << te.m_topicId << te.m_startTime << te.m_length << static_cast(te.m_type) << te.isSet << te.index << jsonContent << te.x << te.y << te.h << te.w << te.m_channelName << te.m_description; return ds; } QDataStream &operator>>(QDataStream &ds, TrackEntry &te) { QByteArray jsonContent; quint8 type; ds >> te.m_topicId >> te.m_startTime >> te.m_length >> type >> te.isSet >> te.index >> jsonContent >> te.x >> te.y >> te.h >> te.w >> te.m_channelName >> te.m_description; te.m_type = static_cast(type); te.m_detailData = QJsonDocument::fromBinaryData(jsonContent).object(); return ds; } bool isCollision(int start_s, int end_s, int toCompare_start_s, int toCompare_end_s) { if (start_s >= toCompare_start_s && start_s < toCompare_end_s) { return true; } if (start_s < toCompare_start_s && end_s > toCompare_start_s) { return true; } return false; } bool isCollision2(int startPreview_s, int endPreview_s, const TrackEntry *pTrackEntry) { return isCollision(startPreview_s, endPreview_s, pTrackEntry->m_startTime, pTrackEntry->m_startTime + pTrackEntry->m_length); } void sortTrackEntriesChronologically(QVector &vecTrackEntries) { std::sort(vecTrackEntries.begin(), vecTrackEntries.end(), [](const TrackEntry *a, const TrackEntry *b) -> bool { return a->m_startTime < b->m_startTime; }); } bool TrackContentWidget::checkForCollisionsSimple(const TrackEntry &previewEntry) const { const int startPreview = previewEntry.m_startTime; if (startPreview < 0) return false; const int endPreview = startPreview + previewEntry.m_length; const QVector trackEntries = getTrackEntries(); for (const TrackEntry *checkTe : trackEntries) { if (checkTe->m_topicId == previewEntry.m_topicId) continue; if (LtsScriptTimeline::isCollision2(startPreview, endPreview, checkTe)) return false; } return true; } bool TrackContentWidget::checkForCollisions(const TrackContentWidget *tcw, const QVector &preview, int dropTime_s, QVector &vecCollisions, bool ignoreSelected, bool ignoreSelfCheck) { QVector _preview = preview; sortTrackEntriesChronologically(_preview); vecCollisions.clear(); QVector vecEntriesToCheck; const QVector &trackEntries = tcw->getTrackEntries(); if (ignoreSelected) { const QVector &selectedEntries = tcw->getSelectedEntries(); for (TrackEntry *te : qAsConst(trackEntries)) { if (!selectedEntries.contains(te)) vecEntriesToCheck.append(te); } } else vecEntriesToCheck = trackEntries; auto leftMostI = std::min_element(_preview.constBegin(), _preview.constEnd(), [](const TrackEntry * a, const TrackEntry * b) { return a->m_startTime < b->m_startTime; }); if (leftMostI == _preview.constEnd()) return false; int startTimeMostLeft_s = (*leftMostI)->m_startTime; int startOffset_s = dropTime_s - startTimeMostLeft_s; for (TrackEntry *previewEntry : qAsConst(_preview)) { int startPreview_s = previewEntry->m_startTime + startOffset_s; if (startPreview_s < 0) { vecCollisions.append(previewEntry); continue; } int endPreview_s = startPreview_s + previewEntry->m_length; for (const TrackEntry *checkTe : qAsConst(vecEntriesToCheck)) { if (checkTe->m_topicId == previewEntry->m_topicId && !ignoreSelfCheck) { continue; } if (LtsScriptTimeline::isCollision2(startPreview_s, endPreview_s, checkTe)) { vecCollisions.append(previewEntry); break; } } } return !vecCollisions.isEmpty(); } bool TrackContentWidget::checkForCollisions(const TrackContentWidget *pTCW, const QVector &vecPreviewTrackEntries, int dropTime_s) { QVector dummy_vecCollidingTopics; return TrackContentWidget::checkForCollisions(pTCW, vecPreviewTrackEntries, dropTime_s, dummy_vecCollidingTopics, true); } QString TrackContentWidget::parallelError() { return tr("At least one topic of type Situation, Building, Audio or Video has been selected\nwhich can not be played parallel on both screens. Placement not possible."); } bool TrackContentWidget::checkForParallelTopics(const TrackContentWidget *tcw, const QVector &preview, int dropTime_s, QVector &vecParallelTopics, bool ignoreSelectedTopics, bool hack) { { QVector vecParallelForbiddenFilter = { TrackEntryType::Building, TrackEntryType::Audio, TrackEntryType::Video, TrackEntryType::SituationMap, TrackEntryType::Situation }; QVector previewCopy(preview); sortTrackEntriesChronologically(previewCopy); QVector vecTrackEntries(tcw->getTrackEntries()); auto itE = std::remove_if(previewCopy.begin(), previewCopy.end(), [vecParallelForbiddenFilter](TrackEntry *pTE) { return !vecParallelForbiddenFilter.contains(pTE->m_type); }); previewCopy.erase(itE, previewCopy.end()); if (ignoreSelectedTopics) { itE = std::remove_if(previewCopy.begin(), previewCopy.end(), [tcw](TrackEntry *pTE) { return tcw->getSelectedEntries().contains(pTE); }); previewCopy.erase(itE, previewCopy.end()); } itE = std::remove_if(vecTrackEntries.begin(), vecTrackEntries.end(), [vecParallelForbiddenFilter](TrackEntry *pTE) { return !vecParallelForbiddenFilter.contains(pTE->m_type); }); vecTrackEntries.erase(itE, vecTrackEntries.end()); itE = std::remove_if(vecTrackEntries.begin(), vecTrackEntries.end(), [tcw](TrackEntry *pTE) { return tcw->getSelectedEntries().contains(pTE); }); vecTrackEntries.erase(itE, vecTrackEntries.end()); if (!previewCopy.isEmpty() && !vecTrackEntries.isEmpty()) { auto leftMostI = std::min_element(previewCopy.constBegin(), previewCopy.constEnd(), [](const TrackEntry *a, const TrackEntry *b) { return a->m_startTime < b->m_startTime; }); if (leftMostI != previewCopy.constEnd()) { int startTimeMostLeft_s = (*leftMostI)->m_startTime; for (TrackEntry *previewEntry : qAsConst(previewCopy)) { int startOffset_s = previewEntry->m_startTime; if (!hack) startOffset_s -= startTimeMostLeft_s; for (TrackEntry *pTrackEntry : qAsConst(vecTrackEntries)) { if (previewEntry->m_type != pTrackEntry->m_type) { continue; } if (previewEntry->m_topicId == pTrackEntry->m_topicId) { continue; } int startPreview_s = startOffset_s; if (!hack) startPreview_s += dropTime_s; int endPreview_s = startPreview_s + previewEntry->m_length; if (LtsScriptTimeline::isCollision2(startPreview_s, endPreview_s, pTrackEntry)) { vecParallelTopics.append(previewEntry); break; } } } } } else { } } { QVector previewCopy(preview); sortTrackEntriesChronologically(previewCopy); QVector > differentParallelForbidden = { { TrackEntryType::Situation, TrackEntryType::Snapshot }, { TrackEntryType::Situation, TrackEntryType::SituationReplay }, { TrackEntryType::Situation, TrackEntryType::SituationMap }, }; QVector vecTrackEntries(tcw->getTrackEntries()); int step = 1; auto findUnusedType = [differentParallelForbidden, &step](const TrackEntry *pTE) { auto x = std::find_if(differentParallelForbidden.constBegin(), differentParallelForbidden.constEnd(), [pTE, step](const QPair &pair) { bool x = (pair.first == pTE->m_type || pair.second == pTE->m_type); if (x) qDebug() << ">>"<m_type << "in type list - not removing"; return x; }); if (x == differentParallelForbidden.constEnd()) qDebug() << ">>"<m_type << "not found in type list - removing!"; return (x == differentParallelForbidden.constEnd()); }; auto itE = std::remove_if(previewCopy.begin(), previewCopy.end(), findUnusedType); previewCopy.erase(itE, previewCopy.end()); step = 2; itE = std::remove_if(vecTrackEntries.begin(), vecTrackEntries.end(), findUnusedType); vecTrackEntries.erase(itE, vecTrackEntries.end()); if (ignoreSelectedTopics) { itE = std::remove_if(previewCopy.begin(), previewCopy.end(), [tcw](TrackEntry *pTE) { return tcw->getSelectedEntries().contains(pTE); }); previewCopy.erase(itE, previewCopy.end()); } itE = std::remove_if(vecTrackEntries.begin(), vecTrackEntries.end(), [tcw](TrackEntry *pTE) { bool x = tcw->getSelectedEntries().contains(pTE); return x; }); vecTrackEntries.erase(itE, vecTrackEntries.end()); if (!previewCopy.size() || !vecTrackEntries.size()) { return !vecParallelTopics.isEmpty(); } auto leftMostI = std::min_element(previewCopy.constBegin(), previewCopy.constEnd(), [](const TrackEntry * a, const TrackEntry * b) { return a->m_startTime < b->m_startTime; }); if (leftMostI == previewCopy.constEnd()) return !vecParallelTopics.isEmpty(); int startTimeMostLeft_s = (*leftMostI)->m_startTime; QVector correspondingDifferentTopicTypes; for (TrackEntry *previewEntry : qAsConst(previewCopy)) { correspondingDifferentTopicTypes.clear(); int startOffset_s = previewEntry->m_startTime; if (!hack) startOffset_s -= startTimeMostLeft_s; for (const QPair &pair : qAsConst(differentParallelForbidden)) { if (pair.first == previewEntry->m_type) correspondingDifferentTopicTypes += pair.second; if (pair.second == previewEntry->m_type) correspondingDifferentTopicTypes += pair.first; } Q_ASSERT(!correspondingDifferentTopicTypes.isEmpty()); if (correspondingDifferentTopicTypes.isEmpty()) continue; for (const TrackEntryType correspondingDifferentTopicType : qAsConst(correspondingDifferentTopicTypes)) { for (TrackEntry *pTrackEntry : qAsConst(vecTrackEntries)) { if (pTrackEntry->m_type != correspondingDifferentTopicType) { continue; } if (previewEntry->m_topicId == pTrackEntry->m_topicId) { continue; } int startPreview_s = startOffset_s; if (!hack) startPreview_s += dropTime_s; int endPreview_s = startPreview_s + previewEntry->m_length; if (LtsScriptTimeline::isCollision2(startPreview_s, endPreview_s, pTrackEntry)) { vecParallelTopics.append(previewEntry); break; } } } } } return !vecParallelTopics.isEmpty(); } bool TrackContentWidget::checkForParallelTopics(const TrackContentWidget *pTCW, const QVector &vecPreviewTrackEntries, int dropTime_s, bool hack) { QVector dummy_vecParallelTopics; return checkForParallelTopics(pTCW, vecPreviewTrackEntries, dropTime_s, dummy_vecParallelTopics, false, hack); } QString TrackContentWidget::mime = QStringLiteral("application/rheinmetall.script.topic"); TrackContentWidget::TrackContentWidget(QWidget *parent, bool isFilter, Tools *tools, int mainTopicId) : QWidget(parent) , m_selectHandler(new SelectHandler(this)) , m_resizeHandler(new ResizeHandler(this)) , m_dragHandler(new DragHandler(this)) , m_dragOffsetTime(-1) , m_dragPos(-1) , m_mainSelected(nullptr) , m_width(0) , m_cursorSet(false) , m_oldEntryLength(-1) , m_unselectAllImmune(false) , m_indicatorTime(-1) , m_isFilter(isFilter) , m_readonly(false) , m_trackWidget(nullptr) , m_scriptTimeline(nullptr) , m_tools(tools) , m_isMultiLineMultiSelectionMode(false) , m_multiLineMultiSelectionOkay(false) , m_dragPossible(false) , m_moveValid(false) , m_mainTopicId(mainTopicId) { setAcceptDrops(true); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); addFilterType(TrackEntryType::TacticalPlan); addFilterType(TrackEntryType::SituationMapFilter); if (m_isFilter) { addAllowedType(TrackEntryType::Text); addAllowedType(TrackEntryType::Pdf); addAllowedType(TrackEntryType::Image); } } TrackContentWidget::~TrackContentWidget() { delete m_selectHandler; delete m_resizeHandler; delete m_dragHandler; QVector entryList = m_entryList; for (TrackEntry *te : entryList) { disconnect(te, &TrackEntry::destroyed, this, nullptr); } qDeleteAll(entryList); } bool TrackContentWidget::setDescriptionForSelected() { if (getSelectedEntries().count() > 1) { QApplication::beep(); return false; } m_selectHandler->slotSetDescription(); return true; } void TrackContentWidget::addCorrespondingTrackWidget(TrackContentWidget *pCorrespondingTrackWidget) { m_tcwCorresponding += pCorrespondingTrackWidget; m_dragHandler->m_tcwCorresponding += pCorrespondingTrackWidget; m_resizeHandler->m_tcwCorresponding += pCorrespondingTrackWidget; } void TrackContentWidget::setTools(Tools *tools) { m_tools = tools; } void TrackContentWidget::setTrackWidget(TrackWidget *trackWidget) { m_trackWidget = trackWidget; } void TrackContentWidget::setReadonly(bool readonly) { m_readonly = readonly; } void TrackContentWidget::addFilterType(const TrackEntryType &tet) { if (m_isFilter) addAllowedType(tet); else addDisallowedType(tet); } void TrackContentWidget::setIndicatorTime(int time) { m_indicatorTime = time; update(); } void TrackContentWidget::mouseMoveEvent(QMouseEvent *event) { if (!m_resizeHandler->mouseMoveEvent(event)) m_dragHandler->mouseMoveEvent(event); emit signalModifyRubberband(); } TrackEntry *TrackContentWidget::getTrackEntryAt(int clickedTime) { for (auto i = m_entryList.begin(); i != m_entryList.end(); ++i) { TrackEntry *te = *i; if (te->m_startTime <= clickedTime && (te->m_startTime + te->m_length) >= clickedTime) return te; } return nullptr; } void TrackContentWidget::mousePressEvent(QMouseEvent *event) { if (m_readonly) return; m_selectHandler->mousePressEvent(event); m_resizeHandler->mousePressEvent(event); m_dragHandler->mousePressEvent(event); if (!m_selected.isEmpty()) return; if (event->buttons() != Qt::LeftButton) return; emit signalStartRubberband(event->pos()); } void TrackContentWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); m_width = event->size().width(); } void TrackContentWidget::mouseReleaseEvent(QMouseEvent *event) { if (m_readonly) return; if (!m_resizeHandler->m_resizing) m_selectHandler->mouseReleaseEvent(event); m_resizeHandler->mouseReleaseEvent(event); emit signalStopRubberband(); } void TrackContentWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); painter.save(); if (m_isFilter) { painter.setBrush(QGuiApplication::palette().window().color()); painter.drawRect(rect()); } painter.setPen(QColor(36, 36, 36)); painter.drawLine(QPoint(0, 0), QPoint(0, height())); if (m_isFilter) painter.drawLine(QPoint(width() - 1, 0), QPoint(width() - 1, height())); painter.setClipping(true); painter.setClipRect(1, 1, width() - 1, height() - 1); for (auto i = m_entryList.begin(); i != m_entryList.end(); ++i) { TrackEntry *te = *i; int startPx = m_tools->getPos(te->m_startTime, m_isFilter); int endPx = m_tools->getPos(te->m_startTime + te->m_length, m_isFilter); if (endPx < 0 || startPx > width()) continue; int heightPx = height() - 2; int lengthPx = endPx - startPx; painter.save(); painter.translate(startPx, 1); bool isSelected = m_selected.contains(te); QRect trackRect(0, 0, lengthPx, heightPx); drawTrackEntry(te, painter, trackRect, isSelected); if (isSelected) { } painter.restore(); } if (m_dragPos > -1 && !m_preview.isEmpty()) { int firstTeTime = -1; for (const TrackEntry *te : qAsConst(m_preview)) { if (firstTeTime > te->m_startTime || firstTeTime == -1) firstTeTime = te->m_startTime; } int drop = m_tools->getTime(m_dragPos, m_isFilter); int finalDrop = drop - m_dragOffsetTime; int diff = finalDrop - firstTeTime; int heightPx = height() - 2; for (auto i = m_preview.constBegin(); i != m_preview.constEnd(); ++i) { const TrackEntry *te = *i; if (m_dragHandler->m_vecCollisions.contains(*i) || m_dragHandler->m_vecParallel.contains(*i) || !isDragPossible() || !m_moveValid) painter.setBrush(QColor("red")); else painter.setBrush(QColor("lightgreen")); int time = te->m_startTime + diff; int startPx = m_tools->getPos(time, m_isFilter); int endPx = m_tools->getPos(time + te->m_length, m_isFilter); if (endPx < 0 || startPx > width()) continue; int lengthPx = endPx - startPx; painter.drawRect(startPx, 1, lengthPx, heightPx); } } if (m_indicatorTime > -1) { int pos = m_tools->getPos(m_indicatorTime, m_isFilter); if (pos > 0 && pos <= m_tools->m_paintWidth) { painter.setPen(QColor("cyan")); painter.drawLine(pos, 0, pos, height()); } } painter.restore(); } void TrackContentWidget::dragEnterEvent(QDragEnterEvent *event) { if (m_readonly) return; m_dragHandler->dragEnterEvent(event); } void TrackContentWidget::dragLeave() { } void TrackContentWidget::dragLeaveEvent(QDragLeaveEvent *event) { if (m_readonly) return; m_dragHandler->dragLeaveEvent(event); } void TrackContentWidget::unselect() { m_unselectAllImmune = true; emit signalUnselectAll(); m_unselectAllImmune = false; } void TrackContentWidget::forceLocalUnselect() { m_mainSelected = nullptr; m_selected.clear(); } void TrackContentWidget::selectAll() { m_selected.clear(); for (TrackEntry *te : qAsConst(m_entryList)) { m_selected.append(te); m_mainSelected = te; } emit signalTrackEntrySelectionChanged(nullptr); update(); } void TrackContentWidget::unselectAll() { if (!m_unselectAllImmune) forceLocalUnselect(); else { if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) { forceLocalUnselect(); emit signalTrackEntrySelectionChanged(nullptr); } } update(); } TrackEntry *TrackContentWidget::getParentTopicOfFilter(TrackEntry *filterTe) const { if (m_isFilter && m_trackWidget != nullptr) { int mainTopicId = m_trackWidget->getParentTopicIdOfFilter(filterTe); TrackContentWidget *mainTcw = m_trackWidget->getTrackContentWidget(); const QVector &teList = mainTcw->getTrackEntries(); for (auto i = teList.constBegin(); i != teList.constEnd(); ++i) { TrackEntry *mainTe = *i; if (mainTe->m_topicId != mainTopicId) continue; return mainTe; } } return nullptr; } bool TrackContentWidget::addTrackEntry(TrackEntry *te, bool selectTrackEntry) { if (!allowedByType(te)) { qWarning() << "TrackContentWidget::addTrackEntry: topic #" << te->m_topicId << "type" << (int)te->m_type << "not allowed"; return false; } connect(te, &TrackEntry::destroyed, this, [this]() { TrackEntry *o = static_cast(sender()); m_entryList.removeAll(o); m_selected.removeAll(o); if (m_mainSelected == o) { m_mainSelected = nullptr; emit signalTrackEntrySelectionChanged(nullptr); } }); te->m_channelName = m_trackName; m_entryList.append(te); TrackEntry *mainTe = getParentTopicOfFilter(te); if (mainTe != nullptr) { if ((te->m_startTime + te->m_length) > mainTe->m_length) te->m_length = mainTe->m_length - te->m_startTime; te->m_filterParentChannel = mainTe->m_channelName; } if (selectTrackEntry && !m_readonly) selectEntry(te); emit signalTrackEntryAdded(te); update(); return true; } void TrackContentWidget::removeTrackEntryByTopicId(TrackEntry *te) { auto itE = std::remove_if(m_entryList.begin(), m_entryList.end(), [te](TrackEntry * pTE) { if (pTE->m_topicId == te->m_topicId) { pTE->deleteLater(); return true; } return false; }); m_entryList.erase(itE, m_entryList.end()); itE = std::remove_if(m_selected.begin(), m_selected.end(), [te](TrackEntry * pTE) { if (pTE->m_topicId == te->m_topicId) { pTE->deleteLater(); return true; } return false; }); m_selected.erase(itE, m_selected.end()); } void TrackContentWidget::dropEvent(QDropEvent *event) { if (m_readonly) return; m_dragHandler->dropEvent(event); } void TrackContentWidget::dragMoveEvent(QDragMoveEvent *event) { if (m_readonly) return; m_dragHandler->dragMoveEvent(event); } bool TrackContentWidget::canMoveAll(int offset) const { if (m_selected.isEmpty()) return true; auto leftMostI = std::min_element(m_selected.constBegin(), m_selected.constEnd(), [](const TrackEntry * a, const TrackEntry * b) { return a->m_startTime < b->m_startTime; }); if (leftMostI == m_selected.constEnd()) return false; int startTimeMostLeft = (*leftMostI)->m_startTime; QVector cols; int newTime = startTimeMostLeft + offset; checkForCollisions(this, m_selected, newTime, cols, true); QVector parls; for (const auto &tcw : qAsConst(m_tcwCorresponding)) checkForParallelTopics(tcw, m_selected, newTime, parls); if (!cols.isEmpty() || !parls.isEmpty()) return false; return true; } void TrackContentWidget::forceMoveAll(int offset) { for (auto i = m_selected.begin(); i != m_selected.end(); ++i) { TrackEntry *te = *i; int newTime = te->m_startTime + offset; if (newTime < 0) newTime = 0; te->m_startTime = newTime; } update(); emit signalChangesMade(); } void TrackContentWidget::deleteSelectedTopics() { m_mainSelected = nullptr; m_selectHandler->m_pressedEntry = nullptr; bool somethingSeleted = false; for (auto &pTopic : m_selected) { m_preview.removeAll(pTopic); m_entryList.removeAll(pTopic); emit signalTrackEntryDeleted(pTopic->m_topicId); pTopic->deleteLater(); somethingSeleted = true; } if (somethingSeleted) emit signalTrackEntrySelectionChanged(nullptr); m_selected.clear(); emit signalChangesMade(); update(); } void TrackContentWidget::mouseDoubleClickEvent(QMouseEvent *event) { if (m_readonly) return; m_selectHandler->mouseDoubleClick(event); } void TrackContentWidget::keyPressEvent(QKeyEvent *event) { if (m_readonly) return; if (event->key() == Qt::Key_Delete) { emit signalDeleteSelectedTopics(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_C) { emit signalCopyTopics(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_V) { emit signalPasteTopics(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_X) { emit signalCutTopics(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_A) { emit signalSelectAllGlobal(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_S) { emit signalSave(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_D) { setDescriptionForSelected(); } else { m_resizeHandler->keyPressEvent(event); m_selectHandler->keyPressEvent(event); m_dragHandler->keyPressEvent(event); } if (event->key() == Qt::Key_Escape) { emit signalEscapeRubberband(); emit signalMultiLineMultiSelectionDone(-1); } } void TrackContentWidget::selectEntry(int topicId) { for (auto i = m_entryList.begin(); i != m_entryList.end(); ++i) { TrackEntry *te = *i; if (te->m_topicId == topicId) { selectEntry((te)); return; } } } void TrackContentWidget::selectEntriesAtPositions(int fromPos, int toPos) { m_unselectAllImmune = true; for (TrackEntry *te : qAsConst(m_entryList)) { int start = m_tools->getPos(te->m_startTime); int end = m_tools->getPos(te->m_startTime + te->m_length); if (end >= fromPos && start <= toPos) selectEntry(te); } m_unselectAllImmune = false; } void TrackContentWidget::selectEntry(TrackEntry *te) { bool ctrl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier); if (!m_unselectAllImmune && !ctrl) unselect(); if (te == nullptr) return; if (!m_selected.contains(te)) { m_selected.append(te); m_mainSelected = te; emit signalTrackEntrySelectionChanged(nullptr); } else { m_selected.removeAll(te); if (m_mainSelected == te) m_mainSelected = nullptr; emit signalTrackEntrySelectionChanged(nullptr); } update(); } bool TrackContentWidget::canPasteTopics(const QVector &topics, int offsetTime, int pasteTime) const { TemporaryPointerVector newTes; newTes.reserve(topics.size()); for (TrackEntry *te : qAsConst(topics)) { TrackEntry *newTe = new TrackEntry(te); newTe->m_startTime = (newTe->m_startTime - offsetTime + pasteTime); newTes += newTe; } QVector dummy_vecCollidingTopics; bool parl = false; for (const auto &tcw : qAsConst(m_tcwCorresponding)) { parl = checkForParallelTopics(tcw, newTes, pasteTime, true ); if (parl) break; } if (checkForCollisions(this, newTes, pasteTime, dummy_vecCollidingTopics, false, true) || parl) return false; return true; } void TrackContentWidget::forcePasteTopics(const QVector &topics, int offsetTime, int pasteTime) { m_selected.clear(); for (TrackEntry *te : qAsConst(topics)) { TrackEntry *newTe = new TrackEntry(te); newTe->m_topicId = m_scriptTimeline->nextUniqueId(); newTe->m_startTime = (newTe->m_startTime - offsetTime + pasteTime); newTe->isSet = true; m_unselectAllImmune = true; if (!m_trackWidget->addTrackEntry(newTe, false)) { qWarning() << "TrackContentWidget::forcePasteTopics: topic #" << te->m_topicId << "type" << (int)te->m_type << "not allowed"; delete newTe; delete te; continue; } m_unselectAllImmune = false; } } bool TrackContentWidget::allowedByType(const TrackEntry *te) const { if (m_disallowedTypes.contains(te->m_type)) return false; if (m_allowedTypes.isEmpty()) return true; return (m_allowedTypes.contains(te->m_type)); } void TrackContentWidget::setTrackName(const QString &trackName) { m_trackName = trackName; for (auto i = m_entryList.begin(); i != m_entryList.end(); ++i) { TrackEntry *te = *i; te->m_channelName = m_trackName; } } TrackEntry *TrackContentWidget::getTrackEntryOnPos(QPoint globalPos) { QPoint clickedPos = mapFromGlobal(globalPos); int clickedPosY = clickedPos.y(); int clickedPosX = clickedPos.x(); if (clickedPosY < 0 || clickedPosY > height()) return nullptr; int clickedTime = m_tools->getTime(clickedPosX, m_isFilter); return getTrackEntryAt(clickedTime); } QVector TrackContentWidget::dataToItems(const QByteArray &data, int &offset) { if (data.isEmpty()) return {}; QDataStream dataStream(data); int count; dataStream >> count; dataStream >> offset; QVector tes; tes.reserve(count); for (int i = 0; i < count; i++) { TrackEntry *te = new TrackEntry; dataStream >> *te; te->m_startTime -= offset; tes += te; } return tes; } bool TrackContentWidget::dragEnter(const QByteArray &itemData) { dragEnd(); TemporaryPointerVector tes(dataToItems(itemData, m_dragOffsetTime)); if (tes.isEmpty()) return false; for (TrackEntry *te : qAsConst(tes)) { if (!allowedByType(te)) { qWarning() << "TrackContentWidget::dragEnter: topic #" << te->m_topicId << "type" << (int)te->m_type << "not allowed"; return false; } } if (m_isFilter && m_mainTopicId != -1) { auto it = std::find_if(tes.begin(), tes.end(), [this](const TrackEntry * a) { return (a->m_topicId == m_mainTopicId); }); if (it != tes.end()) return false; } const QVector &thisTes = getTrackEntries(); if (m_isMultiLineMultiSelectionMode) { for (TrackEntry *te : qAsConst(tes)) { bool found = false; for (TrackEntry *thisTe : qAsConst(thisTes)) { if (thisTe->m_topicId == te->m_topicId) { found = true; break; } } if (!found) return false; } } sortTrackEntriesChronologically(tes); if (!m_isFilter && tes.size() == 1) { TrackEntry *pPreview = tes.front(); bool hasJustBeenAddedByUser = true; for (auto pTE : qAsConst(m_entryList)) { if (pPreview->m_topicId == pTE->m_topicId) { hasJustBeenAddedByUser = false; break; } } for (const auto &tcw : qAsConst(m_tcwCorresponding)) { for (auto pTE : qAsConst(tcw->m_entryList)) { if (pPreview->m_topicId == pTE->m_topicId) { hasJustBeenAddedByUser = false; break; } } } if (hasJustBeenAddedByUser) { m_selected.clear(); for (const auto &tcw : qAsConst(m_tcwCorresponding)) tcw->m_selected.clear(); } } tes.setDiscardable(false); m_preview = tes; return true; } void TrackContentWidget::clearDrag() { qDeleteAll(m_preview); m_preview.clear(); m_dragOffsetTime = -1; m_dragPos = -1; m_dragPossible = false; m_moveValid = false; update(); } int TrackContentWidget::getDropPos() const { return (m_dragHandler == nullptr) ? -1 : m_dragHandler->m_dropPos; } void TrackContentWidget::dragEnd() { clearDrag(); } bool TrackContentWidget::dragDrop(int pos) { if (!isDragPossible() || !m_moveValid) { dragEnd(); return false; } int dropTime = m_tools->getTime(pos, m_isFilter) - m_dragOffsetTime; QVector selectedOld = m_selected; if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && !m_isMultiLineMultiSelectionMode) unselect(); int firstTeTime = -1; for (const TrackEntry *te : qAsConst(m_preview)) { if (firstTeTime > te->m_startTime || firstTeTime == -1) firstTeTime = te->m_startTime; } int selected = firstTeTime; int diff = dropTime - selected; for (auto i = m_preview.constBegin(); i != m_preview.constEnd(); ++i) { TrackEntry *prevTe = *i; TrackEntry *newTe = new TrackEntry(prevTe); newTe->m_topicId = m_scriptTimeline->nextUniqueId(); newTe->m_startTime = prevTe->m_startTime + diff; newTe->isSet = true; m_unselectAllImmune = true; if (m_isFilter) { addTrackEntry(newTe, true); } else { if (m_trackWidget == nullptr || !m_trackWidget->addTrackEntry(newTe, true)) { delete newTe; return false; } } update(); for (const auto &tcw : qAsConst(m_tcwCorresponding)) tcw->removeTrackEntryByTopicId(prevTe); m_unselectAllImmune = false; } dragEnd(); if (m_isMultiLineMultiSelectionMode) { for (TrackEntry *oldSel : qAsConst(selectedOld)) emit signalTrackEntryDeleted(oldSel->m_topicId); qDeleteAll(selectedOld); emit signalMultiLineMultiSelectionDone(pos); } emit signalChangesMade(); return true; } bool TrackContentWidget::dragMove(int pos) { m_dragPos = pos; int dropTime_s = m_tools->getTime(m_dragPos, m_isFilter) - m_dragOffsetTime; checkForCollisions(this, m_preview, dropTime_s, m_dragHandler->m_vecCollisions, true); m_dragHandler->m_vecParallel.clear(); for (const auto &tcw : qAsConst(m_tcwCorresponding)) checkForParallelTopics(tcw, m_preview, dropTime_s, m_dragHandler->m_vecParallel); m_moveValid = m_dragHandler->m_vecCollisions.isEmpty() && m_dragHandler->m_vecParallel.isEmpty(); if (!m_moveValid || !isDragPossible()) { setCursor(Qt::ForbiddenCursor); } else { setCursor(Qt::DragMoveCursor); } update(); if (m_isMultiLineMultiSelectionMode) { if (m_moveValid) emit signalMultiLineMultiSelectionOkay(); else emit signalMultiLineMultiSelectionFailed(); } static int scrollByPixels = 1; if (pos >= (m_width - 60)) emit signalScroll(scrollByPixels); else if (pos < 60) emit signalScroll(-scrollByPixels); return m_moveValid; } TrackEntry *TrackContentWidget::trackEntryAtPos(int x) const { int time = m_tools->getTime(x); const QVector tes = getTrackEntries(); for (TrackEntry *te : tes) { int start = te->m_startTime; if (time >= start && time <= (start + te->m_length)) return te; } return nullptr; } QByteArray TrackContentWidget::itemsToData(const QVector &items, int dragPosTime, bool calculateFirstTeTime) { int count = items.count(); if (!count) return {}; QByteArray itemData; QDataStream dataStream(&itemData, QIODevice::WriteOnly); dataStream << count; if (dragPosTime != -1) { int firstTeTime = -1; if (calculateFirstTeTime) { for (TrackEntry *te : items) { if (firstTeTime > te->m_startTime || firstTeTime == -1) firstTeTime = te->m_startTime; } } else firstTeTime = 0; int finalOffsetTime = dragPosTime - firstTeTime; dataStream << finalOffsetTime; } else dataStream << 0; for (auto i = items.constBegin(); i != items.constEnd(); ++i) { TrackEntry *te = *i; dataStream << *te; } return itemData; } QByteArray TrackContentWidget::selectedItemsToData(int dragPosTime, bool calculateFirstTeTime) { return itemsToData(m_selected, dragPosTime, calculateFirstTeTime); } void TrackContentWidget::setDragPossible(bool possible) { m_dragPossible = possible; update(); } }