2012-05-26 14:26:10 +02:00
|
|
|
//=============================================================================
|
|
|
|
// 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
|
|
|
|
//=============================================================================
|
|
|
|
|
2013-06-10 11:03:34 +02:00
|
|
|
#include "score.h"
|
2012-05-26 14:26:10 +02:00
|
|
|
#include "spanner.h"
|
|
|
|
#include "system.h"
|
|
|
|
#include "chordrest.h"
|
2013-06-16 23:33:37 +02:00
|
|
|
#include "chord.h"
|
2012-05-26 14:26:10 +02:00
|
|
|
#include "segment.h"
|
2013-06-10 11:03:34 +02:00
|
|
|
#include "measure.h"
|
2014-07-10 14:32:04 +02:00
|
|
|
#include "undo.h"
|
2015-06-30 09:06:30 +02:00
|
|
|
#include "staff.h"
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2013-05-13 18:49:17 +02:00
|
|
|
namespace Ms {
|
|
|
|
|
Fixes #19155, #22861 (duplicate of the former) and #23100.
__References__:
Issues: https://musescore.org/en/node/19155 https://musescore.org/en/node/22861 https://musescore.org/en/node/23100
__Description__:
Allows to change the start and end note to which a glissando is anchored after it has been entered. Either anchor can be changed independently.
The user interface follows the current working of other 'snappable' lines. Once either the start or end grip is selected:
- `[Shift]+[Left]` snaps the anchor to the previous chord, defaulting to its top note.
- `[Shift]+[Right]` snaps to the next chord, defaulting to its top note.
- `[Shift]+[Up]` snaps to the note above (possibly in a chord, voice or staff above the current one).
- `[Shift]+[Down]` snaps to the note below (possibly in a chord, voice or staff below the current one).
This permits to set the anchor points of a glissando to any note in the score, allowing several glissandi between the notes of the same two chords and other complex configurations (glissandi skipping intermediate chords, start and end notes in different voices or staves, and so on).
It is possible to move the anchor to a different staff of the same instrument, but not to a different instrument; also, it is not possible to 'cross' a change of instrument in the same staff.
__Known limitations__:
- The `[Shift]+[Up]` and `[Shift]+[Down]` use the same note-finding functions as the `[Alt]+[Up]` and `[Alt]+[Down]`actions which move the selection cursor to the above and below note, even across voices or staves. Occasionally, in particular if the note immediately above or below is not time-aligned, the algorithm has little expected results; however, the behaviour is already known to the user. Improving the algorithm would benefit both uses.
__Notes__:
- Most of the added infrastructure is not specific to glissando but to any spanner anchored to notes, then it should also add after-the-fact "snap to" note support to note-anchored text line.
- When moving an anchor, the algorithm usually prefers a note in the same voice/staff of the old note if it exists; if there is none, it tries other voices of the same staff.
- The change of anchor is undoable.
- The fix corrects the management of the `Chord::_endsGlissando` flag, taking into account that a chord can be the ending point of several glissandi and removing one of them not necessarily means the chord no longer ends a glissando (another glissando may still exists).
- The fix also improved the rendering of the glissando wavy line, with better alignment with anchor notes and, with glissando text, better text-line spacing.
2015-08-06 11:11:16 +02:00
|
|
|
int Spanner::editTick;
|
|
|
|
int Spanner::editTick2;
|
|
|
|
int Spanner::editTrack2;
|
|
|
|
Note* Spanner::editEndNote;
|
|
|
|
Note* Spanner::editStartNote;
|
2013-06-10 11:03:34 +02:00
|
|
|
QList<QPointF> Spanner::userOffsets2;
|
|
|
|
QList<QPointF> Spanner::userOffsets;
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// SpannerSegment
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
SpannerSegment::SpannerSegment(Score* s)
|
|
|
|
: Element(s)
|
|
|
|
{
|
2014-07-09 18:05:58 +02:00
|
|
|
setFlags(ElementFlag::MOVABLE | ElementFlag::SELECTABLE | ElementFlag::SEGMENT | ElementFlag::ON_STAFF);
|
2014-05-26 20:38:22 +02:00
|
|
|
setSpannerSegmentType(SpannerSegmentType::SINGLE);
|
2012-05-26 14:26:10 +02:00
|
|
|
_spanner = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
SpannerSegment::SpannerSegment(const SpannerSegment& s)
|
|
|
|
: Element(s)
|
|
|
|
{
|
|
|
|
_spanner = s._spanner;
|
2013-03-05 20:23:59 +01:00
|
|
|
_spannerSegmentType = s._spannerSegmentType;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// startEdit
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void SpannerSegment::startEdit(MuseScoreView*s , const QPointF& p)
|
|
|
|
{
|
|
|
|
spanner()->startEdit(s, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// endEdit
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void SpannerSegment::endEdit()
|
|
|
|
{
|
|
|
|
spanner()->endEdit();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setSystem
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void SpannerSegment::setSystem(System* s)
|
|
|
|
{
|
|
|
|
if (system() != s) {
|
|
|
|
if (system())
|
|
|
|
system()->remove(this);
|
2013-06-10 11:03:34 +02:00
|
|
|
if (s)
|
|
|
|
s->add(this);
|
|
|
|
else
|
|
|
|
setParent(0);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-02 16:12:17 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// getProperty
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QVariant SpannerSegment::getProperty(P_ID id) const
|
|
|
|
{
|
|
|
|
switch (id) {
|
2014-05-26 18:18:01 +02:00
|
|
|
case P_ID::COLOR:
|
|
|
|
case P_ID::VISIBLE:
|
2013-05-02 16:12:17 +02:00
|
|
|
return spanner()->getProperty(id);
|
2014-05-26 18:18:01 +02:00
|
|
|
case P_ID::USER_OFF2:
|
2013-06-10 11:03:34 +02:00
|
|
|
return _userOff2;
|
|
|
|
|
2013-05-02 16:12:17 +02:00
|
|
|
default:
|
|
|
|
return Element::getProperty(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setProperty
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool SpannerSegment::setProperty(P_ID id, const QVariant& v)
|
|
|
|
{
|
|
|
|
switch (id) {
|
2014-05-26 18:18:01 +02:00
|
|
|
case P_ID::COLOR:
|
|
|
|
case P_ID::VISIBLE:
|
2013-05-02 16:12:17 +02:00
|
|
|
return spanner()->setProperty(id, v);
|
2014-05-26 18:18:01 +02:00
|
|
|
case P_ID::USER_OFF2:
|
2013-06-10 11:03:34 +02:00
|
|
|
_userOff2 = v.toPointF();
|
2016-03-02 13:20:19 +01:00
|
|
|
score()->setLayoutAll();
|
2013-06-10 11:03:34 +02:00
|
|
|
break;
|
2013-05-02 16:12:17 +02:00
|
|
|
default:
|
|
|
|
return Element::setProperty(id, v);
|
|
|
|
}
|
2013-06-10 11:03:34 +02:00
|
|
|
return true;
|
2013-05-02 16:12:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// propertyDefault
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QVariant SpannerSegment::propertyDefault(P_ID id) const
|
|
|
|
{
|
|
|
|
switch (id) {
|
2014-05-26 18:18:01 +02:00
|
|
|
case P_ID::COLOR:
|
|
|
|
case P_ID::VISIBLE:
|
2013-05-02 16:12:17 +02:00
|
|
|
return spanner()->propertyDefault(id);
|
2014-05-26 18:18:01 +02:00
|
|
|
case P_ID::USER_OFF2:
|
2013-06-10 11:03:34 +02:00
|
|
|
return QVariant();
|
2013-05-02 16:12:17 +02:00
|
|
|
default:
|
|
|
|
return Element::propertyDefault(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-26 10:39:16 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// reset
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void SpannerSegment::reset()
|
|
|
|
{
|
2014-05-26 18:18:01 +02:00
|
|
|
score()->undoChangeProperty(this, P_ID::USER_OFF2, QPointF());
|
2013-08-26 10:39:16 +02:00
|
|
|
Element::reset();
|
|
|
|
spanner()->reset();
|
|
|
|
}
|
|
|
|
|
2014-05-07 12:10:28 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setSelected
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void SpannerSegment::setSelected(bool f)
|
|
|
|
{
|
|
|
|
for (SpannerSegment* ss : _spanner->spannerSegments())
|
|
|
|
ss->_selected = f;
|
|
|
|
_spanner->_selected = f;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setVisible
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void SpannerSegment::setVisible(bool f)
|
|
|
|
{
|
|
|
|
if (_spanner) {
|
|
|
|
for (SpannerSegment* ss : _spanner->spannerSegments())
|
|
|
|
ss->_visible = f;
|
|
|
|
_spanner->_visible = f;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
_visible = f;
|
|
|
|
}
|
|
|
|
|
2014-10-01 15:15:08 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setColor
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void SpannerSegment::setColor(const QColor& col)
|
|
|
|
{
|
|
|
|
if (_spanner) {
|
|
|
|
for (SpannerSegment* ss : _spanner->spannerSegments())
|
|
|
|
ss->_color = col;
|
|
|
|
_spanner->_color = col;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
_color = col;
|
|
|
|
}
|
|
|
|
|
2014-06-20 22:48:34 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// nextElement
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Element* SpannerSegment::nextElement()
|
|
|
|
{
|
|
|
|
return spanner()->nextElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// prevElement
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Element* SpannerSegment::prevElement()
|
|
|
|
{
|
|
|
|
return spanner()->prevElement();
|
|
|
|
}
|
|
|
|
|
2014-07-10 14:13:37 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// accessibleInfo
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-02-04 17:06:32 +01:00
|
|
|
QString SpannerSegment::accessibleInfo() const
|
2014-07-10 14:13:37 +02:00
|
|
|
{
|
|
|
|
return spanner()->accessibleInfo();
|
|
|
|
}
|
|
|
|
|
2015-02-02 14:52:13 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// styleChanged
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void SpannerSegment::styleChanged()
|
|
|
|
{
|
|
|
|
_spanner->styleChanged();
|
|
|
|
}
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// Spanner
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Spanner::Spanner(Score* s)
|
|
|
|
: Element(s)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Spanner::Spanner(const Spanner& s)
|
|
|
|
: Element(s)
|
|
|
|
{
|
2013-06-16 23:33:37 +02:00
|
|
|
_anchor = s._anchor;
|
|
|
|
_startElement = s._startElement;
|
|
|
|
_endElement = s._endElement;
|
|
|
|
_tick = s._tick;
|
2014-08-13 15:42:40 +02:00
|
|
|
_ticks = s._ticks;
|
2013-10-14 11:56:54 +02:00
|
|
|
_track2 = s._track2;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Spanner::~Spanner()
|
|
|
|
{
|
2013-06-16 23:33:37 +02:00
|
|
|
foreach (SpannerSegment* ss, spannerSegments())
|
2012-05-26 14:26:10 +02:00
|
|
|
delete ss;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// add
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::add(Element* e)
|
|
|
|
{
|
|
|
|
SpannerSegment* ls = static_cast<SpannerSegment*>(e);
|
|
|
|
ls->setSpanner(this);
|
2014-05-07 12:10:28 +02:00
|
|
|
ls->setSelected(selected());
|
2014-08-14 09:47:35 +02:00
|
|
|
ls->setTrack(ls->spanner()->track());
|
2012-05-26 14:26:10 +02:00
|
|
|
segments.append(ls);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// remove
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::remove(Element* e)
|
|
|
|
{
|
|
|
|
SpannerSegment* ss = static_cast<SpannerSegment*>(e);
|
|
|
|
if (ss->system())
|
|
|
|
ss->system()->remove(ss);
|
|
|
|
segments.removeOne(ss);
|
|
|
|
}
|
|
|
|
|
2015-03-10 11:38:27 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// undoInserTimeUnmanaged
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::undoInsertTimeUnmanaged(int fromTick, int len)
|
|
|
|
{
|
|
|
|
int newTick1 = tick();
|
|
|
|
int newTick2 = tick2();
|
|
|
|
|
|
|
|
// check spanner start and end point
|
|
|
|
if (len > 0) { // 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 < 0) { // removing time
|
|
|
|
int 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(P_ID::SPANNER_TICKS, newTick2-newTick1);
|
|
|
|
if (newTick1 != tick())
|
|
|
|
setProperty(P_ID::SPANNER_TICK, newTick1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// scanElements
|
2014-05-07 12:10:28 +02:00
|
|
|
// used in palettes
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::scanElements(void* data, void (*func)(void*, Element*), bool all)
|
|
|
|
{
|
2015-03-10 11:38:27 +01:00
|
|
|
Q_UNUSED(all);
|
2014-05-07 12:10:28 +02:00
|
|
|
for (SpannerSegment* seg : segments)
|
2012-05-26 14:26:10 +02:00
|
|
|
seg->scanElements(data, func, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setScore
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::setScore(Score* s)
|
|
|
|
{
|
|
|
|
Element::setScore(s);
|
|
|
|
foreach(SpannerSegment* seg, segments)
|
|
|
|
seg->setScore(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// startEdit
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::startEdit(MuseScoreView*, const QPointF&)
|
|
|
|
{
|
2014-07-10 14:32:04 +02:00
|
|
|
editTick = _tick;
|
2014-08-13 15:42:40 +02:00
|
|
|
editTick2 = tick2();
|
2013-09-27 11:18:30 +02:00
|
|
|
editTrack2 = _track2;
|
Fixes #19155, #22861 (duplicate of the former) and #23100.
__References__:
Issues: https://musescore.org/en/node/19155 https://musescore.org/en/node/22861 https://musescore.org/en/node/23100
__Description__:
Allows to change the start and end note to which a glissando is anchored after it has been entered. Either anchor can be changed independently.
The user interface follows the current working of other 'snappable' lines. Once either the start or end grip is selected:
- `[Shift]+[Left]` snaps the anchor to the previous chord, defaulting to its top note.
- `[Shift]+[Right]` snaps to the next chord, defaulting to its top note.
- `[Shift]+[Up]` snaps to the note above (possibly in a chord, voice or staff above the current one).
- `[Shift]+[Down]` snaps to the note below (possibly in a chord, voice or staff below the current one).
This permits to set the anchor points of a glissando to any note in the score, allowing several glissandi between the notes of the same two chords and other complex configurations (glissandi skipping intermediate chords, start and end notes in different voices or staves, and so on).
It is possible to move the anchor to a different staff of the same instrument, but not to a different instrument; also, it is not possible to 'cross' a change of instrument in the same staff.
__Known limitations__:
- The `[Shift]+[Up]` and `[Shift]+[Down]` use the same note-finding functions as the `[Alt]+[Up]` and `[Alt]+[Down]`actions which move the selection cursor to the above and below note, even across voices or staves. Occasionally, in particular if the note immediately above or below is not time-aligned, the algorithm has little expected results; however, the behaviour is already known to the user. Improving the algorithm would benefit both uses.
__Notes__:
- Most of the added infrastructure is not specific to glissando but to any spanner anchored to notes, then it should also add after-the-fact "snap to" note support to note-anchored text line.
- When moving an anchor, the algorithm usually prefers a note in the same voice/staff of the old note if it exists; if there is none, it tries other voices of the same staff.
- The change of anchor is undoable.
- The fix corrects the management of the `Chord::_endsGlissando` flag, taking into account that a chord can be the ending point of several glissandi and removing one of them not necessarily means the chord no longer ends a glissando (another glissando may still exists).
- The fix also improved the rendering of the glissando wavy line, with better alignment with anchor notes and, with glissando text, better text-line spacing.
2015-08-06 11:11:16 +02:00
|
|
|
if (_anchor == Spanner::Anchor::NOTE) {
|
|
|
|
editEndNote = static_cast<Note*>(_endElement);
|
|
|
|
editStartNote = static_cast<Note*>(_startElement);
|
|
|
|
}
|
2013-09-27 11:18:30 +02:00
|
|
|
|
2013-06-10 11:03:34 +02:00
|
|
|
userOffsets.clear();
|
|
|
|
userOffsets2.clear();
|
|
|
|
foreach (SpannerSegment* ss, spannerSegments()) {
|
|
|
|
userOffsets.push_back(ss->userOff());
|
|
|
|
userOffsets2.push_back(ss->userOff2());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// endEdit
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::endEdit()
|
|
|
|
{
|
2013-10-10 15:41:25 +02:00
|
|
|
bool rebuild = false;
|
Fixes #19155, #22861 (duplicate of the former) and #23100.
__References__:
Issues: https://musescore.org/en/node/19155 https://musescore.org/en/node/22861 https://musescore.org/en/node/23100
__Description__:
Allows to change the start and end note to which a glissando is anchored after it has been entered. Either anchor can be changed independently.
The user interface follows the current working of other 'snappable' lines. Once either the start or end grip is selected:
- `[Shift]+[Left]` snaps the anchor to the previous chord, defaulting to its top note.
- `[Shift]+[Right]` snaps to the next chord, defaulting to its top note.
- `[Shift]+[Up]` snaps to the note above (possibly in a chord, voice or staff above the current one).
- `[Shift]+[Down]` snaps to the note below (possibly in a chord, voice or staff below the current one).
This permits to set the anchor points of a glissando to any note in the score, allowing several glissandi between the notes of the same two chords and other complex configurations (glissandi skipping intermediate chords, start and end notes in different voices or staves, and so on).
It is possible to move the anchor to a different staff of the same instrument, but not to a different instrument; also, it is not possible to 'cross' a change of instrument in the same staff.
__Known limitations__:
- The `[Shift]+[Up]` and `[Shift]+[Down]` use the same note-finding functions as the `[Alt]+[Up]` and `[Alt]+[Down]`actions which move the selection cursor to the above and below note, even across voices or staves. Occasionally, in particular if the note immediately above or below is not time-aligned, the algorithm has little expected results; however, the behaviour is already known to the user. Improving the algorithm would benefit both uses.
__Notes__:
- Most of the added infrastructure is not specific to glissando but to any spanner anchored to notes, then it should also add after-the-fact "snap to" note support to note-anchored text line.
- When moving an anchor, the algorithm usually prefers a note in the same voice/staff of the old note if it exists; if there is none, it tries other voices of the same staff.
- The change of anchor is undoable.
- The fix corrects the management of the `Chord::_endsGlissando` flag, taking into account that a chord can be the ending point of several glissandi and removing one of them not necessarily means the chord no longer ends a glissando (another glissando may still exists).
- The fix also improved the rendering of the glissando wavy line, with better alignment with anchor notes and, with glissando text, better text-line spacing.
2015-08-06 11:11:16 +02:00
|
|
|
if (_anchor == Spanner::Anchor::NOTE) {
|
|
|
|
if (_endElement != editEndNote || _startElement != editStartNote) {
|
|
|
|
// swap original anchor elements into the spanner
|
|
|
|
// and set the new one via an undoable operation
|
|
|
|
Note* newStartNote = static_cast<Note*>(_startElement);
|
|
|
|
Note* newEndNote = static_cast<Note*>(_endElement);
|
|
|
|
_startElement = editStartNote;
|
|
|
|
_endElement = editEndNote;
|
|
|
|
score()->undo(new ChangeSpannerElements(this, newStartNote, newEndNote));
|
|
|
|
}
|
2013-10-10 15:41:25 +02:00
|
|
|
}
|
Fixes #19155, #22861 (duplicate of the former) and #23100.
__References__:
Issues: https://musescore.org/en/node/19155 https://musescore.org/en/node/22861 https://musescore.org/en/node/23100
__Description__:
Allows to change the start and end note to which a glissando is anchored after it has been entered. Either anchor can be changed independently.
The user interface follows the current working of other 'snappable' lines. Once either the start or end grip is selected:
- `[Shift]+[Left]` snaps the anchor to the previous chord, defaulting to its top note.
- `[Shift]+[Right]` snaps to the next chord, defaulting to its top note.
- `[Shift]+[Up]` snaps to the note above (possibly in a chord, voice or staff above the current one).
- `[Shift]+[Down]` snaps to the note below (possibly in a chord, voice or staff below the current one).
This permits to set the anchor points of a glissando to any note in the score, allowing several glissandi between the notes of the same two chords and other complex configurations (glissandi skipping intermediate chords, start and end notes in different voices or staves, and so on).
It is possible to move the anchor to a different staff of the same instrument, but not to a different instrument; also, it is not possible to 'cross' a change of instrument in the same staff.
__Known limitations__:
- The `[Shift]+[Up]` and `[Shift]+[Down]` use the same note-finding functions as the `[Alt]+[Up]` and `[Alt]+[Down]`actions which move the selection cursor to the above and below note, even across voices or staves. Occasionally, in particular if the note immediately above or below is not time-aligned, the algorithm has little expected results; however, the behaviour is already known to the user. Improving the algorithm would benefit both uses.
__Notes__:
- Most of the added infrastructure is not specific to glissando but to any spanner anchored to notes, then it should also add after-the-fact "snap to" note support to note-anchored text line.
- When moving an anchor, the algorithm usually prefers a note in the same voice/staff of the old note if it exists; if there is none, it tries other voices of the same staff.
- The change of anchor is undoable.
- The fix corrects the management of the `Chord::_endsGlissando` flag, taking into account that a chord can be the ending point of several glissandi and removing one of them not necessarily means the chord no longer ends a glissando (another glissando may still exists).
- The fix also improved the rendering of the glissando wavy line, with better alignment with anchor notes and, with glissando text, better text-line spacing.
2015-08-06 11:11:16 +02:00
|
|
|
else {
|
|
|
|
if (editTick != tick()) {
|
|
|
|
score()->undoPropertyChanged(this, P_ID::SPANNER_TICK, editTick);
|
|
|
|
rebuild = true;
|
|
|
|
}
|
|
|
|
// ticks may also change by moving initial anchor, without moving ending anchor
|
|
|
|
if (editTick2 != tick2() || editTick2 - editTick != tick2() - tick()) {
|
|
|
|
score()->undoPropertyChanged(this, P_ID::SPANNER_TICKS, editTick2 - editTick);
|
|
|
|
rebuild = true;
|
|
|
|
}
|
|
|
|
if (editTrack2 != track2()) {
|
|
|
|
score()->undoPropertyChanged(this, P_ID::SPANNER_TRACK2, editTrack2);
|
|
|
|
rebuild = true;
|
|
|
|
}
|
2013-08-13 14:26:40 +02:00
|
|
|
}
|
2013-06-10 11:03:34 +02:00
|
|
|
|
2013-10-10 15:41:25 +02:00
|
|
|
if (rebuild)
|
|
|
|
score()->rebuildBspTree();
|
|
|
|
|
2013-06-10 11:03:34 +02:00
|
|
|
if (spannerSegments().size() != userOffsets2.size()) {
|
2014-07-31 16:30:04 +02:00
|
|
|
qDebug("Spanner::endEdit(): segment size changed");
|
2013-06-10 11:03:34 +02:00
|
|
|
return;
|
|
|
|
}
|
2014-07-31 16:30:04 +02:00
|
|
|
|
2013-06-10 11:03:34 +02:00
|
|
|
for (int i = 0; i < userOffsets2.size(); ++i) {
|
|
|
|
SpannerSegment* ss = segments[i];
|
2014-05-26 18:18:01 +02:00
|
|
|
score()->undoPropertyChanged(ss, P_ID::USER_OFF, userOffsets[i]);
|
|
|
|
score()->undoPropertyChanged(ss, P_ID::USER_OFF2, userOffsets2[i]);
|
2013-06-10 11:03:34 +02:00
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2013-06-10 11:03:34 +02:00
|
|
|
// getProperty
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2013-06-10 11:03:34 +02:00
|
|
|
QVariant Spanner::getProperty(P_ID propertyId) const
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2013-06-10 11:03:34 +02:00
|
|
|
switch (propertyId) {
|
2014-05-26 18:18:01 +02:00
|
|
|
case P_ID::SPANNER_TICK:
|
2013-06-10 11:03:34 +02:00
|
|
|
return tick();
|
2014-08-13 15:42:40 +02:00
|
|
|
case P_ID::SPANNER_TICKS:
|
|
|
|
return ticks();
|
2014-05-26 18:18:01 +02:00
|
|
|
case P_ID::SPANNER_TRACK2:
|
2013-09-27 11:18:30 +02:00
|
|
|
return track2();
|
2014-06-18 20:57:45 +02:00
|
|
|
case P_ID::ANCHOR:
|
|
|
|
return int(anchor());
|
2013-06-10 11:03:34 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return Element::getProperty(propertyId);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2013-06-10 11:03:34 +02:00
|
|
|
// setProperty
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2013-06-10 11:03:34 +02:00
|
|
|
bool Spanner::setProperty(P_ID propertyId, const QVariant& v)
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2013-06-10 11:03:34 +02:00
|
|
|
switch(propertyId) {
|
2014-05-26 18:18:01 +02:00
|
|
|
case P_ID::SPANNER_TICK:
|
2013-06-10 11:03:34 +02:00
|
|
|
setTick(v.toInt());
|
|
|
|
break;
|
2014-08-13 15:42:40 +02:00
|
|
|
case P_ID::SPANNER_TICKS:
|
|
|
|
setTicks(v.toInt());
|
2013-06-10 11:03:34 +02:00
|
|
|
break;
|
2015-04-10 11:51:15 +02:00
|
|
|
case P_ID::TRACK:
|
|
|
|
setTrack(v.toInt());
|
|
|
|
setStartElement(0);
|
|
|
|
break;
|
2014-05-26 18:18:01 +02:00
|
|
|
case P_ID::SPANNER_TRACK2:
|
2013-09-27 11:18:30 +02:00
|
|
|
setTrack2(v.toInt());
|
2015-04-10 11:51:15 +02:00
|
|
|
setEndElement(0);
|
2013-09-27 11:18:30 +02:00
|
|
|
break;
|
2014-06-18 20:57:45 +02:00
|
|
|
case P_ID::ANCHOR:
|
|
|
|
setAnchor(Anchor(v.toInt()));
|
|
|
|
break;
|
2013-06-10 11:03:34 +02:00
|
|
|
default:
|
|
|
|
if (!Element::setProperty(propertyId, v))
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
}
|
2016-03-02 13:20:19 +01:00
|
|
|
score()->setLayoutAll();
|
2013-06-10 11:03:34 +02:00
|
|
|
return true;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2013-06-10 11:03:34 +02:00
|
|
|
// propertyDefault
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2013-06-10 11:03:34 +02:00
|
|
|
QVariant Spanner::propertyDefault(P_ID propertyId) const
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2013-06-10 11:03:34 +02:00
|
|
|
switch(propertyId) {
|
2014-06-18 20:57:45 +02:00
|
|
|
case P_ID::ANCHOR:
|
|
|
|
return int(Anchor::SEGMENT);
|
2013-06-10 11:03:34 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return Element::propertyDefault(propertyId);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
2013-06-10 11:03:34 +02:00
|
|
|
//---------------------------------------------------------
|
2013-06-16 23:33:37 +02:00
|
|
|
// computeStartElement
|
2013-06-10 11:03:34 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2013-06-16 23:33:37 +02:00
|
|
|
void Spanner::computeStartElement()
|
2013-06-10 11:03:34 +02:00
|
|
|
{
|
|
|
|
switch (_anchor) {
|
2014-08-22 18:03:50 +02:00
|
|
|
case Anchor::SEGMENT: {
|
|
|
|
Segment* seg = score()->tick2segmentMM(tick(), false, Segment::Type::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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-06-16 23:33:37 +02:00
|
|
|
break;
|
|
|
|
|
2014-05-26 20:48:27 +02:00
|
|
|
case Anchor::MEASURE:
|
2013-06-25 14:29:18 +02:00
|
|
|
_startElement = score()->tick2measure(tick());
|
2013-06-16 23:33:37 +02:00
|
|
|
break;
|
2013-06-12 14:23:57 +02:00
|
|
|
|
2014-05-26 20:48:27 +02:00
|
|
|
case Anchor::CHORD:
|
|
|
|
case Anchor::NOTE:
|
2013-06-16 23:33:37 +02:00
|
|
|
return;
|
2013-06-10 11:03:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2013-06-16 23:33:37 +02:00
|
|
|
// computeEndElement
|
2013-06-10 11:03:34 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2013-06-16 23:33:37 +02:00
|
|
|
void Spanner::computeEndElement()
|
2013-06-10 11:03:34 +02:00
|
|
|
{
|
|
|
|
switch (_anchor) {
|
2015-02-05 11:47:36 +01:00
|
|
|
case Anchor::SEGMENT: {
|
2015-08-12 20:07:29 +02:00
|
|
|
// find last cr on this staff that ends before tick2
|
|
|
|
_endElement = score()->findCRinStaff(tick2(), track2() / VOICES);
|
2015-02-11 23:51:41 +01:00
|
|
|
if (!_endElement) {
|
|
|
|
qDebug("%s no end element for tick %d", name(), tick2());
|
|
|
|
return;
|
|
|
|
}
|
2015-07-10 19:58:55 +02:00
|
|
|
if (!endCR()->measure()->isMMRest()) {
|
2015-08-12 20:07:29 +02:00
|
|
|
ChordRest* cr = endCR();
|
|
|
|
int nticks = cr->tick() + cr->actualTicks() - _tick;
|
2015-08-15 16:26:04 +02:00
|
|
|
// allow fudge factor for tuplets
|
|
|
|
// TODO: replace with fraction-based calculation
|
|
|
|
int fudge = cr->tuplet() ? 5 : 0;
|
|
|
|
if (qAbs(_ticks - nticks) > fudge) {
|
2015-07-10 19:58:55 +02:00
|
|
|
qDebug("%s ticks changed, %d -> %d", name(), _ticks, nticks);
|
|
|
|
setTicks(nticks);
|
|
|
|
if (type() == Element::Type::OTTAVA)
|
|
|
|
staff()->updateOttava();
|
|
|
|
}
|
2015-02-05 11:47:36 +01:00
|
|
|
}
|
|
|
|
}
|
2013-06-16 23:33:37 +02:00
|
|
|
break;
|
|
|
|
|
2014-05-26 20:48:27 +02:00
|
|
|
case Anchor::MEASURE:
|
2013-06-26 20:38:44 +02:00
|
|
|
_endElement = score()->tick2measure(tick2() - 1);
|
2014-08-13 15:42:40 +02:00
|
|
|
if (!_endElement) {
|
|
|
|
qDebug("Spanner::computeEndElement(), measure not found for tick %d\n", tick2()-1);
|
2013-07-29 18:06:11 +02:00
|
|
|
_endElement = score()->lastMeasure();
|
2014-08-13 15:42:40 +02:00
|
|
|
}
|
2013-06-16 23:33:37 +02:00
|
|
|
break;
|
|
|
|
|
2014-05-26 20:48:27 +02:00
|
|
|
case Anchor::CHORD:
|
|
|
|
case Anchor::NOTE:
|
2013-06-10 11:03:34 +02:00
|
|
|
break;
|
|
|
|
}
|
2013-06-16 23:33:37 +02:00
|
|
|
}
|
|
|
|
|
2015-02-28 09:57:46 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// 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 = static_cast<Note*>(sp->startElement());
|
|
|
|
Note* oldEnd = static_cast<Note*>(sp->endElement());
|
|
|
|
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 (static_cast<Note*>(newEl)->score() == score
|
|
|
|
&& static_cast<Note*>(newEl)->track() == newTrack) {
|
|
|
|
newStart = static_cast<Note*>(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 = static_cast<Note*>(sp->startElement());
|
|
|
|
Note* oldEnd = static_cast<Note*>(sp->endElement());
|
|
|
|
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 (static_cast<Note*>(newEl)->score() == score
|
|
|
|
&& static_cast<Note*>(newEl)->track() == newTrack) {
|
|
|
|
newEnd = static_cast<Note*>(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());
|
|
|
|
}
|
|
|
|
|
2013-06-19 16:25:29 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// startChord
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-07-05 12:05:27 +02:00
|
|
|
Chord* Spanner::startChord()
|
2013-06-16 23:33:37 +02:00
|
|
|
{
|
2014-05-26 20:48:27 +02:00
|
|
|
Q_ASSERT(_anchor == Anchor::CHORD);
|
2014-07-05 12:05:27 +02:00
|
|
|
if (!_startElement)
|
|
|
|
_startElement = score()->findCR(tick(), track());
|
2014-07-08 11:16:21 +02:00
|
|
|
Q_ASSERT(_startElement->type() == Element::Type::CHORD);
|
2013-06-16 23:33:37 +02:00
|
|
|
return static_cast<Chord*>(_startElement);
|
|
|
|
}
|
|
|
|
|
2013-06-19 16:25:29 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// endChord
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-07-05 12:05:27 +02:00
|
|
|
Chord* Spanner::endChord()
|
2013-06-16 23:33:37 +02:00
|
|
|
{
|
2014-05-26 20:48:27 +02:00
|
|
|
Q_ASSERT(_anchor == Anchor::CHORD);
|
2014-07-05 12:05:27 +02:00
|
|
|
|
|
|
|
if (!_endElement && type() == Element::Type::SLUR) {
|
|
|
|
Segment* s = score()->tick2segmentMM(tick2(), false, Segment::Type::ChordRest);
|
|
|
|
_endElement = s ? static_cast<ChordRest*>(s->element(track2())) : nullptr;
|
|
|
|
if (_endElement->type() != Element::Type::CHORD)
|
|
|
|
_endElement = nullptr;
|
|
|
|
}
|
2013-06-16 23:33:37 +02:00
|
|
|
return static_cast<Chord*>(_endElement);
|
2013-06-10 11:03:34 +02:00
|
|
|
}
|
2013-06-16 23:33:37 +02:00
|
|
|
|
2013-06-19 16:25:29 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// startCR
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-07-05 12:05:27 +02:00
|
|
|
ChordRest* Spanner::startCR()
|
2013-06-16 23:33:37 +02:00
|
|
|
{
|
2014-05-26 20:48:27 +02:00
|
|
|
Q_ASSERT(_anchor == Anchor::SEGMENT || _anchor == Anchor::CHORD);
|
2014-07-05 12:05:27 +02:00
|
|
|
if (!_startElement)
|
2014-08-23 15:16:27 +02:00
|
|
|
_startElement = score()->findCR(tick(), track());
|
2013-06-16 23:33:37 +02:00
|
|
|
return static_cast<ChordRest*>(_startElement);
|
|
|
|
}
|
|
|
|
|
2013-06-19 16:25:29 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// endCR
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-07-05 12:05:27 +02:00
|
|
|
ChordRest* Spanner::endCR()
|
2013-06-16 23:33:37 +02:00
|
|
|
{
|
2014-05-26 20:48:27 +02:00
|
|
|
Q_ASSERT(_anchor == Anchor::SEGMENT || _anchor == Anchor::CHORD);
|
2014-07-05 12:05:27 +02:00
|
|
|
if (!_endElement && type() == Element::Type::SLUR) {
|
|
|
|
Segment* s = score()->tick2segmentMM(tick2(), false, Segment::Type::ChordRest);
|
|
|
|
_endElement = s ? static_cast<ChordRest*>(s->element(track2())) : nullptr;
|
|
|
|
}
|
2013-06-16 23:33:37 +02:00
|
|
|
return static_cast<ChordRest*>(_endElement);
|
|
|
|
}
|
|
|
|
|
2013-06-19 16:25:29 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// startSegment
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Segment* Spanner::startSegment() const
|
|
|
|
{
|
2013-09-03 16:34:56 +02:00
|
|
|
return score()->tick2rightSegment(tick());
|
2013-06-19 16:25:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// endSegment
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Segment* Spanner::endSegment() const
|
|
|
|
{
|
2013-09-03 16:34:56 +02:00
|
|
|
return score()->tick2leftSegment(tick2());
|
2013-06-19 16:25:29 +02:00
|
|
|
}
|
2014-05-07 12:10:28 +02:00
|
|
|
|
2015-01-19 12:37:17 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// startMeasure
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Measure* Spanner::startMeasure() const
|
|
|
|
{
|
|
|
|
Q_ASSERT(!_endElement || _endElement->type() == Element::Type::MEASURE);
|
|
|
|
return static_cast<Measure*>(_startElement);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// endMeasure
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Measure* Spanner::endMeasure() const
|
|
|
|
{
|
|
|
|
Q_ASSERT(!_endElement || _endElement->type() == Element::Type::MEASURE);
|
|
|
|
return static_cast<Measure*>(_endElement);
|
|
|
|
}
|
|
|
|
|
2014-05-07 12:10:28 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setSelected
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::setSelected(bool f)
|
|
|
|
{
|
|
|
|
for (SpannerSegment* ss : spannerSegments())
|
|
|
|
ss->setSelected(f);
|
|
|
|
_selected = f;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setVisible
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::setVisible(bool f)
|
|
|
|
{
|
|
|
|
for (SpannerSegment* ss : spannerSegments())
|
|
|
|
ss->setVisible(f);
|
|
|
|
_visible = f;
|
|
|
|
}
|
|
|
|
|
2014-10-01 15:15:08 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setColor
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::setColor(const QColor& col)
|
|
|
|
{
|
|
|
|
for (SpannerSegment* ss : spannerSegments())
|
|
|
|
ss->setColor(col);
|
|
|
|
_color = col;
|
|
|
|
}
|
|
|
|
|
2014-08-07 10:18:50 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setStartElement
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::setStartElement(Element* e)
|
|
|
|
{
|
|
|
|
#ifndef NDEBUG
|
|
|
|
if (_anchor == Anchor::NOTE)
|
2014-08-07 11:44:52 +02:00
|
|
|
Q_ASSERT(!e || e->type() == Element::Type::NOTE);
|
2014-08-07 10:18:50 +02:00
|
|
|
#endif
|
|
|
|
_startElement = e;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setEndElement
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::setEndElement(Element* e)
|
|
|
|
{
|
|
|
|
#ifndef NDEBUG
|
|
|
|
if (_anchor == Anchor::NOTE)
|
2014-08-07 11:44:52 +02:00
|
|
|
Q_ASSERT(!e || e->type() == Element::Type::NOTE);
|
2014-08-07 10:18:50 +02:00
|
|
|
#endif
|
|
|
|
_endElement = e;
|
|
|
|
}
|
|
|
|
|
2014-06-20 22:48:34 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// nextElement
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Element* Spanner::nextElement()
|
|
|
|
{
|
|
|
|
Segment* s = startSegment();
|
|
|
|
if (s)
|
|
|
|
return s->firstElement(staffIdx());
|
|
|
|
return score()->firstElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// prevElement
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Element* Spanner::prevElement()
|
|
|
|
{
|
|
|
|
Segment* s = endSegment();
|
|
|
|
if (s)
|
|
|
|
return s->lastElement(staffIdx());
|
|
|
|
return score()->lastElement();
|
|
|
|
}
|
|
|
|
|
2015-01-19 12:37:17 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setTick
|
2016-03-02 13:20:19 +01:00
|
|
|
// //no: @warning Alters spannerMap - Do not call from within a loop over spannerMap
|
2015-01-19 12:37:17 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::setTick(int v)
|
|
|
|
{
|
|
|
|
_tick = v;
|
2016-03-02 13:20:19 +01:00
|
|
|
// WS: this is a low level function and should have no side effects
|
2016-03-10 10:41:31 +01:00
|
|
|
// if (score()) {
|
2016-03-02 13:20:19 +01:00
|
|
|
//our starting tick changed, we'd need to occupy a different position in the spannerMap
|
2016-03-10 10:41:31 +01:00
|
|
|
// if (score()->spannerMap().removeSpanner(this))
|
|
|
|
// score()->addSpanner(this);
|
2016-03-02 13:20:19 +01:00
|
|
|
// }
|
2015-01-19 12:37:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setTick2
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::setTick2(int v)
|
|
|
|
{
|
|
|
|
_ticks = v - _tick;
|
2016-03-10 10:41:31 +01:00
|
|
|
if (score())
|
|
|
|
score()->spannerMap().setDirty();
|
2015-01-19 12:37:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setTicks
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Spanner::setTicks(int v)
|
|
|
|
{
|
|
|
|
_ticks = v;
|
2016-03-10 10:41:31 +01:00
|
|
|
if (score())
|
|
|
|
score()->spannerMap().setDirty();
|
2015-01-19 12:37:17 +01:00
|
|
|
}
|
|
|
|
|
2013-05-13 18:49:17 +02:00
|
|
|
}
|
|
|
|
|