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 : #include "dynamic.h"
23 :
24 : #include "../editing/edithairpin.h"
25 : #include "../editing/textedit.h"
26 : #include "../editing/transaction/transaction.h"
27 : #include "../types/typesconv.h"
28 :
29 : #include "dynamichairpingroup.h"
30 : #include "expression.h"
31 : #include "hairpin.h"
32 : #include "measure.h"
33 : #include "score.h"
34 : #include "segment.h"
35 : #include "staff.h"
36 : #include "system.h"
37 :
38 : #include "log.h"
39 :
40 : using namespace muse::draw;
41 : using namespace mu::engraving;
42 :
43 : namespace mu::engraving {
44 : //-----------------------------------------------------------------------------
45 : // Dyn
46 : // see: http://en.wikipedia.org/wiki/File:Dynamic's_Note_Velocity.svg
47 : //-----------------------------------------------------------------------------
48 :
49 : // variant with ligatures, works for both emmentaler and bravura:
50 : const std::vector<Dyn> Dynamic::DYN_LIST = {
51 : // dynamic:
52 : { DynamicType::OTHER, -1, 0, true, "" },
53 : { DynamicType::PPPPPP, 1, 0, false,
54 : "<sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
55 : { DynamicType::PPPPP, 5, 0, false,
56 : "<sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
57 : { DynamicType::PPPP, 10, 0, false,
58 : "<sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
59 : { DynamicType::PPP, 16, 0, false,
60 : "<sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
61 : { DynamicType::PP, 33, 0, false, "<sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
62 : { DynamicType::P, 49, 0, false, "<sym>dynamicPiano</sym>" },
63 :
64 : { DynamicType::MP, 64, 0, false, "<sym>dynamicMezzo</sym><sym>dynamicPiano</sym>" },
65 : { DynamicType::MF, 80, 0, false, "<sym>dynamicMezzo</sym><sym>dynamicForte</sym>" },
66 :
67 : { DynamicType::F, 96, 0, false, "<sym>dynamicForte</sym>" },
68 : { DynamicType::FF, 112, 0, false, "<sym>dynamicForte</sym><sym>dynamicForte</sym>" },
69 : { DynamicType::FFF, 126, 0, false, "<sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
70 : { DynamicType::FFFF, 127, 0, false,
71 : "<sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
72 : { DynamicType::FFFFF, 127, 0, false,
73 : "<sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
74 : { DynamicType::FFFFFF, 127, 0, false,
75 : "<sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
76 :
77 : { DynamicType::FP, 96, -47, true, "<sym>dynamicForte</sym><sym>dynamicPiano</sym>" },
78 : { DynamicType::PF, 49, 47, true, "<sym>dynamicPiano</sym><sym>dynamicForte</sym>" },
79 :
80 : { DynamicType::SF, 112, -18, true, "<sym>dynamicSforzando</sym><sym>dynamicForte</sym>" },
81 : { DynamicType::SFZ, 112, -18, true, "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicZ</sym>" },
82 : { DynamicType::SFF, 126, -18, true, "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
83 : { DynamicType::SFFZ, 126, -18, true,
84 : "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicZ</sym>" },
85 : { DynamicType::SFFF, 127, -18, true,
86 : "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
87 : { DynamicType::SFFFZ, 127, -18, true,
88 : "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicZ</sym>" },
89 : { DynamicType::SFP, 112, -47, true, "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicPiano</sym>" },
90 : { DynamicType::SFPP, 112, -79, true,
91 : "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
92 :
93 : { DynamicType::RFZ, 112, -18, true, "<sym>dynamicRinforzando</sym><sym>dynamicForte</sym><sym>dynamicZ</sym>" },
94 : { DynamicType::RF, 112, -18, true, "<sym>dynamicRinforzando</sym><sym>dynamicForte</sym>" },
95 : { DynamicType::FZ, 112, -18, true, "<sym>dynamicForte</sym><sym>dynamicZ</sym>" },
96 :
97 : { DynamicType::M, 96, -16, true, "<sym>dynamicMezzo</sym>" },
98 : { DynamicType::R, 112, -18, true, "<sym>dynamicRinforzando</sym>" },
99 : { DynamicType::S, 112, -18, true, "<sym>dynamicSforzando</sym>" },
100 : { DynamicType::Z, 80, 0, true, "<sym>dynamicZ</sym>" },
101 : { DynamicType::N, 49, -48, true, "<sym>dynamicNiente</sym>" }
102 : };
103 :
104 : //---------------------------------------------------------
105 : // dynamicsStyle
106 : //---------------------------------------------------------
107 :
108 : static const ElementStyle dynamicsStyle {
109 : { Sid::dynamicsMinDistance, Pid::MIN_DISTANCE },
110 : { Sid::avoidBarLines, Pid::AVOID_BARLINES },
111 : { Sid::centerOnNotehead, Pid::CENTER_ON_NOTEHEAD },
112 : };
113 :
114 : //---------------------------------------------------------
115 : // Dynamic
116 : //---------------------------------------------------------
117 :
118 884 : Dynamic::Dynamic(DummyParentOr<Segment> parent)
119 884 : : TextBase(ElementType::DYNAMIC, parent, TextStyleType::DYNAMICS, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
120 : {
121 884 : m_velocity = -1;
122 884 : m_dynamicType = DynamicType::OTHER;
123 884 : m_changeInVelocity = 128;
124 884 : m_velChangeSpeed = DynamicSpeed::NORMAL;
125 884 : initElementStyle(&dynamicsStyle);
126 884 : }
127 :
128 93 : Dynamic::Dynamic(const Dynamic& d)
129 93 : : TextBase(d)
130 : {
131 93 : m_dynamicType = d.m_dynamicType;
132 93 : m_velocity = d.m_velocity;
133 93 : m_changeInVelocity = d.m_changeInVelocity;
134 93 : m_velChangeSpeed = d.m_velChangeSpeed;
135 93 : _avoidBarLines = d._avoidBarLines;
136 93 : _centerOnNotehead = d._centerOnNotehead;
137 93 : }
138 :
139 : //---------------------------------------------------------
140 : // velocity
141 : //---------------------------------------------------------
142 :
143 788 : int Dynamic::velocity() const
144 : {
145 788 : return m_velocity <= 0 ? DYN_LIST[int(dynamicType())].velocity : m_velocity;
146 : }
147 :
148 : //---------------------------------------------------------
149 : // changeInVelocity
150 : //---------------------------------------------------------
151 :
152 124 : int Dynamic::changeInVelocity() const
153 : {
154 124 : return m_changeInVelocity >= 128 ? DYN_LIST[int(dynamicType())].changeInVelocity : m_changeInVelocity;
155 : }
156 :
157 : //---------------------------------------------------------
158 : // setChangeInVelocity
159 : //---------------------------------------------------------
160 :
161 0 : void Dynamic::setChangeInVelocity(int val)
162 : {
163 0 : if (DYN_LIST[int(dynamicType())].changeInVelocity == val) {
164 0 : m_changeInVelocity = 128;
165 : } else {
166 0 : m_changeInVelocity = val;
167 : }
168 0 : }
169 :
170 : //---------------------------------------------------------
171 : // velocityChangeLength
172 : // the time over which the velocity change occurs
173 : //---------------------------------------------------------
174 :
175 32 : Fraction Dynamic::velocityChangeLength(BeatsPerSecond tempo) const
176 : {
177 32 : if (changeInVelocity() == 0) {
178 0 : return Fraction::fromTicks(0);
179 : }
180 :
181 32 : double ratio = tempo.val / Constants::DEFAULT_TEMPO.val;
182 : double speedMult;
183 32 : switch (velChangeSpeed()) {
184 0 : case DynamicSpeed::SLOW:
185 0 : speedMult = 1.3;
186 0 : break;
187 0 : case DynamicSpeed::FAST:
188 0 : speedMult = 0.5;
189 0 : break;
190 32 : case DynamicSpeed::NORMAL:
191 : default:
192 32 : speedMult = 0.8;
193 32 : break;
194 : }
195 :
196 32 : return Fraction::fromTicks(int(ratio * (speedMult * double(Constants::DIVISION))));
197 : }
198 :
199 : //---------------------------------------------------------
200 : // isVelocityChangeAvailable
201 : //---------------------------------------------------------
202 :
203 622 : bool Dynamic::isVelocityChangeAvailable() const
204 : {
205 622 : switch (dynamicType()) {
206 48 : case DynamicType::FP:
207 : case DynamicType::SF:
208 : case DynamicType::SFZ:
209 : case DynamicType::SFF:
210 : case DynamicType::SFFZ:
211 : case DynamicType::SFFF:
212 : case DynamicType::SFFFZ:
213 : case DynamicType::SFP:
214 : case DynamicType::SFPP:
215 : case DynamicType::RFZ:
216 : case DynamicType::RF:
217 : case DynamicType::FZ:
218 : case DynamicType::M:
219 : case DynamicType::R:
220 : case DynamicType::S:
221 48 : return true;
222 574 : default:
223 574 : return false;
224 : }
225 : }
226 :
227 : //---------------------------------------------------------
228 : // setDynamicType
229 : //---------------------------------------------------------
230 :
231 874 : void Dynamic::setDynamicType(const String& tag)
232 : {
233 874 : const auto dynamicInfo = parseDynamicText(tag);
234 :
235 874 : if (dynamicInfo.first == DynamicType::OTHER) {
236 32 : LOGD("setDynamicType: other <%s>", muPrintable(tag));
237 : }
238 :
239 874 : setDynamicType(dynamicInfo.first);
240 874 : setXmlText(dynamicInfo.second);
241 874 : }
242 :
243 874 : std::pair<DynamicType, String> Dynamic::parseDynamicText(const String& tag) const
244 : {
245 874 : std::string utf8Tag = tag.toStdString();
246 874 : const std::regex dynamicRegex(R"((?:<sym>.*?</sym>)+|(?:\b)[fmnprsz]+(?:\b(?=[^>]|$)))");
247 874 : auto begin = std::sregex_iterator(utf8Tag.begin(), utf8Tag.end(), dynamicRegex);
248 934 : for (auto it = begin; it != std::sregex_iterator(); ++it) {
249 872 : const std::smatch match = *it;
250 872 : const std::string matchStr = match.str();
251 872 : size_t n = DYN_LIST.size();
252 9056 : for (size_t i = 0; i < n; ++i) {
253 9026 : if (TConv::toXml(DynamicType(i)).ascii() == matchStr || DYN_LIST[i].text == matchStr) {
254 842 : utf8Tag.replace(match.position(0), match.length(0), DYN_LIST[i].text);
255 842 : return { DynamicType(i), String::fromStdString(utf8Tag) };
256 : }
257 : }
258 2588 : }
259 32 : return { DynamicType::OTHER, tag };
260 874 : }
261 :
262 949 : String Dynamic::dynamicText(DynamicType t)
263 : {
264 949 : return String::fromUtf8(DYN_LIST[int(t)].text);
265 : }
266 :
267 0 : Expression* Dynamic::snappedExpression() const
268 : {
269 0 : EngravingItem* item = ldata()->itemSnappedAfter();
270 0 : return item && item->isExpression() ? toExpression(item) : nullptr;
271 : }
272 :
273 1216 : HairpinSegment* Dynamic::findSnapBeforeHairpinAcrossSystemBreak() const
274 : {
275 : /* Normally it is the hairpin which looks for a snappable dynamic. Except if this dynamic
276 : * is on the first beat of next system, in which case it needs to look back for a hairpin. */
277 1216 : Segment* seg = segment();
278 1216 : Measure* measure = seg ? seg->measure() : nullptr;
279 1216 : System* system = measure ? measure->system() : nullptr;
280 1216 : bool isOnFirstBeatOfSystem = system && system->firstMeasure() == measure && seg->rtick().isZero();
281 1216 : if (!isOnFirstBeatOfSystem) {
282 605 : return nullptr;
283 : }
284 :
285 611 : Measure* prevMeasure = measure->prevMeasure();
286 611 : System* prevSystem = prevMeasure ? prevMeasure->system() : nullptr;
287 611 : if (!prevSystem) {
288 595 : return nullptr;
289 : }
290 :
291 44 : for (SpannerSegment* spannerSeg : prevSystem->spannerSegments()) {
292 28 : if (!spannerSeg->isHairpinSegment() || spannerSeg->track() != track() || spannerSeg->spanner()->tick2() != tick()) {
293 28 : continue;
294 : }
295 0 : HairpinSegment* hairpinSeg = toHairpinSegment(spannerSeg);
296 0 : if (hairpinSeg->findElementToSnapAfter() == this) {
297 0 : return hairpinSeg;
298 : }
299 : }
300 :
301 16 : return nullptr;
302 : }
303 :
304 0 : bool Dynamic::acceptDrop(EditData& ed) const
305 : {
306 0 : ElementType droppedType = ed.dropElement->type();
307 0 : return droppedType == ElementType::DYNAMIC || droppedType == ElementType::EXPRESSION || droppedType == ElementType::HAIRPIN;
308 : }
309 :
310 0 : EngravingItem* Dynamic::drop(Transaction& tx, EditData& ed)
311 : {
312 0 : EngravingItem* item = ed.dropElement;
313 :
314 0 : if (item->isHairpin()) {
315 0 : EditHairpin::addHairpinToDynamic(tx, score(), toHairpin(item), this);
316 0 : return item;
317 : }
318 :
319 0 : if (item->isDynamic()) {
320 0 : Dynamic* dynamic = toDynamic(item);
321 0 : undoChangeProperty(Pid::DYNAMIC_TYPE, dynamic->dynamicType());
322 0 : undoChangeProperty(Pid::TEXT, dynamic->xmlText());
323 0 : delete dynamic;
324 0 : ed.dropElement = this;
325 0 : return this;
326 : }
327 :
328 0 : if (item->isExpression()) {
329 0 : item->setTrack(track());
330 0 : item->setOwnershipParent(segment());
331 0 : toExpression(item)->setVoiceAssignment(voiceAssignment());
332 0 : score()->undoAddElement(item);
333 0 : return item;
334 : }
335 :
336 0 : return nullptr;
337 : }
338 :
339 14 : int Dynamic::dynamicVelocity(DynamicType t)
340 : {
341 14 : return DYN_LIST[int(t)].velocity;
342 : }
343 :
344 3 : void Dynamic::startEdit(EditData& ed)
345 : {
346 3 : if (ed.curGrip != Grip::NO_GRIP) {
347 0 : EngravingItem::startEdit(ed);
348 : } else {
349 3 : TextBase::startEdit(ed);
350 : }
351 3 : }
352 :
353 3 : void Dynamic::endEdit(EditData& ed)
354 : {
355 3 : if (cursor() && cursor()->editing()) {
356 3 : TextBase::endEdit(ed);
357 : } else {
358 0 : EngravingItem::endEdit(ed);
359 : }
360 3 : }
361 :
362 0 : TranslatableString Dynamic::subtypeUserName() const
363 : {
364 0 : if (dynamicType() == DynamicType::OTHER) {
365 0 : String s = plainText().simplified();
366 0 : if (s.size() > 20) {
367 0 : s.truncate(20);
368 0 : s += u"…";
369 : }
370 0 : return TranslatableString::untranslatable(s);
371 0 : } else {
372 0 : return TConv::userName(dynamicType());
373 : }
374 : }
375 :
376 0 : void Dynamic::dragGrip(EditData& ed)
377 : {
378 0 : const bool hasLeftGrip = this->hasLeftGrip();
379 0 : const bool hasRightGrip = this->hasRightGrip();
380 :
381 : // Right grip (when two grips/when single grip)
382 0 : if ((int(ed.curGrip) == 1 && hasLeftGrip && hasRightGrip) || (int(ed.curGrip) == 0 && !hasLeftGrip && hasRightGrip)) {
383 0 : m_rightDragOffset += ed.evtDelta.x();
384 0 : if (m_rightDragOffset < 0) {
385 0 : m_rightDragOffset = 0;
386 : }
387 0 : return;
388 : }
389 :
390 : // Left grip (when two grips or single grip)
391 0 : if (int(ed.curGrip) == 0 && hasLeftGrip) {
392 0 : m_leftDragOffset += ed.evtDelta.x();
393 0 : if (m_leftDragOffset > 0) {
394 0 : m_leftDragOffset = 0;
395 : }
396 0 : return;
397 : }
398 :
399 0 : UNREACHABLE;
400 : }
401 :
402 0 : void Dynamic::endDragGrip(EditData& ed)
403 : {
404 0 : m_leftDragOffset = m_rightDragOffset = 0.0;
405 :
406 0 : TextBase::endDragGrip(ed);
407 0 : }
408 :
409 : //---------------------------------------------------------
410 : // reset
411 : //---------------------------------------------------------
412 :
413 0 : void Dynamic::reset()
414 : {
415 0 : undoResetProperty(Pid::DIRECTION);
416 0 : undoResetProperty(Pid::CENTER_BETWEEN_STAVES);
417 0 : TextBase::reset();
418 0 : Expression* snappedExp = snappedExpression();
419 0 : if (snappedExp && !snappedExp->offset().isNull()) {
420 0 : snappedExp->reset();
421 : }
422 0 : }
423 :
424 : //---------------------------------------------------------
425 : // getDragGroup
426 : //---------------------------------------------------------
427 :
428 0 : std::unique_ptr<ElementGroup> Dynamic::getDragGroup(std::function<bool(const EngravingItem*)> isDragged)
429 : {
430 0 : if (auto g = HairpinWithDynamicsDragGroup::detectFor(this, isDragged)) {
431 0 : return g;
432 0 : }
433 0 : if (auto g = DynamicNearHairpinsDragGroup::detectFor(this, isDragged)) {
434 0 : return g;
435 0 : }
436 0 : if (auto g = DynamicExpressionDragGroup::detectFor(this, isDragged)) {
437 0 : return g;
438 0 : }
439 0 : return TextBase::getDragGroup(isDragged);
440 : }
441 :
442 : //---------------------------------------------------------
443 : // getProperty
444 : //---------------------------------------------------------
445 :
446 18439 : PropertyValue Dynamic::getProperty(Pid propertyId) const
447 : {
448 18439 : switch (propertyId) {
449 599 : case Pid::DYNAMIC_TYPE:
450 599 : return m_dynamicType;
451 590 : case Pid::VELOCITY:
452 590 : return velocity();
453 18 : case Pid::VELO_CHANGE:
454 18 : if (isVelocityChangeAvailable()) {
455 16 : return changeInVelocity();
456 : } else {
457 2 : return PropertyValue();
458 : }
459 18 : case Pid::VELO_CHANGE_SPEED:
460 18 : return m_velChangeSpeed;
461 8 : case Pid::AVOID_BARLINES:
462 8 : return avoidBarLines();
463 4 : case Pid::CENTER_ON_NOTEHEAD:
464 4 : return _centerOnNotehead;
465 588 : case Pid::PLAY:
466 588 : return playDynamic();
467 617 : case Pid::ANCHOR_TO_END_OF_PREVIOUS:
468 617 : return anchorToEndOfPrevious();
469 15997 : default:
470 15997 : return TextBase::getProperty(propertyId);
471 : }
472 : }
473 :
474 : //---------------------------------------------------------
475 : // setProperty
476 : //---------------------------------------------------------
477 :
478 17758 : bool Dynamic::setProperty(Pid propertyId, const PropertyValue& v)
479 : {
480 17758 : switch (propertyId) {
481 3 : case Pid::DYNAMIC_TYPE:
482 3 : if (v.type() == P_TYPE::DYNAMIC_TYPE) {
483 0 : setDynamicType(v.value<DynamicType>());
484 0 : break;
485 : }
486 3 : setDynamicType(v.value<String>());
487 3 : break;
488 2 : case Pid::VELOCITY:
489 2 : m_velocity = v.toInt();
490 2 : break;
491 0 : case Pid::VELO_CHANGE:
492 0 : if (isVelocityChangeAvailable()) {
493 0 : setChangeInVelocity(v.toInt());
494 : }
495 0 : break;
496 0 : case Pid::VELO_CHANGE_SPEED:
497 0 : m_velChangeSpeed = v.value<DynamicSpeed>();
498 0 : break;
499 929 : case Pid::AVOID_BARLINES:
500 929 : setAvoidBarLines(v.toBool());
501 929 : break;
502 923 : case Pid::CENTER_ON_NOTEHEAD:
503 923 : _centerOnNotehead = v.toBool();
504 923 : break;
505 0 : case Pid::PLAY:
506 0 : setPlayDynamic(v.toBool());
507 0 : break;
508 1 : case Pid::ANCHOR_TO_END_OF_PREVIOUS:
509 1 : setAnchorToEndOfPrevious(v.toBool());
510 1 : break;
511 15900 : default:
512 15900 : if (!TextBase::setProperty(propertyId, v)) {
513 0 : return false;
514 : }
515 15900 : break;
516 : }
517 17758 : triggerLayout();
518 17758 : return true;
519 : }
520 :
521 : //---------------------------------------------------------
522 : // propertyDefault
523 : //---------------------------------------------------------
524 :
525 9632 : PropertyValue Dynamic::propertyDefault(Pid id) const
526 : {
527 9632 : switch (id) {
528 588 : case Pid::TEXT_STYLE:
529 588 : return TextStyleType::DYNAMICS;
530 588 : case Pid::VELOCITY:
531 588 : return -1;
532 16 : case Pid::VELO_CHANGE:
533 16 : if (isVelocityChangeAvailable()) {
534 16 : return DYN_LIST[int(dynamicType())].changeInVelocity;
535 : } else {
536 0 : return PropertyValue();
537 : }
538 16 : case Pid::VELO_CHANGE_SPEED:
539 16 : return DynamicSpeed::NORMAL;
540 588 : case Pid::PLAY:
541 588 : return true;
542 588 : case Pid::ANCHOR_TO_END_OF_PREVIOUS:
543 588 : return false;
544 7248 : default:
545 7248 : return TextBase::propertyDefault(id);
546 : }
547 : }
548 :
549 7970 : Sid Dynamic::getPropertyStyle(Pid pid) const
550 : {
551 7970 : switch (pid) {
552 0 : case Pid::PLACEMENT:
553 0 : return Sid::dynamicsPlacement;
554 7970 : default:
555 7970 : return TextBase::getPropertyStyle(pid);
556 : }
557 : }
558 :
559 : //---------------------------------------------------------
560 : // accessibleInfo
561 : //---------------------------------------------------------
562 :
563 0 : String Dynamic::accessibleInfo() const
564 : {
565 0 : return String(u"%1: %2").arg(EngravingItem::accessibleInfo(), translatedSubtypeUserName());
566 : }
567 :
568 : //---------------------------------------------------------
569 : // screenReaderInfo
570 : //---------------------------------------------------------
571 :
572 0 : String Dynamic::screenReaderInfo() const
573 : {
574 0 : String s;
575 :
576 0 : if (dynamicType() == DynamicType::OTHER) {
577 0 : s = plainText().simplified();
578 : } else {
579 0 : s = TConv::translatedUserName(dynamicType());
580 : }
581 0 : return String(u"%1: %2").arg(EngravingItem::accessibleInfo(), s);
582 0 : }
583 : }
584 :
585 1 : bool Dynamic::isEditAllowed(EditData& ed) const
586 : {
587 1 : if (!dynamic_cast<TextEditData*>(ed.getData(this).get())) {
588 0 : return EngravingItem::isEditAllowed(ed);
589 : }
590 :
591 1 : return TextBase::isEditAllowed(ed);
592 : }
593 :
594 : //---------------------------------------------------------
595 : // hasLeftHairpin
596 : //---------------------------------------------------------
597 :
598 0 : bool Dynamic::hasLeftGrip() const
599 : {
600 0 : if (segment()->tick().isZero()) {
601 0 : return false; // Don't show the left grip for the leftmost dynamic with tick zero
602 : }
603 0 : return m_leftHairpin == nullptr;
604 : }
605 :
606 : //---------------------------------------------------------
607 : // hasRightHairpin
608 : //---------------------------------------------------------
609 :
610 0 : bool Dynamic::hasRightGrip() const
611 : {
612 0 : return m_rightHairpin == nullptr;
613 : }
614 :
615 : //---------------------------------------------------------
616 : // findAdjacentHairpins
617 : //---------------------------------------------------------
618 :
619 0 : void Dynamic::findAdjacentHairpins()
620 : {
621 0 : m_leftHairpin = nullptr;
622 0 : m_rightHairpin = nullptr;
623 :
624 0 : const Fraction tick = segment()->tick();
625 0 : const int intTick = tick.ticks();
626 :
627 0 : const auto& spanners = score()->spannerMap().findOverlapping(intTick - 1, intTick + 1);
628 0 : for (auto i : spanners) {
629 0 : Spanner* sp = i.value;
630 0 : if (sp->track() == track() && sp->isHairpin()) {
631 0 : Hairpin* hp = toHairpin(sp);
632 0 : if (hp->tick() == tick) {
633 0 : m_rightHairpin = hp;
634 0 : } else if (hp->tick2() == tick) {
635 0 : m_leftHairpin = hp;
636 : }
637 : }
638 : }
639 0 : }
640 :
641 1262 : Shape Dynamic::symShapeWithCutouts(SymId id) const
642 : {
643 1262 : Staff* stf = staff();
644 1262 : double staffMag = stf ? stf->staffMag(tick()) : 1.0;
645 1262 : Shape shape = score()->engravingFont()->shapeWithCutouts(id, magS() * staffMag * symbolScale());
646 4865 : for (ShapeElement& element : shape.elements()) {
647 3603 : element.setItem(this);
648 : }
649 :
650 1262 : return shape;
651 : }
652 :
653 1125 : Dyn Dynamic::dynInfo(DynamicType type)
654 : {
655 1125 : return DYN_LIST[static_cast<int>(type)];
656 : }
657 :
658 : //---------------------------------------------------------
659 : // gripsCount
660 : //---------------------------------------------------------
661 :
662 0 : int Dynamic::gripsCount() const
663 : {
664 0 : if (empty()) {
665 0 : return 0;
666 : }
667 :
668 0 : const bool hasLeftGrip = this->hasLeftGrip();
669 0 : const bool hasRightGrip = this->hasRightGrip();
670 :
671 0 : if (hasLeftGrip && hasRightGrip) {
672 0 : return 2;
673 0 : } else if (hasLeftGrip || hasRightGrip) {
674 0 : return 1;
675 : } else {
676 0 : return 0;
677 : }
678 : }
679 :
680 : //---------------------------------------------------------
681 : // gripsPositions
682 : //---------------------------------------------------------
683 :
684 0 : std::vector<PointF> Dynamic::gripsPositions(const EditData&) const
685 : {
686 0 : const RectF bbox = ldata()->bbox();
687 0 : const PointF pagePos = this->pagePos();
688 0 : const double horizontalPadding = absoluteFromSpatium(score()->style().styleS(Sid::hairpinMinDistance));
689 :
690 : // Based on rendering::score::AlignmentLayout::yOpticalCenter
691 0 : const double yOpticalCenter = [&] {
692 0 : switch (align().vertical) {
693 0 : case AlignV::TOP:
694 0 : return 0.5 * bbox.height();
695 0 : case AlignV::VCENTER:
696 0 : return 0.0;
697 0 : case AlignV::BOTTOM:
698 0 : return -0.5 * bbox.height();
699 0 : case AlignV::BASELINE:
700 0 : return -0.46 * spatium() * symbolScale(); // approximated half x-height of dynamic
701 : }
702 0 : return 0.0;
703 0 : }();
704 :
705 0 : PointF leftOffset(bbox.left() - horizontalPadding + m_leftDragOffset, yOpticalCenter);
706 0 : PointF rightOffset(bbox.right() + horizontalPadding + m_rightDragOffset, yOpticalCenter);
707 :
708 0 : const bool hasLeftGrip = this->hasLeftGrip();
709 0 : const bool hasRightGrip = this->hasRightGrip();
710 :
711 0 : if (hasLeftGrip && hasRightGrip) {
712 0 : return { pagePos + leftOffset, pagePos + rightOffset };
713 0 : } else if (hasLeftGrip) {
714 0 : return { pagePos + leftOffset };
715 0 : } else if (hasRightGrip) {
716 0 : return { pagePos + rightOffset };
717 : } else {
718 0 : return {};
719 : }
720 : }
|