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 "lyrics.h"
24 :
25 : #include "../editing/navigation.h"
26 :
27 : #include "chord.h"
28 : #include "chordrest.h"
29 : #include "factory.h"
30 : #include "measure.h"
31 : #include "note.h"
32 : #include "score.h"
33 : #include "segment.h"
34 : #include "system.h"
35 : #include "utils.h"
36 :
37 : using namespace mu;
38 :
39 : namespace mu::engraving {
40 : //---------------------------------------------------------
41 : // LyricsLine
42 : //---------------------------------------------------------
43 :
44 : static const ElementStyle lyricsLineElementStyle {
45 : { Sid::lyricsDashLineThickness, Pid::LINE_WIDTH }
46 : };
47 :
48 247 : LyricsLine::LyricsLine(DummyParentOr<EngravingItem> parent)
49 247 : : SLine(ElementType::LYRICSLINE, parent)
50 : {
51 247 : setDiagonal(false);
52 247 : initElementStyle(&lyricsLineElementStyle);
53 247 : m_nextLyrics = 0;
54 247 : }
55 :
56 11 : LyricsLine::LyricsLine(const ElementType& type, DummyParentOr<EngravingItem> parent, ElementFlags f)
57 11 : : SLine(type, parent, f)
58 : {
59 11 : setDiagonal(false);
60 11 : initElementStyle(&lyricsLineElementStyle);
61 11 : m_nextLyrics = 0;
62 11 : }
63 :
64 0 : LyricsLine::LyricsLine(const LyricsLine& g)
65 0 : : SLine(g)
66 : {
67 0 : m_nextLyrics = 0;
68 0 : }
69 :
70 : //---------------------------------------------------------
71 : // createLineSegment
72 : //---------------------------------------------------------
73 :
74 250 : LineSegment* LyricsLine::createLineSegment()
75 : {
76 250 : LyricsLineSegment* seg = new LyricsLineSegment(this);
77 250 : seg->setTrack(track());
78 250 : seg->setColor(color());
79 250 : return seg;
80 : }
81 :
82 : //---------------------------------------------------------
83 : // removeUnmanaged
84 : // same as Spanner::removeUnmanaged(), but in addition, remove from hosting Lyrics
85 : //---------------------------------------------------------
86 :
87 235 : void LyricsLine::removeUnmanaged()
88 : {
89 235 : Spanner::removeUnmanaged();
90 235 : if (lyrics()) {
91 235 : lyrics()->remove(this);
92 : }
93 235 : }
94 :
95 : //---------------------------------------------------------
96 : // setProperty
97 : //---------------------------------------------------------
98 :
99 625 : bool LyricsLine::setProperty(Pid propertyId, const engraving::PropertyValue& v)
100 : {
101 625 : switch (propertyId) {
102 2 : case Pid::SPANNER_TICKS:
103 : {
104 : // if parent lyrics has a melisma, change its length too
105 4 : if (ownershipParent() && ownershipParent()->isLyrics()
106 4 : && isEndMelisma()) {
107 2 : Fraction newTicks = toLyrics(ownershipParent())->ticks() + v.value<Fraction>() - ticks();
108 2 : ownershipParent()->undoChangeProperty(Pid::LYRIC_TICKS, newTicks);
109 : }
110 2 : setTicks(v.value<Fraction>());
111 : }
112 2 : break;
113 623 : default:
114 623 : if (!SLine::setProperty(propertyId, v)) {
115 0 : return false;
116 : }
117 623 : break;
118 : }
119 625 : triggerLayout();
120 625 : return true;
121 : }
122 :
123 105 : PropertyValue LyricsLine::propertyDefault(Pid id) const
124 : {
125 105 : switch (id) {
126 4 : case Pid::LINE_WIDTH:
127 4 : return styleValue(Pid::LINE_WIDTH, getPropertyStyle(Pid::LINE_WIDTH));
128 101 : default:
129 101 : return SLine::propertyDefault(id);
130 : }
131 : }
132 :
133 607 : Sid LyricsLine::getPropertyStyle(Pid propertyId) const
134 : {
135 607 : if (propertyId == Pid::LINE_WIDTH) {
136 605 : return isEndMelisma() ? Sid::lyricsLineThickness : Sid::lyricsDashLineThickness;
137 : }
138 :
139 2 : return SLine::getPropertyStyle(propertyId);
140 : }
141 :
142 336 : void LyricsLine::doComputeEndElement()
143 : {
144 336 : if (!isEndMelisma()) {
145 232 : Spanner::doComputeEndElement();
146 232 : return;
147 : }
148 :
149 104 : setEndElement(score()->findChordRestEndingBeforeTickInTrack(tick2(), track()));
150 :
151 104 : if (!endElement()) {
152 1 : setEndElement(score()->findChordRestEndingBeforeTickInStaff(tick2(), track2staff(track())));
153 : }
154 : }
155 :
156 : //=========================================================
157 : // LyricsLineSegment
158 : //=========================================================
159 :
160 0 : void LyricsLineSegment::rebaseAnchors(EditData&, Grip)
161 : {
162 : // Don't rebase lyric line anchors on drag
163 0 : return;
164 : }
165 :
166 250 : LyricsLineSegment::LyricsLineSegment(LyricsLine* sp)
167 250 : : LineSegment(ElementType::LYRICSLINE_SEGMENT, sp, ElementFlag::ON_STAFF)
168 : {
169 250 : }
170 :
171 3 : LyricsLineSegment::LyricsLineSegment(const ElementType& type, LyricsLine* sp, ElementFlags f)
172 3 : : LineSegment(type, sp, f)
173 : {
174 3 : }
175 :
176 339 : double LyricsLineSegment::baseLineShift() const
177 : {
178 339 : if (lyricsLine()->isEndMelisma()) {
179 111 : return -0.5 * absoluteFromSpatium(lineWidth());
180 : }
181 :
182 228 : Lyrics* segLyrics = lyrics();
183 228 : return -style().styleD(Sid::lyricsDashYposRatio) * segLyrics->fontMetrics().xHeight();
184 : }
185 :
186 3 : PropertyValue LyricsLineSegment::getProperty(Pid propertyId) const
187 : {
188 3 : if (EngravingObject* delegate = propertyDelegate(propertyId)) {
189 1 : return delegate->getProperty(propertyId);
190 : }
191 :
192 2 : return LineSegment::getProperty(propertyId);
193 : }
194 :
195 4 : bool LyricsLineSegment::setProperty(Pid propertyId, const PropertyValue& val)
196 : {
197 4 : if (EngravingObject* delegate = propertyDelegate(propertyId)) {
198 1 : return delegate->setProperty(propertyId, val);
199 : }
200 :
201 3 : return LineSegment::setProperty(propertyId, val);
202 : }
203 :
204 3 : PropertyValue LyricsLineSegment::propertyDefault(Pid propertyId) const
205 : {
206 3 : if (EngravingObject* delegate = propertyDelegate(propertyId)) {
207 1 : return delegate->propertyDefault(propertyId);
208 : }
209 :
210 2 : return LineSegment::propertyDefault(propertyId);
211 : }
212 :
213 21 : EngravingObject* LyricsLineSegment::propertyDelegate(Pid propertyId) const
214 : {
215 21 : if (propertyId == Pid::GENERATED) {
216 0 : return lyricsLine();
217 : }
218 :
219 21 : return LineSegment::propertyDelegate(propertyId);
220 : }
221 :
222 : //=========================================================
223 : // PartialLyricsLine
224 : //=========================================================
225 :
226 : static const ElementStyle partialLyricsLineElementStyle {
227 : { Sid::lyricsDashLineThickness, Pid::LINE_WIDTH },
228 : { Sid::lyricsPlacement, Pid::PLACEMENT },
229 : };
230 :
231 11 : PartialLyricsLine::PartialLyricsLine(DummyParentOr<EngravingItem> parent)
232 11 : : LyricsLine(ElementType::PARTIAL_LYRICSLINE, parent)
233 : {
234 11 : setGenerated(false);
235 11 : initElementStyle(&partialLyricsLineElementStyle);
236 11 : }
237 :
238 0 : PartialLyricsLine::PartialLyricsLine(const PartialLyricsLine& other)
239 0 : : LyricsLine(other)
240 : {
241 0 : m_isEndMelisma = other.m_isEndMelisma;
242 0 : m_centerBetweenStaves = other.m_centerBetweenStaves;
243 0 : }
244 :
245 3 : LineSegment* PartialLyricsLine::createLineSegment()
246 : {
247 3 : PartialLyricsLineSegment* seg = new PartialLyricsLineSegment(this);
248 3 : seg->setTrack(track());
249 3 : seg->setColor(color());
250 3 : return seg;
251 : }
252 :
253 107 : PropertyValue PartialLyricsLine::getProperty(Pid propertyId) const
254 : {
255 107 : switch (propertyId) {
256 12 : case Pid::VERSE:
257 12 : return m_verse;
258 8 : case Pid::CENTER_BETWEEN_STAVES:
259 8 : return centerBetweenStaves();
260 87 : default:
261 87 : return LyricsLine::getProperty(propertyId);
262 : }
263 : }
264 :
265 36 : bool PartialLyricsLine::setProperty(Pid propertyId, const PropertyValue& val)
266 : {
267 36 : switch (propertyId) {
268 2 : case Pid::VERSE:
269 2 : setVerse(val.toInt());
270 2 : break;
271 4 : case Pid::CENTER_BETWEEN_STAVES:
272 4 : setCenterBetweenStaves(val.value<AutoOnOff>());
273 4 : break;
274 30 : default:
275 30 : return LyricsLine::setProperty(propertyId, val);
276 : }
277 :
278 6 : triggerLayout();
279 :
280 6 : return true;
281 : }
282 :
283 90 : PropertyValue PartialLyricsLine::propertyDefault(Pid propertyId) const
284 : {
285 90 : switch (propertyId) {
286 6 : case Pid::VERSE:
287 6 : return 0;
288 7 : case Pid::CENTER_BETWEEN_STAVES:
289 14 : return AutoOnOff::AUTO;
290 77 : default:
291 77 : return LyricsLine::propertyDefault(propertyId);
292 : }
293 : }
294 :
295 0 : void PartialLyricsLine::reset()
296 : {
297 0 : undoResetProperty(Pid::CENTER_BETWEEN_STAVES);
298 0 : LyricsLine::reset();
299 0 : }
300 :
301 27 : Sid PartialLyricsLine::getPropertyStyle(Pid propertyId) const
302 : {
303 27 : switch (propertyId) {
304 14 : case Pid::PLACEMENT:
305 14 : return Sid::lyricsPlacement;
306 13 : default:
307 13 : return LyricsLine::getPropertyStyle(propertyId);
308 : }
309 : }
310 :
311 1 : void PartialLyricsLine::undoChangeProperty(Pid id, const PropertyValue& v, PropertyFlags ps)
312 : {
313 1 : if (id == Pid::VERSE && verse() != v.toInt()) {
314 1 : ChordRest* endCR = endElement()
315 1 : && endElement()->isChordRest() ? toChordRest(endElement()) : nullptr;
316 1 : Lyrics* endLyrics = nullptr;
317 1 : if (endCR) {
318 1 : for (Lyrics* lyr : endCR->lyrics()) {
319 1 : if (lyr->verse() == verse()) {
320 1 : endLyrics = lyr;
321 1 : break;
322 : }
323 : }
324 : }
325 :
326 1 : LyricsLine::undoChangeProperty(id, v, ps);
327 1 : if (endLyrics && endLyrics->verse() != v.toInt()) {
328 1 : endLyrics->undoChangeProperty(id, v, ps);
329 : }
330 1 : return;
331 : }
332 :
333 0 : LyricsLine::undoChangeProperty(id, v, ps);
334 : }
335 :
336 8 : void PartialLyricsLine::doComputeEndElement()
337 : {
338 8 : LyricsLine::doComputeEndElement();
339 :
340 8 : if (startElement() && endElement() && startElement()->tick() > endElement()->tick()) {
341 0 : setEndElement(startElement());
342 : }
343 8 : }
344 :
345 : //=========================================================
346 : // PartialLyricsLineSegment
347 : //=========================================================
348 :
349 : static const ElementStyle partialLyricsLineSegmentElementStyle {
350 : { Sid::lyricsMinTopDistance, Pid::MIN_DISTANCE },
351 : };
352 :
353 3 : PartialLyricsLineSegment::PartialLyricsLineSegment(PartialLyricsLine* line)
354 3 : : LyricsLineSegment(ElementType::PARTIAL_LYRICSLINE_SEGMENT, line, ElementFlag::ON_STAFF)
355 : {
356 3 : setGenerated(false);
357 3 : initElementStyle(&partialLyricsLineSegmentElementStyle);
358 3 : }
359 :
360 6 : double PartialLyricsLineSegment::lineSpacing() const
361 : {
362 6 : const Lyrics* lyrics = lyricsLine()->findAdjacentLyricsOrDefault();
363 :
364 6 : if (!lyrics) {
365 0 : return 0.0;
366 : }
367 :
368 6 : return lyrics->lineSpacing();
369 : }
370 :
371 6 : double PartialLyricsLineSegment::baseLineShift() const
372 : {
373 6 : if (lyricsLine()->isEndMelisma()) {
374 3 : return -0.5 * absoluteFromSpatium(lineWidth());
375 : }
376 :
377 3 : const Lyrics* lyrics = lyricsLine()->findAdjacentLyricsOrDefault();
378 3 : if (!lyrics) {
379 0 : return 0.0;
380 : }
381 :
382 3 : return -style().styleD(Sid::lyricsDashYposRatio) * lyrics->fontMetrics().xHeight();
383 : }
384 :
385 24 : EngravingObject* PartialLyricsLineSegment::propertyDelegate(Pid pid) const
386 : {
387 24 : switch (pid) {
388 3 : case Pid::VERSE:
389 : case Pid::CENTER_BETWEEN_STAVES:
390 3 : return lyricsLine();
391 21 : default:
392 21 : return LyricsLineSegment::propertyDelegate(pid);
393 : }
394 : }
395 :
396 9 : Lyrics* PartialLyricsLine::findLyricsInPreviousRepeatSeg() const
397 : {
398 9 : const std::vector<Measure*> measures = findPreviousRepeatMeasures(findStartMeasure());
399 :
400 15 : for (const Measure* measure : measures) {
401 9 : Lyrics* prev = Navigation::lastLyricsInMeasure(measure->last(SegmentType::ChordRest), staffIdx(), verse(), placement());
402 :
403 9 : if (!prev) {
404 6 : continue;
405 : }
406 :
407 3 : return prev;
408 : }
409 :
410 6 : return nullptr;
411 9 : }
412 :
413 9 : Lyrics* PartialLyricsLine::findAdjacentLyricsOrDefault() const
414 : {
415 9 : Lyrics* prev = findLyricsInPreviousRepeatSeg();
416 9 : if (prev) {
417 3 : return prev;
418 : }
419 :
420 6 : Lyrics* next = nextLyrics();
421 6 : if (next) {
422 0 : return next;
423 : }
424 :
425 : // If there are no adjacent lyrics, create dummy lyrics using the odd lyrics text style to get font information
426 6 : Lyrics* dummyLyr = Factory::createLyrics(score()->dummy());
427 6 : dummyLyr->setTextStyleType(TextStyleType::LYRICS_ODD);
428 6 : return dummyLyr;
429 : }
430 : }
|