1653 lines
36 KiB
C++
Executable File
1653 lines
36 KiB
C++
Executable File
#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 <LtsWidgets/lts_message_dialog.h>
|
|
|
|
#include <QPainter>
|
|
#include <QBrush>
|
|
#include <QColor>
|
|
#include <QByteArray>
|
|
#include <QDataStream>
|
|
#include <QKeyEvent>
|
|
#include <QJsonDocument>
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include <QJsonParseError>
|
|
#include <QJsonValue>
|
|
#include <QGuiApplication>
|
|
#include <QApplication>
|
|
|
|
#include <cassert>
|
|
|
|
|
|
#include <QtCore/qglobal.h>
|
|
|
|
|
|
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<quint8>(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<TrackEntryType>(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<TrackEntry *> &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<TrackEntry *> 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<TrackEntry *> &preview, int dropTime_s, QVector<TrackEntry *> &vecCollisions, bool ignoreSelected, bool ignoreSelfCheck)
|
|
{
|
|
QVector<TrackEntry *> _preview = preview;
|
|
sortTrackEntriesChronologically(_preview);
|
|
|
|
vecCollisions.clear();
|
|
|
|
QVector<TrackEntry *> vecEntriesToCheck;
|
|
const QVector<TrackEntry *> &trackEntries = tcw->getTrackEntries();
|
|
|
|
if (ignoreSelected)
|
|
{
|
|
|
|
const QVector<TrackEntry *> &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<TrackEntry *> &vecPreviewTrackEntries, int dropTime_s)
|
|
{
|
|
QVector<TrackEntry *> 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<TrackEntry *> &preview, int dropTime_s, QVector<TrackEntry *> &vecParallelTopics, bool ignoreSelectedTopics, bool hack)
|
|
{
|
|
|
|
{
|
|
QVector<TrackEntryType> vecParallelForbiddenFilter = { TrackEntryType::Building, TrackEntryType::Audio, TrackEntryType::Video, TrackEntryType::SituationMap, TrackEntryType::Situation };
|
|
QVector<TrackEntry *> previewCopy(preview);
|
|
sortTrackEntriesChronologically(previewCopy);
|
|
QVector<TrackEntry *> 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<TrackEntry *> previewCopy(preview);
|
|
sortTrackEntriesChronologically(previewCopy);
|
|
QVector<QPair<TrackEntryType, TrackEntryType> > differentParallelForbidden =
|
|
{
|
|
{ TrackEntryType::Situation, TrackEntryType::Snapshot },
|
|
{ TrackEntryType::Situation, TrackEntryType::SituationReplay },
|
|
{ TrackEntryType::Situation, TrackEntryType::SituationMap },
|
|
};
|
|
QVector<TrackEntry *> 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<TrackEntryType, TrackEntryType> &pair)
|
|
{
|
|
bool x = (pair.first == pTE->m_type || pair.second == pTE->m_type);
|
|
if (x)
|
|
qDebug() << ">>"<<step<<".1 type" << (int)pTE->m_type << "in type list - not removing";
|
|
return x;
|
|
});
|
|
|
|
if (x == differentParallelForbidden.constEnd())
|
|
qDebug() << ">>"<<step<<".2 type" << (int)pTE->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<TrackEntryType> correspondingDifferentTopicTypes;
|
|
|
|
for (TrackEntry *previewEntry : qAsConst(previewCopy))
|
|
{
|
|
correspondingDifferentTopicTypes.clear();
|
|
|
|
|
|
int startOffset_s = previewEntry->m_startTime;
|
|
|
|
if (!hack)
|
|
startOffset_s -= startTimeMostLeft_s;
|
|
|
|
|
|
for (const QPair<TrackEntryType, TrackEntryType> &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<TrackEntry *> &vecPreviewTrackEntries, int dropTime_s, bool hack)
|
|
{
|
|
QVector<TrackEntry *> 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<TrackEntry *> 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<TrackEntry *> &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<TrackEntry *>(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<TrackEntry *> cols;
|
|
|
|
int newTime = startTimeMostLeft + offset;
|
|
checkForCollisions(this, m_selected, newTime, cols, true);
|
|
|
|
QVector<TrackEntry *> 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<TrackEntry *> &topics, int offsetTime, int pasteTime) const
|
|
{
|
|
TemporaryPointerVector<TrackEntry *> 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<TrackEntry *> 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<TrackEntry *> &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<TrackEntry *> TrackContentWidget::dataToItems(const QByteArray &data, int &offset)
|
|
{
|
|
if (data.isEmpty())
|
|
return {};
|
|
|
|
QDataStream dataStream(data);
|
|
|
|
int count;
|
|
dataStream >> count;
|
|
dataStream >> offset;
|
|
|
|
QVector<TrackEntry *> 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<TrackEntry *> 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<TrackEntry *> &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<TrackEntry *> 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<TrackEntry *> 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<TrackEntry *> &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();
|
|
}
|
|
|
|
}
|