#include "timeline_model.h" #include "timeline_view_drag_handler.h" #include "track.h" #include #include 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(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 : 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 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 : 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 &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({ 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 &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(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 : 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(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; } }