Files
qt6-migration-pipeline/Lts/Common/LtsTimeline/timeline_model.cpp
T

762 lines
16 KiB
C++
Executable File

#include "timeline_model.h"
#include "timeline_view_drag_handler.h"
#include "track.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QDebug>
namespace LtsTimeline
{
TimelineModel::TimelineModel(TimelineView *view, QObject *parent)
: QAbstractItemModel(parent)
, m_view(view)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.TimelineModel"))
{
m_rootTrack.m_id = nextFreeTrackId();
m_trackCache.insert(std::pair<int, Track *>(m_rootTrack.m_id, &m_rootTrack));
}
void TimelineModel::clear()
{
m_rootTrack.m_childrenTracks.clear();
m_rootTrack.m_items.clear();
m_rootTrack.m_copyItemIds.clear();
m_trackCache.clear();
m_itemCache.clear();
m_currentTrackId = 1;
m_currentItemId = 0;
m_trackCache.insert(std::pair<int, Track *>(m_rootTrack.m_id, &m_rootTrack));
}
void TimelineModel::clearItems()
{
for (std::pair<int , Track *> p : m_trackCache)
p.second->clearItems();
m_itemCache.clear();
m_currentItemId = 0;
}
Track &TimelineModel::track(int id) noexcept(false)
{
if (!m_trackCaching)
{
if (id == m_rootTrack.id())
return m_rootTrack;
return m_rootTrack.track(id);
}
auto it = m_trackCache.find(id);
if (it == m_trackCache.end())
throw QStringLiteral("Model: track with id %1 not found in cache").arg(id);
return *it->second;
}
const Track &TimelineModel::track(int id) const noexcept(false)
{
if (!m_trackCaching)
{
if (id == m_rootTrack.id())
return m_rootTrack;
return m_rootTrack.track(id);
}
std::map<int , Track *>::const_iterator it = m_trackCache.find(id);
if (it == m_trackCache.cend())
throw QStringLiteral("Model: const track with id %1 not found in cache").arg(id);
return *it->second;
}
Item &TimelineModel::item(int id) noexcept(false)
{
if (!m_itemCaching)
return m_rootTrack.item(id);
auto it = m_itemCache.find(id);
if (it == m_itemCache.end())
throw QStringLiteral("Model: item with id %1 not found in cache").arg(id);
return *it->second;
}
const Item &TimelineModel::item(int id) const noexcept(false)
{
if (!m_itemCaching)
return m_rootTrack.item(id);
decltype(m_itemCache)::const_iterator it = m_itemCache.find(id);
if (it == m_itemCache.cend())
throw QStringLiteral("Model: const item with id %1 not found in cache").arg(id);
return *it->second;
}
const QVector<int> TimelineModel::itemIds() const
{
if (!m_itemCaching)
return m_rootTrack.itemIds(true);
QVector<int> ids;
std::transform(m_itemCache.cbegin(), m_itemCache.cend(), std::back_inserter(ids),
[](const std::pair<int, Item *> &pair)
{
return pair.first;
});
return ids;
}
const QVector<int> TimelineModel::itemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted) const
{
return m_rootTrack.itemIdsBetween(start, end, strict, complete, recursive, sorted);
}
bool TimelineModel::isItemsBetween(qint64 start, qint64 end, const QVector<int> &ignoreIds, bool strict, bool complete, bool recursive) const
{
return m_rootTrack.isItemsBetween(start, end, ignoreIds, strict, complete, recursive);
}
const QVector<int> TimelineModel::copyItemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool sorted) const
{
return m_rootTrack.copyItemIdsBetween(start, end, strict, complete, true, sorted);
}
bool TimelineModel::isItemsBetween(qint64 start, qint64 end, int ignoreId, bool strict, bool complete, bool recursive) const
{
return m_rootTrack.isItemsBetween(start, end, ignoreId, strict, complete, recursive);
}
const QVector<int> TimelineModel::_copyItemIdsBetween(Track &t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const
{
QVector<int> itemIds;
itemIds.reserve(t.itemCount());
for (const int itemId : qAsConst(t.copyItemIds()))
{
const Item &i = item(itemId);
if (Track::isItemBetween(i, start, end, strict, complete, minimumDuration))
itemIds += i.id();
}
if (recursive)
for (const TrackPtr &trackPtr : qAsConst(t.tracks()))
itemIds += _copyItemIdsBetween(*trackPtr.get(), start, end, strict, complete, true, false, minimumDuration);
itemIds.erase(std::unique(itemIds.begin(), itemIds.end()), itemIds.end());
itemIds.squeeze();
if (!sorted)
return itemIds;
std::sort(itemIds.begin(), itemIds.end(), [this](const int itemIdA, const int itemIdB)
{
try
{
return item(itemIdA).startTime() < item(itemIdB).startTime();
}
catch (...) { return false; }
});
return itemIds;
}
const QVector<int> TimelineModel::_itemIdsBetween(Track &t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const
{
QVector<int> itemIds;
itemIds.reserve(t.itemCount());
for (const ItemPtr &itemPtr : qAsConst(t.items()))
{
const Item &i = *itemPtr;
if (Track::isItemBetween(i, start, end, strict, complete, minimumDuration))
itemIds += i.id();
}
if (recursive)
for (const TrackPtr &trackPtr : qAsConst(t.tracks()))
itemIds += _itemIdsBetween(*trackPtr.get(), start, end, strict, complete, true, false, minimumDuration);
itemIds.squeeze();
if (!sorted)
return itemIds;
std::sort(itemIds.begin(), itemIds.end(), [this](const int itemIdA, const int itemIdB)
{
try
{
return item(itemIdA).startTime() < item(itemIdB).startTime();
}
catch (...) { return false; }
});
return itemIds;
}
bool TimelineModel::_isItemsBetween(const Track &t, qint64 start, qint64 end, const QVector<int> &ignoreIds, bool strict, bool complete, bool recursive, qint64 minimumDuration) const
{
for (const ItemPtr &itemPtr : qAsConst(t.items()))
{
const Item &i = *itemPtr;
if (!ignoreIds.contains(i.id()))
if (Track::isItemBetween(i, start, end, strict, complete, minimumDuration))
return true;
}
if (!recursive)
return false;
for (const TrackPtr &trackPtr : qAsConst(t.tracks()))
if (_isItemsBetween(*trackPtr.get(), start, end, ignoreIds, strict, complete, true, minimumDuration))
return true;
return false;
}
bool TimelineModel::_isItemsBetween(const Track &t, qint64 start, qint64 end, int ignoreId, bool strict, bool complete, bool recursive, qint64 minimumDuration) const
{
return _isItemsBetween(t, start, end, QVector<int>({ ignoreId }), strict, complete, recursive, minimumDuration);
}
QVariant TimelineModel::data(const QModelIndex &index, int role) const
{
if ((role != Qt::DisplayRole && role != Qt::UserRole) || !index.isValid())
return {};
int trackId = index.internalId();
int column = index.column();
if (column == 1 && role == Qt::DisplayRole)
return trackId;
if (role == Qt::UserRole)
{
if (column == 0)
return trackId;
try
{
const Track &t = track(trackId);
return t.userData();
}
catch(...)
{
Q_ASSERT(false);
return {};
}
}
if (column != 0)
return {};
QString text;
try
{
const Track &t = track(trackId);
#if 0
text = QStringLiteral("[%1] %3").arg(t.id()).arg(t.text());
#else
text = t.text();
#endif
}
catch(...)
{
Q_ASSERT(false);
return QStringLiteral("[?????]");
}
if (text.isEmpty())
text = tr("[unknown]");
return text;
}
Qt::ItemFlags TimelineModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return {};
return QAbstractItemModel::flags(index);
}
QModelIndex TimelineModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return {};
int childTrackId;
try
{
const Track &parentTrack = parent.isValid() ? track(parent.internalId()) : m_rootTrack;
const Track &childTrack = parentTrack.trackAt(row);
childTrackId = childTrack.id();
}
catch (const QString &msg)
{
DEBUG_LOG(m_logger) << Q_FUNC_INFO << "cannot get parent track " << msg;
return {};
}
catch (const std::out_of_range &oor)
{
DEBUG_LOG(m_logger) << Q_FUNC_INFO << "cannot get child track " << QString::fromUtf8(oor.what());
return {};
}
return createIndex(row, column, childTrackId);
}
int TimelineModel::rowOfTrackId(int id) const noexcept(false)
{
const Track &parent = track(track(id).parentId());
if (parent.items().empty())
throw("parent doesn't have items");
int row = 0;
for (const TrackPtr &trackPtr : qAsConst(parent.tracks()))
{
if (trackPtr->id() == id)
return row;
row++;
}
throw("not found");
}
const Track &TimelineModel::trackOfIndex(const QModelIndex &index) const
{
if (!index.isValid())
throw QStringLiteral("trackOfIndex: index is invalid!");
try
{
return track(index.internalId());
}
catch (const QString &msg)
{
throw QStringLiteral("trackOfIndex: cannot find track by internal id %1: %2").arg(index.internalId()).arg(msg);
}
return m_rootTrack;
}
const Track &TimelineModel::parentOfTrackId(int id) const noexcept(false)
{
if (!m_trackCaching)
{
if (m_rootTrack.hasTrack(id))
return m_rootTrack;
}
const Track &theTrack = track(id);
if (theTrack.parentId() == m_rootTrack.id())
return m_rootTrack;
return track(theTrack.parentId());
}
QModelIndex TimelineModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return {};
int trackId = index.internalId();
int parentId;
int superParentId;
try
{
const Track &parent = parentOfTrackId(trackId);
parentId = parent.id();
superParentId = parent.parentId();
}
catch (const QString &msg)
{
DEBUG_LOG(m_logger) << Q_FUNC_INFO << "cannot get parent of track with id" << trackId << msg;
return {};
}
if (parentId == m_rootTrack.id())
return {};
int parentsRow;
try
{
const Track &superParent = track(superParentId);
try
{
parentsRow = superParent.rowOfTrack(parentId);
}
catch (const QString &msg)
{
DEBUG_LOG(m_logger) << Q_FUNC_INFO << "row of track for parent not found for parent id" << parentId << "-" << msg;
return {};
}
}
catch (const QString &msg)
{
DEBUG_LOG(m_logger) << Q_FUNC_INFO << "super parent not found for super parent id" << superParentId << "-" << msg;
return {};
}
return createIndex(parentsRow, 0, parentId);
}
void TimelineModel::setItemStartTime(Track &t, Item &i, qint64 newStart)
{
i._setStartTime(newStart);
t.sort();
}
void TimelineModel::setItemStartTime(Item &i, qint64 newStart) noexcept(false)
{
setItemStartTime(track(i.parentTrackId()), i, newStart);
}
void TimelineModel::setItemStartTime(int itemId, qint64 newStart) noexcept(false)
{
setItemStartTime(item(itemId), newStart);
}
void TimelineModel::setItemDuration(Track &t, Item &i, int duration)
{
i._setDuration(std::max(duration, m_minItemDuration));
t.sort();
}
void TimelineModel::setItemDuration(Item &i, int duration) noexcept(false)
{
setItemDuration(track(i.parentTrackId()), i, duration);
}
void TimelineModel::setItemDuration(int itemId, int duration) noexcept(false)
{
setItemDuration(item(itemId), duration);
}
void TimelineModel::copyToClipboard(const QVector<int> &ids) noexcept(false)
{
m_clipboard.clear();
m_clipboard.reserve(ids.size());
qint64 tStartMin = LLONG_MAX;
for (int itemId : qAsConst(ids))
{
const Item &i = item(itemId);
ItemPtr iPtr = std::make_unique<Item>(Item(i.startTime(), i.duration(), i.color()));
iPtr->m_parentTrackId = i.m_parentTrackId;
m_clipboard.push_back(std::move(iPtr));
tStartMin = std::min(tStartMin, i.startTime());
}
for (ItemPtr &iPtr : m_clipboard)
iPtr->_setStartTime(iPtr->startTime() - tStartMin);
}
void TimelineModel::cutToClipboard(const QVector<int> &ids) noexcept(false)
{
copyToClipboard(ids);
for (int itemId : qAsConst(ids))
removeItem(itemId);
}
void TimelineModel::pasteFromClipboard(qint64 time) noexcept(false)
{
if (m_isSnapEnabled)
time -= (time % m_snapStep);
for (ItemPtr &iPtr : m_clipboard)
{
iPtr->_setStartTime(time + iPtr->startTime());
addItem(iPtr->parentTrackId(), std::move(iPtr));
}
m_clipboard.clear();
}
int TimelineModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
if (!parent.isValid())
return m_rootTrack.tracks().size();
try
{
return track(parent.internalId()).tracks().size();
}
catch(...) {}
return 0;
}
QModelIndex TimelineModel::indexOfTrack(int trackId) const
{
for (int row = 0; row < rowCount(); ++row)
{
QModelIndex idx = indexOfTrackFromIndex(index(row, 0, {}), trackId);
if (idx.isValid())
return idx;
}
return {};
}
QModelIndex TimelineModel::indexOfTrackFromIndex(const QModelIndex &idx, int trackId) const
{
if (static_cast<int>(idx.internalId()) == trackId)
return index(idx.row(), 1, idx.parent());
for (int row = 0; row < rowCount(idx); ++row)
{
const QModelIndex i = indexOfTrackFromIndex(index(row, 0, idx), trackId);
if (i.isValid())
return i;
}
return {};
}
QModelIndex TimelineModel::indexOfTrackSafe(int trackId) const noexcept(false)
{
for (int row = 0; row < rowCount(); ++row)
{
const QModelIndex idx(indexOfTrackFromIndex(index(row, 0 , {}), trackId));
if (idx.isValid())
return idx;
}
QString trackText;
try
{
trackText = track(trackId).text();
} catch(...){}
throw QStringLiteral("indexOfTrackSafe: index for trackId %1 ('%2') not found ").arg(trackId).arg(trackText.isEmpty() ? QStringLiteral("(unknown)") : trackText);
}
QModelIndex TimelineModel::indexOfTrackFromIndexSafe(const QModelIndex &idx, int trackId) const noexcept(false)
{
if (static_cast<int>(idx.internalId()) == trackId)
return idx;
for (int row = 0; row < rowCount(idx); ++row)
{
const QModelIndex i = indexOfTrackFromIndex(index(row, 1, idx), trackId);
if (i.isValid())
return i;
}
throw QStringLiteral("indexOfTrackFromIndexSafe: index for trackId %1 not found").arg(trackId);
}
void TimelineModel::moveItemToTrack(int itemId, int trackId, qint64 time) noexcept(false)
{
const Item &theItem = item(itemId);
Track &oldTrack = track(theItem.parentTrackId());
Track &newTrack = track(trackId);
ItemPtr itemPtr = oldTrack.takeItem(itemId);
Item *theItemPtr = newTrack.addItem(std::move(itemPtr));
setItemStartTime(newTrack, *theItemPtr, time);
theItemPtr->m_parentTrackId = trackId;
}
void TimelineModel::removeItem(int itemId) noexcept(false)
{
const Item &i = item(itemId);
Track &t = track(i.parentTrackId());
t.removeItem(itemId);
auto it = m_itemCache.find(itemId);
if (it == m_itemCache.end())
{
Q_ASSERT(false);
throw QStringLiteral("removeItem: item with id %1 not found").arg(itemId);
}
m_itemCache.erase(it);
}
void TimelineModel::removeTrack(int trackId) noexcept(false)
{
if (trackId == m_rootTrack.id())
{
Q_ASSERT(false);
throw QStringLiteral("removeTrack: the root track shouldn't and *can't* be deleted");
}
const Track &t = track(trackId);
Track &parentTrack = track(t.parentId());
for (int itemId : t.itemIds(true))
{
auto it = m_itemCache.find(itemId);
if (it == m_itemCache.end())
{
Q_ASSERT(false);
continue;
}
m_itemCache.erase(it);
}
parentTrack.removeTrack(trackId);
auto it = m_trackCache.find(trackId);
if (it == m_trackCache.end())
{
Q_ASSERT(false);
return;
}
m_trackCache.erase(it);
}
void TimelineModel::setMinimumDuration(int duration)
{
if (duration < 0)
{
WARNING_LOG(m_logger) << "negative duration is not possible -> setting minimum duration to 0";
duration = 0;
}
if (duration % 1000 && m_isSnapEnabled)
WARNING_LOG(m_logger) << "Snapping to 1000ms enabled, yet minimum duration is not a multiple of 1000ms: " << duration;
m_minItemDuration = duration;
}
bool TimelineModel::canDropItem(const Item &item, int trackId, qint64 start) const
{
Q_UNUSED(item);
Q_UNUSED(trackId);
Q_UNUSED(start);
return true;
}
bool TimelineModel::canMoveItem(const Item &item, qint64 start) const
{
Q_UNUSED(item);
Q_UNUSED(start);
return true;
}
bool TimelineModel::canResizeItem(const Item &item, qint64 start, int duration) const
{
Q_UNUSED(item);
Q_UNUSED(start);
Q_UNUSED(duration);
return true;
}
bool TimelineModel::isItemDragged(int _itemId) const
{
return m_view->dragHandler() ? m_view->dragHandler()->dragItemIds().contains(_itemId) : false;
}
}