diff --git a/src/engraving/api/v1/score.cpp b/src/engraving/api/v1/score.cpp index cb127b2cf3dfc..988b32f3c8e02 100644 --- a/src/engraving/api/v1/score.cpp +++ b/src/engraving/api/v1/score.cpp @@ -560,6 +560,24 @@ QQmlListProperty Score::systems() const return wrapContainerProperty(this, score()->systems()); } +/** Exposes the score's live measure-base list without a shared temporary container. */ +QQmlListProperty Score::measures() +{ + return QQmlListProperty(this, score()->measures(), + /** Returns the current number of measures and frames. */ + [](QQmlListProperty* list) -> qsizetype { + return static_cast(list->data)->size(); + }, /** Returns the wrapped item at index, or null when the index is invalid. */ + [](QQmlListProperty* list, qsizetype index) -> MeasureBase* { + const auto* measures = static_cast(list->data); + if (index < 0 || index >= measures->size()) { + return nullptr; + } + engraving::MeasureBase* measure = measures->at(static_cast(index)); + return qobject_cast(wrap(measure, Ownership::SCORE)); + }); +} + QQmlListProperty Score::brackets(int staffIdx) { return wrapContainerProperty(this, score()->brackets(static_cast(staffIdx))); diff --git a/src/engraving/api/v1/score.h b/src/engraving/api/v1/score.h index ae74d77cf7756..0f8daa7d4acfa 100644 --- a/src/engraving/api/v1/score.h +++ b/src/engraving/api/v1/score.h @@ -152,6 +152,15 @@ class Score : public apiv1::ScoreElement, public muse::Contextable */ Q_PROPERTY(QQmlListProperty pages READ pages) + /** APIDOC + * List of measures and frames in score order, including individual measures + * within multimeasure rests. This list does not expand playback repeats. + * @readonly + * @q_property {Engraving.MeasureBase[]} + * @since 5.0 + */ + Q_PROPERTY(QQmlListProperty measures READ measures) + /** APIDOC * Page numbering offset. The user-visible number of the given page is defined as * ``` @@ -397,6 +406,8 @@ class Score : public apiv1::ScoreElement, public muse::Contextable int npages() const { return static_cast(score()->npages()); } QQmlListProperty pages() const; + /** Returns read-only access to the score's measures and frames. */ + QQmlListProperty measures(); int pageNumberOffset() const { return score()->pageNumberOffset(); } void setPageNumberOffset(int offset) { score()->undoChangePageNumberOffset(offset); } diff --git a/src/engraving/dom/measurebase.cpp b/src/engraving/dom/measurebase.cpp index 3a7043c503f50..55b619b2671c0 100644 --- a/src/engraving/dom/measurebase.cpp +++ b/src/engraving/dom/measurebase.cpp @@ -754,8 +754,25 @@ MeasureBaseList::MeasureBaseList() m_size = 0; } +/** Returns a score-order item in constant time after lazily rebuilding the index. */ +MeasureBase* MeasureBaseList::at(size_t index) const +{ + if (index >= static_cast(m_size)) { + return nullptr; + } + if (m_index.empty()) { + m_index.reserve(m_size); + for (MeasureBase* measure = m_first; measure; measure = measure->next()) { + m_index.push_back(measure); + } + } + return m_index.at(index); +} + +/** Empties the list and invalidates its lookup indexes without deleting its items. */ void MeasureBaseList::clear() { + m_index.clear(); m_first = nullptr; m_last = nullptr; m_size = 0; @@ -766,8 +783,10 @@ void MeasureBaseList::clear() // push_back //--------------------------------------------------------- +/** Links an item at the end and invalidates indexed access. */ void MeasureBaseList::push_back(MeasureBase* m) { + m_index.clear(); ++m_size; if (m_last) { m_last->setNext(m); @@ -785,8 +804,10 @@ void MeasureBaseList::push_back(MeasureBase* m) // push_front //--------------------------------------------------------- +/** Links an item at the beginning and invalidates indexed access. */ void MeasureBaseList::push_front(MeasureBase* m) { + m_index.clear(); ++m_size; if (m_first) { m_first->setPrev(m); @@ -805,8 +826,10 @@ void MeasureBaseList::push_front(MeasureBase* m) // insert m before m->next() //--------------------------------------------------------- +/** Inserts an item before its next item and invalidates indexed access. */ void MeasureBaseList::add(MeasureBase* m) { + m_index.clear(); MeasureBase* el = m->next(); if (el == 0) { append(m); @@ -826,8 +849,10 @@ void MeasureBaseList::add(MeasureBase* m) // remove //--------------------------------------------------------- +/** Unlinks one item and invalidates indexed access without deleting the item. */ void MeasureBaseList::remove(MeasureBase* m) { + m_index.clear(); --m_size; if (m->prev()) { m->prev()->setNext(m->next()); @@ -845,8 +870,10 @@ void MeasureBaseList::remove(MeasureBase* m) // insert //--------------------------------------------------------- +/** Inserts an inclusive linked range and invalidates indexed access. */ void MeasureBaseList::insert(MeasureBase* fm, MeasureBase* lm) { + m_index.clear(); for (MeasureBase* m = fm; m != lm; m = m->next()) { ++m_size; } @@ -869,8 +896,10 @@ void MeasureBaseList::insert(MeasureBase* fm, MeasureBase* lm) // remove //--------------------------------------------------------- +/** Unlinks an inclusive range and invalidates indexed access without deleting its items. */ void MeasureBaseList::remove(MeasureBase* fm, MeasureBase* lm) { + m_index.clear(); for (MeasureBase* m = fm; m != lm; m = m->next()) { --m_size; } @@ -893,8 +922,10 @@ void MeasureBaseList::remove(MeasureBase* fm, MeasureBase* lm) // change //--------------------------------------------------------- +/** Replaces an item in place and invalidates indexed access even when the size is unchanged. */ void MeasureBaseList::change(MeasureBase* ob, MeasureBase* nb) { + m_index.clear(); nb->setPrev(ob->prev()); nb->setNext(ob->next()); if (ob->prev()) { diff --git a/src/engraving/dom/measurebase.h b/src/engraving/dom/measurebase.h index 841ed9a76439a..c451c3750a45d 100644 --- a/src/engraving/dom/measurebase.h +++ b/src/engraving/dom/measurebase.h @@ -208,6 +208,8 @@ class MeasureBaseList MeasureBaseList(); MeasureBase* first() const { return m_first; } MeasureBase* last() const { return m_last; } + /** Returns the item at index, or null if out of bounds; rebuilds the index after list changes. */ + MeasureBase* at(size_t index) const; void clear(); void add(MeasureBase*); void remove(MeasureBase*); @@ -233,6 +235,9 @@ class MeasureBaseList MeasureBase* m_first = nullptr; MeasureBase* m_last = nullptr; + // Non-owning index, cleared by every structural mutation and rebuilt on demand. + mutable std::vector m_index; + // At a tick there can be any number of MeasureBases // There can only be one Measure // There can be any number of Boxes