MuseScore/libmscore/spanner.cpp

896 lines
29 KiB
C++
Raw Normal View History

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"
#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);
setSpannerSegmentType(SpannerSegmentType::SINGLE);
2012-05-26 14:26:10 +02:00
_spanner = 0;
}
SpannerSegment::SpannerSegment(const SpannerSegment& s)
: Element(s)
{
_spanner = s._spanner;
_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;
}
//---------------------------------------------------------
// setColor
//---------------------------------------------------------
void SpannerSegment::setColor(const QColor& col)
{
if (_spanner) {
for (SpannerSegment* ss : _spanner->spannerSegments())
ss->_color = col;
_spanner->_color = col;
}
else
_color = col;
}
//---------------------------------------------------------
// nextElement
//---------------------------------------------------------
Element* SpannerSegment::nextElement()
{
return spanner()->nextElement();
}
//---------------------------------------------------------
// prevElement
//---------------------------------------------------------
Element* SpannerSegment::prevElement()
{
return spanner()->prevElement();
}
//---------------------------------------------------------
// accessibleInfo
//---------------------------------------------------------
2016-02-04 17:06:32 +01:00
QString SpannerSegment::accessibleInfo() const
{
return spanner()->accessibleInfo();
}
//---------------------------------------------------------
// 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());
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);
}
//---------------------------------------------------------
// 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)
{
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()
{
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));
}
}
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
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;
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());
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:
_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) {
case Anchor::SEGMENT: {
// 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;
}
if (!endCR()->measure()->isMMRest()) {
ChordRest* cr = endCR();
int nticks = cr->tick() + cr->actualTicks() - _tick;
// allow fudge factor for tuplets
// TODO: replace with fraction-based calculation
int fudge = cr->tuplet() ? 5 : 0;
if (qAbs(_ticks - nticks) > fudge) {
qDebug("%s ticks changed, %d -> %d", name(), _ticks, nticks);
setTicks(nticks);
if (type() == Element::Type::OTTAVA)
staff()->updateOttava();
}
}
}
2013-06-16 23:33:37 +02:00
break;
2014-05-26 20:48:27 +02:00
case Anchor::MEASURE:
_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
}
//---------------------------------------------------------
// 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
//---------------------------------------------------------
Chord* Spanner::startChord()
2013-06-16 23:33:37 +02:00
{
2014-05-26 20:48:27 +02:00
Q_ASSERT(_anchor == Anchor::CHORD);
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
//---------------------------------------------------------
Chord* Spanner::endChord()
2013-06-16 23:33:37 +02:00
{
2014-05-26 20:48:27 +02:00
Q_ASSERT(_anchor == Anchor::CHORD);
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
//---------------------------------------------------------
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);
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
//---------------------------------------------------------
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);
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
{
return score()->tick2rightSegment(tick());
2013-06-19 16:25:29 +02:00
}
//---------------------------------------------------------
// endSegment
//---------------------------------------------------------
Segment* Spanner::endSegment() const
{
return score()->tick2leftSegment(tick2());
2013-06-19 16:25:29 +02:00
}
2014-05-07 12:10:28 +02: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;
}
//---------------------------------------------------------
// 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;
}
//---------------------------------------------------------
// 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();
}
//---------------------------------------------------------
// setTick
2016-03-02 13:20:19 +01:00
// //no: @warning Alters spannerMap - Do not call from within a loop over spannerMap
//---------------------------------------------------------
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
// }
}
//---------------------------------------------------------
// setTick2
//---------------------------------------------------------
void Spanner::setTick2(int v)
{
_ticks = v - _tick;
2016-03-10 10:41:31 +01:00
if (score())
score()->spannerMap().setDirty();
}
//---------------------------------------------------------
// setTicks
//---------------------------------------------------------
void Spanner::setTicks(int v)
{
_ticks = v;
2016-03-10 10:41:31 +01:00
if (score())
score()->spannerMap().setDirty();
}
2013-05-13 18:49:17 +02:00
}