#include "timeline_model.h" #include "timeline_view_drag_handler.h" #include "track.h" #include #include #include namespace LtsTimeline { TimelineModel::TimelineModel(TimelineView *view, QObject *parent) : QAbstractItemModel(parent) , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.TimelineModel")) , m_view(view) { m_rootTrack.m_id = nextFreeTrackId(); m_trackCache.insert(std::pair(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(m_rootTrack.m_id, &m_rootTrack)); } void TimelineModel::clearItems() { for (std::pair 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::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 TimelineModel::itemIds() const { if (!m_itemCaching) return m_rootTrack.itemIds(true); QVector ids; std::transform(m_itemCache.cbegin(), m_itemCache.cend(), std::back_inserter(ids), [](const std::pair& pair) { return pair.first; }); return ids; } const QVector 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& ignoreIds, bool strict, bool complete, bool recursive) const { return m_rootTrack.isItemsBetween(start, end, ignoreIds, strict, complete, recursive); } const QVector 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 TimelineModel::_copyItemIdsBetween(Track& t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const { QVector itemIds; itemIds.reserve(t.itemCount()); for (const int itemId : std::as_const(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 : std::as_const(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 TimelineModel::_itemIdsBetween(Track& t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const { QVector itemIds; itemIds.reserve(t.itemCount()); for (const ItemPtr& itemPtr : std::as_const(t.items())) { const Item& i = *itemPtr; if (Track::isItemBetween(i, start, end, strict, complete, minimumDuration)) itemIds += i.id(); } if (recursive) for (const TrackPtr& trackPtr : std::as_const(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& ignoreIds, bool strict, bool complete, bool recursive, qint64 minimumDuration) const { for (const ItemPtr& itemPtr : std::as_const(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 : std::as_const(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({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 : std::as_const(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& ids) noexcept(false) { m_clipboard.clear(); m_clipboard.reserve(ids.size()); qint64 tStartMin = LLONG_MAX; for (int itemId : std::as_const(ids)) { const Item& i = item(itemId); ItemPtr iPtr = std::make_unique(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& ids) noexcept(false) { copyToClipboard(ids); for (int itemId : std::as_const(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(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(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; } } // namespace LtsTimeline