LCOV - code coverage report
Current view: top level - src/notationscene/qml/MuseScore/NotationScene - notationviewinputcontroller.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 14.9 % 1046 156
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 "notationviewinputcontroller.h"
      24              : 
      25              : #include <set>
      26              : 
      27              : #include <QApplication>
      28              : #include <QMimeData>
      29              : #include <QQuickItem>
      30              : #include <QTimer>
      31              : #include <QtMath>
      32              : 
      33              : #include "defer.h"
      34              : #include "log.h"
      35              : 
      36              : #include "abstractelementpopupmodel.h"
      37              : 
      38              : #include "engraving/dom/drumset.h"
      39              : #include "engraving/dom/fret.h"
      40              : #include "engraving/dom/harmony.h"
      41              : #include "engraving/dom/mscore.h"
      42              : #include "engraving/dom/shadownote.h"
      43              : #include "engraving/dom/staff.h"
      44              : 
      45              : #include "notation/inotation.h"
      46              : #include "notation/inotationelements.h" // IWYU pragma: keep
      47              : #include "notation/inotationnoteinput.h"
      48              : #include "notation/inotationpainting.h" // IWYU pragma: keep
      49              : #include "notation/inotationselection.h"
      50              : #include "notation/inotationstyle.h"
      51              : #include "notation/inotationviewstate.h" // IWYU pragma: keep
      52              : 
      53              : #include "notationscene/notationcommands.h"
      54              : 
      55              : using namespace muse::rcommand;
      56              : using namespace mu;
      57              : using namespace mu::notation;
      58              : using namespace mu::engraving;
      59              : 
      60              : static constexpr int PIXELSSTEPSFACTOR = 5;
      61              : 
      62            2 : static bool seekAllowed(const mu::engraving::EngravingItem* element)
      63              : {
      64            2 :     if (!element) {
      65            0 :         return false;
      66              :     }
      67              : 
      68              :     static const ElementTypeSet ALLOWED_TYPES {
      69              :         ElementType::NOTE,
      70              :         ElementType::REST,
      71              :         ElementType::MMREST,
      72              :         ElementType::MEASURE,
      73              :         ElementType::BAR_LINE
      74            4 :     };
      75              : 
      76            2 :     return muse::contains(ALLOWED_TYPES, element->type());
      77              : }
      78              : 
      79           12 : NotationViewInputController::NotationViewInputController(IControlledView* view, const muse::modularity::ContextPtr& iocCtx)
      80           12 :     : muse::Contextable(iocCtx), m_view(view)
      81              : {
      82           12 : }
      83              : 
      84           24 : NotationViewInputController::~NotationViewInputController()
      85              : {
      86           24 : }
      87              : 
      88            0 : void NotationViewInputController::init()
      89              : {
      90            0 :     m_possibleZoomPercentages = configuration()->possibleZoomPercentageList();
      91              : 
      92            0 :     if (!m_readonly) {
      93            0 :         commandsController()->setViewController(this);
      94              : 
      95            0 :         onNotationChanged();
      96            0 :         projectSession()->currentNotationChanged().onNotify(this, [this]() {
      97            0 :             onNotationChanged();
      98            0 :         });
      99              :     }
     100            0 : }
     101              : 
     102            0 : void NotationViewInputController::deinit()
     103              : {
     104            0 :     if (commandsController()->viewController() == this) {
     105            0 :         commandsController()->setViewController(nullptr);
     106              :     }
     107            0 : }
     108              : 
     109            0 : void NotationViewInputController::onNotationChanged()
     110              : {
     111            0 :     INotationPtr currNotation = currentNotation();
     112            0 :     if (!currNotation) {
     113            0 :         return;
     114              :     }
     115              : 
     116            0 :     currNotation->interaction()->selectionChanged().onNotify(this, [this]() {
     117            0 :         const INotationPtr notation = currentNotation();
     118            0 :         if (!notation) {
     119            0 :             return;
     120              :         }
     121              : 
     122            0 :         const EngravingItem* selectedItem = notation->interaction()->selection()->element();
     123            0 :         ElementType type = selectedItem ? selectedItem->type() : ElementType::INVALID;
     124              : 
     125            0 :         bool noChanges = selectedItem && m_prevSelectedElement == selectedItem;
     126            0 :         m_prevSelectedElement = selectedItem;
     127              : 
     128            0 :         if (noChanges) {
     129            0 :             return;
     130              :         }
     131              : 
     132            0 :         m_view->hideContextMenu();
     133            0 :         m_view->hideElementPopup();
     134              : 
     135            0 :         if (!(QApplication::mouseButtons() & Qt::RightButton)
     136            0 :             && AbstractElementPopupModel::hasElementEditPopup(selectedItem)) {
     137            0 :             m_view->showElementPopup(type);
     138              :         }
     139            0 :     }, Asyncable::Mode::SetReplace /* FIXME */);
     140              : 
     141            0 :     currNotation->interaction()->textEditingStarted().onNotify(this, [this] {
     142            0 :         const INotationPtr notation = currentNotation();
     143            0 :         if (!notation) {
     144            0 :             return;
     145              :         }
     146              : 
     147            0 :         m_view->hideContextMenu();
     148              : 
     149            0 :         const TextBase* item = notation->interaction()->editedText();
     150            0 :         if (AbstractElementPopupModel::hasTextStylePopup(item) && item->selected()) {
     151              :             static const std::set<ElementType> TYPES_NEEDING_STAFF {
     152              :                 ElementType::LYRICS,
     153              :                 ElementType::FINGERING,
     154              :                 ElementType::STICKING,
     155              :                 ElementType::HARMONY,
     156              :                 ElementType::FIGURED_BASS
     157            0 :             };
     158            0 :             const bool needToSeeStaffWhileEnteringText = muse::contains(TYPES_NEEDING_STAFF, item->type());
     159            0 :             if (!needToSeeStaffWhileEnteringText || !item->empty()) {
     160            0 :                 m_view->showElementPopup(item->type());
     161            0 :                 return;
     162              :             }
     163              :         }
     164              : 
     165            0 :         if (item->isDynamic()) {
     166            0 :             m_view->showElementPopup(item->type());
     167            0 :             return;
     168              :         }
     169              : 
     170            0 :         m_view->hideElementPopup();
     171            0 :     }, Asyncable::Mode::SetReplace /* FIXME */);
     172              : 
     173            0 :     currNotation->interaction()->textEditingChanged().onNotify(this, [this] {
     174            0 :         const INotationPtr notation = currentNotation();
     175            0 :         if (!notation) {
     176            0 :             return;
     177              :         }
     178              : 
     179            0 :         if (!notation->interaction()->isTextEditingStarted()) {
     180            0 :             return;
     181              :         }
     182              : 
     183            0 :         const TextBase* item = notation->interaction()->editedText();
     184            0 :         if (AbstractElementPopupModel::hasTextStylePopup(item) && item->cursor()->hasSelection() && item->selected()) {
     185            0 :             m_view->showElementPopup(item->type());
     186              :         }
     187            0 :     }, Asyncable::Mode::SetReplace /* FIXME */);
     188              : 
     189            0 :     currNotation->interaction()->textEditingEnded().onReceive(this, [this](const TextBase*) {
     190            0 :         m_view->hideElementPopup(PopupModelType::TYPE_TEXT);
     191            0 :     }, Asyncable::Mode::SetReplace /* FIXME */);
     192            0 : }
     193              : 
     194            0 : void NotationViewInputController::initZoom()
     195              : {
     196            0 :     IF_ASSERT_FAILED(currentNotation()) {
     197            0 :         return;
     198              :     }
     199              : 
     200            0 :     ZoomType defaultZoomType = configuration()->defaultZoomType();
     201              : 
     202            0 :     currentNotation()->viewState()->setZoomType(defaultZoomType);
     203              : 
     204            0 :     switch (defaultZoomType) {
     205            0 :     case ZoomType::Percentage:
     206            0 :         doSetZoom(configuration()->defaultZoom(), {});
     207            0 :         break;
     208            0 :     case ZoomType::PageWidth:
     209            0 :         doZoomToPageWidth();
     210            0 :         break;
     211            0 :     case ZoomType::WholePage:
     212            0 :         doZoomToWholePage();
     213            0 :         break;
     214            0 :     case ZoomType::TwoPages:
     215            0 :         doZoomToTwoPages();
     216            0 :         break;
     217              :     }
     218              : 
     219            0 :     currentNotation()->viewState()->setMatrixInited(true);
     220              : }
     221              : 
     222            0 : void NotationViewInputController::initCanvasPos()
     223              : {
     224            0 :     RectF notationContentRect = m_view->notationContentRect();
     225            0 :     double totalScoreWidth = notationContentRect.width();
     226            0 :     double totalScoreHeight = notationContentRect.height();
     227              : 
     228            0 :     double curScaling = m_view->currentScaling();
     229            0 :     double viewWidth = m_view->width() / curScaling;
     230            0 :     double viewHeight = m_view->height() / curScaling;
     231              : 
     232            0 :     const double canvasMargin = MScore::horizontalPageGapOdd;
     233              : 
     234            0 :     bool centerHorizontally = totalScoreWidth < viewWidth - 2 * canvasMargin;
     235            0 :     bool centerVertically = totalScoreHeight < viewHeight - 2 * canvasMargin;
     236              : 
     237            0 :     double xMove = centerHorizontally ? 0.5 * (viewWidth - totalScoreWidth) : canvasMargin;
     238            0 :     double yMove = centerVertically ? 0.5 * (viewHeight - totalScoreHeight) : canvasMargin;
     239              : 
     240            0 :     m_view->moveCanvas(xMove, yMove);
     241            0 : }
     242              : 
     243            0 : void NotationViewInputController::updateZoomAfterSizeChange()
     244              : {
     245            0 :     IF_ASSERT_FAILED(currentNotation()) {
     246            0 :         return;
     247              :     }
     248              : 
     249            0 :     switch (currentNotation()->viewState()->zoomType().val) {
     250            0 :     case ZoomType::Percentage:
     251            0 :         break;
     252            0 :     case ZoomType::PageWidth:
     253            0 :         doZoomToPageWidth();
     254            0 :         break;
     255            0 :     case ZoomType::WholePage:
     256            0 :         doZoomToWholePage();
     257            0 :         break;
     258            0 :     case ZoomType::TwoPages:
     259            0 :         doZoomToTwoPages();
     260            0 :         break;
     261              :     }
     262              : }
     263              : 
     264            0 : bool NotationViewInputController::readonly() const
     265              : {
     266            0 :     return m_readonly;
     267              : }
     268              : 
     269            0 : void NotationViewInputController::setReadonly(bool readonly)
     270              : {
     271            0 :     m_readonly = readonly;
     272            0 : }
     273              : 
     274            0 : INotationPtr NotationViewInputController::currentNotation() const
     275              : {
     276            0 :     return projectSession()->currentNotation();
     277              : }
     278              : 
     279            0 : INotationStylePtr NotationViewInputController::notationStyle() const
     280              : {
     281            0 :     return currentNotation() ? currentNotation()->style() : nullptr;
     282              : }
     283              : 
     284          117 : INotationInteractionPtr NotationViewInputController::viewInteraction() const
     285              : {
     286          117 :     return m_view->notationInteraction();
     287              : }
     288              : 
     289            0 : const INotationInteraction::HitElementContext& NotationViewInputController::hitElementContext() const
     290              : {
     291            0 :     return viewInteraction()->hitElementContext();
     292              : }
     293              : 
     294            0 : void NotationViewInputController::zoomIn()
     295              : {
     296            0 :     int maxIndex = m_possibleZoomPercentages.size() > 0 ? m_possibleZoomPercentages.size() - 1 : 0;
     297            0 :     int currentIndex = std::min(currentZoomIndex() + 1, maxIndex);
     298            0 :     int zoom = m_possibleZoomPercentages[currentIndex];
     299            0 :     doSetZoom(zoom, findZoomFocusPoint());
     300            0 : }
     301              : 
     302            0 : void NotationViewInputController::zoomOut()
     303              : {
     304            0 :     int currentIndex = std::max(currentZoomIndex() - 1, 0);
     305            0 :     int zoom = m_possibleZoomPercentages[currentIndex];
     306            0 :     doSetZoom(zoom, findZoomFocusPoint());
     307            0 : }
     308              : 
     309            0 : PointF NotationViewInputController::findZoomFocusPoint() const
     310              : {
     311            0 :     double resultX = 0.0;
     312            0 :     double resultY = 0.0;
     313              : 
     314            0 :     INotationSelectionPtr selection = m_view->notationInteraction()->selection();
     315              : 
     316            0 :     if (selection->isNone()) {
     317              :         // No selection: zoom at the center of the view
     318            0 :         resultX = m_view->width() / 2;
     319            0 :         resultY = m_view->height() / 2;
     320              :     } else {
     321              :         // Selection: zoom at the center of the selection
     322            0 :         PointF result = m_view->fromLogical(selection->canvasBoundingRect().center());
     323            0 :         resultX = result.x();
     324            0 :         resultY = result.y();
     325              :     }
     326              : 
     327            0 :     return PointF(std::clamp(resultX, 0.0, m_view->width()),
     328            0 :                   std::clamp(resultY, 0.0, m_view->height()));
     329            0 : }
     330              : 
     331            0 : void NotationViewInputController::zoomToPageWidth()
     332              : {
     333            0 :     IF_ASSERT_FAILED(currentNotation()) {
     334            0 :         return;
     335              :     }
     336              : 
     337            0 :     currentNotation()->viewState()->setZoomType(ZoomType::PageWidth);
     338            0 :     doZoomToPageWidth();
     339              : }
     340              : 
     341            0 : void NotationViewInputController::doZoomToPageWidth()
     342              : {
     343            0 :     if (!notationStyle()) {
     344            0 :         return;
     345              :     }
     346              : 
     347            0 :     qreal pageWidth = notationStyle()->styleValue(mu::engraving::Sid::pageWidth).toDouble() * mu::engraving::DPI
     348            0 :                       + 2 * MScore::horizontalPageGapOdd;
     349            0 :     double viewWidth = m_view->width();
     350              : 
     351            0 :     qreal scale = viewWidth / pageWidth;
     352            0 :     setScaling(scale, PointF(), false);
     353              : }
     354              : 
     355            0 : void NotationViewInputController::zoomToWholePage()
     356              : {
     357            0 :     IF_ASSERT_FAILED(currentNotation()) {
     358            0 :         return;
     359              :     }
     360              : 
     361            0 :     currentNotation()->viewState()->setZoomType(ZoomType::WholePage);
     362            0 :     doZoomToWholePage();
     363              : }
     364              : 
     365            0 : void NotationViewInputController::doZoomToWholePage()
     366              : {
     367            0 :     if (!notationStyle()) {
     368            0 :         return;
     369              :     }
     370              : 
     371            0 :     qreal pageWidth = notationStyle()->styleValue(mu::engraving::Sid::pageWidth).toDouble() * mu::engraving::DPI
     372            0 :                       + 2 * MScore::horizontalPageGapOdd;
     373            0 :     qreal pageHeight = notationStyle()->styleValue(mu::engraving::Sid::pageHeight).toDouble() * mu::engraving::DPI
     374            0 :                        + 2 * MScore::horizontalPageGapOdd;
     375              : 
     376            0 :     double viewWidth = m_view->width();
     377            0 :     double viewHeight = m_view->height();
     378              : 
     379            0 :     qreal pageWidthScale = viewWidth / pageWidth;
     380            0 :     qreal pageHeightScale = viewHeight / pageHeight;
     381              : 
     382            0 :     qreal scale = std::min(pageWidthScale, pageHeightScale);
     383            0 :     setScaling(scale, PointF(), false);
     384              : }
     385              : 
     386            0 : void NotationViewInputController::zoomToTwoPages()
     387              : {
     388            0 :     IF_ASSERT_FAILED(currentNotation()) {
     389            0 :         return;
     390              :     }
     391              : 
     392            0 :     currentNotation()->viewState()->setZoomType(ZoomType::TwoPages);
     393            0 :     doZoomToTwoPages();
     394              : }
     395              : 
     396            0 : void NotationViewInputController::doZoomToTwoPages()
     397              : {
     398            0 :     if (!notationStyle()) {
     399            0 :         return;
     400              :     }
     401              : 
     402            0 :     qreal viewWidth = m_view->width();
     403            0 :     qreal viewHeight = m_view->height();
     404            0 :     qreal pageWidth = notationStyle()->styleValue(mu::engraving::Sid::pageWidth).toDouble() * mu::engraving::DPI
     405            0 :                       + 2 * MScore::horizontalPageGapOdd;
     406            0 :     qreal pageHeight = notationStyle()->styleValue(mu::engraving::Sid::pageHeight).toDouble() * mu::engraving::DPI
     407            0 :                        + 2 * MScore::horizontalPageGapOdd;
     408              : 
     409            0 :     qreal pageHeightScale = 0.0;
     410            0 :     qreal pageWidthScale = 0.0;
     411              : 
     412            0 :     if (configuration()->canvasOrientation().val == muse::Orientation::Vertical) {
     413            0 :         constexpr qreal VERTICAL_PAGE_GAP = 5.0;
     414            0 :         pageHeightScale = viewHeight / (pageHeight * 2.0 + VERTICAL_PAGE_GAP);
     415            0 :         pageWidthScale = viewWidth / pageWidth;
     416              :     } else {
     417            0 :         constexpr qreal HORIZONTAL_PAGE_GAP = 50.0;
     418            0 :         pageHeightScale = viewHeight / pageHeight;
     419            0 :         pageWidthScale = viewWidth / (pageWidth * 2.0 + HORIZONTAL_PAGE_GAP);
     420              :     }
     421              : 
     422            0 :     qreal scale = std::min(pageHeightScale, pageWidthScale);
     423            0 :     setScaling(scale, PointF(), false);
     424              : }
     425              : 
     426            0 : int NotationViewInputController::currentZoomIndex() const
     427              : {
     428            0 :     for (int index = 0; index < m_possibleZoomPercentages.size(); ++index) {
     429            0 :         if (m_possibleZoomPercentages[index] >= currentZoomPercentage()) {
     430            0 :             return index;
     431              :         }
     432              :     }
     433              : 
     434            0 :     return m_possibleZoomPercentages.isEmpty() ? 0 : m_possibleZoomPercentages.size() - 1;
     435              : }
     436              : 
     437            0 : int NotationViewInputController::currentZoomPercentage() const
     438              : {
     439            0 :     return zoomPercentageFromScaling(m_view->currentScaling());
     440              : }
     441              : 
     442            0 : void NotationViewInputController::setScaling(qreal scaling, const PointF& pos, bool overrideZoomType)
     443              : {
     444            0 :     qreal minScaling = scalingFromZoomPercentage(m_possibleZoomPercentages.first());
     445            0 :     qreal maxScaling = scalingFromZoomPercentage(m_possibleZoomPercentages.last());
     446            0 :     qreal correctedScaling = std::clamp(scaling, minScaling, maxScaling);
     447              : 
     448            0 :     m_view->setScaling(correctedScaling, pos, overrideZoomType);
     449            0 : }
     450              : 
     451            0 : void NotationViewInputController::setZoom(int zoomPercentage)
     452              : {
     453            0 :     doSetZoom(zoomPercentage, findZoomFocusPoint());
     454            0 : }
     455              : 
     456            0 : void NotationViewInputController::doSetZoom(int zoomPercentage, const PointF& pos)
     457              : {
     458            0 :     int minZoom = m_possibleZoomPercentages.first();
     459            0 :     int maxZoom = m_possibleZoomPercentages.last();
     460            0 :     int correctedZoom = std::clamp(zoomPercentage, minZoom, maxZoom);
     461              : 
     462            0 :     qreal scaling = scalingFromZoomPercentage(correctedZoom);
     463            0 :     m_view->setScaling(scaling, pos);
     464            0 : }
     465              : 
     466            0 : qreal NotationViewInputController::scalingFromZoomPercentage(int zoomPercentage) const
     467              : {
     468            0 :     return contextConfiguration()->scalingFromZoomPercentage(zoomPercentage);
     469              : }
     470              : 
     471            0 : int NotationViewInputController::zoomPercentageFromScaling(qreal scaling) const
     472              : {
     473            0 :     return contextConfiguration()->zoomPercentageFromScaling(scaling);
     474              : }
     475              : 
     476            0 : void NotationViewInputController::setViewMode(ViewMode viewMode)
     477              : {
     478            0 :     auto notation = projectSession()->currentNotation();
     479            0 :     if (notation) {
     480            0 :         notation->viewState()->setViewMode(viewMode);
     481            0 :         notation->painting()->setViewMode(viewMode);
     482              :     }
     483            0 : }
     484              : 
     485            0 : void NotationViewInputController::openContextMenuOfSelection()
     486              : {
     487            0 :     m_view->showContextMenu(selectionType(), m_view->fromLogical(selectionElementPos()).toQPointF());
     488            0 : }
     489              : 
     490              : constexpr qreal scrollStep = .8;
     491              : constexpr qreal notationScreenPadding = 25.0;
     492              : 
     493            0 : void NotationViewInputController::moveScreen(int direction)
     494              : {
     495            0 :     auto notation = currentNotation();
     496            0 :     if (!notation || muse::RealIsNull(m_view->width())) {
     497            0 :         return;
     498              :     }
     499            0 :     auto scale = m_view->currentScaling();
     500            0 :     if (notation->viewMode() == ViewMode::LINE) {
     501            0 :         m_view->moveCanvasHorizontal(m_view->width() * direction * scrollStep / scale);
     502              :     } else {
     503            0 :         auto offset = m_view->toLogical(QPoint());
     504            0 :         auto rect = m_view->notationContentRect();
     505            0 :         if (direction > 0 && offset.y() <= 0.0) {
     506            0 :             if (offset.x() >= -notationScreenPadding) {
     507            0 :                 m_view->moveCanvas(m_view->width() * direction * scrollStep / scale,
     508            0 :                                    offset.y() - notationScreenPadding - (rect.height() - m_view->height() / scale));
     509              :             }
     510            0 :         } else if (direction < 0 && offset.y() >= (rect.height() - m_view->height() / scale)) {
     511            0 :             auto dx = m_view->width() * direction * scrollStep / scale;
     512            0 :             if (offset.x() < rect.width() + notationScreenPadding + dx) {
     513            0 :                 m_view->moveCanvas(dx, offset.y() + notationScreenPadding);
     514              :             }
     515              :         } else {
     516            0 :             m_view->moveCanvasVertical(m_view->height() * direction * scrollStep / scale);
     517              :         }
     518              :     }
     519            0 : }
     520              : 
     521            0 : void NotationViewInputController::movePage(int direction)
     522              : {
     523            0 :     auto notation = currentNotation();
     524            0 :     if (!notation) {
     525            0 :         return;
     526              :     }
     527            0 :     if (notation->viewMode() != ViewMode::PAGE) {
     528            0 :         moveScreen(direction);
     529            0 :         return;
     530              :     }
     531            0 :     Page* page = notation->elements()->msScore()->pages().back();
     532            0 :     if (configuration()->canvasOrientation().val == muse::Orientation::Vertical) {
     533            0 :         qreal offset = std::min((page->height() + notationScreenPadding) * direction,
     534            0 :                                 m_view->toLogical(QPoint()).y() + notationScreenPadding);
     535            0 :         m_view->moveCanvasVertical(offset);
     536              :     } else {
     537            0 :         qreal offset = std::min((page->width() + notationScreenPadding) * direction,
     538            0 :                                 m_view->toLogical(QPoint()).x() + notationScreenPadding);
     539            0 :         m_view->moveCanvasHorizontal(offset);
     540              :     }
     541            0 : }
     542              : 
     543            0 : void NotationViewInputController::nextScreen()
     544              : {
     545            0 :     moveScreen(-1);
     546            0 : }
     547              : 
     548            0 : void NotationViewInputController::previousScreen()
     549              : {
     550            0 :     moveScreen(1);
     551            0 : }
     552              : 
     553            0 : void NotationViewInputController::nextPage()
     554              : {
     555            0 :     movePage(-1);
     556            0 : }
     557              : 
     558            0 : void NotationViewInputController::previousPage()
     559              : {
     560            0 :     movePage(1);
     561            0 : }
     562              : 
     563            0 : void NotationViewInputController::startOfScore()
     564              : {
     565            0 :     auto offset = m_view->toLogical(QPoint());
     566            0 :     m_view->moveCanvas(offset.x() + notationScreenPadding, offset.y() + notationScreenPadding);
     567            0 : }
     568              : 
     569            0 : void NotationViewInputController::endOfScore()
     570              : {
     571            0 :     auto notation = currentNotation();
     572            0 :     if (!notation) {
     573            0 :         return;
     574              :     }
     575            0 :     mu::engraving::MeasureBase* lastMeasure = notation->elements()->msScore()->lastMeasureMM();
     576            0 :     auto lmRect = lastMeasure->canvasBoundingRect();
     577            0 :     auto scale = m_view->currentScaling();
     578            0 :     qreal desiredX = std::max(-notationScreenPadding, lmRect.right() + notationScreenPadding - m_view->width() / scale);
     579            0 :     qreal desiredY = std::max(-notationScreenPadding, lmRect.bottom()
     580            0 :                               + lastMeasure->score()->style().styleD(mu::engraving::Sid::spatium) * 5 - m_view->height() / scale);
     581            0 :     auto offset = m_view->toLogical(QPoint());
     582            0 :     m_view->moveCanvas(offset.x() - desiredX, offset.y() - desiredY);
     583            0 : }
     584              : 
     585            0 : void NotationViewInputController::pinchToZoom(qreal scaleFactor, const QPointF& pos)
     586              : {
     587            0 :     double scale = m_view->currentScaling() * scaleFactor;
     588            0 :     setScaling(scale, PointF::fromQPointF(pos));
     589            0 : }
     590              : 
     591            4 : void NotationViewInputController::wheelEvent(QWheelEvent* event)
     592              : {
     593            4 :     QPoint pixelsScrolled = event->pixelDelta();
     594            4 :     QPoint stepsScrolled = event->angleDelta();
     595              : 
     596            4 :     int dx = 0;
     597            4 :     int dy = 0;
     598            4 :     qreal stepsX = 0.0;
     599            4 :     qreal stepsY = 0.0;
     600              : 
     601              : // pixelDelta is unreliable on X11
     602              : #ifdef Q_OS_LINUX
     603            4 :     if (std::getenv("WAYLAND_DISPLAY") == NULL) {
     604              :         // Ignore pixelsScrolled unless Wayland is used
     605            0 :         pixelsScrolled.setX(0);
     606            0 :         pixelsScrolled.setY(0);
     607              :     }
     608              : #endif
     609              : 
     610            4 :     if (!pixelsScrolled.isNull()) {
     611            3 :         dx = pixelsScrolled.x();
     612            3 :         dy = pixelsScrolled.y();
     613            3 :         stepsX = dx / static_cast<qreal>(PIXELSSTEPSFACTOR);
     614            3 :         stepsY = dy / static_cast<qreal>(PIXELSSTEPSFACTOR);
     615            1 :     } else if (!stepsScrolled.isNull()) {
     616            1 :         dx = (stepsScrolled.x() * qMax(2.0, m_view->width() / 10.0)) / static_cast<qreal>(QWheelEvent::DefaultDeltasPerStep);
     617            1 :         dy = (stepsScrolled.y() * qMax(2.0, m_view->height() / 10.0)) / static_cast<qreal>(QWheelEvent::DefaultDeltasPerStep);
     618            1 :         stepsX = static_cast<qreal>(stepsScrolled.x()) / static_cast<qreal>(QWheelEvent::DefaultDeltasPerStep);
     619            1 :         stepsY = static_cast<qreal>(stepsScrolled.y()) / static_cast<qreal>(QWheelEvent::DefaultDeltasPerStep);
     620              :     }
     621              : 
     622            4 :     Qt::KeyboardModifiers keyState = event->modifiers();
     623              : 
     624              :     // Windows touch pad pinches also execute this
     625            4 :     if (keyState & Qt::ControlModifier) {
     626            0 :         double zoomSpeed = qPow(2.0, 1.0 / configuration()->mouseZoomPrecision());
     627            0 :         qreal absSteps = sqrt(stepsX * stepsX + stepsY * stepsY) * (stepsY > -stepsX ? 1 : -1);
     628            0 :         double scale = m_view->currentScaling() * qPow(zoomSpeed, absSteps);
     629            0 :         setScaling(scale, PointF::fromQPointF(event->position()));
     630              :     } else {
     631            4 :         qreal correction = 1.0 / m_view->currentScaling();
     632            4 :         if (keyState & Qt::ShiftModifier) {
     633            1 :             int abs = sqrt(dx * dx + dy * dy) * (dy > -dx ? 1 : -1);
     634            1 :             m_view->moveCanvasHorizontal(abs * correction);
     635              :         } else {
     636            3 :             m_view->moveCanvas(dx * correction, dy * correction);
     637              :         }
     638              :     }
     639            4 : }
     640              : 
     641           12 : void NotationViewInputController::mousePressEvent(QMouseEvent* event)
     642              : {
     643           12 :     PointF logicPos = m_view->toLogical(event->pos());
     644           12 :     Qt::MouseButton button = event->button();
     645              : 
     646           12 :     m_mouseDownInfo = {
     647              :         /*.dragAction =*/ MouseDownInfo::Other,
     648           12 :         /*.physicalBeginPoint =*/ event->pos(),
     649              :         /*.logicalBeginPoint =*/ logicPos
     650              :     };
     651              : 
     652           12 :     m_shouldStartEditOnLeftClickRelease = false;
     653           12 :     m_ignoreNextMouseContextMenuEvent = false;
     654              : 
     655              :     // When using MiddleButton, just start moving the canvas
     656           12 :     if (button == Qt::MiddleButton) {
     657            0 :         return;
     658              :     }
     659              : 
     660           12 :     EngravingItem* hitElement = nullptr;
     661           12 :     staff_idx_t hitStaffIndex = muse::nidx;
     662              : 
     663           12 :     if (!m_readonly) {
     664           12 :         INotationInteraction::HitElementContext context;
     665           12 :         context.element = viewInteraction()->hitElement(logicPos, hitWidth());
     666           12 :         context.staff = viewInteraction()->hitStaff(logicPos);
     667           12 :         viewInteraction()->setHitElementContext(context);
     668              : 
     669           12 :         hitElement = context.element;
     670           12 :         hitStaffIndex = context.staff ? context.staff->idx() : muse::nidx;
     671              :     }
     672              : 
     673              :     // note enter mode
     674           12 :     if (m_view->isNoteEnterMode()) {
     675            0 :         handleClickInNoteInputMode(event);
     676            0 :         return;
     677              :     }
     678              : 
     679              :     // triple click
     680           12 :     if (m_tripleClickPending) {
     681            0 :         if (viewInteraction()->isTextEditingStarted()) {
     682            0 :             viewInteraction()->selectText(mu::engraving::SelectTextType::All);
     683            0 :             return;
     684              :         }
     685              :     }
     686              : 
     687           12 :     if (playbackController()->isPlaying()) {
     688            0 :         playbackController()->seekElement(hitElement);
     689            0 :         return;
     690              :     }
     691              : 
     692           12 :     ClickContext ctx;
     693           12 :     ctx.logicClickPos = logicPos;
     694           12 :     ctx.hitElement = hitElement;
     695           12 :     ctx.hitStaff = hitStaffIndex;
     696           12 :     ctx.isHitGrip = viewInteraction()->isHitGrip(logicPos);
     697           12 :     ctx.event = event;
     698              : 
     699              :     // Grip
     700           12 :     bool consumed = mousePress_considerGrip(ctx);
     701           12 :     if (consumed) {
     702            0 :         return;
     703              :     }
     704              : 
     705              :     // End editing
     706           12 :     if (!ctx.hitElement) {
     707            0 :         viewInteraction()->endEditElement();
     708              :     }
     709              : 
     710              :     // Drag outgoing: element
     711           12 :     consumed = mousePress_considerDragOutgoingElement(ctx);
     712           12 :     if (consumed) {
     713            0 :         return;
     714              :     }
     715              : 
     716              :     // Drag outgoing: range
     717           12 :     consumed = mousePress_considerDragOutgoingRange(ctx);
     718           12 :     if (consumed) {
     719            0 :         return;
     720              :     }
     721              : 
     722              :     // Alt+click to paste range
     723           12 :     consumed = mousePress_considerStartPasteRangeOnRelease(ctx);
     724           12 :     if (consumed) {
     725            0 :         return;
     726              :     }
     727              : 
     728              :     // Select
     729           12 :     mousePress_considerSelect(ctx);
     730              : 
     731              :     // Seek
     732           12 :     mousePress_seekSelection(ctx);
     733              : 
     734              :     // Misc
     735           12 :     if (button == Qt::LeftButton) {
     736           10 :         handleLeftClick(ctx);
     737            2 :     } else if (button == Qt::RightButton) {
     738            2 :         handleRightClick();
     739              :     }
     740              : }
     741              : 
     742            0 : void NotationViewInputController::handleClickInNoteInputMode(QMouseEvent* event)
     743              : {
     744            0 :     const PointF logicPos = m_view->toLogical(event->pos());
     745              : 
     746            0 :     if (event->button() == Qt::RightButton) {
     747            0 :         m_ignoreNextMouseContextMenuEvent = true;
     748            0 :         muse::rcommand::CommandQuery query(SCREEN_REMOVE_NOTE_COMMAND);
     749            0 :         query.set("pos_x", muse::Val(logicPos.x()));
     750            0 :         query.set("pos_y", muse::Val(logicPos.y()));
     751            0 :         commandDispatcher()->dispatch(query);
     752            0 :     } else {
     753            0 :         const Qt::KeyboardModifiers keyState = event->modifiers();
     754            0 :         const bool replace = keyState & Qt::ShiftModifier;
     755            0 :         const bool insert = keyState & Qt::ControlModifier;
     756              : 
     757            0 :         muse::rcommand::CommandQuery query(SCREEN_PUT_NOTE_COMMAND);
     758            0 :         query.set("pos_x", muse::Val(logicPos.x()));
     759            0 :         query.set("pos_y", muse::Val(logicPos.y()));
     760            0 :         query.set("replace", replace);
     761            0 :         query.set("insert", insert);
     762            0 :         commandDispatcher()->dispatch(query);
     763            0 :     }
     764            0 : }
     765              : 
     766           12 : bool NotationViewInputController::mousePress_considerGrip(const ClickContext& ctx)
     767              : {
     768           12 :     if (ctx.isHitGrip && ctx.event->button() == Qt::LeftButton) {
     769            0 :         viewInteraction()->startEditGrip(ctx.logicClickPos);
     770            0 :         return true;
     771           12 :     } else if (viewInteraction()->isGripEditStarted()) {
     772            0 :         viewInteraction()->endEditGrip();
     773              :     }
     774              : 
     775           12 :     return false;
     776              : }
     777              : 
     778           12 : bool NotationViewInputController::mousePress_considerDragOutgoingElement(const ClickContext& ctx)
     779              : {
     780           12 :     if (ctx.event->button() != Qt::LeftButton
     781           12 :         || ctx.event->modifiers() != (Qt::ShiftModifier | Qt::ControlModifier)) {
     782           12 :         return false;
     783              :     }
     784              : 
     785            0 :     if (!ctx.hitElement || !viewInteraction()->isOutgoingDragElementAllowed(ctx.hitElement)) {
     786            0 :         return false;
     787              :     }
     788              : 
     789            0 :     viewInteraction()->select({ ctx.hitElement }, SelectType::SINGLE, ctx.hitStaff);
     790            0 :     playbackController()->seekElement(ctx.hitElement);
     791            0 :     m_mouseDownInfo.dragAction = MouseDownInfo::DragOutgoingElement;
     792              : 
     793            0 :     return true;
     794              : }
     795              : 
     796           12 : bool NotationViewInputController::mousePress_considerStartPasteRangeOnRelease(const ClickContext& ctx)
     797              : {
     798              :     // Check if this is a left click with Alt modifier
     799           12 :     if (ctx.event->button() != Qt::LeftButton || !(ctx.event->modifiers() & Qt::AltModifier)) {
     800           12 :         return false;
     801              :     }
     802              : 
     803              :     // Check if we have a range selection that can be copied
     804            0 :     const INotationSelectionPtr selection = viewInteraction()->selection();
     805            0 :     if (!selection->isRange() || !selection->canCopy()) {
     806            0 :         return false;
     807              :     }
     808              : 
     809            0 :     const INotationSelectionRangePtr range = selection->range();
     810            0 :     const Fraction sourceTick = range->startTick();
     811            0 :     const Fraction tickLength = range->endTick() - range->startTick();
     812            0 :     const engraving::staff_idx_t sourceStaffIdx = range->startStaffIndex();
     813            0 :     const size_t numStaves = range->endStaffIndex() - range->startStaffIndex();
     814              : 
     815            0 :     const bool started = viewInteraction()->startDropRange(sourceTick, tickLength, sourceStaffIdx, numStaves,
     816              :                                                            /*preserveMeasureAlignment=*/ true);
     817            0 :     if (!started) {
     818            0 :         return false;
     819              :     }
     820              : 
     821            0 :     m_mouseDownInfo.dragAction = MouseDownInfo::PasteRangeOnRelease;
     822              : 
     823            0 :     const bool canDrop = viewInteraction()->updateDropRange(ctx.logicClickPos);
     824            0 :     m_view->asItem()->setCursor(canDrop ? Qt::DragCopyCursor : Qt::ForbiddenCursor);
     825              : 
     826            0 :     return true;
     827            0 : }
     828              : 
     829           12 : void NotationViewInputController::mousePress_considerSelect(const ClickContext& ctx)
     830              : {
     831           12 :     if (!ctx.hitElement) {
     832            0 :         return;
     833              :     }
     834              : 
     835           12 :     m_hitElementWasAlreadySingleSelected = ctx.hitElement == viewInteraction()->selection()->element();
     836              : 
     837           12 :     if (ctx.event->button() == Qt::LeftButton) {
     838           10 :         if (ctx.event->modifiers() & Qt::ControlModifier) {
     839            0 :             const std::vector<EngravingItem*> overlappingHitElements = viewInteraction()->hitElements(ctx.logicClickPos, hitWidth());
     840            0 :             if (overlappingHitElements.size() > 1) {
     841            0 :                 cycleOverlappingHitElements(overlappingHitElements, ctx.hitStaff);
     842            0 :                 return;
     843              :             }
     844            0 :         }
     845            2 :     } else if (ctx.event->button() == Qt::RightButton) {
     846            2 :         if (ctx.hitElement->selected()) {
     847            0 :             return;
     848              :         }
     849              :     }
     850              : 
     851           12 :     SelectType selectType = SelectType::SINGLE;
     852           12 :     if (ctx.event->modifiers() & Qt::ShiftModifier) {
     853            5 :         selectType = SelectType::RANGE;
     854            7 :     } else if (ctx.event->modifiers() & Qt::ControlModifier) {
     855            0 :         selectType = SelectType::ADD;
     856              :     } else {
     857            7 :         const INotationSelectionPtr selection = viewInteraction()->selection();
     858              : 
     859            7 :         if (selection->isRange()
     860           23 :             && (selection->range()->containsItem(ctx.hitElement, ctx.hitStaff)
     861            9 :                 || selection->range()->containsPoint(ctx.logicClickPos))) {
     862            2 :             m_shouldSelectOnLeftClickRelease = true;
     863            2 :             return;
     864            5 :         } else if (ctx.hitElement->selected()) {
     865            4 :             if (selection->elements().size() > 1) {
     866            0 :                 m_shouldSelectOnLeftClickRelease = true;
     867              :             }
     868            4 :             return;
     869              :         }
     870            7 :     }
     871              : 
     872           18 :     viewInteraction()->select({ ctx.hitElement }, selectType, ctx.hitStaff);
     873              : }
     874              : 
     875           12 : void NotationViewInputController::mousePress_seekSelection(const ClickContext& ctx)
     876              : {
     877           12 :     const INotationSelectionPtr selection = viewInteraction()->selection();
     878           12 :     const std::vector<EngravingItem*>& elements = selection->elements();
     879           12 :     if (elements.empty()) {
     880            0 :         return;
     881              :     }
     882              : 
     883           12 :     if (!selection->isRange()) {
     884            3 :         playbackController()->seekElement(elements.back());
     885            3 :         return;
     886              :     }
     887              : 
     888            9 :     EngravingItem* elementToSeek = ctx.hitElement;
     889              : 
     890           20 :     for (EngravingItem* element: elements) {
     891           11 :         if (!element || element == elementToSeek) {
     892            9 :             continue;
     893              :         }
     894              : 
     895            2 :         if (!seekAllowed(element)) {
     896            0 :             continue;
     897              :         }
     898              : 
     899            2 :         if (elementToSeek && elementToSeek->tick() <= element->tick()) {
     900            0 :             continue;
     901              :         }
     902              : 
     903            2 :         elementToSeek = element;
     904              :     }
     905              : 
     906            9 :     playbackController()->seekElement(elementToSeek);
     907           12 : }
     908              : 
     909            0 : void NotationViewInputController::cycleOverlappingHitElements(const std::vector<EngravingItem*>& hitElements,
     910              :                                                               staff_idx_t hitStaffIndex)
     911              : {
     912            0 :     const size_t numHitElements = hitElements.size();
     913            0 :     size_t currTop = numHitElements - 1;
     914            0 :     EngravingItem* e = hitElements[currTop];
     915            0 :     std::set<EngravingItem*> selectedAtPosition;
     916            0 :     bool found = false;
     917              : 
     918              :     // e is the topmost element in stacking order,
     919              :     // but we want to replace it with "first non-measure element after a selected element"
     920              :     // (if such an element exists)
     921            0 :     for (size_t i = 0; i <= numHitElements; ++i) {
     922            0 :         if (found) {
     923            0 :             e = hitElements[currTop];
     924            0 :             if (!e->isMeasure()) {
     925            0 :                 break;
     926              :             }
     927            0 :         } else if (hitElements[currTop]->selected()) {
     928            0 :             found = true;
     929            0 :             selectedAtPosition.emplace(hitElements[currTop]);
     930            0 :             e = nullptr;
     931              :         }
     932            0 :         currTop = (currTop + 1) % numHitElements;
     933              :     }
     934              : 
     935            0 :     if (e && !e->selected()) {
     936            0 :         for (EngravingItem* selectedElem : selectedAtPosition) {
     937            0 :             selectedElem->score()->deselect(selectedElem);
     938              :         }
     939            0 :         viewInteraction()->select({ e }, SelectType::ADD, hitStaffIndex);
     940              :     }
     941            0 : }
     942              : 
     943           12 : bool NotationViewInputController::mousePress_considerDragOutgoingRange(const ClickContext& ctx)
     944              : {
     945           12 :     if (ctx.event->button() != Qt::LeftButton) {
     946            2 :         return false;
     947              :     }
     948              : 
     949           10 :     if (!ctx.hitElement || ctx.hitElement->isMeasure()) {
     950            5 :         const INotationSelectionPtr selection = viewInteraction()->selection();
     951              : 
     952            5 :         if (selection->isRange() && selection->range()->containsPoint(ctx.logicClickPos)) {
     953            0 :             m_mouseDownInfo.dragAction = MouseDownInfo::DragOutgoingRange;
     954            0 :             return true;
     955              :         }
     956            5 :     }
     957              : 
     958           10 :     return false;
     959              : }
     960              : 
     961           10 : void NotationViewInputController::handleLeftClick(const ClickContext& ctx)
     962              : {
     963           10 :     if (!ctx.hitElement || !ctx.hitElement->selected()) {
     964            4 :         return;
     965              :     }
     966              : 
     967              :     // If it is the only selected element, start editing if needed
     968           18 :     if (ctx.hitElement == viewInteraction()->selection()->element()
     969           18 :         && ctx.hitElement->needStartEditingAfterSelecting()) {
     970            0 :         viewInteraction()->startEditElement(ctx.hitElement);
     971              :     }
     972              : 
     973            6 :     if (ctx.hitElement->isPlayable()) {
     974            9 :         playbackController()->playElements({ ctx.hitElement });
     975              :     }
     976              : 
     977            6 :     if (viewInteraction()->isTextSelected()) {
     978            1 :         updateTextCursorPosition();
     979              :     }
     980              : }
     981              : 
     982            2 : void NotationViewInputController::handleRightClick()
     983              : {
     984            2 :     m_mouseDownInfo.dragAction = MouseDownInfo::Nothing;
     985              : 
     986              :     // See also AbstractNotationPaintView::event for context menu event handling
     987            2 : }
     988              : 
     989            0 : bool NotationViewInputController::startTextEditingAllowed() const
     990              : {
     991            0 :     INotationInteractionPtr interaction = viewInteraction();
     992            0 :     return interaction->isTextSelected() && !interaction->isTextEditingStarted();
     993            0 : }
     994              : 
     995            1 : void NotationViewInputController::updateTextCursorPosition()
     996              : {
     997            1 :     if (viewInteraction()->isTextEditingStarted()) {
     998            1 :         viewInteraction()->changeTextCursorPosition(m_mouseDownInfo.logicalBeginPoint);
     999              : 
    1000              :         // Show text style popup
    1001            1 :         const TextBase* item = viewInteraction()->editedText();
    1002            1 :         if (AbstractElementPopupModel::hasTextStylePopup(item)
    1003            1 :             && (!item->isLyrics() || !item->empty()) && item->selected()) {
    1004            0 :             m_view->showElementPopup(item->type());
    1005              :         }
    1006              :     }
    1007            1 : }
    1008              : 
    1009            0 : bool NotationViewInputController::tryPercussionShortcut(QKeyEvent* event)
    1010              : {
    1011            0 :     INotationNoteInputPtr noteInput = viewInteraction()->noteInput();
    1012            0 :     const mu::engraving::Drumset* drumset = noteInput ? noteInput->state().drumset() : nullptr;
    1013            0 :     if (!drumset) {
    1014            0 :         return false;
    1015              :     }
    1016              : 
    1017            0 :     const QKeyCombination noModifiers(Qt::Key(event->key()));
    1018            0 :     const int pitchToWrite = drumset->pitchForShortcut(QKeySequence(noModifiers).toString());
    1019            0 :     if (pitchToWrite < 0) {
    1020            0 :         return false;
    1021              :     }
    1022              : 
    1023            0 :     const Qt::KeyboardModifiers mods = event->modifiers();
    1024            0 :     NoteAddingMode addingMode = NoteAddingMode::NextChord;
    1025              : 
    1026            0 :     if (mods & Qt::ShiftModifier && (mods & Qt::ControlModifier || mods & Qt::MetaModifier)) {
    1027            0 :         addingMode = NoteAddingMode::InsertChord;
    1028            0 :     } else if (mods & Qt::ShiftModifier) {
    1029            0 :         addingMode = NoteAddingMode::CurrentChord;
    1030            0 :     } else if (mods & ~Qt::NoModifier) {
    1031              :         // Not a supported modifier combination...
    1032            0 :         return false;
    1033              :     }
    1034              : 
    1035              :     muse::rcommand::Params params = {
    1036            0 :         { "pitch", muse::Val(pitchToWrite) },
    1037            0 :         Contract<NoteAddingMode>::param(addingMode)
    1038            0 :     };
    1039              : 
    1040            0 :     commandDispatcher()->dispatch(ADD_DRUM_NOTE_COMMAND, params);
    1041              : 
    1042            0 :     return true;
    1043            0 : }
    1044              : 
    1045            0 : void NotationViewInputController::mouseMoveEvent(QMouseEvent* event)
    1046              : {
    1047            0 :     if (m_mouseDownInfo.dragAction == MouseDownInfo::Nothing) {
    1048            0 :         return;
    1049              :     }
    1050              : 
    1051            0 :     const QPointF physicalDragDelta = event->pos() - m_mouseDownInfo.physicalBeginPoint;
    1052              : 
    1053            0 :     const bool isDragStarted = m_isCanvasDragged
    1054            0 :                                || viewInteraction()->isDragStarted()
    1055            0 :                                || viewInteraction()->isOutgoingDragStarted();
    1056            0 :     if (!isDragStarted) {
    1057              :         // only start drag operations after a minimum of movement:
    1058            0 :         const bool canStartDrag = physicalDragDelta.manhattanLength() > 4;
    1059            0 :         if (!canStartDrag) {
    1060            0 :             return;
    1061              :         }
    1062              :     }
    1063              : 
    1064            0 :     switch (m_mouseDownInfo.dragAction) {
    1065            0 :     case MouseDownInfo::DragOutgoingElement: {
    1066            0 :         if (!isDragStarted) {
    1067            0 :             const EngravingItem* element = viewInteraction()->hitElementContext().element;
    1068            0 :             const PointF hotSpot = m_mouseDownInfo.logicalBeginPoint - element->canvasBoundingRect().topLeft();
    1069              : 
    1070            0 :             viewInteraction()->startOutgoingDragElement(element, m_view->asItem(), hotSpot);
    1071              :         }
    1072            0 :         return;
    1073              :     }
    1074            0 :     case MouseDownInfo::DragOutgoingRange: {
    1075            0 :         if (!isDragStarted) {
    1076            0 :             viewInteraction()->startOutgoingDragRange(m_view->asItem());
    1077              :         }
    1078            0 :         return;
    1079              :     }
    1080            0 :     case MouseDownInfo::PasteRangeOnRelease: {
    1081            0 :         const PointF logicPos = m_view->toLogical(event->pos());
    1082              : 
    1083            0 :         std::optional<bool> preserveMeasureAlignment;
    1084            0 :         if (physicalDragDelta.manhattanLength() > 4) {
    1085            0 :             preserveMeasureAlignment = false;
    1086              :         }
    1087              : 
    1088            0 :         const bool canDrop = viewInteraction()->updateDropRange(logicPos, preserveMeasureAlignment);
    1089            0 :         m_view->asItem()->setCursor(canDrop ? Qt::DragCopyCursor : Qt::ForbiddenCursor);
    1090            0 :         return;
    1091              :     }
    1092            0 :     case MouseDownInfo::Nothing:
    1093            0 :         return;
    1094            0 :     case MouseDownInfo::Other:
    1095            0 :         break;
    1096              :     }
    1097              : 
    1098            0 :     const Qt::KeyboardModifiers keyState = event->modifiers();
    1099              : 
    1100            0 :     m_view->hideContextMenu();
    1101            0 :     if (!viewInteraction()->isTextEditingStarted()) {
    1102            0 :         m_view->hideElementPopup();
    1103              :     }
    1104              : 
    1105            0 :     const PointF logicPos = m_view->toLogical(event->pos());
    1106              : 
    1107            0 :     const bool isNoteEnterMode = m_view->isNoteEnterMode();
    1108            0 :     const bool isMiddleButton  = (event->buttons() & Qt::MiddleButton);
    1109            0 :     const bool isDragObjectsAllowed = !(readonly() || isNoteEnterMode || playbackController()->isPlaying() || isMiddleButton);
    1110            0 :     if (isDragObjectsAllowed) {
    1111            0 :         const EngravingItem* hitElement = hitElementContext().element;
    1112              : 
    1113              :         // drag element
    1114            0 :         if ((hitElement && (hitElement->isMovable() || viewInteraction()->isEditingElement()))
    1115            0 :             || viewInteraction()->isGripEditStarted()) {
    1116            0 :             if (hitElement && !viewInteraction()->isDragStarted()) {
    1117            0 :                 startDragElements(hitElement->type(), hitElement->offset());
    1118              :             }
    1119              : 
    1120            0 :             if (viewInteraction()->isGripEditStarted() && !viewInteraction()->isDragStarted()) {
    1121            0 :                 const EngravingItem* selectedElement = viewInteraction()->selection()->element();
    1122            0 :                 ElementType type = selectedElement ? selectedElement->type() : ElementType::INVALID;
    1123            0 :                 startDragElements(type, selectedElement->offset());
    1124              :             }
    1125              : 
    1126            0 :             DragMode mode = DragMode::BothXY;
    1127            0 :             if (keyState & Qt::ShiftModifier) {
    1128            0 :                 mode = DragMode::OnlyY;
    1129            0 :             } else if (keyState & Qt::ControlModifier) {
    1130            0 :                 mode = DragMode::OnlyX;
    1131              :             }
    1132              : 
    1133            0 :             const std::vector<int> oldPitches = pitchesBeingDragged();
    1134            0 :             viewInteraction()->drag(m_mouseDownInfo.logicalBeginPoint, logicPos, mode);
    1135              : 
    1136            0 :             if (!oldPitches.empty() && oldPitches != pitchesBeingDragged()) {
    1137            0 :                 playbackController()->playElements(m_notesBeingDragged);
    1138              :             }
    1139              : 
    1140            0 :             return;
    1141            0 :         } else if (hitElement == nullptr && (keyState & Qt::ShiftModifier)) {
    1142            0 :             if (!viewInteraction()->isDragStarted()) {
    1143            0 :                 viewInteraction()->startDrag(std::vector<EngravingItem*>(), PointF(), [](const EngravingItem*) { return false; });
    1144              :             }
    1145            0 :             viewInteraction()->drag(m_mouseDownInfo.logicalBeginPoint, logicPos, DragMode::BothXY);
    1146              : 
    1147            0 :             return;
    1148              :         }
    1149              :     }
    1150              : 
    1151              :     // move canvas
    1152            0 :     if (!isNoteEnterMode || isMiddleButton) {
    1153            0 :         const PointF logicalDragDelta = logicPos - m_mouseDownInfo.logicalBeginPoint;
    1154            0 :         m_view->moveCanvas(logicalDragDelta.x(), logicalDragDelta.y());
    1155              : 
    1156            0 :         m_isCanvasDragged = true;
    1157              :     }
    1158              : }
    1159              : 
    1160            0 : void NotationViewInputController::startDragElements(ElementType elementsType, const PointF& elementsOffset)
    1161              : {
    1162            0 :     if (elementsType == ElementType::INVALID) {
    1163            0 :         return;
    1164              :     }
    1165              : 
    1166            0 :     const std::vector<EngravingItem*>& elements = viewInteraction()->selection()->elements();
    1167            0 :     if (elements.empty()) {
    1168            0 :         return;
    1169              :     }
    1170              : 
    1171            0 :     bool isFilterType = viewInteraction()->selection()->isRange();
    1172            0 :     auto isDraggable = [isFilterType, elementsType](const EngravingItem* element) {
    1173            0 :         return element && element->selected() && (!isFilterType || elementsType == element->type());
    1174            0 :     };
    1175              : 
    1176            0 :     viewInteraction()->startDrag(elements, elementsOffset, isDraggable);
    1177              : 
    1178            0 :     if (!viewInteraction()->isDragStarted()) {
    1179            0 :         return;
    1180              :     }
    1181              : 
    1182            0 :     for (const EngravingItem* item : elements) {
    1183            0 :         if (item->isNote()) {
    1184            0 :             m_notesBeingDragged.push_back(item);
    1185              :         }
    1186              :     }
    1187              : }
    1188              : 
    1189            0 : void NotationViewInputController::mouseReleaseEvent(QMouseEvent* event)
    1190              : {
    1191            0 :     if (m_mouseDownInfo.dragAction == MouseDownInfo::Nothing) {
    1192            0 :         return;
    1193              :     }
    1194              : 
    1195            0 :     INotationInteractionPtr interaction = viewInteraction();
    1196            0 :     INotationNoteInputPtr noteInput = interaction->noteInput();
    1197            0 :     const EngravingItem* hitElement = hitElementContext().element;
    1198              : 
    1199            0 :     DEFER {
    1200            0 :         m_mouseDownInfo.dragAction = MouseDownInfo::Nothing;
    1201            0 :         m_view->asItem()->setCursor(QCursor());
    1202            0 :     };
    1203              : 
    1204            0 :     if (m_mouseDownInfo.dragAction == MouseDownInfo::PasteRangeOnRelease) {
    1205            0 :         INotationSelectionPtr selection = interaction->selection();
    1206            0 :         if (!selection->isRange() || !selection->canCopy()) {
    1207            0 :             viewInteraction()->endDrop();
    1208            0 :             return;
    1209              :         }
    1210            0 :         muse::ByteArray rangeData = selection->mimeData();
    1211            0 :         if (rangeData.empty()) {
    1212            0 :             viewInteraction()->endDrop();
    1213            0 :             return;
    1214              :         }
    1215            0 :         PointF logicPos = m_view->toLogical(event->pos());
    1216            0 :         viewInteraction()->dropRange(rangeData.toQByteArrayNoCopy(), logicPos, false);
    1217            0 :         return;
    1218            0 :     }
    1219              : 
    1220            0 :     if (event->modifiers() != Qt::ControlModifier && event->modifiers() != Qt::ShiftModifier) {
    1221            0 :         if (!hitElement && !m_isCanvasDragged && !interaction->isGripEditStarted()
    1222            0 :             && !interaction->isDragStarted() && !noteInput->isNoteInputMode()) {
    1223            0 :             interaction->clearSelection();
    1224              :         }
    1225              :     }
    1226              : 
    1227            0 :     if (event->button() == Qt::LeftButton && event->modifiers() == Qt::NoModifier) {
    1228            0 :         handleLeftClickRelease(event->pos());
    1229              :     }
    1230              : 
    1231            0 :     m_isCanvasDragged = false;
    1232              : 
    1233            0 :     if (interaction->isDragStarted()) {
    1234              :         bool isDraggingHairpinSegmentStartEndGrip
    1235            0 :             = interaction->isGripEditStarted()
    1236            0 :               && interaction->selection()->element()
    1237            0 :               && interaction->selection()->element()->isHairpinSegment()
    1238            0 :               && interaction->isStartEndGripSelected();
    1239              : 
    1240            0 :         interaction->endDrag();
    1241              : 
    1242              :         // When dragging start or end of hairpin to a note or rest, open dynamic popup
    1243              :         // Check for note or rest happens in Score::addText which is called through addTextToItem in toggleDynamicPopup
    1244            0 :         if (isDraggingHairpinSegmentStartEndGrip) {
    1245            0 :             interaction->toggleDynamicPopup();
    1246              :         }
    1247              :     }
    1248              : 
    1249            0 :     if (interaction->isOutgoingDragStarted()) {
    1250            0 :         interaction->endOutgoingDrag();
    1251              :     }
    1252              : 
    1253            0 :     m_notesBeingDragged.clear();
    1254            0 : }
    1255              : 
    1256            0 : void NotationViewInputController::handleLeftClickRelease(const QPointF& releasePoint)
    1257              : {
    1258            0 :     if (m_view->isNoteEnterMode() || playbackController()->isPlaying()) {
    1259            0 :         return;
    1260              :     }
    1261              : 
    1262            0 :     if (m_shouldStartEditOnLeftClickRelease) {
    1263            0 :         muse::rcommand::CommandQuery query(SCREEN_EDIT_ELEMENT_COMMAND);
    1264            0 :         query.set("pos_x", muse::Val(m_mouseDownInfo.logicalBeginPoint.x()));
    1265            0 :         query.set("pos_y", muse::Val(m_mouseDownInfo.logicalBeginPoint.y()));
    1266            0 :         commandDispatcher()->dispatch(query);
    1267            0 :         m_shouldStartEditOnLeftClickRelease = false;
    1268            0 :         return;
    1269            0 :     }
    1270              : 
    1271            0 :     const INotationInteraction::HitElementContext& ctx = hitElementContext();
    1272            0 :     if (!ctx.element) {
    1273            0 :         return;
    1274              :     }
    1275              : 
    1276            0 :     if (releasePoint != m_mouseDownInfo.physicalBeginPoint) {
    1277            0 :         return;
    1278              :     }
    1279              : 
    1280            0 :     if (m_shouldSelectOnLeftClickRelease) {
    1281            0 :         m_shouldSelectOnLeftClickRelease = false;
    1282            0 :         engraving::staff_idx_t staffIndex = ctx.staff ? ctx.staff->idx() : muse::nidx;
    1283              : 
    1284            0 :         INotationInteractionPtr interaction = viewInteraction();
    1285            0 :         interaction->select({ ctx.element }, SelectType::SINGLE, staffIndex);
    1286              : 
    1287            0 :         if (ctx.element->needStartEditingAfterSelecting()) {
    1288            0 :             viewInteraction()->startEditElement(ctx.element);
    1289            0 :             return;
    1290              :         }
    1291            0 :     }
    1292              : 
    1293            0 :     if (!m_hitElementWasAlreadySingleSelected) {
    1294            0 :         return;
    1295              :     }
    1296              : 
    1297              :     // Same element clicked again while it was already selected.
    1298              :     // Either toggle popup or start text editing.
    1299              : 
    1300            0 :     if (viewInteraction()->textEditingAllowed(ctx.element)) {
    1301            0 :         if (!viewInteraction()->isTextEditingStarted()) {
    1302            0 :             viewInteraction()->startEditText(ctx.element, m_mouseDownInfo.logicalBeginPoint);
    1303              :         }
    1304              :     } else {
    1305            0 :         togglePopupForItemIfSupports(ctx.element);
    1306              :     }
    1307              : }
    1308              : 
    1309            0 : void NotationViewInputController::mouseDoubleClickEvent(QMouseEvent* event)
    1310              : {
    1311            0 :     if (m_view->isNoteEnterMode()) {
    1312            0 :         return;
    1313              :     }
    1314              : 
    1315              :     // Can't pass `this` as the second argument, because not QObject. But `m_view` works as well.
    1316            0 :     QTimer::singleShot(QApplication::doubleClickInterval(), m_view->asItem(), [this]() {
    1317            0 :         m_tripleClickPending = false;
    1318            0 :     });
    1319              : 
    1320            0 :     m_tripleClickPending = true;
    1321              : 
    1322            0 :     if (viewInteraction()->isTextEditingStarted()) {
    1323            0 :         viewInteraction()->selectText(mu::engraving::SelectTextType::Word);
    1324            0 :         return;
    1325              :     }
    1326              : 
    1327            0 :     EngravingItem* hitElement = hitElementContext().element;
    1328            0 :     if (!hitElement) {
    1329            0 :         return;
    1330              :     }
    1331              : 
    1332            0 :     if (viewInteraction()->textEditingAllowed(hitElement) && !playbackController()->isPlaying()) {
    1333            0 :         viewInteraction()->startEditText(hitElement, m_mouseDownInfo.logicalBeginPoint);
    1334            0 :         return;
    1335              :     }
    1336              : 
    1337            0 :     const Qt::KeyboardModifiers modifiers = event->modifiers();
    1338              : 
    1339            0 :     std::optional<SelectType> selectType;
    1340            0 :     switch (modifiers) {
    1341            0 :     case Qt::NoModifier: {
    1342            0 :         selectType = SelectType::REPLACE;
    1343            0 :         break;
    1344              :     }
    1345            0 :     case Qt::ControlModifier: {
    1346            0 :         selectType = SelectType::ADD;
    1347            0 :         break;
    1348              :     }
    1349            0 :     default: {
    1350            0 :         selectType = std::nullopt;
    1351            0 :         break;
    1352              :     }
    1353              :     }
    1354              : 
    1355            0 :     switch (hitElement->type()) {
    1356            0 :     case ElementType::INSTRUMENT_NAME: {
    1357            0 :         if (modifiers != Qt::NoModifier) {
    1358            0 :             break;     // Doesn't support modifiers...
    1359              :         }
    1360            0 :         m_shouldStartEditOnLeftClickRelease = true;
    1361            0 :         break;
    1362              :     }
    1363            0 :     case ElementType::MEASURE: {
    1364            0 :         if (modifiers != Qt::NoModifier) {
    1365            0 :             break;     // Doesn't support modifiers...
    1366              :         }
    1367            0 :         commandDispatcher()->dispatch(TOGGLE_NOTE_INPUT_COMMAND);
    1368            0 :         break;
    1369              :     }
    1370            0 :     case ElementType::NOTE: {
    1371            0 :         if (!selectType.has_value()) {
    1372            0 :             break;     // Unsupported modifier(s)...
    1373              :         }
    1374            0 :         const Chord* chord = toNote(hitElement)->chord();
    1375            0 :         IF_ASSERT_FAILED(chord) {
    1376            0 :             break;
    1377              :         }
    1378            0 :         const std::vector<Note*>& notes = chord->notes();
    1379            0 :         if (selectType.value() == SelectType::ADD && std::all_of(notes.begin(), notes.end(), [](Note* n){ return n->selected(); })) {
    1380            0 :             for (Note* n : notes) {
    1381            0 :                 n->score()->deselect(n);
    1382              :             }
    1383              :         } else {
    1384            0 :             viewInteraction()->select({ notes.begin(), notes.end() }, selectType.value());
    1385              :         }
    1386            0 :         break;
    1387              :     }
    1388            0 :     case ElementType::FRET_DIAGRAM: {
    1389            0 :         if (!selectType.has_value()) {
    1390            0 :             return;     // Unsupported modifier(s)...
    1391              :         }
    1392            0 :         if (Harmony* harmony = toFretDiagram(hitElement)->harmony()) {
    1393            0 :             viewInteraction()->select({ hitElement, harmony }, selectType.value());
    1394              :         }
    1395            0 :         break;
    1396              :     }
    1397            0 :     default: break;
    1398              :     }
    1399              : }
    1400              : 
    1401            0 : void NotationViewInputController::hoverMoveEvent(QHoverEvent* event)
    1402              : {
    1403            0 :     if (!m_view->isNoteEnterMode()) {
    1404            0 :         return;
    1405              :     }
    1406              : 
    1407            0 :     const PointF oldPos = m_view->toLogical(event->oldPosF());
    1408            0 :     PointF pos = m_view->toLogical(event->position());
    1409              : 
    1410            0 :     const ShadowNote* shadowNote = viewInteraction()->shadowNote();
    1411            0 :     if (shadowNote && m_view->elementPopupIsOpen(shadowNote->type())) {
    1412              :         // Lock the X position to the shadow note X (prevents the popup from jumping horizontally to other input positions)
    1413            0 :         pos.setX(shadowNote->canvasX());
    1414              :     }
    1415              : 
    1416            0 :     if (oldPos == pos) {
    1417            0 :         return;
    1418              :     }
    1419              : 
    1420            0 :     viewInteraction()->showShadowNoteForPosition(pos);
    1421              : 
    1422            0 :     if (event->modifiers() == Qt::ShiftModifier) {
    1423            0 :         updateShadowNotePopupVisibility();
    1424              :     }
    1425              : }
    1426              : 
    1427            0 : void NotationViewInputController::hoverLeaveEvent(QHoverEvent*)
    1428              : {
    1429            0 :     viewInteraction()->hideShadowNote();
    1430            0 : }
    1431              : 
    1432            0 : bool NotationViewInputController::isAnchorEditingEvent(QKeyEvent* event) const
    1433              : {
    1434            0 :     bool anchorEditingKeyCombo = (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right)
    1435            0 :                                  && event->modifiers() & Qt::ShiftModifier;
    1436            0 :     EngravingItem* selectedItem = viewInteraction()->selection()->element();
    1437            0 :     return selectedItem && selectedItem->allowTimeAnchor() && anchorEditingKeyCombo;
    1438              : }
    1439              : 
    1440            0 : bool NotationViewInputController::shortcutOverrideEvent(QKeyEvent* event)
    1441              : {
    1442            0 :     auto key = event->key();
    1443              : 
    1444            0 :     const bool editTextKeysFound = key == Qt::Key_Return || key == Qt::Key_Enter;
    1445            0 :     if (editTextKeysFound && startTextEditingAllowed()) {
    1446            0 :         return true;
    1447              :     }
    1448              : 
    1449            0 :     if (key == Qt::Key_Escape && m_mouseDownInfo.dragAction == MouseDownInfo::PasteRangeOnRelease) {
    1450            0 :         return true;
    1451              :     }
    1452              : 
    1453            0 :     if (viewInteraction()->isEditingElement()) {
    1454            0 :         return viewInteraction()->isEditAllowed(event);
    1455              :     }
    1456              : 
    1457            0 :     if (isAnchorEditingEvent(event)) {
    1458            0 :         return true;
    1459              :     }
    1460              : 
    1461            0 :     return tryPercussionShortcut(event);
    1462              : }
    1463              : 
    1464            0 : void NotationViewInputController::keyPressEvent(QKeyEvent* event)
    1465              : {
    1466            0 :     auto key = event->key();
    1467              : 
    1468            0 :     if (startTextEditingAllowed() && (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)) {
    1469            0 :         commandDispatcher()->dispatch(SCREEN_EDIT_TEXT_COMMAND);
    1470            0 :         event->accept();
    1471            0 :     } else if (event->key() == Qt::Key_Escape && m_mouseDownInfo.dragAction == MouseDownInfo::PasteRangeOnRelease) {
    1472              :         // Cancel "Alt+click to paste range"
    1473            0 :         viewInteraction()->endDrop();
    1474            0 :         m_mouseDownInfo.dragAction = MouseDownInfo::Nothing;
    1475            0 :         m_view->asItem()->setCursor({});
    1476            0 :         event->accept();
    1477            0 :     } else if (viewInteraction()->isEditingElement()) {
    1478            0 :         viewInteraction()->editElement(event);
    1479            0 :         if (key == Qt::Key_Shift) {
    1480            0 :             viewInteraction()->updateTimeTickAnchors(event);
    1481              :         }
    1482            0 :     } else if (key == Qt::Key_Shift) {
    1483            0 :         viewInteraction()->updateTimeTickAnchors(event);
    1484            0 :         updateShadowNotePopupVisibility();
    1485            0 :         return;
    1486            0 :     } else if (isAnchorEditingEvent(event)) {
    1487            0 :         viewInteraction()->moveElementAnchors(event);
    1488              :     }
    1489              : 
    1490            0 :     updateShadowNotePopupVisibility(/*forceHide*/ true);
    1491              : }
    1492              : 
    1493            0 : void NotationViewInputController::keyReleaseEvent(QKeyEvent* event)
    1494              : {
    1495            0 :     if (event->key() != Qt::Key_Shift) {
    1496            0 :         return;
    1497              :     }
    1498              : 
    1499            0 :     viewInteraction()->editElement(event);
    1500            0 :     updateShadowNotePopupVisibility(/*forceHide*/ true);
    1501            0 :     viewInteraction()->updateTimeTickAnchors(event);
    1502              : }
    1503              : 
    1504            0 : void NotationViewInputController::inputMethodEvent(QInputMethodEvent* event)
    1505              : {
    1506            0 :     if (viewInteraction()->isTextEditingStarted()) {
    1507            0 :         viewInteraction()->editText(event);
    1508              :     }
    1509            0 : }
    1510              : 
    1511            0 : bool NotationViewInputController::canHandleInputMethodQuery(Qt::InputMethodQuery query) const
    1512              : {
    1513            0 :     if (!viewInteraction()->isTextEditingStarted()) {
    1514            0 :         return false;
    1515              :     }
    1516              : 
    1517              :     static const QList<Qt::InputMethodQuery> allowedQueries {
    1518              :         Qt::ImCursorRectangle,
    1519              :         Qt::ImEnabled,
    1520              :         Qt::ImHints
    1521            0 :     };
    1522              : 
    1523            0 :     return allowedQueries.contains(query);
    1524              : }
    1525              : 
    1526              : //! NOTE: Copied from ScoreView::inputMethodQuery
    1527            0 : QVariant NotationViewInputController::inputMethodQuery(Qt::InputMethodQuery query) const
    1528              : {
    1529            0 :     if (!viewInteraction()->isTextEditingStarted()) {
    1530            0 :         return QVariant();
    1531              :     }
    1532              : 
    1533            0 :     switch (query) {
    1534            0 :     case Qt::ImCursorRectangle: {
    1535            0 :         const TextBase* editedText = viewInteraction()->editedText();
    1536            0 :         RectF cursorRect = editedText->cursor()->cursorRect().translated(editedText->canvasPos());
    1537              : 
    1538            0 :         QRectF rect = m_view->fromLogical(cursorRect).toQRectF();
    1539            0 :         rect.setWidth(1);     // InputMethod doesn't display properly if width left at 0
    1540            0 :         rect.setHeight(rect.height() + 10);     // add a little margin under the cursor
    1541              : 
    1542            0 :         return rect;
    1543              :     }
    1544            0 :     case Qt::ImEnabled:
    1545            0 :         return true;     // TextBase will always accept input method input
    1546            0 :     case Qt::ImHints:
    1547            0 :         return Qt::ImhNone;     // No hints for now, but maybe in future will give hints
    1548            0 :     default:
    1549            0 :         break;
    1550              :     }
    1551              : 
    1552            0 :     return QVariant();
    1553              : }
    1554              : 
    1555            0 : void NotationViewInputController::dragEnterEvent(QDragEnterEvent* event)
    1556              : {
    1557            0 :     if (m_readonly) {
    1558            0 :         event->ignore();
    1559            0 :         return;
    1560              :     }
    1561              : 
    1562            0 :     const QMimeData* mimeData = dragController()->mimeData(event);
    1563            0 :     IF_ASSERT_FAILED(mimeData) {
    1564            0 :         return;
    1565              :     }
    1566              : 
    1567            0 :     if (mimeData->hasFormat(mimeSymbolFormat)) {
    1568            0 :         event->setDropAction(Qt::CopyAction);
    1569            0 :         if (event->dropAction() != Qt::CopyAction) {
    1570            0 :             event->ignore();
    1571            0 :             return;
    1572              :         }
    1573              : 
    1574            0 :         QByteArray edata = mimeData->data(mimeSymbolFormat);
    1575            0 :         if (viewInteraction()->startDropSingle(edata)) {
    1576            0 :             event->accept();
    1577            0 :             return;
    1578              :         }
    1579              : 
    1580            0 :         event->ignore();
    1581            0 :         return;
    1582            0 :     }
    1583              : 
    1584            0 :     if (mimeData->hasFormat(mimeStaffListFormat)) {
    1585            0 :         bool isInternal = event->source() == m_view->asItem();
    1586            0 :         if (!isInternal || event->modifiers() & Qt::AltModifier) {
    1587            0 :             event->setDropAction(Qt::CopyAction);
    1588            0 :             if (event->dropAction() != Qt::CopyAction) {
    1589            0 :                 event->ignore();
    1590            0 :                 return;
    1591              :             }
    1592              :         } else {
    1593            0 :             event->setDropAction(Qt::MoveAction);
    1594            0 :             if (event->dropAction() != Qt::MoveAction) {
    1595            0 :                 event->ignore();
    1596            0 :                 return;
    1597              :             }
    1598              :         }
    1599              : 
    1600            0 :         QByteArray edata = mimeData->data(mimeStaffListFormat);
    1601            0 :         if (viewInteraction()->startDropRange(edata)) {
    1602            0 :             event->accept();
    1603            0 :             return;
    1604              :         }
    1605              : 
    1606            0 :         event->ignore();
    1607            0 :         return;
    1608            0 :     }
    1609              : 
    1610            0 :     for (const QUrl& url : mimeData->urls()) {
    1611            0 :         if (viewInteraction()->startDropImage(url)) {
    1612            0 :             event->accept();
    1613            0 :             return;
    1614              :         }
    1615            0 :     }
    1616              : 
    1617            0 :     event->ignore();
    1618              : }
    1619              : 
    1620            0 : void NotationViewInputController::dragMoveEvent(QDragMoveEvent* event)
    1621              : {
    1622            0 :     const QMimeData* mimeData = dragController()->mimeData(event);
    1623            0 :     IF_ASSERT_FAILED(mimeData) {
    1624            0 :         return;
    1625              :     }
    1626              : 
    1627            0 :     PointF pos = m_view->toLogical(event->position());
    1628            0 :     Qt::KeyboardModifiers modifiers = event->modifiers();
    1629              : 
    1630            0 :     bool isAccepted = false;
    1631            0 :     if (mimeData->hasFormat(mimeStaffListFormat)) {
    1632            0 :         bool isInternal = event->source() == m_view->asItem();
    1633            0 :         if (!isInternal || modifiers & Qt::AltModifier) {
    1634            0 :             event->setDropAction(Qt::CopyAction);
    1635              :         } else {
    1636            0 :             event->setDropAction(Qt::MoveAction);
    1637              :         }
    1638              : 
    1639            0 :         isAccepted = viewInteraction()->updateDropRange(pos);
    1640              :     } else {
    1641            0 :         event->setDropAction(Qt::CopyAction);
    1642              : 
    1643            0 :         isAccepted = viewInteraction()->updateDropSingle(pos, modifiers);
    1644              :     }
    1645              : 
    1646            0 :     event->setAccepted(isAccepted);
    1647              : 
    1648            0 :     if (!uiConfiguration()->isSystemDragSupported()) {
    1649            0 :         m_lastDragMoveEvent = {
    1650            0 :             event->position(),
    1651            0 :             event->modifiers(),
    1652            0 :             event->dropAction(),
    1653            0 :             event->source()
    1654              :         };
    1655              :     }
    1656              : }
    1657              : 
    1658            0 : void NotationViewInputController::dragLeaveEvent(QDragLeaveEvent*)
    1659              : {
    1660            0 :     if (!uiConfiguration()->isSystemDragSupported()) {
    1661            0 :         dropEvent(m_lastDragMoveEvent);
    1662              :     }
    1663              : 
    1664            0 :     viewInteraction()->endDrop();
    1665            0 :     m_mouseDownInfo.dragAction = MouseDownInfo::Nothing;
    1666            0 : }
    1667              : 
    1668            0 : void NotationViewInputController::dropEvent(QDropEvent* event)
    1669              : {
    1670            0 :     if (!uiConfiguration()->isSystemDragSupported()) {
    1671            0 :         event->setAccepted(false);
    1672            0 :         return;
    1673              :     }
    1674              : 
    1675              :     DragMoveEvent de = {
    1676            0 :         event->position(),
    1677            0 :         event->modifiers(),
    1678            0 :         event->dropAction(),
    1679            0 :         event->source()
    1680            0 :     };
    1681              : 
    1682            0 :     bool isAccepted = dropEvent(de, event->mimeData());
    1683            0 :     event->setAccepted(isAccepted);
    1684              : }
    1685              : 
    1686            0 : bool NotationViewInputController::dropEvent(const DragMoveEvent& event, const QMimeData* mimeData)
    1687              : {
    1688            0 :     if (m_readonly) {
    1689            0 :         return false;
    1690              :     }
    1691              : 
    1692            0 :     if (!mimeData) {
    1693            0 :         mimeData = dragController()->mimeData();
    1694              :     }
    1695              : 
    1696            0 :     IF_ASSERT_FAILED(mimeData) {
    1697            0 :         return false;
    1698              :     }
    1699              : 
    1700            0 :     PointF pos = m_view->toLogical(event.position);
    1701            0 :     Qt::KeyboardModifiers modifiers = event.modifiers;
    1702              : 
    1703            0 :     bool isAccepted = false;
    1704            0 :     if (mimeData->hasFormat(mimeStaffListFormat)) {
    1705            0 :         bool isInternal = event.source == m_view->asItem();
    1706            0 :         bool isMove = isInternal && event.dropAction == Qt::MoveAction;
    1707            0 :         isAccepted = viewInteraction()->dropRange(mimeData->data(mimeStaffListFormat), pos, isMove);
    1708              :     } else {
    1709            0 :         isAccepted = viewInteraction()->dropSingle(pos, modifiers);
    1710              :     }
    1711              : 
    1712            0 :     return isAccepted;
    1713              : }
    1714              : 
    1715            0 : std::vector<int> NotationViewInputController::pitchesBeingDragged() const
    1716              : {
    1717            0 :     std::vector<int> pitches;
    1718            0 :     pitches.reserve(m_notesBeingDragged.size());
    1719              : 
    1720            0 :     for (const EngravingItem* item : m_notesBeingDragged) {
    1721            0 :         pitches.push_back(toNote(item)->pitch());
    1722              :     }
    1723              : 
    1724            0 :     return pitches;
    1725            0 : }
    1726              : 
    1727           12 : float NotationViewInputController::hitWidth() const
    1728              : {
    1729           12 :     return configuration()->selectionProximity() * 0.5 / m_view->currentScaling();
    1730              : }
    1731              : 
    1732            0 : ElementType NotationViewInputController::selectionType() const
    1733              : {
    1734            0 :     ElementType type = ElementType::INVALID;
    1735              : 
    1736            0 :     auto selection = viewInteraction()->selection();
    1737            0 :     if (!selection) {
    1738            0 :         return type;
    1739              :     }
    1740              : 
    1741            0 :     if (auto hitElement = hitElementContext().element) {
    1742            0 :         return hitElement->type();
    1743            0 :     } else if (selection->isRange()) {
    1744            0 :         return ElementType::MEASURE;
    1745            0 :     } else if (auto selectedElement = selection->element()) {
    1746            0 :         return selectedElement->type();
    1747              :     }
    1748              : 
    1749            0 :     return ElementType::PAGE;
    1750            0 : }
    1751              : 
    1752            0 : muse::PointF NotationViewInputController::selectionElementPos() const
    1753              : {
    1754            0 :     auto selection = viewInteraction()->selection();
    1755            0 :     if (!selection) {
    1756            0 :         return muse::PointF();
    1757              :     }
    1758              : 
    1759            0 :     if (auto hitElement = hitElementContext().element) {
    1760            0 :         return hitElement->canvasBoundingRect().center();
    1761            0 :     } else if (selection->isRange()) {
    1762            0 :         auto range = selection->range();
    1763            0 :         return range->boundingArea().front().center();
    1764            0 :     } else if (auto selectedElement = selection->element()) {
    1765            0 :         return selectedElement->canvasBoundingRect().center();
    1766              :     }
    1767              : 
    1768            0 :     return muse::PointF();
    1769            0 : }
    1770              : 
    1771            0 : void NotationViewInputController::togglePopupForItemIfSupports(const EngravingItem* item)
    1772              : {
    1773            0 :     if (!item) {
    1774            0 :         return;
    1775              :     }
    1776              : 
    1777            0 :     ElementType type = item->type();
    1778              : 
    1779            0 :     if (AbstractElementPopupModel::hasElementEditPopup(item)) {
    1780            0 :         m_view->toggleElementPopup(type);
    1781              :     }
    1782              : }
    1783              : 
    1784            0 : void NotationViewInputController::showSearch()
    1785              : {
    1786            0 :     m_view->showSearch();
    1787            0 : }
    1788              : 
    1789            0 : void NotationViewInputController::updateShadowNotePopupVisibility(bool forceHide)
    1790              : {
    1791            0 :     const mu::engraving::ShadowNote* shadowNote = viewInteraction()->shadowNote();
    1792            0 :     if (forceHide || !shadowNote || !AbstractElementPopupModel::hasElementEditPopup(shadowNote)) {
    1793            0 :         m_view->hideElementPopup(ElementType::SHADOW_NOTE);
    1794            0 :         return;
    1795              :     }
    1796              : 
    1797            0 :     m_view->showElementPopup(ElementType::SHADOW_NOTE);
    1798              : }
    1799              : 
    1800            0 : void NotationViewInputController::redrawView()
    1801              : {
    1802            0 :     m_view->scheduleRedraw();
    1803            0 : }
        

Generated by: LCOV version 2.0-1