LCOV - code coverage report
Current view: top level - src/notationscene/qml/MuseScore/NotationScene - abstractnotationpaintview.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 1026 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "abstractnotationpaintview.h"
      24              : 
      25              : #include <QCursor>
      26              : #include <QPainter>
      27              : #include <QMimeData>
      28              : 
      29              : #include "async/async.h"
      30              : 
      31              : #include "engraving/dom/score.h"
      32              : #include "engraving/dom/shadownote.h"
      33              : 
      34              : #include "notation/imasternotation.h" // IWYU pragma: keep
      35              : #include "notation/inotationaccessibility.h" // IWYU pragma: keep
      36              : #include "notation/inotationautomation.h"
      37              : #include "notation/inotationelements.h"
      38              : #include "notation/inotationnoteinput.h"
      39              : #include "notation/inotationpainting.h" // IWYU pragma: keep
      40              : #include "notation/inotationselection.h"
      41              : #include "notation/inotationstyle.h"
      42              : #include "notation/inotationviewstate.h"
      43              : 
      44              : #include "log.h"
      45              : 
      46              : using namespace mu;
      47              : using namespace mu::notation;
      48              : using namespace muse;
      49              : using namespace muse::ui;
      50              : using namespace muse::draw;
      51              : 
      52              : static constexpr qreal SCROLL_LIMIT_OFF_OVERSCROLL_FACTOR = 0.75;
      53              : 
      54            0 : AbstractNotationPaintView::AbstractNotationPaintView(QQuickItem* parent)
      55            0 :     : muse::uicomponents::QuickPaintedView(parent), muse::Contextable(muse::iocCtxForQmlObject(this))
      56              : {
      57            0 :     setFlag(ItemHasContents, true);
      58            0 :     setFlag(ItemAcceptsDrops, true);
      59            0 :     setAcceptedMouseButtons(Qt::AllButtons);
      60              : 
      61            0 :     connect(this, &QQuickPaintedItem::widthChanged, this, &AbstractNotationPaintView::onViewSizeChanged);
      62            0 :     connect(this, &QQuickPaintedItem::heightChanged, this, &AbstractNotationPaintView::onViewSizeChanged);
      63              : 
      64            0 :     connect(this, &AbstractNotationPaintView::horizontalScrollChanged, [this]() {
      65            0 :         m_previousHorizontalScrollPosition = startHorizontalScrollPosition();
      66            0 :     });
      67              : 
      68            0 :     connect(this, &AbstractNotationPaintView::verticalScrollChanged, [this]() {
      69            0 :         m_previousVerticalScrollPosition = startVerticalScrollPosition();
      70            0 :     });
      71              : 
      72            0 :     m_enableAutoScrollTimer.setSingleShot(true);
      73            0 :     connect(&m_enableAutoScrollTimer, &QTimer::timeout, this, [this]() {
      74            0 :         m_autoScrollEnabled = true;
      75            0 :     });
      76              : 
      77            0 :     connect(&m_updatePlaybackCursorInterpolatedTimer, &QTimer::timeout, this, [this]() {
      78            0 :         updatePlaybackCursorInterpolated();
      79            0 :     });
      80            0 : }
      81              : 
      82            0 : AbstractNotationPaintView::~AbstractNotationPaintView()
      83              : {
      84            0 :     m_inputController->deinit();
      85              : 
      86            0 :     if (m_notation && isMainView()) {
      87            0 :         m_notation->accessibility()->setMapToScreenFunc(nullptr);
      88            0 :         m_notation->interaction()->setGetViewRectFunc(nullptr);
      89              :     }
      90              : 
      91            0 :     clear();
      92            0 : }
      93              : 
      94            0 : void AbstractNotationPaintView::load()
      95              : {
      96            0 :     TRACEFUNC;
      97              : 
      98            0 :     m_loadCalled = true;
      99            0 :     m_inputController = std::make_unique<NotationViewInputController>(this, iocContext());
     100              : 
     101              :     // Clip automation lines to the view bounds
     102            0 :     m_automationLinesContainer = new QQuickItem(this);
     103            0 :     m_automationLinesContainer->setClip(true);
     104            0 :     m_automationLinesContainer->setWidth(width());
     105            0 :     m_automationLinesContainer->setHeight(height());
     106            0 :     connect(this, &QQuickItem::widthChanged, m_automationLinesContainer, [this]() {
     107            0 :         m_automationLinesContainer->setWidth(width());
     108            0 :     });
     109            0 :     connect(this, &QQuickItem::heightChanged, m_automationLinesContainer, [this]() {
     110            0 :         m_automationLinesContainer->setHeight(height());
     111            0 :     });
     112              : 
     113            0 :     m_notationAutomationController = std::make_unique<NotationAutomationController>(m_automationLinesContainer, iocContext());
     114            0 :     m_playbackCursor = std::make_unique<PlaybackCursor>(iocContext());
     115            0 :     m_playbackCursor->setVisible(false);
     116            0 :     m_noteInputCursor = std::make_unique<NoteInputCursor>(iocContext(), notationConfiguration()->thinNoteInputCursor());
     117            0 :     m_ruler = std::make_unique<NotationRuler>(iocContext());
     118              : 
     119            0 :     m_loopInMarker = std::make_unique<LoopMarker>(LoopBoundaryType::LoopIn, iocContext());
     120            0 :     m_loopOutMarker = std::make_unique<LoopMarker>(LoopBoundaryType::LoopOut, iocContext());
     121              : 
     122            0 :     m_continuousPanel = std::make_unique<ContinuousPanel>();
     123              : 
     124            0 :     m_inputController->setReadonly(m_readonly);
     125            0 :     m_inputController->init();
     126              : 
     127            0 :     m_elapsedTimer.start();
     128              : 
     129            0 :     onNotationSetup();
     130              : 
     131            0 :     initBackground();
     132            0 :     initNavigatorOrientation();
     133              : 
     134            0 :     configuration()->isLimitCanvasScrollAreaChanged().onNotify(this, [this]() {
     135            0 :         ensureViewportInsideScrollableArea();
     136              : 
     137            0 :         emit horizontalScrollChanged();
     138            0 :         emit verticalScrollChanged();
     139            0 :         emit viewportChanged();
     140            0 :     }, async::Asyncable::Mode::SetReplace);
     141              : 
     142            0 :     m_isAutomaticallyPanEnabled = notationConfiguration()->isAutomaticallyPanEnabled();
     143            0 :     notationConfiguration()->isAutomaticallyPanEnabledChanged().onNotify(this, [this]() {
     144            0 :         m_isAutomaticallyPanEnabled = notationConfiguration()->isAutomaticallyPanEnabled();
     145            0 :     }, async::Asyncable::Mode::SetReplace);
     146              : 
     147            0 :     m_isSmoothPanningEnabled = configuration()->isSmoothPanning();
     148            0 :     configuration()->isSmoothPanningChanged().onNotify(this, [this]() {
     149            0 :         m_isSmoothPanningEnabled = configuration()->isSmoothPanning();
     150            0 :     }, async::Asyncable::Mode::SetReplace);
     151              : 
     152            0 :     playbackState()->playbackPositionChanged().onReceive(this, [this](secs_t secs) {
     153            0 :         m_lastPlaybackPosition = secs;
     154            0 :         m_lastPlaybackPositionUpdateTimeNs = m_elapsedTimer.nsecsElapsed();
     155            0 :     }, async::Asyncable::Mode::SetReplace);
     156              : 
     157            0 :     playbackState()->isPlayingChanged().onReceive(this, [this](bool) {
     158            0 :         onPlayingChanged();
     159            0 :     }, async::Asyncable::Mode::SetReplace);
     160              : 
     161            0 :     scheduleRedraw();
     162            0 : }
     163              : 
     164            0 : void AbstractNotationPaintView::initBackground()
     165              : {
     166            0 :     emit backgroundColorChanged(notationConfiguration()->backgroundColor());
     167              : 
     168            0 :     notationConfiguration()->backgroundChanged().onNotify(this, [this]() {
     169            0 :         emit backgroundColorChanged(notationConfiguration()->backgroundColor());
     170            0 :         scheduleRedraw();
     171            0 :     }, async::Asyncable::Mode::SetReplace);
     172            0 : }
     173              : 
     174            0 : void AbstractNotationPaintView::initNavigatorOrientation()
     175              : {
     176            0 :     notationConfiguration()->canvasOrientation().ch.onReceive(this, [this](muse::Orientation) {
     177            0 :         moveCanvasToPosition(PointF(0, 0));
     178            0 :     }, async::Asyncable::Mode::SetReplace);
     179            0 : }
     180              : 
     181            0 : void AbstractNotationPaintView::moveCanvasToCenter()
     182              : {
     183            0 :     TRACEFUNC;
     184              : 
     185            0 :     if (!isInited()) {
     186            0 :         return;
     187              :     }
     188              : 
     189            0 :     PointF canvasCenter = this->canvasCenter();
     190            0 :     Transform oldMatrix = m_matrix;
     191              : 
     192            0 :     if (doMoveCanvas(canvasCenter.x(), canvasCenter.y())) {
     193            0 :         onMatrixChanged(oldMatrix, m_matrix, false);
     194              :     }
     195            0 : }
     196              : 
     197            0 : void AbstractNotationPaintView::scrollHorizontal(qreal position)
     198              : {
     199            0 :     TRACEFUNC;
     200              : 
     201            0 :     qreal scrollStep = position - m_previousHorizontalScrollPosition;
     202            0 :     if (qFuzzyIsNull(scrollStep)) {
     203            0 :         return;
     204              :     }
     205              : 
     206            0 :     qreal dx = horizontalScrollableSize() * scrollStep;
     207            0 :     moveCanvasHorizontal(-dx);
     208            0 : }
     209              : 
     210            0 : void AbstractNotationPaintView::scrollVertical(qreal position)
     211              : {
     212            0 :     TRACEFUNC;
     213              : 
     214            0 :     qreal scrollStep = position - m_previousVerticalScrollPosition;
     215            0 :     if (qFuzzyIsNull(scrollStep)) {
     216            0 :         return;
     217              :     }
     218              : 
     219            0 :     qreal dy = verticalScrollableSize() * scrollStep;
     220            0 :     moveCanvasVertical(-dy);
     221            0 : }
     222              : 
     223            0 : void AbstractNotationPaintView::zoomIn()
     224              : {
     225            0 :     m_inputController->zoomIn();
     226            0 : }
     227              : 
     228            0 : void AbstractNotationPaintView::zoomOut()
     229              : {
     230            0 :     m_inputController->zoomOut();
     231            0 : }
     232              : 
     233            0 : void AbstractNotationPaintView::selectOnNavigationActive()
     234              : {
     235            0 :     TRACEFUNC;
     236              : 
     237            0 :     if (!notation()) {
     238            0 :         return;
     239              :     }
     240              : 
     241            0 :     auto interaction = notation()->interaction();
     242            0 :     if (!interaction->selection()->isNone()) {
     243            0 :         return;
     244              :     }
     245              : 
     246            0 :     interaction->select(SelectionTarget::FirstItem);
     247            0 : }
     248              : 
     249            0 : void AbstractNotationPaintView::onCurrentNotationChanged()
     250              : {
     251            0 :     TRACEFUNC;
     252              : 
     253            0 :     if (m_notation) {
     254            0 :         onUnloadNotation(m_notation);
     255              :     }
     256              : 
     257            0 :     setNotation(projectSession()->currentNotation());
     258              : 
     259            0 :     if (!m_notation) {
     260            0 :         return;
     261              :     }
     262              : 
     263            0 :     onLoadNotation(m_notation);
     264            0 : }
     265              : 
     266            0 : void AbstractNotationPaintView::onLoadNotation(INotationPtr)
     267              : {
     268            0 :     if (viewport().isValid() && !m_notation->viewState()->isMatrixInited()) {
     269            0 :         initZoomAndPosition();
     270              :     }
     271              : 
     272            0 :     if (publishMode()) {
     273            0 :         m_notation->painting()->setViewMode(ViewMode::PAGE);
     274              :     } else {
     275            0 :         m_notation->painting()->setViewMode(m_notation->viewState()->viewMode());
     276              :     }
     277              : 
     278            0 :     m_notation->notationChanged().onReceive(this, [this](const RectF& updateRect) {
     279            0 :         m_pageCache.clear();
     280            0 :         updateLoopMarkers();
     281            0 :         updateShadowNoteVisibility();
     282            0 :         scheduleRedraw(updateRect.isValid() ? fromLogical(updateRect) : RectF());
     283            0 :     });
     284              : 
     285              :     // Layout may delete the cached page before notationChanged is sent
     286            0 :     m_notation->elements()->msScore()->changesChannel().onReceive(this, [this](const engraving::ScoreChanges&) {
     287            0 :         m_pageCache.clear();
     288            0 :     });
     289              : 
     290            0 :     onNoteInputStateChanged();
     291            0 :     if (isNoteEnterMode()) {
     292            0 :         emit activeFocusRequested();
     293              :     }
     294              : 
     295            0 :     INotationInteractionPtr interaction = notationInteraction();
     296              : 
     297            0 :     interaction->noteInput()->stateChanged().onNotify(this, [this]() {
     298            0 :         onNoteInputStateChanged();
     299            0 :     });
     300              : 
     301            0 :     interaction->noteInput()->noteInputStarted().onReceive(this, [this](bool focusNotation) {
     302            0 :         if (focusNotation) {
     303            0 :             emit activeFocusRequested();
     304              :         }
     305            0 :     });
     306              : 
     307            0 :     interaction->selectionChanged().onNotify(this, [this]() {
     308            0 :         scheduleRedraw();
     309            0 :     });
     310              : 
     311            0 :     interaction->showItemRequested().onReceive(this, [this](const INotationInteraction::ShowItemRequest& request) {
     312            0 :         onShowItemRequested(request);
     313            0 :     });
     314              : 
     315            0 :     interaction->textEditingStarted().onNotify(this, [this]() {
     316            0 :         setFlag(ItemAcceptsInputMethod, true);
     317            0 :         setFocus(false); // Remove focus once so that the IME reloads the state
     318            0 :         forceFocusIn();
     319            0 :     });
     320              : 
     321            0 :     interaction->textEditingEnded().onReceive(this, [this](const engraving::TextBase*) {
     322            0 :         setFlag(ItemAcceptsInputMethod, false);
     323            0 :         setFocus(false); // Remove focus once so that the IME reloads the state
     324            0 :         forceFocusIn();
     325            0 :     });
     326              : 
     327            0 :     interaction->dropChanged().onNotify(this, [this]() {
     328            0 :         if (!hasActiveFocus()) {
     329            0 :             forceFocusIn(); // grab keyboard focus after element added from palette
     330              :         }
     331            0 :     });
     332              : 
     333            0 :     interaction->shadowNoteChanged().onReceive(this, [this](bool visible) {
     334            0 :         if (m_shadowNoteRect.isValid()) {
     335            0 :             scheduleRedraw(m_shadowNoteRect);
     336              :         }
     337              : 
     338            0 :         if (m_previewMeasureRect.isValid()) {
     339            0 :             scheduleRedraw(m_previewMeasureRect);
     340              :         }
     341              : 
     342            0 :         if (!visible) {
     343            0 :             m_shadowNoteRect = RectF();
     344            0 :             m_previewMeasureRect = RectF();
     345            0 :             return;
     346              :         }
     347              : 
     348            0 :         RectF shadowNoteRect = fromLogical(notationInteraction()->shadowNoteRect());
     349              : 
     350            0 :         if (shadowNoteRect.isValid()) {
     351            0 :             scheduleRedraw(shadowNoteRect);
     352              :         }
     353              : 
     354            0 :         m_shadowNoteRect = shadowNoteRect;
     355              : 
     356            0 :         RectF previewMeasureRect = fromLogical(notationInteraction()->previewMeasureRect());
     357              : 
     358            0 :         if (previewMeasureRect.isValid()) {
     359            0 :             scheduleRedraw(previewMeasureRect);
     360              :         }
     361              : 
     362            0 :         m_previewMeasureRect = previewMeasureRect;
     363              :     });
     364              : 
     365            0 :     updateLoopMarkers();
     366              :     // FIXME: only un-/re-subscribe when master notation changes
     367            0 :     notationPlayback()->loopBoundariesChanged().onNotify(this, [this]() {
     368            0 :         updateLoopMarkers();
     369            0 :         scheduleRedraw();
     370            0 :     });
     371              : 
     372            0 :     m_notation->viewModeChanged().onNotify(this, [this]() {
     373            0 :         ensureViewportInsideScrollableArea();
     374            0 :     });
     375              : 
     376              :     // FIXME: only un-/re-subscribe when master notation changes
     377            0 :     m_notationAutomationController->init();
     378            0 :     notationAutomation()->automationModeEnabledChanged().onNotify(this, [this]() {
     379            0 :         scheduleRedraw();
     380            0 :         emit automationModeChanged();
     381            0 :     });
     382              : 
     383            0 :     if (isMainView()) {
     384            0 :         connect(this, &QQuickPaintedItem::focusChanged, this, [this](bool focused) {
     385            0 :             if (notation()) {
     386            0 :                 notation()->accessibility()->setEnabled(focused);
     387              :             }
     388            0 :         });
     389              : 
     390            0 :         notation()->accessibility()->setMapToScreenFunc([this](const RectF& elementRect) {
     391            0 :             if (elementRect.isEmpty()) {
     392            0 :                 return RectF(PointF::fromQPointF(mapToGlobal({ 0, 0 })), SizeF(width(), height()));
     393              :             }
     394              : 
     395            0 :             auto res = fromLogical(elementRect);
     396            0 :             res = RectF(PointF::fromQPointF(mapToGlobal(res.topLeft().toQPointF())), SizeF(res.width(), res.height()));
     397              : 
     398            0 :             return res;
     399              :         });
     400              : 
     401            0 :         notation()->interaction()->setGetViewRectFunc([this]() {
     402            0 :             return viewport();
     403              :         });
     404              :     }
     405              : 
     406            0 :     async::Async::call(this, [this](){
     407            0 :         forceFocusIn();
     408            0 :     });
     409              : 
     410            0 :     scheduleRedraw();
     411              : 
     412            0 :     emit horizontalScrollChanged();
     413            0 :     emit verticalScrollChanged();
     414            0 :     emit viewportChanged();
     415            0 :     emit automationModeChanged();
     416            0 : }
     417              : 
     418            0 : void AbstractNotationPaintView::onUnloadNotation(INotationPtr)
     419              : {
     420            0 :     m_pageCache.clear();
     421              : 
     422            0 :     m_notation->notationChanged().disconnect(this);
     423            0 :     m_notation->elements()->msScore()->changesChannel().disconnect(this);
     424            0 :     INotationInteractionPtr interaction = m_notation->interaction();
     425            0 :     interaction->noteInput()->stateChanged().disconnect(this);
     426            0 :     interaction->noteInput()->noteInputStarted().disconnect(this);
     427            0 :     interaction->selectionChanged().disconnect(this);
     428            0 :     interaction->showItemRequested().disconnect(this);
     429            0 :     interaction->textEditingStarted().disconnect(this);
     430            0 :     interaction->textEditingEnded().disconnect(this);
     431            0 :     interaction->dropChanged().disconnect(this);
     432            0 :     interaction->shadowNoteChanged().disconnect(this);
     433            0 :     notationPlayback()->loopBoundariesChanged().disconnect(this);
     434            0 :     m_notation->viewModeChanged().disconnect(this);
     435            0 :     notationAutomation()->automationModeEnabledChanged().disconnect(this);
     436              : 
     437            0 :     if (isMainView()) {
     438            0 :         disconnect(this, &QQuickPaintedItem::focusChanged, this, nullptr);
     439            0 :         m_notation->accessibility()->setMapToScreenFunc(nullptr);
     440            0 :         m_notation->interaction()->setGetViewRectFunc(nullptr);
     441              :     }
     442            0 : }
     443              : 
     444            0 : void AbstractNotationPaintView::initZoomAndPosition()
     445              : {
     446            0 : }
     447              : 
     448            0 : void AbstractNotationPaintView::setMatrix(const Transform& matrix)
     449              : {
     450            0 :     if (m_matrix == matrix) {
     451            0 :         return;
     452              :     }
     453              : 
     454            0 :     Transform oldMatrix = m_matrix;
     455            0 :     m_matrix = matrix;
     456              : 
     457              :     // If `ensureViewportInsideScrollableArea` returns true, it has already
     458              :     // notified about matrix changed, so no need to do it again.
     459            0 :     if (ensureViewportInsideScrollableArea()) {
     460            0 :         return;
     461              :     }
     462              : 
     463            0 :     onMatrixChanged(oldMatrix, m_matrix, false);
     464              : }
     465              : 
     466            0 : void AbstractNotationPaintView::onMatrixChanged(const Transform& oldMatrix, const Transform& newMatrix, bool overrideZoomType)
     467              : {
     468              :     UNUSED(overrideZoomType);
     469              : 
     470            0 :     Transform oldMatrixInverted = oldMatrix.inverted();
     471              : 
     472            0 :     if (m_shadowNoteRect.isValid()) {
     473            0 :         RectF logicRect = oldMatrixInverted.map(m_shadowNoteRect);
     474            0 :         m_shadowNoteRect = newMatrix.map(logicRect);
     475              :     }
     476              : 
     477            0 :     if (m_previewMeasureRect.isValid()) {
     478            0 :         RectF logicRect = oldMatrixInverted.map(m_previewMeasureRect);
     479            0 :         m_previewMeasureRect = newMatrix.map(logicRect);
     480              :     }
     481              : 
     482            0 :     if (m_notationAutomationController) {
     483            0 :         m_notationAutomationController->setViewMatrix(newMatrix);
     484              :     }
     485              : 
     486            0 :     scheduleRedraw();
     487              : 
     488            0 :     emit horizontalScrollChanged();
     489            0 :     emit verticalScrollChanged();
     490            0 :     emit matrixChanged();
     491            0 :     emit viewportChanged();
     492              : 
     493            0 :     onPlaybackCursorRectChanged();
     494            0 : }
     495              : 
     496            0 : void AbstractNotationPaintView::onViewSizeChanged()
     497              : {
     498            0 :     TRACEFUNC;
     499              : 
     500            0 :     if (!notation()) {
     501            0 :         return;
     502              :     }
     503              : 
     504            0 :     if (viewport().isValid()) {
     505            0 :         if (!notation()->viewState()->isMatrixInited()) {
     506            0 :             initZoomAndPosition();
     507              :         } else {
     508            0 :             m_inputController->updateZoomAfterSizeChange();
     509              :         }
     510              :     }
     511              : 
     512            0 :     ensureViewportInsideScrollableArea();
     513              : 
     514            0 :     scheduleRedraw();
     515              : 
     516            0 :     emit horizontalScrollChanged();
     517            0 :     emit verticalScrollChanged();
     518            0 :     emit viewportChanged();
     519              : 
     520            0 :     onPlaybackCursorRectChanged();
     521            0 : }
     522              : 
     523            0 : void AbstractNotationPaintView::updateLoopMarkers()
     524              : {
     525            0 :     TRACEFUNC;
     526              : 
     527            0 :     const LoopBoundaries& loop = notationPlayback()->loopBoundaries();
     528              : 
     529            0 :     m_loopInMarker->setVisible(loop.enabled);
     530            0 :     m_loopOutMarker->setVisible(loop.enabled);
     531              : 
     532            0 :     if (loop.enabled) {
     533            0 :         m_loopInMarker->updatePosition(loop.loopInTick);
     534            0 :         m_loopOutMarker->updatePosition(loop.loopOutTick);
     535              :     }
     536            0 : }
     537              : 
     538            0 : void AbstractNotationPaintView::updateShadowNoteVisibility()
     539              : {
     540            0 :     INotationInteractionPtr interaction = notationInteraction();
     541            0 :     const engraving::ShadowNote* shadowNote = interaction ? interaction->shadowNote() : nullptr;
     542            0 :     if (!shadowNote || !shadowNote->visible()) {
     543            0 :         m_shadowNoteRect = RectF();
     544            0 :         m_previewMeasureRect = RectF();
     545            0 :         return;
     546              :     }
     547              : 
     548            0 :     if (isNoteEnterMode()) {
     549              :         //! NOTE: The following may actually hide the shadow note
     550              :         //! if cursorPos is no longer valid...
     551            0 :         const QPointF cursorPos = mapFromGlobal(QCursor::pos());
     552            0 :         interaction->showShadowNoteForPosition(toLogical(cursorPos));
     553              :     } else {
     554            0 :         interaction->hideShadowNote();
     555            0 :         m_shadowNoteRect = RectF();
     556            0 :         m_previewMeasureRect = RectF();
     557            0 :         return;
     558              :     }
     559            0 : }
     560              : 
     561            0 : NotationViewInputController* AbstractNotationPaintView::inputController() const
     562              : {
     563            0 :     return m_inputController.get();
     564              : }
     565              : 
     566            0 : INotationPtr AbstractNotationPaintView::notation() const
     567              : {
     568            0 :     return m_notation;
     569              : }
     570              : 
     571            0 : INotationInteractionPtr AbstractNotationPaintView::notationInteraction() const
     572              : {
     573            0 :     return notation() ? notation()->interaction() : nullptr;
     574              : }
     575              : 
     576            0 : INotationPlaybackPtr AbstractNotationPaintView::notationPlayback() const
     577              : {
     578            0 :     return notation() ? notation()->masterNotation()->playback() : nullptr;
     579              : }
     580              : 
     581            0 : QQuickItem* AbstractNotationPaintView::asItem()
     582              : {
     583            0 :     return this;
     584              : }
     585              : 
     586            0 : INotationNoteInputPtr AbstractNotationPaintView::notationNoteInput() const
     587              : {
     588            0 :     return notationInteraction() ? notationInteraction()->noteInput() : nullptr;
     589              : }
     590              : 
     591            0 : INotationElementsPtr AbstractNotationPaintView::notationElements() const
     592              : {
     593            0 :     return notation() ? notation()->elements() : nullptr;
     594              : }
     595              : 
     596            0 : INotationStylePtr AbstractNotationPaintView::notationStyle() const
     597              : {
     598            0 :     return notation() ? notation()->style() : nullptr;
     599              : }
     600              : 
     601            0 : INotationSelectionPtr AbstractNotationPaintView::notationSelection() const
     602              : {
     603            0 :     return notationInteraction() ? notationInteraction()->selection() : nullptr;
     604              : }
     605              : 
     606            0 : INotationAutomationPtr AbstractNotationPaintView::notationAutomation() const
     607              : {
     608            0 :     return m_notation ? m_notation->masterNotation()->automation() : nullptr;
     609              : }
     610              : 
     611            0 : void AbstractNotationPaintView::onNoteInputStateChanged()
     612              : {
     613            0 :     TRACEFUNC;
     614              : 
     615            0 :     setAcceptHoverEvents(isNoteEnterMode());
     616            0 :     updateLoopMarkers();
     617            0 :     updateShadowNoteVisibility();
     618            0 :     scheduleRedraw();
     619            0 : }
     620              : 
     621            0 : void AbstractNotationPaintView::onShowItemRequested(const INotationInteraction::ShowItemRequest& request)
     622              : {
     623            0 :     IF_ASSERT_FAILED(request.item) {
     624            0 :         return;
     625              :     }
     626              : 
     627            0 :     RectF viewRect = viewport();
     628              : 
     629            0 :     RectF showRect = request.showRect;
     630            0 :     RectF itemBoundingRect = request.item->canvasBoundingRect();
     631              : 
     632            0 :     if (viewRect.width() < showRect.width()) {
     633            0 :         showRect.setLeft(itemBoundingRect.x());
     634            0 :         showRect.setWidth(itemBoundingRect.width());
     635              :     }
     636              : 
     637            0 :     if (viewRect.height() < showRect.height()) {
     638            0 :         showRect.setTop(itemBoundingRect.y());
     639            0 :         showRect.setHeight(itemBoundingRect.height());
     640              :     }
     641              : 
     642            0 :     adjustCanvasPosition(showRect);
     643              : }
     644              : 
     645            0 : bool AbstractNotationPaintView::isNoteEnterMode() const
     646              : {
     647            0 :     return notationNoteInput() ? notationNoteInput()->isNoteInputMode() : false;
     648              : }
     649              : 
     650            0 : void AbstractNotationPaintView::showContextMenu(const ElementType& elementType, const QPointF& pos)
     651              : {
     652            0 :     TRACEFUNC;
     653              : 
     654            0 :     if (m_readonly) {
     655            0 :         return;
     656              :     }
     657              : 
     658            0 :     QPointF _pos = pos;
     659            0 :     if (_pos.isNull()) {
     660            0 :         _pos = QPointF(width() / 2, height() / 2);
     661              :     }
     662              : 
     663            0 :     emit showContextMenuRequested(static_cast<int>(elementType), _pos);
     664            0 : }
     665              : 
     666            0 : void AbstractNotationPaintView::hideContextMenu()
     667              : {
     668            0 :     TRACEFUNC;
     669              : 
     670            0 :     if (m_isContextMenuOpen) {
     671            0 :         emit hideContextMenuRequested();
     672              :     }
     673            0 : }
     674              : 
     675            0 : void AbstractNotationPaintView::showSearch()
     676              : {
     677            0 :     emit showSearchRequested();
     678            0 : }
     679              : 
     680            0 : void AbstractNotationPaintView::showElementPopup(const ElementType& elementType)
     681              : {
     682            0 :     TRACEFUNC;
     683              : 
     684            0 :     const PopupModelType modelType = AbstractElementPopupModel::modelTypeFromElement(elementType);
     685            0 :     if (m_currentElementPopupType == modelType) {
     686              :         // Don't do anything if a popup of this type is already open...
     687            0 :         return;
     688              :     }
     689              : 
     690            0 :     m_currentElementPopupType = modelType;
     691            0 :     emit showElementPopupRequested(modelType);
     692            0 : }
     693              : 
     694            0 : void AbstractNotationPaintView::hideElementPopup(const ElementType& elementType)
     695              : {
     696            0 :     TRACEFUNC;
     697              : 
     698            0 :     hideElementPopup(AbstractElementPopupModel::modelTypeFromElement(elementType));
     699            0 : }
     700              : 
     701            0 : void AbstractNotationPaintView::hideElementPopup(PopupModelType modelType)
     702              : {
     703            0 :     TRACEFUNC;
     704              : 
     705            0 :     if (m_currentElementPopupType == PopupModelType::TYPE_UNDEFINED) {
     706              :         // Popup is already hidden...
     707            0 :         return;
     708              :     }
     709              : 
     710              :     // Hide the popup if the model type matches the currently open model type, or if no element type was specified
     711            0 :     if (modelType == m_currentElementPopupType || modelType == PopupModelType::TYPE_UNDEFINED) {
     712            0 :         m_currentElementPopupType = PopupModelType::TYPE_UNDEFINED;
     713            0 :         emit hideElementPopupRequested();
     714              :     }
     715            0 : }
     716              : 
     717            0 : void AbstractNotationPaintView::toggleElementPopup(const ElementType& elementType)
     718              : {
     719            0 :     if (m_currentElementPopupType != PopupModelType::TYPE_UNDEFINED) {
     720            0 :         hideElementPopup(elementType);
     721            0 :         return;
     722              :     }
     723              : 
     724            0 :     showElementPopup(elementType);
     725              : }
     726              : 
     727            0 : bool AbstractNotationPaintView::elementPopupIsOpen(const ElementType& elementType) const
     728              : {
     729            0 :     const PopupModelType modelType = AbstractElementPopupModel::modelTypeFromElement(elementType);
     730            0 :     return m_currentElementPopupType == modelType;
     731              : }
     732              : 
     733            0 : void AbstractNotationPaintView::paint(QPainter* qp)
     734              : {
     735            0 :     TRACEFUNC;
     736              : 
     737            0 :     RectF rect = RectF::fromQRectF(qp->clipBoundingRect());
     738            0 :     rect = correctDrawRect(rect);
     739              : 
     740            0 :     muse::draw::Painter mup(qp, objectName().toStdString());
     741            0 :     muse::draw::Painter* painter = &mup;
     742              : 
     743            0 :     paintBackground(rect, painter);
     744              : 
     745            0 :     if (!isInited()) {
     746            0 :         return;
     747              :     }
     748              : 
     749            0 :     qreal guiScaling = notationContextConfiguration()->guiScaling();
     750            0 :     Transform guiScalingCompensation;
     751            0 :     guiScalingCompensation.scale(guiScaling, guiScaling);
     752              : 
     753            0 :     painter->setWorldTransform(m_matrix * guiScalingCompensation);
     754              : 
     755            0 :     const bool isPrinting = publishMode() || m_inputController->readonly();
     756            0 :     const bool isAutomation = automationMode();
     757            0 :     notation()->painting()->paintView(painter, toLogical(rect), isPrinting, isAutomation);
     758              : 
     759            0 :     const INotationNoteInputPtr noteInput = notationNoteInput();
     760            0 :     if (noteInput->isNoteInputMode() && !publishMode()) {
     761            0 :         if (noteInput->usingNoteInputMethod(NoteInputMethod::BY_DURATION)
     762            0 :             && !notationConfiguration()->useNoteInputCursorInInputByDuration()) {
     763            0 :             m_ruler->paint(painter, noteInput->state());
     764              :         } else {
     765            0 :             m_noteInputCursor->paint(painter);
     766              :         }
     767              :     }
     768              : 
     769            0 :     m_loopInMarker->paint(painter);
     770            0 :     m_loopOutMarker->paint(painter);
     771              : 
     772            0 :     if (notation()->viewMode() == engraving::LayoutMode::LINE) {
     773            0 :         ContinuousPanel::NotationViewContext nvCtx;
     774            0 :         nvCtx.xOffset = m_matrix.dx();
     775            0 :         nvCtx.yOffset = m_matrix.dy();
     776            0 :         nvCtx.scaling = currentScaling();
     777            0 :         nvCtx.fromLogical = [this](const PointF& pos) -> PointF { return fromLogical(pos); };
     778              : 
     779            0 :         engraving::rendering::PaintOptions opt;
     780            0 :         opt.invertColors = notationConfiguration()->shouldInvertScore();
     781            0 :         m_continuousPanel->paint(*painter, nvCtx, opt);
     782            0 :     }
     783            0 : }
     784              : 
     785            0 : void AbstractNotationPaintView::onNotationSetup()
     786              : {
     787            0 :     TRACEFUNC;
     788            0 :     onCurrentNotationChanged();
     789            0 :     onPlayingChanged();
     790              : 
     791            0 :     projectSession()->currentNotationChanged().onNotify(this, [this]() {
     792            0 :         onCurrentNotationChanged();
     793            0 :     });
     794              : 
     795            0 :     notationConfiguration()->foregroundChanged().onNotify(this, [this]() {
     796            0 :         scheduleRedraw();
     797            0 :     });
     798              : 
     799            0 :     notationConfiguration()->notationColorChanged().onNotify(this, [this]() {
     800            0 :         scheduleRedraw();
     801            0 :     });
     802              : 
     803            0 :     uiConfiguration()->currentThemeChanged().onNotify(this, [this]() {
     804            0 :         scheduleRedraw();
     805            0 :     }, async::Asyncable::Mode::SetReplace);
     806              : 
     807            0 :     engravingConfiguration()->selectionColorChanged().onReceive(this, [this](voice_idx_t, const muse::draw::Color&) {
     808            0 :         scheduleRedraw();
     809            0 :     });
     810              : 
     811            0 :     engravingConfiguration()->formattingColorChanged().onReceive(this, [this](const Color&) {
     812            0 :         scheduleRedraw();
     813            0 :     });
     814              : 
     815            0 :     engravingConfiguration()->invisibleColorChanged().onReceive(this, [this](const Color&) {
     816            0 :         scheduleRedraw();
     817            0 :     });
     818              : 
     819            0 :     engravingConfiguration()->unlinkedColorChanged().onReceive(this, [this](const Color&) {
     820            0 :         scheduleRedraw();
     821            0 :     });
     822              : 
     823            0 :     engravingConfiguration()->debuggingOptionsChanged().onNotify(this, [this]() {
     824            0 :         scheduleRedraw();
     825            0 :     });
     826            0 : }
     827              : 
     828            0 : void AbstractNotationPaintView::paintBackground(const RectF& rect, muse::draw::Painter* painter)
     829              : {
     830            0 :     TRACEFUNC;
     831              : 
     832            0 :     const QPixmap& wallpaper = notationConfiguration()->backgroundWallpaper();
     833              : 
     834            0 :     if (notationConfiguration()->backgroundUseColor() || wallpaper.isNull()) {
     835            0 :         painter->fillRect(rect, notationConfiguration()->backgroundColor());
     836              :     } else {
     837            0 :         painter->drawTiledPixmap(rect, wallpaper, rect.topLeft() - PointF(m_matrix.m31(), m_matrix.m32()));
     838              :     }
     839            0 : }
     840              : 
     841            0 : PointF AbstractNotationPaintView::canvasCenter() const
     842              : {
     843            0 :     TRACEFUNC;
     844            0 :     RectF canvasRect = m_matrix.map(notationContentRect());
     845              : 
     846            0 :     qreal canvasWidth = canvasRect.width();
     847            0 :     qreal canvasHeight = canvasRect.height();
     848              : 
     849            0 :     qreal x = (width() - canvasWidth) / 2;
     850            0 :     qreal y = (height() - canvasHeight) / 2;
     851              : 
     852            0 :     return toLogical(PointF(x, y));
     853            0 : }
     854              : 
     855            0 : std::pair<qreal, qreal> AbstractNotationPaintView::constraintCanvas(qreal dx, qreal dy) const
     856              : {
     857            0 :     TRACEFUNC;
     858            0 :     RectF scrollableArea = scrollableAreaRect();
     859            0 :     RectF viewport = this->viewport();
     860              : 
     861              :     // horizontal
     862              :     {
     863            0 :         qreal newLeft = viewport.left() - dx;
     864            0 :         if (viewport.width() > scrollableArea.width()) {
     865            0 :             newLeft = scrollableArea.center().x() - viewport.width() / 2;
     866              :         } else {
     867            0 :             newLeft = qBound(scrollableArea.left(), newLeft, scrollableArea.right() - viewport.width());
     868              :         }
     869            0 :         dx = viewport.left() - newLeft;
     870              :     }
     871              : 
     872              :     // vertical
     873              :     {
     874            0 :         qreal newTop = viewport.top() - dy;
     875            0 :         if (viewport.height() > scrollableArea.height()) {
     876            0 :             newTop = scrollableArea.center().y() - viewport.height() / 2;
     877              :         } else {
     878            0 :             newTop = qBound(scrollableArea.top(), newTop, scrollableArea.bottom() - viewport.height());
     879              :         }
     880            0 :         dy = viewport.top() - newTop;
     881              :     }
     882              : 
     883            0 :     return { dx, dy };
     884            0 : }
     885              : 
     886            0 : QVariant AbstractNotationPaintView::matrix() const
     887              : {
     888            0 :     return Transform::toQTransform(m_matrix);
     889              : }
     890              : 
     891            0 : PointF AbstractNotationPaintView::viewportTopLeft() const
     892              : {
     893            0 :     return toLogical(PointF(0.0, 0.0));
     894              : }
     895              : 
     896            0 : RectF AbstractNotationPaintView::viewport() const
     897              : {
     898            0 :     return toLogical(RectF(0.0, 0.0, width(), height()));
     899              : }
     900              : 
     901            0 : QRectF AbstractNotationPaintView::viewport_property() const
     902              : {
     903            0 :     return viewport().toQRectF();
     904              : }
     905              : 
     906            0 : RectF AbstractNotationPaintView::notationContentRect() const
     907              : {
     908            0 :     TRACEFUNC;
     909            0 :     if (!notationElements()) {
     910            0 :         return RectF();
     911              :     }
     912              : 
     913            0 :     RectF result;
     914            0 :     for (const Page* page: notationElements()->pages()) {
     915            0 :         result.unite(page->ldata()->bbox().translated(page->pos()));
     916              :     }
     917              : 
     918            0 :     return result;
     919            0 : }
     920              : 
     921            0 : RectF AbstractNotationPaintView::scrollableAreaRect() const
     922              : {
     923            0 :     TRACEFUNC;
     924            0 :     RectF viewport = this->viewport();
     925            0 :     qreal overscrollFactor = configuration()->isLimitCanvasScrollArea() ? 0.0 : SCROLL_LIMIT_OFF_OVERSCROLL_FACTOR;
     926              : 
     927            0 :     qreal overscrollX = viewport.width() * overscrollFactor;
     928            0 :     qreal overscrollY = viewport.height() * overscrollFactor;
     929              : 
     930            0 :     return notationContentRect().adjusted(-overscrollX, -overscrollY, overscrollX, overscrollY);
     931            0 : }
     932              : 
     933            0 : qreal AbstractNotationPaintView::horizontalScrollableSize() const
     934              : {
     935            0 :     TRACEFUNC;
     936            0 :     RectF viewport = this->viewport();
     937            0 :     RectF contentRect = notationContentRect();
     938              : 
     939            0 :     qreal left = std::min(contentRect.left(), viewport.left());
     940            0 :     qreal right = std::max(contentRect.right(), viewport.right());
     941              : 
     942            0 :     qreal size = 0;
     943            0 :     if ((left < 0) && (right > 0)) {
     944            0 :         size = std::abs(left) + right;
     945              :     } else {
     946            0 :         size = std::abs(right) - std::abs(left);
     947              :     }
     948              : 
     949            0 :     return size;
     950            0 : }
     951              : 
     952            0 : qreal AbstractNotationPaintView::verticalScrollableSize() const
     953              : {
     954            0 :     TRACEFUNC;
     955            0 :     RectF viewport = this->viewport();
     956            0 :     RectF contentRect = notationContentRect();
     957              : 
     958            0 :     qreal top = std::min(contentRect.top(), viewport.top());
     959            0 :     qreal bottom = std::max(contentRect.bottom(), viewport.bottom());
     960              : 
     961            0 :     qreal size = 0;
     962            0 :     if ((top < 0) && (bottom > 0)) {
     963            0 :         size = std::abs(top) + bottom;
     964              :     } else {
     965            0 :         size = std::abs(bottom) - std::abs(top);
     966              :     }
     967              : 
     968            0 :     return size;
     969            0 : }
     970              : 
     971            0 : qreal AbstractNotationPaintView::horizontalScrollbarSize() const
     972              : {
     973            0 :     TRACEFUNC;
     974            0 :     RectF viewport = this->viewport();
     975            0 :     RectF contentRect = notationContentRect();
     976              : 
     977            0 :     if (viewport.left() < contentRect.left()
     978            0 :         && viewport.right() > contentRect.right()) {
     979            0 :         return 0;
     980              :     }
     981              : 
     982            0 :     qreal scrollableWidth = horizontalScrollableSize();
     983            0 :     if (qFuzzyIsNull(scrollableWidth)) {
     984            0 :         return 0;
     985              :     }
     986              : 
     987            0 :     return viewport.width() / scrollableWidth;
     988            0 : }
     989              : 
     990            0 : qreal AbstractNotationPaintView::verticalScrollbarSize() const
     991              : {
     992            0 :     TRACEFUNC;
     993            0 :     RectF viewport = this->viewport();
     994            0 :     RectF contentRect = notationContentRect();
     995              : 
     996            0 :     if (viewport.top() < contentRect.top()
     997            0 :         && viewport.bottom() > contentRect.bottom()) {
     998            0 :         return 0;
     999              :     }
    1000              : 
    1001            0 :     qreal scrollableHeight = verticalScrollableSize();
    1002            0 :     if (qFuzzyIsNull(scrollableHeight)) {
    1003            0 :         return 0;
    1004              :     }
    1005              : 
    1006            0 :     return viewport.height() / scrollableHeight;
    1007            0 : }
    1008              : 
    1009            0 : qreal AbstractNotationPaintView::startHorizontalScrollPosition() const
    1010              : {
    1011            0 :     TRACEFUNC;
    1012            0 :     RectF viewport = this->viewport();
    1013            0 :     RectF contentRect = notationContentRect();
    1014              : 
    1015            0 :     if (!viewport.isValid() || !contentRect.isValid()) {
    1016            0 :         return 0.0;
    1017              :     }
    1018              : 
    1019            0 :     return (viewport.left() - contentRect.left()) / contentRect.width();
    1020            0 : }
    1021              : 
    1022            0 : qreal AbstractNotationPaintView::startVerticalScrollPosition() const
    1023              : {
    1024            0 :     TRACEFUNC;
    1025            0 :     RectF viewport = this->viewport();
    1026            0 :     RectF contentRect = notationContentRect();
    1027              : 
    1028            0 :     if (!viewport.isValid() || !contentRect.isValid()) {
    1029            0 :         return 0.0;
    1030              :     }
    1031              : 
    1032            0 :     return (viewport.top() - contentRect.top()) / contentRect.height();
    1033            0 : }
    1034              : 
    1035            0 : bool AbstractNotationPaintView::adjustCanvasPosition(const RectF& logicRect, bool adjustVertically)
    1036              : {
    1037            0 :     TRACEFUNC;
    1038              : 
    1039            0 :     RectF viewRect = viewport();
    1040            0 :     PointF pos = viewRect.topLeft();
    1041              : 
    1042              :     // Account for continuous panel if panning in continuous view
    1043            0 :     qreal continuousPanelWidth = 0;
    1044            0 :     if (notation()->viewMode() == engraving::LayoutMode::LINE) {
    1045            0 :         continuousPanelWidth = m_continuousPanel->width();
    1046              :     }
    1047            0 :     viewRect.adjust(continuousPanelWidth, 0, 0, 0);
    1048              : 
    1049            0 :     double viewArea = viewRect.width() * viewRect.height();
    1050            0 :     double logicRectArea = logicRect.width() * logicRect.height();
    1051              : 
    1052            0 :     if (viewArea < logicRectArea) {
    1053            0 :         return false;
    1054              :     }
    1055              : 
    1056            0 :     if (viewRect.contains(logicRect)) {
    1057            0 :         return false;
    1058              :     }
    1059              : 
    1060            0 :     constexpr int BORDER_SPACING_RATIO = 3;
    1061              : 
    1062            0 :     double _spatium = notationStyle()->styleValue(StyleId::spatium).toDouble();
    1063            0 :     qreal border = _spatium * BORDER_SPACING_RATIO;
    1064            0 :     qreal _scale = currentScaling();
    1065            0 :     if (qFuzzyIsNull(_scale)) {
    1066            0 :         _scale = 1;
    1067              :     }
    1068              : 
    1069            0 :     PointF oldPos = pos;
    1070              : 
    1071            0 :     RectF showRect = logicRect;
    1072              : 
    1073            0 :     if (showRect.left() < viewRect.left()) {
    1074            0 :         pos.setX(showRect.left() - border - continuousPanelWidth);
    1075            0 :     } else if (showRect.left() > viewRect.right()) {
    1076            0 :         pos.setX(showRect.right() - width() / _scale + border);
    1077            0 :     } else if (viewRect.width() >= showRect.width() && showRect.right() > viewRect.right()) {
    1078            0 :         pos.setX(showRect.left() - border - continuousPanelWidth);
    1079              :     }
    1080              : 
    1081            0 :     if (adjustVertically) {
    1082            0 :         if (showRect.top() < viewRect.top() && showRect.bottom() < viewRect.bottom()) {
    1083            0 :             pos.setY(showRect.top() - border);
    1084            0 :         } else if (showRect.top() > viewRect.bottom()) {
    1085            0 :             pos.setY(showRect.bottom() - height() / _scale + border);
    1086            0 :         } else if (viewRect.height() >= showRect.height() && showRect.bottom() > viewRect.bottom()) {
    1087            0 :             pos.setY(showRect.top() - border);
    1088              :         }
    1089              :     }
    1090              : 
    1091            0 :     pos = alignToCurrentPageBorder(showRect, pos);
    1092              : 
    1093            0 :     if (pos == oldPos) {
    1094            0 :         return false;
    1095              :     }
    1096              : 
    1097            0 :     return moveCanvasToPosition(pos);
    1098            0 : }
    1099              : 
    1100            0 : bool AbstractNotationPaintView::adjustCanvasPositionSmoothPan(const RectF& cursorRect)
    1101              : {
    1102            0 :     RectF viewRect = viewport();
    1103            0 :     PointF pos(cursorRect.x() - (viewRect.width() / 2), viewRect.y());
    1104              : 
    1105            0 :     if (!viewport().intersects(cursorRect)) {
    1106            0 :         pos.setY(cursorRect.y() - (viewRect.height() / 2));
    1107              :     }
    1108              : 
    1109            0 :     return moveCanvasToPosition(pos);
    1110              : }
    1111              : 
    1112            0 : bool AbstractNotationPaintView::ensureViewportInsideScrollableArea()
    1113              : {
    1114            0 :     TRACEFUNC;
    1115              : 
    1116            0 :     if (!m_notation) {
    1117            0 :         return false;
    1118              :     }
    1119              : 
    1120              :     // When the viewport is empty, which might happen for example when the view
    1121              :     // is being loaded or unloaded, the scrollable area is computed without any
    1122              :     // overscroll allowance, resulting in unexpected clamping of the canvas
    1123              :     // position to (0, 0).
    1124            0 :     if (size().isEmpty()) {
    1125            0 :         return false;
    1126              :     }
    1127              : 
    1128            0 :     auto [dx, dy] = constraintCanvas(0, 0);
    1129            0 :     if (qFuzzyIsNull(dx) && qFuzzyIsNull(dy)) {
    1130            0 :         return false;
    1131              :     }
    1132              : 
    1133            0 :     Transform oldMatrix = m_matrix;
    1134            0 :     m_matrix.translate(dx, dy);
    1135            0 :     onMatrixChanged(oldMatrix, m_matrix, false);
    1136            0 :     return true;
    1137            0 : }
    1138              : 
    1139            0 : bool AbstractNotationPaintView::moveCanvasToPosition(const PointF& logicPos)
    1140              : {
    1141            0 :     TRACEFUNC;
    1142              : 
    1143            0 :     PointF viewTopLeft = viewportTopLeft();
    1144            0 :     Transform oldMatrix = m_matrix;
    1145            0 :     if (doMoveCanvas(viewTopLeft.x() - logicPos.x(), viewTopLeft.y() - logicPos.y())) {
    1146            0 :         onMatrixChanged(oldMatrix, m_matrix, false);
    1147            0 :         return true;
    1148              :     }
    1149              : 
    1150            0 :     return false;
    1151            0 : }
    1152              : 
    1153            0 : bool AbstractNotationPaintView::moveCanvas(qreal dx, qreal dy)
    1154              : {
    1155            0 :     TRACEFUNC;
    1156              : 
    1157            0 :     Transform oldMatrix = m_matrix;
    1158            0 :     bool moved = doMoveCanvas(dx, dy);
    1159              : 
    1160            0 :     if (moved) {
    1161            0 :         onMatrixChanged(oldMatrix, m_matrix, false);
    1162              : 
    1163            0 :         m_autoScrollEnabled = false;
    1164            0 :         m_enableAutoScrollTimer.start(2000);
    1165              : 
    1166            0 :         hideElementPopup();
    1167              :     }
    1168              : 
    1169            0 :     return moved;
    1170            0 : }
    1171              : 
    1172            0 : bool AbstractNotationPaintView::doMoveCanvas(qreal dx, qreal dy)
    1173              : {
    1174            0 :     if (!m_notation) {
    1175            0 :         return false;
    1176              :     }
    1177              : 
    1178            0 :     if (qFuzzyIsNull(dx) && qFuzzyIsNull(dy)) {
    1179            0 :         return false;
    1180              :     }
    1181              : 
    1182            0 :     auto [correctedDX, correctedDY] = constraintCanvas(dx, dy);
    1183            0 :     if (qFuzzyIsNull(correctedDX) && qFuzzyIsNull(correctedDY)) {
    1184            0 :         return false;
    1185              :     }
    1186              : 
    1187            0 :     m_matrix.translate(correctedDX, correctedDY);
    1188              : 
    1189            0 :     return true;
    1190              : }
    1191              : 
    1192            0 : void AbstractNotationPaintView::scheduleRedraw(const muse::RectF& rect)
    1193              : {
    1194            0 :     muse::RectF redrawRect = correctDrawRect(rect);
    1195              : 
    1196              :     // Convert the floating-point rectangle to an integer QRect for the update() call,
    1197              :     // ensuring that we cover the entire area of the original rect.
    1198            0 :     int left = floor(redrawRect.left());
    1199            0 :     int right = ceil(redrawRect.right());
    1200            0 :     int top = floor(redrawRect.top());
    1201            0 :     int bottom = ceil(redrawRect.bottom());
    1202            0 :     QRect updateRect = QRect(left, top, right - left, bottom - top);
    1203              : 
    1204            0 :     update(updateRect);
    1205            0 : }
    1206              : 
    1207            0 : RectF AbstractNotationPaintView::correctDrawRect(const RectF& rect) const
    1208              : {
    1209            0 :     if (!rect.isValid() || rect.isNull()) {
    1210            0 :         return RectF(0, 0, width(), height());
    1211              :     }
    1212              : 
    1213              :     // Compensate for floating point precision issues resulting in occasional
    1214              :     // thin unpainted lines at the edges of the update rectangle.
    1215            0 :     return rect.padded(1);
    1216              : }
    1217              : 
    1218            0 : void AbstractNotationPaintView::moveCanvasVertical(qreal dy)
    1219              : {
    1220            0 :     moveCanvas(0, dy);
    1221            0 : }
    1222              : 
    1223            0 : void AbstractNotationPaintView::moveCanvasHorizontal(qreal dx)
    1224              : {
    1225            0 :     moveCanvas(dx, 0);
    1226            0 : }
    1227              : 
    1228            0 : qreal AbstractNotationPaintView::currentScaling() const
    1229              : {
    1230            0 :     return m_matrix.m11();
    1231              : }
    1232              : 
    1233            0 : void AbstractNotationPaintView::setScaling(qreal scaling, const PointF& pos, bool overrideZoomType)
    1234              : {
    1235            0 :     TRACEFUNC;
    1236              : 
    1237            0 :     if (!m_notation) {
    1238            0 :         return;
    1239              :     }
    1240              : 
    1241            0 :     hideElementPopup();
    1242              : 
    1243            0 :     qreal currentScaling = this->currentScaling();
    1244              : 
    1245            0 :     IF_ASSERT_FAILED(!qFuzzyIsNull(scaling)) {
    1246            0 :         return;
    1247              :     }
    1248              : 
    1249            0 :     if (qFuzzyCompare(currentScaling, scaling)) {
    1250            0 :         return;
    1251              :     }
    1252              : 
    1253            0 :     if (qFuzzyIsNull(currentScaling)) {
    1254            0 :         currentScaling = 1;
    1255              :     }
    1256              : 
    1257            0 :     qreal deltaScaling = scaling / currentScaling;
    1258            0 :     scale(deltaScaling, pos, overrideZoomType);
    1259            0 : }
    1260              : 
    1261            0 : void AbstractNotationPaintView::scale(qreal factor, const PointF& pos, bool overrideZoomType)
    1262              : {
    1263            0 :     TRACEFUNC;
    1264              : 
    1265            0 :     if (!m_notation) {
    1266            0 :         return;
    1267              :     }
    1268              : 
    1269            0 :     if (qFuzzyCompare(factor, 1.0)) {
    1270            0 :         return;
    1271              :     }
    1272              : 
    1273            0 :     PointF pointBeforeScaling = toLogical(pos);
    1274              : 
    1275            0 :     Transform oldMatrix = m_matrix;
    1276            0 :     m_matrix.scale(factor, factor);
    1277              : 
    1278            0 :     PointF pointAfterScaling = toLogical(pos);
    1279              : 
    1280            0 :     qreal dx = pointAfterScaling.x() - pointBeforeScaling.x();
    1281            0 :     qreal dy = pointAfterScaling.y() - pointBeforeScaling.y();
    1282              : 
    1283            0 :     doMoveCanvas(dx, dy);
    1284              : 
    1285            0 :     onMatrixChanged(oldMatrix, m_matrix, overrideZoomType);
    1286            0 : }
    1287              : 
    1288            0 : void AbstractNotationPaintView::pinchToZoom(qreal scaleFactor, const QPointF& pos)
    1289              : {
    1290            0 :     if (isInited()) {
    1291            0 :         m_inputController->pinchToZoom(scaleFactor, pos);
    1292              :     }
    1293            0 : }
    1294              : 
    1295            0 : void AbstractNotationPaintView::wheelEvent(QWheelEvent* event)
    1296              : {
    1297            0 :     TRACEFUNC;
    1298            0 :     if (isInited()) {
    1299            0 :         m_inputController->wheelEvent(event);
    1300              :     }
    1301            0 : }
    1302              : 
    1303            0 : void AbstractNotationPaintView::forceFocusIn()
    1304              : {
    1305            0 :     TRACEFUNC;
    1306            0 :     setFocus(true);
    1307            0 :     emit activeFocusRequested();
    1308            0 :     forceActiveFocus();
    1309            0 : }
    1310              : 
    1311            0 : void AbstractNotationPaintView::onContextMenuIsOpenChanged(bool open)
    1312              : {
    1313            0 :     m_isContextMenuOpen = open;
    1314            0 : }
    1315              : 
    1316            0 : void AbstractNotationPaintView::onElementPopupIsOpenChanged(const PopupModelType& popupType)
    1317              : {
    1318            0 :     m_currentElementPopupType = popupType;
    1319            0 : }
    1320              : 
    1321            0 : void AbstractNotationPaintView::mousePressEvent(QMouseEvent* event)
    1322              : {
    1323            0 :     TRACEFUNC;
    1324            0 :     forceFocusIn();
    1325              : 
    1326            0 :     if (isInited()) {
    1327            0 :         m_inputController->mousePressEvent(event);
    1328              :     }
    1329            0 : }
    1330              : 
    1331            0 : void AbstractNotationPaintView::mouseMoveEvent(QMouseEvent* event)
    1332              : {
    1333            0 :     TRACEFUNC;
    1334            0 :     if (isInited()) {
    1335            0 :         m_inputController->mouseMoveEvent(event);
    1336              :     }
    1337            0 : }
    1338              : 
    1339            0 : void AbstractNotationPaintView::mouseDoubleClickEvent(QMouseEvent* event)
    1340              : {
    1341            0 :     TRACEFUNC;
    1342            0 :     forceFocusIn();
    1343              : 
    1344            0 :     if (isInited()) {
    1345            0 :         m_inputController->mouseDoubleClickEvent(event);
    1346              :     }
    1347            0 : }
    1348              : 
    1349            0 : void AbstractNotationPaintView::mouseReleaseEvent(QMouseEvent* event)
    1350              : {
    1351            0 :     if (isInited()) {
    1352            0 :         m_inputController->mouseReleaseEvent(event);
    1353              :     }
    1354            0 : }
    1355              : 
    1356            0 : void AbstractNotationPaintView::hoverMoveEvent(QHoverEvent* event)
    1357              : {
    1358            0 :     if (isInited()) {
    1359            0 :         m_inputController->hoverMoveEvent(event);
    1360              :     }
    1361            0 : }
    1362              : 
    1363            0 : void AbstractNotationPaintView::hoverLeaveEvent(QHoverEvent* event)
    1364              : {
    1365            0 :     if (isInited()) {
    1366            0 :         m_inputController->hoverLeaveEvent(event);
    1367              :     }
    1368            0 : }
    1369              : 
    1370            0 : bool AbstractNotationPaintView::shortcutOverride(QKeyEvent* event)
    1371              : {
    1372            0 :     if (isInited()) {
    1373            0 :         return m_inputController->shortcutOverrideEvent(event);
    1374              :     }
    1375              : 
    1376            0 :     return false;
    1377              : }
    1378              : 
    1379            0 : void AbstractNotationPaintView::keyPressEvent(QKeyEvent* event)
    1380              : {
    1381            0 :     if (isInited()) {
    1382            0 :         m_inputController->keyPressEvent(event);
    1383              :     }
    1384              : 
    1385            0 :     if (event->key() == m_lastAcceptedKey) {
    1386              :         // required to prevent Qt-Quick from changing focus on tab
    1387            0 :         m_lastAcceptedKey = -1;
    1388              :     }
    1389              : 
    1390            0 :     return;
    1391              : }
    1392              : 
    1393            0 : void AbstractNotationPaintView::keyReleaseEvent(QKeyEvent* event)
    1394              : {
    1395            0 :     if (isInited()) {
    1396            0 :         m_inputController->keyReleaseEvent(event);
    1397              :     }
    1398            0 : }
    1399              : 
    1400            0 : bool AbstractNotationPaintView::event(QEvent* event)
    1401              : {
    1402            0 :     if (!isInited()) {
    1403            0 :         return QQuickPaintedItem::event(event);
    1404              :     }
    1405              : 
    1406            0 :     QEvent::Type eventType = event->type();
    1407            0 :     auto keyEvent = dynamic_cast<QKeyEvent*>(event);
    1408              : 
    1409            0 :     bool isContextMenuEvent = ((eventType == QEvent::ShortcutOverride && keyEvent->key() == Qt::Key_Menu)
    1410            0 :                                || eventType == QEvent::Type::ContextMenu) && hasFocus();
    1411              : 
    1412            0 :     if (isContextMenuEvent) {
    1413            0 :         QContextMenuEvent* contextMenuEvent = dynamic_cast<QContextMenuEvent*>(event);
    1414            0 :         if (contextMenuEvent && contextMenuEvent->reason() == QContextMenuEvent::Mouse
    1415            0 :             && m_inputController->ignoreNextMouseContextMenuEvent()) {
    1416            0 :             return true;
    1417              :         }
    1418            0 :         QPointF pos = contextMenuEvent && !contextMenuEvent->pos().isNull()
    1419            0 :                       ? mapFromGlobal(contextMenuEvent->globalPos())
    1420            0 :                       : fromLogical(m_inputController->selectionElementPos()).toQPointF();
    1421            0 :         showContextMenu(m_inputController->selectionType(), pos);
    1422            0 :         return true;
    1423            0 :     } else if (eventType == QEvent::Type::ShortcutOverride) {
    1424            0 :         bool shouldOverrideShortcut = shortcutOverride(keyEvent);
    1425              : 
    1426            0 :         if (shouldOverrideShortcut) {
    1427            0 :             m_lastAcceptedKey = keyEvent->key();
    1428            0 :             keyEvent->accept();
    1429            0 :             return true;
    1430              :         }
    1431              :     }
    1432              : 
    1433            0 :     return QQuickPaintedItem::event(event);
    1434              : }
    1435              : 
    1436            0 : void AbstractNotationPaintView::inputMethodEvent(QInputMethodEvent* event)
    1437              : {
    1438            0 :     if (isInited()) {
    1439            0 :         m_inputController->inputMethodEvent(event);
    1440              :     }
    1441            0 : }
    1442              : 
    1443            0 : QVariant AbstractNotationPaintView::inputMethodQuery(Qt::InputMethodQuery query) const
    1444              : {
    1445            0 :     if (isInited() && m_inputController->canHandleInputMethodQuery(query)) {
    1446            0 :         return m_inputController->inputMethodQuery(query);
    1447              :     }
    1448              : 
    1449            0 :     return QQuickPaintedItem::inputMethodQuery(query);
    1450              : }
    1451              : 
    1452            0 : void AbstractNotationPaintView::dragEnterEvent(QDragEnterEvent* event)
    1453              : {
    1454            0 :     if (isInited()) {
    1455            0 :         m_inputController->dragEnterEvent(event);
    1456              :     }
    1457            0 : }
    1458              : 
    1459            0 : void AbstractNotationPaintView::dragLeaveEvent(QDragLeaveEvent* event)
    1460              : {
    1461            0 :     if (isInited()) {
    1462            0 :         m_inputController->dragLeaveEvent(event);
    1463              :     }
    1464            0 : }
    1465              : 
    1466            0 : void AbstractNotationPaintView::dragMoveEvent(QDragMoveEvent* event)
    1467              : {
    1468            0 :     if (isInited()) {
    1469            0 :         m_inputController->dragMoveEvent(event);
    1470              :     }
    1471            0 : }
    1472              : 
    1473            0 : void AbstractNotationPaintView::dropEvent(QDropEvent* event)
    1474              : {
    1475            0 :     if (isInited()) {
    1476            0 :         m_inputController->dropEvent(event);
    1477              :     }
    1478            0 : }
    1479              : 
    1480            0 : void AbstractNotationPaintView::setNotation(INotationPtr notation)
    1481              : {
    1482            0 :     m_notation = notation;
    1483              : 
    1484            0 :     if (publishMode() && isNoteEnterMode()) {
    1485            0 :         notationNoteInput()->endNoteInput(true);
    1486              :     }
    1487              : 
    1488            0 :     if (m_loadCalled) {
    1489            0 :         m_continuousPanel->setNotation(m_notation);
    1490            0 :         m_playbackCursor->setNotation(m_notation);
    1491            0 :         m_loopInMarker->setNotation(m_notation);
    1492            0 :         m_loopOutMarker->setNotation(m_notation);
    1493              :     }
    1494            0 : }
    1495              : 
    1496            0 : bool AbstractNotationPaintView::readonly() const
    1497              : {
    1498            0 :     return m_readonly;
    1499              : }
    1500              : 
    1501            0 : void AbstractNotationPaintView::setReadonly(bool readonly)
    1502              : {
    1503            0 :     if (m_readonly == readonly) {
    1504            0 :         return;
    1505              :     }
    1506              : 
    1507            0 :     m_readonly = readonly;
    1508            0 :     if (m_inputController) {
    1509            0 :         m_inputController->setReadonly(m_readonly);
    1510              :     }
    1511              : 
    1512            0 :     emit readonlyChanged();
    1513              : }
    1514              : 
    1515            0 : void AbstractNotationPaintView::clear()
    1516              : {
    1517            0 :     Transform oldMatrix = m_matrix;
    1518            0 :     m_matrix = Transform();
    1519            0 :     m_previousHorizontalScrollPosition = 0;
    1520            0 :     m_previousVerticalScrollPosition = 0;
    1521            0 :     m_shadowNoteRect = RectF();
    1522            0 :     m_previewMeasureRect = RectF();
    1523            0 :     onMatrixChanged(oldMatrix, m_matrix, false);
    1524            0 : }
    1525              : 
    1526            0 : qreal AbstractNotationPaintView::width() const
    1527              : {
    1528            0 :     return QQuickPaintedItem::width();
    1529              : }
    1530              : 
    1531            0 : qreal AbstractNotationPaintView::height() const
    1532              : {
    1533            0 :     return QQuickPaintedItem::height();
    1534              : }
    1535              : 
    1536            0 : PointF AbstractNotationPaintView::toLogical(const PointF& point) const
    1537              : {
    1538            0 :     return m_matrix.inverted().map(point);
    1539              : }
    1540              : 
    1541            0 : PointF AbstractNotationPaintView::toLogical(const QPointF& point) const
    1542              : {
    1543            0 :     return toLogical(PointF::fromQPointF(point));
    1544              : }
    1545              : 
    1546            0 : RectF AbstractNotationPaintView::toLogical(const RectF& rect) const
    1547              : {
    1548            0 :     return m_matrix.inverted().map(rect);
    1549              : }
    1550              : 
    1551            0 : PointF AbstractNotationPaintView::fromLogical(const PointF& point) const
    1552              : {
    1553            0 :     return m_matrix.map(point);
    1554              : }
    1555              : 
    1556            0 : RectF AbstractNotationPaintView::fromLogical(const RectF& rect) const
    1557              : {
    1558            0 :     return m_matrix.map(rect);
    1559              : }
    1560              : 
    1561            0 : bool AbstractNotationPaintView::isInited() const
    1562              : {
    1563            0 :     if (qFuzzyIsNull(width()) || qFuzzyIsNull(height())) {
    1564            0 :         return false;
    1565              :     }
    1566              : 
    1567            0 :     return notation() != nullptr;
    1568              : }
    1569              : 
    1570            0 : void AbstractNotationPaintView::onPlayingChanged()
    1571              : {
    1572            0 :     TRACEFUNC;
    1573              : 
    1574            0 :     bool isPlaying = playbackState()->isPlaying();
    1575            0 :     m_playbackCursor->setVisible(isPlaying);
    1576              : 
    1577            0 :     if (m_playbackCursorItem) {
    1578            0 :         m_playbackCursorItem->setVisible(isPlaying);
    1579              :     }
    1580              : 
    1581            0 :     m_autoScrollEnabled = true;
    1582            0 :     m_enableAutoScrollTimer.stop();
    1583              : 
    1584            0 :     if (isPlaying) {
    1585            0 :         m_lastPlaybackPosition = playbackState()->playbackPosition();
    1586            0 :         m_lastPlaybackPositionUpdateTimeNs = m_elapsedTimer.nsecsElapsed();
    1587            0 :         updatePlaybackCursorInterpolated();
    1588            0 :         m_updatePlaybackCursorInterpolatedTimer.start(23); // ~43 FPS (1024 buffer + 44100)
    1589              :     } else {
    1590            0 :         m_updatePlaybackCursorInterpolatedTimer.stop();
    1591            0 :         scheduleRedraw();
    1592              :     }
    1593            0 : }
    1594              : 
    1595            0 : void AbstractNotationPaintView::updatePlaybackCursorInterpolated()
    1596              : {
    1597            0 :     const INotationPlaybackPtr playback = notationPlayback();
    1598            0 :     if (!playback) {
    1599            0 :         return;
    1600              :     }
    1601              : 
    1602            0 :     const qint64 elapsed = m_elapsedTimer.nsecsElapsed();
    1603            0 :     const secs_t deltaSecs = (elapsed - m_lastPlaybackPositionUpdateTimeNs) / 1e9;
    1604            0 :     const secs_t estimatedSecs = m_lastPlaybackPosition + deltaSecs;
    1605            0 :     const midi::tick_t estimatedTick = playback->secToTick(estimatedSecs);
    1606              : 
    1607            0 :     movePlaybackCursor(estimatedTick);
    1608            0 : }
    1609              : 
    1610            0 : void AbstractNotationPaintView::movePlaybackCursor(muse::midi::tick_t tick)
    1611              : {
    1612            0 :     TRACEFUNC;
    1613              : 
    1614            0 :     RectF oldCursorRect = m_playbackCursor->rect();
    1615            0 :     m_playbackCursor->move(tick);
    1616            0 :     const RectF& newCursorRect = m_playbackCursor->rect();
    1617              : 
    1618            0 :     if (newCursorRect != oldCursorRect) {
    1619            0 :         onPlaybackCursorRectChanged();
    1620              :     }
    1621              : 
    1622            0 :     if (!m_playbackCursor->visible() || newCursorRect.isNull()) {
    1623            0 :         return;
    1624              :     }
    1625              : 
    1626            0 :     if (m_isAutomaticallyPanEnabled) {
    1627            0 :         if ((notation()->viewMode() == engraving::LayoutMode::LINE) && m_isSmoothPanningEnabled
    1628            0 :             && adjustCanvasPositionSmoothPan(newCursorRect)) {
    1629            0 :             return;
    1630              :         }
    1631              : 
    1632            0 :         if (m_autoScrollEnabled) {
    1633            0 :             bool adjustVertically = shouldAdjustCanvasVerticallyDuringPlayback(newCursorRect);
    1634            0 :             if (adjustCanvasPosition(newCursorRect, adjustVertically)) {
    1635            0 :                 return;
    1636              :             }
    1637              :         }
    1638              :     }
    1639            0 : }
    1640              : 
    1641            0 : bool AbstractNotationPaintView::shouldAdjustCanvasVerticallyDuringPlayback(const RectF& cursorRect)
    1642              : {
    1643            0 :     if (!viewport().intersects(cursorRect)) {
    1644            0 :         return true;
    1645              :     }
    1646              : 
    1647            0 :     const Page* page = pageByPoint(cursorRect.topRight());
    1648            0 :     if (!page) {
    1649            0 :         return false;
    1650              :     }
    1651              : 
    1652            0 :     if (m_pageCache.adjustVerticallyDuringPlayback.has_value()) {
    1653            0 :         return m_pageCache.adjustVerticallyDuringPlayback.value();
    1654              :     }
    1655              : 
    1656            0 :     int nonEmptySystemCount = 0;
    1657            0 :     m_pageCache.adjustVerticallyDuringPlayback = false;
    1658              : 
    1659            0 :     for (const System* system : page->systems()) {
    1660            0 :         if (!system->staves().empty()) {
    1661            0 :             nonEmptySystemCount++;
    1662              :         }
    1663              : 
    1664            0 :         if (nonEmptySystemCount > 1) {
    1665            0 :             m_pageCache.adjustVerticallyDuringPlayback = true;
    1666            0 :             break;
    1667              :         }
    1668              :     }
    1669              : 
    1670            0 :     return m_pageCache.adjustVerticallyDuringPlayback.value();
    1671              : }
    1672              : 
    1673            0 : void AbstractNotationPaintView::onPlaybackCursorRectChanged()
    1674              : {
    1675            0 :     if (!m_playbackCursor || !m_playbackCursorItem) {
    1676            0 :         return;
    1677              :     }
    1678              : 
    1679            0 :     QRectF cursorRect = fromLogical(m_playbackCursor->rect()).toQRectF();
    1680            0 :     cursorRect.setWidth(std::max(std::round(cursorRect.width()), 1.0)); // makes it move more smoothly
    1681              : 
    1682            0 :     QRectF viewRect(0.0, 0.0, width(), height());
    1683            0 :     QRectF newRect = cursorRect.intersected(viewRect);
    1684              : 
    1685            0 :     m_playbackCursorItem->setX(newRect.x());
    1686            0 :     m_playbackCursorItem->setY(newRect.y());
    1687            0 :     m_playbackCursorItem->setSize(newRect.size());
    1688              : }
    1689              : 
    1690            0 : const Page* AbstractNotationPaintView::pageByPoint(const PointF& point) const
    1691              : {
    1692            0 :     if (m_pageCache.page) {
    1693            0 :         const RectF r = m_pageCache.page->ldata()->bbox().translated(m_pageCache.page->pos());
    1694            0 :         if (r.contains(point)) {
    1695            0 :             return m_pageCache.page;
    1696              :         }
    1697              :     }
    1698              : 
    1699            0 :     m_pageCache.clear();
    1700              : 
    1701            0 :     INotationElementsPtr elements = notationElements();
    1702            0 :     m_pageCache.page = elements ? elements->pageByPoint(point) : nullptr;
    1703              : 
    1704            0 :     return m_pageCache.page;
    1705            0 : }
    1706              : 
    1707            0 : PointF AbstractNotationPaintView::alignToCurrentPageBorder(const RectF& showRect, const PointF& pos) const
    1708              : {
    1709            0 :     TRACEFUNC;
    1710              : 
    1711            0 :     PointF result = pos;
    1712            0 :     const Page* page = pageByPoint(showRect.topLeft());
    1713            0 :     if (!page) {
    1714            0 :         return result;
    1715              :     }
    1716              : 
    1717            0 :     RectF viewRect = viewport();
    1718              : 
    1719            0 :     if (result.x() < page->x() || viewRect.width() >= page->width()) {
    1720            0 :         result.setX(page->x());
    1721            0 :     } else if (viewRect.width() < page->width() && viewRect.width() + pos.x() > page->width() + page->x()) {
    1722            0 :         result.setX((page->width() + page->x()) - viewRect.width());
    1723              :     }
    1724            0 :     if (result.y() < page->y() || viewRect.height() >= page->height()) {
    1725            0 :         result.setY(page->y());
    1726            0 :     } else if (viewRect.height() < page->height() && viewRect.height() + pos.y() > page->height() + page->y()) {
    1727            0 :         result.setY((page->height() + page->y()) - viewRect.height());
    1728              :     }
    1729              : 
    1730            0 :     return result;
    1731            0 : }
    1732              : 
    1733            0 : bool AbstractNotationPaintView::publishMode() const
    1734              : {
    1735            0 :     return m_publishMode;
    1736              : }
    1737              : 
    1738            0 : void AbstractNotationPaintView::setPublishMode(bool arg)
    1739              : {
    1740            0 :     if (m_publishMode == arg) {
    1741            0 :         return;
    1742              :     }
    1743              : 
    1744            0 :     m_publishMode = arg;
    1745            0 :     if (m_publishMode) {
    1746            0 :         setReadonly(true);
    1747              :     }
    1748            0 :     emit publishModeChanged();
    1749              : }
    1750              : 
    1751            0 : bool AbstractNotationPaintView::automationMode() const
    1752              : {
    1753            0 :     return notationAutomation() ? notationAutomation()->isAutomationModeEnabled() : false;
    1754              : }
    1755              : 
    1756            0 : bool AbstractNotationPaintView::isMainView() const
    1757              : {
    1758            0 :     return m_isMainView;
    1759              : }
    1760              : 
    1761            0 : void AbstractNotationPaintView::setIsMainView(bool isMainView)
    1762              : {
    1763            0 :     if (m_isMainView == isMainView) {
    1764            0 :         return;
    1765              :     }
    1766              : 
    1767            0 :     m_isMainView = isMainView;
    1768            0 :     emit isMainViewChanged(m_isMainView);
    1769              : }
    1770              : 
    1771            0 : void AbstractNotationPaintView::setPlaybackCursorItem(QQuickItem* cursor)
    1772              : {
    1773            0 :     if (m_playbackCursorItem == cursor) {
    1774            0 :         return;
    1775              :     }
    1776              : 
    1777            0 :     m_playbackCursorItem = cursor;
    1778              : 
    1779            0 :     if (m_playbackCursorItem) {
    1780            0 :         m_playbackCursorItem->setVisible(playbackState()->isPlaying());
    1781            0 :         m_playbackCursorItem->setEnabled(false); // ignore mouse & keyboard events
    1782            0 :         m_playbackCursorItem->setProperty("color", notationConfiguration()->playbackCursorColor());
    1783              : 
    1784            0 :         connect(m_playbackCursorItem, &QObject::destroyed, this, [this]() {
    1785            0 :             m_playbackCursorItem = nullptr;
    1786            0 :         });
    1787              :     }
    1788              : }
        

Generated by: LCOV version 2.0-1