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 : }
|