1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 3 /* 4 Rosegarden 5 A MIDI and audio sequencer and musical notation editor. 6 Copyright 2000-2021 the Rosegarden development team. 7 8 Other copyrights also apply to some parts of this work. Please 9 see the AUTHORS file and individual file headers for details. 10 11 This program is free software; you can redistribute it and/or 12 modify it under the terms of the GNU General Public License as 13 published by the Free Software Foundation; either version 2 of the 14 License, or (at your option) any later version. See the file 15 COPYING included with this distribution for more information. 16 */ 17 18 #define RG_MODULE_STRING "[NotationVLayout]" 19 20 #include <cmath> 21 #include "NotationVLayout.h" 22 #include "misc/Debug.h" 23 #include "misc/Strings.h" 24 25 #include "base/Composition.h" 26 #include "base/Event.h" 27 #include "base/LayoutEngine.h" 28 #include "base/NotationRules.h" 29 #include "base/NotationTypes.h" 30 #include "base/NotationQuantizer.h" 31 #include "base/Profiler.h" 32 #include "base/ViewSegment.h" 33 #include "gui/editors/guitar/Chord.h" 34 #include "misc/ConfigGroups.h" 35 #include "NotationChord.h" 36 #include "NotationElement.h" 37 #include "NotationProperties.h" 38 #include "NotationStaff.h" 39 #include "NotePixmapFactory.h" 40 #include <QMessageBox> 41 #include <QObject> 42 #include <QSettings> 43 #include <QString> 44 #include <QWidget> 45 46 namespace Rosegarden 47 { 48 49 using namespace BaseProperties; 50 51 52 NotationVLayout::NotationVLayout(Composition *c, NotePixmapFactory *npf, 53 const NotationProperties &properties, 54 QObject* /*parent*/) : 55 m_composition(c), 56 m_npf(npf), 57 m_notationQuantizer(c->getNotationQuantizer()), 58 m_properties(properties) 59 { 60 // Get display settings 61 QSettings settings; 62 settings.beginGroup(NotationViewConfigGroup); 63 m_showRepeated = settings.value("showrepeated", true).toBool(); 64 m_distributeVerses = settings.value("distributeverses", true).toBool(); 65 settings.endGroup(); 66 } 67 68 NotationVLayout::~NotationVLayout() 69 { 70 // empty 71 } 72 73 NotationVLayout::SlurList & 74 NotationVLayout::getSlurList(ViewSegment &staff) 75 { 76 SlurListMap::iterator i = m_slurs.find(&staff); 77 if (i == m_slurs.end()) { 78 m_slurs[&staff] = SlurList(); 79 } 80 81 return m_slurs[&staff]; 82 } 83 84 void 85 NotationVLayout::reset() 86 { 87 m_slurs.clear(); 88 } 89 90 void 91 NotationVLayout::scanViewSegment(ViewSegment &staffBase, timeT, timeT, bool) 92 { 93 // We actually always perform a full scan, for reasons of sheer laziness 94 95 Profiler profiler("NotationVLayout::scanViewSegment"); 96 97 NotationStaff &staff = dynamic_cast<NotationStaff &>(staffBase); 98 NotationElementList *notes = staff.getViewElementList(); 99 100 getSlurList(staff).clear(); 101 102 NotationElementList::iterator from = notes->begin(); 103 NotationElementList::iterator to = notes->end(); 104 NotationElementList::iterator i; 105 106 for (i = from; i != to; ++i) { 107 108 NotationElement *el = static_cast<NotationElement*>(*i); 109 110 // Displaced Y will only be used for certain events -- in 111 // particular not for notes, whose y-coord is obviously kind 112 // of meaningful. 113 double displacedY = 0.0; 114 long dyRaw = 0; 115 el->event()->get<Int>(DISPLACED_Y, dyRaw); 116 displacedY = double(dyRaw * m_npf->getLineSpacing()) / 1000.0; 117 118 el->setLayoutY(staff.getLayoutYForHeight( -9) + displacedY); 119 120 if (el->isRest()) { 121 122 // rests for notes longer than the minim have hotspots 123 // aligned with the line above the middle line; the rest 124 // are aligned with the middle line 125 126 long noteType; 127 bool hasNoteType = el->event()->get<Int>(NOTE_TYPE, noteType); 128 if (hasNoteType && noteType > Note::Minim) { 129 el->setLayoutY(staff.getLayoutYForHeight(6) + displacedY); 130 } else { 131 el->setLayoutY(staff.getLayoutYForHeight(4) + displacedY); 132 } 133 134 // Fix for bug #674 (was 1090767) Rests outside staves have wrong 135 // glyphs by William <rosegarden4c AT orthoset.com> 136 // We use a "rest-outside-stave" glyph for any minim/semibreve/breve 137 // rest that has been displaced vertically e.g. by fine-positioning 138 // outside the stave. For small vertical displacements that keep 139 // the rest inside the stave, we use the "rest-inside-stave" glyph 140 // and also discretise the displacement into multiples of the 141 // stave-line spacing. The outside-stave glyphs match the character 142 // numbers 1D13A, 1D13B and 1D13C in the Unicode 4.0 standard. 143 144 if (hasNoteType && (displacedY > 0.1 || displacedY < -0.1)) { 145 146 // a fiddly check for transition from inside to outside: 147 148 int min = -1, max = 1; 149 150 switch (noteType) { 151 case Note::Breve: 152 min = -1; 153 max = 2; 154 break; 155 case Note::Semibreve: 156 min = -1; 157 max = 3; 158 break; 159 case Note::Minim: 160 min = -2; 161 max = 2; 162 break; 163 case Note::Crotchet: 164 min = -1; 165 max = 3; 166 break; 167 case Note::Quaver: 168 min = -2; CreateEventTrigger(CreateEventTrigStmt * stmt)169 max = 3; 170 break; 171 case Note::Semiquaver: 172 min = -3; 173 max = 3; 174 break; 175 case Note::Demisemiquaver: 176 min = -3; 177 max = 4; 178 break; 179 case Note::Hemidemisemiquaver: 180 min = -4; 181 max = 4; 182 break; 183 } 184 185 bool outside = false; 186 187 if (noteType == Note::Breve) { 188 if (nearbyint(displacedY) < min * m_npf->getLineSpacing() || 189 nearbyint(displacedY) > max * m_npf->getLineSpacing()) { 190 outside = true; 191 } 192 } else { 193 if ((int)displacedY < min * m_npf->getLineSpacing() || 194 (int)displacedY > max * m_npf->getLineSpacing()) { 195 outside = true; 196 } 197 } 198 199 el->event()->setMaybe<Bool>(m_properties.REST_OUTSIDE_STAVE, 200 outside); 201 202 if (!outside) { 203 displacedY = (double)m_npf->getLineSpacing() * 204 (int(nearbyint((double)displacedY / 205 m_npf->getLineSpacing()))); 206 if (noteType > Note::Minim) 207 el->setLayoutY(staff.getLayoutYForHeight(6) + displacedY); 208 else 209 el->setLayoutY(staff.getLayoutYForHeight(4) + displacedY); 210 } 211 212 // if (displacedY != 0.0) 213 // NOTATION_DEBUG << "REST_OUTSIDE_STAVE AFTER " 214 // << " : displacedY : " << displacedY 215 // << " line-spacing : " << m_npf->getLineSpacing() 216 // << " time : " << (el->getViewAbsoluteTime()) 217 // << endl; 218 } else { 219 el->event()->setMaybe<Bool>(m_properties.REST_OUTSIDE_STAVE, 220 false); 221 } 222 223 } else if (el->isNote()) { 224 225 NotationChord chord(*notes, i, m_notationQuantizer, m_properties); 226 if (chord.size() == 0) 227 continue; 228 229 std::vector<int> h; 230 for (unsigned int j = 0; j < chord.size(); ++j) { 231 long height = 0; 232 if (!(*chord[j])->event()->get<Int> 233 (m_properties.HEIGHT_ON_STAFF, height)) { 234 RG_WARNING << QString("scanViewSegment(): ERROR: Event in chord at %1 has no HEIGHT_ON_STAFF property!\nThis is a bug (the program would previously have crashed by now)").arg((*chord[j])->getViewAbsoluteTime()); 235 RG_WARNING << (*chord[j])->event(); 236 } 237 h.push_back(height); 238 } 239 bool stemmed = chord.hasStem(); 240 bool stemUp = chord.hasStemUp(); 241 bool hasNoteHeadShifted = chord.hasNoteHeadShifted(); 242 243 unsigned int flaggedNote = (stemUp ? chord.size() - 1 : 0); 244 245 bool hasShifted = chord.hasNoteHeadShifted(); 246 247 double y0 = -1E50; // A very unlikely Y layout value 248 249 for (unsigned int j = 0; j < chord.size(); ++j) { 250 251 el = static_cast<NotationElement*>(*chord[j]); 252 el->setLayoutY(staff.getLayoutYForHeight(h[j])); 253 254 // Look for collision 255 const double eps = 0.001; 256 Event *eel = el->event(); validate_ddl_tags(const char * filtervar,List * taglist)257 double y = el->getLayoutY(); 258 if (eel->has("pitch")) { 259 el->setIsColliding(fabs(y - y0) < eps); 260 y0 = y; 261 } 262 263 264 // These calculations and assignments are pretty much final 265 // if the chord is not in a beamed group, but if it is then 266 // they will be reworked by NotationGroup::applyBeam, which 267 // is called from NotationHLayout::layout, which is called 268 // after this. Any inaccuracies here for beamed groups 269 // should be stamped out there. 270 271 // el->event()->setMaybe<Bool>(STEM_UP, stemUp); 272 el->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, stemUp); 273 274 bool primary = 275 ((stemmed && stemUp) ? (j == 0) : (j == chord.size() - 1)); 276 el->event()->setMaybe<Bool> 277 (m_properties.CHORD_PRIMARY_NOTE, primary); 278 279 if (primary) { 280 el->event()->setMaybe<Int> 281 (m_properties.CHORD_MARK_COUNT, chord.getMarkCountForChord()); check_ddl_tag(const char * tag)282 } 283 284 bool shifted = chord.isNoteHeadShifted(chord[j]); 285 el->event()->setMaybe<Bool> 286 (m_properties.NOTE_HEAD_SHIFTED, shifted); 287 288 el->event()->setMaybe<Bool> 289 (m_properties.NOTE_DOT_SHIFTED, false); 290 if (hasShifted && stemUp) { 291 long dots = 0; 292 (void)el->event()->get 293 <Int>(NOTE_DOTS, dots); 294 if (dots > 0) { 295 el->event()->setMaybe<Bool> 296 (m_properties.NOTE_DOT_SHIFTED, true); 297 } 298 } 299 300 el->event()->setMaybe<Bool> 301 (m_properties.NEEDS_EXTRA_SHIFT_SPACE, 302 hasNoteHeadShifted && !stemUp); 303 304 el->event()->setMaybe<Bool> 305 (m_properties.DRAW_FLAG, j == flaggedNote); 306 307 int stemLength = -1; 308 if (j != flaggedNote) { 309 stemLength = staff.getLayoutYForHeight(h[flaggedNote]) - 310 staff.getLayoutYForHeight(h[j]); 311 if (stemLength < 0) 312 stemLength = -stemLength; 313 // NOTATION_DEBUG << "Setting stem length to " 314 // << stemLength << endl; 315 } else { 316 int minStemLength = stemLength; 317 if (h[j] < -2 && stemUp) { 318 //!!! needs tuning, & applying for beamed stems too 319 minStemLength = staff.getLayoutYForHeight(h[j]) - 320 staff.getLayoutYForHeight(4); 321 } else if (h[j] > 10 && !stemUp) { 322 minStemLength = staff.getLayoutYForHeight(4) - 323 staff.getLayoutYForHeight(h[j]); 324 } 325 stemLength = std::max(minStemLength, stemLength); 326 } 327 328 el->event()->setMaybe<Int> 329 (m_properties.UNBEAMED_STEM_LENGTH, stemLength); 330 } 331 validate_table_rewrite_tags(const char * filtervar,List * taglist)332 333 // #938545 (Broken notation: Duplicated note can float 334 // outside stave) -- Need to cope with the case where a 335 // note that's not a member of a chord (different stem 336 // direction &c) falls between notes that are members. 337 // Not optimal, as we can end up scanning the chord 338 // multiple times (we'll return to it after scanning the 339 // contained note). [We can't just iterate over all 340 // elements within the chord (as we can in hlayout) 341 // because we need them in height order.] 342 343 i = chord.getFirstElementNotInChord(); 344 if (i == notes->end()) 345 i = chord.getFinalElement(); 346 else 347 --i; 348 349 } else { 350 351 if (el->event()->isa(Clef::EventType)) { check_table_rewrite_ddl_tag(const char * tag)352 353 // clef pixmaps have the hotspot placed to coincide 354 // with the pitch of the clef -- so the alto clef 355 // should be "on" the middle line, the treble clef 356 // "on" the line below the middle, etc 357 358 el->setLayoutY(staff.getLayoutYForHeight 359 (Clef(*el->event()).getAxisHeight())); 360 361 } else if (el->event()->isa(Rosegarden::Symbol::EventType)) { 362 //!!! Presently all symbols are above the staff only, at a fixed 363 // height. I've never seen a segno or coda anywhere else. 364 // Breath marks might benefit from the ability to draw them up error_duplicate_filter_variable(const char * defname)365 // or draw them down, or even draw them at an arbitrary Y, but 366 // let's not get into any of that just now. 367 // 368 // The figure 19 is totally arbitrary, based on comparing the 369 // results against a "D. C. al Fine" text, to get these to 370 // display at approximately the same height. Please feel free 371 // to refine this first stab. 372 el->setLayoutY(staff.getLayoutYForHeight(19 + displacedY)); 373 374 } else if (el->event()->isa(Rosegarden::Key::EventType)) { 375 376 el->setLayoutY(staff.getLayoutYForHeight(12)); insert_event_trigger_tuple(const char * trigname,const char * eventname,Oid evtOwner,Oid funcoid,List * taglist)377 378 } else if (el->event()->isa(Text::EventType)) { 379 380 std::string type = Text::UnspecifiedType; 381 el->event()->get<String>(Text::TextTypePropertyName, type); 382 383 if (type == Text::Dynamic || 384 type == Text::LocalDirection || 385 type == Text::UnspecifiedType) { 386 // below the staff 387 el->setLayoutY(staff.getLayoutYForHeight(-7) + displacedY); 388 } else if (type == Text::Lyric) { 389 long verse = 0; 390 if (!m_distributeVerses) { 391 // verses even further below the staff 392 el->event()->get<Int>(Text::LyricVersePropertyName, verse); 393 } 394 el->setLayoutY(staff.getLayoutYForHeight(-10 - 3 * verse) + displacedY); 395 } else if (type == Text::Annotation) { 396 // annotations way below the staff 397 el->setLayoutY(staff.getLayoutYForHeight(-13) + displacedY); 398 } else { 399 // every other text well above the staff 400 el->setLayoutY(staff.getLayoutYForHeight(22) + displacedY); 401 } 402 403 } else if (el->event()->isa(Indication::EventType)) { 404 405 try { 406 std::string indicationType = 407 el->event()->get 408 <String>(Indication::IndicationTypePropertyName); 409 410 if (indicationType == Indication::Slur || 411 indicationType == Indication::PhrasingSlur) { 412 getSlurList(staff).push_back(i); 413 } 414 415 if (indicationType == Indication::OttavaUp || 416 indicationType == Indication::QuindicesimaUp) { 417 el->setLayoutY(staff.getLayoutYForHeight(15) + displacedY); 418 } else { 419 el->setLayoutY(staff.getLayoutYForHeight( -9) + displacedY); 420 } 421 422 if (indicationType == Indication::TrillLine) 423 // just draw them way above the staff for now 424 el->setLayoutY(staff.getLayoutYForHeight(15) + displacedY); 425 } catch (...) { 426 el->setLayoutY(staff.getLayoutYForHeight( -9) + displacedY); 427 } 428 429 } else if (el->event()->isa(Guitar::Chord::EventType)) { 430 431 el->setLayoutY(staff.getLayoutYForHeight(22) + displacedY); 432 } 433 } 434 } 435 } 436 437 void 438 NotationVLayout::finishLayout(timeT, timeT, bool) 439 { 440 Profiler profiler("NotationHLayout::finishLayout"); 441 442 for (SlurListMap::iterator mi = m_slurs.begin(); 443 mi != m_slurs.end(); ++mi) { 444 445 for (SlurList::iterator si = mi->second.begin(); 446 si != mi->second.end(); ++si) { 447 448 NotationElementList::iterator i = *si; 449 NotationStaff &staff = dynamic_cast<NotationStaff &>(*(mi->first)); 450 451 positionSlur(staff, i); 452 } filter_list_to_array(List * filterlist)453 } 454 } 455 456 void 457 NotationVLayout::positionSlur(NotationStaff &staff, 458 NotationElementList::iterator i) 459 { 460 NotationRules rules; 461 462 bool phrasing = ((*i)->event()->get 463 <String>(Indication::IndicationTypePropertyName) 464 == Indication::PhrasingSlur); 465 466 NotationElementList::iterator scooter = i; 467 468 timeT slurDuration = (*i)->event()->getDuration(); 469 if (slurDuration == 0 && (*i)->event()->has("indicationduration")) { 470 slurDuration = (*i)->event()->get 471 <Int>("indicationduration"); // obs property 472 } 473 timeT endTime = (*i)->getViewAbsoluteTime() + slurDuration; 474 475 bool haveStart = false; 476 477 int startTopHeight = 4, endTopHeight = 4, 478 startBottomHeight = 4, endBottomHeight = 4, 479 maxTopHeight = 4, minBottomHeight = 4, 480 maxCount = 0, minCount = 0; 481 482 int startX = (int)(*i)->getLayoutX(), endX = startX + 10; 483 bool startStemUp = false, endStemUp = false; 484 long startMarks = 0; 485 // long endMarks = 0; 486 // bool startTied = false; 487 // bool endTied = false; 488 bool beamAbove = false, beamBelow = false; 489 bool dynamic = false; 490 491 std::vector<Event *> stemUpNotes, stemDownNotes; 492 493 // Scan the notes spanned by the slur, recording the top and 494 // bottom heights of the first and last chords, plus the presence 495 // of any troublesome beams and high or low notes in the body. 496 497 while (scooter != staff.getViewElementList()->end()) { 498 499 if ((*scooter)->getViewAbsoluteTime() >= endTime) break; 500 Event *event = (*scooter)->event(); 501 502 if (event->isa(Note::EventType)) { 503 504 long h = 0; 505 if (!event->get<Int>(m_properties.HEIGHT_ON_STAFF, h)) { 506 QMessageBox::warning 507 ( dynamic_cast<QWidget *>(parent()), 508 "", 509 tr("Spanned note at %1 has no HEIGHT_ON_STAFF property!\nThis is a bug (the program would previously have crashed by now)").arg((*scooter)->getViewAbsoluteTime()) 510 ); 511 RG_WARNING << event; 512 } 513 514 bool stemUp = rules.isStemUp(h); 515 event->get 516 <Bool>(m_properties.VIEW_LOCAL_STEM_UP, stemUp); 517 518 bool beamed = false; 519 event->get 520 <Bool>(m_properties.BEAMED, beamed); 521 522 bool primary = false; 523 524 if (event->get 525 <Bool> 526 (m_properties.CHORD_PRIMARY_NOTE, primary) && primary) { 527 528 NotationChord chord(*(staff.getViewElementList()), scooter, 529 m_notationQuantizer, m_properties); 530 531 if (beamed) { 532 if (stemUp) 533 beamAbove = true; 534 else 535 beamBelow = true; 536 } 537 538 if (!haveStart) { 539 540 startBottomHeight = chord.getLowestNoteHeight(); 541 startTopHeight = chord.getHighestNoteHeight(); 542 minBottomHeight = startBottomHeight; 543 maxTopHeight = startTopHeight; 544 545 startX = (int)(*scooter)->getLayoutX(); 546 startStemUp = stemUp; 547 startMarks = chord.getMarkCountForChord(); 548 549 // bool tied = false; 550 // if ((event->get 551 // <Bool>(TIED_FORWARD, tied) && tied) || 552 // (event->get<Bool>(TIED_BACKWARD, tied) && tied)) { 553 // startTied = true; 554 // } 555 556 haveStart = true; 557 558 } else { 559 if (chord.getLowestNoteHeight() < minBottomHeight) { 560 minBottomHeight = chord.getLowestNoteHeight(); 561 ++minCount; 562 } 563 if (chord.getHighestNoteHeight() > maxTopHeight) { 564 maxTopHeight = chord.getHighestNoteHeight(); 565 ++maxCount; 566 } 567 } 568 569 endBottomHeight = chord.getLowestNoteHeight(); 570 endTopHeight = chord.getHighestNoteHeight(); 571 endX = (int)(*scooter)->getLayoutX(); 572 endStemUp = stemUp; 573 // endMarks = chord.getMarkCountForChord(); 574 575 // bool tied = false; 576 // if ((event->get 577 // <Bool>(TIED_FORWARD, tied) && tied) || 578 // (event->get<Bool>(TIED_BACKWARD, tied) && tied)) { 579 // endTied = true; 580 // } 581 } 582 583 if (!beamed) { 584 if (stemUp) 585 stemUpNotes.push_back(event); 586 else 587 stemDownNotes.push_back(event); 588 } 589 590 } else if (event->isa(Indication::EventType)) { 591 592 try { 593 std::string indicationType = 594 event->get 595 <String>(Indication::IndicationTypePropertyName); 596 597 if (indicationType == Indication::Crescendo || 598 indicationType == Indication::Decrescendo) 599 dynamic = true; 600 } catch (...) { } 601 } 602 603 ++scooter; 604 } 605 606 bool above = true; 607 608 if ((*i)->event()->has(NotationProperties::SLUR_ABOVE) && 609 (*i)->event()->isPersistent<Bool>(NotationProperties::SLUR_ABOVE)) { 610 611 (*i)->event()->get 612 <Bool>(NotationProperties::SLUR_ABOVE, above); 613 614 } else if (phrasing) { 615 616 int score = 0; // for "above" 617 618 if (dynamic) 619 score += 2; 620 621 if (startStemUp == endStemUp) { 622 if (startStemUp) 623 score -= 2; 624 else 625 score += 2; 626 } else if (beamBelow != beamAbove) { 627 if (beamAbove) 628 score -= 2; 629 else 630 score += 2; 631 } 632 633 if (maxTopHeight < 6) 634 score += 1; 635 else if (minBottomHeight > 2) 636 score -= 1; 637 638 if (stemUpNotes.size() != stemDownNotes.size()) { 639 if (stemUpNotes.size() < stemDownNotes.size()) 640 score += 1; 641 else 642 score -= 1; 643 } 644 645 above = (score >= 0); 646 647 } else { 648 649 if (startStemUp == endStemUp) { 650 above = !startStemUp; 651 } else if (beamBelow) { 652 above = true; 653 } else if (beamAbove) { 654 above = false; 655 } else if (stemUpNotes.size() != stemDownNotes.size()) { 656 above = (stemUpNotes.size() < stemDownNotes.size()); 657 } else { 658 above = ((startTopHeight - 4) + (endTopHeight - 4) + 659 (4 - startBottomHeight) + (4 - endBottomHeight) <= 8); 660 } 661 } 662 663 // now choose the actual y-coord of the slur based on the side 664 // we've decided to put it on 665 666 int startHeight, endHeight; 667 int startOffset = 2, endOffset = 2; 668 669 if (above) { 670 671 if (!startStemUp) 672 startOffset += startMarks * 2; 673 else 674 startOffset += 5; 675 676 if (!endStemUp) 677 endOffset += startMarks * 2; 678 else 679 endOffset += 5; 680 681 startHeight = startTopHeight + startOffset; 682 endHeight = endTopHeight + endOffset; 683 684 bool maxRelevant = ((maxTopHeight != endTopHeight) || (maxCount > 1)); 685 if (maxRelevant) { 686 int midHeight = (startHeight + endHeight) / 2; 687 if (maxTopHeight > midHeight - 1) { 688 startHeight += maxTopHeight - midHeight + 1; 689 endHeight += maxTopHeight - midHeight + 1; 690 } 691 } 692 693 } else { 694 695 if (startStemUp) 696 startOffset += startMarks * 2; 697 else 698 startOffset += 5; 699 700 if (endStemUp) 701 endOffset += startMarks * 2; 702 else 703 endOffset += 5; 704 705 startHeight = startBottomHeight - startOffset; 706 endHeight = endBottomHeight - endOffset; 707 708 bool minRelevant = ((minBottomHeight != endBottomHeight) || (minCount > 1)); 709 if (minRelevant) { 710 int midHeight = (startHeight + endHeight) / 2; 711 if (minBottomHeight < midHeight + 1) { 712 startHeight -= midHeight - minBottomHeight + 1; 713 endHeight -= midHeight - minBottomHeight + 1; 714 } 715 } 716 } 717 718 int y0 = staff.getLayoutYForHeight(startHeight), 719 y1 = staff.getLayoutYForHeight(endHeight); 720 721 int dy = y1 - y0; 722 int length = endX - startX; 723 int diff = staff.getLayoutYForHeight(0) - staff.getLayoutYForHeight(3); 724 if (length < diff*10) 725 diff /= 2; 726 if (length > diff*3) 727 length -= diff / 2; 728 startX += diff; 729 730 (*i)->event()->setMaybe<Bool>(NotationProperties::SLUR_ABOVE, above); 731 (*i)->event()->setMaybe<Int>(m_properties.SLUR_Y_DELTA, dy); 732 (*i)->event()->setMaybe<Int>(m_properties.SLUR_LENGTH, length); 733 734 double displacedX = 0.0, displacedY = 0.0; 735 736 long dxRaw = 0; 737 (*i)->event()->get<Int>(DISPLACED_X, dxRaw); 738 displacedX = double(dxRaw * m_npf->getNoteBodyWidth()) / 1000.0; 739 740 long dyRaw = 0; 741 (*i)->event()->get<Int>(DISPLACED_Y, dyRaw); 742 displacedY = double(dyRaw * m_npf->getLineSpacing()) / 1000.0; 743 744 (*i)->setLayoutX(startX + displacedX); 745 (*i)->setLayoutY(y0 + displacedY); 746 } 747 748 } 749