#include "script_timeline.h" #include "ui_script_timeline.h" #include "track_widget.h" #include "tools.h" #include "track_entry.h" #include "track_content_widget.h" #include "filter_track_widget.h" #include "filter_background_widget.h" #include "temporary_pointer_vector.h" #include #include #include #include #include #include #include #include namespace LtsScriptTimeline { ScriptTimeline::ScriptTimeline(QWidget *parent) : QWidget(parent) , ui(new Ui::ScriptTimeline) , m_uniqueId(0) , m_scriptId(-1) , m_baseTrackWidgetWidth(150) , m_readonly(false) , m_time(0) , m_tools(new Tools(this)) , m_mlmsMaster(nullptr) , m_ignoreChanges(false) { ui->setupUi(this); this->setFocusPolicy(Qt::StrongFocus); // catch key events ui->indicator->setTools(m_tools); // initial timeline length = 5min (5*60) ui->trackScrollBar->setMinimum(0); ui->trackScrollBar->setMaximum(5 * 60); connect(ui->zoomSlider, &QSlider::valueChanged, this, &ScriptTimeline::slotZoomValueChanged); connect(ui->trackScrollBar, &QScrollBar::sliderReleased, this, &ScriptTimeline::slotScrollbarValueChanged); connect(ui->trackScrollBar, &QScrollBar::actionTriggered, this, &ScriptTimeline::slotScrollbarValueChanged); connect(ui->indicator, &Indicator::signalHandleResized, this, &ScriptTimeline::slotHeaderHandleResized); connect(ui->indicator, &Indicator::signalHeaderIndicatorTimeChanged, this, &ScriptTimeline::slotHeaderIndicatorTimeChanged); connect(ui->indicator, &Indicator::signalHeaderIndicatorTimeUserChanged, this, &ScriptTimeline::signalHeaderIndicatorTimeUserChanged); // SetNewScrollBarMaximum: get the 'most right' topic to get the end of the timeline (hacked) QTimer *t = new QTimer(this); t->setInterval(1500); connect(t, &QTimer::timeout, this, &ScriptTimeline::slotSetNewScrollBarMaximum); t->start(); //#warning TODO: ! //ui->lblTime->setText(QTime::fromMSecsSinceStartOfDay(0).toString(QStringLiteral("HH:mm:ss"))); slotZoomValueChanged(ui->zoomSlider->value()); } ScriptTimeline::~ScriptTimeline() { m_copyTopics.clear(); // otherwise it will crash due m_copyTopics.remove(static_cast(obj)); in copyTopics() m_ignoreChanges = true; qDeleteAll(m_clipboard); // clear(); <= dangerous delete ui; } void ScriptTimeline::selectAll() { const QList vecTrackWidgets = getTrackWidgets(); for (TrackWidget *tw : vecTrackWidgets) { TrackContentWidget *tcw = tw->getTrackContentWidget(); tcw->selectAll(); } } bool ScriptTimeline::checkNewLength(TrackEntry *te, int newLength) { const QList tws = getTrackWidgets(); TrackContentWidget *tesTcw = nullptr; // find parent tcw of te for (TrackWidget *tw : tws) { const QVector &tes = tw->getTrackContentWidget()->getTrackEntries(); auto it = std::find_if(tes.begin(), tes.end(), [te](const TrackEntry * a) { return (a->m_topicId == te->m_topicId); }); if (it == tes.end()) continue; tesTcw = tw->getTrackContentWidget(); break; } if (tesTcw == nullptr) return false; // the actual magic: TrackEntry newTe(te); newTe.m_length = newLength; // set new length of the copy of the te to try to see if there is a collision later return tesTcw->checkForCollisionsSimple(newTe); // note: new te and check te have the same id (because it was copied), so checkForCollisionsSimple will ignore the check with both topics (or more like "itself") } void ScriptTimeline::lockIndicator(bool lock) { ui->indicator->lockIndicator(lock); } void ScriptTimeline::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Delete) { slotDeleteSelectedTopics(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_C) { slotCopyTopics(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_V) { slotPasteTopics(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_X) { slotCutTopics(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_A) { selectAll(); } else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_S) { emit signalSave(); } else { const QList vecTrackWidgets = getTrackWidgets(); for (auto w : vecTrackWidgets) w->keyPressEvent(event); } } void ScriptTimeline::mousePressEvent(QMouseEvent *event) { if (event->buttons() != Qt::LeftButton) return; slotStartRubberband(event->pos()); } void ScriptTimeline::mouseMoveEvent(QMouseEvent *) { slotModifyRubberband(); } void ScriptTimeline::mouseReleaseEvent(QMouseEvent *) { const QList vecTrackWidgets = getTrackWidgets(); for (auto w : vecTrackWidgets) w->unselectAll(); slotStopRubberband(); } void ScriptTimeline::slotScroll(int incr) { int oldValue = ui->trackScrollBar->value(); ui->trackScrollBar->setValue(oldValue + incr); slotScrollbarValueChanged(); } void ScriptTimeline::setReadonly(bool readonly) { m_readonly = readonly; const QList vecTrackWidgets = getTrackWidgets(); for (TrackWidget *tw : vecTrackWidgets) tw->setReadonly(readonly); } void ScriptTimeline::clear() { m_copyTopics.clear(); const QList trackWidgetList = getTrackWidgets(); //qDeleteAll(trackWidgetList); for (TrackWidget *tw : trackWidgetList) delete tw; //->deleteLater(); using deleteLater will make following calls think that these tws are still available.. qDeleteAll(m_clipboard); m_clipboard.clear(); m_uniqueId = 0; updateEverything(); } void ScriptTimeline::slotSetNewScrollBarMaximum() { int max = 5 * 60; // basis length of the script timeline const QList trackWidgetList = getTrackWidgets(); for (const TrackWidget *tw : trackWidgetList) { const QVector &trackEntries = tw->getTrackContentWidget()->getTrackEntries(); for (const TrackEntry *te : trackEntries) { int maxTeTime = te->m_startTime + te->m_length; //qWarning() << "te" << te->topicId << "mexTeTime" << maxTeTime; if (maxTeTime > max) { max = maxTeTime + 5 * 60; // add 1 minute } } } //qWarning() << "ScriptTimeline::slotSetNewScrollBarMaximum: max" << max; ui->trackScrollBar->setMaximum(max); ui->trackScrollBar->setPageStep(60); m_tools->m_timelineLength = max; updateEverything(); } void ScriptTimeline::setIndicatorTime(int time) { ui->indicator->setIndicatorTime(time); } int ScriptTimeline::getCurrentScriptEndTime() { int currEndTime = 0; const QList listTrackWidgets = getTrackWidgets(); for (const auto ltw : listTrackWidgets) { const auto cont = ltw->getTrackEntries(); for (const auto pTE : cont) { int endTime = pTE->m_startTime + pTE->m_length; currEndTime = qMax(currEndTime, endTime); } } return currEndTime; } void ScriptTimeline::slotHeaderIndicatorTimeChanged(int time) { m_time = time; //#warning TODO: ! //ui->lblTime->setText(QTime::fromMSecsSinceStartOfDay(time * 1000).toString(QStringLiteral("HH:mm:ss"))); const QList trackWidgetList = getTrackWidgets(); for (TrackWidget *tw : trackWidgetList) tw->setIndicatorTime(time); emit signalHeaderIndicatorTimeChanged(time); } void ScriptTimeline::slotHeaderHandleResized(int newSize) { m_baseTrackWidgetWidth = newSize; updateEverything(); const QList trackWidgetList = getTrackWidgets(); for (TrackWidget *tw : trackWidgetList) tw->setBaseWidth(newSize); } void ScriptTimeline::slotDeleteTracks() { int count = 0; const QList trackWidgetList = getTrackWidgets(); for (const TrackWidget *tw : trackWidgetList) { if (tw->isSelected()) count++; } if (count < 1) { QApplication::beep(); return; } if (QMessageBox::question(this, tr("Delete selected tracks"), tr("Are you sure you want to delete all %1 tracks?").arg(count), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) // replace with framework msg dlg return; for (TrackWidget *tw : trackWidgetList) { if (tw->isSelected()) tw->deleteLater(); } //ui->pbDeleteTracks->setEnabled(false); // nothing selected anymore } void ScriptTimeline::slotZoomValueChanged(int value) { if (value < 1) value = 1; // prevent 0-div m_tools->m_zoomFactor = value; //#warning TODO: center the visible mid when zooming (in EventTimeline, too) updateEverything(); } void ScriptTimeline::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); { QPixmap p(QStringLiteral(":/style/portrait-mode.svg")); // TODO: do not reload svg on every resize int h = ui->trackScrollBar->height(); QSize s(h, h); // 1:1 ui->lbPortrait->setPixmap(p.scaled(s, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); ui->lbPortrait->setFixedSize(s); } { QPixmap p(QStringLiteral(":/style/landscape-mode.svg")); int h = ui->trackScrollBar->height(); QSize s(h, h); // 1:1 ui->lbLandscape->setPixmap(p.scaled(s, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); ui->lbLandscape->setFixedSize(s); } updateEverything(); } void ScriptTimeline::slotUnselectAll() { const QList trackWidgetList = getTrackWidgets(); for (TrackWidget *tw : trackWidgetList) tw->unselectAll(); } int ScriptTimeline::getFilterTrackEntryRealTime(TrackEntry *te) { const QList trackWidgetList = getTrackWidgets(); for (TrackWidget *tw : trackWidgetList) { for (FilterTrackWidget *ftw : tw->filterLines()) { const QMap &filterContentWidgets = ftw->fbw()->filterContentWidgets(); int foundMainTrackEntryId = -1; for (auto itFcw = filterContentWidgets.begin(); itFcw != filterContentWidgets.end(); ++itFcw) { const TrackContentWidget *tcw = itFcw.value(); const QVector &trackEntries = tcw->getTrackEntries(); for (const TrackEntry *tempFilterTe : trackEntries) { if (tempFilterTe->m_topicId == te->m_topicId) { foundMainTrackEntryId = itFcw.key(); // filter's parent main widget found !!1 break; } } if (foundMainTrackEntryId != -1) break; } if (foundMainTrackEntryId != -1) { const QVector &trackEntries = tw->getTrackContentWidget()->getTrackEntries(); for (const TrackEntry *mainTe : trackEntries) { if (mainTe->m_topicId == foundMainTrackEntryId) return mainTe->m_startTime + te->m_startTime; } } } } return -1; } QList ScriptTimeline::getTrackWidgets() const { return ui->trackScrollArea->findChildren(); } TrackEntry *ScriptTimeline::getTrackEntryWithFiltersAsEntries(int topicId) { const QList trackWidgetList = getTrackWidgets(); for (const TrackWidget *tw : trackWidgetList) { const QVector trackEntries = tw->getTrackEntriesFiltersAsEntries(); for (TrackEntry *te : qAsConst(trackEntries)) { if (te->m_topicId == topicId) return te; } } return nullptr; } TrackEntry *ScriptTimeline::getTrackEntry(int topicId) { const QList trackWidgetList = getTrackWidgets(); for (const TrackWidget *tw : trackWidgetList) { const QVector trackEntries = tw->getTrackEntries(); for (TrackEntry *te : trackEntries) { if (te->m_topicId == topicId) return te; } } return nullptr; } QVector ScriptTimeline::getTrackEntriesWithFiltersAsEntries(bool selectedEntriesOnly) const { QVector tes; const QList trackWidgetList = getTrackWidgets(); for (TrackWidget *tw : trackWidgetList) tes += tw->getTrackEntriesFiltersAsEntries(selectedEntriesOnly); return tes; } QMap > ScriptTimeline::getTrackEntriesWithFilters(bool selectedEntriesOnly) const { QMap > trackList; const QList trackWidgetList = getTrackWidgets(); for (const TrackWidget *tw : trackWidgetList) { QString trackName(tw->trackName()); const QVector trackEntries = tw->getTrackEntriesWithFilters(selectedEntriesOnly); trackList.insert(trackName, trackEntries); } return trackList; } QVector ScriptTimeline::getTrackEntries(bool selectedEntriesOnly) const { QVector tes; const QList trackWidgetList = getTrackWidgets(); for (const TrackWidget *tw : trackWidgetList) tes += tw->getTrackEntries(selectedEntriesOnly); return tes; } bool ScriptTimeline::importTrackEntries(const QVector &importTes) { // 1. calculate the length of all tes from importTes together int importLength = 0; for (TrackEntry *te : qAsConst(importTes)) { int teLen = te->m_startTime + te->m_length; if (teLen > importLength) importLength = teLen; } // 2. get the time from when all topics should be moved to after the imported script const QVector tes = getTrackEntries(); int pasteTime = m_time; for (TrackEntry *te : qAsConst(tes)) { int start = te->m_startTime; int sum = start + te->m_length; if (start < m_time && sum > m_time && pasteTime < sum) { pasteTime = sum; } } int diff = pasteTime - m_time; // because there (can) be space that is skipped (because of intersecting topics), this space has to be added, too // 3. move all tes at m_time and later by the sum length calculated by '1.' & '2.' for (TrackEntry *te : qAsConst(tes)) { if (te->m_startTime >= m_time) te->m_startTime += importLength + diff; } // 4. set import tes start times by adding an offset (indicator_pos) and finally add it to the script QVector finalImportTes(importTes); // copy to make it editable! (NOTE: at this point the tes must not be deleted anymore as they are added to the script timeline) - which means that the temp vector must not discard for (TrackEntry *te : finalImportTes) { // 3.1 set start time te->m_startTime += pasteTime; // 3.1 finally add te if (!addTopic(te)) { qCritical() << "could not add te #" << te->m_topicId; delete te; // that's okay because the temp vector must not discard anymore (so no double free) } } // 5. set new indicator time to show the user where the script really has been imported to (skipped all intersecting topics) m_time = pasteTime; setIndicatorTime(m_time); return true; } bool ScriptTimeline::addTopic(TrackEntry *te) { if (te->m_topicId == 0) te->m_topicId = nextUniqueId(); if (te->m_channelName.isEmpty()) te->m_channelName = tr("Unnamed Track"); te->isSet = true; // added = isSet // find (or create) track and add new entry to it const QList trackWidgetList = getTrackWidgets(); for (TrackWidget *tw : trackWidgetList) { if (tw->trackName() == te->m_channelName) { m_ignoreChanges = true; bool result = tw->addTrackEntry(te, false); m_ignoreChanges = false; if (!result) return false; tw->updateMe(); return true; } } // tw not found.. return false; } QVector ScriptTimeline::getSelectedEntriesWithFilterEntries() const { QVector tes; const QList tws = getTrackWidgets(); for (const TrackWidget *tw : qAsConst(tws)) tes += tw->getTrackEntriesFiltersAsEntries(true); return tes; } QString ScriptTimeline::newTrackName() const { QString newTrackName; int add = 1; const QList trackWidgetList = getTrackWidgets(); bool newTrackNameFound = false; while (!newTrackNameFound) { newTrackNameFound = true; newTrackName = tr("New Track %1").arg(add); for (const TrackWidget *tw : trackWidgetList) { if (tw->trackName() == newTrackName) { // found the same name, start over add++; newTrackNameFound = false; break; } } } return newTrackName; } void ScriptTimeline::updateAllTracks() { const QList tws = getTrackWidgets(); for (TrackWidget *tw : tws) tw->updateMe(); } QVector ScriptTimeline::getSelectedFilterEntries() const { QVector result; const QList tws = getTrackWidgets(); for (const TrackWidget *tw : qAsConst(tws)) { result += tw->getSelectedFilterEntries(); } return result; } void ScriptTimeline::slotDeleteSelectedTopics() { int topicCount = 0; const QList tws = getTrackWidgets(); for (const TrackWidget *tw : qAsConst(tws)) { topicCount += tw->getSelectedFilterEntries().count(); TrackContentWidget *tcw = tw->getTrackContentWidget(); topicCount += tcw->getSelectedEntries().count(); } if (!topicCount) { QApplication::beep(); return; } connect(new LtsWidgets::LtsMessageDialogNew(this, tr("Are you sure that you want to delete %1 topics?").arg(topicCount), QString(), LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes | LtsWidgets::Button::No), &LtsWidgets::LtsMessageDialogNew::yes, this, [this]() { const QList tws = getTrackWidgets(); for (const TrackWidget *tw : qAsConst(tws)) { // delete all selected filter topics const QVector &ftws = tw->filterLines(); for (const FilterTrackWidget *ftw : qAsConst(ftws)) { const FilterBackgroundWidget *fbw = ftw->fbw(); const QMap &fcws = fbw->filterContentWidgets(); for (TrackContentWidget *tcw: fcws) tcw->deleteSelectedTopics(); } TrackContentWidget *tcw = tw->getTrackContentWidget(); tcw->deleteSelectedTopics(); } }); } void ScriptTimeline::slotCutTopics() { slotCopyTopics(); const QList tws = getTrackWidgets(); for (const TrackWidget *tw : qAsConst(tws)) { TrackContentWidget *tcw = tw->getTrackContentWidget(); tcw->deleteSelectedTopics(); } } void ScriptTimeline::slotCopyTopics() { const QList tws = getTrackWidgets(); int firstTeTime = -1; // get firstTeTime first for (const TrackWidget *tw : qAsConst(tws)) { TrackContentWidget *tcw = tw->getTrackContentWidget(); const QVector &selected = tcw->getSelectedEntries(); // filters can be ignored at this point, check (main) times only if (tcw->getSelectedEntries().isEmpty()) continue; for (const TrackEntry *te : qAsConst(selected)) { if (firstTeTime > te->m_startTime || firstTeTime == -1) firstTeTime = te->m_startTime; } } if (firstTeTime == -1) // not set = no selected topics { QApplication::beep(); return; } // there is something to copy, clear old vector before m_copyTopics.clear(); // finally copy with firstTeTime for (TrackWidget *tw : tws) { TrackContentWidget *tcw = tw->getTrackContentWidget(); if (tcw->getSelectedEntries().isEmpty()) continue; // for security reasons: connect(tw, &QObject::destroyed, this, [this](QObject *obj) { m_copyTopics.remove(static_cast(obj)); }); m_copyTopics.insert(tw, TrackContentWidget::itemsToData(tw->getTrackEntriesWithFilters(true), firstTeTime, false)); } } void ScriptTimeline::slotPasteTopics() { if (m_copyTopics.isEmpty() || m_time < 0) { QApplication::beep(); return; } // test if all tcws can paste there for (auto i = m_copyTopics.constBegin(); i != m_copyTopics.constEnd(); ++i) { const TrackWidget *tw = i.key(); const QByteArray &data = i.value(); TrackContentWidget *tcw = tw->getTrackContentWidget(); int dummy = 0; const TemporaryPointerVector tempTes(TrackContentWidget::dataToItems(data, dummy)); if (!tcw->canPasteTopics(tempTes, 0, m_time)) { new LtsWidgets::LtsMessageDialogNew(this, tr("Topics can't be pasted here as one or more topics intersects with another existing topic.")); return; } } // paste now if not failed for (auto i = m_copyTopics.constBegin(); i != m_copyTopics.constEnd(); ++i) { TrackWidget *tw = i.key(); TrackContentWidget *tcw = tw->getTrackContentWidget(); const QByteArray &data = i.value(); int dummy = 0; const TemporaryPointerVector tempTes(TrackContentWidget::dataToItems(data, dummy)); tcw->forcePasteTopics(tempTes, 0, m_time); } } void ScriptTimeline::slotMoveAll(int direction) { int factor = 1; if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) factor = 10; QList tws = getTrackWidgets(); // first test if all tcws can move for (TrackWidget *tw : qAsConst(tws)) { TrackContentWidget *tcw = tw->getTrackContentWidget(); if (!tcw->canMoveAll(direction * factor)) { QApplication::beep(); qWarning() << "cols and/or parls @" << tcw; return; } } // if not failed, finally move all for (TrackWidget *tw : qAsConst(tws)) { TrackContentWidget *tcw = tw->getTrackContentWidget(); tcw->forceMoveAll(direction * factor); } } static inline bool containsFailedTrackContentWidget(const QHash &mlmsTCWStates) { auto end = mlmsTCWStates.end(); for (auto it = mlmsTCWStates.begin(); it != end; ++it) { if (it.value() == false) return true; } return false; } TrackWidget *ScriptTimeline::addTrack(const QString &name, bool filtersAllowed) { TrackWidget *tw = new TrackWidget(m_baseTrackWidgetWidth, m_readonly, m_tools, filtersAllowed); tw->setScriptTimeline(this); tw->setTrackName(name); ui->trackLayout->insertWidget(1, tw); // after indicator connect(tw, &TrackWidget::signalTrackEntrySelectionChanged, this, &ScriptTimeline::slotTrackEntrySelectionChanged); connect(tw, &TrackWidget::signalTrackEntryDeleted, this, &ScriptTimeline::signalTrackEntryDeleted); connect(tw, &TrackWidget::signalUnselectAll, this, &ScriptTimeline::slotUnselectAll); connect(tw, &TrackWidget::signalSelectTrack, this, &ScriptTimeline::slotSelectTrack); connect(tw, &TrackWidget::signalChangesMade, this, &ScriptTimeline::signalChangesMade); connect(tw, &TrackWidget::signalEditEntry, this, &ScriptTimeline::signalEditEntry); TrackContentWidget *tcw = tw->getTrackContentWidget(); connect(tcw, &TrackContentWidget::signalStartRubberband, this, &ScriptTimeline::slotStartRubberband); connect(tcw, &TrackContentWidget::signalStopRubberband, this, &ScriptTimeline::slotStopRubberband); connect(tcw, &TrackContentWidget::signalEscapeRubberband, this, &ScriptTimeline::slotEscapeRubberband); connect(tcw, &TrackContentWidget::signalModifyRubberband, this, &ScriptTimeline::slotModifyRubberband); connect(tcw, &TrackContentWidget::signalSelectAllGlobal, this, &ScriptTimeline::selectAll); connect(tcw, &TrackContentWidget::signalCopyTopics, this, &ScriptTimeline::slotCopyTopics); connect(tcw, &TrackContentWidget::signalPasteTopics, this, &ScriptTimeline::slotPasteTopics); connect(tcw, &TrackContentWidget::signalCutTopics, this, &ScriptTimeline::slotCutTopics); connect(tcw, &TrackContentWidget::signalDeleteSelectedTopics, this, &ScriptTimeline::slotDeleteSelectedTopics); connect(tcw, &TrackContentWidget::signalSave, this, &ScriptTimeline::signalSave); connect(tcw, &TrackContentWidget::signalMoveAll, this, &ScriptTimeline::slotMoveAll); connect(tcw, &TrackContentWidget::signalScroll, this, &ScriptTimeline::slotScroll); connect(tcw, &TrackContentWidget::signalMultiLineMultiSelectionFailed, // can be sent by any tcw this, [this]() { TrackContentWidget *senderTcw = qobject_cast(sender()); if (senderTcw == nullptr) return; m_mlmsTCWStates.insert(senderTcw, false); const QList tws = getTrackWidgets(); for (TrackWidget *tw : tws) { // set all tcw's drags impossible TrackContentWidget *tcw = tw->getTrackContentWidget(); tcw->setDragPossible(false); // NOTE: even on the sender } }); connect(tcw, &TrackContentWidget::signalMultiLineMultiSelectionOkay, // can be sent by any tcw this, [this]() { // only set all tcw's drags possible again, every tcw has to have "drag possible" set to true // so last to call this okay slot will set TrackContentWidget *senderTcw = qobject_cast(sender()); if (senderTcw == nullptr) return; m_mlmsTCWStates.insert(senderTcw, true); if (containsFailedTrackContentWidget(m_mlmsTCWStates)) // any failed TCWs left? then stop return; // .. otherwise, finally, set all TCWs drag possible again const QList tws = getTrackWidgets(); for (TrackWidget *tw : tws) { TrackContentWidget *tcw = tw->getTrackContentWidget(); if (tcw == senderTcw) continue; tcw->setDragPossible(true); } }); connect(tcw, &TrackContentWidget::signalMultiLineMultiSelectionDone, // can only be sent by mlms master tcw this, [this](int dropPos) { TrackContentWidget *senderTcw = qobject_cast(sender()); if (senderTcw == nullptr) return; // if (senderTcw != m_mlmsMaster) // return; const QList tws = getTrackWidgets(); for (TrackWidget *tw : tws) { TrackContentWidget *tcw = tw->getTrackContentWidget(); if (tcw == senderTcw) continue; if (containsFailedTrackContentWidget(m_mlmsTCWStates) || dropPos == -1) // any failed TCWs left? then stop { QApplication::beep(); tcw->dragEnd(); } else tcw->dragDrop(dropPos);//senderTcw->getDropPos()); } m_mlmsMaster = nullptr; m_mlmsTCWStates.clear(); m_ignoreChanges = false; // reactivate updateAllTracks(); }); connect(tcw, &TrackContentWidget::signalMultiLineMultiSelectionMove, // can only be sent by mlms master tcw this, [this](int x) { TrackContentWidget *senderTcw = qobject_cast(sender()); if (senderTcw == nullptr) return; // only the master sends, so, not needed if (senderTcw != m_mlmsMaster) return; const QList tws = getTrackWidgets(); for (TrackWidget *tw : tws) { TrackContentWidget *tcw = tw->getTrackContentWidget(); if (tcw == senderTcw) continue; tcw->dragMove(x); } }); connect(tcw, &TrackContentWidget::signalMultiLineMultiSelectionStart, // can only be sent by mlms master this, [this](int dragPosTime) { if (m_mlmsMaster != nullptr) // already started return; TrackContentWidget *senderTcw = qobject_cast(sender()); if (senderTcw == nullptr) return; m_mlmsMaster = senderTcw; m_ignoreChanges = true; // otherwise mlms mode will exit when selection is "altered in a bad way" const QList tws = getTrackWidgets(); for (TrackWidget *tw : tws) { TrackContentWidget *tcw = tw->getTrackContentWidget(); if (tcw == senderTcw) continue; tcw->dragEnter(tcw->selectedItemsToData(dragPosTime)); } }); return tw; } void ScriptTimeline::slotEscapeRubberband() { if (m_rubberbandAction.rubberBand == nullptr) return; m_rubberbandAction.rubberBand->deleteLater(); } void ScriptTimeline::slotStopRubberband() { if (m_rubberbandAction.rubberBand == nullptr) return; QRect geo(m_rubberbandAction.rubberBand->geometry()); m_rubberbandAction.rubberBand->deleteLater(); if (m_readonly) return; QPoint rubberbandLength = geo.topLeft() - geo.bottomRight(); if (rubberbandLength.manhattanLength() <= 6) // don't do anything if cursor didn't move significantly much return; const QList tws = getTrackWidgets(); for (TrackWidget *tw : tws) { TrackContentWidget *tcw = tw->getTrackContentWidget(); QRect tcwGeo(tcw->geometry()); QPoint topLeft(tcw->mapTo(this, tcwGeo.topLeft())); QPoint bottomRight(tcw->mapTo(this, tcwGeo.bottomRight())); if (QRect(topLeft, bottomRight).intersects(geo)) { QPoint tcwTL(tcw->mapFrom(this, geo.topLeft())); QPoint tcwBR(tcw->mapFrom(this, geo.bottomRight())); int fromPos = tcwTL.x(); int toPos = tcwBR.x(); tcw->selectEntriesAtPositions(fromPos, toPos); } } } void ScriptTimeline::slotStartRubberband(const QPoint &) { if (m_readonly) return; if (m_rubberbandAction.rubberBand == nullptr) { QPoint cursorPos(mapFromGlobal(QCursor::pos())); m_rubberbandAction.origin = cursorPos; m_rubberbandAction.rubberBand = new QRubberBand(QRubberBand::Rectangle, this); m_rubberbandAction.rubberBand->setGeometry(QRect(cursorPos, QSize())); m_rubberbandAction.rubberBand->show(); } } void ScriptTimeline::slotModifyRubberband() { if (!m_rubberbandAction.rubberBand) return; m_rubberbandAction.rubberBand->setGeometry(QRect(m_rubberbandAction.origin, mapFromGlobal(QCursor::pos())).normalized()); } void ScriptTimeline::slotTrackEntrySelectionChanged(TrackEntry *) { if (m_ignoreChanges) return; const QList tws = getTrackWidgets(); // activate mlms mode if more than 1 tw has selection(s) bool hasSelections = false; bool activateMultiLineMultiSelectionMode = false; for (const TrackWidget *tw : tws) { if (!hasSelections) { if (!tw->getSelectedEntries().isEmpty() || !tw->getSelectedFilterEntries().isEmpty()) hasSelections = true; } else if (!tw->getSelectedEntries().isEmpty()) // here are selections, too { // more than 1 tw has selections, activate multi line multi selection mode ! activateMultiLineMultiSelectionMode = true; break; } } // de-/activate mlms mode for (TrackWidget *tw : qAsConst(tws)) tw->getTrackContentWidget()->setMultiLineMultiSelectionMode(activateMultiLineMultiSelectionMode); emit signalTrackEntrySelectionChanged(nullptr); // forward to subscribers } void ScriptTimeline::slotSelectTrack() { const QList trackWidgetList = getTrackWidgets(); for (const TrackWidget *tw : trackWidgetList) { if (tw->isSelected()) { //ui->pbDeleteTracks->setEnabled(true); return; } } //ui->pbDeleteTracks->setEnabled(false); // none selected, as the code comes here } void ScriptTimeline::updateEverything() { //qWarning() << "updateEverything"; // update tools' width TrackWidget *tw = ui->trackScrollArea->findChild(); // get one, unspecific tw (doesn't matter which, because they all have the same length) if (tw != nullptr) m_tools->m_paintWidth = tw->width(); ui->indicator->updateMe(); updateAllTracks(); update(); } void ScriptTimeline::slotScrollbarValueChanged() { m_tools->m_scrollPos = ui->trackScrollBar->value(); updateEverything(); } } // namespace LtsScriptTimeline