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) 2023 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 "guitardivelayout.h"
23 :
24 : #include "dom/guitarbend.h"
25 : #include "dom/note.h"
26 : #include "dom/staff.h"
27 : #include "dom/stafftype.h"
28 : #include "dom/system.h"
29 : #include "dom/whammybar.h"
30 :
31 : #include "horizontalspacing.h"
32 : #include "textlayout.h"
33 : #include "tlayout.h"
34 :
35 : using namespace mu::engraving;
36 : using namespace muse::draw;
37 : using namespace mu::engraving::rendering::score;
38 :
39 7388 : void GuitarDiveLayout::updateDiveSequences(const std::vector<GuitarBend*>& bends, const LayoutContext& ctx)
40 : {
41 7388 : std::vector<GuitarBend*> dives;
42 7814 : for (GuitarBend* bend : bends) {
43 426 : if ((bend->bendType() == GuitarBendType::DIVE || bend->bendType() == GuitarBendType::PRE_DIVE) && bend->staffType()->isTabStaff()) {
44 0 : dives.push_back(bend);
45 : }
46 : }
47 :
48 7388 : for (GuitarBend* dive : dives) {
49 0 : dive->computeBendAmount();
50 0 : dive->mutldata()->resetDiveLevels();
51 : }
52 :
53 7388 : for (auto riter = dives.rbegin(); riter != dives.rend(); ++riter) {
54 0 : GuitarBend* dive = *riter;
55 0 : if (!dive->ldata()->diveLevels().empty()) {
56 0 : continue;
57 : }
58 :
59 : // On-staff vs above-staff layout
60 0 : DirectionV tabPos = dive->diveTabPos();
61 :
62 0 : std::set<int> diveLevels;
63 0 : std::unordered_set<GuitarBend*> diveSequence;
64 :
65 0 : diveLevels.insert(dive->totBendAmountIncludingPrecedingBends());
66 0 : diveSequence.insert(dive);
67 :
68 : // Consider dives part of the same sequence if they are directly connected
69 : // or if they are spanned by the same whammy bar line
70 :
71 0 : if (WhammyBar* whammyBar = dive->findOverlappingWhammyBar(dive->tick(), dive->tick2())) {
72 0 : for (auto riter2 = riter + 1; riter2 != dives.rend(); ++riter2) {
73 0 : GuitarBend* earlierDive = *riter2;
74 0 : if (earlierDive->tick() >= whammyBar->tick()) {
75 0 : diveLevels.insert(earlierDive->totBendAmountIncludingPrecedingBends());
76 0 : diveSequence.insert(earlierDive);
77 0 : if (earlierDive->diveTabPos() != DirectionV::AUTO) {
78 0 : tabPos = earlierDive->diveTabPos();
79 : }
80 : }
81 : }
82 : }
83 :
84 0 : GuitarBend* prevDive = dive->findPrecedingBend();
85 0 : while (prevDive) {
86 0 : diveLevels.insert(prevDive->totBendAmountIncludingPrecedingBends());
87 0 : diveSequence.insert(prevDive);
88 0 : if (prevDive->diveTabPos() != DirectionV::AUTO) {
89 0 : tabPos = prevDive->diveTabPos();
90 : }
91 0 : prevDive = prevDive->findPrecedingBend();
92 : }
93 :
94 0 : bool layoutAboveStaff = tabPos == DirectionV::UP || (tabPos == DirectionV::AUTO && ctx.conf().styleB(Sid::guitarDivesAboveStaff));
95 :
96 0 : for (GuitarBend* d : diveSequence) {
97 0 : d->mutldata()->setDiveLevels(diveLevels);
98 0 : d->mutldata()->setAboveStaff(layoutAboveStaff);
99 : }
100 0 : }
101 7388 : }
102 :
103 0 : void GuitarDiveLayout::layoutDiveTabStaff(GuitarBendSegment* item, LayoutContext& ctx)
104 : {
105 0 : GuitarBend* bend = item->guitarBend();
106 0 : if (bend->bendType() == GuitarBendType::DIP) {
107 0 : layoutDip(item, ctx);
108 0 : return;
109 : }
110 0 : if (bend->bendType() == GuitarBendType::SCOOP) {
111 0 : layoutScoop(item);
112 0 : return;
113 : }
114 :
115 0 : GuitarBendSegment::LayoutData* ldata = item->mutldata();
116 :
117 0 : Note* startNote = bend->startNote();
118 0 : Note* endNote = bend->endNote();
119 0 : if (!startNote || !endNote) {
120 0 : return;
121 : }
122 :
123 0 : Chord* startChord = startNote->chord();
124 0 : Chord* endChord = endNote->chord();
125 0 : if (startNote->string() != startChord->upString() || endNote->string() != endChord->upString()) {
126 0 : return; // On TAB only one dive line can exist and only from the top note
127 : }
128 :
129 0 : if (bend->bendType() == GuitarBendType::PRE_DIVE) {
130 0 : bool alignToGrace = ctx.conf().styleB(Sid::alignPreBendAndPreDiveToGraceNote);
131 0 : for (Note* note : startChord->notes()) {
132 0 : if (!note->overrideBendVisibilityRules()) {
133 0 : note->setVisible(alignToGrace);
134 : }
135 : }
136 0 : for (Note* note : endChord->notes()) {
137 0 : if (!note->overrideBendVisibilityRules()) {
138 0 : note->setVisible(!alignToGrace);
139 : }
140 : }
141 0 : } else if (bend->isFullReleaseDive() || item->isEndType()) {
142 0 : for (Note* note : endChord->notes()) {
143 0 : if (!note->overrideBendVisibilityRules()) {
144 0 : note->setVisible(true);
145 : }
146 0 : note->setGhost(true);
147 0 : note->mutldata()->reset();
148 : }
149 0 : TLayout::layoutChord(endNote->chord(), ctx);
150 0 : } else if (!bend->isFullReleaseDive() || !ctx.conf().styleB(Sid::showFretOnFullBendRelease)) {
151 0 : for (Note* note : endChord->notes()) {
152 0 : if (!note->overrideBendVisibilityRules()) {
153 0 : note->setVisible(false);
154 : }
155 : }
156 : }
157 :
158 0 : bool aboveStaff = bend->ldata()->aboveStaff();
159 0 : if (aboveStaff) {
160 0 : item->setPos(computeStartPosAboveStaff(item, ctx));
161 0 : item->setPos2(computeEndPosAboveStaff(item, ctx) - item->ldata()->pos());
162 : } else {
163 0 : item->setPos(computeStartPosOnStaff(item, ctx));
164 0 : item->setPos2(computeEndPosOnStaff(item, ctx) - item->ldata()->pos());
165 : }
166 :
167 0 : GuitarBendText* bendText = item->bendText();
168 0 : if (item->isSingleEndType()) {
169 0 : bendText->setXmlText(bend->ldata()->bendDigit());
170 0 : TextLayout::layoutBaseTextBase(bendText, ctx);
171 0 : double verticalTextPad = 0.35 * item->spatium();
172 0 : PointF bendTextPos = item->pos2();
173 0 : if (aboveStaff) {
174 0 : if (bend->bendType() == GuitarBendType::PRE_DIVE && bend->bendAmountInQuarterTones() < 0) {
175 0 : bendTextPos = PointF(); // If predive is downwards the text must be above the start
176 : }
177 0 : bendTextPos += PointF(-0.5 * bendText->width(), -bendText->height() - verticalTextPad);
178 : } else {
179 0 : bendTextPos += PointF(-0.5 * bendText->width(),
180 0 : item->pos().y() + item->pos2().y() > 0 ? verticalTextPad : -bendText->height() - verticalTextPad);
181 : }
182 0 : bendText->setPos(bendTextPos);
183 : } else {
184 0 : bendText->mutldata()->reset();
185 : }
186 :
187 0 : PainterPath path;
188 0 : if (bend->isSlack()) {
189 0 : const double slackLineLen = 2 * item->spatium();
190 0 : PointF vertexPoint = item->pos2() - PointF(0.5 * slackLineLen, 0.0);
191 0 : path.lineTo(vertexPoint);
192 0 : item->setPos2(item->pos2() + PointF(0.5 * slackLineLen, 0.0));
193 : }
194 0 : path.lineTo(item->pos2());
195 0 : ldata->setPath(path);
196 :
197 0 : const double lineWidth = item->lineWidth();
198 0 : RectF r = RectF(PointF(), item->pos2()).normalized();
199 0 : if (muse::RealIsNull(r.height())) {
200 0 : r.setHeight(lineWidth);
201 : }
202 0 : if (muse::RealIsNull(r.width())) {
203 0 : r.setWidth(lineWidth);
204 : }
205 0 : item->mutldata()->setBbox(r);
206 0 : }
207 :
208 0 : PointF GuitarDiveLayout::computeStartPosOnStaff(GuitarBendSegment* item, LayoutContext& ctx)
209 : {
210 0 : GuitarBend* bend = item->guitarBend();
211 :
212 0 : if (bend->bendType() == GuitarBendType::PRE_DIVE) {
213 0 : Note* note = ctx.conf().styleB(Sid::alignPreBendAndPreDiveToGraceNote) ? bend->startNote() : bend->endNote();
214 0 : if (bend->bendAmountInQuarterTones() > 0) {
215 0 : note = note->chord()->upNote();
216 : } else {
217 0 : note = note->chord()->downNote();
218 : }
219 0 : PointF notePos = note->systemPos();
220 0 : RectF noteBbox = getNoteAndParenthesesShape(note);
221 0 : double verticalPadding = 0.25 * item->spatium();
222 0 : notePos += PointF(0.5 * noteBbox.width(), bend->bendAmountInQuarterTones() > 0
223 0 : ? noteBbox.top() - verticalPadding
224 0 : : noteBbox.bottom() + verticalPadding);
225 0 : return notePos;
226 : }
227 :
228 0 : LineSegment* prevSegment = findPrevHoldOrBendSegment(item, /*excludeFullReleaseDive*/ true);
229 0 : if (prevSegment) {
230 0 : return prevSegment->pos() + prevSegment->pos2();
231 : }
232 :
233 0 : PointF startPos;
234 0 : Note* startNote = bend->startNote();
235 0 : RectF startNoteBbox = getNoteAndParenthesesShape(startNote);
236 0 : if (item->isSingleBeginType()) {
237 0 : startPos = startNote->systemPos();
238 0 : double horizontalIndent = 0.25 * item->spatium();
239 0 : startPos += PointF(startNoteBbox.right() + horizontalIndent, -0.5 * startNoteBbox.height() + 0.5 * item->lineWidth());
240 : } else {
241 0 : startPos.setX(item->system()->firstNoteRestSegmentX(true));
242 0 : startPos.setY(bend->frontSegment()->ldata()->pos().y() + bend->frontSegment()->ipos2().y());
243 : }
244 :
245 0 : return startPos;
246 : }
247 :
248 0 : PointF GuitarDiveLayout::computeEndPosOnStaff(GuitarBendSegment* item, LayoutContext&)
249 : {
250 0 : GuitarBend* bend = item->guitarBend();
251 0 : double spatium = item->spatium();
252 :
253 0 : double x = 0.0;
254 0 : if (item->isSingleEndType()) {
255 0 : Note* endNote = bend->endNote();
256 0 : PointF endNotePos = endNote->systemPos();
257 0 : RectF endNoteBbox = getNoteAndParenthesesShape(endNote);
258 :
259 0 : if (bend->isFullReleaseDive()) {
260 0 : x = endNotePos.x() + endNoteBbox.left() - 0.25 * spatium;
261 0 : double y = endNotePos.y() + endNoteBbox.top() + 0.5 * item->lineWidth();
262 0 : return PointF(x, y);
263 : }
264 :
265 0 : x = bend->bendType() == GuitarBendType::PRE_DIVE ? item->pos().x() : endNotePos.x() + 0.5 * endNoteBbox.width();
266 : } else {
267 0 : x = item->system()->endingXForOpenEndedLines();
268 :
269 0 : if (bend->isFullReleaseDive()) {
270 : /* Full release dives end at the same y as they started, so we can use the startNote's position
271 : * here in the case that the segment is BEGIN type (bc its endNote hasn't been laid out yet) */
272 0 : Note* startNote = bend->startNote();
273 0 : PointF startNotePos = startNote->systemPos();
274 0 : RectF startNoteBbox = getNoteAndParenthesesShape(startNote);
275 0 : double y = startNotePos.y() + startNoteBbox.top() + 0.5 * item->lineWidth();
276 0 : return PointF(x, y);
277 : }
278 : }
279 :
280 0 : if (bend->bendType() == GuitarBendType::PRE_DIVE) {
281 0 : LineSegment* prevSegment = findPrevHoldOrBendSegment(item);
282 0 : if (prevSegment && prevSegment->isGuitarBendHoldSegment()) {
283 0 : double y = prevSegment->pos().y() + prevSegment->pos2().y();
284 0 : return PointF(x, y);
285 : }
286 : }
287 :
288 0 : int diveLevel = bend->totBendAmountIncludingPrecedingBends();
289 0 : bool aboveStaff = diveLevel > 0;
290 :
291 0 : const StaffType* staffType = item->staffType();
292 0 : IF_ASSERT_FAILED(staffType) {
293 0 : return PointF(x, 0.0);
294 : }
295 0 : int staffLines = staffType->lines();
296 0 : double lineDist = item->absoluteFromSpatium(staffType->lineDistance());
297 :
298 0 : if (!item->isSingleEndType()) {
299 0 : double y = aboveStaff ? -lineDist : staffLines * lineDist;
300 0 : return PointF(x, y);
301 : }
302 :
303 0 : const std::set<int>& diveLevels = bend->ldata()->diveLevels();
304 0 : std::deque<int> diveLevelsInThisDirection;
305 0 : for (int level : diveLevels) {
306 0 : if (aboveStaff && level > 0) {
307 0 : diveLevelsInThisDirection.push_back(level);
308 0 : } else if (!aboveStaff && level < 0) {
309 0 : diveLevelsInThisDirection.push_front(level);
310 : }
311 : }
312 :
313 0 : size_t levelNumber = muse::indexOf(diveLevelsInThisDirection, diveLevel);
314 0 : IF_ASSERT_FAILED(levelNumber != muse::nidx) {
315 0 : return PointF(x, 0.0);
316 : }
317 :
318 0 : double zeroDiveLevel = aboveStaff ? -1.5 * lineDist : lineDist * (staffLines + 0.5);
319 0 : double increment = spatium;
320 :
321 0 : double y = zeroDiveLevel + (aboveStaff ? -1 : 1) * (increment * levelNumber);
322 :
323 0 : return PointF(x, y);
324 0 : }
325 :
326 0 : PointF GuitarDiveLayout::computeStartPosAboveStaff(GuitarBendSegment* item, LayoutContext& ctx)
327 : {
328 0 : GuitarBend* bend = item->guitarBend();
329 :
330 0 : LineSegment* prevSegment = findPrevHoldOrBendSegment(item);
331 0 : if (prevSegment) {
332 0 : return prevSegment->pos() + prevSegment->pos2();
333 : }
334 :
335 0 : PointF startPos;
336 :
337 0 : if (item->isSingleBeginType()) {
338 0 : Note* note = bend->bendType() == GuitarBendType::PRE_BEND && !ctx.conf().styleB(Sid::alignPreBendAndPreDiveToGraceNote)
339 0 : ? bend->endNote() : bend->startNote();
340 0 : RectF noteBbox = getNoteAndParenthesesShape(note);
341 0 : startPos.setX(note->systemPos().x() + 0.5 * noteBbox.width());
342 :
343 0 : const StaffType* staffType = item->staffType();
344 0 : IF_ASSERT_FAILED(staffType) {
345 0 : startPos.setY(0.0);
346 : } else {
347 0 : double spatium = item->spatium();
348 0 : double lineDist = item->absoluteFromSpatium(staffType->lineDistance());
349 0 : const double baseLine = -1.5 * lineDist;
350 0 : const double increment = spatium;
351 0 : std::set<int> diveLevels = bend->mutldata()->diveLevels();
352 0 : diveLevels.insert(0); // Because for dives above the staff we need the zero level too
353 0 : size_t zeroLevel = muse::indexOf(diveLevels, 0);
354 0 : double totIncrement = increment * zeroLevel;
355 0 : startPos.setY(baseLine - totIncrement);
356 0 : }
357 : } else {
358 0 : startPos.setX(item->system()->firstNoteRestSegmentX(true));
359 0 : startPos.setY(bend->frontSegment()->ldata()->pos().y());
360 : }
361 :
362 0 : return startPos;
363 : }
364 :
365 0 : PointF GuitarDiveLayout::computeEndPosAboveStaff(GuitarBendSegment* item, LayoutContext&)
366 : {
367 0 : GuitarBend* bend = item->guitarBend();
368 0 : double spatium = item->spatium();
369 :
370 0 : double x = 0.0;
371 0 : if (item->isSingleEndType()) {
372 0 : Note* endNote = bend->endNote();
373 0 : PointF endNotePos = endNote->systemPos();
374 0 : RectF endNoteBbox = getNoteAndParenthesesShape(endNote);
375 0 : x = bend->bendType() == GuitarBendType::PRE_DIVE
376 0 : ? item->pos().x() : endNotePos.x() + 0.5 * endNoteBbox.width();
377 : } else {
378 0 : x = item->system()->endingXForOpenEndedLines();
379 : }
380 :
381 0 : if (bend->bendType() == GuitarBendType::PRE_DIVE) {
382 0 : LineSegment* prevSegment = findPrevHoldOrBendSegment(item);
383 0 : if (prevSegment && prevSegment->isGuitarBendHoldSegment()) {
384 0 : double y = prevSegment->pos().y() + prevSegment->pos2().y();
385 0 : return PointF(x, y);
386 : }
387 : }
388 :
389 0 : int endingLevel = bend->totBendAmountIncludingPrecedingBends();
390 0 : int startingLevel = endingLevel - bend->bendAmountInQuarterTones();
391 :
392 0 : std::set<int> diveLevels = bend->ldata()->diveLevels();
393 0 : diveLevels.insert(0); // Because for dives above the staff we need the zero level too
394 :
395 0 : size_t endingIdx = muse::indexOf(diveLevels, endingLevel);
396 0 : size_t startingIdx = muse::indexOf(diveLevels, startingLevel);
397 0 : IF_ASSERT_FAILED(endingIdx != muse::nidx && startingIdx != muse::nidx && endingIdx != startingIdx) {
398 0 : return PointF(x, 0.0);
399 : }
400 :
401 0 : int steps = static_cast<int>(endingIdx) - static_cast<int>(startingIdx);
402 0 : double increment = spatium;
403 :
404 0 : double y = item->ldata()->pos().y() - steps * increment;
405 :
406 0 : return PointF(x, y);
407 0 : }
408 :
409 0 : LineSegment* GuitarDiveLayout::findPrevHoldOrBendSegment(GuitarBendSegment* item, bool excludeFullReleaseDive)
410 : {
411 0 : GuitarBend* bend = item->guitarBend();
412 0 : GuitarBend* prevBend = item->guitarBend()->findPrecedingBend();
413 0 : if (!prevBend) {
414 0 : return nullptr;
415 : }
416 :
417 0 : GuitarBendHold* hold = prevBend->holdLine();
418 0 : if (hold && !hold->segmentsEmpty() && prevBend->endNote() != bend->startNote() && hold->backSegment()->system() == item->system()) {
419 0 : return hold->backSegment();
420 : }
421 :
422 0 : if (!prevBend->segmentsEmpty() && !(excludeFullReleaseDive && prevBend->isFullReleaseDive())
423 0 : && prevBend->backSegment()->system() == item->system()) {
424 0 : return prevBend->backSegment();
425 : }
426 :
427 0 : return nullptr;
428 : }
429 :
430 6 : void GuitarDiveLayout::layoutDip(GuitarBendSegment* item, LayoutContext& ctx)
431 : {
432 6 : GuitarBend* bend = item->guitarBend();
433 6 : Note* startNote = bend->startNote();
434 6 : IF_ASSERT_FAILED(startNote) {
435 0 : return;
436 : }
437 :
438 6 : PointF notePos = startNote->systemPos();
439 6 : double centerX = notePos.x() + 0.5 * startNote->width();
440 6 : double spatium = item->spatium();
441 6 : double baseY = std::min(-spatium, notePos.y() - 0.5 * startNote->height() - spatium);
442 :
443 6 : const double vHeight = 2 * spatium;
444 6 : const double vWidth = 2 * spatium;
445 :
446 6 : bool upwards = bend->bendAmountInQuarterTones() > 0;
447 6 : PointF start = PointF(centerX - 0.5 * vWidth, upwards ? baseY : baseY - vHeight);
448 6 : PointF vertex = PointF(centerX, upwards ? baseY - vHeight : baseY) - start;
449 6 : PointF end = PointF(centerX + 0.5 * vWidth, upwards ? baseY : baseY - vHeight) - start;
450 :
451 6 : item->setPos(start);
452 6 : item->setPos2(end);
453 :
454 6 : PainterPath path;
455 6 : path.lineTo(vertex);
456 6 : path.lineTo(end);
457 6 : item->mutldata()->setPath(path);
458 :
459 6 : RectF bbox = RectF(PointF(), vertex).normalized().united(RectF(vertex, end).normalized());
460 6 : item->mutldata()->setBbox(bbox);
461 :
462 6 : GuitarBendText* bendText = item->bendText();
463 6 : bendText->setXmlText(bend->ldata()->bendDigit());
464 6 : TextLayout::layoutBaseTextBase(bendText, ctx);
465 6 : double verticalTextPad = 0.35 * item->spatium();
466 6 : bendText->setPos(PointF(vertex.x() - 0.5 * bendText->width(), bbox.top() - verticalTextPad - bendText->height()));
467 6 : }
468 :
469 3 : void GuitarDiveLayout::layoutScoop(GuitarBendSegment* item)
470 : {
471 3 : GuitarBend* bend = item->guitarBend();
472 3 : Note* startNote = bend->startNote();
473 3 : IF_ASSERT_FAILED(startNote) {
474 0 : return;
475 : }
476 :
477 3 : const SymId scoopSym = SymId::guitarVibratoBarScoop;
478 3 : RectF bbox = item->symBbox(scoopSym);
479 3 : item->setbbox(bbox);
480 :
481 3 : PointF pos = startNote->systemPos();
482 3 : const double spatium = item->spatium();
483 3 : const double leftPad = 0.25 * spatium;
484 3 : const double downPad = 0.35 * spatium;
485 :
486 3 : double yMove = bbox.height();
487 3 : if (item->staffType() && item->staffType()->isTabStaff()) {
488 0 : yMove -= 0.5 * startNote->height();
489 : } else {
490 3 : yMove += startNote->line() % 2 ? 0.0 : downPad;
491 : }
492 :
493 3 : Shape chordShape = startNote->chord()->shape();
494 6 : chordShape.removeTypes({ ElementType::GUITAR_BEND, ElementType::GUITAR_BEND_SEGMENT, ElementType::BEAM });
495 :
496 3 : double xMove = -HorizontalSpacing::minHorizontalDistance(Shape(bbox.translated(0.0, startNote->y())), chordShape, spatium) - leftPad;
497 :
498 3 : startNote->chord()->segment()->staffShape(startNote->staffIdx()).add(bbox.translated(xMove, yMove + startNote->y()));
499 :
500 3 : item->setPos(pos + PointF(xMove, yMove));
501 3 : item->setPos2(PointF());
502 3 : }
503 :
504 0 : RectF GuitarDiveLayout::getNoteAndParenthesesShape(const Note* note)
505 : {
506 0 : const NoteParenthesisInfo* parenInfo = note->parenthesisInfo();
507 0 : RectF noteBbox = note->ldata()->bbox();
508 :
509 0 : if (!parenInfo) {
510 0 : return noteBbox;
511 : }
512 :
513 0 : const Parenthesis* leftParen = parenInfo->leftParen();
514 0 : const Parenthesis* rightParen = parenInfo->rightParen();
515 :
516 0 : noteBbox.unite(leftParen->ldata()->bbox().translated(leftParen->pos() - note->pos()));
517 0 : noteBbox.unite(rightParen->ldata()->bbox().translated(rightParen->pos() - note->pos()));
518 :
519 0 : return noteBbox;
520 : }
|