LCOV - code coverage report
Current view: top level - src/notationscene/qml/MuseScore/NotationScene - abstractelementpopupmodel.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 2.6 % 117 3
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) 2022 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 "abstractelementpopupmodel.h"
      24              : 
      25              : #include "engraving/dom/property.h"
      26              : #include "engraving/dom/score.h"
      27              : 
      28              : #include "notation/inotation.h"
      29              : #include "notation/inotationinteraction.h"
      30              : #include "notation/inotationselection.h"
      31              : #include "notation/inotationundostack.h"
      32              : #include "notation/inotationviewstate.h"
      33              : 
      34              : #include "elementpopups/partialtiepopupmodel.h"
      35              : #include "elementpopups/shadownotepopupmodel.h"
      36              : 
      37              : #include "log.h"
      38              : 
      39              : using namespace mu::notation;
      40              : 
      41              : static const QMap<mu::engraving::ElementType, PopupModelType> ELEMENT_POPUP_TYPES = {
      42              :     { mu::engraving::ElementType::HARP_DIAGRAM, PopupModelType::TYPE_HARP_DIAGRAM },
      43              :     { mu::engraving::ElementType::CAPO, PopupModelType::TYPE_CAPO },
      44              :     { mu::engraving::ElementType::STRING_TUNINGS, PopupModelType::TYPE_STRING_TUNINGS },
      45              :     { mu::engraving::ElementType::SOUND_FLAG, PopupModelType::TYPE_SOUND_FLAG },
      46              :     { mu::engraving::ElementType::STAFF_VISIBILITY_INDICATOR, PopupModelType::TYPE_STAFF_VISIBILITY },
      47              :     { mu::engraving::ElementType::DYNAMIC, PopupModelType::TYPE_DYNAMIC },
      48              :     { mu::engraving::ElementType::TEXT, PopupModelType::TYPE_TEXT },
      49              :     { mu::engraving::ElementType::STAFF_TEXT, PopupModelType::TYPE_TEXT },
      50              :     { mu::engraving::ElementType::SYSTEM_TEXT, PopupModelType::TYPE_TEXT },
      51              :     { mu::engraving::ElementType::EXPRESSION, PopupModelType::TYPE_TEXT },
      52              :     { mu::engraving::ElementType::PLAYTECH_ANNOTATION, PopupModelType::TYPE_TEXT },
      53              :     { mu::engraving::ElementType::REHEARSAL_MARK, PopupModelType::TYPE_TEXT },
      54              :     { mu::engraving::ElementType::INSTRUMENT_CHANGE, PopupModelType::TYPE_TEXT },
      55              :     { mu::engraving::ElementType::FINGERING, PopupModelType::TYPE_TEXT },
      56              :     { mu::engraving::ElementType::STICKING, PopupModelType::TYPE_TEXT },
      57              :     { mu::engraving::ElementType::LYRICS, PopupModelType::TYPE_TEXT },
      58              :     { mu::engraving::ElementType::FIGURED_BASS, PopupModelType::TYPE_TEXT },
      59              :     { mu::engraving::ElementType::TEMPO_TEXT, PopupModelType::TYPE_TEXT },
      60              :     { mu::engraving::ElementType::PLAY_COUNT_TEXT, PopupModelType::TYPE_TEXT },
      61              :     { mu::engraving::ElementType::TIE_SEGMENT, PopupModelType::TYPE_PARTIAL_TIE },
      62              :     { mu::engraving::ElementType::PARTIAL_TIE_SEGMENT, PopupModelType::TYPE_PARTIAL_TIE },
      63              :     { mu::engraving::ElementType::SHADOW_NOTE, PopupModelType::TYPE_SHADOW_NOTE },
      64              : };
      65              : 
      66              : static const QHash<PopupModelType, mu::engraving::ElementTypeSet> POPUP_DEPENDENT_ELEMENT_TYPES = {
      67              :     { PopupModelType::TYPE_HARP_DIAGRAM, { mu::engraving::ElementType::HARP_DIAGRAM } },
      68              :     { PopupModelType::TYPE_CAPO, { mu::engraving::ElementType::CAPO } },
      69              :     { PopupModelType::TYPE_STRING_TUNINGS, { mu::engraving::ElementType::STRING_TUNINGS } },
      70              :     { PopupModelType::TYPE_SOUND_FLAG, { mu::engraving::ElementType::SOUND_FLAG, mu::engraving::ElementType::STAFF_TEXT } },
      71              :     { PopupModelType::TYPE_STAFF_VISIBILITY, { mu::engraving::ElementType::STAFF_VISIBILITY_INDICATOR } },
      72              :     { PopupModelType::TYPE_DYNAMIC, { mu::engraving::ElementType::DYNAMIC } },
      73              :     { PopupModelType::TYPE_TEXT,
      74              :       { mu::engraving::ElementType::TEXT,
      75              :         mu::engraving::ElementType::STAFF_TEXT,
      76              :         mu::engraving::ElementType::SYSTEM_TEXT,
      77              :         mu::engraving::ElementType::EXPRESSION,
      78              :         mu::engraving::ElementType::PLAYTECH_ANNOTATION,
      79              :         mu::engraving::ElementType::REHEARSAL_MARK,
      80              :         mu::engraving::ElementType::INSTRUMENT_CHANGE,
      81              :         mu::engraving::ElementType::FINGERING,
      82              :         mu::engraving::ElementType::STICKING,
      83              :         mu::engraving::ElementType::LYRICS,
      84              :         mu::engraving::ElementType::FIGURED_BASS,
      85              :         mu::engraving::ElementType::TEMPO_TEXT,
      86              :         mu::engraving::ElementType::PLAY_COUNT_TEXT } },
      87              :     { PopupModelType::TYPE_PARTIAL_TIE, { mu::engraving::ElementType::PARTIAL_TIE_SEGMENT, mu::engraving::ElementType::TIE_SEGMENT } },
      88              : };
      89              : 
      90            0 : AbstractElementPopupModel::AbstractElementPopupModel(PopupModelType modelType, QObject* parent)
      91            0 :     : QObject(parent), muse::Contextable(muse::iocCtxForQmlObject(this)), m_modelType(modelType)
      92              : {
      93            0 : }
      94              : 
      95            0 : PopupModelType AbstractElementPopupModel::modelType() const
      96              : {
      97            0 :     return m_modelType;
      98              : }
      99              : 
     100            0 : QRect AbstractElementPopupModel::itemRect() const
     101              : {
     102            0 :     return m_itemRect;
     103              : }
     104              : 
     105            0 : bool AbstractElementPopupModel::hasElementEditPopup(const EngravingItem* element)
     106              : {
     107            0 :     if (!element) {
     108            0 :         return false;
     109              :     }
     110              : 
     111            0 :     const PopupModelType modelType = modelTypeFromElement(element->type());
     112            0 :     if (modelType == PopupModelType::TYPE_UNDEFINED) {
     113            0 :         return false;
     114              :     }
     115              : 
     116            0 :     if (modelType == PopupModelType::TYPE_TEXT) {
     117              :         // Text style popup is only opened when making a selection during text editing
     118            0 :         return false;
     119              :     }
     120              : 
     121            0 :     switch (modelType) {
     122            0 :     case PopupModelType::TYPE_PARTIAL_TIE:
     123            0 :         return PartialTiePopupModel::canOpen(element);
     124            0 :     case PopupModelType::TYPE_SHADOW_NOTE:
     125            0 :         return ShadowNotePopupModel::canOpen(element);
     126            0 :     default:
     127            0 :         return true;
     128              :     }
     129              : }
     130              : 
     131            1 : bool AbstractElementPopupModel::hasTextStylePopup(const EngravingItem* element)
     132              : {
     133            1 :     if (!element) {
     134            1 :         return false;
     135              :     }
     136              : 
     137            0 :     const PopupModelType modelType = modelTypeFromElement(element->type());
     138            0 :     return modelType == PopupModelType::TYPE_TEXT;
     139              : }
     140              : 
     141            0 : PopupModelType AbstractElementPopupModel::modelTypeFromElement(const engraving::ElementType& elementType)
     142              : {
     143            0 :     return ELEMENT_POPUP_TYPES.value(elementType, PopupModelType::TYPE_UNDEFINED);
     144              : }
     145              : 
     146            0 : muse::PointF AbstractElementPopupModel::fromLogical(muse::PointF point) const
     147              : {
     148            0 :     return currentNotation() ? currentNotation()->viewState()->matrix().map(point) : muse::PointF();
     149              : }
     150              : 
     151            0 : muse::RectF AbstractElementPopupModel::fromLogical(muse::RectF rect) const
     152              : {
     153            0 :     return currentNotation() ? currentNotation()->viewState()->matrix().map(rect) : muse::RectF();
     154              : }
     155              : 
     156            0 : INotationUndoStackPtr AbstractElementPopupModel::undoStack() const
     157              : {
     158            0 :     return currentNotation() ? currentNotation()->undoStack() : nullptr;
     159              : }
     160              : 
     161            0 : void AbstractElementPopupModel::beginCommand(const muse::TranslatableString& actionName)
     162              : {
     163            0 :     if (undoStack()) {
     164            0 :         undoStack()->prepareChanges(actionName);
     165              :     }
     166            0 : }
     167              : 
     168            0 : void AbstractElementPopupModel::beginMultiCommands(const muse::TranslatableString& actionName)
     169              : {
     170            0 :     beginCommand(actionName);
     171              : 
     172            0 :     if (undoStack()) {
     173            0 :         undoStack()->lock();
     174              :     }
     175            0 : }
     176              : 
     177            0 : void AbstractElementPopupModel::endCommand()
     178              : {
     179            0 :     if (undoStack()) {
     180            0 :         undoStack()->commitChanges();
     181              :     }
     182            0 : }
     183              : 
     184            0 : void AbstractElementPopupModel::endMultiCommands()
     185              : {
     186            0 :     if (undoStack()) {
     187            0 :         undoStack()->unlock();
     188              :     }
     189              : 
     190            0 :     endCommand();
     191            0 : }
     192              : 
     193            0 : void AbstractElementPopupModel::updateNotation()
     194              : {
     195            0 :     if (!currentNotation()) {
     196            0 :         return;
     197              :     }
     198              : 
     199            0 :     currentNotation()->notationChanged().send(muse::RectF());
     200              : }
     201              : 
     202            0 : INotationPtr AbstractElementPopupModel::currentNotation() const
     203              : {
     204            0 :     return projectSession()->currentNotation();
     205              : }
     206              : 
     207            0 : void AbstractElementPopupModel::changeItemProperty(mu::engraving::Pid id, const engraving::PropertyValue& value)
     208              : {
     209            0 :     IF_ASSERT_FAILED(m_item) {
     210            0 :         return;
     211              :     }
     212              : 
     213            0 :     mu::engraving::PropertyFlags flags = m_item->propertyFlags(id);
     214            0 :     if (flags == mu::engraving::PropertyFlags::STYLED) {
     215            0 :         flags = mu::engraving::PropertyFlags::UNSTYLED;
     216              :     }
     217              : 
     218            0 :     beginCommand(muse::TranslatableString("undoableAction", "Edit %1").arg(mu::engraving::propertyUserName(id)));
     219            0 :     m_item->undoChangeProperty(id, value, flags);
     220            0 :     endCommand();
     221            0 :     updateNotation();
     222              : }
     223              : 
     224            0 : void AbstractElementPopupModel::changeItemProperty(mu::engraving::Pid id, const engraving::PropertyValue& value,
     225              :                                                    mu::engraving::PropertyFlags flags)
     226              : {
     227            0 :     IF_ASSERT_FAILED(m_item) {
     228            0 :         return;
     229              :     }
     230              : 
     231            0 :     beginCommand(muse::TranslatableString("undoableAction", "Edit %1").arg(mu::engraving::propertyUserName(id)));
     232            0 :     m_item->undoChangeProperty(id, value, flags);
     233            0 :     endCommand();
     234            0 :     updateNotation();
     235              : }
     236              : 
     237            0 : INotationInteractionPtr AbstractElementPopupModel::interaction() const
     238              : {
     239            0 :     INotationPtr notation = projectSession()->currentNotation();
     240            0 :     return notation ? notation->interaction() : nullptr;
     241            0 : }
     242              : 
     243            0 : INotationSelectionPtr AbstractElementPopupModel::selection() const
     244              : {
     245            0 :     INotationInteractionPtr interaction = this->interaction();
     246            0 :     return interaction ? interaction->selection() : nullptr;
     247            0 : }
     248              : 
     249            0 : void AbstractElementPopupModel::init()
     250              : {
     251            0 :     m_item = nullptr;
     252              : 
     253            0 :     INotationSelectionPtr selection = this->selection();
     254            0 :     if (!selection) {
     255            0 :         return;
     256              :     }
     257              : 
     258            0 :     INotationUndoStackPtr undoStack = this->undoStack();
     259            0 :     if (!undoStack) {
     260            0 :         return;
     261              :     }
     262              : 
     263            0 :     m_item = selection->element();
     264              : 
     265            0 :     undoStack->changesChannel().onReceive(this, [this] (const engraving::ScoreChanges& changes) {
     266            0 :         if (ignoreTextEditingChanges() && changes.isTextEditing) {
     267            0 :             return;
     268              :         }
     269              : 
     270            0 :         for (ElementType type : dependentElementTypes()) {
     271            0 :             if (muse::contains(changes.changedTypes, type)) {
     272            0 :                 emit dataChanged();
     273            0 :                 updateItemRect();
     274            0 :                 return;
     275              :             }
     276              :         }
     277              :     });
     278              : 
     279            0 :     updateItemRect();
     280            0 : }
     281              : 
     282            0 : mu::engraving::ElementType AbstractElementPopupModel::elementType() const
     283              : {
     284            0 :     return ELEMENT_POPUP_TYPES.key(m_modelType, ElementType::INVALID);
     285              : }
     286              : 
     287            0 : const mu::engraving::ElementTypeSet& AbstractElementPopupModel::dependentElementTypes() const
     288              : {
     289            0 :     auto it = POPUP_DEPENDENT_ELEMENT_TYPES.find(m_modelType);
     290            0 :     if (it != POPUP_DEPENDENT_ELEMENT_TYPES.end()) {
     291            0 :         return it.value();
     292              :     }
     293              : 
     294            0 :     static const engraving::ElementTypeSet dummy;
     295            0 :     return dummy;
     296              : }
     297              : 
     298            0 : void AbstractElementPopupModel::updateItemRect()
     299              : {
     300            0 :     const QRect rect = m_item ? fromLogical(m_item->canvasBoundingRect()).toQRect() : QRect();
     301              : 
     302            0 :     if (m_itemRect != rect) {
     303            0 :         m_itemRect = rect;
     304            0 :         emit itemRectChanged(rect);
     305              :     }
     306            0 : }
        

Generated by: LCOV version 2.0-1