Files
2026-08-13 18:33:10 +02:00

1203 lines
31 KiB
C++
Executable File

#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 <LtsWidgets/lts_message_dialog.h>
#include <QMessageBox>
#include <QPixmap>
#include <QTimer>
#include <QTime>
#include <QRubberBand>
#include <QCursor>
#include <QDebug>
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<TrackWidget *>(obj)); in copyTopics()
m_ignoreChanges = true;
qDeleteAll(m_clipboard); // clear(); <= dangerous
delete ui;
}
void ScriptTimeline::selectAll()
{
const QList<TrackWidget *> vecTrackWidgets = getTrackWidgets();
for (TrackWidget *tw : vecTrackWidgets)
{
TrackContentWidget *tcw = tw->getTrackContentWidget();
tcw->selectAll();
}
}
bool ScriptTimeline::checkNewLength(TrackEntry *te, int newLength)
{
const QList<TrackWidget *> tws = getTrackWidgets();
TrackContentWidget *tesTcw = nullptr;
// find parent tcw of te
for (TrackWidget *tw : tws)
{
const QVector<TrackEntry *> &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<TrackWidget *> 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<TrackWidget *> 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<TrackWidget *> vecTrackWidgets = getTrackWidgets();
for (TrackWidget *tw : vecTrackWidgets)
tw->setReadonly(readonly);
}
void ScriptTimeline::clear()
{
m_copyTopics.clear();
const QList<TrackWidget *> 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<TrackWidget *> trackWidgetList = getTrackWidgets();
for (const TrackWidget *tw : trackWidgetList)
{
const QVector<TrackEntry *> &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<TrackWidget *> 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<TrackWidget *> trackWidgetList = getTrackWidgets();
for (TrackWidget *tw : trackWidgetList)
tw->setIndicatorTime(time);
emit signalHeaderIndicatorTimeChanged(time);
}
void ScriptTimeline::slotHeaderHandleResized(int newSize)
{
m_baseTrackWidgetWidth = newSize;
updateEverything();
const QList<TrackWidget *> trackWidgetList = getTrackWidgets();
for (TrackWidget *tw : trackWidgetList)
tw->setBaseWidth(newSize);
}
void ScriptTimeline::slotDeleteTracks()
{
int count = 0;
const QList<TrackWidget *> 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<TrackWidget *> trackWidgetList = getTrackWidgets();
for (TrackWidget *tw : trackWidgetList)
tw->unselectAll();
}
int ScriptTimeline::getFilterTrackEntryRealTime(TrackEntry *te)
{
const QList<TrackWidget *> trackWidgetList = getTrackWidgets();
for (TrackWidget *tw : trackWidgetList)
{
for (FilterTrackWidget *ftw : tw->filterLines())
{
const QMap<int /*id*/, TrackContentWidget *> &filterContentWidgets = ftw->fbw()->filterContentWidgets();
int foundMainTrackEntryId = -1;
for (auto itFcw = filterContentWidgets.begin(); itFcw != filterContentWidgets.end(); ++itFcw)
{
const TrackContentWidget *tcw = itFcw.value();
const QVector<TrackEntry *> &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<TrackEntry *> &trackEntries = tw->getTrackContentWidget()->getTrackEntries();
for (const TrackEntry *mainTe : trackEntries)
{
if (mainTe->m_topicId == foundMainTrackEntryId)
return mainTe->m_startTime + te->m_startTime;
}
}
}
}
return -1;
}
QList<TrackWidget *> ScriptTimeline::getTrackWidgets() const
{
return ui->trackScrollArea->findChildren<TrackWidget *>();
}
TrackEntry *ScriptTimeline::getTrackEntryWithFiltersAsEntries(int topicId)
{
const QList<TrackWidget *> trackWidgetList = getTrackWidgets();
for (const TrackWidget *tw : trackWidgetList)
{
const QVector<TrackEntry *> trackEntries = tw->getTrackEntriesFiltersAsEntries();
for (TrackEntry *te : qAsConst(trackEntries))
{
if (te->m_topicId == topicId)
return te;
}
}
return nullptr;
}
TrackEntry *ScriptTimeline::getTrackEntry(int topicId)
{
const QList<TrackWidget *> trackWidgetList = getTrackWidgets();
for (const TrackWidget *tw : trackWidgetList)
{
const QVector<TrackEntry *> trackEntries = tw->getTrackEntries();
for (TrackEntry *te : trackEntries)
{
if (te->m_topicId == topicId)
return te;
}
}
return nullptr;
}
QVector<TrackEntry *> ScriptTimeline::getTrackEntriesWithFiltersAsEntries(bool selectedEntriesOnly) const
{
QVector<TrackEntry *> tes;
const QList<TrackWidget *> trackWidgetList = getTrackWidgets();
for (TrackWidget *tw : trackWidgetList)
tes += tw->getTrackEntriesFiltersAsEntries(selectedEntriesOnly);
return tes;
}
QMap<QString, QVector<TrackEntry *> > ScriptTimeline::getTrackEntriesWithFilters(bool selectedEntriesOnly) const
{
QMap<QString, QVector<TrackEntry *> > trackList;
const QList<TrackWidget *> trackWidgetList = getTrackWidgets();
for (const TrackWidget *tw : trackWidgetList)
{
QString trackName(tw->trackName());
const QVector<TrackEntry *> trackEntries = tw->getTrackEntriesWithFilters(selectedEntriesOnly);
trackList.insert(trackName, trackEntries);
}
return trackList;
}
QVector<TrackEntry *> ScriptTimeline::getTrackEntries(bool selectedEntriesOnly) const
{
QVector<TrackEntry *> tes;
const QList<TrackWidget *> trackWidgetList = getTrackWidgets();
for (const TrackWidget *tw : trackWidgetList)
tes += tw->getTrackEntries(selectedEntriesOnly);
return tes;
}
bool ScriptTimeline::importTrackEntries(const QVector<LtsScriptTimeline::TrackEntry *> &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<TrackEntry *> 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<LtsScriptTimeline::TrackEntry *> 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<TrackWidget *> 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<TrackEntry *> ScriptTimeline::getSelectedEntriesWithFilterEntries() const
{
QVector<TrackEntry *> tes;
const QList<TrackWidget *> 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<TrackWidget *> 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<TrackWidget *> tws = getTrackWidgets();
for (TrackWidget *tw : tws)
tw->updateMe();
}
QVector<TrackEntry *> ScriptTimeline::getSelectedFilterEntries() const
{
QVector<TrackEntry *> result;
const QList<TrackWidget *> tws = getTrackWidgets();
for (const TrackWidget *tw : qAsConst(tws))
{
result += tw->getSelectedFilterEntries();
}
return result;
}
void ScriptTimeline::slotDeleteSelectedTopics()
{
int topicCount = 0;
const QList<TrackWidget *> 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<TrackWidget *> tws = getTrackWidgets();
for (const TrackWidget *tw : qAsConst(tws))
{
// delete all selected filter topics
const QVector<FilterTrackWidget *> &ftws = tw->filterLines();
for (const FilterTrackWidget *ftw : qAsConst(ftws))
{
const FilterBackgroundWidget *fbw = ftw->fbw();
const QMap<int /*id*/, TrackContentWidget *> &fcws = fbw->filterContentWidgets();
for (TrackContentWidget *tcw: fcws)
tcw->deleteSelectedTopics();
}
TrackContentWidget *tcw = tw->getTrackContentWidget();
tcw->deleteSelectedTopics();
}
});
}
void ScriptTimeline::slotCutTopics()
{
slotCopyTopics();
const QList<TrackWidget *> tws = getTrackWidgets();
for (const TrackWidget *tw : qAsConst(tws))
{
TrackContentWidget *tcw = tw->getTrackContentWidget();
tcw->deleteSelectedTopics();
}
}
void ScriptTimeline::slotCopyTopics()
{
const QList<TrackWidget *> tws = getTrackWidgets();
int firstTeTime = -1;
// get firstTeTime first
for (const TrackWidget *tw : qAsConst(tws))
{
TrackContentWidget *tcw = tw->getTrackContentWidget();
const QVector<TrackEntry *> &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<TrackWidget *>(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<TrackEntry *> 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<TrackEntry *> 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<TrackWidget *> 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<TrackContentWidget *, bool> &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<TrackContentWidget *>(sender());
if (senderTcw == nullptr)
return;
m_mlmsTCWStates.insert(senderTcw, false);
const QList<TrackWidget *> 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<TrackContentWidget *>(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<TrackWidget *> 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<TrackContentWidget *>(sender());
if (senderTcw == nullptr)
return;
// if (senderTcw != m_mlmsMaster)
// return;
const QList<TrackWidget *> 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<TrackContentWidget *>(sender());
if (senderTcw == nullptr)
return;
// only the master sends, so, not needed
if (senderTcw != m_mlmsMaster)
return;
const QList<TrackWidget *> 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<TrackContentWidget *>(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<TrackWidget *> 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<TrackWidget *> 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<TrackWidget *> 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<TrackWidget *> 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<TrackWidget *>(); // 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