ad3b5c2dde
Resolves: https://musescore.org/en/node/301496 Alt+Left/Right commands were skipping voltas because we were checking start element and checking against the active staff, but the start element is actually the measure for voltas. The main change here is to go ahead and visit the volta if the active staff if you are navigating the top staff. Arguably, it could make sense to check for the top *visible* staff, since that is what the volta at least appears to be attached to. So I have code here to that. But I disabled it because in practice, neither the navigation commands themsevles nor the screen reader treat invisible staves specially. So a blind user navigating would have no way of knowing the top staff is not visible. So they would likely continue to see it as relevant. I would not the same issue occurs for system text, which we always treat as attached to the top staff only. I added a TODO to indicate where this code would need updating. Eventually we could consider coming up with some way of presenting information about hidden staves. Perhaps in conjunction with a facility allow user to hide staves on specific systems only, which seems to be a fairly common request.
1484 lines
50 KiB
C++
1484 lines
50 KiB
C++
//=============================================================================
|
|
// MuseScore
|
|
// Music Composition & Notation
|
|
//
|
|
// Copyright (C) 2010-2011 Werner Schweer
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License version 2
|
|
// as published by the Free Software Foundation and appearing in
|
|
// the file LICENCE.GPL
|
|
//=============================================================================
|
|
|
|
#include "connector.h"
|
|
#include "score.h"
|
|
#include "spanner.h"
|
|
#include "system.h"
|
|
#include "chordrest.h"
|
|
#include "chord.h"
|
|
#include "segment.h"
|
|
#include "measure.h"
|
|
#include "undo.h"
|
|
#include "staff.h"
|
|
#include "lyrics.h"
|
|
#include "musescoreCore.h"
|
|
|
|
namespace Ms {
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// @@ SpannerWriter
|
|
/// Helper class for writing Spanners
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class SpannerWriter : public ConnectorInfoWriter {
|
|
protected:
|
|
const char* tagName() const override { return "Spanner"; }
|
|
public:
|
|
SpannerWriter(XmlWriter& xml, const Element* current, const Spanner* spanner, int track, Fraction frac, bool start);
|
|
|
|
static void fillSpannerPosition(Location& l, const MeasureBase* endpoint, const Fraction& tick, bool clipboardmode);
|
|
};
|
|
|
|
//---------------------------------------------------------
|
|
// SpannerSegment
|
|
//---------------------------------------------------------
|
|
|
|
SpannerSegment::SpannerSegment(Spanner* sp, Score* s, ElementFlags f)
|
|
: Element(s, f)
|
|
{
|
|
_spanner = sp;
|
|
setSpannerSegmentType(SpannerSegmentType::SINGLE);
|
|
}
|
|
|
|
SpannerSegment::SpannerSegment(Score* s, ElementFlags f)
|
|
: Element(s, f)
|
|
{
|
|
setSpannerSegmentType(SpannerSegmentType::SINGLE);
|
|
_spanner = 0;
|
|
}
|
|
|
|
SpannerSegment::SpannerSegment(const SpannerSegment& s)
|
|
: Element(s)
|
|
{
|
|
_spanner = s._spanner;
|
|
_spannerSegmentType = s._spannerSegmentType;
|
|
_p2 = s._p2;
|
|
_offset2 = s._offset2;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// mag
|
|
//---------------------------------------------------------
|
|
|
|
qreal SpannerSegment::mag() const
|
|
{
|
|
if (spanner()->systemFlag())
|
|
return 1.0;
|
|
return staff() ? staff()->mag(spanner()->tick()) : 1.0;
|
|
}
|
|
|
|
Fraction SpannerSegment::tick() const
|
|
{
|
|
return _spanner ? _spanner->tick() : Fraction(0, 1);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setSystem
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::setSystem(System* s)
|
|
{
|
|
if (system() != s) {
|
|
if (system())
|
|
system()->remove(this);
|
|
if (s)
|
|
s->add(this);
|
|
else
|
|
setParent(0);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// spatiumChanged
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::spatiumChanged(qreal ov, qreal nv)
|
|
{
|
|
Element::spatiumChanged(ov, nv);
|
|
if (sizeIsSpatiumDependent())
|
|
_offset2 *= (nv / ov);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// mimeData
|
|
//---------------------------------------------------------
|
|
|
|
QByteArray SpannerSegment::mimeData(const QPointF& dragOffset) const
|
|
{
|
|
if (dragOffset.isNull()) // where is dragOffset used?
|
|
return spanner()->mimeData(dragOffset);
|
|
return Element::mimeData(dragOffset);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// propertyDelegate
|
|
//---------------------------------------------------------
|
|
|
|
Element* SpannerSegment::propertyDelegate(Pid pid)
|
|
{
|
|
if (pid == Pid::COLOR || pid == Pid::VISIBLE || pid == Pid::PLACEMENT)
|
|
return spanner();
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getProperty
|
|
//---------------------------------------------------------
|
|
|
|
QVariant SpannerSegment::getProperty(Pid pid) const
|
|
{
|
|
if (Element* e = const_cast<SpannerSegment*>(this)->propertyDelegate(pid))
|
|
return e->getProperty(pid);
|
|
switch (pid) {
|
|
case Pid::OFFSET2:
|
|
return _offset2;
|
|
default:
|
|
return Element::getProperty(pid);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setProperty
|
|
//---------------------------------------------------------
|
|
|
|
bool SpannerSegment::setProperty(Pid pid, const QVariant& v)
|
|
{
|
|
if (Element* e = propertyDelegate(pid))
|
|
return e->setProperty(pid, v);
|
|
switch (pid) {
|
|
case Pid::OFFSET2:
|
|
_offset2 = v.toPointF();
|
|
triggerLayoutAll();
|
|
break;
|
|
default:
|
|
return Element::setProperty(pid, v);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// propertyDefault
|
|
//---------------------------------------------------------
|
|
|
|
QVariant SpannerSegment::propertyDefault(Pid pid) const
|
|
{
|
|
if (Element* e = const_cast<SpannerSegment*>(this)->propertyDelegate(pid))
|
|
return e->propertyDefault(pid);
|
|
switch (pid) {
|
|
case Pid::OFFSET2:
|
|
return QVariant();
|
|
default:
|
|
return Element::propertyDefault(pid);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getPropertyStyle
|
|
//---------------------------------------------------------
|
|
|
|
Sid SpannerSegment::getPropertyStyle(Pid pid) const
|
|
{
|
|
if (Element* e = const_cast<SpannerSegment*>(this)->propertyDelegate(pid))
|
|
return e->getPropertyStyle(pid);
|
|
return Element::getPropertyStyle(pid);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// propertyFlags
|
|
//---------------------------------------------------------
|
|
|
|
PropertyFlags SpannerSegment::propertyFlags(Pid pid) const
|
|
{
|
|
if (Element* e = const_cast<SpannerSegment*>(this)->propertyDelegate(pid))
|
|
return e->propertyFlags(pid);
|
|
return Element::propertyFlags(pid);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// resetProperty
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::resetProperty(Pid pid)
|
|
{
|
|
if (Element* e = propertyDelegate(pid))
|
|
return e->resetProperty(pid);
|
|
return Element::resetProperty(pid);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// styleChanged
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::styleChanged()
|
|
{
|
|
spanner()->styleChanged();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// reset
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::reset()
|
|
{
|
|
undoChangeProperty(Pid::OFFSET2, QPointF());
|
|
Element::reset();
|
|
spanner()->reset();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// undoChangeProperty
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::undoChangeProperty(Pid pid, const QVariant& val, PropertyFlags ps)
|
|
{
|
|
if (pid == Pid::AUTOPLACE && (val.toBool() == true && !autoplace())) {
|
|
// Switching autoplacement on. Save user-defined
|
|
// placement properties to undo stack.
|
|
undoPushProperty(Pid::OFFSET2);
|
|
// other will be saved in Element::undoChangeProperty
|
|
}
|
|
Element::undoChangeProperty(pid, val, ps);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setSelected
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::setSelected(bool f)
|
|
{
|
|
for (SpannerSegment* ss : _spanner->spannerSegments())
|
|
ss->Element::setSelected(f);
|
|
_spanner->setSelected(f);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setVisible
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::setVisible(bool f)
|
|
{
|
|
if (_spanner) {
|
|
for (SpannerSegment* ss : _spanner->spannerSegments())
|
|
ss->Element::setVisible(f);
|
|
_spanner->setVisible(f);
|
|
}
|
|
else
|
|
Element::setVisible(f);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setColor
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::setColor(const QColor& col)
|
|
{
|
|
if (_spanner) {
|
|
for (SpannerSegment* ss : _spanner->spannerSegments())
|
|
ss->_color = col;
|
|
_spanner->_color = col;
|
|
}
|
|
else
|
|
_color = col;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// nextSegmentElement
|
|
//---------------------------------------------------------
|
|
|
|
Element* SpannerSegment::nextSegmentElement()
|
|
{
|
|
return spanner()->nextSegmentElement();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// prevSegmentElement
|
|
//---------------------------------------------------------
|
|
|
|
Element* SpannerSegment::prevSegmentElement()
|
|
{
|
|
return spanner()->prevSegmentElement();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// accessibleInfo
|
|
//---------------------------------------------------------
|
|
|
|
QString SpannerSegment::accessibleInfo() const
|
|
{
|
|
return spanner()->accessibleInfo();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// triggerLayout
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::triggerLayout() const
|
|
{
|
|
if (_spanner)
|
|
_spanner->triggerLayout();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Spanner
|
|
//---------------------------------------------------------
|
|
|
|
Spanner::Spanner(Score* s, ElementFlags f)
|
|
: Element(s, f)
|
|
{
|
|
}
|
|
|
|
Spanner::Spanner(const Spanner& s)
|
|
: Element(s)
|
|
{
|
|
_anchor = s._anchor;
|
|
_startElement = s._startElement;
|
|
_endElement = s._endElement;
|
|
_tick = s._tick;
|
|
_ticks = s._ticks;
|
|
_track2 = s._track2;
|
|
}
|
|
|
|
Spanner::~Spanner()
|
|
{
|
|
qDeleteAll(segments);
|
|
qDeleteAll(unusedSegments);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// mag
|
|
//---------------------------------------------------------
|
|
|
|
qreal Spanner::mag() const
|
|
{
|
|
if (systemFlag())
|
|
return 1.0;
|
|
return staff() ? staff()->mag(tick()) : 1.0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// add
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::add(Element* e)
|
|
{
|
|
SpannerSegment* ls = toSpannerSegment(e);
|
|
ls->setSpanner(this);
|
|
ls->setSelected(selected());
|
|
ls->setTrack(track());
|
|
// ls->setAutoplace(autoplace());
|
|
segments.push_back(ls);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// remove
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::remove(Element* e)
|
|
{
|
|
SpannerSegment* ss = toSpannerSegment(e);
|
|
if (ss->system())
|
|
ss->system()->remove(ss);
|
|
segments.erase(std::remove(segments.begin(), segments.end(), ss), segments.end());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// removeUnmanaged
|
|
//
|
|
// Remove the Spanner and its segments from objects which may know about them
|
|
//
|
|
// This method and the following are used for spanners which are contained within compound elements
|
|
// which manage their parts themselves without using the standard management supplied by Score;
|
|
// Example can be the LyricsLine within a Lyrics element or the FiguredBassLine within a FiguredBass
|
|
// (not implemented yet).
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::removeUnmanaged()
|
|
{
|
|
for (SpannerSegment* ss : spannerSegments())
|
|
if (ss->system()) {
|
|
// ss->system()->remove(ss);
|
|
ss->setSystem(nullptr);
|
|
}
|
|
score()->removeUnmanagedSpanner(this);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// insertTimeUnmanaged
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::insertTimeUnmanaged(const Fraction& fromTick, const Fraction& len)
|
|
{
|
|
Fraction newTick1 = tick();
|
|
Fraction newTick2 = tick2();
|
|
|
|
// check spanner start and end point
|
|
if (len > Fraction(0,1)) { // adding time
|
|
if (tick() > fromTick) // start after insertion point: shift start to right
|
|
newTick1 += len;
|
|
if (tick2() > fromTick) // end after insertion point: shift end to right
|
|
newTick2 += len;
|
|
}
|
|
if (len < Fraction(0,1)) { // removing time
|
|
Fraction toTick = fromTick - len;
|
|
if (tick() > fromTick) { // start after beginning of removed time
|
|
if (tick() < toTick) { // start within removed time: bring start at removing point
|
|
if (parent()) {
|
|
parent()->remove(this);
|
|
return;
|
|
}
|
|
else
|
|
newTick1 = fromTick;
|
|
}
|
|
else // start after removed time: shift start to left
|
|
newTick1 += len;
|
|
}
|
|
if (tick2() > fromTick) { // end after start of removed time
|
|
if (tick2() < toTick) // end within removed time: bring end at removing point
|
|
newTick2 = fromTick;
|
|
else // end after removed time: shift end to left
|
|
newTick2 += len;
|
|
}
|
|
}
|
|
|
|
// update properties as required
|
|
if (newTick2 <= newTick1) { // if no longer any span: remove it
|
|
if (parent())
|
|
parent()->remove(this);
|
|
}
|
|
else { // if either TICKS or TICK did change, update property
|
|
if (newTick2-newTick1 != tick2()- tick())
|
|
setProperty(Pid::SPANNER_TICKS, newTick2-newTick1);
|
|
if (newTick1 != tick())
|
|
setProperty(Pid::SPANNER_TICK, newTick1);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// scanElements
|
|
// used in palettes
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::scanElements(void* data, void (*func)(void*, Element*), bool all)
|
|
{
|
|
Q_UNUSED(all);
|
|
for (SpannerSegment* seg : segments)
|
|
seg->scanElements(data, func, true);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setScore
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setScore(Score* s)
|
|
{
|
|
Element::setScore(s);
|
|
foreach(SpannerSegment* seg, segments)
|
|
seg->setScore(s);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getProperty
|
|
//---------------------------------------------------------
|
|
|
|
QVariant Spanner::getProperty(Pid propertyId) const
|
|
{
|
|
switch (propertyId) {
|
|
case Pid::SPANNER_TICK:
|
|
return _tick;
|
|
case Pid::SPANNER_TICKS:
|
|
return _ticks;
|
|
case Pid::SPANNER_TRACK2:
|
|
return track2();
|
|
case Pid::ANCHOR:
|
|
return int(anchor());
|
|
case Pid::LOCATION_STAVES:
|
|
return (track2() / VOICES) - (track() / VOICES);
|
|
case Pid::LOCATION_VOICES:
|
|
return (track2() % VOICES) - (track() / VOICES);
|
|
case Pid::LOCATION_FRACTIONS:
|
|
return _ticks;
|
|
case Pid::LOCATION_MEASURES:
|
|
case Pid::LOCATION_GRACE:
|
|
case Pid::LOCATION_NOTE:
|
|
return Location::getLocationProperty(propertyId, startElement(), endElement());
|
|
default:
|
|
break;
|
|
}
|
|
return Element::getProperty(propertyId);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setProperty
|
|
//---------------------------------------------------------
|
|
|
|
bool Spanner::setProperty(Pid propertyId, const QVariant& v)
|
|
{
|
|
switch (propertyId) {
|
|
case Pid::SPANNER_TICK:
|
|
setTick(v.value<Fraction>());
|
|
setStartElement(0); // invalidate
|
|
setEndElement(0); //
|
|
if (score() && score()->spannerMap().removeSpanner(this))
|
|
score()->addSpanner(this);
|
|
break;
|
|
case Pid::SPANNER_TICKS:
|
|
setTicks(v.value<Fraction>());
|
|
setEndElement(0); // invalidate
|
|
break;
|
|
case Pid::TRACK:
|
|
setTrack(v.toInt());
|
|
setStartElement(0); // invalidate
|
|
break;
|
|
case Pid::SPANNER_TRACK2:
|
|
setTrack2(v.toInt());
|
|
setEndElement(0); // invalidate
|
|
break;
|
|
case Pid::ANCHOR:
|
|
setAnchor(Anchor(v.toInt()));
|
|
break;
|
|
default:
|
|
return Element::setProperty(propertyId, v);
|
|
}
|
|
triggerLayout();
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// propertyDefault
|
|
//---------------------------------------------------------
|
|
|
|
QVariant Spanner::propertyDefault(Pid propertyId) const
|
|
{
|
|
switch (propertyId) {
|
|
case Pid::ANCHOR:
|
|
return int(Anchor::SEGMENT);
|
|
default:
|
|
break;
|
|
}
|
|
return Element::propertyDefault(propertyId);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// computeStartElement
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::computeStartElement()
|
|
{
|
|
switch (_anchor) {
|
|
case Anchor::SEGMENT: {
|
|
Segment* seg = score()->tick2segmentMM(tick(), false, SegmentType::ChordRest);
|
|
int strack = (track() / VOICES) * VOICES;
|
|
int etrack = strack + VOICES;
|
|
_startElement = 0;
|
|
if (seg) {
|
|
for (int t = strack; t < etrack; ++t) {
|
|
if (seg->element(t)) {
|
|
_startElement = seg->element(t);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Anchor::MEASURE:
|
|
_startElement = score()->tick2measure(tick());
|
|
break;
|
|
|
|
case Anchor::CHORD:
|
|
case Anchor::NOTE:
|
|
return;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// computeEndElement
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::computeEndElement()
|
|
{
|
|
if (score()->isPalette()) {
|
|
// return immediately to prevent lots of
|
|
// "no element found" messages from appearing
|
|
_endElement = nullptr;
|
|
return;
|
|
}
|
|
|
|
switch (_anchor) {
|
|
case Anchor::SEGMENT: {
|
|
if (track2() == -1)
|
|
setTrack2(track());
|
|
if (ticks().isZero() && isTextLine() && parent()) // special case palette
|
|
setTicks(score()->lastSegment()->tick() - _tick);
|
|
|
|
if (isLyricsLine() && toLyricsLine(this)->isEndMelisma()) {
|
|
// lyrics endTick should already indicate the segment we want
|
|
// except for TEMP_MELISMA_TICKS case
|
|
Lyrics* l = toLyricsLine(this)->lyrics();
|
|
Fraction tick = (l->ticks().ticks() == Lyrics::TEMP_MELISMA_TICKS) ? l->tick() : l->endTick();
|
|
Segment* s = score()->tick2segment(tick, true, SegmentType::ChordRest);
|
|
if (!s) {
|
|
qDebug("%s no end segment for tick %d", name(), tick.ticks());
|
|
return;
|
|
}
|
|
int t = trackZeroVoice(track2());
|
|
// take the first chordrest we can find;
|
|
// linePos will substitute one in current voice if available
|
|
for (int v = 0; v < VOICES; ++v) {
|
|
_endElement = s->element(t + v);
|
|
if (_endElement)
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
// find last cr on this staff that ends before tick2
|
|
_endElement = score()->findCRinStaff(tick2(), track2() / VOICES);
|
|
}
|
|
if (!_endElement) {
|
|
qDebug("%s no end element for tick %d", name(), tick2().ticks());
|
|
return;
|
|
}
|
|
|
|
if (!endCR()->measure()->isMMRest()) {
|
|
ChordRest* cr = endCR();
|
|
Fraction nticks = cr->tick() + cr->actualTicks() - _tick;
|
|
if ((_ticks - nticks).isNotZero()) {
|
|
qDebug("%s ticks changed, %d -> %d", name(), _ticks.ticks(), nticks.ticks());
|
|
setTicks(nticks);
|
|
if (isOttava())
|
|
staff()->updateOttava();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Anchor::MEASURE:
|
|
_endElement = score()->tick2measure(tick2() - Fraction(1, 1920));
|
|
if (!_endElement) {
|
|
qDebug("Spanner::computeEndElement(), measure not found for tick %d\n", tick2().ticks()-1);
|
|
_endElement = score()->lastMeasure();
|
|
}
|
|
break;
|
|
|
|
case Anchor::CHORD:
|
|
case Anchor::NOTE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// startElementFromSpanner
|
|
//
|
|
// Given a Spanner and an end element, determines a start element suitable for the end
|
|
// element of a new Spanner, so that it is 'parallel' to the old one.
|
|
// Can be used while cloning a linked Spanner, to update the cloned spanner start and end elements
|
|
// (Spanner(const Spanner&) copies start and end elements from the original to the copy).
|
|
// NOTES: Only spanners with Anchor::NOTE are currently supported.
|
|
// Going back from end to start ensures the 'other' anchor of this is already set up
|
|
// (for instance, while cloning staves)
|
|
//---------------------------------------------------------
|
|
|
|
Note* Spanner::startElementFromSpanner(Spanner* sp, Element* newEnd)
|
|
{
|
|
if (sp->anchor() != Anchor::NOTE)
|
|
return nullptr;
|
|
|
|
Note* oldStart = toNote(sp->startElement());
|
|
Note* oldEnd = toNote(sp->endElement());
|
|
if (oldStart == nullptr || oldEnd == nullptr)
|
|
return nullptr;
|
|
Note* newStart = nullptr;
|
|
Score* score = newEnd->score();
|
|
// determine the track where to expect the 'parallel' start element
|
|
int newTrack = (newEnd->track() - oldEnd->track()) + oldStart->track();
|
|
// look in notes linked to oldStart for a note with the
|
|
// same score as new score and appropriate track
|
|
for (ScoreElement* newEl : oldStart->linkList())
|
|
if (toNote(newEl)->score() == score && toNote(newEl)->track() == newTrack) {
|
|
newStart = toNote(newEl);
|
|
break;
|
|
}
|
|
return newStart;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// endElementFromSpanner
|
|
//
|
|
// Given a Spanner and a start element, determines an end element suitable for the start
|
|
// element of a new Spanner, so that it is 'parallel' to the old one.
|
|
// Can be used while cloning a linked Spanner, to update the cloned spanner start and end elements
|
|
// (Spanner(const Spanner&) copies start and end elements from the original to the copy).
|
|
// NOTES: Only spanners with Anchor::NOTE are currently supported.
|
|
//---------------------------------------------------------
|
|
|
|
Note* Spanner::endElementFromSpanner(Spanner* sp, Element* newStart)
|
|
{
|
|
if (sp->anchor() != Anchor::NOTE)
|
|
return nullptr;
|
|
|
|
Note* oldStart = toNote(sp->startElement());
|
|
Note* oldEnd = toNote(sp->endElement());
|
|
if (oldStart == nullptr || oldEnd == nullptr)
|
|
return nullptr;
|
|
Note* newEnd = nullptr;
|
|
Score* score = newStart->score();
|
|
// determine the track where to expect the 'parallel' start element
|
|
int newTrack = newStart->track() + (oldEnd->track() - oldStart->track());
|
|
// look in notes linked to oldEnd for a note with the
|
|
// same score as new score and appropriate track
|
|
for (ScoreElement* newEl : oldEnd->linkList())
|
|
if (toNote(newEl)->score() == score && toNote(newEl)->track() == newTrack) {
|
|
newEnd = toNote(newEl);
|
|
break;
|
|
}
|
|
return newEnd;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setNoteSpan
|
|
//
|
|
// Sets up all the variables congruent with given start and end note anchors.
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setNoteSpan(Note* startNote, Note* endNote)
|
|
{
|
|
if (_anchor != Anchor::NOTE)
|
|
return;
|
|
|
|
setScore(startNote->score());
|
|
setParent(startNote);
|
|
setStartElement(startNote);
|
|
setEndElement(endNote);
|
|
setTick(startNote->chord()->tick());
|
|
setTick2(endNote->chord()->tick());
|
|
setTrack(startNote->track());
|
|
setTrack2(endNote->track());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// startChord
|
|
//---------------------------------------------------------
|
|
|
|
Chord* Spanner::startChord()
|
|
{
|
|
Q_ASSERT(_anchor == Anchor::CHORD);
|
|
if (!_startElement)
|
|
_startElement = score()->findCR(tick(), track());
|
|
return toChord(_startElement);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// endChord
|
|
//---------------------------------------------------------
|
|
|
|
Chord* Spanner::endChord()
|
|
{
|
|
Q_ASSERT(_anchor == Anchor::CHORD);
|
|
|
|
if (!_endElement && type() == ElementType::SLUR) {
|
|
Segment* s = score()->tick2segmentMM(tick2(), false, SegmentType::ChordRest);
|
|
_endElement = s ? toChordRest(s->element(track2())) : nullptr;
|
|
if (!_endElement->isChord())
|
|
_endElement = nullptr;
|
|
}
|
|
return toChord(_endElement);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// startCR
|
|
//---------------------------------------------------------
|
|
|
|
ChordRest* Spanner::startCR()
|
|
{
|
|
Q_ASSERT(_anchor == Anchor::SEGMENT || _anchor == Anchor::CHORD);
|
|
if (!_startElement || _startElement->score() != score())
|
|
_startElement = score()->findCR(tick(), track());
|
|
return toChordRest(_startElement);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// endCR
|
|
//---------------------------------------------------------
|
|
|
|
ChordRest* Spanner::endCR()
|
|
{
|
|
Q_ASSERT(_anchor == Anchor::SEGMENT || _anchor == Anchor::CHORD);
|
|
if ((!_endElement || _endElement->score() != score())) {
|
|
Segment* s = score()->tick2segmentMM(tick2(), false, SegmentType::ChordRest);
|
|
const int tr2 = effectiveTrack2();
|
|
_endElement = s ? toChordRest(s->element(tr2)) : nullptr;
|
|
}
|
|
return toChordRest(_endElement);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// startSegment
|
|
//---------------------------------------------------------
|
|
|
|
Segment* Spanner::startSegment() const
|
|
{
|
|
Q_ASSERT(score() != NULL);
|
|
return score()->tick2rightSegment(tick());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// endSegment
|
|
//---------------------------------------------------------
|
|
|
|
Segment* Spanner::endSegment() const
|
|
{
|
|
return score()->tick2leftSegment(tick2());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// startMeasure
|
|
//---------------------------------------------------------
|
|
|
|
Measure* Spanner::startMeasure() const
|
|
{
|
|
return toMeasure(_startElement);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// endMeasure
|
|
//---------------------------------------------------------
|
|
|
|
Measure* Spanner::endMeasure() const
|
|
{
|
|
return toMeasure(_endElement);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setSelected
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setSelected(bool f)
|
|
{
|
|
for (SpannerSegment* ss : spannerSegments())
|
|
ss->Element::setSelected(f);
|
|
Element::setSelected(f);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setVisible
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setVisible(bool f)
|
|
{
|
|
for (SpannerSegment* ss : spannerSegments())
|
|
ss->Element::setVisible(f);
|
|
Element::setVisible(f);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setAutoplace
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setAutoplace(bool f)
|
|
{
|
|
for (SpannerSegment* ss : spannerSegments())
|
|
ss->Element::setAutoplace(f);
|
|
Element::setAutoplace(f);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setColor
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setColor(const QColor& col)
|
|
{
|
|
for (SpannerSegment* ss : spannerSegments())
|
|
ss->setColor(col);
|
|
_color = col;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setStartElement
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setStartElement(Element* e)
|
|
{
|
|
#ifndef NDEBUG
|
|
if (_anchor == Anchor::NOTE)
|
|
Q_ASSERT(!e || e->type() == ElementType::NOTE);
|
|
#endif
|
|
_startElement = e;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setEndElement
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setEndElement(Element* e)
|
|
{
|
|
#ifndef NDEBUG
|
|
if (_anchor == Anchor::NOTE)
|
|
Q_ASSERT(!e || e->type() == ElementType::NOTE);
|
|
#endif
|
|
_endElement = e;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// nextSpanner
|
|
//---------------------------------------------------------
|
|
|
|
Spanner* Spanner::nextSpanner(Element* e, int activeStaff)
|
|
{
|
|
std::multimap<int, Spanner*> mmap = score()->spanner();
|
|
auto range = mmap.equal_range(tick().ticks());
|
|
if (range.first != range.second) { // range not empty
|
|
for (auto i = range.first; i != range.second; ++i) {
|
|
if (i->second == e) {
|
|
while (i != range.second) {
|
|
++i;
|
|
if (i == range.second)
|
|
return nullptr;
|
|
Spanner* s = i->second;
|
|
Element* st = s->startElement();
|
|
if (!st)
|
|
continue;
|
|
if (s->startSegment() == toSpanner(e)->startSegment()) {
|
|
if (st->staffIdx() == activeStaff)
|
|
return s;
|
|
#if 1
|
|
else if (st->isMeasure() && activeStaff == 0)
|
|
return s;
|
|
#else
|
|
// TODO: when navigating system spanners, check firstVisibleStaff()?
|
|
// currently, information about which staves are hidden
|
|
// is not exposed through navigation,
|
|
// so it may make more sense to continue to navigate systems elements
|
|
// only when actually on staff 0
|
|
// see also https://musescore.org/en/node/301496
|
|
// and https://github.com/musescore/MuseScore/pull/5755
|
|
else if (st->isMeasure()) {
|
|
SpannerSegment* ss = s->frontSegment();
|
|
int top = ss && ss->system() ? ss->system()->firstVisibleStaff() : 0;
|
|
if (activeStaff == top)
|
|
return s;
|
|
}
|
|
#endif
|
|
}
|
|
//else
|
|
//return nullptr;
|
|
}
|
|
break;
|
|
/* else {
|
|
break;
|
|
}*/
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// prevSpanner
|
|
//---------------------------------------------------------
|
|
|
|
Spanner* Spanner::prevSpanner(Element* e, int activeStaff)
|
|
{
|
|
std::multimap<int, Spanner*> mmap = score()->spanner();
|
|
auto range = mmap.equal_range(tick().ticks());
|
|
if (range.first != range.second) { // range not empty
|
|
for (auto i = range.first; i != range.second; ++i) {
|
|
if (i->second == e) {
|
|
if (i == range.first)
|
|
return nullptr;
|
|
while (i != range.first) {
|
|
--i;
|
|
Spanner* s = i->second;
|
|
Element* st = s->startElement();
|
|
if (s->startSegment() == toSpanner(e)->startSegment()) {
|
|
if (st->staffIdx() == activeStaff)
|
|
return s;
|
|
#if 1
|
|
else if (st->isMeasure() && activeStaff == 0)
|
|
return s;
|
|
#else
|
|
// TODO: see nextSpanner()
|
|
else if (st->isMeasure()) {
|
|
SpannerSegment* ss = s->frontSegment();
|
|
int top = ss && ss->system() ? ss->system()->firstVisibleStaff() : 0;
|
|
if (activeStaff == top)
|
|
return s;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// nextSegmentElement
|
|
//---------------------------------------------------------
|
|
|
|
Element* Spanner::nextSegmentElement()
|
|
{
|
|
Segment* s = startSegment();
|
|
if (s)
|
|
return s->firstElement(staffIdx());
|
|
return score()->lastElement();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// prevSegmentElement
|
|
//---------------------------------------------------------
|
|
|
|
Element* Spanner::prevSegmentElement()
|
|
{
|
|
Segment* s = endSegment();
|
|
if (s)
|
|
return s->lastElement(staffIdx());
|
|
return score()->firstElement();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setTick
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setTick(const Fraction& v)
|
|
{
|
|
_tick = v;
|
|
if (score())
|
|
score()->spannerMap().setDirty();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setTick2
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setTick2(const Fraction& f)
|
|
{
|
|
setTicks(f - _tick);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setTicks
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::setTicks(const Fraction& f)
|
|
{
|
|
_ticks = f;
|
|
if (score())
|
|
score()->spannerMap().setDirty();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// triggerLayout
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::triggerLayout() const
|
|
{
|
|
// Spanners do not have parent even when added to a score, so can't check parent here
|
|
const int tr2 = effectiveTrack2();
|
|
score()->setLayout(_tick, _tick + _ticks, staffIdx(), track2staff(tr2), this);
|
|
}
|
|
|
|
void Spanner::triggerLayoutAll() const
|
|
{
|
|
// Spanners do not have parent even when added to a score, so can't check parent here
|
|
score()->setLayoutAll(staffIdx(), this);
|
|
|
|
const int tr2 = track2();
|
|
if (tr2 != -1 && tr2 != track())
|
|
score()->setLayoutAll(track2staff(tr2), this);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// pushUnusedSegment
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::pushUnusedSegment(SpannerSegment* seg)
|
|
{
|
|
if (!seg)
|
|
return;
|
|
seg->setSystem(nullptr);
|
|
unusedSegments.push_back(seg);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// popUnusedSegment
|
|
// Take the next unused segment for reusing it.
|
|
// If there is no unused segments left returns nullptr.
|
|
//---------------------------------------------------------
|
|
|
|
SpannerSegment* Spanner::popUnusedSegment()
|
|
{
|
|
if (unusedSegments.empty())
|
|
return nullptr;
|
|
SpannerSegment* seg = unusedSegments.front();
|
|
unusedSegments.pop_front();
|
|
return seg;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// reuse
|
|
// called when segment from unusedSegments is added
|
|
// back to the spanner.
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::reuse(SpannerSegment* seg)
|
|
{
|
|
add(seg);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// reuseSegments
|
|
// Adds \p number segments from unusedSegments to this
|
|
// spanner via reuse() call. Returns number of new
|
|
// segments that still need to be created, that is,
|
|
// returns (number - nMovedSegments).
|
|
//---------------------------------------------------------
|
|
|
|
int Spanner::reuseSegments(int number)
|
|
{
|
|
while (number > 0) {
|
|
SpannerSegment* seg = popUnusedSegment();
|
|
if (!seg)
|
|
break;
|
|
reuse(seg);
|
|
--number;
|
|
}
|
|
return number;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// fixupSegments
|
|
// Makes number of segments match targetNumber.
|
|
// Tries to reuse unused segments. If there are no
|
|
// unused segments left, uses \p createSegment to create
|
|
// the needed segments.
|
|
// Previously unused segments are added via reuse() call
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::fixupSegments(unsigned int targetNumber, std::function<SpannerSegment*()> createSegment)
|
|
{
|
|
const int diff = targetNumber - int(nsegments());
|
|
if (diff == 0)
|
|
return;
|
|
if (diff > 0) {
|
|
const int ncreate = reuseSegments(diff);
|
|
for (int i = 0; i < ncreate; ++i)
|
|
add(createSegment());
|
|
}
|
|
else { // diff < 0
|
|
const int nremove = -diff;
|
|
for (int i = 0; i < nremove; ++i) {
|
|
SpannerSegment* seg = segments.back();
|
|
segments.pop_back();
|
|
pushUnusedSegment(seg);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// eraseSpannerSegments
|
|
// Completely erase all spanner segments, both used and
|
|
// unused.
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::eraseSpannerSegments()
|
|
{
|
|
qDeleteAll(segments);
|
|
qDeleteAll(unusedSegments);
|
|
segments.clear();
|
|
unusedSegments.clear();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layoutSystem
|
|
//---------------------------------------------------------
|
|
|
|
SpannerSegment* Spanner::layoutSystem(System*)
|
|
{
|
|
qDebug(" %s", name());
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getNextLayoutSystemSegment
|
|
//---------------------------------------------------------
|
|
|
|
SpannerSegment* Spanner::getNextLayoutSystemSegment(System* system, std::function<SpannerSegment*()> createSegment)
|
|
{
|
|
SpannerSegment* seg = nullptr;
|
|
for (SpannerSegment* ss : spannerSegments()) {
|
|
if (!ss->system()) {
|
|
seg = ss;
|
|
break;
|
|
}
|
|
}
|
|
if (!seg) {
|
|
if ((seg = popUnusedSegment()))
|
|
reuse(seg);
|
|
else {
|
|
seg = createSegment();
|
|
Q_ASSERT(seg);
|
|
add(seg);
|
|
}
|
|
}
|
|
seg->setSystem(system);
|
|
seg->setSpanner(this);
|
|
seg->setTrack(track());
|
|
seg->setVisible(visible());
|
|
return seg;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layoutSystemsDone
|
|
// Called after layout of all systems is done so precise
|
|
// number of systems for this spanner becomes available.
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::layoutSystemsDone()
|
|
{
|
|
std::vector<SpannerSegment*> validSegments;
|
|
for (SpannerSegment* seg : segments) {
|
|
if (seg->system())
|
|
validSegments.push_back(seg);
|
|
else // TODO: score()->selection().remove(ss); needed?
|
|
pushUnusedSegment(seg);
|
|
}
|
|
segments = std::move(validSegments);
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// fraction
|
|
//---------------------------------------------------------
|
|
|
|
static Fraction fraction(const XmlWriter& xml, const Element* current, const Fraction& t)
|
|
{
|
|
Fraction tick(t);
|
|
if (!xml.clipboardmode()) {
|
|
const Measure* m = toMeasure(current->findMeasure());
|
|
if (m)
|
|
tick -= m->tick();
|
|
}
|
|
return tick;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Spanner::readProperties
|
|
//---------------------------------------------------------
|
|
|
|
bool Spanner::readProperties(XmlReader& e)
|
|
{
|
|
const QStringRef tag(e.name());
|
|
if (e.pasteMode()) {
|
|
if (tag == "ticks_f") {
|
|
setTicks(e.readFraction());
|
|
return true;
|
|
}
|
|
}
|
|
return Element::readProperties(e);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Spanner::writeProperties
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::writeProperties(XmlWriter& xml) const
|
|
{
|
|
if (xml.clipboardmode())
|
|
xml.tag("ticks_f", ticks());
|
|
Element::writeProperties(xml);
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// Spanner::writeSpannerStart
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::writeSpannerStart(XmlWriter& xml, const Element* current, int track, Fraction tick) const
|
|
{
|
|
Fraction frac = fraction(xml, current, tick);
|
|
SpannerWriter w(xml, current, this, track, frac, true);
|
|
w.write();
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// Spanner::writeSpannerEnd
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::writeSpannerEnd(XmlWriter& xml, const Element* current, int track, Fraction tick) const
|
|
{
|
|
Fraction frac = fraction(xml, current, tick);
|
|
SpannerWriter w(xml, current, this, track, frac, false);
|
|
w.write();
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// Spanner::readSpanner
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::readSpanner(XmlReader& e, Element* current, int track)
|
|
{
|
|
std::unique_ptr<ConnectorInfoReader> info(new ConnectorInfoReader(e, current, track));
|
|
ConnectorInfoReader::readConnector(std::move(info), e);
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// Spanner::readSpanner
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::readSpanner(XmlReader& e, Score* current, int track)
|
|
{
|
|
std::unique_ptr<ConnectorInfoReader> info(new ConnectorInfoReader(e, current, track));
|
|
ConnectorInfoReader::readConnector(std::move(info), e);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// SpannerWriter::fillSpannerPosition
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerWriter::fillSpannerPosition(Location& l, const MeasureBase* m, const Fraction& tick, bool clipboardmode)
|
|
{
|
|
if (clipboardmode) {
|
|
l.setMeasure(0);
|
|
l.setFrac(tick);
|
|
}
|
|
else {
|
|
if (!m) {
|
|
qWarning("fillSpannerPosition: couldn't find spanner's endpoint's measure");
|
|
l.setMeasure(0);
|
|
l.setFrac(tick);
|
|
return;
|
|
}
|
|
l.setMeasure(m->measureIndex());
|
|
l.setFrac(tick - m->tick());
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// SpannerWriter::SpannerWriter
|
|
//---------------------------------------------------------
|
|
|
|
SpannerWriter::SpannerWriter(XmlWriter& xml, const Element* current, const Spanner* sp, int track, Fraction frac, bool start)
|
|
: ConnectorInfoWriter(xml, current, sp, track, frac)
|
|
{
|
|
const bool clipboardmode = xml.clipboardmode();
|
|
if (!sp->startElement() || !sp->endElement()) {
|
|
qWarning("SpannerWriter: spanner (%s) doesn't have an endpoint!", sp->name());
|
|
return;
|
|
}
|
|
if (current->isMeasure() || current->isSegment() || (sp->startElement()->type() != current->type())) {
|
|
// (The latter is the hairpins' case, for example, though they are
|
|
// covered by the other checks too.)
|
|
// We cannot determine position of the spanner from its start/end
|
|
// elements and will try to obtain this info from the spanner itself.
|
|
if (!start) {
|
|
_prevLoc.setTrack(sp->track());
|
|
Measure* m = sp->score()->tick2measure(sp->tick());
|
|
fillSpannerPosition(_prevLoc, m, sp->tick(), clipboardmode);
|
|
}
|
|
else {
|
|
const int track2 = (sp->track2() != -1) ? sp->track2() : sp->track();
|
|
_nextLoc.setTrack(track2);
|
|
Measure* m = sp->score()->tick2measure(sp->tick2());
|
|
fillSpannerPosition(_nextLoc, m, sp->tick2(), clipboardmode);
|
|
}
|
|
}
|
|
else {
|
|
// We can obtain the spanner position info from its start/end
|
|
// elements and will prefer this source of information.
|
|
// Reason: some spanners contain no or wrong information (e.g. Ties).
|
|
if (!start)
|
|
updateLocation(sp->startElement(), _prevLoc, clipboardmode);
|
|
else
|
|
updateLocation(sp->endElement(), _nextLoc, clipboardmode);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// autoplaceSpannerSegment
|
|
//---------------------------------------------------------
|
|
|
|
void SpannerSegment::autoplaceSpannerSegment()
|
|
{
|
|
if (!parent()) {
|
|
setOffset(QPointF());
|
|
return;
|
|
}
|
|
if (isStyled(Pid::OFFSET))
|
|
setOffset(spanner()->propertyDefault(Pid::OFFSET).toPointF());
|
|
|
|
if (spanner()->anchor() == Spanner::Anchor::NOTE)
|
|
return;
|
|
|
|
// rebase vertical offset on drag
|
|
qreal rebase = 0.0;
|
|
if (offsetChanged() != OffsetChange::NONE)
|
|
rebase = rebaseOffset();
|
|
|
|
if (autoplace()) {
|
|
qreal sp = score()->spatium();
|
|
if (!systemFlag() && !spanner()->systemFlag())
|
|
sp *= staff()->mag(spanner()->tick());
|
|
qreal md = minDistance().val() * sp;
|
|
bool above = spanner()->placeAbove();
|
|
SkylineLine sl(!above);
|
|
Shape sh = shape();
|
|
sl.add(sh.translated(pos()));
|
|
qreal yd = 0.0;
|
|
if (above) {
|
|
qreal d = system()->topDistance(staffIdx(), sl);
|
|
if (d > -md)
|
|
yd = -(d + md);
|
|
}
|
|
else {
|
|
qreal d = system()->bottomDistance(staffIdx(), sl);
|
|
if (d > -md)
|
|
yd = d + md;
|
|
}
|
|
if (yd != 0.0) {
|
|
if (offsetChanged() != OffsetChange::NONE) {
|
|
// user moved element within the skyline
|
|
// we may need to adjust minDistance, yd, and/or offset
|
|
qreal adj = pos().y() + rebase;
|
|
bool inStaff = above ? sh.bottom() + adj > 0.0 : sh.top() + adj < staff()->height();
|
|
rebaseMinDistance(md, yd, sp, rebase, above, inStaff);
|
|
}
|
|
rypos() += yd;
|
|
}
|
|
}
|
|
setOffsetChanged(false);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// undoChangeProperty
|
|
//---------------------------------------------------------
|
|
|
|
void Spanner::undoChangeProperty(Pid id, const QVariant& v, PropertyFlags ps)
|
|
{
|
|
if (id == Pid::PLACEMENT) {
|
|
ScoreElement::undoChangeProperty(id, v, ps);
|
|
// change offset of all segments if styled
|
|
|
|
for (SpannerSegment* s : segments) {
|
|
if (s->isStyled(Pid::OFFSET)) {
|
|
s->setOffset(s->propertyDefault(Pid::OFFSET).toPointF());
|
|
s->triggerLayout();
|
|
}
|
|
}
|
|
MuseScoreCore::mscoreCore->updateInspector();
|
|
return;
|
|
}
|
|
Element::undoChangeProperty(id, v, ps);
|
|
}
|
|
|
|
}
|
|
|