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