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