2170 lines
71 KiB
C++
2170 lines
71 KiB
C++
//=============================================================================
|
|
// MuseScore
|
|
// Music Composition & Notation
|
|
//
|
|
// Copyright (C) 2002-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
|
|
//=============================================================================
|
|
|
|
/**
|
|
\file
|
|
Implementation of Element, ElementList
|
|
*/
|
|
|
|
#include "element.h"
|
|
#include "accidental.h"
|
|
#include "ambitus.h"
|
|
#include "arpeggio.h"
|
|
#include "articulation.h"
|
|
#include "bagpembell.h"
|
|
#include "barline.h"
|
|
#include "bend.h"
|
|
#include "box.h"
|
|
#include "bracket.h"
|
|
#include "breath.h"
|
|
#include "chord.h"
|
|
#include "chordline.h"
|
|
#include "chordrest.h"
|
|
#include "clef.h"
|
|
#include "connector.h"
|
|
#include "dynamic.h"
|
|
#include "figuredbass.h"
|
|
#include "fingering.h"
|
|
#include "fret.h"
|
|
#include "glissando.h"
|
|
#include "hairpin.h"
|
|
#include "harmony.h"
|
|
#include "icon.h"
|
|
#include "image.h"
|
|
#include "iname.h"
|
|
#include "instrchange.h"
|
|
#include "jump.h"
|
|
#include "keysig.h"
|
|
#include "layoutbreak.h"
|
|
#include "lyrics.h"
|
|
#include "marker.h"
|
|
#include "measure.h"
|
|
#include "mscore.h"
|
|
#include "notedot.h"
|
|
#include "note.h"
|
|
#include "noteline.h"
|
|
#include "ossia.h"
|
|
#include "ottava.h"
|
|
#include "page.h"
|
|
#include "pedal.h"
|
|
#include "rehearsalmark.h"
|
|
#include "repeat.h"
|
|
#include "rest.h"
|
|
#include "score.h"
|
|
#include "segment.h"
|
|
#include "slur.h"
|
|
#include "spacer.h"
|
|
#include "staff.h"
|
|
#include "staffstate.h"
|
|
#include "stafftext.h"
|
|
#include "systemtext.h"
|
|
#include "stafftype.h"
|
|
#include "stem.h"
|
|
#include "style.h"
|
|
#include "symbol.h"
|
|
#include "sym.h"
|
|
#include "system.h"
|
|
#include "tempotext.h"
|
|
#include "textframe.h"
|
|
#include "text.h"
|
|
#include "measurenumber.h"
|
|
#include "textline.h"
|
|
#include "tie.h"
|
|
#include "timesig.h"
|
|
#include "tremolobar.h"
|
|
#include "tremolo.h"
|
|
#include "trill.h"
|
|
#include "undo.h"
|
|
#include "utils.h"
|
|
#include "volta.h"
|
|
#include "xml.h"
|
|
#include "systemdivider.h"
|
|
#include "stafftypechange.h"
|
|
#include "stafflines.h"
|
|
#include "letring.h"
|
|
#include "vibrato.h"
|
|
#include "palmmute.h"
|
|
#include "fermata.h"
|
|
#include "shape.h"
|
|
|
|
namespace Ms {
|
|
|
|
// extern bool showInvisible;
|
|
|
|
//---------------------------------------------------------
|
|
// spatiumChanged
|
|
//---------------------------------------------------------
|
|
|
|
void Element::spatiumChanged(qreal oldValue, qreal newValue)
|
|
{
|
|
if (sizeIsSpatiumDependent())
|
|
_offset *= (newValue / oldValue);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// localSpatiumChanged
|
|
// the scale of a staff changed
|
|
//---------------------------------------------------------
|
|
|
|
void Element::localSpatiumChanged(qreal oldValue, qreal newValue)
|
|
{
|
|
if (sizeIsSpatiumDependent())
|
|
_offset *= (newValue / oldValue);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// spatium
|
|
//---------------------------------------------------------
|
|
|
|
qreal Element::spatium() const
|
|
{
|
|
Staff* s = staff();
|
|
return s ? s->spatium(tick()) : score()->spatium();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// magS
|
|
//---------------------------------------------------------
|
|
|
|
qreal Element::magS() const
|
|
{
|
|
return mag() * (score()->spatium() / SPATIUM20);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// name
|
|
//---------------------------------------------------------
|
|
|
|
QString Element::subtypeName() const
|
|
{
|
|
return "";
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Element
|
|
//---------------------------------------------------------
|
|
|
|
Element::Element(Score* s, ElementFlags f)
|
|
: ScoreElement(s)
|
|
{
|
|
_flags = f;
|
|
_track = -1;
|
|
_color = MScore::defaultColor;
|
|
_mag = 1.0;
|
|
_tag = 1;
|
|
_z = -1;
|
|
}
|
|
|
|
Element::Element(const Element& e)
|
|
: ScoreElement(e)
|
|
{
|
|
_parent = e._parent;
|
|
_bbox = e._bbox;
|
|
_mag = e._mag;
|
|
_pos = e._pos;
|
|
_offset = e._offset;
|
|
_track = e._track;
|
|
_flags = e._flags;
|
|
_tag = e._tag;
|
|
_z = e._z;
|
|
_color = e._color;
|
|
itemDiscovered = false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// ~Element
|
|
//---------------------------------------------------------
|
|
|
|
Element::~Element()
|
|
{
|
|
#if 0
|
|
if (score() && flag(ElementFlag::SELECTED)) {
|
|
if (score()->selection().elements().removeOne(this))
|
|
printf("remove element from selection\n");
|
|
else
|
|
printf("element not in selection\n");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// linkedClone
|
|
//---------------------------------------------------------
|
|
|
|
Element* Element::linkedClone()
|
|
{
|
|
Element* e = clone();
|
|
e->setAutoplace(true);
|
|
score()->undo(new Link(e, this));
|
|
return e;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// scanElements
|
|
//---------------------------------------------------------
|
|
|
|
void Element::scanElements(void* data, void (*func)(void*, Element*), bool all)
|
|
{
|
|
if (all || visible() || score()->showInvisible())
|
|
func(data, this);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// reset
|
|
//---------------------------------------------------------
|
|
|
|
void Element::reset()
|
|
{
|
|
undoResetProperty(Pid::AUTOPLACE);
|
|
undoResetProperty(Pid::PLACEMENT);
|
|
ScoreElement::reset();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// change
|
|
//---------------------------------------------------------
|
|
|
|
void Element::change(Element* o, Element* n)
|
|
{
|
|
remove(o);
|
|
add(n);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// staff
|
|
//---------------------------------------------------------
|
|
|
|
Staff* Element::staff() const
|
|
{
|
|
if (_track == -1 || score()->staves().empty())
|
|
return 0;
|
|
|
|
return score()->staff(_track >> 2);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// z
|
|
//---------------------------------------------------------
|
|
|
|
int Element::z() const
|
|
{
|
|
if (_z == -1)
|
|
_z = int(type()) * 100;
|
|
return _z;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// part
|
|
//---------------------------------------------------------
|
|
|
|
Part* Element::part() const
|
|
{
|
|
Staff* s = staff();
|
|
return s ? s->part() : 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// curColor
|
|
//---------------------------------------------------------
|
|
|
|
QColor Element::curColor() const
|
|
{
|
|
return curColor(visible());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// curColor
|
|
//---------------------------------------------------------
|
|
|
|
QColor Element::curColor(bool isVisible) const
|
|
{
|
|
return curColor(isVisible, color());
|
|
}
|
|
|
|
QColor Element::curColor(bool isVisible, QColor normalColor) const
|
|
{
|
|
// the default element color is always interpreted as black in
|
|
// printing
|
|
if (score() && score()->printing())
|
|
return (normalColor == MScore::defaultColor) ? Qt::black : normalColor;
|
|
|
|
if (flag(ElementFlag::DROP_TARGET))
|
|
return MScore::dropColor;
|
|
bool marked = false;
|
|
if (isNote()) {
|
|
//const Note* note = static_cast<const Note*>(this);
|
|
marked = toNote(this)->mark();
|
|
}
|
|
if (selected() || marked ) {
|
|
QColor originalColor;
|
|
if (track() == -1)
|
|
originalColor = MScore::selectColor[0];
|
|
else
|
|
originalColor = MScore::selectColor[voice()];
|
|
if (isVisible)
|
|
return originalColor;
|
|
else {
|
|
int red = originalColor.red();
|
|
int green = originalColor.green();
|
|
int blue = originalColor.blue();
|
|
float tint = .6f; // Between 0 and 1. Higher means lighter, lower means darker
|
|
return QColor(red + tint * (255 - red), green + tint * (255 - green), blue + tint * (255 - blue));
|
|
}
|
|
}
|
|
if (!isVisible)
|
|
return Qt::gray;
|
|
return normalColor;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// pagePos
|
|
// return position in canvas coordinates
|
|
//---------------------------------------------------------
|
|
|
|
QPointF Element::pagePos() const
|
|
{
|
|
QPointF p(pos());
|
|
if (parent() == 0)
|
|
return p;
|
|
|
|
if (_flags & ElementFlag::ON_STAFF) {
|
|
System* system = 0;
|
|
Measure* measure = 0;
|
|
if (parent()->isSegment())
|
|
measure = toSegment(parent())->measure();
|
|
else if (parent()->isMeasure()) // used in measure number
|
|
measure = toMeasure(parent());
|
|
else if (parent()->isSystem())
|
|
system = toSystem(parent());
|
|
else if (parent()->isFretDiagram())
|
|
return p + parent()->pagePos();
|
|
else
|
|
qFatal("this %s parent %s\n", name(), parent()->name());
|
|
if (measure) {
|
|
system = measure->system();
|
|
p.ry() += measure->staffLines(vStaffIdx())->y();
|
|
}
|
|
if (system) {
|
|
if (system->staves()->size() <= vStaffIdx()) {
|
|
qDebug("staffIdx out of bounds: %s", name());
|
|
}
|
|
p.ry() += system->staffYpage(vStaffIdx());
|
|
}
|
|
p.rx() = pageX();
|
|
}
|
|
else {
|
|
if (parent()->parent())
|
|
p += parent()->pagePos();
|
|
}
|
|
return p;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// canvasPos
|
|
//---------------------------------------------------------
|
|
|
|
QPointF Element::canvasPos() const
|
|
{
|
|
QPointF p(pos());
|
|
if (parent() == nullptr)
|
|
return p;
|
|
|
|
if (_flags & ElementFlag::ON_STAFF) {
|
|
System* system = nullptr;
|
|
Measure* measure = nullptr;
|
|
if (parent()->isSegment())
|
|
measure = toSegment(parent())->measure();
|
|
else if (parent()->isMeasure()) // used in measure number
|
|
measure = toMeasure(parent());
|
|
// system = toMeasure(parent())->system();
|
|
else if (parent()->isSystem())
|
|
system = toSystem(parent());
|
|
else if (parent()->isChord()) // grace chord
|
|
measure = toSegment(parent()->parent())->measure();
|
|
else if (parent()->isFretDiagram())
|
|
return p + parent()->canvasPos();
|
|
else
|
|
qFatal("this %s parent %s\n", name(), parent()->name());
|
|
if (measure) {
|
|
p.ry() += measure->staffLines(vStaffIdx())->y();
|
|
system = measure->system();
|
|
if (system) {
|
|
Page* page = system->page();
|
|
if (page)
|
|
p.ry() += page->y();
|
|
}
|
|
}
|
|
if (system)
|
|
p.ry() += system->staffYpage(vStaffIdx());
|
|
p.rx() = canvasX();
|
|
}
|
|
else
|
|
p += parent()->canvasPos();
|
|
return p;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// pageX
|
|
//---------------------------------------------------------
|
|
|
|
qreal Element::pageX() const
|
|
{
|
|
qreal xp = x();
|
|
for (Element* e = parent(); e && e->parent(); e = e->parent())
|
|
xp += e->x();
|
|
return xp;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// canvasX
|
|
//---------------------------------------------------------
|
|
|
|
qreal Element::canvasX() const
|
|
{
|
|
qreal xp = x();
|
|
for (Element* e = parent(); e; e = e->parent())
|
|
xp += e->x();
|
|
return xp;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// contains
|
|
// Return true if p is inside the shape of the object.
|
|
// Note: p is in page coordinates
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::contains(const QPointF& p) const
|
|
{
|
|
return shape().contains(p - pagePos());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// intersects
|
|
// Return true if \a rr intersects bounding box of object.
|
|
// Note: \a rr is in page coordinates
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::intersects(const QRectF& rr) const
|
|
{
|
|
return shape().intersects(rr.translated(-pagePos()));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// writeProperties
|
|
//---------------------------------------------------------
|
|
|
|
void Element::writeProperties(XmlWriter& xml) const
|
|
{
|
|
// copy paste should not keep links
|
|
if (_links && (_links->size() > 1) && !xml.clipboardmode()) {
|
|
if (MScore::debugMode)
|
|
xml.tag("lid", _links->lid());
|
|
Element* me = static_cast<Element*>(_links->mainElement());
|
|
Q_ASSERT(type() == me->type());
|
|
Staff* s = staff();
|
|
if (!s) {
|
|
s = score()->staff(xml.curTrack() / VOICES);
|
|
if (!s)
|
|
qWarning("Element::writeProperties: linked element's staff not found (%s)", name());
|
|
}
|
|
Location loc = Location::positionForElement(this);
|
|
if (me == this) {
|
|
xml.tagE("linkedMain");
|
|
xml.setLidLocalIndex(_links->lid(), xml.assignLocalIndex(loc));
|
|
}
|
|
else {
|
|
if (s->links()) {
|
|
Staff* linkedStaff = toStaff(s->links()->mainElement());
|
|
loc.setStaff(linkedStaff->idx());
|
|
}
|
|
xml.stag("linked");
|
|
if (!me->score()->isMaster()) {
|
|
if (me->score() == score()) {
|
|
xml.tag("score", "same");
|
|
}
|
|
else {
|
|
qWarning("Element::writeProperties: linked elements belong to different scores but none of them is master score: (%s lid=%d)", name(), _links->lid());
|
|
}
|
|
}
|
|
Location mainLoc = Location::positionForElement(me);
|
|
const int guessedLocalIndex = xml.assignLocalIndex(mainLoc);
|
|
if (loc != mainLoc) {
|
|
mainLoc.toRelative(loc);
|
|
mainLoc.write(xml);
|
|
}
|
|
const int indexDiff = xml.lidLocalIndex(_links->lid()) - guessedLocalIndex;
|
|
xml.tag("indexDiff", indexDiff, 0);
|
|
xml.etag(); // </linked>
|
|
}
|
|
}
|
|
if ((track() != xml.curTrack()) && (track() != -1) && !isBeam()) {
|
|
// Writing track number for beams is redundant as it is calculated
|
|
// during layout.
|
|
int t;
|
|
t = track() + xml.trackDiff();
|
|
xml.tag("track", t);
|
|
}
|
|
if (_tag != 0x1) {
|
|
for (int i = 1; i < MAX_TAGS; i++) {
|
|
if (_tag == ((unsigned)1 << i)) {
|
|
xml.tag("tag", score()->layerTags()[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (propertyFlags(Pid::OFFSET) == PropertyFlags::NOSTYLE)
|
|
writeProperty(xml, Pid::OFFSET);
|
|
|
|
for (Pid pid : { Pid::COLOR, Pid::VISIBLE, Pid::Z, Pid::PLACEMENT}) {
|
|
if (propertyFlags(pid) == PropertyFlags::NOSTYLE)
|
|
writeProperty(xml, pid);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readProperties
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::readProperties(XmlReader& e)
|
|
{
|
|
const QStringRef& tag(e.name());
|
|
|
|
if (readProperty(tag, e, Pid::SIZE_SPATIUM_DEPENDENT))
|
|
;
|
|
else if (readProperty(tag, e, Pid::OFFSET))
|
|
;
|
|
else if (tag == "track")
|
|
setTrack(e.readInt() + e.trackOffset());
|
|
else if (tag == "color")
|
|
setColor(e.readColor());
|
|
else if (tag == "visible")
|
|
setVisible(e.readInt());
|
|
else if (tag == "selected") // obsolete
|
|
e.readInt();
|
|
else if ((tag == "linked") || (tag == "linkedMain")) {
|
|
Staff* s = staff();
|
|
if (!s) {
|
|
s = score()->staff(e.track() / VOICES);
|
|
if (!s) {
|
|
qWarning("Element::readProperties: linked element's staff not found (%s)", name());
|
|
e.skipCurrentElement();
|
|
return true;
|
|
}
|
|
}
|
|
if (tag == "linkedMain") {
|
|
_links = new LinkedElements(score());
|
|
_links->push_back(this);
|
|
e.addLink(s, _links);
|
|
e.readNext();
|
|
}
|
|
else {
|
|
Staff* ls = s->links() ? toStaff(s->links()->mainElement()) : nullptr;
|
|
bool linkedIsMaster = ls ? ls->score()->isMaster() : false;
|
|
Location loc = e.location(true);
|
|
if (ls)
|
|
loc.setStaff(ls->idx());
|
|
Location mainLoc = Location::relative();
|
|
bool locationRead = false;
|
|
int localIndexDiff = 0;
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& ntag(e.name());
|
|
|
|
if (ntag == "score") {
|
|
QString val(e.readElementText());
|
|
if (val == "same")
|
|
linkedIsMaster = score()->isMaster();
|
|
}
|
|
else if (ntag == "location") {
|
|
mainLoc.read(e);
|
|
mainLoc.toAbsolute(loc);
|
|
locationRead = true;
|
|
}
|
|
else if (ntag == "indexDiff")
|
|
localIndexDiff = e.readInt();
|
|
else
|
|
e.unknown();
|
|
}
|
|
if (!locationRead)
|
|
mainLoc = loc;
|
|
LinkedElements* link = e.getLink(linkedIsMaster, mainLoc, localIndexDiff);
|
|
if (link) {
|
|
ScoreElement* linked = link->mainElement();
|
|
if (linked->type() == type())
|
|
linkTo(linked);
|
|
else
|
|
qWarning("Element::readProperties: linked elements have different types: %s, %s. Input file corrupted?", name(), linked->name());
|
|
}
|
|
if (!_links)
|
|
qWarning("Element::readProperties: could not link %s at staff %d", name(), mainLoc.staff() + 1);
|
|
}
|
|
}
|
|
else if (tag == "lid") {
|
|
if (score()->mscVersion() >= 301) {
|
|
e.skipCurrentElement();
|
|
return true;
|
|
}
|
|
int id = e.readInt();
|
|
_links = e.linkIds().value(id);
|
|
if (!_links) {
|
|
if (!score()->isMaster()) // DEBUG
|
|
qDebug("---link %d not found (%d)", id, e.linkIds().size());
|
|
_links = new LinkedElements(score(), id);
|
|
e.linkIds().insert(id, _links);
|
|
}
|
|
#ifndef NDEBUG
|
|
else {
|
|
for (ScoreElement* eee : *_links) {
|
|
Element* ee = static_cast<Element*>(eee);
|
|
if (ee->type() != type()) {
|
|
qFatal("link %s(%d) type mismatch %s linked to %s",
|
|
ee->name(), id, ee->name(), name());
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
Q_ASSERT(!_links->contains(this));
|
|
_links->append(this);
|
|
}
|
|
else if (tag == "tick") {
|
|
int val = e.readInt();
|
|
if (val >= 0)
|
|
e.initTick(score()->fileDivision(val));
|
|
}
|
|
else if (tag == "pos") // obsolete
|
|
readProperty(e, Pid::OFFSET);
|
|
else if (tag == "voice")
|
|
setTrack((_track/VOICES)*VOICES + e.readInt());
|
|
else if (tag == "tag") {
|
|
QString val(e.readElementText());
|
|
for (int i = 1; i < MAX_TAGS; i++) {
|
|
if (score()->layerTags()[i] == val) {
|
|
_tag = 1 << i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (readProperty(tag, e, Pid::PLACEMENT))
|
|
;
|
|
else if (tag == "z")
|
|
setZ(e.readInt());
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// write
|
|
//---------------------------------------------------------
|
|
|
|
void Element::write(XmlWriter& xml) const
|
|
{
|
|
xml.stag(name());
|
|
writeProperties(xml);
|
|
xml.etag();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// read
|
|
//---------------------------------------------------------
|
|
|
|
void Element::read(XmlReader& e)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
if (!readProperties(e))
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// remove
|
|
/// Remove \a el from the list. Return true on success.
|
|
//---------------------------------------------------------
|
|
|
|
bool ElementList::remove(Element* el)
|
|
{
|
|
auto i = find(begin(), end(), el);
|
|
if (i == end())
|
|
return false;
|
|
erase(i);
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// replace
|
|
//---------------------------------------------------------
|
|
|
|
void ElementList::replace(Element* o, Element* n)
|
|
{
|
|
auto i = find(begin(), end(), o);
|
|
if (i == end()) {
|
|
qDebug("ElementList::replace: element not found");
|
|
return;
|
|
}
|
|
*i = n;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// write
|
|
//---------------------------------------------------------
|
|
|
|
void ElementList::write(XmlWriter& xml) const
|
|
{
|
|
for (const Element* e : *this)
|
|
e->write(xml);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Compound
|
|
//---------------------------------------------------------
|
|
|
|
Compound::Compound(Score* s)
|
|
: Element(s)
|
|
{
|
|
}
|
|
|
|
Compound::Compound(const Compound& c)
|
|
: Element(c)
|
|
{
|
|
elements.clear();
|
|
foreach(Element* e, c.elements)
|
|
elements.append(e->clone());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// draw
|
|
//---------------------------------------------------------
|
|
|
|
void Compound::draw(QPainter* painter) const
|
|
{
|
|
foreach(Element* e, elements) {
|
|
QPointF pt(e->pos());
|
|
painter->translate(pt);
|
|
e->draw(painter);
|
|
painter->translate(-pt);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// addElement
|
|
//---------------------------------------------------------
|
|
|
|
/**
|
|
offset \a x and \a y are in Point units
|
|
*/
|
|
|
|
void Compound::addElement(Element* e, qreal x, qreal y)
|
|
{
|
|
e->setPos(x, y);
|
|
e->setParent(this);
|
|
elements.push_back(e);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layout
|
|
//---------------------------------------------------------
|
|
|
|
void Compound::layout()
|
|
{
|
|
setbbox(QRectF());
|
|
for (auto i = elements.begin(); i != elements.end(); ++i) {
|
|
Element* e = *i;
|
|
e->layout();
|
|
addbbox(e->bbox().translated(e->pos()));
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setSelected
|
|
//---------------------------------------------------------
|
|
|
|
void Compound::setSelected(bool f)
|
|
{
|
|
Element::setSelected(f);
|
|
for (auto i = elements.begin(); i != elements.end(); ++i)
|
|
(*i)->setSelected(f);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setVisible
|
|
//---------------------------------------------------------
|
|
|
|
void Compound::setVisible(bool f)
|
|
{
|
|
Element::setVisible(f);
|
|
for (auto i = elements.begin(); i != elements.end(); ++i)
|
|
(*i)->setVisible(f);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// clear
|
|
//---------------------------------------------------------
|
|
|
|
void Compound::clear()
|
|
{
|
|
foreach(Element* e, elements) {
|
|
if (e->selected())
|
|
score()->deselect(e);
|
|
delete e;
|
|
}
|
|
elements.clear();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// dump
|
|
//---------------------------------------------------------
|
|
|
|
void Element::dump() const
|
|
{
|
|
qDebug("---Element: %s, pos(%4.2f,%4.2f)"
|
|
"\n bbox(%g,%g,%g,%g)"
|
|
"\n abox(%g,%g,%g,%g)"
|
|
"\n parent: %p",
|
|
name(), ipos().x(), ipos().y(),
|
|
_bbox.x(), _bbox.y(), _bbox.width(), _bbox.height(),
|
|
abbox().x(), abbox().y(), abbox().width(), abbox().height(),
|
|
parent());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// mimeData
|
|
//---------------------------------------------------------
|
|
|
|
QByteArray Element::mimeData(const QPointF& dragOffset) const
|
|
{
|
|
QBuffer buffer;
|
|
buffer.open(QIODevice::WriteOnly);
|
|
XmlWriter xml(score(), &buffer);
|
|
xml.setClipboardmode(true);
|
|
xml.stag("Element");
|
|
if (isNote())
|
|
xml.tag("duration", toNote(this)->chord()->duration());
|
|
if (!dragOffset.isNull())
|
|
xml.tag("dragOffset", dragOffset);
|
|
write(xml);
|
|
xml.etag();
|
|
buffer.close();
|
|
return buffer.buffer();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readType
|
|
// return new position of QDomElement in e
|
|
//---------------------------------------------------------
|
|
|
|
ElementType Element::readType(XmlReader& e, QPointF* dragOffset,
|
|
Fraction* duration)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
if (e.name() == "Element")
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag = e.name();
|
|
if (tag == "dragOffset")
|
|
*dragOffset = e.readPoint();
|
|
else if (tag == "duration")
|
|
*duration = e.readFraction();
|
|
else {
|
|
ElementType type = name2type(tag);
|
|
if (type == ElementType::INVALID)
|
|
break;
|
|
return type;
|
|
}
|
|
}
|
|
else
|
|
e.unknown();
|
|
}
|
|
return ElementType::INVALID;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// add
|
|
//---------------------------------------------------------
|
|
|
|
void Element::add(Element* e)
|
|
{
|
|
qDebug("Element: cannot add %s to %s", e->name(), name());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// remove
|
|
//---------------------------------------------------------
|
|
|
|
void Element::remove(Element* e)
|
|
{
|
|
qFatal("Element: cannot remove %s from %s", e->name(), name());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// create
|
|
// Element factory
|
|
//---------------------------------------------------------
|
|
|
|
Element* Element::create(ElementType type, Score* score)
|
|
{
|
|
switch (type) {
|
|
case ElementType::VOLTA: return new Volta(score);
|
|
case ElementType::OTTAVA: return new Ottava(score);
|
|
case ElementType::TEXTLINE: return new TextLine(score);
|
|
case ElementType::NOTELINE: return new NoteLine(score);
|
|
case ElementType::LYRICSLINE: return new LyricsLine(score);
|
|
case ElementType::TRILL: return new Trill(score);
|
|
case ElementType::LET_RING: return new LetRing(score);
|
|
case ElementType::VIBRATO: return new Vibrato(score);
|
|
case ElementType::PALM_MUTE: return new PalmMute(score);
|
|
case ElementType::PEDAL: return new Pedal(score);
|
|
case ElementType::HAIRPIN: return new Hairpin(score);
|
|
case ElementType::CLEF: return new Clef(score);
|
|
case ElementType::KEYSIG: return new KeySig(score);
|
|
case ElementType::TIMESIG: return new TimeSig(score);
|
|
case ElementType::BAR_LINE: return new BarLine(score);
|
|
case ElementType::SYSTEM_DIVIDER: return new SystemDivider(score);
|
|
case ElementType::ARPEGGIO: return new Arpeggio(score);
|
|
case ElementType::BREATH: return new Breath(score);
|
|
case ElementType::GLISSANDO: return new Glissando(score);
|
|
case ElementType::BRACKET: return new Bracket(score);
|
|
case ElementType::ARTICULATION: return new Articulation(score);
|
|
case ElementType::FERMATA: return new Fermata(score);
|
|
case ElementType::CHORDLINE: return new ChordLine(score);
|
|
case ElementType::ACCIDENTAL: return new Accidental(score);
|
|
case ElementType::DYNAMIC: return new Dynamic(score);
|
|
case ElementType::TEXT: return new Text(score);
|
|
case ElementType::MEASURE_NUMBER: return new MeasureNumber(score);
|
|
case ElementType::INSTRUMENT_NAME: return new InstrumentName(score);
|
|
case ElementType::STAFF_TEXT: return new StaffText(score);
|
|
case ElementType::SYSTEM_TEXT: return new SystemText(score);
|
|
case ElementType::REHEARSAL_MARK: return new RehearsalMark(score);
|
|
case ElementType::INSTRUMENT_CHANGE: return new InstrumentChange(score);
|
|
case ElementType::STAFFTYPE_CHANGE: return new StaffTypeChange(score);
|
|
case ElementType::NOTEHEAD: return new NoteHead(score);
|
|
case ElementType::NOTEDOT: return new NoteDot(score);
|
|
case ElementType::TREMOLO: return new Tremolo(score);
|
|
case ElementType::LAYOUT_BREAK: return new LayoutBreak(score);
|
|
case ElementType::MARKER: return new Marker(score);
|
|
case ElementType::JUMP: return new Jump(score);
|
|
case ElementType::REPEAT_MEASURE: return new RepeatMeasure(score);
|
|
case ElementType::ICON: return new Icon(score);
|
|
case ElementType::NOTE: return new Note(score);
|
|
case ElementType::SYMBOL: return new Symbol(score);
|
|
case ElementType::FSYMBOL: return new FSymbol(score);
|
|
case ElementType::CHORD: return new Chord(score);
|
|
case ElementType::REST: return new Rest(score);
|
|
case ElementType::SPACER: return new Spacer(score);
|
|
case ElementType::STAFF_STATE: return new StaffState(score);
|
|
case ElementType::TEMPO_TEXT: return new TempoText(score);
|
|
case ElementType::HARMONY: return new Harmony(score);
|
|
case ElementType::FRET_DIAGRAM: return new FretDiagram(score);
|
|
case ElementType::BEND: return new Bend(score);
|
|
case ElementType::TREMOLOBAR: return new TremoloBar(score);
|
|
case ElementType::LYRICS: return new Lyrics(score);
|
|
case ElementType::FIGURED_BASS: return new FiguredBass(score);
|
|
case ElementType::STEM: return new Stem(score);
|
|
case ElementType::SLUR: return new Slur(score);
|
|
case ElementType::TIE: return new Tie(score);
|
|
case ElementType::FINGERING: return new Fingering(score);
|
|
case ElementType::HBOX: return new HBox(score);
|
|
case ElementType::VBOX: return new VBox(score);
|
|
case ElementType::TBOX: return new TBox(score);
|
|
case ElementType::FBOX: return new FBox(score);
|
|
case ElementType::MEASURE: return new Measure(score);
|
|
case ElementType::TAB_DURATION_SYMBOL: return new TabDurationSymbol(score);
|
|
case ElementType::OSSIA: return new Ossia(score);
|
|
case ElementType::IMAGE: return new Image(score);
|
|
case ElementType::BAGPIPE_EMBELLISHMENT: return new BagpipeEmbellishment(score);
|
|
case ElementType::AMBITUS: return new Ambitus(score);
|
|
|
|
case ElementType::TEXTLINE_BASE:
|
|
case ElementType::TEXTLINE_SEGMENT:
|
|
case ElementType::GLISSANDO_SEGMENT:
|
|
case ElementType::SLUR_SEGMENT:
|
|
case ElementType::TIE_SEGMENT:
|
|
case ElementType::STEM_SLASH:
|
|
case ElementType::PAGE:
|
|
case ElementType::BEAM:
|
|
case ElementType::HOOK:
|
|
case ElementType::TUPLET:
|
|
case ElementType::HAIRPIN_SEGMENT:
|
|
case ElementType::OTTAVA_SEGMENT:
|
|
case ElementType::TRILL_SEGMENT:
|
|
case ElementType::LET_RING_SEGMENT:
|
|
case ElementType::VIBRATO_SEGMENT:
|
|
case ElementType::PALM_MUTE_SEGMENT:
|
|
case ElementType::VOLTA_SEGMENT:
|
|
case ElementType::PEDAL_SEGMENT:
|
|
case ElementType::LYRICSLINE_SEGMENT:
|
|
case ElementType::LEDGER_LINE:
|
|
case ElementType::STAFF_LINES:
|
|
case ElementType::SELECTION:
|
|
case ElementType::LASSO:
|
|
case ElementType::SHADOW_NOTE:
|
|
case ElementType::SEGMENT:
|
|
case ElementType::SYSTEM:
|
|
case ElementType::COMPOUND:
|
|
case ElementType::ELEMENT:
|
|
case ElementType::ELEMENT_LIST:
|
|
case ElementType::STAFF_LIST:
|
|
case ElementType::MEASURE_LIST:
|
|
case ElementType::MAXTYPE:
|
|
case ElementType::INVALID:
|
|
case ElementType::PART:
|
|
case ElementType::STAFF:
|
|
case ElementType::SCORE:
|
|
case ElementType::BRACKET_ITEM:
|
|
break;
|
|
}
|
|
qDebug("cannot create type %d <%s>", int(type), Element::name(type));
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// name2Element
|
|
//---------------------------------------------------------
|
|
|
|
Element* Element::name2Element(const QStringRef& s, Score* sc)
|
|
{
|
|
ElementType type = Element::name2type(s);
|
|
if (type == ElementType::INVALID) {
|
|
qDebug("invalid <%s>\n", qPrintable(s.toString()));
|
|
return 0;
|
|
}
|
|
return Element::create(type, sc);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// elementLessThan
|
|
//---------------------------------------------------------
|
|
|
|
bool elementLessThan(const Element* const e1, const Element* const e2)
|
|
{
|
|
return e1->z() <= e2->z();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// collectElements
|
|
//---------------------------------------------------------
|
|
|
|
void collectElements(void* data, Element* e)
|
|
{
|
|
QList<Element*>* el = static_cast<QList<Element*>*>(data);
|
|
el->append(e);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getProperty
|
|
//---------------------------------------------------------
|
|
|
|
QVariant Element::getProperty(Pid propertyId) const
|
|
{
|
|
switch (propertyId) {
|
|
case Pid::TRACK:
|
|
return track();
|
|
case Pid::GENERATED:
|
|
return generated();
|
|
case Pid::COLOR:
|
|
return color();
|
|
case Pid::VISIBLE:
|
|
return visible();
|
|
case Pid::SELECTED:
|
|
return selected();
|
|
case Pid::OFFSET:
|
|
return _offset;
|
|
case Pid::PLACEMENT:
|
|
return int(placement());
|
|
case Pid::AUTOPLACE:
|
|
return autoplace();
|
|
case Pid::Z:
|
|
return z();
|
|
case Pid::SYSTEM_FLAG:
|
|
return systemFlag();
|
|
case Pid::SIZE_SPATIUM_DEPENDENT:
|
|
return sizeIsSpatiumDependent();
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setProperty
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::setProperty(Pid propertyId, const QVariant& v)
|
|
{
|
|
switch (propertyId) {
|
|
case Pid::TRACK:
|
|
setTrack(v.toInt());
|
|
break;
|
|
case Pid::GENERATED:
|
|
setGenerated(v.toBool());
|
|
break;
|
|
case Pid::COLOR:
|
|
setColor(v.value<QColor>());
|
|
break;
|
|
case Pid::VISIBLE:
|
|
setVisible(v.toBool());
|
|
break;
|
|
case Pid::SELECTED:
|
|
setSelected(v.toBool());
|
|
break;
|
|
case Pid::OFFSET:
|
|
_offset = v.toPointF();
|
|
break;
|
|
case Pid::PLACEMENT:
|
|
setPlacement(Placement(v.toInt()));
|
|
break;
|
|
case Pid::AUTOPLACE:
|
|
setAutoplace(v.toBool());
|
|
break;
|
|
case Pid::Z:
|
|
setZ(v.toInt());
|
|
break;
|
|
case Pid::SYSTEM_FLAG:
|
|
setSystemFlag(v.toBool());
|
|
break;
|
|
case Pid::SIZE_SPATIUM_DEPENDENT:
|
|
setSizeIsSpatiumDependent(v.toBool());
|
|
break;
|
|
default:
|
|
// qFatal("<%s> unknown <%s>(%d), data <%s>", name(), propertyQmlName(propertyId), int(propertyId), qPrintable(v.toString()));
|
|
qDebug("%s unknown <%s>(%d), data <%s>", name(), propertyQmlName(propertyId), int(propertyId), qPrintable(v.toString()));
|
|
return false;
|
|
}
|
|
triggerLayout();
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// propertyDefault
|
|
//---------------------------------------------------------
|
|
|
|
QVariant Element::propertyDefault(Pid pid) const
|
|
{
|
|
switch (pid) {
|
|
case Pid::GENERATED:
|
|
return false;
|
|
case Pid::VISIBLE:
|
|
return true;
|
|
case Pid::COLOR:
|
|
return MScore::defaultColor;
|
|
case Pid::PLACEMENT:
|
|
return int(Placement::BELOW);
|
|
case Pid::SELECTED:
|
|
return false;
|
|
case Pid::OFFSET: {
|
|
QVariant v = ScoreElement::propertyDefault(pid);
|
|
if (v.isValid()) // if its a styled property
|
|
return v;
|
|
return QPointF();
|
|
}
|
|
case Pid::AUTOPLACE:
|
|
return true;
|
|
case Pid::Z:
|
|
return int(type()) * 100;
|
|
default:
|
|
return ScoreElement::propertyDefault(pid);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// custom
|
|
// check if property is != default
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::custom(Pid id) const
|
|
{
|
|
return propertyDefault(id) != getProperty(id);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// isPrintable
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::isPrintable() const
|
|
{
|
|
switch (type()) {
|
|
case ElementType::PAGE:
|
|
case ElementType::SYSTEM:
|
|
case ElementType::MEASURE:
|
|
case ElementType::SEGMENT:
|
|
case ElementType::VBOX:
|
|
case ElementType::HBOX:
|
|
case ElementType::TBOX:
|
|
case ElementType::FBOX:
|
|
case ElementType::SPACER:
|
|
case ElementType::SHADOW_NOTE:
|
|
case ElementType::LASSO:
|
|
case ElementType::ELEMENT_LIST:
|
|
case ElementType::STAFF_LIST:
|
|
case ElementType::MEASURE_LIST:
|
|
case ElementType::SELECTION:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// findMeasure
|
|
//---------------------------------------------------------
|
|
|
|
Element* Element::findMeasure()
|
|
{
|
|
if (isMeasure())
|
|
return this;
|
|
else if (_parent)
|
|
return _parent->findMeasure();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// findMeasure
|
|
//---------------------------------------------------------
|
|
|
|
const Element* Element::findMeasure() const
|
|
{
|
|
Element* e = const_cast<Element*>(this);
|
|
return e->findMeasure();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// findMeasureBase
|
|
//---------------------------------------------------------
|
|
|
|
MeasureBase* Element::findMeasureBase()
|
|
{
|
|
if (isMeasureBase())
|
|
return toMeasureBase(this);
|
|
else if (_parent)
|
|
return _parent->findMeasureBase();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// findMeasureBase
|
|
//---------------------------------------------------------
|
|
|
|
const MeasureBase* Element::findMeasureBase() const
|
|
{
|
|
Element* e = const_cast<Element*>(this);
|
|
return e->findMeasureBase();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// undoSetColor
|
|
//---------------------------------------------------------
|
|
|
|
void Element::undoSetColor(const QColor& c)
|
|
{
|
|
undoChangeProperty(Pid::COLOR, c);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// undoSetVisible
|
|
//---------------------------------------------------------
|
|
|
|
void Element::undoSetVisible(bool v)
|
|
{
|
|
undoChangeProperty(Pid::VISIBLE, v);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// drawSymbol
|
|
//---------------------------------------------------------
|
|
|
|
void Element::drawSymbol(SymId id, QPainter* p, const QPointF& o, qreal scale) const
|
|
{
|
|
score()->scoreFont()->draw(id, p, magS() * scale, o);
|
|
}
|
|
|
|
void Element::drawSymbol(SymId id, QPainter* p, const QPointF& o, int n) const
|
|
{
|
|
score()->scoreFont()->draw(id, p, magS(), o, n);
|
|
}
|
|
|
|
void Element::drawSymbols(const std::vector<SymId>& s, QPainter* p, const QPointF& o, qreal scale) const
|
|
{
|
|
score()->scoreFont()->draw(s, p, magS() * scale, o);
|
|
}
|
|
|
|
void Element::drawSymbols(const std::vector<SymId>& s, QPainter* p, const QPointF& o, const QSizeF& scale) const
|
|
{
|
|
score()->scoreFont()->draw(s, p, magS() * scale, o);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// symHeight
|
|
//---------------------------------------------------------
|
|
|
|
qreal Element::symHeight(SymId id) const
|
|
{
|
|
return score()->scoreFont()->height(id, magS());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// symWidth
|
|
//---------------------------------------------------------
|
|
|
|
qreal Element::symWidth(SymId id) const
|
|
{
|
|
return score()->scoreFont()->width(id, magS());
|
|
}
|
|
qreal Element::symWidth(const std::vector<SymId>& s) const
|
|
{
|
|
return score()->scoreFont()->width(s, magS());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// symAdvance
|
|
//---------------------------------------------------------
|
|
|
|
qreal Element::symAdvance(SymId id) const
|
|
{
|
|
return score()->scoreFont()->advance(id, magS());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// symBbox
|
|
//---------------------------------------------------------
|
|
|
|
QRectF Element::symBbox(SymId id) const
|
|
{
|
|
return score()->scoreFont()->bbox(id, magS());
|
|
}
|
|
|
|
QRectF Element::symBbox(const std::vector<SymId>& s) const
|
|
{
|
|
return score()->scoreFont()->bbox(s, magS());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// symStemDownNW
|
|
//---------------------------------------------------------
|
|
|
|
QPointF Element::symStemDownNW(SymId id) const
|
|
{
|
|
return score()->scoreFont()->stemDownNW(id, magS());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// symStemUpSE
|
|
//---------------------------------------------------------
|
|
|
|
QPointF Element::symStemUpSE(SymId id) const
|
|
{
|
|
return score()->scoreFont()->stemUpSE(id, magS());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// symCutOutNE / symCutOutNW / symCutOutSE / symCutOutNW
|
|
//---------------------------------------------------------
|
|
|
|
QPointF Element::symCutOutNE(SymId id) const
|
|
{
|
|
return score()->scoreFont()->cutOutNE(id, magS());
|
|
}
|
|
|
|
QPointF Element::symCutOutNW(SymId id) const
|
|
{
|
|
return score()->scoreFont()->cutOutNW(id, magS());
|
|
}
|
|
|
|
QPointF Element::symCutOutSE(SymId id) const
|
|
{
|
|
return score()->scoreFont()->cutOutSE(id, magS());
|
|
}
|
|
|
|
QPointF Element::symCutOutSW(SymId id) const
|
|
{
|
|
return score()->scoreFont()->cutOutSW(id, magS());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// symIsValid
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::symIsValid(SymId id) const
|
|
{
|
|
return score()->scoreFont()->isValid(id);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// concertPitch
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::concertPitch() const
|
|
{
|
|
return score()->styleB(Sid::concertPitch);
|
|
}
|
|
//---------------------------------------------------------
|
|
// nextElement
|
|
// selects the next score element
|
|
//---------------------------------------------------------
|
|
|
|
Element* Element::nextElement()
|
|
{
|
|
Element* e = score()->selection().element();
|
|
if (!e && !score()->selection().elements().isEmpty())
|
|
e = score()->selection().elements().first();
|
|
if (e) {
|
|
switch (e->type()) {
|
|
case ElementType::SEGMENT: {
|
|
Segment* s = toSegment(e);
|
|
return s->nextElement(staffIdx());
|
|
}
|
|
case ElementType::MEASURE: {
|
|
Measure* m = toMeasure(e);
|
|
return m->nextElementStaff(staffIdx());
|
|
}
|
|
case ElementType::CLEF:
|
|
case ElementType::KEYSIG:
|
|
case ElementType::TIMESIG:
|
|
case ElementType::BAR_LINE:
|
|
return nextSegmentElement();
|
|
default: {
|
|
return e->parent()->nextElement();
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// prevElement
|
|
// selects the previous score element
|
|
//---------------------------------------------------------
|
|
|
|
Element* Element::prevElement()
|
|
{
|
|
Element* e = score()->selection().element();
|
|
if (!e && !score()->selection().elements().isEmpty() )
|
|
e = score()->selection().elements().last();
|
|
if (e) {
|
|
switch(e->type()) {
|
|
case ElementType::SEGMENT: {
|
|
Segment* s = toSegment(e);
|
|
return s->prevElement(staffIdx());
|
|
}
|
|
case ElementType::MEASURE: {
|
|
Measure* m = toMeasure(e);
|
|
return m->prevElementStaff(staffIdx());
|
|
}
|
|
case ElementType::CLEF:
|
|
case ElementType::KEYSIG:
|
|
case ElementType::TIMESIG:
|
|
case ElementType::BAR_LINE:
|
|
return prevSegmentElement();
|
|
default: {
|
|
return e->parent()->prevElement();
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
|
// nextSegmentElement
|
|
// This function is used in for the next-element command to navigate between main elements
|
|
// of segments. (Note, Rest, Clef, Time Signature, Key Signature, Barline, Ambitus, Breath, etc.)
|
|
// The default implementation is to look for the first such element. After it is found each
|
|
// element knows how to find the next one and overrides this method
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
Element* Element::nextSegmentElement()
|
|
{
|
|
Element* p = this;
|
|
while (p) {
|
|
switch (p->type()) {
|
|
case ElementType::NOTE:
|
|
if (toNote(p)->chord()->isGrace())
|
|
break;
|
|
return p;
|
|
case ElementType::REST:
|
|
return p;
|
|
case ElementType::CHORD: {
|
|
Chord* c = toChord(p);
|
|
if (!c->isGrace())
|
|
return c->notes().back();
|
|
}
|
|
break;
|
|
case ElementType::SEGMENT: {
|
|
Segment* s = toSegment(p);
|
|
return s->firstElement(staffIdx());
|
|
}
|
|
case ElementType::MEASURE: {
|
|
Measure* m = toMeasure(p);
|
|
return m->nextElementStaff(staffIdx());
|
|
}
|
|
case ElementType::SYSTEM: {
|
|
System* sys = toSystem(p);
|
|
return sys->nextSegmentElement();
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
p = p->parent();
|
|
}
|
|
return score()->firstElement();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------------------
|
|
// prevSegmentElement
|
|
// This function is used in for the prev-element command to navigate between main elements
|
|
// of segments. (Note, Rest, Clef, Time Signature, Key Signature, Barline, Ambitus, Breath, etc.)
|
|
// The default implementation is to look for the first such element. After it is found each
|
|
// element knows how to find the previous one and overrides this method
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
Element* Element::prevSegmentElement()
|
|
{
|
|
Element* p = this;
|
|
while (p) {
|
|
switch (p->type()) {
|
|
case ElementType::NOTE:
|
|
if (toNote(p)->chord()->isGrace())
|
|
break;
|
|
return p;
|
|
case ElementType::REST:
|
|
return p;
|
|
case ElementType::CHORD: {
|
|
Chord* c = toChord(p);
|
|
if (!c->isGrace())
|
|
return c->notes().front();
|
|
}
|
|
break;
|
|
case ElementType::SEGMENT: {
|
|
Segment* s = toSegment(p);
|
|
return s->lastElement(staffIdx());
|
|
}
|
|
case ElementType::MEASURE: {
|
|
Measure* m = toMeasure(p);
|
|
return m->prevElementStaff(staffIdx());
|
|
}
|
|
case ElementType::SYSTEM: {
|
|
System* sys = toSystem(p);
|
|
return sys->prevSegmentElement();
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
p = p->parent();
|
|
}
|
|
return score()->firstElement();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// accessibleInfo
|
|
//---------------------------------------------------------
|
|
|
|
QString Element::accessibleInfo() const
|
|
{
|
|
return Element::userName();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// nextGrip
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::nextGrip(EditData& ed) const
|
|
{
|
|
int i = int(ed.curGrip) + 1;
|
|
if (i >= ed.grips) {
|
|
ed.curGrip = Grip(0);
|
|
return false;
|
|
}
|
|
ed.curGrip = Grip(i);
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// prevGrip
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::prevGrip(EditData& ed) const
|
|
{
|
|
int i = int(ed.curGrip) - 1;
|
|
if (i < 0) {
|
|
ed.curGrip = Grip(ed.grips - 1);
|
|
return false;
|
|
}
|
|
ed.curGrip = Grip(i);
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// isUserModified
|
|
// Check if this element was modified by user and
|
|
// therefore must be saved.
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::isUserModified() const
|
|
{
|
|
for (const StyledProperty& spp : *styledProperties()) {
|
|
Pid pid = spp.pid;
|
|
QVariant val = getProperty(pid);
|
|
QVariant defaultValue = propertyDefault(pid);
|
|
|
|
if (propertyType(pid) == P_TYPE::SP_REAL) {
|
|
if (qAbs(val.toReal() - defaultValue.toReal()) > 0.0001) // we dont care spatium diffs that small
|
|
return true;
|
|
}
|
|
else {
|
|
if (getProperty(pid) != propertyDefault(pid))
|
|
return true;
|
|
}
|
|
}
|
|
return !visible() || !offset().isNull() || (color() != MScore::defaultColor);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// tick
|
|
// utility, searches for segment / segment parent
|
|
//---------------------------------------------------------
|
|
|
|
int Element::tick() const
|
|
{
|
|
const Element* e = this;
|
|
while (e) {
|
|
if (e->isSegment())
|
|
return toSegment(e)->tick();
|
|
else if (e->isMeasureBase())
|
|
return toMeasureBase(e)->tick();
|
|
e = e->parent();
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// rtick
|
|
// utility, searches for segment / segment parent
|
|
//---------------------------------------------------------
|
|
|
|
int Element::rtick() const
|
|
{
|
|
const Element* e = this;
|
|
while (e) {
|
|
if (e->isSegment())
|
|
return toSegment(e)->rtick();
|
|
e = e->parent();
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// rfrac
|
|
// Position of element in fractions relative to a
|
|
// measure start.
|
|
//---------------------------------------------------------
|
|
|
|
Fraction Element::rfrac() const
|
|
{
|
|
const Element* e = this;
|
|
while (e) {
|
|
if (e->isSegment())
|
|
return toSegment(e)->rfrac();
|
|
else if (e->isMeasureBase())
|
|
return toMeasureBase(e)->rfrac();
|
|
e = e->parent();
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// afrac
|
|
// Absolute position of element in fractions.
|
|
//---------------------------------------------------------
|
|
|
|
Fraction Element::afrac() const
|
|
{
|
|
const Element* e = this;
|
|
while (e) {
|
|
if (e->isSegment())
|
|
return toSegment(e)->afrac();
|
|
else if (e->isMeasureBase())
|
|
return toMeasureBase(e)->afrac();
|
|
e = e->parent();
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// triggerLayout
|
|
//---------------------------------------------------------
|
|
|
|
void Element::triggerLayout() const
|
|
{
|
|
score()->setLayout(tick());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// init
|
|
//---------------------------------------------------------
|
|
|
|
void EditData::init()
|
|
{
|
|
grip.clear();
|
|
grips = 0;
|
|
curGrip = Grip(0);
|
|
pos = QPointF();
|
|
startMove = QPointF();
|
|
lastPos = QPointF();
|
|
delta = QPointF();
|
|
hRaster = false;
|
|
vRaster = false;
|
|
key = 0;
|
|
modifiers = 0;
|
|
s.clear();
|
|
|
|
dragOffset = QPointF();
|
|
element = 0;
|
|
duration = Fraction(1,4);
|
|
clearData();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// control
|
|
//---------------------------------------------------------
|
|
|
|
bool EditData::control(bool textEditing) const
|
|
{
|
|
if (textEditing)
|
|
return modifiers & CONTROL_MODIFIER;
|
|
else
|
|
return modifiers & Qt::ControlModifier;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// clearData
|
|
//---------------------------------------------------------
|
|
|
|
void EditData::clearData()
|
|
{
|
|
qDeleteAll(data);
|
|
data.clear();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getData
|
|
//---------------------------------------------------------
|
|
|
|
ElementEditData* EditData::getData(const Element* e) const
|
|
{
|
|
for (ElementEditData* ed : data) {
|
|
if (ed->e == e)
|
|
return ed;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// addData
|
|
//---------------------------------------------------------
|
|
|
|
void EditData::addData(ElementEditData* ed)
|
|
{
|
|
data.push_back(ed);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// drawEditMode
|
|
//---------------------------------------------------------
|
|
|
|
void Element::drawEditMode(QPainter* p, EditData& ed)
|
|
{
|
|
QPen pen(MScore::defaultColor, 0.0);
|
|
p->setPen(pen);
|
|
for (int i = 0; i < ed.grips; ++i) {
|
|
if (Grip(i) == ed.curGrip)
|
|
p->setBrush(MScore::frameMarginColor);
|
|
else
|
|
p->setBrush(Qt::NoBrush);
|
|
p->drawRect(ed.grip[i]);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// startDrag
|
|
//---------------------------------------------------------
|
|
|
|
void Element::startDrag(EditData& ed)
|
|
{
|
|
if (!isMovable())
|
|
return;
|
|
ElementEditData* eed = new ElementEditData();
|
|
eed->e = this;
|
|
eed->pushProperty(Pid::OFFSET);
|
|
ed.addData(eed);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// drag
|
|
/// Return update Rect relative to canvas.
|
|
//---------------------------------------------------------
|
|
|
|
QRectF Element::drag(EditData& ed)
|
|
{
|
|
if (!isMovable())
|
|
return QRectF();
|
|
|
|
QRectF r(canvasBoundingRect());
|
|
|
|
qreal x = ed.delta.x();
|
|
qreal y = ed.delta.y();
|
|
|
|
qreal _spatium = spatium();
|
|
if (ed.hRaster) {
|
|
qreal hRaster = _spatium / MScore::hRaster();
|
|
int n = lrint(x / hRaster);
|
|
x = hRaster * n;
|
|
}
|
|
if (ed.vRaster) {
|
|
qreal vRaster = _spatium / MScore::vRaster();
|
|
int n = lrint(y / vRaster);
|
|
y = vRaster * n;
|
|
}
|
|
|
|
setOffset(QPointF(x, y));
|
|
// setGenerated(false);
|
|
|
|
if (isTextBase()) { // TODO: check for other types
|
|
//
|
|
// restrict move to page boundaries
|
|
//
|
|
Page* p = 0;
|
|
Element* e = this;
|
|
while (e) {
|
|
if (e->isPage()) {
|
|
p = toPage(e);
|
|
break;
|
|
}
|
|
e = e->parent();
|
|
}
|
|
if (p) {
|
|
bool move = false;
|
|
QRectF pr(p->canvasBoundingRect());
|
|
if (r.right() > pr.right()) {
|
|
x -= r.right() - pr.right();
|
|
move = true;
|
|
}
|
|
else if (r.left() < pr.left()) {
|
|
x += pr.left() - r.left();
|
|
move = true;
|
|
}
|
|
if (r.bottom() > pr.bottom()) {
|
|
y -= r.bottom() - pr.bottom();
|
|
move = true;
|
|
}
|
|
else if (r.top() < pr.top()) {
|
|
y += pr.top() - r.top();
|
|
move = true;
|
|
}
|
|
if (move)
|
|
setOffset(QPointF(x, y));
|
|
}
|
|
}
|
|
return canvasBoundingRect() | r;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// endDrag
|
|
//---------------------------------------------------------
|
|
|
|
void Element::endDrag(EditData& ed)
|
|
{
|
|
if (!isMovable())
|
|
return;
|
|
ElementEditData* eed = ed.getData(this);
|
|
for (PropertyData pd : eed->propertyData) {
|
|
PropertyFlags f = propertyFlags(pd.id);
|
|
if (f == PropertyFlags::STYLED)
|
|
f = PropertyFlags::UNSTYLED;
|
|
score()->undoPropertyChanged(this, pd.id, pd.data, f);
|
|
setGenerated(false);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// startEdit
|
|
//---------------------------------------------------------
|
|
|
|
void Element::startEdit(EditData& ed)
|
|
{
|
|
ElementEditData* elementData = new ElementEditData();
|
|
elementData->e = this;
|
|
ed.addData(elementData);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// edit
|
|
// return true if event is accepted
|
|
//---------------------------------------------------------
|
|
|
|
bool Element::edit(EditData& ed)
|
|
{
|
|
if (ed.key == Qt::Key_Home) {
|
|
setOffset(QPoint());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// startEditDrag
|
|
//---------------------------------------------------------
|
|
|
|
void Element::startEditDrag(EditData& ed)
|
|
{
|
|
ElementEditData* eed = ed.getData(this);
|
|
eed->pushProperty(Pid::OFFSET);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// editDrag
|
|
//---------------------------------------------------------
|
|
|
|
void Element::editDrag(EditData& ed)
|
|
{
|
|
score()->addRefresh(canvasBoundingRect());
|
|
setOffset(offset() + ed.delta);
|
|
score()->addRefresh(canvasBoundingRect());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// endEditDrag
|
|
//---------------------------------------------------------
|
|
|
|
void Element::endEditDrag(EditData& ed)
|
|
{
|
|
ElementEditData* eed = ed.getData(this);
|
|
bool changed = false;
|
|
if (eed) {
|
|
for (PropertyData pd : eed->propertyData) {
|
|
if (score()->undoPropertyChanged(this, pd.id, pd.data))
|
|
changed = true;
|
|
}
|
|
eed->propertyData.clear();
|
|
}
|
|
if (changed) {
|
|
undoChangeProperty(Pid::GENERATED, false);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// endEdit
|
|
//---------------------------------------------------------
|
|
|
|
void Element::endEdit(EditData&)
|
|
{
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// styleP
|
|
//---------------------------------------------------------
|
|
|
|
qreal Element::styleP(Sid idx) const
|
|
{
|
|
return score()->styleP(idx);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// autoplaceSegmentElement
|
|
//---------------------------------------------------------
|
|
|
|
#if 0
|
|
void Element::autoplaceSegmentElement(qreal minDistance)
|
|
{
|
|
if (visible() && autoplace() && parent()) {
|
|
setOffset(QPointF());
|
|
Segment* s = toSegment(parent());
|
|
Measure* m = s->measure();
|
|
int si = staffIdx();
|
|
Shape s1 = m->staffShape(si);
|
|
Shape s2 = shape().translated(s->pos() + pos());
|
|
|
|
if (isTextBase()) {
|
|
// look for collisions in next measures
|
|
qreal totalWidth = m->width();
|
|
for (Measure* nm = m->nextMeasure(); nm; nm = nm->nextMeasure()) {
|
|
if (s2.right() > totalWidth) {
|
|
s1.add(nm->staffShape(si).translated(QPointF(totalWidth, 0.0)));
|
|
totalWidth += nm->width();
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
// look for collisions in previous measures
|
|
totalWidth = 0;
|
|
for (Measure* pm = m->prevMeasure(); pm; pm = pm->prevMeasure()) {
|
|
if (s2.left() > totalWidth) {
|
|
s1.add(pm->staffShape(si).translated(QPointF(-(totalWidth + pm->width()), 0.0)));
|
|
totalWidth += pm->width();
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
// actually check for collisions
|
|
bool intersection = true;
|
|
qreal totalYOff = 0;
|
|
while (intersection) {
|
|
intersection = false;
|
|
for (ShapeElement se : s1) {
|
|
if (s2.intersects(se)){
|
|
intersection = true;
|
|
break;
|
|
}
|
|
}
|
|
if (! intersection)
|
|
break;
|
|
else {
|
|
qreal yd = -1;
|
|
if (placeAbove())
|
|
yd = 1;
|
|
totalYOff -= yd;
|
|
s2.translateY(-yd);
|
|
}
|
|
}
|
|
|
|
// margin of 5 to stop slight overlap, hardcoded for now
|
|
qreal textMarginBottom = 5;
|
|
s2.translateY(-textMarginBottom);
|
|
rUserYoffset() = totalYOff - textMarginBottom;
|
|
|
|
m->staffShape(si).add(s2);
|
|
|
|
Shape s3 = s2.translated(QPointF()); // make a copy of s2
|
|
totalWidth = m->width();
|
|
for (Measure* nm = m->nextMeasure(); nm; nm = nm->nextMeasure()) {
|
|
if (s2.right() > totalWidth) {
|
|
s3.translateX(-totalWidth);
|
|
nm->staffShape(staffIdx()).add(s3);
|
|
totalWidth += nm->width();
|
|
s3 = s2.translated(QPointF()); // reset translation
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
s3 = s2.translated(QPointF());
|
|
totalWidth = 0;
|
|
for (Measure* pm = m->prevMeasure(); pm; pm = pm->prevMeasure()) {
|
|
if (s2.left() > totalWidth) {
|
|
s3.translateX(totalWidth+pm->width());
|
|
pm->staffShape(staffIdx()).add(s3);
|
|
totalWidth += pm->width();
|
|
s3 = s2.translated(QPointF()); // reset translation
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
}
|
|
else {
|
|
// look for collisions in the next measure
|
|
// if necessary
|
|
bool cnm = (s2.right() > m->width()) && m->nextMeasure() && m->nextMeasure()->system() == m->system();
|
|
if (cnm) {
|
|
Measure* nm = m->nextMeasure();
|
|
s1.add(nm->staffShape(si).translated(QPointF(m->width(), 0.0)));
|
|
}
|
|
qreal d = placeAbove() ? s2.minVerticalDistance(s1) : s1.minVerticalDistance(s2);
|
|
if (d > -minDistance) {
|
|
qreal yd = d + minDistance;
|
|
if (placeAbove())
|
|
yd *= -1.0;
|
|
rUserYoffset() = yd;
|
|
s2.translateY(yd);
|
|
}
|
|
m->staffShape(si).add(s2);
|
|
if (cnm) {
|
|
Measure* nm = m->nextMeasure();
|
|
s2.translateX(-m->width());
|
|
nm->staffShape(staffIdx()).add(s2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//---------------------------------------------------------
|
|
// autoplaceSegmentElement
|
|
//---------------------------------------------------------
|
|
|
|
void Element::autoplaceSegmentElement(qreal minDistance)
|
|
{
|
|
if (visible() && autoplace() && parent()) {
|
|
Segment* s = toSegment(parent());
|
|
Measure* m = s->measure();
|
|
int si = staffIdx();
|
|
|
|
SysStaff* ss = m->system()->staff(si);
|
|
QRectF r = bbox().translated(m->pos() + s->pos() + pos());
|
|
|
|
SkylineLine sk(!placeAbove());
|
|
qreal d;
|
|
if (placeAbove()) {
|
|
sk.add(r.x(), r.bottom(), r.width());
|
|
d = sk.minDistance(ss->skyline().north());
|
|
}
|
|
else {
|
|
sk.add(r.x(), r.top(), r.width());
|
|
d = ss->skyline().south().minDistance(sk);
|
|
}
|
|
|
|
if (d > -minDistance) {
|
|
qreal yd = d + minDistance;
|
|
if (placeAbove())
|
|
yd *= -1.0;
|
|
rypos() += yd;
|
|
r.translate(QPointF(0.0, yd));
|
|
}
|
|
ss->skyline().add(r);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// autoplaceMeasureElement
|
|
//---------------------------------------------------------
|
|
|
|
void Element::autoplaceMeasureElement(qreal minDistance)
|
|
{
|
|
if (visible() && autoplace() && parent()) {
|
|
Measure* m = toMeasure(parent());
|
|
int si = staffIdx();
|
|
|
|
SysStaff* ss = m->system()->staff(si);
|
|
QRectF r = bbox().translated(m->pos() + pos());
|
|
|
|
SkylineLine sk(!placeAbove());
|
|
qreal d;
|
|
if (placeAbove()) {
|
|
sk.add(r.x(), r.bottom(), r.width());
|
|
d = sk.minDistance(ss->skyline().north());
|
|
}
|
|
else {
|
|
sk.add(r.x(), r.top(), r.width());
|
|
d = ss->skyline().south().minDistance(sk);
|
|
}
|
|
if (d > -minDistance) {
|
|
qreal yd = d + minDistance;
|
|
if (placeAbove())
|
|
yd *= -1.0;
|
|
rypos() += yd;
|
|
r.translate(QPointF(0.0, yd));
|
|
}
|
|
ss->skyline().add(r);
|
|
}
|
|
}
|
|
|
|
}
|